diff --git a/AndroidKernel.mk b/AndroidKernel.mk new file mode 100644 index 0000000000000000000000000000000000000000..3ee63c06058840614d3e33a82b6542ae2d5bdfc9 --- /dev/null +++ b/AndroidKernel.mk @@ -0,0 +1,91 @@ +#Android makefile to build kernel as a part of Android Build +PERL = perl + +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 +KERNEL_IMG=$(KERNEL_OUT)/arch/arm/boot/Image + +MSM_ARCH ?= $(shell $(PERL) -e 'while (<>) {$$a = $$1 if /CONFIG_ARCH_((?:MSM|QSD)[a-zA-Z0-9]+)=y/; $$r = $$1 if /CONFIG_MSM_SOC_REV_(?!NONE)(\w+)=y/;} print lc("$$a$$r\n");' $(KERNEL_CONFIG)) +KERNEL_USE_OF ?= $(shell $(PERL) -e '$$of = "n"; while (<>) { if (/CONFIG_USE_OF=y/) { $$of = "y"; break; } } print $$of;' kernel/arch/arm/configs/$(KERNEL_DEFCONFIG)) + +ifeq "$(KERNEL_USE_OF)" "y" +DTS_NAME ?= $(MSM_ARCH) +DTS_FILES = $(wildcard $(TOP)/kernel/arch/arm/boot/dts/$(DTS_NAME)*.dts) +DTS_FILE = $(lastword $(subst /, ,$(1))) +DTB_FILE = $(addprefix $(KERNEL_OUT)/arch/arm/boot/,$(patsubst %.dts,%.dtb,$(call DTS_FILE,$(1)))) +ZIMG_FILE = $(addprefix $(KERNEL_OUT)/arch/arm/boot/,$(patsubst %.dts,%-zImage,$(call DTS_FILE,$(1)))) +KERNEL_ZIMG = $(KERNEL_OUT)/arch/arm/boot/zImage +DTC = $(KERNEL_OUT)/scripts/dtc/dtc + +define append-dtb +mkdir -p $(KERNEL_OUT)/arch/arm/boot;\ +$(foreach d, $(DTS_FILES), \ + $(DTC) -p 1024 -O dtb -o $(call DTB_FILE,$(d)) $(d); \ + cat $(KERNEL_ZIMG) $(call DTB_FILE,$(d)) > $(call ZIMG_FILE,$(d));) +endef +else + +define append-dtb +endef +endif + +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) + $(append-dtb) + +$(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/ABI/testing/sysfs-bus-pil b/Documentation/ABI/testing/sysfs-bus-pil new file mode 100644 index 0000000000000000000000000000000000000000..797b2ea29b7fdf17b911b905f5277350c6fda74f --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-pil @@ -0,0 +1,18 @@ +What: /sys/bus/pil/devices/.../name +Date: March 2012 +Contact: Stephen Boyd +Description: + Shows the name of the peripheral used in pil_get(). + +What: /sys/bus/pil/devices/.../state +Date: March 2012 +Contact: Stephen Boyd +Description: + Shows the state state of a peripheral. Current states + supported are: + + OFFLINE - peripheral is offline + ONLINE - peripheral is online + + This file supports poll() to detect when a peripheral changes + state. diff --git a/Documentation/arm/msm/boot.txt b/Documentation/arm/msm/boot.txt new file mode 100644 index 0000000000000000000000000000000000000000..1a41cd53202072129f784443f3d551fedd49dbd3 --- /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 0000000000000000000000000000000000000000..b0d007ed4c858d3ec56c9cf87a41cc04f51b633f --- /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 67a81620adf67d7830a5037d21a47ad48e2acdfc..aaf0793be076689f2b3202f9827a86ce016c7eef 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 0000000000000000000000000000000000000000..c572312c43732ffb7d180d0b9e432ef66f78d60a --- /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 0000000000000000000000000000000000000000..3e7d1e9f460f197cb73c01d6d6cc1ae4b06812ce --- /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 0000000000000000000000000000000000000000..5b0b527a6aac162efc920d5b0d17f60990b33d48 --- /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 0000000000000000000000000000000000000000..9c9511fb030245dcc54edd91473e255bf0cb019e --- /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 0000000000000000000000000000000000000000..9f6827c38b520494a058f95f73c204b617f5b671 --- /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/arm/msm/tspp.txt b/Documentation/arm/msm/tspp.txt new file mode 100644 index 0000000000000000000000000000000000000000..a56f014b3ca0d1582bf17bb7a1ba862a3e37bb88 --- /dev/null +++ b/Documentation/arm/msm/tspp.txt @@ -0,0 +1,250 @@ +Introduction +============ +The TSPP (Transport stream packet processor) is a hardware accelerator +designed to process MPEG2 TS (transport stream) data. It is mainly used for +broadcast terrestrial services to off-load the host CPU from real-time +sensitive TS processing for high bandwidth streams (~20Mbps). Data is received +either via TSIF (Transport stream interface) or system memory. +The TSPP driver manages the TSIF 12Seg HW core, which consists of a TSPP, a +BAM (Bus access manager, used for DMA) and two TSIF inputs. +It is applicable to the TSIF 12Seg core found on select Qualcomm MSM chips. + +For more information on the TSIF interface, please refer to TSIF documentation +(Documentation/arm/msm/tsif.txt). +For more information on the BAM interface, please refer to SPS documentation +(Documentation/dma/sps/sps_architecture.txt). + +Hardware description +==================== +The TSPP unit expands the capabilities of the TSIF interface by adding MPEG2 +stream processing such as: + - Elementary Stream PID filtering and de-multiplexing + - Several TSP processing operation modes: + - TSP Raw processing (with or w/o suffix) + - TSP PES assembly + - Error handling + - Multi2 decryption + + +-------------+ +------+ + +>|ARM subsystem| |Memory| + | +-------------+ +------+ + | | | + I| | | + R| ========================== System bus + Q| | + s| | TSIF 12Seg + | +-----------------------+ + | | +---+ | + | | |BAM| | + | | +---+ | GPIOs + | | | +------+--|-------- TSIF 0 clk + | | +----+ |TSIF 0|--|-------- TSIF 0 data + +-| | |-----+------+--|-------- TSIF 0 en + | |TSPP| | + | | |-----+------+--|-------- TSIF 1 clk + | +----+ |TSIF 1|--|-------- TSIF 1 data + | +------+--|-------- TSIF 1 en + +-----------------------+ + + The TSPP unit receives an MPEG2 transport stream either via the two TSIF + interfaces, or via system memory. + It uses the BAM interface to transfer data to and from system memory. + The ARM subsystem receives interrupts on various error conditions from TSPP + and TSIF units, and on data transfer events from the BAM. + +Software Description +==================== +The TSPP driver is responsible for: + - TSPP/TSIF hardware configuration (using SPS driver to configure BAM + hardware) + - TSIF GPIO/Clocks configuration + - Memory resource management + - Handling TSIF/TSPP interrupts and BAM events + - TSPP Power management + +TSPP Terminology +---------------- +Device - the TSPP hardware instance + - the device contains many streams +Stream: All data that is received from a particular TS packet source + - For example, MPEG2 Transport Stream from TSIF0 + - A stream can consist of many channels +Channel: A channel routes part of a stream to a specified memory location + - For example, video and audio can be routed to different memory locations + using different channels + - Channel contents are defined by filters +Filter: Enables TS packet filtering and routing according to PID (packet ID) + - The decision regarding which PIDs in the stream will be routed to a + channel is done via filters + - Several filters can be registered to the same channel + - Filters can pass TS packets as-is (Raw mode) or assemble them into PES + packets (PES mode) + - Groups of contiguous PIDs can be filtered together (i.e. PSI/SI 0x0-0x2F, + 1Seg PMTs 0x1FC8-0x1FCF) + - Filters can be used to discard packets (e.g. eliminate processing of + unwanted channels) + +Init flow: +---------- +Driver registers BAM (via SPS driver) and initializes TSIF/TSPP hardware. + +Control path: +------------- +1. Client opens a TSPP stream and channel +2. Client notifies the driver of the source stream (TSIF0/TSIF1/Memory) + - TSPP driver allocates memory for the channel (circular buffer) + - As the amount of memory varies according to stream bandwidth (which the + driver doesn't know), a client can hint to the driver about the required + memory or stream bandwidth + - TSPP driver configures TSIF/BAM hardware according to selected stream +3. Client notifies the driver to filter a set of PIDs for the selected channel + - TSPP driver configures TSPP hardware filters accordingly +4. Client can now read data received from the selected channel + +Optional: Scrambling keys can be configured for a filter to decrypt Multi2 +encrypted streams. The scrambling keys are received encrypted in-stream every +~2 seconds. The client uses a smart card and master key to decrypt the +received scrambling keys. The master key remains inside the smart card and is +never revealed to software. + +Conceptual flow: +Client TSPP Driver SPS Driver Hardware + | | | | + | | Init TSIF/TSPP | | + | |---------------------------|------------------>| + | | Register BAM | | + | |-------------------------->| | + | | | | + | open(tspp.5) | | | + |-------------------------->| | | + | ioctl(tspp.5,SOURCE,TSIF0)| | | + |-------------------------->| | | + | | buff[0..N] = alloc() | | + | |----------> | | + | | Connect(TSPP,MEM) | | + | |-------------------------->| | + | | Transfer(buff[0..N]) | | + | |-------------------------->| | + | | | | + | | Configure TSIF0 | | + | |---------------------------|------------------>| + | | | | + | ioctl(tspp.5,FILTER,pid) | | | + |-------------------------->| | | + | | Configure TSPP filters | | + | |---------------------------|------------------>| + | | | | + | | | INT | + | | |<------------------| + | | notify(EOT,buff[x]) | | + | |<--------------------------| | + | | Transfer(buff[y]) | | + | |-------------------------->| | + | | | | + | read(tspp.5) | | | + |-------------------------->| | | + | | | | + + +Data path: +---------- +The TSPP driver is not involved in data transfer, other than managing the +circular buffer pointers when a transfer is complete. Data loss can occur if +a client cannot keep up with stream bandwidth. +The driver does not notify the application if there is data loss. It is +assumed that the application will read data when the data is ready, and when +the application is able. + +API +=== +int tspp_open_stream(tspp_device *dev, void *stream, void *channel, tspp_mode + mode); +int tspp_close_stream(tspp_device *dev, void *stream); +int tspp_open_channel(tspp_device *dev, int dest, int bufsize, void *channel); +int tspp_close_channel(tspp_device *dev, void *channel); +int tspp_register_filter(tspp_device *dev, void *channel, tspp_filter *filter); +int tspp_unregister_filter(tspp_device *dev, void *channel, int pid); + +Refer to chrdev implementation in kernel/drivers/misc/tspp.c for an example of +how to use this api. + +Each stream is represented by a chrdev device +16 available devices: /dev/tspp00 - /dev/tspp15 +Each device implements these functions: +open() +close() +read() +ioctl() + +ioctl() contains several sub-functions: +0: select source +1: add filter +2: remove filter +3: set decryption keys +4: set initial cbc values (IV) +5: set system keys +6: set buffer size + +You can refer to include/linux/tspp.h for the details on the structures +associated with these IOCTL calls. + +Design +====== +Design is based on the existing TSIF driver, with added control functionality +for the extra hardware features. + +Power Management +=============== +TSPP driver prevents MSM from sleeping while TSPP hardware is active. +To achieve this, the driver holds the wake lock. When no channels are +configured to receive data, the driver stops the clocks to save power. +It will register for suspend/resume in the future. + +SMP/multi-core +============== +Driver is fully SMP aware. + +Performance +=========== +Control flows are rare. +Data path only involves maintaining the circular buffer pointers. + +Interface +========= +Driver exposes a char device interface to user-space for each channel. +Control transactions are performed via ioctl on the channel. Data is read as +any regular char device (blocking and non-blocking). Please see sequence +under software description for more info. + +Debugfs may be used for debug purposes. Through debugfs, all of the TSIF +registers can be accessed under /sys/kernel/debug/tsif0 and tsif1. The +TSPP registers are found under /sys/kernel/debug/tspp0. If you cannot +see these devices, then either the driver has been built without debugfs +support (check the define TSPP_USE_DEBUGFS in tspp.c) or the debugfs has +not been mounted correctly (or mounted in a different location). + +Driver Parameters +================= +TSPP driver has target-specific parameters: +- Memory base addresses for TSIF/TSPP/BAM +- TSIF/TSPP/BAM IRQ numbers +- TSIF GPIO numbers + +Config Options +============== +To enable the driver, set CONFIG_TSPP (=y or =m) in the appropriate +config file for the platform. + +Dependencies +============ +The TSPP driver depends on the SPS driver to configure BAM hardware. + +User space utilities +==================== +The TSPP test suite can be found in: +vendor/qcom/proprietary/kernel-tests/tspp + +Known Issues +============ +Currently PES processing mode cannot be configured for streams in which the PES +length is 0. This is a HW limitation. diff --git a/Documentation/arm/msm/tz_log.txt b/Documentation/arm/msm/tz_log.txt new file mode 100644 index 0000000000000000000000000000000000000000..280865d9566fd43c2af3889d3c56bc2183a04fb9 --- /dev/null +++ b/Documentation/arm/msm/tz_log.txt @@ -0,0 +1,132 @@ +Introduction: +============= + +The tz_log driver is a platform device driver that exposes a debugfs +interface for accessing and displaying diagnostic information related +to secure code (Trustzone). + +The Secure code (Trustzone) will store the diagnostic data in 4KB of +IMEM. The address of this IMEM region varies from platform. The +diagnostic data encodes information related to secure code boot-up, +reset, interrupt and other attributes in a specific format as shown +below: + + ---------------------------- + | | + | General info | + | (Magic #, CPU cnt etc) | + | | + ---------------------------- + | | + | VMID info | + | | + ---------------------------- + | | + | Boot info (per CPU) | + | | + ---------------------------- + | | + | Reset info (per CPU) | + | | + ---------------------------- + | | + | Interrupt info (per CPU) | + | | + ---------------------------- + | | + | Data logged by TZ | + | | + ---------------------------- + +During the initialization of the driver module, this 4KB of IMEM +is remapped for access by kernel. Further more, an additonal 4KB +memory is allocated for storing the formatted data that will be +displayed by the debugfs interface. + +Once the device is booted up and HLOS is up, the standard debugfs +interface is used to read out and display this information that +was logged in by secure code in a specific format as shown below. + +Debugfs is typically mounted with a command like: + mount -t debugfs none /sys/kernel/debug +(Or an equivalent /etc/fstab line). + +Note that the debugfs API is exported GPL-only to modules. + +Software description +==================== + +The tz_log module is a Linux platform device driver with a debugfs +interface. The goal of this module is to provide a way to peek into +the Trustzone diagnostic information to help debug issues with +Trustzone. Although, this tz_log platform device driver will be +compiled into the kernel, the debugfs entries will not be exposed +unless Trustzone is supported by the platform. + + +On loading the tz_log driver, tzdbgfs_init() is invoked. tzdbgfs_init() +initializes the tz_log debugfs interface. The following is done in +this initialization call. + +(1) Create a directory "tzdbg", to hold a set of debugfs files + +(2) Create the following debugfs files in the "tzdbg" directory +- boot_info + Contains information on the warm boot jump address +- reset_info + Contains information on the cause of a CPU reset, number of + resets occurred on a specific CPU +- interrupt_info + Contains information on the number of IRQ and FIQ Interrupts + (with a brief description), interrupts fired and the number + of times it is fired on a specific CPU. +- general_info + Contains information on number of CPUs supported, magic number, + version number. +- vmid_info + Contains information on VMID supported, with a brief description +- log + Debug information (ASCII text) that is logged by Trustzone + +Following are the set of file operation defines and register +- read() +- open() + +(3) Remap the IMEM region where the secure code diagnostic information +is stored. + +(4) Allocate 4KB buffer for storing the formatted information +to be displayed + +When the tz_log driver is unloaded the tz_log debugfs entries are +explicitly removed. + + +Power Management +================ + +n/a + +Security +======== + +None + +Interface +========= + +This module will create debugfs files under sys/kernel/debug which +contains information that can be displayed by using the "cat" command. + + +Dependencies +============ + +This driver interacts with Trustzone operating environment, thus depends +on the TZBSP supported architecture. It also depends on debugfs. + + +To do +===== + +TBD diff --git a/Documentation/cgroups/cpuacct.txt b/Documentation/cgroups/cpuacct.txt index 9d73cc0cadb98a63818f532022b5c375285f1215..e21a932059fb0d692f434a8c022665425438283e 100644 --- a/Documentation/cgroups/cpuacct.txt +++ b/Documentation/cgroups/cpuacct.txt @@ -39,6 +39,13 @@ system: Time spent by tasks of the cgroup in kernel mode. user and system are in USER_HZ unit. +cpuacct.cpufreq file gives CPU time (in nanoseconds) spent at each CPU +frequency. Platform hooks must be implemented inorder to properly track +time at each CPU frequency. + +cpuacct.power file gives CPU power consumed (in milliWatt seconds). Platform +must provide and implement power callback functions. + cpuacct controller uses percpu_counter interface to collect user and system times. This has two side effects: diff --git a/Documentation/crypto/msm/qce.txt b/Documentation/crypto/msm/qce.txt new file mode 100644 index 0000000000000000000000000000000000000000..18435d170e19165356f3c04a71a2f5a866094180 --- /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 0000000000000000000000000000000000000000..e99f7d7ef6cf7013eb4f2cbfc3f0ca37ddf3cb99 --- /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 0000000000000000000000000000000000000000..fde69bbed7c08720923cc94dfb4ec6faa6b0a58f --- /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 0000000000000000000000000000000000000000..81aa1941e1570130f95aa1afca1066d574b34d07 --- /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 0000000000000000000000000000000000000000..22d5e35bc0fde2156353f502ce48743409eeb938 --- /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/devicetree/bindings/arm/arch_timer.txt b/Documentation/devicetree/bindings/arm/arch_timer.txt new file mode 100644 index 0000000000000000000000000000000000000000..eb3986eaf2b0c058a74a81d7ff72c0ef1c443ce7 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/arch_timer.txt @@ -0,0 +1,23 @@ +* ARM architected timer + +ARM Cortex-A7 and Cortex-A15 have a per-core architected timer, which +provides a per-cpu local timer. + +The timer is attached to a GIC to deliver its two per-processor +interrupts (one for the secure mode, one for the non-secure mode). + +** Timer node properties: + +- compatible : Should be "arm,armv7-timer" + +- interrupts : One or two interrupts for secure and non-secure mode + +- clock-frequency : The frequency of the main counter, in Hz. Optional. + +Example: + + timer { + compatible = "arm,armv7-timer""; + interrupts = <1 13 0xf08 1 14 0xf08>; + clock-frequency = <100000000>; + }; diff --git a/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt b/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt new file mode 100644 index 0000000000000000000000000000000000000000..35385d31630fd400cfd0a99852f1cc068d812302 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt @@ -0,0 +1,47 @@ +* Low Power Management Levels + +The application processor in MSM can do a variety of C-States for low power +management. These C-States are invoked by the CPUIdle framework when the core +becomes idle. But based on the time available until the next scheduled wakeup, +the system can do a combination of low power modes of different resources - +L2, XO, Vdd Dig and Vdd Mem. The combination is captured in the device tree as +lpm-level. The units for voltage are dependent on the PMIC used on the target +and are in uV. + +The required nodes for lpm-levels are: + +- compatible: "qcom,lpm-levels" +- reg: The numeric level id +- qcom,mode: The sleep mode of the processor +- qcom,xo: The state of XO clock. +- qcom,l2: The state of L2 cache. +- qcom,vdd-mem-upper-bound: The upper bound value of mem voltage in uV +- qcom,vdd-mem-lower-bound: The lower bound value of mem voltage in uV +- qcom,vdd-dig-upper-bound: The upper bound value of dig voltage in uV +- qcom,vdd-dig-lower-bound: The lower bound value of dig voltage in uV +- qcom,latency-us: The latency in handling the interrupt if this level was + chosen, in uSec +- qcom,ss-power: The steady state power expelled when the processor is in this + level in mWatts +- qcom,energy-overhead: The energy used up in entering and exiting this level + in mWatts.uSec +- qcom,time-overhead: The time spent in entering and exiting this level in uS + +Example: + +qcom,lpm-levels { + qcom,lpm-level@0 { + reg = <0>; + qcom,mode = <0>; /* MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT */ + qcom,xo = <1>; /* ON */ + qcom,l2 = <3>; /* ACTIVE */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <100>; + qcom,ss-power = <650>; + qcom,energy-overhead = <801>; + qcom,time-overhead = <200>; + }; +}; diff --git a/Documentation/devicetree/bindings/arm/msm/pm-boot.txt b/Documentation/devicetree/bindings/arm/msm/pm-boot.txt new file mode 100644 index 0000000000000000000000000000000000000000..cce9d0e9ba7fe08c2471598964b6de955eea3dc3 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/pm-boot.txt @@ -0,0 +1,40 @@ +* Power Management boot configuration (pm-boot) + +Low power management drivers need to specify the warmboot entry path for the +application processors to resume from sleep/suspend. The boot configuration +can vary if the core does/does not support a secure boot mode. The secure +boot configuration boots the core sets up the core sub system registers and +calls into the kernel entry point. In the absence of a secure boot mode, the +core when powered on from reset will need to be configured with the warmboot +entry pointer. The physical and the virtual address for the entry pointer +need to provided to the driver. + + +The device tree parameters for pm-boot are: + +Required parameters: + +- compatible: Must be "qcom,pm-boot" +- qcom,mode: The mode that the target will use for booting + MSM_PM_BOOT_CONFIG_TZ = 0, + MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS = 1, + MSM_PM_BOOT_CONFIG_RESET_VECTOR_VIRT = 2, + MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR = 3, + +Optional parameters (based on the mode chosen): + +- qcom,phy-addr: The physical address that will be used for warmboot entry if + the processor remap register can be programmed. + Needed for modes = { MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS, + MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR } +- qcom,virt-addr: The virtual address at which the processor start booting from + Needed for modes = { MSM_PM_BOOT_CONFIG_RESET_VECTOR_VIRT, + MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR } + + +Example: + + qcom,pm-boot { + compatible = "qcom,pm-boot"; + qcom,mode = <0>; /* MSM_PM_BOOT_CONFIG_TZ */ + }; diff --git a/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt b/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt new file mode 100644 index 0000000000000000000000000000000000000000..786635fa27f344d916704fd7977ac2ba18bce7a3 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt @@ -0,0 +1,153 @@ +Qualcomm RPM Regulators + +rpm-regulator-smd is a regulator driver which supports regulators inside of +PMICs which are controlled by the RPM processor. Communication with the RPM +processor takes place over SMD. + +Required structure: +- RPM regulators must be described in two levels of devices nodes. The first + level describes the interface with the RPM. The second level describes + properties of one regulator framework interface (of potentially many) to + the regulator. + +[First Level Nodes] + +Required properties: +- compatible: Must be "qcom,rpm-regulator-smd-resource" +- qcom,resource-name: Resource name string for this regulator to be used in RPM + transactions. Length is 4 characters max. +- qcom,resource-id: Resource instance ID for this regulator to be used in RPM + transactions. +- qcom,regulator-type: Type of this regulator. Supported values are: + 0 = LDO + 1 = SMPS + 2 = VS + 3 = NCP + +Optional properties: +- qcom,allow-atomic: Flag specifying if atomic access is allowed for this + regulator. Supported values are: + 0 or not present = mutex locks used + 1 = spinlocks used +- qcom,enable-time: Time in us to delay after enabling the regulator +- qcom,hpm-min-load: Load current in uA which corresponds to the minimum load + which requires the regulator to be in high power mode. + +[Second Level Nodes] + +Required properties: +- compatible: Must be "qcom,rpm-regulator-smd" +- regulator-name: A string used as a descriptive name for regulator outputs +- qcom,set: Specifies which sets that requests made with this + regulator interface should be sent to. Regulator + requests sent in the active set take effect immediately. + Requests sent in the sleep set take effect when the Apps + processor transitions into RPM assisted power collapse. + Supported values are: + 1 = Active set only + 2 = Sleep set only + 3 = Both active and sleep sets + + + +Optional properties: +- parent-supply: phandle to the parent supply/regulator node +- qcom,system-load: Load in uA present on regulator that is not + captured by any consumer request +The following properties specify initial values for parameters to be sent to the +RPM in regulator requests. +- qcom,init-enable: 0 = regulator disabled + 1 = regulator enabled +- qcom,init-voltage: Voltage in uV +- qcom,init-current: Current in mA +- qcom,init-ldo-mode: Operating mode to be used with LDO regulators + Supported values are: + 0 = mode determined by current requests + 1 = force HPM (NPM) +- qcom,init-smps-mode: Operating mode to be used with SMPS regulators + Supported values are: + 0 = auto; hardware determines mode + 1 = mode determined by current requests + 2 = force HPM (PWM) +- qcom,init-pin-ctrl-enable: Bit mask specifying which hardware pins should be + used to enable the regulator, if any; supported + bits are: + 0 = ignore all hardware enable signals + BIT(0) = follow HW0_EN signal + BIT(1) = follow HW1_EN signal + BIT(2) = follow HW2_EN signal + BIT(3) = follow HW3_EN signal +- qcom,init-pin-ctrl-mode: Bit mask specifying which hardware pins should be + used to force the regulator into high power + mode, if any. Supported bits are: + 0 = ignore all hardware enable signals + BIT(0) = follow HW0_EN signal + BIT(1) = follow HW1_EN signal + BIT(2) = follow HW2_EN signal + BIT(3) = follow HW3_EN signal + BIT(4) = follow PMIC awake state +- qcom,init-frequency: Switching frequency in MHz for SMPS regulators. + Supported values are: + 0 = Don't care about frequency used + 1 = 19.20 + 2 = 9.60 + 3 = 6.40 + 4 = 4.80 + 5 = 3.84 + 6 = 3.20 + 7 = 2.74 + 8 = 2.40 + 9 = 2.13 + 10 = 1.92 + 11 = 1.75 + 12 = 1.60 + 13 = 1.48 + 14 = 1.37 + 15 = 1.28 + 16 = 1.20 +- qcom,init-head-room: Voltage head room in uV required for the + regulator +- qcom,init-quiet-mode: Specify that quiet mode is needed for an SMPS + regulator in order to have lower output noise. + Supported values are: + 0 = No quiet mode + 1 = Quiet mode + 2 = Super quiet mode +- qcom,init-freq-reason: Consumer requiring specified frequency for an + SMPS regulator. Supported values are: + 0 = None + 1 = Bluetooth + 2 = GPS + 4 = WLAN + 8 = WAN + +All properties specified within the core regulator framework can also be used in +second level nodes. These bindings can be found in: +Documentation/devicetree/bindings/regulator/regulator.txt. + +Example: + +rpm-regulator-smpb1 { + qcom,resource-name = "smpb"; + qcom,resource-id = <1>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + pm8841_s1: regulator-s1 { + regulator-name = "8841_s1"; + qcom,set = <3>; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <1150000>; + qcom,init-voltage = <1150000>; + compatible = "qcom,rpm-regulator-smd"; + }; + pm8841_s1_ao: regulator-s1-ao { + regulator-name = "8841_s1_ao"; + qcom,set = <1>; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <1150000>; + compatible = "qcom,rpm-regulator-smd"; + }; +}; diff --git a/Documentation/devicetree/bindings/arm/msm/rpm-smd.txt b/Documentation/devicetree/bindings/arm/msm/rpm-smd.txt new file mode 100644 index 0000000000000000000000000000000000000000..8ebd3bac1109414c887d602bf15e4933d42739ec --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/rpm-smd.txt @@ -0,0 +1,30 @@ +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 hardware +accelerators. The RPM driver communicates with the hardware engine using +SMD. + +The devicetree representation of the SPM block should be: + +Required properties + +- compatible: "qcom,rpm-smd" +- rpm-channel-name: The string corresponding to the channel name of the + peripheral subsystem +- rpm-channel-type: The interal SMD edge for this subsystem found in + + +Example: + + qcom,rpm-smd { + compatible = "qcom,rpm-smd" + qcom,rpm-channel-name = "rpm_requests"; + qcom,rpm-channel-type = 15; /* SMD_APPS_RPM */ + } +} diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt new file mode 100644 index 0000000000000000000000000000000000000000..a33d8335e380ecbabe894f37ab410c0e7cded620 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt @@ -0,0 +1,66 @@ +* MSM Subsystem Power Manager (spm-v2) + +S4 generation of MSMs have SPM hardware blocks to control the Application +Processor Sub-System power. These SPM blocks run individual state machine +to determine what the core (L2 or Krait/Scorpion) would do when the WFI +instruction is executed by the core. The SAW hardware block handles SPM and +AVS functionality for the cores. + +The devicetree representation of the SPM block should be: + +Required properties + +- compatible: "qcom,spm-v2" +- reg: The physical address and the size of the SPM's memory mapped registers +- qcom, core-id: The core id the SPM block is attached to. + {0..n} for cores {0..n} + {0xffff} for L2 +- qcom,saw2-ver-reg: The location of the version register +- qcom,saw2-cfg: SAW2 configuration register +- qcom,saw2-avs-ctl: The AVS control register +- qcom,saw2-avs-hysterisis: The AVS hysterisis register to delay the AVS + controller requests +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM + sequence +- qcom,saw2-spm-ctl: The SPM control register +- qcom,saw2-vctl-timeout-us: The timeout value to wait for voltage to change + after sending the voltage command to the PMIC + +Optional properties + +- qcom,saw2-avs-limit: The AVS limit register +- qcom,saw2-avs-dly: The AVS delay register is used to specify the delay values + between AVS controller requests +- qcom,saw2-pmic-dly: The delay values for waiting on PMIC response +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS + index to send the PMIC data to +- qcom,saw2-vctl-port: The FTS port used for changing voltage +- qcom,saw2-phase-port: The FTS port used for changing the number of phases +- qcom,saw2-spm-cmd-wfi: The WFI command sequence +- qcom,saw2-spm-cmd-ret: The Retention command sequence +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence + +Example: + qcom,spm@f9089000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf9089000 0x1000>; + qcom,core-id = <0>; + qcom,saw2-ver-reg = <0xfd0>; + qcom,saw2-cfg = <0x1b>; + qcom,saw2-avs-ctl = <0>; + qcom,saw2-avs-hysteresis = <0>; + qcom,saw2-avs-limit = <0>; + qcom,saw2-avs-dly= <0>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,spm-cmd-wfi = [03 0b 0f]; + qcom,spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + qcom,spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92 + a0 b0 82 10 30 02 22 30 0f]; + }; + diff --git a/Documentation/devicetree/bindings/gpio/qpnp-gpio.txt b/Documentation/devicetree/bindings/gpio/qpnp-gpio.txt new file mode 100644 index 0000000000000000000000000000000000000000..7cab09b85f5b5584b3b10bbe18824615c9f4e97d --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/qpnp-gpio.txt @@ -0,0 +1,143 @@ +* msm-qpnp-gpio + +msm-qpnp-gpio is a GPIO chip driver for the MSM SPMI implementation. +It creates a spmi_device for every spmi-dev-container block of device_nodes. +These device_nodes contained within specify the PMIC GPIO number associated +with each GPIO chip. The driver will map these to Linux GPIO numbers. + +[PMIC GPIO Device Declarations] + +-Root Node- + +Required properties : + - spmi-dev-container : Used to specify the following child nodes as part of the + same SPMI device. + - gpio-controller : Specify as gpio-contoller. All child nodes will belong to this + gpio_chip. + - #gpio-cells: We encode a PMIC GPIO number and a 32-bit flag field to + specify the gpio configuration. This must be set to '2'. + - #address-cells: Specify one address field. This must be set to '1'. + - #size-cells: Specify one size-cell. This must be set to '1'. + - compatible = "qcom,qpnp-gpio" : Specify driver matching for this driver. + +-Child Nodes- + +Required properties : + - reg : Specify the spmi offset and size for this gpio device. + - qcom,gpio-num : Specify the PMIC GPIO number for this gpio device. + +Optional configuration properties : + - qcom,direction: indicates whether the gpio should be input, output, or + both. + QPNP_GPIO_DIR_IN = 0, + QPNP_GPIO_DIR_OUT = 1, + QPNP_GPIO_DIR_BOTH = 2 + + - qcom,output-type: indicates gpio should be configured as CMOS or open + drain. + QPNP_GPIO_OUT_BUF_CMOS = 0 + QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS = 1, + QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS = 2, + + - qcom,invert: Invert the signal of the gpio line - + QPNP_GPIO_INVERT_DISABLE = 0 + QPNP_GPIO_INVERT_ENABLE = 1 + + - qcom,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. + QPNP_GPIO_PULL_UP_30 = 0, + QPNP_GPIO_PULL_UP_1P5 = 1, + QPNP_GPIO_PULL_UP_31P5 = 2, + QPNP_GPIO_PULL_UP_1P5_30 = 3, + QPNP_GPIO_PULL_DN = 4, + QPNP_GPIO_PULL_NO = 5 + + - qcom,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. + QPNP_GPIO_VIN0 = 0, + QPNP_GPIO_VIN1 = 1, + QPNP_GPIO_VIN2 = 2, + QPNP_GPIO_VIN3 = 3, + QPNP_GPIO_VIN4 = 4, + QPNP_GPIO_VIN5 = 5, + QPNP_GPIO_VIN6 = 6, + QPNP_GPIO_VIN7 = 7 + + - qcom,out-strength: the amount of current supplied for an output gpio. + QPNP_GPIO_OUT_STRENGTH_LOW = 1 + QPNP_GPIO_OUT_STRENGTH_MED = 2, + QPNP_GPIO_OUT_STRENGTH_HIGH = 3, + + - qcom,source-sel: choose alternate function for the gpio. Certain gpios + can be paired (shorted) with each other. Some gpio pin + can act as alternate functions. + QPNP_GPIO_FUNC_NORMAL = 0, + QPNP_GPIO_FUNC_PAIRED = 1 + QPNP_GPIO_FUNC_1 = 2, + QPNP_GPIO_FUNC_2 = 3, + QPNP_GPIO_DTEST1 = 4, + QPNP_GPIO_DTEST2 = 5, + QPNP_GPIO_DTEST3 = 6, + QPNP_GPIO_DTEST4 = 7 + + - qcom,master-en: 1 = Enable features within the + GPIO block based on configurations. + 0 = Completely disable the GPIO + block and let the pin float with high impedance + regardless of other settings. + +*Note: If any of the configuration properties are not specified, then the + qpnp-gpio driver will not modify that respective configuration in + hardware. + +[PMIC GPIO clients] + +Required properties : + - gpios : Contains 3 fields of the form <&gpio_controller pmic_gpio_num flags> + +[Example] + +qpnp: qcom,spmi@fc4c0000 { + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <3>; + + qcom,pm8941@0 { + spmi-slave-container; + reg = <0x0>; + #address-cells = <1>; + #size-cells = <1>; + + pm8941_gpios: gpios { + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <1>; + + gpio@c000 { + reg = <0xc000 0x100>; + qcom,gpio-num = <62>; + }; + + gpio@c100 { + reg = <0xc100 0x100>; + qcom,gpio-num = <20>; + qcom,source_sel = <2>; + qcom,pull = <5>; + }; + }; + + qcom,testgpio@1000 { + compatible = "qcom,qpnp-testgpio"; + reg = <0x1000 0x1000>; + gpios = <&pm8941_gpios 62 0x0 &pm8941_gpios 20 0x1>; + }; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/i2c/i2c-qup.txt b/Documentation/devicetree/bindings/i2c/i2c-qup.txt new file mode 100644 index 0000000000000000000000000000000000000000..60de39653cc632a80dc788aa728d38d6603f4a9a --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-qup.txt @@ -0,0 +1,37 @@ +Qualcomm I2C controller + +Required properties: + + - reg : Offset and length of the register region(s) for the device + For GSBI based controller, GSBI and QUP regions are expected + For BLSP based controller, QUP region offset is expected + - reg-names : Register region name(s) referenced in reg above + BLSP based controller expects QUP region name ("qup_phys_addr") + GSBI controller expects QUP region name and GSBI region name + ("gsbi_qup_i2c_addr") + - compatible : should be "qcom,i2c-qup" + - cell-index : I2C bus number used for this controller + - interrupts : QUP core interrupt(s). Core may have 1 error interrupt and flags + for input/output service, or 3 separate interrupts for the 3 services + - interrupt-names: QUP core interrupt name(s) referenced in interrupts above + Expected interrupt resource name(s) are: "qup_err_irq", "qup_in_irq", + and "qup_out_irq" + - qcom,i2c-bus-freq : desired I2C bus clock frequency is Hz + +Optional property: + - qcom,i2c-src-freq : Frequency of the source clocking this bus in Hz. + Divider value is set based on soruce-frequency and + desired I2C bus frequency. If this value is not + provided, the source clock is assumed to be running + at 19.2 MHz. +Example: + i2c@f9966000 { + cell-index = <0>; + compatible = "qcom,i2c-qup"; + reg = <0xf9966000 0x1000>; + reg-names = "qup_phys_addr"; + interrupts = <0 104 0>; + interrupt-names = "qup_err_intr"; + qcom,i2c-bus-freq = <100000>; + qcom,i2c-src-freq = <24000000>; + }; diff --git a/Documentation/devicetree/bindings/iommu/msm_iommu.txt b/Documentation/devicetree/bindings/iommu/msm_iommu.txt new file mode 100644 index 0000000000000000000000000000000000000000..67933e77d181cc012e7667c1c9e69ca80ec6bf6d --- /dev/null +++ b/Documentation/devicetree/bindings/iommu/msm_iommu.txt @@ -0,0 +1,35 @@ +* Qualcomm MSM IOMMU + +Required properties: +- compatible : one of: + - "qcom,msm-smmu-v2" +- reg : offset and length of the register set for the device. + +- List of sub nodes, one for each of the translation context banks supported. + Each sub node has the following required properties: + + - reg : offset and length of the register set for the context bank. + - interrupts : should contain the context bank interrupt. + - qcom,iommu-ctx-sids : List of stream identifiers associated with this + translation context. + - qcom,iommu-ctx-name : Name of the context bank + +Example: + + qcom,iommu@fda64000 { + compatible = "qcom,msm-smmu-v2"; + reg = <0xfda64000 0x10000>; + + qcom,iommu-ctx@fda6c000 { + reg = <0xfda6c000 0x1000>; + interrupts = <0 70 0>; + qcom,iommu-ctx-sids = <0 2>; + qcom,iommu-ctx-name = "ctx_0"; + }; + qcom,iommu-ctx@fda6d000 { + reg = <0xfda6d000 0x1000>; + interrupts = <0 71 0>; + qcom,iommu-ctx-sids = <1>; + qcom,iommu-ctx-name = "ctx_1"; + }; + }; diff --git a/Documentation/devicetree/bindings/media/video/msm-vidc.txt b/Documentation/devicetree/bindings/media/video/msm-vidc.txt new file mode 100644 index 0000000000000000000000000000000000000000..11af7a976dd694b947c29f15ec4823af884d3f53 --- /dev/null +++ b/Documentation/devicetree/bindings/media/video/msm-vidc.txt @@ -0,0 +1,15 @@ +* Qualcomm MSM VIDC + +Required properties: +- compatible : one of: + - "qcom,msm-vidc" +- reg : offset and length of the register set for the device. +- interrupts : should contain the vidc interrupt. + +Example: + + qcom,vidc@fdc00000 { + compatible = "qcom,msm-vidc"; + reg = <0xfdc00000 0xff000>; + interrupts = <0 44 0>; + }; diff --git a/Documentation/devicetree/bindings/mmc/msm_sdcc.txt b/Documentation/devicetree/bindings/mmc/msm_sdcc.txt new file mode 100644 index 0000000000000000000000000000000000000000..0c1762d81ce8c433643c2335cdf998b0afab57ae --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/msm_sdcc.txt @@ -0,0 +1,41 @@ +Qualcomm Secure Digital Card Controller (SDCC) + +Secure Digital Card Controller provides host interface to +SD/MMC/SDIO cards. + +Required properties: + - compatible : should be "qcom,msm-sdcc" + - reg : should contain SDCC, BAM register map. + - interrupts : should contain SDCC core interrupt. + - qcom,sdcc-clk-rates : specifies supported SDCC clock frequencies, Units - Hz. + - qcom,sdcc-sup-voltages: specifies supported voltage ranges for card. Should always be + specified in pairs (min, max), Units - mV. + +Optional Properties: + - cell-index - defines slot ID. + - qcom,sdcc-bus-width - defines the bus I/O width that controller supports. + - qcom,sdcc-wp-gpio - defines write protect switch gpio. + - qcom,sdcc-wp-polarity - specifies the polarity of wp switch. + - qcom,sdcc-cd-gpio - defines card detect gpio number. + - qcom,sdcc-cd-polarity - specifies the polarity of cd gpio. + - qcom,sdcc-nonremovable - specifies whether the card in slot is + hot pluggable or hard wired. + - qcom,sdcc-disable_cmd23 - disable sending CMD23 to card when controller can't support it. + - qcom,sdcc-hs200 - enable eMMC4.5 HS200 bus speed mode + +Example: + + qcom,sdcc@f9600000 { + /* SDC1 used as eMMC slot */ + cell-index = <1>; + compatible = "qcom,msm-sdcc"; + reg = <0xf9600000 0x800 // SDCC register interface + 0xf9600800 0x1800 // DML register interface + 0xf9602000 0x2000> // BAM register interface + + interrupts = <123>; + qcom,sdcc-clk-rates = <400000 24000000 48000000>; + qcom,sdcc-sup-voltages = <2700 3300>; + qcom,sdcc-bus-width = <8>; //8-bit wide + qcom,sdcc-nonremovable; +}; diff --git a/Documentation/devicetree/bindings/pil/pil-pronto.txt b/Documentation/devicetree/bindings/pil/pil-pronto.txt new file mode 100644 index 0000000000000000000000000000000000000000..6193b681b89eb042bbf7dcd011ce48cfc612bcf4 --- /dev/null +++ b/Documentation/devicetree/bindings/pil/pil-pronto.txt @@ -0,0 +1,25 @@ +* Qualcomm WCNSS Pronto Peripheral Image Loader + +pil-pronto is a peripheral image loading (PIL) driver. It is used for loading +Pronto firmware images for wireless connectivity subsystems into memory and +preparing the subsystem's processor to execute code. It is also used for +shutting down the processor when it's not needed. + +Required properties: +- compatible: "pil-pronto" +- reg: offset and length of the register set for the device. The first pair + corresponds to PRONTO_PMU, the second pair corresponds to CLK_CTL_WCNSS + the third pair corresponds to WCNSS_HALTREQ. +- vdd_pronto_pll-supply: regulator to supply pronto pll. +- qcom,firmware-name: Base name of the firmware image. Ex. "wcnss" + +Example: + qcom,pronto@fb21b000 { + compatible = "qcom,pil-pronto"; + reg = <0xfb21b000 0x3000>, + <0xfc401700 0x4>, + <0xfd485300 0xc>; + vdd_pronto_pll-supply = <&pm8941_l12>; + + qcom,firmware-name = "wcnss"; + }; diff --git a/Documentation/devicetree/bindings/pil/pil-q6v5-lpass.txt b/Documentation/devicetree/bindings/pil/pil-q6v5-lpass.txt new file mode 100644 index 0000000000000000000000000000000000000000..308f99233eb775256f0093a7955097a4055b3a30 --- /dev/null +++ b/Documentation/devicetree/bindings/pil/pil-q6v5-lpass.txt @@ -0,0 +1,22 @@ +Qualcomm LPASS QDSP6v5 Peripheral Image Loader + +pil-qdsp6v5-lpass is a peripheral image loader (PIL) driver. It is used for +loading QDSP6v5 (Hexagon) firmware images for Low Power Audio Subsystems +into memory and preparing the subsystem's processor to execute code. It's +also responsible for shutting down the processor when it's not needed. + +Required properties: +- compatible: Must be "qcom,pil-q6v5-lpass" +- reg: Two pairs of physical base addresses and region sizes + of memory mapped registers. The first region corresponds + to QDSP6SS_PUB, and the second to LPASS_HALTREQ. +- qcom,firmware-name: Base name of the firmware image. Ex. "lpass" + +Example: + qcom,lpass@fe200000 { + compatible = "qcom,pil-q6v5-lpass"; + reg = <0xfe200000 0x00100>, + <0xfd485100 0x00010>; + + qcom,firmware-name = "lpass"; + }; diff --git a/Documentation/devicetree/bindings/prng/msm-rng.txt b/Documentation/devicetree/bindings/prng/msm-rng.txt new file mode 100644 index 0000000000000000000000000000000000000000..3d558089ec900743fbc3384b82aeeb1f0a9fd52f --- /dev/null +++ b/Documentation/devicetree/bindings/prng/msm-rng.txt @@ -0,0 +1,12 @@ +* RNG (Random Number Generator) + +Required properties: +- compatible : Should be "qcom,msm-rng" +- reg : Offset and length of the register set for the device + +Example: + + qcom,msm-rng@f9bff000 { + compatible = "qcom,msm-rng"; + reg = <0xf9bff000 0x200>; + }; diff --git a/Documentation/devicetree/bindings/regulator/gdsc-regulator.txt b/Documentation/devicetree/bindings/regulator/gdsc-regulator.txt new file mode 100644 index 0000000000000000000000000000000000000000..cd7bdce29f3a389a05794d21c11d1e4d769e3946 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/gdsc-regulator.txt @@ -0,0 +1,21 @@ +Qualcomm Global Distributed Switch Controller (GDSC) Regulator Driver + +The GDSC driver, implemented under the regulator framework, is responsible for +safely collapsing and restoring power to peripheral cores on chipsets like +msm-copper for power savings. + +Required properties: + - compatible: Must be "qcom,gdsc" + - regulator-name: A string used as a descriptive name for regulator outputs + - reg: The address of the GDSCR register + +Optional properties: + - parent-supply: phandle to the parent supply/regulator node + +Example: + gdsc_oxili_gx: qcom,gdsc@fd8c4024 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_oxili_gx"; + parent-supply = <&pm8841_s4>; + reg = <0xfd8c4024 0x4>; + }; diff --git a/Documentation/devicetree/bindings/regulator/qpnp-regulator.txt b/Documentation/devicetree/bindings/regulator/qpnp-regulator.txt new file mode 100644 index 0000000000000000000000000000000000000000..c9bc284cbf87a2a7d79ee8dabfe4ddef8b137c85 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/qpnp-regulator.txt @@ -0,0 +1,134 @@ +Qualcomm QPNP Regulators + +qpnp-regulator is a regulator driver which supports regulators inside of PMICs +that utilize the MSM SPMI implementation. + +Required properties: +- compatible: Must be "qcom,qpnp-regulator" +- reg: Specifies the SPMI address and size for this regulator device + Note, this is the only property which can be used within a + subnode of a node which has specified spmi-dev-container. +- regulator-name: A string used as a descriptive name for regulator outputs +- parent-supply: phandle to the parent supply/regulator node + +Required structure: +- A qcom,qpnp-regulator node must be a child of an SPMI node that has specified + the spmi-slave-container property + +Optional properties: +- qcom,system-load: Load in uA present on regulator that is not + captured by any consumer request +- qcom,enable-time: Time in us to delay after enabling the regulator +- qcom,ocp-enable-time: Time to delay in us between enabling a switch and + subsequently enabling over current protection + (OCP) for the switch +- qcom,auto-mode-enable: 1 = Enable automatic hardware selection of + regulator mode (HPM vs LPM); not available on + boost type regulators + 0 = Disable auto mode selection +- qcom,bypass-mode-enable: 1 = Enable bypass mode for an LDO type regulator + so that it acts like a switch and simply outputs + its input voltage + 0 = Do not enable bypass mode +- qcom,ocp-enable: 1 = Enable over current protection (OCP) for + voltage switch type regulators so that they + latch off automatically when over current is + detected + 0 = Disable OCP +- qcom,pull-down-enable: 1 = Enable output pull down resistor when the + regulator is disabled + 0 = Disable pull down resistor +- qcom,soft-start-enable: 1 = Enable soft start for LDO and voltage switch + type regulators so that output voltage slowly + ramps up when the regulator is enabled + 0 = Disable soft start +- qcom,boost-current-limit: This property sets the current limit of boost + type regulators; supported values are: + 0 = 300 mA + 1 = 600 mA + 2 = 900 mA + 3 = 1200 mA + 4 = 1500 mA + 5 = 1800 mA + 6 = 2100 mA + 7 = 2400 mA +- qcom,pin-ctrl-enable: Bit mask specifying which hardware pins should be + used to enable the regulator, if any; supported + bits are: + 0 = ignore all hardware enable signals + BIT(0) = follow HW0_EN signal + BIT(1) = follow HW1_EN signal + BIT(2) = follow HW2_EN signal + BIT(3) = follow HW3_EN signal +- qcom,pin-ctrl-hpm: Bit mask specifying which hardware pins should be + used to force the regulator into high power + mode, if any; supported bits are: + 0 = ignore all hardware enable signals + BIT(0) = follow HW0_EN signal + BIT(1) = follow HW1_EN signal + BIT(2) = follow HW2_EN signal + BIT(3) = follow HW3_EN signal + BIT(4) = follow PMIC awake state +- qcom,vs-soft-start-strength: This property sets the soft start strength for + voltage switch type regulators; supported values + are: + 0 = 0.05 uA + 1 = 0.25 uA + 2 = 0.55 uA + 3 = 0.75 uA + +- spmi-dev-container: This specifies that all the device nodes specified + within this node should have their resources coalesced into a single + spmi_device. This is used to specify all SPMI peripherals that + logically make up a single regulator device. + +Note, if a given optional qcom,* binding is not present, then the qpnp-regulator +driver will leave that feature in the default hardware state. + +All properties specified within the core regulator framework can also be used. +These bindings can be found in regulator.txt. + +Example: + qcom,spmi@fc4c0000 { + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <3>; + + qcom,pm8941@1 { + spmi-slave-container; + reg = <0x1>; + #address-cells = <1>; + #size-cells = <1>; + + regulator@1400 { + regulator-name = "8941_s1"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1400 0x300>; + regulator-min-microvolt = <1300000>; + regulator-max-microvolt = <1400000>; + + qcom,ctl@1400 { + reg = <0x1400 0x100>; + }; + qcom,ps@1500 { + reg = <0x1500 0x100>; + }; + qcom,freq@1600 { + reg = <0x1600 0x100>; + }; + }; + + regulator@4000 { + regulator-name = "8941_l1"; + reg = <0x4000 0x100>; + compatible = "qcom,qpnp-regulator"; + regulator-min-microvolt = <1225000>; + regulator-max-microvolt = <1300000>; + qcom,pull-down-enable = <1>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/slimbus/slim-msm-ctrl.txt b/Documentation/devicetree/bindings/slimbus/slim-msm-ctrl.txt new file mode 100644 index 0000000000000000000000000000000000000000..cf727d9fd44283d56d7e2c449424e4a2a918eb8a --- /dev/null +++ b/Documentation/devicetree/bindings/slimbus/slim-msm-ctrl.txt @@ -0,0 +1,38 @@ +Qualcomm SLIMBUS controller + +Required properties: + + - reg : Offset and length of the register region(s) for the device + - reg-names : Register region name(s) referenced in reg above + Required register resource entries are: + "slimbus_physical": Physical adderss of controller register blocks + "slimbus_bam_physical": Physical address of Bus Access Module (BAM) + for this controller + - compatible : should be "qcom,slim-msm" + - cell-index : SLIMBUS number used for this controller + - interrupts : Interrupt numbers used by this controller + - interrupt-names : Required interrupt resource entries are: + "slimbus_irq" : Interrupt for SLIMBUS core + "slimbus_bam_irq" : Interrupt for controller core's BAM + +Optional property: + - reg entry for slew rate : If slew rate control register is provided, this + entry should be used. + - reg-name for slew rate: "slimbus_slew_reg" + - qcom,min-clk-gear : Minimum clock gear at which this controller can be run + (range: 1-10) + Default value will be 1 if this entry is not specified + - qcom,max-clk-gear: Maximum clock gear at which this controller can be run + (range: 1-10) + Default value will be 10 if this entry is not specified +Example: + slim@fe12f000 { + cell-index = <1>; + compatible = "qcom,slim-msm"; + reg = <0xfe12f000 0x35000>, + <0xfe104000 0x20000>; + reg-names = "slimbus_physical", "slimbus_bam_physical"; + interrupts = <0 163 0 0 164 0>; + interrupt-names = "slimbus_irq", "slimbus_bam_irq"; + qcom,min-clk-gear = <10>; + }; diff --git a/Documentation/devicetree/bindings/spi/spi_qsd.txt b/Documentation/devicetree/bindings/spi/spi_qsd.txt new file mode 100644 index 0000000000000000000000000000000000000000..939f77b7237fcf21ed913d00bdd8700abd9337a5 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi_qsd.txt @@ -0,0 +1,46 @@ +Qualcomm Serial Peripheral Interface (SPI) + +Required properties: +- compatible : should be "qcom,spi-qup-v2". +- reg : offset and length of the QUP register map. +- interrupts : should contain the QUP core interrupt. +- spi-max-frequency : specifies maximum SPI clock frequency, Units - Hz. + +Optional properties: +- gpios : specifies the gpio pins to be used for SPI CLK, MISO, MOSI in + that order. +- cs-gpios : specifies the gpio pins to be used for chipselects. + +SPI slave nodes must be children of the SPI master node and contain +the following properties. +- reg : (required) chip select address of device. +- compatible : (required) name of SPI device following generic names + recommended practice +- spi-max-frequency : (required) Maximum SPI clocking speed of device in Hz +- interrupts : (recommended) should contain the SPI slave interrupt number + encoded depending on the type of the interrupt controller. +- interrupt-parent : (recommended) the phandle for the interrupt controller + that services interrupts for this device. +- spi-cpol : (optional) Empty property indicating device requires inverse + clock polarity (CPOL) mode +- spi-cpha : (optional) Empty property indicating device requires shifted + clock phase (CPHA) mode +- spi-cs-high : (optional) Empty property indicating device requires + chip select active high + +Example: + spi@f9924000 { + compatible = "qcom,spi-qup-v2"; + reg = <0xf9924000 0x1000>; + interrupts = <0 96 0>; + spi-max-frequency = <24000000>; + #address-cells = <1>; + #size-cells = <0>; + + device@0 { + compatible = "spidev"; + reg = <0>; + spi-max-frequency = <5000000>; + }; + }; + diff --git a/Documentation/devicetree/bindings/spmi/msm-spmi.txt b/Documentation/devicetree/bindings/spmi/msm-spmi.txt new file mode 100644 index 0000000000000000000000000000000000000000..d50037feceb06423636cedfb1a5f68e5a6cd3eb2 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/msm-spmi.txt @@ -0,0 +1,160 @@ +* SPMI + +The SPMI Device Tree support interprets up to three levels of Device Tree +topology. The first level is required and specifies only a slave address. +The second level is optional and allows for the specification of different +device nodes within the same 16-bit address space underneath a particular +SPMI slave ID. Within the second level, any number of address ranges can be +associated with a particular device within that 16-bit range. An additional +flag allows for the possiblity to specify that all device nodes should +have their resources dedicated to only one spmi_device. This flag can +be specified at the second level, or an optional third level. By default +without this flag, one spmi_device is created for each device_node. + +[Root Node] + +Recommended properties : + - interrupt-controller : Used to specify the root node as the + interrupt controller for SPMI devices. + - #interrupt-cells : The number of cells used to express one interrupt. + +Notes : + - It is considered an error to include either spmi-container-dev or + spmi-slave-dev in the Root Node. + +[First Level Nodes] + +Required properites : + + - reg: SPMI Slave ID (0-15) with no size cell. + - compatible : "qcom," prefixed string to match against the driver. + +Recommended properties : + + - interrupts : where a is the slave ID, b is the peripheral ID, + c is the device interrupt number (0-7). Each device supports any arbitrary + number of interrupts. + - interrupt-parent : the phandle for the interrupt controller that + services interrupts for this device. + +[Second Level Nodes] + +Required properties : + - spmi-slave-container: Used by the parser to understand that this is the + second level of the tree that includes device nodes associated with the + same slave_id. + - reg: where a is < 65536 and b is a size. Each device supports an + arbitrary number of address ranges. + - compatible : "qcom," prefixed string to match against the driver. + +Recommended properties : + + - interrupts : where a is the slave ID, b is the peripheral ID, + c is the device interrupt number (0-7). Each device supports any arbitrary + number of interrupts. + - interrupt-parent : the phandle for the interrupt controller that + services interrupts for this device. + +Optional properties : + + - spmi-dev-container: This specifies that all the device nodes specified for + this slave_id should have their resources coalesced into only one + spmi_device. + +[Third Level Nodes] + +Required properties : + + - spmi-dev-container: This specifies that all the device nodes specified for + this slave_id should have their resources coalesced into only one + spmi_device. + - reg: where a is < 65536 and b is a size. Each device supports an + arbitrary number of address ranges. + - compatible : "qcom," prefixed string to match against the driver. + +Recommended properties : + + - interrupts : where a is the slave ID, b is the peripheral ID, + c is the device interrupt number (0-7). Each device supports any arbitrary + number of interrupts. + - interrupt-parent : the phandle for the interrupt controller that + services interrupts for this device. + +Notes : + - It is considered an error to include spmi-slave-dev at this level. + +[Example] + +/ { + qpnp: qcom,spmi@fc4c0000 { + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <3>; + + testint@f { + interrupt-parent = <&qpnp>; + compatible = "qcom,qpnp-testint"; + reg = <0xf>; + interrupts = <0x3 0x15 0x0 0x3 0x15 0x02 0x1 0x47 0x0>; + + }; + + pm8941@0 { + spmi-slave-container; + reg = <0x0>; + #address-cells = <1>; + #size-cells = <1>; + + pm8941_gpios: gpios { + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + gpio-controller; + #gpio-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + + pm8941_gpio1@0xc000 { + compatible = "qcom,qpnp-gpio"; + reg = <0xc000 0x100>; + qcom,qpnp_gpio = <1>; + interrupt-parent = <&qpnp>; + interrupts = <0x3 0x15 0x02 0x1 0x47 0x0>; + }; + + pm8941_gpio2@0xc100 { + compatible = "qcom,qpnp-gpio"; + reg = <0xc100 0x100>; + qcom,qpnp_gpio = <2>; + interrupt-parent = <&qpnp>; + interrupts = <0x3 0x15 0x0>; + }; + }; + + testgpio@0x1000 { + compatible = "qcom,qpnp-testgpio"; + reg = <0x1000 0x1000>; + qpnp-gpios = <&pm8941_gpios 0x0>; + }; + }; + pm8841@2 { + spmi-slave-container; + reg = <0x2>; + #address-cells = <1>; + #size-cells = <1>; + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + + pm8841_gpio1@0xc000 { + reg = <0xc000 0x100>; + qcom,qpnp_gpio = <1>; + }; + + pm8841_gpio2@0xc100 { + reg = <0xc100 0x100>; + qcom,qpnp_gpio = <2>; + }; + }; + + }; +}; diff --git a/Documentation/devicetree/bindings/spmi/spmi-pmic-arb.txt b/Documentation/devicetree/bindings/spmi/spmi-pmic-arb.txt new file mode 100644 index 0000000000000000000000000000000000000000..b5ca08e83aa0a12da6e36bd71d146a3d4bb10407 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/spmi-pmic-arb.txt @@ -0,0 +1,41 @@ +Qualcomm SPMI Controller (PMIC Arbiter) + +Required properties: +- cell-index : the bus identifier. +- compatible : should be "qcom,spmi-pmic-arb". +- reg : offset and length of the PMIC Arbiter Core register map. +- reg : offset and length of the PMIC Arbiter Interrupt controller register map. +- interrupts : the PMIC Arbiter interrupt. +- qcom,pmic-arb-ee : the execution environment (EE) identifier. +- qcom,pmic-arb-channel : the assigned channel number for channel registers. +- qcom,pmic-arb-ppid-map : an array used to map a 12-bit PPID to 8-bit APID. + +Peripherals on the SPMI bus are identified with a 12-bit identifier (PPID) +which is composed of a 4-bit slave address and an 8-bit peripheral identifier. +The PMIC Arbiter hardware uses an 8-bit APID (Arbiter Peripheral Identifier) +internally. Software needs the mapping between the APID and the PPID. +Up to a maximum of 256 peripherals are supported and the mapping is target +specific. + +Data format of pmic-arb-ppid-map: +<0x13100001> +value is 32 bit. +MSB 12 bits are the PPID +12 bits padding +LSB 8 bit are the APID + +Example: + + qcom,spmi@fc4c0000 { + cell-index = <0>; + compatible = "qcom,spmi-pmic-arb"; + reg = <0xfc4cf000 0x1000>, + <0Xfc4cb000 0x1000>; + interrupts = <0>; + qcom,pmic-arb-ee = <0>; + qcom,pmic-arb-channel = <0>; + qcom,pmic-arb-ppid-map = <0x13000000>, /* PPID 0x130, APID 0 */ + <0x13100001>, /* PPID 0x131, APID 1 */ + }; + + diff --git a/Documentation/devicetree/bindings/tty/serial/msm_serial.txt b/Documentation/devicetree/bindings/tty/serial/msm_serial.txt index aef383eb8876a470879547a31422fffc2b53859b..3b0426ba0d040c6058f838cb0378924fb0c47719 100644 --- a/Documentation/devicetree/bindings/tty/serial/msm_serial.txt +++ b/Documentation/devicetree/bindings/tty/serial/msm_serial.txt @@ -25,3 +25,19 @@ Example: <0x19c00000 0x1000>; interrupts = <195>; }; + +* Qualcomm MSM HSUART + +Required properties: +- compatible : one of: + - "qcom,msm-lsuart-v14" +- reg : offset and length of the register set for the device. +- interrupts : should contain the uart interrupt. + +Example: + + serial@19c400000 { + compatible = "qcom,msm-lsuart-v14" + reg = <0x19c40000 0x1000">; + interrupts = <195>; + }; diff --git a/Documentation/devicetree/bindings/usb/msm-hsusb.txt b/Documentation/devicetree/bindings/usb/msm-hsusb.txt new file mode 100644 index 0000000000000000000000000000000000000000..95ddf34f08f8c7ac82eb9109046a69f381a5ab11 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/msm-hsusb.txt @@ -0,0 +1,51 @@ +MSM SoC HSUSB controllers + +OTG: + +Required properties : +- compatible : should be "qcom,hsusb-otg" +- regs : offset and length of the register set in the memory map +- interrupts: IRQ line +- qcom,hsusb-otg-phy-type: PHY type can be one of + 1 - Chipidea 45nm PHY + 2 - Synopsis 28nm PHY +- qcom,hsusb-otg-mode: Operational mode. Can be one of + 1 - Peripheral only mode + 2 - Host only mode + 3 - OTG mode + Based on the mode, OTG driver registers platform devices for + gadget and host. +- qcom,hsusb-otg-control: OTG control (VBUS and ID notifications) + can be one of + 1 - PHY control + 2 - PMIC control + 3 - User control (via debugfs) + +Optional properties : +- qcom,hsusb-otg-default-mode: The default USB mode after boot-up. + Applicable only when OTG is controlled by user. Can be one of + 0 - None. Low power mode + 1 - Peripheral + 2 - Host +- qcom,hsusb-otg-phy-init-seq: PHY configuration sequence. val, reg pairs + terminate with -1 +- qcom,hsusb-otg-power-budget: VBUS power budget in mA + 0 will be treated as 500mA +- qcom,hsusb-otg-pclk-src-name: The source of pclk +- qcom,hsusb-otg-pmic-id-irq: ID, routed to PMIC IRQ number + +Example HSUSB OTG controller device node : + usb@f9690000 { + compatible = "qcom,hsusb-otg"; + reg = <0xf9690000 0x400>; + interrupts = <134>; + + qcom,hsusb-otg-phy-type = <2>; + qcom,hsusb-otg-mode = <1>; + qcom,hsusb-otg-otg-control = <1>; + qcom,hsusb-otg-default-mode = <2>; + qcom,hsusb-otg-phy-init-seq = <0x01 0x90 0xffffffff>; + qcom,hsusb-otg-power-budget = <500>; + qcom,hsusb-otg-pclk-src-name = "dfab_usb_clk"; + qcom,hsusb-otg-pmic-id-irq = <47> + }; diff --git a/Documentation/devicetree/bindings/usb/msm-ssusb.txt b/Documentation/devicetree/bindings/usb/msm-ssusb.txt new file mode 100644 index 0000000000000000000000000000000000000000..40b3bc38473424048cfafd7abac96692aeb3ec8c --- /dev/null +++ b/Documentation/devicetree/bindings/usb/msm-ssusb.txt @@ -0,0 +1,18 @@ +MSM SuperSpeed USB3.0 SoC controller + +Required properties : +- compatible : should be "qcom,dwc-usb3-msm" +- reg : offset and length of the register set in the memory map +- interrupts: IRQ line +- qcom,dwc-usb3-msm-dbm-eps: Number of endpoints avaliable for + the DBM (Device Bus Manager). The DBM is HW unit which is part of + the MSM USB3.0 core (which also includes the Synopsys DesignWare + USB3.0 controller) + +Example MSM USB3.0 controller device node : + usb@f9200000 { + compatible = "qcom,dwc-usb3-msm"; + reg = <0xf9200000 0xCCFF>; + interrupts = <0 131 0> + qcom,dwc-usb3-msm-dbm-eps = <4> + }; diff --git a/Documentation/dvb/qcom-mpq.txt b/Documentation/dvb/qcom-mpq.txt new file mode 100644 index 0000000000000000000000000000000000000000..28f5d397c703257f3902c7009a5439fea1a91c37 --- /dev/null +++ b/Documentation/dvb/qcom-mpq.txt @@ -0,0 +1,409 @@ +Introduction +============ +MPQ DVB Adapter implements Digital Video Broadcasting devices according +to LinuxTV (linuxtv.org) defined API and infrastructure. + +The implemented devices are dvb/demux devices, dvb/dvr devices and +dvb/video devices. + +These devices are used in Qualcomm's MPQ chipsets that support +broadcast applications. + +dvb/demux is responsible to receive a digital stream broadcasted over +the air from a hardware unit (TSPP - Transport stream packet processor, +or TSIF - Transport Stream Interface) and separates the stream into its +various sub-streams such as video, audio and auxiliary data. +The separation operation is named demuxing. + +dvb/dvr is used in conjunction with dvb/demux to re-play a digital +stream from memory or to record stream to memory. + +dvb/video is used to handle the video decoding, it receives compressed +video from dvb/demux through a stream-buffer interface and interacts +with the existing HW video driver to perform the decoding. + +For more information on the TSIF interface, please refer to TSIF +documentation under "Documentation/arm/msm/tsif.txt". +For more information on the TSPP interface, please refer to TSPP +documentation under "Documentation/arm/msm/tspp.txt". +For more information on DVB-API definition, please refer dvb +documentation under "Documentation/dvb/readme.txt". + +Hardware description +==================== +dvb/demux, dvb/dvr and dvb/video do not interact with a hardware directly; +The implementation of these drivers is done using the kernel API of TSPP, +TSIF and video drivers. + +Software description +==================== + +Terminology +----------- +Stream: A stream is a TS packet source + - For example, MPEG2 Transport Stream from TSIF0 +Filter: Enables TS packet filtering and routing according to PID (packet ID) + - The decision regarding which PIDs in the stream will be routed + is done via filters, each demux open request corresponds to a filter. + - Filters can pass TS packets as-is (for recording), assemble them into + "other" PES packets (for PES packets read by client), assemble and send + them to decoder (for decoder PES), or assemble them into sections. +Service: A service is a set of PIDs as defined in the service PMT. + Each service may be carried in a different transport stream or part of the + same transport stream. Processing a service means either preparing the + data for display and/or for recording. + +Requirments +----------- +1. Demuxing from different sources: + - Live transport stream inputs (TSIF) + - Memory inputs +2. Support different packet formats: + - 188-bytes transport packets + - 192-bytes transport packets +3. PID filtering +4. Output of the following data: + - Decoder PES: PES (video and/or audio) that can be directed to HW decoders + in tunneling mode (without interaction of user-space). + - Other PES: a non-decoder PES, such as subtitle, teletext. The consumer + of this data is user-space that reads the data through standard read + calls. + - Sections: Sections are used by user-space to acquire different kinds of + information such as channels list, program user guide, etc. + - Transport Stream Packets: Transport stream packets of specific PIDs as + they were received in the input stream. User-space can use those to + record specific services and/or to perform time-shift buffer. + - PCR/STC: Pairs of PCR/STC can be used by user-space to perform + clock-recovery. + - Frame-indexing: For recorded stream, demux provides indexing of the + I-frames within the stream that can be used for trick-modes operations + while playing a recorded file. +5. Support decryption of scrambled transport packets. +6. Support recording of scrambled streams. +8. Section filtering. + +Control path +------------ +1. Client opens a demux device. Open request is done on the same demux + device for each filter. +2. Client may configure the demux's internal ring-buffer size used to + hold the data for user-space (or default is used). +3. Client configures the opened filter either to capture sections, + TS packets (for recording) or PES (decoder or non-decoder PES). + - The demux configures the underlying HW accordingly through + TSPP or TSIF kernel APIs + - demux receives notification of new data from the underlying HW and + performs demuxing operation based on the configuration. +4. Client can then read data received from the selected filter. + +Data path +--------- +For each filter that is opened, demux manages a circular buffer that +holds the captured filter data; Client read commands extract data from +the relevant ring buffer. Data loss can occur if a client cannot keep up +with stream bandwidth. + +For PES data tunneled to decoder, demux manages a stream-buffer used to +transfer the PES data to the decoder. The stream-buffer is built from +two ring-buffers: One holding the PES payload (elementary stream) and +the other holding PES parameters extracted from the PES header. The +ring-buffer with PES parameters points to the location of respective PES +payload in the PES payload ring-buffer. + +To allow concurrency of multiple stream processing, multiple demux/dvr +devices exist. Each demux devices handles a single stream input. The +number of demux devices is configurable depending on the required number +of concurrent stream processing. + +The client configures each demux device with the stream to process, +by default, all devices are configured to process stream from memory. +The default setting can be changed by issuing ioctl that configures +the demux source to either TSIF0 or TSIF1. For specific TSIF input, +only one demux device may process it at a time. + +Background Processing +--------------------- +When demux receives notifications from underlying HW drivers about new +data, it schedules work to a single-threaded workqueue to process the +notification. + +The processing is the action of demuxing of the new data; it may sleep +as it locks against the demux data-structure that may be accessed by +user-space in the meanwhile. + +A single threaded workqueue exists for each live input (TSIF0 or TSIF1) +to process the inputs in parallel. + +Dependencies +------------ +The demux driver depends on the following kernel drivers and subsystems: +1. TSIF driver: Used to receive TS packets from TSIF interface for + targets supporting TSIF only. +2. TSPP driver: Used to receive TS packets and/or PES from TSPP + interface for targets supporting TSPP. +3. TZ-COM: Used to communicate with TrustZone to handle scrambled + streams. +4. ION: Used to allocate memory for buffers holding decoder-data in + case the data is tunneled between demux and decoders. + Also used to allocate memory for TSPP/TSIF output pipes. +5. dvb-core: Existing Linux infrastructure used for implementation of + dvb devices. + +Design +====== + +Goals +----- +The demux driver is designed to: +1. Fulfil the requirements listed above. +2. Be able to work on different chipsets having different HW + capabilities. For example, some chipsets are equipped with TSIF only, + others are equipped with TSPP of different versions. + +Design Blocks +------------- +Demux implementation hooks to the existing Linux dvb-core +infrastructure as follows: + + +----------+ +------------------------------------------+ + | | | MPQ Demux Driver | + | | | +----------+ +----------+ +----------+ | + | | | | MPQ DMX | | MPQ DMX | | MPQ DMX | | + | QCOM MPQ | | | TSIF | | TSPPv1 | | TSPPv2 | | + | Adapter | | | Plugin | | Plugin | | Plugin | | + | | | +----------+ +----------+ +----------+ | + | | | +--------------------------------------+ | + | | | | MPQ Demux Common Services | | + | | | +--------------------------------------+ | + +----------+ +------------------------------------------+ + +--------------------------------------------------------+ + | Linux DVB Core | + | +----------+ +----------+ +----------+ | + | | DVB | | DVB DMX | | DVB | | + | | Demux | | Device | | Device | | + | +----------+ +----------+ +----------+ | + +--------------------------------------------------------+ + +The new developed code is QCOM MPQ Adapter and the MPQ Demux driver +with the various MPQ-DMX Plugins. + +QCOM MPQ Adapter registers a new DVB adapter to Linux dvb-core. +The MPQ DVB adapter is built as a separate kernel module. Using it +demux and video devices can register themselves to the adapter. + +MPQ-DMX plugins exist to hook to dvb-core demux implementation +depending on the HW capabilities. Only one of these plugins might be +compiled and run at a time on the target. +As the name of each plugin implies, one plugin implements demux +functionality for targets supporting TSIF only, and the others +implement pluging for targets supporting TSPP in different versions. + +The plugin implementation is not hooked to specific chipset as +different chipsets might have the same HW capability. + +The MPQ-DMX Plugin Common Services provides common services that are +used by all plugins, such as registrations of demux devices to +the dvb-core. + +The demux plugin is built as a separate kernel module. Each plugin +hooks to the DVB-Demux by providing set of pointers to functions +required for DVB-Demux and dvb-core operation. The actual +implementation of these function differs between the plugins depending +on the HW capabilities. The plugins may be viewed as "classes" +inheriting from DVB-Demux "class". + +Interface to TSPP/TSIF Drivers +------------------------------ +Each demux plugin interacts with the kernel API of the relevant driver +(either TSIF or TSPP) to receive TS packets or other kinds of data +depending on the HW capabilities. + +The demux uses the kernel API of TSIF and TSPP drivers and registers +callback triggered when new data is received. The callback schedules +work to a single-threaded workqueue to process the data. The actual +processing of the data depends on the HW capabilities. + +Interface to TZ-COM Driver +-------------------------- +For cases HW does not support descrambling, the descrambling is +performed by communicating with TZ using TZ-COM kernel API. + +ION driver is used to allocate input and output buffers provided to TZ. + +Interface to Decoders +--------------------- +The interface to the decoders is done through a stream-buffer interface. +The design aims not to have direct calls between dvb/demux and +dvb/video for de-coupling and generality. dvb/demux and dvb/video +interact only with stream-buffer API. + +Stream buffer is built of two ring-buffers, one holding the PES payload +(the video elementary stream) and the other holding parameters from PES +headers required by decoders. + +The separation to two ring-buffers allows locking the payload buffer +as secured buffer that only the decoder's HW may access while allowing +the software to access the PES headers which are not required to be +secured. Locking of the payload buffer is done when the data should be +secured (scrambled video stream for example). + +The stream-buffer API make use of dvb-ring buffer implementation that +is part of dvb-core. + +SMP/multi-core +============== +Driver is fully SMP aware. + +Interface +========= + +User-space API +-------------- +dvb/demux and dvb/dvr each expose a char device interface to user-space +as defined by linuxtv.org. Extension to this interface is done to add +new features required by MPQ use-cases. The extensions preserve backward +compatibility of the API defined by linuxtv.org + +The devices appear in file-system under: +/dev/dvb/adapter0/demuxN +/dev/dvb/adapter0/dvrN + +Where "N" ranges between 0 to total number of demux devices defined. +The default configuration is 4 devices. + +Extensions to this API (through new ioctl) exist to provide the +following functionality: + +1. DMX_SET_TS_PACKET_FORMAT: Set the transport stream TS packet format. + Configures whether the stream fed to demux from memory is with TS packet + of 188 bytes long, 192 bytes long, etc. + Default is 188 bytes long to preserve backward compatibility. + + Returns the following values: + 0 in case of success. + -EINVAL if the parameter is invalid. + -EBUSY if demux is already running. + +2. DMX_SET_DECODER_BUFFER_SIZE: Set the decoder's buffer size. + For data tunneled to decoder, client can configure the size of the buffer + holding the PES payload. + Default is set to the fixed size value that exists in current dvb-core to + preserve backward compatibility. + + Returns the following values: + 0 in case of success. + -EINVAL if the parameter is invalid. + -EBUSY if demux is already running. + +3. DMX_SET_TS_OUT_FORMAT: Set the TS packet recording format. + Indicates whether the TS packet used for recording should be in 188 or 192 + bytes long format. In case of 192-packets output, 4 bytes zero timestamp + is attached to the original 188 packet. + Default is set for 188 to preserve backward compatibility. + + Returns the following values: + 0 in case of success. + -EINVAL if the parameter is invalid. + -EBUSY if demux is already running. + +4. Added support for mmap for direct access to input/output buffers. + User can either use the original read/write syscalls or use mmap + on the specific file-handle. Several ioctls were exposed so that + user can find-out the status of the buffers (DMX_GET_BUFFER_STATUS), + to notify demux when data is consumed (DMX_RELEASE_DATA) or notify + dvr when data is fed (DMX_FEED_DATA). + +5. DMX_SET_PLAYBACK_MODE: Set playback mode in memory input. + In memory input, contrary to live input, playback can be in pull mode, + where if one of output buffers is full, demux stalls waiting for free space, + this would cause DVR input buffer fullness to accumulate. + + Returns the following values: + 0 in case of success. + -EINVAL if the parameter is invalid. + -EBUSY if demux is already running. + +debugfs +------- +debugfs is used for debug purposes. + +Directory in debugfs is created for each demux device. + +Each directory includes several performance counters of the specific demux: +Total demuxing time, total CRC time, HW notification rate, HW notification +buffer size. + + +Exported Kernel API +------------------- +MPQ adapter exports the following kernel API: +1. Getter API for the registered MPQ adapter handle. + This is used by demux plugin as well as dvb/video implementation to + register their devices to that adapter. +2. Stream buffer API: Used to tunnel the data between dvb/demux and + decoders. The API is used by dvb/demux and by decoders to write/read + tunneled data. +3. Stream buffer interface registration: Used to register stream-buffer + interfaces. When demux driver is asked to tunnel data to a decoder, + the demux allocates a stream-buffer to be shared between demux and + the decoder. For the decoder to retrieve the info of the + stream-buffer it should connect to, stream-buffer registration API + exist. + The demux registers the new allocated stream buffer handle to MPQ + Adapter, and the decoder may query the registered interface through + MPQ Adapter. + +Driver parameters +================= +There are three kernel modules required for DVB API operation: +1. dvb-core.ko: This is an existing Linux module for dvb functionality. + The parameters for this module are the one defined by linuxtv.org. + An additional parameter was added to specify whether to collect + performance debug information exposed through debugfs. + Parameter name: dvb_demux_performancecheck + +2. mpq-adapter.ko: MPQ DVB adapter module. Has a parameter to + specify the adapter number, the number (X) is the same as the one + that appears in /dev/dvb/adapterX. Default is 0. + Parameter name: adapter_nr + +3. mpq-dmx-hw-plugin.ko: Module for demux HW plugin. Receives as a + parameter the number of required demux devices. Default is set to the + number specified in kernel configuration. + Parameter name: mpq_demux_device_num + +Config options +============== +New kernel configurations is available (through make menuconfig) to +enable MPQ based adapter functionality. The following configurations +exist: +1. Control whether to enable QCOM MPQ DVB adapter (tri-state). + It depends on having dvb-core enabled. +2. If MPQ adapter is enabled: + 2.1. Control whether to enable MPQ dvb/demux (tri-state) + 2.2. Control whether to enable MPQ dvb/video (tri-state) + 2.3. If dvb/demux is enabled: + 2.3.1. Configure the number of demux devices. Default is 4. + 2.3.2. Select the desired demux plugin. Each plugin would appear + in the list of options depending whether the respective + driver (TSIF/TSPP) is enabled or not. + +Dependencies +============ +1. The implementation depends on having dvb-core enabled. +2. Each demux plugin depends on whether the relevant driver it uses + is enabled. TSIF plugin depends on TSIF driver and TSPP plugins + depend on TSPP driver. +3. There's no communication to other processors. + +User space utilities +==================== +N/A + +Other +===== +N/A + +Known issues +============ +N/A diff --git a/Documentation/genlock.txt b/Documentation/genlock.txt new file mode 100644 index 0000000000000000000000000000000000000000..cd8261467748a8ab6887345b54ab34f9c2ae46df --- /dev/null +++ b/Documentation/genlock.txt @@ -0,0 +1,167 @@ +Introduction + +'genlock' is an in-kernel API and optional userspace interface for a generic +cross-process locking mechanism. The API is designed for situations where +multiple user space processes and/or kernel drivers need to coordinate access +to a shared resource, such as a graphics buffer. The API was designed with +graphics buffers in mind, but is sufficiently generic to allow it to be +independently used with different types of resources. The chief advantage +of genlock over other cross-process locking mechanisms is that the resources +can be accessed by both userspace and kernel drivers which allows resources +to be locked or unlocked by asynchronous events in the kernel without the +intervention of user space. + +As an example, consider a graphics buffer that is shared between a rendering +application and a compositing window manager. The application renders into a +buffer. That buffer is reused by the compositing window manager as a texture. +To avoid corruption, access to the buffer needs to be restricted so that one +is not drawing on the surface while the other is reading. Locks can be +explicitly added between the rendering stages in the processes, but explicit +locks require that the application wait for rendering and purposely release the +lock. An implicit release triggered by an asynchronous event from the GPU +kernel driver, however, will let execution continue without requiring the +intercession of user space. + +SW Goals + +The genlock API implements exclusive write locks and shared read locks meaning +that there can only be one writer at a time, but multiple readers. Processes +that are unable to acquire a lock can be optionally blocked until the resource +becomes available. + +Locks are shared between processes. Each process will have its own private +instance for a lock known as a handle. Handles can be shared between user +space and kernel space to allow a kernel driver to unlock or lock a buffer +on behalf of a user process. + +Locks within a process using a single genlock handle follow the same rules for +exclusive write locks with multiple readers. Genlock cannot provide deadlock +protection because the same handle can be used simultaneously by a producer and +consumer. In practice in the event that the client creates a deadlock an error +will still be generated when the timeout expires. + +Kernel API + +Access to the genlock API can either be via the in-kernel API or via an +optional character device (/dev/genlock). The character device is primarily +to be used for legacy resource sharing APIs that cannot be easily changed. +New resource sharing APIs from this point should implement a scheme specific +wrapper for locking. + +To create or attach to an existing lock, a process or kernel driver must first +create a handle. Each handle is linked to a single lock at any time. An entityi +may have multiple handles, each associated with a different lock. Once a handle +has been created, the owner may create a new lock or attach an existing lock +that has been exported from a different handle. + +Once the handle has a lock attached, the owning process may attempt to lock the +buffer for read or write. Write locks are exclusive, meaning that only one +process may acquire it at any given time. Read locks are shared, meaning that +multiple readers can hold the lock at the same time. Attempts to acquire a read +lock with a writer active or a write lock with one or more readers or writers +active will typically cause the process to block until the lock is acquired. +When the lock is released, all waiting processes will be woken up. Ownership +of the lock is reference counted, meaning that any one owner can "lock" +multiple times. The lock will only be released from the owner when all the +references to the lock are released via unlock. + +The owner of a write lock may atomically convert the lock into a read lock +(which will wake up other processes waiting for a read lock) without first +releasing the lock. The owner would simply issue a new request for a read lock. +However, the owner of a read lock cannot convert it into a write lock in the +same manner. To switch from a read lock to a write lock, the owner must +release the lock and then try to reacquire it. + +These are the in-kernel API calls that drivers can use to create and +manipulate handles and locks. Handles can either be created and managed +completely inside of kernel space, or shared from user space via a file +descriptor. + +* struct genlock_handle *genlock_get_handle(void) +Create a new handle. + +* struct genlock_handle * genlock_get_handle_fd(int fd) +Given a valid file descriptor, return the handle associated with that +descriptor. + +* void genlock_put_handle(struct genlock_handle *) +Release a handle. + +* struct genlock * genlock_create_lock(struct genlock_handle *) +Create a new lock and attach it to the handle. Once a lock is attached to a +handle it stays attached until the handle is destroyed. + +* struct genlock * genlock_attach_lock(struct genlock_handle *handle, int fd) +Given a valid file descriptor, get the lock associated with it and attach it to +the handle. + +* int genlock_lock(struct genlock_handle *, int op, int flags, u32 timeout) +Lock or unlock the lock attached to the handle. A zero timeout value will +be treated just like if the GENOCK_NOBLOCK flag is passed; if the lock +can be acquired without blocking then do so otherwise return -EAGAIN. +Function returns -ETIMEDOUT if the timeout expired or 0 if the lock was +acquired. + +* int genlock_wait(struct genloc_handle *, u32 timeout) +Wait for a lock held by the handle to go to the unlocked state. A non-zero +timeout value must be passed. Returns -ETIMEDOUT if the timeout expired or +0 if the lock is in an unlocked state. + +Character Device + +Opening an instance to the /dev/genlock character device will automatically +create a new handle. All ioctl functions with the exception of NEW and +RELEASE use the following parameter structure: + +struct genlock_lock { + int fd; /* Returned by EXPORT, used by ATTACH */ + int op; /* Used by LOCK */ + int flags; /* used by LOCK */ + u32 timeout; /* Used by LOCK and WAIT */ +} + +*GENLOCK_IOC_NEW +Create a new lock and attaches it to the handle. Returns -EINVAL if the handle +already has a lock attached (use GENLOCK_IOC_RELEASE to remove it). Returns +-ENOMEM if the memory for the lock can not be allocated. No data is passed +from the user for this ioctl. + +*GENLOCK_IOC_EXPORT +Export the currently attached lock to a file descriptor. The file descriptor +is returned in genlock_lock.fd. + +*GENLOCK_IOC_ATTACH +Attach an exported lock file descriptor to the current handle. Return -EINVAL +if the handle already has a lock attached (use GENLOCK_IOC_RELEASE to remove +it). Pass the file descriptor in genlock_lock.fd. + +*GENLOCK_IOC_LOCK +Lock or unlock the attached lock. Pass the desired operation in +genlock_lock.op: + * GENLOCK_WRLOCK - write lock + * GENLOCK_RDLOCK - read lock + * GENLOCK_UNLOCK - unlock an existing lock + +Pass flags in genlock_lock.flags: + * GENLOCK_NOBLOCK - Do not block if the lock is already taken + * GENLOCK_WRITE_TO_READ - Convert a write lock that the handle owns to a read + lock. For instance graphics may hold a write lock + while rendering the back buffer then when swapping + convert the lock to a read lock to copy the front + buffer in the next frame for preserved buffers. + +Pass a timeout value in milliseconds in genlock_lock.timeout. +genlock_lock.flags and genlock_lock.timeout are not used for UNLOCK. +Returns -EINVAL if no lock is attached, -EAGAIN if the lock is taken and +NOBLOCK is specified or if the timeout value is zero, -ETIMEDOUT if the timeout +expires or 0 if the lock was successful. + +* GENLOCK_IOC_WAIT +Wait for the lock attached to the handle to be released (i.e. goes to unlock). +This is mainly used for a thread that needs to wait for a peer to release a +lock on the same shared handle. A non-zero timeout value in milliseconds is +passed in genlock_lock.timeout. Returns 0 when the lock has been released, +-EINVAL if a zero timeout is passed, or -ETIMEDOUT if the timeout expires. + +* GENLOCK_IOC_RELEASE +This ioctl has been deprecated. Do not use. diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index c1601e5a8b71e81ae57f7d1d5a70be5449501443..64349f0a926ea1d2e1f33df1e07a4e049259ddb7 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2522,6 +2522,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/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index 22ae8441489fcef3fe8f21ff4322dc3a8b85e5e6..08f7312a3fbdcab5eba7403c5410ad52d74fa1b8 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -8,6 +8,23 @@ The following attributes are read/write. force_ro Enforce read-only access even if write protect switch is off. + num_wr_reqs_to_start_packing This attribute is used to determine + the trigger for activating the write packing, in case the write + packing control feature is enabled. + + When the MMC manages to reach a point where num_wr_reqs_to_start_packing + write requests could be packed, it enables the write packing feature. + This allows us to start the write packing only when it is beneficial + and has minimum affect on the read latency. + + The number of potential packed requests that will trigger the packing + can be configured via sysfs by writing the required value to: + /sys/block//num_wr_reqs_to_start_packing. + + The default value of num_wr_reqs_to_start_packing was determined by + running parallel lmdd write and lmdd read operations and calculating + the max number of packed writes requests. + SD and MMC Device Attributes ============================ diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index 1619a8c8087341477621388701fec82ca832b70b..5aecb4889415fe10b11a89495265ff934af4d7ea 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -1039,9 +1039,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 0000000000000000000000000000000000000000..182043f6b51750803224c25099a2f90eaa38dd00 --- /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/power/pm_qos_interface.txt b/Documentation/power/pm_qos_interface.txt index 17e130a803470377ba898d069b84f8b0f51eda69..b4ea2b50b34b431d4ee655451e696f7afce0103a 100644 --- a/Documentation/power/pm_qos_interface.txt +++ b/Documentation/power/pm_qos_interface.txt @@ -19,7 +19,7 @@ Each parameters have defined units: The infrastructure exposes multiple misc device nodes one per implemented parameter. The set of parameters implement is defined by pm_qos_power_init() -and pm_qos_params.h. This is done because having the available parameters +and pm_qos.h. This is done because having the available parameters being runtime configurable or changeable from a driver was seen as too easy to abuse. diff --git a/Documentation/sound/alsa/compress/snd_compress_data.txt b/Documentation/sound/alsa/compress/snd_compress_data.txt new file mode 100644 index 0000000000000000000000000000000000000000..98e2cc98c346a1e2a41eb22953b373d29807a961 --- /dev/null +++ b/Documentation/sound/alsa/compress/snd_compress_data.txt @@ -0,0 +1,187 @@ + snd_compress_data.txt + ===================== + Pierre-Louis.Bossart + Vinod Koul + +Overview + +Since its early days, the ALSA API was defined with PCM support or +constant bitrates payloads such as IEC61937 in mind. Arguments and +returned values in frames are the norm, making it a challenge to +extend the existing API to compressed data streams. + +In recent years, audio digital signal processors (DSP) were integrated +in system-on-chip designs, and DSPs are also integrated in audio +codecs. Processing compressed data on such DSPs results in a dramatic +reduction of power consumption compared to host-based +processing. Support for such hardware has not been very good in Linux, +mostly because of a lack of a generic API available in the mainline +kernel. + +Rather than requiring a compability break with an API change of the +ALSA PCM interface, a new 'Compressed Data' API is introduced to +provide a control and data-streaming interface for audio DSPs. + +The design of this API was inspired by the 2-year experience with the +Intel Moorestown SOC, with many corrections required to upstream the +API in the mainline kernel instead of the staging tree and make it +usable by others. + +Requirements + +The main requirements are: + +- separation between byte counts and time. Compressed formats may have + a header per file, per frame, or no header at all. The payload size + may vary from frame-to-frame. As a result, it is not possible to + estimate reliably the duration of audio buffers when handling + compressed data. Dedicated mechanisms are required to allow for + reliable audio-video synchronization, which requires precise + reporting of the number of samples rendered at any given time. + +- Handling of multiple formats. PCM data only requires a specification + of the sampling rate, number of channels and bits per sample. In + contrast, compressed data comes in a variety of formats. Audio DSPs + may also provide support for a limited number of audio encoders and + decoders embedded in firmware, or may support more choices through + dynamic download of libraries. + +- Focus on main formats. This API provides support for the most + popular formats used for audio and video capture and playback. It is + likely that as audio compression technology advances, new formats + will be added. + +- Handling of multiple configurations. Even for a given format like + AAC, some implementations may support AAC multichannel but HE-AAC + stereo. Likewise WMA10 level M3 may require too much memory and cpu + cycles. The new API needs to provide a generic way of listing these + formats. + +- Rendering/Grabbing only. This API does not provide any means of + hardware acceleration, where PCM samples are provided back to + user-space for additional processing. This API focuses instead on + streaming compressed data to a DSP, with the assumption that the + decoded samples are routed to a physical output or logical back-end. + + - Complexity hiding. Existing user-space multimedia frameworks all + have existing enums/structures for each compressed format. This new + API assumes the existence of a platform-specific compatibility layer + to expose, translate and make use of the capabilities of the audio + DSP, eg. Android HAL or PulseAudio sinks. By construction, regular + applications are not supposed to make use of this API. + + +Design + +The new API shares a number of concepts with with the PCM API for flow +control. Start, pause, resume, drain and stop commands have the same +semantics no matter what the content is. + +The concept of memory ring buffer divided in a set of fragments is +borrowed from the ALSA PCM API. However, only sizes in bytes can be +specified. + +Seeks/trick modes are assumed to be handled by the host. + +The notion of rewinds/forwards is not supported. Data committed to the +ring buffer cannot be invalidated, except when dropping all buffers. + +The Compressed Data API does not make any assumptions on how the data +is transmitted to the audio DSP. DMA transfers from main memory to an +embedded audio cluster or to a SPI interface for external DSPs are +possible. As in the ALSA PCM case, a core set of routines is exposed; +each driver implementer will have to write support for a set of +mandatory routines and possibly make use of optional ones. + +The main additions are + +- get_codecs +This routine returns the list of audio formats supported. Querying the +codecs on a capture stream will return encoders, decoders will be +listed for playback streams. + +- get_codec_caps +For each codec, this routine returns a list of capabilities. The +intent is to make sure all the capabilities correspond to valid +settings, and to minimize the risks of configuration failures. For +example, for a complex codec such as AAC, the number of channels +supported may depend on a specific profile. If the capabilities were +exposed with a single descriptor, it may happen that a specific +combination of profiles/channels/formats may not be +supported. Likewise, embedded DSPs have limited memory and cpu cycles, +it is likely that some implementations make the list of capabilities +dynamic and dependent on existing workloads. + +- set_params +This routine sets the configuration chosen for a specific codec. The +most important field in the parameters is the codec type; in most +cases decoders will ignore other fields, while encoders will strictly +comply to the settings + +- get_params +This routines returns the actual settings used by the DSP. Changes to +the settings should remain the exception. + +- get_timestamp +The timestamp becomes a multiple field structure. It lists the number +of bytes transferred, the number of samples processed and the number +of samples rendered/grabbed. All these values can be used to determine +the avarage bitrate, figure out if the ring buffer needs to be +refilled or the delay due to decoding/encoding/io on the DSP. + +Note that the list of codecs/profiles/modes was derived from the +OpenMAX AL specification instead of reinventing the wheel. +Modifications include: +- Addition of FLAC and IEC formats +- Merge of encoder/decoder capabilities +- Profiles/modes listed as bitmasks to make descriptors more compact +- Addition of set_params for decoders (missing in OpenMAX AL) +- Addition of AMR/AMR-WB encoding modes (missing in OpenMAX AL) +- Addition of format information for WMA +- Addition of encoding options when required (derived from OpenMAX IL) +- Addition of rateControlSupported (missing in OpenMAX AL) + +Not supported: + +- Support for VoIP/circuit-switched calls is not the target of this + API. Support for dynamic bit-rate changes would require a tight + coupling between the DSP and the host stack, limiting power savings. + +- Packet-loss concealment is not supported. This would require an + additional interface to let the decoder synthesize data when frames + are lost during transmission. This may be added in the future. + +- Volume control/routing is not handled by this API. Devices exposing a + compressed data interface will be considered as regular ALSA devices + +Instead, + offloaded processing will be considered as regular ALSA devices; + volume changes and routing information will be provided with regular + ALSA kcontrols. + +- Embedded audio effects. Such effects should be enabled in the same + manner, no matter if the input was PCM or compressed. + +- multichannel IEC encoding. Unclear if this is required. + +- Encoding/decoding acceleration is not supported as mentioned + above. It is possible to route the output of a decoder to a capture + stream, or even implement transcoding capabilities. This routing + would be enabled with ALSA kcontrols. + +- Audio policy/resource management. This API does not provide any + hooks to query the utilization of the audio DSP, nor any premption + mechanisms. + +- No notion of underun/overrun. Since the bytes written are compressed + in nature and data written/read doesn't translate directly to + rendered output in time, this does not deal with underrun/overun and + maybe dealt in user-library + +Credits: +- Mark Brown and Liam Girdwood for discussions on the need for this API +- Harsha Priya for her work on intel_sst compressed API +- Rakesh Ughreja for valuable feedback +- Sing Nallasellan, Sikkandar Madar and Prasanna Samaga for + demonstrating and quantifying the benefits of audio offload on a + real platform. diff --git a/Documentation/sysctl/kernel.txt b/Documentation/sysctl/kernel.txt index 6d78841fd41677d4f81dbcb53eed7ab8602cddb4..a419f5e3143605c044510bdf085b5ff465445e41 100644 --- a/Documentation/sysctl/kernel.txt +++ b/Documentation/sysctl/kernel.txt @@ -23,6 +23,7 @@ show up in /proc/sys/kernel: - auto_msgmni - bootloader_type [ X86 only ] - bootloader_version [ X86 only ] +- boot_reason [ ARM only ] - callhome [ S390 only ] - cap_last_cap - core_pattern @@ -142,6 +143,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 0000000000000000000000000000000000000000..7472deeede6615f2dbd331d50ead12994e8a18a6 --- /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 0000000000000000000000000000000000000000..3f00cf0ea9e916507d85416b35fdc620d44170ea --- /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 0000000000000000000000000000000000000000..2bf05a1b1313a698b2392dfcfe7516b2196e9311 --- /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 0000000000000000000000000000000000000000..d0f1d639e9a30ca42fbc925dcd088a47d5aa4da2 --- /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 0000000000000000000000000000000000000000..18b93d8ff31c26c5745cc9f7d521aa4cb64ac462 --- /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 0000000000000000000000000000000000000000..29b6f1a635c71216147037e6c0e0281595d1f5e7 --- /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 a6879630a3eacdcc72f54d705cf366da4f87eddf..75b36ae3c7d4e17e1d34991898c0dc8bf2f0b0f5 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 684eb5af439dc5cee005f51675c2db814a29bf38..bba59d1b3f1c75976993a681d5098c06a854b5e7 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -166,6 +166,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 c03eb3c9fd7923a2a6b839659a37d405aadd0640..5e1cd06b7f078c5892367f17d633db4fac7559c4 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -27,7 +27,7 @@ config ARM select HAVE_PERF_EVENTS select PERF_USE_VMALLOC select HAVE_REGS_AND_STACK_ACCESS_API - select HAVE_HW_BREAKPOINT if (PERF_EVENTS && (CPU_V6 || CPU_V6K || CPU_V7)) + #select HAVE_HW_BREAKPOINT if (PERF_EVENTS && (CPU_V6 || CPU_V6K || CPU_V7)) select HAVE_C_RECORDMCOUNT select HAVE_GENERIC_HARDIRQS select GENERIC_IRQ_SHOW @@ -136,9 +136,18 @@ config GENERIC_IRQ_PROBE config GENERIC_LOCKBREAK bool - default y + default y if !ARM_TICKET_LOCKS depends on SMP && PREEMPT +config ARM_TICKET_LOCKS + bool + help + Enable ticket locks, which help preserve fairness among + contended locks and prevent livelock in multicore systems. + Say 'y' if system stability is important. + default y if ARCH_MSM_SCORPIONMP || ARCH_MSM_KRAITMP + depends on SMP + config RWSEM_GENERIC_SPINLOCK bool default y @@ -732,6 +741,14 @@ 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 + select HAVE_SCHED_CLOCK + select HAVE_CLK_PREPARE + select NEED_MACH_MEMORY_H + select NEED_MACH_IO_H help Support for Qualcomm MSM/QSD based systems. This runs on the apps processor of the MSM/QSD and depends on a shared memory @@ -1158,6 +1175,15 @@ config ARM_NR_BANKS default 16 if ARCH_EP93XX default 8 +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 @@ -1405,6 +1431,29 @@ config PL310_ERRATA_769419 on systems with an outer cache, the store buffer is drained explicitly. +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" @@ -1469,6 +1518,32 @@ source "drivers/pci/Kconfig" source "drivers/pcmcia/Kconfig" +config ARM_ERRATA_764369 + bool "ARM errata: Data cache line maintenance operation by MVA may not succeed" + depends on CPU_V7 && SMP + help + This option enables the workaround for erratum 764369 + affecting Cortex-A9 MPCore with two or more processors (all + current revisions). Under certain timing circumstances, a data + cache line maintenance operation by MVA targeting an Inner + Shareable memory region may fail to proceed up to either the + Point of Coherency or to the Point of Unification of the + system. This workaround adds a DSB instruction before the + relevant cache maintenance functions and sets a specific bit + in the diagnostic control register of the SCU. + +config PL310_ERRATA_769419 + bool "PL310 errata: no automatic Store Buffer drain" + depends on CACHE_L2X0 + help + On revisions of the PL310 prior to r3p2, the Store Buffer does + not automatically drain. This can cause normal, non-cacheable + writes to be retained when the memory system is idle, leading + to suboptimal I/O performance for drivers using coherent DMA. + This option adds a write barrier to the cpu_idle loop so that, + on systems with an outer cache, the store buffer is drained + explicitly. + endmenu menu "Kernel Features" @@ -1491,7 +1566,7 @@ config SMP depends on HAVE_SMP depends on MMU select USE_GENERIC_SMP_HELPERS - select HAVE_ARM_SCU if !ARCH_MSM_SCORPIONMP + select HAVE_ARM_SCU 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 @@ -1552,6 +1627,13 @@ config HAVE_ARM_SCU help This option enables support for the ARM system coherency unit +config ARM_ARCH_TIMER + bool "Architected timer support" + depends on CPU_V7 + select TICK_ONESHOT + help + This option enables support for the ARM architected timer + config HAVE_ARM_TWD bool depends on SMP @@ -1599,7 +1681,7 @@ config LOCAL_TIMERS bool "Use local timer interrupts" depends on 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 @@ -1751,8 +1833,44 @@ 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 + +config ARCH_MEMORY_REMOVE + def_bool n + +config ARCH_POPULATES_NODE_MAP + def_bool n + +config ENABLE_DMM + def_bool n + +config FIX_MOVABLE_ZONE + def_bool n + +config DONT_MAP_HOLE_AFTER_MEMBANK0 + def_bool n + depends on SPARSEMEM + +config ARCH_ENABLE_MEMORY_HOTPLUG + def_bool n + +config ARCH_ENABLE_MEMORY_HOTREMOVE + def_bool n + +config HOLES_IN_ZONE + def_bool n + depends on SPARSEMEM + config FORCE_MAX_ZONEORDER int "Maximum zone order" if ARCH_SHMOBILE range 11 64 if ARCH_SHMOBILE @@ -1894,6 +2012,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" @@ -2201,6 +2330,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" @@ -2290,7 +2427,7 @@ menu "Power management options" source "kernel/power/Kconfig" config ARCH_SUSPEND_POSSIBLE - depends on !ARCH_S5PC100 + depends on !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/Kconfig.debug b/arch/arm/Kconfig.debug index 037bd5affe87306d85de251db27c7e0a44b927f3..ed18caeda74e2037c29c0401b250b570b07d46a2 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -208,43 +208,6 @@ choice Say Y here if you want kernel low-level debugging support on i.MX6Q UART4. - config DEBUG_MSM_UART1 - bool "Kernel low-level debugging messages via MSM UART1" - depends on ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50 - help - Say Y here if you want the debug print routines to direct - their output to the first serial port on MSM devices. - - config DEBUG_MSM_UART2 - bool "Kernel low-level debugging messages via MSM UART2" - depends on ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50 - help - Say Y here if you want the debug print routines to direct - their output to the second serial port on MSM devices. - - config DEBUG_MSM_UART3 - bool "Kernel low-level debugging messages via MSM UART3" - depends on ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50 - help - Say Y here if you want the debug print routines to direct - their output to the third serial port on MSM devices. - - config DEBUG_MSM8660_UART - bool "Kernel low-level debugging messages via MSM 8660 UART" - depends on ARCH_MSM8X60 - select MSM_HAS_DEBUG_UART_HS - help - Say Y here if you want the debug print routines to direct - their output to the serial port on MSM 8660 devices. - - config DEBUG_MSM8960_UART - bool "Kernel low-level debugging messages via MSM 8960 UART" - depends on ARCH_MSM8960 - select MSM_HAS_DEBUG_UART_HS - help - Say Y here if you want the debug print routines to direct - their output to the serial port on MSM 8960 devices. - config DEBUG_REALVIEW_STD_PORT bool "RealView Default UART" depends on ARCH_REALVIEW @@ -353,4 +316,13 @@ config ARM_KPROBES_TEST help Perform tests of kprobes API and instruction set simulation. +config PID_IN_CONTEXTIDR + bool "Write the current PID to the CONTEXTIDR register" + depends on CPU_COPY_V6 + help + Enabling this option causes the kernel to write the current PID to + the PROCID field of the CONTEXTIDR register, at the expense of some + additional instructions during context switch. Say Y here only if you + are planning to use hardware trace tools with this kernel. + endmenu diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 047a20780fc15a5b753c3ea80c2178efed29c18c..81b5dc9c82979ec1271ddd13ef1ca7a5a78449da 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -128,9 +128,6 @@ textofs-$(CONFIG_PM_H1940) := 0x00108000 ifeq ($(CONFIG_ARCH_SA1100),y) textofs-$(CONFIG_SA1111) := 0x00208000 endif -textofs-$(CONFIG_ARCH_MSM7X30) := 0x00208000 -textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000 -textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000 # Machine directory name. This list is sorted alphanumerically # by CONFIG_* macro name. @@ -257,6 +254,7 @@ core-y += arch/arm/net/ 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/dts/msm-gdsc.dtsi b/arch/arm/boot/dts/msm-gdsc.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..f83fe767efb3235479d344d8006059437155a71d --- /dev/null +++ b/arch/arm/boot/dts/msm-gdsc.dtsi @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/include/ "skeleton.dtsi" + +/ { + gdsc_venus: qcom,gdsc@fd8c1024 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_venus"; + reg = <0xfd8c1024 0x4>; + }; + + gdsc_mdss: qcom,gdsc@fd8c2304 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_mdss"; + reg = <0xfd8c2304 0x4>; + }; + + gdsc_jpeg: qcom,gdsc@fd8c35a4 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_jpeg"; + reg = <0xfd8c35a4 0x4>; + }; + + gdsc_vfe: qcom,gdsc@fd8c36a4 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_vfe"; + reg = <0xfd8c36a4 0x4>; + }; + + gdsc_oxili_gx: qcom,gdsc@fd8c4024 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_oxili_gx"; + reg = <0xfd8c4024 0x4>; + }; + + gdsc_oxili_cx: qcom,gdsc@fd8c4034 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_oxili_cx"; + reg = <0xfd8c4034 0x4>; + }; + + gdsc_usb_hsic: qcom,gdsc@fc400404 { + compatible = "qcom,gdsc"; + regulator-name = "gdsc_usb_hsic"; + reg = <0xfc400404 0x4>; + }; +}; diff --git a/arch/arm/boot/dts/msm-pm8841.dtsi b/arch/arm/boot/dts/msm-pm8841.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..523c9b0fab393da2539ececb730397c776d46975 --- /dev/null +++ b/arch/arm/boot/dts/msm-pm8841.dtsi @@ -0,0 +1,187 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/ { + qcom,spmi@fc4c0000 { + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <3>; + + qcom,pm8841@5 { + spmi-slave-container; + reg = <0x5>; + #address-cells = <1>; + #size-cells = <1>; + + regulator@1400 { + regulator-name = "8841_s1"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1400 0x300>; + status = "disabled"; + + qcom,ctl@1400 { + reg = <0x1400 0x100>; + }; + qcom,ps@1500 { + reg = <0x1500 0x100>; + }; + qcom,freq@1600 { + reg = <0x1600 0x100>; + }; + }; + + regulator@1700 { + regulator-name = "8841_s2"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1700 0x300>; + status = "disabled"; + + qcom,ctl@1700 { + reg = <0x1700 0x100>; + }; + qcom,ps@1800 { + reg = <0x1800 0x100>; + }; + qcom,freq@1900 { + reg = <0x1900 0x100>; + }; + }; + + regulator@1a00 { + regulator-name = "8841_s3"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1a00 0x300>; + status = "disabled"; + + qcom,ctl@1a00 { + reg = <0x1a00 0x100>; + }; + qcom,ps@1b00 { + reg = <0x1b00 0x100>; + }; + qcom,freq@1c00 { + reg = <0x1c00 0x100>; + }; + }; + + regulator@1d00 { + regulator-name = "8841_s4"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1d00 0x300>; + status = "disabled"; + + qcom,ctl@1d00 { + reg = <0x1d00 0x100>; + }; + qcom,ps@1e00 { + reg = <0x1e00 0x100>; + }; + qcom,freq@1f00 { + reg = <0x1f00 0x100>; + }; + }; + + regulator@2000 { + regulator-name = "8841_s5"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x2000 0x300>; + status = "disabled"; + + qcom,ctl@0 { + reg = <0x2000 0x100>; + }; + qcom,ps@100 { + reg = <0x2100 0x100>; + }; + qcom,freq@200 { + reg = <0x2200 0x100>; + }; + }; + + regulator@2300 { + regulator-name = "8841_s6"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x2300 0x300>; + status = "disabled"; + + qcom,ctl@2300 { + reg = <0x2300 0x100>; + }; + qcom,ps@2400 { + reg = <0x2400 0x100>; + }; + qcom,freq@2500 { + reg = <0x2500 0x100>; + }; + }; + + regulator@2600 { + regulator-name = "8841_s7"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x2600 0x300>; + status = "disabled"; + + qcom,ctl@2600 { + reg = <0x2300 0x100>; + }; + qcom,ps@2700 { + reg = <0x2400 0x100>; + }; + qcom,freq@2800 { + reg = <0x2500 0x100>; + }; + }; + + regulator@2900 { + regulator-name = "8841_s8"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x2900 0x300>; + status = "disabled"; + + qcom,ctl@2900 { + reg = <0x2900 0x100>; + }; + qcom,ps@2a000 { + reg = <0x2a00 0x100>; + }; + qcom,freq@2b00 { + reg = <0x2b00 0x100>; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/msm-pm8941.dtsi b/arch/arm/boot/dts/msm-pm8941.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..2698ea7195fbdb7814e1eee5c3b67d08aadf8948 --- /dev/null +++ b/arch/arm/boot/dts/msm-pm8941.dtsi @@ -0,0 +1,529 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/ { + qcom,spmi@fc4c0000 { + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <3>; + + qcom,pm8941@0 { + spmi-slave-container; + reg = <0x0>; + #address-cells = <1>; + #size-cells = <1>; + + pm8941_gpios { + spmi-dev-container; + compatible = "qcom,qpnp-gpio"; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <1>; + + gpio@c000 { + reg = <0xc000 0x100>; + qcom,gpio-num = <1>; + status = "disabled"; + }; + + gpio@c100 { + reg = <0xc100 0x100>; + qcom,gpio-num = <2>; + status = "disabled"; + }; + + gpio@c200 { + reg = <0xc200 0x100>; + qcom,gpio-num = <3>; + status = "disabled"; + }; + + gpio@c300 { + reg = <0xc300 0x100>; + qcom,gpio-num = <4>; + status = "disabled"; + }; + + gpio@c400 { + reg = <0xc400 0x100>; + qcom,gpio-num = <5>; + status = "disabled"; + }; + + gpio@c500 { + reg = <0xc500 0x100>; + qcom,gpio-num = <6>; + status = "disabled"; + }; + + gpio@c600 { + reg = <0xc600 0x100>; + qcom,gpio-num = <7>; + status = "disabled"; + }; + + gpio@c700 { + reg = <0xc700 0x100>; + qcom,gpio-num = <8>; + status = "disabled"; + }; + + gpio@c800 { + reg = <0xc800 0x100>; + qcom,gpio-num = <9>; + status = "disabled"; + }; + + gpio@c900 { + reg = <0xc900 0x100>; + qcom,gpio-num = <10>; + status = "disabled"; + }; + + gpio@ca00 { + reg = <0xca00 0x100>; + qcom,gpio-num = <11>; + status = "disabled"; + }; + + gpio@cb00 { + reg = <0xcb00 0x100>; + qcom,gpio-num = <12>; + status = "disabled"; + }; + + gpio@cc00 { + reg = <0xcc00 0x100>; + qcom,gpio-num = <13>; + status = "disabled"; + }; + + gpio@cd00 { + reg = <0xcd00 0x100>; + qcom,gpio-num = <14>; + status = "disabled"; + }; + + gpio@ce00 { + reg = <0xce00 0x100>; + qcom,gpio-num = <15>; + status = "disabled"; + }; + + gpio@cf00 { + reg = <0xcf00 0x100>; + qcom,gpio-num = <16>; + status = "disabled"; + }; + + gpio@d000 { + reg = <0xd000 0x100>; + qcom,gpio-num = <17>; + status = "disabled"; + }; + + gpio@d100 { + reg = <0xd100 0x100>; + qcom,gpio-num = <18>; + status = "disabled"; + }; + + gpio@d200 { + reg = <0xd200 0x100>; + qcom,gpio-num = <19>; + status = "disabled"; + }; + + gpio@d300 { + reg = <0xd300 0x100>; + qcom,gpio-num = <20>; + status = "disabled"; + }; + + gpio@d400 { + reg = <0xd400 0x100>; + qcom,gpio-num = <21>; + status = "disabled"; + }; + + gpio@d500 { + reg = <0xd500 0x100>; + qcom,gpio-num = <22>; + status = "disabled"; + }; + + gpio@d600 { + reg = <0xd600 0x100>; + qcom,gpio-num = <23>; + status = "disabled"; + }; + + gpio@d700 { + reg = <0xd700 0x100>; + qcom,gpio-num = <24>; + status = "disabled"; + }; + + gpio@d800 { + reg = <0xd800 0x100>; + qcom,gpio-num = <25>; + status = "disabled"; + }; + + gpio@d900 { + reg = <0xd900 0x100>; + qcom,gpio-num = <26>; + status = "disabled"; + }; + + gpio@da00 { + reg = <0xda00 0x100>; + qcom,gpio-num = <27>; + status = "disabled"; + }; + + gpio@db00 { + reg = <0xdb00 0x100>; + qcom,gpio-num = <28>; + status = "disabled"; + }; + + gpio@dc00 { + reg = <0xdc00 0x100>; + qcom,gpio-num = <29>; + status = "disabled"; + }; + + gpio@dd00 { + reg = <0xdd00 0x100>; + qcom,gpio-num = <30>; + status = "disabled"; + }; + + gpio@de00 { + reg = <0xde00 0x100>; + qcom,gpio-num = <31>; + status = "disabled"; + }; + + gpio@df00 { + reg = <0xdf00 0x100>; + qcom,gpio-num = <32>; + status = "disabled"; + }; + + gpio@e000 { + reg = <0xe000 0x100>; + qcom,gpio-num = <33>; + status = "disabled"; + }; + + gpio@e100 { + reg = <0xe100 0x100>; + qcom,gpio-num = <34>; + status = "disabled"; + }; + + gpio@e200 { + reg = <0xe200 0x100>; + qcom,gpio-num = <35>; + status = "disabled"; + }; + + gpio@e300 { + reg = <0xe300 0x100>; + qcom,gpio-num = <36>; + status = "disabled"; + }; + }; + }; + + qcom,pm8941@1 { + spmi-slave-container; + reg = <0x1>; + #address-cells = <1>; + #size-cells = <1>; + + regulator@1400 { + regulator-name = "8941_s1"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1400 0x300>; + status = "disabled"; + + qcom,ctl@1400 { + reg = <0x1400 0x100>; + }; + qcom,ps@1500 { + reg = <0x1500 0x100>; + }; + qcom,freq@1600 { + reg = <0x1600 0x100>; + }; + }; + + regulator@1700 { + regulator-name = "8941_s2"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1700 0x300>; + status = "disabled"; + + qcom,ctl@1700 { + reg = <0x1700 0x100>; + }; + qcom,ps@1800 { + reg = <0x1800 0x100>; + }; + qcom,freq@1900 { + reg = <0x1900 0x100>; + }; + }; + + regulator@1a00 { + regulator-name = "8941_s3"; + spmi-dev-container; + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qpnp-regulator"; + reg = <0x1400 0x300>; + status = "disabled"; + + qcom,ctl@1a00 { + reg = <0x1a00 0x100>; + }; + qcom,ps@1b00 { + reg = <0x1b00 0x100>; + }; + qcom,freq@1c00 { + reg = <0x1c00 0x100>; + }; + }; + + regulator@1d00 { + regulator-name = "8941_boost"; + reg = <0x1d00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4000 { + regulator-name = "8941_l1"; + reg = <0x4000 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4100 { + regulator-name = "8941_l2"; + reg = <0x4100 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4200 { + regulator-name = "8941_l3"; + reg = <0x4200 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4300 { + regulator-name = "8941_l4"; + reg = <0x4300 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4400 { + regulator-name = "8941_l5"; + reg = <0x4400 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4500 { + regulator-name = "8941_l6"; + reg = <0x4500 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4600 { + regulator-name = "8941_l7"; + reg = <0x4600 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4700 { + regulator-name = "8941_l8"; + reg = <0x4700 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4800 { + regulator-name = "8941_l9"; + reg = <0x4800 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4900 { + regulator-name = "8941_l10"; + reg = <0x4900 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4a00 { + regulator-name = "8941_l11"; + reg = <0x4a00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4b00 { + regulator-name = "8941_l12"; + reg = <0x4b00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4c00 { + regulator-name = "8941_l13"; + reg = <0x4c00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4d00 { + regulator-name = "8941_l14"; + reg = <0x4d00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4e00 { + regulator-name = "8941_l15"; + reg = <0x4e00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@4f00 { + regulator-name = "8941_l16"; + reg = <0x4f00 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5000 { + regulator-name = "8941_l17"; + reg = <0x5000 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5100 { + regulator-name = "8941_l18"; + reg = <0x5100 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5200 { + regulator-name = "8941_l19"; + reg = <0x5200 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5300 { + regulator-name = "8941_l20"; + reg = <0x5300 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5400 { + regulator-name = "8941_l21"; + reg = <0x5400 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5500 { + regulator-name = "8941_l22"; + reg = <0x5500 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5600 { + regulator-name = "8941_l23"; + reg = <0x5600 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@5700 { + regulator-name = "8941_l24"; + reg = <0x5700 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@8000 { + regulator-name = "8941_lvs1"; + reg = <0x8000 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@8100 { + regulator-name = "8941_lvs2"; + reg = <0x8100 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@8200 { + regulator-name = "8941_lvs3"; + reg = <0x8200 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@8300 { + regulator-name = "8941_mvs1"; + reg = <0x8300 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + + regulator@8400 { + regulator-name = "8941_mvs2"; + reg = <0x8400 0x100>; + compatible = "qcom,qpnp-regulator"; + status = "disabled"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/msm-pm8x41-rpm-regulator.dtsi b/arch/arm/boot/dts/msm-pm8x41-rpm-regulator.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..019112a616608bdcca53f6c7681e8d9fcde58c69 --- /dev/null +++ b/arch/arm/boot/dts/msm-pm8x41-rpm-regulator.dtsi @@ -0,0 +1,587 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/ { + qcom,rpm-smd { + rpm-regulator-smpb1 { + qcom,resource-name = "smpb"; + qcom,resource-id = <1>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s1 { + regulator-name = "8841_s1"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-smpb2 { + qcom,resource-name = "smpb"; + qcom,resource-id = <2>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s2 { + regulator-name = "8841_s2"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-smpb3 { + qcom,resource-name = "smpb"; + qcom,resource-id = <3>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s3 { + regulator-name = "8841_s3"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-smpb4 { + qcom,resource-name = "smpb"; + qcom,resource-id = <4>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s4 { + regulator-name = "8841_s4"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-smpa1 { + qcom,resource-name = "smpa"; + qcom,resource-id = <1>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s1 { + regulator-name = "8941_s1"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-smpa2 { + qcom,resource-name = "smpa"; + qcom,resource-id = <2>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s2 { + regulator-name = "8941_s2"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-smpa3 { + qcom,resource-name = "smpa"; + qcom,resource-id = <3>; + qcom,regulator-type = <1>; + qcom,hpm-min-load = <100000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-s3 { + regulator-name = "8941_s3"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa1 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <1>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l1 { + regulator-name = "8941_l1"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa2 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <2>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l2 { + regulator-name = "8941_l2"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa3 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <3>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l3 { + regulator-name = "8941_l3"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa4 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <4>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l4 { + regulator-name = "8941_l4"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa5 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <5>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l5 { + regulator-name = "8941_l5"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa6 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <6>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l6 { + regulator-name = "8941_l6"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa7 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <7>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l7 { + regulator-name = "8941_l7"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa8 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <8>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l8 { + regulator-name = "8941_l8"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa9 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <9>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l9 { + regulator-name = "8941_l9"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa10 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <10>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l10 { + regulator-name = "8941_l10"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa11 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <11>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l11 { + regulator-name = "8941_l11"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa12 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <12>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l12 { + regulator-name = "8941_l12"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa13 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <13>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l13 { + regulator-name = "8941_l13"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa14 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <14>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l14 { + regulator-name = "8941_l14"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa15 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <15>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l15 { + regulator-name = "8941_l15"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa16 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <16>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l16 { + regulator-name = "8941_l16"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa17 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <17>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l17 { + regulator-name = "8941_l17"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa18 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <18>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l18 { + regulator-name = "8941_l18"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa19 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <19>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l19 { + regulator-name = "8941_l19"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa20 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <20>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l20 { + regulator-name = "8941_l20"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa21 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <21>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l21 { + regulator-name = "8941_l21"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa22 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <22>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l22 { + regulator-name = "8941_l22"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa23 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <23>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l23 { + regulator-name = "8941_l23"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-ldoa24 { + qcom,resource-name = "ldoa"; + qcom,resource-id = <24>; + qcom,regulator-type = <0>; + qcom,hpm-min-load = <10000>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-l24 { + regulator-name = "8941_l24"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + /* TODO: find out correct resource names for LVS vs MVS */ + rpm-regulator-vsa1 { + qcom,resource-name = "vsa"; + qcom,resource-id = <1>; + qcom,regulator-type = <2>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-lvs1 { + regulator-name = "8941_lvs1"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-vsa2 { + qcom,resource-name = "vsa"; + qcom,resource-id = <2>; + qcom,regulator-type = <2>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-lvs2 { + regulator-name = "8941_lvs2"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-vsa3 { + qcom,resource-name = "vsa"; + qcom,resource-id = <3>; + qcom,regulator-type = <2>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-lvs3 { + regulator-name = "8941_lvs3"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-vsa4 { + qcom,resource-name = "vsa"; + qcom,resource-id = <4>; + qcom,regulator-type = <2>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-mvs1 { + regulator-name = "8941_mvs1"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + + rpm-regulator-vsa5 { + qcom,resource-name = "vsa"; + qcom,resource-id = <5>; + qcom,regulator-type = <2>; + compatible = "qcom,rpm-regulator-smd-resource"; + status = "disabled"; + + regulator-mvs2 { + regulator-name = "8941_mvs2"; + qcom,set = <3>; + status = "disabled"; + compatible = "qcom,rpm-regulator-smd"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/msm9625.dts b/arch/arm/boot/dts/msm9625.dts new file mode 100644 index 0000000000000000000000000000000000000000..d5aed00d70d53e0ea83e09515628c4e8a6e7f4aa --- /dev/null +++ b/arch/arm/boot/dts/msm9625.dts @@ -0,0 +1,47 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/dts-v1/; +/include/ "skeleton.dtsi" + +/ { + model = "Qualcomm MSM 9625"; + compatible = "qcom,msm9625"; + interrupt-parent = <&intc>; + + intc: interrupt-controller@F9000000 { + compatible = "qcom,msm-qgic2"; + interrupt-controller; + #interrupt-cells = <3>; + reg = <0xF9000000 0x1000>, + <0xF9002000 0x1000>; + }; + + msmgpio: gpio@fd510000 { + compatible = "qcom,msm-gpio"; + interrupt-controller; + #interrupt-cells = <2>; + reg = <0xfd510000 0x4000>; + }; + + timer { + compatible = "qcom,msm-qtimer", "arm,armv7-timer"; + interrupts = <0 7 0>; + clock-frequency = <5000000>; + }; + + serial@f991f000 { + compatible = "qcom,msm-lsuart-v14"; + reg = <0xf991f000 0x1000>; + interrupts = <0 109 0>; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper-gpio.dtsi b/arch/arm/boot/dts/msmcopper-gpio.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..7c3f5ce000cc2e315d55f816bcd75d9430fce61b --- /dev/null +++ b/arch/arm/boot/dts/msmcopper-gpio.dtsi @@ -0,0 +1,214 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/ { + qcom,spmi@fc4c0000 { + + qcom,pm8941@0 { + + pm8941_gpios: pm8941_gpios { + + gpio@c000 { + qcom,gpio-num = <1>; + status = "ok"; + }; + + gpio@c100 { + qcom,gpio-num = <2>; + status = "ok"; + }; + + gpio@c200 { + qcom,gpio-num = <3>; + status = "ok"; + }; + + gpio@c300 { + qcom,gpio-num = <4>; + status = "ok"; + }; + + gpio@c400 { + qcom,gpio-num = <5>; + status = "ok"; + }; + + gpio@c500 { + qcom,gpio-num = <6>; + status = "ok"; + }; + + gpio@c600 { + qcom,gpio-num = <7>; + status = "ok"; + }; + + gpio@c700 { + qcom,gpio-num = <8>; + status = "ok"; + }; + + gpio@c800 { + qcom,gpio-num = <9>; + status = "ok"; + }; + + gpio@c900 { + qcom,gpio-num = <10>; + status = "ok"; + }; + + gpio@ca00 { + qcom,gpio-num = <11>; + status = "ok"; + }; + + gpio@cb00 { + qcom,gpio-num = <12>; + status = "ok"; + }; + + gpio@cc00 { + qcom,gpio-num = <13>; + status = "ok"; + }; + + gpio@cd00 { + qcom,gpio-num = <14>; + status = "ok"; + }; + + gpio@ce00 { + qcom,gpio-num = <15>; + status = "ok"; + }; + + gpio@cf00 { + qcom,gpio-num = <16>; + status = "ok"; + }; + + gpio@d000 { + qcom,gpio-num = <17>; + status = "ok"; + }; + + gpio@d100 { + qcom,gpio-num = <18>; + status = "ok"; + }; + + gpio@d200 { + qcom,gpio-num = <19>; + status = "ok"; + }; + + gpio@d300 { + qcom,gpio-num = <20>; + status = "ok"; + }; + + gpio@d400 { + qcom,gpio-num = <21>; + status = "ok"; + }; + + gpio@d500 { + qcom,gpio-num = <22>; + status = "ok"; + }; + + gpio@d600 { + qcom,gpio-num = <23>; + status = "ok"; + }; + + gpio@d700 { + qcom,gpio-num = <24>; + status = "ok"; + }; + + gpio@d800 { + qcom,gpio-num = <25>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@d900 { + qcom,gpio-num = <26>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@da00 { + qcom,gpio-num = <27>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@db00 { + qcom,gpio-num = <28>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@dc00 { + qcom,gpio-num = <29>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@dd00 { + qcom,gpio-num = <30>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@de00 { + qcom,gpio-num = <31>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@df00 { + qcom,gpio-num = <32>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@e000 { + qcom,gpio-num = <33>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@e100 { + qcom,gpio-num = <34>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@e200 { + qcom,gpio-num = <35>; + qcom,out-strength = <1>; + status = "ok"; + }; + + gpio@e300 { + qcom,gpio-num = <36>; + qcom,out-strength = <1>; + status = "ok"; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper-iommu.dtsi b/arch/arm/boot/dts/msmcopper-iommu.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..e0ce8ac088763a9254d50c9903e4b3df6a15409d --- /dev/null +++ b/arch/arm/boot/dts/msmcopper-iommu.dtsi @@ -0,0 +1,88 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/ { + jpeg: qcom,iommu@fda64000 { + compatible = "qcom,msm-smmu-v2"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + reg = <0xfda64000 0x10000>; + + qcom,iommu-ctx@fda6c000 { + reg = <0xfda6c000 0x1000>; + interrupts = <0 69 0>; + qcom,iommu-ctx-sids = <0>; + qcom,iommu-ctx-name = "jpeg_enc0"; + }; + qcom,iommu-ctx@fda6d000 { + reg = <0xfda6d000 0x1000>; + interrupts = <0 70 0>; + qcom,iommu-ctx-sids = <1>; + qcom,iommu-ctx-name = "jpeg_enc1"; + }; + qcom,iommu-ctx@fda6e000 { + reg = <0xfda6e000 0x1000>; + interrupts = <0 71 0>; + qcom,iommu-ctx-sids = <2>; + qcom,iommu-ctx-name = "jpeg_dec"; + }; + }; + + mdp: qcom,iommu@fd928000 { + compatible = "qcom,msm-smmu-v2"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + reg = <0xfd928000 0x10000>; + + qcom,iommu-ctx@fd930000 { + reg = <0xfd930000 0x1000>; + interrupts = <0 74 0>; + qcom,iommu-ctx-sids = <0>; + qcom,iommu-ctx-name = "mdp_0"; + }; + qcom,iommu-ctx@fd931000 { + reg = <0xfd931000 0x1000>; + interrupts = <0 75 0>; + qcom,iommu-ctx-sids = <1>; + qcom,iommu-ctx-name = "mdp_1"; + }; + }; + + venus: qcom,iommu@fdc84000 { + compatible = "qcom,msm-smmu-v2"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + reg = <0xfdc84000 0x10000>; + + qcom,iommu-ctx@fdc8c000 { + reg = <0xfdc8c000 0x1000>; + interrupts = <0 43 0>; + qcom,iommu-ctx-sids = <0 1 2 3 4 5>; + qcom,iommu-ctx-name = "venus_ns"; + }; + qcom,iommu-ctx@fdc8d000 { + reg = <0xfdc8d000 0x1000>; + interrupts = <0 42 0>; + qcom,iommu-ctx-sids = <0x80 0x81 0x82 0x83 0x84 0x85>; + qcom,iommu-ctx-name = "venus_cp"; + }; + qcom,iommu-ctx@fdc8e000 { + reg = <0xfdc8e000 0x1000>; + interrupts = <0 41 0>; + qcom,iommu-ctx-sids = <0xc0 0xc6>; + qcom,iommu-ctx-name = "venus_fw"; + }; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper-regulator.dtsi b/arch/arm/boot/dts/msmcopper-regulator.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..bb26e00de3f1254a9945bb6268d4d16bddc68042 --- /dev/null +++ b/arch/arm/boot/dts/msmcopper-regulator.dtsi @@ -0,0 +1,394 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/ { + qcom,spmi@fc4c0000 { + + qcom,pm8941@1 { + + pm8941_s1: regulator@1400 { + regulator-min-microvolt = <1300000>; + regulator-max-microvolt = <1300000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8941_s2: regulator@1700 { + regulator-min-microvolt = <2150000>; + regulator-max-microvolt = <2150000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_s3: regulator@1a00 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8941_boost: regulator@1d00 { + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + qcom,enable-time = <500>; + status = "okay"; + }; + + pm8941_l1: regulator@4000 { + parent-supply = <&pm8941_s1>; + regulator-min-microvolt = <1225000>; + regulator-max-microvolt = <1225000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8941_l2: regulator@4100 { + parent-supply = <&pm8941_s3>; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l3: regulator@4200 { + parent-supply = <&pm8941_s1>; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l4: regulator@4300 { + parent-supply = <&pm8941_s1>; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1150000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l5: regulator@4400 { + parent-supply = <&pm8941_s2>; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l6: regulator@4500 { + parent-supply = <&pm8941_s2>; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l7: regulator@4600 { + parent-supply = <&pm8941_s2>; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l8: regulator@4700 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l9: regulator@4800 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <2950000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l10: regulator@4900 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <2950000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l11: regulator@4a00 { + parent-supply = <&pm8941_s1>; + regulator-min-microvolt = <1250000>; + regulator-max-microvolt = <1250000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l12: regulator@4b00 { + parent-supply = <&pm8941_s2>; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l13: regulator@4c00 { + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <2950000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l14: regulator@4d00 { + parent-supply = <&pm8941_s2>; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l15: regulator@4e00 { + parent-supply = <&pm8941_s2>; + regulator-min-microvolt = <2050000>; + regulator-max-microvolt = <2050000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l16: regulator@4f00 { + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <2700000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l17: regulator@5000 { + regulator-min-microvolt = <2850000>; + regulator-max-microvolt = <2850000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l18: regulator@5100 { + regulator-min-microvolt = <2850000>; + regulator-max-microvolt = <2850000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l19: regulator@5200 { + regulator-min-microvolt = <2900000>; + regulator-max-microvolt = <2900000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l20: regulator@5300 { + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <2950000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l21: regulator@5400 { + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <2950000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l22: regulator@5500 { + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l23: regulator@5600 { + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_l24: regulator@5700 { + regulator-min-microvolt = <3075000>; + regulator-max-microvolt = <3075000>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_lvs1: regulator@8000 { + parent-supply = <&pm8941_s3>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_lvs2: regulator@8100 { + parent-supply = <&pm8941_s3>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_lvs3: regulator@8200 { + parent-supply = <&pm8941_s3>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_mvs1: regulator@8300 { + parent-supply = <&pm8941_boost>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8941_mvs2: regulator@8400 { + parent-supply = <&pm8941_boost>; + qcom,enable-time = <200>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + }; + + qcom,pm8841@5 { + + pm8841_s1: regulator@1400 { + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <1150000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8841_s2: regulator@1700 { + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <1150000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8841_s3: regulator@1a00 { + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1150000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8841_s4: regulator@1d00 { + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8841_s5: regulator@2000 { + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <1100000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + regulator-always-on; + status = "okay"; + }; + + pm8841_s6: regulator@2300 { + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <1100000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8841_s7: regulator@2600 { + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <1100000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + + pm8841_s8: regulator@2900 { + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <1100000>; + qcom,enable-time = <500>; + qcom,pull-down-enable = <1>; + status = "okay"; + }; + }; + }; + + krait0_vreg: regulator@f9088000 { + compatible = "qcom,krait-regulator"; + regulator-name = "krait0"; + reg = <0xf9088000 0x1000>; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1100000>; + }; + + krait1_vreg: regulator@f9098000 { + compatible = "qcom,krait-regulator"; + regulator-name = "krait1"; + reg = <0xf9098000 0x1000>; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1100000>; + }; + + krait2_vreg: regulator@f90a8000 { + compatible = "qcom,krait-regulator"; + regulator-name = "krait2"; + reg = <0xf90a8000 0x1000>; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1100000>; + }; + + krait3_vreg: regulator@f90b8000 { + compatible = "qcom,krait-regulator"; + regulator-name = "krait3"; + reg = <0xf90b8000 0x1000>; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1100000>; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper-rumi.dts b/arch/arm/boot/dts/msmcopper-rumi.dts new file mode 100644 index 0000000000000000000000000000000000000000..5bfb22895aa8c7fa013ec3aa70f684485b96bd6f --- /dev/null +++ b/arch/arm/boot/dts/msmcopper-rumi.dts @@ -0,0 +1,114 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/dts-v1/; + +/include/ "msmcopper.dtsi" + +/ { + model = "Qualcomm MSM Copper RUMI"; + compatible = "qcom,msmcopper-rumi", "qcom,msmcopper"; + + timer { + clock-frequency = <5000000>; + }; + + serial@f991f000 { + status = "disable"; + }; + + usb@f9a55000 { + status = "disable"; + }; + + qcom,sdcc@f9824000 { + status = "disable"; + }; + + qcom,sdcc@f9864000 { + status = "disable"; + }; + + qcom,sdcc@f98a4000 { + status = "disable"; + }; + + qcom,sdcc@f98e4000 { + status = "disable"; + }; + + qcom,sps@f998000 { + status = "disable"; + }; + + spi@f9924000 { + status = "disable"; + }; + + spi@f9923000 { + compatible = "qcom,spi-qup-v2"; + reg = <0xf9923000 0x1000>; + interrupts = <0 95 0>; + spi-max-frequency = <24000000>; + #address-cells = <1>; + #size-cells = <0>; + gpios = <&msmgpio 3 0>, /* CLK */ + <&msmgpio 1 0>, /* MISO */ + <&msmgpio 0 0>; /* MOSI */ + cs-gpios = <&msmgpio 9 0>; + + ethernet-switch@2 { + compatible = "simtec,ks8851"; + reg = <2>; + interrupt-parent = <&msmgpio>; + interrupts = <90 0>; + spi-max-frequency = <5000000>; + }; + }; + + i2c@f9966000 { + status = "disable"; + }; + + i2c@f9967000 { + cell-index = <0>; + compatible = "qcom,i2c-qup"; + reg = <0Xf9967000 0x1000>; + reg-names = "qup_phys_addr"; + interrupts = <0 105 0>; + interrupt-names = "qup_err_intr"; + qcom,i2c-bus-freq = <100000>; + qcom,i2c-src-freq = <24000000>; + gpios = <&msmgpio 83 0>, /* DAT */ + <&msmgpio 84 0>; /* CLK */ + }; + + slim@fe12f000 { + status = "disable"; + }; + + qcom,spmi@fc4c0000 { + status = "disable"; + }; + + qcom,ssusb@F9200000 { + status = "disable"; + }; + + qcom,lpass@fe200000 { + status = "disable"; + }; + + qcom,pronto@fb21b000 { + status = "disable"; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper-sim.dts b/arch/arm/boot/dts/msmcopper-sim.dts new file mode 100644 index 0000000000000000000000000000000000000000..ae3f2ddde4144326015e2861a234ae5601cafa7d --- /dev/null +++ b/arch/arm/boot/dts/msmcopper-sim.dts @@ -0,0 +1,38 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/dts-v1/; + +/include/ "msmcopper.dtsi" + +/ { + model = "Qualcomm MSM Copper Simulator"; + compatible = "qcom,msmcopper-sim", "qcom,msmcopper"; + + qcom,sdcc@f9824000 { + qcom,sdcc-disable_cmd23; + }; + + qcom,sdcc@f98a4000 { + status = "disable"; + }; + + qcom,sdcc@f9864000 { + qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000 200000000>; + qcom,sdcc-sup-voltages = <2950 2950>; + qcom,sdcc-disable_cmd23; + }; + + qcom,sdcc@f98e4000 { + status = "disable"; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper.dtsi b/arch/arm/boot/dts/msmcopper.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..60f59d39815241da79c3c5e8566e610c268d4774 --- /dev/null +++ b/arch/arm/boot/dts/msmcopper.dtsi @@ -0,0 +1,316 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/include/ "skeleton.dtsi" +/include/ "msmcopper_pm.dtsi" +/include/ "msm-pm8x41-rpm-regulator.dtsi" +/include/ "msm-pm8841.dtsi" +/include/ "msm-pm8941.dtsi" +/include/ "msmcopper-regulator.dtsi" +/include/ "msmcopper-gpio.dtsi" +/include/ "msmcopper-iommu.dtsi" +/include/ "msm-gdsc.dtsi" + +/ { + model = "Qualcomm MSM Copper"; + compatible = "qcom,msmcopper"; + interrupt-parent = <&intc>; + + intc: interrupt-controller@F9000000 { + compatible = "qcom,msm-qgic2"; + interrupt-controller; + #interrupt-cells = <3>; + reg = <0xF9000000 0x1000>, + <0xF9002000 0x1000>; + }; + + msmgpio: gpio@fd510000 { + compatible = "qcom,msm-gpio"; + interrupt-controller; + #interrupt-cells = <2>; + reg = <0xfd510000 0x4000>; + #gpio-cells = <2>; + }; + + timer { + compatible = "qcom,msm-qtimer", "arm,armv7-timer"; + interrupts = <1 2 0 1 3 0>; + clock-frequency = <19200000>; + }; + + qcom,vidc@fdc00000 { + compatible = "qcom,msm-vidc"; + reg = <0xfdc00000 0xff000>; + interrupts = <0 44 0>; + }; + + serial@f991f000 { + compatible = "qcom,msm-lsuart-v14"; + reg = <0xf991f000 0x1000>; + interrupts = <0 109 0>; + }; + + serial@f995e000 { + compatible = "qcom,msm-lsuart-v14"; + reg = <0xf995e000 0x1000>; + interrupts = <0 114 0>; + }; + + usb@f9a55000 { + compatible = "qcom,hsusb-otg"; + reg = <0xf9a55000 0x400>; + interrupts = <0 134 0>; + HSUSB_VDDCX-supply = <&pm8841_s2>; + HSUSB_1p8-supply = <&pm8941_l6>; + HSUSB_3p3-supply = <&pm8941_l24>; + + qcom,hsusb-otg-phy-type = <2>; + qcom,hsusb-otg-mode = <1>; + qcom,hsusb-otg-otg-control = <1>; + }; + + qcom,sdcc@f9824000 { + cell-index = <1>; + compatible = "qcom,msm-sdcc"; + reg = <0xf9824000 0x1000>; + interrupts = <0 123 0>; + + qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000 200000000>; + qcom,sdcc-sup-voltages = <2950 2950>; + qcom,sdcc-bus-width = <8>; + qcom,sdcc-hs200; + qcom,sdcc-nonremovable; + }; + + qcom,sdcc@f98a4000 { + cell-index = <2>; + compatible = "qcom,msm-sdcc"; + reg = <0xf98a4000 0x1000>; + interrupts = <0 125 0>; + + qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000 200000000>; + qcom,sdcc-sup-voltages = <2950 2950>; + qcom,sdcc-bus-width = <4>; + }; + + qcom,sdcc@f9864000 { + cell-index = <3>; + compatible = "qcom,msm-sdcc"; + reg = <0xf9864000 0x1000>; + interrupts = <0 127 0>; + + qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000>; + qcom,sdcc-sup-voltages = <1800 1800>; + qcom,sdcc-bus-width = <4>; + }; + + qcom,sdcc@f98e4000 { + cell-index = <4>; + compatible = "qcom,msm-sdcc"; + reg = <0xf98e4000 0x1000>; + interrupts = <0 129 0>; + + qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000>; + qcom,sdcc-sup-voltages = <1800 1800>; + qcom,sdcc-bus-width = <4>; + }; + + qcom,sps@f9980000 { + compatible = "qcom,msm_sps"; + reg = <0xf9984000 0x15000>, + <0xf9999000 0xb000>; + interrupts = <0 94 0>; + + qcom,bam-dma-res-pipes = <6>; + }; + + + spi@f9924000 { + compatible = "qcom,spi-qup-v2"; + reg = <0xf9924000 0x1000>; + interrupts = <0 96 0>; + spi-max-frequency = <25000000>; + }; + + slim@fe12f000 { + cell-index = <1>; + compatible = "qcom,slim-msm"; + reg = <0xfe12f000 0x35000>, + <0xfe104000 0x20000>; + reg-names = "slimbus_physical", "slimbus_bam_physical"; + interrupts = <0 163 0 0 164 0>; + interrupt-names = "slimbus_irq", "slimbus_bam_irq"; + qcom,min-clk-gear = <10>; + }; + + qcom,spmi@fc4c0000 { + cell-index = <0>; + compatible = "qcom,spmi-pmic-arb"; + reg = <0xfc4cf000 0x1000>, + <0Xfc4cb000 0x1000>; + /* 190,ee0_krait_hlos_spmi_periph_irq */ + /* 187,channel_0_krait_hlos_trans_done_irq */ + interrupts = <0 190 0 0 187 0>; + qcom,pmic-arb-ee = <0>; + qcom,pmic-arb-channel = <0>; + qcom,pmic-arb-ppid-map = <0x13000000>, /* PM8941_LDO1 */ + <0x13100001>, /* PM8941_LDO2 */ + <0x13200002>, /* PM8941_LDO3 */ + <0x13300003>, /* PM8941_LDO4 */ + <0x13400004>, /* PM8941_LDO5 */ + <0x13500005>, /* PM8941_LDO6 */ + <0x13600006>, /* PM8941_LDO7 */ + <0x13700007>, /* PM8941_LDO8 */ + <0x13800008>, /* PM8941_LDO9 */ + <0x13900009>, /* PM8941_LDO10 */ + <0x13a0000a>, /* PM8941_LDO11 */ + <0x13b0000b>, /* PM8941_LDO12 */ + <0x13c0000c>, /* PM8941_LDO13 */ + <0x13d0000d>, /* PM8941_LDO14 */ + <0x13e0000e>, /* PM8941_LDO15 */ + <0x13f0000f>, /* PM8941_LDO16 */ + <0x14000010>, /* PM8941_LDO17 */ + <0x14100011>, /* PM8941_LDO18 */ + <0x14200012>, /* PM8941_LDO19 */ + <0x14300013>, /* PM8941_LDO20 */ + <0x14400014>, /* PM8941_LDO21 */ + <0x14500015>, /* PM8941_LDO22 */ + <0x14600016>, /* PM8941_LDO23 */ + <0x14700017>, /* PM8941_LDO24 */ + <0x14800018>, /* PM8941_LDO25 */ + <0x14900019>, /* PM8941_LDO26 */ + <0x0c00001a>, /* PM8941_GPIO1 */ + <0x0c10001b>, /* PM8941_GPIO2 */ + <0x0c20001c>, /* PM8941_GPIO3 */ + <0x0c30001d>, /* PM8941_GPIO4 */ + <0x0c40001e>, /* PM8941_GPIO5 */ + <0x0c50001f>, /* PM8941_GPIO6 */ + <0x0c600020>, /* PM8941_GPIO7 */ + <0x0c700021>, /* PM8941_GPIO8 */ + <0x0c800022>, /* PM8941_GPIO9 */ + <0x0c900023>, /* PM8941_GPIO10 */ + <0x0ca00024>, /* PM8941_GPIO11 */ + <0x0cb00025>, /* PM8941_GPIO12 */ + <0x0cc00026>, /* PM8941_GPIO13 */ + <0x0cd00027>, /* PM8941_GPIO14 */ + <0x0ce00028>, /* PM8941_GPIO15 */ + <0x0cf00029>, /* PM8941_GPIO16 */ + <0x0d00002a>, /* PM8941_GPIO17 */ + <0x0d10002b>, /* PM8941_GPIO18 */ + <0x0d20002c>, /* PM8941_GPIO19 */ + <0x0d30002d>, /* PM8941_GPIO20 */ + <0x0d40002e>, /* PM8941_GPIO21 */ + <0x0d50002f>, /* PM8941_GPIO22 */ + <0x0d600030>, /* PM8941_GPIO23 */ + <0x0d700031>, /* PM8941_GPIO24 */ + <0x0d800032>, /* PM8941_GPIO25 */ + <0x0d900033>, /* PM8941_GPIO26 */ + <0x0da00034>, /* PM8941_GPIO27 */ + <0x0db00035>, /* PM8941_GPIO28 */ + <0x0dc00036>, /* PM8941_GPIO29 */ + <0x0dd00037>, /* PM8941_GPIO30 */ + <0x0de00038>, /* PM8941_GPIO31 */ + <0x0df00039>, /* PM8941_GPIO32 */ + <0x0e00003a>, /* PM8941_GPIO33 */ + <0x0e10003b>, /* PM8941_GPIO34 */ + <0x0e20003c>, /* PM8941_GPIO35 */ + <0x0e30003d>, /* PM8941_GPIO36 */ + <0x0280003e>, /* COINCELL */ + <0x0100003f>, /* SMBC_OVP */ + <0x01100040>, /* SMBC_CHG */ + <0x01200041>, /* SMBC_BIF */ + <0x00500042>, /* INTERRUPT */ + <0x00100043>, /* PM8941_0 */ + <0x20100044>, /* PM8841_0 */ + <0x10100045>, /* PM8941_1 */ + <0x30100046>, /* PM8841_1 */ + <0x00800047>, /* PON0 */ + <0x20800048>, /* PON1 */ + <0x11000049>, /* PM8941_SMPS1 */ + <0x1110004a>, /* PM8941_SMPS2 */ + <0x1120004b>, /* PM8941_SMPS3 */ + <0x3100004c>, /* PM8841_SMPS1 */ + <0x3110004d>, /* PM8841_SMPS2 */ + <0x3120004e>, /* PM8841_SMPS3 */ + <0x3130004f>, /* PM8841_SMPS4 */ + <0x31400050>, /* PM8841_SMPS5 */ + <0x31500051>, /* PM8841_SMPS6 */ + <0x31600052>, /* PM8841_SMPS7 */ + <0x31700053>, /* PM8841_SMPS8 */ + <0x05000054>, /* SHARED_XO */ + <0x05100055>, /* BB_CLK1 */ + <0x05200056>, /* BB_CLK2 */ + <0x05900057>, /* SLEEP_CLK */ + <0x07000058>, /* PBS_CORE */ + <0x07100059>, /* PBS_CLIENT1 */ + <0x0720005a>; /* PBS_CLIENT2 */ + }; + + i2c@f9966000 { + cell-index = <0>; + compatible = "qcom,i2c-qup"; + reg = <0Xf9966000 0x1000>; + reg-names = "qup_phys_addr"; + interrupts = <0 104 0>; + interrupt-names = "qup_err_intr"; + qcom,i2c-bus-freq = <100000>; + qcom,i2c-src-freq = <24000000>; + }; + + qcom,acpuclk@f9000000 { + compatible = "qcom,acpuclk-copper"; + }; + + qcom,ssusb@F9200000 { + compatible = "qcom,dwc-usb3-msm"; + reg = <0xF9200000 0xCCFF>; + interrupts = <0 131 0>; + qcom,dwc-usb3-msm-dbm-eps = <4>; + }; + + gdsc_oxili_gx: qcom,gdsc@fd8c4024 { + parent-supply = <&pm8841_s4>; + }; + + qcom,lpass@fe200000 { + compatible = "qcom,pil-q6v5-lpass"; + reg = <0xfe200000 0x00100>, + <0xfd485100 0x00010>; + + qcom,firmware-name = "adsp"; + }; + + qcom,pronto@fb21b000 { + compatible = "qcom,pil-pronto"; + reg = <0xfb21b000 0x3000>, + <0xfc401700 0x4>, + <0xfd485300 0xc>; + vdd_pronto_pll-supply = <&pm8941_l12>; + + qcom,firmware-name = "wcnss"; + }; + + qcom,ocmem@fdd00000 { + compatible = "qcom,msm_ocmem"; + }; + + qcom,rpm-smd { + compatible = "qcom,rpm-smd"; + rpm-channel-name = "rpm_requests"; + rpm-channel-type = <15>; /* SMD_APPS_RPM */ + }; + + qcom,msm-rng@f9bff000 { + compatible = "qcom,msm-rng"; + reg = <0xf9bff000 0x200>; + }; +}; diff --git a/arch/arm/boot/dts/msmcopper_pm.dtsi b/arch/arm/boot/dts/msmcopper_pm.dtsi new file mode 100644 index 0000000000000000000000000000000000000000..0da320035e9dbda8857a77dbba42c51c6d2dbf3d --- /dev/null +++ b/arch/arm/boot/dts/msmcopper_pm.dtsi @@ -0,0 +1,280 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/include/ "skeleton.dtsi" + +/ { + qcom,spm@f9089000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf9089000 0x1000>; + qcom,core-id = <0>; + qcom,saw2-ver-reg = <0xfd0>; + qcom,saw2-cfg = <0x1b>; + qcom,saw2-avs-ctl = <0>; + qcom,saw2-avs-hysteresis = <0>; + qcom,saw2-avs-limit = <0>; + qcom,saw2-avs-dly= <0>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,spm-cmd-wfi = [03 0b 0f]; + qcom,spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + qcom,spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92 + a0 b0 82 10 30 02 22 30 0f]; + }; + + qcom,spm@f9099000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf9099000 0x1000>; + qcom,core-id = <1>; + qcom,saw2-ver-reg = <0xfd0>; + qcom,saw2-cfg = <0x1b>; + qcom,saw2-avs-ctl = <0>; + qcom,saw2-avs-hysteresis = <0>; + qcom,saw2-avs-limit = <0>; + qcom,saw2-avs-dly= <0>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,spm-cmd-wfi = [03 0b 0f]; + qcom,spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + qcom,spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92 + a0 b0 82 10 30 02 22 30 0f]; + }; + + qcom,spm@f90a9000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf90a9000 0x1000>; + qcom,core-id = <2>; + qcom,saw2-ver-reg = <0xfd0>; + qcom,saw2-cfg = <0x1b>; + qcom,saw2-avs-ctl = <0>; + qcom,saw2-avs-hysteresis = <0>; + qcom,saw2-avs-limit = <0>; + qcom,saw2-avs-dly= <0>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,spm-cmd-wfi = [03 0b 0f]; + qcom,spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + qcom,spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92 + a0 b0 82 10 30 02 22 30 0f]; + }; + + qcom,spm@f90b9000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf90b9000 0x1000>; + qcom,core-id = <3>; + qcom,saw2-ver-reg = <0xfd0>; + qcom,saw2-cfg = <0x1b>; + qcom,saw2-avs-ctl = <0>; + qcom,saw2-avs-hysteresis = <0>; + qcom,saw2-avs-limit = <0>; + qcom,saw2-avs-dly= <0>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,spm-cmd-wfi = [03 0b 0f]; + qcom,spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + qcom,spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92 + a0 b0 82 10 30 02 22 30 0f]; + }; + + qcom,spm@f9012000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf9012000 0x1000>; + qcom,core-id = <0xffff>; /* L2/APCS SAW */ + qcom,saw2-ver-reg = <0xfd0>; + qcom,saw2-cfg = <0x1a>; + qcom,saw2-avs-ctl = <0>; + qcom,saw2-avs-hysteresis = <0>; + qcom,saw2-avs-limit = <0>; + qcom,saw2-avs-dly= <0>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x0>; /* TODO: Enable L2 SPM */ + qcom,saw2-pmic-dly = <0x02020204>; + qcom,saw2-pmic-data0 = <0x0400009c>; + qcom,saw2-pmic-data1 = <0x00000060>; + qcom,saw2-pmic-data2 = <0x0000001c>; + qcom,saw2-pmic-data3 = <0x04000000>; + qcom,vctl-timeout-us = <50>; + qcom,vctl-port = <0x0>; /* TODO: */ + qcom,phase-port = <0x1>; /* TODO: */ + qcom,spm-cmd-ret = [0b 00 20 03 22 00 0f]; + qcom,spm-cmd-spc = [00 20 32 60 70 80 42 03 + 78 80 44 22 50 3b 60 02 32 + 50 0f]; + qcom,spm-cmd-pc = [00 10 32 60 70 80 b0 11 42 + 07 01 b0 78 80 12 44 a0 50 + 3b 60 02 32 a0 50 0f]; + }; + + qcom,lpm-levels { + compatible = "qcom,lpm-levels"; + #address-cells = <1>; + #size-cells = <0>; + + qcom,lpm-level@0 { + reg = <0x0>; + qcom,mode = <0>; /* MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT */ + qcom,xo = <1>; /* ON */ + qcom,l2 = <3>; /* ACTIVE */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <100>; + qcom,ss-power = <650>; + qcom,energy-overhead = <801>; + qcom,time-overhead = <200>; + }; + + qcom,lpm-level@1 { + reg = <0x1>; + qcom,mode = <2>; /* MSM_PM_SLEEP_MODE_STANDALONE_POWER_COLLAPSE */ + qcom,xo = <1>; /* ON */ + qcom,l2 = <3>; /* ACTIVE */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <2000>; + qcom,ss-power = <200>; + qcom,energy-overhead = <576000>; + qcom,time-overhead = <2000>; + }; + + qcom,lpm-level@2 { + reg = <0x2>; + qcom,mode = <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <1>; /* ON */ + qcom,l2 = <1>; /* GDHS */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <8500>; + qcom,ss-power = <51>; + qcom,energy-overhead = <1122000>; + qcom,time-overhead = <8500>; + }; + + qcom,lpm-level@3 { + reg = <0x3>; + qcom,mode = <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <1>; /* ON */ + qcom,l2 = <0>; /* OFF */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <9000>; + qcom,ss-power = <51>; + qcom,energy-overhead = <1130300>; + qcom,time-overhead = <9000>; + }; + + qcom,lpm-level@4 { + reg = <0x4>; + qcom,mode = <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <1>; /* ON */ + qcom,l2 = <0>; /* OFF */ + qcom,vdd-mem-upper-bound = <1050000>; /* ACTIVE */ + qcom,vdd-mem-lower-bound = <750000>; /* RETENTION HIGH */ + qcom,vdd-dig-upper-bound = <950000>; /* ACTIVE */ + qcom,vdd-dig-lower-bound = <750000>; /* RETENTION HIGH */ + qcom,latency-us = <10000>; + qcom,ss-power = <51>; + qcom,energy-overhead = <1130300>; + qcom,time-overhead = <10000>; + }; + + qcom,lpm-level@5 { + reg = <0x5>; + qcom,mode = <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <0>; /* OFF */ + qcom,l2 = <1>; /* GDHS */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <12000>; + qcom,ss-power = <14>; + qcom,energy-overhead = <2205900>; + qcom,time-overhead = <12000>; + }; + + qcom,lpm-level@6 { + reg = <0x6>; + qcom,mode = <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <0>; /* OFF */ + qcom,l2 = <0>; /* OFF */ + qcom,vdd-mem-upper-bound = <1150000>; /* MAX */ + qcom,vdd-mem-lower-bound = <1050000>; /* ACTIVE */ + qcom,vdd-dig-upper-bound = <1150000>; /* MAX */ + qcom,vdd-dig-lower-bound = <950000>; /* ACTIVE */ + qcom,latency-us = <18000>; + qcom,ss-power = <12>; + qcom,energy-overhead = <2364250>; + qcom,time-overhead = <18000>; + }; + + qcom,lpm-level@7 { + reg = <0x7>; + qcom,mode= <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <0>; /* OFF */ + qcom,l2 = <0>; /* OFF */ + qcom,vdd-mem-upper-bound = <1050000>; /* ACTIVE */ + qcom,vdd-mem-lower-bound = <750000>; /* RETENTION HIGH */ + qcom,vdd-dig-upper-bound = <950000>; /* ACTIVE */ + qcom,vdd-dig-lower-bound = <750000>; /* RETIONTION HIGH */ + qcom,latency-us = <23500>; + qcom,ss-power = <10>; + qcom,energy-overhead = <2667000>; + qcom,time-overhead = <23500>; + }; + + qcom,lpm-level@8 { + reg = <0x8>; + qcom,mode= <3>; /* MSM_PM_SLEEP_MODE_POWER_COLLAPSE */ + qcom,xo = <0>; /* OFF */ + qcom,l2 = <0>; /* OFF */ + qcom,vdd-mem-upper-bound = <750000>; /* RETENTION HIGH */ + qcom,vdd-mem-lower-bound = <750000>; /* RETENTION LOW */ + qcom,vdd-dig-upper-bound = <750000>; /* RETENTION HIGH */ + qcom,vdd-dig-lower-bound = <500000>; /* RETENTION LOW */ + qcom,latency-us = <29700>; + qcom,ss-power = <5>; + qcom,energy-overhead = <2867000>; + qcom,time-overhead = <30000>; + }; + }; + + qcom,pm-boot { + compatible = "qcom,pm-boot"; + qcom,mode = <0>; /* MSM_PM_BOOT_CONFIG_TZ */ + }; +}; diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig index 271dd136753c629beddfcb826bdc872a829d148d..4ab36cdc07028de95b5bd18b188ee3743694a178 100644 --- a/arch/arm/common/Kconfig +++ b/arch/arm/common/Kconfig @@ -1,11 +1,16 @@ config ARM_GIC select IRQ_DOMAIN select MULTI_IRQ_HANDLER + select MSM_SHOW_RESUME_IRQ bool config GIC_NON_BANKED bool +config GIC_SECURE + bool + depends on ARM_GIC + config ARM_VIC select IRQ_DOMAIN select MULTI_IRQ_HANDLER diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile index 11670f9d51977dd894f0fd7e4c2b80384a20a89f..6c24dce5618df2d7923b2ad9ff0f94a41b29bdcc 100644 --- a/arch/arm/common/Makefile +++ b/arch/arm/common/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_PCI_HOST_ITE8152) += it8152.o obj-$(CONFIG_ARM_TIMER_SP804) += timer-sp.o obj-$(CONFIG_FIQ_GLUE) += fiq_glue.o fiq_glue_setup.o obj-$(CONFIG_FIQ_DEBUGGER) += fiq_debugger.o +obj-$(CONFIG_CP_ACCESS) += cpaccess.o diff --git a/arch/arm/common/cpaccess.c b/arch/arm/common/cpaccess.c new file mode 100644 index 0000000000000000000000000000000000000000..e71e318576a4dbb25e41167ff7965f99b8b6f221 --- /dev/null +++ b/arch/arm/common/cpaccess.c @@ -0,0 +1,351 @@ +/* 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 + +#ifdef CONFIG_ARCH_MSM_KRAIT +#include +#endif + +#define TYPE_MAX_CHARACTERS 10 + +/* + * CP parameters + */ +struct cp_params { + unsigned long il2index; + 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 unsigned long il2_output; +static int cpu; +char type[TYPE_MAX_CHARACTERS] = "C"; + +static DEFINE_PER_CPU(struct cp_params, cp_param) + = { 0, 15, 0, 0, 0, 0, 0, 'r' }; + +static struct sysdev_class cpaccess_sysclass = { + .name = "cpaccess", +}; + +#ifdef CONFIG_ARCH_MSM_KRAIT +/* + * do_read_il2 - Read indirect L2 registers + * @ret: Pointer to return value + * + */ +static void do_read_il2(void *ret) +{ + *(unsigned long *)ret = + get_l2_indirect_reg(per_cpu(cp_param.il2index, cpu)); +} + +/* + * do_write_il2 - Write indirect L2 registers + * @ret: Pointer to return value + * + */ +static void do_write_il2(void *ret) +{ + *(unsigned long *)ret = + set_get_l2_indirect_reg(per_cpu(cp_param.il2index, cpu), + per_cpu(cp_param.write_value, cpu)); +} + +/* + * do_il2_rw - Call Read/Write indirect L2 register functions + * @ret: Pointer to return value in case of CP register + * + */ +static int do_il2_rw(char *str_tmp) +{ + unsigned long write_value, il2index; + char rw; + int ret = 0; + + il2index = 0; + sscanf(str_tmp, "%lx:%c:%lx:%d", &il2index, &rw, &write_value, + &cpu); + per_cpu(cp_param.il2index, cpu) = il2index; + per_cpu(cp_param.rw, cpu) = rw; + per_cpu(cp_param.write_value, cpu) = write_value; + + if (per_cpu(cp_param.rw, cpu) == 'r') { + if (is_smp()) { + if (smp_call_function_single(cpu, do_read_il2, + &il2_output, 1)) + pr_err("Error cpaccess smp call single\n"); + } else + do_read_il2(&il2_output); + } else if (per_cpu(cp_param.rw, cpu) == 'w') { + if (is_smp()) { + if (smp_call_function_single(cpu, do_write_il2, + &il2_output, 1)) + pr_err("Error cpaccess smp call single\n"); + } else + do_write_il2(&il2_output); + } else { + pr_err("cpaccess: Wrong Entry for 'r' or 'w'.\n"); + return -EINVAL; + } + return ret; +} +#else +static void do_il2_rw(char *str_tmp) +{ + il2_output = 0; +} +#endif + +/* + * 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, write the MRC/MCR + * instruction, ensuring cache coherency. + */ + p_opcode = (unsigned long *)&cpaccess_dummy; + mem_text_write_kernel_word(p_opcode, opcode); + +#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; +} + +static int get_register_params(char *str_tmp) +{ + unsigned long op1, op2, crn, crm, cp = 15, write_value, il2index; + char rw; + int cnt = 0; + + il2index = 0; + strncpy(type, strsep(&str_tmp, ":"), TYPE_MAX_CHARACTERS); + + if (strncasecmp(type, "C", TYPE_MAX_CHARACTERS) == 0) { + + sscanf(str_tmp, "%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') && + (per_cpu(cp_param.rw, cpu) != 'r')) { + pr_err("cpaccess: Wrong entry for 'r' or 'w'.\n"); + return -EINVAL; + } + + if (per_cpu(cp_param.rw, cpu) == 'w') + do_cpregister_rw(1); + } else if (strncasecmp(type, "IL2", TYPE_MAX_CHARACTERS) == 0) + do_il2_rw(str_tmp); + else { + pr_err("cpaccess: Not a valid type. Entered: %s\n", type); + return -EINVAL; + } + + return cnt; +} + +/* + * 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) +{ + char *str_tmp = (char *)buf; + + if (down_timeout(&cp_sem, 6000)) + return -ERESTARTSYS; + + get_register_params(str_tmp); + + 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; + + if (strncasecmp(type, "C", TYPE_MAX_CHARACTERS) == 0) + ret = snprintf(buf, TYPE_MAX_CHARACTERS, "%lx\n", + do_cpregister_rw(0)); + else if (strncasecmp(type, "IL2", TYPE_MAX_CHARACTERS) == 0) + ret = snprintf(buf, TYPE_MAX_CHARACTERS, "%lx\n", il2_output); + else + ret = -EINVAL; + + 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 + pr_err("Error initializing cpaccess interface\n"); + + if (!error) + error = sysdev_create_file(&device_cpaccess, + &attr_cp_rw); + else { + pr_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 aa52699841879a36347ec8feafc76b289232739f..54a839265d854bbfcf459dc7bdda677d725418e5 100644 --- a/arch/arm/common/gic.c +++ b/arch/arm/common/gic.c @@ -38,12 +38,14 @@ #include #include #include +#include #include #include #include #include #include +#include union gic_base { void __iomem *common_base; @@ -51,24 +53,37 @@ union gic_base { }; struct gic_chip_data { + unsigned int irq_offset; union gic_base dist_base; union gic_base cpu_base; #ifdef CONFIG_CPU_PM u32 saved_spi_enable[DIV_ROUND_UP(1020, 32)]; u32 saved_spi_conf[DIV_ROUND_UP(1020, 16)]; u32 saved_spi_target[DIV_ROUND_UP(1020, 4)]; + u32 saved_dist_pri[DIV_ROUND_UP(1020, 4)]; u32 __percpu *saved_ppi_enable; u32 __percpu *saved_ppi_conf; #endif - struct irq_domain *domain; +#ifdef CONFIG_IRQ_DOMAIN + struct irq_domain domain; +#endif unsigned int gic_irqs; #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); +#endif + unsigned int max_irq; +#ifdef CONFIG_PM + unsigned int wakeup_irqs[32]; + unsigned int enabled_irqs[32]; #endif }; static DEFINE_RAW_SPINLOCK(irq_controller_lock); +#ifdef CONFIG_CPU_PM +static unsigned int saved_dist_ctrl, saved_cpu_ctrl; +#endif + /* * Supported arch specific GIC irq extension. * Default make them NULL. @@ -80,6 +95,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 @@ -137,6 +153,26 @@ static inline unsigned int gic_irq(struct irq_data *d) return d->hwirq; } +#if defined(CONFIG_CPU_V7) && defined(CONFIG_GIC_SECURE) +static const inline bool is_cpu_secure(void) +{ + unsigned int dscr; + + asm volatile ("mrc p14, 0, %0, c0, c1, 0" : "=r" (dscr)); + + /* BIT(18) - NS bit; 1 = NS; 0 = S */ + if (BIT(18) & dscr) + return false; + else + return true; +} +#else +static const inline bool is_cpu_secure(void) +{ + return false; +} +#endif + /* * Routines to acknowledge, disable and enable interrupts */ @@ -162,6 +198,132 @@ static void gic_unmask_irq(struct irq_data *d) raw_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_data_dist_base(gic); +#ifdef CONFIG_ARCH_MSM8625 + unsigned long flags; +#endif + + for (i = 0; i * 32 < gic->max_irq; i++) { +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock_irqsave(&irq_controller_lock, flags); +#endif + 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); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +#endif + } + 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_data_dist_base(gic); +#ifdef CONFIG_ARCH_MSM8625 + unsigned long flags; +#endif + + if (!msm_show_resume_irq_mask) + return; + +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock_irqsave(&irq_controller_lock, flags); +#else + raw_spin_lock(&irq_controller_lock); +#endif + 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; + } +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +#else + raw_spin_lock(&irq_controller_lock); +#endif + + 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_data_dist_base(gic); +#ifdef CONFIG_ARCH_MSM8625 + unsigned long flags; +#endif + gic_show_resume_irq(gic); + for (i = 0; i * 32 < gic->max_irq; i++) { +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock_irqsave(&irq_controller_lock, flags); +#endif + /* 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); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +#endif + } + 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) { @@ -169,8 +331,13 @@ static void gic_eoi_irq(struct irq_data *d) gic_arch_extn.irq_eoi(d); raw_spin_unlock(&irq_controller_lock); } - +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock(&irq_controller_lock); +#endif writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock(&irq_controller_lock); +#endif } static int gic_set_type(struct irq_data *d, unsigned int type) @@ -257,6 +424,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); @@ -275,16 +456,28 @@ asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic); do { +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock(&irq_controller_lock); +#endif irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock(&irq_controller_lock); +#endif irqnr = irqstat & ~0x1c00; if (likely(irqnr > 15 && irqnr < 1021)) { - irqnr = irq_find_mapping(gic->domain, irqnr); + irqnr = irq_domain_to_irq(&gic->domain, irqnr); handle_IRQ(irqnr, regs); continue; } if (irqnr < 16) { +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock(&irq_controller_lock); +#endif writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock(&irq_controller_lock); +#endif #ifdef CONFIG_SMP handle_IPI(irqnr, regs); #endif @@ -311,8 +504,8 @@ static void gic_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) if (gic_irq == 1023) goto out; - cascade_irq = irq_find_mapping(chip_data->domain, gic_irq); - if (unlikely(gic_irq < 32 || gic_irq > 1020)) + cascade_irq = irq_domain_to_irq(&chip_data->domain, gic_irq); + if (unlikely(gic_irq < 32 || gic_irq > 1020 || cascade_irq >= NR_IRQS)) do_bad_IRQ(cascade_irq, desc); else generic_handle_irq(cascade_irq); @@ -331,6 +524,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, }; @@ -345,9 +539,10 @@ void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) static void __init gic_dist_init(struct gic_chip_data *gic) { - unsigned int i; + unsigned int i, irq; u32 cpumask; unsigned int gic_irqs = gic->gic_irqs; + struct irq_domain *domain = &gic->domain; void __iomem *base = gic_data_dist_base(gic); u32 cpu = cpu_logical_map(smp_processor_id()); @@ -369,6 +564,14 @@ static void __init gic_dist_init(struct gic_chip_data *gic) for (i = 32; i < gic_irqs; i += 4) writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4); + /* + * Set NS/S. + */ + if (is_cpu_secure()) + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xFFFFFFFF, + base + GIC_DIST_ISR + i * 4 / 32); + /* * Set priority on all global interrupts. */ @@ -382,7 +585,31 @@ static void __init gic_dist_init(struct gic_chip_data *gic) for (i = 32; i < gic_irqs; i += 32) writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4 / 32); - writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Setup the Linux IRQ subsystem. + */ + irq_domain_for_each_irq(domain, i, irq) { + if (i < 32) { + irq_set_percpu_devid(irq); + irq_set_chip_and_handler(irq, &gic_chip, + handle_percpu_devid_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); + } else { + irq_set_chip_and_handler(irq, &gic_chip, + handle_fasteoi_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + irq_set_chip_data(irq, gic); + } + + gic->max_irq = gic_irqs; + + if (is_cpu_secure()) + writel_relaxed(3, base + GIC_DIST_CTRL); + else + writel_relaxed(1, base + GIC_DIST_CTRL); + + mb(); } static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) @@ -395,9 +622,16 @@ static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) * Deal with the banked PPI and SGI interrupts - disable all * PPI interrupts, ensure all SGI interrupts are enabled. */ +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock(&irq_controller_lock); +#endif writel_relaxed(0xffff0000, dist_base + GIC_DIST_ENABLE_CLEAR); writel_relaxed(0x0000ffff, dist_base + GIC_DIST_ENABLE_SET); + /* Set NS/S */ + if (is_cpu_secure()) + writel_relaxed(0xFFFFFFFF, dist_base + GIC_DIST_ISR); + /* * Set priority on PPI and SGI interrupts */ @@ -405,7 +639,15 @@ static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4); writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + + if (is_cpu_secure()) + writel_relaxed(0xF, base + GIC_CPU_CTRL); + else + writel_relaxed(1, base + GIC_CPU_CTRL); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock(&irq_controller_lock); +#endif + mb(); } #ifdef CONFIG_CPU_PM @@ -430,6 +672,8 @@ static void gic_dist_save(unsigned int gic_nr) if (!dist_base) return; + saved_dist_ctrl = readl_relaxed(dist_base + GIC_DIST_CTRL); + for (i = 0; i < DIV_ROUND_UP(gic_irqs, 16); i++) gic_data[gic_nr].saved_spi_conf[i] = readl_relaxed(dist_base + GIC_DIST_CONFIG + i * 4); @@ -438,6 +682,10 @@ static void gic_dist_save(unsigned int gic_nr) gic_data[gic_nr].saved_spi_target[i] = readl_relaxed(dist_base + GIC_DIST_TARGET + i * 4); + for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++) + gic_data[gic_nr].saved_dist_pri[i] = + readl_relaxed(dist_base + GIC_DIST_PRI + i * 4); + for (i = 0; i < DIV_ROUND_UP(gic_irqs, 32); i++) gic_data[gic_nr].saved_spi_enable[i] = readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + i * 4); @@ -472,7 +720,7 @@ static void gic_dist_restore(unsigned int gic_nr) dist_base + GIC_DIST_CONFIG + i * 4); for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++) - writel_relaxed(0xa0a0a0a0, + writel_relaxed(gic_data[gic_nr].saved_dist_pri[i], dist_base + GIC_DIST_PRI + i * 4); for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++) @@ -483,7 +731,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4); - writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(saved_dist_ctrl, dist_base + GIC_DIST_CTRL); } static void gic_cpu_save(unsigned int gic_nr) @@ -502,6 +750,12 @@ static void gic_cpu_save(unsigned int gic_nr) if (!dist_base || !cpu_base) return; + saved_cpu_ctrl = readl_relaxed(cpu_base + GIC_CPU_CTRL); + + for (i = 0; i < DIV_ROUND_UP(32, 4); i++) + gic_data[gic_nr].saved_dist_pri[i] = readl_relaxed(dist_base + + GIC_DIST_PRI + i * 4); + ptr = __this_cpu_ptr(gic_data[gic_nr].saved_ppi_enable); for (i = 0; i < DIV_ROUND_UP(32, 32); i++) ptr[i] = readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + i * 4); @@ -537,10 +791,11 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(ptr[i], dist_base + GIC_DIST_CONFIG + i * 4); for (i = 0; i < DIV_ROUND_UP(32, 4); i++) - writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4); + writel_relaxed(gic_data[gic_nr].saved_dist_pri[i], + dist_base + GIC_DIST_PRI + i * 4); writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(saved_cpu_ctrl, cpu_base + GIC_CPU_CTRL); } static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -597,27 +852,11 @@ static void __init gic_pm_init(struct gic_chip_data *gic) } #endif -static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, - irq_hw_number_t hw) -{ - if (hw < 32) { - irq_set_percpu_devid(irq); - irq_set_chip_and_handler(irq, &gic_chip, - handle_percpu_devid_irq); - set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); - } else { - irq_set_chip_and_handler(irq, &gic_chip, - handle_fasteoi_irq); - set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); - } - irq_set_chip_data(irq, d->host_data); - return 0; -} - -static int gic_irq_domain_xlate(struct irq_domain *d, - struct device_node *controller, - const u32 *intspec, unsigned int intsize, - unsigned long *out_hwirq, unsigned int *out_type) +#ifdef CONFIG_OF +static int gic_irq_domain_dt_translate(struct irq_domain *d, + struct device_node *controller, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type) { if (d->of_node != controller) return -EINVAL; @@ -634,23 +873,26 @@ static int gic_irq_domain_xlate(struct irq_domain *d, *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; return 0; } +#endif const struct irq_domain_ops gic_irq_domain_ops = { - .map = gic_irq_domain_map, - .xlate = gic_irq_domain_xlate, +#ifdef CONFIG_OF + .dt_translate = gic_irq_domain_dt_translate, +#endif }; void __init gic_init_bases(unsigned int gic_nr, int irq_start, void __iomem *dist_base, void __iomem *cpu_base, - u32 percpu_offset, struct device_node *node) + u32 percpu_offset) { - irq_hw_number_t hwirq_base; struct gic_chip_data *gic; - int gic_irqs, irq_base; + struct irq_domain *domain; + int gic_irqs, rc; BUG_ON(gic_nr >= MAX_GIC_NR); gic = &gic_data[gic_nr]; + domain = &gic->domain; #ifdef CONFIG_GIC_NON_BANKED if (percpu_offset) { /* Frankein-GIC without banked registers... */ unsigned int cpu; @@ -658,11 +900,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, gic->dist_base.percpu_base = alloc_percpu(void __iomem *); gic->cpu_base.percpu_base = alloc_percpu(void __iomem *); if (WARN_ON(!gic->dist_base.percpu_base || - !gic->cpu_base.percpu_base)) { - free_percpu(gic->dist_base.percpu_base); - free_percpu(gic->cpu_base.percpu_base); - return; - } + !gic->cpu_base.percpu_base)) + goto init_bases_err; for_each_possible_cpu(cpu) { unsigned long offset = percpu_offset * cpu_logical_map(cpu); @@ -686,12 +925,13 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, * For primary GICs, skip over SGIs. * For secondary GICs, skip over PPIs, too. */ - if (gic_nr == 0 && (irq_start & 31) > 0) { - hwirq_base = 16; - if (irq_start != -1) - irq_start = (irq_start & ~31) + 16; - } else { - hwirq_base = 32; + domain->hwirq_base = 32; + if (gic_nr == 0) { + if ((irq_start & 31) > 0) { + domain->hwirq_base = 16; + if (irq_start != -1) + irq_start = (irq_start & ~31) + 16; + } } /* @@ -704,22 +944,33 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, gic_irqs = 1020; gic->gic_irqs = gic_irqs; - gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ - irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); - if (IS_ERR_VALUE(irq_base)) { + domain->nr_irq = gic_irqs - domain->hwirq_base; + domain->irq_base = irq_alloc_descs(irq_start, 16, domain->nr_irq, + numa_node_id()); + if (IS_ERR_VALUE(domain->irq_base)) { WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", irq_start); - irq_base = irq_start; + domain->irq_base = irq_start; } - gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, - hwirq_base, &gic_irq_domain_ops, gic); - if (WARN_ON(!gic->domain)) - return; + domain->priv = gic; + domain->ops = &gic_irq_domain_ops; + rc = irq_domain_add(domain); + if (rc) { + WARN(1, "Unable to create irq_domain\n"); + goto init_bases_err; + } + irq_domain_register(domain); gic_chip.flags |= gic_arch_extn.flags; gic_dist_init(gic); gic_cpu_init(gic); gic_pm_init(gic); + + return; + +init_bases_err: + free_percpu(gic->dist_base.percpu_base); + free_percpu(gic->cpu_base.percpu_base); } void __cpuinit gic_secondary_init(unsigned int gic_nr) @@ -733,23 +984,68 @@ void __cpuinit gic_secondary_init(unsigned int gic_nr) void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; + unsigned long sgir; unsigned long map = 0; +#ifdef CONFIG_ARCH_MSM8625 + unsigned long flags; +#endif /* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) map |= 1 << cpu_logical_map(cpu); + sgir = (map << 16) | irq; + if (is_cpu_secure()) + sgir |= (1 << 15); + /* * Ensure that stores to Normal memory are visible to the * other CPUs before issuing the IPI. */ dsb(); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_lock_irqsave(&irq_controller_lock, flags); +#endif /* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + + writel_relaxed(sgir, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); +#ifdef CONFIG_ARCH_MSM8625 + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +#endif + mb(); } #endif +void gic_set_irq_secure(unsigned int irq) +{ + unsigned int gicd_isr_reg, gicd_pri_reg; + unsigned int mask = 0xFFFFFF00; + struct gic_chip_data *gic_data = &gic_data[0]; + struct irq_data *d = irq_get_irq_data(irq); + + if (is_cpu_secure()) { + raw_spin_lock(&irq_controller_lock); + gicd_isr_reg = readl_relaxed(gic_dist_base(d) + + GIC_DIST_ISR + gic_irq(d) / 32 * 4); + gicd_isr_reg &= ~BIT(gic_irq(d) % 32); + writel_relaxed(gicd_isr_reg, gic_dist_base(d) + + GIC_DIST_ISR + gic_irq(d) / 32 * 4); + /* Also increase the priority of that irq */ + gicd_pri_reg = readl_relaxed(gic_dist_base(d) + + GIC_DIST_PRI + (gic_irq(d) * 4 / 4)); + gicd_pri_reg &= mask; + gicd_pri_reg |= 0x80; /* Priority of 0x80 > 0xA0 */ + writel_relaxed(gicd_pri_reg, gic_dist_base(d) + GIC_DIST_PRI + + gic_irq(d) * 4 / 4); + mb(); + raw_spin_unlock(&irq_controller_lock); + } else { + WARN(1, "Trying to run secure operation from Non-secure mode"); + } +} + #ifdef CONFIG_OF static int gic_cnt __initdata = 0; @@ -759,6 +1055,7 @@ int __init gic_of_init(struct device_node *node, struct device_node *parent) void __iomem *dist_base; u32 percpu_offset; int irq; + struct irq_domain *domain = &gic_data[gic_cnt].domain; if (WARN_ON(!node)) return -ENODEV; @@ -772,7 +1069,9 @@ int __init gic_of_init(struct device_node *node, struct device_node *parent) if (of_property_read_u32(node, "cpu-offset", &percpu_offset)) percpu_offset = 0; - gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node); + domain->of_node = of_node_get(node); + + gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset); if (parent) { irq = irq_of_parse_and_map(node, 0); @@ -782,3 +1081,156 @@ int __init gic_of_init(struct device_node *node, struct device_node *parent) return 0; } #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()); + raw_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); + raw_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()); + raw_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); + raw_spin_unlock(&irq_controller_lock); +} + +#ifdef CONFIG_ARCH_MSM8625 + /* + * Check for any interrupts which are enabled are pending + * in the pending set or not. + * Return : + * 0 : No pending interrupts + * 1 : Pending interrupts other than A9_M2A_5 + */ +unsigned int msm_gic_spi_ppi_pending(void) +{ + unsigned int i, bit = 0; + unsigned int pending_enb = 0, pending = 0; + unsigned long value = 0; + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *base = gic_data_dist_base(gic); + unsigned long flags; + + raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * PPI and SGI to be included. + * MSM8625_INT_A9_M2A_5 needs to be ignored, as A9_M2A_5 + * requesting sleep triggers it + */ + for (i = 0; (i * 32) < gic->max_irq; i++) { + pending = readl_relaxed(base + + GIC_DIST_PENDING_SET + i * 4); + pending_enb = readl_relaxed(base + + GIC_DIST_ENABLE_SET + i * 4); + value = pending & pending_enb; + + if (value) { + for (bit = 0; bit < 32; bit++) { + bit = find_next_bit(&value, 32, bit); + if ((bit + 32 * i) != MSM8625_INT_A9_M2A_5) { + raw_spin_unlock_irqrestore( + &irq_controller_lock, flags); + return 1; + } + } + } + } + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + + return 0; +} + +void msm_gic_save(bool modem_wake, int from_idle) +{ + unsigned int i; + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *base = gic_data_dist_base(gic); + + gic_cpu_save(0); + gic_dist_save(0); + + /* Disable all the Interrupts, before we enter pc */ + for (i = 0; (i * 32) < gic->max_irq; i++) { + raw_spin_lock(&irq_controller_lock); + writel_relaxed(0xffffffff, base + + GIC_DIST_ENABLE_CLEAR + i * 4); + raw_spin_unlock(&irq_controller_lock); + } +} + +void msm_gic_restore(void) +{ + gic_dist_restore(0); + gic_cpu_restore(0); +} + +/* + * Configure the GIC after we come out of power collapse. + * This function will configure some of the GIC registers so as to prepare the + * core1 to receive an SPI(ACSR_MP_CORE_IPC1, (32 + 8)), which will bring + * core1 out of GDFS. + */ +void core1_gic_configure_and_raise(void) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *base = gic_data_dist_base(gic); + unsigned int value = 0; + unsigned long flags; + + raw_spin_lock_irqsave(&irq_controller_lock, flags); + + value = __raw_readl(base + GIC_DIST_ACTIVE_BIT + 0x4); + value |= BIT(8); + __raw_writel(value, base + GIC_DIST_ACTIVE_BIT + 0x4); + mb(); + + value = __raw_readl(base + GIC_DIST_TARGET + 0x24); + value |= BIT(13); + __raw_writel(value, base + GIC_DIST_TARGET + 0x24); + mb(); + + value = __raw_readl(base + GIC_DIST_TARGET + 0x28); + value |= BIT(1); + __raw_writel(value, base + GIC_DIST_TARGET + 0x28); + mb(); + + value = __raw_readl(base + GIC_DIST_ENABLE_SET + 0x4); + value |= BIT(8); + __raw_writel(value, base + GIC_DIST_ENABLE_SET + 0x4); + mb(); + + value = __raw_readl(base + GIC_DIST_PENDING_SET + 0x4); + value |= BIT(8); + __raw_writel(value, base + GIC_DIST_PENDING_SET + 0x4); + mb(); + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +} +#endif diff --git a/arch/arm/configs/fsm9xxx-perf_defconfig b/arch/arm/configs/fsm9xxx-perf_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..18f6d7fff5aa91e5a4ca74ca3bbb3a6af0a92c62 --- /dev/null +++ b/arch/arm/configs/fsm9xxx-perf_defconfig @@ -0,0 +1,184 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="$(KERNEL_LOCAL_VERSION)" +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +# CONFIG_PERF_EVENTS is not set +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_PARTITION_ADVANCED=y +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_IOSCHED_CFQ is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_FSM9XXX=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM7X00A_USE_DG_TIMER=y +CONFIG_MSM7X00A_SLEEP_WAIT_FOR_INTERRUPT=y +CONFIG_MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT=y +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG3=y +# CONFIG_MSM_SMD_DEBUG is not set +CONFIG_MSM_ONCRPCROUTER=y +# CONFIG_MSM_ONCRPCROUTER_DEBUG is not set +# CONFIG_MSM_HW3D is not set +# CONFIG_QSD_AUDIO is not set +# CONFIG_SURF_FFA_GPIO_KEYPAD is not set +CONFIG_MSM_WATCHDOG=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_VMSPLIT_2G=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_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +# 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_INET6_XFRM_MODE_ROUTEOPTIMIZATION=y +CONFIG_IPV6_TUNNEL=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_PIMSM_V2=y +# CONFIG_NET_ACTIVITY_STATS is not set +CONFIG_RFKILL=y +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=8 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_PMIC8058_XOADC=y +CONFIG_QFP_FUSE=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_DUMMY=y +# CONFIG_MSM_RMNET is not set +CONFIG_QFEC=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +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_KEYBOARD_ATKBD is not set +# CONFIG_INPUT_MOUSE is not set +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_DIAG_OVER_USB is not set +# CONFIG_HW_RANDOM is not set +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_SSBI=y +CONFIG_SPI=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_SENSORS_MSM_ADC=y +CONFIG_PMIC8058=y +# CONFIG_MFD_PM8XXX_PWM is not set +# CONFIG_MFD_PM8XXX_MISC is not set +CONFIG_REGULATOR=y +CONFIG_REGULATOR_PM8058_XO=y +# CONFIG_USB_SUPPORT is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=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_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_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_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_STACKTRACE is not set +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +# CONFIG_CRYPTO_MANAGER_DISABLE_TESTS is not set +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_TEST=m +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_DEV_QCRYPTO=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_OTA_CRYPTO=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/fsm9xxx_defconfig b/arch/arm/configs/fsm9xxx_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..47075560d9e80e19d0d28cff9bd278b93fdc5df6 --- /dev/null +++ b/arch/arm/configs/fsm9xxx_defconfig @@ -0,0 +1,186 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="$(KERNEL_LOCAL_VERSION)" +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +# CONFIG_PERF_EVENTS is not set +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_PARTITION_ADVANCED=y +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_IOSCHED_CFQ is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_FSM9XXX=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM7X00A_USE_DG_TIMER=y +CONFIG_MSM7X00A_SLEEP_WAIT_FOR_INTERRUPT=y +CONFIG_MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT=y +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG3=y +CONFIG_MSM_ONCRPCROUTER=y +# CONFIG_MSM_HW3D is not set +# CONFIG_QSD_AUDIO is not set +# CONFIG_SURF_FFA_GPIO_KEYPAD is not set +CONFIG_MSM_WATCHDOG=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_VMSPLIT_2G=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_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +# 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_INET6_XFRM_MODE_ROUTEOPTIMIZATION=y +CONFIG_IPV6_TUNNEL=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_PIMSM_V2=y +# CONFIG_NET_ACTIVITY_STATS is not set +CONFIG_RFKILL=y +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=8 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_PMIC8058_XOADC=y +CONFIG_QFP_FUSE=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_DUMMY=y +# CONFIG_MSM_RMNET is not set +CONFIG_QFEC=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +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_KEYBOARD_ATKBD is not set +# CONFIG_INPUT_MOUSE is not set +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_DIAG_OVER_USB is not set +# CONFIG_HW_RANDOM is not set +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_SSBI=y +CONFIG_SPI=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_SENSORS_MSM_ADC=y +CONFIG_PMIC8058=y +# CONFIG_MFD_PM8XXX_PWM is not set +# CONFIG_MFD_PM8XXX_MISC is not set +CONFIG_REGULATOR=y +CONFIG_REGULATOR_PM8058_XO=y +# CONFIG_USB_SUPPORT is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=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_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_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_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_SCHEDSTATS=y +# CONFIG_STACKTRACE is not set +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +# CONFIG_CRYPTO_MANAGER_DISABLE_TESTS is not set +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_TEST=m +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_DEV_QCRYPTO=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_OTA_CRYPTO=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm-copper_defconfig b/arch/arm/configs/msm-copper_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..e0811ac97f734bfe62c8153b22fba7abdba521b5 --- /dev/null +++ b/arch/arm/configs/msm-copper_defconfig @@ -0,0 +1,201 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +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_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_PARTITION_ADVANCED=y +CONFIG_EFI_PARTITION=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSMCOPPER=y +CONFIG_MSM_KRAIT_TBB_ABORT_HANDLER=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_KERNEL_PMEM_EBI_REGION=y +CONFIG_CPU_HAS_L2_PMU=y +# 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=y +CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_PIL_LPASS_QDSP6V5=y +CONFIG_MSM_TZ_LOG=y +CONFIG_MSM_PIL_PRONTO=y +CONFIG_MSM_DIRECT_SCLK_ACCESS=y +CONFIG_MSM_OCMEM=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +# CONFIG_SMP_ON_UP is not set +CONFIG_ARM_ARCH_TIMER=y +CONFIG_HOTPLUG_CPU=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x19000000 +CONFIG_USE_OF=y +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ATAG_DTB_COMPAT=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_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +# CONFIG_SUSPEND is not set +CONFIG_NET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_PNP=y +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=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_MSM_RMNET is not set +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_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_QUP=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=m +CONFIG_SPMI=y +CONFIG_SPMI_MSM_PMIC_ARB=y +CONFIG_MSM_QPNP=y +CONFIG_SLIMBUS=y +CONFIG_SLIMBUS_MSM_CTRL=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +# CONFIG_BATTERY_MSM is not set +# CONFIG_HWMON is not set +CONFIG_REGULATOR_STUB=y +CONFIG_REGULATOR_QPNP=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_RC_CORE is not set +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +# CONFIG_VIDEO_CAPTURE_DRIVERS is not set +# CONFIG_RADIO_ADAPTERS is not set +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_FB=y +CONFIG_FB_VIRTUAL=y +# CONFIG_HID_SUPPORT is not set +CONFIG_USB_GADGET=y +CONFIG_USB_CI13XXX_MSM=y +CONFIG_USB_G_ANDROID=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_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_SWITCH=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_MSM_SSBI=y +CONFIG_SPS=y +CONFIG_SPS_SUPPORT_BAMDMA=y +CONFIG_SPS_SUPPORT_NDP_BAM=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_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_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_DEBUG_LL=y +CONFIG_EARLY_PRINTK=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_AES=y +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRC_CCITT=y +CONFIG_LIBCRC32C=y +CONFIG_HW_RANDOM_MSM=y diff --git a/arch/arm/configs/msm7627a-perf_defconfig b/arch/arm/configs/msm7627a-perf_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..afea042a25847498adc3b9eb296a48dfae7ed54e --- /dev/null +++ b/arch/arm/configs/msm7627a-perf_defconfig @@ -0,0 +1,343 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="$(KERNEL_LOCAL_VERSION)-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_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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_PARTITION_ADVANCED=y +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X27=y +CONFIG_ARCH_MSM8625=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_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_MSM_SLEEP_STATS_DEVICE=y +CONFIG_BT_MSM_PINTEST=y +CONFIG_MSM_RPC_VIBRATOR=y +CONFIG_PM8XXX_RPC_VIBRATOR=y +CONFIG_MSM_SPM_V2=y +CONFIG_ARM_THUMBEE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0xC800000 +CONFIG_CP_ACCESS=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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_BT=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_MSM_BT_POWER=y +CONFIG_CFG80211=m +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +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_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_SMC91X=y +CONFIG_SMSC911X=y +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_TOUCHSCREEN_ATMEL_MXT=y +CONFIG_TOUCHSCREEN_SYNAPTICS_RMI4_I2C=y +CONFIG_TOUCHSCREEN_FT5X06=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_SENSORS_MSM_ADC=y +CONFIG_MARIMBA_CORE=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +# CONFIG_RC_CORE is not set +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_OV5647=y +CONFIG_AD5046_ACT=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_S5K4E1=y +CONFIG_DW9712_ACT=y +CONFIG_MSM_CAMERA_FLASH_SC628A=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_ACTUATOR=y +CONFIG_OV7692=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_LCDC_TRULY_HVGA_IPS3P2335_PT_PANEL=y +CONFIG_FB_MSM_MIPI_PANEL_DETECT=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_MSM_SOC=y +CONFIG_HID_APPLE=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=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_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_CLKGATE=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_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_MSM_PDM=y +CONFIG_LEDS_PMIC_MPP=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_FUSE_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=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_SHIRQ=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_ENABLE_DEFAULT_TRACERS=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_defconfig b/arch/arm/configs/msm7627a_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..7e24b0d60f372f8f787971b4eb379d8aa2e63471 --- /dev/null +++ b/arch/arm/configs/msm7627a_defconfig @@ -0,0 +1,351 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="$(KERNEL_LOCAL_VERSION)" +# 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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_PARTITION_ADVANCED=y +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X27=y +CONFIG_ARCH_MSM8625=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_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_MSM_SLEEP_STATS_DEVICE=y +CONFIG_BT_MSM_PINTEST=y +CONFIG_MSM_RPC_VIBRATOR=y +CONFIG_PM8XXX_RPC_VIBRATOR=y +CONFIG_MSM_SPM_V2=y +CONFIG_ARM_THUMBEE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0xC800000 +CONFIG_CP_ACCESS=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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_BT=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_MSM_BT_POWER=y +CONFIG_CFG80211=m +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +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_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_SMC91X=y +CONFIG_SMSC911X=y +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_TOUCHSCREEN_ATMEL_MXT=y +CONFIG_TOUCHSCREEN_SYNAPTICS_RMI4_I2C=y +CONFIG_TOUCHSCREEN_FT5X06=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_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_SENSORS_MSM_ADC=y +CONFIG_MARIMBA_CORE=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +# CONFIG_RC_CORE is not set +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_OV5647=y +CONFIG_AD5046_ACT=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_S5K4E1=y +CONFIG_DW9712_ACT=y +CONFIG_MSM_CAMERA_FLASH_SC628A=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_ACTUATOR=y +CONFIG_OV7692=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_LCDC_TRULY_HVGA_IPS3P2335_PT_PANEL=y +CONFIG_FB_MSM_MIPI_PANEL_DETECT=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_MSM_SOC=y +CONFIG_HID_APPLE=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=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_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_CLKGATE=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_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_LEDS_MSM_PDM=y +CONFIG_LEDS_PMIC_MPP=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_FUSE_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=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_SHIRQ=y +CONFIG_LOCKUP_DETECTOR=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_SLAB=y +CONFIG_DEBUG_SLAB_LEAK=y +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_ATOMIC_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_LIST=y +CONFIG_DEBUG_PAGEALLOC=y +CONFIG_ENABLE_DEFAULT_TRACERS=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/msm7630-perf_defconfig b/arch/arm/configs/msm7630-perf_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..4000a0f8b5e2b54d132b5baa777752b6fd3af212 --- /dev/null +++ b/arch/arm/configs/msm7630-perf_defconfig @@ -0,0 +1,377 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +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_CC_OPTIMIZE_FOR_SIZE=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_PARTITION_ADVANCED=y +# 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_MSM_STANDALONE_POWER_COLLAPSE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x1A000000 +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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_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_MSM_BT_POWER=y +CONFIG_CFG80211=y +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +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_HAPTIC_ISA1200=y +CONFIG_PMIC8XXX_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_MSM_RMNET_SDIO=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_LIBRA_SDIOIF=m +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_PMIC8XXX=y +# 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_QC=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_PMIC8058=y +CONFIG_MARIMBA_CORE=y +CONFIG_MARIMBA_CODEC=y +CONFIG_TIMPANI_CODEC=y +# CONFIG_MFD_PM8XXX_DEBUG is not set +# CONFIG_MFD_PM8XXX_PWM is not set +# CONFIG_MFD_PM8XXX_MISC is not set +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_VIDEO_HELPER_CHIPS_AUTO=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_HID_APPLE=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=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_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_CLKGATE=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_CARD_HW_DETECTION=y +# CONFIG_MMC_MSM_SDC1_SUPPORT is not set +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +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_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_FUSE_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_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_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 0000000000000000000000000000000000000000..fdcd566a365cfc93376991587b8e87e9bb855caf --- /dev/null +++ b/arch/arm/configs/msm7630_defconfig @@ -0,0 +1,385 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +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_CC_OPTIMIZE_FOR_SIZE=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_PARTITION_ADVANCED=y +# 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_MSM_STANDALONE_POWER_COLLAPSE=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x1A000000 +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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_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_MSM_BT_POWER=y +CONFIG_CFG80211=y +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +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_HAPTIC_ISA1200=y +CONFIG_PMIC8XXX_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_MSM_RMNET_SDIO=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_LIBRA_SDIOIF=m +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_PMIC8XXX=y +# 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_QC=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_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_PMIC8058=y +CONFIG_MARIMBA_CORE=y +CONFIG_MARIMBA_CODEC=y +CONFIG_TIMPANI_CODEC=y +# CONFIG_MFD_PM8XXX_DEBUG is not set +# CONFIG_MFD_PM8XXX_PWM is not set +# CONFIG_MFD_PM8XXX_MISC is not set +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_VIDEO_HELPER_CHIPS_AUTO=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_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_CLKGATE=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_CARD_HW_DETECTION=y +# CONFIG_MMC_MSM_SDC1_SUPPORT is not set +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +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_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_FUSE_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_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_LOCKUP_DETECTOR=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_SLAB=y +CONFIG_DEBUG_SLAB_LEAK=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_PROVE_LOCKING=y +CONFIG_DEBUG_ATOMIC_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_LIST=y +CONFIG_DEBUG_PAGEALLOC=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 0000000000000000000000000000000000000000..d5b4007c2110574a3f210fe7459c97d0d909d5b7 --- /dev/null +++ b/arch/arm/configs/msm8660-perf_defconfig @@ -0,0 +1,450 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="-perf" +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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_PARTITION_ADVANCED=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_MACH_MSM8X60_DRAGON=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_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_PIL_MODEM=y +CONFIG_MSM_PIL_QDSP6V3=y +CONFIG_MSM_PIL_TZAPPS=y +CONFIG_MSM_PIL_DSPS=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_TZ_LOG=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_GSBI9_UART=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_NR_CPUS=2 +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_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_MSM_BT_POWER=y +CONFIG_CFG80211=y +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_UID_STAT=y +CONFIG_TSIF=m +CONFIG_TSIF_CHRDEV=m +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8XXX_VIBRATOR=y +CONFIG_PMIC8XXX_UPL=y +CONFIG_PMIC8058_XOADC=y +CONFIG_QSEECOM=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_MSM_RMNET_SDIO=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_ASYNC=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_LIBRA_SDIOIF=m +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_INPUT_KEYRESET=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_MATRIX=y +CONFIG_KEYBOARD_PMIC8XXX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_CY8C_TS=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_PMIC8XXX_PWRKEY=y +CONFIG_INPUT_UINPUT=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_TSENS=y +CONFIG_THERMAL_PM8XXX=y +CONFIG_PMIC8058=y +CONFIG_PMIC8901=y +CONFIG_MARIMBA_CORE=y +CONFIG_TIMPANI_CODEC=y +# CONFIG_MFD_PM8XXX_PWM is not set +CONFIG_MFD_PM8XXX_BATT_ALARM=y +CONFIG_REGULATOR_MSM_GPIO=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_IMX074=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_IMX074_ACT=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_ACTUATOR=y +CONFIG_MSM_GEMINI=y +CONFIG_OV7692=y +CONFIG_RADIO_TAVARUA=y +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_MSM_KGSL_PAGE_TABLE_COUNT=24 +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_OVERLAY0_WRITEBACK=y +CONFIG_FB_MSM_OVERLAY1_WRITEBACK=y +CONFIG_FB_MSM_WRITEBACK_MSM_PANEL=y +CONFIG_FB_MSM_LCDC_MIPI_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_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=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_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_CLKGATE=y +CONFIG_MMC_EMBEDDED_SDIO=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_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +CONFIG_MMC_MSM_SDC5_SUPPORT=y +CONFIG_LEDS_GPIO=y +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_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_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_MSM_SSBI=y +CONFIG_MSM_IOMMU=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_FUSE_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_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_INFO=y +CONFIG_ENABLE_DEFAULT_TRACERS=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 diff --git a/arch/arm/configs/msm8660_defconfig b/arch/arm/configs/msm8660_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..5e2c1a81a380518ea06a5f1ec04e622e9e39d005 --- /dev/null +++ b/arch/arm/configs/msm8660_defconfig @@ -0,0 +1,462 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_PARTITION_ADVANCED=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_MACH_MSM8X60_DRAGON=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_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_PIL_MODEM=y +CONFIG_MSM_PIL_QDSP6V3=y +CONFIG_MSM_PIL_TZAPPS=y +CONFIG_MSM_PIL_DSPS=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_TZ_LOG=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_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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_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_MSM_BT_POWER=y +CONFIG_CFG80211=y +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_UID_STAT=y +CONFIG_TSIF=m +CONFIG_TSIF_CHRDEV=m +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8XXX_VIBRATOR=y +CONFIG_PMIC8XXX_UPL=y +CONFIG_PMIC8058_XOADC=y +CONFIG_QSEECOM=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_MSM_RMNET_SDIO=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_ASYNC=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_LIBRA_SDIOIF=m +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_INPUT_KEYRESET=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_MATRIX=y +CONFIG_KEYBOARD_PMIC8XXX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_CY8C_TS=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_PMIC8XXX_PWRKEY=y +CONFIG_INPUT_UINPUT=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_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_TSENS=y +CONFIG_THERMAL_PM8XXX=y +CONFIG_PMIC8058=y +CONFIG_PMIC8901=y +CONFIG_MARIMBA_CORE=y +CONFIG_TIMPANI_CODEC=y +# CONFIG_MFD_PM8XXX_PWM is not set +CONFIG_MFD_PM8XXX_BATT_ALARM=y +CONFIG_REGULATOR_MSM_GPIO=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_IMX074=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_IMX074_ACT=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_ACTUATOR=y +CONFIG_MSM_GEMINI=y +CONFIG_OV7692=y +CONFIG_RADIO_TAVARUA=y +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_MSM_KGSL_PAGE_TABLE_COUNT=24 +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_OVERLAY0_WRITEBACK=y +CONFIG_FB_MSM_OVERLAY1_WRITEBACK=y +CONFIG_FB_MSM_WRITEBACK_MSM_PANEL=y +CONFIG_FB_MSM_LCDC_MIPI_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_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=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_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_CLKGATE=y +CONFIG_MMC_EMBEDDED_SDIO=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_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +CONFIG_MMC_MSM_SDC5_SUPPORT=y +CONFIG_LEDS_GPIO=y +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_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_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_MSM_SSBI=y +CONFIG_MSM_IOMMU=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_FUSE_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_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_LOCKUP_DETECTOR=y +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y +CONFIG_SLUB_DEBUG_ON=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_ATOMIC_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_VM=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DEBUG_LIST=y +CONFIG_DEBUG_SG=y +CONFIG_DEBUG_PAGEALLOC=y +CONFIG_ENABLE_DEFAULT_TRACERS=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-perf_defconfig b/arch/arm/configs/msm8960-perf_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..74d31b34414dc8939fde57410e0f109c5dc3ae57 --- /dev/null +++ b/arch/arm/configs/msm8960-perf_defconfig @@ -0,0 +1,483 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="-perf" +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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_EFI_PARTITION=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM8960=y +CONFIG_ARCH_MSM8930=y +CONFIG_ARCH_APQ8064=y +CONFIG_MSM_KRAIT_TBB_ABORT_HANDLER=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_MACH_MSM8960_LIQUID=y +CONFIG_MACH_MSM8930_CDP=y +CONFIG_MACH_MSM8930_MTP=y +CONFIG_MACH_MSM8930_FLUID=y +CONFIG_MACH_MSM8627_CDP=y +CONFIG_MACH_MSM8627_MTP=y +CONFIG_MACH_APQ8064_SIM=y +CONFIG_MACH_APQ8064_RUMI3=y +CONFIG_MACH_APQ8064_CDP=y +CONFIG_MACH_APQ8064_MTP=y +CONFIG_MACH_APQ8064_LIQUID=y +CONFIG_MACH_MPQ8064_CDP=y +CONFIG_MACH_MPQ8064_HRD=y +CONFIG_MACH_MPQ8064_DTV=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_KERNEL_PMEM_EBI_REGION=y +# 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_PCIE=y +CONFIG_MSM_BAM_DMUX=y +CONFIG_MSM_RMNET_SMUX=y +CONFIG_MSM_DSPS=y +CONFIG_MSM_IPC_ROUTER=y +CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_PIL_QDSP6V4=y +CONFIG_MSM_PIL_RIVA=y +CONFIG_MSM_PIL_TZAPPS=y +CONFIG_MSM_PIL_DSPS=y +CONFIG_MSM_PIL_VIDC=y +CONFIG_MSM_PIL_GSS=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_MODEM_8960=y +CONFIG_MSM_LPASS_8960=y +CONFIG_MSM_WCNSS_SSR_8960=y +CONFIG_MSM_GSS_SSR_8064=y +CONFIG_MSM_TZ_LOG=y +CONFIG_MSM_RPM_LOG=y +CONFIG_MSM_RPM_STATS_LOG=y +CONFIG_MSM_BUS_SCALING=y +CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED=y +CONFIG_MSM_WATCHDOG=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_MSM_QDSS=y +CONFIG_MSM_SLEEP_STATS=y +CONFIG_MSM_CACHE_ERP=y +CONFIG_MSM_L2_ERP_2BIT_PANIC=y +CONFIG_MSM_DCVS=y +CONFIG_MSM_HSIC_SYSMON=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_PCI=y +CONFIG_PCI_MSI=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_CC_STACKPROTECTOR=y +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_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_ROUTE_VERBOSE=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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_NF_CONNTRACK_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_CLS_FW=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_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=y +CONFIG_CFG80211=m +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8XXX_VIBRATOR=y +CONFIG_QSEECOM=y +CONFIG_USB_HSIC_SMSC_HUB=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_KS8851=m +# CONFIG_MSM_RMNET is not set +CONFIG_MSM_RMNET_BAM=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_USB_USBNET=y +CONFIG_MSM_RMNET_USB=y +CONFIG_WCNSS_CORE=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_MATRIX=y +CONFIG_KEYBOARD_PMIC8XXX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MXT=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_PMIC8XXX_PWRKEY=y +CONFIG_INPUT_UINPUT=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_N_SMUX=y +CONFIG_N_SMUX_LOOPBACK=y +CONFIG_SMUX_CTL=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_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=m +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_ISL9519_CHARGER=y +CONFIG_SMB349_CHARGER=y +CONFIG_PM8921_CHARGER=y +CONFIG_PM8921_BMS=y +CONFIG_PM8921_BCL=y +CONFIG_SENSORS_PM8XXX_ADC=y +CONFIG_SENSORS_EPM_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_TSENS8960=y +CONFIG_THERMAL_PM8XXX=y +CONFIG_THERMAL_MONITOR=y +CONFIG_MFD_PM8921_CORE=y +CONFIG_MFD_PM8821_CORE=y +CONFIG_MFD_PM8038_CORE=y +CONFIG_MFD_PM8XXX_SPK=y +CONFIG_MFD_PM8XXX_BATT_ALARM=y +CONFIG_WCD9304_CODEC=y +CONFIG_WCD9310_CODEC=y +CONFIG_REGULATOR_PM8XXX=y +CONFIG_REGULATOR_MSM_GPIO=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_USER_RC_INPUT=y +CONFIG_IR_GPIO_CIR=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_IMX074=y +CONFIG_MT9M114=y +CONFIG_IMX074_ACT=y +CONFIG_MSM_CAMERA_FLASH_SC628A=y +CONFIG_MSM_CAMERA_FLASH_TPS61310=y +CONFIG_OV2720=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_ACTUATOR=y +CONFIG_MSM_EEPROM=y +CONFIG_IMX074_EEPROM=y +CONFIG_IMX091_EEPROM=y +CONFIG_MSM_GEMINI=y +CONFIG_S5K3L1YX=y +CONFIG_IMX091=y +CONFIG_RADIO_IRIS=y +CONFIG_RADIO_IRIS_TRANSPORT=m +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_MSM_KGSL_PAGE_TABLE_COUNT=24 +CONFIG_FB=y +CONFIG_FB_VIRTUAL=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_OVERLAY0_WRITEBACK=y +CONFIG_FB_MSM_OVERLAY1_WRITEBACK=y +CONFIG_FB_MSM_WRITEBACK_MSM_PANEL=y +CONFIG_FB_MSM_LVDS_MIPI_PANEL_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_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_MSM8960=y +CONFIG_HID_APPLE=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=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_EHCI_MSM_HSIC=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_SERIAL=y +CONFIG_USB_SERIAL_QUALCOMM=y +CONFIG_USB_SERIAL_CSVT=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_QCOM_DIAG_BRIDGE=y +CONFIG_USB_QCOM_MDM_BRIDGE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_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_CLKGATE=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_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_SDC3_WP_SUPPORT=y +CONFIG_MMC_MSM_SPS_SUPPORT=y +CONFIG_LEDS_PM8XXX=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +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_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_MSM_SSBI=y +CONFIG_SPS=y +CONFIG_SPS_SUPPORT_BAMDMA=y +CONFIG_MSM_IOMMU=y +CONFIG_MOBICORE_SUPPORT=m +CONFIG_MOBICORE_API=m +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_FUSE_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_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=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_ENABLE_DEFAULT_TRACERS=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_PID_IN_CONTEXTIDR=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/msm8960_defconfig b/arch/arm/configs/msm8960_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..07a3f91239f7885bbf648797c34f18e097c6af84 --- /dev/null +++ b/arch/arm/configs/msm8960_defconfig @@ -0,0 +1,500 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_EFI_PARTITION=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM8960=y +CONFIG_ARCH_MSM8930=y +CONFIG_ARCH_APQ8064=y +CONFIG_MSM_KRAIT_TBB_ABORT_HANDLER=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_MACH_MSM8960_LIQUID=y +CONFIG_MACH_MSM8930_CDP=y +CONFIG_MACH_MSM8930_MTP=y +CONFIG_MACH_MSM8930_FLUID=y +CONFIG_MACH_MSM8627_CDP=y +CONFIG_MACH_MSM8627_MTP=y +CONFIG_MACH_APQ8064_SIM=y +CONFIG_MACH_APQ8064_RUMI3=y +CONFIG_MACH_APQ8064_CDP=y +CONFIG_MACH_APQ8064_MTP=y +CONFIG_MACH_APQ8064_LIQUID=y +CONFIG_MACH_MPQ8064_CDP=y +CONFIG_MACH_MPQ8064_HRD=y +CONFIG_MACH_MPQ8064_DTV=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_KERNEL_PMEM_EBI_REGION=y +# 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_PCIE=y +CONFIG_MSM_BAM_DMUX=y +CONFIG_MSM_DSPS=y +CONFIG_MSM_IPC_ROUTER=y +CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_PIL_QDSP6V4=y +CONFIG_MSM_PIL_RIVA=y +CONFIG_MSM_PIL_TZAPPS=y +CONFIG_MSM_PIL_DSPS=y +CONFIG_MSM_PIL_VIDC=y +CONFIG_MSM_PIL_GSS=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_MODEM_8960=y +CONFIG_MSM_LPASS_8960=y +CONFIG_MSM_WCNSS_SSR_8960=y +CONFIG_MSM_GSS_SSR_8064=y +CONFIG_MSM_TZ_LOG=y +CONFIG_MSM_RPM_LOG=y +CONFIG_MSM_RPM_STATS_LOG=y +CONFIG_MSM_BUS_SCALING=y +CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED=y +CONFIG_MSM_WATCHDOG=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_MSM_QDSS=y +CONFIG_MSM_QDSS_ETM_DEFAULT_ENABLE=y +CONFIG_MSM_RTB=y +CONFIG_MSM_RTB_SEPARATE_CPUS=y +CONFIG_MSM_CACHE_ERP=y +CONFIG_MSM_L1_ERR_PANIC=y +CONFIG_MSM_L2_ERP_PRINT_ACCESS_ERRORS=y +CONFIG_MSM_L2_ERP_1BIT_PANIC=y +CONFIG_MSM_L2_ERP_2BIT_PANIC=y +CONFIG_MSM_DCVS=y +CONFIG_MSM_CACHE_DUMP=y +CONFIG_MSM_CACHE_DUMP_ON_PANIC=y +CONFIG_MSM_HSIC_SYSMON=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_PCI=y +CONFIG_PCI_MSI=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_CC_STACKPROTECTOR=y +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_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_ROUTE_VERBOSE=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_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_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=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_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_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_NF_CONNTRACK_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_CLS_FW=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_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=y +CONFIG_CFG80211=m +# CONFIG_CFG80211_WEXT is not set +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8XXX_VIBRATOR=y +CONFIG_QSEECOM=y +CONFIG_USB_HSIC_SMSC_HUB=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_KS8851=m +# CONFIG_MSM_RMNET is not set +CONFIG_MSM_RMNET_BAM=y +CONFIG_MSM_RMNET_SMUX=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_USB_USBNET=y +CONFIG_MSM_RMNET_USB=y +CONFIG_WCNSS_CORE=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_MATRIX=y +CONFIG_KEYBOARD_PMIC8XXX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MXT=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_PMIC8XXX_PWRKEY=y +CONFIG_INPUT_UINPUT=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_N_SMUX=y +CONFIG_N_SMUX_LOOPBACK=y +CONFIG_SMUX_CTL=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_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=m +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_ISL9519_CHARGER=y +CONFIG_SMB349_CHARGER=y +CONFIG_PM8921_CHARGER=y +CONFIG_PM8921_BMS=y +CONFIG_SENSORS_PM8XXX_ADC=y +CONFIG_SENSORS_EPM_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_TSENS8960=y +CONFIG_THERMAL_PM8XXX=y +CONFIG_THERMAL_MONITOR=y +CONFIG_MFD_PM8921_CORE=y +CONFIG_MFD_PM8821_CORE=y +CONFIG_MFD_PM8038_CORE=y +CONFIG_MFD_PM8XXX_SPK=y +CONFIG_MFD_PM8XXX_BATT_ALARM=y +CONFIG_WCD9304_CODEC=y +CONFIG_WCD9310_CODEC=y +CONFIG_REGULATOR_PM8XXX=y +CONFIG_REGULATOR_MSM_GPIO=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_USER_RC_INPUT=y +CONFIG_IR_GPIO_CIR=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_IMX074=y +CONFIG_MT9M114=y +CONFIG_IMX074_ACT=y +CONFIG_MSM_CAMERA_FLASH_SC628A=y +CONFIG_OV2720=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_ACTUATOR=y +CONFIG_MSM_EEPROM=y +CONFIG_IMX074_EEPROM=y +CONFIG_IMX091_EEPROM=y +CONFIG_MSM_GEMINI=y +CONFIG_S5K3L1YX=y +CONFIG_IMX091=y +CONFIG_RADIO_IRIS=y +CONFIG_RADIO_IRIS_TRANSPORT=m +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_MSM_KGSL_PAGE_TABLE_COUNT=24 +CONFIG_FB=y +CONFIG_FB_VIRTUAL=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_OVERLAY0_WRITEBACK=y +CONFIG_FB_MSM_OVERLAY1_WRITEBACK=y +CONFIG_FB_MSM_WRITEBACK_MSM_PANEL=y +CONFIG_FB_MSM_LVDS_MIPI_PANEL_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_DYNAMIC_MINORS=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_MSM8960=y +CONFIG_HID_APPLE=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_MON=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_EHCI_MSM_HSIC=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_SERIAL=y +CONFIG_USB_SERIAL_QUALCOMM=y +CONFIG_USB_SERIAL_CSVT=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_QCOM_DIAG_BRIDGE=y +CONFIG_USB_QCOM_MDM_BRIDGE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_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_CLKGATE=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_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_SDC3_WP_SUPPORT=y +CONFIG_MMC_MSM_SPS_SUPPORT=y +CONFIG_LEDS_PM8XXX=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +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_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_MSM_SSBI=y +CONFIG_SPS=y +CONFIG_SPS_SUPPORT_BAMDMA=y +CONFIG_MSM_IOMMU=y +CONFIG_MOBICORE_SUPPORT=m +CONFIG_MOBICORE_API=m +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_FUSE_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_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_LOCKUP_DETECTOR=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_KMEMLEAK=y +CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_ATOMIC_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DEBUG_LIST=y +CONFIG_FAULT_INJECTION=y +CONFIG_FAILSLAB=y +CONFIG_FAIL_PAGE_ALLOC=y +CONFIG_FAULT_INJECTION_DEBUG_FS=y +CONFIG_FAULT_INJECTION_STACKTRACE_FILTER=y +CONFIG_DEBUG_PAGEALLOC=y +CONFIG_ENABLE_DEFAULT_TRACERS=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_PID_IN_CONTEXTIDR=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/msm9615_defconfig b/arch/arm/configs/msm9615_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..37bc41615aa106be1819f07e2d208ef3ccd1e6f6 --- /dev/null +++ b/arch/arm/configs/msm9615_defconfig @@ -0,0 +1,297 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +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_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y +# CONFIG_PERF_EVENTS is not set +CONFIG_PROFILING=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_EFI_PARTITION=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM9615=y +CONFIG_MACH_MSM9615_CDP=y +CONFIG_MACH_MSM9615_MTP=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_CPU_HAS_L2_PMU=y +# 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_BAM_DMUX=y +# CONFIG_MSM_RESET_MODEM is not set +CONFIG_MSM_IPC_ROUTER=y +CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +# CONFIG_MSM_SYSMON_COMM is not set +CONFIG_MSM_MODEM_8960=y +CONFIG_MSM_LPASS_8960=y +CONFIG_MSM_RPM_LOG=y +CONFIG_MSM_RPM_STATS_LOG=y +CONFIG_MSM_DIRECT_SCLK_ACCESS=y +CONFIG_MSM_BUS_SCALING=y +CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED=y +CONFIG_MSM_WATCHDOG=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_SWP_EMULATE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +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_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +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_INET6_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET6_XFRM_MODE_TUNNEL is not set +# CONFIG_INET6_XFRM_MODE_BEET is not set +# CONFIG_IPV6_SIT is not set +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +# CONFIG_ANDROID_PARANOID_NETWORK is not set +CONFIG_NETFILTER=y +CONFIG_NETFILTER_DEBUG=y +CONFIG_NETFILTER_NETLINK_QUEUE=y +CONFIG_NETFILTER_NETLINK_LOG=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SIP=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NETFILTER_XT_MARK=y +CONFIG_NETFILTER_XT_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_IP_SET=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_TARGET_REJECT_SKERR=y +CONFIG_IP_NF_TARGET_ULOG=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_TARGET_ECN=y +CONFIG_IP_NF_TARGET_TTL=y +CONFIG_IP_NF_RAW=y +CONFIG_NF_CONNTRACK_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_TARGET_REJECT_SKERR=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_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_ANDROID_PMEM is not set +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_MSM_RMNET is not set +CONFIG_MSM_RMNET_BAM=y +CONFIG_ATH6K_LEGACY_EXT=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_MISC=y +CONFIG_INPUT_PMIC8XXX_PWRKEY=y +CONFIG_INPUT_UINPUT=y +CONFIG_SERIO_LIBPS2=y +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=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_HW_RANDOM_MSM=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_MSM_CTRL=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +# CONFIG_BATTERY_MSM is not set +CONFIG_SENSORS_PM8XXX_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_TSENS8960=y +CONFIG_THERMAL_PM8XXX=y +CONFIG_MFD_PM8018_CORE=y +CONFIG_WCD9310_CODEC=y +CONFIG_REGULATOR_PM8XXX=y +CONFIG_REGULATOR_MSM_GPIO=y +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_MDM9615=y +# CONFIG_HID_SUPPORT is not set +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_MSM=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_CI13XXX_MSM=m +CONFIG_USB_CI13XXX_MSM_HSIC=m +CONFIG_USB_G_ANDROID=y +CONFIG_RMNET_SMD_CTL_CHANNEL="DATA36_CNTL" +CONFIG_RMNET_SMD_DATA_CHANNEL="DATA36" +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_CLKGATE=y +CONFIG_MMC_EMBEDDED_SDIO=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_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SPS_SUPPORT=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_PM8XXX=y +CONFIG_SWITCH=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_INTF_ALARM is not set +# CONFIG_RTC_DRV_MSM is not set +CONFIG_RTC_DRV_PM8XXX=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_LOGGER=y +CONFIG_MSM_SSBI=y +CONFIG_SPS=y +CONFIG_USB_BAM=y +CONFIG_SPS_SUPPORT_BAMDMA=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=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_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_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_DES=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEFLATE=y +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_DEV_QCRYPTO=m +CONFIG_CRYPTO_DEV_QCE=m +CONFIG_CRYPTO_DEV_QCEDEV=m +CONFIG_CRC_CCITT=y +CONFIG_LIBCRC32C=y diff --git a/arch/arm/configs/msm9625_defconfig b/arch/arm/configs/msm9625_defconfig new file mode 100644 index 0000000000000000000000000000000000000000..89fb8888f165031b1c9304feedd02a8b8bb30054 --- /dev/null +++ b/arch/arm/configs/msm9625_defconfig @@ -0,0 +1,107 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_SPARSE_IRQ=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_KALLSYMS_ALL=y +CONFIG_EMBEDDED=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_MSM9625=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_CPU_HAS_L2_PMU=y +# CONFIG_MSM_FIQ_SUPPORT is not set +# CONFIG_MSM_PROC_COMM is not set +CONFIG_MSM_DIRECT_SCLK_ACCESS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x19000000 +CONFIG_USE_OF=y +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ATAG_DTB_COMPAT=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +# CONFIG_SUSPEND is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_MISC_DEVICES=y +# CONFIG_ANDROID_PMEM is not set +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=m +CONFIG_SERIO_LIBPS2=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM_HSL=y +CONFIG_SERIAL_MSM_HSL_CONSOLE=y +CONFIG_HW_RANDOM=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=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_VFAT_FS=y +CONFIG_TMPFS=y +# CONFIG_MISC_FILESYSTEMS is not set +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_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=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_CRC16=y +CONFIG_LIBCRC32C=y diff --git a/arch/arm/include/asm/arch_timer.h b/arch/arm/include/asm/arch_timer.h new file mode 100644 index 0000000000000000000000000000000000000000..20982887927c5fe78701f8438d8d662be5c02c09 --- /dev/null +++ b/arch/arm/include/asm/arch_timer.h @@ -0,0 +1,25 @@ +#ifndef __ASMARM_ARCH_TIMER_H +#define __ASMARM_ARCH_TIMER_H + +#include + +struct arch_timer { + struct resource res[2]; +}; + +#ifdef CONFIG_ARM_ARCH_TIMER +int arch_timer_register(struct arch_timer *); +int arch_timer_of_register(void); +#else +static inline int arch_timer_register(struct arch_timer *at) +{ + return -ENXIO; +} + +static inline int arch_timer_of_register(void) +{ + return -ENXIO; +} +#endif + +#endif diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index 5684cbc52efe51943a3681b031cdab78e0e1d738..d021905cfd163a753057227ee9100f93af9227ec 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -88,6 +88,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. @@ -108,6 +123,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 *); }; @@ -134,6 +151,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 @@ -154,6 +173,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/delay.h b/arch/arm/include/asm/delay.h index b2deda1815496d83ab3dde5c9eb2c4f5194c7b1e..5c6b9a3c5df5272f8b67825324ff88b4037a04cb 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 cb3b7c981c4b729c31c8dbf33c9fa4334edfc07d..dc988ff0544623bec0344dc4ca3a30481eb5c907 100644 --- a/arch/arm/include/asm/dma-mapping.h +++ b/arch/arm/include/asm/dma-mapping.h @@ -141,6 +141,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 @@ -330,6 +370,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 3d2220498abc2db2d98378c94a3309c71e91ccee..b216a00cac63268d5c8008b16639210964f96291 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 @@ -31,8 +32,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 @@ -60,6 +66,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__( \ @@ -67,6 +84,7 @@ : : "r" (x)); \ isb(); \ } while (0) +#endif #define modify_domain(dom,type) \ do { \ diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b742a1383e716ab1b3c8f3286756e27689..ec4b8b8107ddbcba36b80c6bb03cf7874ef0f79a 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -33,11 +33,22 @@ struct fiq_handler { void *dev_id; }; +#ifdef CONFIG_FIQ extern int claim_fiq(struct fiq_handler *f); extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +#else +static inline int claim_fiq(struct fiq_handler *f) +{ + return 0; +} +static inline void release_fiq(struct fiq_handler *f) { } +static inline void set_fiq_handler(void *start, unsigned int length) { } +static inline void enable_fiq(int fiq) { } +static inline void disable_fiq(int fiq) { } +#endif /* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index 2740c2a2df639361617f6fe484ead14f8625eaf2..3d7351c844aac0ae2392d441796ce9904dcaf717 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include #include -#define NR_IPI 6 +#define NR_IPI 7 typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/hardware/cache-l2x0.h b/arch/arm/include/asm/hardware/cache-l2x0.h index bd2c6a530a584d8abfd9d2b33082cdba9e89ecad..a244039eb976c837a82932e4002b418489d7ee29 100644 --- a/arch/arm/include/asm/hardware/cache-l2x0.h +++ b/arch/arm/include/asm/hardware/cache-l2x0.h @@ -91,6 +91,7 @@ #define L2X0_AUX_CTRL_WAY_SIZE_SHIFT 17 #define L2X0_AUX_CTRL_WAY_SIZE_MASK (0x7 << 17) #define L2X0_AUX_CTRL_SHARE_OVERRIDE_SHIFT 22 +#define L2X0_AUX_CTRL_L2_FORCE_NWA_SHIFT 23 #define L2X0_AUX_CTRL_NS_LOCKDOWN_SHIFT 26 #define L2X0_AUX_CTRL_NS_INT_CTRL_SHIFT 27 #define L2X0_AUX_CTRL_DATA_PREFETCH_SHIFT 28 @@ -105,7 +106,18 @@ #define REV_PL310_R2P0 4 +#define L2X0_LATENCY_CTRL_SETUP_SHIFT 0 +#define L2X0_LATENCY_CTRL_RD_SHIFT 4 +#define L2X0_LATENCY_CTRL_WR_SHIFT 8 + +#define L2X0_PREFETCH_CTRL_OFFSET_SHIFT 0 +#define L2X0_PREFETCH_CTRL_WRAP8_INC_SHIFT 23 +#define L2X0_PREFETCH_CTRL_WRAP8_SHIFT 30 + #ifndef __ASSEMBLY__ +extern void l2cc_suspend(void); +extern void l2cc_resume(void); +extern void l2x0_cache_sync(void); extern void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask); #if defined(CONFIG_CACHE_L2X0) && defined(CONFIG_OF) extern int l2x0_of_init(u32 aux_val, u32 aux_mask); diff --git a/arch/arm/include/asm/hardware/gic.h b/arch/arm/include/asm/hardware/gic.h index 4b1ce6cd477f06299d22a6c4bdb24cc881aa0ab1..3fb0a1c396b8b4a055988f989afbdda0cc7ff2eb 100644 --- a/arch/arm/include/asm/hardware/gic.h +++ b/arch/arm/include/asm/hardware/gic.h @@ -22,6 +22,7 @@ #define GIC_DIST_CTRL 0x000 #define GIC_DIST_CTR 0x004 +#define GIC_DIST_ISR 0x080 #define GIC_DIST_ENABLE_SET 0x100 #define GIC_DIST_ENABLE_CLEAR 0x180 #define GIC_DIST_PENDING_SET 0x200 @@ -39,19 +40,31 @@ struct device_node; extern struct irq_chip gic_arch_extn; void gic_init_bases(unsigned int, int, void __iomem *, void __iomem *, - u32 offset, struct device_node *); + u32 offset); int gic_of_init(struct device_node *node, struct device_node *parent); void gic_secondary_init(unsigned int); void gic_handle_irq(struct pt_regs *regs); void gic_cascade_irq(unsigned int gic_nr, unsigned int irq); void gic_raise_softirq(const struct cpumask *mask, unsigned int irq); - +#ifdef CONFIG_ARM_GIC +void gic_set_irq_secure(unsigned int irq); +#else +static inline void gic_set_irq_secure(unsigned int irq) { } +#endif static inline void gic_init(unsigned int nr, int start, void __iomem *dist , void __iomem *cpu) { - gic_init_bases(nr, start, dist, cpu, 0, NULL); + gic_init_bases(nr, start, dist, cpu, 0); } +bool gic_is_spi_pending(unsigned int irq); +void gic_clear_spi_pending(unsigned int irq); +void gic_set_irq_secure(unsigned int irq); +#endif +#ifdef CONFIG_ARCH_MSM8625 +void msm_gic_save(bool modem_wake, int from_idle); +void msm_gic_restore(void); +void core1_gic_configure_and_raise(void); #endif #endif diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index 9af5563dd3ebbc6be0f53448e107a05249cca859..42fef7cca6fd9b4687f8345b99888c3ef1a1d0f7 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -27,6 +27,7 @@ #include #include #include +#include /* * ISA I/O bus memory addresses are 1:1 with the physical address. @@ -47,13 +48,52 @@ extern void __raw_readsb(const void __iomem *addr, void *data, int bytelen); extern void __raw_readsw(const void __iomem *addr, void *data, int wordlen); extern void __raw_readsl(const void __iomem *addr, void *data, int longlen); -#define __raw_writeb(v,a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v)) -#define __raw_writew(v,a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v)) -#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v)) +/* + * There may be cases when clients don't want to support or can't support the + * logging. The appropriate functions can be used but clients should carefully + * consider why they can't support the logging. + */ -#define __raw_readb(a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a)) -#define __raw_readw(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a)) -#define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a)) +#define __raw_write_logged(v, a, _t) ({ \ + int _ret; \ + void *_addr = (void *)(a); \ + _ret = uncached_logk(LOGK_WRITEL, _addr); \ + ETB_WAYPOINT; \ + __raw_write##_t##_no_log((v), _addr); \ + if (_ret) \ + LOG_BARRIER; \ + }) + + +#define __raw_writeb_no_log(v, a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v)) +#define __raw_writew_no_log(v, a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v)) +#define __raw_writel_no_log(v, a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v)) + + +#define __raw_writeb(v, a) __raw_write_logged((v), (a), b) +#define __raw_writew(v, a) __raw_write_logged((v), (a), w) +#define __raw_writel(v, a) __raw_write_logged((v), (a), l) + +#define __raw_readb_no_log(a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a)) +#define __raw_readw_no_log(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a)) +#define __raw_readl_no_log(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a)) + +#define __raw_read_logged(a, _l, _t) ({ \ + unsigned _t __a; \ + void *_addr = (void *)(a); \ + int _ret; \ + _ret = uncached_logk(LOGK_READL, _addr); \ + ETB_WAYPOINT; \ + __a = __raw_read##_l##_no_log(_addr);\ + if (_ret) \ + LOG_BARRIER; \ + __a; \ + }) + + +#define __raw_readb(a) __raw_read_logged((a), b, char) +#define __raw_readw(a) __raw_read_logged((a), w, short) +#define __raw_readl(a) __raw_read_logged((a), l, int) /* * Architecture ioremap implementation. diff --git a/arch/arm/include/asm/localtimer.h b/arch/arm/include/asm/localtimer.h index f77ffc1eb0c2b0d2e5a6125169da694b33bd2a0f..87de9155726ed5d2ea8e8cdc8cb1317191048237 100644 --- a/arch/arm/include/asm/localtimer.h +++ b/arch/arm/include/asm/localtimer.h @@ -14,6 +14,11 @@ struct clock_event_device; +/* + * Setup a per-cpu timer, whether it be a local timer or dummy broadcast + */ +void percpu_timer_setup(void); + struct local_timer_ops { int (*setup)(struct clock_event_device *); void (*stop)(struct clock_event_device *); diff --git a/arch/arm/include/asm/mach/arch.h b/arch/arm/include/asm/mach/arch.h index d7692cafde7fdd3d0b4e297620fbf2dd45d611d6..0a45dee98ac8d041b0b17089c148719f5bcead80 100644 --- a/arch/arm/include/asm/mach/arch.h +++ b/arch/arm/include/asm/mach/arch.h @@ -22,7 +22,7 @@ struct machine_desc { const char *const *dt_compat; /* array of device tree * 'compatible' strings */ - unsigned int nr_irqs; /* number of IRQs */ + int nr_irqs; /* number of IRQs */ #ifdef CONFIG_ZONE_DMA unsigned long dma_zone_size; /* size of DMA-able area */ @@ -39,6 +39,7 @@ struct machine_desc { struct meminfo *); void (*reserve)(void);/* reserve mem blocks */ void (*map_io)(void);/* IO mapping function */ + void (*init_very_early)(void); void (*init_early)(void); void (*init_irq)(void); struct sys_timer *timer; /* system tick timer */ diff --git a/arch/arm/include/asm/mach/flash.h b/arch/arm/include/asm/mach/flash.h index 4ca69fe2c850c0a995574ffe250444d8062d8d95..36938ea24a3dd54ef34f69e0a0f75e3df2ad194b 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 b36f3654bf54ebcc9e1c9617663c706d81bcfd97..5f731dff755b685cc86c326c9dad54d4e9fe718d 100644 --- a/arch/arm/include/asm/mach/map.h +++ b/arch/arm/include/asm/mach/map.h @@ -9,6 +9,9 @@ * * Page table mapping constructs and function prototypes */ +#ifndef __ASM_ARM_MACH_MAP_H +#define __ASM_ARM_MACH_MAP_H + #include struct map_desc { @@ -30,6 +33,9 @@ struct map_desc { #define MT_MEMORY_DTCM 12 #define MT_MEMORY_ITCM 13 #define MT_MEMORY_SO 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); @@ -41,6 +47,11 @@ extern const struct mem_type *get_mem_type(unsigned int type); */ extern int ioremap_page(unsigned long virt, unsigned long phys, const struct mem_type *mtype); + +extern int ioremap_pages(unsigned long virt, unsigned long phys, + unsigned long size, const struct mem_type *mtype); #else #define iotable_init(map,num) do { } while (0) #endif + +#endif diff --git a/arch/arm/include/asm/mach/mmc.h b/arch/arm/include/asm/mach/mmc.h index bca864ac945f60c20a41fa7b888d64244d6fd213..745a3a4be966f07c18b2a233f14ef1172632fe25 100644 --- a/arch/arm/include/asm/mach/mmc.h +++ b/arch/arm/include/asm/mach/mmc.h @@ -7,6 +7,13 @@ #include #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 +22,103 @@ 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 low_vol_level; + unsigned int high_vol_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 + * + * Some regulators (like gpio-regulators, LVS (low voltage swtiches) + * PMIC regulators) dont have the capability to call + * regulator_set_voltage or regulator_set_optimum_mode + * Use this variable to indicate if its a such regulator or not + */ + 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 msm_mmc_bus_voting_data { + struct msm_bus_scale_pdata *use_cases; + unsigned int *bw_vecs; + unsigned int bw_vecs_size; +}; + struct mmc_platform_data { unsigned int ocr_mask; /* available voltages */ int built_in; /* built-in device flag */ @@ -23,6 +127,40 @@ 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; + /* Indicates the polarity of the GPIO line when card is inserted */ + bool is_status_gpio_active_low; + unsigned int sdiowakeup_irq; + unsigned long irq_flags; + unsigned long mmc_bus_width; + int (*wpswitch) (struct device *); + unsigned int msmsdcc_fmin; + unsigned int msmsdcc_fmid; + unsigned int msmsdcc_fmax; + bool nonremovable; + bool pclk_src_dfab; + unsigned int mpm_sdiowakeup_int; + 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; + bool disable_bam; + bool disable_runtime_pm; + bool disable_cmd23; + u32 cpu_dma_latency; + struct msm_mmc_bus_voting_data *msm_bus_voting_data; }; #endif diff --git a/arch/arm/include/asm/memory.h b/arch/arm/include/asm/memory.h index fcb575747e5eb66082a9a7c96bbc6391a767faf5..17b7b317534c7c20870ffee6f23df9a87c456829 100644 --- a/arch/arm/include/asm/memory.h +++ b/arch/arm/include/asm/memory.h @@ -280,6 +280,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/mmu_writeable.h b/arch/arm/include/asm/mmu_writeable.h new file mode 100644 index 0000000000000000000000000000000000000000..96d348cb04e0faa9b129307390831d76ccc760bf --- /dev/null +++ b/arch/arm/include/asm/mmu_writeable.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 _MMU_WRITEABLE_H +#define _MMU_WRITEABLE_H + +#ifdef CONFIG_STRICT_MEMORY_RWX +void mem_text_writeable_spinlock(unsigned long *flags); +void mem_text_address_writeable(unsigned long); +void mem_text_address_restore(void); +void mem_text_writeable_spinunlock(unsigned long *flags); +#else +static inline void mem_text_writeable_spinlock(unsigned long *flags) {}; +static inline void mem_text_address_writeable(unsigned long addr) {}; +static inline void mem_text_address_restore(void) {}; +static inline void mem_text_writeable_spinunlock(unsigned long *flags) {}; +#endif + +void mem_text_write_kernel_word(unsigned long *addr, unsigned long word); + +#endif diff --git a/arch/arm/include/asm/mutex.h b/arch/arm/include/asm/mutex.h index 93226cf23ae0a838e192be1d675ee886be9dd52e..fd3f17ef94a04e4d2efde1f92db298728d74455e 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 5838361c48b335040c892233da299605ce9e089f..afc1ca7ec9c53d5fd1771d4e371e1be8badf1bd8 100644 --- a/arch/arm/include/asm/page.h +++ b/arch/arm/include/asm/page.h @@ -167,6 +167,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 00cbe10a50e3693b32afe48d0d20e3787e9183d0..2fecc60fc958bb2aaaccdc20f1915cd5446267af 100644 --- a/arch/arm/include/asm/perf_event.h +++ b/arch/arm/include/asm/perf_event.h @@ -23,6 +23,9 @@ enum arm_perf_pmu_ids { ARM_PERF_PMU_ID_CA5, ARM_PERF_PMU_ID_CA15, ARM_PERF_PMU_ID_CA7, + 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 0000000000000000000000000000000000000000..8d21dcd121c1d53826c845cd11fb00ca9d467645 --- /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 f66626d71e7d1a304ad2c750fc4b00be9a436901..7b6f42a4b0b3ca44da1a1d15d9ffbd6a1e4e0f57 100644 --- a/arch/arm/include/asm/pgtable.h +++ b/arch/arm/include/asm/pgtable.h @@ -103,16 +103,30 @@ extern pgprot_t pgprot_kernel; #define pgprot_stronglyordered(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED) +#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__ */ @@ -306,7 +320,7 @@ static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) * into virtual address `from' */ #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) #define pgtable_cache_init() do { } while (0) diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h index 90114faa9f3c7c087f6fce6b871b16ab5e433b44..1e54b58dfc9f165c44467e3d061fbad7f42e726d 100644 --- a/arch/arm/include/asm/pmu.h +++ b/arch/arm/include/asm/pmu.h @@ -21,6 +21,7 @@ */ enum arm_pmu_type { ARM_PMU_DEVICE_CPU = 0, + ARM_PMU_DEVICE_L2 = 1, ARM_NUM_PMU_DEVICES, }; @@ -108,7 +109,9 @@ struct arm_pmu { cpumask_t active_irqs; const char *name; irqreturn_t (*handle_irq)(int irq_num, void *dev); - void (*enable)(struct hw_perf_event *evt, int idx); + int (*request_pmu_irq)(int irq, irq_handler_t *irq_h); + void (*free_pmu_irq)(int irq); + void (*enable)(struct hw_perf_event *evt, int idx, int cpu); void (*disable)(struct hw_perf_event *evt, int idx); int (*get_event_idx)(struct pmu_hw_events *hw_events, struct hw_perf_event *hwc); diff --git a/arch/arm/include/asm/processor.h b/arch/arm/include/asm/processor.h index 5ac8d3d3e0259cc8ceaa16b7ac7caa6facf177f7..07209d7538b89bff2eb32f2ba86243cbcf43cd64 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 0000000000000000000000000000000000000000..702b6696131b341d74e854a37bebfae9a6e6f0cb --- /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 23ebc0c82a3975ae5c455dd39598e93ab33922e7..d1f97097e9b2aed1bfd44c4d84a8efe978346b72 100644 --- a/arch/arm/include/asm/setup.h +++ b/arch/arm/include/asm/setup.h @@ -211,7 +211,8 @@ extern struct meminfo meminfo; for (iter = 0; iter < (mi)->nr_banks; iter++) #define bank_pfn_start(bank) __phys_to_pfn((bank)->start) -#define bank_pfn_end(bank) __phys_to_pfn((bank)->start + (bank)->size) +#define bank_pfn_end(bank) (__phys_to_pfn((bank)->start) + \ + __phys_to_pfn((bank)->size)) #define bank_pfn_size(bank) ((bank)->size >> PAGE_SHIFT) #define bank_phys_start(bank) (bank)->start #define bank_phys_end(bank) ((bank)->start + (bank)->size) @@ -221,6 +222,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/spinlock.h b/arch/arm/include/asm/spinlock.h index 65fa3c88095c3c7b324a5027610133dc1f825d4b..582c9b36616517a0f54f1be57d2b73a3489e02d2 100644 --- a/arch/arm/include/asm/spinlock.h +++ b/arch/arm/include/asm/spinlock.h @@ -58,6 +58,7 @@ static inline void dsb_sev(void) #endif } +#ifndef CONFIG_ARM_TICKET_LOCKS /* * ARMv6 Spin-locking. * @@ -126,6 +127,131 @@ static inline void arch_spin_unlock(arch_spinlock_t *lock) dsb_sev(); } +#else +/* + * ARM Ticket spin-locking + * + * Ticket locks are conceptually two parts, one indicating the current head of + * the queue, and the other indicating the current tail. The lock is acquired + * by atomically noting the tail and incrementing it by one (thus adding + * ourself to the queue and noting our position), then waiting until the head + * becomes equal to the the initial value of the tail. + * + * Unlocked value: 0 + * Locked value: now_serving != next_ticket + * + * 31 17 16 15 14 0 + * +----------------------------------------------------+ + * | now_serving | next_ticket | + * +----------------------------------------------------+ + */ + +#define TICKET_SHIFT 16 +#define TICKET_BITS 16 +#define TICKET_MASK 0xFFFF + +#define arch_spin_lock_flags(lock, flags) arch_spin_lock(lock) + +static inline void arch_spin_lock(arch_spinlock_t *lock) +{ + unsigned long tmp, ticket, next_ticket; + + /* Grab the next ticket and wait for it to be "served" */ + __asm__ __volatile__( +"1: ldrex %[ticket], [%[lockaddr]]\n" +" uadd16 %[next_ticket], %[ticket], %[val1]\n" +" strex %[tmp], %[next_ticket], [%[lockaddr]]\n" +" teq %[tmp], #0\n" +" bne 1b\n" +" uxth %[ticket], %[ticket]\n" +"2:\n" +#ifdef CONFIG_CPU_32v6K +" wfene\n" +#endif +" ldr %[tmp], [%[lockaddr]]\n" +" cmp %[ticket], %[tmp], lsr #16\n" +" bne 2b" + : [ticket]"=&r" (ticket), [tmp]"=&r" (tmp), [next_ticket]"=&r" (next_ticket) + : [lockaddr]"r" (&lock->lock), [val1]"r" (1) + : "cc"); + smp_mb(); +} + +static inline int arch_spin_trylock(arch_spinlock_t *lock) +{ + unsigned long tmp, ticket, next_ticket; + + /* Grab lock if now_serving == next_ticket and access is exclusive */ + __asm__ __volatile__( +" ldrex %[ticket], [%[lockaddr]]\n" +" ror %[tmp], %[ticket], #16\n" +" eors %[tmp], %[tmp], %[ticket]\n" +" bne 1f\n" +" uadd16 %[next_ticket], %[ticket], %[val1]\n" +" strex %[tmp], %[next_ticket], [%[lockaddr]]\n" +"1:" + : [ticket]"=&r" (ticket), [tmp]"=&r" (tmp), + [next_ticket]"=&r" (next_ticket) + : [lockaddr]"r" (&lock->lock), [val1]"r" (1) + : "cc"); + if (!tmp) + smp_mb(); + return !tmp; +} + +static inline void arch_spin_unlock(arch_spinlock_t *lock) +{ + unsigned long ticket, tmp; + + smp_mb(); + + /* Bump now_serving by 1 */ + __asm__ __volatile__( +"1: ldrex %[ticket], [%[lockaddr]]\n" +" uadd16 %[ticket], %[ticket], %[serving1]\n" +" strex %[tmp], %[ticket], [%[lockaddr]]\n" +" teq %[tmp], #0\n" +" bne 1b" + : [ticket]"=&r" (ticket), [tmp]"=&r" (tmp) + : [lockaddr]"r" (&lock->lock), [serving1]"r" (0x00010000) + : "cc"); + dsb_sev(); +} + +static inline void arch_spin_unlock_wait(arch_spinlock_t *lock) +{ + unsigned long ticket; + + /* Wait for now_serving == next_ticket */ + __asm__ __volatile__( +#ifdef CONFIG_CPU_32v6K +" cmpne %[lockaddr], %[lockaddr]\n" +"1: wfene\n" +#else +"1:\n" +#endif +" ldr %[ticket], [%[lockaddr]]\n" +" eor %[ticket], %[ticket], %[ticket], lsr #16\n" +" uxth %[ticket], %[ticket]\n" +" cmp %[ticket], #0\n" +" bne 1b" + : [ticket]"=&r" (ticket) + : [lockaddr]"r" (&lock->lock) + : "cc"); +} + +static inline int arch_spin_is_locked(arch_spinlock_t *lock) +{ + unsigned long tmp = ACCESS_ONCE(lock->lock); + return (((tmp >> TICKET_SHIFT) ^ tmp) & TICKET_MASK) != 0; +} + +static inline int arch_spin_is_contended(arch_spinlock_t *lock) +{ + unsigned long tmp = ACCESS_ONCE(lock->lock); + return ((tmp - (tmp >> TICKET_SHIFT)) & TICKET_MASK) > 1; +} +#endif /* * RWLOCKS diff --git a/arch/arm/include/asm/tlbflush.h b/arch/arm/include/asm/tlbflush.h index 85fe61e7320265e6932e8659d95c57c7712ad04d..d44d33f350fc92da19e57d0115092ec2e26896a7 100644 --- a/arch/arm/include/asm/tlbflush.h +++ b/arch/arm/include/asm/tlbflush.h @@ -409,7 +409,7 @@ local_flush_tlb_page(struct vm_area_struct *vma, unsigned long uaddr) tlb_op(TLB_V6_U_PAGE, "c8, c7, 1", uaddr); tlb_op(TLB_V6_D_PAGE, "c8, c6, 1", uaddr); tlb_op(TLB_V6_I_PAGE, "c8, c5, 1", uaddr); -#ifdef CONFIG_ARM_ERRATA_720789 +#if defined(CONFIG_ARM_ERRATA_720789) || defined(CONFIG_ARCH_MSM8X60) tlb_op(TLB_V7_UIS_PAGE, "c8, c3, 3", uaddr & PAGE_MASK); #else tlb_op(TLB_V7_UIS_PAGE, "c8, c3, 1", uaddr); @@ -439,7 +439,11 @@ static inline void local_flush_tlb_kernel_page(unsigned long kaddr) tlb_op(TLB_V6_U_PAGE, "c8, c7, 1", kaddr); tlb_op(TLB_V6_D_PAGE, "c8, c6, 1", kaddr); tlb_op(TLB_V6_I_PAGE, "c8, c5, 1", kaddr); +#ifdef CONFIG_ARCH_MSM8X60 + tlb_op(TLB_V7_UIS_PAGE, "c8, c3, 3", kaddr); +#else tlb_op(TLB_V7_UIS_PAGE, "c8, c3, 1", kaddr); +#endif if (tlb_flag(TLB_BARRIER)) { dsb(); diff --git a/arch/arm/include/asm/vfp.h b/arch/arm/include/asm/vfp.h index f4ab34fd4f72cc70ce0ece49eef7187f97e33763..5a1e789352d2f06a179342a179b8ea0d108993e7 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_pm_suspend(void); +void vfp_pm_resume(void); +#endif diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 7b787d642af4fe2ac3c9f81be97c8913ea8b2e95..22b0f1e255f0936542c8f72afd1b13aea3a83a75 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_ARM_CPU_SUSPEND) += sleep.o suspend.o obj-$(CONFIG_SMP) += smp.o smp_tlb.o obj-$(CONFIG_HAVE_ARM_SCU) += smp_scu.o obj-$(CONFIG_HAVE_ARM_TWD) += smp_twd.o +obj-$(CONFIG_ARM_ARCH_TIMER) += arch_timer.o obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o insn.o obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o insn.o obj-$(CONFIG_JUMP_LABEL) += jump_label.o insn.o patch.o diff --git a/arch/arm/kernel/arch_timer.c b/arch/arm/kernel/arch_timer.c new file mode 100644 index 0000000000000000000000000000000000000000..81a9a7101fffb15e7bc348aeede05989156ba2af --- /dev/null +++ b/arch/arm/kernel/arch_timer.c @@ -0,0 +1,378 @@ +/* + * linux/arch/arm/kernel/arch_timer.c + * + * Copyright (C) 2011 ARM Ltd. + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static unsigned long arch_timer_rate; +static int arch_timer_ppi; +static int arch_timer_ppi2; + +static struct clock_event_device __percpu **arch_timer_evt; + +/* + * Architected system timer support. + */ + +#define ARCH_TIMER_CTRL_ENABLE (1 << 0) +#define ARCH_TIMER_CTRL_IT_MASK (1 << 1) +#define ARCH_TIMER_CTRL_IT_STAT (1 << 2) + +#define ARCH_TIMER_REG_CTRL 0 +#define ARCH_TIMER_REG_FREQ 1 +#define ARCH_TIMER_REG_TVAL 2 + +static void arch_timer_reg_write(int reg, u32 val) +{ + switch (reg) { + case ARCH_TIMER_REG_CTRL: + asm volatile("mcr p15, 0, %0, c14, c2, 1" : : "r" (val)); + break; + case ARCH_TIMER_REG_TVAL: + asm volatile("mcr p15, 0, %0, c14, c2, 0" : : "r" (val)); + break; + } + + isb(); +} + +static u32 arch_timer_reg_read(int reg) +{ + u32 val; + + switch (reg) { + case ARCH_TIMER_REG_CTRL: + asm volatile("mrc p15, 0, %0, c14, c2, 1" : "=r" (val)); + break; + case ARCH_TIMER_REG_FREQ: + asm volatile("mrc p15, 0, %0, c14, c0, 0" : "=r" (val)); + break; + case ARCH_TIMER_REG_TVAL: + asm volatile("mrc p15, 0, %0, c14, c2, 0" : "=r" (val)); + break; + default: + BUG(); + } + + return val; +} + +static irqreturn_t arch_timer_handler(int irq, void *dev_id) +{ + struct clock_event_device *evt; + unsigned long ctrl; + + ctrl = arch_timer_reg_read(ARCH_TIMER_REG_CTRL); + if (ctrl & ARCH_TIMER_CTRL_IT_STAT) { + ctrl |= ARCH_TIMER_CTRL_IT_MASK; + arch_timer_reg_write(ARCH_TIMER_REG_CTRL, ctrl); + evt = *__this_cpu_ptr(arch_timer_evt); + evt->event_handler(evt); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void arch_timer_disable(void) +{ + unsigned long ctrl; + + ctrl = arch_timer_reg_read(ARCH_TIMER_REG_CTRL); + ctrl &= ~ARCH_TIMER_CTRL_ENABLE; + arch_timer_reg_write(ARCH_TIMER_REG_CTRL, ctrl); +} + +static void arch_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *clk) +{ + switch (mode) { + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + arch_timer_disable(); + break; + default: + break; + } +} + +static int arch_timer_set_next_event(unsigned long evt, + struct clock_event_device *unused) +{ + unsigned long ctrl; + + ctrl = arch_timer_reg_read(ARCH_TIMER_REG_CTRL); + ctrl |= ARCH_TIMER_CTRL_ENABLE; + ctrl &= ~ARCH_TIMER_CTRL_IT_MASK; + + arch_timer_reg_write(ARCH_TIMER_REG_CTRL, ctrl); + arch_timer_reg_write(ARCH_TIMER_REG_TVAL, evt); + + return 0; +} + +static int __cpuinit arch_timer_setup(struct clock_event_device *clk) +{ + /* setup clock event only once for CPU 0 */ + if (!smp_processor_id() && clk->irq == arch_timer_ppi) + return 0; + + /* Be safe... */ + arch_timer_disable(); + + clk->features = CLOCK_EVT_FEAT_ONESHOT; + clk->name = "arch_sys_timer"; + clk->rating = 450; + clk->set_mode = arch_timer_set_mode; + clk->set_next_event = arch_timer_set_next_event; + clk->irq = arch_timer_ppi; + + clockevents_config_and_register(clk, arch_timer_rate, + 0xf, 0x7fffffff); + + *__this_cpu_ptr(arch_timer_evt) = clk; + + enable_percpu_irq(clk->irq, 0); + if (arch_timer_ppi2) + enable_percpu_irq(arch_timer_ppi2, 0); + + return 0; +} + +/* Is the optional system timer available? */ +static int local_timer_is_architected(void) +{ + return (cpu_architecture() >= CPU_ARCH_ARMv7) && + ((read_cpuid_ext(CPUID_EXT_PFR1) >> 16) & 0xf) == 1; +} + +static int arch_timer_available(void) +{ + unsigned long freq; + + if (!local_timer_is_architected()) + return -ENXIO; + + if (arch_timer_rate == 0) { + arch_timer_reg_write(ARCH_TIMER_REG_CTRL, 0); + freq = arch_timer_reg_read(ARCH_TIMER_REG_FREQ); + + /* Check the timer frequency. */ + if (freq == 0) { + pr_warn("Architected timer frequency not available\n"); + return -EINVAL; + } + + arch_timer_rate = freq; + pr_info("Architected local timer running at %lu.%02luMHz.\n", + freq / 1000000, (freq / 10000) % 100); + } + + return 0; +} + +static inline cycle_t arch_counter_get_cntpct(void) +{ + u32 cvall, cvalh; + + asm volatile("mrrc p15, 0, %0, %1, c14" : "=r" (cvall), "=r" (cvalh)); + + return ((cycle_t) cvalh << 32) | cvall; +} + +static inline cycle_t arch_counter_get_cntvct(void) +{ + u32 cvall, cvalh; + + asm volatile("mrrc p15, 1, %0, %1, c14" : "=r" (cvall), "=r" (cvalh)); + + return ((cycle_t) cvalh << 32) | cvall; +} + +static cycle_t arch_counter_read(struct clocksource *cs) +{ + return arch_counter_get_cntpct(); +} + +#ifdef ARCH_HAS_READ_CURRENT_TIMER +int read_current_timer(unsigned long *timer_val) +{ + *timer_val = (unsigned long)arch_counter_get_cntpct(); + return 0; +} +#endif + +static struct clocksource clocksource_counter = { + .name = "arch_sys_counter", + .rating = 400, + .read = arch_counter_read, + .mask = CLOCKSOURCE_MASK(56), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static u32 arch_counter_get_cntvct32(void) +{ + cycle_t cntvct; + + cntvct = arch_counter_get_cntvct(); + + /* + * The sched_clock infrastructure only knows about counters + * with at most 32bits. Forget about the upper 24 bits for the + * time being... + */ + return (u32)(cntvct & (u32)~0); +} + +static u32 notrace arch_timer_update_sched_clock(void) +{ + return arch_counter_get_cntvct32(); +} + +static void __cpuinit arch_timer_stop(struct clock_event_device *clk) +{ + pr_debug("arch_timer_teardown disable IRQ%d cpu #%d\n", + clk->irq, smp_processor_id()); + disable_percpu_irq(clk->irq); + if (arch_timer_ppi2) + disable_percpu_irq(arch_timer_ppi2); + arch_timer_set_mode(CLOCK_EVT_MODE_UNUSED, clk); +} + +static struct local_timer_ops arch_timer_ops __cpuinitdata = { + .setup = arch_timer_setup, + .stop = arch_timer_stop, +}; + +static int __init arch_timer_common_register(void) +{ + int err; + + err = arch_timer_available(); + if (err) + return err; + + arch_timer_evt = alloc_percpu(struct clock_event_device *); + if (!arch_timer_evt) + return -ENOMEM; + + clocksource_register_hz(&clocksource_counter, arch_timer_rate); + + setup_sched_clock(arch_timer_update_sched_clock, 32, arch_timer_rate); + +#ifdef ARCH_HAS_READ_CURRENT_TIMER + set_delay_fn(read_current_timer_delay_loop); +#endif + + err = request_percpu_irq(arch_timer_ppi, arch_timer_handler, + "arch_timer", arch_timer_evt); + if (err) { + pr_err("arch_timer: can't register interrupt %d (%d)\n", + arch_timer_ppi, err); + goto out_free; + } + + if (arch_timer_ppi2) { + err = request_percpu_irq(arch_timer_ppi2, arch_timer_handler, + "arch_timer", arch_timer_evt); + if (err) { + pr_err("arch_timer: can't register interrupt %d (%d)\n", + arch_timer_ppi2, err); + arch_timer_ppi2 = 0; + goto out_free_irq; + } + } + + err = local_timer_register(&arch_timer_ops); + if (err) + goto out_free_irq; + percpu_timer_setup(); + + return 0; + +out_free_irq: + free_percpu_irq(arch_timer_ppi, arch_timer_evt); + if (arch_timer_ppi2) + free_percpu_irq(arch_timer_ppi2, arch_timer_evt); + +out_free: + free_percpu(arch_timer_evt); + + return err; +} + +int __init arch_timer_register(struct arch_timer *at) +{ + if (at->res[0].start <= 0 || !(at->res[0].flags & IORESOURCE_IRQ)) + return -EINVAL; + + arch_timer_ppi = at->res[0].start; + + if (at->res[1].start > 0 && (at->res[1].flags & IORESOURCE_IRQ)) + arch_timer_ppi2 = at->res[1].start; + + return arch_timer_common_register(); +} + +#ifdef CONFIG_OF +static const struct of_device_id arch_timer_of_match[] __initconst = { + { .compatible = "arm,armv7-timer", }, + {}, +}; + +int __init arch_timer_of_register(void) +{ + struct device_node *np; + u32 freq; + int ret; + + np = of_find_matching_node(NULL, arch_timer_of_match); + if (!np) { + pr_err("arch_timer: can't find DT node\n"); + return -ENODEV; + } + + /* Try to determine the frequency from the device tree or CNTFRQ */ + if (!of_property_read_u32(np, "clock-frequency", &freq)) + arch_timer_rate = freq; + + ret = irq_of_parse_and_map(np, 0); + if (ret <= 0) { + pr_err("arch_timer: interrupt not specified in timer node\n"); + return -ENODEV; + } + arch_timer_ppi = ret; + ret = irq_of_parse_and_map(np, 1); + if (ret > 0) + arch_timer_ppi2 = ret; + pr_info("arch_timer: found %s irqs %d %d\n", + np->name, arch_timer_ppi, arch_timer_ppi2); + + return arch_timer_common_register(); +} +#endif diff --git a/arch/arm/kernel/armksyms.c b/arch/arm/kernel/armksyms.c index b57c75e0b01f975defe734de3cce7c6ecfb34dda..f1a50f37efddbc3912e6552d21f5fa937a2e19da 100644 --- a/arch/arm/kernel/armksyms.c +++ b/arch/arm/kernel/armksyms.c @@ -48,10 +48,6 @@ extern void __aeabi_ulcmp(void); extern void fpundefinstr(void); - /* 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 7fd3ad048da9b7318ed516edc81ae67cb95e0c52..7c44acd07b7a0a1f91796caa967daae6e0172180 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -713,7 +713,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/ftrace.c b/arch/arm/kernel/ftrace.c index bf17145177c9f0782996996a5b64cbb472942e18..1ef5022501635e4538f105a2f86e071b8cd079aa 100644 --- a/arch/arm/kernel/ftrace.c +++ b/arch/arm/kernel/ftrace.c @@ -78,6 +78,20 @@ int ftrace_arch_code_modify_post_process(void) return 0; } +int ftrace_arch_code_modify_prepare(void) +{ + set_kernel_text_rw(); + set_all_modules_text_rw(); + return 0; +} + +int ftrace_arch_code_modify_post_process(void) +{ + set_all_modules_text_ro(); + set_kernel_text_ro(); + return 0; +} + static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr) { return arm_gen_branch_link(pc, addr); diff --git a/arch/arm/kernel/head.S b/arch/arm/kernel/head.S index 3bf0c7f8b043c52b4e616397cc768541a6009a89..313ff0b808198dd85db74ae66e85276b5cc13f0b 100644 --- a/arch/arm/kernel/head.S +++ b/arch/arm/kernel/head.S @@ -337,7 +337,6 @@ __turn_mmu_on_loc: .long __turn_mmu_on_end #if defined(CONFIG_SMP) - __CPUINIT ENTRY(secondary_startup) /* * Common entry point for secondary CPUs. @@ -422,10 +421,17 @@ __enable_mmu: mov r5, #0 mcrr p15, 0, r4, r5, c2 @ load TTBR0 #else - 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 #endif @@ -455,6 +461,15 @@ ENTRY(__turn_mmu_on) mrc p15, 0, r3, c0, c0, 0 @ read id reg instr_sync mov r3, r3 +#ifdef CONFIG_ARCH_MSM_KRAIT + ldr r3, =0xff00fc00 + and r3, r9, r3 + ldr r4, =0x51000400 + cmp r3, r4 + mrceq p15, 7, r3, c15, c0, 2 + biceq r3, r3, #0x400 + mcreq p15, 7, r3, c15, c0, 2 +#endif mov r3, r13 mov pc, r3 __turn_mmu_on_end: diff --git a/arch/arm/kernel/hw_breakpoint.c b/arch/arm/kernel/hw_breakpoint.c index ba386bd94107642d9819a7d8bafb7ba04e92b83f..ff2c0ad2d3d78be51a129781097dec625ceebe2b 100644 --- a/arch/arm/kernel/hw_breakpoint.c +++ b/arch/arm/kernel/hw_breakpoint.c @@ -853,6 +853,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. */ @@ -938,12 +950,11 @@ static void reset_ctrl_regs(void *unused) if (enable_monitor_mode()) return; - /* We must also reset any reserved registers. */ - raw_num_brps = get_num_brp_resources(); - for (i = 0; i < raw_num_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 8349d4e97e2b8b9b7bb5672b2cf974c2cdb74fd0..bc48bff404ff8f6de1217aa50b77cb11509a0e65 100644 --- a/arch/arm/kernel/irq.c +++ b/arch/arm/kernel/irq.c @@ -40,6 +40,8 @@ #include #include +#include + /* * No architecture-specific irq_finish function defined in arm/arch/irqs.h. */ @@ -71,6 +73,7 @@ void handle_IRQ(unsigned int irq, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); + perf_mon_interrupt_in(); irq_enter(); /* @@ -90,6 +93,7 @@ void handle_IRQ(unsigned int irq, struct pt_regs *regs) irq_exit(); set_irq_regs(old_regs); + perf_mon_interrupt_out(); } /* @@ -128,8 +132,18 @@ void __init init_IRQ(void) #ifdef CONFIG_SPARSE_IRQ int __init arch_probe_nr_irqs(void) { - nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS; - return nr_irqs; + /* + * machine_desc->nr_irqs < 0 is a special case that + * specifies not to preallocate any irq_descs. + */ + if (machine_desc->nr_irqs < 0) { + nr_irqs = 0; + return nr_irqs; + } else { + nr_irqs = machine_desc->nr_irqs ? + machine_desc->nr_irqs : NR_IRQS; + return nr_irqs; + } } #endif diff --git a/arch/arm/kernel/patch.c b/arch/arm/kernel/patch.c index 07314af477336a9c798e2ca7c794a2893fbfe624..7a27c47dad5b04687aabb5366db459edf70af3bf 100644 --- a/arch/arm/kernel/patch.c +++ b/arch/arm/kernel/patch.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "patch.h" @@ -17,6 +18,10 @@ void __kprobes __patch_text(void *addr, unsigned int insn) { bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL); int size; + unsigned long flags; + + mem_text_writeable_spinlock(&flags); + mem_text_address_writeable((unsigned long)addr); if (thumb2 && __opcode_is_thumb16(insn)) { *(u16 *)addr = __opcode_to_mem_thumb16(insn); @@ -42,6 +47,9 @@ void __kprobes __patch_text(void *addr, unsigned int insn) flush_icache_range((uintptr_t)(addr), (uintptr_t)(addr) + size); + + mem_text_address_restore(); + mem_text_writeable_spinunlock(&flags); } static int __kprobes patch_text_stop_machine(void *data) diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c index 186c8cb982c543a2cc1631796b34376151b33702..af6e7e6e0ecd0a70ed4ea08ac20d945dcbe46bb5 100644 --- a/arch/arm/kernel/perf_event.c +++ b/arch/arm/kernel/perf_event.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -78,7 +79,7 @@ EXPORT_SYMBOL_GPL(perf_num_counters); #define CACHE_OP_UNSUPPORTED 0xFFFF static int -armpmu_map_cache_event(const unsigned (*cache_map) +armpmu_map_cache_event(unsigned (*cache_map) [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX], @@ -121,7 +122,7 @@ armpmu_map_raw_event(u32 raw_event_mask, u64 config) static int map_cpu_event(struct perf_event *event, const unsigned (*event_map)[PERF_COUNT_HW_MAX], - const unsigned (*cache_map) + unsigned (*cache_map) [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX], @@ -253,7 +254,7 @@ armpmu_start(struct perf_event *event, int flags) * happened since disabling. */ armpmu_event_set_period(event, hwc, hwc->idx); - armpmu->enable(hwc, hwc->idx); + armpmu->enable(hwc, hwc->idx, event->cpu); } static void @@ -362,25 +363,44 @@ static irqreturn_t armpmu_platform_irq(int irq, void *dev) return plat->handle_irq(irq, dev, armpmu->handle_irq); } +int +armpmu_generic_request_irq(int irq, irq_handler_t *handle_irq) +{ + return request_irq(irq, *handle_irq, + IRQF_DISABLED | IRQF_NOBALANCING, + "armpmu", NULL); +} + +void +armpmu_generic_free_irq(int irq) +{ + if (irq >= 0) + free_irq(irq, NULL); +} + static void armpmu_release_hardware(struct arm_pmu *armpmu) { int i, irq, irqs; struct platform_device *pmu_device = armpmu->plat_device; +#if 0 struct arm_pmu_platdata *plat = dev_get_platdata(&pmu_device->dev); - +#endif irqs = min(pmu_device->num_resources, num_possible_cpus()); for (i = 0; i < irqs; ++i) { if (!cpumask_test_and_clear_cpu(i, &armpmu->active_irqs)) continue; irq = platform_get_irq(pmu_device, i); + armpmu->free_pmu_irq(irq); +#if 0 if (irq >= 0) { if (plat && plat->disable_irq) plat->disable_irq(irq); free_irq(irq, armpmu); } +#endif } release_pmu(armpmu->type); @@ -432,6 +452,17 @@ armpmu_reserve_hardware(struct arm_pmu *armpmu) continue; } + err = armpmu->request_pmu_irq(irq, &handle_irq); + + if (err) { + pr_warning("unable to request IRQ%d for %s perf " + "counters\n", irq, armpmu->name); + + armpmu_release_hardware(cpu_pmu); + return err; + } + +#if 0 err = request_irq(irq, handle_irq, IRQF_DISABLED | IRQF_NOBALANCING, "arm-pmu", armpmu); @@ -442,6 +473,7 @@ armpmu_reserve_hardware(struct arm_pmu *armpmu) return err; } else if (plat && plat->enable_irq) plat->enable_irq(irq); +#endif cpumask_set_cpu(i, &armpmu->active_irqs); } @@ -611,6 +643,7 @@ int __init armpmu_register(struct arm_pmu *armpmu, char *name, int type) #include "perf_event_xscale.c" #include "perf_event_v6.c" #include "perf_event_v7.c" +#include "perf_event_msm_krait.c" /* * Ensure the PMU has sane values out of reset. @@ -637,7 +670,7 @@ static struct of_device_id armpmu_of_device_ids[] = { }; static struct platform_device_id armpmu_plat_device_ids[] = { - {.name = "arm-pmu"}, + {.name = "cpu-arm-pmu"}, {}, }; @@ -652,7 +685,7 @@ static int __devinit armpmu_device_probe(struct platform_device *pdev) static struct platform_driver armpmu_driver = { .driver = { - .name = "arm-pmu", + .name = "cpu-arm-pmu", .of_match_table = armpmu_of_device_ids, }, .probe = armpmu_device_probe, @@ -753,8 +786,28 @@ init_hw_perf_events(void) cpu_pmu = xscale2pmu_init(); break; } + /* Qualcomm CPUs */ + } else if (0x51 == implementor) { + switch (part_number) { + case 0x00F0: /* 8x50 & 7x30*/ +// cpu_pmu = armv7_scorpion_pmu_init(); + break; + case 0x02D0: /* 8x60 */ +// fabricmon_pmu_init(); +// cpu_pmu = armv7_scorpionmp_pmu_init(); +// scorpionmp_l2_pmu_init(); + break; + case 0x0490: /* 8960 sim */ + case 0x04D0: /* 8960 */ + case 0x06F0: /* 8064 */ +// fabricmon_pmu_init(); + cpu_pmu = armv7_krait_pmu_init(); +// krait_l2_pmu_init(); + break; + } } + if (cpu_pmu) { pr_info("enabled with %s PMU driver, %d counters available\n", cpu_pmu->name, cpu_pmu->num_events); diff --git a/arch/arm/kernel/perf_event_msm.c b/arch/arm/kernel/perf_event_msm.c new file mode 100644 index 0000000000000000000000000000000000000000..0ca516479aa774bbd8be6a28562677dd76d52af4 --- /dev/null +++ b/arch/arm/kernel/perf_event_msm.c @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include +#include +#include "../vfp/vfpinstr.h" + +#ifdef CONFIG_CPU_V7 +#define SCORPION_EVT_PREFIX 1 +#define SCORPION_MAX_L1_REG 4 + +#define SCORPION_EVTYPE_EVENT 0xfffff + +static u32 scorpion_evt_type_base[] = {0x4c, 0x50, 0x54, 0x58, 0x5c}; + +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 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_PRED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + }, + [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; + u8 prefix; + u8 reg; + u8 code; + u8 group; + + prefix = (scorpion_evt_type & 0xF0000) >> 16; + if (prefix == SCORPION_EVT_PREFIX) { + reg = (scorpion_evt_type & 0x0F000) >> 12; + code = (scorpion_evt_type & 0x00FF0) >> 4; + group = scorpion_evt_type & 0x0000F; + + if ((group > 3) || (reg > SCORPION_MAX_L1_REG)) + return -EINVAL; + + evtinfo->group_setval = 0x80000000 | (code << (group * 8)); + evtinfo->groupcode = reg; + evtinfo->armv7_evt_type = scorpion_evt_type_base[reg] | group; + + return evtinfo->armv7_evt_type; + } + + 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; + u32 v_orig_val; + u32 f_orig_val; + + /* CPACR Enable CP10 access */ + v_orig_val = get_copro_access(); + venum_new_val = v_orig_val | CPACC_SVC(10); + set_copro_access(venum_new_val); + /* Store orig venum val */ + __get_cpu_var(venum_orig_val) = v_orig_val; + + /* Enable FPEXC */ + f_orig_val = fmrx(FPEXC); + fp_new_val = f_orig_val | FPEXC_EN; + fmxr(FPEXC, fp_new_val); + /* Store orig fp val */ + __get_cpu_var(fp_orig_val) = f_orig_val; +} + +static void scorpion_post_vlpm(void) +{ + /* Restore FPEXC */ + fmxr(FPEXC, __get_cpu_var(fp_orig_val)); + isb(); + /* Restore CPACR */ + set_copro_access(__get_cpu_var(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 &= SCORPION_EVTYPE_EVENT; + + 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; + unsigned long long prev_count = local64_read(&hwc->prev_count); + + /* + * 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 &= SCORPION_EVTYPE_EVENT; + + 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); + + /* Restore prev val */ + armv7pmu_write_counter(idx, prev_count & COUNT_MASK); + + /* Enable counter */ + armv7_pmnc_enable_counter(idx); + +scorpion_out: + raw_spin_unlock_irqrestore(&pmu_lock, flags); +} + +static void enable_irq_callback(void *info) +{ + int irq = *(unsigned int *)info; + + enable_percpu_irq(irq, IRQ_TYPE_EDGE_RISING); +} + +static void disable_irq_callback(void *info) +{ + int irq = *(unsigned int *)info; + + disable_percpu_irq(irq); +} + +static int +msm_request_irq(int irq, irq_handler_t *handle_irq) +{ + int err = 0; + int cpu; + + err = request_percpu_irq(irq, *handle_irq, "armpmu", + &cpu_hw_events); + + if (!err) { + for_each_cpu(cpu, cpu_online_mask) { + smp_call_function_single(cpu, + enable_irq_callback, &irq, 1); + } + } + + return err; +} + +static void +msm_free_irq(int irq) +{ + int cpu; + + if (irq >= 0) { + for_each_cpu(cpu, cpu_online_mask) { + smp_call_function_single(cpu, + disable_irq_callback, &irq, 1); + } + free_percpu_irq(irq, &cpu_hw_events); + } +} + +static void scorpion_pmu_reset(void *info) +{ + u32 idx, nb_cnt = armpmu->num_events; + + /* Stop all counters and their interrupts */ + for (idx = 1; idx < nb_cnt; ++idx) { + armv7_pmnc_disable_counter(idx); + armv7_pmnc_disable_intens(idx); + } + + /* Clear all pmresrs */ + scorpion_clear_pmuregs(); + + /* Reset irq stat reg */ + armv7_pmnc_getreset_flags(); + + /* Reset all ctrs to 0 */ + armv7_pmnc_write(ARMV7_PMNC_P | ARMV7_PMNC_C); +} + +static struct arm_pmu scorpion_pmu = { + .handle_irq = armv7pmu_handle_irq, + .request_pmu_irq = msm_request_irq, + .free_pmu_irq = msm_free_irq, + .enable = scorpion_pmu_enable_event, + .disable = scorpion_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, + .reset = scorpion_pmu_reset, + .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 0000000000000000000000000000000000000000..8dbd047b164f28ffe0083699fce8c7ebc04374fa --- /dev/null +++ b/arch/arm/kernel/perf_event_msm_krait.c @@ -0,0 +1,640 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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 +#define KRAIT_EVT_PREFIX 1 +#define KRAIT_VENUMEVT_PREFIX 2 +/* + event encoding: prccg + p = prefix (1 for Krait L1) (2 for Krait VeNum events) + r = register + cc = code + g = group +*/ + +#define KRAIT_L1_ICACHE_ACCESS 0x10011 +#define KRAIT_L1_ICACHE_MISS 0x10010 + +#define KRAIT_P1_L1_ITLB_ACCESS 0x121b2 +#define KRAIT_P1_L1_DTLB_ACCESS 0x121c0 + +#define KRAIT_P2_L1_ITLB_ACCESS 0x12222 +#define KRAIT_P2_L1_DTLB_ACCESS 0x12210 + +#define KRAIT_EVENT_MASK 0xfffff +#define KRAIT_MODE_EXCL_MASK 0xc0000000 + +#define COUNT_MASK 0xffffffff + +u32 evt_type_base[][4] = { + {0x4c, 0x50, 0x54}, /* Pass 1 */ + {0xcc, 0xd0, 0xd4, 0xd8}, /* Pass 2 */ +}; + +#define KRAIT_MIDR_PASS1 0x510F04D0 +#define KRAIT_MIDR_MASK 0xfffffff0 + +/* + * This offset is used to calculate the index + * into evt_type_base[][] and krait_functions[] + */ +#define VENUM_BASE_OFFSET 3 + +/* Krait Pass 1 has 3 groups, Pass 2 has 4 */ +static u32 krait_ver, evt_index; +static u32 krait_max_l1_reg; + +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 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_L1_DCACHE_ACCESS, + [C(RESULT_MISS)] = ARMV7_PERFCTR_L1_DCACHE_REFILL, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = ARMV7_PERFCTR_L1_DCACHE_ACCESS, + [C(RESULT_MISS)] = ARMV7_PERFCTR_L1_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)] = 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(ITLB)] = { + [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(BPU)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_PRED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, +}; + +static int krait_8960_map_event(struct perf_event *event) +{ + return map_cpu_event(event, &armv7_krait_perf_map, + &armv7_krait_perf_cache_map, 0xfffff); +} + +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 ((group > 3) || (reg > krait_max_l1_reg)) + return -EINVAL; + + if (prefix != KRAIT_EVT_PREFIX && prefix != KRAIT_VENUMEVT_PREFIX) + return -EINVAL; + + if (prefix == KRAIT_VENUMEVT_PREFIX) { + if ((code & 0xe0) || krait_ver != 2) + return -EINVAL; + else + reg += VENUM_BASE_OFFSET; + } + + evtinfo->group_setval = 0x80000000 | (code << (group * 8)); + evtinfo->groupcode = reg; + evtinfo->armv7_evt_type = evt_type_base[evt_index][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)); +} + +static u32 krait_read_vmresr0(void) +{ + u32 val; + + asm volatile ("mrc p10, 7, %0, c11, c0, 0" : "=r" (val)); + return val; +} + +static void krait_write_vmresr0(u32 val) +{ + asm volatile ("mcr p10, 7, %0, c11, c0, 0" : : "r" (val)); +} + +static DEFINE_PER_CPU(u32, venum_orig_val); +static DEFINE_PER_CPU(u32, fp_orig_val); + +static void krait_pre_vmresr0(void) +{ + u32 venum_new_val; + u32 fp_new_val; + u32 v_orig_val; + u32 f_orig_val; + + /* CPACR Enable CP10 and CP11 access */ + v_orig_val = get_copro_access(); + venum_new_val = v_orig_val | CPACC_SVC(10) | CPACC_SVC(11); + set_copro_access(venum_new_val); + /* Store orig venum val */ + __get_cpu_var(venum_orig_val) = v_orig_val; + + /* Enable FPEXC */ + f_orig_val = fmrx(FPEXC); + fp_new_val = f_orig_val | FPEXC_EN; + fmxr(FPEXC, fp_new_val); + /* Store orig fp val */ + __get_cpu_var(fp_orig_val) = f_orig_val; + +} + +static void krait_post_vmresr0(void) +{ + /* Restore FPEXC */ + fmxr(FPEXC, __get_cpu_var(fp_orig_val)); + isb(); + /* Restore CPACR */ + set_copro_access(__get_cpu_var(venum_orig_val)); +} + +struct krait_access_funcs { + u32 (*read) (void); + void (*write) (u32); + void (*pre) (void); + void (*post) (void); +}; + +/* + * 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, NULL, NULL}, + {krait_read_pmresr1, krait_write_pmresr1, NULL, NULL}, + {krait_read_pmresr2, krait_write_pmresr2, NULL, NULL}, + {krait_read_vmresr0, krait_write_vmresr0, krait_pre_vmresr0, + krait_post_vmresr0}, +}; + +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; + + if (krait_functions[gr].pre) + krait_functions[gr].pre(); + + val = krait_get_columnmask(evt_code) & krait_functions[gr].read(); + val = val | setval; + krait_functions[gr].write(val); + + if (krait_functions[gr].post) + krait_functions[gr].post(); +} + +static void krait_clear_pmuregs(void) +{ + krait_write_pmresr0(0); + krait_write_pmresr1(0); + krait_write_pmresr2(0); + + krait_pre_vmresr0(); + krait_write_vmresr0(0); + krait_post_vmresr0(); +} + +static void krait_clearpmu(u32 grp, u32 val, u32 evt_code) +{ + u32 new_pmuval; + + if (krait_functions[grp].pre) + krait_functions[grp].pre(); + + new_pmuval = krait_functions[grp].read() & + krait_get_columnmask(evt_code); + krait_functions[grp].write(new_pmuval); + + if (krait_functions[grp].post) + krait_functions[grp].post(); +} + +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; + struct pmu_hw_events *events = cpu_pmu->get_hw_events(); + + + /* Disable counter and interrupt */ + raw_spin_lock_irqsave(&events->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_IDX_CYCLE_COUNTER) { + val = hwc->config_base; + val &= KRAIT_EVENT_MASK; + + 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(&events->pmu_lock, flags); +} + +static void krait_pmu_enable_event(struct hw_perf_event *hwc, int idx, int cpu) +{ + unsigned long flags; + u32 val = 0; + u32 gr; + unsigned long event; + struct krait_evt evtinfo; + unsigned long long prev_count = local64_read(&hwc->prev_count); + struct pmu_hw_events *events = cpu_pmu->get_hw_events(); + + /* + * Enable counter and interrupt, and set the counter to count + * the event that we're interested in. + */ + raw_spin_lock_irqsave(&events->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_IDX_CYCLE_COUNTER) { + val = hwc->config_base; + val &= KRAIT_EVENT_MASK; + + if (val < 0x40) { + armv7_pmnc_write_evtsel(idx, hwc->config_base); + } else { + event = get_krait_evtinfo(val, &evtinfo); + + if (event == -EINVAL) + goto krait_out; + + /* Restore Mode-exclusion bits */ + event |= (hwc->config_base & KRAIT_MODE_EXCL_MASK); + + /* + * 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); + + /* Restore prev val */ + armv7pmu_write_counter(idx, prev_count & COUNT_MASK); + + /* Enable counter */ + armv7_pmnc_enable_counter(idx); + +krait_out: + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void krait_pmu_reset(void *info) +{ + u32 idx, nb_cnt = cpu_pmu->num_events; + + /* Stop all counters and their interrupts */ + for (idx = 1; idx < nb_cnt; ++idx) { + armv7_pmnc_disable_counter(idx); + armv7_pmnc_disable_intens(idx); + } + + /* Clear all pmresrs */ + krait_clear_pmuregs(); + + /* Reset irq stat reg */ + armv7_pmnc_getreset_flags(); + + /* Reset all ctrs to 0 */ + armv7_pmnc_write(ARMV7_PMNC_P | ARMV7_PMNC_C); +} + +static void enable_irq_callback(void *info) +{ + int irq = *(unsigned int *)info; + + enable_percpu_irq(irq, IRQ_TYPE_EDGE_RISING); +} + +static void disable_irq_callback(void *info) +{ + int irq = *(unsigned int *)info; + + disable_percpu_irq(irq); +} + +static int +msm_request_irq(int irq, irq_handler_t *handle_irq) +{ + int err = 0; + int cpu; + + err = request_percpu_irq(irq, *handle_irq, "krait-l1-armpmu", + &cpu_hw_events); + + if (!err) { + for_each_cpu(cpu, cpu_online_mask) { + smp_call_function_single(cpu, + enable_irq_callback, &irq, 1); + } + } + + return err; +} + +static void +msm_free_irq(int irq) +{ + int cpu; + + if (irq >= 0) { + for_each_cpu(cpu, cpu_online_mask) { + smp_call_function_single(cpu, + disable_irq_callback, &irq, 1); + } + free_percpu_irq(irq, &cpu_hw_events); + } +} + +static struct arm_pmu krait_pmu = { + .handle_irq = armv7pmu_handle_irq, + .request_pmu_irq = msm_request_irq, + .free_pmu_irq = msm_free_irq, + .enable = krait_pmu_enable_event, + .disable = krait_pmu_disable_event, + .read_counter = armv7pmu_read_counter, + .write_counter = armv7pmu_write_counter, + .get_event_idx = armv7pmu_get_event_idx, + .start = armv7pmu_start, + .stop = armv7pmu_stop, + .reset = krait_pmu_reset, + .max_period = (1LLU << 32) - 1, +}; + +int get_krait_ver(void) +{ + int ver = 0; + int midr = read_cpuid_id(); + + if ((midr & KRAIT_MIDR_MASK) != KRAIT_MIDR_PASS1) + ver = 2; + + pr_debug("krait_ver: %d, midr: %x\n", ver, midr); + + return ver; +} + +static struct arm_pmu *__init armv7_krait_pmu_init(void) +{ + krait_pmu.id = ARM_PERF_PMU_ID_KRAIT; + krait_pmu.name = "ARMv7 Krait"; + krait_pmu.map_event = krait_8960_map_event; + krait_pmu.num_events = armv7_read_num_pmnc_events(); + krait_clear_pmuregs(); + + krait_ver = get_krait_ver(); + + if (krait_ver > 0) { + evt_index = 1; + krait_max_l1_reg = 3; + + krait_pmu.set_event_filter = armv7pmu_set_event_filter, + + armv7_krait_perf_cache_map[C(ITLB)] + [C(OP_READ)] + [C(RESULT_ACCESS)] = KRAIT_P2_L1_ITLB_ACCESS; + armv7_krait_perf_cache_map[C(ITLB)] + [C(OP_WRITE)] + [C(RESULT_ACCESS)] = KRAIT_P2_L1_ITLB_ACCESS; + armv7_krait_perf_cache_map[C(DTLB)] + [C(OP_READ)] + [C(RESULT_ACCESS)] = KRAIT_P2_L1_DTLB_ACCESS; + armv7_krait_perf_cache_map[C(DTLB)] + [C(OP_WRITE)] + [C(RESULT_ACCESS)] = KRAIT_P2_L1_DTLB_ACCESS; + } else { + evt_index = 0; + krait_max_l1_reg = 2; + armv7_krait_perf_cache_map[C(ITLB)] + [C(OP_READ)] + [C(RESULT_ACCESS)] = KRAIT_P1_L1_ITLB_ACCESS; + armv7_krait_perf_cache_map[C(ITLB)] + [C(OP_WRITE)] + [C(RESULT_ACCESS)] = KRAIT_P1_L1_ITLB_ACCESS; + armv7_krait_perf_cache_map[C(DTLB)] + [C(OP_READ)] + [C(RESULT_ACCESS)] = KRAIT_P1_L1_DTLB_ACCESS; + armv7_krait_perf_cache_map[C(DTLB)] + [C(OP_WRITE)] + [C(RESULT_ACCESS)] = KRAIT_P1_L1_DTLB_ACCESS; + } + + 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 0000000000000000000000000000000000000000..baa05ac52894821fc433b9a73c46c143c5470605 --- /dev/null +++ b/arch/arm/kernel/perf_event_msm_krait_l2.c @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2011, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#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 0xfe + +#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_task_mode(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 set_evfilter_sys_mode(int ctr) +{ + u32 filter_reg = (ctr * 16) + IA_L2PMXEVFILTER_BASE; + u32 filter_val = 0x000f003f; + + 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); + + if (event->cpu < 0) + set_evfilter_task_mode(idx); + else + set_evfilter_sys_mode(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; + } + + hwc->config = 0; + hwc->event_base = 0; + + /* Check if we came via perf default syms */ + if (event->attr.config == PERF_COUNT_HW_L2_CYCLES) + hwc->config_base = L2CYCLE_CTR_RAW_CODE; + else + hwc->config_base = event->attr.config; + + /* 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(); + + raw_spin_lock_init(&hw_krait_l2_pmu.lock); + + /* 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 0000000000000000000000000000000000000000..26753d89ae2a218df45ace9194cc74186d04f23a --- /dev/null +++ b/arch/arm/kernel/perf_event_msm_l2.c @@ -0,0 +1,1013 @@ +/* + * Copyright (c) 2011, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_MSM8X60 + +#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 0xfe +#define SCORPIONL2_PMNC_E (1 << 0) /* Enable all counters */ +#define SCORPION_L2_EVT_PREFIX 3 +#define SCORPION_MAX_L2_REG 4 + +/* + * 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; + u8 prefix; + u8 reg; + u8 code; + u8 group; + + prefix = (evt_type & 0xF0000) >> 16; + if (prefix == SCORPION_L2_EVT_PREFIX) { + reg = (evt_type & 0x0F000) >> 12; + code = (evt_type & 0x00FF0) >> 4; + group = evt_type & 0x0000F; + + if ((group > 3) || (reg > SCORPION_MAX_L2_REG)) + return BB_L2_INV_EVTYPE; + + evtinfo->val = 0x80000000 | (code << (group * 8)); + evtinfo->grp = reg; + evtinfo->evt_type_act = group | (reg << 2); + return evtinfo->evt_type_act; + } + + 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_task_mode(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_set_evfilter_sys_mode(void) +{ + u32 filter_val = 0x000f003f; + + 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(); + + if (event->cpu < 0) + bb_l2_set_evfilter_task_mode(); + else + bb_l2_set_evfilter_sys_mode(); + + 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; + } + + hwc->config = 0; + hwc->event_base = 0; + + /* Check if we came via perf default syms */ + if (event->attr.config == PERF_COUNT_HW_L2_CYCLES) + hwc->config_base = BB_L2CYCLE_CTR_RAW_CODE; + else + hwc->config_base = event->attr.config; + + /* 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(); + + raw_spin_lock_init(&hw_bb_l2_pmu.lock); + + /* 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/perf_event_v6.c b/arch/arm/kernel/perf_event_v6.c index b78af0cc6ef36ddf8b371c9b57541b19caab5677..b5ba783326d8fb5de6cb8292a59c8ad027f88d25 100644 --- a/arch/arm/kernel/perf_event_v6.c +++ b/arch/arm/kernel/perf_event_v6.c @@ -76,7 +76,7 @@ static const unsigned armv6_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = ARMV6_PERFCTR_LSU_FULL_STALL, }; -static const unsigned armv6_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv6_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -231,7 +231,7 @@ static const unsigned armv6mpcore_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = ARMV6MPCORE_PERFCTR_LSU_FULL_STALL, }; -static const unsigned armv6mpcore_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv6mpcore_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -655,6 +655,8 @@ static struct arm_pmu armv6pmu = { .id = ARM_PERF_PMU_ID_V6, .name = "v6", .handle_irq = armv6pmu_handle_irq, + .request_pmu_irq = armpmu_generic_request_irq, + .free_pmu_irq = armpmu_generic_free_irq, .enable = armv6pmu_enable_event, .disable = armv6pmu_disable_event, .read_counter = armv6pmu_read_counter, @@ -690,6 +692,8 @@ static struct arm_pmu armv6mpcore_pmu = { .id = ARM_PERF_PMU_ID_V6MP, .name = "v6mpcore", .handle_irq = armv6pmu_handle_irq, + .request_pmu_irq = armpmu_generic_request_irq, + .free_pmu_irq = armpmu_generic_free_irq, .enable = armv6pmu_enable_event, .disable = armv6mpcore_pmu_disable_event, .read_counter = armv6pmu_read_counter, diff --git a/arch/arm/kernel/perf_event_v7.c b/arch/arm/kernel/perf_event_v7.c index 00755d82e2f2cbe06cda363c53ff115397d94d59..678c55de53d5ae6f1b0b96ea5179f8c55073d575 100644 --- a/arch/arm/kernel/perf_event_v7.c +++ b/arch/arm/kernel/perf_event_v7.c @@ -130,7 +130,7 @@ static const unsigned armv7_a8_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = HW_OP_UNSUPPORTED, }; -static const unsigned armv7_a8_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv7_a8_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -254,7 +254,7 @@ static const unsigned armv7_a9_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = ARMV7_A9_PERFCTR_STALL_DISPATCH, }; -static const unsigned armv7_a9_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv7_a9_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -378,7 +378,7 @@ static const unsigned armv7_a5_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = HW_OP_UNSUPPORTED, }; -static const unsigned armv7_a5_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv7_a5_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -500,7 +500,7 @@ static const unsigned armv7_a15_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = HW_OP_UNSUPPORTED, }; -static const unsigned armv7_a15_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv7_a15_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -624,7 +624,7 @@ static const unsigned armv7_a7_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = HW_OP_UNSUPPORTED, }; -static const unsigned armv7_a7_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned armv7_a7_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -993,7 +993,7 @@ static void armv7_pmnc_dump_regs(void) } #endif -static void armv7pmu_enable_event(struct hw_perf_event *hwc, int idx) +static void armv7pmu_enable_event(struct hw_perf_event *hwc, int idx, int cpu) { unsigned long flags; struct pmu_hw_events *events = cpu_pmu->get_hw_events(); diff --git a/arch/arm/kernel/perf_event_xscale.c b/arch/arm/kernel/perf_event_xscale.c index 71a21e6712f5356daa77aa134796701101ce1a13..1a751f7727158cac0b03335ad799eb26627357da 100644 --- a/arch/arm/kernel/perf_event_xscale.c +++ b/arch/arm/kernel/perf_event_xscale.c @@ -59,7 +59,7 @@ static const unsigned xscale_perf_map[PERF_COUNT_HW_MAX] = { [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = HW_OP_UNSUPPORTED, }; -static const unsigned xscale_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] +static unsigned xscale_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(L1D)] = { @@ -810,6 +810,8 @@ static struct arm_pmu xscale2pmu = { .id = ARM_PERF_PMU_ID_XSCALE2, .name = "xscale2", .handle_irq = xscale2pmu_handle_irq, + .request_pmu_irq = armpmu_generic_request_irq, + .free_pmu_irq = armpmu_generic_free_irq, .enable = xscale2pmu_enable_event, .disable = xscale2pmu_disable_event, .read_counter = xscale2pmu_read_counter, diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 014f0908b7b4365ce9af27887386838bb9159224..a9582f9b300f7f66715831dc1458bd01c4cee8e6 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c @@ -154,6 +154,9 @@ static void __soft_restart(void *addr) /* Push out any further dirty data, and ensure cache is empty */ flush_cache_all(); + /* Push out the dirty data from external caches */ + outer_disable(); + /* Switch to the identity mapping. */ phys_reset = (phys_reset_t)(unsigned long)virt_to_phys(cpu_reset); phys_reset((unsigned long)addr); @@ -218,7 +221,8 @@ EXPORT_SYMBOL_GPL(cpu_idle_wait); * This is our default idle handler. */ -void (*arm_pm_idle)(void); +extern void arch_idle(void); +void (*arm_pm_idle)(void) = arch_idle; static void default_idle(void) { diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index ebfac782593f048c9cf81a5619f0b3d24900ca8c..e30f1d87144d272165eee3f1f18a92945027dae3 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -103,6 +103,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; @@ -959,6 +961,9 @@ void __init setup_arch(char **cmdline_p) parse_early_param(); + if (mdesc->init_very_early) + mdesc->init_very_early(); + sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL); sanity_check_meminfo(); arm_memblock_init(&meminfo, mdesc); @@ -1054,7 +1059,7 @@ static int c_show(struct seq_file *m, void *v) cpu_name, read_cpuid_id() & 15, elf_platform); #if defined(CONFIG_SMP) - for_each_online_cpu(i) { + for_each_present_cpu(i) { /* * glibc reads /proc/cpuinfo to determine the number of * online processors, looking for lines beginning with diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index f30d68326e2aa4a95b398fb1325ccccbf2cccb93..4738d713eab6e390123672e98c5b0134e838b772 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -51,6 +51,7 @@ struct secondary_data secondary_data; enum ipi_msg_type { + IPI_CPU_START = 1, IPI_TIMER = 2, IPI_RESCHEDULE, IPI_CALL_FUNC, @@ -183,7 +184,7 @@ void __cpu_die(unsigned int cpu) pr_err("CPU%u: cpu didn't die\n", cpu); return; } - printk(KERN_NOTICE "CPU%u: shutdown\n", cpu); + pr_debug("CPU%u: shutdown\n", cpu); if (!platform_cpu_kill(cpu)) printk("CPU%u: unable to kill\n", cpu); @@ -241,8 +242,6 @@ static void __cpuinit smp_store_cpu_info(unsigned int cpuid) store_cpu_topology(cpuid); } -static void percpu_timer_setup(void); - /* * This is the secondary CPU boot entry. We're using this CPUs * idle thread stack, but a set of temporary page tables. @@ -263,7 +262,7 @@ asmlinkage void __cpuinit secondary_start_kernel(void) enter_lazy_tlb(mm, current); local_flush_tlb_all(); - printk("CPU%u: Booted secondary processor\n", cpu); + pr_debug("CPU%u: Booted secondary processor\n", cpu); cpu_init(); preempt_disable(); @@ -378,7 +377,8 @@ void arch_send_call_function_single_ipi(int cpu) } static const char *ipi_types[NR_IPI] = { -#define S(x,s) [x - IPI_TIMER] = s +#define S(x,s) [x - IPI_CPU_START] = s + S(IPI_CPU_START, "CPU start interrupts"), S(IPI_TIMER, "Timer broadcast interrupts"), S(IPI_RESCHEDULE, "Rescheduling interrupts"), S(IPI_CALL_FUNC, "Function call interrupts"), @@ -464,7 +464,7 @@ int local_timer_register(struct local_timer_ops *ops) } #endif -static void __cpuinit percpu_timer_setup(void) +void __cpuinit percpu_timer_setup(void) { unsigned int cpu = smp_processor_id(); struct clock_event_device *evt = &per_cpu(percpu_clockevent, cpu); @@ -581,10 +581,13 @@ void handle_IPI(int ipinr, struct pt_regs *regs) unsigned int cpu = smp_processor_id(); struct pt_regs *old_regs = set_irq_regs(regs); - if (ipinr >= IPI_TIMER && ipinr < IPI_TIMER + NR_IPI) - __inc_irq_stat(cpu, ipi_irqs[ipinr - IPI_TIMER]); + if (ipinr >= IPI_CPU_START && ipinr < IPI_CPU_START + NR_IPI) + __inc_irq_stat(cpu, ipi_irqs[ipinr - IPI_CPU_START]); switch (ipinr) { + case IPI_CPU_START: + /* Wake up from WFI/WFE using SGI */ + break; case IPI_TIMER: irq_enter(); ipi_timer(); diff --git a/arch/arm/kernel/swp_emulate.c b/arch/arm/kernel/swp_emulate.c index df745188f5de4abdf3bd04faaa5d31b653b0f210..5d1448512887546ce7af7d53c16fa4ca3eab9b32 100644 --- a/arch/arm/kernel/swp_emulate.c +++ b/arch/arm/kernel/swp_emulate.c @@ -174,6 +174,57 @@ static int emulate_swpX(unsigned int address, unsigned int *data, return res; } +static int check_condition(struct pt_regs *regs, unsigned int insn) +{ + unsigned int base_cond, neg, cond = 0; + unsigned int cpsr_z, cpsr_c, cpsr_n, cpsr_v; + + cpsr_n = (regs->ARM_cpsr & PSR_N_BIT) ? 1 : 0; + cpsr_z = (regs->ARM_cpsr & PSR_Z_BIT) ? 1 : 0; + cpsr_c = (regs->ARM_cpsr & PSR_C_BIT) ? 1 : 0; + cpsr_v = (regs->ARM_cpsr & PSR_V_BIT) ? 1 : 0; + + /* Upper 3 bits indicate condition, lower bit incicates negation */ + base_cond = insn >> 29; + neg = insn & BIT(28) ? 1 : 0; + + switch (base_cond) { + case 0x0: /* equal */ + cond = cpsr_z; + break; + + case 0x1: /* carry set */ + cond = cpsr_c; + break; + + case 0x2: /* minus / negative */ + cond = cpsr_n; + break; + + case 0x3: /* overflow */ + cond = cpsr_v; + break; + + case 0x4: /* unsigned higher */ + cond = (cpsr_c == 1) && (cpsr_z == 0); + break; + + case 0x5: /* signed greater / equal */ + cond = (cpsr_n == cpsr_v); + break; + + case 0x6: /* signed greater */ + cond = (cpsr_z == 0) && (cpsr_n == cpsr_v); + break; + + case 0x7: /* always */ + cond = 1; + break; + }; + + return cond && !neg; +} + /* * swp_handler logs the id of calling process, dissects the instruction, sanity * checks the memory location, calls emulate_swpX for the actual operation and @@ -207,6 +258,12 @@ static int swp_handler(struct pt_regs *regs, unsigned int instr) previous_pid = current->pid; } + /* Ignore the instruction if it fails its condition code check */ + if (!check_condition(regs, instr)) { + regs->ARM_pc += 4; + return 0; + } + address = regs->uregs[EXTRACT_REG_NUM(instr, RN_OFFSET)]; data = regs->uregs[EXTRACT_REG_NUM(instr, RT2_OFFSET)]; destreg = EXTRACT_REG_NUM(instr, RT_OFFSET); diff --git a/arch/arm/kernel/vmlinux.lds.S b/arch/arm/kernel/vmlinux.lds.S index 43a31fb06318dc0a1213827a5069b73cac19f5a6..89f7f23d7904ee6c0a8822bbbcca22f11d3731f0 100644 --- a/arch/arm/kernel/vmlinux.lds.S +++ b/arch/arm/kernel/vmlinux.lds.S @@ -8,7 +8,10 @@ #include #include #include - +#ifdef CONFIG_STRICT_MEMORY_RWX +#include +#endif + #define PROC_INFO \ . = ALIGN(4); \ VMLINUX_SYMBOL(__proc_info_begin) = .; \ @@ -90,6 +93,7 @@ SECTIONS _text = .; HEAD_TEXT } + .text : { /* Real text segment */ _stext = .; /* Text and read-only data */ __exception_text_start = .; @@ -111,6 +115,9 @@ SECTIONS *(.got) /* Global offset table */ ARM_CPU_KEEP(PROC_INFO) } +#ifdef CONFIG_STRICT_MEMORY_RWX + . = ALIGN(1< -#include -#include - .text - -.LC0: .word loops_per_jiffy -.LC1: .word (2199023*HZ)>>11 - -/* - * r0 <= 2000 - * lpj <= 0x01ffffff (max. 3355 bogomips) - * HZ <= 1000 - */ - -ENTRY(__udelay) - ldr r2, .LC1 - mul r0, r2, r0 -ENTRY(__const_udelay) @ 0 <= r0 <= 0x7fffff06 - mov r1, #-1 - ldr r2, .LC0 - ldr r2, [r2] @ max = 0x01ffffff - add r0, r0, r1, lsr #32-14 - mov r0, r0, lsr #14 @ max = 0x0001ffff - add r2, r2, r1, lsr #32-10 - mov r2, r2, lsr #10 @ max = 0x00007fff - mul r0, r2, r0 @ max = 2^32-1 - add r0, r0, r1, lsr #32-6 - movs r0, r0, lsr #6 - moveq pc, lr - -/* - * loops = r0 * HZ * loops_per_jiffy / 1000000 - * - * Oh, if only we had a cycle counter... - */ - -@ Delay routine -ENTRY(__delay) - subs r0, r0, #1 -#if 0 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 -#endif - bhi __delay - mov pc, lr -ENDPROC(__udelay) -ENDPROC(__const_udelay) -ENDPROC(__delay) diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c new file mode 100644 index 0000000000000000000000000000000000000000..fc9a37cb6c1036dc99519e4f22451863c5f866aa --- /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 * ((2199023UL*HZ)>>11)); +} +EXPORT_SYMBOL(__udelay); diff --git a/arch/arm/lib/lib1funcs.S b/arch/arm/lib/lib1funcs.S index c562f649734cea40f813c3d6c50b9cffcd619ae9..63b75df56d2fbb1cf6a9934a87ccac79eef94b12 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 1cd40ad301d3b48e81516db128ef5d28387d8961..cbdb51aac66e404159b6b8e8f5b7917be6c5769e 100644 --- a/arch/arm/mach-msm/Kconfig +++ b/arch/arm/mach-msm/Kconfig @@ -1,141 +1,714 @@ if ARCH_MSM -choice - prompt "Qualcomm MSM SoC Type" - default ARCH_MSM7X00A +menu "MSM SoC Type" -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 GPIO_MSM_V1 + select MSM_REMOTE_SPINLOCK_SWP + +config ARCH_MSM7X25 + bool "MSM7x25" + select ARCH_MSM_ARM11 + select MSM_VIC select CPU_V6 select GPIO_MSM_V1 - select MSM_PROC_COMM + select MSM_REMOTE_SPINLOCK_SWP + select MULTI_IRQ_HANDLER + +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 GPIO_MSM_V1 + select MSM_REMOTE_SPINLOCK_SWP if MSM_SOC_REV_NONE + select MSM_GPIOMUX + select REGULATOR + select MULTI_IRQ_HANDLER + select MSM_PROC_COMM_REGULATOR + select CLEANCACHE + select QCACHE + select MSM_PM2 if PM + select MSM_RUN_QUEUE_STATS if MSM_SOC_REV_A + select DONT_MAP_HOLE_AFTER_MEMBANK0 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_GPIOMUX select GPIO_MSM_V1 - select MSM_PROC_COMM + select MSM_REMOTE_SPINLOCK_DEKKERS + select ARCH_SPARSEMEM_ENABLE + select ARCH_HAS_HOLES_MEMORYMODEL + select MEMORY_HOTPLUG + select MEMORY_HOTREMOVE + select ARCH_ENABLE_MEMORY_HOTPLUG + select ARCH_ENABLE_MEMORY_HOTREMOVE + select MIGRATION + select ARCH_MEMORY_PROBE + select ARCH_MEMORY_REMOVE + select MSM_GPIOMUX + select RESERVE_FIRST_PAGE + select MSM_DALRPC + select MSM_SPM_V1 + select REGULATOR + select MSM_PROC_COMM_REGULATOR + select MULTI_IRQ_HANDLER + select MSM_PM2 if PM 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_GPIOMUX select GPIO_MSM_V1 - select MSM_PROC_COMM + select MSM_REMOTE_SPINLOCK_LDREX + select CPU_USE_DOMAINS + select EMULATE_DOMAIN_MANAGER_V7 + select MSM_GPIOMUX + select MSM_DALRPC + select MSM_PM2 if PM 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_V2_TLMM + select MSM_REMOTE_SPINLOCK_LDREX + select ARCH_REQUIRE_GPIOLIB + select MSM_ADM3 + select REGULATOR + select MSM_RPM_REGULATOR select GPIO_MSM_V2 + select MSM_PIL + select ARCH_HAS_CPU_IDLE_WAIT + select MSM_DIRECT_SCLK_ACCESS + select MSM_RPM + select MSM_XO select MSM_GPIOMUX + select MSM_BUS_SCALING + select MSM_SECURE_IO + select MSM_DALRPC + select MSM_QDSP6_APR + select MSM_QDSP6_CODECS + 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 select MSM_SCM if SMP + select MULTI_IRQ_HANDLER + select MSM_MULTIMEDIA_USE_ION + select MSM_PM8X60 if PM + select MSM_RUN_QUEUE_STATS 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 GPIO_MSM_V2 + select MSM_GPIOMUX + select MSM_SCM if SMP + select MSM_DIRECT_SCLK_ACCESS + select REGULATOR + select MSM_RPM_REGULATOR + select MSM_RPM + select MSM_XO + select MSM_QDSP6_APR + select MSM_QDSP6_CODECS + 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 + select DONT_MAP_HOLE_AFTER_MEMBANK0 + select MSM_REMOTE_SPINLOCK_SFPB + select ARCH_SPARSEMEM_ENABLE + select ARCH_HAS_HOLES_MEMORYMODEL + select CLEANCACHE + select QCACHE + select MSM_MULTIMEDIA_USE_ION + select MULTI_IRQ_HANDLER + select MSM_PM8X60 if PM + select HOLES_IN_ZONE if SPARSEMEM + select MSM_RUN_QUEUE_STATS + +config ARCH_MSM8930 + bool "MSM8930" + select ARCH_MSM_KRAITMP + select ARM_GIC + select CPU_V7 + select GPIO_MSM_V2 + select MSM_GPIOMUX + select MSM_SCM if SMP + select MSM_DIRECT_SCLK_ACCESS + select REGULATOR + select MSM_RPM_REGULATOR + select MSM_RPM + select MSM_XO + select MSM_QDSP6_APR + select MSM_QDSP6_CODECS + 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 + select DONT_MAP_HOLE_AFTER_MEMBANK0 + select MSM_REMOTE_SPINLOCK_SFPB + select ARCH_SPARSEMEM_ENABLE + select ARCH_HAS_HOLES_MEMORYMODEL + select MSM_ULTRASOUND + select MULTI_IRQ_HANDLER + select MSM_PM8X60 if PM + select HOLES_IN_ZONE if SPARSEMEM + +config ARCH_APQ8064 + bool "APQ8064" + select ARCH_MSM_KRAITMP + select GPIO_MSM_V2 + select ARM_GIC + select CPU_V7 + select MSM_SCM if SMP select MSM_GPIOMUX + select MSM_REMOTE_SPINLOCK_SFPB + select MSM_PIL + select MSM_QDSP6_APR + select MSM_QDSP6_CODECS + select MSM_AUDIO_QDSP6 if SND_SOC + select MULTI_IRQ_HANDLER + select MSM_RPM + select MSM_SPM_V2 + select MSM_L2_SPM + select MSM_PM8X60 if PM + select CPU_HAS_L2_PMU + select HOLES_IN_ZONE if SPARSEMEM + select CLEANCACHE + select QCACHE + select MIGHT_HAVE_PCI + select ARCH_SUPPORTS_MSI + +config ARCH_MSMCOPPER + bool "MSM Copper" + select ARCH_MSM_KRAITMP + select GPIO_MSM_V2 + select ARM_GIC + select CPU_V7 select MSM_SCM if SMP + select MSM_GPIOMUX + select MULTI_IRQ_HANDLER + select MSM_MULTIMEDIA_USE_ION + select MSM_PIL + select MSM_SPM_V2 + select MSM_L2_SPM + select MSM_PM8X60 if PM + select MAY_HAVE_SPARSE_IRQ + select SPARSE_IRQ + select MSM_RPM_SMD + select REGULATOR + +config ARCH_FSM9XXX + bool "FSM9XXX" + select ARCH_MSM_SCORPION + select MSM_VIC + select CPU_V7 + select MSM_REMOTE_SPINLOCK_LDREX + select GPIO_FSM9XXX + select MULTI_IRQ_HANDLER + select MSM_DALRPC + +config ARCH_MSM9615 + bool "MSM9615" + select ARM_GIC + select GIC_SECURE + select ARCH_MSM_CORTEX_A5 + select CPU_V7 + select GPIO_MSM_V2 + select MSM_GPIOMUX + select MSM_RPM + select MSM_SPM_V2 + select MSM_NATIVE_RESTART + select REGULATOR + select MSM_RPM_REGULATOR + select MULTI_IRQ_HANDLER + select MSM_PM8X60 if PM + select MSM_XO + select MSM_MULTIMEDIA_USE_ION + select MSM_QDSP6_APR + select MSM_AUDIO_QDSP6 if SND_SOC + select FIQ + +config ARCH_MSM8625 + bool "MSM8625" + select ARCH_MSM_CORTEX_A5 + select CPU_V7 + select GPIO_MSM_V1 + select MSM_GPIOMUX + select ARM_GIC + select ARCH_MSM_CORTEXMP + select MULTI_IRQ_HANDLER + select ARM_TICKET_LOCKS + select MSM_RUN_QUEUE_STATS + +config ARCH_MSM9625 + bool "MSM9625" + select ARM_GIC + select GIC_SECURE + select ARCH_MSM_CORTEX_A5 + select SMP + select MSM_SMP + select CPU_V7 + select MSM_GPIOMUX + select MULTI_IRQ_HANDLER + select GPIO_MSM_V2 + +endmenu +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 MSM_HAS_DEBUG_UART_HS +config MSM_KRAIT_TBB_ABORT_HANDLER + bool "Krait TBB/TBH data abort handler" + depends on ARCH_MSM_KRAIT + depends on ARM_THUMB + help + Certain early samples of the Krait processor may generate data + aborts for TBB / TBH instructions that fail their condition code + checks. Enabling this option will ignore these erroneous data aborts, + at the expense of a very small performance penalty. + + If unsure, say N. + +config ARCH_MSM_ARM11 bool -config MSM_SOC_REV_A +config ARCH_MSM_SCORPION bool -config ARCH_MSM_SCORPIONMP + +config ARCH_MSM_KRAIT bool + select ARM_L1_CACHE_SHIFT_6 + +config MSM_SMP select HAVE_SMP + bool -config ARCH_MSM_ARM11 +config ARCH_MSM_SCORPIONMP + select ARCH_MSM_SCORPION + select MSM_SMP + select HAVE_ARCH_HAS_CURRENT_TIMER bool -config ARCH_MSM_SCORPION + +config ARCH_MSM_KRAITMP + select ARCH_MSM_KRAIT + select MSM_SMP + select HAVE_ARCH_HAS_CURRENT_TIMER + bool + select HAVE_HW_BRKPT_RESERVED_RW_ACCESS + +config ARCH_MSM_CORTEXMP + select MSM_SMP + bool + +config ARCH_MSM_CORTEX_A5 + bool + select HAVE_HW_BRKPT_RESERVED_RW_ACCESS + +config ARCH_MSM7X27A bool + select MSM_DALRPC + select MSM_PROC_COMM_REGULATOR + select MULTI_IRQ_HANDLER + select ARM_GIC + select ARCH_MSM_CORTEXMP config MSM_VIC bool -menu "Qualcomm MSM Board Type" +config MSM_RPM + bool "Resource Power Manager" + select MSM_MPM + +config MSM_RPM_SMD + depends on MSM_SMD + bool "Support for using SMD as the transport layer for communicatons with RPM" + +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_REMOTE_SPINLOCK_SFPB + 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 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_MSM7625A_SURF + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7625A SURF" + help + Support for the Qualcomm MSM7625A SURF. + +config MACH_MSM7625A_FFA + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7625A FFA" + help + Support for the Qualcomm MSM7625A FFA. + +config MACH_MSM7627A_QRD1 + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7627A QRD1" + help + Support for the Qualcomm MSM7627A Reference Design. + +config MACH_MSM7627A_QRD3 + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7627A QRD3" + help + Support for the Qualcomm MSM7627A Reference Design. + +config MACH_MSM7627A_EVB + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7627A EVB" + help + Support for the Qualcomm MSM7627A Reference Design. + +config MACH_MSM8625_RUMI3 + depends on ARCH_MSM8625 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8625 RUMI3" + help + Support for the Qualcomm MSM8625 RUMI3 Emulation Platform. + +config MACH_MSM8625_SURF + depends on ARCH_MSM8625 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8625 SURF" + help + Support for the Qualcomm MSM8625 SURF. + +config MACH_MSM8625_FFA + depends on ARCH_MSM8625 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8625 FFA" help - Support for the Qualcomm MSM7x30 SURF eval board. + Support for the Qualcomm MSM8625 FFA. + +config MACH_MSM8625_EVB + depends on ARCH_MSM8625 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8625 EVB" + help + Support for the Qualcomm MSM8625 Reference Design. + +config MACH_MSM8625_QRD7 + depends on ARCH_MSM8625 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8625 QRD7" + help + Support for the Qualcomm MSM8625 Reference Design. + +config MACH_MSM8625_EVT + depends on ARCH_MSM8625 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8625 EVT" + help + Support for the Qualcomm MSM8625 Reference Design. + +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 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 ST1.5. + 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_MSM8X60_DRAGON + depends on ARCH_MSM8X60 + default n + bool "MSM8x60 DRAGON" + help + Support for the Qualcomm MSM8x60 Dragon board. + config MACH_MSM8960_SIM depends on ARCH_MSM8960 bool "MSM8960 Simulator" @@ -148,23 +721,1640 @@ 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 MACH_MSM8960_LIQUID + depends on ARCH_MSM8960 + bool "MSM8960 LIQUID" + help + Support for the Qualcomm MSM8960 LIQUID device. + +config MACH_MSM8930_CDP + depends on ARCH_MSM8930 + bool "MSM8930 CDP" + help + Support for the Qualcomm MSM8930 CDP device. + +config MACH_MSM8930_MTP + depends on ARCH_MSM8930 + bool "MSM8930 MTP" + help + Support for the Qualcomm MSM8930 MTP device. + +config MACH_MSM8930_FLUID + depends on ARCH_MSM8930 + bool "MSM8930 FLUID" + help + Support for the Qualcomm MSM8930 FLUID device. + +config MACH_MSM8627_CDP + depends on ARCH_MSM8930 + bool "MSM8627 CDP" + help + Support for the Qualcomm MSM8627 CDP device. + +config MACH_MSM8627_MTP + depends on ARCH_MSM8930 + bool "MSM8627 MTP" + help + Support for the Qualcomm MSM8627 MTP device. + +config MACH_MSM9615_CDP + depends on ARCH_MSM9615 + bool "MSM9615 CDP" + help + Support for the Qualcomm MSM9615 CDP device. + +config MACH_MSM9615_MTP + depends on ARCH_MSM9615 + bool "MSM9615 MTP" + help + Support for the Qualcomm MSM9615 MTP 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_APQ8064_RUMI3 + depends on ARCH_APQ8064 + bool "APQ8064 RUMI3" + help + Support for the Qualcomm APQ8064 RUMI3 emulator. + +config MACH_APQ8064_CDP + depends on ARCH_APQ8064 + bool "APQ8064 CDP" + help + Support for the Qualcomm APQ8064 CDP device. + +config MACH_APQ8064_MTP + depends on ARCH_APQ8064 + bool "APQ8064 MTP" + help + Support for the Qualcomm APQ8064 MTP device. + +config MACH_APQ8064_LIQUID + depends on ARCH_APQ8064 + bool "APQ8064 LIQUID" + help + Support for the Qualcomm APQ8064 LIQUID device. + +config MACH_MPQ8064_CDP + depends on ARCH_APQ8064 + bool "MPQ8064 CDP" + help + Support for the Qualcomm MPQ8064 CDP device. + +config MACH_MPQ8064_HRD + depends on ARCH_APQ8064 + bool "MPQ8064 HRD" + help + Support for the Qualcomm MPQ8064 HRD device. + +config MACH_MPQ8064_DTV + depends on ARCH_APQ8064 + bool "MPQ8064 DTV" + help + Support for the Qualcomm MPQ8064 DTV device. + +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_SMD_PKG3 - bool +config MSM_STACKED_MEMORY + bool "Stacked Memory" + default y + help + 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. -config MSM_PROC_COMM - bool +config PHYS_OFFSET + hex + default "0x40800000" if ARCH_MSM9615 + default "0x80200000" if ARCH_APQ8064 + default "0x80200000" if ARCH_MSM8960 + default "0x80200000" if ARCH_MSM8930 + default "0x00000000" if ARCH_MSMCOPPER + default "0x10000000" if ARCH_FSM9XXX + default "0x20200000" if ARCH_MSM9625 + 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 MSM_SMD - bool +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 || ARCH_MSMCOPPER) + help + Enable the in-kernel PMEM allocator to use EBI memory. -config MSM_GPIOMUX - bool +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_V2_TLMM + config MSM_AMSS_VERSION_6225 + bool "6.2.20 + New ADSP" +endchoice + +config MSM_HAS_DEBUG_UART_HS bool + help + Say Y here if high speed MSM UART is present. -config MSM_SCM +config MSM_HAS_DEBUG_UART_HS_V14 bool + select MSM_HAS_DEBUG_UART_HS + help + Say Y here if high speed MSM UART v1.4 is present. + +config MSM_DEBUG_UART_PHYS + hex + default 0xA9A00000 if (ARCH_MSM7X27 || ARCH_QSD8X50) && DEBUG_MSM_UART1 + default 0xACA00000 if ARCH_MSM7X30 && DEBUG_MSM_UART1 + default 0x94000000 if ARCH_FSM9XXX && DEBUG_MSM_UART1 + default 0xA9B00000 if (ARCH_MSM7X27 || ARCH_QSD8X50) && DEBUG_MSM_UART2 + default 0xACB00000 if ARCH_MSM7X30 && DEBUG_MSM_UART2 + default 0x94100000 if ARCH_FSM9XXX && DEBUG_MSM_UART2 + default 0xA9C00000 if (ARCH_MSM7X27 || ARCH_QSD8X50) && DEBUG_MSM_UART3 + default 0xACC00000 if ARCH_MSM7X30 && DEBUG_MSM_UART3 + +choice + prompt "Debug UART" + depends on DEBUG_LL + + config DEBUG_MSM_UART1 + bool "Kernel low-level debugging messages via MSM UART1" + depends on ARCH_MSM7X27 || ARCH_MSM7X30 || ARCH_QSD8X50 || ARCH_FSM9XXX + help + Say Y here if you want the debug print routines to direct + their output to the first serial port on MSM devices. + + config DEBUG_MSM_UART2 + bool "Kernel low-level debugging messages via MSM UART2" + depends on ARCH_MSM7X27 || ARCH_MSM7X30 || ARCH_QSD8X50 || ARCH_FSM9XXX + help + Say Y here if you want the debug print routines to direct + their output to the second serial port on MSM devices. + + config DEBUG_MSM_UART3 + bool "Kernel low-level debugging messages via MSM UART3" + depends on ARCH_MSM7X27 || ARCH_MSM7X30 || ARCH_QSD8X50 + help + Say Y here if you want the debug print routines to direct + their output to the third serial port on MSM devices. + + config DEBUG_MSM8660_UART + bool "Kernel low-level debugging messages via MSM 8660 UART" + depends on ARCH_MSM8X60 + select MSM_HAS_DEBUG_UART_HS + help + Say Y here if you want the debug print routines to direct + their output to the serial port on MSM 8660 devices. + + config DEBUG_MSM8960_UART + bool "Kernel low-level debugging messages via MSM 8960 UART" + depends on ARCH_MSM8960 && DEBUG_LL + select MSM_HAS_DEBUG_UART_HS + help + Say Y here if you want the debug print routines to direct + their output to the serial port on MSM 8960 devices. + + config DEBUG_MSM8930_UART + bool "Kernel low-level debugging messages via MSM 8930 UART" + depends on ARCH_MSM8930 && DEBUG_LL + select MSM_HAS_DEBUG_UART_HS + help + Say Y here if you want the debug print routines to direct + their output to the serial port on MSM 8930 devices. + + config DEBUG_APQ8064_UART + bool "Kernel low-level debugging messages via APQ 8064 UART" + depends on ARCH_APQ8064 && DEBUG_LL + select MSM_HAS_DEBUG_UART_HS + help + Say Y here if you want the debug print routines to direct + their output to the serial port on APQ 8064 devices. + + config DEBUG_MSMCOPPER_UART + bool "Kernel low-level debugging messages via MSM Copper UART" + depends on ARCH_MSMCOPPER + select MSM_HAS_DEBUG_UART_HS_V14 + help + Say Y here if you want the debug print routines to direct + their output to the serial port on MSM Copper devices. +endchoice + +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 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 + default y + bool "Proc-Comm RPC Interface" + help + Enables a lightweight communications interface to the + baseband processor. + +config MSM_SMD + 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_PCIE + bool "MSM PCIe Controller driver" + depends on PCI && PCI_MSI + help + Enables the PCIe functionality by configures PCIe core on + MSM chipset and by enabling the ARM PCI framework extension. + +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_MSM7X01A)) + 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_MSM7X01A)) + 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_IPC_LOGGING + bool "MSM Debug Logging for IPC Drivers" + help + This option allows the debug logging for IPC Drivers. + + If in doubt, say no. + +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 NET + default n + bool "MSM IPC Router support" + help + Support for the MSM IPC Router for communication between + the APPs and the MODEM + +config MSM_IPC_ROUTER_SMD_XPRT + depends on MSM_SMD + depends on MSM_IPC_ROUTER + default n + bool "MSM SMD XPRT Layer" + help + SMD Transport Layer for IPC Router + +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" + default n + 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 RTAC + bool "MSM8K real-time audio calibration support" + default y + help + Enable support for rtac. 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_PROC_COMM_REGULATOR + bool + depends on MSM_PROC_COMM && REGULATOR + help + Enable regulator framework support for regulators managed by PMLIB + on the modem, and controlled through proccomm calls. + +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_RPM_REGULATOR + bool "RPM regulator driver" + depends on MSM_RPM && REGULATOR + help + Compile in support for the RPM regulator driver, used for setting + voltages and other parameters of the various power rails supplied + by some Qualcomm PMICs. + +config MSM_RPM_REGULATOR_SMD + bool "SMD RPM regulator driver" + depends on REGULATOR + depends on OF + depends on MSM_RPM_SMD + help + Compile in support for the SMD RPM regulator driver which is used for + setting voltages and other parameters of the various power rails + supplied by some Qualcomm PMICs. The SMD RPM regulator driver should + be used on systems which contain an RPM which communicates with the + application processor over SMD. + +config MSM_PIL + bool "Peripheral image loading" + select FW_LOADER + 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_PIL_MODEM + tristate "Modem (ARM11) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down ARM11 Modem processors. + +config MSM_PIL_QDSP6V3 + tristate "QDSP6v3 (Hexagon) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down QDSP6v3 processors (hexagon). + The QDSP6 is a low power DSP used in audio software applications. + +config MSM_PIL_QDSP6V4 + tristate "QDSP6v4 (Hexagon) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down QDSP6v4 processors (hexagon). + The QDSP6 is a low power DSP used in audio, modem firmware, and modem + software applications. + +config MSM_PIL_LPASS_QDSP6V5 + tristate "LPASS QDSP6v5 (Hexagon) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down QDSP6v5 processors (Hexagon) + processors in low power audio subsystems. + +config MSM_PIL_RIVA + tristate "RIVA (WCNSS) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down the RIVA processor (WCNSS). + Riva is the wireless subsystem processor used in bluetooth, wireless + LAN, and FM software applications. + +config MSM_PIL_TZAPPS + tristate "TZApps Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down TZApps. + + TZApps is an image that runs in the secure processor state. It is + used to decrypt data and perform secure operations on the behalf of + the kernel. + +config MSM_PIL_DSPS + tristate "DSPS Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down ARM7 DSPS processors. + + DSPS is a sensors offloading processor used for applications such + as rotation detection, temperature, etc. + +config MSM_PIL_VIDC + tristate "Video Core Secure Boot Support" + depends on MSM_PIL + help + Support for authenticating the video core image. + +config MSM_PIL_GSS + tristate "GSS (Coretx A5) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down Cortex A5 processors which run + GPS subsystem firmware. + +config MSM_PIL_PRONTO + tristate "PRONTO (WCNSS) Boot Support" + depends on MSM_PIL + help + Support for booting and shutting down the PRONTO processor (WCNSS). + PRONTO is the wireless subsystem processor used in bluetooth, wireless + LAN, and FM software applications. + +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 || ARCH_MSM9615) + default n + help + This option enables the MSM subsystem restart driver, which provides + a framework to handle subsystem crashes. + +config MSM_SYSMON_COMM + bool "MSM System Monitor communication support" + depends on MSM_SMD && MSM_SUBSYSTEM_RESTART + default y + help + This option adds support for MSM System Monitor library, which + provides an API that may be used for notifying subsystems within + the SoC about other subsystems' power-up/down state-changes. + +config MSM_MODEM_8960 + bool "MSM 8960 Modem driver" + depends on (ARCH_MSM8960 || ARCH_MSM9615) + help + This option enables the modem driver for the MSM8960 and MSM9615, which monitors + modem hardware watchdog interrupt lines and plugs into the subsystem + restart and PIL drivers. For MSM9615, it only supports a full chip reset. + +config MSM_LPASS_8960 + tristate "MSM 8960 Lpass driver" + depends on (ARCH_MSM8960 || ARCH_MSM9615) + help + This option enables the lpass driver for the MSM8960 and MSM9615. This monitors + lpass hardware watchdog interrupt lines and plugs into the subsystem + restart and PIL drivers. For MSM9615, it only supports a full chip reset. + +config MSM_WCNSS_SSR_8960 + tristate "MSM 8960 WCNSS restart module" + depends on (ARCH_MSM8960) + help + This option enables the WCNSS restart module for MSM8960, which + monitors WCNSS hardware watchdog interrupt lines and plugs WCNSS + into the subsystem restart framework. + +config MSM_GSS_SSR_8064 + bool "MSM 8064 GSS restart driver" + depends on (ARCH_APQ8064) + help + This option enables the gps subsystem restart driver for APQ8064, which monitors + gss 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_BUSPM_DEV + tristate "MSM Bus Performance Monitor Kernel Module" + depends on (ARCH_MSM8X60 || ARCH_MSM8960) + default m + help + This kernel module is used to mmap() hardware registers for the + performance monitors, counters, etc. The module can also be used to + allocate physical memory which is used by bus performance hardware to + dump performance data. + +config MSM_TZ_LOG + tristate "MSM Trust Zone (TZ) Log Driver" + depends on DEBUG_FS + help + This option enables a driver with a debugfs interface for messages + produced by the Secure code (Trust zone). These messages provide + diagnostic information about TZ operation. + +config MSM_RPM_LOG + tristate "MSM Resource Power Manager Log Driver" + depends on DEBUG_FS + depends on MSM_RPM + 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 MSM_RPM + 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_DIRECT_SCLK_ACCESS + bool "Direct access to the SCLK timer" + default n + +config IOMMU_API + bool + +config MSM_GPIOMUX + bool + +config MSM_SECURE_IO + bool + +config MSM_NATIVE_RESTART + bool + +config MSM_PM2 + depends on PM + bool + +config MSM_PM8X60 + depends on PM + bool + +config MSM_NOPM + default y if !PM + bool + +config MSM_BUS_SCALING + bool "Bus scaling driver" + default n + +config MSM_BUS_RPM_MULTI_TIER_ENABLED + bool "RPM Multi-tiering Configuration" + depends on MSM_BUS_SCALING + +config MSM_WATCHDOG + bool "MSM Watchdog Support" + depends on ARCH_MSM8X60 || ARCH_MSM8960 || ARCH_MSM9615 || ARCH_FSM9XXX + 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 || ARCH_MSM9615 + 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_JTAG + bool "JTAG debug and trace support" + help + Add additional support for JTAG kernel debugging and tracing. + +config MSM_ETM + tristate "Enable MSM ETM and ETB" + depends on ARCH_MSM8X60 + select MSM_JTAG + help + Enables embedded trace collection on MSM8660 + +config MSM_QDSS + bool "Qualcomm Debug Subsystem" + select MSM_JTAG + help + Enables support for Qualcomm Debug Subsystem. + +config MSM_QDSS_ETM_DEFAULT_ENABLE + bool "Turn on QDSS ETM Tracing by Default" + depends on MSM_QDSS + help + Turns on QDSS ETM tracing by default. Otherwise, tracing is + disabled by default but can be enabled by other means. + +config MSM_SLEEP_STATS + bool "Enable exporting of MSM sleep stats to userspace" + depends on CPU_IDLE + default n + +config MSM_SLEEP_STATS_DEVICE + bool "Enable exporting of MSM sleep device stats to userspace" + +config MSM_RUN_QUEUE_STATS + bool "Enable collection and exporting of MSM Run Queue stats to userspace" + depends on (MSM_SOC_REV_A || ARCH_MSM8X60 || ARCH_MSM8960) + help + This option enalbes statistics collection on Run Queue. A daemon + in user mode, called MPDecision will be using this data to decide + on when to switch off/on the other cores. + +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 (ARM_GIC || PMIC8058) + default y if PMIC8058 + 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_QDSP6_CODECS + bool "Audio Codecs on QDSP6 APR " + depends on MSM_SMD + default n + help + Enable Audio codecs with 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_ULTRASOUND + bool "MSM ultrasound support" + depends on MSM_AUDIO_QDSP6 + help + Enable support for qdsp6/ultrasound. + +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. + +config MSM_MULTIMEDIA_USE_ION + bool "Multimedia suport using Ion" + depends on ION_MSM + help + Enable support for multimedia drivers using Ion for buffer management + instead of pmem. Selecting this may also involve userspace + dependencies as well. + +config MSM_OCMEM + bool "MSM On-Chip memory driver (OCMEM)" + help + Enable support for On-Chip Memory available on certain MSM chipsets. + OCMEM is a low latency, high performance pool shared by subsystems. + +config MSM_RTB + bool "Register tracing" + help + Add support for logging different events to a small uncached + region. This is designed to aid in debugging reset cases where the + caches may not be flushed before the target resets. + +config MSM_RTB_SEPARATE_CPUS + bool "Separate entries for each cpu" + depends on MSM_RTB + depends on SMP + help + Under some circumstances, it may be beneficial to give dedicated space + for each cpu to log accesses. Selecting this option will log each cpu + separately. This will guarantee that the last acesses for each cpu + will be logged but there will be fewer entries per cpu + +config MSM_CACHE_ERP + bool "Cache / CPU error reporting" + depends on ARCH_MSM_KRAIT + help + Say 'Y' here to enable reporting of cache and TLB errors to the kernel + log. Enabling this feature can be used as a system debugging technique + if cache corruption is suspected. Cache error statistics will also be + reported in /proc/cpu/msm_cache_erp. + + For production builds, you should probably say 'N' here. + +config MSM_L1_ERR_PANIC + bool "Panic on L1 cache errors" + depends on MSM_CACHE_ERP + help + To cause the kernel to panic whenever an L1 cache error is detected, say + 'Y' here. This may be useful as a debugging technique if general system + instability is suspected. + + For production builds, you should probably say 'N' here. + +config MSM_L2_ERP_PRINT_ACCESS_ERRORS + bool "Report L2 master port slave/decode errors in kernel log" + depends on MSM_CACHE_ERP + help + Master port errors can occur when a memory request is not properly + handled by the destination slave. This can occur if the destination + register does not exist or is inaccessible due to security + restrictions or (in some cases) clock configuration. Enabling this + option will cause a backtrace to be printed to the kernel log whenever + such an error is encountered. Note that the error is reported as an + interrupt rather than as an exception, meaning that the backtrace may + have some skid. This option may help with debugging, though production + builds should probably say 'N' here. + +config MSM_L2_ERP_PORT_PANIC + bool "Panic on L2 master port errors" + depends on MSM_CACHE_ERP && MSM_L2_ERP_PRINT_ACCESS_ERRORS + help + Master port errors can occur when a memory request is not properly + handled by the destination slave. Enable this option to catch drivers + which attempt to access bad areas of the address space, or access + hardware registers in an improper state (such as certain clocks not + being on). This option may help with debugging, though production + builds should probably say 'N' here. + +config MSM_L2_ERP_1BIT_PANIC + bool "Panic on recoverable L2 soft errors" + depends on MSM_CACHE_ERP + help + Enable this option to cause a kernel panic whenever the L2 cache + encounters a single-bit (correctable) soft error. This option should + only be enabled when doing low-level debugging where cache corruption + is suspected. + + For production builds, you should definitely say 'N' here. + +config MSM_L2_ERP_2BIT_PANIC + bool "Panic on unrecoverable L2 soft errors" + depends on MSM_CACHE_ERP + help + Enable this option to cause a kernel panic whenever the L2 cache + encounters a double-bit (non-correctable) soft error. Debug builds + will likely benefit from having this option enabled to catch cache + problems as soon as possible. + + For production builds, it may be acceptable to say 'N' here, since + an uncorrectable error might not necessarily cause further problems. + +config MSM_DCVS + bool "Use MSM DCVS for CPU/GPU Frequency control" + depends on MSM_SCM + help + Enable support for MSM DCVS to control all CPU and GPU core frequencies. + The DCVS manager allows idle driver to feed the idle information to the + algorithm and the algorithm returns a frequency for the core which is + passed to the frequency change driver. + +config HAVE_ARCH_HAS_CURRENT_TIMER + bool + +config MSM_CACHE_DUMP + bool "Cache dumping support" + help + Add infrastructure to dump the L1 and L2 caches to an allocated buffer. + This allows for analysis of the caches in case cache corruption is + suspected. + +config MSM_CACHE_DUMP_ON_PANIC + bool "Dump caches on panic" + depends on MSM_CACHE_DUMP + help + By default, the caches are flushed on panic. This means that trying to + look at them in a RAM dump will give useless data. Select this if you + want to dump the L1 and L2 caches on panic before any flush occurs. + If unsure, say N + +config MSM_HSIC_SYSMON + tristate "MSM HSIC system monitor driver" + depends on USB + help + Add support for bridging with the system monitor interface of MDM + over HSIC. This driver allows the local system monitor to + communicate with the remote system monitor interface. + +config MSM_HSIC_SYSMON_TEST + tristate "MSM HSIC system monitor bridge test" + depends on USB && MSM_HSIC_SYSMON && DEBUG_FS + help + Enable the test hook for the Qualcomm system monitor HSIC driver. + This will create a debugfs file entry named "hsic_sysmon_test" which + can be read and written to send character data to the sysmon port of + the modem over USB. + endif diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index 4ad3969b98817cc42446d22476aae2ce5771efc6..975c1798aed2b9687299a2ad27869421c10ef940 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -1,33 +1,368 @@ -obj-y += io.o idle.o timer.o -obj-y += clock.o -obj-$(CONFIG_DEBUG_FS) += clock-debug.o +CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) + +obj-y += io.o dma.o memory.o +ifndef CONFIG_ARM_ARCH_TIMER +obj-y += timer.o +endif +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-y += acpuclock.o +obj-$(CONFIG_ARCH_MSM7X27) += acpuclock-7627.o clock-pll.o +obj-$(CONFIG_ARCH_MSM_SCORPION) += pmu.o +obj-$(CONFIG_ARCH_MSM_KRAIT) += msm-krait-l2-accessors.o pmu.o +obj-$(CONFIG_ARCH_MSM7X27A) += 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) += 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 +ifdef CONFIG_ARCH_MSM8625 + obj-$(CONFIG_SMP) += platsmp-8625.o +else + obj-$(CONFIG_SMP) += platsmp.o +endif +obj-$(CONFIG_HOTPLUG_CPU) += hotplug.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) += jtag.o -obj-$(CONFIG_MSM_PROC_COMM) += proc_comm.o clock-pcom.o vreg.o +msm-etm-objs := etm.o +obj-$(CONFIG_MSM_ETM) += msm-etm.o +obj-$(CONFIG_MSM_QDSS) += qdss.o qdss-etb.o qdss-tpiu.o qdss-funnel.o qdss-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 +obj-$(CONFIG_MSM_PIL) += scm-pas.o +obj-$(CONFIG_MSM_PIL_QDSP6V3) += pil-q6v3.o +obj-$(CONFIG_MSM_PIL_QDSP6V4) += pil-q6v4.o +obj-$(CONFIG_MSM_PIL_LPASS_QDSP6V5) += pil-q6v5.o pil-q6v5-lpass.o +obj-$(CONFIG_MSM_PIL_RIVA) += pil-riva.o +obj-$(CONFIG_MSM_PIL_TZAPPS) += pil-tzapps.o +obj-$(CONFIG_MSM_PIL_VIDC) += pil-vidc.o +obj-$(CONFIG_MSM_PIL_MODEM) += pil-modem.o +obj-$(CONFIG_MSM_PIL_DSPS) += pil-dsps.o +obj-$(CONFIG_MSM_PIL_GSS) += pil-gss.o +obj-$(CONFIG_MSM_PIL_PRONTO) += pil-pronto.o +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_IPC_LOGGING) += ipc_logging.o +ifdef CONFIG_DEBUG_FS +obj-$(CONFIG_MSM_IPC_LOGGING) += ipc_logging_debug.o +endif +obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o remote_spinlock.o smd_private.o +obj-y += socinfo.o +ifndef CONFIG_ARCH_MSM9615 +ifndef CONFIG_ARCH_APQ8064 +ifndef CONFIG_ARCH_MSM8960 +ifndef CONFIG_ARCH_MSM8X60 +ifndef CONFIG_ARCH_MSMCOPPER + obj-$(CONFIG_MSM_SMD) += pmic.o + obj-$(CONFIG_MSM_ONCRPCROUTER) += rpc_hsusb.o rpc_pmapp.o rpc_fsusb.o +endif +endif +endif +endif +endif +ifndef CONFIG_ARCH_MSM8960 +ifndef CONFIG_ARCH_MSM8X60 +ifndef CONFIG_ARCH_APQ8064 +ifndef CONFIG_ARCH_MSMCOPPER +ifndef CONFIG_ARCH_MSM9625 + obj-y += nand_partitions.o +endif +endif +endif +endif +endif +obj-$(CONFIG_MSM_SDIO_TTY) += sdio_tty.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 +obj-$(CONFIG_PM) += pm-boot.o +obj-$(CONFIG_MSM_PM8X60) += pm-8x60.o pm-data.o +obj-$(CONFIG_MSM_IDLE_STATS) += pm-stats.o +obj-$(CONFIG_MSM_PM2) += pm2.o +obj-$(CONFIG_MSM_NOPM) += no-pm.o -CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) +obj-$(CONFIG_MSM_PCIE) += pcie.o pcie_irq.o -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_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 clock-pll.o +obj-$(CONFIG_ARCH_MSM8X60) += clock-rpm.o +obj-$(CONFIG_ARCH_MSM8X60) += saw-regulator.o +obj-$(CONFIG_ARCH_MSM8X60) += footswitch-8x60.o + +ifdef CONFIG_MSM_RPM_REGULATOR +obj-y += rpm-regulator.o +obj-$(CONFIG_ARCH_MSM8X60) += rpm-regulator-8660.o +obj-$(CONFIG_ARCH_MSM8960) += rpm-regulator-8960.o +obj-$(CONFIG_ARCH_MSM9615) += rpm-regulator-9615.o +obj-$(CONFIG_ARCH_MSM8930) += rpm-regulator-8930.o +obj-$(CONFIG_ARCH_APQ8064) += rpm-regulator-8960.o +endif + +obj-$(CONFIG_MSM_RPM_REGULATOR_SMD) += rpm-regulator-smd.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 +ifdef CONFIG_MSM_SUBSYSTEM_RESTART + obj-y += subsystem_notif.o + obj-y += subsystem_restart.o + obj-y += ramdump.o + obj-$(CONFIG_ARCH_MSM8X60) += modem-8660.o lpass-8660.o +endif +obj-$(CONFIG_MSM_SYSMON_COMM) += sysmon.o +obj-$(CONFIG_MSM_MODEM_8960) += modem-8960.o +obj-$(CONFIG_MSM_LPASS_8960) += lpass-8960.o +obj-$(CONFIG_MSM_WCNSS_SSR_8960) += wcnss-ssr-8960.o +obj-$(CONFIG_MSM_GSS_SSR_8064) += gss-8064.o -obj-$(CONFIG_ARCH_MSM7X30) += gpiomux-v1.o gpiomux.o +ifdef CONFIG_CPU_IDLE + obj-$(CONFIG_ARCH_APQ8064) += cpuidle.o + obj-$(CONFIG_ARCH_MSM8960) += cpuidle.o + obj-$(CONFIG_ARCH_MSM8X60) += cpuidle.o + obj-$(CONFIG_ARCH_MSM9615) += cpuidle.o + obj-$(CONFIG_ARCH_MSMCOPPER) += cpuidle.o +endif + +ifdef CONFIG_MSM_CAMERA_V4L2 + obj-$(CONFIG_ARCH_MSM8X60) += board-msm8x60-camera.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 xo-fsm9xxx.o + +obj-$(CONFIG_MSM_WATCHDOG) += msm_watchdog.o +obj-$(CONFIG_MSM_WATCHDOG) += msm_watchdog_asm.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_MSM8X60_DRAGON) += 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 devices-msm7x27a.o +board-7627a-all-objs += board-msm7627a-storage.o board-msm7627a-bt.o board-msm7627a-camera.o +board-7627a-all-objs += board-msm7627a-display.o board-msm7627a-wlan.o board-msm7627a-io.o +obj-$(CONFIG_MACH_MSM7X27A_RUMI3) += board-msm7x27a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM7X27A_SURF) += board-msm7x27a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM7X27A_FFA) += board-msm7x27a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM7627A_QRD1) += board-qrd7627a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM7627A_QRD3) += board-qrd7627a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM7627A_EVB) += board-qrd7627a.o board-7627a-all.o +obj-$(CONFIG_ARCH_MSM8625) += devices-msm7x27a.o clock-pcom-lookup.o mpm-8625.o +obj-$(CONFIG_MACH_MSM8625_RUMI3) += board-msm7x27a.o +obj-$(CONFIG_MACH_MSM8625_SURF) += board-msm7x27a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM8625_EVB) += board-qrd7627a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM8625_QRD7) += board-qrd7627a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM8625_FFA) += board-msm7x27a.o board-7627a-all.o +obj-$(CONFIG_MACH_MSM8625_EVT) += board-msm7x27a.o board-7627a-all.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 clock-pll.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 clock-pll.o +obj-$(CONFIG_ARCH_MSM8960) += footswitch-8x60.o +obj-$(CONFIG_ARCH_MSM8960) += acpuclock-8960.o +obj-$(CONFIG_ARCH_MSM8960) += memory_topology.o +obj-$(CONFIG_ARCH_MSM8960) += saw-regulator.o +obj-$(CONFIG_ARCH_MSM8960) += devices-8960.o +obj-$(CONFIG_ARCH_APQ8064) += devices-8960.o devices-8064.o +board-8960-all-objs += board-8960.o board-8960-camera.o board-8960-display.o board-8960-pmic.o board-8960-storage.o board-8960-gpiomux.o +board-8930-all-objs += board-8930.o board-8930-camera.o board-8930-display.o board-8930-pmic.o board-8930-storage.o board-8930-gpiomux.o devices-8930.o board-8930-gpu.o +board-8064-all-objs += board-8064.o board-8064-pmic.o board-8064-storage.o board-8064-gpiomux.o board-8064-camera.o board-8064-display.o board-8064-gpu.o +obj-$(CONFIG_MACH_MSM8960_SIM) += board-8960-all.o board-8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_RUMI3) += board-8960-all.o board-8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_CDP) += board-8960-all.o board-8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_MTP) += board-8960-all.o board-8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_FLUID) += board-8960-all.o board-8960-regulator.o +obj-$(CONFIG_MACH_MSM8930_CDP) += board-8930-all.o board-8930-regulator.o +obj-$(CONFIG_MACH_MSM8930_MTP) += board-8930-all.o board-8930-regulator.o +obj-$(CONFIG_MACH_MSM8930_FLUID) += board-8930-all.o board-8930-regulator.o +obj-$(CONFIG_PM8921_BMS) += bms-batterydata.o bms-batterydata-desay.o +obj-$(CONFIG_MACH_APQ8064_SIM) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_MACH_APQ8064_RUMI3) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_MACH_APQ8064_CDP) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_MACH_APQ8064_MTP) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_MACH_APQ8064_LIQUID) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_MACH_MPQ8064_HRD) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_MACH_MPQ8064_DTV) += board-8064-all.o board-8064-regulator.o +obj-$(CONFIG_ARCH_MSM9615) += board-9615.o devices-9615.o board-9615-regulator.o board-9615-gpiomux.o board-9615-storage.o board-9615-display.o +obj-$(CONFIG_ARCH_MSM9615) += clock-local.o clock-9615.o acpuclock-9615.o clock-rpm.o clock-pll.o +obj-$(CONFIG_ARCH_MSMCOPPER) += board-copper.o board-dt.o board-copper-regulator.o board-copper-gpiomux.o +obj-$(CONFIG_ARCH_MSMCOPPER) += acpuclock-krait.o acpuclock-copper.o +obj-$(CONFIG_ARCH_MSMCOPPER) += clock-local2.o clock-pll.o clock-copper.o +obj-$(CONFIG_ARCH_MSMCOPPER) += gdsc.o +obj-$(CONFIG_ARCH_MSM9625) += board-9625.o board-9625-gpiomux.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_MSM_RPM) += rpm.o +ifdef CONFIG_MSM_RPM + obj-$(CONFIG_ARCH_APQ8064) += rpm_resources.o + obj-$(CONFIG_ARCH_MSM8960) += rpm_resources.o + obj-$(CONFIG_ARCH_MSM8X60) += rpm_resources.o + obj-$(CONFIG_ARCH_MSM9615) += rpm_resources.o + obj-$(CONFIG_ARCH_MSMCOPPER) += lpm_levels.o +endif +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_TZ_LOG) += tz_log.o +obj-$(CONFIG_MSM_XO) += msm_xo.o +obj-$(CONFIG_MSM_BUS_SCALING) += msm_bus/ +obj-$(CONFIG_MSM_BUSPM_DEV) += msm-buspm-dev.o + +obj-$(CONFIG_MSM_IOMMU) += devices-iommu.o iommu_domains.o + +ifdef CONFIG_VCM +obj-$(CONFIG_ARCH_MSM8X60) += board-msm8x60-vcm.o +endif +obj-$(CONFIG_MSM_OCMEM) += ocmem.o ocmem_allocator.o + +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 +obj-$(CONFIG_ARCH_APQ8064) += gpiomux-v2.o gpiomux.o +obj-$(CONFIG_ARCH_MSM9615) += gpiomux-v2.o gpiomux.o +obj-$(CONFIG_ARCH_MSMCOPPER) += gpiomux-v2.o gpiomux.o +obj-$(CONFIG_ARCH_MSM9625) += gpiomux-v2.o gpiomux.o + + +obj-$(CONFIG_MSM_SLEEP_STATS) += idle_stats.o +obj-$(CONFIG_MSM_SLEEP_STATS_DEVICE) += idle_stats_device.o +obj-$(CONFIG_MSM_DCVS) += msm_dcvs_scm.o msm_dcvs.o msm_dcvs_idle.o +obj-$(CONFIG_MSM_RUN_QUEUE_STATS) += msm_rq_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 + +obj-$(CONFIG_MSM_PROC_COMM_REGULATOR) += proccomm-regulator.o +ifdef CONFIG_MSM_PROC_COMM_REGULATOR +obj-$(CONFIG_MACH_MSM7X27_SURF) += board-msm7627-regulator.o +obj-$(CONFIG_MACH_MSM7X27_FFA) += board-msm7627-regulator.o +obj-$(CONFIG_ARCH_MSM7X30) += board-msm7x30-regulator.o +obj-$(CONFIG_ARCH_MSM7X27A) += board-msm7x27a-regulator.o +endif + +obj-$(CONFIG_ARCH_MSM8960) += mdm2.o mdm_common.o +obj-$(CONFIG_MSM_RTB) += msm_rtb.o +obj-$(CONFIG_MSM_CACHE_ERP) += cache_erp.o +obj-$(CONFIG_MSM_CACHE_DUMP) += msm_cache_dump.o + +obj-$(CONFIG_MSM_HSIC_SYSMON) += hsic_sysmon.o +obj-$(CONFIG_MSM_HSIC_SYSMON_TEST) += hsic_sysmon_test.o + +obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd.o diff --git a/arch/arm/mach-msm/Makefile.boot b/arch/arm/mach-msm/Makefile.boot index 9b803a578b4db92f25996aee1af5899e8f5f320d..bd8d153b59a6e804e72948990442304d677b108e 100644 --- a/arch/arm/mach-msm/Makefile.boot +++ b/arch/arm/mach-msm/Makefile.boot @@ -1,3 +1,60 @@ - 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 + +# MSM8625 + zreladdr-$(CONFIG_ARCH_MSM8625) := 0x00208000 +params_phys-$(CONFIG_ARCH_MSM8625) := 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 + +# MSM8930 + zreladdr-$(CONFIG_ARCH_MSM8930) := 0x80208000 + +# APQ8064 + zreladdr-$(CONFIG_ARCH_APQ8064) := 0x80208000 + +# MSMCOPPER + zreladdr-$(CONFIG_ARCH_MSMCOPPER) := 0x00008000 + +# MSM9615 + zreladdr-$(CONFIG_ARCH_MSM9615) := 0x40808000 + +# MSM9625 + zreladdr-$(CONFIG_ARCH_MSM9625) := 0x20208000 + +# 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-7627.c b/arch/arm/mach-msm/acpuclock-7627.c new file mode 100644 index 0000000000000000000000000000000000000000..7c2c556e16e5dcffe8286a4def1bc70cb613b7b7 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-7627.c @@ -0,0 +1,992 @@ +/* + * MSM architecture clock driver + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2012, 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 "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 POWER_COLLAPSE_KHZ 19200 + +/* Max CPU frequency allowed by hardware while in standby waiting for an irq. */ +#define MAX_WAIT_FOR_IRQ_KHZ 128000 + +/** + * enum - For acpuclock PLL IDs + */ +enum { + ACPU_PLL_0 = 0, + ACPU_PLL_1, + ACPU_PLL_2, + ACPU_PLL_3, + ACPU_PLL_4, + ACPU_PLL_TCXO, + ACPU_PLL_END, +}; + +struct acpu_clk_src { + struct clk *clk; + const char *name; +}; + +static struct acpu_clk_src pll_clk[ACPU_PLL_END] = { + [ACPU_PLL_0] = { .name = "pll0_clk" }, + [ACPU_PLL_1] = { .name = "pll1_clk" }, + [ACPU_PLL_2] = { .name = "pll2_clk" }, + [ACPU_PLL_4] = { .name = "pll4_clk" }, +}; + +struct clock_state { + struct clkctl_acpu_speed *current_speed; + struct mutex lock; + uint32_t max_speed_delta_khz; + struct clk *ebi1_clk; +}; + +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 struct clock_state drv_state = { 0 }; +static struct clkctl_acpu_speed *acpu_freq_tbl; + +/* + * 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. + */ + +/* 7627 with GSM capable modem */ +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, 160000 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 133333, 2, 5, 160000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627 with CDMA capable modem */ +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, 160000 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 133333, 2, 5, 160000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627 with GSM capable modem - 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, 160000 }, + { 0, 400000, ACPU_PLL_2, 2, 1, 133333, 2, 5, 160000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 160000 }, + { 1, 800000, ACPU_PLL_2, 2, 0, 200000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627 with CDMA capable modem - 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, 160000 }, + { 0, 400000, ACPU_PLL_2, 2, 1, 133333, 2, 5, 160000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 160000 }, + { 1, 800000, ACPU_PLL_2, 2, 0, 200000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627a PLL2 @ 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, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 3, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 30720, 3, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 0, 400000, ACPU_PLL_4, 6, 1, 50000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 800000, ACPU_PLL_4, 6, 0, 100000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627a PLL2 @ 1200MHz with CDMA capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_1200_pll4_800[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 3, 8192, 3, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 12288, 3, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 24576, 3, 3, 98304 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 120000 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 120000 }, + { 0, 400000, ACPU_PLL_4, 6, 1, 50000, 3, 4, 120000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 120000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 800000, ACPU_PLL_4, 6, 0, 100000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627aa PLL4 @ 1008MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_1200_pll4_1008[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 3, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 30720, 3, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 0, 504000, ACPU_PLL_4, 6, 1, 63000, 3, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 1008000, ACPU_PLL_4, 6, 0, 126000, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627aa PLL4 @ 1008MHz with CDMA capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_1200_pll4_1008[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 3, 8192, 3, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 12288, 3, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 24576, 3, 3, 98304 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 0, 504000, ACPU_PLL_4, 6, 1, 63000, 3, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 1008000, ACPU_PLL_4, 6, 0, 126000, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 8625 PLL4 @ 1209MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_1200_pll4_1209[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 3, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 30720, 3, 3, 61440 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 0, 604800, ACPU_PLL_4, 6, 1, 75600, 3, 6, 160000 }, + { 1, 1209600, ACPU_PLL_4, 6, 0, 151200, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 8625 PLL4 @ 1209MHz with CDMA capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_1200_pll4_1209[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 3, 8192, 3, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 12288, 3, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 24576, 3, 3, 98304 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 0, 604800, ACPU_PLL_4, 6, 1, 75600, 3, 6, 160000 }, + { 1, 1209600, ACPU_PLL_4, 6, 0, 151200, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 8625 PLL4 @ 1152MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_1200_pll4_1152[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 3, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 30720, 3, 3, 61440 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 0, 576000, ACPU_PLL_4, 6, 1, 72000, 3, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 1152000, ACPU_PLL_4, 6, 0, 144000, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 8625 PLL4 @ 1115MHz with CDMA capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_1200_pll4_1152[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 3, 8192, 3, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 12288, 3, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 24576, 3, 3, 98304 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 0, 576000, ACPU_PLL_4, 6, 1, 72000, 3, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 1152000, ACPU_PLL_4, 6, 0, 144000, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + + +/* 7625a PLL2 @ 1200MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_1200_25a[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 3, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 30720, 3, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 50000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627a PLL2 @ 1200MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_737_pll2_1200_pll4_800[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 11, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 5, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 2, 30720, 3, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 0, 400000, ACPU_PLL_4, 6, 1, 50000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 800000, ACPU_PLL_4, 6, 0, 100000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627a PLL2 @ 1200MHz with CDMA capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_589_pll2_1200_pll4_800[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 8, 8192, 3, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 5, 12288, 3, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 2, 24576, 3, 3, 98304 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 120000 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 120000 }, + { 0, 400000, ACPU_PLL_4, 6, 1, 50000, 3, 4, 120000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 120000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 800000, ACPU_PLL_4, 6, 0, 100000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627aa PLL4 @ 1008MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_737_pll2_1200_pll4_1008[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 11, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 5, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 2, 30720, 3, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 0, 504000, ACPU_PLL_4, 6, 1, 63000, 3, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 1008000, ACPU_PLL_4, 6, 0, 126000, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7627aa PLL4 @ 1008MHz with CDMA capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_589_pll2_1200_pll4_1008[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 8, 8192, 3, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 5, 12288, 3, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 2, 24576, 3, 3, 98304 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 0, 504000, ACPU_PLL_4, 6, 1, 63000, 3, 6, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 160000 }, + { 1, 1008000, ACPU_PLL_4, 6, 0, 126000, 3, 7, 200000}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7625a PLL2 @ 1200MHz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_737_pll2_1200_25a[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 2400, 3, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 11, 7680, 3, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 5, 15360, 3, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 2, 30720, 3, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 37500, 3, 4, 122880 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 40000, 3, 4, 122880 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 50000, 3, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 60000, 3, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 75000, 3, 6, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +#define PLL_CONFIG(m0, m1, m2, m4) { \ + m0, m1, m2, m4, \ + pll0_##m0##_pll1_##m1##_pll2_##m2##_pll4_##m4 \ +} + +struct pll_freq_tbl_map { + unsigned int pll0_rate; + unsigned int pll1_rate; + unsigned int pll2_rate; + unsigned int pll4_rate; + struct clkctl_acpu_speed *tbl; +}; + +static struct pll_freq_tbl_map acpu_freq_tbl_list[] = { + 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), + PLL_CONFIG(960, 245, 1200, 1008), + PLL_CONFIG(960, 196, 1200, 1008), + PLL_CONFIG(960, 737, 1200, 800), + PLL_CONFIG(960, 589, 1200, 800), + PLL_CONFIG(960, 737, 1200, 1008), + PLL_CONFIG(960, 589, 1200, 1008), + PLL_CONFIG(960, 245, 1200, 1209), + PLL_CONFIG(960, 196, 1200, 1209), + PLL_CONFIG(960, 245, 1200, 1152), + PLL_CONFIG(960, 196, 1200, 1152), + { 0, 0, 0, 0, 0 } +}; + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[NR_CPUS][20]; + +static void __init cpufreq_table_init(void) +{ + int cpu; + for_each_possible_cpu(cpu) { + unsigned int i, 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[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][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[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency = CPUFREQ_TABLE_END; + /* Register table with CPUFreq. */ + cpufreq_frequency_table_get_attr(freq_table[cpu], cpu); + pr_info("CPU%d: %d scaling frequencies supported.\n", + cpu, freq_cnt); + } +} +#endif + +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(62); + 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); + + /* Wait for the clock switch to complete */ + mb(); + udelay(50); + + /* + * 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); + } +} + +static int acpuclk_7627_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, "%s: not initialized\n", __func__); + 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 = clk_enable(pll_clk[tgt_s->pll].clk); + 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 max_speed_delta_khz, + * regardless of PLL. If differnece is greater, use the + * predefined 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 = clk_enable(pll_clk[cur_s->pll].clk); + 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. */ +#ifdef CONFIG_SMP + for_each_possible_cpu(cpu) { + per_cpu(cpu_data, cpu).loops_per_jiffy = + cur_s->lpj; + } +#endif + /* Adjust the global one */ + loops_per_jiffy = cur_s->lpj; + + } + + /* 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)) + clk_disable(pll_clk[pll].clk); + + /* 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_hw_init(void) +{ + struct clkctl_acpu_speed *speed; + uint32_t div, sel, reg_clksel; + int res; + + /* + * Prepare all the PLLs because we enable/disable them + * from atomic context and can't always ensure they're + * all prepared in non-atomic context. Same goes for + * ebi1_acpu_clk. + */ + BUG_ON(clk_prepare(pll_clk[ACPU_PLL_0].clk)); + BUG_ON(clk_prepare(pll_clk[ACPU_PLL_1].clk)); + BUG_ON(clk_prepare(pll_clk[ACPU_PLL_2].clk)); + BUG_ON(clk_prepare(pll_clk[ACPU_PLL_4].clk)); + BUG_ON(clk_prepare(drv_state.ebi1_clk)); + + /* + * 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; + } + + 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 (clk_enable(pll_clk[speed->pll].clk)) + pr_warning("Failed to vote for boot PLL\n"); + } + + /* Fix div2 to 2 for 7x27/5a(aa) targets */ + if (!cpu_is_msm7x27()) { + reg_clksel = readl_relaxed(A11S_CLK_SEL_ADDR); + reg_clksel &= ~(0x3 << 14); + reg_clksel |= (0x1 << 14); + writel_relaxed(reg_clksel, A11S_CLK_SEL_ADDR); + } + + 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); +} + +static unsigned long acpuclk_7627_get_rate(int cpu) +{ + WARN_ONCE(drv_state.current_speed == NULL, + "%s: not initialized\n", __func__); + if (drv_state.current_speed) + return drv_state.current_speed->a11clk_khz; + else + return 0; +} + +/*---------------------------------------------------------------------------- + * Clock driver initialization + *---------------------------------------------------------------------------*/ +#define MHZ 1000000 +static void __init select_freq_plan(void) +{ + unsigned long pll_mhz[ACPU_PLL_END]; + struct pll_freq_tbl_map *t; + int i; + + /* Get PLL clocks */ + for (i = 0; i < ACPU_PLL_END; i++) { + if (pll_clk[i].name) { + pll_clk[i].clk = clk_get_sys("acpu", pll_clk[i].name); + if (IS_ERR(pll_clk[i].clk)) { + pll_mhz[i] = 0; + continue; + } + /* Get PLL's Rate */ + pll_mhz[i] = clk_get_rate(pll_clk[i].clk)/MHZ; + } + } + + /* + * For the pll configuration used in acpuclock table e.g. + * pll0_960_pll1_245_pll2_1200" is same for 7627 and + * 7625a (as pll0,pll1,pll2) having same rates, but frequency + * table is different for both targets. + * + * Hence below for loop will not be able to select correct + * table based on PLL rates as rates are same. Hence we need + * to add this cpu check for selecting the correct acpuclock table. + */ + if (cpu_is_msm7x25a()) { + if (pll_mhz[ACPU_PLL_1] == 245) { + acpu_freq_tbl = + pll0_960_pll1_245_pll2_1200_25a; + } else if (pll_mhz[ACPU_PLL_1] == 737) { + acpu_freq_tbl = + pll0_960_pll1_737_pll2_1200_25a; + } + } else { + /* Select the right table to use. */ + for (t = acpu_freq_tbl_list; t->tbl != 0; t++) { + if (t->pll0_rate == pll_mhz[ACPU_PLL_0] + && t->pll1_rate == pll_mhz[ACPU_PLL_1] + && t->pll2_rate == pll_mhz[ACPU_PLL_2] + && t->pll4_rate == pll_mhz[ACPU_PLL_4]) { + acpu_freq_tbl = t->tbl; + break; + } + } + } + + if (acpu_freq_tbl == NULL) { + pr_crit("Unknown PLL configuration!\n"); + BUG(); + } +} + +/* + * 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; +} + +static void __init lpj_init(void) +{ + int i = 0, cpu; + const struct clkctl_acpu_speed *base_clk = drv_state.current_speed; + unsigned long loops; + + for_each_possible_cpu(cpu) { +#ifdef CONFIG_SMP + loops = per_cpu(cpu_data, cpu).loops_per_jiffy; +#else + loops = loops_per_jiffy; +#endif + for (i = 0; acpu_freq_tbl[i].a11clk_khz; i++) { + acpu_freq_tbl[i].lpj = cpufreq_scale( + loops, + 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 struct acpuclk_data acpuclk_7627_data = { + .set_rate = acpuclk_7627_set_rate, + .get_rate = acpuclk_7627_get_rate, + .power_collapse_khz = POWER_COLLAPSE_KHZ, + .switch_time_us = 50, +}; + +static int __init acpuclk_7627_init(struct acpuclk_soc_data *soc_data) +{ + pr_info("%s()\n", __func__); + + drv_state.ebi1_clk = clk_get(NULL, "ebi1_acpu_clk"); + BUG_ON(IS_ERR(drv_state.ebi1_clk)); + + mutex_init(&drv_state.lock); + drv_state.max_speed_delta_khz = soc_data->max_speed_delta_khz; + select_freq_plan(); + acpuclk_7627_data.wait_for_irq_khz = find_wait_for_irq_khz(); + precompute_stepping(); + acpuclk_hw_init(); + lpj_init(); + print_acpu_freq_tbl(); + acpuclk_register(&acpuclk_7627_data); + +#ifdef CONFIG_CPU_FREQ_MSM + cpufreq_table_init(); +#endif + return 0; +} + +struct acpuclk_soc_data acpuclk_7x27_soc_data __initdata = { + .max_speed_delta_khz = 400000, + .init = acpuclk_7627_init, +}; + +struct acpuclk_soc_data acpuclk_7x27a_soc_data __initdata = { + .max_speed_delta_khz = 400000, + .init = acpuclk_7627_init, +}; + +struct acpuclk_soc_data acpuclk_7x27aa_soc_data __initdata = { + .max_speed_delta_khz = 504000, + .init = acpuclk_7627_init, +}; + +struct acpuclk_soc_data acpuclk_8625_soc_data __initdata = { + /* TODO: Need to update speed delta from H/w Team */ + .max_speed_delta_khz = 604800, + .init = acpuclk_7627_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-7x30.c b/arch/arm/mach-msm/acpuclock-7x30.c new file mode 100644 index 0000000000000000000000000000000000000000..29b00655c68b61cdafb2865a01132c3e3883fd32 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-7x30.c @@ -0,0 +1,499 @@ +/* + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2012, 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_ACC0_BASE + 0x04) +#define SCSS_CLK_SEL_ADDR (MSM_ACC0_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; + 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 } +}; + +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(62); + 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(); +} + +static int acpuclk_7x30_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_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_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; +} + +static unsigned long acpuclk_7x30_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; +} + +/*---------------------------------------------------------------------------- + * Clock driver initialization + *---------------------------------------------------------------------------*/ + +static void __init acpuclk_hw_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_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])); + /* + * Prepare all the PLLs because we enable/disable them + * from atomic context and can't always ensure they're + * all prepared in non-atomic context. + */ + BUG_ON(clk_prepare(acpuclk_sources[PLL_1])); + BUG_ON(clk_prepare(acpuclk_sources[PLL_2])); + BUG_ON(clk_prepare(acpuclk_sources[PLL_3])); +} + +static struct acpuclk_data acpuclk_7x30_data = { + .set_rate = acpuclk_7x30_set_rate, + .get_rate = acpuclk_7x30_get_rate, + .power_collapse_khz = MAX_AXI_KHZ, + .wait_for_irq_khz = MAX_AXI_KHZ, + .switch_time_us = 50, +}; + +static int __init acpuclk_7x30_init(struct acpuclk_soc_data *soc_data) +{ + pr_info("%s()\n", __func__); + + mutex_init(&drv_state.lock); + pll2_fixup(); + populate_plls(); + acpuclk_hw_init(); + lpj_init(); + setup_cpufreq_table(); + acpuclk_register(&acpuclk_7x30_data); + + return 0; +} + +struct acpuclk_soc_data acpuclk_7x30_soc_data __initdata = { + .init = acpuclk_7x30_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-8960.c b/arch/arm/mach-msm/acpuclock-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..f467aba37c84c24d41da9b515b95413b242e818e --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-8960.c @@ -0,0 +1,1533 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#include + +#include "acpuclock.h" +#include "pm.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 +#define SEC_SRC_SEL_AUX 2 + +/* 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_LOW_VDD_PLL_L_MAX 0x28 + +#define SECCLKAGD BIT(4) + +/* PTE EFUSE register. */ +#define QFPROM_PTE_EFUSE_ADDR (MSM_QFPROM_BASE + 0x00C0) + +/* Corner type vreg VDD values */ +#define LVL_NONE RPM_VREG_CORNER_NONE +#define LVL_LOW RPM_VREG_CORNER_LOW +#define LVL_NOM RPM_VREG_CORNER_NOMINAL +#define LVL_HIGH RPM_VREG_CORNER_HIGH + +enum scalables { + CPU0 = 0, + CPU1, + CPU2, + CPU3, + L2, + NUM_SCALABLES +}; + +enum vregs { + VREG_CORE, + VREG_MEM, + VREG_DIG, + VREG_HFPLL_A, + VREG_HFPLL_B, + NUM_VREG +}; + +enum hfpll_vdd_levels { + HFPLL_VDD_NONE, + HFPLL_VDD_LOW, + HFPLL_VDD_NOM +}; + +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]; + unsigned int *hfpll_vdd_tbl; +}; + +static unsigned int hfpll_vdd_tbl_8960[] = { + [HFPLL_VDD_NONE] = 0, + [HFPLL_VDD_LOW] = 850000, + [HFPLL_VDD_NOM] = 1050000 +}; + +static unsigned int hfpll_vdd_tbl_8064[] = { + [HFPLL_VDD_NONE] = 0, + [HFPLL_VDD_LOW] = 945000, + [HFPLL_VDD_NOM] = 1050000 +}; + +static unsigned int hfpll_vdd_dig_tbl_8930[] = { + [HFPLL_VDD_NONE] = LVL_NONE, + [HFPLL_VDD_LOW] = LVL_LOW, + [HFPLL_VDD_NOM] = LVL_NOM +}; + +static struct scalable scalable_8960[] = { + [CPU0] = { + .hfpll_base = MSM_HFPLL_BASE + 0x200, + .aux_clk_sel = MSM_ACC0_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait0", 1300000 }, + .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 }, + .vreg[VREG_HFPLL_A] = { "hfpll0_s8", 2100000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8921_S8 }, + .vreg[VREG_HFPLL_B] = { "hfpll0_l23", 1800000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8921_L23 }, + }, + [CPU1] = { + .hfpll_base = MSM_HFPLL_BASE + 0x300, + .aux_clk_sel = MSM_ACC1_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait1", 1300000 }, + .vreg[VREG_MEM] = { "krait1_mem", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_L24 }, + .vreg[VREG_DIG] = { "krait1_dig", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_S3 }, + .vreg[VREG_HFPLL_A] = { "hfpll1_s8", 2100000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_S8 }, + .vreg[VREG_HFPLL_B] = { "hfpll1_l23", 1800000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_L23 }, + }, + [L2] = { + .hfpll_base = MSM_HFPLL_BASE + 0x400, + .hfpll_vdd_tbl = hfpll_vdd_tbl_8960, + .aux_clk_sel = MSM_APCS_GCC_BASE + 0x028, + .l2cpmr_iaddr = L2CPMR_IADDR, + .vreg[VREG_HFPLL_A] = { "hfpll_l2_s8", 2100000, + RPM_VREG_VOTER6, + RPM_VREG_ID_PM8921_S8 }, + .vreg[VREG_HFPLL_B] = { "hfpll_l2_l23", 1800000, + RPM_VREG_VOTER6, + RPM_VREG_ID_PM8921_L23 }, + }, +}; + +static DEFINE_MUTEX(driver_lock); +static DEFINE_SPINLOCK(l2_lock); + +static struct scalable scalable_8064[] = { + [CPU0] = { + .hfpll_base = MSM_HFPLL_BASE + 0x200, + .aux_clk_sel = MSM_ACC0_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait0", 1300000 }, + .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 }, + .vreg[VREG_HFPLL_B] = { "hfpll0", 1800000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8921_LVS7 }, + }, + [CPU1] = { + .hfpll_base = MSM_HFPLL_BASE + 0x240, + .aux_clk_sel = MSM_ACC1_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait1", 1300000 }, + .vreg[VREG_MEM] = { "krait1_mem", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_L24 }, + .vreg[VREG_DIG] = { "krait1_dig", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_S3 }, + .vreg[VREG_HFPLL_B] = { "hfpll1", 1800000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_LVS7 }, + }, + [CPU2] = { + .hfpll_base = MSM_HFPLL_BASE + 0x280, + .aux_clk_sel = MSM_ACC2_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait2", 1300000 }, + .vreg[VREG_MEM] = { "krait2_mem", 1150000, + RPM_VREG_VOTER4, + RPM_VREG_ID_PM8921_L24 }, + .vreg[VREG_DIG] = { "krait2_dig", 1150000, + RPM_VREG_VOTER4, + RPM_VREG_ID_PM8921_S3 }, + .vreg[VREG_HFPLL_B] = { "hfpll2", 1800000, + RPM_VREG_VOTER4, + RPM_VREG_ID_PM8921_LVS7 }, + }, + [CPU3] = { + .hfpll_base = MSM_HFPLL_BASE + 0x2C0, + .aux_clk_sel = MSM_ACC3_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait3", 1300000 }, + .vreg[VREG_MEM] = { "krait3_mem", 1150000, + RPM_VREG_VOTER5, + RPM_VREG_ID_PM8921_L24 }, + .vreg[VREG_DIG] = { "krait3_dig", 1150000, + RPM_VREG_VOTER5, + RPM_VREG_ID_PM8921_S3 }, + .vreg[VREG_HFPLL_B] = { "hfpll3", 1800000, + RPM_VREG_VOTER5, + RPM_VREG_ID_PM8921_LVS7 }, + }, + [L2] = { + .hfpll_base = MSM_HFPLL_BASE + 0x300, + .hfpll_vdd_tbl = hfpll_vdd_tbl_8064, + .aux_clk_sel = MSM_APCS_GCC_BASE + 0x028, + .l2cpmr_iaddr = L2CPMR_IADDR, + .vreg[VREG_HFPLL_B] = { "hfpll_l2", 1800000, + RPM_VREG_VOTER6, + RPM_VREG_ID_PM8921_LVS7 }, + }, +}; + +static struct scalable scalable_8930[] = { + [CPU0] = { + .hfpll_base = MSM_HFPLL_BASE + 0x200, + .aux_clk_sel = MSM_ACC0_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait0", 1300000 }, + .vreg[VREG_MEM] = { "krait0_mem", 1150000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8038_L24 }, + .vreg[VREG_DIG] = { "krait0_dig", LVL_HIGH, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8038_VDD_DIG_CORNER + }, + .vreg[VREG_HFPLL_B] = { "hfpll0", 1800000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8038_L23 }, + }, + [CPU1] = { + .hfpll_base = MSM_HFPLL_BASE + 0x300, + .aux_clk_sel = MSM_ACC1_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait1", 1300000 }, + .vreg[VREG_MEM] = { "krait1_mem", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8038_L24 }, + .vreg[VREG_DIG] = { "krait1_dig", LVL_HIGH, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8038_VDD_DIG_CORNER + }, + .vreg[VREG_HFPLL_B] = { "hfpll1", 1800000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8038_L23 }, + }, + [L2] = { + .hfpll_base = MSM_HFPLL_BASE + 0x400, + .hfpll_vdd_tbl = hfpll_vdd_dig_tbl_8930, + .aux_clk_sel = MSM_APCS_GCC_BASE + 0x028, + .l2cpmr_iaddr = L2CPMR_IADDR, + .vreg[VREG_HFPLL_B] = { "hfpll_l2", 1800000, + RPM_VREG_VOTER6, + RPM_VREG_ID_PM8038_L23 }, + }, +}; + +/*TODO: Update the rpm vreg id when the rpm driver is ready */ +static struct scalable scalable_8627[] = { + [CPU0] = { + .hfpll_base = MSM_HFPLL_BASE + 0x200, + .aux_clk_sel = MSM_ACC0_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait0", 1300000 }, + .vreg[VREG_MEM] = { "krait0_mem", 1150000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8038_L24 }, + .vreg[VREG_DIG] = { "krait0_dig", LVL_HIGH, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8038_VDD_DIG_CORNER + }, + .vreg[VREG_HFPLL_B] = { "hfpll0", 1800000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8038_L23 }, + }, + [CPU1] = { + .hfpll_base = MSM_HFPLL_BASE + 0x300, + .aux_clk_sel = MSM_ACC1_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait1", 1300000 }, + .vreg[VREG_MEM] = { "krait1_mem", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8038_L24 }, + .vreg[VREG_DIG] = { "krait1_dig", LVL_HIGH, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8038_VDD_DIG_CORNER + }, + .vreg[VREG_HFPLL_B] = { "hfpll1", 1800000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8038_L23 }, + }, + [L2] = { + .hfpll_base = MSM_HFPLL_BASE + 0x400, + .hfpll_vdd_tbl = hfpll_vdd_dig_tbl_8930, + .aux_clk_sel = MSM_APCS_GCC_BASE + 0x028, + .l2cpmr_iaddr = L2CPMR_IADDR, + .vreg[VREG_HFPLL_B] = { "hfpll_l2", 1800000, + RPM_VREG_VOTER6, + RPM_VREG_ID_PM8038_L23 }, + }, +}; + +static struct l2_level *l2_freq_tbl; +static struct acpu_level *acpu_freq_tbl; +static int l2_freq_tbl_size; +static struct scalable *scalable; +#define SCALABLE_TO_CPU(sc) ((sc) - scalable) + +/* 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(640), /* At least 80 MHz on bus. */ + [1] = BW_MBPS(1064), /* At least 133 MHz on bus. */ + [2] = BW_MBPS(1600), /* At least 200 MHz on bus. */ + [3] = BW_MBPS(2128), /* At least 266 MHz on bus. */ + [4] = BW_MBPS(3200), /* At least 400 MHz on bus. */ + [5] = BW_MBPS(3600), /* At least 450 MHz on bus. */ + [6] = BW_MBPS(3936), /* At least 492 MHz on bus. */ + [7] = BW_MBPS(4264), /* At least 533 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_8960_kraitv1[(x)]) +static struct l2_level l2_freq_tbl_8960_kraitv1[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0x00 }, 1050000, 1050000, 0 }, + [1] = { { 384000, PLL_8, 0, 2, 0x00 }, 1050000, 1050000, 1 }, + [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, 2 }, + [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 }, +}; + +static struct acpu_level acpu_freq_tbl_8960_kraitv1_slow[] = { + { 0, {STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 900000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 900000 }, + { 1, { 432000, HFPLL, 2, 0, 0x20 }, L2(6), 925000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(6), 925000 }, + { 1, { 540000, HFPLL, 2, 0, 0x28 }, L2(6), 937500 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(6), 962500 }, + { 1, { 648000, HFPLL, 1, 0, 0x18 }, L2(6), 987500 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(6), 1000000 }, + { 1, { 756000, HFPLL, 1, 0, 0x1C }, L2(11), 1025000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(11), 1062500 }, + { 1, { 864000, HFPLL, 1, 0, 0x20 }, L2(11), 1062500 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(11), 1087500 }, + { 0, { 0 } } +}; + +static struct acpu_level acpu_freq_tbl_8960_kraitv1_nom_fast[] = { + { 0, {STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 862500 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 862500 }, + { 1, { 432000, HFPLL, 2, 0, 0x20 }, L2(6), 862500 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(6), 887500 }, + { 1, { 540000, HFPLL, 2, 0, 0x28 }, L2(6), 900000 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(6), 925000 }, + { 1, { 648000, HFPLL, 1, 0, 0x18 }, L2(6), 925000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(6), 937500 }, + { 1, { 756000, HFPLL, 1, 0, 0x1C }, L2(11), 962500 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(11), 1012500 }, + { 1, { 864000, HFPLL, 1, 0, 0x20 }, L2(11), 1025000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(11), 1025000 }, + { 0, { 0 } } +}; + +#undef L2 +#define L2(x) (&l2_freq_tbl_8960_kraitv2[(x)]) +static struct l2_level l2_freq_tbl_8960_kraitv2[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0x00 }, 1050000, 1050000, 0 }, + [1] = { { 384000, PLL_8, 0, 2, 0x00 }, 1050000, 1050000, 1 }, + [2] = { { 432000, HFPLL, 2, 0, 0x20 }, 1050000, 1050000, 2 }, + [3] = { { 486000, HFPLL, 2, 0, 0x24 }, 1050000, 1050000, 2 }, + [4] = { { 540000, HFPLL, 2, 0, 0x28 }, 1050000, 1050000, 2 }, + [5] = { { 594000, HFPLL, 1, 0, 0x16 }, 1050000, 1050000, 2 }, + [6] = { { 648000, HFPLL, 1, 0, 0x18 }, 1050000, 1050000, 4 }, + [7] = { { 702000, HFPLL, 1, 0, 0x1A }, 1050000, 1050000, 4 }, + [8] = { { 756000, HFPLL, 1, 0, 0x1C }, 1150000, 1150000, 4 }, + [9] = { { 810000, HFPLL, 1, 0, 0x1E }, 1150000, 1150000, 4 }, + [10] = { { 864000, HFPLL, 1, 0, 0x20 }, 1150000, 1150000, 4 }, + [11] = { { 918000, HFPLL, 1, 0, 0x22 }, 1150000, 1150000, 6 }, + [12] = { { 972000, HFPLL, 1, 0, 0x24 }, 1150000, 1150000, 6 }, + [13] = { { 1026000, HFPLL, 1, 0, 0x26 }, 1150000, 1150000, 6 }, + [14] = { { 1080000, HFPLL, 1, 0, 0x28 }, 1150000, 1150000, 6 }, + [15] = { { 1134000, HFPLL, 1, 0, 0x2A }, 1150000, 1150000, 6 }, + [16] = { { 1188000, HFPLL, 1, 0, 0x2C }, 1150000, 1150000, 6 }, + [17] = { { 1242000, HFPLL, 1, 0, 0x2E }, 1150000, 1150000, 6 }, + [18] = { { 1296000, HFPLL, 1, 0, 0x30 }, 1150000, 1150000, 6 }, + [19] = { { 1350000, HFPLL, 1, 0, 0x32 }, 1150000, 1150000, 6 }, +}; + +static struct acpu_level acpu_freq_tbl_8960_kraitv2_slow[] = { + { 0, { STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 950000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 950000 }, + { 0, { 432000, HFPLL, 2, 0, 0x20 }, L2(7), 975000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(7), 975000 }, + { 0, { 540000, HFPLL, 2, 0, 0x28 }, L2(7), 1000000 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(7), 1000000 }, + { 0, { 648000, HFPLL, 1, 0, 0x18 }, L2(7), 1025000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(7), 1025000 }, + { 0, { 756000, HFPLL, 1, 0, 0x1C }, L2(7), 1075000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(7), 1075000 }, + { 0, { 864000, HFPLL, 1, 0, 0x20 }, L2(7), 1100000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(7), 1100000 }, + { 0, { 972000, HFPLL, 1, 0, 0x24 }, L2(7), 1125000 }, + { 1, { 1026000, HFPLL, 1, 0, 0x26 }, L2(7), 1125000 }, + { 0, { 1080000, HFPLL, 1, 0, 0x28 }, L2(19), 1175000 }, + { 1, { 1134000, HFPLL, 1, 0, 0x2A }, L2(19), 1175000 }, + { 0, { 1188000, HFPLL, 1, 0, 0x2C }, L2(19), 1200000 }, + { 1, { 1242000, HFPLL, 1, 0, 0x2E }, L2(19), 1200000 }, + { 0, { 1296000, HFPLL, 1, 0, 0x30 }, L2(19), 1225000 }, + { 1, { 1350000, HFPLL, 1, 0, 0x32 }, L2(19), 1225000 }, + { 0, { 1404000, HFPLL, 1, 0, 0x34 }, L2(19), 1237500 }, + { 1, { 1458000, HFPLL, 1, 0, 0x36 }, L2(19), 1237500 }, + { 1, { 1512000, HFPLL, 1, 0, 0x38 }, L2(19), 1250000 }, + { 0, { 0 } } +}; + +static struct acpu_level acpu_freq_tbl_8960_kraitv2_nom[] = { + { 0, { STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 900000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 900000 }, + { 0, { 432000, HFPLL, 2, 0, 0x20 }, L2(7), 925000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(7), 925000 }, + { 0, { 540000, HFPLL, 2, 0, 0x28 }, L2(7), 950000 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(7), 950000 }, + { 0, { 648000, HFPLL, 1, 0, 0x18 }, L2(7), 975000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(7), 975000 }, + { 0, { 756000, HFPLL, 1, 0, 0x1C }, L2(7), 1025000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(7), 1025000 }, + { 0, { 864000, HFPLL, 1, 0, 0x20 }, L2(7), 1050000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(7), 1050000 }, + { 0, { 972000, HFPLL, 1, 0, 0x24 }, L2(7), 1075000 }, + { 1, { 1026000, HFPLL, 1, 0, 0x26 }, L2(7), 1075000 }, + { 0, { 1080000, HFPLL, 1, 0, 0x28 }, L2(19), 1125000 }, + { 1, { 1134000, HFPLL, 1, 0, 0x2A }, L2(19), 1125000 }, + { 0, { 1188000, HFPLL, 1, 0, 0x2C }, L2(19), 1150000 }, + { 1, { 1242000, HFPLL, 1, 0, 0x2E }, L2(19), 1150000 }, + { 0, { 1296000, HFPLL, 1, 0, 0x30 }, L2(19), 1175000 }, + { 1, { 1350000, HFPLL, 1, 0, 0x32 }, L2(19), 1175000 }, + { 0, { 1404000, HFPLL, 1, 0, 0x34 }, L2(19), 1187500 }, + { 1, { 1458000, HFPLL, 1, 0, 0x36 }, L2(19), 1187500 }, + { 1, { 1512000, HFPLL, 1, 0, 0x38 }, L2(19), 1200000 }, + { 0, { 0 } } +}; + +static struct acpu_level acpu_freq_tbl_8960_kraitv2_fast[] = { + { 0, { STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 850000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 850000 }, + { 0, { 432000, HFPLL, 2, 0, 0x20 }, L2(7), 875000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(7), 875000 }, + { 0, { 540000, HFPLL, 2, 0, 0x28 }, L2(7), 900000 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(7), 900000 }, + { 0, { 648000, HFPLL, 1, 0, 0x18 }, L2(7), 925000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(7), 925000 }, + { 0, { 756000, HFPLL, 1, 0, 0x1C }, L2(7), 975000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(7), 975000 }, + { 0, { 864000, HFPLL, 1, 0, 0x20 }, L2(7), 1000000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(7), 1000000 }, + { 0, { 972000, HFPLL, 1, 0, 0x24 }, L2(7), 1025000 }, + { 1, { 1026000, HFPLL, 1, 0, 0x26 }, L2(7), 1025000 }, + { 0, { 1080000, HFPLL, 1, 0, 0x28 }, L2(19), 1075000 }, + { 1, { 1134000, HFPLL, 1, 0, 0x2A }, L2(19), 1075000 }, + { 0, { 1188000, HFPLL, 1, 0, 0x2C }, L2(19), 1100000 }, + { 1, { 1242000, HFPLL, 1, 0, 0x2E }, L2(19), 1100000 }, + { 0, { 1296000, HFPLL, 1, 0, 0x30 }, L2(19), 1125000 }, + { 1, { 1350000, HFPLL, 1, 0, 0x32 }, L2(19), 1125000 }, + { 0, { 1404000, HFPLL, 1, 0, 0x34 }, L2(19), 1137500 }, + { 1, { 1458000, HFPLL, 1, 0, 0x36 }, L2(19), 1137500 }, + { 1, { 1512000, HFPLL, 1, 0, 0x38 }, L2(19), 1150000 }, + { 0, { 0 } } +}; + +/* TODO: Update vdd_dig and vdd_mem when voltage data is available. */ +#undef L2 +#define L2(x) (&l2_freq_tbl_8064[(x)]) +static struct l2_level l2_freq_tbl_8064[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0x00 }, 1050000, 1050000, 0 }, + [1] = { { 384000, PLL_8, 0, 2, 0x00 }, 1050000, 1050000, 1 }, + [2] = { { 432000, HFPLL, 2, 0, 0x20 }, 1050000, 1050000, 2 }, + [3] = { { 486000, HFPLL, 2, 0, 0x24 }, 1050000, 1050000, 2 }, + [4] = { { 540000, HFPLL, 2, 0, 0x28 }, 1050000, 1050000, 2 }, + [5] = { { 594000, HFPLL, 1, 0, 0x16 }, 1050000, 1050000, 2 }, + [6] = { { 648000, HFPLL, 1, 0, 0x18 }, 1050000, 1050000, 4 }, + [7] = { { 702000, HFPLL, 1, 0, 0x1A }, 1050000, 1050000, 4 }, + [8] = { { 756000, HFPLL, 1, 0, 0x1C }, 1150000, 1150000, 4 }, + [9] = { { 810000, HFPLL, 1, 0, 0x1E }, 1150000, 1150000, 4 }, + [10] = { { 864000, HFPLL, 1, 0, 0x20 }, 1150000, 1150000, 4 }, + [11] = { { 918000, HFPLL, 1, 0, 0x22 }, 1150000, 1150000, 7 }, + [12] = { { 972000, HFPLL, 1, 0, 0x24 }, 1150000, 1150000, 7 }, + [13] = { { 1026000, HFPLL, 1, 0, 0x26 }, 1150000, 1150000, 7 }, + [14] = { { 1080000, HFPLL, 1, 0, 0x28 }, 1150000, 1150000, 7 }, + [15] = { { 1134000, HFPLL, 1, 0, 0x2A }, 1150000, 1150000, 7 }, +}; + +/* TODO: Update core voltages when data is available. */ +static struct acpu_level acpu_freq_tbl_8064[] = { + { 0, { STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 950000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 950000 }, + { 0, { 432000, HFPLL, 2, 0, 0x20 }, L2(7), 975000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(7), 975000 }, + { 0, { 540000, HFPLL, 2, 0, 0x28 }, L2(7), 1000000 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(7), 1000000 }, + { 0, { 648000, HFPLL, 1, 0, 0x18 }, L2(7), 1025000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(7), 1025000 }, + { 0, { 756000, HFPLL, 1, 0, 0x1C }, L2(7), 1075000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(7), 1075000 }, + { 0, { 864000, HFPLL, 1, 0, 0x20 }, L2(7), 1100000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(7), 1100000 }, + { 0, { 972000, HFPLL, 1, 0, 0x24 }, L2(7), 1125000 }, + { 1, { 1026000, HFPLL, 1, 0, 0x26 }, L2(7), 1125000 }, + { 0, { 1080000, HFPLL, 1, 0, 0x28 }, L2(15), 1175000 }, + { 1, { 1134000, HFPLL, 1, 0, 0x2A }, L2(15), 1175000 }, + { 0, { 1188000, HFPLL, 1, 0, 0x2C }, L2(15), 1200000 }, + { 1, { 1242000, HFPLL, 1, 0, 0x2E }, L2(15), 1200000 }, + { 0, { 1296000, HFPLL, 1, 0, 0x30 }, L2(15), 1225000 }, + { 1, { 1350000, HFPLL, 1, 0, 0x32 }, L2(15), 1225000 }, + { 0, { 1404000, HFPLL, 1, 0, 0x34 }, L2(15), 1237500 }, + { 1, { 1458000, HFPLL, 1, 0, 0x36 }, L2(15), 1237500 }, + { 1, { 1512000, HFPLL, 1, 0, 0x38 }, L2(15), 1250000 }, + { 0, { 0 } } +}; + +/* TODO: Update vdd_dig, vdd_mem and bw when data is available. */ +#undef L2 +#define L2(x) (&l2_freq_tbl_8930[(x)]) +static struct l2_level l2_freq_tbl_8930[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0x00 }, LVL_NOM, 1050000, 0 }, + [1] = { { 384000, PLL_8, 0, 2, 0x00 }, LVL_NOM, 1050000, 1 }, + [2] = { { 432000, HFPLL, 2, 0, 0x20 }, LVL_NOM, 1050000, 2 }, + [3] = { { 486000, HFPLL, 2, 0, 0x24 }, LVL_NOM, 1050000, 2 }, + [4] = { { 540000, HFPLL, 2, 0, 0x28 }, LVL_NOM, 1050000, 2 }, + [5] = { { 594000, HFPLL, 1, 0, 0x16 }, LVL_NOM, 1050000, 2 }, + [6] = { { 648000, HFPLL, 1, 0, 0x18 }, LVL_NOM, 1050000, 4 }, + [7] = { { 702000, HFPLL, 1, 0, 0x1A }, LVL_NOM, 1050000, 4 }, + [8] = { { 756000, HFPLL, 1, 0, 0x1C }, LVL_HIGH, 1150000, 4 }, + [9] = { { 810000, HFPLL, 1, 0, 0x1E }, LVL_HIGH, 1150000, 4 }, + [10] = { { 864000, HFPLL, 1, 0, 0x20 }, LVL_HIGH, 1150000, 4 }, + [11] = { { 918000, HFPLL, 1, 0, 0x22 }, LVL_HIGH, 1150000, 7 }, + [12] = { { 972000, HFPLL, 1, 0, 0x24 }, LVL_HIGH, 1150000, 7 }, + [13] = { { 1026000, HFPLL, 1, 0, 0x26 }, LVL_HIGH, 1150000, 7 }, + [14] = { { 1080000, HFPLL, 1, 0, 0x28 }, LVL_HIGH, 1150000, 7 }, + [15] = { { 1134000, HFPLL, 1, 0, 0x2A }, LVL_HIGH, 1150000, 7 }, + [16] = { { 1188000, HFPLL, 1, 0, 0x2C }, LVL_HIGH, 1150000, 7 }, +}; + +/* TODO: Update core voltages when data is available. */ +static struct acpu_level acpu_freq_tbl_8930[] = { + { 0, { STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 925000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 925000 }, + { 1, { 432000, HFPLL, 2, 0, 0x20 }, L2(6), 937500 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(6), 962500 }, + { 1, { 540000, HFPLL, 2, 0, 0x28 }, L2(6), 987500 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(6), 1000000 }, + { 1, { 648000, HFPLL, 1, 0, 0x18 }, L2(6), 1025000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(6), 1037500 }, + { 1, { 756000, HFPLL, 1, 0, 0x1C }, L2(11), 1062500 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(11), 1087500 }, + { 1, { 864000, HFPLL, 1, 0, 0x20 }, L2(11), 1100000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(11), 1125000 }, + { 1, { 972000, HFPLL, 1, 0, 0x24 }, L2(16), 1137500 }, + { 1, { 1026000, HFPLL, 1, 0, 0x26 }, L2(16), 1162500 }, + { 1, { 1080000, HFPLL, 1, 0, 0x28 }, L2(16), 1187500 }, + { 1, { 1134000, HFPLL, 1, 0, 0x2A }, L2(16), 1200000 }, + { 1, { 1188000, HFPLL, 1, 0, 0x2C }, L2(16), 1225000 }, + { 0, { 0 } } +}; + +/* TODO: Update vdd_dig, vdd_mem and bw when data is available. */ +#undef L2 +#define L2(x) (&l2_freq_tbl_8627[(x)]) +static struct l2_level l2_freq_tbl_8627[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0x00 }, LVL_NOM, 1050000, 0 }, + [1] = { { 384000, PLL_8, 0, 2, 0x00 }, LVL_NOM, 1050000, 1 }, + [2] = { { 432000, HFPLL, 2, 0, 0x20 }, LVL_NOM, 1050000, 1 }, + [3] = { { 486000, HFPLL, 2, 0, 0x24 }, LVL_NOM, 1050000, 1 }, + [4] = { { 540000, HFPLL, 2, 0, 0x28 }, LVL_NOM, 1050000, 2 }, + [5] = { { 594000, HFPLL, 1, 0, 0x16 }, LVL_NOM, 1050000, 2 }, + [6] = { { 648000, HFPLL, 1, 0, 0x18 }, LVL_NOM, 1050000, 2 }, + [7] = { { 702000, HFPLL, 1, 0, 0x1A }, LVL_NOM, 1050000, 3 }, + [8] = { { 756000, HFPLL, 1, 0, 0x1C }, LVL_HIGH, 1150000, 3 }, + [9] = { { 810000, HFPLL, 1, 0, 0x1E }, LVL_HIGH, 1150000, 3 }, + [10] = { { 864000, HFPLL, 1, 0, 0x20 }, LVL_HIGH, 1150000, 4 }, + [11] = { { 918000, HFPLL, 1, 0, 0x22 }, LVL_HIGH, 1150000, 4 }, + [12] = { { 972000, HFPLL, 1, 0, 0x24 }, LVL_HIGH, 1150000, 4 }, +}; + +/* TODO: Update core voltages when data is available. */ +static struct acpu_level acpu_freq_tbl_8627[] = { + { 0, { STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 900000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 900000 }, + { 1, { 432000, HFPLL, 2, 0, 0x20 }, L2(5), 925000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(5), 925000 }, + { 1, { 540000, HFPLL, 2, 0, 0x28 }, L2(5), 937500 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(5), 962500 }, + { 1, { 648000, HFPLL, 1, 0, 0x18 }, L2(9), 987500 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(9), 1000000 }, + { 1, { 756000, HFPLL, 1, 0, 0x1C }, L2(9), 1025000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(9), 1062500 }, + { 1, { 864000, HFPLL, 1, 0, 0x20 }, L2(12), 1062500 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(12), 1087500 }, + { 1, { 972000, HFPLL, 1, 0, 0x24 }, L2(12), 1100000 }, + { 0, { 0 } } +}; + +static unsigned long acpuclk_8960_get_rate(int cpu) +{ + return scalable[cpu].current_speed->khz; +} + +/* Get the selected source on primary MUX. */ +static int get_pri_clk_src(struct scalable *sc) +{ + uint32_t regval; + + regval = get_l2_indirect_reg(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 = get_l2_indirect_reg(sc->l2cpmr_iaddr); + regval &= ~0x3; + regval |= (pri_src_sel & 0x3); + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + /* 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 = get_l2_indirect_reg(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 = get_l2_indirect_reg(sc->l2cpmr_iaddr); + regval |= SECCLKAGD; + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + + /* Program the MUX. */ + regval &= ~(0x3 << 2); + regval |= ((sec_src_sel & 0x3) << 2); + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + + /* Wait for switch to complete. */ + mb(); + udelay(1); + + /* Re-enable secondary source clock gating. */ + regval &= ~SECCLKAGD; + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); +} + +/* Enable an already-configured HFPLL. */ +static void hfpll_enable(struct scalable *sc, bool skip_regulators) +{ + int rc; + + if (!skip_regulators) { + if (cpu_is_msm8960()) { + rc = rpm_vreg_set_voltage( + sc->vreg[VREG_HFPLL_A].rpm_vreg_id, + sc->vreg[VREG_HFPLL_A].rpm_vreg_voter, + 2050000, + sc->vreg[VREG_HFPLL_A].max_vdd, 0); + if (rc) + pr_err("%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_A].name, rc); + } + rc = rpm_vreg_set_voltage(sc->vreg[VREG_HFPLL_B].rpm_vreg_id, + sc->vreg[VREG_HFPLL_B].rpm_vreg_voter, 1800000, + sc->vreg[VREG_HFPLL_B].max_vdd, 0); + if (rc) + pr_err("%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_B].name, rc); + } + /* 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, bool skip_regulators) +{ + int rc; + + /* + * Disable the PLL output, disable test mode, enable + * the bypass mode, and assert the reset. + */ + writel_relaxed(0, sc->hfpll_base + HFPLL_MODE); + + if (!skip_regulators) { + rc = rpm_vreg_set_voltage(sc->vreg[VREG_HFPLL_B].rpm_vreg_id, + sc->vreg[VREG_HFPLL_B].rpm_vreg_voter, 0, + 0, 0); + if (rc) + pr_err("%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_B].name, rc); + + if (cpu_is_msm8960()) { + rc = rpm_vreg_set_voltage( + sc->vreg[VREG_HFPLL_A].rpm_vreg_id, + sc->vreg[VREG_HFPLL_A].rpm_vreg_voter, + 0, 0, 0); + if (rc) + pr_err("%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_A].name, rc); + } + } +} + +/* 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 + l2_freq_tbl_size)); + + /* 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 to an always-on source running at a frequency that does + * not require an elevated CPU voltage. PLL8 is used here. + */ + set_sec_clk_src(sc, SEC_SRC_SEL_AUX); + set_pri_clk_src(sc, PRI_SRC_SEL_SEC_SRC); + + /* Program CPU HFPLL. */ + hfpll_disable(sc, 1); + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc, 1); + + /* 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) { + /* + * If responding to CPU_DEAD we must be running on another CPU. + * Therefore, we can't access the downed CPU's clock MUX CP15 + * registers from here and can't change clock sources. If the + * CPU is collapsed, however, it is still safe to turn off the + * PLL without switching the MUX away from it. + */ + 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, 0); + } else if (reason == SETRATE_HOTPLUG + && msm_pm_verify_cpu_pc(SCALABLE_TO_CPU(sc))) { + hfpll_disable(sc, 0); + } + } else if (strt_s->src != HFPLL && tgt_s->src == HFPLL) { + /* + * 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]) { + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc, 0); + set_pri_clk_src(sc, tgt_s->pri_src_sel); + } else if (reason == SETRATE_HOTPLUG + && msm_pm_verify_cpu_pc(SCALABLE_TO_CPU(sc))) { + /* PLL was disabled during hot-unplug. Re-enable it. */ + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc, 0); + } + } else { + if (reason != SETRATE_HOTPLUG || sc == &scalable[L2]) + set_sec_clk_src(sc, tgt_s->sec_src_sel); + } + + 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 = 0; + + /* + * Increase vdd_mem active-set before vdd_dig. + * vdd_mem should be >= 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 increase failed (%d)\n", + sc->vreg[VREG_MEM].name, 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 increase failed (%d)\n", + sc->vreg[VREG_DIG].name, 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 increase failed (%d)\n", + sc->vreg[VREG_CORE].name, 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 decrease failed (%d)\n", + sc->vreg[VREG_CORE].name, 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 decrease failed (%d)\n", + sc->vreg[VREG_DIG].name, ret); + return; + } + sc->vreg[VREG_DIG].cur_vdd = vdd_dig; + } + + /* + * Decrease vdd_mem active-set after vdd_dig. + * vdd_mem should be >= 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 decrease failed (%d)\n", + sc->vreg[VREG_MEM].name, ret); + return; + } + sc->vreg[VREG_MEM].cur_vdd = vdd_mem; + } +} + +static unsigned int calculate_vdd_mem(struct acpu_level *tgt) +{ + return 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.src != HFPLL) + pll_vdd_dig = scalable[L2].hfpll_vdd_tbl[HFPLL_VDD_NONE]; + else if (tgt->l2_level->speed.pll_l_val > HFPLL_LOW_VDD_PLL_L_MAX) + pll_vdd_dig = scalable[L2].hfpll_vdd_tbl[HFPLL_VDD_NOM]; + else + pll_vdd_dig = scalable[L2].hfpll_vdd_tbl[HFPLL_VDD_LOW]; + + return max(tgt->l2_level->vdd_dig, pll_vdd_dig); +} + +static unsigned int calculate_vdd_core(struct acpu_level *tgt) +{ + return tgt->vdd_core; +} + +/* Set the CPU's clock rate and adjust the L2 rate, if appropriate. */ +static int acpuclk_8960_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) + 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_8960_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); + + 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, 1); + + /* 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, 0); +} + +/* Voltage regulator initialization. */ +static void __init regulator_init(struct acpu_level *lvl) +{ + int cpu, ret; + struct scalable *sc; + unsigned int vdd_mem, vdd_dig, vdd_core; + + vdd_mem = calculate_vdd_mem(lvl); + vdd_dig = calculate_vdd_dig(lvl); + + for_each_possible_cpu(cpu) { + sc = &scalable[cpu]; + + /* Set initial vdd_mem vote. */ + 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 initialization failed (%d)\n", + sc->vreg[VREG_MEM].name, ret); + BUG(); + } + sc->vreg[VREG_MEM].cur_vdd = vdd_mem; + + /* Set initial vdd_dig vote. */ + 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 initialization failed (%d)\n", + sc->vreg[VREG_DIG].name, ret); + BUG(); + } + sc->vreg[VREG_DIG].cur_vdd = vdd_dig; + + /* Setup Krait CPU regulators and initial core voltage. */ + 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(); + } + vdd_core = calculate_vdd_core(lvl); + ret = regulator_set_voltage(sc->vreg[VREG_CORE].reg, vdd_core, + sc->vreg[VREG_CORE].max_vdd); + if (ret) { + pr_err("%s initialization failed (%d)\n", + sc->vreg[VREG_CORE].name, ret); + BUG(); + } + sc->vreg[VREG_CORE].cur_vdd = vdd_core; + ret = regulator_enable(sc->vreg[VREG_CORE].reg); + if (ret) { + pr_err("regulator_enable(%s) failed (%d)\n", + sc->vreg[VREG_CORE].name, ret); + BUG(); + } + } +} + +/* Set initial rate for a given core. */ +static void __init init_clock_sources(struct scalable *sc, + struct core_speed *tgt_s) +{ + uint32_t regval; + + /* Select PLL8 as AUX source input to the secondary MUX. */ + writel_relaxed(0x3, sc->aux_clk_sel); + + /* Switch away from the HFPLL while it's re-initialized. */ + set_sec_clk_src(sc, SEC_SRC_SEL_AUX); + 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 = get_l2_indirect_reg(sc->l2cpmr_iaddr); + regval &= ~(0x3 << 6); + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + + /* Switch to the target clock source. */ + set_sec_clk_src(sc, tgt_s->sec_src_sel); + set_pri_clk_src(sc, tgt_s->pri_src_sel); + sc->current_speed = tgt_s; +} + +static void __init per_cpu_init(void *data) +{ + struct acpu_level *max_acpu_level = data; + int cpu = smp_processor_id(); + + init_clock_sources(&scalable[cpu], &max_acpu_level->speed); + scalable[cpu].l2_vote = max_acpu_level->l2_level; +} + +/* Register with bus driver. */ +static void __init bus_init(unsigned int init_bw) +{ + 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, init_bw); + 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; + + switch (action) { + case CPU_DYING: + case CPU_DYING_FROZEN: + /* + * On Krait v1 and 8064v1, the primary and secondary muxes must + * be set to QSB before L2 power collapse and restored after. + */ + if (cpu_is_krait_v1() || cpu_is_apq8064()) { + 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_8960_get_rate(cpu); + /* Fall through. */ + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + acpuclk_8960_set_rate(cpu, HOT_UNPLUG_KHZ, SETRATE_HOTPLUG); + break; + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + if (WARN_ON(!prev_khz[cpu])) + return NOTIFY_BAD; + acpuclk_8960_set_rate(cpu, prev_khz[cpu], SETRATE_HOTPLUG); + break; + case CPU_STARTING: + case CPU_STARTING_FROZEN: + if (cpu_is_krait_v1() || cpu_is_apq8064()) { + 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, +}; + +static const int krait_needs_vmin(void) +{ + switch (read_cpuid_id()) { + case 0x511F04D0: + case 0x511F04D1: + case 0x510F06F0: + return 1; + default: + return 0; + }; +} + +static void kraitv2_apply_vmin(struct acpu_level *tbl) +{ + for (; tbl->speed.khz != 0; tbl++) + if (tbl->vdd_core < 1150000) + tbl->vdd_core = 1150000; +} + +static struct acpu_level * __init select_freq_plan(void) +{ + struct acpu_level *l, *max_acpu_level = NULL; + + /* Select frequency tables. */ + if (cpu_is_msm8960()) { + uint32_t pte_efuse, pvs; + struct acpu_level *v1, *v2; + + pte_efuse = readl_relaxed(QFPROM_PTE_EFUSE_ADDR); + pvs = (pte_efuse >> 10) & 0x7; + if (pvs == 0x7) + pvs = (pte_efuse >> 13) & 0x7; + + switch (pvs) { + case 0x0: + case 0x7: + pr_info("ACPU PVS: Slow\n"); + v1 = acpu_freq_tbl_8960_kraitv1_slow; + v2 = acpu_freq_tbl_8960_kraitv2_slow; + break; + case 0x1: + pr_info("ACPU PVS: Nominal\n"); + v1 = acpu_freq_tbl_8960_kraitv1_nom_fast; + v2 = acpu_freq_tbl_8960_kraitv2_nom; + break; + case 0x3: + pr_info("ACPU PVS: Fast\n"); + v1 = acpu_freq_tbl_8960_kraitv1_nom_fast; + v2 = acpu_freq_tbl_8960_kraitv2_fast; + break; + default: + pr_warn("ACPU PVS: Unknown. Defaulting to slow.\n"); + v1 = acpu_freq_tbl_8960_kraitv1_slow; + v2 = acpu_freq_tbl_8960_kraitv2_slow; + break; + } + + scalable = scalable_8960; + if (cpu_is_krait_v1()) { + acpu_freq_tbl = v1; + l2_freq_tbl = l2_freq_tbl_8960_kraitv1; + l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl_8960_kraitv1); + } else { + acpu_freq_tbl = v2; + l2_freq_tbl = l2_freq_tbl_8960_kraitv2; + l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl_8960_kraitv2); + } + } else if (cpu_is_apq8064()) { + scalable = scalable_8064; + acpu_freq_tbl = acpu_freq_tbl_8064; + l2_freq_tbl = l2_freq_tbl_8064; + l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl_8064); + } else if (cpu_is_msm8627()) { + scalable = scalable_8627; + acpu_freq_tbl = acpu_freq_tbl_8627; + l2_freq_tbl = l2_freq_tbl_8627; + l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl_8627); + } else if (cpu_is_msm8930()) { + scalable = scalable_8930; + acpu_freq_tbl = acpu_freq_tbl_8930; + l2_freq_tbl = l2_freq_tbl_8930; + l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl_8930); + } else { + BUG(); + } + if (krait_needs_vmin()) + kraitv2_apply_vmin(acpu_freq_tbl); + + /* Find the max supported scaling frequency. */ + for (l = acpu_freq_tbl; l->speed.khz != 0; l++) + if (l->use_for_scaling) + max_acpu_level = l; + BUG_ON(!max_acpu_level); + pr_info("Max ACPU freq: %u KHz\n", max_acpu_level->speed.khz); + + return max_acpu_level; +} + +static struct acpuclk_data acpuclk_8960_data = { + .set_rate = acpuclk_8960_set_rate, + .get_rate = acpuclk_8960_get_rate, + .power_collapse_khz = STBY_KHZ, + .wait_for_irq_khz = STBY_KHZ, +}; + +static int __init acpuclk_8960_init(struct acpuclk_soc_data *soc_data) +{ + struct acpu_level *max_acpu_level = select_freq_plan(); + + regulator_init(max_acpu_level); + bus_init(max_acpu_level->l2_level->bw_level); + + init_clock_sources(&scalable[L2], &max_acpu_level->l2_level->speed); + on_each_cpu(per_cpu_init, max_acpu_level, true); + + cpufreq_table_init(); + + acpuclk_register(&acpuclk_8960_data); + register_hotcpu_notifier(&acpuclock_cpu_notifier); + + return 0; +} + +struct acpuclk_soc_data acpuclk_8960_soc_data __initdata = { + .init = acpuclk_8960_init, +}; + +struct acpuclk_soc_data acpuclk_8930_soc_data __initdata = { + .init = acpuclk_8960_init, +}; + +struct acpuclk_soc_data acpuclk_8064_soc_data __initdata = { + .init = acpuclk_8960_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-8x50.c b/arch/arm/mach-msm/acpuclock-8x50.c new file mode 100644 index 0000000000000000000000000000000000000000..cde5a144ad16face71a178c45b0eb5b7b9cd5f8d --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-8x50.c @@ -0,0 +1,741 @@ +/* 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 "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) + +#ifdef CONFIG_QSD_SVS +#define TPS65023_MAX_DCDC1 1600 +#else +#define TPS65023_MAX_DCDC1 CONFIG_QSD_PMIC_DEFAULT_DCDC1 +#endif + +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 128000 +/* 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 245760 + +#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; + 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(62); + + 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; + } +} + +static int acpuclk_8x50_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_hw_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); +} + +static unsigned long acpuclk_8x50_get_rate(int cpu) +{ + return drv_state.current_speed->acpuclk_khz; +} + +/* 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); + + /* 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 > TPS65023_MAX_DCDC1) { + 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 + +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 acpuclk_data acpuclk_8x50_data = { + .set_rate = acpuclk_8x50_set_rate, + .get_rate = acpuclk_8x50_get_rate, + .power_collapse_khz = POWER_COLLAPSE_KHZ, + .wait_for_irq_khz = WAIT_FOR_IRQ_KHZ, + .switch_time_us = 20, +}; + +static int __init acpuclk_8x50_init(struct acpuclk_soc_data *soc_data) +{ + mutex_init(&drv_state.lock); + drv_state.acpu_set_vdd = qsd8x50_tps65023_set_dcdc1; + + drv_state.ebi1_clk = clk_get(NULL, "ebi1_acpu_clk"); + BUG_ON(IS_ERR(drv_state.ebi1_clk)); + + acpu_freq_tbl_fixup(); + acpuclk_hw_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); + + acpuclk_register(&acpuclk_8x50_data); + +#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 + return 0; +} + +struct acpuclk_soc_data acpuclk_8x50_soc_data __initdata = { + .init = acpuclk_8x50_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-8x60.c b/arch/arm/mach-msm/acpuclock-8x60.c new file mode 100644 index 0000000000000000000000000000000000000000..48efa18b46abcd72c1ef057398399c65d570930f --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-8x60.c @@ -0,0 +1,1096 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 has a minimum frequency of 384MHz. + * Calibration should respect this limit. */ +#define L_VAL_SCPLL_CAL_MIN 0x08 /* = 432 MHz with 27MHz source */ + +#define MAX_VDD_SC 1325000 /* uV */ +#define MAX_VDD_MEM 1325000 /* 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_OFFSET(l_val) (0x38 + (((l_val) / 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; +} 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_1512mhz_slow[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 800000, 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), 825000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 825000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 850000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 850000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 875000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 875000, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 900000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 900000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 925000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 975000, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 975000, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 1000000, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 1025000, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 1025000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1050000, 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), 1100000, 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), 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), 1200000, 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_1512mhz_nom[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 800000, 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), 825000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 825000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 850000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 850000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 875000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 875000, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 900000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 900000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 925000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 950000, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 975000, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 975000, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 1000000, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 1000000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1025000, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1025000, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1050000, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1075000, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1100000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1125000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1150000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1150000, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1175000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_1512mhz_fast[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 800000, 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), 825000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 825000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 850000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 850000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 875000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 875000, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 900000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 900000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 925000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 925000, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 950000, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 950000, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 950000, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 975000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1000000, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1000000, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1025000, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1050000, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1075000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1100000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1100000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1100000, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1125000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_1674mhz_slower[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 775000, 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), 775000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 775000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 775000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 775000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 787500, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 800000, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 825000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 837500, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 850000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 875000, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 900000, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 912500, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 937500, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 962500, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 987500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1012500, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1025000, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1062500, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1087500, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1100000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1125000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1150000, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1187500, 0x03006000}, + { {1, 1}, 1566000, ACPU_SCPLL, 0, 0, 1, 0x1D, L2(19), 1225000, 0x03006000}, + { {1, 1}, 1620000, ACPU_SCPLL, 0, 0, 1, 0x1E, L2(19), 1262500, 0x03006000}, + { {1, 1}, 1674000, ACPU_SCPLL, 0, 0, 1, 0x1F, L2(19), 1300000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_1674mhz_slow[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 775000, 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), 775000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 775000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 775000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 775000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 787500, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 800000, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 825000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 837500, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 850000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 862500, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 887500, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 900000, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 925000, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 937500, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 962500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 987500, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1000000, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1025000, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1050000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1062500, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1087500, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1112500, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1150000, 0x03006000}, + { {1, 1}, 1566000, ACPU_SCPLL, 0, 0, 1, 0x1D, L2(19), 1175000, 0x03006000}, + { {1, 1}, 1620000, ACPU_SCPLL, 0, 0, 1, 0x1E, L2(19), 1212500, 0x03006000}, + { {1, 1}, 1674000, ACPU_SCPLL, 0, 0, 1, 0x1F, L2(19), 1250000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_1674mhz_nom[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 775000, 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), 775000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 775000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 775000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 775000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 787500, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 800000, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 812500, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 825000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 837500, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 850000, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 875000, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 887500, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 900000, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 912500, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 937500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 950000, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 975000, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 987500, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1012500, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1025000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1050000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1075000, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1112500, 0x03006000}, + { {1, 1}, 1566000, ACPU_SCPLL, 0, 0, 1, 0x1D, L2(19), 1137500, 0x03006000}, + { {1, 1}, 1620000, ACPU_SCPLL, 0, 0, 1, 0x1E, L2(19), 1175000, 0x03006000}, + { {1, 1}, 1674000, ACPU_SCPLL, 0, 0, 1, 0x1F, L2(19), 1200000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_1674mhz_fast[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 775000, 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), 775000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 775000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 775000, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 775000, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 775000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 787500, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 800000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 812500, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 825000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 837500, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 862500, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 875000, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 887500, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 900000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 925000, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 937500, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 950000, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 962500, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 975000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1000000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1025000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1050000, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1075000, 0x03006000}, + { {1, 1}, 1566000, ACPU_SCPLL, 0, 0, 1, 0x1D, L2(19), 1100000, 0x03006000}, + { {1, 1}, 1620000, ACPU_SCPLL, 0, 0, 1, 0x1E, L2(19), 1125000, 0x03006000}, + { {1, 1}, 1674000, ACPU_SCPLL, 0, 0, 1, 0x1F, L2(19), 1150000, 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); + +static unsigned long acpuclk_8x60_get_rate(int cpu) +{ + return drv_state.current_speed[cpu]->acpuclk_khz; +} + +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; +} + +static int acpuclk_8x60_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 pll, unsigned int max_l_val) +{ + uint32_t regval; + + pr_debug("Initializing SCPLL%d\n", 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[pll] + SCPLL_DEBUG_OFFSET); + writel_relaxed(0x0, sc_pll_base[pll] + SCPLL_LUT_OFFSET(max_l_val)); + writel_relaxed(SCPLL_DEBUG_NONE, sc_pll_base[pll] + SCPLL_DEBUG_OFFSET); + + /* Power-up SCPLL into standby mode. */ + writel_relaxed(SCPLL_STANDBY, sc_pll_base[pll] + SCPLL_CTL_OFFSET); + mb(); + udelay(10); + + /* Calibrate the SCPLL for the frequency range needed. */ + regval = (max_l_val << 24) | (L_VAL_SCPLL_CAL_MIN << 16); + writel_relaxed(regval, sc_pll_base[pll] + SCPLL_CAL_OFFSET); + + /* Start calibration */ + writel_relaxed(SCPLL_FULL_CAL, sc_pll_base[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[pll] + SCPLL_LUT_OFFSET(max_l_val))) + cpu_relax(); + + /* Wait for calibration to complete. */ + while (readl_relaxed(sc_pll_base[pll] + SCPLL_STATUS_OFFSET) & 0x2) + cpu_relax(); + + /* Power-down SCPLL. */ + scpll_disable(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_8x60_get_rate(cpu); + /* Fall through. */ + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + acpuclk_8x60_set_rate(cpu, HOT_UNPLUG_KHZ, SETRATE_HOTPLUG); + break; + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + if (WARN_ON(!prev_khz[cpu])) + return NOTIFY_BAD; + acpuclk_8x60_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 __init struct clkctl_acpu_speed *select_freq_plan(void) +{ + uint32_t pte_efuse, speed_bin, pvs; + 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; + + pvs = (pte_efuse >> 10) & 0x7; + if (pvs == 0x7) + pvs = (pte_efuse >> 13) & 0x7; + + if (speed_bin == 0x2) { + switch (pvs) { + case 0x7: + case 0x4: + acpu_freq_tbl = acpu_freq_tbl_1674mhz_slower; + pr_info("ACPU PVS: Slower\n"); + break; + case 0x0: + acpu_freq_tbl = acpu_freq_tbl_1674mhz_slow; + pr_info("ACPU PVS: Slow\n"); + break; + case 0x1: + acpu_freq_tbl = acpu_freq_tbl_1674mhz_nom; + pr_info("ACPU PVS: Nominal\n"); + break; + case 0x3: + acpu_freq_tbl = acpu_freq_tbl_1674mhz_fast; + pr_info("ACPU PVS: Fast\n"); + break; + default: + acpu_freq_tbl = acpu_freq_tbl_1674mhz_slower; + pr_warn("ACPU PVS: Unknown. Defaulting to slower.\n"); + break; + } + } else if (speed_bin == 0x1) { + switch (pvs) { + case 0x0: + case 0x7: + acpu_freq_tbl = acpu_freq_tbl_1512mhz_slow; + pr_info("ACPU PVS: Slow\n"); + break; + case 0x1: + acpu_freq_tbl = acpu_freq_tbl_1512mhz_nom; + pr_info("ACPU PVS: Nominal\n"); + break; + case 0x3: + acpu_freq_tbl = acpu_freq_tbl_1512mhz_fast; + pr_info("ACPU PVS: Fast\n"); + break; + default: + acpu_freq_tbl = acpu_freq_tbl_1512mhz_slow; + pr_warn("ACPU PVS: Unknown. Defaulting to slow.\n"); + break; + } + } else { + acpu_freq_tbl = acpu_freq_tbl_1188mhz; + } + + for (f = acpu_freq_tbl; f->acpuclk_khz != 0; f++) + ; + f--; + pr_info("Max ACPU freq: %u KHz\n", f->acpuclk_khz); + + return f; +} + +static struct acpuclk_data acpuclk_8x60_data = { + .set_rate = acpuclk_8x60_set_rate, + .get_rate = acpuclk_8x60_get_rate, + .power_collapse_khz = MAX_AXI, + .wait_for_irq_khz = MAX_AXI, +}; + +static int __init acpuclk_8x60_init(struct acpuclk_soc_data *soc_data) +{ + struct clkctl_acpu_speed *max_freq; + int cpu; + + mutex_init(&drv_state.lock); + spin_lock_init(&drv_state.l2_lock); + + /* Configure hardware. */ + max_freq = select_freq_plan(); + unselect_scplls(); + scpll_set_refs(); + for_each_possible_cpu(cpu) + scpll_init(cpu, max_freq->l_val); + scpll_init(L2, max_freq->l2_level->l_val); + regulator_init(); + bus_init(); + + /* Improve boot time by ramping up CPUs immediately. */ + for_each_online_cpu(cpu) + acpuclk_8x60_set_rate(cpu, max_freq->acpuclk_khz, SETRATE_INIT); + + acpuclk_register(&acpuclk_8x60_data); + cpufreq_table_init(); + register_hotcpu_notifier(&acpuclock_cpu_notifier); + + return 0; +} + +struct acpuclk_soc_data acpuclk_8x60_soc_data __initdata = { + .init = acpuclk_8x60_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-9615.c b/arch/arm/mach-msm/acpuclock-9615.c new file mode 100644 index 0000000000000000000000000000000000000000..8882f413e75b90cbb3e3c84d609d8af46a8ac093 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-9615.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "acpuclock.h" + +#define REG_CLKSEL_0 (MSM_APCS_GLB_BASE + 0x08) +#define REG_CLKDIV_0 (MSM_APCS_GLB_BASE + 0x0C) +#define REG_CLKSEL_1 (MSM_APCS_GLB_BASE + 0x10) +#define REG_CLKDIV_1 (MSM_APCS_GLB_BASE + 0x14) +#define REG_CLKOUTSEL (MSM_APCS_GLB_BASE + 0x18) + +#define MAX_VDD_CPU 1150000 +#define MAX_VDD_MEM 1150000 + +enum clk_src { + SRC_CXO, + SRC_PLL0, + SRC_PLL8, + SRC_PLL9, + NUM_SRC, +}; + +struct src_clock { + struct clk *clk; + const char *name; +}; + +static struct src_clock clocks[NUM_SRC] = { + [SRC_PLL0].name = "pll0", + [SRC_PLL8].name = "pll8", + [SRC_PLL9].name = "pll9", +}; + +struct clkctl_acpu_speed { + bool use_for_scaling; + unsigned int khz; + int src; + unsigned int src_sel; + unsigned int src_div; + unsigned int vdd_cpu; + unsigned int vdd_mem; + unsigned int bw_level; +}; + +struct acpuclk_state { + struct mutex lock; + struct clkctl_acpu_speed *current_speed; +}; + +static struct acpuclk_state drv_state = { + .current_speed = &(struct clkctl_acpu_speed){ 0 }, +}; + +/* 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(152), /* At least 19 MHz on bus. */ + [1] = BW_MBPS(368), /* At least 46 MHz on bus. */ + [2] = BW_MBPS(552), /* At least 69 MHz on bus. */ + [3] = BW_MBPS(736), /* At least 92 MHz on bus. */ + [4] = BW_MBPS(1064), /* At least 133 MHz on bus. */ + [5] = BW_MBPS(1536), /* At least 192 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; + +static struct clkctl_acpu_speed acpu_freq_tbl[] = { + { 0, 19200, SRC_CXO, 0, 0, 950000, 1050000, 0 }, + { 1, 138000, SRC_PLL0, 6, 1, 950000, 1050000, 2 }, + { 1, 276000, SRC_PLL0, 6, 0, 1050000, 1050000, 2 }, + { 1, 384000, SRC_PLL8, 3, 0, 1150000, 1150000, 4 }, + /* The row below may be changed at runtime depending on hw rev. */ + { 1, 440000, SRC_PLL9, 2, 0, 1150000, 1150000, 4 }, + { 0 } +}; + +static void select_clk_source_div(struct clkctl_acpu_speed *s) +{ + static void * __iomem const sel_reg[] = {REG_CLKSEL_0, REG_CLKSEL_1}; + static void * __iomem const div_reg[] = {REG_CLKDIV_0, REG_CLKDIV_1}; + uint32_t next_bank; + + next_bank = !(readl_relaxed(REG_CLKOUTSEL) & 1); + writel_relaxed(s->src_sel, sel_reg[next_bank]); + writel_relaxed(s->src_div, div_reg[next_bank]); + writel_relaxed(next_bank, REG_CLKOUTSEL); + + /* Wait for switch to complete. */ + mb(); + udelay(1); +} + +/* 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); + + return; +} + +/* Apply any per-cpu voltage increases. */ +static int increase_vdd(unsigned int vdd_cpu, unsigned int vdd_mem) +{ + int rc = 0; + + /* + * Increase vdd_mem active-set before vdd_cpu. + * vdd_mem should be >= vdd_cpu. + */ + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_L9, RPM_VREG_VOTER1, + vdd_mem, MAX_VDD_MEM, 0); + if (rc) { + pr_err("vdd_mem increase failed (%d)\n", rc); + return rc; + } + + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_S1, RPM_VREG_VOTER1, + vdd_cpu, MAX_VDD_CPU, 0); + if (rc) + pr_err("vdd_cpu increase failed (%d)\n", rc); + + return rc; +} + +/* Apply any per-cpu voltage decreases. */ +static void decrease_vdd(unsigned int vdd_cpu, unsigned int vdd_mem) +{ + int ret; + + /* Update CPU voltage. */ + ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_S1, RPM_VREG_VOTER1, + vdd_cpu, MAX_VDD_CPU, 0); + if (ret) { + pr_err("vdd_cpu decrease failed (%d)\n", ret); + return; + } + + /* + * Decrease vdd_mem active-set after vdd_cpu. + * vdd_mem should be >= vdd_cpu. + */ + ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_L9, RPM_VREG_VOTER1, + vdd_mem, MAX_VDD_MEM, 0); + if (ret) + pr_err("vdd_mem decrease failed (%d)\n", ret); +} + +static int acpuclk_9615_set_rate(int cpu, unsigned long rate, + enum setrate_reason reason) +{ + struct clkctl_acpu_speed *tgt_s, *strt_s; + int rc = 0; + + if (reason == SETRATE_CPUFREQ) + mutex_lock(&drv_state.lock); + + strt_s = drv_state.current_speed; + + /* Return early if rate didn't change. */ + if (rate == strt_s->khz) + goto out; + + /* Find target frequency. */ + for (tgt_s = acpu_freq_tbl; tgt_s->khz != 0; tgt_s++) + if (tgt_s->khz == rate) + break; + if (tgt_s->khz == 0) { + rc = -EINVAL; + goto out; + } + + /* Increase VDD levels if needed. */ + if ((reason == SETRATE_CPUFREQ || reason == SETRATE_INIT) + && (tgt_s->khz > strt_s->khz)) { + rc = increase_vdd(tgt_s->vdd_cpu, tgt_s->vdd_mem); + if (rc) + goto out; + } + + pr_debug("Switching from CPU rate %u KHz -> %u KHz\n", + strt_s->khz, tgt_s->khz); + + /* Switch CPU speed. */ + clk_enable(clocks[tgt_s->src].clk); + select_clk_source_div(tgt_s); + clk_disable(clocks[strt_s->src].clk); + + drv_state.current_speed = tgt_s; + pr_debug("CPU speed change complete\n"); + + /* Nothing else to do for SWFI or power-collapse. */ + if (reason == SETRATE_SWFI || reason == SETRATE_PC) + goto out; + + /* Update bus bandwith request. */ + set_bus_bw(tgt_s->bw_level); + + /* Drop VDD levels if we can. */ + if (tgt_s->khz < strt_s->khz) + decrease_vdd(tgt_s->vdd_cpu, tgt_s->vdd_mem); + +out: + if (reason == SETRATE_CPUFREQ) + mutex_unlock(&drv_state.lock); + return rc; +} + +static unsigned long acpuclk_9615_get_rate(int cpu) +{ + return drv_state.current_speed->khz; +} + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[30]; + +static void __init cpufreq_table_init(void) +{ + int i, freq_cnt = 0; + + /* Construct the freq_table tables from acpu_freq_tbl. */ + for (i = 0; acpu_freq_tbl[i].khz != 0 + && freq_cnt < ARRAY_SIZE(freq_table); 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].khz; + freq_cnt++; + } + } + /* freq_table not big enough to store all usable freqs. */ + BUG_ON(acpu_freq_tbl[i].khz != 0); + + freq_table[freq_cnt].index = freq_cnt; + freq_table[freq_cnt].frequency = CPUFREQ_TABLE_END; + + pr_info("CPU: %d scaling frequencies supported.\n", freq_cnt); + + /* Register table with CPUFreq. */ + cpufreq_frequency_table_get_attr(freq_table, smp_processor_id()); +} +#else +static void __init cpufreq_table_init(void) {} +#endif + +static struct acpuclk_data acpuclk_9615_data = { + .set_rate = acpuclk_9615_set_rate, + .get_rate = acpuclk_9615_get_rate, + .power_collapse_khz = 19200, + .wait_for_irq_khz = 19200, +}; + +static int __init acpuclk_9615_init(struct acpuclk_soc_data *soc_data) +{ + unsigned long max_cpu_khz = 0; + int i; + + mutex_init(&drv_state.lock); + + bus_perf_client = msm_bus_scale_register_client(&bus_client_pdata); + if (!bus_perf_client) { + pr_err("Unable to register bus client\n"); + BUG(); + } + + for (i = 0; i < NUM_SRC; i++) { + if (clocks[i].name) { + clocks[i].clk = clk_get_sys("acpu", clocks[i].name); + BUG_ON(IS_ERR(clocks[i].clk)); + /* + * Prepare the PLLs because we enable/disable them + * in atomic context during power collapse/restore. + */ + BUG_ON(clk_prepare(clocks[i].clk)); + } + } + + /* Determine the rate of PLL9 and fixup tables accordingly */ + if (clk_get_rate(clocks[SRC_PLL9].clk) == 550000000) { + for (i = 0; i < ARRAY_SIZE(acpu_freq_tbl); i++) + if (acpu_freq_tbl[i].src == SRC_PLL9) { + acpu_freq_tbl[i].khz = 550000; + acpu_freq_tbl[i].bw_level = 5; + } + } + + /* Improve boot time by ramping up CPU immediately. */ + for (i = 0; acpu_freq_tbl[i].khz != 0; i++) + max_cpu_khz = acpu_freq_tbl[i].khz; + acpuclk_9615_set_rate(smp_processor_id(), max_cpu_khz, SETRATE_INIT); + + acpuclk_register(&acpuclk_9615_data); + cpufreq_table_init(); + + return 0; +} + +struct acpuclk_soc_data acpuclk_9615_soc_data __initdata = { + .init = acpuclk_9615_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-arm11.c b/arch/arm/mach-msm/acpuclock-arm11.c index 805d4ee53f7e1f7ef2a43f2d60f9a226175574b8..6f900d39b47de44304859026c529db6dc7ba7bac 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 @@ -29,8 +30,8 @@ #include #include #include +#include -#include "proc_comm.h" #include "acpuclock.h" @@ -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-copper.c b/arch/arm/mach-msm/acpuclock-copper.c new file mode 100644 index 0000000000000000000000000000000000000000..f0da74c862a83170186a89ec1a0a7366b065bfc3 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-copper.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "acpuclock.h" +#include "acpuclock-krait.h" + +/* Corner type vreg VDD values */ +#define LVL_NONE RPM_VREG_CORNER_NONE +#define LVL_LOW RPM_VREG_CORNER_LOW +#define LVL_NOM RPM_VREG_CORNER_NOMINAL +#define LVL_HIGH RPM_VREG_CORNER_HIGH + +static struct hfpll_data hfpll_data_cpu = { + .mode_offset = 0x00, + .l_offset = 0x04, + .m_offset = 0x08, + .n_offset = 0x0C, + .config_offset = 0x14, + /* TODO: Verify magic number for copper when available. */ + .config_val = 0x7845C665, + .low_vdd_l_max = 52, + .vdd[HFPLL_VDD_NONE] = 0, + .vdd[HFPLL_VDD_LOW] = 810000, + .vdd[HFPLL_VDD_NOM] = 900000, +}; + +static struct hfpll_data hfpll_data_l2 = { + .mode_offset = 0x00, + .l_offset = 0x04, + .m_offset = 0x08, + .n_offset = 0x0C, + .config_offset = 0x14, + /* TODO: Verify magic number for copper when available. */ + .config_val = 0x7845C665, + .low_vdd_l_max = 52, + .vdd[HFPLL_VDD_NONE] = LVL_NONE, + .vdd[HFPLL_VDD_LOW] = LVL_LOW, + .vdd[HFPLL_VDD_NOM] = LVL_NOM, +}; + +static struct scalable scalable[] = { + [CPU0] = { + .hfpll_phys_base = 0xF908A000, + .hfpll_data = &hfpll_data_cpu, + .l2cpmr_iaddr = 0x4501, + .vreg[VREG_CORE] = { "krait0", 1050000, 3200000 }, + .vreg[VREG_MEM] = { "krait0_mem", 1050000, 0, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8941_S1 }, + .vreg[VREG_DIG] = { "krait0_dig", 1050000, 0, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8941_S2 }, + .vreg[VREG_HFPLL_A] = { "hfpll", 1800000, 0, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8941_L12 }, + }, + [CPU1] = { + .hfpll_phys_base = 0xF909A000, + .hfpll_data = &hfpll_data_cpu, + .l2cpmr_iaddr = 0x5501, + .vreg[VREG_CORE] = { "krait1", 1050000, 3200000 }, + .vreg[VREG_MEM] = { "krait1_mem", 1050000, 0, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8941_S1 }, + .vreg[VREG_DIG] = { "krait1_dig", 1050000, 0, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8941_S2 }, + .vreg[VREG_HFPLL_A] = { "hfpll", 1800000, 0, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8941_L12 }, + }, + [CPU2] = { + .hfpll_phys_base = 0xF90AA000, + .hfpll_data = &hfpll_data_cpu, + .l2cpmr_iaddr = 0x6501, + .vreg[VREG_CORE] = { "krait2", 1050000, 3200000 }, + .vreg[VREG_MEM] = { "krait2_mem", 1050000, 0, + RPM_VREG_VOTER4, + RPM_VREG_ID_PM8921_S1 }, + .vreg[VREG_DIG] = { "krait2_dig", 1050000, 0, + RPM_VREG_VOTER4, + RPM_VREG_ID_PM8921_S2 }, + .vreg[VREG_HFPLL_A] = { "hfpll", 1800000, 0, + RPM_VREG_VOTER4, + RPM_VREG_ID_PM8941_L12 }, + }, + [CPU3] = { + .hfpll_phys_base = 0xF90BA000, + .hfpll_data = &hfpll_data_cpu, + .l2cpmr_iaddr = 0x7501, + .vreg[VREG_CORE] = { "krait3", 1050000, 3200000 }, + .vreg[VREG_MEM] = { "krait3_mem", 1050000, 0, + RPM_VREG_VOTER5, + RPM_VREG_ID_PM8941_S1 }, + .vreg[VREG_DIG] = { "krait3_dig", 1050000, 0, + RPM_VREG_VOTER5, + RPM_VREG_ID_PM8941_S2 }, + .vreg[VREG_HFPLL_A] = { "hfpll", 1800000, 0, + RPM_VREG_VOTER5, + RPM_VREG_ID_PM8941_L12 }, + }, + [L2] = { + .hfpll_phys_base = 0xF9016000, + .hfpll_data = &hfpll_data_l2, + .l2cpmr_iaddr = 0x0500, + .vreg[VREG_HFPLL_A] = { "hfpll", 1800000, 0, + RPM_VREG_VOTER6, + RPM_VREG_ID_PM8941_L12 }, + }, +}; + +static struct msm_bus_paths bw_level_tbl[] = { + [0] = BW_MBPS(400), /* At least 50 MHz on bus. */ + [1] = BW_MBPS(800), /* At least 100 MHz on bus. */ + [2] = BW_MBPS(1334), /* At least 167 MHz on bus. */ + [3] = BW_MBPS(2666), /* At least 200 MHz on bus. */ + [4] = BW_MBPS(3200), /* At least 333 MHz on bus. */ +}; + +static struct msm_bus_scale_pdata bus_scale_data = { + .usecase = bw_level_tbl, + .num_usecases = ARRAY_SIZE(bw_level_tbl), + .active_only = 1, + .name = "acpuclk-copper", +}; + +#define L2(x) (&l2_freq_tbl[(x)]) +static struct l2_level l2_freq_tbl[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0 }, LVL_NOM, 1050000, 0 }, + [1] = { { 300000, PLL_0, 0, 2, 0 }, LVL_NOM, 1050000, 2 }, + [2] = { { 384000, HFPLL, 2, 0, 40 }, LVL_NOM, 1050000, 2 }, + [3] = { { 460800, HFPLL, 2, 0, 48 }, LVL_NOM, 1050000, 2 }, + [4] = { { 537600, HFPLL, 1, 0, 28 }, LVL_NOM, 1050000, 2 }, + [5] = { { 576000, HFPLL, 1, 0, 30 }, LVL_NOM, 1050000, 3 }, + [6] = { { 652800, HFPLL, 1, 0, 34 }, LVL_NOM, 1050000, 3 }, + [7] = { { 729600, HFPLL, 1, 0, 38 }, LVL_NOM, 1050000, 3 }, + [8] = { { 806400, HFPLL, 1, 0, 42 }, LVL_NOM, 1050000, 3 }, + [9] = { { 883200, HFPLL, 1, 0, 46 }, LVL_NOM, 1050000, 4 }, + [10] = { { 960000, HFPLL, 1, 0, 50 }, LVL_NOM, 1050000, 4 }, + [11] = { { 1036800, HFPLL, 1, 0, 54 }, LVL_NOM, 1050000, 4 }, +}; + +static struct acpu_level acpu_freq_tbl[] = { + { 0, {STBY_KHZ, QSB, 0, 0, 0 }, L2(0), 1050000 }, + { 1, { 300000, PLL_0, 0, 2, 0 }, L2(1), 1050000 }, + { 1, { 384000, HFPLL, 2, 0, 40 }, L2(2), 1050000 }, + { 1, { 460800, HFPLL, 2, 0, 48 }, L2(3), 1050000 }, + { 1, { 537600, HFPLL, 1, 0, 28 }, L2(4), 1050000 }, + { 1, { 576000, HFPLL, 1, 0, 30 }, L2(5), 1050000 }, + { 1, { 652800, HFPLL, 1, 0, 34 }, L2(6), 1050000 }, + { 1, { 729600, HFPLL, 1, 0, 38 }, L2(7), 1050000 }, + { 1, { 806400, HFPLL, 1, 0, 42 }, L2(8), 1050000 }, + { 1, { 883200, HFPLL, 1, 0, 46 }, L2(9), 1050000 }, + { 1, { 960000, HFPLL, 1, 0, 50 }, L2(10), 1050000 }, + { 1, { 1036800, HFPLL, 1, 0, 54 }, L2(11), 1050000 }, + { 0, { 0 } } +}; + +static struct acpuclk_krait_params acpuclk_copper_params = { + .scalable = scalable, + .pvs_acpu_freq_tbl[PVS_SLOW] = acpu_freq_tbl, + .pvs_acpu_freq_tbl[PVS_NOMINAL] = acpu_freq_tbl, + .pvs_acpu_freq_tbl[PVS_FAST] = acpu_freq_tbl, + .l2_freq_tbl = l2_freq_tbl, + .l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl), + .bus_scale_data = &bus_scale_data, + .qfprom_phys_base = 0xFC4A8000, +}; + +static int __init acpuclk_copper_probe(struct platform_device *pdev) +{ + return acpuclk_krait_init(&pdev->dev, &acpuclk_copper_params); +} + +static struct of_device_id acpuclk_copper_match_table[] = { + { .compatible = "qcom,acpuclk-copper" }, + {} +}; + +static struct platform_driver acpuclk_copper_driver = { + .driver = { + .name = "acpuclk-copper", + .of_match_table = acpuclk_copper_match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init acpuclk_8960_init(void) +{ + return platform_driver_probe(&acpuclk_copper_driver, + acpuclk_copper_probe); +} +device_initcall(acpuclk_8960_init); diff --git a/arch/arm/mach-msm/acpuclock-fsm9xxx.c b/arch/arm/mach-msm/acpuclock-fsm9xxx.c new file mode 100644 index 0000000000000000000000000000000000000000..3cdc58d48897248119ae2ea9a12230d7bd90a4cf --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-fsm9xxx.c @@ -0,0 +1,52 @@ +/* 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) + +static unsigned long acpuclk_9xxx_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; +} + +static struct acpuclk_data acpuclk_9xxx_data = { + .get_rate = acpuclk_9xxx_get_rate, +}; + +static int __init acpuclk_9xxx_init(struct acpuclk_soc_data *soc_data) +{ + acpuclk_register(&acpuclk_9xxx_data); + pr_info("ACPU running at %lu KHz\n", acpuclk_get_rate(0)); + return 0; +} + +struct acpuclk_soc_data acpuclk_9xxx_soc_data __initdata = { + .init = acpuclk_9xxx_init, +}; diff --git a/arch/arm/mach-msm/acpuclock-krait.c b/arch/arm/mach-msm/acpuclock-krait.c new file mode 100644 index 0000000000000000000000000000000000000000..5682ac32516bdc3328191e1f91726ef938a6afeb --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-krait.c @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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" +#include "acpuclock-krait.h" + +/* 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 +#define SEC_SRC_SEL_L2PLL 1 +#define SEC_SRC_SEL_AUX 2 + +/* PTE EFUSE register offset. */ +#define PTE_EFUSE 0xC0 + +static DEFINE_MUTEX(driver_lock); +static DEFINE_SPINLOCK(l2_lock); + +static struct drv_data { + const struct acpu_level *acpu_freq_tbl; + const struct l2_level *l2_freq_tbl; + struct scalable *scalable; + u32 bus_perf_client; + struct device *dev; +} drv; + +static unsigned long acpuclk_krait_get_rate(int cpu) +{ + return drv.scalable[cpu].cur_speed->khz; +} + +/* Select a source on the primary MUX. */ +static void set_pri_clk_src(struct scalable *sc, u32 pri_src_sel) +{ + u32 regval; + + regval = get_l2_indirect_reg(sc->l2cpmr_iaddr); + regval &= ~0x3; + regval |= (pri_src_sel & 0x3); + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + /* Wait for switch to complete. */ + mb(); + udelay(1); +} + +/* Select a source on the secondary MUX. */ +static void set_sec_clk_src(struct scalable *sc, u32 sec_src_sel) +{ + u32 regval; + + regval = get_l2_indirect_reg(sc->l2cpmr_iaddr); + regval &= ~(0x3 << 2); + regval |= ((sec_src_sel & 0x3) << 2); + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + /* Wait for switch to complete. */ + mb(); + udelay(1); +} + +/* Enable an already-configured HFPLL. */ +static void hfpll_enable(struct scalable *sc, bool skip_regulators) +{ + int rc; + + if (!skip_regulators) { + /* Enable regulators required by the HFPLL. */ + if (sc->vreg[VREG_HFPLL_A].rpm_vreg_id) { + rc = rpm_vreg_set_voltage( + sc->vreg[VREG_HFPLL_A].rpm_vreg_id, + sc->vreg[VREG_HFPLL_A].rpm_vreg_voter, + sc->vreg[VREG_HFPLL_A].cur_vdd, + sc->vreg[VREG_HFPLL_A].max_vdd, 0); + if (rc) + dev_err(drv.dev, + "%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_A].name, rc); + } + if (sc->vreg[VREG_HFPLL_B].rpm_vreg_id) { + rc = rpm_vreg_set_voltage( + sc->vreg[VREG_HFPLL_B].rpm_vreg_id, + sc->vreg[VREG_HFPLL_B].rpm_vreg_voter, + sc->vreg[VREG_HFPLL_B].cur_vdd, + sc->vreg[VREG_HFPLL_B].max_vdd, 0); + if (rc) + dev_err(drv.dev, + "%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_B].name, rc); + } + } + + /* Disable PLL bypass mode. */ + writel_relaxed(0x2, sc->hfpll_base + sc->hfpll_data->mode_offset); + + /* + * 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 + sc->hfpll_data->mode_offset); + + /* Wait for PLL to lock. */ + mb(); + udelay(60); + + /* Enable PLL output. */ + writel_relaxed(0x7, sc->hfpll_base + sc->hfpll_data->mode_offset); +} + +/* Disable a HFPLL for power-savings or while it's being reprogrammed. */ +static void hfpll_disable(struct scalable *sc, bool skip_regulators) +{ + int rc; + + /* + * Disable the PLL output, disable test mode, enable the bypass mode, + * and assert the reset. + */ + writel_relaxed(0, sc->hfpll_base + sc->hfpll_data->mode_offset); + + if (!skip_regulators) { + /* Remove voltage votes required by the HFPLL. */ + if (sc->vreg[VREG_HFPLL_B].rpm_vreg_id) { + rc = rpm_vreg_set_voltage( + sc->vreg[VREG_HFPLL_B].rpm_vreg_id, + sc->vreg[VREG_HFPLL_B].rpm_vreg_voter, + 0, 0, 0); + if (rc) + dev_err(drv.dev, + "%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_B].name, rc); + } + if (sc->vreg[VREG_HFPLL_A].rpm_vreg_id) { + rc = rpm_vreg_set_voltage( + sc->vreg[VREG_HFPLL_A].rpm_vreg_id, + sc->vreg[VREG_HFPLL_A].rpm_vreg_voter, + 0, 0, 0); + if (rc) + dev_err(drv.dev, + "%s regulator enable failed (%d)\n", + sc->vreg[VREG_HFPLL_A].name, rc); + } + } +} + +/* Program the HFPLL rate. Assumes HFPLL is already disabled. */ +static void hfpll_set_rate(struct scalable *sc, const struct core_speed *tgt_s) +{ + writel_relaxed(tgt_s->pll_l_val, + sc->hfpll_base + sc->hfpll_data->l_offset); +} + +/* Return the L2 speed that should be applied. */ +static const struct l2_level *compute_l2_level(struct scalable *sc, + const struct l2_level *vote_l) +{ + const struct l2_level *new_l; + int cpu; + + /* Find max L2 speed vote. */ + sc->l2_vote = vote_l; + new_l = drv.l2_freq_tbl; + for_each_present_cpu(cpu) + new_l = max(new_l, drv.scalable[cpu].l2_vote); + + return new_l; +} + +/* Update the bus bandwidth request. */ +static void set_bus_bw(unsigned int bw) +{ + int ret; + + /* Update bandwidth if request has changed. This may sleep. */ + ret = msm_bus_scale_client_update_request(drv.bus_perf_client, bw); + if (ret) + dev_err(drv.dev, "bandwidth request failed (%d)\n", ret); +} + +/* Set the CPU or L2 clock speed. */ +static void set_speed(struct scalable *sc, const struct core_speed *tgt_s) +{ + const struct core_speed *strt_s = sc->cur_speed; + + if (strt_s->src == HFPLL && tgt_s->src == HFPLL) { + /* + * Move to an always-on source running at a frequency + * that does not require an elevated CPU voltage. + */ + set_sec_clk_src(sc, SEC_SRC_SEL_AUX); + set_pri_clk_src(sc, PRI_SRC_SEL_SEC_SRC); + + /* Re-program HFPLL. */ + hfpll_disable(sc, 1); + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc, 1); + + /* Move to HFPLL. */ + set_pri_clk_src(sc, tgt_s->pri_src_sel); + } else if (strt_s->src == HFPLL && tgt_s->src != HFPLL) { + set_sec_clk_src(sc, tgt_s->sec_src_sel); + set_pri_clk_src(sc, tgt_s->pri_src_sel); + hfpll_disable(sc, 0); + } else if (strt_s->src != HFPLL && tgt_s->src == HFPLL) { + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc, 0); + set_pri_clk_src(sc, tgt_s->pri_src_sel); + } else { + set_sec_clk_src(sc, tgt_s->sec_src_sel); + } + + sc->cur_speed = tgt_s; +} + +/* Apply any per-cpu voltage increases. */ +static int increase_vdd(int cpu, int vdd_core, int vdd_mem, int vdd_dig, + enum setrate_reason reason) +{ + struct scalable *sc = &drv.scalable[cpu]; + int rc = 0; + + /* + * Increase vdd_mem active-set before vdd_dig. + * vdd_mem should be >= 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) { + dev_err(drv.dev, + "vdd_mem (cpu%d) increase failed (%d)\n", + 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) { + dev_err(drv.dev, + "vdd_dig (cpu%d) increase failed (%d)\n", + 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) { + dev_err(drv.dev, + "vdd_core (cpu%d) increase failed (%d)\n", + 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, int vdd_core, int vdd_mem, int vdd_dig, + enum setrate_reason reason) +{ + struct scalable *sc = &drv.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) { + dev_err(drv.dev, + "vdd_core (cpu%d) decrease failed (%d)\n", + 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) { + dev_err(drv.dev, + "vdd_dig (cpu%d) decrease failed (%d)\n", + cpu, ret); + return; + } + sc->vreg[VREG_DIG].cur_vdd = vdd_dig; + } + + /* + * Decrease vdd_mem active-set after vdd_dig. + * vdd_mem should be >= 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) { + dev_err(drv.dev, + "vdd_mem (cpu%d) decrease failed (%d)\n", + cpu, ret); + return; + } + sc->vreg[VREG_MEM].cur_vdd = vdd_mem; + } +} + +static int calculate_vdd_mem(const struct acpu_level *tgt) +{ + return tgt->l2_level->vdd_mem; +} + +static int calculate_vdd_dig(const struct acpu_level *tgt) +{ + int pll_vdd_dig; + const int *hfpll_vdd = drv.scalable[L2].hfpll_data->vdd; + const u32 low_vdd_l_max = drv.scalable[L2].hfpll_data->low_vdd_l_max; + + if (tgt->l2_level->speed.src != HFPLL) + pll_vdd_dig = hfpll_vdd[HFPLL_VDD_NONE]; + else if (tgt->l2_level->speed.pll_l_val > low_vdd_l_max) + pll_vdd_dig = hfpll_vdd[HFPLL_VDD_NOM]; + else + pll_vdd_dig = hfpll_vdd[HFPLL_VDD_LOW]; + + return max(tgt->l2_level->vdd_dig, pll_vdd_dig); +} + +static int calculate_vdd_core(const struct acpu_level *tgt) +{ + return tgt->vdd_core; +} + +/* Set the CPU's clock rate and adjust the L2 rate, voltage and BW requests. */ +static int acpuclk_krait_set_rate(int cpu, unsigned long rate, + enum setrate_reason reason) +{ + const struct core_speed *strt_acpu_s, *tgt_acpu_s; + const struct l2_level *tgt_l2_l; + const struct acpu_level *tgt; + 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 = drv.scalable[cpu].cur_speed; + + /* Return early if rate didn't change. */ + if (rate == strt_acpu_s->khz) + goto out; + + /* Find target frequency. */ + for (tgt = drv.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 %lu KHz -> %lu KHz\n", + cpu, strt_acpu_s->khz, tgt_acpu_s->khz); + + /* Set the new CPU speed. */ + set_speed(&drv.scalable[cpu], tgt_acpu_s); + + /* + * Update the L2 vote and apply the rate change. A spinlock is + * necessary to ensure L2 rate is calculated and set atomically + * with the CPU frequency, even if acpuclk_krait_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(&drv.scalable[cpu], tgt->l2_level); + set_speed(&drv.scalable[L2], &tgt_l2_l->speed); + 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); + + 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, + const struct core_speed *tgt_s) +{ + pr_debug("Initializing HFPLL%d\n", sc - drv.scalable); + + /* Disable the PLL for re-programming. */ + hfpll_disable(sc, 1); + + /* Configure PLL parameters for integer mode. */ + writel_relaxed(sc->hfpll_data->config_val, + sc->hfpll_base + sc->hfpll_data->config_offset); + writel_relaxed(0, sc->hfpll_base + sc->hfpll_data->m_offset); + writel_relaxed(1, sc->hfpll_base + sc->hfpll_data->n_offset); + + /* Set an initial rate and enable the PLL. */ + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc, 0); +} + +/* Voltage regulator initialization. */ +static void __init regulator_init(const struct acpu_level *lvl) +{ + int cpu, ret; + struct scalable *sc; + int vdd_mem, vdd_dig, vdd_core; + + vdd_mem = calculate_vdd_mem(lvl); + vdd_dig = calculate_vdd_dig(lvl); + + for_each_possible_cpu(cpu) { + sc = &drv.scalable[cpu]; + + /* Set initial vdd_mem vote. */ + 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) { + dev_err(drv.dev, "%s initialization failed (%d)\n", + sc->vreg[VREG_MEM].name, ret); + BUG(); + } + sc->vreg[VREG_MEM].cur_vdd = vdd_mem; + + /* Set initial vdd_dig vote. */ + 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) { + dev_err(drv.dev, "%s initialization failed (%d)\n", + sc->vreg[VREG_DIG].name, ret); + BUG(); + } + sc->vreg[VREG_DIG].cur_vdd = vdd_dig; + + /* Setup Krait CPU regulators and initial core voltage. */ + sc->vreg[VREG_CORE].reg = regulator_get(NULL, + sc->vreg[VREG_CORE].name); + if (IS_ERR(sc->vreg[VREG_CORE].reg)) { + dev_err(drv.dev, "regulator_get(%s) failed (%ld)\n", + sc->vreg[VREG_CORE].name, + PTR_ERR(sc->vreg[VREG_CORE].reg)); + BUG(); + } + vdd_core = calculate_vdd_core(lvl); + ret = regulator_set_voltage(sc->vreg[VREG_CORE].reg, vdd_core, + sc->vreg[VREG_CORE].max_vdd); + if (ret) { + dev_err(drv.dev, "regulator_set_voltage(%s) (%d)\n", + sc->vreg[VREG_CORE].name, ret); + BUG(); + } + sc->vreg[VREG_CORE].cur_vdd = vdd_core; + ret = regulator_set_optimum_mode(sc->vreg[VREG_CORE].reg, + sc->vreg[VREG_CORE].peak_ua); + if (ret < 0) { + dev_err(drv.dev, "regulator_set_optimum_mode(%s) failed" + " (%d)\n", sc->vreg[VREG_CORE].name, ret); + BUG(); + } + ret = regulator_enable(sc->vreg[VREG_CORE].reg); + if (ret) { + dev_err(drv.dev, "regulator_enable(%s) failed (%d)\n", + sc->vreg[VREG_CORE].name, ret); + BUG(); + } + } +} + +/* Set initial rate for a given core. */ +static void __init init_clock_sources(struct scalable *sc, + const struct core_speed *tgt_s) +{ + u32 regval; + + /* Program AUX source input to the secondary MUX. */ + if (sc->aux_clk_sel_addr) + writel_relaxed(sc->aux_clk_sel, sc->aux_clk_sel_addr); + + /* Switch away from the HFPLL while it's re-initialized. */ + set_sec_clk_src(sc, SEC_SRC_SEL_AUX); + 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 = get_l2_indirect_reg(sc->l2cpmr_iaddr); + regval &= ~(0x3 << 6); + set_l2_indirect_reg(sc->l2cpmr_iaddr, regval); + + /* Switch to the target clock source. */ + set_sec_clk_src(sc, tgt_s->sec_src_sel); + set_pri_clk_src(sc, tgt_s->pri_src_sel); + sc->cur_speed = tgt_s; +} + +static void __init per_cpu_init(int cpu, const struct acpu_level *max_level) +{ + drv.scalable[cpu].hfpll_base = + ioremap(drv.scalable[cpu].hfpll_phys_base, SZ_32); + BUG_ON(!drv.scalable[cpu].hfpll_base); + + init_clock_sources(&drv.scalable[cpu], &max_level->speed); + drv.scalable[cpu].l2_vote = max_level->l2_level; +} + +/* Register with bus driver. */ +static void __init bus_init(struct msm_bus_scale_pdata *bus_scale_data, + unsigned int init_bw) +{ + int ret; + + drv.bus_perf_client = msm_bus_scale_register_client(bus_scale_data); + if (!drv.bus_perf_client) { + dev_err(drv.dev, "unable to register bus client\n"); + BUG(); + } + + ret = msm_bus_scale_client_update_request(drv.bus_perf_client, init_bw); + if (ret) + dev_err(drv.dev, "initial bandwidth req failed (%d)\n", ret); +} + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[NR_CPUS][35]; + +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; drv.acpu_freq_tbl[i].speed.khz != 0 + && freq_cnt < ARRAY_SIZE(*freq_table); i++) { + if (drv.acpu_freq_tbl[i].use_for_scaling) { + freq_table[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency + = drv.acpu_freq_tbl[i].speed.khz; + freq_cnt++; + } + } + /* freq_table not big enough to store all usable freqs. */ + BUG_ON(drv.acpu_freq_tbl[i].speed.khz != 0); + + freq_table[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency = CPUFREQ_TABLE_END; + + dev_info(drv.dev, "CPU%d: %d 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 acpuclk_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + static int prev_khz[NR_CPUS]; + int rc, cpu = (int)hcpu; + struct scalable *sc = &drv.scalable[cpu]; + + switch (action & ~CPU_TASKS_FROZEN) { + case CPU_DEAD: + prev_khz[cpu] = acpuclk_krait_get_rate(cpu); + /* Fall through. */ + case CPU_UP_CANCELED: + acpuclk_krait_set_rate(cpu, HOT_UNPLUG_KHZ, SETRATE_HOTPLUG); + regulator_set_optimum_mode(sc->vreg[VREG_CORE].reg, 0); + break; + case CPU_UP_PREPARE: + if (WARN_ON(!prev_khz[cpu])) + return NOTIFY_BAD; + rc = regulator_set_optimum_mode(sc->vreg[VREG_CORE].reg, + sc->vreg[VREG_CORE].peak_ua); + if (rc < 0) + return NOTIFY_BAD; + acpuclk_krait_set_rate(cpu, prev_khz[cpu], SETRATE_HOTPLUG); + break; + default: + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata acpuclk_cpu_notifier = { + .notifier_call = acpuclk_cpu_callback, +}; + +static const struct acpu_level __init *select_freq_plan( + const struct acpu_level *const *pvs_tbl, u32 qfprom_phys) +{ + const struct acpu_level *l, *max_acpu_level = NULL; + void __iomem *qfprom_base; + u32 pte_efuse, pvs, tbl_idx; + char *pvs_names[] = { "Slow", "Nominal", "Fast", "Unknown" }; + + qfprom_base = ioremap(qfprom_phys, SZ_256); + /* Select frequency tables. */ + if (qfprom_base) { + pte_efuse = readl_relaxed(qfprom_base + PTE_EFUSE); + pvs = (pte_efuse >> 10) & 0x7; + iounmap(qfprom_base); + if (pvs == 0x7) + pvs = (pte_efuse >> 13) & 0x7; + + switch (pvs) { + case 0x0: + case 0x7: + tbl_idx = PVS_SLOW; + break; + case 0x1: + tbl_idx = PVS_NOMINAL; + break; + case 0x3: + tbl_idx = PVS_FAST; + break; + default: + tbl_idx = PVS_UNKNOWN; + break; + } + } else { + tbl_idx = PVS_UNKNOWN; + dev_err(drv.dev, "Unable to map QFPROM base\n"); + } + dev_info(drv.dev, "ACPU PVS: %s\n", pvs_names[tbl_idx]); + if (tbl_idx == PVS_UNKNOWN) { + tbl_idx = PVS_SLOW; + dev_warn(drv.dev, "ACPU PVS: Defaulting to %s\n", + pvs_names[tbl_idx]); + } + drv.acpu_freq_tbl = pvs_tbl[tbl_idx]; + + /* Find the max supported scaling frequency. */ + for (l = drv.acpu_freq_tbl; l->speed.khz != 0; l++) + if (l->use_for_scaling) + max_acpu_level = l; + BUG_ON(!max_acpu_level); + dev_info(drv.dev, "Max ACPU freq: %lu KHz\n", + max_acpu_level->speed.khz); + + return max_acpu_level; +} + +static struct acpuclk_data acpuclk_krait_data = { + .set_rate = acpuclk_krait_set_rate, + .get_rate = acpuclk_krait_get_rate, + .power_collapse_khz = STBY_KHZ, + .wait_for_irq_khz = STBY_KHZ, +}; + +int __init acpuclk_krait_init(struct device *dev, + const struct acpuclk_krait_params *params) +{ + const struct acpu_level *max_acpu_level; + int cpu; + + drv.scalable = params->scalable; + drv.l2_freq_tbl = params->l2_freq_tbl; + drv.dev = dev; + + drv.scalable[L2].hfpll_base = + ioremap(drv.scalable[L2].hfpll_phys_base, SZ_32); + BUG_ON(!drv.scalable[L2].hfpll_base); + + max_acpu_level = select_freq_plan(params->pvs_acpu_freq_tbl, + params->qfprom_phys_base); + regulator_init(max_acpu_level); + bus_init(params->bus_scale_data, max_acpu_level->l2_level->bw_level); + init_clock_sources(&drv.scalable[L2], &max_acpu_level->l2_level->speed); + for_each_online_cpu(cpu) + per_cpu_init(cpu, max_acpu_level); + + cpufreq_table_init(); + + acpuclk_register(&acpuclk_krait_data); + register_hotcpu_notifier(&acpuclk_cpu_notifier); + + return 0; +} diff --git a/arch/arm/mach-msm/acpuclock-krait.h b/arch/arm/mach-msm/acpuclock-krait.h new file mode 100644 index 0000000000000000000000000000000000000000..fbf1f5f1bed43965a3055b643b3e9ba802fa3d62 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-krait.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ACPUCLOCK_KRAIT_H +#define __ARCH_ARM_MACH_MSM_ACPUCLOCK_KRAIT_H + +#define STBY_KHZ 1 + +#define BW_MBPS(_bw) \ + { \ + .vectors = (struct msm_bus_vectors[]){ \ + {\ + .src = MSM_BUS_MASTER_AMPSS_M0, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ib = (_bw) * 1000000UL, \ + }, \ + { \ + .src = MSM_BUS_MASTER_AMPSS_M1, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ib = (_bw) * 1000000UL, \ + }, \ + }, \ + .num_paths = 2, \ + } + +/** + * src_id - Clock source IDs. + */ +enum src_id { + PLL_0 = 0, + HFPLL, + QSB, +}; + +/** + * enum pvs - IDs to distinguish between CPU frequency tables. + */ +enum pvs { + PVS_SLOW = 0, + PVS_NOMINAL, + PVS_FAST, + PVS_UNKNOWN, + NUM_PVS +}; + +/** + * enum scalables - IDs of frequency scalable hardware blocks. + */ +enum scalables { + CPU0 = 0, + CPU1, + CPU2, + CPU3, + L2, +}; + + +/** + * enum hfpll_vdd_level - IDs of HFPLL voltage levels. + */ +enum hfpll_vdd_levels { + HFPLL_VDD_NONE, + HFPLL_VDD_LOW, + HFPLL_VDD_NOM, + NUM_HFPLL_VDD +}; + +/** + * enum vregs - IDs of voltage regulators. + */ +enum vregs { + VREG_CORE, + VREG_MEM, + VREG_DIG, + VREG_HFPLL_A, + VREG_HFPLL_B, + NUM_VREG +}; + +/** + * struct vreg - Voltage regulator data. + * @name: Name of requlator. + * @max_vdd: Limit the maximum-settable voltage. + * @rpm_vreg_id: ID to use with rpm_vreg_*() APIs. + * @reg: Regulator handle. + * @cur_vdd: Last-set voltage in uV. + * @peak_ua: Maximum current draw expected in uA. + */ +struct vreg { + const char name[15]; + const int max_vdd; + const int peak_ua; + const int rpm_vreg_voter; + const int rpm_vreg_id; + struct regulator *reg; + int cur_vdd; +}; + +/** + * struct core_speed - Clock tree and configuration parameters. + * @khz: Clock rate in KHz. + * @src: Clock source ID. + * @pri_src_sel: Input to select on the primary MUX. + * @sec_src_sel: Input to select on the secondary MUX. + * @pll_l_val: HFPLL "L" value to be applied when an HFPLL source is selected. + */ +struct core_speed { + const unsigned long khz; + const int src; + const u32 pri_src_sel; + const u32 sec_src_sel; + const u32 pll_l_val; +}; + +/** + * struct l2_level - L2 clock rate and associated voltage and b/w requirements. + * @speed: L2 clock configuration. + * @vdd_dig: vdd_dig voltage in uV. + * @vdd_mem: vdd_mem voltage in uV. + * @bw_level: Bandwidth performance level number. + */ +struct l2_level { + const struct core_speed speed; + const int vdd_dig; + const int vdd_mem; + const unsigned int bw_level; +}; + +/** + * struct acpu_level - CPU clock rate and L2 rate and voltage requirements. + * @use_for_scaling: Flag indicating whether or not the level should be used. + * @speed: CPU clock configuration. + * @l2_level: L2 configuration to use. + * @vdd_core: CPU core voltage in uV. + */ +struct acpu_level { + const int use_for_scaling; + const struct core_speed speed; + const struct l2_level *l2_level; + const int vdd_core; +}; + +/** + * struct hfpll_data - Descriptive data of HFPLL hardware. + * @mode_offset: Mode register offset from base address. + * @l_offset: "L" value register offset from base address. + * @m_offset: "M" value register offset from base address. + * @n_offset: "N" value register offset from base address. + * @config_offset: Configuration register offset from base address. + * @config_val: Value to initialize the @config_offset register to. + * @vdd: voltage requirements for each VDD level. + */ +struct hfpll_data { + const u32 mode_offset; + const u32 l_offset; + const u32 m_offset; + const u32 n_offset; + const u32 config_offset; + const u32 config_val; + const u32 low_vdd_l_max; + const int vdd[NUM_HFPLL_VDD]; +}; + +/** + * struct scalable - Register locations and state associated with a scalable HW. + * @hfpll_phys_base: Physical base address of HFPLL register. + * @hfpll_base: Virtual base address of HFPLL registers. + * @aux_clk_sel_addr: Virtual address of auxiliary MUX. + * @aux_clk_sel: Auxiliary mux input to select at boot. + * @l2cpmr_iaddr: Indirect address of the CPMR MUX/divider CP15 register. + * @hfpll_data: Descriptive data of HFPLL hardware. + * @cur_speed: Pointer to currently-set speed. + * @l2_vote: L2 performance level vote associate with the current CPU speed. + * @vreg: Array of voltage regulators needed by the scalable. + */ +struct scalable { + const u32 hfpll_phys_base; + void __iomem *hfpll_base; + void __iomem *aux_clk_sel_addr; + const u32 aux_clk_sel; + const u32 l2cpmr_iaddr; + const struct hfpll_data *hfpll_data; + const struct core_speed *cur_speed; + const struct l2_level *l2_vote; + struct vreg vreg[NUM_VREG]; +}; + +/** + * struct acpuclk_krait_params - SoC specific driver parameters. + * @scalable: Array of scalables. + * @pvs_acpu_freq_tbl: Array of CPU frequency tables. + * @l2_freq_tbl: L2 frequency table. + * @l2_freq_tbl_size: Number of rows in @l2_freq_tbl. + * @qfprom_phys_base: Physical base address of QFPROM. + * @bus_scale_data: MSM bus driver parameters. + */ +struct acpuclk_krait_params { + struct scalable *scalable; + const struct acpu_level *pvs_acpu_freq_tbl[NUM_PVS]; + const struct l2_level *l2_freq_tbl; + const size_t l2_freq_tbl_size; + const u32 qfprom_phys_base; + struct msm_bus_scale_pdata *bus_scale_data; +}; + +/** + * acpuclk_krait_init - Initialize the Krait CPU clock driver give SoC params. + */ +extern int acpuclk_krait_init(struct device *dev, + const struct acpuclk_krait_params *params); + +#endif diff --git a/arch/arm/mach-msm/acpuclock.c b/arch/arm/mach-msm/acpuclock.c new file mode 100644 index 0000000000000000000000000000000000000000..91071c47259fc651a33cc97c0cc8255e278a942c --- /dev/null +++ b/arch/arm/mach-msm/acpuclock.c @@ -0,0 +1,76 @@ +/* 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 "acpuclock.h" + +static struct acpuclk_data *acpuclk_data; + +unsigned long acpuclk_get_rate(int cpu) +{ + if (!acpuclk_data->get_rate) + return 0; + + return acpuclk_data->get_rate(cpu); +} + +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason) +{ + if (!acpuclk_data->set_rate) + return 0; + + return acpuclk_data->set_rate(cpu, rate, reason); +} + +uint32_t acpuclk_get_switch_time(void) +{ + return acpuclk_data->switch_time_us; +} + +unsigned long acpuclk_power_collapse(void) +{ + unsigned long rate = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), acpuclk_data->power_collapse_khz, + SETRATE_PC); + return rate; +} + +unsigned long acpuclk_wait_for_irq(void) +{ + unsigned long rate = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), acpuclk_data->wait_for_irq_khz, + SETRATE_SWFI); + return rate; +} + +void __init acpuclk_register(struct acpuclk_data *data) +{ + acpuclk_data = data; +} + +int __init acpuclk_init(struct acpuclk_soc_data *soc_data) +{ + int rc; + + if (!soc_data->init) + return -EINVAL; + + rc = soc_data->init(soc_data); + if (rc) + return rc; + + if (!acpuclk_data) + return -ENODEV; + + return 0; +} diff --git a/arch/arm/mach-msm/acpuclock.h b/arch/arm/mach-msm/acpuclock.h index 415de2eb9a5ee55559e0caadab42680e020b080e..c5f0ee3005c33a9f3ddf5a58b4cc553401352784 100644 --- a/arch/arm/mach-msm/acpuclock.h +++ b/arch/arm/mach-msm/acpuclock.h @@ -1,9 +1,8 @@ -/* arch/arm/mach-msm/acpuclock.h - * - * MSM architecture clock driver header +/* + * MSM architecture CPU clock driver header * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2012, Code Aurora Forum. All rights reserved. * Author: San Mehat * * This software is licensed under the terms of the GNU General Public @@ -20,13 +19,97 @@ #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); +/** + * enum setrate_reason - Reasons for use with acpuclk_set_rate() + */ +enum setrate_reason { + SETRATE_CPUFREQ = 0, + SETRATE_SWFI, + SETRATE_PC, + SETRATE_HOTPLUG, + SETRATE_INIT, +}; + +/** + * struct acpuclk_soc_data - SoC data for acpuclk_init() + */ +struct acpuclk_soc_data { + unsigned long max_speed_delta_khz; + unsigned int max_axi_khz; + int (*init)(struct acpuclk_soc_data *); +}; + +/** + * struct acpuclk_data - Function pointers and data for function implementations + */ +struct acpuclk_data { + unsigned long (*get_rate)(int cpu); + int (*set_rate)(int cpu, unsigned long rate, enum setrate_reason); + uint32_t switch_time_us; + unsigned long power_collapse_khz; + unsigned long wait_for_irq_khz; +}; + +/** + * acpulock_get_rate() - Get a CPU's clock rate in KHz + * @cpu: CPU to query the rate of + */ +unsigned long acpuclk_get_rate(int cpu); + +/** + * acpuclk_set_rate() - Set a CPU's clock rate + * @cpu: CPU to set rate of + * @rate: Desired rate in KHz + * @setrate_reason: Reason for the rate switch + * + * Returns 0 for success. + */ +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason); + +/** + * acpuclk_get_switch_time() - Query estimated time in us for a CPU rate switch + */ uint32_t acpuclk_get_switch_time(void); -unsigned long acpuclk_wait_for_irq(void); + +/** + * acpuclk_power_collapse() - Prepare current CPU clocks for power-collapse + * + * Returns the previous rate of the CPU in KHz. + */ unsigned long acpuclk_power_collapse(void); -unsigned long acpuclk_get_wfi_rate(void); +/** + * acpuclk_wait_for_irq() - Prepare current CPU clocks for SWFI + * + * Returns the previous rate of the CPU in KHz. + */ +unsigned long acpuclk_wait_for_irq(void); -#endif +/** + * acpuclk_register() - Register acpuclk_data function implementations + * @data: acpuclock API implementations and data + */ +void acpuclk_register(struct acpuclk_data *data); + +/** + * acpuclk_init() - acpuclock driver initialization function + * + * Return 0 for success. + */ +int acpuclk_init(struct acpuclk_soc_data *); + +/* SoC-specific acpuclock initialization functions. */ +extern struct acpuclk_soc_data acpuclk_7x27_soc_data; +extern struct acpuclk_soc_data acpuclk_7x27a_soc_data; +extern struct acpuclk_soc_data acpuclk_7x27aa_soc_data; +extern struct acpuclk_soc_data acpuclk_7x30_soc_data; +extern struct acpuclk_soc_data acpuclk_8x50_soc_data; +extern struct acpuclk_soc_data acpuclk_8x60_soc_data; +extern struct acpuclk_soc_data acpuclk_8960_soc_data; +extern struct acpuclk_soc_data acpuclk_9xxx_soc_data; +extern struct acpuclk_soc_data acpuclk_9615_soc_data; +extern struct acpuclk_soc_data acpuclk_8930_soc_data; +extern struct acpuclk_soc_data acpuclk_8064_soc_data; +extern struct acpuclk_soc_data acpuclk_8625_soc_data; +#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 0000000000000000000000000000000000000000..82a6db8bb0773e64ea28ce2299876f2cc32778d3 --- /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 0000000000000000000000000000000000000000..827adab00e2a28ac90641ff11095003ce8d45b45 --- /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 0000000000000000000000000000000000000000..a549e9de7f26075ff1f8839833ced192655c0fc8 --- /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 0000000000000000000000000000000000000000..1cc3ce0b1079f03c7b2a8317506e15bb5e4224f8 --- /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 0000000000000000000000000000000000000000..d53e471536f91d93dc545667a7f8d1366c451fd1 --- /dev/null +++ b/arch/arm/mach-msm/bam_dmux.c @@ -0,0 +1,2326 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#include + +#include +#include +#include +#include +#include +#include + +#define BAM_CH_LOCAL_OPEN 0x1 +#define BAM_CH_REMOTE_OPEN 0x2 +#define BAM_CH_IN_RESET 0x4 + +#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 BAM_MUX_HDR_CMD_STATUS 3 /* unused */ +#define BAM_MUX_HDR_CMD_OPEN_NO_A2_PC 4 + +#define POLLING_MIN_SLEEP 950 /* 0.95 ms */ +#define POLLING_MAX_SLEEP 1050 /* 1.05 ms */ +#define POLLING_INACTIVITY 40 /* cycles before switch to intr mode */ + +#define LOW_WATERMARK 2 +#define HIGH_WATERMARK 4 + +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; +static uint32_t bam_dmux_tx_sps_failure_cnt; +static uint32_t bam_dmux_tx_stall_cnt; +static atomic_t bam_dmux_ack_out_cnt = ATOMIC_INIT(0); +static atomic_t bam_dmux_ack_in_cnt = ATOMIC_INIT(0); +static atomic_t bam_dmux_a2_pwr_cntl_in_cnt = ATOMIC_INIT(0); + +#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) + +#define DBG_INC_TX_SPS_FAILURE_CNT() do { \ + bam_dmux_tx_sps_failure_cnt++; \ +} while (0) + +#define DBG_INC_TX_STALL_CNT() do { \ + bam_dmux_tx_stall_cnt++; \ +} while (0) + +#define DBG_INC_ACK_OUT_CNT() \ + atomic_inc(&bam_dmux_ack_out_cnt) + +#define DBG_INC_A2_POWER_CONTROL_IN_CNT() \ + atomic_inc(&bam_dmux_a2_pwr_cntl_in_cnt) + +#define DBG_INC_ACK_IN_CNT() \ + atomic_inc(&bam_dmux_ack_in_cnt) +#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) +#define DBG_INC_TX_SPS_FAILURE_CNT() do { } while (0) +#define DBG_INC_TX_STALL_CNT() do { } while (0) +#define DBG_INC_ACK_OUT_CNT() do { } while (0) +#define DBG_INC_A2_POWER_CONTROL_IN_CNT() \ + do { } while (0) +#define DBG_INC_ACK_IN_CNT() do { } while (0) +#endif + +struct bam_ch_info { + uint32_t status; + void (*notify)(void *, int, unsigned long); + void *priv; + spinlock_t lock; + struct platform_device *pdev; + char name[BAM_DMUX_CH_NAME_MAX_LEN]; + int num_tx_pkts; + int use_wm; +}; + +struct tx_pkt_info { + struct sk_buff *skb; + dma_addr_t dma_address; + char is_cmd; + uint32_t len; + struct work_struct work; + struct list_head list_node; + unsigned ts_sec; + unsigned long ts_nsec; +}; + +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 sps_bam_props a2_props; +static u32 a2_device_handle; +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 sps_register_event rx_register_event; + +static struct bam_ch_info bam_ch[BAM_DMUX_NUM_CHANNELS]; +static int bam_mux_initialized; + +static int polling_mode; + +static LIST_HEAD(bam_rx_pool); +static DEFINE_MUTEX(bam_rx_pool_mutexlock); +static int bam_rx_pool_len; +static LIST_HEAD(bam_tx_pool); +static DEFINE_SPINLOCK(bam_tx_pool_spinlock); + +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 notify_all(int event, unsigned long data); +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 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; + +/* A2 power collaspe */ +#define UL_TIMEOUT_DELAY 1000 /* in ms */ +#define ENABLE_DISCONNECT_ACK 0x1 +static void toggle_apps_ack(void); +static void reconnect_to_bam(void); +static void disconnect_to_bam(void); +static void ul_wakeup(void); +static void ul_timeout(struct work_struct *work); +static void vote_dfab(void); +static void unvote_dfab(void); +static void kickoff_ul_wakeup_func(struct work_struct *work); +static void grab_wakelock(void); +static void release_wakelock(void); + +static int bam_is_connected; +static DEFINE_MUTEX(wakeup_lock); +static struct completion ul_wakeup_ack_completion; +static struct completion bam_connection_completion; +static struct delayed_work ul_timeout_work; +static int ul_packet_written; +static atomic_t ul_ondemand_vote = ATOMIC_INIT(0); +static struct clk *dfab_clk, *xo_clk; +static DEFINE_RWLOCK(ul_wakeup_lock); +static DECLARE_WORK(kickoff_ul_wakeup, kickoff_ul_wakeup_func); +static int bam_connection_is_active; +static int wait_for_ack; +static struct wake_lock bam_wakelock; +static int a2_pc_disabled; +static DEFINE_MUTEX(dfab_status_lock); +static int dfab_is_on; +static int wait_for_dfab; +static struct completion dfab_unvote_completion; +static DEFINE_SPINLOCK(wakelock_reference_lock); +static int wakelock_reference_count; +static int a2_pc_disabled_wakelock_skipped; +static int disconnect_ack; +static LIST_HEAD(bam_other_notify_funcs); +static DEFINE_MUTEX(smsm_cb_lock); +static DEFINE_MUTEX(delayed_ul_vote_lock); +static int need_delayed_ul_vote; + +struct outside_notify_func { + void (*notify)(void *, int, unsigned long); + void *priv; + struct list_head list_node; +}; +/* End A2 power collaspe */ + +/* subsystem restart */ +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data); + +static struct notifier_block restart_notifier = { + .notifier_call = restart_notifier_cb, +}; +static int in_global_reset; +/* end subsystem restart */ + +#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) + +#define bam_ch_is_in_reset(x) \ + (bam_ch[(x)].status & BAM_CH_IN_RESET) + +#define LOG_MESSAGE_MAX_SIZE 80 +struct kfifo bam_dmux_state_log; +static uint32_t bam_dmux_state_logging_disabled; +static DEFINE_SPINLOCK(bam_dmux_logging_spinlock); +static int bam_dmux_uplink_vote; +static int bam_dmux_power_state; + + +#define DMUX_LOG_KERR(fmt...) \ +do { \ + bam_dmux_log(fmt); \ + pr_err(fmt); \ +} while (0) + +/** + * Log a state change along with a small message. + * + * Complete size of messsage is limited to @todo. + */ +static void bam_dmux_log(const char *fmt, ...) +{ + char buff[LOG_MESSAGE_MAX_SIZE]; + unsigned long flags; + va_list arg_list; + unsigned long long t_now; + unsigned long nanosec_rem; + int len = 0; + + if (bam_dmux_state_logging_disabled) + return; + + t_now = sched_clock(); + nanosec_rem = do_div(t_now, 1000000000U); + + /* + * States + * D: 1 = Power collapse disabled + * R: 1 = in global reset + * P: 1 = BAM is powered up + * A: 1 = BAM initialized and ready for data + * + * V: 1 = Uplink vote for power + * U: 1 = Uplink active + * W: 1 = Uplink Wait-for-ack + * A: 1 = Uplink ACK received + * #: >=1 On-demand uplink vote + * D: 1 = Disconnect ACK active + */ + len += scnprintf(buff, sizeof(buff), + " %u.%09lu %c%c%c%c %c%c%c%c%d%c ", + (unsigned)t_now, nanosec_rem, + a2_pc_disabled ? 'D' : 'd', + in_global_reset ? 'R' : 'r', + bam_dmux_power_state ? 'P' : 'p', + bam_connection_is_active ? 'A' : 'a', + bam_dmux_uplink_vote ? 'V' : 'v', + bam_is_connected ? 'U' : 'u', + wait_for_ack ? 'W' : 'w', + ul_wakeup_ack_completion.done ? 'A' : 'a', + atomic_read(&ul_ondemand_vote), + disconnect_ack ? 'D' : 'd' + ); + + va_start(arg_list, fmt); + len += vscnprintf(buff + len, sizeof(buff) - len, fmt, arg_list); + va_end(arg_list); + memset(buff + len, 0x0, sizeof(buff) - len); + + spin_lock_irqsave(&bam_dmux_logging_spinlock, flags); + if (kfifo_avail(&bam_dmux_state_log) < LOG_MESSAGE_MAX_SIZE) { + char junk[LOG_MESSAGE_MAX_SIZE]; + int ret; + + ret = kfifo_out(&bam_dmux_state_log, junk, sizeof(junk)); + if (ret != LOG_MESSAGE_MAX_SIZE) { + pr_err("%s: unable to empty log %d\n", __func__, ret); + spin_unlock_irqrestore(&bam_dmux_logging_spinlock, + flags); + return; + } + } + kfifo_in(&bam_dmux_state_log, buff, sizeof(buff)); + spin_unlock_irqrestore(&bam_dmux_logging_spinlock, flags); +} + +static inline void set_tx_timestamp(struct tx_pkt_info *pkt) +{ + unsigned long long t_now; + + t_now = sched_clock(); + pkt->ts_nsec = do_div(t_now, 1000000000U); + pkt->ts_sec = (unsigned)t_now; +} + +static inline void verify_tx_queue_is_empty(const char *func) +{ + unsigned long flags; + struct tx_pkt_info *info; + int reported = 0; + + spin_lock_irqsave(&bam_tx_pool_spinlock, flags); + list_for_each_entry(info, &bam_tx_pool, list_node) { + if (!reported) { + bam_dmux_log("%s: tx pool not empty\n", func); + if (!in_global_reset) + pr_err("%s: tx pool not empty\n", func); + reported = 1; + } + bam_dmux_log("%s: node=%p ts=%u.%09lu\n", __func__, + &info->list_node, info->ts_sec, info->ts_nsec); + if (!in_global_reset) + pr_err("%s: node=%p ts=%u.%09lu\n", __func__, + &info->list_node, info->ts_sec, info->ts_nsec); + } + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); +} + +static void queue_rx(void) +{ + void *ptr; + struct rx_pkt_info *info; + int ret; + int rx_len_cached; + + mutex_lock(&bam_rx_pool_mutexlock); + rx_len_cached = bam_rx_pool_len; + mutex_unlock(&bam_rx_pool_mutexlock); + + while (rx_len_cached < NUM_BUFFERS) { + if (in_global_reset) + goto fail; + + info = kmalloc(sizeof(struct rx_pkt_info), GFP_KERNEL); + if (!info) { + pr_err("%s: unable to alloc rx_pkt_info\n", __func__); + goto fail; + } + + INIT_WORK(&info->work, handle_bam_mux_cmd); + + info->skb = __dev_alloc_skb(BUFFER_SIZE, GFP_KERNEL); + if (info->skb == NULL) { + DMUX_LOG_KERR("%s: unable to alloc skb\n", __func__); + goto fail_info; + } + ptr = skb_put(info->skb, BUFFER_SIZE); + + info->dma_address = dma_map_single(NULL, ptr, BUFFER_SIZE, + DMA_FROM_DEVICE); + if (info->dma_address == 0 || info->dma_address == ~0) { + DMUX_LOG_KERR("%s: dma_map_single failure %p for %p\n", + __func__, (void *)info->dma_address, ptr); + goto fail_skb; + } + + mutex_lock(&bam_rx_pool_mutexlock); + list_add_tail(&info->list_node, &bam_rx_pool); + rx_len_cached = ++bam_rx_pool_len; + mutex_unlock(&bam_rx_pool_mutexlock); + + ret = sps_transfer_one(bam_rx_pipe, info->dma_address, + BUFFER_SIZE, info, + SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT); + + if (ret) { + DMUX_LOG_KERR("%s: sps_transfer_one failed %d\n", + __func__, ret); + goto fail_transfer; + } + } + return; + +fail_transfer: + mutex_lock(&bam_rx_pool_mutexlock); + list_del(&info->list_node); + --bam_rx_pool_len; + rx_len_cached = bam_rx_pool_len; + mutex_unlock(&bam_rx_pool_mutexlock); + + dma_unmap_single(NULL, info->dma_address, BUFFER_SIZE, + DMA_FROM_DEVICE); + +fail_skb: + dev_kfree_skb_any(info->skb); + +fail_info: + kfree(info); + +fail: + if (rx_len_cached == 0) { + DMUX_LOG_KERR("%s: RX queue failure\n", __func__); + in_global_reset = 1; + } +} + +static void bam_mux_process_data(struct sk_buff *rx_skb) +{ + unsigned long flags; + struct bam_mux_hdr *rx_hdr; + unsigned long event_data; + + 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; + rx_skb->truesize = rx_hdr->pkt_len + sizeof(struct sk_buff); + + event_data = (unsigned long)(rx_skb); + + spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags); + if (bam_ch[rx_hdr->ch_id].notify) + bam_ch[rx_hdr->ch_id].notify( + bam_ch[rx_hdr->ch_id].priv, BAM_DMUX_RECEIVE, + event_data); + else + dev_kfree_skb_any(rx_skb); + spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags); + + queue_rx(); +} + +static inline void handle_bam_mux_cmd_open(struct bam_mux_hdr *rx_hdr) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags); + bam_ch[rx_hdr->ch_id].status |= BAM_CH_REMOTE_OPEN; + bam_ch[rx_hdr->ch_id].num_tx_pkts = 0; + spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags); + queue_rx(); + ret = platform_device_add(bam_ch[rx_hdr->ch_id].pdev); + if (ret) + pr_err("%s: platform_device_add() error: %d\n", + __func__, ret); +} + +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; + dma_unmap_single(NULL, info->dma_address, BUFFER_SIZE, DMA_FROM_DEVICE); + 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) { + DMUX_LOG_KERR("%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; + } + + if (rx_hdr->ch_id >= BAM_DMUX_NUM_CHANNELS) { + DMUX_LOG_KERR("%s: dropping invalid LCID %d" + " reserved %d cmd %d" + " pad %d ch %d len %d\n", __func__, + rx_hdr->ch_id, 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: + bam_dmux_log("%s: opening cid %d PC enabled\n", __func__, + rx_hdr->ch_id); + handle_bam_mux_cmd_open(rx_hdr); + if (rx_hdr->reserved & ENABLE_DISCONNECT_ACK) { + bam_dmux_log("%s: activating disconnect ack\n"); + disconnect_ack = 1; + } + dev_kfree_skb_any(rx_skb); + break; + case BAM_MUX_HDR_CMD_OPEN_NO_A2_PC: + bam_dmux_log("%s: opening cid %d PC disabled\n", __func__, + rx_hdr->ch_id); + + if (!a2_pc_disabled) { + a2_pc_disabled = 1; + ul_wakeup(); + } + + handle_bam_mux_cmd_open(rx_hdr); + dev_kfree_skb_any(rx_skb); + break; + case BAM_MUX_HDR_CMD_CLOSE: + /* probably should drop pending write */ + bam_dmux_log("%s: closing cid %d\n", __func__, + rx_hdr->ch_id); + 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); + queue_rx(); + platform_device_unregister(bam_ch[rx_hdr->ch_id].pdev); + bam_ch[rx_hdr->ch_id].pdev = + platform_device_alloc(bam_ch[rx_hdr->ch_id].name, 2); + if (!bam_ch[rx_hdr->ch_id].pdev) + pr_err("%s: platform_device_alloc failed\n", __func__); + dev_kfree_skb_any(rx_skb); + break; + default: + DMUX_LOG_KERR("%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; + unsigned long flags; + + pkt = kmalloc(sizeof(struct tx_pkt_info), GFP_ATOMIC); + if (pkt == NULL) { + pr_err("%s: mem alloc for tx_pkt_info failed\n", __func__); + rc = -ENOMEM; + 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__); + kfree(pkt); + rc = -ENOMEM; + return rc; + } + pkt->skb = (struct sk_buff *)(data); + pkt->len = len; + pkt->dma_address = dma_address; + pkt->is_cmd = 1; + set_tx_timestamp(pkt); + INIT_WORK(&pkt->work, bam_mux_write_done); + spin_lock_irqsave(&bam_tx_pool_spinlock, flags); + list_add_tail(&pkt->list_node, &bam_tx_pool); + rc = sps_transfer_one(bam_tx_pipe, dma_address, len, + pkt, SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT); + if (rc) { + DMUX_LOG_KERR("%s sps_transfer_one failed rc=%d\n", + __func__, rc); + list_del(&pkt->list_node); + DBG_INC_TX_SPS_FAILURE_CNT(); + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + dma_unmap_single(NULL, pkt->dma_address, + pkt->len, + DMA_TO_DEVICE); + kfree(pkt); + } else { + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + } + + ul_packet_written = 1; + 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; + struct tx_pkt_info *info_expected; + unsigned long event_data; + unsigned long flags; + + if (in_global_reset) + return; + + info = container_of(work, struct tx_pkt_info, work); + + spin_lock_irqsave(&bam_tx_pool_spinlock, flags); + info_expected = list_first_entry(&bam_tx_pool, + struct tx_pkt_info, list_node); + if (unlikely(info != info_expected)) { + struct tx_pkt_info *errant_pkt; + + DMUX_LOG_KERR("%s: bam_tx_pool mismatch .next=%p," + " list_node=%p, ts=%u.%09lu\n", + __func__, bam_tx_pool.next, &info->list_node, + info->ts_sec, info->ts_nsec + ); + + list_for_each_entry(errant_pkt, &bam_tx_pool, list_node) { + DMUX_LOG_KERR("%s: node=%p ts=%u.%09lu\n", __func__, + &errant_pkt->list_node, errant_pkt->ts_sec, + errant_pkt->ts_nsec); + + } + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + BUG(); + } + list_del(&info->list_node); + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + + if (info->is_cmd) { + kfree(info->skb); + kfree(info); + return; + } + skb = info->skb; + kfree(info); + hdr = (struct bam_mux_hdr *)skb->data; + DBG_INC_WRITE_CNT(skb->len); + event_data = (unsigned long)(skb); + spin_lock_irqsave(&bam_ch[hdr->ch_id].lock, flags); + bam_ch[hdr->ch_id].num_tx_pkts--; + spin_unlock_irqrestore(&bam_ch[hdr->ch_id].lock, flags); + if (bam_ch[hdr->ch_id].notify) + bam_ch[hdr->ch_id].notify( + bam_ch[hdr->ch_id].priv, BAM_DMUX_WRITE_DONE, + event_data); + 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; + } + + if (bam_ch[id].use_wm && + (bam_ch[id].num_tx_pkts >= HIGH_WATERMARK)) { + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + pr_err("%s: watermark exceeded: %d\n", __func__, id); + return -EAGAIN; + } + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + read_lock(&ul_wakeup_lock); + if (!bam_is_connected) { + read_unlock(&ul_wakeup_lock); + ul_wakeup(); + if (unlikely(in_global_reset == 1)) + return -EFAULT; + read_lock(&ul_wakeup_lock); + notify_all(BAM_DMUX_UL_CONNECTED, (unsigned long)(NULL)); + } + + /* 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__); + goto write_fail; + } + 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__); + goto write_fail2; + } + + 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__); + goto write_fail3; + } + pkt->skb = skb; + pkt->dma_address = dma_address; + pkt->is_cmd = 0; + set_tx_timestamp(pkt); + INIT_WORK(&pkt->work, bam_mux_write_done); + spin_lock_irqsave(&bam_tx_pool_spinlock, flags); + list_add_tail(&pkt->list_node, &bam_tx_pool); + rc = sps_transfer_one(bam_tx_pipe, dma_address, skb->len, + pkt, SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT); + if (rc) { + DMUX_LOG_KERR("%s sps_transfer_one failed rc=%d\n", + __func__, rc); + list_del(&pkt->list_node); + DBG_INC_TX_SPS_FAILURE_CNT(); + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + dma_unmap_single(NULL, pkt->dma_address, + pkt->skb->len, DMA_TO_DEVICE); + kfree(pkt); + if (new_skb) + dev_kfree_skb_any(new_skb); + } else { + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + spin_lock_irqsave(&bam_ch[id].lock, flags); + bam_ch[id].num_tx_pkts++; + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + } + ul_packet_written = 1; + read_unlock(&ul_wakeup_lock); + return rc; + +write_fail3: + kfree(pkt); +write_fail2: + if (new_skb) + dev_kfree_skb_any(new_skb); +write_fail: + read_unlock(&ul_wakeup_lock); + return -ENOMEM; +} + +int msm_bam_dmux_open(uint32_t id, void *priv, + void (*notify)(void *, int, unsigned long)) +{ + struct bam_mux_hdr *hdr; + unsigned long flags; + int rc = 0; + + DBG("%s: opening ch %d\n", __func__, id); + if (!bam_mux_initialized) { + DBG("%s: not inititialized\n", __func__); + return -ENODEV; + } + if (id >= BAM_DMUX_NUM_CHANNELS) { + pr_err("%s: invalid channel id %d\n", __func__, id); + return -EINVAL; + } + if (notify == NULL) { + pr_err("%s: notify function is NULL\n", __func__); + 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); + return -ENODEV; + } + + bam_ch[id].notify = notify; + bam_ch[id].priv = priv; + bam_ch[id].status |= BAM_CH_LOCAL_OPEN; + bam_ch[id].num_tx_pkts = 0; + bam_ch[id].use_wm = 0; + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + read_lock(&ul_wakeup_lock); + if (!bam_is_connected) { + read_unlock(&ul_wakeup_lock); + ul_wakeup(); + if (unlikely(in_global_reset == 1)) + return -EFAULT; + read_lock(&ul_wakeup_lock); + notify_all(BAM_DMUX_UL_CONNECTED, (unsigned long)(NULL)); + } + + 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)); + read_unlock(&ul_wakeup_lock); + +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; + + read_lock(&ul_wakeup_lock); + if (!bam_is_connected && !bam_ch_is_in_reset(id)) { + read_unlock(&ul_wakeup_lock); + ul_wakeup(); + if (unlikely(in_global_reset == 1)) + return -EFAULT; + read_lock(&ul_wakeup_lock); + notify_all(BAM_DMUX_UL_CONNECTED, (unsigned long)(NULL)); + } + + spin_lock_irqsave(&bam_ch[id].lock, flags); + bam_ch[id].notify = NULL; + bam_ch[id].priv = NULL; + bam_ch[id].status &= ~BAM_CH_LOCAL_OPEN; + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + if (bam_ch_is_in_reset(id)) { + read_unlock(&ul_wakeup_lock); + bam_ch[id].status &= ~BAM_CH_IN_RESET; + return 0; + } + + hdr = kmalloc(sizeof(struct bam_mux_hdr), GFP_ATOMIC); + if (hdr == NULL) { + pr_err("%s: hdr kmalloc failed. ch: %d\n", __func__, id); + read_unlock(&ul_wakeup_lock); + 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)); + read_unlock(&ul_wakeup_lock); + + DBG("%s: closed ch %d\n", __func__, id); + return rc; +} + +int msm_bam_dmux_is_ch_full(uint32_t id) +{ + unsigned long flags; + int ret; + + if (id >= BAM_DMUX_NUM_CHANNELS) + return -EINVAL; + + spin_lock_irqsave(&bam_ch[id].lock, flags); + bam_ch[id].use_wm = 1; + ret = bam_ch[id].num_tx_pkts >= HIGH_WATERMARK; + DBG("%s: ch %d num tx pkts=%d, HWM=%d\n", __func__, + id, bam_ch[id].num_tx_pkts, ret); + if (!bam_ch_is_local_open(id)) { + ret = -ENODEV; + pr_err("%s: port not open: %d\n", __func__, bam_ch[id].status); + } + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + return ret; +} + +int msm_bam_dmux_is_ch_low(uint32_t id) +{ + unsigned long flags; + int ret; + + if (id >= BAM_DMUX_NUM_CHANNELS) + return -EINVAL; + + spin_lock_irqsave(&bam_ch[id].lock, flags); + bam_ch[id].use_wm = 1; + ret = bam_ch[id].num_tx_pkts <= LOW_WATERMARK; + DBG("%s: ch %d num tx pkts=%d, LWM=%d\n", __func__, + id, bam_ch[id].num_tx_pkts, ret); + if (!bam_ch_is_local_open(id)) { + ret = -ENODEV; + pr_err("%s: port not open: %d\n", __func__, bam_ch[id].status); + } + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + return ret; +} + +static void rx_switch_to_interrupt_mode(void) +{ + struct sps_connect cur_rx_conn; + struct sps_iovec iov; + struct rx_pkt_info *info; + int ret; + + /* + * Attempt to enable interrupts - if this fails, + * continue polling and we will retry later. + */ + ret = sps_get_config(bam_rx_pipe, &cur_rx_conn); + if (ret) { + pr_err("%s: sps_get_config() failed %d\n", __func__, ret); + goto fail; + } + + rx_register_event.options = SPS_O_EOT; + ret = sps_register_event(bam_rx_pipe, &rx_register_event); + if (ret) { + pr_err("%s: sps_register_event() failed %d\n", __func__, ret); + goto fail; + } + + cur_rx_conn.options = SPS_O_AUTO_ENABLE | + SPS_O_EOT | SPS_O_ACK_TRANSFERS; + ret = sps_set_config(bam_rx_pipe, &cur_rx_conn); + if (ret) { + pr_err("%s: sps_set_config() failed %d\n", __func__, ret); + goto fail; + } + polling_mode = 0; + release_wakelock(); + + /* handle any rx packets before interrupt was enabled */ + while (bam_connection_is_active && !polling_mode) { + ret = sps_get_iovec(bam_rx_pipe, &iov); + if (ret) { + pr_err("%s: sps_get_iovec failed %d\n", + __func__, ret); + break; + } + if (iov.addr == 0) + break; + + mutex_lock(&bam_rx_pool_mutexlock); + if (unlikely(list_empty(&bam_rx_pool))) { + mutex_unlock(&bam_rx_pool_mutexlock); + continue; + } + info = list_first_entry(&bam_rx_pool, struct rx_pkt_info, + list_node); + list_del(&info->list_node); + --bam_rx_pool_len; + mutex_unlock(&bam_rx_pool_mutexlock); + if (info->dma_address != iov.addr) + DMUX_LOG_KERR("%s: iovec %p != dma %p\n", + __func__, + (void *)info->dma_address, (void *)iov.addr); + handle_bam_mux_cmd(&info->work); + } + return; + +fail: + pr_err("%s: reverting to polling\n", __func__); + queue_work_on(0, bam_mux_rx_workqueue, &rx_timer_work); +} + +static void rx_timer_work_func(struct work_struct *work) +{ + struct sps_iovec iov; + struct rx_pkt_info *info; + int inactive_cycles = 0; + int ret; + + while (bam_connection_is_active) { /* timer loop */ + ++inactive_cycles; + while (bam_connection_is_active) { /* deplete queue loop */ + if (in_global_reset) + return; + + ret = sps_get_iovec(bam_rx_pipe, &iov); + if (ret) { + pr_err("%s: sps_get_iovec failed %d\n", + __func__, ret); + break; + } + if (iov.addr == 0) + break; + inactive_cycles = 0; + mutex_lock(&bam_rx_pool_mutexlock); + if (unlikely(list_empty(&bam_rx_pool))) { + mutex_unlock(&bam_rx_pool_mutexlock); + continue; + } + info = list_first_entry(&bam_rx_pool, + struct rx_pkt_info, list_node); + --bam_rx_pool_len; + list_del(&info->list_node); + mutex_unlock(&bam_rx_pool_mutexlock); + handle_bam_mux_cmd(&info->work); + } + + if (inactive_cycles == POLLING_INACTIVITY) { + rx_switch_to_interrupt_mode(); + break; + } + + usleep_range(POLLING_MIN_SLEEP, POLLING_MAX_SLEEP); + } +} + +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); + + if (in_global_reset) + return; + + 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); + else + dma_unmap_single(NULL, pkt->dma_address, + pkt->len, + DMA_TO_DEVICE); + queue_work(bam_mux_tx_workqueue, &pkt->work); + break; + default: + pr_err("%s: recieved unexpected event id %d\n", __func__, + notify->event_id); + } +} + +static void bam_mux_rx_notify(struct sps_event_notify *notify) +{ + int ret; + struct sps_connect cur_rx_conn; + + DBG("%s: event %d notified\n", __func__, notify->event_id); + + if (in_global_reset) + return; + + switch (notify->event_id) { + case SPS_EVENT_EOT: + /* attempt to disable interrupts in this pipe */ + if (!polling_mode) { + ret = sps_get_config(bam_rx_pipe, &cur_rx_conn); + if (ret) { + pr_err("%s: sps_get_config() failed %d, interrupts" + " not disabled\n", __func__, ret); + break; + } + cur_rx_conn.options = SPS_O_AUTO_ENABLE | + SPS_O_ACK_TRANSFERS | SPS_O_POLL; + ret = sps_set_config(bam_rx_pipe, &cur_rx_conn); + if (ret) { + pr_err("%s: sps_set_config() failed %d, interrupts" + " not disabled\n", __func__, ret); + break; + } + grab_wakelock(); + polling_mode = 1; + /* + * run on core 0 so that netif_rx() in rmnet uses only + * one queue + */ + queue_work_on(0, bam_mux_rx_workqueue, &rx_timer_work); + } + 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; +} + +static int debug_ul_pkt_cnt(char *buf, int max) +{ + struct list_head *p; + unsigned long flags; + int n = 0; + + spin_lock_irqsave(&bam_tx_pool_spinlock, flags); + __list_for_each(p, &bam_tx_pool) { + ++n; + } + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + + return scnprintf(buf, max, "Number of UL packets in flight: %d\n", n); +} + +static int debug_stats(char *buf, int max) +{ + int i = 0; + + i += scnprintf(buf + i, max - i, + "skb read cnt: %u\n" + "skb write cnt: %u\n" + "skb copy cnt: %u\n" + "skb copy bytes: %u\n" + "sps tx failures: %u\n" + "sps tx stalls: %u\n" + "rx queue len: %d\n" + "a2 ack out cnt: %d\n" + "a2 ack in cnt: %d\n" + "a2 pwr cntl in: %d\n", + bam_dmux_read_cnt, + bam_dmux_write_cnt, + bam_dmux_write_cpy_cnt, + bam_dmux_write_cpy_bytes, + bam_dmux_tx_sps_failure_cnt, + bam_dmux_tx_stall_cnt, + bam_rx_pool_len, + atomic_read(&bam_dmux_ack_out_cnt), + atomic_read(&bam_dmux_ack_in_cnt), + atomic_read(&bam_dmux_a2_pwr_cntl_in_cnt) + ); + + return i; +} + +static int debug_log(char *buff, int max, loff_t *ppos) +{ + unsigned long flags; + int i = 0; + + if (bam_dmux_state_logging_disabled) { + i += scnprintf(buff - i, max - i, "Logging disabled\n"); + return i; + } + + if (*ppos == 0) { + i += scnprintf(buff - i, max - i, + " timestamp FLAGS [Message]\n" + "FLAGS:\n" + "\tD: 1 = Power collapse disabled\n" + "\tR: 1 = in global reset\n" + "\tP: 1 = BAM is powered up\n" + "\tA: 1 = BAM initialized and ready for data\n" + "\n" + "\tV: 1 = Uplink vote for power\n" + "\tU: 1 = Uplink active\n" + "\tW: 1 = Uplink Wait-for-ack\n" + "\tA: 1 = Uplink ACK received\n" + "\t#: >=1 On-demand uplink vote\n" + "\tD: 1 = Disconnect ACK active\n" + ); + buff += i; + } + + spin_lock_irqsave(&bam_dmux_logging_spinlock, flags); + while (kfifo_len(&bam_dmux_state_log) + && (i + LOG_MESSAGE_MAX_SIZE) < max) { + int k_len; + k_len = kfifo_out(&bam_dmux_state_log, + buff, LOG_MESSAGE_MAX_SIZE); + if (k_len != LOG_MESSAGE_MAX_SIZE) { + pr_err("%s: retrieve failure %d\n", __func__, k_len); + break; + } + + /* keep non-null portion of string and add line break */ + k_len = strnlen(buff, LOG_MESSAGE_MAX_SIZE); + buff += k_len; + i += k_len; + if (k_len && *(buff - 1) != '\n') { + *buff++ = '\n'; + ++i; + } + } + spin_unlock_irqrestore(&bam_dmux_logging_spinlock, 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 ssize_t debug_read_multiple(struct file *file, char __user *buff, + size_t count, loff_t *ppos) +{ + int (*util_func)(char *buf, int max, loff_t *) = file->private_data; + char *buffer; + int bsize; + + buffer = kmalloc(count, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + bsize = util_func(buffer, count, ppos); + + if (bsize >= 0) { + if (copy_to_user(buff, buffer, bsize)) { + kfree(buffer); + return -EFAULT; + } + *ppos += bsize; + } + 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_multiple = { + .read = debug_read_multiple, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max)) +{ + struct dentry *file; + + file = debugfs_create_file(name, mode, dent, fill, &debug_ops); + if (IS_ERR(file)) + pr_err("%s: debugfs create failed %d\n", __func__, + (int)PTR_ERR(file)); +} + +static void debug_create_multiple(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max, loff_t *ppos)) +{ + struct dentry *file; + + file = debugfs_create_file(name, mode, dent, fill, &debug_ops_multiple); + if (IS_ERR(file)) + pr_err("%s: debugfs create failed %d\n", __func__, + (int)PTR_ERR(file)); +} +#endif + +static void notify_all(int event, unsigned long data) +{ + int i; + struct list_head *temp; + struct outside_notify_func *func; + + for (i = 0; i < BAM_DMUX_NUM_CHANNELS; ++i) { + if (bam_ch_is_open(i)) { + bam_ch[i].notify(bam_ch[i].priv, event, data); + bam_dmux_log("%s: cid=%d, event=%d, data=%lu\n", + __func__, i, event, data); + } + } + + __list_for_each(temp, &bam_other_notify_funcs) { + func = container_of(temp, struct outside_notify_func, + list_node); + func->notify(func->priv, event, data); + } +} + +static void kickoff_ul_wakeup_func(struct work_struct *work) +{ + read_lock(&ul_wakeup_lock); + if (!bam_is_connected) { + read_unlock(&ul_wakeup_lock); + ul_wakeup(); + if (unlikely(in_global_reset == 1)) + return; + read_lock(&ul_wakeup_lock); + ul_packet_written = 1; + notify_all(BAM_DMUX_UL_CONNECTED, (unsigned long)(NULL)); + } + read_unlock(&ul_wakeup_lock); +} + +int msm_bam_dmux_kickoff_ul_wakeup(void) +{ + int is_connected; + + read_lock(&ul_wakeup_lock); + ul_packet_written = 1; + is_connected = bam_is_connected; + if (!is_connected) + queue_work(bam_mux_tx_workqueue, &kickoff_ul_wakeup); + read_unlock(&ul_wakeup_lock); + + return is_connected; +} + +static void power_vote(int vote) +{ + bam_dmux_log("%s: curr=%d, vote=%d\n", __func__, + bam_dmux_uplink_vote, vote); + + if (bam_dmux_uplink_vote == vote) + bam_dmux_log("%s: warning - duplicate power vote\n", __func__); + + bam_dmux_uplink_vote = vote; + if (vote) + smsm_change_state(SMSM_APPS_STATE, 0, SMSM_A2_POWER_CONTROL); + else + smsm_change_state(SMSM_APPS_STATE, SMSM_A2_POWER_CONTROL, 0); +} + +/* + * @note: Must be called with ul_wakeup_lock locked. + */ +static inline void ul_powerdown(void) +{ + bam_dmux_log("%s: powerdown\n", __func__); + verify_tx_queue_is_empty(__func__); + + if (a2_pc_disabled) { + wait_for_dfab = 1; + INIT_COMPLETION(dfab_unvote_completion); + release_wakelock(); + } else { + wait_for_ack = 1; + INIT_COMPLETION(ul_wakeup_ack_completion); + power_vote(0); + } + bam_is_connected = 0; + notify_all(BAM_DMUX_UL_DISCONNECTED, (unsigned long)(NULL)); +} + +static inline void ul_powerdown_finish(void) +{ + if (a2_pc_disabled && wait_for_dfab) { + unvote_dfab(); + complete_all(&dfab_unvote_completion); + wait_for_dfab = 0; + } +} + +/* + * Votes for UL power and returns current power state. + * + * @returns true if currently connected + */ +int msm_bam_dmux_ul_power_vote(void) +{ + int is_connected; + + read_lock(&ul_wakeup_lock); + atomic_inc(&ul_ondemand_vote); + is_connected = bam_is_connected; + if (!is_connected) + queue_work(bam_mux_tx_workqueue, &kickoff_ul_wakeup); + read_unlock(&ul_wakeup_lock); + + return is_connected; +} + +/* + * Unvotes for UL power. + * + * @returns true if vote count is 0 (UL shutdown possible) + */ +int msm_bam_dmux_ul_power_unvote(void) +{ + int vote; + + read_lock(&ul_wakeup_lock); + vote = atomic_dec_return(&ul_ondemand_vote); + if (unlikely(vote) < 0) + DMUX_LOG_KERR("%s: invalid power vote %d\n", __func__, vote); + read_unlock(&ul_wakeup_lock); + + return vote == 0; +} + +int msm_bam_dmux_reg_notify(void *priv, + void (*notify)(void *priv, int event_type, + unsigned long data)) +{ + struct outside_notify_func *func; + + if (!notify) + return -EINVAL; + + func = kmalloc(sizeof(struct outside_notify_func), GFP_KERNEL); + if (!func) + return -ENOMEM; + + func->notify = notify; + func->priv = priv; + list_add(&func->list_node, &bam_other_notify_funcs); + + return 0; +} + +static void ul_timeout(struct work_struct *work) +{ + unsigned long flags; + int ret; + + if (in_global_reset) + return; + ret = write_trylock_irqsave(&ul_wakeup_lock, flags); + if (!ret) { /* failed to grab lock, reschedule and bail */ + schedule_delayed_work(&ul_timeout_work, + msecs_to_jiffies(UL_TIMEOUT_DELAY)); + return; + } + if (bam_is_connected) { + if (!ul_packet_written) { + spin_lock(&bam_tx_pool_spinlock); + if (!list_empty(&bam_tx_pool)) { + struct tx_pkt_info *info; + + info = list_first_entry(&bam_tx_pool, + struct tx_pkt_info, list_node); + DMUX_LOG_KERR("%s: UL delayed ts=%u.%09lu\n", + __func__, info->ts_sec, info->ts_nsec); + DBG_INC_TX_STALL_CNT(); + ul_packet_written = 1; + } + spin_unlock(&bam_tx_pool_spinlock); + } + + if (ul_packet_written || atomic_read(&ul_ondemand_vote)) { + bam_dmux_log("%s: pkt written %d\n", + __func__, ul_packet_written); + ul_packet_written = 0; + schedule_delayed_work(&ul_timeout_work, + msecs_to_jiffies(UL_TIMEOUT_DELAY)); + } else { + ul_powerdown(); + } + } + write_unlock_irqrestore(&ul_wakeup_lock, flags); + ul_powerdown_finish(); +} + +static int ssrestart_check(void) +{ + DMUX_LOG_KERR("%s: modem timeout: BAM DMUX disabled\n", __func__); + in_global_reset = 1; + if (get_restart_level() <= RESET_SOC) + DMUX_LOG_KERR("%s: ssrestart not enabled\n", __func__); + return 1; +} + +static void ul_wakeup(void) +{ + int ret; + int do_vote_dfab = 0; + + mutex_lock(&wakeup_lock); + if (bam_is_connected) { /* bam got connected before lock grabbed */ + bam_dmux_log("%s Already awake\n", __func__); + mutex_unlock(&wakeup_lock); + return; + } + + /* + * if someone is voting for UL before bam is inited (modem up first + * time), set flag for init to kickoff ul wakeup once bam is inited + */ + mutex_lock(&delayed_ul_vote_lock); + if (unlikely(!bam_mux_initialized)) { + need_delayed_ul_vote = 1; + mutex_unlock(&delayed_ul_vote_lock); + mutex_unlock(&wakeup_lock); + return; + } + mutex_unlock(&delayed_ul_vote_lock); + + if (a2_pc_disabled) { + /* + * don't grab the wakelock the first time because it is + * already grabbed when a2 powers on + */ + if (likely(a2_pc_disabled_wakelock_skipped)) { + grab_wakelock(); + do_vote_dfab = 1; /* vote must occur after wait */ + } else { + a2_pc_disabled_wakelock_skipped = 1; + } + if (wait_for_dfab) { + ret = wait_for_completion_timeout( + &dfab_unvote_completion, HZ); + BUG_ON(ret == 0); + } + if (likely(do_vote_dfab)) + vote_dfab(); + schedule_delayed_work(&ul_timeout_work, + msecs_to_jiffies(UL_TIMEOUT_DELAY)); + bam_is_connected = 1; + mutex_unlock(&wakeup_lock); + return; + } + + /* + * must wait for the previous power down request to have been acked + * chances are it already came in and this will just fall through + * instead of waiting + */ + if (wait_for_ack) { + bam_dmux_log("%s waiting for previous ack\n", __func__); + ret = wait_for_completion_timeout( + &ul_wakeup_ack_completion, HZ); + wait_for_ack = 0; + if (unlikely(ret == 0) && ssrestart_check()) { + mutex_unlock(&wakeup_lock); + bam_dmux_log("%s timeout previous ack\n", __func__); + return; + } + } + INIT_COMPLETION(ul_wakeup_ack_completion); + power_vote(1); + bam_dmux_log("%s waiting for wakeup ack\n", __func__); + ret = wait_for_completion_timeout(&ul_wakeup_ack_completion, HZ); + if (unlikely(ret == 0) && ssrestart_check()) { + mutex_unlock(&wakeup_lock); + bam_dmux_log("%s timeout wakeup ack\n", __func__); + return; + } + bam_dmux_log("%s waiting completion\n", __func__); + ret = wait_for_completion_timeout(&bam_connection_completion, HZ); + if (unlikely(ret == 0) && ssrestart_check()) { + mutex_unlock(&wakeup_lock); + bam_dmux_log("%s timeout power on\n", __func__); + return; + } + + bam_is_connected = 1; + bam_dmux_log("%s complete\n", __func__); + schedule_delayed_work(&ul_timeout_work, + msecs_to_jiffies(UL_TIMEOUT_DELAY)); + mutex_unlock(&wakeup_lock); +} + +static void reconnect_to_bam(void) +{ + int i; + + in_global_reset = 0; + vote_dfab(); + i = sps_device_reset(a2_device_handle); + if (i) + pr_err("%s: device reset failed rc = %d\n", __func__, i); + i = sps_connect(bam_tx_pipe, &tx_connection); + if (i) + pr_err("%s: tx connection failed rc = %d\n", __func__, i); + i = sps_connect(bam_rx_pipe, &rx_connection); + if (i) + pr_err("%s: rx connection failed rc = %d\n", __func__, i); + i = sps_register_event(bam_tx_pipe, &tx_register_event); + if (i) + pr_err("%s: tx event reg failed rc = %d\n", __func__, i); + i = sps_register_event(bam_rx_pipe, &rx_register_event); + if (i) + pr_err("%s: rx event reg failed rc = %d\n", __func__, i); + + bam_connection_is_active = 1; + + if (polling_mode) + rx_switch_to_interrupt_mode(); + + toggle_apps_ack(); + complete_all(&bam_connection_completion); + queue_rx(); +} + +static void disconnect_to_bam(void) +{ + struct list_head *node; + struct rx_pkt_info *info; + unsigned long flags; + + bam_connection_is_active = 0; + + /* handle disconnect during active UL */ + write_lock_irqsave(&ul_wakeup_lock, flags); + if (bam_is_connected) { + bam_dmux_log("%s: UL active - forcing powerdown\n", __func__); + ul_powerdown(); + } + write_unlock_irqrestore(&ul_wakeup_lock, flags); + ul_powerdown_finish(); + + /* tear down BAM connection */ + INIT_COMPLETION(bam_connection_completion); + sps_disconnect(bam_tx_pipe); + sps_disconnect(bam_rx_pipe); + unvote_dfab(); + __memzero(rx_desc_mem_buf.base, rx_desc_mem_buf.size); + __memzero(tx_desc_mem_buf.base, tx_desc_mem_buf.size); + + mutex_lock(&bam_rx_pool_mutexlock); + while (!list_empty(&bam_rx_pool)) { + node = bam_rx_pool.next; + list_del(node); + info = container_of(node, struct rx_pkt_info, list_node); + dma_unmap_single(NULL, info->dma_address, BUFFER_SIZE, + DMA_FROM_DEVICE); + dev_kfree_skb_any(info->skb); + kfree(info); + } + bam_rx_pool_len = 0; + mutex_unlock(&bam_rx_pool_mutexlock); + + if (disconnect_ack) + toggle_apps_ack(); + + verify_tx_queue_is_empty(__func__); +} + +static void vote_dfab(void) +{ + int rc; + + bam_dmux_log("%s\n", __func__); + mutex_lock(&dfab_status_lock); + if (dfab_is_on) { + bam_dmux_log("%s: dfab is already on\n", __func__); + mutex_unlock(&dfab_status_lock); + return; + } + rc = clk_prepare_enable(dfab_clk); + if (rc) + DMUX_LOG_KERR("bam_dmux vote for dfab failed rc = %d\n", rc); + rc = clk_prepare_enable(xo_clk); + if (rc) + DMUX_LOG_KERR("bam_dmux vote for xo failed rc = %d\n", rc); + dfab_is_on = 1; + mutex_unlock(&dfab_status_lock); +} + +static void unvote_dfab(void) +{ + bam_dmux_log("%s\n", __func__); + mutex_lock(&dfab_status_lock); + if (!dfab_is_on) { + DMUX_LOG_KERR("%s: dfab is already off\n", __func__); + dump_stack(); + mutex_unlock(&dfab_status_lock); + return; + } + clk_disable_unprepare(dfab_clk); + clk_disable_unprepare(xo_clk); + dfab_is_on = 0; + mutex_unlock(&dfab_status_lock); +} + +/* reference counting wrapper around wakelock */ +static void grab_wakelock(void) +{ + unsigned long flags; + + spin_lock_irqsave(&wakelock_reference_lock, flags); + bam_dmux_log("%s: ref count = %d\n", __func__, + wakelock_reference_count); + if (wakelock_reference_count == 0) + wake_lock(&bam_wakelock); + ++wakelock_reference_count; + spin_unlock_irqrestore(&wakelock_reference_lock, flags); +} + +static void release_wakelock(void) +{ + unsigned long flags; + + spin_lock_irqsave(&wakelock_reference_lock, flags); + if (wakelock_reference_count == 0) { + DMUX_LOG_KERR("%s: bam_dmux wakelock not locked\n", __func__); + dump_stack(); + spin_unlock_irqrestore(&wakelock_reference_lock, flags); + return; + } + bam_dmux_log("%s: ref count = %d\n", __func__, + wakelock_reference_count); + --wakelock_reference_count; + if (wakelock_reference_count == 0) + wake_unlock(&bam_wakelock); + spin_unlock_irqrestore(&wakelock_reference_lock, flags); +} + +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data) +{ + int i; + struct list_head *node; + struct tx_pkt_info *info; + int temp_remote_status; + unsigned long flags; + + if (code != SUBSYS_AFTER_SHUTDOWN) + return NOTIFY_DONE; + + bam_dmux_log("%s: begin\n", __func__); + in_global_reset = 1; + + /* Handle uplink Powerdown */ + write_lock_irqsave(&ul_wakeup_lock, flags); + if (bam_is_connected) { + ul_powerdown(); + wait_for_ack = 0; + } + /* + * if modem crash during ul_wakeup(), power_vote is 1, needs to be + * reset to 0. harmless if bam_is_connected check above passes + */ + power_vote(0); + write_unlock_irqrestore(&ul_wakeup_lock, flags); + ul_powerdown_finish(); + a2_pc_disabled = 0; + a2_pc_disabled_wakelock_skipped = 0; + disconnect_ack = 0; + + /* Cleanup Channel States */ + for (i = 0; i < BAM_DMUX_NUM_CHANNELS; ++i) { + temp_remote_status = bam_ch_is_remote_open(i); + bam_ch[i].status &= ~BAM_CH_REMOTE_OPEN; + bam_ch[i].num_tx_pkts = 0; + if (bam_ch_is_local_open(i)) + bam_ch[i].status |= BAM_CH_IN_RESET; + if (temp_remote_status) { + platform_device_unregister(bam_ch[i].pdev); + bam_ch[i].pdev = platform_device_alloc( + bam_ch[i].name, 2); + } + } + + /* Cleanup pending UL data */ + spin_lock_irqsave(&bam_tx_pool_spinlock, flags); + while (!list_empty(&bam_tx_pool)) { + node = bam_tx_pool.next; + list_del(node); + info = container_of(node, struct tx_pkt_info, + list_node); + if (!info->is_cmd) { + dma_unmap_single(NULL, info->dma_address, + info->skb->len, + DMA_TO_DEVICE); + dev_kfree_skb_any(info->skb); + } else { + dma_unmap_single(NULL, info->dma_address, + info->len, + DMA_TO_DEVICE); + kfree(info->skb); + } + kfree(info); + } + spin_unlock_irqrestore(&bam_tx_pool_spinlock, flags); + + bam_dmux_log("%s: complete\n", __func__); + return NOTIFY_DONE; +} + +static int bam_init(void) +{ + u32 h; + dma_addr_t dma_addr; + int ret; + void *a2_virt_addr; + int skip_iounmap = 0; + + vote_dfab(); + /* 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 ioremap_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.options = SPS_BAM_OPT_IRQ_WAKEUP; + a2_props.num_pipes = A2_NUM_PIPES; + a2_props.summing_threshold = A2_SUMMING_THRESHOLD; + if (cpu_is_msm9615()) + a2_props.manage = SPS_BAM_MGR_DEVICE_REMOTE; + /* 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; + } + a2_device_handle = h; + + bam_tx_pipe = sps_alloc_endpoint(); + if (bam_tx_pipe == NULL) { + pr_err("%s: tx alloc endpoint failed\n", __func__); + ret = -ENOMEM; + goto tx_alloc_endpoint_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_get_config_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 rx_alloc_endpoint_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; + 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; + } + + rx_register_event.options = SPS_O_EOT; + rx_register_event.mode = SPS_TRIGGER_CALLBACK; + rx_register_event.xfer_done = NULL; + rx_register_event.callback = bam_mux_rx_notify; + rx_register_event.user = NULL; + ret = sps_register_event(bam_rx_pipe, &rx_register_event); + if (ret < 0) { + pr_err("%s: tx register event error %d\n", __func__, ret); + goto rx_event_reg_failed; + } + + mutex_lock(&delayed_ul_vote_lock); + bam_mux_initialized = 1; + if (need_delayed_ul_vote) { + need_delayed_ul_vote = 0; + msm_bam_dmux_kickoff_ul_wakeup(); + } + mutex_unlock(&delayed_ul_vote_lock); + toggle_apps_ack(); + bam_connection_is_active = 1; + complete_all(&bam_connection_completion); + queue_rx(); + return 0; + +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: +rx_get_config_failed: + sps_free_endpoint(bam_rx_pipe); +rx_alloc_endpoint_failed: + sps_disconnect(bam_tx_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_alloc_endpoint_failed: + sps_deregister_bam_device(h); + /* + * sps_deregister_bam_device() calls iounmap. calling iounmap on the + * same handle below will cause a crash, so skip it if we've freed + * the handle here. + */ + skip_iounmap = 1; +register_bam_failed: + if (!skip_iounmap) + iounmap(a2_virt_addr); +ioremap_failed: + /*destroy_workqueue(bam_mux_workqueue);*/ + return ret; +} + +static int bam_init_fallback(void) +{ + u32 h; + int ret; + void *a2_virt_addr; + + unvote_dfab(); + /* 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 ioremap_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.options = SPS_BAM_OPT_IRQ_WAKEUP; + a2_props.num_pipes = A2_NUM_PIPES; + a2_props.summing_threshold = A2_SUMMING_THRESHOLD; + if (cpu_is_msm9615()) + a2_props.manage = SPS_BAM_MGR_DEVICE_REMOTE; + 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; + } + a2_device_handle = h; + + mutex_lock(&delayed_ul_vote_lock); + bam_mux_initialized = 1; + if (need_delayed_ul_vote) { + need_delayed_ul_vote = 0; + msm_bam_dmux_kickoff_ul_wakeup(); + } + mutex_unlock(&delayed_ul_vote_lock); + toggle_apps_ack(); + + return 0; + +register_bam_failed: + iounmap(a2_virt_addr); +ioremap_failed: + return ret; +} + +static void msm9615_bam_init(void) +{ + int ret = 0; + + ret = bam_init(); + if (ret) { + ret = bam_init_fallback(); + if (ret) + pr_err("%s: bam init fallback failed: %d", + __func__, ret); + } +} + +static void toggle_apps_ack(void) +{ + static unsigned int clear_bit; /* 0 = set the bit, else clear bit */ + + bam_dmux_log("%s: apps ack %d->%d\n", __func__, + clear_bit & 0x1, ~clear_bit & 0x1); + smsm_change_state(SMSM_APPS_STATE, + clear_bit & SMSM_A2_POWER_CONTROL_ACK, + ~clear_bit & SMSM_A2_POWER_CONTROL_ACK); + clear_bit = ~clear_bit; + DBG_INC_ACK_OUT_CNT(); +} + +static void bam_dmux_smsm_cb(void *priv, uint32_t old_state, uint32_t new_state) +{ + static int last_processed_state; + + mutex_lock(&smsm_cb_lock); + bam_dmux_power_state = new_state & SMSM_A2_POWER_CONTROL ? 1 : 0; + DBG_INC_A2_POWER_CONTROL_IN_CNT(); + bam_dmux_log("%s: 0x%08x -> 0x%08x\n", __func__, old_state, + new_state); + if (last_processed_state == (new_state & SMSM_A2_POWER_CONTROL)) { + bam_dmux_log("%s: already processed this state\n", __func__); + mutex_unlock(&smsm_cb_lock); + return; + } + + last_processed_state = new_state & SMSM_A2_POWER_CONTROL; + + if (bam_mux_initialized && new_state & SMSM_A2_POWER_CONTROL) { + bam_dmux_log("%s: reconnect\n", __func__); + grab_wakelock(); + reconnect_to_bam(); + } else if (bam_mux_initialized && + !(new_state & SMSM_A2_POWER_CONTROL)) { + bam_dmux_log("%s: disconnect\n", __func__); + disconnect_to_bam(); + release_wakelock(); + } else if (new_state & SMSM_A2_POWER_CONTROL) { + bam_dmux_log("%s: init\n", __func__); + grab_wakelock(); + if (cpu_is_msm9615()) + msm9615_bam_init(); + else + bam_init(); + } else { + bam_dmux_log("%s: bad state change\n", __func__); + pr_err("%s: unsupported state change\n", __func__); + } + mutex_unlock(&smsm_cb_lock); + +} + +static void bam_dmux_smsm_ack_cb(void *priv, uint32_t old_state, + uint32_t new_state) +{ + DBG_INC_ACK_IN_CNT(); + bam_dmux_log("%s: 0x%08x -> 0x%08x\n", __func__, old_state, + new_state); + complete_all(&ul_wakeup_ack_completion); +} + +static int bam_dmux_probe(struct platform_device *pdev) +{ + int rc; + + DBG("%s probe called\n", __func__); + if (bam_mux_initialized) + return 0; + + xo_clk = clk_get(&pdev->dev, "xo"); + if (IS_ERR(xo_clk)) { + pr_err("%s: did not get xo clock\n", __func__); + return PTR_ERR(xo_clk); + } + dfab_clk = clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(dfab_clk)) { + pr_err("%s: did not get dfab clock\n", __func__); + return -EFAULT; + } + + rc = clk_set_rate(dfab_clk, 64000000); + if (rc) + pr_err("%s: unable to set dfab clock rate\n", __func__); + + /* + * setup the workqueue so that it can be pinned to core 0 and not + * block the watchdog pet function, so that netif_rx() in rmnet + * only uses one queue. + */ + bam_mux_rx_workqueue = alloc_workqueue("bam_dmux_rx", + WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 1); + 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); + scnprintf(bam_ch[rc].name, BAM_DMUX_CH_NAME_MAX_LEN, + "bam_dmux_ch_%d", rc); + /* bus 2, ie a2 stream 2 */ + bam_ch[rc].pdev = platform_device_alloc(bam_ch[rc].name, 2); + if (!bam_ch[rc].pdev) { + pr_err("%s: platform device alloc failed\n", __func__); + destroy_workqueue(bam_mux_rx_workqueue); + destroy_workqueue(bam_mux_tx_workqueue); + return -ENOMEM; + } + } + + init_completion(&ul_wakeup_ack_completion); + init_completion(&bam_connection_completion); + init_completion(&dfab_unvote_completion); + INIT_DELAYED_WORK(&ul_timeout_work, ul_timeout); + wake_lock_init(&bam_wakelock, WAKE_LOCK_SUSPEND, "bam_dmux_wakelock"); + + rc = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_A2_POWER_CONTROL, + bam_dmux_smsm_cb, NULL); + + if (rc) { + destroy_workqueue(bam_mux_rx_workqueue); + destroy_workqueue(bam_mux_tx_workqueue); + pr_err("%s: smsm cb register failed, rc: %d\n", __func__, rc); + return -ENOMEM; + } + + rc = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_A2_POWER_CONTROL_ACK, + bam_dmux_smsm_ack_cb, NULL); + + if (rc) { + destroy_workqueue(bam_mux_rx_workqueue); + destroy_workqueue(bam_mux_tx_workqueue); + smsm_state_cb_deregister(SMSM_MODEM_STATE, + SMSM_A2_POWER_CONTROL, + bam_dmux_smsm_cb, NULL); + pr_err("%s: smsm ack cb register failed, rc: %d\n", __func__, + rc); + for (rc = 0; rc < BAM_DMUX_NUM_CHANNELS; ++rc) + platform_device_put(bam_ch[rc].pdev); + return -ENOMEM; + } + + if (smsm_get_state(SMSM_MODEM_STATE) & SMSM_A2_POWER_CONTROL) + bam_dmux_smsm_cb(NULL, 0, smsm_get_state(SMSM_MODEM_STATE)); + + 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) +{ + int ret; +#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); + debug_create("ul_pkt_cnt", 0444, dent, debug_ul_pkt_cnt); + debug_create("stats", 0444, dent, debug_stats); + debug_create_multiple("log", 0444, dent, debug_log); + } +#endif + ret = kfifo_alloc(&bam_dmux_state_log, PAGE_SIZE, GFP_KERNEL); + if (ret) { + pr_err("%s: failed to allocate log %d\n", __func__, ret); + bam_dmux_state_logging_disabled = 1; + } + + subsys_notif_register_notifier("modem", &restart_notifier); + return platform_driver_register(&bam_dmux_driver); +} + +late_initcall(bam_dmux_init); /* needs to init after SMD */ +MODULE_DESCRIPTION("MSM BAM DMUX"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/bms-batterydata-desay.c b/arch/arm/mach-msm/bms-batterydata-desay.c new file mode 100644 index 0000000000000000000000000000000000000000..f362a72f325ef7f737bdcb25dcb360d8a1b3ca55 --- /dev/null +++ b/arch/arm/mach-msm/bms-batterydata-desay.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 desay_5200_fcc_temp = { + .x = {-20, 0, 25, 40}, + .y = {5690, 5722, 5722, 5727}, + .cols = 4 +}; + +static struct single_row_lut desay_5200_fcc_sf = { + .x = {0}, + .y = {100}, + .cols = 1 +}; + +static struct pc_temp_ocv_lut desay_5200_pc_temp_ocv = { + .rows = 29, + .cols = 4, + .temp = {-20, 0, 25, 40}, + .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 = { + {4185, 4184, 4181, 4178}, + {4103, 4117, 4120, 4119}, + {4044, 4067, 4074, 4073}, + {3987, 4019, 4031, 4030}, + {3941, 3974, 3992, 3992}, + {3902, 3936, 3958, 3957}, + {3866, 3901, 3926, 3926}, + {3835, 3870, 3891, 3896}, + {3811, 3842, 3855, 3858}, + {3792, 3818, 3827, 3827}, + {3776, 3795, 3806, 3806}, + {3762, 3778, 3789, 3790}, + {3748, 3765, 3777, 3777}, + {3735, 3752, 3767, 3765}, + {3720, 3739, 3756, 3754}, + {3704, 3726, 3743, 3736}, + {3685, 3712, 3723, 3716}, + {3664, 3697, 3695, 3689}, + {3623, 3672, 3669, 3664}, + {3611, 3666, 3666, 3661}, + {3597, 3659, 3662, 3658}, + {3579, 3648, 3657, 3653}, + {3559, 3630, 3644, 3639}, + {3532, 3600, 3612, 3606}, + {3497, 3558, 3565, 3559}, + {3450, 3500, 3504, 3498}, + {3380, 3417, 3421, 3416}, + {3265, 3287, 3296, 3293}, + {3000, 3000, 3000, 3000} + }, +}; + +static struct sf_lut desay_5200_pc_sf = { + .rows = 1, + .cols = 1, + /* row_entries are cycles here */ + .row_entries = {0}, + .percent = {100}, + .sf = { + {100} + }, +}; + +struct pm8921_bms_battery_data desay_5200_data = { + .fcc = 5200, + .fcc_temp_lut = &desay_5200_fcc_temp, + .fcc_sf_lut = &desay_5200_fcc_sf, + .pc_temp_ocv_lut = &desay_5200_pc_temp_ocv, + .pc_sf_lut = &desay_5200_pc_sf, + .default_rbatt_mohm = 156, +}; diff --git a/arch/arm/mach-msm/bms-batterydata.c b/arch/arm/mach-msm/bms-batterydata.c new file mode 100644 index 0000000000000000000000000000000000000000..77e7dab816b3050d2cf492a24c9844dd53d3152d --- /dev/null +++ b/arch/arm/mach-msm/bms-batterydata.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 palladium_1500_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 palladium_1500_fcc_sf = { + .x = {100, 200, 300, 400, 500}, + .y = {97, 93, 93, 90, 87}, + .cols = 5, +}; + +static struct sf_lut palladium_1500_pc_sf = { + .rows = 10, + .cols = 5, + /* row_entries are chargecycles */ + .row_entries = {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 sf_lut palladium_1500_rbatt_sf = { + .rows = 19, + .cols = 5, + /* row_entries are temperature */ + .row_entries = {-20, 0, 20, 40, 65}, + .percent = {100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, + 45, 40, 35, 30, 25, 20, 15, 10 + }, + .sf = { + {645, 301, 100, 80, 69}, + {616, 290, 100, 79, 69}, + {586, 279, 100, 78, 68}, + {564, 270, 100, 78, 68}, + {546, 262, 100, 78, 68}, + {537, 256, 100, 79, 68}, + {536, 253, 100, 79, 69}, + {552, 258, 100, 81, 71}, + {618, 284, 100, 80, 72}, + {643, 290, 100, 77, 68}, + {673, 294, 100, 77, 68}, + {720, 296, 100, 77, 69}, + {769, 294, 100, 76, 68}, + {821, 288, 100, 74, 67}, + {892, 284, 100, 74, 61}, + {1003, 290, 100, 71, 58}, + {1192, 307, 100, 70, 58}, + {1579, 345, 100, 68, 57}, + {1261, 324, 100, 68, 57}, + } +}; +static struct pc_temp_ocv_lut palladium_1500_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 = &palladium_1500_fcc_temp, + .fcc_sf_lut = &palladium_1500_fcc_sf, + .pc_temp_ocv_lut = &palladium_1500_pc_temp_ocv, + .pc_sf_lut = &palladium_1500_pc_sf, + .rbatt_sf_lut = &palladium_1500_rbatt_sf, + .default_rbatt_mohm = 254, + .delta_rbatt_mohm = 60, +}; diff --git a/arch/arm/mach-msm/board-8064-camera.c b/arch/arm/mach-msm/board-8064-camera.c new file mode 100644 index 0000000000000000000000000000000000000000..2d1f787c42ceed7a4e6223e1bf1391b656d096ae --- /dev/null +++ b/arch/arm/mach-msm/board-8064-camera.c @@ -0,0 +1,697 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#include + +#include +#include +#include + +#include "devices.h" +#include "board-8064.h" + +#ifdef CONFIG_MSM_CAMERA + +static struct gpiomux_setting cam_settings[] = { + { + .func = GPIOMUX_FUNC_GPIO, /*suspend*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + + { + .func = GPIOMUX_FUNC_1, /*active 1*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_GPIO, /*active 2*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_2, /*active 3*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_5, /*active 4*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_6, /*active 5*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_2, /*active 6*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_3, /*active 7*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_GPIO, /*i2c suspend*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, + }, + + { + .func = GPIOMUX_FUNC_9, /*active 9*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, + }, + { + .func = GPIOMUX_FUNC_A, /*active 10*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, + }, + { + .func = GPIOMUX_FUNC_6, /*active 11*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, + }, + { + .func = GPIOMUX_FUNC_4, /*active 12*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + +}; + +static struct msm_gpiomux_config apq8064_cam_common_configs[] = { + { + .gpio = 1, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 2, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[12], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 5, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[1], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 34, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 107, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 10, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[9], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 11, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[10], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 12, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[11], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 13, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[11], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, +}; + + +#define VFE_CAMIF_TIMER1_GPIO 3 +#define VFE_CAMIF_TIMER2_GPIO 1 + +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_EXT, + ._fsrc.ext_driver_src.led_en = VFE_CAMIF_TIMER1_GPIO, + ._fsrc.ext_driver_src.led_flash_en = VFE_CAMIF_TIMER2_GPIO, + ._fsrc.ext_driver_src.flash_id = MAM_CAMERA_EXT_LED_FLASH_SC628A, +}; + +static struct msm_gpiomux_config apq8064_cam_2d_configs[] = { +}; + +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 = 27648000, + .ib = 110592000, + }, + { + .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 = 140451840, + .ib = 561807360, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 206807040, + .ib = 488816640, + }, + { + .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 = 274423680, + .ib = 1097694720, + }, + { + .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 = 540000000, + .ib = 1350000000, + }, +}; + +static struct msm_bus_vectors cam_zsl_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 302071680, + .ib = 1208286720, + }, + { + .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 = 540000000, + .ib = 1350000000, + }, +}; + +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, + }, +}; + +static struct msm_bus_scale_pdata cam_bus_client_pdata = { + cam_bus_client_config, + ARRAY_SIZE(cam_bus_client_config), + .name = "msm_camera", +}; + +static struct msm_camera_device_platform_data msm_camera_csi_device_data[] = { + { + .csid_core = 0, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, + { + .csid_core = 1, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, +}; + +static struct camera_vreg_t apq_8064_back_cam_vreg[] = { + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +static struct camera_vreg_t apq_8064_front_cam_vreg[] = { + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +#define CAML_RSTN PM8921_GPIO_PM_TO_SYS(28) +#define CAMR_RSTN 34 + +static struct gpio apq8064_common_cam_gpio[] = { +}; + +static struct gpio apq8064_back_cam_gpio[] = { + {5, GPIOF_DIR_IN, "CAMIF_MCLK"}, + {CAML_RSTN, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct msm_gpio_set_tbl apq8064_back_cam_gpio_set_tbl[] = { + {CAML_RSTN, GPIOF_OUT_INIT_LOW, 10000}, + {CAML_RSTN, GPIOF_OUT_INIT_HIGH, 10000}, +}; + +static struct msm_camera_gpio_conf apq8064_back_cam_gpio_conf = { + .cam_gpiomux_conf_tbl = apq8064_cam_2d_configs, + .cam_gpiomux_conf_tbl_size = ARRAY_SIZE(apq8064_cam_2d_configs), + .cam_gpio_common_tbl = apq8064_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(apq8064_common_cam_gpio), + .cam_gpio_req_tbl = apq8064_back_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(apq8064_back_cam_gpio), + .cam_gpio_set_tbl = apq8064_back_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(apq8064_back_cam_gpio_set_tbl), +}; + +static struct gpio apq8064_front_cam_gpio[] = { + {4, GPIOF_DIR_IN, "CAMIF_MCLK"}, + {12, GPIOF_DIR_IN, "CAMIF_I2C_DATA"}, + {13, GPIOF_DIR_IN, "CAMIF_I2C_CLK"}, + {CAMR_RSTN, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct msm_gpio_set_tbl apq8064_front_cam_gpio_set_tbl[] = { + {CAMR_RSTN, GPIOF_OUT_INIT_LOW, 10000}, + {CAMR_RSTN, GPIOF_OUT_INIT_HIGH, 10000}, +}; + +static struct msm_camera_gpio_conf apq8064_front_cam_gpio_conf = { + .cam_gpiomux_conf_tbl = apq8064_cam_2d_configs, + .cam_gpiomux_conf_tbl_size = ARRAY_SIZE(apq8064_cam_2d_configs), + .cam_gpio_common_tbl = apq8064_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(apq8064_common_cam_gpio), + .cam_gpio_req_tbl = apq8064_front_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(apq8064_front_cam_gpio), + .cam_gpio_set_tbl = apq8064_front_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(apq8064_front_cam_gpio_set_tbl), +}; + +static struct msm_camera_i2c_conf apq8064_back_cam_i2c_conf = { + .use_i2c_mux = 1, + .mux_dev = &msm8960_device_i2c_mux_gsbi4, + .i2c_mux_mode = MODE_L, +}; + +static struct i2c_board_info msm_act_main_cam_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x11), +}; + +static struct msm_actuator_info msm_act_main_cam_0_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_0, + .bus_id = APQ_8064_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + +static struct i2c_board_info msm_act_main_cam1_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x18), +}; + +static struct msm_actuator_info msm_act_main_cam_1_info = { + .board_info = &msm_act_main_cam1_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_1, + .bus_id = APQ_8064_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + + +static struct msm_camera_i2c_conf apq8064_front_cam_i2c_conf = { + .use_i2c_mux = 1, + .mux_dev = &msm8960_device_i2c_mux_gsbi4, + .i2c_mux_mode = MODE_L, +}; + +static struct msm_camera_sensor_flash_data flash_imx074 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_csi_lane_params imx074_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_imx074 = { + .mount_angle = 90, + .cam_vreg = apq_8064_back_cam_vreg, + .num_vreg = ARRAY_SIZE(apq_8064_back_cam_vreg), + .gpio_conf = &apq8064_back_cam_gpio_conf, + .i2c_conf = &apq8064_back_cam_i2c_conf, + .csi_lane_params = &imx074_csi_lane_params, +}; + +static struct i2c_board_info imx074_eeprom_i2c_info = { + I2C_BOARD_INFO("imx074_eeprom", 0x34 << 1), +}; + +static struct msm_eeprom_info imx074_eeprom_info = { + .board_info = &imx074_eeprom_i2c_info, + .bus_id = APQ_8064_GSBI4_QUP_I2C_BUS_ID, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx074_data = { + .sensor_name = "imx074", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_imx074, + .sensor_platform_info = &sensor_board_info_imx074, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_0_info, + .eeprom_info = &imx074_eeprom_info, +}; + +static struct msm_camera_csi_lane_params imx091_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct camera_vreg_t apq_8064_imx091_vreg[] = { + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vio", REG_VS, 0, 0, 0}, +}; + +static struct msm_camera_sensor_flash_data flash_imx091 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_imx091 = { + .mount_angle = 0, + .cam_vreg = apq_8064_imx091_vreg, + .num_vreg = ARRAY_SIZE(apq_8064_imx091_vreg), + .gpio_conf = &apq8064_back_cam_gpio_conf, + .i2c_conf = &apq8064_back_cam_i2c_conf, + .csi_lane_params = &imx091_csi_lane_params, +}; + +static struct i2c_board_info imx091_eeprom_i2c_info = { + I2C_BOARD_INFO("imx091_eeprom", 0x21), +}; + +static struct msm_eeprom_info imx091_eeprom_info = { + .board_info = &imx091_eeprom_i2c_info, + .bus_id = APQ_8064_GSBI4_QUP_I2C_BUS_ID, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx091_data = { + .sensor_name = "imx091", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_imx091, + .sensor_platform_info = &sensor_board_info_imx091, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_1_info, + .eeprom_info = &imx091_eeprom_info, +}; + +static struct camera_vreg_t apq_8064_s5k3l1yx_vreg[] = { + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +static struct msm_camera_sensor_flash_data flash_s5k3l1yx = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_csi_lane_params s5k3l1yx_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_s5k3l1yx = { + .mount_angle = 90, + .cam_vreg = apq_8064_s5k3l1yx_vreg, + .num_vreg = ARRAY_SIZE(apq_8064_s5k3l1yx_vreg), + .gpio_conf = &apq8064_back_cam_gpio_conf, + .i2c_conf = &apq8064_back_cam_i2c_conf, + .csi_lane_params = &s5k3l1yx_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k3l1yx_data = { + .sensor_name = "s5k3l1yx", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_s5k3l1yx, + .sensor_platform_info = &sensor_board_info_s5k3l1yx, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, +}; + +static struct camera_vreg_t apq_8064_mt9m114_vreg[] = { + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +static struct msm_camera_sensor_flash_data flash_mt9m114 = { + .flash_type = MSM_CAMERA_FLASH_NONE +}; + +static struct msm_camera_csi_lane_params mt9m114_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0x1, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_mt9m114 = { + .mount_angle = 90, + .cam_vreg = apq_8064_mt9m114_vreg, + .num_vreg = ARRAY_SIZE(apq_8064_mt9m114_vreg), + .gpio_conf = &apq8064_front_cam_gpio_conf, + .i2c_conf = &apq8064_front_cam_i2c_conf, + .csi_lane_params = &mt9m114_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9m114_data = { + .sensor_name = "mt9m114", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_mt9m114, + .sensor_platform_info = &sensor_board_info_mt9m114, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = YUV_SENSOR, +}; + +static struct msm_camera_sensor_flash_data flash_ov2720 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_csi_lane_params ov2720_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0x3, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_ov2720 = { + .mount_angle = 0, + .cam_vreg = apq_8064_front_cam_vreg, + .num_vreg = ARRAY_SIZE(apq_8064_front_cam_vreg), + .gpio_conf = &apq8064_front_cam_gpio_conf, + .i2c_conf = &apq8064_front_cam_i2c_conf, + .csi_lane_params = &ov2720_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov2720_data = { + .sensor_name = "ov2720", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_ov2720, + .sensor_platform_info = &sensor_board_info_ov2720, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = BAYER_SENSOR, +}; + +static struct platform_device msm_camera_server = { + .name = "msm_cam_server", + .id = 0, +}; + +void __init apq8064_init_cam(void) +{ + msm_gpiomux_install(apq8064_cam_common_configs, + ARRAY_SIZE(apq8064_cam_common_configs)); + + if (machine_is_apq8064_cdp()) { + sensor_board_info_imx074.mount_angle = 0; + sensor_board_info_mt9m114.mount_angle = 0; + } else if (machine_is_apq8064_liquid()) + sensor_board_info_imx074.mount_angle = 180; + + platform_device_register(&msm_camera_server); + platform_device_register(&msm8960_device_i2c_mux_gsbi4); + platform_device_register(&msm8960_device_csiphy0); + platform_device_register(&msm8960_device_csiphy1); + platform_device_register(&msm8960_device_csid0); + platform_device_register(&msm8960_device_csid1); + platform_device_register(&msm8960_device_ispif); + platform_device_register(&msm8960_device_vfe); + platform_device_register(&msm8960_device_vpe); +} + +#ifdef CONFIG_I2C +static struct i2c_board_info apq8064_camera_i2c_boardinfo[] = { + { + I2C_BOARD_INFO("imx074", 0x1A), + .platform_data = &msm_camera_sensor_imx074_data, + }, + { + I2C_BOARD_INFO("mt9m114", 0x48), + .platform_data = &msm_camera_sensor_mt9m114_data, + }, + { + I2C_BOARD_INFO("ov2720", 0x6C), + .platform_data = &msm_camera_sensor_ov2720_data, + }, + { + I2C_BOARD_INFO("sc628a", 0x6E), + }, + { + I2C_BOARD_INFO("imx091", 0x34), + .platform_data = &msm_camera_sensor_imx091_data, + }, + { + I2C_BOARD_INFO("s5k3l1yx", 0x20), + .platform_data = &msm_camera_sensor_s5k3l1yx_data, + }, +}; + +struct msm_camera_board_info apq8064_camera_board_info = { + .board_info = apq8064_camera_i2c_boardinfo, + .num_i2c_board_info = ARRAY_SIZE(apq8064_camera_i2c_boardinfo), +}; +#endif +#endif diff --git a/arch/arm/mach-msm/board-8064-display.c b/arch/arm/mach-msm/board-8064-display.c new file mode 100644 index 0000000000000000000000000000000000000000..55e8123be5762fe76fc67b93ae7828bd4c3ae434 --- /dev/null +++ b/arch/arm/mach-msm/board-8064-display.c @@ -0,0 +1,995 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "board-8064.h" + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +/* prim = 1366 x 768 x 3(bpp) x 3(pages) */ +#define MSM_FB_PRIM_BUF_SIZE roundup(1920 * 1088 * 4 * 3, 0x10000) +#else +/* prim = 1366 x 768 x 3(bpp) x 2(pages) */ +#define MSM_FB_PRIM_BUF_SIZE roundup(1920 * 1088 * 4 * 2, 0x10000) +#endif + +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE, 4096) + +#ifdef CONFIG_FB_MSM_OVERLAY0_WRITEBACK +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE roundup((1376 * 768 * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY0_WRITEBACK */ + +#ifdef CONFIG_FB_MSM_OVERLAY1_WRITEBACK +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE roundup((1920 * 1088 * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY1_WRITEBACK */ + + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +#define LVDS_CHIMEI_PANEL_NAME "lvds_chimei_wxga" +#define MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME "mipi_video_toshiba_wsvga" +#define MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME "mipi_video_chimei_wxga" +#define HDMI_PANEL_NAME "hdmi_msm" +#define TVOUT_PANEL_NAME "tvout_msm" + +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +static unsigned char hdmi_is_primary = 1; +#else +static unsigned char hdmi_is_primary; +#endif + +unsigned char apq8064_hdmi_as_primary_selected(void) +{ + return hdmi_is_primary; +} + +static void set_mdp_clocks_for_wuxga(void); + +static int msm_fb_detect_panel(const char *name) +{ + u32 version; + if (machine_is_apq8064_liquid()) { + version = socinfo_get_platform_version(); + if ((SOCINFO_VERSION_MAJOR(version) == 1) && + (SOCINFO_VERSION_MINOR(version) == 1)) { + if (!strncmp(name, MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME, + strnlen(MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + } else { + if (!strncmp(name, LVDS_CHIMEI_PANEL_NAME, + strnlen(LVDS_CHIMEI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + } + } else if (machine_is_apq8064_mtp()) { + if (!strncmp(name, MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + } else if (machine_is_apq8064_cdp() || + machine_is_mpq8064_dtv()) { + if (!strncmp(name, LVDS_CHIMEI_PANEL_NAME, + strnlen(LVDS_CHIMEI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + } + + if (!strncmp(name, HDMI_PANEL_NAME, + strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + if (apq8064_hdmi_as_primary_selected()) + set_mdp_clocks_for_wuxga(); + return 0; + } + + + return -ENODEV; +} + +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, +}; + +void __init apq8064_allocate_fb_region(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)); +} + +#define MDP_VSYNC_GPIO 0 + +static struct msm_bus_vectors mdp_init_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_ui_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 216000000 * 2, + .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_EBI_CH0, + .ab = 216000000 * 2, + .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_EBI_CH0, + .ab = 230400000 * 2, + .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_EBI_CH0, + .ab = 334080000 * 2, + .ib = 417600000 * 2, + }, +}; + +static struct msm_bus_paths mdp_bus_scale_usecases[] = { + { + ARRAY_SIZE(mdp_init_vectors), + mdp_init_vectors, + }, + { + ARRAY_SIZE(mdp_ui_vectors), + mdp_ui_vectors, + }, + { + ARRAY_SIZE(mdp_ui_vectors), + mdp_ui_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", +}; + +static int mdp_core_clk_rate_table[] = { + 85330000, + 128000000, + 160000000, + 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), + .mdp_bus_scale_table = &mdp_bus_scale_pdata, + .mdp_rev = MDP_REV_44, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .mem_hid = BIT(ION_CP_MM_HEAP_ID), +#else + .mem_hid = MEMTYPE_EBI1, +#endif +}; + +void __init apq8064_mdp_writeback(struct memtype_reserve* reserve_table) +{ + mdp_pdata.ov0_wb_size = MSM_FB_OVERLAY0_WRITEBACK_SIZE; + mdp_pdata.ov1_wb_size = MSM_FB_OVERLAY1_WRITEBACK_SIZE; +#if defined(CONFIG_ANDROID_PMEM) && !defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov0_wb_size; + reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov1_wb_size; +#endif +} + +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, +}; + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL +static struct platform_device wfd_panel_device = { + .name = "wfd_panel", + .id = 0, + .dev.platform_data = NULL, +}; + +static struct platform_device wfd_device = { + .name = "msm_wfd", + .id = -1, +}; +#endif + +/* HDMI related GPIOs */ +#define HDMI_CEC_VAR_GPIO 69 +#define HDMI_DDC_CLK_GPIO 70 +#define HDMI_DDC_DATA_GPIO 71 +#define HDMI_HPD_GPIO 72 + +static bool dsi_power_on; +static int mipi_dsi_panel_power(int on) +{ + static struct regulator *reg_lvs7, *reg_l2, *reg_l11, *reg_ext_3p3v; + static int gpio36, gpio25, gpio26, mpp3; + int rc; + + pr_debug("%s: on=%d\n", __func__, on); + + if (!dsi_power_on) { + reg_lvs7 = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi1_vddio"); + if (IS_ERR_OR_NULL(reg_lvs7)) { + pr_err("could not get 8921_lvs7, rc = %ld\n", + PTR_ERR(reg_lvs7)); + return -ENODEV; + } + + reg_l2 = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi1_pll_vdda"); + if (IS_ERR_OR_NULL(reg_l2)) { + pr_err("could not get 8921_l2, rc = %ld\n", + PTR_ERR(reg_l2)); + return -ENODEV; + } + + rc = regulator_set_voltage(reg_l2, 1200000, 1200000); + if (rc) { + pr_err("set_voltage l2 failed, rc=%d\n", rc); + return -EINVAL; + } + reg_l11 = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi1_avdd"); + if (IS_ERR(reg_l11)) { + pr_err("could not get 8921_l11, rc = %ld\n", + PTR_ERR(reg_l11)); + return -ENODEV; + } + rc = regulator_set_voltage(reg_l11, 3000000, 3000000); + if (rc) { + pr_err("set_voltage l11 failed, rc=%d\n", rc); + return -EINVAL; + } + + if (machine_is_apq8064_liquid()) { + reg_ext_3p3v = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi1_vccs_3p3v"); + if (IS_ERR_OR_NULL(reg_ext_3p3v)) { + pr_err("could not get reg_ext_3p3v, rc = %ld\n", + PTR_ERR(reg_ext_3p3v)); + reg_ext_3p3v = NULL; + return -ENODEV; + } + mpp3 = PM8921_MPP_PM_TO_SYS(3); + rc = gpio_request(mpp3, "backlight_en"); + if (rc) { + pr_err("request mpp3 failed, rc=%d\n", rc); + return -ENODEV; + } + } + + gpio25 = PM8921_GPIO_PM_TO_SYS(25); + rc = gpio_request(gpio25, "disp_rst_n"); + if (rc) { + pr_err("request gpio 25 failed, rc=%d\n", rc); + return -ENODEV; + } + + gpio26 = PM8921_GPIO_PM_TO_SYS(26); + rc = gpio_request(gpio26, "pwm_backlight_ctrl"); + if (rc) { + pr_err("request gpio 26 failed, rc=%d\n", rc); + return -ENODEV; + } + + gpio36 = PM8921_GPIO_PM_TO_SYS(36); /* lcd1_pwr_en_n */ + rc = gpio_request(gpio36, "lcd1_pwr_en_n"); + if (rc) { + pr_err("request gpio 36 failed, rc=%d\n", rc); + return -ENODEV; + } + + dsi_power_on = true; + } + + if (on) { + rc = regulator_enable(reg_lvs7); + if (rc) { + pr_err("enable lvs7 failed, rc=%d\n", rc); + return -ENODEV; + } + + rc = regulator_set_optimum_mode(reg_l11, 110000); + if (rc < 0) { + pr_err("set_optimum_mode l11 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_enable(reg_l11); + if (rc) { + pr_err("enable l11 failed, rc=%d\n", rc); + return -ENODEV; + } + + 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_l2); + if (rc) { + pr_err("enable l2 failed, rc=%d\n", rc); + return -ENODEV; + } + + if (machine_is_apq8064_liquid()) { + rc = regulator_enable(reg_ext_3p3v); + if (rc) { + pr_err("enable reg_ext_3p3v failed, rc=%d\n", + rc); + return -ENODEV; + } + gpio_set_value_cansleep(mpp3, 1); + } + + gpio_set_value_cansleep(gpio36, 0); + gpio_set_value_cansleep(gpio25, 1); + } else { + gpio_set_value_cansleep(gpio25, 0); + gpio_set_value_cansleep(gpio36, 1); + + if (machine_is_apq8064_liquid()) { + gpio_set_value_cansleep(mpp3, 0); + + rc = regulator_disable(reg_ext_3p3v); + if (rc) { + pr_err("disable reg_ext_3p3v failed, rc=%d\n", + rc); + return -ENODEV; + } + } + + rc = regulator_disable(reg_lvs7); + if (rc) { + pr_err("disable reg_lvs7 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_l2); + if (rc) { + pr_err("disable reg_l2 failed, rc=%d\n", rc); + return -ENODEV; + } + } + + return 0; +} + +static struct mipi_dsi_platform_data mipi_dsi_pdata = { + .dsi_power_save = mipi_dsi_panel_power, +}; + +static bool lvds_power_on; +static int lvds_panel_power(int on) +{ + static struct regulator *reg_lvs7, *reg_l2, *reg_ext_3p3v; + static int gpio36, gpio26, mpp3; + int rc; + + pr_debug("%s: on=%d\n", __func__, on); + + if (!lvds_power_on) { + reg_lvs7 = regulator_get(&msm_lvds_device.dev, + "lvds_vdda"); + if (IS_ERR_OR_NULL(reg_lvs7)) { + pr_err("could not get 8921_lvs7, rc = %ld\n", + PTR_ERR(reg_lvs7)); + return -ENODEV; + } + + reg_l2 = regulator_get(&msm_lvds_device.dev, + "lvds_pll_vdda"); + if (IS_ERR_OR_NULL(reg_l2)) { + pr_err("could not get 8921_l2, rc = %ld\n", + PTR_ERR(reg_l2)); + return -ENODEV; + } + + rc = regulator_set_voltage(reg_l2, 1200000, 1200000); + if (rc) { + pr_err("set_voltage l2 failed, rc=%d\n", rc); + return -EINVAL; + } + + reg_ext_3p3v = regulator_get(&msm_lvds_device.dev, + "lvds_vccs_3p3v"); + if (IS_ERR_OR_NULL(reg_ext_3p3v)) { + pr_err("could not get reg_ext_3p3v, rc = %ld\n", + PTR_ERR(reg_ext_3p3v)); + return -ENODEV; + } + + gpio26 = PM8921_GPIO_PM_TO_SYS(26); + rc = gpio_request(gpio26, "pwm_backlight_ctrl"); + if (rc) { + pr_err("request gpio 26 failed, rc=%d\n", rc); + return -ENODEV; + } + + gpio36 = PM8921_GPIO_PM_TO_SYS(36); /* lcd1_pwr_en_n */ + rc = gpio_request(gpio36, "lcd1_pwr_en_n"); + if (rc) { + pr_err("request gpio 36 failed, rc=%d\n", rc); + return -ENODEV; + } + + mpp3 = PM8921_MPP_PM_TO_SYS(3); + rc = gpio_request(mpp3, "backlight_en"); + if (rc) { + pr_err("request mpp3 failed, rc=%d\n", rc); + return -ENODEV; + } + + lvds_power_on = true; + } + + if (on) { + rc = regulator_enable(reg_lvs7); + if (rc) { + pr_err("enable lvs7 failed, rc=%d\n", rc); + return -ENODEV; + } + + 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_l2); + if (rc) { + pr_err("enable l2 failed, rc=%d\n", rc); + return -ENODEV; + } + + rc = regulator_enable(reg_ext_3p3v); + if (rc) { + pr_err("enable reg_ext_3p3v failed, rc=%d\n", rc); + return -ENODEV; + } + + gpio_set_value_cansleep(gpio36, 0); + gpio_set_value_cansleep(mpp3, 1); + } else { + gpio_set_value_cansleep(mpp3, 0); + gpio_set_value_cansleep(gpio36, 1); + + rc = regulator_disable(reg_lvs7); + if (rc) { + pr_err("disable reg_lvs7 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_l2); + if (rc) { + pr_err("disable reg_l2 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_ext_3p3v); + if (rc) { + pr_err("disable reg_ext_3p3v failed, rc=%d\n", rc); + return -ENODEV; + } + } + + return 0; +} + +static int lvds_pixel_remap(void) +{ + if (machine_is_apq8064_cdp() || + machine_is_apq8064_liquid()) { + u32 ver = socinfo_get_version(); + if ((SOCINFO_VERSION_MAJOR(ver) == 1) && + (SOCINFO_VERSION_MINOR(ver) == 0)) + return 1; + } + return 0; +} + +static struct lcdc_platform_data lvds_pdata = { + .lcdc_power_save = lvds_panel_power, + .lvds_pixel_remap = lvds_pixel_remap +}; + +#define LPM_CHANNEL 2 +static int lvds_chimei_gpio[] = {LPM_CHANNEL}; + +static struct lvds_panel_platform_data lvds_chimei_pdata = { + .gpio = lvds_chimei_gpio, +}; + +static struct platform_device lvds_chimei_panel_device = { + .name = "lvds_chimei_wxga", + .id = 0, + .dev = { + .platform_data = &lvds_chimei_pdata, + } +}; + +static int dsi2lvds_gpio[2] = { + LPM_CHANNEL,/* Backlight PWM-ID=0 for PMIC-GPIO#24 */ + 0x1F08 /* DSI2LVDS Bridge GPIO Output, mask=0x1f, out=0x08 */ +}; +static struct msm_panel_common_pdata mipi_dsi2lvds_pdata = { + .gpio_num = dsi2lvds_gpio, +}; + +static struct platform_device mipi_dsi2lvds_bridge_device = { + .name = "mipi_tc358764", + .id = 0, + .dev.platform_data = &mipi_dsi2lvds_pdata, +}; + +static int toshiba_gpio[] = {LPM_CHANNEL}; +static struct mipi_dsi_panel_platform_data toshiba_pdata = { + .gpio = toshiba_gpio, +}; + +static struct platform_device mipi_dsi_toshiba_panel_device = { + .name = "mipi_toshiba", + .id = 0, + .dev = { + .platform_data = &toshiba_pdata, + } +}; + +static struct msm_bus_vectors dtv_bus_init_vectors[] = { + { + .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[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 566092800 * 2, + .ib = 707616000 * 2, + }, +}; + +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, +}; + +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 (IS_ERR(reg_8921_hdmi_mvs)) { + pr_err("could not get reg_8921_hdmi_mvs, rc = %ld\n", + PTR_ERR(reg_8921_hdmi_mvs)); + reg_8921_hdmi_mvs = NULL; + return -ENODEV; + } + } + + 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_lvs7, *reg_8921_s4, *reg_ext_3p3v; + static int prev_on; + int rc; + int pmic_gpio14 = PM8921_GPIO_PM_TO_SYS(14); + + if (on == prev_on) + return 0; + + /* TBD: PM8921 regulator instead of 8901 */ + if (!reg_ext_3p3v) { + reg_ext_3p3v = regulator_get(&hdmi_msm_device.dev, + "hdmi_mux_vdd"); + if (IS_ERR_OR_NULL(reg_ext_3p3v)) { + pr_err("could not get reg_ext_3p3v, rc = %ld\n", + PTR_ERR(reg_ext_3p3v)); + reg_ext_3p3v = NULL; + return -ENODEV; + } + } + + if (!reg_8921_lvs7) { + reg_8921_lvs7 = regulator_get(&hdmi_msm_device.dev, + "hdmi_vdda"); + if (IS_ERR(reg_8921_lvs7)) { + pr_err("could not get reg_8921_lvs7, rc = %ld\n", + PTR_ERR(reg_8921_lvs7)); + reg_8921_lvs7 = NULL; + return -ENODEV; + } + } + if (!reg_8921_s4) { + reg_8921_s4 = regulator_get(&hdmi_msm_device.dev, + "hdmi_lvl_tsl"); + if (IS_ERR(reg_8921_s4)) { + pr_err("could not get reg_8921_s4, rc = %ld\n", + PTR_ERR(reg_8921_s4)); + reg_8921_s4 = NULL; + return -ENODEV; + } + rc = regulator_set_voltage(reg_8921_s4, 1800000, 1800000); + if (rc) { + pr_err("set_voltage failed for 8921_s4, rc=%d\n", rc); + return -EINVAL; + } + } + + if (on) { + /* + * Configure 3P3V_BOOST_EN as GPIO, 8mA drive strength, + * pull none, out-high + */ + rc = regulator_set_optimum_mode(reg_ext_3p3v, 290000); + if (rc < 0) { + pr_err("set_optimum_mode ext_3p3v failed, rc=%d\n", rc); + return -EINVAL; + } + + rc = regulator_enable(reg_ext_3p3v); + if (rc) { + pr_err("enable reg_ext_3p3v failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_enable(reg_8921_lvs7); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "hdmi_vdda", rc); + return rc; + } + rc = regulator_enable(reg_8921_s4); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "hdmi_lvl_tsl", rc); + return rc; + } + rc = gpio_request(HDMI_DDC_CLK_GPIO, "HDMI_DDC_CLK"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_DDC_CLK", HDMI_DDC_CLK_GPIO, rc); + goto error1; + } + rc = gpio_request(HDMI_DDC_DATA_GPIO, "HDMI_DDC_DATA"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_DDC_DATA", HDMI_DDC_DATA_GPIO, rc); + goto error2; + } + rc = gpio_request(HDMI_HPD_GPIO, "HDMI_HPD"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_HPD", HDMI_HPD_GPIO, rc); + goto error3; + } + if (machine_is_apq8064_liquid()) { + rc = gpio_request(pmic_gpio14, "PMIC_HDMI_MUX_SEL"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "PMIC_HDMI_MUX_SEL", 14, rc); + goto error4; + } + gpio_set_value_cansleep(pmic_gpio14, 0); + } + pr_debug("%s(on): success\n", __func__); + } else { + gpio_free(HDMI_DDC_CLK_GPIO); + gpio_free(HDMI_DDC_DATA_GPIO); + gpio_free(HDMI_HPD_GPIO); + + if (machine_is_apq8064_liquid()) { + gpio_set_value_cansleep(pmic_gpio14, 1); + gpio_free(pmic_gpio14); + } + + rc = regulator_disable(reg_ext_3p3v); + if (rc) { + pr_err("disable reg_ext_3p3v failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_8921_lvs7); + if (rc) { + pr_err("disable reg_8921_l23 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_8921_s4); + if (rc) { + pr_err("disable reg_8921_s4 failed, rc=%d\n", rc); + return -ENODEV; + } + pr_debug("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; + +error4: + gpio_free(HDMI_HPD_GPIO); +error3: + gpio_free(HDMI_DDC_DATA_GPIO); +error2: + gpio_free(HDMI_DDC_CLK_GPIO); +error1: + regulator_disable(reg_8921_lvs7); + regulator_disable(reg_8921_s4); + 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(HDMI_CEC_VAR_GPIO, "HDMI_CEC_VAR"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_CEC_VAR", HDMI_CEC_VAR_GPIO, rc); + goto error; + } + pr_debug("%s(on): success\n", __func__); + } else { + gpio_free(HDMI_CEC_VAR_GPIO); + pr_debug("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; +error: + return rc; +} + +void __init apq8064_init_fb(void) +{ + platform_device_register(&msm_fb_device); + platform_device_register(&lvds_chimei_panel_device); + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL + platform_device_register(&wfd_panel_device); + platform_device_register(&wfd_device); +#endif + + if (machine_is_apq8064_liquid()) + platform_device_register(&mipi_dsi2lvds_bridge_device); + if (machine_is_apq8064_mtp()) + platform_device_register(&mipi_dsi_toshiba_panel_device); + + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("lvds", &lvds_pdata); + msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); + platform_device_register(&hdmi_msm_device); + msm_fb_register_device("dtv", &dtv_pdata); +} + +/** + * Set MDP clocks to high frequency to avoid DSI underflow + * when using high resolution 1200x1920 WUXGA panels + */ +static void set_mdp_clocks_for_wuxga(void) +{ + int i; + + mdp_ui_vectors[0].ab = 2000000000; + mdp_ui_vectors[0].ib = 2000000000; + mdp_vga_vectors[0].ab = 2000000000; + mdp_vga_vectors[0].ib = 2000000000; + mdp_720p_vectors[0].ab = 2000000000; + mdp_720p_vectors[0].ib = 2000000000; + mdp_1080p_vectors[0].ab = 2000000000; + mdp_1080p_vectors[0].ib = 2000000000; + + mdp_pdata.mdp_core_clk_rate = 200000000; + + for (i = 0; i < ARRAY_SIZE(mdp_core_clk_rate_table); i++) + mdp_core_clk_rate_table[i] = 200000000; + + if (apq8064_hdmi_as_primary_selected()) { + dtv_bus_def_vectors[0].ab = 2000000000; + dtv_bus_def_vectors[0].ib = 2000000000; + } +} + +void __init apq8064_set_display_params(char *prim_panel, char *ext_panel) +{ + /* + * For certain MPQ boards, HDMI should be set as primary display + * by default, with the flexibility to specify any other panel + * as a primary panel through boot parameters. + */ + if (machine_is_mpq8064_hrd() || machine_is_mpq8064_cdp()) { + pr_debug("HDMI is the primary display by default for MPQ\n"); + if (!strnlen(prim_panel, PANEL_NAME_MAX_LEN)) + strlcpy(msm_fb_pdata.prim_panel_name, HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN); + } + + if (strnlen(prim_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.prim_panel_name, prim_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.prim_panel_name %s\n", + msm_fb_pdata.prim_panel_name); + + if (!strncmp((char *)msm_fb_pdata.prim_panel_name, + HDMI_PANEL_NAME, strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + pr_debug("HDMI is the primary display by" + " boot parameter\n"); + hdmi_is_primary = 1; + set_mdp_clocks_for_wuxga(); + } + } + if (strnlen(ext_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.ext_panel_name, ext_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.ext_panel_name %s\n", + msm_fb_pdata.ext_panel_name); + } +} diff --git a/arch/arm/mach-msm/board-8064-gpiomux.c b/arch/arm/mach-msm/board-8064-gpiomux.c new file mode 100644 index 0000000000000000000000000000000000000000..b22c684ba6c0925c8d465e91af749240c5635d41 --- /dev/null +++ b/arch/arm/mach-msm/board-8064-gpiomux.c @@ -0,0 +1,1107 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "board-8064.h" + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +static struct gpiomux_setting gpio_eth_config = { + .pull = GPIOMUX_PULL_NONE, + .drv = GPIOMUX_DRV_8MA, + .func = GPIOMUX_FUNC_GPIO, +}; + +/* The SPI configurations apply to GSBI 5*/ +static struct gpiomux_setting gpio_spi_config = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +/* The SPI configurations apply to GSBI 5 chip select 2*/ +static struct gpiomux_setting gpio_spi_cs2_config = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +/* Chip selects for SPI clients */ +static struct gpiomux_setting gpio_spi_cs_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_UP, +}; + +/* Chip selects for EPM SPI clients */ +static struct gpiomux_setting gpio_epm_spi_cs_config = { + .func = GPIOMUX_FUNC_6, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_UP, +}; + +struct msm_gpiomux_config apq8064_ethernet_configs[] = { + { + .gpio = 43, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + [GPIOMUX_ACTIVE] = &gpio_eth_config, + } + }, +}; +#endif + +#ifdef CONFIG_MSM_VCAP +static struct gpiomux_setting gpio_vcap_config[] = { + { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_4, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_5, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_6, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_7, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_8, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_9, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + { + .func = GPIOMUX_FUNC_A, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, +}; + +struct msm_gpiomux_config vcap_configs[] = { + { + .gpio = 20, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[7], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[7], + } + }, + { + .gpio = 25, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 24, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[1], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[1], + } + }, + { + .gpio = 23, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 19, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[8], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[8], + } + }, + { + .gpio = 22, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 21, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[7], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[7], + } + }, + { + .gpio = 12, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[6], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[6], + } + }, + { + .gpio = 18, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[9], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[9], + } + }, + { + .gpio = 11, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[10], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[10], + } + }, + { + .gpio = 10, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[9], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[9], + } + }, + { + .gpio = 9, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 26, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[1], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[1], + } + }, + { + .gpio = 8, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[3], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[3], + } + }, + { + .gpio = 7, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[7], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[7], + } + }, + { + .gpio = 6, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[7], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[7], + } + }, + { + .gpio = 80, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 86, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[1], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[1], + } + }, + { + .gpio = 85, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[4], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[4], + } + }, + { + .gpio = 84, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[3], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[3], + } + }, + { + .gpio = 5, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 4, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[3], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[3], + } + }, + { + .gpio = 3, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[6], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[6], + } + }, + { + .gpio = 2, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[5], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[5], + } + }, + { + .gpio = 82, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[4], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[4], + } + }, + { + .gpio = 83, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[4], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[4], + } + }, + { + .gpio = 87, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[2], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[2], + } + }, + { + .gpio = 13, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_vcap_config[6], + [GPIOMUX_ACTIVE] = &gpio_vcap_config[6], + } + }, +}; +#endif + +static struct gpiomux_setting gpio_i2c_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gpio_i2c_config_sus = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting mbhc_hs_detect = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .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 wcnss_5wire_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting wcnss_5wire_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + + +static struct gpiomux_setting slimbus = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting gsbi1_uart_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting ext_regulator_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_LOW, +}; + +static struct gpiomux_setting gsbi7_func1_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi7_func2_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi3_suspended_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting gsbi3_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +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_16MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting gsbi5_suspended_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi5_active_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sx150x_suspended_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sx150x_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +#ifdef CONFIG_USB_EHCI_MSM_HSIC +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_DOWN, +}; + +static struct msm_gpiomux_config cyts_gpio_configs[] __initdata = { + { /* TS INTERRUPT */ + .gpio = 6, + .settings = { + [GPIOMUX_ACTIVE] = &cyts_int_act_cfg, + [GPIOMUX_SUSPENDED] = &cyts_int_sus_cfg, + }, + }, + { /* TS SLEEP */ + .gpio = 33, + .settings = { + [GPIOMUX_ACTIVE] = &cyts_sleep_act_cfg, + [GPIOMUX_SUSPENDED] = &cyts_sleep_sus_cfg, + }, + }, +}; + +static struct gpiomux_setting hsic_act_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting hsic_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + .dir = GPIOMUX_OUT_LOW, +}; + +static struct gpiomux_setting hsic_wakeup_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, + .dir = GPIOMUX_IN, +}; + +static struct gpiomux_setting hsic_wakeup_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + .dir = GPIOMUX_IN, +}; + +static struct msm_gpiomux_config apq8064_hsic_configs[] = { + { + .gpio = 88, /*HSIC_STROBE */ + .settings = { + [GPIOMUX_ACTIVE] = &hsic_act_cfg, + [GPIOMUX_SUSPENDED] = &hsic_sus_cfg, + }, + }, + { + .gpio = 89, /* HSIC_DATA */ + .settings = { + [GPIOMUX_ACTIVE] = &hsic_act_cfg, + [GPIOMUX_SUSPENDED] = &hsic_sus_cfg, + }, + }, + { + .gpio = 47, /* wake up */ + .settings = { + [GPIOMUX_ACTIVE] = &hsic_wakeup_act_cfg, + [GPIOMUX_SUSPENDED] = &hsic_wakeup_sus_cfg, + }, + }, +}; +#endif + +static struct gpiomux_setting mxt_reset_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mxt_reset_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting mxt_int_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mxt_int_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct msm_gpiomux_config apq8064_hdmi_configs[] __initdata = { + { + .gpio = 69, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 70, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 71, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 72, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_2_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, +}; + +static struct msm_gpiomux_config apq8064_gsbi_configs[] __initdata = { + { + .gpio = 8, /* GSBI3 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi3_active_cfg, + }, + }, + { + .gpio = 9, /* GSBI3 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi3_active_cfg, + }, + }, + { + .gpio = 18, /* GSBI1 UART TX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi1_uart_config, + }, + }, + { + .gpio = 19, /* GSBI1 UART RX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi1_uart_config, + }, + }, +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) + { + .gpio = 51, /* GSBI5 QUP SPI_DATA_MOSI */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, + { + .gpio = 52, /* GSBI5 QUP SPI_DATA_MISO */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, + { + .gpio = 53, /* Funny CS0 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, + { + .gpio = 31, /* GSBI5 QUP SPI_CS2_N */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_cs2_config, + }, + }, + { + .gpio = 54, /* GSBI5 QUP SPI_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, +#endif + { + .gpio = 30, /* FP CS */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_cs_config, + }, + }, + { + .gpio = 32, /* EPM CS */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_epm_spi_cs_config, + }, + }, + { + .gpio = 53, /* NOR CS */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_cs_config, + }, + }, + { + .gpio = 82, /* GSBI7 UART2 TX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi7_func2_cfg, + }, + }, + { + .gpio = 83, /* GSBI7 UART2 RX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi7_func1_cfg, + }, + }, + { + .gpio = 21, /* GSBI1 QUP I2C_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_i2c_config_sus, + [GPIOMUX_ACTIVE] = &gpio_i2c_config, + }, + }, + { + .gpio = 20, /* GSBI1 QUP I2C_DATA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_i2c_config_sus, + [GPIOMUX_ACTIVE] = &gpio_i2c_config, + }, + }, +}; + +static struct msm_gpiomux_config apq8064_slimbus_config[] __initdata = { + { + .gpio = 40, /* slimbus clk */ + .settings = { + [GPIOMUX_SUSPENDED] = &slimbus, + }, + }, + { + .gpio = 41, /* slimbus data */ + .settings = { + [GPIOMUX_SUSPENDED] = &slimbus, + }, + }, +}; + +static struct gpiomux_setting spkr_i2c = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct msm_gpiomux_config mpq8064_spkr_i2s_config[] __initdata = { + { + .gpio = 47, /* spkr i2c sck */ + .settings = { + [GPIOMUX_SUSPENDED] = &spkr_i2c, + }, + }, + { + .gpio = 48, /* spkr_i2s_ws */ + .settings = { + [GPIOMUX_SUSPENDED] = &spkr_i2c, + }, + }, + { + .gpio = 49, /* spkr_i2s_dout */ + .settings = { + [GPIOMUX_SUSPENDED] = &spkr_i2c, + }, + }, + { + .gpio = 50, /* spkr_i2s_mclk */ + .settings = { + [GPIOMUX_SUSPENDED] = &spkr_i2c, + }, + }, +}; + +static struct msm_gpiomux_config apq8064_audio_codec_configs[] __initdata = { + { + .gpio = 38, + .settings = { + [GPIOMUX_SUSPENDED] = &mbhc_hs_detect, + }, + }, + { + .gpio = 39, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_mclk, + }, + }, +}; + +/* External 3.3 V regulator enable */ +static struct msm_gpiomux_config apq8064_ext_regulator_configs[] __initdata = { + { + .gpio = APQ8064_EXT_3P3V_REG_EN_GPIO, + .settings = { + [GPIOMUX_SUSPENDED] = &ext_regulator_config, + }, + }, +}; + +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_errfatal_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting ap2mdm_soft_reset_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting ap2mdm_wakeup = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct msm_gpiomux_config mdm_configs[] __initdata = { + /* AP2MDM_STATUS */ + { + .gpio = 48, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* MDM2AP_STATUS */ + { + .gpio = 49, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_status_cfg, + } + }, + /* MDM2AP_ERRFATAL */ + { + .gpio = 19, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_errfatal_cfg, + } + }, + /* AP2MDM_ERRFATAL */ + { + .gpio = 18, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* AP2MDM_SOFT_RESET, aka AP2MDM_PON_RESET_N */ + { + .gpio = 27, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_soft_reset_cfg, + } + }, + /* AP2MDM_WAKEUP */ + { + .gpio = 35, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_wakeup, + } + }, +}; + +static struct gpiomux_setting mi2s_act_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mi2s_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct msm_gpiomux_config mpq8064_mi2s_configs[] __initdata = { + { + .gpio = 27, /* mi2s ws */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, + { + .gpio = 28, /* mi2s sclk */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, + { + .gpio = 29, /* mi2s dout3 */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, + { + .gpio = 30, /* mi2s dout2 */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, + + { + .gpio = 31, /* mi2s dout1 */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, + { + .gpio = 32, /* mi2s dout0 */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, + + { + .gpio = 33, /* mi2s mclk */ + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_act_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_sus_cfg, + }, + }, +}; +static struct msm_gpiomux_config apq8064_mxt_configs[] __initdata = { + { /* TS INTERRUPT */ + .gpio = 6, + .settings = { + [GPIOMUX_ACTIVE] = &mxt_int_act_cfg, + [GPIOMUX_SUSPENDED] = &mxt_int_sus_cfg, + }, + }, + { /* TS RESET */ + .gpio = 33, + .settings = { + [GPIOMUX_ACTIVE] = &mxt_reset_act_cfg, + [GPIOMUX_SUSPENDED] = &mxt_reset_sus_cfg, + }, + }, +}; + +static struct msm_gpiomux_config wcnss_5wire_interface[] = { + { + .gpio = 64, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 65, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 66, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 67, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 68, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, +}; + +static struct msm_gpiomux_config mpq8064_gsbi5_i2c_configs[] __initdata = { + { + .gpio = 53, /* GSBI5 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi5_active_cfg, + }, + }, + { + .gpio = 54, /* GSBI5 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi5_active_cfg, + }, + }, +}; + +static struct gpiomux_setting ir_suspended_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ir_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct msm_gpiomux_config mpq8064_ir_configs[] __initdata = { + { + .gpio = 88, /* GPIO IR */ + .settings = { + [GPIOMUX_SUSPENDED] = &ir_suspended_cfg, + [GPIOMUX_ACTIVE] = &ir_active_cfg, + }, + }, +}; + +static struct msm_gpiomux_config sx150x_int_configs[] __initdata = { + { + .gpio = 81, + .settings = { + [GPIOMUX_SUSPENDED] = &sx150x_suspended_cfg, + [GPIOMUX_ACTIVE] = &sx150x_active_cfg, + }, + }, +}; + +void __init apq8064_init_gpiomux(void) +{ + int rc; + + rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err(KERN_ERR "msm_gpiomux_init failed %d\n", rc); + return; + } + + msm_gpiomux_install(wcnss_5wire_interface, + ARRAY_SIZE(wcnss_5wire_interface)); + + if (machine_is_mpq8064_cdp() || machine_is_mpq8064_hrd() || + machine_is_mpq8064_dtv()) { + msm_gpiomux_install(mpq8064_gsbi5_i2c_configs, + ARRAY_SIZE(mpq8064_gsbi5_i2c_configs)); +#ifdef CONFIG_MSM_VCAP + msm_gpiomux_install(vcap_configs, + ARRAY_SIZE(vcap_configs)); +#endif + msm_gpiomux_install(sx150x_int_configs, + ARRAY_SIZE(sx150x_int_configs)); + } else { + #if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) + msm_gpiomux_install(apq8064_ethernet_configs, + ARRAY_SIZE(apq8064_ethernet_configs)); + #endif + + msm_gpiomux_install(apq8064_gsbi_configs, + ARRAY_SIZE(apq8064_gsbi_configs)); + } + + msm_gpiomux_install(apq8064_slimbus_config, + ARRAY_SIZE(apq8064_slimbus_config)); + + msm_gpiomux_install(apq8064_audio_codec_configs, + ARRAY_SIZE(apq8064_audio_codec_configs)); + + if (machine_is_mpq8064_cdp() || machine_is_mpq8064_hrd() || + machine_is_mpq8064_dtv()) { + msm_gpiomux_install(mpq8064_spkr_i2s_config, + ARRAY_SIZE(mpq8064_spkr_i2s_config)); + } + + pr_debug("%s(): audio-auxpcm: Include GPIO configs" + " as audio is not the primary user" + " for these GPIO Pins\n", __func__); + + if (machine_is_mpq8064_cdp() || machine_is_mpq8064_hrd() || + machine_is_mpq8064_dtv()) + msm_gpiomux_install(mpq8064_mi2s_configs, + ARRAY_SIZE(mpq8064_mi2s_configs)); + + msm_gpiomux_install(apq8064_ext_regulator_configs, + ARRAY_SIZE(apq8064_ext_regulator_configs)); + + if (machine_is_apq8064_mtp()) + msm_gpiomux_install(mdm_configs, + ARRAY_SIZE(mdm_configs)); + +#ifdef CONFIG_USB_EHCI_MSM_HSIC + if (machine_is_apq8064_mtp()) + msm_gpiomux_install(cyts_gpio_configs, + ARRAY_SIZE(cyts_gpio_configs)); + + if (machine_is_apq8064_mtp()) + msm_gpiomux_install(apq8064_hsic_configs, + ARRAY_SIZE(apq8064_hsic_configs)); +#endif + + if (machine_is_apq8064_cdp() || machine_is_apq8064_liquid()) + msm_gpiomux_install(apq8064_mxt_configs, + ARRAY_SIZE(apq8064_mxt_configs)); + + msm_gpiomux_install(apq8064_hdmi_configs, + ARRAY_SIZE(apq8064_hdmi_configs)); + + if (machine_is_mpq8064_cdp()) + msm_gpiomux_install(mpq8064_ir_configs, + ARRAY_SIZE(mpq8064_ir_configs)); +} diff --git a/arch/arm/mach-msm/board-8064-gpu.c b/arch/arm/mach-msm/board-8064-gpu.c new file mode 100644 index 0000000000000000000000000000000000000000..e24cac6581685bcfe655ad3f086457482e7769a0 --- /dev/null +++ b/arch/arm/mach-msm/board-8064-gpu.c @@ -0,0 +1,251 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "devices.h" +#include "board-8064.h" + +#ifdef CONFIG_MSM_DCVS +static struct msm_dcvs_freq_entry grp3d_freq[] = { + {0, 0, 333932}, + {0, 0, 497532}, + {0, 0, 707610}, + {0, 0, 844545}, +}; + +static struct msm_dcvs_core_info grp3d_core_info = { + .freq_tbl = &grp3d_freq[0], + .core_param = { + .max_time_us = 100000, + .num_freq = ARRAY_SIZE(grp3d_freq), + }, + .algo_param = { + .slack_time_us = 39000, + .disable_pc_threshold = 86000, + .ss_window_size = 1000000, + .ss_util_pct = 95, + .em_max_util_pct = 97, + .ss_iobusy_conv = 100, + }, +}; +#endif /* CONFIG_MSM_DCVS */ + +#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, + }, + { + .src = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp3d_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(1000), + }, + { + .src = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(1000), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2000), + }, + { + .src = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2000), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_high_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(3200), + }, + { + .src = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(3200), + }, +}; + +static struct msm_bus_vectors grp3d_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(4264), + }, + { + .src = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(4264), + }, +}; + +static struct msm_bus_paths grp3d_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp3d_init_vectors), + grp3d_init_vectors, + }, + { + ARRAY_SIZE(grp3d_low_vectors), + grp3d_low_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", +}; +#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 const struct kgsl_iommu_ctx kgsl_3d0_iommu0_ctxs[] = { + { "gfx3d_user", 0 }, + { "gfx3d_priv", 1 }, +}; + +static const struct kgsl_iommu_ctx kgsl_3d0_iommu1_ctxs[] = { + { "gfx3d1_user", 0 }, + { "gfx3d1_priv", 1 }, +}; + +static struct kgsl_device_iommu_data kgsl_3d0_iommu_data[] = { + { + .iommu_ctxs = kgsl_3d0_iommu0_ctxs, + .iommu_ctx_count = ARRAY_SIZE(kgsl_3d0_iommu0_ctxs), + .physstart = 0x07C00000, + .physend = 0x07C00000 + SZ_1M - 1, + }, + { + .iommu_ctxs = kgsl_3d0_iommu1_ctxs, + .iommu_ctx_count = ARRAY_SIZE(kgsl_3d0_iommu1_ctxs), + .physstart = 0x07D00000, + .physend = 0x07D00000 + SZ_1M - 1, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwrlevel = { + { + .gpu_freq = 400000000, + .bus_freq = 4, + .io_fraction = 0, + }, + { + .gpu_freq = 325000000, + .bus_freq = 3, + .io_fraction = 33, + }, + { + .gpu_freq = 200000000, + .bus_freq = 2, + .io_fraction = 100, + }, + { + .gpu_freq = 128000000, + .bus_freq = 1, + .io_fraction = 100, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 1, + .num_levels = 5, + .set_grp_async = NULL, + .idle_timeout = HZ/10, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE | KGSL_CLK_MEM_IFACE, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp3d_bus_scale_pdata, +#endif + .iommu_data = kgsl_3d0_iommu_data, + .iommu_count = ARRAY_SIZE(kgsl_3d0_iommu_data), +#ifdef CONFIG_MSM_DCVS + .core_info = &grp3d_core_info, +#endif +}; + +struct platform_device device_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 apq8064_init_gpu(void) +{ + platform_device_register(&device_kgsl_3d0); +} diff --git a/arch/arm/mach-msm/board-8064-pmic.c b/arch/arm/mach-msm/board-8064-pmic.c new file mode 100644 index 0000000000000000000000000000000000000000..877c9fc6d8e49030abb3c817600cc73d5aff1de2 --- /dev/null +++ b/arch/arm/mach-msm/board-8064-pmic.c @@ -0,0 +1,445 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "board-8064.h" + +struct pm8xxx_gpio_init { + unsigned gpio; + struct pm_gpio config; +}; + +struct pm8xxx_mpp_init { + unsigned mpp; + struct pm8xxx_mpp_config_data config; +}; + +#define PM8921_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 PM8921_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 PM8821_MPP_INIT(_mpp, _type, _level, _control) \ +{ \ + .mpp = PM8821_MPP_PM_TO_SYS(_mpp), \ + .config = { \ + .type = PM8XXX_MPP_TYPE_##_type, \ + .level = _level, \ + .control = PM8XXX_MPP_##_control, \ + } \ +} + +#define PM8921_GPIO_DISABLE(_gpio) \ + PM8921_GPIO_INIT(_gpio, PM_GPIO_DIR_IN, 0, 0, 0, PM_GPIO_VIN_S4, \ + 0, 0, 0, 1) + +#define PM8921_GPIO_OUTPUT(_gpio, _val, _strength) \ + PM8921_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, PM_GPIO_VIN_S4, \ + PM_GPIO_STRENGTH_##_strength, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8921_GPIO_OUTPUT_BUFCONF(_gpio, _val, _strength, _bufconf) \ + PM8921_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT,\ + PM_GPIO_OUT_BUF_##_bufconf, _val, \ + PM_GPIO_PULL_NO, PM_GPIO_VIN_S4, \ + PM_GPIO_STRENGTH_##_strength, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8921_GPIO_INPUT(_gpio, _pull) \ + PM8921_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 PM8921_GPIO_OUTPUT_FUNC(_gpio, _val, _func) \ + PM8921_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) + +#define PM8921_GPIO_OUTPUT_VIN(_gpio, _val, _vin) \ + PM8921_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, _vin, \ + PM_GPIO_STRENGTH_HIGH, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +/* Initial PM8921 GPIO configurations */ +static struct pm8xxx_gpio_init pm8921_gpios[] __initdata = { + PM8921_GPIO_OUTPUT(14, 1, HIGH), /* HDMI Mux Selector */ + PM8921_GPIO_OUTPUT(23, 0, HIGH), /* touchscreen power FET */ + PM8921_GPIO_OUTPUT_BUFCONF(25, 0, LOW, CMOS), /* DISP_RESET_N */ + PM8921_GPIO_OUTPUT_FUNC(26, 0, PM_GPIO_FUNC_2), /* Bl: Off, PWM mode */ + PM8921_GPIO_OUTPUT_VIN(30, 1, PM_GPIO_VIN_VPH), /* SMB349 susp line */ + PM8921_GPIO_OUTPUT_BUFCONF(36, 1, LOW, OPEN_DRAIN), + PM8921_GPIO_OUTPUT_FUNC(44, 0, PM_GPIO_FUNC_2), + PM8921_GPIO_OUTPUT(33, 0, HIGH), + PM8921_GPIO_OUTPUT(20, 0, HIGH), + PM8921_GPIO_INPUT(35, PM_GPIO_PULL_UP_30), + PM8921_GPIO_INPUT(38, PM_GPIO_PULL_UP_30), + /* TABLA CODEC RESET */ + PM8921_GPIO_OUTPUT(34, 1, MED), + PM8921_GPIO_INPUT(31, PM_GPIO_PULL_NO), + PM8921_GPIO_OUTPUT(13, 0, HIGH), /* PCIE_CLK_PWR_EN */ +}; + +static struct pm8xxx_gpio_init pm8921_mtp_kp_gpios[] __initdata = { + PM8921_GPIO_INPUT(3, PM_GPIO_PULL_UP_30), + PM8921_GPIO_INPUT(4, PM_GPIO_PULL_UP_30), +}; + +static struct pm8xxx_gpio_init pm8921_cdp_kp_gpios[] __initdata = { + PM8921_GPIO_INPUT(27, PM_GPIO_PULL_UP_30), + PM8921_GPIO_INPUT(42, PM_GPIO_PULL_UP_30), + PM8921_GPIO_INPUT(17, PM_GPIO_PULL_UP_1P5), /* SD_WP */ +}; + +/* Initial PM8XXX MPP configurations */ +static struct pm8xxx_mpp_init pm8xxx_mpps[] __initdata = { + PM8921_MPP_INIT(3, D_OUTPUT, PM8921_MPP_DIG_LEVEL_VPH, DOUT_CTRL_LOW), + PM8921_MPP_INIT(8, D_OUTPUT, PM8921_MPP_DIG_LEVEL_S4, DOUT_CTRL_LOW), + /*MPP9 is used to detect docking station connection/removal on Liquid*/ + PM8921_MPP_INIT(9, D_INPUT, PM8921_MPP_DIG_LEVEL_S4, DIN_TO_INT), + /* PCIE_RESET_N */ + PM8921_MPP_INIT(1, D_OUTPUT, PM8921_MPP_DIG_LEVEL_VPH, DOUT_CTRL_HIGH), +}; + +void __init apq8064_pm8xxx_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; + } + } + + if (machine_is_apq8064_cdp() || machine_is_apq8064_liquid()) + for (i = 0; i < ARRAY_SIZE(pm8921_cdp_kp_gpios); i++) { + rc = pm8xxx_gpio_config(pm8921_cdp_kp_gpios[i].gpio, + &pm8921_cdp_kp_gpios[i].config); + if (rc) { + pr_err("%s: pm8xxx_gpio_config: rc=%d\n", + __func__, rc); + break; + } + } + + if (machine_is_apq8064_mtp()) + for (i = 0; i < ARRAY_SIZE(pm8921_mtp_kp_gpios); i++) { + rc = pm8xxx_gpio_config(pm8921_mtp_kp_gpios[i].gpio, + &pm8921_mtp_kp_gpios[i].config); + if (rc) { + pr_err("%s: pm8xxx_gpio_config: rc=%d\n", + __func__, rc); + break; + } + } + + for (i = 0; i < ARRAY_SIZE(pm8xxx_mpps); i++) { + rc = pm8xxx_mpp_config(pm8xxx_mpps[i].mpp, + &pm8xxx_mpps[i].config); + if (rc) { + pr_err("%s: pm8xxx_mpp_config: rc=%d\n", __func__, rc); + break; + } + } +} + +static struct pm8xxx_pwrkey_platform_data apq8064_pm8921_pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 15625, + .wakeup = 1, +}; + +static struct pm8xxx_misc_platform_data apq8064_pm8921_misc_pdata = { + .priority = 0, +}; + +#define PM8921_LC_LED_MAX_CURRENT 4 /* I = 4mA */ +#define PM8921_LC_LED_LOW_CURRENT 1 /* I = 1mA */ +#define PM8XXX_LED_PWM_PERIOD 1000 +#define PM8XXX_LED_PWM_DUTY_MS 20 +/** + * PM8XXX_PWM_CHANNEL_NONE shall be used when LED shall not be + * driven using PWM feature. + */ +#define PM8XXX_PWM_CHANNEL_NONE -1 + +static struct led_info pm8921_led_info[] = { + [0] = { + .name = "led:red", + .default_trigger = "ac-online", + }, +}; + +static struct led_platform_data pm8921_led_core_pdata = { + .num_leds = ARRAY_SIZE(pm8921_led_info), + .leds = pm8921_led_info, +}; + +static int pm8921_led0_pwm_duty_pcts[56] = { + 1, 4, 8, 12, 16, 20, 24, 28, 32, 36, + 40, 44, 46, 52, 56, 60, 64, 68, 72, 76, + 80, 84, 88, 92, 96, 100, 100, 100, 98, 95, + 92, 88, 84, 82, 78, 74, 70, 66, 62, 58, + 58, 54, 50, 48, 42, 38, 34, 30, 26, 22, + 14, 10, 6, 4, 1 +}; + +static struct pm8xxx_pwm_duty_cycles pm8921_led0_pwm_duty_cycles = { + .duty_pcts = (int *)&pm8921_led0_pwm_duty_pcts, + .num_duty_pcts = ARRAY_SIZE(pm8921_led0_pwm_duty_pcts), + .duty_ms = PM8XXX_LED_PWM_DUTY_MS, + .start_idx = 0, +}; + +static struct pm8xxx_led_config pm8921_led_configs[] = { + [0] = { + .id = PM8XXX_ID_LED_0, + .mode = PM8XXX_LED_MODE_PWM2, + .max_current = PM8921_LC_LED_MAX_CURRENT, + .pwm_channel = 5, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD, + .pwm_duty_cycles = &pm8921_led0_pwm_duty_cycles, + }, +}; + +static struct pm8xxx_led_platform_data apq8064_pm8921_leds_pdata = { + .led_core = &pm8921_led_core_pdata, + .configs = pm8921_led_configs, + .num_configs = ARRAY_SIZE(pm8921_led_configs), +}; + +static struct pm8xxx_adc_amux apq8064_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}, + {"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}, + {"xo_therm", CHANNEL_MUXOFF, CHAN_PATH_SCALING1, AMUX_RSV0, + ADC_DECIMATION_TYPE2, ADC_SCALE_XOTHERM}, +}; + +static struct pm8xxx_adc_properties apq8064_pm8921_adc_data = { + .adc_vdd_reference = 1800, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, +}; + +static struct pm8xxx_adc_platform_data apq8064_pm8921_adc_pdata = { + .adc_channel = apq8064_pm8921_adc_channels_data, + .adc_num_board_channel = ARRAY_SIZE(apq8064_pm8921_adc_channels_data), + .adc_prop = &apq8064_pm8921_adc_data, + .adc_mpp_base = PM8921_MPP_PM_TO_SYS(1), +}; + +static struct pm8xxx_mpp_platform_data +apq8064_pm8921_mpp_pdata __devinitdata = { + .mpp_base = PM8921_MPP_PM_TO_SYS(1), +}; + +static struct pm8xxx_gpio_platform_data +apq8064_pm8921_gpio_pdata __devinitdata = { + .gpio_base = PM8921_GPIO_PM_TO_SYS(1), +}; + +static struct pm8xxx_irq_platform_data +apq8064_pm8921_irq_pdata __devinitdata = { + .irq_base = PM8921_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(74), + .irq_trigger_flag = IRQF_TRIGGER_LOW, + .dev_id = 0, +}; + +static struct pm8xxx_rtc_platform_data +apq8064_pm8921_rtc_pdata = { + .rtc_write_enable = false, + .rtc_alarm_powerup = false, +}; + +static int apq8064_pm8921_therm_mitigation[] = { + 1100, + 700, + 600, + 325, +}; + +#define MAX_VOLTAGE_MV 4200 +static struct pm8921_charger_platform_data +apq8064_pm8921_chg_pdata __devinitdata = { + .safety_time = 180, + .update_time = 60000, + .max_voltage = MAX_VOLTAGE_MV, + .min_voltage = 3200, + .resume_voltage_delta = 100, + .term_current = 100, + .cool_temp = 10, + .warm_temp = 40, + .temp_check_period = 1, + .max_bat_chg_current = 1100, + .cool_bat_chg_current = 350, + .warm_bat_chg_current = 350, + .cool_bat_voltage = 4100, + .warm_bat_voltage = 4100, + .thermal_mitigation = apq8064_pm8921_therm_mitigation, + .thermal_levels = ARRAY_SIZE(apq8064_pm8921_therm_mitigation), +}; + +static struct pm8xxx_ccadc_platform_data +apq8064_pm8xxx_ccadc_pdata = { + .r_sense = 10, +}; + +static struct pm8921_bms_platform_data +apq8064_pm8921_bms_pdata __devinitdata = { + .battery_type = BATT_UNKNOWN, + .r_sense = 10, + .i_test = 2500, + .v_failure = 3000, + .calib_delay_ms = 600000, + .max_voltage_uv = MAX_VOLTAGE_MV * 1000, +}; + +static struct pm8921_platform_data +apq8064_pm8921_platform_data __devinitdata = { + .regulator_pdatas = msm8064_pm8921_regulator_pdata, + .irq_pdata = &apq8064_pm8921_irq_pdata, + .gpio_pdata = &apq8064_pm8921_gpio_pdata, + .mpp_pdata = &apq8064_pm8921_mpp_pdata, + .rtc_pdata = &apq8064_pm8921_rtc_pdata, + .pwrkey_pdata = &apq8064_pm8921_pwrkey_pdata, + .misc_pdata = &apq8064_pm8921_misc_pdata, + .leds_pdata = &apq8064_pm8921_leds_pdata, + .adc_pdata = &apq8064_pm8921_adc_pdata, + .charger_pdata = &apq8064_pm8921_chg_pdata, + .bms_pdata = &apq8064_pm8921_bms_pdata, + .ccadc_pdata = &apq8064_pm8xxx_ccadc_pdata, +}; + +static struct pm8xxx_irq_platform_data +apq8064_pm8821_irq_pdata __devinitdata = { + .irq_base = PM8821_IRQ_BASE, + .devirq = PM8821_SEC_IRQ_N, + .irq_trigger_flag = IRQF_TRIGGER_HIGH, + .dev_id = 1, +}; + +static struct pm8xxx_mpp_platform_data +apq8064_pm8821_mpp_pdata __devinitdata = { + .mpp_base = PM8821_MPP_PM_TO_SYS(1), +}; + +static struct pm8821_platform_data +apq8064_pm8821_platform_data __devinitdata = { + .irq_pdata = &apq8064_pm8821_irq_pdata, + .mpp_pdata = &apq8064_pm8821_mpp_pdata, +}; + +static struct msm_ssbi_platform_data apq8064_ssbi_pm8921_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8921-core", + .platform_data = &apq8064_pm8921_platform_data, + }, +}; + +static struct msm_ssbi_platform_data apq8064_ssbi_pm8821_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8821-core", + .platform_data = &apq8064_pm8821_platform_data, + }, +}; + +void __init apq8064_init_pmic(void) +{ + pmic_reset_irq = PM8921_IRQ_BASE + PM8921_RESOUT_IRQ; + + apq8064_device_ssbi_pmic1.dev.platform_data = + &apq8064_ssbi_pm8921_pdata; + apq8064_device_ssbi_pmic2.dev.platform_data = + &apq8064_ssbi_pm8821_pdata; + apq8064_pm8921_platform_data.num_regulators = + msm8064_pm8921_regulator_pdata_len; + + if (machine_is_apq8064_rumi3()) { + apq8064_pm8921_irq_pdata.devirq = 0; + apq8064_pm8821_irq_pdata.devirq = 0; + } else if (machine_is_apq8064_mtp()) { + apq8064_pm8921_bms_pdata.battery_type = BATT_PALLADIUM; + } else if (machine_is_apq8064_liquid()) { + apq8064_pm8921_bms_pdata.battery_type = BATT_DESAY; + } +} diff --git a/arch/arm/mach-msm/board-8064-regulator.c b/arch/arm/mach-msm/board-8064-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..40222b8a45ea688b02648c41c5c3149605199a89 --- /dev/null +++ b/arch/arm/mach-msm/board-8064-regulator.c @@ -0,0 +1,619 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "board-8064.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("mipi_csi_vdd", "msm_csid.0"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.1"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.2"), + REGULATOR_SUPPLY("lvds_pll_vdda", "lvds.0"), + REGULATOR_SUPPLY("dsi1_pll_vdda", "mipi_dsi.1"), +}; +VREG_CONSUMERS(L3) = { + REGULATOR_SUPPLY("8921_l3", NULL), + REGULATOR_SUPPLY("HSUSB_3p3", "msm_otg"), + REGULATOR_SUPPLY("HSUSB_3p3", "msm_ehci_host.0"), + REGULATOR_SUPPLY("HSUSB_3p3", "msm_ehci_host.1"), +}; +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("cam_vana", "4-001a"), + REGULATOR_SUPPLY("cam_vana", "4-0048"), + REGULATOR_SUPPLY("cam_vana", "4-006c"), + REGULATOR_SUPPLY("cam_vana", "4-0034"), + REGULATOR_SUPPLY("cam_vana", "4-0020"), +}; +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("dsi1_avdd", "mipi_dsi.1"), +}; +VREG_CONSUMERS(L12) = { + REGULATOR_SUPPLY("cam_vdig", "4-001a"), + REGULATOR_SUPPLY("cam_vdig", "4-0048"), + REGULATOR_SUPPLY("cam_vdig", "4-006c"), + REGULATOR_SUPPLY("cam_vdig", "4-0034"), + REGULATOR_SUPPLY("cam_vdig", "4-0020"), + REGULATOR_SUPPLY("8921_l12", NULL), +}; +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", "4-001a"), + REGULATOR_SUPPLY("cam_vaf", "4-0048"), + REGULATOR_SUPPLY("cam_vaf", "4-006c"), + REGULATOR_SUPPLY("cam_vaf", "4-0034"), + REGULATOR_SUPPLY("cam_vaf", "4-0020"), +}; +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("pll_vdd", "pil_qdsp6v4.1"), + REGULATOR_SUPPLY("pll_vdd", "pil_qdsp6v4.2"), + REGULATOR_SUPPLY("HSUSB_1p8", "msm_ehci_host.0"), + REGULATOR_SUPPLY("HSUSB_1p8", "msm_ehci_host.1"), +}; +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"), + REGULATOR_SUPPLY("VDDD_CDC_D", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "tabla2x-slim"), +}; +VREG_CONSUMERS(L26) = { + REGULATOR_SUPPLY("8921_l26", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.0"), +}; +VREG_CONSUMERS(L27) = { + REGULATOR_SUPPLY("8921_l27", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.2"), +}; +VREG_CONSUMERS(L28) = { + REGULATOR_SUPPLY("8921_l28", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.1"), +}; +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("HSUSB_VDDCX", "msm_ehci_host.0"), + REGULATOR_SUPPLY("HSUSB_VDDCX", "msm_ehci_host.1"), + REGULATOR_SUPPLY("HSIC_VDDCX", "msm_hsic_host"), + REGULATOR_SUPPLY("riva_vddcx", "wcnss_wlan.0"), + REGULATOR_SUPPLY("vp_pcie", "msm_pcie"), + REGULATOR_SUPPLY("vptx_pcie", "msm_pcie"), +}; +VREG_CONSUMERS(S4) = { + REGULATOR_SUPPLY("8921_s4", NULL), + REGULATOR_SUPPLY("sdc_vccq", "msm_sdcc.1"), + 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"), + REGULATOR_SUPPLY("VDDIO_CDC", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDD_CP", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "tabla2x-slim"), + REGULATOR_SUPPLY("riva_vddpx", "wcnss_wlan.0"), + REGULATOR_SUPPLY("vcc_i2c", "3-005b"), + REGULATOR_SUPPLY("vcc_i2c", "3-0024"), + REGULATOR_SUPPLY("vddp", "0-0048"), + REGULATOR_SUPPLY("hdmi_lvl_tsl", "hdmi_msm.0"), +}; +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("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), +}; +VREG_CONSUMERS(LVS5) = { + REGULATOR_SUPPLY("8921_lvs5", NULL), + REGULATOR_SUPPLY("cam_vio", "4-001a"), + REGULATOR_SUPPLY("cam_vio", "4-0048"), + REGULATOR_SUPPLY("cam_vio", "4-006c"), + REGULATOR_SUPPLY("cam_vio", "4-0034"), + REGULATOR_SUPPLY("cam_vio", "4-0020"), +}; +VREG_CONSUMERS(LVS6) = { + REGULATOR_SUPPLY("8921_lvs6", NULL), + REGULATOR_SUPPLY("vdd_pcie_vph", "msm_pcie"), +}; +VREG_CONSUMERS(LVS7) = { + REGULATOR_SUPPLY("8921_lvs7", NULL), + REGULATOR_SUPPLY("pll_vdd", "pil_riva"), + REGULATOR_SUPPLY("lvds_vdda", "lvds.0"), + REGULATOR_SUPPLY("dsi1_vddio", "mipi_dsi.1"), + REGULATOR_SUPPLY("hdmi_vdda", "hdmi_msm.0"), +}; +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(8821_S0) = { + REGULATOR_SUPPLY("8821_s0", NULL), + REGULATOR_SUPPLY("krait2", NULL), +}; +VREG_CONSUMERS(8821_S1) = { + REGULATOR_SUPPLY("8821_s1", NULL), + REGULATOR_SUPPLY("krait3", NULL), +}; +VREG_CONSUMERS(EXT_5V) = { + REGULATOR_SUPPLY("ext_5v", NULL), + REGULATOR_SUPPLY("ext_ddr3", NULL), + REGULATOR_SUPPLY("vbus", "msm_ehci_host.0"), +}; +VREG_CONSUMERS(EXT_MPP8) = { + REGULATOR_SUPPLY("ext_mpp8", NULL), + REGULATOR_SUPPLY("vbus", "msm_ehci_host.1"), +}; +VREG_CONSUMERS(EXT_3P3V) = { + REGULATOR_SUPPLY("ext_3p3v", NULL), + REGULATOR_SUPPLY("vdd_io", "spi0.2"), + REGULATOR_SUPPLY("mhl_ext_3p3v", "msm_otg"), + REGULATOR_SUPPLY("lvds_vccs_3p3v", "lvds.0"), + REGULATOR_SUPPLY("dsi1_vccs_3p3v", "mipi_dsi.1"), + REGULATOR_SUPPLY("hdmi_mux_vdd", "hdmi_msm.0"), + REGULATOR_SUPPLY("pcie_ext_3p3v", "msm_pcie"), +}; +VREG_CONSUMERS(EXT_TS_SW) = { + REGULATOR_SUPPLY("ext_ts_sw", NULL), + REGULATOR_SUPPLY("vdd_ana", "3-005b"), +}; +VREG_CONSUMERS(FRC_5V) = { + REGULATOR_SUPPLY("frc_5v", NULL), +}; +VREG_CONSUMERS(AVC_1P2V) = { + REGULATOR_SUPPLY("avc_1p2v", NULL), +}; +VREG_CONSUMERS(AVC_1P8V) = { + REGULATOR_SUPPLY("avc_1p8v", NULL), +}; +VREG_CONSUMERS(AVC_2P2V) = { + REGULATOR_SUPPLY("avc_2p2v", NULL), +}; +VREG_CONSUMERS(AVC_5V) = { + REGULATOR_SUPPLY("avc_5v", NULL), +}; +VREG_CONSUMERS(AVC_3P3V) = { + REGULATOR_SUPPLY("avc_3p3v", NULL), +}; + +#define PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, _modes, _ops, \ + _apply_uV, _pull_down, _always_on, _supply_regulator, \ + _system_uA, _enable_time, _reg_id) \ + { \ + .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, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pull_down_enable = _pull_down, \ + .system_uA = _system_uA, \ + .enable_time = _enable_time, \ + } + +#define PM8XXX_LDO(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_NLDO1200(_id, _name, _always_on, _pull_down, _min_uV, \ + _max_uV, _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_SMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_FTSMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_VS(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +#define PM8XXX_VS300(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +#define PM8XXX_NCP(_id, _name, _always_on, _min_uV, _max_uV, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, 0, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, 0, 0, \ + _always_on, _supply_regulator, 0, _enable_time, _reg_id) + +/* Pin control initialization */ +#define PM8XXX_PC(_id, _name, _always_on, _pin_fn, _pin_ctrl, \ + _supply_regulator, _reg_id) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .always_on = _always_on, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id##_PC), \ + .consumer_supplies = vreg_consumers_##_id##_PC, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pin_fn = PM8XXX_VREG_PIN_FN_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +#define GPIO_VREG(_id, _reg_name, _gpio_label, _gpio, _supply_regulator) \ + [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, \ + .supply_regulator = _supply_regulator, \ + }, \ + .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, \ + } + +#define RPM_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, _default_uV, \ + _peak_uA, _avg_uA, _pull_down, _pin_ctrl, _freq, _pin_fn, \ + _force_mode, _sleep_set_force_mode, _power_mode, _state, \ + _sleep_selectable, _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 = _min_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 = RPM_VREG_ID_PM8921_##_id, \ + .default_uV = _default_uV, \ + .peak_uA = _peak_uA, \ + .avg_uA = _avg_uA, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = _pin_ctrl, \ + .freq = RPM_VREG_FREQ_##_freq, \ + .pin_fn = _pin_fn, \ + .force_mode = _force_mode, \ + .sleep_set_force_mode = _sleep_set_force_mode, \ + .power_mode = _power_mode, \ + .state = _state, \ + .sleep_selectable = _sleep_selectable, \ + .system_uA = _system_uA, \ + } + +#define RPM_LDO(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _init_peak_uA) \ + RPM_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, _max_uV, _init_peak_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, RPM_VREG_POWER_MODE_8960_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, _system_uA) + +#define RPM_SMPS(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _freq, _force_mode, \ + _sleep_set_force_mode) \ + RPM_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, _max_uV, _system_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_##_force_mode, \ + RPM_VREG_FORCE_MODE_8960_##_sleep_set_force_mode, \ + RPM_VREG_POWER_MODE_8960_PWM, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on, _supply_regulator, _system_uA) + +#define RPM_VS(_id, _always_on, _pd, _sleep_selectable, _supply_regulator) \ + RPM_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, 0, 1000, 1000, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, RPM_VREG_POWER_MODE_8960_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +#define RPM_NCP(_id, _always_on, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _freq) \ + RPM_INIT(_id, _min_uV, _max_uV, 0, REGULATOR_CHANGE_VOLTAGE \ + | REGULATOR_CHANGE_STATUS, 0, _max_uV, 1000, 1000, 0, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, RPM_VREG_POWER_MODE_8960_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +/* Pin control initialization */ +#define RPM_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 = RPM_VREG_ID_PM8921_##_id##_PC, \ + .pin_fn = RPM_VREG_PIN_FN_8960_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +/* GPIO regulator constraints */ +struct gpio_regulator_platform_data +apq8064_gpio_regulator_pdata[] __devinitdata = { + /* ID vreg_name gpio_label gpio supply */ + GPIO_VREG(EXT_5V, "ext_5v", "ext_5v_en", PM8921_MPP_PM_TO_SYS(7), NULL), + GPIO_VREG(EXT_3P3V, "ext_3p3v", "ext_3p3v_en", + APQ8064_EXT_3P3V_REG_EN_GPIO, NULL), + GPIO_VREG(EXT_TS_SW, "ext_ts_sw", "ext_ts_sw_en", + PM8921_GPIO_PM_TO_SYS(23), "ext_3p3v"), + GPIO_VREG(EXT_MPP8, "ext_mpp8", "ext_mpp8_en", + PM8921_MPP_PM_TO_SYS(8), NULL), +}; + +struct gpio_regulator_platform_data +mpq8064_gpio_regulator_pdata[] __devinitdata = { + GPIO_VREG(FRC_5V, "frc_5v", "frc_5v_en", SX150X_GPIO(4, 10), NULL), + GPIO_VREG(AVC_1P2V, "avc_1p2v", "avc_1p2v_en", SX150X_GPIO(4, 2), NULL), + GPIO_VREG(AVC_1P8V, "avc_1p8v", "avc_1p8v_en", SX150X_GPIO(4, 4), NULL), + GPIO_VREG(AVC_2P2V, "avc_2p2v", "avc_2p2v_en", + SX150X_GPIO(4, 14), NULL), + GPIO_VREG(AVC_5V, "avc_5v", "avc_5v_en", SX150X_GPIO(4, 3), NULL), + GPIO_VREG(AVC_3P3V, "avc_3p3v", "avc_3p3v_en", + SX150X_GPIO(4, 15), "avc_5v"), +}; + +/* SAW regulator constraints */ +struct regulator_init_data msm8064_saw_regulator_pdata_8921_s5 = + /* ID vreg_name min_uV max_uV */ + SAW_VREG_INIT(S5, "8921_s5", 950000, 1300000); +struct regulator_init_data msm8064_saw_regulator_pdata_8921_s6 = + SAW_VREG_INIT(S6, "8921_s6", 950000, 1300000); + +struct regulator_init_data msm8064_saw_regulator_pdata_8821_s0 = + /* ID vreg_name min_uV max_uV */ + SAW_VREG_INIT(8821_S0, "8821_s0", 950000, 1300000); +struct regulator_init_data msm8064_saw_regulator_pdata_8821_s1 = + SAW_VREG_INIT(8821_S1, "8821_s1", 950000, 1300000); + +/* PM8921 regulator constraints */ +struct pm8xxx_regulator_platform_data +msm8064_pm8921_regulator_pdata[] __devinitdata = { + /* + * ID name always_on pd min_uV max_uV en_t supply + * system_uA reg_ID + */ + PM8XXX_NLDO1200(L26, "8921_l26", 0, 1, 375000, 1050000, 200, "8921_s7", + 0, 1), + + /* ID name always_on pd en_t supply reg_ID */ + PM8XXX_VS300(USB_OTG, "8921_usb_otg", 0, 0, 0, "ext_5v", 2), + PM8XXX_VS300(HDMI_MVS, "8921_hdmi_mvs", 0, 1, 0, "ext_5v", 3), +}; + +static struct rpm_regulator_init_data +apq8064_rpm_regulator_init_data[] __devinitdata = { + /* ID a_on pd ss min_uV max_uV supply sys_uA freq fm ss_fm */ + RPM_SMPS(S1, 1, 1, 0, 1225000, 1225000, NULL, 100000, 3p20, NONE, NONE), + RPM_SMPS(S2, 0, 1, 0, 1300000, 1300000, NULL, 0, 1p60, NONE, NONE), + RPM_SMPS(S3, 0, 1, 1, 500000, 1150000, NULL, 100000, 4p80, NONE, NONE), + RPM_SMPS(S4, 1, 1, 0, 1800000, 1800000, NULL, 100000, 1p60, AUTO, AUTO), + RPM_SMPS(S7, 0, 1, 0, 1300000, 1300000, NULL, 100000, 3p20, NONE, NONE), + RPM_SMPS(S8, 0, 1, 0, 2200000, 2200000, NULL, 0, 1p60, NONE, NONE), + + /* ID a_on pd ss min_uV max_uV supply sys_uA init_ip */ + RPM_LDO(L1, 1, 1, 0, 1100000, 1100000, "8921_s4", 0, 1000), + RPM_LDO(L2, 0, 1, 0, 1200000, 1200000, "8921_s4", 0, 0), + RPM_LDO(L3, 0, 1, 0, 3075000, 3075000, NULL, 0, 0), + RPM_LDO(L4, 1, 1, 0, 1800000, 1800000, NULL, 0, 10000), + RPM_LDO(L5, 0, 1, 0, 2950000, 2950000, NULL, 0, 0), + RPM_LDO(L6, 0, 1, 0, 2950000, 2950000, NULL, 0, 0), + RPM_LDO(L7, 0, 1, 0, 1850000, 2950000, NULL, 0, 0), + RPM_LDO(L8, 0, 1, 0, 2800000, 2800000, NULL, 0, 0), + RPM_LDO(L9, 0, 1, 0, 3000000, 3000000, NULL, 0, 0), + RPM_LDO(L10, 0, 1, 0, 2900000, 2900000, NULL, 0, 0), + RPM_LDO(L11, 0, 1, 0, 3000000, 3000000, NULL, 0, 0), + RPM_LDO(L12, 0, 1, 0, 1200000, 1200000, "8921_s4", 0, 0), + RPM_LDO(L14, 0, 1, 0, 1800000, 1800000, NULL, 0, 0), + RPM_LDO(L15, 0, 1, 0, 1800000, 2950000, NULL, 0, 0), + RPM_LDO(L16, 0, 1, 0, 2800000, 2800000, NULL, 0, 0), + RPM_LDO(L17, 0, 1, 0, 2000000, 2000000, NULL, 0, 0), + RPM_LDO(L18, 0, 1, 0, 1300000, 1800000, "8921_s4", 0, 0), + RPM_LDO(L21, 0, 1, 0, 1050000, 1050000, NULL, 0, 0), + RPM_LDO(L22, 0, 1, 0, 2600000, 2600000, NULL, 0, 0), + RPM_LDO(L23, 0, 1, 0, 1800000, 1800000, NULL, 0, 0), + RPM_LDO(L24, 0, 1, 1, 750000, 1150000, "8921_s1", 10000, 10000), + RPM_LDO(L25, 1, 1, 0, 1225000, 1225000, "8921_s1", 10000, 10000), + RPM_LDO(L27, 0, 1, 0, 1100000, 1100000, "8921_s7", 0, 0), + RPM_LDO(L28, 0, 1, 0, 1050000, 1050000, "8921_s7", 0, 0), + RPM_LDO(L29, 0, 1, 0, 2000000, 2000000, NULL, 0, 0), + + /* ID a_on pd ss supply */ + RPM_VS(LVS1, 0, 1, 0, "8921_s4"), + RPM_VS(LVS2, 0, 1, 0, "8921_s1"), + RPM_VS(LVS3, 0, 1, 0, "8921_s4"), + RPM_VS(LVS4, 0, 1, 0, "8921_s4"), + RPM_VS(LVS5, 0, 1, 0, "8921_s4"), + RPM_VS(LVS6, 0, 1, 0, "8921_s4"), + RPM_VS(LVS7, 0, 1, 1, "8921_s4"), + + /* ID a_on ss min_uV max_uV supply freq */ + RPM_NCP(NCP, 0, 0, 1800000, 1800000, "8921_l6", 1p60), +}; + +int msm8064_pm8921_regulator_pdata_len __devinitdata = + ARRAY_SIZE(msm8064_pm8921_regulator_pdata); + +struct rpm_regulator_platform_data apq8064_rpm_regulator_pdata __devinitdata = { + .init_data = apq8064_rpm_regulator_init_data, + .num_regulators = ARRAY_SIZE(apq8064_rpm_regulator_init_data), + .version = RPM_VREG_VERSION_8960, + .vreg_id_vdd_mem = RPM_VREG_ID_PM8921_L24, + .vreg_id_vdd_dig = RPM_VREG_ID_PM8921_S3, +}; diff --git a/arch/arm/mach-msm/board-8064-storage.c b/arch/arm/mach-msm/board-8064-storage.c new file mode 100644 index 0000000000000000000000000000000000000000..ed186c39f42661d84bd83a58f648e8f10207bca3 --- /dev/null +++ b/arch/arm/mach-msm/board-8064-storage.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "board-8064.h" +#include "board-storage-common-a.h" + + +/* APQ8064 has 4 SDCC controllers */ +enum sdcc_controllers { + SDCC1, + SDCC2, + SDCC3, + SDCC4, + MAX_SDCC_CONTROLLER +}; + +/* All SDCC controllers require VDD/VCC voltage */ +static struct msm_mmc_reg_data mmc_vdd_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : eMMC card connected */ + [SDCC1] = { + .name = "sdc_vdd", + .high_vol_level = 2950000, + .low_vol_level = 2950000, + .always_on = 1, + .lpm_sup = 1, + .lpm_uA = 9000, + .hpm_uA = 200000, /* 200mA */ + }, + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .name = "sdc_vdd", + .high_vol_level = 2950000, + .low_vol_level = 2950000, + .hpm_uA = 800000, /* 800mA */ + } +}; + +/* 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", + .always_on = 1, + .high_vol_level = 1800000, + .low_vol_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", + .high_vol_level = 2950000, + .low_vol_level = 1850000, + .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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + {TLMM_PULL_SDC1_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC1_DATA, GPIO_CFG_PULL_UP} +}; + +/* 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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_UP} +}; + +static 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) + }, +}; + +static 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) + }, +}; + +static 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] + }, +}; + +static 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], + }, +}; + +#define MSM_MPM_PIN_SDC1_DAT1 17 +#define MSM_MPM_PIN_SDC3_DAT1 21 + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static unsigned int sdc1_sup_clk_rates[] = { + 400000, 24000000, 48000000, 96000000 +}; + +static struct mmc_platform_data 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), + .pclk_src_dfab = 1, + .nonremovable = 1, + .pin_data = &mmc_slot_pin_data[SDCC1], + .vreg_data = &mmc_slot_vreg_data[SDCC1], + .uhs_caps = MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50, + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC1_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +static struct mmc_platform_data *apq8064_sdc1_pdata = &sdc1_data; +#else +static struct mmc_platform_data *apq8064_sdc1_pdata; +#endif + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static unsigned int sdc3_sup_clk_rates[] = { + 400000, 24000000, 48000000, 96000000, 192000000 +}; + +static struct mmc_platform_data 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), + .pclk_src_dfab = 1, + .pin_data = &mmc_slot_pin_data[SDCC3], + .vreg_data = &mmc_slot_vreg_data[SDCC3], + .wpswitch_gpio = PM8921_GPIO_PM_TO_SYS(17), + .wpswitch_polarity = 1, + .status_gpio = 26, + .status_irq = MSM_GPIO_TO_INT(26), + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + .is_status_gpio_active_low = 1, + .xpc_cap = 1, + .uhs_caps = (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_DDR50 | + MMC_CAP_UHS_SDR104 | MMC_CAP_MAX_CURRENT_800), + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC3_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +static struct mmc_platform_data *apq8064_sdc3_pdata = &sdc3_data; +#else +static struct mmc_platform_data *apq8064_sdc3_pdata; +#endif + +void __init apq8064_init_mmc(void) +{ + if ((machine_is_apq8064_rumi3()) || machine_is_apq8064_sim()) { + if (apq8064_sdc1_pdata) { + if (machine_is_apq8064_sim()) + apq8064_sdc1_pdata->disable_bam = true; + apq8064_sdc1_pdata->disable_runtime_pm = true; + apq8064_sdc1_pdata->disable_cmd23 = true; + } + if (apq8064_sdc3_pdata) { + if (machine_is_apq8064_sim()) + apq8064_sdc3_pdata->disable_bam = true; + apq8064_sdc3_pdata->disable_runtime_pm = true; + apq8064_sdc3_pdata->disable_cmd23 = true; + } + } + + if (apq8064_sdc1_pdata) + apq8064_add_sdcc(1, apq8064_sdc1_pdata); + + if (apq8064_sdc3_pdata) { + if (!machine_is_apq8064_cdp()) { + apq8064_sdc3_pdata->wpswitch_gpio = 0; + apq8064_sdc3_pdata->wpswitch_polarity = 0; + } + if (machine_is_mpq8064_cdp() || machine_is_mpq8064_hrd() || + machine_is_mpq8064_dtv()) { + apq8064_sdc3_pdata->status_gpio = + PM8921_GPIO_PM_TO_SYS(31); + apq8064_sdc3_pdata->status_irq = + PM8921_GPIO_IRQ(PM8921_IRQ_BASE, 31); + } + apq8064_add_sdcc(3, apq8064_sdc3_pdata); + } +} diff --git a/arch/arm/mach-msm/board-8064.c b/arch/arm/mach-msm/board-8064.c new file mode 100644 index 0000000000000000000000000000000000000000..cdc6b5a53162f30d3b0c6a8269bd08d4e2114f7e --- /dev/null +++ b/arch/arm/mach-msm/board-8064.c @@ -0,0 +1,3070 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +#include +#include "timer.h" +#include "devices.h" +#include +#include +#ifdef CONFIG_ANDROID_PMEM +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_watchdog.h" +#include "board-8064.h" +#include "acpuclock.h" +#include "spm.h" +#include +#include "rpm_resources.h" +#include "pm.h" +#include "pm-boot.h" +#include "devices-msm8x60.h" +#include "smd_private.h" + +#define MSM_PMEM_ADSP_SIZE 0x7800000 +#define MSM_PMEM_AUDIO_SIZE 0x4CF000 +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +#define MSM_PMEM_SIZE 0x4000000 /* 64 Mbytes */ +#else +#define MSM_PMEM_SIZE 0x4000000 /* 64 Mbytes */ +#endif + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +#define HOLE_SIZE 0x20000 +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x65000 +#ifdef CONFIG_MSM_IOMMU +#define MSM_ION_MM_SIZE 0x3800000 +#define MSM_ION_SF_SIZE 0 +#define MSM_ION_QSECOM_SIZE 0x780000 /* (7.5MB) */ +#define MSM_ION_HEAP_NUM 7 +#else +#define MSM_ION_MM_SIZE MSM_PMEM_ADSP_SIZE +#define MSM_ION_SF_SIZE MSM_PMEM_SIZE +#define MSM_ION_QSECOM_SIZE 0x600000 /* (6MB) */ +#define MSM_ION_HEAP_NUM 8 +#endif +#define MSM_ION_MM_FW_SIZE (0x200000 - HOLE_SIZE) /* (2MB - 128KB) */ +#define MSM_ION_MFC_SIZE SZ_8K +#define MSM_ION_AUDIO_SIZE MSM_PMEM_AUDIO_SIZE +#else +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x110C000 +#define MSM_ION_HEAP_NUM 1 +#endif + +#define APQ8064_FIXED_AREA_START (0xa0000000 - (MSM_ION_MM_FW_SIZE + \ + HOLE_SIZE)) +#define MAX_FIXED_AREA_SIZE 0x10000000 +#define MSM_MM_FW_SIZE (0x200000 - HOLE_SIZE) +#define APQ8064_FW_START APQ8064_FIXED_AREA_START + +/* PCIe power enable pmic gpio */ +#define PCIE_PWR_EN_PMIC_GPIO 13 +#define PCIE_RST_N_PMIC_MPP 1 + +#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 +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +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 apq8064_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 apq8064_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 apq8064_android_pmem_audio_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_audio_pdata }, +}; +#endif /* CONFIG_MSM_MULTIMEDIA_USE_ION */ +#endif /* CONFIG_ANDROID_PMEM */ + +struct fmem_platform_data apq8064_fmem_pdata = { +}; + +static struct memtype_reserve apq8064_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static void __init reserve_rtb_memory(void) +{ +#if defined(CONFIG_MSM_RTB) + apq8064_reserve_table[MEMTYPE_EBI1].size += apq8064_rtb_pdata.size; +#endif +} + + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_pdata.size = pmem_size; + android_pmem_audio_pdata.size = MSM_PMEM_AUDIO_SIZE; +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +} + +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + apq8064_reserve_table[p->memory_type].size += p->size; +} +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ + apq8064_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif /*CONFIG_ANDROID_PMEM*/ +} + +static int apq8064_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +#define FMEM_ENABLED 0 + +#ifdef CONFIG_ION_MSM +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +static struct ion_cp_heap_pdata cp_mm_apq8064_ion_pdata = { + .permission_type = IPT_TYPE_MM_CARVEOUT, + .align = PAGE_SIZE, + .reusable = FMEM_ENABLED, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_MIDDLE, +}; + +static struct ion_cp_heap_pdata cp_mfc_apq8064_ion_pdata = { + .permission_type = IPT_TYPE_MFC_SHAREDMEM, + .align = PAGE_SIZE, + .reusable = 0, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_HIGH, +}; + +static struct ion_co_heap_pdata co_apq8064_ion_pdata = { + .adjacent_mem_id = INVALID_HEAP_ID, + .align = PAGE_SIZE, + .mem_is_fmem = 0, +}; + +static struct ion_co_heap_pdata fw_co_apq8064_ion_pdata = { + .adjacent_mem_id = ION_CP_MM_HEAP_ID, + .align = SZ_128K, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_LOW, +}; +#endif + +/** + * These heaps are listed in the order they will be allocated. Due to + * video hardware restrictions and content protection the FW heap has to + * be allocated adjacent (below) the MM heap and the MFC heap has to be + * allocated after the MM heap to ensure MFC heap is not more than 256MB + * away from the base address of the FW heap. + * However, the order of FW heap and MM heap doesn't matter since these + * two heaps are taken care of by separate code to ensure they are adjacent + * to each other. + * Don't swap the order unless you know what you are doing! + */ +static struct ion_platform_data apq8064_ion_pdata = { + .nr = MSM_ION_HEAP_NUM, + .heaps = { + { + .id = ION_SYSTEM_HEAP_ID, + .type = ION_HEAP_TYPE_SYSTEM, + .name = ION_VMALLOC_HEAP_NAME, + }, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + { + .id = ION_CP_MM_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MM_HEAP_NAME, + .size = MSM_ION_MM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mm_apq8064_ion_pdata, + }, + { + .id = ION_MM_FIRMWARE_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_MM_FIRMWARE_HEAP_NAME, + .size = MSM_ION_MM_FW_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &fw_co_apq8064_ion_pdata, + }, + { + .id = ION_CP_MFC_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MFC_HEAP_NAME, + .size = MSM_ION_MFC_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mfc_apq8064_ion_pdata, + }, +#ifndef CONFIG_MSM_IOMMU + { + .id = ION_SF_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_SF_HEAP_NAME, + .size = MSM_ION_SF_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_apq8064_ion_pdata, + }, +#endif + { + .id = ION_IOMMU_HEAP_ID, + .type = ION_HEAP_TYPE_IOMMU, + .name = ION_IOMMU_HEAP_NAME, + }, + { + .id = ION_QSECOM_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_QSECOM_HEAP_NAME, + .size = MSM_ION_QSECOM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_apq8064_ion_pdata, + }, + { + .id = ION_AUDIO_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_AUDIO_HEAP_NAME, + .size = MSM_ION_AUDIO_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_apq8064_ion_pdata, + }, +#endif + } +}; + +static struct platform_device apq8064_ion_dev = { + .name = "ion-msm", + .id = 1, + .dev = { .platform_data = &apq8064_ion_pdata }, +}; +#endif + +static struct platform_device apq8064_fmem_device = { + .name = "fmem", + .id = 1, + .dev = { .platform_data = &apq8064_fmem_pdata }, +}; + +static void __init reserve_mem_for_ion(enum ion_memory_types mem_type, + unsigned long size) +{ + apq8064_reserve_table[mem_type].size += size; +} + +static void __init apq8064_reserve_fixed_area(unsigned long fixed_area_size) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + int ret; + + if (fixed_area_size > MAX_FIXED_AREA_SIZE) + panic("fixed area size is larger than %dM\n", + MAX_FIXED_AREA_SIZE >> 20); + + reserve_info->fixed_area_size = fixed_area_size; + reserve_info->fixed_area_start = APQ8064_FW_START; + + ret = memblock_remove(reserve_info->fixed_area_start, + reserve_info->fixed_area_size); + BUG_ON(ret); +#endif +} + +/** + * Reserve memory for ION and calculate amount of reusable memory for fmem. + * We only reserve memory for heaps that are not reusable. However, we only + * support one reusable heap at the moment so we ignore the reusable flag for + * other than the first heap with reusable flag set. Also handle special case + * for video heaps (MM,FW, and MFC). Video requires heaps MM and MFC to be + * at a higher address than FW in addition to not more than 256MB away from the + * base address of the firmware. This means that if MM is reusable the other + * two heaps must be allocated in the same region as FW. This is handled by the + * mem_is_fmem flag in the platform data. In addition the MM heap must be + * adjacent to the FW heap for content protection purposes. + */ +static void __init reserve_ion_memory(void) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + unsigned int i; + unsigned int reusable_count = 0; + unsigned int fixed_size = 0; + unsigned int fixed_low_size, fixed_middle_size, fixed_high_size; + unsigned long fixed_low_start, fixed_middle_start, fixed_high_start; + + apq8064_fmem_pdata.size = 0; + apq8064_fmem_pdata.reserved_size_low = 0; + apq8064_fmem_pdata.reserved_size_high = 0; + apq8064_fmem_pdata.align = PAGE_SIZE; + fixed_low_size = 0; + fixed_middle_size = 0; + fixed_high_size = 0; + + /* We only support 1 reusable heap. Check if more than one heap + * is specified as reusable and set as non-reusable if found. + */ + for (i = 0; i < apq8064_ion_pdata.nr; ++i) { + const struct ion_platform_heap *heap = + &(apq8064_ion_pdata.heaps[i]); + + if (heap->type == ION_HEAP_TYPE_CP && heap->extra_data) { + struct ion_cp_heap_pdata *data = heap->extra_data; + + reusable_count += (data->reusable) ? 1 : 0; + + if (data->reusable && reusable_count > 1) { + pr_err("%s: Too many heaps specified as " + "reusable. Heap %s was not configured " + "as reusable.\n", __func__, heap->name); + data->reusable = 0; + } + } + } + + for (i = 0; i < apq8064_ion_pdata.nr; ++i) { + const struct ion_platform_heap *heap = + &(apq8064_ion_pdata.heaps[i]); + + if (heap->extra_data) { + int fixed_position = NOT_FIXED; + int mem_is_fmem = 0; + + switch (heap->type) { + case ION_HEAP_TYPE_CP: + mem_is_fmem = ((struct ion_cp_heap_pdata *) + heap->extra_data)->mem_is_fmem; + fixed_position = ((struct ion_cp_heap_pdata *) + heap->extra_data)->fixed_position; + break; + case ION_HEAP_TYPE_CARVEOUT: + mem_is_fmem = ((struct ion_co_heap_pdata *) + heap->extra_data)->mem_is_fmem; + fixed_position = ((struct ion_co_heap_pdata *) + heap->extra_data)->fixed_position; + break; + default: + break; + } + + if (fixed_position != NOT_FIXED) + fixed_size += heap->size; + else + reserve_mem_for_ion(MEMTYPE_EBI1, heap->size); + + if (fixed_position == FIXED_LOW) + fixed_low_size += heap->size; + else if (fixed_position == FIXED_MIDDLE) + fixed_middle_size += heap->size; + else if (fixed_position == FIXED_HIGH) + fixed_high_size += heap->size; + + if (mem_is_fmem) + apq8064_fmem_pdata.size += heap->size; + } + } + + if (!fixed_size) + return; + + if (apq8064_fmem_pdata.size) { + apq8064_fmem_pdata.reserved_size_low = fixed_low_size + + HOLE_SIZE; + apq8064_fmem_pdata.reserved_size_high = fixed_high_size; + } + + /* Since the fixed area may be carved out of lowmem, + * make sure the length is a multiple of 1M. + */ + fixed_size = (fixed_size + HOLE_SIZE + SECTION_SIZE - 1) + & SECTION_MASK; + apq8064_reserve_fixed_area(fixed_size); + + fixed_low_start = APQ8064_FIXED_AREA_START; + fixed_middle_start = fixed_low_start + fixed_low_size + HOLE_SIZE; + fixed_high_start = fixed_middle_start + fixed_middle_size; + + for (i = 0; i < apq8064_ion_pdata.nr; ++i) { + struct ion_platform_heap *heap = &(apq8064_ion_pdata.heaps[i]); + + if (heap->extra_data) { + int fixed_position = NOT_FIXED; + struct ion_cp_heap_pdata *pdata = NULL; + + switch (heap->type) { + case ION_HEAP_TYPE_CP: + pdata = + (struct ion_cp_heap_pdata *)heap->extra_data; + fixed_position = pdata->fixed_position; + break; + case ION_HEAP_TYPE_CARVEOUT: + fixed_position = ((struct ion_co_heap_pdata *) + heap->extra_data)->fixed_position; + break; + default: + break; + } + + switch (fixed_position) { + case FIXED_LOW: + heap->base = fixed_low_start; + break; + case FIXED_MIDDLE: + heap->base = fixed_middle_start; + pdata->secure_base = fixed_middle_start + - HOLE_SIZE; + pdata->secure_size = HOLE_SIZE + heap->size; + break; + case FIXED_HIGH: + heap->base = fixed_high_start; + break; + default: + break; + } + } + } +#endif +} + +static void __init reserve_mdp_memory(void) +{ + apq8064_mdp_writeback(apq8064_reserve_table); +} + +static void __init reserve_cache_dump_memory(void) +{ +#ifdef CONFIG_MSM_CACHE_DUMP + unsigned int total; + + total = apq8064_cache_dump_pdata.l1_size + + apq8064_cache_dump_pdata.l2_size; + apq8064_reserve_table[MEMTYPE_EBI1].size += total; +#endif +} + +static void __init apq8064_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); + reserve_ion_memory(); + reserve_mdp_memory(); + reserve_rtb_memory(); + reserve_cache_dump_memory(); +} + +static struct reserve_info apq8064_reserve_info __initdata = { + .memtype_reserve_table = apq8064_reserve_table, + .calculate_reserve_sizes = apq8064_calculate_reserve_sizes, + .reserve_fixed_area = apq8064_reserve_fixed_area, + .paddr_to_memtype = apq8064_paddr_to_memtype, +}; + +static int apq8064_memory_bank_size(void) +{ + return 1<<29; +} + +static void __init locate_unstable_memory(void) +{ + struct membank *mb = &meminfo.bank[meminfo.nr_banks - 1]; + unsigned long bank_size; + unsigned long low, high; + + bank_size = apq8064_memory_bank_size(); + low = meminfo.bank[0].start; + high = mb->start + mb->size; + + /* Check if 32 bit overflow occured */ + if (high < mb->start) + high = -PAGE_SIZE; + + low &= ~(bank_size - 1); + + if (high - low <= bank_size) + goto no_dmm; + +#ifdef CONFIG_ENABLE_DMM + apq8064_reserve_info.low_unstable_address = mb->start - + MIN_MEMORY_BLOCK_SIZE + mb->size; + apq8064_reserve_info.max_unstable_size = MIN_MEMORY_BLOCK_SIZE; + + apq8064_reserve_info.bank_size = bank_size; + pr_info("low unstable address %lx max size %lx bank size %lx\n", + apq8064_reserve_info.low_unstable_address, + apq8064_reserve_info.max_unstable_size, + apq8064_reserve_info.bank_size); + return; +#endif +no_dmm: + apq8064_reserve_info.low_unstable_address = high; + apq8064_reserve_info.max_unstable_size = 0; +} + +static int apq8064_change_memory_power(u64 start, u64 size, + int change_type) +{ + return soc_change_memory_power(start, size, change_type); +} + +static char prim_panel_name[PANEL_NAME_MAX_LEN]; +static char ext_panel_name[PANEL_NAME_MAX_LEN]; +static int __init prim_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(prim_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("prim_display", prim_display_setup); + +static int __init ext_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(ext_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("ext_display", ext_display_setup); + +static void __init apq8064_reserve(void) +{ + apq8064_set_display_params(prim_panel_name, ext_panel_name); + msm_reserve(); + if (apq8064_fmem_pdata.size) { +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + if (reserve_info->fixed_area_size) { + apq8064_fmem_pdata.phys = + reserve_info->fixed_area_start + MSM_MM_FW_SIZE; + pr_info("mm fw at %lx (fixed) size %x\n", + reserve_info->fixed_area_start, MSM_MM_FW_SIZE); + pr_info("fmem start %lx (fixed) size %lx\n", + apq8064_fmem_pdata.phys, + apq8064_fmem_pdata.size); + } +#endif + } +} + +static void __init place_movable_zone(void) +{ +#ifdef CONFIG_ENABLE_DMM + movable_reserved_start = apq8064_reserve_info.low_unstable_address; + movable_reserved_size = apq8064_reserve_info.max_unstable_size; + pr_info("movable zone start %lx size %lx\n", + movable_reserved_start, movable_reserved_size); +#endif +} + +static void __init apq8064_early_reserve(void) +{ + reserve_info = &apq8064_reserve_info; + locate_unstable_memory(); + place_movable_zone(); + +} +#ifdef CONFIG_USB_EHCI_MSM_HSIC +/* Bandwidth requests (zero) if no vote placed */ +static struct msm_bus_vectors hsic_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_SPS, + .ab = 0, + .ib = 0, + }, +}; + +/* Bus bandwidth requests in Bytes/sec */ +static struct msm_bus_vectors hsic_max_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 60000000, /* At least 480Mbps on bus. */ + .ib = 960000000, /* MAX bursts rate */ + }, + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_SPS, + .ab = 0, + .ib = 512000000, /*vote for 64Mhz dfab clk rate*/ + }, +}; + +static struct msm_bus_paths hsic_bus_scale_usecases[] = { + { + ARRAY_SIZE(hsic_init_vectors), + hsic_init_vectors, + }, + { + ARRAY_SIZE(hsic_max_vectors), + hsic_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata hsic_bus_scale_pdata = { + hsic_bus_scale_usecases, + ARRAY_SIZE(hsic_bus_scale_usecases), + .name = "hsic", +}; + +static struct msm_hsic_host_platform_data msm_hsic_pdata = { + .strobe = 88, + .data = 89, + .bus_scale_table = &hsic_bus_scale_pdata, +}; +#else +static struct msm_hsic_host_platform_data msm_hsic_pdata; +#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) +{ + 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; + strlcpy(dload->serial_number, snum, SERIAL_NUMBER_LENGTH); +out: + 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, + }, +}; + +/* Bandwidth requests (zero) if no vote placed */ +static struct msm_bus_vectors usb_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +/* Bus bandwidth requests in Bytes/sec */ +static struct msm_bus_vectors usb_max_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 60000000, /* At least 480Mbps on bus. */ + .ib = 960000000, /* MAX bursts rate */ + }, +}; + +static struct msm_bus_paths usb_bus_scale_usecases[] = { + { + ARRAY_SIZE(usb_init_vectors), + usb_init_vectors, + }, + { + ARRAY_SIZE(usb_max_vectors), + usb_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata usb_bus_scale_pdata = { + usb_bus_scale_usecases, + ARRAY_SIZE(usb_bus_scale_usecases), + .name = "usb", +}; + +static int phy_init_seq[] = { + 0x38, 0x81, /* update DC voltage level */ + 0x24, 0x82, /* set pre-emphasis and rise/fall time */ + -1 +}; + +static struct msm_otg_platform_data msm_otg_pdata = { + .mode = USB_OTG, + .otg_control = OTG_PMIC_CONTROL, + .phy_type = SNPS_28NM_INTEGRATED_PHY, + .pmic_id_irq = PM8921_USB_ID_IN_IRQ(PM8921_IRQ_BASE), + .power_budget = 750, + .bus_scale_table = &usb_bus_scale_pdata, + .phy_init_seq = phy_init_seq, +}; + +static struct msm_usb_host_platform_data msm_ehci_host_pdata3 = { + .power_budget = 500, +}; + +#ifdef CONFIG_USB_EHCI_MSM_HOST4 +static struct msm_usb_host_platform_data msm_ehci_host_pdata4; +#endif + +static void __init apq8064_ehci_host_init(void) +{ + if (machine_is_apq8064_liquid() || machine_is_mpq8064_cdp() || + machine_is_mpq8064_hrd() || machine_is_mpq8064_dtv()) { + if (machine_is_apq8064_liquid()) + msm_ehci_host_pdata3.dock_connect_irq = + PM8921_MPP_IRQ(PM8921_IRQ_BASE, 9); + + apq8064_device_ehci_host3.dev.platform_data = + &msm_ehci_host_pdata3; + platform_device_register(&apq8064_device_ehci_host3); + +#ifdef CONFIG_USB_EHCI_MSM_HOST4 + apq8064_device_ehci_host4.dev.platform_data = + &msm_ehci_host_pdata4; + platform_device_register(&apq8064_device_ehci_host4); +#endif + } +} + +static struct smb349_platform_data smb349_data __initdata = { + .en_n_gpio = PM8921_GPIO_PM_TO_SYS(37), + .chg_susp_gpio = PM8921_GPIO_PM_TO_SYS(30), + .chg_current_ma = 2200, +}; + +static struct i2c_board_info smb349_charger_i2c_info[] __initdata = { + { + I2C_BOARD_INFO(SMB349_NAME, 0x1B), + .platform_data = &smb349_data, + }, +}; + +struct sx150x_platform_data apq8064_sx150x_data[] = { + [SX150X_EPM] = { + .gpio_base = GPIO_EPM_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0, + .io_pulldn_ena = 0x0, + .io_open_drain_ena = 0x0, + .io_polarity = 0, + .irq_summary = -1, + }, +}; + +static struct epm_chan_properties ads_adc_channel_data[] = { + {10, 100}, {500, 50}, {1, 1}, {1, 1}, + {20, 50}, {10, 100}, {1, 1}, {1, 1}, + {10, 100}, {10, 100}, {100, 100}, {200, 100}, + {100, 50}, {2000, 50}, {1000, 50}, {200, 50}, + {200, 100}, {1, 1}, {20, 50}, {500, 50}, + {50, 50}, {200, 100}, {500, 100}, {20, 50}, + {200, 50}, {2000, 100}, {1000, 50}, {100, 50}, + {200, 100}, {500, 50}, {1000, 100}, {200, 50}, + {1000, 50}, {50, 50}, {100, 50}, {100, 50}, + {1, 1}, {1, 1}, {20, 100}, {20, 50}, + {500, 100}, {1000, 100}, {100, 50}, {1000, 50}, + {100, 50}, {1000, 100}, {100, 50}, {100, 50}, +}; + +static struct epm_adc_platform_data epm_adc_pdata = { + .channel = ads_adc_channel_data, + .bus_id = 0x0, + .epm_i2c_board_info = { + .type = "sx1509q", + .addr = 0x3e, + .platform_data = &apq8064_sx150x_data[SX150X_EPM], + }, + .gpio_expander_base_addr = GPIO_EPM_EXPANDER_BASE, +}; + +static struct platform_device epm_adc_device = { + .name = "epm_adc", + .id = -1, + .dev = { + .platform_data = &epm_adc_pdata, + }, +}; + +static void __init apq8064_epm_adc_init(void) +{ + epm_adc_pdata.num_channels = 32; + epm_adc_pdata.num_adc = 2; + epm_adc_pdata.chan_per_adc = 16; + epm_adc_pdata.chan_per_mux = 8; +}; + +/* Micbias setting is based on 8660 CDP/MTP/FLUID requirement + * 4 micbiases are used to power various analog and digital + * microphones operating at 1800 mV. Technically, all micbiases + * can source from single cfilter since all microphones operate + * at the same voltage level. The arrangement below is to make + * sure all cfilters are exercised. LDO_H regulator ouput level + * does not need to be as high as 2.85V. It is choosen for + * microphone sensitivity purpose. + */ +static struct wcd9xxx_pdata apq8064_tabla_platform_data = { + .slimbus_slave_device = { + .name = "tabla-slave", + .e_addr = {0, 0, 0x10, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(42), + .irq_base = TABLA_INTERRUPT_BASE, + .num_irqs = NR_WCD9XXX_IRQS, + .reset_gpio = PM8921_GPIO_PM_TO_SYS(34), + .micbias = { + .ldoh_v = TABLA_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 1800, + .cfilt3_mv = 1800, + .bias1_cfilt_sel = TABLA_CFILT1_SEL, + .bias2_cfilt_sel = TABLA_CFILT2_SEL, + .bias3_cfilt_sel = TABLA_CFILT3_SEL, + .bias4_cfilt_sel = TABLA_CFILT3_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device apq8064_slim_tabla = { + .name = "tabla-slim", + .e_addr = {0, 1, 0x10, 0, 0x17, 2}, + .dev = { + .platform_data = &apq8064_tabla_platform_data, + }, +}; + +static struct wcd9xxx_pdata apq8064_tabla20_platform_data = { + .slimbus_slave_device = { + .name = "tabla-slave", + .e_addr = {0, 0, 0x60, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(42), + .irq_base = TABLA_INTERRUPT_BASE, + .num_irqs = NR_WCD9XXX_IRQS, + .reset_gpio = PM8921_GPIO_PM_TO_SYS(34), + .micbias = { + .ldoh_v = TABLA_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 1800, + .cfilt3_mv = 1800, + .bias1_cfilt_sel = TABLA_CFILT1_SEL, + .bias2_cfilt_sel = TABLA_CFILT2_SEL, + .bias3_cfilt_sel = TABLA_CFILT3_SEL, + .bias4_cfilt_sel = TABLA_CFILT3_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device apq8064_slim_tabla20 = { + .name = "tabla2x-slim", + .e_addr = {0, 1, 0x60, 0, 0x17, 2}, + .dev = { + .platform_data = &apq8064_tabla20_platform_data, + }, +}; + +/* enable the level shifter for cs8427 to make sure the I2C + * clock is running at 100KHz and voltage levels are at 3.3 + * and 5 volts + */ +static int enable_100KHz_ls(int enable) +{ + int ret = 0; + if (enable) { + ret = gpio_request(SX150X_GPIO(1, 10), + "cs8427_100KHZ_ENABLE"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + SX150X_GPIO(1, 10)); + return ret; + } + gpio_direction_output(SX150X_GPIO(1, 10), 1); + } else + gpio_free(SX150X_GPIO(1, 10)); + return ret; +} + +static struct cs8427_platform_data cs8427_i2c_platform_data = { + .irq = SX150X_GPIO(1, 4), + .reset_gpio = SX150X_GPIO(1, 6), + .enable = enable_100KHz_ls, +}; + +static struct i2c_board_info cs8427_device_info[] __initdata = { + { + I2C_BOARD_INFO("cs8427", CS8427_ADDR4), + .platform_data = &cs8427_i2c_platform_data, + }, +}; + +#define HAP_SHIFT_LVL_OE_GPIO PM8921_MPP_PM_TO_SYS(8) +#define ISA1200_HAP_EN_GPIO PM8921_GPIO_PM_TO_SYS(33) +#define ISA1200_HAP_LEN_GPIO PM8921_GPIO_PM_TO_SYS(20) +#define ISA1200_HAP_CLK PM8921_GPIO_PM_TO_SYS(44) + +static int isa1200_clk_enable(bool on) +{ + int rc = 0; + + gpio_set_value_cansleep(ISA1200_HAP_CLK, on); + + if (on) { + rc = pm8xxx_aux_clk_control(CLK_MP3_2, XO_DIV_1, true); + if (rc) { + pr_err("%s: unable to write aux clock register(%d)\n", + __func__, rc); + goto err_gpio_dis; + } + } else { + rc = pm8xxx_aux_clk_control(CLK_MP3_2, XO_DIV_NONE, true); + if (rc) + pr_err("%s: unable to write aux clock register(%d)\n", + __func__, rc); + } + + return rc; + +err_gpio_dis: + gpio_set_value_cansleep(ISA1200_HAP_CLK, !on); + return rc; +} + +static int isa1200_dev_setup(bool enable) +{ + int rc = 0; + + if (!enable) + goto free_gpio; + + rc = gpio_request(ISA1200_HAP_CLK, "haptics_clk"); + if (rc) { + pr_err("%s: unable to request gpio %d config(%d)\n", + __func__, ISA1200_HAP_CLK, rc); + return rc; + } + + rc = gpio_direction_output(ISA1200_HAP_CLK, 0); + if (rc) { + pr_err("%s: unable to set direction\n", __func__); + goto free_gpio; + } + + return 0; + +free_gpio: + gpio_free(ISA1200_HAP_CLK); + return rc; +} + +static struct isa1200_regulator isa1200_reg_data[] = { + { + .name = "vddp", + .min_uV = ISA_I2C_VTG_MIN_UV, + .max_uV = ISA_I2C_VTG_MAX_UV, + .load_uA = ISA_I2C_CURR_UA, + }, +}; + +static struct isa1200_platform_data isa1200_1_pdata = { + .name = "vibrator", + .dev_setup = isa1200_dev_setup, + .clk_enable = isa1200_clk_enable, + .hap_en_gpio = ISA1200_HAP_EN_GPIO, + .hap_len_gpio = ISA1200_HAP_LEN_GPIO, + .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, + .regulator_info = isa1200_reg_data, + .num_regulators = ARRAY_SIZE(isa1200_reg_data), +}; + +static struct i2c_board_info isa1200_board_info[] __initdata = { + { + I2C_BOARD_INFO("isa1200_1", 0x90>>1), + .platform_data = &isa1200_1_pdata, + }, +}; +/* configuration data for mxt1386e using V2.1 firmware */ +static const u8 mxt1386e_config_data_v2_1[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 14, 2, 0, 24, 5, 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, 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, + /* T7 Object */ + 100, 10, 50, + /* T8 Object */ + 25, 0, 20, 20, 0, 0, 0, 0, 0, 0, + /* T9 Object */ + 139, 0, 0, 26, 42, 0, 32, 80, 2, 5, + 0, 5, 5, 0, 10, 30, 10, 10, 255, 2, + 85, 5, 0, 5, 9, 5, 12, 35, 70, 40, + 20, 5, 0, 0, 0, + /* T18 Object */ + 0, 0, + /* T24 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T25 Object */ + 1, 0, 60, 115, 156, 99, + /* T27 Object */ + 0, 0, 0, 0, 0, 0, 0, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T42 Object */ + 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, + /* T43 Object */ + 0, 0, 0, 0, 0, 0, 0, 64, 0, 8, + 16, + /* T46 Object */ + 68, 0, 16, 16, 0, 0, 0, 0, 0, + /* T47 Object */ + 0, 0, 0, 0, 0, 0, 3, 64, 66, 0, + /* T48 Object */ + 1, 64, 64, 0, 0, 0, 0, 0, 0, 0, + 32, 40, 0, 10, 10, 0, 0, 100, 10, 90, + 0, 0, 0, 0, 0, 0, 0, 10, 1, 10, + 52, 10, 12, 0, 33, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T56 Object */ + 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, 0, 0, 0, + 0, +}; + +#define MXT_TS_GPIO_IRQ 6 +#define MXT_TS_PWR_EN_GPIO PM8921_GPIO_PM_TO_SYS(23) +#define MXT_TS_RESET_GPIO 33 + +static struct mxt_config_info mxt_config_array[] = { + { + .config = mxt1386e_config_data_v2_1, + .config_length = ARRAY_SIZE(mxt1386e_config_data_v2_1), + .family_id = 0xA0, + .variant_id = 0x7, + .version = 0x21, + .build = 0xAA, + .bootldr_id = MXT_BOOTLOADER_ID_1386E, + .fw_name = "atmel_8064_liquid_v2_2_AA.hex", + }, + { + /* The config data for V2.2.AA is the same as for V2.1.AA */ + .config = mxt1386e_config_data_v2_1, + .config_length = ARRAY_SIZE(mxt1386e_config_data_v2_1), + .family_id = 0xA0, + .variant_id = 0x7, + .version = 0x22, + .build = 0xAA, + .bootldr_id = MXT_BOOTLOADER_ID_1386E, + }, +}; + +static struct mxt_platform_data mxt_platform_data = { + .config_array = mxt_config_array, + .config_array_size = ARRAY_SIZE(mxt_config_array), + .panel_minx = 0, + .panel_maxx = 1365, + .panel_miny = 0, + .panel_maxy = 767, + .disp_minx = 0, + .disp_maxx = 1365, + .disp_miny = 0, + .disp_maxy = 767, + .irqflags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .i2c_pull_up = true, + .reset_gpio = MXT_TS_RESET_GPIO, + .irq_gpio = MXT_TS_GPIO_IRQ, +}; + +static struct i2c_board_info mxt_device_info[] __initdata = { + { + I2C_BOARD_INFO("atmel_mxt_ts", 0x5b), + .platform_data = &mxt_platform_data, + .irq = MSM_GPIO_TO_INT(MXT_TS_GPIO_IRQ), + }, +}; +#define CYTTSP_TS_GPIO_IRQ 6 +#define CYTTSP_TS_GPIO_SLEEP 33 + +static ssize_t tma340_vkeys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + 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"); +} + +static struct kobj_attribute tma340_vkeys_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = &tma340_vkeys_show, +}; + +static struct attribute *tma340_properties_attrs[] = { + &tma340_vkeys_attr.attr, + NULL +}; + +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 cyttsp_regulator_data[] = { + { + .name = "vdd", + .min_uV = CY_TMA300_VTG_MIN_UV, + .max_uV = CY_TMA300_VTG_MAX_UV, + .hpm_load_uA = CY_TMA300_CURR_24HZ_UA, + .lpm_load_uA = CY_TMA300_CURR_24HZ_UA, + }, + { + .name = "vcc_i2c", + .min_uV = CY_I2C_VTG_MIN_UV, + .max_uV = CY_I2C_VTG_MAX_UV, + .hpm_load_uA = CY_I2C_CURR_UA, + .lpm_load_uA = CY_I2C_CURR_UA, + }, +}; + +static struct cyttsp_platform_data cyttsp_pdata = { + .panel_maxx = 634, + .panel_maxy = 1166, + .disp_maxx = 599, + .disp_maxy = 1023, + .disp_minx = 0, + .disp_miny = 0, + .flags = 0x01, + .gen = CY_GEN3, + .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_DEEP_SLEEP_SEL, + .use_gestures = CY_USE_GESTURES, + .fw_fname = "cyttsp_8064_mtp.hex", + /* 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_GPIO_SLEEP, + .resout_gpio = -1, + .irq_gpio = CYTTSP_TS_GPIO_IRQ, + .regulator_info = cyttsp_regulator_data, + .num_regulators = ARRAY_SIZE(cyttsp_regulator_data), + .init = cyttsp_platform_init, + .correct_fw_ver = 17, +}; + +static struct i2c_board_info cyttsp_info[] __initdata = { + { + I2C_BOARD_INFO(CY_I2C_NAME, 0x24), + .platform_data = &cyttsp_pdata, + .irq = MSM_GPIO_TO_INT(CYTTSP_TS_GPIO_IRQ), + }, +}; + +#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, + }, + { + .start = 64, + .end = 68, + .name = "wcnss_gpios_5wire", + .flags = IORESOURCE_IO, + }, +}; + +static struct qcom_wcnss_opts qcom_wcnss_pdata = { + .has_48mhz_xo = 1, +}; + +static struct platform_device msm_device_wcnss_wlan = { + .name = "wcnss_wlan", + .id = 0, + .num_resources = ARRAY_SIZE(resources_wcnss_wlan), + .resource = resources_wcnss_wlan, + .dev = {.platform_data = &qcom_wcnss_pdata}, +}; + +static struct platform_device msm_device_iris_fm __devinitdata = { + .name = "iris_fm", + .id = -1, +}; + +#ifdef CONFIG_QSEECOM +/* qseecom bus scaling */ +static struct msm_bus_vectors qseecom_clks_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 0, + .ab = 0, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = 0, + .ab = 0, + }, +}; + +static struct msm_bus_vectors qseecom_enable_dfab_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = (492 * 8) * 1000000UL, + .ab = (492 * 8) * 100000UL, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = 0, + .ab = 0, + }, +}; + +static struct msm_bus_vectors qseecom_enable_sfpb_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 0, + .ab = 0, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = (64 * 8) * 1000000UL, + .ab = (64 * 8) * 100000UL, + }, +}; + +static struct msm_bus_paths qseecom_hw_bus_scale_usecases[] = { + { + ARRAY_SIZE(qseecom_clks_init_vectors), + qseecom_clks_init_vectors, + }, + { + ARRAY_SIZE(qseecom_enable_dfab_vectors), + qseecom_enable_sfpb_vectors, + }, + { + ARRAY_SIZE(qseecom_enable_sfpb_vectors), + qseecom_enable_sfpb_vectors, + }, +}; + +static struct msm_bus_scale_pdata qseecom_bus_pdata = { + qseecom_hw_bus_scale_usecases, + ARRAY_SIZE(qseecom_hw_bus_scale_usecases), + .name = "qsee", +}; + +static struct platform_device qseecom_device = { + .name = "qseecom", + .id = 0, + .dev = { + .platform_data = &qseecom_bus_pdata, + }, +}; +#endif + +#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 0x11000000 + +#define QCE_HW_KEY_SUPPORT 0 +#define QCE_SHA_HMAC_SUPPORT 1 +#define QCE_SHARE_CE_RESOURCE 3 +#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 = DMOV8064_CE_IN_CHAN, + .end = DMOV8064_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV8064_CE_IN_CRCI, + .end = DMOV8064_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV8064_CE_OUT_CRCI, + .end = DMOV8064_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 = DMOV8064_CE_IN_CHAN, + .end = DMOV8064_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV8064_CE_IN_CRCI, + .end = DMOV8064_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV8064_CE_OUT_CRCI, + .end = DMOV8064_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, + .bus_scale_table = NULL, +}; + +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, + .bus_scale_table = NULL, +}; + +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 struct mdm_platform_data mdm_platform_data = { + .mdm_version = "3.0", + .ramdump_delay_ms = 2000, + .early_power_on = 1, + .sfr_query = 1, + .peripheral_platform_device = &apq8064_device_hsic_host, +}; + +static struct tsens_platform_data apq_tsens_pdata = { + .tsens_factor = 1000, + .hw_type = APQ_8064, + .tsens_num_sensor = 11, + .slope = {1176, 1176, 1154, 1176, 1111, + 1132, 1132, 1199, 1132, 1199, 1132}, +}; + +static struct platform_device msm_tsens_device = { + .name = "tsens8960-tm", + .id = -1, +}; + +#define MSM_SHARED_RAM_PHYS 0x80000000 +static void __init apq8064_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_apq8064_io(); + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); +} + +static void __init apq8064_init_irq(void) +{ + struct msm_mpm_device_data *data = NULL; + +#ifdef CONFIG_MSM_MPM + data = &apq8064_mpm_dev_data; +#endif + + msm_mpm_irq_extn_init(data); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); +} + +static struct platform_device msm8064_device_saw_regulator_core0 = { + .name = "saw-regulator", + .id = 0, + .dev = { + .platform_data = &msm8064_saw_regulator_pdata_8921_s5, + }, +}; + +static struct platform_device msm8064_device_saw_regulator_core1 = { + .name = "saw-regulator", + .id = 1, + .dev = { + .platform_data = &msm8064_saw_regulator_pdata_8921_s6, + }, +}; + +static struct platform_device msm8064_device_saw_regulator_core2 = { + .name = "saw-regulator", + .id = 2, + .dev = { + .platform_data = &msm8064_saw_regulator_pdata_8821_s0, + }, +}; + +static struct platform_device msm8064_device_saw_regulator_core3 = { + .name = "saw-regulator", + .id = 3, + .dev = { + .platform_data = &msm8064_saw_regulator_pdata_8821_s1, + + }, +}; + +static struct msm_rpmrs_level msm_rpmrs_levels[] = { + { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1, 784, 180000, 100, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1300, 228, 1200000, 2000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, GDHS, MAX, ACTIVE), + false, + 2000, 138, 1208400, 3200, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 6000, 119, 1850300, 9000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, GDHS, MAX, ACTIVE), + false, + 9200, 68, 2839200, 16400, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, MAX, ACTIVE), + false, + 10300, 63, 3128000, 18200, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 18000, 10, 4602600, 27000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, RET_HIGH, RET_LOW), + false, + 20000, 2, 5752000, 32000, + }, +}; + +static struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_TZ, +}; + +static struct msm_rpmrs_platform_data msm_rpmrs_data __initdata = { + .levels = &msm_rpmrs_levels[0], + .num_levels = ARRAY_SIZE(msm_rpmrs_levels), + .vdd_mem_levels = { + [MSM_RPMRS_VDD_MEM_RET_LOW] = 750000, + [MSM_RPMRS_VDD_MEM_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_MEM_ACTIVE] = 1050000, + [MSM_RPMRS_VDD_MEM_MAX] = 1150000, + }, + .vdd_dig_levels = { + [MSM_RPMRS_VDD_DIG_RET_LOW] = 500000, + [MSM_RPMRS_VDD_DIG_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_DIG_ACTIVE] = 950000, + [MSM_RPMRS_VDD_DIG_MAX] = 1150000, + }, + .vdd_mask = 0x7FFFFF, + .rpmrs_target_id = { + [MSM_RPMRS_ID_PXO_CLK] = MSM_RPM_ID_PXO_CLK, + [MSM_RPMRS_ID_L2_CACHE_CTL] = MSM_RPM_ID_LAST, + [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, + [MSM_RPMRS_ID_RPM_CTL] = MSM_RPM_ID_RPM_CTL, + }, +}; + +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 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_SPM_CTL] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DLY] = 0x02020204, + .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), + }, +}; + +static struct msm_spm_platform_data msm_spm_data[] __initdata = { + [0] = { + .reg_base_addr = MSM_SAW0_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x1F, +#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] = 0x02020204, + .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_CFG] = 0x1F, +#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] = 0x02020204, + .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, + }, + [2] = { + .reg_base_addr = MSM_SAW2_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x1F, +#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] = 0x02020204, + .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, + }, + [3] = { + .reg_base_addr = MSM_SAW3_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x1F, +#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] = 0x02020204, + .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 struct msm_pm_sleep_status_data msm_pm_slp_sts_data = { + .base_addr = MSM_ACC0_BASE + 0x08, + .cpu_offset = MSM_ACC1_BASE - MSM_ACC0_BASE, + .mask = 1UL << 13, +}; + +static void __init apq8064_init_buses(void) +{ + msm_bus_rpm_set_mt_mask(); + msm_bus_8064_apps_fabric_pdata.rpm_enabled = 1; + msm_bus_8064_sys_fabric_pdata.rpm_enabled = 1; + msm_bus_8064_mm_fabric_pdata.rpm_enabled = 1; + msm_bus_8064_apps_fabric.dev.platform_data = + &msm_bus_8064_apps_fabric_pdata; + msm_bus_8064_sys_fabric.dev.platform_data = + &msm_bus_8064_sys_fabric_pdata; + msm_bus_8064_mm_fabric.dev.platform_data = + &msm_bus_8064_mm_fabric_pdata; + msm_bus_8064_sys_fpb.dev.platform_data = &msm_bus_8064_sys_fpb_pdata; + msm_bus_8064_cpss_fpb.dev.platform_data = &msm_bus_8064_cpss_fpb_pdata; +} + +/* PCIe gpios */ +static struct msm_pcie_gpio_info_t msm_pcie_gpio_info[MSM_PCIE_MAX_GPIO] = { + {"rst_n", PM8921_MPP_PM_TO_SYS(PCIE_RST_N_PMIC_MPP), 0}, + {"pwr_en", PM8921_GPIO_PM_TO_SYS(PCIE_PWR_EN_PMIC_GPIO), 1}, +}; + +static struct msm_pcie_platform msm_pcie_platform_data = { + .gpio = msm_pcie_gpio_info, +}; + +static void __init mpq8064_pcie_init(void) +{ + msm_device_pcie.dev.platform_data = &msm_pcie_platform_data; + platform_device_register(&msm_device_pcie); +} + +static struct platform_device apq8064_device_ext_5v_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8921_MPP_PM_TO_SYS(7), + .dev = { + .platform_data + = &apq8064_gpio_regulator_pdata[GPIO_VREG_ID_EXT_5V], + }, +}; + +static struct platform_device apq8064_device_ext_mpp8_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8921_MPP_PM_TO_SYS(8), + .dev = { + .platform_data + = &apq8064_gpio_regulator_pdata[GPIO_VREG_ID_EXT_MPP8], + }, +}; + +static struct platform_device apq8064_device_ext_3p3v_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = APQ8064_EXT_3P3V_REG_EN_GPIO, + .dev = { + .platform_data = + &apq8064_gpio_regulator_pdata[GPIO_VREG_ID_EXT_3P3V], + }, +}; + +static struct platform_device apq8064_device_ext_ts_sw_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8921_GPIO_PM_TO_SYS(23), + .dev = { + .platform_data + = &apq8064_gpio_regulator_pdata[GPIO_VREG_ID_EXT_TS_SW], + }, +}; + +static struct platform_device apq8064_device_rpm_regulator __devinitdata = { + .name = "rpm-regulator", + .id = -1, + .dev = { + .platform_data = &apq8064_rpm_regulator_pdata, + }, +}; + +static struct gpio_ir_recv_platform_data gpio_ir_recv_pdata = { + .gpio_nr = 88, + .active_low = 1, +}; + +static struct platform_device gpio_ir_recv_pdev = { + .name = "gpio-rc-recv", + .dev = { + .platform_data = &gpio_ir_recv_pdata, + }, +}; + +static struct platform_device *common_not_mpq_devices[] __initdata = { + &apq8064_device_qup_i2c_gsbi1, + &apq8064_device_qup_i2c_gsbi3, + &apq8064_device_qup_i2c_gsbi4, +}; + +static struct platform_device *common_devices[] __initdata = { + &apq8064_device_dmov, + &apq8064_device_qup_spi_gsbi5, + &apq8064_device_ext_5v_vreg, + &apq8064_device_ext_mpp8_vreg, + &apq8064_device_ext_3p3v_vreg, + &apq8064_device_ssbi_pmic1, + &apq8064_device_ssbi_pmic2, + &apq8064_device_ext_ts_sw_vreg, + &msm_device_smd_apq8064, + &apq8064_device_otg, + &apq8064_device_gadget_peripheral, + &apq8064_device_hsusb_host, + &android_usb_device, + &msm_device_wcnss_wlan, + &msm_device_iris_fm, + &apq8064_fmem_device, +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + &apq8064_android_pmem_device, + &apq8064_android_pmem_adsp_device, + &apq8064_android_pmem_audio_device, +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +#ifdef CONFIG_ION_MSM + &apq8064_ion_dev, +#endif + &msm8064_device_watchdog, + &msm8064_device_saw_regulator_core0, + &msm8064_device_saw_regulator_core1, + &msm8064_device_saw_regulator_core2, + &msm8064_device_saw_regulator_core3, +#if defined(CONFIG_QSEECOM) + &qseecom_device, +#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_HW_RANDOM_MSM + &apq8064_device_rng, +#endif + &apq_pcm, + &apq_pcm_routing, + &apq_cpudai0, + &apq_cpudai1, + &mpq_cpudai_sec_i2s_rx, + &mpq_cpudai_mi2s_tx, + &apq_cpudai_hdmi_rx, + &apq_cpudai_bt_rx, + &apq_cpudai_bt_tx, + &apq_cpudai_fm_rx, + &apq_cpudai_fm_tx, + &apq_cpu_fe, + &apq_stub_codec, + &apq_voice, + &apq_voip, + &apq_lpa_pcm, + &apq_compr_dsp, + &apq_multi_ch_pcm, + &apq_pcm_hostless, + &apq_cpudai_afe_01_rx, + &apq_cpudai_afe_01_tx, + &apq_cpudai_afe_02_rx, + &apq_cpudai_afe_02_tx, + &apq_pcm_afe, + &apq_cpudai_auxpcm_rx, + &apq_cpudai_auxpcm_tx, + &apq_cpudai_stub, + &apq_cpudai_slimbus_1_rx, + &apq_cpudai_slimbus_1_tx, + &apq_cpudai_slimbus_2_tx, + &apq_cpudai_slimbus_3_rx, + &apq8064_rpm_device, + &apq8064_rpm_log_device, + &apq8064_rpm_stat_device, + &msm_bus_8064_apps_fabric, + &msm_bus_8064_sys_fabric, + &msm_bus_8064_mm_fabric, + &msm_bus_8064_sys_fpb, + &msm_bus_8064_cpss_fpb, + &apq8064_msm_device_vidc, + &msm_pil_dsps, + &msm_8960_riva, + &msm_8960_q6_lpass, + &msm_pil_vidc, + &msm_gss, + &apq8064_rtb_device, + &apq8064_cpu_idle_device, + &apq8064_msm_gov_device, + &apq8064_device_cache_erp, + &epm_adc_device, + &apq8064_qdss_device, + &msm_etb_device, + &msm_tpiu_device, + &msm_funnel_device, + &apq8064_etm_device, + &apq_cpudai_slim_4_rx, + &apq_cpudai_slim_4_tx, +#ifdef CONFIG_MSM_GEMINI + &msm8960_gemini_device, +#endif + &apq8064_iommu_domain_device, + &msm_tsens_device, + &apq8064_cache_dump_device, +}; + +static struct platform_device *sim_devices[] __initdata = { + &apq8064_device_uart_gsbi3, + &msm_device_sps_apq8064, +}; + +static struct platform_device *rumi3_devices[] __initdata = { + &apq8064_device_uart_gsbi1, + &msm_device_sps_apq8064, +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif +}; + +static struct platform_device *cdp_devices[] __initdata = { + &apq8064_device_uart_gsbi1, + &apq8064_device_uart_gsbi7, + &msm_device_sps_apq8064, +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif +}; + +static struct platform_device +mpq8064_device_ext_5v_frc_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = SX150X_GPIO(4, 10), + .dev = { + .platform_data = + &mpq8064_gpio_regulator_pdata[GPIO_VREG_ID_FRC_5V], + }, +}; + +static struct platform_device +mpq8064_device_ext_1p2_buck_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = SX150X_GPIO(4, 2), + .dev = { + .platform_data = + &mpq8064_gpio_regulator_pdata[GPIO_VREG_ID_AVC_1P2V], + }, +}; + +static struct platform_device +mpq8064_device_ext_1p8_buck_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = SX150X_GPIO(4, 4), + .dev = { + .platform_data = + &mpq8064_gpio_regulator_pdata[GPIO_VREG_ID_AVC_1P8V], + }, +}; + +static struct platform_device +mpq8064_device_ext_2p2_buck_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = SX150X_GPIO(4, 14), + .dev = { + .platform_data = + &mpq8064_gpio_regulator_pdata[GPIO_VREG_ID_AVC_2P2V], + }, +}; + +static struct platform_device +mpq8064_device_ext_5v_buck_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = SX150X_GPIO(4, 3), + .dev = { + .platform_data = + &mpq8064_gpio_regulator_pdata[GPIO_VREG_ID_AVC_5V], + }, +}; + +static struct platform_device +mpq8064_device_ext_3p3v_ldo_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = SX150X_GPIO(4, 15), + .dev = { + .platform_data = + &mpq8064_gpio_regulator_pdata[GPIO_VREG_ID_AVC_3P3V], + }, +}; + +static struct platform_device rc_input_loopback_pdev = { + .name = "rc-user-input", + .id = -1, +}; + +static int rf4ce_gpio_init(void) +{ + if (!machine_is_mpq8064_cdp()) + return -EINVAL; + + /* CC2533 SRDY Input */ + if (!gpio_request(SX150X_GPIO(4, 6), "rf4ce_srdy")) { + gpio_direction_input(SX150X_GPIO(4, 6)); + gpio_export(SX150X_GPIO(4, 6), true); + } + + /* CC2533 MRDY Output */ + if (!gpio_request(SX150X_GPIO(4, 5), "rf4ce_mrdy")) { + gpio_direction_output(SX150X_GPIO(4, 5), 1); + gpio_export(SX150X_GPIO(4, 5), true); + } + + /* CC2533 Reset Output */ + if (!gpio_request(SX150X_GPIO(4, 7), "rf4ce_reset")) { + gpio_direction_output(SX150X_GPIO(4, 7), 0); + gpio_export(SX150X_GPIO(4, 7), true); + } + + return 0; +} +late_initcall(rf4ce_gpio_init); + +static struct platform_device *mpq_devices[] __initdata = { + &msm_device_sps_apq8064, + &mpq8064_device_qup_i2c_gsbi5, +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif + &gpio_ir_recv_pdev, + &mpq8064_device_ext_5v_frc_vreg, + &mpq8064_device_ext_1p2_buck_vreg, + &mpq8064_device_ext_1p8_buck_vreg, + &mpq8064_device_ext_2p2_buck_vreg, + &mpq8064_device_ext_5v_buck_vreg, + &mpq8064_device_ext_3p3v_ldo_vreg, +#ifdef CONFIG_MSM_VCAP + &msm8064_device_vcap, +#endif + &rc_input_loopback_pdev, +}; + +static struct msm_spi_platform_data apq8064_qup_spi_gsbi5_pdata = { + .max_clock_speed = 1100000, +}; + +#define KS8851_IRQ_GPIO 43 + +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 = 2, + .mode = SPI_MODE_0, + }, + { + .modalias = "epm_adc", + .max_speed_hz = 1100000, + .bus_num = 0, + .chip_select = 3, + .mode = SPI_MODE_0, + }, +}; + +static struct slim_boardinfo apq8064_slim_devices[] = { + { + .bus_num = 1, + .slim_slave = &apq8064_slim_tabla, + }, + { + .bus_num = 1, + .slim_slave = &apq8064_slim_tabla20, + }, + /* add more slimbus slaves as needed */ +}; + +static struct msm_i2c_platform_data apq8064_i2c_qup_gsbi1_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data apq8064_i2c_qup_gsbi3_pdata = { + .clk_freq = 384000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data apq8064_i2c_qup_gsbi4_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data mpq8064_i2c_qup_gsbi5_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +#define GSBI_DUAL_MODE_CODE 0x60 +#define MSM_GSBI1_PHYS 0x12440000 +static void __init apq8064_i2c_init(void) +{ + void __iomem *gsbi_mem; + + apq8064_device_qup_i2c_gsbi1.dev.platform_data = + &apq8064_i2c_qup_gsbi1_pdata; + gsbi_mem = ioremap_nocache(MSM_GSBI1_PHYS, 4); + writel_relaxed(GSBI_DUAL_MODE_CODE, gsbi_mem); + /* Ensure protocol code is written before proceeding */ + wmb(); + iounmap(gsbi_mem); + apq8064_i2c_qup_gsbi1_pdata.use_gsbi_shared_mode = 1; + apq8064_device_qup_i2c_gsbi3.dev.platform_data = + &apq8064_i2c_qup_gsbi3_pdata; + apq8064_device_qup_i2c_gsbi1.dev.platform_data = + &apq8064_i2c_qup_gsbi1_pdata; + apq8064_device_qup_i2c_gsbi4.dev.platform_data = + &apq8064_i2c_qup_gsbi4_pdata; + mpq8064_device_qup_i2c_gsbi5.dev.platform_data = + &mpq8064_i2c_qup_gsbi5_pdata; +} + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +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; + } + + return 0; +fail: + return ret; +} +#else +static int ethernet_init(void) +{ + return 0; +} +#endif + +#define GPIO_KEY_HOME PM8921_GPIO_PM_TO_SYS(27) +#define GPIO_KEY_VOLUME_UP PM8921_GPIO_PM_TO_SYS(35) +#define GPIO_KEY_VOLUME_DOWN PM8921_GPIO_PM_TO_SYS(38) +#define GPIO_KEY_CAM_FOCUS PM8921_GPIO_PM_TO_SYS(3) +#define GPIO_KEY_CAM_SNAP PM8921_GPIO_PM_TO_SYS(4) +#define GPIO_KEY_ROTATION PM8921_GPIO_PM_TO_SYS(42) + +static struct gpio_keys_button cdp_keys[] = { + { + .code = KEY_HOME, + .gpio = GPIO_KEY_HOME, + .desc = "home_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = KEY_VOLUMEUP, + .gpio = GPIO_KEY_VOLUME_UP, + .desc = "volume_up_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = KEY_VOLUMEDOWN, + .gpio = GPIO_KEY_VOLUME_DOWN, + .desc = "volume_down_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = SW_ROTATE_LOCK, + .gpio = GPIO_KEY_ROTATION, + .desc = "rotate_key", + .active_low = 1, + .type = EV_SW, + .debounce_interval = 15, + }, +}; + +static struct gpio_keys_platform_data cdp_keys_data = { + .buttons = cdp_keys, + .nbuttons = ARRAY_SIZE(cdp_keys), +}; + +static struct platform_device cdp_kp_pdev = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &cdp_keys_data, + }, +}; + +static struct gpio_keys_button mtp_keys[] = { + { + .code = KEY_CAMERA_FOCUS, + .gpio = GPIO_KEY_CAM_FOCUS, + .desc = "cam_focus_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = KEY_VOLUMEUP, + .gpio = GPIO_KEY_VOLUME_UP, + .desc = "volume_up_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = KEY_VOLUMEDOWN, + .gpio = GPIO_KEY_VOLUME_DOWN, + .desc = "volume_down_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = KEY_CAMERA_SNAPSHOT, + .gpio = GPIO_KEY_CAM_SNAP, + .desc = "cam_snap_key", + .active_low = 1, + .type = EV_KEY, + .debounce_interval = 15, + }, +}; + +static struct gpio_keys_platform_data mtp_keys_data = { + .buttons = mtp_keys, + .nbuttons = ARRAY_SIZE(mtp_keys), +}; + +static struct platform_device mtp_kp_pdev = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &mtp_keys_data, + }, +}; + +static struct gpio_keys_button mpq_keys[] = { + { + .code = KEY_VOLUMEDOWN, + .gpio = GPIO_KEY_VOLUME_DOWN, + .desc = "volume_down_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, + { + .code = KEY_VOLUMEUP, + .gpio = GPIO_KEY_VOLUME_UP, + .desc = "volume_up_key", + .active_low = 1, + .type = EV_KEY, + .wakeup = 1, + .debounce_interval = 15, + }, +}; + +static struct gpio_keys_platform_data mpq_keys_data = { + .buttons = mpq_keys, + .nbuttons = ARRAY_SIZE(mpq_keys), +}; + +static struct platform_device mpq_gpio_keys_pdev = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &mpq_keys_data, + }, +}; + +#define MPQ_KP_ROW_BASE SX150X_EXP2_GPIO_BASE +#define MPQ_KP_COL_BASE (SX150X_EXP2_GPIO_BASE + 4) + +static unsigned int mpq_row_gpios[] = {MPQ_KP_ROW_BASE, MPQ_KP_ROW_BASE + 1, + MPQ_KP_ROW_BASE + 2, MPQ_KP_ROW_BASE + 3}; +static unsigned int mpq_col_gpios[] = {MPQ_KP_COL_BASE, MPQ_KP_COL_BASE + 1, + MPQ_KP_COL_BASE + 2}; + +static const unsigned int mpq_keymap[] = { + KEY(0, 0, KEY_UP), + KEY(0, 1, KEY_ENTER), + KEY(0, 2, KEY_3), + + KEY(1, 0, KEY_DOWN), + KEY(1, 1, KEY_EXIT), + KEY(1, 2, KEY_4), + + KEY(2, 0, KEY_LEFT), + KEY(2, 1, KEY_1), + KEY(2, 2, KEY_5), + + KEY(3, 0, KEY_RIGHT), + KEY(3, 1, KEY_2), + KEY(3, 2, KEY_6), +}; + +static struct matrix_keymap_data mpq_keymap_data = { + .keymap_size = ARRAY_SIZE(mpq_keymap), + .keymap = mpq_keymap, +}; + +static struct matrix_keypad_platform_data mpq_keypad_data = { + .keymap_data = &mpq_keymap_data, + .row_gpios = mpq_row_gpios, + .col_gpios = mpq_col_gpios, + .num_row_gpios = ARRAY_SIZE(mpq_row_gpios), + .num_col_gpios = ARRAY_SIZE(mpq_col_gpios), + .col_scan_delay_us = 32000, + .debounce_ms = 20, + .wakeup = 1, + .active_low = 1, + .no_autorepeat = 1, +}; + +static struct platform_device mpq_keypad_device = { + .name = "matrix-keypad", + .id = -1, + .dev = { + .platform_data = &mpq_keypad_data, + }, +}; + +/* Sensors DSPS platform data */ +#define DSPS_PIL_GENERIC_NAME "dsps" +static void __init apq8064_init_dsps(void) +{ + struct msm_dsps_platform_data *pdata = + msm_dsps_device_8064.dev.platform_data; + pdata->pil_name = DSPS_PIL_GENERIC_NAME; + pdata->gpios = NULL; + pdata->gpios_num = 0; + + platform_device_register(&msm_dsps_device_8064); +} + +#define I2C_SURF 1 +#define I2C_FFA (1 << 1) +#define I2C_RUMI (1 << 2) +#define I2C_SIM (1 << 3) +#define I2C_LIQUID (1 << 4) +#define I2C_MPQ_CDP BIT(5) +#define I2C_MPQ_HRD BIT(6) +#define I2C_MPQ_DTV BIT(7) + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; + +static struct i2c_registry apq8064_i2c_devices[] __initdata = { + { + I2C_LIQUID, + APQ_8064_GSBI1_QUP_I2C_BUS_ID, + smb349_charger_i2c_info, + ARRAY_SIZE(smb349_charger_i2c_info) + }, + { + I2C_SURF | I2C_LIQUID, + APQ_8064_GSBI3_QUP_I2C_BUS_ID, + mxt_device_info, + ARRAY_SIZE(mxt_device_info), + }, + { + I2C_FFA, + APQ_8064_GSBI3_QUP_I2C_BUS_ID, + cyttsp_info, + ARRAY_SIZE(cyttsp_info), + }, + { + I2C_FFA | I2C_LIQUID, + APQ_8064_GSBI1_QUP_I2C_BUS_ID, + isa1200_board_info, + ARRAY_SIZE(isa1200_board_info), + }, + { + I2C_MPQ_CDP, + APQ_8064_GSBI5_QUP_I2C_BUS_ID, + cs8427_device_info, + ARRAY_SIZE(cs8427_device_info), + }, +}; + +#define SX150X_EXP1_INT_N PM8921_MPP_IRQ(PM8921_IRQ_BASE, 9) +#define SX150X_EXP2_INT_N MSM_GPIO_TO_INT(81) + +struct sx150x_platform_data mpq8064_sx150x_pdata[] = { + [SX150X_EXP1] = { + .gpio_base = SX150X_EXP1_GPIO_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0, + .io_pulldn_ena = 0x0, + .io_open_drain_ena = 0x0, + .io_polarity = 0, + .irq_summary = SX150X_EXP1_INT_N, + .irq_base = SX150X_EXP1_IRQ_BASE, + }, + [SX150X_EXP2] = { + .gpio_base = SX150X_EXP2_GPIO_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0f, + .io_pulldn_ena = 0x70, + .io_open_drain_ena = 0x0, + .io_polarity = 0, + .irq_summary = SX150X_EXP2_INT_N, + .irq_base = SX150X_EXP2_IRQ_BASE, + }, + [SX150X_EXP3] = { + .gpio_base = SX150X_EXP3_GPIO_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0, + .io_pulldn_ena = 0x0, + .io_open_drain_ena = 0x0, + .io_polarity = 0, + .irq_summary = -1, + }, + [SX150X_EXP4] = { + .gpio_base = SX150X_EXP4_GPIO_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0, + .io_pulldn_ena = 0x0, + .io_open_drain_ena = 0x0, + .io_polarity = 0, + .irq_summary = -1, + }, +}; + +static struct i2c_board_info sx150x_gpio_exp_info[] = { + { + I2C_BOARD_INFO("sx1509q", 0x70), + .platform_data = &mpq8064_sx150x_pdata[SX150X_EXP1], + }, + { + I2C_BOARD_INFO("sx1508q", 0x23), + .platform_data = &mpq8064_sx150x_pdata[SX150X_EXP2], + }, + { + I2C_BOARD_INFO("sx1508q", 0x22), + .platform_data = &mpq8064_sx150x_pdata[SX150X_EXP3], + }, + { + I2C_BOARD_INFO("sx1509q", 0x3E), + .platform_data = &mpq8064_sx150x_pdata[SX150X_EXP4], + }, +}; + +#define MPQ8064_I2C_GSBI5_BUS_ID 5 + +static struct i2c_registry mpq8064_i2c_devices[] __initdata = { + { + I2C_MPQ_CDP, + MPQ8064_I2C_GSBI5_BUS_ID, + sx150x_gpio_exp_info, + ARRAY_SIZE(sx150x_gpio_exp_info), + }, +}; + +static void __init register_i2c_devices(void) +{ + u8 mach_mask = 0; + int i; + +#ifdef CONFIG_MSM_CAMERA + struct i2c_registry apq8064_camera_i2c_devices = { + I2C_SURF | I2C_FFA | I2C_LIQUID | I2C_RUMI, + APQ_8064_GSBI4_QUP_I2C_BUS_ID, + apq8064_camera_board_info.board_info, + apq8064_camera_board_info.num_i2c_board_info, + }; +#endif + /* Build the matching 'supported_machs' bitmask */ + if (machine_is_apq8064_cdp()) + mach_mask = I2C_SURF; + else if (machine_is_apq8064_mtp()) + mach_mask = I2C_FFA; + else if (machine_is_apq8064_liquid()) + mach_mask = I2C_LIQUID; + else if (machine_is_apq8064_rumi3()) + mach_mask = I2C_RUMI; + else if (machine_is_apq8064_sim()) + mach_mask = I2C_SIM; + else if (PLATFORM_IS_MPQ8064()) + mach_mask = I2C_MPQ_CDP; + 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(apq8064_i2c_devices); ++i) { + if (apq8064_i2c_devices[i].machs & mach_mask) + i2c_register_board_info(apq8064_i2c_devices[i].bus, + apq8064_i2c_devices[i].info, + apq8064_i2c_devices[i].len); + } +#ifdef CONFIG_MSM_CAMERA + if (apq8064_camera_i2c_devices.machs & mach_mask) + i2c_register_board_info(apq8064_camera_i2c_devices.bus, + apq8064_camera_i2c_devices.info, + apq8064_camera_i2c_devices.len); +#endif + + for (i = 0; i < ARRAY_SIZE(mpq8064_i2c_devices); ++i) { + if (mpq8064_i2c_devices[i].machs & mach_mask) + i2c_register_board_info( + mpq8064_i2c_devices[i].bus, + mpq8064_i2c_devices[i].info, + mpq8064_i2c_devices[i].len); + } +} + +static void enable_ddr3_regulator(void) +{ + static struct regulator *ext_ddr3; + + /* Use MPP7 output state as a flag for PCDDR3 presence. */ + if (gpio_get_value_cansleep(PM8921_MPP_PM_TO_SYS(7)) > 0) { + ext_ddr3 = regulator_get(NULL, "ext_ddr3"); + if (IS_ERR(ext_ddr3) || ext_ddr3 == NULL) + pr_err("Could not get MPP7 regulator\n"); + else + regulator_enable(ext_ddr3); + } +} + +static void enable_avc_i2c_bus(void) +{ + int avc_i2c_en_mpp = PM8921_MPP_PM_TO_SYS(8); + int rc; + + rc = gpio_request(avc_i2c_en_mpp, "avc_i2c_en"); + if (rc) + pr_err("request for avc_i2c_en mpp failed," + "rc=%d\n", rc); + else + gpio_set_value_cansleep(avc_i2c_en_mpp, 1); +} + +static void __init apq8064_common_init(void) +{ + msm_tsens_early_init(&apq_tsens_pdata); + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); + BUG_ON(msm_rpm_init(&apq8064_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + regulator_suppress_info_printing(); + platform_device_register(&apq8064_device_rpm_regulator); + if (msm_xo_init()) + pr_err("Failed to initialize XO votes\n"); + msm_clock_init(&apq8064_clock_init_data); + apq8064_init_gpiomux(); + apq8064_i2c_init(); + register_i2c_devices(); + + apq8064_device_qup_spi_gsbi5.dev.platform_data = + &apq8064_qup_spi_gsbi5_pdata; + apq8064_init_pmic(); + if (machine_is_apq8064_liquid()) + msm_otg_pdata.mhl_enable = true; + + android_usb_pdata.swfi_latency = + msm_rpmrs_levels[0].latency_us; + + apq8064_device_otg.dev.platform_data = &msm_otg_pdata; + apq8064_ehci_host_init(); + apq8064_init_buses(); + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + if (!(machine_is_mpq8064_cdp() || machine_is_mpq8064_hrd() || + machine_is_mpq8064_dtv())) + platform_add_devices(common_not_mpq_devices, + ARRAY_SIZE(common_not_mpq_devices)); + enable_ddr3_regulator(); + if (machine_is_apq8064_mtp()) { + apq8064_device_hsic_host.dev.platform_data = &msm_hsic_pdata; + device_initialize(&apq8064_device_hsic_host.dev); + } + apq8064_pm8xxx_gpio_mpp_init(); + apq8064_init_mmc(); + + if (machine_is_apq8064_mtp()) { + mdm_8064_device.dev.platform_data = &mdm_platform_data; + platform_device_register(&mdm_8064_device); + } + platform_device_register(&apq8064_slim_ctrl); + slim_register_board_info(apq8064_slim_devices, + ARRAY_SIZE(apq8064_slim_devices)); + apq8064_init_dsps(); + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + acpuclk_init(&acpuclk_8064_soc_data); + msm_spm_l2_init(msm_spm_l2_data); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_init_sleep_status_data(&msm_pm_slp_sts_data); + apq8064_epm_adc_init(); +} + +static void __init apq8064_allocate_memory_regions(void) +{ + apq8064_allocate_fb_region(); +} + +static void __init apq8064_sim_init(void) +{ + struct msm_watchdog_pdata *wdog_pdata = (struct msm_watchdog_pdata *) + &msm8064_device_watchdog.dev.platform_data; + + wdog_pdata->bark_time = 15000; + apq8064_common_init(); + platform_add_devices(sim_devices, ARRAY_SIZE(sim_devices)); +} + +static void __init apq8064_rumi3_init(void) +{ + apq8064_common_init(); + ethernet_init(); + platform_add_devices(rumi3_devices, ARRAY_SIZE(rumi3_devices)); + spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); +} + +static void __init apq8064_cdp_init(void) +{ + if (meminfo_init(SYS_MEMORY, SZ_256M) < 0) + pr_err("meminfo_init() failed!\n"); + apq8064_common_init(); + if (machine_is_mpq8064_cdp() || machine_is_mpq8064_hrd() || + machine_is_mpq8064_dtv()) { + enable_avc_i2c_bus(); + platform_add_devices(mpq_devices, ARRAY_SIZE(mpq_devices)); + mpq8064_pcie_init(); + } else { + ethernet_init(); + platform_add_devices(cdp_devices, ARRAY_SIZE(cdp_devices)); + spi_register_board_info(spi_board_info, + ARRAY_SIZE(spi_board_info)); + } + apq8064_init_fb(); + apq8064_init_gpu(); + platform_add_devices(apq8064_footswitch, apq8064_num_footswitch); +#ifdef CONFIG_MSM_CAMERA + apq8064_init_cam(); +#endif + + if (machine_is_apq8064_cdp() || machine_is_apq8064_liquid()) + platform_device_register(&cdp_kp_pdev); + + if (machine_is_apq8064_mtp()) + platform_device_register(&mtp_kp_pdev); + + change_memory_power = &apq8064_change_memory_power; + + if (machine_is_mpq8064_cdp()) { + platform_device_register(&mpq_gpio_keys_pdev); + platform_device_register(&mpq_keypad_device); + } +} + +MACHINE_START(APQ8064_SIM, "QCT APQ8064 SIMULATOR") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_sim_init, +MACHINE_END + +MACHINE_START(APQ8064_RUMI3, "QCT APQ8064 RUMI3") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_rumi3_init, + .init_early = apq8064_allocate_memory_regions, +MACHINE_END + +MACHINE_START(APQ8064_CDP, "QCT APQ8064 CDP") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_cdp_init, + .init_early = apq8064_allocate_memory_regions, + .init_very_early = apq8064_early_reserve, +MACHINE_END + +MACHINE_START(APQ8064_MTP, "QCT APQ8064 MTP") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_cdp_init, + .init_early = apq8064_allocate_memory_regions, + .init_very_early = apq8064_early_reserve, +MACHINE_END + +MACHINE_START(APQ8064_LIQUID, "QCT APQ8064 LIQUID") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_cdp_init, + .init_early = apq8064_allocate_memory_regions, + .init_very_early = apq8064_early_reserve, +MACHINE_END + +MACHINE_START(MPQ8064_CDP, "QCT MPQ8064 CDP") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_cdp_init, + .init_early = apq8064_allocate_memory_regions, + .init_very_early = apq8064_early_reserve, +MACHINE_END + +MACHINE_START(MPQ8064_HRD, "QCT MPQ8064 HRD") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_cdp_init, + .init_early = apq8064_allocate_memory_regions, + .init_very_early = apq8064_early_reserve, +MACHINE_END + +MACHINE_START(MPQ8064_DTV, "QCT MPQ8064 DTV") + .map_io = apq8064_map_io, + .reserve = apq8064_reserve, + .init_irq = apq8064_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = apq8064_cdp_init, + .init_early = apq8064_allocate_memory_regions, + .init_very_early = apq8064_early_reserve, +MACHINE_END + diff --git a/arch/arm/mach-msm/board-8064.h b/arch/arm/mach-msm/board-8064.h new file mode 100644 index 0000000000000000000000000000000000000000..39c367fe06ba5bcd75e74a31612adc4df12d73e0 --- /dev/null +++ b/arch/arm/mach-msm/board-8064.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_APQ8064_H +#define __ARCH_ARM_MACH_MSM_BOARD_APQ8064_H + +#include +#include +#include +#include +#include +#include +#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_mpp) (pm_mpp - 1 + PM8921_MPP_BASE) +#define PM8921_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) + +#define PM8821_MPP_BASE (PM8921_MPP_BASE + PM8921_NR_MPPS) +#define PM8821_MPP_PM_TO_SYS(pm_mpp) (pm_mpp - 1 + PM8821_MPP_BASE) +#define PM8821_IRQ_BASE (PM8921_IRQ_BASE + PM8921_NR_IRQS) + +#define TABLA_INTERRUPT_BASE (PM8821_IRQ_BASE + PM8821_NR_IRQS) + +extern struct pm8xxx_regulator_platform_data + msm8064_pm8921_regulator_pdata[] __devinitdata; + +extern int msm8064_pm8921_regulator_pdata_len __devinitdata; + +#define GPIO_VREG_ID_EXT_5V 0 +#define GPIO_VREG_ID_EXT_3P3V 1 +#define GPIO_VREG_ID_EXT_TS_SW 2 +#define GPIO_VREG_ID_EXT_MPP8 3 + +#define GPIO_VREG_ID_FRC_5V 0 +#define GPIO_VREG_ID_AVC_1P2V 1 +#define GPIO_VREG_ID_AVC_1P8V 2 +#define GPIO_VREG_ID_AVC_2P2V 3 +#define GPIO_VREG_ID_AVC_5V 4 +#define GPIO_VREG_ID_AVC_3P3V 5 + +#define APQ8064_EXT_3P3V_REG_EN_GPIO 77 + +extern struct gpio_regulator_platform_data + apq8064_gpio_regulator_pdata[] __devinitdata; + +extern struct gpio_regulator_platform_data + mpq8064_gpio_regulator_pdata[] __devinitdata; + +extern struct rpm_regulator_platform_data + apq8064_rpm_regulator_pdata __devinitdata; + +extern struct regulator_init_data msm8064_saw_regulator_pdata_8921_s5; +extern struct regulator_init_data msm8064_saw_regulator_pdata_8921_s6; +extern struct regulator_init_data msm8064_saw_regulator_pdata_8821_s0; +extern struct regulator_init_data msm8064_saw_regulator_pdata_8821_s1; + +struct mmc_platform_data; +int __init apq8064_add_sdcc(unsigned int controller, + struct mmc_platform_data *plat); + +void apq8064_init_mmc(void); +void apq8064_init_gpiomux(void); +void apq8064_init_pmic(void); + +extern struct msm_camera_board_info apq8064_camera_board_info; +void apq8064_init_cam(void); + +#define APQ_8064_GSBI1_QUP_I2C_BUS_ID 0 +#define APQ_8064_GSBI3_QUP_I2C_BUS_ID 3 +#define APQ_8064_GSBI4_QUP_I2C_BUS_ID 4 +#define APQ_8064_GSBI5_QUP_I2C_BUS_ID 5 + +unsigned char apq8064_hdmi_as_primary_selected(void); +void apq8064_init_fb(void); +void apq8064_allocate_fb_region(void); +void apq8064_mdp_writeback(struct memtype_reserve *reserve_table); +void __init apq8064_set_display_params(char *prim_panel, char *ext_panel); + +void apq8064_init_gpu(void); +void apq8064_pm8xxx_gpio_mpp_init(void); + +#define PLATFORM_IS_MPQ8064() \ + (machine_is_mpq8064_hrd() || \ + machine_is_mpq8064_dtv() || \ + machine_is_mpq8064_cdp() \ + ) + + +#define GPIO_EXPANDER_IRQ_BASE (TABLA_INTERRUPT_BASE + \ + NR_TABLA_IRQS) +#define GPIO_EXPANDER_GPIO_BASE (PM8821_MPP_BASE + PM8821_NR_MPPS) + +#define GPIO_EPM_EXPANDER_BASE GPIO_EXPANDER_GPIO_BASE +#define SX150X_EPM_NR_GPIOS 16 +#define SX150X_EPM_NR_IRQS 8 + +#define SX150X_EXP1_GPIO_BASE (GPIO_EPM_EXPANDER_BASE + \ + SX150X_EPM_NR_GPIOS) +#define SX150X_EXP1_IRQ_BASE (GPIO_EXPANDER_IRQ_BASE + \ + SX150X_EPM_NR_IRQS) +#define SX150X_EXP1_NR_IRQS 16 +#define SX150X_EXP1_NR_GPIOS 16 + +#define SX150X_EXP2_GPIO_BASE (SX150X_EXP1_GPIO_BASE + \ + SX150X_EXP1_NR_GPIOS) +#define SX150X_EXP2_IRQ_BASE (SX150X_EXP1_IRQ_BASE + SX150X_EXP1_NR_IRQS) +#define SX150X_EXP2_NR_IRQS 8 +#define SX150X_EXP2_NR_GPIOS 8 + +#define SX150X_EXP3_GPIO_BASE (SX150X_EXP2_GPIO_BASE + \ + SX150X_EXP2_NR_GPIOS) +#define SX150X_EXP3_IRQ_BASE (SX150X_EXP2_IRQ_BASE + SX150X_EXP2_NR_IRQS) +#define SX150X_EXP3_NR_IRQS 8 +#define SX150X_EXP3_NR_GPIOS 8 + +#define SX150X_EXP4_GPIO_BASE (SX150X_EXP3_GPIO_BASE + \ + SX150X_EXP3_NR_GPIOS) +#define SX150X_EXP4_IRQ_BASE (SX150X_EXP3_IRQ_BASE + SX150X_EXP3_NR_IRQS) +#define SX150X_EXP4_NR_IRQS 16 +#define SX150X_EXP4_NR_GPIOS 16 + +#define SX150X_GPIO(_expander, _pin) (SX150X_EXP##_expander##_GPIO_BASE + _pin) + +enum { + SX150X_EPM, + SX150X_EXP1, + SX150X_EXP2, + SX150X_EXP3, + SX150X_EXP4, +}; + +extern struct msm_rtb_platform_data apq8064_rtb_pdata; +extern struct msm_cache_dump_platform_data apq8064_cache_dump_pdata; +#endif diff --git a/arch/arm/mach-msm/board-8930-camera.c b/arch/arm/mach-msm/board-8930-camera.c new file mode 100644 index 0000000000000000000000000000000000000000..e7f0e68a8ee45eaa615e5e2423396248e7dc3835 --- /dev/null +++ b/arch/arm/mach-msm/board-8930-camera.c @@ -0,0 +1,636 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "devices.h" +#include "board-8930.h" + +#ifdef CONFIG_MSM_CAMERA + +#if (defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE)) && \ + defined(CONFIG_I2C) + +static struct i2c_board_info cam_expander_i2c_info[] = { + { + I2C_BOARD_INFO("sx1508q", 0x22), + .platform_data = &msm8930_sx150x_data[SX150X_CAM] + }, +}; + +static struct msm_cam_expander_info cam_expander_info[] = { + { + cam_expander_i2c_info, + MSM_8930_GSBI4_QUP_I2C_BUS_ID, + }, +}; +#endif + +static struct gpiomux_setting cam_settings[] = { + { + .func = GPIOMUX_FUNC_GPIO, /*suspend*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + + { + .func = GPIOMUX_FUNC_1, /*active 1*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_GPIO, /*active 2*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_1, /*active 3*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_5, /*active 4*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_6, /*active 5*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_2, /*active 6*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_3, /*active 7*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_GPIO, /*i2c suspend*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, + }, + { + .func = GPIOMUX_FUNC_2, /*active 9*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + +}; + + +static struct msm_gpiomux_config msm8930_cam_common_configs[] = { + { + .gpio = 2, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[1], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[9], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 5, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[1], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 76, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 107, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 54, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, +}; + +static struct msm_gpiomux_config msm8930_cam_2d_configs[] = { + { + .gpio = 18, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 19, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 20, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 21, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, +}; + +#define VFE_CAMIF_TIMER1_GPIO 2 +#define VFE_CAMIF_TIMER2_GPIO 3 +#define VFE_CAMIF_TIMER3_GPIO_INT 4 +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), +}; + +#ifdef CONFIG_MSM_CAMERA_FLASH +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_EXT, + ._fsrc.ext_driver_src.led_en = VFE_CAMIF_TIMER1_GPIO, + ._fsrc.ext_driver_src.led_flash_en = VFE_CAMIF_TIMER2_GPIO, + ._fsrc.ext_driver_src.flash_id = MAM_CAMERA_EXT_LED_FLASH_TPS61310, +}; +#endif + +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 = 27648000, + .ib = 110592000, + }, + { + .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 = 140451840, + .ib = 561807360, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 206807040, + .ib = 488816640, + }, + { + .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 = 274423680, + .ib = 1097694720, + }, + { + .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 = 540000000, + .ib = 1350000000, + }, +}; + +static struct msm_bus_vectors cam_zsl_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 302071680, + .ib = 1208286720, + }, + { + .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 = 540000000, + .ib = 1350000000, + }, +}; + +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, + }, +}; + +static struct msm_bus_scale_pdata cam_bus_client_pdata = { + cam_bus_client_config, + ARRAY_SIZE(cam_bus_client_config), + .name = "msm_camera", +}; + +static struct msm_camera_device_platform_data msm_camera_csi_device_data[] = { + { + .csid_core = 0, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, + { + .csid_core = 1, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, +}; + +static struct camera_vreg_t msm_8930_back_cam_vreg[] = { + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +static struct camera_vreg_t msm_8930_front_cam_vreg[] = { + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, +}; + +static struct gpio msm8930_common_cam_gpio[] = { + {20, GPIOF_DIR_IN, "CAMIF_I2C_DATA"}, + {21, GPIOF_DIR_IN, "CAMIF_I2C_CLK"}, +}; + +static struct gpio msm8930_front_cam_gpio[] = { + {4, GPIOF_DIR_IN, "CAMIF_MCLK"}, + {76, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct gpio msm8930_back_cam_gpio[] = { + {5, GPIOF_DIR_IN, "CAMIF_MCLK"}, + {107, GPIOF_DIR_OUT, "CAM_RESET"}, + {54, GPIOF_DIR_OUT, "CAM_STBY_N"}, +}; + +static struct msm_gpio_set_tbl msm8930_front_cam_gpio_set_tbl[] = { + {76, GPIOF_OUT_INIT_LOW, 1000}, + {76, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_gpio_set_tbl msm8930_back_cam_gpio_set_tbl[] = { + {54, GPIOF_OUT_INIT_LOW, 1000}, + {54, GPIOF_OUT_INIT_HIGH, 4000}, + {107, GPIOF_OUT_INIT_LOW, 1000}, + {107, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_camera_gpio_conf msm_8930_front_cam_gpio_conf = { + .cam_gpiomux_conf_tbl = msm8930_cam_2d_configs, + .cam_gpiomux_conf_tbl_size = ARRAY_SIZE(msm8930_cam_2d_configs), + .cam_gpio_common_tbl = msm8930_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(msm8930_common_cam_gpio), + .cam_gpio_req_tbl = msm8930_front_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(msm8930_front_cam_gpio), + .cam_gpio_set_tbl = msm8930_front_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(msm8930_front_cam_gpio_set_tbl), +}; + +static struct msm_camera_gpio_conf msm_8930_back_cam_gpio_conf = { + .cam_gpiomux_conf_tbl = msm8930_cam_2d_configs, + .cam_gpiomux_conf_tbl_size = ARRAY_SIZE(msm8930_cam_2d_configs), + .cam_gpio_common_tbl = msm8930_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(msm8930_common_cam_gpio), + .cam_gpio_req_tbl = msm8930_back_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(msm8930_back_cam_gpio), + .cam_gpio_set_tbl = msm8930_back_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(msm8930_back_cam_gpio_set_tbl), +}; + +static struct i2c_board_info msm_act_main_cam_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x11), +}; + +static struct msm_actuator_info msm_act_main_cam_0_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_0, + .bus_id = MSM_8930_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + +static struct msm_camera_sensor_flash_data flash_imx074 = { + .flash_type = MSM_CAMERA_FLASH_LED, +#ifdef CONFIG_MSM_CAMERA_FLASH + .flash_src = &msm_flash_src +#endif +}; + +static struct msm_camera_csi_lane_params imx074_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_imx074 = { + .mount_angle = 90, + .cam_vreg = msm_8930_back_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8930_back_cam_vreg), + .gpio_conf = &msm_8930_back_cam_gpio_conf, + .csi_lane_params = &imx074_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx074_data = { + .sensor_name = "imx074", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_imx074, + .strobe_flash_data = &strobe_flash_xenon, + .sensor_platform_info = &sensor_board_info_imx074, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_0_info, +}; + +static struct camera_vreg_t msm_8930_mt9m114_vreg[] = { + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +static struct msm_camera_sensor_flash_data flash_mt9m114 = { + .flash_type = MSM_CAMERA_FLASH_NONE +}; + +static struct msm_camera_csi_lane_params mt9m114_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0x1, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_mt9m114 = { + .mount_angle = 90, + .cam_vreg = msm_8930_mt9m114_vreg, + .num_vreg = ARRAY_SIZE(msm_8930_mt9m114_vreg), + .gpio_conf = &msm_8930_front_cam_gpio_conf, + .csi_lane_params = &mt9m114_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9m114_data = { + .sensor_name = "mt9m114", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_mt9m114, + .sensor_platform_info = &sensor_board_info_mt9m114, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = YUV_SENSOR, +}; + +static struct msm_camera_sensor_flash_data flash_ov2720 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_csi_lane_params ov2720_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0x3, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_ov2720 = { + .mount_angle = 0, + .cam_vreg = msm_8930_front_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8930_front_cam_vreg), + .gpio_conf = &msm_8930_front_cam_gpio_conf, + .csi_lane_params = &ov2720_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov2720_data = { + .sensor_name = "ov2720", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_ov2720, + .sensor_platform_info = &sensor_board_info_ov2720, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = BAYER_SENSOR, +}; + +static struct camera_vreg_t msm_8930_s5k3l1yx_vreg[] = { + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vaf", REG_LDO, 2800000, 2850000, 300000}, +}; + +static struct msm_camera_sensor_flash_data flash_s5k3l1yx = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_csi_lane_params s5k3l1yx_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_s5k3l1yx = { + .mount_angle = 90, + .cam_vreg = msm_8930_s5k3l1yx_vreg, + .num_vreg = ARRAY_SIZE(msm_8930_s5k3l1yx_vreg), + .gpio_conf = &msm_8930_back_cam_gpio_conf, + .csi_lane_params = &s5k3l1yx_csi_lane_params, +}; + +static struct msm_actuator_info msm_act_main_cam_2_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_2, + .bus_id = MSM_8930_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k3l1yx_data = { + .sensor_name = "s5k3l1yx", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_s5k3l1yx, + .sensor_platform_info = &sensor_board_info_s5k3l1yx, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_2_info, +}; + +static struct platform_device msm_camera_server = { + .name = "msm_cam_server", + .id = 0, +}; + +void __init msm8930_init_cam(void) +{ + msm_gpiomux_install(msm8930_cam_common_configs, + ARRAY_SIZE(msm8930_cam_common_configs)); + + if (machine_is_msm8930_cdp()) { + struct msm_camera_sensor_info *s_info; + s_info = &msm_camera_sensor_s5k3l1yx_data; + s_info->sensor_platform_info->mount_angle = 0; + msm_flash_src._fsrc.ext_driver_src.led_en = + GPIO_CAM_GP_LED_EN1; + msm_flash_src._fsrc.ext_driver_src.led_flash_en = + GPIO_CAM_GP_LED_EN2; +#if defined(CONFIG_I2C) && (defined(CONFIG_GPIO_SX150X) || \ + defined(CONFIG_GPIO_SX150X_MODULE)) + msm_flash_src._fsrc.ext_driver_src.expander_info = + cam_expander_info; +#endif + } + + platform_device_register(&msm_camera_server); + platform_device_register(&msm8960_device_csiphy0); + platform_device_register(&msm8960_device_csiphy1); + platform_device_register(&msm8960_device_csid0); + platform_device_register(&msm8960_device_csid1); + platform_device_register(&msm8960_device_ispif); + platform_device_register(&msm8960_device_vfe); + platform_device_register(&msm8960_device_vpe); +} + +#ifdef CONFIG_I2C +struct i2c_board_info msm8930_camera_i2c_boardinfo[] = { + { + I2C_BOARD_INFO("imx074", 0x1A), + .platform_data = &msm_camera_sensor_imx074_data, + }, + { + I2C_BOARD_INFO("ov2720", 0x6C), + .platform_data = &msm_camera_sensor_ov2720_data, + }, + { + I2C_BOARD_INFO("mt9m114", 0x48), + .platform_data = &msm_camera_sensor_mt9m114_data, + }, + { + I2C_BOARD_INFO("s5k3l1yx", 0x20), + .platform_data = &msm_camera_sensor_s5k3l1yx_data, + }, + { + I2C_BOARD_INFO("tps61310", 0x66), + }, +}; + +struct msm_camera_board_info msm8930_camera_board_info = { + .board_info = msm8930_camera_i2c_boardinfo, + .num_i2c_board_info = ARRAY_SIZE(msm8930_camera_i2c_boardinfo), +}; +#endif +#endif diff --git a/arch/arm/mach-msm/board-8930-display.c b/arch/arm/mach-msm/board-8930-display.c new file mode 100644 index 0000000000000000000000000000000000000000..e18e40d8481de527483e9c4ec048917eca38ff5f --- /dev/null +++ b/arch/arm/mach-msm/board-8930-display.c @@ -0,0 +1,791 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "board-8930.h" + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_PRIM_BUF_SIZE \ + (roundup((1920 * 1088 * 4), 4096) * 3) /* 4 bpp x 3 pages */ +#else +#define MSM_FB_PRIM_BUF_SIZE \ + (roundup((1920 * 1088 * 4), 4096) * 2) /* 4 bpp x 2 pages */ +#endif +/* Note: must be multiple of 4096 */ +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE, 4096) + +#ifdef CONFIG_FB_MSM_OVERLAY0_WRITEBACK +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE roundup((1376 * 768 * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY0_WRITEBACK */ + +#ifdef CONFIG_FB_MSM_OVERLAY1_WRITEBACK +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE roundup((1920 * 1088 * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY1_WRITEBACK */ + +#define MDP_VSYNC_GPIO 0 + +#define MIPI_CMD_NOVATEK_QHD_PANEL_NAME "mipi_cmd_novatek_qhd" +#define MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME "mipi_video_novatek_qhd" +#define MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME "mipi_video_toshiba_wsvga" +#define MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME "mipi_video_chimei_wxga" +#define MIPI_VIDEO_SIMULATOR_VGA_PANEL_NAME "mipi_video_simulator_vga" +#define MIPI_CMD_RENESAS_FWVGA_PANEL_NAME "mipi_cmd_renesas_fwvga" +#define HDMI_PANEL_NAME "hdmi_msm" +#define TVOUT_PANEL_NAME "tvout_msm" + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static int msm_fb_detect_panel(const char *name) +{ + if (!strncmp(name, MIPI_CMD_NOVATEK_QHD_PANEL_NAME, + strnlen(MIPI_CMD_NOVATEK_QHD_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + +#if !defined(CONFIG_FB_MSM_LVDS_MIPI_PANEL_DETECT) && \ + !defined(CONFIG_FB_MSM_MIPI_PANEL_DETECT) + if (!strncmp(name, MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME, + strnlen(MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_VIDEO_SIMULATOR_VGA_PANEL_NAME, + strnlen(MIPI_VIDEO_SIMULATOR_VGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_CMD_RENESAS_FWVGA_PANEL_NAME, + strnlen(MIPI_CMD_RENESAS_FWVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; +#endif + + if (!strncmp(name, HDMI_PANEL_NAME, + strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, TVOUT_PANEL_NAME, + strnlen(TVOUT_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + 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, +}; + +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 bool dsi_power_on; + +/* + * TODO: When physical 8930/PM8038 hardware becomes + * available, replace mipi_dsi_cdp_panel_power with + * appropriate function. + */ +#define DISP_RST_GPIO 58 +static int mipi_dsi_cdp_panel_power(int on) +{ + static struct regulator *reg_l8, *reg_l23, *reg_l2; + int rc; + + pr_debug("%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 8038_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 8038_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 8038_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; + } + rc = gpio_request(DISP_RST_GPIO, "disp_rst_n"); + if (rc) { + pr_err("request gpio DISP_RST_GPIO failed, rc=%d\n", + rc); + gpio_free(DISP_RST_GPIO); + return -ENODEV; + } + 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; + } + usleep(10000); + gpio_set_value(DISP_RST_GPIO, 1); + usleep(10); + gpio_set_value(DISP_RST_GPIO, 0); + usleep(20); + gpio_set_value(DISP_RST_GPIO, 1); + } else { + + gpio_set_value(DISP_RST_GPIO, 0); + + rc = regulator_disable(reg_l2); + if (rc) { + pr_err("disable reg_l2 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_l8); + if (rc) { + pr_err("disable reg_l8 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_l23); + if (rc) { + pr_err("disable reg_l23 failed, rc=%d\n", rc); + return -ENODEV; + } + 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; + } + } + return 0; +} + +static int mipi_dsi_panel_power(int on) +{ + pr_debug("%s: on=%d\n", __func__, on); + + return mipi_dsi_cdp_panel_power(on); +} + +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[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +static struct msm_bus_vectors hdmi_as_primary_vectors[] = { + /* If HDMI is used as primary */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2000000000, + .ib = 2000000000, + }, +}; +static struct msm_bus_paths mdp_bus_scale_usecases[] = { + { + ARRAY_SIZE(mdp_init_vectors), + mdp_init_vectors, + }, + { + ARRAY_SIZE(hdmi_as_primary_vectors), + hdmi_as_primary_vectors, + }, + { + ARRAY_SIZE(hdmi_as_primary_vectors), + hdmi_as_primary_vectors, + }, + { + ARRAY_SIZE(hdmi_as_primary_vectors), + hdmi_as_primary_vectors, + }, + { + ARRAY_SIZE(hdmi_as_primary_vectors), + hdmi_as_primary_vectors, + }, + { + ARRAY_SIZE(hdmi_as_primary_vectors), + hdmi_as_primary_vectors, + }, +}; +#else +static struct msm_bus_vectors mdp_ui_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 216000000 * 2, + .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_EBI_CH0, + .ab = 216000000 * 2, + .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_EBI_CH0, + .ab = 230400000 * 2, + .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_EBI_CH0, + .ab = 334080000 * 2, + .ib = 417600000 * 2, + }, +}; + +static struct msm_bus_paths mdp_bus_scale_usecases[] = { + { + ARRAY_SIZE(mdp_init_vectors), + mdp_init_vectors, + }, + { + ARRAY_SIZE(mdp_ui_vectors), + mdp_ui_vectors, + }, + { + ARRAY_SIZE(mdp_ui_vectors), + mdp_ui_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, + }, +}; +#endif + +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_FB_MSM_HDMI_AS_PRIMARY +static int mdp_core_clk_rate_table[] = { + 200000000, + 200000000, + 200000000, + 200000000, +}; +#else +static int mdp_core_clk_rate_table[] = { + 85330000, + 128000000, + 160000000, + 200000000, +}; +#endif + +static struct msm_panel_common_pdata mdp_pdata = { + .gpio = MDP_VSYNC_GPIO, +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY + .mdp_core_clk_rate = 200000000, +#else + .mdp_core_clk_rate = 85330000, +#endif + .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, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .mem_hid = BIT(ION_CP_MM_HEAP_ID), +#else + .mem_hid = MEMTYPE_EBI1, +#endif +}; + +void __init msm8930_mdp_writeback(struct memtype_reserve* reserve_table) +{ + mdp_pdata.ov0_wb_size = MSM_FB_OVERLAY0_WRITEBACK_SIZE; + mdp_pdata.ov1_wb_size = MSM_FB_OVERLAY1_WRITEBACK_SIZE; +#if defined(CONFIG_ANDROID_PMEM) && !defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov0_wb_size; + reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov1_wb_size; +#endif +} + +#define LPM_CHANNEL0 0 +static int toshiba_gpio[] = {LPM_CHANNEL0}; + +static struct mipi_dsi_panel_platform_data toshiba_pdata = { + .gpio = toshiba_gpio, +}; + +static struct platform_device mipi_dsi_toshiba_panel_device = { + .name = "mipi_toshiba", + .id = 0, + .dev = { + .platform_data = &toshiba_pdata, + } +}; + +#define FPGA_3D_GPIO_CONFIG_ADDR 0xB5 + +static struct mipi_dsi_phy_ctrl dsi_novatek_cmd_mode_phy_db = { + +/* DSI_BIT_CLK at 500MHz, 2 lane, RGB888 */ + {0x0F, 0x0a, 0x04, 0x00, 0x20}, /* regulator */ + /* timing */ + {0xab, 0x8a, 0x18, 0x00, 0x92, 0x97, 0x1b, 0x8c, + 0x0c, 0x03, 0x04, 0xa0}, + {0x5f, 0x00, 0x00, 0x10}, /* phy ctrl */ + {0xff, 0x00, 0x06, 0x00}, /* strength */ + /* pll control */ + {0x40, 0xf9, 0x30, 0xda, 0x00, 0x40, 0x03, 0x62, + 0x40, 0x07, 0x03, + 0x00, 0x1a, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x01}, +}; + +static struct mipi_dsi_panel_platform_data novatek_pdata = { + .fpga_3d_config_addr = FPGA_3D_GPIO_CONFIG_ADDR, + .fpga_ctrl_mode = FPGA_SPI_INTF, + .phy_ctrl_settings = &dsi_novatek_cmd_mode_phy_db, + .dlane_swap = 0x1, + .enable_wled_bl_ctrl = 0x1, +}; + +static struct platform_device mipi_dsi_novatek_panel_device = { + .name = "mipi_novatek", + .id = 0, + .dev = { + .platform_data = &novatek_pdata, + } +}; + +#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_WRITEBACK_MSM_PANEL +static struct platform_device wfd_panel_device = { + .name = "wfd_panel", + .id = 0, + .dev.platform_data = NULL, +}; + +static struct platform_device wfd_device = { + .name = "msm_wfd", + .id = -1, +}; +#endif + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors dtv_bus_init_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +static struct msm_bus_vectors dtv_bus_def_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2000000000, + .ib = 2000000000, + }, +}; +#else +static struct msm_bus_vectors dtv_bus_def_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 566092800 * 2, + .ib = 707616000 * 2, + }, +}; +#endif + +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 + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +static int hdmi_enable_5v(int on) +{ + static struct regulator *reg_ext_5v; /* HDMI_5V */ + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (!reg_ext_5v) { + reg_ext_5v = regulator_get(&hdmi_msm_device.dev, "hdmi_mvs"); + if (IS_ERR(reg_ext_5v)) { + pr_err("'%s' regulator not found, rc=%ld\n", + "hdmi_mvs", IS_ERR(reg_ext_5v)); + reg_ext_5v = NULL; + return -ENODEV; + } + } + + if (on) { + rc = regulator_enable(reg_ext_5v); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "reg_ext_5v", rc); + return rc; + } + pr_debug("%s(on): success\n", __func__); + } else { + rc = regulator_disable(reg_ext_5v); + if (rc) + pr_warning("'%s' regulator disable failed, rc=%d\n", + "reg_ext_5v", rc); + pr_debug("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; +} + +static int hdmi_core_power(int on, int show) +{ + /* Both HDMI "avdd" and "vcc" are powered by 8038_l23 regulator */ + static struct regulator *reg_8038_l23; + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (!reg_8038_l23) { + reg_8038_l23 = regulator_get(&hdmi_msm_device.dev, "hdmi_avdd"); + if (IS_ERR(reg_8038_l23)) { + pr_err("could not get reg_8038_l23, rc = %ld\n", + PTR_ERR(reg_8038_l23)); + return -ENODEV; + } + rc = regulator_set_voltage(reg_8038_l23, 1800000, 1800000); + if (rc) { + pr_err("set_voltage failed for 8921_l23, rc=%d\n", rc); + return -EINVAL; + } + } + + if (on) { + rc = regulator_set_optimum_mode(reg_8038_l23, 100000); + if (rc < 0) { + pr_err("set_optimum_mode l23 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_enable(reg_8038_l23); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "hdmi_avdd", 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_disable(reg_8038_l23); + if (rc) { + pr_err("disable reg_8038_l23 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_set_optimum_mode(reg_8038_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_8038_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 */ + +void __init msm8930_init_fb(void) +{ + platform_device_register(&msm_fb_device); + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL + platform_device_register(&wfd_panel_device); + platform_device_register(&wfd_device); +#endif + + platform_device_register(&mipi_dsi_novatek_panel_device); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + platform_device_register(&hdmi_msm_device); +#endif + + platform_device_register(&mipi_dsi_toshiba_panel_device); + + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); +#ifdef CONFIG_MSM_BUS_SCALING + msm_fb_register_device("dtv", &dtv_pdata); +#endif +} + +void __init msm8930_allocate_fb_region(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)); +} diff --git a/arch/arm/mach-msm/board-8930-gpiomux.c b/arch/arm/mach-msm/board-8930-gpiomux.c new file mode 100644 index 0000000000000000000000000000000000000000..c7ab260a76535e130a0e7c4fc3dfdab923c6661f --- /dev/null +++ b/arch/arm/mach-msm/board-8930-gpiomux.c @@ -0,0 +1,691 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "devices.h" +#include "board-8930.h" + +/* The SPI configurations apply to GSBI 1*/ +static struct gpiomux_setting spi_active = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting spi_suspended_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting gsbi3_suspended_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting gsbi3_active_cfg = { + .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 gsbi9 = { + .func = GPIOMUX_FUNC_2, + .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 audio_auxpcm[] = { + /* Suspended state */ + { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + /* Active state */ + { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, +}; + +static struct gpiomux_setting audio_mbhc = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting audio_spkr_boost = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +static struct gpiomux_setting gpio_eth_config = { + .pull = GPIOMUX_PULL_NONE, + .drv = GPIOMUX_DRV_8MA, + .func = GPIOMUX_FUNC_GPIO, +}; +#endif + +static struct gpiomux_setting slimbus = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting wcnss_5wire_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting wcnss_5wire_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting atmel_resout_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting atmel_resout_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting atmel_ldo_en_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting atmel_ldo_en_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting atmel_int_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting atmel_int_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; +#ifdef MSM8930_PHASE_2 +static struct gpiomux_setting hsusb_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_LOW, +}; +static struct msm_gpiomux_config msm8930_hsusb_configs[] = { + { + .gpio = 63, /* HSUSB_EXTERNAL_5V_LDO_EN */ + .settings = { + [GPIOMUX_SUSPENDED] = &hsusb_sus_cfg, + }, + }, + { + .gpio = 97, /* HSUSB_5V_EN */ + .settings = { + [GPIOMUX_SUSPENDED] = &hsusb_sus_cfg, + }, + }, +}; +#endif + +static struct gpiomux_setting hap_lvl_shft_suspended_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting hap_lvl_shft_active_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +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_errfatal_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting ap2mdm_kpdpwr_n_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mdp_vsync_suspend_cfg = { + .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_DOWN, +}; + +#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, +}; +#endif + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +static struct msm_gpiomux_config msm8960_ethernet_configs[] = { + { + .gpio = 90, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, + { + .gpio = 89, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, +}; +#endif + +static struct msm_gpiomux_config msm8960_gsbi_configs[] __initdata = { + { + .gpio = 6, /* GSBI1 QUP SPI_DATA_MOSI */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 7, /* GSBI1 QUP SPI_DATA_MISO */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 8, /* GSBI1 QUP SPI_CS_N */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 9, /* GSBI1 QUP SPI_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 16, /* GSBI3 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi3_active_cfg, + }, + }, + { + .gpio = 17, /* GSBI3 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi3_active_cfg, + }, + }, + { + .gpio = 22, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + .gpio = 23, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + .gpio = 44, /* GSBI12 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi12, + }, + }, + { + .gpio = 95, /* GSBI9 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi9, + }, + }, + { + .gpio = 96, /* GSBI12 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi9, + }, + }, + { + .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 msm_gpiomux_config msm8960_audio_mbhc_configs[] __initdata = { + { + .gpio = 37, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_mbhc, + }, + }, +}; + +static struct msm_gpiomux_config msm8960_audio_spkr_configs[] __initdata = { + { + .gpio = 15, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_spkr_boost, + }, + }, +}; + + +static struct msm_gpiomux_config msm8960_audio_auxpcm_configs[] __initdata = { + { + .gpio = 63, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, + { + .gpio = 64, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, + { + .gpio = 65, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, + { + .gpio = 66, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, +}; + +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 msm_gpiomux_config msm8960_atmel_configs[] __initdata = { + { /* TS INTERRUPT */ + .gpio = 11, + .settings = { + [GPIOMUX_ACTIVE] = &atmel_int_act_cfg, + [GPIOMUX_SUSPENDED] = &atmel_int_sus_cfg, + }, + }, + { /* TS LDO ENABLE */ + .gpio = 50, + .settings = { + [GPIOMUX_ACTIVE] = &atmel_ldo_en_act_cfg, + [GPIOMUX_SUSPENDED] = &atmel_ldo_en_sus_cfg, + }, + }, + { /* TS RESOUT */ + .gpio = 52, + .settings = { + [GPIOMUX_ACTIVE] = &atmel_resout_act_cfg, + [GPIOMUX_SUSPENDED] = &atmel_resout_sus_cfg, + }, + }, +}; + +static struct msm_gpiomux_config hap_lvl_shft_config[] __initdata = { + { + .gpio = 47, + .settings = { + [GPIOMUX_SUSPENDED] = &hap_lvl_shft_suspended_config, + [GPIOMUX_ACTIVE] = &hap_lvl_shft_active_config, + }, + }, +}; + +static struct msm_gpiomux_config mdm_configs[] __initdata = { + /* AP2MDM_STATUS */ + { + .gpio = 94, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* MDM2AP_STATUS */ + { + .gpio = 69, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_status_cfg, + } + }, + /* MDM2AP_ERRFATAL */ + { + .gpio = 70, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_errfatal_cfg, + } + }, + /* AP2MDM_ERRFATAL */ + { + .gpio = 95, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* AP2MDM_KPDPWR_N */ + { + .gpio = 81, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_kpdpwr_n_cfg, + } + }, + /* AP2MDM_PMIC_RESET_N */ + { + .gpio = 80, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_kpdpwr_n_cfg, + } + } +}; + +static struct msm_gpiomux_config msm8960_mdp_vsync_configs[] __initdata = { + { + .gpio = 0, + .settings = { + [GPIOMUX_ACTIVE] = &mdp_vsync_active_cfg, + [GPIOMUX_SUSPENDED] = &mdp_vsync_suspend_cfg, + }, + } +}; + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +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, + }, + }, +}; +#endif + +static struct gpiomux_setting haptics_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; +static struct gpiomux_setting haptics_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct msm_gpiomux_config msm8930_haptics_configs[] __initdata = { + { + .gpio = 77, + .settings = { + [GPIOMUX_ACTIVE] = &haptics_active_cfg, + [GPIOMUX_SUSPENDED] = &haptics_suspend_cfg, + }, + }, + { + .gpio = 78, + .settings = { + [GPIOMUX_ACTIVE] = &haptics_active_cfg, + [GPIOMUX_SUSPENDED] = &haptics_suspend_cfg, + }, + }, +}; + +int __init msm8930_init_gpiomux(void) +{ + int rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err(KERN_ERR "msm_gpiomux_init failed %d\n", rc); + return rc; + } + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) + msm_gpiomux_install(msm8960_ethernet_configs, + ARRAY_SIZE(msm8960_ethernet_configs)); +#endif + + msm_gpiomux_install(msm8960_gsbi_configs, + ARRAY_SIZE(msm8960_gsbi_configs)); + + msm_gpiomux_install(msm8960_atmel_configs, + ARRAY_SIZE(msm8960_atmel_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)); + + msm_gpiomux_install(msm8960_audio_mbhc_configs, + ARRAY_SIZE(msm8960_audio_mbhc_configs)); + + msm_gpiomux_install(msm8960_audio_spkr_configs, + ARRAY_SIZE(msm8960_audio_spkr_configs)); + + msm_gpiomux_install(msm8960_audio_auxpcm_configs, + ARRAY_SIZE(msm8960_audio_auxpcm_configs)); + + msm_gpiomux_install(wcnss_5wire_interface, + ARRAY_SIZE(wcnss_5wire_interface)); + + if (machine_is_msm8930_mtp() || machine_is_msm8930_fluid() || + machine_is_msm8930_cdp()) { + msm_gpiomux_install(hap_lvl_shft_config, + ARRAY_SIZE(hap_lvl_shft_config)); +#ifdef MSM8930_PHASE_2 + msm_gpiomux_install(msm8930_hsusb_configs, + ARRAY_SIZE(msm8930_hsusb_configs)); +#endif + } + + if (PLATFORM_IS_CHARM25()) + msm_gpiomux_install(mdm_configs, + ARRAY_SIZE(mdm_configs)); + + if (machine_is_msm8930_cdp() || machine_is_msm8930_mtp() + || machine_is_msm8930_fluid()) + msm_gpiomux_install(msm8930_haptics_configs, + ARRAY_SIZE(msm8930_haptics_configs)); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + msm_gpiomux_install(msm8960_hdmi_configs, + ARRAY_SIZE(msm8960_hdmi_configs)); +#endif + + msm_gpiomux_install(msm8960_mdp_vsync_configs, + ARRAY_SIZE(msm8960_mdp_vsync_configs)); + return 0; +} diff --git a/arch/arm/mach-msm/board-8930-gpu.c b/arch/arm/mach-msm/board-8930-gpu.c new file mode 100644 index 0000000000000000000000000000000000000000..3c3843a4c1fe4aa9ea965fd05aa1dceb2cc44b3c --- /dev/null +++ b/arch/arm/mach-msm/board-8930-gpu.c @@ -0,0 +1,163 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "devices.h" +#include "board-8930.h" + +#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_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2000), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(3200), + }, +}; + +static struct msm_bus_vectors grp3d_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(4264), + }, +}; + +static struct msm_bus_paths grp3d_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp3d_init_vectors), + grp3d_init_vectors, + }, + { + ARRAY_SIZE(grp3d_low_vectors), + grp3d_low_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", +}; +#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 const struct kgsl_iommu_ctx kgsl_3d0_iommu0_ctxs[] = { + { "gfx3d_user", 0 }, + { "gfx3d_priv", 1 }, +}; + +static struct kgsl_device_iommu_data kgsl_3d0_iommu_data[] = { + { + .iommu_ctxs = kgsl_3d0_iommu0_ctxs, + .iommu_ctx_count = ARRAY_SIZE(kgsl_3d0_iommu0_ctxs), + .physstart = 0x07C00000, + .physend = 0x07C00000 + SZ_1M - 1, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwrlevel = { + { + .gpu_freq = 450000000, + .bus_freq = 3, + .io_fraction = 0, + }, + { + .gpu_freq = 320000000, + .bus_freq = 2, + .io_fraction = 33, + }, + { + .gpu_freq = 192000000, + .bus_freq = 1, + .io_fraction = 100, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 4, + .set_grp_async = NULL, + .idle_timeout = HZ/12, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE | KGSL_CLK_MEM_IFACE, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp3d_bus_scale_pdata, +#endif + .iommu_data = kgsl_3d0_iommu_data, + .iommu_count = ARRAY_SIZE(kgsl_3d0_iommu_data), +}; + +static struct platform_device device_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 msm8930_init_gpu(void) +{ + platform_device_register(&device_kgsl_3d0); +} diff --git a/arch/arm/mach-msm/board-8930-pmic.c b/arch/arm/mach-msm/board-8930-pmic.c new file mode 100644 index 0000000000000000000000000000000000000000..cf7a829ce1ad46450224b9b2d1e8302a0352b403 --- /dev/null +++ b/arch/arm/mach-msm/board-8930-pmic.c @@ -0,0 +1,380 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 "board-8930.h" + +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 = PM8038_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 = PM8038_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, PM8038_GPIO_VIN_L11, \ + 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, PM8038_GPIO_VIN_L11, \ + 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, PM8038_GPIO_VIN_L11, \ + 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, PM8038_GPIO_VIN_L11, \ + PM_GPIO_STRENGTH_HIGH, \ + _func, 0, 0) + +#define PM8XXX_GPIO_OUTPUT_VIN(_gpio, _val, _vin) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, _vin, \ + PM_GPIO_STRENGTH_HIGH, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +/* Initial pm8038 GPIO configurations */ +static struct pm8xxx_gpio_init pm8038_gpios[] __initdata = { + /* keys GPIOs */ + PM8XXX_GPIO_INPUT(3, PM_GPIO_PULL_UP_30), + PM8XXX_GPIO_INPUT(8, PM_GPIO_PULL_UP_30), + PM8XXX_GPIO_INPUT(10, PM_GPIO_PULL_UP_30), + PM8XXX_GPIO_INPUT(11, PM_GPIO_PULL_UP_30), + /* haptics gpio */ + PM8XXX_GPIO_OUTPUT_FUNC(7, 0, PM_GPIO_FUNC_1), +}; + +/* Initial pm8038 MPP configurations */ +static struct pm8xxx_mpp_init pm8038_mpps[] __initdata = { + /* External 5V regulator enable; shared by HDMI and USB_OTG switches. */ + PM8XXX_MPP_INIT(3, D_INPUT, PM8038_MPP_DIG_LEVEL_VPH, DIN_TO_INT), +}; + +void __init msm8930_pm8038_gpio_mpp_init(void) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(pm8038_gpios); i++) { + rc = pm8xxx_gpio_config(pm8038_gpios[i].gpio, + &pm8038_gpios[i].config); + if (rc) { + pr_err("%s: pm8xxx_gpio_config: rc=%d\n", __func__, rc); + break; + } + } + + /* Initial MPP configuration. */ + for (i = 0; i < ARRAY_SIZE(pm8038_mpps); i++) { + rc = pm8xxx_mpp_config(pm8038_mpps[i].mpp, + &pm8038_mpps[i].config); + if (rc) { + pr_err("%s: pm8xxx_mpp_config: rc=%d\n", __func__, rc); + break; + } + } +} + +static struct pm8xxx_adc_amux pm8xxx_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}, + {"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_RSV2, + 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}, + {"pa_therm1", ADC_MPP_1_AMUX4, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM}, + {"xo_therm", CHANNEL_MUXOFF, CHAN_PATH_SCALING1, AMUX_RSV0, + ADC_DECIMATION_TYPE2, ADC_SCALE_XOTHERM}, + {"pa_therm0", ADC_MPP_1_AMUX3, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM}, +}; + +static struct pm8xxx_adc_properties pm8xxx_adc_data = { + .adc_vdd_reference = 1800, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, +}; + +static struct pm8xxx_adc_platform_data pm8xxx_adc_pdata = { + .adc_channel = pm8xxx_adc_channels_data, + .adc_num_board_channel = ARRAY_SIZE(pm8xxx_adc_channels_data), + .adc_prop = &pm8xxx_adc_data, + .adc_mpp_base = PM8038_MPP_PM_TO_SYS(1), +}; + +static struct pm8xxx_irq_platform_data pm8xxx_irq_pdata __devinitdata = { + .irq_base = PM8038_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 = PM8038_GPIO_PM_TO_SYS(1), +}; + +static struct pm8xxx_mpp_platform_data pm8xxx_mpp_pdata __devinitdata = { + .mpp_base = PM8038_MPP_PM_TO_SYS(1), +}; + +static struct pm8xxx_rtc_platform_data pm8xxx_rtc_pdata __devinitdata = { + .rtc_write_enable = false, + .rtc_alarm_powerup = false, +}; + +static struct pm8xxx_pwrkey_platform_data pm8xxx_pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 15625, + .wakeup = 1, +}; + +static int pm8921_therm_mitigation[] = { + 1100, + 700, + 600, + 325, +}; + +#define MAX_VOLTAGE_MV 4200 +static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = { + .safety_time = 180, + .update_time = 60000, + .max_voltage = MAX_VOLTAGE_MV, + .min_voltage = 3200, + .resume_voltage_delta = 100, + .term_current = 100, + .cool_temp = 10, + .warm_temp = 40, + .temp_check_period = 1, + .max_bat_chg_current = 1100, + .cool_bat_chg_current = 350, + .warm_bat_chg_current = 350, + .cool_bat_voltage = 4100, + .warm_bat_voltage = 4100, + .thermal_mitigation = pm8921_therm_mitigation, + .thermal_levels = ARRAY_SIZE(pm8921_therm_mitigation), + .led_src_config = LED_SRC_VPH_PWR, +}; + +#define PM8038_WLED_MAX_CURRENT 25 +#define PM8XXX_LED_PWM_PERIOD 1000 +#define PM8XXX_LED_PWM_DUTY_MS 20 +#define PM8038_RGB_LED_MAX_CURRENT 12 + +static struct led_info pm8038_led_info[] = { + [0] = { + .name = "wled", + .default_trigger = "bkl_trigger", + }, + [1] = { + .name = "led:rgb_red", + .default_trigger = "battery-charging", + }, + [2] = { + .name = "led:rgb_green", + }, + [3] = { + .name = "led:rgb_blue", + }, +}; + +static struct led_platform_data pm8038_led_core_pdata = { + .num_leds = ARRAY_SIZE(pm8038_led_info), + .leds = pm8038_led_info, +}; + +static struct wled_config_data wled_cfg = { + .dig_mod_gen_en = true, + .cs_out_en = true, + .ctrl_delay_us = 0, + .op_fdbck = true, + .ovp_val = WLED_OVP_32V, + .boost_curr_lim = WLED_CURR_LIMIT_525mA, + .num_strings = 1, +}; + +static int pm8038_led0_pwm_duty_pcts[56] = { + 1, 4, 8, 12, 16, 20, 24, 28, 32, 36, + 40, 44, 46, 52, 56, 60, 64, 68, 72, 76, + 80, 84, 88, 92, 96, 100, 100, 100, 98, 95, + 92, 88, 84, 82, 78, 74, 70, 66, 62, 58, + 58, 54, 50, 48, 42, 38, 34, 30, 26, 22, + 14, 10, 6, 4, 1 +}; + +static struct pm8xxx_pwm_duty_cycles pm8038_led0_pwm_duty_cycles = { + .duty_pcts = (int *)&pm8038_led0_pwm_duty_pcts, + .num_duty_pcts = ARRAY_SIZE(pm8038_led0_pwm_duty_pcts), + .duty_ms = PM8XXX_LED_PWM_DUTY_MS, + .start_idx = 0, +}; + +static struct pm8xxx_led_config pm8038_led_configs[] = { + [0] = { + .id = PM8XXX_ID_WLED, + .mode = PM8XXX_LED_MODE_MANUAL, + .max_current = PM8038_WLED_MAX_CURRENT, + .default_state = 0, + .wled_cfg = &wled_cfg, + }, + [1] = { + .id = PM8XXX_ID_RGB_LED_RED, + .mode = PM8XXX_LED_MODE_PWM1, + .max_current = PM8038_RGB_LED_MAX_CURRENT, + .pwm_channel = 5, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD, + .pwm_duty_cycles = &pm8038_led0_pwm_duty_cycles, + }, + [2] = { + .id = PM8XXX_ID_RGB_LED_GREEN, + .mode = PM8XXX_LED_MODE_PWM1, + .max_current = PM8038_RGB_LED_MAX_CURRENT, + .pwm_channel = 4, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD, + .pwm_duty_cycles = &pm8038_led0_pwm_duty_cycles, + }, + [3] = { + .id = PM8XXX_ID_RGB_LED_BLUE, + .mode = PM8XXX_LED_MODE_PWM1, + .max_current = PM8038_RGB_LED_MAX_CURRENT, + .pwm_channel = 3, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD, + .pwm_duty_cycles = &pm8038_led0_pwm_duty_cycles, + }, +}; + +static struct pm8xxx_led_platform_data pm8xxx_leds_pdata = { + .led_core = &pm8038_led_core_pdata, + .configs = pm8038_led_configs, + .num_configs = ARRAY_SIZE(pm8038_led_configs), +}; + +static struct pm8xxx_ccadc_platform_data pm8xxx_ccadc_pdata = { + .r_sense = 10, +}; + +static struct pm8xxx_misc_platform_data pm8xxx_misc_pdata = { + .priority = 0, +}; + +static struct pm8xxx_spk_platform_data pm8xxx_spk_pdata = { + .spk_add_enable = false, +}; + +static struct pm8921_bms_platform_data pm8921_bms_pdata __devinitdata = { + .battery_type = BATT_UNKNOWN, + .r_sense = 10, + .i_test = 2500, + .v_failure = 3000, + .calib_delay_ms = 600000, + .max_voltage_uv = MAX_VOLTAGE_MV * 1000, +}; + +static struct pm8038_platform_data pm8038_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, + .misc_pdata = &pm8xxx_misc_pdata, + .regulator_pdatas = msm8930_pm8038_regulator_pdata, + .charger_pdata = &pm8921_chg_pdata, + .bms_pdata = &pm8921_bms_pdata, + .adc_pdata = &pm8xxx_adc_pdata, + .leds_pdata = &pm8xxx_leds_pdata, + .ccadc_pdata = &pm8xxx_ccadc_pdata, + .spk_pdata = &pm8xxx_spk_pdata, +}; + +static struct msm_ssbi_platform_data msm8930_ssbi_pm8038_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8038-core", + .platform_data = &pm8038_platform_data, + }, +}; + +void __init msm8930_init_pmic(void) +{ + pmic_reset_irq = PM8038_IRQ_BASE + PM8038_RESOUT_IRQ; + msm8960_device_ssbi_pmic.dev.platform_data = + &msm8930_ssbi_pm8038_pdata; + pm8038_platform_data.num_regulators + = msm8930_pm8038_regulator_pdata_len; + if (machine_is_apq8064_mtp()) + pm8921_bms_pdata.battery_type = BATT_PALLADIUM; + else if (machine_is_apq8064_liquid()) + pm8921_bms_pdata.battery_type = BATT_DESAY; +} diff --git a/arch/arm/mach-msm/board-8930-regulator.c b/arch/arm/mach-msm/board-8930-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..2f24c950d67c0945aa0a6ee4330a36220f606480 --- /dev/null +++ b/arch/arm/mach-msm/board-8930-regulator.c @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "board-8930.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("8038_l1", NULL), + REGULATOR_SUPPLY("iris_vddrfa", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(L2) = { + REGULATOR_SUPPLY("8038_l2", NULL), + REGULATOR_SUPPLY("iris_vdddig", "wcnss_wlan.0"), + REGULATOR_SUPPLY("dsi_vdda", "mipi_dsi.1"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.0"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.1"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.2"), +}; +VREG_CONSUMERS(L3) = { + REGULATOR_SUPPLY("8038_l3", NULL), + REGULATOR_SUPPLY("HSUSB_3p3", "msm_otg"), +}; +VREG_CONSUMERS(L4) = { + REGULATOR_SUPPLY("8038_l4", NULL), + REGULATOR_SUPPLY("HSUSB_1p8", "msm_otg"), + REGULATOR_SUPPLY("iris_vddxo", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(L5) = { + REGULATOR_SUPPLY("8038_l5", NULL), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.1"), +}; +VREG_CONSUMERS(L6) = { + REGULATOR_SUPPLY("8038_l6", NULL), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.3"), +}; +VREG_CONSUMERS(L7) = { + REGULATOR_SUPPLY("8038_l7", NULL), +}; +VREG_CONSUMERS(L8) = { + REGULATOR_SUPPLY("8038_l8", NULL), + REGULATOR_SUPPLY("dsi_vdc", "mipi_dsi.1"), +}; +VREG_CONSUMERS(L9) = { + REGULATOR_SUPPLY("8038_l9", NULL), + REGULATOR_SUPPLY("vdd_ana", "3-004a"), + REGULATOR_SUPPLY("vdd", "3-0024"), + REGULATOR_SUPPLY("cam_vana", "4-001a"), + REGULATOR_SUPPLY("cam_vana", "4-006c"), + REGULATOR_SUPPLY("cam_vana", "4-0048"), + REGULATOR_SUPPLY("cam_vaf", "4-001a"), + REGULATOR_SUPPLY("cam_vaf", "4-006c"), + REGULATOR_SUPPLY("cam_vaf", "4-0048"), + REGULATOR_SUPPLY("cam_vana", "4-0020"), + REGULATOR_SUPPLY("cam_vaf", "4-0020"), +}; +VREG_CONSUMERS(L10) = { + REGULATOR_SUPPLY("8038_l10", NULL), + REGULATOR_SUPPLY("iris_vddpa", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(L11) = { + REGULATOR_SUPPLY("8038_l11", NULL), + REGULATOR_SUPPLY("vdd_dig", "3-004a"), + REGULATOR_SUPPLY("iris_vddio", "wcnss_wlan.0"), + REGULATOR_SUPPLY("riva_vddpx", "wcnss_wlan.0"), + REGULATOR_SUPPLY("sdc_vccq", "msm_sdcc.1"), + REGULATOR_SUPPLY("VDDIO_CDC", "sitar-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "sitar-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "sitar-slim"), + REGULATOR_SUPPLY("VDDIO_CDC", "sitar1p1-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "sitar1p1-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "sitar1p1-slim"), + REGULATOR_SUPPLY("vddp", "0-0048"), +}; +VREG_CONSUMERS(L12) = { + REGULATOR_SUPPLY("8038_l12", NULL), + REGULATOR_SUPPLY("cam_vdig", "4-001a"), + REGULATOR_SUPPLY("cam_vdig", "4-006c"), + REGULATOR_SUPPLY("cam_vdig", "4-0048"), + REGULATOR_SUPPLY("cam_vdig", "4-0020"), +}; +VREG_CONSUMERS(L14) = { + REGULATOR_SUPPLY("8038_l14", NULL), + REGULATOR_SUPPLY("pa_therm", "pm8xxx-adc"), +}; +VREG_CONSUMERS(L15) = { + REGULATOR_SUPPLY("8038_l15", NULL), +}; +VREG_CONSUMERS(L16) = { + REGULATOR_SUPPLY("8038_l16", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.2"), +}; +VREG_CONSUMERS(L17) = { + REGULATOR_SUPPLY("8038_l17", NULL), +}; +VREG_CONSUMERS(L18) = { + REGULATOR_SUPPLY("8038_l18", NULL), +}; +VREG_CONSUMERS(L19) = { + REGULATOR_SUPPLY("8038_l19", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.1"), +}; +VREG_CONSUMERS(L20) = { + REGULATOR_SUPPLY("8038_l20", NULL), + REGULATOR_SUPPLY("VDDD_CDC_D", "sitar-slim"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "sitar-slim"), + REGULATOR_SUPPLY("VDDD_CDC_D", "sitar1p1-slim"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "sitar1p1-slim"), +}; +VREG_CONSUMERS(L21) = { + REGULATOR_SUPPLY("8038_l21", NULL), +}; +VREG_CONSUMERS(L22) = { + REGULATOR_SUPPLY("8038_l22", NULL), + REGULATOR_SUPPLY("sdc_vddp", "msm_sdcc.3"), +}; +VREG_CONSUMERS(L23) = { + REGULATOR_SUPPLY("8038_l23", NULL), + REGULATOR_SUPPLY("dsi_vddio", "mipi_dsi.1"), + REGULATOR_SUPPLY("hdmi_avdd", "hdmi_msm.0"), + REGULATOR_SUPPLY("hdmi_vcc", "hdmi_msm.0"), + REGULATOR_SUPPLY("pll_vdd", "pil_riva"), + REGULATOR_SUPPLY("pll_vdd", "pil_qdsp6v4.1"), + REGULATOR_SUPPLY("pll_vdd", "pil_qdsp6v4.2"), +}; +VREG_CONSUMERS(L24) = { + REGULATOR_SUPPLY("8038_l24", NULL), + REGULATOR_SUPPLY("riva_vddmx", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(L26) = { + REGULATOR_SUPPLY("8038_l26", NULL), +}; +VREG_CONSUMERS(L27) = { + REGULATOR_SUPPLY("8038_l27", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.0"), +}; +VREG_CONSUMERS(S1) = { + REGULATOR_SUPPLY("8038_s1", NULL), + REGULATOR_SUPPLY("riva_vddcx", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(S2) = { + REGULATOR_SUPPLY("8038_s2", NULL), +}; +VREG_CONSUMERS(S3) = { + REGULATOR_SUPPLY("8038_s3", NULL), +}; +VREG_CONSUMERS(S4) = { + REGULATOR_SUPPLY("8038_s4", NULL), + REGULATOR_SUPPLY("CDC_VDD_CP", "sitar-slim"), + REGULATOR_SUPPLY("CDC_VDD_CP", "sitar1p1-slim"), +}; +VREG_CONSUMERS(S5) = { + REGULATOR_SUPPLY("8038_s5", NULL), + REGULATOR_SUPPLY("krait0", NULL), +}; +VREG_CONSUMERS(S6) = { + REGULATOR_SUPPLY("8038_s6", NULL), + REGULATOR_SUPPLY("krait1", NULL), +}; +VREG_CONSUMERS(LVS1) = { + REGULATOR_SUPPLY("8038_lvs1", NULL), + REGULATOR_SUPPLY("cam_vio", "4-001a"), + REGULATOR_SUPPLY("cam_vio", "4-006c"), + REGULATOR_SUPPLY("cam_vio", "4-0048"), + REGULATOR_SUPPLY("cam_vio", "4-0020"), +}; +VREG_CONSUMERS(LVS2) = { + REGULATOR_SUPPLY("8038_lvs2", NULL), + REGULATOR_SUPPLY("vcc_i2c", "3-004a"), + REGULATOR_SUPPLY("vcc_i2c", "3-0024"), + REGULATOR_SUPPLY("vcc_i2c", "0-0048"), +}; +VREG_CONSUMERS(EXT_5V) = { + REGULATOR_SUPPLY("ext_5v", NULL), + REGULATOR_SUPPLY("hdmi_mvs", "hdmi_msm.0"), +}; +VREG_CONSUMERS(EXT_OTG_SW) = { + REGULATOR_SUPPLY("ext_otg_sw", NULL), + REGULATOR_SUPPLY("vbus_otg", "msm_otg"), +}; +VREG_CONSUMERS(VDD_DIG_CORNER) = { + REGULATOR_SUPPLY("vdd_dig_corner", NULL), + REGULATOR_SUPPLY("hsusb_vdd_dig", "msm_otg"), +}; + +#define PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, _modes, _ops, \ + _apply_uV, _pull_down, _always_on, _supply_regulator, \ + _system_uA, _enable_time, _reg_id) \ + { \ + .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, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pull_down_enable = _pull_down, \ + .system_uA = _system_uA, \ + .enable_time = _enable_time, \ + } + +#define PM8XXX_LDO(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_NLDO1200(_id, _name, _always_on, _pull_down, _min_uV, \ + _max_uV, _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_SMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_FTSMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_VS(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +#define PM8XXX_VS300(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +#define PM8XXX_NCP(_id, _name, _always_on, _min_uV, _max_uV, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, 0, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, 0, 0, \ + _always_on, _supply_regulator, 0, _enable_time, _reg_id) + +/* Pin control initialization */ +#define PM8XXX_PC(_id, _name, _always_on, _pin_fn, _pin_ctrl, \ + _supply_regulator, _reg_id) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .always_on = _always_on, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id##_PC), \ + .consumer_supplies = vreg_consumers_##_id##_PC, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pin_fn = PM8XXX_VREG_PIN_FN_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +#define RPM_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, _default_uV, \ + _peak_uA, _avg_uA, _pull_down, _pin_ctrl, _freq, _pin_fn, \ + _force_mode, _sleep_set_force_mode, _power_mode, _state, \ + _sleep_selectable, _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 = _min_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 = RPM_VREG_ID_PM8038_##_id, \ + .default_uV = _default_uV, \ + .peak_uA = _peak_uA, \ + .avg_uA = _avg_uA, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = _pin_ctrl, \ + .freq = RPM_VREG_FREQ_##_freq, \ + .pin_fn = _pin_fn, \ + .force_mode = _force_mode, \ + .sleep_set_force_mode = _sleep_set_force_mode, \ + .power_mode = _power_mode, \ + .state = _state, \ + .sleep_selectable = _sleep_selectable, \ + .system_uA = _system_uA, \ + } + +#define RPM_LDO(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _init_peak_uA) \ + RPM_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, _max_uV, _init_peak_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, RPM_VREG_POWER_MODE_8930_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, _system_uA) + +#define RPM_SMPS(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _freq, _force_mode, \ + _sleep_set_force_mode) \ + RPM_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, _min_uV, _system_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_##_force_mode, \ + RPM_VREG_FORCE_MODE_8930_##_sleep_set_force_mode, \ + RPM_VREG_POWER_MODE_8930_PWM, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on, _supply_regulator, _system_uA) + +#define RPM_VS(_id, _always_on, _pd, _sleep_selectable, _supply_regulator) \ + RPM_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, 0, 1000, 1000, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, RPM_VREG_POWER_MODE_8930_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +#define RPM_NCP(_id, _always_on, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _freq) \ + RPM_INIT(_id, _min_uV, _max_uV, 0, REGULATOR_CHANGE_VOLTAGE \ + | REGULATOR_CHANGE_STATUS, 0, _max_uV, 1000, 1000, 0, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, RPM_VREG_POWER_MODE_8930_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +#define RPM_CORNER(_id, _always_on, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator) \ + RPM_INIT(_id, _min_uV, _max_uV, 0, REGULATOR_CHANGE_VOLTAGE \ + | REGULATOR_CHANGE_STATUS, 0, _max_uV, 0, 0, 0, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, \ + RPM_VREG_FORCE_MODE_8930_NONE, RPM_VREG_POWER_MODE_8930_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +/* Pin control initialization */ +#define RPM_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 = RPM_VREG_ID_PM8038_##_id##_PC, \ + .pin_fn = RPM_VREG_PIN_FN_8930_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +#define GPIO_VREG(_id, _reg_name, _gpio_label, _gpio, _supply_regulator) \ + [MSM8930_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, \ + .supply_regulator = _supply_regulator, \ + }, \ + .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 +msm8930_gpio_regulator_pdata[] __devinitdata = { + /* ID vreg_name gpio_label gpio supply */ + GPIO_VREG(EXT_5V, "ext_5v", "ext_5v_en", 63, NULL), + GPIO_VREG(EXT_OTG_SW, "ext_otg_sw", "ext_otg_sw_en", 97, "ext_5v"), +}; + +/* SAW regulator constraints */ +struct regulator_init_data msm8930_saw_regulator_core0_pdata = + /* ID vreg_name min_uV max_uV */ + SAW_VREG_INIT(S5, "8038_s5", 850000, 1300000); +struct regulator_init_data msm8930_saw_regulator_core1_pdata = + SAW_VREG_INIT(S6, "8038_s6", 850000, 1300000); + +/* PM8038 regulator constraints */ +struct pm8xxx_regulator_platform_data +msm8930_pm8038_regulator_pdata[] __devinitdata = { + /* + * ID name always_on pd min_uV max_uV en_t supply + * system_uA reg_ID + */ + PM8XXX_NLDO1200(L16, "8038_l16", 0, 1, 375000, 1050000, 200, "8038_s3", + 0, 0), + PM8XXX_NLDO1200(L19, "8038_l19", 0, 1, 375000, 1050000, 200, "8038_s3", + 0, 1), + PM8XXX_NLDO1200(L27, "8038_l27", 0, 1, 375000, 1050000, 200, "8038_s3", + 0, 2), +}; + +static struct rpm_regulator_init_data +msm8930_rpm_regulator_init_data[] __devinitdata = { + /* ID a_on pd ss min_uV max_uV supply sys_uA freq fm ss_fm */ + RPM_SMPS(S1, 0, 1, 1, 500000, 1150000, NULL, 100000, 4p80, AUTO, LPM), + RPM_SMPS(S2, 1, 1, 1, 1400000, 1400000, NULL, 100000, 1p60, AUTO, LPM), + RPM_SMPS(S3, 0, 1, 1, 1150000, 1150000, NULL, 100000, 3p20, AUTO, LPM), + RPM_SMPS(S4, 1, 1, 1, 1950000, 2200000, NULL, 100000, 1p60, AUTO, LPM), + + /* ID a_on pd ss min_uV max_uV supply sys_uA init_ip */ + RPM_LDO(L1, 0, 1, 0, 1300000, 1300000, "8038_s2", 0, 0), + RPM_LDO(L2, 0, 1, 0, 1200000, 1200000, "8038_s2", 0, 0), + RPM_LDO(L3, 0, 1, 0, 3075000, 3075000, NULL, 0, 0), + RPM_LDO(L4, 1, 1, 0, 1800000, 1800000, NULL, 10000, 10000), + RPM_LDO(L5, 0, 1, 0, 2950000, 2950000, NULL, 0, 0), + RPM_LDO(L6, 0, 1, 0, 2950000, 2950000, NULL, 0, 0), + RPM_LDO(L7, 0, 1, 0, 2050000, 2050000, "8038_s4", 0, 0), + RPM_LDO(L8, 0, 1, 0, 2800000, 2800000, NULL, 0, 0), + RPM_LDO(L9, 0, 1, 0, 2850000, 2850000, NULL, 0, 0), + RPM_LDO(L10, 0, 1, 0, 2900000, 2900000, NULL, 0, 0), + RPM_LDO(L11, 1, 1, 0, 1800000, 1800000, "8038_s4", 10000, 10000), + RPM_LDO(L12, 0, 1, 0, 1200000, 1200000, "8038_s2", 0, 0), + RPM_LDO(L14, 0, 1, 0, 1800000, 1800000, NULL, 0, 0), + RPM_LDO(L15, 0, 1, 0, 1800000, 2950000, NULL, 0, 0), + RPM_LDO(L17, 0, 1, 0, 1800000, 2950000, NULL, 0, 0), + RPM_LDO(L18, 0, 1, 0, 1800000, 1800000, NULL, 0, 0), + RPM_LDO(L20, 1, 1, 0, 1200000, 1200000, "8038_s2", 10000, 10000), + RPM_LDO(L21, 0, 1, 0, 1900000, 1900000, "8038_s4", 0, 0), + RPM_LDO(L22, 1, 1, 0, 1850000, 2950000, NULL, 10000, 10000), + RPM_LDO(L23, 1, 1, 1, 1800000, 1800000, "8038_s4", 0, 0), + RPM_LDO(L24, 0, 1, 1, 500000, 1150000, "8038_s2", 10000, 10000), + RPM_LDO(L26, 1, 1, 0, 1050000, 1050000, "8038_s2", 10000, 10000), + + /* ID a_on pd ss supply */ + RPM_VS(LVS1, 0, 1, 0, "8038_l11"), + RPM_VS(LVS2, 0, 1, 0, "8038_l11"), + + /* ID a_on ss min_corner max_corner supply */ + RPM_CORNER(VDD_DIG_CORNER, 0, 1, RPM_VREG_CORNER_NONE, + RPM_VREG_CORNER_HIGH, NULL), +}; + +int msm8930_pm8038_regulator_pdata_len __devinitdata = + ARRAY_SIZE(msm8930_pm8038_regulator_pdata); + +struct rpm_regulator_platform_data msm8930_rpm_regulator_pdata __devinitdata = { + .init_data = msm8930_rpm_regulator_init_data, + .num_regulators = ARRAY_SIZE(msm8930_rpm_regulator_init_data), + .version = RPM_VREG_VERSION_8930, + .vreg_id_vdd_mem = RPM_VREG_ID_PM8038_L24, + .vreg_id_vdd_dig = RPM_VREG_ID_PM8038_VDD_DIG_CORNER, +}; diff --git a/arch/arm/mach-msm/board-8930-storage.c b/arch/arm/mach-msm/board-8930-storage.c new file mode 100644 index 0000000000000000000000000000000000000000..52a11bcc42a6d536abd279613f25e13105d5b8a4 --- /dev/null +++ b/arch/arm/mach-msm/board-8930-storage.c @@ -0,0 +1,296 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "board-8930.h" +#include "board-storage-common-a.h" + +/* MSM8960 has 5 SDCC controllers */ +enum sdcc_controllers { + SDCC1, + SDCC2, + SDCC3, + SDCC4, + SDCC5, + MAX_SDCC_CONTROLLER +}; + +/* All SDCC controllers require VDD/VCC voltage */ +static struct msm_mmc_reg_data mmc_vdd_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : eMMC card connected */ + [SDCC1] = { + .name = "sdc_vdd", + .high_vol_level = 2950000, + .low_vol_level = 2950000, + .always_on = 1, + .lpm_sup = 1, + .lpm_uA = 9000, + .hpm_uA = 200000, /* 200mA */ + }, + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .name = "sdc_vdd", + .high_vol_level = 2950000, + .low_vol_level = 2950000, + .hpm_uA = 800000, /* 800mA */ + } +}; + +/* 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", + .always_on = 1, + .high_vol_level = 1800000, + .low_vol_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", + .high_vol_level = 2950000, + .low_vol_level = 1850000, + .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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + {TLMM_PULL_SDC1_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC1_DATA, GPIO_CFG_PULL_UP} +}; + +/* 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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + /* + * SDC3 CMD line should be PULLed UP otherwise fluid platform will + * see transitions (1 -> 0 and 0 -> 1) on card detection line, + * which would result in false card detection interrupts. + */ + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_UP}, + /* + * Keeping DATA lines status to PULL UP will make sure that + * there is no current leak during sleep if external pull up + * is connected to DATA lines. + */ + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_UP} +}; + +static 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) + }, +}; + +static 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) + }, +}; + +static 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] + }, +}; + +static 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], + }, +}; + +#define MSM_MPM_PIN_SDC1_DAT1 17 +#define MSM_MPM_PIN_SDC3_DAT1 21 + +static unsigned int sdc1_sup_clk_rates[] = { + 400000, 24000000, 48000000, +}; + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static unsigned int sdc3_sup_clk_rates[] = { + 400000, 24000000, 48000000, 96000000, 192000000, +}; +#endif + +#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), + .pclk_src_dfab = 1, + .nonremovable = 1, + .vreg_data = &mmc_slot_vreg_data[SDCC1], + .pin_data = &mmc_slot_pin_data[SDCC1], + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC1_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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), + .pclk_src_dfab = 1, +#ifdef CONFIG_MMC_MSM_SDC3_WP_SUPPORT +/*TODO: Insert right replacement for PM8038 */ +#ifndef MSM8930_PHASE_2 + .wpswitch_gpio = PM8921_GPIO_PM_TO_SYS(16), +#else + .wpswitch_gpio = 66, + .wpswitch_polarity = 1, +#endif +#endif + .vreg_data = &mmc_slot_vreg_data[SDCC3], + .pin_data = &mmc_slot_pin_data[SDCC3], +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION +/*TODO: Insert right replacement for PM8038 */ +#ifndef MSM8930_PHASE_2 + .status_gpio = PM8921_GPIO_PM_TO_SYS(26), + .status_irq = PM8921_GPIO_IRQ(PM8921_IRQ_BASE, 26), +#else + .status_gpio = 94, + .status_irq = MSM_GPIO_TO_INT(94), +#endif + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + .is_status_gpio_active_low = true, +#endif + .xpc_cap = 1, + .uhs_caps = (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_DDR50 | + MMC_CAP_UHS_SDR104 | MMC_CAP_MAX_CURRENT_800), + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC3_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#endif + +void __init msm8930_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 */ + if (!machine_is_msm8930_cdp()) { + msm8960_sdc3_data.wpswitch_gpio = 0; + msm8960_sdc3_data.wpswitch_polarity = 0; + } + msm_add_sdcc(3, &msm8960_sdc3_data); +#endif +} diff --git a/arch/arm/mach-msm/board-8930.c b/arch/arm/mach-msm/board-8930.c new file mode 100644 index 0000000000000000000000000000000000000000..5781e3ed2a12f3ab56143ed9da0a7de2a9130138 --- /dev/null +++ b/arch/arm/mach-msm/board-8930.c @@ -0,0 +1,2439 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ANDROID_PMEM +#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 +#ifdef CONFIG_USB_MSM_OTG_72K +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "timer.h" +#include "devices.h" +#include "devices-msm8x60.h" +#include "spm.h" +#include "pm.h" +#include +#include "rpm_resources.h" +#include +#include "acpuclock.h" +#include "smd_private.h" +#include "pm-boot.h" +#include "msm_watchdog.h" +#include "board-8930.h" + +static struct platform_device msm_fm_platform_init = { + .name = "iris_fm", + .id = -1, +}; + +#define KS8851_RST_GPIO 89 +#define KS8851_IRQ_GPIO 90 +#define HAP_SHIFT_LVL_OE_GPIO 47 + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + +struct sx150x_platform_data msm8930_sx150x_data[] = { + [SX150X_CAM] = { + .gpio_base = GPIO_CAM_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0, + .io_pulldn_ena = 0xc0, + .io_open_drain_ena = 0x0, + .irq_summary = -1, + }, +}; + +#endif + +#define MSM_PMEM_ADSP_SIZE 0x7800000 +#define MSM_PMEM_AUDIO_SIZE 0x4CF000 +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +#define MSM_PMEM_SIZE 0x4000000 /* 64 Mbytes */ +#else +#define MSM_PMEM_SIZE 0x2800000 /* 40 Mbytes */ +#endif +#define MSM_LIQUID_PMEM_SIZE 0x4000000 /* 64 Mbytes */ + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x65000 +#ifdef CONFIG_MSM_IOMMU +#define MSM_ION_MM_SIZE 0x3800000 /* Need to be multiple of 64K */ +#define MSM_ION_SF_SIZE 0x0 +#define MSM_ION_QSECOM_SIZE 0x780000 /* (7.5MB) */ +#define MSM_ION_HEAP_NUM 7 +#else +#define MSM_ION_SF_SIZE MSM_PMEM_SIZE +#define MSM_ION_MM_SIZE MSM_PMEM_ADSP_SIZE +#define MSM_ION_QSECOM_SIZE 0x600000 /* (6MB) */ +#define MSM_ION_HEAP_NUM 8 +#endif +#define MSM_ION_MM_FW_SIZE 0x200000 /* (2MB) */ +#define MSM_ION_MFC_SIZE SZ_8K +#define MSM_ION_AUDIO_SIZE MSM_PMEM_AUDIO_SIZE + +#define MSM_LIQUID_ION_MM_SIZE (MSM_ION_MM_SIZE + 0x600000) +#define MSM_LIQUID_ION_SF_SIZE MSM_LIQUID_PMEM_SIZE +#define MSM_HDMI_PRIM_ION_SF_SIZE MSM_HDMI_PRIM_PMEM_SIZE + +#define MSM8930_FIXED_AREA_START 0xa0000000 +#define MAX_FIXED_AREA_SIZE 0x10000000 +#define MSM_MM_FW_SIZE 0x200000 +#define MSM8930_FW_START (MSM8930_FIXED_AREA_START - MSM_MM_FW_SIZE) + +#else +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x110C000 +#define MSM_ION_HEAP_NUM 1 +#endif + +#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 +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +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 msm8930_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 msm8930_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 msm8930_android_pmem_audio_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_audio_pdata }, +}; +#endif /* CONFIG_MSM_MULTIMEDIA_USE_ION */ +#endif /* CONFIG_ANDROID_PMEM */ + +struct fmem_platform_data msm8930_fmem_pdata = { +}; + +#define DSP_RAM_BASE_8960 0x8da00000 +#define DSP_RAM_SIZE_8960 0x1800000 +static int dspcrashd_pdata_8960 = 0xDEADDEAD; + +static struct resource resources_dspcrashd_8960[] = { + { + .name = "msm_dspcrashd", + .start = DSP_RAM_BASE_8960, + .end = DSP_RAM_BASE_8960 + DSP_RAM_SIZE_8960, + .flags = IORESOURCE_DMA, + }, +}; + +static struct platform_device msm_device_dspcrashd_8960 = { + .name = "msm_dspcrashd", + .num_resources = ARRAY_SIZE(resources_dspcrashd_8960), + .resource = resources_dspcrashd_8960, + .dev = { .platform_data = &dspcrashd_pdata_8960 }, +}; + +static struct memtype_reserve msm8930_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + + +static void __init reserve_rtb_memory(void) +{ +#if defined(CONFIG_MSM_RTB) + msm8930_reserve_table[MEMTYPE_EBI1].size += msm8930_rtb_pdata.size; +#endif +} + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_pdata.size = pmem_size; + android_pmem_audio_pdata.size = MSM_PMEM_AUDIO_SIZE; +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +} + +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm8930_reserve_table[p->memory_type].size += p->size; +} +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ + msm8930_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif /*CONFIG_ANDROID_PMEM*/ +} + +static int msm8930_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +#define FMEM_ENABLED 0 +#ifdef CONFIG_ION_MSM +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +static struct ion_cp_heap_pdata cp_mm_msm8930_ion_pdata = { + .permission_type = IPT_TYPE_MM_CARVEOUT, + .align = PAGE_SIZE, + .reusable = FMEM_ENABLED, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_MIDDLE, +}; + +static struct ion_cp_heap_pdata cp_mfc_msm8930_ion_pdata = { + .permission_type = IPT_TYPE_MFC_SHAREDMEM, + .align = PAGE_SIZE, + .reusable = 0, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_HIGH, +}; + +static struct ion_co_heap_pdata co_msm8930_ion_pdata = { + .adjacent_mem_id = INVALID_HEAP_ID, + .align = PAGE_SIZE, + .mem_is_fmem = 0, +}; + +static struct ion_co_heap_pdata fw_co_msm8930_ion_pdata = { + .adjacent_mem_id = ION_CP_MM_HEAP_ID, + .align = SZ_128K, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_LOW, +}; +#endif + +/** + * These heaps are listed in the order they will be allocated. Due to + * video hardware restrictions and content protection the FW heap has to + * be allocated adjacent (below) the MM heap and the MFC heap has to be + * allocated after the MM heap to ensure MFC heap is not more than 256MB + * away from the base address of the FW heap. + * However, the order of FW heap and MM heap doesn't matter since these + * two heaps are taken care of by separate code to ensure they are adjacent + * to each other. + * Don't swap the order unless you know what you are doing! + */ +static struct ion_platform_data msm8930_ion_pdata = { + .nr = MSM_ION_HEAP_NUM, + .heaps = { + { + .id = ION_SYSTEM_HEAP_ID, + .type = ION_HEAP_TYPE_SYSTEM, + .name = ION_VMALLOC_HEAP_NAME, + }, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + { + .id = ION_CP_MM_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MM_HEAP_NAME, + .size = MSM_ION_MM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mm_msm8930_ion_pdata, + }, + { + .id = ION_MM_FIRMWARE_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_MM_FIRMWARE_HEAP_NAME, + .size = MSM_ION_MM_FW_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &fw_co_msm8930_ion_pdata, + }, + { + .id = ION_CP_MFC_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MFC_HEAP_NAME, + .size = MSM_ION_MFC_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mfc_msm8930_ion_pdata, + }, +#ifndef CONFIG_MSM_IOMMU + { + .id = ION_SF_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_SF_HEAP_NAME, + .size = MSM_ION_SF_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_msm8930_ion_pdata, + }, +#endif + { + .id = ION_IOMMU_HEAP_ID, + .type = ION_HEAP_TYPE_IOMMU, + .name = ION_IOMMU_HEAP_NAME, + }, + { + .id = ION_QSECOM_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_QSECOM_HEAP_NAME, + .size = MSM_ION_QSECOM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_msm8930_ion_pdata, + }, + { + .id = ION_AUDIO_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_AUDIO_HEAP_NAME, + .size = MSM_ION_AUDIO_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_msm8930_ion_pdata, + }, +#endif + } +}; + +static struct platform_device msm8930_ion_dev = { + .name = "ion-msm", + .id = 1, + .dev = { .platform_data = &msm8930_ion_pdata }, +}; +#endif + +struct platform_device msm8930_fmem_device = { + .name = "fmem", + .id = 1, + .dev = { .platform_data = &msm8930_fmem_pdata }, +}; + +static void __init reserve_mem_for_ion(enum ion_memory_types mem_type, + unsigned long size) +{ + msm8930_reserve_table[mem_type].size += size; +} + +static void __init msm8930_reserve_fixed_area(unsigned long fixed_area_size) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + int ret; + + if (fixed_area_size > MAX_FIXED_AREA_SIZE) + panic("fixed area size is larger than %dM\n", + MAX_FIXED_AREA_SIZE >> 20); + + reserve_info->fixed_area_size = fixed_area_size; + reserve_info->fixed_area_start = MSM8930_FW_START; + + ret = memblock_remove(reserve_info->fixed_area_start, + reserve_info->fixed_area_size); + BUG_ON(ret); +#endif +} + +/** + * Reserve memory for ION and calculate amount of reusable memory for fmem. + * We only reserve memory for heaps that are not reusable. However, we only + * support one reusable heap at the moment so we ignore the reusable flag for + * other than the first heap with reusable flag set. Also handle special case + * for video heaps (MM,FW, and MFC). Video requires heaps MM and MFC to be + * at a higher address than FW in addition to not more than 256MB away from the + * base address of the firmware. This means that if MM is reusable the other + * two heaps must be allocated in the same region as FW. This is handled by the + * mem_is_fmem flag in the platform data. In addition the MM heap must be + * adjacent to the FW heap for content protection purposes. + */ +static void __init reserve_ion_memory(void) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + unsigned int i; + unsigned int reusable_count = 0; + unsigned int fixed_size = 0; + unsigned int fixed_low_size, fixed_middle_size, fixed_high_size; + unsigned long fixed_low_start, fixed_middle_start, fixed_high_start; + + msm8930_fmem_pdata.size = 0; + msm8930_fmem_pdata.reserved_size_low = 0; + msm8930_fmem_pdata.reserved_size_high = 0; + msm8930_fmem_pdata.align = PAGE_SIZE; + fixed_low_size = 0; + fixed_middle_size = 0; + fixed_high_size = 0; + + /* We only support 1 reusable heap. Check if more than one heap + * is specified as reusable and set as non-reusable if found. + */ + for (i = 0; i < msm8930_ion_pdata.nr; ++i) { + const struct ion_platform_heap *heap = + &(msm8930_ion_pdata.heaps[i]); + + if (heap->type == ION_HEAP_TYPE_CP && heap->extra_data) { + struct ion_cp_heap_pdata *data = heap->extra_data; + + reusable_count += (data->reusable) ? 1 : 0; + + if (data->reusable && reusable_count > 1) { + pr_err("%s: Too many heaps specified as " + "reusable. Heap %s was not configured " + "as reusable.\n", __func__, heap->name); + data->reusable = 0; + } + } + } + + for (i = 0; i < msm8930_ion_pdata.nr; ++i) { + const struct ion_platform_heap *heap = + &(msm8930_ion_pdata.heaps[i]); + + if (heap->extra_data) { + int fixed_position = NOT_FIXED; + int mem_is_fmem = 0; + + switch (heap->type) { + case ION_HEAP_TYPE_CP: + mem_is_fmem = ((struct ion_cp_heap_pdata *) + heap->extra_data)->mem_is_fmem; + fixed_position = ((struct ion_cp_heap_pdata *) + heap->extra_data)->fixed_position; + break; + case ION_HEAP_TYPE_CARVEOUT: + mem_is_fmem = ((struct ion_co_heap_pdata *) + heap->extra_data)->mem_is_fmem; + fixed_position = ((struct ion_co_heap_pdata *) + heap->extra_data)->fixed_position; + break; + default: + break; + } + + if (fixed_position != NOT_FIXED) + fixed_size += heap->size; + else + reserve_mem_for_ion(MEMTYPE_EBI1, heap->size); + + if (fixed_position == FIXED_LOW) + fixed_low_size += heap->size; + else if (fixed_position == FIXED_MIDDLE) + fixed_middle_size += heap->size; + else if (fixed_position == FIXED_HIGH) + fixed_high_size += heap->size; + + if (mem_is_fmem) + msm8930_fmem_pdata.size += heap->size; + } + } + + if (!fixed_size) + return; + + if (msm8930_fmem_pdata.size) { + msm8930_fmem_pdata.reserved_size_low = fixed_low_size; + msm8930_fmem_pdata.reserved_size_high = fixed_high_size; + } + + /* Since the fixed area may be carved out of lowmem, + * make sure the length is a multiple of 1M. + */ + fixed_size = (fixed_size + MSM_MM_FW_SIZE + SECTION_SIZE - 1) + & SECTION_MASK; + msm8930_reserve_fixed_area(fixed_size); + + fixed_low_start = MSM8930_FIXED_AREA_START; + fixed_middle_start = fixed_low_start + fixed_low_size; + fixed_high_start = fixed_middle_start + fixed_middle_size; + + for (i = 0; i < msm8930_ion_pdata.nr; ++i) { + struct ion_platform_heap *heap = &(msm8930_ion_pdata.heaps[i]); + + if (heap->extra_data) { + int fixed_position = NOT_FIXED; + + switch (heap->type) { + case ION_HEAP_TYPE_CP: + fixed_position = ((struct ion_cp_heap_pdata *) + heap->extra_data)->fixed_position; + break; + case ION_HEAP_TYPE_CARVEOUT: + fixed_position = ((struct ion_co_heap_pdata *) + heap->extra_data)->fixed_position; + break; + default: + break; + } + + switch (fixed_position) { + case FIXED_LOW: + heap->base = fixed_low_start; + break; + case FIXED_MIDDLE: + heap->base = fixed_middle_start; + break; + case FIXED_HIGH: + heap->base = fixed_high_start; + break; + default: + break; + } + } + } +#endif +} + +static void __init reserve_mdp_memory(void) +{ + msm8930_mdp_writeback(msm8930_reserve_table); +} + +static void __init msm8930_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); + reserve_ion_memory(); + reserve_mdp_memory(); + reserve_rtb_memory(); +} + +static struct reserve_info msm8930_reserve_info __initdata = { + .memtype_reserve_table = msm8930_reserve_table, + .calculate_reserve_sizes = msm8930_calculate_reserve_sizes, + .reserve_fixed_area = msm8930_reserve_fixed_area, + .paddr_to_memtype = msm8930_paddr_to_memtype, +}; + +static int msm8930_memory_bank_size(void) +{ + return 1<<29; +} + +static void __init locate_unstable_memory(void) +{ + struct membank *mb = &meminfo.bank[meminfo.nr_banks - 1]; + unsigned long bank_size; + unsigned long low, high; + + bank_size = msm8930_memory_bank_size(); + low = meminfo.bank[0].start; + high = mb->start + mb->size; + + /* Check if 32 bit overflow occured */ + if (high < mb->start) + high -= PAGE_SIZE; + + if (high < MAX_FIXED_AREA_SIZE + MSM8930_FIXED_AREA_START) + panic("fixed area extends beyond end of memory\n"); + + low &= ~(bank_size - 1); + + if (high - low <= bank_size) + goto no_dmm; + + msm8930_reserve_info.bank_size = bank_size; +#ifdef CONFIG_ENABLE_DMM + msm8930_reserve_info.low_unstable_address = mb->start - + MIN_MEMORY_BLOCK_SIZE + mb->size; + msm8930_reserve_info.max_unstable_size = MIN_MEMORY_BLOCK_SIZE; + pr_info("low unstable address %lx max size %lx bank size %lx\n", + msm8930_reserve_info.low_unstable_address, + msm8930_reserve_info.max_unstable_size, + msm8930_reserve_info.bank_size); + return; +#endif +no_dmm: + msm8930_reserve_info.low_unstable_address = high; + msm8930_reserve_info.max_unstable_size = 0; +} + +static void __init place_movable_zone(void) +{ +#ifdef CONFIG_ENABLE_DMM + movable_reserved_start = msm8930_reserve_info.low_unstable_address; + movable_reserved_size = msm8930_reserve_info.max_unstable_size; + pr_info("movable zone start %lx size %lx\n", + movable_reserved_start, movable_reserved_size); +#endif +} + +static void __init msm8930_early_memory(void) +{ + reserve_info = &msm8930_reserve_info; + locate_unstable_memory(); + place_movable_zone(); +} + +static void __init msm8930_reserve(void) +{ + msm_reserve(); + if (msm8930_fmem_pdata.size) { +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + if (reserve_info->fixed_area_size) { + msm8930_fmem_pdata.phys = + reserve_info->fixed_area_start + MSM_MM_FW_SIZE; + pr_info("mm fw at %lx (fixed) size %x\n", + reserve_info->fixed_area_start, MSM_MM_FW_SIZE); + pr_info("fmem start %lx (fixed) size %lx\n", + msm8930_fmem_pdata.phys, msm8930_fmem_pdata.size); + } +#endif + } +} + +static int msm8930_change_memory_power(u64 start, u64 size, + int change_type) +{ + return soc_change_memory_power(start, size, change_type); +} + +static void __init msm8930_allocate_memory_regions(void) +{ + msm8930_allocate_fb_region(); +} + +#ifdef CONFIG_WCD9304_CODEC + +#define SITAR_INTERRUPT_BASE (NR_MSM_IRQS + NR_GPIO_IRQS + NR_PM8921_IRQS) + +/* Micbias setting is based on 8660 CDP/MTP/FLUID requirement + * 4 micbiases are used to power various analog and digital + * microphones operating at 1800 mV. Technically, all micbiases + * can source from single cfilter since all microphones operate + * at the same voltage level. The arrangement below is to make + * sure all cfilters are exercised. LDO_H regulator ouput level + * does not need to be as high as 2.85V. It is choosen for + * microphone sensitivity purpose. + */ +static struct wcd9xxx_pdata sitar_platform_data = { + .slimbus_slave_device = { + .name = "sitar-slave", + .e_addr = {0, 0, 0x00, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(62), + .irq_base = SITAR_INTERRUPT_BASE, + .num_irqs = NR_WCD9XXX_IRQS, + .reset_gpio = 42, + .micbias = { + .ldoh_v = SITAR_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 1800, + .bias1_cfilt_sel = SITAR_CFILT1_SEL, + .bias2_cfilt_sel = SITAR_CFILT2_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1950000, + .max_uV = 2200000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1200000, + .max_uV = 1200000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1200000, + .max_uV = 1200000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device msm_slim_sitar = { + .name = "sitar-slim", + .e_addr = {0, 1, 0x00, 0, 0x17, 2}, + .dev = { + .platform_data = &sitar_platform_data, + }, +}; + +static struct wcd9xxx_pdata sitar1p1_platform_data = { + .slimbus_slave_device = { + .name = "sitar-slave", + .e_addr = {0, 0, 0x70, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(62), + .irq_base = SITAR_INTERRUPT_BASE, + .num_irqs = NR_WCD9XXX_IRQS, + .reset_gpio = 42, + .micbias = { + .ldoh_v = SITAR_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 1800, + .bias1_cfilt_sel = SITAR_CFILT1_SEL, + .bias2_cfilt_sel = SITAR_CFILT2_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1950000, + .max_uV = 2200000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1200000, + .max_uV = 1200000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1200000, + .max_uV = 1200000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device msm_slim_sitar1p1 = { + .name = "sitar1p1-slim", + .e_addr = {0, 1, 0x70, 0, 0x17, 2}, + .dev = { + .platform_data = &sitar1p1_platform_data, + }, +}; +#endif + + +static struct slim_boardinfo msm_slim_devices[] = { +#ifdef CONFIG_WCD9304_CODEC + { + .bus_num = 1, + .slim_slave = &msm_slim_sitar, + }, + { + .bus_num = 1, + .slim_slave = &msm_slim_sitar1p1, + }, +#endif + /* add more slimbus slaves as needed */ +}; + +#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, + }, + { + .start = 84, + .end = 88, + .name = "wcnss_gpios_5wire", + .flags = IORESOURCE_IO, + }, +}; + +static struct qcom_wcnss_opts qcom_wcnss_pdata = { + .has_48mhz_xo = 1, +}; + +static struct platform_device msm_device_wcnss_wlan = { + .name = "wcnss_wlan", + .id = 0, + .num_resources = ARRAY_SIZE(resources_wcnss_wlan), + .resource = resources_wcnss_wlan, + .dev = {.platform_data = &qcom_wcnss_pdata}, +}; + +#ifdef CONFIG_QSEECOM +/* qseecom bus scaling */ +static struct msm_bus_vectors qseecom_clks_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 0, + .ab = 0, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = 0, + .ab = 0, + }, +}; + +static struct msm_bus_vectors qseecom_enable_dfab_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = (492 * 8) * 1000000UL, + .ab = (492 * 8) * 100000UL, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = 0, + .ab = 0, + }, +}; + +static struct msm_bus_vectors qseecom_enable_sfpb_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 0, + .ab = 0, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = (64 * 8) * 1000000UL, + .ab = (64 * 8) * 100000UL, + }, +}; + +static struct msm_bus_paths qseecom_hw_bus_scale_usecases[] = { + { + ARRAY_SIZE(qseecom_clks_init_vectors), + qseecom_clks_init_vectors, + }, + { + ARRAY_SIZE(qseecom_enable_dfab_vectors), + qseecom_enable_sfpb_vectors, + }, + { + ARRAY_SIZE(qseecom_enable_sfpb_vectors), + qseecom_enable_sfpb_vectors, + }, +}; + +static struct msm_bus_scale_pdata qseecom_bus_pdata = { + qseecom_hw_bus_scale_usecases, + ARRAY_SIZE(qseecom_hw_bus_scale_usecases), + .name = "qsee", +}; + +static struct platform_device qseecom_device = { + .name = "qseecom", + .id = 0, + .dev = { + .platform_data = &qseecom_bus_pdata, + }, +}; +#endif + +#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 + +#define MDM2AP_ERRFATAL 70 +#define AP2MDM_ERRFATAL 95 +#define MDM2AP_STATUS 69 +#define AP2MDM_STATUS 94 +#define AP2MDM_PMIC_RESET_N 80 +#define AP2MDM_KPDPWR_N 81 + +static struct resource mdm_resources[] = { + { + .start = MDM2AP_ERRFATAL, + .end = MDM2AP_ERRFATAL, + .name = "MDM2AP_ERRFATAL", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_ERRFATAL, + .end = AP2MDM_ERRFATAL, + .name = "AP2MDM_ERRFATAL", + .flags = IORESOURCE_IO, + }, + { + .start = MDM2AP_STATUS, + .end = MDM2AP_STATUS, + .name = "MDM2AP_STATUS", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_STATUS, + .end = AP2MDM_STATUS, + .name = "AP2MDM_STATUS", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_PMIC_RESET_N, + .end = AP2MDM_PMIC_RESET_N, + .name = "AP2MDM_PMIC_RESET_N", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_KPDPWR_N, + .end = AP2MDM_KPDPWR_N, + .name = "AP2MDM_KPDPWR_N", + .flags = IORESOURCE_IO, + }, +}; + +static struct mdm_platform_data mdm_platform_data = { + .mdm_version = "2.5", +}; + +static struct platform_device mdm_device = { + .name = "mdm2_modem", + .id = -1, + .num_resources = ARRAY_SIZE(mdm_resources), + .resource = mdm_resources, + .dev = { + .platform_data = &mdm_platform_data, + }, +}; + +static struct platform_device *mdm_devices[] __initdata = { + &mdm_device, +}; + +#ifdef CONFIG_MSM_MPM +static uint16_t msm_mpm_irqs_m2a[MSM_MPM_NR_MPM_IRQS] __initdata = { + [1] = MSM_GPIO_TO_INT(46), + [2] = MSM_GPIO_TO_INT(150), + [4] = MSM_GPIO_TO_INT(103), + [5] = MSM_GPIO_TO_INT(104), + [6] = MSM_GPIO_TO_INT(105), + [7] = MSM_GPIO_TO_INT(106), + [8] = MSM_GPIO_TO_INT(107), + [9] = MSM_GPIO_TO_INT(7), + [10] = MSM_GPIO_TO_INT(11), + [11] = MSM_GPIO_TO_INT(15), + [12] = MSM_GPIO_TO_INT(19), + [13] = MSM_GPIO_TO_INT(23), + [14] = MSM_GPIO_TO_INT(27), + [15] = MSM_GPIO_TO_INT(31), + [16] = MSM_GPIO_TO_INT(35), + [19] = MSM_GPIO_TO_INT(90), + [20] = MSM_GPIO_TO_INT(92), + [23] = MSM_GPIO_TO_INT(85), + [24] = MSM_GPIO_TO_INT(83), + [25] = USB1_HS_IRQ, + [27] = HDMI_IRQ, + [29] = MSM_GPIO_TO_INT(10), + [30] = MSM_GPIO_TO_INT(102), + [31] = MSM_GPIO_TO_INT(81), + [32] = MSM_GPIO_TO_INT(78), + [33] = MSM_GPIO_TO_INT(94), + [34] = MSM_GPIO_TO_INT(72), + [35] = MSM_GPIO_TO_INT(39), + [36] = MSM_GPIO_TO_INT(43), + [37] = MSM_GPIO_TO_INT(61), + [38] = MSM_GPIO_TO_INT(50), + [39] = MSM_GPIO_TO_INT(42), + [41] = MSM_GPIO_TO_INT(62), + [42] = MSM_GPIO_TO_INT(76), + [43] = MSM_GPIO_TO_INT(75), + [44] = MSM_GPIO_TO_INT(70), + [45] = MSM_GPIO_TO_INT(69), + [46] = MSM_GPIO_TO_INT(67), + [47] = MSM_GPIO_TO_INT(65), + [48] = MSM_GPIO_TO_INT(58), + [49] = MSM_GPIO_TO_INT(54), + [50] = MSM_GPIO_TO_INT(52), + [51] = MSM_GPIO_TO_INT(49), + [52] = MSM_GPIO_TO_INT(40), + [53] = MSM_GPIO_TO_INT(37), + [54] = MSM_GPIO_TO_INT(24), + [55] = MSM_GPIO_TO_INT(14), +}; + +static uint16_t msm_mpm_bypassed_apps_irqs[] __initdata = { + 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_30, + SPS_MTI_31, + RIVA_APSS_SPARE_IRQ, + RIVA_APPS_WLAN_SMSM_IRQ, + RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ, + RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ, +}; + +struct msm_mpm_device_data msm8930_mpm_dev_data __initdata = { + .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 + +#define MSM_SHARED_RAM_PHYS 0x80000000 + +static void __init msm8930_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_msm8930_io(); + + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); +} + +static void __init msm8930_init_irq(void) +{ + struct msm_mpm_device_data *data = NULL; +#ifdef CONFIG_MSM_MPM + data = &msm8930_mpm_dev_data; +#endif + + msm_mpm_irq_extn_init(data); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); +} + +static void __init msm8930_init_buses(void) +{ +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_rpm_set_mt_mask(); + msm_bus_8930_apps_fabric_pdata.rpm_enabled = 1; + msm_bus_8930_sys_fabric_pdata.rpm_enabled = 1; + msm_bus_8930_mm_fabric_pdata.rpm_enabled = 1; + msm_bus_8930_apps_fabric.dev.platform_data = + &msm_bus_8930_apps_fabric_pdata; + msm_bus_8930_sys_fabric.dev.platform_data = + &msm_bus_8930_sys_fabric_pdata; + msm_bus_8930_mm_fabric.dev.platform_data = + &msm_bus_8930_mm_fabric_pdata; + msm_bus_8930_sys_fpb.dev.platform_data = &msm_bus_8930_sys_fpb_pdata; + msm_bus_8930_cpss_fpb.dev.platform_data = &msm_bus_8930_cpss_fpb_pdata; +#endif +} + +static struct msm_spi_platform_data msm8960_qup_spi_gsbi1_pdata = { + .max_clock_speed = 15060000, +}; + +#ifdef CONFIG_USB_MSM_OTG_72K +static struct msm_otg_platform_data msm_otg_pdata; +#else +#ifdef CONFIG_MSM_BUS_SCALING +/* Bandwidth requests (zero) if no vote placed */ +static struct msm_bus_vectors usb_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +/* Bus bandwidth requests in Bytes/sec */ +static struct msm_bus_vectors usb_max_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 60000000, /* At least 480Mbps on bus. */ + .ib = 960000000, /* MAX bursts rate */ + }, +}; + +static struct msm_bus_paths usb_bus_scale_usecases[] = { + { + ARRAY_SIZE(usb_init_vectors), + usb_init_vectors, + }, + { + ARRAY_SIZE(usb_max_vectors), + usb_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata usb_bus_scale_pdata = { + usb_bus_scale_usecases, + ARRAY_SIZE(usb_bus_scale_usecases), + .name = "usb", +}; +#endif + +static struct msm_otg_platform_data msm_otg_pdata = { + .mode = USB_OTG, + .otg_control = OTG_PMIC_CONTROL, + .phy_type = SNPS_28NM_INTEGRATED_PHY, + .pmic_id_irq = PM8038_USB_ID_IN_IRQ(PM8038_IRQ_BASE), + .power_budget = 750, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &usb_bus_scale_pdata, +#endif +}; +#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) +{ + 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; + strlcpy(dload->serial_number, snum, SERIAL_NUMBER_LENGTH); +out: + 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, + }, +}; + +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_CFG] = 0x1F, +#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] = 0x02020204, + .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_CFG] = 0x1F, +#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] = 0x02020204, + .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_SPM_CTL] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DLY] = 0x02020204, + .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 ISA1200_HAP_EN_GPIO 77 +#define ISA1200_HAP_LEN_GPIO 78 +#define ISA1200_HAP_CLK PM8038_GPIO_PM_TO_SYS(7) + +static int isa1200_power(int on) +{ + int rc = 0; + + gpio_set_value_cansleep(ISA1200_HAP_CLK, !!on); + + if (on) + rc = pm8xxx_aux_clk_control(CLK_MP3_1, XO_DIV_1, true); + else + rc = pm8xxx_aux_clk_control(CLK_MP3_1, XO_DIV_NONE, true); + + if (rc) { + pr_err("%s: unable to write aux clock register(%d)\n", + __func__, rc); + } + + return rc; +} + +static int isa1200_dev_setup(bool enable) +{ + int rc = 0; + + if (!enable) + goto fail_gpio_dir; + + rc = gpio_request(ISA1200_HAP_CLK, "haptics_clk"); + if (rc) { + pr_err("%s: gpio_request for %d gpio failed rc(%d)\n", + __func__, ISA1200_HAP_CLK, rc); + goto fail_gpio_req; + } + + rc = gpio_direction_output(ISA1200_HAP_CLK, 0); + if (rc) { + pr_err("%s: gpio_direction_output failed for %d gpio rc(%d)\n", + __func__, ISA1200_HAP_CLK, rc); + goto fail_gpio_dir; + } + + return 0; + +fail_gpio_dir: + gpio_free(ISA1200_HAP_CLK); +fail_gpio_req: + return rc; + +} + +static struct isa1200_regulator isa1200_reg_data[] = { + { + .name = "vddp", + .min_uV = ISA_I2C_VTG_MIN_UV, + .max_uV = ISA_I2C_VTG_MAX_UV, + .load_uA = ISA_I2C_CURR_UA, + }, + { + .name = "vcc_i2c", + .min_uV = ISA_I2C_VTG_MIN_UV, + .max_uV = ISA_I2C_VTG_MAX_UV, + .load_uA = ISA_I2C_CURR_UA, + }, +}; + +static struct isa1200_platform_data isa1200_1_pdata = { + .name = "vibrator", + .dev_setup = isa1200_dev_setup, + .power_on = isa1200_power, + .hap_en_gpio = ISA1200_HAP_EN_GPIO, + .hap_len_gpio = ISA1200_HAP_LEN_GPIO, + .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, + .regulator_info = isa1200_reg_data, + .num_regulators = ARRAY_SIZE(isa1200_reg_data), +}; + +static struct i2c_board_info msm_isa1200_board_info[] __initdata = { + { + I2C_BOARD_INFO("isa1200_1", 0x90>>1), + .platform_data = &isa1200_1_pdata, + }, +}; + +#define MXT_TS_GPIO_IRQ 11 +#define MXT_TS_RESET_GPIO 52 + +static const u8 mxt_config_data_8930[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 15, 3, 0, 15, 12, 11, 0, 0, + /* T7 Object */ + 32, 16, 50, + /* T8 Object */ + 30, 0, 5, 1, 0, 0, 8, 8, 0, 0, + /* T9 Object */ + 131, 0, 0, 19, 11, 0, 16, 43, 2, 3, + 10, 7, 2, 0, 4, 5, 35, 10, 43, 4, + 54, 2, 15, 32, 38, 38, 143, 40, 143, 80, + 7, 9, 50, 50, 2, + /* T15 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + /* T18 Object */ + 0, 0, + /* T19 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + /* T23 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /* T25 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T42 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* T46 Object */ + 0, 3, 8, 16, 0, 0, 1, 0, 0, + /* T47 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T48 Object */ + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 100, 4, 64, + 0, 0, 5, 42, 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 ssize_t mxt224e_vkeys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, 200, + __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":57:1030:90:90" + ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":206:1030:90:90" + ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":366:1030:90:90" + ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":503:1030:90:90" + "\n"); +} + +static struct kobj_attribute mxt224e_vkeys_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = &mxt224e_vkeys_show, +}; + +static struct attribute *mxt224e_properties_attrs[] = { + &mxt224e_vkeys_attr.attr, + NULL +}; + +static struct attribute_group mxt224e_properties_attr_group = { + .attrs = mxt224e_properties_attrs, +}; + +static void mxt_init_vkeys_8930(void) +{ + int rc = 0; + static struct kobject *mxt224e_properties_kobj; + + mxt224e_vkeys_attr.attr.name = "virtualkeys.atmel_mxt_ts"; + mxt224e_properties_kobj = kobject_create_and_add("board_properties", + NULL); + if (mxt224e_properties_kobj) + rc = sysfs_create_group(mxt224e_properties_kobj, + &mxt224e_properties_attr_group); + if (!mxt224e_properties_kobj || rc) + pr_err("%s: failed to create board_properties\n", + __func__); + + return; +} + +static struct mxt_config_info mxt_config_array[] = { + { + .config = mxt_config_data_8930, + .config_length = ARRAY_SIZE(mxt_config_data_8930), + .family_id = 0x81, + .variant_id = 0x01, + .version = 0x10, + .build = 0xAA, + }, +}; + +static struct mxt_platform_data mxt_platform_data_8930 = { + .config_array = mxt_config_array, + .config_array_size = ARRAY_SIZE(mxt_config_array), + .panel_minx = 0, + .panel_maxx = 566, + .panel_miny = 0, + .panel_maxy = 1067, + .disp_minx = 0, + .disp_maxx = 540, + .disp_miny = 0, + .disp_maxy = 960, + .irqflags = IRQF_TRIGGER_FALLING, +#ifdef MSM8930_PHASE_2 + .digital_pwr_regulator = true, +#endif + .i2c_pull_up = true, + .reset_gpio = MXT_TS_RESET_GPIO, + .irq_gpio = MXT_TS_GPIO_IRQ, +}; + +static struct i2c_board_info mxt_device_info_8930[] __initdata = { + { + I2C_BOARD_INFO("atmel_mxt_ts", 0x4a), + .platform_data = &mxt_platform_data_8930, + .irq = MSM_GPIO_TO_INT(MXT_TS_GPIO_IRQ), + }, +}; + +#ifdef MSM8930_PHASE_2 + +#define GPIO_VOLUME_UP PM8038_GPIO_PM_TO_SYS(3) +#define GPIO_VOLUME_DOWN PM8038_GPIO_PM_TO_SYS(8) +#define GPIO_CAMERA_SNAPSHOT PM8038_GPIO_PM_TO_SYS(10) +#define GPIO_CAMERA_FOCUS PM8038_GPIO_PM_TO_SYS(11) + +static struct gpio_keys_button keys_8930[] = { + { + .code = KEY_VOLUMEUP, + .type = EV_KEY, + .desc = "volume_up", + .gpio = GPIO_VOLUME_UP, + .wakeup = 1, + .active_low = 1, + }, + { + .code = KEY_VOLUMEDOWN, + .type = EV_KEY, + .desc = "volume_down", + .gpio = GPIO_VOLUME_DOWN, + .wakeup = 1, + .active_low = 1, + }, + { + .code = KEY_CAMERA_FOCUS, + .type = EV_KEY, + .desc = "camera_focus", + .gpio = GPIO_CAMERA_FOCUS, + .wakeup = 1, + .active_low = 1, + }, + { + .code = KEY_CAMERA_SNAPSHOT, + .type = EV_KEY, + .desc = "camera_snapshot", + .gpio = GPIO_CAMERA_SNAPSHOT, + .wakeup = 1, + .active_low = 1, + }, +}; + +/* Add GPIO keys for 8930 */ +static struct gpio_keys_platform_data gpio_keys_8930_pdata = { + .buttons = keys_8930, + .nbuttons = 4, +}; + +static struct platform_device gpio_keys_8930 = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &gpio_keys_8930_pdata, + }, +}; +#endif /* MSM8930_PHASE_2 */ + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi4_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi3_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi9_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi10_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi12_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + + +static struct ks8851_pdata spi_eth_pdata = { + .irq_gpio = KS8851_IRQ_GPIO, + .rst_gpio = KS8851_RST_GPIO, +}; + +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, + .platform_data = &spi_eth_pdata + }, + { + .modalias = "dsi_novatek_3d_panel_spi", + .max_speed_hz = 10800000, + .bus_num = 0, + .chip_select = 1, + .mode = SPI_MODE_0, + }, +}; + +static struct platform_device msm_device_saw_core0 = { + .name = "saw-regulator", + .id = 0, + .dev = { + .platform_data = &msm8930_saw_regulator_core0_pdata, + }, +}; + +static struct platform_device msm_device_saw_core1 = { + .name = "saw-regulator", + .id = 1, + .dev = { + .platform_data = &msm8930_saw_regulator_core1_pdata, + }, +}; + +static struct tsens_platform_data msm_tsens_pdata = { + .tsens_factor = 1000, + .hw_type = APQ_8064, + .tsens_num_sensor = 10, + .slope = {1132, 1135, 1137, 1135, 1157, + 1142, 1124, 1153, 1175, 1166}, +}; + +static struct platform_device msm_tsens_device = { + .name = "tsens8960-tm", + .id = -1, +}; + +#ifdef CONFIG_MSM_FAKE_BATTERY +static struct platform_device fish_battery_device = { + .name = "fish_battery", +}; +#endif + +#ifndef MSM8930_PHASE_2 + +/* 8930 Phase 1 */ +static struct platform_device msm8930_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], + }, +}; + +static struct platform_device msm8930_device_ext_l2_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = 91, + .dev = { + .platform_data = &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_L2], + }, +}; + +#else + +/* 8930 Phase 2 */ +static struct platform_device msm8930_device_ext_5v_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = 63, + .dev = { + .platform_data = + &msm8930_gpio_regulator_pdata[MSM8930_GPIO_VREG_ID_EXT_5V], + }, +}; + +static struct platform_device msm8930_device_ext_otg_sw_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = 97, + .dev = { + .platform_data = + &msm8930_gpio_regulator_pdata[MSM8930_GPIO_VREG_ID_EXT_OTG_SW], + }, +}; + +#endif + +static struct platform_device msm8930_device_rpm_regulator __devinitdata = { + .name = "rpm-regulator", + .id = -1, + .dev = { +#ifndef MSM8930_PHASE_2 + .platform_data = &msm_rpm_regulator_pdata, +#else + .platform_data = &msm8930_rpm_regulator_pdata, +#endif + }, +}; + +static struct platform_device *common_devices[] __initdata = { + &msm8960_device_dmov, + &msm_device_smd, + &msm8960_device_uart_gsbi5, + &msm_device_uart_dm6, + &msm_device_saw_core0, + &msm_device_saw_core1, + &msm8930_device_ext_5v_vreg, +#ifndef MSM8930_PHASE_2 + &msm8930_device_ext_l2_vreg, +#endif + &msm8960_device_ssbi_pmic, +#ifdef MSM8930_PHASE_2 + &msm8930_device_ext_otg_sw_vreg, +#endif + &msm_8960_q6_lpass, + &msm_8960_q6_mss_fw, + &msm_8960_q6_mss_sw, + &msm_8960_riva, + &msm_pil_tzapps, + &msm_pil_vidc, + &msm8960_device_qup_spi_gsbi1, + &msm8960_device_qup_i2c_gsbi3, + &msm8960_device_qup_i2c_gsbi4, + &msm8960_device_qup_i2c_gsbi9, + &msm8960_device_qup_i2c_gsbi10, + &msm8960_device_qup_i2c_gsbi12, + &msm_slim_ctrl, + &msm_device_wcnss_wlan, +#if defined(CONFIG_QSEECOM) + &qseecom_device, +#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_ROTATOR + &msm_rotator_device, +#endif + &msm_device_sps, +#ifdef CONFIG_MSM_FAKE_BATTERY + &fish_battery_device, +#endif +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + &msm8930_android_pmem_device, + &msm8930_android_pmem_adsp_device, + &msm8930_android_pmem_audio_device, +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + &msm8930_fmem_device, + &msm_device_bam_dmux, + &msm_fm_platform_init, + +#ifdef CONFIG_HW_RANDOM_MSM + &msm_device_rng, +#endif + &msm8930_rpm_device, + &msm8930_rpm_log_device, + &msm8930_rpm_stat_device, +#ifdef CONFIG_ION_MSM + &msm8930_ion_dev, +#endif + &msm_device_tz_log, + +#ifdef CONFIG_MSM_QDSS + &msm_qdss_device, + &msm_etb_device, + &msm_tpiu_device, + &msm_funnel_device, + &msm_etm_device, +#endif + &msm_device_dspcrashd_8960, + &msm8960_device_watchdog, +#ifdef MSM8930_PHASE_2 + &gpio_keys_8930, +#endif + &msm8930_rtb_device, + &msm8930_cpu_idle_device, + &msm8930_msm_gov_device, + &msm_bus_8930_apps_fabric, + &msm_bus_8930_sys_fabric, + &msm_bus_8930_mm_fabric, + &msm_bus_8930_sys_fpb, + &msm_bus_8930_cpss_fpb, + &msm8960_device_cache_erp, + &msm8930_iommu_domain_device, + &msm_tsens_device, +}; + +static struct platform_device *cdp_devices[] __initdata = { + &msm8960_device_otg, + &msm8960_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_cpudai_auxpcm_rx, + &msm_cpudai_auxpcm_tx, + &msm_cpu_fe, + &msm_stub_codec, +#ifdef CONFIG_MSM_GEMINI + &msm8960_gemini_device, +#endif + &msm_voice, + &msm_voip, + &msm_lpa_pcm, + &msm_cpudai_afe_01_rx, + &msm_cpudai_afe_01_tx, + &msm_cpudai_afe_02_rx, + &msm_cpudai_afe_02_tx, + &msm_pcm_afe, + &msm_compr_dsp, + &msm_cpudai_incall_music_rx, + &msm_cpudai_incall_record_rx, + &msm_cpudai_incall_record_tx, + &msm_pcm_hostless, +}; + +static void __init msm8930_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_gsbi9.dev.platform_data = + &msm8960_i2c_qup_gsbi9_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 msm_rpmrs_level msm_rpmrs_levels[] __initdata = { + { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1, 784, 180000, 100, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1300, 228, 1200000, 2000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, GDHS, MAX, ACTIVE), + false, + 2000, 138, 1208400, 3200, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 6000, 119, 1850300, 9000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, GDHS, MAX, ACTIVE), + false, + 9200, 68, 2839200, 16400, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, MAX, ACTIVE), + false, + 10300, 63, 3128000, 18200, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 18000, 10, 4602600, 27000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, RET_HIGH, RET_LOW), + false, + 20000, 2, 5752000, 32000, + }, +}; + +static struct msm_rpmrs_platform_data msm_rpmrs_data __initdata = { + .levels = &msm_rpmrs_levels[0], + .num_levels = ARRAY_SIZE(msm_rpmrs_levels), + .vdd_mem_levels = { + [MSM_RPMRS_VDD_MEM_RET_LOW] = 750000, + [MSM_RPMRS_VDD_MEM_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_MEM_ACTIVE] = 1050000, + [MSM_RPMRS_VDD_MEM_MAX] = 1150000, + }, + .vdd_dig_levels = { + [MSM_RPMRS_VDD_DIG_RET_LOW] = 0, + [MSM_RPMRS_VDD_DIG_RET_HIGH] = 0, + [MSM_RPMRS_VDD_DIG_ACTIVE] = 1, + [MSM_RPMRS_VDD_DIG_MAX] = 3, + }, + .vdd_mask = 0x7FFFFF, + .rpmrs_target_id = { + [MSM_RPMRS_ID_PXO_CLK] = MSM_RPM_ID_PXO_CLK, + [MSM_RPMRS_ID_L2_CACHE_CTL] = MSM_RPM_ID_LAST, + [MSM_RPMRS_ID_VDD_DIG_0] = MSM_RPM_ID_VOLTAGE_CORNER, + [MSM_RPMRS_ID_VDD_DIG_1] = MSM_RPM_ID_LAST, + [MSM_RPMRS_ID_VDD_MEM_0] = MSM_RPM_ID_PM8038_L24_0, + [MSM_RPMRS_ID_VDD_MEM_1] = MSM_RPM_ID_PM8038_L24_1, + [MSM_RPMRS_ID_RPM_CTL] = MSM_RPM_ID_RPM_CTL, + }, +}; + +static struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_TZ, +}; + +static struct msm_pm_sleep_status_data msm_pm_slp_sts_data = { + .base_addr = MSM_ACC0_BASE + 0x08, + .cpu_offset = MSM_ACC1_BASE - MSM_ACC0_BASE, + .mask = 1UL << 13, +}; + +#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 I2C_LIQUID (1 << 5) + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; + +#ifdef CONFIG_ISL9519_CHARGER +static struct isl_platform_data isl_data __initdata = { + .valid_n_gpio = 0, /* Not required when notify-by-pmic */ + .chg_detection_config = NULL, /* Not required when notify-by-pmic */ + .max_system_voltage = 4200, + .min_system_voltage = 3200, + .chgcurrent = 1000, /* 1900, */ + .term_current = 400, /* Need fine tuning */ + .input_current = 2048, +}; + +static struct i2c_board_info isl_charger_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("isl9519q", 0x9), + .irq = 0, /* Not required when notify-by-pmic */ + .platform_data = &isl_data, + }, +}; +#endif /* CONFIG_ISL9519_CHARGER */ + +static struct i2c_registry msm8960_i2c_devices[] __initdata = { +#ifdef CONFIG_ISL9519_CHARGER + { + I2C_LIQUID, + MSM_8930_GSBI10_QUP_I2C_BUS_ID, + isl_charger_i2c_info, + ARRAY_SIZE(isl_charger_i2c_info), + }, +#endif /* CONFIG_ISL9519_CHARGER */ + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_8930_GSBI9_QUP_I2C_BUS_ID, + msm_isa1200_board_info, + ARRAY_SIZE(msm_isa1200_board_info), + }, + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_8930_GSBI3_QUP_I2C_BUS_ID, + mxt_device_info_8930, + ARRAY_SIZE(mxt_device_info_8930), + }, +}; +#endif /* CONFIG_I2C */ + +static void __init register_i2c_devices(void) +{ +#ifdef CONFIG_I2C + u8 mach_mask = 0; + int i; +#ifdef CONFIG_MSM_CAMERA + struct i2c_registry msm8930_camera_i2c_devices = { + I2C_SURF | I2C_FFA | I2C_FLUID | I2C_LIQUID | I2C_RUMI, + MSM_8930_GSBI4_QUP_I2C_BUS_ID, + msm8930_camera_board_info.board_info, + msm8930_camera_board_info.num_i2c_board_info, + }; +#endif + + /* Build the matching 'supported_machs' bitmask */ + if (machine_is_msm8930_cdp() || machine_is_msm8627_cdp()) + mach_mask = I2C_SURF; + else if (machine_is_msm8930_fluid()) + mach_mask = I2C_FLUID; + else if (machine_is_msm8930_mtp() || machine_is_msm8627_mtp()) + mach_mask = I2C_FFA; + 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); + } +#ifdef CONFIG_MSM_CAMERA + if (msm8930_camera_i2c_devices.machs & mach_mask) + i2c_register_board_info(msm8930_camera_i2c_devices.bus, + msm8930_camera_i2c_devices.info, + msm8930_camera_i2c_devices.len); +#endif +#endif +} + +static void __init msm8930_cdp_init(void) +{ + if (meminfo_init(SYS_MEMORY, SZ_256M) < 0) + pr_err("meminfo_init() failed!\n"); + + msm_tsens_early_init(&msm_tsens_pdata); + BUG_ON(msm_rpm_init(&msm8930_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + + regulator_suppress_info_printing(); + if (msm_xo_init()) + pr_err("Failed to initialize XO votes\n"); + platform_device_register(&msm8930_device_rpm_regulator); + msm_clock_init(&msm8930_clock_init_data); + msm8960_device_otg.dev.platform_data = &msm_otg_pdata; + android_usb_pdata.swfi_latency = + msm_rpmrs_levels[0].latency_us; + msm8930_init_gpiomux(); + 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)); + + /* + * TODO: When physical 8930/PM8038 hardware becomes + * available, remove this block or add the config + * option. + */ +#ifndef MSM8930_PHASE_2 + msm8960_init_pmic(); +#else + msm8930_init_pmic(); +#endif + msm8930_i2c_init(); + msm8930_init_gpu(); + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + msm_spm_l2_init(msm_spm_l2_data); + msm8930_init_buses(); + platform_add_devices(msm8930_footswitch, msm8930_num_footswitch); + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + msm8930_add_vidc_device(); + /* + * TODO: When physical 8930/PM8038 hardware becomes + * available, remove this block or add the config + * option. + */ +#ifndef MSM8930_PHASE_2 + msm8960_pm8921_gpio_mpp_init(); +#else + msm8930_pm8038_gpio_mpp_init(); +#endif + platform_add_devices(cdp_devices, ARRAY_SIZE(cdp_devices)); +#ifdef CONFIG_MSM_CAMERA + msm8930_init_cam(); +#endif + msm8930_init_mmc(); + acpuclk_init(&acpuclk_8930_soc_data); + mxt_init_vkeys_8930(); + register_i2c_devices(); + msm8930_init_fb(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + change_memory_power = &msm8930_change_memory_power; + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_init_sleep_status_data(&msm_pm_slp_sts_data); + + if (PLATFORM_IS_CHARM25()) + platform_add_devices(mdm_devices, ARRAY_SIZE(mdm_devices)); +} + +MACHINE_START(MSM8930_CDP, "QCT MSM8930 CDP") + .map_io = msm8930_map_io, + .reserve = msm8930_reserve, + .init_irq = msm8930_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8930_cdp_init, + .init_early = msm8930_allocate_memory_regions, + .init_very_early = msm8930_early_memory, +MACHINE_END + +MACHINE_START(MSM8930_MTP, "QCT MSM8930 MTP") + .map_io = msm8930_map_io, + .reserve = msm8930_reserve, + .init_irq = msm8930_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8930_cdp_init, + .init_early = msm8930_allocate_memory_regions, + .init_very_early = msm8930_early_memory, +MACHINE_END + +MACHINE_START(MSM8930_FLUID, "QCT MSM8930 FLUID") + .map_io = msm8930_map_io, + .reserve = msm8930_reserve, + .init_irq = msm8930_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8930_cdp_init, + .init_early = msm8930_allocate_memory_regions, + .init_very_early = msm8930_early_memory, +MACHINE_END + +MACHINE_START(MSM8627_CDP, "QCT MSM8627 CDP") + .map_io = msm8930_map_io, + .reserve = msm8930_reserve, + .init_irq = msm8930_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8930_cdp_init, + .init_early = msm8930_allocate_memory_regions, + .init_very_early = msm8930_early_memory, +MACHINE_END + +MACHINE_START(MSM8627_MTP, "QCT MSM8627 MTP") + .map_io = msm8930_map_io, + .reserve = msm8930_reserve, + .init_irq = msm8930_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8930_cdp_init, + .init_early = msm8930_allocate_memory_regions, + .init_very_early = msm8930_early_memory, +MACHINE_END diff --git a/arch/arm/mach-msm/board-8930.h b/arch/arm/mach-msm/board-8930.h new file mode 100644 index 0000000000000000000000000000000000000000..e564aff5b8278550a6cbbd8ed650518080a3b5f0 --- /dev/null +++ b/arch/arm/mach-msm/board-8930.h @@ -0,0 +1,142 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_MSM8930_H +#define __ARCH_ARM_MACH_MSM_BOARD_MSM8930_H + +#define MSM8930_PHASE_2 + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * TODO: When physical 8930/PM8038 hardware becomes + * available, remove this block. + */ +#ifndef MSM8930_PHASE_2 +#include +#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) +#endif + +/* Macros assume PMIC GPIOs and MPPs start at 1 */ +#define PM8038_GPIO_BASE NR_GPIO_IRQS +#define PM8038_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8038_GPIO_BASE) +#define PM8038_MPP_BASE (PM8038_GPIO_BASE + PM8038_NR_GPIOS) +#define PM8038_MPP_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8038_MPP_BASE) +#define PM8038_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) + +/* + * TODO: When physical 8930/PM8038 hardware becomes + * available, replace this block with 8930/pm8038 regulator + * declarations. + */ +#ifndef MSM8930_PHASE_2 +extern struct pm8xxx_regulator_platform_data + msm_pm8921_regulator_pdata[] __devinitdata; + +extern int msm_pm8921_regulator_pdata_len __devinitdata; + +extern struct gpio_regulator_platform_data + msm_gpio_regulator_pdata[] __devinitdata; + +extern struct rpm_regulator_platform_data msm_rpm_regulator_pdata __devinitdata; + +#define GPIO_VREG_ID_EXT_5V 0 +#define GPIO_VREG_ID_EXT_L2 1 +#define GPIO_VREG_ID_EXT_3P3V 2 +#endif + +extern struct regulator_init_data msm8930_saw_regulator_core0_pdata; +extern struct regulator_init_data msm8930_saw_regulator_core1_pdata; + +extern struct pm8xxx_regulator_platform_data + msm8930_pm8038_regulator_pdata[] __devinitdata; + +extern int msm8930_pm8038_regulator_pdata_len __devinitdata; + +#define MSM8930_GPIO_VREG_ID_EXT_5V 0 +#define MSM8930_GPIO_VREG_ID_EXT_OTG_SW 1 + +extern struct gpio_regulator_platform_data + msm8930_gpio_regulator_pdata[] __devinitdata; + +extern struct rpm_regulator_platform_data + msm8930_rpm_regulator_pdata __devinitdata; + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) +enum { + GPIO_EXPANDER_IRQ_BASE = (PM8038_IRQ_BASE + PM8038_NR_IRQS), + GPIO_EXPANDER_GPIO_BASE = (PM8038_MPP_BASE + PM8038_NR_MPPS), + /* CAM Expander */ + GPIO_CAM_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE, + GPIO_CAM_GP_STROBE_READY = GPIO_CAM_EXPANDER_BASE, + GPIO_CAM_GP_AFBUSY, + GPIO_CAM_GP_STROBE_CE, + GPIO_CAM_GP_CAM1MP_XCLR, + GPIO_CAM_GP_CAMIF_RESET_N, + GPIO_CAM_GP_XMT_FLASH_INT, + GPIO_CAM_GP_LED_EN1, + GPIO_CAM_GP_LED_EN2, + +}; +#endif + +enum { + SX150X_CAM, +}; + +#endif + +extern struct sx150x_platform_data msm8930_sx150x_data[]; +extern struct msm_camera_board_info msm8930_camera_board_info; +void msm8930_init_cam(void); +void msm8930_init_fb(void); +void msm8930_init_pmic(void); +extern void msm8930_add_vidc_device(void); + +/* + * TODO: When physical 8930/PM8038 hardware becomes + * available, remove this block or add the config + * option. + */ +#ifndef MSM8930_PHASE_2 +void msm8960_init_pmic(void); +void msm8960_pm8921_gpio_mpp_init(void); +#endif + +void msm8930_init_mmc(void); +int msm8930_init_gpiomux(void); +void msm8930_allocate_fb_region(void); +void msm8930_pm8038_gpio_mpp_init(void); +void msm8930_mdp_writeback(struct memtype_reserve *reserve_table); +void __init msm8930_init_gpu(void); + +#define PLATFORM_IS_CHARM25() \ + (machine_is_msm8930_cdp() && \ + (socinfo_get_platform_subtype() == 1) \ + ) + +#define MSM_8930_GSBI3_QUP_I2C_BUS_ID 3 +#define MSM_8930_GSBI4_QUP_I2C_BUS_ID 4 +#define MSM_8930_GSBI9_QUP_I2C_BUS_ID 0 +#define MSM_8930_GSBI10_QUP_I2C_BUS_ID 10 + +extern struct msm_rtb_platform_data msm8930_rtb_pdata; diff --git a/arch/arm/mach-msm/board-8960-camera.c b/arch/arm/mach-msm/board-8960-camera.c new file mode 100644 index 0000000000000000000000000000000000000000..9c25b78f713d92097270febef60a69b93401476b --- /dev/null +++ b/arch/arm/mach-msm/board-8960-camera.c @@ -0,0 +1,812 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "devices.h" +#include "board-8960.h" + +#ifdef CONFIG_MSM_CAMERA + +#if (defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE)) && \ + defined(CONFIG_I2C) + +static struct i2c_board_info cam_expander_i2c_info[] = { + { + I2C_BOARD_INFO("sx1508q", 0x22), + .platform_data = &msm8960_sx150x_data[SX150X_CAM] + }, +}; + +static struct msm_cam_expander_info cam_expander_info[] = { + { + cam_expander_i2c_info, + MSM_8960_GSBI4_QUP_I2C_BUS_ID, + }, +}; +#endif + +static struct gpiomux_setting cam_settings[] = { + { + .func = GPIOMUX_FUNC_GPIO, /*suspend*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + + { + .func = GPIOMUX_FUNC_1, /*active 1*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_GPIO, /*active 2*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_1, /*active 3*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, + }, + + { + .func = GPIOMUX_FUNC_5, /*active 4*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_6, /*active 5*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_2, /*active 6*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_3, /*active 7*/ + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, + }, + + { + .func = GPIOMUX_FUNC_GPIO, /*i2c suspend*/ + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, + }, + +}; + +static struct msm_gpiomux_config msm8960_cdp_flash_configs[] = { + { + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[1], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, +}; + +static struct msm_gpiomux_config msm8960_cam_common_configs[] = { + { + .gpio = 2, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[1], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 5, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 76, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, + { + .gpio = 107, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[2], + [GPIOMUX_SUSPENDED] = &cam_settings[0], + }, + }, +}; + +static struct msm_gpiomux_config msm8960_cam_2d_configs[] = { + { + .gpio = 18, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 19, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 20, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, + { + .gpio = 21, + .settings = { + [GPIOMUX_ACTIVE] = &cam_settings[3], + [GPIOMUX_SUSPENDED] = &cam_settings[8], + }, + }, +}; + +#define VFE_CAMIF_TIMER1_GPIO 2 +#define VFE_CAMIF_TIMER2_GPIO 3 +#define VFE_CAMIF_TIMER3_GPIO_INT 4 +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), +}; + +#ifdef CONFIG_MSM_CAMERA_FLASH +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_EXT, + ._fsrc.ext_driver_src.led_en = VFE_CAMIF_TIMER1_GPIO, + ._fsrc.ext_driver_src.led_flash_en = VFE_CAMIF_TIMER2_GPIO, + ._fsrc.ext_driver_src.flash_id = MAM_CAMERA_EXT_LED_FLASH_SC628A, +}; +#endif + +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, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_preview_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 27648000, + .ib = 110592000, + }, + { + .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, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_video_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 154275840, + .ib = 617103360, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 206807040, + .ib = 488816640, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_snapshot_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 274423680, + .ib = 1097694720, + }, + { + .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 = 540000000, + .ib = 1350000000, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 43200000, + .ib = 69120000, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 43200000, + .ib = 69120000, + }, +}; + +static struct msm_bus_vectors cam_zsl_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 302071680, + .ib = 1208286720, + }, + { + .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 = 540000000, + .ib = 1350000000, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 43200000, + .ib = 69120000, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_MM_IMEM, + .ab = 43200000, + .ib = 69120000, + }, +}; + +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, + }, +}; + +static struct msm_bus_scale_pdata cam_bus_client_pdata = { + cam_bus_client_config, + ARRAY_SIZE(cam_bus_client_config), + .name = "msm_camera", +}; + +static struct msm_camera_device_platform_data msm_camera_csi_device_data[] = { + { + .csid_core = 0, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, + { + .csid_core = 1, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, + { + .csid_core = 2, + .is_csiphy = 1, + .is_csid = 1, + .is_ispif = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + }, +}; + +static struct camera_vreg_t msm_8960_back_cam_vreg[] = { + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2800000, 300000}, +}; + +static struct camera_vreg_t msm_8960_front_cam_vreg[] = { + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, +}; + +static struct gpio msm8960_common_cam_gpio[] = { + {5, GPIOF_DIR_IN, "CAMIF_MCLK"}, + {20, GPIOF_DIR_IN, "CAMIF_I2C_DATA"}, + {21, GPIOF_DIR_IN, "CAMIF_I2C_CLK"}, +}; + +static struct gpio msm8960_front_cam_gpio[] = { + {76, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct gpio msm8960_back_cam_gpio[] = { + {107, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct msm_gpio_set_tbl msm8960_front_cam_gpio_set_tbl[] = { + {76, GPIOF_OUT_INIT_LOW, 1000}, + {76, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_gpio_set_tbl msm8960_back_cam_gpio_set_tbl[] = { + {107, GPIOF_OUT_INIT_LOW, 1000}, + {107, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_camera_gpio_conf msm_8960_front_cam_gpio_conf = { + .cam_gpiomux_conf_tbl = msm8960_cam_2d_configs, + .cam_gpiomux_conf_tbl_size = ARRAY_SIZE(msm8960_cam_2d_configs), + .cam_gpio_common_tbl = msm8960_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(msm8960_common_cam_gpio), + .cam_gpio_req_tbl = msm8960_front_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(msm8960_front_cam_gpio), + .cam_gpio_set_tbl = msm8960_front_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(msm8960_front_cam_gpio_set_tbl), +}; + +static struct msm_camera_gpio_conf msm_8960_back_cam_gpio_conf = { + .cam_gpiomux_conf_tbl = msm8960_cam_2d_configs, + .cam_gpiomux_conf_tbl_size = ARRAY_SIZE(msm8960_cam_2d_configs), + .cam_gpio_common_tbl = msm8960_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(msm8960_common_cam_gpio), + .cam_gpio_req_tbl = msm8960_back_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(msm8960_back_cam_gpio), + .cam_gpio_set_tbl = msm8960_back_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(msm8960_back_cam_gpio_set_tbl), +}; + +static struct i2c_board_info msm_act_main_cam_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x11), +}; + +static struct msm_actuator_info msm_act_main_cam_0_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_0, + .bus_id = MSM_8960_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + +static struct i2c_board_info msm_act_main_cam1_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x18), +}; + +static struct msm_actuator_info msm_act_main_cam_1_info = { + .board_info = &msm_act_main_cam1_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_1, + .bus_id = MSM_8960_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + +static struct msm_camera_sensor_flash_data flash_imx074 = { + .flash_type = MSM_CAMERA_FLASH_LED, +#ifdef CONFIG_MSM_CAMERA_FLASH + .flash_src = &msm_flash_src +#endif +}; + +static struct msm_camera_csi_lane_params imx074_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_imx074 = { + .mount_angle = 90, + .cam_vreg = msm_8960_back_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8960_back_cam_vreg), + .gpio_conf = &msm_8960_back_cam_gpio_conf, + .csi_lane_params = &imx074_csi_lane_params, +}; + +static struct i2c_board_info imx074_eeprom_i2c_info = { + I2C_BOARD_INFO("imx074_eeprom", 0x34 << 1), +}; + +static struct msm_eeprom_info imx074_eeprom_info = { + .board_info = &imx074_eeprom_i2c_info, + .bus_id = MSM_8960_GSBI4_QUP_I2C_BUS_ID, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx074_data = { + .sensor_name = "imx074", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_imx074, + .strobe_flash_data = &strobe_flash_xenon, + .sensor_platform_info = &sensor_board_info_imx074, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_0_info, + .eeprom_info = &imx074_eeprom_info, +}; + +static struct camera_vreg_t msm_8960_mt9m114_vreg[] = { + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2800000, 300000}, +}; + +static struct msm_camera_sensor_flash_data flash_mt9m114 = { + .flash_type = MSM_CAMERA_FLASH_NONE +}; + +static struct msm_camera_csi_lane_params mt9m114_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0x1, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_mt9m114 = { + .mount_angle = 90, + .cam_vreg = msm_8960_mt9m114_vreg, + .num_vreg = ARRAY_SIZE(msm_8960_mt9m114_vreg), + .gpio_conf = &msm_8960_front_cam_gpio_conf, + .csi_lane_params = &mt9m114_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9m114_data = { + .sensor_name = "mt9m114", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_mt9m114, + .sensor_platform_info = &sensor_board_info_mt9m114, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = YUV_SENSOR, +}; + +static struct msm_camera_sensor_flash_data flash_ov2720 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_csi_lane_params ov2720_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0x3, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_ov2720 = { + .mount_angle = 0, + .cam_vreg = msm_8960_front_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8960_front_cam_vreg), + .gpio_conf = &msm_8960_front_cam_gpio_conf, + .csi_lane_params = &ov2720_csi_lane_params, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov2720_data = { + .sensor_name = "ov2720", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_ov2720, + .sensor_platform_info = &sensor_board_info_ov2720, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = BAYER_SENSOR, +}; + +static struct camera_vreg_t msm_8960_s5k3l1yx_vreg[] = { + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vaf", REG_LDO, 2800000, 2800000, 300000}, +}; + +static struct msm_camera_sensor_flash_data flash_s5k3l1yx = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_csi_lane_params s5k3l1yx_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_s5k3l1yx = { + .mount_angle = 0, + .cam_vreg = msm_8960_s5k3l1yx_vreg, + .num_vreg = ARRAY_SIZE(msm_8960_s5k3l1yx_vreg), + .gpio_conf = &msm_8960_back_cam_gpio_conf, + .csi_lane_params = &s5k3l1yx_csi_lane_params, +}; + +static struct msm_actuator_info msm_act_main_cam_2_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_2, + .bus_id = MSM_8960_GSBI4_QUP_I2C_BUS_ID, + .vcm_pwd = 0, + .vcm_enable = 0, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k3l1yx_data = { + .sensor_name = "s5k3l1yx", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_s5k3l1yx, + .sensor_platform_info = &sensor_board_info_s5k3l1yx, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_2_info, +}; + +static struct msm_camera_csi_lane_params imx091_csi_lane_params = { + .csi_lane_assign = 0xE4, + .csi_lane_mask = 0xF, +}; + +static struct camera_vreg_t msm_8960_imx091_vreg[] = { + {"cam_vana", REG_LDO, 2800000, 2850000, 85600}, + {"cam_vaf", REG_LDO, 2800000, 2800000, 300000}, + {"cam_vdig", REG_LDO, 1200000, 1200000, 105000}, + {"cam_vio", REG_VS, 0, 0, 0}, +}; + +static struct msm_camera_sensor_flash_data flash_imx091 = { + .flash_type = MSM_CAMERA_FLASH_LED, +#ifdef CONFIG_MSM_CAMERA_FLASH + .flash_src = &msm_flash_src +#endif +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_imx091 = { + .mount_angle = 0, + .cam_vreg = msm_8960_imx091_vreg, + .num_vreg = ARRAY_SIZE(msm_8960_imx091_vreg), + .gpio_conf = &msm_8960_back_cam_gpio_conf, + .csi_lane_params = &imx091_csi_lane_params, +}; + +static struct i2c_board_info imx091_eeprom_i2c_info = { + I2C_BOARD_INFO("imx091_eeprom", 0x21), +}; + +static struct msm_eeprom_info imx091_eeprom_info = { + .board_info = &imx091_eeprom_i2c_info, + .bus_id = MSM_8960_GSBI4_QUP_I2C_BUS_ID, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx091_data = { + .sensor_name = "imx091", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_imx091, + .sensor_platform_info = &sensor_board_info_imx091, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_1_info, + .eeprom_info = &imx091_eeprom_info, +}; + +static struct pm8xxx_mpp_config_data privacy_light_on_config = { + .type = PM8XXX_MPP_TYPE_SINK, + .level = PM8XXX_MPP_CS_OUT_5MA, + .control = PM8XXX_MPP_CS_CTRL_MPP_LOW_EN, +}; + +static struct pm8xxx_mpp_config_data privacy_light_off_config = { + .type = PM8XXX_MPP_TYPE_SINK, + .level = PM8XXX_MPP_CS_OUT_5MA, + .control = PM8XXX_MPP_CS_CTRL_DISABLE, +}; + +static int32_t msm_camera_8960_ext_power_ctrl(int enable) +{ + int rc = 0; + if (enable) { + rc = pm8xxx_mpp_config(PM8921_MPP_PM_TO_SYS(12), + &privacy_light_on_config); + } else { + rc = pm8xxx_mpp_config(PM8921_MPP_PM_TO_SYS(12), + &privacy_light_off_config); + } + return rc; +} + +static struct platform_device msm_camera_server = { + .name = "msm_cam_server", + .id = 0, +}; + +void __init msm8960_init_cam(void) +{ + msm_gpiomux_install(msm8960_cam_common_configs, + ARRAY_SIZE(msm8960_cam_common_configs)); + + if (machine_is_msm8960_cdp()) { + msm_gpiomux_install(msm8960_cdp_flash_configs, + ARRAY_SIZE(msm8960_cdp_flash_configs)); + msm_flash_src._fsrc.ext_driver_src.led_en = + GPIO_CAM_GP_LED_EN1; + msm_flash_src._fsrc.ext_driver_src.led_flash_en = + GPIO_CAM_GP_LED_EN2; + #if defined(CONFIG_I2C) && (defined(CONFIG_GPIO_SX150X) || \ + defined(CONFIG_GPIO_SX150X_MODULE)) + msm_flash_src._fsrc.ext_driver_src.expander_info = + cam_expander_info; + #endif + } + + if (machine_is_msm8960_liquid()) { + struct msm_camera_sensor_info *s_info; + s_info = &msm_camera_sensor_imx074_data; + s_info->sensor_platform_info->mount_angle = 180; + s_info = &msm_camera_sensor_ov2720_data; + s_info->sensor_platform_info->ext_power_ctrl = + msm_camera_8960_ext_power_ctrl; + } + + if (machine_is_msm8960_fluid()) { + msm_camera_sensor_imx091_data.sensor_platform_info-> + mount_angle = 270; + } + + platform_device_register(&msm_camera_server); + platform_device_register(&msm8960_device_csiphy0); + platform_device_register(&msm8960_device_csiphy1); + platform_device_register(&msm8960_device_csiphy2); + platform_device_register(&msm8960_device_csid0); + platform_device_register(&msm8960_device_csid1); + platform_device_register(&msm8960_device_csid2); + platform_device_register(&msm8960_device_ispif); + platform_device_register(&msm8960_device_vfe); + platform_device_register(&msm8960_device_vpe); +} + +#ifdef CONFIG_I2C +static struct i2c_board_info msm8960_camera_i2c_boardinfo[] = { + { + I2C_BOARD_INFO("imx074", 0x1A), + .platform_data = &msm_camera_sensor_imx074_data, + }, + { + I2C_BOARD_INFO("ov2720", 0x6C), + .platform_data = &msm_camera_sensor_ov2720_data, + }, + { + I2C_BOARD_INFO("mt9m114", 0x48), + .platform_data = &msm_camera_sensor_mt9m114_data, + }, + { + I2C_BOARD_INFO("s5k3l1yx", 0x20), + .platform_data = &msm_camera_sensor_s5k3l1yx_data, + }, +#ifdef CONFIG_MSM_CAMERA_FLASH_SC628A + { + I2C_BOARD_INFO("sc628a", 0x6E), + }, +#endif + { + I2C_BOARD_INFO("imx091", 0x34), + .platform_data = &msm_camera_sensor_imx091_data, + }, +}; + +struct msm_camera_board_info msm8960_camera_board_info = { + .board_info = msm8960_camera_i2c_boardinfo, + .num_i2c_board_info = ARRAY_SIZE(msm8960_camera_i2c_boardinfo), +}; +#endif +#endif diff --git a/arch/arm/mach-msm/board-8960-display.c b/arch/arm/mach-msm/board-8960-display.c new file mode 100644 index 0000000000000000000000000000000000000000..a9b2a59d4aec549fb09ec2f1d12002f043d59aba --- /dev/null +++ b/arch/arm/mach-msm/board-8960-display.c @@ -0,0 +1,1095 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "board-8960.h" + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_PRIM_BUF_SIZE \ + (roundup((roundup(1920, 32) * roundup(1200, 32) * 4), 4096) * 3) + /* 4 bpp x 3 pages */ +#else +#define MSM_FB_PRIM_BUF_SIZE \ + (roundup((roundup(1920, 32) * roundup(1200, 32) * 4), 4096) * 2) + /* 4 bpp x 2 pages */ +#endif + +/* Note: must be multiple of 4096 */ +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE, 4096) + +#ifdef CONFIG_FB_MSM_OVERLAY0_WRITEBACK +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE \ + roundup((roundup(1920, 32) * roundup(1200, 32) * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY0_WRITEBACK */ + +#ifdef CONFIG_FB_MSM_OVERLAY1_WRITEBACK +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE \ + roundup((roundup(1920, 32) * roundup(1080, 32) * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY1_WRITEBACK */ + +#define MDP_VSYNC_GPIO 0 + +#define MIPI_CMD_NOVATEK_QHD_PANEL_NAME "mipi_cmd_novatek_qhd" +#define MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME "mipi_video_novatek_qhd" +#define MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME "mipi_video_toshiba_wsvga" +#define MIPI_VIDEO_TOSHIBA_WUXGA_PANEL_NAME "mipi_video_toshiba_wuxga" +#define MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME "mipi_video_chimei_wxga" +#define MIPI_VIDEO_CHIMEI_WUXGA_PANEL_NAME "mipi_video_chimei_wuxga" +#define MIPI_VIDEO_SIMULATOR_VGA_PANEL_NAME "mipi_video_simulator_vga" +#define MIPI_CMD_RENESAS_FWVGA_PANEL_NAME "mipi_cmd_renesas_fwvga" +#define MIPI_VIDEO_ORISE_720P_PANEL_NAME "mipi_video_orise_720p" +#define MIPI_CMD_ORISE_720P_PANEL_NAME "mipi_cmd_orise_720p" +#define HDMI_PANEL_NAME "hdmi_msm" +#define TVOUT_PANEL_NAME "tvout_msm" + +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +static unsigned char hdmi_is_primary = 1; +#else +static unsigned char hdmi_is_primary; +#endif + +unsigned char msm8960_hdmi_as_primary_selected(void) +{ + return hdmi_is_primary; +} + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static void set_mdp_clocks_for_wuxga(void); + +static int msm_fb_detect_panel(const char *name) +{ + if (machine_is_msm8960_liquid()) { + u32 ver = socinfo_get_platform_version(); + if (SOCINFO_VERSION_MAJOR(ver) == 3) { + if (!strncmp(name, MIPI_VIDEO_CHIMEI_WUXGA_PANEL_NAME, + strnlen(MIPI_VIDEO_CHIMEI_WUXGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + set_mdp_clocks_for_wuxga(); + return 0; + } + } else { + if (!strncmp(name, MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME, + strnlen(MIPI_VIDEO_CHIMEI_WXGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + } + } else { + if (!strncmp(name, MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + +#if !defined(CONFIG_FB_MSM_LVDS_MIPI_PANEL_DETECT) && \ + !defined(CONFIG_FB_MSM_MIPI_PANEL_DETECT) + if (!strncmp(name, MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME, + strnlen(MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_CMD_NOVATEK_QHD_PANEL_NAME, + strnlen(MIPI_CMD_NOVATEK_QHD_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_VIDEO_SIMULATOR_VGA_PANEL_NAME, + strnlen(MIPI_VIDEO_SIMULATOR_VGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_CMD_RENESAS_FWVGA_PANEL_NAME, + strnlen(MIPI_CMD_RENESAS_FWVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_VIDEO_TOSHIBA_WUXGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WUXGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + set_mdp_clocks_for_wuxga(); + return 0; + } + + if (!strncmp(name, MIPI_VIDEO_ORISE_720P_PANEL_NAME, + strnlen(MIPI_VIDEO_ORISE_720P_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_CMD_ORISE_720P_PANEL_NAME, + strnlen(MIPI_CMD_ORISE_720P_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; +#endif + } + + if (!strncmp(name, HDMI_PANEL_NAME, + strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + if (hdmi_is_primary) + set_mdp_clocks_for_wuxga(); + return 0; + } + + if (!strncmp(name, TVOUT_PANEL_NAME, + strnlen(TVOUT_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + 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, +}; + +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 void mipi_dsi_panel_pwm_cfg(void) +{ + int rc; + static int mipi_dsi_panel_gpio_configured; + static struct pm_gpio pwm_enable = { + .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_VPH, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .disable_pin = 0, + }; + static struct pm_gpio pwm_mode = { + .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_S4, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, + .inv_int_pol = 0, + .disable_pin = 0, + }; + + if (mipi_dsi_panel_gpio_configured == 0) { + /* pm8xxx: gpio-21, Backlight Enable */ + rc = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(21), + &pwm_enable); + if (rc != 0) + pr_err("%s: pwm_enabled failed\n", __func__); + + /* pm8xxx: gpio-24, Bl: Off, PWM mode */ + rc = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(24), + &pwm_mode); + if (rc != 0) + pr_err("%s: pwm_mode failed\n", __func__); + + mipi_dsi_panel_gpio_configured++; + } +} + +static bool dsi_power_on; + +/** + * LiQUID panel on/off + * + * @param on + * + * @return int + */ +static int mipi_dsi_liquid_panel_power(int on) +{ + static struct regulator *reg_l2, *reg_ext_3p3v; + static int gpio21, gpio24, gpio43; + int rc; + + mipi_dsi_panel_pwm_cfg(); + pr_debug("%s: on=%d\n", __func__, on); + + gpio21 = PM8921_GPIO_PM_TO_SYS(21); /* disp power enable_n */ + gpio43 = PM8921_GPIO_PM_TO_SYS(43); /* Displays Enable (rst_n)*/ + gpio24 = PM8921_GPIO_PM_TO_SYS(24); /* Backlight PWM */ + + if (!dsi_power_on) { + + 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_l2, 1200000, 1200000); + if (rc) { + pr_err("set_voltage l2 failed, rc=%d\n", rc); + return -EINVAL; + } + + reg_ext_3p3v = regulator_get(&msm_mipi_dsi1_device.dev, + "vdd_lvds_3p3v"); + if (IS_ERR(reg_ext_3p3v)) { + pr_err("could not get reg_ext_3p3v, rc = %ld\n", + PTR_ERR(reg_ext_3p3v)); + return -ENODEV; + } + + rc = gpio_request(gpio21, "disp_pwr_en_n"); + if (rc) { + pr_err("request gpio 21 failed, rc=%d\n", rc); + return -ENODEV; + } + + rc = gpio_request(gpio43, "disp_rst_n"); + if (rc) { + pr_err("request gpio 43 failed, rc=%d\n", rc); + return -ENODEV; + } + + rc = gpio_request(gpio24, "disp_backlight_pwm"); + if (rc) { + pr_err("request gpio 24 failed, rc=%d\n", rc); + return -ENODEV; + } + + dsi_power_on = true; + } + + if (on) { + 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_l2); + if (rc) { + pr_err("enable l2 failed, rc=%d\n", rc); + return -ENODEV; + } + + rc = regulator_enable(reg_ext_3p3v); + if (rc) { + pr_err("enable reg_ext_3p3v failed, rc=%d\n", rc); + return -ENODEV; + } + + /* set reset pin before power enable */ + gpio_set_value_cansleep(gpio43, 0); /* disp disable (resx=0) */ + + gpio_set_value_cansleep(gpio21, 0); /* disp power enable_n */ + msleep(20); + gpio_set_value_cansleep(gpio43, 1); /* disp enable */ + msleep(20); + gpio_set_value_cansleep(gpio43, 0); /* disp enable */ + msleep(20); + gpio_set_value_cansleep(gpio43, 1); /* disp enable */ + msleep(20); + } else { + gpio_set_value_cansleep(gpio43, 0); + gpio_set_value_cansleep(gpio21, 1); + + rc = regulator_disable(reg_l2); + if (rc) { + pr_err("disable reg_l2 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_ext_3p3v); + if (rc) { + pr_err("disable reg_ext_3p3v failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_set_optimum_mode(reg_l2, 100); + if (rc < 0) { + pr_err("set_optimum_mode l2 failed, rc=%d\n", rc); + return -EINVAL; + } + } + + return 0; +} + +static int mipi_dsi_cdp_panel_power(int on) +{ + static struct regulator *reg_l8, *reg_l23, *reg_l2; + static int gpio43; + int rc; + + pr_debug("%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; + } + 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; + } + gpio_set_value_cansleep(gpio43, 1); + } else { + rc = regulator_disable(reg_l2); + if (rc) { + pr_err("disable reg_l2 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_l8); + if (rc) { + pr_err("disable reg_l8 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_l23); + if (rc) { + pr_err("disable reg_l23 failed, rc=%d\n", rc); + return -ENODEV; + } + 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 char mipi_dsi_splash_is_enabled(void); +static int mipi_dsi_panel_power(int on) +{ + int ret; + + pr_debug("%s: on=%d\n", __func__, on); + + if (machine_is_msm8960_liquid()) + ret = mipi_dsi_liquid_panel_power(on); + else + ret = mipi_dsi_cdp_panel_power(on); + + return ret; +} + +static struct mipi_dsi_platform_data mipi_dsi_pdata = { + .vsync_gpio = MDP_VSYNC_GPIO, + .dsi_power_save = mipi_dsi_panel_power, + .splash_is_enabled = mipi_dsi_splash_is_enabled, +}; + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors mdp_init_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_ui_vectors[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 216000000 * 2, + .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_EBI_CH0, + .ab = 216000000 * 2, + .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_EBI_CH0, + .ab = 230400000 * 2, + .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_EBI_CH0, + .ab = 334080000 * 2, + .ib = 417600000 * 2, + }, +}; + +static struct msm_bus_paths mdp_bus_scale_usecases[] = { + { + ARRAY_SIZE(mdp_init_vectors), + mdp_init_vectors, + }, + { + ARRAY_SIZE(mdp_ui_vectors), + mdp_ui_vectors, + }, + { + ARRAY_SIZE(mdp_ui_vectors), + mdp_ui_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 + +static int mdp_core_clk_rate_table[] = { + 85330000, + 128000000, + 160000000, + 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, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .mem_hid = BIT(ION_CP_MM_HEAP_ID), +#else + .mem_hid = MEMTYPE_EBI1, +#endif + .cont_splash_enabled = 0x01, +}; + +void __init msm8960_mdp_writeback(struct memtype_reserve* reserve_table) +{ + mdp_pdata.ov0_wb_size = MSM_FB_OVERLAY0_WRITEBACK_SIZE; + mdp_pdata.ov1_wb_size = MSM_FB_OVERLAY1_WRITEBACK_SIZE; +#if defined(CONFIG_ANDROID_PMEM) && !defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov0_wb_size; + reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov1_wb_size; +#endif +} + +static char mipi_dsi_splash_is_enabled(void) +{ + return mdp_pdata.cont_splash_enabled; +} + +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, +}; + +#define LPM_CHANNEL0 0 +static int toshiba_gpio[] = {LPM_CHANNEL0}; + +static struct mipi_dsi_panel_platform_data toshiba_pdata = { + .gpio = toshiba_gpio, + .dsi_pwm_cfg = mipi_dsi_panel_pwm_cfg, +}; + +static struct platform_device mipi_dsi_toshiba_panel_device = { + .name = "mipi_toshiba", + .id = 0, + .dev = { + .platform_data = &toshiba_pdata, + } +}; + +#define FPGA_3D_GPIO_CONFIG_ADDR 0xB5 +static int dsi2lvds_gpio[4] = { + 0,/* Backlight PWM-ID=0 for PMIC-GPIO#24 */ + 0x1F08, /* DSI2LVDS Bridge GPIO Output, mask=0x1f, out=0x08 */ + GPIO_LIQUID_EXPANDER_BASE+6, /* TN Enable */ + GPIO_LIQUID_EXPANDER_BASE+7, /* TN Mode */ + }; + +static struct msm_panel_common_pdata mipi_dsi2lvds_pdata = { + .gpio_num = dsi2lvds_gpio, +}; + +static struct mipi_dsi_phy_ctrl dsi_novatek_cmd_mode_phy_db = { + +/* DSI_BIT_CLK at 500MHz, 2 lane, RGB888 */ + {0x0F, 0x0a, 0x04, 0x00, 0x20}, /* regulator */ + /* timing */ + {0xab, 0x8a, 0x18, 0x00, 0x92, 0x97, 0x1b, 0x8c, + 0x0c, 0x03, 0x04, 0xa0}, + {0x5f, 0x00, 0x00, 0x10}, /* phy ctrl */ + {0xff, 0x00, 0x06, 0x00}, /* strength */ + /* pll control */ + {0x40, 0xf9, 0x30, 0xda, 0x00, 0x40, 0x03, 0x62, + 0x40, 0x07, 0x03, + 0x00, 0x1a, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x01}, +}; + +static struct mipi_dsi_panel_platform_data novatek_pdata = { + .fpga_3d_config_addr = FPGA_3D_GPIO_CONFIG_ADDR, + .fpga_ctrl_mode = FPGA_SPI_INTF, + .phy_ctrl_settings = &dsi_novatek_cmd_mode_phy_db, +}; + +static struct platform_device mipi_dsi_novatek_panel_device = { + .name = "mipi_novatek", + .id = 0, + .dev = { + .platform_data = &novatek_pdata, + } +}; + +static struct platform_device mipi_dsi2lvds_bridge_device = { + .name = "mipi_tc358764", + .id = 0, + .dev.platform_data = &mipi_dsi2lvds_pdata, +}; + +static struct platform_device mipi_dsi_orise_panel_device = { + .name = "mipi_orise", + .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 */ + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL +static struct platform_device wfd_panel_device = { + .name = "wfd_panel", + .id = 0, + .dev.platform_data = NULL, +}; + +static struct platform_device wfd_device = { + .name = "msm_wfd", + .id = -1, +}; +#endif + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors dtv_bus_init_vectors[] = { + { + .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[] = { + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 566092800 * 2, + .ib = 707616000 * 2, + }, +}; + +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 + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +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 (IS_ERR(reg_8921_hdmi_mvs)) { + pr_err("'%s' regulator not found, rc=%ld\n", + "hdmi_mvs", IS_ERR(reg_8921_hdmi_mvs)); + reg_8921_hdmi_mvs = NULL; + return -ENODEV; + } + } + + 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 (IS_ERR(reg_8921_l23)) { + pr_err("could not get reg_8921_l23, rc = %ld\n", + PTR_ERR(reg_8921_l23)); + return -ENODEV; + } + rc = regulator_set_voltage(reg_8921_l23, 1800000, 1800000); + if (rc) { + pr_err("set_voltage failed for 8921_l23, rc=%d\n", rc); + return -EINVAL; + } + } + if (!reg_8921_s4) { + reg_8921_s4 = regulator_get(&hdmi_msm_device.dev, "hdmi_vcc"); + if (IS_ERR(reg_8921_s4)) { + pr_err("could not get reg_8921_s4, rc = %ld\n", + PTR_ERR(reg_8921_s4)); + return -ENODEV; + } + rc = regulator_set_voltage(reg_8921_s4, 1800000, 1800000); + if (rc) { + pr_err("set_voltage failed for 8921_s4, rc=%d\n", rc); + return -EINVAL; + } + } + + 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_enable(reg_8921_l23); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "hdmi_avdd", rc); + return 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_disable(reg_8921_l23); + if (rc) { + pr_err("disable reg_8921_l23 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_disable(reg_8921_s4); + if (rc) { + pr_err("disable reg_8921_s4 failed, rc=%d\n", rc); + return -ENODEV; + } + 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); + regulator_disable(reg_8921_s4); + 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 */ + +void __init msm8960_init_fb(void) +{ + platform_device_register(&msm_fb_device); + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL + platform_device_register(&wfd_panel_device); + platform_device_register(&wfd_device); +#endif + + if (machine_is_msm8960_sim()) + platform_device_register(&mipi_dsi_simulator_panel_device); + + if (machine_is_msm8960_rumi3()) + platform_device_register(&mipi_dsi_renesas_panel_device); + + if (!machine_is_msm8960_sim() && !machine_is_msm8960_rumi3()) { + platform_device_register(&mipi_dsi_novatek_panel_device); + platform_device_register(&mipi_dsi_orise_panel_device); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + platform_device_register(&hdmi_msm_device); +#endif + } + + if (machine_is_msm8960_liquid()) + platform_device_register(&mipi_dsi2lvds_bridge_device); + else + platform_device_register(&mipi_dsi_toshiba_panel_device); + + 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_MSM_BUS_SCALING + msm_fb_register_device("dtv", &dtv_pdata); +#endif +} + +void __init msm8960_allocate_fb_region(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)); +} + +/** + * Set MDP clocks to high frequency to avoid DSI underflow + * when using high resolution 1200x1920 WUXGA panels + */ +static void set_mdp_clocks_for_wuxga(void) +{ + int i; + + mdp_ui_vectors[0].ab = 2000000000; + mdp_ui_vectors[0].ib = 2000000000; + mdp_vga_vectors[0].ab = 2000000000; + mdp_vga_vectors[0].ib = 2000000000; + mdp_720p_vectors[0].ab = 2000000000; + mdp_720p_vectors[0].ib = 2000000000; + mdp_1080p_vectors[0].ab = 2000000000; + mdp_1080p_vectors[0].ib = 2000000000; + + mdp_pdata.mdp_core_clk_rate = 200000000; + + for (i = 0; i < ARRAY_SIZE(mdp_core_clk_rate_table); i++) + mdp_core_clk_rate_table[i] = 200000000; + + if (hdmi_is_primary) { + dtv_bus_def_vectors[0].ab = 2000000000; + dtv_bus_def_vectors[0].ib = 2000000000; + } +} + +void __init msm8960_set_display_params(char *prim_panel, char *ext_panel) +{ + int disable_splash = 0; + if (strnlen(prim_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.prim_panel_name, prim_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.prim_panel_name %s\n", + msm_fb_pdata.prim_panel_name); + + if (strncmp((char *)msm_fb_pdata.prim_panel_name, + MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WSVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + /* Disable splash for panels other than Toshiba WSVGA */ + disable_splash = 1; + } + + if (!strncmp((char *)msm_fb_pdata.prim_panel_name, + HDMI_PANEL_NAME, strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + pr_debug("HDMI is the primary display by" + " boot parameter\n"); + hdmi_is_primary = 1; + set_mdp_clocks_for_wuxga(); + } + if (!strncmp((char *)msm_fb_pdata.prim_panel_name, + MIPI_VIDEO_TOSHIBA_WUXGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WUXGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + set_mdp_clocks_for_wuxga(); + } + } + if (strnlen(ext_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.ext_panel_name, ext_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.ext_panel_name %s\n", + msm_fb_pdata.ext_panel_name); + } + + if (disable_splash) + mdp_pdata.cont_splash_enabled = 0; +} diff --git a/arch/arm/mach-msm/board-8960-gpiomux.c b/arch/arm/mach-msm/board-8960-gpiomux.c new file mode 100644 index 0000000000000000000000000000000000000000..fd326f1921e7cfbe7a1daff124c9729359ecf656 --- /dev/null +++ b/arch/arm/mach-msm/board-8960-gpiomux.c @@ -0,0 +1,1004 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "devices.h" +#include "board-8960.h" + +/* The SPI configurations apply to GSBI 1*/ +static struct gpiomux_setting spi_active = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting spi_suspended_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting spi_active_config2 = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting spi_suspended_config2 = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting gsbi3_suspended_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting gsbi3_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting external_vfr[] = { + /* Suspended state */ + { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, + }, + /* Active state */ + { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_KEEPER, + }, +}; + +static struct gpiomux_setting gsbi_uart = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi9_active_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting gsbi9_suspended_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +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 audio_auxpcm[] = { + /* Suspended state */ + { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + }, + /* Active state */ + { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + }, +}; + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +static struct gpiomux_setting gpio_eth_config = { + .pull = GPIOMUX_PULL_NONE, + .drv = GPIOMUX_DRV_8MA, + .func = GPIOMUX_FUNC_GPIO, +}; +#endif + +static struct gpiomux_setting slimbus = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +static struct gpiomux_setting wcnss_5wire_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting wcnss_5wire_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +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_DOWN, +}; + +#ifdef CONFIG_USB_EHCI_MSM_HSIC +static struct gpiomux_setting hsic_act_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting hsic_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + .dir = GPIOMUX_OUT_LOW, +}; + +static struct gpiomux_setting hsic_hub_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; +#endif + +static struct gpiomux_setting hap_lvl_shft_suspended_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting hap_lvl_shft_active_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +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_errfatal_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting ap2mdm_kpdpwr_n_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .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 mdp_vsync_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +#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, +}; + +#if defined(CONFIG_FB_MSM_HDMI_MHL_8334) || defined(CONFIG_FB_MSM_HDMI_MHL_9244) +static struct gpiomux_setting hdmi_active_3_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, + .dir = GPIOMUX_IN, +}; + +static struct gpiomux_setting hdmi_active_4_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, + .dir = GPIOMUX_OUT_HIGH, +}; +#endif +#endif + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +static struct msm_gpiomux_config msm8960_ethernet_configs[] = { + { + .gpio = 90, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, + { + .gpio = 89, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, +}; +#endif + +static struct msm_gpiomux_config msm8960_fusion_gsbi_configs[] = { + { + .gpio = 93, + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi9_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi9_active_cfg, + } + }, + { + .gpio = 94, + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi9_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi9_active_cfg, + } + }, + { + .gpio = 95, + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi9_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi9_active_cfg, + } + }, + { + .gpio = 96, + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi9_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi9_active_cfg, + } + }, +}; + +static struct msm_gpiomux_config msm8960_gsbi_configs[] __initdata = { + { + .gpio = 6, /* GSBI1 QUP SPI_DATA_MOSI */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 7, /* GSBI1 QUP SPI_DATA_MISO */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 8, /* GSBI1 QUP SPI_CS_N */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 9, /* GSBI1 QUP SPI_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 14, /* GSBI1 SPI_CS_1 */ + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config2, + [GPIOMUX_ACTIVE] = &spi_active_config2, + }, + }, + { + .gpio = 16, /* GSBI3 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi3_active_cfg, + }, + }, + { + .gpio = 17, /* GSBI3 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_suspended_cfg, + [GPIOMUX_ACTIVE] = &gsbi3_active_cfg, + }, + }, + { + .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_gsbi5_uart_configs[] __initdata = { + { + .gpio = 22, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, + { + .gpio = 23, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, + { + .gpio = 24, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, + { + .gpio = 25, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, +}; + +static struct msm_gpiomux_config msm8960_external_vfr_configs[] __initdata = { + { + .gpio = 23, /* EXTERNAL VFR */ + .settings = { + [GPIOMUX_SUSPENDED] = &external_vfr[0], + [GPIOMUX_ACTIVE] = &external_vfr[1], + }, + }, +}; + +static struct msm_gpiomux_config msm8960_gsbi8_uart_configs[] __initdata = { + { + .gpio = 34, /* GSBI8 UART3 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, + { + .gpio = 35, /* GSBI8 UART3 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, + { + .gpio = 36, /* GSBI8 UART3 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, + { + .gpio = 37, /* GSBI8 UART3 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi_uart, + }, + }, +}; + +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 msm_gpiomux_config msm8960_audio_auxpcm_configs[] __initdata = { + { + .gpio = 63, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, + { + .gpio = 64, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, + { + .gpio = 65, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, + { + .gpio = 66, + .settings = { + [GPIOMUX_SUSPENDED] = &audio_auxpcm[0], + [GPIOMUX_ACTIVE] = &audio_auxpcm[1], + }, + }, +}; + +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 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, + }, + }, +}; + +#ifdef CONFIG_USB_EHCI_MSM_HSIC +static struct msm_gpiomux_config msm8960_hsic_configs[] = { + { + .gpio = 150, /*HSIC_STROBE */ + .settings = { + [GPIOMUX_ACTIVE] = &hsic_act_cfg, + [GPIOMUX_SUSPENDED] = &hsic_sus_cfg, + }, + }, + { + .gpio = 151, /* HSIC_DATA */ + .settings = { + [GPIOMUX_ACTIVE] = &hsic_act_cfg, + [GPIOMUX_SUSPENDED] = &hsic_sus_cfg, + }, + }, +}; + +static struct msm_gpiomux_config msm8960_hsic_hub_configs[] = { + { + .gpio = 91, /* HSIC_HUB_RESET */ + .settings = { + [GPIOMUX_ACTIVE] = &hsic_hub_act_cfg, + [GPIOMUX_SUSPENDED] = &hsic_sus_cfg, + }, + }, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static struct gpiomux_setting sdcc4_clk_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc4_cmd_data_0_3_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc4_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting sdcc4_data_1_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct msm_gpiomux_config msm8960_sdcc4_configs[] __initdata = { + { + /* SDC4_DATA_3 */ + .gpio = 83, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc4_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc4_suspend_cfg, + }, + }, + { + /* SDC4_DATA_2 */ + .gpio = 84, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc4_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc4_suspend_cfg, + }, + }, + { + /* SDC4_DATA_1 */ + .gpio = 85, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc4_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc4_data_1_suspend_cfg, + }, + }, + { + /* SDC4_DATA_0 */ + .gpio = 86, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc4_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc4_suspend_cfg, + }, + }, + { + /* SDC4_CMD */ + .gpio = 87, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc4_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc4_suspend_cfg, + }, + }, + { + /* SDC4_CLK */ + .gpio = 88, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc4_clk_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc4_suspend_cfg, + }, + }, +}; +#endif + + +static struct msm_gpiomux_config hap_lvl_shft_config[] __initdata = { + { + .gpio = 47, + .settings = { + [GPIOMUX_SUSPENDED] = &hap_lvl_shft_suspended_config, + [GPIOMUX_ACTIVE] = &hap_lvl_shft_active_config, + }, + }, +}; + +static struct msm_gpiomux_config sglte_configs[] __initdata = { + /* AP2MDM_STATUS */ + { + .gpio = 77, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* MDM2AP_STATUS */ + { + .gpio = 24, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_status_cfg, + } + }, + /* MDM2AP_ERRFATAL */ + { + .gpio = 40, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_errfatal_cfg, + } + }, + /* AP2MDM_ERRFATAL */ + { + .gpio = 80, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* AP2MDM_KPDPWR_N */ + { + .gpio = 79, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_kpdpwr_n_cfg, + } + }, + /* AP2MDM_PMIC_PWR_EN */ + { + .gpio = 22, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_kpdpwr_n_cfg, + } + }, + /* AP2MDM_SOFT_RESET */ + { + .gpio = 78, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, +}; + +static struct msm_gpiomux_config msm8960_mdp_vsync_configs[] __initdata = { + { + .gpio = 0, + .settings = { + [GPIOMUX_ACTIVE] = &mdp_vsync_active_cfg, + [GPIOMUX_SUSPENDED] = &mdp_vsync_suspend_cfg, + }, + } +}; + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +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, + }, + }, +#ifdef CONFIG_FB_MSM_HDMI_MHL_9244 + { + .gpio = 15, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_3_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 66, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_4_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, +#endif +#ifdef CONFIG_FB_MSM_HDMI_MHL_8334 + { + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_3_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 15, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_4_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, +#endif /* CONFIG_FB_MSM_HDMI_MHL */ +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct gpiomux_setting sdcc2_clk_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc2_cmd_data_0_3_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc2_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting sdcc2_data_1_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct msm_gpiomux_config msm8960_sdcc2_configs[] __initdata = { + { + /* DATA_3 */ + .gpio = 92, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* DATA_2 */ + .gpio = 91, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* DATA_1 */ + .gpio = 90, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_data_1_suspend_cfg, + }, + }, + { + /* DATA_0 */ + .gpio = 89, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* CMD */ + .gpio = 97, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* CLK */ + .gpio = 98, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_clk_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, +}; +#endif + +int __init msm8960_init_gpiomux(void) +{ + int rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err(KERN_ERR "msm_gpiomux_init failed %d\n", rc); + return rc; + } + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) + msm_gpiomux_install(msm8960_ethernet_configs, + ARRAY_SIZE(msm8960_ethernet_configs)); +#endif + + 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)); + + msm_gpiomux_install(msm8960_audio_auxpcm_configs, + ARRAY_SIZE(msm8960_audio_auxpcm_configs)); + + msm_gpiomux_install(wcnss_5wire_interface, + ARRAY_SIZE(wcnss_5wire_interface)); + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + msm_gpiomux_install(msm8960_sdcc4_configs, + ARRAY_SIZE(msm8960_sdcc4_configs)); +#endif + + if (machine_is_msm8960_mtp() || machine_is_msm8960_fluid() || + machine_is_msm8960_liquid() || machine_is_msm8960_cdp()) + msm_gpiomux_install(hap_lvl_shft_config, + ARRAY_SIZE(hap_lvl_shft_config)); + +#ifdef CONFIG_USB_EHCI_MSM_HSIC + if ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 1) && + machine_is_msm8960_liquid()) + msm_gpiomux_install(msm8960_hsic_configs, + ARRAY_SIZE(msm8960_hsic_configs)); + + if ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 1) && + machine_is_msm8960_liquid()) + msm_gpiomux_install(msm8960_hsic_hub_configs, + ARRAY_SIZE(msm8960_hsic_hub_configs)); +#endif + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + msm_gpiomux_install(msm8960_hdmi_configs, + ARRAY_SIZE(msm8960_hdmi_configs)); +#endif + + msm_gpiomux_install(msm8960_mdp_vsync_configs, + ARRAY_SIZE(msm8960_mdp_vsync_configs)); + + if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) + msm_gpiomux_install(msm8960_gsbi8_uart_configs, + ARRAY_SIZE(msm8960_gsbi8_uart_configs)); + else + msm_gpiomux_install(msm8960_gsbi5_uart_configs, + ARRAY_SIZE(msm8960_gsbi5_uart_configs)); + + if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) { + /* For 8960 Fusion 2.2 Primary IPC */ + msm_gpiomux_install(msm8960_fusion_gsbi_configs, + ARRAY_SIZE(msm8960_fusion_gsbi_configs)); + /* For SGLTE 8960 Fusion External VFR */ + msm_gpiomux_install(msm8960_external_vfr_configs, + ARRAY_SIZE(msm8960_external_vfr_configs)); + } + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + msm_gpiomux_install(msm8960_sdcc2_configs, + ARRAY_SIZE(msm8960_sdcc2_configs)); +#endif + + if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) + msm_gpiomux_install(sglte_configs, + ARRAY_SIZE(sglte_configs)); + + return 0; +} diff --git a/arch/arm/mach-msm/board-8960-pmic.c b/arch/arm/mach-msm/board-8960-pmic.c new file mode 100644 index 0000000000000000000000000000000000000000..ea1ab5875a95cb247fdaa917524667368f2e6816 --- /dev/null +++ b/arch/arm/mach-msm/board-8960-pmic.c @@ -0,0 +1,624 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "devices.h" +#include "board-8960.h" + +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) + +#define PM8XXX_GPIO_OUTPUT_VIN(_gpio, _val, _vin) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, _vin, \ + PM_GPIO_STRENGTH_HIGH, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8XXX_GPIO_OUTPUT_STRENGTH(_gpio, _val, _out_strength) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, PM_GPIO_VIN_S4, \ + _out_strength, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +/* Initial PM8921 GPIO configurations */ +static struct pm8xxx_gpio_init pm8921_gpios[] __initdata = { + PM8XXX_GPIO_OUTPUT_VIN(6, 1, PM_GPIO_VIN_VPH), /* MHL power EN_N */ + PM8XXX_GPIO_DISABLE(7), /* Disable NFC */ + PM8XXX_GPIO_INPUT(16, PM_GPIO_PULL_UP_30), /* SD_CARD_WP */ + /* External regulator shared by display and touchscreen on LiQUID */ + PM8XXX_GPIO_OUTPUT(17, 0), /* DISP 3.3 V Boost */ + PM8XXX_GPIO_OUTPUT(18, 0), /* TABLA SPKR_LEFT_EN=off */ + PM8XXX_GPIO_OUTPUT(19, 0), /* TABLA SPKR_RIGHT_EN=off */ + PM8XXX_GPIO_DISABLE(22), /* Disable NFC */ + PM8XXX_GPIO_OUTPUT_FUNC(25, 0, PM_GPIO_FUNC_2), /* TN_CLK */ + PM8XXX_GPIO_INPUT(26, PM_GPIO_PULL_UP_30), /* SD_CARD_DET_N */ + PM8XXX_GPIO_OUTPUT(43, 1), /* DISP_RESET_N */ + PM8XXX_GPIO_OUTPUT(42, 0), /* USB 5V reg enable */ + /* TABLA CODEC RESET */ + PM8XXX_GPIO_OUTPUT_STRENGTH(34, 1, PM_GPIO_STRENGTH_MED) +}; + +/* 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(PM8XXX_AMUX_MPP_8, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH8, + DOUT_CTRL_LOW), +}; + +void __init msm8960_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; + } + } +} + +static struct pm8xxx_adc_amux pm8xxx_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}, + {"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}, + {"pa_therm1", ADC_MPP_1_AMUX8, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM}, + {"xo_therm", CHANNEL_MUXOFF, CHAN_PATH_SCALING1, AMUX_RSV0, + ADC_DECIMATION_TYPE2, ADC_SCALE_XOTHERM}, + {"pa_therm0", ADC_MPP_1_AMUX3, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM}, +}; + +static struct pm8xxx_adc_properties pm8xxx_adc_data = { + .adc_vdd_reference = 1800, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, +}; + +static struct pm8xxx_adc_platform_data pm8xxx_adc_pdata = { + .adc_channel = pm8xxx_adc_channels_data, + .adc_num_board_channel = ARRAY_SIZE(pm8xxx_adc_channels_data), + .adc_prop = &pm8xxx_adc_data, + .adc_mpp_base = PM8921_MPP_PM_TO_SYS(1), +}; + +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, + .rtc_alarm_powerup = false, +}; + +static struct pm8xxx_pwrkey_platform_data pm8xxx_pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 15625, + .wakeup = 1, +}; + +/* Rotate lock key is not available so use F1 */ +#define KEY_ROTATE_LOCK KEY_F1 + +static const unsigned int keymap_liquid[] = { + KEY(0, 0, KEY_VOLUMEUP), + KEY(0, 1, KEY_VOLUMEDOWN), + KEY(1, 3, KEY_ROTATE_LOCK), + KEY(1, 4, KEY_HOME), +}; + +static struct matrix_keymap_data keymap_data_liquid = { + .keymap_size = ARRAY_SIZE(keymap_liquid), + .keymap = keymap_liquid, +}; + +static struct pm8xxx_keypad_platform_data keypad_data_liquid = { + .input_name = "keypad_8960_liquid", + .input_phys_device = "keypad_8960/input0", + .num_rows = 2, + .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_liquid, +}; + + +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 int pm8921_therm_mitigation[] = { + 1100, + 700, + 600, + 325, +}; + +#define MAX_VOLTAGE_MV 4200 +static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = { + .safety_time = 180, + .update_time = 60000, + .max_voltage = MAX_VOLTAGE_MV, + .min_voltage = 3200, + .resume_voltage_delta = 100, + .term_current = 100, + .cool_temp = 10, + .warm_temp = 40, + .temp_check_period = 1, + .max_bat_chg_current = 1100, + .cool_bat_chg_current = 350, + .warm_bat_chg_current = 350, + .cool_bat_voltage = 4100, + .warm_bat_voltage = 4100, + .thermal_mitigation = pm8921_therm_mitigation, + .thermal_levels = ARRAY_SIZE(pm8921_therm_mitigation), + .rconn_mohm = 18, +}; + +static struct pm8xxx_misc_platform_data pm8xxx_misc_pdata = { + .priority = 0, +}; + +static struct pm8921_bms_platform_data pm8921_bms_pdata __devinitdata = { + .battery_type = BATT_UNKNOWN, + .r_sense = 10, + .i_test = 2500, + .v_failure = 3000, + .calib_delay_ms = 600000, + .max_voltage_uv = MAX_VOLTAGE_MV * 1000, + .rconn_mohm = 30, +}; + +#define PM8921_LC_LED_MAX_CURRENT 4 /* I = 4mA */ +#define PM8921_LC_LED_LOW_CURRENT 1 /* I = 1mA */ +#define PM8XXX_LED_PWM_PERIOD 1000 +#define PM8XXX_LED_PWM_DUTY_MS 20 +/** + * PM8XXX_PWM_CHANNEL_NONE shall be used when LED shall not be + * driven using PWM feature. + */ +#define PM8XXX_PWM_CHANNEL_NONE -1 + +static struct led_info pm8921_led_info_liquid[] = { + { + .name = "led:red", + .flags = PM8XXX_ID_LED_0, + .default_trigger = "battery-charging", + }, + { + .name = "led:green", + .flags = PM8XXX_ID_LED_0, + .default_trigger = "battery-full", + }, + { + .name = "led:blue", + .flags = PM8XXX_ID_LED_2, + .default_trigger = "notification", + }, +}; + +static struct pm8xxx_led_config pm8921_led_configs_liquid[] = { + [0] = { + .id = PM8XXX_ID_LED_0, + .mode = PM8XXX_LED_MODE_MANUAL, + .max_current = PM8921_LC_LED_MAX_CURRENT, + }, + [1] = { + .id = PM8XXX_ID_LED_1, + .mode = PM8XXX_LED_MODE_MANUAL, + .max_current = PM8921_LC_LED_LOW_CURRENT, + }, + [2] = { + .id = PM8XXX_ID_LED_2, + .mode = PM8XXX_LED_MODE_MANUAL, + .max_current = PM8921_LC_LED_MAX_CURRENT, + }, +}; + +static struct led_platform_data pm8xxx_leds_core_liquid = { + .num_leds = ARRAY_SIZE(pm8921_led_info_liquid), + .leds = pm8921_led_info_liquid, +}; + +static struct pm8xxx_led_platform_data pm8xxx_leds_pdata_liquid = { + .led_core = &pm8xxx_leds_core_liquid, + .configs = pm8921_led_configs_liquid, + .num_configs = ARRAY_SIZE(pm8921_led_configs_liquid), +}; + +static struct led_info pm8921_led_info[] = { + [0] = { + .name = "led:battery_charging", + .default_trigger = "battery-charging", + }, + [1] = { + .name = "led:battery_full", + .default_trigger = "battery-full", + }, +}; + +static struct led_platform_data pm8921_led_core_pdata = { + .num_leds = ARRAY_SIZE(pm8921_led_info), + .leds = pm8921_led_info, +}; + +static int pm8921_led0_pwm_duty_pcts[56] = { + 1, 4, 8, 12, 16, 20, 24, 28, 32, 36, + 40, 44, 46, 52, 56, 60, 64, 68, 72, 76, + 80, 84, 88, 92, 96, 100, 100, 100, 98, 95, + 92, 88, 84, 82, 78, 74, 70, 66, 62, 58, + 58, 54, 50, 48, 42, 38, 34, 30, 26, 22, + 14, 10, 6, 4, 1 +}; + +static struct pm8xxx_pwm_duty_cycles pm8921_led0_pwm_duty_cycles = { + .duty_pcts = (int *)&pm8921_led0_pwm_duty_pcts, + .num_duty_pcts = ARRAY_SIZE(pm8921_led0_pwm_duty_pcts), + .duty_ms = PM8XXX_LED_PWM_DUTY_MS, + .start_idx = 0, +}; + +static struct pm8xxx_led_config pm8921_led_configs[] = { + [0] = { + .id = PM8XXX_ID_LED_0, + .mode = PM8XXX_LED_MODE_PWM2, + .max_current = PM8921_LC_LED_MAX_CURRENT, + .pwm_channel = 5, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD, + .pwm_duty_cycles = &pm8921_led0_pwm_duty_cycles, + }, + [1] = { + .id = PM8XXX_ID_LED_1, + .mode = PM8XXX_LED_MODE_PWM1, + .max_current = PM8921_LC_LED_MAX_CURRENT, + .pwm_channel = 4, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD, + }, +}; + +static struct pm8xxx_led_platform_data pm8xxx_leds_pdata = { + .led_core = &pm8921_led_core_pdata, + .configs = pm8921_led_configs, + .num_configs = ARRAY_SIZE(pm8921_led_configs), +}; + +static struct pm8xxx_ccadc_platform_data pm8xxx_ccadc_pdata = { + .r_sense = 10, +}; + +/** + * PM8XXX_PWM_DTEST_CHANNEL_NONE shall be used when no LPG + * channel should be in DTEST mode. + */ + +#define PM8XXX_PWM_DTEST_CHANNEL_NONE (-1) + +static struct pm8xxx_pwm_platform_data pm8xxx_pwm_pdata = { + .dtest_channel = PM8XXX_PWM_DTEST_CHANNEL_NONE, +}; + +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 = &pm8xxx_adc_pdata, + .leds_pdata = &pm8xxx_leds_pdata, + .ccadc_pdata = &pm8xxx_ccadc_pdata, + .pwm_pdata = &pm8xxx_pwm_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, + }, +}; + +void __init msm8960_init_pmic(void) +{ + pmic_reset_irq = PM8921_IRQ_BASE + PM8921_RESOUT_IRQ; + msm8960_device_ssbi_pmic.dev.platform_data = + &msm8960_ssbi_pm8921_pdata; + pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len; + + /* Simulator supports a QWERTY keypad */ + if (machine_is_msm8960_sim()) + pm8921_platform_data.keypad_pdata = &keypad_data_sim; + + if (machine_is_msm8960_liquid()) { + pm8921_platform_data.keypad_pdata = &keypad_data_liquid; + pm8921_platform_data.leds_pdata = &pm8xxx_leds_pdata_liquid; + pm8921_platform_data.bms_pdata->battery_type = BATT_DESAY; + } else if (machine_is_msm8960_mtp()) { + pm8921_platform_data.bms_pdata->battery_type = BATT_PALLADIUM; + } + + if (machine_is_msm8960_fluid()) + pm8921_bms_pdata.rconn_mohm = 20; +} diff --git a/arch/arm/mach-msm/board-8960-regulator.c b/arch/arm/mach-msm/board-8960-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..93c72ea8fe5330527a604fe522832796be0f4b3e --- /dev/null +++ b/arch/arm/mach-msm/board-8960-regulator.c @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "board-8960.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_csid.0"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.1"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_csid.2"), +}; +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"), + REGULATOR_SUPPLY("vdd_ana", "3-004a"), +}; +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", "4-001a"), + REGULATOR_SUPPLY("cam_vana", "4-006c"), + REGULATOR_SUPPLY("cam_vana", "4-0048"), + REGULATOR_SUPPLY("cam_vana", "4-0020"), + REGULATOR_SUPPLY("cam_vana", "4-0034"), +}; +VREG_CONSUMERS(L12) = { + REGULATOR_SUPPLY("8921_l12", NULL), + REGULATOR_SUPPLY("cam_vdig", "4-001a"), + REGULATOR_SUPPLY("cam_vdig", "4-006c"), + REGULATOR_SUPPLY("cam_vdig", "4-0048"), + REGULATOR_SUPPLY("cam_vdig", "4-0020"), + REGULATOR_SUPPLY("cam_vdig", "4-0034"), +}; +VREG_CONSUMERS(L14) = { + REGULATOR_SUPPLY("8921_l14", NULL), + REGULATOR_SUPPLY("pa_therm", "pm8xxx-adc"), +}; +VREG_CONSUMERS(L15) = { + REGULATOR_SUPPLY("8921_l15", NULL), +}; +VREG_CONSUMERS(L16) = { + REGULATOR_SUPPLY("8921_l16", NULL), + REGULATOR_SUPPLY("cam_vaf", "4-001a"), + REGULATOR_SUPPLY("cam_vaf", "4-006c"), + REGULATOR_SUPPLY("cam_vaf", "4-0048"), + REGULATOR_SUPPLY("cam_vaf", "4-0020"), + REGULATOR_SUPPLY("cam_vaf", "4-0034"), +}; +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"), + REGULATOR_SUPPLY("pll_vdd", "pil_riva"), + REGULATOR_SUPPLY("pll_vdd", "pil_qdsp6v4.1"), + REGULATOR_SUPPLY("pll_vdd", "pil_qdsp6v4.2"), +}; +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"), + REGULATOR_SUPPLY("VDDD_CDC_D", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "tabla2x-slim"), +}; +VREG_CONSUMERS(L26) = { + REGULATOR_SUPPLY("8921_l26", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.0"), +}; +VREG_CONSUMERS(L27) = { + REGULATOR_SUPPLY("8921_l27", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.2"), +}; +VREG_CONSUMERS(L28) = { + REGULATOR_SUPPLY("8921_l28", NULL), + REGULATOR_SUPPLY("core_vdd", "pil_qdsp6v4.1"), +}; +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"), + REGULATOR_SUPPLY("HSIC_VDDCX", "msm_hsic_host"), +}; +VREG_CONSUMERS(S4) = { + REGULATOR_SUPPLY("8921_s4", NULL), + REGULATOR_SUPPLY("sdc_vccq", "msm_sdcc.1"), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.2"), + REGULATOR_SUPPLY("sdc_vddp", "msm_sdcc.4"), + 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"), + REGULATOR_SUPPLY("VDDIO_CDC", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDD_CP", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "tabla2x-slim"), + REGULATOR_SUPPLY("vcc_i2c", "3-005b"), + REGULATOR_SUPPLY("EXT_HUB_VDDIO", "msm_smsc_hub"), + REGULATOR_SUPPLY("vcc_i2c", "10-0048"), +}; +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("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"), + REGULATOR_SUPPLY("vcc_i2c", "3-004a"), +}; +VREG_CONSUMERS(LVS5) = { + REGULATOR_SUPPLY("8921_lvs5", NULL), + REGULATOR_SUPPLY("cam_vio", "4-001a"), + REGULATOR_SUPPLY("cam_vio", "4-006c"), + REGULATOR_SUPPLY("cam_vio", "4-0048"), + REGULATOR_SUPPLY("cam_vio", "4-0020"), + REGULATOR_SUPPLY("cam_vio", "4-0034"), +}; +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), +}; +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"), +}; +VREG_CONSUMERS(EXT_3P3V) = { + REGULATOR_SUPPLY("ext_3p3v", NULL), + REGULATOR_SUPPLY("vdd_ana", "3-005b"), + REGULATOR_SUPPLY("vdd_lvds_3p3v", "mipi_dsi.1"), + REGULATOR_SUPPLY("mhl_ext_3p3v", "msm_otg"), +}; +VREG_CONSUMERS(EXT_OTG_SW) = { + REGULATOR_SUPPLY("ext_otg_sw", NULL), + REGULATOR_SUPPLY("vbus_otg", "msm_otg"), +}; + +#define PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, _modes, _ops, \ + _apply_uV, _pull_down, _always_on, _supply_regulator, \ + _system_uA, _enable_time, _reg_id) \ + { \ + .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, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pull_down_enable = _pull_down, \ + .system_uA = _system_uA, \ + .enable_time = _enable_time, \ + } + +#define PM8XXX_LDO(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_NLDO1200(_id, _name, _always_on, _pull_down, _min_uV, \ + _max_uV, _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_SMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_FTSMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_VS(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +#define PM8XXX_VS300(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +#define PM8XXX_NCP(_id, _name, _always_on, _min_uV, _max_uV, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, 0, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, 0, 0, \ + _always_on, _supply_regulator, 0, _enable_time, _reg_id) + +/* Pin control initialization */ +#define PM8XXX_PC(_id, _name, _always_on, _pin_fn, _pin_ctrl, \ + _supply_regulator, _reg_id) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .always_on = _always_on, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id##_PC), \ + .consumer_supplies = vreg_consumers_##_id##_PC, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pin_fn = PM8XXX_VREG_PIN_FN_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +#define GPIO_VREG(_id, _reg_name, _gpio_label, _gpio, _supply_regulator) \ + [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, \ + .supply_regulator = _supply_regulator, \ + }, \ + .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, \ + } + +#define RPM_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, _default_uV, \ + _peak_uA, _avg_uA, _pull_down, _pin_ctrl, _freq, _pin_fn, \ + _force_mode, _sleep_set_force_mode, _power_mode, _state, \ + _sleep_selectable, _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 = _min_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 = RPM_VREG_ID_PM8921_##_id, \ + .default_uV = _default_uV, \ + .peak_uA = _peak_uA, \ + .avg_uA = _avg_uA, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = _pin_ctrl, \ + .freq = RPM_VREG_FREQ_##_freq, \ + .pin_fn = _pin_fn, \ + .force_mode = _force_mode, \ + .sleep_set_force_mode = _sleep_set_force_mode, \ + .power_mode = _power_mode, \ + .state = _state, \ + .sleep_selectable = _sleep_selectable, \ + .system_uA = _system_uA, \ + } + +#define RPM_LDO(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _init_peak_uA) \ + RPM_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, _max_uV, _init_peak_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, RPM_VREG_POWER_MODE_8960_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, _system_uA) + +#define RPM_SMPS(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _freq, _force_mode, \ + _sleep_set_force_mode) \ + RPM_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, _max_uV, _system_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_##_force_mode, \ + RPM_VREG_FORCE_MODE_8960_##_sleep_set_force_mode, \ + RPM_VREG_POWER_MODE_8960_PWM, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on, _supply_regulator, _system_uA) + +#define RPM_VS(_id, _always_on, _pd, _sleep_selectable, _supply_regulator) \ + RPM_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, 0, 1000, 1000, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, RPM_VREG_POWER_MODE_8960_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +#define RPM_NCP(_id, _always_on, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _freq) \ + RPM_INIT(_id, _min_uV, _max_uV, 0, REGULATOR_CHANGE_VOLTAGE \ + | REGULATOR_CHANGE_STATUS, 0, _max_uV, 1000, 1000, 0, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, \ + RPM_VREG_FORCE_MODE_8960_NONE, RPM_VREG_POWER_MODE_8960_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +/* Pin control initialization */ +#define RPM_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 = RPM_VREG_ID_PM8921_##_id##_PC, \ + .pin_fn = RPM_VREG_PIN_FN_8960_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +/* GPIO regulator constraints */ +struct gpio_regulator_platform_data msm_gpio_regulator_pdata[] __devinitdata = { + /* ID vreg_name gpio_label gpio supply */ + GPIO_VREG(EXT_5V, "ext_5v", "ext_5v_en", PM8921_MPP_PM_TO_SYS(7), NULL), + GPIO_VREG(EXT_L2, "ext_l2", "ext_l2_en", 91, NULL), + GPIO_VREG(EXT_3P3V, "ext_3p3v", "ext_3p3v_en", + PM8921_GPIO_PM_TO_SYS(17), NULL), + GPIO_VREG(EXT_OTG_SW, "ext_otg_sw", "ext_otg_sw_en", + PM8921_GPIO_PM_TO_SYS(42), "8921_usb_otg"), +}; + +/* 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", 850000, 1300000); +struct regulator_init_data msm_saw_regulator_pdata_s6 = + SAW_VREG_INIT(S6, "8921_s6", 850000, 1300000); + +/* PM8921 regulator constraints */ +struct pm8xxx_regulator_platform_data +msm_pm8921_regulator_pdata[] __devinitdata = { + /* + * ID name always_on pd min_uV max_uV en_t supply + * system_uA reg_ID + */ + PM8XXX_NLDO1200(L26, "8921_l26", 0, 1, 375000, 1050000, 200, "8921_s7", + 0, 1), + PM8XXX_NLDO1200(L27, "8921_l27", 0, 1, 375000, 1050000, 200, "8921_s7", + 0, 2), + PM8XXX_NLDO1200(L28, "8921_l28", 0, 1, 375000, 1050000, 200, "8921_s7", + 0, 3), + PM8XXX_LDO(L29, "8921_l29", 0, 1, 2050000, 2100000, 200, "8921_s8", + 0, 4), + + /* ID name always_on pd en_t supply reg_ID */ + PM8XXX_VS300(USB_OTG, "8921_usb_otg", 0, 1, 0, "ext_5v", 5), + PM8XXX_VS300(HDMI_MVS, "8921_hdmi_mvs", 0, 1, 0, "ext_5v", 6), +}; + +static struct rpm_regulator_init_data +msm_rpm_regulator_init_data[] __devinitdata = { + /* ID a_on pd ss min_uV max_uV supply sys_uA freq fm ss_fm */ + RPM_SMPS(S1, 1, 1, 0, 1225000, 1225000, NULL, 100000, 3p20, NONE, NONE), + RPM_SMPS(S2, 0, 1, 0, 1300000, 1300000, NULL, 0, 1p60, NONE, NONE), + RPM_SMPS(S3, 0, 1, 1, 500000, 1150000, NULL, 100000, 4p80, NONE, NONE), + RPM_SMPS(S4, 1, 1, 0, 1800000, 1800000, NULL, 100000, 1p60, AUTO, AUTO), + RPM_SMPS(S7, 0, 1, 0, 1150000, 1150000, NULL, 100000, 3p20, NONE, NONE), + RPM_SMPS(S8, 1, 1, 1, 2050000, 2050000, NULL, 100000, 1p60, NONE, NONE), + + /* ID a_on pd ss min_uV max_uV supply sys_uA init_ip */ + RPM_LDO(L1, 1, 1, 0, 1050000, 1050000, "8921_s4", 0, 10000), + RPM_LDO(L2, 0, 1, 0, 1200000, 1200000, "8921_s4", 0, 0), + RPM_LDO(L3, 0, 1, 0, 3075000, 3075000, NULL, 0, 0), + RPM_LDO(L4, 1, 1, 0, 1800000, 1800000, NULL, 10000, 10000), + RPM_LDO(L5, 0, 1, 0, 2950000, 2950000, NULL, 0, 0), + RPM_LDO(L6, 0, 1, 0, 2950000, 2950000, NULL, 0, 0), + RPM_LDO(L7, 1, 1, 0, 1850000, 2950000, NULL, 10000, 10000), + RPM_LDO(L8, 0, 1, 0, 2800000, 3000000, NULL, 0, 0), + RPM_LDO(L9, 0, 1, 0, 3000000, 3000000, NULL, 0, 0), + RPM_LDO(L10, 0, 1, 0, 3000000, 3000000, NULL, 0, 0), + RPM_LDO(L11, 0, 1, 0, 2850000, 2850000, NULL, 0, 0), + RPM_LDO(L12, 0, 1, 0, 1200000, 1200000, "8921_s4", 0, 0), + RPM_LDO(L14, 0, 1, 0, 1800000, 1800000, NULL, 0, 0), + RPM_LDO(L15, 0, 1, 0, 1800000, 2950000, NULL, 0, 0), + RPM_LDO(L16, 0, 1, 0, 2800000, 2800000, NULL, 0, 0), + RPM_LDO(L17, 0, 1, 0, 1800000, 2950000, NULL, 0, 0), + RPM_LDO(L18, 0, 1, 0, 1300000, 1300000, "8921_s4", 0, 0), + RPM_LDO(L21, 0, 1, 0, 1900000, 1900000, "8921_s8", 0, 0), + RPM_LDO(L22, 0, 1, 0, 2750000, 2750000, NULL, 0, 0), + RPM_LDO(L23, 1, 1, 1, 1800000, 1800000, "8921_s8", 10000, 10000), + RPM_LDO(L24, 0, 1, 1, 750000, 1150000, "8921_s1", 10000, 10000), + RPM_LDO(L25, 1, 1, 0, 1250000, 1250000, "8921_s1", 10000, 10000), + + /* ID a_on pd ss supply */ + RPM_VS(LVS1, 0, 1, 0, "8921_s4"), + RPM_VS(LVS2, 0, 1, 0, "8921_s1"), + RPM_VS(LVS3, 0, 1, 0, "8921_s4"), + RPM_VS(LVS4, 0, 1, 0, "8921_s4"), + RPM_VS(LVS5, 0, 1, 0, "8921_s4"), + RPM_VS(LVS6, 0, 1, 0, "8921_s4"), + RPM_VS(LVS7, 0, 1, 0, "8921_s4"), + + /* ID a_on ss min_uV max_uV supply freq */ + RPM_NCP(NCP, 0, 0, 1800000, 1800000, "8921_l6", 1p60), +}; + +int msm_pm8921_regulator_pdata_len __devinitdata = + ARRAY_SIZE(msm_pm8921_regulator_pdata); + +struct rpm_regulator_platform_data msm_rpm_regulator_pdata __devinitdata = { + .init_data = msm_rpm_regulator_init_data, + .num_regulators = ARRAY_SIZE(msm_rpm_regulator_init_data), + .version = RPM_VREG_VERSION_8960, + .vreg_id_vdd_mem = RPM_VREG_ID_PM8921_L24, + .vreg_id_vdd_dig = RPM_VREG_ID_PM8921_S3, +}; diff --git a/arch/arm/mach-msm/board-8960-storage.c b/arch/arm/mach-msm/board-8960-storage.c new file mode 100644 index 0000000000000000000000000000000000000000..1279b75de122f73488fdc0a0f26ee026f8ebe529 --- /dev/null +++ b/arch/arm/mach-msm/board-8960-storage.c @@ -0,0 +1,387 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "devices.h" +#include "board-8960.h" +#include "board-storage-common-a.h" + +/* MSM8960 has 5 SDCC controllers */ +enum sdcc_controllers { + SDCC1, + SDCC2, + SDCC3, + SDCC4, + SDCC5, + MAX_SDCC_CONTROLLER +}; + +/* All SDCC controllers require VDD/VCC voltage */ +static struct msm_mmc_reg_data mmc_vdd_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : eMMC card connected */ + [SDCC1] = { + .name = "sdc_vdd", + .high_vol_level = 2950000, + .low_vol_level = 2950000, + .always_on = 1, + .lpm_sup = 1, + .lpm_uA = 9000, + .hpm_uA = 200000, /* 200mA */ + }, + /* SDCC2 : SDIO slot connected */ + [SDCC2] = { + .name = "sdc_vdd", + .high_vol_level = 1800000, + .low_vol_level = 1800000, + .always_on = 1, + .lpm_sup = 1, + .lpm_uA = 9000, + .hpm_uA = 200000, /* 200mA */ + }, + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .name = "sdc_vdd", + .high_vol_level = 2950000, + .low_vol_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", + .always_on = 1, + .high_vol_level = 1800000, + .low_vol_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", + .high_vol_level = 2950000, + .low_vol_level = 1850000, + .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, + }, + /* SDCC4 : SDIO slot connected */ + [SDCC4] = { + .name = "sdc_vddp", + .high_vol_level = 1800000, + .low_vol_level = 1800000, + .always_on = 1, + .lpm_sup = 1, + .hpm_uA = 200000, /* 200mA */ + .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], + }, + /* SDCC2 : SDIO card slot connected */ + [SDCC2] = { + .vdd_data = &mmc_vdd_reg_data[SDCC2], + }, + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .vdd_data = &mmc_vdd_reg_data[SDCC3], + .vddp_data = &mmc_vddp_reg_data[SDCC3], + }, + /* SDCC4 : SDIO card slot connected */ + [SDCC4] = { + .vddp_data = &mmc_vddp_reg_data[SDCC4], + }, +}; + +/* 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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + {TLMM_PULL_SDC1_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC1_DATA, GPIO_CFG_PULL_UP} +}; + +/* 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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + /* + * SDC3 CMD line should be PULLed UP otherwise fluid platform will + * see transitions (1 -> 0 and 0 -> 1) on card detection line, + * which would result in false card detection interrupts. + */ + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_UP}, + /* + * Keeping DATA lines status to PULL UP will make sure that + * there is no current leak during sleep if external pull up + * is connected to DATA lines. + */ + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_UP} +}; + +static 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) + }, +}; + +static 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_gpio sdc2_gpio[] = { + {92, "sdc2_dat_3"}, + {91, "sdc2_dat_2"}, + {90, "sdc2_dat_1"}, + {89, "sdc2_dat_0"}, + {97, "sdc2_cmd"}, + {98, "sdc2_clk"} +}; + +struct msm_mmc_gpio sdc4_gpio[] = { + {83, "sdc4_dat_3"}, + {84, "sdc4_dat_2"}, + {85, "sdc4_dat_1"}, + {86, "sdc4_dat_0"}, + {87, "sdc4_cmd"}, + {88, "sdc4_clk"} +}; + +struct msm_mmc_gpio_data mmc_gpio_data[MAX_SDCC_CONTROLLER] = { + [SDCC2] = { + .gpio = sdc2_gpio, + .size = ARRAY_SIZE(sdc2_gpio), + }, + [SDCC4] = { + .gpio = sdc4_gpio, + .size = ARRAY_SIZE(sdc4_gpio), + }, +}; + +static 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] + }, +}; + +static struct msm_mmc_pin_data mmc_slot_pin_data[MAX_SDCC_CONTROLLER] = { + [SDCC1] = { + .pad_data = &mmc_pad_data[SDCC1], + }, + [SDCC2] = { + .is_gpio = 1, + .gpio_data = &mmc_gpio_data[SDCC2], + }, + [SDCC3] = { + .pad_data = &mmc_pad_data[SDCC3], + }, + [SDCC4] = { + .is_gpio = 1, + .gpio_data = &mmc_gpio_data[SDCC4], + }, +}; + +#define MSM_MPM_PIN_SDC1_DAT1 17 +#define MSM_MPM_PIN_SDC3_DAT1 21 + +static unsigned int sdc1_sup_clk_rates[] = { + 400000, 24000000, 48000000 +}; + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static unsigned int sdc3_sup_clk_rates[] = { + 400000, 24000000, 48000000, 96000000, 192000000 +}; +#endif + +#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), + .pclk_src_dfab = 1, + .nonremovable = 1, + .vreg_data = &mmc_slot_vreg_data[SDCC1], + .pin_data = &mmc_slot_pin_data[SDCC1], + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC1_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static unsigned int sdc2_sup_clk_rates[] = { + 400000, 24000000, 48000000 +}; + +static struct mmc_platform_data msm8960_sdc2_data = { + .ocr_mask = MMC_VDD_165_195, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .sup_clk_table = sdc2_sup_clk_rates, + .sup_clk_cnt = ARRAY_SIZE(sdc2_sup_clk_rates), + .pclk_src_dfab = 1, + .vreg_data = &mmc_slot_vreg_data[SDCC2], + .pin_data = &mmc_slot_pin_data[SDCC2], + .sdiowakeup_irq = MSM_GPIO_TO_INT(90), + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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), + .pclk_src_dfab = 1, +#ifdef CONFIG_MMC_MSM_SDC3_WP_SUPPORT + .wpswitch_gpio = PM8921_GPIO_PM_TO_SYS(16), +#endif + .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, + .is_status_gpio_active_low = true, +#endif + .xpc_cap = 1, + .uhs_caps = (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_DDR50 | + MMC_CAP_UHS_SDR104 | MMC_CAP_MAX_CURRENT_600), + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC3_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static unsigned int sdc4_sup_clk_rates[] = { + 400000, 24000000, 48000000 +}; + +static struct mmc_platform_data msm8960_sdc4_data = { + .ocr_mask = MMC_VDD_165_195, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .sup_clk_table = sdc4_sup_clk_rates, + .sup_clk_cnt = ARRAY_SIZE(sdc4_sup_clk_rates), + .pclk_src_dfab = 1, + .vreg_data = &mmc_slot_vreg_data[SDCC4], + .pin_data = &mmc_slot_pin_data[SDCC4], + .sdiowakeup_irq = MSM_GPIO_TO_INT(85), + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#endif + +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_SDC2_SUPPORT + /* SDC2: SDIO slot for WLAN*/ + msm_add_sdcc(2, &msm8960_sdc2_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + /* SDC3: External card slot */ + msm_add_sdcc(3, &msm8960_sdc3_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + /* SDC4: SDIO slot for WLAN */ + msm_add_sdcc(4, &msm8960_sdc4_data); +#endif +} diff --git a/arch/arm/mach-msm/board-8960.c b/arch/arm/mach-msm/board-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..898332f594d8c4a437a3ab3fd0d4bb05c010da5b --- /dev/null +++ b/arch/arm/mach-msm/board-8960.c @@ -0,0 +1,3158 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ANDROID_PMEM +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include + +#ifdef CONFIG_WCD9310_CODEC +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "timer.h" +#include "devices.h" +#include "devices-msm8x60.h" +#include "spm.h" +#include "board-8960.h" +#include "pm.h" +#include +#include "rpm_resources.h" +#include +#include "acpuclock.h" +#include "smd_private.h" +#include "pm-boot.h" +#include "msm_watchdog.h" + +static struct platform_device msm_fm_platform_init = { + .name = "iris_fm", + .id = -1, +}; + +#define KS8851_RST_GPIO 89 +#define KS8851_IRQ_GPIO 90 +#define HAP_SHIFT_LVL_OE_GPIO 47 + +#define MHL_GPIO_INT 4 +#define MHL_GPIO_RESET 15 + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + +struct sx150x_platform_data msm8960_sx150x_data[] = { + [SX150X_CAM] = { + .gpio_base = GPIO_CAM_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0, + .io_pulldn_ena = 0xc0, + .io_open_drain_ena = 0x0, + .irq_summary = -1, + }, + [SX150X_LIQUID] = { + .gpio_base = GPIO_LIQUID_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, + }, +}; + +#endif + +#define MSM_PMEM_ADSP_SIZE 0x7800000 +#define MSM_PMEM_AUDIO_SIZE 0x4CF000 +#define MSM_PMEM_SIZE 0x2800000 /* 40 Mbytes */ +#define MSM_LIQUID_PMEM_SIZE 0x4000000 /* 64 Mbytes */ +#define MSM_HDMI_PRIM_PMEM_SIZE 0x4000000 /* 64 Mbytes */ + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x65000 +#ifdef CONFIG_MSM_IOMMU +#define MSM_ION_MM_SIZE 0x3800000 /* Need to be multiple of 64K */ +#define MSM_ION_SF_SIZE 0x0 +#define MSM_ION_QSECOM_SIZE 0x780000 /* (7.5MB) */ +#define MSM_ION_HEAP_NUM 7 +#else +#define MSM_ION_MM_SIZE MSM_PMEM_ADSP_SIZE +#define MSM_ION_SF_SIZE MSM_PMEM_SIZE +#define MSM_ION_QSECOM_SIZE 0x600000 /* (6MB) */ +#define MSM_ION_HEAP_NUM 8 +#endif +#define MSM_ION_MM_FW_SIZE 0x200000 /* (2MB) */ +#define MSM_ION_MFC_SIZE SZ_8K +#define MSM_ION_AUDIO_SIZE MSM_PMEM_AUDIO_SIZE + +#define MSM_LIQUID_ION_MM_SIZE (MSM_ION_MM_SIZE + 0x600000) +#define MSM_LIQUID_ION_SF_SIZE MSM_LIQUID_PMEM_SIZE +#define MSM_HDMI_PRIM_ION_SF_SIZE MSM_HDMI_PRIM_PMEM_SIZE + +#define MSM8960_FIXED_AREA_START 0xa0000000 +#define MAX_FIXED_AREA_SIZE 0x10000000 +#define MSM_MM_FW_SIZE 0x200000 +#define MSM8960_FW_START (MSM8960_FIXED_AREA_START - MSM_MM_FW_SIZE) + +static unsigned msm_ion_sf_size = MSM_ION_SF_SIZE; +#else +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x110C000 +#define MSM_ION_HEAP_NUM 1 +#endif + +#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 unsigned pmem_param_set; +static int __init pmem_size_setup(char *p) +{ + pmem_size = memparse(p, NULL); + pmem_param_set = 1; + 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 +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +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 msm8960_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 msm8960_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 msm8960_android_pmem_audio_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_audio_pdata }, +}; +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + +struct fmem_platform_data msm8960_fmem_pdata = { +}; + +#define DSP_RAM_BASE_8960 0x8da00000 +#define DSP_RAM_SIZE_8960 0x1800000 +static int dspcrashd_pdata_8960 = 0xDEADDEAD; + +static struct resource resources_dspcrashd_8960[] = { + { + .name = "msm_dspcrashd", + .start = DSP_RAM_BASE_8960, + .end = DSP_RAM_BASE_8960 + DSP_RAM_SIZE_8960, + .flags = IORESOURCE_DMA, + }, +}; + +static struct platform_device msm_device_dspcrashd_8960 = { + .name = "msm_dspcrashd", + .num_resources = ARRAY_SIZE(resources_dspcrashd_8960), + .resource = resources_dspcrashd_8960, + .dev = { .platform_data = &dspcrashd_pdata_8960 }, +}; + +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 reserve_rtb_memory(void) +{ +#if defined(CONFIG_MSM_RTB) + msm8960_reserve_table[MEMTYPE_EBI1].size += msm8960_rtb_pdata.size; +#endif +} + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + android_pmem_adsp_pdata.size = pmem_adsp_size; + + if (!pmem_param_set) { + if (machine_is_msm8960_liquid()) + pmem_size = MSM_LIQUID_PMEM_SIZE; + if (msm8960_hdmi_as_primary_selected()) + pmem_size = MSM_HDMI_PRIM_PMEM_SIZE; + } + + android_pmem_pdata.size = pmem_size; + android_pmem_audio_pdata.size = MSM_PMEM_AUDIO_SIZE; +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +} + +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm8960_reserve_table[p->memory_type].size += p->size; +} +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); +#endif + msm8960_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif +} + +static int msm8960_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +#define FMEM_ENABLED 0 + +#ifdef CONFIG_ION_MSM +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +static struct ion_cp_heap_pdata cp_mm_msm8960_ion_pdata = { + .permission_type = IPT_TYPE_MM_CARVEOUT, + .align = SZ_64K, + .reusable = FMEM_ENABLED, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_MIDDLE, + .iommu_map_all = 1, + .iommu_2x_map_domain = VIDEO_DOMAIN, +}; + +static struct ion_cp_heap_pdata cp_mfc_msm8960_ion_pdata = { + .permission_type = IPT_TYPE_MFC_SHAREDMEM, + .align = PAGE_SIZE, + .reusable = 0, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_HIGH, +}; + +static struct ion_co_heap_pdata co_msm8960_ion_pdata = { + .adjacent_mem_id = INVALID_HEAP_ID, + .align = PAGE_SIZE, + .mem_is_fmem = 0, +}; + +static struct ion_co_heap_pdata fw_co_msm8960_ion_pdata = { + .adjacent_mem_id = ION_CP_MM_HEAP_ID, + .align = SZ_128K, + .mem_is_fmem = FMEM_ENABLED, + .fixed_position = FIXED_LOW, +}; +#endif + +/** + * These heaps are listed in the order they will be allocated. Due to + * video hardware restrictions and content protection the FW heap has to + * be allocated adjacent (below) the MM heap and the MFC heap has to be + * allocated after the MM heap to ensure MFC heap is not more than 256MB + * away from the base address of the FW heap. + * However, the order of FW heap and MM heap doesn't matter since these + * two heaps are taken care of by separate code to ensure they are adjacent + * to each other. + * Don't swap the order unless you know what you are doing! + */ +static struct ion_platform_data msm8960_ion_pdata = { + .nr = MSM_ION_HEAP_NUM, + .heaps = { + { + .id = ION_SYSTEM_HEAP_ID, + .type = ION_HEAP_TYPE_SYSTEM, + .name = ION_VMALLOC_HEAP_NAME, + }, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + { + .id = ION_CP_MM_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MM_HEAP_NAME, + .size = MSM_ION_MM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mm_msm8960_ion_pdata, + }, + { + .id = ION_MM_FIRMWARE_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_MM_FIRMWARE_HEAP_NAME, + .size = MSM_ION_MM_FW_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &fw_co_msm8960_ion_pdata, + }, + { + .id = ION_CP_MFC_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MFC_HEAP_NAME, + .size = MSM_ION_MFC_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mfc_msm8960_ion_pdata, + }, +#ifndef CONFIG_MSM_IOMMU + { + .id = ION_SF_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_SF_HEAP_NAME, + .size = MSM_ION_SF_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_msm8960_ion_pdata, + }, +#endif + { + .id = ION_IOMMU_HEAP_ID, + .type = ION_HEAP_TYPE_IOMMU, + .name = ION_IOMMU_HEAP_NAME, + }, + { + .id = ION_QSECOM_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_QSECOM_HEAP_NAME, + .size = MSM_ION_QSECOM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_msm8960_ion_pdata, + }, + { + .id = ION_AUDIO_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_AUDIO_HEAP_NAME, + .size = MSM_ION_AUDIO_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_msm8960_ion_pdata, + }, +#endif + } +}; + +static struct platform_device msm8960_ion_dev = { + .name = "ion-msm", + .id = 1, + .dev = { .platform_data = &msm8960_ion_pdata }, +}; +#endif + +struct platform_device msm8960_fmem_device = { + .name = "fmem", + .id = 1, + .dev = { .platform_data = &msm8960_fmem_pdata }, +}; + +static void __init adjust_mem_for_liquid(void) +{ + unsigned int i; + + if (!pmem_param_set) { + if (machine_is_msm8960_liquid()) + msm_ion_sf_size = MSM_LIQUID_ION_SF_SIZE; + + if (msm8960_hdmi_as_primary_selected()) + msm_ion_sf_size = MSM_HDMI_PRIM_ION_SF_SIZE; + + if (machine_is_msm8960_liquid() || + msm8960_hdmi_as_primary_selected()) { + for (i = 0; i < msm8960_ion_pdata.nr; i++) { + if (msm8960_ion_pdata.heaps[i].id == + ION_SF_HEAP_ID) { + msm8960_ion_pdata.heaps[i].size = + msm_ion_sf_size; + pr_debug("msm_ion_sf_size 0x%x\n", + msm_ion_sf_size); + break; + } + } + } + } +} + +static void __init reserve_mem_for_ion(enum ion_memory_types mem_type, + unsigned long size) +{ + msm8960_reserve_table[mem_type].size += size; +} + +static void __init msm8960_reserve_fixed_area(unsigned long fixed_area_size) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + int ret; + + if (fixed_area_size > MAX_FIXED_AREA_SIZE) + panic("fixed area size is larger than %dM\n", + MAX_FIXED_AREA_SIZE >> 20); + + reserve_info->fixed_area_size = fixed_area_size; + reserve_info->fixed_area_start = MSM8960_FW_START; + + ret = memblock_remove(reserve_info->fixed_area_start, + reserve_info->fixed_area_size); + BUG_ON(ret); +#endif +} + +/** + * Reserve memory for ION and calculate amount of reusable memory for fmem. + * We only reserve memory for heaps that are not reusable. However, we only + * support one reusable heap at the moment so we ignore the reusable flag for + * other than the first heap with reusable flag set. Also handle special case + * for video heaps (MM,FW, and MFC). Video requires heaps MM and MFC to be + * at a higher address than FW in addition to not more than 256MB away from the + * base address of the firmware. This means that if MM is reusable the other + * two heaps must be allocated in the same region as FW. This is handled by the + * mem_is_fmem flag in the platform data. In addition the MM heap must be + * adjacent to the FW heap for content protection purposes. + */ +static void __init reserve_ion_memory(void) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + unsigned int i; + unsigned int reusable_count = 0; + unsigned int fixed_size = 0; + unsigned int fixed_low_size, fixed_middle_size, fixed_high_size; + unsigned long fixed_low_start, fixed_middle_start, fixed_high_start; + + adjust_mem_for_liquid(); + msm8960_fmem_pdata.size = 0; + msm8960_fmem_pdata.reserved_size_low = 0; + msm8960_fmem_pdata.reserved_size_high = 0; + msm8960_fmem_pdata.align = PAGE_SIZE; + fixed_low_size = 0; + fixed_middle_size = 0; + fixed_high_size = 0; + + /* We only support 1 reusable heap. Check if more than one heap + * is specified as reusable and set as non-reusable if found. + */ + for (i = 0; i < msm8960_ion_pdata.nr; ++i) { + const struct ion_platform_heap *heap = + &(msm8960_ion_pdata.heaps[i]); + + if (heap->type == ION_HEAP_TYPE_CP && heap->extra_data) { + struct ion_cp_heap_pdata *data = heap->extra_data; + + reusable_count += (data->reusable) ? 1 : 0; + + if (data->reusable && reusable_count > 1) { + pr_err("%s: Too many heaps specified as " + "reusable. Heap %s was not configured " + "as reusable.\n", __func__, heap->name); + data->reusable = 0; + } + } + } + + for (i = 0; i < msm8960_ion_pdata.nr; ++i) { + struct ion_platform_heap *heap = + &(msm8960_ion_pdata.heaps[i]); + int align = SZ_4K; + int iommu_map_all = 0; + int adjacent_mem_id = INVALID_HEAP_ID; + + if (heap->extra_data) { + int fixed_position = NOT_FIXED; + int mem_is_fmem = 0; + + switch (heap->type) { + case ION_HEAP_TYPE_CP: + mem_is_fmem = ((struct ion_cp_heap_pdata *) + heap->extra_data)->mem_is_fmem; + fixed_position = ((struct ion_cp_heap_pdata *) + heap->extra_data)->fixed_position; + align = ((struct ion_cp_heap_pdata *) + heap->extra_data)->align; + iommu_map_all = + ((struct ion_cp_heap_pdata *) + heap->extra_data)->iommu_map_all; + break; + case ION_HEAP_TYPE_CARVEOUT: + mem_is_fmem = ((struct ion_co_heap_pdata *) + heap->extra_data)->mem_is_fmem; + fixed_position = ((struct ion_co_heap_pdata *) + heap->extra_data)->fixed_position; + adjacent_mem_id = ((struct ion_co_heap_pdata *) + heap->extra_data)->adjacent_mem_id; + break; + default: + break; + } + + if (iommu_map_all) { + if (heap->size & (SZ_64K-1)) { + heap->size = ALIGN(heap->size, SZ_64K); + pr_info("Heap %s not aligned to 64K. Adjusting size to %x\n", + heap->name, heap->size); + } + } + + if (mem_is_fmem && adjacent_mem_id != INVALID_HEAP_ID) + msm8960_fmem_pdata.align = align; + + if (fixed_position != NOT_FIXED) + fixed_size += heap->size; + else + reserve_mem_for_ion(MEMTYPE_EBI1, heap->size); + + if (fixed_position == FIXED_LOW) + fixed_low_size += heap->size; + else if (fixed_position == FIXED_MIDDLE) + fixed_middle_size += heap->size; + else if (fixed_position == FIXED_HIGH) + fixed_high_size += heap->size; + + if (mem_is_fmem) + msm8960_fmem_pdata.size += heap->size; + } + } + + if (!fixed_size) + return; + + if (msm8960_fmem_pdata.size) { + msm8960_fmem_pdata.reserved_size_low = fixed_low_size; + msm8960_fmem_pdata.reserved_size_high = fixed_high_size; + } + + /* Since the fixed area may be carved out of lowmem, + * make sure the length is a multiple of 1M. + */ + fixed_size = (fixed_size + MSM_MM_FW_SIZE + SECTION_SIZE - 1) + & SECTION_MASK; + msm8960_reserve_fixed_area(fixed_size); + + fixed_low_start = MSM8960_FIXED_AREA_START; + fixed_middle_start = fixed_low_start + fixed_low_size; + fixed_high_start = fixed_middle_start + fixed_middle_size; + + for (i = 0; i < msm8960_ion_pdata.nr; ++i) { + struct ion_platform_heap *heap = &(msm8960_ion_pdata.heaps[i]); + + if (heap->extra_data) { + int fixed_position = NOT_FIXED; + + switch (heap->type) { + case ION_HEAP_TYPE_CP: + fixed_position = ((struct ion_cp_heap_pdata *) + heap->extra_data)->fixed_position; + break; + case ION_HEAP_TYPE_CARVEOUT: + fixed_position = ((struct ion_co_heap_pdata *) + heap->extra_data)->fixed_position; + break; + default: + break; + } + + switch (fixed_position) { + case FIXED_LOW: + heap->base = fixed_low_start; + break; + case FIXED_MIDDLE: + heap->base = fixed_middle_start; + break; + case FIXED_HIGH: + heap->base = fixed_high_start; + break; + default: + break; + } + } + } +#endif +} + +static void __init reserve_mdp_memory(void) +{ + msm8960_mdp_writeback(msm8960_reserve_table); +} + +static void __init reserve_cache_dump_memory(void) +{ +#ifdef CONFIG_MSM_CACHE_DUMP + unsigned int total; + + total = msm8960_cache_dump_pdata.l1_size + + msm8960_cache_dump_pdata.l2_size; + msm8960_reserve_table[MEMTYPE_EBI1].size += total; +#endif +} + +static void __init msm8960_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); + reserve_ion_memory(); + reserve_mdp_memory(); + reserve_rtb_memory(); + reserve_cache_dump_memory(); +} + +static struct reserve_info msm8960_reserve_info __initdata = { + .memtype_reserve_table = msm8960_reserve_table, + .calculate_reserve_sizes = msm8960_calculate_reserve_sizes, + .reserve_fixed_area = msm8960_reserve_fixed_area, + .paddr_to_memtype = msm8960_paddr_to_memtype, +}; + +static int msm8960_memory_bank_size(void) +{ + return 1<<29; +} + +static void __init locate_unstable_memory(void) +{ + struct membank *mb = &meminfo.bank[meminfo.nr_banks - 1]; + unsigned long bank_size; + unsigned long low, high; + + bank_size = msm8960_memory_bank_size(); + msm8960_reserve_info.bank_size = bank_size; + + low = meminfo.bank[0].start; + high = mb->start + mb->size; + + /* Check if 32 bit overflow occured */ + if (high < mb->start) + high = ~0UL; + + if (high < MAX_FIXED_AREA_SIZE + MSM8960_FIXED_AREA_START) + panic("fixed area extends beyond end of memory\n"); + + low &= ~(bank_size - 1); + + if (high - low <= bank_size) + goto no_dmm; + +#ifdef CONFIG_ENABLE_DMM + msm8960_reserve_info.low_unstable_address = mb->start - + MIN_MEMORY_BLOCK_SIZE + mb->size; + msm8960_reserve_info.max_unstable_size = MIN_MEMORY_BLOCK_SIZE; + pr_info("low unstable address %lx max size %lx bank size %lx\n", + msm8960_reserve_info.low_unstable_address, + msm8960_reserve_info.max_unstable_size, + msm8960_reserve_info.bank_size); + return; +#endif +no_dmm: + msm8960_reserve_info.low_unstable_address = high; + msm8960_reserve_info.max_unstable_size = 0; +} + +static void __init place_movable_zone(void) +{ +#ifdef CONFIG_ENABLE_DMM + movable_reserved_start = msm8960_reserve_info.low_unstable_address; + movable_reserved_size = msm8960_reserve_info.max_unstable_size; + pr_info("movable zone start %lx size %lx\n", + movable_reserved_start, movable_reserved_size); +#endif +} + +static void __init msm8960_early_memory(void) +{ + reserve_info = &msm8960_reserve_info; + locate_unstable_memory(); + place_movable_zone(); +} + +static char prim_panel_name[PANEL_NAME_MAX_LEN]; +static char ext_panel_name[PANEL_NAME_MAX_LEN]; +static int __init prim_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(prim_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("prim_display", prim_display_setup); + +static int __init ext_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(ext_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("ext_display", ext_display_setup); + +static void __init msm8960_reserve(void) +{ + msm8960_set_display_params(prim_panel_name, ext_panel_name); + msm_reserve(); + if (msm8960_fmem_pdata.size) { +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + if (reserve_info->fixed_area_size) { + msm8960_fmem_pdata.phys = + reserve_info->fixed_area_start + MSM_MM_FW_SIZE; + pr_info("mm fw at %lx (fixed) size %x\n", + reserve_info->fixed_area_start, MSM_MM_FW_SIZE); + pr_info("fmem start %lx (fixed) size %lx\n", + msm8960_fmem_pdata.phys, + msm8960_fmem_pdata.size); + } +#endif + } +} + +static int msm8960_change_memory_power(u64 start, u64 size, + int change_type) +{ + return soc_change_memory_power(start, size, change_type); +} + +static void __init msm8960_allocate_memory_regions(void) +{ + msm8960_allocate_fb_region(); +} + +#ifdef CONFIG_WCD9310_CODEC + +#define TABLA_INTERRUPT_BASE (NR_MSM_IRQS + NR_GPIO_IRQS + NR_PM8921_IRQS) + +/* Micbias setting is based on 8660 CDP/MTP/FLUID requirement + * 4 micbiases are used to power various analog and digital + * microphones operating at 1800 mV. Technically, all micbiases + * can source from single cfilter since all microphones operate + * at the same voltage level. The arrangement below is to make + * sure all cfilters are exercised. LDO_H regulator ouput level + * does not need to be as high as 2.85V. It is choosen for + * microphone sensitivity purpose. + */ +static struct wcd9xxx_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_WCD9XXX_IRQS, + .reset_gpio = PM8921_GPIO_PM_TO_SYS(34), + .micbias = { + .ldoh_v = TABLA_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 2700, + .cfilt3_mv = 1800, + .bias1_cfilt_sel = TABLA_CFILT1_SEL, + .bias2_cfilt_sel = TABLA_CFILT2_SEL, + .bias3_cfilt_sel = TABLA_CFILT3_SEL, + .bias4_cfilt_sel = TABLA_CFILT3_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1250000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1250000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device msm_slim_tabla = { + .name = "tabla-slim", + .e_addr = {0, 1, 0x10, 0, 0x17, 2}, + .dev = { + .platform_data = &tabla_platform_data, + }, +}; + +static struct wcd9xxx_pdata tabla20_platform_data = { + .slimbus_slave_device = { + .name = "tabla-slave", + .e_addr = {0, 0, 0x60, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(62), + .irq_base = TABLA_INTERRUPT_BASE, + .num_irqs = NR_WCD9XXX_IRQS, + .reset_gpio = PM8921_GPIO_PM_TO_SYS(34), + .micbias = { + .ldoh_v = TABLA_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 2700, + .cfilt3_mv = 1800, + .bias1_cfilt_sel = TABLA_CFILT1_SEL, + .bias2_cfilt_sel = TABLA_CFILT2_SEL, + .bias3_cfilt_sel = TABLA_CFILT3_SEL, + .bias4_cfilt_sel = TABLA_CFILT3_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1250000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1250000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device msm_slim_tabla20 = { + .name = "tabla2x-slim", + .e_addr = {0, 1, 0x60, 0, 0x17, 2}, + .dev = { + .platform_data = &tabla20_platform_data, + }, +}; +#endif + +static struct slim_boardinfo msm_slim_devices[] = { +#ifdef CONFIG_WCD9310_CODEC + { + .bus_num = 1, + .slim_slave = &msm_slim_tabla, + }, + { + .bus_num = 1, + .slim_slave = &msm_slim_tabla20, + }, +#endif + /* add more slimbus slaves as needed */ +}; + +#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, + }, + { + .start = 84, + .end = 88, + .name = "wcnss_gpios_5wire", + .flags = IORESOURCE_IO, + }, +}; + +static struct qcom_wcnss_opts qcom_wcnss_pdata = { + .has_48mhz_xo = 1, +}; + +static struct platform_device msm_device_wcnss_wlan = { + .name = "wcnss_wlan", + .id = 0, + .num_resources = ARRAY_SIZE(resources_wcnss_wlan), + .resource = resources_wcnss_wlan, + .dev = {.platform_data = &qcom_wcnss_pdata}, +}; + +#ifdef CONFIG_QSEECOM +/* qseecom bus scaling */ +static struct msm_bus_vectors qseecom_clks_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 0, + .ab = 0, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = 0, + .ab = 0, + }, +}; + +static struct msm_bus_vectors qseecom_enable_dfab_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = (492 * 8) * 1000000UL, + .ab = (492 * 8) * 100000UL, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = 0, + .ab = 0, + }, +}; + +static struct msm_bus_vectors qseecom_enable_sfpb_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 0, + .ab = 0, + }, + { + .src = MSM_BUS_MASTER_SPDM, + .dst = MSM_BUS_SLAVE_SPDM, + .ib = (64 * 8) * 1000000UL, + .ab = (64 * 8) * 100000UL, + }, +}; + +static struct msm_bus_paths qseecom_hw_bus_scale_usecases[] = { + { + ARRAY_SIZE(qseecom_clks_init_vectors), + qseecom_clks_init_vectors, + }, + { + ARRAY_SIZE(qseecom_enable_dfab_vectors), + qseecom_enable_sfpb_vectors, + }, + { + ARRAY_SIZE(qseecom_enable_sfpb_vectors), + qseecom_enable_sfpb_vectors, + }, +}; + +static struct msm_bus_scale_pdata qseecom_bus_pdata = { + qseecom_hw_bus_scale_usecases, + ARRAY_SIZE(qseecom_hw_bus_scale_usecases), + .name = "qsee", +}; + +static struct platform_device qseecom_device = { + .name = "qseecom", + .id = 0, + .dev = { + .platform_data = &qseecom_bus_pdata, + }, +}; +#endif + +#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 + +/* Begin Bus scaling definitions */ +static struct msm_bus_vectors crypto_hw_init_vectors[] = { + { + .src = MSM_BUS_MASTER_ADM_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_ADM_PORT1, + .dst = MSM_BUS_SLAVE_GSBI1_UART, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors crypto_hw_active_vectors[] = { + { + .src = MSM_BUS_MASTER_ADM_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 70000000UL, + .ib = 70000000UL, + }, + { + .src = MSM_BUS_MASTER_ADM_PORT1, + .dst = MSM_BUS_SLAVE_GSBI1_UART, + .ab = 2480000000UL, + .ib = 2480000000UL, + }, +}; + +static struct msm_bus_paths crypto_hw_bus_scale_usecases[] = { + { + ARRAY_SIZE(crypto_hw_init_vectors), + crypto_hw_init_vectors, + }, + { + ARRAY_SIZE(crypto_hw_active_vectors), + crypto_hw_active_vectors, + }, +}; + +static struct msm_bus_scale_pdata crypto_hw_bus_scale_pdata = { + crypto_hw_bus_scale_usecases, + ARRAY_SIZE(crypto_hw_bus_scale_usecases), + .name = "cryptohw", +}; +/* End Bus Scaling Definitions*/ + +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, + .bus_scale_table = &crypto_hw_bus_scale_pdata, +}; + +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, + .bus_scale_table = &crypto_hw_bus_scale_pdata, +}; + +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 struct mdm_platform_data sglte_platform_data = { + .mdm_version = "4.0", + .ramdump_delay_ms = 1000, + .soft_reset_inverted = 1, + .peripheral_platform_device = NULL, +}; + +#define MSM_SHARED_RAM_PHYS 0x80000000 + +static void __init msm8960_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_msm8960_io(); + + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); +} + +static void __init msm8960_init_irq(void) +{ + struct msm_mpm_device_data *data = NULL; + +#ifdef CONFIG_MSM_MPM + data = &msm8960_mpm_dev_data; +#endif + + msm_mpm_irq_extn_init(data); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); +} + +static void __init msm8960_init_buses(void) +{ +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_rpm_set_mt_mask(); + msm_bus_8960_apps_fabric_pdata.rpm_enabled = 1; + msm_bus_8960_sys_fabric_pdata.rpm_enabled = 1; + msm_bus_8960_mm_fabric_pdata.rpm_enabled = 1; + msm_bus_apps_fabric.dev.platform_data = + &msm_bus_8960_apps_fabric_pdata; + msm_bus_sys_fabric.dev.platform_data = &msm_bus_8960_sys_fabric_pdata; + msm_bus_mm_fabric.dev.platform_data = &msm_bus_8960_mm_fabric_pdata; + msm_bus_sys_fpb.dev.platform_data = &msm_bus_8960_sys_fpb_pdata; + msm_bus_cpss_fpb.dev.platform_data = &msm_bus_8960_cpss_fpb_pdata; +#endif +} + +static struct msm_spi_platform_data msm8960_qup_spi_gsbi1_pdata = { + .max_clock_speed = 15060000, +}; + +#ifdef CONFIG_USB_MSM_OTG_72K +static struct msm_otg_platform_data msm_otg_pdata; +#else +static int wr_phy_init_seq[] = { + 0x44, 0x80, /* set VBUS valid threshold + and disconnect valid threshold */ + 0x38, 0x81, /* update DC voltage level */ + 0x14, 0x82, /* set preemphasis and rise/fall time */ + 0x13, 0x83, /* set source impedance adjusment */ + -1}; + +static int liquid_v1_phy_init_seq[] = { + 0x44, 0x80,/* set VBUS valid threshold + and disconnect valid threshold */ + 0x3C, 0x81,/* update DC voltage level */ + 0x18, 0x82,/* set preemphasis and rise/fall time */ + 0x23, 0x83,/* set source impedance sdjusment */ + -1}; + +#ifdef CONFIG_MSM_BUS_SCALING +/* Bandwidth requests (zero) if no vote placed */ +static struct msm_bus_vectors usb_init_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +/* Bus bandwidth requests in Bytes/sec */ +static struct msm_bus_vectors usb_max_vectors[] = { + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 60000000, /* At least 480Mbps on bus. */ + .ib = 960000000, /* MAX bursts rate */ + }, +}; + +static struct msm_bus_paths usb_bus_scale_usecases[] = { + { + ARRAY_SIZE(usb_init_vectors), + usb_init_vectors, + }, + { + ARRAY_SIZE(usb_max_vectors), + usb_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata usb_bus_scale_pdata = { + usb_bus_scale_usecases, + ARRAY_SIZE(usb_bus_scale_usecases), + .name = "usb", +}; +#endif + +static struct msm_otg_platform_data msm_otg_pdata = { + .mode = USB_OTG, + .otg_control = OTG_PMIC_CONTROL, + .phy_type = SNPS_28NM_INTEGRATED_PHY, + .pmic_id_irq = PM8921_USB_ID_IN_IRQ(PM8921_IRQ_BASE), + .power_budget = 750, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &usb_bus_scale_pdata, +#endif +}; +#endif + +#ifdef CONFIG_USB_EHCI_MSM_HSIC +#define HSIC_HUB_RESET_GPIO 91 +static struct msm_hsic_host_platform_data msm_hsic_pdata = { + .strobe = 150, + .data = 151, +}; + +static struct smsc_hub_platform_data hsic_hub_pdata = { + .hub_reset = HSIC_HUB_RESET_GPIO, +}; +#else +static struct msm_hsic_host_platform_data msm_hsic_pdata; +static struct smsc_hub_platform_data hsic_hub_pdata; +#endif + +static struct platform_device smsc_hub_device = { + .name = "msm_smsc_hub", + .id = -1, + .dev = { + .platform_data = &hsic_hub_pdata, + }, +}; + +#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) +{ + 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; + strlcpy(dload->serial_number, snum, SERIAL_NUMBER_LENGTH); +out: + 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, + }, +}; + +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_CFG] = 0x1F, +#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] = 0x02020204, + .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_CFG] = 0x1F, +#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] = 0x02020204, + .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_SPM_CTL] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DLY] = 0x02020204, + .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 PM_HAP_EN_GPIO PM8921_GPIO_PM_TO_SYS(33) +#define PM_HAP_LEN_GPIO PM8921_GPIO_PM_TO_SYS(20) + +static struct msm_xo_voter *xo_handle_d1; + +static int isa1200_power(int on) +{ + int rc = 0; + + gpio_set_value(HAP_SHIFT_LVL_OE_GPIO, !!on); + + rc = on ? msm_xo_mode_vote(xo_handle_d1, MSM_XO_MODE_ON) : + msm_xo_mode_vote(xo_handle_d1, MSM_XO_MODE_OFF); + if (rc < 0) { + pr_err("%s: failed to %svote for TCXO D1 buffer%d\n", + __func__, on ? "" : "de-", rc); + goto err_xo_vote; + } + + return 0; + +err_xo_vote: + gpio_set_value(HAP_SHIFT_LVL_OE_GPIO, !on); + return rc; +} + +static int isa1200_dev_setup(bool enable) +{ + int rc = 0; + + struct pm_gpio hap_gpio_config = { + .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, + }; + + if (enable == true) { + rc = pm8xxx_gpio_config(PM_HAP_EN_GPIO, &hap_gpio_config); + if (rc) { + pr_err("%s: pm8921 gpio %d config failed(%d)\n", + __func__, PM_HAP_EN_GPIO, rc); + return rc; + } + + rc = pm8xxx_gpio_config(PM_HAP_LEN_GPIO, &hap_gpio_config); + if (rc) { + pr_err("%s: pm8921 gpio %d config failed(%d)\n", + __func__, PM_HAP_LEN_GPIO, rc); + return rc; + } + + rc = gpio_request(HAP_SHIFT_LVL_OE_GPIO, "hap_shft_lvl_oe"); + if (rc) { + pr_err("%s: unable to request gpio %d (%d)\n", + __func__, HAP_SHIFT_LVL_OE_GPIO, rc); + return rc; + } + + rc = gpio_direction_output(HAP_SHIFT_LVL_OE_GPIO, 0); + if (rc) { + pr_err("%s: Unable to set direction\n", __func__); + goto free_gpio; + } + + xo_handle_d1 = msm_xo_get(MSM_XO_TCXO_D1, "isa1200"); + if (IS_ERR(xo_handle_d1)) { + rc = PTR_ERR(xo_handle_d1); + pr_err("%s: failed to get the handle for D1(%d)\n", + __func__, rc); + goto gpio_set_dir; + } + } else { + gpio_free(HAP_SHIFT_LVL_OE_GPIO); + + msm_xo_put(xo_handle_d1); + } + + return 0; + +gpio_set_dir: + gpio_set_value(HAP_SHIFT_LVL_OE_GPIO, 0); +free_gpio: + gpio_free(HAP_SHIFT_LVL_OE_GPIO); + return rc; +} + +static struct isa1200_regulator isa1200_reg_data[] = { + { + .name = "vcc_i2c", + .min_uV = ISA_I2C_VTG_MIN_UV, + .max_uV = ISA_I2C_VTG_MAX_UV, + .load_uA = ISA_I2C_CURR_UA, + }, +}; + +static struct isa1200_platform_data isa1200_1_pdata = { + .name = "vibrator", + .dev_setup = isa1200_dev_setup, + .power_on = isa1200_power, + .hap_en_gpio = PM_HAP_EN_GPIO, + .hap_len_gpio = PM_HAP_LEN_GPIO, + .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, + .regulator_info = isa1200_reg_data, + .num_regulators = ARRAY_SIZE(isa1200_reg_data), +}; + +static struct i2c_board_info msm_isa1200_board_info[] __initdata = { + { + I2C_BOARD_INFO("isa1200_1", 0x90>>1), + }, +}; + +#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) +{ + 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"); +} + +static struct kobj_attribute tma340_vkeys_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = &tma340_vkeys_show, +}; + +static struct attribute *tma340_properties_attrs[] = { + &tma340_vkeys_attr.attr, + NULL +}; + +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, + .hpm_load_uA = CY_TMA300_CURR_24HZ_UA, + .lpm_load_uA = CY_TMA300_SLEEP_CURR_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, + .hpm_load_uA = CY_I2C_CURR_UA, + .lpm_load_uA = CY_I2C_SLEEP_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_DEEP_SLEEP_SEL | CY_USE_LOW_POWER_SEL, + .use_gestures = CY_USE_GESTURES, + .fw_fname = "cyttsp_8960_cdp.hex", + /* 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 = 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, + .correct_fw_ver = 9, +}; + +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 */ + }, +}; + +/* configuration data for mxt1386 */ +static const u8 mxt1386_config_data[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 11, 2, 0, 11, 11, 11, 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, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T7 Object */ + 100, 16, 50, + /* T8 Object */ + 8, 0, 0, 0, 0, 0, 8, 14, 50, 215, + /* T9 Object */ + 131, 0, 0, 26, 42, 0, 32, 63, 3, 5, + 0, 2, 1, 113, 10, 10, 8, 10, 255, 2, + 85, 5, 0, 0, 20, 20, 75, 25, 202, 29, + 10, 10, 45, 46, + /* T15 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + /* T18 Object */ + 0, 0, + /* T22 Object */ + 5, 0, 0, 0, 0, 0, 0, 0, 30, 0, + 0, 0, 5, 8, 10, 13, 0, + /* T24 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T25 Object */ + 3, 0, 188, 52, 52, 33, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T27 Object */ + 0, 0, 0, 0, 0, 0, 0, + /* T28 Object */ + 0, 0, 0, 8, 12, 60, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T41 Object */ + 0, 0, 0, 0, 0, 0, + /* T43 Object */ + 0, 0, 0, 0, 0, 0, +}; + +/* configuration data for mxt1386e using V1.0 firmware */ +static const u8 mxt1386e_config_data_v1_0[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 12, 1, 0, 17, 1, 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, 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, + /* T7 Object */ + 100, 16, 50, + /* T8 Object */ + 25, 0, 20, 20, 0, 0, 20, 50, 0, 0, + /* T9 Object */ + 131, 0, 0, 26, 42, 0, 32, 80, 2, 5, + 0, 5, 5, 0, 10, 30, 10, 10, 255, 2, + 85, 5, 10, 10, 10, 10, 135, 55, 70, 40, + 10, 5, 0, 0, 0, + /* T18 Object */ + 0, 0, + /* T24 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T25 Object */ + 3, 0, 60, 115, 156, 99, + /* T27 Object */ + 0, 0, 0, 0, 0, 0, 0, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T42 Object */ + 2, 0, 255, 0, 255, 0, 0, 0, 0, 0, + /* T43 Object */ + 0, 0, 0, 0, 0, 0, 0, + /* T46 Object */ + 64, 0, 20, 20, 0, 0, 0, 0, 0, + /* T47 Object */ + 0, 0, 0, 0, 0, 0, 3, 64, 66, 0, + /* T48 Object */ + 31, 64, 64, 0, 0, 0, 0, 0, 0, 0, + 48, 40, 0, 10, 10, 0, 0, 100, 10, 80, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 52, 0, 12, 0, 17, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T56 Object */ + 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, + 2, 99, 33, +}; + +/* configuration data for mxt1386e using V2.1 firmware */ +static const u8 mxt1386e_config_data_v2_1[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 12, 3, 0, 24, 5, 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, 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, + /* T7 Object */ + 100, 16, 50, + /* T8 Object */ + 25, 0, 20, 20, 0, 0, 20, 50, 0, 0, + /* T9 Object */ + 139, 0, 0, 26, 42, 0, 32, 80, 2, 5, + 0, 5, 5, 0, 10, 30, 10, 10, 255, 2, + 85, 5, 10, 10, 10, 10, 135, 55, 70, 40, + 10, 5, 0, 0, 0, + /* T18 Object */ + 0, 0, + /* T24 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T25 Object */ + 1, 0, 60, 115, 156, 99, + /* T27 Object */ + 0, 0, 0, 0, 0, 0, 0, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T42 Object */ + 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, + /* T43 Object */ + 0, 0, 0, 0, 0, 0, 0, 64, 0, 8, + 16, + /* T46 Object */ + 64, 0, 20, 20, 0, 0, 0, 0, 0, + /* T47 Object */ + 0, 0, 0, 0, 0, 0, 3, 64, 66, 0, + /* T48 Object */ + 1, 64, 64, 0, 0, 0, 0, 0, 0, 0, + 48, 40, 0, 10, 10, 0, 0, 100, 10, 80, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 52, 0, 12, 0, 17, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T56 Object */ + 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, + 2, 99, 33, 0, 149, 24, 193, 255, 255, 255, + 255, +}; + +/* configuration data for mxt1386e on 3D SKU using V2.1 firmware */ +static const u8 mxt1386e_config_data_3d[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 13, 1, 0, 23, 2, 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, 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, + /* T7 Object */ + 100, 10, 50, + /* T8 Object */ + 25, 0, 20, 20, 0, 0, 0, 0, 0, 0, + /* T9 Object */ + 131, 0, 0, 26, 42, 0, 32, 80, 2, 5, + 0, 5, 5, 0, 10, 30, 10, 10, 175, 4, + 127, 7, 26, 21, 17, 19, 143, 35, 207, 40, + 20, 5, 54, 49, 0, + /* T18 Object */ + 0, 0, + /* T24 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T25 Object */ + 0, 0, 72, 113, 168, 97, + /* T27 Object */ + 0, 0, 0, 0, 0, 0, 0, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T42 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T43 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + /* T46 Object */ + 68, 0, 16, 16, 0, 0, 0, 0, 0, + /* T47 Object */ + 0, 0, 0, 0, 0, 0, 3, 64, 66, 0, + /* T48 Object */ + 31, 64, 64, 0, 0, 0, 0, 0, 0, 0, + 32, 50, 0, 10, 10, 0, 0, 100, 10, 90, + 0, 0, 0, 0, 0, 0, 0, 10, 1, 30, + 52, 10, 5, 0, 33, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T56 Object */ + 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, 0, 0, 0, + 0, +}; + +#define MXT_TS_GPIO_IRQ 11 +#define MXT_TS_LDO_EN_GPIO 50 +#define MXT_TS_RESET_GPIO 52 + +static void mxt_init_hw_liquid(void) +{ + int rc; + + rc = gpio_request(MXT_TS_LDO_EN_GPIO, "mxt_ldo_en_gpio"); + if (rc) { + pr_err("%s: unable to request mxt_ldo_en_gpio [%d]\n", + __func__, MXT_TS_LDO_EN_GPIO); + return; + } + + rc = gpio_direction_output(MXT_TS_LDO_EN_GPIO, 1); + if (rc) { + pr_err("%s: unable to set_direction for mxt_ldo_en_gpio [%d]\n", + __func__, MXT_TS_LDO_EN_GPIO); + goto err_ldo_gpio_req; + } + + return; + +err_ldo_gpio_req: + gpio_free(MXT_TS_LDO_EN_GPIO); +} + +static struct mxt_config_info mxt_config_array_2d[] = { + { + .config = mxt1386_config_data, + .config_length = ARRAY_SIZE(mxt1386_config_data), + .family_id = 0xA0, + .variant_id = 0x0, + .version = 0x10, + .build = 0xAA, + .bootldr_id = MXT_BOOTLOADER_ID_1386, + }, + { + .config = mxt1386e_config_data_v1_0, + .config_length = ARRAY_SIZE(mxt1386e_config_data_v1_0), + .family_id = 0xA0, + .variant_id = 0x2, + .version = 0x10, + .build = 0xAA, + .bootldr_id = MXT_BOOTLOADER_ID_1386E, + .fw_name = "atmel_8960_liquid_v2_2_AA.hex", + }, + { + .config = mxt1386e_config_data_v2_1, + .config_length = ARRAY_SIZE(mxt1386e_config_data_v2_1), + .family_id = 0xA0, + .variant_id = 0x7, + .version = 0x21, + .build = 0xAA, + .bootldr_id = MXT_BOOTLOADER_ID_1386E, + .fw_name = "atmel_8960_liquid_v2_2_AA.hex", + }, + { + /* The config data for V2.2.AA is the same as for V2.1.AA */ + .config = mxt1386e_config_data_v2_1, + .config_length = ARRAY_SIZE(mxt1386e_config_data_v2_1), + .family_id = 0xA0, + .variant_id = 0x7, + .version = 0x22, + .build = 0xAA, + .bootldr_id = MXT_BOOTLOADER_ID_1386E, + }, +}; + +static struct mxt_platform_data mxt_platform_data_2d = { + .config_array = mxt_config_array_2d, + .config_array_size = ARRAY_SIZE(mxt_config_array_2d), + .panel_minx = 0, + .panel_maxx = 1365, + .panel_miny = 0, + .panel_maxy = 767, + .disp_minx = 0, + .disp_maxx = 1365, + .disp_miny = 0, + .disp_maxy = 767, + .irqflags = IRQF_TRIGGER_FALLING, + .i2c_pull_up = true, + .reset_gpio = MXT_TS_RESET_GPIO, + .irq_gpio = MXT_TS_GPIO_IRQ, +}; + +static struct mxt_config_info mxt_config_array_3d[] = { + { + .config = mxt1386e_config_data_3d, + .config_length = ARRAY_SIZE(mxt1386e_config_data_3d), + .family_id = 0xA0, + .variant_id = 0x7, + .version = 0x21, + .build = 0xAA, + }, +}; + +static struct mxt_platform_data mxt_platform_data_3d = { + .config_array = mxt_config_array_3d, + .config_array_size = ARRAY_SIZE(mxt_config_array_3d), + .panel_minx = 0, + .panel_maxx = 1919, + .panel_miny = 0, + .panel_maxy = 1199, + .disp_minx = 0, + .disp_maxx = 1919, + .disp_miny = 0, + .disp_maxy = 1199, + .irqflags = IRQF_TRIGGER_FALLING, + .i2c_pull_up = true, + .reset_gpio = MXT_TS_RESET_GPIO, + .irq_gpio = MXT_TS_GPIO_IRQ, +}; + +static struct i2c_board_info mxt_device_info[] __initdata = { + { + I2C_BOARD_INFO("atmel_mxt_ts", 0x5b), + .irq = MSM_GPIO_TO_INT(MXT_TS_GPIO_IRQ), + }, +}; + +#ifdef CONFIG_FB_MSM_HDMI_MHL_8334 +static void mhl_sii_reset_gpio(int on) +{ + gpio_set_value(MHL_GPIO_RESET, on); + return; +} + +/* + * Request for GPIO allocations + * Set appropriate GPIO directions + */ +static int mhl_sii_gpio_setup(int on) +{ + int ret; + + if (on) { + ret = gpio_request(MHL_GPIO_RESET, "W_RST#"); + if (ret < 0) { + pr_err("GPIO RESET request failed: %d\n", ret); + return -EBUSY; + } + ret = gpio_direction_output(MHL_GPIO_RESET, 1); + if (ret < 0) { + pr_err("SET GPIO RESET direction failed: %d\n", ret); + gpio_free(MHL_GPIO_RESET); + return -EBUSY; + } + ret = gpio_request(MHL_GPIO_INT, "W_INT"); + if (ret < 0) { + pr_err("GPIO INT request failed: %d\n", ret); + gpio_free(MHL_GPIO_RESET); + return -EBUSY; + } + ret = gpio_direction_input(MHL_GPIO_INT); + if (ret < 0) { + pr_err("SET GPIO INTR direction failed: %d\n", ret); + gpio_free(MHL_GPIO_RESET); + gpio_free(MHL_GPIO_INT); + return -EBUSY; + } + } else { + gpio_free(MHL_GPIO_RESET); + gpio_free(MHL_GPIO_INT); + } + + return 0; +} + +static struct msm_mhl_platform_data mhl_platform_data = { + .irq = MSM_GPIO_TO_INT(4), + .gpio_setup = mhl_sii_gpio_setup, + .reset_pin = mhl_sii_reset_gpio, +}; +#endif + +static struct i2c_board_info sii_device_info[] __initdata = { + { +#ifdef CONFIG_FB_MSM_HDMI_MHL_8334 + /* + * keeps SI 8334 as the default + * MHL TX + */ + I2C_BOARD_INFO("sii8334", 0x39), + .platform_data = &mhl_platform_data, +#endif +#ifdef CONFIG_FB_MSM_HDMI_MHL_9244 + I2C_BOARD_INFO("Sil-9244", 0x39), + .irq = MSM_GPIO_TO_INT(15), +#endif /* CONFIG_MSM_HDMI_MHL */ + .flags = I2C_CLIENT_WAKE, + }, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi4_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi3_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi10_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi12_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +static struct msm_pm_sleep_status_data msm_pm_slp_sts_data = { + .base_addr = MSM_ACC0_BASE + 0x08, + .cpu_offset = MSM_ACC1_BASE - MSM_ACC0_BASE, + .mask = 1UL << 13, +}; + +static struct ks8851_pdata spi_eth_pdata = { + .irq_gpio = KS8851_IRQ_GPIO, + .rst_gpio = KS8851_RST_GPIO, +}; + +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, + .platform_data = &spi_eth_pdata + }, + { + .modalias = "dsi_novatek_3d_panel_spi", + .max_speed_hz = 10800000, + .bus_num = 0, + .chip_select = 1, + .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, + }, +}; + +static struct tsens_platform_data msm_tsens_pdata = { + .slope = {910, 910, 910, 910, 910}, + .tsens_factor = 1000, + .hw_type = MSM_8960, + .tsens_num_sensor = 5, +}; + +static struct platform_device msm_tsens_device = { + .name = "tsens8960-tm", + .id = -1, +}; + +#ifdef CONFIG_MSM_FAKE_BATTERY +static struct platform_device fish_battery_device = { + .name = "fish_battery", +}; +#endif + +static 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], + }, +}; + +static 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 msm8960_device_ext_3p3v_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8921_GPIO_PM_TO_SYS(17), + .dev = { + .platform_data = + &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_3P3V], + }, +}; + +static struct platform_device msm8960_device_ext_otg_sw_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8921_GPIO_PM_TO_SYS(42), + .dev = { + .platform_data = + &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_OTG_SW], + }, +}; + +static struct platform_device msm8960_device_rpm_regulator __devinitdata = { + .name = "rpm-regulator", + .id = -1, + .dev = { + .platform_data = &msm_rpm_regulator_pdata, + }, +}; +#ifdef CONFIG_SERIAL_MSM_HS +static int configure_uart_gpios(int on) +{ + int ret = 0, i; + int uart_gpios[] = {93, 94, 95, 96}; + + for (i = 0; i < ARRAY_SIZE(uart_gpios); i++) { + if (on) { + ret = gpio_request(uart_gpios[i], NULL); + if (ret) { + pr_err("%s: unable to request uart gpio[%d]\n", + __func__, uart_gpios[i]); + break; + } + } else { + gpio_free(uart_gpios[i]); + } + } + + if (ret && on && i) + for (; i >= 0; i--) + gpio_free(uart_gpios[i]); + return ret; +} + +static struct msm_serial_hs_platform_data msm_uart_dm9_pdata = { + .gpio_config = configure_uart_gpios, +}; +#else +static struct msm_serial_hs_platform_data msm_uart_dm9_pdata; +#endif + +static struct platform_device *common_devices[] __initdata = { + &msm8960_device_dmov, + &msm_device_smd, + &msm_device_uart_dm6, + &msm_device_uart_dm9, + &msm_device_saw_core0, + &msm_device_saw_core1, + &msm8960_device_ext_5v_vreg, + &msm8960_device_ssbi_pmic, + &msm8960_device_ext_otg_sw_vreg, + &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_QSEECOM) + &qseecom_device, +#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_ROTATOR + &msm_rotator_device, +#endif + &msm_device_sps, +#ifdef CONFIG_MSM_FAKE_BATTERY + &fish_battery_device, +#endif + &msm8960_fmem_device, +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + &msm8960_android_pmem_device, + &msm8960_android_pmem_adsp_device, + &msm8960_android_pmem_audio_device, +#endif +#endif + &msm_device_vidc, + &msm_device_bam_dmux, + &msm_fm_platform_init, + +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) +#ifdef CONFIG_MSM_USE_TSIF1 + &msm_device_tsif[1], +#else + &msm_device_tsif[0], +#endif +#endif + +#ifdef CONFIG_HW_RANDOM_MSM + &msm_device_rng, +#endif +#ifdef CONFIG_ION_MSM + &msm8960_ion_dev, +#endif + &msm8960_rpm_device, + &msm8960_rpm_log_device, + &msm8960_rpm_stat_device, + &msm_device_tz_log, +#ifdef CONFIG_MSM_QDSS + &msm_qdss_device, + &msm_etb_device, + &msm_tpiu_device, + &msm_funnel_device, + &msm_etm_device, +#endif + &msm_device_dspcrashd_8960, + &msm8960_device_watchdog, + &msm8960_rtb_device, + &msm8960_cpu_idle_device, + &msm8960_msm_gov_device, + &msm8960_device_cache_erp, + &msm8960_cache_dump_device, + &msm8960_iommu_domain_device, + &msm_tsens_device, +}; + +static struct platform_device *sim_devices[] __initdata = { + &msm8960_device_uart_gsbi5, + &msm8960_device_otg, + &msm8960_device_gadget_peripheral, + &msm_device_hsusb_host, + &msm_device_hsic_host, + &android_usb_device, + &msm_device_vidc, + &msm_bus_apps_fabric, + &msm_bus_sys_fabric, + &msm_bus_mm_fabric, + &msm_bus_sys_fpb, + &msm_bus_cpss_fpb, + &msm_pcm, + &msm_multi_ch_pcm, + &msm_pcm_routing, + &msm_cpudai0, + &msm_cpudai1, + &msm8960_cpudai_slimbus_2_tx, + &msm_cpudai_hdmi_rx, + &msm_cpudai_bt_rx, + &msm_cpudai_bt_tx, + &msm_cpudai_fm_rx, + &msm_cpudai_fm_tx, + &msm_cpudai_auxpcm_rx, + &msm_cpudai_auxpcm_tx, + &msm_cpu_fe, + &msm_stub_codec, + &msm_voice, + &msm_voip, + &msm_lpa_pcm, + &msm_compr_dsp, + &msm_cpudai_incall_music_rx, + &msm_cpudai_incall_record_rx, + &msm_cpudai_incall_record_tx, + +#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, +#ifdef CONFIG_MSM_GEMINI + &msm8960_gemini_device, +#endif +}; + +static struct platform_device *cdp_devices[] __initdata = { + &msm_8960_q6_lpass, + &msm_8960_q6_mss_fw, + &msm_8960_q6_mss_sw, + &msm_8960_riva, + &msm_pil_tzapps, + &msm_pil_dsps, + &msm_pil_vidc, + &msm8960_device_otg, + &msm8960_device_gadget_peripheral, + &msm_device_hsusb_host, + &android_usb_device, + &msm_pcm, + &msm_multi_ch_pcm, + &msm_pcm_routing, + &msm_cpudai0, + &msm_cpudai1, + &msm8960_cpudai_slimbus_2_tx, + &msm_cpudai_hdmi_rx, + &msm_cpudai_bt_rx, + &msm_cpudai_bt_tx, + &msm_cpudai_fm_rx, + &msm_cpudai_fm_tx, + &msm_cpudai_auxpcm_rx, + &msm_cpudai_auxpcm_tx, + &msm_cpu_fe, + &msm_stub_codec, + &msm_kgsl_3d0, +#ifdef CONFIG_MSM_KGSL_2D + &msm_kgsl_2d0, + &msm_kgsl_2d1, +#endif +#ifdef CONFIG_MSM_GEMINI + &msm8960_gemini_device, +#endif + &msm_voice, + &msm_voip, + &msm_lpa_pcm, + &msm_cpudai_afe_01_rx, + &msm_cpudai_afe_01_tx, + &msm_cpudai_afe_02_rx, + &msm_cpudai_afe_02_tx, + &msm_pcm_afe, + &msm_compr_dsp, + &msm_cpudai_incall_music_rx, + &msm_cpudai_incall_record_rx, + &msm_cpudai_incall_record_tx, + &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 void __init msm8960_gfx_init(void) +{ + uint32_t soc_platform_version = socinfo_get_version(); + if (SOCINFO_VERSION_MAJOR(soc_platform_version) == 1) { + struct kgsl_device_platform_data *kgsl_3d0_pdata = + msm_kgsl_3d0.dev.platform_data; + kgsl_3d0_pdata->pwrlevel[0].gpu_freq = 320000000; + kgsl_3d0_pdata->pwrlevel[1].gpu_freq = 266667000; + } +} + +static struct msm_rpmrs_level msm_rpmrs_levels[] = { + { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1, 784, 180000, 100, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1300, 228, 1200000, 2000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, GDHS, MAX, ACTIVE), + false, + 2000, 138, 1208400, 3200, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 6000, 119, 1850300, 9000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, GDHS, MAX, ACTIVE), + false, + 9200, 68, 2839200, 16400, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, MAX, ACTIVE), + false, + 10300, 63, 3128000, 18200, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 18000, 10, 4602600, 27000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, RET_HIGH, RET_LOW), + false, + 20000, 2, 5752000, 32000, + }, +}; + +static struct msm_rpmrs_platform_data msm_rpmrs_data __initdata = { + .levels = &msm_rpmrs_levels[0], + .num_levels = ARRAY_SIZE(msm_rpmrs_levels), + .vdd_mem_levels = { + [MSM_RPMRS_VDD_MEM_RET_LOW] = 750000, + [MSM_RPMRS_VDD_MEM_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_MEM_ACTIVE] = 1050000, + [MSM_RPMRS_VDD_MEM_MAX] = 1150000, + }, + .vdd_dig_levels = { + [MSM_RPMRS_VDD_DIG_RET_LOW] = 500000, + [MSM_RPMRS_VDD_DIG_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_DIG_ACTIVE] = 950000, + [MSM_RPMRS_VDD_DIG_MAX] = 1150000, + }, + .vdd_mask = 0x7FFFFF, + .rpmrs_target_id = { + [MSM_RPMRS_ID_PXO_CLK] = MSM_RPM_ID_PXO_CLK, + [MSM_RPMRS_ID_L2_CACHE_CTL] = MSM_RPM_ID_LAST, + [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, + [MSM_RPMRS_ID_RPM_CTL] = MSM_RPM_ID_RPM_CTL, + }, +}; + +static struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_TZ, +}; + +#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 I2C_LIQUID (1 << 5) + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; + +/* 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 int hsic_peripheral_status = 1; +static DEFINE_MUTEX(hsic_status_lock); + +void peripheral_connect() +{ + mutex_lock(&hsic_status_lock); + if (hsic_peripheral_status) + goto out; + platform_device_add(&msm_device_hsic_host); + hsic_peripheral_status = 1; +out: + mutex_unlock(&hsic_status_lock); +} +EXPORT_SYMBOL(peripheral_connect); + +void peripheral_disconnect() +{ + mutex_lock(&hsic_status_lock); + if (!hsic_peripheral_status) + goto out; + platform_device_del(&msm_device_hsic_host); + hsic_peripheral_status = 0; +out: + mutex_unlock(&hsic_status_lock); +} +EXPORT_SYMBOL(peripheral_disconnect); + +static void __init msm8960_init_smsc_hub(void) +{ + uint32_t version = socinfo_get_version(); + + if (SOCINFO_VERSION_MAJOR(version) == 1) + return; + + if (machine_is_msm8960_liquid()) + platform_device_register(&smsc_hub_device); +} + +static void __init msm8960_init_hsic(void) +{ +#ifdef CONFIG_USB_EHCI_MSM_HSIC + uint32_t version = socinfo_get_version(); + + if (SOCINFO_VERSION_MAJOR(version) == 1) + return; + + if (machine_is_msm8960_liquid()) + platform_device_register(&msm_device_hsic_host); +#endif +} + +#ifdef CONFIG_ISL9519_CHARGER +static struct isl_platform_data isl_data __initdata = { + .valid_n_gpio = 0, /* Not required when notify-by-pmic */ + .chg_detection_config = NULL, /* Not required when notify-by-pmic */ + .max_system_voltage = 4200, + .min_system_voltage = 3200, + .chgcurrent = 1900, + .term_current = 0, + .input_current = 2048, +}; + +static struct i2c_board_info isl_charger_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("isl9519q", 0x9), + .irq = 0, /* Not required when notify-by-pmic */ + .platform_data = &isl_data, + }, +}; +#endif /* CONFIG_ISL9519_CHARGER */ + +static struct i2c_board_info liquid_io_expander_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1508q", 0x20), + .platform_data = &msm8960_sx150x_data[SX150X_LIQUID] + }, +}; + +static struct i2c_registry msm8960_i2c_devices[] __initdata = { +#ifdef CONFIG_ISL9519_CHARGER + { + I2C_LIQUID, + MSM_8960_GSBI10_QUP_I2C_BUS_ID, + isl_charger_i2c_info, + ARRAY_SIZE(isl_charger_i2c_info), + }, +#endif /* CONFIG_ISL9519_CHARGER */ + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_8960_GSBI3_QUP_I2C_BUS_ID, + cyttsp_info, + ARRAY_SIZE(cyttsp_info), + }, + { + I2C_LIQUID, + MSM_8960_GSBI3_QUP_I2C_BUS_ID, + mxt_device_info, + ARRAY_SIZE(mxt_device_info), + }, + { + I2C_SURF | I2C_FFA | I2C_LIQUID, + MSM_8960_GSBI10_QUP_I2C_BUS_ID, + sii_device_info, + ARRAY_SIZE(sii_device_info), + }, + { + I2C_LIQUID, + MSM_8960_GSBI10_QUP_I2C_BUS_ID, + msm_isa1200_board_info, + ARRAY_SIZE(msm_isa1200_board_info), + }, + { + I2C_LIQUID, + MSM_8960_GSBI10_QUP_I2C_BUS_ID, + liquid_io_expander_i2c_info, + ARRAY_SIZE(liquid_io_expander_i2c_info), + }, +}; +#endif /* CONFIG_I2C */ + +static void __init register_i2c_devices(void) +{ +#ifdef CONFIG_I2C + u8 mach_mask = 0; + int i; +#ifdef CONFIG_MSM_CAMERA + struct i2c_registry msm8960_camera_i2c_devices = { + I2C_SURF | I2C_FFA | I2C_FLUID | I2C_LIQUID | I2C_RUMI, + MSM_8960_GSBI4_QUP_I2C_BUS_ID, + msm8960_camera_board_info.board_info, + msm8960_camera_board_info.num_i2c_board_info, + }; +#endif + + /* 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 if (machine_is_msm8960_fluid()) + mach_mask = I2C_FLUID; + else if (machine_is_msm8960_liquid()) + mach_mask = I2C_LIQUID; + else if (machine_is_msm8960_mtp()) + mach_mask = I2C_FFA; + else + pr_err("unmatched machine ID in register_i2c_devices\n"); + + if (machine_is_msm8960_liquid()) { + if (SOCINFO_VERSION_MAJOR(socinfo_get_platform_version()) == 3) + mxt_device_info[0].platform_data = + &mxt_platform_data_3d; + else + mxt_device_info[0].platform_data = + &mxt_platform_data_2d; + } + + /* 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); + } +#ifdef CONFIG_MSM_CAMERA + if (msm8960_camera_i2c_devices.machs & mach_mask) + i2c_register_board_info(msm8960_camera_i2c_devices.bus, + msm8960_camera_i2c_devices.info, + msm8960_camera_i2c_devices.len); +#endif +#endif +} + +static void __init msm8960_sim_init(void) +{ + struct msm_watchdog_pdata *wdog_pdata = (struct msm_watchdog_pdata *) + &msm8960_device_watchdog.dev.platform_data; + + wdog_pdata->bark_time = 15000; + msm_tsens_early_init(&msm_tsens_pdata); + BUG_ON(msm_rpm_init(&msm8960_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + regulator_suppress_info_printing(); + platform_device_register(&msm8960_device_rpm_regulator); + msm_clock_init(&msm8960_clock_init_data); + msm8960_init_pmic(); + + msm8960_device_otg.dev.platform_data = &msm_otg_pdata; + msm8960_init_gpiomux(); + 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)); + msm8960_pm8921_gpio_mpp_init(); + platform_add_devices(sim_devices, ARRAY_SIZE(sim_devices)); + acpuclk_init(&acpuclk_8960_soc_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(); + msm8960_init_fb(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_init_sleep_status_data(&msm_pm_slp_sts_data); +} + +static void __init msm8960_rumi3_init(void) +{ + msm_tsens_early_init(&msm_tsens_pdata); + BUG_ON(msm_rpm_init(&msm8960_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + regulator_suppress_info_printing(); + platform_device_register(&msm8960_device_rpm_regulator); + msm8960_init_gpiomux(); + msm8960_init_pmic(); + 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)); + msm8960_pm8921_gpio_mpp_init(); + platform_add_devices(rumi3_devices, ARRAY_SIZE(rumi3_devices)); + msm8960_init_mmc(); + register_i2c_devices(); + + + msm8960_init_fb(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_init_sleep_status_data(&msm_pm_slp_sts_data); +} + +static void __init msm8960_cdp_init(void) +{ + if (meminfo_init(SYS_MEMORY, SZ_256M) < 0) + pr_err("meminfo_init() failed!\n"); + + msm_tsens_early_init(&msm_tsens_pdata); + BUG_ON(msm_rpm_init(&msm8960_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + + regulator_suppress_info_printing(); + if (msm_xo_init()) + pr_err("Failed to initialize XO votes\n"); + platform_device_register(&msm8960_device_rpm_regulator); + msm_clock_init(&msm8960_clock_init_data); + if (machine_is_msm8960_liquid()) + msm_otg_pdata.mhl_enable = true; + msm8960_device_otg.dev.platform_data = &msm_otg_pdata; + if (machine_is_msm8960_mtp() || machine_is_msm8960_fluid() || + machine_is_msm8960_cdp()) { + msm_otg_pdata.phy_init_seq = wr_phy_init_seq; + } else if (machine_is_msm8960_liquid()) { + msm_otg_pdata.phy_init_seq = + liquid_v1_phy_init_seq; + } + android_usb_pdata.swfi_latency = + msm_rpmrs_levels[0].latency_us; + msm_device_hsic_host.dev.platform_data = &msm_hsic_pdata; + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) >= 2 && + machine_is_msm8960_liquid()) + msm_device_hsic_host.dev.parent = &smsc_hub_device.dev; + msm8960_init_gpiomux(); + 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_pmic(); + if ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) >= 2 && + (machine_is_msm8960_mtp())) || machine_is_msm8960_liquid()) + msm_isa1200_board_info[0].platform_data = &isa1200_1_pdata; + msm8960_i2c_init(); + msm8960_gfx_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(msm8960_footswitch, msm8960_num_footswitch); + if (machine_is_msm8960_liquid()) + platform_device_register(&msm8960_device_ext_3p3v_vreg); + if (machine_is_msm8960_cdp()) + platform_device_register(&msm8960_device_ext_l2_vreg); + + if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) + platform_device_register(&msm8960_device_uart_gsbi8); + else + platform_device_register(&msm8960_device_uart_gsbi5); + + /* For 8960 Fusion 2.2 Primary IPC */ + if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) { + msm_uart_dm9_pdata.wakeup_irq = gpio_to_irq(94); /* GSBI9(2) */ + msm_device_uart_dm9.dev.platform_data = &msm_uart_dm9_pdata; + } + + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + msm8960_pm8921_gpio_mpp_init(); + platform_add_devices(cdp_devices, ARRAY_SIZE(cdp_devices)); + msm8960_init_smsc_hub(); + msm8960_init_hsic(); +#ifdef CONFIG_MSM_CAMERA + msm8960_init_cam(); +#endif + msm8960_init_mmc(); + acpuclk_init(&acpuclk_8960_soc_data); + if (machine_is_msm8960_liquid()) + mxt_init_hw_liquid(); + register_i2c_devices(); + msm8960_init_fb(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + msm8960_init_dsps(); + change_memory_power = &msm8960_change_memory_power; + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_init_sleep_status_data(&msm_pm_slp_sts_data); + if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) { + mdm_sglte_device.dev.platform_data = &sglte_platform_data; + platform_device_register(&mdm_sglte_device); + } +} + +MACHINE_START(MSM8960_SIM, "QCT MSM8960 SIMULATOR") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8960_sim_init, + .init_early = msm8960_allocate_memory_regions, + .init_very_early = msm8960_early_memory, +MACHINE_END + +MACHINE_START(MSM8960_RUMI3, "QCT MSM8960 RUMI3") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8960_rumi3_init, + .init_early = msm8960_allocate_memory_regions, + .init_very_early = msm8960_early_memory, +MACHINE_END + +MACHINE_START(MSM8960_CDP, "QCT MSM8960 CDP") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, + .init_very_early = msm8960_early_memory, +MACHINE_END + +MACHINE_START(MSM8960_MTP, "QCT MSM8960 MTP") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, + .init_very_early = msm8960_early_memory, +MACHINE_END + +MACHINE_START(MSM8960_FLUID, "QCT MSM8960 FLUID") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, + .init_very_early = msm8960_early_memory, +MACHINE_END + +MACHINE_START(MSM8960_LIQUID, "QCT MSM8960 LIQUID") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, + .init_very_early = msm8960_early_memory, +MACHINE_END diff --git a/arch/arm/mach-msm/board-8960.h b/arch/arm/mach-msm/board-8960.h new file mode 100644 index 0000000000000000000000000000000000000000..d98667007a477680c3b16bfc84857ba94e110e53 --- /dev/null +++ b/arch/arm/mach-msm/board-8960.h @@ -0,0 +1,94 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#include +#include +#include +#include +#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) + +extern struct pm8xxx_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 +#define GPIO_VREG_ID_EXT_3P3V 2 +#define GPIO_VREG_ID_EXT_OTG_SW 3 + +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 rpm_regulator_platform_data msm_rpm_regulator_pdata __devinitdata; + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) +enum { + GPIO_EXPANDER_IRQ_BASE = (PM8921_IRQ_BASE + PM8921_NR_IRQS), + GPIO_EXPANDER_GPIO_BASE = (PM8921_MPP_BASE + PM8921_NR_MPPS), + /* CAM Expander */ + GPIO_CAM_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE, + GPIO_CAM_GP_STROBE_READY = GPIO_CAM_EXPANDER_BASE, + GPIO_CAM_GP_AFBUSY, + GPIO_CAM_GP_STROBE_CE, + GPIO_CAM_GP_CAM1MP_XCLR, + GPIO_CAM_GP_CAMIF_RESET_N, + GPIO_CAM_GP_XMT_FLASH_INT, + GPIO_CAM_GP_LED_EN1, + GPIO_CAM_GP_LED_EN2, + GPIO_LIQUID_EXPANDER_BASE = GPIO_CAM_EXPANDER_BASE + 8, +}; +#endif + +enum { + SX150X_CAM, + SX150X_LIQUID, +}; + +#endif + +extern struct sx150x_platform_data msm8960_sx150x_data[]; +extern struct msm_camera_board_info msm8960_camera_board_info; + +void msm8960_init_cam(void); +void msm8960_init_fb(void); +void msm8960_init_pmic(void); +void msm8960_init_mmc(void); +int msm8960_init_gpiomux(void); +unsigned char msm8960_hdmi_as_primary_selected(void); +void msm8960_allocate_fb_region(void); +void msm8960_set_display_params(char *prim_panel, char *ext_panel); +void msm8960_pm8921_gpio_mpp_init(void); +void msm8960_mdp_writeback(struct memtype_reserve *reserve_table); +#define MSM_8960_GSBI4_QUP_I2C_BUS_ID 4 +#define MSM_8960_GSBI3_QUP_I2C_BUS_ID 3 +#define MSM_8960_GSBI10_QUP_I2C_BUS_ID 10 + +extern struct msm_rtb_platform_data msm8960_rtb_pdata; +extern struct msm_cache_dump_platform_data msm8960_cache_dump_pdata; diff --git a/arch/arm/mach-msm/board-9615-display.c b/arch/arm/mach-msm/board-9615-display.c new file mode 100644 index 0000000000000000000000000000000000000000..74bc984cf1726f8f2b3b8a5a4baae6369cbdb3b9 --- /dev/null +++ b/arch/arm/mach-msm/board-9615-display.c @@ -0,0 +1,169 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "board-9615.h" + +/* prim = 240 x 320 x 4(bpp) x 2(pages) */ +#define MSM_FB_PRIM_BUF_SIZE roundup(240 * 320 * 4 * 2, 0x10000) +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE, 4096) + +#define GPIO_PIN_EBI2_LCD_A_D 21 +#define GPIO_PIN_EBI2_LCD_CS 22 +#define GPIO_PIN_EBI2_LCD_RS 24 + + +#ifdef CONFIG_FB_MSM + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_MEM, + } +}; + +static int msm_fb_detect_panel(const char *name) +{ + return 0; +} + +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, +}; + +void __init mdm9615_allocate_fb_region(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)); +} + + +static bool ebi2_power_init; +static int ebi2_panel_power(int on) +{ + static struct regulator *panel_power; + int rc; + + pr_debug("%s: on=%d\n", __func__, on); + + if (!ebi2_power_init) { + panel_power = regulator_get(&msm_ebi2_lcdc_device.dev, + "VDDI2"); + if (IS_ERR_OR_NULL(panel_power)) { + pr_err("could not get L14, rc = %ld\n", + PTR_ERR(panel_power)); + return -ENODEV; + } + + rc = regulator_set_voltage(panel_power, 2800000, 3800000); + if (rc) { + pr_err("set_voltage L14 failed, rc=%d\n", rc); + return -EINVAL; + } + + ebi2_power_init = true; + } + + if (on) { + rc = regulator_enable(panel_power); + if (rc) { + pr_err("enable L14 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = gpio_request(GPIO_PIN_EBI2_LCD_A_D, "disp_a_d"); + if (rc) { + pr_err("request gpio EBI2_LCD_A_D failed, rc=%d\n", rc); + goto error1; + } + rc = gpio_request(GPIO_PIN_EBI2_LCD_CS, "disp_cs"); + if (rc) { + pr_err("request gpio EBI2_LCD_CS failed, rc=%d\n", rc); + goto error2; + } + rc = gpio_request(GPIO_PIN_EBI2_LCD_RS, "disp_rs"); + if (rc) { + pr_err("request gpio EBI2_LCD_RS failed, rc=%d\n", rc); + goto error3; + } + } else { + gpio_free(GPIO_PIN_EBI2_LCD_RS); + gpio_free(GPIO_PIN_EBI2_LCD_CS); + gpio_free(GPIO_PIN_EBI2_LCD_A_D); + + rc = regulator_disable(panel_power); + if (rc) { + pr_err("disable L14 failed, rc=%d\n", rc); + return -ENODEV; + } + } + + return 0; +error3: + gpio_free(GPIO_PIN_EBI2_LCD_CS); +error2: + gpio_free(GPIO_PIN_EBI2_LCD_A_D); +error1: + regulator_disable(panel_power); + return rc; + +} + +static struct lcdc_platform_data ebi2_lcdc_pdata = { + .lcdc_power_save = ebi2_panel_power, +}; + +static struct lvds_panel_platform_data ebi2_epson_s1d_pdata; + +static struct platform_device ebi2_epson_s1d_panel_device = { + .name = "ebi2_epson_s1d_qvga", + .id = 0, + .dev = { + .platform_data = &ebi2_epson_s1d_pdata, + } +}; + +void __init mdm9615_init_fb(void) +{ + platform_device_register(&msm_fb_device); + platform_device_register(&ebi2_epson_s1d_panel_device); + + msm_fb_register_device("ebi2_lcd", &ebi2_lcdc_pdata); +} +#endif diff --git a/arch/arm/mach-msm/board-9615-gpiomux.c b/arch/arm/mach-msm/board-9615-gpiomux.c new file mode 100644 index 0000000000000000000000000000000000000000..47a9835ffcb16a8828c4169bc2b5e962cf87c1a1 --- /dev/null +++ b/arch/arm/mach-msm/board-9615-gpiomux.c @@ -0,0 +1,370 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the 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-9615.h" + +static struct gpiomux_setting ps_hold = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting slimbus = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +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 gsbi3 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi3_cs1_config = { + .func = GPIOMUX_FUNC_4, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +#ifdef CONFIG_LTC4088_CHARGER +static struct gpiomux_setting ltc4088_chg_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; +#endif + +static struct gpiomux_setting sdcc2_clk_actv_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc2_cmd_data_0_3_actv_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc2_suspend_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting cdc_mclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +#ifdef CONFIG_FB_MSM_EBI2 +static struct gpiomux_setting ebi2_lcdc_a_d = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting ebi2_lcdc_cs = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ebi2_lcdc_rs = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_DOWN, +}; +#endif + +static struct gpiomux_setting wlan_active_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_LOW, +}; + +static struct gpiomux_setting wlan_suspend_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_IN, +}; + +static struct msm_gpiomux_config msm9615_audio_codec_configs[] __initdata = { + { + .gpio = 24, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_mclk, + }, + }, +}; + +static struct msm_gpiomux_config msm9615_sdcc2_configs[] __initdata = { + { + /* SDC2_DATA_0 */ + .gpio = 25, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* SDC2_DATA_1 */ + .gpio = 26, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_cmd_data_0_3_actv_cfg, + }, + }, + { + /* SDC2_DATA_2 */ + .gpio = 27, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* SDC2_DATA_3 */ + .gpio = 28, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* SDC2_CMD */ + .gpio = 29, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_cmd_data_0_3_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, + { + /* SDC2_CLK */ + .gpio = 30, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_clk_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_cfg, + }, + }, +}; + +struct msm_gpiomux_config msm9615_ps_hold_config[] __initdata = { + { + .gpio = 83, + .settings = { + [GPIOMUX_SUSPENDED] = &ps_hold, + }, + }, +}; + +#ifdef CONFIG_LTC4088_CHARGER +static struct msm_gpiomux_config + msm9615_ltc4088_charger_config[] __initdata = { + { + .gpio = 4, + .settings = { + [GPIOMUX_SUSPENDED] = <c4088_chg_cfg, + }, + }, + { + .gpio = 6, + .settings = { + [GPIOMUX_SUSPENDED] = <c4088_chg_cfg, + }, + }, + { + .gpio = 7, + .settings = { + [GPIOMUX_SUSPENDED] = <c4088_chg_cfg, + }, + }, +}; +#endif + +struct msm_gpiomux_config msm9615_gsbi_configs[] __initdata = { + { + .gpio = 8, /* GSBI3 QUP SPI_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3, + }, + }, + { + .gpio = 9, /* GSBI3 QUP SPI_CS_N */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3, + }, + }, + { + .gpio = 10, /* GSBI3 QUP SPI_DATA_MISO */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3, + }, + }, + { + .gpio = 11, /* GSBI3 QUP SPI_DATA_MOSI */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3, + }, + }, + { + .gpio = 12, /* GSBI4 UART */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi4, + }, + }, + { + .gpio = 13, /* GSBI4 UART */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi4, + }, + }, + { + .gpio = 14, /* GSBI4 UART */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi4, + }, + }, + { + .gpio = 15, /* GSBI4 UART */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi4, + }, + }, + { + .gpio = 16, /* GSBI5 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + [GPIOMUX_ACTIVE] = &gsbi5, + }, + }, + { + .gpio = 17, /* GSBI5 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + /* GPIO 19 can be used for I2C/UART on GSBI5 */ + .gpio = 19, /* GSBI3 QUP SPI_CS_1 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3_cs1_config, + }, + }, +}; + +static struct msm_gpiomux_config msm9615_slimbus_configs[] __initdata = { + { + .gpio = 20, /* Slimbus data */ + .settings = { + [GPIOMUX_SUSPENDED] = &slimbus, + }, + }, + { + .gpio = 23, /* Slimbus clk */ + .settings = { + [GPIOMUX_SUSPENDED] = &slimbus, + }, + }, +}; + +#ifdef CONFIG_FB_MSM_EBI2 +static struct msm_gpiomux_config msm9615_ebi2_lcdc_configs[] __initdata = { + { + .gpio = 21, /* a_d */ + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_lcdc_a_d, + }, + }, + { + .gpio = 22, /* cs */ + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_lcdc_cs, + }, + }, + { + .gpio = 24, /* rs */ + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_lcdc_rs, + }, + }, +}; +#endif + +static struct msm_gpiomux_config msm9615_wlan_configs[] __initdata = { + { + .gpio = 21,/* WLAN_RESET_N */ + .settings = { + [GPIOMUX_ACTIVE] = &wlan_active_config, + [GPIOMUX_SUSPENDED] = &wlan_suspend_config, + }, + }, +}; + + +int __init msm9615_init_gpiomux(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(msm9615_gsbi_configs, + ARRAY_SIZE(msm9615_gsbi_configs)); + + msm_gpiomux_install(msm9615_slimbus_configs, + ARRAY_SIZE(msm9615_slimbus_configs)); + + msm_gpiomux_install(msm9615_ps_hold_config, + ARRAY_SIZE(msm9615_ps_hold_config)); + msm_gpiomux_install(msm9615_sdcc2_configs, + ARRAY_SIZE(msm9615_sdcc2_configs)); +#ifdef CONFIG_LTC4088_CHARGER + msm_gpiomux_install(msm9615_ltc4088_charger_config, + ARRAY_SIZE(msm9615_ltc4088_charger_config)); +#endif + msm_gpiomux_install(msm9615_audio_codec_configs, + ARRAY_SIZE(msm9615_audio_codec_configs)); + + msm_gpiomux_install(msm9615_wlan_configs, + ARRAY_SIZE(msm9615_wlan_configs)); + +#ifdef CONFIG_FB_MSM_EBI2 + msm_gpiomux_install(msm9615_ebi2_lcdc_configs, + ARRAY_SIZE(msm9615_ebi2_lcdc_configs)); +#endif + + return 0; +} diff --git a/arch/arm/mach-msm/board-9615-regulator.c b/arch/arm/mach-msm/board-9615-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..7ed350d7509a1f0397e0d207d72670fce5eedc20 --- /dev/null +++ b/arch/arm/mach-msm/board-9615-regulator.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "board-9615.h" + +#define VREG_CONSUMERS(_id) \ + static struct regulator_consumer_supply vreg_consumers_##_id[] + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +VREG_CONSUMERS(L2) = { + REGULATOR_SUPPLY("8018_l2", NULL), + REGULATOR_SUPPLY("HSUSB_1p8", "msm_otg"), +}; +VREG_CONSUMERS(L3) = { + REGULATOR_SUPPLY("8018_l3", NULL), +}; +VREG_CONSUMERS(L4) = { + REGULATOR_SUPPLY("8018_l4", NULL), + REGULATOR_SUPPLY("HSUSB_3p3", "msm_otg"), +}; +VREG_CONSUMERS(L5) = { + REGULATOR_SUPPLY("8018_l5", NULL), +}; +VREG_CONSUMERS(L6) = { + REGULATOR_SUPPLY("8018_l6", NULL), +}; +VREG_CONSUMERS(L7) = { + REGULATOR_SUPPLY("8018_l7", NULL), +}; +VREG_CONSUMERS(L8) = { + REGULATOR_SUPPLY("8018_l8", NULL), +}; +VREG_CONSUMERS(L9) = { + REGULATOR_SUPPLY("8018_l9", NULL), +}; +VREG_CONSUMERS(L10) = { + REGULATOR_SUPPLY("8018_l10", NULL), +}; +VREG_CONSUMERS(L11) = { + REGULATOR_SUPPLY("8018_l11", NULL), +}; +VREG_CONSUMERS(L12) = { + REGULATOR_SUPPLY("8018_l12", NULL), +}; +VREG_CONSUMERS(L13) = { + REGULATOR_SUPPLY("8018_l13", NULL), + REGULATOR_SUPPLY("sdc_vddp", "msm_sdcc.1"), +}; +VREG_CONSUMERS(L14) = { + REGULATOR_SUPPLY("8018_l14", NULL), + REGULATOR_SUPPLY("VDDI2", "ebi2_lcd.0"), +}; +VREG_CONSUMERS(S1) = { + REGULATOR_SUPPLY("8018_s1", NULL), + REGULATOR_SUPPLY("HSUSB_VDDCX", "msm_otg"), + REGULATOR_SUPPLY("HSIC_VDDCX", "msm_hsic_peripheral"), + REGULATOR_SUPPLY("HSIC_VDDCX", "msm_hsic_host"), +}; +VREG_CONSUMERS(S2) = { + REGULATOR_SUPPLY("8018_s2", NULL), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "tabla2x-slim"), + REGULATOR_SUPPLY("VDDD_CDC_D", "tabla-slim"), + REGULATOR_SUPPLY("VDDD_CDC_D", "tabla2x-slim"), + REGULATOR_SUPPLY("VDDD_CDC_D", "0-000d"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "0-000d"), + REGULATOR_SUPPLY("VDDD_CDC_D", "tabla top level"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "tabla top level"), +}; +VREG_CONSUMERS(S3) = { + REGULATOR_SUPPLY("8018_s3", NULL), + REGULATOR_SUPPLY("wlan_vreg", "wlan_ar6000_pm_dev"), + REGULATOR_SUPPLY("CDC_VDD_CP", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDD_CP", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "tabla2x-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "tabla2x-slim"), + REGULATOR_SUPPLY("VDDIO_CDC", "tabla-slim"), + REGULATOR_SUPPLY("VDDIO_CDC", "tabla2x-slim"), + REGULATOR_SUPPLY("VDDIO_CDC", "tabla top level"), + REGULATOR_SUPPLY("CDC_VDD_CP", "tabla top level"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "tabla top level"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "tabla top level"), + REGULATOR_SUPPLY("VDDIO_CDC", "0-000d"), + REGULATOR_SUPPLY("CDC_VDD_CP", "0-000d"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "0-000d"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "0-000d"), +}; +VREG_CONSUMERS(S4) = { + REGULATOR_SUPPLY("8018_s4", NULL), +}; +VREG_CONSUMERS(S5) = { + REGULATOR_SUPPLY("8018_s5", NULL), +}; +VREG_CONSUMERS(LVS1) = { + REGULATOR_SUPPLY("8018_lvs1", NULL), +}; +VREG_CONSUMERS(EXT_2P95V) = { + REGULATOR_SUPPLY("ext_2p95v", NULL), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.1"), +}; + +#define PM8XXX_VREG_INIT(_id, _name, _min_uV, _max_uV, _modes, _ops, \ + _apply_uV, _pull_down, _always_on, _supply_regulator, \ + _system_uA, _enable_time, _reg_id) \ + { \ + .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, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pull_down_enable = _pull_down, \ + .system_uA = _system_uA, \ + .enable_time = _enable_time, \ + } + +#define PM8XXX_LDO(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_NLDO1200(_id, _name, _always_on, _pull_down, _min_uV, \ + _max_uV, _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_SMPS(_id, _name, _always_on, _pull_down, _min_uV, _max_uV, \ + _enable_time, _supply_regulator, _system_uA, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, _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, _enable_time, _reg_id) + +#define PM8XXX_VS(_id, _name, _always_on, _pull_down, _enable_time, \ + _supply_regulator, _reg_id) \ + PM8XXX_VREG_INIT(_id, _name, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, \ + _pull_down, _always_on, _supply_regulator, 0, _enable_time, \ + _reg_id) + +/* Pin control initialization */ +#define PM8XXX_PC(_id, _name, _always_on, _pin_fn, _pin_ctrl, \ + _supply_regulator, _reg_id) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .always_on = _always_on, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id##_PC), \ + .consumer_supplies = vreg_consumers_##_id##_PC, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = _reg_id, \ + .pin_fn = PM8XXX_VREG_PIN_FN_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +#define RPM_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, _default_uV, \ + _peak_uA, _avg_uA, _pull_down, _pin_ctrl, _freq, _pin_fn, \ + _force_mode, _sleep_set_force_mode, _power_mode, _state, \ + _sleep_selectable, _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 = _min_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 = RPM_VREG_ID_PM8018_##_id, \ + .default_uV = _default_uV, \ + .peak_uA = _peak_uA, \ + .avg_uA = _avg_uA, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = _pin_ctrl, \ + .freq = RPM_VREG_FREQ_##_freq, \ + .pin_fn = _pin_fn, \ + .force_mode = _force_mode, \ + .sleep_set_force_mode = _sleep_set_force_mode, \ + .power_mode = _power_mode, \ + .state = _state, \ + .sleep_selectable = _sleep_selectable, \ + .system_uA = _system_uA, \ + } + +#define RPM_LDO(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _init_peak_uA) \ + RPM_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, _max_uV, _init_peak_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_9615_NONE, \ + RPM_VREG_FORCE_MODE_9615_NONE, \ + RPM_VREG_FORCE_MODE_9615_NONE, RPM_VREG_POWER_MODE_9615_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, _system_uA) + +#define RPM_SMPS(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _supply_regulator, _system_uA, _freq) \ + RPM_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, _max_uV, _system_uA, 0, _pd, \ + RPM_VREG_PIN_CTRL_NONE, _freq, RPM_VREG_PIN_FN_9615_NONE, \ + RPM_VREG_FORCE_MODE_9615_NONE, \ + RPM_VREG_FORCE_MODE_9615_NONE, RPM_VREG_POWER_MODE_9615_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, _system_uA) + +#define RPM_VS(_id, _always_on, _pd, _sleep_selectable, _supply_regulator) \ + RPM_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, 0, 1000, 1000, _pd, \ + RPM_VREG_PIN_CTRL_NONE, NONE, RPM_VREG_PIN_FN_9615_NONE, \ + RPM_VREG_FORCE_MODE_9615_NONE, \ + RPM_VREG_FORCE_MODE_9615_NONE, RPM_VREG_POWER_MODE_9615_PWM, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on, \ + _supply_regulator, 0) + +/* Pin control initialization */ +#define RPM_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 = RPM_VREG_ID_PM8018_##_id##_PC, \ + .pin_fn = RPM_VREG_PIN_FN_9615_##_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, \ + } + +/* GPIO regulator constraints */ +struct gpio_regulator_platform_data msm_gpio_regulator_pdata[] = { + GPIO_VREG_INIT(EXT_2P95V, "ext_2p95v", "ext_2p95_en", 18), +}; + +/* PM8018 regulator constraints */ +struct pm8xxx_regulator_platform_data +msm_pm8018_regulator_pdata[] __devinitdata = { +}; + +static struct rpm_regulator_init_data +msm_rpm_regulator_init_data[] __devinitdata = { + /* ID a_on pd ss min_uV max_uV supply sys_uA freq */ + RPM_SMPS(S1, 0, 1, 1, 500000, 1150000, NULL, 100000, 1p60), + RPM_SMPS(S2, 0, 1, 0, 1225000, 1300000, NULL, 0, 1p60), + RPM_SMPS(S3, 1, 1, 0, 1800000, 1800000, NULL, 100000, 1p60), + RPM_SMPS(S4, 0, 1, 0, 2100000, 2200000, NULL, 0, 1p60), + RPM_SMPS(S5, 1, 1, 0, 1350000, 1350000, NULL, 100000, 1p60), + + /* ID a_on pd ss min_uV max_uV supply sys_uA init_ip */ + RPM_LDO(L2, 1, 1, 0, 1800000, 1800000, NULL, 0, 10000), + RPM_LDO(L3, 0, 1, 0, 1800000, 1800000, NULL, 0, 0), + RPM_LDO(L4, 0, 1, 0, 3075000, 3075000, NULL, 0, 0), + RPM_LDO(L5, 0, 1, 0, 2850000, 2850000, NULL, 0, 0), + RPM_LDO(L6, 0, 1, 0, 1800000, 2850000, NULL, 0, 0), + RPM_LDO(L7, 0, 1, 0, 1850000, 1900000, "8018_s4", 0, 0), + RPM_LDO(L8, 0, 1, 0, 1200000, 1200000, "8018_s3", 0, 0), + RPM_LDO(L9, 0, 1, 1, 750000, 1150000, "8018_s5", 10000, 10000), + RPM_LDO(L10, 0, 1, 0, 1050000, 1050000, "8018_s5", 0, 0), + RPM_LDO(L11, 0, 1, 0, 1050000, 1050000, "8018_s5", 0, 0), + RPM_LDO(L12, 0, 1, 0, 1050000, 1050000, "8018_s5", 0, 0), + RPM_LDO(L13, 0, 1, 0, 1850000, 2950000, NULL, 0, 0), + RPM_LDO(L14, 0, 1, 0, 2850000, 2850000, NULL, 0, 0), + + /* ID a_on pd ss supply */ + RPM_VS(LVS1, 0, 1, 0, "8018_s3"), +}; + +int msm_pm8018_regulator_pdata_len __devinitdata = + ARRAY_SIZE(msm_pm8018_regulator_pdata); + +struct rpm_regulator_platform_data +msm_rpm_regulator_9615_pdata __devinitdata = { + .init_data = msm_rpm_regulator_init_data, + .num_regulators = ARRAY_SIZE(msm_rpm_regulator_init_data), + .version = RPM_VREG_VERSION_9615, + .vreg_id_vdd_mem = RPM_VREG_ID_PM8018_L9, + .vreg_id_vdd_dig = RPM_VREG_ID_PM8018_S1, +}; diff --git a/arch/arm/mach-msm/board-9615-storage.c b/arch/arm/mach-msm/board-9615-storage.c new file mode 100644 index 0000000000000000000000000000000000000000..5bdeb94cc0a48cf03dc292b1ec51787dc1c42e80 --- /dev/null +++ b/arch/arm/mach-msm/board-9615-storage.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 "devices.h" + +#include "board-9615.h" +#include "board-storage-common-a.h" + +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT) \ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)) + +#define GPIO_SDC1_HW_DET 80 +#define GPIO_SDC2_DAT1_WAKEUP 26 + +/* MDM9x15 has 2 SDCC controllers */ +enum sdcc_controllers { + SDCC1, + SDCC2, + MAX_SDCC_CONTROLLER +}; + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +/* All SDCC controllers requires VDD/VCC voltage */ +static struct msm_mmc_reg_data mmc_vdd_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : External card slot connected */ + [SDCC1] = { + .name = "sdc_vdd", + /* + * This is a gpio-regulator and does not support + * regulator_set_voltage and regulator_set_optimum_mode + */ + .high_vol_level = 2950000, + .low_vol_level = 2950000, + .hpm_uA = 600000, /* 600mA */ + } +}; + +/* All SDCC controllers may require voting for VDD PAD voltage */ +static struct msm_mmc_reg_data mmc_vddp_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : External card slot connected */ + [SDCC1] = { + .name = "sdc_vddp", + .high_vol_level = 2950000, + .low_vol_level = 1850000, + .always_on = true, + .lpm_sup = true, + /* 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 : External card slot connected */ + [SDCC1] = { + .vdd_data = &mmc_vdd_reg_data[SDCC1], + .vddp_data = &mmc_vddp_reg_data[SDCC1], + } +}; + +/* 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_CLK, GPIO_CFG_NO_PULL}, + {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_CLK, GPIO_CFG_NO_PULL}, + {TLMM_PULL_SDC1_CMD, GPIO_CFG_PULL_DOWN}, + {TLMM_PULL_SDC1_DATA, GPIO_CFG_PULL_DOWN} +}; + +static 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) + }, +}; + +static 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) + }, +}; + +static struct msm_mmc_pad_data mmc_pad_data[MAX_SDCC_CONTROLLER] = { + [SDCC1] = { + .pull = &mmc_pad_pull_data[SDCC1], + .drv = &mmc_pad_drv_data[SDCC1] + }, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + +static struct msm_mmc_gpio sdc2_gpio_cfg[] = { + {25, "sdc2_dat_0"}, + {26, "sdc2_dat_1"}, + {27, "sdc2_dat_2"}, + {28, "sdc2_dat_3"}, + {29, "sdc2_cmd"}, + {30, "sdc2_clk"}, +}; + +static struct msm_mmc_gpio_data mmc_gpio_data[MAX_SDCC_CONTROLLER] = { + [SDCC2] = { + .gpio = sdc2_gpio_cfg, + .size = ARRAY_SIZE(sdc2_gpio_cfg), + }, +}; +#endif + +static struct msm_mmc_pin_data mmc_slot_pin_data[MAX_SDCC_CONTROLLER] = { +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + [SDCC1] = { + .is_gpio = 0, + .pad_data = &mmc_pad_data[SDCC1], + }, +#endif +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + [SDCC2] = { + .is_gpio = 1, + .gpio_data = &mmc_gpio_data[SDCC2], + }, +#endif +}; + +#define MSM_MPM_PIN_SDC1_DAT1 17 + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static unsigned int sdc1_sup_clk_rates[] = { + 400000, 24000000, 48000000 +}; + +static struct mmc_platform_data sdc1_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .sup_clk_table = sdc1_sup_clk_rates, + .sup_clk_cnt = ARRAY_SIZE(sdc1_sup_clk_rates), + .pclk_src_dfab = true, + .vreg_data = &mmc_slot_vreg_data[SDCC1], + .pin_data = &mmc_slot_pin_data[SDCC1], +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + .status_gpio = GPIO_SDC1_HW_DET, + .status_irq = MSM_GPIO_TO_INT(GPIO_SDC1_HW_DET), + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +#endif + .xpc_cap = 1, + .uhs_caps = (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_MAX_CURRENT_400), + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC1_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +static struct mmc_platform_data *msm9615_sdc1_pdata = &sdc1_data; +#else +static struct mmc_platform_data *msm9615_sdc1_pdata; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static unsigned int sdc2_sup_clk_rates[] = { + 400000, 24000000, 48000000 +}; + +static struct mmc_platform_data sdc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .sup_clk_table = sdc2_sup_clk_rates, + .sup_clk_cnt = ARRAY_SIZE(sdc2_sup_clk_rates), + .pclk_src_dfab = 1, + .pin_data = &mmc_slot_pin_data[SDCC2], + .sdiowakeup_irq = MSM_GPIO_TO_INT(GPIO_SDC2_DAT1_WAKEUP), + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +static struct mmc_platform_data *msm9615_sdc2_pdata = &sdc2_data; +#else +static struct mmc_platform_data *msm9615_sdc2_pdata; +#endif + +void __init msm9615_init_mmc(void) +{ + if (msm9615_sdc1_pdata) + /* SDC1: External card slot for SD/MMC cards */ + msm_add_sdcc(1, msm9615_sdc1_pdata); + + if (msm9615_sdc2_pdata) + /* SDC2: External card slot used for WLAN */ + msm_add_sdcc(2, msm9615_sdc2_pdata); +} +#else +void __init msm9615_init_mmc(void) +{ +} +#endif diff --git a/arch/arm/mach-msm/board-9615.c b/arch/arm/mach-msm/board-9615.c new file mode 100644 index 0000000000000000000000000000000000000000..d5a0857bc6a629e3abcd7b72c07898e41ddf9c4f --- /dev/null +++ b/arch/arm/mach-msm/board-9615.c @@ -0,0 +1,1030 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#ifdef CONFIG_WCD9310_CODEC +#include +#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 "timer.h" +#include "devices.h" +#include "board-9615.h" +#include "pm.h" +#include "acpuclock.h" +#include "pm-boot.h" +#include + +#ifdef CONFIG_ION_MSM +#define MSM_ION_AUDIO_SIZE 0xAF000 +#define MSM_ION_HEAP_NUM 3 +#define MSM_KERNEL_EBI_SIZE 0x51000 + +static struct memtype_reserve msm9615_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static int msm9615_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +static struct ion_co_heap_pdata co_ion_pdata = { + .adjacent_mem_id = INVALID_HEAP_ID, + .align = PAGE_SIZE, +}; + +static struct ion_platform_data ion_pdata = { + .nr = MSM_ION_HEAP_NUM, + .heaps = { + { + .id = ION_SYSTEM_HEAP_ID, + .type = ION_HEAP_TYPE_SYSTEM, + .name = ION_VMALLOC_HEAP_NAME, + }, + { + .id = ION_IOMMU_HEAP_ID, + .type = ION_HEAP_TYPE_IOMMU, + .name = ION_IOMMU_HEAP_NAME, + }, + { + .id = ION_AUDIO_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_AUDIO_HEAP_NAME, + .size = MSM_ION_AUDIO_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_ion_pdata, + }, + } +}; + +static struct platform_device ion_dev = { + .name = "ion-msm", + .id = 1, + .dev = { .platform_data = &ion_pdata }, +}; + +static void __init reserve_ion_memory(void) +{ + msm9615_reserve_table[MEMTYPE_EBI1].size += MSM_ION_AUDIO_SIZE; +} + +static void __init msm9615_calculate_reserve_sizes(void) +{ + reserve_ion_memory(); + msm9615_reserve_table[MEMTYPE_EBI1].size += MSM_KERNEL_EBI_SIZE; +} + +static struct reserve_info msm9615_reserve_info __initdata = { + .memtype_reserve_table = msm9615_reserve_table, + .calculate_reserve_sizes = msm9615_calculate_reserve_sizes, + .paddr_to_memtype = msm9615_paddr_to_memtype, +}; +#endif + +struct pm8xxx_gpio_init { + unsigned gpio; + struct pm_gpio config; +}; + +struct pm8xxx_mpp_init { + unsigned mpp; + struct pm8xxx_mpp_config_data config; +}; + +#define PM8018_GPIO_INIT(_gpio, _dir, _buf, _val, _pull, _vin, _out_strength, \ + _func, _inv, _disable) \ +{ \ + .gpio = PM8018_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 PM8018_MPP_INIT(_mpp, _type, _level, _control) \ +{ \ + .mpp = PM8018_MPP_PM_TO_SYS(_mpp), \ + .config = { \ + .type = PM8XXX_MPP_TYPE_##_type, \ + .level = _level, \ + .control = PM8XXX_MPP_##_control, \ + } \ +} + +#define PM8018_GPIO_DISABLE(_gpio) \ + PM8018_GPIO_INIT(_gpio, PM_GPIO_DIR_IN, 0, 0, 0, PM8018_GPIO_VIN_S3, \ + 0, 0, 0, 1) + +#define PM8018_GPIO_OUTPUT(_gpio, _val, _strength) \ + PM8018_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, PM8018_GPIO_VIN_S3, \ + PM_GPIO_STRENGTH_##_strength, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8018_GPIO_INPUT(_gpio, _pull) \ + PM8018_GPIO_INIT(_gpio, PM_GPIO_DIR_IN, PM_GPIO_OUT_BUF_CMOS, 0, \ + _pull, PM8018_GPIO_VIN_S3, \ + PM_GPIO_STRENGTH_NO, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8018_GPIO_OUTPUT_FUNC(_gpio, _val, _func) \ + PM8018_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, PM8018_GPIO_VIN_S3, \ + PM_GPIO_STRENGTH_HIGH, \ + _func, 0, 0) + +#define PM8018_GPIO_OUTPUT_VIN(_gpio, _val, _vin) \ + PM8018_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, _vin, \ + PM_GPIO_STRENGTH_HIGH, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +/* Initial PM8018 GPIO configurations */ +static struct pm8xxx_gpio_init pm8018_gpios[] __initdata = { + PM8018_GPIO_OUTPUT(2, 0, HIGH) /* EXT_LDO_EN_WLAN */ +}; + +/* Initial PM8018 MPP configurations */ +static struct pm8xxx_mpp_init pm8018_mpps[] __initdata = { +}; + +void __init msm9615_pm8xxx_gpio_mpp_init(void) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(pm8018_gpios); i++) { + rc = pm8xxx_gpio_config(pm8018_gpios[i].gpio, + &pm8018_gpios[i].config); + if (rc) { + pr_err("%s: pm8018_gpio_config: rc=%d\n", __func__, rc); + break; + } + } + + for (i = 0; i < ARRAY_SIZE(pm8018_mpps); i++) { + rc = pm8xxx_mpp_config(pm8018_mpps[i].mpp, + &pm8018_mpps[i].config); + if (rc) { + pr_err("%s: pm8018_mpp_config: rc=%d\n", __func__, rc); + break; + } + } +} + +static struct pm8xxx_adc_amux pm8018_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}, + {"vph_pwr", CHANNEL_VPH_PWR, CHAN_PATH_SCALING2, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + /* AMUX8 is used to read either Batt_id/Batt_therm. + * Current configuration is to support Batt_id. If clients + * want to read the Batt_therm, the scaling function needs to be + * updated to use ADC_SCALE_BATT_THERM instead of ADC_SCALE_DEFAULT. + * E.g. + * {"batt_therm", CHANNEL_BATT_ID_THERM, CHAN_PATH_SCALING1, + * AMUX_RSV2, ADC_DECIMATION_TYPE2, ADC_SCALE_BATT_THERM}, + */ + {"batt_id", CHANNEL_BATT_ID_THERM, CHAN_PATH_SCALING1, + AMUX_RSV2, 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}, + {"pa_therm0", ADC_MPP_1_AMUX3, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_PA_THERM}, +}; + +static struct pm8xxx_adc_properties pm8018_adc_data = { + .adc_vdd_reference = 1800, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, +}; + +static struct pm8xxx_adc_platform_data pm8018_adc_pdata = { + .adc_channel = pm8018_adc_channels_data, + .adc_num_board_channel = ARRAY_SIZE(pm8018_adc_channels_data), + .adc_prop = &pm8018_adc_data, +}; + +static struct pm8xxx_irq_platform_data pm8xxx_irq_pdata __devinitdata = { + .irq_base = PM8018_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(87), + .irq_trigger_flag = IRQF_TRIGGER_LOW, +}; + +static struct pm8xxx_gpio_platform_data pm8xxx_gpio_pdata __devinitdata = { + .gpio_base = PM8018_GPIO_PM_TO_SYS(1), +}; + +static struct pm8xxx_mpp_platform_data pm8xxx_mpp_pdata __devinitdata = { + .mpp_base = PM8018_MPP_PM_TO_SYS(1), +}; + +static struct pm8xxx_rtc_platform_data pm8xxx_rtc_pdata __devinitdata = { + .rtc_write_enable = false, + .rtc_alarm_powerup = false, +}; + +static struct pm8xxx_pwrkey_platform_data pm8xxx_pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 15625, + .wakeup = 1, +}; + +static struct pm8xxx_misc_platform_data pm8xxx_misc_pdata = { + .priority = 0, +}; + +#define PM8018_LED_KB_MAX_CURRENT 20 /* I = 20mA */ +#define PM8XXX_LED_PWM_PERIOD_US 1000 + +/** + * PM8XXX_PWM_CHANNEL_NONE shall be used when LED shall not be + * driven using PWM feature. + */ +#define PM8XXX_PWM_CHANNEL_NONE -1 + +static struct led_info pm8018_led_info[] = { + [0] = { + .name = "led:kb", + }, +}; + +static struct led_platform_data pm8018_led_core_pdata = { + .num_leds = ARRAY_SIZE(pm8018_led_info), + .leds = pm8018_led_info, +}; + +static struct pm8xxx_led_config pm8018_led_configs[] = { + [0] = { + .id = PM8XXX_ID_LED_KB_LIGHT, + .mode = PM8XXX_LED_MODE_PWM3, + .max_current = PM8018_LED_KB_MAX_CURRENT, + .pwm_channel = 2, + .pwm_period_us = PM8XXX_LED_PWM_PERIOD_US, + }, +}; + +static struct pm8xxx_led_platform_data pm8xxx_leds_pdata = { + .led_core = &pm8018_led_core_pdata, + .configs = pm8018_led_configs, + .num_configs = ARRAY_SIZE(pm8018_led_configs), +}; + +#ifdef CONFIG_LTC4088_CHARGER +static struct ltc4088_charger_platform_data ltc4088_chg_pdata = { + .gpio_mode_select_d0 = 7, + .gpio_mode_select_d1 = 6, + .gpio_mode_select_d2 = 4, +}; +#endif + +static struct pm8018_platform_data pm8018_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, + .misc_pdata = &pm8xxx_misc_pdata, + .regulator_pdatas = msm_pm8018_regulator_pdata, + .adc_pdata = &pm8018_adc_pdata, + .leds_pdata = &pm8xxx_leds_pdata, +}; + +static struct msm_ssbi_platform_data msm9615_ssbi_pm8018_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = PM8018_CORE_DEV_NAME, + .platform_data = &pm8018_platform_data, + }, +}; + +static struct platform_device msm9615_device_rpm_regulator __devinitdata = { + .name = "rpm-regulator", + .id = -1, + .dev = { + .platform_data = &msm_rpm_regulator_9615_pdata, + }, +}; + +static struct platform_device msm9615_device_ext_2p95v_vreg = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = 18, + .dev = { + .platform_data = + &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_2P95V], + }, +}; + +static struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR, + .v_addr = MSM_APCS_GLB_BASE + 0x24, +}; + +static void __init msm9615_init_buses(void) +{ +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_rpm_set_mt_mask(); + msm_bus_9615_sys_fabric_pdata.rpm_enabled = 1; + msm_bus_9615_sys_fabric.dev.platform_data = + &msm_bus_9615_sys_fabric_pdata; + msm_bus_def_fab.dev.platform_data = &msm_bus_9615_def_fab_pdata; +#endif +} + +#ifdef CONFIG_WCD9310_CODEC + +#define TABLA_INTERRUPT_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) + +/* + * MDM9x15 I2S. + */ +static struct wcd9xxx_pdata wcd9xxx_i2c_platform_data = { + .irq = MSM_GPIO_TO_INT(85), + .irq_base = TABLA_INTERRUPT_BASE, + .num_irqs = NR_TABLA_IRQS, + .reset_gpio = 84, + .micbias = { + .ldoh_v = TABLA_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 1800, + .cfilt3_mv = 1800, + .bias1_cfilt_sel = TABLA_CFILT1_SEL, + .bias2_cfilt_sel = TABLA_CFILT2_SEL, + .bias3_cfilt_sel = TABLA_CFILT3_SEL, + .bias4_cfilt_sel = TABLA_CFILT3_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + } + }, +}; + +static struct i2c_board_info wcd9xxx_device_info[] __initdata = { + { + I2C_BOARD_INFO("tabla top level", TABLA_I2C_SLAVE_ADDR), + .platform_data = &wcd9xxx_i2c_platform_data, + }, + { + I2C_BOARD_INFO("tabla analog", TABLA_ANALOG_I2C_SLAVE_ADDR), + .platform_data = &wcd9xxx_i2c_platform_data, + }, + { + I2C_BOARD_INFO("tabla digital1", TABLA_DIGITAL1_I2C_SLAVE_ADDR), + .platform_data = &wcd9xxx_i2c_platform_data, + }, + { + I2C_BOARD_INFO("tabla digital2", TABLA_DIGITAL2_I2C_SLAVE_ADDR), + .platform_data = &wcd9xxx_i2c_platform_data, + }, +}; + +static struct i2c_registry msm9615_i2c_devices[] __initdata = { + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_9615_GSBI5_QUP_I2C_BUS_ID, + wcd9xxx_device_info, + ARRAY_SIZE(wcd9xxx_device_info), + }, +}; +/* + * MDM9x15 I2S. + */ + +/* Micbias setting is based on 8660 CDP/MTP/FLUID requirement + * 4 micbiases are used to power various analog and digital + * microphones operating at 1800 mV. Technically, all micbiases + * can source from single cfilter since all microphones operate + * at the same voltage level. The arrangement below is to make + * sure all cfilters are exercised. LDO_H regulator ouput level + * does not need to be as high as 2.85V. It is choosen for + * microphone sensitivity purpose. + */ + +static struct wcd9xxx_pdata tabla20_platform_data = { + .slimbus_slave_device = { + .name = "tabla-slave", + .e_addr = {0, 0, 0x60, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(85), + .irq_base = TABLA_INTERRUPT_BASE, + .num_irqs = NR_WCD9XXX_IRQS, + .reset_gpio = 84, + .micbias = { + .ldoh_v = TABLA_LDOH_2P85_V, + .cfilt1_mv = 1800, + .cfilt2_mv = 1800, + .cfilt3_mv = 1800, + .bias1_cfilt_sel = TABLA_CFILT1_SEL, + .bias2_cfilt_sel = TABLA_CFILT2_SEL, + .bias3_cfilt_sel = TABLA_CFILT3_SEL, + .bias4_cfilt_sel = TABLA_CFILT3_SEL, + }, + .regulator = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = WCD9XXX_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = WCD9XXX_VDDD_CDC_A_CUR_MAX, + }, + }, +}; + +static struct slim_device msm_slim_tabla20 = { + .name = "tabla2x-slim", + .e_addr = {0, 1, 0x60, 0, 0x17, 2}, + .dev = { + .platform_data = &tabla20_platform_data, + }, +}; +#endif + +static struct slim_boardinfo msm_slim_devices[] = { + /* add slimbus slaves as needed */ +#ifdef CONFIG_WCD9310_CODEC + { + .bus_num = 1, + .slim_slave = &msm_slim_tabla20, + }, +#endif +}; + +static struct msm_spi_platform_data msm9615_qup_spi_gsbi3_pdata = { + .max_clock_speed = 24000000, +}; + +static struct msm_i2c_platform_data msm9615_i2c_qup_gsbi5_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, +}; + +#define USB_5V_EN 3 +#define PM_USB_5V_EN PM8018_GPIO_PM_TO_SYS(USB_5V_EN) + +static int msm_hsusb_vbus_power(bool on) +{ + int rc; + struct pm_gpio usb_vbus = { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }; + + usb_vbus.output_value = on; + + rc = pm8xxx_gpio_config(PM_USB_5V_EN, &usb_vbus); + if (rc) + pr_err("failed to config usb_5v_en gpio\n"); + + return rc; +} + +static int shelby_phy_init_seq[] = { + 0x44, 0x80,/* set VBUS valid threshold and + disconnect valid threshold */ + 0x38, 0x81, /* update DC voltage level */ + 0x24, 0x82,/* set preemphasis and rise/fall time */ + 0x13, 0x83,/* set source impedance adjustment */ + -1}; + +#define USB_BAM_PHY_BASE 0x12502000 +#define HSIC_BAM_PHY_BASE 0x12542000 +#define A2_BAM_PHY_BASE 0x124C2000 +static struct usb_bam_pipe_connect msm_usb_bam_connections[2][4][2] = { + [0][0][USB_TO_PEER_PERIPHERAL] = { + .src_phy_addr = USB_BAM_PHY_BASE, + .src_pipe_index = 11, + .dst_phy_addr = A2_BAM_PHY_BASE, + .dst_pipe_index = 0, + .data_fifo_base_offset = 0x1100, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x1700, + .desc_fifo_size = 0x300, + }, + [0][0][PEER_PERIPHERAL_TO_USB] = { + .src_phy_addr = A2_BAM_PHY_BASE, + .src_pipe_index = 1, + .dst_phy_addr = USB_BAM_PHY_BASE, + .dst_pipe_index = 10, + .data_fifo_base_offset = 0xa00, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x1000, + .desc_fifo_size = 0x100, + }, + [0][1][USB_TO_PEER_PERIPHERAL] = { + .src_phy_addr = USB_BAM_PHY_BASE, + .src_pipe_index = 13, + .dst_phy_addr = A2_BAM_PHY_BASE, + .dst_pipe_index = 2, + .data_fifo_base_offset = 0x2100, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x2700, + .desc_fifo_size = 0x300, + }, + [0][1][PEER_PERIPHERAL_TO_USB] = { + .src_phy_addr = A2_BAM_PHY_BASE, + .src_pipe_index = 3, + .dst_phy_addr = USB_BAM_PHY_BASE, + .dst_pipe_index = 12, + .data_fifo_base_offset = 0x1a00, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x2000, + .desc_fifo_size = 0x100, + }, + [0][2][USB_TO_PEER_PERIPHERAL] = { + .src_phy_addr = USB_BAM_PHY_BASE, + .src_pipe_index = 15, + .dst_phy_addr = A2_BAM_PHY_BASE, + .dst_pipe_index = 4, + .data_fifo_base_offset = 0x3100, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x3700, + .desc_fifo_size = 0x300, + }, + [0][2][PEER_PERIPHERAL_TO_USB] = { + .src_phy_addr = A2_BAM_PHY_BASE, + .src_pipe_index = 5, + .dst_phy_addr = USB_BAM_PHY_BASE, + .dst_pipe_index = 14, + .data_fifo_base_offset = 0x2a00, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x3000, + .desc_fifo_size = 0x100, + }, + [1][0][USB_TO_PEER_PERIPHERAL] = { + .src_phy_addr = HSIC_BAM_PHY_BASE, + .src_pipe_index = 1, + .dst_phy_addr = A2_BAM_PHY_BASE, + .dst_pipe_index = 0, + .data_fifo_base_offset = 0x1100, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x1700, + .desc_fifo_size = 0x300, + }, + [1][0][PEER_PERIPHERAL_TO_USB] = { + .src_phy_addr = A2_BAM_PHY_BASE, + .src_pipe_index = 1, + .dst_phy_addr = HSIC_BAM_PHY_BASE, + .dst_pipe_index = 0, + .data_fifo_base_offset = 0xa00, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x1000, + .desc_fifo_size = 0x100, + }, + [1][1][USB_TO_PEER_PERIPHERAL] = { + .src_phy_addr = HSIC_BAM_PHY_BASE, + .src_pipe_index = 3, + .dst_phy_addr = A2_BAM_PHY_BASE, + .dst_pipe_index = 2, + .data_fifo_base_offset = 0x2100, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x2700, + .desc_fifo_size = 0x300, + }, + [1][1][PEER_PERIPHERAL_TO_USB] = { + .src_phy_addr = A2_BAM_PHY_BASE, + .src_pipe_index = 3, + .dst_phy_addr = HSIC_BAM_PHY_BASE, + .dst_pipe_index = 2, + .data_fifo_base_offset = 0x1a00, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x2000, + .desc_fifo_size = 0x100, + }, + [1][2][USB_TO_PEER_PERIPHERAL] = { + .src_phy_addr = HSIC_BAM_PHY_BASE, + .src_pipe_index = 5, + .dst_phy_addr = A2_BAM_PHY_BASE, + .dst_pipe_index = 4, + .data_fifo_base_offset = 0x3100, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x3700, + .desc_fifo_size = 0x300, + }, + [1][2][PEER_PERIPHERAL_TO_USB] = { + .src_phy_addr = A2_BAM_PHY_BASE, + .src_pipe_index = 5, + .dst_phy_addr = HSIC_BAM_PHY_BASE, + .dst_pipe_index = 4, + .data_fifo_base_offset = 0x2a00, + .data_fifo_size = 0x600, + .desc_fifo_base_offset = 0x3000, + .desc_fifo_size = 0x100, + } +}; + +static struct msm_usb_bam_platform_data msm_usb_bam_pdata = { + .connections = &msm_usb_bam_connections[0][0][0], +#ifndef CONFIG_USB_CI13XXX_MSM_HSIC + .usb_active_bam = HSUSB_BAM, +#else + .usb_active_bam = HSIC_BAM, +#endif + .usb_bam_num_pipes = 16, +}; + +static struct msm_otg_platform_data msm_otg_pdata = { + .mode = USB_OTG, + .otg_control = OTG_PHY_CONTROL, + .phy_type = SNPS_28NM_INTEGRATED_PHY, + .vbus_power = msm_hsusb_vbus_power, + .disable_reset_on_disconnect = true, + .enable_lpm_on_dev_suspend = true, +}; + +static struct msm_hsic_peripheral_platform_data msm_hsic_peripheral_pdata = { + .keep_core_clk_on_suspend_workaround = true, +}; + +#define PID_MAGIC_ID 0x71432909 +#define SERIAL_NUM_MAGIC_ID 0x61945374 +#define SERIAL_NUMBER_LENGTH 127 +#define DLOAD_USB_BASE_ADD 0x2B0000C8 + +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) { + memset(dload->serial_number, 0, SERIAL_NUMBER_LENGTH); + goto out; + } + + dload->magic_struct.serial_num = SERIAL_NUM_MAGIC_ID; + strlcpy(dload->serial_number, snum, SERIAL_NUMBER_LENGTH); +out: + iounmap(dload); + return 0; +} + +static struct platform_device msm_wlan_ar6000_pm_device = { + .name = "wlan_ar6000_pm_dev", + .id = -1, +}; + +static int __init msm9615_init_ar6000pm(void) +{ + return platform_device_register(&msm_wlan_ar6000_pm_device); +} + +#ifdef CONFIG_LTC4088_CHARGER +static struct platform_device msm_device_charger = { + .name = LTC4088_CHARGER_DEV_NAME, + .id = -1, + .dev = { + .platform_data = <c4088_chg_pdata, + }, +}; +#endif + +static struct tsens_platform_data msm_tsens_pdata = { + .tsens_factor = 1000, + .hw_type = MDM_9615, + .tsens_num_sensor = 5, + .slope = {1176, 1162, 1162, 1149, 1176}, +}; + +static struct platform_device msm_tsens_device = { + .name = "tsens8960-tm", + .id = -1, +}; + +static struct platform_device *common_devices[] = { + &msm9615_device_dmov, + &msm_device_smd, +#ifdef CONFIG_LTC4088_CHARGER + &msm_device_charger, +#endif + &msm_device_otg, + &msm_device_hsic_peripheral, + &msm_device_gadget_peripheral, + &msm_device_hsusb_host, + &msm_device_hsic_host, + &msm_device_usb_bam, + &msm_android_usb_device, + &msm9615_device_uart_gsbi4, + &msm9615_device_ext_2p95v_vreg, + &msm9615_device_ssbi_pmic1, + &msm9615_device_qup_i2c_gsbi5, + &msm9615_device_qup_spi_gsbi3, + &msm_device_sps, + &msm9615_slim_ctrl, + &msm_device_nand, + &msm_device_bam_dmux, + &msm9615_rpm_device, +#ifdef CONFIG_HW_RANDOM_MSM + &msm_device_rng, +#endif +#ifdef CONFIG_ION_MSM + &ion_dev, +#endif + + &msm_pcm, + &msm_multi_ch_pcm, + &msm_pcm_routing, + &msm_cpudai0, + &msm_cpudai1, + &msm_cpudai_bt_rx, + &msm_cpudai_bt_tx, + &msm_cpu_fe, + &msm_stub_codec, + &msm_voice, + &msm_voip, + &msm_i2s_cpudai0, + &msm_i2s_cpudai1, + &msm_pcm_hostless, + &msm_cpudai_afe_01_rx, + &msm_cpudai_afe_01_tx, + &msm_cpudai_afe_02_rx, + &msm_cpudai_afe_02_tx, + &msm_pcm_afe, + &msm_cpudai_auxpcm_rx, + &msm_cpudai_auxpcm_tx, + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + &msm9615_qcrypto_device, +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + &msm9615_qcedev_device, +#endif + &msm9615_device_watchdog, + &msm_bus_9615_sys_fabric, + &msm_bus_def_fab, + &msm9615_rpm_log_device, + &msm9615_rpm_stat_device, + &msm_tsens_device, +}; + +static void __init msm9615_i2c_init(void) +{ + u8 mach_mask = 0; + int i; + /* Mask is hardcoded to SURF (CDP). + * works on MTP with same configuration. + */ + mach_mask = I2C_SURF; + if (machine_is_msm9615_cdp()) + mach_mask = I2C_SURF; + else if (machine_is_msm9615_mtp()) + mach_mask = I2C_FFA; + else + pr_err("unmatched machine ID in register_i2c_devices\n"); + msm9615_device_qup_i2c_gsbi5.dev.platform_data = + &msm9615_i2c_qup_gsbi5_pdata; + for (i = 0; i < ARRAY_SIZE(msm9615_i2c_devices); ++i) { + if (msm9615_i2c_devices[i].machs & mach_mask) { + i2c_register_board_info(msm9615_i2c_devices[i].bus, + msm9615_i2c_devices[i].info, + msm9615_i2c_devices[i].len); + } + } +} + +static void __init msm9615_reserve(void) +{ +#ifdef CONFIG_ION_MSM + reserve_info = &msm9615_reserve_info; + msm_reserve(); +#endif +} + +static void __init msm9615_common_init(void) +{ + struct android_usb_platform_data *android_pdata = + msm_android_usb_device.dev.platform_data; + + msm9615_device_init(); + msm9615_init_gpiomux(); + msm9615_i2c_init(); + regulator_suppress_info_printing(); + platform_device_register(&msm9615_device_rpm_regulator); + msm_xo_init(); + msm_clock_init(&msm9615_clock_init_data); + msm9615_init_buses(); + msm9615_device_qup_spi_gsbi3.dev.platform_data = + &msm9615_qup_spi_gsbi3_pdata; + msm9615_device_ssbi_pmic1.dev.platform_data = + &msm9615_ssbi_pm8018_pdata; + pm8018_platform_data.num_regulators = msm_pm8018_regulator_pdata_len; + + msm_device_otg.dev.platform_data = &msm_otg_pdata; + msm_otg_pdata.phy_init_seq = shelby_phy_init_seq; + msm_device_hsic_peripheral.dev.platform_data = + &msm_hsic_peripheral_pdata; + msm_device_usb_bam.dev.platform_data = &msm_usb_bam_pdata; + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + msm9615_pm8xxx_gpio_mpp_init(); + acpuclk_init(&acpuclk_9615_soc_data); + + /* Ensure ar6000pm device is registered before MMC/SDC */ + msm9615_init_ar6000pm(); + + msm9615_init_mmc(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + android_pdata->update_pid_and_serial_num = + usb_diag_update_pid_and_serial_num; + msm_pm_boot_pdata.p_addr = allocate_contiguous_ebi_nomap(SZ_8, SZ_64K); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_tsens_early_init(&msm_tsens_pdata); +} + +static void __init msm9615_cdp_init(void) +{ + msm9615_common_init(); +#ifdef CONFIG_FB_MSM + mdm9615_init_fb(); +#endif +} + +static void __init msm9615_mtp_init(void) +{ + msm9615_common_init(); +} + +#ifdef CONFIG_FB_MSM +static void __init mdm9615_allocate_memory_regions(void) +{ + mdm9615_allocate_fb_region(); +} +#endif + +MACHINE_START(MSM9615_CDP, "QCT MSM9615 CDP") + .map_io = msm9615_map_io, + .init_irq = msm9615_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm9615_cdp_init, + .reserve = msm9615_reserve, +#ifdef CONFIG_FB_MSM + .init_early = mdm9615_allocate_memory_regions, +#endif +MACHINE_END + +MACHINE_START(MSM9615_MTP, "QCT MSM9615 MTP") + .map_io = msm9615_map_io, + .init_irq = msm9615_init_irq, + .handle_irq = gic_handle_irq, + .timer = &msm_timer, + .init_machine = msm9615_mtp_init, + .reserve = msm9615_reserve, +MACHINE_END diff --git a/arch/arm/mach-msm/board-9615.h b/arch/arm/mach-msm/board-9615.h new file mode 100644 index 0000000000000000000000000000000000000000..68d9951c18252fe96946246e6c63b52078c11248 --- /dev/null +++ b/arch/arm/mach-msm/board-9615.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_9615_H +#define __ARCH_ARM_MACH_MSM_BOARD_9615_H + +#include +#include +#include + +/* + * MDM9x15 I2S. + */ +#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 I2C_LIQUID (1 << 5) + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; +#endif +/* Tabla slave address for I2C */ +#define TABLA_I2C_SLAVE_ADDR 0x0d +#define TABLA_ANALOG_I2C_SLAVE_ADDR 0x77 +#define TABLA_DIGITAL1_I2C_SLAVE_ADDR 0x66 +#define TABLA_DIGITAL2_I2C_SLAVE_ADDR 0x55 +#define MSM_9615_GSBI5_QUP_I2C_BUS_ID 0 +/* + * MDM9x15 I2S. + */ + +/* Macros assume PMIC GPIOs and MPPs start at 1 */ +#define PM8018_GPIO_BASE NR_GPIO_IRQS +#define PM8018_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8018_GPIO_BASE) +#define PM8018_MPP_BASE (PM8018_GPIO_BASE + PM8018_NR_GPIOS) +#define PM8018_MPP_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8018_MPP_BASE) +#define PM8018_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) +#define PM8018_MPP_IRQ_BASE (PM8018_IRQ_BASE + NR_GPIO_IRQS) + +extern struct pm8xxx_regulator_platform_data + msm_pm8018_regulator_pdata[] __devinitdata; + +extern int msm_pm8018_regulator_pdata_len __devinitdata; + +extern struct rpm_regulator_platform_data +msm_rpm_regulator_9615_pdata __devinitdata; + +#define GPIO_VREG_ID_EXT_2P95V 0 + +extern struct gpio_regulator_platform_data msm_gpio_regulator_pdata[]; +uint32_t msm9615_rpm_get_swfi_latency(void); +int msm9615_init_gpiomux(void); +void msm9615_init_mmc(void); +void mdm9615_allocate_fb_region(void); +void mdm9615_init_fb(void); +#endif diff --git a/arch/arm/mach-msm/board-9625-gpiomux.c b/arch/arm/mach-msm/board-9625-gpiomux.c new file mode 100644 index 0000000000000000000000000000000000000000..e28c734a7195b8b28f93f981de0b3c28dea51ce8 --- /dev/null +++ b/arch/arm/mach-msm/board-9625-gpiomux.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the 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 struct gpiomux_setting gpio_uart_config = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_HIGH, +}; + +static struct msm_gpiomux_config msm_blsp_configs[] __initdata = { + { + .gpio = 45, /* BLSP1 UART TX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_uart_config, + }, + }, + { + .gpio = 46, /* BLSP1 UART RX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_uart_config, + }, + }, +}; + +void __init msm9625_init_gpiomux(void) +{ + int rc; + + rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err(KERN_ERR "msm9625_init_gpiomux failed %d\n", rc); + return; + } + + msm_gpiomux_install(msm_blsp_configs, ARRAY_SIZE(msm_blsp_configs)); +} diff --git a/arch/arm/mach-msm/board-9625.c b/arch/arm/mach-msm/board-9625.c new file mode 100644 index 0000000000000000000000000000000000000000..60dfe3c287f10a1854693f9033fc77fc435e558f --- /dev/null +++ b/arch/arm/mach-msm/board-9625.c @@ -0,0 +1,104 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "clock.h" + +static struct clk_lookup msm_clocks_dummy[] = { + CLK_DUMMY("core_clk", BLSP1_UART_CLK, "msm_serial_hsl.0", OFF), + CLK_DUMMY("iface_clk", BLSP1_UART_CLK, "msm_serial_hsl.0", OFF), + CLK_DUMMY("phy_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("core_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("alt_core_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("iface_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("xo", NULL, "msm_otg", OFF), + CLK_DUMMY("dfab_clk", DFAB_CLK, NULL, 0), + CLK_DUMMY("dma_bam_pclk", DMA_BAM_P_CLK, NULL, 0), + CLK_DUMMY("mem_clk", NULL, NULL, 0), + CLK_DUMMY("core_clk", NULL, "spi_qsd.1", OFF), + CLK_DUMMY("iface_clk", NULL, "spi_qsd.1", OFF), + CLK_DUMMY("core_clk", NULL, "f9966000.i2c", 0), + CLK_DUMMY("iface_clk", NULL, "f9966000.i2c", 0), + CLK_DUMMY("core_clk", NULL, "fe12f000.slim", OFF), +}; + +struct clock_init_data msm_dummy_clock_init_data __initdata = { + .table = msm_clocks_dummy, + .size = ARRAY_SIZE(msm_clocks_dummy), +}; + +static struct of_device_id irq_match[] __initdata = { + { .compatible = "qcom,msm-qgic2", .data = gic_of_init, }, + { .compatible = "qcom,msm-gpio", .data = msm_gpio_of_init, }, + {} +}; + +static const char *msm9625_dt_match[] __initconst = { + "qcom,msm9625", + NULL +}; + +static struct of_dev_auxdata msm9625_auxdata_lookup[] __initdata = { + OF_DEV_AUXDATA("qcom,msm-lsuart-v14", 0xF991F000, \ + "msm_serial_hsl.0", NULL), + {} +}; + +void __init msm9625_init_irq(void) +{ + of_irq_init(irq_match); +} + +static void __init msm_dt_timer_init(void) +{ + arch_timer_of_register(); +} + +static struct sys_timer msm_dt_timer = { + .init = msm_dt_timer_init +}; + +void __init msm9625_init(void) +{ + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed\n", __func__); + msm_clock_init(&msm_dummy_clock_init_data); + of_platform_populate(NULL, of_default_bus_match_table, + msm9625_auxdata_lookup, NULL); +} + +DT_MACHINE_START(MSM_DT, "Qualcomm MSM (Flattened Device Tree)") + .map_io = msm_map_msm9625_io, + .init_irq = msm9625_init_irq, + .init_machine = msm9625_init, + .handle_irq = gic_handle_irq, + .timer = &msm_dt_timer, + .dt_compat = msm9625_dt_match, + .nr_irqs = -1, +MACHINE_END diff --git a/arch/arm/mach-msm/board-copper-gpiomux.c b/arch/arm/mach-msm/board-copper-gpiomux.c new file mode 100644 index 0000000000000000000000000000000000000000..caba698438cf7ee7813af81fd10568703634b5a6 --- /dev/null +++ b/arch/arm/mach-msm/board-copper-gpiomux.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the 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 KS8851_IRQ_GPIO 90 + +static struct gpiomux_setting gpio_uart_config = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_HIGH, +}; + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) +static struct gpiomux_setting gpio_eth_config = { + .pull = GPIOMUX_PULL_NONE, + .drv = GPIOMUX_DRV_8MA, + .func = GPIOMUX_FUNC_GPIO, +}; + +static struct gpiomux_setting gpio_spi_cs_config = { + .func = GPIOMUX_FUNC_4, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gpio_spi_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct msm_gpiomux_config msm_eth_configs[] = { + { + .gpio = KS8851_IRQ_GPIO, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, +}; +#endif +static struct gpiomux_setting gpio_i2c_config = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + + +static struct msm_gpiomux_config msm_blsp_configs[] __initdata = { +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) + { + .gpio = 0, /* BLSP1 QUP SPI_DATA_MOSI */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, + { + .gpio = 1, /* BLSP1 QUP SPI_DATA_MISO */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, + { + .gpio = 3, /* BLSP1 QUP SPI_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_config, + }, + }, + { + .gpio = 9, /* BLSP1 QUP SPI_CS_N */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_spi_cs_config, + }, + }, +#endif + { + .gpio = 83, /* BLSP11 QUP I2C_DAT */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_i2c_config, + }, + }, + { + .gpio = 84, /* BLSP11 QUP I2C_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_i2c_config, + }, + }, + { + .gpio = 45, /* BLSP8 UART TX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_uart_config, + }, + }, + { + .gpio = 46, /* BLSP8 UART RX */ + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_uart_config, + }, + }, +}; + +void __init msm_copper_init_gpiomux(void) +{ + int rc; + + rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err(KERN_ERR "msmcopper_init_gpiomux failed %d\n", rc); + return; + } + +#if defined(CONFIG_KS8851) || defined(CONFIG_KS8851_MODULE) + msm_gpiomux_install(msm_eth_configs, ARRAY_SIZE(msm_eth_configs)); +#endif + msm_gpiomux_install(msm_blsp_configs, ARRAY_SIZE(msm_blsp_configs)); +} diff --git a/arch/arm/mach-msm/board-copper-regulator.c b/arch/arm/mach-msm/board-copper-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..75438725124d40707a6f1bb70cb3ec5fa966e7fe --- /dev/null +++ b/arch/arm/mach-msm/board-copper-regulator.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 VREG_CONSUMERS(_name) \ + static struct regulator_consumer_supply vreg_consumers_##_name[] + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +VREG_CONSUMERS(K0) = { + REGULATOR_SUPPLY("krait0", NULL), +}; +VREG_CONSUMERS(K1) = { + REGULATOR_SUPPLY("krait1", NULL), +}; +VREG_CONSUMERS(K2) = { + REGULATOR_SUPPLY("krait2", NULL), +}; +VREG_CONSUMERS(K3) = { + REGULATOR_SUPPLY("krait3", NULL), +}; + +#define PM8X41_VREG_INIT(_id, _name, _min_uV, _max_uV, _modes, _ops, \ + _always_on, _supply_regulator, _hpm_min, _system_uA) \ + struct stub_regulator_pdata vreg_dev_##_id##_pdata __devinitdata = { \ + .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 = 0, \ + .always_on = _always_on, \ + .name = _name, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + .supply_regulator = _supply_regulator, \ + }, \ + .hpm_min_load = _hpm_min, \ + .system_uA = _system_uA, \ + } + +#define KRAIT_PWR(_id, _name, _always_on, _min_uV, _max_uV, \ + _supply_regulator, _hpm_min, _system_uA) \ + PM8X41_VREG_INIT(_id, _name, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \ + | REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, _always_on, \ + _supply_regulator, _hpm_min, _system_uA) + +/* ID name a_on min_uV max_uV supply hpm_min sys_uA */ +KRAIT_PWR(K0, "krait0", 0, 850000, 1100000, NULL, 100000, 0); +KRAIT_PWR(K1, "krait1", 0, 850000, 1100000, NULL, 100000, 0); +KRAIT_PWR(K2, "krait2", 0, 850000, 1100000, NULL, 100000, 0); +KRAIT_PWR(K3, "krait3", 0, 850000, 1100000, NULL, 100000, 0); + +#define VREG_DEVICE(_name, _devid) \ + vreg_device_##_name __devinitdata = \ + { \ + .name = STUB_REGULATOR_DRIVER_NAME, \ + .id = _devid, \ + .dev = { .platform_data = &vreg_dev_##_name##_pdata }, \ + } + +static struct platform_device VREG_DEVICE(K0, 0); +static struct platform_device VREG_DEVICE(K1, 1); +static struct platform_device VREG_DEVICE(K2, 2); +static struct platform_device VREG_DEVICE(K3, 3); + +struct platform_device *msm_copper_stub_regulator_devices[] __devinitdata = { + &vreg_device_K0, + &vreg_device_K1, + &vreg_device_K2, + &vreg_device_K3, +}; + +int msm_copper_stub_regulator_devices_len __devinitdata = + ARRAY_SIZE(msm_copper_stub_regulator_devices); diff --git a/arch/arm/mach-msm/board-copper.c b/arch/arm/mach-msm/board-copper.c new file mode 100644 index 0000000000000000000000000000000000000000..a8094e1802028d0073eb83a111f85138353297a8 --- /dev/null +++ b/arch/arm/mach-msm/board-copper.c @@ -0,0 +1,522 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_ION_MSM +#include +#endif +#include +#ifdef CONFIG_ANDROID_PMEM +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ION_MSM +#include +#endif +#include +#include +#include +#include +#include +#include +#include "clock.h" +#include "devices.h" +#include "spm.h" + +#define MSM_KERNEL_EBI1_MEM_SIZE 0x280000 +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +#define MSM_ION_SF_SIZE 0x4000000 /* 64 Mbytes */ +#else +#define MSM_ION_SF_SIZE 0x2800000 /* 40 Mbytes */ +#endif +#define MSM_ION_MM_FW_SIZE 0xa00000 /* (10MB) */ +#define MSM_ION_MM_SIZE 0x7800000 /* (120MB) */ +#define MSM_ION_QSECOM_SIZE 0x100000 /* (1MB) */ +#define MSM_ION_MFC_SIZE SZ_8K +#define MSM_ION_AUDIO_SIZE 0x2B4000 +#define MSM_ION_HEAP_NUM 8 + +#ifdef CONFIG_KERNEL_PMEM_EBI_REGION +static unsigned kernel_ebi1_mem_size = MSM_KERNEL_EBI1_MEM_SIZE; +static int __init kernel_ebi1_mem_size_setup(char *p) +{ + kernel_ebi1_mem_size = memparse(p, NULL); + return 0; +} +early_param("kernel_ebi1_mem_size", kernel_ebi1_mem_size_setup); +#endif + +static struct memtype_reserve msm_copper_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static int msm_copper_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +#ifdef CONFIG_ION_MSM +static struct ion_cp_heap_pdata cp_mm_ion_pdata = { + .permission_type = IPT_TYPE_MM_CARVEOUT, + .align = PAGE_SIZE, +}; + +static struct ion_cp_heap_pdata cp_mfc_ion_pdata = { + .permission_type = IPT_TYPE_MFC_SHAREDMEM, + .align = PAGE_SIZE, +}; + +static struct ion_co_heap_pdata co_ion_pdata = { + .adjacent_mem_id = INVALID_HEAP_ID, + .align = PAGE_SIZE, +}; + +static struct ion_co_heap_pdata fw_co_ion_pdata = { + .adjacent_mem_id = ION_CP_MM_HEAP_ID, + .align = SZ_128K, +}; + +/** + * These heaps are listed in the order they will be allocated. Due to + * video hardware restrictions and content protection the FW heap has to + * be allocated adjacent (below) the MM heap and the MFC heap has to be + * allocated after the MM heap to ensure MFC heap is not more than 256MB + * away from the base address of the FW heap. + * However, the order of FW heap and MM heap doesn't matter since these + * two heaps are taken care of by separate code to ensure they are adjacent + * to each other. + * Don't swap the order unless you know what you are doing! + */ +static struct ion_platform_data ion_pdata = { + .nr = MSM_ION_HEAP_NUM, + .heaps = { + { + .id = ION_SYSTEM_HEAP_ID, + .type = ION_HEAP_TYPE_SYSTEM, + .name = ION_VMALLOC_HEAP_NAME, + }, + { + .id = ION_CP_MM_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MM_HEAP_NAME, + .size = MSM_ION_MM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mm_ion_pdata, + }, + { + .id = ION_MM_FIRMWARE_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_MM_FIRMWARE_HEAP_NAME, + .size = MSM_ION_MM_FW_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &fw_co_ion_pdata, + }, + { + .id = ION_CP_MFC_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MFC_HEAP_NAME, + .size = MSM_ION_MFC_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_mfc_ion_pdata, + }, + { + .id = ION_SF_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_SF_HEAP_NAME, + .size = MSM_ION_SF_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_ion_pdata, + }, + { + .id = ION_IOMMU_HEAP_ID, + .type = ION_HEAP_TYPE_IOMMU, + .name = ION_IOMMU_HEAP_NAME, + }, + { + .id = ION_QSECOM_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_QSECOM_HEAP_NAME, + .size = MSM_ION_QSECOM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_ion_pdata, + }, + { + .id = ION_AUDIO_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_AUDIO_HEAP_NAME, + .size = MSM_ION_AUDIO_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_ion_pdata, + }, + } +}; + +static struct platform_device ion_dev = { + .name = "ion-msm", + .id = 1, + .dev = { .platform_data = &ion_pdata }, +}; + +static void __init reserve_ion_memory(void) +{ + msm_copper_reserve_table[MEMTYPE_EBI1].size += MSM_ION_MM_SIZE; + msm_copper_reserve_table[MEMTYPE_EBI1].size += MSM_ION_MM_FW_SIZE; + msm_copper_reserve_table[MEMTYPE_EBI1].size += MSM_ION_SF_SIZE; + msm_copper_reserve_table[MEMTYPE_EBI1].size += MSM_ION_MFC_SIZE; + msm_copper_reserve_table[MEMTYPE_EBI1].size += MSM_ION_QSECOM_SIZE; + msm_copper_reserve_table[MEMTYPE_EBI1].size += MSM_ION_AUDIO_SIZE; +#ifdef CONFIG_KERNEL_PMEM_EBI_REGION + msm_copper_reserve_table[MEMTYPE_EBI1].size += kernel_ebi1_mem_size; +#endif +} +#endif + +static struct resource smd_resource[] = { + { + .name = "modem_smd_in", + .start = 32 + 17, /* mss_sw_to_kpss_ipc_irq0 */ + .flags = IORESOURCE_IRQ, + }, + { + .name = "modem_smsm_in", + .start = 32 + 18, /* mss_sw_to_kpss_ipc_irq1 */ + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_smd_in", + .start = 32 + 156, /* lpass_to_kpss_ipc_irq0 */ + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_smsm_in", + .start = 32 + 157, /* lpass_to_kpss_ipc_irq1 */ + .flags = IORESOURCE_IRQ, + }, + { + .name = "wcnss_smd_in", + .start = 32 + 142, /* WcnssAppsSmdMedIrq */ + .flags = IORESOURCE_IRQ, + }, + { + .name = "wcnss_smsm_in", + .start = 32 + 144, /* RivaAppsWlanSmsmIrq */ + .flags = IORESOURCE_IRQ, + }, + { + .name = "rpm_smd_in", + .start = 32 + 168, /* rpm_to_kpss_ipc_irq4 */ + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smd_subsystem_config smd_config_list[] = { + { + .irq_config_id = SMD_MODEM, + .subsys_name = "modem", + .edge = SMD_APPS_MODEM, + + .smd_int.irq_name = "modem_smd_in", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 12, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "modem_smsm_in", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smsm_dev", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 13, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_Q6, + .subsys_name = "q6", + .edge = SMD_APPS_QDSP, + + .smd_int.irq_name = "adsp_smd_in", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 8, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "adsp_smsm_in", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smsm_dev", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 9, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_WCNSS, + .subsys_name = "wcnss", + .edge = SMD_APPS_WCNSS, + + .smd_int.irq_name = "wcnss_smd_in", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 17, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "wcnss_smsm_in", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smsm_dev", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 19, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_RPM, + .subsys_name = NULL, /* do not use PIL to load RPM */ + .edge = SMD_APPS_RPM, + + .smd_int.irq_name = "rpm_smd_in", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 0, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = NULL, /* RPM does not support SMSM */ + .smsm_int.flags = 0, + .smsm_int.irq_id = 0, + .smsm_int.device_name = NULL, + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 0, + .smsm_int.out_base = NULL, + .smsm_int.out_offset = 0, + }, +}; + +static struct smd_smem_regions aux_smem_areas[] = { + { + .phys_addr = (void *)(0xfc428000), + .size = 0x4000, + }, +}; + +static struct smd_subsystem_restart_config smd_ssr_cfg = { + .disable_smsm_reset_handshake = 1, +}; + +static struct smd_platform smd_platform_data = { + .num_ss_configs = ARRAY_SIZE(smd_config_list), + .smd_ss_configs = smd_config_list, + .smd_ssr_config = &smd_ssr_cfg, + .num_smem_areas = ARRAY_SIZE(aux_smem_areas), + .smd_smem_areas = aux_smem_areas, +}; + +struct platform_device msm_device_smd_copper = { + .name = "msm_smd", + .id = -1, + .resource = smd_resource, + .num_resources = ARRAY_SIZE(smd_resource), + .dev = { + .platform_data = &smd_platform_data, + } +}; + +static void __init msm_copper_calculate_reserve_sizes(void) +{ +#ifdef CONFIG_ION_MSM + reserve_ion_memory(); +#endif +} + +static struct reserve_info msm_copper_reserve_info __initdata = { + .memtype_reserve_table = msm_copper_reserve_table, + .calculate_reserve_sizes = msm_copper_calculate_reserve_sizes, + .paddr_to_memtype = msm_copper_paddr_to_memtype, +}; + +static void __init msm_copper_early_memory(void) +{ + reserve_info = &msm_copper_reserve_info; +} + +void __init msm_copper_reserve(void) +{ + msm_reserve(); +} + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, +}; + +#define SHARED_IMEM_TZ_BASE 0xFE805720 +static struct resource copper_tzlog_resources[] = { + { + .start = SHARED_IMEM_TZ_BASE, + .end = SHARED_IMEM_TZ_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device copper_device_tz_log = { + .name = "tz_log", + .id = 0, + .num_resources = ARRAY_SIZE(copper_tzlog_resources), + .resource = copper_tzlog_resources, +}; + + +void __init msm_copper_add_devices(void) +{ +#ifdef CONFIG_ION_MSM + platform_device_register(&ion_dev); +#endif + platform_device_register(&msm_device_smd_copper); + platform_device_register(&android_usb_device); + platform_add_devices(msm_copper_stub_regulator_devices, + msm_copper_stub_regulator_devices_len); + platform_device_register(&copper_device_tz_log); +} + +/* + * Used to satisfy dependencies for devices that need to be + * run early or in a particular order. Most likely your device doesn't fall + * into this category, and thus the driver should not be added here. The + * EPROBE_DEFER can satisfy most dependency problems. + */ +void __init msm_copper_add_drivers(void) +{ + msm_smd_init(); + msm_rpm_driver_init(); + rpm_regulator_smd_driver_init(); + msm_spm_device_init(); + regulator_stub_init(); +} + +static struct of_device_id irq_match[] __initdata = { + { .compatible = "qcom,msm-qgic2", .data = gic_of_init, }, + { .compatible = "qcom,msm-gpio", .data = msm_gpio_of_init, }, + { .compatible = "qcom,spmi-pmic-arb", .data = qpnpint_of_init, }, + {} +}; + +void __init msm_copper_init_irq(void) +{ + of_irq_init(irq_match); +} + +static struct clk_lookup msm_clocks_dummy[] = { + CLK_DUMMY("xo", XO_CLK, NULL, OFF), + CLK_DUMMY("xo", XO_CLK, "pil_pronto", OFF), + CLK_DUMMY("core_clk", BLSP2_UART_CLK, "msm_serial_hsl.0", OFF), + CLK_DUMMY("iface_clk", BLSP2_UART_CLK, "msm_serial_hsl.0", OFF), + CLK_DUMMY("core_clk", SDC1_CLK, NULL, OFF), + CLK_DUMMY("iface_clk", SDC1_P_CLK, NULL, OFF), + CLK_DUMMY("core_clk", SDC3_CLK, NULL, OFF), + CLK_DUMMY("iface_clk", SDC3_P_CLK, NULL, OFF), + CLK_DUMMY("phy_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("core_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("iface_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("xo", NULL, "msm_otg", OFF), + CLK_DUMMY("dfab_clk", DFAB_CLK, NULL, 0), + CLK_DUMMY("dma_bam_pclk", DMA_BAM_P_CLK, NULL, 0), + CLK_DUMMY("mem_clk", NULL, NULL, 0), + CLK_DUMMY("core_clk", SPI_CLK, "spi_qsd.1", OFF), + CLK_DUMMY("iface_clk", SPI_P_CLK, "spi_qsd.1", OFF), + CLK_DUMMY("core_clk", NULL, "f9966000.i2c", 0), + CLK_DUMMY("iface_clk", NULL, "f9966000.i2c", 0), + CLK_DUMMY("core_clk", NULL, "fe12f000.slim", OFF), +}; + +struct clock_init_data msm_dummy_clock_init_data __initdata = { + .table = msm_clocks_dummy, + .size = ARRAY_SIZE(msm_clocks_dummy), +}; + +static struct of_dev_auxdata msm_copper_auxdata_lookup[] __initdata = { + OF_DEV_AUXDATA("qcom,msm-lsuart-v14", 0xF991F000, \ + "msm_serial_hsl.0", NULL), + OF_DEV_AUXDATA("qcom,hsusb-otg", 0xF9A55000, \ + "msm_otg", NULL), + OF_DEV_AUXDATA("qcom,spi-qup-v2", 0xF9924000, \ + "spi_qsd.1", NULL), + OF_DEV_AUXDATA("qcom,spmi-pmic-arb", 0xFC4C0000, \ + "spmi-pmic-arb.0", NULL), + OF_DEV_AUXDATA("qcom,msm-sdcc", 0xF9824000, \ + "msm_sdcc.1", NULL), + OF_DEV_AUXDATA("qcom,msm-sdcc", 0xF98A4000, \ + "msm_sdcc.2", NULL), + OF_DEV_AUXDATA("qcom,msm-sdcc", 0xF9864000, \ + "msm_sdcc.3", NULL), + OF_DEV_AUXDATA("qcom,msm-sdcc", 0xF98E4000, \ + "msm_sdcc.4", NULL), + OF_DEV_AUXDATA("qcom,pil-q6v5-lpass", 0xFE200000, \ + "pil-q6v5-lpass", NULL), + OF_DEV_AUXDATA("qcom,pil-pronto", 0xFB21B000, \ + "pil_pronto", NULL), + OF_DEV_AUXDATA("qcom,msm-rng", 0xF9BFF000, \ + "msm_rng", NULL), + {} +}; + +void __init msm_copper_init(struct of_dev_auxdata **adata) +{ + msm_copper_init_gpiomux(); + + if (machine_is_copper_rumi()) + msm_clock_init(&msm_dummy_clock_init_data); + else + msm_clock_init(&msmcopper_clock_init_data); + + *adata = msm_copper_auxdata_lookup; + + regulator_has_full_constraints(); +} + +void __init msm_copper_very_early(void) +{ + msm_copper_early_memory(); +} diff --git a/arch/arm/mach-msm/board-dt.c b/arch/arm/mach-msm/board-dt.c new file mode 100644 index 0000000000000000000000000000000000000000..674df0929dfc102e8531e4821acd42fbc506cc6c --- /dev/null +++ b/arch/arm/mach-msm/board-dt.c @@ -0,0 +1,91 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 void __init msm_dt_timer_init(void) +{ + arch_timer_of_register(); +} + +static struct sys_timer msm_dt_timer = { + .init = msm_dt_timer_init +}; + +static void __init msm_dt_init_irq(void) +{ + if (machine_is_copper()) + msm_copper_init_irq(); +} + +static void __init msm_dt_map_io(void) +{ + if (early_machine_is_copper()) + msm_map_copper_io(); + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed\n", __func__); +} + +static void __init msm_dt_init(void) +{ + struct of_dev_auxdata *adata = NULL; + + if (machine_is_copper()) + msm_copper_init(&adata); + + of_platform_populate(NULL, of_default_bus_match_table, adata, NULL); + if (machine_is_copper()) { + msm_copper_add_devices(); + msm_copper_add_drivers(); + } +} + +static const char *msm_dt_match[] __initconst = { + "qcom,msmcopper", + NULL +}; + +static void __init msm_dt_reserve(void) +{ + if (early_machine_is_copper()) + msm_copper_reserve(); +} + +static void __init msm_dt_init_very_early(void) +{ + if (early_machine_is_copper()) + msm_copper_very_early(); +} + +DT_MACHINE_START(MSM_DT, "Qualcomm MSM (Flattened Device Tree)") + .map_io = msm_dt_map_io, + .init_irq = msm_dt_init_irq, + .init_machine = msm_dt_init, + .handle_irq = gic_handle_irq, + .timer = &msm_dt_timer, + .dt_compat = msm_dt_match, + .nr_irqs = -1, + .reserve = msm_dt_reserve, + .init_very_early = msm_dt_init_very_early, +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 0000000000000000000000000000000000000000..6b440872c437ff75e1748f400a7942f5efd18c7f --- /dev/null +++ b/arch/arm/mach-msm/board-fsm9xxx.c @@ -0,0 +1,926 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "timer.h" +#include "acpuclock.h" +#include "pm.h" +#include "spm.h" +#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_MAC_TXD_3 119 +#define GPIO_MAC_TXD_2 120 +#define GPIO_MAC_TXD_1 121 +#define GPIO_MAC_TXD_0 122 +#define GPIO_MAC_TX_EN 123 +#define GPIO_MAC_MDIO 127 +#define GPIO_MAC_MDC 128 +#define GPIO_MAC_TX_CLK 133 +#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 GPIO_USER_FIRST 58 +#define GPIO_USER_LAST 63 + +#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 PM8058_MPP_BASE (NR_MSM_GPIOS + 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 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 + */ +struct pm8xxx_mpp_init_info { + unsigned mpp; + struct pm8xxx_mpp_config_data config; +}; + +#define PM8XXX_MPP_INIT(_mpp, _type, _level, _control) \ +{ \ + .mpp = PM8058_MPP_PM_TO_SYS(_mpp), \ + .config = { \ + .type = PM8XXX_MPP_TYPE_##_type, \ + .level = _level, \ + .control = PM8XXX_MPP_##_control, \ + } \ +} + +static int pm8058_gpios_init(void) +{ + int i; + int rc; + struct pm8058_gpio_cfg { + int gpio; + struct pm_gpio cfg; + }; + + struct pm8058_gpio_cfg gpio_cfgs[] = { + { /* 5V PA Power */ + PM8058_GPIO_PM_TO_SYS(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 */ + PM8058_GPIO_PM_TO_SYS(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 = pm8xxx_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, i; + + struct pm8xxx_mpp_init_info pm8058_mpps[] = { + PM8XXX_MPP_INIT(PMIC_MPP_3, A_OUTPUT, + PM8XXX_MPP_AOUT_LVL_1V25_2, AOUT_CTRL_ENABLE), + PM8XXX_MPP_INIT(PMIC_MPP_6, A_OUTPUT, + PM8XXX_MPP_AOUT_LVL_1V25_2, AOUT_CTRL_ENABLE), + }; + + for (i = 0; i < ARRAY_SIZE(pm8058_mpps); i++) { + rc = pm8xxx_mpp_config(pm8058_mpps[i].mpp, + &pm8058_mpps[i].config); + if (rc) { + pr_err("%s: Config %d mpp pm 8058 failed\n", + __func__, pm8058_mpps[i].mpp); + return rc; + } + } + + return 0; +} + +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) \ + { \ + .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], \ + }, \ + .id = _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_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), +}; + +#ifdef CONFIG_SENSORS_MSM_ADC +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, i; + struct pm8xxx_mpp_init_info xoadc_mpps[] = { + PM8XXX_MPP_INIT(PMIC_MPP_7, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH5, + AOUT_CTRL_DISABLE), + PM8XXX_MPP_INIT(PMIC_MPP_10, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH6, + AOUT_CTRL_DISABLE), + }; + for (i = 0; i < ARRAY_SIZE(xoadc_mpps); i++) { + rc = pm8xxx_mpp_config(xoadc_mpps[i].mpp, + &xoadc_mpps[i].config); + if (rc) { + pr_err("%s: Config MPP %d of PM8058 failed\n", + __func__, xoadc_mpps[i].mpp); + } + } +} + +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 pm8058_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 + +#define XO_CONSUMERS(_id) \ + static struct regulator_consumer_supply xo_consumers_##_id[] + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +XO_CONSUMERS(A0) = { + REGULATOR_SUPPLY("8058_xo_a0", NULL), + REGULATOR_SUPPLY("a0_clk_buffer", "fsm_xo_driver"), +}; +XO_CONSUMERS(A1) = { + REGULATOR_SUPPLY("8058_xo_a1", NULL), + REGULATOR_SUPPLY("a1_clk_buffer", "fsm_xo_driver"), +}; + +#define PM8058_XO_INIT(_id, _modes, _ops, _always_on) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_modes_mask = _modes, \ + .valid_ops_mask = _ops, \ + .boot_on = 1, \ + .always_on = _always_on, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(xo_consumers_##_id),\ + .consumer_supplies = xo_consumers_##_id, \ + }, \ + .id = PM8058_XO_ID_##_id, \ + } + +#define PM8058_XO_INIT_AX(_id) \ + PM8058_XO_INIT(_id, REGULATOR_MODE_NORMAL, REGULATOR_CHANGE_STATUS, 0) + +static struct pm8058_xo_pdata pm8058_xo_init_pdata[] = { + PM8058_XO_INIT_AX(A0), + PM8058_XO_INIT_AX(A1), +}; + +#define PM8058_GPIO_INT 47 + +static struct pm8xxx_irq_platform_data pm8xxx_irq_pdata = { + .irq_base = PMIC8058_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(PM8058_GPIO_INT), + .irq_trigger_flag = IRQF_TRIGGER_LOW, +}; + +static struct pm8xxx_gpio_platform_data pm8xxx_gpio_pdata = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(0), +}; + +static struct pm8xxx_mpp_platform_data pm8xxx_mpp_pdata = { + .mpp_base = PM8058_MPP_PM_TO_SYS(0), +}; + +static struct pm8058_platform_data pm8058_fsm9xxx_data = { + .irq_pdata = &pm8xxx_irq_pdata, + .gpio_pdata = &pm8xxx_gpio_pdata, + .mpp_pdata = &pm8xxx_mpp_pdata, + .regulator_pdatas = pm8058_vreg_init, + .num_regulators = ARRAY_SIZE(pm8058_vreg_init), + .xo_buffer_pdata = pm8058_xo_init_pdata, + .num_xo_buffers = ARRAY_SIZE(pm8058_xo_init_pdata), +#ifdef CONFIG_SENSORS_MSM_ADC + .xoadc_pdata = &pm8058_xoadc_pdata, +#endif +}; + +#ifdef CONFIG_MSM_SSBI +static struct msm_ssbi_platform_data fsm9xxx_ssbi_pm8058_pdata = { + .controller_type = FSM_SBI_CTRL_SSBI, + .slave = { + .name = "pm8058-core", + .platform_data = &pm8058_fsm9xxx_data, + }, +}; +#endif + +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); + + 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_8MA), "MAC_RST_N" }, + { GPIO_CFG(GPIO_MAC_TXD_3, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "MAC_TXD_3"}, + { GPIO_CFG(GPIO_MAC_TXD_2, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "MAC_TXD_2"}, + { GPIO_CFG(GPIO_MAC_TXD_1, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "MAC_TXD_1"}, + { GPIO_CFG(GPIO_MAC_TXD_0, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "MAC_TXD_0"}, + { GPIO_CFG(GPIO_MAC_TX_EN, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "MAC_TX_EN"}, + { GPIO_CFG(GPIO_MAC_TX_CLK, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_10MA), "MAC_TX_CLK"}, + { GPIO_CFG(GPIO_MAC_MDIO, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_6MA), "MDIO_MAC_MDIO"}, + { GPIO_CFG(GPIO_MAC_MDC, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_6MA), "MDC_MAC_MDC"}, +}; + +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_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, +}; +#endif + +#if defined(CONFIG_I2C_SSBI) || defined(CONFIG_MSM_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 + +/* + * User GPIOs + */ + +static void user_gpios_init(void) +{ + unsigned int gpio; + + for (gpio = GPIO_USER_FIRST; gpio <= GPIO_USER_LAST; ++gpio) + gpio_tlmm_config(GPIO_CFG(gpio, 0, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE); +} + +/* + * 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, + .bus_scale_table = NULL, +}; + +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, + .bus_scale_table = NULL, +}; + +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_MSM_SSBI + &msm_device_ssbi_pmic1, +#endif +#ifdef CONFIG_I2C_SSBI + &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, + &fsm_xo_device, + &fsm9xxx_device_watchdog, +}; + +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) +{ + acpuclk_init(&acpuclk_9xxx_soc_data); + + regulator_has_full_constraints(); + +#if defined(CONFIG_I2C_SSBI) || defined(CONFIG_MSM_SSBI) + fsm9xxx_init_ssbi_gpio(); +#endif +#ifdef CONFIG_MSM_SSBI + msm_device_ssbi_pmic1.dev.platform_data = + &fsm9xxx_ssbi_pm8058_pdata; +#endif + buses_init(); + + platform_add_devices(devices, ARRAY_SIZE(devices)); + +#ifdef CONFIG_MSM_SPM + msm_spm_init(&msm_spm_data, 1); +#endif + pm8058_gpios_init(); + pm8058_mpps_init(); + phy_init(); + grfc_init(); + user_gpios_init(); + +#ifdef CONFIG_SERIAL_MSM_CONSOLE + fsm9xxx_init_uart1(); +#endif +#ifdef CONFIG_I2C_SSBI + 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(&fsm9xxx_clock_init_data); + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed!\n", + __func__); + +} + +MACHINE_START(FSM9XXX_SURF, "QCT FSM9XXX") + .atag_offset = 0x100, + .map_io = fsm9xxx_map_io, + .init_irq = fsm9xxx_init_irq, + .handle_irq = vic_handle_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 0000000000000000000000000000000000000000..49c1075627d324d0994a2643295f4b81d1cec90a --- /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 0000000000000000000000000000000000000000..2a6a6ed020499bc2535eaef354fcc8fb880cc8c0 --- /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 + +#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 0000000000000000000000000000000000000000..edcdacb34c274eb3b9b08add4c0047fda1f67477 --- /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 0000000000000000000000000000000000000000..523d187794c1c0fb80c7c2fcbed1768723141760 --- /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 + +#include "board-mahimahi.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 0000000000000000000000000000000000000000..829b1f11cf21812d00a5b332993ac1a60226e8c0 --- /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 0000000000000000000000000000000000000000..93b4095edaab1ee1453e219c3d338c0d19460cc0 --- /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 0000000000000000000000000000000000000000..ab38847d15e217d9197911e24ccc56a07fd14b97 --- /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 0000000000000000000000000000000000000000..da30672d26a160ae7bdb22ddc75530828a454901 --- /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 0000000000000000000000000000000000000000..dc57781475035a45551059d67f62076523c24c23 --- /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 + +#include "board-mahimahi.h" +#include "devices.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 0000000000000000000000000000000000000000..31ec742293ebbe7ecafcb05a58d831f11e532ebf --- /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 + +#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 0000000000000000000000000000000000000000..05c9bb0b4d5562917949752daacd8d54db2274e3 --- /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 100644 index 0000000000000000000000000000000000000000..b80db78491e190c1c95c524043d3e047c0fffc65 --- /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 0000000000000000000000000000000000000000..13b326fa71dfad8374158274a25b5082fa837549 --- /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 0000000000000000000000000000000000000000..78919b9b1954325c581ef9f95cf757e1de50bdfe --- /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 0000000000000000000000000000000000000000..dc110122094548aa627bec7094a13806f707a429 --- /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 0000000000000000000000000000000000000000..8cd24766b03c48ecbbfa475f0fe4f723cc4d021e --- /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.c b/arch/arm/mach-msm/board-mahimahi.c index 5a4882fc6f7a0faaf5122b150290fb7ed74d715e..ec87a0dd1f1a0ceda342de91157b8d0feb8f17b3 100644 --- a/arch/arm/mach-msm/board-mahimahi.c +++ b/arch/arm/mach-msm/board-mahimahi.c @@ -31,10 +31,10 @@ #include #include #include +#include #include "board-mahimahi.h" #include "devices.h" -#include "proc_comm.h" static uint debug_uart; diff --git a/arch/arm/mach-msm/board-mahimahi.h b/arch/arm/mach-msm/board-mahimahi.h new file mode 100644 index 0000000000000000000000000000000000000000..9696a47c4006a6b239bb44cab5a7f3d6cd4c0866 --- /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-msm7627-regulator.c b/arch/arm/mach-msm/board-msm7627-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..7437911befe0abd5e93b245c727014b1dc4f3612 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627-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. + */ + +#include "board-msm7627-regulator.h" + +#define PCOM_VREG_CONSUMERS(name) \ + static struct regulator_consumer_supply __pcom_vreg_supply_##name[] + +#define PCOM_VREG_CONSTRAINT_LVSW(_name, _always_on, _boot_on, _supply_uV) \ +{ \ + .name = #_name, \ + .min_uV = 0, \ + .max_uV = 0, \ + .input_uV = _supply_uV, \ + .valid_modes_mask = REGULATOR_MODE_NORMAL, \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .apply_uV = 0, \ + .boot_on = _boot_on, \ + .always_on = _always_on \ +} + +#define PCOM_VREG_CONSTRAINT_DYN(_name, _min_uV, _max_uV, _always_on, \ + _boot_on, _apply_uV, _supply_uV) \ +{ \ + .name = #_name, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .valid_modes_mask = REGULATOR_MODE_NORMAL, \ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, \ + .input_uV = _supply_uV, \ + .apply_uV = _apply_uV, \ + .boot_on = _boot_on, \ + .always_on = _always_on \ +} + + +#define PCOM_VREG_INIT(_name, _supply, _constraints)\ +{ \ + .supply_regulator = _supply, \ + .consumer_supplies = __pcom_vreg_supply_##_name, \ + .num_consumer_supplies = ARRAY_SIZE(__pcom_vreg_supply_##_name), \ + .constraints = _constraints \ +} + +#define PCOM_VREG_SMP(_name, _id, _supply, _min_uV, _max_uV, _rise_time, \ + _pulldown, _always_on, _boot_on, _apply_uV, _supply_uV) \ +{ \ + .init_data = PCOM_VREG_INIT(_name, _supply, \ + PCOM_VREG_CONSTRAINT_DYN(_name, _min_uV, _max_uV, _always_on, \ + _boot_on, _apply_uV, _supply_uV)), \ + .id = _id, \ + .rise_time = _rise_time, \ + .pulldown = _pulldown, \ + .negative = 0, \ +} + +#define PCOM_VREG_LDO PCOM_VREG_SMP + +PCOM_VREG_CONSUMERS(smps0) = { + REGULATOR_SUPPLY("smps0", NULL), + REGULATOR_SUPPLY("msmc1", NULL), +}; + +PCOM_VREG_CONSUMERS(smps1) = { + REGULATOR_SUPPLY("smps1", NULL), + REGULATOR_SUPPLY("msmc2", NULL), +}; + +PCOM_VREG_CONSUMERS(smps2) = { + REGULATOR_SUPPLY("smps2", NULL), + REGULATOR_SUPPLY("pa", NULL), +}; + +PCOM_VREG_CONSUMERS(smps3) = { + REGULATOR_SUPPLY("smps3", NULL), + REGULATOR_SUPPLY("msme1", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo00) = { + REGULATOR_SUPPLY("ldo00", NULL), + REGULATOR_SUPPLY("gp3", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo01) = { + REGULATOR_SUPPLY("ldo01", NULL), + REGULATOR_SUPPLY("msma", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo02) = { + REGULATOR_SUPPLY("ldo02", NULL), + REGULATOR_SUPPLY("msmp", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo03) = { + REGULATOR_SUPPLY("ldo03", NULL), + REGULATOR_SUPPLY("ruim", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo04) = { + REGULATOR_SUPPLY("ldo04", NULL), + REGULATOR_SUPPLY("tcxo", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo05) = { + REGULATOR_SUPPLY("ldo05", NULL), + REGULATOR_SUPPLY("mmc", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo06) = { + REGULATOR_SUPPLY("ldo06", NULL), + REGULATOR_SUPPLY("usb", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo07) = { + REGULATOR_SUPPLY("ldo07", NULL), + REGULATOR_SUPPLY("rfrx1", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo08) = { + REGULATOR_SUPPLY("ldo08", NULL), + REGULATOR_SUPPLY("synt", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo09) = { + REGULATOR_SUPPLY("ldo09", NULL), + REGULATOR_SUPPLY("gp1", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo10) = { + REGULATOR_SUPPLY("ldo10", NULL), + REGULATOR_SUPPLY("gp4", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo11) = { + REGULATOR_SUPPLY("ldo11", NULL), + REGULATOR_SUPPLY("gp2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo12) = { + REGULATOR_SUPPLY("ldo12", NULL), + REGULATOR_SUPPLY("rftx", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo13) = { + REGULATOR_SUPPLY("ldo13", NULL), + REGULATOR_SUPPLY("wlan", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo14) = { + REGULATOR_SUPPLY("ldo14", NULL), + REGULATOR_SUPPLY("rf", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo15) = { + REGULATOR_SUPPLY("ldo15", NULL), + REGULATOR_SUPPLY("gp6", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo16) = { + REGULATOR_SUPPLY("ldo16", NULL), + REGULATOR_SUPPLY("gp5", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo17) = { + REGULATOR_SUPPLY("ldo17", NULL), + REGULATOR_SUPPLY("msme2", NULL), +}; + +/** + * Minimum and Maximum range for the regulators is as per the + * device Datasheet. Actual value used by consumer is between + * the provided range. + */ +static struct proccomm_regulator_info msm7627_pcom_vreg_info[] = { + /* Standard regulators (SMPS and LDO) + * R = rise time (us) + * P = pulldown (1 = pull down, 0 = float, -1 = don't care) + * A = always on + * B = boot on + * V = automatic voltage set (meaningful for single-voltage regs only) + * S = supply voltage (uV) + * name id supp min uV max uV R P A B V S */ + PCOM_VREG_SMP(smps0, 3, NULL, 750000, 3050000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps1, 4, NULL, 750000, 3050000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps2, 10, NULL, 750000, 3050000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps3, 2, NULL, 750000, 3050000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo00, 5, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo01, 0, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo02, 1, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo03, 19, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo04, 9, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo05, 18, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo06, 16, NULL, 3300000, 3300000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo07, 12, NULL, 2700000, 2700000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo08, 14, NULL, 2700000, 2700000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo09, 8, NULL, 2900000, 2900000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo10, 7, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo11, 21, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo12, 11, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo13, 15, NULL, 1800000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo14, 24, NULL, 2700000, 2700000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo15, 23, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo16, 22, NULL, 2850000, 3000000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo17, 6, NULL, 1300000, 1300000, 0, -1, 0, 0, 0, 0), + +}; + +struct proccomm_regulator_platform_data msm7627_proccomm_regulator_data = { + .regs = msm7627_pcom_vreg_info, + .nregs = ARRAY_SIZE(msm7627_pcom_vreg_info) +}; diff --git a/arch/arm/mach-msm/board-msm7627-regulator.h b/arch/arm/mach-msm/board-msm7627-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..d82c5c09baeaa813a453aa82c9e0818819d15a8a --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627-regulator.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 __ARCH_ARM_MACH_MSM_BOARD_7627_REGULATOR_H__ +#define __ARCH_ARM_MACH_MSM_BOARD_7627_REGULATOR_H__ + +#include "proccomm-regulator.h" + +extern struct proccomm_regulator_platform_data msm7627_proccomm_regulator_data; + +#endif diff --git a/arch/arm/mach-msm/board-msm7627a-bt.c b/arch/arm/mach-msm/board-msm7627a-bt.c new file mode 100644 index 0000000000000000000000000000000000000000..e4edf9bae2e0ebe7699b7d9b70af2c05b539b20d --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a-bt.c @@ -0,0 +1,1018 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "board-msm7627a.h" +#include "devices-msm7x2xa.h" + +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + + +static struct bt_vreg_info bt_vregs[] = { + {"msme1", 2, 1800000, 1800000, 0, NULL}, + {"bt", 21, 2900000, 3300000, 1, NULL} +}; + +static struct platform_device msm_bt_power_device = { + .name = "bt_power", +}; + +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 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), +}; + +int gpio_bt_sys_rest_en = 133; +static void gpio_bt_config(void) +{ + u32 socinfo = socinfo_get_platform_version(); + if (machine_is_msm7627a_qrd1()) + gpio_bt_sys_rest_en = 114; + if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() + || machine_is_msm8625_evt()) + gpio_bt_sys_rest_en = 16; + if (machine_is_msm8625_qrd7()) + gpio_bt_sys_rest_en = 88; + if (machine_is_msm7627a_qrd3()) { + if (socinfo == 0x70002) + gpio_bt_sys_rest_en = 88; + else + gpio_bt_sys_rest_en = 85; + } +} + +static int bt_set_gpio(int on) +{ + int rc = 0; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA}; + + pr_debug("%s: Setting SYS_RST_PIN(%d) to %d\n", + __func__, gpio_bt_sys_rest_en, on); + if (on) { + + if (machine_is_msm7627a_evb() || machine_is_msm8625_qrd7()) { + rc = gpio_tlmm_config(GPIO_CFG(gpio_bt_sys_rest_en, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + + gpio_set_value(gpio_bt_sys_rest_en, 1); + } else { + rc = gpio_direction_output(gpio_bt_sys_rest_en, 1); + } + msleep(100); + } else { + + if (!marimba_get_fm_status(&config) && + !marimba_get_bt_status(&config)) { + if (machine_is_msm7627a_evb() || + machine_is_msm8625_qrd7()) { + gpio_set_value(gpio_bt_sys_rest_en, 0); + rc = gpio_tlmm_config(GPIO_CFG( + gpio_bt_sys_rest_en, 0, + GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + } else { + gpio_set_value_cansleep(gpio_bt_sys_rest_en, 0); + rc = gpio_direction_input(gpio_bt_sys_rest_en); + } + msleep(100); + } + } + if (rc) + pr_err("%s: BT sys_reset_en GPIO : Error", __func__); + + return rc; +} + +static struct regulator *fm_regulator; +static int fm_radio_setup(struct marimba_fm_platform_data *pdata) +{ + int rc = 0; + const char *id = "FMPW"; + uint32_t irqcfg; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA}; + u8 value; + + /* Voting for 1.8V Regulator */ + fm_regulator = regulator_get(NULL , "msme1"); + if (IS_ERR(fm_regulator)) { + rc = PTR_ERR(fm_regulator); + pr_err("%s: could not get regulator: %d\n", __func__, rc); + goto out; + } + + /* Set the voltage level to 1.8V */ + rc = regulator_set_voltage(fm_regulator, 1800000, 1800000); + if (rc < 0) { + pr_err("%s: could not set voltage: %d\n", __func__, rc); + goto reg_free; + } + + /* Enabling the 1.8V regulator */ + rc = regulator_enable(fm_regulator); + if (rc) { + pr_err("%s: could not enable regulator: %d\n", __func__, rc); + goto reg_free; + } + + /* 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 reg_disable; + } + + rc = bt_set_gpio(1); + if (rc) { + pr_err("%s: bt_set_gpio = %d", __func__, rc); + goto gpio_deconfig; + } + /*re-write FM Slave Id, after reset*/ + value = BAHAMA_SLAVE_ID_FM_ADDR; + rc = marimba_write_bit_mask(&config, + BAHAMA_SLAVE_ID_FM_REG, &value, 1, 0xFF); + if (rc < 0) { + pr_err("%s: FM Slave ID rewrite Failed = %d", __func__, rc); + goto gpio_deconfig; + } + /* 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 gpio_deconfig; + } + + return 0; + +gpio_deconfig: + pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_OFF); + bt_set_gpio(0); +reg_disable: + regulator_disable(fm_regulator); +reg_free: + regulator_put(fm_regulator); + fm_regulator = NULL; +out: + 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 (!IS_ERR_OR_NULL(fm_regulator)) { + rc = regulator_disable(fm_regulator); + if (rc) + pr_err("%s: could not disable regulator: %d\n", + __func__, rc); + regulator_put(fm_regulator); + 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); + rc = bt_set_gpio(0); + if (rc) + pr_err("%s: bt_set_gpio = %d", __func__, rc); +} +static int switch_pcm_i2s_reg_mode(int mode) +{ + unsigned char reg = 0; + int rc = -1; + unsigned char set = I2C_PIN_CTL; /*SET PIN CTL mode*/ + unsigned char unset = I2C_NORMAL; /* UNSET PIN CTL MODE*/ + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA}; + + if (mode == 0) { + /* as we need to switch path to FM we need to move + BT AUX PCM lines to PIN CONTROL mode then move + FM to normal mode.*/ + for (reg = BT_PCM_BCLK_MODE; reg <= BT_PCM_SYNC_MODE; reg++) { + rc = marimba_write(&config, reg, &set, 1); + if (rc < 0) { + pr_err("pcm pinctl failed = %d", rc); + goto err_all; + } + } + for (reg = FM_I2S_SD_MODE; reg <= FM_I2S_SCK_MODE; reg++) { + rc = marimba_write(&config, reg, &unset, 1); + if (rc < 0) { + pr_err("i2s normal failed = %d", rc); + goto err_all; + } + } + } else { + /* as we need to switch path to AUXPCM we need to move + FM I2S lines to PIN CONTROL mode then move + BT AUX_PCM to normal mode.*/ + for (reg = FM_I2S_SD_MODE; reg <= FM_I2S_SCK_MODE; reg++) { + rc = marimba_write(&config, reg, &set, 1); + if (rc < 0) { + pr_err("i2s pinctl failed = %d", rc); + goto err_all; + } + } + for (reg = BT_PCM_BCLK_MODE; reg <= BT_PCM_SYNC_MODE; reg++) { + rc = marimba_write(&config, reg, &unset, 1); + if (rc < 0) { + pr_err("pcm normal failed = %d", rc); + goto err_all; + } + } + } + + return 0; + +err_all: + return rc; +} + + +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 int config_i2s(int mode) +{ + int pin, rc = 0; + + if (mode == FM_I2S_ON) { + if (machine_is_msm7x27a_surf() || machine_is_msm7625a_surf() + || machine_is_msm8625_surf()) + config_pcm_i2s_mode(0); + pr_err("%s mode = FM_I2S_ON", __func__); + + rc = switch_pcm_i2s_reg_mode(0); + if (rc) { + pr_err("switch mode failed"); + return rc; + } + 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__); + rc = switch_pcm_i2s_reg_mode(1); + if (rc) { + pr_err("switch mode failed"); + return rc; + } + 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() || machine_is_msm7625a_surf() + || machine_is_msm8625_surf()) + config_pcm_i2s_mode(1); + pr_err("%s mode =BT_PCM_ON", __func__); + rc = switch_pcm_i2s_reg_mode(1); + if (rc) { + pr_err("switch mode failed"); + return rc; + } + 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__); + rc = switch_pcm_i2s_reg_mode(0); + if (rc) { + pr_err("switch mode failed"); + return rc; + } + 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 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 ((int)version < 0 || version == BAHAMA_VER_UNSUPPORTED) { + dev_err(&msm_bt_power_device.dev, "%s : Bahama " + "version read Error, version = %d\n", + __func__, version); + 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_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); + 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_dbg(&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; + const char *id = "BTPW"; + + for (i = 0; i < ARRAY_SIZE(bt_vregs); i++) { + if (IS_ERR_OR_NULL(bt_vregs[i].reg)) { + bt_vregs[i].reg = + regulator_get(&msm_bt_power_device.dev, + bt_vregs[i].name); + if (IS_ERR(bt_vregs[i].reg)) { + rc = PTR_ERR(bt_vregs[i].reg); + dev_err(&msm_bt_power_device.dev, + "%s: could not get regulator %s: %d\n", + __func__, bt_vregs[i].name, rc); + goto reg_disable; + } + } + + rc = on ? regulator_set_voltage(bt_vregs[i].reg, + bt_vregs[i].min_level, + bt_vregs[i].max_level) : 0; + if (rc) { + dev_err(&msm_bt_power_device.dev, + "%s: could not set voltage for %s: %d\n", + __func__, bt_vregs[i].name, rc); + goto reg_disable; + } + + rc = on ? regulator_enable(bt_vregs[i].reg) : 0; + if (rc) { + dev_err(&msm_bt_power_device.dev, + "%s: could not %sable regulator %s: %d\n", + __func__, "en", bt_vregs[i].name, rc); + goto reg_disable; + } + + if (bt_vregs[i].is_pin_controlled) { + rc = pmapp_vreg_lpm_pincntrl_vote(id, + bt_vregs[i].pmapp_id, + PMAPP_CLOCK_ID_D1, + on ? PMAPP_CLOCK_VOTE_ON : + PMAPP_CLOCK_VOTE_OFF); + if (rc) { + dev_err(&msm_bt_power_device.dev, + "%s: pin control failed for %s: %d\n", + __func__, bt_vregs[i].name, rc); + goto pin_cnt_fail; + } + } + rc = on ? 0 : regulator_disable(bt_vregs[i].reg); + + if (rc) { + dev_err(&msm_bt_power_device.dev, + "%s: could not %sable regulator %s: %d\n", + __func__, "dis", bt_vregs[i].name, rc); + goto reg_disable; + } + } + + return rc; +pin_cnt_fail: + if (on) + regulator_disable(bt_vregs[i].reg); +reg_disable: + while (i) { + if (on) { + i--; + regulator_disable(bt_vregs[i].reg); + regulator_put(bt_vregs[i].reg); + bt_vregs[i].reg = NULL; + } + } + return rc; +} + +static struct regulator *reg_s3; +static unsigned int msm_bahama_setup_power(void) +{ + int rc = 0; + + reg_s3 = regulator_get(NULL, "msme1"); + if (IS_ERR(reg_s3)) { + rc = PTR_ERR(reg_s3); + pr_err("%s: could not get regulator: %d\n", __func__, rc); + goto out; + } + + rc = regulator_set_voltage(reg_s3, 1800000, 1800000); + if (rc < 0) { + pr_err("%s: could not set voltage: %d\n", __func__, rc); + goto reg_fail; + } + + rc = regulator_enable(reg_s3); + if (rc < 0) { + pr_err("%s: could not enable regulator: %d\n", __func__, rc); + goto reg_fail; + } + gpio_tlmm_config(GPIO_CFG(gpio_bt_sys_rest_en, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + + /*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 reg_disable; + } + + rc = bt_set_gpio(1); + if (rc < 0) { + pr_err("%s: bt_set_gpio %d = %d\n", __func__, + gpio_bt_sys_rest_en, rc); + goto gpio_fail; + } + + return rc; + +gpio_fail: + gpio_free(gpio_bt_sys_rest_en); +reg_disable: + regulator_disable(reg_s3); +reg_fail: + regulator_put(reg_s3); +out: + reg_s3 = NULL; + return rc; +} + +static unsigned int msm_bahama_shutdown_power(int value) +{ + int rc = 0; + + if (IS_ERR_OR_NULL(reg_s3)) { + rc = reg_s3 ? PTR_ERR(reg_s3) : -ENODEV; + goto out; + } + + rc = regulator_disable(reg_s3); + if (rc) { + pr_err("%s: could not disable regulator: %d\n", __func__, rc); + goto out; + } + + if (value == BAHAMA_ID) { + rc = bt_set_gpio(0); + if (rc) { + pr_err("%s: bt_set_gpio = %d\n", + __func__, rc); + goto reg_enable; + } + gpio_free(gpio_bt_sys_rest_en); + } + + regulator_put(reg_s3); + reg_s3 = NULL; + + return 0; + +reg_enable: + regulator_enable(reg_s3); +out: + 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); + } + } + } + rc = bt_set_gpio(0); + if (rc) { + pr_err("%s: bt_set_gpio = %d\n", + __func__, rc); + } + 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 = bt_set_gpio(on); + if (rc) { + pr_err("%s: bt_set_gpio = %d\n", + __func__, rc); + goto exit; + } + 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); + + 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); + } + rc = bt_set_gpio(on); + if (rc) { + pr_err("%s: bt_set_gpio = %d\n", + __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); + } + } +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 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 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, +}; + +static struct i2c_board_info bahama_devices[] = { +{ + I2C_BOARD_INFO("marimba", 0xc), + .platform_data = &marimba_pdata, +}, +}; + +void __init msm7627a_bt_power_init(void) +{ + int i, rc = 0; + struct device *dev; + + + gpio_bt_config(); + + rc = i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + bahama_devices, + ARRAY_SIZE(bahama_devices)); + if (rc < 0) { + pr_err("%s: I2C Register failed\n", __func__); + return; + } + + rc = platform_device_register(&msm_bt_power_device); + if (rc < 0) { + pr_err("%s: device register failed\n", __func__); + return; + } + + dev = &msm_bt_power_device.dev; + + for (i = 0; i < ARRAY_SIZE(bt_vregs); i++) { + bt_vregs[i].reg = regulator_get(dev, bt_vregs[i].name); + if (IS_ERR(bt_vregs[i].reg)) { + rc = PTR_ERR(bt_vregs[i].reg); + dev_err(dev, "%s: could not get regulator %s: %d\n", + __func__, bt_vregs[i].name, rc); + goto reg_get_fail; + } + } + + dev->platform_data = &bluetooth_power; + + return; + +reg_get_fail: + while (i--) { + regulator_put(bt_vregs[i].reg); + bt_vregs[i].reg = NULL; + } + platform_device_unregister(&msm_bt_power_device); +} +#endif diff --git a/arch/arm/mach-msm/board-msm7627a-camera.c b/arch/arm/mach-msm/board-msm7627a-camera.c new file mode 100644 index 0000000000000000000000000000000000000000..3270d19e55b3fa2555c5b97ce2fd34e957b146d9 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a-camera.c @@ -0,0 +1,1184 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices-msm7x2xa.h" +#include "board-msm7627a.h" +#include + +#define GPIO_SKU1_CAM_VGA_SHDN 18 +#define GPIO_SKU1_CAM_VGA_RESET_N 29 +#define GPIO_SKU3_CAM_5MP_SHDN_N 5 /* PWDN */ +#define GPIO_SKU3_CAM_5MP_CAMIF_RESET 6 /* (board_is(EVT))?123:121 RESET */ +#define GPIO_SKU3_CAM_5MP_CAM_DRIVER_PWDN 30 +#define GPIO_SKU7_CAM_VGA_SHDN 91 +#define GPIO_SKU7_CAM_5MP_SHDN_N 93 /* PWDN */ +#define GPIO_SKU7_CAM_5MP_CAMIF_RESET 23 /* (board_is(EVT))?123:121 RESET */ + +#ifdef CONFIG_MSM_CAMERA_V4L2 +static uint32_t camera_off_gpio_table[] = { + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; + +static uint32_t camera_on_gpio_table[] = { + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; + +static struct gpio s5k4e1_cam_req_gpio[] = { + {GPIO_CAM_GP_CAMIF_RESET_N, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct msm_gpio_set_tbl s5k4e1_cam_gpio_set_tbl[] = { + {GPIO_CAM_GP_CAMIF_RESET_N, GPIOF_OUT_INIT_LOW, 1000}, + {GPIO_CAM_GP_CAMIF_RESET_N, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_camera_gpio_conf gpio_conf_s5k4e1 = { + .camera_off_table = camera_off_gpio_table, + .camera_off_table_size = ARRAY_SIZE(camera_off_gpio_table), + .camera_on_table = camera_on_gpio_table, + .camera_on_table_size = ARRAY_SIZE(camera_on_gpio_table), + .cam_gpio_req_tbl = s5k4e1_cam_req_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(s5k4e1_cam_req_gpio), + .cam_gpio_set_tbl = s5k4e1_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(s5k4e1_cam_gpio_set_tbl), + .gpio_no_mux = 1, +}; + +static struct msm_camera_gpio_conf gpio_conf_mt9e013 = { + .camera_off_table = camera_off_gpio_table, + .camera_on_table = camera_on_gpio_table, + .gpio_no_mux = 1, +}; + +static struct msm_camera_gpio_conf gpio_conf_ov9726 = { + .camera_off_table = camera_off_gpio_table, + .camera_on_table = camera_on_gpio_table, + .gpio_no_mux = 1, +}; + +#ifdef CONFIG_OV7692 +static struct gpio ov7692_cam_req_gpio[] = { + {GPIO_SKU1_CAM_VGA_SHDN, GPIOF_DIR_OUT, "CAM_VGA_SHDN"}, + {GPIO_SKU1_CAM_VGA_RESET_N, GPIOF_DIR_OUT, "CAM_VGA_RESET"}, +}; + +static struct msm_gpio_set_tbl ov7692_cam_gpio_set_tbl[] = { + {GPIO_SKU1_CAM_VGA_SHDN, GPIOF_OUT_INIT_HIGH, 5000}, + {GPIO_SKU1_CAM_VGA_SHDN, GPIOF_OUT_INIT_LOW, 5000}, + {GPIO_SKU1_CAM_VGA_RESET_N, GPIOF_OUT_INIT_HIGH, 5000}, + {GPIO_SKU1_CAM_VGA_RESET_N, GPIOF_OUT_INIT_LOW, 5000}, +}; + +static struct msm_camera_gpio_conf gpio_conf_ov7692 = { + .cam_gpio_req_tbl = ov7692_cam_req_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(ov7692_cam_req_gpio), + .cam_gpio_set_tbl = ov7692_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(ov7692_cam_gpio_set_tbl), + .gpio_no_mux = 1, +}; +#endif + +#ifdef CONFIG_OV5647 +static struct msm_camera_gpio_conf gpio_conf_ov5647 = { + .camera_off_table = camera_off_gpio_table, + .camera_on_table = camera_on_gpio_table, + .gpio_no_mux = 1, +}; +#endif + +#ifdef CONFIG_MSM_CAMERA_FLASH +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_EXT, + ._fsrc.ext_driver_src.led_en = GPIO_CAM_GP_LED_EN1, + ._fsrc.ext_driver_src.led_flash_en = GPIO_CAM_GP_LED_EN2, +}; +#endif + +static struct camera_vreg_t msm_cam_vreg[] = { + {"msme1", REG_LDO, 1800000, 1800000, 0}, + {"gp2", REG_LDO, 2850000, 2850000, 0}, + {"usb2", REG_LDO, 1800000, 1800000, 0}, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k4e1_data; + +struct msm_camera_device_platform_data msm_camera_device_data_csi1[] = { + { + .csid_core = 1, + .is_csic = 1, + .ioclk = { + .vfe_clk_rate = 192000000, + }, + }, + { + .csid_core = 1, + .is_csic = 1, + .ioclk = { + .vfe_clk_rate = 266667000, + }, + }, +}; + +struct msm_camera_device_platform_data msm_camera_device_data_csi0[] = { + { + .csid_core = 0, + .is_csic = 1, + .ioclk = { + .vfe_clk_rate = 192000000, + }, + }, + { + .csid_core = 0, + .is_csic = 1, + .ioclk = { + .vfe_clk_rate = 266667000, + }, + }, +}; + +static struct i2c_board_info msm_act_main_cam_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x11), +}; + +static struct msm_actuator_info msm_act_main_cam_4_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_4, + .bus_id = MSM_GSBI0_QUP_I2C_BUS_ID, + .vcm_pwd = GPIO_CAM_GP_CAM_PWDN, + .vcm_enable = 1, +}; + +#ifdef CONFIG_S5K4E1 +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_platform_info sensor_board_info_s5k4e1 = { + .mount_angle = 90, + .cam_vreg = msm_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_cam_vreg), + .gpio_conf = &gpio_conf_s5k4e1, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k4e1_data = { + .sensor_name = "s5k4e1", + .sensor_reset_enable = 1, + .pmic_gpio_enable = 0, + .pdata = &msm_camera_device_data_csi1[0], + .flash_data = &flash_s5k4e1, + .sensor_platform_info = &sensor_board_info_s5k4e1, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_4_info, +}; +#endif + +#ifdef CONFIG_OV7692 +static struct msm_camera_sensor_platform_info sensor_board_info_ov7692 = { + .mount_angle = 90, + .cam_vreg = msm_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_cam_vreg), + .gpio_conf = &gpio_conf_ov7692, +}; + +static struct msm_camera_sensor_flash_data flash_ov7692 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov7692_data = { + .sensor_name = "ov7692", + .sensor_reset_enable = 0, + .pmic_gpio_enable = 1, + .sensor_lcd_gpio_onoff = lcd_camera_power_onoff, + .sensor_reset = GPIO_SKU1_CAM_VGA_RESET_N, + .sensor_pwd = GPIO_SKU1_CAM_VGA_SHDN, + .pdata = &msm_camera_device_data_csi0[0], + .flash_data = &flash_ov7692, + .sensor_platform_info = &sensor_board_info_ov7692, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = YUV_SENSOR, +}; +#endif + +#ifdef CONFIG_OV5647 + +static struct msm_actuator_info msm_act_main_cam_5_info = { + .board_info = &msm_act_main_cam_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_5, + .bus_id = MSM_GSBI0_QUP_I2C_BUS_ID, + .vcm_pwd = GPIO_SKU3_CAM_5MP_CAM_DRIVER_PWDN, + .vcm_enable = 1, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_ov5647 = { + .mount_angle = 90, + .cam_vreg = msm_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_cam_vreg), + .gpio_conf = &gpio_conf_ov5647, +}; + +static struct msm_camera_sensor_flash_src msm_flash_src_ov5647 = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_LED1, + ._fsrc.ext_driver_src.led_en = 13, + ._fsrc.ext_driver_src.led_flash_en = 32, +}; + +static struct msm_camera_sensor_flash_data flash_ov5647 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_ov5647, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov5647_data = { + .sensor_name = "ov5647", + .sensor_reset_enable = 1, + .pmic_gpio_enable = 1, + .sensor_lcd_gpio_onoff = lcd_camera_power_onoff, + .sensor_reset = GPIO_SKU3_CAM_5MP_CAMIF_RESET, + .sensor_pwd = GPIO_SKU3_CAM_5MP_SHDN_N, + .pdata = &msm_camera_device_data_csi1[0], + .flash_data = &flash_ov5647, + .sensor_platform_info = &sensor_board_info_ov5647, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, + .actuator_info = &msm_act_main_cam_5_info, +}; + +#endif +#ifdef CONFIG_MT9E013 +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_platform_info sensor_board_info_mt9e013 = { + .mount_angle = 90, + .cam_vreg = msm_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_cam_vreg), + .gpio_conf = &gpio_conf_mt9e013, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9e013_data = { + .sensor_name = "mt9e013", + .sensor_reset_enable = 1, + .pmic_gpio_enable = 0, + .pdata = &msm_camera_device_data_csi1[1], + .flash_data = &flash_mt9e013, + .sensor_platform_info = &sensor_board_info_mt9e013, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .sensor_type = BAYER_SENSOR, +}; +#endif + +#ifdef CONFIG_WEBCAM_OV9726 +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_platform_info sensor_board_info_ov9726 = { + .mount_angle = 90, + .cam_vreg = msm_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_cam_vreg), + .gpio_conf = &gpio_conf_ov9726, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov9726_data = { + .sensor_name = "ov9726", + .sensor_reset_enable = 0, + .pmic_gpio_enable = 0, + .pdata = &msm_camera_device_data_csi0[0], + .flash_data = &flash_ov9726, + .sensor_platform_info = &sensor_board_info_ov9726, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, + .sensor_type = BAYER_SENSOR, +}; +#endif + +static struct platform_device msm_camera_server = { + .name = "msm_cam_server", + .id = 0, +}; + +static void __init msm7x27a_init_cam(void) +{ + if (!(machine_is_msm7x27a_ffa() || machine_is_msm7625a_ffa() + || machine_is_msm7627a_qrd1() + || machine_is_msm8625_ffa())) { + sensor_board_info_s5k4e1.cam_vreg = NULL; + sensor_board_info_s5k4e1.num_vreg = 0; + sensor_board_info_mt9e013.cam_vreg = NULL; + sensor_board_info_mt9e013.num_vreg = 0; + sensor_board_info_ov9726.cam_vreg = NULL; + sensor_board_info_ov9726.num_vreg = 0; + sensor_board_info_ov7692.cam_vreg = NULL; + sensor_board_info_ov7692.num_vreg = 0; + sensor_board_info_ov5647.cam_vreg = NULL; + sensor_board_info_ov5647.num_vreg = 0; + } + platform_device_register(&msm_camera_server); + if (machine_is_msm8625_surf() || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm8625_qrd7()) { + platform_device_register(&msm8625_device_csic0); + platform_device_register(&msm8625_device_csic1); + } else { + platform_device_register(&msm7x27a_device_csic0); + platform_device_register(&msm7x27a_device_csic1); + } + if (machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm8625_qrd7()) + *(int *) msm7x27a_device_clkctl.dev.platform_data = 1; + platform_device_register(&msm7x27a_device_clkctl); + platform_device_register(&msm7x27a_device_vfe); +} + +static struct i2c_board_info i2c_camera_devices[] = { + { + I2C_BOARD_INFO("s5k4e1", 0x36), + .platform_data = &msm_camera_sensor_s5k4e1_data, + }, + { + I2C_BOARD_INFO("ov9726", 0x10), + .platform_data = &msm_camera_sensor_ov9726_data, + }, + { + I2C_BOARD_INFO("mt9e013", 0x6C >> 2), + .platform_data = &msm_camera_sensor_mt9e013_data, + }, + { + I2C_BOARD_INFO("ov7692", 0x78), + .platform_data = &msm_camera_sensor_ov7692_data, + }, + { + I2C_BOARD_INFO("ov5647", 0x36 << 1), + .platform_data = &msm_camera_sensor_ov5647_data, + }, + { + I2C_BOARD_INFO("sc628a", 0x6E), + }, +}; +#else +static uint32_t camera_off_gpio_table[] = { + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; + +static uint32_t camera_on_gpio_table[] = { + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; + +#ifdef CONFIG_MSM_CAMERA_FLASH +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_EXT, + ._fsrc.ext_driver_src.led_en = GPIO_CAM_GP_LED_EN1, + ._fsrc.ext_driver_src.led_flash_en = GPIO_CAM_GP_LED_EN2, +}; +#endif + +static struct regulator_bulk_data regs_camera[] = { + { .supply = "msme1", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "gp2", .min_uV = 2850000, .max_uV = 2850000 }, + { .supply = "usb2", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +static void qrd1_camera_gpio_cfg(void) +{ + + int rc = 0; + + rc = gpio_request(QRD_GPIO_CAM_5MP_SHDN_EN, "ov5640"); + if (rc < 0) + pr_err("%s: gpio_request---GPIO_CAM_5MP_SHDN_EN failed!", + __func__); + + + rc = gpio_tlmm_config(GPIO_CFG(QRD_GPIO_CAM_5MP_SHDN_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: unable to enable Power Down gpio for main" + "camera!\n", __func__); + gpio_free(QRD_GPIO_CAM_5MP_SHDN_EN); + } + + + rc = gpio_request(QRD_GPIO_CAM_5MP_RESET, "ov5640"); + if (rc < 0) { + pr_err("%s: gpio_request---GPIO_CAM_5MP_RESET failed!", + __func__); + gpio_free(QRD_GPIO_CAM_5MP_SHDN_EN); + } + + + rc = gpio_tlmm_config(GPIO_CFG(QRD_GPIO_CAM_5MP_RESET, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: unable to enable reset gpio for main camera!\n", + __func__); + gpio_free(QRD_GPIO_CAM_5MP_RESET); + } + + rc = gpio_request(QRD_GPIO_CAM_3MP_PWDN, "ov7692"); + if (rc < 0) + pr_err("%s: gpio_request---GPIO_CAM_3MP_PWDN failed!", + __func__); + + rc = gpio_tlmm_config(GPIO_CFG(QRD_GPIO_CAM_3MP_PWDN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: unable to enable Power Down gpio for front" + "camera!\n", __func__); + gpio_free(QRD_GPIO_CAM_3MP_PWDN); + } + + gpio_direction_output(QRD_GPIO_CAM_5MP_SHDN_EN, 1); + gpio_direction_output(QRD_GPIO_CAM_5MP_RESET, 1); + gpio_direction_output(QRD_GPIO_CAM_3MP_PWDN, 1); +} +#endif + +static void evb_camera_gpio_cfg(void) +{ + int rc = 0; + + rc = gpio_request(msm_camera_sensor_ov5647_data.sensor_pwd, "ov5647"); + if (rc < 0) + pr_err("%s: gpio_request OV5647 sensor_pwd: %d failed!", + __func__, msm_camera_sensor_ov5647_data.sensor_pwd); + + rc = gpio_tlmm_config(GPIO_CFG(msm_camera_sensor_ov5647_data.sensor_pwd, + 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s:unable to enable Powr Dwn gpio for main camera!\n", + __func__); + gpio_free(msm_camera_sensor_ov5647_data.sensor_pwd); + } + + rc = gpio_direction_output(msm_camera_sensor_ov5647_data.sensor_pwd, 1); + if (rc < 0) + pr_err("%s: unable to set gpio: %d direction for ov5647 camera\n", + __func__, msm_camera_sensor_ov5647_data.sensor_pwd); + + rc = gpio_request(msm_camera_sensor_ov5647_data.sensor_reset, "ov5647"); + if (rc < 0) + pr_err("%s: gpio_request OV5647 sensor_reset: %d failed!", + __func__, msm_camera_sensor_ov5647_data.sensor_reset); + + rc = gpio_tlmm_config(GPIO_CFG( + msm_camera_sensor_ov5647_data.sensor_reset, + 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: unable to enable reset gpio for main camera!\n", + __func__); + gpio_free(msm_camera_sensor_ov5647_data.sensor_reset); + } + + rc = gpio_direction_output( + msm_camera_sensor_ov5647_data.sensor_reset, 1); + if (rc < 0) + pr_err("%s: unable to set gpio: %d direction for ov5647 camera\n", + __func__, msm_camera_sensor_ov5647_data.sensor_reset); + +} + +#ifndef CONFIG_MSM_CAMERA_V4L2 + +static void msm_camera_vreg_config(int vreg_en) +{ + int rc = vreg_en ? + regulator_bulk_enable(ARRAY_SIZE(regs_camera), regs_camera) : + regulator_bulk_disable(ARRAY_SIZE(regs_camera), regs_camera); + + if (rc) + pr_err("%s: could not %sable regulators: %d\n", + __func__, vreg_en ? "en" : "dis", rc); +} + +static int config_gpio_table(uint32_t *table, int len) +{ + int rc = 0, i = 0; + + for (i = 0; i < len; i++) { + rc = gpio_tlmm_config(table[i], GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s not able to get gpio\n", __func__); + for (i--; i >= 0; i--) + gpio_tlmm_config(camera_off_gpio_table[i], + GPIO_CFG_ENABLE); + break; + } + } + return rc; +} + +static int config_camera_on_gpios_rear(void) +{ + int rc = 0; + + if (machine_is_msm7x27a_ffa() || machine_is_msm7625a_ffa() + || machine_is_msm7627a_qrd1() + || machine_is_msm8625_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() || machine_is_msm7625a_ffa() + || machine_is_msm7627a_qrd1() + || machine_is_msm8625_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() || machine_is_msm7625a_ffa() + || machine_is_msm7627a_qrd1() + || machine_is_msm8625_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() || machine_is_msm7625a_ffa() + || machine_is_msm7627a_qrd1() + || machine_is_msm8625_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 = MSM7XXX_CLK_CTL_PHYS, + .ioext.appsz = MSM7XXX_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 = MSM7XXX_CLK_CTL_PHYS, + .ioext.appsz = MSM7XXX_CLK_CTL_SIZE, +}; + +#ifdef CONFIG_OV5647 + +static struct msm_camera_sensor_platform_info ov5647_sensor_7627a_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_src msm_flash_src_ov5647 = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_LED, + ._fsrc.led_src.led_name = "flashlight", + ._fsrc.led_src.led_name_len = 10, +}; + +static struct msm_camera_sensor_flash_data flash_ov5647 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_ov5647, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov5647_data = { + .sensor_name = "ov5647", + .sensor_reset_enable = 1, + .sensor_reset = GPIO_SKU3_CAM_5MP_CAMIF_RESET, + .pmic_gpio_enable = 1, + .sensor_pwd = GPIO_SKU3_CAM_5MP_SHDN_N, + .vcm_pwd = GPIO_SKU3_CAM_5MP_CAM_DRIVER_PWDN, + .vcm_enable = 1, + .pdata = &msm_camera_device_data_rear, + .flash_data = &flash_ov5647, + .sensor_platform_info = &ov5647_sensor_7627a_info, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_ov5647 = { + .name = "msm_camera_ov5647", + .dev = { + .platform_data = &msm_camera_sensor_ov5647_data, + }, +}; +#endif + +#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, + .pmic_gpio_enable = 0, + .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,*/ + .pmic_gpio_enable = 0, + .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_info msm_camera_sensor_ov9726_data; +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, + .pmic_gpio_enable = 0, + .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, + }, +}; +#else +static inline void msm_camera_vreg_init(void) { } +#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, + .pmic_gpio_enable = 0, + .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 + +#ifdef CONFIG_OV5640 +static struct msm_camera_sensor_platform_info ov5640_sensor_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_src msm_flash_src_ov5640 = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_LED, + ._fsrc.led_src.led_name = "flashlight", + ._fsrc.led_src.led_name_len = 10, +}; + +static struct msm_camera_sensor_flash_data flash_ov5640 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_ov5640, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov5640_data = { + .sensor_name = "ov5640", + .sensor_reset_enable = 1, + .pmic_gpio_enable = 0, + .sensor_reset = QRD_GPIO_CAM_5MP_RESET, + .sensor_pwd = QRD_GPIO_CAM_5MP_SHDN_EN, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_rear, + .flash_data = &flash_ov5640, + .sensor_platform_info = &ov5640_sensor_info, + .csi_if = 1, +}; + +static struct platform_device msm_camera_sensor_ov5640 = { + .name = "msm_camera_ov5640", + .dev = { + .platform_data = &msm_camera_sensor_ov5640_data, + }, +}; +#endif + +#ifdef CONFIG_WEBCAM_OV7692_QRD +static struct msm_camera_sensor_platform_info ov7692_sensor_7627a_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_data flash_ov7692 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov7692_data = { + .sensor_name = "ov7692", + .sensor_reset_enable = 0, + .pmic_gpio_enable = 1, + .sensor_reset = GPIO_SKU1_CAM_VGA_RESET_N, + .sensor_pwd = GPIO_SKU1_CAM_VGA_SHDN, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_front, + .flash_data = &flash_ov7692, + .sensor_platform_info = &ov7692_sensor_7627a_info, + .csi_if = 1, +}; + +static struct platform_device msm_camera_sensor_ov7692 = { + .name = "msm_camera_ov7692", + .dev = { + .platform_data = &msm_camera_sensor_ov7692_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", 0x6E), + }, +}; + +static struct i2c_board_info i2c_camera_devices_qrd[] = { + #ifdef CONFIG_OV5640 + { + I2C_BOARD_INFO("ov5640", 0x78 >> 1), + }, + #endif + #ifdef CONFIG_WEBCAM_OV7692_QRD + { + I2C_BOARD_INFO("ov7692", 0x78), + }, + #endif +}; + +static struct i2c_board_info i2c_camera_devices_evb[] = { + #ifdef CONFIG_OV5647 + { + I2C_BOARD_INFO("ov5647", 0x36 << 1), + }, + { + I2C_BOARD_INFO("ov5647_af", 0x18 >> 1), + }, + #endif + #ifdef CONFIG_WEBCAM_OV7692_QRD + { + I2C_BOARD_INFO("ov7692", 0x78), + }, + #endif +}; + +static struct platform_device *camera_devices_msm[] __initdata = { +#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 +}; + +static struct platform_device *camera_devices_qrd[] __initdata = { +#ifdef CONFIG_OV5640 + &msm_camera_sensor_ov5640, +#endif +#ifdef CONFIG_WEBCAM_OV7692_QRD + &msm_camera_sensor_ov7692, +#endif +}; + +static struct platform_device *camera_devices_evb[] __initdata = { +#ifdef CONFIG_OV5647 + &msm_camera_sensor_ov5647, +#endif +#ifdef CONFIG_WEBCAM_OV7692_QRD + &msm_camera_sensor_ov7692, +#endif +}; +#endif + +enum { + SX150X_CAM, +}; + +static struct sx150x_platform_data sx150x_data[] __initdata = { + [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, + }, +}; + +static struct i2c_board_info cam_exp_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1508q", 0x22), + .platform_data = &sx150x_data[SX150X_CAM], + }, +}; + +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)); +} + +#define LCD_CAMERA_LDO_2V8 35 /* SKU1&SKU3 2.8V LDO */ +#define SKU3_LCD_CAMERA_LDO_1V8 40 /* SKU3 1.8V LDO */ +#define SKU7_LCD_CAMERA_LDO_1V8 58 /* SKU7 1.8V LDO */ + +static int lcd_camera_ldo_1v8 = SKU3_LCD_CAMERA_LDO_1V8; + +static void lcd_camera_power_init(void) +{ + int rc = 0; + + pr_debug("lcd_camera_power_init\n"); + + if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) + lcd_camera_ldo_1v8 = SKU7_LCD_CAMERA_LDO_1V8; + else + lcd_camera_ldo_1v8 = SKU3_LCD_CAMERA_LDO_1V8; + + /* LDO_EXT2V8 */ + if (gpio_request(LCD_CAMERA_LDO_2V8, "lcd_camera_ldo_2v8")) { + pr_err("failed to request gpio lcd_camera_ldo_2v8\n"); + return; + } + + rc = gpio_tlmm_config(GPIO_CFG(LCD_CAMERA_LDO_2V8, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: unable to enable lcd_camera_ldo_2v8!\n", __func__); + goto fail_gpio2; + } + + /* LDO_EVT1V8 */ + if (gpio_request(lcd_camera_ldo_1v8, "lcd_camera_ldo_1v8")) { + pr_err("failed to request gpio lcd_camera_ldo_1v8\n"); + goto fail_gpio2; + } + + rc = gpio_tlmm_config(GPIO_CFG(lcd_camera_ldo_1v8, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: unable to enable lcd_camera_ldo_1v8!\n", __func__); + goto fail_gpio1; + } + + return; + +fail_gpio1: + gpio_free(lcd_camera_ldo_1v8); +fail_gpio2: + gpio_free(LCD_CAMERA_LDO_2V8); + + return; +} + +static int lcd_camera_power_on_sku3(void) +{ + int rc = 0; + + pr_debug("turn on sku3 lcd_camera_ldo_1v8\n"); + gpio_set_value_cansleep(lcd_camera_ldo_1v8, 1); + + pr_debug("turn on sku3 lcd_camera_ldo\n"); + gpio_set_value_cansleep(LCD_CAMERA_LDO_2V8, 1); + + return rc; +} + +static int lcd_camera_power_off_sku3(void) +{ + int rc = 0; + + pr_debug("turn off sku3 lcd_camera_ldo_1v8\n"); + gpio_set_value_cansleep(lcd_camera_ldo_1v8, 0); + + pr_debug("turn off sku3 lcd_camera_ldo\n"); + gpio_set_value_cansleep(LCD_CAMERA_LDO_2V8, 0); + + gpio_free(lcd_camera_ldo_1v8); + gpio_free(LCD_CAMERA_LDO_2V8); + + return rc; +} + +int lcd_camera_power_onoff(int on) +{ + int rc = 0; + + pr_debug("lcd_camera_power_onoff on = %d,\n", on); + + if (on) + rc = lcd_camera_power_on_sku3(); + else + rc = lcd_camera_power_off_sku3(); + + return rc; +} +EXPORT_SYMBOL(lcd_camera_power_onoff); + +void __init msm7627a_camera_init(void) +{ + +#ifndef CONFIG_MSM_CAMERA_V4L2 + int rc; +#endif + + pr_debug("msm7627a_camera_init Entered\n"); + + if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) { + ov7692_cam_req_gpio[0].gpio = + GPIO_SKU7_CAM_VGA_SHDN; + ov7692_cam_gpio_set_tbl[0].gpio = GPIO_SKU7_CAM_VGA_SHDN; + ov7692_cam_gpio_set_tbl[1].gpio = GPIO_SKU7_CAM_VGA_SHDN; + + msm_camera_sensor_ov5647_data.sensor_pwd = + GPIO_SKU7_CAM_5MP_SHDN_N; + msm_camera_sensor_ov5647_data.sensor_reset = + GPIO_SKU7_CAM_5MP_CAMIF_RESET; + + } + + /* LCD and camera power (VREG & LDO) init */ + if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) { + + lcd_camera_power_init(); + evb_camera_gpio_cfg(); + } + +#ifndef CONFIG_MSM_CAMERA_V4L2 + if (machine_is_msm7627a_qrd1()) { + qrd1_camera_gpio_cfg(); + platform_add_devices(camera_devices_qrd, + ARRAY_SIZE(camera_devices_qrd)); + } else if (machine_is_msm7627a_evb() + || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) { + platform_add_devices(camera_devices_evb, + ARRAY_SIZE(camera_devices_evb)); + } else if (machine_is_msm7627a_qrd3()) + return; + else + platform_add_devices(camera_devices_msm, + ARRAY_SIZE(camera_devices_msm)); +#endif + if (!machine_is_msm7627a_qrd1() || !machine_is_msm7627a_evb() + || !machine_is_msm8625_evb() + || !machine_is_msm8625_evt() + || !machine_is_msm7627a_qrd3() + || !machine_is_msm8625_qrd7()) + register_i2c_devices(); +#ifndef CONFIG_MSM_CAMERA_V4L2 + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs_camera), regs_camera); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + return; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_camera), regs_camera); + + if (rc) { + pr_err("%s: could not set voltages: %d\n", __func__, rc); + return; + } +#endif + +#if defined(CONFIG_MSM_CAMERA_V4L2) + msm7x27a_init_cam(); +#endif +#ifndef CONFIG_MSM_CAMERA_V4L2 + if (machine_is_msm7627a_qrd1()) { + i2c_register_board_info(MSM_GSBI0_QUP_I2C_BUS_ID, + i2c_camera_devices_qrd, + ARRAY_SIZE(i2c_camera_devices_qrd)); + } else if (machine_is_msm7627a_evb() + || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) { + pr_debug("machine_is_msm7627a_evb i2c_register_board_info\n"); + i2c_register_board_info(MSM_GSBI0_QUP_I2C_BUS_ID, + i2c_camera_devices_evb, + ARRAY_SIZE(i2c_camera_devices_evb)); + } else +#endif + pr_debug("i2c_register_board_info\n"); + i2c_register_board_info(MSM_GSBI0_QUP_I2C_BUS_ID, + i2c_camera_devices, + ARRAY_SIZE(i2c_camera_devices)); +} diff --git a/arch/arm/mach-msm/board-msm7627a-display.c b/arch/arm/mach-msm/board-msm7627a-display.c new file mode 100644 index 0000000000000000000000000000000000000000..bd38f30214cb70340a3a4fb1907e859381194a97 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a-display.c @@ -0,0 +1,1326 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "board-msm7627a.h" + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_SIZE 0x4BF000 +#define MSM7x25A_MSM_FB_SIZE 0x1C2000 +#define MSM8x25_MSM_FB_SIZE 0x5FA000 +#else +#define MSM_FB_SIZE 0x32A000 +#define MSM7x25A_MSM_FB_SIZE 0x12C000 +#define MSM8x25_MSM_FB_SIZE 0x3FC000 +#endif + +/* + * Reserve enough v4l2 space for a double buffered full screen + * res image (864x480x1.5x2) + */ +#define MSM_V4L2_VIDEO_OVERLAY_BUF_SIZE 1244160 + +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 uint32_t lcdc_truly_gpio_initialized; +static struct regulator_bulk_data regs_truly_lcdc[] = { + { .supply = "rfrx1", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +#define SKU3_LCDC_GPIO_DISPLAY_RESET 90 +#define SKU3_LCDC_GPIO_SPI_MOSI 19 +#define SKU3_LCDC_GPIO_SPI_CLK 20 +#define SKU3_LCDC_GPIO_SPI_CS0_N 21 +#define SKU3_LCDC_LCD_CAMERA_LDO_2V8 35 /*LCD_CAMERA_LDO_2V8*/ +#define SKU3_LCDC_LCD_CAMERA_LDO_1V8 34 /*LCD_CAMERA_LDO_1V8*/ +#define SKU3_1_LCDC_LCD_CAMERA_LDO_1V8 58 /*LCD_CAMERA_LDO_1V8*/ + +static uint32_t lcdc_truly_gpio_table[] = { + 19, + 20, + 21, + 89, + 90, +}; + +static char *lcdc_gpio_name_table[5] = { + "spi_mosi", + "spi_clk", + "spi_cs", + "gpio_bkl_en", + "gpio_disp_reset", +}; + +static int lcdc_truly_gpio_init(void) +{ + int i; + int rc = 0; + + if (!lcdc_truly_gpio_initialized) { + for (i = 0; i < ARRAY_SIZE(lcdc_truly_gpio_table); i++) { + rc = gpio_request(lcdc_truly_gpio_table[i], + lcdc_gpio_name_table[i]); + if (rc < 0) { + pr_err("Error request gpio %s\n", + lcdc_gpio_name_table[i]); + goto truly_gpio_fail; + } + rc = gpio_tlmm_config(GPIO_CFG(lcdc_truly_gpio_table[i], + 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("Error config lcdc gpio:%d\n", + lcdc_truly_gpio_table[i]); + goto truly_gpio_fail; + } + rc = gpio_direction_output(lcdc_truly_gpio_table[i], 0); + if (rc < 0) { + pr_err("Error direct lcdc gpio:%d\n", + lcdc_truly_gpio_table[i]); + goto truly_gpio_fail; + } + } + + lcdc_truly_gpio_initialized = 1; + } + + return rc; + +truly_gpio_fail: + for (; i >= 0; i--) { + pr_err("Freeing GPIO: %d", lcdc_truly_gpio_table[i]); + gpio_free(lcdc_truly_gpio_table[i]); + } + + lcdc_truly_gpio_initialized = 0; + return rc; +} + + +void sku3_lcdc_lcd_camera_power_init(void) +{ + int rc = 0; + u32 socinfo = socinfo_get_platform_type(); + + /* LDO_EXT2V8 */ + if (gpio_request(SKU3_LCDC_LCD_CAMERA_LDO_2V8, "lcd_camera_ldo_2v8")) { + pr_err("failed to request gpio lcd_camera_ldo_2v8\n"); + return; + } + + rc = gpio_tlmm_config(GPIO_CFG(SKU3_LCDC_LCD_CAMERA_LDO_2V8, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + + if (rc < 0) { + pr_err("%s:unable to enable lcd_camera_ldo_2v8!\n", __func__); + goto fail_gpio2; + } + + /* LDO_EVT1V8 */ + if (socinfo == 0x0B) { + if (gpio_request(SKU3_LCDC_LCD_CAMERA_LDO_1V8, + "lcd_camera_ldo_1v8")) { + pr_err("failed to request gpio lcd_camera_ldo_1v8\n"); + goto fail_gpio1; + } + + rc = gpio_tlmm_config(GPIO_CFG(SKU3_LCDC_LCD_CAMERA_LDO_1V8, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + + if (rc < 0) { + pr_err("%s: unable to enable lcdc_camera_ldo_1v8!\n", + __func__); + goto fail_gpio1; + } + } else if (socinfo == 0x0F || machine_is_msm8625_qrd7()) { + if (gpio_request(SKU3_1_LCDC_LCD_CAMERA_LDO_1V8, + "lcd_camera_ldo_1v8")) { + pr_err("failed to request gpio lcd_camera_ldo_1v8\n"); + goto fail_gpio1; + } + + rc = gpio_tlmm_config(GPIO_CFG(SKU3_1_LCDC_LCD_CAMERA_LDO_1V8, + 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + + if (rc < 0) { + pr_err("%s: unable to enable lcdc_camera_ldo_1v8!\n", + __func__); + goto fail_gpio1; + } + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs_truly_lcdc), + regs_truly_lcdc); + if (rc) + pr_err("%s: could not get regulators: %d\n", __func__, rc); + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_truly_lcdc), + regs_truly_lcdc); + if (rc) + pr_err("%s: could not set voltages: %d\n", __func__, rc); + + return; + +fail_gpio1: + if (socinfo == 0x0B) + gpio_free(SKU3_LCDC_LCD_CAMERA_LDO_1V8); + else if (socinfo == 0x0F || machine_is_msm8625_qrd7()) + gpio_free(SKU3_1_LCDC_LCD_CAMERA_LDO_1V8); +fail_gpio2: + gpio_free(SKU3_LCDC_LCD_CAMERA_LDO_2V8); + return; +} + +int sku3_lcdc_lcd_camera_power_onoff(int on) +{ + int rc = 0; + u32 socinfo = socinfo_get_platform_type(); + + if (on) { + if (socinfo == 0x0B) + gpio_set_value_cansleep(SKU3_LCDC_LCD_CAMERA_LDO_1V8, + 1); + else if (socinfo == 0x0F || machine_is_msm8625_qrd7()) + gpio_set_value_cansleep(SKU3_1_LCDC_LCD_CAMERA_LDO_1V8, + 1); + + gpio_set_value_cansleep(SKU3_LCDC_LCD_CAMERA_LDO_2V8, 1); + + rc = regulator_bulk_enable(ARRAY_SIZE(regs_truly_lcdc), + regs_truly_lcdc); + if (rc) + pr_err("%s: could not enable regulators: %d\n", + __func__, rc); + } else { + if (socinfo == 0x0B) + gpio_set_value_cansleep(SKU3_LCDC_LCD_CAMERA_LDO_1V8, + 0); + else if (socinfo == 0x0F || machine_is_msm8625_qrd7()) + gpio_set_value_cansleep(SKU3_1_LCDC_LCD_CAMERA_LDO_1V8, + 0); + + gpio_set_value_cansleep(SKU3_LCDC_LCD_CAMERA_LDO_2V8, 0); + + rc = regulator_bulk_disable(ARRAY_SIZE(regs_truly_lcdc), + regs_truly_lcdc); + if (rc) + pr_err("%s: could not disable regulators: %d\n", + __func__, rc); + } + + return rc; +} + +static int sku3_lcdc_power_save(int on) +{ + int rc = 0; + + if (on) { + sku3_lcdc_lcd_camera_power_onoff(1); + rc = lcdc_truly_gpio_init(); + if (rc < 0) { + pr_err("%s(): Truly GPIO initializations failed", + __func__); + return rc; + } + + if (lcdc_truly_gpio_initialized) { + /*LCD reset*/ + gpio_set_value(SKU3_LCDC_GPIO_DISPLAY_RESET, 1); + msleep(20); + gpio_set_value(SKU3_LCDC_GPIO_DISPLAY_RESET, 0); + msleep(20); + gpio_set_value(SKU3_LCDC_GPIO_DISPLAY_RESET, 1); + msleep(20); + } + } else { + /* pull down LCD IO to avoid current leakage */ + gpio_set_value(SKU3_LCDC_GPIO_SPI_MOSI, 0); + gpio_set_value(SKU3_LCDC_GPIO_SPI_CLK, 0); + gpio_set_value(SKU3_LCDC_GPIO_SPI_CS0_N, 0); + gpio_set_value(SKU3_LCDC_GPIO_DISPLAY_RESET, 0); + + sku3_lcdc_lcd_camera_power_onoff(0); + } + return rc; +} + +static struct msm_panel_common_pdata lcdc_truly_panel_data = { + .panel_config_gpio = NULL, + .gpio_num = lcdc_truly_gpio_table, +}; + +static struct platform_device lcdc_truly_panel_device = { + .name = "lcdc_truly_hvga_ips3p2335_pt", + .id = 0, + .dev = { + .platform_data = &lcdc_truly_panel_data, + } +}; + +static struct regulator_bulk_data regs_lcdc[] = { + { .supply = "gp2", .min_uV = 2850000, .max_uV = 2850000 }, + { .supply = "msme1", .min_uV = 1800000, .max_uV = 1800000 }, +}; +static uint32_t lcdc_gpio_initialized; + +static void lcdc_toshiba_gpio_init(void) +{ + int 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(); + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs_lcdc), + regs_lcdc); + if (rc) { + pr_err("%s: could not get regulators: %d\n", + __func__, rc); + goto fail_gpio1; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_lcdc), + regs_lcdc); + if (rc) { + pr_err("%s: could not set voltages: %d\n", + __func__, rc); + goto fail_vreg; + } + lcdc_gpio_initialized = 1; + } + return; +fail_vreg: + regulator_bulk_free(ARRAY_SIZE(regs_lcdc), regs_lcdc); +fail_gpio1: + 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 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); + + rc = on ? regulator_bulk_enable( + ARRAY_SIZE(regs_lcdc), regs_lcdc) : + regulator_bulk_disable( + ARRAY_SIZE(regs_lcdc), regs_lcdc); + + if (rc) + pr_err("%s: could not %sable regulators: %d\n", + __func__, on ? "en" : "dis", rc); + } + + 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 int msm_lcdc_power_save(int on) +{ + int rc = 0; + if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) + rc = sku3_lcdc_power_save(on); + else + rc = msm_fb_lcdc_power_save(on); + + return rc; +} + +static struct lcdc_platform_data lcdc_pdata = { + .lcdc_gpio_config = NULL, + .lcdc_power_save = msm_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, + } +}; + +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE +static struct resource msm_v4l2_video_overlay_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; +#endif + +#define LCDC_TOSHIBA_FWVGA_PANEL_NAME "lcdc_toshiba_fwvga_pt" +#define MIPI_CMD_RENESAS_FWVGA_PANEL_NAME "mipi_cmd_renesas_fwvga" + +static int msm_fb_detect_panel(const char *name) +{ + int ret = -ENODEV; + + if (machine_is_msm7x27a_surf() || machine_is_msm7625a_surf() || + machine_is_msm8625_surf()) { + if (!strncmp(name, "lcdc_toshiba_fwvga_pt", 21) || + !strncmp(name, "mipi_cmd_renesas_fwvga", 22)) + ret = 0; + } else if (machine_is_msm7x27a_ffa() || machine_is_msm7625a_ffa() + || machine_is_msm8625_ffa()) { + if (!strncmp(name, "mipi_cmd_renesas_fwvga", 22)) + ret = 0; + } else if (machine_is_msm7627a_qrd1()) { + if (!strncmp(name, "mipi_video_truly_wvga", 21)) + ret = 0; + } else if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) { + if (!strncmp(name, "lcdc_truly_hvga_ips3p2335_pt", 28)) + ret = 0; + } else if (machine_is_msm7627a_evb() || machine_is_msm8625_evb()) { + if (!strncmp(name, "mipi_cmd_nt35510_wvga", 21)) + ret = 0; + } else if (machine_is_msm8625_evt()) { + if (!strncmp(name, "mipi_video_nt35510_wvga", 23)) + ret = 0; + } + +#if !defined(CONFIG_FB_MSM_LCDC_AUTO_DETECT) && \ + !defined(CONFIG_FB_MSM_MIPI_PANEL_AUTO_DETECT) && \ + !defined(CONFIG_FB_MSM_LCDC_MIPI_PANEL_AUTO_DETECT) + if (machine_is_msm7x27a_surf() || + machine_is_msm7625a_surf() || + machine_is_msm8625_surf()) { + if (!strncmp(name, LCDC_TOSHIBA_FWVGA_PANEL_NAME, + strnlen(LCDC_TOSHIBA_FWVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + } +#endif + + return ret; +} + +static int mipi_truly_set_bl(int on) +{ + gpio_set_value_cansleep(QRD_GPIO_BACKLIGHT_EN, !!on); + + return 1; +} + +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_MSM_V4L2_VIDEO_OVERLAY_DEVICE +static struct platform_device msm_v4l2_video_overlay_device = { + .name = "msm_v4l2_overlay_pd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_v4l2_video_overlay_resources), + .resource = msm_v4l2_video_overlay_resources, + }; +#endif + + +#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 int evb_backlight_control(int level) +{ + + int i = 0; + int remainder; + /* device address byte = 0x72 */ + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + gpio_set_value(96, 0); + udelay(33); + gpio_set_value(96, 1); + udelay(67); + gpio_set_value(96, 0); + udelay(33); + gpio_set_value(96, 1); + udelay(67); + gpio_set_value(96, 0); + udelay(33); + gpio_set_value(96, 1); + udelay(67); + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + gpio_set_value(96, 0); + udelay(33); + gpio_set_value(96, 1); + udelay(67); + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + + /* t-EOS and t-start */ + gpio_set_value(96, 0); + ndelay(4200); + gpio_set_value(96, 1); + ndelay(9000); + + /* data byte */ + /* RFA = 0 */ + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + + /* Address bits */ + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + + /* Data bits */ + for (i = 0; i < 5; i++) { + remainder = (level) & (16); + if (remainder) { + gpio_set_value(96, 0); + udelay(33); + gpio_set_value(96, 1); + udelay(67); + } else { + gpio_set_value(96, 0); + udelay(67); + gpio_set_value(96, 1); + udelay(33); + } + level = level << 1; + } + + /* t-EOS */ + gpio_set_value(96, 0); + ndelay(12000); + gpio_set_value(96, 1); + return 0; +} + +static int mipi_NT35510_rotate_panel(void) +{ + int rotate = 0; + if (machine_is_msm8625_evt()) + rotate = 1; + + return rotate; +} + +static struct msm_panel_common_pdata mipi_truly_pdata = { + .pmic_backlight = mipi_truly_set_bl, +}; + +static struct platform_device mipi_dsi_truly_panel_device = { + .name = "mipi_truly", + .id = 0, + .dev = { + .platform_data = &mipi_truly_pdata, + } +}; + +static struct msm_panel_common_pdata mipi_NT35510_pdata = { + .pmic_backlight = evb_backlight_control, + .rotate_panel = mipi_NT35510_rotate_panel, +}; + +static struct platform_device mipi_dsi_NT35510_panel_device = { + .name = "mipi_NT35510", + .id = 0, + .dev = { + .platform_data = &mipi_NT35510_pdata, + } +}; + +static struct msm_panel_common_pdata mipi_NT35516_pdata = { + .pmic_backlight = evb_backlight_control, +}; + +static struct platform_device mipi_dsi_NT35516_panel_device = { + .name = "mipi_truly_tft540960_1_e", + .id = 0, + .dev = { + .platform_data = &mipi_NT35516_pdata, + } +}; + +static struct platform_device *msm_fb_devices[] __initdata = { + &msm_fb_device, + &lcdc_toshiba_panel_device, +#ifdef CONFIG_FB_MSM_MIPI_DSI + &mipi_dsi_renesas_panel_device, +#endif +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE + &msm_v4l2_video_overlay_device, +#endif +}; + +static struct platform_device *qrd_fb_devices[] __initdata = { + &msm_fb_device, + &mipi_dsi_truly_panel_device, +}; + +static struct platform_device *qrd3_fb_devices[] __initdata = { + &msm_fb_device, + &lcdc_truly_panel_device, +}; + +static struct platform_device *evb_fb_devices[] __initdata = { + &msm_fb_device, + &mipi_dsi_NT35510_panel_device, + &mipi_dsi_NT35516_panel_device, +}; + +void __init msm_msm7627a_allocate_memory_regions(void) +{ + void *addr; + unsigned long fb_size; + + if (machine_is_msm7625a_surf() || machine_is_msm7625a_ffa()) + fb_size = MSM7x25A_MSM_FB_SIZE; + else if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() + || machine_is_msm8625_evt()) + fb_size = MSM8x25_MSM_FB_SIZE; + else + fb_size = MSM_FB_SIZE; + + addr = alloc_bootmem_align(fb_size, 0x1000); + msm_fb_resources[0].start = __pa(addr); + msm_fb_resources[0].end = msm_fb_resources[0].start + fb_size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for fb\n", fb_size, + addr, __pa(addr)); + +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE + fb_size = MSM_V4L2_VIDEO_OVERLAY_BUF_SIZE; + addr = alloc_bootmem_align(fb_size, 0x1000); + msm_v4l2_video_overlay_resources[0].start = __pa(addr); + msm_v4l2_video_overlay_resources[0].end = + msm_v4l2_video_overlay_resources[0].start + fb_size - 1; + pr_debug("allocating %lu bytes at %p (%lx physical) for v4l2\n", + fb_size, addr, __pa(addr)); +#endif + +} + +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 GPIO_LCD_DSI_SEL 125 +#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_PD */ +}; + +static unsigned lcd_dsi_sel_gpio[] = { + GPIO_CFG(GPIO_LCD_DSI_SEL, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_2MA), +}; + +enum { + DSI_SINGLE_LANE = 1, + DSI_TWO_LANES, +}; + +static int msm_fb_get_lane_config(void) +{ + /* For MSM7627A SURF/FFA and QRD */ + int rc = DSI_TWO_LANES; + if (machine_is_msm7625a_surf() || machine_is_msm7625a_ffa()) { + rc = DSI_SINGLE_LANE; + pr_info("DSI_SINGLE_LANES\n"); + } else { + pr_info("DSI_TWO_LANES\n"); + } + return rc; +} + +static int msm_fb_dsi_client_msm_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() || machine_is_msm7625a_surf() + || machine_is_msm8625_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 int mipi_truly_sel_mode(int video_mode) +{ + int rc = 0; + + rc = gpio_request(GPIO_LCD_DSI_SEL, "lcd_dsi_sel"); + if (rc < 0) + goto gpio_error; + + rc = gpio_tlmm_config(lcd_dsi_sel_gpio[0], GPIO_CFG_ENABLE); + if (rc) + goto gpio_error; + + rc = gpio_direction_output(GPIO_LCD_DSI_SEL, 1); + if (!rc) { + gpio_set_value_cansleep(GPIO_LCD_DSI_SEL, video_mode); + return rc; + } else { + goto gpio_error; + } + +gpio_error: + pr_err("mipi_truly_sel_mode failed\n"); + gpio_free(GPIO_LCD_DSI_SEL); + return rc; +} + +static int msm_fb_dsi_client_qrd1_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_tlmm_config(mipi_dsi_gpio[0], GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("Failed to enable LCDC Bridge reset enable\n"); + return rc; + } + + rc = gpio_direction_output(GPIO_LCDC_BRDG_RESET_N, 1); + if (rc < 0) { + pr_err("Failed GPIO bridge pd\n"); + gpio_free(GPIO_LCDC_BRDG_RESET_N); + return rc; + } + + mipi_truly_sel_mode(1); + + return rc; +} + +#define GPIO_QRD3_LCD_BRDG_RESET_N 85 +#define GPIO_QRD3_LCD_BACKLIGHT_EN 96 +#define GPIO_QRD3_LCD_EXT_2V85_EN 35 +#define GPIO_QRD3_LCD_EXT_1V8_EN 40 + +static unsigned qrd3_mipi_dsi_gpio[] = { + GPIO_CFG(GPIO_QRD3_LCD_BRDG_RESET_N, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), /* GPIO_QRD3_LCD_BRDG_RESET_N */ + GPIO_CFG(GPIO_QRD3_LCD_BACKLIGHT_EN, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), /* GPIO_QRD3_LCD_BACKLIGHT_EN */ + GPIO_CFG(GPIO_QRD3_LCD_EXT_2V85_EN, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), /* GPIO_QRD3_LCD_EXT_2V85_EN */ + GPIO_CFG(GPIO_QRD3_LCD_EXT_1V8_EN, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), /* GPIO_QRD3_LCD_EXT_1V8_EN */ +}; + +static int msm_fb_dsi_client_qrd3_reset(void) +{ + int rc = 0; + + rc = gpio_request(GPIO_QRD3_LCD_BRDG_RESET_N, "qrd3_lcd_brdg_reset_n"); + if (rc < 0) { + pr_err("failed to request qrd3 lcd brdg reset_n\n"); + return rc; + } + + return rc; +} + +static int msm_fb_dsi_client_reset(void) +{ + int rc = 0; + + if (machine_is_msm7627a_qrd1()) + rc = msm_fb_dsi_client_qrd1_reset(); + else if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() + || machine_is_msm8625_evt()) + rc = msm_fb_dsi_client_qrd3_reset(); + else + rc = msm_fb_dsi_client_msm_reset(); + + return rc; + +} + +static struct regulator_bulk_data regs_dsi[] = { + { .supply = "gp2", .min_uV = 2850000, .max_uV = 2850000 }, + { .supply = "msme1", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +static int dsi_gpio_initialized; + +static int mipi_dsi_panel_msm_power(int on) +{ + int rc = 0; + uint32_t lcdc_reset_cfg; + + /* I2C-controlled GPIO Expander -init of the GPIOs very late */ + if (unlikely(!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() || machine_is_msm7625a_surf() + || machine_is_msm8625_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; + } + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs_dsi), regs_dsi); + if (rc) { + pr_err("%s: could not get regulators: %d\n", + __func__, rc); + goto fail_gpio2; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_dsi), + regs_dsi); + if (rc) { + pr_err("%s: could not set voltages: %d\n", + __func__, rc); + goto fail_vreg; + } + if (pmapp_disp_backlight_set_brightness(100)) + pr_err("backlight set brightness failed\n"); + + dsi_gpio_initialized = 1; + } + if (machine_is_msm7x27a_surf() || machine_is_msm7625a_surf() || + machine_is_msm8625_surf()) { + gpio_set_value_cansleep(GPIO_DISPLAY_PWR_EN, on); + gpio_set_value_cansleep(GPIO_BACKLIGHT_EN, on); + } else if (machine_is_msm7x27a_ffa() || machine_is_msm7625a_ffa() + || machine_is_msm8625_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() || + machine_is_msm7625a_surf() || + machine_is_msm8625_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); + } + } else { + gpio_set_value_cansleep(GPIO_LCDC_BRDG_PD, 1); + } + + rc = on ? regulator_bulk_enable(ARRAY_SIZE(regs_dsi), regs_dsi) : + regulator_bulk_disable(ARRAY_SIZE(regs_dsi), regs_dsi); + + if (rc) + pr_err("%s: could not %sable regulators: %d\n", + __func__, on ? "en" : "dis", rc); + + return rc; +fail_vreg: + regulator_bulk_free(ARRAY_SIZE(regs_dsi), regs_dsi); +fail_gpio2: + gpio_free(GPIO_BACKLIGHT_EN); +fail_gpio1: + gpio_free(GPIO_DISPLAY_PWR_EN); + dsi_gpio_initialized = 0; + return rc; +} + +static int mipi_dsi_panel_qrd1_power(int on) +{ + int rc = 0; + + if (!dsi_gpio_initialized) { + rc = gpio_request(QRD_GPIO_BACKLIGHT_EN, "gpio_bkl_en"); + if (rc < 0) + return rc; + + rc = gpio_tlmm_config(GPIO_CFG(QRD_GPIO_BACKLIGHT_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("failed GPIO_BACKLIGHT_EN tlmm config\n"); + return rc; + } + + rc = gpio_direction_output(QRD_GPIO_BACKLIGHT_EN, 1); + if (rc < 0) { + pr_err("failed to enable backlight\n"); + gpio_free(QRD_GPIO_BACKLIGHT_EN); + return rc; + } + dsi_gpio_initialized = 1; + } + + gpio_set_value_cansleep(QRD_GPIO_BACKLIGHT_EN, !!on); + + if (on) { + gpio_set_value_cansleep(GPIO_LCDC_BRDG_RESET_N, 1); + msleep(20); + gpio_set_value_cansleep(GPIO_LCDC_BRDG_RESET_N, 0); + msleep(20); + gpio_set_value_cansleep(GPIO_LCDC_BRDG_RESET_N, 1); + + } + + return rc; +} + +static int qrd3_dsi_gpio_initialized; + +static int mipi_dsi_panel_qrd3_power(int on) +{ + int rc = 0; + + if (!qrd3_dsi_gpio_initialized) { + rc = gpio_request(GPIO_QRD3_LCD_BACKLIGHT_EN, + "qrd3_gpio_bkl_en"); + if (rc < 0) + return rc; + + rc = gpio_request(GPIO_QRD3_LCD_EXT_2V85_EN, + "qrd3_gpio_ext_2v85_en"); + if (rc < 0) + return rc; + + rc = gpio_tlmm_config(GPIO_CFG(GPIO_QRD3_LCD_EXT_2V85_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("failed QRD3 GPIO_QRD3_LCD_EXT_2V85_EN tlmm config\n"); + return rc; + } + + rc = gpio_direction_output(GPIO_QRD3_LCD_EXT_2V85_EN, 1); + if (rc < 0) { + pr_err("failed to enable external 2V85\n"); + gpio_free(GPIO_QRD3_LCD_EXT_2V85_EN); + return rc; + } + + rc = gpio_request(GPIO_QRD3_LCD_EXT_1V8_EN, + "qrd3_gpio_ext_1v8_en"); + if (rc < 0) + return rc; + + rc = gpio_tlmm_config(GPIO_CFG(GPIO_QRD3_LCD_EXT_1V8_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("failed QRD3 GPIO_QRD3_LCD_EXT_1V8_EN tlmm config\n"); + return rc; + } + + rc = gpio_direction_output(GPIO_QRD3_LCD_EXT_1V8_EN, 1); + if (rc < 0) { + pr_err("failed to enable external 1v8\n"); + gpio_free(GPIO_QRD3_LCD_EXT_1V8_EN); + return rc; + } + + qrd3_dsi_gpio_initialized = 1; + } + + if (on) { + rc = gpio_tlmm_config(GPIO_CFG(GPIO_QRD3_LCD_BACKLIGHT_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("failed QRD3 GPIO_BACKLIGHT_EN tlmm config\n"); + return rc; + } + rc = gpio_direction_output(GPIO_QRD3_LCD_BACKLIGHT_EN, 1); + if (rc < 0) { + pr_err("failed to enable backlight\n"); + gpio_free(GPIO_QRD3_LCD_BACKLIGHT_EN); + return rc; + } + + gpio_set_value_cansleep(GPIO_QRD3_LCD_BACKLIGHT_EN, 1); + udelay(190); + gpio_set_value_cansleep(GPIO_QRD3_LCD_BACKLIGHT_EN, 0); + udelay(286); + gpio_set_value_cansleep(GPIO_QRD3_LCD_BACKLIGHT_EN, 1); + /* 1 wire mode starts from this low to high transition */ + udelay(50); + } else { + gpio_tlmm_config(GPIO_CFG(GPIO_QRD3_LCD_BACKLIGHT_EN, 0, + GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + GPIO_CFG_DISABLE); + } + + gpio_set_value_cansleep(GPIO_QRD3_LCD_EXT_2V85_EN, !!on); + gpio_set_value_cansleep(GPIO_QRD3_LCD_EXT_1V8_EN, !!on); + + if (on) { + rc = gpio_tlmm_config(qrd3_mipi_dsi_gpio[0], GPIO_CFG_ENABLE); + + if (rc < 0) { + pr_err("Failed to enable LCD Bridge reset enable\n"); + return rc; + } + + rc = gpio_direction_output(GPIO_QRD3_LCD_BRDG_RESET_N, 1); + + if (rc < 0) { + pr_err("Failed GPIO bridge Reset\n"); + gpio_free(GPIO_QRD3_LCD_BRDG_RESET_N); + return rc; + } + + msleep(20); + gpio_set_value_cansleep(GPIO_QRD3_LCD_BRDG_RESET_N, 0); + msleep(20); + gpio_set_value_cansleep(GPIO_QRD3_LCD_BRDG_RESET_N, 1); + msleep(20); + } else { + gpio_tlmm_config(GPIO_CFG(GPIO_QRD3_LCD_BRDG_RESET_N, 0, + GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + GPIO_CFG_DISABLE); + } + + return rc; +} + +static int mipi_dsi_panel_power(int on) +{ + int rc = 0; + + if (machine_is_msm7627a_qrd1()) + rc = mipi_dsi_panel_qrd1_power(on); + else if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() + || machine_is_msm8625_evt()) + rc = mipi_dsi_panel_qrd3_power(on); + else + rc = mipi_dsi_panel_msm_power(on); + return rc; +} + +#define MDP_303_VSYNC_GPIO 97 + +#ifdef CONFIG_FB_MSM_MIPI_DSI +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 char prim_panel_name[PANEL_NAME_MAX_LEN]; +static int __init prim_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(prim_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("prim_display", prim_display_setup); + +void msm7x27a_set_display_params(char *prim_panel) +{ + if (strnlen(prim_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.prim_panel_name, prim_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.prim_panel_name %s\n", + msm_fb_pdata.prim_panel_name); + } +} + +void __init msm_fb_add_devices(void) +{ + msm7x27a_set_display_params(prim_panel_name); + if (machine_is_msm7627a_qrd1()) + platform_add_devices(qrd_fb_devices, + ARRAY_SIZE(qrd_fb_devices)); + else if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() + || machine_is_msm8625_evt()) { + mipi_NT35510_pdata.bl_lock = 1; + mipi_NT35516_pdata.bl_lock = 1; + platform_add_devices(evb_fb_devices, + ARRAY_SIZE(evb_fb_devices)); + } else if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) { + sku3_lcdc_lcd_camera_power_init(); + platform_add_devices(qrd3_fb_devices, + ARRAY_SIZE(qrd3_fb_devices)); + } else + platform_add_devices(msm_fb_devices, + ARRAY_SIZE(msm_fb_devices)); + + msm_fb_register_device("mdp", &mdp_pdata); + if (machine_is_msm7625a_surf() || machine_is_msm7x27a_surf() || + machine_is_msm8625_surf() || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) + msm_fb_register_device("lcdc", &lcdc_pdata); +#ifdef CONFIG_FB_MSM_MIPI_DSI + msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); +#endif +} diff --git a/arch/arm/mach-msm/board-msm7627a-io.c b/arch/arm/mach-msm/board-msm7627a-io.c new file mode 100644 index 0000000000000000000000000000000000000000..ec168f920bc0fdb3540d696cacc2d5195745c68f --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a-io.c @@ -0,0 +1,891 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "board-msm7627a.h" +#include "devices-msm7x2xa.h" + +#define ATMEL_TS_I2C_NAME "maXTouch" +#define ATMEL_X_OFFSET 13 +#define ATMEL_Y_OFFSET 0 + +#if defined(CONFIG_TOUCHSCREEN_SYNAPTICS_RMI4_I2C) || \ +defined(CONFIG_TOUCHSCREEN_SYNAPTICS_RMI4_I2C_MODULE) + +#ifndef CLEARPAD3000_ATTEN_GPIO +#define CLEARPAD3000_ATTEN_GPIO (48) +#endif + +#ifndef CLEARPAD3000_RESET_GPIO +#define CLEARPAD3000_RESET_GPIO (26) +#endif + +#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.tv64 = 40 * NSEC_PER_USEC, + .poll_time.tv64 = 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, + }, +}; + +/* 8625 keypad device information */ +static unsigned int kp_row_gpios_8625[] = {31}; +static unsigned int kp_col_gpios_8625[] = {36, 37}; + +static const unsigned short keymap_8625[] = { + KEY_VOLUMEUP, + KEY_VOLUMEDOWN, +}; + +static const unsigned short keymap_8625_evt[] = { + KEY_VOLUMEDOWN, + KEY_VOLUMEUP, +}; + +static struct gpio_event_matrix_info kp_matrix_info_8625 = { + .info.func = gpio_event_matrix_func, + .keymap = keymap_8625, + .output_gpios = kp_row_gpios_8625, + .input_gpios = kp_col_gpios_8625, + .noutputs = ARRAY_SIZE(kp_row_gpios_8625), + .ninputs = ARRAY_SIZE(kp_col_gpios_8625), + .settle_time.tv64 = 40 * NSEC_PER_USEC, + .poll_time.tv64 = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | + GPIOKPF_PRINT_UNMAPPED_KEYS, +}; + +static struct gpio_event_info *kp_info_8625[] = { + &kp_matrix_info_8625.info, +}; + +static struct gpio_event_platform_data kp_pdata_8625 = { + .name = "7x27a_kp", + .info = kp_info_8625, + .info_count = ARRAY_SIZE(kp_info_8625) +}; + +static struct platform_device kp_pdev_8625 = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &kp_pdata_8625, + }, +}; + +#define LED_GPIO_PDM 96 +#define LED_RED_GPIO_8625 49 +#define LED_GREEN_GPIO_8625 34 + +static struct gpio_led gpio_leds_config_8625[] = { + { + .name = "green", + .gpio = LED_GREEN_GPIO_8625, + }, + { + .name = "red", + .gpio = LED_RED_GPIO_8625, + }, +}; + +static struct gpio_led_platform_data gpio_leds_pdata_8625 = { + .num_leds = ARRAY_SIZE(gpio_leds_config_8625), + .leds = gpio_leds_config_8625, +}; + +static struct platform_device gpio_leds_8625 = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &gpio_leds_pdata_8625, + }, +}; + +#define MXT_TS_IRQ_GPIO 48 +#define MXT_TS_RESET_GPIO 26 +#define MAX_VKEY_LEN 100 + +static ssize_t mxt_virtual_keys_register(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *virtual_keys = __stringify(EV_KEY) ":" __stringify(KEY_MENU) \ + ":60:840:120:80" ":" __stringify(EV_KEY) \ + ":" __stringify(KEY_HOME) ":180:840:120:80" \ + ":" __stringify(EV_KEY) ":" \ + __stringify(KEY_BACK) ":300:840:120:80" \ + ":" __stringify(EV_KEY) ":" \ + __stringify(KEY_SEARCH) ":420:840:120:80" "\n"; + + return snprintf(buf, strnlen(virtual_keys, MAX_VKEY_LEN) + 1 , "%s", + virtual_keys); +} + +static struct kobj_attribute mxt_virtual_keys_attr = { + .attr = { + .name = "virtualkeys.atmel_mxt_ts", + .mode = S_IRUGO, + }, + .show = &mxt_virtual_keys_register, +}; + +static struct attribute *mxt_virtual_key_properties_attrs[] = { + &mxt_virtual_keys_attr.attr, + NULL, +}; + +static struct attribute_group mxt_virtual_key_properties_attr_group = { + .attrs = mxt_virtual_key_properties_attrs, +}; + +struct kobject *mxt_virtual_key_properties_kobj; + +static int mxt_vkey_setup(void) +{ + int retval; + + mxt_virtual_key_properties_kobj = + kobject_create_and_add("board_properties", NULL); + if (mxt_virtual_key_properties_kobj) + retval = sysfs_create_group(mxt_virtual_key_properties_kobj, + &mxt_virtual_key_properties_attr_group); + if (!mxt_virtual_key_properties_kobj || retval) + pr_err("failed to create mxt board_properties\n"); + + return retval; +} + +static const u8 mxt_config_data[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 16, 1, 0, 0, 0, 0, 0, 0, + /* T7 Object */ + 32, 16, 50, + /* T8 Object */ + 30, 0, 20, 20, 0, 0, 20, 0, 50, 0, + /* T9 Object */ + 3, 0, 0, 18, 11, 0, 32, 75, 3, 3, + 0, 1, 1, 0, 10, 10, 10, 10, 31, 3, + 223, 1, 11, 11, 15, 15, 151, 43, 145, 80, + 100, 15, 0, 0, 0, + /* T15 Object */ + 131, 0, 11, 11, 1, 1, 0, 45, 3, 0, + 0, + /* T18 Object */ + 0, 0, + /* T19 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + /* T23 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /* T25 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T40 Object */ + 0, 0, 0, 0, 0, + /* T42 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* T46 Object */ + 0, 2, 32, 48, 0, 0, 0, 0, 0, + /* T47 Object */ + 1, 20, 60, 5, 2, 50, 40, 0, 0, 40, + /* T48 Object */ + 1, 12, 80, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 6, 0, 0, 100, 4, 64, + 10, 0, 20, 5, 0, 38, 0, 20, 0, 0, + 0, 0, 0, 0, 16, 65, 3, 1, 1, 0, + 10, 10, 10, 0, 0, 15, 15, 154, 58, 145, + 80, 100, 15, 3, +}; + +static const u8 mxt_config_data_evt[] = { + /* T6 Object */ + 0, 0, 0, 0, 0, 0, + /* T38 Object */ + 20, 0, 0, 0, 0, 0, 0, 0, + /* T7 Object */ + 24, 12, 10, + /* T8 Object */ + 30, 0, 20, 20, 0, 0, 9, 45, 10, 192, + /* T9 Object */ + 3, 0, 0, 18, 11, 0, 16, 60, 3, 1, + 0, 1, 1, 0, 10, 10, 10, 10, 107, 3, + 223, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 20, 15, 0, 0, 2, + /* T15 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + /* T18 Object */ + 0, 0, + /* T19 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + /* T23 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /* T25 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + /* T40 Object */ + 17, 0, 0, 30, 30, + /* T42 Object */ + 3, 20, 45, 40, 128, 0, 0, 0, + /* T46 Object */ + 0, 2, 16, 16, 0, 0, 0, 0, 0, + /* T47 Object */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* T48 Object */ + 1, 128, 96, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 6, 0, 0, 63, 4, 64, + 10, 0, 32, 5, 0, 38, 0, 8, 0, 0, + 0, 0, 0, 0, 16, 65, 3, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, +}; + +static struct mxt_config_info mxt_config_array[] = { + { + .config = mxt_config_data, + .config_length = ARRAY_SIZE(mxt_config_data), + .family_id = 0x81, + .variant_id = 0x01, + .version = 0x10, + .build = 0xAA, + }, +}; + +static int mxt_key_codes[MXT_KEYARRAY_MAX_KEYS] = { + [0] = KEY_HOME, + [1] = KEY_MENU, + [9] = KEY_BACK, + [10] = KEY_SEARCH, +}; + +static struct mxt_platform_data mxt_platform_data = { + .config_array = mxt_config_array, + .config_array_size = ARRAY_SIZE(mxt_config_array), + .panel_minx = 0, + .panel_maxx = 479, + .panel_miny = 0, + .panel_maxy = 799, + .disp_minx = 0, + .disp_maxx = 479, + .disp_miny = 0, + .disp_maxy = 799, + .irqflags = IRQF_TRIGGER_FALLING, + .i2c_pull_up = true, + .reset_gpio = MXT_TS_RESET_GPIO, + .irq_gpio = MXT_TS_IRQ_GPIO, + .key_codes = mxt_key_codes, +}; + +static struct i2c_board_info mxt_device_info[] __initdata = { + { + I2C_BOARD_INFO("atmel_mxt_ts", 0x4a), + .platform_data = &mxt_platform_data, + .irq = MSM_GPIO_TO_INT(MXT_TS_IRQ_GPIO), + }, +}; + +static int synaptics_touchpad_setup(void); + +static struct msm_gpio clearpad3000_cfg_data[] = { + {GPIO_CFG(CLEARPAD3000_ATTEN_GPIO, 0, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_6MA), "rmi4_attn"}, + {GPIO_CFG(CLEARPAD3000_RESET_GPIO, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_8MA), "rmi4_reset"}, +}; + +static struct rmi_XY_pair rmi_offset = {.x = 0, .y = 0}; +static struct rmi_range rmi_clipx = {.min = 48, .max = 980}; +static struct rmi_range rmi_clipy = {.min = 7, .max = 1647}; +static struct rmi_f11_functiondata synaptics_f11_data = { + .swap_axes = false, + .flipX = false, + .flipY = false, + .offset = &rmi_offset, + .button_height = 113, + .clipX = &rmi_clipx, + .clipY = &rmi_clipy, +}; + +#define MAX_LEN 100 + +static ssize_t clearpad3000_virtual_keys_register(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *virtual_keys = __stringify(EV_KEY) ":" __stringify(KEY_MENU) \ + ":60:830:120:60" ":" __stringify(EV_KEY) \ + ":" __stringify(KEY_HOME) ":180:830:120:60" \ + ":" __stringify(EV_KEY) ":" \ + __stringify(KEY_SEARCH) ":300:830:120:60" \ + ":" __stringify(EV_KEY) ":" \ + __stringify(KEY_BACK) ":420:830:120:60" "\n"; + + return snprintf(buf, strnlen(virtual_keys, MAX_LEN) + 1 , "%s", + virtual_keys); +} + +static struct kobj_attribute clearpad3000_virtual_keys_attr = { + .attr = { + .name = "virtualkeys.sensor00fn11", + .mode = S_IRUGO, + }, + .show = &clearpad3000_virtual_keys_register, +}; + +static struct attribute *virtual_key_properties_attrs[] = { + &clearpad3000_virtual_keys_attr.attr, + NULL +}; + +static struct attribute_group virtual_key_properties_attr_group = { + .attrs = virtual_key_properties_attrs, +}; + +struct kobject *virtual_key_properties_kobj; + +static struct rmi_functiondata synaptics_functiondata[] = { + { + .function_index = RMI_F11_INDEX, + .data = &synaptics_f11_data, + }, +}; + +static struct rmi_functiondata_list synaptics_perfunctiondata = { + .count = ARRAY_SIZE(synaptics_functiondata), + .functiondata = synaptics_functiondata, +}; + +static struct rmi_sensordata synaptics_sensordata = { + .perfunctiondata = &synaptics_perfunctiondata, + .rmi_sensor_setup = synaptics_touchpad_setup, +}; + +static struct rmi_i2c_platformdata synaptics_platformdata = { + .i2c_address = 0x2c, + .irq_type = IORESOURCE_IRQ_LOWLEVEL, + .sensordata = &synaptics_sensordata, +}; + +static struct i2c_board_info synaptic_i2c_clearpad3k[] = { + { + I2C_BOARD_INFO("rmi4_ts", 0x2c), + .platform_data = &synaptics_platformdata, + }, +}; + +static int synaptics_touchpad_setup(void) +{ + int retval = 0; + + virtual_key_properties_kobj = + kobject_create_and_add("board_properties", NULL); + if (virtual_key_properties_kobj) + retval = sysfs_create_group(virtual_key_properties_kobj, + &virtual_key_properties_attr_group); + if (!virtual_key_properties_kobj || retval) + pr_err("failed to create ft5202 board_properties\n"); + + retval = msm_gpios_request_enable(clearpad3000_cfg_data, + sizeof(clearpad3000_cfg_data)/sizeof(struct msm_gpio)); + if (retval) { + pr_err("%s:Failed to obtain touchpad GPIO %d. Code: %d.", + __func__, CLEARPAD3000_ATTEN_GPIO, retval); + retval = 0; /* ignore the err */ + } + synaptics_platformdata.irq = gpio_to_irq(CLEARPAD3000_ATTEN_GPIO); + + gpio_set_value(CLEARPAD3000_RESET_GPIO, 0); + usleep(10000); + gpio_set_value(CLEARPAD3000_RESET_GPIO, 1); + usleep(50000); + + return retval; +} +#endif + +static struct regulator_bulk_data regs_atmel[] = { + { .supply = "ldo12", .min_uV = 2700000, .max_uV = 3300000 }, + { .supply = "smps3", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +#define ATMEL_TS_GPIO_IRQ 82 + +static int atmel_ts_power_on(bool on) +{ + int rc = on ? + regulator_bulk_enable(ARRAY_SIZE(regs_atmel), regs_atmel) : + regulator_bulk_disable(ARRAY_SIZE(regs_atmel), regs_atmel); + + if (rc) + pr_err("%s: could not %sable regulators: %d\n", + __func__, on ? "en" : "dis", rc); + else + msleep(50); + + return rc; +} + +static int atmel_ts_platform_init(struct i2c_client *client) +{ + int rc; + struct device *dev = &client->dev; + + rc = regulator_bulk_get(dev, ARRAY_SIZE(regs_atmel), regs_atmel); + if (rc) { + dev_err(dev, "%s: could not get regulators: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_atmel), regs_atmel); + if (rc) { + dev_err(dev, "%s: could not set voltages: %d\n", + __func__, rc); + goto reg_free; + } + + 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) { + dev_err(dev, "%s: gpio_tlmm_config for %d failed\n", + __func__, ATMEL_TS_GPIO_IRQ); + goto reg_free; + } + + /* configure touchscreen interrupt gpio */ + rc = gpio_request(ATMEL_TS_GPIO_IRQ, "atmel_maxtouch_gpio"); + if (rc) { + dev_err(dev, "%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) { + dev_err(dev, "%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); +reg_free: + regulator_bulk_free(ARRAY_SIZE(regs_atmel), regs_atmel); +out: + 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); + regulator_bulk_free(ARRAY_SIZE(regs_atmel), regs_atmel); + 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(); +} + + +static struct maxtouch_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), + }, +}; + +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 FT5X06_IRQ_GPIO 48 +#define FT5X06_RESET_GPIO 26 + +static ssize_t +ft5x06_virtual_keys_register(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, 200, + __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":40:510:80:60" + ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":120:510:80:60" + ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":200:510:80:60" + ":" __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":280:510:80:60" + "\n"); +} + +static struct kobj_attribute ft5x06_virtual_keys_attr = { + .attr = { + .name = "virtualkeys.ft5x06_ts", + .mode = S_IRUGO, + }, + .show = &ft5x06_virtual_keys_register, +}; + +static struct attribute *ft5x06_virtual_key_properties_attrs[] = { + &ft5x06_virtual_keys_attr.attr, + NULL, +}; + +static struct attribute_group ft5x06_virtual_key_properties_attr_group = { + .attrs = ft5x06_virtual_key_properties_attrs, +}; + +struct kobject *ft5x06_virtual_key_properties_kobj; + +static struct ft5x06_ts_platform_data ft5x06_platformdata = { + .x_max = 320, + .y_max = 480, + .reset_gpio = FT5X06_RESET_GPIO, + .irq_gpio = FT5X06_IRQ_GPIO, + .irqflags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT, +}; + +static struct i2c_board_info ft5x06_device_info[] __initdata = { + { + I2C_BOARD_INFO("ft5x06_ts", 0x38), + .platform_data = &ft5x06_platformdata, + .irq = MSM_GPIO_TO_INT(FT5X06_IRQ_GPIO), + }, +}; + +static void __init ft5x06_touchpad_setup(void) +{ + int rc; + + rc = gpio_tlmm_config(GPIO_CFG(FT5X06_IRQ_GPIO, 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__, FT5X06_IRQ_GPIO); + + rc = gpio_tlmm_config(GPIO_CFG(FT5X06_RESET_GPIO, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_8MA), GPIO_CFG_ENABLE); + if (rc) + pr_err("%s: gpio_tlmm_config for %d failed\n", + __func__, FT5X06_RESET_GPIO); + + ft5x06_virtual_key_properties_kobj = + kobject_create_and_add("board_properties", NULL); + + if (ft5x06_virtual_key_properties_kobj) + rc = sysfs_create_group(ft5x06_virtual_key_properties_kobj, + &ft5x06_virtual_key_properties_attr_group); + + if (!ft5x06_virtual_key_properties_kobj || rc) + pr_err("%s: failed to create board_properties\n", __func__); + + i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + ft5x06_device_info, + ARRAY_SIZE(ft5x06_device_info)); +} + +/* SKU3/SKU7 keypad device information */ +#define KP_INDEX_SKU3(row, col) ((row)*ARRAY_SIZE(kp_col_gpios_sku3) + (col)) +static unsigned int kp_row_gpios_sku3[] = {31, 32}; +static unsigned int kp_col_gpios_sku3[] = {36, 37}; + +static const unsigned short keymap_sku3[] = { + [KP_INDEX_SKU3(0, 0)] = KEY_VOLUMEUP, + [KP_INDEX_SKU3(0, 1)] = KEY_VOLUMEDOWN, + [KP_INDEX_SKU3(1, 1)] = KEY_CAMERA, +}; + +static struct gpio_event_matrix_info kp_matrix_info_sku3 = { + .info.func = gpio_event_matrix_func, + .keymap = keymap_sku3, + .output_gpios = kp_row_gpios_sku3, + .input_gpios = kp_col_gpios_sku3, + .noutputs = ARRAY_SIZE(kp_row_gpios_sku3), + .ninputs = ARRAY_SIZE(kp_col_gpios_sku3), + .settle_time.tv64 = 40 * NSEC_PER_USEC, + .poll_time.tv64 = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | + GPIOKPF_PRINT_UNMAPPED_KEYS, +}; + +static struct gpio_event_info *kp_info_sku3[] = { + &kp_matrix_info_sku3.info, +}; +static struct gpio_event_platform_data kp_pdata_sku3 = { + .name = "7x27a_kp", + .info = kp_info_sku3, + .info_count = ARRAY_SIZE(kp_info_sku3) +}; + +static struct platform_device kp_pdev_sku3 = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &kp_pdata_sku3, + }, +}; + +static struct led_info ctp_backlight_info = { + .name = "button-backlight", + .flags = PM_MPP__I_SINK__LEVEL_40mA << 16 | PM_MPP_7, +}; + +static struct led_platform_data ctp_backlight_pdata = { + .leds = &ctp_backlight_info, + .num_leds = 1, +}; + +static struct platform_device pmic_mpp_leds_pdev = { + .name = "pmic-mpp-leds", + .id = -1, + .dev = { + .platform_data = &ctp_backlight_pdata, + }, +}; + +void __init msm7627a_add_io_devices(void) +{ + /* touchscreen */ + if (machine_is_msm7625a_surf() || machine_is_msm7625a_ffa()) { + 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)); + /* keypad */ + platform_device_register(&kp_pdev); + + /* headset */ + platform_device_register(&hs_pdev); + + /* LED: 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); + + /* Vibrator */ + if (machine_is_msm7x27a_ffa() || machine_is_msm7625a_ffa() + || machine_is_msm8625_ffa()) + msm_init_pmic_vibrator(); +} + +void __init qrd7627a_add_io_devices(void) +{ + int rc; + + /* touchscreen */ + if (machine_is_msm7627a_qrd1()) { + i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + synaptic_i2c_clearpad3k, + ARRAY_SIZE(synaptic_i2c_clearpad3k)); + } else if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() || + machine_is_msm8625_evt()) { + /* Use configuration data for EVT */ + if (machine_is_msm8625_evt()) { + mxt_config_array[0].config = mxt_config_data_evt; + mxt_config_array[0].config_length = + ARRAY_SIZE(mxt_config_data_evt); + mxt_platform_data.panel_maxy = 875; + mxt_vkey_setup(); + } + + rc = gpio_tlmm_config(GPIO_CFG(MXT_TS_IRQ_GPIO, 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__, MXT_TS_IRQ_GPIO); + } + + rc = gpio_tlmm_config(GPIO_CFG(MXT_TS_RESET_GPIO, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_8MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config for %d failed\n", + __func__, MXT_TS_RESET_GPIO); + } + + i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + mxt_device_info, + ARRAY_SIZE(mxt_device_info)); + } else if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) { + ft5x06_touchpad_setup(); + } + + /* headset */ + platform_device_register(&hs_pdev); + + /* vibrator */ +#ifdef CONFIG_MSM_RPC_VIBRATOR + msm_init_pmic_vibrator(); +#endif + + /* keypad */ + if (machine_is_msm8625_evt()) + kp_matrix_info_8625.keymap = keymap_8625_evt; + + if (machine_is_msm7627a_evb() || machine_is_msm8625_evb() || + machine_is_msm8625_evt()) + platform_device_register(&kp_pdev_8625); + else if (machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7()) + platform_device_register(&kp_pdev_sku3); + + /* leds */ + if (machine_is_msm7627a_evb() || machine_is_msm8625_evb()) { + rc = gpio_tlmm_config(GPIO_CFG(LED_RED_GPIO_8625, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_16MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config for %d failed\n", + __func__, LED_RED_GPIO_8625); + } + + rc = gpio_tlmm_config(GPIO_CFG(LED_GREEN_GPIO_8625, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_16MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config for %d failed\n", + __func__, LED_GREEN_GPIO_8625); + } + + platform_device_register(&gpio_leds_8625); + platform_device_register(&pmic_mpp_leds_pdev); + } +} diff --git a/arch/arm/mach-msm/board-msm7627a-storage.c b/arch/arm/mach-msm/board-msm7627a-storage.c new file mode 100644 index 0000000000000000000000000000000000000000..e2184f4c7e52636062c37a2b27fa8684f147e193 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a-storage.c @@ -0,0 +1,412 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 "pm.h" +#include "board-msm7627a.h" + +#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)) + +#define MAX_SDCC_CONTROLLER 4 +static unsigned long vreg_sts, gpio_sts; + +struct sdcc_gpio { + struct msm_gpio *cfg_data; + uint32_t size; + struct msm_gpio *sleep_cfg_data; +}; + +/** + * Due to insufficient drive strengths for SDC GPIO lines some old versioned + * SD/MMC cards may cause data CRC errors. Hence, set optimal values + * for SDC slots based on timing closure and marginality. SDC1 slot + * require higher value since it should handle bad signal quality due + * to size of T-flash adapters. + */ +static struct msm_gpio sdc1_cfg_data[] = { + {GPIO_CFG(51, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_14MA), + "sdc1_dat_3"}, + {GPIO_CFG(52, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_14MA), + "sdc1_dat_2"}, + {GPIO_CFG(53, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_14MA), + "sdc1_dat_1"}, + {GPIO_CFG(54, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_14MA), + "sdc1_dat_0"}, + {GPIO_CFG(55, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_14MA), + "sdc1_cmd"}, + {GPIO_CFG(56, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_14MA), + "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_10MA), + "sdc2_cmd"}, + {GPIO_CFG(64, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc2_dat_3"}, + {GPIO_CFG(65, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc2_dat_2"}, + {GPIO_CFG(66, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc2_dat_1"}, + {GPIO_CFG(67, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc2_dat_0"}, +}; + +static struct msm_gpio sdc2_sleep_cfg_data[] = { + {GPIO_CFG(62, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc2_clk"}, + {GPIO_CFG(63, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, GPIO_CFG_2MA), + "sdc2_cmd"}, + {GPIO_CFG(64, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, GPIO_CFG_2MA), + "sdc2_dat_3"}, + {GPIO_CFG(65, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, GPIO_CFG_2MA), + "sdc2_dat_2"}, + {GPIO_CFG(66, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, GPIO_CFG_2MA), + "sdc2_dat_1"}, + {GPIO_CFG(67, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, 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_10MA), + "sdc3_cmd"}, + {GPIO_CFG(90, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_3"}, + {GPIO_CFG(91, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_2"}, + {GPIO_CFG(92, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_1"}, + {GPIO_CFG(93, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_0"}, +#ifdef CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT + {GPIO_CFG(19, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_7"}, + {GPIO_CFG(20, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_6"}, + {GPIO_CFG(21, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_5"}, + {GPIO_CFG(108, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc3_dat_4"}, +#endif +}; + +static struct msm_gpio sdc4_cfg_data[] = { + {GPIO_CFG(19, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc4_dat_3"}, + {GPIO_CFG(20, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc4_dat_2"}, + {GPIO_CFG(21, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc4_dat_1"}, + {GPIO_CFG(107, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "sdc4_cmd"}, + {GPIO_CFG(108, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_10MA), + "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 gpio_sdc1_hw_det = 85; +static void gpio_sdc1_config(void) +{ + if (machine_is_msm7627a_qrd1() || machine_is_msm7627a_evb() + || machine_is_msm8625_evb() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) + gpio_sdc1_hw_det = 42; +} + +static struct regulator *sdcc_vreg_data[MAX_SDCC_CONTROLLER]; +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 regulator *curr = sdcc_vreg_data[dev_id - 1]; + + if (test_bit(dev_id, &vreg_sts) == enable) + return 0; + + if (!curr) + return -ENODEV; + + if (IS_ERR(curr)) + return PTR_ERR(curr); + + if (enable) { + set_bit(dev_id, &vreg_sts); + + rc = regulator_enable(curr); + if (rc) + pr_err("%s: could not enable regulator: %d\n", + __func__, rc); + } else { + clear_bit(dev_id, &vreg_sts); + + rc = regulator_disable(curr); + if (rc) + pr_err("%s: could not disable regulator: %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; +} + +#if defined(CONFIG_MMC_MSM_SDC1_SUPPORT) \ + && defined(CONFIG_MMC_MSM_CARD_HW_DETECTION) +static unsigned int msm7627a_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) { + if (machine_is_msm7627a_qrd1() || + machine_is_msm7627a_evb() || + machine_is_msm8625_evb() || + machine_is_msm7627a_qrd3() || + machine_is_msm8625_qrd7()) + status = !gpio_get_value(gpio_sdc1_hw_det); + else + 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 = msm7627a_sdcc_slot_status, + .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, + .sdiowakeup_irq = MSM_GPIO_TO_INT(66), + .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 + +static int __init mmc_regulator_init(int sdcc_no, const char *supply, int uV) +{ + int rc; + + BUG_ON(sdcc_no < 1 || sdcc_no > 4); + + sdcc_no--; + + sdcc_vreg_data[sdcc_no] = regulator_get(NULL, supply); + + if (IS_ERR(sdcc_vreg_data[sdcc_no])) { + rc = PTR_ERR(sdcc_vreg_data[sdcc_no]); + pr_err("%s: could not get regulator \"%s\": %d\n", + __func__, supply, rc); + goto out; + } + + rc = regulator_set_voltage(sdcc_vreg_data[sdcc_no], uV, uV); + + if (rc) { + pr_err("%s: could not set voltage for \"%s\" to %d uV: %d\n", + __func__, supply, uV, rc); + goto reg_free; + } + + return rc; + +reg_free: + regulator_put(sdcc_vreg_data[sdcc_no]); +out: + sdcc_vreg_data[sdcc_no] = NULL; + return rc; +} + +void __init msm7627a_init_mmc(void) +{ + /* eMMC slot */ +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + + /* There is no eMMC on SDC3 for QRD3 based devices */ + if (!(machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7())) { + if (mmc_regulator_init(3, "emmc", 3000000)) + return; + msm_add_sdcc(3, &sdc3_plat_data); + } +#endif + /* Micro-SD slot */ +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + gpio_sdc1_config(); + if (mmc_regulator_init(1, "mmc", 2850000)) + return; +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + /* 8x25 EVT do not use hw detector */ + if (!(machine_is_msm8625_evt())) + sdc1_plat_data.status_irq = MSM_GPIO_TO_INT(gpio_sdc1_hw_det); + if (machine_is_msm8625_evt()) + sdc1_plat_data.status = NULL; +#endif + + msm_add_sdcc(1, &sdc1_plat_data); +#endif + /* SDIO WLAN slot */ +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + if (mmc_regulator_init(2, "smps3", 1800000)) + return; + 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)) + /* There is no SDC4 for QRD3/7 based devices */ + if (!(machine_is_msm7627a_qrd3() || machine_is_msm8625_qrd7())) { + if (mmc_regulator_init(4, "smps3", 1800000)) + return; + msm_add_sdcc(4, &sdc4_plat_data); + } +#endif +} +#endif diff --git a/arch/arm/mach-msm/board-msm7627a-wlan.c b/arch/arm/mach-msm/board-msm7627a-wlan.c new file mode 100644 index 0000000000000000000000000000000000000000..07b7ab0385e44be7da9e545890a823a581c3b3d2 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a-wlan.c @@ -0,0 +1,385 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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-msm7627a.h" +#include "devices-msm7x2xa.h" +#include "timer.h" + +#define GPIO_WLAN_3V3_EN 119 +static const char *id = "WLAN"; + +enum { + WLAN_VREG_S3 = 0, + WLAN_VREG_L17, + WLAN_VREG_L19 +}; + +struct wlan_vreg_info { + const char *vreg_id; + unsigned int level_min; + unsigned int level_max; + unsigned int pmapp_id; + unsigned int is_vreg_pin_controlled; + struct regulator *reg; +}; + +static struct wlan_vreg_info vreg_info[] = { + {"msme1", 1800000, 1800000, 2, 0, NULL}, + {"bt", 3300000, 3300000, 21, 1, NULL}, + {"wlan4", 1800000, 1800000, 23, 1, NULL} +}; + +int gpio_wlan_sys_rest_en = 134; +static void gpio_wlan_config(void) +{ + if (machine_is_msm7627a_qrd1() || machine_is_msm7627a_evb() + || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) + gpio_wlan_sys_rest_en = 124; +} + +static unsigned int qrf6285_init_regs(void) +{ + struct regulator_bulk_data regs[ARRAY_SIZE(vreg_info)]; + int i = 0, rc = 0; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + regs[i].supply = vreg_info[i].vreg_id; + regs[i].min_uV = vreg_info[i].level_min; + regs[i].max_uV = vreg_info[i].level_max; + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs), regs); + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(regs); i++) + vreg_info[i].reg = regs[i].consumer; + +out: + return rc; +} + +static unsigned int setup_wlan_gpio(bool on) +{ + int rc = 0; + + if (on) { + rc = gpio_direction_output(gpio_wlan_sys_rest_en, 1); + msleep(100); + } else { + gpio_set_value_cansleep(gpio_wlan_sys_rest_en, 0); + rc = gpio_direction_input(gpio_wlan_sys_rest_en); + msleep(100); + } + + if (rc) + pr_err("%s: WLAN sys_reset_en GPIO: Error", __func__); + + return rc; +} + +static unsigned int setup_wlan_clock(bool on) +{ + int rc = 0; + + if (on) { + /* Vote for A0 clock */ + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_A0, + PMAPP_CLOCK_VOTE_ON); + } else { + /* Vote against A0 clock */ + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_A0, + PMAPP_CLOCK_VOTE_OFF); + } + + if (rc) + pr_err("%s: Configuring A0 clock for WLAN: Error", __func__); + + return rc; +} + +static unsigned int wlan_switch_regulators(int on) +{ + int rc = 0, index = 0; + + if (machine_is_msm7627a_qrd1()) + index = 2; + + for ( ; index < ARRAY_SIZE(vreg_info); index++) { + if (on) { + rc = regulator_set_voltage(vreg_info[index].reg, + vreg_info[index].level_min, + vreg_info[index].level_max); + if (rc) { + pr_err("%s:%s set voltage failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + goto reg_disable; + } + + rc = regulator_enable(vreg_info[index].reg); + if (rc) { + pr_err("%s:%s vreg enable failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + goto reg_disable; + } + + if (vreg_info[index].is_vreg_pin_controlled) { + rc = pmapp_vreg_lpm_pincntrl_vote(id, + vreg_info[index].pmapp_id, + PMAPP_CLOCK_ID_A0, 1); + if (rc) { + pr_err("%s:%s pincntrl failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + goto pin_cnt_fail; + } + } + } else { + if (vreg_info[index].is_vreg_pin_controlled) { + rc = pmapp_vreg_lpm_pincntrl_vote(id, + vreg_info[index].pmapp_id, + PMAPP_CLOCK_ID_A0, 0); + if (rc) { + pr_err("%s:%s pincntrl failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + goto pin_cnt_fail; + } + } + + rc = regulator_disable(vreg_info[index].reg); + if (rc) { + pr_err("%s:%s vreg disable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + goto reg_disable; + } + } + } + return 0; +pin_cnt_fail: + if (on) + regulator_disable(vreg_info[index].reg); +reg_disable: + if (!machine_is_msm7627a_qrd1()) { + while (index) { + if (on) { + index--; + regulator_disable(vreg_info[index].reg); + regulator_put(vreg_info[index].reg); + } + } + } + return rc; +} + +static unsigned int msm_AR600X_setup_power(bool on) +{ + int rc = 0; + static bool init_done; + + if (unlikely(!init_done)) { + gpio_wlan_config(); + rc = qrf6285_init_regs(); + if (rc) { + pr_err("%s: qrf6285 init failed = %d\n", __func__, rc); + return rc; + } else { + init_done = true; + } + } + + rc = wlan_switch_regulators(on); + if (rc) { + pr_err("%s: wlan_switch_regulators error = %d\n", __func__, rc); + goto out; + } + + /* GPIO_WLAN_3V3_EN is only required for the QRD7627a */ + if (machine_is_msm7627a_qrd1()) { + rc = gpio_tlmm_config(GPIO_CFG(GPIO_WLAN_3V3_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s gpio_tlmm_config 119 failed,error = %d\n", + __func__, rc); + goto reg_disable; + } + gpio_set_value(GPIO_WLAN_3V3_EN, 1); + } + + /* + * gpio_wlan_sys_rest_en is not from the GPIO expander for QRD7627a, + * EVB1.0 and QRD8625,so the below step is required for those devices. + */ + if (machine_is_msm7627a_qrd1() || machine_is_msm7627a_evb() + || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) { + rc = gpio_tlmm_config(GPIO_CFG(gpio_wlan_sys_rest_en, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s gpio_tlmm_config 119 failed,error = %d\n", + __func__, rc); + goto qrd_gpio_fail; + } + gpio_set_value(gpio_wlan_sys_rest_en, 1); + } else { + rc = gpio_request(gpio_wlan_sys_rest_en, "WLAN_DEEP_SLEEP_N"); + if (rc) { + pr_err("%s: WLAN sys_rest_en GPIO %d request failed %d\n", + __func__, + gpio_wlan_sys_rest_en, rc); + goto qrd_gpio_fail; + } + rc = setup_wlan_gpio(on); + if (rc) { + pr_err("%s: wlan_set_gpio = %d\n", __func__, rc); + goto gpio_fail; + } + } + + /* Enable the A0 clock */ + rc = setup_wlan_clock(on); + if (rc) { + pr_err("%s: setup_wlan_clock = %d\n", __func__, rc); + goto set_gpio_fail; + } + + /* Configure A0 clock 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 A0 to Pin controllable failed %d\n", + __func__, rc); + goto set_clock_fail; + } + + pr_info("WLAN power-up success\n"); + return 0; +set_clock_fail: + setup_wlan_clock(0); +set_gpio_fail: + setup_wlan_gpio(0); +gpio_fail: + gpio_free(gpio_wlan_sys_rest_en); +qrd_gpio_fail: + /* GPIO_WLAN_3V3_EN is only required for the QRD7627a */ + if (machine_is_msm7627a_qrd1()) + gpio_free(GPIO_WLAN_3V3_EN); +reg_disable: + wlan_switch_regulators(0); +out: + pr_info("WLAN power-up failed\n"); + return rc; +} + +static unsigned int msm_AR600X_shutdown_power(bool on) +{ + int rc = 0; + + /* Disable the A0 clock */ + rc = setup_wlan_clock(on); + if (rc) { + pr_err("%s: setup_wlan_clock = %d\n", __func__, rc); + goto set_clock_fail; + } + + /* + * gpio_wlan_sys_rest_en is not from the GPIO expander for QRD7627a, + * EVB1.0 and QRD8625,so the below step is required for those devices. + */ + if (machine_is_msm7627a_qrd1() || machine_is_msm7627a_evb() + || machine_is_msm8625_evb() + || machine_is_msm8625_evt() + || machine_is_msm7627a_qrd3() + || machine_is_msm8625_qrd7()) { + rc = gpio_tlmm_config(GPIO_CFG(gpio_wlan_sys_rest_en, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s gpio_tlmm_config 119 failed,error = %d\n", + __func__, rc); + goto gpio_fail; + } + gpio_set_value(gpio_wlan_sys_rest_en, 0); + } else { + gpio_request(gpio_wlan_sys_rest_en, "WLAN_DEEP_SLEEP_N"); + rc = setup_wlan_gpio(on); + if (rc) { + pr_err("%s: wlan_set_gpio = %d\n", __func__, rc); + goto set_gpio_fail; + } + gpio_free(gpio_wlan_sys_rest_en); + } + + /* GPIO_WLAN_3V3_EN is only required for the QRD7627a */ + if (machine_is_msm7627a_qrd1()) { + rc = gpio_tlmm_config(GPIO_CFG(GPIO_WLAN_3V3_EN, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s gpio_tlmm_config 119 failed,error = %d\n", + __func__, rc); + goto qrd_gpio_fail; + } + gpio_set_value(GPIO_WLAN_3V3_EN, 0); + } + + rc = wlan_switch_regulators(on); + if (rc) { + pr_err("%s: wlan_switch_regulators error = %d\n", + __func__, rc); + goto reg_disable; + } + + pr_info("WLAN power-down success\n"); + return 0; +set_clock_fail: + setup_wlan_clock(0); +set_gpio_fail: + setup_wlan_gpio(0); +gpio_fail: + gpio_free(gpio_wlan_sys_rest_en); +qrd_gpio_fail: + /* GPIO_WLAN_3V3_EN is only required for the QRD7627a */ + if (machine_is_msm7627a_qrd1()) + gpio_free(GPIO_WLAN_3V3_EN); +reg_disable: + wlan_switch_regulators(0); + pr_info("WLAN power-down failed\n"); + return rc; +} + +int ar600x_wlan_power(bool on) +{ + if (on) + msm_AR600X_setup_power(on); + else + msm_AR600X_shutdown_power(on); + + return 0; +} diff --git a/arch/arm/mach-msm/board-msm7627a.h b/arch/arm/mach-msm/board-msm7627a.h new file mode 100644 index 0000000000000000000000000000000000000000..4357e01fd9e4cde1c8760663eff34b31e38e2533 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7627a.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_7627A__ +#define __ARCH_ARM_MACH_MSM_BOARD_7627A__ + +#include "pm.h" +void __init msm7627a_init_mmc(void); + +void __init msm_msm7627a_allocate_memory_regions(void); +void __init msm_fb_add_devices(void); + +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, +}; + +enum { + QRD_GPIO_HOST_VBUS_EN = 107, + QRD_GPIO_BT_SYS_REST_EN = 114, + QRD_GPIO_WAKE_ON_WIRELESS, + QRD_GPIO_BACKLIGHT_EN, + QRD_GPIO_NC, + QRD_GPIO_CAM_3MP_PWDN, /* CAM_VGA */ + QRD_GPIO_WLAN_EN, + QRD_GPIO_CAM_5MP_SHDN_EN, + QRD_GPIO_CAM_5MP_RESET, + QRD_GPIO_TP, + QRD_GPIO_CAM_GP_CAMIF_RESET, +}; + +#define ADSP_RPC_PROG 0x3000000a +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + +#define FPGA_MSM_CNTRL_REG2 0x90008010 +#define BAHAMA_SLAVE_ID_FM_REG 0x02 +#define BAHAMA_SLAVE_ID_FM_ADDR 0x2A +#define BAHAMA_SLAVE_ID_QMEMBIST_ADDR 0x7B +#define FM_GPIO 83 +#define BT_PCM_BCLK_MODE 0x88 +#define BT_PCM_DIN_MODE 0x89 +#define BT_PCM_DOUT_MODE 0x8A +#define BT_PCM_SYNC_MODE 0x8B +#define FM_I2S_SD_MODE 0x8E +#define FM_I2S_WS_MODE 0x8F +#define FM_I2S_SCK_MODE 0x90 +#define I2C_PIN_CTL 0x15 +#define I2C_NORMAL 0x40 + +struct bahama_config_register { + u8 reg; + u8 value; + u8 mask; +}; + +struct bt_vreg_info { + const char *name; + unsigned int pmapp_id; + unsigned int min_level; + unsigned int max_level; + unsigned int is_pin_controlled; + struct regulator *reg; +}; + +void __init msm7627a_bt_power_init(void); +#endif + +void __init msm7627a_camera_init(void); +int lcd_camera_power_onoff(int on); + +void __init msm7627a_add_io_devices(void); +void __init qrd7627a_add_io_devices(void); +#endif diff --git a/arch/arm/mach-msm/board-msm7x27.c b/arch/arm/mach-msm/board-msm7x27.c index 6d84ee740df483911363f2ef54311b618787bf54..d42458f25399d9ac5570e6d46e52fd17cc5c3a81 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-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -13,15 +13,17 @@ * GNU General Public License for more details. * */ -#include #include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -32,17 +34,64 @@ #include #endif +#include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include + +#ifdef CONFIG_USB_G_ANDROID +#include +#include +#endif +#include "board-msm7627-regulator.h" #include "devices.h" -#include "socinfo.h" #include "clock.h" +#include "acpuclock.h" +#include "msm-keypad-devices.h" +#include "pm.h" +#include "pm-boot.h" + +#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 +#define ADSP_RPC_PROG 0x3000000a static struct resource smc91x_resources[] = { [0] = { @@ -64,66 +113,1873 @@ static struct platform_device smc91x_device = { .resource = smc91x_resources, }; -static struct platform_device *devices[] __initdata = { - &msm_device_uart3, - &msm_device_smd, - &msm_device_dmov, - &msm_device_nand, - &smc91x_device, +#ifdef CONFIG_USB_G_ANDROID +static struct android_usb_platform_data android_usb_pdata = { + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, }; -extern struct sys_timer msm_timer; +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; +#endif -static void __init msm7x2x_init_irq(void) +#ifdef CONFIG_USB_EHCI_MSM_72K +static void msm_hsusb_vbus_power(unsigned phy_info, int on) { - msm_init_irq(); + if (on) + msm_hsusb_vbus_powerup(); + else + msm_hsusb_vbus_shutdown(); } -static void __init msm7x2x_init(void) +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 (socinfo_init() < 0) - BUG(); + 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 +static int msm_hsusb_ldo_init(int init) +{ + static struct regulator *reg_hsusb; + int rc; + if (init) { + reg_hsusb = regulator_get(NULL, "usb"); + if (IS_ERR(reg_hsusb)) { + rc = PTR_ERR(reg_hsusb); + pr_err("%s: could not get regulator: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_set_voltage(reg_hsusb, 3300000, 3300000); + if (rc < 0) { + pr_err("%s: could not set voltage: %d\n", + __func__, rc); + goto usb_reg_fail; + } + + rc = regulator_enable(reg_hsusb); + if (rc < 0) { + pr_err("%s: could not enable regulator: %d\n", + __func__, rc); + goto usb_reg_fail; + } + + /* + * 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. + */ + + rc = regulator_disable(reg_hsusb); + if (rc < 0) { + pr_err("%s: could not disable regulator: %d\n", + __func__, rc); + goto usb_reg_fail; + } + + regulator_put(reg_hsusb); + } + + return 0; +usb_reg_fail: + regulator_put(reg_hsusb); +out: + return rc; +} + +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, +}; + +#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--) + regulator_put(vreg[i - 1]); + return rc; + +vreg_lcdc_fail: + if (on) { + for (; i > 0; i--) + regulator_disable(vreg[i - 1]); + } else { + for (; i > 0; i--) + regulator_enable(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()) { - 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)) { - printk(KERN_ERR - "%s: Err: Config GPIO-85 INT\n", - __func__); + 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) +{ + int pin, rc; + static struct regulator *vreg_bt; + + printk(KERN_DEBUG "%s\n", __func__); + + /* do not have vreg bt defined, gp6 is the same */ + /* vreg_get parameter 1 (struct device *) is ignored */ + + 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; + } + } + vreg_bt = regulator_get(NULL, "gp6"); + + if (IS_ERR(vreg_bt)) { + rc = PTR_ERR(vreg_bt); + pr_err("%s: could get not regulator: %d\n", + __func__, rc); + goto out; + } + + /* units of mV, steps of 50 mV */ + rc = regulator_set_voltage(vreg_bt, 2600000, 2600000); + if (rc < 0) { + pr_err("%s: could not set voltage: %d\n", __func__, rc); + goto bt_vreg_fail; + } + rc = regulator_enable(vreg_bt); + if (rc < 0) { + pr_err("%s: could not enable regulator: %d\n", + __func__, rc); + goto bt_vreg_fail; + } + } else { + rc = regulator_disable(vreg_bt); + if (rc < 0) { + pr_err("%s: could not disable regulator: %d\n", + __func__, rc); + goto bt_vreg_fail; + } + regulator_put(vreg_bt); + 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; - platform_add_devices(devices, ARRAY_SIZE(devices)); +bt_vreg_fail: + regulator_put(vreg_bt); +out: + return rc; } -static void __init msm7x2x_map_io(void) +static void __init bt_power_init(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); + 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 void msm_camera_vreg_config(int vreg_en) +{ + int rc; + static struct regulator *vreg_gp2; + static struct regulator *vreg_gp3; + + if (vreg_gp2 == NULL && vreg_gp3 == NULL) { + vreg_gp2 = regulator_get(NULL, "gp2"); + if (IS_ERR(vreg_gp2)) { + rc = PTR_ERR(vreg_gp2); + pr_err("%s: could not get regulator: %d\n", + __func__, rc); + return; + } + + rc = regulator_set_voltage(vreg_gp2, 1800000, 1800000); + if (rc < 0) { + pr_err("%s: could not set voltage: %d\n", + __func__, rc); + goto cam_vreg_fail; + } - if (machine_is_msm7x25_surf() || machine_is_msm7x25_ffa()) - msm_clock_init(msm_clocks_7x25, msm_num_clocks_7x25); + vreg_gp3 = regulator_get(NULL, "gp3"); + if (IS_ERR(vreg_gp3)) { + rc = PTR_ERR(vreg_gp3); + pr_err("%s: could not get regulator: %d\n", + __func__, rc); + goto cam_vreg_fail; + } + + rc = regulator_set_voltage(vreg_gp3, 2850000, 2850000); + if (rc < 0) { + pr_err("%s: could not set voltage: %d\n", __func__, rc); + goto cam_vreg2_fail; + } + + return; + + } + + if (vreg_gp2 == NULL || vreg_gp3 == NULL) { + pr_err("Camera Regulators are not initialized\n"); + return; + } + + if (vreg_en) { + rc = regulator_enable(vreg_gp2); + if (rc) { + pr_err("%s: could not enable regulator: %d\n", + __func__, rc); + goto cam_vreg2_fail; + } + + rc = regulator_enable(vreg_gp3); + if (rc) { + pr_err("%s: could not enable regulator: %d\n", + __func__, rc); + goto vreg_gp3_fail; + } + } else { + rc = regulator_disable(vreg_gp2); + if (rc) { + pr_err("%s: could not disable regulator: %d\n", + __func__, rc); + return; + } + + rc = regulator_disable(vreg_gp3); + if (rc) { + pr_err("%s: could not disable regulator: %d\n", + __func__, rc); + goto cam_vreg2_fail; + } + } + + return; + +vreg_gp3_fail: + if (vreg_en) + regulator_disable(vreg_gp2); + +cam_vreg2_fail: + regulator_put(vreg_gp3); +cam_vreg_fail: + regulator_put(vreg_gp2); + vreg_gp3 = NULL; + vreg_gp2 = NULL; +} + +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 = MSM7XXX_MDC_PHYS, + .ioext.mdcsz = MSM7XXX_MDC_SIZE, + .ioext.appphy = MSM7XXX_CLK_CTL_PHYS, + .ioext.appsz = MSM7XXX_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 + +#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 = { + &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_G_ANDROID + &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, + .mdp_rev = MDP_REV_30, +}; + +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) +{ + msm_init_irq(); +} + +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 regulator *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 = regulator_disable(vreg_mmc); + if (rc) { + pr_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 = regulator_set_voltage(vreg_mmc, 2850000, 2850000); + if (!rc) + rc = regulator_enable(vreg_mmc); + } + if (rc) { + pr_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, +}; +#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, + .sdiowakeup_irq = MSM_GPIO_TO_INT(66), + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#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, +}; +#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, +}; +#endif + +static void __init msm7x2x_init_mmc(void) +{ + if (!machine_is_msm7x25_ffa() && !machine_is_msm7x27_ffa()) { + vreg_mmc = regulator_get(NULL, "mmc"); + if (IS_ERR(vreg_mmc)) { + pr_err("%s: could not get regulator: %ld\n", + __func__, PTR_ERR(vreg_mmc)); + } + } + +#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_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)].latency = 16000, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN)] + .latency = 12000, + + [MSM_PM_MODE(0, 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_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 16000, + .residency = 20000, + }, + + [MSM_PM_MODE(0, 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_MODE(0, 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 struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS, + .p_addr = 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 struct platform_device msm_proccomm_regulator_dev = { + .name = PROCCOMM_REGULATOR_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &msm7627_proccomm_regulator_data + } +}; + +static void __init msm7627_init_regulators(void) +{ + int rc = platform_device_register(&msm_proccomm_regulator_dev); + if (rc) + pr_err("%s: could not register regulator device: %d\n", + __func__, rc); +} +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 msm_adsp_add_pdev(void) +{ + int rc = 0; + struct rpc_board_dev *rpc_adsp_pdev; + + rpc_adsp_pdev = kzalloc(sizeof(struct rpc_board_dev), GFP_KERNEL); + if (rpc_adsp_pdev == NULL) { + pr_err("%s: Memory Allocation failure\n", __func__); + return; + } + rpc_adsp_pdev->prog = ADSP_RPC_PROG; + rpc_adsp_pdev->pdev = msm_adsp_device; + rc = msm_rpc_add_board_dev(rpc_adsp_pdev, 1); + if (rc < 0) { + pr_err("%s: return val: %d\n", __func__, rc); + kfree(rpc_adsp_pdev); + } +} + +static void __init msm7x2x_init(void) +{ + + msm7627_init_regulators(); +#ifdef CONFIG_ARCH_MSM7X25 + msm_clock_init(msm_clocks_7x25, msm_num_clocks_7x25); +#elif defined(CONFIG_ARCH_MSM7X27) + msm_clock_init(&msm7x27_clock_init_data); +#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_CFG_INPUT, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), + GPIO_CFG_ENABLE)) { + printk(KERN_ERR + "%s: Err: Config GPIO-85 INT\n", + __func__); + } + } +#endif + acpuclk_init(&acpuclk_7x27_soc_data); + + usb_mpp_init(); + + +#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_adsp_add_pdev(); + 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)); + + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + + 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(); + + 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 } @@ -131,31 +1987,43 @@ static void __init msm7x2x_map_io(void) MACHINE_START(MSM7X27_SURF, "QCT MSM7x27 SURF") .atag_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, + .handle_irq = vic_handle_irq, MACHINE_END MACHINE_START(MSM7X27_FFA, "QCT MSM7x27 FFA") .atag_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, + .handle_irq = vic_handle_irq, MACHINE_END MACHINE_START(MSM7X25_SURF, "QCT MSM7x25 SURF") .atag_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, + .handle_irq = vic_handle_irq, MACHINE_END MACHINE_START(MSM7X25_FFA, "QCT MSM7x25 FFA") .atag_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, + .handle_irq = vic_handle_irq, MACHINE_END diff --git a/arch/arm/mach-msm/board-msm7x27a-regulator.c b/arch/arm/mach-msm/board-msm7x27a-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..c67ab7f65fd1d4287844e8b0abbeb24f5c9e5812 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7x27a-regulator.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "board-msm7x27a-regulator.h" + +#define VOLTAGE_RANGE(min_uV, max_uV, step_uV) ((max_uV - min_uV) / step_uV) + +/* Physically available PMIC regulator voltage setpoint ranges */ +#define p_ranges VOLTAGE_RANGE(1500000, 3300000, 25000) + +#define n_ranges VOLTAGE_RANGE(750000, 1525000, 12500) + +#define s_ranges (VOLTAGE_RANGE(700000, 1500000, 12500) + \ + VOLTAGE_RANGE(1500000, 3050000, 25000)) + +#define PCOM_VREG_CONSUMERS(name) \ + static struct regulator_consumer_supply __pcom_vreg_supply_##name[] + +#define PCOM_VREG_INIT_DATA(_name, _supply, _min_uV, _max_uV, _always_on, \ + _boot_on, _apply_uV, _supply_uV)\ +{ \ + .supply_regulator = _supply, \ + .consumer_supplies = __pcom_vreg_supply_##_name, \ + .num_consumer_supplies = ARRAY_SIZE(__pcom_vreg_supply_##_name), \ + .constraints = { \ + .name = #_name, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .valid_modes_mask = REGULATOR_MODE_NORMAL, \ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS, \ + .input_uV = _supply_uV, \ + .apply_uV = _apply_uV, \ + .boot_on = _boot_on, \ + .always_on = _always_on \ + } \ +} + +#define PCOM_VREG_SMP(_name, _id, _supply, _min_uV, _max_uV, _rise_time, \ + _pulldown, _always_on, _boot_on, _apply_uV, _supply_uV, _range) \ +{ \ + .init_data = PCOM_VREG_INIT_DATA(_name, _supply, _min_uV, _max_uV, \ + _always_on, _boot_on, _apply_uV, _supply_uV), \ + .id = _id, \ + .rise_time = _rise_time, \ + .pulldown = _pulldown, \ + .negative = 0, \ + .n_voltages = _range##_ranges, \ +} + +#define PCOM_VREG_LDO PCOM_VREG_SMP + +#define PCOM_VREG_NCP(_name, _id, _supply, _min_uV, _max_uV, _rise_time, \ + _always_on, _boot_on, _apply_uV, _supply_uV) \ +{ \ + .init_data = PCOM_VREG_INIT_DATA(_name, _supply, -(_min_uV), \ + -(_max_uV), _always_on, _boot_on, _apply_uV, _supply_uV), \ + .id = _id, \ + .rise_time = _rise_time, \ + .pulldown = -1, \ + .negative = 1, \ +} + +PCOM_VREG_CONSUMERS(smps1) = { + REGULATOR_SUPPLY("smps1", NULL), + REGULATOR_SUPPLY("msmc1", NULL), +}; + +PCOM_VREG_CONSUMERS(smps2) = { + REGULATOR_SUPPLY("smps2", NULL), + REGULATOR_SUPPLY("msmc2", NULL), +}; + +PCOM_VREG_CONSUMERS(smps3) = { + REGULATOR_SUPPLY("smps3", NULL), + REGULATOR_SUPPLY("msme1", NULL), + REGULATOR_SUPPLY("vcc_i2c", "1-004a"), + REGULATOR_SUPPLY("vcc_i2c", "1-0038"), +}; + +PCOM_VREG_CONSUMERS(smps4) = { + REGULATOR_SUPPLY("smps4", NULL), + REGULATOR_SUPPLY("rf", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo01) = { + REGULATOR_SUPPLY("ldo01", NULL), + REGULATOR_SUPPLY("ldo1", NULL), + REGULATOR_SUPPLY("rfrx1", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo02) = { + REGULATOR_SUPPLY("ldo02", NULL), + REGULATOR_SUPPLY("ldo2", NULL), + REGULATOR_SUPPLY("rfrx2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo03) = { + REGULATOR_SUPPLY("ldo03", NULL), + REGULATOR_SUPPLY("ldo3", NULL), + REGULATOR_SUPPLY("mddi", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo04) = { + REGULATOR_SUPPLY("ldo04", NULL), + REGULATOR_SUPPLY("ldo4", NULL), + REGULATOR_SUPPLY("pllx", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo05) = { + REGULATOR_SUPPLY("ldo05", NULL), + REGULATOR_SUPPLY("ldo5", NULL), + REGULATOR_SUPPLY("wlan2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo06) = { + REGULATOR_SUPPLY("ldo06", NULL), + REGULATOR_SUPPLY("ldo6", NULL), + REGULATOR_SUPPLY("wlan3", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo07) = { + REGULATOR_SUPPLY("ldo07", NULL), + REGULATOR_SUPPLY("ldo7", NULL), + REGULATOR_SUPPLY("msma", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo08) = { + REGULATOR_SUPPLY("ldo08", NULL), + REGULATOR_SUPPLY("ldo8", NULL), + REGULATOR_SUPPLY("tcxo", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo09) = { + REGULATOR_SUPPLY("ldo09", NULL), + REGULATOR_SUPPLY("ldo9", NULL), + REGULATOR_SUPPLY("usb2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo10) = { + REGULATOR_SUPPLY("ldo10", NULL), + REGULATOR_SUPPLY("emmc", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo11) = { + REGULATOR_SUPPLY("ldo11", NULL), + REGULATOR_SUPPLY("wlan_tcx0", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo12) = { + REGULATOR_SUPPLY("ldo12", NULL), + REGULATOR_SUPPLY("gp2", NULL), + REGULATOR_SUPPLY("vdd_ana", "1-004a"), + REGULATOR_SUPPLY("vdd", "1-0038"), +}; + +PCOM_VREG_CONSUMERS(ldo13) = { + REGULATOR_SUPPLY("ldo13", NULL), + REGULATOR_SUPPLY("mmc", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo14) = { + REGULATOR_SUPPLY("ldo14", NULL), + REGULATOR_SUPPLY("usb", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo15) = { + REGULATOR_SUPPLY("ldo15", NULL), + REGULATOR_SUPPLY("usim2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo16) = { + REGULATOR_SUPPLY("ldo16", NULL), + REGULATOR_SUPPLY("ruim", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo17) = { + REGULATOR_SUPPLY("ldo17", NULL), + REGULATOR_SUPPLY("bt", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo18) = { + REGULATOR_SUPPLY("ldo18", NULL), + REGULATOR_SUPPLY("rftx", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo19) = { + REGULATOR_SUPPLY("ldo19", NULL), + REGULATOR_SUPPLY("wlan4", NULL), +}; + +PCOM_VREG_CONSUMERS(ncp) = { + REGULATOR_SUPPLY("ncp", NULL), +}; + +static struct proccomm_regulator_info msm7x27a_pcom_vreg_info[] = { + /* Standard regulators (SMPS and LDO) + * R = rise time (us) + * P = pulldown (1 = pull down, 0 = float, -1 = don't care) + * A = always on + * B = boot on + * V = automatic voltage set (meaningful for single-voltage regs only) + * S = supply voltage (uV) + * T = type of regulator (smps, pldo, nldo) + * name id supp min uV max uV R P A B V S T*/ + PCOM_VREG_SMP(smps1, 3, NULL, 1100000, 1100000, 0, -1, 0, 0, 0, 0, s), + PCOM_VREG_SMP(smps2, 4, NULL, 1100000, 1100000, 0, -1, 0, 0, 0, 0, s), + PCOM_VREG_SMP(smps3, 2, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0, s), + PCOM_VREG_SMP(smps4, 24, NULL, 2100000, 2100000, 0, -1, 0, 0, 0, 0, s), + PCOM_VREG_LDO(ldo01, 12, NULL, 1800000, 2100000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo02, 13, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo03, 49, NULL, 1200000, 1200000, 0, -1, 0, 0, 0, 0, n), + PCOM_VREG_LDO(ldo04, 50, NULL, 1100000, 1100000, 0, -1, 0, 0, 0, 0, n), + PCOM_VREG_LDO(ldo05, 45, NULL, 1300000, 1350000, 0, -1, 0, 0, 0, 0, n), + PCOM_VREG_LDO(ldo06, 51, NULL, 1200000, 1200000, 0, -1, 0, 0, 0, 0, n), + PCOM_VREG_LDO(ldo07, 0, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo08, 9, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo09, 44, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo10, 52, NULL, 1800000, 3000000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo11, 53, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo12, 21, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo13, 18, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo14, 16, NULL, 3300000, 3300000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo15, 54, NULL, 1800000, 2850000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo16, 19, NULL, 1800000, 2850000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo17, 56, NULL, 2900000, 3300000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo18, 11, NULL, 2700000, 2700000, 0, -1, 0, 0, 0, 0, p), + PCOM_VREG_LDO(ldo19, 57, NULL, 1200000, 1800000, 0, -1, 0, 0, 0, 0, p), + + PCOM_VREG_NCP(ncp, 31, NULL, -1800000, -1800000, 0, 0, 0, 0, 0), +}; + +struct proccomm_regulator_platform_data msm7x27a_proccomm_regulator_data = { + .regs = msm7x27a_pcom_vreg_info, + .nregs = ARRAY_SIZE(msm7x27a_pcom_vreg_info) +}; diff --git a/arch/arm/mach-msm/board-msm7x27a-regulator.h b/arch/arm/mach-msm/board-msm7x27a-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..01dc70e987d64e3d558c6e4a8af4fd36ca867317 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7x27a-regulator.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 __ARCH_ARM_MACH_MSM_BOARD_7X27A_REGULATOR_H__ +#define __ARCH_ARM_MACH_MSM_BOARD_7X27A_REGULATOR_H__ + +#include "proccomm-regulator.h" + +extern struct proccomm_regulator_platform_data msm7x27a_proccomm_regulator_data; + +#endif diff --git a/arch/arm/mach-msm/board-msm7x27a.c b/arch/arm/mach-msm/board-msm7x27a.c new file mode 100644 index 0000000000000000000000000000000000000000..6ecd1e31da1749676ea15c3451958503e3133fc9 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7x27a.c @@ -0,0 +1,1261 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +#include +#include +#include +#include +#include +#include "devices.h" +#include "timer.h" +#include "board-msm7x27a-regulator.h" +#include "devices-msm7x2xa.h" +#include "pm.h" +#include +#include +#include "pm-boot.h" +#include "board-msm7627a.h" + +#define PMEM_KERNEL_EBI1_SIZE 0x3A000 +#define MSM_PMEM_AUDIO_SIZE 0x1F4000 + +#if defined(CONFIG_GPIO_SX150X) +enum { + SX150X_CORE, +}; + +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 = 0x02, + .io_open_drain_ena = 0xfef8, + .irq_summary = -1, + }, +}; +#endif + + +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) +static struct platform_device msm_wlan_ar6000_pm_device = { + .name = "wlan_ar6000_pm_dev", + .id = -1, +}; +#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 void __init register_i2c_devices(void) +{ + if (machine_is_msm7x27a_surf() || machine_is_msm7625a_surf() || + machine_is_msm8625_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)); +} +#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, + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi1_qup_i2c_pdata = { + .clk_freq = 100000, + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +#ifdef CONFIG_ARCH_MSM7X27A +#define MSM_PMEM_MDP_SIZE 0x2300000 +#define MSM7x25A_MSM_PMEM_MDP_SIZE 0x1500000 + +#define MSM_PMEM_ADSP_SIZE 0x1100000 +#define MSM7x25A_MSM_PMEM_ADSP_SIZE 0xB91000 + +#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 regulator *reg_hsusb; +static int msm_hsusb_ldo_init(int init) +{ + int rc = 0; + + if (init) { + reg_hsusb = regulator_get(NULL, "usb"); + if (IS_ERR(reg_hsusb)) { + rc = PTR_ERR(reg_hsusb); + pr_err("%s: could not get regulator: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_set_voltage(reg_hsusb, 3300000, 3300000); + if (rc) { + pr_err("%s: could not set voltage: %d\n", + __func__, rc); + goto reg_free; + } + + return 0; + } + /* else fall through */ +reg_free: + regulator_put(reg_hsusb); +out: + reg_hsusb = NULL; + return rc; +} + +static int msm_hsusb_ldo_enable(int enable) +{ + static int ldo_status; + + if (IS_ERR_OR_NULL(reg_hsusb)) + return reg_hsusb ? PTR_ERR(reg_hsusb) : -ENODEV; + + if (ldo_status == enable) + return 0; + + ldo_status = enable; + + return enable ? + regulator_enable(reg_hsusb) : + regulator_disable(reg_hsusb); +} + +#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, + .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, +}; + +#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_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 16000, + .residency = 20000, + }, + [MSM_PM_MODE(0, 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_MODE(0, 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_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, + }, +}; + +static struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS, + .p_addr = 0, +}; + +/* 8625 PM platform data */ +static struct msm_pm_platform_data msm8625_pm_data[MSM_PM_SLEEP_MODE_NR * 2] = { + /* CORE0 entries */ + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 16000, + .residency = 20000, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 12000, + .residency = 20000, + }, + + /* picked latency & redisdency values from 7x30 */ + [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 = 10, + }, + + /* picked latency & redisdency values from 7x30 */ + [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 = 10, + }, + +}; + +static struct msm_pm_boot_platform_data msm_pm_8625_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR, + .v_addr = MSM_CFG_CTL_BASE, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 1, + .memory_type = MEMTYPE_EBI1, + .request_region = request_fmem_c_region, + .release_region = release_fmem_c_region, + .reusable = 1, +}; + +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); + +#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(STEREO_HEADSET_AND_SPEAKER, 31), + SND(CURRENT, 0x7FFFFFFE), + 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<reusable) + fmem_pdata.size += pdata->size; + + reusable_count += (pdata->reusable) ? 1 : 0; + + if (pdata->reusable && reusable_count > 1) { + pr_err("%s: Too many PMEM devices specified as reusable. PMEM device %s was not configured as reusable.\n", + __func__, pdata->name); + pdata->reusable = 0; + } + } +#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 + unsigned int i; + for (i = 0; i < ARRAY_SIZE(pmem_pdata_array); ++i) + reserve_memory_for(pmem_pdata_array[i]); + + 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 msm8625_reserve(void) +{ + msm7x27a_reserve(); + memblock_remove(MSM8625_SECONDARY_PHYS, SZ_8); + memblock_remove(MSM8625_WARM_BOOT_PHYS, SZ_32); +} + +static void __init msm7x27a_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 void __init msm8625_device_i2c_init(void) +{ + msm8625_gsbi0_qup_i2c_device.dev.platform_data = + &msm_gsbi0_qup_i2c_pdata; + msm8625_gsbi1_qup_i2c_device.dev.platform_data = + &msm_gsbi1_qup_i2c_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() || + machine_is_msm7625a_surf() || machine_is_msm8625_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() || machine_is_msm7625a_surf()) + ebi2_cfg |= (1 << 31); + + writel(ebi2_cfg, ebi2_cfg_ptr); + iounmap(ebi2_cfg_ptr); +} + +static struct platform_device msm_proccomm_regulator_dev = { + .name = PROCCOMM_REGULATOR_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &msm7x27a_proccomm_regulator_data + } +}; + +static void msm_adsp_add_pdev(void) +{ + int rc = 0; + struct rpc_board_dev *rpc_adsp_pdev; + + rpc_adsp_pdev = kzalloc(sizeof(struct rpc_board_dev), GFP_KERNEL); + if (rpc_adsp_pdev == NULL) { + pr_err("%s: Memory Allocation failure\n", __func__); + return; + } + rpc_adsp_pdev->prog = ADSP_RPC_PROG; + + if (cpu_is_msm8625()) + rpc_adsp_pdev->pdev = msm8625_device_adsp; + else + rpc_adsp_pdev->pdev = msm_adsp_device; + rc = msm_rpc_add_board_dev(rpc_adsp_pdev, 1); + if (rc < 0) { + pr_err("%s: return val: %d\n", __func__, rc); + kfree(rpc_adsp_pdev); + } +} + +static void __init msm7627a_rumi3_init(void) +{ + msm7x27a_init_ebi2(); + platform_add_devices(rumi_sim_devices, + ARRAY_SIZE(rumi_sim_devices)); +} + +static void __init msm8625_rumi3_init(void) +{ + msm7x2x_misc_init(); + msm_adsp_add_pdev(); + msm8625_device_i2c_init(); + platform_add_devices(msm8625_rumi3_devices, + ARRAY_SIZE(msm8625_rumi3_devices)); + + msm_pm_set_platform_data(msm8625_pm_data, + ARRAY_SIZE(msm8625_pm_data)); + BUG_ON(msm_pm_boot_init(&msm_pm_8625_boot_pdata)); + msm8x25_spm_device_init(); +} + +#define UART1DM_RX_GPIO 45 + +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) +static int __init msm7x27a_init_ar6000pm(void) +{ + msm_wlan_ar6000_pm_device.dev.platform_data = &ar600x_wlan_power; + return platform_device_register(&msm_wlan_ar6000_pm_device); +} +#else +static int __init msm7x27a_init_ar6000pm(void) { return 0; } +#endif + +static void __init msm7x27a_init_regulators(void) +{ + int rc = platform_device_register(&msm_proccomm_regulator_dev); + if (rc) + pr_err("%s: could not register regulator device: %d\n", + __func__, rc); +} + +static void __init msm7x27a_add_footswitch_devices(void) +{ + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); +} + +static void __init msm7x27a_add_platform_devices(void) +{ + if (machine_is_msm8625_surf() || machine_is_msm8625_ffa()) { + platform_add_devices(msm8625_surf_devices, + ARRAY_SIZE(msm8625_surf_devices)); + } else { + platform_add_devices(msm7627a_surf_ffa_devices, + ARRAY_SIZE(msm7627a_surf_ffa_devices)); + } + + platform_add_devices(common_devices, + ARRAY_SIZE(common_devices)); +} + +static void __init msm7x27a_uartdm_config(void) +{ + msm7x27a_cfg_uart2dm_serial(); + msm_uart_dm1_pdata.wakeup_irq = gpio_to_irq(UART1DM_RX_GPIO); + if (cpu_is_msm8625()) + msm8625_device_uart_dm1.dev.platform_data = + &msm_uart_dm1_pdata; + else + msm_device_uart_dm1.dev.platform_data = &msm_uart_dm1_pdata; +} + +static void __init msm7x27a_otg_gadget(void) +{ + if (cpu_is_msm8625()) { + msm_otg_pdata.swfi_latency = + msm8625_pm_data[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT].latency; + msm8625_device_otg.dev.platform_data = &msm_otg_pdata; + msm8625_device_gadget_peripheral.dev.platform_data = + &msm_gadget_pdata; + } else { + 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; + msm_device_gadget_peripheral.dev.platform_data = + &msm_gadget_pdata; + } +} + +static void __init msm7x27a_pm_init(void) +{ + if (machine_is_msm8625_surf() || machine_is_msm8625_ffa()) { + msm_pm_set_platform_data(msm8625_pm_data, + ARRAY_SIZE(msm8625_pm_data)); + BUG_ON(msm_pm_boot_init(&msm_pm_8625_boot_pdata)); + msm8x25_spm_device_init(); + } else { + msm_pm_set_platform_data(msm7x27a_pm_data, + ARRAY_SIZE(msm7x27a_pm_data)); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + } + + msm_pm_register_irqs(); +} + +static void __init msm7x2x_init(void) +{ + msm7x2x_misc_init(); + + /* Initialize regulators first so that other devices can use them */ + msm7x27a_init_regulators(); + msm_adsp_add_pdev(); + if (cpu_is_msm8625()) + msm8625_device_i2c_init(); + else + msm7x27a_device_i2c_init(); + msm7x27a_init_ebi2(); + msm7x27a_uartdm_config(); + + msm7x27a_otg_gadget(); + msm7x27a_cfg_smsc911x(); + + msm7x27a_add_footswitch_devices(); + msm7x27a_add_platform_devices(); + /* Ensure ar6000pm device is registered before MMC/SDC */ + msm7x27a_init_ar6000pm(); + msm7627a_init_mmc(); + msm_fb_add_devices(); + msm7x2x_init_host(); + msm7x27a_pm_init(); + register_i2c_devices(); +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + msm7627a_bt_power_init(); +#endif + msm7627a_camera_init(); + msm7627a_add_io_devices(); + /*7x25a kgsl initializations*/ + msm7x25a_kgsl_3d0_init(); + /*8x25 kgsl initializations*/ + msm8x25_kgsl_3d0_init(); +} + +static void __init msm7x2x_init_early(void) +{ + msm_msm7627a_allocate_memory_regions(); +} + +MACHINE_START(MSM7X27A_RUMI3, "QCT MSM7x27a RUMI3") + .atag_offset = 0x100, + .map_io = msm_common_io_init, + .reserve = msm7x27a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm7627a_rumi3_init, + .timer = &msm_timer, + .init_early = msm7x2x_init_early, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM7X27A_SURF, "QCT MSM7x27a SURF") + .atag_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, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM7X27A_FFA, "QCT MSM7x27a FFA") + .atag_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, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM7625A_SURF, "QCT MSM7625a SURF") + .atag_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, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM7625A_FFA, "QCT MSM7625a FFA") + .atag_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, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM8625_RUMI3, "QCT MSM8625 RUMI3") + .atag_offset = 0x100, + .map_io = msm8625_map_io, + .reserve = msm8625_reserve, + .init_irq = msm8625_init_irq, + .init_machine = msm8625_rumi3_init, + .timer = &msm_timer, + .handle_irq = gic_handle_irq, +MACHINE_END +MACHINE_START(MSM8625_SURF, "QCT MSM8625 SURF") + .atag_offset = 0x100, + .map_io = msm8625_map_io, + .reserve = msm8625_reserve, + .init_irq = msm8625_init_irq, + .init_machine = msm7x2x_init, + .timer = &msm_timer, + .init_early = msm7x2x_init_early, + .handle_irq = gic_handle_irq, +MACHINE_END +MACHINE_START(MSM8625_FFA, "QCT MSM8625 FFA") + .atag_offset = 0x100, + .map_io = msm8625_map_io, + .reserve = msm8625_reserve, + .init_irq = msm8625_init_irq, + .init_machine = msm7x2x_init, + .timer = &msm_timer, + .init_early = msm7x2x_init_early, + .handle_irq = gic_handle_irq, +MACHINE_END diff --git a/arch/arm/mach-msm/board-msm7x30-regulator.c b/arch/arm/mach-msm/board-msm7x30-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..a3f5ee12d2339dd73aeebcdc86df4d2004fb06a2 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7x30-regulator.c @@ -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. + */ + +#include "board-msm7x30-regulator.h" + +#define PCOM_VREG_CONSUMERS(name) \ + static struct regulator_consumer_supply __pcom_vreg_supply_##name[] + +#define PCOM_VREG_CONSTRAINT_LVSW(_name, _always_on, _boot_on, _supply_uV) \ +{ \ + .name = #_name, \ + .min_uV = 0, \ + .max_uV = 0, \ + .input_uV = _supply_uV, \ + .valid_modes_mask = REGULATOR_MODE_NORMAL, \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .apply_uV = 0, \ + .boot_on = _boot_on, \ + .always_on = _always_on \ +} + +#define PCOM_VREG_CONSTRAINT_DYN(_name, _min_uV, _max_uV, _always_on, \ + _boot_on, _apply_uV, _supply_uV) \ +{ \ + .name = #_name, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .valid_modes_mask = REGULATOR_MODE_NORMAL, \ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, \ + .input_uV = _supply_uV, \ + .apply_uV = _apply_uV, \ + .boot_on = _boot_on, \ + .always_on = _always_on \ +} + + +#define PCOM_VREG_INIT(_name, _supply, _constraints)\ +{ \ + .supply_regulator = _supply, \ + .consumer_supplies = __pcom_vreg_supply_##_name, \ + .num_consumer_supplies = ARRAY_SIZE(__pcom_vreg_supply_##_name), \ + .constraints = _constraints \ +} + +#define PCOM_VREG_SMP(_name, _id, _supply, _min_uV, _max_uV, _rise_time, \ + _pulldown, _always_on, _boot_on, _apply_uV, _supply_uV) \ +{ \ + .init_data = PCOM_VREG_INIT(_name, _supply, \ + PCOM_VREG_CONSTRAINT_DYN(_name, _min_uV, _max_uV, _always_on, \ + _boot_on, _apply_uV, _supply_uV)), \ + .id = _id, \ + .rise_time = _rise_time, \ + .pulldown = _pulldown, \ + .negative = 0, \ +} + +#define PCOM_VREG_LDO PCOM_VREG_SMP + +#define PCOM_VREG_LVS(_name, _id, _supply, _rise_time, _pulldown, _always_on, \ + _boot_on) \ +{ \ + .init_data = PCOM_VREG_INIT(_name, _supply, \ + PCOM_VREG_CONSTRAINT_LVSW(_name, _always_on, _boot_on, 0)), \ + .id = _id, \ + .rise_time = _rise_time, \ + .pulldown = _pulldown, \ + .negative = 0, \ +} + +#define PCOM_VREG_NCP(_name, _id, _supply, _min_uV, _max_uV, _rise_time, \ + _always_on, _boot_on, _apply_uV, _supply_uV) \ +{ \ + .init_data = PCOM_VREG_INIT(_name, _supply, \ + PCOM_VREG_CONSTRAINT_DYN(_name, -(_min_uV), -(_max_uV), \ + _always_on, _boot_on, _apply_uV, _supply_uV)), \ + .id = _id, \ + .rise_time = _rise_time, \ + .pulldown = -1, \ + .negative = 1, \ +} + +PCOM_VREG_CONSUMERS(smps0) = { + REGULATOR_SUPPLY("smps0", NULL), + REGULATOR_SUPPLY("msmc1", NULL), +}; + +PCOM_VREG_CONSUMERS(smps1) = { + REGULATOR_SUPPLY("smps1", NULL), + REGULATOR_SUPPLY("msmc2", NULL), +}; + +PCOM_VREG_CONSUMERS(smps2) = { + REGULATOR_SUPPLY("smps2", NULL), + REGULATOR_SUPPLY("s2", NULL), +}; + +PCOM_VREG_CONSUMERS(smps3) = { + REGULATOR_SUPPLY("smps3", NULL), + REGULATOR_SUPPLY("s3", NULL), +}; + +PCOM_VREG_CONSUMERS(smps4) = { + REGULATOR_SUPPLY("smps4", NULL), + REGULATOR_SUPPLY("s4", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo00) = { + REGULATOR_SUPPLY("ldo00", NULL), + REGULATOR_SUPPLY("ldo0", NULL), + REGULATOR_SUPPLY("gp3", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo02) = { + REGULATOR_SUPPLY("ldo02", NULL), + REGULATOR_SUPPLY("ldo2", NULL), + REGULATOR_SUPPLY("xo_out", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo03) = { + REGULATOR_SUPPLY("ldo03", NULL), + REGULATOR_SUPPLY("ldo3", NULL), + REGULATOR_SUPPLY("ruim", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo04) = { + REGULATOR_SUPPLY("ldo04", NULL), + REGULATOR_SUPPLY("ldo4", NULL), + REGULATOR_SUPPLY("tcxo", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo05) = { + REGULATOR_SUPPLY("ldo05", NULL), + REGULATOR_SUPPLY("ldo5", NULL), + REGULATOR_SUPPLY("mmc", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo06) = { + REGULATOR_SUPPLY("ldo06", NULL), + REGULATOR_SUPPLY("ldo6", NULL), + REGULATOR_SUPPLY("usb", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo07) = { + REGULATOR_SUPPLY("ldo07", NULL), + REGULATOR_SUPPLY("ldo7", NULL), + REGULATOR_SUPPLY("usb2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo08) = { + REGULATOR_SUPPLY("ldo08", NULL), + REGULATOR_SUPPLY("ldo8", NULL), + REGULATOR_SUPPLY("gp7", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo09) = { + REGULATOR_SUPPLY("ldo09", NULL), + REGULATOR_SUPPLY("ldo9", NULL), + REGULATOR_SUPPLY("gp1", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo10) = { + REGULATOR_SUPPLY("ldo10", NULL), + REGULATOR_SUPPLY("gp4", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo11) = { + REGULATOR_SUPPLY("ldo11", NULL), + REGULATOR_SUPPLY("gp2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo12) = { + REGULATOR_SUPPLY("ldo12", NULL), + REGULATOR_SUPPLY("gp9", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo13) = { + REGULATOR_SUPPLY("ldo13", NULL), + REGULATOR_SUPPLY("wlan", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo14) = { + REGULATOR_SUPPLY("ldo14", NULL), + REGULATOR_SUPPLY("rf", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo15) = { + REGULATOR_SUPPLY("ldo15", NULL), + REGULATOR_SUPPLY("gp6", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo16) = { + REGULATOR_SUPPLY("ldo16", NULL), + REGULATOR_SUPPLY("gp10", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo17) = { + REGULATOR_SUPPLY("ldo17", NULL), + REGULATOR_SUPPLY("gp11", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo18) = { + REGULATOR_SUPPLY("ldo18", NULL), + REGULATOR_SUPPLY("gp12", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo19) = { + REGULATOR_SUPPLY("ldo19", NULL), + REGULATOR_SUPPLY("wlan2", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo20) = { + REGULATOR_SUPPLY("ldo20", NULL), + REGULATOR_SUPPLY("gp13", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo21) = { + REGULATOR_SUPPLY("ldo21", NULL), + REGULATOR_SUPPLY("gp14", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo22) = { + REGULATOR_SUPPLY("ldo22", NULL), + REGULATOR_SUPPLY("gp15", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo23) = { + REGULATOR_SUPPLY("ldo23", NULL), + REGULATOR_SUPPLY("gp5", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo24) = { + REGULATOR_SUPPLY("ldo24", NULL), + REGULATOR_SUPPLY("gp16", NULL), +}; + +PCOM_VREG_CONSUMERS(ldo25) = { + REGULATOR_SUPPLY("ldo25", NULL), + REGULATOR_SUPPLY("gp17", NULL), +}; + +PCOM_VREG_CONSUMERS(lvsw0) = { + REGULATOR_SUPPLY("lvsw0", NULL), + REGULATOR_SUPPLY("lvs0", NULL), +}; + +PCOM_VREG_CONSUMERS(lvsw1) = { + REGULATOR_SUPPLY("lvsw1", NULL), + REGULATOR_SUPPLY("lvs1", NULL), +}; + +PCOM_VREG_CONSUMERS(ncp) = { + REGULATOR_SUPPLY("ncp", NULL), +}; + +/* This list needs to be verified against actual 7x30 hardware requirements. */ +static struct proccomm_regulator_info msm7x30_pcom_vreg_info[] = { + /* Standard regulators (SMPS and LDO) + * R = rise time (us) + * P = pulldown (1 = pull down, 0 = float, -1 = don't care) + * A = always on + * B = boot on + * V = automatic voltage set (meaningful for single-voltage regs only) + * S = supply voltage (uV) + * name id supp min uV max uV R P A B V S */ + PCOM_VREG_SMP(smps0, 3, NULL, 500000, 1500000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps1, 4, NULL, 500000, 1500000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps2, 28, NULL, 1300000, 1300000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps3, 29, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_SMP(smps4, 43, NULL, 2200000, 2200000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo00, 5, NULL, 1200000, 1200000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo02, 46, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo03, 19, NULL, 1800000, 3000000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo04, 9, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo05, 18, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo06, 16, NULL, 3075000, 3400000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo07, 44, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo08, 32, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo09, 8, NULL, 2050000, 2050000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo10, 7, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo11, 21, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo12, 34, NULL, 1800000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo13, 15, NULL, 2900000, 3050000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo14, 24, NULL, 2850000, 2850000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo15, 23, NULL, 3050000, 3100000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo16, 35, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo17, 36, NULL, 2600000, 2600000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo18, 37, NULL, 2200000, 2200000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo19, 45, NULL, 2400000, 2500000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo20, 38, NULL, 1500000, 1800000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo21, 39, NULL, 1100000, 1100000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo22, 40, NULL, 1200000, 1300000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo23, 22, NULL, 1350000, 1350000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo24, 41, NULL, 1200000, 1200000, 0, -1, 0, 0, 0, 0), + PCOM_VREG_LDO(ldo25, 42, NULL, 1200000, 1200000, 0, -1, 0, 0, 0, 0), + + /* Low-voltage switches */ + PCOM_VREG_LVS(lvsw0, 47, NULL, 0, -1, 0, 0), + PCOM_VREG_LVS(lvsw1, 48, NULL, 0, -1, 0, 0), + + PCOM_VREG_NCP(ncp, 31, NULL, -1800000, -1800000, 0, 0, 0, 0, 0), +}; + +struct proccomm_regulator_platform_data msm7x30_proccomm_regulator_data = { + .regs = msm7x30_pcom_vreg_info, + .nregs = ARRAY_SIZE(msm7x30_pcom_vreg_info) +}; diff --git a/arch/arm/mach-msm/board-msm7x30-regulator.h b/arch/arm/mach-msm/board-msm7x30-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..bd9b02da98cc3f13a592ded638fb2791cce4a080 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7x30-regulator.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 __ARCH_ARM_MACH_MSM_BOARD_7X30_REGULATOR_H__ +#define __ARCH_ARM_MACH_MSM_BOARD_7X30_REGULATOR_H__ + +#include "proccomm-regulator.h" + +extern struct proccomm_regulator_platform_data msm7x30_proccomm_regulator_data; + +#endif diff --git a/arch/arm/mach-msm/board-msm7x30.c b/arch/arm/mach-msm/board-msm7x30.c index db81ed531031354b7547c3e48996af1845202c94..8abcef0a6c88227253b6740cef73d410477c3b9f 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-2012, Code Aurora Forum. All rights reserved. * * This 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,143 +9,7328 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 +#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 + +#include "devices.h" +#include "timer.h" +#ifdef CONFIG_USB_G_ANDROID +#include +#include +#endif +#include "pm.h" +#include "pm-boot.h" +#include "spm.h" +#include "acpuclock.h" +#include +#include +#include +#include +#include +#include "smd_private.h" +#include + +#include "board-msm7x30-regulator.h" +#include "pm.h" + +#define MSM_PMEM_SF_SIZE 0x1700000 +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_SIZE 0x780000 +#else +#define MSM_FB_SIZE 0x500000 +#endif +/* + * Reserve space for double buffered full screen + * res V4L2 video overlay - i.e. 1280x720x1.5x2 + */ +#define MSM_V4L2_VIDEO_OVERLAY_BUF_SIZE 2764800 +#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 +#define PMIC_GPIO_SDC4_PWR_EN_N 24 /* PMIC GPIO Number 25 */ + +/* 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 PM8058_MPP_BASE PM8058_GPIO_PM_TO_SYS(PM8058_GPIOS) +#define PM8058_MPP_PM_TO_SYS(pm_gpio) (pm_gpio + PM8058_MPP_BASE) + +#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 */ + +#define DDR1_BANK_BASE 0X20000000 +#define DDR2_BANK_BASE 0X40000000 + +static unsigned int phys_add = DDR2_BANK_BASE; +unsigned long ebi1_phys_offset = DDR2_BANK_BASE; +EXPORT_SYMBOL(ebi1_phys_offset); + +struct pm8xxx_gpio_init_info { + unsigned gpio; + struct pm_gpio config; +}; + +static int pm8058_gpios_init(void) +{ + int rc; + + struct pm8xxx_gpio_init_info sdc4_en = { + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC4_EN_N), + { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_L5, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .out_strength = PM_GPIO_STRENGTH_LOW, + .output_value = 0, + }, + }; + + struct pm8xxx_gpio_init_info sdc4_pwr_en = { + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC4_PWR_EN_N), + { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_L5, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .out_strength = PM_GPIO_STRENGTH_LOW, + .output_value = 0, + }, + }; + + struct pm8xxx_gpio_init_info haptics_enable = { + PM8058_GPIO_PM_TO_SYS(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, + }, + }; + + struct pm8xxx_gpio_init_info hdmi_5V_en = { + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_HDMI_5V_EN_V3), + { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_VPH, + .function = PM_GPIO_FUNC_NORMAL, + .out_strength = PM_GPIO_STRENGTH_LOW, + .output_value = 0, + }, + }; + + struct pm8xxx_gpio_init_info flash_boost_enable = { + PM8058_GPIO_PM_TO_SYS(PMIC_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 = PM8058_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, + }, + }; + + struct pm8xxx_gpio_init_info gpio23 = { + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_WLAN_EXT_POR), + { + .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, + } + }; + +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + struct pm8xxx_gpio_init_info sdcc_det = { + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SD_DET - 1), + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }; + + if (machine_is_msm7x30_fluid()) + sdcc_det.config.inv_int_pol = 1; + + rc = pm8xxx_gpio_config(sdcc_det.gpio, &sdcc_det.config); + if (rc) { + pr_err("%s PMIC_GPIO_SD_DET config failed\n", __func__); + return rc; + } +#endif + + if (machine_is_msm8x55_svlte_surf() || machine_is_msm8x55_svlte_ffa() || + machine_is_msm7x30_fluid()) + hdmi_5V_en.gpio = PMIC_GPIO_HDMI_5V_EN_V2; + else + hdmi_5V_en.gpio = PMIC_GPIO_HDMI_5V_EN_V3; + + hdmi_5V_en.gpio = PM8058_GPIO_PM_TO_SYS(hdmi_5V_en.gpio); + + rc = pm8xxx_gpio_config(hdmi_5V_en.gpio, &hdmi_5V_en.config); + 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 = pm8xxx_gpio_config(gpio23.gpio, &gpio23.config); + if (rc) { + pr_err("%s PMIC_GPIO_WLAN_EXT_POR config failed\n", __func__); + return rc; + } + + if (machine_is_msm7x30_fluid()) { + /* Haptics gpio */ + rc = pm8xxx_gpio_config(haptics_enable.gpio, + &haptics_enable.config); + if (rc) { + pr_err("%s: PMIC GPIO %d write failed\n", __func__, + haptics_enable.gpio); + return rc; + } + /* Flash boost gpio */ + rc = pm8xxx_gpio_config(flash_boost_enable.gpio, + &flash_boost_enable.config); + if (rc) { + pr_err("%s: PMIC GPIO %d write failed\n", __func__, + flash_boost_enable.gpio); + return rc; + } + /* SCD4 gpio */ + rc = pm8xxx_gpio_config(sdc4_en.gpio, &sdc4_en.config); + if (rc) { + pr_err("%s PMIC_GPIO_SDC4_EN_N config failed\n", + __func__); + return rc; + } + rc = gpio_request(sdc4_en.gpio, "sdc4_en"); + if (rc) { + pr_err("%s PMIC_GPIO_SDC4_EN_N gpio_request failed\n", + __func__); + return rc; + } + gpio_set_value_cansleep(sdc4_en.gpio, 0); + } + /* FFA -> gpio_25 controls vdd of sdcc4 */ + else { + /* SCD4 gpio_25 */ + rc = pm8xxx_gpio_config(sdc4_pwr_en.gpio, &sdc4_pwr_en.config); + if (rc) { + pr_err("%s PMIC_GPIO_SDC4_PWR_EN_N config failed: %d\n", + __func__, rc); + return rc; + } + + rc = gpio_request(sdc4_pwr_en.gpio, "sdc4_pwr_en"); + if (rc) { + pr_err("PMIC_GPIO_SDC4_PWR_EN_N gpio_req failed: %d\n", + rc); + return rc; + } + } + + return 0; +} + +/* Regulator API support */ + +#ifdef CONFIG_MSM_PROC_COMM_REGULATOR +static struct platform_device msm_proccomm_regulator_dev = { + .name = PROCCOMM_REGULATOR_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &msm7x30_proccomm_regulator_data + } +}; +#endif + +/*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; +static struct regulator_bulk_data cyttsp_regs[] = { + { .supply = "ldo8", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "ldo15", .min_uV = 3050000, .max_uV = 3100000 }, +}; + +#define CYTTSP_TS_GPIO_IRQ 150 +static int cyttsp_platform_init(struct i2c_client *client) +{ + int rc = -EINVAL; + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(cyttsp_regs), cyttsp_regs); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(cyttsp_regs), cyttsp_regs); + + if (rc) { + pr_err("%s: could not set regulator voltages: %d\n", __func__, + rc); + goto regs_free; + } + + rc = regulator_bulk_enable(ARRAY_SIZE(cyttsp_regs), cyttsp_regs); + + if (rc) { + pr_err("%s: could not enable regulators: %d\n", __func__, rc); + goto regs_free; + } + + /* 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 regs_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 regs_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; + +regs_disable: + regulator_bulk_disable(ARRAY_SIZE(cyttsp_regs), cyttsp_regs); +regs_free: + regulator_bulk_free(ARRAY_SIZE(cyttsp_regs), cyttsp_regs); +out: + return rc; +} + +/* TODO: Put the regulator to LPM / HPM in suspend/resume*/ +static int cyttsp_platform_suspend(struct i2c_client *client) +{ + msleep(20); + + return CY_OK; +} + +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_DEEP_SLEEP_SEL | CY_USE_LOW_POWER_SEL, + .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, + .suspend = cyttsp_platform_suspend, + .init = cyttsp_platform_init, + .sleep_gpio = -1, + .resout_gpio = -1, + .irq_gpio = CYTTSP_TS_GPIO_IRQ, + .correct_fw_ver = 2, +}; + +static int pm8058_pwm_config(struct pwm_device *pwm, int ch, int on) +{ + struct pm_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 = PM8058_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 = pm8xxx_gpio_config(PM8058_GPIO_PM_TO_SYS(id - 1), + &pwm_gpio_config); + if (rc) + pr_err("%s: pm8xxx_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 matrix_keymap_data surf_keymap_data = { + .keymap_size = ARRAY_SIZE(surf_keymap), + .keymap = surf_keymap, +}; + +static struct pm8xxx_keypad_platform_data surf_keypad_data = { + .input_name = "surf_keypad", + .input_phys_device = "surf_keypad/input0", + .num_rows = 12, + .num_cols = 8, + .rows_gpio_start = PM8058_GPIO_PM_TO_SYS(8), + .cols_gpio_start = PM8058_GPIO_PM_TO_SYS(0), + .debounce_ms = 15, + .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 pm8xxx_keypad_platform_data fluid_keypad_data = { + .input_name = "fluid-keypad", + .input_phys_device = "fluid-keypad/input0", + .num_rows = 5, + .num_cols = 5, + .rows_gpio_start = PM8058_GPIO_PM_TO_SYS(8), + .cols_gpio_start = PM8058_GPIO_PM_TO_SYS(0), + .debounce_ms = 15, + .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, +}; + +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 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 pm8xxx_irq_platform_data pm8xxx_irq_pdata = { + .irq_base = PMIC8058_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(PMIC_GPIO_INT), + .irq_trigger_flag = IRQF_TRIGGER_LOW, +}; + +static struct pm8xxx_gpio_platform_data pm8xxx_gpio_pdata = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(0), +}; + +static struct pm8xxx_mpp_platform_data pm8xxx_mpp_pdata = { + .mpp_base = PM8058_MPP_PM_TO_SYS(0), +}; + +static struct pm8058_platform_data pm8058_7x30_data = { + .irq_pdata = &pm8xxx_irq_pdata, + .gpio_pdata = &pm8xxx_gpio_pdata, + .mpp_pdata = &pm8xxx_mpp_pdata, + .pwm_pdata = &pm8058_pwm_data, +}; + +#ifdef CONFIG_MSM_SSBI +static struct msm_ssbi_platform_data msm7x30_ssbi_pm8058_pdata = { + .rsl_id = "D:PMIC_SSBI", + .controller_type = MSM_SBI_CTRL_SSBI2, + .slave = { + .name = "pm8058-core", + .platform_data = &pm8058_7x30_data, + }, +}; +#endif + +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 */ + }, +}; + +#ifdef CONFIG_MSM_CAMERA_V4L2 +static struct msm_camera_device_platform_data msm_camera_csi_device_data[] = { + { + .csid_core = 0, + .is_csic = 1, + .is_vpe = 1, + .ioclk = { + .vfe_clk_rate = 153600000, + }, + }, + { + .csid_core = 0, + .is_vpe = 1, + .ioclk = { + .vfe_clk_rate = 153600000, + }, + }, +}; + +static struct camera_vreg_t msm_7x30_back_cam_vreg[] = { + {"gp2", REG_LDO, 2600000, 2600000, -1}, + {"lvsw1", REG_VS, 0, 0, 0}, +}; + +static uint32_t camera_off_gpio_table[] = { + /* parallel CAMERA interfaces */ + /* RST */ + GPIO_CFG(0, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT2 */ + GPIO_CFG(2, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT3 */ + GPIO_CFG(3, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT4 */ + GPIO_CFG(4, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT5 */ + GPIO_CFG(5, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT6 */ + GPIO_CFG(6, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT7 */ + GPIO_CFG(7, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT8 */ + GPIO_CFG(8, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT9 */ + GPIO_CFG(9, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT10 */ + GPIO_CFG(10, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT11 */ + GPIO_CFG(11, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* PCLK */ + GPIO_CFG(12, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* HSYNC_IN */ + GPIO_CFG(13, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* VSYNC_IN */ + GPIO_CFG(14, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* MCLK */ + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; + +static uint32_t camera_on_gpio_table[] = { + /* parallel CAMERA interfaces */ + /* RST */ + GPIO_CFG(0, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT2 */ + GPIO_CFG(2, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT3 */ + GPIO_CFG(3, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT4 */ + GPIO_CFG(4, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT5 */ + GPIO_CFG(5, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT6 */ + GPIO_CFG(6, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT7 */ + GPIO_CFG(7, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT8 */ + GPIO_CFG(8, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT9 */ + GPIO_CFG(9, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT10 */ + GPIO_CFG(10, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT11 */ + GPIO_CFG(11, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* PCLK */ + GPIO_CFG(12, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* HSYNC_IN */ + GPIO_CFG(13, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* VSYNC_IN */ + GPIO_CFG(14, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* MCLK */ + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; + +static struct gpio msm7x30_back_cam_gpio[] = { + {0, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct msm_gpio_set_tbl msm7x30_back_cam_gpio_set_tbl[] = { + {0, GPIOF_OUT_INIT_LOW, 1000}, + {0, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_camera_gpio_conf msm_7x30_back_cam_gpio_conf = { + .cam_gpio_req_tbl = msm7x30_back_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(msm7x30_back_cam_gpio), + .cam_gpio_set_tbl = msm7x30_back_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(msm7x30_back_cam_gpio_set_tbl), + .camera_off_table = camera_off_gpio_table, + .camera_off_table_size = ARRAY_SIZE(camera_off_gpio_table), + .camera_on_table = camera_on_gpio_table, + .camera_on_table_size = ARRAY_SIZE(camera_on_gpio_table), + .gpio_no_mux = 1, +}; + +static struct msm_camera_sensor_flash_data flash_vx6953 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_vx6953 = { + .mount_angle = 0, + .cam_vreg = msm_7x30_back_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_7x30_back_cam_vreg), + .gpio_conf = &msm_7x30_back_cam_gpio_conf, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_vx6953_data = { + .sensor_name = "vx6953", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_vx6953, + .sensor_platform_info = &sensor_board_info_vx6953, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, +}; + +static struct platform_device msm_camera_server = { + .name = "msm_cam_server", + .id = 0, +}; + +void __init msm7x30_init_cam(void) +{ + platform_device_register(&msm_camera_server); + platform_device_register(&msm_device_csic0); + platform_device_register(&msm_device_vfe); + platform_device_register(&msm_device_vpe); +} + +#ifdef CONFIG_I2C +static struct i2c_board_info msm_camera_boardinfo[] = { + { + I2C_BOARD_INFO("vx6953", 0x20), + .platform_data = &msm_camera_sensor_vx6953_data, + }, +}; +#endif +#else +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 */ + /* RST */ + GPIO_CFG(0, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT2 */ + GPIO_CFG(2, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT3 */ + GPIO_CFG(3, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT4 */ + GPIO_CFG(4, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT5 */ + GPIO_CFG(5, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT6 */ + GPIO_CFG(6, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT7 */ + GPIO_CFG(7, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT8 */ + GPIO_CFG(8, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT9 */ + GPIO_CFG(9, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT10 */ + GPIO_CFG(10, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT11 */ + GPIO_CFG(11, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* PCLK */ + GPIO_CFG(12, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* HSYNC_IN */ + GPIO_CFG(13, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* VSYNC_IN */ + GPIO_CFG(14, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* MCLK */ + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; + +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 */ + /* RST */ + GPIO_CFG(0, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT2 */ + GPIO_CFG(2, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT3 */ + GPIO_CFG(3, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT4 */ + GPIO_CFG(4, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT5 */ + GPIO_CFG(5, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT6 */ + GPIO_CFG(6, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT7 */ + GPIO_CFG(7, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT8 */ + GPIO_CFG(8, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT9 */ + GPIO_CFG(9, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT10 */ + GPIO_CFG(10, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* DAT11 */ + GPIO_CFG(11, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* PCLK */ + GPIO_CFG(12, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* HSYNC_IN */ + GPIO_CFG(13, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* VSYNC_IN */ + GPIO_CFG(14, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* MCLK */ + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; + +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_platform_info vx6953_sensor_7630_info = { + .mount_angle = 0 +}; + +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), + .sensor_platform_info = &vx6953_sensor_7630_info, + .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_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*/ +#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_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 regulator_bulk_data snddev_regs[] = { + { .supply = "gp4", .min_uV = 2600000, .max_uV = 2600000 }, + { .supply = "ncp", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +static int __init snddev_hsed_voltage_init(void) +{ + int rc; + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(snddev_regs), snddev_regs); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(snddev_regs), snddev_regs); + + if (rc) { + pr_err("%s: could not set regulator voltages: %d\n", + __func__, rc); + goto regs_free; + } + + return 0; + +regs_free: + regulator_bulk_free(ARRAY_SIZE(snddev_regs), snddev_regs); +out: + return rc; +} + + +void msm_snddev_hsed_voltage_on(void) +{ + int rc = regulator_bulk_enable(ARRAY_SIZE(snddev_regs), snddev_regs); + + if (rc) + pr_err("%s: could not enable regulators: %d\n", __func__, rc); +} + +void msm_snddev_hsed_voltage_off(void) +{ + int rc = regulator_bulk_disable(ARRAY_SIZE(snddev_regs), snddev_regs); + + if (rc) { + pr_err("%s: could not disable regulators: %d\n", __func__, rc); + } +} + +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_msm8x60_fluid()) + pm8058_7x30_data.keypad_pdata = &fluid_keypad_data; + else + pm8058_7x30_data.keypad_pdata = &surf_keypad_data; + + 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 regulator *vreg_marimba_1; +static struct regulator *vreg_marimba_2; +static struct regulator *vreg_bahama; + +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 = regulator_enable(vreg_marimba_1); + if (rc) { + pr_err("%s: regulator_enable failed (%d)\n", __func__, rc); + goto out; + } + + rc = regulator_enable(vreg_marimba_2); + if (rc) { + pr_err("%s: regulator_enable failed (%d)\n", __func__, rc); + goto disable_marimba_1; + } + + rc = gpio_direction_output(TIMPANI_RESET_GPIO, 1); + if (rc < 0) { + pr_err("%s: gpio_direction_output failed (%d)\n", + __func__, rc); + msm_gpios_free(timpani_reset_gpio_cfg, + ARRAY_SIZE(timpani_reset_gpio_cfg)); + goto disable_marimba_2; + } + + return 0; + +disable_marimba_2: + regulator_disable(vreg_marimba_2); +disable_marimba_1: + regulator_disable(vreg_marimba_1); +out: + return rc; +}; + +static void msm_timpani_shutdown_power(void) +{ + int rc; + + rc = regulator_disable(vreg_marimba_2); + if (rc) + pr_err("%s: regulator_disable failed (%d)\n", __func__, rc); + + rc = regulator_disable(vreg_marimba_1); + if (rc) + pr_err("%s: regulator_disable failed (%d)\n", __func__, rc); + + rc = gpio_direction_output(TIMPANI_RESET_GPIO, 0); + if (rc < 0) + pr_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 = regulator_enable(vreg_bahama); + + if (rc) + pr_err("%s: regulator_enable failed (%d)\n", __func__, rc); + + return rc; +}; + +static unsigned int msm_bahama_shutdown_power(int value) +{ + int rc = 0; + + if (value != BAHAMA_ID) { + rc = regulator_disable(vreg_bahama); + + if (rc) + pr_err("%s: regulator_disable failed (%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 = regulator_enable(vreg_marimba_1); + if (rc) { + pr_err("%s: regulator_enable failed (%d)\n", __func__, rc); + goto out; + } + + rc = regulator_enable(vreg_marimba_2); + if (rc) { + pr_err("%s: regulator_enable failed (%d)\n", __func__, rc); + goto disable_marimba_1; + } + + 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) { + pr_err("%s: msm_gpios_request_enable failed (%d)\n", + __func__, rc); + goto disable_marimba_2; + } + + rc = gpio_direction_output(GPIO_PIN + (marimba_svlte_config_clock->gpio_cfg), 0); + if (rc < 0) { + pr_err("%s: gpio_direction_output failed (%d)\n", + __func__, rc); + goto disable_marimba_2; + } + } + + return 0; + +disable_marimba_2: + regulator_disable(vreg_marimba_2); +disable_marimba_1: + regulator_disable(vreg_marimba_1); +out: + return rc; +}; + +static void msm_marimba_shutdown_power(void) +{ + int rc; + + rc = regulator_disable(vreg_marimba_2); + if (rc) + pr_err("%s: regulator_disable failed (%d)\n", __func__, rc); + + rc = regulator_disable(vreg_marimba_1); + if (rc) + pr_err("%s: regulator_disable failed (%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 regulator *fm_regulator; +static int fm_radio_setup(struct marimba_fm_platform_data *pdata) +{ + int rc, voltage; + uint32_t irqcfg; + const char *id = "FMPW"; + + int bahama_not_marimba = bahama_present(); + + if (bahama_not_marimba < 0) { + pr_warn("%s: bahama_present: %d\n", + __func__, bahama_not_marimba); + rc = -ENODEV; + goto out; + } + if (bahama_not_marimba) { + fm_regulator = regulator_get(NULL, "s3"); + voltage = 1800000; + } else { + fm_regulator = regulator_get(NULL, "s2"); + voltage = 1300000; + } + + if (IS_ERR(fm_regulator)) { + rc = PTR_ERR(fm_regulator); + pr_err("%s: regulator_get failed (%d)\n", __func__, rc); + goto out; + } + + rc = regulator_set_voltage(fm_regulator, voltage, voltage); + + if (rc) { + pr_err("%s: regulator_set_voltage failed (%d)\n", __func__, rc); + goto regulator_free; + } + + rc = regulator_enable(fm_regulator); + + if (rc) { + pr_err("%s: regulator_enable failed (%d)\n", __func__, rc); + goto regulator_free; + } + + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, PMAPP_CLOCK_VOTE_ON); + + if (rc < 0) { + pr_err("%s: clock vote failed (%d)\n", __func__, rc); + goto regulator_disable; + } + + /*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) { + pr_err("%s: clock enable for svlte : %d\n", + __func__, rc); + goto clock_devote; + } + } + 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) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", __func__, irqcfg, rc); + rc = -EIO; + goto gpio_deconfig; + + } + return 0; + +gpio_deconfig: + if (machine_is_msm8x55_svlte_surf() || machine_is_msm8x55_svlte_ffa()) + marimba_gpio_config(0); +clock_devote: + pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, PMAPP_CLOCK_VOTE_OFF); +regulator_disable: + regulator_disable(fm_regulator); +regulator_free: + regulator_put(fm_regulator); + fm_regulator = NULL; +out: + 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) { + pr_warn("%s: bahama_present: %d\n", + __func__, bahama_not_marimba); + return; + } + + rc = gpio_tlmm_config(irqcfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", __func__, irqcfg, rc); + } + if (!IS_ERR_OR_NULL(fm_regulator)) { + rc = regulator_disable(fm_regulator); + + if (rc) + pr_err("%s: return val: %d\n", __func__, rc); + + regulator_put(fm_regulator); + fm_regulator = NULL; + } + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_OFF); + if (rc < 0) + pr_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) + pr_err("%s: clock disable for svlte : %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 struct regulator_bulk_data regs_tsadc_marimba[] = { + { .supply = "gp12", .min_uV = 2200000, .max_uV = 2200000 }, + { .supply = "s2", .min_uV = 1300000, .max_uV = 1300000 }, +}; + +static struct regulator_bulk_data regs_tsadc_timpani[] = { + { .supply = "s3", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "gp12", .min_uV = 2200000, .max_uV = 2200000 }, + { .supply = "gp16", .min_uV = 1200000, .max_uV = 1200000 }, +}; + +static struct regulator_bulk_data *regs_tsadc; +static int regs_tsadc_count; + +static int marimba_tsadc_power(int vreg_on) +{ + int rc = 0; + int tsadc_adie_type = adie_get_detected_codec_type(); + + switch (tsadc_adie_type) { + case TIMPANI_ID: + 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 D1_vote_fail; + } + + /* fall through */ + case MARIMBA_ID: + 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 D0_vote_fail; + } + + WARN_ON(regs_tsadc_count == 0); + + rc = vreg_on ? + regulator_bulk_enable(regs_tsadc_count, regs_tsadc) : + regulator_bulk_disable(regs_tsadc_count, regs_tsadc); + + if (rc) { + pr_err("%s: regulator %sable failed: %d\n", + __func__, vreg_on ? "en" : "dis", rc); + goto regulator_switch_fail; + } + + break; + default: + pr_err("%s:Adie %d not supported\n", + __func__, tsadc_adie_type); + return -ENODEV; + } + + msleep(5); /* ensure power is stable */ + + return 0; + +regulator_switch_fail: + pmapp_clock_vote(tsadc_id, PMAPP_CLOCK_ID_DO, + vreg_on ? PMAPP_CLOCK_VOTE_OFF : PMAPP_CLOCK_VOTE_ON); +D0_vote_fail: + if (tsadc_adie_type == TIMPANI_ID) + pmapp_clock_vote(tsadc_id, PMAPP_CLOCK_ID_D1, + vreg_on ? PMAPP_CLOCK_VOTE_OFF : PMAPP_CLOCK_VOTE_ON); +D1_vote_fail: + return rc; +} + +static int marimba_tsadc_init(void) +{ + int rc = 0; + int tsadc_adie_type = adie_get_detected_codec_type(); + + switch (tsadc_adie_type) { + case MARIMBA_ID: + regs_tsadc = regs_tsadc_marimba; + regs_tsadc_count = ARRAY_SIZE(regs_tsadc_marimba); + break; + case TIMPANI_ID: + regs_tsadc = regs_tsadc_timpani; + regs_tsadc_count = ARRAY_SIZE(regs_tsadc_timpani); + break; + default: + pr_err("%s:Adie %d not supported\n", + __func__, tsadc_adie_type); + rc = -ENODEV; + goto out; + } + + rc = regulator_bulk_get(NULL, regs_tsadc_count, regs_tsadc); + if (rc) { + pr_err("%s: could not get regulators: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(regs_tsadc_count, regs_tsadc); + if (rc) { + pr_err("%s: could not set regulator voltages: %d\n", + __func__, rc); + goto vreg_free; + } + + return 0; + +vreg_free: + regulator_bulk_free(regs_tsadc_count, regs_tsadc); +out: + regs_tsadc = NULL; + regs_tsadc_count = 0; + return rc; +} + +static int marimba_tsadc_exit(void) +{ + regulator_bulk_free(regs_tsadc_count, regs_tsadc); + regs_tsadc_count = 0; + regs_tsadc = NULL; + + return 0; +} + + +static struct msm_ts_platform_data msm_ts_data = { + .min_x = 284, + .max_x = 3801, + .min_y = 155, + .max_y = 3929, + .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, + .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 regulator_bulk_data codec_regs[] = { + { .supply = "s4", .min_uV = 2200000, .max_uV = 2200000 }, +}; + +static int __init msm_marimba_codec_init(void) +{ + int rc = regulator_bulk_get(NULL, ARRAY_SIZE(codec_regs), codec_regs); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(codec_regs), codec_regs); + if (rc) { + pr_err("%s: could not set regulator voltages: %d\n", + __func__, rc); + goto reg_free; + } + + return rc; + +reg_free: + regulator_bulk_free(ARRAY_SIZE(codec_regs), codec_regs); +out: + return rc; +} + +static int msm_marimba_codec_power(int vreg_on) +{ + int rc = vreg_on ? + regulator_bulk_enable(ARRAY_SIZE(codec_regs), codec_regs) : + regulator_bulk_disable(ARRAY_SIZE(codec_regs), codec_regs); + + if (rc) { + pr_err("%s: could not %sable regulators: %d", + __func__, vreg_on ? "en" : "dis", rc); + return rc; + } + + return 0; +} + +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; + + struct regulator_bulk_data regs[] = { + { .supply = "s3", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "gp16", .min_uV = 1200000, .max_uV = 1200000 }, + { .supply = "usb2", .min_uV = 1800000, .max_uV = 1800000 }, + }; + + rc = msm_marimba_codec_init(); + + if (rc) { + pr_err("%s: msm_marimba_codec_init failed (%d)\n", + __func__, rc); + return; + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs), regs); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + return; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs), regs); + + if (rc) { + pr_err("%s: could not set voltages: %d\n", __func__, rc); + regulator_bulk_free(ARRAY_SIZE(regs), regs); + return; + } + + vreg_marimba_1 = regs[0].consumer; + vreg_marimba_2 = regs[1].consumer; + vreg_bahama = regs[2].consumer; +} + +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 pm8xxx_gpio_init_info usb_vbus = { + PM8058_GPIO_PM_TO_SYS(36), + { + .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 = pm8xxx_gpio_config(usb_vbus.gpio, &usb_vbus.config); + 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 regulator *vreg_3p3; +static int msm_hsusb_ldo_init(int init) +{ + uint32_t version = 0; + int def_vol = 3400000; + + version = socinfo_get_version(); + + if (SOCINFO_VERSION_MAJOR(version) >= 2 && + SOCINFO_VERSION_MINOR(version) >= 1) { + def_vol = 3075000; + pr_debug("%s: default voltage:%d\n", __func__, def_vol); + } + + if (init) { + vreg_3p3 = regulator_get(NULL, "usb"); + if (IS_ERR(vreg_3p3)) + return PTR_ERR(vreg_3p3); + regulator_set_voltage(vreg_3p3, def_vol, def_vol); + } else + regulator_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 regulator_enable(vreg_3p3); + else + return regulator_disable(vreg_3p3); +} + +static int msm_hsusb_ldo_set_voltage(int mV) +{ + static int cur_voltage; + + 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 regulator_set_voltage(vreg_3p3, mV*1000, mV*1000); +} +#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 + .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 struct regulator_bulk_data hdmi_core_regs[] = { + { .supply = "ldo8", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +static struct regulator_bulk_data hdmi_comm_regs[] = { + { .supply = "ldo8", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "ldo10", .min_uV = 2600000, .max_uV = 2600000 }, +}; + +static struct regulator_bulk_data hdmi_cec_regs[] = { + { .supply = "ldo17", .min_uV = 2600000, .max_uV = 2600000 }, +}; + +static int __init hdmi_init_regs(void) +{ + int rc; + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(hdmi_core_regs), + hdmi_core_regs); + + if (rc) { + pr_err("%s: could not get %s regulators: %d\n", + __func__, "core", rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(hdmi_core_regs), + hdmi_core_regs); + + if (rc) { + pr_err("%s: could not set %s voltages: %d\n", + __func__, "core", rc); + goto free_core; + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(hdmi_comm_regs), + hdmi_comm_regs); + + if (rc) { + pr_err("%s: could not get %s regulators: %d\n", + __func__, "comm", rc); + goto free_core; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(hdmi_comm_regs), + hdmi_comm_regs); + + if (rc) { + pr_err("%s: could not set %s voltages: %d\n", + __func__, "comm", rc); + goto free_comm; + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(hdmi_cec_regs), + hdmi_cec_regs); + + if (rc) { + pr_err("%s: could not get %s regulators: %d\n", + __func__, "cec", rc); + goto free_comm; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(hdmi_cec_regs), + hdmi_cec_regs); + + if (rc) { + pr_err("%s: could not set %s voltages: %d\n", + __func__, "cec", rc); + goto free_cec; + } + + return 0; + +free_cec: + regulator_bulk_free(ARRAY_SIZE(hdmi_cec_regs), hdmi_cec_regs); +free_comm: + regulator_bulk_free(ARRAY_SIZE(hdmi_comm_regs), hdmi_comm_regs); +free_core: + regulator_bulk_free(ARRAY_SIZE(hdmi_core_regs), hdmi_core_regs); +out: + 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_comm_power(int on, int show) +{ + if (show) + pr_info("%s: i2c comm: %d \n", __func__, on); + return on ? + regulator_bulk_enable(ARRAY_SIZE(hdmi_comm_regs), + hdmi_comm_regs) : + regulator_bulk_disable(ARRAY_SIZE(hdmi_comm_regs), + hdmi_comm_regs); +} + +static int hdmi_core_power(int on, int show) +{ + if (show) + pr_info("%s: %d \n", __func__, on); + return on ? + regulator_bulk_enable(ARRAY_SIZE(hdmi_core_regs), + hdmi_core_regs) : + regulator_bulk_disable(ARRAY_SIZE(hdmi_core_regs), + hdmi_core_regs); +} + +static int hdmi_cec_power(int on) +{ + pr_info("%s: %d \n", __func__, on); + return on ? regulator_bulk_enable(ARRAY_SIZE(hdmi_cec_regs), + hdmi_cec_regs) : + regulator_bulk_disable(ARRAY_SIZE(hdmi_cec_regs), + hdmi_cec_regs); +} + +#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 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 */ + rc = hdmi_core_power(1, 0); + if (rc) { + pr_err("%s: could not enable hdmi core regs: %d", + __func__, rc); + goto adapter_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: + hdmi_core_power(0, 0); +adapter_put: + i2c_put_adapter(adapter); + return rc; +} +fs_initcall_sync(fluid_i2c_address_fixup); +#endif + +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, + } +}; + +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE +static struct resource msm_v4l2_video_overlay_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; +#endif + +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, + } +}; + +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE + +static struct platform_device msm_v4l2_video_overlay_device = { + .name = "msm_v4l2_overlay_pd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_v4l2_video_overlay_resources), + .resource = msm_v4l2_video_overlay_resources, +}; +#endif + +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, + /* Bus Scaling declaration*/ + .bus_scale_table = NULL, +}; + +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, + /* Bus Scaling declaration*/ + .bus_scale_table = NULL, +}; +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 pm8xxx_gpio_init_info pmic_quickvx_clk_gpio = { + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_QUICKVX_CLK), + { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, + }, +}; + +static struct regulator *mddi_ldo20; +static struct regulator *mddi_ldo12; +static struct regulator *mddi_ldo16; +static struct regulator *mddi_ldo6; +static struct regulator *mddi_lcd; + +static int display_common_init(void) +{ + struct regulator_bulk_data regs[5] = { + { .supply = "ldo20", /* voltage set in display_common_power */}, + { .supply = "ldo12", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "ldo6", .min_uV = 3075000, .max_uV = 3400000 }, + { .supply = "ldo16", .min_uV = 2600000, .max_uV = 2600000 }, + { .supply = NULL, /* mddi_lcd, initialized below */ }, + }; + + int rc = 0; + + if (machine_is_msm7x30_fluid()) { + /* lcd: LDO8 @1.8V */ + regs[4].supply = "ldo8"; + regs[4].min_uV = 1800000; + regs[4].max_uV = 1800000; + } else { + /* lcd: LDO15 @3.1V */ + regs[4].supply = "ldo15"; + regs[4].min_uV = 3100000; + regs[4].max_uV = 3100000; + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs), regs); + if (rc) { + pr_err("%s: regulator_bulk_get failed: %d\n", + __func__, rc); + goto bail; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs), regs); + if (rc) { + pr_err("%s: regulator_bulk_set_voltage failed: %d\n", + __func__, rc); + goto put_regs; + } + + mddi_ldo20 = regs[0].consumer; + mddi_ldo12 = regs[1].consumer; + mddi_ldo6 = regs[2].consumer; + mddi_ldo16 = regs[3].consumer; + mddi_lcd = regs[4].consumer; + + return rc; + +put_regs: + regulator_bulk_free(ARRAY_SIZE(regs), regs); +bail: + return rc; +} + +static int display_common_power(int on) +{ + int rc = 0, flag_on = !!on; + static int display_common_power_save_on; + static bool display_regs_initialized; + + if (display_common_power_save_on == flag_on) + return 0; + + display_common_power_save_on = flag_on; + + if (unlikely(!display_regs_initialized)) { + rc = display_common_init(); + if (rc) { + pr_err("%s: regulator init failed: %d\n", + __func__, rc); + return rc; + } + display_regs_initialized = true; + } + + + 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 = pm8xxx_gpio_config(pmic_quickvx_clk_gpio.gpio, + &pmic_quickvx_clk_gpio.config); + if (rc) { + pr_err("%s: pm8xxx_gpio_config(%#x)=%d\n", + __func__, pmic_quickvx_clk_gpio.gpio, + rc); + return rc; + } + + gpio_set_value_cansleep(PM8058_GPIO_PM_TO_SYS( + PMIC_GPIO_QUICKVX_CLK), 0); + } + } + + if (quickvx_mddi_client) + rc = regulator_set_voltage(mddi_ldo20, 1800000, 1800000); + else + rc = regulator_set_voltage(mddi_ldo20, 1500000, 1500000); + + if (rc) { + pr_err("%s: could not set voltage for ldo20: %d\n", + __func__, rc); + return rc; + } + + if (on) { + rc = regulator_enable(mddi_ldo20); + if (rc) { + pr_err("%s: LDO20 regulator enable failed (%d)\n", + __func__, rc); + return rc; + } + + rc = regulator_enable(mddi_ldo12); + if (rc) { + pr_err("%s: LDO12 regulator enable failed (%d)\n", + __func__, rc); + return rc; + } + + if (other_mddi_client) { + rc = regulator_enable(mddi_ldo16); + if (rc) { + pr_err("%s: LDO16 regulator enable failed (%d)\n", + __func__, rc); + return rc; + } + } + + if (quickvx_ldo_enabled) { + /* Disable LDO6 during display ON */ + rc = regulator_disable(mddi_ldo6); + if (rc) { + pr_err("%s: LDO6 regulator disable failed (%d)\n", + __func__, rc); + return rc; + } + quickvx_ldo_enabled = 0; + } + + rc = regulator_enable(mddi_lcd); + if (rc) { + pr_err("%s: LCD regulator 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 = regulator_disable(mddi_ldo20); + if (rc) { + pr_err("%s: LDO20 regulator disable failed (%d)\n", + __func__, rc); + return rc; + } + + + if (other_mddi_client) { + rc = regulator_disable(mddi_ldo16); + if (rc) { + pr_err("%s: LDO16 regulator 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 = regulator_enable(mddi_ldo6); + if (rc) { + pr_err("%s: LDO6 regulator 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); + } + + rc = regulator_disable(mddi_lcd); + if (rc) { + pr_err("%s: LCD regulator disable failed (%d)\n", + __func__, rc); + return rc; + } + + mdelay(5); /* ensure power is stable */ + + rc = regulator_disable(mddi_ldo12); + if (rc) { + pr_err("%s: LDO12 regulator 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) +{ + 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; + if (IS_ERR(mddi_ldo16)) { + rc = PTR_ERR(mddi_ldo16); + pr_err("%s: gp10 vreg get failed (%d)\n", __func__, rc); + return rc; + } + rc = regulator_disable(mddi_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); + } + + 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, + 192000000, + 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), + .mdp_rev = MDP_REV_40, +}; + +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, lcdc_power_initialized; + + if (lcdc_power_save_on == flag_on) + return 0; + + lcdc_power_save_on = flag_on; + + if (unlikely(!lcdc_power_initialized)) { + quickvx_mddi_client = 0; + display_common_init(); + lcdc_power_initialized = 1; + } + + 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 struct regulator *atv_s4, *atv_ldo9; + +static int __init atv_dac_power_init(void) +{ + int rc; + struct regulator_bulk_data regs[] = { + { .supply = "smps4", .min_uV = 2200000, .max_uV = 2200000 }, + { .supply = "ldo9", .min_uV = 2050000, .max_uV = 2050000 }, + }; + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs), regs); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto bail; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs), regs); + + if (rc) { + pr_err("%s: could not set voltages: %d\n", __func__, rc); + goto reg_free; + } + + atv_s4 = regs[0].consumer; + atv_ldo9 = regs[1].consumer; + +reg_free: + regulator_bulk_free(ARRAY_SIZE(regs), regs); +bail: + return rc; +} + +static int atv_dac_power(int on) +{ + int rc = 0; + + if (on) { + rc = regulator_enable(atv_s4); + if (rc) { + pr_err("%s: s4 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + rc = regulator_enable(atv_ldo9); + if (rc) { + pr_err("%s: ldo9 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + } else { + rc = regulator_disable(atv_ldo9); + if (rc) { + pr_err("%s: ldo9 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + rc = regulator_disable(atv_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 u8 bahama_version; + +static struct regulator_bulk_data regs_bt_marimba[] = { + { .supply = "smps3", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "smps2", .min_uV = 1300000, .max_uV = 1300000 }, + { .supply = "ldo24", .min_uV = 1200000, .max_uV = 1200000 }, + { .supply = "ldo13", .min_uV = 2900000, .max_uV = 3050000 }, +}; + +static struct regulator_bulk_data regs_bt_bahama_v1[] = { + { .supply = "smps3", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "ldo7", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "smps2", .min_uV = 1300000, .max_uV = 1300000 }, + { .supply = "ldo13", .min_uV = 2900000, .max_uV = 3050000 }, +}; + +static struct regulator_bulk_data regs_bt_bahama_v2[] = { + { .supply = "smps3", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "ldo7", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "ldo13", .min_uV = 2900000, .max_uV = 3050000 }, +}; + +static struct regulator_bulk_data *regs_bt; +static int regs_bt_count; + +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; + + + 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); + + return 0; +} + +static int bluetooth_regs_init(int bahama_not_marimba) +{ + int rc = 0; + struct device *const dev = &msm_bt_power_device.dev; + + if (bahama_not_marimba) { + bahama_version = read_bahama_ver(); + + switch (bahama_version) { + case VER_1_0: + regs_bt = regs_bt_bahama_v1; + regs_bt_count = ARRAY_SIZE(regs_bt_bahama_v1); + break; + case VER_2_0: + regs_bt = regs_bt_bahama_v2; + regs_bt_count = ARRAY_SIZE(regs_bt_bahama_v2); + break; + case VER_UNSUPPORTED: + default: + dev_err(dev, + "%s: i2c failure or unsupported version: %d\n", + __func__, bahama_version); + rc = -EIO; + goto out; + } + } else { + regs_bt = regs_bt_marimba; + regs_bt_count = ARRAY_SIZE(regs_bt_marimba); + } + + rc = regulator_bulk_get(&msm_bt_power_device.dev, + regs_bt_count, regs_bt); + if (rc) { + dev_err(dev, "%s: could not get regulators: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(regs_bt_count, regs_bt); + if (rc) { + dev_err(dev, "%s: could not set voltages: %d\n", + __func__, rc); + goto reg_free; + } + + return 0; + +reg_free: + regulator_bulk_free(regs_bt_count, regs_bt); +out: + regs_bt_count = 0; + regs_bt = NULL; + return rc; +} + +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 (unlikely(regs_bt_count == 0)) { + rc = bluetooth_regs_init(bahama_not_marimba); + if (rc) + return rc; + } + + if (on) { + rc = regulator_bulk_enable(regs_bt_count, regs_bt); + if (rc) + return rc; + + 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 = regulator_bulk_disable(regs_bt_count, regs_bt); + if (rc) + return rc; + + } + +out: + printk(KERN_DEBUG "Bluetooth power switch: %d\n", on); + + 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 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 +#ifdef CONFIG_MSM_PROC_COMM_REGULATOR + &msm_proccomm_regulator_dev, +#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_MSM_SSBI + &msm_device_ssbi_pmic1, +#endif +#ifdef CONFIG_I2C_SSBI + &msm_device_ssbi7, +#endif + &android_pmem_device, + &msm_fb_device, +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE + &msm_v4l2_video_overlay_device, +#endif + &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, +#ifndef CONFIG_MSM_CAMERA_V4L2 +#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 +#endif + &msm_device_vidc_720p, +#ifdef CONFIG_MSM_GEMINI + &msm_gemini_device, +#endif +#ifndef CONFIG_MSM_CAMERA_V4L2 +#ifdef CONFIG_MSM_VPE + &msm_vpe_device, +#endif +#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, + &msm_adsp_device +}; + +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 regulator *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 (!IS_ERR_OR_NULL(qup_vreg)) { + rc = regulator_enable(qup_vreg); + if (rc) { + pr_err("%s: regulator_enable failed: %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, + .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 = regulator_get(&qup_device_i2c.dev, "lvsw1"); + if (IS_ERR(qup_vreg)) { + dev_err(&qup_device_i2c.dev, + "%s: regulator_get failed: %ld\n", + __func__, PTR_ERR(qup_vreg)); + } +#endif +} + +#ifdef CONFIG_I2C_SSBI +static struct msm_i2c_ssbi_platform_data msm_i2c_ssbi7_pdata = { + .rsl_id = "D:CODEC_SSBI", + .controller_type = MSM_SBI_CTRL_SSBI, +}; +#endif + +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"}, +}; + +#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, + }, +}; + +static struct regulator *sdcc_vreg_data[ARRAY_SIZE(sdcc_cfg_data)]; + +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 regulator *curr = sdcc_vreg_data[dev_id - 1]; + static int enabled_once[] = {0, 0, 0, 0}; + + if (test_bit(dev_id, &vreg_sts) == enable) + return rc; + + if (dev_id == 4) { + if (enable) { + pr_debug("Enable Vdd dev_%d\n", dev_id); + gpio_set_value_cansleep( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC4_PWR_EN_N), + 0); + set_bit(dev_id, &vreg_sts); + } else { + pr_debug("Disable Vdd dev_%d\n", dev_id); + gpio_set_value_cansleep( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC4_PWR_EN_N), + 1); + clear_bit(dev_id, &vreg_sts); + } + } + + if (!enable || enabled_once[dev_id - 1]) + return 0; + if (!curr) + return -ENODEV; + + if (IS_ERR(curr)) + return PTR_ERR(curr); + + if (enable) { + set_bit(dev_id, &vreg_sts); + + rc = regulator_enable(curr); + if (rc) + pr_err("%s: could not enable regulator: %d\n", + __func__, rc); + enabled_once[dev_id - 1] = 1; + } else { + clear_bit(dev_id, &vreg_sts); + + rc = regulator_disable(curr); + if (rc) + pr_err("%s: could not disable regulator: %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 struct regulator_bulk_data mbp_regs_io[2]; +static struct regulator_bulk_data mbp_regs_rf[2]; +static struct regulator_bulk_data mbp_regs_adc[1]; +static struct regulator_bulk_data mbp_regs_core[1]; + +static int mbp_init_regs(struct device *dev) +{ + struct regulator_bulk_data regs[] = { + /* Analog and I/O regs */ + { .supply = "gp4", .min_uV = 2600000, .max_uV = 2600000 }, + { .supply = "s3", .min_uV = 1800000, .max_uV = 1800000 }, + /* RF regs */ + { .supply = "s2", .min_uV = 1300000, .max_uV = 1300000 }, + { .supply = "rf", .min_uV = 2600000, .max_uV = 2600000 }, + /* ADC regs */ + { .supply = "s4", .min_uV = 2200000, .max_uV = 2200000 }, + /* Core regs */ + { .supply = "gp16", .min_uV = 1200000, .max_uV = 1200000 }, + }; + + struct regulator_bulk_data *regptr = regs; + int rc; + + rc = regulator_bulk_get(dev, ARRAY_SIZE(regs), regs); + + if (rc) { + dev_err(dev, "%s: could not get regulators: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs), regs); + + if (rc) { + dev_err(dev, "%s: could not set voltages: %d\n", + __func__, rc); + goto reg_free; + } + + memcpy(mbp_regs_io, regptr, sizeof(mbp_regs_io)); + regptr += ARRAY_SIZE(mbp_regs_io); + + memcpy(mbp_regs_rf, regptr, sizeof(mbp_regs_rf)); + regptr += ARRAY_SIZE(mbp_regs_rf); + + memcpy(mbp_regs_adc, regptr, sizeof(mbp_regs_adc)); + regptr += ARRAY_SIZE(mbp_regs_adc); + + memcpy(mbp_regs_core, regptr, sizeof(mbp_regs_core)); + + return 0; + +reg_free: + regulator_bulk_free(ARRAY_SIZE(regs), regs); +out: + return rc; +} + +static int mbp_setup_rf_vregs(int state) +{ + return state ? + regulator_bulk_enable(ARRAY_SIZE(mbp_regs_rf), mbp_regs_rf) : + regulator_bulk_disable(ARRAY_SIZE(mbp_regs_rf), mbp_regs_rf); +} + +static int mbp_setup_vregs(int state) +{ + return state ? + regulator_bulk_enable(ARRAY_SIZE(mbp_regs_io), mbp_regs_io) : + regulator_bulk_disable(ARRAY_SIZE(mbp_regs_io), mbp_regs_io); +} + +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; + } + 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) +{ + static bool is_enabled; + int rc = 0; + + if (enable && !is_enabled) { + rc = regulator_bulk_enable(ARRAY_SIZE(mbp_regs_core), + mbp_regs_core); + if (rc) { + pr_err("%s: could not enable regulators: %d\n", + __func__, rc); + } else { + is_enabled = true; + } + } + + 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) +{ + return state ? + regulator_bulk_enable(ARRAY_SIZE(mbp_regs_adc), mbp_regs_adc) : + regulator_bulk_disable(ARRAY_SIZE(mbp_regs_adc), mbp_regs_adc); +} + +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; + + 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__); + + 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_setup_vregs(MBP_OFF); + if (rc) + goto exit; + pr_debug("%s: mbp_setup_vregs(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) { + rc = mbp_init_regs(dv); + if (rc) { + dev_err(dv, "%s: regulator init failed: %d\n", + __func__, rc); + goto out; + } + 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, + .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, + .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 + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 1, +}; +#endif + +#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, + .sdiowakeup_irq = MSM_GPIO_TO_INT(118), + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif + +#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, + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static int msm_sdc1_lvlshft_enable(void) +{ + static struct regulator *ldo5; + int rc; + + /* Enable LDO5, an input to the FET that powers slot 1 */ + + ldo5 = regulator_get(NULL, "ldo5"); + + if (IS_ERR(ldo5)) { + rc = PTR_ERR(ldo5); + pr_err("%s: could not get ldo5: %d\n", __func__, rc); + goto out; + } + + rc = regulator_set_voltage(ldo5, 2850000, 2850000); + if (rc) { + pr_err("%s: could not set ldo5 voltage: %d\n", __func__, rc); + goto ldo5_free; + } + + rc = regulator_enable(ldo5); + if (rc) { + pr_err("%s: could not enable ldo5: %d\n", __func__, rc); + goto ldo5_free; + } + + /* 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__); + + return 0; + +ldo5_free: + regulator_put(ldo5); +out: + ldo5 = NULL; + return rc; +} +#endif + +static int mmc_regulator_init(int sdcc_no, const char *supply, int uV) +{ + int rc; + + BUG_ON(sdcc_no < 1 || sdcc_no > 4); + + sdcc_no--; + + sdcc_vreg_data[sdcc_no] = regulator_get(NULL, supply); + + if (IS_ERR(sdcc_vreg_data[sdcc_no])) { + rc = PTR_ERR(sdcc_vreg_data[sdcc_no]); + pr_err("%s: could not get regulator \"%s\": %d\n", + __func__, supply, rc); + goto out; + } + + rc = regulator_set_voltage(sdcc_vreg_data[sdcc_no], uV, uV); + + if (rc) { + pr_err("%s: could not set voltage for \"%s\" to %d uV: %d\n", + __func__, supply, uV, rc); + goto reg_free; + } + + return rc; + +reg_free: + regulator_put(sdcc_vreg_data[sdcc_no]); +out: + sdcc_vreg_data[sdcc_no] = NULL; + return rc; +} + +static void __init msm7x30_init_mmc(void) +{ +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + if (mmc_regulator_init(1, "s3", 1800000)) + goto out1; + + if (machine_is_msm7x30_fluid()) { + msm7x30_sdc1_data.ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29; + if (msm_sdc1_lvlshft_enable()) { + pr_err("%s: could not enable level shift\n"); + goto out1; + } + } + + msm_add_sdcc(1, &msm7x30_sdc1_data); +out1: +#endif +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + if (mmc_regulator_init(2, "s3", 1800000)) + goto out2; + + if (machine_is_msm8x55_svlte_surf()) + msm7x30_sdc2_data.msmsdcc_fmax = 24576000; + if (machine_is_msm8x55_svlte_surf() || + machine_is_msm8x55_svlte_ffa()) { + msm7x30_sdc2_data.sdiowakeup_irq = MSM_GPIO_TO_INT(68); + msm7x30_sdc2_data.is_sdio_al_client = 1; + } + + msm_add_sdcc(2, &msm7x30_sdc2_data); +out2: +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + if (mmc_regulator_init(3, "s3", 1800000)) + goto out3; + + msm_sdcc_setup_gpio(3, 1); + msm_add_sdcc(3, &msm7x30_sdc3_data); +out3: +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + if (mmc_regulator_init(4, "mmc", 2850000)) + return; + + 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; + } + + 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__); + } +} + +#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 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) -#include -#include -#include -#include +#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) -#include -#include -#include +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", }, +}; -#include -#include "devices.h" -#include "gpiomux.h" -#include "proc_comm.h" +static struct msm_tsif_platform_data tsif_platform_data = { + .num_gpios = ARRAY_SIZE(tsif_gpios), + .gpios = tsif_gpios, + .tsif_pclk = "iface_clk", + .tsif_ref_clk = "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.leds_pdata = &pm8058_surf_leds_data; + else if (!machine_is_msm7x30_fluid()) + pm8058_7x30_data.leds_pdata = &pm8058_ffa_leds_data; + else if (machine_is_msm7x30_fluid()) + pm8058_7x30_data.leds_pdata = &pm8058_fluid_leds_data; +} + +static struct msm_spm_platform_data msm_spm_data __initdata = { + .reg_base_addr = MSM_SAW0_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 regulator_bulk_data tsc2007_regs[] = { + { .supply = "s3", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "s2", .min_uV = 1300000, .max_uV = 1300000 }, +}; + +static int tsc2007_init(void) +{ + int rc; + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(tsc2007_regs), tsc2007_regs); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); + + if (rc) { + pr_err("%s: could not set voltages: %d\n", __func__, rc); + goto reg_free; + } + + rc = regulator_bulk_enable(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); + + if (rc) { + pr_err("%s: could not enable regulators: %d\n", __func__, rc); + goto reg_free; + } + + 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 reg_disable; + } + + return 0; + +reg_disable: + regulator_bulk_disable(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); +reg_free: + regulator_bulk_free(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); +out: + 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) +{ + + regulator_bulk_disable(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); + regulator_bulk_free(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); + + msm_gpios_disable_free(tsc2007_config_data, + ARRAY_SIZE(tsc2007_config_data)); +} + +static int tsc2007_power_shutdown(bool enable) +{ + int rc; + + rc = (enable == false) ? + regulator_bulk_enable(ARRAY_SIZE(tsc2007_regs), tsc2007_regs) : + regulator_bulk_disable(ARRAY_SIZE(tsc2007_regs), tsc2007_regs); + + if (rc) { + pr_err("%s: could not %sable regulators: %d\n", + __func__, enable ? "dis" : "en", rc); + return rc; + } + + if (enable == false) + msleep(20); + + return 0; +} + +static struct tsc2007_platform_data tsc2007_ts_data = { + .model = 2007, + .x_plate_ohms = 300, + .min_x = 210, + .max_x = 3832, + .min_y = 150, + .max_y = 3936, + .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, + }, +}; +#endif + +static struct regulator_bulk_data regs_isa1200[] = { + { .supply = "gp7", .min_uV = 1800000, .max_uV = 1800000 }, + { .supply = "gp10", .min_uV = 2600000, .max_uV = 2600000 }, +}; + +static int isa1200_power(int vreg_on) +{ + int rc = 0; + + rc = vreg_on ? + regulator_bulk_enable(ARRAY_SIZE(regs_isa1200), regs_isa1200) : + regulator_bulk_disable(ARRAY_SIZE(regs_isa1200), regs_isa1200); + + if (rc) { + pr_err("%s: could not %sable regulators: %d\n", + __func__, vreg_on ? "en" : "dis", rc); + goto out; + } + + /* 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; + } -extern struct sys_timer msm_timer; + return 0; -static void __init msm7x30_fixup(struct tag *tag, char **cmdline, - struct meminfo *mi) +vreg_fail: + if (vreg_on) + regulator_bulk_disable(ARRAY_SIZE(regs_isa1200), regs_isa1200); + else + regulator_bulk_enable(ARRAY_SIZE(regs_isa1200), regs_isa1200); +out: + return rc; +} + +static int isa1200_dev_setup(bool enable) { - for (; tag->hdr.size; tag = tag_next(tag)) - if (tag->hdr.tag == ATAG_MEM && tag->u.mem.start == 0x200000) { - tag->u.mem.start = 0; - tag->u.mem.size += SZ_2M; + int rc; + + if (enable == true) { + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs_isa1200), + regs_isa1200); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", + __func__, rc); + goto out; } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_isa1200), + regs_isa1200); + if (rc) { + pr_err("%s: could not set voltages: %d\n", + __func__, rc); + goto reg_free; + } + + 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 reg_free; + } + + 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 reg_free; + } + + gpio_set_value(HAP_LVL_SHFT_MSM_GPIO, 1); + } else { + regulator_bulk_free(ARRAY_SIZE(regs_isa1200), regs_isa1200); + gpio_free(HAP_LVL_SHFT_MSM_GPIO); + } + + return 0; + +reg_free: + regulator_bulk_free(ARRAY_SIZE(regs_isa1200), regs_isa1200); +out: + 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), + .hap_len_gpio = -1, + .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 void __init msm7x30_reserve(void) +static struct i2c_board_info msm_isa1200_board_info[] = { + { + I2C_BOARD_INFO("isa1200_1", 0x90>>1), + .platform_data = &isa1200_1_pdata, + }, +}; + + +static int kp_flip_mpp_config(void) { - memblock_remove(0x0, SZ_2M); + struct pm8xxx_mpp_config_data kp_flip_mpp = { + .type = PM8XXX_MPP_TYPE_D_INPUT, + .level = PM8018_MPP_DIG_LEVEL_S3, + .control = PM8XXX_MPP_DIN_TO_INT, + }; + + return pm8xxx_mpp_config(PM8058_MPP_PM_TO_SYS(PM_FLIP_MPP), + &kp_flip_mpp); } -static int hsusb_phy_init_seq[] = { - 0x30, 0x32, /* Enable and set Pre-Emphasis Depth to 20% */ - 0x02, 0x36, /* Disable CDR Auto Reset feature */ - -1 +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 struct msm_otg_platform_data msm_otg_pdata = { - .phy_init_seq = hsusb_phy_init_seq, - .mode = USB_PERIPHERAL, - .otg_control = OTG_PHY_CONTROL, +static struct platform_device flip_switch_device = { + .name = "kp_flip_switch", + .id = -1, + .dev = { + .platform_data = &flip_switch_data, + } +}; + +static struct regulator_bulk_data regs_tma300[] = { + { .supply = "gp6", .min_uV = 3050000, .max_uV = 3100000 }, + { .supply = "gp7", .min_uV = 1800000, .max_uV = 1800000 }, +}; + +static int tma300_power(int vreg_on) +{ + int rc; + + rc = vreg_on ? + regulator_bulk_enable(ARRAY_SIZE(regs_tma300), regs_tma300) : + regulator_bulk_disable(ARRAY_SIZE(regs_tma300), regs_tma300); + + if (rc) + pr_err("%s: could not %sable regulators: %d\n", + __func__, vreg_on ? "en" : "dis", rc); + return rc; +} + +#define TS_GPIO_IRQ 150 + +static int tma300_dev_setup(bool enable) +{ + int rc; + + if (enable) { + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs_tma300), + regs_tma300); + + if (rc) { + pr_err("%s: could not get regulators: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_bulk_set_voltage(ARRAY_SIZE(regs_tma300), + regs_tma300); + + if (rc) { + pr_err("%s: could not set voltages: %d\n", + __func__, rc); + goto reg_free; + } + + /* 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 reg_free; + } + + /* 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_properties\n", __func__); + rc = -ENOMEM; + goto reg_free; + } + 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); + goto kobj_free; + } + } else { + regulator_bulk_free(ARRAY_SIZE(regs_tma300), regs_tma300); + /* destroy virtual keys */ + if (properties_kobj) { + sysfs_remove_group(properties_kobj, + &tma300_properties_attr_group); + kobject_put(properties_kobj); + } + } + return 0; + +kobj_free: + kobject_put(properties_kobj); + properties_kobj = NULL; +reg_free: + regulator_bulk_free(ARRAY_SIZE(regs_tma300), regs_tma300); +out: + 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, }; -struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS] = { +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; + + soc_version = socinfo_get_version(); + + msm_clock_init(&msm7x30_clock_init_data); #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, + msm7x30_init_uart2(); +#endif + msm_spm_init(&msm_spm_data, 1); + acpuclk_init(&acpuclk_7x30_soc_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; +#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); + } + + pmic8058_leds_init(); + + buses_init(); + +#ifdef CONFIG_MSM_SSBI + msm_device_ssbi_pmic1.dev.platform_data = + &msm7x30_ssbi_pm8058_pdata; +#endif + + 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 +#ifdef CONFIG_MSM_CAMERA_V4L2 + msm7x30_init_cam(); +#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 + + atv_dac_power_init(); + sensors_ldo_init(); + hdmi_init_regs(); + msm_fb_add_devices(); + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_register_irqs(); + msm_device_i2c_init(); + msm_device_i2c_2_init(); + qup_device_i2c_init(); + msm7x30_init_marimba(); +#ifdef CONFIG_MSM7KV2_AUDIO + snddev_poweramp_gpio_init(); + snddev_hsed_voltage_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_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); + + pm8058_gpios_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] = { }, - [51] = { /* UART2 RX */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_2 | GPIOMUX_VALID, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, }, - [52] = { /* UART2 TX */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_2 | GPIOMUX_VALID, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, }, -#endif }; -static struct platform_device *devices[] __initdata = { -#if defined(CONFIG_SERIAL_MSM) || defined(CONFIG_MSM_SERIAL_DEBUGGER) - &msm_device_uart2, +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 - &msm_device_smd, - &msm_device_otg, - &msm_device_hsusb, - &msm_device_hsusb_host, +} + +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 < phys_add) + return MEMTYPE_EBI0; + if (paddr >= phys_add && 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_init_irq(void) +static void __init msm7x30_reserve(void) { - msm_init_irq(); + reserve_info = &msm7x30_reserve_info; + msm_reserve(); } -static void __init msm7x30_init(void) +static void __init msm7x30_allocate_memory_regions(void) { - 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; + void *addr; + unsigned long size; - platform_add_devices(devices, ARRAY_SIZE(devices)); + 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)); + +#ifdef CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE + size = MSM_V4L2_VIDEO_OVERLAY_BUF_SIZE; + addr = alloc_bootmem_align(size, 0x1000); + msm_v4l2_video_overlay_resources[0].start = __pa(addr); + msm_v4l2_video_overlay_resources[0].end = + msm_v4l2_video_overlay_resources[0].start + size - 1; + pr_debug("allocating %lu bytes at %p (%lx physical) for v4l2\n", + size, addr, __pa(addr)); +#endif } 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); + if (socinfo_init() < 0) + printk(KERN_ERR "%s: socinfo_init() failed!\n", + __func__); +} + +static void __init msm7x30_init_early(void) +{ + msm7x30_allocate_memory_regions(); +} + +static void __init msm7x30_fixup(struct tag *tags, char **cmdline, + struct meminfo *mi) +{ + for (; tags->hdr.size; tags = tag_next(tags)) { + if (tags->hdr.tag == ATAG_MEM && tags->u.mem.start == + DDR1_BANK_BASE) { + ebi1_phys_offset = DDR1_BANK_BASE; + phys_add = DDR1_BANK_BASE; + break; + } + } } MACHINE_START(MSM7X30_SURF, "QCT MSM7X30 SURF") .atag_offset = 0x100, - .fixup = msm7x30_fixup, - .reserve = msm7x30_reserve, .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, + .handle_irq = vic_handle_irq, + .fixup = msm7x30_fixup, MACHINE_END MACHINE_START(MSM7X30_FFA, "QCT MSM7X30 FFA") .atag_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, + .handle_irq = vic_handle_irq, .fixup = msm7x30_fixup, +MACHINE_END + +MACHINE_START(MSM7X30_FLUID, "QCT MSM7X30 FLUID") + .atag_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, + .handle_irq = vic_handle_irq, + .fixup = msm7x30_fixup, +MACHINE_END + +MACHINE_START(MSM8X55_SURF, "QCT MSM8X55 SURF") + .atag_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, + .handle_irq = vic_handle_irq, + .fixup = msm7x30_fixup, MACHINE_END -MACHINE_START(MSM7X30_FLUID, "QCT MSM7X30 FLUID") +MACHINE_START(MSM8X55_FFA, "QCT MSM8X55 FFA") .atag_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, + .handle_irq = vic_handle_irq, .fixup = msm7x30_fixup, +MACHINE_END +MACHINE_START(MSM8X55_SVLTE_SURF, "QCT MSM8X55 SVLTE SURF") + .atag_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, + .handle_irq = vic_handle_irq, + .fixup = msm7x30_fixup, +MACHINE_END +MACHINE_START(MSM8X55_SVLTE_FFA, "QCT MSM8X55 SVLTE FFA") + .atag_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, + .handle_irq = vic_handle_irq, + .fixup = msm7x30_fixup, MACHINE_END diff --git a/arch/arm/mach-msm/board-msm8960.c b/arch/arm/mach-msm/board-msm8960.c deleted file mode 100644 index ed3598128530f143df0c89fedf66a96177c8f4e0..0000000000000000000000000000000000000000 --- a/arch/arm/mach-msm/board-msm8960.c +++ /dev/null @@ -1,115 +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 - -#include -#include -#include -#include - -#include -#include - -#include "devices.h" - -static void __init msm8960_fixup(struct tag *tag, char **cmdline, - struct meminfo *mi) -{ - for (; tag->hdr.size; tag = tag_next(tag)) - if (tag->hdr.tag == ATAG_MEM && - tag->u.mem.start == 0x40200000) { - tag->u.mem.start = 0x40000000; - tag->u.mem.size += SZ_2M; - } -} - -static void __init msm8960_reserve(void) -{ - memblock_remove(0x40000000, SZ_2M); -} - -static void __init msm8960_map_io(void) -{ - msm_map_msm8960_io(); -} - -static void __init msm8960_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(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); - - if (machine_is_msm8960_rumi3()) - writel(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); - - /* 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 *sim_devices[] __initdata = { - &msm8960_device_uart_gsbi2, -}; - -static struct platform_device *rumi3_devices[] __initdata = { - &msm8960_device_uart_gsbi5, -}; - -static void __init msm8960_sim_init(void) -{ - platform_add_devices(sim_devices, ARRAY_SIZE(sim_devices)); -} - -static void __init msm8960_rumi3_init(void) -{ - platform_add_devices(rumi3_devices, ARRAY_SIZE(rumi3_devices)); -} - -MACHINE_START(MSM8960_SIM, "QCT MSM8960 SIMULATOR") - .fixup = msm8960_fixup, - .reserve = msm8960_reserve, - .map_io = msm8960_map_io, - .init_irq = msm8960_init_irq, - .timer = &msm_timer, - .handle_irq = gic_handle_irq, - .init_machine = msm8960_sim_init, -MACHINE_END - -MACHINE_START(MSM8960_RUMI3, "QCT MSM8960 RUMI3") - .fixup = msm8960_fixup, - .reserve = msm8960_reserve, - .map_io = msm8960_map_io, - .init_irq = msm8960_init_irq, - .timer = &msm_timer, - .handle_irq = gic_handle_irq, - .init_machine = msm8960_rumi3_init, -MACHINE_END - diff --git a/arch/arm/mach-msm/board-msm8x60-camera.c b/arch/arm/mach-msm/board-msm8x60-camera.c new file mode 100644 index 0000000000000000000000000000000000000000..32d5530f287778bd7675f6853e177ac49c0d48bc --- /dev/null +++ b/arch/arm/mach-msm/board-msm8x60-camera.c @@ -0,0 +1,543 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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-msm8x60.h" +#include "devices.h" + +#define GPIO_EXT_CAMIF_PWR_EN1 (PM8901_MPP_BASE + PM8901_MPPS + 13) +#define GPIO_WEB_CAMIF_STANDBY1 (PM8901_MPP_BASE + PM8901_MPPS + 60) +#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, +}; +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 + +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_zsl_vectors, + }, + { + ARRAY_SIZE(cam_preview_vectors), + cam_zsl_vectors, + }, + { + ARRAY_SIZE(cam_video_vectors), + cam_zsl_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", +}; + +static struct msm_camera_device_platform_data msm_camera_csi_device_data[] = { + { + .csid_core = 0, + .is_csic = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + .ioclk = { + .vfe_clk_rate = 228570000, + }, + }, + { + .csid_core = 1, + .is_csic = 1, + .is_vpe = 1, + .cam_bus_scale_table = &cam_bus_client_pdata, + .ioclk = { + .vfe_clk_rate = 228570000, + }, + }, +}; +static struct camera_vreg_t msm_8x60_back_cam_vreg[] = { + {"cam_vana", REG_LDO, 2850000, 2850000, -1}, + {"cam_vio", REG_VS, 0, 0, 0}, + {"cam_vdig", REG_LDO, 1200000, 1200000, -1}, +}; + +static struct gpio msm8x60_common_cam_gpio[] = { + {32, GPIOF_DIR_IN, "CAMIF_MCLK"}, + {47, GPIOF_DIR_IN, "CAMIF_I2C_DATA"}, + {48, GPIOF_DIR_IN, "CAMIF_I2C_CLK"}, + {105, GPIOF_DIR_IN, "STANDBY"}, +}; + +static struct gpio msm8x60_back_cam_gpio[] = { + {GPIO_EXT_CAMIF_PWR_EN1, GPIOF_DIR_OUT, "CAMIF_PWR_EN"}, + {106, GPIOF_DIR_OUT, "CAM_RESET"}, +}; + +static struct msm_gpio_set_tbl msm8x60_back_cam_gpio_set_tbl[] = { + {GPIO_EXT_CAMIF_PWR_EN1, GPIOF_OUT_INIT_LOW, 10000}, + {GPIO_EXT_CAMIF_PWR_EN1, GPIOF_OUT_INIT_HIGH, 5000}, + {106, GPIOF_OUT_INIT_LOW, 1000}, + {106, GPIOF_OUT_INIT_HIGH, 4000}, +}; + +static struct msm_camera_gpio_conf msm_8x60_back_cam_gpio_conf = { + .cam_gpio_common_tbl = msm8x60_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(msm8x60_common_cam_gpio), + .cam_gpio_req_tbl = msm8x60_back_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(msm8x60_back_cam_gpio), + .cam_gpio_set_tbl = msm8x60_back_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(msm8x60_back_cam_gpio_set_tbl), +}; + + +static struct i2c_board_info imx074_actuator_i2c_info = { + I2C_BOARD_INFO("msm_actuator", 0x11), +}; + +static struct msm_actuator_info imx074_actuator_info = { + .board_info = &imx074_actuator_i2c_info, + .cam_name = MSM_ACTUATOR_MAIN_CAM_0, + .bus_id = MSM_GSBI4_QUP_I2C_BUS_ID, + .vcm_enable = 0, +}; + +static struct msm_camera_sensor_flash_data flash_imx074 = { + .flash_type = MSM_CAMERA_FLASH_LED, +#ifdef CONFIG_MSM_CAMERA_FLASH + .flash_src = &msm_flash_src, +#endif +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_imx074 = { + .mount_angle = 180, + .cam_vreg = msm_8x60_back_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8x60_back_cam_vreg), + .gpio_conf = &msm_8x60_back_cam_gpio_conf, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx074_data = { + .sensor_name = "imx074", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_imx074, + .strobe_flash_data = &strobe_flash_xenon, + .sensor_platform_info = &sensor_board_info_imx074, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, + .actuator_info = &imx074_actuator_info +}; + +static struct msm_camera_sensor_flash_data flash_mt9e013 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_mt9e013 = { + .mount_angle = 0, + .cam_vreg = msm_8x60_back_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8x60_back_cam_vreg), + .gpio_conf = &msm_8x60_back_cam_gpio_conf, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9e013_data = { + .sensor_name = "mt9e013", + .pdata = &msm_camera_csi_device_data[0], + .flash_data = &flash_mt9e013, + .sensor_platform_info = &sensor_board_info_mt9e013, + .csi_if = 1, + .camera_type = BACK_CAMERA_2D, +}; + +static struct gpio ov7692_cam_gpio[] = { + {GPIO_WEB_CAMIF_STANDBY1, GPIOF_DIR_OUT, "CAM_EN"}, +}; + +static struct msm_gpio_set_tbl ov7692_cam_gpio_set_tbl[] = { + {GPIO_WEB_CAMIF_STANDBY1, GPIOF_OUT_INIT_LOW, 10000}, +}; + +static struct msm_camera_gpio_conf ov7692_cam_gpio_conf = { + .cam_gpio_common_tbl = msm8x60_common_cam_gpio, + .cam_gpio_common_tbl_size = ARRAY_SIZE(msm8x60_common_cam_gpio), + .cam_gpio_req_tbl = ov7692_cam_gpio, + .cam_gpio_req_tbl_size = ARRAY_SIZE(ov7692_cam_gpio), + .cam_gpio_set_tbl = ov7692_cam_gpio_set_tbl, + .cam_gpio_set_tbl_size = ARRAY_SIZE(ov7692_cam_gpio_set_tbl), +}; + +static struct msm_camera_sensor_flash_data flash_ov7692 = { + .flash_type = MSM_CAMERA_FLASH_NONE, +}; + +static struct msm_camera_sensor_platform_info sensor_board_info_ov7692 = { + .mount_angle = 0, + .cam_vreg = msm_8x60_back_cam_vreg, + .num_vreg = ARRAY_SIZE(msm_8x60_back_cam_vreg), + .gpio_conf = &ov7692_cam_gpio_conf, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov7692_data = { + .sensor_name = "ov7692", + .pdata = &msm_camera_csi_device_data[1], + .flash_data = &flash_ov7692, + .sensor_platform_info = &sensor_board_info_ov7692, + .csi_if = 1, + .camera_type = FRONT_CAMERA_2D, +}; + +static struct platform_device msm_camera_server = { + .name = "msm_cam_server", + .id = 0, +}; + +void __init msm8x60_init_cam(void) +{ + platform_device_register(&msm_camera_server); + platform_device_register(&msm_device_csic0); + platform_device_register(&msm_device_csic1); + platform_device_register(&msm_device_vfe); + platform_device_register(&msm_device_vpe); +} + +#ifdef CONFIG_I2C +static struct i2c_board_info msm8x60_camera_i2c_boardinfo[] = { + { + I2C_BOARD_INFO("imx074", 0x1A), + .platform_data = &msm_camera_sensor_imx074_data, + }, + { + I2C_BOARD_INFO("mt9e013", 0x6C), + .platform_data = &msm_camera_sensor_mt9e013_data, + }, + { + I2C_BOARD_INFO("ov7692", 0x78), + .platform_data = &msm_camera_sensor_ov7692_data, + }, +}; + +struct msm_camera_board_info msm8x60_camera_board_info = { + .board_info = msm8x60_camera_i2c_boardinfo, + .num_i2c_board_info = ARRAY_SIZE(msm8x60_camera_i2c_boardinfo), +}; +#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 0000000000000000000000000000000000000000..6078367c05f542eaa19c2481b20323ec1b06f38f --- /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 fb3496a52ef4c08921de04e75e4682ad00e796a2..efde720f132fc551de3cb9737cca28f8a57ba78c 100644 --- a/arch/arm/mach-msm/board-msm8x60.c +++ b/arch/arm/mach-msm/board-msm8x60.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This 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,149 +8,10624 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or 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 + +#ifdef CONFIG_ANDROID_PMEM +#include +#endif + +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) +#include +#endif +#ifdef CONFIG_SND_SOC_WM8903 +#include +#endif +#include +#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 +#include +#include + +#include "devices.h" +#include "devices-msm8x60.h" +#include +#include "pm.h" +#include +#include "spm.h" +#include "rpm_log.h" +#include "timer.h" +#include "gpiomux-8x60.h" +#include "rpm_stats.h" +#include "peripheral-loader.h" +#include +#include "rpm_resources.h" +#include "acpuclock.h" +#include "pm-boot.h" +#include "board-storage-common-a.h" + +#include +#include +#include + +#define MSM_SHARED_RAM_PHYS 0x40000000 +#define MDM2AP_SYNC 129 + +#define GPIO_ETHERNET_RESET_N_DRAGON 30 +#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 LCDC_NT35582_PANEL_NAME "lcdc_nt35582_wvga" + +#define MIPI_CMD_NOVATEK_QHD_PANEL_NAME "mipi_cmd_novatek_qhd" +#define MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME "mipi_video_novatek_qhd" +#define MIPI_VIDEO_TOSHIBA_WVGA_PANEL_NAME "mipi_video_toshiba_wvga" +#define HDMI_PANEL_NAME "hdmi_msm" +#define TVOUT_PANEL_NAME "tvout_msm" + +#define DSPS_PIL_GENERIC_NAME "dsps" +#define DSPS_PIL_FLUID_NAME "dsps_fluid" + +#ifdef CONFIG_ION_MSM +static struct platform_device ion_dev; +#endif + +enum { + GPIO_EXPANDER_IRQ_BASE = PM8901_IRQ_BASE + NR_PMIC8901_IRQS, + GPIO_EXPANDER_GPIO_BASE = PM8901_MPP_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, +}; + +struct pm8xxx_mpp_init_info { + unsigned mpp; + struct pm8xxx_mpp_config_data config; +}; + +#define PM8058_MPP_INIT(_mpp, _type, _level, _control) \ +{ \ + .mpp = PM8058_MPP_PM_TO_SYS(_mpp), \ + .config = { \ + .type = PM8XXX_MPP_TYPE_##_type, \ + .level = _level, \ + .control = PM8XXX_MPP_##_control, \ + } \ +} + +#define PM8901_MPP_INIT(_mpp, _type, _level, _control) \ +{ \ + .mpp = PM8901_MPP_PM_TO_SYS(_mpp), \ + .config = { \ + .type = PM8XXX_MPP_TYPE_##_type, \ + .level = _level, \ + .control = PM8XXX_MPP_##_control, \ + } \ +} + +/* + * 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, + }, +}; + +/* + * 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 = 800000, + .max_uV = 1325000, + }, + .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 = 800000, + .max_uV = 1325000, + }, + .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, + .bus_scale_table = NULL, +}; + +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, + .bus_scale_table = NULL, +}; + +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 */ +#define PMIC_GPIO_HAP_LDO_ENABLE 5 /* PMIC GPIO Number 6 */ +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), + .hap_len_gpio = PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_HAP_LDO_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_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, + }, +}; + +static struct msm_rpmrs_platform_data msm_rpmrs_data __initdata = { + .levels = &msm_rpmrs_levels[0], + .num_levels = ARRAY_SIZE(msm_rpmrs_levels), + .vdd_mem_levels = { + [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] = 1325, + }, + .vdd_dig_levels = { + [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, + }, + .vdd_mask = 0xFFF, + .rpmrs_target_id = { + [MSM_RPMRS_ID_PXO_CLK] = MSM_RPM_ID_PXO_CLK, + [MSM_RPMRS_ID_L2_CACHE_CTL] = MSM_RPM_ID_APPS_L2_CACHE_CTL, + [MSM_RPMRS_ID_VDD_DIG_0] = MSM_RPM_ID_SMPS1_0, + [MSM_RPMRS_ID_VDD_DIG_1] = MSM_RPM_ID_SMPS1_1, + [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_RPM_CTL] = MSM_RPM_ID_TRIGGER_SET_FROM, + }, +}; + +static struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_TZ, +}; + +#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_MSM_72K) || defined(CONFIG_USB_EHCI_MSM_72K) +static struct msm_otg_platform_data msm_otg_pdata; +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) +#define PMIC_ID_GPIO 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_phy_id_setup_init(int init) +{ + unsigned ret; + + struct pm8xxx_mpp_config_data hsusb_phy_mpp = { + .type = PM8XXX_MPP_TYPE_D_OUTPUT, + .level = PM8901_MPP_DIG_LEVEL_L5, + }; + + if (init) { + hsusb_phy_mpp.control = PM8XXX_MPP_DOUT_CTRL_HIGH; + ret = pm8xxx_mpp_config(PM8901_MPP_PM_TO_SYS(1), + &hsusb_phy_mpp); + if (ret < 0) + pr_err("%s:MPP2 configuration failed\n", __func__); + } else { + hsusb_phy_mpp.control = PM8XXX_MPP_DOUT_CTRL_LOW; + ret = pm8xxx_mpp_config(PM8901_MPP_PM_TO_SYS(1), + &hsusb_phy_mpp); + if (ret < 0) + pr_err("%s:MPP2 un config failed\n", __func__); + } + return ret; +} + +static int msm_hsusb_pmic_id_notif_init(void (*callback)(int online), int init) +{ + unsigned ret = -ENODEV; + + struct pm_gpio pmic_id_cfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + }; + struct pm_gpio pmic_id_uncfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + }; + 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() || + machine_is_msm8x60_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; + INIT_DELAYED_WORK(&pmic_id_det, pmic_id_detect); + ret = pm8xxx_gpio_config(PM8058_GPIO_PM_TO_SYS(PMIC_ID_GPIO), + &pmic_id_cfg); + if (ret) { + pr_err("%s:return val of pm8xxx_gpio_config: %d\n", + __func__, ret); + return ret; + } + ret = request_threaded_irq(PMICID_INT, NULL, pmic_id_on_irq, + (IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING), + "msm_otg_id", NULL); + if (ret) { + pr_err("%s:pmic_usb_id interrupt registration failed", + __func__); + return ret; + } + msm_otg_pdata.pmic_id_irq = PMICID_INT; + } else { + usb_phy_susp_dig_vol = 750000; + free_irq(PMICID_INT, 0); + ret = pm8xxx_gpio_config(PM8058_GPIO_PM_TO_SYS(PMIC_ID_GPIO), + &pmic_id_uncfg); + if (ret) { + pr_err("%s: return val of pm8xxx_gpio_config: %d\n", + __func__, ret); + return ret; + } + msm_otg_pdata.pmic_id_irq = 0; + cancel_delayed_work_sync(&pmic_id_det); + notify_vbus_state_func_ptr = NULL; + } + 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_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 + */ + .pemp_level = PRE_EMPHASIS_WITH_20_PERCENT, + .cdr_autoreset = CDR_AUTO_RESET_DISABLE, + .se1_gating = SE1_GATING_DISABLE, + .bam_disable = 1, +#ifdef CONFIG_USB_EHCI_MSM_72K + .pmic_id_notif_init = msm_hsusb_pmic_id_notif_init, + .phy_id_setup_init = msm_hsusb_phy_id_setup_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_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 +#ifndef CONFIG_MSM_CAMERA_V4L2 +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 +#endif + +#ifdef CONFIG_MSM_CAMERA +#ifndef CONFIG_MSM_CAMERA_V4L2 +#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; + } + + if (!machine_is_msm8x60_dragon()) { + rc = gpio_request(GPIO_EXT_CAMIF_PWR_EN, "CAM_EN"); + if (rc < 0) { + config_gpio_table(MSM_CAM_OFF); + pr_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); + msleep(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); + + if (!machine_is_msm8x60_dragon()) { + 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() || machine_is_msm8x60_dragon())) { + 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() || machine_is_msm8x60_dragon())) { + 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_platform_info imx074_sensor_board_info = { + .mount_angle = 180 +}; + +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 = &imx074_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_enable = 1, + .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_VX6953 +static struct msm_camera_sensor_platform_info vx6953_sensor_8660_info = { + .mount_angle = 270 +}; + +static struct msm_camera_sensor_flash_data flash_vx6953 = { + .flash_type = MSM_CAMERA_FLASH_NONE, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_vx6953_data = { + .sensor_name = "vx6953", + .sensor_reset = 63, + .sensor_pwd = 63, + .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_vx6953, + .sensor_platform_info = &vx6953_sensor_8660_info, + .csi_if = 1 +}; +struct platform_device msm_camera_sensor_vx6953 = { + .name = "msm_camera_vx6953", + .dev = { + .platform_data = &msm_camera_sensor_vx6953_data, + }, +}; +#endif +#ifdef CONFIG_QS_S5K4E1 + +static struct msm_camera_sensor_platform_info qs_s5k4e1_sensor_8660_info = { +#ifdef CONFIG_FB_MSM_MIPI_NOVATEK_CMD_QHD_PT + .mount_angle = 90 +#else + .mount_angle = 0 +#endif +}; + +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, + .sensor_platform_info = &qs_s5k4e1_sensor_8660_info, + .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 +}; + +static struct i2c_board_info msm_camera_dragon_boardinfo[] __initdata = { + #ifdef CONFIG_WEBCAM_OV9726 + { + I2C_BOARD_INFO("ov9726", 0x10), + }, + #endif + #ifdef CONFIG_VX6953 + { + I2C_BOARD_INFO("vx6953", 0x20), + }, + #endif +}; +#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, + .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, + .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, + .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, + .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, + .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, + .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 +/* 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 */ + pdata->pil_name = DSPS_PIL_FLUID_NAME; + msm_pil_dsps.dev.platform_data = DSPS_PIL_FLUID_NAME; + pdata->gpios = dsps_fluid_gpios; + pdata->gpios_num = ARRAY_SIZE(dsps_fluid_gpios); + } else { + pdata->pil_name = DSPS_PIL_GENERIC_NAME; + msm_pil_dsps.dev.platform_data = DSPS_PIL_GENERIC_NAME; + pdata->gpios = dsps_surf_gpios; + pdata->gpios_num = ARRAY_SIZE(dsps_surf_gpios); + } + + platform_device_register(&msm_dsps_device); +} +#endif /* CONFIG_MSM_DSPS */ + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_PRIM_BUF_SIZE \ + (roundup((1024 * 600 * 4), 4096) * 3) /* 4 bpp x 3 pages */ +#else +#define MSM_FB_PRIM_BUF_SIZE \ + (roundup((1024 * 600 * 4), 4096) * 2) /* 4 bpp x 2 pages */ +#endif + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +#define MSM_FB_EXT_BUF_SIZE \ + (roundup((1920 * 1080 * 2), 4096) * 1) /* 2 bpp x 1 page */ +#elif defined(CONFIG_FB_MSM_TVOUT) +#define MSM_FB_EXT_BUF_SIZE \ + (roundup((720 * 576 * 2), 4096) * 2) /* 2 bpp x 2 pages */ +#else +#define MSM_FB_EXT_BUFT_SIZE 0 +#endif + +/* Note: must be multiple of 4096 */ +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE + MSM_FB_EXT_BUF_SIZE + \ + MSM_FB_DSUB_PMEM_ADDER, 4096) + +#define MSM_PMEM_SF_SIZE 0x4000000 /* 64 Mbytes */ +#define MSM_HDMI_PRIM_PMEM_SF_SIZE 0x8000000 /* 128 Mbytes */ + +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY +unsigned char hdmi_is_primary = 1; +#else +unsigned char hdmi_is_primary; +#endif + +#ifdef CONFIG_FB_MSM_OVERLAY0_WRITEBACK +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE roundup((1376 * 768 * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY0_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY0_WRITEBACK */ + +#ifdef CONFIG_FB_MSM_OVERLAY1_WRITEBACK +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE roundup((1920 * 1088 * 3 * 2), 4096) +#else +#define MSM_FB_OVERLAY1_WRITEBACK_SIZE (0) +#endif /* CONFIG_FB_MSM_OVERLAY1_WRITEBACK */ + +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x3BC000 +#define MSM_PMEM_ADSP_SIZE 0x4200000 +#define MSM_PMEM_AUDIO_SIZE 0x4CF000 + +#define MSM_SMI_BASE 0x38000000 +#define MSM_SMI_SIZE 0x4000000 + +#define KERNEL_SMI_BASE (MSM_SMI_BASE) +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) +#define KERNEL_SMI_SIZE 0x000000 +#else +#define KERNEL_SMI_SIZE 0x600000 +#endif + +#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 + +#define MSM_ION_SF_SIZE 0x4000000 /* 64MB */ +#define MSM_ION_CAMERA_SIZE MSM_PMEM_ADSP_SIZE +#define MSM_ION_MM_FW_SIZE 0x200000 /* (2MB) */ +#define MSM_ION_MM_SIZE 0x3c00000 /* (60MB) Must be a multiple of 64K */ +#define MSM_ION_MFC_SIZE SZ_8K +#ifdef CONFIG_FB_MSM_OVERLAY1_WRITEBACK +#define MSM_ION_WB_SIZE 0xC00000 /* 12MB */ +#else +#define MSM_ION_WB_SIZE 0x600000 /* 6MB */ +#endif + +#define MSM_ION_QSECOM_SIZE 0x600000 /* (6MB) */ + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +#define MSM_ION_AUDIO_SIZE MSM_PMEM_AUDIO_SIZE +#define MSM_ION_HEAP_NUM 9 +#define MSM_HDMI_PRIM_ION_SF_SIZE MSM_HDMI_PRIM_PMEM_SF_SIZE +static unsigned msm_ion_sf_size = MSM_ION_SF_SIZE; +#else +#define MSM_ION_HEAP_NUM 1 +#endif + +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, + } +}; + +static void set_mdp_clocks_for_wuxga(void); + +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, + strnlen(LCDC_SAMSUNG_OLED_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; +#endif + } else { /*P3 and up use AUO panel */ +#ifdef CONFIG_FB_MSM_LCDC_AUO_WVGA + if (!strncmp(name, LCDC_AUO_PANEL_NAME, + strnlen(LCDC_AUO_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; +#endif + } +#ifdef CONFIG_FB_MSM_LCDC_NT35582_WVGA + } else if machine_is_msm8x60_dragon() { + if (!strncmp(name, LCDC_NT35582_PANEL_NAME, + strnlen(LCDC_NT35582_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; +#endif + } else { + if (!strncmp(name, LCDC_SAMSUNG_WSVGA_PANEL_NAME, + strnlen(LCDC_SAMSUNG_WSVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + +#if !defined(CONFIG_FB_MSM_LCDC_AUTO_DETECT) && \ + !defined(CONFIG_FB_MSM_MIPI_PANEL_AUTO_DETECT) && \ + !defined(CONFIG_FB_MSM_LCDC_MIPI_PANEL_AUTO_DETECT) + if (!strncmp(name, MIPI_VIDEO_TOSHIBA_WVGA_PANEL_NAME, + strnlen(MIPI_VIDEO_TOSHIBA_WVGA_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME, + strnlen(MIPI_VIDEO_NOVATEK_QHD_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + if (!strncmp(name, MIPI_CMD_NOVATEK_QHD_PANEL_NAME, + strnlen(MIPI_CMD_NOVATEK_QHD_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; +#endif + } + + if (!strncmp(name, HDMI_PANEL_NAME, + strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + if (hdmi_is_primary) + set_mdp_clocks_for_wuxga(); + return 0; + } + + if (!strncmp(name, TVOUT_PANEL_NAME, + strnlen(TVOUT_PANEL_NAME, + PANEL_NAME_MAX_LEN))) + return 0; + + 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, +}; + +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_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +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 /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#define PMEM_BUS_WIDTH(_bw) \ + { \ + .vectors = &(struct msm_bus_vectors){ \ + .src = MSM_BUS_MASTER_AMPSS_M0, \ + .dst = MSM_BUS_SLAVE_SMI, \ + .ib = (_bw), \ + .ab = 0, \ + }, \ + .num_paths = 1, \ + } + +static struct msm_bus_paths mem_smi_table[] = { + [0] = PMEM_BUS_WIDTH(0), /* Off */ + [1] = PMEM_BUS_WIDTH(1), /* On */ +}; + +static struct msm_bus_scale_pdata smi_client_pdata = { + .usecase = mem_smi_table, + .num_usecases = ARRAY_SIZE(mem_smi_table), + .name = "mem_smi", +}; + +int request_smi_region(void *data) +{ + int bus_id = (int) data; + + msm_bus_scale_client_update_request(bus_id, 1); + return 0; +} + +int release_smi_region(void *data) +{ + int bus_id = (int) data; + + msm_bus_scale_client_update_request(bus_id, 0); + return 0; +} + +void *setup_smi_region(void) +{ + return (void *)msm_bus_scale_register_client(&smi_client_pdata); +} +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +static struct android_pmem_platform_data android_pmem_smipool_pdata = { + .name = "pmem_smipool", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_SMI, + .request_region = request_smi_region, + .release_region = release_smi_region, + .setup_region = setup_smi_region, + .map_on_demand = 1, +}; +static struct platform_device android_pmem_smipool_device = { + .name = "android_pmem", + .id = 7, + .dev = { .platform_data = &android_pmem_smipool_pdata }, +}; +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + +#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_LCDC_NT35582_WVGA + +#define GPIO_NT35582_RESET 94 +#define GPIO_NT35582_BL_EN_HW_PIN 24 +#define GPIO_NT35582_BL_EN \ + PM8058_GPIO_PM_TO_SYS(GPIO_NT35582_BL_EN_HW_PIN - 1) + +static int lcdc_nt35582_pmic_gpio[] = {GPIO_NT35582_BL_EN }; + +static struct msm_panel_common_pdata lcdc_nt35582_panel_data = { + .gpio_num = lcdc_nt35582_pmic_gpio, +}; + +static struct platform_device lcdc_nt35582_panel_device = { + .name = LCDC_NT35582_PANEL_NAME, + .id = 0, + .dev = { + .platform_data = &lcdc_nt35582_panel_data, + } +}; + +static struct spi_board_info lcdc_nt35582_spi_board_info[] __initdata = { + { + .modalias = "lcdc_nt35582_spi", + .mode = SPI_MODE_0, + .bus_num = 0, + .chip_select = 0, + .max_speed_hz = 1100000, + } +}; +#endif + +#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_panel_platform_data novatek_pdata = { + .fpga_3d_config_addr = FPGA_3D_GPIO_CONFIG_ADDR, + .fpga_ctrl_mode = FPGA_EBI2_INTF, +}; + +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; + + if (hdmi_is_primary) + size = roundup((1920 * 1088 * 4 * 2), 4096); + else + 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)); + +} + +void __init msm8x60_set_display_params(char *prim_panel, char *ext_panel) +{ + if (strnlen(prim_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.prim_panel_name, prim_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.prim_panel_name %s\n", + msm_fb_pdata.prim_panel_name); + + if (!strncmp((char *)msm_fb_pdata.prim_panel_name, + HDMI_PANEL_NAME, strnlen(HDMI_PANEL_NAME, + PANEL_NAME_MAX_LEN))) { + pr_debug("HDMI is the primary display by" + " boot parameter\n"); + hdmi_is_primary = 1; + set_mdp_clocks_for_wuxga(); + } + } + if (strnlen(ext_panel, PANEL_NAME_MAX_LEN)) { + strlcpy(msm_fb_pdata.ext_panel_name, ext_panel, + PANEL_NAME_MAX_LEN); + pr_debug("msm_fb_pdata.ext_panel_name %s\n", + msm_fb_pdata.ext_panel_name); + } +} + +#if defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC) || \ + defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC_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()) { + properties_kobj = kobject_create_and_add("board_properties", + NULL); + if (properties_kobj); + 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; +} + +/* TODO: Put the regulator to LPM / HPM in suspend/resume*/ +static int cyttsp_platform_suspend(struct i2c_client *client) +{ + msleep(20); + + return CY_OK; +} + +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_DEEP_SLEEP_SEL | CY_USE_LOW_POWER_SEL, + .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, + .suspend = cyttsp_platform_suspend, + .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_DEEP_SLEEP_SEL | CY_USE_LOW_POWER_SEL, + .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, + .suspend = cyttsp_platform_suspend, + .init = cyttsp_platform_init, + .disable_ghost_det = true, +}; +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 */ + msleep(20); + + 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, + } +}; + +static struct regulator *vreg_tma340; + +static int tma340_power(int vreg_on) +{ + int rc = -EINVAL; + + if (!vreg_tma340) { + pr_err("%s: regulator 8901_l2 not found (%d)\n", + __func__, rc); + return rc; + } + + rc = vreg_on ? regulator_enable(vreg_tma340) : + regulator_disable(vreg_tma340); + if (rc < 0) + pr_err("%s: vreg 8901_l2 %s failed (%d)\n", + __func__, vreg_on ? "enable" : "disable", rc); + + /* wait for vregs to stabilize */ + msleep(100); + + return rc; +} + +static struct kobject *tma340_prop_kobj; + +static int tma340_dragon_dev_setup(bool enable) +{ + int rc; + + if (enable) { + vreg_tma340 = regulator_get(NULL, "8901_l2"); + if (IS_ERR(vreg_tma340)) { + pr_err("%s: regulator get of 8901_l2 failed (%ld)\n", + __func__, PTR_ERR(vreg_tma340)); + rc = PTR_ERR(vreg_tma340); + return rc; + } + + rc = regulator_set_voltage(vreg_tma340, 3300000, 3300000); + if (rc) { + pr_err("%s: regulator_set_voltage() = %d\n", + __func__, rc); + goto reg_put; + } + tma340_prop_kobj = kobject_create_and_add("board_properties", + NULL); + if (tma340_prop_kobj) { + ; + if (rc) { + kobject_put(tma340_prop_kobj); + pr_err("%s: failed to create board_properties\n", + __func__); + goto reg_put; + } + } + + } else { + /* put voltage sources */ + regulator_put(vreg_tma340); + /* destroy virtual keys */ + if (tma340_prop_kobj) { + kobject_put(tma340_prop_kobj); + } + } + return 0; +reg_put: + regulator_put(vreg_tma340); + return rc; +} + + +static struct cy8c_ts_platform_data cy8ctma340_dragon_pdata = { + .ts_name = "cy8ctma340", + .dis_min_x = 0, + .dis_max_x = 479, + .dis_min_y = 0, + .dis_max_y = 799, + .min_tid = 0, + .max_tid = 255, + .min_touch = 0, + .max_touch = 255, + .min_width = 0, + .max_width = 255, + .power_on = tma340_power, + .dev_setup = tma340_dragon_dev_setup, + .nfingers = 2, + .irq_gpio = TS_PEN_IRQ_GPIO, + .resout_gpio = -1, +}; + +static struct i2c_board_info cy8ctma340_dragon_board_info[] = { + { + I2C_BOARD_INFO("cy8ctma340", 0x24), + .platform_data = &cy8ctma340_dragon_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 + +#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), + REGULATOR_SUPPLY("cam_vana", "1-001a"), + REGULATOR_SUPPLY("cam_vana", "1-006c"), + REGULATOR_SUPPLY("cam_vana", "1-0078"), +}; +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), + REGULATOR_SUPPLY("cam_vdig", "1-001a"), + REGULATOR_SUPPLY("cam_vdig", "1-006c"), + REGULATOR_SUPPLY("cam_vdig", "1-0078"), +}; +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), + REGULATOR_SUPPLY("cam_vio", "1-001a"), + REGULATOR_SUPPLY("cam_vio", "1-006c"), + REGULATOR_SUPPLY("cam_vio", "1-0078"), +}; +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), +}; + +/* Pin control regulators */ +static struct regulator_consumer_supply vreg_consumers_PM8058_L8_PC[] = { + REGULATOR_SUPPLY("8058_l8_pc", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L20_PC[] = { + REGULATOR_SUPPLY("8058_l20_pc", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L21_PC[] = { + REGULATOR_SUPPLY("8058_l21_pc", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_S2_PC[] = { + REGULATOR_SUPPLY("8058_s2_pc", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L0_PC[] = { + REGULATOR_SUPPLY("8901_l0_pc", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_S4_PC[] = { + REGULATOR_SUPPLY("8901_s4_pc", 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, _force_mode, _sleep_set_force_mode, \ + _state, _sleep_selectable, _always_on) \ + { \ + .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), \ + }, \ + .id = RPM_VREG_ID_##_id, \ + .default_uV = _default_uV, \ + .peak_uA = _peak_uA, \ + .avg_uA = _avg_uA, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = _pin_ctrl, \ + .freq = RPM_VREG_FREQ_##_freq, \ + .pin_fn = _pin_fn, \ + .force_mode = _force_mode, \ + .sleep_set_force_mode = _sleep_set_force_mode, \ + .state = _state, \ + .sleep_selectable = _sleep_selectable, \ + } + +/* Pin control initialization */ +#define RPM_PC(_id, _always_on, _pin_fn, _pin_ctrl) \ + { \ + .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, \ + }, \ + .id = RPM_VREG_ID_##_id##_PC, \ + .pin_fn = RPM_VREG_PIN_FN_8660_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +/* + * 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_LDO(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _init_peak_uA) \ + 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, RPM_VREG_PIN_CTRL_NONE, NONE, \ + RPM_VREG_PIN_FN_8660_ENABLE, \ + RPM_VREG_FORCE_MODE_8660_NONE, \ + RPM_VREG_FORCE_MODE_8660_NONE, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on) + +#define RPM_SMPS(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV, \ + _init_peak_uA, _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, RPM_VREG_PIN_CTRL_NONE, _freq, \ + RPM_VREG_PIN_FN_8660_ENABLE, \ + RPM_VREG_FORCE_MODE_8660_NONE, \ + RPM_VREG_FORCE_MODE_8660_NONE, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on) + +#define RPM_VS(_id, _always_on, _pd, _sleep_selectable) \ + RPM_VREG_INIT(_id, 0, 0, REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE, \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE, 0, 0, \ + 1000, 1000, _pd, RPM_VREG_PIN_CTRL_NONE, NONE, \ + RPM_VREG_PIN_FN_8660_ENABLE, \ + RPM_VREG_FORCE_MODE_8660_NONE, \ + RPM_VREG_FORCE_MODE_8660_NONE, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on) + +#define RPM_NCP(_id, _always_on, _pd, _sleep_selectable, _min_uV, _max_uV) \ + RPM_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, 0, \ + _min_uV, 1000, 1000, _pd, RPM_VREG_PIN_CTRL_NONE, NONE, \ + RPM_VREG_PIN_FN_8660_ENABLE, \ + RPM_VREG_FORCE_MODE_8660_NONE, \ + RPM_VREG_FORCE_MODE_8660_NONE, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on) + +#define LDO50HMIN RPM_VREG_8660_LDO_50_HPM_MIN_LOAD +#define LDO150HMIN RPM_VREG_8660_LDO_150_HPM_MIN_LOAD +#define LDO300HMIN RPM_VREG_8660_LDO_300_HPM_MIN_LOAD +#define SMPS_HMIN RPM_VREG_8660_SMPS_HPM_MIN_LOAD +#define FTS_HMIN RPM_VREG_8660_FTSMPS_HPM_MIN_LOAD + +/* RPM early regulator constraints */ +static struct rpm_regulator_init_data rpm_regulator_early_init_data[] = { + /* ID a_on pd ss min_uV max_uV init_ip freq */ + RPM_SMPS(PM8058_S0, 0, 1, 1, 500000, 1325000, SMPS_HMIN, 1p60), + RPM_SMPS(PM8058_S1, 0, 1, 1, 500000, 1250000, SMPS_HMIN, 1p60), +}; + +/* RPM regulator constraints */ +static struct rpm_regulator_init_data rpm_regulator_init_data[] = { + /* ID a_on pd ss min_uV max_uV init_ip */ + RPM_LDO(PM8058_L0, 0, 1, 0, 1200000, 1200000, LDO150HMIN), + RPM_LDO(PM8058_L1, 0, 1, 0, 1200000, 1200000, LDO300HMIN), + RPM_LDO(PM8058_L2, 0, 1, 0, 1800000, 2600000, LDO300HMIN), + RPM_LDO(PM8058_L3, 0, 1, 0, 1800000, 1800000, LDO150HMIN), + RPM_LDO(PM8058_L4, 0, 1, 0, 2850000, 2850000, LDO50HMIN), + RPM_LDO(PM8058_L5, 0, 1, 0, 2850000, 2850000, LDO300HMIN), + RPM_LDO(PM8058_L6, 0, 1, 0, 3000000, 3600000, LDO50HMIN), + RPM_LDO(PM8058_L7, 0, 1, 0, 1800000, 1800000, LDO50HMIN), + RPM_LDO(PM8058_L8, 0, 1, 0, 2900000, 3050000, LDO300HMIN), + RPM_LDO(PM8058_L9, 0, 1, 0, 1800000, 1800000, LDO300HMIN), + RPM_LDO(PM8058_L10, 0, 1, 0, 2600000, 2600000, LDO300HMIN), + RPM_LDO(PM8058_L11, 0, 1, 0, 1500000, 1500000, LDO150HMIN), + RPM_LDO(PM8058_L12, 0, 1, 0, 2900000, 2900000, LDO150HMIN), + RPM_LDO(PM8058_L13, 0, 1, 0, 2050000, 2050000, LDO300HMIN), + RPM_LDO(PM8058_L14, 0, 0, 0, 2850000, 2850000, LDO300HMIN), + RPM_LDO(PM8058_L15, 0, 1, 0, 2850000, 2850000, LDO300HMIN), + RPM_LDO(PM8058_L16, 1, 1, 0, 1800000, 1800000, LDO300HMIN), + RPM_LDO(PM8058_L17, 0, 1, 0, 2600000, 2600000, LDO150HMIN), + RPM_LDO(PM8058_L18, 0, 1, 0, 2200000, 2200000, LDO150HMIN), + RPM_LDO(PM8058_L19, 0, 1, 0, 2500000, 2500000, LDO150HMIN), + RPM_LDO(PM8058_L20, 0, 1, 0, 1800000, 1800000, LDO150HMIN), + RPM_LDO(PM8058_L21, 1, 1, 0, 1200000, 1200000, LDO150HMIN), + RPM_LDO(PM8058_L22, 0, 1, 0, 1150000, 1150000, LDO300HMIN), + RPM_LDO(PM8058_L23, 0, 1, 0, 1200000, 1200000, LDO300HMIN), + RPM_LDO(PM8058_L24, 0, 1, 0, 1200000, 1200000, LDO150HMIN), + RPM_LDO(PM8058_L25, 0, 1, 0, 1200000, 1200000, LDO150HMIN), + + /* ID a_on pd ss min_uV max_uV init_ip freq */ + RPM_SMPS(PM8058_S2, 0, 1, 1, 1200000, 1400000, SMPS_HMIN, 1p60), + RPM_SMPS(PM8058_S3, 1, 1, 0, 1800000, 1800000, SMPS_HMIN, 1p60), + RPM_SMPS(PM8058_S4, 1, 1, 0, 2200000, 2200000, SMPS_HMIN, 1p60), + + /* ID a_on pd ss */ + RPM_VS(PM8058_LVS0, 0, 1, 0), + RPM_VS(PM8058_LVS1, 0, 1, 0), + + /* ID a_on pd ss min_uV max_uV */ + RPM_NCP(PM8058_NCP, 0, 1, 0, 1800000, 1800000), + + /* ID a_on pd ss min_uV max_uV init_ip */ + RPM_LDO(PM8901_L0, 0, 1, 0, 1200000, 1200000, LDO300HMIN), + RPM_LDO(PM8901_L1, 0, 1, 0, 3300000, 3300000, LDO300HMIN), + RPM_LDO(PM8901_L2, 0, 1, 0, 2850000, 3300000, LDO300HMIN), + RPM_LDO(PM8901_L3, 0, 1, 0, 3300000, 3300000, LDO300HMIN), + RPM_LDO(PM8901_L4, 0, 1, 0, 2600000, 2600000, LDO300HMIN), + RPM_LDO(PM8901_L5, 0, 1, 0, 2850000, 2850000, LDO300HMIN), + RPM_LDO(PM8901_L6, 0, 1, 0, 2200000, 2200000, LDO300HMIN), + + /* ID a_on pd ss min_uV max_uV init_ip freq */ + RPM_SMPS(PM8901_S2, 0, 1, 0, 1300000, 1300000, FTS_HMIN, 1p60), + RPM_SMPS(PM8901_S3, 0, 1, 0, 1100000, 1100000, FTS_HMIN, 1p60), + RPM_SMPS(PM8901_S4, 0, 1, 0, 1225000, 1225000, FTS_HMIN, 1p60), + + /* ID a_on pd ss */ + RPM_VS(PM8901_LVS0, 1, 1, 0), + RPM_VS(PM8901_LVS1, 0, 1, 0), + RPM_VS(PM8901_LVS2, 0, 1, 0), + RPM_VS(PM8901_LVS3, 0, 1, 0), + RPM_VS(PM8901_MVS0, 0, 1, 0), + + /* ID a_on pin_func pin_ctrl */ + RPM_PC(PM8058_L8, 0, SLEEP_B, RPM_VREG_PIN_CTRL_NONE), + RPM_PC(PM8058_L20, 0, SLEEP_B, RPM_VREG_PIN_CTRL_NONE), + RPM_PC(PM8058_L21, 1, SLEEP_B, RPM_VREG_PIN_CTRL_NONE), + RPM_PC(PM8058_S2, 0, ENABLE, RPM_VREG_PIN_CTRL_PM8058_A0), + RPM_PC(PM8901_L0, 0, ENABLE, RPM_VREG_PIN_CTRL_PM8901_A0), + RPM_PC(PM8901_S4, 0, ENABLE, RPM_VREG_PIN_CTRL_PM8901_A0), +}; + +static struct rpm_regulator_platform_data rpm_regulator_early_pdata = { + .init_data = rpm_regulator_early_init_data, + .num_regulators = ARRAY_SIZE(rpm_regulator_early_init_data), + .version = RPM_VREG_VERSION_8660, + .vreg_id_vdd_mem = RPM_VREG_ID_PM8058_S0, + .vreg_id_vdd_dig = RPM_VREG_ID_PM8058_S1, +}; + +static struct rpm_regulator_platform_data rpm_regulator_pdata = { + .init_data = rpm_regulator_init_data, + .num_regulators = ARRAY_SIZE(rpm_regulator_init_data), + .version = RPM_VREG_VERSION_8660, +}; + +static struct platform_device rpm_regulator_early_device = { + .name = "rpm-regulator", + .id = 0, + .dev = { + .platform_data = &rpm_regulator_early_pdata, + }, +}; + +static struct platform_device rpm_regulator_device = { + .name = "rpm-regulator", + .id = 1, + .dev = { + .platform_data = &rpm_regulator_pdata, + }, +}; + +static struct platform_device *early_regulators[] __initdata = { + &msm_device_saw_s0, + &msm_device_saw_s1, + &rpm_regulator_early_device, +}; + +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_ssbi3, +#endif +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_smipool_device, + &android_pmem_audio_device, +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +#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 +#ifndef CONFIG_MSM_CAMERA_V4L2 +#ifdef CONFIG_MT9E013 + &msm_camera_sensor_mt9e013, +#endif +#ifdef CONFIG_IMX074 + &msm_camera_sensor_imx074, +#endif +#ifdef CONFIG_VX6953 + &msm_camera_sensor_vx6953, +#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 +#endif +#ifdef CONFIG_MSM_GEMINI + &msm_gemini_device, +#endif +#ifdef CONFIG_MSM_VPE +#ifndef CONFIG_MSM_CAMERA_V4L2 + &msm_vpe_device, +#endif +#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 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 struct msm_rtb_platform_data msm_rtb_pdata = { + .size = SZ_1M, +}; + +static int __init msm_rtb_set_buffer_size(char *p) +{ + int s; + + s = memparse(p, NULL); + msm_rtb_pdata.size = ALIGN(s, SZ_4K); + return 0; +} +early_param("msm_rtb_size", msm_rtb_set_buffer_size); + + +static struct platform_device msm_rtb_device = { + .name = "msm_rtb", + .id = -1, + .dev = { + .platform_data = &msm_rtb_pdata, + }, +}; + +static void pmic8058_xoadc_mpp_config(void) +{ + int rc, i; + struct pm8xxx_mpp_init_info xoadc_mpps[] = { + PM8058_MPP_INIT(XOADC_MPP_3, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH5, + AOUT_CTRL_DISABLE), + PM8058_MPP_INIT(XOADC_MPP_5, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH9, + AOUT_CTRL_DISABLE), + PM8058_MPP_INIT(XOADC_MPP_7, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH6, + AOUT_CTRL_DISABLE), + PM8058_MPP_INIT(XOADC_MPP_8, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH8, + AOUT_CTRL_DISABLE), + PM8058_MPP_INIT(XOADC_MPP_10, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH7, + AOUT_CTRL_DISABLE), + PM8901_MPP_INIT(XOADC_MPP_4, D_OUTPUT, PM8901_MPP_DIG_LEVEL_S4, + DOUT_CTRL_LOW), + }; + + for (i = 0; i < ARRAY_SIZE(xoadc_mpps); i++) { + rc = pm8xxx_mpp_config(xoadc_mpps[i].mpp, + &xoadc_mpps[i].config); + if (rc) { + pr_err("%s: Config MPP %d of PM8058 failed\n", + __func__, xoadc_mpps[i].mpp); + } + } +} + +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 pm8058_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 = 0x0202, + .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 = { + .parent = &msm_charm_modem.dev, + .platform_data = &sdio_al_pdata, + }, +}; + +#endif /* CONFIG_MSM_SDIO_AL */ + +#define GPIO_VREG_ID_EXT_5V 0 + +static struct regulator_consumer_supply vreg_consumers_EXT_5V[] = { + REGULATOR_SUPPLY("ext_5v", NULL), + REGULATOR_SUPPLY("8901_mpp0", NULL), +}; + +#define GPIO_VREG_INIT(_id, _reg_name, _gpio_label, _gpio, _active_low) \ + [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, \ + .active_low = _active_low, \ + .gpio_label = _gpio_label, \ + .gpio = _gpio, \ + } + +/* GPIO regulator constraints */ +static struct gpio_regulator_platform_data msm_gpio_regulator_pdata[] = { + GPIO_VREG_INIT(EXT_5V, "ext_5v", "ext_5v_en", + PM8901_MPP_PM_TO_SYS(0), 0), +}; + +/* GPIO regulator */ +static struct platform_device msm8x60_8901_mpp_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8901_MPP_PM_TO_SYS(0), + .dev = { + .platform_data = + &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_5V], + }, +}; + +static void __init pm8901_vreg_mpp0_init(void) +{ + int rc; + + struct pm8xxx_mpp_init_info pm8901_vreg_mpp0 = { + .mpp = PM8901_MPP_PM_TO_SYS(0), + .config = { + .type = PM8XXX_MPP_TYPE_D_OUTPUT, + .level = PM8901_MPP_DIG_LEVEL_VPH, + }, + }; + + /* + * 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()) { + msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_5V].active_low = 1; + pm8901_vreg_mpp0.config.control = PM8XXX_MPP_DOUT_CTRL_HIGH; + } else { + msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_5V].active_low = 0; + pm8901_vreg_mpp0.config.control = PM8XXX_MPP_DOUT_CTRL_LOW; + } + + rc = pm8xxx_mpp_config(pm8901_vreg_mpp0.mpp, &pm8901_vreg_mpp0.config); + if (rc) + pr_err("%s: pm8xxx_mpp_config: rc=%d\n", __func__, rc); +} + +static struct platform_device *charm_devices[] __initdata = { + &msm_charm_modem, +#ifdef CONFIG_MSM_SDIO_AL + &msm_device_sdio_al, +#endif +}; + +#ifdef CONFIG_SND_SOC_MSM8660_APQ +static struct platform_device *dragon_alsa_devices[] __initdata = { + &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_lpa_pcm, +}; +#endif + +static struct platform_device *asoc_devices[] __initdata = { + &asoc_msm_pcm, + &asoc_msm_dai0, + &asoc_msm_dai1, +}; + +static struct platform_device *surf_devices[] __initdata = { + &msm_device_smd, + &msm_device_uart_dm12, + &msm_pil_q6v3, + &msm_pil_modem, + &msm_pil_tzapps, + &msm_pil_dsps, +#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_MSM_SSBI + &msm_device_ssbi_pmic1, + &msm_device_ssbi_pmic2, +#endif +#ifdef CONFIG_I2C_SSBI + &msm_device_ssbi3, +#endif +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + &isp1763_device, +#endif + +#if defined (CONFIG_MSM_8x60_VOIP) + &asoc_msm_mvs, + &asoc_mvs_dai0, + &asoc_mvs_dai1, +#endif + +#if defined(CONFIG_USB_MSM_72K) || defined(CONFIG_USB_EHCI_HCD) + &msm_device_otg, +#endif +#ifdef CONFIG_USB_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 +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_smipool_device, + &android_pmem_audio_device, +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +#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_NT35582_WVGA + &lcdc_nt35582_panel_device, +#endif +#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 +#ifndef CONFIG_MSM_CAMERA_V4L2 +#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 +#ifdef CONFIG_VX6953 + &msm_camera_sensor_vx6953, +#endif +#endif +#endif +#ifdef CONFIG_MSM_GEMINI + &msm_gemini_device, +#endif +#ifdef CONFIG_MSM_VPE +#ifndef CONFIG_MSM_CAMERA_V4L2 + &msm_vpe_device, +#endif +#endif + +#if defined(CONFIG_MSM_RPM_LOG) || defined(CONFIG_MSM_RPM_LOG_MODULE) + &msm8660_rpm_log_device, +#endif +#if defined(CONFIG_MSM_RPM_STATS_LOG) + &msm8660_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 + &rpm_regulator_device, + +#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 + + +#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, + &msm8660_rpm_device, +#ifdef CONFIG_ION_MSM + &ion_dev, +#endif + &msm8660_device_watchdog, + &msm_device_tz_log, + &msm_rtb_device, +}; + +#ifdef CONFIG_ION_MSM +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +static struct ion_cp_heap_pdata cp_mm_ion_pdata = { + .permission_type = IPT_TYPE_MM_CARVEOUT, + .align = SZ_64K, + .request_region = request_smi_region, + .release_region = release_smi_region, + .setup_region = setup_smi_region, + .iommu_map_all = 1, + .iommu_2x_map_domain = VIDEO_DOMAIN, +}; + +static struct ion_cp_heap_pdata cp_mfc_ion_pdata = { + .permission_type = IPT_TYPE_MFC_SHAREDMEM, + .align = PAGE_SIZE, + .request_region = request_smi_region, + .release_region = release_smi_region, + .setup_region = setup_smi_region, +}; + +static struct ion_cp_heap_pdata cp_wb_ion_pdata = { + .permission_type = IPT_TYPE_MDP_WRITEBACK, + .align = PAGE_SIZE, +}; + +static struct ion_co_heap_pdata fw_co_ion_pdata = { + .adjacent_mem_id = ION_CP_MM_HEAP_ID, + .align = SZ_128K, +}; + +static struct ion_co_heap_pdata co_ion_pdata = { + .adjacent_mem_id = INVALID_HEAP_ID, + .align = PAGE_SIZE, +}; +#endif + +/** + * These heaps are listed in the order they will be allocated. Due to + * video hardware restrictions and content protection the FW heap has to + * be allocated adjacent (below) the MM heap and the MFC heap has to be + * allocated after the MM heap to ensure MFC heap is not more than 256MB + * away from the base address of the FW heap. + * However, the order of FW heap and MM heap doesn't matter since these + * two heaps are taken care of by separate code to ensure they are adjacent + * to each other. + * Don't swap the order unless you know what you are doing! + */ +static struct ion_platform_data ion_pdata = { + .nr = MSM_ION_HEAP_NUM, + .heaps = { + { + .id = ION_SYSTEM_HEAP_ID, + .type = ION_HEAP_TYPE_SYSTEM, + .name = ION_VMALLOC_HEAP_NAME, + }, +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + { + .id = ION_CP_MM_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MM_HEAP_NAME, + .size = MSM_ION_MM_SIZE, + .memory_type = ION_SMI_TYPE, + .extra_data = (void *) &cp_mm_ion_pdata, + }, + { + .id = ION_MM_FIRMWARE_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_MM_FIRMWARE_HEAP_NAME, + .size = MSM_ION_MM_FW_SIZE, + .memory_type = ION_SMI_TYPE, + .extra_data = (void *) &fw_co_ion_pdata, + }, + { + .id = ION_CP_MFC_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_MFC_HEAP_NAME, + .size = MSM_ION_MFC_SIZE, + .memory_type = ION_SMI_TYPE, + .extra_data = (void *) &cp_mfc_ion_pdata, + }, + { + .id = ION_SF_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_SF_HEAP_NAME, + .size = MSM_ION_SF_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *)&co_ion_pdata, + }, + { + .id = ION_CAMERA_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_CAMERA_HEAP_NAME, + .size = MSM_ION_CAMERA_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = &co_ion_pdata, + }, + { + .id = ION_CP_WB_HEAP_ID, + .type = ION_HEAP_TYPE_CP, + .name = ION_WB_HEAP_NAME, + .size = MSM_ION_WB_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &cp_wb_ion_pdata, + }, + { + .id = ION_QSECOM_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_QSECOM_HEAP_NAME, + .size = MSM_ION_QSECOM_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *) &co_ion_pdata, + }, + { + .id = ION_AUDIO_HEAP_ID, + .type = ION_HEAP_TYPE_CARVEOUT, + .name = ION_AUDIO_HEAP_NAME, + .size = MSM_ION_AUDIO_SIZE, + .memory_type = ION_EBI_TYPE, + .extra_data = (void *)&co_ion_pdata, + }, +#endif + } +}; + +static struct platform_device ion_dev = { + .name = "ion-msm", + .id = 1, + .dev = { .platform_data = &ion_pdata }, +}; +#endif + + +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 reserve_ion_memory(void) +{ +#if defined(CONFIG_ION_MSM) && defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + unsigned int i; + + if (hdmi_is_primary) { + msm_ion_sf_size = MSM_HDMI_PRIM_ION_SF_SIZE; + for (i = 0; i < ion_pdata.nr; i++) { + if (ion_pdata.heaps[i].id == ION_SF_HEAP_ID) { + ion_pdata.heaps[i].size = msm_ion_sf_size; + pr_debug("msm_ion_sf_size 0x%x\n", + msm_ion_sf_size); + break; + } + } + } + + /* Verify size of heap is a multiple of 64K */ + for (i = 0; i < ion_pdata.nr; i++) { + struct ion_platform_heap *heap = &(ion_pdata.heaps[i]); + + if (heap->extra_data && heap->type == ION_HEAP_TYPE_CP) { + int map_all = ((struct ion_cp_heap_pdata *) + heap->extra_data)->iommu_map_all; + + if (map_all && (heap->size & (SZ_64K-1))) { + heap->size = ALIGN(heap->size, SZ_64K); + pr_err("Heap %s size is not a multiple of 64K. Adjusting size to %x\n", + heap->name, heap->size); + + } + } + } + + msm8x60_reserve_table[MEMTYPE_EBI1].size += msm_ion_sf_size; + msm8x60_reserve_table[MEMTYPE_SMI].size += MSM_ION_MM_FW_SIZE; + msm8x60_reserve_table[MEMTYPE_SMI].size += MSM_ION_MM_SIZE; + msm8x60_reserve_table[MEMTYPE_SMI].size += MSM_ION_MFC_SIZE; + msm8x60_reserve_table[MEMTYPE_EBI1].size += MSM_ION_CAMERA_SIZE; + msm8x60_reserve_table[MEMTYPE_EBI1].size += MSM_ION_WB_SIZE; + msm8x60_reserve_table[MEMTYPE_EBI1].size += MSM_ION_AUDIO_SIZE; + msm8x60_reserve_table[MEMTYPE_EBI1].size += MSM_ION_QSECOM_SIZE; +#endif +} + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_smipool_pdata.size = MSM_PMEM_SMIPOOL_SIZE; + + if (hdmi_is_primary) + pmem_sf_size = MSM_HDMI_PRIM_PMEM_SF_SIZE; + android_pmem_pdata.size = pmem_sf_size; + android_pmem_audio_pdata.size = MSM_PMEM_AUDIO_SIZE; +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ +} + +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm8x60_reserve_table[p->memory_type].size += p->size; +} +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ +#endif /*CONFIG_ANDROID_PMEM*/ + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_smipool_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); +#endif /*CONFIG_MSM_MULTIMEDIA_USE_ION*/ + msm8x60_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif /*CONFIG_ANDROID_PMEM*/ +} + +static void __init reserve_mdp_memory(void); + +static void __init reserve_rtb_memory(void) +{ +#if defined(CONFIG_MSM_RTB) + msm8x60_reserve_table[MEMTYPE_EBI1].size += msm_rtb_pdata.size; +#endif +} + +static void __init msm8x60_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); + reserve_ion_memory(); + reserve_mdp_memory(); + reserve_rtb_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 char prim_panel_name[PANEL_NAME_MAX_LEN]; +static char ext_panel_name[PANEL_NAME_MAX_LEN]; +static int __init prim_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(prim_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("prim_display", prim_display_setup); + +static int __init ext_display_setup(char *param) +{ + if (strnlen(param, PANEL_NAME_MAX_LEN)) + strlcpy(ext_panel_name, param, PANEL_NAME_MAX_LEN); + return 0; +} +early_param("ext_display", ext_display_setup); + +static void __init msm8x60_reserve(void) +{ + msm8x60_set_display_params(prim_panel_name, ext_panel_name); + reserve_info = &msm8x60_reserve_info; + msm_reserve(); +} + +#define EXT_CHG_VALID_MPP 10 +#define EXT_CHG_VALID_MPP_2 11 + +static struct pm8xxx_mpp_init_info isl_mpp[] = { + PM8058_MPP_INIT(EXT_CHG_VALID_MPP, D_INPUT, + PM8058_MPP_DIG_LEVEL_S3, DIN_TO_INT), + PM8058_MPP_INIT(EXT_CHG_VALID_MPP_2, D_BI_DIR, + PM8058_MPP_DIG_LEVEL_S3, BI_PULLUP_10KOHM), +}; + +#ifdef CONFIG_ISL9519_CHARGER +static int isl_detection_setup(void) +{ + int ret = 0, i; + + for (i = 0; i < ARRAY_SIZE(isl_mpp); i++) { + ret = pm8xxx_mpp_config(isl_mpp[i].mpp, + &isl_mpp[i].config); + if (ret) { + pr_err("%s: Config MPP %d of PM8058 failed\n", + __func__, isl_mpp[i].mpp); + return ret; + } + } + + 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_IRQ_BASE + PM8058_CBLPWR_IRQ, + .platform_data = &isl_data, + }, +}; +#endif + +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) +static int smb137b_detection_setup(void) +{ + int ret = 0, i; + + for (i = 0; i < ARRAY_SIZE(isl_mpp); i++) { + ret = pm8xxx_mpp_config(isl_mpp[i].mpp, + &isl_mpp[i].config); + if (ret) { + pr_err("%s: Config MPP %d of PM8058 failed\n", + __func__, isl_mpp[i].mpp); + return ret; + } + } + + 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_IRQ_BASE + PM8058_CBLPWR_IRQ, + .platform_data = &smb137b_data, + }, +}; +#endif + +#ifdef CONFIG_PMIC8058 +#define PMIC_GPIO_SDC3_DET 22 +#define PMIC_GPIO_TOUCH_DISC_INTR 5 + +static int pm8058_gpios_init(void) +{ + int i; + int rc; + struct pm8058_gpio_cfg { + int gpio; + struct pm_gpio cfg; + }; + + struct pm8058_gpio_cfg gpio_cfgs[] = { + { /* FFA ethernet */ + PM8058_GPIO_PM_TO_SYS(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 + { + PM8058_GPIO_PM_TO_SYS(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 */ + PM8058_GPIO_PM_TO_SYS(UI_INT1_N), + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* docking gpio expander */ + PM8058_GPIO_PM_TO_SYS(UI_INT2_N), + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* FHA/keypad gpio expanders */ + PM8058_GPIO_PM_TO_SYS(UI_INT3_N), + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* Timpani Reset */ + PM8058_GPIO_PM_TO_SYS(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 */ + PM8058_GPIO_PM_TO_SYS(36), + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + } + }, + }; + +#if defined(CONFIG_TOUCHDISC_VTD518_SHINETSU) || \ + defined(CONFIG_TOUCHDISC_VTD518_SHINETSU_MODULE) + struct pm_gpio touchdisc_intr_gpio_cfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + }; +#endif + +#if defined(CONFIG_HAPTIC_ISA1200) || \ + defined(CONFIG_HAPTIC_ISA1200_MODULE) + struct pm_gpio en_hap_gpio_cfg = { + .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 = { + PM8058_GPIO_PM_TO_SYS(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 = { + PM8058_GPIO_PM_TO_SYS(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 +#ifdef CONFIG_FB_MSM_LCDC_NT35582_WVGA + struct pm8058_gpio_cfg pmic_lcdc_nt35582_gpio_cfg = { + PM8058_GPIO_PM_TO_SYS(GPIO_NT35582_BL_EN_HW_PIN - 1), + { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_UP_30, + /* 2.9V PM_GPIO_VIN_L2, which gives 2.6V */ + .vin_sel = PM8058_GPIO_VIN_L5, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + } + }; +#endif +#if defined(CONFIG_HAPTIC_ISA1200) || \ + defined(CONFIG_HAPTIC_ISA1200_MODULE) + if (machine_is_msm8x60_fluid()) { + rc = pm8xxx_gpio_config( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_HAP_ENABLE), + &en_hap_gpio_cfg); + if (rc < 0) { + pr_err("%s: pmic haptics gpio config failed\n", + __func__); + } + rc = pm8xxx_gpio_config( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_HAP_LDO_ENABLE), + &en_hap_gpio_cfg); + if (rc < 0) { + pr_err("%s: pmic haptics ldo gpio config failed\n", + __func__); + } + + } +#endif + +#if defined(CONFIG_TOUCHDISC_VTD518_SHINETSU) || \ + defined(CONFIG_TOUCHDISC_VTD518_SHINETSU_MODULE) + if (machine_is_msm8x60_ffa() || machine_is_msm8x60_surf() || + machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { + rc = pm8xxx_gpio_config( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_TOUCH_DISC_INTR), + &touchdisc_intr_gpio_cfg); + if (rc < 0) { + pr_err("%s: Touchdisc interrupt gpio config failed\n", + __func__); + } + } +#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_dragon() || + machine_is_msm8x60_fusn_ffa()) { + rc = pm8xxx_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 + +#ifdef CONFIG_FB_MSM_LCDC_NT35582_WVGA + if (machine_is_msm8x60_dragon()) { + rc = pm8xxx_gpio_config(pmic_lcdc_nt35582_gpio_cfg.gpio, + &pmic_lcdc_nt35582_gpio_cfg.cfg); + if (rc < 0) { + pr_err("%s pmic 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 = pm8xxx_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 = pm8xxx_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 const unsigned int dragon_keymap[] = { + KEY(0, 0, KEY_MENU), + KEY(0, 2, KEY_1), + KEY(0, 3, KEY_4), + KEY(0, 4, KEY_7), + + KEY(1, 0, KEY_UP), + KEY(1, 1, KEY_LEFT), + KEY(1, 2, KEY_DOWN), + KEY(1, 3, KEY_5), + KEY(1, 4, KEY_8), + + KEY(2, 0, KEY_HOME), + KEY(2, 1, KEY_REPLY), + KEY(2, 2, KEY_2), + KEY(2, 3, KEY_6), + KEY(2, 4, KEY_0), + + KEY(3, 0, KEY_VOLUMEUP), + KEY(3, 1, KEY_RIGHT), + KEY(3, 2, KEY_3), + KEY(3, 3, KEY_9), + KEY(3, 4, KEY_SWITCHVIDEOMODE), + + KEY(4, 0, KEY_VOLUMEDOWN), + KEY(4, 1, KEY_BACK), + KEY(4, 2, KEY_CAMERA), + KEY(4, 3, KEY_KBDILLUMTOGGLE), +}; + +static struct matrix_keymap_data ffa_keymap_data = { + .keymap_size = ARRAY_SIZE(ffa_keymap), + .keymap = ffa_keymap, +}; + +static struct pm8xxx_keypad_platform_data ffa_keypad_data = { + .input_name = "ffa-keypad", + .input_phys_device = "ffa-keypad/input0", + .num_rows = 6, + .num_cols = 5, + .rows_gpio_start = PM8058_GPIO_PM_TO_SYS(8), + .cols_gpio_start = PM8058_GPIO_PM_TO_SYS(0), + .debounce_ms = 15, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &ffa_keymap_data, +}; + +static struct matrix_keymap_data dragon_keymap_data = { + .keymap_size = ARRAY_SIZE(dragon_keymap), + .keymap = dragon_keymap, +}; + +static struct pm8xxx_keypad_platform_data dragon_keypad_data = { + .input_name = "dragon-keypad", + .input_phys_device = "dragon-keypad/input0", + .num_rows = 6, + .num_cols = 5, + .rows_gpio_start = PM8058_GPIO_PM_TO_SYS(8), + .cols_gpio_start = PM8058_GPIO_PM_TO_SYS(0), + .debounce_ms = 15, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &dragon_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), /* 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 pm8xxx_keypad_platform_data fluid_keypad_data = { + .input_name = "fluid-keypad", + .input_phys_device = "fluid-keypad/input0", + .num_rows = 6, + .num_cols = 5, + .rows_gpio_start = PM8058_GPIO_PM_TO_SYS(8), + .cols_gpio_start = PM8058_GPIO_PM_TO_SYS(0), + .debounce_ms = 15, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &fluid_keymap_data, +}; + +static struct pm8xxx_vibrator_platform_data pm8058_vib_pdata = { + .initial_vibrate_ms = 500, + .level_mV = 3000, + .max_timeout_ms = 15000, +}; + +static struct pm8xxx_rtc_platform_data pm8058_rtc_pdata = { + .rtc_write_enable = false, + .rtc_alarm_powerup = false, +}; + +static struct pm8xxx_pwrkey_platform_data pm8058_pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 15625, + .wakeup = 1, +}; + +#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 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; + } + } + } +} + + +static int pm8058_pwm_config(struct pwm_device *pwm, int ch, int on) +{ + struct pm_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 = PM8058_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 = pm8xxx_gpio_config(PM8058_GPIO_PM_TO_SYS(id - 1), + &pwm_gpio_config); + if (rc) + pr_err("%s: pm8xxx_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 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_dragon_leds[] = { + [0] = { + /* RED */ + .name = "led_drv0", + .max_brightness = 15, + .id = PMIC8058_ID_LED_0, + },/* 300 mA flash led0 drv sink */ + [1] = { + /* Yellow */ + .name = "led_drv1", + .max_brightness = 15, + .id = PMIC8058_ID_LED_1, + },/* 300 mA flash led0 drv sink */ + [2] = { + /* Green */ + .name = "led_drv2", + .max_brightness = 15, + .id = PMIC8058_ID_LED_2, + },/* 300 mA flash led0 drv sink */ + [3] = { + .name = "led_psensor", + .max_brightness = 15, + .id = PMIC8058_ID_LED_KB_LIGHT, + },/* 300 mA flash led0 drv sink */ +}; + +static struct pmic8058_leds_platform_data pm8058_dragon_leds_data = { + .num_leds = ARRAY_SIZE(pmic8058_dragon_leds), + .leds = pmic8058_dragon_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 pmic8058_charger_data pmic8058_charger_dragon = { + .charger_data_valid = true, + .max_source_current = 1800, + .charger_type = CHG_TYPE_AC, +}; + +static struct pmic8058_charger_data pmic8058_charger_ffa_surf = { + .charger_data_valid = false, +}; + +static struct pm8xxx_misc_platform_data pm8058_misc_pdata = { + .priority = 0, +}; + +static struct pm8xxx_irq_platform_data pm8058_irq_pdata = { + .irq_base = PM8058_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(PM8058_GPIO_INT), + .irq_trigger_flag = IRQF_TRIGGER_LOW, +}; + +static struct pm8xxx_gpio_platform_data pm8058_gpio_pdata = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(0), +}; + +static struct pm8xxx_mpp_platform_data pm8058_mpp_pdata = { + .mpp_base = PM8058_MPP_PM_TO_SYS(0), +}; + +static struct pm8058_platform_data pm8058_platform_data = { + .irq_pdata = &pm8058_irq_pdata, + .gpio_pdata = &pm8058_gpio_pdata, + .mpp_pdata = &pm8058_mpp_pdata, + .rtc_pdata = &pm8058_rtc_pdata, + .pwrkey_pdata = &pm8058_pwrkey_pdata, + .othc0_pdata = &othc_config_pdata_0, + .othc1_pdata = &othc_config_pdata_1, + .othc2_pdata = &othc_config_pdata_2, + .pwm_pdata = &pm8058_pwm_data, + .misc_pdata = &pm8058_misc_pdata, +#ifdef CONFIG_SENSORS_MSM_ADC + .xoadc_pdata = &pm8058_xoadc_pdata, +#endif +}; + +#ifdef CONFIG_MSM_SSBI +static struct msm_ssbi_platform_data msm8x60_ssbi_pm8058_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8058-core", + .platform_data = &pm8058_platform_data, + }, +}; +#endif +#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_SND_SOC_WM8903 +static struct wm8903_platform_data wm8903_pdata = { + .gpio_cfg[2] = 0x3A8, +}; + +#define WM8903_I2C_SLAVE_ADDR 0x34 +static struct i2c_board_info wm8903_codec_i2c_info[] = { + { + I2C_BOARD_INFO("wm8903", WM8903_I2C_SLAVE_ADDR >> 1), + .platform_data = &wm8903_pdata, + }, +}; +#endif +#ifdef CONFIG_PMIC8901 + +#define PM8901_GPIO_INT 91 +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +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) \ + { \ + .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), \ + }, \ + .id = PM8901_VREG_ID_##_id, \ + } + +#define PM8901_VREG_INIT_VS(_id) \ + PM8901_VREG_INIT(_id, 0, 0, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_STATUS, 0, 0) + +static struct pm8901_vreg_pdata pm8901_vreg_init[] = { + PM8901_VREG_INIT_VS(USB_OTG), + PM8901_VREG_INIT_VS(HDMI_MVS), +}; + +static struct pm8xxx_misc_platform_data pm8901_misc_pdata = { + .priority = 1, +}; + +static struct pm8xxx_irq_platform_data pm8901_irq_pdata = { + .irq_base = PM8901_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(PM8901_GPIO_INT), + .irq_trigger_flag = IRQF_TRIGGER_LOW, +}; + +static struct pm8xxx_mpp_platform_data pm8901_mpp_pdata = { + .mpp_base = PM8901_MPP_PM_TO_SYS(0), +}; + +static struct pm8901_platform_data pm8901_platform_data = { + .irq_pdata = &pm8901_irq_pdata, + .mpp_pdata = &pm8901_mpp_pdata, + .regulator_pdatas = pm8901_vreg_init, + .num_regulators = ARRAY_SIZE(pm8901_vreg_init), + .misc_pdata = &pm8901_misc_pdata, +}; + +static struct msm_ssbi_platform_data msm8x60_ssbi_pm8901_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8901-core", + .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; +static int msm_bahama_sys_rst = GPIO_MS_SYS_RESET_N; + +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 int msm_bahama_setup_power_enable; +static unsigned int msm_bahama_setup_power(void) +{ + int rc = 0; + const char *msm_bahama_regulator = "8058_s3"; + + if (machine_is_msm8x60_dragon()) + msm_bahama_sys_rst = GPIO_CDC_RST_N; + + 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); + return rc; + } + + rc = regulator_set_voltage(vreg_bahama, 1800000, 1800000); + if (rc) { + pr_err("%s: regulator_set_voltage %s = %d\n", __func__, + msm_bahama_regulator, rc); + goto unget; + } + + rc = regulator_enable(vreg_bahama); + if (rc) { + pr_err("%s: regulator_enable %s = %d\n", __func__, + msm_bahama_regulator, rc); + goto unget; + } + + rc = gpio_request(msm_bahama_sys_rst, "bahama sys_rst_n"); + if (rc) { + pr_err("%s: gpio_request %d = %d\n", __func__, + msm_bahama_sys_rst, rc); + goto unenable; + } + + gpio_direction_output(msm_bahama_sys_rst, 0); + usleep_range(1000, 1050); + gpio_set_value_cansleep(msm_bahama_sys_rst, 1); + usleep_range(1000, 1050); + msm_bahama_setup_power_enable = 1; + return rc; + +unenable: + regulator_disable(vreg_bahama); +unget: + regulator_put(vreg_bahama); + return rc; +}; + +static unsigned int msm_bahama_shutdown_power(int value) +{ + if (msm_bahama_setup_power_enable) { + gpio_set_value_cansleep(msm_bahama_sys_rst, 0); + gpio_free(msm_bahama_sys_rst); + regulator_disable(vreg_bahama); + regulator_put(vreg_bahama); + msm_bahama_setup_power_enable = 0; + } + + 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 pm_gpio cfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM8058_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 = pm8xxx_gpio_config(PM8058_GPIO_PM_TO_SYS(FM_GPIO), &cfg); + if (rc) { + printk(KERN_ERR "%s: return val of pm8xxx_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) +#define I2C_DRAGON (1 << 5) + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; + +static struct i2c_registry msm8x60_i2c_devices[] __initdata = { +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + { + I2C_SURF | I2C_FFA | I2C_DRAGON, + MSM_GSBI8_QUP_I2C_BUS_ID, + core_expander_i2c_info, + ARRAY_SIZE(core_expander_i2c_info), + }, + { + I2C_SURF | I2C_FFA | I2C_DRAGON, + 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 | I2C_DRAGON, + 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 | I2C_DRAGON, + 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), + }, + { + I2C_DRAGON, + MSM_GSBI3_QUP_I2C_BUS_ID, + cy8ctma340_dragon_board_info, + ARRAY_SIZE(cy8ctma340_dragon_board_info), + }, +#if defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC) || \ + defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC_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 +#ifndef CONFIG_MSM_CAMERA_V4L2 + { + I2C_SURF | I2C_FFA | I2C_FLUID , + MSM_GSBI4_QUP_I2C_BUS_ID, + msm_camera_boardinfo, + ARRAY_SIZE(msm_camera_boardinfo), + }, + { + I2C_DRAGON, + MSM_GSBI4_QUP_I2C_BUS_ID, + msm_camera_dragon_boardinfo, + ARRAY_SIZE(msm_camera_dragon_boardinfo), + }, +#endif +#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 | I2C_DRAGON, + 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 +#if defined(CONFIG_SND_SOC_WM8903) || defined(CONFIG_SND_SOC_WM8903_MODULE) + { + I2C_DRAGON, + MSM_GSBI8_QUP_I2C_BUS_ID, + wm8903_codec_i2c_info, + ARRAY_SIZE(wm8903_codec_i2c_info), + }, +#endif +}; +#endif /* CONFIG_I2C */ + +static void __init 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() || + machine_is_msm8x60_dragon()) + 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 +#endif +} + +static void __init register_i2c_devices(void) +{ +#ifdef CONFIG_I2C + u8 mach_mask = 0; + int i; +#ifdef CONFIG_MSM_CAMERA_V4L2 + struct i2c_registry msm8x60_camera_i2c_devices = { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_GSBI4_QUP_I2C_BUS_ID, + msm8x60_camera_board_info.board_info, + msm8x60_camera_board_info.num_i2c_board_info, + }; +#endif + + /* 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 if (machine_is_msm8x60_dragon()) + mach_mask = I2C_DRAGON; + 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); + } +#ifdef CONFIG_MSM_CAMERA_V4L2 + if (msm8x60_camera_i2c_devices.machs & mach_mask) + i2c_register_board_info(msm8x60_camera_i2c_devices.bus, + msm8x60_camera_i2c_devices.info, + msm8x60_camera_i2c_devices.len); +#endif +#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_ssbi3.dev.platform_data = &msm_ssbi3_pdata; +#endif + +#ifdef CONFIG_MSM_SSBI + msm_device_ssbi_pmic1.dev.platform_data = + &msm8x60_ssbi_pm8058_pdata; + msm_device_ssbi_pmic2.dev.platform_data = + &msm8x60_ssbi_pm8901_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_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_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(); + + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); +} + +/* + * 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; + struct clk *mem_clk = clk_get_sys("msm_ebi2", "mem_clk"); + + if (IS_ERR(mem_clk)) { + pr_err("%s: clk_get_sys(%s,%s), failed", __func__, + "msm_ebi2", "mem_clk"); + return; + } + clk_prepare_enable(mem_clk); + clk_put(mem_clk); + + 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() || + machine_is_msm8x60_dragon()) + 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() || machine_is_msm8x60_dragon()) { + 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 has 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 require 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 has 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 +#endif + +#define MSM_MPM_PIN_SDC3_DAT1 21 +#define MSM_MPM_PIN_SDC4_DAT1 23 + +#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, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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_MSM_SDIO_AL + .is_sdio_al_client = 1, +#endif + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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, + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC3_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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, + .mpm_sdiowakeup_int = MSM_MPM_PIN_SDC4_DAT1, + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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_MSM_SDIO_AL + .is_sdio_al_client = 1, +#endif + .msm_bus_voting_data = &sps_to_ddr_bus_voting_data, +}; +#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()) { + msm8x60_sdc2_data.sdiowakeup_irq = gpio_to_irq(144); + msm_sdcc_setup_gpio(2, 1); + 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()) { + msm8x60_sdc5_data.sdiowakeup_irq = gpio_to_irq(99); + msm_sdcc_setup_gpio(5, 1); + 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 +#if defined(CONFIG_FB_MSM_LCDC_NT35582_WVGA) + else if (machine_is_msm8x60_dragon()) { + static struct regulator *dragon_reg; + static struct regulator *dragon_reg2; + + if (on) { + _GET_REGULATOR(dragon_reg, "8901_l2"); + if (!dragon_reg) + return; + _GET_REGULATOR(dragon_reg2, "8058_l16"); + if (!dragon_reg2) { + regulator_put(dragon_reg); + dragon_reg = NULL; + return; + } + + rc = gpio_request(GPIO_NT35582_BL_EN, "lcdc_bl_en"); + if (rc) { + pr_err("%s: gpio %d request failed with rc=%d\n", + __func__, GPIO_NT35582_BL_EN, rc); + regulator_put(dragon_reg); + regulator_put(dragon_reg2); + dragon_reg = NULL; + dragon_reg2 = NULL; + return; + } + + if (gpio_tlmm_config(GPIO_CFG(GPIO_NT35582_RESET, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, + GPIO_CFG_16MA), GPIO_CFG_ENABLE)) { + pr_err("%s: config gpio '%d' failed!\n", + __func__, GPIO_NT35582_RESET); + gpio_free(GPIO_NT35582_BL_EN); + regulator_put(dragon_reg); + regulator_put(dragon_reg2); + dragon_reg = NULL; + dragon_reg2 = NULL; + return; + } + + rc = gpio_request(GPIO_NT35582_RESET, "lcdc_reset"); + if (rc) { + pr_err("%s: unable to request gpio %d (rc=%d)\n", + __func__, GPIO_NT35582_RESET, rc); + gpio_free(GPIO_NT35582_BL_EN); + regulator_put(dragon_reg); + regulator_put(dragon_reg2); + dragon_reg = NULL; + dragon_reg2 = NULL; + return; + } + + regulator_set_voltage(dragon_reg, 3300000, 3300000); + regulator_set_voltage(dragon_reg2, 1800000, 1800000); + regulator_enable(dragon_reg); + regulator_enable(dragon_reg2); + msleep(20); + + gpio_set_value_cansleep(GPIO_NT35582_RESET, 1); + msleep(20); + gpio_set_value_cansleep(GPIO_NT35582_RESET, 0); + msleep(20); + gpio_set_value_cansleep(GPIO_NT35582_RESET, 1); + msleep(50); + + gpio_set_value_cansleep(GPIO_NT35582_BL_EN, 1); + + display_power_on = 1; + } else if ((dragon_reg != NULL) && (dragon_reg2 != NULL)) { + gpio_free(GPIO_NT35582_RESET); + gpio_free(GPIO_NT35582_BL_EN); + regulator_disable(dragon_reg2); + regulator_disable(dragon_reg); + regulator_put(dragon_reg2); + regulator_put(dragon_reg); + display_power_on = 0; + dragon_reg = NULL; + dragon_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 + +static struct msm_bus_vectors rotator_init_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors rotator_ui_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (1024 * 600 * 4 * 2 * 60), + .ib = (1024 * 600 * 4 * 2 * 60 * 1.5), + }, +}; + +static struct msm_bus_vectors rotator_vga_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_SMI, + .ab = (640 * 480 * 2 * 2 * 30), + .ib = (640 * 480 * 2 * 2 * 30 * 1.5), + }, + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (640 * 480 * 2 * 2 * 30), + .ib = (640 * 480 * 2 * 2 * 30 * 1.5), + }, +}; + +static struct msm_bus_vectors rotator_720p_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_SMI, + .ab = (1280 * 736 * 2 * 2 * 30), + .ib = (1280 * 736 * 2 * 2 * 30 * 1.5), + }, + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (1280 * 736 * 2 * 2 * 30), + .ib = (1280 * 736 * 2 * 2 * 30 * 1.5), + }, +}; + +static struct msm_bus_vectors rotator_1080p_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_SMI, + .ab = (1920 * 1088 * 2 * 2 * 30), + .ib = (1920 * 1088 * 2 * 2 * 30 * 1.5), + }, + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (1920 * 1088 * 2 * 2 * 30), + .ib = (1920 * 1088 * 2 * 2 * 30 * 1.5), + }, +}; + +static struct msm_bus_paths rotator_bus_scale_usecases[] = { + { + ARRAY_SIZE(rotator_init_vectors), + rotator_init_vectors, + }, + { + ARRAY_SIZE(rotator_ui_vectors), + rotator_ui_vectors, + }, + { + ARRAY_SIZE(rotator_vga_vectors), + rotator_vga_vectors, + }, + { + ARRAY_SIZE(rotator_720p_vectors), + rotator_720p_vectors, + }, + { + ARRAY_SIZE(rotator_1080p_vectors), + rotator_1080p_vectors, + }, +}; + +struct msm_bus_scale_pdata rotator_bus_scale_pdata = { + rotator_bus_scale_usecases, + ARRAY_SIZE(rotator_bus_scale_usecases), + .name = "rotator", +}; + +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, + }, +}; + +#ifdef CONFIG_FB_MSM_LCDC_DSUB +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_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 = 550000000 * 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_vectors dtv_bus_hdmi_prim_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 = 2000000000, + .ib = 2000000000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2000000000, + .ib = 2000000000, + }, +}; + +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, +}; + +static struct msm_bus_paths dtv_hdmi_prim_bus_scale_usecases[] = { + { + ARRAY_SIZE(dtv_bus_init_vectors), + dtv_bus_init_vectors, + }, + { + ARRAY_SIZE(dtv_bus_hdmi_prim_vectors), + dtv_bus_hdmi_prim_vectors, + }, +}; + +static struct msm_bus_scale_pdata dtv_hdmi_prim_bus_scale_pdata = { + dtv_hdmi_prim_bus_scale_usecases, + ARRAY_SIZE(dtv_hdmi_prim_bus_scale_usecases), + .name = "dtv", +}; + +static struct lcdc_platform_data dtv_hdmi_prim_pdata = { + .bus_scale_table = &dtv_hdmi_prim_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; + + if (mipi_dsi_power_save_on == flag_on) + return 0; + + mipi_dsi_power_save_on = flag_on; + + 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; + + 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, 1000); + 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) +{ + 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, + 128000000, + 160000000, + 200000000, +}; +#else +int mdp_core_clk_rate_table[] = { + 59080000, + 128000000, + 128000000, + 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_MSM_MULTIMEDIA_USE_ION + .mem_hid = BIT(ION_CP_WB_HEAP_ID), +#else + .mem_hid = MEMTYPE_EBI1, +#endif +}; + +static void __init reserve_mdp_memory(void) +{ + mdp_pdata.ov0_wb_size = MSM_FB_OVERLAY0_WRITEBACK_SIZE; + mdp_pdata.ov1_wb_size = MSM_FB_OVERLAY1_WRITEBACK_SIZE; +#if defined(CONFIG_ANDROID_PMEM) && !defined(CONFIG_MSM_MULTIMEDIA_USE_ION) + msm8x60_reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov0_wb_size; + msm8x60_reserve_table[mdp_pdata.mem_hid].size += + mdp_pdata.ov1_wb_size; +#endif +} + +#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 + if (hdmi_is_primary) + msm_fb_register_device("dtv", &dtv_hdmi_prim_pdata); + else + 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 +} + +/** + * Set MDP clocks to high frequency to avoid underflow when + * using high resolution 1200x1920 WUXGA/HDMI as primary panels */ +static void set_mdp_clocks_for_wuxga(void) +{ + int i; -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + mdp_sd_smi_vectors[0].ab = 2000000000; + mdp_sd_smi_vectors[0].ib = 2000000000; + mdp_sd_smi_vectors[1].ab = 2000000000; + mdp_sd_smi_vectors[1].ib = 2000000000; -#include -#include -#include -#include + mdp_sd_ebi_vectors[0].ab = 2000000000; + mdp_sd_ebi_vectors[0].ib = 2000000000; + mdp_sd_ebi_vectors[1].ab = 2000000000; + mdp_sd_ebi_vectors[1].ib = 2000000000; -#include -#include + mdp_vga_vectors[0].ab = 2000000000; + mdp_vga_vectors[0].ib = 2000000000; + mdp_vga_vectors[1].ab = 2000000000; + mdp_vga_vectors[1].ib = 2000000000; + + mdp_720p_vectors[0].ab = 2000000000; + mdp_720p_vectors[0].ib = 2000000000; + mdp_720p_vectors[1].ab = 2000000000; + mdp_720p_vectors[1].ib = 2000000000; + + mdp_1080p_vectors[0].ab = 2000000000; + mdp_1080p_vectors[0].ib = 2000000000; + mdp_1080p_vectors[1].ab = 2000000000; + mdp_1080p_vectors[1].ib = 2000000000; + + mdp_pdata.mdp_core_clk_rate = 200000000; + + for (i = 0; i < ARRAY_SIZE(mdp_core_clk_rate_table); i++) + mdp_core_clk_rate_table[i] = 200000000; +} + +#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 void __init msm8x60_fixup(struct tag *tag, char **cmdline, - struct meminfo *mi) +static int bluetooth_use_regulators(int on) { - for (; tag->hdr.size; tag = tag_next(tag)) - if (tag->hdr.tag == ATAG_MEM && - tag->u.mem.start == 0x40200000) { - tag->u.mem.start = 0x40000000; - tag->u.mem.size += SZ_2M; + 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 void __init msm8x60_reserve(void) +static int bluetooth_switch_regulators(int on) { - memblock_remove(0x40000000, SZ_2M); + 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 void __init msm8x60_map_io(void) +static struct msm_xo_voter *bt_clock; + +static int bluetooth_power(int on) { - msm_map_msm8x60_io(); + 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; +} + +#endif /*CONFIG_MARIMBA_CORE, CONFIG_MSM_BT_POWER, CONFIG_MSM_BT_POWER_MODULE*/ + +static void __init msm8x60_cfg_smsc911x(void) +{ + smsc911x_resources[1].start = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 6); + smsc911x_resources[1].end = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 6); +} + +void msm_fusion_setup_pinctrl(void) +{ + struct msm_xo_voter *a1; + + if (socinfo_get_platform_subtype() == 0x3) { + /* + * Vote for the A1 clock to be in pin control mode before + * the external images are loaded. + */ + a1 = msm_xo_get(MSM_XO_TCXO_A1, "mdm"); + BUG_ON(!a1); + msm_xo_mode_vote(a1, MSM_XO_MODE_PIN_CTRL); + } } -#ifdef CONFIG_OF -static struct of_device_id msm_dt_gic_match[] __initdata = { - { .compatible = "qcom,msm-8660-qgic", .data = gic_of_init }, - {} +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, +}; + +static struct msm_board_data msm8x60_ffa_board_data __initdata = { + .gpiomux_cfgs = msm8x60_surf_ffa_gpiomux_cfgs, +}; + +static struct msm_board_data msm8x60_fluid_board_data __initdata = { + .gpiomux_cfgs = msm8x60_fluid_gpiomux_cfgs, +}; + +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 struct msm_board_data msm8x60_dragon_board_data __initdata = { + .gpiomux_cfgs = msm8x60_dragon_gpiomux_cfgs, }; -#endif -static void __init msm8x60_init_irq(void) +static void __init msm8x60_init(struct msm_board_data *board_data) { - if (!of_have_populated_dt()) - gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, - (void *)MSM_QGIC_CPU_BASE); -#ifdef CONFIG_OF + uint32_t soc_platform_version; +#ifdef CONFIG_USB_EHCI_MSM_72K + struct pm8xxx_mpp_config_data hsusb_phy_mpp = { + .type = PM8XXX_MPP_TYPE_D_OUTPUT, + .level = PM8901_MPP_DIG_LEVEL_L5, + .control = PM8XXX_MPP_DOUT_CTRL_HIGH, + }; +#endif + pmic_reset_irq = PM8058_IRQ_BASE + PM8058_RESOUT_IRQ; + + /* + * Initialize RPM first as other drivers and devices may need + * it for their initialization. + */ + BUG_ON(msm_rpm_init(&msm8660_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + if (msm_xo_init()) + pr_err("Failed to initialize XO votes\n"); + + 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()) { + int i; + + for (i = 0; i < ARRAY_SIZE(rpm_regulator_init_data); i++) + if (rpm_regulator_init_data[i].id + == RPM_VREG_ID_PM8901_L4 + || rpm_regulator_init_data[i].id + == RPM_VREG_ID_PM8901_L6) + rpm_regulator_init_data[i] + .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)); + + msm_clock_init(&msm8x60_clock_init_data); + + /* 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()) + acpuclk_init(&acpuclk_8x60_soc_data); + + /* + * Enable EBI2 only for boards which make use of it. Leave + * it disabled for all others for additional power savings. + */ + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_rumi3() || + machine_is_msm8x60_sim() || + machine_is_msm8x60_fluid() || + machine_is_msm8x60_dragon()) + msm8x60_init_ebi2(); + msm8x60_init_tlmm(); + msm8x60_init_gpiomux(board_data->gpiomux_cfgs); + msm8x60_init_uart12dm(); +#ifdef CONFIG_MSM_CAMERA_V4L2 + msm8x60_init_cam(); +#endif + 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.keypad_pdata = &fluid_keypad_data; + else if (machine_is_msm8x60_dragon()) + pm8058_platform_data.keypad_pdata = &dragon_keypad_data; + else + pm8058_platform_data.keypad_pdata = &ffa_keypad_data; +#if !defined(CONFIG_MSM_CAMERA_V4L2) && defined(CONFIG_WEBCAM_OV9726) + /* Specify reset pin for OV9726 */ + if (machine_is_msm8x60_dragon()) { + msm_camera_sensor_ov9726_data.sensor_reset = 62; + ov9726_sensor_8660_info.mount_angle = 270; + } +#endif +#ifdef CONFIG_BATTERY_MSM8X60 + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusion() || machine_is_msm8x60_dragon() || + machine_is_msm8x60_fusn_ffa() || machine_is_msm8x60_fluid()) + platform_device_register(&msm_charger_device); +#endif + + if (machine_is_msm8x60_dragon()) + pm8058_platform_data.charger_pdata = &pmic8058_charger_dragon; + if (!machine_is_msm8x60_fluid()) + pm8058_platform_data.charger_pdata = &pmic8058_charger_ffa_surf; + + /* configure pmic leds */ + if (machine_is_msm8x60_fluid()) + pm8058_platform_data.leds_pdata = &pm8058_fluid_flash_leds_data; + else if (machine_is_msm8x60_dragon()) + pm8058_platform_data.leds_pdata = &pm8058_dragon_leds_data; else - of_irq_init(msm_dt_gic_match); + pm8058_platform_data.leds_pdata = &pm8058_flash_leds_data; + + if (machine_is_msm8x60_ffa() || machine_is_msm8x60_fusn_ffa() || + machine_is_msm8x60_dragon()) { + pm8058_platform_data.vibrator_pdata = &pm8058_vib_pdata; + } + + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fluid() || machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa() || machine_is_msm8x60_dragon()) { + msm8x60_cfg_smsc911x(); + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 1) + platform_add_devices(msm8660_footswitch, + msm8660_num_footswitch); + 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 - /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ - writel(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); + pm8901_vreg_mpp0_init(); - /* 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. + platform_device_register(&msm8x60_8901_mpp_vreg); + +#ifdef CONFIG_USB_EHCI_MSM_72K + /* + * Drive MPP2 pin HIGH for PHY to generate ID interrupts on 8660 + * fluid */ + if (machine_is_msm8x60_fluid()) + pm8xxx_mpp_config(PM8901_MPP_PM_TO_SYS(1), &hsusb_phy_mpp); + msm_add_host(0, &msm_usb_host_pdata); +#endif + +#ifdef CONFIG_SND_SOC_MSM8660_APQ + if (machine_is_msm8x60_dragon()) + platform_add_devices(dragon_alsa_devices, + ARRAY_SIZE(dragon_alsa_devices)); + else +#endif + platform_add_devices(asoc_devices, + ARRAY_SIZE(asoc_devices)); + } 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() || + machine_is_msm8x60_dragon()) + msm8x60_cfg_isp1763(); +#endif + + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) + platform_add_devices(charm_devices, ARRAY_SIZE(charm_devices)); + + +#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_QC) || \ + defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC_MODULE) + if (machine_is_msm8x60_fluid()) + cyttsp_set_params(); +#endif if (!machine_is_msm8x60_sim()) - writel(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); + msm_fb_add_devices(); + fixup_i2c_configs(); + register_i2c_devices(); + + if (machine_is_msm8x60_dragon()) + smsc911x_config.reset_gpio + = GPIO_ETHERNET_RESET_N_DRAGON; + + 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) || \ + defined(CONFIG_FB_MSM_LCDC_NT35582_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 + } +#ifdef CONFIG_FB_MSM_LCDC_NT35582_WVGA + } else if (machine_is_msm8x60_dragon()) { + spi_register_board_info(lcdc_nt35582_spi_board_info, + ARRAY_SIZE(lcdc_nt35582_spi_board_info)); +#endif + } +#endif + + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + + pm8058_gpios_init(); + +#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 + + msm8x60_multi_sdio_init(); + + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) + msm_fusion_setup_pinctrl(); } -static void __init msm8x60_init(void) +static void __init msm8x60_rumi3_init(void) { + msm8x60_init(&msm8x60_rumi3_board_data); } -#ifdef CONFIG_OF -static struct of_dev_auxdata msm_auxdata_lookup[] __initdata = { - {} -}; +static void __init msm8x60_sim_init(void) +{ + msm8x60_init(&msm8x60_sim_board_data); +} -static void __init msm8x60_dt_init(void) +static void __init msm8x60_surf_init(void) { - if (of_machine_is_compatible("qcom,msm8660-surf")) { - printk(KERN_INFO "Init surf UART registers\n"); - msm8x60_init_uart12dm(); - } + msm8x60_init(&msm8x60_surf_board_data); +} - of_platform_populate(NULL, of_default_bus_match_table, - msm_auxdata_lookup, NULL); +static void __init msm8x60_ffa_init(void) +{ + msm8x60_init(&msm8x60_ffa_board_data); } -static const char *msm8x60_fluid_match[] __initdata = { - "qcom,msm8660-fluid", - "qcom,msm8660-surf", - NULL -}; -#endif /* CONFIG_OF */ +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_charm_init_early(void) +{ + msm8x60_allocate_memory_regions(); +} + +static void __init msm8x60_dragon_init(void) +{ + msm8x60_init(&msm8x60_dragon_board_data); +} MACHINE_START(MSM8X60_RUMI3, "QCT MSM8X60 RUMI3") - .fixup = msm8x60_fixup, + .map_io = msm8x60_map_io, .reserve = msm8x60_reserve, + .init_irq = msm8x60_init_irq, + .handle_irq = gic_handle_irq, + .init_machine = msm8x60_rumi3_init, + .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, +MACHINE_END + +MACHINE_START(MSM8X60_SIM, "QCT MSM8X60 SIMULATOR") .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, .handle_irq = gic_handle_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_sim_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END MACHINE_START(MSM8X60_SURF, "QCT MSM8X60 SURF") - .fixup = msm8x60_fixup, - .reserve = msm8x60_reserve, .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, .handle_irq = gic_handle_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_surf_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END -MACHINE_START(MSM8X60_SIM, "QCT MSM8X60 SIMULATOR") - .fixup = msm8x60_fixup, +MACHINE_START(MSM8X60_FFA, "QCT MSM8X60 FFA") + .map_io = msm8x60_map_io, .reserve = msm8x60_reserve, + .init_irq = msm8x60_init_irq, + .handle_irq = gic_handle_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, .handle_irq = gic_handle_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_fluid_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END -MACHINE_START(MSM8X60_FFA, "QCT MSM8X60 FFA") - .fixup = msm8x60_fixup, +MACHINE_START(MSM8X60_FUSION, "QCT MSM8X60 FUSION SURF") + .map_io = msm8x60_map_io, .reserve = msm8x60_reserve, + .init_irq = msm8x60_init_irq, + .handle_irq = gic_handle_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, .handle_irq = gic_handle_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_charm_ffa_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END -#ifdef CONFIG_OF -/* TODO: General device tree support for all MSM. */ -DT_MACHINE_START(MSM_DT, "Qualcomm MSM (Flattened Device Tree)") +MACHINE_START(MSM8X60_DRAGON, "QCT MSM8X60 DRAGON") .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, - .init_machine = msm8x60_dt_init, + .handle_irq = gic_handle_irq, + .init_machine = msm8x60_dragon_init, .timer = &msm_timer, - .dt_compat = msm8x60_fluid_match, + .init_early = msm8x60_charm_init_early, MACHINE_END -#endif /* CONFIG_OF */ diff --git a/arch/arm/mach-msm/board-qrd7627a.c b/arch/arm/mach-msm/board-qrd7627a.c new file mode 100644 index 0000000000000000000000000000000000000000..db0c9c047918e8b5d1fe13d305325f23fbf3e149 --- /dev/null +++ b/arch/arm/mach-msm/board-qrd7627a.c @@ -0,0 +1,1025 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +#include +#include +#include +#include +#include +#include "board-msm7x27a-regulator.h" +#include "devices.h" +#include "devices-msm7x2xa.h" +#include "pm.h" +#include "timer.h" +#include "pm-boot.h" +#include "board-msm7x27a-regulator.h" +#include "board-msm7627a.h" + +#define PMEM_KERNEL_EBI1_SIZE 0x3A000 +#define MSM_PMEM_AUDIO_SIZE 0x1F4000 +#define BAHAMA_SLAVE_ID_FM_REG 0x02 +#define FM_GPIO 83 +#define BT_PCM_BCLK_MODE 0x88 +#define BT_PCM_DIN_MODE 0x89 +#define BT_PCM_DOUT_MODE 0x8A +#define BT_PCM_SYNC_MODE 0x8B +#define FM_I2S_SD_MODE 0x8E +#define FM_I2S_WS_MODE 0x8F +#define FM_I2S_SCK_MODE 0x90 +#define I2C_PIN_CTL 0x15 +#define I2C_NORMAL 0x40 + +static struct platform_device msm_wlan_ar6000_pm_device = { + .name = "wlan_ar6000_pm_dev", + .id = -1, +}; + +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, + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi1_qup_i2c_pdata = { + .clk_freq = 100000, + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +#ifdef CONFIG_ARCH_MSM7X27A +#define MSM_PMEM_MDP_SIZE 0x2300000 +#define MSM_PMEM_ADSP_SIZE 0x1100000 +#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 = QRD_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 msm7627a_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 regulator *reg_hsusb; +static int msm_hsusb_ldo_init(int init) +{ + int rc = 0; + + if (init) { + reg_hsusb = regulator_get(NULL, "usb"); + if (IS_ERR(reg_hsusb)) { + rc = PTR_ERR(reg_hsusb); + pr_err("%s: could not get regulator: %d\n", + __func__, rc); + goto out; + } + + rc = regulator_set_voltage(reg_hsusb, 3300000, 3300000); + if (rc) { + pr_err("%s: could not set voltage: %d\n", + __func__, rc); + goto reg_free; + } + + return 0; + } + /* else fall through */ +reg_free: + regulator_put(reg_hsusb); +out: + reg_hsusb = NULL; + return rc; +} + +static int msm_hsusb_ldo_enable(int enable) +{ + static int ldo_status; + + if (IS_ERR_OR_NULL(reg_hsusb)) + return reg_hsusb ? PTR_ERR(reg_hsusb) : -ENODEV; + + if (ldo_status == enable) + return 0; + + ldo_status = enable; + + return enable ? + regulator_enable(reg_hsusb) : + regulator_disable(reg_hsusb); +} + +#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, + .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, +}; + +#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 msm7627a_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 msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS, + .p_addr = 0, +}; + +/* 8625 PM platform data */ +static struct msm_pm_platform_data msm8625_pm_data[MSM_PM_SLEEP_MODE_NR * 2] = { + /* CORE0 entries */ + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 16000, + .residency = 20000, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 12000, + .residency = 20000, + }, + + /* picked latency & redisdency values from 7x30 */ + [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 = 10, + }, + + /* picked latency & redisdency values from 7x30 */ + [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 = 10, + }, + +}; + +static struct msm_pm_boot_platform_data msm_pm_8625_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR, + .v_addr = MSM_CFG_CTL_BASE, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 1, + .memory_type = MEMTYPE_EBI1, + .request_region = request_fmem_c_region, + .release_region = release_fmem_c_region, + .reusable = 1, +}; + +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); + +#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(STEREO_HEADSET_AND_SPEAKER, 31), + SND(CURRENT, 0x7FFFFFFE), + 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<reusable) + fmem_pdata.size += pdata->size; + + reusable_count += (pdata->reusable) ? 1 : 0; + + if (pdata->reusable && reusable_count > 1) { + pr_err("%s: Too many PMEM devices specified as reusable. PMEM device %s was not configured as reusable.\n", + __func__, pdata->name); + pdata->reusable = 0; + } + } + +#endif +} + +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm7627a_reserve_table[p->memory_type].size += p->size; +} + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM + unsigned int i; + for (i = 0; i < ARRAY_SIZE(pmem_pdata_array); ++i) + reserve_memory_for(pmem_pdata_array[i]); + + msm7627a_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif +} + +static void __init msm7627a_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); +} + +static int msm7627a_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +static struct reserve_info msm7627a_reserve_info __initdata = { + .memtype_reserve_table = msm7627a_reserve_table, + .calculate_reserve_sizes = msm7627a_calculate_reserve_sizes, + .paddr_to_memtype = msm7627a_paddr_to_memtype, +}; + +static void __init msm7627a_reserve(void) +{ + reserve_info = &msm7627a_reserve_info; + msm_reserve(); + memblock_remove(MSM8625_WARM_BOOT_PHYS, SZ_32); +} + +static void __init msm8625_reserve(void) +{ + memblock_remove(MSM8625_SECONDARY_PHYS, SZ_8); + msm7627a_reserve(); +} + +static void msmqrd_adsp_add_pdev(void) +{ + int rc = 0; + struct rpc_board_dev *rpc_adsp_pdev; + + rpc_adsp_pdev = kzalloc(sizeof(struct rpc_board_dev), GFP_KERNEL); + if (rpc_adsp_pdev == NULL) { + pr_err("%s: Memory Allocation failure\n", __func__); + return; + } + rpc_adsp_pdev->prog = ADSP_RPC_PROG; + + if (cpu_is_msm8625()) + rpc_adsp_pdev->pdev = msm8625_device_adsp; + else + rpc_adsp_pdev->pdev = msm_adsp_device; + rc = msm_rpc_add_board_dev(rpc_adsp_pdev, 1); + if (rc < 0) { + pr_err("%s: return val: %d\n", __func__, rc); + kfree(rpc_adsp_pdev); + } +} + +static void __init msm7627a_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 void __init msm8625_device_i2c_init(void) +{ + msm8625_gsbi0_qup_i2c_device.dev.platform_data + = &msm_gsbi0_qup_i2c_pdata; + msm8625_gsbi1_qup_i2c_device.dev.platform_data + = &msm_gsbi1_qup_i2c_pdata; +} + +static struct platform_device msm_proccomm_regulator_dev = { + .name = PROCCOMM_REGULATOR_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &msm7x27a_proccomm_regulator_data + } +}; + +static void __init msm7627a_init_regulators(void) +{ + int rc = platform_device_register(&msm_proccomm_regulator_dev); + if (rc) + pr_err("%s: could not register regulator device: %d\n", + __func__, rc); +} + +static int __init msm_qrd_init_ar6000pm(void) +{ + msm_wlan_ar6000_pm_device.dev.platform_data = &ar600x_wlan_power; + return platform_device_register(&msm_wlan_ar6000_pm_device); +} + +static void __init msm_add_footswitch_devices(void) +{ + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); +} + +static void __init add_platform_devices(void) +{ + if (machine_is_msm8625_evb() || machine_is_msm8625_qrd7() + || machine_is_msm8625_evt()) { + platform_add_devices(msm8625_evb_devices, + ARRAY_SIZE(msm8625_evb_devices)); + platform_add_devices(qrd3_devices, + ARRAY_SIZE(qrd3_devices)); + } else { + platform_add_devices(qrd7627a_devices, + ARRAY_SIZE(qrd7627a_devices)); + } + + if (machine_is_msm7627a_qrd3() || machine_is_msm7627a_evb()) + platform_add_devices(qrd3_devices, + ARRAY_SIZE(qrd3_devices)); + + platform_add_devices(common_devices, + ARRAY_SIZE(common_devices)); +} + +#define UART1DM_RX_GPIO 45 +static void __init qrd7627a_uart1dm_config(void) +{ + msm_uart_dm1_pdata.wakeup_irq = gpio_to_irq(UART1DM_RX_GPIO); + if (cpu_is_msm8625()) + msm8625_device_uart_dm1.dev.platform_data = + &msm_uart_dm1_pdata; + else + msm_device_uart_dm1.dev.platform_data = &msm_uart_dm1_pdata; +} + +static void __init qrd7627a_otg_gadget(void) +{ + if (cpu_is_msm8625()) { + msm_otg_pdata.swfi_latency = msm8625_pm_data + [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT].latency; + msm8625_device_otg.dev.platform_data = &msm_otg_pdata; + msm8625_device_gadget_peripheral.dev.platform_data = + &msm_gadget_pdata; + + } else { + msm_otg_pdata.swfi_latency = msm7627a_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; + msm_device_otg.dev.platform_data = &msm_otg_pdata; + msm_device_gadget_peripheral.dev.platform_data = + &msm_gadget_pdata; + } +} + +static void __init msm_pm_init(void) +{ + + if (!cpu_is_msm8625()) { + msm_pm_set_platform_data(msm7627a_pm_data, + ARRAY_SIZE(msm7627a_pm_data)); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + } else { + msm_pm_set_platform_data(msm8625_pm_data, + ARRAY_SIZE(msm8625_pm_data)); + BUG_ON(msm_pm_boot_init(&msm_pm_8625_boot_pdata)); + msm8x25_spm_device_init(); + } +} + +static void __init msm_qrd_init(void) +{ + msm7x2x_misc_init(); + msm7627a_init_regulators(); + msmqrd_adsp_add_pdev(); + + if (cpu_is_msm8625()) + msm8625_device_i2c_init(); + else + msm7627a_device_i2c_init(); + + /* uart1dm*/ + qrd7627a_uart1dm_config(); + /*OTG gadget*/ + qrd7627a_otg_gadget(); + + msm_add_footswitch_devices(); + add_platform_devices(); + + /* Ensure ar6000pm device is registered before MMC/SDC */ + msm_qrd_init_ar6000pm(); + msm7627a_init_mmc(); + +#ifdef CONFIG_USB_EHCI_MSM_72K + msm7627a_init_host(); +#endif + msm_pm_init(); + + msm_pm_register_irqs(); + msm_fb_add_devices(); + +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + msm7627a_bt_power_init(); +#endif + + msm7627a_camera_init(); + qrd7627a_add_io_devices(); + msm7x25a_kgsl_3d0_init(); + msm8x25_kgsl_3d0_init(); +} + +static void __init qrd7627a_init_early(void) +{ + msm_msm7627a_allocate_memory_regions(); +} + +MACHINE_START(MSM7627A_QRD1, "QRD MSM7627a QRD1") + .atag_offset = 0x100, + .map_io = msm_common_io_init, + .reserve = msm7627a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm_qrd_init, + .timer = &msm_timer, + .init_early = qrd7627a_init_early, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM7627A_QRD3, "QRD MSM7627a QRD3") + .atag_offset = 0x100, + .map_io = msm_common_io_init, + .reserve = msm7627a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm_qrd_init, + .timer = &msm_timer, + .init_early = qrd7627a_init_early, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM7627A_EVB, "QRD MSM7627a EVB") + .atag_offset = 0x100, + .map_io = msm_common_io_init, + .reserve = msm7627a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm_qrd_init, + .timer = &msm_timer, + .init_early = qrd7627a_init_early, + .handle_irq = vic_handle_irq, +MACHINE_END +MACHINE_START(MSM8625_EVB, "QRD MSM8625 EVB") + .atag_offset = 0x100, + .map_io = msm8625_map_io, + .reserve = msm8625_reserve, + .init_irq = msm8625_init_irq, + .init_machine = msm_qrd_init, + .timer = &msm_timer, + .init_early = qrd7627a_init_early, + .handle_irq = gic_handle_irq, +MACHINE_END +MACHINE_START(MSM8625_QRD7, "QRD MSM8625 QRD7") + .atag_offset = 0x100, + .map_io = msm8625_map_io, + .reserve = msm8625_reserve, + .init_irq = msm8625_init_irq, + .init_machine = msm_qrd_init, + .timer = &msm_timer, + .init_early = qrd7627a_init_early, + .handle_irq = gic_handle_irq, +MACHINE_END +MACHINE_START(MSM8625_EVT, "QRD MSM8625 EVT") + .atag_offset = 0x100, + .map_io = msm8625_map_io, + .reserve = msm8625_reserve, + .init_irq = msm8625_init_irq, + .init_machine = msm_qrd_init, + .timer = &msm_timer, + .init_early = qrd7627a_init_early, + .handle_irq = gic_handle_irq, +MACHINE_END diff --git a/arch/arm/mach-msm/board-qsd8x50.c b/arch/arm/mach-msm/board-qsd8x50.c index 7e8909c978c38d3f9a5fc471ad23d2cf2f978fd7..6a39316464b2f9f74234c6adc8b2fde91974ced3 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-2012, Code Aurora Forum. All rights reserved. * * This 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,106 +9,2077 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "devices.h" +#include "timer.h" +#include "msm-keypad-devices.h" +#include "acpuclock.h" +#include "pm.h" +#include "irq.h" +#include "pm-boot.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, + .mdp_rev = MDP_REV_31, +}; + +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 = "core_clk", + .tsif_ref_clk = "ref_clk", +}; + +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +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); + } + } +} + +static int config_camera_on_gpios(void) +{ + int vreg_en = 1; + + if (machine_is_qsd8x50_ffa()) { + config_gpio_table(camera_on_gpio_ffa_table, + ARRAY_SIZE(camera_on_gpio_ffa_table)); + + 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; +} -#include -#include -#include -#include +static void config_camera_off_gpios(void) +{ + int vreg_en = 0; -#include -#include -#include -#include -#include + if (machine_is_qsd8x50_ffa()) { + config_gpio_table(camera_off_gpio_ffa_table, + ARRAY_SIZE(camera_off_gpio_ffa_table)); -#include "devices.h" + 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, + }, + { + .start = INT_VFE, + .end = INT_VFE, + .flags = IORESOURCE_IRQ, + }, +}; -extern struct sys_timer msm_timer; +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 const resource_size_t qsd8x50_surf_smc91x_base __initdata = 0x70000300; -static const unsigned qsd8x50_surf_smc91x_gpio __initdata = 156; +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, +}; -/* 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, +#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, }, - [1] = { - .flags = IORESOURCE_IRQ, +}; +#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 -static struct platform_device smc91x_device = { - .name = "smc91x", - .id = 0, - .num_resources = ARRAY_SIZE(smc91x_resources), - .resource = smc91x_resources, +#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 int __init msm_init_smc91x(void) +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) { - 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); - } + u32 low_voltage = msm_psy_batt_data.voltage_min_design; + u32 high_voltage = msm_psy_batt_data.voltage_max_design; - return 0; + 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, }; +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) { @@ -116,6 +2087,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) @@ -126,69 +2098,431 @@ 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, + .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, + .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 + .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, + .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 struct msm_pm_boot_platform_data msm_pm_boot_pdata __initdata = { + .mode = MSM_PM_BOOT_CONFIG_RESET_VECTOR_VIRT, + .v_addr = (unsigned int *)PAGE_OFFSET, +}; + +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) { + msm_clock_init(&qds8x50_clock_init_data); + qsd8x50_cfg_smc91x(); + acpuclk_init(&acpuclk_8x50_soc_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)); + BUG_ON(msm_pm_boot_init(&msm_pm_boot_pdata)); + msm_pm_register_irqs(); + +#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(); + if (socinfo_init() < 0) + printk(KERN_ERR "%s: socinfo_init() failed!\n", + __func__); } MACHINE_START(QSD8X50_SURF, "QCT QSD8X50 SURF") @@ -199,7 +2533,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") .atag_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 0000000000000000000000000000000000000000..375440c5afb9df1f24fa5a2fe3fac31e00112629 --- /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 0000000000000000000000000000000000000000..aa83e216974dc4633661761e8f1f0e3abd73ebd0 --- /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 100644 index 0000000000000000000000000000000000000000..5c8fc37649a3c3cc3b7443d54080fbb0147224cc --- /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 100644 index 0000000000000000000000000000000000000000..8cb913fade1338f961ac9b273ce370967163bdb4 --- /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 + +#include "devices.h" +#include "gpio_chip.h" +#include "board-sapphire.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 0000000000000000000000000000000000000000..1a5f63acec5378a8b1abc795b3fe72f1895adfd1 --- /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 + +#include "gpio_chip.h" +#include "board-sapphire.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 0000000000000000000000000000000000000000..2fd6ea198e3df614455b00782914d19e0463fa4f --- /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 0000000000000000000000000000000000000000..43f827c60f13eabd86c479731bbb555127259f1d --- /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.c b/arch/arm/mach-msm/board-sapphire.c index 4a8ea0d40b6ffe0b794d95a29f6d9e9003456eab..abb61577e2dd97b7d9f70944bc14283a246190b6 100644 --- a/arch/arm/mach-msm/board-sapphire.c +++ b/arch/arm/mach-msm/board-sapphire.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -40,7 +41,6 @@ #include "gpio_chip.h" #include "board-sapphire.h" -#include "proc_comm.h" #include "devices.h" void msm_init_irq(void); diff --git a/arch/arm/mach-msm/board-sapphire.h b/arch/arm/mach-msm/board-sapphire.h new file mode 100644 index 0000000000000000000000000000000000000000..d96760a25ef8a027d54d1eb08a25d1f87e89194e --- /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-storage-common-a.h b/arch/arm/mach-msm/board-storage-common-a.h new file mode 100644 index 0000000000000000000000000000000000000000..7737819bf4c520082410508fa25d0d9260f566b7 --- /dev/null +++ b/arch/arm/mach-msm/board-storage-common-a.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _BOARD_STORAGE_A_H +#define _BOARD_STORAGE_A_H + +#include +#include +#include + +#define MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(num, _ib) \ +static struct msm_bus_vectors sps_to_ddr_perf_vectors_##num[] = { \ + { \ + .src = MSM_BUS_MASTER_SPS, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ib = (_ib), \ + .ab = ((_ib) / 2), \ + } \ +} + +#define MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(num) \ + { \ + ARRAY_SIZE(sps_to_ddr_perf_vectors_##num), \ + sps_to_ddr_perf_vectors_##num, \ + } + +/* no bandwidth required */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(0, 0); +/* + * 13 MB/s bandwidth + * 4-bit MMC_TIMING_LEGACY + * 4-bit MMC_TIMING_UHS_SDR12 + */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(1, 13 * 1024 * 1024); +/* + * 26 MB/s bandwidth + * 8-bit MMC_TIMING_LEGACY + * 4-bit MMC_TIMING_MMC_HS / MMC_TIMING_SD_HS / + * MMC_TIMING_UHS_SDR25 + */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(2, 26 * 1024 * 1024); +/* + * 52 MB/s bandwidth + * 8-bit MMC_TIMING_MMC_HS + * 4-bit MMC_TIMING_UHS_SDR50 / MMC_TIMING_UHS_DDR50 + */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(3, 52 * 1024 * 1024); +/* + * 104 MB/s bandwidth + * 8-bit MMC_TIMING_UHS_DDR50 + * 4-bit MMC_TIMING_UHS_SDR104 / MMC_TIMING_MMC_HS200 + */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(4, 104 * 1024 * 1024); +/* + * 200 MB/s bandwidth + * 8-bit MMC_TIMING_MMC_HS200 + */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(5, 200 * 1024 * 1024); +/* max. possible bandwidth */ +MSM_BUS_SPS_TO_DDR_VOTE_VECTOR(6, UINT_MAX); + +static unsigned int sdcc_bw_vectors[] = {0, (13 * 1024 * 1024), + (26 * 1024 * 1024), (52 * 1024 * 1024), + (104 * 1024 * 1024), (200 * 1024 * 1024), + UINT_MAX}; + +static struct msm_bus_paths sps_to_ddr_bus_scale_usecases[] = { + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(0), + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(1), + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(2), + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(3), + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(4), + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(5), + MSM_BUS_SPS_TO_DDR_VOTE_VECTOR_USECASE(6), +}; + +static struct msm_bus_scale_pdata sps_to_ddr_bus_scale_data = { + sps_to_ddr_bus_scale_usecases, + ARRAY_SIZE(sps_to_ddr_bus_scale_usecases), + .name = "msm_sdcc", +}; + +static struct msm_mmc_bus_voting_data sps_to_ddr_bus_voting_data = { + .use_cases = &sps_to_ddr_bus_scale_data, + .bw_vecs = sdcc_bw_vectors, + .bw_vecs_size = sizeof(sdcc_bw_vectors), +}; + +#endif /* _BOARD_STORAGE_A_H */ 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 0000000000000000000000000000000000000000..f2c2f3962f65166e352f12ec6a4af26723e41305 --- /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 0000000000000000000000000000000000000000..e4a2a64df0d05a26aa7320688b70dc1c75e690be --- /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 + +#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 0000000000000000000000000000000000000000..cf5f3f62b767df67643dd88c273caa9adb724650 --- /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 0000000000000000000000000000000000000000..45d5bb07adf89437530d453dcb29c44082ccf50c --- /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 + +#include "board-swordfish.h" +#include "devices.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 + .atag_offset = 0x100, + .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 + .atag_offset = 0x100, + .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 0000000000000000000000000000000000000000..b9ea54f680dbcb717030ebeb3da8ab24423bed71 --- /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 0000000000000000000000000000000000000000..0299d0686de9f8676cec7e1dd444e502fa53d9d7 --- /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-mmc.c b/arch/arm/mach-msm/board-trout-mmc.c index 8650342b7493e00e4828ffd1bf057c81979efee3..f7109d5f4ab1a08aa39c2453b2e4b8282d6291ce 100644 --- a/arch/arm/mach-msm/board-trout-mmc.c +++ b/arch/arm/mach-msm/board-trout-mmc.c @@ -14,15 +14,13 @@ #include #include - +#include #include #include "devices.h" #include "board-trout.h" -#include "proc_comm.h" - #define DEBUG_SDSLOT_VDD 1 /* ---- COMMON ---- */ diff --git a/arch/arm/mach-msm/board-trout-panel.c b/arch/arm/mach-msm/board-trout-panel.c index 89bf6b426699863d8230d9fcdf2b9d66bfa3cdc3..52948b2602aaa0b3cd5505c35284a4929ed437a2 100644 --- a/arch/arm/mach-msm/board-trout-panel.c +++ b/arch/arm/mach-msm/board-trout-panel.c @@ -16,9 +16,9 @@ #include #include +#include #include "board-trout.h" -#include "proc_comm.h" #include "devices.h" #define TROUT_DEFAULT_BACKLIGHT_BRIGHTNESS 255 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 0000000000000000000000000000000000000000..e68eb2ae4c51705e093a12eb858d61260b3ba452 --- /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 0000000000000000000000000000000000000000..51b26a405369530cedfaea9990bd1bcfa6f0811a --- /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 0000000000000000000000000000000000000000..97a511e80eddf42d138999d783ee2529f7271814 --- /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 + +#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/cache_erp.c b/arch/arm/mach-msm/cache_erp.c new file mode 100644 index 0000000000000000000000000000000000000000..97225ac1bd94c86ba9043eb60831660aedac8f82 --- /dev/null +++ b/arch/arm/mach-msm/cache_erp.c @@ -0,0 +1,515 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "acpuclock.h" + +#define CESR_DCTPE BIT(0) +#define CESR_DCDPE BIT(1) +#define CESR_ICTPE BIT(2) +#define CESR_ICDPE BIT(3) +#define CESR_DCTE (BIT(4) | BIT(5)) +#define CESR_ICTE (BIT(6) | BIT(7)) +#define CESR_TLBMH BIT(16) +#define CESR_I_MASK 0x000000CC + +/* Print a message for everything but TLB MH events */ +#define CESR_PRINT_MASK 0x000000FF + +#define L2ESR_IND_ADDR 0x204 +#define L2ESYNR0_IND_ADDR 0x208 +#define L2ESYNR1_IND_ADDR 0x209 +#define L2EAR0_IND_ADDR 0x20C +#define L2EAR1_IND_ADDR 0x20D + +#define L2ESR_MPDCD BIT(0) +#define L2ESR_MPSLV BIT(1) +#define L2ESR_TSESB BIT(2) +#define L2ESR_TSEDB BIT(3) +#define L2ESR_DSESB BIT(4) +#define L2ESR_DSEDB BIT(5) +#define L2ESR_MSE BIT(6) +#define L2ESR_MPLDREXNOK BIT(8) + +#define L2ESR_ACCESS_ERR_MASK 0xFFFC + +#define L2ESR_CPU_MASK 0x0F +#define L2ESR_CPU_SHIFT 16 + +#ifdef CONFIG_MSM_L1_ERR_PANIC +#define ERP_L1_ERR(a) panic(a) +#else +#define ERP_L1_ERR(a) do { } while (0) +#endif + +#ifdef CONFIG_MSM_L2_ERP_PORT_PANIC +#define ERP_PORT_ERR(a) panic(a) +#else +#define ERP_PORT_ERR(a) WARN(1, a) +#endif + +#ifdef CONFIG_MSM_L2_ERP_1BIT_PANIC +#define ERP_1BIT_ERR(a) panic(a) +#else +#define ERP_1BIT_ERR(a) do { } while (0) +#endif + +#ifdef CONFIG_MSM_L2_ERP_PRINT_ACCESS_ERRORS +#define print_access_errors() 1 +#else +#define print_access_errors() 0 +#endif + +#ifdef CONFIG_MSM_L2_ERP_2BIT_PANIC +#define ERP_2BIT_ERR(a) panic(a) +#else +#define ERP_2BIT_ERR(a) do { } while (0) +#endif + +#define MODULE_NAME "msm_cache_erp" + +struct msm_l1_err_stats { + unsigned int dctpe; + unsigned int dcdpe; + unsigned int ictpe; + unsigned int icdpe; + unsigned int dcte; + unsigned int icte; + unsigned int tlbmh; +}; + +struct msm_l2_err_stats { + unsigned int mpdcd; + unsigned int mpslv; + unsigned int tsesb; + unsigned int tsedb; + unsigned int dsesb; + unsigned int dsedb; + unsigned int mse; + unsigned int mplxrexnok; +}; + +static DEFINE_PER_CPU(struct msm_l1_err_stats, msm_l1_erp_stats); +static struct msm_l2_err_stats msm_l2_erp_stats; + +static int l1_erp_irq, l2_erp_irq; +static struct proc_dir_entry *procfs_entry; + +static inline unsigned int read_cesr(void) +{ + unsigned int cesr; + asm volatile ("mrc p15, 7, %0, c15, c0, 1" : "=r" (cesr)); + return cesr; +} + +static inline void write_cesr(unsigned int cesr) +{ + asm volatile ("mcr p15, 7, %[cesr], c15, c0, 1" : : [cesr]"r" (cesr)); +} + +static inline unsigned int read_cesynr(void) +{ + unsigned int cesynr; + asm volatile ("mrc p15, 7, %0, c15, c0, 3" : "=r" (cesynr)); + return cesynr; +} + +static int proc_read_status(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct msm_l1_err_stats *l1_stats; + char *p = page; + int len, cpu, ret, bytes_left = PAGE_SIZE; + + for_each_present_cpu(cpu) { + l1_stats = &per_cpu(msm_l1_erp_stats, cpu); + + ret = snprintf(p, bytes_left, + "CPU %d:\n" \ + "\tD-cache tag parity errors:\t%u\n" \ + "\tD-cache data parity errors:\t%u\n" \ + "\tI-cache tag parity errors:\t%u\n" \ + "\tI-cache data parity errors:\t%u\n" \ + "\tD-cache timing errors:\t\t%u\n" \ + "\tI-cache timing errors:\t\t%u\n" \ + "\tTLB multi-hit errors:\t\t%u\n\n", \ + cpu, + l1_stats->dctpe, + l1_stats->dcdpe, + l1_stats->ictpe, + l1_stats->icdpe, + l1_stats->dcte, + l1_stats->icte, + l1_stats->tlbmh); + p += ret; + bytes_left -= ret; + } + + p += snprintf(p, bytes_left, + "L2 master port decode errors:\t\t%u\n" \ + "L2 master port slave errors:\t\t%u\n" \ + "L2 tag soft errors, single-bit:\t\t%u\n" \ + "L2 tag soft errors, double-bit:\t\t%u\n" \ + "L2 data soft errors, single-bit:\t%u\n" \ + "L2 data soft errors, double-bit:\t%u\n" \ + "L2 modified soft errors:\t\t%u\n" \ + "L2 master port LDREX NOK errors:\t%u\n", + msm_l2_erp_stats.mpdcd, + msm_l2_erp_stats.mpslv, + msm_l2_erp_stats.tsesb, + msm_l2_erp_stats.tsedb, + msm_l2_erp_stats.dsesb, + msm_l2_erp_stats.dsedb, + msm_l2_erp_stats.mse, + msm_l2_erp_stats.mplxrexnok); + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; +} + +static irqreturn_t msm_l1_erp_irq(int irq, void *dev_id) +{ + struct msm_l1_err_stats *l1_stats = dev_id; + unsigned int cesr = read_cesr(); + unsigned int i_cesynr, d_cesynr; + unsigned int cpu = smp_processor_id(); + int print_regs = cesr & CESR_PRINT_MASK; + + void *const saw_bases[] = { + MSM_SAW0_BASE, + MSM_SAW1_BASE, + MSM_SAW2_BASE, + MSM_SAW3_BASE, + }; + + if (print_regs) { + pr_alert("L1 / TLB Error detected on CPU %d!\n", cpu); + pr_alert("\tCESR = 0x%08x\n", cesr); + pr_alert("\tCPU speed = %lu\n", acpuclk_get_rate(cpu)); + pr_alert("\tMIDR = 0x%08x\n", read_cpuid_id()); + pr_alert("\tPTE fuses = 0x%08x\n", + readl_relaxed(MSM_QFPROM_BASE + 0xC0)); + pr_alert("\tPMIC_VREG = 0x%08x\n", + readl_relaxed(saw_bases[cpu] + 0x14)); + } + + if (cesr & CESR_DCTPE) { + pr_alert("D-cache tag parity error\n"); + l1_stats->dctpe++; + } + + if (cesr & CESR_DCDPE) { + pr_alert("D-cache data parity error\n"); + l1_stats->dcdpe++; + } + + if (cesr & CESR_ICTPE) { + pr_alert("I-cache tag parity error\n"); + l1_stats->ictpe++; + } + + if (cesr & CESR_ICDPE) { + pr_alert("I-cache data parity error\n"); + l1_stats->icdpe++; + } + + if (cesr & CESR_DCTE) { + pr_alert("D-cache timing error\n"); + l1_stats->dcte++; + } + + if (cesr & CESR_ICTE) { + pr_alert("I-cache timing error\n"); + l1_stats->icte++; + } + + if (cesr & CESR_TLBMH) { + asm ("mcr p15, 0, r0, c8, c7, 0"); + l1_stats->tlbmh++; + } + + if (cesr & (CESR_ICTPE | CESR_ICDPE | CESR_ICTE)) { + i_cesynr = read_cesynr(); + pr_alert("I-side CESYNR = 0x%08x\n", i_cesynr); + write_cesr(CESR_I_MASK); + + /* + * Clear the I-side bits from the captured CESR value so that we + * don't accidentally clear any new I-side errors when we do + * the CESR write-clear operation. + */ + cesr &= ~CESR_I_MASK; + } + + if (cesr & (CESR_DCTPE | CESR_DCDPE | CESR_DCTE)) { + d_cesynr = read_cesynr(); + pr_alert("D-side CESYNR = 0x%08x\n", d_cesynr); + } + + /* Clear the interrupt bits we processed */ + write_cesr(cesr); + + if (print_regs) + ERP_L1_ERR("L1 cache error detected"); + + return IRQ_HANDLED; +} + +static irqreturn_t msm_l2_erp_irq(int irq, void *dev_id) +{ + unsigned int l2esr; + unsigned int l2esynr0; + unsigned int l2esynr1; + unsigned int l2ear0; + unsigned int l2ear1; + int soft_error = 0; + int port_error = 0; + int unrecoverable = 0; + int print_alert; + + l2esr = get_l2_indirect_reg(L2ESR_IND_ADDR); + l2esynr0 = get_l2_indirect_reg(L2ESYNR0_IND_ADDR); + l2esynr1 = get_l2_indirect_reg(L2ESYNR1_IND_ADDR); + l2ear0 = get_l2_indirect_reg(L2EAR0_IND_ADDR); + l2ear1 = get_l2_indirect_reg(L2EAR1_IND_ADDR); + + print_alert = print_access_errors() || (l2esr & L2ESR_ACCESS_ERR_MASK); + + if (print_alert) { + pr_alert("L2 Error detected!\n"); + pr_alert("\tL2ESR = 0x%08x\n", l2esr); + pr_alert("\tL2ESYNR0 = 0x%08x\n", l2esynr0); + pr_alert("\tL2ESYNR1 = 0x%08x\n", l2esynr1); + pr_alert("\tL2EAR0 = 0x%08x\n", l2ear0); + pr_alert("\tL2EAR1 = 0x%08x\n", l2ear1); + pr_alert("\tCPU bitmap = 0x%x\n", (l2esr >> L2ESR_CPU_SHIFT) & + L2ESR_CPU_MASK); + } + + if (l2esr & L2ESR_MPDCD) { + if (print_alert) + pr_alert("L2 master port decode error\n"); + port_error++; + msm_l2_erp_stats.mpdcd++; + } + + if (l2esr & L2ESR_MPSLV) { + if (print_alert) + pr_alert("L2 master port slave error\n"); + port_error++; + msm_l2_erp_stats.mpslv++; + } + + if (l2esr & L2ESR_TSESB) { + pr_alert("L2 tag soft error, single-bit\n"); + soft_error++; + msm_l2_erp_stats.tsesb++; + } + + if (l2esr & L2ESR_TSEDB) { + pr_alert("L2 tag soft error, double-bit\n"); + soft_error++; + unrecoverable++; + msm_l2_erp_stats.tsedb++; + } + + if (l2esr & L2ESR_DSESB) { + pr_alert("L2 data soft error, single-bit\n"); + soft_error++; + msm_l2_erp_stats.dsesb++; + } + + if (l2esr & L2ESR_DSEDB) { + pr_alert("L2 data soft error, double-bit\n"); + soft_error++; + unrecoverable++; + msm_l2_erp_stats.dsedb++; + } + + if (l2esr & L2ESR_MSE) { + pr_alert("L2 modified soft error\n"); + soft_error++; + msm_l2_erp_stats.mse++; + } + + if (l2esr & L2ESR_MPLDREXNOK) { + pr_alert("L2 master port LDREX received Normal OK response\n"); + port_error++; + msm_l2_erp_stats.mplxrexnok++; + } + + if (port_error && print_alert) + ERP_PORT_ERR("L2 master port error detected"); + + if (soft_error && !unrecoverable) + ERP_1BIT_ERR("L2 single-bit error detected"); + + if (unrecoverable) + ERP_2BIT_ERR("L2 double-bit error detected, trouble ahead"); + + set_l2_indirect_reg(L2ESR_IND_ADDR, l2esr); + return IRQ_HANDLED; +} + +static void enable_erp_irq_callback(void *info) +{ + enable_percpu_irq(l1_erp_irq, IRQ_TYPE_LEVEL_HIGH); +} + +static void disable_erp_irq_callback(void *info) +{ + disable_percpu_irq(l1_erp_irq); +} + +static int cache_erp_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + switch (action & (~CPU_TASKS_FROZEN)) { + case CPU_STARTING: + enable_erp_irq_callback(NULL); + break; + + case CPU_DYING: + disable_erp_irq_callback(NULL); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block cache_erp_cpu_notifier = { + .notifier_call = cache_erp_cpu_callback, +}; + +static int msm_cache_erp_probe(struct platform_device *pdev) +{ + struct resource *r; + int ret, cpu; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "l1_irq"); + + if (!r) { + pr_err("Could not get L1 resource\n"); + ret = -ENODEV; + goto fail; + } + + l1_erp_irq = r->start; + + ret = request_percpu_irq(l1_erp_irq, msm_l1_erp_irq, "MSM_L1", + &msm_l1_erp_stats); + + if (ret) { + pr_err("Failed to request the L1 cache error interrupt\n"); + goto fail; + } + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "l2_irq"); + + if (!r) { + pr_err("Could not get L2 resource\n"); + ret = -ENODEV; + goto fail_l1; + } + + l2_erp_irq = r->start; + ret = request_irq(l2_erp_irq, msm_l2_erp_irq, 0, "MSM_L2", NULL); + + if (ret) { + pr_err("Failed to request the L2 cache error interrupt\n"); + goto fail_l1; + } + + procfs_entry = create_proc_entry("cpu/msm_cache_erp", S_IRUGO, NULL); + + if (!procfs_entry) { + pr_err("Failed to create procfs node for cache error reporting\n"); + ret = -ENODEV; + goto fail_l2; + } + + get_online_cpus(); + register_hotcpu_notifier(&cache_erp_cpu_notifier); + for_each_cpu(cpu, cpu_online_mask) + smp_call_function_single(cpu, enable_erp_irq_callback, NULL, 1); + put_online_cpus(); + + procfs_entry->read_proc = proc_read_status; + return 0; + +fail_l2: + free_irq(l2_erp_irq, NULL); +fail_l1: + free_percpu_irq(l1_erp_irq, NULL); +fail: + return ret; +} + +static int msm_cache_erp_remove(struct platform_device *pdev) +{ + int cpu; + if (procfs_entry) + remove_proc_entry("cpu/msm_cache_erp", NULL); + + get_online_cpus(); + unregister_hotcpu_notifier(&cache_erp_cpu_notifier); + for_each_cpu(cpu, cpu_online_mask) + smp_call_function_single(cpu, disable_erp_irq_callback, NULL, + 1); + put_online_cpus(); + + free_percpu_irq(l1_erp_irq, NULL); + + disable_irq(l2_erp_irq); + free_irq(l2_erp_irq, NULL); + return 0; +} + +static struct platform_driver msm_cache_erp_driver = { + .probe = msm_cache_erp_probe, + .remove = msm_cache_erp_remove, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_cache_erp_init(void) +{ + return platform_driver_register(&msm_cache_erp_driver); +} + +static void __exit msm_cache_erp_exit(void) +{ + platform_driver_unregister(&msm_cache_erp_driver); +} + + +module_init(msm_cache_erp_init); +module_exit(msm_cache_erp_exit); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM cache error reporting driver"); diff --git a/arch/arm/mach-msm/clock-7x30.c b/arch/arm/mach-msm/clock-7x30.c new file mode 100644 index 0000000000000000000000000000000000000000..aa94be64dd5c9ad39af07ebc70849d495bd1057f --- /dev/null +++ b/arch/arm/mach-msm/clock-7x30.c @@ -0,0 +1,3024 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "clock.h" +#include "clock-local.h" +#include "clock-pcom.h" +#include "clock-voter.h" +#include "clock-pll.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 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 N8(msb, lsb, m, n) (BVAL(msb, lsb, ~(n-m)) | BVAL(6, 5, \ + (MN_MODE_DUAL_EDGE * !!(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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = SDIV(SRC_SEL_##s, div), \ + } + +#define F_MND16(f, s, div, m, n) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = N16(m, n) | SPDIV(SRC_SEL_##s, div), \ + } + +#define F_MND8(f, nmsb, nlsb, s, div, m, n) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = N8(nmsb, nlsb, m, n) | SPDIV(SRC_SEL_##s, div), \ + } + +enum vdd_dig_levels { + VDD_DIG_NONE, + VDD_DIG_LOW, + VDD_DIG_NOMINAL, + VDD_DIG_HIGH +}; + +static int set_vdd_dig(struct clk_vdd_class *vdd_class, int level) +{ + int rc, target_mv; + + static const int mv[] = { + [VDD_DIG_NONE] = 1000, + [VDD_DIG_LOW] = 1000, + [VDD_DIG_NOMINAL] = 1100, + [VDD_DIG_HIGH] = 1200 + }; + + target_mv = mv[level]; + rc = msm_proc_comm(PCOM_CLKCTL_RPC_MIN_MSMC1, &target_mv, NULL); + if (rc) + return rc; + if (target_mv) + rc = -EINVAL; + + return rc; +} + +static DEFINE_VDD_CLASS(vdd_dig, set_vdd_dig); + +#define VDD_DIG_FMAX_MAP1(l1, f1) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1) +#define VDD_DIG_FMAX_MAP2(l1, f1, l2, f2) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2) + +#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 enum handoff xo_clk_handoff(struct clk *clk) +{ + return HANDOFF_ENABLED_CLK; +} + +static struct clk_ops clk_ops_tcxo = { + .enable = tcxo_clk_enable, + .disable = tcxo_clk_disable, + .handoff = xo_clk_handoff, + .is_local = pcom_is_local, +}; + +static struct fixed_clk tcxo_clk = { + .c = { + .dbg_name = "tcxo_clk", + .rate = 19200000, + .ops = &clk_ops_tcxo, + CLK_INIT(tcxo_clk.c), + .warned = true, + }, +}; + +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, + .handoff = xo_clk_handoff, + .is_local = pcom_is_local, +}; + +static struct fixed_clk lpxo_clk = { + .c = { + .dbg_name = "lpxo_clk", + .rate = 24576000, + .ops = &clk_ops_lpxo, + CLK_INIT(lpxo_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll1_clk = { + .en_reg = PLL_ENA_REG, + .en_mask = BIT(1), + .status_reg = PLL1_STATUS_BASE_REG, + .status_mask = BIT(16), + .parent = &tcxo_clk.c, + .c = { + .dbg_name = "pll1_clk", + .rate = 768000000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll1_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll2_clk = { + .en_reg = PLL_ENA_REG, + .en_mask = BIT(2), + .status_reg = PLL2_STATUS_BASE_REG, + .status_mask = BIT(16), + .parent = &tcxo_clk.c, + .c = { + .dbg_name = "pll2_clk", + .rate = 806400000, /* TODO: Support scaling */ + .ops = &clk_ops_pll_vote, + CLK_INIT(pll2_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll3_clk = { + .en_reg = PLL_ENA_REG, + .en_mask = BIT(3), + .status_reg = PLL3_STATUS_BASE_REG, + .status_mask = BIT(16), + .parent = &lpxo_clk.c, + .c = { + .dbg_name = "pll3_clk", + .rate = 737280000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll3_clk.c), + }, +}; + +static struct pll_vote_clk pll4_clk = { + .en_reg = PLL_ENA_REG, + .en_mask = BIT(4), + .status_reg = PLL4_STATUS_BASE_REG, + .status_mask = BIT(16), + .parent = &lpxo_clk.c, + .c = { + .dbg_name = "pll4_clk", + .rate = 891000000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll4_clk.c), + .warned = true, + }, +}; + +static struct clk_freq_tbl clk_tbl_axi[] = { + F_RAW(1, &lpxo_clk.c, 0, 0, 0, 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 = &rcg_dummy_freq, + .set_rate = set_rate_nop, + .c = { + .dbg_name = "glbl_root_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 1), + 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), + F_MND8(153600000, 24, 17, pll1, 2, 2, 5), + F_MND8(192000000, 24, 17, pll1, 4, 0, 0), + F_MND8(384000000, 24, 17, pll1, 2, 0, 0), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_csi, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "csi0_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 384000000), + CLK_INIT(csi0_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_tcxo[] = { + F_RAW(19200000, &tcxo_clk.c, 0, 0, 0, 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "i2c_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 19200000), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "i2c_2_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 19200000), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "qup_i2c_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 19200000), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "uart1_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 19200000), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "uart2_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 19200000), + CLK_INIT(uart2_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_uartdm[] = { + F_MND16( 0, gnd, 1, 0, 0), + F_MND16( 3686400, pll3, 3, 3, 200), + F_MND16( 7372800, pll3, 3, 3, 100), + F_MND16(14745600, pll3, 3, 3, 50), + F_MND16(32000000, pll3, 3, 25, 192), + F_MND16(40000000, pll3, 3, 125, 768), + F_MND16(46400000, pll3, 3, 145, 768), + F_MND16(48000000, pll3, 3, 25, 128), + F_MND16(51200000, pll3, 3, 5, 24), + F_MND16(56000000, pll3, 3, 175, 768), + F_MND16(58982400, pll3, 3, 6, 25), + F_MND16(64000000, pll1, 4, 1, 3), + 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), + .mnd_en_mask = BIT(8), + .freq_tbl = clk_tbl_uartdm, + .ns_mask = F_MASK_MND16, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "uart1dm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 64000000), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "uart2dm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 64000000), + CLK_INIT(uart2dm_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdh[] = { + F_BASIC( 0, gnd, 1), + F_BASIC( 49150000, pll3, 15), + F_BASIC( 92160000, pll3, 8), + F_BASIC(122880000, pll3, 6), + F_BASIC(184320000, pll3, 4), + F_BASIC(245760000, pll3, 3), + F_BASIC(368640000, pll3, 2), + F_BASIC(384000000, pll1, 2), + F_BASIC(445500000, pll4, 2), + 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, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdh, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "emdh_clk", + .flags = CLKFLAG_MIN | CLKFLAG_MAX, + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 445500000), + CLK_INIT(emdh_clk.c), + .depends = &axi_li_adsp_a_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, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdh, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "pmdh_clk", + .flags = CLKFLAG_MIN | CLKFLAG_MAX, + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 445500000), + CLK_INIT(pmdh_clk.c), + .depends = &axi_li_adsp_a_clk.c, + }, +}; + +static struct clk_freq_tbl clk_tbl_grp[] = { + F_BASIC( 24576000, lpxo, 1), + F_BASIC( 46080000, pll3, 16), + F_BASIC( 49152000, pll3, 15), + F_BASIC( 52662875, pll3, 14), + F_BASIC( 56713846, pll3, 13), + F_BASIC( 61440000, pll3, 12), + F_BASIC( 67025454, pll3, 11), + F_BASIC( 73728000, pll3, 10), + F_BASIC( 81920000, pll3, 9), + F_BASIC( 92160000, pll3, 8), + F_BASIC(105325714, pll3, 7), + F_BASIC(122880000, pll3, 6), + F_BASIC(147456000, pll3, 5), + F_BASIC(184320000, pll3, 4), + F_BASIC(192000000, pll1, 4), + F_BASIC(245760000, pll3, 3), + /* Sync to AXI. Hence this "rate" is not fixed. */ + F_RAW(1, &lpxo_clk.c, 0, BIT(14), 0, 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, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "grp_2d_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(NOMINAL, 192000000, HIGH, 245760000), + CLK_INIT(grp_2d_clk.c), + .depends = &axi_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, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "grp_3d_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(NOMINAL, 192000000, HIGH, 245760000), + CLK_INIT(grp_3d_src_clk.c), + .depends = &axi_li_grp_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), + F_MND8( 144000, 19, 12, lpxo, 1, 1, 171), + F_MND8( 400000, 19, 12, lpxo, 1, 2, 123), + F_MND8(16027000, 19, 12, pll3, 3, 14, 215), + F_MND8(17000000, 19, 12, pll3, 4, 19, 206), + F_MND8(20480000, 19, 12, pll3, 4, 23, 212), + F_MND8(24576000, 19, 12, lpxo, 1, 0, 0), + F_MND8(49152000, 19, 12, pll3, 3, 1, 5), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc1_3, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc1_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 49152000), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc1_3, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc3_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 49152000), + CLK_INIT(sdc3_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_sdc2_4[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0), + F_MND8( 144000, 20, 13, lpxo, 1, 1, 171), + F_MND8( 400000, 20, 13, lpxo, 1, 2, 123), + F_MND8(16027000, 20, 13, pll3, 3, 14, 215), + F_MND8(17000000, 20, 13, pll3, 4, 19, 206), + F_MND8(20480000, 20, 13, pll3, 4, 23, 212), + F_MND8(24576000, 20, 13, lpxo, 1, 0, 0), + F_MND8(49152000, 20, 13, pll3, 3, 1, 5), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc2_4, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc2_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 49152000), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc2_4, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc4_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 49152000), + CLK_INIT(sdc4_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdp_core[] = { + F_BASIC( 24576000, lpxo, 1), + F_BASIC( 46080000, pll3, 16), + F_BASIC( 49152000, pll3, 15), + F_BASIC( 52663000, pll3, 14), + F_BASIC( 92160000, pll3, 8), + F_BASIC(122880000, pll3, 6), + F_BASIC(147456000, pll3, 5), + F_BASIC(153600000, pll1, 5), + F_BASIC(192000000, pll1, 4), + 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, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdp_core, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(NOMINAL, 153600000, HIGH, 192000000), + CLK_INIT(mdp_clk.c), + .depends = &axi_mdp_clk.c, + }, +}; + +static struct clk_freq_tbl clk_tbl_mdp_lcdc[] = { + F_MND16( 0, gnd, 1, 0, 0), + F_MND16(24576000, lpxo, 1, 0, 0), + F_MND16(30720000, pll3, 4, 1, 6), + F_MND16(32768000, pll3, 3, 2, 15), + F_MND16(40960000, pll3, 2, 1, 9), + F_MND16(73728000, pll3, 2, 1, 5), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mdp_lcdc, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_lcdc_pclk_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 73728000), + 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, NULL), + F_RAW(24576000, &lpxo_clk.c, 0, (0x1<<2), 0, 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_vsync_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 24576000), + CLK_INIT(mdp_vsync_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mi2s_codec[] = { + F_MND16( 0, gnd, 1, 0, 0), + F_MND16( 2048000, lpxo, 4, 1, 3), + F_MND16(12288000, lpxo, 2, 0, 0), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mi2s_codec, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mi2s_codec_rx_m_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 12288000), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mi2s_codec, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mi2s_codec_tx_m_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 12288000), + 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), + F_MND16(12288000, lpxo, 2, 0, 0), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mi2s, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mi2s_m_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 12288000), + 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), + }, +}; + +#define F_SDAC(f, s, div, m, n) \ + { \ + .freq_hz = f, \ + .md_val = MD16(m, n), \ + .ns_val = N16(m, n) | SPDIV(SRC_SEL_SDAC_##s, div), \ + .src_clk = &s##_clk.c, \ + } + +static struct clk_freq_tbl clk_tbl_sdac[] = { + F_SDAC( 256000, lpxo, 4, 1, 24), + F_SDAC( 352800, lpxo, 1, 147, 10240), + F_SDAC( 384000, lpxo, 4, 1, 16), + F_SDAC( 512000, lpxo, 4, 1, 12), + F_SDAC( 705600, lpxo, 1, 147, 5120), + F_SDAC( 768000, lpxo, 4, 1, 8), + F_SDAC(1024000, lpxo, 4, 1, 6), + F_SDAC(1411200, lpxo, 1, 147, 2560), + F_SDAC(1536000, lpxo, 4, 1, 4), + 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), + .mnd_en_mask = BIT(8), + .freq_tbl = clk_tbl_sdac, + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "sdac_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 1536000), + 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), + F_MND8(27000000, 23, 16, pll4, 2, 2, 33), + F_MND8(74250000, 23, 16, pll4, 2, 1, 6), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_tv, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "tv_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 74250000), + 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), + F_MND8(60000000, 23, 16, pll1, 2, 5, 32), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_usb, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "usb_hs_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 60000000), + CLK_INIT(usb_hs_src_clk.c), + .depends = &axi_li_adsp_a_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), + F_MND16( 36864000, pll3, 4, 1, 5), + F_MND16( 46080000, pll3, 4, 1, 4), + F_MND16( 61440000, pll3, 4, 1, 3), + F_MND16( 73728000, pll3, 2, 1, 5), + F_MND16( 81920000, pll3, 3, 1, 3), + F_MND16( 92160000, pll3, 4, 1, 2), + F_MND16( 98304000, pll3, 3, 2, 5), + F_MND16(105326000, pll3, 2, 2, 7), + F_MND16(122880000, pll3, 2, 1, 3), + F_MND16(147456000, pll3, 2, 2, 5), + F_MND16(153600000, pll1, 2, 2, 5), + F_MND16(192000000, pll1, 4, 0, 0), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "jpeg_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(NOMINAL, 153600000, HIGH, 192000000), + CLK_INIT(jpeg_clk.c), + .depends = &axi_li_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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "vfe_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(NOMINAL, 153600000, HIGH, 192000000), + CLK_INIT(vfe_clk.c), + .depends = &axi_li_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), + F_MND16( 6000000, pll1, 4, 1, 32), + F_MND16( 8000000, pll1, 4, 1, 24), + F_MND16(12000000, pll1, 4, 1, 16), + F_MND16(16000000, pll1, 4, 1, 12), + F_MND16(19200000, pll1, 4, 1, 10), + F_MND16(24000000, pll1, 4, 1, 8), + F_MND16(32000000, pll1, 4, 1, 6), + F_MND16(48000000, pll1, 4, 1, 4), + F_MND16(64000000, pll1, 4, 1, 3), + 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, + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "cam_m_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 64000000), + CLK_INIT(cam_m_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_vpe[] = { + F_MND8( 24576000, 22, 15, lpxo, 1, 0, 0), + F_MND8( 30720000, 22, 15, pll3, 4, 1, 6), + F_MND8( 61440000, 22, 15, pll3, 4, 1, 3), + F_MND8( 81920000, 22, 15, pll3, 3, 1, 3), + F_MND8(122880000, 22, 15, pll3, 3, 1, 2), + F_MND8(147456000, 22, 15, pll3, 1, 1, 5), + F_MND8(153600000, 22, 15, pll1, 1, 1, 5), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_vpe, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "vpe_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 153600000), + CLK_INIT(vpe_clk.c), + .depends = &axi_vpe_clk.c, + }, +}; + +static struct clk_freq_tbl clk_tbl_mfc[] = { + F_MND8( 24576000, 24, 17, lpxo, 1, 0, 0), + F_MND8( 30720000, 24, 17, pll3, 4, 1, 6), + F_MND8( 61440000, 24, 17, pll3, 4, 1, 3), + F_MND8( 81920000, 24, 17, pll3, 3, 1, 3), + F_MND8(122880000, 24, 17, pll3, 3, 1, 2), + F_MND8(147456000, 24, 17, pll3, 1, 1, 5), + F_MND8(153600000, 24, 17, pll1, 1, 1, 5), + F_MND8(170667000, 24, 17, pll1, 1, 2, 9), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_mfc, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "mfc_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 170667000), + CLK_INIT(mfc_clk.c), + .depends = &axi_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), + F_MND8( 9963243, 19, 12, pll3, 4, 2, 37), + F_MND8(24576000, 19, 12, lpxo, 1, 0, 0), + F_MND8(26331429, 19, 12, pll3, 4, 1, 7), + 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), + .mnd_en_mask = BIT(8), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_spi, + .current_freq = &rcg_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "spi_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 26331429), + CLK_INIT(spi_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_lpa_codec[] = { + F_RAW(1, NULL, 0, 0, 0, NULL), /* src MI2S_CODEC_RX */ + F_RAW(2, NULL, 0, 1, 0, NULL), /* src ECODEC_CIF */ + F_RAW(3, NULL, 0, 2, 0, NULL), /* src MI2S */ + F_RAW(4, NULL, 0, 3, 0, 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "lpa_codec_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 4), + CLK_INIT(lpa_codec_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdc[] = { + F_RAW(1, NULL, 0, 0, 0, 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 = &rcg_dummy_freq, + .set_rate = set_rate_nop, + .c = { + .dbg_name = "mdc_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 1), + 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, CLKFLAG_MIN); +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, CLKFLAG_MIN); +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, 0); +static DEFINE_CLK_VOTER(ebi_grp_3d_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_grp_2d_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_lcdc_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_mddi_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_tv_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_vcd_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_vfe_clk, &ebi1_fixed_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_adm_clk, &ebi1_fixed_clk.c, 0); + +#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(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 unsigned long 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 long 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_prepare_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_unprepare(&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 long measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops clk_ops_measure = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, +}; + +static struct clk measure_clk = { + .dbg_name = "measure_clk", + .ops = &clk_ops_measure, + 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("core_clk", gp_clk.c, "") }, + { CLK_LOOKUP("core_clk", uart3_clk.c, "msm_serial.2") }, + { CLK_LOOKUP("phy_clk", usb_phy_clk.c, "msm_otg") }, + + /* Voters */ + { CLK_LOOKUP("mem_clk", ebi_dtv_clk.c, "dtv.0") }, + { CLK_LOOKUP("bus_clk", ebi_grp_2d_clk.c, "kgsl-2d0.0") }, + { CLK_LOOKUP("bus_clk", ebi_grp_3d_clk.c, "kgsl-3d0.0") }, + { CLK_LOOKUP("mem_clk", ebi_lcdc_clk.c, "lcdc.0") }, + { CLK_LOOKUP("mem_clk", ebi_mddi_clk.c, "mddi.0") }, + { CLK_LOOKUP("mem_clk", ebi_tv_clk.c, "tvenc.0") }, + { CLK_LOOKUP("mem_clk", ebi_vcd_clk.c, "msm_vidc.0") }, + { CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL) }, + { CLK_LOOKUP("mem_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, "core_clk", grp_2d_clk, "kgsl-2d0.0"), + OWN(APPS1, 6, "core_clk", grp_2d_clk, "footswitch-pcom.0"), + OWN(APPS1, 6, "iface_clk", grp_2d_p_clk, "kgsl-2d0.0"), + OWN(APPS1, 6, "iface_clk", grp_2d_p_clk, "footswitch-pcom.0"), + OWN(APPS1, 31, "hdmi_clk", hdmi_clk, "dtv.0"), + OWN(APPS1, 0, "core_clk", jpeg_clk, "msm_gemini.0"), + OWN(APPS1, 0, "iface_clk", jpeg_p_clk, "msm_gemini.0"), + 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), + 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, "core_clk", vfe_clk, "footswitch-pcom.8"), + 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(APPS1, 8, "iface_clk", vfe_p_clk, "footswitch-pcom.8"), + + OWN(APPS2, 0, "core_clk", grp_3d_clk, "kgsl-3d0.0"), + OWN(APPS2, 0, "core_clk", grp_3d_clk, "footswitch-pcom.2"), + OWN(APPS2, 0, "iface_clk", grp_3d_p_clk, "kgsl-3d0.0"), + OWN(APPS2, 0, "iface_clk", grp_3d_p_clk, "footswitch-pcom.2"), + { CLK_LOOKUP("src_clk", grp_3d_src_clk.c, "kgsl-3d0.0"), + O(APPS2), BIT(0), &p_grp_3d_clk.c }, + { CLK_LOOKUP("src_clk", grp_3d_src_clk.c, "footswitch-pcom.2"), + O(APPS2), BIT(0), &p_grp_3d_clk.c }, + OWN(APPS2, 0, "mem_clk", imem_clk, "kgsl-3d0.0"), + OWN(APPS2, 4, "lcdc_clk", mdp_lcdc_pad_pclk_clk, "lcdc.0"), + OWN(APPS2, 4, "mdp_clk", mdp_lcdc_pclk_clk, "lcdc.0"), + OWN(APPS2, 4, "iface_clk", mdp_p_clk, "mdp.0"), + OWN(APPS2, 4, "iface_clk", mdp_p_clk, "footswitch-pcom.4"), + OWN(APPS2, 28, "vsync_clk", mdp_vsync_clk, "mdp.0"), + OWN(APPS2, 5, "ref_clk", tsif_ref_clk, "msm_tsif.0"), + OWN(APPS2, 5, "iface_clk", tsif_p_clk, "msm_tsif.0"), + { CLK_LOOKUP("src_clk", tv_clk.c, "dtv.0"), + 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, "core_clk", emdh_clk, "msm_mddi.1"), + OWN(ROW1, 7, "iface_clk", emdh_p_clk, "msm_mddi.1"), + OWN(ROW1, 11, "core_clk", i2c_clk, "msm_i2c.0"), + OWN(ROW1, 12, "core_clk", i2c_2_clk, "msm_i2c.2"), + OWN(ROW1, 17, "mdc_clk", mdc_clk, NULL), + OWN(ROW1, 19, "core_clk", pmdh_clk, "mddi.0"), + OWN(ROW1, 19, "iface_clk", pmdh_p_clk, "mddi.0"), + OWN(ROW1, 23, "core_clk", sdc1_clk, "msm_sdcc.1"), + OWN(ROW1, 23, "iface_clk", sdc1_p_clk, "msm_sdcc.1"), + OWN(ROW1, 25, "core_clk", sdc2_clk, "msm_sdcc.2"), + OWN(ROW1, 25, "iface_clk", sdc2_p_clk, "msm_sdcc.2"), + OWN(ROW1, 27, "core_clk", sdc3_clk, "msm_sdcc.3"), + OWN(ROW1, 27, "iface_clk", sdc3_p_clk, "msm_sdcc.3"), + OWN(ROW1, 29, "core_clk", sdc4_clk, "msm_sdcc.4"), + OWN(ROW1, 29, "iface_clk", sdc4_p_clk, "msm_sdcc.4"), + OWN(ROW1, 0, "core_clk", uart2_clk, "msm_serial.1"), + OWN(ROW1, 2, "alt_core_clk", usb_hs2_clk, "msm_hsusb_host.0"), + OWN(ROW1, 2, "core_clk", usb_hs2_core_clk, "msm_hsusb_host.0"), + OWN(ROW1, 2, "iface_clk", usb_hs2_p_clk, "msm_hsusb_host.0"), + OWN(ROW1, 4, "alt_core_clk", usb_hs3_clk, ""), + OWN(ROW1, 4, "core_clk", usb_hs3_core_clk, ""), + OWN(ROW1, 4, "iface_clk", usb_hs3_p_clk, ""), + + OWN(ROW2, 3, "core_clk", qup_i2c_clk, "qup_i2c.4"), + OWN(ROW2, 1, "core_clk", spi_clk, "spi_qsd.0"), + OWN(ROW2, 1, "iface_clk", spi_p_clk, "spi_qsd.0"), + OWN(ROW2, 9, "core_clk", uart1_clk, "msm_serial.0"), + OWN(ROW2, 6, "core_clk", uart1dm_clk, "msm_serial_hs.0"), + OWN(ROW2, 8, "core_clk", uart2dm_clk, "msm_serial_hs.1"), + OWN(ROW2, 11, "alt_core_clk", usb_hs_clk, "msm_otg"), + OWN(ROW2, 11, "core_clk", usb_hs_core_clk, "msm_otg"), + OWN(ROW2, 11, "iface_clk", usb_hs_p_clk, "msm_otg"), + + OWN(APPS3, 6, "cam_m_clk", cam_m_clk, NULL), + OWN(APPS3, 6, "cam_clk", cam_m_clk, "4-0020"), + OWN(APPS3, 6, "camif_pad_pclk", camif_pad_p_clk, NULL), + OWN(APPS3, 6, "iface_clk", camif_pad_p_clk, "qup_i2c.4"), + 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, "core_clk", mdp_clk, "mdp.0"), + OWN(APPS3, 0, "core_clk", mdp_clk, "footswitch-pcom.4"), + OWN(APPS3, 2, "core_clk", mfc_clk, "msm_vidc.0"), + OWN(APPS3, 2, "core_clk", mfc_clk, "footswitch-pcom.5"), + OWN(APPS3, 2, "core_div2_clk", mfc_div2_clk, "msm_vidc.0"), + OWN(APPS3, 2, "iface_clk", mfc_p_clk, "msm_vidc.0"), + OWN(APPS3, 2, "iface_clk", mfc_p_clk, "footswitch-pcom.5"), + OWN(APPS3, 4, "vpe_clk", vpe_clk, NULL), + OWN(APPS3, 4, "core_clk", vpe_clk, "footswitch-pcom.9"), + + OWN(GLBL, 8, "core_clk", adm_clk, "msm_dmov"), + { CLK_LOOKUP("iface_clk", adm_p_clk.c, "msm_dmov"), + O(GLBL), BIT(13), &dummy_clk }, + OWN(GLBL, 8, "core_clk", ce_clk, "qce.0"), + OWN(GLBL, 8, "core_clk", ce_clk, "crypto.0"), + OWN(GLBL, 13, "core_clk", axi_rotator_clk, "msm_rotator.0"), + OWN(GLBL, 13, "core_clk", axi_rotator_clk, "footswitch-pcom.6"), + OWN(GLBL, 13, "mem_clk", rotator_imem_clk, "msm_rotator.0"), + OWN(GLBL, 13, "iface_clk", rotator_p_clk, "msm_rotator.0"), + OWN(GLBL, 13, "iface_clk", rotator_p_clk, "footswitch-pcom.6"), + { CLK_LOOKUP("iface_clk", uart1dm_p_clk.c, "msm_serial_hs.0"), + O(GLBL), BIT(8), &dummy_clk }, + { CLK_LOOKUP("iface_clk", 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)}, +}; + +static void __init msm7x30_clock_pre_init(void) +{ + int i; + uint32_t val; + + clk_ops_branch.reset = soc_branch_clk_reset; + clk_ops_rcg.reset = msm7x30_clk_reset; + clk_ops_rcg.set_flags = soc_clk_set_flags; + + cache_ownership(); + print_ownership(); + set_clock_ownership(); + + /* 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. */ + + 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); + } +} + +static void __init msm7x30_clock_post_init(void) +{ + clk_set_rate(&usb_hs_src_clk.c, 60000000); + 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(&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); +} + +struct clock_init_data msm7x30_clock_init_data __initdata = { + .table = msm_clocks_7x30, + .size = ARRAY_SIZE(msm_clocks_7x30), + .pre_init = msm7x30_clock_pre_init, + .post_init = msm7x30_clock_post_init, +}; diff --git a/arch/arm/mach-msm/clock-8960.c b/arch/arm/mach-msm/clock-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..a1b9c1c04b38caa416b02895612758318a809f6f --- /dev/null +++ b/arch/arm/mach-msm/clock-8960.c @@ -0,0 +1,6332 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "clock-pll.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)) +#define REG_GCC(off) (MSM_APCS_GCC_BASE + (off)) + +/* Peripheral clock registers. */ +#define ADM0_PBUS_CLK_CTL_REG REG(0x2208) +#define SFAB_SATA_S_HCLK_CTL_REG REG(0x2480) +#define CE1_HCLK_CTL_REG REG(0x2720) +#define CE1_CORE_CLK_CTL_REG REG(0x2724) +#define PRNG_CLK_NS_REG REG(0x2E80) +#define CE3_HCLK_CTL_REG REG(0x36C4) +#define CE3_CORE_CLK_CTL_REG REG(0x36CC) +#define CE3_CLK_SRC_NS_REG REG(0x36C0) +#define DMA_BAM_HCLK_CTL REG(0x25C0) +#define CLK_HALT_AFAB_SFAB_STATEA_REG REG(0x2FC0) +#define CLK_HALT_AFAB_SFAB_STATEB_REG REG(0x2FC4) +#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) +/* 8064 name CLK_HALT_GSS_KPSS_MISC_STATE_REG */ +#define CLK_HALT_MSS_SMPSS_MISC_STATE_REG REG(0x2FDC) +#define CLK_HALT_SFPB_MISC_STATE_REG REG(0x2FD8) +#define CLK_HALT_AFAB_SFAB_STATEB_REG REG(0x2FC4) +#define CLK_TEST_REG REG(0x2FA0) +#define GPn_MD_REG(n) REG(0x2D00+(0x20*(n))) +#define GPn_NS_REG(n) REG(0x2D24+(0x20*(n))) +#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) +/* 8064 name BB_PLL_ENA_APCS_REG */ +#define BB_PLL_ENA_SC0_REG REG(0x34C0) +#define BB_PLL_ENA_RPM_REG REG(0x34A0) +#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 BB_MMCC_PLL2_MODE_REG REG(0x3160) +#define BB_MMCC_PLL2_TEST_CTL_REG REG(0x3170) +#define BB_PLL14_MODE_REG REG(0x31C0) +#define BB_PLL14_L_VAL_REG REG(0x31C4) +#define BB_PLL14_M_VAL_REG REG(0x31C8) +#define BB_PLL14_N_VAL_REG REG(0x31CC) +#define BB_PLL14_TEST_CTL_REG REG(0x31D0) +#define BB_PLL14_CONFIG_REG REG(0x31D4) +#define BB_PLL14_STATUS_REG REG(0x31D8) +#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 RPM_MSG_RAM_HCLK_CTL_REG REG(0x27E0) +#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 SATA_HCLK_CTL_REG REG(0x2C00) +#define SATA_CLK_SRC_NS_REG REG(0x2C08) +#define SATA_RXOOB_CLK_CTL_REG REG(0x2C0C) +#define SATA_PMALIVE_CLK_CTL_REG REG(0x2C10) +#define SATA_PHY_REF_CLK_CTL_REG REG(0x2C14) +#define SATA_ACLK_CTL_REG REG(0x2C20) +#define SATA_PHY_CFG_CLK_CTL_REG REG(0x2C40) +#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_HCLK_FS_REG REG(0x2904) +#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_HS3_HCLK_CTL_REG REG(0x3700) +#define USB_HS3_HCLK_FS_REG REG(0x3704) +#define USB_HS3_RESET_REG REG(0x3710) +#define USB_HS3_XCVR_FS_CLK_MD_REG REG(0X3708) +#define USB_HS3_XCVR_FS_CLK_NS_REG REG(0X370C) +#define USB_HS4_HCLK_CTL_REG REG(0x3720) +#define USB_HS4_HCLK_FS_REG REG(0x3724) +#define USB_HS4_RESET_REG REG(0x3730) +#define USB_HS4_XCVR_FS_CLK_MD_REG REG(0X3728) +#define USB_HS4_XCVR_FS_CLK_NS_REG REG(0X372C) +#define USB_HSIC_HCLK_CTL_REG REG(0x2920) +#define USB_HSIC_HSIC_CLK_CTL_REG REG(0x2B44) +#define USB_HSIC_HSIC_CLK_SRC_CTL_REG REG(0x2B40) +#define USB_HSIC_HSIO_CAL_CLK_CTL_REG REG(0x2B48) +#define USB_HSIC_RESET_REG REG(0x2934) +#define USB_HSIC_SYSTEM_CLK_CTL_REG REG(0x292C) +#define USB_HSIC_XCVR_FS_CLK_MD_REG REG(0x2924) +#define USB_HSIC_XCVR_FS_CLK_NS_REG REG(0x2928) +#define USB_PHY0_RESET_REG REG(0x2E20) +#define PCIE_ALT_REF_CLK_NS_REG REG(0x3860) +#define PCIE_ACLK_CTL_REG REG(0x22C0) +#define PCIE_HCLK_CTL_REG REG(0x22CC) +#define PCIE_PCLK_CTL_REG REG(0x22D0) +#define GPLL1_MODE_REG REG(0x3160) +#define GPLL1_L_VAL_REG REG(0x3164) +#define GPLL1_M_VAL_REG REG(0x3168) +#define GPLL1_N_VAL_REG REG(0x316C) +#define GPLL1_CONFIG_REG REG(0x3174) +#define GPLL1_STATUS_REG REG(0x3178) +#define PXO_SRC_CLK_CTL_REG REG(0x2EA0) + +/* Multimedia clock registers. */ +#define AHB_EN_REG REG_MM(0x0008) +#define AHB_EN2_REG REG_MM(0x0038) +#define AHB_EN3_REG REG_MM(0x0248) +#define AHB_NS_REG REG_MM(0x0004) +#define AXI_NS_REG REG_MM(0x0014) +#define CAMCLK0_NS_REG REG_MM(0x0148) +#define CAMCLK0_CC_REG REG_MM(0x0140) +#define CAMCLK0_MD_REG REG_MM(0x0144) +#define CAMCLK1_NS_REG REG_MM(0x015C) +#define CAMCLK1_CC_REG REG_MM(0x0154) +#define CAMCLK1_MD_REG REG_MM(0x0158) +#define CAMCLK2_NS_REG REG_MM(0x0228) +#define CAMCLK2_CC_REG REG_MM(0x0220) +#define CAMCLK2_MD_REG REG_MM(0x0224) +#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 CSI2_NS_REG REG_MM(0x0234) +#define CSI2_CC_REG REG_MM(0x022C) +#define CSI2_MD_REG REG_MM(0x0230) +#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_BUS_VEC_J_REG REG_MM(0x0240) +#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 VCAP_CC_REG REG_MM(0x0178) +#define VCAP_NS_REG REG_MM(0x021C) +#define VCAP_MD0_REG REG_MM(0x01EC) +#define VCAP_MD1_REG REG_MM(0x0218) +#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 MAXI_EN5_REG REG_MM(0x0244) +#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 MISC_CC3_REG REG_MM(0x0238) +#define MM_PLL1_MODE_REG REG_MM(0x031C) +#define MM_PLL1_L_VAL_REG REG_MM(0x0320) +#define MM_PLL1_M_VAL_REG REG_MM(0x0324) +#define MM_PLL1_N_VAL_REG REG_MM(0x0328) +#define MM_PLL1_CONFIG_REG REG_MM(0x032C) +#define MM_PLL1_TEST_CTL_REG REG_MM(0x0330) +#define MM_PLL1_STATUS_REG REG_MM(0x0334) +#define MM_PLL3_MODE_REG REG_MM(0x0338) +#define MM_PLL3_L_VAL_REG REG_MM(0x033C) +#define MM_PLL3_M_VAL_REG REG_MM(0x0340) +#define MM_PLL3_N_VAL_REG REG_MM(0x0344) +#define MM_PLL3_CONFIG_REG REG_MM(0x0348) +#define MM_PLL3_TEST_CTL_REG REG_MM(0x034C) +#define MM_PLL3_STATUS_REG REG_MM(0x0350) +#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 SW_RESET_CORE2_REG REG_MM(0x0214) +#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 VFE_CC2_REG REG_MM(0x023C) +#define VPE_CC_REG REG_MM(0x0110) +#define VPE_NS_REG REG_MM(0x0118) + +/* Low-power Audio clock registers. */ +#define LCC_CLK_HS_DEBUG_CFG_REG REG_LPA(0x00A4) +#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_MODE_REG REG_LPA(0x0000) +#define LCC_PLL0_L_VAL_REG REG_LPA(0x0004) +#define LCC_PLL0_M_VAL_REG REG_LPA(0x0008) +#define LCC_PLL0_N_VAL_REG REG_LPA(0x000C) +#define LCC_PLL0_CONFIG_REG REG_LPA(0x0014) +#define LCC_PLL0_STATUS_REG REG_LPA(0x0018) +#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) +#define LCC_PRI_PLL_CLK_CTL_REG REG_LPA(0x00C4) + +#define GCC_APCS_CLK_DIAG REG_GCC(0x001C) + +/* MUX source input identifiers. */ +#define pxo_to_bb_mux 0 +#define cxo_to_bb_mux 5 +#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 pll3_to_bb_mux 6 +#define pxo_to_mm_mux 0 +#define pll1_to_mm_mux 1 +#define pll2_to_mm_mux 1 /* or MMCC_PLL1 */ +#define pll8_to_mm_mux 2 /* or GCC_PERF */ +#define pll0_to_mm_mux 3 +#define pll15_to_mm_mux 3 /* or MM_PLL3 */ +#define gnd_to_mm_mux 4 +#define pll3_to_mm_mux 3 /* or MMCC_PLL2 */ +#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 +#define pxo_to_pcie_mux 0 +#define pll3_to_pcie_mux 1 + +/* 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_CPUL2 6 +#define TEST_TYPE_LPA_HS 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_LPA_HS(s) TEST_VECTOR((s), TEST_TYPE_LPA_HS) +#define TEST_CPUL2(s) TEST_VECTOR((s), TEST_TYPE_CPUL2) + +#define MN_MODE_DUAL_EDGE 0x2 + +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 } + +enum vdd_dig_levels { + VDD_DIG_NONE, + VDD_DIG_LOW, + VDD_DIG_NOMINAL, + VDD_DIG_HIGH +}; + +static int set_vdd_dig_8960(struct clk_vdd_class *vdd_class, int level) +{ + static const int vdd_uv[] = { + [VDD_DIG_NONE] = 0, + [VDD_DIG_LOW] = 945000, + [VDD_DIG_NOMINAL] = 1050000, + [VDD_DIG_HIGH] = 1150000 + }; + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_S3, RPM_VREG_VOTER3, + vdd_uv[level], 1150000, 1); +} + +static DEFINE_VDD_CLASS(vdd_dig, set_vdd_dig_8960); + +static int set_vdd_dig_8930(struct clk_vdd_class *vdd_class, int level) +{ + static const int vdd_corner[] = { + [VDD_DIG_NONE] = RPM_VREG_CORNER_NONE, + [VDD_DIG_LOW] = RPM_VREG_CORNER_LOW, + [VDD_DIG_NOMINAL] = RPM_VREG_CORNER_NOMINAL, + [VDD_DIG_HIGH] = RPM_VREG_CORNER_HIGH, + }; + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8038_VDD_DIG_CORNER, + RPM_VREG_VOTER3, + vdd_corner[level], + RPM_VREG_CORNER_HIGH, 1); +} + +#define VDD_DIG_FMAX_MAP1(l1, f1) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1) +#define VDD_DIG_FMAX_MAP2(l1, f1, l2, f2) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2) +#define VDD_DIG_FMAX_MAP3(l1, f1, l2, f2, l3, f3) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2), \ + .fmax[VDD_DIG_##l3] = (f3) + +enum vdd_sr2_pll_levels { + VDD_SR2_PLL_OFF, + VDD_SR2_PLL_ON +}; + +static int set_vdd_sr2_pll_8960(struct clk_vdd_class *vdd_class, int level) +{ + int rc = 0; + + if (level == VDD_SR2_PLL_OFF) { + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_L23, + RPM_VREG_VOTER3, 0, 0, 1); + if (rc) + return rc; + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_S8, + RPM_VREG_VOTER3, 0, 0, 1); + if (rc) + rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_L23, + RPM_VREG_VOTER3, 1800000, 1800000, 1); + } else { + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_S8, + RPM_VREG_VOTER3, 2050000, 2100000, 1); + if (rc) + return rc; + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_L23, + RPM_VREG_VOTER3, 1800000, 1800000, 1); + if (rc) + rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_S8, + RPM_VREG_VOTER3, 0, 0, 1); + } + + return rc; +} + +static DEFINE_VDD_CLASS(vdd_sr2_pll, set_vdd_sr2_pll_8960); + +static int sr2_lreg_uv[] = { + [VDD_SR2_PLL_OFF] = 0, + [VDD_SR2_PLL_ON] = 1800000, +}; + +static int set_vdd_sr2_pll_8064(struct clk_vdd_class *vdd_class, int level) +{ + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_LVS7, RPM_VREG_VOTER3, + sr2_lreg_uv[level], sr2_lreg_uv[level], 1); +} + +static int set_vdd_sr2_pll_8930(struct clk_vdd_class *vdd_class, int level) +{ + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8038_L23, RPM_VREG_VOTER3, + sr2_lreg_uv[level], sr2_lreg_uv[level], 1); +} + +/* + * Clock Descriptions + */ + +DEFINE_CLK_RPM_BRANCH(pxo_clk, pxo_a_clk, PXO, 27000000); +DEFINE_CLK_RPM_BRANCH(cxo_clk, cxo_a_clk, CXO, 19200000); + +static struct pll_clk pll2_clk = { + .mode_reg = MM_PLL1_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll2_clk", + .rate = 800000000, + .ops = &clk_ops_local_pll, + CLK_INIT(pll2_clk.c), + .warned = true, + }, +}; + +static struct pll_clk pll3_clk = { + .mode_reg = BB_MMCC_PLL2_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll3_clk", + .rate = 1200000000, + .ops = &clk_ops_local_pll, + .vdd_class = &vdd_sr2_pll, + .fmax[VDD_SR2_PLL_ON] = ULONG_MAX, + CLK_INIT(pll3_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll4_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(4), + .status_reg = LCC_PLL0_STATUS_REG, + .status_mask = BIT(16), + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll4_clk", + .rate = 393216000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll4_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll8_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(8), + .status_reg = BB_PLL8_STATUS_REG, + .status_mask = BIT(16), + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll8_clk", + .rate = 384000000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll8_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll14_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(14), + .status_reg = BB_PLL14_STATUS_REG, + .status_mask = BIT(16), + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll14_clk", + .rate = 480000000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll14_clk.c), + .warned = true, + }, +}; + +static struct pll_clk pll15_clk = { + .mode_reg = MM_PLL3_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll15_clk", + .rate = 975000000, + .ops = &clk_ops_local_pll, + CLK_INIT(pll15_clk.c), + .warned = true, + }, +}; + +/* 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, + .retain_reg = MAXI_EN2_REG, + .retain_mask = BIT(21), + }, + .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), + .hwcg_reg = MAXI_EN_REG, + .hwcg_mask = BIT(11), + .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), + .hwcg_reg = MAXI_EN_REG, + .hwcg_mask = BIT(15), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 7, + .retain_reg = MAXI_EN2_REG, + .retain_mask = BIT(10), + }, + .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_b_clk = { + .b = { + .ctl_reg = MAXI_EN4_REG, + .en_mask = BIT(23), + .hwcg_reg = MAXI_EN4_REG, + .hwcg_mask = BIT(22), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 25, + .retain_reg = MAXI_EN4_REG, + .retain_mask = BIT(21), + }, + .c = { + .dbg_name = "vcodec_axi_b_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_b_clk.c), + }, +}; + +static struct branch_clk vcodec_axi_a_clk = { + .b = { + .ctl_reg = MAXI_EN4_REG, + .en_mask = BIT(25), + .hwcg_reg = MAXI_EN4_REG, + .hwcg_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 26, + .retain_reg = MAXI_EN4_REG, + .retain_mask = BIT(10), + }, + .c = { + .dbg_name = "vcodec_axi_a_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_a_clk.c), + .depends = &vcodec_axi_b_clk.c, + }, +}; + +static struct branch_clk vcodec_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(19), + .hwcg_reg = MAXI_EN_REG, + .hwcg_mask = BIT(13), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(4)|BIT(5)|BIT(7), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 3, + .retain_reg = MAXI_EN2_REG, + .retain_mask = BIT(28), + }, + .c = { + .dbg_name = "vcodec_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_clk.c), + .depends = &vcodec_axi_a_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), + .hwcg_reg = MAXI_EN_REG, + .hwcg_mask = BIT(16), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(13), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 8, + .retain_reg = MAXI_EN_REG, + .retain_mask = BIT(0), + }, + .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), + .hwcg_reg = MAXI_EN2_REG, + .hwcg_mask = BIT(25), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(6), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 2, + .retain_reg = MAXI_EN3_REG, + .retain_mask = BIT(10), + }, + .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), + .hwcg_reg = MAXI_EN2_REG, + .hwcg_mask = BIT(27), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 1, + .retain_reg = MAXI_EN3_REG, + .retain_mask = BIT(21), + + }, + .c = { + .dbg_name = "vpe_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vpe_axi_clk.c), + }, +}; + +static struct branch_clk vcap_axi_clk = { + .b = { + .ctl_reg = MAXI_EN5_REG, + .en_mask = BIT(12), + .hwcg_reg = MAXI_EN5_REG, + .hwcg_mask = BIT(11), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(16), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 20, + }, + .c = { + .dbg_name = "vcap_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcap_axi_clk.c), + }, +}; + +/* gfx3d_axi_clk is set as a dependency of gmem_axi_clk at runtime */ +static struct branch_clk gfx3d_axi_clk_8064 = { + .b = { + .ctl_reg = MAXI_EN5_REG, + .en_mask = BIT(25), + .hwcg_reg = MAXI_EN5_REG, + .hwcg_mask = BIT(24), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(17), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 30, + }, + .c = { + .dbg_name = "gfx3d_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx3d_axi_clk_8064.c), + }, +}; + +static struct branch_clk gfx3d_axi_clk_8930 = { + .b = { + .ctl_reg = MAXI_EN5_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(16), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 12, + }, + .c = { + .dbg_name = "gfx3d_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx3d_axi_clk_8930.c), + }, +}; + +/* AHB Interfaces */ +static struct branch_clk amp_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(24), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(20), + .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 csi_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 = "csi_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi_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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(20), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(15), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(28), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(29), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(27), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(21), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(22), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(12), + .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), + .hwcg_reg = AHB_EN_REG, + .hwcg_mask = BIT(26), + .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), + .hwcg_reg = AHB_EN2_REG, + .hwcg_mask = BIT(26), + .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, + .retain_reg = AHB_EN2_REG, + .retain_mask = BIT(0), + }, + .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), + }, +}; + +static struct branch_clk vcap_p_clk = { + .b = { + .ctl_reg = AHB_EN3_REG, + .en_mask = BIT(1), + .hwcg_reg = AHB_EN3_REG, + .hwcg_mask = BIT(0), + .reset_reg = SW_RESET_AHB2_REG, + .reset_mask = BIT(2), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 23, + }, + .c = { + .dbg_name = "vcap_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcap_p_clk.c), + }, +}; + +/* + * Peripheral Clocks + */ +#define CLK_GP(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GPn_NS_REG(n), \ + .en_mask = BIT(9), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GPn_NS_REG(n), \ + .md_reg = GPn_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gp, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gp[] = { + F_GP( 0, gnd, 1, 0, 0), + F_GP( 9600000, cxo, 2, 0, 0), + F_GP( 13500000, pxo, 2, 0, 0), + F_GP( 19200000, cxo, 1, 0, 0), + F_GP( 27000000, pxo, 1, 0, 0), + F_GP( 64000000, pll8, 2, 1, 3), + F_GP( 76800000, pll8, 1, 1, 5), + F_GP( 96000000, pll8, 4, 0, 0), + F_GP(128000000, pll8, 3, 0, 0), + F_GP(192000000, pll8, 2, 0, 0), + F_END +}; + +static CLK_GP(gp0, 0, CLK_HALT_SFPB_MISC_STATE_REG, 7); +static CLK_GP(gp1, 1, CLK_HALT_SFPB_MISC_STATE_REG, 6); +static CLK_GP(gp2, 2, CLK_HALT_SFPB_MISC_STATE_REG, 5); + +#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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_uart, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 32000000, NOMINAL, 64000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_UART(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gsbi_uart[] = { + F_GSBI_UART( 0, gnd, 1, 0, 0), + F_GSBI_UART( 1843200, pll8, 2, 6, 625), + F_GSBI_UART( 3686400, pll8, 2, 12, 625), + F_GSBI_UART( 7372800, pll8, 2, 24, 625), + F_GSBI_UART(14745600, pll8, 2, 48, 625), + F_GSBI_UART(16000000, pll8, 4, 1, 6), + F_GSBI_UART(24000000, pll8, 4, 1, 4), + F_GSBI_UART(32000000, pll8, 4, 1, 3), + F_GSBI_UART(40000000, pll8, 1, 5, 48), + F_GSBI_UART(46400000, pll8, 1, 29, 240), + F_GSBI_UART(48000000, pll8, 4, 1, 2), + F_GSBI_UART(51200000, pll8, 1, 2, 15), + F_GSBI_UART(56000000, pll8, 1, 7, 48), + F_GSBI_UART(58982400, pll8, 1, 96, 625), + F_GSBI_UART(64000000, pll8, 2, 1, 3), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_qup, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 24000000, NOMINAL, 52000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_QUP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gsbi_qup[] = { + F_GSBI_QUP( 0, gnd, 1, 0, 0), + F_GSBI_QUP( 1100000, pxo, 1, 2, 49), + F_GSBI_QUP( 5400000, pxo, 1, 1, 5), + F_GSBI_QUP(10800000, pxo, 1, 2, 5), + F_GSBI_QUP(15060000, pll8, 1, 2, 51), + F_GSBI_QUP(24000000, pll8, 4, 1, 4), + F_GSBI_QUP(25600000, pll8, 1, 1, 15), + F_GSBI_QUP(27000000, pxo, 1, 0, 0), + F_GSBI_QUP(48000000, pll8, 4, 1, 2), + F_GSBI_QUP(51200000, pll8, 1, 2, 15), + 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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + } +static struct clk_freq_tbl clk_tbl_pdm[] = { + F_PDM( 0, gnd, 1), + F_PDM(27000000, pxo, 1), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "pdm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 27000000), + CLK_INIT(pdm_clk.c), + }, +}; + +static struct branch_clk pmem_clk = { + .b = { + .ctl_reg = PMEM_ACLK_CTL_REG, + .en_mask = BIT(4), + .hwcg_reg = PMEM_ACLK_CTL_REG, + .hwcg_mask = BIT(6), + .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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + } +static struct clk_freq_tbl clk_tbl_prng_32[] = { + F_PRNG(32000000, pll8), + F_END +}; + +static struct clk_freq_tbl clk_tbl_prng_64[] = { + F_PRNG(64000000, pll8), + 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_32, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "prng_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 32000000, NOMINAL, 64000000), + CLK_INIT(prng_clk.c), + }, +}; + +#define CLK_SDC(name, n, h_b, fmax_low, fmax_nom) \ + struct rcg_clk name = { \ + .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 = CLK_HALT_DFAB_STATE_REG, \ + .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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_sdc, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #name, \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, fmax_low, NOMINAL, fmax_nom), \ + CLK_INIT(name.c), \ + }, \ + } +#define F_SDC(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_sdc[] = { + F_SDC( 0, gnd, 1, 0, 0), + F_SDC( 144000, pxo, 3, 2, 125), + F_SDC( 400000, pll8, 4, 1, 240), + F_SDC( 16000000, pll8, 4, 1, 6), + F_SDC( 17070000, pll8, 1, 2, 45), + F_SDC( 20210000, pll8, 1, 1, 19), + F_SDC( 24000000, pll8, 4, 1, 4), + F_SDC( 48000000, pll8, 4, 1, 2), + F_SDC( 64000000, pll8, 3, 1, 2), + F_SDC( 96000000, pll8, 4, 0, 0), + F_SDC(192000000, pll8, 2, 0, 0), + F_END +}; + +static CLK_SDC(sdc1_clk, 1, 6, 52000000, 104000000); +static CLK_SDC(sdc2_clk, 2, 5, 52000000, 104000000); +static CLK_SDC(sdc3_clk, 3, 4, 104000000, 208000000); +static CLK_SDC(sdc4_clk, 4, 3, 33000000, 67000000); +static CLK_SDC(sdc5_clk, 5, 2, 33000000, 67000000); + +#define F_TSIF_REF(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_tsif_ref[] = { + F_TSIF_REF( 0, gnd, 1, 0, 0), + F_TSIF_REF(105000, pxo, 1, 1, 256), + 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)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_tsif_ref, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "tsif_ref_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 27000000, NOMINAL, 54000000), + CLK_INIT(tsif_ref_clk.c), + }, +}; + +#define F_TSSC(f, s) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + } +static struct clk_freq_tbl clk_tbl_tssc[] = { + F_TSSC( 0, gnd), + F_TSSC(27000000, pxo), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "tssc_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 27000000), + CLK_INIT(tssc_clk.c), + }, +}; + +#define CLK_USB_HS(name, n, h_b) \ + static struct rcg_clk name = { \ + .b = { \ + .ctl_reg = USB_HS##n##_XCVR_FS_CLK_NS_REG, \ + .en_mask = BIT(9), \ + .reset_reg = USB_HS##n##_RESET_REG, \ + .reset_mask = BIT(0), \ + .halt_reg = CLK_HALT_DFAB_STATE_REG, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = USB_HS##n##_XCVR_FS_CLK_NS_REG, \ + .md_reg = USB_HS##n##_XCVR_FS_CLK_MD_REG, \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_usb, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #name, \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(NOMINAL, 64000000), \ + CLK_INIT(name.c), \ + }, \ +} + +#define F_USB(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_usb[] = { + F_USB( 0, gnd, 1, 0, 0), + F_USB(60000000, pll8, 1, 5, 32), + F_END +}; + +CLK_USB_HS(usb_hs1_xcvr_clk, 1, 0); +CLK_USB_HS(usb_hs3_xcvr_clk, 3, 30); +CLK_USB_HS(usb_hs4_xcvr_clk, 4, 2); + +static struct clk_freq_tbl clk_tbl_usb_hsic[] = { + F_USB( 0, gnd, 1, 0, 0), + F_USB(60000000, pll8, 1, 5, 32), + F_END +}; + +static struct rcg_clk usb_hsic_xcvr_fs_clk = { + .b = { + .ctl_reg = USB_HSIC_XCVR_FS_CLK_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 26, + }, + .ns_reg = USB_HSIC_XCVR_FS_CLK_NS_REG, + .md_reg = USB_HSIC_XCVR_FS_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(23, 16) | BM(6, 0)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb_hsic, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hsic_xcvr_fs_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 60000000), + CLK_INIT(usb_hsic_xcvr_fs_clk.c), + }, +}; + +static struct branch_clk usb_hsic_system_clk = { + .b = { + .ctl_reg = USB_HSIC_SYSTEM_CLK_CTL_REG, + .en_mask = BIT(4), + .reset_reg = USB_HSIC_RESET_REG, + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 24, + }, + .parent = &usb_hsic_xcvr_fs_clk.c, + .c = { + .dbg_name = "usb_hsic_system_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hsic_system_clk.c), + }, +}; + +#define F_USB_HSIC(f, s) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + } +static struct clk_freq_tbl clk_tbl_usb2_hsic[] = { + F_USB_HSIC(480000000, pll14), + F_END +}; + +static struct rcg_clk usb_hsic_hsic_src_clk = { + .b = { + .ctl_reg = USB_HSIC_HSIC_CLK_SRC_CTL_REG, + .halt_check = NOCHECK, + }, + .root_en_mask = BIT(0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_usb2_hsic, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hsic_hsic_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 480000000), + CLK_INIT(usb_hsic_hsic_src_clk.c), + }, +}; + +static struct branch_clk usb_hsic_hsic_clk = { + .b = { + .ctl_reg = USB_HSIC_HSIC_CLK_CTL_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 19, + }, + .parent = &usb_hsic_hsic_src_clk.c, + .c = { + .dbg_name = "usb_hsic_hsic_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hsic_hsic_clk.c), + }, +}; + +#define F_USB_HSIO_CAL(f, s) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + } +static struct clk_freq_tbl clk_tbl_usb_hsio_cal[] = { + F_USB_HSIO_CAL(9000000, pxo), + F_END +}; + +static struct rcg_clk usb_hsic_hsio_cal_clk = { + .b = { + .ctl_reg = USB_HSIC_HSIO_CAL_CLK_CTL_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 23, + }, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_usb_hsio_cal, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hsic_hsio_cal_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 10000000), + CLK_INIT(usb_hsic_hsio_cal_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, fmax_nom) \ + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_usb, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(NOMINAL, fmax_nom), \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +static CLK_USB_FS(usb_fs1_src, 1, 64000000); +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, 60000000); +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), + .hwcg_reg = CE1_CORE_CLK_CTL_REG, + .hwcg_mask = BIT(6), + .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), + }, +}; + +#define F_CE3(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(6, 3, d, 2, 0, s##_to_bb_mux), \ + } + +static struct clk_freq_tbl clk_tbl_ce3[] = { + F_CE3( 0, gnd, 1), + F_CE3( 48000000, pll8, 8), + F_CE3(100000000, pll3, 12), + F_END +}; + +static struct rcg_clk ce3_src_clk = { + .b = { + .ctl_reg = CE3_CLK_SRC_NS_REG, + .halt_check = NOCHECK, + }, + .ns_reg = CE3_CLK_SRC_NS_REG, + .root_en_mask = BIT(7), + .ns_mask = BM(6, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_ce3, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "ce3_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 50000000, NOMINAL, 100000000), + CLK_INIT(ce3_src_clk.c), + }, +}; + +static struct branch_clk ce3_core_clk = { + .b = { + .ctl_reg = CE3_CORE_CLK_CTL_REG, + .en_mask = BIT(4), + .reset_reg = CE3_CORE_CLK_CTL_REG, + .reset_mask = BIT(7), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_bit = 5, + }, + .parent = &ce3_src_clk.c, + .c = { + .dbg_name = "ce3_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(ce3_core_clk.c), + } +}; + +static struct branch_clk ce3_p_clk = { + .b = { + .ctl_reg = CE3_HCLK_CTL_REG, + .en_mask = BIT(4), + .reset_reg = CE3_HCLK_CTL_REG, + .reset_mask = BIT(7), + .halt_reg = CLK_HALT_AFAB_SFAB_STATEB_REG, + .halt_bit = 16, + }, + .parent = &ce3_src_clk.c, + .c = { + .dbg_name = "ce3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(ce3_p_clk.c), + } +}; + +#define F_SATA(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(6, 3, d, 2, 0, s##_to_bb_mux), \ + } + +static struct clk_freq_tbl clk_tbl_sata[] = { + F_SATA( 0, gnd, 1), + F_SATA( 48000000, pll8, 8), + F_SATA(100000000, pll3, 12), + F_END +}; + +static struct rcg_clk sata_src_clk = { + .b = { + .ctl_reg = SATA_CLK_SRC_NS_REG, + .halt_check = NOCHECK, + }, + .ns_reg = SATA_CLK_SRC_NS_REG, + .root_en_mask = BIT(7), + .ns_mask = BM(6, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_sata, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "sata_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 50000000, NOMINAL, 100000000), + CLK_INIT(sata_src_clk.c), + }, +}; + +static struct branch_clk sata_rxoob_clk = { + .b = { + .ctl_reg = SATA_RXOOB_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_bit = 26, + }, + .parent = &sata_src_clk.c, + .c = { + .dbg_name = "sata_rxoob_clk", + .ops = &clk_ops_branch, + CLK_INIT(sata_rxoob_clk.c), + }, +}; + +static struct branch_clk sata_pmalive_clk = { + .b = { + .ctl_reg = SATA_PMALIVE_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_bit = 25, + }, + .parent = &sata_src_clk.c, + .c = { + .dbg_name = "sata_pmalive_clk", + .ops = &clk_ops_branch, + CLK_INIT(sata_pmalive_clk.c), + }, +}; + +static struct branch_clk sata_phy_ref_clk = { + .b = { + .ctl_reg = SATA_PHY_REF_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_bit = 24, + }, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "sata_phy_ref_clk", + .ops = &clk_ops_branch, + CLK_INIT(sata_phy_ref_clk.c), + }, +}; + +static struct branch_clk sata_a_clk = { + .b = { + .ctl_reg = SATA_ACLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_AFAB_SFAB_STATEA_REG, + .halt_bit = 12, + }, + .c = { + .dbg_name = "sata_a_clk", + .ops = &clk_ops_branch, + CLK_INIT(sata_a_clk.c), + }, +}; + +static struct branch_clk sata_p_clk = { + .b = { + .ctl_reg = SATA_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_bit = 27, + }, + .c = { + .dbg_name = "sata_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sata_p_clk.c), + }, +}; + +static struct branch_clk sfab_sata_s_p_clk = { + .b = { + .ctl_reg = SFAB_SATA_S_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_AFAB_SFAB_STATEB_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "sfab_sata_s_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sfab_sata_s_p_clk.c), + }, +}; +static struct branch_clk pcie_p_clk = { + .b = { + .ctl_reg = PCIE_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 8, + }, + .c = { + .dbg_name = "pcie_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(pcie_p_clk.c), + }, +}; + +static struct branch_clk pcie_phy_ref_clk = { + .b = { + .ctl_reg = PCIE_PCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_bit = 29, + }, + .c = { + .dbg_name = "pcie_phy_ref_clk", + .ops = &clk_ops_branch, + CLK_INIT(pcie_phy_ref_clk.c), + }, +}; + +static struct branch_clk pcie_a_clk = { + .b = { + .ctl_reg = PCIE_ACLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_AFAB_SFAB_STATEA_REG, + .halt_bit = 13, + }, + .c = { + .dbg_name = "pcie_a_clk", + .ops = &clk_ops_branch, + CLK_INIT(pcie_a_clk.c), + }, +}; + +static struct branch_clk dma_bam_p_clk = { + .b = { + .ctl_reg = DMA_BAM_HCLK_CTL, + .en_mask = BIT(4), + .hwcg_reg = DMA_BAM_HCLK_CTL, + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(1), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(2), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(3), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(4), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(5), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(6), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(7), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(8), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(9), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(10), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(11), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = GSBIn_HCLK_CTL_REG(12), + .hwcg_mask = BIT(6), + .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 sata_phy_cfg_clk = { + .b = { + .ctl_reg = SATA_PHY_CFG_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 12, + }, + .c = { + .dbg_name = "sata_phy_cfg_clk", + .ops = &clk_ops_branch, + CLK_INIT(sata_phy_cfg_clk.c), + }, +}; + +static struct branch_clk tsif_p_clk = { + .b = { + .ctl_reg = TSIF_HCLK_CTL_REG, + .en_mask = BIT(4), + .hwcg_reg = TSIF_HCLK_CTL_REG, + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = USB_HS1_HCLK_CTL_REG, + .hwcg_mask = BIT(6), + .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 usb_hs3_p_clk = { + .b = { + .ctl_reg = USB_HS3_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 31, + }, + .c = { + .dbg_name = "usb_hs3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs3_p_clk.c), + }, +}; + +static struct branch_clk usb_hs4_p_clk = { + .b = { + .ctl_reg = USB_HS4_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "usb_hs4_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs4_p_clk.c), + }, +}; + +static struct branch_clk usb_hsic_p_clk = { + .b = { + .ctl_reg = USB_HSIC_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 28, + }, + .c = { + .dbg_name = "usb_hsic_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hsic_p_clk.c), + }, +}; + +static struct branch_clk sdc1_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .hwcg_reg = SDCn_HCLK_CTL_REG(1), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = SDCn_HCLK_CTL_REG(2), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = SDCn_HCLK_CTL_REG(3), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = SDCn_HCLK_CTL_REG(4), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = SDCn_HCLK_CTL_REG(5), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = ADM0_PBUS_CLK_CTL_REG, + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = RPM_MSG_RAM_HCLK_CTL_REG, + .hwcg_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 + */ + +#define CLK_CAM(name, n, hb) \ + struct rcg_clk name = { \ + .b = { \ + .ctl_reg = CAMCLK##n##_CC_REG, \ + .en_mask = BIT(0), \ + .halt_reg = DBG_BUS_VEC_I_REG, \ + .halt_bit = hb, \ + }, \ + .ns_reg = CAMCLK##n##_NS_REG, \ + .md_reg = CAMCLK##n##_MD_REG, \ + .root_en_mask = BIT(2), \ + .ns_mask = BM(31, 24) | BM(15, 14) | BM(2, 0), \ + .mnd_en_mask = BIT(5), \ + .ctl_mask = BM(7, 6), \ + .set_rate = set_rate_mnd_8, \ + .freq_tbl = clk_tbl_cam, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #name, \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 64000000, NOMINAL, 128000000), \ + CLK_INIT(name.c), \ + }, \ + } +#define F_CAM(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_cam[] = { + F_CAM( 0, gnd, 1, 0, 0), + F_CAM( 6000000, pll8, 4, 1, 16), + F_CAM( 8000000, pll8, 4, 1, 12), + F_CAM( 12000000, pll8, 4, 1, 8), + F_CAM( 16000000, pll8, 4, 1, 6), + F_CAM( 19200000, pll8, 4, 1, 5), + F_CAM( 24000000, pll8, 4, 1, 4), + F_CAM( 32000000, pll8, 4, 1, 3), + F_CAM( 48000000, pll8, 4, 1, 2), + F_CAM( 64000000, pll8, 3, 1, 2), + F_CAM( 96000000, pll8, 4, 0, 0), + F_CAM(128000000, pll8, 3, 0, 0), + F_END +}; + +static CLK_CAM(cam0_clk, 0, 15); +static CLK_CAM(cam1_clk, 1, 16); +static CLK_CAM(cam2_clk, 2, 31); + +#define F_CSI(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_csi[] = { + F_CSI( 0, gnd, 1, 0, 0), + F_CSI( 27000000, pxo, 1, 0, 0), + F_CSI( 85330000, pll8, 1, 2, 9), + F_CSI(177780000, pll2, 1, 2, 9), + 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, 14) | BM(2, 0), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_csi, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "csi0_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 86000000, NOMINAL, 178000000), + 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, 14) | BM(2, 0), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_csi, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "csi1_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 86000000, NOMINAL, 178000000), + 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), + }, +}; + +static struct rcg_clk csi2_src_clk = { + .ns_reg = CSI2_NS_REG, + .b = { + .ctl_reg = CSI2_CC_REG, + .halt_check = NOCHECK, + }, + .md_reg = CSI2_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(31, 24) | BM(15, 14) | BM(2, 0), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_csi, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "csi2_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 86000000, NOMINAL, 178000000), + CLK_INIT(csi2_src_clk.c), + }, +}; + +static struct branch_clk csi2_clk = { + .b = { + .ctl_reg = CSI2_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE2_REG, + .reset_mask = BIT(2), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 29, + }, + .parent = &csi2_src_clk.c, + .c = { + .dbg_name = "csi2_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi2_clk.c), + }, +}; + +static struct branch_clk csi2_phy_clk = { + .b = { + .ctl_reg = CSI2_CC_REG, + .en_mask = BIT(8), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(31), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 29, + }, + .parent = &csi2_src_clk.c, + .c = { + .dbg_name = "csi2_phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi2_phy_clk.c), + }, +}; + +static struct clk *pix_rdi_mux_map[] = { + [0] = &csi0_clk.c, + [1] = &csi1_clk.c, + [2] = &csi2_clk.c, + NULL, +}; + +struct pix_rdi_clk { + bool enabled; + unsigned long cur_rate; + + void __iomem *const s_reg; + u32 s_mask; + + void __iomem *const s2_reg; + u32 s2_mask; + + struct branch b; + struct clk c; +}; + +static inline struct pix_rdi_clk *to_pix_rdi_clk(struct clk *clk) +{ + return container_of(clk, struct pix_rdi_clk, c); +} + +static int pix_rdi_clk_set_rate(struct clk *c, unsigned long rate) +{ + int ret, i; + u32 reg; + unsigned long flags; + struct pix_rdi_clk *clk = to_pix_rdi_clk(c); + struct clk **mux_map = pix_rdi_mux_map; + + /* + * These clocks select three inputs via two muxes. One mux selects + * between csi0 and csi1 and the second mux selects between that mux's + * output and csi2. The source and destination selections for each + * mux must be clocking for the switch to succeed so just turn on + * all three sources because it's easier than figuring out what source + * needs to be on at what time. + */ + for (i = 0; mux_map[i]; i++) { + ret = clk_enable(mux_map[i]); + if (ret) + goto err; + } + if (rate >= i) { + ret = -EINVAL; + goto err; + } + /* Keep the new source on when switching inputs of an enabled clock */ + if (clk->enabled) { + clk_disable(mux_map[clk->cur_rate]); + clk_enable(mux_map[rate]); + } + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg = readl_relaxed(clk->s2_reg); + reg &= ~clk->s2_mask; + reg |= rate == 2 ? clk->s2_mask : 0; + writel_relaxed(reg, clk->s2_reg); + /* + * Wait at least 6 cycles of slowest clock + * for the glitch-free MUX to fully switch sources. + */ + mb(); + udelay(1); + reg = readl_relaxed(clk->s_reg); + reg &= ~clk->s_mask; + reg |= rate == 1 ? clk->s_mask : 0; + writel_relaxed(reg, clk->s_reg); + /* + * Wait at least 6 cycles of slowest clock + * for the glitch-free MUX to fully switch sources. + */ + mb(); + udelay(1); + clk->cur_rate = rate; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +err: + for (i--; i >= 0; i--) + clk_disable(mux_map[i]); + + return 0; +} + +static unsigned long pix_rdi_clk_get_rate(struct clk *c) +{ + return to_pix_rdi_clk(c)->cur_rate; +} + +static int pix_rdi_clk_enable(struct clk *c) +{ + unsigned long flags; + struct pix_rdi_clk *clk = to_pix_rdi_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_enable_reg(&clk->b, clk->c.dbg_name); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + clk->enabled = true; + + return 0; +} + +static void pix_rdi_clk_disable(struct clk *c) +{ + unsigned long flags; + struct pix_rdi_clk *clk = to_pix_rdi_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_disable_reg(&clk->b, clk->c.dbg_name); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + clk->enabled = false; +} + +static int pix_rdi_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return branch_reset(&to_pix_rdi_clk(clk)->b, action); +} + +static struct clk *pix_rdi_clk_get_parent(struct clk *c) +{ + struct pix_rdi_clk *clk = to_pix_rdi_clk(c); + + return pix_rdi_mux_map[clk->cur_rate]; +} + +static int pix_rdi_clk_list_rate(struct clk *c, unsigned n) +{ + if (pix_rdi_mux_map[n]) + return n; + return -ENXIO; +} + +static enum handoff pix_rdi_clk_handoff(struct clk *c) +{ + u32 reg; + struct pix_rdi_clk *clk = to_pix_rdi_clk(c); + enum handoff ret; + + ret = branch_handoff(&clk->b, &clk->c); + if (ret == HANDOFF_DISABLED_CLK) + return ret; + + reg = readl_relaxed(clk->s_reg); + clk->cur_rate = reg & clk->s_mask ? 1 : 0; + reg = readl_relaxed(clk->s2_reg); + clk->cur_rate = reg & clk->s2_mask ? 2 : clk->cur_rate; + + return HANDOFF_ENABLED_CLK; +} + +static struct clk_ops clk_ops_pix_rdi_8960 = { + .enable = pix_rdi_clk_enable, + .disable = pix_rdi_clk_disable, + .auto_off = pix_rdi_clk_disable, + .handoff = pix_rdi_clk_handoff, + .set_rate = pix_rdi_clk_set_rate, + .get_rate = pix_rdi_clk_get_rate, + .list_rate = pix_rdi_clk_list_rate, + .reset = pix_rdi_clk_reset, + .get_parent = pix_rdi_clk_get_parent, +}; + +static struct pix_rdi_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), + }, + .s_reg = MISC_CC_REG, + .s_mask = BIT(25), + .s2_reg = MISC_CC3_REG, + .s2_mask = BIT(13), + .c = { + .dbg_name = "csi_pix_clk", + .ops = &clk_ops_pix_rdi_8960, + CLK_INIT(csi_pix_clk.c), + }, +}; + +static struct pix_rdi_clk csi_pix1_clk = { + .b = { + .ctl_reg = MISC_CC3_REG, + .en_mask = BIT(10), + .halt_check = DELAY, + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(30), + }, + .s_reg = MISC_CC3_REG, + .s_mask = BIT(8), + .s2_reg = MISC_CC3_REG, + .s2_mask = BIT(9), + .c = { + .dbg_name = "csi_pix1_clk", + .ops = &clk_ops_pix_rdi_8960, + CLK_INIT(csi_pix1_clk.c), + }, +}; + +static struct pix_rdi_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), + }, + .s_reg = MISC_CC_REG, + .s_mask = BIT(12), + .s2_reg = MISC_CC3_REG, + .s2_mask = BIT(12), + .c = { + .dbg_name = "csi_rdi_clk", + .ops = &clk_ops_pix_rdi_8960, + CLK_INIT(csi_rdi_clk.c), + }, +}; + +static struct pix_rdi_clk csi_rdi1_clk = { + .b = { + .ctl_reg = MISC_CC3_REG, + .en_mask = BIT(2), + .halt_check = DELAY, + .reset_reg = SW_RESET_CORE2_REG, + .reset_mask = BIT(1), + }, + .s_reg = MISC_CC3_REG, + .s_mask = BIT(0), + .s2_reg = MISC_CC3_REG, + .s2_mask = BIT(1), + .c = { + .dbg_name = "csi_rdi1_clk", + .ops = &clk_ops_pix_rdi_8960, + CLK_INIT(csi_rdi1_clk.c), + }, +}; + +static struct pix_rdi_clk csi_rdi2_clk = { + .b = { + .ctl_reg = MISC_CC3_REG, + .en_mask = BIT(6), + .halt_check = DELAY, + .reset_reg = SW_RESET_CORE2_REG, + .reset_mask = BIT(0), + }, + .s_reg = MISC_CC3_REG, + .s_mask = BIT(4), + .s2_reg = MISC_CC3_REG, + .s2_mask = BIT(5), + .c = { + .dbg_name = "csi_rdi2_clk", + .ops = &clk_ops_pix_rdi_8960, + CLK_INIT(csi_rdi2_clk.c), + }, +}; + +#define F_CSI_PHYTIMER(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_csi_phytimer[] = { + F_CSI_PHYTIMER( 0, gnd, 1, 0, 0), + F_CSI_PHYTIMER( 85330000, pll8, 1, 2, 9), + F_CSI_PHYTIMER(177780000, pll2, 1, 2, 9), + 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)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd_8, + .freq_tbl = clk_tbl_csi_phytimer, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "csiphy_timer_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 86000000, NOMINAL, 178000000), + 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), + }, +}; + +static struct branch_clk csi2phy_timer_clk = { + .b = { + .ctl_reg = CSIPHYTIMER_CC_REG, + .en_mask = BIT(11), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 30, + }, + .parent = &csiphy_timer_src_clk.c, + .c = { + .dbg_name = "csi2phy_timer_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi2phy_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, + .retain_reg = DSI1_BYTE_CC_REG, + .retain_mask = BIT(31), + }, + .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 = &rcg_dummy_freq, + .c = { + .dbg_name = "dsi1_byte_clk", + .ops = &clk_ops_rcg, + 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, + .retain_reg = DSI2_BYTE_CC_REG, + .retain_mask = BIT(31), + }, + .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 = &rcg_dummy_freq, + .c = { + .dbg_name = "dsi2_byte_clk", + .ops = &clk_ops_rcg, + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "dsi1_esc_clk", + .ops = &clk_ops_rcg, + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "dsi2_esc_clk", + .ops = &clk_ops_rcg, + CLK_INIT(dsi2_esc_clk.c), + }, +}; + +#define F_GFX2D(f, s, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gfx2d[] = { + F_GFX2D( 0, gnd, 0, 0), + F_GFX2D( 27000000, pxo, 0, 0), + F_GFX2D( 48000000, pll8, 1, 8), + F_GFX2D( 54857000, pll8, 1, 7), + F_GFX2D( 64000000, pll8, 1, 6), + F_GFX2D( 76800000, pll8, 1, 5), + F_GFX2D( 96000000, pll8, 1, 4), + F_GFX2D(128000000, pll8, 1, 3), + F_GFX2D(145455000, pll2, 2, 11), + F_GFX2D(160000000, pll2, 1, 5), + F_GFX2D(177778000, pll2, 2, 9), + F_GFX2D(200000000, pll2, 1, 4), + F_GFX2D(228571000, pll2, 2, 7), + 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, + .retain_reg = GFX2D0_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = GFX2D0_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_info = &bmnd_info_gfx2d0, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "gfx2d0_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 100000000, NOMINAL, 200000000, + HIGH, 228571000), + 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, + .retain_reg = GFX2D1_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = GFX2D1_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_info = &bmnd_info_gfx2d1, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "gfx2d1_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 100000000, NOMINAL, 200000000, + HIGH, 228571000), + CLK_INIT(gfx2d1_clk.c), + }, +}; + +#define F_GFX3D(f, s, m, n) \ + { \ + .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), \ + } + +static struct clk_freq_tbl clk_tbl_gfx3d_8960[] = { + F_GFX3D( 0, gnd, 0, 0), + F_GFX3D( 27000000, pxo, 0, 0), + F_GFX3D( 48000000, pll8, 1, 8), + F_GFX3D( 54857000, pll8, 1, 7), + F_GFX3D( 64000000, pll8, 1, 6), + F_GFX3D( 76800000, pll8, 1, 5), + F_GFX3D( 96000000, pll8, 1, 4), + F_GFX3D(128000000, pll8, 1, 3), + F_GFX3D(145455000, pll2, 2, 11), + F_GFX3D(160000000, pll2, 1, 5), + F_GFX3D(177778000, pll2, 2, 9), + F_GFX3D(200000000, pll2, 1, 4), + F_GFX3D(228571000, pll2, 2, 7), + F_GFX3D(266667000, pll2, 1, 3), + F_GFX3D(300000000, pll3, 1, 4), + F_GFX3D(320000000, pll2, 2, 5), + F_GFX3D(400000000, pll2, 1, 2), + F_END +}; + +static struct clk_freq_tbl clk_tbl_gfx3d_8064[] = { + F_GFX3D( 0, gnd, 0, 0), + F_GFX3D( 27000000, pxo, 0, 0), + F_GFX3D( 48000000, pll8, 1, 8), + F_GFX3D( 54857000, pll8, 1, 7), + F_GFX3D( 64000000, pll8, 1, 6), + F_GFX3D( 76800000, pll8, 1, 5), + F_GFX3D( 96000000, pll8, 1, 4), + F_GFX3D(128000000, pll8, 1, 3), + F_GFX3D(145455000, pll2, 2, 11), + F_GFX3D(160000000, pll2, 1, 5), + F_GFX3D(177778000, pll2, 2, 9), + F_GFX3D(200000000, pll2, 1, 4), + F_GFX3D(228571000, pll2, 2, 7), + F_GFX3D(266667000, pll2, 1, 3), + F_GFX3D(325000000, pll15, 1, 3), + F_GFX3D(400000000, pll2, 1, 2), + F_END +}; + +static struct clk_freq_tbl clk_tbl_gfx3d_8930[] = { + F_GFX3D( 0, gnd, 0, 0), + F_GFX3D( 27000000, pxo, 0, 0), + F_GFX3D( 48000000, pll8, 1, 8), + F_GFX3D( 54857000, pll8, 1, 7), + F_GFX3D( 64000000, pll8, 1, 6), + F_GFX3D( 76800000, pll8, 1, 5), + F_GFX3D( 96000000, pll8, 1, 4), + F_GFX3D(128000000, pll8, 1, 3), + F_GFX3D(145455000, pll2, 2, 11), + F_GFX3D(160000000, pll2, 1, 5), + F_GFX3D(177778000, pll2, 2, 9), + F_GFX3D(192000000, pll8, 1, 2), + F_GFX3D(200000000, pll2, 1, 4), + F_GFX3D(228571000, pll2, 2, 7), + F_GFX3D(266667000, pll2, 1, 3), + F_GFX3D(320000000, pll2, 2, 5), + F_GFX3D(400000000, pll2, 1, 2), + F_GFX3D(450000000, pll15, 1, 2), + F_END +}; + +static unsigned long fmax_gfx3d_8064[MAX_VDD_LEVELS] __initdata = { + [VDD_DIG_LOW] = 128000000, + [VDD_DIG_NOMINAL] = 325000000, + [VDD_DIG_HIGH] = 400000000 +}; + +static unsigned long fmax_gfx3d_8930[MAX_VDD_LEVELS] __initdata = { + [VDD_DIG_LOW] = 192000000, + [VDD_DIG_NOMINAL] = 320000000, + [VDD_DIG_HIGH] = 450000000 +}; + +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, + .retain_reg = GFX3D_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = GFX3D_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx3d_8960, + .bank_info = &bmnd_info_gfx3d, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "gfx3d_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 128000000, NOMINAL, 300000000, + HIGH, 400000000), + CLK_INIT(gfx3d_clk.c), + .depends = &gmem_axi_clk.c, + }, +}; + +#define F_VCAP(f, s, m, n) \ + { \ + .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), \ + } + +static struct clk_freq_tbl clk_tbl_vcap[] = { + F_VCAP( 0, gnd, 0, 0), + F_VCAP( 27000000, pxo, 0, 0), + F_VCAP( 54860000, pll8, 1, 7), + F_VCAP( 64000000, pll8, 1, 6), + F_VCAP( 76800000, pll8, 1, 5), + F_VCAP(128000000, pll8, 1, 3), + F_VCAP(160000000, pll2, 1, 5), + F_VCAP(200000000, pll2, 1, 4), + F_END +}; + +static struct bank_masks bmnd_info_vcap = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = VCAP_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 = VCAP_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 vcap_clk = { + .b = { + .ctl_reg = VCAP_CC_REG, + .en_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 15, + }, + .ns_reg = VCAP_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_vcap, + .bank_info = &bmnd_info_vcap, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "vcap_clk", + .ops = &clk_ops_rcg, + .depends = &vcap_axi_clk.c, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(vcap_clk.c), + }, +}; + +static struct branch_clk vcap_npl_clk = { + .b = { + .ctl_reg = VCAP_CC_REG, + .en_mask = BIT(13), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 25, + }, + .parent = &vcap_clk.c, + .c = { + .dbg_name = "vcap_npl_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcap_npl_clk.c), + }, +}; + +#define F_IJPEG(f, s, d, m, n) \ + { \ + .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), \ + } + +static struct clk_freq_tbl clk_tbl_ijpeg[] = { + F_IJPEG( 0, gnd, 1, 0, 0), + F_IJPEG( 27000000, pxo, 1, 0, 0), + F_IJPEG( 36570000, pll8, 1, 2, 21), + F_IJPEG( 54860000, pll8, 7, 0, 0), + F_IJPEG( 96000000, pll8, 4, 0, 0), + F_IJPEG(109710000, pll8, 1, 2, 7), + F_IJPEG(128000000, pll8, 3, 0, 0), + F_IJPEG(153600000, pll8, 1, 2, 5), + F_IJPEG(200000000, pll2, 4, 0, 0), + F_IJPEG(228571000, pll2, 1, 2, 7), + F_IJPEG(266667000, pll2, 1, 1, 3), + F_IJPEG(320000000, pll2, 1, 2, 5), + F_END +}; + +static unsigned long fmax_ijpeg_8064[MAX_VDD_LEVELS] __initdata = { + [VDD_DIG_LOW] = 128000000, + [VDD_DIG_NOMINAL] = 266667000, + [VDD_DIG_HIGH] = 320000000 +}; + +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, + .retain_reg = IJPEG_CC_REG, + .retain_mask = BIT(31), + }, + .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)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_ijpeg, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "ijpeg_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 110000000, NOMINAL, 266667000, + HIGH, 320000000), + CLK_INIT(ijpeg_clk.c), + .depends = &ijpeg_axi_clk.c, + }, +}; + +#define F_JPEGD(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + } +static struct clk_freq_tbl clk_tbl_jpegd[] = { + F_JPEGD( 0, gnd, 1), + F_JPEGD( 64000000, pll8, 6), + F_JPEGD( 76800000, pll8, 5), + F_JPEGD( 96000000, pll8, 4), + F_JPEGD(160000000, pll2, 5), + F_JPEGD(200000000, pll2, 4), + 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, + .retain_reg = JPEGD_CC_REG, + .retain_mask = BIT(31), + }, + .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, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "jpegd_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 96000000, NOMINAL, 200000000), + CLK_INIT(jpegd_clk.c), + .depends = &jpegd_axi_clk.c, + }, +}; + +#define F_MDP(f, s, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_mdp[] = { + F_MDP( 0, gnd, 0, 0), + F_MDP( 9600000, pll8, 1, 40), + F_MDP( 13710000, pll8, 1, 28), + F_MDP( 27000000, pxo, 0, 0), + F_MDP( 29540000, pll8, 1, 13), + F_MDP( 34910000, pll8, 1, 11), + F_MDP( 38400000, pll8, 1, 10), + F_MDP( 59080000, pll8, 2, 13), + F_MDP( 76800000, pll8, 1, 5), + F_MDP( 85330000, pll8, 2, 9), + F_MDP( 96000000, pll8, 1, 4), + F_MDP(128000000, pll8, 1, 3), + F_MDP(160000000, pll2, 1, 5), + F_MDP(177780000, pll2, 2, 9), + F_MDP(200000000, pll2, 1, 4), + F_MDP(266667000, pll2, 1, 3), + F_END +}; + +static unsigned long fmax_mdp_8064[MAX_VDD_LEVELS] __initdata = { + [VDD_DIG_LOW] = 128000000, + [VDD_DIG_NOMINAL] = 266667000 +}; + +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, + .retain_reg = MDP_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = MDP_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_mdp, + .bank_info = &bmnd_info_mdp, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 96000000, NOMINAL, 200000000), + CLK_INIT(mdp_clk.c), + .depends = &mdp_axi_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, + .retain_reg = MDP_LUT_CC_REG, + .retain_mask = BIT(31), + }, + .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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(13, 13, s##_to_bb_mux), \ + } +static struct clk_freq_tbl clk_tbl_mdp_vsync[] = { + F_MDP_VSYNC(27000000, pxo), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_vsync_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 27000000), + CLK_INIT(mdp_vsync_clk.c), + }, +}; + +#define F_ROT(f, s, d) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_rot[] = { + F_ROT( 0, gnd, 1), + F_ROT( 27000000, pxo, 1), + F_ROT( 29540000, pll8, 13), + F_ROT( 32000000, pll8, 12), + F_ROT( 38400000, pll8, 10), + F_ROT( 48000000, pll8, 8), + F_ROT( 54860000, pll8, 7), + F_ROT( 64000000, pll8, 6), + F_ROT( 76800000, pll8, 5), + F_ROT( 96000000, pll8, 4), + F_ROT(100000000, pll2, 8), + F_ROT(114290000, pll2, 7), + F_ROT(133330000, pll2, 6), + F_ROT(160000000, pll2, 5), + F_ROT(200000000, pll2, 4), + 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, + .retain_reg = ROT_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = ROT_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_div_banked, + .freq_tbl = clk_tbl_rot, + .bank_info = &bdiv_info_rot, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "rot_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 96000000, NOMINAL, 200000000), + CLK_INIT(rot_clk.c), + .depends = &rot_axi_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 long hdmi_pll_clk_get_rate(struct clk *clk) +{ + return hdmi_pll_get_rate(); +} + +static struct clk *hdmi_pll_clk_get_parent(struct clk *clk) +{ + return &pxo_clk.c; +} + +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, + .get_parent = hdmi_pll_clk_get_parent, +}; + +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) \ + { \ + .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), \ + } +#define F_TV(f, s, p_r, d, m, n) \ + { \ + .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), \ + .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), + F_TV( 25200000, hdmi_pll, 25200000, 1, 0, 0), + F_TV( 27000000, hdmi_pll, 27000000, 1, 0, 0), + F_TV( 27030000, hdmi_pll, 27030000, 1, 0, 0), + F_TV( 74250000, hdmi_pll, 74250000, 1, 0, 0), + F_TV(148500000, hdmi_pll, 148500000, 1, 0, 0), + F_END +}; + +static unsigned long fmax_tv_src_8064[MAX_VDD_LEVELS] __initdata = { + [VDD_DIG_LOW] = 74250000, + [VDD_DIG_NOMINAL] = 149000000 +}; + +/* + * 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, + .retain_reg = TV_CC_REG, + .retain_mask = BIT(31), + }, + .md_reg = TV_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(15, 14) | BM(2, 0)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_tv, + .freq_tbl = clk_tbl_tv, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "tv_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 27030000, NOMINAL, 149000000), + CLK_INIT(tv_src_clk.c), + }, +}; + +static struct cdiv_clk tv_src_div_clk = { + .b = { + .ctl_reg = TV_NS_REG, + .halt_check = NOCHECK, + }, + .ns_reg = TV_NS_REG, + .div_offset = 6, + .max_div = 2, + .c = { + .dbg_name = "tv_src_div_clk", + .ops = &clk_ops_cdiv, + CLK_INIT(tv_src_div_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, + .retain_reg = TV_CC2_REG, + .retain_mask = BIT(10), + }, + .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 rgb_tv_clk = { + .b = { + .ctl_reg = TV_CC2_REG, + .en_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 27, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "rgb_tv_clk", + .ops = &clk_ops_branch, + CLK_INIT(rgb_tv_clk.c), + }, +}; + +static struct branch_clk npl_tv_clk = { + .b = { + .ctl_reg = TV_CC2_REG, + .en_mask = BIT(16), + .halt_reg = DBG_BUS_VEC_J_REG, + .halt_bit = 26, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "npl_tv_clk", + .ops = &clk_ops_branch, + CLK_INIT(npl_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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_vcodec[] = { + F_VCODEC( 0, gnd, 0, 0), + F_VCODEC( 27000000, pxo, 0, 0), + F_VCODEC( 32000000, pll8, 1, 12), + F_VCODEC( 48000000, pll8, 1, 8), + F_VCODEC( 54860000, pll8, 1, 7), + F_VCODEC( 96000000, pll8, 1, 4), + F_VCODEC(133330000, pll2, 1, 6), + F_VCODEC(200000000, pll2, 1, 4), + F_VCODEC(228570000, pll2, 2, 7), + 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, + .retain_reg = VCODEC_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = VCODEC_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .bank_info = &bmnd_info_vcodec, + .freq_tbl = clk_tbl_vcodec, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "vcodec_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 100000000, NOMINAL, 200000000, + HIGH, 228571000), + CLK_INIT(vcodec_clk.c), + .depends = &vcodec_axi_clk.c, + }, +}; + +#define F_VPE(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + } +static struct clk_freq_tbl clk_tbl_vpe[] = { + F_VPE( 0, gnd, 1), + F_VPE( 27000000, pxo, 1), + F_VPE( 34909000, pll8, 11), + F_VPE( 38400000, pll8, 10), + F_VPE( 64000000, pll8, 6), + F_VPE( 76800000, pll8, 5), + F_VPE( 96000000, pll8, 4), + F_VPE(100000000, pll2, 8), + F_VPE(160000000, pll2, 5), + 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, + .retain_reg = VPE_CC_REG, + .retain_mask = BIT(31), + }, + .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 = &rcg_dummy_freq, + .c = { + .dbg_name = "vpe_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 76800000, NOMINAL, 160000000), + CLK_INIT(vpe_clk.c), + .depends = &vpe_axi_clk.c, + }, +}; + +#define F_VFE(f, s, d, m, n) \ + { \ + .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), \ + } + +static struct clk_freq_tbl clk_tbl_vfe[] = { + F_VFE( 0, gnd, 1, 0, 0), + F_VFE( 13960000, pll8, 1, 2, 55), + F_VFE( 27000000, pxo, 1, 0, 0), + F_VFE( 36570000, pll8, 1, 2, 21), + F_VFE( 38400000, pll8, 2, 1, 5), + F_VFE( 45180000, pll8, 1, 2, 17), + F_VFE( 48000000, pll8, 2, 1, 4), + F_VFE( 54860000, pll8, 1, 1, 7), + F_VFE( 64000000, pll8, 2, 1, 3), + F_VFE( 76800000, pll8, 1, 1, 5), + F_VFE( 96000000, pll8, 2, 1, 2), + F_VFE(109710000, pll8, 1, 2, 7), + F_VFE(128000000, pll8, 1, 1, 3), + F_VFE(153600000, pll8, 1, 2, 5), + F_VFE(200000000, pll2, 2, 1, 2), + F_VFE(228570000, pll2, 1, 2, 7), + F_VFE(266667000, pll2, 1, 1, 3), + F_VFE(320000000, pll2, 1, 2, 5), + F_END +}; + +static unsigned long fmax_vfe_8064[MAX_VDD_LEVELS] __initdata = { + [VDD_DIG_LOW] = 128000000, + [VDD_DIG_NOMINAL] = 266667000, + [VDD_DIG_HIGH] = 320000000 +}; + +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), + .retain_reg = VFE_CC2_REG, + .retain_mask = BIT(31), + }, + .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)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_vfe, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "vfe_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 110000000, NOMINAL, 266667000, + HIGH, 320000000), + CLK_INIT(vfe_clk.c), + .depends = &vfe_axi_clk.c, + }, +}; + +static struct branch_clk csi_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 = "csi_vfe_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi_vfe_clk.c), + }, +}; + +/* + * Low Power Audio Clocks + */ +#define F_AIF_OSR(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_aif_osr[] = { + F_AIF_OSR( 0, gnd, 1, 0, 0), + F_AIF_OSR( 512000, pll4, 4, 1, 192), + F_AIF_OSR( 768000, pll4, 4, 1, 128), + F_AIF_OSR( 1024000, pll4, 4, 1, 96), + F_AIF_OSR( 1536000, pll4, 4, 1, 64), + F_AIF_OSR( 2048000, pll4, 4, 1, 48), + F_AIF_OSR( 3072000, pll4, 4, 1, 32), + F_AIF_OSR( 4096000, pll4, 4, 1, 24), + F_AIF_OSR( 6144000, pll4, 4, 1, 16), + F_AIF_OSR( 8192000, pll4, 4, 1, 12), + F_AIF_OSR(12288000, pll4, 4, 1, 8), + F_AIF_OSR(24576000, pll4, 4, 1, 4), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(LOW, 24576000), \ + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(LOW, 24576000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define CLK_AIF_BIT(i, ns, h_r) \ + struct cdiv_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(15), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ext_mask = BIT(14), \ + .div_offset = 10, \ + .max_div = 16, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_cdiv, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define CLK_AIF_BIT_DIV(i, ns, h_r) \ + struct cdiv_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(19), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ext_mask = BIT(18), \ + .div_offset = 10, \ + .max_div = 256, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_cdiv, \ + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_pcm[] = { + { .ns_val = BIT(10) /* external input */ }, + F_PCM( 512000, pll4, 4, 1, 192), + F_PCM( 768000, pll4, 4, 1, 128), + F_PCM( 1024000, pll4, 4, 1, 96), + F_PCM( 1536000, pll4, 4, 1, 64), + F_PCM( 2048000, pll4, 4, 1, 48), + F_PCM( 3072000, pll4, 4, 1, 32), + F_PCM( 4096000, pll4, 4, 1, 24), + F_PCM( 6144000, pll4, 4, 1, 16), + F_PCM( 8192000, pll4, 4, 1, 12), + F_PCM(12288000, pll4, 4, 1, 8), + F_PCM(24576000, pll4, 4, 1, 4), + 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) | BIT(10) | BM(6, 0), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pcm, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "pcm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 24576000), + 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)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_aif_osr, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "audio_slimbus_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 24576000), + 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_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, NULL); +DEFINE_CLK_RPM(cfpb_clk, cfpb_a_clk, CFPB, NULL); +DEFINE_CLK_RPM(dfab_clk, dfab_a_clk, DAYTONA_FABRIC, NULL); +DEFINE_CLK_RPM(ebi1_clk, ebi1_a_clk, EBI1, NULL); +DEFINE_CLK_RPM(mmfab_clk, mmfab_a_clk, MM_FABRIC, NULL); +DEFINE_CLK_RPM(mmfpb_clk, mmfpb_a_clk, MMFPB, NULL); +DEFINE_CLK_RPM(sfab_clk, sfab_a_clk, SYSTEM_FABRIC, NULL); +DEFINE_CLK_RPM(sfpb_clk, sfpb_a_clk, SFPB, NULL); + +static DEFINE_CLK_VOTER(sfab_msmbus_a_clk, &sfab_a_clk.c, 0); +static DEFINE_CLK_VOTER(sfab_tmr_a_clk, &sfab_a_clk.c, 0); + +static DEFINE_CLK_VOTER(dfab_dsps_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_usb_hs_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_usb_hs3_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_usb_hs4_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc1_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc2_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc3_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc4_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc5_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sps_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_bam_dmux_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_scm_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_qseecom_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_tzcom_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_msmbus_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_msmbus_a_clk, &dfab_a_clk.c, 0); + +static DEFINE_CLK_VOTER(ebi1_msmbus_clk, &ebi1_clk.c, LONG_MAX); +static DEFINE_CLK_VOTER(ebi1_adm_clk, &ebi1_clk.c, 0); + +#ifdef CONFIG_DEBUG_FS +struct measure_sel { + u32 test_vector; + struct clk *clk; +}; + +static DEFINE_CLK_MEASURE(l2_m_clk); +static DEFINE_CLK_MEASURE(krait0_m_clk); +static DEFINE_CLK_MEASURE(krait1_m_clk); +static DEFINE_CLK_MEASURE(krait2_m_clk); +static DEFINE_CLK_MEASURE(krait3_m_clk); +static DEFINE_CLK_MEASURE(q6sw_clk); +static DEFINE_CLK_MEASURE(q6fw_clk); +static DEFINE_CLK_MEASURE(q6_func_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(0x1F), &gp0_clk.c }, + { TEST_PER_LS(0x20), &gp1_clk.c }, + { TEST_PER_LS(0x21), &gp2_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(0x59), &sfab_sata_s_p_clk.c }, + { TEST_PER_LS(0x5A), &gsbi8_uart_clk.c }, + { TEST_PER_LS(0x5A), &sata_p_clk.c }, + { TEST_PER_LS(0x5B), &sata_rxoob_clk.c }, + { TEST_PER_LS(0x5C), &sata_pmalive_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(0x5E), &pcie_p_clk.c }, + { TEST_PER_LS(0x5F), &ce3_p_clk.c }, + { TEST_PER_LS(0x60), &ce3_core_clk.c }, + { TEST_PER_LS(0x63), &usb_hs3_p_clk.c }, + { TEST_PER_LS(0x64), &usb_hs3_xcvr_clk.c }, + { TEST_PER_LS(0x65), &usb_hs4_p_clk.c }, + { TEST_PER_LS(0x66), &usb_hs4_xcvr_clk.c }, + { TEST_PER_LS(0x6B), &sata_phy_ref_clk.c }, + { TEST_PER_LS(0x6C), &sata_phy_cfg_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(0x86), &usb_hsic_p_clk.c }, + { TEST_PER_LS(0x87), &usb_hsic_system_clk.c }, + { TEST_PER_LS(0x88), &usb_hsic_xcvr_fs_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(0x9D), &usb_hsic_hsio_cal_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(0x26), &q6sw_clk }, + { TEST_PER_HS(0x27), &q6fw_clk }, + { TEST_PER_HS(0x2A), &adm0_clk.c }, + { TEST_PER_HS(0x31), &sata_a_clk.c }, + { TEST_PER_HS(0x2D), &pcie_phy_ref_clk.c }, + { TEST_PER_HS(0x32), &pcie_a_clk.c }, + { TEST_PER_HS(0x34), &ebi1_clk.c }, + { TEST_PER_HS(0x34), &ebi1_a_clk.c }, + { TEST_PER_HS(0x50), &usb_hsic_hsic_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), &csi_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_LS(0x27), &cam2_clk.c }, + { TEST_MM_LS(0x28), &vcap_p_clk.c }, + + { TEST_MM_HS(0x00), &csi0_clk.c }, + { TEST_MM_HS(0x01), &csi1_clk.c }, + { TEST_MM_HS(0x04), &csi_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_MM_HS(0x2D), &csi2_clk.c }, + { TEST_MM_HS(0x2E), &csi2_phy_clk.c }, + { TEST_MM_HS(0x2F), &csi2phy_timer_clk.c }, + { TEST_MM_HS(0x30), &csi_pix1_clk.c }, + { TEST_MM_HS(0x31), &csi_rdi1_clk.c }, + { TEST_MM_HS(0x32), &csi_rdi2_clk.c }, + { TEST_MM_HS(0x33), &vcap_clk.c }, + { TEST_MM_HS(0x34), &vcap_npl_clk.c }, + { TEST_MM_HS(0x34), &gfx3d_axi_clk_8930.c }, + { TEST_MM_HS(0x35), &vcap_axi_clk.c }, + { TEST_MM_HS(0x36), &rgb_tv_clk.c }, + { TEST_MM_HS(0x37), &npl_tv_clk.c }, + { TEST_MM_HS(0x38), &gfx3d_axi_clk_8064.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 }, + + { TEST_LPA_HS(0x00), &q6_func_clk }, + + { TEST_CPUL2(0x2), &l2_m_clk }, + { TEST_CPUL2(0x0), &krait0_m_clk }, + { TEST_CPUL2(0x1), &krait1_m_clk }, + { TEST_CPUL2(0x4), &krait2_m_clk }, + { TEST_CPUL2(0x5), &krait3_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 multiplier. + */ + clk->sample_ticks = 0x10000; + clk_sel = p->test_vector & TEST_CLK_SEL_MASK; + clk->multiplier = 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_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_LPA_HS: + writel_relaxed(0x402BC00, CLK_TEST_REG); + writel_relaxed(BVAL(2, 1, clk_sel)|BIT(0), + LCC_CLK_HS_DEBUG_CFG_REG); + break; + case TEST_TYPE_CPUL2: + writel_relaxed(0x4030400, CLK_TEST_REG); + writel_relaxed(0x80|BVAL(5, 3, clk_sel), GCC_APCS_CLK_DIAG); + 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(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 long 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; + + ret = clk_prepare_enable(&cxo_clk.c); + if (ret) { + pr_warning("CXO clock failed to enable. Can't measure\n"); + return 0; + } + + 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)); + ret = (raw_count_full * clk->multiplier); + } + + /* Route dbg_hs_clk to PLLTEST. 300mV single-ended amplitude. */ + writel_relaxed(0x38F8, PLLTEST_PAD_CFG_REG); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + clk_disable_unprepare(&cxo_clk.c); + + return ret; +} +#else /* !CONFIG_DEBUG_FS */ +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + return -EINVAL; +} + +static unsigned long measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops clk_ops_measure = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, +}; + +static struct measure_clk measure_clk = { + .c = { + .dbg_name = "measure_clk", + .ops = &clk_ops_measure, + CLK_INIT(measure_clk.c), + }, + .multiplier = 1, +}; + +static struct clk_lookup msm_clocks_8064[] = { + CLK_LOOKUP("xo", cxo_a_clk.c, ""), + CLK_LOOKUP("xo", pxo_a_clk.c, ""), + CLK_LOOKUP("cxo", cxo_clk.c, "wcnss_wlan.0"), + CLK_LOOKUP("cxo", cxo_clk.c, "pil_riva"), + CLK_LOOKUP("xo", pxo_clk.c, "pil_qdsp6v4.0"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_qdsp6v4.1"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_qdsp6v4.2"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_gss"), + CLK_LOOKUP("xo", cxo_clk.c, "BAM_RMNT"), + CLK_LOOKUP("xo", cxo_clk.c, "msm_xo"), + 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.c, "debug"), + + CLK_LOOKUP("bus_clk", afab_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_a_clk", afab_a_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_clk", cfpb_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_a_clk", cfpb_a_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_clk", sfab_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_a_clk", sfab_msmbus_a_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_clk", sfpb_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_a_clk", sfpb_a_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_clk", mmfab_clk.c, "msm_mm_fab"), + CLK_LOOKUP("bus_a_clk", mmfab_a_clk.c, "msm_mm_fab"), + CLK_LOOKUP("mem_clk", ebi1_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("mem_a_clk", ebi1_a_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_clk", dfab_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_a_clk", dfab_msmbus_a_clk.c, "msm_bus"), + + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, ""), + CLK_LOOKUP("mmfpb_clk", mmfpb_clk.c, ""), + CLK_LOOKUP("mmfpb_a_clk", mmfpb_a_clk.c, "clock-8960"), + CLK_LOOKUP("cfpb_a_clk", cfpb_a_clk.c, "clock-8960"), + + CLK_LOOKUP("core_clk", gp0_clk.c, ""), + CLK_LOOKUP("core_clk", gp1_clk.c, ""), + CLK_LOOKUP("core_clk", gp2_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi1_uart_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("core_clk", gsbi2_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi4_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi5_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi6_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi7_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", gsbi1_qup_clk.c, "qup_i2c.0"), + CLK_LOOKUP("core_clk", gsbi2_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_qup_clk.c, "qup_i2c.3"), + CLK_LOOKUP("core_clk", gsbi4_qup_clk.c, "qup_i2c.4"), + CLK_LOOKUP("core_clk", gsbi5_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("core_clk", gsbi5_qup_clk.c, "qup_i2c.5"), + CLK_LOOKUP("core_clk", gsbi6_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi7_qup_clk.c, ""), + CLK_LOOKUP("core_clk", pdm_clk.c, ""), + CLK_LOOKUP("mem_clk", pmem_clk.c, "msm_sps"), + CLK_LOOKUP("core_clk", prng_clk.c, "msm_rng.0"), + CLK_LOOKUP("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, ""), + CLK_LOOKUP("core_clk", tssc_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_hs1_xcvr_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs3_xcvr_clk.c, "msm_ehci_host.0"), + CLK_LOOKUP("alt_core_clk", usb_hs4_xcvr_clk.c, "msm_ehci_host.1"), + CLK_LOOKUP("src_clk", usb_fs1_src_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_fs1_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs1_sys_clk.c, ""), + CLK_LOOKUP("ref_clk", sata_phy_ref_clk.c, ""), + CLK_LOOKUP("cfg_clk", sata_phy_cfg_clk.c, ""), + CLK_LOOKUP("src_clk", sata_src_clk.c, ""), + CLK_LOOKUP("core_rxoob_clk", sata_rxoob_clk.c, ""), + CLK_LOOKUP("core_pmalive_clk", sata_pmalive_clk.c, ""), + CLK_LOOKUP("bus_clk", sata_a_clk.c, ""), + CLK_LOOKUP("iface_clk", sata_p_clk.c, ""), + CLK_LOOKUP("slave_iface_clk", sfab_sata_s_p_clk.c, ""), + CLK_LOOKUP("iface_clk", ce3_p_clk.c, "qce.0"), + CLK_LOOKUP("iface_clk", ce3_p_clk.c, "qcrypto.0"), + CLK_LOOKUP("core_clk", ce3_core_clk.c, "qce.0"), + CLK_LOOKUP("core_clk", ce3_core_clk.c, "qcrypto.0"), + CLK_LOOKUP("ce3_core_src_clk", ce3_src_clk.c, "qce.0"), + CLK_LOOKUP("ce3_core_src_clk", ce3_src_clk.c, "qcrypto.0"), + CLK_LOOKUP("dma_bam_pclk", dma_bam_p_clk.c, NULL), + CLK_LOOKUP("iface_clk", gsbi1_p_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("iface_clk", gsbi1_p_clk.c, "qup_i2c.0"), + CLK_LOOKUP("iface_clk", gsbi2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi3_p_clk.c, "qup_i2c.3"), + CLK_LOOKUP("iface_clk", gsbi4_p_clk.c, "qup_i2c.4"), + CLK_LOOKUP("iface_clk", gsbi5_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("iface_clk", gsbi5_p_clk.c, "qup_i2c.5"), + CLK_LOOKUP("iface_clk", gsbi6_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi7_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_fs1_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_hs1_p_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", usb_hs3_p_clk.c, "msm_ehci_host.0"), + CLK_LOOKUP("iface_clk", usb_hs4_p_clk.c, "msm_ehci_host.1"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", pcie_p_clk.c, "msm_pcie"), + CLK_LOOKUP("ref_clk", pcie_phy_ref_clk.c, "msm_pcie"), + CLK_LOOKUP("bus_clk", pcie_a_clk.c, "msm_pcie"), + CLK_LOOKUP("core_clk", adm0_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", adm0_p_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", pmic_arb0_p_clk.c, ""), + CLK_LOOKUP("iface_clk", pmic_arb1_p_clk.c, ""), + CLK_LOOKUP("core_clk", pmic_ssbi2_clk.c, ""), + CLK_LOOKUP("mem_clk", rpm_msg_ram_p_clk.c, ""), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-001a"), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-0034"), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-0020"), + CLK_LOOKUP("cam_clk", cam1_clk.c, "4-0048"), + CLK_LOOKUP("cam_clk", cam1_clk.c, "4-006c"), + CLK_LOOKUP("csi_src_clk", csi0_src_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_src_clk", csi1_src_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_src_clk", csi2_src_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_clk", csi0_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_clk", csi2_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_phy_clk", csi0_phy_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_phy_clk", csi1_phy_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_phy_clk", csi2_phy_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_pix_clk", csi_pix_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_pix1_clk", csi_pix1_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi_clk", csi_rdi_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi1_clk", csi_rdi1_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi2_clk", csi_rdi2_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.0"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.1"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.2"), + CLK_LOOKUP("csiphy_timer_clk", csi0phy_timer_clk.c, "msm_csiphy.0"), + CLK_LOOKUP("csiphy_timer_clk", csi1phy_timer_clk.c, "msm_csiphy.1"), + CLK_LOOKUP("csiphy_timer_clk", csi2phy_timer_clk.c, "msm_csiphy.2"), + CLK_LOOKUP("byte_clk", dsi1_byte_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("byte_clk", dsi2_byte_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("esc_clk", dsi1_esc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("esc_clk", dsi2_esc_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("rgb_clk", rgb_tv_clk.c, ""), + CLK_LOOKUP("npl_clk", npl_tv_clk.c, ""), + + CLK_LOOKUP("core_clk", gfx3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("bus_clk", + gfx3d_axi_clk_8064.c, "footswitch-8x60.2"), + CLK_LOOKUP("iface_clk", vcap_p_clk.c, ""), + CLK_LOOKUP("iface_clk", vcap_p_clk.c, "msm_vcap.0"), + CLK_LOOKUP("iface_clk", vcap_p_clk.c, "footswitch-8x60.10"), + CLK_LOOKUP("bus_clk", vcap_axi_clk.c, "footswitch-8x60.10"), + CLK_LOOKUP("core_clk", vcap_clk.c, ""), + CLK_LOOKUP("core_clk", vcap_clk.c, "msm_vcap.0"), + CLK_LOOKUP("core_clk", vcap_clk.c, "footswitch-8x60.10"), + CLK_LOOKUP("vcap_npl_clk", vcap_npl_clk.c, ""), + CLK_LOOKUP("vcap_npl_clk", vcap_npl_clk.c, "msm_vcap.0"), + CLK_LOOKUP("bus_clk", ijpeg_axi_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("mem_clk", imem_axi_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("core_clk", jpegd_clk.c, ""), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("lut_clk", lut_mdp_clk.c, "mdp.0"), + CLK_LOOKUP("lut_clk", lut_mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("core_clk", rot_clk.c, "msm_rotator.0"), + CLK_LOOKUP("core_clk", rot_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("tv_src_clk", tv_src_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("src_clk", tv_src_clk.c, "dtv.0"), + CLK_LOOKUP("div_clk", tv_src_div_clk.c, ""), + CLK_LOOKUP("core_clk", vcodec_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("mdp_clk", mdp_tv_clk.c, "dtv.0"), + CLK_LOOKUP("tv_clk", mdp_tv_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("hdmi_clk", hdmi_tv_clk.c, "dtv.0"), + CLK_LOOKUP("core_clk", hdmi_app_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("vpe_clk", vpe_clk.c, "msm_vpe.0"), + CLK_LOOKUP("core_clk", vpe_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("core_clk", vfe_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("csi_vfe_clk", csi_vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("bus_clk", vfe_axi_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("bus_clk", mdp_axi_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("bus_clk", rot_axi_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("bus_clk", vcodec_axi_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_a_clk", vcodec_axi_a_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_b_clk", vcodec_axi_b_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_clk", vpe_axi_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("arb_clk", amp_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("arb_clk", amp_p_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.2"), + CLK_LOOKUP("master_iface_clk", dsi1_m_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("slave_iface_clk", dsi1_s_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("master_iface_clk", dsi2_m_p_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("slave_iface_clk", dsi2_s_p_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("master_iface_clk", hdmi_m_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("slave_iface_clk", hdmi_s_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "msm_gemini.0"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("iface_clk", jpegd_p_clk.c, ""), + CLK_LOOKUP("mem_iface_clk", imem_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "mdp.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("iface_clk", smmu_p_clk.c, "msm_iommu"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "msm_rotator.0"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("vfe_pclk", vfe_p_clk.c, "msm_vfe.0"), + CLK_LOOKUP("iface_clk", vfe_p_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("vpe_pclk", vpe_p_clk.c, "msm_vpe.0"), + CLK_LOOKUP("iface_clk", vpe_p_clk.c, "footswitch-8x60.9"), + + CLK_LOOKUP("bit_clk", mi2s_bit_clk.c, + "msm-dai-q6-mi2s"), + CLK_LOOKUP("osr_clk", mi2s_osr_clk.c, + "msm-dai-q6-mi2s"), + CLK_LOOKUP("bit_clk", codec_i2s_mic_bit_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("osr_clk", codec_i2s_mic_osr_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("bit_clk", spare_i2s_mic_bit_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("osr_clk", spare_i2s_mic_osr_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("bit_clk", codec_i2s_spkr_bit_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("osr_clk", codec_i2s_spkr_osr_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("bit_clk", spare_i2s_spkr_bit_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("osr_clk", spare_i2s_spkr_osr_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.2"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.3"), + CLK_LOOKUP("sps_slimbus_clk", sps_slimbus_clk.c, ""), + CLK_LOOKUP("core_clk", audio_slimbus_clk.c, "msm_slim_ctrl.1"), + CLK_LOOKUP("core_clk", jpegd_axi_clk.c, ""), + CLK_LOOKUP("core_clk", vpe_axi_clk.c, ""), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, ""), + CLK_LOOKUP("core_clk", vcap_axi_clk.c, ""), + CLK_LOOKUP("core_clk", rot_axi_clk.c, ""), + CLK_LOOKUP("core_clk", ijpeg_axi_clk.c, ""), + CLK_LOOKUP("core_clk", vfe_axi_clk.c, ""), + CLK_LOOKUP("core_clk", vcodec_axi_a_clk.c, ""), + CLK_LOOKUP("core_clk", vcodec_axi_b_clk.c, ""), + CLK_LOOKUP("core_clk", gfx3d_axi_clk_8064.c, ""), + + CLK_LOOKUP("dfab_dsps_clk", dfab_dsps_clk.c, NULL), + CLK_LOOKUP("core_clk", dfab_usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("core_clk", dfab_usb_hs3_clk.c, "msm_ehci_host.0"), + CLK_LOOKUP("core_clk", dfab_usb_hs3_clk.c, "msm_ehci_host.1"), + CLK_LOOKUP("bus_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("bus_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("bus_clk", dfab_sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("bus_clk", dfab_sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("dfab_clk", dfab_sps_clk.c, "msm_sps"), + CLK_LOOKUP("bus_clk", dfab_bam_dmux_clk.c, "BAM_RMNT"), + CLK_LOOKUP("bus_clk", dfab_scm_clk.c, "scm"), + CLK_LOOKUP("bus_clk", dfab_qseecom_clk.c, "qseecom"), + CLK_LOOKUP("bus_clk", dfab_tzcom_clk.c, "tzcom"), + + CLK_LOOKUP("alt_core_clk", usb_hsic_xcvr_fs_clk.c, "msm_hsic_host"), + CLK_LOOKUP("phy_clk", usb_hsic_hsic_clk.c, "msm_hsic_host"), + CLK_LOOKUP("cal_clk", usb_hsic_hsio_cal_clk.c, "msm_hsic_host"), + CLK_LOOKUP("core_clk", usb_hsic_system_clk.c, "msm_hsic_host"), + CLK_LOOKUP("iface_clk", usb_hsic_p_clk.c, "msm_hsic_host"), + + CLK_LOOKUP("core_clk", jpegd_axi_clk.c, "msm_iommu.0"), + CLK_LOOKUP("core_clk", vpe_axi_clk.c, "msm_iommu.1"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.2"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.3"), + CLK_LOOKUP("core_clk", rot_axi_clk.c, "msm_iommu.4"), + CLK_LOOKUP("core_clk", ijpeg_axi_clk.c, "msm_iommu.5"), + CLK_LOOKUP("core_clk", vfe_axi_clk.c, "msm_iommu.6"), + CLK_LOOKUP("core_clk", vcodec_axi_a_clk.c, "msm_iommu.7"), + CLK_LOOKUP("core_clk", vcodec_axi_b_clk.c, "msm_iommu.8"), + CLK_LOOKUP("core_clk", gfx3d_axi_clk_8064.c, "msm_iommu.9"), + CLK_LOOKUP("core_clk", gfx3d_axi_clk_8064.c, "msm_iommu.10"), + CLK_LOOKUP("core_clk", vcap_axi_clk.c, "msm_iommu.11"), + + CLK_LOOKUP("mdp_iommu_clk", mdp_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("rot_iommu_clk", rot_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu0_clk", vcodec_axi_a_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu1_clk", vcodec_axi_b_clk.c, "msm_vidc.0"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_axi_clk.c, "pil_vidc"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "pil_vidc"), + + CLK_LOOKUP("mem_clk", ebi1_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("l2_mclk", l2_m_clk, ""), + CLK_LOOKUP("krait0_mclk", krait0_m_clk, ""), + CLK_LOOKUP("krait1_mclk", krait1_m_clk, ""), + CLK_LOOKUP("krait2_mclk", krait2_m_clk, ""), + CLK_LOOKUP("krait3_mclk", krait3_m_clk, ""), +}; + +static struct clk_lookup msm_clocks_8960[] = { + CLK_LOOKUP("xo", cxo_a_clk.c, ""), + CLK_LOOKUP("xo", pxo_a_clk.c, ""), + CLK_LOOKUP("cxo", cxo_clk.c, "wcnss_wlan.0"), + CLK_LOOKUP("cxo", cxo_clk.c, "pil_riva"), + CLK_LOOKUP("xo", pxo_clk.c, "pil_qdsp6v4.0"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_qdsp6v4.1"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_qdsp6v4.2"), + CLK_LOOKUP("xo", cxo_clk.c, "BAM_RMNT"), + CLK_LOOKUP("xo", cxo_clk.c, "msm_xo"), + 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.c, "debug"), + + CLK_LOOKUP("bus_clk", afab_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_a_clk", afab_a_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_clk", cfpb_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_a_clk", cfpb_a_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_clk", sfab_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_a_clk", sfab_msmbus_a_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_clk", sfpb_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_a_clk", sfpb_a_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_clk", mmfab_clk.c, "msm_mm_fab"), + CLK_LOOKUP("bus_a_clk", mmfab_a_clk.c, "msm_mm_fab"), + CLK_LOOKUP("mem_clk", ebi1_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("mem_a_clk", ebi1_a_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_clk", dfab_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_a_clk", dfab_msmbus_a_clk.c, "msm_bus"), + + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("mmfpb_clk", mmfpb_clk.c, NULL), + CLK_LOOKUP("mmfpb_a_clk", mmfpb_a_clk.c, "clock-8960"), + CLK_LOOKUP("cfpb_a_clk", cfpb_a_clk.c, "clock-8960"), + + CLK_LOOKUP("core_clk", gp0_clk.c, ""), + CLK_LOOKUP("core_clk", gp1_clk.c, ""), + CLK_LOOKUP("core_clk", gp2_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi1_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi2_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi4_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi5_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", gsbi6_uart_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", gsbi7_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi8_uart_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("core_clk", gsbi9_uart_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("core_clk", gsbi10_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi11_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi12_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi1_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("core_clk", gsbi2_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_qup_clk.c, "qup_i2c.3"), + CLK_LOOKUP("core_clk", gsbi4_qup_clk.c, "qup_i2c.4"), + CLK_LOOKUP("core_clk", gsbi5_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi6_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi7_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi8_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi9_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi10_qup_clk.c, "qup_i2c.10"), + CLK_LOOKUP("core_clk", gsbi11_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi12_qup_clk.c, "qup_i2c.12"), + CLK_LOOKUP("core_clk", pdm_clk.c, ""), + CLK_LOOKUP("mem_clk", pmem_clk.c, "msm_sps"), + CLK_LOOKUP("core_clk", prng_clk.c, "msm_rng.0"), + CLK_LOOKUP("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("slimbus_xo_src_clk", slimbus_xo_src_clk.c, NULL), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, ""), + CLK_LOOKUP("core_clk", tssc_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_hs1_xcvr_clk.c, "msm_otg"), + CLK_LOOKUP("phy_clk", usb_phy0_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_fs1_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs1_sys_clk.c, ""), + CLK_LOOKUP("src_clk", usb_fs1_src_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_fs2_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs2_sys_clk.c, ""), + CLK_LOOKUP("src_clk", usb_fs2_src_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_hsic_xcvr_fs_clk.c, "msm_hsic_host"), + CLK_LOOKUP("phy_clk", usb_hsic_hsic_clk.c, "msm_hsic_host"), + CLK_LOOKUP("cal_clk", usb_hsic_hsio_cal_clk.c, "msm_hsic_host"), + CLK_LOOKUP("core_clk", usb_hsic_system_clk.c, "msm_hsic_host"), + CLK_LOOKUP("iface_clk", usb_hsic_p_clk.c, "msm_hsic_host"), + CLK_LOOKUP("iface_clk", ce1_p_clk.c, "qce.0"), + CLK_LOOKUP("iface_clk", ce1_p_clk.c, "qcrypto.0"), + CLK_LOOKUP("core_clk", ce1_core_clk.c, "qce.0"), + CLK_LOOKUP("core_clk", ce1_core_clk.c, "qcrypto.0"), + CLK_LOOKUP("dma_bam_pclk", dma_bam_p_clk.c, NULL), + CLK_LOOKUP("iface_clk", gsbi1_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("iface_clk", gsbi2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi3_p_clk.c, "qup_i2c.3"), + CLK_LOOKUP("iface_clk", gsbi4_p_clk.c, "qup_i2c.4"), + CLK_LOOKUP("iface_clk", gsbi5_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("iface_clk", gsbi6_p_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("iface_clk", gsbi7_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi8_p_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("iface_clk", gsbi9_p_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("iface_clk", gsbi10_p_clk.c, "qup_i2c.10"), + CLK_LOOKUP("iface_clk", gsbi11_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi12_p_clk.c, "qup_i2c.12"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_fs1_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_fs2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_hs1_p_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc5_p_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("core_clk", adm0_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", adm0_p_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", pmic_arb0_p_clk.c, ""), + CLK_LOOKUP("iface_clk", pmic_arb1_p_clk.c, ""), + CLK_LOOKUP("core_clk", pmic_ssbi2_clk.c, ""), + CLK_LOOKUP("mem_clk", rpm_msg_ram_p_clk.c, ""), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-001a"), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-006c"), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-0048"), + CLK_LOOKUP("cam_clk", cam2_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-0020"), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-0034"), + CLK_LOOKUP("csi_src_clk", csi0_src_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_src_clk", csi1_src_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_src_clk", csi2_src_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_clk", csi0_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_clk", csi2_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_phy_clk", csi0_phy_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_phy_clk", csi1_phy_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_phy_clk", csi2_phy_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_pix_clk", csi_pix_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi_clk", csi_rdi_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_src_clk", csi2_src_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi2_clk.c, NULL), + CLK_LOOKUP("csi_pix1_clk", csi_pix1_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi1_clk", csi_rdi1_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi2_clk", csi_rdi2_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_phy_clk", csi2_phy_clk.c, NULL), + CLK_LOOKUP("csi2phy_timer_clk", csi2phy_timer_clk.c, NULL), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.0"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.1"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.2"), + CLK_LOOKUP("csiphy_timer_clk", csi0phy_timer_clk.c, "msm_csiphy.0"), + CLK_LOOKUP("csiphy_timer_clk", csi1phy_timer_clk.c, "msm_csiphy.1"), + CLK_LOOKUP("csiphy_timer_clk", csi2phy_timer_clk.c, "msm_csiphy.2"), + CLK_LOOKUP("byte_clk", dsi1_byte_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("byte_clk", dsi2_byte_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("esc_clk", dsi1_esc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("esc_clk", dsi2_esc_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("core_clk", gfx2d0_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("core_clk", gfx2d0_clk.c, "footswitch-8x60.0"), + CLK_LOOKUP("core_clk", gfx2d1_clk.c, "kgsl-2d1.1"), + CLK_LOOKUP("core_clk", gfx2d1_clk.c, "footswitch-8x60.1"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("bus_clk", ijpeg_axi_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("mem_clk", imem_axi_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("core_clk", jpegd_clk.c, ""), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("lut_clk", lut_mdp_clk.c, "mdp.0"), + CLK_LOOKUP("lut_clk", lut_mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("core_clk", rot_clk.c, "msm_rotator.0"), + CLK_LOOKUP("core_clk", rot_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("src_clk", tv_src_clk.c, "dtv.0"), + CLK_LOOKUP("src_clk", tv_src_clk.c, "tvenc.0"), + CLK_LOOKUP("tv_src_clk", tv_src_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("enc_clk", tv_enc_clk.c, "tvenc.0"), + CLK_LOOKUP("dac_clk", tv_dac_clk.c, "tvenc.0"), + CLK_LOOKUP("core_clk", vcodec_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("mdp_clk", mdp_tv_clk.c, "dtv.0"), + CLK_LOOKUP("mdp_clk", mdp_tv_clk.c, "tvenc.0"), + CLK_LOOKUP("tv_clk", mdp_tv_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("hdmi_clk", hdmi_tv_clk.c, "dtv.0"), + CLK_LOOKUP("core_clk", hdmi_app_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("vpe_clk", vpe_clk.c, "msm_vpe.0"), + CLK_LOOKUP("core_clk", vpe_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("core_clk", vfe_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("csi_vfe_clk", csi_vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("bus_clk", vfe_axi_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("bus_clk", mdp_axi_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("bus_clk", rot_axi_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("bus_clk", vcodec_axi_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_a_clk", vcodec_axi_a_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_b_clk", vcodec_axi_b_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_clk", vpe_axi_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("arb_clk", amp_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("arb_clk", amp_p_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.2"), + CLK_LOOKUP("master_iface_clk", dsi1_m_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("slave_iface_clk", dsi1_s_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("master_iface_clk", dsi2_m_p_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("slave_iface_clk", dsi2_s_p_clk.c, "mipi_dsi.2"), + CLK_LOOKUP("iface_clk", gfx2d0_p_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("iface_clk", gfx2d0_p_clk.c, "footswitch-8x60.0"), + CLK_LOOKUP("iface_clk", gfx2d1_p_clk.c, "kgsl-2d1.1"), + CLK_LOOKUP("iface_clk", gfx2d1_p_clk.c, "footswitch-8x60.1"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("master_iface_clk", hdmi_m_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("slave_iface_clk", hdmi_s_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "msm_gemini.0"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("iface_clk", jpegd_p_clk.c, ""), + CLK_LOOKUP("mem_iface_clk", imem_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "mdp.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("iface_clk", smmu_p_clk.c, "msm_iommu"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "msm_rotator.0"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("iface_clk", tv_enc_p_clk.c, "tvenc.0"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("vfe_pclk", vfe_p_clk.c, "msm_vfe.0"), + CLK_LOOKUP("iface_clk", vfe_p_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("vpe_pclk", vpe_p_clk.c, "msm_vpe.0"), + CLK_LOOKUP("iface_clk", vpe_p_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("bit_clk", mi2s_bit_clk.c, + "msm-dai-q6-mi2s"), + CLK_LOOKUP("osr_clk", mi2s_osr_clk.c, + "msm-dai-q6-mi2s"), + CLK_LOOKUP("bit_clk", codec_i2s_mic_bit_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("osr_clk", codec_i2s_mic_osr_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("bit_clk", spare_i2s_mic_bit_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("osr_clk", spare_i2s_mic_osr_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("bit_clk", codec_i2s_spkr_bit_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("osr_clk", codec_i2s_spkr_osr_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("bit_clk", spare_i2s_spkr_bit_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("osr_clk", spare_i2s_spkr_osr_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.2"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.3"), + CLK_LOOKUP("sps_slimbus_clk", sps_slimbus_clk.c, NULL), + CLK_LOOKUP("core_clk", audio_slimbus_clk.c, "msm_slim_ctrl.1"), + CLK_LOOKUP("core_clk", jpegd_axi_clk.c, "msm_iommu.0"), + CLK_LOOKUP("core_clk", vpe_axi_clk.c, "msm_iommu.1"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.2"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.3"), + CLK_LOOKUP("core_clk", rot_axi_clk.c, "msm_iommu.4"), + CLK_LOOKUP("core_clk", ijpeg_axi_clk.c, "msm_iommu.5"), + CLK_LOOKUP("core_clk", vfe_axi_clk.c, "msm_iommu.6"), + CLK_LOOKUP("core_clk", vcodec_axi_a_clk.c, "msm_iommu.7"), + CLK_LOOKUP("core_clk", vcodec_axi_b_clk.c, "msm_iommu.8"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "msm_iommu.9"), + CLK_LOOKUP("core_clk", gfx2d0_clk.c, "msm_iommu.10"), + CLK_LOOKUP("core_clk", gfx2d1_clk.c, "msm_iommu.11"), + + CLK_LOOKUP("mdp_iommu_clk", mdp_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("rot_iommu_clk", rot_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu0_clk", vcodec_axi_a_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu1_clk", vcodec_axi_b_clk.c, "msm_vidc.0"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_axi_clk.c, "pil_vidc"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "pil_vidc"), + + CLK_LOOKUP("dfab_dsps_clk", dfab_dsps_clk.c, NULL), + CLK_LOOKUP("core_clk", dfab_usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("bus_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("bus_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("bus_clk", dfab_sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("bus_clk", dfab_sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("bus_clk", dfab_sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("dfab_clk", dfab_sps_clk.c, "msm_sps"), + CLK_LOOKUP("bus_clk", dfab_bam_dmux_clk.c, "BAM_RMNT"), + CLK_LOOKUP("bus_clk", dfab_scm_clk.c, "scm"), + CLK_LOOKUP("bus_clk", dfab_qseecom_clk.c, "qseecom"), + CLK_LOOKUP("bus_clk", dfab_tzcom_clk.c, "tzcom"), + + CLK_LOOKUP("mem_clk", ebi1_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("l2_mclk", l2_m_clk, ""), + CLK_LOOKUP("krait0_mclk", krait0_m_clk, ""), + CLK_LOOKUP("krait1_mclk", krait1_m_clk, ""), + CLK_LOOKUP("q6sw_clk", q6sw_clk, ""), + CLK_LOOKUP("q6fw_clk", q6fw_clk, ""), + CLK_LOOKUP("q6_func_clk", q6_func_clk, ""), +}; + +static struct clk_lookup msm_clocks_8930[] = { + CLK_LOOKUP("xo", cxo_clk.c, "msm_xo"), + CLK_LOOKUP("cxo", cxo_clk.c, "wcnss_wlan.0"), + CLK_LOOKUP("cxo", cxo_clk.c, "pil_riva"), + CLK_LOOKUP("xo", pxo_clk.c, "pil_qdsp6v4.0"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_qdsp6v4.1"), + CLK_LOOKUP("xo", cxo_clk.c, "pil_qdsp6v4.2"), + CLK_LOOKUP("xo", cxo_clk.c, "BAM_RMNT"), + 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.c, "debug"), + + CLK_LOOKUP("bus_clk", afab_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_a_clk", afab_a_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_clk", cfpb_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_a_clk", cfpb_a_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_clk", sfab_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_a_clk", sfab_msmbus_a_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_clk", sfpb_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_a_clk", sfpb_a_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_clk", mmfab_clk.c, "msm_mm_fab"), + CLK_LOOKUP("bus_a_clk", mmfab_a_clk.c, "msm_mm_fab"), + CLK_LOOKUP("mem_clk", ebi1_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("mem_a_clk", ebi1_a_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_clk", dfab_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_a_clk", dfab_msmbus_a_clk.c, "msm_bus"), + + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("mmfpb_clk", mmfpb_clk.c, NULL), + CLK_LOOKUP("mmfpb_a_clk", mmfpb_a_clk.c, "clock-8960"), + CLK_LOOKUP("cfpb_a_clk", cfpb_a_clk.c, "clock-8960"), + + CLK_LOOKUP("core_clk", gp0_clk.c, ""), + CLK_LOOKUP("core_clk", gp1_clk.c, ""), + CLK_LOOKUP("core_clk", gp2_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi1_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi2_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi4_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi5_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", gsbi6_uart_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", gsbi7_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi8_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi9_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi10_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi11_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi12_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi1_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("core_clk", gsbi2_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_qup_clk.c, "qup_i2c.3"), + CLK_LOOKUP("core_clk", gsbi4_qup_clk.c, "qup_i2c.4"), + CLK_LOOKUP("core_clk", gsbi5_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi6_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi7_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi8_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi9_qup_clk.c, "qup_i2c.0"), + CLK_LOOKUP("core_clk", gsbi10_qup_clk.c, "qup_i2c.10"), + CLK_LOOKUP("core_clk", gsbi11_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi12_qup_clk.c, "qup_i2c.12"), + CLK_LOOKUP("core_clk", pdm_clk.c, ""), + CLK_LOOKUP("mem_clk", pmem_clk.c, "msm_sps"), + CLK_LOOKUP("core_clk", prng_clk.c, "msm_rng.0"), + CLK_LOOKUP("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, ""), + CLK_LOOKUP("core_clk", tssc_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_hs1_xcvr_clk.c, "msm_otg"), + CLK_LOOKUP("phy_clk", usb_phy0_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_fs1_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs1_sys_clk.c, ""), + CLK_LOOKUP("src_clk", usb_fs1_src_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_fs2_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs2_sys_clk.c, ""), + CLK_LOOKUP("src_clk", usb_fs2_src_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_hsic_xcvr_fs_clk.c, "msm_hsic_host"), + CLK_LOOKUP("phy_clk", usb_hsic_hsic_clk.c, "msm_hsic_host"), + CLK_LOOKUP("cal_clk", usb_hsic_hsio_cal_clk.c, "msm_hsic_host"), + CLK_LOOKUP("core_clk", usb_hsic_system_clk.c, "msm_hsic_host"), + CLK_LOOKUP("iface_clk", usb_hsic_p_clk.c, "msm_hsic_host"), + CLK_LOOKUP("iface_clk", ce1_p_clk.c, "qce.0"), + CLK_LOOKUP("iface_clk", ce1_p_clk.c, "qcrypto.0"), + CLK_LOOKUP("core_clk", ce1_core_clk.c, "qce.0"), + CLK_LOOKUP("core_clk", ce1_core_clk.c, "qcrypto.0"), + CLK_LOOKUP("dma_bam_pclk", dma_bam_p_clk.c, NULL), + CLK_LOOKUP("iface_clk", gsbi1_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("iface_clk", gsbi2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi3_p_clk.c, "qup_i2c.3"), + CLK_LOOKUP("iface_clk", gsbi4_p_clk.c, "qup_i2c.4"), + CLK_LOOKUP("iface_clk", gsbi5_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("iface_clk", gsbi6_p_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("iface_clk", gsbi7_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi8_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi9_p_clk.c, "qup_i2c.0"), + CLK_LOOKUP("iface_clk", gsbi10_p_clk.c, "qup_i2c.10"), + CLK_LOOKUP("iface_clk", gsbi11_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi12_p_clk.c, "qup_i2c.12"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_fs1_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_fs2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_hs1_p_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc5_p_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("core_clk", adm0_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", adm0_p_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", pmic_arb0_p_clk.c, ""), + CLK_LOOKUP("iface_clk", pmic_arb1_p_clk.c, ""), + CLK_LOOKUP("core_clk", pmic_ssbi2_clk.c, ""), + CLK_LOOKUP("mem_clk", rpm_msg_ram_p_clk.c, ""), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-001a"), + CLK_LOOKUP("cam_clk", cam1_clk.c, "4-006c"), + CLK_LOOKUP("cam_clk", cam1_clk.c, "4-0048"), + CLK_LOOKUP("cam_clk", cam2_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam0_clk.c, "4-0020"), + CLK_LOOKUP("csi_src_clk", csi0_src_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_src_clk", csi1_src_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_src_clk", csi2_src_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_clk", csi0_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_clk", csi2_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_phy_clk", csi0_phy_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_phy_clk", csi1_phy_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_phy_clk", csi2_phy_clk.c, "msm_csid.2"), + CLK_LOOKUP("csi_pix_clk", csi_pix_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi_clk", csi_rdi_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_src_clk", csi2_src_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi2_clk.c, NULL), + CLK_LOOKUP("csi_pix1_clk", csi_pix1_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi1_clk", csi_rdi1_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_rdi2_clk", csi_rdi2_clk.c, "msm_ispif.0"), + CLK_LOOKUP("csi_phy_clk", csi2_phy_clk.c, NULL), + CLK_LOOKUP("csi2phy_timer_clk", csi2phy_timer_clk.c, NULL), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.0"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.1"), + CLK_LOOKUP("csiphy_timer_src_clk", + csiphy_timer_src_clk.c, "msm_csiphy.2"), + CLK_LOOKUP("csiphy_timer_clk", csi0phy_timer_clk.c, "msm_csiphy.0"), + CLK_LOOKUP("csiphy_timer_clk", csi1phy_timer_clk.c, "msm_csiphy.1"), + CLK_LOOKUP("csiphy_timer_clk", csi2phy_timer_clk.c, "msm_csiphy.2"), + CLK_LOOKUP("byte_clk", dsi1_byte_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("esc_clk", dsi1_esc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("bus_clk", + gfx3d_axi_clk_8930.c, "footswitch-8x60.2"), + CLK_LOOKUP("bus_clk", ijpeg_axi_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("mem_clk", imem_axi_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("lut_clk", lut_mdp_clk.c, "mdp.0"), + CLK_LOOKUP("lut_clk", lut_mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("core_clk", rot_clk.c, "msm_rotator.0"), + CLK_LOOKUP("core_clk", rot_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("src_clk", tv_src_clk.c, "dtv.0"), + CLK_LOOKUP("src_clk", tv_src_clk.c, "tvenc.0"), + CLK_LOOKUP("tv_src_clk", tv_src_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("dac_clk", tv_dac_clk.c, "tvenc.0"), + CLK_LOOKUP("core_clk", vcodec_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("mdp_clk", mdp_tv_clk.c, "dtv.0"), + CLK_LOOKUP("mdp_clk", mdp_tv_clk.c, "tvenc.0"), + CLK_LOOKUP("tv_clk", mdp_tv_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("hdmi_clk", hdmi_tv_clk.c, "dtv.0"), + CLK_LOOKUP("core_clk", hdmi_app_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("vpe_clk", vpe_clk.c, "msm_vpe.0"), + CLK_LOOKUP("core_clk", vpe_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("core_clk", vfe_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("csi_vfe_clk", csi_vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("bus_clk", vfe_axi_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("bus_clk", mdp_axi_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("bus_clk", rot_axi_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("bus_clk", vcodec_axi_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_a_clk", vcodec_axi_a_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_b_clk", vcodec_axi_b_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_clk", vpe_axi_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("arb_clk", amp_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.0"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.1"), + CLK_LOOKUP("csi_pclk", csi_p_clk.c, "msm_csid.2"), + CLK_LOOKUP("master_iface_clk", dsi1_m_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("slave_iface_clk", dsi1_s_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("master_iface_clk", hdmi_m_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("slave_iface_clk", hdmi_s_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "msm_gemini.0"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("mem_iface_clk", imem_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "mdp.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("iface_clk", smmu_p_clk.c, "msm_iommu"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "msm_rotator.0"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("vfe_pclk", vfe_p_clk.c, "msm_vfe.0"), + CLK_LOOKUP("iface_clk", vfe_p_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("vpe_pclk", vpe_p_clk.c, "msm_vpe.0"), + CLK_LOOKUP("iface_clk", vpe_p_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("bit_clk", mi2s_bit_clk.c, "msm-dai-q6.6"), + CLK_LOOKUP("osr_clk", mi2s_osr_clk.c, "msm-dai-q6.6"), + CLK_LOOKUP("bit_clk", codec_i2s_mic_bit_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("osr_clk", codec_i2s_mic_osr_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("bit_clk", spare_i2s_mic_bit_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("osr_clk", spare_i2s_mic_osr_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("bit_clk", codec_i2s_spkr_bit_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("osr_clk", codec_i2s_spkr_osr_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("bit_clk", spare_i2s_spkr_bit_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("osr_clk", spare_i2s_spkr_osr_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.2"), + CLK_LOOKUP("sps_slimbus_clk", sps_slimbus_clk.c, NULL), + CLK_LOOKUP("core_clk", audio_slimbus_clk.c, "msm_slim_ctrl.1"), + CLK_LOOKUP("core_clk", vpe_axi_clk.c, "msm_iommu.1"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.2"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.3"), + CLK_LOOKUP("core_clk", rot_axi_clk.c, "msm_iommu.4"), + CLK_LOOKUP("core_clk", ijpeg_axi_clk.c, "msm_iommu.5"), + CLK_LOOKUP("core_clk", vfe_axi_clk.c, "msm_iommu.6"), + CLK_LOOKUP("core_clk", vcodec_axi_a_clk.c, "msm_iommu.7"), + CLK_LOOKUP("core_clk", vcodec_axi_b_clk.c, "msm_iommu.8"), + CLK_LOOKUP("core_clk", gfx3d_axi_clk_8930.c, "msm_iommu.9"), + CLK_LOOKUP("core_clk", gfx3d_axi_clk_8930.c, "msm_iommu.10"), + + CLK_LOOKUP("mdp_iommu_clk", mdp_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("rot_iommu_clk", rot_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu0_clk", vcodec_axi_a_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu1_clk", vcodec_axi_b_clk.c, "msm_vidc.0"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_axi_clk.c, "pil_vidc"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "pil_vidc"), + + CLK_LOOKUP("dfab_dsps_clk", dfab_dsps_clk.c, NULL), + CLK_LOOKUP("core_clk", dfab_usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("bus_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("bus_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("bus_clk", dfab_sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("bus_clk", dfab_sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("bus_clk", dfab_sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("dfab_clk", dfab_sps_clk.c, "msm_sps"), + CLK_LOOKUP("bus_clk", dfab_bam_dmux_clk.c, "BAM_RMNT"), + CLK_LOOKUP("bus_clk", dfab_scm_clk.c, "scm"), + CLK_LOOKUP("bus_clk", dfab_qseecom_clk.c, "qseecom"), + + CLK_LOOKUP("mem_clk", ebi1_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("l2_mclk", l2_m_clk, ""), + CLK_LOOKUP("krait0_mclk", krait0_m_clk, ""), + CLK_LOOKUP("krait1_mclk", krait1_m_clk, ""), + CLK_LOOKUP("q6sw_clk", q6sw_clk, ""), + CLK_LOOKUP("q6fw_clk", q6fw_clk, ""), + CLK_LOOKUP("q6_func_clk", q6_func_clk, ""), +}; +/* + * 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 struct pll_config_regs pll4_regs __initdata = { + .l_reg = LCC_PLL0_L_VAL_REG, + .m_reg = LCC_PLL0_M_VAL_REG, + .n_reg = LCC_PLL0_N_VAL_REG, + .config_reg = LCC_PLL0_CONFIG_REG, + .mode_reg = LCC_PLL0_MODE_REG, +}; + +static struct pll_config pll4_config __initdata = { + .l = 0xE, + .m = 0x27A, + .n = 0x465, + .vco_val = 0x0, + .vco_mask = BM(17, 16), + .pre_div_val = 0x0, + .pre_div_mask = BIT(19), + .post_div_val = 0x0, + .post_div_mask = BM(21, 20), + .mn_ena_val = BIT(22), + .mn_ena_mask = BIT(22), + .main_output_val = BIT(23), + .main_output_mask = BIT(23), +}; + +static struct pll_config_regs pll15_regs __initdata = { + .l_reg = MM_PLL3_L_VAL_REG, + .m_reg = MM_PLL3_M_VAL_REG, + .n_reg = MM_PLL3_N_VAL_REG, + .config_reg = MM_PLL3_CONFIG_REG, + .mode_reg = MM_PLL3_MODE_REG, +}; + +static struct pll_config pll15_config __initdata = { + .l = (0x24 | BVAL(31, 7, 0x620)), + .m = 0x1, + .n = 0x9, + .vco_val = BVAL(17, 16, 0x2), + .vco_mask = BM(17, 16), + .pre_div_val = 0x0, + .pre_div_mask = BIT(19), + .post_div_val = 0x0, + .post_div_mask = BM(21, 20), + .mn_ena_val = BIT(22), + .mn_ena_mask = BIT(22), + .main_output_val = BIT(23), + .main_output_mask = BIT(23), +}; + +static struct pll_config_regs pll14_regs __initdata = { + .l_reg = BB_PLL14_L_VAL_REG, + .m_reg = BB_PLL14_M_VAL_REG, + .n_reg = BB_PLL14_N_VAL_REG, + .config_reg = BB_PLL14_CONFIG_REG, + .mode_reg = BB_PLL14_MODE_REG, +}; + +static struct pll_config pll14_config __initdata = { + .l = (0x11 | BVAL(31, 7, 0x620)), + .m = 0x7, + .n = 0x9, + .vco_val = 0x0, + .vco_mask = BM(17, 16), + .pre_div_val = 0x0, + .pre_div_mask = BIT(19), + .post_div_val = 0x0, + .post_div_mask = BM(21, 20), + .mn_ena_val = BIT(22), + .mn_ena_mask = BIT(22), + .main_output_val = BIT(23), + .main_output_mask = BIT(23), +}; + +static void __init reg_init(void) +{ + void __iomem *imem_reg; + + /* Deassert MM SW_RESET_ALL signal. */ + writel_relaxed(0, SW_RESET_ALL_REG); + + /* + * Some bits are only used on 8960 or 8064 or 8930 and are marked as + * reserved bits on the other SoCs. Writing to these reserved bits + * should have no effect. + */ + /* + * Initialize MM AHB registers: Enable the FPB clock and disable HW + * gating on non-8960 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. + */ + if (cpu_is_msm8960() || cpu_is_apq8064()) { + rmwreg(0x44000000, AHB_EN_REG, 0x6C000103); + writel_relaxed(0x3C7097F9, AHB_EN2_REG); + } else { + rmwreg(0x00000003, AHB_EN_REG, 0x6C000103); + writel_relaxed(0x000007F9, AHB_EN2_REG); + } + if (cpu_is_apq8064()) + rmwreg(0x00000001, AHB_EN3_REG, 0x00000001); + + /* Deassert all locally-owned MM AHB resets. */ + rmwreg(0, SW_RESET_AHB_REG, 0xFFF7DFFF); + rmwreg(0, SW_RESET_AHB2_REG, 0x0000000F); + + /* 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. */ + if ((cpu_is_msm8960() && + SOCINFO_VERSION_MAJOR(socinfo_get_version()) >= 3) || + cpu_is_apq8064()) { + rmwreg(0x0003AFF9, MAXI_EN_REG, 0x0803FFFF); + rmwreg(0x3A27FCFF, MAXI_EN2_REG, 0x3A3FFFFF); + rmwreg(0x0027FCFF, MAXI_EN4_REG, 0x017FFFFF); + } else { + rmwreg(0x000007F9, MAXI_EN_REG, 0x0803FFFF); + rmwreg(0x3027FCFF, MAXI_EN2_REG, 0x3A3FFFFF); + rmwreg(0x0027FCFF, MAXI_EN4_REG, 0x017FFFFF); + } + rmwreg(0x0027FCFF, MAXI_EN3_REG, 0x003FFFFF); + if (cpu_is_apq8064()) + rmwreg(0x019FECFF, MAXI_EN5_REG, 0x01FFEFFF); + if (cpu_is_msm8930()) + rmwreg(0x000004FF, MAXI_EN5_REG, 0x00000FFF); + if (cpu_is_msm8960() || cpu_is_apq8064()) + rmwreg(0x00003C38, SAXI_EN_REG, 0x00003FFF); + else + rmwreg(0x000003C7, SAXI_EN_REG, 0x00003FFF); + + /* Enable IMEM's clk_on signal */ + imem_reg = ioremap(0x04b00040, 4); + if (imem_reg) { + writel_relaxed(0x3, imem_reg); + iounmap(imem_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. */ + rmwreg(0x00000000, CSI0_CC_REG, 0x00000410); + rmwreg(0x00000000, CSI1_CC_REG, 0x00000410); + rmwreg(0x80FF0000, DSI1_BYTE_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, DSI_PIXEL_CC_REG, 0xE0FF0010); + rmwreg(0xC0FF0000, GFX3D_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, IJPEG_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, MDP_CC_REG, 0xE1FF0010); + rmwreg(0x80FF0000, MDP_LUT_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, ROT_CC_REG, 0xE0FF0010); + rmwreg(0x000004FF, TV_CC2_REG, 0x000007FF); + rmwreg(0xC0FF0000, VCODEC_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, VFE_CC_REG, 0xE0FF4010); + rmwreg(0x800000FF, VFE_CC2_REG, 0xE00000FF); + rmwreg(0x80FF0000, VPE_CC_REG, 0xE0FF0010); + if (cpu_is_msm8960() || cpu_is_apq8064()) { + rmwreg(0x80FF0000, DSI2_BYTE_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, DSI2_PIXEL_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, JPEGD_CC_REG, 0xE0FF0010); + } + if (cpu_is_msm8960() || cpu_is_msm8930()) + rmwreg(0x80FF0000, TV_CC_REG, 0xE1FFC010); + + if (cpu_is_msm8960()) { + rmwreg(0x80FF0000, GFX2D0_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, GFX2D1_CC_REG, 0xE0FF0010); + } + if (cpu_is_apq8064()) { + rmwreg(0x00000000, TV_CC_REG, 0x00004010); + rmwreg(0x80FF0000, VCAP_CC_REG, 0xE0FF1010); + } + + /* + * Initialize USB_HS_HCLK_FS registers: Set FORCE_C_ON bits so that + * core remain active during halt state of the clk. Also, set sleep + * and wake-up value to max. + */ + rmwreg(0x0000004F, USB_HS1_HCLK_FS_REG, 0x0000007F); + if (cpu_is_apq8064()) { + rmwreg(0x0000004F, USB_HS3_HCLK_FS_REG, 0x0000007F); + rmwreg(0x0000004F, USB_HS4_HCLK_FS_REG, 0x0000007F); + } + + /* 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); + writel_relaxed(0, SW_RESET_CORE2_REG); + + /* 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 */ + if (cpu_is_msm8960()) + 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); + if (cpu_is_msm8960() || cpu_is_apq8064()) + rmwreg(0x2, DSI2_BYTE_NS_REG, 0x7); + + /* + * Source the sata_phy_ref_clk from PXO and set predivider of + * sata_pmalive_clk to 1. + */ + if (cpu_is_apq8064()) { + rmwreg(0, SATA_PHY_REF_CLK_CTL_REG, 0x1); + rmwreg(0, SATA_PMALIVE_CLK_CTL_REG, 0x3); + } + + /* + * TODO: Programming below PLLs and prng_clk is temporary and + * needs to be removed after bootloaders program them. + */ + if (cpu_is_apq8064()) { + u32 is_pll_enabled; + + /* Program pxo_src_clk to source from PXO */ + rmwreg(0x1, PXO_SRC_CLK_CTL_REG, 0x7); + + /* Check if PLL14 is active */ + is_pll_enabled = readl_relaxed(BB_PLL14_STATUS_REG) & BIT(16); + if (!is_pll_enabled) + /* Ref clk = 27MHz and program pll14 to 480MHz */ + configure_pll(&pll14_config, &pll14_regs, 1); + + /* Program PLL15 to 975MHz with ref clk = 27MHz */ + configure_pll(&pll15_config, &pll15_regs, 0); + + /* Check if PLL4 is active */ + is_pll_enabled = readl_relaxed(LCC_PLL0_STATUS_REG) & BIT(16); + if (!is_pll_enabled) + /* Ref clk = 27MHz and program pll4 to 393.2160MHz */ + configure_pll(&pll4_config, &pll4_regs, 1); + + /* Enable PLL4 source on the LPASS Primary PLL Mux */ + writel_relaxed(0x1, LCC_PRI_PLL_CLK_CTL_REG); + + /* Program prng_clk to 64MHz if it isn't configured */ + if (!readl_relaxed(PRNG_CLK_NS_REG)) + writel_relaxed(0x2B, PRNG_CLK_NS_REG); + } + + /* + * Program PLL15 to 900MHz with ref clk = 27MHz and + * only enable PLL main output. + */ + if (cpu_is_msm8930()) { + pll15_config.l = 0x21 | BVAL(31, 7, 0x600); + pll15_config.m = 0x1; + pll15_config.n = 0x3; + configure_pll(&pll15_config, &pll15_regs, 0); + /* Disable AUX and BIST outputs */ + writel_relaxed(0, MM_PLL3_TEST_CTL_REG); + } +} + +static void __init msm8960_clock_pre_init(void) +{ + if (cpu_is_apq8064()) { + vdd_sr2_pll.set_vdd = set_vdd_sr2_pll_8064; + } else if (cpu_is_msm8930() || cpu_is_msm8627()) { + vdd_dig.set_vdd = set_vdd_dig_8930; + vdd_sr2_pll.set_vdd = set_vdd_sr2_pll_8930; + } + + /* + * Change the freq tables for and voltage requirements for + * clocks which differ between 8960 and 8064. + */ + if (cpu_is_apq8064()) { + gfx3d_clk.freq_tbl = clk_tbl_gfx3d_8064; + + memcpy(gfx3d_clk.c.fmax, fmax_gfx3d_8064, + sizeof(gfx3d_clk.c.fmax)); + memcpy(ijpeg_clk.c.fmax, fmax_ijpeg_8064, + sizeof(ijpeg_clk.c.fmax)); + memcpy(mdp_clk.c.fmax, fmax_mdp_8064, + sizeof(ijpeg_clk.c.fmax)); + memcpy(tv_src_clk.c.fmax, fmax_tv_src_8064, + sizeof(tv_src_clk.c.fmax)); + memcpy(vfe_clk.c.fmax, fmax_vfe_8064, + sizeof(vfe_clk.c.fmax)); + + gmem_axi_clk.c.depends = &gfx3d_axi_clk_8064.c; + } + + /* + * Change the freq tables and voltage requirements for + * clocks which differ between 8960 and 8930. + */ + if (cpu_is_msm8930()) { + gfx3d_clk.freq_tbl = clk_tbl_gfx3d_8930; + + memcpy(gfx3d_clk.c.fmax, fmax_gfx3d_8930, + sizeof(gfx3d_clk.c.fmax)); + + pll15_clk.c.rate = 900000000; + gmem_axi_clk.c.depends = &gfx3d_axi_clk_8930.c; + } + if ((readl_relaxed(PRNG_CLK_NS_REG) & 0x7F) == 0x2B) + prng_clk.freq_tbl = clk_tbl_prng_64; + + vote_vdd_level(&vdd_dig, VDD_DIG_HIGH); + + clk_ops_local_pll.enable = sr_pll_clk_enable; + + /* Initialize clock registers. */ + reg_init(); +} + +static void __init msm8960_clock_post_init(void) +{ + /* Keep PXO on whenever APPS cpu is active */ + clk_prepare_enable(&pxo_a_clk.c); + + /* Reset 3D core while clocked to ensure it resets completely. */ + clk_set_rate(&gfx3d_clk.c, 27000000); + clk_prepare_enable(&gfx3d_clk.c); + clk_reset(&gfx3d_clk.c, CLK_RESET_ASSERT); + udelay(5); + clk_reset(&gfx3d_clk.c, CLK_RESET_DEASSERT); + clk_disable_unprepare(&gfx3d_clk.c); + + /* Initialize rates for clocks that only support one. */ + clk_set_rate(&pdm_clk.c, 27000000); + clk_set_rate(&prng_clk.c, prng_clk.freq_tbl->freq_hz); + 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); + if (cpu_is_apq8064()) { + clk_set_rate(&usb_hs3_xcvr_clk.c, 60000000); + clk_set_rate(&usb_hs4_xcvr_clk.c, 60000000); + } + clk_set_rate(&usb_fs1_src_clk.c, 60000000); + if (cpu_is_msm8960() || cpu_is_msm8930()) + clk_set_rate(&usb_fs2_src_clk.c, 60000000); + clk_set_rate(&usb_hsic_xcvr_fs_clk.c, 60000000); + clk_set_rate(&usb_hsic_hsic_src_clk.c, 480000000); + clk_set_rate(&usb_hsic_hsio_cal_clk.c, 9000000); + clk_set_rate(&usb_hsic_system_clk.c, 60000000); + /* + * Set the CSI rates to a safe default to avoid warnings when + * switching csi pix and rdi clocks. + */ + clk_set_rate(&csi0_src_clk.c, 27000000); + clk_set_rate(&csi1_src_clk.c, 27000000); + clk_set_rate(&csi2_src_clk.c, 27000000); + + /* + * The halt status bits for these clocks may be incorrect at boot. + * Toggle these clocks on and off to refresh them. + */ + clk_prepare_enable(&pdm_clk.c); + clk_disable_unprepare(&pdm_clk.c); + clk_prepare_enable(&tssc_clk.c); + clk_disable_unprepare(&tssc_clk.c); + clk_prepare_enable(&usb_hsic_hsic_clk.c); + clk_disable_unprepare(&usb_hsic_hsic_clk.c); + + /* + * Keep sfab floor @ 54MHz so that Krait AHB is at least 27MHz at all + * times when Apps CPU is active. This ensures the timer's requirement + * of Krait AHB running 4 times as fast as the timer itself. + */ + clk_set_rate(&sfab_tmr_a_clk.c, 54000000); + clk_prepare_enable(&sfab_tmr_a_clk.c); +} + +static int __init msm8960_clock_late_init(void) +{ + int rc; + struct clk *mmfpb_a_clk = clk_get_sys("clock-8960", "mmfpb_a_clk"); + struct clk *cfpb_a_clk = clk_get_sys("clock-8960", "cfpb_a_clk"); + + /* Vote for MMFPB to be at least 76.8MHz when an Apps CPU is active. */ + 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_rate(mmfpb_a_clk, 76800000); + if (WARN(rc, "mmfpb_a_clk rate was not set (%d)\n", rc)) + return rc; + rc = clk_prepare_enable(mmfpb_a_clk); + if (WARN(rc, "mmfpb_a_clk not enabled (%d)\n", rc)) + return rc; + + /* Vote for CFPB to be at least 64MHz when an Apps CPU is active. */ + if (WARN(IS_ERR(cfpb_a_clk), "cfpb_a_clk not found (%ld)\n", + PTR_ERR(cfpb_a_clk))) + return PTR_ERR(cfpb_a_clk); + rc = clk_set_rate(cfpb_a_clk, 64000000); + if (WARN(rc, "cfpb_a_clk rate was not set (%d)\n", rc)) + return rc; + rc = clk_prepare_enable(cfpb_a_clk); + if (WARN(rc, "cfpb_a_clk not enabled (%d)\n", rc)) + return rc; + + return unvote_vdd_level(&vdd_dig, VDD_DIG_HIGH); +} + +struct clock_init_data msm8960_clock_init_data __initdata = { + .table = msm_clocks_8960, + .size = ARRAY_SIZE(msm_clocks_8960), + .pre_init = msm8960_clock_pre_init, + .post_init = msm8960_clock_post_init, + .late_init = msm8960_clock_late_init, +}; + +struct clock_init_data apq8064_clock_init_data __initdata = { + .table = msm_clocks_8064, + .size = ARRAY_SIZE(msm_clocks_8064), + .pre_init = msm8960_clock_pre_init, + .post_init = msm8960_clock_post_init, + .late_init = msm8960_clock_late_init, +}; + +struct clock_init_data msm8930_clock_init_data __initdata = { + .table = msm_clocks_8930, + .size = ARRAY_SIZE(msm_clocks_8930), + .pre_init = msm8960_clock_pre_init, + .post_init = msm8960_clock_post_init, + .late_init = msm8960_clock_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 0000000000000000000000000000000000000000..7aed57984b7eaf324da7010c9ab842326921c5e1 --- /dev/null +++ b/arch/arm/mach-msm/clock-8x60.c @@ -0,0 +1,3869 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-pll.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 EBI2_2X_CLK_CTL_REG REG(0x2660) +#define EBI2_CLK_CTL_REG REG(0x2664) +#define GPn_MD_REG(n) REG(0x2D00+(0x20*(n))) +#define GPn_NS_REG(n) REG(0x2D24+(0x20*(n))) +#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 PRNG_CLK_NS_REG REG(0x2E80) +#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_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 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 5 +#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 + */ + +enum vdd_dig_levels { + VDD_DIG_NONE, + VDD_DIG_LOW, + VDD_DIG_NOMINAL, + VDD_DIG_HIGH +}; + +static int set_vdd_dig(struct clk_vdd_class *vdd_class, int level) +{ + static const int vdd_uv[] = { + [VDD_DIG_NONE] = 500000, + [VDD_DIG_LOW] = 1000000, + [VDD_DIG_NOMINAL] = 1100000, + [VDD_DIG_HIGH] = 1200000 + }; + + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8058_S1, RPM_VREG_VOTER3, + vdd_uv[level], 1200000, 1); +} + +static DEFINE_VDD_CLASS(vdd_dig, set_vdd_dig); + +#define VDD_DIG_FMAX_MAP1(l1, f1) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1) +#define VDD_DIG_FMAX_MAP2(l1, f1, l2, f2) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2) +#define VDD_DIG_FMAX_MAP3(l1, f1, l2, f2, l3, f3) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2), \ + .fmax[VDD_DIG_##l3] = (f3) + +DEFINE_CLK_RPM_BRANCH(pxo_clk, pxo_a_clk, PXO, 27000000); +DEFINE_CLK_RPM_BRANCH(cxo_clk, cxo_a_clk, CXO, 19200000); + +static struct pll_vote_clk pll8_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(8), + .status_reg = BB_PLL8_STATUS_REG, + .status_mask = BIT(16), + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll8_clk", + .rate = 384000000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll8_clk.c), + .warned = true, + }, +}; + +static struct pll_clk pll2_clk = { + .mode_reg = MM_PLL1_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll2_clk", + .rate = 800000000, + .ops = &clk_ops_local_pll, + CLK_INIT(pll2_clk.c), + .warned = true, + }, +}; + +static struct pll_clk pll3_clk = { + .mode_reg = MM_PLL2_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll3_clk", + .rate = 0, /* TODO: Detect rate dynamically */ + .ops = &clk_ops_local_pll, + CLK_INIT(pll3_clk.c), + .warned = true, + }, +}; + +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 enum handoff pll4_clk_handoff(struct clk *clk) +{ + struct msm_rpm_iv_pair iv = { MSM_RPM_ID_PLL_4 }; + int rc = msm_rpm_get_status(&iv, 1); + if (rc < 0 || !iv.value) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +static struct clk_ops clk_ops_pll4 = { + .enable = pll4_clk_enable, + .disable = pll4_clk_disable, + .get_parent = pll4_clk_get_parent, + .is_local = pll4_clk_is_local, + .handoff = pll4_clk_handoff, +}; + +static struct fixed_clk pll4_clk = { + .c = { + .dbg_name = "pll4_clk", + .rate = 540672000, + .ops = &clk_ops_pll4, + CLK_INIT(pll4_clk.c), + .warned = true, + }, +}; + +/* + * 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); +} + +/* + * 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, + .retain_reg = MAXI_EN2_REG, + .retain_mask = BIT(10), + }, + .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, + .retain_reg = MAXI_EN_REG, + .retain_mask = BIT(0), + }, + .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 = { + .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_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_bit = 1, + }, + .c = { + .dbg_name = "vpe_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vpe_axi_clk.c), + }, +}; + +static struct branch_clk smi_2x_axi_clk = { + .b = { + .ctl_reg = MAXI_EN2_REG, + .en_mask = BIT(30), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 0, + }, + .c = { + .dbg_name = "smi_2x_axi_clk", + .ops = &clk_ops_branch, + .flags = CLKFLAG_SKIP_AUTO_OFF, + CLK_INIT(smi_2x_axi_clk.c), + }, +}; + +/* AHB Interfaces */ +static struct branch_clk amp_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(24), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(20), + .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, + .retain_reg = AHB_EN2_REG, + .retain_mask = BIT(0), + }, + .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_GP(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GPn_NS_REG(n), \ + .en_mask = BIT(9), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GPn_NS_REG(n), \ + .md_reg = GPn_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gp, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(LOW, 27000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gp[] = { + F_GP( 0, gnd, 1, 0, 0), + F_GP( 9600000, cxo, 2, 0, 0), + F_GP( 13500000, pxo, 2, 0, 0), + F_GP( 19200000, cxo, 1, 0, 0), + F_GP( 27000000, pxo, 1, 0, 0), + F_END +}; + +static CLK_GP(gp0, 0, CLK_HALT_SFPB_MISC_STATE_REG, 7); +static CLK_GP(gp1, 1, CLK_HALT_SFPB_MISC_STATE_REG, 6); +static CLK_GP(gp2, 2, CLK_HALT_SFPB_MISC_STATE_REG, 5); + +#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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_uart, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 32000000, NOMINAL, 64000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_UART(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gsbi_uart[] = { + F_GSBI_UART( 0, gnd, 1, 0, 0), + F_GSBI_UART( 1843200, pll8, 1, 3, 625), + F_GSBI_UART( 3686400, pll8, 1, 6, 625), + F_GSBI_UART( 7372800, pll8, 1, 12, 625), + F_GSBI_UART(14745600, pll8, 1, 24, 625), + F_GSBI_UART(16000000, pll8, 4, 1, 6), + F_GSBI_UART(24000000, pll8, 4, 1, 4), + F_GSBI_UART(32000000, pll8, 4, 1, 3), + F_GSBI_UART(40000000, pll8, 1, 5, 48), + F_GSBI_UART(46400000, pll8, 1, 29, 240), + F_GSBI_UART(48000000, pll8, 4, 1, 2), + F_GSBI_UART(51200000, pll8, 1, 2, 15), + F_GSBI_UART(56000000, pll8, 1, 7, 48), + F_GSBI_UART(58982400, pll8, 1, 96, 625), + F_GSBI_UART(64000000, pll8, 2, 1, 3), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_qup, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 24000000, NOMINAL, 52000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_QUP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gsbi_qup[] = { + F_GSBI_QUP( 0, gnd, 1, 0, 0), + F_GSBI_QUP( 1100000, pxo, 1, 2, 49), + F_GSBI_QUP( 5400000, pxo, 1, 1, 5), + F_GSBI_QUP(10800000, pxo, 1, 2, 5), + F_GSBI_QUP(15060000, pll8, 1, 2, 51), + F_GSBI_QUP(24000000, pll8, 4, 1, 4), + F_GSBI_QUP(25600000, pll8, 1, 1, 15), + F_GSBI_QUP(27000000, pxo, 1, 0, 0), + F_GSBI_QUP(48000000, pll8, 4, 1, 2), + F_GSBI_QUP(51200000, pll8, 1, 2, 15), + 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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + } +static struct clk_freq_tbl clk_tbl_pdm[] = { + F_PDM( 0, gnd, 1), + F_PDM(27000000, pxo, 1), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "pdm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 27000000), + 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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + } +static struct clk_freq_tbl clk_tbl_prng_32[] = { + F_PRNG(32000000, pll8), + F_END +}; + +static struct clk_freq_tbl clk_tbl_prng_64[] = { + F_PRNG(64000000, pll8), + 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_32, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "prng_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 32000000, NOMINAL, 64000000), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_sdc, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_SDC(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_sdc[] = { + F_SDC( 0, gnd, 1, 0, 0), + F_SDC( 144000, pxo, 3, 2, 125), + F_SDC( 400000, pll8, 4, 1, 240), + F_SDC(16000000, pll8, 4, 1, 6), + F_SDC(17070000, pll8, 1, 2, 45), + F_SDC(20210000, pll8, 1, 1, 19), + F_SDC(24000000, pll8, 4, 1, 4), + F_SDC(48000000, pll8, 4, 1, 2), + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_tsif_ref[] = { + F_TSIF_REF( 0, gnd, 1, 0, 0), + F_TSIF_REF(105000, pxo, 1, 1, 256), + 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)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_tsif_ref, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "tsif_ref_clk", + .ops = &clk_ops_rcg, + CLK_INIT(tsif_ref_clk.c), + }, +}; + +#define F_TSSC(f, s) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + } +static struct clk_freq_tbl clk_tbl_tssc[] = { + F_TSSC( 0, gnd), + F_TSSC(27000000, pxo), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "tssc_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 27000000), + CLK_INIT(tssc_clk.c), + }, +}; + +#define F_USB(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_usb[] = { + F_USB( 0, gnd, 1, 0, 0), + F_USB(60000000, pll8, 1, 5, 32), + 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)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hs1_xcvr_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 60000000), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_usb, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(NOMINAL, 60000000), \ + 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), + }, +}; + +static struct branch_clk ebi2_2x_clk = { + .b = { + .ctl_reg = EBI2_2X_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 18, + }, + .c = { + .dbg_name = "ebi2_2x_clk", + .ops = &clk_ops_branch, + CLK_INIT(ebi2_2x_clk.c), + }, +}; + +static struct branch_clk ebi2_clk = { + .b = { + .ctl_reg = EBI2_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 19, + }, + .c = { + .dbg_name = "ebi2_clk", + .ops = &clk_ops_branch, + CLK_INIT(ebi2_clk.c), + .depends = &ebi2_2x_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 + */ + +#define F_CAM(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_cam[] = { + F_CAM( 0, gnd, 1, 0, 0), + F_CAM( 6000000, pll8, 4, 1, 16), + F_CAM( 8000000, pll8, 4, 1, 12), + F_CAM( 12000000, pll8, 4, 1, 8), + F_CAM( 16000000, pll8, 4, 1, 6), + F_CAM( 19200000, pll8, 4, 1, 5), + F_CAM( 24000000, pll8, 4, 1, 4), + F_CAM( 32000000, pll8, 4, 1, 3), + F_CAM( 48000000, pll8, 4, 1, 2), + F_CAM( 64000000, pll8, 3, 1, 2), + F_CAM( 96000000, pll8, 4, 0, 0), + F_CAM(128000000, pll8, 3, 0, 0), + 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)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd_8, + .freq_tbl = clk_tbl_cam, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "cam_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 64000000, NOMINAL, 128000000), + CLK_INIT(cam_clk.c), + }, +}; + +#define F_CSI(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + } +static struct clk_freq_tbl clk_tbl_csi[] = { + F_CSI( 0, gnd, 1), + F_CSI(192000000, pll8, 2), + F_CSI(384000000, pll8, 1), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "csi_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 192000000, NOMINAL, 384000000), + 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), + .retain_reg = MISC_CC2_REG, + .retain_mask = BIT(10), + }, + .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 = &rcg_dummy_freq, + .c = { + .dbg_name = "dsi_byte_clk", + .ops = &clk_ops_rcg, + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gfx2d[] = { + F_GFX2D( 0, gnd, 0, 0), + F_GFX2D( 27000000, pxo, 0, 0), + F_GFX2D( 48000000, pll8, 1, 8), + F_GFX2D( 54857000, pll8, 1, 7), + F_GFX2D( 64000000, pll8, 1, 6), + F_GFX2D( 76800000, pll8, 1, 5), + F_GFX2D( 96000000, pll8, 1, 4), + F_GFX2D(128000000, pll8, 1, 3), + F_GFX2D(145455000, pll2, 2, 11), + F_GFX2D(160000000, pll2, 1, 5), + F_GFX2D(177778000, pll2, 2, 9), + F_GFX2D(200000000, pll2, 1, 4), + F_GFX2D(228571000, pll2, 2, 7), + 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, + .retain_reg = GFX2D0_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = GFX2D0_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_info = &bmnd_info_gfx2d0, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "gfx2d0_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 100000000, NOMINAL, 200000000, + HIGH, 228571000), + 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, + .retain_reg = GFX2D1_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = GFX2D1_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_info = &bmnd_info_gfx2d1, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "gfx2d1_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 100000000, NOMINAL, 200000000, + HIGH, 228571000), + CLK_INIT(gfx2d1_clk.c), + }, +}; + +#define F_GFX3D(f, s, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gfx3d[] = { + F_GFX3D( 0, gnd, 0, 0), + F_GFX3D( 27000000, pxo, 0, 0), + F_GFX3D( 48000000, pll8, 1, 8), + F_GFX3D( 54857000, pll8, 1, 7), + F_GFX3D( 64000000, pll8, 1, 6), + F_GFX3D( 76800000, pll8, 1, 5), + F_GFX3D( 96000000, pll8, 1, 4), + F_GFX3D(128000000, pll8, 1, 3), + F_GFX3D(145455000, pll2, 2, 11), + F_GFX3D(160000000, pll2, 1, 5), + F_GFX3D(177778000, pll2, 2, 9), + F_GFX3D(200000000, pll2, 1, 4), + F_GFX3D(228571000, pll2, 2, 7), + F_GFX3D(266667000, pll2, 1, 3), + F_GFX3D(320000000, pll2, 2, 5), + 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, + .retain_reg = GFX3D_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = GFX3D_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx3d, + .bank_info = &bmnd_info_gfx3d, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "gfx3d_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 96000000, NOMINAL, 200000000, + HIGH, 320000000), + CLK_INIT(gfx3d_clk.c), + .depends = &gmem_axi_clk.c, + }, +}; + +#define F_IJPEG(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_ijpeg[] = { + F_IJPEG( 0, gnd, 1, 0, 0), + F_IJPEG( 27000000, pxo, 1, 0, 0), + F_IJPEG( 36570000, pll8, 1, 2, 21), + F_IJPEG( 54860000, pll8, 7, 0, 0), + F_IJPEG( 96000000, pll8, 4, 0, 0), + F_IJPEG(109710000, pll8, 1, 2, 7), + F_IJPEG(128000000, pll8, 3, 0, 0), + F_IJPEG(153600000, pll8, 1, 2, 5), + F_IJPEG(200000000, pll2, 4, 0, 0), + F_IJPEG(228571000, pll2, 1, 2, 7), + 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, + .retain_reg = IJPEG_CC_REG, + .retain_mask = BIT(31), + }, + .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)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_ijpeg, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "ijpeg_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 110000000, NOMINAL, 228571000), + CLK_INIT(ijpeg_clk.c), + .depends = &ijpeg_axi_clk.c, + }, +}; + +#define F_JPEGD(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + } +static struct clk_freq_tbl clk_tbl_jpegd[] = { + F_JPEGD( 0, gnd, 1), + F_JPEGD( 64000000, pll8, 6), + F_JPEGD( 76800000, pll8, 5), + F_JPEGD( 96000000, pll8, 4), + F_JPEGD(160000000, pll2, 5), + F_JPEGD(200000000, pll2, 4), + 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, + .retain_reg = JPEGD_CC_REG, + .retain_mask = BIT(31), + }, + .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, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "jpegd_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 96000000, NOMINAL, 200000000), + CLK_INIT(jpegd_clk.c), + .depends = &jpegd_axi_clk.c, + }, +}; + +#define F_MDP(f, s, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_mdp[] = { + F_MDP( 0, gnd, 0, 0), + F_MDP( 9600000, pll8, 1, 40), + F_MDP( 13710000, pll8, 1, 28), + F_MDP( 27000000, pxo, 0, 0), + F_MDP( 29540000, pll8, 1, 13), + F_MDP( 34910000, pll8, 1, 11), + F_MDP( 38400000, pll8, 1, 10), + F_MDP( 59080000, pll8, 2, 13), + F_MDP( 76800000, pll8, 1, 5), + F_MDP( 85330000, pll8, 2, 9), + F_MDP( 96000000, pll8, 1, 4), + F_MDP(128000000, pll8, 1, 3), + F_MDP(160000000, pll2, 1, 5), + F_MDP(177780000, pll2, 2, 9), + F_MDP(200000000, pll2, 1, 4), + 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, + .retain_reg = MDP_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = MDP_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_mdp, + .bank_info = &bmnd_info_mdp, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 85330000, NOMINAL, 200000000, + HIGH, 228571000), + CLK_INIT(mdp_clk.c), + .depends = &mdp_axi_clk.c, + }, +}; + +#define F_MDP_VSYNC(f, s) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(13, 13, s##_to_bb_mux), \ + } +static struct clk_freq_tbl clk_tbl_mdp_vsync[] = { + F_MDP_VSYNC(27000000, pxo), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "mdp_vsync_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 27000000), + CLK_INIT(mdp_vsync_clk.c), + }, +}; + +#define F_PIXEL_MDP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_pixel_mdp[] = { + F_PIXEL_MDP( 0, gnd, 1, 0, 0), + F_PIXEL_MDP( 25600000, pll8, 3, 1, 5), + F_PIXEL_MDP( 42667000, pll8, 1, 1, 9), + F_PIXEL_MDP( 43192000, pll8, 1, 64, 569), + F_PIXEL_MDP( 48000000, pll8, 4, 1, 2), + F_PIXEL_MDP( 53990000, pll8, 2, 169, 601), + F_PIXEL_MDP( 64000000, pll8, 2, 1, 3), + F_PIXEL_MDP( 69300000, pll8, 1, 231, 1280), + F_PIXEL_MDP( 76800000, pll8, 1, 1, 5), + F_PIXEL_MDP( 85333000, pll8, 1, 2, 9), + F_PIXEL_MDP(106500000, pll8, 1, 71, 256), + F_PIXEL_MDP(109714000, pll8, 1, 2, 7), + 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, + .retain_reg = PIXEL_CC_REG, + .retain_mask = BIT(31), + }, + .root_en_mask = BIT(2), + .ns_mask = (BM(31, 16) | BM(15, 14) | BM(2, 0)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pixel_mdp, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "pixel_mdp_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 85333000, NOMINAL, 170000000), + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_rot[] = { + F_ROT( 0, gnd, 1), + F_ROT( 27000000, pxo, 1), + F_ROT( 29540000, pll8, 13), + F_ROT( 32000000, pll8, 12), + F_ROT( 38400000, pll8, 10), + F_ROT( 48000000, pll8, 8), + F_ROT( 54860000, pll8, 7), + F_ROT( 64000000, pll8, 6), + F_ROT( 76800000, pll8, 5), + F_ROT( 96000000, pll8, 4), + F_ROT(100000000, pll2, 8), + F_ROT(114290000, pll2, 7), + F_ROT(133330000, pll2, 6), + F_ROT(160000000, pll2, 5), + 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, + .retain_reg = ROT_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = ROT_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_div_banked, + .freq_tbl = clk_tbl_rot, + .bank_info = &bdiv_info_rot, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "rot_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 80000000, NOMINAL, 160000000), + CLK_INIT(rot_clk.c), + .depends = &rot_axi_clk.c, + }, +}; + +#define F_TV(f, s, p_r, d, m, n) \ + { \ + .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), \ + .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), + F_TV( 25200000, pll3, &mm_pll2_rate[0], 2, 0, 0), + F_TV( 27000000, pll3, &mm_pll2_rate[1], 2, 0, 0), + F_TV( 27030000, pll3, &mm_pll2_rate[2], 4, 0, 0), + F_TV( 74250000, pll3, &mm_pll2_rate[3], 2, 0, 0), + F_TV(148500000, pll3, &mm_pll2_rate[4], 2, 0, 0), + F_END +}; + +static struct rcg_clk tv_src_clk = { + .ns_reg = TV_NS_REG, + .b = { + .ctl_reg = TV_CC_REG, + .halt_check = NOCHECK, + .retain_reg = TV_CC_REG, + .retain_mask = BIT(31), + }, + .md_reg = TV_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(15, 14) | BM(2, 0)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_tv, + .freq_tbl = clk_tbl_tv, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "tv_src_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 27030000, NOMINAL, 149000000), + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_vcodec[] = { + F_VCODEC( 0, gnd, 0, 0), + F_VCODEC( 27000000, pxo, 0, 0), + F_VCODEC( 32000000, pll8, 1, 12), + F_VCODEC( 48000000, pll8, 1, 8), + F_VCODEC( 54860000, pll8, 1, 7), + F_VCODEC( 96000000, pll8, 1, 4), + F_VCODEC(133330000, pll2, 1, 6), + F_VCODEC(200000000, pll2, 1, 4), + F_VCODEC(228570000, pll2, 2, 7), + 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, + .retain_reg = VCODEC_CC_REG, + .retain_mask = BIT(31), + }, + .ns_reg = VCODEC_NS_REG, + .md_reg = VCODEC_MD0_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(18, 11) | BM(2, 0)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_vcodec, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "vcodec_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 100000000, NOMINAL, 200000000, + HIGH, 228571000), + CLK_INIT(vcodec_clk.c), + .depends = &vcodec_axi_clk.c, + }, +}; + +#define F_VPE(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + } +static struct clk_freq_tbl clk_tbl_vpe[] = { + F_VPE( 0, gnd, 1), + F_VPE( 27000000, pxo, 1), + F_VPE( 34909000, pll8, 11), + F_VPE( 38400000, pll8, 10), + F_VPE( 64000000, pll8, 6), + F_VPE( 76800000, pll8, 5), + F_VPE( 96000000, pll8, 4), + F_VPE(100000000, pll2, 8), + F_VPE(160000000, pll2, 5), + F_VPE(200000000, pll2, 4), + 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, + .retain_reg = VPE_CC_REG, + .retain_mask = BIT(31), + }, + .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 = &rcg_dummy_freq, + .c = { + .dbg_name = "vpe_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 76800000, NOMINAL, 160000000, + HIGH, 200000000), + CLK_INIT(vpe_clk.c), + .depends = &vpe_axi_clk.c, + }, +}; + +#define F_VFE(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_vfe[] = { + F_VFE( 0, gnd, 1, 0, 0), + F_VFE( 13960000, pll8, 1, 2, 55), + F_VFE( 27000000, pxo, 1, 0, 0), + F_VFE( 36570000, pll8, 1, 2, 21), + F_VFE( 38400000, pll8, 2, 1, 5), + F_VFE( 45180000, pll8, 1, 2, 17), + F_VFE( 48000000, pll8, 2, 1, 4), + F_VFE( 54860000, pll8, 1, 1, 7), + F_VFE( 64000000, pll8, 2, 1, 3), + F_VFE( 76800000, pll8, 1, 1, 5), + F_VFE( 96000000, pll8, 2, 1, 2), + F_VFE(109710000, pll8, 1, 2, 7), + F_VFE(128000000, pll8, 1, 1, 3), + F_VFE(153600000, pll8, 1, 2, 5), + F_VFE(200000000, pll2, 2, 1, 2), + F_VFE(228570000, pll2, 1, 2, 7), + F_VFE(266667000, pll2, 1, 1, 3), + 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), + .retain_reg = VFE_CC_REG, + .retain_mask = BIT(31), + }, + .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)), + .mnd_en_mask = BIT(5), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_vfe, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "vfe_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 110000000, NOMINAL, 228570000, + HIGH, 266667000), + CLK_INIT(vfe_clk.c), + .depends = &vfe_axi_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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_aif_osr[] = { + F_AIF_OSR( 0, gnd, 1, 0, 0), + F_AIF_OSR( 768000, pll4, 4, 1, 176), + F_AIF_OSR( 1024000, pll4, 4, 1, 132), + F_AIF_OSR( 1536000, pll4, 4, 1, 88), + F_AIF_OSR( 2048000, pll4, 4, 1, 66), + F_AIF_OSR( 3072000, pll4, 4, 1, 44), + F_AIF_OSR( 4096000, pll4, 4, 1, 33), + F_AIF_OSR( 6144000, pll4, 4, 1, 22), + F_AIF_OSR( 8192000, pll4, 2, 1, 33), + F_AIF_OSR(12288000, pll4, 4, 1, 11), + F_AIF_OSR(24576000, pll4, 2, 1, 11), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(LOW, 24576000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define CLK_AIF_BIT(i, ns, h_r) \ + struct cdiv_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(15), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ext_mask = BIT(14), \ + .div_offset = 10, \ + .max_div = 16, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_cdiv, \ + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_pcm[] = { + { .ns_val = BIT(10) /* external input */ }, + F_PCM( 512000, pll4, 4, 1, 264), + F_PCM( 768000, pll4, 4, 1, 176), + F_PCM( 1024000, pll4, 4, 1, 132), + F_PCM( 1536000, pll4, 4, 1, 88), + F_PCM( 2048000, pll4, 4, 1, 66), + F_PCM( 3072000, pll4, 4, 1, 44), + F_PCM( 4096000, pll4, 4, 1, 33), + F_PCM( 6144000, pll4, 4, 1, 22), + F_PCM( 8192000, pll4, 2, 1, 33), + F_PCM(12288000, pll4, 4, 1, 11), + F_PCM(24580000, pll4, 2, 1, 11), + 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) | BIT(10) | BM(6, 0), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pcm, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "pcm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 24580000), + CLK_INIT(pcm_clk.c), + }, +}; + +DEFINE_CLK_RPM(afab_clk, afab_a_clk, APPS_FABRIC, NULL); +DEFINE_CLK_RPM(cfpb_clk, cfpb_a_clk, CFPB, NULL); +DEFINE_CLK_RPM(dfab_clk, dfab_a_clk, DAYTONA_FABRIC, NULL); +DEFINE_CLK_RPM(ebi1_clk, ebi1_a_clk, EBI1, NULL); +DEFINE_CLK_RPM(mmfab_clk, mmfab_a_clk, MM_FABRIC, NULL); +DEFINE_CLK_RPM(mmfpb_clk, mmfpb_a_clk, MMFPB, NULL); +DEFINE_CLK_RPM(sfab_clk, sfab_a_clk, SYSTEM_FABRIC, NULL); +DEFINE_CLK_RPM(sfpb_clk, sfpb_a_clk, SFPB, NULL); +DEFINE_CLK_RPM(smi_clk, smi_a_clk, SMI, &smi_2x_axi_clk.c); + +static DEFINE_CLK_VOTER(dfab_dsps_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_usb_hs_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc1_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc2_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc3_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc4_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc5_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_scm_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_qseecom_clk, &dfab_clk.c, 0); + +static DEFINE_CLK_VOTER(ebi1_msmbus_clk, &ebi1_clk.c, LONG_MAX); +static DEFINE_CLK_VOTER(ebi1_adm0_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi1_adm1_clk, &ebi1_clk.c, 0); +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(0x1D), &ebi2_2x_clk.c }, + { TEST_PER_LS(0x1E), &ebi2_clk.c }, + { TEST_PER_LS(0x1F), &gp0_clk.c }, + { TEST_PER_LS(0x20), &gp1_clk.c }, + { TEST_PER_LS(0x21), &gp2_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(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), &smi_2x_axi_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(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 long 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 long measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops clk_ops_measure = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, +}; + +static struct measure_clk measure_clk = { + .c = { + .dbg_name = "measure_clk", + .ops = &clk_ops_measure, + CLK_INIT(measure_clk.c), + }, + .multiplier = 1, + .divider = 1, +}; + +static struct clk_lookup msm_clocks_8x60[] = { + CLK_LOOKUP("xo", cxo_clk.c, ""), + CLK_LOOKUP("xo", cxo_a_clk.c, ""), + CLK_LOOKUP("xo", pxo_a_clk.c, ""), + CLK_LOOKUP("xo", pxo_clk.c, "pil_modem"), + CLK_LOOKUP("pll4", pll4_clk.c, "pil_qdsp6v3"), + CLK_LOOKUP("measure", measure_clk.c, "debug"), + + CLK_LOOKUP("bus_clk", afab_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_a_clk", afab_a_clk.c, "msm_apps_fab"), + CLK_LOOKUP("bus_clk", sfab_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_a_clk", sfab_a_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_clk", sfpb_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_a_clk", sfpb_a_clk.c, "msm_sys_fpb"), + CLK_LOOKUP("bus_clk", mmfab_clk.c, "msm_mm_fab"), + CLK_LOOKUP("bus_a_clk", mmfab_a_clk.c, "msm_mm_fab"), + CLK_LOOKUP("bus_clk", cfpb_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("bus_a_clk", cfpb_a_clk.c, "msm_cpss_fpb"), + CLK_LOOKUP("mem_clk", ebi1_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("mem_a_clk", ebi1_a_clk.c, "msm_bus"), + CLK_LOOKUP("smi_clk", smi_clk.c, "msm_bus"), + CLK_LOOKUP("smi_a_clk", smi_a_clk.c, "msm_bus"), + + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("dfab_clk", dfab_clk.c, NULL), + CLK_LOOKUP("dfab_a_clk", dfab_a_clk.c, NULL), + CLK_LOOKUP("mmfpb_clk", mmfpb_clk.c, NULL), + CLK_LOOKUP("mmfpb_a_clk", mmfpb_a_clk.c, NULL), + + CLK_LOOKUP("core_clk", gp0_clk.c, ""), + CLK_LOOKUP("core_clk", gp1_clk.c, ""), + CLK_LOOKUP("core_clk", gp2_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi1_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi2_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_uart_clk.c, "msm_serial_hsl.2"), + CLK_LOOKUP("core_clk", gsbi4_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi5_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi6_uart_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", gsbi7_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi8_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi9_uart_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("core_clk", gsbi10_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi11_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi12_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", gsbi1_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("core_clk", gsbi2_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi3_qup_clk.c, "qup_i2c.0"), + CLK_LOOKUP("core_clk", gsbi4_qup_clk.c, "qup_i2c.1"), + CLK_LOOKUP("core_clk", gsbi5_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi6_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi7_qup_clk.c, "qup_i2c.4"), + CLK_LOOKUP("core_clk", gsbi8_qup_clk.c, "qup_i2c.3"), + CLK_LOOKUP("core_clk", gsbi9_qup_clk.c, "qup_i2c.2"), + CLK_LOOKUP("core_clk", gsbi10_qup_clk.c, "spi_qsd.1"), + CLK_LOOKUP("core_clk", gsbi11_qup_clk.c, ""), + CLK_LOOKUP("gsbi_qup_clk", gsbi12_qup_clk.c, "msm_dsps"), + CLK_LOOKUP("core_clk", gsbi12_qup_clk.c, "qup_i2c.5"), + CLK_LOOKUP("core_clk", pdm_clk.c, ""), + CLK_LOOKUP("mem_clk", pmem_clk.c, "msm_dsps"), + CLK_LOOKUP("core_clk", prng_clk.c, "msm_rng.0"), + CLK_LOOKUP("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, "msm_tsif.0"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, "msm_tsif.1"), + CLK_LOOKUP("core_clk", tssc_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_hs1_xcvr_clk.c, "msm_otg"), + CLK_LOOKUP("phy_clk", usb_phy0_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_fs1_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs1_sys_clk.c, ""), + CLK_LOOKUP("src_clk", usb_fs1_src_clk.c, ""), + CLK_LOOKUP("alt_core_clk", usb_fs2_xcvr_clk.c, ""), + CLK_LOOKUP("sys_clk", usb_fs2_sys_clk.c, ""), + CLK_LOOKUP("src_clk", usb_fs2_src_clk.c, ""), + CLK_LOOKUP("core_clk", ce2_p_clk.c, "qce.0"), + CLK_LOOKUP("core_clk", ce2_p_clk.c, "qcrypto.0"), + CLK_LOOKUP("iface_clk", gsbi1_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("iface_clk", gsbi2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi3_p_clk.c, "msm_serial_hsl.2"), + CLK_LOOKUP("iface_clk", gsbi3_p_clk.c, "qup_i2c.0"), + CLK_LOOKUP("iface_clk", gsbi4_p_clk.c, "qup_i2c.1"), + CLK_LOOKUP("iface_clk", gsbi5_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi6_p_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("iface_clk", gsbi7_p_clk.c, "qup_i2c.4"), + CLK_LOOKUP("iface_clk", gsbi8_p_clk.c, "qup_i2c.3"), + CLK_LOOKUP("iface_clk", gsbi9_p_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("iface_clk", gsbi9_p_clk.c, "qup_i2c.2"), + CLK_LOOKUP("iface_clk", gsbi10_p_clk.c, "spi_qsd.1"), + CLK_LOOKUP("iface_clk", gsbi11_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi12_p_clk.c, ""), + CLK_LOOKUP("iface_clk", gsbi12_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("iface_clk", gsbi12_p_clk.c, "qup_i2c.5"), + CLK_LOOKUP("iface_clk", ppss_p_clk.c, "msm_dsps"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, "msm_tsif.0"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, "msm_tsif.1"), + CLK_LOOKUP("iface_clk", usb_fs1_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_fs2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_hs1_p_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc5_p_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("mem_clk", ebi2_2x_clk.c, ""), + CLK_LOOKUP("mem_clk", ebi2_clk.c, "msm_ebi2"), + CLK_LOOKUP("core_clk", adm0_clk.c, "msm_dmov.0"), + CLK_LOOKUP("iface_clk", adm0_p_clk.c, "msm_dmov.0"), + CLK_LOOKUP("core_clk", adm1_clk.c, "msm_dmov.1"), + CLK_LOOKUP("iface_clk", adm1_p_clk.c, "msm_dmov.1"), + CLK_LOOKUP("iface_clk", modem_ahb1_p_clk.c, ""), + CLK_LOOKUP("iface_clk", modem_ahb2_p_clk.c, ""), + CLK_LOOKUP("iface_clk", pmic_arb0_p_clk.c, ""), + CLK_LOOKUP("iface_clk", pmic_arb1_p_clk.c, ""), + CLK_LOOKUP("core_clk", pmic_ssbi2_clk.c, ""), + CLK_LOOKUP("mem_clk", rpm_msg_ram_p_clk.c, ""), + 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_clk", csi1_clk.c, "msm_csic.1"), + CLK_LOOKUP("csi_src_clk", csi_src_clk.c, NULL), + CLK_LOOKUP("byte_clk", dsi_byte_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("esc_clk", dsi_esc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("core_clk", gfx2d0_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("core_clk", gfx2d0_clk.c, "footswitch-8x60.0"), + CLK_LOOKUP("core_clk", gfx2d1_clk.c, "kgsl-2d1.1"), + CLK_LOOKUP("core_clk", gfx2d1_clk.c, "footswitch-8x60.1"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "msm_gemini.0"), + CLK_LOOKUP("core_clk", ijpeg_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("core_clk", jpegd_clk.c, NULL), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("lcdc_clk", pixel_lcdc_clk.c, "lcdc.0"), + CLK_LOOKUP("pixel_lcdc_clk", pixel_lcdc_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("mdp_clk", pixel_mdp_clk.c, "lcdc.0"), + CLK_LOOKUP("pixel_mdp_clk", pixel_mdp_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("core_clk", rot_clk.c, "msm_rotator.0"), + CLK_LOOKUP("core_clk", rot_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("core_clk", vcodec_clk.c, "msm_vidc.0"), + CLK_LOOKUP("core_clk", vcodec_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("mdp_clk", mdp_tv_clk.c, "dtv.0"), + CLK_LOOKUP("tv_clk", mdp_tv_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("hdmi_clk", hdmi_tv_clk.c, "dtv.0"), + CLK_LOOKUP("src_clk", tv_src_clk.c, "dtv.0"), + CLK_LOOKUP("tv_src_clk", tv_src_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("core_clk", hdmi_app_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("vpe_clk", vpe_clk.c, NULL), + CLK_LOOKUP("core_clk", vpe_clk.c, "footswitch-8x60.9"), + 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("csi_vfe_clk", csi1_vfe_clk.c, "msm_csic.1"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("core_clk", vfe_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("bus_clk", vfe_axi_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("bus_clk", ijpeg_axi_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("mem_clk", imem_axi_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("bus_clk", mdp_axi_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("bus_clk", rot_axi_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("bus_clk", vcodec_axi_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("bus_clk", vpe_axi_clk.c, "footswitch-8x60.9"), + CLK_LOOKUP("arb_clk", amp_p_clk.c, "mipi_dsi.1"), + 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("csi_pclk", csi1_p_clk.c, "msm_csic.1"), + CLK_LOOKUP("master_iface_clk", dsi_m_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("slave_iface_clk", dsi_s_p_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("iface_clk", gfx2d0_p_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("iface_clk", gfx2d0_p_clk.c, "footswitch-8x60.0"), + CLK_LOOKUP("iface_clk", gfx2d1_p_clk.c, "kgsl-2d1.1"), + CLK_LOOKUP("iface_clk", gfx2d1_p_clk.c, "footswitch-8x60.1"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", gfx3d_p_clk.c, "footswitch-8x60.2"), + CLK_LOOKUP("master_iface_clk", hdmi_m_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("slave_iface_clk", hdmi_s_p_clk.c, "hdmi_msm.1"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "msm_gemini.0"), + CLK_LOOKUP("iface_clk", ijpeg_p_clk.c, "footswitch-8x60.3"), + CLK_LOOKUP("iface_clk", jpegd_p_clk.c, NULL), + CLK_LOOKUP("mem_iface_clk", imem_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "mdp.0"), + CLK_LOOKUP("iface_clk", mdp_p_clk.c, "footswitch-8x60.4"), + CLK_LOOKUP("iface_clk", smmu_p_clk.c, "msm_iommu"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "msm_rotator.0"), + CLK_LOOKUP("iface_clk", rot_p_clk.c, "footswitch-8x60.6"), + CLK_LOOKUP("tv_enc_pclk", tv_enc_p_clk.c, NULL), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "msm_vidc.0"), + CLK_LOOKUP("iface_clk", vcodec_p_clk.c, "footswitch-8x60.7"), + CLK_LOOKUP("vfe_pclk", vfe_p_clk.c, NULL), + CLK_LOOKUP("iface_clk", vfe_p_clk.c, "footswitch-8x60.8"), + CLK_LOOKUP("vpe_pclk", vpe_p_clk.c, NULL), + CLK_LOOKUP("iface_clk", vpe_p_clk.c, "footswitch-8x60.9"), + 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("core_clk", jpegd_axi_clk.c, "msm_iommu.0"), + CLK_LOOKUP("core_clk", vpe_axi_clk.c, "msm_iommu.1"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.2"), + CLK_LOOKUP("core_clk", mdp_axi_clk.c, "msm_iommu.3"), + CLK_LOOKUP("core_clk", rot_axi_clk.c, "msm_iommu.4"), + CLK_LOOKUP("core_clk", ijpeg_axi_clk.c, "msm_iommu.5"), + CLK_LOOKUP("core_clk", vfe_axi_clk.c, "msm_iommu.6"), + CLK_LOOKUP("core_clk", vcodec_axi_clk.c, "msm_iommu.7"), + CLK_LOOKUP("core_clk", vcodec_axi_clk.c, "msm_iommu.8"), + CLK_LOOKUP("core_clk", gfx3d_clk.c, "msm_iommu.9"), + CLK_LOOKUP("core_clk", gfx2d0_clk.c, "msm_iommu.10"), + CLK_LOOKUP("core_clk", gfx2d1_clk.c, "msm_iommu.11"), + + CLK_LOOKUP("mdp_iommu_clk", mdp_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("rot_iommu_clk", rot_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu0_clk", vcodec_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("vcodec_iommu1_clk", vcodec_axi_clk.c, "msm_vidc.0"), + CLK_LOOKUP("smmu_iface_clk", smmu_p_clk.c, "msm_vidc.0"), + + CLK_LOOKUP("dfab_dsps_clk", dfab_dsps_clk.c, NULL), + CLK_LOOKUP("core_clk", dfab_usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("bus_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("bus_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("bus_clk", dfab_sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("bus_clk", dfab_sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("bus_clk", dfab_sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("bus_clk", dfab_scm_clk.c, "scm"), + CLK_LOOKUP("bus_clk", dfab_qseecom_clk.c, "qseecom"), + + CLK_LOOKUP("mem_clk", ebi1_adm0_clk.c, "msm_dmov.0"), + CLK_LOOKUP("mem_clk", ebi1_adm1_clk.c, "msm_dmov.1"), + + CLK_LOOKUP("sc0_mclk", sc0_m_clk, ""), + CLK_LOOKUP("sc1_mclk", sc1_m_clk, ""), + CLK_LOOKUP("l2_mclk", l2_m_clk, ""), +}; + +/* + * 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 msm8660_clock_pre_init(void) +{ + vote_vdd_level(&vdd_dig, VDD_DIG_HIGH); + + /* 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, 0x6C000003); + writel_relaxed(0x000007F9, AHB_EN2_REG); + + /* 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(0x100207F9, MAXI_EN_REG, 0x1803FFFF); + rmwreg(0x7027FCFF, MAXI_EN2_REG, 0x7A3FFFFF); + 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. */ + rmwreg(0x00000000, CSI_CC_REG, 0x00000018); + rmwreg(0x00000400, MISC_CC_REG, 0x017C0400); + rmwreg(0x000007FD, MISC_CC2_REG, 0x70C2E7FF); + rmwreg(0x80FF0000, GFX2D0_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, GFX2D1_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, GFX3D_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, IJPEG_CC_REG, 0xE0FF0018); + rmwreg(0x80FF0000, JPEGD_CC_REG, 0xE0FF0018); + rmwreg(0x80FF0000, MDP_CC_REG, 0xE1FF0010); + rmwreg(0x80FF0000, PIXEL_CC_REG, 0xE1FF0010); + rmwreg(0x000004FF, PIXEL_CC2_REG, 0x000007FF); + rmwreg(0x80FF0000, ROT_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, TV_CC_REG, 0xE1FFC010); + rmwreg(0x000004FF, TV_CC2_REG, 0x000027FF); + rmwreg(0xC0FF0000, VCODEC_CC_REG, 0xE0FF0010); + rmwreg(0x80FF0000, VFE_CC_REG, 0xE0FFC010); + rmwreg(0x80FF0000, VPE_CC_REG, 0xE0FF0010); + + /* 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); + + /* 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); + + if ((readl_relaxed(PRNG_CLK_NS_REG) & 0x7F) == 0x2B) + prng_clk.freq_tbl = clk_tbl_prng_64; +} + +static void __init msm8660_clock_post_init(void) +{ + /* Keep PXO on whenever APPS cpu is active */ + clk_prepare_enable(&pxo_a_clk.c); + + /* Reset 3D core while clocked to ensure it resets completely. */ + clk_set_rate(&gfx3d_clk.c, 27000000); + clk_prepare_enable(&gfx3d_clk.c); + clk_reset(&gfx3d_clk.c, CLK_RESET_ASSERT); + udelay(5); + clk_reset(&gfx3d_clk.c, CLK_RESET_DEASSERT); + clk_disable_unprepare(&gfx3d_clk.c); + + /* Initialize rates for clocks that only support one. */ + clk_set_rate(&pdm_clk.c, 27000000); + clk_set_rate(&prng_clk.c, prng_clk.freq_tbl->freq_hz); + 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. */ + clk_prepare_enable(&pdm_clk.c); + clk_disable_unprepare(&pdm_clk.c); + clk_prepare_enable(&tssc_clk.c); + clk_disable_unprepare(&tssc_clk.c); +} + +static int __init msm8660_clock_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_rate(mmfpb_a_clk, 64000000); + if (WARN(rc, "mmfpb_a_clk rate was not set (%d)\n", rc)) + return rc; + rc = clk_prepare_enable(mmfpb_a_clk); + if (WARN(rc, "mmfpb_a_clk not enabled (%d)\n", rc)) + return rc; + + return unvote_vdd_level(&vdd_dig, VDD_DIG_HIGH); +} + +struct clock_init_data msm8x60_clock_init_data __initdata = { + .table = msm_clocks_8x60, + .size = ARRAY_SIZE(msm_clocks_8x60), + .pre_init = msm8660_clock_pre_init, + .post_init = msm8660_clock_post_init, + .late_init = msm8660_clock_late_init, +}; diff --git a/arch/arm/mach-msm/clock-9615.c b/arch/arm/mach-msm/clock-9615.c new file mode 100644 index 0000000000000000000000000000000000000000..66d849a4c54883ad9d8b54c0646175c1d1ad0637 --- /dev/null +++ b/arch/arm/mach-msm/clock-9615.c @@ -0,0 +1,1837 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-voter.h" +#include "clock-rpm.h" +#include "devices.h" +#include "clock-pll.h" + +#define REG(off) (MSM_CLK_CTL_BASE + (off)) +#define REG_LPA(off) (MSM_LPASS_CLK_CTL_BASE + (off)) +#define REG_GCC(off) (MSM_APCS_GCC_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_KPSS_MISC_STATE_REG REG(0x2FDC) +#define CLK_HALT_SFPB_MISC_STATE_REG REG(0x2FD8) +#define CLK_TEST_REG REG(0x2FA0) +#define GPn_MD_REG(n) REG(0x2D00+(0x20*(n))) +#define GPn_NS_REG(n) REG(0x2D24+(0x20*(n))) +#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_L_VAL_REG REG(0x30C4) +#define BB_PLL0_M_VAL_REG REG(0x30C8) +#define BB_PLL0_MODE_REG REG(0x30C0) +#define BB_PLL0_N_VAL_REG REG(0x30CC) +#define BB_PLL0_STATUS_REG REG(0x30D8) +#define BB_PLL0_CONFIG_REG REG(0x30D4) +#define BB_PLL0_TEST_CTL_REG REG(0x30D0) + +#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 BB_PLL14_L_VAL_REG REG(0x31C4) +#define BB_PLL14_M_VAL_REG REG(0x31C8) +#define BB_PLL14_MODE_REG REG(0x31C0) +#define BB_PLL14_N_VAL_REG REG(0x31CC) +#define BB_PLL14_STATUS_REG REG(0x31D8) +#define BB_PLL14_CONFIG_REG REG(0x31D4) +#define BB_PLL14_TEST_CTL_REG REG(0x31D0) + +#define SC_PLL0_L_VAL_REG REG(0x3208) +#define SC_PLL0_M_VAL_REG REG(0x320C) +#define SC_PLL0_MODE_REG REG(0x3200) +#define SC_PLL0_N_VAL_REG REG(0x3210) +#define SC_PLL0_STATUS_REG REG(0x321C) +#define SC_PLL0_CONFIG_REG REG(0x3204) +#define SC_PLL0_TEST_CTL_REG REG(0x3218) + +#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 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_HS1_SYS_CLK_MD_REG REG(0x36A0) +#define USB_HS1_SYS_CLK_NS_REG REG(0x36A4) +#define USB_HSIC_HCLK_CTL_REG REG(0x2920) +#define USB_HSIC_XCVR_FS_CLK_MD_REG REG(0x2924) +#define USB_HSIC_XCVR_FS_CLK_NS_REG REG(0x2928) +#define USB_HSIC_RESET_REG REG(0x2934) +#define USB_HSIC_HSIO_CAL_CLK_CTL_REG REG(0x2B48) +#define USB_HSIC_CLK_MD_REG REG(0x2B4C) +#define USB_HSIC_CLK_NS_REG REG(0x2B50) +#define USB_HSIC_SYSTEM_CLK_MD_REG REG(0x2B54) +#define USB_HSIC_SYSTEM_CLK_NS_REG REG(0x2B58) +#define SLIMBUS_XO_SRC_CLK_CTL_REG REG(0x2628) + +/* Low-power Audio clock registers. */ +#define LCC_CLK_HS_DEBUG_CFG_REG REG_LPA(0x00A4) +#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_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) +#define LCC_PRI_PLL_CLK_CTL_REG REG_LPA(0x00C4) + +#define GCC_APCS_CLK_DIAG REG_GCC(0x001C) + +/* MUX source input identifiers. */ +#define cxo_to_bb_mux 0 +#define pll8_to_bb_mux 3 +#define pll8_acpu_to_bb_mux 3 +#define pll14_to_bb_mux 4 +#define gnd_to_bb_mux 6 +#define cxo_to_xo_mux 0 +#define gnd_to_xo_mux 3 +#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_LPA 5 +#define TEST_TYPE_LPA_HS 6 +#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_LPA(s) TEST_VECTOR((s), TEST_TYPE_LPA) +#define TEST_LPA_HS(s) TEST_VECTOR((s), TEST_TYPE_LPA_HS) + +enum vdd_dig_levels { + VDD_DIG_NONE, + VDD_DIG_LOW, + VDD_DIG_NOMINAL, + VDD_DIG_HIGH +}; + +static int set_vdd_dig(struct clk_vdd_class *vdd_class, int level) +{ + static const int vdd_uv[] = { + [VDD_DIG_NONE] = 0, + [VDD_DIG_LOW] = 945000, + [VDD_DIG_NOMINAL] = 1050000, + [VDD_DIG_HIGH] = 1150000 + }; + + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_S1, RPM_VREG_VOTER3, + vdd_uv[level], vdd_uv[VDD_DIG_HIGH], 1); +} + +static DEFINE_VDD_CLASS(vdd_dig, set_vdd_dig); + +#define VDD_DIG_FMAX_MAP1(l1, f1) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1) +#define VDD_DIG_FMAX_MAP2(l1, f1, l2, f2) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2) + +/* + * Clock Descriptions + */ + +DEFINE_CLK_RPM_BRANCH(cxo_clk, cxo_a_clk, CXO, 19200000); + +static DEFINE_SPINLOCK(soft_vote_lock); + +static int pll_acpu_vote_clk_enable(struct clk *clk) +{ + int ret = 0; + unsigned long flags; + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + + spin_lock_irqsave(&soft_vote_lock, flags); + + if (!*pll->soft_vote) + ret = pll_vote_clk_enable(clk); + if (ret == 0) + *pll->soft_vote |= (pll->soft_vote_mask); + + spin_unlock_irqrestore(&soft_vote_lock, flags); + return ret; +} + +static void pll_acpu_vote_clk_disable(struct clk *clk) +{ + unsigned long flags; + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + + spin_lock_irqsave(&soft_vote_lock, flags); + + *pll->soft_vote &= ~(pll->soft_vote_mask); + if (!*pll->soft_vote) + pll_vote_clk_disable(clk); + + spin_unlock_irqrestore(&soft_vote_lock, flags); +} + +static struct clk_ops clk_ops_pll_acpu_vote = { + .enable = pll_acpu_vote_clk_enable, + .disable = pll_acpu_vote_clk_disable, + .auto_off = pll_acpu_vote_clk_disable, + .is_enabled = pll_vote_clk_is_enabled, + .get_parent = pll_vote_clk_get_parent, +}; + +#define PLL_SOFT_VOTE_PRIMARY BIT(0) +#define PLL_SOFT_VOTE_ACPU BIT(1) + +static unsigned int soft_vote_pll0; + +static struct pll_vote_clk pll0_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(0), + .status_reg = BB_PLL0_STATUS_REG, + .status_mask = BIT(16), + .parent = &cxo_clk.c, + .soft_vote = &soft_vote_pll0, + .soft_vote_mask = PLL_SOFT_VOTE_PRIMARY, + .c = { + .dbg_name = "pll0_clk", + .rate = 276000000, + .ops = &clk_ops_pll_acpu_vote, + CLK_INIT(pll0_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll0_acpu_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(0), + .status_reg = BB_PLL0_STATUS_REG, + .status_mask = BIT(16), + .soft_vote = &soft_vote_pll0, + .soft_vote_mask = PLL_SOFT_VOTE_ACPU, + .c = { + .dbg_name = "pll0_acpu_clk", + .rate = 276000000, + .ops = &clk_ops_pll_acpu_vote, + CLK_INIT(pll0_acpu_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll4_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(4), + .status_reg = LCC_PLL0_STATUS_REG, + .status_mask = BIT(16), + .parent = &cxo_clk.c, + .c = { + .dbg_name = "pll4_clk", + .rate = 393216000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll4_clk.c), + .warned = true, + }, +}; + +static unsigned int soft_vote_pll8; + +static struct pll_vote_clk pll8_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(8), + .status_reg = BB_PLL8_STATUS_REG, + .status_mask = BIT(16), + .parent = &cxo_clk.c, + .soft_vote = &soft_vote_pll8, + .soft_vote_mask = PLL_SOFT_VOTE_PRIMARY, + .c = { + .dbg_name = "pll8_clk", + .rate = 384000000, + .ops = &clk_ops_pll_acpu_vote, + CLK_INIT(pll8_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll8_acpu_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(8), + .status_reg = BB_PLL8_STATUS_REG, + .status_mask = BIT(16), + .soft_vote = &soft_vote_pll8, + .soft_vote_mask = PLL_SOFT_VOTE_ACPU, + .c = { + .dbg_name = "pll8_acpu_clk", + .rate = 384000000, + .ops = &clk_ops_pll_acpu_vote, + CLK_INIT(pll8_acpu_clk.c), + .warned = true, + }, +}; + +static struct pll_clk pll9_acpu_clk = { + .mode_reg = SC_PLL0_MODE_REG, + .c = { + .dbg_name = "pll9_acpu_clk", + .rate = 440000000, + .ops = &clk_ops_local_pll, + CLK_INIT(pll9_acpu_clk.c), + .warned = true, + }, +}; + +static struct pll_vote_clk pll14_clk = { + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(11), + .status_reg = BB_PLL14_STATUS_REG, + .status_mask = BIT(16), + .parent = &cxo_clk.c, + .c = { + .dbg_name = "pll14_clk", + .rate = 480000000, + .ops = &clk_ops_pll_vote, + CLK_INIT(pll14_clk.c), + .warned = true, + }, +}; + +/* + * Peripheral Clocks + */ +#define CLK_GP(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GPn_NS_REG(n), \ + .en_mask = BIT(9), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GPn_NS_REG(n), \ + .md_reg = GPn_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gp, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP1(LOW, 27000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gp[] = { + F_GP( 0, gnd, 1, 0, 0), + F_GP( 9600000, cxo, 2, 0, 0), + F_GP( 19200000, cxo, 1, 0, 0), + F_END +}; + +static CLK_GP(gp0, 0, CLK_HALT_SFPB_MISC_STATE_REG, 7); +static CLK_GP(gp1, 1, CLK_HALT_SFPB_MISC_STATE_REG, 6); +static CLK_GP(gp2, 2, CLK_HALT_SFPB_MISC_STATE_REG, 5); + +#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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_uart, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 32000000, NOMINAL, 64000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_UART(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gsbi_uart[] = { + F_GSBI_UART( 0, gnd, 1, 0, 0), + F_GSBI_UART( 3686400, pll8, 2, 12, 625), + F_GSBI_UART( 7372800, pll8, 2, 24, 625), + F_GSBI_UART(14745600, pll8, 2, 48, 625), + F_GSBI_UART(16000000, pll8, 4, 1, 6), + F_GSBI_UART(24000000, pll8, 4, 1, 4), + F_GSBI_UART(32000000, pll8, 4, 1, 3), + F_GSBI_UART(40000000, pll8, 1, 5, 48), + F_GSBI_UART(46400000, pll8, 1, 29, 240), + F_GSBI_UART(48000000, pll8, 4, 1, 2), + F_GSBI_UART(51200000, pll8, 1, 2, 15), + F_GSBI_UART(56000000, pll8, 1, 7, 48), + F_GSBI_UART(58982400, pll8, 1, 96, 625), + F_GSBI_UART(64000000, pll8, 2, 1, 3), + 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); + +#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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_qup, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 24000000, NOMINAL, 52000000), \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_QUP(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_gsbi_qup[] = { + F_GSBI_QUP( 0, gnd, 1, 0, 0), + F_GSBI_QUP( 960000, cxo, 4, 1, 5), + F_GSBI_QUP( 4800000, cxo, 4, 0, 1), + F_GSBI_QUP( 9600000, cxo, 2, 0, 1), + F_GSBI_QUP(15058800, pll8, 1, 2, 51), + F_GSBI_QUP(24000000, pll8, 4, 1, 4), + F_GSBI_QUP(25600000, pll8, 1, 1, 15), + F_GSBI_QUP(48000000, pll8, 4, 1, 2), + F_GSBI_QUP(51200000, pll8, 1, 2, 15), + 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); + +#define F_PDM(f, s, d) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + } +static struct clk_freq_tbl clk_tbl_pdm[] = { + F_PDM( 0, gnd, 1), + F_PDM(19200000, cxo, 1), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "pdm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 19200000), + CLK_INIT(pdm_clk.c), + }, +}; + +static struct branch_clk pmem_clk = { + .b = { + .ctl_reg = PMEM_ACLK_CTL_REG, + .en_mask = BIT(4), + .hwcg_reg = PMEM_ACLK_CTL_REG, + .hwcg_mask = BIT(6), + .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) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + } +static struct clk_freq_tbl clk_tbl_prng[] = { + F_PRNG(32000000, pll8), + 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 = &rcg_dummy_freq, + .c = { + .dbg_name = "prng_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 32000000, NOMINAL, 65000000), + CLK_INIT(prng_clk.c), + }, +}; + +#define CLK_SDC(name, n, h_b, f_table) \ + struct rcg_clk name = { \ + .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 = CLK_HALT_DFAB_STATE_REG, \ + .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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = f_table, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #name, \ + .ops = &clk_ops_rcg, \ + VDD_DIG_FMAX_MAP2(LOW, 26000000, NOMINAL, 52000000), \ + CLK_INIT(name.c), \ + }, \ + } +#define F_SDC(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_sdc1_2[] = { + F_SDC( 0, gnd, 1, 0, 0), + F_SDC( 144300, cxo, 1, 1, 133), + F_SDC( 400000, pll8, 4, 1, 240), + F_SDC( 16000000, pll8, 4, 1, 6), + F_SDC( 17070000, pll8, 1, 2, 45), + F_SDC( 20210000, pll8, 1, 1, 19), + F_SDC( 24000000, pll8, 4, 1, 4), + F_SDC( 38400000, pll8, 2, 1, 5), + F_SDC( 48000000, pll8, 4, 1, 2), + F_SDC( 64000000, pll8, 3, 1, 2), + F_SDC( 76800000, pll8, 1, 1, 5), + F_END +}; + +static CLK_SDC(sdc1_clk, 1, 6, clk_tbl_sdc1_2); +static CLK_SDC(sdc2_clk, 2, 5, clk_tbl_sdc1_2); + +#define F_USB(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_usb[] = { + F_USB( 0, gnd, 1, 0, 0), + F_USB(60000000, pll8, 1, 5, 32), + F_END +}; + +static struct clk_freq_tbl clk_tbl_usb_hsic_sys[] = { + F_USB( 0, gnd, 1, 0, 0), + F_USB(64000000, pll8_acpu, 1, 1, 6), + 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)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hs1_xcvr_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 60000000), + CLK_INIT(usb_hs1_xcvr_clk.c), + }, +}; + +static struct rcg_clk usb_hs1_sys_clk = { + .b = { + .ctl_reg = USB_HS1_SYS_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 = 4, + }, + .ns_reg = USB_HS1_SYS_CLK_NS_REG, + .md_reg = USB_HS1_SYS_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(23, 16) | BM(6, 0)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hs1_sys_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 60000000), + CLK_INIT(usb_hs1_sys_clk.c), + }, +}; + +static struct rcg_clk usb_hsic_xcvr_clk = { + .b = { + .ctl_reg = USB_HSIC_XCVR_FS_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = USB_HSIC_RESET_REG, + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 9, + }, + .ns_reg = USB_HSIC_XCVR_FS_CLK_NS_REG, + .md_reg = USB_HSIC_XCVR_FS_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(23, 16) | BM(6, 0)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hsic_xcvr_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 60000000), + CLK_INIT(usb_hsic_xcvr_clk.c), + }, +}; + +static struct rcg_clk usb_hsic_sys_clk = { + .b = { + .ctl_reg = USB_HSIC_SYSTEM_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = USB_HSIC_RESET_REG, + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 7, + }, + .ns_reg = USB_HSIC_SYSTEM_CLK_NS_REG, + .md_reg = USB_HSIC_SYSTEM_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(23, 16) | BM(6, 0)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb_hsic_sys, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hsic_sys_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 64000000), + CLK_INIT(usb_hsic_sys_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_usb_hsic[] = { + F_USB( 0, gnd, 1, 0, 0), + F_USB(480000000, pll14, 1, 0, 0), + F_END +}; + +static struct rcg_clk usb_hsic_clk = { + .b = { + .ctl_reg = USB_HSIC_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = USB_HSIC_RESET_REG, + .reset_mask = BIT(0), + .halt_check = DELAY, + }, + .ns_reg = USB_HSIC_CLK_NS_REG, + .md_reg = USB_HSIC_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_hsic, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "usb_hsic_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 480000000), + CLK_INIT(usb_hsic_clk.c), + }, +}; + +static struct branch_clk usb_hsic_hsio_cal_clk = { + .b = { + .ctl_reg = USB_HSIC_HSIO_CAL_CLK_CTL_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 8, + }, + .parent = &cxo_clk.c, + .c = { + .dbg_name = "usb_hsic_hsio_cal_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hsic_hsio_cal_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), + .hwcg_reg = CE1_CORE_CLK_CTL_REG, + .hwcg_mask = BIT(6), + .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 usb_hs1_p_clk = { + .b = { + .ctl_reg = USB_HS1_HCLK_CTL_REG, + .en_mask = BIT(4), + .hwcg_reg = USB_HS1_HCLK_CTL_REG, + .hwcg_mask = BIT(6), + .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 usb_hsic_p_clk = { + .b = { + .ctl_reg = USB_HSIC_HCLK_CTL_REG, + .en_mask = BIT(4), + .hwcg_reg = USB_HSIC_HCLK_CTL_REG, + .hwcg_mask = BIT(6), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "usb_hsic_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hsic_p_clk.c), + }, +}; + +static struct branch_clk sdc1_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .hwcg_reg = SDCn_HCLK_CTL_REG(1), + .hwcg_mask = BIT(6), + .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), + .hwcg_reg = SDCn_HCLK_CTL_REG(2), + .hwcg_mask = BIT(6), + .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), + }, +}; + +/* 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_KPSS_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_KPSS_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), + }, +}; + +/* + * Low Power Audio Clocks + */ +#define F_AIF_OSR(f, s, d, m, n) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_aif_osr[] = { + F_AIF_OSR( 0, gnd, 1, 0, 0), + F_AIF_OSR( 512000, pll4, 4, 1, 192), + F_AIF_OSR( 768000, pll4, 4, 1, 128), + F_AIF_OSR( 1024000, pll4, 4, 1, 96), + F_AIF_OSR( 1536000, pll4, 4, 1, 64), + F_AIF_OSR( 2048000, pll4, 4, 1, 48), + F_AIF_OSR( 3072000, pll4, 4, 1, 32), + F_AIF_OSR( 4096000, pll4, 4, 1, 24), + F_AIF_OSR( 6144000, pll4, 4, 1, 16), + F_AIF_OSR( 8192000, pll4, 4, 1, 12), + F_AIF_OSR(12288000, pll4, 4, 1, 8), + F_AIF_OSR(24576000, pll4, 4, 1, 4), + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + 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)), \ + .mnd_en_mask = BIT(8), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &rcg_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_rcg, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define CLK_AIF_BIT(i, ns, h_r) \ + struct cdiv_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(15), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ext_mask = BIT(14), \ + .div_offset = 10, \ + .max_div = 16, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_cdiv, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define CLK_AIF_BIT_DIV(i, ns, h_r) \ + struct cdiv_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(19), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ext_mask = BIT(18), \ + .div_offset = 10, \ + .max_div = 256, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &clk_ops_cdiv, \ + 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) \ + { \ + .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), \ + } +static struct clk_freq_tbl clk_tbl_pcm[] = { + { .ns_val = BIT(10) /* external input */ }, + F_PCM( 512000, pll4, 4, 1, 192), + F_PCM( 768000, pll4, 4, 1, 128), + F_PCM( 1024000, pll4, 4, 1, 96), + F_PCM( 1536000, pll4, 4, 1, 64), + F_PCM( 2048000, pll4, 4, 1, 48), + F_PCM( 3072000, pll4, 4, 1, 32), + F_PCM( 4096000, pll4, 4, 1, 24), + F_PCM( 6144000, pll4, 4, 1, 16), + F_PCM( 8192000, pll4, 4, 1, 12), + F_PCM(12288000, pll4, 4, 1, 8), + F_PCM(24576000, pll4, 4, 1, 4), + 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) | BIT(10) | BM(6, 0), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pcm, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "pcm_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 24576000), + 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)), + .mnd_en_mask = BIT(8), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_aif_osr, + .current_freq = &rcg_dummy_freq, + .c = { + .dbg_name = "audio_slimbus_clk", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 24576000), + 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_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(cfpb_clk, cfpb_a_clk, CFPB, NULL); +DEFINE_CLK_RPM(dfab_clk, dfab_a_clk, DAYTONA_FABRIC, NULL); +DEFINE_CLK_RPM(ebi1_clk, ebi1_a_clk, EBI1, NULL); +DEFINE_CLK_RPM(sfab_clk, sfab_a_clk, SYSTEM_FABRIC, NULL); +DEFINE_CLK_RPM(sfpb_clk, sfpb_a_clk, SFPB, NULL); + +static DEFINE_CLK_VOTER(dfab_usb_hs_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc1_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sdc2_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_sps_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_bam_dmux_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_msmbus_clk, &dfab_clk.c, 0); +static DEFINE_CLK_VOTER(dfab_msmbus_a_clk, &dfab_a_clk.c, 0); +static DEFINE_CLK_VOTER(ebi1_msmbus_clk, &ebi1_clk.c, LONG_MAX); +static DEFINE_CLK_VOTER(ebi1_adm_clk, &ebi1_clk.c, 0); + +#ifdef CONFIG_DEBUG_FS +struct measure_sel { + u32 test_vector; + struct clk *clk; +}; + +static DEFINE_CLK_MEASURE(q6sw_clk); +static DEFINE_CLK_MEASURE(q6fw_clk); +static DEFINE_CLK_MEASURE(q6_func_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(0x1F), &gp0_clk.c }, + { TEST_PER_LS(0x20), &gp1_clk.c }, + { TEST_PER_LS(0x21), &gp2_clk.c }, + { TEST_PER_LS(0x26), &pmem_clk.c }, + { TEST_PER_LS(0x25), &dfab_clk.c }, + { TEST_PER_LS(0x25), &dfab_a_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(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(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(0x86), &usb_hsic_sys_clk.c }, + { TEST_PER_LS(0x87), &usb_hsic_p_clk.c }, + { TEST_PER_LS(0x88), &usb_hsic_xcvr_clk.c }, + { TEST_PER_LS(0x8B), &usb_hsic_hsio_cal_clk.c }, + { TEST_PER_LS(0x8D), &usb_hs1_sys_clk.c }, + { TEST_PER_LS(0x92), &ce1_p_clk.c }, + { TEST_PER_HS(0x18), &sfab_clk.c }, + { TEST_PER_HS(0x18), &sfab_a_clk.c }, + { TEST_PER_HS(0x26), &q6sw_clk }, + { TEST_PER_HS(0x27), &q6fw_clk }, + { TEST_PER_LS(0xA4), &ce1_core_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_PER_HS(0x3E), &usb_hsic_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 }, + { TEST_LPA_HS(0x00), &q6_func_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 multiplier. + */ + clk->sample_ticks = 0x10000; + clk_sel = p->test_vector & TEST_CLK_SEL_MASK; + clk->multiplier = 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_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_LPA_HS: + writel_relaxed(0x402BC00, CLK_TEST_REG); + writel_relaxed(BVAL(2, 1, clk_sel)|BIT(0), + LCC_CLK_HS_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 unsigned long run_measurement(unsigned ticks) +{ + /* Stop counters and set the XO4 counter start value. */ + 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(28)|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 long 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)); + ret = (raw_count_full * clk->multiplier); + } + + /* Route dbg_hs_clk to PLLTEST. 300mV single-ended amplitude. */ + writel_relaxed(0x38F8, 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 long measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops clk_ops_measure = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, +}; + +static struct measure_clk measure_clk = { + .c = { + .dbg_name = "measure_clk", + .ops = &clk_ops_measure, + CLK_INIT(measure_clk.c), + }, + .multiplier = 1, +}; + +static struct clk_lookup msm_clocks_9615[] = { + CLK_LOOKUP("xo", cxo_a_clk.c, ""), + CLK_LOOKUP("xo", cxo_clk.c, "BAM_RMNT"), + CLK_LOOKUP("xo", cxo_clk.c, "msm_xo"), + CLK_LOOKUP("pll0", pll0_clk.c, NULL), + CLK_LOOKUP("pll8", pll8_clk.c, NULL), + CLK_LOOKUP("pll14", pll14_clk.c, NULL), + + CLK_LOOKUP("pll0", pll0_acpu_clk.c, "acpu"), + CLK_LOOKUP("pll8", pll8_acpu_clk.c, "acpu"), + CLK_LOOKUP("pll9", pll9_acpu_clk.c, "acpu"), + + CLK_LOOKUP("measure", measure_clk.c, "debug"), + + CLK_LOOKUP("bus_clk", sfab_clk.c, "msm_sys_fab"), + CLK_LOOKUP("bus_a_clk", sfab_a_clk.c, "msm_sys_fab"), + CLK_LOOKUP("mem_clk", ebi1_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("mem_a_clk", ebi1_a_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_clk", dfab_msmbus_clk.c, "msm_bus"), + CLK_LOOKUP("dfab_a_clk", dfab_msmbus_a_clk.c, "msm_bus"), + + CLK_LOOKUP("bus_clk", sfpb_clk.c, NULL), + CLK_LOOKUP("bus_a_clk", sfpb_a_clk.c, NULL), + CLK_LOOKUP("bus_clk", cfpb_clk.c, NULL), + CLK_LOOKUP("bus_a_clk", cfpb_a_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + + CLK_LOOKUP("core_clk", gp0_clk.c, ""), + CLK_LOOKUP("core_clk", gp1_clk.c, ""), + CLK_LOOKUP("core_clk", gp2_clk.c, ""), + + CLK_LOOKUP("core_clk", gsbi3_uart_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi4_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", gsbi5_uart_clk.c, ""), + + CLK_LOOKUP("core_clk", gsbi3_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("core_clk", gsbi4_qup_clk.c, ""), + CLK_LOOKUP("core_clk", gsbi5_qup_clk.c, "qup_i2c.0"), + + CLK_LOOKUP("core_clk", pdm_clk.c, ""), + CLK_LOOKUP("mem_clk", pmem_clk.c, "msm_sps"), + CLK_LOOKUP("core_clk", prng_clk.c, "msm_rng.0"), + CLK_LOOKUP("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", ce1_p_clk.c, ""), + CLK_LOOKUP("core_clk", ce1_core_clk.c, ""), + CLK_LOOKUP("dma_bam_pclk", dma_bam_p_clk.c, NULL), + + CLK_LOOKUP("iface_clk", gsbi3_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("iface_clk", gsbi4_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("iface_clk", gsbi5_p_clk.c, "qup_i2c.0"), + + CLK_LOOKUP("iface_clk", usb_hs1_p_clk.c, "msm_otg"), + CLK_LOOKUP("core_clk", usb_hs1_sys_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs1_xcvr_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hsic_xcvr_clk.c, "msm_hsic_host"), + CLK_LOOKUP("cal_clk", usb_hsic_hsio_cal_clk.c, "msm_hsic_host"), + CLK_LOOKUP("core_clk", usb_hsic_sys_clk.c, "msm_hsic_host"), + CLK_LOOKUP("iface_clk", usb_hsic_p_clk.c, "msm_hsic_host"), + CLK_LOOKUP("phy_clk", usb_hsic_clk.c, "msm_hsic_host"), + CLK_LOOKUP("alt_core_clk", usb_hsic_xcvr_clk.c, "msm_hsic_peripheral"), + CLK_LOOKUP("cal_clk", usb_hsic_hsio_cal_clk.c, "msm_hsic_peripheral"), + CLK_LOOKUP("core_clk", usb_hsic_sys_clk.c, "msm_hsic_peripheral"), + CLK_LOOKUP("iface_clk", usb_hsic_p_clk.c, "msm_hsic_peripheral"), + CLK_LOOKUP("phy_clk", usb_hsic_clk.c, "msm_hsic_peripheral"), + + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", adm0_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", adm0_p_clk.c, "msm_dmov"), + CLK_LOOKUP("iface_clk", pmic_arb0_p_clk.c, ""), + CLK_LOOKUP("iface_clk", pmic_arb1_p_clk.c, ""), + CLK_LOOKUP("core_clk", pmic_ssbi2_clk.c, ""), + CLK_LOOKUP("mem_clk", rpm_msg_ram_p_clk.c, ""), + CLK_LOOKUP("bit_clk", mi2s_bit_clk.c, "msm-dai-q6.6"), + CLK_LOOKUP("osr_clk", mi2s_osr_clk.c, "msm-dai-q6.6"), + + CLK_LOOKUP("bit_clk", codec_i2s_mic_bit_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("osr_clk", codec_i2s_mic_osr_clk.c, + "msm-dai-q6.1"), + CLK_LOOKUP("bit_clk", codec_i2s_spkr_bit_clk.c, + "msm-dai-q6.0"), + CLK_LOOKUP("osr_clk", codec_i2s_spkr_osr_clk.c, + "msm-dai-q6.0"), + CLK_LOOKUP("bit_clk", spare_i2s_mic_bit_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("osr_clk", spare_i2s_mic_osr_clk.c, + "msm-dai-q6.5"), + CLK_LOOKUP("bit_clk", codec_i2s_spkr_bit_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("osr_clk", codec_i2s_spkr_osr_clk.c, + "msm-dai-q6.16384"), + CLK_LOOKUP("bit_clk", spare_i2s_spkr_bit_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("osr_clk", spare_i2s_spkr_osr_clk.c, + "msm-dai-q6.4"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.2"), + CLK_LOOKUP("pcm_clk", pcm_clk.c, "msm-dai-q6.3"), + + CLK_LOOKUP("sps_slimbus_clk", sps_slimbus_clk.c, NULL), + CLK_LOOKUP("core_clk", audio_slimbus_clk.c, "msm_slim_ctrl.1"), + CLK_LOOKUP("core_clk", dfab_usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("bus_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("bus_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("dfab_clk", dfab_sps_clk.c, "msm_sps"), + CLK_LOOKUP("bus_clk", dfab_bam_dmux_clk.c, "BAM_RMNT"), + CLK_LOOKUP("mem_clk", ebi1_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("iface_clk", ce1_p_clk.c, "qce.0"), + CLK_LOOKUP("iface_clk", ce1_p_clk.c, "qcrypto.0"), + CLK_LOOKUP("core_clk", ce1_core_clk.c, "qce.0"), + CLK_LOOKUP("core_clk", ce1_core_clk.c, "qcrypto.0"), + + CLK_LOOKUP("q6sw_clk", q6sw_clk, NULL), + CLK_LOOKUP("q6fw_clk", q6fw_clk, NULL), + CLK_LOOKUP("q6_func_clk", q6_func_clk, NULL), +}; + +static struct pll_config_regs pll0_regs __initdata = { + .l_reg = BB_PLL0_L_VAL_REG, + .m_reg = BB_PLL0_M_VAL_REG, + .n_reg = BB_PLL0_N_VAL_REG, + .config_reg = BB_PLL0_CONFIG_REG, + .mode_reg = BB_PLL0_MODE_REG, +}; + +static struct pll_config pll0_config __initdata = { + .l = 0xE, + .m = 0x3, + .n = 0x8, + .vco_val = 0x0, + .vco_mask = BM(17, 16), + .pre_div_val = 0x0, + .pre_div_mask = BIT(19), + .post_div_val = 0x0, + .post_div_mask = BM(21, 20), + .mn_ena_val = BIT(22), + .mn_ena_mask = BIT(22), + .main_output_val = BIT(23), + .main_output_mask = BIT(23), +}; + +static struct pll_config_regs pll14_regs __initdata = { + .l_reg = BB_PLL14_L_VAL_REG, + .m_reg = BB_PLL14_M_VAL_REG, + .n_reg = BB_PLL14_N_VAL_REG, + .config_reg = BB_PLL14_CONFIG_REG, + .mode_reg = BB_PLL14_MODE_REG, +}; + +static struct pll_config pll14_config __initdata = { + .l = 0x19, + .m = 0x0, + .n = 0x1, + .vco_val = 0x0, + .vco_mask = BM(17, 16), + .pre_div_val = 0x0, + .pre_div_mask = BIT(19), + .post_div_val = 0x0, + .post_div_mask = BM(21, 20), + .main_output_val = BIT(23), + .main_output_mask = BIT(23), +}; + +/* + * Miscellaneous clock register initializations + */ +static void __init msm9615_clock_pre_init(void) +{ + u32 regval, is_pll_enabled, pll9_lval; + + vote_vdd_level(&vdd_dig, VDD_DIG_HIGH); + + clk_ops_local_pll.enable = sr_pll_clk_enable; + + /* Enable PDM CXO source. */ + regval = readl_relaxed(PDM_CLK_NS_REG); + writel_relaxed(BIT(13) | regval, PDM_CLK_NS_REG); + + /* Check if PLL0 is active */ + is_pll_enabled = readl_relaxed(BB_PLL0_STATUS_REG) & BIT(16); + + if (!is_pll_enabled) { + /* Enable AUX output */ + regval = readl_relaxed(BB_PLL0_TEST_CTL_REG); + regval |= BIT(12); + writel_relaxed(regval, BB_PLL0_TEST_CTL_REG); + + configure_pll(&pll0_config, &pll0_regs, 1); + } + + /* Check if PLL14 is enabled in FSM mode */ + is_pll_enabled = readl_relaxed(BB_PLL14_STATUS_REG) & BIT(16); + + if (!is_pll_enabled) + configure_pll(&pll14_config, &pll14_regs, 1); + else if (!(readl_relaxed(BB_PLL14_MODE_REG) & BIT(20))) + WARN(1, "PLL14 enabled in non-FSM mode!\n"); + + /* Detect PLL9 rate and fixup structure accordingly */ + pll9_lval = readl_relaxed(SC_PLL0_L_VAL_REG); + + if (pll9_lval == 0x1C) + pll9_acpu_clk.c.rate = 550000000; + + /* Enable PLL4 source on the LPASS Primary PLL Mux */ + regval = readl_relaxed(LCC_PRI_PLL_CLK_CTL_REG); + writel_relaxed(regval | BIT(0), LCC_PRI_PLL_CLK_CTL_REG); + + /* + * Disable hardware clock gating for pmem_clk. Leaving it enabled + * results in the clock staying on. + */ + regval = readl_relaxed(PMEM_ACLK_CTL_REG); + regval &= ~BIT(6); + writel_relaxed(regval, PMEM_ACLK_CTL_REG); + + /* + * Disable hardware clock gating for dma_bam_p_clk, which does + * not have working support for the feature. + */ + regval = readl_relaxed(DMA_BAM_HCLK_CTL); + regval &= ~BIT(6); + writel_relaxed(regval, DMA_BAM_HCLK_CTL); +} + +static void __init msm9615_clock_post_init(void) +{ + /* Keep CXO on whenever APPS cpu is active */ + clk_prepare_enable(&cxo_a_clk.c); + + /* Initialize rates for clocks that only support one. */ + clk_set_rate(&pdm_clk.c, 19200000); + clk_set_rate(&prng_clk.c, 32000000); + clk_set_rate(&usb_hs1_xcvr_clk.c, 60000000); + clk_set_rate(&usb_hs1_sys_clk.c, 60000000); + clk_set_rate(&usb_hsic_xcvr_clk.c, 60000000); + clk_set_rate(&usb_hsic_sys_clk.c, 64000000); + clk_set_rate(&usb_hsic_clk.c, 480000000); + + /* + * The halt status bits for PDM may be incorrect at boot. + * Toggle these clocks on and off to refresh them. + */ + clk_prepare_enable(&pdm_clk.c); + clk_disable_unprepare(&pdm_clk.c); +} + +static int __init msm9615_clock_late_init(void) +{ + return unvote_vdd_level(&vdd_dig, VDD_DIG_HIGH); +} + +struct clock_init_data msm9615_clock_init_data __initdata = { + .table = msm_clocks_9615, + .size = ARRAY_SIZE(msm_clocks_9615), + .pre_init = msm9615_clock_pre_init, + .post_init = msm9615_clock_post_init, + .late_init = msm9615_clock_late_init, +}; diff --git a/arch/arm/mach-msm/clock-copper.c b/arch/arm/mach-msm/clock-copper.c new file mode 100644 index 0000000000000000000000000000000000000000..4d5f77382e9a45a2487d7b32cb609493cdb3adf6 --- /dev/null +++ b/arch/arm/mach-msm/clock-copper.c @@ -0,0 +1,5202 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "clock-local2.h" +#include "clock-pll.h" + +enum { + GCC_BASE, + MMSS_BASE, + LPASS_BASE, + MSS_BASE, + N_BASES, +}; + +static void __iomem *virt_bases[N_BASES]; + +#define GCC_REG_BASE(x) (void __iomem *)(virt_bases[GCC_BASE] + (x)) +#define MMSS_REG_BASE(x) (void __iomem *)(virt_bases[MMSS_BASE] + (x)) +#define LPASS_REG_BASE(x) (void __iomem *)(virt_bases[LPASS_BASE] + (x)) +#define MSS_REG_BASE(x) (void __iomem *)(virt_bases[MSS_BASE] + (x)) + +#define GPLL0_MODE_REG 0x0000 +#define GPLL0_L_REG 0x0004 +#define GPLL0_M_REG 0x0008 +#define GPLL0_N_REG 0x000C +#define GPLL0_USER_CTL_REG 0x0010 +#define GPLL0_CONFIG_CTL_REG 0x0014 +#define GPLL0_TEST_CTL_REG 0x0018 +#define GPLL0_STATUS_REG 0x001C + +#define GPLL1_MODE_REG 0x0040 +#define GPLL1_L_REG 0x0044 +#define GPLL1_M_REG 0x0048 +#define GPLL1_N_REG 0x004C +#define GPLL1_USER_CTL_REG 0x0050 +#define GPLL1_CONFIG_CTL_REG 0x0054 +#define GPLL1_TEST_CTL_REG 0x0058 +#define GPLL1_STATUS_REG 0x005C + +#define MMPLL0_MODE_REG 0x0000 +#define MMPLL0_L_REG 0x0004 +#define MMPLL0_M_REG 0x0008 +#define MMPLL0_N_REG 0x000C +#define MMPLL0_USER_CTL_REG 0x0010 +#define MMPLL0_CONFIG_CTL_REG 0x0014 +#define MMPLL0_TEST_CTL_REG 0x0018 +#define MMPLL0_STATUS_REG 0x001C + +#define MMPLL1_MODE_REG 0x0040 +#define MMPLL1_L_REG 0x0044 +#define MMPLL1_M_REG 0x0048 +#define MMPLL1_N_REG 0x004C +#define MMPLL1_USER_CTL_REG 0x0050 +#define MMPLL1_CONFIG_CTL_REG 0x0054 +#define MMPLL1_TEST_CTL_REG 0x0058 +#define MMPLL1_STATUS_REG 0x005C + +#define MMPLL3_MODE_REG 0x0080 +#define MMPLL3_L_REG 0x0084 +#define MMPLL3_M_REG 0x0088 +#define MMPLL3_N_REG 0x008C +#define MMPLL3_USER_CTL_REG 0x0090 +#define MMPLL3_CONFIG_CTL_REG 0x0094 +#define MMPLL3_TEST_CTL_REG 0x0098 +#define MMPLL3_STATUS_REG 0x009C + +#define LPAPLL_MODE_REG 0x0000 +#define LPAPLL_L_REG 0x0004 +#define LPAPLL_M_REG 0x0008 +#define LPAPLL_N_REG 0x000C +#define LPAPLL_USER_CTL_REG 0x0010 +#define LPAPLL_CONFIG_CTL_REG 0x0014 +#define LPAPLL_TEST_CTL_REG 0x0018 +#define LPAPLL_STATUS_REG 0x001C + +#define GCC_DEBUG_CLK_CTL_REG 0x1880 +#define CLOCK_FRQ_MEASURE_CTL_REG 0x1884 +#define CLOCK_FRQ_MEASURE_STATUS_REG 0x1888 +#define GCC_XO_DIV4_CBCR_REG 0x10C8 +#define APCS_GPLL_ENA_VOTE_REG 0x1480 +#define MMSS_PLL_VOTE_APCS_REG 0x0100 +#define MMSS_DEBUG_CLK_CTL_REG 0x0900 +#define LPASS_DEBUG_CLK_CTL_REG 0x29000 +#define LPASS_LPA_PLL_VOTE_APPS_REG 0x2000 +#define MSS_DEBUG_CLK_CTL_REG 0x0078 + +#define USB30_MASTER_CMD_RCGR 0x03D4 +#define USB30_MOCK_UTMI_CMD_RCGR 0x03E8 +#define USB_HSIC_SYSTEM_CMD_RCGR 0x041C +#define USB_HSIC_CMD_RCGR 0x0440 +#define USB_HSIC_IO_CAL_CMD_RCGR 0x0458 +#define USB_HS_SYSTEM_CMD_RCGR 0x0490 +#define SDCC1_APPS_CMD_RCGR 0x04D0 +#define SDCC2_APPS_CMD_RCGR 0x0510 +#define SDCC3_APPS_CMD_RCGR 0x0550 +#define SDCC4_APPS_CMD_RCGR 0x0590 +#define BLSP1_QUP1_SPI_APPS_CMD_RCGR 0x064C +#define BLSP1_UART1_APPS_CMD_RCGR 0x068C +#define BLSP1_QUP2_SPI_APPS_CMD_RCGR 0x06CC +#define BLSP1_UART2_APPS_CMD_RCGR 0x070C +#define BLSP1_QUP3_SPI_APPS_CMD_RCGR 0x074C +#define BLSP1_UART3_APPS_CMD_RCGR 0x078C +#define BLSP1_QUP4_SPI_APPS_CMD_RCGR 0x07CC +#define BLSP1_UART4_APPS_CMD_RCGR 0x080C +#define BLSP1_QUP5_SPI_APPS_CMD_RCGR 0x084C +#define BLSP1_UART5_APPS_CMD_RCGR 0x088C +#define BLSP1_QUP6_SPI_APPS_CMD_RCGR 0x08CC +#define BLSP1_UART6_APPS_CMD_RCGR 0x090C +#define BLSP2_QUP1_SPI_APPS_CMD_RCGR 0x098C +#define BLSP2_UART1_APPS_CMD_RCGR 0x09CC +#define BLSP2_QUP2_SPI_APPS_CMD_RCGR 0x0A0C +#define BLSP2_UART2_APPS_CMD_RCGR 0x0A4C +#define BLSP2_QUP3_SPI_APPS_CMD_RCGR 0x0A8C +#define BLSP2_UART3_APPS_CMD_RCGR 0x0ACC +#define BLSP2_QUP4_SPI_APPS_CMD_RCGR 0x0B0C +#define BLSP2_UART4_APPS_CMD_RCGR 0x0B4C +#define BLSP2_QUP5_SPI_APPS_CMD_RCGR 0x0B8C +#define BLSP2_UART5_APPS_CMD_RCGR 0x0BCC +#define BLSP2_QUP6_SPI_APPS_CMD_RCGR 0x0C0C +#define BLSP2_UART6_APPS_CMD_RCGR 0x0C4C +#define PDM2_CMD_RCGR 0x0CD0 +#define TSIF_REF_CMD_RCGR 0x0D90 +#define CE1_CMD_RCGR 0x1050 +#define CE2_CMD_RCGR 0x1090 +#define GP1_CMD_RCGR 0x1904 +#define GP2_CMD_RCGR 0x1944 +#define GP3_CMD_RCGR 0x1984 +#define LPAIF_SPKR_CMD_RCGR 0xA000 +#define LPAIF_PRI_CMD_RCGR 0xB000 +#define LPAIF_SEC_CMD_RCGR 0xC000 +#define LPAIF_TER_CMD_RCGR 0xD000 +#define LPAIF_QUAD_CMD_RCGR 0xE000 +#define LPAIF_PCM0_CMD_RCGR 0xF000 +#define LPAIF_PCM1_CMD_RCGR 0x10000 +#define RESAMPLER_CMD_RCGR 0x11000 +#define SLIMBUS_CMD_RCGR 0x12000 +#define LPAIF_PCMOE_CMD_RCGR 0x13000 +#define AHBFABRIC_CMD_RCGR 0x18000 +#define VCODEC0_CMD_RCGR 0x1000 +#define PCLK0_CMD_RCGR 0x2000 +#define PCLK1_CMD_RCGR 0x2020 +#define MDP_CMD_RCGR 0x2040 +#define EXTPCLK_CMD_RCGR 0x2060 +#define VSYNC_CMD_RCGR 0x2080 +#define EDPPIXEL_CMD_RCGR 0x20A0 +#define EDPLINK_CMD_RCGR 0x20C0 +#define EDPAUX_CMD_RCGR 0x20E0 +#define HDMI_CMD_RCGR 0x2100 +#define BYTE0_CMD_RCGR 0x2120 +#define BYTE1_CMD_RCGR 0x2140 +#define ESC0_CMD_RCGR 0x2160 +#define ESC1_CMD_RCGR 0x2180 +#define CSI0PHYTIMER_CMD_RCGR 0x3000 +#define CSI1PHYTIMER_CMD_RCGR 0x3030 +#define CSI2PHYTIMER_CMD_RCGR 0x3060 +#define CSI0_CMD_RCGR 0x3090 +#define CSI1_CMD_RCGR 0x3100 +#define CSI2_CMD_RCGR 0x3160 +#define CSI3_CMD_RCGR 0x31C0 +#define CCI_CMD_RCGR 0x3300 +#define MCLK0_CMD_RCGR 0x3360 +#define MCLK1_CMD_RCGR 0x3390 +#define MCLK2_CMD_RCGR 0x33C0 +#define MCLK3_CMD_RCGR 0x33F0 +#define MMSS_GP0_CMD_RCGR 0x3420 +#define MMSS_GP1_CMD_RCGR 0x3450 +#define JPEG0_CMD_RCGR 0x3500 +#define JPEG1_CMD_RCGR 0x3520 +#define JPEG2_CMD_RCGR 0x3540 +#define VFE0_CMD_RCGR 0x3600 +#define VFE1_CMD_RCGR 0x3620 +#define CPP_CMD_RCGR 0x3640 +#define GFX3D_CMD_RCGR 0x4000 +#define RBCPR_CMD_RCGR 0x4060 +#define AHB_CMD_RCGR 0x5000 +#define AXI_CMD_RCGR 0x5040 +#define OCMEMNOC_CMD_RCGR 0x5090 + +#define MMSS_BCR 0x0240 +#define USB_30_BCR 0x03C0 +#define USB3_PHY_BCR 0x03FC +#define USB_HS_HSIC_BCR 0x0400 +#define USB_HS_BCR 0x0480 +#define SDCC1_BCR 0x04C0 +#define SDCC2_BCR 0x0500 +#define SDCC3_BCR 0x0540 +#define SDCC4_BCR 0x0580 +#define BLSP1_BCR 0x05C0 +#define BLSP1_QUP1_BCR 0x0640 +#define BLSP1_UART1_BCR 0x0680 +#define BLSP1_QUP2_BCR 0x06C0 +#define BLSP1_UART2_BCR 0x0700 +#define BLSP1_QUP3_BCR 0x0740 +#define BLSP1_UART3_BCR 0x0780 +#define BLSP1_QUP4_BCR 0x07C0 +#define BLSP1_UART4_BCR 0x0800 +#define BLSP1_QUP5_BCR 0x0840 +#define BLSP1_UART5_BCR 0x0880 +#define BLSP1_QUP6_BCR 0x08C0 +#define BLSP1_UART6_BCR 0x0900 +#define BLSP2_BCR 0x0940 +#define BLSP2_QUP1_BCR 0x0980 +#define BLSP2_UART1_BCR 0x09C0 +#define BLSP2_QUP2_BCR 0x0A00 +#define BLSP2_UART2_BCR 0x0A40 +#define BLSP2_QUP3_BCR 0x0A80 +#define BLSP2_UART3_BCR 0x0AC0 +#define BLSP2_QUP4_BCR 0x0B00 +#define BLSP2_UART4_BCR 0x0B40 +#define BLSP2_QUP5_BCR 0x0B80 +#define BLSP2_UART5_BCR 0x0BC0 +#define BLSP2_QUP6_BCR 0x0C00 +#define BLSP2_UART6_BCR 0x0C40 +#define BOOT_ROM_BCR 0x0E00 +#define PDM_BCR 0x0CC0 +#define PRNG_BCR 0x0D00 +#define BAM_DMA_BCR 0x0D40 +#define TSIF_BCR 0x0D80 +#define CE1_BCR 0x1040 +#define CE2_BCR 0x1080 +#define AUDIO_CORE_BCR 0x4000 +#define VENUS0_BCR 0x1020 +#define MDSS_BCR 0x2300 +#define CAMSS_PHY0_BCR 0x3020 +#define CAMSS_PHY1_BCR 0x3050 +#define CAMSS_PHY2_BCR 0x3080 +#define CAMSS_CSI0_BCR 0x30B0 +#define CAMSS_CSI0PHY_BCR 0x30C0 +#define CAMSS_CSI0RDI_BCR 0x30D0 +#define CAMSS_CSI0PIX_BCR 0x30E0 +#define CAMSS_CSI1_BCR 0x3120 +#define CAMSS_CSI1PHY_BCR 0x3130 +#define CAMSS_CSI1RDI_BCR 0x3140 +#define CAMSS_CSI1PIX_BCR 0x3150 +#define CAMSS_CSI2_BCR 0x3180 +#define CAMSS_CSI2PHY_BCR 0x3190 +#define CAMSS_CSI2RDI_BCR 0x31A0 +#define CAMSS_CSI2PIX_BCR 0x31B0 +#define CAMSS_CSI3_BCR 0x31E0 +#define CAMSS_CSI3PHY_BCR 0x31F0 +#define CAMSS_CSI3RDI_BCR 0x3200 +#define CAMSS_CSI3PIX_BCR 0x3210 +#define CAMSS_ISPIF_BCR 0x3220 +#define CAMSS_CCI_BCR 0x3340 +#define CAMSS_MCLK0_BCR 0x3380 +#define CAMSS_MCLK1_BCR 0x33B0 +#define CAMSS_MCLK2_BCR 0x33E0 +#define CAMSS_MCLK3_BCR 0x3410 +#define CAMSS_GP0_BCR 0x3440 +#define CAMSS_GP1_BCR 0x3470 +#define CAMSS_TOP_BCR 0x3480 +#define CAMSS_MICRO_BCR 0x3490 +#define CAMSS_JPEG_BCR 0x35A0 +#define CAMSS_VFE_BCR 0x36A0 +#define CAMSS_CSI_VFE0_BCR 0x3700 +#define CAMSS_CSI_VFE1_BCR 0x3710 +#define OCMEMNOC_BCR 0x50B0 +#define MMSSNOCAHB_BCR 0x5020 +#define MMSSNOCAXI_BCR 0x5060 +#define OXILI_GFX3D_CBCR 0x4028 +#define OXILICX_AHB_CBCR 0x403C +#define OXILICX_AXI_CBCR 0x4038 +#define OXILI_BCR 0x4020 +#define OXILICX_BCR 0x4030 +#define LPASS_Q6SS_BCR 0x6000 +#define MSS_Q6SS_BCR 0x1068 + +#define OCMEM_SYS_NOC_AXI_CBCR 0x0244 +#define OCMEM_NOC_CFG_AHB_CBCR 0x0248 +#define MMSS_NOC_CFG_AHB_CBCR 0x024C + +#define USB30_MASTER_CBCR 0x03C8 +#define USB30_MOCK_UTMI_CBCR 0x03D0 +#define USB_HSIC_AHB_CBCR 0x0408 +#define USB_HSIC_SYSTEM_CBCR 0x040C +#define USB_HSIC_CBCR 0x0410 +#define USB_HSIC_IO_CAL_CBCR 0x0414 +#define USB_HS_SYSTEM_CBCR 0x0484 +#define USB_HS_AHB_CBCR 0x0488 +#define SDCC1_APPS_CBCR 0x04C4 +#define SDCC1_AHB_CBCR 0x04C8 +#define SDCC2_APPS_CBCR 0x0504 +#define SDCC2_AHB_CBCR 0x0508 +#define SDCC3_APPS_CBCR 0x0544 +#define SDCC3_AHB_CBCR 0x0548 +#define SDCC4_APPS_CBCR 0x0584 +#define SDCC4_AHB_CBCR 0x0588 +#define BLSP1_AHB_CBCR 0x05C4 +#define BLSP1_QUP1_SPI_APPS_CBCR 0x0644 +#define BLSP1_QUP1_I2C_APPS_CBCR 0x0648 +#define BLSP1_UART1_APPS_CBCR 0x0684 +#define BLSP1_UART1_SIM_CBCR 0x0688 +#define BLSP1_QUP2_SPI_APPS_CBCR 0x06C4 +#define BLSP1_QUP2_I2C_APPS_CBCR 0x06C8 +#define BLSP1_UART2_APPS_CBCR 0x0704 +#define BLSP1_UART2_SIM_CBCR 0x0708 +#define BLSP1_QUP3_SPI_APPS_CBCR 0x0744 +#define BLSP1_QUP3_I2C_APPS_CBCR 0x0748 +#define BLSP1_UART3_APPS_CBCR 0x0784 +#define BLSP1_UART3_SIM_CBCR 0x0788 +#define BLSP1_QUP4_SPI_APPS_CBCR 0x07C4 +#define BLSP1_QUP4_I2C_APPS_CBCR 0x07C8 +#define BLSP1_UART4_APPS_CBCR 0x0804 +#define BLSP1_UART4_SIM_CBCR 0x0808 +#define BLSP1_QUP5_SPI_APPS_CBCR 0x0844 +#define BLSP1_QUP5_I2C_APPS_CBCR 0x0848 +#define BLSP1_UART5_APPS_CBCR 0x0884 +#define BLSP1_UART5_SIM_CBCR 0x0888 +#define BLSP1_QUP6_SPI_APPS_CBCR 0x08C4 +#define BLSP1_QUP6_I2C_APPS_CBCR 0x08C8 +#define BLSP1_UART6_APPS_CBCR 0x0904 +#define BLSP1_UART6_SIM_CBCR 0x0908 +#define BLSP2_AHB_CBCR 0x0944 +#define BOOT_ROM_AHB_CBCR 0x0E04 +#define BLSP2_QUP1_SPI_APPS_CBCR 0x0984 +#define BLSP2_QUP1_I2C_APPS_CBCR 0x0988 +#define BLSP2_UART1_APPS_CBCR 0x09C4 +#define BLSP2_UART1_SIM_CBCR 0x09C8 +#define BLSP2_QUP2_SPI_APPS_CBCR 0x0A04 +#define BLSP2_QUP2_I2C_APPS_CBCR 0x0A08 +#define BLSP2_UART2_APPS_CBCR 0x0A44 +#define BLSP2_UART2_SIM_CBCR 0x0A48 +#define BLSP2_QUP3_SPI_APPS_CBCR 0x0A84 +#define BLSP2_QUP3_I2C_APPS_CBCR 0x0A88 +#define BLSP2_UART3_APPS_CBCR 0x0AC4 +#define BLSP2_UART3_SIM_CBCR 0x0AC8 +#define BLSP2_QUP4_SPI_APPS_CBCR 0x0B04 +#define BLSP2_QUP4_I2C_APPS_CBCR 0x0B08 +#define BLSP2_UART4_APPS_CBCR 0x0B44 +#define BLSP2_UART4_SIM_CBCR 0x0B48 +#define BLSP2_QUP5_SPI_APPS_CBCR 0x0B84 +#define BLSP2_QUP5_I2C_APPS_CBCR 0x0B88 +#define BLSP2_UART5_APPS_CBCR 0x0BC4 +#define BLSP2_UART5_SIM_CBCR 0x0BC8 +#define BLSP2_QUP6_SPI_APPS_CBCR 0x0C04 +#define BLSP2_QUP6_I2C_APPS_CBCR 0x0C08 +#define BLSP2_UART6_APPS_CBCR 0x0C44 +#define BLSP2_UART6_SIM_CBCR 0x0C48 +#define PDM_AHB_CBCR 0x0CC4 +#define PDM_XO4_CBCR 0x0CC8 +#define PDM2_CBCR 0x0CCC +#define PRNG_AHB_CBCR 0x0D04 +#define BAM_DMA_AHB_CBCR 0x0D44 +#define TSIF_AHB_CBCR 0x0D84 +#define TSIF_REF_CBCR 0x0D88 +#define MSG_RAM_AHB_CBCR 0x0E44 +#define CE1_CBCR 0x1044 +#define CE1_AXI_CBCR 0x1048 +#define CE1_AHB_CBCR 0x104C +#define CE2_CBCR 0x1084 +#define CE2_AXI_CBCR 0x1088 +#define CE2_AHB_CBCR 0x108C +#define GCC_AHB_CBCR 0x10C0 +#define GP1_CBCR 0x1900 +#define GP2_CBCR 0x1940 +#define GP3_CBCR 0x1980 +#define AUDIO_CORE_LPAIF_CODEC_SPKR_OSR_CBCR 0xA014 +#define AUDIO_CORE_LPAIF_CODEC_SPKR_IBIT_CBCR 0xA018 +#define AUDIO_CORE_LPAIF_CODEC_SPKR_EBIT_CBCR 0xA01C +#define AUDIO_CORE_LPAIF_PRI_OSR_CBCR 0xB014 +#define AUDIO_CORE_LPAIF_PRI_IBIT_CBCR 0xB018 +#define AUDIO_CORE_LPAIF_PRI_EBIT_CBCR 0xB01C +#define AUDIO_CORE_LPAIF_SEC_OSR_CBCR 0xC014 +#define AUDIO_CORE_LPAIF_SEC_IBIT_CBCR 0xC018 +#define AUDIO_CORE_LPAIF_SEC_EBIT_CBCR 0xC01C +#define AUDIO_CORE_LPAIF_TER_OSR_CBCR 0xD014 +#define AUDIO_CORE_LPAIF_TER_IBIT_CBCR 0xD018 +#define AUDIO_CORE_LPAIF_TER_EBIT_CBCR 0xD01C +#define AUDIO_CORE_LPAIF_QUAD_OSR_CBCR 0xE014 +#define AUDIO_CORE_LPAIF_QUAD_IBIT_CBCR 0xE018 +#define AUDIO_CORE_LPAIF_QUAD_EBIT_CBCR 0xE01C +#define AUDIO_CORE_LPAIF_PCM0_IBIT_CBCR 0xF014 +#define AUDIO_CORE_LPAIF_PCM0_EBIT_CBCR 0xF018 +#define AUDIO_CORE_LPAIF_PCM1_IBIT_CBCR 0x10014 +#define AUDIO_CORE_LPAIF_PCM1_EBIT_CBCR 0x10018 +#define AUDIO_CORE_RESAMPLER_CORE_CBCR 0x11014 +#define AUDIO_CORE_RESAMPLER_LFABIF_CBCR 0x11018 +#define AUDIO_CORE_SLIMBUS_CORE_CBCR 0x12014 +#define AUDIO_CORE_SLIMBUS_LFABIF_CBCR 0x12018 +#define AUDIO_CORE_LPAIF_PCM_DATA_OE_CBCR 0x13014 +#define VENUS0_VCODEC0_CBCR 0x1028 +#define VENUS0_AHB_CBCR 0x1030 +#define VENUS0_AXI_CBCR 0x1034 +#define VENUS0_OCMEMNOC_CBCR 0x1038 +#define MDSS_AHB_CBCR 0x2308 +#define MDSS_HDMI_AHB_CBCR 0x230C +#define MDSS_AXI_CBCR 0x2310 +#define MDSS_PCLK0_CBCR 0x2314 +#define MDSS_PCLK1_CBCR 0x2318 +#define MDSS_MDP_CBCR 0x231C +#define MDSS_MDP_LUT_CBCR 0x2320 +#define MDSS_EXTPCLK_CBCR 0x2324 +#define MDSS_VSYNC_CBCR 0x2328 +#define MDSS_EDPPIXEL_CBCR 0x232C +#define MDSS_EDPLINK_CBCR 0x2330 +#define MDSS_EDPAUX_CBCR 0x2334 +#define MDSS_HDMI_CBCR 0x2338 +#define MDSS_BYTE0_CBCR 0x233C +#define MDSS_BYTE1_CBCR 0x2340 +#define MDSS_ESC0_CBCR 0x2344 +#define MDSS_ESC1_CBCR 0x2348 +#define CAMSS_PHY0_CSI0PHYTIMER_CBCR 0x3024 +#define CAMSS_PHY1_CSI1PHYTIMER_CBCR 0x3054 +#define CAMSS_PHY2_CSI2PHYTIMER_CBCR 0x3084 +#define CAMSS_CSI0_CBCR 0x30B4 +#define CAMSS_CSI0_AHB_CBCR 0x30BC +#define CAMSS_CSI0PHY_CBCR 0x30C4 +#define CAMSS_CSI0RDI_CBCR 0x30D4 +#define CAMSS_CSI0PIX_CBCR 0x30E4 +#define CAMSS_CSI1_CBCR 0x3124 +#define CAMSS_CSI1_AHB_CBCR 0x3128 +#define CAMSS_CSI1PHY_CBCR 0x3134 +#define CAMSS_CSI1RDI_CBCR 0x3144 +#define CAMSS_CSI1PIX_CBCR 0x3154 +#define CAMSS_CSI2_CBCR 0x3184 +#define CAMSS_CSI2_AHB_CBCR 0x3188 +#define CAMSS_CSI2PHY_CBCR 0x3194 +#define CAMSS_CSI2RDI_CBCR 0x31A4 +#define CAMSS_CSI2PIX_CBCR 0x31B4 +#define CAMSS_CSI3_CBCR 0x31E4 +#define CAMSS_CSI3_AHB_CBCR 0x31E8 +#define CAMSS_CSI3PHY_CBCR 0x31F4 +#define CAMSS_CSI3RDI_CBCR 0x3204 +#define CAMSS_CSI3PIX_CBCR 0x3214 +#define CAMSS_ISPIF_AHB_CBCR 0x3224 +#define CAMSS_CCI_CCI_CBCR 0x3344 +#define CAMSS_CCI_CCI_AHB_CBCR 0x3348 +#define CAMSS_MCLK0_CBCR 0x3384 +#define CAMSS_MCLK1_CBCR 0x33B4 +#define CAMSS_MCLK2_CBCR 0x33E4 +#define CAMSS_MCLK3_CBCR 0x3414 +#define CAMSS_GP0_CBCR 0x3444 +#define CAMSS_GP1_CBCR 0x3474 +#define CAMSS_TOP_AHB_CBCR 0x3484 +#define CAMSS_MICRO_AHB_CBCR 0x3494 +#define CAMSS_JPEG_JPEG0_CBCR 0x35A8 +#define CAMSS_JPEG_JPEG1_CBCR 0x35AC +#define CAMSS_JPEG_JPEG2_CBCR 0x35B0 +#define CAMSS_JPEG_JPEG_AHB_CBCR 0x35B4 +#define CAMSS_JPEG_JPEG_AXI_CBCR 0x35B8 +#define CAMSS_JPEG_JPEG_OCMEMNOC_CBCR 0x35BC +#define CAMSS_VFE_VFE0_CBCR 0x36A8 +#define CAMSS_VFE_VFE1_CBCR 0x36AC +#define CAMSS_VFE_CPP_CBCR 0x36B0 +#define CAMSS_VFE_CPP_AHB_CBCR 0x36B4 +#define CAMSS_VFE_VFE_AHB_CBCR 0x36B8 +#define CAMSS_VFE_VFE_AXI_CBCR 0x36BC +#define CAMSS_VFE_VFE_OCMEMNOC_CBCR 0x36C0 +#define CAMSS_CSI_VFE0_CBCR 0x3704 +#define CAMSS_CSI_VFE1_CBCR 0x3714 +#define MMSS_MMSSNOC_AXI_CBCR 0x506C +#define MMSS_MMSSNOC_AHB_CBCR 0x5024 +#define MMSS_MMSSNOC_BTO_AHB_CBCR 0x5028 +#define MMSS_MISC_AHB_CBCR 0x502C +#define MMSS_S0_AXI_CBCR 0x5064 +#define OCMEMNOC_CBCR 0x50B4 +#define LPASS_Q6SS_AHB_LFABIF_CBCR 0x22000 +#define LPASS_Q6SS_XO_CBCR 0x26000 +#define MSS_XO_Q6_CBCR 0x108C +#define MSS_BUS_Q6_CBCR 0x10A4 +#define MSS_CFG_AHB_CBCR 0x0280 + +#define APCS_CLOCK_BRANCH_ENA_VOTE 0x1484 +#define APCS_CLOCK_SLEEP_ENA_VOTE 0x1488 + +/* Mux source select values */ +#define cxo_source_val 0 +#define gpll0_source_val 1 +#define gpll1_source_val 2 +#define gnd_source_val 5 +#define mmpll0_mm_source_val 1 +#define mmpll1_mm_source_val 2 +#define mmpll3_mm_source_val 3 +#define gpll0_mm_source_val 5 +#define cxo_mm_source_val 0 +#define mm_gnd_source_val 6 +#define gpll1_hsic_source_val 4 +#define cxo_lpass_source_val 0 +#define lpapll0_lpass_source_val 1 +#define gpll0_lpass_source_val 5 +#define edppll_270_mm_source_val 4 +#define edppll_350_mm_source_val 4 +#define dsipll_750_mm_source_val 1 +#define dsipll_250_mm_source_val 2 +#define hdmipll_297_mm_source_val 3 + +#define F(f, s, div, m, n) \ + { \ + .freq_hz = (f), \ + .src_clk = &s##_clk_src.c, \ + .m_val = (m), \ + .n_val = ~((n)-(m)), \ + .d_val = ~(n),\ + .div_src_val = BVAL(4, 0, (int)(2*(div) - 1)) \ + | BVAL(10, 8, s##_source_val), \ + } + +#define F_MM(f, s, div, m, n) \ + { \ + .freq_hz = (f), \ + .src_clk = &s##_clk_src.c, \ + .m_val = (m), \ + .n_val = ~((n)-(m)), \ + .d_val = ~(n),\ + .div_src_val = BVAL(4, 0, (int)(2*(div) - 1)) \ + | BVAL(10, 8, s##_mm_source_val), \ + } + +#define F_MDSS(f, s, div, m, n) \ + { \ + .freq_hz = (f), \ + .m_val = (m), \ + .n_val = ~((n)-(m)), \ + .d_val = ~(n),\ + .div_src_val = BVAL(4, 0, (int)(2*(div) - 1)) \ + | BVAL(10, 8, s##_mm_source_val), \ + } + +#define F_HSIC(f, s, div, m, n) \ + { \ + .freq_hz = (f), \ + .src_clk = &s##_clk_src.c, \ + .m_val = (m), \ + .n_val = ~((n)-(m)), \ + .d_val = ~(n),\ + .div_src_val = BVAL(4, 0, (int)(2*(div) - 1)) \ + | BVAL(10, 8, s##_hsic_source_val), \ + } + +#define F_LPASS(f, s, div, m, n) \ + { \ + .freq_hz = (f), \ + .src_clk = &s##_clk_src.c, \ + .m_val = (m), \ + .n_val = ~((n)-(m)), \ + .d_val = ~(n),\ + .div_src_val = BVAL(4, 0, (int)(2*(div) - 1)) \ + | BVAL(10, 8, s##_lpass_source_val), \ + } + +#define VDD_DIG_FMAX_MAP1(l1, f1) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1) +#define VDD_DIG_FMAX_MAP2(l1, f1, l2, f2) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2) +#define VDD_DIG_FMAX_MAP3(l1, f1, l2, f2, l3, f3) \ + .vdd_class = &vdd_dig, \ + .fmax[VDD_DIG_##l1] = (f1), \ + .fmax[VDD_DIG_##l2] = (f2), \ + .fmax[VDD_DIG_##l3] = (f3) + +enum vdd_dig_levels { + VDD_DIG_NONE, + VDD_DIG_LOW, + VDD_DIG_NOMINAL, + VDD_DIG_HIGH +}; + +static int set_vdd_dig(struct clk_vdd_class *vdd_class, int level) +{ + /* TODO: Actually call into regulator APIs to set VDD_DIG here. */ + return 0; +} + +static DEFINE_VDD_CLASS(vdd_dig, set_vdd_dig); + +static int cxo_clk_enable(struct clk *clk) +{ + /* TODO: Remove from here once the rpm xo clock is ready. */ + return 0; +} + +static void cxo_clk_disable(struct clk *clk) +{ + /* TODO: Remove from here once the rpm xo clock is ready. */ + return; +} + +static enum handoff cxo_clk_handoff(struct clk *clk) +{ + /* TODO: Remove from here once the rpm xo clock is ready. */ + return HANDOFF_ENABLED_CLK; +} + +static struct clk_ops clk_ops_cxo = { + .enable = cxo_clk_enable, + .disable = cxo_clk_disable, + .handoff = cxo_clk_handoff, +}; + +static struct fixed_clk cxo_clk_src = { + .c = { + .rate = 19200000, + .dbg_name = "cxo_clk_src", + .ops = &clk_ops_cxo, + .warned = true, + CLK_INIT(cxo_clk_src.c), + }, +}; + +static struct pll_vote_clk gpll0_clk_src = { + .en_reg = (void __iomem *)APCS_GPLL_ENA_VOTE_REG, + .en_mask = BIT(0), + .status_reg = (void __iomem *)GPLL0_STATUS_REG, + .status_mask = BIT(17), + .parent = &cxo_clk_src.c, + .base = &virt_bases[GCC_BASE], + .c = { + .rate = 600000000, + .dbg_name = "gpll0_clk_src", + .ops = &clk_ops_pll_vote, + .warned = true, + CLK_INIT(gpll0_clk_src.c), + }, +}; + +static struct pll_vote_clk gpll1_clk_src = { + .en_reg = (void __iomem *)APCS_GPLL_ENA_VOTE_REG, + .en_mask = BIT(1), + .status_reg = (void __iomem *)GPLL1_STATUS_REG, + .status_mask = BIT(17), + .parent = &cxo_clk_src.c, + .base = &virt_bases[GCC_BASE], + .c = { + .rate = 480000000, + .dbg_name = "gpll1_clk_src", + .ops = &clk_ops_pll_vote, + .warned = true, + CLK_INIT(gpll1_clk_src.c), + }, +}; + +static struct pll_vote_clk lpapll0_clk_src = { + .en_reg = (void __iomem *)LPASS_LPA_PLL_VOTE_APPS_REG, + .en_mask = BIT(0), + .status_reg = (void __iomem *)LPAPLL_STATUS_REG, + .status_mask = BIT(17), + .parent = &cxo_clk_src.c, + .base = &virt_bases[LPASS_BASE], + .c = { + .rate = 491520000, + .dbg_name = "lpapll0_clk_src", + .ops = &clk_ops_pll_vote, + .warned = true, + CLK_INIT(lpapll0_clk_src.c), + }, +}; + +static struct pll_vote_clk mmpll0_clk_src = { + .en_reg = (void __iomem *)MMSS_PLL_VOTE_APCS_REG, + .en_mask = BIT(0), + .status_reg = (void __iomem *)MMPLL0_STATUS_REG, + .status_mask = BIT(17), + .parent = &cxo_clk_src.c, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmpll0_clk_src", + .rate = 800000000, + .ops = &clk_ops_pll_vote, + .warned = true, + CLK_INIT(mmpll0_clk_src.c), + }, +}; + +static struct pll_vote_clk mmpll1_clk_src = { + .en_reg = (void __iomem *)MMSS_PLL_VOTE_APCS_REG, + .en_mask = BIT(1), + .status_reg = (void __iomem *)MMPLL1_STATUS_REG, + .status_mask = BIT(17), + .parent = &cxo_clk_src.c, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmpll1_clk_src", + .rate = 1000000000, + .ops = &clk_ops_pll_vote, + .warned = true, + CLK_INIT(mmpll1_clk_src.c), + }, +}; + +static struct pll_clk mmpll3_clk_src = { + .mode_reg = (void __iomem *)MMPLL3_MODE_REG, + .status_reg = (void __iomem *)MMPLL3_STATUS_REG, + .parent = &cxo_clk_src.c, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmpll3_clk_src", + .rate = 1000000000, + .ops = &clk_ops_local_pll, + CLK_INIT(mmpll3_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_usb30_master_clk[] = { + F(125000000, gpll0, 1, 5, 24), + F_END +}; + +static struct rcg_clk usb30_master_clk_src = { + .cmd_rcgr_reg = USB30_MASTER_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_usb30_master_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "usb30_master_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP1(NOMINAL, 125000000), + CLK_INIT(usb30_master_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk[] = { + F( 960000, cxo, 10, 1, 2), + F( 4800000, cxo, 4, 0, 0), + F( 9600000, cxo, 2, 0, 0), + F(15000000, gpll0, 10, 1, 4), + F(19200000, cxo, 1, 0, 0), + F(25000000, gpll0, 12, 1, 2), + F(50000000, gpll0, 12, 0, 0), + F_END +}; + +static struct rcg_clk blsp1_qup1_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_QUP1_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_qup1_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp1_qup1_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_qup2_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_QUP2_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_qup2_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp1_qup2_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_qup3_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_QUP3_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_qup3_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp1_qup3_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_qup4_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_QUP4_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_qup4_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp1_qup4_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_qup5_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_QUP5_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_qup5_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp1_qup5_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_qup6_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_QUP6_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_qup6_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp1_qup6_spi_apps_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_blsp1_2_uart1_6_apps_clk[] = { + F( 3686400, gpll0, 1, 96, 15625), + F( 7372800, gpll0, 1, 192, 15625), + F(14745600, gpll0, 1, 384, 15625), + F(16000000, gpll0, 5, 2, 15), + F(19200000, cxo, 1, 0, 0), + F(24000000, gpll0, 5, 1, 5), + F(32000000, gpll0, 1, 4, 75), + F(40000000, gpll0, 15, 0, 0), + F(46400000, gpll0, 1, 29, 375), + F(48000000, gpll0, 12.5, 0, 0), + F(51200000, gpll0, 1, 32, 375), + F(56000000, gpll0, 1, 7, 75), + F(58982400, gpll0, 1, 1536, 15625), + F(60000000, gpll0, 10, 0, 0), + F_END +}; + +static struct rcg_clk blsp1_uart1_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_UART1_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_uart1_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp1_uart1_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_uart2_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_UART2_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_uart2_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp1_uart2_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_uart3_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_UART3_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_uart3_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp1_uart3_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_uart4_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_UART4_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_uart4_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp1_uart4_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_uart5_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_UART5_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_uart5_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp1_uart5_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp1_uart6_apps_clk_src = { + .cmd_rcgr_reg = BLSP1_UART6_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp1_uart6_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp1_uart6_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_qup1_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_QUP1_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_qup1_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp2_qup1_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_qup2_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_QUP2_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_qup2_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp2_qup2_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_qup3_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_QUP3_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_qup3_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp2_qup3_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_qup4_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_QUP4_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_qup4_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp2_qup4_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_qup5_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_QUP5_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_qup5_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp2_qup5_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_qup6_spi_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_QUP6_SPI_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_qup1_6_spi_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_qup6_spi_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 25000000, NOMINAL, 50000000), + CLK_INIT(blsp2_qup6_spi_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_uart1_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_UART1_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_uart1_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp2_uart1_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_uart2_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_UART2_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_uart2_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp2_uart2_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_uart3_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_UART3_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_uart3_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp2_uart3_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_uart4_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_UART4_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_uart4_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp2_uart4_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_uart5_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_UART5_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_uart5_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp2_uart5_apps_clk_src.c), + }, +}; + +static struct rcg_clk blsp2_uart6_apps_clk_src = { + .cmd_rcgr_reg = BLSP2_UART6_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_blsp1_2_uart1_6_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "blsp2_uart6_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 31580000, NOMINAL, 63160000), + CLK_INIT(blsp2_uart6_apps_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_ce1_clk[] = { + F( 50000000, gpll0, 12, 0, 0), + F(100000000, gpll0, 6, 0, 0), + F_END +}; + +static struct rcg_clk ce1_clk_src = { + .cmd_rcgr_reg = CE1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_ce1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "ce1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 50000000, NOMINAL, 100000000), + CLK_INIT(ce1_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_ce2_clk[] = { + F( 50000000, gpll0, 12, 0, 0), + F(100000000, gpll0, 6, 0, 0), + F_END +}; + +static struct rcg_clk ce2_clk_src = { + .cmd_rcgr_reg = CE2_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_ce2_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "ce2_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 50000000, NOMINAL, 100000000), + CLK_INIT(ce2_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_gp_clk[] = { + F(19200000, cxo, 1, 0, 0), + F_END +}; + +static struct rcg_clk gp1_clk_src = { + .cmd_rcgr_reg = GP1_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_gp_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gp1_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(gp1_clk_src.c), + }, +}; + +static struct rcg_clk gp2_clk_src = { + .cmd_rcgr_reg = GP2_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_gp_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gp2_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(gp2_clk_src.c), + }, +}; + +static struct rcg_clk gp3_clk_src = { + .cmd_rcgr_reg = GP3_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_gp_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gp3_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(gp3_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_pdm2_clk[] = { + F(60000000, gpll0, 10, 0, 0), + F_END +}; + +static struct rcg_clk pdm2_clk_src = { + .cmd_rcgr_reg = PDM2_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_pdm2_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "pdm2_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 60000000), + CLK_INIT(pdm2_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_sdcc1_2_apps_clk[] = { + F( 144000, cxo, 16, 3, 25), + F( 400000, cxo, 12, 1, 4), + F( 20000000, gpll0, 15, 1, 2), + F( 25000000, gpll0, 12, 1, 2), + F( 50000000, gpll0, 12, 0, 0), + F(100000000, gpll0, 6, 0, 0), + F(200000000, gpll0, 3, 0, 0), + F_END +}; + +static struct clk_freq_tbl ftbl_gcc_sdcc3_4_apps_clk[] = { + F( 144000, cxo, 16, 3, 25), + F( 400000, cxo, 12, 1, 4), + F( 20000000, gpll0, 15, 1, 2), + F( 25000000, gpll0, 12, 1, 2), + F( 50000000, gpll0, 12, 0, 0), + F(100000000, gpll0, 6, 0, 0), + F_END +}; + +static struct rcg_clk sdcc1_apps_clk_src = { + .cmd_rcgr_reg = SDCC1_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_sdcc1_2_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "sdcc1_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(sdcc1_apps_clk_src.c), + }, +}; + +static struct rcg_clk sdcc2_apps_clk_src = { + .cmd_rcgr_reg = SDCC2_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_sdcc1_2_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "sdcc2_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(sdcc2_apps_clk_src.c), + }, +}; + +static struct rcg_clk sdcc3_apps_clk_src = { + .cmd_rcgr_reg = SDCC3_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_sdcc3_4_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "sdcc3_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 50000000, NOMINAL, 100000000), + CLK_INIT(sdcc3_apps_clk_src.c), + }, +}; + +static struct rcg_clk sdcc4_apps_clk_src = { + .cmd_rcgr_reg = SDCC4_APPS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_sdcc3_4_apps_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "sdcc4_apps_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 50000000, NOMINAL, 100000000), + CLK_INIT(sdcc4_apps_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_tsif_ref_clk[] = { + F(105000, cxo, 2, 1, 91), + F_END +}; + +static struct rcg_clk tsif_ref_clk_src = { + .cmd_rcgr_reg = TSIF_REF_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_gcc_tsif_ref_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "tsif_ref_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP1(LOW, 105500), + CLK_INIT(tsif_ref_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_usb30_mock_utmi_clk[] = { + F(60000000, gpll0, 10, 0, 0), + F_END +}; + +static struct rcg_clk usb30_mock_utmi_clk_src = { + .cmd_rcgr_reg = USB30_MOCK_UTMI_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_usb30_mock_utmi_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "usb30_mock_utmi_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(NOMINAL, 60000000), + CLK_INIT(usb30_mock_utmi_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_usb_hs_system_clk[] = { + F(75000000, gpll0, 8, 0, 0), + F_END +}; + +static struct rcg_clk usb_hs_system_clk_src = { + .cmd_rcgr_reg = USB_HS_SYSTEM_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_usb_hs_system_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "usb_hs_system_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 37500000, NOMINAL, 75000000), + CLK_INIT(usb_hs_system_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_usb_hsic_clk[] = { + F_HSIC(480000000, gpll1, 1, 0, 0), + F_END +}; + +static struct rcg_clk usb_hsic_clk_src = { + .cmd_rcgr_reg = USB_HSIC_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_usb_hsic_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "usb_hsic_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 480000000), + CLK_INIT(usb_hsic_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_usb_hsic_io_cal_clk[] = { + F(9600000, cxo, 2, 0, 0), + F_END +}; + +static struct rcg_clk usb_hsic_io_cal_clk_src = { + .cmd_rcgr_reg = USB_HSIC_IO_CAL_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_usb_hsic_io_cal_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "usb_hsic_io_cal_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 9600000), + CLK_INIT(usb_hsic_io_cal_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_gcc_usb_hsic_system_clk[] = { + F(75000000, gpll0, 8, 0, 0), + F_END +}; + +static struct rcg_clk usb_hsic_system_clk_src = { + .cmd_rcgr_reg = USB_HSIC_SYSTEM_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_gcc_usb_hsic_system_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "usb_hsic_system_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 37500000, NOMINAL, 75000000), + CLK_INIT(usb_hsic_system_clk_src.c), + }, +}; + +static struct local_vote_clk gcc_bam_dma_ahb_clk = { + .cbcr_reg = BAM_DMA_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(12), + .bcr_reg = BAM_DMA_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_bam_dma_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_bam_dma_ahb_clk.c), + }, +}; + +static struct local_vote_clk gcc_blsp1_ahb_clk = { + .cbcr_reg = BLSP1_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(17), + .bcr_reg = BLSP1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_blsp1_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup1_i2c_apps_clk = { + .cbcr_reg = BLSP1_QUP1_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP1_QUP1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup1_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup1_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup1_spi_apps_clk = { + .cbcr_reg = BLSP1_QUP1_SPI_APPS_CBCR, + .parent = &blsp1_qup1_spi_apps_clk_src.c, + .bcr_reg = BLSP1_QUP1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup1_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup1_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup2_i2c_apps_clk = { + .cbcr_reg = BLSP1_QUP2_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP1_QUP2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup2_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup2_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup2_spi_apps_clk = { + .cbcr_reg = BLSP1_QUP2_SPI_APPS_CBCR, + .parent = &blsp1_qup2_spi_apps_clk_src.c, + .bcr_reg = BLSP1_QUP2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup2_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup2_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup3_i2c_apps_clk = { + .cbcr_reg = BLSP1_QUP3_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP1_QUP3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup3_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup3_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup3_spi_apps_clk = { + .cbcr_reg = BLSP1_QUP3_SPI_APPS_CBCR, + .parent = &blsp1_qup3_spi_apps_clk_src.c, + .bcr_reg = BLSP1_QUP3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup3_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup3_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup4_i2c_apps_clk = { + .cbcr_reg = BLSP1_QUP4_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP1_QUP4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup4_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup4_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup4_spi_apps_clk = { + .cbcr_reg = BLSP1_QUP4_SPI_APPS_CBCR, + .parent = &blsp1_qup4_spi_apps_clk_src.c, + .bcr_reg = BLSP1_QUP4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup4_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup4_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup5_i2c_apps_clk = { + .cbcr_reg = BLSP1_QUP5_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP1_QUP5_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup5_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup5_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup5_spi_apps_clk = { + .cbcr_reg = BLSP1_QUP5_SPI_APPS_CBCR, + .parent = &blsp1_qup5_spi_apps_clk_src.c, + .bcr_reg = BLSP1_QUP5_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup5_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup5_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup6_i2c_apps_clk = { + .cbcr_reg = BLSP1_QUP6_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP1_QUP6_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup6_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup6_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_qup6_spi_apps_clk = { + .cbcr_reg = BLSP1_QUP6_SPI_APPS_CBCR, + .parent = &blsp1_qup6_spi_apps_clk_src.c, + .bcr_reg = BLSP1_QUP6_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_qup6_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_qup6_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_uart1_apps_clk = { + .cbcr_reg = BLSP1_UART1_APPS_CBCR, + .parent = &blsp1_uart1_apps_clk_src.c, + .bcr_reg = BLSP1_UART1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_uart1_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_uart1_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_uart2_apps_clk = { + .cbcr_reg = BLSP1_UART2_APPS_CBCR, + .parent = &blsp1_uart2_apps_clk_src.c, + .bcr_reg = BLSP1_UART2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_uart2_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_uart2_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_uart3_apps_clk = { + .cbcr_reg = BLSP1_UART3_APPS_CBCR, + .parent = &blsp1_uart3_apps_clk_src.c, + .bcr_reg = BLSP1_UART3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_uart3_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_uart3_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_uart4_apps_clk = { + .cbcr_reg = BLSP1_UART4_APPS_CBCR, + .parent = &blsp1_uart4_apps_clk_src.c, + .bcr_reg = BLSP1_UART4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_uart4_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_uart4_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_uart5_apps_clk = { + .cbcr_reg = BLSP1_UART5_APPS_CBCR, + .parent = &blsp1_uart5_apps_clk_src.c, + .bcr_reg = BLSP1_UART5_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_uart5_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_uart5_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp1_uart6_apps_clk = { + .cbcr_reg = BLSP1_UART6_APPS_CBCR, + .parent = &blsp1_uart6_apps_clk_src.c, + .bcr_reg = BLSP1_UART6_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp1_uart6_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp1_uart6_apps_clk.c), + }, +}; + +static struct local_vote_clk gcc_boot_rom_ahb_clk = { + .cbcr_reg = BOOT_ROM_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(10), + .bcr_reg = BOOT_ROM_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_boot_rom_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_boot_rom_ahb_clk.c), + }, +}; + +static struct local_vote_clk gcc_blsp2_ahb_clk = { + .cbcr_reg = BLSP2_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(15), + .bcr_reg = BLSP2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_blsp2_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup1_i2c_apps_clk = { + .cbcr_reg = BLSP2_QUP1_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP2_QUP1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup1_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup1_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup1_spi_apps_clk = { + .cbcr_reg = BLSP2_QUP1_SPI_APPS_CBCR, + .parent = &blsp2_qup1_spi_apps_clk_src.c, + .bcr_reg = BLSP2_QUP1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup1_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup1_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup2_i2c_apps_clk = { + .cbcr_reg = BLSP2_QUP2_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP2_QUP2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup2_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup2_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup2_spi_apps_clk = { + .cbcr_reg = BLSP2_QUP2_SPI_APPS_CBCR, + .parent = &blsp2_qup2_spi_apps_clk_src.c, + .bcr_reg = BLSP2_QUP2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup2_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup2_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup3_i2c_apps_clk = { + .cbcr_reg = BLSP2_QUP3_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP2_QUP3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup3_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup3_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup3_spi_apps_clk = { + .cbcr_reg = BLSP2_QUP3_SPI_APPS_CBCR, + .parent = &blsp2_qup3_spi_apps_clk_src.c, + .bcr_reg = BLSP2_QUP3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup3_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup3_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup4_i2c_apps_clk = { + .cbcr_reg = BLSP2_QUP4_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP2_QUP4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup4_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup4_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup4_spi_apps_clk = { + .cbcr_reg = BLSP2_QUP4_SPI_APPS_CBCR, + .parent = &blsp2_qup4_spi_apps_clk_src.c, + .bcr_reg = BLSP2_QUP4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup4_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup4_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup5_i2c_apps_clk = { + .cbcr_reg = BLSP2_QUP5_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP2_QUP5_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup5_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup5_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup5_spi_apps_clk = { + .cbcr_reg = BLSP2_QUP5_SPI_APPS_CBCR, + .parent = &blsp2_qup5_spi_apps_clk_src.c, + .bcr_reg = BLSP2_QUP5_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup5_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup5_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup6_i2c_apps_clk = { + .cbcr_reg = BLSP2_QUP6_I2C_APPS_CBCR, + .parent = &cxo_clk_src.c, + .has_sibling = 1, + .bcr_reg = BLSP2_QUP6_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup6_i2c_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup6_i2c_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_qup6_spi_apps_clk = { + .cbcr_reg = BLSP2_QUP6_SPI_APPS_CBCR, + .parent = &blsp2_qup6_spi_apps_clk_src.c, + .bcr_reg = BLSP2_QUP6_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_qup6_spi_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_qup6_spi_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_uart1_apps_clk = { + .cbcr_reg = BLSP2_UART1_APPS_CBCR, + .parent = &blsp2_uart1_apps_clk_src.c, + .bcr_reg = BLSP2_UART1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_uart1_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_uart1_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_uart2_apps_clk = { + .cbcr_reg = BLSP2_UART2_APPS_CBCR, + .parent = &blsp2_uart2_apps_clk_src.c, + .bcr_reg = BLSP2_UART2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_uart2_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_uart2_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_uart3_apps_clk = { + .cbcr_reg = BLSP2_UART3_APPS_CBCR, + .parent = &blsp2_uart3_apps_clk_src.c, + .bcr_reg = BLSP2_UART3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_uart3_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_uart3_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_uart4_apps_clk = { + .cbcr_reg = BLSP2_UART4_APPS_CBCR, + .parent = &blsp2_uart4_apps_clk_src.c, + .bcr_reg = BLSP2_UART4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_uart4_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_uart4_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_uart5_apps_clk = { + .cbcr_reg = BLSP2_UART5_APPS_CBCR, + .parent = &blsp2_uart5_apps_clk_src.c, + .bcr_reg = BLSP2_UART5_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_uart5_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_uart5_apps_clk.c), + }, +}; + +static struct branch_clk gcc_blsp2_uart6_apps_clk = { + .cbcr_reg = BLSP2_UART6_APPS_CBCR, + .parent = &blsp2_uart6_apps_clk_src.c, + .bcr_reg = BLSP2_UART6_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_blsp2_uart6_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_blsp2_uart6_apps_clk.c), + }, +}; + +static struct local_vote_clk gcc_ce1_clk = { + .cbcr_reg = CE1_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(5), + .bcr_reg = CE1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_ce1_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_ce1_clk.c), + }, +}; + +static struct local_vote_clk gcc_ce1_ahb_clk = { + .cbcr_reg = CE1_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(3), + .bcr_reg = CE1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_ce1_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_ce1_ahb_clk.c), + }, +}; + +static struct local_vote_clk gcc_ce1_axi_clk = { + .cbcr_reg = CE1_AXI_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(4), + .bcr_reg = CE1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_ce1_axi_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_ce1_axi_clk.c), + }, +}; + +static struct local_vote_clk gcc_ce2_clk = { + .cbcr_reg = CE2_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(2), + .bcr_reg = CE2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_ce2_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_ce2_clk.c), + }, +}; + +static struct local_vote_clk gcc_ce2_ahb_clk = { + .cbcr_reg = CE2_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(0), + .bcr_reg = CE2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_ce1_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_ce1_ahb_clk.c), + }, +}; + +static struct local_vote_clk gcc_ce2_axi_clk = { + .cbcr_reg = CE2_AXI_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(1), + .bcr_reg = CE2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_ce1_axi_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_ce2_axi_clk.c), + }, +}; + +static struct branch_clk gcc_gp1_clk = { + .cbcr_reg = GP1_CBCR, + .parent = &gp1_clk_src.c, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_gp1_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_gp1_clk.c), + }, +}; + +static struct branch_clk gcc_gp2_clk = { + .cbcr_reg = GP2_CBCR, + .parent = &gp2_clk_src.c, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_gp2_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_gp2_clk.c), + }, +}; + +static struct branch_clk gcc_gp3_clk = { + .cbcr_reg = GP3_CBCR, + .parent = &gp3_clk_src.c, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_gp3_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_gp3_clk.c), + }, +}; + +static struct branch_clk gcc_pdm2_clk = { + .cbcr_reg = PDM2_CBCR, + .parent = &pdm2_clk_src.c, + .bcr_reg = PDM_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_pdm2_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_pdm2_clk.c), + }, +}; + +static struct branch_clk gcc_pdm_ahb_clk = { + .cbcr_reg = PDM_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = PDM_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_pdm_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_pdm_ahb_clk.c), + }, +}; + +static struct local_vote_clk gcc_prng_ahb_clk = { + .cbcr_reg = PRNG_AHB_CBCR, + .vote_reg = APCS_CLOCK_BRANCH_ENA_VOTE, + .en_mask = BIT(13), + .bcr_reg = PRNG_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_prng_ahb_clk", + .ops = &clk_ops_vote, + CLK_INIT(gcc_prng_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc1_ahb_clk = { + .cbcr_reg = SDCC1_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = SDCC1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc1_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc1_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc1_apps_clk = { + .cbcr_reg = SDCC1_APPS_CBCR, + .parent = &sdcc1_apps_clk_src.c, + .bcr_reg = SDCC1_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc1_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc1_apps_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc2_ahb_clk = { + .cbcr_reg = SDCC2_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = SDCC2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc2_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc2_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc2_apps_clk = { + .cbcr_reg = SDCC2_APPS_CBCR, + .parent = &sdcc2_apps_clk_src.c, + .bcr_reg = SDCC2_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc2_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc2_apps_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc3_ahb_clk = { + .cbcr_reg = SDCC3_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = SDCC3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc3_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc3_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc3_apps_clk = { + .cbcr_reg = SDCC3_APPS_CBCR, + .parent = &sdcc3_apps_clk_src.c, + .bcr_reg = SDCC3_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc3_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc3_apps_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc4_ahb_clk = { + .cbcr_reg = SDCC4_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = SDCC4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc4_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc4_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_sdcc4_apps_clk = { + .cbcr_reg = SDCC4_APPS_CBCR, + .parent = &sdcc4_apps_clk_src.c, + .bcr_reg = SDCC4_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_sdcc4_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_sdcc4_apps_clk.c), + }, +}; + +static struct branch_clk gcc_tsif_ahb_clk = { + .cbcr_reg = TSIF_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = TSIF_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_tsif_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_tsif_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_tsif_ref_clk = { + .cbcr_reg = TSIF_REF_CBCR, + .parent = &tsif_ref_clk_src.c, + .bcr_reg = TSIF_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_tsif_ref_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_tsif_ref_clk.c), + }, +}; + +static struct branch_clk gcc_usb30_master_clk = { + .cbcr_reg = USB30_MASTER_CBCR, + .parent = &usb30_master_clk_src.c, + .has_sibling = 1, + .bcr_reg = USB_30_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb30_master_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb30_master_clk.c), + }, +}; + +static struct branch_clk gcc_usb30_mock_utmi_clk = { + .cbcr_reg = USB30_MOCK_UTMI_CBCR, + .parent = &usb30_mock_utmi_clk_src.c, + .bcr_reg = USB_30_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb30_mock_utmi_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb30_mock_utmi_clk.c), + }, +}; + +static struct branch_clk gcc_usb_hs_ahb_clk = { + .cbcr_reg = USB_HS_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = USB_HS_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb_hs_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb_hs_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_usb_hs_system_clk = { + .cbcr_reg = USB_HS_SYSTEM_CBCR, + .parent = &usb_hs_system_clk_src.c, + .bcr_reg = USB_HS_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb_hs_system_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb_hs_system_clk.c), + }, +}; + +static struct branch_clk gcc_usb_hsic_ahb_clk = { + .cbcr_reg = USB_HSIC_AHB_CBCR, + .has_sibling = 1, + .bcr_reg = USB_HS_HSIC_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb_hsic_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb_hsic_ahb_clk.c), + }, +}; + +static struct branch_clk gcc_usb_hsic_clk = { + .cbcr_reg = USB_HSIC_CBCR, + .parent = &usb_hsic_clk_src.c, + .bcr_reg = USB_HS_HSIC_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb_hsic_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb_hsic_clk.c), + }, +}; + +static struct branch_clk gcc_usb_hsic_io_cal_clk = { + .cbcr_reg = USB_HSIC_IO_CAL_CBCR, + .parent = &usb_hsic_io_cal_clk_src.c, + .bcr_reg = USB_HS_HSIC_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb_hsic_io_cal_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb_hsic_io_cal_clk.c), + }, +}; + +static struct branch_clk gcc_usb_hsic_system_clk = { + .cbcr_reg = USB_HSIC_SYSTEM_CBCR, + .parent = &usb_hsic_system_clk_src.c, + .bcr_reg = USB_HS_HSIC_BCR, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_usb_hsic_system_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_usb_hsic_system_clk.c), + }, +}; + +static struct branch_clk gcc_mss_cfg_ahb_clk = { + .cbcr_reg = MSS_CFG_AHB_CBCR, + .has_sibling = 1, + .base = &virt_bases[GCC_BASE], + .c = { + .dbg_name = "gcc_mss_cfg_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(gcc_mss_cfg_ahb_clk.c), + }, +}; + +static struct clk_freq_tbl ftbl_mmss_ahb_clk[] = { + F_MM(19200000, cxo, 1, 0, 0), + F_MM(40000000, gpll0, 15, 0, 0), + F_MM(80000000, mmpll0, 10, 0, 0), + F_END, +}; + +/* TODO: This may go away (may be controlled by the RPM). */ +static struct rcg_clk ahb_clk_src = { + .cmd_rcgr_reg = 0x5000, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mmss_ahb_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "ahb_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 40000000, NOMINAL, 80000000), + CLK_INIT(ahb_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mmss_axi_clk[] = { + F_MM( 19200000, cxo, 1, 0, 0), + F_MM(150000000, gpll0, 4, 0, 0), + F_MM(333330000, mmpll1, 3, 0, 0), + F_MM(400000000, mmpll0, 2, 0, 0), + F_END +}; + +static struct rcg_clk axi_clk_src = { + .cmd_rcgr_reg = 0x5040, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mmss_axi_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "axi_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 150000000, NOMINAL, 333330000, + HIGH, 400000000), + CLK_INIT(axi_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_csi0_3_clk[] = { + F_MM(100000000, gpll0, 6, 0, 0), + F_MM(200000000, mmpll0, 4, 0, 0), + F_END +}; + +static struct rcg_clk csi0_clk_src = { + .cmd_rcgr_reg = CSI0_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_csi0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi0_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi0_clk_src.c), + }, +}; + +static struct rcg_clk csi1_clk_src = { + .cmd_rcgr_reg = CSI1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_csi0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi1_clk_src.c), + }, +}; + +static struct rcg_clk csi2_clk_src = { + .cmd_rcgr_reg = CSI2_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_csi0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi2_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi2_clk_src.c), + }, +}; + +static struct rcg_clk csi3_clk_src = { + .cmd_rcgr_reg = CSI3_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_csi0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi3_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi3_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_vfe_vfe0_1_clk[] = { + F_MM( 37500000, gpll0, 16, 0, 0), + F_MM( 50000000, gpll0, 12, 0, 0), + F_MM( 60000000, gpll0, 10, 0, 0), + F_MM( 80000000, gpll0, 7.5, 0, 0), + F_MM(100000000, gpll0, 6, 0, 0), + F_MM(109090000, gpll0, 5.5, 0, 0), + F_MM(150000000, gpll0, 4, 0, 0), + F_MM(200000000, gpll0, 3, 0, 0), + F_MM(228570000, mmpll0, 3.5, 0, 0), + F_MM(266670000, mmpll0, 3, 0, 0), + F_MM(320000000, mmpll0, 2.5, 0, 0), + F_END +}; + +static struct rcg_clk vfe0_clk_src = { + .cmd_rcgr_reg = VFE0_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_vfe_vfe0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "vfe0_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(vfe0_clk_src.c), + }, +}; + +static struct rcg_clk vfe1_clk_src = { + .cmd_rcgr_reg = VFE1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_vfe_vfe0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "vfe1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(vfe1_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_mdp_clk[] = { + F_MM( 37500000, gpll0, 16, 0, 0), + F_MM( 60000000, gpll0, 10, 0, 0), + F_MM( 75000000, gpll0, 8, 0, 0), + F_MM( 85710000, gpll0, 7, 0, 0), + F_MM(100000000, gpll0, 6, 0, 0), + F_MM(133330000, mmpll0, 6, 0, 0), + F_MM(160000000, mmpll0, 5, 0, 0), + F_MM(200000000, mmpll0, 4, 0, 0), + F_MM(266670000, mmpll0, 3, 0, 0), + F_MM(320000000, mmpll0, 2.5, 0, 0), + F_END +}; + +static struct rcg_clk mdp_clk_src = { + .cmd_rcgr_reg = MDP_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_mdp_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdp_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(mdp_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_cci_cci_clk[] = { + F_MM(19200000, cxo, 1, 0, 0), + F_END +}; + +static struct rcg_clk cci_clk_src = { + .cmd_rcgr_reg = CCI_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_cci_cci_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "cci_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 20000000, NOMINAL, 40000000), + CLK_INIT(cci_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_gp0_1_clk[] = { + F_MM( 10000, cxo, 16, 1, 120), + F_MM( 20000, cxo, 16, 1, 50), + F_MM( 6000000, gpll0, 10, 1, 10), + F_MM(12000000, gpll0, 10, 1, 5), + F_MM(13000000, gpll0, 10, 13, 60), + F_MM(24000000, gpll0, 5, 1, 5), + F_END +}; + +static struct rcg_clk mmss_gp0_clk_src = { + .cmd_rcgr_reg = MMSS_GP0_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_camss_gp0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_gp0_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(mmss_gp0_clk_src.c), + }, +}; + +static struct rcg_clk mmss_gp1_clk_src = { + .cmd_rcgr_reg = MMSS_GP1_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_camss_gp0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_gp1_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(mmss_gp1_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_jpeg_jpeg0_2_clk[] = { + F_MM( 75000000, gpll0, 8, 0, 0), + F_MM(150000000, gpll0, 4, 0, 0), + F_MM(200000000, gpll0, 3, 0, 0), + F_MM(228570000, mmpll0, 3.5, 0, 0), + F_MM(266670000, mmpll0, 3, 0, 0), + F_MM(320000000, mmpll0, 2.5, 0, 0), + F_END +}; + +static struct rcg_clk jpeg0_clk_src = { + .cmd_rcgr_reg = JPEG0_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_jpeg_jpeg0_2_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "jpeg0_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(jpeg0_clk_src.c), + }, +}; + +static struct rcg_clk jpeg1_clk_src = { + .cmd_rcgr_reg = JPEG1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_jpeg_jpeg0_2_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "jpeg1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(jpeg1_clk_src.c), + }, +}; + +static struct rcg_clk jpeg2_clk_src = { + .cmd_rcgr_reg = JPEG2_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_jpeg_jpeg0_2_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "jpeg2_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(jpeg2_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_mclk0_3_clk[] = { + F_MM(66670000, gpll0, 9, 0, 0), + F_END +}; + +static struct rcg_clk mclk0_clk_src = { + .cmd_rcgr_reg = MCLK0_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_mclk0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mclk0_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 66670000), + CLK_INIT(mclk0_clk_src.c), + }, +}; + +static struct rcg_clk mclk1_clk_src = { + .cmd_rcgr_reg = MCLK1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_mclk0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mclk1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 66670000), + CLK_INIT(mclk1_clk_src.c), + }, +}; + +static struct rcg_clk mclk2_clk_src = { + .cmd_rcgr_reg = MCLK2_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_mclk0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mclk2_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 66670000), + CLK_INIT(mclk2_clk_src.c), + }, +}; + +static struct rcg_clk mclk3_clk_src = { + .cmd_rcgr_reg = MCLK3_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_mclk0_3_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mclk3_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP1(LOW, 66670000), + CLK_INIT(mclk3_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_phy0_2_csi0_2phytimer_clk[] = { + F_MM(100000000, gpll0, 6, 0, 0), + F_MM(200000000, mmpll0, 4, 0, 0), + F_END +}; + +static struct rcg_clk csi0phytimer_clk_src = { + .cmd_rcgr_reg = CSI0PHYTIMER_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_phy0_2_csi0_2phytimer_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi0phytimer_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi0phytimer_clk_src.c), + }, +}; + +static struct rcg_clk csi1phytimer_clk_src = { + .cmd_rcgr_reg = CSI1PHYTIMER_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_phy0_2_csi0_2phytimer_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi1phytimer_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi1phytimer_clk_src.c), + }, +}; + +static struct rcg_clk csi2phytimer_clk_src = { + .cmd_rcgr_reg = CSI2PHYTIMER_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_phy0_2_csi0_2phytimer_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "csi2phytimer_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 100000000, NOMINAL, 200000000), + CLK_INIT(csi2phytimer_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_camss_vfe_cpp_clk[] = { + F_MM(150000000, gpll0, 4, 0, 0), + F_MM(266670000, mmpll0, 3, 0, 0), + F_MM(320000000, mmpll0, 2.5, 0, 0), + F_END +}; + +static struct rcg_clk cpp_clk_src = { + .cmd_rcgr_reg = CPP_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_camss_vfe_cpp_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "cpp_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 320000000), + CLK_INIT(cpp_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_byte0_1_clk[] = { + F_MDSS( 93750000, dsipll_750, 8, 0, 0), + F_MDSS(187500000, dsipll_750, 4, 0, 0), + F_END +}; + +static struct rcg_clk byte0_clk_src = { + .cmd_rcgr_reg = BYTE0_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_byte0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "byte0_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 93800000, NOMINAL, 187500000, + HIGH, 188000000), + CLK_INIT(byte0_clk_src.c), + }, +}; + +static struct rcg_clk byte1_clk_src = { + .cmd_rcgr_reg = BYTE1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_byte0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "byte1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP3(LOW, 93800000, NOMINAL, 187500000, + HIGH, 188000000), + CLK_INIT(byte1_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_edpaux_clk[] = { + F_MM(19200000, cxo, 1, 0, 0), + F_END +}; + +static struct rcg_clk edpaux_clk_src = { + .cmd_rcgr_reg = EDPAUX_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_edpaux_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "edpaux_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 20000000, NOMINAL, 40000000), + CLK_INIT(edpaux_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_edplink_clk[] = { + F_MDSS(135000000, edppll_270, 2, 0, 0), + F_MDSS(270000000, edppll_270, 11, 0, 0), + F_END +}; + +static struct rcg_clk edplink_clk_src = { + .cmd_rcgr_reg = EDPLINK_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_edplink_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "edplink_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 135000000, NOMINAL, 270000000), + CLK_INIT(edplink_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_edppixel_clk[] = { + F_MDSS(175000000, edppll_350, 2, 0, 0), + F_MDSS(350000000, edppll_350, 11, 0, 0), + F_END +}; + +static struct rcg_clk edppixel_clk_src = { + .cmd_rcgr_reg = EDPPIXEL_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_mdss_edppixel_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "edppixel_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 175000000, NOMINAL, 350000000), + CLK_INIT(edppixel_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_esc0_1_clk[] = { + F_MM(19200000, cxo, 1, 0, 0), + F_END +}; + +static struct rcg_clk esc0_clk_src = { + .cmd_rcgr_reg = ESC0_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_esc0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "esc0_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 20000000, NOMINAL, 40000000), + CLK_INIT(esc0_clk_src.c), + }, +}; + +static struct rcg_clk esc1_clk_src = { + .cmd_rcgr_reg = ESC1_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_esc0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "esc1_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 20000000, NOMINAL, 40000000), + CLK_INIT(esc1_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_extpclk_clk[] = { + F_MDSS(148500000, hdmipll_297, 2, 0, 0), + F_END +}; + +static struct rcg_clk extpclk_clk_src = { + .cmd_rcgr_reg = EXTPCLK_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_extpclk_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "extpclk_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 148500000, NOMINAL, 297000000), + CLK_INIT(extpclk_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_hdmi_clk[] = { + F_MDSS(19200000, cxo, 1, 0, 0), + F_END +}; + +static struct rcg_clk hdmi_clk_src = { + .cmd_rcgr_reg = HDMI_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_hdmi_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "hdmi_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 20000000, NOMINAL, 40000000), + CLK_INIT(hdmi_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_pclk0_1_clk[] = { + F_MDSS(125000000, dsipll_250, 2, 0, 0), + F_MDSS(250000000, dsipll_250, 1, 0, 0), + F_END +}; + +static struct rcg_clk pclk0_clk_src = { + .cmd_rcgr_reg = PCLK0_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_mdss_pclk0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "pclk0_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 125000000, NOMINAL, 250000000), + CLK_INIT(pclk0_clk_src.c), + }, +}; + +static struct rcg_clk pclk1_clk_src = { + .cmd_rcgr_reg = PCLK1_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_mdss_pclk0_1_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "pclk1_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 125000000, NOMINAL, 250000000), + CLK_INIT(pclk1_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_mdss_vsync_clk[] = { + F_MDSS(19200000, cxo, 1, 0, 0), + F_END +}; + +static struct rcg_clk vsync_clk_src = { + .cmd_rcgr_reg = VSYNC_CMD_RCGR, + .set_rate = set_rate_hid, + .freq_tbl = ftbl_mdss_vsync_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "vsync_clk_src", + .ops = &clk_ops_rcg, + VDD_DIG_FMAX_MAP2(LOW, 20000000, NOMINAL, 40000000), + CLK_INIT(vsync_clk_src.c), + }, +}; + +static struct clk_freq_tbl ftbl_venus0_vcodec0_clk[] = { + F_MM( 50000000, gpll0, 12, 0, 0), + F_MM(100000000, gpll0, 6, 0, 0), + F_MM(133330000, mmpll0, 6, 0, 0), + F_MM(200000000, mmpll0, 4, 0, 0), + F_MM(266670000, mmpll0, 3, 0, 0), + F_MM(410000000, mmpll3, 2, 0, 0), + F_END +}; + +static struct rcg_clk vcodec0_clk_src = { + .cmd_rcgr_reg = VCODEC0_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_venus0_vcodec0_clk, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "vcodec0_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP3(LOW, 133330000, NOMINAL, 266670000, + HIGH, 410000000), + CLK_INIT(vcodec0_clk_src.c), + }, +}; + +static struct branch_clk camss_cci_cci_ahb_clk = { + .cbcr_reg = CAMSS_CCI_CCI_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CCI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_cci_cci_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_cci_cci_ahb_clk.c), + }, +}; + +static struct branch_clk camss_cci_cci_clk = { + .cbcr_reg = CAMSS_CCI_CCI_CBCR, + .parent = &cci_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_CCI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_cci_cci_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_cci_cci_clk.c), + }, +}; + +static struct branch_clk camss_csi0_ahb_clk = { + .cbcr_reg = CAMSS_CSI0_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi0_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi0_ahb_clk.c), + }, +}; + +static struct branch_clk camss_csi0_clk = { + .cbcr_reg = CAMSS_CSI0_CBCR, + .parent = &csi0_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi0_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi0_clk.c), + }, +}; + +static struct branch_clk camss_csi0phy_clk = { + .cbcr_reg = CAMSS_CSI0PHY_CBCR, + .parent = &csi0_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI0PHY_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi0phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi0phy_clk.c), + }, +}; + +static struct branch_clk camss_csi0pix_clk = { + .cbcr_reg = CAMSS_CSI0PIX_CBCR, + .parent = &csi0_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI0PIX_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi0pix_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi0pix_clk.c), + }, +}; + +static struct branch_clk camss_csi0rdi_clk = { + .cbcr_reg = CAMSS_CSI0RDI_CBCR, + .parent = &csi0_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI0RDI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi0rdi_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi0rdi_clk.c), + }, +}; + +static struct branch_clk camss_csi1_ahb_clk = { + .cbcr_reg = CAMSS_CSI1_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI1_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi1_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi1_ahb_clk.c), + }, +}; + +static struct branch_clk camss_csi1_clk = { + .cbcr_reg = CAMSS_CSI1_CBCR, + .parent = &csi1_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI1_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi1_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi1_clk.c), + }, +}; + +static struct branch_clk camss_csi1phy_clk = { + .cbcr_reg = CAMSS_CSI1PHY_CBCR, + .parent = &csi1_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI1PHY_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi1phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi1phy_clk.c), + }, +}; + +static struct branch_clk camss_csi1pix_clk = { + .cbcr_reg = CAMSS_CSI1PIX_CBCR, + .parent = &csi1_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI1PIX_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi1pix_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi1pix_clk.c), + }, +}; + +static struct branch_clk camss_csi1rdi_clk = { + .cbcr_reg = CAMSS_CSI1RDI_CBCR, + .parent = &csi1_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI1RDI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi1rdi_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi1rdi_clk.c), + }, +}; + +static struct branch_clk camss_csi2_ahb_clk = { + .cbcr_reg = CAMSS_CSI2_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI2_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi2_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi2_ahb_clk.c), + }, +}; + +static struct branch_clk camss_csi2_clk = { + .cbcr_reg = CAMSS_CSI2_CBCR, + .parent = &csi2_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI2_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi2_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi2_clk.c), + }, +}; + +static struct branch_clk camss_csi2phy_clk = { + .cbcr_reg = CAMSS_CSI2PHY_CBCR, + .parent = &csi2_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI2PHY_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi2phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi2phy_clk.c), + }, +}; + +static struct branch_clk camss_csi2pix_clk = { + .cbcr_reg = CAMSS_CSI2PIX_CBCR, + .parent = &csi2_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI2PIX_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi2pix_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi2pix_clk.c), + }, +}; + +static struct branch_clk camss_csi2rdi_clk = { + .cbcr_reg = CAMSS_CSI2RDI_CBCR, + .parent = &csi2_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI2RDI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi2rdi_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi2rdi_clk.c), + }, +}; + +static struct branch_clk camss_csi3_ahb_clk = { + .cbcr_reg = CAMSS_CSI3_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI3_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi3_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi3_ahb_clk.c), + }, +}; + +static struct branch_clk camss_csi3_clk = { + .cbcr_reg = CAMSS_CSI3_CBCR, + .parent = &csi3_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI3_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi3_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi3_clk.c), + }, +}; + +static struct branch_clk camss_csi3phy_clk = { + .cbcr_reg = CAMSS_CSI3PHY_CBCR, + .parent = &csi3_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI3PHY_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi3phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi3phy_clk.c), + }, +}; + +static struct branch_clk camss_csi3pix_clk = { + .cbcr_reg = CAMSS_CSI3PIX_CBCR, + .parent = &csi3_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI3PIX_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi3pix_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi3pix_clk.c), + }, +}; + +static struct branch_clk camss_csi3rdi_clk = { + .cbcr_reg = CAMSS_CSI3RDI_CBCR, + .parent = &csi3_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI3RDI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi3rdi_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi3rdi_clk.c), + }, +}; + +static struct branch_clk camss_csi_vfe0_clk = { + .cbcr_reg = CAMSS_CSI_VFE0_CBCR, + .parent = &vfe0_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI_VFE0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi_vfe0_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi_vfe0_clk.c), + }, +}; + +static struct branch_clk camss_csi_vfe1_clk = { + .cbcr_reg = CAMSS_CSI_VFE1_CBCR, + .parent = &vfe1_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_CSI_VFE1_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_csi_vfe1_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_csi_vfe1_clk.c), + }, +}; + +static struct branch_clk camss_gp0_clk = { + .cbcr_reg = CAMSS_GP0_CBCR, + .parent = &mmss_gp0_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_GP0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_gp0_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_gp0_clk.c), + }, +}; + +static struct branch_clk camss_gp1_clk = { + .cbcr_reg = CAMSS_GP1_CBCR, + .parent = &mmss_gp1_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_GP1_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_gp1_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_gp1_clk.c), + }, +}; + +static struct branch_clk camss_ispif_ahb_clk = { + .cbcr_reg = CAMSS_ISPIF_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_ISPIF_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_ispif_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_ispif_ahb_clk.c), + }, +}; + +static struct branch_clk camss_jpeg_jpeg0_clk = { + .cbcr_reg = CAMSS_JPEG_JPEG0_CBCR, + .parent = &jpeg0_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_JPEG_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_jpeg_jpeg0_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_jpeg_jpeg0_clk.c), + }, +}; + +static struct branch_clk camss_jpeg_jpeg1_clk = { + .cbcr_reg = CAMSS_JPEG_JPEG1_CBCR, + .parent = &jpeg1_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_JPEG_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_jpeg_jpeg1_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_jpeg_jpeg1_clk.c), + }, +}; + +static struct branch_clk camss_jpeg_jpeg2_clk = { + .cbcr_reg = CAMSS_JPEG_JPEG2_CBCR, + .parent = &jpeg2_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_JPEG_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_jpeg_jpeg2_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_jpeg_jpeg2_clk.c), + }, +}; + +static struct branch_clk camss_jpeg_jpeg_ahb_clk = { + .cbcr_reg = CAMSS_JPEG_JPEG_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_JPEG_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_jpeg_jpeg_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_jpeg_jpeg_ahb_clk.c), + }, +}; + +static struct branch_clk camss_jpeg_jpeg_axi_clk = { + .cbcr_reg = CAMSS_JPEG_JPEG_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_JPEG_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_jpeg_jpeg_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_jpeg_jpeg_axi_clk.c), + }, +}; + +static struct branch_clk camss_jpeg_jpeg_ocmemnoc_clk = { + .cbcr_reg = CAMSS_JPEG_JPEG_OCMEMNOC_CBCR, + .has_sibling = 1, + .bcr_reg = CAMSS_JPEG_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_jpeg_jpeg_ocmemnoc_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_jpeg_jpeg_ocmemnoc_clk.c), + }, +}; + +static struct branch_clk camss_mclk0_clk = { + .cbcr_reg = CAMSS_MCLK0_CBCR, + .parent = &mclk0_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_MCLK0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_mclk0_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_mclk0_clk.c), + }, +}; + +static struct branch_clk camss_mclk1_clk = { + .cbcr_reg = CAMSS_MCLK1_CBCR, + .parent = &mclk1_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_MCLK1_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_mclk1_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_mclk1_clk.c), + }, +}; + +static struct branch_clk camss_mclk2_clk = { + .cbcr_reg = CAMSS_MCLK2_CBCR, + .parent = &mclk2_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_MCLK2_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_mclk2_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_mclk2_clk.c), + }, +}; + +static struct branch_clk camss_mclk3_clk = { + .cbcr_reg = CAMSS_MCLK3_CBCR, + .parent = &mclk3_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_MCLK3_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_mclk3_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_mclk3_clk.c), + }, +}; + +static struct branch_clk camss_micro_ahb_clk = { + .cbcr_reg = CAMSS_MICRO_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_MICRO_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_micro_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_micro_ahb_clk.c), + }, +}; + +static struct branch_clk camss_phy0_csi0phytimer_clk = { + .cbcr_reg = CAMSS_PHY0_CSI0PHYTIMER_CBCR, + .parent = &csi0phytimer_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_PHY0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_phy0_csi0phytimer_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_phy0_csi0phytimer_clk.c), + }, +}; + +static struct branch_clk camss_phy1_csi1phytimer_clk = { + .cbcr_reg = CAMSS_PHY1_CSI1PHYTIMER_CBCR, + .parent = &csi1phytimer_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_PHY1_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_phy1_csi1phytimer_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_phy1_csi1phytimer_clk.c), + }, +}; + +static struct branch_clk camss_phy2_csi2phytimer_clk = { + .cbcr_reg = CAMSS_PHY2_CSI2PHYTIMER_CBCR, + .parent = &csi2phytimer_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_PHY2_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_phy2_csi2phytimer_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_phy2_csi2phytimer_clk.c), + }, +}; + +static struct branch_clk camss_top_ahb_clk = { + .cbcr_reg = CAMSS_TOP_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_TOP_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_top_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_top_ahb_clk.c), + }, +}; + +static struct branch_clk camss_vfe_cpp_ahb_clk = { + .cbcr_reg = CAMSS_VFE_CPP_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_cpp_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_cpp_ahb_clk.c), + }, +}; + +static struct branch_clk camss_vfe_cpp_clk = { + .cbcr_reg = CAMSS_VFE_CPP_CBCR, + .parent = &cpp_clk_src.c, + .has_sibling = 0, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_cpp_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_cpp_clk.c), + }, +}; + +static struct branch_clk camss_vfe_vfe0_clk = { + .cbcr_reg = CAMSS_VFE_VFE0_CBCR, + .parent = &vfe0_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_vfe0_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_vfe0_clk.c), + }, +}; + +static struct branch_clk camss_vfe_vfe1_clk = { + .cbcr_reg = CAMSS_VFE_VFE1_CBCR, + .parent = &vfe1_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_vfe1_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_vfe1_clk.c), + }, +}; + +static struct branch_clk camss_vfe_vfe_ahb_clk = { + .cbcr_reg = CAMSS_VFE_VFE_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_vfe_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_vfe_ahb_clk.c), + }, +}; + +static struct branch_clk camss_vfe_vfe_axi_clk = { + .cbcr_reg = CAMSS_VFE_VFE_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_vfe_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_vfe_axi_clk.c), + }, +}; + +static struct branch_clk camss_vfe_vfe_ocmemnoc_clk = { + .cbcr_reg = CAMSS_VFE_VFE_OCMEMNOC_CBCR, + .has_sibling = 1, + .bcr_reg = CAMSS_VFE_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "camss_vfe_vfe_ocmemnoc_clk", + .ops = &clk_ops_branch, + CLK_INIT(camss_vfe_vfe_ocmemnoc_clk.c), + }, +}; + +static struct branch_clk mdss_ahb_clk = { + .cbcr_reg = MDSS_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_ahb_clk.c), + }, +}; + +static struct branch_clk mdss_axi_clk = { + .cbcr_reg = MDSS_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_axi_clk.c), + }, +}; + +static struct branch_clk mdss_byte0_clk = { + .cbcr_reg = MDSS_BYTE0_CBCR, + .parent = &byte0_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_byte0_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_byte0_clk.c), + }, +}; + +static struct branch_clk mdss_byte1_clk = { + .cbcr_reg = MDSS_BYTE1_CBCR, + .parent = &byte1_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_byte1_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_byte1_clk.c), + }, +}; + +static struct branch_clk mdss_edpaux_clk = { + .cbcr_reg = MDSS_EDPAUX_CBCR, + .parent = &edpaux_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_edpaux_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_edpaux_clk.c), + }, +}; + +static struct branch_clk mdss_edplink_clk = { + .cbcr_reg = MDSS_EDPLINK_CBCR, + .parent = &edplink_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_edplink_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_edplink_clk.c), + }, +}; + +static struct branch_clk mdss_edppixel_clk = { + .cbcr_reg = MDSS_EDPPIXEL_CBCR, + .parent = &edppixel_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_edppixel_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_edppixel_clk.c), + }, +}; + +static struct branch_clk mdss_esc0_clk = { + .cbcr_reg = MDSS_ESC0_CBCR, + .parent = &esc0_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_esc0_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_esc0_clk.c), + }, +}; + +static struct branch_clk mdss_esc1_clk = { + .cbcr_reg = MDSS_ESC1_CBCR, + .parent = &esc1_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_esc1_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_esc1_clk.c), + }, +}; + +static struct branch_clk mdss_extpclk_clk = { + .cbcr_reg = MDSS_EXTPCLK_CBCR, + .parent = &extpclk_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_extpclk_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_extpclk_clk.c), + }, +}; + +static struct branch_clk mdss_hdmi_ahb_clk = { + .cbcr_reg = MDSS_HDMI_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_hdmi_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_hdmi_ahb_clk.c), + }, +}; + +static struct branch_clk mdss_hdmi_clk = { + .cbcr_reg = MDSS_HDMI_CBCR, + .parent = &hdmi_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_hdmi_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_hdmi_clk.c), + }, +}; + +static struct branch_clk mdss_mdp_clk = { + .cbcr_reg = MDSS_MDP_CBCR, + .parent = &mdp_clk_src.c, + .has_sibling = 1, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_mdp_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_mdp_clk.c), + }, +}; + +static struct branch_clk mdss_mdp_lut_clk = { + .cbcr_reg = MDSS_MDP_LUT_CBCR, + .parent = &mdp_clk_src.c, + .has_sibling = 1, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_mdp_lut_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_mdp_lut_clk.c), + }, +}; + +static struct branch_clk mdss_pclk0_clk = { + .cbcr_reg = MDSS_PCLK0_CBCR, + .parent = &pclk0_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_pclk0_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_pclk0_clk.c), + }, +}; + +static struct branch_clk mdss_pclk1_clk = { + .cbcr_reg = MDSS_PCLK1_CBCR, + .parent = &pclk1_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_pclk1_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_pclk1_clk.c), + }, +}; + +static struct branch_clk mdss_vsync_clk = { + .cbcr_reg = MDSS_VSYNC_CBCR, + .parent = &vsync_clk_src.c, + .has_sibling = 0, + .bcr_reg = MDSS_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mdss_vsync_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdss_vsync_clk.c), + }, +}; + +static struct branch_clk mmss_misc_ahb_clk = { + .cbcr_reg = MMSS_MISC_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = MMSSNOCAHB_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_misc_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(mmss_misc_ahb_clk.c), + }, +}; + +static struct branch_clk mmss_mmssnoc_ahb_clk = { + .cbcr_reg = MMSS_MMSSNOC_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = MMSSNOCAHB_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_mmssnoc_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(mmss_mmssnoc_ahb_clk.c), + }, +}; + +static struct branch_clk mmss_mmssnoc_bto_ahb_clk = { + .cbcr_reg = MMSS_MMSSNOC_BTO_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = MMSSNOCAHB_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_mmssnoc_bto_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(mmss_mmssnoc_bto_ahb_clk.c), + }, +}; + +static struct branch_clk mmss_mmssnoc_axi_clk = { + .cbcr_reg = MMSS_MMSSNOC_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = MMSSNOCAXI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_mmssnoc_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(mmss_mmssnoc_axi_clk.c), + }, +}; + +static struct branch_clk mmss_s0_axi_clk = { + .cbcr_reg = MMSS_S0_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = MMSSNOCAXI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "mmss_s0_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(mmss_s0_axi_clk.c), + }, +}; + +static struct branch_clk venus0_ahb_clk = { + .cbcr_reg = VENUS0_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = VENUS0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "venus0_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(venus0_ahb_clk.c), + }, +}; + +static struct branch_clk venus0_axi_clk = { + .cbcr_reg = VENUS0_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = VENUS0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "venus0_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(venus0_axi_clk.c), + }, +}; + +static struct branch_clk venus0_ocmemnoc_clk = { + .cbcr_reg = VENUS0_OCMEMNOC_CBCR, + .has_sibling = 1, + .bcr_reg = VENUS0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "venus0_ocmemnoc_clk", + .ops = &clk_ops_branch, + CLK_INIT(venus0_ocmemnoc_clk.c), + }, +}; + +static struct branch_clk venus0_vcodec0_clk = { + .cbcr_reg = VENUS0_VCODEC0_CBCR, + .parent = &vcodec0_clk_src.c, + .has_sibling = 0, + .bcr_reg = VENUS0_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "venus0_vcodec0_clk", + .ops = &clk_ops_branch, + CLK_INIT(venus0_vcodec0_clk.c), + }, +}; + +static struct branch_clk oxili_gfx3d_clk = { + .cbcr_reg = OXILI_GFX3D_CBCR, + .has_sibling = 1, + .bcr_reg = OXILI_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "oxili_gfx3d_clk", + .ops = &clk_ops_branch, + CLK_INIT(oxili_gfx3d_clk.c), + }, +}; + +static struct branch_clk oxilicx_ahb_clk = { + .cbcr_reg = OXILICX_AHB_CBCR, + .parent = &ahb_clk_src.c, + .has_sibling = 1, + .bcr_reg = OXILICX_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "oxilicx_ahb_clk", + .ops = &clk_ops_branch, + CLK_INIT(oxilicx_ahb_clk.c), + }, +}; + +static struct branch_clk oxilicx_axi_clk = { + .cbcr_reg = OXILICX_AXI_CBCR, + .parent = &axi_clk_src.c, + .has_sibling = 1, + .bcr_reg = OXILICX_BCR, + .base = &virt_bases[MMSS_BASE], + .c = { + .dbg_name = "oxilicx_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(oxilicx_axi_clk.c), + }, +}; + +static struct clk_freq_tbl ftbl_audio_core_slimbus_core_clock[] = { + F_LPASS(28800000, lpapll0, 1, 15, 256), + F_END +}; + +static struct rcg_clk audio_core_slimbus_core_clk_src = { + .cmd_rcgr_reg = SLIMBUS_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_slimbus_core_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_slimbus_core_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 70000000, NOMINAL, 140000000), + CLK_INIT(audio_core_slimbus_core_clk_src.c), + }, +}; + +static struct branch_clk audio_core_slimbus_core_clk = { + .cbcr_reg = AUDIO_CORE_SLIMBUS_CORE_CBCR, + .parent = &audio_core_slimbus_core_clk_src.c, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_slimbus_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_slimbus_core_clk.c), + }, +}; + +static struct branch_clk audio_core_slimbus_lfabif_clk = { + .cbcr_reg = AUDIO_CORE_SLIMBUS_LFABIF_CBCR, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_slimbus_lfabif_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_slimbus_lfabif_clk.c), + }, +}; + +static struct clk_freq_tbl ftbl_audio_core_lpaif_clock[] = { + F_LPASS( 512000, lpapll0, 16, 1, 60), + F_LPASS( 768000, lpapll0, 16, 1, 40), + F_LPASS( 1024000, lpapll0, 16, 1, 30), + F_LPASS( 1536000, lpapll0, 16, 1, 10), + F_LPASS( 2048000, lpapll0, 16, 1, 15), + F_LPASS( 3072000, lpapll0, 16, 1, 10), + F_LPASS( 4096000, lpapll0, 15, 1, 8), + F_LPASS( 6144000, lpapll0, 10, 1, 8), + F_LPASS( 8192000, lpapll0, 15, 1, 4), + F_LPASS(12288000, lpapll0, 10, 1, 4), + F_END +}; + +static struct rcg_clk audio_core_lpaif_codec_spkr_clk_src = { + .cmd_rcgr_reg = LPAIF_SPKR_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_codec_spkr_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_codec_spkr_clk_src.c), + }, +}; + +static struct rcg_clk audio_core_lpaif_pri_clk_src = { + .cmd_rcgr_reg = LPAIF_PRI_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pri_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_pri_clk_src.c), + }, +}; + +static struct rcg_clk audio_core_lpaif_sec_clk_src = { + .cmd_rcgr_reg = LPAIF_SEC_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_sec_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_sec_clk_src.c), + }, +}; + +static struct rcg_clk audio_core_lpaif_ter_clk_src = { + .cmd_rcgr_reg = LPAIF_TER_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_ter_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_ter_clk_src.c), + }, +}; + +static struct rcg_clk audio_core_lpaif_quad_clk_src = { + .cmd_rcgr_reg = LPAIF_QUAD_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_quad_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_quad_clk_src.c), + }, +}; + +static struct rcg_clk audio_core_lpaif_pcm0_clk_src = { + .cmd_rcgr_reg = LPAIF_PCM0_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pcm0_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_pcm0_clk_src.c), + }, +}; + +static struct rcg_clk audio_core_lpaif_pcm1_clk_src = { + .cmd_rcgr_reg = LPAIF_PCM1_CMD_RCGR, + .set_rate = set_rate_mnd, + .freq_tbl = ftbl_audio_core_lpaif_clock, + .current_freq = &rcg_dummy_freq, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pcm1_clk_src", + .ops = &clk_ops_rcg_mnd, + VDD_DIG_FMAX_MAP2(LOW, 12000000, NOMINAL, 25000000), + CLK_INIT(audio_core_lpaif_pcm1_clk_src.c), + }, +}; + +static struct branch_clk audio_core_lpaif_codec_spkr_osr_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_CODEC_SPKR_OSR_CBCR, + .parent = &audio_core_lpaif_codec_spkr_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_codec_spkr_clk_src", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_codec_spkr_clk_src.c), + }, +}; + +static struct branch_clk audio_core_lpaif_codec_spkr_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_CODEC_SPKR_EBIT_CBCR, + .parent = &audio_core_lpaif_codec_spkr_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_codec_spkr_clk_src", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_codec_spkr_clk_src.c), + }, +}; + +static struct branch_clk audio_core_lpaif_codec_spkr_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_CODEC_SPKR_IBIT_CBCR, + .parent = &audio_core_lpaif_codec_spkr_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_codec_spkr_clk_src", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_codec_spkr_clk_src.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pri_osr_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PRI_OSR_CBCR, + .parent = &audio_core_lpaif_pri_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pri_osr_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pri_osr_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pri_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PRI_EBIT_CBCR, + .parent = &audio_core_lpaif_pri_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pri_ebit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pri_ebit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pri_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PRI_IBIT_CBCR, + .parent = &audio_core_lpaif_pri_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pri_ibit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pri_ibit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_sec_osr_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_SEC_OSR_CBCR, + .parent = &audio_core_lpaif_sec_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_sec_osr_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_sec_osr_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_sec_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_SEC_EBIT_CBCR, + .parent = &audio_core_lpaif_sec_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_sec_ebit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_sec_ebit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_sec_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_SEC_IBIT_CBCR, + .parent = &audio_core_lpaif_sec_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_sec_ibit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_sec_ibit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_ter_osr_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_TER_OSR_CBCR, + .parent = &audio_core_lpaif_ter_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_ter_osr_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_ter_osr_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_ter_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_TER_EBIT_CBCR, + .parent = &audio_core_lpaif_ter_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_ter_ebit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_ter_ebit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_ter_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_TER_IBIT_CBCR, + .parent = &audio_core_lpaif_ter_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_ter_ibit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_ter_ibit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_quad_osr_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_QUAD_OSR_CBCR, + .parent = &audio_core_lpaif_quad_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_quad_osr_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_quad_osr_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_quad_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_QUAD_EBIT_CBCR, + .parent = &audio_core_lpaif_quad_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_quad_ebit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_quad_ebit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_quad_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_QUAD_IBIT_CBCR, + .parent = &audio_core_lpaif_quad_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_quad_ibit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_quad_ibit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pcm0_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PCM0_EBIT_CBCR, + .parent = &audio_core_lpaif_pcm0_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pcm0_ebit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pcm0_ebit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pcm0_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PCM0_IBIT_CBCR, + .parent = &audio_core_lpaif_pcm0_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pcm0_ibit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pcm0_ibit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pcm1_ebit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PCM1_EBIT_CBCR, + .parent = &audio_core_lpaif_pcm1_clk_src.c, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pcm1_ebit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pcm1_ebit_clk.c), + }, +}; + +static struct branch_clk audio_core_lpaif_pcm1_ibit_clk = { + .cbcr_reg = AUDIO_CORE_LPAIF_PCM1_IBIT_CBCR, + .parent = &audio_core_lpaif_pcm1_clk_src.c, + .has_sibling = 1, + .max_div = 16, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "audio_core_lpaif_pcm1_ibit_clk", + .ops = &clk_ops_branch, + CLK_INIT(audio_core_lpaif_pcm1_ibit_clk.c), + }, +}; + +static struct branch_clk q6ss_ahb_lfabif_clk = { + .cbcr_reg = LPASS_Q6SS_AHB_LFABIF_CBCR, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "q6ss_ahb_lfabif_clk", + .ops = &clk_ops_branch, + CLK_INIT(q6ss_ahb_lfabif_clk.c), + }, +}; + +static struct branch_clk q6ss_xo_clk = { + .cbcr_reg = LPASS_Q6SS_XO_CBCR, + .bcr_reg = LPASS_Q6SS_BCR, + .has_sibling = 1, + .base = &virt_bases[LPASS_BASE], + .c = { + .dbg_name = "q6ss_xo_clk", + .ops = &clk_ops_branch, + CLK_INIT(q6ss_xo_clk.c), + }, +}; + +static struct branch_clk mss_xo_q6_clk = { + .cbcr_reg = MSS_XO_Q6_CBCR, + .bcr_reg = MSS_Q6SS_BCR, + .has_sibling = 1, + .base = &virt_bases[MSS_BASE], + .c = { + .dbg_name = "mss_xo_q6_clk", + .ops = &clk_ops_branch, + CLK_INIT(mss_xo_q6_clk.c), + .depends = &gcc_mss_cfg_ahb_clk.c, + }, +}; + +static struct branch_clk mss_bus_q6_clk = { + .cbcr_reg = MSS_BUS_Q6_CBCR, + .bcr_reg = MSS_Q6SS_BCR, + .has_sibling = 1, + .base = &virt_bases[MSS_BASE], + .c = { + .dbg_name = "mss_bus_q6_clk", + .ops = &clk_ops_branch, + CLK_INIT(mss_bus_q6_clk.c), + .depends = &gcc_mss_cfg_ahb_clk.c, + }, +}; + +#ifdef CONFIG_DEBUG_FS + +struct measure_mux_entry { + struct clk *c; + int base; + u32 debug_mux; +}; + +struct measure_mux_entry measure_mux[] = { + {&gcc_bam_dma_ahb_clk.c, GCC_BASE, 0x00e8}, + {&gcc_blsp1_ahb_clk.c, GCC_BASE, 0x0090}, + {&gcc_blsp1_qup1_i2c_apps_clk.c, GCC_BASE, 0x0093}, + {&gcc_blsp1_qup1_spi_apps_clk.c, GCC_BASE, 0x0092}, + {&gcc_blsp1_qup2_i2c_apps_clk.c, GCC_BASE, 0x0098}, + {&gcc_blsp1_qup2_spi_apps_clk.c, GCC_BASE, 0x0096}, + {&gcc_blsp1_qup3_i2c_apps_clk.c, GCC_BASE, 0x009c}, + {&gcc_blsp1_qup3_spi_apps_clk.c, GCC_BASE, 0x009b}, + {&gcc_blsp1_qup4_i2c_apps_clk.c, GCC_BASE, 0x00a1}, + {&gcc_blsp1_qup4_spi_apps_clk.c, GCC_BASE, 0x00a0}, + {&gcc_blsp1_qup5_i2c_apps_clk.c, GCC_BASE, 0x00a5}, + {&gcc_blsp1_qup5_spi_apps_clk.c, GCC_BASE, 0x00a4}, + {&gcc_blsp1_qup6_i2c_apps_clk.c, GCC_BASE, 0x00aa}, + {&gcc_blsp1_qup6_spi_apps_clk.c, GCC_BASE, 0x00a9}, + {&gcc_blsp1_uart1_apps_clk.c, GCC_BASE, 0x0094}, + {&gcc_blsp1_uart2_apps_clk.c, GCC_BASE, 0x0099}, + {&gcc_blsp1_uart3_apps_clk.c, GCC_BASE, 0x009d}, + {&gcc_blsp1_uart4_apps_clk.c, GCC_BASE, 0x00a2}, + {&gcc_blsp1_uart5_apps_clk.c, GCC_BASE, 0x00a6}, + {&gcc_blsp1_uart6_apps_clk.c, GCC_BASE, 0x00ab}, + {&gcc_blsp2_ahb_clk.c, GCC_BASE, 0x00b0}, + {&gcc_blsp2_qup1_i2c_apps_clk.c, GCC_BASE, 0x00b3}, + {&gcc_blsp2_qup1_spi_apps_clk.c, GCC_BASE, 0x00b2}, + {&gcc_blsp2_qup2_i2c_apps_clk.c, GCC_BASE, 0x00b8}, + {&gcc_blsp2_qup2_spi_apps_clk.c, GCC_BASE, 0x00b6}, + {&gcc_blsp2_qup3_i2c_apps_clk.c, GCC_BASE, 0x00bc}, + {&gcc_blsp2_qup3_spi_apps_clk.c, GCC_BASE, 0x00bb}, + {&gcc_blsp2_qup4_i2c_apps_clk.c, GCC_BASE, 0x00c1}, + {&gcc_blsp2_qup4_spi_apps_clk.c, GCC_BASE, 0x00c0}, + {&gcc_blsp2_qup5_i2c_apps_clk.c, GCC_BASE, 0x00c5}, + {&gcc_blsp2_qup5_spi_apps_clk.c, GCC_BASE, 0x00c4}, + {&gcc_blsp2_qup6_i2c_apps_clk.c, GCC_BASE, 0x00ca}, + {&gcc_blsp2_qup6_spi_apps_clk.c, GCC_BASE, 0x00c9}, + {&gcc_blsp2_uart1_apps_clk.c, GCC_BASE, 0x00b4}, + {&gcc_blsp2_uart2_apps_clk.c, GCC_BASE, 0x00b9}, + {&gcc_blsp2_uart3_apps_clk.c, GCC_BASE, 0x00bd}, + {&gcc_blsp2_uart4_apps_clk.c, GCC_BASE, 0x00c2}, + {&gcc_blsp2_uart5_apps_clk.c, GCC_BASE, 0x00c6}, + {&gcc_blsp2_uart6_apps_clk.c, GCC_BASE, 0x00cb}, + {&gcc_boot_rom_ahb_clk.c, GCC_BASE, 0x0100}, + {&gcc_mss_cfg_ahb_clk.c, GCC_BASE, 0x0030}, + {&gcc_ce1_clk.c, GCC_BASE, 0x0140}, + {&gcc_ce2_clk.c, GCC_BASE, 0x0148}, + {&gcc_pdm2_clk.c, GCC_BASE, 0x00da}, + {&gcc_pdm_ahb_clk.c, GCC_BASE, 0x00d8}, + {&gcc_prng_ahb_clk.c, GCC_BASE, 0x00e0}, + {&gcc_sdcc1_ahb_clk.c, GCC_BASE, 0x0071}, + {&gcc_sdcc1_apps_clk.c, GCC_BASE, 0x0070}, + {&gcc_sdcc2_ahb_clk.c, GCC_BASE, 0x0079}, + {&gcc_sdcc2_apps_clk.c, GCC_BASE, 0x0078}, + {&gcc_sdcc3_ahb_clk.c, GCC_BASE, 0x0081}, + {&gcc_sdcc3_apps_clk.c, GCC_BASE, 0x0080}, + {&gcc_sdcc4_ahb_clk.c, GCC_BASE, 0x0089}, + {&gcc_sdcc4_apps_clk.c, GCC_BASE, 0x0088}, + {&gcc_tsif_ahb_clk.c, GCC_BASE, 0x00f0}, + {&gcc_tsif_ref_clk.c, GCC_BASE, 0x00f1}, + {&gcc_usb30_master_clk.c, GCC_BASE, 0x0050}, + {&gcc_usb30_mock_utmi_clk.c, GCC_BASE, 0x0052}, + {&gcc_usb_hs_ahb_clk.c, GCC_BASE, 0x0069}, + {&gcc_usb_hs_system_clk.c, GCC_BASE, 0x0068}, + {&gcc_usb_hsic_ahb_clk.c, GCC_BASE, 0x0060}, + {&gcc_usb_hsic_clk.c, GCC_BASE, 0x0062}, + {&gcc_usb_hsic_io_cal_clk.c, GCC_BASE, 0x0063}, + {&gcc_usb_hsic_system_clk.c, GCC_BASE, 0x0061}, + {&mmss_mmssnoc_ahb_clk.c, MMSS_BASE, 0x0001}, + {&mmss_mmssnoc_axi_clk.c, MMSS_BASE, 0x0004}, + {&camss_cci_cci_ahb_clk.c, MMSS_BASE, 0x002e}, + {&camss_cci_cci_clk.c, MMSS_BASE, 0x002d}, + {&camss_csi0_ahb_clk.c, MMSS_BASE, 0x0042}, + {&camss_csi0_clk.c, MMSS_BASE, 0x0041}, + {&camss_csi0phy_clk.c, MMSS_BASE, 0x0043}, + {&camss_csi0pix_clk.c, MMSS_BASE, 0x0045}, + {&camss_csi0rdi_clk.c, MMSS_BASE, 0x0044}, + {&camss_csi1_ahb_clk.c, MMSS_BASE, 0x0047}, + {&camss_csi1_clk.c, MMSS_BASE, 0x0046}, + {&camss_csi1phy_clk.c, MMSS_BASE, 0x0048}, + {&camss_csi1pix_clk.c, MMSS_BASE, 0x004a}, + {&camss_csi1rdi_clk.c, MMSS_BASE, 0x0049}, + {&camss_csi2_ahb_clk.c, MMSS_BASE, 0x004c}, + {&camss_csi2_clk.c, MMSS_BASE, 0x004b}, + {&camss_csi2phy_clk.c, MMSS_BASE, 0x004d}, + {&camss_csi2pix_clk.c, MMSS_BASE, 0x004f}, + {&camss_csi2rdi_clk.c, MMSS_BASE, 0x004e}, + {&camss_csi3_ahb_clk.c, MMSS_BASE, 0x0051}, + {&camss_csi3_clk.c, MMSS_BASE, 0x0050}, + {&camss_csi3phy_clk.c, MMSS_BASE, 0x0052}, + {&camss_csi3pix_clk.c, MMSS_BASE, 0x0054}, + {&camss_csi3rdi_clk.c, MMSS_BASE, 0x0053}, + {&camss_csi_vfe0_clk.c, MMSS_BASE, 0x003f}, + {&camss_csi_vfe1_clk.c, MMSS_BASE, 0x0040}, + {&camss_gp0_clk.c, MMSS_BASE, 0x0027}, + {&camss_gp1_clk.c, MMSS_BASE, 0x0028}, + {&camss_ispif_ahb_clk.c, MMSS_BASE, 0x0055}, + {&camss_jpeg_jpeg0_clk.c, MMSS_BASE, 0x0032}, + {&camss_jpeg_jpeg1_clk.c, MMSS_BASE, 0x0033}, + {&camss_jpeg_jpeg2_clk.c, MMSS_BASE, 0x0034}, + {&camss_jpeg_jpeg_ahb_clk.c, MMSS_BASE, 0x0035}, + {&camss_jpeg_jpeg_axi_clk.c, MMSS_BASE, 0x0036}, + {&camss_jpeg_jpeg_ocmemnoc_clk.c, MMSS_BASE, 0x0037}, + {&camss_mclk0_clk.c, MMSS_BASE, 0x0029}, + {&camss_mclk1_clk.c, MMSS_BASE, 0x002a}, + {&camss_mclk2_clk.c, MMSS_BASE, 0x002b}, + {&camss_mclk3_clk.c, MMSS_BASE, 0x002c}, + {&camss_micro_ahb_clk.c, MMSS_BASE, 0x0026}, + {&camss_phy0_csi0phytimer_clk.c, MMSS_BASE, 0x002f}, + {&camss_phy1_csi1phytimer_clk.c, MMSS_BASE, 0x0030}, + {&camss_phy2_csi2phytimer_clk.c, MMSS_BASE, 0x0031}, + {&camss_top_ahb_clk.c, MMSS_BASE, 0x0025}, + {&camss_vfe_cpp_ahb_clk.c, MMSS_BASE, 0x003b}, + {&camss_vfe_cpp_clk.c, MMSS_BASE, 0x003a}, + {&camss_vfe_vfe0_clk.c, MMSS_BASE, 0x0038}, + {&camss_vfe_vfe1_clk.c, MMSS_BASE, 0x0039}, + {&camss_vfe_vfe_ahb_clk.c, MMSS_BASE, 0x003c}, + {&camss_vfe_vfe_axi_clk.c, MMSS_BASE, 0x003d}, + {&camss_vfe_vfe_ocmemnoc_clk.c, MMSS_BASE, 0x003e}, + {&mdss_ahb_clk.c, MMSS_BASE, 0x0022}, + {&mdss_hdmi_clk.c, MMSS_BASE, 0x001d}, + {&mdss_mdp_clk.c, MMSS_BASE, 0x0014}, + {&mdss_mdp_lut_clk.c, MMSS_BASE, 0x0015}, + {&mdss_axi_clk.c, MMSS_BASE, 0x0024}, + {&mdss_vsync_clk.c, MMSS_BASE, 0x001c}, + {&mdss_esc0_clk.c, MMSS_BASE, 0x0020}, + {&mdss_esc1_clk.c, MMSS_BASE, 0x0021}, + {&mdss_edpaux_clk.c, MMSS_BASE, 0x001b}, + {&mdss_byte0_clk.c, MMSS_BASE, 0x001e}, + {&mdss_byte1_clk.c, MMSS_BASE, 0x001f}, + {&mdss_edplink_clk.c, MMSS_BASE, 0x001a}, + {&mdss_edppixel_clk.c, MMSS_BASE, 0x0019}, + {&mdss_extpclk_clk.c, MMSS_BASE, 0x0018}, + {&mdss_hdmi_ahb_clk.c, MMSS_BASE, 0x0023}, + {&mdss_pclk0_clk.c, MMSS_BASE, 0x0016}, + {&mdss_pclk1_clk.c, MMSS_BASE, 0x0017}, + {&audio_core_lpaif_pri_clk_src.c, LPASS_BASE, 0x0017}, + {&audio_core_lpaif_sec_clk_src.c, LPASS_BASE, 0x0016}, + {&audio_core_lpaif_ter_clk_src.c, LPASS_BASE, 0x0015}, + {&audio_core_lpaif_quad_clk_src.c, LPASS_BASE, 0x0014}, + {&audio_core_lpaif_pcm0_clk_src.c, LPASS_BASE, 0x0013}, + {&audio_core_lpaif_pcm1_clk_src.c, LPASS_BASE, 0x0012}, + {&audio_core_slimbus_core_clk.c, LPASS_BASE, 0x003d}, + {&audio_core_slimbus_lfabif_clk.c, LPASS_BASE, 0x003e}, + {&q6ss_xo_clk.c, LPASS_BASE, 0x002b}, + {&q6ss_ahb_lfabif_clk.c, LPASS_BASE, 0x001e}, + {&mss_bus_q6_clk.c, MSS_BASE, 0x003c}, + {&mss_xo_q6_clk.c, MSS_BASE, 0x0007}, + + {&dummy_clk, N_BASES, 0x0000}, +}; + +static int measure_clk_set_parent(struct clk *c, struct clk *parent) +{ + struct measure_clk *clk = to_measure_clk(c); + unsigned long flags; + u32 regval, clk_sel, i; + + if (!parent) + return -EINVAL; + + for (i = 0; i < (ARRAY_SIZE(measure_mux) - 1); i++) + if (measure_mux[i].c == parent) + break; + + if (measure_mux[i].c == &dummy_clk) + return -EINVAL; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + /* + * Program the test vector, measurement period (sample_ticks) + * and scaling multiplier. + */ + clk->sample_ticks = 0x10000; + clk->multiplier = 1; + + writel_relaxed(0, MSS_REG_BASE(MSS_DEBUG_CLK_CTL_REG)); + writel_relaxed(0, LPASS_REG_BASE(LPASS_DEBUG_CLK_CTL_REG)); + writel_relaxed(0, MMSS_REG_BASE(MMSS_DEBUG_CLK_CTL_REG)); + writel_relaxed(0, GCC_REG_BASE(GCC_DEBUG_CLK_CTL_REG)); + + switch (measure_mux[i].base) { + + case GCC_BASE: + clk_sel = measure_mux[i].debug_mux; + break; + + case MMSS_BASE: + clk_sel = 0x02C; + regval = BVAL(11, 0, measure_mux[i].debug_mux); + writel_relaxed(regval, MMSS_REG_BASE(MMSS_DEBUG_CLK_CTL_REG)); + + /* Activate debug clock output */ + regval |= BIT(16); + writel_relaxed(regval, MMSS_REG_BASE(MMSS_DEBUG_CLK_CTL_REG)); + break; + + case LPASS_BASE: + clk_sel = 0x169; + regval = BVAL(11, 0, measure_mux[i].debug_mux); + writel_relaxed(regval, LPASS_REG_BASE(LPASS_DEBUG_CLK_CTL_REG)); + + /* Activate debug clock output */ + regval |= BIT(16); + writel_relaxed(regval, LPASS_REG_BASE(LPASS_DEBUG_CLK_CTL_REG)); + break; + + case MSS_BASE: + clk_sel = 0x32; + regval = BVAL(5, 0, measure_mux[i].debug_mux); + writel_relaxed(regval, MSS_REG_BASE(MSS_DEBUG_CLK_CTL_REG)); + break; + + default: + return -EINVAL; + } + + /* Set debug mux clock index */ + regval = BVAL(8, 0, clk_sel); + writel_relaxed(regval, GCC_REG_BASE(GCC_DEBUG_CLK_CTL_REG)); + + /* Activate debug clock output */ + regval |= BIT(16); + writel_relaxed(regval, GCC_REG_BASE(GCC_DEBUG_CLK_CTL_REG)); + + /* Make sure test vector is set before starting measurements. */ + mb(); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +/* Sample clock for 'ticks' reference clock ticks. */ +static u32 run_measurement(unsigned ticks) +{ + /* Stop counters and set the XO4 counter start value. */ + writel_relaxed(ticks, GCC_REG_BASE(CLOCK_FRQ_MEASURE_CTL_REG)); + + /* Wait for timer to become ready. */ + while ((readl_relaxed(GCC_REG_BASE(CLOCK_FRQ_MEASURE_STATUS_REG)) & + BIT(25)) != 0) + cpu_relax(); + + /* Run measurement and wait for completion. */ + writel_relaxed(BIT(20)|ticks, GCC_REG_BASE(CLOCK_FRQ_MEASURE_CTL_REG)); + while ((readl_relaxed(GCC_REG_BASE(CLOCK_FRQ_MEASURE_STATUS_REG)) & + BIT(25)) == 0) + cpu_relax(); + + /* Return measured ticks. */ + return readl_relaxed(GCC_REG_BASE(CLOCK_FRQ_MEASURE_STATUS_REG)) & + BM(24, 0); +} + +/* + * Perform a hardware rate measurement for a given clock. + * FOR DEBUG USE ONLY: Measurements take ~15 ms! + */ +static unsigned long measure_clk_get_rate(struct clk *c) +{ + unsigned long flags; + u32 gcc_xo4_reg_backup; + u64 raw_count_short, raw_count_full; + struct measure_clk *clk = to_measure_clk(c); + unsigned ret; + + ret = clk_prepare_enable(&cxo_clk_src.c); + if (ret) { + pr_warning("CXO clock failed to enable. Can't measure\n"); + return 0; + } + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Enable CXO/4 and RINGOSC branch. */ + gcc_xo4_reg_backup = readl_relaxed(GCC_REG_BASE(GCC_XO_DIV4_CBCR_REG)); + writel_relaxed(0x1, GCC_REG_BASE(GCC_XO_DIV4_CBCR_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(gcc_xo4_reg_backup, GCC_REG_BASE(GCC_XO_DIV4_CBCR_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)); + ret = (raw_count_full * clk->multiplier); + } + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + clk_disable_unprepare(&cxo_clk_src.c); + + return ret; +} +#else /* !CONFIG_DEBUG_FS */ +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + return -EINVAL; +} + +static unsigned long measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops clk_ops_measure = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, +}; + +static struct measure_clk measure_clk = { + .c = { + .dbg_name = "measure_clk", + .ops = &clk_ops_measure, + CLK_INIT(measure_clk.c), + }, + .multiplier = 1, +}; + +static struct clk_lookup msm_clocks_copper[] = { + CLK_LOOKUP("xo", cxo_clk_src.c, "msm_otg"), + CLK_LOOKUP("xo", cxo_clk_src.c, "pil-q6v5-lpass"), + CLK_LOOKUP("xo", cxo_clk_src.c, "pil_pronto"), + CLK_LOOKUP("measure", measure_clk.c, "debug"), + + CLK_LOOKUP("dma_bam_pclk", gcc_bam_dma_ahb_clk.c, "msm_sps"), + CLK_LOOKUP("iface_clk", gcc_blsp1_ahb_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("iface_clk", gcc_blsp1_ahb_clk.c, "spi_qsd.1"), + CLK_LOOKUP("core_clk", gcc_blsp1_qup1_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup1_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup2_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup2_spi_apps_clk.c, "spi_qsd.1"), + CLK_LOOKUP("core_clk", gcc_blsp1_qup3_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup3_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup4_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup4_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup5_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup5_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup6_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_qup6_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_uart1_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_uart2_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_uart3_apps_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", gcc_blsp1_uart4_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_uart5_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp1_uart6_apps_clk.c, ""), + + CLK_LOOKUP("iface_clk", gcc_blsp2_ahb_clk.c, "f9966000.i2c"), + CLK_LOOKUP("iface_clk", gcc_blsp2_ahb_clk.c, "f995e000.serial"), + CLK_LOOKUP("core_clk", gcc_blsp2_qup1_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup1_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup2_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup2_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup3_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup3_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup4_i2c_apps_clk.c, "f9966000.i2c"), + CLK_LOOKUP("core_clk", gcc_blsp2_qup4_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup5_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup5_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup6_i2c_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_qup6_spi_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_uart1_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_uart2_apps_clk.c, "f995e000.serial"), + CLK_LOOKUP("core_clk", gcc_blsp2_uart3_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_uart4_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_uart5_apps_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_blsp2_uart6_apps_clk.c, ""), + + CLK_LOOKUP("core_clk", gcc_ce1_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_ce2_clk.c, ""), + CLK_LOOKUP("iface_clk", gcc_ce1_ahb_clk.c, ""), + CLK_LOOKUP("iface_clk", gcc_ce2_ahb_clk.c, ""), + CLK_LOOKUP("bus_clk", gcc_ce1_axi_clk.c, ""), + CLK_LOOKUP("bus_clk", gcc_ce2_axi_clk.c, ""), + + CLK_LOOKUP("core_clk", gcc_gp1_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_gp2_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_gp3_clk.c, ""), + + CLK_LOOKUP("core_clk", gcc_pdm2_clk.c, ""), + CLK_LOOKUP("iface_clk", gcc_pdm_ahb_clk.c, ""), + CLK_LOOKUP("iface_clk", gcc_prng_ahb_clk.c, ""), + + CLK_LOOKUP("iface_clk", gcc_sdcc1_ahb_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", gcc_sdcc1_apps_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", gcc_sdcc2_ahb_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", gcc_sdcc2_apps_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", gcc_sdcc3_ahb_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", gcc_sdcc3_apps_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", gcc_sdcc4_ahb_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", gcc_sdcc4_apps_clk.c, "msm_sdcc.4"), + + CLK_LOOKUP("iface_clk", gcc_tsif_ahb_clk.c, ""), + CLK_LOOKUP("ref_clk", gcc_tsif_ref_clk.c, ""), + + CLK_LOOKUP("core_clk", gcc_usb30_master_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_usb30_mock_utmi_clk.c, ""), + CLK_LOOKUP("iface_clk", gcc_usb_hs_ahb_clk.c, "msm_otg"), + CLK_LOOKUP("core_clk", gcc_usb_hs_system_clk.c, ""), + CLK_LOOKUP("iface_clk", gcc_usb_hsic_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_usb_hsic_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_usb_hsic_io_cal_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_usb_hsic_system_clk.c, ""), + + /* Multimedia clocks */ + CLK_LOOKUP("bus_clk_src", axi_clk_src.c, ""), + CLK_LOOKUP("bus_clk_src", ahb_clk_src.c, ""), + CLK_LOOKUP("bus_clk", mmss_mmssnoc_ahb_clk.c, ""), + CLK_LOOKUP("bus_clk", mmss_mmssnoc_axi_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_edpaux_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_edppixel_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_esc0_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_esc1_clk.c, ""), + CLK_LOOKUP("iface_clk", mdss_hdmi_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_hdmi_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_mdp_clk.c, ""), + CLK_LOOKUP("core_clk", mdss_mdp_lut_clk.c, ""), + CLK_LOOKUP("core_clk", mdp_clk_src.c, ""), + CLK_LOOKUP("core_clk", mdss_vsync_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_cci_cci_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", camss_cci_cci_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_csi0_ahb_clk.c, ""), + CLK_LOOKUP("camss_csi0_clk", camss_csi0_clk.c, ""), + CLK_LOOKUP("camss_csi0phy_clk", camss_csi0phy_clk.c, ""), + CLK_LOOKUP("camss_csi0pix_clk", camss_csi0pix_clk.c, ""), + CLK_LOOKUP("camss_csi0rdi_clk", camss_csi0rdi_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_csi1_ahb_clk.c, ""), + CLK_LOOKUP("camss_csi1_clk", camss_csi1_clk.c, ""), + CLK_LOOKUP("camss_csi1phy_clk", camss_csi1phy_clk.c, ""), + CLK_LOOKUP("camss_csi1pix_clk", camss_csi1pix_clk.c, ""), + CLK_LOOKUP("camss_csi1rdi_clk", camss_csi1rdi_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_csi2_ahb_clk.c, ""), + CLK_LOOKUP("camss_csi2_clk", camss_csi2_clk.c, ""), + CLK_LOOKUP("camss_csi2phy_clk", camss_csi2phy_clk.c, ""), + CLK_LOOKUP("camss_csi2pix_clk", camss_csi2pix_clk.c, ""), + CLK_LOOKUP("camss_csi2rdi_clk", camss_csi2rdi_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_csi3_ahb_clk.c, ""), + CLK_LOOKUP("camss_csi3_clk", camss_csi3_clk.c, ""), + CLK_LOOKUP("camss_csi3phy_clk", camss_csi3phy_clk.c, ""), + CLK_LOOKUP("camss_csi3pix_clk", camss_csi3pix_clk.c, ""), + CLK_LOOKUP("camss_csi3rdi_clk", camss_csi3rdi_clk.c, ""), + CLK_LOOKUP("camss_csi0_clk_src", csi0_clk_src.c, ""), + CLK_LOOKUP("camss_csi1_clk_src", csi1_clk_src.c, ""), + CLK_LOOKUP("camss_csi2_clk_src", csi2_clk_src.c, ""), + CLK_LOOKUP("camss_csi3_clk_src", csi3_clk_src.c, ""), + CLK_LOOKUP("camss_csi_vfe0_clk", camss_csi_vfe0_clk.c, ""), + CLK_LOOKUP("camss_csi_vfe1_clk", camss_csi_vfe1_clk.c, ""), + CLK_LOOKUP("core_clk", camss_gp0_clk.c, ""), + CLK_LOOKUP("core_clk", camss_gp1_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_ispif_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", camss_jpeg_jpeg0_clk.c, ""), + CLK_LOOKUP("core_clk", camss_jpeg_jpeg1_clk.c, ""), + CLK_LOOKUP("core_clk", camss_jpeg_jpeg2_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_jpeg_jpeg_ahb_clk.c, + "fda64000.qcom,iommu"), + CLK_LOOKUP("core_clk", camss_jpeg_jpeg_axi_clk.c, + "fda64000.qcom,iommu"), + CLK_LOOKUP("bus_clk", camss_jpeg_jpeg_axi_clk.c, ""), + CLK_LOOKUP("bus_clk", camss_jpeg_jpeg_ocmemnoc_clk.c, ""), + CLK_LOOKUP("core_clk", camss_mclk0_clk.c, ""), + CLK_LOOKUP("core_clk", camss_mclk1_clk.c, ""), + CLK_LOOKUP("core_clk", camss_mclk2_clk.c, ""), + CLK_LOOKUP("core_clk", camss_mclk3_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_micro_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", camss_phy0_csi0phytimer_clk.c, ""), + CLK_LOOKUP("core_clk", camss_phy1_csi1phytimer_clk.c, ""), + CLK_LOOKUP("core_clk", camss_phy2_csi2phytimer_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_top_ahb_clk.c, ""), + CLK_LOOKUP("iface_clk", camss_vfe_cpp_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", camss_vfe_cpp_clk.c, ""), + CLK_LOOKUP("camss_vfe_vfe0_clk", camss_vfe_vfe0_clk.c, ""), + CLK_LOOKUP("camss_vfe_vfe1_clk", camss_vfe_vfe1_clk.c, ""), + CLK_LOOKUP("vfe0_clk_src", vfe0_clk_src.c, ""), + CLK_LOOKUP("vfe1_clk_src", vfe1_clk_src.c, ""), + CLK_LOOKUP("iface_clk", camss_vfe_vfe_ahb_clk.c, ""), + CLK_LOOKUP("bus_clk", camss_vfe_vfe_axi_clk.c, ""), + CLK_LOOKUP("bus_clk", camss_vfe_vfe_ocmemnoc_clk.c, ""), + CLK_LOOKUP("iface_clk", mdss_ahb_clk.c, "fd928000.qcom,iommu"), + CLK_LOOKUP("core_clk", mdss_axi_clk.c, "fd928000.qcom,iommu"), + CLK_LOOKUP("bus_clk", mdss_axi_clk.c, ""), + CLK_LOOKUP("core_clk", oxili_gfx3d_clk.c, ""), + CLK_LOOKUP("iface_clk", oxilicx_ahb_clk.c, ""), + CLK_LOOKUP("bus_clk", oxilicx_axi_clk.c, ""), + CLK_LOOKUP("iface_clk", venus0_ahb_clk.c, "fdc84000.qcom,iommu"), + CLK_LOOKUP("core_clk", venus0_axi_clk.c, "fdc84000.qcom,iommu"), + CLK_LOOKUP("bus_clk", venus0_axi_clk.c, ""), + + /* LPASS clocks */ + CLK_LOOKUP("core_clk", audio_core_slimbus_core_clk.c, "fe12f000.slim"), + CLK_LOOKUP("iface_clk", audio_core_slimbus_lfabif_clk.c, + "fe12f000.slim"), + CLK_LOOKUP("core_clk", audio_core_lpaif_codec_spkr_clk_src.c, ""), + CLK_LOOKUP("osr_clk", audio_core_lpaif_codec_spkr_osr_clk.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_codec_spkr_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_codec_spkr_ibit_clk.c, ""), + CLK_LOOKUP("core_clk", audio_core_lpaif_pri_clk_src.c, ""), + CLK_LOOKUP("osr_clk", audio_core_lpaif_pri_osr_clk.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_pri_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_pri_ibit_clk.c, ""), + CLK_LOOKUP("core_clk", audio_core_lpaif_sec_clk_src.c, ""), + CLK_LOOKUP("osr_clk", audio_core_lpaif_sec_osr_clk.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_sec_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_sec_ibit_clk.c, ""), + CLK_LOOKUP("core_clk", audio_core_lpaif_ter_clk_src.c, ""), + CLK_LOOKUP("osr_clk", audio_core_lpaif_ter_osr_clk.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_ter_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_ter_ibit_clk.c, ""), + CLK_LOOKUP("core_clk", audio_core_lpaif_quad_clk_src.c, ""), + CLK_LOOKUP("osr_clk", audio_core_lpaif_quad_osr_clk.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_quad_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_quad_ibit_clk.c, ""), + CLK_LOOKUP("core_clk", audio_core_lpaif_pcm0_clk_src.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_pcm0_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_pcm0_ibit_clk.c, ""), + CLK_LOOKUP("core_clk", audio_core_lpaif_pcm1_clk_src.c, ""), + CLK_LOOKUP("ebit_clk", audio_core_lpaif_pcm1_ebit_clk.c, ""), + CLK_LOOKUP("ibit_clk", audio_core_lpaif_pcm1_ibit_clk.c, ""), + + CLK_LOOKUP("core_clk", mss_xo_q6_clk.c, ""), + CLK_LOOKUP("bus_clk", mss_bus_q6_clk.c, ""), + CLK_LOOKUP("core_clk", q6ss_xo_clk.c, "pil-q6v5-lpass"), + CLK_LOOKUP("bus_clk", q6ss_ahb_lfabif_clk.c, "pil-q6v5-lpass"), + CLK_LOOKUP("mem_clk", gcc_boot_rom_ahb_clk.c, ""), + CLK_LOOKUP("bus_clk", gcc_mss_cfg_ahb_clk.c, ""), + CLK_LOOKUP("core_clk", gcc_prng_ahb_clk.c, "msm_rng"), + + /* TODO: Remove dummy clocks as soon as they become unnecessary */ + CLK_DUMMY("phy_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("core_clk", NULL, "msm_otg", OFF), + CLK_DUMMY("dfab_clk", DFAB_CLK, "msm_sps", OFF), + CLK_DUMMY("mem_clk", NULL, "msm_sps", OFF), + CLK_DUMMY("bus_clk", NULL, "scm", OFF), +}; + +static struct pll_config_regs gpll0_regs __initdata = { + .l_reg = (void __iomem *)GPLL0_L_REG, + .m_reg = (void __iomem *)GPLL0_M_REG, + .n_reg = (void __iomem *)GPLL0_N_REG, + .config_reg = (void __iomem *)GPLL0_USER_CTL_REG, + .mode_reg = (void __iomem *)GPLL0_MODE_REG, + .base = &virt_bases[GCC_BASE], +}; + +/* GPLL0 at 600 MHz, main output enabled. */ +static struct pll_config gpll0_config __initdata = { + .l = 0x1f, + .m = 0x1, + .n = 0x4, + .vco_val = 0x0, + .vco_mask = BM(21, 20), + .pre_div_val = 0x0, + .pre_div_mask = BM(14, 12), + .post_div_val = 0x0, + .post_div_mask = BM(9, 8), + .mn_ena_val = BIT(24), + .mn_ena_mask = BIT(24), + .main_output_val = BIT(0), + .main_output_mask = BIT(0), +}; + +static struct pll_config_regs gpll1_regs __initdata = { + .l_reg = (void __iomem *)GPLL1_L_REG, + .m_reg = (void __iomem *)GPLL1_M_REG, + .n_reg = (void __iomem *)GPLL1_N_REG, + .config_reg = (void __iomem *)GPLL1_USER_CTL_REG, + .mode_reg = (void __iomem *)GPLL1_MODE_REG, + .base = &virt_bases[GCC_BASE], +}; + +/* GPLL1 at 480 MHz, main output enabled. */ +static struct pll_config gpll1_config __initdata = { + .l = 0x19, + .m = 0x0, + .n = 0x1, + .vco_val = 0x0, + .vco_mask = BM(21, 20), + .pre_div_val = 0x0, + .pre_div_mask = BM(14, 12), + .post_div_val = 0x0, + .post_div_mask = BM(9, 8), + .main_output_val = BIT(0), + .main_output_mask = BIT(0), +}; + +static struct pll_config_regs mmpll0_regs __initdata = { + .l_reg = (void __iomem *)MMPLL0_L_REG, + .m_reg = (void __iomem *)MMPLL0_M_REG, + .n_reg = (void __iomem *)MMPLL0_N_REG, + .config_reg = (void __iomem *)MMPLL0_USER_CTL_REG, + .mode_reg = (void __iomem *)MMPLL0_MODE_REG, + .base = &virt_bases[MMSS_BASE], +}; + +/* MMPLL0 at 800 MHz, main output enabled. */ +static struct pll_config mmpll0_config __initdata = { + .l = 0x29, + .m = 0x2, + .n = 0x3, + .vco_val = 0x0, + .vco_mask = BM(21, 20), + .pre_div_val = 0x0, + .pre_div_mask = BM(14, 12), + .post_div_val = 0x0, + .post_div_mask = BM(9, 8), + .mn_ena_val = BIT(24), + .mn_ena_mask = BIT(24), + .main_output_val = BIT(0), + .main_output_mask = BIT(0), +}; + +static struct pll_config_regs mmpll1_regs __initdata = { + .l_reg = (void __iomem *)MMPLL1_L_REG, + .m_reg = (void __iomem *)MMPLL1_M_REG, + .n_reg = (void __iomem *)MMPLL1_N_REG, + .config_reg = (void __iomem *)MMPLL1_USER_CTL_REG, + .mode_reg = (void __iomem *)MMPLL1_MODE_REG, + .base = &virt_bases[MMSS_BASE], +}; + +/* MMPLL1 at 1000 MHz, main output enabled. */ +static struct pll_config mmpll1_config __initdata = { + .l = 0x34, + .m = 0x1, + .n = 0xC, + .vco_val = 0x0, + .vco_mask = BM(21, 20), + .pre_div_val = 0x0, + .pre_div_mask = BM(14, 12), + .post_div_val = 0x0, + .post_div_mask = BM(9, 8), + .mn_ena_val = BIT(24), + .mn_ena_mask = BIT(24), + .main_output_val = BIT(0), + .main_output_mask = BIT(0), +}; + +static struct pll_config_regs mmpll3_regs __initdata = { + .l_reg = (void __iomem *)MMPLL3_L_REG, + .m_reg = (void __iomem *)MMPLL3_M_REG, + .n_reg = (void __iomem *)MMPLL3_N_REG, + .config_reg = (void __iomem *)MMPLL3_USER_CTL_REG, + .mode_reg = (void __iomem *)MMPLL3_MODE_REG, + .base = &virt_bases[MMSS_BASE], +}; + +/* MMPLL3 at 820 MHz, main output enabled. */ +static struct pll_config mmpll3_config __initdata = { + .l = 0x2A, + .m = 0x11, + .n = 0x18, + .vco_val = 0x0, + .vco_mask = BM(21, 20), + .pre_div_val = 0x0, + .pre_div_mask = BM(14, 12), + .post_div_val = 0x0, + .post_div_mask = BM(9, 8), + .mn_ena_val = BIT(24), + .mn_ena_mask = BIT(24), + .main_output_val = BIT(0), + .main_output_mask = BIT(0), +}; + +static struct pll_config_regs lpapll0_regs __initdata = { + .l_reg = (void __iomem *)LPAPLL_L_REG, + .m_reg = (void __iomem *)LPAPLL_M_REG, + .n_reg = (void __iomem *)LPAPLL_N_REG, + .config_reg = (void __iomem *)LPAPLL_USER_CTL_REG, + .mode_reg = (void __iomem *)LPAPLL_MODE_REG, + .base = &virt_bases[LPASS_BASE], +}; + +/* LPAPLL0 at 491.52 MHz, main output enabled. */ +static struct pll_config lpapll0_config __initdata = { + .l = 0x33, + .m = 0x1, + .n = 0x5, + .vco_val = 0x0, + .vco_mask = BM(21, 20), + .pre_div_val = BVAL(14, 12, 0x1), + .pre_div_mask = BM(14, 12), + .post_div_val = 0x0, + .post_div_mask = BM(9, 8), + .mn_ena_val = BIT(24), + .mn_ena_mask = BIT(24), + .main_output_val = BIT(0), + .main_output_mask = BIT(0), +}; + +#define PLL_AUX_OUTPUT BIT(1) + +static void __init reg_init(void) +{ + u32 regval; + + if (!(readl_relaxed(GCC_REG_BASE(GPLL0_STATUS_REG)) + & gpll0_clk_src.status_mask)) + configure_pll(&gpll0_config, &gpll0_regs, 1); + + if (!(readl_relaxed(GCC_REG_BASE(GPLL1_STATUS_REG)) + & gpll1_clk_src.status_mask)) + configure_pll(&gpll1_config, &gpll1_regs, 1); + + configure_pll(&mmpll0_config, &mmpll0_regs, 1); + configure_pll(&mmpll1_config, &mmpll1_regs, 1); + configure_pll(&mmpll3_config, &mmpll3_regs, 0); + configure_pll(&lpapll0_config, &lpapll0_regs, 1); + + /* Active GPLL0's aux output. This is needed by acpuclock. */ + regval = readl_relaxed(GCC_REG_BASE(GPLL0_USER_CTL_REG)); + regval |= BIT(PLL_AUX_OUTPUT); + writel_relaxed(regval, GCC_REG_BASE(GPLL0_USER_CTL_REG)); + + /* Vote for GPLL0 to turn on. Needed by acpuclock. */ + regval = readl_relaxed(GCC_REG_BASE(APCS_GPLL_ENA_VOTE_REG)); + regval |= BIT(0); + writel_relaxed(regval, GCC_REG_BASE(APCS_GPLL_ENA_VOTE_REG)); + + /* + * TODO: Confirm that no clocks need to be voted on in this sleep vote + * register. + */ + writel_relaxed(0x0, GCC_REG_BASE(APCS_CLOCK_SLEEP_ENA_VOTE)); +} + +static void __init msmcopper_clock_post_init(void) +{ + clk_set_rate(&ahb_clk_src.c, 80000000); + clk_set_rate(&axi_clk_src.c, 333330000); + + /* Set rates for single-rate clocks. */ + clk_set_rate(&usb30_master_clk_src.c, + usb30_master_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&tsif_ref_clk_src.c, + tsif_ref_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&usb_hs_system_clk_src.c, + usb_hs_system_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&usb_hsic_clk_src.c, + usb_hsic_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&usb_hsic_io_cal_clk_src.c, + usb_hsic_io_cal_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&usb_hsic_system_clk_src.c, + usb_hsic_system_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&usb30_mock_utmi_clk_src.c, + usb30_mock_utmi_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&pdm2_clk_src.c, pdm2_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&cci_clk_src.c, cci_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&mclk0_clk_src.c, mclk0_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&mclk1_clk_src.c, mclk1_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&mclk2_clk_src.c, mclk2_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&edpaux_clk_src.c, edpaux_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&esc0_clk_src.c, esc0_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&esc1_clk_src.c, esc1_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&hdmi_clk_src.c, hdmi_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&vsync_clk_src.c, vsync_clk_src.freq_tbl[0].freq_hz); + clk_set_rate(&audio_core_slimbus_core_clk_src.c, + audio_core_slimbus_core_clk_src.freq_tbl[0].freq_hz); +} + +#define GCC_CC_PHYS 0xFC400000 +#define GCC_CC_SIZE SZ_16K + +#define MMSS_CC_PHYS 0xFD8C0000 +#define MMSS_CC_SIZE SZ_256K + +#define LPASS_CC_PHYS 0xFE000000 +#define LPASS_CC_SIZE SZ_256K + +#define MSS_CC_PHYS 0xFC980000 +#define MSS_CC_SIZE SZ_16K + +static void __init msmcopper_clock_pre_init(void) +{ + virt_bases[GCC_BASE] = ioremap(GCC_CC_PHYS, GCC_CC_SIZE); + if (!virt_bases[GCC_BASE]) + panic("clock-copper: Unable to ioremap GCC memory!"); + + virt_bases[MMSS_BASE] = ioremap(MMSS_CC_PHYS, MMSS_CC_SIZE); + if (!virt_bases[MMSS_BASE]) + panic("clock-copper: Unable to ioremap MMSS_CC memory!"); + + virt_bases[LPASS_BASE] = ioremap(LPASS_CC_PHYS, LPASS_CC_SIZE); + if (!virt_bases[LPASS_BASE]) + panic("clock-copper: Unable to ioremap LPASS_CC memory!"); + + virt_bases[MSS_BASE] = ioremap(MSS_CC_PHYS, MSS_CC_SIZE); + if (!virt_bases[MSS_BASE]) + panic("clock-copper: Unable to ioremap MSS_CC memory!"); + + clk_ops_local_pll.enable = copper_pll_clk_enable; + + reg_init(); +} + +struct clock_init_data msmcopper_clock_init_data __initdata = { + .table = msm_clocks_copper, + .size = ARRAY_SIZE(msm_clocks_copper), + .pre_init = msmcopper_clock_pre_init, + .post_init = msmcopper_clock_post_init, +}; diff --git a/arch/arm/mach-msm/clock-debug.c b/arch/arm/mach-msm/clock-debug.c index 4886404d42f587533c175e5474fdedcc4ccd680b..e8c3e0552bc791f354d84891fab9eb5959373abb 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-2012, 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,12 @@ 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) - 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); + ret = clk_set_rate(clock, val); + if (ret) + pr_err("clk_set_rate failed (%d)\n", ret); + return ret; } @@ -49,15 +50,51 @@ 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) +{ + struct clk *clock = data; + int ret, is_hw_gated; + + /* Check to see if the clock is in hardware gating mode */ + if (clock->flags & CLKFLAG_HWCG) + is_hw_gated = clock->ops->in_hwcg_mode(clock); + else + is_hw_gated = 0; + + ret = clk_set_parent(measure, clock); + if (!ret) { + /* + * Disable hw gating to get accurate rate measurements. Only do + * this if the clock is explictly enabled by software. This + * allows us to detect errors where clocks are on even though + * software is not requesting them to be on due to broken + * hardware gating signals. + */ + if (is_hw_gated && clock->count) + clock->ops->disable_hwcg(clock); + *val = clk_get_rate(measure); + /* Reenable hwgating if it was disabled */ + if (is_hw_gated && clock->count) + clock->ops->enable_hwcg(clock); + } + + 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_prepare_enable(clock); else - clock->ops->disable(clock->id); + clk_disable_unprepare(clock); return rc; } @@ -65,20 +102,28 @@ 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); + if (!clock->ops->is_local) + *val = true; + else + *val = clock->ops->is_local(clock); return 0; } @@ -86,16 +131,114 @@ static int clock_debug_local_get(void *data, u64 *val) DEFINE_SIMPLE_ATTRIBUTE(clock_local_fops, clock_debug_local_get, NULL, "%llu\n"); +static int clock_debug_hwcg_get(void *data, u64 *val) +{ + struct clk *clock = data; + *val = !!(clock->flags & CLKFLAG_HWCG); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_hwcg_fops, clock_debug_hwcg_get, + NULL, "%llu\n"); + static struct dentry *debugfs_base; +static u32 debug_suspend; +static struct clk_lookup *msm_clocks; +static size_t num_msm_clocks; -int __init clock_debug_init(void) +int __init clock_debug_init(struct clock_init_data *data) { 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 = data->table; + num_msm_clocks = data->size; + + measure = clk_get_sys("debug", "measure"); + if (IS_ERR(measure)) + measure = NULL; + return 0; } + +static int clock_debug_print_clock(struct clk *c) +{ + size_t ln = 0; + char s[128]; + + if (!c || !c->count) + return 0; + + ln += snprintf(s, sizeof(s), "\t%s", c->dbg_name); + while (ln < sizeof(s) && (c = clk_get_parent(c))) + ln += snprintf(s + ln, sizeof(s) - ln, " -> %s", c->dbg_name); + pr_info("%s\n", s); + return 1; +} + +void clock_debug_print_enabled(void) +{ + unsigned i; + int cnt = 0; + + if (likely(!debug_suspend)) + return; + + pr_info("Enabled clocks:\n"); + for (i = 0; i < num_msm_clocks; i++) + cnt += clock_debug_print_clock(msm_clocks[i].clk); + + 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, level, fmax = 0, i = 0; + + /* Find max frequency supported within voltage constraints. */ + if (!clock->vdd_class) { + fmax = INT_MAX; + } else { + for (level = 0; level < ARRAY_SIZE(clock->fmax); level++) + if (clock->fmax[level]) + fmax = clock->fmax[level]; + } + + /* + * List supported frequencies <= fmax. Higher frequencies may appear in + * the frequency table, but are not valid and should not be listed. + */ + while ((rate = clock->ops->list_rate(clock, i++)) >= 0) { + if (rate <= fmax) + seq_printf(m, "%u\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; @@ -104,7 +247,7 @@ int __init clock_debug_add(struct clk *clock) if (!debugfs_base) return -ENOMEM; - strncpy(temp, clock->dbg_name, ARRAY_SIZE(temp)-1); + strlcpy(temp, clock->dbg_name, ARRAY_SIZE(temp)); for (ptr = temp; *ptr; ptr++) *ptr = tolower(*ptr); @@ -123,6 +266,22 @@ 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 (!debugfs_create_file("has_hw_gating", S_IRUGO, clk_dir, clock, + &clock_hwcg_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 0000000000000000000000000000000000000000..8331899a96f7e28bd25782af015a4bc267cb2f02 --- /dev/null +++ b/arch/arm/mach-msm/clock-dss-8960.c @@ -0,0 +1,330 @@ +/* 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); + /* Make sure iface clock is enabled before register access */ + mb(); + } + + /* Assert PLL S/W reset */ + writel_relaxed(0x8D, HDMI_PHY_PLL_LOCKDET_CFG2); + writel_relaxed(0x10, HDMI_PHY_PLL_LOCKDET_CFG0); + writel_relaxed(0x1A, HDMI_PHY_PLL_LOCKDET_CFG1); + /* De-assert PLL S/W reset */ + writel_relaxed(0x0D, HDMI_PHY_PLL_LOCKDET_CFG2); + + val = readl_relaxed(HDMI_PHY_REG_12); + val |= BIT(5); + /* Assert PHY S/W reset */ + writel_relaxed(val, HDMI_PHY_REG_12); + val &= ~BIT(5); + /* De-assert PHY S/W reset */ + writel_relaxed(val, HDMI_PHY_REG_12); + writel_relaxed(0x3f, HDMI_PHY_REG_2); + + val = readl_relaxed(HDMI_PHY_REG_12); + val |= PWRDN_B; + writel_relaxed(val, HDMI_PHY_REG_12); + /* Wait 10 us for enabling global power for PHY */ + mb(); + udelay(10); + + val = readl_relaxed(HDMI_PHY_PLL_PWRDN_B); + val |= PLL_PWRDN_B; + val &= ~PD_PLL; + writel_relaxed(val, HDMI_PHY_PLL_PWRDN_B); + writel_relaxed(0x80, HDMI_PHY_REG_2); + + 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_REG_12); + val &= (~PWRDN_B); + writel_relaxed(val, HDMI_PHY_REG_12); + + val = readl_relaxed(HDMI_PHY_PLL_PWRDN_B); + val |= PD_PLL; + val &= (~PLL_PWRDN_B); + writel_relaxed(val, HDMI_PHY_PLL_PWRDN_B); + /* Make sure HDMI PHY/PLL are powered down */ + 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; + 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); + /* Make sure iface clock is enabled before register access */ + mb(); + } + + 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(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(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(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(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); + break; + } + + /* Make sure writes complete before disabling iface clock */ + 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 0000000000000000000000000000000000000000..4734cdea9ba6668feeb737867d0c2edddc49eb2f --- /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 0000000000000000000000000000000000000000..54c9de80916b23f78cb4ca41ccd17afa44e46648 --- /dev/null +++ b/arch/arm/mach-msm/clock-dummy.c @@ -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. + */ + +#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 long rate) +{ + return 0; +} + +static int dummy_clk_set_max_rate(struct clk *clk, unsigned long rate) +{ + return 0; +} + +static int dummy_clk_set_flags(struct clk *clk, unsigned flags) +{ + return 0; +} + +static unsigned long dummy_clk_get_rate(struct clk *clk) +{ + return 0; +} + +static long dummy_clk_round_rate(struct clk *clk, unsigned long rate) +{ + return rate; +} + +static struct clk_ops clk_ops_dummy = { + .reset = dummy_clk_reset, + .set_rate = dummy_clk_set_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, +}; + +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 0000000000000000000000000000000000000000..13a5b65829fbbf75d8406f3ac0571aa7de37fc98 --- /dev/null +++ b/arch/arm/mach-msm/clock-fsm9xxx.c @@ -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. + * + */ +#include +#include + +#include + +#include "clock.h" + +/* + * Clocks + */ + +static struct clk_lookup msm_clocks_fsm9xxx[] = { + CLK_DUMMY("core_clk", ADM0_CLK, "msm_dmov", OFF), + CLK_DUMMY("core_clk", UART1_CLK, "msm_serial.0", OFF), + CLK_DUMMY("core_clk", CE_CLK, "qce.0", OFF), + CLK_DUMMY("core_clk", CE_CLK, "qcota.0", OFF), + CLK_DUMMY("core_clk", CE_CLK, "qcrypto.0", OFF), +}; + +struct clock_init_data fsm9xxx_clock_init_data __initdata = { + .table = msm_clocks_fsm9xxx, + .size = 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 0000000000000000000000000000000000000000..4f365fae43602094e016eea75354c1de2de350c3 --- /dev/null +++ b/arch/arm/mach-msm/clock-local.c @@ -0,0 +1,972 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 200 +/* 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 rcg_dummy_freq = F_END; + +/* + * 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_info; + 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); + } + + /* Update the MND_EN and NS masks to match the current bank. */ + clk->mnd_en_mask = new_bank_masks->mnd_en_mask; + 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_info; + 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; +} + +/* + * 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 int branch_in_hwcg_mode(const struct branch *b) +{ + if (!b->hwcg_mask) + return 0; + + return !!(readl_relaxed(b->hwcg_reg) & b->hwcg_mask); +} + +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(); + + /* Skip checking halt bit if the clock is in hardware gated mode */ + if (branch_in_hwcg_mode(clk)) + return; + + /* 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 __rcg_clk_enable_reg(struct rcg_clk *clk) +{ + u32 reg_val; + void __iomem *const reg = clk->b.ctl_reg; + + WARN(clk->current_freq == &rcg_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->md_val) { + reg_val |= clk->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. */ +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(); + + /* Skip checking halt bit if the clock is in hardware gated mode */ + if (branch_in_hwcg_mode(clk)) + return reg_val; + + /* 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 __rcg_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->md_val) { + reg_val &= ~(clk->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); + } +} + +/* Enable a rate-settable clock. */ +static int rcg_clk_enable(struct clk *c) +{ + unsigned long flags; + struct rcg_clk *clk = to_rcg_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __rcg_clk_enable_reg(clk); + clk->enabled = true; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +/* Disable a rate-settable clock. */ +static void rcg_clk_disable(struct clk *c) +{ + unsigned long flags; + struct rcg_clk *clk = to_rcg_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __rcg_clk_disable_reg(clk); + clk->enabled = false; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +/* + * Frequency-related functions + */ + +/* Set a clock to an exact rate. */ +static int rcg_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct rcg_clk *clk = to_rcg_clk(c); + struct clk_freq_tbl *nf, *cf; + struct clk *chld; + int rc = 0; + + for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END + && nf->freq_hz != rate; nf++) + ; + + if (nf->freq_hz == FREQ_END) + return -EINVAL; + + /* Check if frequency is actually changed. */ + cf = clk->current_freq; + if (nf == cf) + return 0; + + if (clk->enabled) { + /* Enable source clock dependency for the new freq. */ + rc = clk_enable(nf->src_clk); + if (rc) + return rc; + } + + spin_lock(&local_clock_reg_lock); + + /* Disable branch if clock isn't dual-banked with a glitch-free MUX. */ + if (!clk->bank_info) { + /* 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) + __rcg_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 __rcg_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_info) { + if (clk->enabled) + __rcg_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 source requirements of the old freq. */ + if (clk->enabled) + clk_disable(cf->src_clk); + + return rc; +} + +/* Check if a clock is currently enabled. */ +static int rcg_clk_is_enabled(struct clk *clk) +{ + return to_rcg_clk(clk)->enabled; +} + +/* Return a supported rate that's at least the specified rate. */ +static long rcg_clk_round_rate(struct clk *c, unsigned long 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; +} + +/* Return the nth supported frequency for a given clock. */ +static int rcg_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; +} + +static struct clk *rcg_clk_get_parent(struct clk *clk) +{ + return to_rcg_clk(clk)->current_freq->src_clk; +} + +/* Disable hw clock gating if not set at boot */ +enum handoff branch_handoff(struct branch *clk, struct clk *c) +{ + if (!branch_in_hwcg_mode(clk)) { + clk->hwcg_mask = 0; + c->flags &= ~CLKFLAG_HWCG; + if (readl_relaxed(clk->ctl_reg) & clk->en_mask) + return HANDOFF_ENABLED_CLK; + } else { + c->flags |= CLKFLAG_HWCG; + } + return HANDOFF_DISABLED_CLK; +} + +static enum handoff branch_clk_handoff(struct clk *c) +{ + struct branch_clk *clk = to_branch_clk(c); + return branch_handoff(&clk->b, &clk->c); +} + +static enum handoff rcg_clk_handoff(struct clk *c) +{ + struct rcg_clk *clk = to_rcg_clk(c); + uint32_t ctl_val, ns_val, md_val, ns_mask; + struct clk_freq_tbl *freq; + enum handoff ret; + + ctl_val = readl_relaxed(clk->b.ctl_reg); + ret = branch_handoff(&clk->b, &clk->c); + if (ret == HANDOFF_DISABLED_CLK) + return HANDOFF_DISABLED_CLK; + + if (clk->bank_info) { + const struct bank_masks *bank_masks = clk->bank_info; + const struct bank_mask_info *bank_info; + if (!(ctl_val & bank_masks->bank_sel_mask)) + bank_info = &bank_masks->bank0_mask; + else + bank_info = &bank_masks->bank1_mask; + + ns_mask = bank_info->ns_mask; + md_val = bank_info->md_reg ? + readl_relaxed(bank_info->md_reg) : 0; + } else { + ns_mask = clk->ns_mask; + md_val = clk->md_reg ? readl_relaxed(clk->md_reg) : 0; + } + if (!ns_mask) + return HANDOFF_UNKNOWN_RATE; + ns_val = readl_relaxed(clk->ns_reg) & ns_mask; + for (freq = clk->freq_tbl; freq->freq_hz != FREQ_END; freq++) { + if ((freq->ns_val & ns_mask) == ns_val && + (!freq->md_val || freq->md_val == md_val)) { + pr_info("%s rate=%d\n", clk->c.dbg_name, freq->freq_hz); + break; + } + } + if (freq->freq_hz == FREQ_END) + return HANDOFF_UNKNOWN_RATE; + + clk->current_freq = freq; + c->rate = freq->freq_hz; + + return HANDOFF_ENABLED_CLK; +} + +struct clk_ops clk_ops_empty; + +struct fixed_clk gnd_clk = { + .c = { + .dbg_name = "ground_clk", + .ops = &clk_ops_empty, + CLK_INIT(gnd_clk.c), + }, +}; + +static 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; +} + +static 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); +} + +static struct clk *branch_clk_get_parent(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + return branch->parent; +} + +static int branch_clk_is_enabled(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + return branch->enabled; +} + +static void branch_enable_hwcg(struct branch *b) +{ + unsigned long flags; + u32 reg_val; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg_val = readl_relaxed(b->hwcg_reg); + reg_val |= b->hwcg_mask; + writel_relaxed(reg_val, b->hwcg_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static void branch_disable_hwcg(struct branch *b) +{ + unsigned long flags; + u32 reg_val; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg_val = readl_relaxed(b->hwcg_reg); + reg_val &= ~b->hwcg_mask; + writel_relaxed(reg_val, b->hwcg_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static void branch_clk_enable_hwcg(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + branch_enable_hwcg(&branch->b); +} + +static void branch_clk_disable_hwcg(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + branch_disable_hwcg(&branch->b); +} + +static int branch_set_flags(struct branch *b, unsigned flags) +{ + unsigned long irq_flags; + u32 reg_val; + int ret = 0; + + if (!b->retain_reg) + return -EPERM; + + spin_lock_irqsave(&local_clock_reg_lock, irq_flags); + reg_val = readl_relaxed(b->retain_reg); + switch (flags) { + case CLKFLAG_RETAIN: + reg_val |= b->retain_mask; + break; + case CLKFLAG_NORETAIN: + reg_val &= ~b->retain_mask; + break; + default: + ret = -EINVAL; + } + writel_relaxed(reg_val, b->retain_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, irq_flags); + + return ret; +} + +static int branch_clk_set_flags(struct clk *clk, unsigned flags) +{ + return branch_set_flags(&to_branch_clk(clk)->b, flags); +} + +static int branch_clk_in_hwcg_mode(struct clk *c) +{ + struct branch_clk *clk = to_branch_clk(c); + return branch_in_hwcg_mode(&clk->b); +} + +static void rcg_clk_enable_hwcg(struct clk *clk) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + branch_enable_hwcg(&rcg->b); +} + +static void rcg_clk_disable_hwcg(struct clk *clk) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + branch_disable_hwcg(&rcg->b); +} + +static int rcg_clk_in_hwcg_mode(struct clk *c) +{ + struct rcg_clk *clk = to_rcg_clk(c); + return branch_in_hwcg_mode(&clk->b); +} + +static int rcg_clk_set_flags(struct clk *clk, unsigned flags) +{ + return branch_set_flags(&to_rcg_clk(clk)->b, flags); +} + +int branch_reset(struct branch *b, enum clk_reset_action action) +{ + int ret = 0; + u32 reg_val; + unsigned long flags; + + if (!b->reset_reg) + return -EPERM; + + /* Disable hw gating when asserting a reset */ + if (b->hwcg_mask && action == CLK_RESET_ASSERT) + branch_disable_hwcg(b); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + /* Assert/Deassert reset */ + reg_val = readl_relaxed(b->reset_reg); + switch (action) { + case CLK_RESET_ASSERT: + reg_val |= b->reset_mask; + break; + case CLK_RESET_DEASSERT: + reg_val &= ~b->reset_mask; + break; + default: + ret = -EINVAL; + } + writel_relaxed(reg_val, b->reset_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Enable hw gating when deasserting a reset */ + if (b->hwcg_mask && action == CLK_RESET_DEASSERT) + branch_enable_hwcg(b); + /* Make sure write is issued before returning. */ + mb(); + return ret; +} + +static int branch_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return branch_reset(&to_branch_clk(clk)->b, action); +} + +struct clk_ops clk_ops_branch = { + .enable = branch_clk_enable, + .disable = branch_clk_disable, + .enable_hwcg = branch_clk_enable_hwcg, + .disable_hwcg = branch_clk_disable_hwcg, + .in_hwcg_mode = branch_clk_in_hwcg_mode, + .auto_off = branch_clk_disable, + .is_enabled = branch_clk_is_enabled, + .reset = branch_clk_reset, + .get_parent = branch_clk_get_parent, + .handoff = branch_clk_handoff, + .set_flags = branch_clk_set_flags, +}; + +struct clk_ops clk_ops_reset = { + .reset = branch_clk_reset, +}; + +static int rcg_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return branch_reset(&to_rcg_clk(clk)->b, action); +} + +struct clk_ops clk_ops_rcg = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .enable_hwcg = rcg_clk_enable_hwcg, + .disable_hwcg = rcg_clk_disable_hwcg, + .in_hwcg_mode = rcg_clk_in_hwcg_mode, + .auto_off = rcg_clk_disable, + .handoff = rcg_clk_handoff, + .set_rate = rcg_clk_set_rate, + .list_rate = rcg_clk_list_rate, + .is_enabled = rcg_clk_is_enabled, + .round_rate = rcg_clk_round_rate, + .reset = rcg_clk_reset, + .get_parent = rcg_clk_get_parent, + .set_flags = rcg_clk_set_flags, +}; + +static int cdiv_clk_enable(struct clk *c) +{ + unsigned long flags; + struct cdiv_clk *clk = to_cdiv_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_enable_reg(&clk->b, clk->c.dbg_name); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +static void cdiv_clk_disable(struct clk *c) +{ + unsigned long flags; + struct cdiv_clk *clk = to_cdiv_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_disable_reg(&clk->b, clk->c.dbg_name); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static int cdiv_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + u32 reg_val; + + if (rate > clk->max_div) + return -EINVAL; + /* Check if frequency is actually changed. */ + if (rate == clk->cur_div) + return 0; + + spin_lock(&local_clock_reg_lock); + reg_val = readl_relaxed(clk->ns_reg); + reg_val &= ~(clk->ext_mask | (clk->max_div - 1) << clk->div_offset); + /* Non-zero rates mean set a divider, zero means use external input */ + if (rate) + reg_val |= (rate - 1) << clk->div_offset; + else + reg_val |= clk->ext_mask; + writel_relaxed(reg_val, clk->ns_reg); + spin_unlock(&local_clock_reg_lock); + + clk->cur_div = rate; + return 0; +} + +static unsigned long cdiv_clk_get_rate(struct clk *c) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + return clk->cur_div; +} + +static long cdiv_clk_round_rate(struct clk *c, unsigned long rate) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + return rate > clk->max_div ? -EPERM : rate; +} + +static int cdiv_clk_list_rate(struct clk *c, unsigned n) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + return n > clk->max_div ? -ENXIO : n; +} + +static enum handoff cdiv_clk_handoff(struct clk *c) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + enum handoff ret; + u32 reg_val; + + ret = branch_handoff(&clk->b, &clk->c); + if (ret == HANDOFF_DISABLED_CLK) + return ret; + + reg_val = readl_relaxed(clk->ns_reg); + if (reg_val & clk->ext_mask) { + clk->cur_div = 0; + } else { + reg_val >>= clk->div_offset; + clk->cur_div = (reg_val & (clk->max_div - 1)) + 1; + } + + return HANDOFF_ENABLED_CLK; +} + +static void cdiv_clk_enable_hwcg(struct clk *c) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + branch_enable_hwcg(&clk->b); +} + +static void cdiv_clk_disable_hwcg(struct clk *c) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + branch_disable_hwcg(&clk->b); +} + +static int cdiv_clk_in_hwcg_mode(struct clk *c) +{ + struct cdiv_clk *clk = to_cdiv_clk(c); + return branch_in_hwcg_mode(&clk->b); +} + +struct clk_ops clk_ops_cdiv = { + .enable = cdiv_clk_enable, + .disable = cdiv_clk_disable, + .in_hwcg_mode = cdiv_clk_in_hwcg_mode, + .enable_hwcg = cdiv_clk_enable_hwcg, + .disable_hwcg = cdiv_clk_disable_hwcg, + .auto_off = cdiv_clk_disable, + .handoff = cdiv_clk_handoff, + .set_rate = cdiv_clk_set_rate, + .get_rate = cdiv_clk_get_rate, + .list_rate = cdiv_clk_list_rate, + .round_rate = cdiv_clk_round_rate, +}; diff --git a/arch/arm/mach-msm/clock-local.h b/arch/arm/mach-msm/clock-local.h new file mode 100644 index 0000000000000000000000000000000000000000..ffc7057406ae399b7d536d0d7df68d14befe763f --- /dev/null +++ b/arch/arm/mach-msm/clock-local.h @@ -0,0 +1,290 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" + +#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))) \ + * !!(n)) +#define MD8(m_lsb, m, n_lsb, n) \ + ((BVAL((m_lsb+7), m_lsb, m) | BVAL((n_lsb+7), n_lsb, ~(n))) \ + * !!(n)) +#define MD16(m, n) ((BVAL(31, 16, m) | BVAL(15, 0, ~(n))) * !!(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) * !!(n)) \ + | (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) * !!(n))|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) * !!(n)) \ + | BVAL((n1_lsb+3), n1_lsb, ~(n-m) * !!(n)) \ + | 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) * !!(n)) \ + | BVAL((n1_lsb+7), n1_lsb, ~(n-m) * !!(n)) \ + | 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)) + +/* + * Clock Definition Macros + */ +#define DEFINE_CLK_MEASURE(name) \ + struct clk name = { \ + .ops = &clk_ops_empty, \ + .dbg_name = #name, \ + CLK_INIT(name), \ + }; \ + +/* + * Generic frequency-definition structs and macros + */ +struct clk_freq_tbl { + const uint32_t freq_hz; + struct clk *const src_clk; + const uint32_t md_val; + const uint32_t ns_val; + const uint32_t ctl_val; + 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, e) { \ + .freq_hz = f, \ + .src_clk = sc, \ + .md_val = m_v, \ + .ns_val = n_v, \ + .ctl_val = c_v, \ + .extra_freq_data = e, \ + } +#define FREQ_END (UINT_MAX-1) +#define F_END { .freq_hz = FREQ_END } + +/** + * struct branch - branch on/off + * @ctl_reg: clock control register + * @en_mask: ORed with @ctl_reg to enable the clock + * @hwcg_reg: hardware clock gating register + * @hwcg_mask: ORed with @hwcg_reg to enable hardware clock gating + * @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 *hwcg_reg; + u32 hwcg_mask; + + void __iomem *const halt_reg; + const u16 halt_check; + const u16 halt_bit; + + void __iomem *const reset_reg; + const u32 reset_mask; + + void __iomem *const retain_reg; + const u32 retain_mask; +}; + +extern struct clk_ops clk_ops_branch; +extern struct clk_ops clk_ops_reset; + +int branch_reset(struct branch *b, enum clk_reset_action action); +void __branch_clk_enable_reg(const struct branch *clk, const char *name); +u32 __branch_clk_disable_reg(const struct branch *clk, const char *name); +enum handoff branch_handoff(struct branch *clk, struct clk *c); + +/* + * 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; + uint32_t mnd_en_mask; + + void *bank_info; + void (*set_rate)(struct rcg_clk *, struct clk_freq_tbl *); + + struct clk_freq_tbl *freq_tbl; + struct clk_freq_tbl *current_freq; + + 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); +} + +extern struct clk_ops clk_ops_rcg; + +extern struct clk_freq_tbl rcg_dummy_freq; + +/** + * struct cdiv_clk - integer divider clock with external source selection + * @ns_reg: source select and divider settings register + * @ext_mask: bit to set to select an external source + * @cur_div: current divider setting (or 0 for external source) + * @max_div: maximum divider value supported (must be power of 2) + * @div_offset: number of bits to shift divider left by in @ns_reg + * @b: branch + * @c: clock + */ +struct cdiv_clk { + void __iomem *const ns_reg; + u32 ext_mask; + + unsigned long cur_div; + u8 div_offset; + u32 max_div; + + struct branch b; + struct clk c; +}; + +static inline struct cdiv_clk *to_cdiv_clk(struct clk *clk) +{ + return container_of(clk, struct cdiv_clk, c); +} + +extern struct clk_ops clk_ops_cdiv; + +/** + * struct fixed_clk - fixed rate clock (used for crystal oscillators) + * @c: clk + */ +struct fixed_clk { + struct 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); +} + +/** + * 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_empty; + +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 fixed_clk gnd_clk; + +/* + * 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-local2.c b/arch/arm/mach-msm/clock-local2.c new file mode 100644 index 0000000000000000000000000000000000000000..e8e88d704f1558f7813065a068182a9b1ad155e2 --- /dev/null +++ b/arch/arm/mach-msm/clock-local2.c @@ -0,0 +1,591 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "clock.h" +#include "clock-local2.h" + +/* + * 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 200 +/* For clock without halt checking, wait this long after enables/disables. */ +#define HALT_CHECK_DELAY_US 10 + +/* + * When updating an RCG configuration, check the update bit up to this number + * number of times (with a 1 us delay in between) before continuing. + */ +#define UPDATE_CHECK_MAX_LOOPS 200 + +DEFINE_SPINLOCK(local_clock_reg_lock); +struct clk_freq_tbl rcg_dummy_freq = F_END; + +#define CMD_RCGR_REG(x) (*(x)->base + (x)->cmd_rcgr_reg) +#define CFG_RCGR_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0x4) +#define M_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0x8) +#define N_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0xC) +#define D_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0x10) +#define CBCR_REG(x) (*(x)->base + (x)->cbcr_reg) +#define BCR_REG(x) (*(x)->base + (x)->bcr_reg) +#define VOTE_REG(x) (*(x)->base + (x)->vote_reg) + +/* + * Important clock bit positions and masks + */ +#define CMD_RCGR_ROOT_ENABLE_BIT BIT(1) +#define CBCR_BRANCH_ENABLE_BIT BIT(0) +#define CBCR_BRANCH_OFF_BIT BIT(31) +#define CMD_RCGR_CONFIG_UPDATE_BIT BIT(0) +#define CMD_RCGR_ROOT_STATUS_BIT BIT(31) +#define BCR_BLK_ARES_BIT BIT(0) +#define CBCR_HW_CTL_BIT BIT(1) +#define CFG_RCGR_DIV_MASK BM(4, 0) +#define CFG_RCGR_SRC_SEL_MASK BM(10, 8) +#define MND_MODE_MASK BM(13, 12) +#define MND_DUAL_EDGE_MODE_BVAL BVAL(13, 12, 0x2) +#define CMD_RCGR_CONFIG_DIRTY_MASK BM(7, 4) +#define CBCR_BRANCH_CDIV_MASK BM(24, 16) +#define CBCR_BRANCH_CDIV_MASKED(val) BVAL(24, 16, (val)); + +enum branch_state { + BRANCH_ON, + BRANCH_OFF, +}; + +/* + * RCG functions + */ + +/* + * Update an RCG with a new configuration. This may include a new M, N, or D + * value, source selection or pre-divider value. + * + */ +static void rcg_update_config(struct rcg_clk *rcg) +{ + u32 cmd_rcgr_regval, count; + + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + cmd_rcgr_regval |= CMD_RCGR_CONFIG_UPDATE_BIT; + writel_relaxed(cmd_rcgr_regval, CMD_RCGR_REG(rcg)); + + /* Wait for update to take effect */ + for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) { + if (!(readl_relaxed(CMD_RCGR_REG(rcg)) & + CMD_RCGR_CONFIG_UPDATE_BIT)) + return; + udelay(1); + } + + WARN(count == 0, "%s: rcg didn't update its configuration.", + rcg->c.dbg_name); +} + +/* RCG set rate function for clocks with Half Integer Dividers. */ +void set_rate_hid(struct rcg_clk *rcg, struct clk_freq_tbl *nf) +{ + u32 cfg_regval; + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + cfg_regval &= ~(CFG_RCGR_DIV_MASK | CFG_RCGR_SRC_SEL_MASK); + cfg_regval |= nf->div_src_val; + writel_relaxed(cfg_regval, CFG_RCGR_REG(rcg)); + + rcg_update_config(rcg); +} + +/* RCG set rate function for clocks with MND & Half Integer Dividers. */ +void set_rate_mnd(struct rcg_clk *rcg, struct clk_freq_tbl *nf) +{ + u32 cfg_regval; + + writel_relaxed(nf->m_val, M_REG(rcg)); + writel_relaxed(nf->n_val, N_REG(rcg)); + writel_relaxed(nf->d_val, D_REG(rcg)); + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + cfg_regval &= ~(CFG_RCGR_DIV_MASK | CFG_RCGR_SRC_SEL_MASK); + cfg_regval |= nf->div_src_val; + + /* Activate or disable the M/N:D divider as necessary */ + cfg_regval &= ~MND_MODE_MASK; + if (nf->n_val != 0) + cfg_regval |= MND_DUAL_EDGE_MODE_BVAL; + writel_relaxed(cfg_regval, CFG_RCGR_REG(rcg)); + + rcg_update_config(rcg); +} + +static int rcg_clk_enable(struct clk *c) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + + WARN(rcg->current_freq == &rcg_dummy_freq, + "Attempting to enable %s before setting its rate. " + "Set the rate first!\n", rcg->c.dbg_name); + + return 0; +} + +static int rcg_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct clk_freq_tbl *cf, *nf; + struct rcg_clk *rcg = to_rcg_clk(c); + int rc = 0; + unsigned long flags; + + for (nf = rcg->freq_tbl; nf->freq_hz != FREQ_END + && nf->freq_hz != rate; nf++) + ; + + if (nf->freq_hz == FREQ_END) + return -EINVAL; + + /* Check if frequency is actually changed. */ + cf = rcg->current_freq; + if (nf == cf) + return 0; + + if (rcg->c.count) { + /* TODO: Modify to use the prepare API */ + /* Enable source clock dependency for the new freq. */ + rc = clk_enable(nf->src_clk); + if (rc) + goto out; + } + + BUG_ON(!rcg->set_rate); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Perform clock-specific frequency switch operations. */ + rcg->set_rate(rcg, nf); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Release source requirements of the old freq. */ + if (rcg->c.count) + clk_disable(cf->src_clk); + + rcg->current_freq = nf; +out: + return rc; +} + +/* Return a supported rate that's at least the specified rate. */ +static long rcg_clk_round_rate(struct clk *c, unsigned long rate) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + struct clk_freq_tbl *f; + + for (f = rcg->freq_tbl; f->freq_hz != FREQ_END; f++) + if (f->freq_hz >= rate) + return f->freq_hz; + + return -EPERM; +} + +/* Return the nth supported frequency for a given clock. */ +static int rcg_clk_list_rate(struct clk *c, unsigned n) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + + if (!rcg->freq_tbl || rcg->freq_tbl->freq_hz == FREQ_END) + return -ENXIO; + + return (rcg->freq_tbl + n)->freq_hz; +} + +static struct clk *rcg_clk_get_parent(struct clk *c) +{ + return to_rcg_clk(c)->current_freq->src_clk; +} + +static enum handoff _rcg_clk_handoff(struct rcg_clk *rcg, int has_mnd) +{ + u32 n_regval = 0, m_regval = 0, d_regval = 0; + u32 cfg_regval; + struct clk_freq_tbl *freq; + u32 cmd_rcgr_regval; + + /* Is the root enabled? */ + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + if ((cmd_rcgr_regval & CMD_RCGR_ROOT_STATUS_BIT)) + return HANDOFF_DISABLED_CLK; + + /* Is there a pending configuration? */ + if (cmd_rcgr_regval & CMD_RCGR_CONFIG_DIRTY_MASK) + return HANDOFF_UNKNOWN_RATE; + + /* Get values of m, n, d, div and src_sel registers. */ + if (has_mnd) { + m_regval = readl_relaxed(M_REG(rcg)); + n_regval = readl_relaxed(N_REG(rcg)); + d_regval = readl_relaxed(D_REG(rcg)); + + /* + * The n and d values stored in the frequency tables are sign + * extended to 32 bits. The n and d values in the registers are + * sign extended to 8 or 16 bits. Sign extend the values read + * from the registers so that they can be compared to the + * values in the frequency tables. + */ + n_regval |= (n_regval >> 8) ? BM(31, 16) : BM(31, 8); + d_regval |= (d_regval >> 8) ? BM(31, 16) : BM(31, 8); + } + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + cfg_regval &= CFG_RCGR_SRC_SEL_MASK | CFG_RCGR_DIV_MASK + | MND_MODE_MASK; + + /* If mnd counter is present, check if it's in use. */ + has_mnd = (has_mnd) && + ((cfg_regval & MND_MODE_MASK) == MND_DUAL_EDGE_MODE_BVAL); + + /* + * Clear out the mn counter mode bits since we now want to compare only + * the source mux selection and pre-divider values in the registers. + */ + cfg_regval &= ~MND_MODE_MASK; + + /* Figure out what rate the rcg is running at */ + for (freq = rcg->freq_tbl; freq->freq_hz != FREQ_END; freq++) { + if (freq->div_src_val != cfg_regval) + continue; + if (has_mnd) { + if (freq->m_val != m_regval) + continue; + if (freq->n_val != n_regval) + continue; + if (freq->d_val != d_regval) + continue; + } + pr_info("%s rate=%lu\n", rcg->c.dbg_name, freq->freq_hz); + break; + } + + /* No known frequency found */ + if (freq->freq_hz == FREQ_END) + return HANDOFF_UNKNOWN_RATE; + + rcg->current_freq = freq; + rcg->c.rate = freq->freq_hz; + + return HANDOFF_ENABLED_CLK; +} + +static enum handoff rcg_mnd_clk_handoff(struct clk *c) +{ + return _rcg_clk_handoff(to_rcg_clk(c), 1); +} + +static enum handoff rcg_clk_handoff(struct clk *c) +{ + return _rcg_clk_handoff(to_rcg_clk(c), 0); +} + +/* + * Branch clock functions + */ +static void branch_clk_halt_check(u32 halt_check, const char *clk_name, + void __iomem *cbcr_reg, + enum branch_state br_status) +{ + char *status_str = (br_status == BRANCH_ON) ? "off" : "on"; + + /* + * 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 disable. + */ + mb(); + + if (halt_check == DELAY || halt_check == HALT_VOTED) { + udelay(HALT_CHECK_DELAY_US); + } else if (halt_check == HALT) { + int count; + for (count = HALT_CHECK_MAX_LOOPS; count > 0; count--) { + if (br_status == BRANCH_ON + && !(readl_relaxed(cbcr_reg) + & CBCR_BRANCH_OFF_BIT)) + return; + if (br_status == BRANCH_OFF + && (readl_relaxed(cbcr_reg) + & CBCR_BRANCH_OFF_BIT)) + return; + udelay(1); + } + WARN(count == 0, "%s status stuck %s", clk_name, status_str); + } +} + +static int branch_clk_enable(struct clk *c) +{ + unsigned long flags; + u32 cbcr_val; + struct branch_clk *branch = to_branch_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + cbcr_val = readl_relaxed(CBCR_REG(branch)); + cbcr_val |= CBCR_BRANCH_ENABLE_BIT; + writel_relaxed(cbcr_val, CBCR_REG(branch)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Wait for clock to enable before continuing. */ + branch_clk_halt_check(branch->halt_check, branch->c.dbg_name, + CBCR_REG(branch), BRANCH_ON); + + return 0; +} + +static void branch_clk_disable(struct clk *c) +{ + unsigned long flags; + struct branch_clk *branch = to_branch_clk(c); + u32 reg_val; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg_val = readl_relaxed(CBCR_REG(branch)); + reg_val &= ~CBCR_BRANCH_ENABLE_BIT; + writel_relaxed(reg_val, CBCR_REG(branch)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Wait for clock to disable before continuing. */ + branch_clk_halt_check(branch->halt_check, branch->c.dbg_name, + CBCR_REG(branch), BRANCH_OFF); +} + +static int branch_cdiv_set_rate(struct branch_clk *branch, unsigned long rate) +{ + unsigned long flags; + u32 regval; + + if (rate > branch->max_div) + return -EINVAL; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(CBCR_REG(branch)); + regval &= ~CBCR_BRANCH_CDIV_MASK; + regval |= CBCR_BRANCH_CDIV_MASKED(rate); + writel_relaxed(regval, CBCR_REG(branch)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +static int branch_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (branch->max_div) + return branch_cdiv_set_rate(branch, rate); + + if (!branch->has_sibling) + return clk_set_rate(branch->parent, rate); + + return -EPERM; +} + +static unsigned long branch_clk_get_rate(struct clk *c) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (branch->max_div) + return branch->c.rate; + + if (!branch->has_sibling) + return clk_get_rate(branch->parent); + + return 0; +} + +static struct clk *branch_clk_get_parent(struct clk *c) +{ + return to_branch_clk(c)->parent; +} + +static int branch_clk_list_rate(struct clk *c, unsigned n) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (branch->has_sibling == 1) + return -ENXIO; + + if (branch->parent) + return rcg_clk_list_rate(branch->parent, n); + else + return 0; +} + +static enum handoff branch_clk_handoff(struct clk *c) +{ + struct branch_clk *branch = to_branch_clk(c); + u32 cbcr_regval; + + cbcr_regval = readl_relaxed(CBCR_REG(branch)); + if ((cbcr_regval & CBCR_BRANCH_OFF_BIT)) + return HANDOFF_DISABLED_CLK; + pr_info("%s enabled.\n", branch->c.dbg_name); + + if (branch->parent) { + if (branch->parent->ops->handoff) + return branch->parent->ops->handoff(branch->parent); + } + + return HANDOFF_ENABLED_CLK; +} + +static int __branch_clk_reset(void __iomem *bcr_reg, + enum clk_reset_action action) +{ + int ret = 0; + unsigned long flags; + u32 reg_val; + + if (!bcr_reg) + return -EPERM; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg_val = readl_relaxed(bcr_reg); + switch (action) { + case CLK_RESET_ASSERT: + reg_val |= BCR_BLK_ARES_BIT; + break; + case CLK_RESET_DEASSERT: + reg_val &= ~BCR_BLK_ARES_BIT; + break; + default: + ret = -EINVAL; + } + writel_relaxed(reg_val, bcr_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Make sure write is issued before returning. */ + mb(); + + return ret; +} + +static int branch_clk_reset(struct clk *c, enum clk_reset_action action) +{ + struct branch_clk *branch = to_branch_clk(c); + return __branch_clk_reset(BCR_REG(branch), action); +} + +/* + * Voteable clock functions + */ +static int local_vote_clk_reset(struct clk *c, enum clk_reset_action action) +{ + struct branch_clk *vclk = to_branch_clk(c); + return __branch_clk_reset(BCR_REG(vclk), action); +} + +static int local_vote_clk_enable(struct clk *c) +{ + unsigned long flags; + u32 ena; + struct local_vote_clk *vclk = to_local_vote_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ena = readl_relaxed(VOTE_REG(vclk)); + ena |= vclk->en_mask; + writel_relaxed(ena, VOTE_REG(vclk)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + branch_clk_halt_check(vclk->halt_check, c->dbg_name, CBCR_REG(vclk), + BRANCH_ON); + + return 0; +} + +static void local_vote_clk_disable(struct clk *c) +{ + unsigned long flags; + u32 ena; + struct local_vote_clk *vclk = to_local_vote_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ena = readl_relaxed(VOTE_REG(vclk)); + ena &= ~vclk->en_mask; + writel_relaxed(ena, VOTE_REG(vclk)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static enum handoff local_vote_clk_handoff(struct clk *c) +{ + struct local_vote_clk *vclk = to_local_vote_clk(c); + u32 vote_regval; + + /* Is the branch voted on by apps? */ + vote_regval = readl_relaxed(VOTE_REG(vclk)); + if (!(vote_regval & vclk->en_mask)) + return HANDOFF_DISABLED_CLK; + pr_info("%s enabled.\n", vclk->c.dbg_name); + + return HANDOFF_ENABLED_CLK; +} + +struct clk_ops clk_ops_rcg = { + .enable = rcg_clk_enable, + .set_rate = rcg_clk_set_rate, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .get_parent = rcg_clk_get_parent, + .handoff = rcg_clk_handoff, +}; + +struct clk_ops clk_ops_rcg_mnd = { + .enable = rcg_clk_enable, + .set_rate = rcg_clk_set_rate, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .get_parent = rcg_clk_get_parent, + .handoff = rcg_mnd_clk_handoff, +}; + +struct clk_ops clk_ops_branch = { + .enable = branch_clk_enable, + .disable = branch_clk_disable, + .auto_off = branch_clk_disable, + .set_rate = branch_clk_set_rate, + .get_rate = branch_clk_get_rate, + .list_rate = branch_clk_list_rate, + .reset = branch_clk_reset, + .get_parent = branch_clk_get_parent, + .handoff = branch_clk_handoff, +}; + +struct clk_ops clk_ops_vote = { + .enable = local_vote_clk_enable, + .disable = local_vote_clk_disable, + .auto_off = local_vote_clk_disable, + .reset = local_vote_clk_reset, + .handoff = local_vote_clk_handoff, +}; diff --git a/arch/arm/mach-msm/clock-local2.h b/arch/arm/mach-msm/clock-local2.h new file mode 100644 index 0000000000000000000000000000000000000000..547e633e636ece2221d37b5db12d3bf90a0e3066 --- /dev/null +++ b/arch/arm/mach-msm/clock-local2.h @@ -0,0 +1,178 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_2_H +#define __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_2_H + +#include +#include "clock.h" + +/* + * Generic frequency-definition structs and macros + */ + +/** + * @freq_hz: output rate + * @src_clk: source clock for freq_hz + * @m_val: M value corresponding to freq_hz + * @n_val: N value corresponding to freq_hz + * @d_val: D value corresponding to freq_hz + * @div_src_val: Pre divider value and source selection mux index for freq_hz + * @sys_vdd: Voltage level required for freq_hz + */ +struct clk_freq_tbl { + unsigned long freq_hz; + struct clk *src_clk; + const u32 m_val; + const u32 n_val; + const u32 d_val; + const u32 div_src_val; + const unsigned sys_vdd; +}; + +#define FREQ_END (UINT_MAX-1) +#define F_END { .freq_hz = FREQ_END } + +/* + * Generic clock-definition struct and macros + */ +/** + * struct rcg_clk - root clock generator + * @cmd_rcgr_reg: command register + * @set_rate: function to set frequency + * @freq_tbl: frequency table for this RCG + * @current_freq: current RCG frequency + * @c: generic clock data + * @base: pointer to base address of ioremapped registers. + */ +struct rcg_clk { + const u32 cmd_rcgr_reg; + + void (*set_rate)(struct rcg_clk *, struct clk_freq_tbl *); + + struct clk_freq_tbl *freq_tbl; + struct clk_freq_tbl *current_freq; + struct clk c; + + void *const __iomem *base; +}; + +static inline struct rcg_clk *to_rcg_clk(struct clk *clk) +{ + return container_of(clk, struct rcg_clk, c); +} + +extern struct clk_freq_tbl rcg_dummy_freq; + +/** + * struct fixed_clk - fixed rate clock (used for crystal oscillators) + * @rate: output rate + * @c: clk + */ +struct fixed_clk { + struct clk c; +}; + +/** + * struct branch_clk - branch clock + * @set_rate: Set the frequency of this branch clock. + * @parent: clock source + * @c: clk + * @cbcr_reg: branch control register + * @bcr_reg: block reset register + * @has_sibling: true if other branches are derived from this branch's source + * @cur_div: current branch divider value + * @max_div: maximum branch divider value (if zero, no divider exists) + * @halt_check: halt checking type + * @base: pointer to base address of ioremapped registers. + */ +struct branch_clk { + void (*set_rate)(struct branch_clk *, struct clk_freq_tbl *); + struct clk *parent; + struct clk c; + const u32 cbcr_reg; + const u32 bcr_reg; + int has_sibling; + u32 cur_div; + const u32 max_div; + const u32 halt_check; + void *const __iomem *base; +}; + +static inline struct branch_clk *to_branch_clk(struct clk *clk) +{ + return container_of(clk, struct branch_clk, c); +} + +/** + * struct local_vote_clk - Voteable branch clock + * @c: clk + * @cbcr_reg: branch control register + * @vote_reg: voting register + * @en_mask: enable mask + * @halt_check: halt checking type + * @base: pointer to base address of ioremapped registers. + * An on/off switch with a rate derived from the parent. + */ +struct local_vote_clk { + struct clk c; + const u32 cbcr_reg; + const u32 vote_reg; + const u32 bcr_reg; + const u32 en_mask; + const u32 halt_check; + void *const __iomem *base; +}; + +static inline struct local_vote_clk *to_local_vote_clk(struct clk *clk) +{ + return container_of(clk, struct local_vote_clk, c); +} + +/** + * 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; +}; + +static inline struct measure_clk *to_measure_clk(struct clk *clk) +{ + return container_of(clk, struct measure_clk, c); +} + +/* + * Generic set-rate implementations + */ +void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf); +void set_rate_hid(struct rcg_clk *clk, struct clk_freq_tbl *nf); + +/* + * Variables from the clock-local driver + */ +extern spinlock_t local_clock_reg_lock; + +extern struct clk_ops clk_ops_rcg; +extern struct clk_ops clk_ops_rcg_mnd; +extern struct clk_ops clk_ops_branch; +extern struct clk_ops clk_ops_vote; + +#endif /* __ARCH_ARM_MACH_MSM_COPPER_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 0000000000000000000000000000000000000000..09c16c71ae4af266ed155d332a736fdddbd57efb --- /dev/null +++ b/arch/arm/mach-msm/clock-pcom-lookup.c @@ -0,0 +1,516 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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-pll.h" +#include "clock-pcom.h" +#include "clock-voter.h" +#include + +#include +#include + +#define PLLn_MODE(n) (MSM_CLK_CTL_BASE + 0x300 + 28 * (n)) +#define PLL4_MODE (MSM_CLK_CTL_BASE + 0x374) + +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 pll_shared_clk pll0_clk = { + .id = PLL_0, + .mode_reg = PLLn_MODE(0), + .c = { + .ops = &clk_ops_pll, + .dbg_name = "pll0_clk", + CLK_INIT(pll0_clk.c), + }, +}; + +static struct pll_shared_clk pll1_clk = { + .id = PLL_1, + .mode_reg = PLLn_MODE(1), + .c = { + .ops = &clk_ops_pll, + .dbg_name = "pll1_clk", + CLK_INIT(pll1_clk.c), + }, +}; + +static struct pll_shared_clk pll2_clk = { + .id = PLL_2, + .mode_reg = PLLn_MODE(2), + .c = { + .ops = &clk_ops_pll, + .dbg_name = "pll2_clk", + CLK_INIT(pll2_clk.c), + }, +}; + +static struct pll_shared_clk pll4_clk = { + .id = PLL_4, + .mode_reg = PLL4_MODE, + .c = { + .ops = &clk_ops_pll, + .dbg_name = "pll4_clk", + CLK_INIT(pll4_clk.c), + }, +}; + +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, CLKFLAG_MIN); +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, 0); +static DEFINE_CLK_VOTER(ebi_grp_3d_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_grp_2d_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_lcdc_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_mddi_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_tv_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_usb_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_vfe_clk, &ebi1_clk.c, 0); +static DEFINE_CLK_VOTER(ebi_adm_clk, &ebi1_clk.c, 0); + +static struct clk_lookup msm_clocks_7x01a[] = { + CLK_LOOKUP("core_clk", adm_clk.c, "msm_dmov"), + 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("core_clk", emdh_clk.c, "msm_mddi.1"), + CLK_LOOKUP("core_clk", gp_clk.c, ""), + CLK_LOOKUP("core_clk", grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_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("mem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("core_clk", pmdh_clk.c, "mddi.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + 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("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", tsif_clk.c, "msm_tsif.0"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, "msm_tsif.0"), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("core_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("core_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("core_clk", uart3_clk.c, "msm_serial.2"), + CLK_LOOKUP("core_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_hsusb_otg"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_hsusb_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_hsusb_peripheral"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_hsusb_peripheral"), + CLK_LOOKUP("alt_core_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), +}; + +struct clock_init_data msm7x01a_clock_init_data __initdata = { + .table = msm_clocks_7x01a, + .size = ARRAY_SIZE(msm_clocks_7x01a), +}; + +static struct clk_lookup msm_clocks_7x27[] = { + CLK_LOOKUP("core_clk", adm_clk.c, "msm_dmov"), + 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("core_clk", gp_clk.c, ""), + CLK_LOOKUP("core_clk", grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", grp_3d_clk.c, "footswitch-pcom.2"), + CLK_LOOKUP("iface_clk", grp_3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", i2c_clk.c, "msm_i2c.0"), + CLK_LOOKUP("iface_clk", grp_3d_p_clk.c, "footswitch-pcom.2"), + CLK_LOOKUP("icodec_rx_clk", icodec_rx_clk.c, NULL), + CLK_LOOKUP("icodec_tx_clk", icodec_tx_clk.c, NULL), + CLK_LOOKUP("mem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("core_clk", pmdh_clk.c, "mddi.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("mdp_clk", mdp_lcdc_pclk_clk.c, "lcdc.0"), + CLK_LOOKUP("lcdc_clk", mdp_lcdc_pad_pclk_clk.c, "lcdc.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + 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("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", tsif_clk.c, "msm_tsif.0"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, "msm_tsif.0"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, "msm_tsif.0"), + CLK_LOOKUP("core_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("core_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("core_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_otg_clk.c, NULL), + CLK_LOOKUP("phy_clk", usb_phy_clk.c, "msm_otg"), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("core_clk", vdc_clk.c, "footswitch-pcom.7"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("core_clk", vfe_clk.c, "footswitch-pcom.8"), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), + + CLK_LOOKUP("ebi1_acpu_clk", ebi_acpu_clk.c, NULL), + CLK_LOOKUP("bus_clk", ebi_grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("mem_clk", ebi_lcdc_clk.c, "lcdc.0"), + CLK_LOOKUP("mem_clk", ebi_mddi_clk.c, "mddi.0"), + CLK_LOOKUP("core_clk", ebi_usb_clk.c, "msm_otg"), + CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL), + CLK_LOOKUP("mem_clk", ebi_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("pll0_clk", pll0_clk.c, "acpu"), + CLK_LOOKUP("pll1_clk", pll1_clk.c, "acpu"), + CLK_LOOKUP("pll2_clk", pll2_clk.c, "acpu"), +}; + +struct clock_init_data msm7x27_clock_init_data __initdata = { + .table = msm_clocks_7x27, + .size = ARRAY_SIZE(msm_clocks_7x27), + .pre_init = msm_shared_pll_control_init, +}; + +/* Clock table for common clocks between 7627a and 7625a */ +static struct clk_lookup msm_cmn_clk_7625a_7627a[] __initdata = { + CLK_LOOKUP("core_clk", adm_clk.c, "msm_dmov"), + CLK_LOOKUP("adsp_clk", adsp_clk.c, NULL), + CLK_LOOKUP("master_iface_clk", ahb_m_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("slave_iface_clk", ahb_s_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("cam_m_clk", cam_m_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam_m_clk.c, "0-0036"), + CLK_LOOKUP("cam_clk", cam_m_clk.c, "0-001b"), + CLK_LOOKUP("cam_clk", cam_m_clk.c, "0-0010"), + CLK_LOOKUP("cam_clk", cam_m_clk.c, "0-0078"), + CLK_LOOKUP("cam_clk", cam_m_clk.c, "0-006c"), + 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", csi0_clk.c, "msm_camera_ov7692.0"), + CLK_LOOKUP("csi_pclk", csi0_p_clk.c, "msm_camera_ov7692.0"), + CLK_LOOKUP("csi_vfe_clk", csi0_vfe_clk.c, "msm_camera_ov7692.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("csi_clk", csi0_clk.c, "msm_csic.0"), + CLK_LOOKUP("csi_pclk", csi0_p_clk.c, "msm_csic.0"), + CLK_LOOKUP("csi_vfe_clk", csi0_vfe_clk.c, "msm_csic.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_csic.1"), + CLK_LOOKUP("csi_pclk", csi1_p_clk.c, "msm_csic.1"), + CLK_LOOKUP("csi_vfe_clk", csi1_vfe_clk.c, "msm_csic.1"), + CLK_LOOKUP("byte_clk", dsi_byte_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("core_clk", dsi_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("esc_clk", dsi_esc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("pixel_clk", dsi_pixel_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("ref_clk", dsi_ref_clk.c, "mipi_dsi.1"), + 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("core_clk", gp_clk.c, ""), + CLK_LOOKUP("core_clk", grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", grp_3d_clk.c, "footswitch-pcom.2"), + CLK_LOOKUP("iface_clk", grp_3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("iface_clk", grp_3d_p_clk.c, "footswitch-pcom.2"), + CLK_LOOKUP("core_clk", gsbi1_qup_clk.c, "qup_i2c.0"), + CLK_LOOKUP("core_clk", gsbi2_qup_clk.c, "qup_i2c.1"), + CLK_LOOKUP("iface_clk", gsbi1_qup_p_clk.c, "qup_i2c.0"), + CLK_LOOKUP("iface_clk", 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("mem_clk", imem_clk.c, NULL), + CLK_LOOKUP("core_clk", pmdh_clk.c, "mddi.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("mdp_clk", mdp_lcdc_pclk_clk.c, "lcdc.0"), + CLK_LOOKUP("lcdc_clk", mdp_lcdc_pad_pclk_clk.c, "lcdc.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + CLK_LOOKUP("mdp_clk", mdp_dsi_p_clk.c, "mipi_dsi.1"), + 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("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, "msm_tsif.0"), + CLK_LOOKUP("iface_clk", tsif_p_clk.c, "msm_tsif.0"), + CLK_LOOKUP("core_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("core_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("core_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", uart2dm_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("core_clk", usb_hs_core_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_otg"), + CLK_LOOKUP("phy_clk", usb_phy_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs2_clk.c, "msm_hsusb_host.0"), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("core_clk", vdc_clk.c, "footswitch-pcom.7"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, "msm_vfe.0"), + CLK_LOOKUP("core_clk", vfe_clk.c, "footswitch-pcom.8"), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), + + CLK_LOOKUP("ebi1_acpu_clk", ebi_acpu_clk.c, NULL), + CLK_LOOKUP("bus_clk", ebi_grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("mem_clk", ebi_lcdc_clk.c, "lcdc.0"), + CLK_LOOKUP("mem_clk", ebi_lcdc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("mem_clk", ebi_mddi_clk.c, "mddi.0"), + CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL), + CLK_LOOKUP("mem_clk", ebi_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("pll0_clk", pll0_clk.c, "acpu"), + CLK_LOOKUP("pll1_clk", pll1_clk.c, "acpu"), + CLK_LOOKUP("pll2_clk", pll2_clk.c, "acpu"), + +}; + +/* PLL 4 clock is available for 7627a target. */ +static struct clk_lookup msm_clk_7627a[] __initdata = { + CLK_LOOKUP("pll4_clk", pll4_clk.c, "acpu"), +}; + +static struct clk_lookup msm_clk_7627a_7625a[ARRAY_SIZE(msm_cmn_clk_7625a_7627a) + + ARRAY_SIZE(msm_clk_7627a)]; + +static void __init msm7627a_clock_pre_init(void) +{ + int size = ARRAY_SIZE(msm_cmn_clk_7625a_7627a); + + /* Intialize shared PLL control structure */ + msm_shared_pll_control_init(); + + memcpy(&msm_clk_7627a_7625a, &msm_cmn_clk_7625a_7627a, + sizeof(msm_cmn_clk_7625a_7627a)); + if (!cpu_is_msm7x25a()) { + memcpy(&msm_clk_7627a_7625a[size], + &msm_clk_7627a, sizeof(msm_clk_7627a)); + size += ARRAY_SIZE(msm_clk_7627a); + } + msm7x27a_clock_init_data.size = size; +} + +struct clock_init_data msm7x27a_clock_init_data __initdata = { + .table = msm_clk_7627a_7625a, + .pre_init = msm7627a_clock_pre_init, +}; + +static struct clk_lookup msm_clocks_8x50[] = { + CLK_LOOKUP("core_clk", adm_clk.c, "msm_dmov"), + CLK_LOOKUP("core_clk", ce_clk.c, "qce.0"), + 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("core_clk", emdh_clk.c, "msm_mddi.1"), + CLK_LOOKUP("core_clk", gp_clk.c, ""), + CLK_LOOKUP("core_clk", grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_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("mem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("core_clk", pmdh_clk.c, "mddi.0"), + CLK_LOOKUP("core_clk", mdp_clk.c, "mdp.0"), + CLK_LOOKUP("mdp_clk", mdp_lcdc_pclk_clk.c, "lcdc.0"), + CLK_LOOKUP("lcdc_clk", mdp_lcdc_pad_pclk_clk.c, "lcdc.0"), + CLK_LOOKUP("vsync_clk", mdp_vsync_clk.c, "mdp.0"), + 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("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", spi_clk.c, "spi_qsd.0"), + CLK_DUMMY("iface_clk", SPI_P_CLK, "spi_qsd.0", 0), + CLK_LOOKUP("core_clk", tsif_clk.c, "msm_tsif.0"), + CLK_LOOKUP("ref_clk", tsif_ref_clk.c, "msm_tsif.0"), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("core_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("core_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("core_clk", uart3_clk.c, "msm_serial.2"), + CLK_LOOKUP("core_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_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("alt_core_clk", usb_hs2_clk.c, "msm_hsusb_host.0"), + CLK_LOOKUP("iface_clk", usb_hs2_p_clk.c, "msm_hsusb_host.0"), + CLK_LOOKUP("alt_core_clk", usb_hs3_clk.c, ""), + CLK_LOOKUP("iface_clk", usb_hs3_p_clk.c, ""), + CLK_LOOKUP("phy_clk", usb_phy_clk.c, "msm_otg"), + + CLK_LOOKUP("ebi1_acpu_clk", ebi_acpu_clk.c, NULL), + CLK_LOOKUP("bus_clk", ebi_grp_3d_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("bus_clk", ebi_grp_2d_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("mem_clk", ebi_lcdc_clk.c, "lcdc.0"), + CLK_LOOKUP("mem_clk", ebi_lcdc_clk.c, "mipi_dsi.1"), + CLK_LOOKUP("mem_clk", ebi_mddi_clk.c, "mddi.0"), + CLK_LOOKUP("mem_clk", ebi_tv_clk.c, "tvenc.0"), + CLK_LOOKUP("core_clk", ebi_usb_clk.c, "msm_otg"), + CLK_LOOKUP("core_clk", ebi_usb_clk.c, "msm_hsusb_host.0"), + CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL), + CLK_LOOKUP("mem_clk", ebi_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("iface_clk", grp_3d_p_clk.c, "kgsl-3d0.0"), + CLK_LOOKUP("core_clk", grp_2d_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("iface_clk", grp_2d_p_clk.c, "kgsl-2d0.0"), + CLK_LOOKUP("core_clk", gsbi_clk.c, "qup_i2c.4"), + CLK_LOOKUP("iface_clk", gsbi_p_clk.c, "qup_i2c.4"), +}; + +struct clock_init_data qds8x50_clock_init_data __initdata = { + .table = msm_clocks_8x50, + .size = ARRAY_SIZE(msm_clocks_8x50), +}; diff --git a/arch/arm/mach-msm/clock-pcom.c b/arch/arm/mach-msm/clock-pcom.c index 63b7113110869dc0ab48b14b81f2361ded53e314..02c8765312cbf5c358eb1dc18c4bdd2ecfd5f787 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,29 +13,43 @@ * */ +#include #include -#include -#include + #include +#include +#include -#include "proc_comm.h" #include "clock.h" #include "clock-pcom.h" /* * 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,39 +68,65 @@ 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 long 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 rc = msm_proc_comm(PCOM_CLKCTL_RPC_SET_RATE, &id, &rate); + unsigned r = rate; + int id = to_pcom_clk(clk)->id; + int rc = msm_proc_comm(PCOM_CLKCTL_RPC_SET_RATE, &id, &r); if (rc < 0) return rc; else 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 long 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); + unsigned r = rate; + rc = msm_proc_comm(PCOM_CLKCTL_RPC_MIN_RATE, &id, &r); 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_rate(struct clk *clk, unsigned long rate) +{ + if (clk->flags & CLKFLAG_MIN) + return _pc_clk_set_min_rate(clk, rate); + else + return _pc_clk_set_rate(clk, rate); +} + +static int pc_clk_set_max_rate(struct clk *clk, unsigned long rate) { - int rc = msm_proc_comm(PCOM_CLKCTL_RPC_MAX_RATE, &id, &rate); + int id = to_pcom_clk(clk)->id; + unsigned r = rate; + int rc = msm_proc_comm(PCOM_CLKCTL_RPC_MAX_RATE, &id, &r); if (rc < 0) return rc; else 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,45 +134,86 @@ 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 long config) +{ + int id = to_pcom_clk(clk)->id; + unsigned c = config; + int rc = msm_proc_comm(PCOM_CLKCTL_RPC_SET_EXT_CONFIG, &id, &c); + if (rc < 0) + return rc; + else + return (int)id < 0 ? -EINVAL : 0; +} + +static unsigned long 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 long 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; } +static enum handoff pc_clk_handoff(struct clk *clk) +{ + /* + * Handoff clock state only since querying and caching the rate here + * would incur more overhead than it would ever save. + */ + if (pc_clk_is_enabled(clk)) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + 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, .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, + .handoff = pc_clk_handoff, +}; + +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_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, + .handoff = pc_clk_handoff, }; + diff --git a/arch/arm/mach-msm/clock-pcom.h b/arch/arm/mach-msm/clock-pcom.h index 974d0032f3a3632d736ded9ef168b431d8491001..723c53c7d750a4d4aed6705f34f6517f76c9077f 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-pll.c b/arch/arm/mach-msm/clock-pll.c new file mode 100644 index 0000000000000000000000000000000000000000..d8399110cc143fd6abc48e8aabea360748ad58e8 --- /dev/null +++ b/arch/arm/mach-msm/clock-pll.c @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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.h" +#include "clock-pll.h" +#include "smd_private.h" + +#ifdef CONFIG_MSM_SECURE_IO +#undef readl_relaxed +#undef writel_relaxed +#define readl_relaxed secure_readl +#define writel_relaxed secure_writel +#endif + +#define PLL_OUTCTRL BIT(0) +#define PLL_BYPASSNL BIT(1) +#define PLL_RESET_N BIT(2) +#define PLL_MODE_MASK BM(3, 0) + +#define PLL_EN_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->en_reg)) : \ + ((x)->en_reg)) +#define PLL_STATUS_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->status_reg)) : \ + ((x)->status_reg)) +#define PLL_MODE_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->mode_reg)) : \ + ((x)->mode_reg)) +#define PLL_L_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->l_reg)) : \ + ((x)->l_reg)) +#define PLL_M_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->m_reg)) : \ + ((x)->m_reg)) +#define PLL_N_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->n_reg)) : \ + ((x)->n_reg)) +#define PLL_CONFIG_REG(x) ((x)->base ? (*(x)->base + (u32)((x)->config_reg)) : \ + ((x)->config_reg)) + +static DEFINE_SPINLOCK(pll_reg_lock); + +#define ENABLE_WAIT_MAX_LOOPS 200 + +int pll_vote_clk_enable(struct clk *clk) +{ + u32 ena, count; + unsigned long flags; + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + + spin_lock_irqsave(&pll_reg_lock, flags); + ena = readl_relaxed(PLL_EN_REG(pll)); + ena |= pll->en_mask; + writel_relaxed(ena, PLL_EN_REG(pll)); + spin_unlock_irqrestore(&pll_reg_lock, flags); + + /* + * Use a memory barrier since some PLL status registers are + * not within the same 1K segment as the voting registers. + */ + mb(); + + /* Wait for pll to enable. */ + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pll)) & pll->status_mask) + return 0; + udelay(1); + } + + WARN("PLL %s didn't enable after voting for it!\n", clk->dbg_name); + + return -ETIMEDOUT; +} + +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(&pll_reg_lock, flags); + ena = readl_relaxed(PLL_EN_REG(pll)); + ena &= ~(pll->en_mask); + writel_relaxed(ena, PLL_EN_REG(pll)); + spin_unlock_irqrestore(&pll_reg_lock, flags); +} + +struct clk *pll_vote_clk_get_parent(struct clk *clk) +{ + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + return pll->parent; +} + +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(pll)) & pll->status_mask); +} + +static enum handoff pll_vote_clk_handoff(struct clk *clk) +{ + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + if (readl_relaxed(PLL_EN_REG(pll)) & pll->en_mask) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + +struct clk_ops clk_ops_pll_vote = { + .enable = pll_vote_clk_enable, + .disable = pll_vote_clk_disable, + .auto_off = pll_vote_clk_disable, + .is_enabled = pll_vote_clk_is_enabled, + .get_parent = pll_vote_clk_get_parent, + .handoff = pll_vote_clk_handoff, +}; + +static void __pll_clk_enable_reg(void __iomem *mode_reg) +{ + u32 mode = readl_relaxed(mode_reg); + /* Disable PLL bypass mode. */ + mode |= PLL_BYPASSNL; + writel_relaxed(mode, 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 |= PLL_RESET_N; + writel_relaxed(mode, mode_reg); + + /* Wait until PLL is locked. */ + mb(); + udelay(50); + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, mode_reg); + + /* Ensure that the write above goes through before returning. */ + mb(); +} + +static int local_pll_clk_enable(struct clk *clk) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(clk); + + spin_lock_irqsave(&pll_reg_lock, flags); + __pll_clk_enable_reg(PLL_MODE_REG(pll)); + spin_unlock_irqrestore(&pll_reg_lock, flags); + + return 0; +} + +static void __pll_clk_disable_reg(void __iomem *mode_reg) +{ + u32 mode = readl_relaxed(mode_reg); + mode &= ~PLL_MODE_MASK; + writel_relaxed(mode, mode_reg); +} + +static void local_pll_clk_disable(struct clk *clk) +{ + 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(&pll_reg_lock, flags); + __pll_clk_disable_reg(PLL_MODE_REG(pll)); + spin_unlock_irqrestore(&pll_reg_lock, flags); +} + +static enum handoff local_pll_clk_handoff(struct clk *clk) +{ + struct pll_clk *pll = to_pll_clk(clk); + u32 mode = readl_relaxed(PLL_MODE_REG(pll)); + u32 mask = PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL; + + if ((mode & mask) == mask) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + +static struct clk *local_pll_clk_get_parent(struct clk *clk) +{ + struct pll_clk *pll = to_pll_clk(clk); + return pll->parent; +} + +int sr_pll_clk_enable(struct clk *clk) +{ + u32 mode; + unsigned long flags; + struct pll_clk *pll = to_pll_clk(clk); + + spin_lock_irqsave(&pll_reg_lock, flags); + mode = readl_relaxed(PLL_MODE_REG(pll)); + /* De-assert active-low PLL reset. */ + mode |= PLL_RESET_N; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* + * 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 |= PLL_BYPASSNL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Wait until PLL is locked. */ + mb(); + udelay(60); + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Ensure that the write above goes through before returning. */ + mb(); + + spin_unlock_irqrestore(&pll_reg_lock, flags); + + return 0; +} + +#define PLL_LOCKED_BIT BIT(16) + +int copper_pll_clk_enable(struct clk *clk) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(clk); + u32 count, mode; + int ret = 0; + + spin_lock_irqsave(&pll_reg_lock, flags); + mode = readl_relaxed(PLL_MODE_REG(pll)); + /* Disable PLL bypass mode. */ + mode |= PLL_BYPASSNL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* + * 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 |= PLL_RESET_N; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Wait for pll to enable. */ + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pll)) & PLL_LOCKED_BIT) + break; + udelay(1); + } + + if (!(readl_relaxed(PLL_STATUS_REG(pll)) & PLL_LOCKED_BIT)) { + WARN("PLL %s didn't lock after enabling it!\n", clk->dbg_name); + ret = -ETIMEDOUT; + goto out; + } + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Ensure the write above goes through before returning. */ + mb(); + +out: + spin_unlock_irqrestore(&pll_reg_lock, flags); + return ret; +} + +struct clk_ops clk_ops_local_pll = { + .enable = local_pll_clk_enable, + .disable = local_pll_clk_disable, + .auto_off = local_pll_clk_disable, + .handoff = local_pll_clk_handoff, + .get_parent = local_pll_clk_get_parent, +}; + +struct pll_rate { + unsigned int lvalue; + unsigned long rate; +}; + +static struct pll_rate pll_l_rate[] = { + {10, 196000000}, + {12, 245760000}, + {30, 589820000}, + {38, 737280000}, + {41, 800000000}, + {50, 960000000}, + {52, 1008000000}, + {60, 1152000000}, + {62, 1200000000}, + {63, 1209600000}, + {0, 0}, +}; + +#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 + PLL_END]; +}; + +static remote_spinlock_t pll_lock; +static struct shared_pll_control *pll_control; + +void __init msm_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_err("Can't find shared PLL control data structure!\n"); + BUG(); + /* + * 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_err("Shared PLL control data" + "structure too small!\n"); + BUG(); + } else if (pll_control->version != 0xCCEE0001) { + pr_err("Shared PLL control version mismatch!\n"); + BUG(); + } else { + pr_info("Shared PLL control available.\n"); + return; + } + +} + +static int pll_clk_enable(struct clk *clk) +{ + struct pll_shared_clk *pll = to_pll_shared_clk(clk); + unsigned int pll_id = pll->id; + + remote_spin_lock(&pll_lock); + + pll_control->pll[PLL_BASE + pll_id].votes |= BIT(1); + if (!pll_control->pll[PLL_BASE + pll_id].on) { + __pll_clk_enable_reg(PLL_MODE_REG(pll)); + pll_control->pll[PLL_BASE + pll_id].on = 1; + } + + remote_spin_unlock(&pll_lock); + return 0; +} + +static void pll_clk_disable(struct clk *clk) +{ + struct pll_shared_clk *pll = to_pll_shared_clk(clk); + unsigned int pll_id = pll->id; + + remote_spin_lock(&pll_lock); + + pll_control->pll[PLL_BASE + pll_id].votes &= ~BIT(1); + if (pll_control->pll[PLL_BASE + pll_id].on + && !pll_control->pll[PLL_BASE + pll_id].votes) { + __pll_clk_disable_reg(PLL_MODE_REG(pll)); + pll_control->pll[PLL_BASE + pll_id].on = 0; + } + + remote_spin_unlock(&pll_lock); +} + +static int pll_clk_is_enabled(struct clk *clk) +{ + struct pll_shared_clk *pll = to_pll_shared_clk(clk); + + return readl_relaxed(PLL_MODE_REG(pll)) & BIT(0); +} + +static enum handoff pll_clk_handoff(struct clk *clk) +{ + struct pll_shared_clk *pll = to_pll_shared_clk(clk); + unsigned int pll_lval; + struct pll_rate *l; + + /* + * Wait for the PLLs to be initialized and then read their frequency. + */ + do { + pll_lval = readl_relaxed(PLL_MODE_REG(pll) + 4) & 0x3ff; + cpu_relax(); + udelay(50); + } while (pll_lval == 0); + + /* Convert PLL L values to PLL Output rate */ + for (l = pll_l_rate; l->rate != 0; l++) { + if (l->lvalue == pll_lval) { + clk->rate = l->rate; + break; + } + } + + if (!clk->rate) { + pr_crit("Unknown PLL's L value!\n"); + BUG(); + } + + return HANDOFF_ENABLED_CLK; +} + +struct clk_ops clk_ops_pll = { + .enable = pll_clk_enable, + .disable = pll_clk_disable, + .handoff = pll_clk_handoff, + .is_enabled = pll_clk_is_enabled, +}; + +static void __init __set_fsm_mode(void __iomem *mode_reg) +{ + u32 regval = readl_relaxed(mode_reg); + + /* De-assert reset to FSM */ + regval &= ~BIT(21); + writel_relaxed(regval, mode_reg); + + /* Program bias count */ + regval &= ~BM(19, 14); + regval |= BVAL(19, 14, 0x1); + writel_relaxed(regval, mode_reg); + + /* Program lock count */ + regval &= ~BM(13, 8); + regval |= BVAL(13, 8, 0x8); + writel_relaxed(regval, mode_reg); + + /* Enable PLL FSM voting */ + regval |= BIT(20); + writel_relaxed(regval, mode_reg); +} + +void __init configure_pll(struct pll_config *config, + struct pll_config_regs *regs, u32 ena_fsm_mode) +{ + u32 regval; + + writel_relaxed(config->l, PLL_L_REG(regs)); + writel_relaxed(config->m, PLL_M_REG(regs)); + writel_relaxed(config->n, PLL_N_REG(regs)); + + regval = readl_relaxed(PLL_CONFIG_REG(regs)); + + /* Enable the MN accumulator */ + if (config->mn_ena_mask) { + regval &= ~config->mn_ena_mask; + regval |= config->mn_ena_val; + } + + /* Enable the main output */ + if (config->main_output_mask) { + regval &= ~config->main_output_mask; + regval |= config->main_output_val; + } + + /* Set pre-divider and post-divider values */ + regval &= ~config->pre_div_mask; + regval |= config->pre_div_val; + regval &= ~config->post_div_mask; + regval |= config->post_div_val; + + /* Select VCO setting */ + regval &= ~config->vco_mask; + regval |= config->vco_val; + writel_relaxed(regval, PLL_CONFIG_REG(regs)); + + /* Configure in FSM mode if necessary */ + if (ena_fsm_mode) + __set_fsm_mode(PLL_MODE_REG(regs)); +} diff --git a/arch/arm/mach-msm/clock-pll.h b/arch/arm/mach-msm/clock-pll.h new file mode 100644 index 0000000000000000000000000000000000000000..a8c642f2cc3194fdaeb96038f38701cb2578fa78 --- /dev/null +++ b/arch/arm/mach-msm/clock-pll.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PLL_H +#define __ARCH_ARM_MACH_MSM_CLOCK_PLL_H + +/** + * enum - For PLL IDs + */ +enum { + PLL_TCXO = -1, + PLL_0 = 0, + PLL_1, + PLL_2, + PLL_3, + PLL_4, + PLL_END, +}; + +/** + * struct pll_shared_clk - PLL shared with other processors without + * any HW voting + * @id: PLL ID + * @mode_reg: enable register + * @parent: clock source + * @c: clk + */ +struct pll_shared_clk { + unsigned int id; + void __iomem *const mode_reg; + struct clk c; + void *const __iomem *base; +}; + +extern struct clk_ops clk_ops_pll; + +static inline struct pll_shared_clk *to_pll_shared_clk(struct clk *clk) +{ + return container_of(clk, struct pll_shared_clk, c); +} + +/** + * msm_shared_pll_control_init() - Initialize shared pll control structure + */ +void msm_shared_pll_control_init(void); + +/** + * struct pll_vote_clk - phase locked loop (HW voteable) + * @soft_vote: soft voting variable for multiple PLL software instances + * @soft_vote_mask: soft voting mask for multiple PLL software instances + * @en_reg: enable register + * @en_mask: ORed with @en_reg to enable the clock + * @status_mask: ANDed with @status_reg to determine if PLL is active. + * @status_reg: status register + * @parent: clock source + * @c: clk + */ +struct pll_vote_clk { + u32 *soft_vote; + const u32 soft_vote_mask; + void __iomem *const en_reg; + const u32 en_mask; + void __iomem *const status_reg; + const u32 status_mask; + + struct clk *parent; + struct clk c; + void *const __iomem *base; +}; + +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 + * @mode_reg: enable register + * @status_reg: status register, contains the lock detection bit + * @parent: clock source + * @c: clk + * @base: pointer to base address of ioremapped registers. + */ +struct pll_clk { + void __iomem *const mode_reg; + void __iomem *const status_reg; + + struct clk *parent; + struct clk c; + void *const __iomem *base; +}; + +extern struct clk_ops clk_ops_local_pll; + +static inline struct pll_clk *to_pll_clk(struct clk *clk) +{ + return container_of(clk, struct pll_clk, c); +} + +int sr_pll_clk_enable(struct clk *clk); +int copper_pll_clk_enable(struct clk *clk); + +/* + * PLL vote clock APIs + */ +int pll_vote_clk_enable(struct clk *clk); +void pll_vote_clk_disable(struct clk *clk); +struct clk *pll_vote_clk_get_parent(struct clk *clk); +int pll_vote_clk_is_enabled(struct clk *clk); + +struct pll_config { + u32 l; + u32 m; + u32 n; + u32 vco_val; + u32 vco_mask; + u32 pre_div_val; + u32 pre_div_mask; + u32 post_div_val; + u32 post_div_mask; + u32 mn_ena_val; + u32 mn_ena_mask; + u32 main_output_val; + u32 main_output_mask; +}; + +struct pll_config_regs { + void __iomem *l_reg; + void __iomem *m_reg; + void __iomem *n_reg; + void __iomem *config_reg; + void __iomem *mode_reg; + void *const __iomem *base; +}; + +void __init configure_pll(struct pll_config *, struct pll_config_regs *, u32); + +#endif diff --git a/arch/arm/mach-msm/clock-rpm.c b/arch/arm/mach-msm/clock-rpm.c new file mode 100644 index 0000000000000000000000000000000000000000..ae87bb77ac96981382b7fcc2353c432bac36191d --- /dev/null +++ b/arch/arm/mach-msm/clock-rpm.c @@ -0,0 +1,204 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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 rpm_clk *r = to_rpm_clk(clk); + struct msm_rpm_iv_pair iv = { .id = r->rpm_clk_id }; + int rc = 0; + unsigned long this_khz, this_sleep_khz; + unsigned long 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; + + /* 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); + if (r->branch) + iv.value = !!iv.value; + + 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); + if (r->branch) + iv.value = !!iv.value; + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_SLEEP, &iv, 1); + if (rc) { + iv.value = peer_khz; + msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_0, &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 = { .id = r->rpm_clk_id }; + struct rpm_clk *peer = r->peer; + unsigned long peer_khz = 0, peer_sleep_khz = 0; + int rc; + + /* 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 = r->branch ? !!peer_khz : peer_khz; + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_0, &iv, 1); + if (rc) + goto out; + + iv.value = r->branch ? !!peer_sleep_khz : 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 int rpm_clk_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long flags; + struct rpm_clk *r = to_rpm_clk(clk); + unsigned long 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 long 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 long 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 long 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, + .set_rate = rpm_clk_set_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, +}; + +struct clk_ops clk_ops_rpm_branch = { + .enable = rpm_clk_enable, + .disable = rpm_clk_disable, + .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 0000000000000000000000000000000000000000..b0d5693c70e012ab156a1885f698563ad30bca2b --- /dev/null +++ b/arch/arm/mach-msm/clock-rpm.h @@ -0,0 +1,105 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; +extern struct clk_ops clk_ops_rpm_branch; + +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; + bool branch; /* true: RPM only accepts 1 for ON and 0 for OFF */ + + 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, dep) \ + 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, \ + .dbg_name = #name, \ + CLK_INIT(name.c), \ + .depends = dep, \ + }, \ + }; \ + 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, \ + .dbg_name = #active, \ + CLK_INIT(active.c), \ + .depends = dep, \ + }, \ + }; + +#define DEFINE_CLK_RPM_BRANCH(name, active, r_id, r) \ + 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, \ + .last_set_khz = ((r) / 1000), \ + .last_set_sleep_khz = ((r) / 1000), \ + .branch = true, \ + .c = { \ + .ops = &clk_ops_rpm_branch, \ + .flags = CLKFLAG_SKIP_AUTO_OFF, \ + .dbg_name = #name, \ + .rate = (r), \ + CLK_INIT(name.c), \ + .warned = true, \ + }, \ + }; \ + 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, \ + .last_set_khz = ((r) / 1000), \ + .active_only = true, \ + .branch = true, \ + .c = { \ + .ops = &clk_ops_rpm_branch, \ + .flags = CLKFLAG_SKIP_AUTO_OFF, \ + .dbg_name = #active, \ + .rate = (r), \ + CLK_INIT(active.c), \ + .warned = true, \ + }, \ + }; + +#endif diff --git a/arch/arm/mach-msm/clock-voter.c b/arch/arm/mach-msm/clock-voter.c new file mode 100644 index 0000000000000000000000000000000000000000..4cd9b1c8662972b3d5ff177fd0c1f117a51363b6 --- /dev/null +++ b/arch/arm/mach-msm/clock-voter.c @@ -0,0 +1,168 @@ +/* + * 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 long voter_clk_aggregate_rate(const struct clk *parent) +{ + struct clk *clk; + unsigned long rate = 0; + + list_for_each_entry(clk, &parent->children, siblings) { + struct clk_voter *v = to_clk_voter(clk); + if (v->enabled) + rate = max(clk->rate, rate); + } + return rate; +} + +static int voter_clk_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = 0; + unsigned long flags; + struct clk *clkp; + struct clk_voter *clkh, *v = to_clk_voter(clk); + unsigned long 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(clkp->rate, other_rate); + } + + cur_rate = max(other_rate, clk->rate); + new_rate = max(other_rate, rate); + + if (new_rate != cur_rate) { + ret = clk_set_rate(parent, new_rate); + if (ret) + goto unlock; + } + } + clk->rate = rate; +unlock: + spin_unlock_irqrestore(&voter_clk_lock, flags); + + return ret; +} + +static int voter_clk_enable(struct clk *clk) +{ + int ret = 0; + unsigned long flags; + unsigned long 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 (clk->rate > cur_rate) { + ret = clk_set_rate(parent, clk->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, cur_rate, new_rate; + struct clk *parent; + struct clk_voter *v = to_clk_voter(clk); + + 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, clk->rate); + + if (new_rate < cur_rate) + clk_set_rate(parent, new_rate); + + spin_unlock_irqrestore(&voter_clk_lock, flags); +} + +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 long rate) +{ + struct clk_voter *v = to_clk_voter(clk); + return clk_round_rate(v->parent, rate); +} + +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; +} + +static enum handoff voter_clk_handoff(struct clk *clk) +{ + /* Apply default rate vote */ + if (clk->rate) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + +struct clk_ops clk_ops_voter = { + .enable = voter_clk_enable, + .disable = voter_clk_disable, + .set_rate = voter_clk_set_rate, + .is_enabled = voter_clk_is_enabled, + .round_rate = voter_clk_round_rate, + .get_parent = voter_clk_get_parent, + .is_local = voter_clk_is_local, + .handoff = voter_clk_handoff, +}; diff --git a/arch/arm/mach-msm/clock-voter.h b/arch/arm/mach-msm/clock-voter.h new file mode 100644 index 0000000000000000000000000000000000000000..c9aebbab2dc42324a60c147978b4da79e976fafa --- /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; + 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, _default_rate) \ + struct clk_voter clk_name = { \ + .parent = _parent, \ + .c = { \ + .dbg_name = #clk_name, \ + .ops = &clk_ops_voter, \ + .flags = CLKFLAG_SKIP_AUTO_OFF, \ + .rate = _default_rate, \ + CLK_INIT(clk_name.c), \ + }, \ + } + +#endif diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c index d9145dfc2a3b04c4096c1dd10f4ac97ef9ac815f..fb5b5809efd7796c534318a7f37fb6b1cae8400b 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-2012, 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,170 +15,488 @@ */ #include -#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); +/* Find the voltage level required for a given rate. */ +static int find_vdd_level(struct clk *clk, unsigned long rate) +{ + int level; + + for (level = 0; level < ARRAY_SIZE(clk->fmax); level++) + if (rate <= clk->fmax[level]) + break; + + if (level == ARRAY_SIZE(clk->fmax)) { + pr_err("Rate %lu for %s is greater than highest Fmax\n", rate, + clk->dbg_name); + return -EINVAL; + } + + return level; +} + +/* Update voltage level given the current votes. */ +static int update_vdd(struct clk_vdd_class *vdd_class) +{ + int level, rc; + + for (level = ARRAY_SIZE(vdd_class->level_votes)-1; level > 0; level--) + if (vdd_class->level_votes[level]) + break; + + if (level == vdd_class->cur_level) + return 0; + + rc = vdd_class->set_vdd(vdd_class, level); + if (!rc) + vdd_class->cur_level = level; + + return rc; +} + +/* Vote for a voltage level. */ +int vote_vdd_level(struct clk_vdd_class *vdd_class, int level) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&vdd_class->lock, flags); + vdd_class->level_votes[level]++; + rc = update_vdd(vdd_class); + if (rc) + vdd_class->level_votes[level]--; + spin_unlock_irqrestore(&vdd_class->lock, flags); + + return rc; +} + +/* Remove vote for a voltage level. */ +int unvote_vdd_level(struct clk_vdd_class *vdd_class, int level) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&vdd_class->lock, flags); + if (WARN(!vdd_class->level_votes[level], + "Reference counts are incorrect for %s level %d\n", + vdd_class->class_name, level)) + goto out; + vdd_class->level_votes[level]--; + rc = update_vdd(vdd_class); + if (rc) + vdd_class->level_votes[level]++; +out: + spin_unlock_irqrestore(&vdd_class->lock, flags); + return rc; +} + +/* Vote for a voltage level corresponding to a clock's rate. */ +static int vote_rate_vdd(struct clk *clk, unsigned long rate) +{ + int level; + + if (!clk->vdd_class) + return 0; + + level = find_vdd_level(clk, rate); + if (level < 0) + return level; + + return vote_vdd_level(clk->vdd_class, level); +} + +/* Remove vote for a voltage level corresponding to a clock's rate. */ +static void unvote_rate_vdd(struct clk *clk, unsigned long rate) +{ + int level; + + if (!clk->vdd_class) + return; + + level = find_vdd_level(clk, rate); + if (level < 0) + return; + + unvote_vdd_level(clk->vdd_class, level); +} + +int clk_prepare(struct clk *clk) +{ + int ret = 0; + struct clk *parent; + + if (!clk) + return 0; + if (IS_ERR(clk)) + return -EINVAL; + + mutex_lock(&clk->prepare_lock); + if (clk->prepare_count == 0) { + parent = clk_get_parent(clk); + + ret = clk_prepare(parent); + if (ret) + goto out; + ret = clk_prepare(clk->depends); + if (ret) + goto err_prepare_depends; + + if (clk->ops->prepare) + ret = clk->ops->prepare(clk); + if (ret) + goto err_prepare_clock; + } + clk->prepare_count++; +out: + mutex_unlock(&clk->prepare_lock); + return ret; +err_prepare_clock: + clk_unprepare(clk->depends); +err_prepare_depends: + clk_unprepare(parent); + goto out; +} +EXPORT_SYMBOL(clk_prepare); /* * 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; + if (IS_ERR(clk)) + return -EINVAL; + + spin_lock_irqsave(&clk->lock, flags); + if (WARN(!clk->warned && !clk->prepare_count, + "%s: Don't call enable on unprepared clocks\n", + clk->dbg_name)) + clk->warned = true; + if (clk->count == 0) { + parent = clk_get_parent(clk); + + ret = clk_enable(parent); + if (ret) + goto err_enable_parent; + ret = clk_enable(clk->depends); + if (ret) + goto err_enable_depends; + + ret = vote_rate_vdd(clk, clk->rate); + if (ret) + goto err_vote_vdd; + trace_clock_enable(clk->dbg_name, 1, smp_processor_id()); + if (clk->ops->enable) + ret = clk->ops->enable(clk); + if (ret) + goto err_enable_clock; + } else if (clk->flags & CLKFLAG_HANDOFF_RATE) { + /* + * The clock was already enabled by handoff code so there is no + * need to enable it again here. Clearing the handoff flag will + * prevent the lateinit handoff code from disabling the clock if + * a client driver still has it enabled. + */ + clk->flags &= ~CLKFLAG_HANDOFF_RATE; + goto out; + } clk->count++; - if (clk->count == 1) - clk->ops->enable(clk->id); - spin_unlock_irqrestore(&clocks_lock, flags); +out: + spin_unlock_irqrestore(&clk->lock, flags); + return 0; + +err_enable_clock: + unvote_rate_vdd(clk, clk->rate); +err_vote_vdd: + clk_disable(clk->depends); +err_enable_depends: + clk_disable(parent); +err_enable_parent: + 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); + + if (IS_ERR_OR_NULL(clk)) + return; + + spin_lock_irqsave(&clk->lock, flags); + if (WARN(!clk->warned && !clk->prepare_count, + "%s: Never called prepare or calling disable " + "after unprepare\n", + clk->dbg_name)) + clk->warned = true; + if (WARN(clk->count == 0, "%s is unbalanced", clk->dbg_name)) + goto out; + if (clk->count == 1) { + struct clk *parent = clk_get_parent(clk); + + trace_clock_disable(clk->dbg_name, 0, smp_processor_id()); + if (clk->ops->disable) + clk->ops->disable(clk); + unvote_rate_vdd(clk, clk->rate); + clk_disable(clk->depends); + 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); +void clk_unprepare(struct clk *clk) +{ + if (IS_ERR_OR_NULL(clk)) + return; + + mutex_lock(&clk->prepare_lock); + if (!clk->prepare_count) { + if (WARN(!clk->warned, "%s is unbalanced (prepare)", + clk->dbg_name)) + clk->warned = true; + goto out; + } + if (clk->prepare_count == 1) { + struct clk *parent = clk_get_parent(clk); + + if (WARN(!clk->warned && clk->count, + "%s: Don't call unprepare when the clock is enabled\n", + clk->dbg_name)) + clk->warned = true; + + if (clk->ops->unprepare) + clk->ops->unprepare(clk); + clk_unprepare(clk->depends); + clk_unprepare(parent); + } + clk->prepare_count--; +out: + mutex_unlock(&clk->prepare_lock); +} +EXPORT_SYMBOL(clk_unprepare); + int clk_reset(struct clk *clk, enum clk_reset_action action) { - return clk->ops->reset(clk->remote_id, action); + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + 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 (IS_ERR_OR_NULL(clk)) + return 0; + + if (!clk->ops->get_rate) + return clk->rate; + + 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; + unsigned long start_rate, flags; + int rc; + + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + if (!clk->ops->set_rate) + return -ENOSYS; + + spin_lock_irqsave(&clk->lock, flags); + trace_clock_set_rate(clk->dbg_name, rate, smp_processor_id()); + if (clk->count) { + start_rate = clk->rate; + /* Enforce vdd requirements for target frequency. */ + rc = vote_rate_vdd(clk, rate); + if (rc) + goto err_vote_vdd; + rc = clk->ops->set_rate(clk, rate); + if (rc) + goto err_set_rate; + /* Release vdd requirements for starting frequency. */ + unvote_rate_vdd(clk, start_rate); + } else { + rc = clk->ops->set_rate(clk, rate); } - if (clk->flags & CLKFLAG_MAX || clk->flags & CLKFLAG_MIN) - return ret; + if (!rc) + clk->rate = rate; - return clk->ops->set_rate(clk->id, rate); + spin_unlock_irqrestore(&clk->lock, flags); + return rc; + +err_set_rate: + unvote_rate_vdd(clk, rate); +err_vote_vdd: + spin_unlock_irqrestore(&clk->lock, flags); + return rc; } EXPORT_SYMBOL(clk_set_rate); long clk_round_rate(struct clk *clk, unsigned long rate) { - return clk->ops->round_rate(clk->id, rate); -} -EXPORT_SYMBOL(clk_round_rate); + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; -int clk_set_min_rate(struct clk *clk, unsigned long rate) -{ - return clk->ops->set_min_rate(clk->id, rate); + if (!clk->ops->round_rate) + return -ENOSYS; + + return clk->ops->round_rate(clk, rate); } -EXPORT_SYMBOL(clk_set_min_rate); +EXPORT_SYMBOL(clk_round_rate); int clk_set_max_rate(struct clk *clk, unsigned long rate) { - return clk->ops->set_max_rate(clk->id, rate); + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + 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 (IS_ERR_OR_NULL(clk)) + return NULL; + + if (!clk->ops->get_parent) + return NULL; + + return clk->ops->get_parent(clk); } EXPORT_SYMBOL(clk_get_parent); int clk_set_flags(struct clk *clk, unsigned long flags) { - if (clk == NULL || IS_ERR(clk)) + if (IS_ERR_OR_NULL(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 clock_init_data __initdata *clk_init_data; -void __init msm_clock_init(struct clk_lookup *clock_tbl, unsigned num_clocks) +void __init msm_clock_init(struct clock_init_data *data) { unsigned n; + struct clk_lookup *clock_tbl; + size_t num_clocks; + struct clk *clk; + + clk_init_data = data; + if (clk_init_data->pre_init) + clk_init_data->pre_init(); + + clock_tbl = data->table; + num_clocks = data->size; - 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 *parent; + clk = clock_tbl[n].clk; + parent = clk_get_parent(clk); + if (parent && list_empty(&clk->siblings)) + list_add(&clk->siblings, &parent->children); + } + + /* + * Detect and preserve initial clock state until clock_late_init() or + * a driver explicitly changes it, whichever is first. + */ + for (n = 0; n < num_clocks; n++) { + clk = clock_tbl[n].clk; + if (clk->ops->handoff && !(clk->flags & CLKFLAG_HANDOFF_RATE) && + (clk->ops->handoff(clk) == HANDOFF_ENABLED_CLK)) { + clk->flags |= CLKFLAG_HANDOFF_RATE; + clk_prepare_enable(clk); + } } - mutex_unlock(&clocks_mutex); - ebi1_clk = clk_get(NULL, "ebi1_clk"); - BUG_ON(ebi1_clk == NULL); + clkdev_add_table(clock_tbl, num_clocks); + if (clk_init_data->post_init) + clk_init_data->post_init(); } -/* 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, count = 0; unsigned long flags; - struct clk *clk; - unsigned count = 0; + int ret = 0; + + clock_debug_init(clk_init_data); + for (n = 0; n < clk_init_data->size; n++) { + struct clk *clk = clk_init_data->table[n].clk; + bool handoff = false; - clock_debug_init(); - mutex_lock(&clocks_mutex); - list_for_each_entry(clk, &clocks, list) { clock_debug_add(clk); - if (clk->flags & CLKFLAG_AUTO_OFF) { - spin_lock_irqsave(&clocks_lock, flags); - if (!clk->count) { + spin_lock_irqsave(&clk->lock, flags); + if (!(clk->flags & CLKFLAG_SKIP_AUTO_OFF)) { + 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); } + if (clk->flags & CLKFLAG_HANDOFF_RATE) { + clk->flags &= ~CLKFLAG_HANDOFF_RATE; + handoff = true; + } + spin_unlock_irqrestore(&clk->lock, flags); + /* + * Calling this outside the lock is safe since + * it doesn't need to be atomic with the flag change. + */ + if (handoff) + clk_disable_unprepare(clk); } - mutex_unlock(&clocks_mutex); pr_info("clock_late_init() disabled %d unused clocks\n", count); - return 0; + if (clk_init_data->late_init) + ret = clk_init_data->late_init(); + return ret; } - late_initcall(clock_late_init); - diff --git a/arch/arm/mach-msm/clock.h b/arch/arm/mach-msm/clock.h index 2c007f606d2921e61d8692bd7a3e288cf08d588b..1be05ad7a362680a536dd3e042e3a4b32ced3e33 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-2012, 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,56 +17,186 @@ #ifndef __ARCH_ARM_MACH_MSM_CLOCK_H #define __ARCH_ARM_MACH_MSM_CLOCK_H -#include +#include #include +#include +#include +#include + #include #define CLKFLAG_INVERT 0x00000001 #define CLKFLAG_NOINVERT 0x00000002 #define CLKFLAG_NONEST 0x00000004 #define CLKFLAG_NORESET 0x00000008 - -#define CLK_FIRST_AVAILABLE_FLAG 0x00000100 -#define CLKFLAG_AUTO_OFF 0x00000200 +#define CLKFLAG_HANDOFF_RATE 0x00000010 +#define CLKFLAG_HWCG 0x00000020 +#define CLKFLAG_RETAIN 0x00000040 +#define CLKFLAG_NORETAIN 0x00000080 +#define CLKFLAG_SKIP_AUTO_OFF 0x00000200 #define CLKFLAG_MIN 0x00000400 #define CLKFLAG_MAX 0x00000800 +/* + * 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 */ + +#define MAX_VDD_LEVELS 4 + +/** + * struct clk_vdd_class - Voltage scaling class + * @class_name: name of the class + * @set_vdd: function to call when applying a new voltage setting + * @level_votes: array of votes for each level + * @cur_level: the currently set voltage level + * @lock: lock to protect this struct + */ +struct clk_vdd_class { + const char *class_name; + int (*set_vdd)(struct clk_vdd_class *v_class, int level); + int level_votes[MAX_VDD_LEVELS]; + unsigned long cur_level; + spinlock_t lock; +}; + +#define DEFINE_VDD_CLASS(_name, _set_vdd) \ + struct clk_vdd_class _name = { \ + .class_name = #_name, \ + .set_vdd = _set_vdd, \ + .cur_level = ARRAY_SIZE(_name.level_votes), \ + .lock = __SPIN_LOCK_UNLOCKED(lock) \ + } + +enum handoff { + HANDOFF_ENABLED_CLK, + HANDOFF_DISABLED_CLK, + HANDOFF_UNKNOWN_RATE, +}; + 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 (*prepare)(struct clk *clk); + int (*enable)(struct clk *clk); + void (*disable)(struct clk *clk); + void (*unprepare)(struct clk *clk); + void (*auto_off)(struct clk *clk); + void (*enable_hwcg)(struct clk *clk); + void (*disable_hwcg)(struct clk *clk); + int (*in_hwcg_mode)(struct clk *clk); + enum handoff (*handoff)(struct clk *clk); + int (*reset)(struct clk *clk, enum clk_reset_action action); + int (*set_rate)(struct clk *clk, unsigned long rate); + int (*set_max_rate)(struct clk *clk, unsigned long rate); + int (*set_flags)(struct clk *clk, unsigned flags); + unsigned long (*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 long 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 + * @prepare_count: prepare refcount + * @prepare_lock: protects clk_prepare()/clk_unprepare() path and @prepare_count + * @count: enable refcount + * @lock: protects clk_enable()/clk_disable() path and @count + * @depends: non-direct parent of clock to enable when this clock is enabled + * @vdd_class: voltage scaling requirement class + * @fmax: maximum frequency in Hz supported at each voltage level + * @warned: true if the clock has warned of incorrect usage, false otherwise + */ 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 clk *depends; + struct clk_vdd_class *vdd_class; + unsigned long fmax[MAX_VDD_LEVELS]; + unsigned long rate; + + struct list_head children; + struct list_head siblings; + + bool warned; + unsigned count; + spinlock_t lock; + unsigned prepare_count; + struct mutex prepare_lock; +}; + +#define CLK_INIT(name) \ + .lock = __SPIN_LOCK_UNLOCKED((name).lock), \ + .prepare_lock = __MUTEX_INITIALIZER((name).prepare_lock), \ + .children = LIST_HEAD_INIT((name).children), \ + .siblings = LIST_HEAD_INIT((name).siblings) + +/** + * struct clock_init_data - SoC specific clock initialization data + * @table: table of lookups to add + * @size: size of @table + * @pre_init: called before initializing the clock driver. + * @post_init: called after registering @table. clock APIs can be called inside. + * @late_init: called during late init + */ +struct clock_init_data { + struct clk_lookup *table; + size_t size; + void (*pre_init)(void); + void (*post_init)(void); + int (*late_init)(void); }; -#define OFF CLKFLAG_AUTO_OFF -#define CLK_MIN CLKFLAG_MIN -#define CLK_MAX CLKFLAG_MAX -#define CLK_MINMAX (CLK_MIN | CLK_MAX) +extern struct clock_init_data msm9615_clock_init_data; +extern struct clock_init_data apq8064_clock_init_data; +extern struct clock_init_data fsm9xxx_clock_init_data; +extern struct clock_init_data msm7x01a_clock_init_data; +extern struct clock_init_data msm7x27_clock_init_data; +extern struct clock_init_data msm7x27a_clock_init_data; +extern struct clock_init_data msm7x30_clock_init_data; +extern struct clock_init_data msm8960_clock_init_data; +extern struct clock_init_data msm8x60_clock_init_data; +extern struct clock_init_data qds8x50_clock_init_data; +extern struct clock_init_data msm8625_dummy_clock_init_data; +extern struct clock_init_data msm8930_clock_init_data; +extern struct clock_init_data msmcopper_clock_init_data; + +void msm_clock_init(struct clock_init_data *data); +int vote_vdd_level(struct clk_vdd_class *vdd_class, int level); +int unvote_vdd_level(struct clk_vdd_class *vdd_class, int level); #ifdef CONFIG_DEBUG_FS -int __init clock_debug_init(void); -int __init clock_debug_add(struct clk *clock); +int clock_debug_init(struct clock_init_data *data); +int 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_add(struct clk *clock) { return 0; } +static inline int clock_debug_init(struct clk_init_data *data) { return 0; } +static inline int 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.h b/arch/arm/mach-msm/cp14.h new file mode 100644 index 0000000000000000000000000000000000000000..d6404120aafa8929e8fba2241bd46985795221da --- /dev/null +++ b/arch/arm/mach-msm/cp14.h @@ -0,0 +1,540 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_CP14_H_ +#define _ARCH_ARM_MACH_MSM_CP14_H_ + +#include + +/* Accessors for CP14 registers */ +#define dbg_read(reg) RCP14_##reg() +#define dbg_write(val, reg) WCP14_##reg(val) +#define etm_read(reg) RCP14_##reg() +#define etm_write(val, reg) WCP14_##reg(val) + +/* MRC14 and MCR14 */ +#define MRC14(op1, crn, crm, op2) \ +({ \ +uint32_t val; \ +asm volatile("mrc p14, "#op1", %0, "#crn", "#crm", "#op2 : "=r" (val)); \ +val; \ +}) + +#define MCR14(val, op1, crn, crm, op2) \ +({ \ +asm volatile("mcr p14, "#op1", %0, "#crn", "#crm", "#op2 : : "r" (val));\ +}) + +/* Debug Registers + * + * Available only in DBGv7 + * DBGECR, DBGDSCCR, DBGDSMCR, DBGDRCR + * + * Available only in DBGv7.1 + * DBGBXVRm, DBGOSDLR, DBGDEVID2, DBGDEVID1 + * + * Read only + * DBGDIDR, DBGDSCRint, DBGDTRRXint, DBGDRAR, DBGOSLSR, DBGOSSRR, DBGPRSR, + * DBGPRSR, DBGDSAR, DBGAUTHSTATUS, DBGDEVID2, DBGDEVID1, DBGDEVID + * + * Write only + * DBGDTRTXint, DBGOSLAR + */ +#define RCP14_DBGDIDR() MRC14(0, c0, c0, 0) +#define RCP14_DBGDSCRint() MRC14(0, c0, c1, 0) +#define RCP14_DBGDTRRXint() MRC14(0, c0, c5, 0) +#define RCP14_DBGWFAR() MRC14(0, c0, c6, 0) +#define RCP14_DBGVCR() MRC14(0, c0, c7, 0) +#define RCP14_DBGECR() MRC14(0, c0, c9, 0) +#define RCP14_DBGDSCCR() MRC14(0, c0, c10, 0) +#define RCP14_DBGDSMCR() MRC14(0, c0, c11, 0) +#define RCP14_DBGDTRRXext() MRC14(0, c0, c0, 2) +#define RCP14_DBGDSCRext() MRC14(0, c0, c2, 2) +#define RCP14_DBGDTRTXext() MRC14(0, c0, c3, 2) +#define RCP14_DBGDRCR() MRC14(0, c0, c4, 2) +#define RCP14_DBGBVR0() MRC14(0, c0, c0, 4) +#define RCP14_DBGBVR1() MRC14(0, c0, c1, 4) +#define RCP14_DBGBVR2() MRC14(0, c0, c2, 4) +#define RCP14_DBGBVR3() MRC14(0, c0, c3, 4) +#define RCP14_DBGBVR4() MRC14(0, c0, c4, 4) +#define RCP14_DBGBVR5() MRC14(0, c0, c5, 4) +#define RCP14_DBGBVR6() MRC14(0, c0, c6, 4) +#define RCP14_DBGBVR7() MRC14(0, c0, c7, 4) +#define RCP14_DBGBVR8() MRC14(0, c0, c8, 4) +#define RCP14_DBGBVR9() MRC14(0, c0, c9, 4) +#define RCP14_DBGBVR10() MRC14(0, c0, c10, 4) +#define RCP14_DBGBVR11() MRC14(0, c0, c11, 4) +#define RCP14_DBGBVR12() MRC14(0, c0, c12, 4) +#define RCP14_DBGBVR13() MRC14(0, c0, c13, 4) +#define RCP14_DBGBVR14() MRC14(0, c0, c14, 4) +#define RCP14_DBGBVR15() MRC14(0, c0, c15, 4) +#define RCP14_DBGBCR0() MRC14(0, c0, c0, 5) +#define RCP14_DBGBCR1() MRC14(0, c0, c1, 5) +#define RCP14_DBGBCR2() MRC14(0, c0, c2, 5) +#define RCP14_DBGBCR3() MRC14(0, c0, c3, 5) +#define RCP14_DBGBCR4() MRC14(0, c0, c4, 5) +#define RCP14_DBGBCR5() MRC14(0, c0, c5, 5) +#define RCP14_DBGBCR6() MRC14(0, c0, c6, 5) +#define RCP14_DBGBCR7() MRC14(0, c0, c7, 5) +#define RCP14_DBGBCR8() MRC14(0, c0, c8, 5) +#define RCP14_DBGBCR9() MRC14(0, c0, c9, 5) +#define RCP14_DBGBCR10() MRC14(0, c0, c10, 5) +#define RCP14_DBGBCR11() MRC14(0, c0, c11, 5) +#define RCP14_DBGBCR12() MRC14(0, c0, c12, 5) +#define RCP14_DBGBCR13() MRC14(0, c0, c13, 5) +#define RCP14_DBGBCR14() MRC14(0, c0, c14, 5) +#define RCP14_DBGBCR15() MRC14(0, c0, c15, 5) +#define RCP14_DBGWVR0() MRC14(0, c0, c0, 6) +#define RCP14_DBGWVR1() MRC14(0, c0, c1, 6) +#define RCP14_DBGWVR2() MRC14(0, c0, c2, 6) +#define RCP14_DBGWVR3() MRC14(0, c0, c3, 6) +#define RCP14_DBGWVR4() MRC14(0, c0, c4, 6) +#define RCP14_DBGWVR5() MRC14(0, c0, c5, 6) +#define RCP14_DBGWVR6() MRC14(0, c0, c6, 6) +#define RCP14_DBGWVR7() MRC14(0, c0, c7, 6) +#define RCP14_DBGWVR8() MRC14(0, c0, c8, 6) +#define RCP14_DBGWVR9() MRC14(0, c0, c9, 6) +#define RCP14_DBGWVR10() MRC14(0, c0, c10, 6) +#define RCP14_DBGWVR11() MRC14(0, c0, c11, 6) +#define RCP14_DBGWVR12() MRC14(0, c0, c12, 6) +#define RCP14_DBGWVR13() MRC14(0, c0, c13, 6) +#define RCP14_DBGWVR14() MRC14(0, c0, c14, 6) +#define RCP14_DBGWVR15() MRC14(0, c0, c15, 6) +#define RCP14_DBGWCR0() MRC14(0, c0, c0, 7) +#define RCP14_DBGWCR1() MRC14(0, c0, c1, 7) +#define RCP14_DBGWCR2() MRC14(0, c0, c2, 7) +#define RCP14_DBGWCR3() MRC14(0, c0, c3, 7) +#define RCP14_DBGWCR4() MRC14(0, c0, c4, 7) +#define RCP14_DBGWCR5() MRC14(0, c0, c5, 7) +#define RCP14_DBGWCR6() MRC14(0, c0, c6, 7) +#define RCP14_DBGWCR7() MRC14(0, c0, c7, 7) +#define RCP14_DBGWCR8() MRC14(0, c0, c8, 7) +#define RCP14_DBGWCR9() MRC14(0, c0, c9, 7) +#define RCP14_DBGWCR10() MRC14(0, c0, c10, 7) +#define RCP14_DBGWCR11() MRC14(0, c0, c11, 7) +#define RCP14_DBGWCR12() MRC14(0, c0, c12, 7) +#define RCP14_DBGWCR13() MRC14(0, c0, c13, 7) +#define RCP14_DBGWCR14() MRC14(0, c0, c14, 7) +#define RCP14_DBGWCR15() MRC14(0, c0, c15, 7) +#define RCP14_DBGDRAR() MRC14(0, c1, c0, 0) +#define RCP14_DBGBXVR0() MRC14(0, c1, c0, 1) +#define RCP14_DBGBXVR1() MRC14(0, c1, c1, 1) +#define RCP14_DBGBXVR2() MRC14(0, c1, c2, 1) +#define RCP14_DBGBXVR3() MRC14(0, c1, c3, 1) +#define RCP14_DBGBXVR4() MRC14(0, c1, c4, 1) +#define RCP14_DBGBXVR5() MRC14(0, c1, c5, 1) +#define RCP14_DBGBXVR6() MRC14(0, c1, c6, 1) +#define RCP14_DBGBXVR7() MRC14(0, c1, c7, 1) +#define RCP14_DBGBXVR8() MRC14(0, c1, c8, 1) +#define RCP14_DBGBXVR9() MRC14(0, c1, c9, 1) +#define RCP14_DBGBXVR10() MRC14(0, c1, c10, 1) +#define RCP14_DBGBXVR11() MRC14(0, c1, c11, 1) +#define RCP14_DBGBXVR12() MRC14(0, c1, c12, 1) +#define RCP14_DBGBXVR13() MRC14(0, c1, c13, 1) +#define RCP14_DBGBXVR14() MRC14(0, c1, c14, 1) +#define RCP14_DBGBXVR15() MRC14(0, c1, c15, 1) +#define RCP14_DBGOSLSR() MRC14(0, c1, c1, 4) +#define RCP14_DBGOSSRR() MRC14(0, c1, c2, 4) +#define RCP14_DBGOSDLR() MRC14(0, c1, c3, 4) +#define RCP14_DBGPRCR() MRC14(0, c1, c4, 4) +#define RCP14_DBGPRSR() MRC14(0, c1, c5, 4) +#define RCP14_DBGDSAR() MRC14(0, c2, c0, 0) +#define RCP14_DBGITCTRL() MRC14(0, c7, c0, 4) +#define RCP14_DBGCLAIMSET() MRC14(0, c7, c8, 6) +#define RCP14_DBGCLAIMCLR() MRC14(0, c7, c9, 6) +#define RCP14_DBGAUTHSTATUS() MRC14(0, c7, c14, 6) +#define RCP14_DBGDEVID2() MRC14(0, c7, c0, 7) +#define RCP14_DBGDEVID1() MRC14(0, c7, c1, 7) +#define RCP14_DBGDEVID() MRC14(0, c7, c2, 7) + +#define WCP14_DBGDTRTXint(val) MCR14(val, 0, c0, c5, 0) +#define WCP14_DBGWFAR(val) MCR14(val, 0, c0, c6, 0) +#define WCP14_DBGVCR(val) MCR14(val, 0, c0, c7, 0) +#define WCP14_DBGECR(val) MCR14(val, 0, c0, c9, 0) +#define WCP14_DBGDSCCR(val) MCR14(val, 0, c0, c10, 0) +#define WCP14_DBGDSMCR(val) MCR14(val, 0, c0, c11, 0) +#define WCP14_DBGDTRRXext(val) MCR14(val, 0, c0, c0, 2) +#define WCP14_DBGDSCRext(val) MCR14(val, 0, c0, c2, 2) +#define WCP14_DBGDTRTXext(val) MCR14(val, 0, c0, c3, 2) +#define WCP14_DBGDRCR(val) MCR14(val, 0, c0, c4, 2) +#define WCP14_DBGBVR0(val) MCR14(val, 0, c0, c0, 4) +#define WCP14_DBGBVR1(val) MCR14(val, 0, c0, c1, 4) +#define WCP14_DBGBVR2(val) MCR14(val, 0, c0, c2, 4) +#define WCP14_DBGBVR3(val) MCR14(val, 0, c0, c3, 4) +#define WCP14_DBGBVR4(val) MCR14(val, 0, c0, c4, 4) +#define WCP14_DBGBVR5(val) MCR14(val, 0, c0, c5, 4) +#define WCP14_DBGBVR6(val) MCR14(val, 0, c0, c6, 4) +#define WCP14_DBGBVR7(val) MCR14(val, 0, c0, c7, 4) +#define WCP14_DBGBVR8(val) MCR14(val, 0, c0, c8, 4) +#define WCP14_DBGBVR9(val) MCR14(val, 0, c0, c9, 4) +#define WCP14_DBGBVR10(val) MCR14(val, 0, c0, c10, 4) +#define WCP14_DBGBVR11(val) MCR14(val, 0, c0, c11, 4) +#define WCP14_DBGBVR12(val) MCR14(val, 0, c0, c12, 4) +#define WCP14_DBGBVR13(val) MCR14(val, 0, c0, c13, 4) +#define WCP14_DBGBVR14(val) MCR14(val, 0, c0, c14, 4) +#define WCP14_DBGBVR15(val) MCR14(val, 0, c0, c15, 4) +#define WCP14_DBGBCR0(val) MCR14(val, 0, c0, c0, 5) +#define WCP14_DBGBCR1(val) MCR14(val, 0, c0, c1, 5) +#define WCP14_DBGBCR2(val) MCR14(val, 0, c0, c2, 5) +#define WCP14_DBGBCR3(val) MCR14(val, 0, c0, c3, 5) +#define WCP14_DBGBCR4(val) MCR14(val, 0, c0, c4, 5) +#define WCP14_DBGBCR5(val) MCR14(val, 0, c0, c5, 5) +#define WCP14_DBGBCR6(val) MCR14(val, 0, c0, c6, 5) +#define WCP14_DBGBCR7(val) MCR14(val, 0, c0, c7, 5) +#define WCP14_DBGBCR8(val) MCR14(val, 0, c0, c8, 5) +#define WCP14_DBGBCR9(val) MCR14(val, 0, c0, c9, 5) +#define WCP14_DBGBCR10(val) MCR14(val, 0, c0, c10, 5) +#define WCP14_DBGBCR11(val) MCR14(val, 0, c0, c11, 5) +#define WCP14_DBGBCR12(val) MCR14(val, 0, c0, c12, 5) +#define WCP14_DBGBCR13(val) MCR14(val, 0, c0, c13, 5) +#define WCP14_DBGBCR14(val) MCR14(val, 0, c0, c14, 5) +#define WCP14_DBGBCR15(val) MCR14(val, 0, c0, c15, 5) +#define WCP14_DBGWVR0(val) MCR14(val, 0, c0, c0, 6) +#define WCP14_DBGWVR1(val) MCR14(val, 0, c0, c1, 6) +#define WCP14_DBGWVR2(val) MCR14(val, 0, c0, c2, 6) +#define WCP14_DBGWVR3(val) MCR14(val, 0, c0, c3, 6) +#define WCP14_DBGWVR4(val) MCR14(val, 0, c0, c4, 6) +#define WCP14_DBGWVR5(val) MCR14(val, 0, c0, c5, 6) +#define WCP14_DBGWVR6(val) MCR14(val, 0, c0, c6, 6) +#define WCP14_DBGWVR7(val) MCR14(val, 0, c0, c7, 6) +#define WCP14_DBGWVR8(val) MCR14(val, 0, c0, c8, 6) +#define WCP14_DBGWVR9(val) MCR14(val, 0, c0, c9, 6) +#define WCP14_DBGWVR10(val) MCR14(val, 0, c0, c10, 6) +#define WCP14_DBGWVR11(val) MCR14(val, 0, c0, c11, 6) +#define WCP14_DBGWVR12(val) MCR14(val, 0, c0, c12, 6) +#define WCP14_DBGWVR13(val) MCR14(val, 0, c0, c13, 6) +#define WCP14_DBGWVR14(val) MCR14(val, 0, c0, c14, 6) +#define WCP14_DBGWVR15(val) MCR14(val, 0, c0, c15, 6) +#define WCP14_DBGWCR0(val) MCR14(val, 0, c0, c0, 7) +#define WCP14_DBGWCR1(val) MCR14(val, 0, c0, c1, 7) +#define WCP14_DBGWCR2(val) MCR14(val, 0, c0, c2, 7) +#define WCP14_DBGWCR3(val) MCR14(val, 0, c0, c3, 7) +#define WCP14_DBGWCR4(val) MCR14(val, 0, c0, c4, 7) +#define WCP14_DBGWCR5(val) MCR14(val, 0, c0, c5, 7) +#define WCP14_DBGWCR6(val) MCR14(val, 0, c0, c6, 7) +#define WCP14_DBGWCR7(val) MCR14(val, 0, c0, c7, 7) +#define WCP14_DBGWCR8(val) MCR14(val, 0, c0, c8, 7) +#define WCP14_DBGWCR9(val) MCR14(val, 0, c0, c9, 7) +#define WCP14_DBGWCR10(val) MCR14(val, 0, c0, c10, 7) +#define WCP14_DBGWCR11(val) MCR14(val, 0, c0, c11, 7) +#define WCP14_DBGWCR12(val) MCR14(val, 0, c0, c12, 7) +#define WCP14_DBGWCR13(val) MCR14(val, 0, c0, c13, 7) +#define WCP14_DBGWCR14(val) MCR14(val, 0, c0, c14, 7) +#define WCP14_DBGWCR15(val) MCR14(val, 0, c0, c15, 7) +#define WCP14_DBGBXVR0(val) MCR14(val, 0, c1, c0, 1) +#define WCP14_DBGBXVR1(val) MCR14(val, 0, c1, c1, 1) +#define WCP14_DBGBXVR2(val) MCR14(val, 0, c1, c2, 1) +#define WCP14_DBGBXVR3(val) MCR14(val, 0, c1, c3, 1) +#define WCP14_DBGBXVR4(val) MCR14(val, 0, c1, c4, 1) +#define WCP14_DBGBXVR5(val) MCR14(val, 0, c1, c5, 1) +#define WCP14_DBGBXVR6(val) MCR14(val, 0, c1, c6, 1) +#define WCP14_DBGBXVR7(val) MCR14(val, 0, c1, c7, 1) +#define WCP14_DBGBXVR8(val) MCR14(val, 0, c1, c8, 1) +#define WCP14_DBGBXVR9(val) MCR14(val, 0, c1, c9, 1) +#define WCP14_DBGBXVR10(val) MCR14(val, 0, c1, c10, 1) +#define WCP14_DBGBXVR11(val) MCR14(val, 0, c1, c11, 1) +#define WCP14_DBGBXVR12(val) MCR14(val, 0, c1, c12, 1) +#define WCP14_DBGBXVR13(val) MCR14(val, 0, c1, c13, 1) +#define WCP14_DBGBXVR14(val) MCR14(val, 0, c1, c14, 1) +#define WCP14_DBGBXVR15(val) MCR14(val, 0, c1, c15, 1) +#define WCP14_DBGOSLAR(val) MCR14(val, 0, c1, c0, 4) +#define WCP14_DBGOSSRR(val) MCR14(val, 0, c1, c2, 4) +#define WCP14_DBGOSDLR(val) MCR14(val, 0, c1, c3, 4) +#define WCP14_DBGPRCR(val) MCR14(val, 0, c1, c4, 4) +#define WCP14_DBGITCTRL(val) MCR14(val, 0, c7, c0, 4) +#define WCP14_DBGCLAIMSET(val) MCR14(val, 0, c7, c8, 6) +#define WCP14_DBGCLAIMCLR(val) MCR14(val, 0, c7, c9, 6) + +/* ETM Registers + * + * Available only in ETMv3.3, 3.4, 3.5 + * ETMASICCR, ETMTECR2, ETMFFRR, ETMVDEVR, ETMVDCR1, ETMVDCR2, ETMVDCR3, + * ETMDCVRn, ETMDCMRn + * + * Available only in ETMv3.5 as read only + * ETMIDR2 + * + * Available only in ETMv3.5, PFTv1.0, 1.1 + * ETMTSEVR, ETMVMIDCVR, ETMPDCR + * + * Read only + * ETMCCR, ETMSCR, ETMIDR, ETMCCER, ETMOSLSR + * ETMLSR, ETMAUTHSTATUS, ETMDEVID, ETMDEVTYPE, ETMPIDR4, ETMPIDR5, ETMPIDR6, + * ETMPIDR7, ETMPIDR0, ETMPIDR1, ETMPIDR2, ETMPIDR2, ETMPIDR3, ETMCIDR0, + * ETMCIDR1, ETMCIDR2, ETMCIDR3 + * + * Write only + * ETMOSLAR, ETMLAR + * Note: ETMCCER[11] controls WO nature of certain regs. Refer ETM arch spec. + */ +#define RCP14_ETMCR() MRC14(1, c0, c0, 0) +#define RCP14_ETMCCR() MRC14(1, c0, c1, 0) +#define RCP14_ETMTRIGGER() MRC14(1, c0, c2, 0) +#define RCP14_ETMASICCR() MRC14(1, c0, c3, 0) +#define RCP14_ETMSR() MRC14(1, c0, c4, 0) +#define RCP14_ETMSCR() MRC14(1, c0, c5, 0) +#define RCP14_ETMTSSCR() MRC14(1, c0, c6, 0) +#define RCP14_ETMTECR2() MRC14(1, c0, c7, 0) +#define RCP14_ETMTEEVR() MRC14(1, c0, c8, 0) +#define RCP14_ETMTECR1() MRC14(1, c0, c9, 0) +#define RCP14_ETMFFRR() MRC14(1, c0, c10, 0) +#define RCP14_ETMFFLR() MRC14(1, c0, c11, 0) +#define RCP14_ETMVDEVR() MRC14(1, c0, c12, 0) +#define RCP14_ETMVDCR1() MRC14(1, c0, c13, 0) +#define RCP14_ETMVDCR2() MRC14(1, c0, c14, 0) +#define RCP14_ETMVDCR3() MRC14(1, c0, c15, 0) +#define RCP14_ETMACVR0() MRC14(1, c0, c0, 1) +#define RCP14_ETMACVR1() MRC14(1, c0, c1, 1) +#define RCP14_ETMACVR2() MRC14(1, c0, c2, 1) +#define RCP14_ETMACVR3() MRC14(1, c0, c3, 1) +#define RCP14_ETMACVR4() MRC14(1, c0, c4, 1) +#define RCP14_ETMACVR5() MRC14(1, c0, c5, 1) +#define RCP14_ETMACVR6() MRC14(1, c0, c6, 1) +#define RCP14_ETMACVR7() MRC14(1, c0, c7, 1) +#define RCP14_ETMACVR8() MRC14(1, c0, c8, 1) +#define RCP14_ETMACVR9() MRC14(1, c0, c9, 1) +#define RCP14_ETMACVR10() MRC14(1, c0, c10, 1) +#define RCP14_ETMACVR11() MRC14(1, c0, c11, 1) +#define RCP14_ETMACVR12() MRC14(1, c0, c12, 1) +#define RCP14_ETMACVR13() MRC14(1, c0, c13, 1) +#define RCP14_ETMACVR14() MRC14(1, c0, c14, 1) +#define RCP14_ETMACVR15() MRC14(1, c0, c15, 1) +#define RCP14_ETMACTR0() MRC14(1, c0, c0, 2) +#define RCP14_ETMACTR1() MRC14(1, c0, c1, 2) +#define RCP14_ETMACTR2() MRC14(1, c0, c2, 2) +#define RCP14_ETMACTR3() MRC14(1, c0, c3, 2) +#define RCP14_ETMACTR4() MRC14(1, c0, c4, 2) +#define RCP14_ETMACTR5() MRC14(1, c0, c5, 2) +#define RCP14_ETMACTR6() MRC14(1, c0, c6, 2) +#define RCP14_ETMACTR7() MRC14(1, c0, c7, 2) +#define RCP14_ETMACTR8() MRC14(1, c0, c8, 2) +#define RCP14_ETMACTR9() MRC14(1, c0, c9, 2) +#define RCP14_ETMACTR10() MRC14(1, c0, c10, 2) +#define RCP14_ETMACTR11() MRC14(1, c0, c11, 2) +#define RCP14_ETMACTR12() MRC14(1, c0, c12, 2) +#define RCP14_ETMACTR13() MRC14(1, c0, c13, 2) +#define RCP14_ETMACTR14() MRC14(1, c0, c14, 2) +#define RCP14_ETMACTR15() MRC14(1, c0, c15, 2) +#define RCP14_ETMDCVR0() MRC14(1, c0, c0, 3) +#define RCP14_ETMDCVR2() MRC14(1, c0, c2, 3) +#define RCP14_ETMDCVR4() MRC14(1, c0, c4, 3) +#define RCP14_ETMDCVR6() MRC14(1, c0, c6, 3) +#define RCP14_ETMDCVR8() MRC14(1, c0, c8, 3) +#define RCP14_ETMDCVR10() MRC14(1, c0, c10, 3) +#define RCP14_ETMDCVR12() MRC14(1, c0, c12, 3) +#define RCP14_ETMDCVR14() MRC14(1, c0, c14, 3) +#define RCP14_ETMDCMR0() MRC14(1, c0, c0, 4) +#define RCP14_ETMDCMR2() MRC14(1, c0, c2, 4) +#define RCP14_ETMDCMR4() MRC14(1, c0, c4, 4) +#define RCP14_ETMDCMR6() MRC14(1, c0, c6, 4) +#define RCP14_ETMDCMR8() MRC14(1, c0, c8, 4) +#define RCP14_ETMDCMR10() MRC14(1, c0, c10, 4) +#define RCP14_ETMDCMR12() MRC14(1, c0, c12, 4) +#define RCP14_ETMDCMR14() MRC14(1, c0, c14, 4) +#define RCP14_ETMCNTRLDVR0() MRC14(1, c0, c0, 5) +#define RCP14_ETMCNTRLDVR1() MRC14(1, c0, c1, 5) +#define RCP14_ETMCNTRLDVR2() MRC14(1, c0, c2, 5) +#define RCP14_ETMCNTRLDVR3() MRC14(1, c0, c3, 5) +#define RCP14_ETMCNTENR0() MRC14(1, c0, c4, 5) +#define RCP14_ETMCNTENR1() MRC14(1, c0, c5, 5) +#define RCP14_ETMCNTENR2() MRC14(1, c0, c6, 5) +#define RCP14_ETMCNTENR3() MRC14(1, c0, c7, 5) +#define RCP14_ETMCNTRLDEVR0() MRC14(1, c0, c8, 5) +#define RCP14_ETMCNTRLDEVR1() MRC14(1, c0, c9, 5) +#define RCP14_ETMCNTRLDEVR2() MRC14(1, c0, c10, 5) +#define RCP14_ETMCNTRLDEVR3() MRC14(1, c0, c11, 5) +#define RCP14_ETMCNTVR0() MRC14(1, c0, c12, 5) +#define RCP14_ETMCNTVR1() MRC14(1, c0, c13, 5) +#define RCP14_ETMCNTVR2() MRC14(1, c0, c14, 5) +#define RCP14_ETMCNTVR3() MRC14(1, c0, c15, 5) +#define RCP14_ETMSQ12EVR() MRC14(1, c0, c0, 6) +#define RCP14_ETMSQ21EVR() MRC14(1, c0, c1, 6) +#define RCP14_ETMSQ23EVR() MRC14(1, c0, c2, 6) +#define RCP14_ETMSQ31EVR() MRC14(1, c0, c3, 6) +#define RCP14_ETMSQ32EVR() MRC14(1, c0, c4, 6) +#define RCP14_ETMSQ13EVR() MRC14(1, c0, c5, 6) +#define RCP14_ETMSQR() MRC14(1, c0, c7, 6) +#define RCP14_ETMEXTOUTEVR0() MRC14(1, c0, c8, 6) +#define RCP14_ETMEXTOUTEVR1() MRC14(1, c0, c9, 6) +#define RCP14_ETMEXTOUTEVR2() MRC14(1, c0, c10, 6) +#define RCP14_ETMEXTOUTEVR3() MRC14(1, c0, c11, 6) +#define RCP14_ETMCIDCVR0() MRC14(1, c0, c12, 6) +#define RCP14_ETMCIDCVR1() MRC14(1, c0, c13, 6) +#define RCP14_ETMCIDCVR2() MRC14(1, c0, c14, 6) +#define RCP14_ETMCIDCMR() MRC14(1, c0, c15, 6) +#define RCP14_ETMIMPSPEC0() MRC14(1, c0, c0, 7) +#define RCP14_ETMIMPSPEC1() MRC14(1, c0, c1, 7) +#define RCP14_ETMIMPSPEC2() MRC14(1, c0, c2, 7) +#define RCP14_ETMIMPSPEC3() MRC14(1, c0, c3, 7) +#define RCP14_ETMIMPSPEC4() MRC14(1, c0, c4, 7) +#define RCP14_ETMIMPSPEC5() MRC14(1, c0, c5, 7) +#define RCP14_ETMIMPSPEC6() MRC14(1, c0, c6, 7) +#define RCP14_ETMIMPSPEC7() MRC14(1, c0, c7, 7) +#define RCP14_ETMSYNCFR() MRC14(1, c0, c8, 7) +#define RCP14_ETMIDR() MRC14(1, c0, c9, 7) +#define RCP14_ETMCCER() MRC14(1, c0, c10, 7) +#define RCP14_ETMEXTINSELR() MRC14(1, c0, c11, 7) +#define RCP14_ETMTESSEICR() MRC14(1, c0, c12, 7) +#define RCP14_ETMEIBCR() MRC14(1, c0, c13, 7) +#define RCP14_ETMTSEVR() MRC14(1, c0, c14, 7) +#define RCP14_ETMAUXCR() MRC14(1, c0, c15, 7) +#define RCP14_ETMTRACEIDR() MRC14(1, c1, c0, 0) +#define RCP14_ETMIDR2() MRC14(1, c1, c2, 0) +#define RCP14_ETMVMIDCVR() MRC14(1, c1, c0, 1) +#define RCP14_ETMOSLSR() MRC14(1, c1, c1, 4) +/* not available in PFTv1.1 */ +#define RCP14_ETMOSSRR() MRC14(1, c1, c2, 4) +#define RCP14_ETMPDCR() MRC14(1, c1, c4, 4) +#define RCP14_ETMPDSR() MRC14(1, c1, c5, 4) +#define RCP14_ETMITCTRL() MRC14(1, c7, c0, 4) +#define RCP14_ETMCLAIMSET() MRC14(1, c7, c8, 6) +#define RCP14_ETMCLAIMCLR() MRC14(1, c7, c9, 6) +#define RCP14_ETMLSR() MRC14(1, c7, c13, 6) +#define RCP14_ETMAUTHSTATUS() MRC14(1, c7, c14, 6) +#define RCP14_ETMDEVID() MRC14(1, c7, c2, 7) +#define RCP14_ETMDEVTYPE() MRC14(1, c7, c3, 7) +#define RCP14_ETMPIDR4() MRC14(1, c7, c4, 7) +#define RCP14_ETMPIDR5() MRC14(1, c7, c5, 7) +#define RCP14_ETMPIDR6() MRC14(1, c7, c6, 7) +#define RCP14_ETMPIDR7() MRC14(1, c7, c7, 7) +#define RCP14_ETMPIDR0() MRC14(1, c7, c8, 7) +#define RCP14_ETMPIDR1() MRC14(1, c7, c9, 7) +#define RCP14_ETMPIDR2() MRC14(1, c7, c10, 7) +#define RCP14_ETMPIDR3() MRC14(1, c7, c11, 7) +#define RCP14_ETMCIDR0() MRC14(1, c7, c12, 7) +#define RCP14_ETMCIDR1() MRC14(1, c7, c13, 7) +#define RCP14_ETMCIDR2() MRC14(1, c7, c14, 7) +#define RCP14_ETMCIDR3() MRC14(1, c7, c15, 7) + +#define WCP14_ETMCR(val) MCR14(val, 1, c0, c0, 0) +#define WCP14_ETMTRIGGER(val) MCR14(val, 1, c0, c2, 0) +#define WCP14_ETMASICCR(val) MCR14(val, 1, c0, c3, 0) +#define WCP14_ETMSR(val) MCR14(val, 1, c0, c4, 0) +#define WCP14_ETMTSSCR(val) MCR14(val, 1, c0, c6, 0) +#define WCP14_ETMTECR2(val) MCR14(val, 1, c0, c7, 0) +#define WCP14_ETMTEEVR(val) MCR14(val, 1, c0, c8, 0) +#define WCP14_ETMTECR1(val) MCR14(val, 1, c0, c9, 0) +#define WCP14_ETMFFRR(val) MCR14(val, 1, c0, c10, 0) +#define WCP14_ETMFFLR(val) MCR14(val, 1, c0, c11, 0) +#define WCP14_ETMVDEVR(val) MCR14(val, 1, c0, c12, 0) +#define WCP14_ETMVDCR1(val) MCR14(val, 1, c0, c13, 0) +#define WCP14_ETMVDCR2(val) MCR14(val, 1, c0, c14, 0) +#define WCP14_ETMVDCR3(val) MCR14(val, 1, c0, c15, 0) +#define WCP14_ETMACVR0(val) MCR14(val, 1, c0, c0, 1) +#define WCP14_ETMACVR1(val) MCR14(val, 1, c0, c1, 1) +#define WCP14_ETMACVR2(val) MCR14(val, 1, c0, c2, 1) +#define WCP14_ETMACVR3(val) MCR14(val, 1, c0, c3, 1) +#define WCP14_ETMACVR4(val) MCR14(val, 1, c0, c4, 1) +#define WCP14_ETMACVR5(val) MCR14(val, 1, c0, c5, 1) +#define WCP14_ETMACVR6(val) MCR14(val, 1, c0, c6, 1) +#define WCP14_ETMACVR7(val) MCR14(val, 1, c0, c7, 1) +#define WCP14_ETMACVR8(val) MCR14(val, 1, c0, c8, 1) +#define WCP14_ETMACVR9(val) MCR14(val, 1, c0, c9, 1) +#define WCP14_ETMACVR10(val) MCR14(val, 1, c0, c10, 1) +#define WCP14_ETMACVR11(val) MCR14(val, 1, c0, c11, 1) +#define WCP14_ETMACVR12(val) MCR14(val, 1, c0, c12, 1) +#define WCP14_ETMACVR13(val) MCR14(val, 1, c0, c13, 1) +#define WCP14_ETMACVR14(val) MCR14(val, 1, c0, c14, 1) +#define WCP14_ETMACVR15(val) MCR14(val, 1, c0, c15, 1) +#define WCP14_ETMACTR0(val) MCR14(val, 1, c0, c0, 2) +#define WCP14_ETMACTR1(val) MCR14(val, 1, c0, c1, 2) +#define WCP14_ETMACTR2(val) MCR14(val, 1, c0, c2, 2) +#define WCP14_ETMACTR3(val) MCR14(val, 1, c0, c3, 2) +#define WCP14_ETMACTR4(val) MCR14(val, 1, c0, c4, 2) +#define WCP14_ETMACTR5(val) MCR14(val, 1, c0, c5, 2) +#define WCP14_ETMACTR6(val) MCR14(val, 1, c0, c6, 2) +#define WCP14_ETMACTR7(val) MCR14(val, 1, c0, c7, 2) +#define WCP14_ETMACTR8(val) MCR14(val, 1, c0, c8, 2) +#define WCP14_ETMACTR9(val) MCR14(val, 1, c0, c9, 2) +#define WCP14_ETMACTR10(val) MCR14(val, 1, c0, c10, 2) +#define WCP14_ETMACTR11(val) MCR14(val, 1, c0, c11, 2) +#define WCP14_ETMACTR12(val) MCR14(val, 1, c0, c12, 2) +#define WCP14_ETMACTR13(val) MCR14(val, 1, c0, c13, 2) +#define WCP14_ETMACTR14(val) MCR14(val, 1, c0, c14, 2) +#define WCP14_ETMACTR15(val) MCR14(val, 1, c0, c15, 2) +#define WCP14_ETMDCVR0(val) MCR14(val, 1, c0, c0, 3) +#define WCP14_ETMDCVR2(val) MCR14(val, 1, c0, c2, 3) +#define WCP14_ETMDCVR4(val) MCR14(val, 1, c0, c4, 3) +#define WCP14_ETMDCVR6(val) MCR14(val, 1, c0, c6, 3) +#define WCP14_ETMDCVR8(val) MCR14(val, 1, c0, c8, 3) +#define WCP14_ETMDCVR10(val) MCR14(val, 1, c0, c10, 3) +#define WCP14_ETMDCVR12(val) MCR14(val, 1, c0, c12, 3) +#define WCP14_ETMDCVR14(val) MCR14(val, 1, c0, c14, 3) +#define WCP14_ETMDCMR0(val) MCR14(val, 1, c0, c0, 4) +#define WCP14_ETMDCMR2(val) MCR14(val, 1, c0, c2, 4) +#define WCP14_ETMDCMR4(val) MCR14(val, 1, c0, c4, 4) +#define WCP14_ETMDCMR6(val) MCR14(val, 1, c0, c6, 4) +#define WCP14_ETMDCMR8(val) MCR14(val, 1, c0, c8, 4) +#define WCP14_ETMDCMR10(val) MCR14(val, 1, c0, c10, 4) +#define WCP14_ETMDCMR12(val) MCR14(val, 1, c0, c12, 4) +#define WCP14_ETMDCMR14(val) MCR14(val, 1, c0, c14, 4) +#define WCP14_ETMCNTRLDVR0(val) MCR14(val, 1, c0, c0, 5) +#define WCP14_ETMCNTRLDVR1(val) MCR14(val, 1, c0, c1, 5) +#define WCP14_ETMCNTRLDVR2(val) MCR14(val, 1, c0, c2, 5) +#define WCP14_ETMCNTRLDVR3(val) MCR14(val, 1, c0, c3, 5) +#define WCP14_ETMCNTENR0(val) MCR14(val, 1, c0, c4, 5) +#define WCP14_ETMCNTENR1(val) MCR14(val, 1, c0, c5, 5) +#define WCP14_ETMCNTENR2(val) MCR14(val, 1, c0, c6, 5) +#define WCP14_ETMCNTENR3(val) MCR14(val, 1, c0, c7, 5) +#define WCP14_ETMCNTRLDEVR0(val) MCR14(val, 1, c0, c8, 5) +#define WCP14_ETMCNTRLDEVR1(val) MCR14(val, 1, c0, c9, 5) +#define WCP14_ETMCNTRLDEVR2(val) MCR14(val, 1, c0, c10, 5) +#define WCP14_ETMCNTRLDEVR3(val) MCR14(val, 1, c0, c11, 5) +#define WCP14_ETMCNTVR0(val) MCR14(val, 1, c0, c12, 5) +#define WCP14_ETMCNTVR1(val) MCR14(val, 1, c0, c13, 5) +#define WCP14_ETMCNTVR2(val) MCR14(val, 1, c0, c14, 5) +#define WCP14_ETMCNTVR3(val) MCR14(val, 1, c0, c15, 5) +#define WCP14_ETMSQ12EVR(val) MCR14(val, 1, c0, c0, 6) +#define WCP14_ETMSQ21EVR(val) MCR14(val, 1, c0, c1, 6) +#define WCP14_ETMSQ23EVR(val) MCR14(val, 1, c0, c2, 6) +#define WCP14_ETMSQ31EVR(val) MCR14(val, 1, c0, c3, 6) +#define WCP14_ETMSQ32EVR(val) MCR14(val, 1, c0, c4, 6) +#define WCP14_ETMSQ13EVR(val) MCR14(val, 1, c0, c5, 6) +#define WCP14_ETMSQR(val) MCR14(val, 1, c0, c7, 6) +#define WCP14_ETMEXTOUTEVR0(val) MCR14(val, 1, c0, c8, 6) +#define WCP14_ETMEXTOUTEVR1(val) MCR14(val, 1, c0, c9, 6) +#define WCP14_ETMEXTOUTEVR2(val) MCR14(val, 1, c0, c10, 6) +#define WCP14_ETMEXTOUTEVR3(val) MCR14(val, 1, c0, c11, 6) +#define WCP14_ETMCIDCVR0(val) MCR14(val, 1, c0, c12, 6) +#define WCP14_ETMCIDCVR1(val) MCR14(val, 1, c0, c13, 6) +#define WCP14_ETMCIDCVR2(val) MCR14(val, 1, c0, c14, 6) +#define WCP14_ETMCIDCMR(val) MCR14(val, 1, c0, c15, 6) +#define WCP14_ETMIMPSPEC0(val) MCR14(val, 1, c0, c0, 7) +#define WCP14_ETMIMPSPEC1(val) MCR14(val, 1, c0, c1, 7) +#define WCP14_ETMIMPSPEC2(val) MCR14(val, 1, c0, c2, 7) +#define WCP14_ETMIMPSPEC3(val) MCR14(val, 1, c0, c3, 7) +#define WCP14_ETMIMPSPEC4(val) MCR14(val, 1, c0, c4, 7) +#define WCP14_ETMIMPSPEC5(val) MCR14(val, 1, c0, c5, 7) +#define WCP14_ETMIMPSPEC6(val) MCR14(val, 1, c0, c6, 7) +#define WCP14_ETMIMPSPEC7(val) MCR14(val, 1, c0, c7, 7) +/* can be read only in ETMv3.4, ETMv3.5 */ +#define WCP14_ETMSYNCFR(val) MCR14(val, 1, c0, c8, 7) +#define WCP14_ETMEXTINSELR(val) MCR14(val, 1, c0, c11, 7) +#define WCP14_ETMTESSEICR(val) MCR14(val, 1, c0, c12, 7) +#define WCP14_ETMEIBCR(val) MCR14(val, 1, c0, c13, 7) +#define WCP14_ETMTSEVR(val) MCR14(val, 1, c0, c14, 7) +#define WCP14_ETMAUXCR(val) MCR14(val, 1, c0, c15, 7) +#define WCP14_ETMTRACEIDR(val) MCR14(val, 1, c1, c0, 0) +#define WCP14_ETMIDR2(val) MCR14(val, 1, c1, c2, 0) +#define WCP14_ETMVMIDCVR(val) MCR14(val, 1, c1, c0, 1) +#define WCP14_ETMOSLAR(val) MCR14(val, 1, c1, c0, 4) +/* not available in PFTv1.1 */ +#define WCP14_ETMOSSRR(val) MCR14(val, 1, c1, c2, 4) +#define WCP14_ETMPDCR(val) MCR14(val, 1, c1, c4, 4) +#define WCP14_ETMPDSR(val) MCR14(val, 1, c1, c5, 4) +#define WCP14_ETMITCTRL(val) MCR14(val, 1, c7, c0, 4) +#define WCP14_ETMCLAIMSET(val) MCR14(val, 1, c7, c8, 6) +#define WCP14_ETMCLAIMCLR(val) MCR14(val, 1, c7, c9, 6) +/* writes to this from CP14 interface are ignored */ +#define WCP14_ETMLAR(val) MCR14(val, 1, c7, c12, 6) + +#endif diff --git a/arch/arm/mach-msm/cpufreq.c b/arch/arm/mach-msm/cpufreq.c new file mode 100644 index 0000000000000000000000000000000000000000..63534a4da08a42ec43f166e73f6e534e8323a227 --- /dev/null +++ b/arch/arm/mach-msm/cpufreq.c @@ -0,0 +1,303 @@ +/* arch/arm/mach-msm/cpufreq.c + * + * MSM architecture cpufreq driver + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2012, 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 + +#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 (!cpu_active(policy->cpu)) { + pr_info("cpufreq: cpu %d is not active.\n", policy->cpu); + return -ENODEV; + } + + if (!alloc_cpumask_var(&mask, GFP_KERNEL)) + return -ENOMEM; +#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); + } + + ret = cpu_work->status; +#else + ret = set_cpu_freq(policy, table[index].frequency); +#endif + +done: +#ifdef CONFIG_SMP + free_cpumask_var(mask); +#endif + 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 (table == NULL) + return -ENODEV; + /* + * In 8625 both cpu core's frequency can not + * be changed independently. Each cpu is bound to + * same frequency. Hence set the cpumask to all cpu. + */ + if (cpu_is_msm8625()) + cpumask_setall(policy->cpus); + + 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) && + cpufreq_frequency_table_target(policy, table, cur_freq, + CPUFREQ_RELATION_L, &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 freq_attr *msm_freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +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", + .attr = msm_freq_attr, +}; + +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 0000000000000000000000000000000000000000..e4ec4d48c2b31470176d4106e75aed69ec4ff6d3 --- /dev/null +++ b/arch/arm/mach-msm/cpuidle.c @@ -0,0 +1,234 @@ +/* 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 "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, +}; + +static struct msm_cpuidle_state msm_cstates[] = { + {0, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {0, 1, "C1", "RETENTION", + MSM_PM_SLEEP_MODE_RETENTION}, + + {0, 2, "C2", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, + + {0, 3, "C3", "POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE}, + + {1, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {1, 1, "C1", "RETENTION", + MSM_PM_SLEEP_MODE_RETENTION}, + + {1, 2, "C2", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, + + {2, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {2, 1, "C1", "RETENTION", + MSM_PM_SLEEP_MODE_RETENTION}, + + {2, 2, "C2", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, + + {3, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {3, 1, "C1", "RETENTION", + MSM_PM_SLEEP_MODE_RETENTION}, + + {3, 2, "C2", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, +}; + + +#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_driver *drv, int index) +{ + int ret = 0; + int i = 0; + enum msm_pm_sleep_mode pm_mode; + struct cpuidle_state_usage *st_usage = NULL; +#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 + +#ifdef CONFIG_CPU_PM + cpu_pm_enter(); +#endif + + pm_mode = msm_pm_idle_prepare(dev, drv, index); + msm_pm_idle_enter(pm_mode); + for (i = 0; i < dev->state_count; i++) { + st_usage = &dev->states_usage[i]; + if ((enum msm_pm_sleep_mode) cpuidle_get_statedata(st_usage) + == pm_mode) { + ret = i; + break; + } + } + +#ifdef CONFIG_CPU_PM + cpu_pm_exit(); +#endif + +#ifdef CONFIG_MSM_SLEEP_STATS + atomic_notifier_call_chain(head, MSM_CPUIDLE_STATE_EXIT, NULL); +#endif + + local_irq_enable(); + + return ret; +} + +static void __init msm_cpuidle_set_states(void) +{ + int i = 0; + int state_count = 0; + struct msm_cpuidle_state *cstate = NULL; + struct cpuidle_state *state = NULL; + + for (i = 0; i < ARRAY_SIZE(msm_cstates); i++) { + cstate = &msm_cstates[i]; + /* We have an asymmetric CPU C-State in MSMs. + * The primary CPU can do PC while all secondary cpus + * can only do standalone PC as part of their idle LPM. + * However, the secondary cpus can do PC when hotplugged + * We do not care about the hotplug here. + * Register the C-States available for Core0. + */ + if (cstate->cpu) + continue; + + state = &msm_cpuidle_driver.states[state_count]; + snprintf(state->name, CPUIDLE_NAME_LEN, cstate->name); + snprintf(state->desc, CPUIDLE_DESC_LEN, cstate->desc); + state->flags = 0; + state->exit_latency = 0; + state->power_usage = 0; + state->target_residency = 0; + state->enter = msm_cpuidle_enter; + + state_count++; + BUG_ON(state_count >= CPUIDLE_STATE_MAX); + } + msm_cpuidle_driver.state_count = state_count; + msm_cpuidle_driver.safe_state_index = 0; +} + +static void __init msm_cpuidle_set_cpu_statedata(struct cpuidle_device *dev) +{ + int i = 0; + int state_count = 0; + struct cpuidle_state_usage *st_usage = NULL; + struct msm_cpuidle_state *cstate = NULL; + + for (i = 0; i < ARRAY_SIZE(msm_cstates); i++) { + cstate = &msm_cstates[i]; + if (cstate->cpu != dev->cpu) + continue; + + st_usage = &dev->states_usage[state_count]; + cpuidle_set_statedata(st_usage, (void *)cstate->mode_nr); + state_count++; + BUG_ON(state_count > msm_cpuidle_driver.state_count); + } + + dev->state_count = state_count; /* Per cpu state count */ +} + +int __init msm_cpuidle_init(void) +{ + unsigned int cpu = 0; + int ret = 0; + + msm_cpuidle_set_states(); + 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); + + dev->cpu = cpu; + msm_cpuidle_set_cpu_statedata(dev); + 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/dal.c b/arch/arm/mach-msm/dal.c new file mode 100644 index 0000000000000000000000000000000000000000..94c02f0e5f56f497c3a621ef488ebbc78299d05c --- /dev/null +++ b/arch/arm/mach-msm/dal.c @@ -0,0 +1,1326 @@ +/* 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. + * + */ +/* + * 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; + + strlcpy(p->port, port, sizeof(p->port)); + 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. */ + strlcpy(dyn_port, (char *)&h->msg.param[1], + sizeof(dyn_port)); + 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 0000000000000000000000000000000000000000..739b7dcd1b27cdf25532863221d31edb59797db4 --- /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 0000000000000000000000000000000000000000..d7a3f34c945e8804b9131889acc4d9864290a0ab --- /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 0000000000000000000000000000000000000000..cb998c904a8f848ff7610bb2e84325077eb3f433 --- /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 0000000000000000000000000000000000000000..ef9b62a8774acd3d3b6dae76bdae805d9808852a --- /dev/null +++ b/arch/arm/mach-msm/devices-8064.c @@ -0,0 +1,2611 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "clock.h" +#include "devices.h" +#include "footswitch.h" +#include "msm_watchdog.h" +#include "rpm_stats.h" +#include "rpm_log.h" +#include +#include +#include + +/* Address of GSBI blocks */ +#define MSM_GSBI1_PHYS 0x12440000 +#define MSM_GSBI3_PHYS 0x16200000 +#define MSM_GSBI4_PHYS 0x16300000 +#define MSM_GSBI5_PHYS 0x1A200000 +#define MSM_GSBI6_PHYS 0x16500000 +#define MSM_GSBI7_PHYS 0x16600000 + +/* GSBI UART devices */ +#define MSM_UART1DM_PHYS (MSM_GSBI1_PHYS + 0x10000) +#define MSM_UART3DM_PHYS (MSM_GSBI3_PHYS + 0x40000) +#define MSM_UART7DM_PHYS (MSM_GSBI7_PHYS + 0x40000) + +/* GSBI QUP devices */ +#define MSM_GSBI1_QUP_PHYS (MSM_GSBI1_PHYS + 0x20000) +#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_QUP_SIZE SZ_4K + +/* Address of SSBI CMD */ +#define MSM_PMIC1_SSBI_CMD_PHYS 0x00500000 +#define MSM_PMIC2_SSBI_CMD_PHYS 0x00C00000 +#define MSM_PMIC_SSBI_SIZE SZ_4K + +/* Address of HS USBOTG1 */ +#define MSM_HSUSB1_PHYS 0x12500000 +#define MSM_HSUSB1_SIZE SZ_4K + +/* Address of HS USB3 */ +#define MSM_HSUSB3_PHYS 0x12520000 +#define MSM_HSUSB3_SIZE SZ_4K + +/* Address of HS USB4 */ +#define MSM_HSUSB4_PHYS 0x12530000 +#define MSM_HSUSB4_SIZE SZ_4K + +/* Address of PCIE20 PARF */ +#define PCIE20_PARF_PHYS 0x1b600000 +#define PCIE20_PARF_SIZE SZ_128 + +/* Address of PCIE20 ELBI */ +#define PCIE20_ELBI_PHYS 0x1b502000 +#define PCIE20_ELBI_SIZE SZ_256 + +/* Address of PCIE20 */ +#define PCIE20_PHYS 0x1b500000 +#define PCIE20_SIZE SZ_4K + +/* AXI address for PCIE device BAR resources */ +#define PCIE_AXI_BAR_PHYS 0x08000000 +#define PCIE_AXI_BAR_SIZE SZ_8M + +/* AXI address for PCIE device config space */ +#define PCIE_AXI_CONF_PHYS 0x08c00000 +#define PCIE_AXI_CONF_SIZE SZ_4K + +static struct msm_watchdog_pdata msm_watchdog_pdata = { + .pet_time = 10000, + .bark_time = 11000, + .has_secure = true, + .needs_expired_enable = true, +}; + +struct platform_device msm8064_device_watchdog = { + .name = "msm_watchdog", + .id = -1, + .dev = { + .platform_data = &msm_watchdog_pdata, + }, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = ADM_0_SCSS_1_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0x18320000, + .end = 0x18320000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 1, + .sd_size = 0x800, +}; + +struct platform_device apq8064_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +static struct resource resources_uart_gsbi1[] = { + { + .start = APQ8064_GSBI1_UARTDM_IRQ, + .end = APQ8064_GSBI1_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI1_PHYS, + .end = MSM_GSBI1_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device apq8064_device_uart_gsbi1 = { + .name = "msm_serial_hsl", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart_gsbi1), + .resource = resources_uart_gsbi1, +}; + +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_i2c_gsbi3[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI3_PHYS, + .end = MSM_GSBI3_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI3_QUP_PHYS, + .end = MSM_GSBI3_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI3_QUP_IRQ, + .end = GSBI3_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = 9, + .end = 9, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = 8, + .end = 8, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource resources_qup_i2c_gsbi1[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI1_PHYS, + .end = MSM_GSBI1_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI1_QUP_PHYS, + .end = MSM_GSBI1_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = APQ8064_GSBI1_QUP_IRQ, + .end = APQ8064_GSBI1_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = 21, + .end = 21, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = 20, + .end = 20, + .flags = IORESOURCE_IO, + }, +}; + +struct platform_device apq8064_device_qup_i2c_gsbi1 = { + .name = "qup_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi1), + .resource = resources_qup_i2c_gsbi1, +}; + +struct platform_device apq8064_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_gsbi4[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI4_PHYS, + .end = MSM_GSBI4_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI4_QUP_PHYS, + .end = MSM_GSBI4_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI4_QUP_IRQ, + .end = GSBI4_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = 11, + .end = 11, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = 10, + .end = 10, + .flags = IORESOURCE_IO, + }, +}; + +struct platform_device apq8064_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_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_qup_i2c_gsbi5[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI5_PHYS, + .end = MSM_GSBI5_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI5_QUP_PHYS, + .end = MSM_GSBI5_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI5_QUP_IRQ, + .end = GSBI5_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = 54, + .end = 54, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = 53, + .end = 53, + .flags = IORESOURCE_IO, + }, +}; + +struct platform_device mpq8064_device_qup_i2c_gsbi5 = { + .name = "qup_i2c", + .id = 5, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi5), + .resource = resources_qup_i2c_gsbi5, +}; + +static struct resource resources_uart_gsbi7[] = { + { + .start = GSBI7_UARTDM_IRQ, + .end = GSBI7_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART7DM_PHYS, + .end = MSM_UART7DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI7_PHYS, + .end = MSM_GSBI7_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device apq8064_device_uart_gsbi7 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart_gsbi7), + .resource = resources_uart_gsbi7, +}; + +struct platform_device apq_pcm = { + .name = "msm-pcm-dsp", + .id = -1, +}; + +struct platform_device apq_pcm_routing = { + .name = "msm-pcm-routing", + .id = -1, +}; + +struct platform_device apq_cpudai0 = { + .name = "msm-dai-q6", + .id = 0x4000, +}; + +struct platform_device apq_cpudai1 = { + .name = "msm-dai-q6", + .id = 0x4001, +}; +struct platform_device mpq_cpudai_sec_i2s_rx = { + .name = "msm-dai-q6", + .id = 4, +}; +struct platform_device apq_cpudai_hdmi_rx = { + .name = "msm-dai-q6-hdmi", + .id = 8, +}; + +struct platform_device apq_cpudai_bt_rx = { + .name = "msm-dai-q6", + .id = 0x3000, +}; + +struct platform_device apq_cpudai_bt_tx = { + .name = "msm-dai-q6", + .id = 0x3001, +}; + +struct platform_device apq_cpudai_fm_rx = { + .name = "msm-dai-q6", + .id = 0x3004, +}; + +struct platform_device apq_cpudai_fm_tx = { + .name = "msm-dai-q6", + .id = 0x3005, +}; + +struct platform_device apq_cpudai_slim_4_rx = { + .name = "msm-dai-q6", + .id = 0x4008, +}; + +struct platform_device apq_cpudai_slim_4_tx = { + .name = "msm-dai-q6", + .id = 0x4009, +}; + +/* + * Machine specific data for AUX PCM Interface + * which the driver will be unware of. + */ +struct msm_dai_auxpcm_pdata apq_auxpcm_pdata = { + .clk = "pcm_clk", + .mode_8k = { + .mode = AFE_PCM_CFG_MODE_PCM, + .sync = AFE_PCM_CFG_SYNC_INT, + .frame = AFE_PCM_CFG_FRM_256BPF, + .quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD, + .slot = 0, + .data = AFE_PCM_CFG_CDATAOE_MASTER, + .pcm_clk_rate = 2048000, + }, + .mode_16k = { + .mode = AFE_PCM_CFG_MODE_PCM, + .sync = AFE_PCM_CFG_SYNC_INT, + .frame = AFE_PCM_CFG_FRM_256BPF, + .quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD, + .slot = 0, + .data = AFE_PCM_CFG_CDATAOE_MASTER, + .pcm_clk_rate = 4096000, + } +}; + +struct platform_device apq_cpudai_auxpcm_rx = { + .name = "msm-dai-q6", + .id = 2, + .dev = { + .platform_data = &apq_auxpcm_pdata, + }, +}; + +struct platform_device apq_cpudai_auxpcm_tx = { + .name = "msm-dai-q6", + .id = 3, + .dev = { + .platform_data = &apq_auxpcm_pdata, + }, +}; + +struct msm_mi2s_pdata mpq_mi2s_tx_data = { + .rx_sd_lines = 0, + .tx_sd_lines = MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2 | + MSM_MI2S_SD3, +}; + +struct platform_device mpq_cpudai_mi2s_tx = { + .name = "msm-dai-q6-mi2s", + .id = -1, /*MI2S_TX */ + .dev = { + .platform_data = &mpq_mi2s_tx_data, + }, +}; + +struct platform_device apq_cpu_fe = { + .name = "msm-dai-fe", + .id = -1, +}; + +struct platform_device apq_stub_codec = { + .name = "msm-stub-codec", + .id = 1, +}; + +struct platform_device apq_voice = { + .name = "msm-pcm-voice", + .id = -1, +}; + +struct platform_device apq_voip = { + .name = "msm-voip-dsp", + .id = -1, +}; + +struct platform_device apq_lpa_pcm = { + .name = "msm-pcm-lpa", + .id = -1, +}; + +struct platform_device apq_compr_dsp = { + .name = "msm-compr-dsp", + .id = -1, +}; + +struct platform_device apq_multi_ch_pcm = { + .name = "msm-multi-ch-pcm-dsp", + .id = -1, +}; + +struct platform_device apq_pcm_hostless = { + .name = "msm-pcm-hostless", + .id = -1, +}; + +struct platform_device apq_cpudai_afe_01_rx = { + .name = "msm-dai-q6", + .id = 0xE0, +}; + +struct platform_device apq_cpudai_afe_01_tx = { + .name = "msm-dai-q6", + .id = 0xF0, +}; + +struct platform_device apq_cpudai_afe_02_rx = { + .name = "msm-dai-q6", + .id = 0xF1, +}; + +struct platform_device apq_cpudai_afe_02_tx = { + .name = "msm-dai-q6", + .id = 0xE1, +}; + +struct platform_device apq_pcm_afe = { + .name = "msm-pcm-afe", + .id = -1, +}; + +struct platform_device apq_cpudai_stub = { + .name = "msm-dai-stub", + .id = -1, +}; + +struct platform_device apq_cpudai_slimbus_1_rx = { + .name = "msm-dai-q6", + .id = 0x4002, +}; + +struct platform_device apq_cpudai_slimbus_1_tx = { + .name = "msm-dai-q6", + .id = 0x4003, +}; + +struct platform_device apq_cpudai_slimbus_2_tx = { + .name = "msm-dai-q6", + .id = 0x4005, +}; + +struct platform_device apq_cpudai_slimbus_3_rx = { + .name = "msm-dai-q6", + .id = 0x4006, +}; + +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, + }, +}; + +#define LPASS_SLIMBUS_PHYS 0x28080000 +#define LPASS_SLIMBUS_BAM_PHYS 0x28084000 +#define LPASS_SLIMBUS_SLEW (MSM8960_TLMM_PHYS + 0x207C) +/* 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 = LPASS_SLIMBUS_SLEW, + .end = LPASS_SLIMBUS_SLEW + 4 - 1, + .flags = IORESOURCE_MEM, + .name = "slimbus_slew_reg", + }, + { + .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 apq8064_slim_ctrl = { + .name = "msm_slim_ctrl", + .id = 1, + .num_resources = ARRAY_SIZE(slimbus_res), + .resource = slimbus_res, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +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_HSUSB1_PHYS, + .end = MSM_HSUSB1_PHYS + MSM_HSUSB1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device apq8064_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_HSUSB1_PHYS, + .end = MSM_HSUSB1_PHYS + MSM_HSUSB1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device apq8064_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 = MSM_HSUSB1_PHYS, + .end = MSM_HSUSB1_PHYS + MSM_HSUSB1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource resources_hsic_host[] = { + { + .start = 0x12510000, + .end = 0x12510000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB2_HSIC_IRQ, + .end = USB2_HSIC_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_GPIO_TO_INT(49), + .end = MSM_GPIO_TO_INT(49), + .name = "peripheral_status_irq", + .flags = IORESOURCE_IRQ, + }, + { + .start = 47, + .end = 47, + .name = "wakeup", + .flags = IORESOURCE_IO, + }, +}; + +static u64 dma_mask = DMA_BIT_MASK(32); +struct platform_device apq8064_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, + }, +}; + +struct platform_device apq8064_device_hsic_host = { + .name = "msm_hsic_host", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsic_host), + .resource = resources_hsic_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource resources_ehci_host3[] = { +{ + .start = MSM_HSUSB3_PHYS, + .end = MSM_HSUSB3_PHYS + MSM_HSUSB3_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB3_HS_IRQ, + .end = USB3_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device apq8064_device_ehci_host3 = { + .name = "msm_ehci_host", + .id = 0, + .num_resources = ARRAY_SIZE(resources_ehci_host3), + .resource = resources_ehci_host3, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct resource resources_ehci_host4[] = { +{ + .start = MSM_HSUSB4_PHYS, + .end = MSM_HSUSB4_PHYS + MSM_HSUSB4_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB4_HS_IRQ, + .end = USB4_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device apq8064_device_ehci_host4 = { + .name = "msm_ehci_host", + .id = 1, + .num_resources = ARRAY_SIZE(resources_ehci_host4), + .resource = resources_ehci_host4, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffff, + }, +}; + +/* MSM Video core device */ +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors vidc_init_vectors[] = { + { + .src = MSM_BUS_MASTER_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .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_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 54525952, + .ib = 436207616, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .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_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 40894464, + .ib = 327155712, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .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_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 163577856, + .ib = 1308622848, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .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_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 121634816, + .ib = 973078528, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .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_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 372244480, + .ib = 2560000000U, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 501219328, + .ib = 2560000000U, + }, + { + .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_VIDEO_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 222298112, + .ib = 2560000000U, + }, + { + .src = MSM_BUS_MASTER_VIDEO_DEC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 330301440, + .ib = 2560000000U, + }, + { + .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 APQ8064_VIDC_BASE_PHYS 0x04400000 +#define APQ8064_VIDC_BASE_SIZE 0x00100000 + +static struct resource apq8064_device_vidc_resources[] = { + { + .start = APQ8064_VIDC_BASE_PHYS, + .end = APQ8064_VIDC_BASE_PHYS + APQ8064_VIDC_BASE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = VCODEC_IRQ, + .end = VCODEC_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct msm_vidc_platform_data apq8064_vidc_platform_data = { +#ifdef CONFIG_MSM_BUS_SCALING + .vidc_bus_client_pdata = &vidc_bus_client_data, +#endif +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .memtype = ION_CP_MM_HEAP_ID, + .enable_ion = 1, + .cp_enabled = 1, +#else + .memtype = MEMTYPE_EBI1, + .enable_ion = 0, +#endif + .disable_dmx = 0, + .disable_fullhd = 0, + .cont_mode_dpb_count = 18, +}; + +struct platform_device apq8064_msm_device_vidc = { + .name = "msm_vidc", + .id = 0, + .num_resources = ARRAY_SIZE(apq8064_device_vidc_resources), + .resource = apq8064_device_vidc_resources, + .dev = { + .platform_data = &apq8064_vidc_platform_data, + }, +}; +#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 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 platform_device msm_bus_8064_sys_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM, +}; +struct platform_device msm_bus_8064_apps_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_APPSS, +}; +struct platform_device msm_bus_8064_mm_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_MMSS, +}; +struct platform_device msm_bus_8064_sys_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM_FPB, +}; +struct platform_device msm_bus_8064_cpss_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_CPSS_FPB, +}; + +static struct msm_sps_platform_data msm_sps_pdata = { + .bamdma_restricted_pipes = 0x06, +}; + +struct platform_device msm_device_sps_apq8064 = { + .name = "msm_sps", + .id = -1, + .num_resources = ARRAY_SIZE(resources_sps), + .resource = resources_sps, + .dev.platform_data = &msm_sps_pdata, +}; + +static struct resource smd_resource[] = { + { + .name = "a9_m2a_0", + .start = INT_A9_M2A_0, + .flags = IORESOURCE_IRQ, + }, + { + .name = "a9_m2a_5", + .start = INT_A9_M2A_5, + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_a11", + .start = INT_ADSP_A11, + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_a11_smsm", + .start = INT_ADSP_A11_SMSM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "dsps_a11", + .start = INT_DSPS_A11, + .flags = IORESOURCE_IRQ, + }, + { + .name = "dsps_a11_smsm", + .start = INT_DSPS_A11_SMSM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "wcnss_a11", + .start = INT_WCNSS_A11, + .flags = IORESOURCE_IRQ, + }, + { + .name = "wcnss_a11_smsm", + .start = INT_WCNSS_A11_SMSM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smd_subsystem_config smd_config_list[] = { + { + .irq_config_id = SMD_MODEM, + .subsys_name = "gss", + .edge = SMD_APPS_MODEM, + + .smd_int.irq_name = "a9_m2a_0", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 3, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "a9_m2a_5", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 4, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_Q6, + .subsys_name = "q6", + .edge = SMD_APPS_QDSP, + + .smd_int.irq_name = "adsp_a11", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 15, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "adsp_a11_smsm", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 14, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_DSPS, + .subsys_name = "dsps", + .edge = SMD_APPS_DSPS, + + .smd_int.irq_name = "dsps_a11", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1, + .smd_int.out_base = (void __iomem *)MSM_SIC_NON_SECURE_BASE, + .smd_int.out_offset = 0x4080, + + .smsm_int.irq_name = "dsps_a11_smsm", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1, + .smsm_int.out_base = (void __iomem *)MSM_SIC_NON_SECURE_BASE, + .smsm_int.out_offset = 0x4094, + }, + { + .irq_config_id = SMD_WCNSS, + .subsys_name = "wcnss", + .edge = SMD_APPS_WCNSS, + + .smd_int.irq_name = "wcnss_a11", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 25, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "wcnss_a11_smsm", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 23, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, +}; + +static struct smd_subsystem_restart_config smd_ssr_config = { + .disable_smsm_reset_handshake = 1, +}; + +static struct smd_platform smd_platform_data = { + .num_ss_configs = ARRAY_SIZE(smd_config_list), + .smd_ss_configs = smd_config_list, + .smd_ssr_config = &smd_ssr_config, +}; + +struct platform_device msm_device_smd_apq8064 = { + .name = "msm_smd", + .id = -1, + .resource = smd_resource, + .num_resources = ARRAY_SIZE(smd_resource), + .dev = { + .platform_data = &smd_platform_data, + }, +}; + +static struct resource resources_msm_pcie[] = { + { + .name = "parf", + .start = PCIE20_PARF_PHYS, + .end = PCIE20_PARF_PHYS + PCIE20_PARF_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "elbi", + .start = PCIE20_ELBI_PHYS, + .end = PCIE20_ELBI_PHYS + PCIE20_ELBI_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "pcie20", + .start = PCIE20_PHYS, + .end = PCIE20_PHYS + PCIE20_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "axi_bar", + .start = PCIE_AXI_BAR_PHYS, + .end = PCIE_AXI_BAR_PHYS + PCIE_AXI_BAR_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "axi_conf", + .start = PCIE_AXI_CONF_PHYS, + .end = PCIE_AXI_CONF_PHYS + PCIE_AXI_CONF_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_pcie = { + .name = "msm_pcie", + .id = -1, + .num_resources = ARRAY_SIZE(resources_msm_pcie), + .resource = resources_msm_pcie, +}; + +#ifdef CONFIG_HW_RANDOM_MSM +/* PRNG device */ +#define MSM_PRNG_PHYS 0x1A500000 +static struct resource rng_resources = { + .flags = IORESOURCE_MEM, + .start = MSM_PRNG_PHYS, + .end = MSM_PRNG_PHYS + SZ_512 - 1, +}; + +struct platform_device apq8064_device_rng = { + .name = "msm_rng", + .id = 0, + .num_resources = 1, + .resource = &rng_resources, +}; +#endif + +static struct resource msm_gss_resources[] = { + { + .start = 0x10000000, + .end = 0x10000000 + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x10008000, + .end = 0x10008000 + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_gss = { + .name = "pil_gss", + .id = -1, + .num_resources = ARRAY_SIZE(msm_gss_resources), + .resource = msm_gss_resources, +}; + +static struct fs_driver_data gfx3d_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk", .reset_rate = 27000000 }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_3D, + .bus_port1 = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, +}; + +static struct fs_driver_data ijpeg_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_JPEG_ENC, +}; + +static struct fs_driver_data rot_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_ROTATOR, +}; + +static struct fs_driver_data ved_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VIDEO_ENC, + .bus_port1 = MSM_BUS_MASTER_VIDEO_DEC, +}; + +static struct fs_driver_data vfe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VFE, +}; + +static struct fs_driver_data vpe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VPE, +}; + +static struct fs_driver_data vcap_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 }, + }, + .bus_port0 = MSM_BUS_MASTER_VIDEO_CAP, +}; + +struct platform_device *apq8064_footswitch[] __initdata = { + FS_8X60(FS_ROT, "vdd", "msm_rotator.0", &rot_fs_data), + FS_8X60(FS_IJPEG, "vdd", "msm_gemini.0", &ijpeg_fs_data), + FS_8X60(FS_VFE, "fs_vfe", NULL, &vfe_fs_data), + FS_8X60(FS_VPE, "fs_vpe", NULL, &vpe_fs_data), + FS_8X60(FS_GFX3D, "vdd", "kgsl-3d0.0", &gfx3d_fs_data), + FS_8X60(FS_VED, "vdd", "msm_vidc.0", &ved_fs_data), + FS_8X60(FS_VCAP, "vdd", "msm_vcap.0", &vcap_fs_data), +}; +unsigned apq8064_num_footswitch __initdata = ARRAY_SIZE(apq8064_footswitch); + +struct msm_rpm_platform_data apq8064_rpm_data __initdata = { + .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_wakeup = RPM_APCC_CPU0_WAKE_UP_IRQ, + .ipc_rpm_reg = MSM_APCS_GCC_BASE + 0x008, + .ipc_rpm_val = 4, + .target_id = { + MSM_RPM_MAP(8064, NOTIFICATION_CONFIGURED_0, NOTIFICATION, 4), + MSM_RPM_MAP(8064, NOTIFICATION_REGISTERED_0, NOTIFICATION, 4), + MSM_RPM_MAP(8064, INVALIDATE_0, INVALIDATE, 8), + MSM_RPM_MAP(8064, TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8064, TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8064, RPM_CTL, RPM_CTL, 1), + MSM_RPM_MAP(8064, CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(8064, PXO_CLK, PXO_CLK, 1), + MSM_RPM_MAP(8064, APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1), + MSM_RPM_MAP(8064, SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(8064, MM_FABRIC_CLK, MM_FABRIC_CLK, 1), + MSM_RPM_MAP(8064, DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(8064, SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(8064, CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(8064, MMFPB_CLK, MMFPB_CLK, 1), + MSM_RPM_MAP(8064, EBI1_CLK, EBI1_CLK, 1), + MSM_RPM_MAP(8064, APPS_FABRIC_CFG_HALT_0, + APPS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8064, APPS_FABRIC_CFG_CLKMOD_0, + APPS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8064, APPS_FABRIC_CFG_IOCTL, + APPS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8064, APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 12), + MSM_RPM_MAP(8064, SYS_FABRIC_CFG_HALT_0, + SYS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8064, SYS_FABRIC_CFG_CLKMOD_0, + SYS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8064, SYS_FABRIC_CFG_IOCTL, + SYS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8064, SYSTEM_FABRIC_ARB_0, SYSTEM_FABRIC_ARB, 30), + MSM_RPM_MAP(8064, MMSS_FABRIC_CFG_HALT_0, + MMSS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8064, MMSS_FABRIC_CFG_CLKMOD_0, + MMSS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8064, MMSS_FABRIC_CFG_IOCTL, + MMSS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8064, MM_FABRIC_ARB_0, MM_FABRIC_ARB, 21), + MSM_RPM_MAP(8064, PM8921_S1_0, PM8921_S1, 2), + MSM_RPM_MAP(8064, PM8921_S2_0, PM8921_S2, 2), + MSM_RPM_MAP(8064, PM8921_S3_0, PM8921_S3, 2), + MSM_RPM_MAP(8064, PM8921_S4_0, PM8921_S4, 2), + MSM_RPM_MAP(8064, PM8921_S5_0, PM8921_S5, 2), + MSM_RPM_MAP(8064, PM8921_S6_0, PM8921_S6, 2), + MSM_RPM_MAP(8064, PM8921_S7_0, PM8921_S7, 2), + MSM_RPM_MAP(8064, PM8921_S8_0, PM8921_S8, 2), + MSM_RPM_MAP(8064, PM8921_L1_0, PM8921_L1, 2), + MSM_RPM_MAP(8064, PM8921_L2_0, PM8921_L2, 2), + MSM_RPM_MAP(8064, PM8921_L3_0, PM8921_L3, 2), + MSM_RPM_MAP(8064, PM8921_L4_0, PM8921_L4, 2), + MSM_RPM_MAP(8064, PM8921_L5_0, PM8921_L5, 2), + MSM_RPM_MAP(8064, PM8921_L6_0, PM8921_L6, 2), + MSM_RPM_MAP(8064, PM8921_L7_0, PM8921_L7, 2), + MSM_RPM_MAP(8064, PM8921_L8_0, PM8921_L8, 2), + MSM_RPM_MAP(8064, PM8921_L9_0, PM8921_L9, 2), + MSM_RPM_MAP(8064, PM8921_L10_0, PM8921_L10, 2), + MSM_RPM_MAP(8064, PM8921_L11_0, PM8921_L11, 2), + MSM_RPM_MAP(8064, PM8921_L12_0, PM8921_L12, 2), + MSM_RPM_MAP(8064, PM8921_L13_0, PM8921_L13, 2), + MSM_RPM_MAP(8064, PM8921_L14_0, PM8921_L14, 2), + MSM_RPM_MAP(8064, PM8921_L15_0, PM8921_L15, 2), + MSM_RPM_MAP(8064, PM8921_L16_0, PM8921_L16, 2), + MSM_RPM_MAP(8064, PM8921_L17_0, PM8921_L17, 2), + MSM_RPM_MAP(8064, PM8921_L18_0, PM8921_L18, 2), + MSM_RPM_MAP(8064, PM8921_L19_0, PM8921_L19, 2), + MSM_RPM_MAP(8064, PM8921_L20_0, PM8921_L20, 2), + MSM_RPM_MAP(8064, PM8921_L21_0, PM8921_L21, 2), + MSM_RPM_MAP(8064, PM8921_L22_0, PM8921_L22, 2), + MSM_RPM_MAP(8064, PM8921_L23_0, PM8921_L23, 2), + MSM_RPM_MAP(8064, PM8921_L24_0, PM8921_L24, 2), + MSM_RPM_MAP(8064, PM8921_L25_0, PM8921_L25, 2), + MSM_RPM_MAP(8064, PM8921_L26_0, PM8921_L26, 2), + MSM_RPM_MAP(8064, PM8921_L27_0, PM8921_L27, 2), + MSM_RPM_MAP(8064, PM8921_L28_0, PM8921_L28, 2), + MSM_RPM_MAP(8064, PM8921_L29_0, PM8921_L29, 2), + MSM_RPM_MAP(8064, PM8921_CLK1_0, PM8921_CLK1, 2), + MSM_RPM_MAP(8064, PM8921_CLK2_0, PM8921_CLK2, 2), + MSM_RPM_MAP(8064, PM8921_LVS1, PM8921_LVS1, 1), + MSM_RPM_MAP(8064, PM8921_LVS2, PM8921_LVS2, 1), + MSM_RPM_MAP(8064, PM8921_LVS3, PM8921_LVS3, 1), + MSM_RPM_MAP(8064, PM8921_LVS4, PM8921_LVS4, 1), + MSM_RPM_MAP(8064, PM8921_LVS5, PM8921_LVS5, 1), + MSM_RPM_MAP(8064, PM8921_LVS6, PM8921_LVS6, 1), + MSM_RPM_MAP(8064, PM8921_LVS7, PM8921_LVS7, 1), + MSM_RPM_MAP(8064, PM8821_S1_0, PM8821_S1, 2), + MSM_RPM_MAP(8064, PM8821_S2_0, PM8821_S2, 2), + MSM_RPM_MAP(8064, PM8821_L1_0, PM8821_L1, 2), + MSM_RPM_MAP(8064, NCP_0, NCP, 2), + MSM_RPM_MAP(8064, CXO_BUFFERS, CXO_BUFFERS, 1), + MSM_RPM_MAP(8064, USB_OTG_SWITCH, USB_OTG_SWITCH, 1), + MSM_RPM_MAP(8064, HDMI_SWITCH, HDMI_SWITCH, 1), + MSM_RPM_MAP(8064, DDR_DMM_0, DDR_DMM, 2), + MSM_RPM_MAP(8064, QDSS_CLK, QDSS_CLK, 1), + }, + .target_status = { + MSM_RPM_STATUS_ID_MAP(8064, VERSION_MAJOR), + MSM_RPM_STATUS_ID_MAP(8064, VERSION_MINOR), + MSM_RPM_STATUS_ID_MAP(8064, VERSION_BUILD), + MSM_RPM_STATUS_ID_MAP(8064, SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8064, SUPPORTED_RESOURCES_1), + MSM_RPM_STATUS_ID_MAP(8064, SUPPORTED_RESOURCES_2), + MSM_RPM_STATUS_ID_MAP(8064, RESERVED_SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8064, SEQUENCE), + MSM_RPM_STATUS_ID_MAP(8064, RPM_CTL), + MSM_RPM_STATUS_ID_MAP(8064, CXO_CLK), + MSM_RPM_STATUS_ID_MAP(8064, PXO_CLK), + MSM_RPM_STATUS_ID_MAP(8064, APPS_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8064, SYSTEM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8064, MM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8064, DAYTONA_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8064, SFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8064, CFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8064, MMFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8064, EBI1_CLK), + MSM_RPM_STATUS_ID_MAP(8064, APPS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8064, APPS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8064, APPS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8064, APPS_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8064, SYS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8064, SYS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8064, SYS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8064, SYSTEM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8064, MMSS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8064, MMSS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8064, MMSS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8064, MM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S1_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S1_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S2_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S2_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S3_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S3_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S4_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S4_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S5_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S5_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S6_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S6_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S7_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S7_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S8_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_S8_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L1_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L1_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L2_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L2_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L3_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L3_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L4_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L4_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L5_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L5_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L6_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L6_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L7_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L7_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L8_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L8_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L9_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L9_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L10_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L10_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L11_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L11_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L12_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L12_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L13_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L13_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L14_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L14_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L15_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L15_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L16_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L16_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L17_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L17_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L18_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L18_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L19_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L19_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L20_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L20_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L21_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L21_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L22_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L22_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L23_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L23_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L24_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L24_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L25_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L25_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L26_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L26_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L27_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L27_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L28_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L28_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L29_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_L29_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_CLK1_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_CLK1_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_CLK2_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_CLK2_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS1), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS2), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS3), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS4), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS5), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS6), + MSM_RPM_STATUS_ID_MAP(8064, PM8921_LVS7), + MSM_RPM_STATUS_ID_MAP(8064, NCP_0), + MSM_RPM_STATUS_ID_MAP(8064, NCP_1), + MSM_RPM_STATUS_ID_MAP(8064, CXO_BUFFERS), + MSM_RPM_STATUS_ID_MAP(8064, USB_OTG_SWITCH), + MSM_RPM_STATUS_ID_MAP(8064, HDMI_SWITCH), + MSM_RPM_STATUS_ID_MAP(8064, DDR_DMM_0), + MSM_RPM_STATUS_ID_MAP(8064, DDR_DMM_1), + MSM_RPM_STATUS_ID_MAP(8064, EBI1_CH0_RANGE), + MSM_RPM_STATUS_ID_MAP(8064, EBI1_CH1_RANGE), + MSM_RPM_STATUS_ID_MAP(8064, PM8821_S1_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8821_S1_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8821_S2_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8821_S2_1), + MSM_RPM_STATUS_ID_MAP(8064, PM8821_L1_0), + MSM_RPM_STATUS_ID_MAP(8064, PM8821_L1_1), + }, + .target_ctrl_id = { + MSM_RPM_CTRL_MAP(8064, VERSION_MAJOR), + MSM_RPM_CTRL_MAP(8064, VERSION_MINOR), + MSM_RPM_CTRL_MAP(8064, VERSION_BUILD), + MSM_RPM_CTRL_MAP(8064, REQ_CTX_0), + MSM_RPM_CTRL_MAP(8064, REQ_SEL_0), + MSM_RPM_CTRL_MAP(8064, ACK_CTX_0), + MSM_RPM_CTRL_MAP(8064, ACK_SEL_0), + }, + .sel_invalidate = MSM_RPM_8064_SEL_INVALIDATE, + .sel_notification = MSM_RPM_8064_SEL_NOTIFICATION, + .sel_last = MSM_RPM_8064_SEL_LAST, + .ver = {3, 0, 0}, +}; + +struct platform_device apq8064_rpm_device = { + .name = "msm_rpm", + .id = -1, +}; + +static struct msm_rpmstats_platform_data msm_rpm_stat_pdata = { + .phys_addr_base = 0x0010D204, + .phys_size = SZ_8K, +}; + +struct platform_device apq8064_rpm_stat_device = { + .name = "msm_rpm_stat", + .id = -1, + .dev = { + .platform_data = &msm_rpm_stat_pdata, + }, +}; + +static struct msm_rpm_log_platform_data msm_rpm_log_pdata = { + .phys_addr_base = 0x0010C000, + .reg_offsets = { + [MSM_RPM_LOG_PAGE_INDICES] = 0x00000080, + [MSM_RPM_LOG_PAGE_BUFFER] = 0x000000A0, + }, + .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 */ +}; + +struct platform_device apq8064_rpm_log_device = { + .name = "msm_rpm_log", + .id = -1, + .dev = { + .platform_data = &msm_rpm_log_pdata, + }, +}; + +/* Sensors DSPS platform data */ + +#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 + * apq8064_init_dsps(). + */ + +struct msm_dsps_platform_data msm_dsps_pdata_8064 = { + .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, + }, + + { + .start = PPSS_WDOG_TIMER_IRQ, + .end = PPSS_WDOG_TIMER_IRQ, + .name = "ppss_wdog", + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_dsps_device_8064 = { + .name = "msm_dsps", + .id = 0, + .num_resources = ARRAY_SIZE(msm_dsps_resources), + .resource = msm_dsps_resources, + .dev.platform_data = &msm_dsps_pdata_8064, +}; + +#ifdef CONFIG_MSM_MPM +static uint16_t msm_mpm_irqs_m2a[MSM_MPM_NR_MPM_IRQS] __initdata = { + [1] = MSM_GPIO_TO_INT(26), + [2] = MSM_GPIO_TO_INT(88), + [4] = MSM_GPIO_TO_INT(73), + [5] = MSM_GPIO_TO_INT(74), + [6] = MSM_GPIO_TO_INT(75), + [7] = MSM_GPIO_TO_INT(76), + [8] = MSM_GPIO_TO_INT(77), + [9] = MSM_GPIO_TO_INT(36), + [10] = MSM_GPIO_TO_INT(84), + [11] = MSM_GPIO_TO_INT(7), + [12] = MSM_GPIO_TO_INT(11), + [13] = MSM_GPIO_TO_INT(52), + [14] = MSM_GPIO_TO_INT(15), + [15] = MSM_GPIO_TO_INT(83), + [16] = USB3_HS_IRQ, + [19] = MSM_GPIO_TO_INT(61), + [20] = MSM_GPIO_TO_INT(58), + [23] = MSM_GPIO_TO_INT(65), + [24] = MSM_GPIO_TO_INT(63), + [25] = USB1_HS_IRQ, + [27] = HDMI_IRQ, + [29] = MSM_GPIO_TO_INT(22), + [30] = MSM_GPIO_TO_INT(72), + [31] = USB4_HS_IRQ, + [33] = MSM_GPIO_TO_INT(44), + [34] = MSM_GPIO_TO_INT(39), + [35] = MSM_GPIO_TO_INT(19), + [36] = MSM_GPIO_TO_INT(23), + [37] = MSM_GPIO_TO_INT(41), + [38] = MSM_GPIO_TO_INT(30), + [41] = MSM_GPIO_TO_INT(42), + [42] = MSM_GPIO_TO_INT(56), + [43] = MSM_GPIO_TO_INT(55), + [44] = MSM_GPIO_TO_INT(50), + [45] = MSM_GPIO_TO_INT(49), + [46] = MSM_GPIO_TO_INT(47), + [47] = MSM_GPIO_TO_INT(45), + [48] = MSM_GPIO_TO_INT(38), + [49] = MSM_GPIO_TO_INT(34), + [50] = MSM_GPIO_TO_INT(32), + [51] = MSM_GPIO_TO_INT(29), + [52] = MSM_GPIO_TO_INT(18), + [53] = MSM_GPIO_TO_INT(10), + [54] = MSM_GPIO_TO_INT(81), + [55] = MSM_GPIO_TO_INT(6), +}; + +static uint16_t msm_mpm_bypassed_apps_irqs[] __initdata = { + 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_30, + SPS_MTI_31, + RIVA_APSS_SPARE_IRQ, + RIVA_APPS_WLAN_SMSM_IRQ, + RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ, + RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ, +}; + +struct msm_mpm_device_data apq8064_mpm_dev_data __initdata = { + .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 + +/* AP2MDM_SOFT_RESET is implemented by the PON_RESET_N gpio */ +#define MDM2AP_ERRFATAL 19 +#define AP2MDM_ERRFATAL 18 +#define MDM2AP_STATUS 49 +#define AP2MDM_STATUS 48 +#define AP2MDM_SOFT_RESET 27 +#define AP2MDM_WAKEUP 35 + +static struct resource mdm_resources[] = { + { + .start = MDM2AP_ERRFATAL, + .end = MDM2AP_ERRFATAL, + .name = "MDM2AP_ERRFATAL", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_ERRFATAL, + .end = AP2MDM_ERRFATAL, + .name = "AP2MDM_ERRFATAL", + .flags = IORESOURCE_IO, + }, + { + .start = MDM2AP_STATUS, + .end = MDM2AP_STATUS, + .name = "MDM2AP_STATUS", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_STATUS, + .end = AP2MDM_STATUS, + .name = "AP2MDM_STATUS", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_SOFT_RESET, + .end = AP2MDM_SOFT_RESET, + .name = "AP2MDM_SOFT_RESET", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_WAKEUP, + .end = AP2MDM_WAKEUP, + .name = "AP2MDM_WAKEUP", + .flags = IORESOURCE_IO, + }, +}; + +struct platform_device mdm_8064_device = { + .name = "mdm2_modem", + .id = -1, + .num_resources = ARRAY_SIZE(mdm_resources), + .resource = mdm_resources, +}; + +static int apq8064_LPM_latency = 1000; /* >100 usec for WFI */ + +struct platform_device apq8064_cpu_idle_device = { + .name = "msm_cpu_idle", + .id = -1, + .dev = { + .platform_data = &apq8064_LPM_latency, + }, +}; + +static struct msm_dcvs_freq_entry apq8064_freq[] = { + { 384000, 166981, 345600}, + { 702000, 213049, 632502}, + {1026000, 285712, 925613}, + {1242000, 383945, 1176550}, + {1458000, 419729, 1465478}, + {1512000, 434116, 1546674}, + +}; + +static struct msm_dcvs_core_info apq8064_core_info = { + .freq_tbl = &apq8064_freq[0], + .core_param = { + .max_time_us = 100000, + .num_freq = ARRAY_SIZE(apq8064_freq), + }, + .algo_param = { + .slack_time_us = 58000, + .scale_slack_time = 0, + .scale_slack_time_pct = 0, + .disable_pc_threshold = 1458000, + .em_window_size = 100000, + .em_max_util_pct = 97, + .ss_window_size = 1000000, + .ss_util_pct = 95, + .ss_iobusy_conv = 100, + }, +}; + +struct platform_device apq8064_msm_gov_device = { + .name = "msm_dcvs_gov", + .id = -1, + .dev = { + .platform_data = &apq8064_core_info, + }, +}; + +#ifdef CONFIG_MSM_VCAP +#define VCAP_HW_BASE 0x05900000 + +static struct msm_bus_vectors vcap_init_vectors[] = { + { + .src = MSM_BUS_MASTER_VIDEO_CAP, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + + +static struct msm_bus_vectors vcap_480_vectors[] = { + { + .src = MSM_BUS_MASTER_VIDEO_CAP, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1280 * 720 * 3 * 60, + .ib = 1280 * 720 * 3 * 60 * 1.5, + }, +}; + +static struct msm_bus_vectors vcap_720_vectors[] = { + { + .src = MSM_BUS_MASTER_VIDEO_CAP, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1280 * 720 * 3 * 60, + .ib = 1280 * 720 * 3 * 60 * 1.5, + }, +}; + +static struct msm_bus_vectors vcap_1080_vectors[] = { + { + .src = MSM_BUS_MASTER_VIDEO_CAP, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1920 * 1080 * 3 * 60, + .ib = 1920 * 1080 * 3 * 60 * 1.5, + }, +}; + +static struct msm_bus_paths vcap_bus_usecases[] = { + { + ARRAY_SIZE(vcap_init_vectors), + vcap_init_vectors, + }, + { + ARRAY_SIZE(vcap_480_vectors), + vcap_480_vectors, + }, + { + ARRAY_SIZE(vcap_720_vectors), + vcap_720_vectors, + }, + { + ARRAY_SIZE(vcap_1080_vectors), + vcap_1080_vectors, + }, +}; + +static struct msm_bus_scale_pdata vcap_axi_client_pdata = { + vcap_bus_usecases, + ARRAY_SIZE(vcap_bus_usecases), +}; + +static struct resource msm_vcap_resources[] = { + { + .name = "vcap", + .start = VCAP_HW_BASE, + .end = VCAP_HW_BASE + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "vc_irq", + .start = VCAP_VC, + .end = VCAP_VC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "vp_irq", + .start = VCAP_VP, + .end = VCAP_VP, + .flags = IORESOURCE_IRQ, + }, +}; + +static unsigned vcap_gpios[] = { + 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 80, 82, + 83, 84, 85, 86, 87, +}; + +static struct vcap_platform_data vcap_pdata = { + .gpios = vcap_gpios, + .num_gpios = ARRAY_SIZE(vcap_gpios), + .bus_client_pdata = &vcap_axi_client_pdata +}; + +struct platform_device msm8064_device_vcap = { + .name = "msm_vcap", + .id = 0, + .resource = msm_vcap_resources, + .num_resources = ARRAY_SIZE(msm_vcap_resources), + .dev = { + .platform_data = &vcap_pdata, + }, +}; +#endif + +static struct resource msm_cache_erp_resources[] = { + { + .name = "l1_irq", + .start = SC_SICCPUXEXTFAULTIRPTREQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "l2_irq", + .start = APCC_QGICL2IRPTREQ, + .flags = IORESOURCE_IRQ, + } +}; + +struct platform_device apq8064_device_cache_erp = { + .name = "msm_cache_erp", + .id = -1, + .num_resources = ARRAY_SIZE(msm_cache_erp_resources), + .resource = msm_cache_erp_resources, +}; + +#define MSM_QDSS_PHYS_BASE 0x01A00000 +#define MSM_ETM_PHYS_BASE (MSM_QDSS_PHYS_BASE + 0x1C000) + +#define QDSS_SOURCE(src_name, fpm) { .name = src_name, .fport_mask = fpm, } + +static struct qdss_source msm_qdss_sources[] = { + QDSS_SOURCE("msm_etm", 0x33), + QDSS_SOURCE("msm_oxili", 0x80), +}; + +static struct msm_qdss_platform_data qdss_pdata = { + .src_table = msm_qdss_sources, + .size = ARRAY_SIZE(msm_qdss_sources), + .afamily = 1, +}; + +struct platform_device apq8064_qdss_device = { + .name = "msm_qdss", + .id = -1, + .dev = { + .platform_data = &qdss_pdata, + }, +}; + +static struct resource msm_etm_resources[] = { + { + .start = MSM_ETM_PHYS_BASE, + .end = MSM_ETM_PHYS_BASE + (SZ_4K * 4) - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device apq8064_etm_device = { + .name = "msm_etm", + .id = 0, + .num_resources = ARRAY_SIZE(msm_etm_resources), + .resource = msm_etm_resources, +}; + +struct msm_iommu_domain_name apq8064_iommu_ctx_names[] = { + /* Camera */ + { + .name = "vpe_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vpe_dst", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_imgwr", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_misc", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_dst", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_dst", + .domain = CAMERA_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_src", + .domain = ROTATOR_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_dst", + .domain = ROTATOR_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 mem_pool apq8064_video_pools[] = { + /* + * Video hardware has the following requirements: + * 1. All video addresses used by the video hardware must be at a higher + * address than video firmware address. + * 2. Video hardware can only access a range of 256MB from the base of + * the video firmware. + */ + [VIDEO_FIRMWARE_POOL] = + /* Low addresses, intended for video firmware */ + { + .paddr = SZ_128K, + .size = SZ_16M - SZ_128K, + }, + [VIDEO_MAIN_POOL] = + /* Main video pool */ + { + .paddr = SZ_16M, + .size = SZ_256M - SZ_16M, + }, + [GEN_POOL] = + /* Remaining address space up to 2G */ + { + .paddr = SZ_256M, + .size = SZ_2G - SZ_256M, + }, +}; + +static struct mem_pool apq8064_camera_pools[] = { + [GEN_POOL] = + /* One address space for camera */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct mem_pool apq8064_display_pools[] = { + [GEN_POOL] = + /* One address space for display */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct mem_pool apq8064_rotator_pools[] = { + [GEN_POOL] = + /* One address space for rotator */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct msm_iommu_domain apq8064_iommu_domains[] = { + [VIDEO_DOMAIN] = { + .iova_pools = apq8064_video_pools, + .npools = ARRAY_SIZE(apq8064_video_pools), + }, + [CAMERA_DOMAIN] = { + .iova_pools = apq8064_camera_pools, + .npools = ARRAY_SIZE(apq8064_camera_pools), + }, + [DISPLAY_DOMAIN] = { + .iova_pools = apq8064_display_pools, + .npools = ARRAY_SIZE(apq8064_display_pools), + }, + [ROTATOR_DOMAIN] = { + .iova_pools = apq8064_rotator_pools, + .npools = ARRAY_SIZE(apq8064_rotator_pools), + }, +}; + +struct iommu_domains_pdata apq8064_iommu_domain_pdata = { + .domains = apq8064_iommu_domains, + .ndomains = ARRAY_SIZE(apq8064_iommu_domains), + .domain_names = apq8064_iommu_ctx_names, + .nnames = ARRAY_SIZE(apq8064_iommu_ctx_names), + .domain_alloc_flags = 0, +}; + +struct platform_device apq8064_iommu_domain_device = { + .name = "iommu_domains", + .id = -1, + .dev = { + .platform_data = &apq8064_iommu_domain_pdata, + } +}; + +struct msm_rtb_platform_data apq8064_rtb_pdata = { + .size = SZ_1M, +}; + +static int __init msm_rtb_set_buffer_size(char *p) +{ + int s; + + s = memparse(p, NULL); + apq8064_rtb_pdata.size = ALIGN(s, SZ_4K); + return 0; +} +early_param("msm_rtb_size", msm_rtb_set_buffer_size); + +struct platform_device apq8064_rtb_device = { + .name = "msm_rtb", + .id = -1, + .dev = { + .platform_data = &apq8064_rtb_pdata, + }, +}; + +#define APQ8064_L1_SIZE SZ_1M +/* + * The actual L2 size is smaller but we need a larger buffer + * size to store other dump information + */ +#define APQ8064_L2_SIZE SZ_8M + +struct msm_cache_dump_platform_data apq8064_cache_dump_pdata = { + .l2_size = APQ8064_L2_SIZE, + .l1_size = APQ8064_L1_SIZE, +}; + +struct platform_device apq8064_cache_dump_device = { + .name = "msm_cache_dump", + .id = -1, + .dev = { + .platform_data = &apq8064_cache_dump_pdata, + }, +}; diff --git a/arch/arm/mach-msm/devices-8930.c b/arch/arm/mach-msm/devices-8930.c new file mode 100644 index 0000000000000000000000000000000000000000..2c4687fe6c6718bb5558587a64457a180873c7dc --- /dev/null +++ b/arch/arm/mach-msm/devices-8930.c @@ -0,0 +1,906 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "rpm_log.h" +#include "rpm_stats.h" +#include "footswitch.h" + +#ifdef CONFIG_MSM_MPM +#include +#endif + +struct msm_rpm_platform_data msm8930_rpm_data __initdata = { + .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_wakeup = RPM_APCC_CPU0_WAKE_UP_IRQ, + .ipc_rpm_reg = MSM_APCS_GCC_BASE + 0x008, + .ipc_rpm_val = 4, + .target_id = { + MSM_RPM_MAP(8930, NOTIFICATION_CONFIGURED_0, NOTIFICATION, 4), + MSM_RPM_MAP(8930, NOTIFICATION_REGISTERED_0, NOTIFICATION, 4), + MSM_RPM_MAP(8930, INVALIDATE_0, INVALIDATE, 8), + MSM_RPM_MAP(8960, TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8960, TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8930, RPM_CTL, RPM_CTL, 1), + MSM_RPM_MAP(8930, CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(8930, PXO_CLK, PXO_CLK, 1), + MSM_RPM_MAP(8930, APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1), + MSM_RPM_MAP(8930, SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(8930, MM_FABRIC_CLK, MM_FABRIC_CLK, 1), + MSM_RPM_MAP(8930, DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(8930, SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(8930, CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(8930, MMFPB_CLK, MMFPB_CLK, 1), + MSM_RPM_MAP(8930, EBI1_CLK, EBI1_CLK, 1), + MSM_RPM_MAP(8930, APPS_FABRIC_CFG_HALT_0, + APPS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8930, APPS_FABRIC_CFG_CLKMOD_0, + APPS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8930, APPS_FABRIC_CFG_IOCTL, + APPS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8930, APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 6), + MSM_RPM_MAP(8930, SYS_FABRIC_CFG_HALT_0, + SYS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8930, SYS_FABRIC_CFG_CLKMOD_0, + SYS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8930, SYS_FABRIC_CFG_IOCTL, + SYS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8930, SYSTEM_FABRIC_ARB_0, + SYSTEM_FABRIC_ARB, 20), + MSM_RPM_MAP(8930, MMSS_FABRIC_CFG_HALT_0, + MMSS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8930, MMSS_FABRIC_CFG_CLKMOD_0, + MMSS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8930, MMSS_FABRIC_CFG_IOCTL, + MMSS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8930, MM_FABRIC_ARB_0, MM_FABRIC_ARB, 11), + MSM_RPM_MAP(8930, PM8038_S1_0, PM8038_S1, 2), + MSM_RPM_MAP(8930, PM8038_S2_0, PM8038_S2, 2), + MSM_RPM_MAP(8930, PM8038_S3_0, PM8038_S3, 2), + MSM_RPM_MAP(8930, PM8038_S4_0, PM8038_S4, 2), + MSM_RPM_MAP(8930, PM8038_S5_0, PM8038_S5, 2), + MSM_RPM_MAP(8930, PM8038_S6_0, PM8038_S6, 2), + MSM_RPM_MAP(8930, PM8038_L1_0, PM8038_L1, 2), + MSM_RPM_MAP(8930, PM8038_L2_0, PM8038_L2, 2), + MSM_RPM_MAP(8930, PM8038_L3_0, PM8038_L3, 2), + MSM_RPM_MAP(8930, PM8038_L4_0, PM8038_L4, 2), + MSM_RPM_MAP(8930, PM8038_L5_0, PM8038_L5, 2), + MSM_RPM_MAP(8930, PM8038_L6_0, PM8038_L6, 2), + MSM_RPM_MAP(8930, PM8038_L7_0, PM8038_L7, 2), + MSM_RPM_MAP(8930, PM8038_L8_0, PM8038_L8, 2), + MSM_RPM_MAP(8930, PM8038_L9_0, PM8038_L9, 2), + MSM_RPM_MAP(8930, PM8038_L10_0, PM8038_L10, 2), + MSM_RPM_MAP(8930, PM8038_L11_0, PM8038_L11, 2), + MSM_RPM_MAP(8930, PM8038_L12_0, PM8038_L12, 2), + MSM_RPM_MAP(8930, PM8038_L13_0, PM8038_L13, 2), + MSM_RPM_MAP(8930, PM8038_L14_0, PM8038_L14, 2), + MSM_RPM_MAP(8930, PM8038_L15_0, PM8038_L15, 2), + MSM_RPM_MAP(8930, PM8038_L16_0, PM8038_L16, 2), + MSM_RPM_MAP(8930, PM8038_L17_0, PM8038_L17, 2), + MSM_RPM_MAP(8930, PM8038_L18_0, PM8038_L18, 2), + MSM_RPM_MAP(8930, PM8038_L19_0, PM8038_L19, 2), + MSM_RPM_MAP(8930, PM8038_L20_0, PM8038_L20, 2), + MSM_RPM_MAP(8930, PM8038_L21_0, PM8038_L21, 2), + MSM_RPM_MAP(8930, PM8038_L22_0, PM8038_L22, 2), + MSM_RPM_MAP(8930, PM8038_L23_0, PM8038_L23, 2), + MSM_RPM_MAP(8930, PM8038_L24_0, PM8038_L24, 2), + MSM_RPM_MAP(8930, PM8038_L25_0, PM8038_L25, 2), + MSM_RPM_MAP(8930, PM8038_L26_0, PM8038_L26, 2), + MSM_RPM_MAP(8930, PM8038_L27_0, PM8038_L27, 2), + MSM_RPM_MAP(8930, PM8038_CLK1_0, PM8038_CLK1, 2), + MSM_RPM_MAP(8930, PM8038_CLK2_0, PM8038_CLK2, 2), + MSM_RPM_MAP(8930, PM8038_LVS1, PM8038_LVS1, 1), + MSM_RPM_MAP(8930, PM8038_LVS2, PM8038_LVS2, 1), + MSM_RPM_MAP(8930, NCP_0, NCP, 2), + MSM_RPM_MAP(8930, CXO_BUFFERS, CXO_BUFFERS, 1), + MSM_RPM_MAP(8930, USB_OTG_SWITCH, USB_OTG_SWITCH, 1), + MSM_RPM_MAP(8930, HDMI_SWITCH, HDMI_SWITCH, 1), + MSM_RPM_MAP(8930, DDR_DMM_0, DDR_DMM, 2), + MSM_RPM_MAP(8930, QDSS_CLK, QDSS_CLK, 1), + MSM_RPM_MAP(8930, VOLTAGE_CORNER, VOLTAGE_CORNER, 1), + }, + .target_status = { + MSM_RPM_STATUS_ID_MAP(8930, VERSION_MAJOR), + MSM_RPM_STATUS_ID_MAP(8930, VERSION_MINOR), + MSM_RPM_STATUS_ID_MAP(8930, VERSION_BUILD), + MSM_RPM_STATUS_ID_MAP(8930, SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8930, SUPPORTED_RESOURCES_1), + MSM_RPM_STATUS_ID_MAP(8930, SUPPORTED_RESOURCES_2), + MSM_RPM_STATUS_ID_MAP(8930, RESERVED_SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8930, SEQUENCE), + MSM_RPM_STATUS_ID_MAP(8930, RPM_CTL), + MSM_RPM_STATUS_ID_MAP(8930, CXO_CLK), + MSM_RPM_STATUS_ID_MAP(8930, PXO_CLK), + MSM_RPM_STATUS_ID_MAP(8930, APPS_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8930, SYSTEM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8930, MM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8930, DAYTONA_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8930, SFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8930, CFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8930, MMFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8930, EBI1_CLK), + MSM_RPM_STATUS_ID_MAP(8930, APPS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8930, APPS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8930, APPS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8930, APPS_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8930, SYS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8930, SYS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8930, SYS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8930, SYSTEM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8930, MMSS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8930, MMSS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8930, MMSS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8930, MM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S1_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S1_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S2_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S2_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S3_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S3_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S4_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_S4_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L1_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L1_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L2_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L2_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L3_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L3_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L4_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L4_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L5_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L5_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L6_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L6_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L7_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L7_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L8_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L8_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L9_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L9_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L10_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L10_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L11_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L11_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L12_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L12_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L13_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L13_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L14_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L14_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L15_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L15_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L16_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L16_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L17_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L17_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L18_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L18_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L19_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L19_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L20_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L20_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L21_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L21_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L22_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L22_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L23_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L23_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L24_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L24_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L25_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_L25_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_CLK1_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_CLK1_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_CLK2_0), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_CLK2_1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_LVS1), + MSM_RPM_STATUS_ID_MAP(8930, PM8038_LVS2), + MSM_RPM_STATUS_ID_MAP(8930, NCP_0), + MSM_RPM_STATUS_ID_MAP(8930, NCP_1), + MSM_RPM_STATUS_ID_MAP(8930, CXO_BUFFERS), + MSM_RPM_STATUS_ID_MAP(8930, USB_OTG_SWITCH), + MSM_RPM_STATUS_ID_MAP(8930, HDMI_SWITCH), + MSM_RPM_STATUS_ID_MAP(8930, DDR_DMM_0), + MSM_RPM_STATUS_ID_MAP(8930, DDR_DMM_1), + MSM_RPM_STATUS_ID_MAP(8930, QDSS_CLK), + MSM_RPM_STATUS_ID_MAP(8930, VOLTAGE_CORNER), + }, + .target_ctrl_id = { + MSM_RPM_CTRL_MAP(8930, VERSION_MAJOR), + MSM_RPM_CTRL_MAP(8930, VERSION_MINOR), + MSM_RPM_CTRL_MAP(8930, VERSION_BUILD), + MSM_RPM_CTRL_MAP(8930, REQ_CTX_0), + MSM_RPM_CTRL_MAP(8930, REQ_SEL_0), + MSM_RPM_CTRL_MAP(8930, ACK_CTX_0), + MSM_RPM_CTRL_MAP(8930, ACK_SEL_0), + }, + .sel_invalidate = MSM_RPM_8930_SEL_INVALIDATE, + .sel_notification = MSM_RPM_8930_SEL_NOTIFICATION, + .sel_last = MSM_RPM_8930_SEL_LAST, + .ver = {3, 0, 0}, +}; + +struct platform_device msm8930_rpm_device = { + .name = "msm_rpm", + .id = -1, +}; + +static struct msm_rpm_log_platform_data msm_rpm_log_pdata = { + .phys_addr_base = 0x0010C000, + .reg_offsets = { + [MSM_RPM_LOG_PAGE_INDICES] = 0x00000080, + [MSM_RPM_LOG_PAGE_BUFFER] = 0x000000A0, + }, + .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 */ +}; + +struct platform_device msm8930_rpm_log_device = { + .name = "msm_rpm_log", + .id = -1, + .dev = { + .platform_data = &msm_rpm_log_pdata, + }, +}; + +static struct msm_rpmstats_platform_data msm_rpm_stat_pdata = { + .phys_addr_base = 0x0010D204, + .phys_size = SZ_8K, +}; + +struct platform_device msm8930_rpm_stat_device = { + .name = "msm_rpm_stat", + .id = -1, + .dev = { + .platform_data = &msm_rpm_stat_pdata, + }, +}; + +static int msm8930_LPM_latency = 1000; /* >100 usec for WFI */ + +struct platform_device msm8930_cpu_idle_device = { + .name = "msm_cpu_idle", + .id = -1, + .dev = { + .platform_data = &msm8930_LPM_latency, + }, +}; + +static struct msm_dcvs_freq_entry msm8930_freq[] = { + { 384000, 166981, 345600}, + { 702000, 213049, 632502}, + {1026000, 285712, 925613}, + {1242000, 383945, 1176550}, + {1458000, 419729, 1465478}, + {1512000, 434116, 1546674}, + +}; + +static struct msm_dcvs_core_info msm8930_core_info = { + .freq_tbl = &msm8930_freq[0], + .core_param = { + .max_time_us = 100000, + .num_freq = ARRAY_SIZE(msm8930_freq), + }, + .algo_param = { + .slack_time_us = 58000, + .scale_slack_time = 0, + .scale_slack_time_pct = 0, + .disable_pc_threshold = 1458000, + .em_window_size = 100000, + .em_max_util_pct = 97, + .ss_window_size = 1000000, + .ss_util_pct = 95, + .ss_iobusy_conv = 100, + }, +}; + +struct platform_device msm8930_msm_gov_device = { + .name = "msm_dcvs_gov", + .id = -1, + .dev = { + .platform_data = &msm8930_core_info, + }, +}; + +struct platform_device msm_bus_8930_sys_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM, +}; +struct platform_device msm_bus_8930_apps_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_APPSS, +}; +struct platform_device msm_bus_8930_mm_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_MMSS, +}; +struct platform_device msm_bus_8930_sys_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM_FPB, +}; +struct platform_device msm_bus_8930_cpss_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_CPSS_FPB, +}; + +static struct fs_driver_data gfx3d_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk", .reset_rate = 27000000 }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_3D, +}; + +static struct fs_driver_data ijpeg_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_JPEG_ENC, +}; + +static struct fs_driver_data mdp_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { .name = "vsync_clk" }, + { .name = "lut_clk" }, + { .name = "tv_src_clk" }, + { .name = "tv_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_MDP_PORT0, + .bus_port1 = MSM_BUS_MASTER_MDP_PORT1, +}; + +static struct fs_driver_data rot_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_ROTATOR, +}; + +static struct fs_driver_data ved_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_HD_CODEC_PORT0, + .bus_port1 = MSM_BUS_MASTER_HD_CODEC_PORT1, +}; + +static struct fs_driver_data vfe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VFE, +}; + +static struct fs_driver_data vpe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VPE, +}; + +struct platform_device *msm8930_footswitch[] __initdata = { + FS_8X60(FS_MDP, "vdd", "mdp.0", &mdp_fs_data), + FS_8X60(FS_ROT, "vdd", "msm_rotator.0", &rot_fs_data), + FS_8X60(FS_IJPEG, "vdd", "msm_gemini.0", &ijpeg_fs_data), + FS_8X60(FS_VFE, "fs_vfe", NULL, &vfe_fs_data), + FS_8X60(FS_VPE, "fs_vpe", NULL, &vpe_fs_data), + FS_8X60(FS_GFX3D, "vdd", "kgsl-3d0.0", &gfx3d_fs_data), + FS_8X60(FS_VED, "vdd", "msm_vidc.0", &ved_fs_data), +}; +unsigned msm8930_num_footswitch __initdata = ARRAY_SIZE(msm8930_footswitch); + +/* 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 = 2560000000U, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 501219328, + .ib = 2560000000U, + }, + { + .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 = 2560000000U, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 330301440, + .ib = 2560000000U, + }, + { + .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 apq8930_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 apq8930_vidc_platform_data = { +#ifdef CONFIG_MSM_BUS_SCALING + .vidc_bus_client_pdata = &vidc_bus_client_data, +#endif +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .memtype = ION_CP_MM_HEAP_ID, + .enable_ion = 1, + .cp_enabled = 1, +#else + .memtype = MEMTYPE_EBI1, + .enable_ion = 0, +#endif + .disable_dmx = 1, + .disable_fullhd = 0, +}; + +struct platform_device apq8930_msm_device_vidc = { + .name = "msm_vidc", + .id = 0, + .num_resources = ARRAY_SIZE(apq8930_device_vidc_resources), + .resource = apq8930_device_vidc_resources, + .dev = { + .platform_data = &apq8930_vidc_platform_data, + }, +}; + +struct platform_device *vidc_device[] __initdata = { + &apq8930_msm_device_vidc +}; + +void __init msm8930_add_vidc_device(void) +{ + if (cpu_is_msm8627()) { + struct msm_vidc_platform_data *pdata; + pdata = (struct msm_vidc_platform_data *) + apq8930_msm_device_vidc.dev.platform_data; + pdata->disable_fullhd = 1; + } + platform_add_devices(vidc_device, ARRAY_SIZE(vidc_device)); +} + +struct msm_iommu_domain_name msm8930_iommu_ctx_names[] = { + /* Camera */ + { + .name = "vpe_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vpe_dst", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_imgwr", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_misc", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_dst", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_dst", + .domain = CAMERA_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_src", + .domain = ROTATOR_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_dst", + .domain = ROTATOR_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 mem_pool msm8930_video_pools[] = { + /* + * Video hardware has the following requirements: + * 1. All video addresses used by the video hardware must be at a higher + * address than video firmware address. + * 2. Video hardware can only access a range of 256MB from the base of + * the video firmware. + */ + [VIDEO_FIRMWARE_POOL] = + /* Low addresses, intended for video firmware */ + { + .paddr = SZ_128K, + .size = SZ_16M - SZ_128K, + }, + [VIDEO_MAIN_POOL] = + /* Main video pool */ + { + .paddr = SZ_16M, + .size = SZ_256M - SZ_16M, + }, + [GEN_POOL] = + /* Remaining address space up to 2G */ + { + .paddr = SZ_256M, + .size = SZ_2G - SZ_256M, + }, +}; + +static struct mem_pool msm8930_camera_pools[] = { + [GEN_POOL] = + /* One address space for camera */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct mem_pool msm8930_display_pools[] = { + [GEN_POOL] = + /* One address space for display */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct mem_pool msm8930_rotator_pools[] = { + [GEN_POOL] = + /* One address space for rotator */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct msm_iommu_domain msm8930_iommu_domains[] = { + [VIDEO_DOMAIN] = { + .iova_pools = msm8930_video_pools, + .npools = ARRAY_SIZE(msm8930_video_pools), + }, + [CAMERA_DOMAIN] = { + .iova_pools = msm8930_camera_pools, + .npools = ARRAY_SIZE(msm8930_camera_pools), + }, + [DISPLAY_DOMAIN] = { + .iova_pools = msm8930_display_pools, + .npools = ARRAY_SIZE(msm8930_display_pools), + }, + [ROTATOR_DOMAIN] = { + .iova_pools = msm8930_rotator_pools, + .npools = ARRAY_SIZE(msm8930_rotator_pools), + }, +}; + +struct iommu_domains_pdata msm8930_iommu_domain_pdata = { + .domains = msm8930_iommu_domains, + .ndomains = ARRAY_SIZE(msm8930_iommu_domains), + .domain_names = msm8930_iommu_ctx_names, + .nnames = ARRAY_SIZE(msm8930_iommu_ctx_names), + .domain_alloc_flags = 0, +}; + +struct platform_device msm8930_iommu_domain_device = { + .name = "iommu_domains", + .id = -1, + .dev = { + .platform_data = &msm8930_iommu_domain_pdata, + } +}; + +struct msm_rtb_platform_data msm8930_rtb_pdata = { + .size = SZ_1M, +}; + +static int __init msm_rtb_set_buffer_size(char *p) +{ + int s; + + s = memparse(p, NULL); + msm8930_rtb_pdata.size = ALIGN(s, SZ_4K); + return 0; +} +early_param("msm_rtb_size", msm_rtb_set_buffer_size); + + +struct platform_device msm8930_rtb_device = { + .name = "msm_rtb", + .id = -1, + .dev = { + .platform_data = &msm8930_rtb_pdata, + }, +}; diff --git a/arch/arm/mach-msm/devices-8960.c b/arch/arm/mach-msm/devices-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..be364e78b5d1f603c41e25cc90c880473901bdff --- /dev/null +++ b/arch/arm/mach-msm/devices-8960.c @@ -0,0 +1,3712 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "clock.h" +#include "devices.h" +#include "devices-msm8x60.h" +#include "footswitch.h" +#include "msm_watchdog.h" +#include "rpm_log.h" +#include "rpm_stats.h" +#include "pil-q6v4.h" +#include "scm-pas.h" +#include +#include + +#ifdef CONFIG_MSM_MPM +#include +#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) +#define MSM_UART6DM_PHYS (MSM_GSBI6_PHYS + 0x40000) +#define MSM_UART8DM_PHYS (MSM_GSBI8_PHYS + 0x40000) +#define MSM_UART9DM_PHYS (MSM_GSBI9_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 + +#define MSM8960_HSUSB_PHYS 0x12500000 +#define MSM8960_HSUSB_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 msm8960_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 msm8960_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 = DMA_BIT_MASK(32); +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_hsic_host[] = { + { + .start = 0x12520000, + .end = 0x12520000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB_HSIC_IRQ, + .end = USB_HSIC_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_GPIO_TO_INT(69), + .end = MSM_GPIO_TO_INT(69), + .name = "peripheral_status_irq", + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsic_host = { + .name = "msm_hsic_host", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsic_host), + .resource = resources_hsic_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define SHARED_IMEM_TZ_BASE 0x2a03f720 +static struct resource tzlog_resources[] = { + { + .start = SHARED_IMEM_TZ_BASE, + .end = SHARED_IMEM_TZ_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_tz_log = { + .name = "tz_log", + .id = 0, + .num_resources = ARRAY_SIZE(tzlog_resources), + .resource = tzlog_resources, +}; + +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, +}; +/* GSBI 6 used into UARTDM Mode */ +static struct resource msm_uart_dm6_resources[] = { + { + .start = MSM_UART6DM_PHYS, + .end = MSM_UART6DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = GSBI6_UARTDM_IRQ, + .end = GSBI6_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_GSBI6_PHYS, + .end = MSM_GSBI6_PHYS + 4 - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = DMOV_HSUART_GSBI6_TX_CHAN, + .end = DMOV_HSUART_GSBI6_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART_GSBI6_TX_CRCI, + .end = DMOV_HSUART_GSBI6_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; +static u64 msm_uart_dm6_dma_mask = DMA_BIT_MASK(32); +struct platform_device msm_device_uart_dm6 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart_dm6_resources), + .resource = msm_uart_dm6_resources, + .dev = { + .dma_mask = &msm_uart_dm6_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; +/* + * GSBI 9 used into UARTDM Mode + * For 8960 Fusion 2.2 Primary IPC + */ +static struct resource msm_uart_dm9_resources[] = { + { + .start = MSM_UART9DM_PHYS, + .end = MSM_UART9DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = GSBI9_UARTDM_IRQ, + .end = GSBI9_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_GSBI9_PHYS, + .end = MSM_GSBI9_PHYS + 4 - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = DMOV_HSUART_GSBI9_TX_CHAN, + .end = DMOV_HSUART_GSBI9_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART_GSBI9_TX_CRCI, + .end = DMOV_HSUART_GSBI9_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; +static u64 msm_uart_dm9_dma_mask = DMA_BIT_MASK(32); +struct platform_device msm_device_uart_dm9 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart_dm9_resources), + .resource = msm_uart_dm9_resources, + .dev = { + .dma_mask = &msm_uart_dm9_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +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, +}; + +static struct msm_serial_hslite_platform_data uart_gsbi8_pdata = { + .line = 0, +}; + +static struct resource resources_uart_gsbi8[] = { + { + .start = GSBI8_UARTDM_IRQ, + .end = GSBI8_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART8DM_PHYS, + .end = MSM_UART8DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI8_PHYS, + .end = MSM_GSBI8_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8960_device_uart_gsbi8 = { + .name = "msm_serial_hsl", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart_gsbi8), + .resource = resources_uart_gsbi8, + .dev.platform_data = &uart_gsbi8_pdata, +}; + +/* 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 = 2560000000U, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 501219328, + .ib = 2560000000U, + }, + { + .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 = 2560000000U, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 330301440, + .ib = 2560000000U, + }, + { + .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 + +#ifdef CONFIG_HW_RANDOM_MSM +/* PRNG device */ +#define MSM_PRNG_PHYS 0x1A500000 +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 + +#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 +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .memtype = ION_CP_MM_HEAP_ID, + .enable_ion = 1, + .cp_enabled = 1, +#else + .memtype = MEMTYPE_EBI1, + .enable_ion = 0, +#endif + .disable_dmx = 0, + .disable_fullhd = 0, + .cont_mode_dpb_count = 18, +}; + +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_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[] = { + { + .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, + }, +}; + +#define MSM_LPASS_QDSP6SS_PHYS 0x28800000 +#define SFAB_LPASS_Q6_ACLK_CTL (MSM_CLK_CTL_BASE + 0x23A0) + +static struct resource msm_8960_q6_lpass_resources[] = { + { + .start = MSM_LPASS_QDSP6SS_PHYS, + .end = MSM_LPASS_QDSP6SS_PHYS + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct pil_q6v4_pdata msm_8960_q6_lpass_data = { + .strap_tcm_base = 0x01460000, + .strap_ahb_upper = 0x00290000, + .strap_ahb_lower = 0x00000280, + .aclk_reg = SFAB_LPASS_Q6_ACLK_CTL, + .name = "q6", + .pas_id = PAS_Q6, + .bus_port = MSM_BUS_MASTER_LPASS_PROC, +}; + +struct platform_device msm_8960_q6_lpass = { + .name = "pil_qdsp6v4", + .id = 0, + .num_resources = ARRAY_SIZE(msm_8960_q6_lpass_resources), + .resource = msm_8960_q6_lpass_resources, + .dev.platform_data = &msm_8960_q6_lpass_data, +}; + +#define MSM_MSS_ENABLE_PHYS 0x08B00000 +#define MSM_FW_QDSP6SS_PHYS 0x08800000 +#define MSS_Q6FW_JTAG_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C6C) +#define SFAB_MSS_Q6_FW_ACLK_CTL (MSM_CLK_CTL_BASE + 0x2044) + +static struct resource msm_8960_q6_mss_fw_resources[] = { + { + .start = MSM_FW_QDSP6SS_PHYS, + .end = MSM_FW_QDSP6SS_PHYS + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_MSS_ENABLE_PHYS, + .end = MSM_MSS_ENABLE_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct pil_q6v4_pdata msm_8960_q6_mss_fw_data = { + .strap_tcm_base = 0x00400000, + .strap_ahb_upper = 0x00090000, + .strap_ahb_lower = 0x00000080, + .aclk_reg = SFAB_MSS_Q6_FW_ACLK_CTL, + .jtag_clk_reg = MSS_Q6FW_JTAG_CLK_CTL, + .name = "modem_fw", + .depends = "q6", + .pas_id = PAS_MODEM_FW, + .bus_port = MSM_BUS_MASTER_MSS_FW_PROC, +}; + +struct platform_device msm_8960_q6_mss_fw = { + .name = "pil_qdsp6v4", + .id = 1, + .num_resources = ARRAY_SIZE(msm_8960_q6_mss_fw_resources), + .resource = msm_8960_q6_mss_fw_resources, + .dev.platform_data = &msm_8960_q6_mss_fw_data, +}; + +#define MSM_SW_QDSP6SS_PHYS 0x08900000 +#define SFAB_MSS_Q6_SW_ACLK_CTL (MSM_CLK_CTL_BASE + 0x2040) +#define MSS_Q6SW_JTAG_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C68) + +static struct resource msm_8960_q6_mss_sw_resources[] = { + { + .start = MSM_SW_QDSP6SS_PHYS, + .end = MSM_SW_QDSP6SS_PHYS + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_MSS_ENABLE_PHYS, + .end = MSM_MSS_ENABLE_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct pil_q6v4_pdata msm_8960_q6_mss_sw_data = { + .strap_tcm_base = 0x00420000, + .strap_ahb_upper = 0x00090000, + .strap_ahb_lower = 0x00000080, + .aclk_reg = SFAB_MSS_Q6_SW_ACLK_CTL, + .jtag_clk_reg = MSS_Q6SW_JTAG_CLK_CTL, + .name = "modem", + .depends = "modem_fw", + .pas_id = PAS_MODEM_SW, + .bus_port = MSM_BUS_MASTER_MSS_SW_PROC, +}; + +struct platform_device msm_8960_q6_mss_sw = { + .name = "pil_qdsp6v4", + .id = 2, + .num_resources = ARRAY_SIZE(msm_8960_q6_mss_sw_resources), + .resource = msm_8960_q6_mss_sw_resources, + .dev.platform_data = &msm_8960_q6_mss_sw_data, +}; + +static struct resource msm_8960_riva_resources[] = { + { + .start = 0x03204000, + .end = 0x03204000 + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_8960_riva = { + .name = "pil_riva", + .id = -1, + .num_resources = ARRAY_SIZE(msm_8960_riva_resources), + .resource = msm_8960_riva_resources, +}; + +struct platform_device msm_pil_tzapps = { + .name = "pil_tzapps", + .id = -1, +}; + +struct platform_device msm_pil_dsps = { + .name = "pil_dsps", + .id = -1, + .dev.platform_data = "dsps", +}; + +struct platform_device msm_pil_vidc = { + .name = "pil_vidc", + .id = -1, +}; + +static struct resource smd_resource[] = { + { + .name = "a9_m2a_0", + .start = INT_A9_M2A_0, + .flags = IORESOURCE_IRQ, + }, + { + .name = "a9_m2a_5", + .start = INT_A9_M2A_5, + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_a11", + .start = INT_ADSP_A11, + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_a11_smsm", + .start = INT_ADSP_A11_SMSM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "dsps_a11", + .start = INT_DSPS_A11, + .flags = IORESOURCE_IRQ, + }, + { + .name = "dsps_a11_smsm", + .start = INT_DSPS_A11_SMSM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "wcnss_a11", + .start = INT_WCNSS_A11, + .flags = IORESOURCE_IRQ, + }, + { + .name = "wcnss_a11_smsm", + .start = INT_WCNSS_A11_SMSM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smd_subsystem_config smd_config_list[] = { + { + .irq_config_id = SMD_MODEM, + .subsys_name = "modem", + .edge = SMD_APPS_MODEM, + + .smd_int.irq_name = "a9_m2a_0", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 3, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "a9_m2a_5", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 4, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_Q6, + .subsys_name = "q6", + .edge = SMD_APPS_QDSP, + + .smd_int.irq_name = "adsp_a11", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 15, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "adsp_a11_smsm", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 14, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, + { + .irq_config_id = SMD_DSPS, + .subsys_name = "dsps", + .edge = SMD_APPS_DSPS, + + .smd_int.irq_name = "dsps_a11", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1, + .smd_int.out_base = (void __iomem *)MSM_SIC_NON_SECURE_BASE, + .smd_int.out_offset = 0x4080, + + .smsm_int.irq_name = "dsps_a11_smsm", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1, + .smsm_int.out_base = (void __iomem *)MSM_SIC_NON_SECURE_BASE, + .smsm_int.out_offset = 0x4094, + }, + { + .irq_config_id = SMD_WCNSS, + .subsys_name = "wcnss", + .edge = SMD_APPS_WCNSS, + + .smd_int.irq_name = "wcnss_a11", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + .smd_int.out_bit_pos = 1 << 25, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "wcnss_a11_smsm", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_smsm", + .smsm_int.dev_id = 0, + .smsm_int.out_bit_pos = 1 << 23, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + }, +}; + +static struct smd_subsystem_restart_config smd_ssr_config = { + .disable_smsm_reset_handshake = 1, +}; + +static struct smd_platform smd_platform_data = { + .num_ss_configs = ARRAY_SIZE(smd_config_list), + .smd_ss_configs = smd_config_list, + .smd_ssr_config = &smd_ssr_config, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, + .resource = smd_resource, + .num_resources = ARRAY_SIZE(smd_resource), + .dev = { + .platform_data = &smd_platform_data, + }, +}; + +struct platform_device msm_device_bam_dmux = { + .name = "BAM_RMNT", + .id = -1, +}; + +static struct msm_watchdog_pdata msm_watchdog_pdata = { + .pet_time = 10000, + .bark_time = 11000, + .has_secure = true, +}; + +struct platform_device msm8960_device_watchdog = { + .name = "msm_watchdog", + .id = -1, + .dev = { + .platform_data = &msm_watchdog_pdata, + }, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = ADM_0_SCSS_1_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0x18320000, + .end = 0x18320000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 1, + .sd_size = 0x800, +}; + +struct platform_device msm8960_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +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 + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI4_QUP_PHYS, + .end = MSM_GSBI4_QUP_PHYS + MSM_QUP_SIZE - 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 + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI3_QUP_PHYS, + .end = MSM_GSBI3_QUP_PHYS + MSM_QUP_SIZE - 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_gsbi9[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI9_PHYS, + .end = MSM_GSBI9_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI9_QUP_PHYS, + .end = MSM_GSBI9_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI9_QUP_IRQ, + .end = GSBI9_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_qup_i2c_gsbi9 = { + .name = "qup_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi9), + .resource = resources_qup_i2c_gsbi9, +}; + +static struct resource resources_qup_i2c_gsbi10[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI10_PHYS, + .end = MSM_GSBI10_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI10_QUP_PHYS, + .end = MSM_GSBI10_QUP_PHYS + MSM_QUP_SIZE - 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 + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI12_QUP_PHYS, + .end = MSM_GSBI12_QUP_PHYS + MSM_QUP_SIZE - 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 +static struct resource msm_cam_gsbi4_i2c_mux_resources[] = { + { + .name = "i2c_mux_rw", + .start = 0x008003E0, + .end = 0x008003E0 + SZ_8 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "i2c_mux_ctl", + .start = 0x008020B8, + .end = 0x008020B8 + SZ_4 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8960_device_i2c_mux_gsbi4 = { + .name = "msm_cam_i2c_mux", + .id = 0, + .resource = msm_cam_gsbi4_i2c_mux_resources, + .num_resources = ARRAY_SIZE(msm_cam_gsbi4_i2c_mux_resources), +}; + +static struct resource msm_csiphy0_resources[] = { + { + .name = "csiphy", + .start = 0x04800C00, + .end = 0x04800C00 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csiphy", + .start = CSIPHY_4LN_IRQ, + .end = CSIPHY_4LN_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_csiphy1_resources[] = { + { + .name = "csiphy", + .start = 0x04801000, + .end = 0x04801000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csiphy", + .start = MSM8960_CSIPHY_2LN_IRQ, + .end = MSM8960_CSIPHY_2LN_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_csiphy2_resources[] = { + { + .name = "csiphy", + .start = 0x04801400, + .end = 0x04801400 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csiphy", + .start = MSM8960_CSIPHY_2_2LN_IRQ, + .end = MSM8960_CSIPHY_2_2LN_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_csiphy0 = { + .name = "msm_csiphy", + .id = 0, + .resource = msm_csiphy0_resources, + .num_resources = ARRAY_SIZE(msm_csiphy0_resources), +}; + +struct platform_device msm8960_device_csiphy1 = { + .name = "msm_csiphy", + .id = 1, + .resource = msm_csiphy1_resources, + .num_resources = ARRAY_SIZE(msm_csiphy1_resources), +}; + +struct platform_device msm8960_device_csiphy2 = { + .name = "msm_csiphy", + .id = 2, + .resource = msm_csiphy2_resources, + .num_resources = ARRAY_SIZE(msm_csiphy2_resources), +}; + +static struct resource msm_csid0_resources[] = { + { + .name = "csid", + .start = 0x04800000, + .end = 0x04800000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csid", + .start = CSI_0_IRQ, + .end = CSI_0_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_csid1_resources[] = { + { + .name = "csid", + .start = 0x04800400, + .end = 0x04800400 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csid", + .start = CSI_1_IRQ, + .end = CSI_1_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_csid2_resources[] = { + { + .name = "csid", + .start = 0x04801800, + .end = 0x04801800 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csid", + .start = CSI_2_IRQ, + .end = CSI_2_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_csid0 = { + .name = "msm_csid", + .id = 0, + .resource = msm_csid0_resources, + .num_resources = ARRAY_SIZE(msm_csid0_resources), +}; + +struct platform_device msm8960_device_csid1 = { + .name = "msm_csid", + .id = 1, + .resource = msm_csid1_resources, + .num_resources = ARRAY_SIZE(msm_csid1_resources), +}; + +struct platform_device msm8960_device_csid2 = { + .name = "msm_csid", + .id = 2, + .resource = msm_csid2_resources, + .num_resources = ARRAY_SIZE(msm_csid2_resources), +}; + +struct resource msm_ispif_resources[] = { + { + .name = "ispif", + .start = 0x04800800, + .end = 0x04800800 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "ispif", + .start = ISPIF_IRQ, + .end = ISPIF_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_ispif = { + .name = "msm_ispif", + .id = 0, + .resource = msm_ispif_resources, + .num_resources = ARRAY_SIZE(msm_ispif_resources), +}; + +static struct resource msm_vfe_resources[] = { + { + .name = "vfe32", + .start = 0x04500000, + .end = 0x04500000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "vfe32", + .start = VFE_IRQ, + .end = VFE_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_vfe = { + .name = "msm_vfe", + .id = 0, + .resource = msm_vfe_resources, + .num_resources = ARRAY_SIZE(msm_vfe_resources), +}; + +static struct resource msm_vpe_resources[] = { + { + .name = "vpe", + .start = 0x05300000, + .end = 0x05300000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "vpe", + .start = VPE_IRQ, + .end = VPE_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_vpe = { + .name = "msm_vpe", + .id = 0, + .resource = msm_vpe_resources, + .num_resources = ARRAY_SIZE(msm_vpe_resources), +}; +#endif + +#define MSM_TSIF0_PHYS (0x18200000) +#define MSM_TSIF1_PHYS (0x18201000) +#define MSM_TSIF_SIZE (0x200) + +#define TSIF_0_CLK GPIO_CFG(75, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_0_EN GPIO_CFG(76, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_0_DATA GPIO_CFG(77, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_0_SYNC GPIO_CFG(82, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_CLK GPIO_CFG(79, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_EN GPIO_CFG(80, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_DATA GPIO_CFG(81, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_SYNC GPIO_CFG(78, 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", }, +}; + +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", +}; + +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, + }, +}; + +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", +}; +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 = { + .platform_data = &tsif0_platform_data + }, + }, + { + .name = "msm_tsif", + .id = 1, + .num_resources = ARRAY_SIZE(tsif1_resources), + .resource = tsif1_resources, + .dev = { + .platform_data = &tsif1_platform_data + }, + } +}; + +static struct resource resources_ssbi_pmic[] = { + { + .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_pmic = { + .name = "msm_ssbi", + .id = 0, + .resource = resources_ssbi_pmic, + .num_resources = ARRAY_SIZE(resources_ssbi_pmic), +}; + +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, + }, + { + .name = "spi_clk", + .start = 9, + .end = 9, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_miso", + .start = 7, + .end = 7, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_mosi", + .start = 6, + .end = 6, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_cs", + .start = 8, + .end = 8, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_cs1", + .start = 14, + .end = 14, + .flags = IORESOURCE_IO, + }, +}; + +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_multi_ch_pcm = { + .name = "msm-multi-ch-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 msm8960_cpudai_slimbus_2_tx = { + .name = "msm-dai-q6", + .id = 0x4005, +}; + +struct platform_device msm_cpudai_hdmi_rx = { + .name = "msm-dai-q6-hdmi", + .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_cpudai_incall_music_rx = { + .name = "msm-dai-q6", + .id = 0x8005, +}; + +struct platform_device msm_cpudai_incall_record_rx = { + .name = "msm-dai-q6", + .id = 0x8004, +}; + +struct platform_device msm_cpudai_incall_record_tx = { + .name = "msm-dai-q6", + .id = 0x8003, +}; + +/* + * Machine specific data for AUX PCM Interface + * which the driver will be unware of. + */ +struct msm_dai_auxpcm_pdata auxpcm_pdata = { + .clk = "pcm_clk", + .mode_8k = { + .mode = AFE_PCM_CFG_MODE_PCM, + .sync = AFE_PCM_CFG_SYNC_INT, + .frame = AFE_PCM_CFG_FRM_256BPF, + .quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD, + .slot = 0, + .data = AFE_PCM_CFG_CDATAOE_MASTER, + .pcm_clk_rate = 2048000, + }, + .mode_16k = { + .mode = AFE_PCM_CFG_MODE_PCM, + .sync = AFE_PCM_CFG_SYNC_INT, + .frame = AFE_PCM_CFG_FRM_256BPF, + .quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD, + .slot = 0, + .data = AFE_PCM_CFG_CDATAOE_MASTER, + .pcm_clk_rate = 4096000, + } +}; + +struct platform_device msm_cpudai_auxpcm_rx = { + .name = "msm-dai-q6", + .id = 2, + .dev = { + .platform_data = &auxpcm_pdata, + }, +}; + +struct platform_device msm_cpudai_auxpcm_tx = { + .name = "msm-dai-q6", + .id = 3, + .dev = { + .platform_data = &auxpcm_pdata, + }, +}; + +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_compr_dsp = { + .name = "msm-compr-dsp", + .id = -1, +}; + +struct platform_device msm_pcm_hostless = { + .name = "msm-pcm-hostless", + .id = -1, +}; + +struct platform_device msm_cpudai_afe_01_rx = { + .name = "msm-dai-q6", + .id = 0xE0, +}; + +struct platform_device msm_cpudai_afe_01_tx = { + .name = "msm-dai-q6", + .id = 0xF0, +}; + +struct platform_device msm_cpudai_afe_02_rx = { + .name = "msm-dai-q6", + .id = 0xF1, +}; + +struct platform_device msm_cpudai_afe_02_tx = { + .name = "msm-dai-q6", + .id = 0xE1, +}; + +struct platform_device msm_pcm_afe = { + .name = "msm-pcm-afe", + .id = -1, +}; + +static struct fs_driver_data gfx2d0_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, +}; + +static struct fs_driver_data gfx2d1_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, +}; + +static struct fs_driver_data gfx3d_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk", .reset_rate = 27000000 }, + { .name = "iface_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_3D, +}; + +static struct fs_driver_data ijpeg_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_JPEG_ENC, +}; + +static struct fs_driver_data mdp_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { .name = "vsync_clk" }, + { .name = "lut_clk" }, + { .name = "tv_src_clk" }, + { .name = "tv_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_MDP_PORT0, + .bus_port1 = MSM_BUS_MASTER_MDP_PORT1, +}; + +static struct fs_driver_data rot_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_ROTATOR, +}; + +static struct fs_driver_data ved_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_HD_CODEC_PORT0, + .bus_port1 = MSM_BUS_MASTER_HD_CODEC_PORT1, +}; + +static struct fs_driver_data vfe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VFE, +}; + +static struct fs_driver_data vpe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VPE, +}; + +struct platform_device *msm8960_footswitch[] __initdata = { + FS_8X60(FS_MDP, "vdd", "mdp.0", &mdp_fs_data), + FS_8X60(FS_ROT, "vdd", "msm_rotator.0", &rot_fs_data), + FS_8X60(FS_IJPEG, "vdd", "msm_gemini.0", &ijpeg_fs_data), + FS_8X60(FS_VFE, "fs_vfe", NULL, &vfe_fs_data), + FS_8X60(FS_VPE, "fs_vpe", NULL, &vpe_fs_data), + FS_8X60(FS_GFX3D, "vdd", "kgsl-3d0.0", &gfx3d_fs_data), + FS_8X60(FS_GFX2D0, "vdd", "kgsl-2d0.0", &gfx2d0_fs_data), + FS_8X60(FS_GFX2D1, "vdd", "kgsl-2d1.1", &gfx2d1_fs_data), + FS_8X60(FS_VED, "vdd", "msm_vidc.0", &ved_fs_data), +}; +unsigned msm8960_num_footswitch __initdata = ARRAY_SIZE(msm8960_footswitch); + +#ifdef CONFIG_MSM_ROTATOR +static struct msm_bus_vectors rotator_init_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors rotator_ui_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (1024 * 600 * 4 * 2 * 60), + .ib = (1024 * 600 * 4 * 2 * 60 * 1.5), + }, +}; + +static struct msm_bus_vectors rotator_vga_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (640 * 480 * 2 * 2 * 30), + .ib = (640 * 480 * 2 * 2 * 30 * 1.5), + }, +}; +static struct msm_bus_vectors rotator_720p_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (1280 * 736 * 2 * 2 * 30), + .ib = (1280 * 736 * 2 * 2 * 30 * 1.5), + }, +}; + +static struct msm_bus_vectors rotator_1080p_vectors[] = { + { + .src = MSM_BUS_MASTER_ROTATOR, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = (1920 * 1088 * 2 * 2 * 30), + .ib = (1920 * 1088 * 2 * 2 * 30 * 1.5), + }, +}; + +static struct msm_bus_paths rotator_bus_scale_usecases[] = { + { + ARRAY_SIZE(rotator_init_vectors), + rotator_init_vectors, + }, + { + ARRAY_SIZE(rotator_ui_vectors), + rotator_ui_vectors, + }, + { + ARRAY_SIZE(rotator_vga_vectors), + rotator_vga_vectors, + }, + { + ARRAY_SIZE(rotator_720p_vectors), + rotator_720p_vectors, + }, + { + ARRAY_SIZE(rotator_1080p_vectors), + rotator_1080p_vectors, + }, +}; + +struct msm_bus_scale_pdata rotator_bus_scale_pdata = { + rotator_bus_scale_usecases, + ARRAY_SIZE(rotator_bus_scale_usecases), + .name = "rotator", +}; + +void __init msm_rotator_update_bus_vectors(unsigned int xres, + unsigned int yres) +{ + rotator_ui_vectors[0].ab = xres * yres * 4 * 2 * 60; + rotator_ui_vectors[0].ib = xres * yres * 4 * 2 * 60 * 3 / 2; +} + +#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 = "core_clk", + .clk_type = ROTATOR_CORE_CLK, + .clk_rate = 200 * 1000 * 1000, + }, + { + .clk_name = "iface_clk", + .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 = 0x01020309, + .rotator_clks = rotator_clocks, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &rotator_bus_scale_pdata, +#endif +}; + +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); +} + +#ifdef CONFIG_MSM_BUS_SCALING +static struct platform_device msm_dtv_device = { + .name = "dtv", + .id = 0, +}; +#endif + +struct platform_device msm_lvds_device = { + .name = "lvds", + .id = 0, +}; + +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 if (!strncmp(name, "lvds", 4)) + msm_register_device(&msm_lvds_device, data); +#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_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] __initdata = { + [1] = MSM_GPIO_TO_INT(46), + [2] = MSM_GPIO_TO_INT(150), + [4] = MSM_GPIO_TO_INT(103), + [5] = MSM_GPIO_TO_INT(104), + [6] = MSM_GPIO_TO_INT(105), + [7] = MSM_GPIO_TO_INT(106), + [8] = MSM_GPIO_TO_INT(107), + [9] = MSM_GPIO_TO_INT(7), + [10] = MSM_GPIO_TO_INT(11), + [11] = MSM_GPIO_TO_INT(15), + [12] = MSM_GPIO_TO_INT(19), + [13] = MSM_GPIO_TO_INT(23), + [14] = MSM_GPIO_TO_INT(27), + [15] = MSM_GPIO_TO_INT(31), + [16] = MSM_GPIO_TO_INT(35), + [19] = MSM_GPIO_TO_INT(90), + [20] = MSM_GPIO_TO_INT(92), + [23] = MSM_GPIO_TO_INT(85), + [24] = MSM_GPIO_TO_INT(83), + [25] = USB1_HS_IRQ, + [27] = HDMI_IRQ, + [29] = MSM_GPIO_TO_INT(10), + [30] = MSM_GPIO_TO_INT(102), + [31] = MSM_GPIO_TO_INT(81), + [32] = MSM_GPIO_TO_INT(78), + [33] = MSM_GPIO_TO_INT(94), + [34] = MSM_GPIO_TO_INT(72), + [35] = MSM_GPIO_TO_INT(39), + [36] = MSM_GPIO_TO_INT(43), + [37] = MSM_GPIO_TO_INT(61), + [38] = MSM_GPIO_TO_INT(50), + [39] = MSM_GPIO_TO_INT(42), + [41] = MSM_GPIO_TO_INT(62), + [42] = MSM_GPIO_TO_INT(76), + [43] = MSM_GPIO_TO_INT(75), + [44] = MSM_GPIO_TO_INT(70), + [45] = MSM_GPIO_TO_INT(69), + [46] = MSM_GPIO_TO_INT(67), + [47] = MSM_GPIO_TO_INT(65), + [48] = MSM_GPIO_TO_INT(58), + [49] = MSM_GPIO_TO_INT(54), + [50] = MSM_GPIO_TO_INT(52), + [51] = MSM_GPIO_TO_INT(49), + [52] = MSM_GPIO_TO_INT(40), + [53] = MSM_GPIO_TO_INT(37), + [54] = MSM_GPIO_TO_INT(24), + [55] = MSM_GPIO_TO_INT(14), +}; + +static uint16_t msm_mpm_bypassed_apps_irqs[] __initdata = { + 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_30, + SPS_MTI_31, + RIVA_APSS_SPARE_IRQ, + RIVA_APPS_WLAN_SMSM_IRQ, + RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ, + RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ, +}; + +struct msm_mpm_device_data msm8960_mpm_dev_data __initdata = { + .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 + +#define LPASS_SLIMBUS_PHYS 0x28080000 +#define LPASS_SLIMBUS_BAM_PHYS 0x28084000 +#define LPASS_SLIMBUS_SLEW (MSM8960_TLMM_PHYS + 0x207C) +/* 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 = LPASS_SLIMBUS_SLEW, + .end = LPASS_SLIMBUS_SLEW + 4 - 1, + .flags = IORESOURCE_MEM, + .name = "slimbus_slew_reg", + }, + { + .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, + }, +}; + +static struct msm_dcvs_freq_entry grp3d_freq[] = { + {0, 0, 333932}, + {0, 0, 497532}, + {0, 0, 707610}, + {0, 0, 844545}, +}; + +static struct msm_dcvs_freq_entry grp2d_freq[] = { + {0, 0, 86000}, + {0, 0, 200000}, +}; + +static struct msm_dcvs_core_info grp3d_core_info = { + .freq_tbl = &grp3d_freq[0], + .core_param = { + .max_time_us = 100000, + .num_freq = ARRAY_SIZE(grp3d_freq), + }, + .algo_param = { + .slack_time_us = 39000, + .disable_pc_threshold = 86000, + .ss_window_size = 1000000, + .ss_util_pct = 95, + .em_max_util_pct = 97, + .ss_iobusy_conv = 100, + }, +}; + +static struct msm_dcvs_core_info grp2d_core_info = { + .freq_tbl = &grp2d_freq[0], + .core_param = { + .max_time_us = 100000, + .num_freq = ARRAY_SIZE(grp2d_freq), + }, + .algo_param = { + .slack_time_us = 39000, + .disable_pc_threshold = 90000, + .ss_window_size = 1000000, + .ss_util_pct = 90, + .em_max_util_pct = 95, + }, +}; + +#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_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(1000), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2048), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_high_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2656), + }, +}; + +static struct msm_bus_vectors grp3d_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(3968), + }, +}; + +static struct msm_bus_paths grp3d_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp3d_init_vectors), + grp3d_init_vectors, + }, + { + ARRAY_SIZE(grp3d_low_vectors), + grp3d_low_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_nominal_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(1000), + }, +}; + +static struct msm_bus_vectors grp2d0_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2048), + }, +}; + +static struct msm_bus_paths grp2d0_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp2d0_init_vectors), + grp2d0_init_vectors, + }, + { + ARRAY_SIZE(grp2d0_nominal_vectors), + grp2d0_nominal_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_nominal_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(1000), + }, +}; + +static struct msm_bus_vectors grp2d1_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2048), + }, +}; + +static struct msm_bus_paths grp2d1_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp2d1_init_vectors), + grp2d1_init_vectors, + }, + { + ARRAY_SIZE(grp2d1_nominal_vectors), + grp2d1_nominal_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 const struct kgsl_iommu_ctx kgsl_3d0_iommu_ctxs[] = { + { "gfx3d_user", 0 }, + { "gfx3d_priv", 1 }, +}; + +static struct kgsl_device_iommu_data kgsl_3d0_iommu_data[] = { + { + .iommu_ctxs = kgsl_3d0_iommu_ctxs, + .iommu_ctx_count = ARRAY_SIZE(kgsl_3d0_iommu_ctxs), + .physstart = 0x07C00000, + .physend = 0x07C00000 + SZ_1M - 1, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwrlevel = { + { + .gpu_freq = 400000000, + .bus_freq = 4, + .io_fraction = 0, + }, + { + .gpu_freq = 300000000, + .bus_freq = 3, + .io_fraction = 33, + }, + { + .gpu_freq = 200000000, + .bus_freq = 2, + .io_fraction = 100, + }, + { + .gpu_freq = 128000000, + .bus_freq = 1, + .io_fraction = 100, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 1, + .num_levels = ARRAY_SIZE(grp3d_freq) + 1, + .set_grp_async = NULL, + .idle_timeout = HZ/12, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE | KGSL_CLK_MEM_IFACE, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp3d_bus_scale_pdata, +#endif + .iommu_data = kgsl_3d0_iommu_data, + .iommu_count = ARRAY_SIZE(kgsl_3d0_iommu_data), + .core_info = &grp3d_core_info, +}; + +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 const struct kgsl_iommu_ctx kgsl_2d0_iommu_ctxs[] = { + { "gfx2d0_2d0", 0 }, +}; + +static struct kgsl_device_iommu_data kgsl_2d0_iommu_data[] = { + { + .iommu_ctxs = kgsl_2d0_iommu_ctxs, + .iommu_ctx_count = ARRAY_SIZE(kgsl_2d0_iommu_ctxs), + .physstart = 0x07D00000, + .physend = 0x07D00000 + SZ_1M - 1, + }, +}; + +static struct kgsl_device_platform_data kgsl_2d0_pdata = { + .pwrlevel = { + { + .gpu_freq = 200000000, + .bus_freq = 2, + }, + { + .gpu_freq = 96000000, + .bus_freq = 1, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = ARRAY_SIZE(grp2d_freq) + 1, + .set_grp_async = NULL, + .idle_timeout = HZ/5, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp2d0_bus_scale_pdata, +#endif + .iommu_data = kgsl_2d0_iommu_data, + .iommu_count = ARRAY_SIZE(kgsl_2d0_iommu_data), + .core_info = &grp2d_core_info, +}; + +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 const struct kgsl_iommu_ctx kgsl_2d1_iommu_ctxs[] = { + { "gfx2d1_2d1", 0 }, +}; + +static struct kgsl_device_iommu_data kgsl_2d1_iommu_data[] = { + { + .iommu_ctxs = kgsl_2d1_iommu_ctxs, + .iommu_ctx_count = ARRAY_SIZE(kgsl_2d1_iommu_ctxs), + .physstart = 0x07E00000, + .physend = 0x07E00000 + SZ_1M - 1, + }, +}; + +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 = { + .pwrlevel = { + { + .gpu_freq = 200000000, + .bus_freq = 2, + }, + { + .gpu_freq = 96000000, + .bus_freq = 1, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = ARRAY_SIZE(grp2d_freq) + 1, + .set_grp_async = NULL, + .idle_timeout = HZ/5, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp2d1_bus_scale_pdata, +#endif + .iommu_data = kgsl_2d1_iommu_data, + .iommu_count = ARRAY_SIZE(kgsl_2d1_iommu_data), + .core_info = &grp2d_core_info, +}; + +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_platform_data msm8960_rpm_data __initdata = { + .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_wakeup = RPM_APCC_CPU0_WAKE_UP_IRQ, + .ipc_rpm_reg = MSM_APCS_GCC_BASE + 0x008, + .ipc_rpm_val = 4, + .target_id = { + MSM_RPM_MAP(8960, NOTIFICATION_CONFIGURED_0, NOTIFICATION, 4), + MSM_RPM_MAP(8960, NOTIFICATION_REGISTERED_0, NOTIFICATION, 4), + MSM_RPM_MAP(8960, INVALIDATE_0, INVALIDATE, 8), + MSM_RPM_MAP(8960, TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8960, TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8960, RPM_CTL, RPM_CTL, 1), + MSM_RPM_MAP(8960, CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(8960, PXO_CLK, PXO_CLK, 1), + MSM_RPM_MAP(8960, APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1), + MSM_RPM_MAP(8960, SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(8960, MM_FABRIC_CLK, MM_FABRIC_CLK, 1), + MSM_RPM_MAP(8960, DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(8960, SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(8960, CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(8960, MMFPB_CLK, MMFPB_CLK, 1), + MSM_RPM_MAP(8960, EBI1_CLK, EBI1_CLK, 1), + MSM_RPM_MAP(8960, APPS_FABRIC_CFG_HALT_0, + APPS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8960, APPS_FABRIC_CFG_CLKMOD_0, + APPS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8960, APPS_FABRIC_CFG_IOCTL, + APPS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8960, APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 12), + MSM_RPM_MAP(8960, SYS_FABRIC_CFG_HALT_0, + SYS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8960, SYS_FABRIC_CFG_CLKMOD_0, + SYS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8960, SYS_FABRIC_CFG_IOCTL, + SYS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8960, SYSTEM_FABRIC_ARB_0, + SYSTEM_FABRIC_ARB, 29), + MSM_RPM_MAP(8960, MMSS_FABRIC_CFG_HALT_0, + MMSS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(8960, MMSS_FABRIC_CFG_CLKMOD_0, + MMSS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(8960, MMSS_FABRIC_CFG_IOCTL, + MMSS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(8960, MM_FABRIC_ARB_0, MM_FABRIC_ARB, 23), + MSM_RPM_MAP(8960, PM8921_S1_0, PM8921_S1, 2), + MSM_RPM_MAP(8960, PM8921_S2_0, PM8921_S2, 2), + MSM_RPM_MAP(8960, PM8921_S3_0, PM8921_S3, 2), + MSM_RPM_MAP(8960, PM8921_S4_0, PM8921_S4, 2), + MSM_RPM_MAP(8960, PM8921_S5_0, PM8921_S5, 2), + MSM_RPM_MAP(8960, PM8921_S6_0, PM8921_S6, 2), + MSM_RPM_MAP(8960, PM8921_S7_0, PM8921_S7, 2), + MSM_RPM_MAP(8960, PM8921_S8_0, PM8921_S8, 2), + MSM_RPM_MAP(8960, PM8921_L1_0, PM8921_L1, 2), + MSM_RPM_MAP(8960, PM8921_L2_0, PM8921_L2, 2), + MSM_RPM_MAP(8960, PM8921_L3_0, PM8921_L3, 2), + MSM_RPM_MAP(8960, PM8921_L4_0, PM8921_L4, 2), + MSM_RPM_MAP(8960, PM8921_L5_0, PM8921_L5, 2), + MSM_RPM_MAP(8960, PM8921_L6_0, PM8921_L6, 2), + MSM_RPM_MAP(8960, PM8921_L7_0, PM8921_L7, 2), + MSM_RPM_MAP(8960, PM8921_L8_0, PM8921_L8, 2), + MSM_RPM_MAP(8960, PM8921_L9_0, PM8921_L9, 2), + MSM_RPM_MAP(8960, PM8921_L10_0, PM8921_L10, 2), + MSM_RPM_MAP(8960, PM8921_L11_0, PM8921_L11, 2), + MSM_RPM_MAP(8960, PM8921_L12_0, PM8921_L12, 2), + MSM_RPM_MAP(8960, PM8921_L13_0, PM8921_L13, 2), + MSM_RPM_MAP(8960, PM8921_L14_0, PM8921_L14, 2), + MSM_RPM_MAP(8960, PM8921_L15_0, PM8921_L15, 2), + MSM_RPM_MAP(8960, PM8921_L16_0, PM8921_L16, 2), + MSM_RPM_MAP(8960, PM8921_L17_0, PM8921_L17, 2), + MSM_RPM_MAP(8960, PM8921_L18_0, PM8921_L18, 2), + MSM_RPM_MAP(8960, PM8921_L19_0, PM8921_L19, 2), + MSM_RPM_MAP(8960, PM8921_L20_0, PM8921_L20, 2), + MSM_RPM_MAP(8960, PM8921_L21_0, PM8921_L21, 2), + MSM_RPM_MAP(8960, PM8921_L22_0, PM8921_L22, 2), + MSM_RPM_MAP(8960, PM8921_L23_0, PM8921_L23, 2), + MSM_RPM_MAP(8960, PM8921_L24_0, PM8921_L24, 2), + MSM_RPM_MAP(8960, PM8921_L25_0, PM8921_L25, 2), + MSM_RPM_MAP(8960, PM8921_L26_0, PM8921_L26, 2), + MSM_RPM_MAP(8960, PM8921_L27_0, PM8921_L27, 2), + MSM_RPM_MAP(8960, PM8921_L28_0, PM8921_L28, 2), + MSM_RPM_MAP(8960, PM8921_L29_0, PM8921_L29, 2), + MSM_RPM_MAP(8960, PM8921_CLK1_0, PM8921_CLK1, 2), + MSM_RPM_MAP(8960, PM8921_CLK2_0, PM8921_CLK2, 2), + MSM_RPM_MAP(8960, PM8921_LVS1, PM8921_LVS1, 1), + MSM_RPM_MAP(8960, PM8921_LVS2, PM8921_LVS2, 1), + MSM_RPM_MAP(8960, PM8921_LVS3, PM8921_LVS3, 1), + MSM_RPM_MAP(8960, PM8921_LVS4, PM8921_LVS4, 1), + MSM_RPM_MAP(8960, PM8921_LVS5, PM8921_LVS5, 1), + MSM_RPM_MAP(8960, PM8921_LVS6, PM8921_LVS6, 1), + MSM_RPM_MAP(8960, PM8921_LVS7, PM8921_LVS7, 1), + MSM_RPM_MAP(8960, NCP_0, NCP, 2), + MSM_RPM_MAP(8960, CXO_BUFFERS, CXO_BUFFERS, 1), + MSM_RPM_MAP(8960, USB_OTG_SWITCH, USB_OTG_SWITCH, 1), + MSM_RPM_MAP(8960, HDMI_SWITCH, HDMI_SWITCH, 1), + MSM_RPM_MAP(8960, DDR_DMM_0, DDR_DMM, 2), + MSM_RPM_MAP(8960, QDSS_CLK, QDSS_CLK, 1), + }, + .target_status = { + MSM_RPM_STATUS_ID_MAP(8960, VERSION_MAJOR), + MSM_RPM_STATUS_ID_MAP(8960, VERSION_MINOR), + MSM_RPM_STATUS_ID_MAP(8960, VERSION_BUILD), + MSM_RPM_STATUS_ID_MAP(8960, SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8960, SUPPORTED_RESOURCES_1), + MSM_RPM_STATUS_ID_MAP(8960, SUPPORTED_RESOURCES_2), + MSM_RPM_STATUS_ID_MAP(8960, RESERVED_SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8960, SEQUENCE), + MSM_RPM_STATUS_ID_MAP(8960, RPM_CTL), + MSM_RPM_STATUS_ID_MAP(8960, CXO_CLK), + MSM_RPM_STATUS_ID_MAP(8960, PXO_CLK), + MSM_RPM_STATUS_ID_MAP(8960, APPS_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8960, SYSTEM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8960, MM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8960, DAYTONA_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8960, SFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8960, CFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8960, MMFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8960, EBI1_CLK), + MSM_RPM_STATUS_ID_MAP(8960, APPS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8960, APPS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8960, APPS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8960, APPS_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8960, SYS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8960, SYS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8960, SYS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8960, SYSTEM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8960, MMSS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(8960, MMSS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(8960, MMSS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(8960, MM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S1_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S1_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S2_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S2_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S3_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S3_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S4_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S4_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S5_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S5_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S6_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S6_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S7_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S7_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S8_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_S8_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L1_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L1_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L2_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L2_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L3_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L3_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L4_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L4_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L5_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L5_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L6_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L6_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L7_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L7_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L8_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L8_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L9_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L9_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L10_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L10_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L11_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L11_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L12_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L12_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L13_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L13_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L14_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L14_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L15_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L15_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L16_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L16_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L17_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L17_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L18_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L18_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L19_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L19_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L20_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L20_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L21_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L21_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L22_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L22_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L23_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L23_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L24_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L24_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L25_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L25_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L26_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L26_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L27_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L27_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L28_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L28_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L29_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_L29_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_CLK1_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_CLK1_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_CLK2_0), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_CLK2_1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS1), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS2), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS3), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS4), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS5), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS6), + MSM_RPM_STATUS_ID_MAP(8960, PM8921_LVS7), + MSM_RPM_STATUS_ID_MAP(8960, NCP_0), + MSM_RPM_STATUS_ID_MAP(8960, NCP_1), + MSM_RPM_STATUS_ID_MAP(8960, CXO_BUFFERS), + MSM_RPM_STATUS_ID_MAP(8960, USB_OTG_SWITCH), + MSM_RPM_STATUS_ID_MAP(8960, HDMI_SWITCH), + MSM_RPM_STATUS_ID_MAP(8960, DDR_DMM_0), + MSM_RPM_STATUS_ID_MAP(8960, DDR_DMM_1), + MSM_RPM_STATUS_ID_MAP(8960, EBI1_CH0_RANGE), + MSM_RPM_STATUS_ID_MAP(8960, EBI1_CH1_RANGE), + }, + .target_ctrl_id = { + MSM_RPM_CTRL_MAP(8960, VERSION_MAJOR), + MSM_RPM_CTRL_MAP(8960, VERSION_MINOR), + MSM_RPM_CTRL_MAP(8960, VERSION_BUILD), + MSM_RPM_CTRL_MAP(8960, REQ_CTX_0), + MSM_RPM_CTRL_MAP(8960, REQ_SEL_0), + MSM_RPM_CTRL_MAP(8960, ACK_CTX_0), + MSM_RPM_CTRL_MAP(8960, ACK_SEL_0), + }, + .sel_invalidate = MSM_RPM_8960_SEL_INVALIDATE, + .sel_notification = MSM_RPM_8960_SEL_NOTIFICATION, + .sel_last = MSM_RPM_8960_SEL_LAST, + .ver = {3, 0, 0}, +}; + +struct platform_device msm8960_rpm_device = { + .name = "msm_rpm", + .id = -1, +}; + +static struct msm_rpm_log_platform_data msm_rpm_log_pdata = { + .phys_addr_base = 0x0010C000, + .reg_offsets = { + [MSM_RPM_LOG_PAGE_INDICES] = 0x00000080, + [MSM_RPM_LOG_PAGE_BUFFER] = 0x000000A0, + }, + .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 */ +}; + +struct platform_device msm8960_rpm_log_device = { + .name = "msm_rpm_log", + .id = -1, + .dev = { + .platform_data = &msm_rpm_log_pdata, + }, +}; + +static struct msm_rpmstats_platform_data msm_rpm_stat_pdata = { + .phys_addr_base = 0x0010D204, + .phys_size = SZ_8K, +}; + +struct platform_device msm8960_rpm_stat_device = { + .name = "msm_rpm_stat", + .id = -1, + .dev = { + .platform_data = &msm_rpm_stat_pdata, + }, +}; + +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, + }, + + { + .start = PPSS_WDOG_TIMER_IRQ, + .end = PPSS_WDOG_TIMER_IRQ, + .name = "ppss_wdog", + .flags = IORESOURCE_IRQ, + }, +}; + +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_MSM_QDSS + +#define MSM_QDSS_PHYS_BASE 0x01A00000 +#define MSM_ETB_PHYS_BASE (MSM_QDSS_PHYS_BASE + 0x1000) +#define MSM_TPIU_PHYS_BASE (MSM_QDSS_PHYS_BASE + 0x3000) +#define MSM_FUNNEL_PHYS_BASE (MSM_QDSS_PHYS_BASE + 0x4000) +#define MSM_ETM_PHYS_BASE (MSM_QDSS_PHYS_BASE + 0x1C000) + +#define QDSS_SOURCE(src_name, fpm) { .name = src_name, .fport_mask = fpm, } + +static struct qdss_source msm_qdss_sources[] = { + QDSS_SOURCE("msm_etm", 0x3), +}; + +static struct msm_qdss_platform_data qdss_pdata = { + .src_table = msm_qdss_sources, + .size = ARRAY_SIZE(msm_qdss_sources), + .afamily = 1, +}; + +struct platform_device msm_qdss_device = { + .name = "msm_qdss", + .id = -1, + .dev = { + .platform_data = &qdss_pdata, + }, +}; + +static struct resource msm_etb_resources[] = { + { + .start = MSM_ETB_PHYS_BASE, + .end = MSM_ETB_PHYS_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_etb_device = { + .name = "msm_etb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_etb_resources), + .resource = msm_etb_resources, +}; + +static struct resource msm_tpiu_resources[] = { + { + .start = MSM_TPIU_PHYS_BASE, + .end = MSM_TPIU_PHYS_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_tpiu_device = { + .name = "msm_tpiu", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tpiu_resources), + .resource = msm_tpiu_resources, +}; + +static struct resource msm_funnel_resources[] = { + { + .start = MSM_FUNNEL_PHYS_BASE, + .end = MSM_FUNNEL_PHYS_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_funnel_device = { + .name = "msm_funnel", + .id = 0, + .num_resources = ARRAY_SIZE(msm_funnel_resources), + .resource = msm_funnel_resources, +}; + +static struct resource msm_etm_resources[] = { + { + .start = MSM_ETM_PHYS_BASE, + .end = MSM_ETM_PHYS_BASE + (SZ_4K * 2) - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_etm_device = { + .name = "msm_etm", + .id = 0, + .num_resources = ARRAY_SIZE(msm_etm_resources), + .resource = msm_etm_resources, +}; + +#endif + +static int msm8960_LPM_latency = 1000; /* >100 usec for WFI */ + +struct platform_device msm8960_cpu_idle_device = { + .name = "msm_cpu_idle", + .id = -1, + .dev = { + .platform_data = &msm8960_LPM_latency, + }, +}; + +static struct msm_dcvs_freq_entry msm8960_freq[] = { + { 384000, 166981, 345600}, + { 702000, 213049, 632502}, + {1026000, 285712, 925613}, + {1242000, 383945, 1176550}, + {1458000, 419729, 1465478}, + {1512000, 434116, 1546674}, + +}; + +static struct msm_dcvs_core_info msm8960_core_info = { + .freq_tbl = &msm8960_freq[0], + .core_param = { + .max_time_us = 100000, + .num_freq = ARRAY_SIZE(msm8960_freq), + }, + .algo_param = { + .slack_time_us = 58000, + .scale_slack_time = 0, + .scale_slack_time_pct = 0, + .disable_pc_threshold = 1458000, + .em_window_size = 100000, + .em_max_util_pct = 97, + .ss_window_size = 1000000, + .ss_util_pct = 95, + .ss_iobusy_conv = 100, + }, +}; + +struct platform_device msm8960_msm_gov_device = { + .name = "msm_dcvs_gov", + .id = -1, + .dev = { + .platform_data = &msm8960_core_info, + }, +}; + +static struct resource msm_cache_erp_resources[] = { + { + .name = "l1_irq", + .start = SC_SICCPUXEXTFAULTIRPTREQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "l2_irq", + .start = APCC_QGICL2IRPTREQ, + .flags = IORESOURCE_IRQ, + } +}; + +struct platform_device msm8960_device_cache_erp = { + .name = "msm_cache_erp", + .id = -1, + .num_resources = ARRAY_SIZE(msm_cache_erp_resources), + .resource = msm_cache_erp_resources, +}; + +struct msm_iommu_domain_name msm8960_iommu_ctx_names[] = { + /* Camera */ + { + .name = "vpe_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vpe_dst", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_imgwr", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_misc", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_dst", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_src", + .domain = CAMERA_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_dst", + .domain = CAMERA_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_src", + .domain = ROTATOR_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_dst", + .domain = ROTATOR_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 mem_pool msm8960_video_pools[] = { + /* + * Video hardware has the following requirements: + * 1. All video addresses used by the video hardware must be at a higher + * address than video firmware address. + * 2. Video hardware can only access a range of 256MB from the base of + * the video firmware. + */ + [VIDEO_FIRMWARE_POOL] = + /* Low addresses, intended for video firmware */ + { + .paddr = SZ_128K, + .size = SZ_16M - SZ_128K, + }, + [VIDEO_MAIN_POOL] = + /* Main video pool */ + { + .paddr = SZ_16M, + .size = SZ_256M - SZ_16M, + }, + [GEN_POOL] = + /* Remaining address space up to 2G */ + { + .paddr = SZ_256M, + .size = SZ_2G - SZ_256M, + }, +}; + +static struct mem_pool msm8960_camera_pools[] = { + [GEN_POOL] = + /* One address space for camera */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct mem_pool msm8960_display_pools[] = { + [GEN_POOL] = + /* One address space for display */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct mem_pool msm8960_rotator_pools[] = { + [GEN_POOL] = + /* One address space for rotator */ + { + .paddr = SZ_128K, + .size = SZ_2G - SZ_128K, + }, +}; + +static struct msm_iommu_domain msm8960_iommu_domains[] = { + [VIDEO_DOMAIN] = { + .iova_pools = msm8960_video_pools, + .npools = ARRAY_SIZE(msm8960_video_pools), + }, + [CAMERA_DOMAIN] = { + .iova_pools = msm8960_camera_pools, + .npools = ARRAY_SIZE(msm8960_camera_pools), + }, + [DISPLAY_DOMAIN] = { + .iova_pools = msm8960_display_pools, + .npools = ARRAY_SIZE(msm8960_display_pools), + }, + [ROTATOR_DOMAIN] = { + .iova_pools = msm8960_rotator_pools, + .npools = ARRAY_SIZE(msm8960_rotator_pools), + }, +}; + +struct iommu_domains_pdata msm8960_iommu_domain_pdata = { + .domains = msm8960_iommu_domains, + .ndomains = ARRAY_SIZE(msm8960_iommu_domains), + .domain_names = msm8960_iommu_ctx_names, + .nnames = ARRAY_SIZE(msm8960_iommu_ctx_names), + .domain_alloc_flags = 0, +}; + +struct platform_device msm8960_iommu_domain_device = { + .name = "iommu_domains", + .id = -1, + .dev = { + .platform_data = &msm8960_iommu_domain_pdata, + } +}; + +struct msm_rtb_platform_data msm8960_rtb_pdata = { + .size = SZ_1M, +}; + +static int __init msm_rtb_set_buffer_size(char *p) +{ + int s; + + s = memparse(p, NULL); + msm8960_rtb_pdata.size = ALIGN(s, SZ_4K); + return 0; +} +early_param("msm_rtb_size", msm_rtb_set_buffer_size); + + +struct platform_device msm8960_rtb_device = { + .name = "msm_rtb", + .id = -1, + .dev = { + .platform_data = &msm8960_rtb_pdata, + }, +}; + +#define MSM_8960_L1_SIZE SZ_1M +/* + * The actual L2 size is smaller but we need a larger buffer + * size to store other dump information + */ +#define MSM_8960_L2_SIZE SZ_4M + +struct msm_cache_dump_platform_data msm8960_cache_dump_pdata = { + .l2_size = MSM_8960_L2_SIZE, + .l1_size = MSM_8960_L1_SIZE, +}; + +struct platform_device msm8960_cache_dump_device = { + .name = "msm_cache_dump", + .id = -1, + .dev = { + .platform_data = &msm8960_cache_dump_pdata, + }, +}; + +#define MDM2AP_ERRFATAL 40 +#define AP2MDM_ERRFATAL 80 +#define MDM2AP_STATUS 24 +#define AP2MDM_STATUS 77 +#define AP2MDM_PMIC_PWR_EN 22 +#define AP2MDM_KPDPWR_N 79 +#define AP2MDM_SOFT_RESET 78 + +static struct resource sglte_resources[] = { + { + .start = MDM2AP_ERRFATAL, + .end = MDM2AP_ERRFATAL, + .name = "MDM2AP_ERRFATAL", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_ERRFATAL, + .end = AP2MDM_ERRFATAL, + .name = "AP2MDM_ERRFATAL", + .flags = IORESOURCE_IO, + }, + { + .start = MDM2AP_STATUS, + .end = MDM2AP_STATUS, + .name = "MDM2AP_STATUS", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_STATUS, + .end = AP2MDM_STATUS, + .name = "AP2MDM_STATUS", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_PMIC_PWR_EN, + .end = AP2MDM_PMIC_PWR_EN, + .name = "AP2MDM_PMIC_PWR_EN", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_KPDPWR_N, + .end = AP2MDM_KPDPWR_N, + .name = "AP2MDM_KPDPWR_N", + .flags = IORESOURCE_IO, + }, + { + .start = AP2MDM_SOFT_RESET, + .end = AP2MDM_SOFT_RESET, + .name = "AP2MDM_SOFT_RESET", + .flags = IORESOURCE_IO, + }, +}; + +struct platform_device mdm_sglte_device = { + .name = "mdm2_modem", + .id = -1, + .num_resources = ARRAY_SIZE(sglte_resources), + .resource = sglte_resources, +}; diff --git a/arch/arm/mach-msm/devices-9615.c b/arch/arm/mach-msm/devices-9615.c new file mode 100644 index 0000000000000000000000000000000000000000..76d79a6d17626a3bd3f069c83172ba25a53bfa14 --- /dev/null +++ b/arch/arm/mach-msm/devices-9615.c @@ -0,0 +1,1381 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "pm.h" +#include "devices.h" +#include +#include "spm.h" +#include "rpm_resources.h" +#include "msm_watchdog.h" +#include "rpm_stats.h" +#include "rpm_log.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_UART4DM_PHYS (MSM_GSBI4_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_QUP_SIZE SZ_4K + +/* Address of SSBI CMD */ +#define MSM_PMIC1_SSBI_CMD_PHYS 0x00500000 +#define MSM_PMIC_SSBI_SIZE SZ_4K + +#define MSM_GPIO_I2C_CLK 16 +#define MSM_GPIO_I2C_SDA 17 + +static struct msm_watchdog_pdata msm_watchdog_pdata = { + .pet_time = 10000, + .bark_time = 11000, + .has_secure = false, + .use_kernel_fiq = true, +}; + +struct platform_device msm9615_device_watchdog = { + .name = "msm_watchdog", + .id = -1, + .dev = { + .platform_data = &msm_watchdog_pdata, + }, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = ADM_0_SCSS_1_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0x18320000, + .end = 0x18320000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 1, + .sd_size = 0x800, +}; + +struct platform_device msm9615_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +#define MSM_USB_BAM_BASE 0x12502000 +#define MSM_USB_BAM_SIZE SZ_16K +#define MSM_HSIC_BAM_BASE 0x12542000 +#define MSM_HSIC_BAM_SIZE SZ_16K + +static struct resource resources_otg[] = { + { + .start = MSM9615_HSUSB_PHYS, + .end = MSM9615_HSUSB_PHYS + MSM9615_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 = DMA_BIT_MASK(32), + }, +}; + +#define MSM_HSUSB_RESUME_GPIO 79 + +static struct resource resources_hsusb[] = { + { + .start = MSM9615_HSUSB_PHYS, + .end = MSM9615_HSUSB_PHYS + MSM9615_HSUSB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_HSUSB_RESUME_GPIO, + .end = MSM_HSUSB_RESUME_GPIO, + .name = "USB_RESUME", + .flags = IORESOURCE_IO, + }, +}; + +static struct resource resources_usb_bam[] = { + { + .name = "usb_bam_addr", + .start = MSM_USB_BAM_BASE, + .end = MSM_USB_BAM_BASE + MSM_USB_BAM_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "usb_bam_irq", + .start = USB1_HS_BAM_IRQ, + .end = USB1_HS_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "hsic_bam_addr", + .start = MSM_HSIC_BAM_BASE, + .end = MSM_HSIC_BAM_BASE + MSM_HSIC_BAM_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "hsic_bam_irq", + .start = USB_HSIC_BAM_IRQ, + .end = USB_HSIC_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_usb_bam = { + .name = "usb_bam", + .id = -1, + .num_resources = ARRAY_SIZE(resources_usb_bam), + .resource = resources_usb_bam, +}; + +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 = DMA_BIT_MASK(32), + }, +}; + +static struct resource resources_hsic_peripheral[] = { + { + .start = MSM9615_HSIC_PHYS, + .end = MSM9615_HSIC_PHYS + MSM9615_HSIC_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB_HSIC_IRQ, + .end = USB_HSIC_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsic_peripheral = { + .name = "msm_hsic_peripheral", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsic_peripheral), + .resource = resources_hsic_peripheral, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource resources_hsusb_host[] = { + { + .start = MSM9615_HSUSB_PHYS, + .end = MSM9615_HSUSB_PHYS + MSM9615_HSUSB_PHYS - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dma_mask = DMA_BIT_MASK(32); +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_hsic_host[] = { + { + .start = MSM9615_HSIC_PHYS, + .end = MSM9615_HSIC_PHYS + MSM9615_HSIC_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB_HSIC_IRQ, + .end = USB_HSIC_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsic_host = { + .name = "msm_hsic_host", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsic_host), + .resource = resources_hsic_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct resource resources_uart_gsbi4[] = { + { + .start = GSBI4_UARTDM_IRQ, + .end = GSBI4_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART4DM_PHYS, + .end = MSM_UART4DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI4_PHYS, + .end = MSM_GSBI4_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm9615_device_uart_gsbi4 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart_gsbi4), + .resource = resources_uart_gsbi4, +}; + +static struct resource resources_qup_i2c_gsbi5[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI5_PHYS, + .end = MSM_GSBI5_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI5_QUP_PHYS, + .end = MSM_GSBI5_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI5_QUP_IRQ, + .end = GSBI5_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = MSM_GPIO_I2C_CLK, + .end = MSM_GPIO_I2C_CLK, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = MSM_GPIO_I2C_SDA, + .end = MSM_GPIO_I2C_SDA, + .flags = IORESOURCE_IO, + + }, +}; + +struct platform_device msm9615_device_qup_i2c_gsbi5 = { + .name = "qup_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi5), + .resource = resources_qup_i2c_gsbi5, +}; + +static struct resource resources_qup_spi_gsbi3[] = { + { + .name = "spi_base", + .start = MSM_GSBI3_QUP_PHYS, + .end = MSM_GSBI3_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_base", + .start = MSM_GSBI3_PHYS, + .end = MSM_GSBI3_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "spi_irq_in", + .start = GSBI3_QUP_IRQ, + .end = GSBI3_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm9615_device_qup_spi_gsbi3 = { + .name = "spi_qsd", + .id = 0, + .num_resources = ARRAY_SIZE(resources_qup_spi_gsbi3), + .resource = resources_qup_spi_gsbi3, +}; + +#define LPASS_SLIMBUS_PHYS 0x28080000 +#define LPASS_SLIMBUS_BAM_PHYS 0x28084000 +#define LPASS_SLIMBUS_SLEW (MSM9615_TLMM_PHYS + 0x207C) +/* 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 = LPASS_SLIMBUS_SLEW, + .end = LPASS_SLIMBUS_SLEW + 4 - 1, + .flags = IORESOURCE_MEM, + .name = "slimbus_slew_reg", + }, + { + .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 msm9615_slim_ctrl = { + .name = "msm_slim_ctrl", + .id = 1, + .num_resources = ARRAY_SIZE(slimbus_res), + .resource = slimbus_res, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct platform_device msm_pcm = { + .name = "msm-pcm-dsp", + .id = -1, +}; + +struct platform_device msm_multi_ch_pcm = { + .name = "msm-multi-ch-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_bt_rx = { + .name = "msm-dai-q6", + .id = 0x3000, +}; + +struct platform_device msm_cpudai_bt_tx = { + .name = "msm-dai-q6", + .id = 0x3001, +}; + +/* + * Machine specific data for AUX PCM Interface + * which the driver will be unware of. + */ +struct msm_dai_auxpcm_pdata auxpcm_pdata = { + .clk = "pcm_clk", + .mode_8k = { + .mode = AFE_PCM_CFG_MODE_PCM, + .sync = AFE_PCM_CFG_SYNC_INT, + .frame = AFE_PCM_CFG_FRM_256BPF, + .quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD, + .slot = 0, + .data = AFE_PCM_CFG_CDATAOE_MASTER, + .pcm_clk_rate = 2048000, + }, + .mode_16k = { + .mode = AFE_PCM_CFG_MODE_PCM, + .sync = AFE_PCM_CFG_SYNC_INT, + .frame = AFE_PCM_CFG_FRM_256BPF, + .quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD, + .slot = 0, + .data = AFE_PCM_CFG_CDATAOE_MASTER, + .pcm_clk_rate = 4096000, + } +}; + +struct platform_device msm_cpudai_auxpcm_rx = { + .name = "msm-dai-q6", + .id = 2, + .dev = { + .platform_data = &auxpcm_pdata, + }, +}; + +struct platform_device msm_cpudai_auxpcm_tx = { + .name = "msm-dai-q6", + .id = 3, + .dev = { + .platform_data = &auxpcm_pdata, + }, +}; + +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_i2s_cpudai0 = { + .name = "msm-dai-q6", + .id = PRIMARY_I2S_RX, +}; + +struct platform_device msm_i2s_cpudai1 = { + .name = "msm-dai-q6", + .id = PRIMARY_I2S_TX, +}; +struct platform_device msm_voip = { + .name = "msm-voip-dsp", + .id = -1, +}; + +struct platform_device msm_compr_dsp = { + .name = "msm-compr-dsp", + .id = -1, +}; + +struct platform_device msm_pcm_hostless = { + .name = "msm-pcm-hostless", + .id = -1, +}; + +struct platform_device msm_cpudai_afe_01_rx = { + .name = "msm-dai-q6", + .id = 0xE0, +}; + +struct platform_device msm_cpudai_afe_01_tx = { + .name = "msm-dai-q6", + .id = 0xF0, +}; + +struct platform_device msm_cpudai_afe_02_rx = { + .name = "msm-dai-q6", + .id = 0xF1, +}; + +struct platform_device msm_cpudai_afe_02_tx = { + .name = "msm-dai-q6", + .id = 0xE1, +}; + +struct platform_device msm_pcm_afe = { + .name = "msm-pcm-afe", + .id = -1, +}; + +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 msm9615_device_ssbi_pmic1 = { + .name = "msm_ssbi", + .id = 0, + .resource = resources_ssbi_pmic1, + .num_resources = ARRAY_SIZE(resources_ssbi_pmic1), +}; + +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, +}; + +#define MSM_NAND_PHYS 0x1B400000 +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, + }, +}; + +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 platform_device msm_device_bam_dmux = { + .name = "BAM_RMNT", + .id = -1, +}; + +#ifdef CONFIG_HW_RANDOM_MSM +/* PRNG device */ +#define MSM_PRNG_PHYS 0x1A500000 +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 + +#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, + .bus_scale_table = NULL, +}; + +struct platform_device msm9615_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, + .bus_scale_table = NULL, +}; + +struct platform_device msm9615_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 + +#define MSM_SDC1_BASE 0x12180000 +#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) + +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 +}; + +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, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 2) + return -EINVAL; + + pdev = msm_sdcc_devices[controller - 1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#ifdef CONFIG_FB_MSM_EBI2 +static struct resource msm_ebi2_lcdc_resources[] = { + { + .name = "base", + .start = 0x1B300000, + .end = 0x1B300000 + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd01", + .start = 0x1FC00000, + .end = 0x1FC00000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_ebi2_lcdc_device = { + .name = "ebi2_lcd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ebi2_lcdc_resources), + .resource = msm_ebi2_lcdc_resources, +}; +#endif + +#ifdef CONFIG_CACHE_L2X0 +static int __init l2x0_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); + + /* L2 Latency setting required by hardware. Default is 0x20 + which is no good. + */ + writel_relaxed(0x220, MSM_L2CC_BASE + L2X0_DATA_LATENCY_CTRL); + l2x0_init(MSM_L2CC_BASE, aux_ctrl, L2X0_AUX_CTRL_MASK); + + return 0; +} +#else +static int __init l2x0_cache_init(void){ return 0; } +#endif + +struct msm_rpm_platform_data msm9615_rpm_data __initdata = { + .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_wakeup = RPM_APCC_CPU0_WAKE_UP_IRQ, + .ipc_rpm_reg = MSM_APCS_GCC_BASE + 0x008, + .ipc_rpm_val = 4, + .target_id = { + MSM_RPM_MAP(9615, NOTIFICATION_CONFIGURED_0, NOTIFICATION, 4), + MSM_RPM_MAP(9615, NOTIFICATION_REGISTERED_0, NOTIFICATION, 4), + MSM_RPM_MAP(9615, INVALIDATE_0, INVALIDATE, 8), + MSM_RPM_MAP(9615, TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(9615, TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + MSM_RPM_MAP(9615, RPM_CTL, RPM_CTL, 1), + MSM_RPM_MAP(9615, CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(9615, SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(9615, DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(9615, SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(9615, CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(9615, EBI1_CLK, EBI1_CLK, 1), + MSM_RPM_MAP(9615, SYS_FABRIC_CFG_HALT_0, + SYS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(9615, SYS_FABRIC_CFG_CLKMOD_0, + SYS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(9615, SYS_FABRIC_CFG_IOCTL, + SYS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(9615, SYSTEM_FABRIC_ARB_0, + SYSTEM_FABRIC_ARB, 27), + MSM_RPM_MAP(9615, PM8018_S1_0, PM8018_S1, 2), + MSM_RPM_MAP(9615, PM8018_S2_0, PM8018_S2, 2), + MSM_RPM_MAP(9615, PM8018_S3_0, PM8018_S3, 2), + MSM_RPM_MAP(9615, PM8018_S4_0, PM8018_S4, 2), + MSM_RPM_MAP(9615, PM8018_S5_0, PM8018_S5, 2), + MSM_RPM_MAP(9615, PM8018_L1_0, PM8018_L1, 2), + MSM_RPM_MAP(9615, PM8018_L2_0, PM8018_L2, 2), + MSM_RPM_MAP(9615, PM8018_L3_0, PM8018_L3, 2), + MSM_RPM_MAP(9615, PM8018_L4_0, PM8018_L4, 2), + MSM_RPM_MAP(9615, PM8018_L5_0, PM8018_L5, 2), + MSM_RPM_MAP(9615, PM8018_L6_0, PM8018_L6, 2), + MSM_RPM_MAP(9615, PM8018_L7_0, PM8018_L7, 2), + MSM_RPM_MAP(9615, PM8018_L8_0, PM8018_L8, 2), + MSM_RPM_MAP(9615, PM8018_L9_0, PM8018_L9, 2), + MSM_RPM_MAP(9615, PM8018_L10_0, PM8018_L10, 2), + MSM_RPM_MAP(9615, PM8018_L11_0, PM8018_L11, 2), + MSM_RPM_MAP(9615, PM8018_L12_0, PM8018_L12, 2), + MSM_RPM_MAP(9615, PM8018_L13_0, PM8018_L13, 2), + MSM_RPM_MAP(9615, PM8018_L14_0, PM8018_L14, 2), + MSM_RPM_MAP(9615, PM8018_LVS1, PM8018_LVS1, 1), + MSM_RPM_MAP(9615, NCP_0, NCP, 2), + MSM_RPM_MAP(9615, CXO_BUFFERS, CXO_BUFFERS, 1), + MSM_RPM_MAP(9615, USB_OTG_SWITCH, USB_OTG_SWITCH, 1), + MSM_RPM_MAP(9615, HDMI_SWITCH, HDMI_SWITCH, 1), + }, + .target_status = { + MSM_RPM_STATUS_ID_MAP(9615, VERSION_MAJOR), + MSM_RPM_STATUS_ID_MAP(9615, VERSION_MINOR), + MSM_RPM_STATUS_ID_MAP(9615, VERSION_BUILD), + MSM_RPM_STATUS_ID_MAP(9615, SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(9615, SUPPORTED_RESOURCES_1), + MSM_RPM_STATUS_ID_MAP(9615, SUPPORTED_RESOURCES_2), + MSM_RPM_STATUS_ID_MAP(9615, RESERVED_SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(9615, SEQUENCE), + MSM_RPM_STATUS_ID_MAP(9615, RPM_CTL), + MSM_RPM_STATUS_ID_MAP(9615, CXO_CLK), + MSM_RPM_STATUS_ID_MAP(9615, SYSTEM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(9615, DAYTONA_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(9615, SFPB_CLK), + MSM_RPM_STATUS_ID_MAP(9615, CFPB_CLK), + MSM_RPM_STATUS_ID_MAP(9615, EBI1_CLK), + MSM_RPM_STATUS_ID_MAP(9615, SYS_FABRIC_CFG_HALT), + MSM_RPM_STATUS_ID_MAP(9615, SYS_FABRIC_CFG_CLKMOD), + MSM_RPM_STATUS_ID_MAP(9615, SYS_FABRIC_CFG_IOCTL), + MSM_RPM_STATUS_ID_MAP(9615, SYSTEM_FABRIC_ARB), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S1_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S1_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S2_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S2_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S3_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S3_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S4_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S4_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S5_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_S5_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L1_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L1_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L2_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L2_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L3_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L3_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L4_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L4_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L5_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L5_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L6_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L6_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L7_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L7_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L8_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L8_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L9_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L9_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L10_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L10_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L11_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L11_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L12_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L12_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L13_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L13_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L14_0), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_L14_1), + MSM_RPM_STATUS_ID_MAP(9615, PM8018_LVS1), + MSM_RPM_STATUS_ID_MAP(9615, NCP_0), + MSM_RPM_STATUS_ID_MAP(9615, NCP_1), + MSM_RPM_STATUS_ID_MAP(9615, CXO_BUFFERS), + MSM_RPM_STATUS_ID_MAP(9615, USB_OTG_SWITCH), + MSM_RPM_STATUS_ID_MAP(9615, HDMI_SWITCH), + }, + .target_ctrl_id = { + MSM_RPM_CTRL_MAP(9615, VERSION_MAJOR), + MSM_RPM_CTRL_MAP(9615, VERSION_MINOR), + MSM_RPM_CTRL_MAP(9615, VERSION_BUILD), + MSM_RPM_CTRL_MAP(9615, REQ_CTX_0), + MSM_RPM_CTRL_MAP(9615, REQ_SEL_0), + MSM_RPM_CTRL_MAP(9615, ACK_CTX_0), + MSM_RPM_CTRL_MAP(9615, ACK_SEL_0), + }, + .sel_invalidate = MSM_RPM_9615_SEL_INVALIDATE, + .sel_notification = MSM_RPM_9615_SEL_NOTIFICATION, + .sel_last = MSM_RPM_9615_SEL_LAST, + .ver = {3, 0, 0}, +}; + +struct platform_device msm9615_rpm_device = { + .name = "msm_rpm", + .id = -1, +}; + +static uint16_t msm_mpm_irqs_m2a[MSM_MPM_NR_MPM_IRQS] __initdata = { + [4] = MSM_GPIO_TO_INT(30), + [5] = MSM_GPIO_TO_INT(59), + [6] = MSM_GPIO_TO_INT(81), + [7] = MSM_GPIO_TO_INT(87), + [8] = MSM_GPIO_TO_INT(86), + [9] = MSM_GPIO_TO_INT(2), + [10] = MSM_GPIO_TO_INT(6), + [11] = MSM_GPIO_TO_INT(10), + [12] = MSM_GPIO_TO_INT(14), + [13] = MSM_GPIO_TO_INT(18), + [14] = MSM_GPIO_TO_INT(7), + [15] = MSM_GPIO_TO_INT(11), + [16] = MSM_GPIO_TO_INT(15), + [19] = MSM_GPIO_TO_INT(26), + [20] = MSM_GPIO_TO_INT(28), + [22] = USB_HSIC_IRQ, + [23] = MSM_GPIO_TO_INT(19), + [24] = MSM_GPIO_TO_INT(23), + [26] = MSM_GPIO_TO_INT(3), + [27] = MSM_GPIO_TO_INT(68), + [29] = MSM_GPIO_TO_INT(78), + [31] = MSM_GPIO_TO_INT(0), + [32] = MSM_GPIO_TO_INT(4), + [33] = MSM_GPIO_TO_INT(22), + [34] = MSM_GPIO_TO_INT(17), + [37] = MSM_GPIO_TO_INT(20), + [39] = MSM_GPIO_TO_INT(84), + [40] = USB1_HS_IRQ, + [42] = MSM_GPIO_TO_INT(24), + [43] = MSM_GPIO_TO_INT(79), + [44] = MSM_GPIO_TO_INT(80), + [45] = MSM_GPIO_TO_INT(82), + [46] = MSM_GPIO_TO_INT(85), + [47] = MSM_GPIO_TO_INT(45), + [48] = MSM_GPIO_TO_INT(50), + [49] = MSM_GPIO_TO_INT(51), + [50] = MSM_GPIO_TO_INT(69), + [51] = MSM_GPIO_TO_INT(77), + [52] = MSM_GPIO_TO_INT(1), + [53] = MSM_GPIO_TO_INT(5), + [54] = MSM_GPIO_TO_INT(40), + [55] = MSM_GPIO_TO_INT(27), +}; + +static uint16_t msm_mpm_bypassed_apps_irqs[] __initdata = { + 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, + MSS_TO_APPS_IRQ_0, + MSS_TO_APPS_IRQ_1, + LPASS_SCSS_GP_LOW_IRQ, + LPASS_SCSS_GP_MEDIUM_IRQ, + LPASS_SCSS_GP_HIGH_IRQ, + SPS_MTI_31, + A2_BAM_IRQ, +}; + +struct msm_mpm_device_data msm9615_mpm_dev_data __initdata = { + .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, +}; + +static uint8_t spm_wfi_cmd_sequence[] __initdata = { + 0x00, 0x03, 0x00, 0x0f, +}; + +static uint8_t spm_power_collapse_without_rpm[] __initdata = { + 0x34, 0x24, 0x14, 0x04, + 0x54, 0x03, 0x54, 0x04, + 0x14, 0x24, 0x3e, 0x0f, +}; + +static uint8_t spm_power_collapse_with_rpm[] __initdata = { + 0x34, 0x24, 0x14, 0x04, + 0x54, 0x07, 0x54, 0x04, + 0x14, 0x24, 0x3e, 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_SPM_CTL] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x1001, + .num_modes = ARRAY_SIZE(msm_spm_seq_list), + .modes = msm_spm_seq_list, + }, +}; + +static struct msm_rpmrs_level msm_rpmrs_levels[] __initdata = { + { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 100, 8000, 100000, 1, + }, + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 2000, 5000, 60100000, 3000, + }, + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + false, + 6300, 5000, 60350000, 3500, + }, + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, MAX, ACTIVE), + false, + 13300, 2000, 71850000, 6800, + }, + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, RET_HIGH, RET_LOW), + false, + 28300, 0, 76350000, 9800, + }, +}; + +static struct msm_rpmrs_platform_data msm_rpmrs_data __initdata = { + .levels = &msm_rpmrs_levels[0], + .num_levels = ARRAY_SIZE(msm_rpmrs_levels), + .vdd_mem_levels = { + [MSM_RPMRS_VDD_MEM_RET_LOW] = 750000, + [MSM_RPMRS_VDD_MEM_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_MEM_ACTIVE] = 1050000, + [MSM_RPMRS_VDD_MEM_MAX] = 1150000, + }, + .vdd_dig_levels = { + [MSM_RPMRS_VDD_DIG_RET_LOW] = 500000, + [MSM_RPMRS_VDD_DIG_RET_HIGH] = 750000, + [MSM_RPMRS_VDD_DIG_ACTIVE] = 950000, + [MSM_RPMRS_VDD_DIG_MAX] = 1150000, + }, + .vdd_mask = 0x7FFFFF, + .rpmrs_target_id = { + [MSM_RPMRS_ID_PXO_CLK] = MSM_RPM_ID_CXO_CLK, + [MSM_RPMRS_ID_L2_CACHE_CTL] = MSM_RPM_ID_LAST, + [MSM_RPMRS_ID_VDD_DIG_0] = MSM_RPM_ID_PM8018_S1_0, + [MSM_RPMRS_ID_VDD_DIG_1] = MSM_RPM_ID_PM8018_S1_1, + [MSM_RPMRS_ID_VDD_MEM_0] = MSM_RPM_ID_PM8018_L9_0, + [MSM_RPMRS_ID_VDD_MEM_1] = MSM_RPM_ID_PM8018_L9_1, + [MSM_RPMRS_ID_RPM_CTL] = MSM_RPM_ID_RPM_CTL, + }, +}; + +static struct msm_rpmstats_platform_data msm_rpm_stat_pdata = { + .phys_addr_base = 0x0010D204, + .phys_size = SZ_8K, +}; + +struct platform_device msm9615_rpm_stat_device = { + .name = "msm_rpm_stat", + .id = -1, + .dev = { + .platform_data = &msm_rpm_stat_pdata, + }, +}; + +static struct msm_rpm_log_platform_data msm_rpm_log_pdata = { + .phys_addr_base = 0x0010AC00, + .reg_offsets = { + [MSM_RPM_LOG_PAGE_INDICES] = 0x00000080, + [MSM_RPM_LOG_PAGE_BUFFER] = 0x000000A0, + }, + .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 */ +}; + +struct platform_device msm9615_rpm_log_device = { + .name = "msm_rpm_log", + .id = -1, + .dev = { + .platform_data = &msm_rpm_log_pdata, + }, +}; + +uint32_t __init msm9615_rpm_get_swfi_latency(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(msm_rpmrs_levels); i++) { + if (msm_rpmrs_levels[i].sleep_mode == + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT) + return msm_rpmrs_levels[i].latency_us; + } + return 0; +} + +struct android_usb_platform_data msm_android_usb_pdata; + +struct platform_device msm_android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &msm_android_usb_pdata, + }, +}; + +void __init msm9615_device_init(void) +{ + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + BUG_ON(msm_rpm_init(&msm9615_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(&msm_rpmrs_data)); + msm_android_usb_pdata.swfi_latency = + msm_rpmrs_levels[0].latency_us; + +} + +#define MSM_SHARED_RAM_PHYS 0x40000000 +void __init msm9615_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_msm9615_io(); + l2x0_cache_init(); + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); +} + +void __init msm9615_init_irq(void) +{ + struct msm_mpm_device_data *data = NULL; + +#ifdef CONFIG_MSM_MPM + data = &msm9615_mpm_dev_data; +#endif + + msm_mpm_irq_extn_init(data); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); +} + +struct platform_device msm_bus_9615_sys_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM, +}; + +struct platform_device msm_bus_def_fab = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_DEFAULT, +}; + +#ifdef CONFIG_FB_MSM_EBI2 +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, "ebi2", 4)) + msm_register_device(&msm_ebi2_lcdc_device, data); + else + pr_err("%s: unknown device! %s\n", __func__, name); +} +#endif diff --git a/arch/arm/mach-msm/devices-fsm9xxx.c b/arch/arm/mach-msm/devices-fsm9xxx.c new file mode 100644 index 0000000000000000000000000000000000000000..777b6d6e5e9de7cb50389077bf7aa2dbaed4892f --- /dev/null +++ b/arch/arm/mach-msm/devices-fsm9xxx.c @@ -0,0 +1,411 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 "msm_watchdog.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_MSM_SSBI +#define MSM_SSBI1_PHYS 0x94080000 +#define MSM_SSBI_PMIC1_PHYS MSM_SSBI1_PHYS +static struct resource msm_ssbi_pmic1_resources[] = { + { + .start = MSM_SSBI_PMIC1_PHYS, + .end = MSM_SSBI_PMIC1_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi_pmic1 = { + .name = "msm_ssbi", + .id = 0, + .resource = msm_ssbi_pmic1_resources, + .num_resources = ARRAY_SIZE(msm_ssbi_pmic1_resources), +}; +#endif + +#ifdef CONFIG_I2C_SSBI +#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 + */ + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0x94610000, + .end = 0x94610000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 3, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +/* + * 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 + +/* + * XO + */ + +struct platform_device fsm_xo_device = { + .name = "fsm_xo_driver", + .id = -1, +}; + +/* + * Watchdog + */ + +static struct msm_watchdog_pdata fsm_watchdog_pdata = { + .pet_time = 10000, + .bark_time = 11000, + .has_secure = false, + .has_vic = true, +}; + +struct platform_device fsm9xxx_device_watchdog = { + .name = "msm_watchdog", + .id = -1, + .dev = { + .platform_data = &fsm_watchdog_pdata, + }, +}; + diff --git a/arch/arm/mach-msm/devices-iommu.c b/arch/arm/mach-msm/devices-iommu.c index 0fb7a17df3987e259297d21de210971d2f65fb80..14329025b642e7230491cdd5ed0d258b7824a186 100644 --- a/arch/arm/mach-msm/devices-iommu.c +++ b/arch/arm/mach-msm/devices-iommu.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This 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,6 +16,7 @@ #include #include #include +#include static struct resource msm_iommu_jpegd_resources[] = { { @@ -31,14 +27,14 @@ static struct resource msm_iommu_jpegd_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_JPEGD_CB_SC_NON_SECURE_IRQ, - .end = SMMU_JPEGD_CB_SC_NON_SECURE_IRQ, + .start = 98, + .end = 98, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_JPEGD_CB_SC_SECURE_IRQ, - .end = SMMU_JPEGD_CB_SC_SECURE_IRQ, + .start = 97, + .end = 97, .flags = IORESOURCE_IRQ, }, }; @@ -52,14 +48,14 @@ static struct resource msm_iommu_vpe_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_VPE_CB_SC_NON_SECURE_IRQ, - .end = SMMU_VPE_CB_SC_NON_SECURE_IRQ, + .start = 84, + .end = 84, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_VPE_CB_SC_SECURE_IRQ, - .end = SMMU_VPE_CB_SC_SECURE_IRQ, + .start = 83, + .end = 83, .flags = IORESOURCE_IRQ, }, }; @@ -73,14 +69,14 @@ static struct resource msm_iommu_mdp0_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_MDP0_CB_SC_NON_SECURE_IRQ, - .end = SMMU_MDP0_CB_SC_NON_SECURE_IRQ, + .start = 96, + .end = 96, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_MDP0_CB_SC_SECURE_IRQ, - .end = SMMU_MDP0_CB_SC_SECURE_IRQ, + .start = 95, + .end = 95, .flags = IORESOURCE_IRQ, }, }; @@ -94,14 +90,14 @@ static struct resource msm_iommu_mdp1_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_MDP1_CB_SC_NON_SECURE_IRQ, - .end = SMMU_MDP1_CB_SC_NON_SECURE_IRQ, + .start = 94, + .end = 94, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_MDP1_CB_SC_SECURE_IRQ, - .end = SMMU_MDP1_CB_SC_SECURE_IRQ, + .start = 93, + .end = 93, .flags = IORESOURCE_IRQ, }, }; @@ -115,14 +111,14 @@ static struct resource msm_iommu_rot_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_ROT_CB_SC_NON_SECURE_IRQ, - .end = SMMU_ROT_CB_SC_NON_SECURE_IRQ, + .start = 92, + .end = 92, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_ROT_CB_SC_SECURE_IRQ, - .end = SMMU_ROT_CB_SC_SECURE_IRQ, + .start = 91, + .end = 91, .flags = IORESOURCE_IRQ, }, }; @@ -136,14 +132,14 @@ static struct resource msm_iommu_ijpeg_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_IJPEG_CB_SC_NON_SECURE_IRQ, - .end = SMMU_IJPEG_CB_SC_NON_SECURE_IRQ, + .start = 100, + .end = 100, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_IJPEG_CB_SC_SECURE_IRQ, - .end = SMMU_IJPEG_CB_SC_SECURE_IRQ, + .start = 99, + .end = 99, .flags = IORESOURCE_IRQ, }, }; @@ -157,14 +153,14 @@ static struct resource msm_iommu_vfe_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_VFE_CB_SC_NON_SECURE_IRQ, - .end = SMMU_VFE_CB_SC_NON_SECURE_IRQ, + .start = 86, + .end = 86, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_VFE_CB_SC_SECURE_IRQ, - .end = SMMU_VFE_CB_SC_SECURE_IRQ, + .start = 85, + .end = 85, .flags = IORESOURCE_IRQ, }, }; @@ -178,14 +174,14 @@ static struct resource msm_iommu_vcodec_a_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_VCODEC_A_CB_SC_NON_SECURE_IRQ, - .end = SMMU_VCODEC_A_CB_SC_NON_SECURE_IRQ, + .start = 90, + .end = 90, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_VCODEC_A_CB_SC_SECURE_IRQ, - .end = SMMU_VCODEC_A_CB_SC_SECURE_IRQ, + .start = 89, + .end = 89, .flags = IORESOURCE_IRQ, }, }; @@ -199,14 +195,14 @@ static struct resource msm_iommu_vcodec_b_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_VCODEC_B_CB_SC_NON_SECURE_IRQ, - .end = SMMU_VCODEC_B_CB_SC_NON_SECURE_IRQ, + .start = 88, + .end = 88, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_VCODEC_B_CB_SC_SECURE_IRQ, - .end = SMMU_VCODEC_B_CB_SC_SECURE_IRQ, + .start = 87, + .end = 87, .flags = IORESOURCE_IRQ, }, }; @@ -220,14 +216,35 @@ static struct resource msm_iommu_gfx3d_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_GFX3D_CB_SC_NON_SECURE_IRQ, - .end = SMMU_GFX3D_CB_SC_NON_SECURE_IRQ, + .start = 102, + .end = 102, + .flags = IORESOURCE_IRQ, + }, + { + .name = "secure_irq", + .start = 101, + .end = 101, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_iommu_gfx3d1_resources[] = { + { + .start = 0x07D00000, + .end = 0x07D00000 + SZ_1M - 1, + .name = "physbase", + .flags = IORESOURCE_MEM, + }, + { + .name = "nonsecure_irq", + .start = 243, + .end = 243, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_GFX3D_CB_SC_SECURE_IRQ, - .end = SMMU_GFX3D_CB_SC_SECURE_IRQ, + .start = 242, + .end = 242, .flags = IORESOURCE_IRQ, }, }; @@ -241,14 +258,14 @@ static struct resource msm_iommu_gfx2d0_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_GFX2D0_CB_SC_NON_SECURE_IRQ, - .end = SMMU_GFX2D0_CB_SC_NON_SECURE_IRQ, + .start = 104, + .end = 104, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_GFX2D0_CB_SC_SECURE_IRQ, - .end = SMMU_GFX2D0_CB_SC_SECURE_IRQ, + .start = 103, + .end = 103, .flags = IORESOURCE_IRQ, }, }; @@ -262,14 +279,35 @@ static struct resource msm_iommu_gfx2d1_resources[] = { }, { .name = "nonsecure_irq", - .start = SMMU_GFX2D1_CB_SC_NON_SECURE_IRQ, - .end = SMMU_GFX2D1_CB_SC_NON_SECURE_IRQ, + .start = 243, + .end = 243, + .flags = IORESOURCE_IRQ, + }, + { + .name = "secure_irq", + .start = 242, + .end = 242, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_iommu_vcap_resources[] = { + { + .start = 0x07200000, + .end = 0x07200000 + SZ_1M - 1, + .name = "physbase", + .flags = IORESOURCE_MEM, + }, + { + .name = "nonsecure_irq", + .start = 269, + .end = 269, .flags = IORESOURCE_IRQ, }, { .name = "secure_irq", - .start = SMMU_GFX2D1_CB_SC_SECURE_IRQ, - .end = SMMU_GFX2D1_CB_SC_SECURE_IRQ, + .start = 268, + .end = 268, .flags = IORESOURCE_IRQ, }, }; @@ -327,16 +365,30 @@ static struct msm_iommu_dev vcodec_b_iommu = { static struct msm_iommu_dev gfx3d_iommu = { .name = "gfx3d", .ncb = 3, + .ttbr_split = 2, +}; + +static struct msm_iommu_dev gfx3d1_iommu = { + .name = "gfx3d1", + .ncb = 3, + .ttbr_split = 2, }; static struct msm_iommu_dev gfx2d0_iommu = { .name = "gfx2d0", .ncb = 2, + .ttbr_split = 2, }; static struct msm_iommu_dev gfx2d1_iommu = { .name = "gfx2d1", .ncb = 2, + .ttbr_split = 2, +}; + +static struct msm_iommu_dev vcap_iommu = { + .name = "vcap", + .ncb = 2, }; static struct platform_device msm_device_iommu_jpegd = { @@ -344,6 +396,7 @@ static struct platform_device msm_device_iommu_jpegd = { .id = 0, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &jpegd_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_jpegd_resources), .resource = msm_iommu_jpegd_resources, @@ -354,6 +407,7 @@ static struct platform_device msm_device_iommu_vpe = { .id = 1, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &vpe_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_vpe_resources), .resource = msm_iommu_vpe_resources, @@ -364,6 +418,7 @@ static struct platform_device msm_device_iommu_mdp0 = { .id = 2, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &mdp0_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_mdp0_resources), .resource = msm_iommu_mdp0_resources, @@ -374,6 +429,7 @@ static struct platform_device msm_device_iommu_mdp1 = { .id = 3, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &mdp1_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_mdp1_resources), .resource = msm_iommu_mdp1_resources, @@ -384,6 +440,7 @@ static struct platform_device msm_device_iommu_rot = { .id = 4, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &rot_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_rot_resources), .resource = msm_iommu_rot_resources, @@ -394,6 +451,7 @@ static struct platform_device msm_device_iommu_ijpeg = { .id = 5, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &ijpeg_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_ijpeg_resources), .resource = msm_iommu_ijpeg_resources, @@ -404,6 +462,7 @@ static struct platform_device msm_device_iommu_vfe = { .id = 6, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &vfe_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_vfe_resources), .resource = msm_iommu_vfe_resources, @@ -414,6 +473,7 @@ static struct platform_device msm_device_iommu_vcodec_a = { .id = 7, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &vcodec_a_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_vcodec_a_resources), .resource = msm_iommu_vcodec_a_resources, @@ -424,6 +484,7 @@ static struct platform_device msm_device_iommu_vcodec_b = { .id = 8, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &vcodec_b_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_vcodec_b_resources), .resource = msm_iommu_vcodec_b_resources, @@ -434,31 +495,56 @@ static struct platform_device msm_device_iommu_gfx3d = { .id = 9, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &gfx3d_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_gfx3d_resources), .resource = msm_iommu_gfx3d_resources, }; +static struct platform_device msm_device_iommu_gfx3d1 = { + .name = "msm_iommu", + .id = 10, + .dev = { + .parent = &msm_root_iommu_dev.dev, + .platform_data = &gfx3d1_iommu, + }, + .num_resources = ARRAY_SIZE(msm_iommu_gfx3d1_resources), + .resource = msm_iommu_gfx3d1_resources, +}; + static struct platform_device msm_device_iommu_gfx2d0 = { .name = "msm_iommu", .id = 10, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &gfx2d0_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_gfx2d0_resources), .resource = msm_iommu_gfx2d0_resources, }; -struct platform_device msm_device_iommu_gfx2d1 = { +static struct platform_device msm_device_iommu_gfx2d1 = { .name = "msm_iommu", .id = 11, .dev = { .parent = &msm_root_iommu_dev.dev, + .platform_data = &gfx2d1_iommu, }, .num_resources = ARRAY_SIZE(msm_iommu_gfx2d1_resources), .resource = msm_iommu_gfx2d1_resources, }; +static struct platform_device msm_device_iommu_vcap = { + .name = "msm_iommu", + .id = 11, + .dev = { + .parent = &msm_root_iommu_dev.dev, + .platform_data = &vcap_iommu, + }, + .num_resources = ARRAY_SIZE(msm_iommu_vcap_resources), + .resource = msm_iommu_vcap_resources, +}; + static struct msm_iommu_ctx_dev jpegd_src_ctx = { .name = "jpegd_src", .num = 0, @@ -483,26 +569,26 @@ static struct msm_iommu_ctx_dev vpe_dst_ctx = { .mids = {1, -1} }; -static struct msm_iommu_ctx_dev mdp_vg1_ctx = { - .name = "mdp_vg1", +static struct msm_iommu_ctx_dev mdp_port0_cb0_ctx = { + .name = "mdp_port0_cb0", .num = 0, .mids = {0, 2, -1} }; -static struct msm_iommu_ctx_dev mdp_rgb1_ctx = { - .name = "mdp_rgb1", +static struct msm_iommu_ctx_dev mdp_port0_cb1_ctx = { + .name = "mdp_port0_cb1", .num = 1, .mids = {1, 3, 4, 5, 6, 7, 8, 9, 10, -1} }; -static struct msm_iommu_ctx_dev mdp_vg2_ctx = { - .name = "mdp_vg2", +static struct msm_iommu_ctx_dev mdp_port1_cb0_ctx = { + .name = "mdp_port1_cb0", .num = 0, .mids = {0, 2, -1} }; -static struct msm_iommu_ctx_dev mdp_rgb2_ctx = { - .name = "mdp_rgb2", +static struct msm_iommu_ctx_dev mdp_port1_cb1_ctx = { + .name = "mdp_port1_cb1", .num = 1, .mids = {1, 3, 4, 5, 6, 7, 8, 9, 10, -1} }; @@ -574,6 +660,19 @@ static struct msm_iommu_ctx_dev gfx3d_priv_ctx = { 31, -1} }; +static struct msm_iommu_ctx_dev gfx3d1_user_ctx = { + .name = "gfx3d1_user", + .num = 0, + .mids = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1} +}; + +static struct msm_iommu_ctx_dev gfx3d1_priv_ctx = { + .name = "gfx3d1_priv", + .num = 1, + .mids = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, -1} +}; + static struct msm_iommu_ctx_dev gfx2d0_2d0_ctx = { .name = "gfx2d0_2d0", .num = 0, @@ -586,11 +685,24 @@ static struct msm_iommu_ctx_dev gfx2d1_2d1_ctx = { .mids = {0, 1, 2, 3, 4, 5, 6, 7, -1} }; +static struct msm_iommu_ctx_dev vcap_vc_ctx = { + .name = "vcap_vc", + .num = 0, + .mids = {0, -1} +}; + +static struct msm_iommu_ctx_dev vcap_vp_ctx = { + .name = "vcap_vp", + .num = 1, + .mids = {1, -1} +}; + static struct platform_device msm_device_jpegd_src_ctx = { .name = "msm_iommu_ctx", .id = 0, .dev = { .parent = &msm_device_iommu_jpegd.dev, + .platform_data = &jpegd_src_ctx, }, }; @@ -599,6 +711,7 @@ static struct platform_device msm_device_jpegd_dst_ctx = { .id = 1, .dev = { .parent = &msm_device_iommu_jpegd.dev, + .platform_data = &jpegd_dst_ctx, }, }; @@ -607,6 +720,7 @@ static struct platform_device msm_device_vpe_src_ctx = { .id = 2, .dev = { .parent = &msm_device_iommu_vpe.dev, + .platform_data = &vpe_src_ctx, }, }; @@ -615,38 +729,43 @@ static struct platform_device msm_device_vpe_dst_ctx = { .id = 3, .dev = { .parent = &msm_device_iommu_vpe.dev, + .platform_data = &vpe_dst_ctx, }, }; -static struct platform_device msm_device_mdp_vg1_ctx = { +static struct platform_device msm_device_mdp_port0_cb0_ctx = { .name = "msm_iommu_ctx", .id = 4, .dev = { .parent = &msm_device_iommu_mdp0.dev, + .platform_data = &mdp_port0_cb0_ctx, }, }; -static struct platform_device msm_device_mdp_rgb1_ctx = { +static struct platform_device msm_device_mdp_port0_cb1_ctx = { .name = "msm_iommu_ctx", .id = 5, .dev = { .parent = &msm_device_iommu_mdp0.dev, + .platform_data = &mdp_port0_cb1_ctx, }, }; -static struct platform_device msm_device_mdp_vg2_ctx = { +static struct platform_device msm_device_mdp_port1_cb0_ctx = { .name = "msm_iommu_ctx", .id = 6, .dev = { .parent = &msm_device_iommu_mdp1.dev, + .platform_data = &mdp_port1_cb0_ctx, }, }; -static struct platform_device msm_device_mdp_rgb2_ctx = { +static struct platform_device msm_device_mdp_port1_cb1_ctx = { .name = "msm_iommu_ctx", .id = 7, .dev = { .parent = &msm_device_iommu_mdp1.dev, + .platform_data = &mdp_port1_cb1_ctx, }, }; @@ -655,6 +774,7 @@ static struct platform_device msm_device_rot_src_ctx = { .id = 8, .dev = { .parent = &msm_device_iommu_rot.dev, + .platform_data = &rot_src_ctx, }, }; @@ -663,6 +783,7 @@ static struct platform_device msm_device_rot_dst_ctx = { .id = 9, .dev = { .parent = &msm_device_iommu_rot.dev, + .platform_data = &rot_dst_ctx, }, }; @@ -671,6 +792,7 @@ static struct platform_device msm_device_ijpeg_src_ctx = { .id = 10, .dev = { .parent = &msm_device_iommu_ijpeg.dev, + .platform_data = &ijpeg_src_ctx, }, }; @@ -679,6 +801,7 @@ static struct platform_device msm_device_ijpeg_dst_ctx = { .id = 11, .dev = { .parent = &msm_device_iommu_ijpeg.dev, + .platform_data = &ijpeg_dst_ctx, }, }; @@ -687,6 +810,7 @@ static struct platform_device msm_device_vfe_imgwr_ctx = { .id = 12, .dev = { .parent = &msm_device_iommu_vfe.dev, + .platform_data = &vfe_imgwr_ctx, }, }; @@ -695,6 +819,7 @@ static struct platform_device msm_device_vfe_misc_ctx = { .id = 13, .dev = { .parent = &msm_device_iommu_vfe.dev, + .platform_data = &vfe_misc_ctx, }, }; @@ -703,6 +828,7 @@ static struct platform_device msm_device_vcodec_a_stream_ctx = { .id = 14, .dev = { .parent = &msm_device_iommu_vcodec_a.dev, + .platform_data = &vcodec_a_stream_ctx, }, }; @@ -711,6 +837,7 @@ static struct platform_device msm_device_vcodec_a_mm1_ctx = { .id = 15, .dev = { .parent = &msm_device_iommu_vcodec_a.dev, + .platform_data = &vcodec_a_mm1_ctx, }, }; @@ -719,6 +846,7 @@ static struct platform_device msm_device_vcodec_b_mm2_ctx = { .id = 16, .dev = { .parent = &msm_device_iommu_vcodec_b.dev, + .platform_data = &vcodec_b_mm2_ctx, }, }; @@ -727,6 +855,7 @@ static struct platform_device msm_device_gfx3d_user_ctx = { .id = 17, .dev = { .parent = &msm_device_iommu_gfx3d.dev, + .platform_data = &gfx3d_user_ctx, }, }; @@ -735,6 +864,25 @@ static struct platform_device msm_device_gfx3d_priv_ctx = { .id = 18, .dev = { .parent = &msm_device_iommu_gfx3d.dev, + .platform_data = &gfx3d_priv_ctx, + }, +}; + +static struct platform_device msm_device_gfx3d1_user_ctx = { + .name = "msm_iommu_ctx", + .id = 19, + .dev = { + .parent = &msm_device_iommu_gfx3d1.dev, + .platform_data = &gfx3d1_user_ctx, + }, +}; + +static struct platform_device msm_device_gfx3d1_priv_ctx = { + .name = "msm_iommu_ctx", + .id = 20, + .dev = { + .parent = &msm_device_iommu_gfx3d1.dev, + .platform_data = &gfx3d1_priv_ctx, }, }; @@ -743,6 +891,7 @@ static struct platform_device msm_device_gfx2d0_2d0_ctx = { .id = 19, .dev = { .parent = &msm_device_iommu_gfx2d0.dev, + .platform_data = &gfx2d0_2d0_ctx, }, }; @@ -751,11 +900,29 @@ static struct platform_device msm_device_gfx2d1_2d1_ctx = { .id = 20, .dev = { .parent = &msm_device_iommu_gfx2d1.dev, + .platform_data = &gfx2d1_2d1_ctx, }, }; -static struct platform_device *msm_iommu_devs[] = { - &msm_device_iommu_jpegd, +static struct platform_device msm_device_vcap_vc_ctx = { + .name = "msm_iommu_ctx", + .id = 21, + .dev = { + .parent = &msm_device_iommu_vcap.dev, + .platform_data = &vcap_vc_ctx, + }, +}; + +static struct platform_device msm_device_vcap_vp_ctx = { + .name = "msm_iommu_ctx", + .id = 22, + .dev = { + .parent = &msm_device_iommu_vcap.dev, + .platform_data = &vcap_vp_ctx, + }, +}; + +static struct platform_device *msm_iommu_common_devs[] = { &msm_device_iommu_vpe, &msm_device_iommu_mdp0, &msm_device_iommu_mdp1, @@ -765,34 +932,29 @@ static struct platform_device *msm_iommu_devs[] = { &msm_device_iommu_vcodec_a, &msm_device_iommu_vcodec_b, &msm_device_iommu_gfx3d, +}; + +static struct platform_device *msm_iommu_gfx2d_devs[] = { &msm_device_iommu_gfx2d0, &msm_device_iommu_gfx2d1, }; -static struct msm_iommu_dev *msm_iommu_data[] = { - &jpegd_iommu, - &vpe_iommu, - &mdp0_iommu, - &mdp1_iommu, - &rot_iommu, - &ijpeg_iommu, - &vfe_iommu, - &vcodec_a_iommu, - &vcodec_b_iommu, - &gfx3d_iommu, - &gfx2d0_iommu, - &gfx2d1_iommu, +static struct platform_device *msm_iommu_8064_devs[] = { + &msm_device_iommu_gfx3d1, + &msm_device_iommu_vcap, }; -static struct platform_device *msm_iommu_ctx_devs[] = { - &msm_device_jpegd_src_ctx, - &msm_device_jpegd_dst_ctx, +static struct platform_device *msm_iommu_jpegd_devs[] = { + &msm_device_iommu_jpegd, +}; + +static struct platform_device *msm_iommu_common_ctx_devs[] = { &msm_device_vpe_src_ctx, &msm_device_vpe_dst_ctx, - &msm_device_mdp_vg1_ctx, - &msm_device_mdp_rgb1_ctx, - &msm_device_mdp_vg2_ctx, - &msm_device_mdp_rgb2_ctx, + &msm_device_mdp_port0_cb0_ctx, + &msm_device_mdp_port0_cb1_ctx, + &msm_device_mdp_port1_cb0_ctx, + &msm_device_mdp_port1_cb1_ctx, &msm_device_rot_src_ctx, &msm_device_rot_dst_ctx, &msm_device_ijpeg_src_ctx, @@ -804,37 +966,32 @@ static struct platform_device *msm_iommu_ctx_devs[] = { &msm_device_vcodec_b_mm2_ctx, &msm_device_gfx3d_user_ctx, &msm_device_gfx3d_priv_ctx, +}; + +static struct platform_device *msm_iommu_gfx2d_ctx_devs[] = { &msm_device_gfx2d0_2d0_ctx, &msm_device_gfx2d1_2d1_ctx, }; -static struct msm_iommu_ctx_dev *msm_iommu_ctx_data[] = { - &jpegd_src_ctx, - &jpegd_dst_ctx, - &vpe_src_ctx, - &vpe_dst_ctx, - &mdp_vg1_ctx, - &mdp_rgb1_ctx, - &mdp_vg2_ctx, - &mdp_rgb2_ctx, - &rot_src_ctx, - &rot_dst_ctx, - &ijpeg_src_ctx, - &ijpeg_dst_ctx, - &vfe_imgwr_ctx, - &vfe_misc_ctx, - &vcodec_a_stream_ctx, - &vcodec_a_mm1_ctx, - &vcodec_b_mm2_ctx, - &gfx3d_user_ctx, - &gfx3d_priv_ctx, - &gfx2d0_2d0_ctx, - &gfx2d1_2d1_ctx, -}; - -static int __init msm8x60_iommu_init(void) +static struct platform_device *msm_iommu_8064_ctx_devs[] = { + &msm_device_gfx3d1_user_ctx, + &msm_device_gfx3d1_priv_ctx, + &msm_device_vcap_vc_ctx, + &msm_device_vcap_vp_ctx, +}; + +static struct platform_device *msm_iommu_jpegd_ctx_devs[] = { + &msm_device_jpegd_src_ctx, + &msm_device_jpegd_dst_ctx, +}; + +static int __init iommu_init(void) { - int ret, i; + int ret; + if (!msm_soc_version_supports_iommu_v1()) { + pr_err("IOMMU v1 is not supported on this SoC version.\n"); + return -ENODEV; + } ret = platform_device_register(&msm_root_iommu_dev); if (ret != 0) { @@ -842,71 +999,95 @@ static int __init msm8x60_iommu_init(void) goto failure; } - for (i = 0; i < ARRAY_SIZE(msm_iommu_devs); i++) { - ret = platform_device_add_data(msm_iommu_devs[i], - msm_iommu_data[i], - sizeof(struct msm_iommu_dev)); - if (ret != 0) { - pr_err("platform_device_add_data failed, " - "i = %d\n", i); - goto failure_unwind; - } - - ret = platform_device_register(msm_iommu_devs[i]); - - if (ret != 0) { - pr_err("platform_device_register iommu failed, " - "i = %d\n", i); - goto failure_unwind; - } + /* Initialize common devs */ + platform_add_devices(msm_iommu_common_devs, + ARRAY_SIZE(msm_iommu_common_devs)); + + /* Initialize soc-specific devs */ + if (cpu_is_msm8x60() || cpu_is_msm8960()) { + platform_add_devices(msm_iommu_jpegd_devs, + ARRAY_SIZE(msm_iommu_jpegd_devs)); + platform_add_devices(msm_iommu_gfx2d_devs, + ARRAY_SIZE(msm_iommu_gfx2d_devs)); } - for (i = 0; i < ARRAY_SIZE(msm_iommu_ctx_devs); i++) { - ret = platform_device_add_data(msm_iommu_ctx_devs[i], - msm_iommu_ctx_data[i], - sizeof(*msm_iommu_ctx_devs[i])); - if (ret != 0) { - pr_err("platform_device_add_data iommu failed, " - "i = %d\n", i); - goto failure_unwind2; - } - - ret = platform_device_register(msm_iommu_ctx_devs[i]); - if (ret != 0) { - pr_err("platform_device_register ctx failed, " - "i = %d\n", i); - goto failure_unwind2; - } + if (cpu_is_apq8064()) { + platform_add_devices(msm_iommu_jpegd_devs, + ARRAY_SIZE(msm_iommu_jpegd_devs)); + platform_add_devices(msm_iommu_8064_devs, + ARRAY_SIZE(msm_iommu_8064_devs)); } - return 0; -failure_unwind2: - while (--i >= 0) - platform_device_unregister(msm_iommu_ctx_devs[i]); -failure_unwind: - while (--i >= 0) - platform_device_unregister(msm_iommu_devs[i]); + /* Initialize common ctx_devs */ + ret = platform_add_devices(msm_iommu_common_ctx_devs, + ARRAY_SIZE(msm_iommu_common_ctx_devs)); + + /* Initialize soc-specific ctx_devs */ + if (cpu_is_msm8x60() || cpu_is_msm8960()) { + platform_add_devices(msm_iommu_jpegd_ctx_devs, + ARRAY_SIZE(msm_iommu_jpegd_ctx_devs)); + platform_add_devices(msm_iommu_gfx2d_ctx_devs, + ARRAY_SIZE(msm_iommu_gfx2d_ctx_devs)); + } + + if (cpu_is_apq8064()) { + platform_add_devices(msm_iommu_jpegd_ctx_devs, + ARRAY_SIZE(msm_iommu_jpegd_ctx_devs)); + platform_add_devices(msm_iommu_8064_ctx_devs, + ARRAY_SIZE(msm_iommu_8064_ctx_devs)); + } + + return 0; - platform_device_unregister(&msm_root_iommu_dev); failure: return ret; } -static void __exit msm8x60_iommu_exit(void) +static void __exit iommu_exit(void) { int i; - for (i = 0; i < ARRAY_SIZE(msm_iommu_ctx_devs); i++) - platform_device_unregister(msm_iommu_ctx_devs[i]); + /* Common ctx_devs */ + for (i = 0; i < ARRAY_SIZE(msm_iommu_common_ctx_devs); i++) + platform_device_unregister(msm_iommu_common_ctx_devs[i]); + + /* Common devs. */ + for (i = 0; i < ARRAY_SIZE(msm_iommu_common_devs); ++i) + platform_device_unregister(msm_iommu_common_devs[i]); + + if (cpu_is_msm8x60() || cpu_is_msm8960()) { + for (i = 0; i < ARRAY_SIZE(msm_iommu_gfx2d_ctx_devs); i++) + platform_device_unregister(msm_iommu_gfx2d_ctx_devs[i]); + + for (i = 0; i < ARRAY_SIZE(msm_iommu_jpegd_ctx_devs); i++) + platform_device_unregister(msm_iommu_jpegd_ctx_devs[i]); - for (i = 0; i < ARRAY_SIZE(msm_iommu_devs); ++i) - platform_device_unregister(msm_iommu_devs[i]); + for (i = 0; i < ARRAY_SIZE(msm_iommu_gfx2d_devs); i++) + platform_device_unregister(msm_iommu_gfx2d_devs[i]); + + for (i = 0; i < ARRAY_SIZE(msm_iommu_jpegd_devs); i++) + platform_device_unregister(msm_iommu_jpegd_devs[i]); + } + + if (cpu_is_apq8064()) { + for (i = 0; i < ARRAY_SIZE(msm_iommu_8064_ctx_devs); i++) + platform_device_unregister(msm_iommu_8064_ctx_devs[i]); + + for (i = 0; i < ARRAY_SIZE(msm_iommu_jpegd_ctx_devs); i++) + platform_device_unregister(msm_iommu_jpegd_ctx_devs[i]); + + for (i = 0; i < ARRAY_SIZE(msm_iommu_8064_devs); i++) + platform_device_unregister(msm_iommu_8064_devs[i]); + + for (i = 0; i < ARRAY_SIZE(msm_iommu_jpegd_devs); i++) + platform_device_unregister(msm_iommu_jpegd_devs[i]); + } platform_device_unregister(&msm_root_iommu_dev); } -subsys_initcall(msm8x60_iommu_init); -module_exit(msm8x60_iommu_exit); +subsys_initcall(iommu_init); +module_exit(iommu_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Stepan Moskovchenko "); diff --git a/arch/arm/mach-msm/devices-msm7x00.c b/arch/arm/mach-msm/devices-msm7x00.c index 993780f490ada952cf54f80aa184c7ed53c68985..54ed40133c553d103d55f7fb74b04decde18b3c7 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 #include "devices.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, @@ -302,8 +414,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; @@ -394,48 +505,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 0000000000000000000000000000000000000000..1b9eb8614fe61efea642e546df9a6a761f50beeb --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x01a.c @@ -0,0 +1,776 @@ +/* + * 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, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xA9700000, + .end = 0xA9700000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 3, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +#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 0000000000000000000000000000000000000000..2be7d5ec6719edcc3d688c8a341da4eedf01f247 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x25.c @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2012, 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 "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, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xA9700000, + .end = 0xA9700000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 3, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +#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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, + .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("core_clk", adm_clk.c, "msm_dmov"), + 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("core_clk", gp_clk.c, NULL), + CLK_LOOKUP("core_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("mem_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("core_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("iface_clk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("core_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("iface_clk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("core_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("iface_clk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("core_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("iface_clk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("core_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("core_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("core_clk", uart3_clk.c, "msm_serial.2"), + CLK_LOOKUP("core_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("core_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_otg"), + CLK_LOOKUP("alt_core_clk", usb_hs_clk.c, "msm_hsusb_peripheral"), + CLK_LOOKUP("iface_clk", usb_hs_p_clk.c, "msm_hsusb_peripheral"), + CLK_LOOKUP("alt_core_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 0000000000000000000000000000000000000000..ffd10fad83fc182321b93132c2ac456077216fd6 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x27.c @@ -0,0 +1,900 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2012, 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 "devices.h" +#include "footswitch.h" + +#include + +#include +#include +#include +#include +#include "irq.h" +#include "pm.h" + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM7XXX_UART1_PHYS, + .end = MSM7XXX_UART1_PHYS + MSM7XXX_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart2[] = { + { + .start = INT_UART2, + .end = INT_UART2, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM7XXX_UART2_PHYS, + .end = MSM7XXX_UART2_PHYS + MSM7XXX_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, +}; + +static struct resource resources_adsp[] = { + { + .start = INT_ADSP_A9_A11, + .end = INT_ADSP_A9_A11, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_adsp_device = { + .name = "msm_adsp", + .id = -1, + .num_resources = ARRAY_SIZE(resources_adsp), + .resource = resources_adsp, +}; + +#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); +} + +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, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xA9700000, + .end = 0xA9700000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 3, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +static struct msm_pm_irq_calls msm7x27_pm_irq_calls = { + .irq_pending = msm_irq_pending, + .idle_sleep_allowed = msm_irq_idle_sleep_allowed, + .enter_sleep1 = msm_irq_enter_sleep1, + .enter_sleep2 = msm_irq_enter_sleep2, + .exit_sleep1 = msm_irq_exit_sleep1, + .exit_sleep2 = msm_irq_exit_sleep2, + .exit_sleep3 = msm_irq_exit_sleep3, +}; + +void __init msm_pm_register_irqs(void) +{ + msm_pm_set_irq_extns(&msm7x27_pm_irq_calls); +} + +#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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, + .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 = { + /* 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, + .strtstp_sleepwake = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE | KGSL_CLK_MEM, +}; + +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, "vdd", "kgsl-3d0.0"), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); + +static struct resource gpio_resources[] = { + { + .start = INT_GPIO_GROUP1, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_GPIO_GROUP2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_device_gpio = { + .name = "msmgpio", + .id = -1, + .resource = gpio_resources, + .num_resources = ARRAY_SIZE(gpio_resources), +}; + +static int __init msm7627_init_gpio(void) +{ + platform_device_register(&msm_device_gpio); + return 0; +} + +postcore_initcall(msm7627_init_gpio); diff --git a/arch/arm/mach-msm/devices-msm7x27a.c b/arch/arm/mach-msm/devices-msm7x27a.c new file mode 100644 index 0000000000000000000000000000000000000000..2d79f623cbfff1377f48b0fee08c2ab7c5d6e165 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x27a.c @@ -0,0 +1,1728 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "devices.h" +#include "devices-msm7x2xa.h" +#include "footswitch.h" +#include "acpuclock.h" +#include "spm.h" +#include "mpm-8625.h" +#include "irq.h" +#include "pm.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, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xA9700000, + .end = 0xA9700000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 3, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +static struct resource smd_8625_resource[] = { + { + .name = "a9_m2a_0", + .start = MSM8625_INT_A9_M2A_0, + .flags = IORESOURCE_IRQ, + }, + { + .name = "a9_m2a_5", + .start = MSM8625_INT_A9_M2A_5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smd_subsystem_config smd_8625_config_list[] = { + { + .irq_config_id = SMD_MODEM, + .subsys_name = "modem", + .edge = SMD_APPS_MODEM, + + .smd_int.irq_name = "a9_m2a_0", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + + .smd_int.out_bit_pos = 1, + .smd_int.out_base = (void __iomem *)MSM_CSR_BASE, + .smd_int.out_offset = 0x400 + (0) * 4, + + .smsm_int.irq_name = "a9_m2a_5", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smsm_dev", + .smsm_int.dev_id = 0, + + .smsm_int.out_bit_pos = 1, + .smsm_int.out_base = (void __iomem *)MSM_CSR_BASE, + .smsm_int.out_offset = 0x400 + (5) * 4, + + } +}; + +static struct smd_platform smd_8625_platform_data = { + .num_ss_configs = ARRAY_SIZE(smd_8625_config_list), + .smd_ss_configs = smd_8625_config_list, +}; + +struct platform_device msm8625_device_smd = { + .name = "msm_smd", + .id = -1, + .resource = smd_8625_resource, + .num_resources = ARRAY_SIZE(smd_8625_resource), + .dev = { + .platform_data = &smd_8625_platform_data, + } +}; + +static struct resource resources_adsp[] = { + { + .start = INT_ADSP_A9_A11, + .end = INT_ADSP_A9_A11, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_adsp_device = { + .name = "msm_adsp", + .id = -1, + .num_resources = ARRAY_SIZE(resources_adsp), + .resource = resources_adsp, +}; + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM7XXX_UART1_PHYS, + .end = MSM7XXX_UART1_PHYS + MSM7XXX_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, + }, +}; + +static struct msm_pm_irq_calls msm7x27a_pm_irq_calls = { + .irq_pending = msm_irq_pending, + .idle_sleep_allowed = msm_irq_idle_sleep_allowed, + .enter_sleep1 = msm_irq_enter_sleep1, + .enter_sleep2 = msm_irq_enter_sleep2, + .exit_sleep1 = msm_irq_exit_sleep1, + .exit_sleep2 = msm_irq_exit_sleep2, + .exit_sleep3 = msm_irq_exit_sleep3, +}; + +static struct msm_pm_irq_calls msm8625_pm_irq_calls = { + .irq_pending = msm_gic_spi_ppi_pending, + .idle_sleep_allowed = msm_gic_irq_idle_sleep_allowed, + .enter_sleep1 = msm_gic_irq_enter_sleep1, + .enter_sleep2 = msm_gic_irq_enter_sleep2, + .exit_sleep1 = msm_gic_irq_exit_sleep1, + .exit_sleep2 = msm_gic_irq_exit_sleep2, + .exit_sleep3 = msm_gic_irq_exit_sleep3, +}; + +void __init msm_pm_register_irqs(void) +{ + if (cpu_is_msm8625()) + msm_pm_set_irq_extns(&msm8625_pm_irq_calls); + else + msm_pm_set_irq_extns(&msm7x27a_pm_irq_calls); + +} + +#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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, + .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, +}; + +#ifdef CONFIG_MSM_CAMERA_V4L2 +static int apps_reset; +static struct resource msm_csic0_resources[] = { + { + .name = "csic", + .start = 0xA0F00000, + .end = 0xA0F00000 + 0x00100000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = INT_CSI_IRQ_0, + .end = INT_CSI_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_csic1_resources[] = { + { + .name = "csic", + .start = 0xA1000000, + .end = 0xA1000000 + 0x00100000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = INT_CSI_IRQ_1, + .end = INT_CSI_IRQ_1, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm7x27a_device_csic0 = { + .name = "msm_csic", + .id = 0, + .resource = msm_csic0_resources, + .num_resources = ARRAY_SIZE(msm_csic0_resources), +}; + +struct platform_device msm7x27a_device_csic1 = { + .name = "msm_csic", + .id = 1, + .resource = msm_csic1_resources, + .num_resources = ARRAY_SIZE(msm_csic1_resources), +}; + +static struct resource msm_clkctl_resources[] = { + { + .name = "clk_ctl", + .start = MSM7XXX_CLK_CTL_PHYS, + .end = MSM7XXX_CLK_CTL_PHYS + MSM7XXX_CLK_CTL_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; +struct platform_device msm7x27a_device_clkctl = { + .name = "msm_clk_ctl", + .id = 0, + .resource = msm_clkctl_resources, + .num_resources = ARRAY_SIZE(msm_clkctl_resources), + .dev = { + .platform_data = &apps_reset, + }, +}; + +struct platform_device msm7x27a_device_vfe = { + .name = "msm_vfe", + .id = 0, +}; + +#endif + +/* Command sequence for simple WFI */ +static uint8_t spm_wfi_cmd_sequence[] __initdata = { + 0x04, 0x03, 0x04, 0x0f, +}; + +/* Command sequence for GDFS, this won't send any interrupt to the modem */ +static uint8_t spm_pc_without_modem[] __initdata = { + 0x20, 0x00, 0x30, 0x10, + 0x03, 0x1e, 0x0e, 0x3e, + 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, + 0x2E, 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_pc_without_modem, + }, +}; + +static struct msm_spm_platform_data msm_spm_data[] __initdata = { + [0] = { + .reg_base_addr = MSM_SAW0_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x0, + .reg_init_values[MSM_SPM_REG_SAW2_SPM_CTL] = 0x01, + .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_CFG] = 0x0, + .reg_init_values[MSM_SPM_REG_SAW2_SPM_CTL] = 0x01, + .num_modes = ARRAY_SIZE(msm_spm_seq_list), + .modes = msm_spm_seq_list, + }, +}; + +void __init msm8x25_spm_device_init(void) +{ + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); +} + +#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 = { + .pwrlevel = { + { + .gpu_freq = 245760000, + .bus_freq = 200000000, + }, + { + .gpu_freq = 192000000, + .bus_freq = 160000000, + }, + { + .gpu_freq = 133330000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 3, + .set_grp_async = set_grp_xbar_async, + .idle_timeout = HZ, + .strtstp_sleepwake = true, + .nap_allowed = false, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE | KGSL_CLK_MEM, +}; + +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() || cpu_is_msm7x25ab()) { + kgsl_3d0_pdata.num_levels = 2; + kgsl_3d0_pdata.pwrlevel[0].gpu_freq = 133330000; + kgsl_3d0_pdata.pwrlevel[0].bus_freq = 160000000; + kgsl_3d0_pdata.pwrlevel[1].gpu_freq = 96000000; + kgsl_3d0_pdata.pwrlevel[1].bus_freq = 0; + } +} + +void __init msm8x25_kgsl_3d0_init(void) +{ + if (cpu_is_msm8625()) { + kgsl_3d0_pdata.idle_timeout = HZ/5; + kgsl_3d0_pdata.strtstp_sleepwake = false; + /* 8x25 supports a higher GPU frequency */ + kgsl_3d0_pdata.pwrlevel[0].gpu_freq = 300000000; + kgsl_3d0_pdata.pwrlevel[0].bus_freq = 200000000; + } +} + +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); +} + + +#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, + }, +}; + +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, +}; + +static struct resource gpio_resources[] = { + { + .start = INT_GPIO_GROUP1, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_GPIO_GROUP2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_device_gpio = { + .name = "msmgpio", + .id = -1, + .resource = gpio_resources, + .num_resources = ARRAY_SIZE(gpio_resources), +}; + +struct platform_device *msm_footswitch_devices[] = { + FS_PCOM(FS_GFX3D, "vdd", "kgsl-3d0.0"), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); + +/* MSM8625 Devices */ + +static struct resource msm8625_resources_uart1[] = { + { + .start = MSM8625_INT_UART1, + .end = MSM8625_INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM7XXX_UART1_PHYS, + .end = MSM7XXX_UART1_PHYS + MSM7XXX_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8625_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(msm8625_resources_uart1), + .resource = msm8625_resources_uart1, +}; + +static struct resource msm8625_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_UART1DM_IRQ, + .end = MSM8625_INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM8625_INT_UART1DM_RX, + .end = MSM8625_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, + }, +}; + +struct platform_device msm8625_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm8625_uart1_dm_resources), + .resource = msm8625_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm8625_uart2dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_UART2DM_IRQ, + .end = MSM8625_INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_device_uart_dm2 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(msm8625_uart2dm_resources), + .resource = msm8625_uart2dm_resources, +}; + +static struct resource msm8625_resources_adsp[] = { + { + .start = MSM8625_INT_ADSP_A9_A11, + .end = MSM8625_INT_ADSP_A9_A11, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_device_adsp = { + .name = "msm_adsp", + .id = -1, + .num_resources = ARRAY_SIZE(msm8625_resources_adsp), + .resource = msm8625_resources_adsp, +}; + +static struct resource msm8625_dmov_resource[] = { + { + .start = MSM8625_INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xA9700000, + .end = 0xA9700000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8625_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm8625_dmov_resource, + .num_resources = ARRAY_SIZE(msm8625_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +static struct resource gsbi0_msm8625_qup_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 = MSM8625_INT_PWB_I2C, + .end = MSM8625_INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +/* Use GSBI0 QUP for /dev/i2c-0 */ +struct platform_device msm8625_gsbi0_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI0_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi0_msm8625_qup_resources), + .resource = gsbi0_msm8625_qup_resources, +}; + +static struct resource gsbi1_msm8625_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 = MSM8625_INT_ARM11_DMA, + .end = MSM8625_INT_ARM11_DMA, + .flags = IORESOURCE_IRQ, + }, +}; + +/* Use GSBI1 QUP for /dev/i2c-1 */ +struct platform_device msm8625_gsbi1_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI1_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi1_qup_i2c_resources), + .resource = gsbi1_msm8625_qup_i2c_resources, +}; + +static struct resource msm8625_gpio_resources[] = { + { + .start = MSM8625_INT_GPIO_GROUP1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM8625_INT_GPIO_GROUP2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm8625_device_gpio = { + .name = "msmgpio", + .id = -1, + .resource = msm8625_gpio_resources, + .num_resources = ARRAY_SIZE(msm8625_gpio_resources), +}; + +static struct resource msm8625_resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_SDC1_0, + .end = MSM8625_INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, + .flags = IORESOURCE_DMA, + } +}; + +static struct resource msm8625_resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_SDC2_0, + .end = MSM8625_INT_SDC2_1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, + .flags = IORESOURCE_DMA, + } +}; + +static struct resource msm8625_resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_SDC3_0, + .end = MSM8625_INT_SDC3_1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource msm8625_resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_SDC4_0, + .end = MSM8625_INT_SDC4_1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm8625_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(msm8625_resources_sdc1), + .resource = msm8625_resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm8625_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(msm8625_resources_sdc2), + .resource = msm8625_resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm8625_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(msm8625_resources_sdc3), + .resource = msm8625_resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm8625_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(msm8625_resources_sdc4), + .resource = msm8625_resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm8625_sdcc_devices[] __initdata = { + &msm8625_device_sdc1, + &msm8625_device_sdc2, + &msm8625_device_sdc3, + &msm8625_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; + + if (cpu_is_msm8625()) + pdev = msm8625_sdcc_devices[controller-1]; + else + pdev = msm_sdcc_devices[controller-1]; + + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +static struct resource msm8625_resources_hsusb_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_USB_HS, + .end = MSM8625_INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(msm8625_resources_hsusb_otg), + .resource = msm8625_resources_hsusb_otg, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource msm8625_resources_gadget_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_USB_HS, + .end = MSM8625_INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(msm8625_resources_gadget_peripheral), + .resource = msm8625_resources_gadget_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource msm8625_resources_hsusb_host[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_USB_HS, + .end = MSM8625_INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = 0, + .num_resources = ARRAY_SIZE(msm8625_resources_hsusb_host), + .resource = msm8625_resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm8625_host_devices[] = { + &msm8625_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + if (cpu_is_msm8625()) + pdev = msm8625_host_devices[host]; + else + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#ifdef CONFIG_MSM_CAMERA_V4L2 +static struct resource msm8625_csic0_resources[] = { + { + .name = "csic", + .start = 0xA0F00000, + .end = 0xA0F00000 + 0x00100000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = MSM8625_INT_CSI_IRQ_0, + .end = MSM8625_INT_CSI_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm8625_csic1_resources[] = { + { + .name = "csic", + .start = 0xA1000000, + .end = 0xA1000000 + 0x00100000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = MSM8625_INT_CSI_IRQ_1, + .end = MSM8625_INT_CSI_IRQ_1, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_device_csic0 = { + .name = "msm_csic", + .id = 0, + .resource = msm8625_csic0_resources, + .num_resources = ARRAY_SIZE(msm8625_csic0_resources), +}; + +struct platform_device msm8625_device_csic1 = { + .name = "msm_csic", + .id = 1, + .resource = msm8625_csic1_resources, + .num_resources = ARRAY_SIZE(msm8625_csic1_resources), +}; +#endif + +static struct resource msm8625_mipi_dsi_resources[] = { + { + .name = "mipi_dsi", + .start = MIPI_DSI_HW_BASE, + .end = MIPI_DSI_HW_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_DSI_IRQ, + .end = MSM8625_INT_DSI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm8625_mipi_dsi_device = { + .name = "mipi_dsi", + .id = 1, + .num_resources = ARRAY_SIZE(msm8625_mipi_dsi_resources), + .resource = msm8625_mipi_dsi_resources, +}; + +static struct resource msm8625_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F1008 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MSM8625_INT_MDP, + .end = MSM8625_INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm8625_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm8625_mdp_resources), + .resource = msm8625_mdp_resources, +}; + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) { + if (cpu_is_msm8625()) + msm_register_device(&msm8625_mdp_device, data); + else + msm_register_device(&msm_mdp_device, data); + } else if (!strncmp(name, "mipi_dsi", 8)) { + if (cpu_is_msm8625()) + msm_register_device(&msm8625_mipi_dsi_device, data); + else + 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); + } +} + +static struct resource msm8625_kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0xA0000000, + .end = 0xA001ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = MSM8625_INT_GRAPHICS, + .end = MSM8625_INT_GRAPHICS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8625_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(msm8625_kgsl_3d0_resources), + .resource = msm8625_kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; + +static struct clk_lookup msm_clock_8625_dummy[] = { + CLK_DUMMY("core_clk", adm_clk.c, "msm_dmov", 0), + CLK_DUMMY("adsp_clk", adsp_clk.c, NULL, 0), + CLK_DUMMY("ahb_m_clk", ahb_m_clk.c, NULL, 0), + CLK_DUMMY("ahb_s_clk", ahb_s_clk.c, NULL, 0), + CLK_DUMMY("cam_m_clk", cam_m_clk.c, NULL, 0), + CLK_DUMMY("csi_clk", csi1_clk.c, NULL, 0), + CLK_DUMMY("csi_pclk", csi1_p_clk.c, NULL, 0), + CLK_DUMMY("csi_vfe_clk", csi1_vfe_clk.c, NULL, 0), + CLK_DUMMY("dsi_byte_clk", dsi_byte_clk.c, NULL, 0), + CLK_DUMMY("dsi_clk", dsi_clk.c, NULL, 0), + CLK_DUMMY("dsi_esc_clk", dsi_esc_clk.c, NULL, 0), + CLK_DUMMY("dsi_pixel_clk", dsi_pixel_clk.c, NULL, 0), + CLK_DUMMY("dsi_ref_clk", dsi_ref_clk.c, NULL, 0), + CLK_DUMMY("ebi1_clk", ebi1_clk.c, NULL, 0), + CLK_DUMMY("ebi2_clk", ebi2_clk.c, NULL, 0), + CLK_DUMMY("ecodec_clk", ecodec_clk.c, NULL, 0), + CLK_DUMMY("gp_clk", gp_clk.c, NULL, 0), + CLK_DUMMY("core_clk", gsbi1_qup_clk.c, "qup_i2c.0", 0), + CLK_DUMMY("core_clk", gsbi2_qup_clk.c, "qup_i2c.1", 0), + CLK_DUMMY("iface_clk", gsbi1_qup_p_clk.c, "qup_i2c.0", 0), + CLK_DUMMY("iface_clk", gsbi2_qup_p_clk.c, "qup_i2c.1", 0), + CLK_DUMMY("icodec_rx_clk", icodec_rx_clk.c, NULL, 0), + CLK_DUMMY("icodec_tx_clk", icodec_tx_clk.c, NULL, 0), + CLK_DUMMY("mem_clk", imem_clk.c, NULL, 0), + CLK_DUMMY("mddi_clk", pmdh_clk.c, NULL, 0), + CLK_DUMMY("mdp_clk", mdp_clk.c, NULL, 0), + CLK_DUMMY("mdp_lcdc_pclk_clk", mdp_lcdc_pclk_clk.c, NULL, 0), + CLK_DUMMY("mdp_lcdc_pad_pclk_clk", mdp_lcdc_pad_pclk_clk.c, NULL, 0), + CLK_DUMMY("mdp_vsync_clk", mdp_vsync_clk.c, NULL, 0), + CLK_DUMMY("mdp_dsi_pclk", mdp_dsi_p_clk.c, NULL, 0), + CLK_DUMMY("pbus_clk", pbus_clk.c, NULL, 0), + CLK_DUMMY("pcm_clk", pcm_clk.c, NULL, 0), + CLK_DUMMY("sdac_clk", sdac_clk.c, NULL, 0), + CLK_DUMMY("core_clk", sdc1_clk.c, "msm_sdcc.1", 0), + CLK_DUMMY("iface_clk", sdc1_p_clk.c, "msm_sdcc.1", 0), + CLK_DUMMY("core_clk", sdc2_clk.c, "msm_sdcc.2", 0), + CLK_DUMMY("iface_clk", sdc2_p_clk.c, "msm_sdcc.2", 0), + CLK_DUMMY("core_clk", sdc3_clk.c, "msm_sdcc.3", 0), + CLK_DUMMY("iface_clk", sdc3_p_clk.c, "msm_sdcc.3", 0), + CLK_DUMMY("core_clk", sdc4_clk.c, "msm_sdcc.4", 0), + CLK_DUMMY("iface_clk", sdc4_p_clk.c, "msm_sdcc.4", 0), + CLK_DUMMY("ref_clk", tsif_ref_clk.c, "msm_tsif.0", 0), + CLK_DUMMY("iface_clk", tsif_p_clk.c, "msm_tsif.0", 0), + CLK_DUMMY("core_clk", uart1_clk.c, "msm_serial.0", 0), + CLK_DUMMY("core_clk", uart2_clk.c, "msm_serial.1", 0), + CLK_DUMMY("core_clk", uart1dm_clk.c, "msm_serial_hs.0", 0), + CLK_DUMMY("core_clk", uart2dm_clk.c, "msm_serial_hsl.0", 0), + CLK_DUMMY("usb_hs_core_clk", usb_hs_core_clk.c, NULL, 0), + CLK_DUMMY("usb_hs2_clk", usb_hs2_clk.c, NULL, 0), + CLK_DUMMY("usb_hs_clk", usb_hs_clk.c, NULL, 0), + CLK_DUMMY("usb_hs_pclk", usb_hs_p_clk.c, NULL, 0), + CLK_DUMMY("usb_phy_clk", usb_phy_clk.c, NULL, 0), + CLK_DUMMY("vdc_clk", vdc_clk.c, NULL, 0), + CLK_DUMMY("ebi1_acpu_clk", ebi_acpu_clk.c, NULL, 0), + CLK_DUMMY("ebi1_lcdc_clk", ebi_lcdc_clk.c, NULL, 0), + CLK_DUMMY("ebi1_mddi_clk", ebi_mddi_clk.c, NULL, 0), + CLK_DUMMY("ebi1_usb_clk", ebi_usb_clk.c, NULL, 0), + CLK_DUMMY("ebi1_vfe_clk", ebi_vfe_clk.c, NULL, 0), + CLK_DUMMY("mem_clk", ebi_adm_clk.c, "msm_dmov", 0), +}; + +struct clock_init_data msm8625_dummy_clock_init_data __initdata = { + .table = msm_clock_8625_dummy, + .size = ARRAY_SIZE(msm_clock_8625_dummy), +}; + +enum { + MSM8625, + MSM8625A, +}; + +static int __init msm8625_cpu_id(void) +{ + int raw_id, cpu; + + raw_id = socinfo_get_raw_id(); + switch (raw_id) { + /* Part number for 1GHz part */ + case 0x770: + case 0x771: + case 0x780: + cpu = MSM8625; + break; + /* Part number for 1.2GHz part */ + case 0x773: + case 0x774: + case 0x781: + cpu = MSM8625A; + break; + default: + pr_err("Invalid Raw ID\n"); + return -ENODEV; + } + return cpu; +} + +int __init msm7x2x_misc_init(void) +{ + if (machine_is_msm8625_rumi3()) { + msm_clock_init(&msm8625_dummy_clock_init_data); + return 0; + } + + msm_clock_init(&msm7x27a_clock_init_data); + if (cpu_is_msm7x27aa() || cpu_is_msm7x25ab()) + acpuclk_init(&acpuclk_7x27aa_soc_data); + else if (cpu_is_msm8625()) { + if (msm8625_cpu_id() == MSM8625) + acpuclk_init(&acpuclk_7x27aa_soc_data); + else if (msm8625_cpu_id() == MSM8625A) + acpuclk_init(&acpuclk_8625_soc_data); + } else { + acpuclk_init(&acpuclk_7x27a_soc_data); + } + + + return 0; +} + +#ifdef CONFIG_CACHE_L2X0 +static int __init msm7x27x_cache_init(void) +{ + int aux_ctrl = 0; + int pctrl = 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); + + if (cpu_is_msm8625()) { + /* Way Size 011(0x3) 64KB */ + aux_ctrl |= (0x3 << L2X0_AUX_CTRL_WAY_SIZE_SHIFT) | \ + (0x1 << L2X0_AUX_CTRL_DATA_PREFETCH_SHIFT) | \ + (0X1 << L2X0_AUX_CTRL_INSTR_PREFETCH_SHIFT) | \ + (0x1 << L2X0_AUX_CTRL_L2_FORCE_NWA_SHIFT); + + /* Write Prefetch Control settings */ + pctrl = readl_relaxed(MSM_L2CC_BASE + L2X0_PREFETCH_CTRL); + pctrl |= (0x3 << L2X0_PREFETCH_CTRL_OFFSET_SHIFT) | \ + (0x1 << L2X0_PREFETCH_CTRL_WRAP8_INC_SHIFT) | \ + (0x1 << L2X0_PREFETCH_CTRL_WRAP8_SHIFT); + writel_relaxed(pctrl , MSM_L2CC_BASE + L2X0_PREFETCH_CTRL); + } + + l2x0_init(MSM_L2CC_BASE, aux_ctrl, L2X0_AUX_CTRL_MASK); + if (cpu_is_msm8625()) { + pctrl = readl_relaxed(MSM_L2CC_BASE + L2X0_PREFETCH_CTRL); + pr_info("Prfetch Ctrl: 0x%08x\n", pctrl); + } + + return 0; +} +#else +static int __init msm7x27x_cache_init(void){ return 0; } +#endif + +void __init msm_common_io_init(void) +{ + msm_map_common_io(); + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed!\n", __func__); + msm7x27x_cache_init(); +} + +void __init msm8625_init_irq(void) +{ + msm_gic_irq_extn_init(MSM_QGIC_DIST_BASE, MSM_QGIC_CPU_BASE); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); +} + +void __init msm8625_map_io(void) +{ + msm_map_msm8625_io(); + + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed!\n", __func__); + msm7x27x_cache_init(); +} + +static int msm7627a_init_gpio(void) +{ + if (cpu_is_msm8625()) + platform_device_register(&msm8625_device_gpio); + else + platform_device_register(&msm_device_gpio); + return 0; +} +postcore_initcall(msm7627a_init_gpio); + +static int msm7627a_panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + flush_cache_all(); + outer_flush_all(); + return NOTIFY_DONE; +} + +static struct notifier_block panic_handler = { + .notifier_call = msm7627a_panic_handler, +}; + +static int __init panic_register(void) +{ + atomic_notifier_chain_register(&panic_notifier_list, + &panic_handler); + return 0; +} +module_init(panic_register); diff --git a/arch/arm/mach-msm/devices-msm7x2xa.h b/arch/arm/mach-msm/devices-msm7x2xa.h new file mode 100644 index 0000000000000000000000000000000000000000..4184a86e08ce98be2afde3ad90c1369a8663cd1a --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x2xa.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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); +extern struct platform_device msm7x27a_device_vfe; +extern struct platform_device msm7x27a_device_csic0; +extern struct platform_device msm7x27a_device_csic1; +extern struct platform_device msm7x27a_device_clkctl; + +extern struct platform_device msm8625_device_csic0; +extern struct platform_device msm8625_device_csic1; + +void __init msm8625_init_irq(void); +void __init msm8625_map_io(void); +int ar600x_wlan_power(bool on); +void __init msm8x25_spm_device_init(void); +void __init msm8x25_kgsl_3d0_init(void); +void __iomem *core1_reset_base(void); +#endif diff --git a/arch/arm/mach-msm/devices-msm7x30.c b/arch/arm/mach-msm/devices-msm7x30.c index 09b4f14038246f6004aa4e9ef5d2721baba285c8..00768a3bcdf0a918ef89a3b83019aa59cd030630 100644 --- a/arch/arm/mach-msm/devices-msm7x30.c +++ b/arch/arm/mach-msm/devices-msm7x30.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2008 Google, Inc. - * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2012, 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,23 +15,93 @@ #include #include - +#include #include -#include +#include +#include +#include +#include #include #include #include #include +#include #include "devices.h" -#include "smd_private.h" +#include "footswitch.h" #include -#include "clock-pcom.h" -#include "clock-7x30.h" +#include +#include +#ifdef CONFIG_PMIC8058 +#include +#endif +#include +#include +#include "pm.h" +#include "irq.h" + +/* EBI THERMAL DRIVER */ +static struct resource msm_ebi0_thermal_resources[] = { + { + .start = 0xA8600000, + .end = 0xA86005FF, + .name = "physbase", + .flags = IORESOURCE_MEM + } +}; + +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_adsp[] = { +{ + .start = INT_ADSP_A9_A11, + .end = INT_ADSP_A9_A11, + .flags = IORESOURCE_IRQ, +}, +}; + +struct platform_device msm_adsp_device = { + .name = "msm_adsp", + .id = -1, + .num_resources = ARRAY_SIZE(resources_adsp), + .resource = resources_adsp, +}; -#include +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM7X30_UART1_PHYS, + .end = MSM7X30_UART1_PHYS + MSM7X30_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; static struct resource resources_uart2[] = { { @@ -40,13 +110,33 @@ static struct resource resources_uart2[] = { .flags = IORESOURCE_IRQ, }, { - .start = MSM_UART2_PHYS, - .end = MSM_UART2_PHYS + MSM_UART2_SIZE - 1, + .start = MSM7X30_UART2_PHYS, + .end = MSM7X30_UART2_PHYS + MSM7X30_UART2_SIZE - 1, .flags = IORESOURCE_MEM, .name = "uart_resource" }, }; +static struct resource resources_uart3[] = { + { + .start = INT_UART3, + .end = INT_UART3, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM7X30_UART3_PHYS, + .end = MSM7X30_UART3_PHYS + MSM7X30_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 +144,303 @@ 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, +}; + +#ifdef CONFIG_MSM_CAMERA_V4L2 +static struct resource msm_csic_resources[] = { + { + .name = "csic", + .start = 0xA6100000, + .end = 0xA6100000 + 0x00000400 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = INT_CSI, + .end = INT_CSI, + .flags = IORESOURCE_IRQ, + }, +}; + +struct resource msm_vfe_resources[] = { + { + .name = "msm_vfe", + .start = 0xA6000000, + .end = 0xA6000000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "msm_vfe", + .start = INT_VFE, + .end = INT_VFE, + .flags = IORESOURCE_IRQ, + }, + { + .name = "msm_camif", + .start = 0xAB000000, + .end = 0xAB000000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_vpe_resources[] = { + { + .name = "vpe", + .start = 0xAD200000, + .end = 0xAD200000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "vpe", + .start = INT_VPE, + .end = INT_VPE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_csic0 = { + .name = "msm_csic", + .id = 0, + .resource = msm_csic_resources, + .num_resources = ARRAY_SIZE(msm_csic_resources), +}; + +struct platform_device msm_device_vfe = { + .name = "msm_vfe", + .id = 0, + .resource = msm_vfe_resources, + .num_resources = ARRAY_SIZE(msm_vfe_resources), +}; + +struct platform_device msm_device_vpe = { + .name = "msm_vpe", + .id = 0, + .resource = msm_vpe_resources, + .num_resources = ARRAY_SIZE(msm_vpe_resources), +}; +#endif + +#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_MSM_SSBI +#define MSM_SSBI_PMIC1_PHYS 0xAD900000 +static struct resource msm_ssbi_pmic1_resources[] = { + { + .start = MSM_SSBI_PMIC1_PHYS, + .end = MSM_SSBI_PMIC1_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi_pmic1 = { + .name = "msm_ssbi", + .id = 0, + .resource = msm_ssbi_pmic1_resources, + .num_resources = ARRAY_SIZE(msm_ssbi_pmic1_resources), +}; +#endif + +#ifdef CONFIG_I2C_SSBI +#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 +450,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 +488,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 +525,856 @@ 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 = PMIC8058_IRQ_BASE + PM8058_CHGVAL_IRQ, + .end = PMIC8058_IRQ_BASE + PM8058_CHGVAL_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 = 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, + }, +}; + +static struct msm_pm_irq_calls msm7x30_pm_irq_calls = { + .irq_pending = msm_irq_pending, + .idle_sleep_allowed = msm_irq_idle_sleep_allowed, + .enter_sleep1 = msm_irq_enter_sleep1, + .enter_sleep2 = msm_irq_enter_sleep2, + .exit_sleep1 = msm_irq_exit_sleep1, + .exit_sleep2 = msm_irq_exit_sleep2, + .exit_sleep3 = msm_irq_exit_sleep3, +}; + +void __init msm_pm_register_irqs(void) +{ + msm_pm_set_irq_extns(&msm7x30_pm_irq_calls); +} + +static struct resource smd_resource[] = { + { + .name = "a9_m2a_0", + .start = INT_A9_M2A_0, + .flags = IORESOURCE_IRQ, + }, + { + .name = "a9_m2a_5", + .start = INT_A9_M2A_5, + .flags = IORESOURCE_IRQ, + }, + { + .name = "adsp_a11_smsm", + .start = INT_ADSP_A11, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smd_subsystem_config smd_config_list[] = { + { + .irq_config_id = SMD_MODEM, + .subsys_name = "modem", + .edge = SMD_APPS_MODEM, + + .smd_int.irq_name = "a9_m2a_0", + .smd_int.flags = IRQF_TRIGGER_RISING, + .smd_int.irq_id = -1, + .smd_int.device_name = "smd_dev", + .smd_int.dev_id = 0, + + .smd_int.out_bit_pos = 1 << 0, + .smd_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smd_int.out_offset = 0x8, + + .smsm_int.irq_name = "a9_m2a_5", + .smsm_int.flags = IRQF_TRIGGER_RISING, + .smsm_int.irq_id = -1, + .smsm_int.device_name = "smd_dev", + .smsm_int.dev_id = 0, + + .smsm_int.out_bit_pos = 1 << 5, + .smsm_int.out_base = (void __iomem *)MSM_APCS_GCC_BASE, + .smsm_int.out_offset = 0x8, + + } +}; + +static struct smd_platform smd_platform_data = { + .num_ss_configs = ARRAY_SIZE(smd_config_list), + .smd_ss_configs = smd_config_list, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, + .resource = smd_resource, + .num_resources = ARRAY_SIZE(smd_resource), + .dev = { + .platform_data = &smd_platform_data, + } + +}; + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xAC400000, + .end = 0xAC400000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 2, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +#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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, + .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, + }, + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, + .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, + .enable_ion = 0, + .disable_dmx = 0, + .cont_mode_dpb_count = 8 +}; + +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 = "core_clk", + .clk_type = ROTATOR_CORE_CLK, + .clk_rate = 0, + }, + { + .clk_name = "iface_clk", + .clk_type = ROTATOR_PCLK, + .clk_rate = 0, + }, + { + .clk_name = "mem_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, +}; + +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 = { + .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, + .idle_needed = true, + .clk_map = KGSL_CLK_SRC | KGSL_CLK_CORE | + KGSL_CLK_IFACE | KGSL_CLK_MEM, +}; + +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 = { + .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, + .idle_needed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE, +}; + +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, "vdd", "kgsl-2d0.0"), + FS_PCOM(FS_GFX3D, "vdd", "kgsl-3d0.0"), + FS_PCOM(FS_MDP, "vdd", "mdp.0"), + FS_PCOM(FS_MFC, "fs_mfc", NULL), + FS_PCOM(FS_ROT, "vdd", "msm_rotator.0"), + FS_PCOM(FS_VFE, "fs_vfe", NULL), + FS_PCOM(FS_VPE, "fs_vpe", NULL), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); + +static struct resource gpio_resources[] = { + { + .start = INT_GPIO_GROUP1, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_GPIO_GROUP2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_device_gpio = { + .name = "msmgpio", + .id = -1, + .resource = gpio_resources, + .num_resources = ARRAY_SIZE(gpio_resources), +}; + +static int __init msm7630_init_gpio(void) +{ + platform_device_register(&msm_device_gpio); + return 0; +} +postcore_initcall(msm7630_init_gpio); diff --git a/arch/arm/mach-msm/devices-msm8960.c b/arch/arm/mach-msm/devices-msm8960.c deleted file mode 100644 index d9e1f26475deb2a229d112a307b1cf6e4838b60b..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..d622af24acb49c6a2b444828f8451cfd57b50c4b --- /dev/null +++ b/arch/arm/mach-msm/devices-msm8x60.c @@ -0,0 +1,2934 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 +#include "rpm_log.h" +#include "rpm_stats.h" +#include +#include "msm_watchdog.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); + 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) +{ + struct msm_mpm_device_data *data = NULL; + +#ifdef CONFIG_MSM_MPM + data = &msm8660_mpm_dev_data; +#endif + + msm_mpm_irq_extn_init(data); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, (void *)MSM_QGIC_CPU_BASE); +} + +#define MSM_LPASS_QDSP6SS_PHYS 0x28800000 + +static struct resource msm_8660_q6_resources[] = { + { + .start = MSM_LPASS_QDSP6SS_PHYS, + .end = MSM_LPASS_QDSP6SS_PHYS + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_pil_q6v3 = { + .name = "pil_qdsp6v3", + .id = -1, + .num_resources = ARRAY_SIZE(msm_8660_q6_resources), + .resource = msm_8660_q6_resources, +}; + +#define MSM_MSS_REGS_PHYS 0x10200000 + +static struct resource msm_8660_modem_resources[] = { + { + .start = MSM_MSS_REGS_PHYS, + .end = MSM_MSS_REGS_PHYS + SZ_256 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_pil_modem = { + .name = "pil_modem", + .id = -1, + .num_resources = ARRAY_SIZE(msm_8660_modem_resources), + .resource = msm_8660_modem_resources, +}; + +struct platform_device msm_pil_tzapps = { + .name = "pil_tzapps", + .id = -1, +}; + +struct platform_device msm_pil_dsps = { + .name = "pil_dsps", + .id = -1, + .dev.platform_data = "dsps", +}; + +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, + .line = 1, +}; + +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_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(990), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(1300), + }, +}; + +static struct msm_bus_vectors grp3d_nominal_high_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2008), + }, +}; + +static struct msm_bus_vectors grp3d_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = KGSL_CONVERT_TO_MBPS(2484), + }, +}; + +static struct msm_bus_paths grp3d_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp3d_init_vectors), + grp3d_init_vectors, + }, + { + ARRAY_SIZE(grp3d_low_vectors), + grp3d_low_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 = KGSL_CONVERT_TO_MBPS(990), + }, +}; + +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 = KGSL_CONVERT_TO_MBPS(990), + }, +}; + +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 = { + .pwrlevel = { + { + .gpu_freq = 266667000, + .bus_freq = 4, + .io_fraction = 0, + }, + { + .gpu_freq = 228571000, + .bus_freq = 3, + .io_fraction = 33, + }, + { + .gpu_freq = 200000000, + .bus_freq = 2, + .io_fraction = 100, + }, + { + .gpu_freq = 177778000, + .bus_freq = 1, + .io_fraction = 100, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 5, + .set_grp_async = NULL, + .idle_timeout = HZ/5, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE | KGSL_CLK_MEM_IFACE, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp3d_bus_scale_pdata, +#endif +}; + +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 = { + .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, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE, +#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 = { + .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, + .nap_allowed = true, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_IFACE, +#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_map = 0; + } +} + +/* 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_MSM_SSBI +#define MSM_SSBI_PMIC1_PHYS 0x00500000 +static struct resource resources_ssbi_pmic1_resource[] = { + { + .start = MSM_SSBI_PMIC1_PHYS, + .end = MSM_SSBI_PMIC1_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi_pmic1 = { + .name = "msm_ssbi", + .id = 0, + .resource = resources_ssbi_pmic1_resource, + .num_resources = ARRAY_SIZE(resources_ssbi_pmic1_resource), +}; + +#define MSM_SSBI2_PMIC2B_PHYS 0x00C00000 +static struct resource resources_ssbi_pmic2_resource[] = { + { + .start = MSM_SSBI2_PMIC2B_PHYS, + .end = MSM_SSBI2_PMIC2B_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi_pmic2 = { + .name = "msm_ssbi", + .id = 1, + .resource = resources_ssbi_pmic2_resource, + .num_resources = ARRAY_SIZE(resources_ssbi_pmic2_resource), +}; +#endif + +#ifdef CONFIG_I2C_SSBI +/* 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_miso", + .start = 34, + .end = 34, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_mosi", + .start = 33, + .end = 33, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_cs", + .start = 35, + .end = 35, + .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 + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, + .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 + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, + .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 + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, + .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 + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, + .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 + { + .name = "sdcc_dma_chnl", + .start = DMOV_SDC5_CHAN, + .end = DMOV_SDC5_CHAN, + .flags = IORESOURCE_DMA, + }, + { + .name = "sdcc_dma_crci", + .start = DMOV_SDC5_CRCI, + .end = DMOV_SDC5_CRCI, + .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); +} + +#ifdef CONFIG_MSM_CAMERA_V4L2 +static struct resource msm_csic0_resources[] = { + { + .name = "csic", + .start = 0x04800000, + .end = 0x04800000 + 0x00000400 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = CSI_0_IRQ, + .end = CSI_0_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_csic1_resources[] = { + { + .name = "csic", + .start = 0x04900000, + .end = 0x04900000 + 0x00000400 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csic", + .start = CSI_1_IRQ, + .end = CSI_1_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct resource msm_vfe_resources[] = { + { + .name = "msm_vfe", + .start = 0x04500000, + .end = 0x04500000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "msm_vfe", + .start = VFE_IRQ, + .end = VFE_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_vpe_resources[] = { + { + .name = "vpe", + .start = 0x05300000, + .end = 0x05300000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "vpe", + .start = INT_VPE, + .end = INT_VPE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_csic0 = { + .name = "msm_csic", + .id = 0, + .resource = msm_csic0_resources, + .num_resources = ARRAY_SIZE(msm_csic0_resources), +}; + +struct platform_device msm_device_csic1 = { + .name = "msm_csic", + .id = 1, + .resource = msm_csic1_resources, + .num_resources = ARRAY_SIZE(msm_csic1_resources), +}; + +struct platform_device msm_device_vfe = { + .name = "msm_vfe", + .id = 0, + .resource = msm_vfe_resources, + .num_resources = ARRAY_SIZE(msm_vfe_resources), +}; + +struct platform_device msm_device_vpe = { + .name = "msm_vpe", + .id = 0, + .resource = msm_vpe_resources, + .num_resources = ARRAY_SIZE(msm_vpe_resources), +}; + +#endif + + +#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 = "core_clk", + .clk_type = ROTATOR_CORE_CLK, + .clk_rate = 160 * 1000 * 1000, + }, + { + .clk_name = "iface_clk", + .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, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &rotator_bus_scale_pdata, +#endif + +}; + +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) + +#define TCSR_GSBI_IRQ_MUX_SEL 0x0044 + +#define GSBI_IRQ_MUX_SEL_MASK 0xF +#define GSBI_IRQ_MUX_SEL_DSPS 0xB + +static void dsps_init1(struct msm_dsps_platform_data *data) +{ + int val; + + /* route GSBI12 interrutps to DSPS */ + val = secure_readl(MSM_TCSR_BASE + TCSR_GSBI_IRQ_MUX_SEL); + val &= ~GSBI_IRQ_MUX_SEL_MASK; + val |= GSBI_IRQ_MUX_SEL_DSPS; + secure_writel(val, MSM_TCSR_BASE + TCSR_GSBI_IRQ_MUX_SEL); +} + +static struct dsps_clk_info dsps_clks[] = { + { + .name = "iface_clk", + .rate = 0, /* no rate just on/off */ + }, + { + .name = "mem_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), + .init = dsps_init1, + .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 = "iface_clk", + .tsif_ref_clk = "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 = "iface_clk", + .tsif_ref_clk = "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, +}; + +static struct msm_watchdog_pdata msm_watchdog_pdata = { + .pet_time = 10000, + .bark_time = 11000, + .has_secure = true, +}; + +struct platform_device msm8660_device_watchdog = { + .name = "msm_watchdog", + .id = -1, + .dev = { + .platform_data = &msm_watchdog_pdata, + }, +}; + +static struct resource msm_dmov_resource_adm0[] = { + { + .start = INT_ADM0_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0x18320000, + .end = 0x18320000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_dmov_resource_adm1[] = { + { + .start = INT_ADM1_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0x18420000, + .end = 0x18420000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata_adm0 = { + .sd = 1, + .sd_size = 0x800, +}; + +static struct msm_dmov_pdata msm_dmov_pdata_adm1 = { + .sd = 1, + .sd_size = 0x800, +}; + +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), + .dev = { + .platform_data = &msm_dmov_pdata_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), + .dev = { + .platform_data = &msm_dmov_pdata_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 +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + .memtype = ION_CP_MM_HEAP_ID, + .enable_ion = 1, + .cp_enabled = 0, +#else + .memtype = MEMTYPE_SMI_KERNEL, + .enable_ion = 0, +#endif + .disable_dmx = 0, + .disable_fullhd = 0, + .cont_mode_dpb_count = 8 +}; + +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_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 */ +}; + +struct platform_device msm8660_rpm_log_device = { + .name = "msm_rpm_log", + .id = -1, + .dev = { + .platform_data = &msm_rpm_log_pdata, + }, +}; +#endif + +#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 msm8660_rpm_stat_device = { + .name = "msm_rpm_stat", + .id = -1, + .dev = { + .platform_data = &msm_rpm_stat_pdata, + }, +}; +#endif + +#define SHARED_IMEM_TZ_BASE 0x2a05f720 +static struct resource tzlog_resources[] = { + { + .start = SHARED_IMEM_TZ_BASE, + .end = SHARED_IMEM_TZ_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_tz_log = { + .name = "tz_log", + .id = 0, + .num_resources = ARRAY_SIZE(tzlog_resources), + .resource = tzlog_resources, +}; + +#ifdef CONFIG_MSM_MPM +static uint16_t msm_mpm_irqs_m2a[MSM_MPM_NR_MPM_IRQS] __initdata = { + [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[] __initdata = { + 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 msm8660_mpm_dev_data __initdata = { + .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 + +#ifdef CONFIG_SND_SOC_MSM8660_APQ +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 = PRIMARY_I2S_RX, +}; + +struct platform_device msm_cpudai1 = { + .name = "msm-dai-q6", + .id = PRIMARY_I2S_TX, +}; + +struct platform_device msm_cpudai_hdmi_rx = { + .name = "msm-dai-q6", + .id = HDMI_RX, +}; + +struct platform_device msm_cpudai_bt_rx = { + .name = "msm-dai-q6", + .id = INT_BT_SCO_RX, +}; + +struct platform_device msm_cpudai_bt_tx = { + .name = "msm-dai-q6", + .id = INT_BT_SCO_TX, +}; + +struct platform_device msm_cpudai_fm_rx = { + .name = "msm-dai-q6", + .id = INT_FM_RX, +}; + +struct platform_device msm_cpudai_fm_tx = { + .name = "msm-dai-q6", + .id = INT_FM_TX, +}; + +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, +}; +#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 + +static struct fs_driver_data gfx2d0_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, +}; + +static struct fs_driver_data gfx2d1_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, +}; + +static struct fs_driver_data gfx3d_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk", .reset_rate = 27000000 }, + { .name = "iface_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_GRAPHICS_3D, +}; + +static struct fs_driver_data ijpeg_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_JPEG_ENC, +}; + +static struct fs_driver_data mdp_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { .name = "vsync_clk" }, + { .name = "tv_src_clk" }, + { .name = "tv_clk" }, + { .name = "pixel_mdp_clk" }, + { .name = "pixel_lcdc_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_MDP_PORT0, + .bus_port1 = MSM_BUS_MASTER_MDP_PORT1, +}; + +static struct fs_driver_data rot_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_ROTATOR, +}; + +static struct fs_driver_data ved_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_HD_CODEC_PORT0, + .bus_port1 = MSM_BUS_MASTER_HD_CODEC_PORT1, +}; + +static struct fs_driver_data vfe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VFE, +}; + +static struct fs_driver_data vpe_fs_data = { + .clks = (struct fs_clk_data[]){ + { .name = "core_clk" }, + { .name = "iface_clk" }, + { .name = "bus_clk" }, + { 0 } + }, + .bus_port0 = MSM_BUS_MASTER_VPE, +}; + +struct platform_device *msm8660_footswitch[] __initdata = { + FS_8X60(FS_IJPEG, "vdd", "msm_gemini.0", &ijpeg_fs_data), + FS_8X60(FS_MDP, "vdd", "mdp.0", &mdp_fs_data), + FS_8X60(FS_ROT, "vdd", "msm_rotator.0", &rot_fs_data), + FS_8X60(FS_VED, "vdd", "msm_vidc.0", &ved_fs_data), + FS_8X60(FS_VFE, "fs_vfe", NULL, &vfe_fs_data), + FS_8X60(FS_VPE, "fs_vpe", NULL, &vpe_fs_data), + FS_8X60(FS_GFX3D, "vdd", "kgsl-3d0.0", &gfx3d_fs_data), + FS_8X60(FS_GFX2D0, "vdd", "kgsl-2d0.0", &gfx2d0_fs_data), + FS_8X60(FS_GFX2D1, "vdd", "kgsl-2d1.1", &gfx2d1_fs_data), +}; +unsigned msm8660_num_footswitch __initdata = ARRAY_SIZE(msm8660_footswitch); + +struct msm_rpm_platform_data msm8660_rpm_data __initdata = { + .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_wakeup = RPM_SCSS_CPU0_WAKE_UP_IRQ, + .ipc_rpm_reg = MSM_GCC_BASE + 0x008, + .ipc_rpm_val = 4, + .target_id = { + MSM_RPM_MAP(8660, NOTIFICATION_CONFIGURED_0, NOTIFICATION, 8), + MSM_RPM_MAP(8660, NOTIFICATION_REGISTERED_0, NOTIFICATION, 8), + MSM_RPM_MAP(8660, INVALIDATE_0, INVALIDATE, 8), + MSM_RPM_MAP(8660, TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8660, TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + MSM_RPM_MAP(8660, TRIGGER_SET_FROM, TRIGGER_SET, 1), + MSM_RPM_MAP(8660, TRIGGER_SET_TO, TRIGGER_SET, 1), + MSM_RPM_MAP(8660, TRIGGER_SET_TRIGGER, TRIGGER_SET, 1), + MSM_RPM_MAP(8660, TRIGGER_CLEAR_FROM, TRIGGER_CLEAR, 1), + MSM_RPM_MAP(8660, TRIGGER_CLEAR_TO, TRIGGER_CLEAR, 1), + MSM_RPM_MAP(8660, TRIGGER_CLEAR_TRIGGER, TRIGGER_CLEAR, 1), + + MSM_RPM_MAP(8660, CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(8660, PXO_CLK, PXO_CLK, 1), + MSM_RPM_MAP(8660, PLL_4, PLL_4, 1), + MSM_RPM_MAP(8660, APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1), + MSM_RPM_MAP(8660, SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(8660, MM_FABRIC_CLK, MM_FABRIC_CLK, 1), + MSM_RPM_MAP(8660, DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(8660, SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(8660, CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(8660, MMFPB_CLK, MMFPB_CLK, 1), + MSM_RPM_MAP(8660, SMI_CLK, SMI_CLK, 1), + MSM_RPM_MAP(8660, EBI1_CLK, EBI1_CLK, 1), + + MSM_RPM_MAP(8660, APPS_L2_CACHE_CTL, APPS_L2_CACHE_CTL, 1), + + MSM_RPM_MAP(8660, APPS_FABRIC_HALT_0, APPS_FABRIC_HALT, 2), + MSM_RPM_MAP(8660, APPS_FABRIC_CLOCK_MODE_0, + APPS_FABRIC_CLOCK_MODE, 3), + MSM_RPM_MAP(8660, APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 6), + + MSM_RPM_MAP(8660, SYSTEM_FABRIC_HALT_0, SYSTEM_FABRIC_HALT, 2), + MSM_RPM_MAP(8660, SYSTEM_FABRIC_CLOCK_MODE_0, + SYSTEM_FABRIC_CLOCK_MODE, 3), + MSM_RPM_MAP(8660, SYSTEM_FABRIC_ARB_0, SYSTEM_FABRIC_ARB, 22), + + MSM_RPM_MAP(8660, MM_FABRIC_HALT_0, MM_FABRIC_HALT, 2), + MSM_RPM_MAP(8660, MM_FABRIC_CLOCK_MODE_0, + MM_FABRIC_CLOCK_MODE, 3), + MSM_RPM_MAP(8660, MM_FABRIC_ARB_0, MM_FABRIC_ARB, 23), + + MSM_RPM_MAP(8660, SMPS0B_0, SMPS0B, 2), + MSM_RPM_MAP(8660, SMPS1B_0, SMPS1B, 2), + MSM_RPM_MAP(8660, SMPS2B_0, SMPS2B, 2), + MSM_RPM_MAP(8660, SMPS3B_0, SMPS3B, 2), + MSM_RPM_MAP(8660, SMPS4B_0, SMPS4B, 2), + MSM_RPM_MAP(8660, LDO0B_0, LDO0B, 2), + MSM_RPM_MAP(8660, LDO1B_0, LDO1B, 2), + MSM_RPM_MAP(8660, LDO2B_0, LDO2B, 2), + MSM_RPM_MAP(8660, LDO3B_0, LDO3B, 2), + MSM_RPM_MAP(8660, LDO4B_0, LDO4B, 2), + MSM_RPM_MAP(8660, LDO5B_0, LDO5B, 2), + MSM_RPM_MAP(8660, LDO6B_0, LDO6B, 2), + MSM_RPM_MAP(8660, LVS0B, LVS0B, 1), + MSM_RPM_MAP(8660, LVS1B, LVS1B, 1), + MSM_RPM_MAP(8660, LVS2B, LVS2B, 1), + MSM_RPM_MAP(8660, LVS3B, LVS3B, 1), + MSM_RPM_MAP(8660, MVS, MVS, 1), + + MSM_RPM_MAP(8660, SMPS0_0, SMPS0, 2), + MSM_RPM_MAP(8660, SMPS1_0, SMPS1, 2), + MSM_RPM_MAP(8660, SMPS2_0, SMPS2, 2), + MSM_RPM_MAP(8660, SMPS3_0, SMPS3, 2), + MSM_RPM_MAP(8660, SMPS4_0, SMPS4, 2), + MSM_RPM_MAP(8660, LDO0_0, LDO0, 2), + MSM_RPM_MAP(8660, LDO1_0, LDO1, 2), + MSM_RPM_MAP(8660, LDO2_0, LDO2, 2), + MSM_RPM_MAP(8660, LDO3_0, LDO3, 2), + MSM_RPM_MAP(8660, LDO4_0, LDO4, 2), + MSM_RPM_MAP(8660, LDO5_0, LDO5, 2), + MSM_RPM_MAP(8660, LDO6_0, LDO6, 2), + MSM_RPM_MAP(8660, LDO7_0, LDO7, 2), + MSM_RPM_MAP(8660, LDO8_0, LDO8, 2), + MSM_RPM_MAP(8660, LDO9_0, LDO9, 2), + MSM_RPM_MAP(8660, LDO10_0, LDO10, 2), + MSM_RPM_MAP(8660, LDO11_0, LDO11, 2), + MSM_RPM_MAP(8660, LDO12_0, LDO12, 2), + MSM_RPM_MAP(8660, LDO13_0, LDO13, 2), + MSM_RPM_MAP(8660, LDO14_0, LDO14, 2), + MSM_RPM_MAP(8660, LDO15_0, LDO15, 2), + MSM_RPM_MAP(8660, LDO16_0, LDO16, 2), + MSM_RPM_MAP(8660, LDO17_0, LDO17, 2), + MSM_RPM_MAP(8660, LDO18_0, LDO18, 2), + MSM_RPM_MAP(8660, LDO19_0, LDO19, 2), + MSM_RPM_MAP(8660, LDO20_0, LDO20, 2), + MSM_RPM_MAP(8660, LDO21_0, LDO21, 2), + MSM_RPM_MAP(8660, LDO22_0, LDO22, 2), + MSM_RPM_MAP(8660, LDO23_0, LDO23, 2), + MSM_RPM_MAP(8660, LDO24_0, LDO24, 2), + MSM_RPM_MAP(8660, LDO25_0, LDO25, 2), + MSM_RPM_MAP(8660, LVS0, LVS0, 1), + MSM_RPM_MAP(8660, LVS1, LVS1, 1), + MSM_RPM_MAP(8660, NCP_0, NCP, 2), + MSM_RPM_MAP(8660, CXO_BUFFERS, CXO_BUFFERS, 1), + }, + .target_status = { + MSM_RPM_STATUS_ID_MAP(8660, VERSION_MAJOR), + MSM_RPM_STATUS_ID_MAP(8660, VERSION_MINOR), + MSM_RPM_STATUS_ID_MAP(8660, VERSION_BUILD), + MSM_RPM_STATUS_ID_MAP(8660, SUPPORTED_RESOURCES_0), + MSM_RPM_STATUS_ID_MAP(8660, SUPPORTED_RESOURCES_1), + MSM_RPM_STATUS_ID_MAP(8660, SUPPORTED_RESOURCES_2), + MSM_RPM_STATUS_ID_MAP(8660, SEQUENCE), + + MSM_RPM_STATUS_ID_MAP(8660, CXO_CLK), + MSM_RPM_STATUS_ID_MAP(8660, PXO_CLK), + MSM_RPM_STATUS_ID_MAP(8660, PLL_4), + MSM_RPM_STATUS_ID_MAP(8660, APPS_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8660, SYSTEM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8660, MM_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8660, DAYTONA_FABRIC_CLK), + MSM_RPM_STATUS_ID_MAP(8660, SFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8660, CFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8660, MMFPB_CLK), + MSM_RPM_STATUS_ID_MAP(8660, SMI_CLK), + MSM_RPM_STATUS_ID_MAP(8660, EBI1_CLK), + + MSM_RPM_STATUS_ID_MAP(8660, APPS_L2_CACHE_CTL), + + MSM_RPM_STATUS_ID_MAP(8660, APPS_FABRIC_HALT), + MSM_RPM_STATUS_ID_MAP(8660, APPS_FABRIC_CLOCK_MODE), + MSM_RPM_STATUS_ID_MAP(8660, APPS_FABRIC_ARB), + + MSM_RPM_STATUS_ID_MAP(8660, SYSTEM_FABRIC_HALT), + MSM_RPM_STATUS_ID_MAP(8660, SYSTEM_FABRIC_CLOCK_MODE), + MSM_RPM_STATUS_ID_MAP(8660, SYSTEM_FABRIC_ARB), + + MSM_RPM_STATUS_ID_MAP(8660, MM_FABRIC_HALT), + MSM_RPM_STATUS_ID_MAP(8660, MM_FABRIC_CLOCK_MODE), + MSM_RPM_STATUS_ID_MAP(8660, MM_FABRIC_ARB), + + + MSM_RPM_STATUS_ID_MAP(8660, SMPS0B_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS0B_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS1B_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS1B_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS2B_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS2B_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS3B_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS3B_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS4B_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS4B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO0B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO0B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO1B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO1B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO2B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO2B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO3B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO3B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO4B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO4B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO5B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO5B_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO6B_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO6B_1), + MSM_RPM_STATUS_ID_MAP(8660, LVS0B), + MSM_RPM_STATUS_ID_MAP(8660, LVS1B), + MSM_RPM_STATUS_ID_MAP(8660, LVS2B), + MSM_RPM_STATUS_ID_MAP(8660, LVS3B), + MSM_RPM_STATUS_ID_MAP(8660, MVS), + + + MSM_RPM_STATUS_ID_MAP(8660, SMPS0_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS0_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS1_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS1_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS2_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS2_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS3_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS3_1), + MSM_RPM_STATUS_ID_MAP(8660, SMPS4_0), + MSM_RPM_STATUS_ID_MAP(8660, SMPS4_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO0_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO0_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO1_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO1_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO2_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO2_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO3_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO3_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO4_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO4_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO5_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO5_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO6_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO6_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO7_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO7_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO8_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO8_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO9_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO9_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO10_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO10_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO11_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO11_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO12_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO12_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO13_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO13_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO14_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO14_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO15_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO15_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO16_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO16_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO17_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO17_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO18_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO18_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO19_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO19_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO20_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO20_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO21_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO21_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO22_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO22_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO23_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO23_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO24_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO24_1), + MSM_RPM_STATUS_ID_MAP(8660, LDO25_0), + MSM_RPM_STATUS_ID_MAP(8660, LDO25_1), + MSM_RPM_STATUS_ID_MAP(8660, LVS0), + MSM_RPM_STATUS_ID_MAP(8660, LVS1), + MSM_RPM_STATUS_ID_MAP(8660, NCP_0), + MSM_RPM_STATUS_ID_MAP(8660, NCP_1), + MSM_RPM_STATUS_ID_MAP(8660, CXO_BUFFERS), + }, + .target_ctrl_id = { + MSM_RPM_CTRL_MAP(8660, VERSION_MAJOR), + MSM_RPM_CTRL_MAP(8660, VERSION_MINOR), + MSM_RPM_CTRL_MAP(8660, VERSION_BUILD), + MSM_RPM_CTRL_MAP(8660, REQ_CTX_0), + MSM_RPM_CTRL_MAP(8660, REQ_SEL_0), + MSM_RPM_CTRL_MAP(8660, ACK_CTX_0), + MSM_RPM_CTRL_MAP(8660, ACK_SEL_0), + }, + .sel_invalidate = MSM_RPM_8660_SEL_INVALIDATE, + .sel_notification = MSM_RPM_8660_SEL_NOTIFICATION, + .sel_last = MSM_RPM_8660_SEL_LAST, + .ver = {2, 0, 0}, +}; + +struct platform_device msm8660_rpm_device = { + .name = "msm_rpm", + .id = -1, +}; diff --git a/arch/arm/mach-msm/devices-msm8x60.h b/arch/arm/mach-msm/devices-msm8x60.h new file mode 100644 index 0000000000000000000000000000000000000000..9bfaeeed40143ca82c1356ade313914dad9b310a --- /dev/null +++ b/arch/arm/mach-msm/devices-msm8x60.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_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_SND_SOC_MSM8660_APQ +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; +#endif + +#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_bus_def_fab; + +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 apq8064_msm_device_vidc; + +extern struct platform_device msm_charm_modem; +extern struct platform_device msm_device_tz_log; +#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 131633b12a34d7c52395fb01683aeb13abf5e35f..ee8a2cfa8448706b3f545d13baf9736e9f671dfc 100644 --- a/arch/arm/mach-msm/devices-qsd8x50.c +++ b/arch/arm/mach-msm/devices-qsd8x50.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2008 Google, Inc. - * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2012, 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,9 +15,10 @@ #include #include -#include -#include +#include +#include +#include #include #include #include @@ -27,8 +28,37 @@ #include -#include -#include "clock-pcom.h" +#include +#include +#include +#include +#include "pm.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[] = { { @@ -44,6 +74,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 +95,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 +220,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 +258,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,103 +322,263 @@ 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 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_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, + }, +}; + +static struct msm_pm_irq_calls qsd8x50_pm_irq_calls = { + .irq_pending = msm_irq_pending, + .idle_sleep_allowed = msm_irq_idle_sleep_allowed, + .enter_sleep1 = msm_irq_enter_sleep1, + .enter_sleep2 = msm_irq_enter_sleep2, + .exit_sleep1 = msm_irq_exit_sleep1, + .exit_sleep2 = msm_irq_exit_sleep2, + .exit_sleep3 = msm_irq_exit_sleep3, +}; + +void __init msm_pm_register_irqs(void) +{ + msm_pm_set_irq_extns(&qsd8x50_pm_irq_calls); +} + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .flags = IORESOURCE_IRQ, + }, + { + .start = 0xA9700000, + .end = 0xA9700000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_dmov_pdata msm_dmov_pdata = { + .sd = 3, + .sd_size = 0x400, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), + .dev = { + .platform_data = &msm_dmov_pdata, + }, +}; + +#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_PHYS, - .end = MSM_SDC1_PHYS + MSM_SDC1_SIZE - 1, + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, .flags = IORESOURCE_MEM, }, { .start = INT_SDC1_0, - .end = INT_SDC1_0, + .end = INT_SDC1_1, .flags = IORESOURCE_IRQ, - .name = "cmd_irq", }, { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" + .name = "sdcc_dma_chnl", + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, }, { - .start = 8, - .end = 8, + .name = "sdcc_dma_crci", + .start = DMOV_SDC1_CRCI, + .end = DMOV_SDC1_CRCI, .flags = IORESOURCE_DMA, - }, + } }; 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, + .end = INT_SDC2_1, .flags = IORESOURCE_IRQ, - .name = "cmd_irq", }, { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" + .name = "sdcc_dma_chnl", + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, }, { - .start = 8, - .end = 8, + .name = "sdcc_dma_crci", + .start = DMOV_SDC2_CRCI, + .end = DMOV_SDC2_CRCI, .flags = IORESOURCE_DMA, - }, + } }; 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, + .end = INT_SDC3_1, .flags = IORESOURCE_IRQ, - .name = "cmd_irq", }, { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" + .name = "sdcc_dma_chnl", + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, }, { - .start = 8, - .end = 8, + .name = "sdcc_dma_crci", + .start = DMOV_SDC3_CRCI, + .end = DMOV_SDC3_CRCI, .flags = IORESOURCE_DMA, }, }; 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, + .end = INT_SDC4_1, .flags = IORESOURCE_IRQ, - .name = "cmd_irq", }, { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" + .name = "sdcc_dma_chnl", + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, }, { - .start = 8, - .end = 8, + .name = "sdcc_dma_crci", + .start = DMOV_SDC4_CRCI, + .end = DMOV_SDC4_CRCI, .flags = IORESOURCE_DMA, }, }; @@ -266,84 +630,321 @@ 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, + } +}; + +static struct resource msm_mddi_ext_resources[] = { + { + .name = "emdh", + .start = EMDH_BASE, + .end = EMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, } +}; - return platform_device_register(pdev); +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 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); } -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); +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 = { + .pwrlevel = { + { + .gpu_freq = 0, + .bus_freq = 128000000, + }, + }, + .init_level = 0, + .num_levels = 1, + .set_grp_async = NULL, + .idle_timeout = HZ/5, + .clk_map = KGSL_CLK_CORE | KGSL_CLK_MEM, +}; + +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 9545c196c6e87832400bbb5fab059cbda93ba5ba..f8ab18a5db589ab1845cf13505e9100953d02b72 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-2012, 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,386 @@ #define __ARCH_ARM_MACH_MSM_DEVICES_H #include - +#include #include "clock.h" +void __init msm9615_device_init(void); +void __init msm9615_map_io(void); +void __init msm_map_msm9615_io(void); +void __init msm9615_init_irq(void); +void __init msm_rotator_update_bus_vectors(unsigned int xres, + unsigned int yres); + +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_adsp_device; 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 msm8625_device_uart1; + +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 msm_device_uart_dm6; +extern struct platform_device msm_device_uart_dm9; extern struct platform_device msm8960_device_uart_gsbi2; extern struct platform_device msm8960_device_uart_gsbi5; +extern struct platform_device msm8960_device_uart_gsbi8; +extern struct platform_device msm8960_device_ssbi_pmic; +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_gsbi9; +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 msm8960_device_i2c_mux_gsbi4; +extern struct platform_device msm8960_device_csiphy0; +extern struct platform_device msm8960_device_csiphy1; +extern struct platform_device msm8960_device_csiphy2; +extern struct platform_device msm8960_device_csid0; +extern struct platform_device msm8960_device_csid1; +extern struct platform_device msm8960_device_csid2; +extern struct platform_device msm8960_device_ispif; +extern struct platform_device msm8960_device_vfe; +extern struct platform_device msm8960_device_vpe; +extern struct platform_device msm8960_device_cache_erp; + +extern struct platform_device apq8064_device_uart_gsbi1; +extern struct platform_device apq8064_device_uart_gsbi3; +extern struct platform_device apq8064_device_uart_gsbi7; +extern struct platform_device apq8064_device_qup_i2c_gsbi1; +extern struct platform_device apq8064_device_qup_i2c_gsbi3; +extern struct platform_device apq8064_device_qup_i2c_gsbi4; +extern struct platform_device apq8064_device_qup_spi_gsbi5; +extern struct platform_device apq8064_slim_ctrl; +extern struct platform_device apq8064_device_ssbi_pmic1; +extern struct platform_device apq8064_device_ssbi_pmic2; +extern struct platform_device apq8064_device_cache_erp; + +extern struct platform_device msm9615_device_uart_gsbi4; +extern struct platform_device msm9615_device_qup_i2c_gsbi5; +extern struct platform_device msm9615_device_qup_spi_gsbi3; +extern struct platform_device msm9615_slim_ctrl; +extern struct platform_device msm9615_device_ssbi_pmic1; +extern struct platform_device msm9615_device_tsens; +extern struct platform_device msm_bus_9615_sys_fabric; +extern struct platform_device msm_bus_def_fab; 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_hsic_host; + +extern struct platform_device msm_device_otg; +extern struct platform_device msm_android_usb_device; +extern struct platform_device msm_device_hsic_peripheral; +extern struct platform_device msm8960_device_otg; +extern struct platform_device msm8960_device_gadget_peripheral; + +extern struct platform_device apq8064_device_otg; +extern struct platform_device apq8064_usb_diag_device; +extern struct platform_device apq8064_device_gadget_peripheral; +extern struct platform_device apq8064_device_hsusb_host; +extern struct platform_device apq8064_device_hsic_host; +extern struct platform_device apq8064_device_ehci_host3; +extern struct platform_device apq8064_device_ehci_host4; 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 msm8625_gsbi0_qup_i2c_device; +extern struct platform_device msm8625_gsbi1_qup_i2c_device; +extern struct platform_device msm8625_device_uart_dm1; +extern struct platform_device msm8625_device_uart_dm2; +extern struct platform_device msm8625_device_sdc1; +extern struct platform_device msm8625_device_sdc2; +extern struct platform_device msm8625_device_sdc3; +extern struct platform_device msm8625_device_sdc4; +extern struct platform_device msm8625_device_gadget_peripheral; +extern struct platform_device msm8625_device_hsusb_host; +extern struct platform_device msm8625_device_otg; +extern struct platform_device msm8625_kgsl_3d0; +extern struct platform_device msm8625_device_adsp; + +extern struct platform_device msm_slim_ctrl; +extern struct platform_device msm_device_sps; +extern struct platform_device msm_device_usb_bam; +extern struct platform_device msm_device_sps_apq8064; +extern struct platform_device msm_device_bam_dmux; extern struct platform_device msm_device_smd; +extern struct platform_device msm_device_smd_apq8064; +extern struct platform_device msm8625_device_smd; +extern struct platform_device msm_device_dmov; +extern struct platform_device msm8960_device_dmov; +extern struct platform_device apq8064_device_dmov; +extern struct platform_device msm9615_device_dmov; +extern struct platform_device msm8625_device_dmov; +extern struct platform_device msm_device_dmov_adm0; +extern struct platform_device msm_device_dmov_adm1; + +extern struct platform_device msm_device_pcie; 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; +#ifdef CONFIG_MSM_VCAP +extern struct platform_device msm8064_device_vcap; +#endif + +#ifdef CONFIG_MSM_BUS_SCALING +extern struct msm_bus_scale_pdata rotator_bus_scale_pdata; +#endif + +extern struct platform_device msm_device_tsif[2]; + +extern struct platform_device msm_device_ssbi_pmic1; +extern struct platform_device msm_device_ssbi_pmic2; +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_vidc_720p; + +extern struct platform_device msm_pcm; +extern struct platform_device msm_multi_ch_pcm; +extern struct platform_device msm_pcm_routing; +extern struct platform_device msm_cpudai0; +extern struct platform_device msm_cpudai1; +extern struct platform_device mpq_cpudai_sec_i2s_rx; +extern struct platform_device msm8960_cpudai_slimbus_2_tx; +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_cpudai_auxpcm_rx; +extern struct platform_device msm_cpudai_auxpcm_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_cpudai_afe_01_rx; +extern struct platform_device msm_cpudai_afe_01_tx; +extern struct platform_device msm_cpudai_afe_02_rx; +extern struct platform_device msm_cpudai_afe_02_tx; +extern struct platform_device msm_pcm_afe; +extern struct platform_device msm_compr_dsp; +extern struct platform_device msm_cpudai_incall_music_rx; +extern struct platform_device msm_cpudai_incall_record_rx; +extern struct platform_device msm_cpudai_incall_record_tx; +extern struct platform_device msm_i2s_cpudai0; +extern struct platform_device msm_i2s_cpudai1; + +extern struct platform_device msm_pil_q6v3; +extern struct platform_device msm_pil_modem; +extern struct platform_device msm_pil_tzapps; +extern struct platform_device msm_pil_dsps; +extern struct platform_device msm_pil_vidc; +extern struct platform_device msm_8960_q6_lpass; +extern struct platform_device msm_8960_q6_mss_fw; +extern struct platform_device msm_8960_q6_mss_sw; +extern struct platform_device msm_8960_riva; +extern struct platform_device msm_gss; + +extern struct platform_device apq_pcm; +extern struct platform_device apq_pcm_routing; +extern struct platform_device apq_cpudai0; +extern struct platform_device apq_cpudai1; +extern struct platform_device mpq_cpudai_mi2s_tx; +extern struct platform_device apq_cpudai_hdmi_rx; +extern struct platform_device apq_cpudai_bt_rx; +extern struct platform_device apq_cpudai_bt_tx; +extern struct platform_device apq_cpudai_fm_rx; +extern struct platform_device apq_cpudai_fm_tx; +extern struct platform_device apq_cpudai_auxpcm_rx; +extern struct platform_device apq_cpudai_auxpcm_tx; +extern struct platform_device apq_cpu_fe; +extern struct platform_device apq_stub_codec; +extern struct platform_device apq_voice; +extern struct platform_device apq_voip; +extern struct platform_device apq_lpa_pcm; +extern struct platform_device apq_compr_dsp; +extern struct platform_device apq_multi_ch_pcm; +extern struct platform_device apq_pcm_hostless; +extern struct platform_device apq_cpudai_afe_01_rx; +extern struct platform_device apq_cpudai_afe_01_tx; +extern struct platform_device apq_cpudai_afe_02_rx; +extern struct platform_device apq_cpudai_afe_02_tx; +extern struct platform_device apq_pcm_afe; +extern struct platform_device apq_cpudai_stub; +extern struct platform_device apq_cpudai_slimbus_1_rx; +extern struct platform_device apq_cpudai_slimbus_1_tx; +extern struct platform_device apq_cpudai_slimbus_2_tx; +extern struct platform_device apq_cpudai_slimbus_3_rx; +extern struct platform_device apq_cpudai_slim_4_rx; +extern struct platform_device apq_cpudai_slim_4_tx; -extern struct clk_lookup msm_clocks_7x01a[]; -extern unsigned msm_num_clocks_7x01a; +extern struct platform_device *msm_footswitch_devices[]; +extern unsigned msm_num_footswitch_devices; +extern struct platform_device *msm8660_footswitch[]; +extern unsigned msm8660_num_footswitch; +extern struct platform_device *msm8960_footswitch[]; +extern unsigned msm8960_num_footswitch; +extern struct platform_device *apq8064_footswitch[]; +extern unsigned apq8064_num_footswitch; +extern struct platform_device *msm8930_footswitch[]; +extern unsigned msm8930_num_footswitch; -extern struct clk_lookup msm_clocks_7x30[]; -extern unsigned msm_num_clocks_7x30; +extern struct platform_device fsm_qfp_fuse_device; -extern struct clk_lookup msm_clocks_8x50[]; -extern unsigned msm_num_clocks_8x50; +extern struct platform_device fsm_xo_device; +extern struct platform_device qfec_device; + +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 platform_device msm_lvds_device; +extern struct platform_device msm_ebi2_lcdc_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 platform_device led_pdev; + +extern struct platform_device msm8960_rpm_device; +extern struct platform_device msm8960_rpm_stat_device; +extern struct platform_device msm8960_rpm_log_device; + +extern struct platform_device msm8930_rpm_device; +extern struct platform_device msm8930_rpm_stat_device; +extern struct platform_device msm8930_rpm_log_device; + +extern struct platform_device msm8660_rpm_device; +extern struct platform_device msm8660_rpm_stat_device; +extern struct platform_device msm8660_rpm_log_device; + +extern struct platform_device msm9615_rpm_device; +extern struct platform_device msm9615_rpm_stat_device; +extern struct platform_device msm9615_rpm_log_device; + +extern struct platform_device apq8064_rpm_device; +extern struct platform_device apq8064_rpm_stat_device; +extern struct platform_device apq8064_rpm_log_device; + +extern struct platform_device msm_device_rng; +extern struct platform_device apq8064_device_rng; + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) +extern struct platform_device msm9615_qcrypto_device; +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) +extern struct platform_device msm9615_qcedev_device; +#endif +extern struct platform_device msm8960_device_watchdog; +extern struct platform_device msm8660_device_watchdog; +extern struct platform_device msm8064_device_watchdog; +extern struct platform_device msm9615_device_watchdog; +extern struct platform_device fsm9xxx_device_watchdog; + +extern struct platform_device apq8064_qdss_device; +extern struct platform_device msm_qdss_device; +extern struct platform_device msm_etb_device; +extern struct platform_device msm_tpiu_device; +extern struct platform_device msm_funnel_device; +extern struct platform_device msm_etm_device; +extern struct platform_device apq8064_etm_device; #endif + +extern struct platform_device msm_bus_8064_apps_fabric; +extern struct platform_device msm_bus_8064_sys_fabric; +extern struct platform_device msm_bus_8064_mm_fabric; +extern struct platform_device msm_bus_8064_sys_fpb; +extern struct platform_device msm_bus_8064_cpss_fpb; + +extern struct platform_device mdm_8064_device; +extern struct platform_device msm_dsps_device_8064; +extern struct platform_device *msm_copper_stub_regulator_devices[]; +extern int msm_copper_stub_regulator_devices_len; + +extern struct platform_device msm8960_cpu_idle_device; +extern struct platform_device msm8930_cpu_idle_device; +extern struct platform_device apq8064_cpu_idle_device; + +extern struct platform_device msm8960_msm_gov_device; +extern struct platform_device msm8930_msm_gov_device; +extern struct platform_device apq8064_msm_gov_device; + +extern struct platform_device msm_bus_8930_apps_fabric; +extern struct platform_device msm_bus_8930_sys_fabric; +extern struct platform_device msm_bus_8930_mm_fabric; +extern struct platform_device msm_bus_8930_sys_fpb; +extern struct platform_device msm_bus_8930_cpss_fpb; + +extern struct platform_device msm_device_csic0; +extern struct platform_device msm_device_csic1; +extern struct platform_device msm_device_vfe; +extern struct platform_device msm_device_vpe; +extern struct platform_device mpq8064_device_qup_i2c_gsbi5; + +extern struct platform_device msm8960_iommu_domain_device; +extern struct platform_device msm8930_iommu_domain_device; +extern struct platform_device apq8064_iommu_domain_device; + +extern struct platform_device msm8960_rtb_device; +extern struct platform_device msm8930_rtb_device; +extern struct platform_device apq8064_rtb_device; + +extern struct platform_device msm8960_cache_dump_device; +extern struct platform_device apq8064_cache_dump_device; + +extern struct platform_device copper_device_tz_log; + +extern struct platform_device mdm_sglte_device; diff --git a/arch/arm/mach-msm/devices_htc.c b/arch/arm/mach-msm/devices_htc.c new file mode 100644 index 0000000000000000000000000000000000000000..b2b45723e2cd64028b06f622e555f18fcac08ba4 --- /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 0000000000000000000000000000000000000000..1a956e3d9e337b8e0f02241fda538ea53d84e4dc --- /dev/null +++ b/arch/arm/mach-msm/dfe-fsm9xxx.c @@ -0,0 +1,433 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_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 02cae5e2951c2d8ebc6cade2c582d5bb37d25d54..d3b227468eedb08682c84c63889d9cdb61448fd8 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, 2012 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,190 @@ #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; + int sd; + size_t sd_size; + struct list_head ready_commands[MSM_DMOV_CHANNEL_COUNT]; + struct list_head active_commands[MSM_DMOV_CHANNEL_COUNT]; + 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) +\ + (dmov_conf[adm].sd * dmov_conf[adm].sd_size)) +#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 +209,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 +223,122 @@ 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) +{ + 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; +} + +static void msm_dmov_clock_timer(unsigned long adm) { - writel((graceful << 31), DMOV_FLUSH0(id)); + unsigned long irq_flags; + 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_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd) +void msm_dmov_enqueue_cmd_ext(unsigned id, struct msm_dmov_cmd *cmd) { unsigned long irq_flags; unsigned int status; + int adm = DMOV_ID_TO_ADM(id); + int ch = DMOV_ID_TO_CHAN(id); - 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)); - } -#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)); + spin_lock_irqsave(&dmov_conf[adm].lock, irq_flags); + if (dmov_conf[adm].clk_ctl == CLK_DIS) { + status = msm_dmov_clk_toggle(adm, 1); + if (status != 0) + goto error; + } 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)); + if (status & DMOV_STATUS_CMD_PTR_RDY) { + 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); + 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])) + 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]); + } +error: + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); +} +EXPORT_SYMBOL(msm_dmov_enqueue_cmd_ext); + +void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd) +{ + /* Disable callback function (for backwards compatibility) */ + cmd->exec_func = NULL; - PRINT_IO("msm_dmov_enqueue_cmd(%d), enqueue command, status %x\n", id, status); - list_add_tail(&cmd->list, &ready_commands[id]); + msm_dmov_enqueue_cmd_ext(id, cmd); +} +EXPORT_SYMBOL(msm_dmov_enqueue_cmd); + +void msm_dmov_flush(unsigned int id, int graceful) +{ + unsigned long irq_flags; + int ch = DMOV_ID_TO_CHAN(id); + int adm = DMOV_ID_TO_ADM(id); + int flush = graceful ? DMOV_FLUSH_TYPE : 0; + 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(flush, 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; @@ -119,12 +369,13 @@ int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr) cmd.dmov_cmd.cmdptr = cmdptr; 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 +386,61 @@ 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] = 0; + 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; + unsigned int valid = 0; 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])) { + valid = 1; + 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 +449,253 @@ 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)); 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); - PRINT_FLOW("msm_datamover_irq_handler id %d, start command\n", id); - writel(cmd->cmdptr, DMOV_CMD_PTR(id)); + 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_relaxed(cmd->cmdptr, + DMOV_REG(DMOV_CMD_PTR(ch), adm)); } } 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); + if (!dmov_conf[adm].channel_active && valid) { + 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); - return IRQ_HANDLED; + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); + return valid ? IRQ_HANDLED : IRQ_NONE; } -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, "core_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, "iface_clk"); + 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, "mem_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 msm_dmov_pdata *pdata = pdev->dev.platform_data; + struct resource *irqres = + platform_get_resource(pdev, IORESOURCE_IRQ, 0); + struct resource *mres = + platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (pdata) { + dmov_conf[adm].sd = pdata->sd; + dmov_conf[adm].sd_size = pdata->sd_size; + } + if (!dmov_conf[adm].sd_size) + return -ENXIO; + + if (!irqres || !irqres->start) + return -ENXIO; + dmov_conf[adm].irq = irqres->start; + if (!mres || !mres->start) + return -ENXIO; + dmov_conf[adm].base = ioremap_nocache(mres->start, resource_size(mres)); + if (!dmov_conf[adm].base) + return -ENOMEM; + + 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); + goto out_map; + } + disable_irq(dmov_conf[adm].irq); + ret = msm_dmov_init_clocks(pdev); + if (ret) { + PRINT_ERROR("Requesting ADM%d clocks failed\n", adm); + goto out_irq; + } + ret = msm_dmov_clk_toggle(adm, 1); + if (ret) { + PRINT_ERROR("Enabling ADM%d clocks failed\n", adm); + goto out_irq; + } + + 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(); + msm_dmov_clk_toggle(adm, 0); + return ret; +out_irq: + free_irq(dmov_conf[adm].irq, NULL); +out_map: + iounmap(dmov_conf[adm].base); + 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 0000000000000000000000000000000000000000..de1ee0abf77b219b2750e6ffb82a52e2c5021d56 --- /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, + 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 0000000000000000000000000000000000000000..6cceff23a21ee7c5124c526e399eafd746031aeb --- /dev/null +++ b/arch/arm/mach-msm/etm.c @@ -0,0 +1,1037 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +/* 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 + +/* ETB Registers */ +#define ETB_REG_CONTROL ETMIMPSPEC1 +#define ETB_REG_STATUS ETMIMPSPEC2 +#define ETB_REG_COUNT ETMIMPSPEC3 +#define ETB_REG_ADDRESS ETMIMPSPEC4 +#define ETB_REG_DATA ETMIMPSPEC5 + +/* Having etb macro accessors allows macro expansion for ETB reg defines */ +#define etb_read(reg) etm_read(reg) +#define etb_write(val, reg) etm_write(val, reg) + +/* 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_to_dump; +static int next_cpu_to_dump; +static struct wake_lock etm_wake_lock; +static struct pm_qos_request 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, +}; + +/* ETM clock is derived from the processor clock and gets enabled on a + * logical OR of below items on Scorpion: + * 1.CPMR[ETMCLKEN] is set + * 2.ETM is not idle. Also means ETMCR[PD] is 0 + * 3.Reset is asserted (core or debug) + * 4.MRC/MCR to ETM reg (CP14 access) + * 5.Debugger access to a ETM register in the core power domain + * + * 1. and 2. above are permanent enables whereas 3., 4. and 5. are + * temporary enables + * + * We rely on 4. to be able to access ETMCR and then use 2. above for ETM + * clock vote in the driver and the save-restore code uses 1. above + * for its vote. + */ +static inline void __cpu_set_etm_pwrdwn(void) +{ + uint32_t etm_control; + + isb(); + etm_control = etm_read(ETMCR); + etm_control |= ETM_CONTROL_POWERDOWN; + etm_write(etm_control, ETMCR); +} + +static inline void __cpu_clear_etm_pwrdwn(void) +{ + uint32_t etm_control; + + etm_control = etm_read(ETMCR); + etm_control &= ~ETM_CONTROL_POWERDOWN; + etm_write(etm_control, ETMCR); + isb(); +} + +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; + etb_write(etb_control, ETB_REG_CONTROL); + + /* write tags to the slots before the write pointer so we can + * detect overflow */ + etb_write(0x00000000, ETB_REG_ADDRESS); + for (i = 0; i < (etm_config.etb_init_ptr >> 2); i++) + etb_write(0xDEADBEEF, ETB_REG_DATA); + + etb_write(0x00000000, ETB_REG_STATUS); + + /* initialize write pointer */ + etb_write(etm_config.etb_init_ptr, ETB_REG_ADDRESS); + + /* multiple of 16 */ + etb_write(etm_config.etb_trig_cnt & 0xFFFFFFF0, ETB_REG_COUNT); + + /* 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; + etb_write(etb_control, ETB_REG_CONTROL); +} + +static void __cpu_disable_etb(void) +{ + uint32_t etb_control; + etb_control = etb_read(ETB_REG_CONTROL); + etb_control &= ~CPTEN; + etb_write(etb_control, ETB_REG_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(ETMCR); + etm_control &= ~ETM_CONTROL_PROGRAM; + etm_write(etm_control, ETMCR); + + while ((etm_read(ETMSR) & 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(ETMCR); + etm_control |= ETM_CONTROL_PROGRAM; + etm_write(etm_control, ETMCR); + + while ((etm_read(ETMSR) & 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(); + + __cpu_disable_etb(); + /* vote for ETM power/clock enable */ + __cpu_clear_etm_pwrdwn(); + __cpu_disable_etm(); + + etm_control = (etm_config.etm_00_control & ~ETM_CONTROL_POWERDOWN) + | ETM_CONTROL_PROGRAM; + etm_write(etm_control, ETMCR); + + 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(etm_trigger, ETMTRIGGER); + etm_write(etm_config.etm_06_te_start_stop, ETMTSSCR); + etm_write(etm_config.etm_07_te_single_addr_comp, ETMTECR2); + etm_write(etm_config.etm_08_te_event, ETMTEEVR); + etm_write(etm_config.etm_09_te_control, ETMTECR1); + etm_write(etm_config.etm_0a_fifofull_region, ETMFFRR); + etm_write(etm_config.etm_0b_fifofull_level, ETMFFLR); + etm_write(etm_config.etm_0c_vd_event, ETMVDEVR); + etm_write(etm_config.etm_0d_vd_single_addr_comp, ETMVDCR1); + etm_write(etm_config.etm_0e_vd_mmd, ETMVDCR2); + etm_write(etm_config.etm_0f_vd_control, ETMVDCR3); + etm_write(etm_config.etm_addr_comp_value[0], ETMACVR0); + etm_write(etm_config.etm_addr_comp_value[1], ETMACVR1); + etm_write(etm_config.etm_addr_comp_value[2], ETMACVR2); + etm_write(etm_config.etm_addr_comp_value[3], ETMACVR3); + etm_write(etm_config.etm_addr_comp_value[4], ETMACVR4); + etm_write(etm_config.etm_addr_comp_value[5], ETMACVR5); + etm_write(etm_config.etm_addr_comp_value[6], ETMACVR6); + etm_write(etm_config.etm_addr_comp_value[7], ETMACVR7); + etm_write(etm_config.etm_addr_access_type[0], ETMACTR0); + etm_write(etm_config.etm_addr_access_type[1], ETMACTR1); + etm_write(etm_config.etm_addr_access_type[2], ETMACTR2); + etm_write(etm_config.etm_addr_access_type[3], ETMACTR3); + etm_write(etm_config.etm_addr_access_type[4], ETMACTR4); + etm_write(etm_config.etm_addr_access_type[5], ETMACTR5); + etm_write(etm_config.etm_addr_access_type[6], ETMACTR6); + etm_write(etm_config.etm_addr_access_type[7], ETMACTR7); + etm_write(etm_config.etm_data_comp_value[0], ETMDCVR0); + etm_write(etm_config.etm_data_comp_value[1], ETMDCVR2); + etm_write(etm_config.etm_data_comp_mask[0], ETMDCMR0); + etm_write(etm_config.etm_data_comp_mask[1], ETMDCMR2); + etm_write(etm_config.etm_counter_reload_value[0], ETMCNTRLDVR0); + etm_write(etm_config.etm_counter_reload_value[1], ETMCNTRLDVR1); + etm_write(etm_config.etm_counter_enable[0], ETMCNTENR0); + etm_write(etm_config.etm_counter_enable[1], ETMCNTENR1); + etm_write(etm_config.etm_counter_reload_event[0], ETMCNTRLDEVR0); + etm_write(etm_config.etm_counter_reload_event[1], ETMCNTRLDEVR1); + etm_write(etm_config.etm_60_seq_event_1_to_2, ETMSQ12EVR); + etm_write(etm_config.etm_61_seq_event_2_to_1, ETMSQ21EVR); + etm_write(etm_config.etm_62_seq_event_2_to_3, ETMSQ23EVR); + etm_write(etm_config.etm_63_seq_event_3_to_1, ETMSQ31EVR); + etm_write(etm_config.etm_64_seq_event_3_to_2, ETMSQ32EVR); + etm_write(etm_config.etm_65_seq_event_1_to_3, ETMSQ13EVR); + etm_write(etm_external_output, ETMEXTOUTEVR0); + etm_write(etm_config.etm_6c_cid_comp_value_1, ETMCIDCVR0); + etm_write(etm_config.etm_6f_cid_comp_mask, ETMCIDCMR); + etm_write(etm_config.etm_78_sync_freq, ETMSYNCFR); + + /* 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) +{ + get_cpu(); + + __cpu_disable_etm(); + + /* program trace enable to be low by using always false event */ + etm_write(0x6F | BIT(14), ETMTEEVR); + /* vote for ETM power/clock disable */ + __cpu_set_etm_pwrdwn(); + + __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] */ + asm volatile("mcr p15, 3, %0, c15, c5, 2" : : "r" (0x1)); + asm volatile("isb"); + } + + get_cpu(); + __cpu_enable_trace(NULL); + smp_call_function(__cpu_enable_trace, NULL, 1); + put_cpu(); + + /* 1. causes all online cpus to come out of idle PC + * 2. prevents idle PC until save restore flag is enabled atomically + * + * we rely on the user to prevent hotplug on/off racing with this + * operation and to ensure cores where trace is expected to be turned + * on are already hotplugged on + */ + trace_enabled = 1; + + pm_qos_update_request(&etm_qos_req, PM_QOS_DEFAULT_VALUE); + wake_unlock(&etm_wake_lock); +} + +static void disable_trace(void) +{ + 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(); + + /* 1. causes all online cpus to come out of idle PC + * 2. prevents idle PC until save restore flag is disabled atomically + * + * we rely on the user to prevent hotplug on/off racing with this + * operation and to ensure cores where trace is expected to be turned + * off are already hotplugged on + */ + trace_enabled = 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 = etb_read(ETB_REG_CONTROL); + etb_control |= AIR; + etb_write(etb_control, ETB_REG_CONTROL); + + if (etb_read(ETB_REG_STATUS) & OV) + full_slots = ETB_RAM_SLOTS; + else + full_slots = etb_read(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(ETMCR)); + emit_log_word(etm_config.etb_init_ptr >> 2); + emit_log_word(etb_read(ETB_REG_ADDRESS) >> 2); + emit_log_word((etb_read(ETB_REG_STATUS) & OV) >> 21); + + etb_write(0x00000000, ETB_REG_ADDRESS); + for (i = 0; i < full_slots; i++) + emit_log_word(etb_read(ETB_REG_DATA)); +} + +/* This should match the number of ETM registers being dumped below */ +#define ETM_NUM_REGS_TO_DUMP 54 +static void generate_etm_dump(void) +{ + uint32_t prim_len; + uint32_t uptime = 0; + + prim_len = 12 + (4 * ETM_NUM_REGS_TO_DUMP); + + 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); + + emit_log_word(etm_read(ETMCR)); + emit_log_word(etm_read(ETMSR)); + emit_log_word(etb_read(ETB_REG_CONTROL)); + emit_log_word(etb_read(ETB_REG_STATUS)); + emit_log_word(etb_read(ETB_REG_COUNT)); + emit_log_word(etb_read(ETB_REG_ADDRESS)); + emit_log_word(0); /* don't read ETB_REG_DATA, changes ETB_REG_ADDRESS */ + emit_log_word(etm_read(ETMTRIGGER)); + emit_log_word(etm_read(ETMTSSCR)); + emit_log_word(etm_read(ETMTECR2)); + emit_log_word(etm_read(ETMTEEVR)); + emit_log_word(etm_read(ETMTECR1)); + emit_log_word(etm_read(ETMFFRR)); + emit_log_word(etm_read(ETMFFLR)); + emit_log_word(etm_read(ETMVDEVR)); + emit_log_word(etm_read(ETMVDCR1)); + emit_log_word(etm_read(ETMVDCR2)); + emit_log_word(etm_read(ETMVDCR3)); + emit_log_word(etm_read(ETMACVR0)); + emit_log_word(etm_read(ETMACVR1)); + emit_log_word(etm_read(ETMACVR2)); + emit_log_word(etm_read(ETMACVR3)); + emit_log_word(etm_read(ETMACVR4)); + emit_log_word(etm_read(ETMACVR5)); + emit_log_word(etm_read(ETMACVR6)); + emit_log_word(etm_read(ETMACVR7)); + emit_log_word(etm_read(ETMACTR0)); + emit_log_word(etm_read(ETMACTR1)); + emit_log_word(etm_read(ETMACTR2)); + emit_log_word(etm_read(ETMACTR3)); + emit_log_word(etm_read(ETMACTR4)); + emit_log_word(etm_read(ETMACTR5)); + emit_log_word(etm_read(ETMACTR6)); + emit_log_word(etm_read(ETMACTR7)); + emit_log_word(etm_read(ETMDCVR0)); + emit_log_word(etm_read(ETMDCVR2)); + emit_log_word(etm_read(ETMDCMR0)); + emit_log_word(etm_read(ETMDCMR2)); + emit_log_word(etm_read(ETMCNTRLDVR0)); + emit_log_word(etm_read(ETMCNTRLDVR1)); + emit_log_word(etm_read(ETMCNTENR0)); + emit_log_word(etm_read(ETMCNTENR1)); + emit_log_word(etm_read(ETMCNTRLDEVR0)); + emit_log_word(etm_read(ETMCNTRLDEVR1)); + emit_log_word(etm_read(ETMSQ12EVR)); + emit_log_word(etm_read(ETMSQ21EVR)); + emit_log_word(etm_read(ETMSQ23EVR)); + emit_log_word(etm_read(ETMSQ31EVR)); + emit_log_word(etm_read(ETMSQ32EVR)); + emit_log_word(etm_read(ETMSQ13EVR)); + emit_log_word(etm_read(ETMEXTOUTEVR0)); + emit_log_word(etm_read(ETMCIDCVR0)); + emit_log_word(etm_read(ETMCIDCMR)); + emit_log_word(etm_read(ETMSYNCFR)); +} + +static void dump_all(void *unused) +{ + get_cpu(); + __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, +}; + +static void __cpu_clear_sticky(void *unused) +{ + etm_read(ETMPDSR); /* clear sticky bit in PDSR */ + isb(); +} + +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]; + + 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); + + /* No need to explicity turn on ETM clock since CP14 access go + * through via the autoclock turn on/off + */ + __cpu_clear_sticky(NULL); + smp_call_function(__cpu_clear_sticky, NULL, 1); + + cpu_to_dump = next_cpu_to_dump = 0; + + pr_info("ETM/ETB intialized.\n"); + + if (trace_on_boot) + enable_trace(); + + return 0; + +err1: + misc_deregister(&etm_dev); + return -ENOMEM; +} +module_init(etm_init); + +static void __exit etm_exit(void) +{ + disable_trace(); + pm_qos_remove_request(&etm_qos_req); + wake_lock_destroy(&etm_wake_lock); + free_percpu(alloc_b); + misc_deregister(&etm_dev); +} +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 0000000000000000000000000000000000000000..cd903908c0878c90d0cf50c25cd8b08eb4ba3de9 --- /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 0000000000000000000000000000000000000000..df1c7084fe115076b9a3436ef7d6b94f4adf3f5e --- /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 0000000000000000000000000000000000000000..19fbb91fe83ae6f43aa2927cd74389817c1e37f7 --- /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 0000000000000000000000000000000000000000..84735aabbbfbbd60aa8b242c5d93100c7d75728a --- /dev/null +++ b/arch/arm/mach-msm/footswitch-8x60.c @@ -0,0 +1,592 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "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 VCAP_GFS_CTL_REG REG(0x0254) + +#define CLAMP_BIT BIT(5) +#define ENABLE_BIT BIT(8) +#define RETENTION_BIT BIT(9) + +#define GFS_DELAY_CNT 31 + +#define RESET_DELAY_US 1 +/* Clock rate to use if one has not previously been set. */ +#define DEFAULT_RATE 27000000 +#define MAX_CLKS 10 + +/* + * Lock is only needed to protect against the first footswitch_enable() + * call occuring concurrently with late_footswitch_init(). + */ +static DEFINE_MUTEX(claim_lock); + +struct footswitch { + struct regulator_dev *rdev; + struct regulator_desc desc; + void *gfs_ctl_reg; + int bus_port0, bus_port1; + bool is_enabled; + bool is_claimed; + struct fs_clk_data *clk_data; + struct clk *core_clk; +}; + +static int setup_clocks(struct footswitch *fs) +{ + int rc = 0; + struct fs_clk_data *clock; + long rate; + + /* + * Enable all clocks in the power domain. If a specific clock rate is + * required for reset timing, set that rate before enabling the clocks. + */ + for (clock = fs->clk_data; clock->clk; clock++) { + clock->rate = clk_get_rate(clock->clk); + if (!clock->rate || clock->reset_rate) { + rate = clock->reset_rate ? + clock->reset_rate : DEFAULT_RATE; + rc = clk_set_rate(clock->clk, rate); + if (rc && rc != -ENOSYS) { + pr_err("Failed to set %s %s rate to %lu Hz.\n", + fs->desc.name, clock->name, clock->rate); + for (clock--; clock >= fs->clk_data; clock--) { + if (clock->enabled) + clk_disable_unprepare( + clock->clk); + clk_set_rate(clock->clk, clock->rate); + } + return rc; + } + } + /* + * Some clocks are for reset purposes only. These clocks will + * fail to enable. Ignore the failures but keep track of them so + * we don't try to disable them later and crash due to + * unbalanced calls. + */ + clock->enabled = !clk_prepare_enable(clock->clk); + } + + return 0; +} + +static void restore_clocks(struct footswitch *fs) +{ + struct fs_clk_data *clock; + + /* Restore clocks to their orignal states before setup_clocks(). */ + for (clock = fs->clk_data; clock->clk; clock++) { + if (clock->enabled) + clk_disable_unprepare(clock->clk); + if (clock->rate && clk_set_rate(clock->clk, clock->rate)) + pr_err("Failed to restore %s %s rate to %lu Hz.\n", + fs->desc.name, clock->name, clock->rate); + } +} + +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); + struct fs_clk_data *clock; + uint32_t regval, rc = 0; + + mutex_lock(&claim_lock); + fs->is_claimed = true; + mutex_unlock(&claim_lock); + + /* Return early if already enabled. */ + regval = readl_relaxed(fs->gfs_ctl_reg); + if ((regval & (ENABLE_BIT | CLAMP_BIT)) == ENABLE_BIT) + return 0; + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + return rc; + + /* Un-halt all bus ports in the power domain. */ + if (fs->bus_port0) { + rc = msm_bus_axi_portunhalt(fs->bus_port0); + if (rc) { + pr_err("%s port 0 unhalt failed.\n", fs->desc.name); + goto err; + } + } + if (fs->bus_port1) { + rc = msm_bus_axi_portunhalt(fs->bus_port1); + if (rc) { + pr_err("%s port 1 unhalt failed.\n", fs->desc.name); + goto err_port2_halt; + } + } + + /* + * (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. + */ + for (clock = fs->clk_data; clock->clk; clock++) + ; /* Do nothing */ + for (clock--; clock >= fs->clk_data; clock--) + clk_reset(clock->clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(RESET_DELAY_US); + + /* Enable the power rail at the footswitch. */ + 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. */ + for (clock = fs->clk_data; clock->clk; clock++) + clk_reset(clock->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); + } + + /* Prevent core memory from collapsing when its clock is gated. */ + clk_set_flags(fs->core_clk, CLKFLAG_RETAIN); + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = true; + return 0; + +err_port2_halt: + msm_bus_axi_porthalt(fs->bus_port0); +err: + restore_clocks(fs); + return rc; +} + +static int footswitch_disable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + struct fs_clk_data *clock; + uint32_t regval, rc = 0; + + /* Return early if already disabled. */ + regval = readl_relaxed(fs->gfs_ctl_reg); + if ((regval & ENABLE_BIT) == 0) + return 0; + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + return rc; + + /* Allow core memory to collapse when its clock is gated. */ + clk_set_flags(fs->core_clk, CLKFLAG_NORETAIN); + + /* Halt all bus ports in the power domain. */ + if (fs->bus_port0) { + rc = msm_bus_axi_porthalt(fs->bus_port0); + if (rc) { + pr_err("%s port 0 halt failed.\n", fs->desc.name); + goto err; + } + } + if (fs->bus_port1) { + rc = msm_bus_axi_porthalt(fs->bus_port1); + if (rc) { + pr_err("%s port 1 halt failed.\n", fs->desc.name); + goto err_port2_halt; + } + } + + /* + * Assert resets for all clocks in the clock domain so that + * outputs settle prior to clamping. + */ + for (clock = fs->clk_data; clock->clk; clock++) + ; /* Do nothing */ + for (clock--; clock >= fs->clk_data; clock--) + clk_reset(clock->clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(RESET_DELAY_US); + + /* + * Return clocks to their state before this function. For robustness + * if memory-retention across collapses is required, clocks should + * be disabled before asserting the clamps. Assuming clocks were off + * before entering footswitch_disable(), this will be true. + */ + restore_clocks(fs); + + /* + * Clamp the I/O ports of the core to ensure the values + * remain fixed while the core is collapsed. + */ + 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); + + fs->is_enabled = false; + return 0; + +err_port2_halt: + msm_bus_axi_portunhalt(fs->bus_port0); +err: + clk_set_flags(fs->core_clk, CLKFLAG_RETAIN); + restore_clocks(fs); + return rc; +} + +static int gfx2d_footswitch_enable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + struct fs_clk_data *clock; + uint32_t regval, rc = 0; + + mutex_lock(&claim_lock); + fs->is_claimed = true; + mutex_unlock(&claim_lock); + + /* Return early if already enabled. */ + regval = readl_relaxed(fs->gfs_ctl_reg); + if ((regval & (ENABLE_BIT | CLAMP_BIT)) == ENABLE_BIT) + return 0; + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + return rc; + + /* Un-halt all bus ports in the power domain. */ + if (fs->bus_port0) { + rc = msm_bus_axi_portunhalt(fs->bus_port0); + if (rc) { + pr_err("%s port 0 unhalt failed.\n", fs->desc.name); + goto err; + } + } + + /* Disable core clock. */ + clk_disable_unprepare(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. + */ + for (clock = fs->clk_data; clock->clk; clock++) + ; /* Do nothing */ + for (clock--; clock >= fs->clk_data; clock--) + clk_reset(clock->clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(RESET_DELAY_US); + + /* Enable the power rail at the footswitch. */ + 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. */ + for (clock = fs->clk_data; clock->clk; clock++) + clk_reset(clock->clk, CLK_RESET_DEASSERT); + udelay(RESET_DELAY_US); + + /* Re-enable core clock. */ + clk_prepare_enable(fs->core_clk); + + /* Prevent core memory from collapsing when its clock is gated. */ + clk_set_flags(fs->core_clk, CLKFLAG_RETAIN); + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = true; + return 0; + +err: + restore_clocks(fs); + return rc; +} + +static int gfx2d_footswitch_disable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + struct fs_clk_data *clock; + uint32_t regval, rc = 0; + + /* Return early if already disabled. */ + regval = readl_relaxed(fs->gfs_ctl_reg); + if ((regval & ENABLE_BIT) == 0) + return 0; + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + return rc; + + /* Allow core memory to collapse when its clock is gated. */ + clk_set_flags(fs->core_clk, CLKFLAG_NORETAIN); + + /* Halt all bus ports in the power domain. */ + if (fs->bus_port0) { + rc = msm_bus_axi_porthalt(fs->bus_port0); + if (rc) { + pr_err("%s port 0 halt failed.\n", fs->desc.name); + goto err; + } + } + + /* Disable core clock. */ + clk_disable_unprepare(fs->core_clk); + + /* + * Assert resets for all clocks in the clock domain so that + * outputs settle prior to clamping. + */ + for (clock = fs->clk_data; clock->clk; clock++) + ; /* Do nothing */ + for (clock--; clock >= fs->clk_data; clock--) + clk_reset(clock->clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(5); + + /* + * Clamp the I/O ports of the core to ensure the values + * remain fixed while the core is collapsed. + */ + 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_prepare_enable(fs->core_clk); + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = false; + return 0; + +err: + clk_set_flags(fs->core_clk, CLKFLAG_RETAIN); + restore_clocks(fs); + 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) \ + [(_id)] = { \ + .desc = { \ + .id = (_id), \ + .name = (_name), \ + .ops = (_ops), \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + .gfs_ctl_reg = (_gfs_ctl_reg), \ + } +static struct footswitch footswitches[] = { + FOOTSWITCH(FS_GFX2D0, "fs_gfx2d0", &gfx2d_fs_ops, GFX2D0_GFS_CTL_REG), + FOOTSWITCH(FS_GFX2D1, "fs_gfx2d1", &gfx2d_fs_ops, GFX2D1_GFS_CTL_REG), + FOOTSWITCH(FS_GFX3D, "fs_gfx3d", &standard_fs_ops, GFX3D_GFS_CTL_REG), + FOOTSWITCH(FS_IJPEG, "fs_ijpeg", &standard_fs_ops, GEMINI_GFS_CTL_REG), + FOOTSWITCH(FS_MDP, "fs_mdp", &standard_fs_ops, MDP_GFS_CTL_REG), + FOOTSWITCH(FS_ROT, "fs_rot", &standard_fs_ops, ROT_GFS_CTL_REG), + FOOTSWITCH(FS_VED, "fs_ved", &standard_fs_ops, VED_GFS_CTL_REG), + FOOTSWITCH(FS_VFE, "fs_vfe", &standard_fs_ops, VFE_GFS_CTL_REG), + FOOTSWITCH(FS_VPE, "fs_vpe", &standard_fs_ops, VPE_GFS_CTL_REG), + FOOTSWITCH(FS_VCAP, "fs_vcap", &standard_fs_ops, VCAP_GFS_CTL_REG), +}; + +static int footswitch_probe(struct platform_device *pdev) +{ + struct footswitch *fs; + struct regulator_init_data *init_data; + struct fs_driver_data *driver_data; + struct fs_clk_data *clock; + uint32_t regval, rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= MAX_FS) + return -ENODEV; + + init_data = pdev->dev.platform_data; + driver_data = init_data->driver_data; + fs = &footswitches[pdev->id]; + fs->clk_data = driver_data->clks; + fs->bus_port0 = driver_data->bus_port0; + fs->bus_port1 = driver_data->bus_port1; + + for (clock = fs->clk_data; clock->name; clock++) { + clock->clk = clk_get(&pdev->dev, clock->name); + if (IS_ERR(clock->clk)) { + rc = PTR_ERR(clock->clk); + pr_err("%s clk_get(%s) failed\n", fs->desc.name, + clock->name); + goto err; + } + if (!strncmp(clock->name, "core_clk", 8)) + fs->core_clk = clock->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 |= GFS_DELAY_CNT; + regval &= ~RETENTION_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + fs->rdev = regulator_register(&fs->desc, &pdev->dev, + init_data, fs, NULL); + if (IS_ERR(footswitches[pdev->id].rdev)) { + pr_err("regulator_register(\"%s\") failed\n", + fs->desc.name); + rc = PTR_ERR(footswitches[pdev->id].rdev); + goto err; + } + + return 0; + +err: + for (clock = fs->clk_data; clock->clk; clock++) + clk_put(clock->clk); + + return rc; +} + +static int __devexit footswitch_remove(struct platform_device *pdev) +{ + struct footswitch *fs = &footswitches[pdev->id]; + struct fs_clk_data *clock; + + for (clock = fs->clk_data; clock->clk; clock++) + clk_put(clock->clk); + regulator_unregister(fs->rdev); + + return 0; +} + +static struct platform_driver footswitch_driver = { + .probe = footswitch_probe, + .remove = __devexit_p(footswitch_remove), + .driver = { + .name = "footswitch-8x60", + .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) + footswitches[i].rdev->desc->ops-> + 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 0000000000000000000000000000000000000000..07d711828636b6efc439f04397772ea4ab236e7e --- /dev/null +++ b/arch/arm/mach-msm/footswitch-pcom.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "footswitch.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 + * @has_ahb_clk: Flag set if footswitched core has an ahb_clk + * @has_src_clk: Flag set if footswitched core has a src_clk + * @src_clk: Controls the core clock's rate + * @core_clk: Clocks the core + * @ahb_clk: Clocks the core's register interface + * @src_clk_init_rate: Rate to use for src_clk if it has not been set yet + * @is_rate_set: Flag set if core_clk'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; + struct clk *src_clk; + struct clk *core_clk; + struct clk *ahb_clk; + const bool has_ahb_clk; + const bool has_src_clk; + const int src_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->src_clk)); + if (!fs->is_rate_set) + clk_set_rate(fs->src_clk, fs->src_clk_init_rate); + clk_prepare_enable(fs->core_clk); + + if (fs->ahb_clk) + clk_prepare_enable(fs->ahb_clk); + + return 0; +} + +static void disable_clocks(struct footswitch *fs) +{ + if (fs->ahb_clk) + clk_disable_unprepare(fs->ahb_clk); + clk_disable_unprepare(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, _src_clk, _rate, _ahb_clk) \ + [_id] = { \ + .desc = { \ + .id = _id, \ + .name = _name, \ + .ops = &footswitch_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + .pcom_id = _pcom_id, \ + .has_src_clk = _src_clk, \ + .src_clk_init_rate = _rate, \ + .has_ahb_clk = _ahb_clk, \ + } +static struct footswitch footswitches[] = { + FOOTSWITCH(FS_GFX3D, PCOM_FS_GRP, + "fs_gfx3d", true, 24576000, true), + FOOTSWITCH(FS_GFX2D0, PCOM_FS_GRP_2D, + "fs_gfx2d0", false, 24576000, true), + FOOTSWITCH(FS_MDP, PCOM_FS_MDP, + "fs_mdp", false, 24576000, true), + FOOTSWITCH(FS_MFC, PCOM_FS_MFC, + "fs_mfc", false, 24576000, true), + FOOTSWITCH(FS_ROT, PCOM_FS_ROTATOR, + "fs_rot", false, 0, true), + FOOTSWITCH(FS_VFE, PCOM_FS_VFE, + "fs_vfe", false, 24576000, true), + FOOTSWITCH(FS_VPE, PCOM_FS_VPE, + "fs_vpe", false, 24576000, false), +}; + +static int get_clocks(struct device *dev, 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->has_src_clk) { + fs->src_clk = clk_get(dev, "src_clk"); + if (IS_ERR(fs->src_clk)) + fs->src_clk = clk_get(dev, "core_clk"); + } else { + fs->src_clk = clk_get(dev, "core_clk"); + } + if (IS_ERR(fs->src_clk)) { + pr_err("%s clk_get(src_clk) failed\n", fs->desc.name); + rc = PTR_ERR(fs->src_clk); + goto err_src_clk; + } + + fs->core_clk = clk_get(dev, "core_clk"); + if (IS_ERR(fs->core_clk)) { + pr_err("%s clk_get(core_clk) failed\n", fs->desc.name); + rc = PTR_ERR(fs->core_clk); + goto err_core_clk; + } + + if (fs->has_ahb_clk) { + fs->ahb_clk = clk_get(dev, "iface_clk"); + if (IS_ERR(fs->ahb_clk)) { + pr_err("%s clk_get(iface_clk) failed\n", fs->desc.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->src_clk); +err_src_clk: + return rc; +} + +static void put_clocks(struct footswitch *fs) +{ + clk_put(fs->src_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(&pdev->dev, fs); + if (rc) + return rc; + + fs->rdev = regulator_register(&fs->desc, &pdev->dev, + init_data, fs, NULL); + 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("GPL v2"); +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 0000000000000000000000000000000000000000..1809b2e50eb531ced084276f2a02b319ce43dcf2 --- /dev/null +++ b/arch/arm/mach-msm/footswitch.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2010-2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +/* Device IDs */ +#define FS_GFX2D0 0 +#define FS_GFX2D1 1 +#define FS_GFX3D 2 +#define FS_IJPEG 3 +#define FS_MDP 4 +#define FS_MFC 5 +#define FS_ROT 6 +#define FS_VED 7 +#define FS_VFE 8 +#define FS_VPE 9 +#define FS_VCAP 10 +#define MAX_FS 11 + +struct fs_clk_data { + const char *name; + struct clk *clk; + unsigned long rate; + unsigned long reset_rate; + bool enabled; +}; + +struct fs_driver_data { + int bus_port0, bus_port1; + struct fs_clk_data *clks; +}; + +#define FS_GENERIC(_drv_name, _id, _name, _dev_id, _data) \ +(&(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), (_dev_id)), \ + .driver_data = (_data), \ + } \ + }, \ +}) +#define FS_PCOM(_id, _name, _dev_id) \ + FS_GENERIC("footswitch-pcom", _id, _name, _dev_id, NULL) +#define FS_8X60(_id, _name, _dev_id, _data) \ + FS_GENERIC("footswitch-8x60", _id, _name, _dev_id, _data) + +#endif diff --git a/arch/arm/mach-msm/gdsc.c b/arch/arm/mach-msm/gdsc.c new file mode 100644 index 0000000000000000000000000000000000000000..df3a92d2651b9047944b8d9763bc083ca2b94101 --- /dev/null +++ b/arch/arm/mach-msm/gdsc.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 PWR_ON_MASK BIT(31) +#define EN_REST_WAIT_MASK (0xF << 20) +#define EN_FEW_WAIT_MASK (0xF << 16) +#define CLK_DIS_WAIT_MASK (0xF << 12) +#define SW_OVERRIDE_MASK BIT(2) +#define HW_CONTROL_MASK BIT(1) +#define SW_COLLAPSE_MASK BIT(0) + +/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */ +#define EN_REST_WAIT_VAL (0x2 << 20) +#define EN_FEW_WAIT_VAL (0x2 << 16) +#define CLK_DIS_WAIT_VAL (0x2 << 12) + +#define TIMEOUT_US 10 + +struct gdsc { + struct regulator_dev *rdev; + struct regulator_desc rdesc; + void __iomem *gdscr; +}; + +static int gdsc_is_enabled(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + + return !!(readl_relaxed(sc->gdscr) & PWR_ON_MASK); +} + +static int gdsc_enable(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + int ret; + + regval = readl_relaxed(sc->gdscr); + regval &= ~SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + + ret = readl_tight_poll_timeout(sc->gdscr, regval, regval & PWR_ON_MASK, + TIMEOUT_US); + if (ret) + dev_err(&rdev->dev, "%s enable timed out\n", sc->rdesc.name); + + return ret; +} + +static int gdsc_disable(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + int ret; + + regval = readl_relaxed(sc->gdscr); + regval |= SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + + ret = readl_tight_poll_timeout(sc->gdscr, regval, + !(regval & PWR_ON_MASK), TIMEOUT_US); + if (ret) + dev_err(&rdev->dev, "%s disable timed out\n", sc->rdesc.name); + + return ret; +} + +static struct regulator_ops gdsc_ops = { + .is_enabled = gdsc_is_enabled, + .enable = gdsc_enable, + .disable = gdsc_disable, +}; + +static int __devinit gdsc_probe(struct platform_device *pdev) +{ + static atomic_t gdsc_count = ATOMIC_INIT(-1); + struct regulator_init_data *init_data; + struct resource *res; + struct gdsc *sc; + uint32_t regval; + int ret; + + sc = devm_kzalloc(&pdev->dev, sizeof(struct gdsc), GFP_KERNEL); + if (sc == NULL) + return -ENOMEM; + + init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node); + if (init_data == NULL) + return -ENOMEM; + + if (of_get_property(pdev->dev.of_node, "parent-supply", NULL)) + init_data->supply_regulator = "parent"; + + ret = of_property_read_string(pdev->dev.of_node, "regulator-name", + &sc->rdesc.name); + if (ret) + return ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -EINVAL; + sc->gdscr = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (sc->gdscr == NULL) + return -ENOMEM; + + sc->rdesc.id = atomic_inc_return(&gdsc_count); + sc->rdesc.ops = &gdsc_ops; + sc->rdesc.type = REGULATOR_VOLTAGE; + sc->rdesc.owner = THIS_MODULE; + platform_set_drvdata(pdev, sc); + + /* + * Disable HW trigger: collapse/restore occur based on registers writes. + * Disable SW override: Use hardware state-machine for sequencing. + */ + regval = readl_relaxed(sc->gdscr); + regval &= ~(HW_CONTROL_MASK | SW_OVERRIDE_MASK); + + /* Configure wait time between states. */ + regval &= ~(EN_REST_WAIT_MASK | EN_FEW_WAIT_MASK | CLK_DIS_WAIT_MASK); + regval |= EN_REST_WAIT_VAL | EN_FEW_WAIT_VAL | CLK_DIS_WAIT_VAL; + writel_relaxed(regval, sc->gdscr); + + sc->rdev = regulator_register(&sc->rdesc, &pdev->dev, init_data, sc, + pdev->dev.of_node); + if (IS_ERR(sc->rdev)) { + dev_err(&pdev->dev, "regulator_register(\"%s\") failed.\n", + sc->rdesc.name); + return PTR_ERR(sc->rdev); + } + + return 0; +} + +static int __devexit gdsc_remove(struct platform_device *pdev) +{ + struct gdsc *sc = platform_get_drvdata(pdev); + regulator_unregister(sc->rdev); + return 0; +} + +static struct of_device_id gdsc_match_table[] = { + { .compatible = "qcom,gdsc" }, + {} +}; + +static struct platform_driver gdsc_driver = { + .probe = gdsc_probe, + .remove = __devexit_p(gdsc_remove), + .driver = { + .name = "gdsc", + .of_match_table = gdsc_match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init gdsc_init(void) +{ + return platform_driver_register(&gdsc_driver); +} +subsys_initcall(gdsc_init); + +static void __exit gdsc_exit(void) +{ + platform_driver_unregister(&gdsc_driver); +} +module_exit(gdsc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Copper GDSC power rail regulator driver"); diff --git a/arch/arm/mach-msm/gpio.h b/arch/arm/mach-msm/gpio.h new file mode 100644 index 0000000000000000000000000000000000000000..59ee8f83270fef74dd63e4d5959905c71e8b60b2 --- /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/gpiomux-7x27.c b/arch/arm/mach-msm/gpiomux-7x27.c new file mode 100644 index 0000000000000000000000000000000000000000..822cd0419a316c0924255ff742f48d3a8732d5ba --- /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 + +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 0000000000000000000000000000000000000000..822cd0419a316c0924255ff742f48d3a8732d5ba --- /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 + +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 f7a4ea593c95054591120d34a23c3f6a32e1cffb..822cd0419a316c0924255ff742f48d3a8732d5ba 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 "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) +#include +#include +#include -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 7b380b31bd0e26cfd44879cca656466b556b38b3..c23a41c76552fde2bff7bc6d1091e55c3c06de50 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,1724 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "gpiomux.h" +#include +#include +#include +#include +#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_UP, +}; + +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_UP, +}; + +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_DOWN, +}; + +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}, +}; + +struct msm_gpiomux_configs +msm8x60_dragon_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}, +}; + +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 0000000000000000000000000000000000000000..cacd1ba48203942a31256cab155baf9876e53207 --- /dev/null +++ b/arch/arm/mach-msm/gpiomux-8x60.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 __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; +extern struct msm_gpiomux_configs msm8x60_dragon_gpiomux_cfgs[] __initdata; + +#endif diff --git a/arch/arm/mach-msm/gpiomux-v1.c b/arch/arm/mach-msm/gpiomux-v1.c index 27de2abd714490fde3813950821e02d8eaaaffa6..1163669f3fb8878d03796846121516e535d90cc3 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 "gpiomux.h" -#include "proc_comm.h" +#include +#include +#include +#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 71d86feba4509615ad12439476377d3ee9d3b944..7cf4582311df183ffaf6aa41456fdd9489e6208d 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 273396d2b12779578a8ce0692941059e68fc1a42..ee1e17a916fe889dfefb664b9f6c1abb1513219d 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" +#include + +#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, gpiomux_config_t val) +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 3bf10e7f03815f65e0b24fb39b8375c96b010117..b200501788b70e091861078c9d9d29cc267b7617 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 53af21abd155bba6c15f2ad281c00e03cfb05067..85936ba672d76c39afd8dba64167c57030ddfc0b 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" +#include +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 deleted file mode 100644 index 00459f6ee13c337ede196e8f512234128b2a391a..0000000000000000000000000000000000000000 --- a/arch/arm/mach-msm/gpiomux.h +++ /dev/null @@ -1,99 +0,0 @@ -/* 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. - */ -#ifndef __ARCH_ARM_MACH_MSM_GPIOMUX_H -#define __ARCH_ARM_MACH_MSM_GPIOMUX_H - -#include -#include -#include - -#if defined(CONFIG_MSM_V2_TLMM) -#include "gpiomux-v2.h" -#else -#include "gpiomux-v1.h" -#endif - -/** - * 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. - * - * @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. - */ -struct msm_gpiomux_config { - gpiomux_config_t active; - gpiomux_config_t suspended; - unsigned ref; -}; - -/** - * @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. - */ -enum { - GPIOMUX_VALID = BIT(sizeof(gpiomux_config_t) * BITS_PER_BYTE - 1), - GPIOMUX_CTL_MASK = GPIOMUX_VALID, -}; - -#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. - */ -extern struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS]; - -/* Install a new configuration to the gpio line. To avoid overwriting - * a configuration, leave the VALID bit out. - */ -int msm_gpiomux_write(unsigned gpio, - gpiomux_config_t active, - gpiomux_config_t suspended); - -/* Architecture-internal function for use by the framework only. - * This function can assume the following: - * - the gpio value has passed a bounds-check - * - the gpiomux spinlock has been obtained - * - * This function is not for public consumption. External users - * should use msm_gpiomux_write. - */ -void __msm_gpiomux_write(unsigned gpio, gpiomux_config_t val); -#else -static inline int msm_gpiomux_write(unsigned gpio, - gpiomux_config_t active, - gpiomux_config_t suspended) -{ - return -ENOSYS; -} -#endif -#endif diff --git a/arch/arm/mach-msm/gss-8064.c b/arch/arm/mach-msm/gss-8064.c new file mode 100644 index 0000000000000000000000000000000000000000..126f8e0aefc1d1778822cccd58e7ed624c10ea16 --- /dev/null +++ b/arch/arm/mach-msm/gss-8064.c @@ -0,0 +1,289 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "smd_private.h" +#include "modem_notifier.h" +#include "ramdump.h" + +static struct gss_8064_data { + struct miscdevice gss_dev; + void *pil_handle; + void *gss_ramdump_dev; + void *smem_ramdump_dev; +} gss_data; + +static int crash_shutdown; + +#define MAX_SSR_REASON_LEN 81U + +static void log_gss_sfr(void) +{ + u32 size; + char *smem_reason, reason[MAX_SSR_REASON_LEN]; + + smem_reason = smem_get_entry(SMEM_SSR_REASON_MSS0, &size); + if (!smem_reason || !size) { + pr_err("GSS subsystem failure reason: (unknown, smem_get_entry failed).\n"); + return; + } + if (!smem_reason[0]) { + pr_err("GSS subsystem failure reason: (unknown, init string found).\n"); + return; + } + + size = min(size, MAX_SSR_REASON_LEN-1); + memcpy(reason, smem_reason, size); + reason[size] = '\0'; + pr_err("GSS subsystem failure reason: %s.\n", reason); + + smem_reason[0] = '\0'; + wmb(); +} + +static void gss_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 gss_state; + + pr_err("Watchdog bite received from GSS!\n"); + + gss_state = smsm_get_state(SMSM_MODEM_STATE); + + if (gss_state & panic_smsm_states) { + + pr_err("GSS SMSM state changed to SMSM_RESET.\n" + "Probable err_fatal on the GSS. " + "Calling subsystem restart...\n"); + log_gss_sfr(); + subsystem_restart("gss"); + + } else if (gss_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_ */ + log_gss_sfr(); + subsystem_restart("gss"); + } +} + +static DECLARE_WORK(gss_fatal_work, gss_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("GSS SMSM state changed to SMSM_RESET.\n" + "Probable err_fatal on the GSS. " + "Calling subsystem restart...\n"); + log_gss_sfr(); + subsystem_restart("gss"); + } +} + +#define Q6_FW_WDOG_ENABLE 0x08882024 +#define Q6_SW_WDOG_ENABLE 0x08982024 +static int gss_shutdown(const struct subsys_data *subsys) +{ + pil_force_shutdown("gss"); + disable_irq_nosync(GSS_A5_WDOG_EXPIRED); + + return 0; +} + +static int gss_powerup(const struct subsys_data *subsys) +{ + pil_force_boot("gss"); + enable_irq(GSS_A5_WDOG_EXPIRED); + return 0; +} + +void gss_crash_shutdown(const struct subsys_data *subsys) +{ + crash_shutdown = 1; + smsm_reset_modem(SMSM_RESET); +} + +/* FIXME: Get address, size from PIL */ +static struct ramdump_segment gss_segments[] = { + {0x89000000, 0x00D00000} +}; + +static struct ramdump_segment smem_segments[] = { + {0x80000000, 0x00200000}, +}; + +static int gss_ramdump(int enable, + const struct subsys_data *crashed_subsys) +{ + int ret = 0; + + if (enable) { + ret = do_ramdump(gss_data.gss_ramdump_dev, gss_segments, + ARRAY_SIZE(gss_segments)); + + if (ret < 0) { + pr_err("Unable to dump gss memory (rc = %d).\n", + ret); + goto out; + } + + ret = do_ramdump(gss_data.smem_ramdump_dev, smem_segments, + ARRAY_SIZE(smem_segments)); + + if (ret < 0) { + pr_err("Unable to dump smem memory (rc = %d).\n", ret); + goto out; + } + } + +out: + return ret; +} + +static irqreturn_t gss_wdog_bite_irq(int irq, void *dev_id) +{ + schedule_work(&gss_fatal_work); + disable_irq_nosync(GSS_A5_WDOG_EXPIRED); + + return IRQ_HANDLED; +} + +static struct subsys_data gss_8064 = { + .name = "gss", + .shutdown = gss_shutdown, + .powerup = gss_powerup, + .ramdump = gss_ramdump, + .crash_shutdown = gss_crash_shutdown +}; + +static int gss_subsystem_restart_init(void) +{ + return ssr_register_subsystem(&gss_8064); +} + +static int gss_open(struct inode *inode, struct file *filep) +{ + void *ret; + gss_data.pil_handle = ret = pil_get("gss"); + if (!ret) + pr_debug("%s - pil_get returned NULL\n", __func__); + return 0; +} + +static int gss_release(struct inode *inode, struct file *filep) +{ + pil_put(gss_data.pil_handle); + pr_debug("%s pil_put called on GSS\n", __func__); + return 0; +} + +const struct file_operations gss_file_ops = { + .open = gss_open, + .release = gss_release, +}; + +static int __init gss_8064_init(void) +{ + int ret; + + if (!cpu_is_apq8064()) + return -ENODEV; + + 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(GSS_A5_WDOG_EXPIRED, gss_wdog_bite_irq, + IRQF_TRIGGER_RISING, "gss_a5_wdog", NULL); + + if (ret < 0) { + pr_err("%s: Unable to request gss watchdog IRQ. (%d)\n", + __func__, ret); + disable_irq_nosync(GSS_A5_WDOG_EXPIRED); + goto out; + } + + ret = gss_subsystem_restart_init(); + + if (ret < 0) { + pr_err("%s: Unable to reg with subsystem restart. (%d)\n", + __func__, ret); + goto out; + } + + gss_data.gss_dev.minor = MISC_DYNAMIC_MINOR; + gss_data.gss_dev.name = "gss"; + gss_data.gss_dev.fops = &gss_file_ops; + ret = misc_register(&gss_data.gss_dev); + + if (ret) { + pr_err("%s: misc_registers failed for %s (%d)", __func__, + gss_data.gss_dev.name, ret); + goto out; + } + + gss_data.gss_ramdump_dev = create_ramdump_device("gss"); + + if (!gss_data.gss_ramdump_dev) { + pr_err("%s: Unable to create gss ramdump device. (%d)\n", + __func__, -ENOMEM); + ret = -ENOMEM; + goto out; + } + + gss_data.smem_ramdump_dev = create_ramdump_device("smem"); + + if (!gss_data.smem_ramdump_dev) { + pr_err("%s: Unable to create smem ramdump device. (%d)\n", + __func__, -ENOMEM); + ret = -ENOMEM; + goto out; + } + + pr_info("%s: gss fatal driver init'ed.\n", __func__); +out: + return ret; +} + +module_init(gss_8064_init); diff --git a/arch/arm/mach-msm/headsmp.S b/arch/arm/mach-msm/headsmp.S index bcd5af223deabaf48451bf3a4d67cea23c82566e..50d20607330f9ca8636a3519140a91755ad0daa7 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, 2012 Code Aurora Forum. All rights reserved. * * 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,31 +10,39 @@ #include #include - __CPUINIT +__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 ENDPROC(msm_secondary_startup) - .align 1: .long . .long pen_release diff --git a/arch/arm/mach-msm/hotplug.c b/arch/arm/mach-msm/hotplug.c index a446fc14221f6f0394806fb32af411ef2742e85e..46e835ff7b05b4d007276e0fd0cd1d9789fa3785 100644 --- a/arch/arm/mach-msm/hotplug.c +++ b/arch/arm/mach-msm/hotplug.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2002 ARM Ltd. * All Rights Reserved + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. * * 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,12 +10,28 @@ #include #include #include +#include #include #include +#include + +#include +#include + +#include "pm.h" +#include "spm.h" extern volatile int pen_release; +struct msm_hotplug_device { + struct completion cpu_killed; + unsigned int warm_boot; +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_hotplug_device, + msm_hotplug_devices); + static inline void cpu_enter_lowpower(void) { /* Just flush the cache. Changing the coherency is not yet @@ -30,18 +47,15 @@ static inline void platform_do_lowpower(unsigned int cpu) { /* Just enter wfi for now. TODO: Properly shut off the cpu. */ for (;;) { - /* - * here's the WFI - */ - asm("wfi" - : - : - : "memory", "cc"); + msm_pm_cpu_enter_lowpower(cpu); if (pen_release == cpu_logical_map(cpu)) { /* * OK, proper wakeup, we're done */ + pen_release = -1; + dmac_flush_range((void *)&pen_release, + (void *)(&pen_release + sizeof(pen_release))); break; } @@ -53,6 +67,8 @@ static inline void platform_do_lowpower(unsigned int cpu) * possible, since we are currently running incoherently, and * therefore cannot safely call printk() or anything else */ + dmac_inv_range((void *)&pen_release, + (void *)(&pen_release + sizeof(pen_release))); pr_debug("CPU%u: spurious wakeup call\n", cpu); } } @@ -69,16 +85,19 @@ int platform_cpu_kill(unsigned int cpu) */ void platform_cpu_die(unsigned int cpu) { + if (unlikely(cpu != smp_processor_id())) { + pr_crit("%s: running on %u, should be %u\n", + __func__, smp_processor_id(), cpu); + BUG(); + } + complete(&__get_cpu_var(msm_hotplug_devices).cpu_killed); /* * we're ready for shutdown now, so do it */ cpu_enter_lowpower(); platform_do_lowpower(cpu); - /* - * bring this CPU back into the world of cache - * coherency, and then restore interrupts - */ + pr_debug("CPU%u: %s: normal wakeup\n", cpu, __func__); cpu_leave_lowpower(); } @@ -90,3 +109,70 @@ int platform_cpu_disable(unsigned int cpu) */ return cpu == 0 ? -EPERM : 0; } + +#define CPU_SHIFT 0 +#define CPU_MASK 0xF +#define CPU_OF(n) (((n) & CPU_MASK) << CPU_SHIFT) +#define CPUSET_SHIFT 4 +#define CPUSET_MASK 0xFFFF +#define CPUSET_OF(n) (((n) & CPUSET_MASK) << CPUSET_SHIFT) + +static int hotplug_rtb_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + /* + * Bits [19:4] of the data are the online mask, lower 4 bits are the + * cpu number that is being changed. Additionally, changes to the + * online_mask that will be done by the current hotplug will be made + * even though they aren't necessarily in the online mask yet. + * + * XXX: This design is limited to supporting at most 16 cpus + */ + int this_cpumask = CPUSET_OF(1 << (int)hcpu); + int cpumask = CPUSET_OF(cpumask_bits(cpu_online_mask)[0]); + int cpudata = CPU_OF((int)hcpu) | cpumask; + + switch (action & (~CPU_TASKS_FROZEN)) { + case CPU_STARTING: + uncached_logk(LOGK_HOTPLUG, (void *)(cpudata | this_cpumask)); + break; + case CPU_DYING: + uncached_logk(LOGK_HOTPLUG, (void *)(cpudata & ~this_cpumask)); + break; + default: + break; + } + + return NOTIFY_OK; +} +static struct notifier_block hotplug_rtb_notifier = { + .notifier_call = hotplug_rtb_callback, +}; + +int msm_platform_secondary_init(unsigned int cpu) +{ + int ret; + struct msm_hotplug_device *dev = &__get_cpu_var(msm_hotplug_devices); + + if (!dev->warm_boot) { + dev->warm_boot = 1; + init_completion(&dev->cpu_killed); + return 0; + } + msm_jtag_restore_state(); +#if defined(CONFIG_VFP) && defined (CONFIG_CPU_PM) + vfp_pm_resume(); +#endif + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false); + + return ret; +} + +static int __init init_hotplug(void) +{ + + struct msm_hotplug_device *dev = &__get_cpu_var(msm_hotplug_devices); + init_completion(&dev->cpu_killed); + return register_hotcpu_notifier(&hotplug_rtb_notifier); +} +early_initcall(init_hotplug); diff --git a/arch/arm/mach-msm/hsic_sysmon.c b/arch/arm/mach-msm/hsic_sysmon.c new file mode 100644 index 0000000000000000000000000000000000000000..2dedbacc8c5eec98f222cef369abd449025c1fa1 --- /dev/null +++ b/arch/arm/mach-msm/hsic_sysmon.c @@ -0,0 +1,449 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* add additional information to our printk's */ +#define pr_fmt(fmt) "%s: " fmt "\n", __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hsic_sysmon.h" +#include "sysmon.h" + +#define DRIVER_DESC "HSIC System monitor driver" + +enum hsic_sysmon_op { + HSIC_SYSMON_OP_READ = 0, + HSIC_SYSMON_OP_WRITE, + NUM_OPS +}; + +struct hsic_sysmon { + struct usb_device *udev; + struct usb_interface *ifc; + __u8 in_epaddr; + __u8 out_epaddr; + unsigned int pipe[NUM_OPS]; + struct kref kref; + struct platform_device pdev; + int id; + + /* debugging counters */ + atomic_t dbg_bytecnt[NUM_OPS]; + atomic_t dbg_pending[NUM_OPS]; +}; +static struct hsic_sysmon *hsic_sysmon_devices[NUM_HSIC_SYSMON_DEVS]; + +static void hsic_sysmon_delete(struct kref *kref) +{ + struct hsic_sysmon *hs = container_of(kref, struct hsic_sysmon, kref); + + usb_put_dev(hs->udev); + hsic_sysmon_devices[hs->id] = NULL; + kfree(hs); +} + +/** + * hsic_sysmon_open() - Opens the system monitor bridge. + * @id: the HSIC system monitor device to open + * + * This should only be called after the platform_device "sys_mon" with id + * SYSMON_SS_EXT_MODEM has been added. The simplest way to do that is to + * register a platform_driver and its probe will be called when the HSIC + * device is ready. + */ +int hsic_sysmon_open(enum hsic_sysmon_device_id id) +{ + struct hsic_sysmon *hs; + + if (id >= NUM_HSIC_SYSMON_DEVS) { + pr_err("invalid dev id(%d)", id); + return -ENODEV; + } + + hs = hsic_sysmon_devices[id]; + if (!hs) { + pr_err("dev is null"); + return -ENODEV; + } + + kref_get(&hs->kref); + + return 0; +} +EXPORT_SYMBOL(hsic_sysmon_open); + +/** + * hsic_sysmon_close() - Closes the system monitor bridge. + * @id: the HSIC system monitor device to close + */ +void hsic_sysmon_close(enum hsic_sysmon_device_id id) +{ + struct hsic_sysmon *hs; + + if (id >= NUM_HSIC_SYSMON_DEVS) { + pr_err("invalid dev id(%d)", id); + return; + } + + hs = hsic_sysmon_devices[id]; + kref_put(&hs->kref, hsic_sysmon_delete); +} +EXPORT_SYMBOL(hsic_sysmon_close); + +/** + * hsic_sysmon_readwrite() - Common function to send read/write over HSIC + */ +static int hsic_sysmon_readwrite(enum hsic_sysmon_device_id id, void *data, + size_t len, size_t *actual_len, int timeout, + enum hsic_sysmon_op op) +{ + struct hsic_sysmon *hs; + int ret; + const char *opstr = (op == HSIC_SYSMON_OP_READ) ? + "read" : "write"; + + pr_debug("%s: id:%d, data len:%d, timeout:%d", opstr, id, len, timeout); + + if (id >= NUM_HSIC_SYSMON_DEVS) { + pr_err("invalid dev id(%d)", id); + return -ENODEV; + } + + if (!len) { + pr_err("length(%d) must be greater than 0", len); + return -EINVAL; + } + + hs = hsic_sysmon_devices[id]; + if (!hs) { + pr_err("device was not opened"); + return -ENODEV; + } + + if (!hs->ifc) { + dev_err(&hs->udev->dev, "can't %s, device disconnected\n", + opstr); + return -ENODEV; + } + + ret = usb_autopm_get_interface(hs->ifc); + if (ret < 0) { + dev_err(&hs->udev->dev, "can't %s, autopm_get failed:%d\n", + opstr, ret); + return ret; + } + + atomic_inc(&hs->dbg_pending[op]); + + ret = usb_bulk_msg(hs->udev, hs->pipe[op], data, len, actual_len, + timeout); + + atomic_dec(&hs->dbg_pending[op]); + + if (ret) + dev_err(&hs->udev->dev, + "can't %s, usb_bulk_msg failed, err:%d\n", opstr, ret); + else + atomic_add(*actual_len, &hs->dbg_bytecnt[op]); + + usb_autopm_put_interface(hs->ifc); + return ret; +} + +/** + * hsic_sysmon_read() - Read data from the HSIC sysmon interface. + * @id: the HSIC system monitor device to open + * @data: pointer to caller-allocated buffer to fill in + * @len: length in bytes of the buffer + * @actual_len: pointer to a location to put the actual length read + * in bytes + * @timeout: time in msecs to wait for the message to complete before + * timing out (if 0 the wait is forever) + * + * Context: !in_interrupt () + * + * Synchronously reads data from the HSIC interface. The call will return + * after the read has completed, encountered an error, or timed out. Upon + * successful return actual_len will reflect the number of bytes read. + * + * If successful, it returns 0, otherwise a negative error number. The number + * of actual bytes transferred will be stored in the actual_len paramater. + */ +int hsic_sysmon_read(enum hsic_sysmon_device_id id, char *data, size_t len, + size_t *actual_len, int timeout) +{ + return hsic_sysmon_readwrite(id, data, len, actual_len, + timeout, HSIC_SYSMON_OP_READ); +} +EXPORT_SYMBOL(hsic_sysmon_read); + +/** + * hsic_sysmon_write() - Write data to the HSIC sysmon interface. + * @id: the HSIC system monitor device to open + * @data: pointer to caller-allocated buffer to write + * @len: length in bytes of the data in buffer to write + * @actual_len: pointer to a location to put the actual length written + * in bytes + * @timeout: time in msecs to wait for the message to complete before + * timing out (if 0 the wait is forever) + * + * Context: !in_interrupt () + * + * Synchronously writes data to the HSIC interface. The call will return + * after the write has completed, encountered an error, or timed out. Upon + * successful return actual_len will reflect the number of bytes written. + * + * If successful, it returns 0, otherwise a negative error number. The number + * of actual bytes transferred will be stored in the actual_len paramater. + */ +int hsic_sysmon_write(enum hsic_sysmon_device_id id, const char *data, + size_t len, int timeout) +{ + size_t actual_len; + return hsic_sysmon_readwrite(id, (void *)data, len, &actual_len, + timeout, HSIC_SYSMON_OP_WRITE); +} +EXPORT_SYMBOL(hsic_sysmon_write); + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 512 +static ssize_t sysmon_debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + int i, ret = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < NUM_HSIC_SYSMON_DEVS; i++) { + struct hsic_sysmon *hs = hsic_sysmon_devices[i]; + if (!hs) + continue; + + ret += scnprintf(buf, DEBUG_BUF_SIZE, + "---HSIC Sysmon #%d---\n" + "epin:%d, epout:%d\n" + "bytes to host: %d\n" + "bytes to mdm: %d\n" + "pending reads: %d\n" + "pending writes: %d\n", + i, hs->in_epaddr & ~0x80, hs->out_epaddr, + atomic_read( + &hs->dbg_bytecnt[HSIC_SYSMON_OP_READ]), + atomic_read( + &hs->dbg_bytecnt[HSIC_SYSMON_OP_WRITE]), + atomic_read( + &hs->dbg_pending[HSIC_SYSMON_OP_READ]), + atomic_read( + &hs->dbg_pending[HSIC_SYSMON_OP_WRITE]) + ); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t sysmon_debug_reset_stats(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + int i; + + for (i = 0; i < NUM_HSIC_SYSMON_DEVS; i++) { + struct hsic_sysmon *hs = hsic_sysmon_devices[i]; + if (hs) { + atomic_set(&hs->dbg_bytecnt[HSIC_SYSMON_OP_READ], 0); + atomic_set(&hs->dbg_bytecnt[HSIC_SYSMON_OP_WRITE], 0); + atomic_set(&hs->dbg_pending[HSIC_SYSMON_OP_READ], 0); + atomic_set(&hs->dbg_pending[HSIC_SYSMON_OP_WRITE], 0); + } + } + + return count; +} + +const struct file_operations sysmon_stats_ops = { + .read = sysmon_debug_read_stats, + .write = sysmon_debug_reset_stats, +}; + +static struct dentry *dent; + +static void hsic_sysmon_debugfs_init(void) +{ + struct dentry *dfile; + + dent = debugfs_create_dir("hsic_sysmon", 0); + if (IS_ERR(dent)) + return; + + dfile = debugfs_create_file("status", 0444, dent, 0, &sysmon_stats_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} + +static void hsic_sysmon_debugfs_cleanup(void) +{ + if (dent) { + debugfs_remove_recursive(dent); + dent = NULL; + } +} +#else +static inline void hsic_sysmon_debugfs_init(void) { } +static inline void hsic_sysmon_debugfs_cleanup(void) { } +#endif + +static int +hsic_sysmon_probe(struct usb_interface *ifc, const struct usb_device_id *id) +{ + struct hsic_sysmon *hs; + struct usb_host_interface *ifc_desc; + struct usb_endpoint_descriptor *ep_desc; + int i; + int ret = -ENOMEM; + __u8 ifc_num; + + pr_debug("id:%lu", id->driver_info); + + ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber; + + /* is this the interface we're looking for? */ + if (ifc_num != id->driver_info) + return -ENODEV; + + hs = kzalloc(sizeof(*hs), GFP_KERNEL); + if (!hs) { + pr_err("unable to allocate hsic_sysmon"); + return -ENOMEM; + } + + hs->udev = usb_get_dev(interface_to_usbdev(ifc)); + hs->ifc = ifc; + kref_init(&hs->kref); + + ifc_desc = ifc->cur_altsetting; + for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) { + ep_desc = &ifc_desc->endpoint[i].desc; + + if (!hs->in_epaddr && usb_endpoint_is_bulk_in(ep_desc)) { + hs->in_epaddr = ep_desc->bEndpointAddress; + hs->pipe[HSIC_SYSMON_OP_READ] = + usb_rcvbulkpipe(hs->udev, hs->in_epaddr); + } + + if (!hs->out_epaddr && usb_endpoint_is_bulk_out(ep_desc)) { + hs->out_epaddr = ep_desc->bEndpointAddress; + hs->pipe[HSIC_SYSMON_OP_WRITE] = + usb_sndbulkpipe(hs->udev, hs->out_epaddr); + } + } + + if (!(hs->in_epaddr && hs->out_epaddr)) { + pr_err("could not find bulk in and bulk out endpoints"); + ret = -ENODEV; + goto error; + } + + hs->id = HSIC_SYSMON_DEV_EXT_MODEM; + hsic_sysmon_devices[HSIC_SYSMON_DEV_EXT_MODEM] = hs; + usb_set_intfdata(ifc, hs); + + hs->pdev.name = "sys_mon"; + hs->pdev.id = SYSMON_SS_EXT_MODEM; + platform_device_register(&hs->pdev); + + pr_debug("complete"); + + return 0; + +error: + if (hs) + kref_put(&hs->kref, hsic_sysmon_delete); + + return ret; +} + +static void hsic_sysmon_disconnect(struct usb_interface *ifc) +{ + struct hsic_sysmon *hs = usb_get_intfdata(ifc); + + platform_device_unregister(&hs->pdev); + kref_put(&hs->kref, hsic_sysmon_delete); + usb_set_intfdata(ifc, NULL); +} + +static int hsic_sysmon_suspend(struct usb_interface *ifc, pm_message_t message) +{ + return 0; +} + +static int hsic_sysmon_resume(struct usb_interface *ifc) +{ + return 0; +} + +/* driver_info maps to the interface number corresponding to sysmon */ +static const struct usb_device_id hsic_sysmon_ids[] = { + { USB_DEVICE(0x5c6, 0x9048), .driver_info = 1, }, + { USB_DEVICE(0x5c6, 0x904C), .driver_info = 1, }, + {} /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, hsic_sysmon_ids); + +static struct usb_driver hsic_sysmon_driver = { + .name = "hsic_sysmon", + .probe = hsic_sysmon_probe, + .disconnect = hsic_sysmon_disconnect, + .suspend = hsic_sysmon_suspend, + .resume = hsic_sysmon_resume, + .id_table = hsic_sysmon_ids, + .supports_autosuspend = 1, +}; + +static int __init hsic_sysmon_init(void) +{ + int ret; + + ret = usb_register(&hsic_sysmon_driver); + if (ret) { + pr_err("unable to register " DRIVER_DESC); + return ret; + } + + hsic_sysmon_debugfs_init(); + return 0; +} + +static void __exit hsic_sysmon_exit(void) +{ + hsic_sysmon_debugfs_cleanup(); + usb_deregister(&hsic_sysmon_driver); +} + +module_init(hsic_sysmon_init); +module_exit(hsic_sysmon_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/hsic_sysmon.h b/arch/arm/mach-msm/hsic_sysmon.h new file mode 100644 index 0000000000000000000000000000000000000000..aa57b93281aa1f70d789cdc733bb798a2f882862 --- /dev/null +++ b/arch/arm/mach-msm/hsic_sysmon.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __HSIC_SYSMON_H__ +#define __HSIC_SYSMON_H__ + +/** + * enum hsic_sysmon_device_id - Supported HSIC subsystem devices + */ +enum hsic_sysmon_device_id { + HSIC_SYSMON_DEV_EXT_MODEM, + NUM_HSIC_SYSMON_DEVS +}; + +#if defined(CONFIG_MSM_HSIC_SYSMON) || defined(CONFIG_MSM_HSIC_SYSMON_MODULE) + +extern int hsic_sysmon_open(enum hsic_sysmon_device_id id); +extern void hsic_sysmon_close(enum hsic_sysmon_device_id id); +extern int hsic_sysmon_read(enum hsic_sysmon_device_id id, char *data, + size_t len, size_t *actual_len, int timeout); +extern int hsic_sysmon_write(enum hsic_sysmon_device_id id, const char *data, + size_t len, int timeout); + +#else /* CONFIG_MSM_HSIC_SYSMON || CONFIG_MSM_HSIC_SYSMON_MODULE */ + +static inline int hsic_sysmon_open(enum hsic_sysmon_device_id id) +{ + return -ENODEV; +} + +static inline void hsic_sysmon_close(enum hsic_sysmon_device_id id) { } + +static inline int hsic_sysmon_read(enum hsic_sysmon_device_id id, char *data, + size_t len, size_t *actual_len, int timeout) +{ + return -ENODEV; +} + +static inline int hsic_sysmon_write(enum hsic_sysmon_device_id id, + const char *data, size_t len, int timeout) +{ + return -ENODEV; +} + +#endif /* CONFIG_MSM_HSIC_SYSMON || CONFIG_MSM_HSIC_SYSMON_MODULE */ + +#endif /* __HSIC_SYSMON_H__ */ diff --git a/arch/arm/mach-msm/hsic_sysmon_test.c b/arch/arm/mach-msm/hsic_sysmon_test.c new file mode 100644 index 0000000000000000000000000000000000000000..9929cb736ec3a8ee03753f1cafcfb7ab88ce381a --- /dev/null +++ b/arch/arm/mach-msm/hsic_sysmon_test.c @@ -0,0 +1,118 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* add additional information to our printk's */ +#define pr_fmt(fmt) "%s: " fmt "\n", __func__ + +#include +#include +#include +#include +#include + +#include "hsic_sysmon.h" +#include "sysmon.h" + +#define DRIVER_DESC "HSIC System monitor driver test" + +#define RD_BUF_SIZE 4096 + +struct sysmon_test_dev { + int buflen; + char buf[RD_BUF_SIZE]; +}; +static struct sysmon_test_dev *sysmon_dev; + +static ssize_t sysmon_test_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct sysmon_test_dev *dev = sysmon_dev; + int ret; + + if (!dev) + return -ENODEV; + + ret = hsic_sysmon_read(HSIC_SYSMON_DEV_EXT_MODEM, dev->buf, RD_BUF_SIZE, + &dev->buflen, 3000); + if (!ret) + return simple_read_from_buffer(ubuf, count, ppos, + dev->buf, dev->buflen); + + return 0; +} + +static ssize_t sysmon_test_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct sysmon_test_dev *dev = sysmon_dev; + int ret; + + if (!dev) + return -ENODEV; + + if (copy_from_user(dev->buf, ubuf, count)) { + pr_err("error copying for writing"); + return 0; + } + + ret = hsic_sysmon_write(HSIC_SYSMON_DEV_EXT_MODEM, + dev->buf, count, 1000); + if (ret < 0) { + pr_err("error writing to hsic_sysmon"); + return ret; + } + + return count; +} + +static int sysmon_test_open(struct inode *inode, struct file *file) +{ + return hsic_sysmon_open(HSIC_SYSMON_DEV_EXT_MODEM); +} + +static int sysmon_test_release(struct inode *inode, struct file *file) +{ + hsic_sysmon_close(HSIC_SYSMON_DEV_EXT_MODEM); + return 0; +} + +static struct dentry *dfile; +const struct file_operations sysmon_test_ops = { + .read = sysmon_test_read, + .write = sysmon_test_write, + .open = sysmon_test_open, + .release = sysmon_test_release +}; + +static int __init sysmon_test_init(void) +{ + sysmon_dev = kzalloc(sizeof(*sysmon_dev), GFP_KERNEL); + if (!sysmon_dev) + return -ENOMEM; + + dfile = debugfs_create_file("hsic_sysmon_test", 0666, NULL, + 0, &sysmon_test_ops); + return 0; +} + +static void __exit sysmon_test_exit(void) +{ + if (dfile) + debugfs_remove(dfile); + kfree(sysmon_dev); +} + +module_init(sysmon_test_init); +module_exit(sysmon_test_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); 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 0000000000000000000000000000000000000000..3f95ff2bb6d6584080fef3964fec52af04cb70a9 --- /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 0000000000000000000000000000000000000000..3de71dddb589325a573052c35e762caa23a12b73 --- /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 0000000000000000000000000000000000000000..ce3c3a0bbfe2ce742d021982134c934abc9e1c28 --- /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 0000000000000000000000000000000000000000..943083fe0fbe857b6e081e9fda9e9c5bcbeb2de7 --- /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 0000000000000000000000000000000000000000..d49c23e864c3e22b791372ce61c4e148b25bcf72 --- /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 0000000000000000000000000000000000000000..a69a2e1ca5f8b81727006b6aec1eaa2bf0f69e34 --- /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 0000000000000000000000000000000000000000..bd286c9f3a8b183e2400a39f40d8422aaee06b0c --- /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 0000000000000000000000000000000000000000..2ec2c7f4bb1b275caed48c2f5e65c87bd3737757 --- /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 0000000000000000000000000000000000000000..2d381a97c070ebcd53b98e858ae1e65b47da3679 --- /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 0000000000000000000000000000000000000000..c2592ec9efb6d22f46c32dccc503aefc58dd0f30 --- /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-macros.S b/arch/arm/mach-msm/idle-macros.S new file mode 100644 index 0000000000000000000000000000000000000000..1622e13d344045bc3749277376aa6f67d2b686c0 --- /dev/null +++ b/arch/arm/mach-msm/idle-macros.S @@ -0,0 +1,153 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +/* Add 300 NOPs after 'wfi' for 8x25 target */ +.macro DELAY_8x25, rept +#ifdef CONFIG_ARCH_MSM8625 + .rept \rept + nop + .endr +#endif +.endm + +/* Switch between smp_to_amp/amp_to_smp configuration */ +.macro SET_SMP_COHERENCY, on = 0 + ldr r0, =target_type + ldr r0, [r0] + mov r1, #TARGET_IS_8625 + cmp r0, r1 + bne skip\@ + mrc p15, 0, r0, c1, c0, 1 /* read ACTLR register */ + .if \on + orr r0, r0, #(1 << 6) /* Set the SMP bit in ACTLR */ + .else + bic r0, r0, #(1 << 6) /* Clear the SMP bit */ + .endif + mcr p15, 0, r0, c1, c0, 1 /* write ACTLR register */ + isb +skip\@: +.endm + +/* + * Enable the "L2" cache, not require to restore the controller registers + */ +.macro ENABLE_8x25_L2 + ldr r0, =target_type + ldr r0, [r0] + mov r1, #TARGET_IS_8625 + cmp r0, r1 + bne skip_enable\@ + ldr r0, =apps_power_collapse + ldr r0, [r0] + cmp r0, #POWER_COLLAPSED + bne skip_enable\@ + ldr r0, =l2x0_base_addr + ldr r0, [r0] + mov r1, #0x1 + str r1, [r0, #L2X0_CTRL] + dmb +skip_enable\@: +.endm + +/* + * Perform the required operation + * operation: type of operation on l2 cache (e.g: clean&inv or inv) + * l2_enable: enable or disable + */ +.macro DO_CACHE_OPERATION, operation, l2_enable + ldr r2, =l2x0_base_addr + ldr r2, [r2] + ldr r0, =0xffff + str r0, [r2, #\operation] +wait\@: + ldr r0, [r2, #\operation] + ldr r1, =0xffff + ands r0, r0, r1 + bne wait\@ +l2x_sync\@: + mov r0, #0x0 + str r0, [r2, #L2X0_CACHE_SYNC] +sync\@: + ldr r0, [r2, #L2X0_CACHE_SYNC] + ands r0, r0, #0x1 + bne sync\@ + mov r1, #\l2_enable + str r1, [r2, #L2X0_CTRL] +.endm + +/* + * Clean and invalidate the L2 cache. + * 1. Check the target type + * 2. Check whether we are coming from PC are not + * 3. Save 'aux', 'data latency', & 'prefetch ctlr' registers + * 4. Start L2 clean & invalidation operation + * 5. Disable the L2 cache + */ +.macro SUSPEND_8x25_L2 + ldr r0, =target_type + ldr r0, [r0] + mov r1, #TARGET_IS_8625 + cmp r0, r1 + bne skip_suspend\@ + ldr r0, =apps_power_collapse + ldr r0, [r0] + cmp r0, #POWER_COLLAPSED + bne skip_suspend\@ + ldr r0, =l2x0_saved_ctrl_reg_val + ldr r1, =l2x0_base_addr + ldr r1, [r1] + ldr r2, [r1, #L2X0_AUX_CTRL] + str r2, [r0, #0x0] /* store aux_ctlr reg value */ + ldr r2, [r1, #L2X0_DATA_LATENCY_CTRL] + str r2, [r0, #0x4] /* store data latency reg value */ + ldr r2, [r1, #L2X0_PREFETCH_CTRL] + str r2, [r0, #0x8] /* store prefetch_ctlr reg value */ + DO_CACHE_OPERATION L2X0_CLEAN_INV_WAY OFF + dmb +skip_suspend\@: +.endm + +/* + * Coming back from a successful PC + * 1. Check the target type + * 2. Check whether we are going to PC are not + * 3. Disable the L2 cache + * 4. Restore 'aux', 'data latency', & 'prefetch ctlr' reg + * 5. Invalidate the cache + * 6. Enable the L2 cache + */ +.macro RESUME_8x25_L2 + ldr r0, =target_type + ldr r0, [r0] + mov r1, #TARGET_IS_8625 + cmp r0, r1 + bne skip_resume\@ + ldr r0, =apps_power_collapse + ldr r0, [r0] + cmp r0, #POWER_COLLAPSED + bne skip_resume\@ + ldr r1, =l2x0_base_addr + ldr r1, [r1] + mov r0, #0x0 + str r0, [r1, #L2X0_CTRL] + ldr r0, =l2x0_saved_ctrl_reg_val + ldr r2, [r0, #0x0] + str r2, [r1, #L2X0_AUX_CTRL] /* restore aux_ctlr reg value */ + ldr r2, [r0, #0x4] + str r2, [r1, #L2X0_DATA_LATENCY_CTRL] + ldr r2, [r0, #0x8] + str r2, [r1, #L2X0_PREFETCH_CTRL] + DO_CACHE_OPERATION L2X0_INV_WAY ON +skip_resume\@: +.endm diff --git a/arch/arm/mach-msm/idle-v6.S b/arch/arm/mach-msm/idle-v6.S new file mode 100644 index 0000000000000000000000000000000000000000..81608775812920cec353fe15f487cababf63c25a --- /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 0000000000000000000000000000000000000000..b75f76fb5bc6edf6ea2ad440d189534482b0e4c7 --- /dev/null +++ b/arch/arm/mach-msm/idle-v7.S @@ -0,0 +1,309 @@ +/* + * Idle processing for ARMv7-based Qualcomm SoCs. + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2009, 2011-2012 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 "idle.h" +#include "idle-macros.S" + +#ifdef CONFIG_ARCH_MSM_KRAIT +#define SCM_SVC_BOOT 0x1 +#define SCM_CMD_TERMINATE_PC 0x2 +#endif + +ENTRY(msm_arch_idle) + wfi +#ifdef CONFIG_ARCH_MSM8X60 + mrc p14, 1, r1, c1, c5, 4 /* read ETM PDSR to clear sticky bit */ + mrc p14, 0, r1, c1, c5, 4 /* read DBG PRSR to clear sticky bit */ + isb +#endif + bx lr + +ENTRY(msm_pm_collapse) +#if defined(CONFIG_MSM_FIQ_SUPPORT) + cpsid f +#endif + + ldr r0, =msm_saved_state /* address of msm_saved_state ptr */ + ldr r0, [r0] /* load ptr */ +#if (NR_CPUS >= 2) + mrc p15, 0, r1, c0, c0, 5 /* MPIDR */ + ands r1, r1, #15 /* What CPU am I */ + mov r2, #CPU_SAVED_STATE_SIZE + mul r1, r1, r2 + add r0, r0, r1 +#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 + bl msm_jtag_save_state +#endif + + ldr r0, =msm_pm_flush_l2_flag + ldr r0, [r0] + mov r1, #0 + mcr p15, 2, r1, c0, c0, 0 /*CCSELR*/ + isb + mrc p15, 1, r1, c0, c0, 0 /*CCSIDR*/ + mov r2, #1 + and r1, r2, r1, ASR #30 /* Check if the cache is write back */ + orr r1, r0, r1 + cmp r1, #1 + bne skip + bl v7_flush_dcache_all +skip: +#ifdef CONFIG_ARCH_MSM_KRAIT + ldr r0, =SCM_SVC_BOOT + ldr r1, =SCM_CMD_TERMINATE_PC + ldr r2, =msm_pm_flush_l2_flag + ldr r2, [r2] + bl scm_call_atomic1 +#else + mrc p15, 0, r4, c1, c0, 0 /* read current CR */ + bic r0, r4, #(1 << 2) /* clear dcache bit */ + bic r0, r0, #(1 << 12) /* clear icache bit */ + mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */ + isb + + SUSPEND_8x25_L2 + SET_SMP_COHERENCY OFF + wfi + DELAY_8x25 300 + + mcr p15, 0, r4, c1, c0, 0 /* restore d/i cache */ + isb + ENABLE_8x25_L2 /* enable only l2, no need to restore the reg back */ + SET_SMP_COHERENCY ON +#endif + +#if defined(CONFIG_MSM_FIQ_SUPPORT) + cpsie f +#endif +#ifdef CONFIG_MSM_JTAG + bl msm_jtag_restore_state +#endif + ldr r0, =msm_saved_state /* address of msm_saved_state ptr */ + ldr r0, [r0] /* load ptr */ +#if (NR_CPUS >= 2) + mrc p15, 0, r1, c0, c0, 5 /* MPIDR */ + ands r1, r1, #15 /* What CPU am I */ + mov r2, #CPU_SAVED_STATE_SIZE + mul r2, r2, r1 + add r0, r0, r2 +#endif + ldmfd r0, {r4-r14} /* restore registers */ + 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, =msm_saved_state_phys + ldr r2, =msm_pm_collapse_exit + adr r3, msm_pm_collapse_exit + add r1, r1, r3 + sub r1, r1, r2 + ldr r1, [r1] + add r1, r1, #CPU_SAVED_STATE_SIZE +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + ands r2, r2, #15 /* What CPU am I */ + mov r3, #CPU_SAVED_STATE_SIZE + mul r2, r2, r3 + add r1, r1, r2 +#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 + +#ifdef CONFIG_ARCH_MSM_KRAIT + mrc p15, 0, r1, c0, c0, 0 + ldr r3, =0xff00fc00 + and r3, r1, r3 + ldr r1, =0x51000400 + cmp r3, r1 + mrceq p15, 7, r3, c15, c0, 2 + biceq r3, r3, #0x400 + mcreq p15, 7, r3, c15, c0, 2 +#else + RESUME_8x25_L2 + SET_SMP_COHERENCY ON +#endif + +#ifdef CONFIG_MSM_JTAG + stmfd sp!, {lr} + bl msm_jtag_restore_state + ldmfd sp!, {lr} +#endif + 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_set_l2_flush_flag) + ldr r1, =msm_pm_flush_l2_flag + str r0, [r1] + bx lr + + .data + + .globl msm_pm_pc_pgd +msm_pm_pc_pgd: + .long 0x0 + + .globl msm_saved_state +msm_saved_state: + .long 0x0 + + .globl msm_saved_state_phys +msm_saved_state_phys: + .long 0x0 + + .globl msm_pm_boot_vector +msm_pm_boot_vector: + .space 4 * NR_CPUS + + .globl target_type +target_type: + .long 0x0 + + .globl apps_power_collapse +apps_power_collapse: + .long 0x0 + + .globl l2x0_base_addr +l2x0_base_addr: + .long 0x0 + +/* + * Default the l2 flush flag to 1 so that caches are flushed during power + * collapse unless the L2 driver decides to flush them only during L2 + * Power collapse. + */ +msm_pm_flush_l2_flag: + .long 0x1 + +/* + * Save & restore l2x0 registers while system is entering and resuming + * from Power Collapse. + * 1. aux_ctrl_save (0x0) + * 2. data_latency_ctrl (0x4) + * 3. prefetch control (0x8) + */ +l2x0_saved_ctrl_reg_val: + .space 4 * 3 diff --git a/arch/arm/mach-msm/idle.h b/arch/arm/mach-msm/idle.h new file mode 100644 index 0000000000000000000000000000000000000000..4abdd047c574d5e3c8489af80e518ecbdbf1aa8e --- /dev/null +++ b/arch/arm/mach-msm/idle.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2007-2009,2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ + +#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 + +#define ON 1 +#define OFF 0 +#define TARGET_IS_8625 1 +#define POWER_COLLAPSED 1 + +#ifndef __ASSEMBLY__ + +int msm_arch_idle(void); +int msm_pm_collapse(void); +void msm_pm_collapse_exit(void); +extern void *msm_saved_state; +extern unsigned long msm_saved_state_phys; + +#ifdef CONFIG_CPU_V7 +void msm_pm_boot_entry(void); +void msm_pm_set_l2_flush_flag(unsigned int flag); +extern unsigned long msm_pm_pc_pgd; +extern unsigned long msm_pm_boot_vector[NR_CPUS]; +extern uint32_t target_type; +extern uint32_t apps_power_collapse; +extern uint32_t *l2x0_base_addr; +#else +static inline void msm_pm_set_l2_flush_flag(unsigned int flag) +{ + /* empty */ +} +static inline void msm_pm_boot_entry(void) +{ + /* empty */ +} +static inline void msm_pm_write_boot_vector(unsigned int cpu, + unsigned long address) +{ + /* empty */ +} +#endif +#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 0000000000000000000000000000000000000000..f4d3a272329174375eb3692b9ba093e3406e5717 --- /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 + +/****************************************************************************** + * 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 0000000000000000000000000000000000000000..6c8db1eb30632f66f3137a3c08584e2984e19bc7 --- /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/idle_stats_device.c b/arch/arm/mach-msm/idle_stats_device.c new file mode 100644 index 0000000000000000000000000000000000000000..01b464ab303bd3917bf8598d7c22b813c1225000 --- /dev/null +++ b/arch/arm/mach-msm/idle_stats_device.c @@ -0,0 +1,378 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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_MUTEX(device_list_lock); +LIST_HEAD(device_list); + +static ktime_t us_to_ktime(__u32 us) +{ + return ns_to_ktime((u64)us * NSEC_PER_USEC); +} + +static struct msm_idle_stats_device *_device_from_minor(unsigned int minor) +{ + struct msm_idle_stats_device *device, *ret = NULL; + + + mutex_lock(&device_list_lock); + list_for_each_entry(device, &device_list, list) { + if (minor == device->miscdev.minor) { + ret = device; + break; + } + } + mutex_unlock(&device_list_lock); + return ret; +} + +void msm_idle_stats_update_event(struct msm_idle_stats_device *device, + __u32 event) +{ + __u32 wake_up = !device->stats->event; + + device->stats->event |= event; + if (wake_up) + wake_up_interruptible(&device->wait); +} +EXPORT_SYMBOL(msm_idle_stats_update_event); + +static enum hrtimer_restart msm_idle_stats_busy_timer(struct hrtimer *timer) +{ + struct msm_idle_stats_device *device = + container_of(timer, struct msm_idle_stats_device, busy_timer); + + + /* This is the only case that the event is modified without a device + * lock. However, since the timer is cancelled in the other cases we are + * assured that we have exclusive access to the event at this time. + */ + hrtimer_set_expires(&device->busy_timer, us_to_ktime(0)); + msm_idle_stats_update_event(device, + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED); + return HRTIMER_NORESTART; +} + +static void start_busy_timer(struct msm_idle_stats_device *device, + ktime_t relative_time) +{ + hrtimer_cancel(&device->busy_timer); + hrtimer_set_expires(&device->busy_timer, us_to_ktime(0)); + if (!((device->stats->event & + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED) || + (device->stats->event & MSM_IDLE_STATS_EVENT_COLLECTION_FULL))) { + if (ktime_to_us(relative_time) > 0) { + hrtimer_start(&device->busy_timer, + relative_time, + HRTIMER_MODE_REL); + } + } +} + +static unsigned int msm_idle_stats_device_poll(struct file *file, + poll_table *wait) +{ + struct msm_idle_stats_device *device = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &device->wait, wait); + if (device->stats->event) + mask = POLLIN | POLLRDNORM; + return mask; +} + +static void msm_idle_stats_add_sample(struct msm_idle_stats_device *device, + struct msm_idle_pulse *pulse) +{ + hrtimer_cancel(&device->busy_timer); + hrtimer_set_expires(&device->busy_timer, us_to_ktime(0)); + if (device->stats->nr_collected >= MSM_IDLE_STATS_NR_MAX_INTERVALS) { + pr_warning("idle_stats_device: Overwriting samples\n"); + device->stats->nr_collected = 0; + } + device->stats->pulse_chain[device->stats->nr_collected] = *pulse; + device->stats->nr_collected++; + + if (device->stats->nr_collected == device->max_samples) { + msm_idle_stats_update_event(device, + MSM_IDLE_STATS_EVENT_COLLECTION_FULL); + } else if (device->stats->nr_collected == + ((device->max_samples * 3) / 4)) { + msm_idle_stats_update_event(device, + MSM_IDLE_STATS_EVENT_COLLECTION_NEARLY_FULL); + } +} + +static long ioctl_read_stats(struct msm_idle_stats_device *device, + unsigned long arg) +{ + int remaining; + int requested; + struct msm_idle_pulse pulse; + struct msm_idle_read_stats *stats; + __s64 remaining_time = + ktime_to_us(hrtimer_get_remaining(&device->busy_timer)); + + device->get_sample(device, &pulse); + spin_lock(&device->lock); + hrtimer_cancel(&device->busy_timer); + stats = device->stats; + if (stats == &device->stats_vector[0]) + device->stats = &device->stats_vector[1]; + else + device->stats = &device->stats_vector[0]; + device->stats->event = 0; + device->stats->nr_collected = 0; + spin_unlock(&device->lock); + if (stats->nr_collected >= device->max_samples) { + stats->nr_collected = device->max_samples; + } else { + stats->pulse_chain[stats->nr_collected] = pulse; + stats->nr_collected++; + if (stats->nr_collected == device->max_samples) + stats->event |= MSM_IDLE_STATS_EVENT_COLLECTION_FULL; + else if (stats->nr_collected == + ((device->max_samples * 3) / 4)) + stats->event |= + MSM_IDLE_STATS_EVENT_COLLECTION_NEARLY_FULL; + } + if (remaining_time < 0) { + stats->busy_timer_remaining = 0; + } else { + stats->busy_timer_remaining = remaining_time; + if ((__s64)stats->busy_timer_remaining != remaining_time) + stats->busy_timer_remaining = -1; + } + stats->return_timestamp = ktime_to_us(ktime_get()); + requested = + ((sizeof(*stats) - sizeof(stats->pulse_chain)) + + (sizeof(stats->pulse_chain[0]) * stats->nr_collected)); + remaining = copy_to_user((void __user *)arg, stats, requested); + if (remaining > 0) + return -EFAULT; + + return 0; +} + +static long ioctl_write_stats(struct msm_idle_stats_device *device, + unsigned long arg) +{ + struct msm_idle_write_stats stats; + int remaining; + int ret = 0; + + remaining = copy_from_user(&stats, (void __user *) arg, sizeof(stats)); + if (remaining > 0) { + ret = -EFAULT; + } else { + spin_lock(&device->lock); + device->busy_timer_interval = us_to_ktime(stats.next_busy_timer); + if (ktime_to_us(device->idle_start) == 0) + start_busy_timer(device, us_to_ktime(stats.busy_timer)); + if ((stats.max_samples > 0) && + (stats.max_samples <= MSM_IDLE_STATS_NR_MAX_INTERVALS)) + device->max_samples = stats.max_samples; + spin_unlock(&device->lock); + } + return ret; +} + +void msm_idle_stats_prepare_idle_start(struct msm_idle_stats_device *device) +{ + spin_lock(&device->lock); + hrtimer_cancel(&device->busy_timer); + spin_unlock(&device->lock); +} +EXPORT_SYMBOL(msm_idle_stats_prepare_idle_start); + +void msm_idle_stats_abort_idle_start(struct msm_idle_stats_device *device) +{ + spin_lock(&device->lock); + if (ktime_to_us(hrtimer_get_expires(&device->busy_timer)) > 0) + hrtimer_restart(&device->busy_timer); + spin_unlock(&device->lock); +} +EXPORT_SYMBOL(msm_idle_stats_abort_idle_start); + +void msm_idle_stats_idle_start(struct msm_idle_stats_device *device) +{ + spin_lock(&device->lock); + hrtimer_cancel(&device->busy_timer); + device->idle_start = ktime_get(); + if (ktime_to_us(hrtimer_get_expires(&device->busy_timer)) > 0) { + device->remaining_time = + hrtimer_get_remaining(&device->busy_timer); + if (ktime_to_us(device->remaining_time) <= 0) + device->remaining_time = us_to_ktime(0); + } else { + device->remaining_time = us_to_ktime(0); + } + spin_unlock(&device->lock); +} +EXPORT_SYMBOL(msm_idle_stats_idle_start); + +void msm_idle_stats_idle_end(struct msm_idle_stats_device *device, + struct msm_idle_pulse *pulse) +{ + int tmp; + u32 idle_time = 0; + spin_lock(&device->lock); + if (ktime_to_us(device->idle_start) != 0) { + idle_time = ktime_to_us(ktime_get()) + - ktime_to_us(device->idle_start); + device->idle_start = us_to_ktime(0); + msm_idle_stats_add_sample(device, pulse); + if (device->stats->event & + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED) { + device->stats->event &= + ~MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED; + msm_idle_stats_update_event(device, + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED_RESET); + } else if (ktime_to_us(device->busy_timer_interval) > 0) { + ktime_t busy_timer = device->busy_timer_interval; + /* if it is serialized, it would be full busy, + * checking 80% + */ + if ((pulse->wait_interval*5 >= idle_time*4) && + (ktime_to_us(device->remaining_time) > 0) && + (ktime_to_us(device->remaining_time) < + ktime_to_us(busy_timer))) + busy_timer = device->remaining_time; + start_busy_timer(device, busy_timer); + /* If previous busy interval exceeds the current submit, + * raise a busy timer expired event intentionally. + */ + tmp = device->stats->nr_collected - 1; + if (tmp > 0) { + if ((device->stats->pulse_chain[tmp - 1].busy_start_time + + device->stats->pulse_chain[tmp - 1].busy_interval) > + device->stats->pulse_chain[tmp].busy_start_time) + msm_idle_stats_update_event(device, + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED); + } + } + } + spin_unlock(&device->lock); +} +EXPORT_SYMBOL(msm_idle_stats_idle_end); + +static long msm_idle_stats_device_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct msm_idle_stats_device *device = file->private_data; + int ret; + + switch (cmd) { + case MSM_IDLE_STATS_IOC_READ_STATS: + ret = ioctl_read_stats(device, arg); + break; + case MSM_IDLE_STATS_IOC_WRITE_STATS: + ret = ioctl_write_stats(device, arg); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int msm_idle_stats_device_release + (struct inode *inode, struct file *filep) +{ + return 0; +} + +static int msm_idle_stats_device_open(struct inode *inode, struct file *filep) +{ + struct msm_idle_stats_device *device; + + + device = _device_from_minor(iminor(inode)); + + if (device == NULL) + return -EPERM; + + filep->private_data = device; + return 0; +} + +static const struct file_operations msm_idle_stats_fops = { + .open = msm_idle_stats_device_open, + .release = msm_idle_stats_device_release, + .unlocked_ioctl = msm_idle_stats_device_ioctl, + .poll = msm_idle_stats_device_poll, +}; + +int msm_idle_stats_register_device(struct msm_idle_stats_device *device) +{ + int ret = -ENOMEM; + + spin_lock_init(&device->lock); + init_waitqueue_head(&device->wait); + hrtimer_init(&device->busy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + device->busy_timer.function = msm_idle_stats_busy_timer; + + device->stats_vector[0].event = 0; + device->stats_vector[0].nr_collected = 0; + device->stats_vector[1].event = 0; + device->stats_vector[1].nr_collected = 0; + device->stats = &device->stats_vector[0]; + device->busy_timer_interval = us_to_ktime(0); + device->max_samples = MSM_IDLE_STATS_NR_MAX_INTERVALS; + + mutex_lock(&device_list_lock); + list_add(&device->list, &device_list); + mutex_unlock(&device_list_lock); + + device->miscdev.minor = MISC_DYNAMIC_MINOR; + device->miscdev.name = device->name; + device->miscdev.fops = &msm_idle_stats_fops; + + ret = misc_register(&device->miscdev); + + if (ret) + goto err_list; + + return ret; + +err_list: + mutex_lock(&device_list_lock); + list_del(&device->list); + mutex_unlock(&device_list_lock); + return ret; +} +EXPORT_SYMBOL(msm_idle_stats_register_device); + +int msm_idle_stats_deregister_device(struct msm_idle_stats_device *device) +{ + if (device == NULL) + return 0; + + mutex_lock(&device_list_lock); + spin_lock(&device->lock); + hrtimer_cancel(&device->busy_timer); + list_del(&device->list); + spin_unlock(&device->lock); + mutex_unlock(&device_list_lock); + + return misc_deregister(&device->miscdev); +} +EXPORT_SYMBOL(msm_idle_stats_deregister_device); 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 0000000000000000000000000000000000000000..1970d0b8d8f55ff3bc96ad5b11f97754e99053ff --- /dev/null +++ b/arch/arm/mach-msm/include/mach/audio_dma_msm8k.h @@ -0,0 +1,221 @@ +/* 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_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) + +#define LPAIF_DMA_PER_CNT_PER_CNT_MASK 0x000FFFFF +#define LPAIF_DMA_PER_CNT_PER_CNT_SHIFT 0 +#define LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK 0x00F00000 +#define LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT 20 + +/* 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 0000000000000000000000000000000000000000..f02a882d991db9023814f362316f8d3cb5c30184 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/bam_dmux.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 + +#define BAM_DMUX_CH_NAME_MAX_LEN 20 + +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 +}; + +/* event type enum */ +enum { + BAM_DMUX_RECEIVE, /* data is struct sk_buff */ + BAM_DMUX_WRITE_DONE, /* data is struct sk_buff */ + BAM_DMUX_UL_CONNECTED, /* data is null */ + BAM_DMUX_UL_DISCONNECTED, /*data is null */ +}; + +/* + * Open a bam_dmux logical channel + * id - the logical channel to open + * priv - private data pointer to be passed to the notify callback + * notify - event callback function + * priv - private data pointer passed to msm_bam_dmux_open() + * event_type - type of event + * data - data relevant to event. May not be valid. See event_type + * enum for valid cases. + */ +#ifdef CONFIG_MSM_BAM_DMUX +int msm_bam_dmux_open(uint32_t id, void *priv, + void (*notify)(void *priv, int event_type, + unsigned long data)); + +int msm_bam_dmux_close(uint32_t id); + +int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb); + +int msm_bam_dmux_kickoff_ul_wakeup(void); + +int msm_bam_dmux_ul_power_vote(void); + +int msm_bam_dmux_ul_power_unvote(void); + +int msm_bam_dmux_is_ch_full(uint32_t id); + +int msm_bam_dmux_is_ch_low(uint32_t id); + +int msm_bam_dmux_reg_notify(void *priv, + void (*notify)(void *priv, int event_type, + unsigned long data)); +#else +static inline int msm_bam_dmux_open(uint32_t id, void *priv, + void (*notify)(void *priv, int event_type, + unsigned long data)) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_close(uint32_t id) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_kickoff_ul_wakeup(void) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_ul_power_vote(void) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_ul_power_unvote(void) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_is_ch_full(uint32_t id) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_is_ch_low(uint32_t id) +{ + return -ENODEV; +} + +static inline int msm_bam_dmux_reg_notify(void *priv, + void (*notify)(void *priv, int event_type, + unsigned long data)) +{ + return -ENODEV; +} +#endif +#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 0000000000000000000000000000000000000000..2d4792c5f75d827d4922a584d9d7014dea52d377 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/barriers.h @@ -0,0 +1,22 @@ +/* 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 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 0000000000000000000000000000000000000000..c22429718809aac9b822554ba284306d202e70bb --- /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-msm8660.h b/arch/arm/mach-msm/include/mach/board-msm8660.h new file mode 100644 index 0000000000000000000000000000000000000000..22e378cc568ecd379e9180fafde5a5bf3055d144 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/board-msm8660.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 __ARCH_ARM_MACH_MSM_BOARD_MSM8660_H +#define __ARCH_ARM_MACH_MSM_BOARD_MSM8660_H + +#include +#include +#include + +/* 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_MPP_BASE (PM8058_GPIO_BASE + \ + PM8058_GPIOS + PM8058_MPPS) +#define PM8901_MPP_PM_TO_SYS(pm_gpio) (pm_gpio + PM8901_MPP_BASE) +#define PM8901_MPP_SYS_TO_PM(sys_gpio) (sys_gpio - PM901_MPP_BASE) +#define PM8901_IRQ_BASE (PM8058_IRQ_BASE + \ + NR_PMIC8058_IRQS) + +#ifdef CONFIG_MSM_CAMERA_V4L2 +extern struct msm_camera_board_info msm8x60_camera_board_info; +void msm8x60_init_cam(void); +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/board.h b/arch/arm/mach-msm/include/mach/board.h index 2ce8f1f2fc4d57c163193229340b7b04d04c7474..5e2eaf18cac42c8031c86ea30f27c9e78a26ace7 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-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -18,33 +19,576 @@ #define __ASM_ARCH_MSM_BOARD_H #include -#include +#include +#include +#include +#include +#include +#include +#include -/* platform device data structures */ +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_cam_expander_info { + struct i2c_board_info const *board_info; + int bus_id; +}; + +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; + uint8_t is_csiphy; + uint8_t is_csic; + uint8_t is_csid; + uint8_t is_ispif; + uint8_t is_vpe; + struct msm_bus_scale_pdata *cam_bus_scale_table; +}; +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) +#define MSM_CAMERA_FLASH_SRC_EXT (0x00000001<<3) +#define MSM_CAMERA_FLASH_SRC_LED (0x00000001<<3) +#define MSM_CAMERA_FLASH_SRC_LED1 (0x00000001<<4) + +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; +}; + +enum msm_camera_ext_led_flash_id { + MAM_CAMERA_EXT_LED_FLASH_SC628A, + MAM_CAMERA_EXT_LED_FLASH_TPS61310, +}; + +struct msm_camera_sensor_flash_external { + uint32_t led_en; + uint32_t led_flash_en; + enum msm_camera_ext_led_flash_id flash_id; + struct msm_cam_expander_info *expander_info; +}; + +struct msm_camera_sensor_flash_led { + const char *led_name; + const int led_name_len; +}; + +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; + struct msm_camera_sensor_flash_external + ext_driver_src; + struct msm_camera_sensor_flash_led led_src; + } _fsrc; +}; + +struct msm_camera_sensor_flash_data { + int flash_type; + struct msm_camera_sensor_flash_src *flash_src; +}; -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; +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; }; +enum msm_camera_type { + BACK_CAMERA_2D, + FRONT_CAMERA_2D, + BACK_CAMERA_3D, + BACK_CAMERA_INT_3D, +}; + +enum msm_sensor_type { + BAYER_SENSOR, + YUV_SENSOR, +}; + +enum camera_vreg_type { + REG_LDO, + REG_VS, +}; + +struct camera_vreg_t { + char *reg_name; + enum camera_vreg_type type; + int min_voltage; + int max_voltage; + int op_mode; +}; + +struct msm_gpio_set_tbl { + unsigned gpio; + unsigned long flags; + uint32_t delay; +}; + +struct msm_camera_csi_lane_params { + uint8_t csi_lane_assign; + uint8_t csi_lane_mask; +}; + +struct msm_camera_gpio_conf { + void *cam_gpiomux_conf_tbl; + uint8_t cam_gpiomux_conf_tbl_size; + struct gpio *cam_gpio_common_tbl; + uint8_t cam_gpio_common_tbl_size; + struct gpio *cam_gpio_req_tbl; + uint8_t cam_gpio_req_tbl_size; + struct msm_gpio_set_tbl *cam_gpio_set_tbl; + uint8_t cam_gpio_set_tbl_size; + uint32_t gpio_no_mux; + uint32_t *camera_off_table; + uint8_t camera_off_table_size; + uint32_t *camera_on_table; + uint8_t camera_on_table_size; +}; + +enum msm_camera_i2c_mux_mode { + MODE_R, + MODE_L, + MODE_DUAL +}; + +struct msm_camera_i2c_conf { + uint8_t use_i2c_mux; + struct platform_device *mux_dev; + enum msm_camera_i2c_mux_mode i2c_mux_mode; +}; + +struct msm_camera_sensor_platform_info { + int mount_angle; + int sensor_reset; + struct camera_vreg_t *cam_vreg; + int num_vreg; + int32_t (*ext_power_ctrl) (int enable); + struct msm_camera_gpio_conf *gpio_conf; + struct msm_camera_i2c_conf *i2c_conf; + struct msm_camera_csi_lane_params *csi_lane_params; +}; + +enum msm_camera_actuator_name { + MSM_ACTUATOR_MAIN_CAM_0, + MSM_ACTUATOR_MAIN_CAM_1, + MSM_ACTUATOR_MAIN_CAM_2, + MSM_ACTUATOR_MAIN_CAM_3, + MSM_ACTUATOR_MAIN_CAM_4, + MSM_ACTUATOR_MAIN_CAM_5, + MSM_ACTUATOR_WEB_CAM_0, + MSM_ACTUATOR_WEB_CAM_1, + MSM_ACTUATOR_WEB_CAM_2, +}; + +struct msm_actuator_info { + struct i2c_board_info const *board_info; + enum msm_camera_actuator_name cam_name; + int bus_id; + int vcm_pwd; + int vcm_enable; +}; + +struct msm_eeprom_info { + struct i2c_board_info const *board_info; + int bus_id; +}; + +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; + enum msm_camera_type camera_type; + enum msm_sensor_type sensor_type; + struct msm_actuator_info *actuator_info; + int pmic_gpio_enable; + int (*sensor_lcd_gpio_onoff)(int on); + struct msm_eeprom_info *eeprom_info; +}; + +struct msm_camera_board_info { + struct i2c_board_info *board_info; + uint8_t num_i2c_board_info; +}; + +int 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, + MDP_REV_43, + MDP_REV_44, +}; + +struct msm_panel_common_pdata { + uintptr_t hw_revision_addr; + int gpio; + bool bl_lock; + spinlock_t bl_spinlock; + int (*backlight_level)(int level, int max, int min); + int (*pmic_backlight)(int level); + int (*rotate_panel)(void); + 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; + u32 ov0_wb_size; /* overlay0 writeback size */ + u32 ov1_wb_size; /* overlay1 writeback size */ + u32 mem_hid; + char cont_splash_enabled; +}; + + + +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 + int (*lvds_pixel_remap)(void); +}; + +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); + char (*splash_is_enabled)(void); + int target_type; +}; + +enum mipi_dsi_3d_ctrl { + FPGA_EBI2_INTF, + FPGA_SPI_INTF, +}; +/* DSI PHY configuration */ +struct mipi_dsi_phy_ctrl { + uint32_t regulator[5]; + uint32_t timing[12]; + uint32_t ctrl[4]; + uint32_t strength[4]; + uint32_t pll[21]; +}; + +struct mipi_dsi_panel_platform_data { + int fpga_ctrl_mode; + int fpga_3d_config_addr; + int *gpio; + struct mipi_dsi_phy_ctrl *phy_ctrl_settings; + char dlane_swap; + void (*dsi_pwm_cfg)(void); + char enable_wled_bl_ctrl; +}; + +struct lvds_panel_platform_data { + int *gpio; +}; + +#define PANEL_NAME_MAX_LEN 50 +struct msm_fb_platform_data { + int (*detect_client)(const char *name); + int mddi_prescan; + int (*allow_set_offset)(void); + char prim_panel_name[PANEL_NAME_MAX_LEN]; + char ext_panel_name[PANEL_NAME_MAX_LEN]; +}; + +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_mhl_platform_data { + int irq; + int (*gpio_setup)(int on); + void (*reset_pin)(int on); +}; + +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; + 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; + u32 enable_ion; + int disable_dmx; + int disable_fullhd; + u32 cp_enabled; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *vidc_bus_client_pdata; +#endif + int cont_mode_dpb_count; +}; + +struct vcap_platform_data { + unsigned *gpios; + int num_gpios; + struct msm_bus_scale_pdata *bus_client_pdata; +}; + +#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_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 *); -int __init msm_add_sdcc(unsigned int controller, - struct msm_mmc_platform_data *plat, - unsigned int stat_irq, unsigned long stat_irq_flags); +#ifdef CONFIG_OF_DEVICE +void msm_copper_init(struct of_dev_auxdata **); +#endif +void msm_add_devices(void); +void msm_copper_add_devices(void); +void msm_copper_add_drivers(void); +void msm_map_common_io(void); +void msm_map_qsd8x50_io(void); +void msm_map_msm8x60_io(void); +void msm_map_msm8960_io(void); +void msm_map_msm8930_io(void); +void msm_map_apq8064_io(void); +void msm_map_msm7x30_io(void); +void msm_map_fsm9xxx_io(void); +void msm_map_copper_io(void); +void msm_map_msm8625_io(void); +void msm_map_msm9625_io(void); +void msm_init_irq(void); +void msm_copper_init_irq(void); +void vic_handle_irq(struct pt_regs *regs); +void msm_copper_reserve(void); +void msm_copper_very_early(void); +void msm_copper_init_gpiomux(void); + +struct mmc_platform_data; +int msm_add_sdcc(unsigned int controller, + struct mmc_platform_data *plat); + +void msm_pm_register_irqs(void); +struct msm_usb_host_platform_data; +int 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 msm_snddev_init(void); +void 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 0000000000000000000000000000000000000000..b537c91b957a0b0f8977f6c5d55e41f2e332bd20 --- /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 0000000000000000000000000000000000000000..47d9b5fbb4268e5025c7d218f8e36421ead858ef --- /dev/null +++ b/arch/arm/mach-msm/include/mach/camera.h @@ -0,0 +1,701 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include "linux/types.h" + +#include +#include +#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 512 +#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_MODE_OF_OPERATION_JPEG_SNAPSHOT, + 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, /* 10 */ + 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, + VFE_MSG_V32_START, + VFE_MSG_V32_START_RECORDING, /* 20 */ + VFE_MSG_V32_CAPTURE, + VFE_MSG_V32_JPEG_CAPTURE, + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_V2X_PREVIEW, + VFE_MSG_V2X_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY, + VFE_MSG_OUTPUT_SECONDARY, +}; + +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_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_ispif_params_list { + uint32_t len; + struct msm_ispif_params params[3]; +}; + +struct msm_vpe_phy_info { + uint32_t sbuf_phy; + uint32_t planar0_off; + uint32_t planar1_off; + uint32_t planar2_off; + uint32_t p0_phy; + uint32_t p1_phy; + uint32_t p2_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; + uint8_t lane_mask; +}; + +struct msm_camera_csi2_params { + struct msm_camera_csid_params csid_params; + struct msm_camera_csiphy_params csiphy_params; +}; + +#ifndef CONFIG_MSM_CAMERA_V4L2 +#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 VFE31_OUTPUT_MODE_P_ALL_CHNLS (0x1 << 5) +#endif + +#define CSI_EMBED_DATA 0x12 +#define CSI_RESERVED_DATA_0 0x13 +#define CSI_YUV422_8 0x1E +#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 CSI_DECODE_DPCM_10_8_10 5 + +struct msm_vfe_phy_info { + uint32_t sbuf_phy; + uint32_t planar0_off; + uint32_t planar1_off; + uint32_t planar2_off; + uint32_t p0_phy; + uint32_t p1_phy; + uint32_t p2_phy; + uint8_t output_id; /* VFE31_OUTPUT_MODE_PT/S/V */ + uint32_t frame_id; +}; + +struct msm_vfe_stats_msg { + uint8_t awb_ymin; + 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 p0_phy; + uint32_t p1_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_mctl_pp_params { + struct msm_mctl_pp_cmd *cmd; + 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 planar0_off, uint32_t planar1_off, + 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; + struct list_head list_eventdata; + 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_mctl_stats_t { + struct hlist_head pmem_stats_list; + spinlock_t pmem_stats_spinlock; +}; + +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 ion_handle *handle; +}; + +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_PIX1_CLK, + CAMIO_CSI_RDI_CLK, + CAMIO_CSI_RDI1_CLK, + CAMIO_CSI_RDI2_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_IMEM_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 +}; + +struct msm_cam_clk_info { + const char *clk_name; + long clk_rate; +}; + +int msm_camio_enable(struct platform_device *dev); +int msm_camio_vpe_clk_enable(uint32_t); +int msm_camio_vpe_clk_disable(void); + +void msm_camio_mode_config(enum msm_camera_i2c_mux_mode mode); +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); +int msm_camio_vfe_clk_rate_set(int rate); +void msm_camio_clk_rate_set_2(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_vfe_blk_reset_2(void); +void msm_camio_vfe_blk_reset_3(void); + +int32_t msm_camio_3d_enable(const struct msm_camera_sensor_info *sinfo); +void msm_camio_3d_disable(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); +int add_axi_qos(void); +int update_axi_qos(uint32_t freq); +void release_axi_qos(void); +void msm_camera_io_w(u32 data, void __iomem *addr); +void msm_camera_io_w_mb(u32 data, void __iomem *addr); +u32 msm_camera_io_r(void __iomem *addr); +u32 msm_camera_io_r_mb(void __iomem *addr); +void msm_camera_io_dump(void __iomem *addr, int size); +void msm_camera_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_camio_bus_scale_cfg( + struct msm_bus_scale_pdata *, enum msm_bus_perf_setting); + +void *msm_isp_sync_alloc(int size, gfp_t gfp); + +void msm_isp_sync_free(void *ptr); + +int msm_cam_clk_enable(struct device *dev, struct msm_cam_clk_info *clk_info, + struct clk **clk_ptr, int num_clk, int enable); +int msm_cam_core_reset(void); + +int msm_camera_config_vreg(struct device *dev, struct camera_vreg_t *cam_vreg, + int num_vreg, struct regulator **reg_ptr, int config); +int msm_camera_enable_vreg(struct device *dev, struct camera_vreg_t *cam_vreg, + int num_vreg, struct regulator **reg_ptr, int enable); + +int msm_camera_config_gpio_table + (struct msm_camera_sensor_info *sinfo, int gpio_en); +int msm_camera_request_gpio_table + (struct msm_camera_sensor_info *sinfo, int gpio_en); +#endif diff --git a/arch/arm/mach-msm/include/mach/clk.h b/arch/arm/mach-msm/include/mach/clk.h index e8d38428d8130e5d1e73aa9ed13e8652885fe2e9..8c0ebfa6ca87a8856d8b285db5cfc132849aa232 100644 --- a/arch/arm/mach-msm/include/mach/clk.h +++ b/arch/arm/mach-msm/include/mach/clk.h @@ -25,9 +25,6 @@ enum clk_reset_action { struct clk; -/* Rate is minimum clock rate in Hz */ -int clk_set_min_rate(struct clk *clk, unsigned long rate); - /* Rate is maximum clock rate in Hz */ int clk_set_max_rate(struct clk *clk, unsigned long rate); diff --git a/arch/arm/mach-msm/include/mach/cpu.h b/arch/arm/mach-msm/include/mach/cpu.h deleted file mode 100644 index a9481b08d5c751ec6b7381f91deac09dfb3b4e18..0000000000000000000000000000000000000000 --- a/arch/arm/mach-msm/include/mach/cpu.h +++ /dev/null @@ -1,54 +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. - */ - -#ifndef __ARCH_ARM_MACH_MSM_CPU_H__ -#define __ARCH_ARM_MACH_MSM_CPU_H__ - -/* TODO: For now, only one CPU can be compiled at a time. */ - -#define cpu_is_msm7x01() 0 -#define cpu_is_msm7x30() 0 -#define cpu_is_qsd8x50() 0 -#define cpu_is_msm8x60() 0 -#define cpu_is_msm8960() 0 - -#ifdef CONFIG_ARCH_MSM7X00A -# undef cpu_is_msm7x01 -# define cpu_is_msm7x01() 1 -#endif - -#ifdef CONFIG_ARCH_MSM7X30 -# undef cpu_is_msm7x30 -# define cpu_is_msm7x30() 1 -#endif - -#ifdef CONFIG_ARCH_QSD8X50 -# undef cpu_is_qsd8x50 -# define cpu_is_qsd8x50() 1 -#endif - -#ifdef CONFIG_ARCH_MSM8X60 -# undef cpu_is_msm8x60 -# define cpu_is_msm8x60() 1 -#endif - -#ifdef CONFIG_ARCH_MSM8960 -# undef cpu_is_msm8960 -# define cpu_is_msm8960() 1 -#endif - -#endif diff --git a/arch/arm/mach-msm/include/mach/cpuidle.h b/arch/arm/mach-msm/include/mach/cpuidle.h new file mode 100644 index 0000000000000000000000000000000000000000..654121f244e47dc504f449205556da8a02db778a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/cpuidle.h @@ -0,0 +1,54 @@ +/* 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 +int msm_cpuidle_init(void); +#else +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/include/mach/dal.h b/arch/arm/mach-msm/include/mach/dal.h new file mode 100644 index 0000000000000000000000000000000000000000..d0c754d380ffa4f1ce3f46d715f6cf7cdb77d891 --- /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 0000000000000000000000000000000000000000..4e32aa3dabf63a4226eeb8a7bf96312f331c9a1f --- /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 3ffd8668c9a5dae483613e8bcc59a98a4bc975a7..c76095397207eb0aefdf5bfc2e216d550ef7b54b 100644 --- a/arch/arm/mach-msm/include/mach/debug-macro.S +++ b/arch/arm/mach-msm/include/mach/debug-macro.S @@ -1,7 +1,6 @@ /* * * 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 @@ -15,52 +14,67 @@ * */ + + #include #include +#include - .macro addruart, rp, rv, tmp #ifdef MSM_DEBUG_UART_PHYS - ldr \rp, =MSM_DEBUG_UART_PHYS - ldr \rv, =MSM_DEBUG_UART_BASE -#endif + .macro addruart, rp, rv, tmp + ldr \rp, =MSM_DEBUG_UART_PHYS + ldr \rv, =MSM_DEBUG_UART_BASE .endm - .macro senduart, rd, rx + .macro senduart,rd,rx #ifdef CONFIG_MSM_HAS_DEBUG_UART_HS + @ Clear TX_READY by writing to the UARTDM_CR register + mov r12, #0x300 + str r12, [\rx, #UARTDM_CR_OFFSET] + @ Write 0x1 to NCF register + mov r12, #0x1 + str r12, [\rx, #UARTDM_NCF_TX_OFFSET] + @ UARTDM reg. Read to induce delay + ldr r12, [\rx, #UARTDM_SR_OFFSET] @ Write the 1 character to UARTDM_TF - str \rd, [\rx, #0x70] + str \rd, [\rx, #UARTDM_TF_OFFSET] #else - teq \rx, #0 - strne \rd, [\rx, #0x0C] + teq \rx, #0 + strne \rd, [\rx, #0x0C] #endif .endm - .macro waituart, rd, rx + .macro waituart,rd,rx #ifdef CONFIG_MSM_HAS_DEBUG_UART_HS @ check for TX_EMT in UARTDM_SR - ldr \rd, [\rx, #0x08] + ldr \rd, [\rx, #UARTDM_SR_OFFSET] tst \rd, #0x08 bne 1002f @ wait for TXREADY in UARTDM_ISR -1001: ldr \rd, [\rx, #0x14] +1001: ldreq \rd, [\rx, #UARTDM_ISR_OFFSET] tst \rd, #0x80 + dsb beq 1001b -1002: - @ Clear TX_READY by writing to the UARTDM_CR register - mov \rd, #0x300 - str \rd, [\rx, #0x10] - @ Write 0x1 to NCF register - mov \rd, #0x1 - str \rd, [\rx, #0x40] - @ UARTDM reg. Read to induce delay - ldr \rd, [\rx, #0x08] #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 .endm - .macro busyuart, rd, rx + .macro senduart,rd,rx + .endm + + .macro waituart,rd,rx + .endm +#endif + + .macro busyuart,rd,rx .endm 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 0000000000000000000000000000000000000000..091798c331257db98e0ebd887f849f18af827e40 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/debug_mm.h @@ -0,0 +1,33 @@ +/* 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_ + +#include + +/* 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/diag_bridge.h b/arch/arm/mach-msm/include/mach/diag_bridge.h new file mode 100644 index 0000000000000000000000000000000000000000..b06f020ef02fe9921700398503163042b5b598fa --- /dev/null +++ b/arch/arm/mach-msm/include/mach/diag_bridge.h @@ -0,0 +1,55 @@ +/* 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_DIAG_BRIDGE_H__ +#define __LINUX_USB_DIAG_BRIDGE_H__ + +struct diag_bridge_ops { + void *ctxt; + void (*read_complete_cb)(void *ctxt, char *buf, + int buf_size, int actual); + void (*write_complete_cb)(void *ctxt, char *buf, + int buf_size, int actual); + int (*suspend)(void *ctxt); + void (*resume)(void *ctxt); +}; + +#if defined(CONFIG_USB_QCOM_DIAG_BRIDGE) \ + || defined(CONFIG_USB_QCOM_DIAG_BRIDGE_MODULE) + +extern int diag_bridge_read(char *data, int size); +extern int diag_bridge_write(char *data, int size); +extern int diag_bridge_open(struct diag_bridge_ops *ops); +extern void diag_bridge_close(void); + +#else + +static int __maybe_unused diag_bridge_read(char *data, int size) +{ + return -ENODEV; +} + +static int __maybe_unused diag_bridge_write(char *data, int size) +{ + return -ENODEV; +} + +static int __maybe_unused diag_bridge_open(struct diag_bridge_ops *ops) +{ + return -ENODEV; +} + +static void __maybe_unused diag_bridge_close(void) { } + +#endif + +#endif 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 0000000000000000000000000000000000000000..e28426727eb16f18d915d9c4b245f267c32222ee --- /dev/null +++ b/arch/arm/mach-msm/include/mach/dma-fsm9xxx.h @@ -0,0 +1,56 @@ +/* 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 + +/* 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 05583f569524449ed2b61ea52ecd42544a959416..70519ff88985b25f9b9bf540c33d6bc7bf1f5938 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-2012, 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]; }; @@ -28,71 +34,191 @@ struct msm_dmov_cmd { 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 */ +}; + +struct msm_dmov_pdata { + int sd; + size_t sd_size; }; -#ifndef CONFIG_ARCH_MSM8X60 void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd); -void msm_dmov_stop_cmd(unsigned id, struct msm_dmov_cmd *cmd, int graceful); +void msm_dmov_enqueue_cmd_ext(unsigned id, struct msm_dmov_cmd *cmd); +void msm_dmov_flush(unsigned int id, 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 +#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)) - -#if defined(CONFIG_ARCH_MSM7X30) -#define DMOV_SD_AARM DMOV_SD2 -#else -#define DMOV_SD_AARM DMOV_SD3 -#endif +#define DMOV_ADDR(off, ch) ((off) + ((ch) << 2)) -#define DMOV_CMD_PTR(ch) DMOV_SD_AARM(0x000, ch) +#define DMOV_CMD_PTR(ch) DMOV_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_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_ADDR(0x080, ch) +#define DMOV_FLUSH1(ch) DMOV_ADDR(0x0C0, ch) +#define DMOV_FLUSH2(ch) DMOV_ADDR(0x100, ch) +#define DMOV_FLUSH3(ch) DMOV_ADDR(0x140, ch) +#define DMOV_FLUSH4(ch) DMOV_ADDR(0x180, ch) +#define DMOV_FLUSH5(ch) DMOV_ADDR(0x1C0, ch) +#define DMOV_FLUSH_TYPE (1 << 31) -#define DMOV_STATUS(ch) DMOV_SD_AARM(0x200, ch) +#define DMOV_STATUS(ch) DMOV_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_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_ADDR(0x280, ci) + +#define DMOV_RSLT_CONF(ch) DMOV_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_ADDR(0x380, 0) + +#define DMOV_CI_CONF(ci) DMOV_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_ADDR(0x3B0, ci) + +#define DMOV_CRCI_CONF0 DMOV_ADDR(0x3D0, 0) +#define DMOV_CRCI_CONF1 DMOV_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_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 9 + +#define DMOV_CE_IN_CHAN 0 +#define DMOV_CE_IN_CRCI 2 + +#define DMOV_CE_OUT_CHAN 1 +#define DMOV_CE_OUT_CRCI 3 + +#define DMOV_TSIF_CHAN 2 +#define DMOV_TSIF_CRCI 11 + +#define DMOV_HSUART_GSBI6_TX_CHAN 7 +#define DMOV_HSUART_GSBI6_TX_CRCI 6 + +#define DMOV_HSUART_GSBI6_RX_CHAN 8 +#define DMOV_HSUART_GSBI6_RX_CRCI 11 + +#define DMOV_HSUART_GSBI9_TX_CHAN 4 +#define DMOV_HSUART_GSBI9_TX_CRCI 13 + +#define DMOV_HSUART_GSBI9_RX_CHAN 3 +#define DMOV_HSUART_GSBI9_RX_CRCI 12 + +#elif defined(CONFIG_ARCH_MSM9615) + +#define DMOV_GP_CHAN 4 + +#define DMOV_CE_IN_CHAN 0 +#define DMOV_CE_IN_CRCI 12 + +#define DMOV_CE_OUT_CHAN 1 +#define DMOV_CE_OUT_CRCI 13 + +#define DMOV_NAND_CHAN 3 +#define DMOV_NAND_CRCI_CMD 15 +#define DMOV_NAND_CRCI_DATA 3 + +#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 +229,38 @@ 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 + +/* channels for APQ8064 */ +#define DMOV8064_CE_IN_CHAN 0 +#define DMOV8064_CE_IN_CRCI 14 + +#define DMOV8064_CE_OUT_CHAN 1 +#define DMOV8064_CE_OUT_CRCI 15 + + /* 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 0000000000000000000000000000000000000000..c0464fa7ded0fafa65f0d17d633d3b0cf359f705 --- /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.S b/arch/arm/mach-msm/include/mach/entry-macro.S index f2ae9087f654fd324f85349b8cff18663840d42e..dd1b54d901f4ffdff701433463f6cfd4e648a811 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,12 +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. - * */ #if !defined(CONFIG_ARM_GIC) 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 0000000000000000000000000000000000000000..29a3ba1f33f3ecbe9c7c7bb38cd4999cf9e1c87e --- /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 0000000000000000000000000000000000000000..e41fe72d2484c36c9b275fc62c7779801e9ff3c7 --- /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 0000000000000000000000000000000000000000..eea4c88f12767b256697167b4bfcb88fb69a90f7 --- /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 40a8c178f10d9e85a2873c83247c3f2fe553f408..8aed079bcd621e90790909d9b007d847b05b230c 100644 --- a/arch/arm/mach-msm/include/mach/gpio.h +++ b/arch/arm/mach-msm/include/mach/gpio.h @@ -1 +1,224 @@ -/* empty */ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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_H +#define __ASM_ARCH_MSM_GPIO_H + +#define ARCH_NR_GPIOS 512 + +#include +#include +#include + +#define FIRST_BOARD_GPIO NR_GPIO_IRQS + +extern struct irq_chip msm_gpio_irq_extn; + +/** + * 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_GPIO_MSM_V2 +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 + +#ifdef CONFIG_OF +int __init msm_gpio_of_init(struct device_node *node, + struct device_node *parent); +#endif + +#endif /* __ASM_ARCH_MSM_GPIO_H */ diff --git a/arch/arm/mach-msm/include/mach/gpiomux.h b/arch/arm/mach-msm/include/mach/gpiomux.h new file mode 100644 index 0000000000000000000000000000000000000000..f75b0e0c8472b40684b501f09bfcc18581449657 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/gpiomux.h @@ -0,0 +1,177 @@ +/* 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_GPIOMUX_H +#define __ARCH_ARM_MACH_MSM_GPIOMUX_H + +#include +#include + +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 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. + * + * @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 { + unsigned gpio; + struct gpiomux_setting *settings[GPIOMUX_NSETTINGS]; +}; + +/** + * 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. + */ +struct msm_gpiomux_configs { + struct msm_gpiomux_config *cfg; + size_t ncfg; +}; + +#ifdef CONFIG_MSM_GPIOMUX + +/* 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. + */ +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); + +/* Decrement a gpio's reference count, possibly suspending the line. */ +int msm_gpiomux_put(unsigned gpio); + +/* 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, 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: + * - the gpio value has passed a bounds-check + * - the gpiomux spinlock has been obtained + * + * This function is not for public consumption. External users + * should use msm_gpiomux_write. + */ +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; +} + +static inline int msm_gpiomux_put(unsigned gpio) +{ + return -ENOSYS; +} + +static inline int msm_gpiomux_write(unsigned gpio, + enum msm_gpiomux_setting which, struct gpiomux_setting *setting, + struct gpiomux_setting *old_setting) +{ + return -ENOSYS; +} +#endif +#endif diff --git a/arch/arm/mach-msm/include/mach/hardware.h b/arch/arm/mach-msm/include/mach/hardware.h index 2d126091ae415ffadbd2bac1770abfc86e6eecca..7b7cbaa656607cde1a074a30397f149f5497ac3b 100644 --- a/arch/arm/mach-msm/include/mach/hardware.h +++ b/arch/arm/mach-msm/include/mach/hardware.h @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/include/mach/hardware.h * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2012, 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,5 +15,8 @@ */ #ifndef __ASM_ARCH_MSM_HARDWARE_H +#define __ASM_ARCH_MSM_HARDWARE_H + +#define pcibios_assign_all_busses() 1 #endif 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 0000000000000000000000000000000000000000..5ce1e2a1e47804659b9b841a736c57bb42d14438 --- /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 0000000000000000000000000000000000000000..2139bf9f903a5601ee0b82bb2ad5b3127b6e0530 --- /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 0000000000000000000000000000000000000000..2f4c18db2625340ddfa574580d9e4a2c9229852a --- /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 0000000000000000000000000000000000000000..c7a91f1d906cf1c643ad2f13ce76155e655b13d1 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..445e17567b10a142ba2d5a37d45608950a14ea57 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/io.h @@ -0,0 +1,23 @@ +/* + * 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 __ASM_ARM_ARCH_IO_H +#define __ASM_ARM_ARCH_IO_H + +#define IO_SPACE_LIMIT 0xffffffff + +#define __io(a) __typesafe_io(a) +#define __mem_pci(a) (a) + +#endif diff --git a/arch/arm/mach-msm/include/mach/iommu.h b/arch/arm/mach-msm/include/mach/iommu.h index 5c7c955e6d253a987c4a16d9c28f0c11e7c01771..b57ae10fe898ba2e1c93f6c75cf56c9a2a25888e 100644 --- a/arch/arm/mach-msm/include/mach/iommu.h +++ b/arch/arm/mach-msm/include/mach/iommu.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This 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 MSM_IOMMU_H @@ -20,16 +15,13 @@ #include #include +#include -/* Sharability attributes of MSM IOMMU mappings */ -#define MSM_IOMMU_ATTR_NON_SH 0x0 -#define MSM_IOMMU_ATTR_SH 0x4 +extern pgprot_t pgprot_kernel; +extern struct platform_device *msm_iommu_root_dev; -/* Cacheability attributes of MSM IOMMU mappings */ -#define MSM_IOMMU_ATTR_NONCACHED 0x0 -#define MSM_IOMMU_ATTR_CACHED_WB_WA 0x1 -#define MSM_IOMMU_ATTR_CACHED_WB_NWA 0x2 -#define MSM_IOMMU_ATTR_CACHED_WT 0x3 +/* Domain attributes */ +#define MSM_IOMMU_DOMAIN_PT_CACHEABLE 0x1 /* Mask for the cache policy attribute */ #define MSM_IOMMU_CP_MASK 0x03 @@ -50,6 +42,7 @@ struct msm_iommu_dev { const char *name; int ncb; + int ttbr_split; }; /** @@ -81,10 +74,11 @@ struct msm_iommu_ctx_dev { */ struct msm_iommu_drvdata { void __iomem *base; - int irq; int ncb; + int ttbr_split; struct clk *clk; struct clk *pclk; + const char *name; }; /** @@ -101,20 +95,53 @@ struct msm_iommu_ctx_drvdata { int num; struct platform_device *pdev; struct list_head attached_elm; + struct iommu_domain *attached_domain; + const char *name; }; +/* + * Interrupt handler for the IOMMU context fault interrupt. Hooking the + * interrupt is not supported in the API yet, but this will print an error + * message and dump useful IOMMU registers. + */ +irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id); +irqreturn_t msm_iommu_fault_handler_v2(int irq, void *dev_id); + +#ifdef CONFIG_MSM_IOMMU /* * Look up an IOMMU context device by its context name. NULL if none found. * Useful for testing and drivers that do not yet fully have IOMMU stuff in * their platform devices. */ struct device *msm_iommu_get_ctx(const char *ctx_name); +#else +static inline struct device *msm_iommu_get_ctx(const char *ctx_name) +{ + return NULL; +} +#endif -/* - * Interrupt handler for the IOMMU context fault interrupt. Hooking the - * interrupt is not supported in the API yet, but this will print an error - * message and dump useful IOMMU registers. - */ -irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id); +#endif +static inline int msm_soc_version_supports_iommu_v1(void) +{ +#ifdef CONFIG_OF + struct device_node *node; + + node = of_find_compatible_node(NULL, NULL, "qcom,msm-smmu-v2"); + if (node) { + of_node_put(node); + return 0; + } #endif + if (cpu_is_msm8960() && + SOCINFO_VERSION_MAJOR(socinfo_get_version()) < 2) + return 0; + + if (cpu_is_msm8x60() && + (SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 2 || + SOCINFO_VERSION_MINOR(socinfo_get_version()) < 1)) { + return 0; + } + return 1; +} 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 0000000000000000000000000000000000000000..1a3a0225262a468337148b1fc6c4a6b435cb5877 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/iommu_domains.h @@ -0,0 +1,180 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#include + +enum { + VIDEO_DOMAIN, + CAMERA_DOMAIN, + DISPLAY_DOMAIN, + ROTATOR_DOMAIN, + MAX_DOMAINS +}; + +enum { + VIDEO_FIRMWARE_POOL, + VIDEO_MAIN_POOL, + GEN_POOL, +}; + +struct msm_iommu_domain_name { + char *name; + int domain; +}; + +struct msm_iommu_domain { + /* iommu domain to map in */ + struct iommu_domain *domain; + /* total number of allocations from this domain */ + atomic_t allocation_cnt; + /* number of iova pools */ + int npools; + /* + * array of gen_pools for allocating iovas. + * behavior is undefined if these overlap + */ + struct mem_pool *iova_pools; +}; + +struct iommu_domains_pdata { + struct msm_iommu_domain *domains; + int ndomains; + struct msm_iommu_domain_name *domain_names; + int nnames; + unsigned int domain_alloc_flags; +}; + + +struct msm_iova_partition { + unsigned long start; + unsigned long size; +}; + +struct msm_iova_layout { + struct msm_iova_partition *partitions; + int npartitions; + const char *client_name; + unsigned int domain_flags; +}; + +#if defined(CONFIG_MSM_IOMMU) + +extern struct iommu_domain *msm_get_iommu_domain(int domain_num); + +extern int msm_allocate_iova_address(unsigned int iommu_domain, + unsigned int partition_no, + unsigned long size, + unsigned long align, + unsigned long *iova); + +extern void msm_free_iova_address(unsigned long iova, + unsigned int iommu_domain, + unsigned int partition_no, + unsigned long size); + +extern int msm_use_iommu(void); + +extern int msm_iommu_map_extra(struct iommu_domain *domain, + unsigned long start_iova, + unsigned long size, + unsigned long page_size, + int cached); + +extern void msm_iommu_unmap_extra(struct iommu_domain *domain, + unsigned long start_iova, + unsigned long size, + unsigned long page_size); + +extern int msm_iommu_map_contig_buffer(unsigned long phys, + unsigned int domain_no, + unsigned int partition_no, + unsigned long size, + unsigned long align, + unsigned long cached, + unsigned long *iova_val); + + +extern void msm_iommu_unmap_contig_buffer(unsigned long iova, + unsigned int domain_no, + unsigned int partition_no, + unsigned long size); + +extern int msm_register_domain(struct msm_iova_layout *layout); + +#else +static inline struct iommu_domain + *msm_get_iommu_domain(int subsys_id) { return NULL; } + + + +static inline int msm_allocate_iova_address(unsigned int iommu_domain, + unsigned int partition_no, + unsigned long size, + unsigned long align, + unsigned long *iova) { return -ENOMEM; } + +static inline void msm_free_iova_address(unsigned long iova, + unsigned int iommu_domain, + unsigned int partition_no, + unsigned long size) { return; } + +static inline int msm_use_iommu(void) +{ + return 0; +} + +static inline int msm_iommu_map_extra(struct iommu_domain *domain, + unsigned long start_iova, + unsigned long size, + unsigned long page_size, + int cached) +{ + return -ENODEV; +} + +static inline void msm_iommu_unmap_extra(struct iommu_domain *domain, + unsigned long start_iova, + unsigned long size, + unsigned long page_size) +{ +} + +static inline int msm_iommu_map_contig_buffer(unsigned long phys, + unsigned int domain_no, + unsigned int partition_no, + unsigned long size, + unsigned long align, + unsigned long cached, + unsigned long *iova_val) +{ + *iova_val = phys; + return 0; +} + +static inline void msm_iommu_unmap_contig_buffer(unsigned long iova, + unsigned int domain_no, + unsigned int partition_no, + unsigned long size) +{ + return; +} + +static inline int msm_register_domain(struct msm_iova_layout *layout) +{ + return -ENODEV; +} +#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 fc160101dead20c6984cb75701493563cc713ea1..af82fd979008c5ed6677aab23fe1e60b6acedeef 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) @@ -61,8 +57,9 @@ do { \ #define FL_TYPE_TABLE (1 << 0) #define FL_TYPE_SECT (2 << 0) #define FL_SUPERSECTION (1 << 18) -#define FL_AP_WRITE (1 << 10) -#define FL_AP_READ (1 << 11) +#define FL_AP0 (1 << 10) +#define FL_AP1 (1 << 11) +#define FL_AP2 (1 << 15) #define FL_SHARED (1 << 16) #define FL_BUFFERABLE (1 << 2) #define FL_CACHEABLE (1 << 3) @@ -77,6 +74,7 @@ do { \ #define SL_TYPE_SMALL (2 << 0) #define SL_AP0 (1 << 4) #define SL_AP1 (2 << 4) +#define SL_AP2 (1 << 9) #define SL_SHARED (1 << 10) #define SL_BUFFERABLE (1 << 2) #define SL_CACHEABLE (1 << 3) diff --git a/arch/arm/mach-msm/include/mach/iommu_hw-v2.h b/arch/arm/mach-msm/include/mach/iommu_hw-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..fac13b33935d4016cdb94a73cbd10eb1616a02c2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/iommu_hw-v2.h @@ -0,0 +1,2111 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IOMMU_HW_V2_H +#define __ARCH_ARM_MACH_MSM_IOMMU_HW_V2_H + +#define CTX_SHIFT 12 +#define CTX_OFFSET 0x8000 + +#define MAX_NUM_SMR 128 + +#define GET_GLOBAL_REG(reg, base) (readl_relaxed((base) + (reg))) +#define GET_CTX_REG(reg, base, ctx) \ + (readl_relaxed((base) + CTX_OFFSET + (reg) + ((ctx) << CTX_SHIFT))) + +#define SET_GLOBAL_REG(reg, base, val) writel_relaxed((val), ((base) + (reg))) + +#define SET_CTX_REG(reg, base, ctx, val) \ + writel_relaxed((val), \ + ((base) + CTX_OFFSET + (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)) +#define GET_GLOBAL_REG_N(b, n, r) GET_GLOBAL_REG((b), ((r) + (n << 2))) + +/* Field wrappers */ +#define GET_GLOBAL_FIELD(b, r, F) \ + GET_FIELD(((b) + (r)), r##_##F##_MASK, r##_##F##_SHIFT) +#define GET_CONTEXT_FIELD(b, c, r, F) \ + GET_FIELD(((b) + CTX_OFFSET + (r) + ((c) << CTX_SHIFT)), \ + r##_##F##_MASK, r##_##F##_SHIFT) + +#define SET_GLOBAL_FIELD(b, r, F, v) \ + SET_FIELD(((b) + (r)), r##_##F##_MASK, r##_##F##_SHIFT, (v)) +#define SET_CONTEXT_FIELD(b, c, r, F, v) \ + SET_FIELD(((b) + CTX_OFFSET + (r) + ((c) << CTX_SHIFT)), \ + r##_##F##_MASK, r##_##F##_SHIFT, (v)) + +/* Wrappers for numbered field registers */ +#define SET_GLOBAL_FIELD_N(b, n, r, F, v) \ + SET_FIELD(((b) + ((n) << 2) + (r)), r##_##F##_MASK, r##_##F##_SHIFT, v) +#define GET_GLOBAL_FIELD_N(b, n, r, F) \ + GET_FIELD(((b) + ((n) << 2) + (r)), r##_##F##_MASK, r##_##F##_SHIFT) + +#define GET_FIELD(addr, mask, shift) ((readl_relaxed(addr) >> (shift)) & (mask)) + +#define SET_FIELD(addr, mask, shift, v) \ +do { \ + int t = readl_relaxed(addr); \ + writel_relaxed((t & ~((mask) << (shift))) + (((v) & \ + (mask)) << (shift)), addr); \ +} while (0) + + +/* Global register space 0 setters / getters */ +#define SET_CR0(b, v) SET_GLOBAL_REG(CR0, (b), (v)) +#define SET_SCR1(b, v) SET_GLOBAL_REG(SCR1, (b), (v)) +#define SET_CR2(b, v) SET_GLOBAL_REG(CR2, (b), (v)) +#define SET_ACR(b, v) SET_GLOBAL_REG(ACR, (b), (v)) +#define SET_IDR0(b, N, v) SET_GLOBAL_REG(IDR0, (b), (v)) +#define SET_IDR1(b, N, v) SET_GLOBAL_REG(IDR1, (b), (v)) +#define SET_IDR2(b, N, v) SET_GLOBAL_REG(IDR2, (b), (v)) +#define SET_IDR7(b, N, v) SET_GLOBAL_REG(IDR7, (b), (v)) +#define SET_GFAR(b, v) SET_GLOBAL_REG(GFAR, (b), (v)) +#define SET_GFSR(b, v) SET_GLOBAL_REG(GFSR, (b), (v)) +#define SET_GFSRRESTORE(b, v) SET_GLOBAL_REG(GFSRRESTORE, (b), (v)) +#define SET_GFSYNR0(b, v) SET_GLOBAL_REG(GFSYNR0, (b), (v)) +#define SET_GFSYNR1(b, v) SET_GLOBAL_REG(GFSYNR1, (b), (v)) +#define SET_GFSYNR2(b, v) SET_GLOBAL_REG(GFSYNR2, (b), (v)) +#define SET_TLBIVMID(b, v) SET_GLOBAL_REG(TLBIVMID, (b), (v)) +#define SET_TLBIALLNSNH(b, v) SET_GLOBAL_REG(TLBIALLNSNH, (b), (v)) +#define SET_TLBIALLH(b, v) SET_GLOBAL_REG(TLBIALLH, (b), (v)) +#define SET_TLBGSYNC(b, v) SET_GLOBAL_REG(TLBGSYNC, (b), (v)) +#define SET_TLBGSTATUS(b, v) SET_GLOBAL_REG(TLBSTATUS, (b), (v)) +#define SET_TLBIVAH(b, v) SET_GLOBAL_REG(TLBIVAH, (b), (v)) +#define SET_GATS1UR(b, v) SET_GLOBAL_REG(GATS1UR, (b), (v)) +#define SET_GATS1UW(b, v) SET_GLOBAL_REG(GATS1UW, (b), (v)) +#define SET_GATS1PR(b, v) SET_GLOBAL_REG(GATS1PR, (b), (v)) +#define SET_GATS1PW(b, v) SET_GLOBAL_REG(GATS1PW, (b), (v)) +#define SET_GATS12UR(b, v) SET_GLOBAL_REG(GATS12UR, (b), (v)) +#define SET_GATS12UW(b, v) SET_GLOBAL_REG(GATS12UW, (b), (v)) +#define SET_GATS12PR(b, v) SET_GLOBAL_REG(GATS12PR, (b), (v)) +#define SET_GATS12PW(b, v) SET_GLOBAL_REG(GATS12PW, (b), (v)) +#define SET_GPAR(b, v) SET_GLOBAL_REG(GPAR, (b), (v)) +#define SET_GATSR(b, v) SET_GLOBAL_REG(GATSR, (b), (v)) +#define SET_NSCR0(b, v) SET_GLOBAL_REG(NSCR0, (b), (v)) +#define SET_NSCR2(b, v) SET_GLOBAL_REG(NSCR2, (b), (v)) +#define SET_NSACR(b, v) SET_GLOBAL_REG(NSACR, (b), (v)) +#define SET_PMCR(b, v) SET_GLOBAL_REG(PMCR, (b), (v)) +#define SET_SMR_N(b, N, v) SET_GLOBAL_REG_N(SMR, N, (b), (v)) +#define SET_S2CR_N(b, N, v) SET_GLOBAL_REG_N(S2CR, N, (b), (v)) + +#define GET_CR0(b) GET_GLOBAL_REG(CR0, (b)) +#define GET_SCR1(b) GET_GLOBAL_REG(SCR1, (b)) +#define GET_CR2(b) GET_GLOBAL_REG(CR2, (b)) +#define GET_ACR(b) GET_GLOBAL_REG(ACR, (b)) +#define GET_IDR0(b, N) GET_GLOBAL_REG(IDR0, (b)) +#define GET_IDR1(b, N) GET_GLOBAL_REG(IDR1, (b)) +#define GET_IDR2(b, N) GET_GLOBAL_REG(IDR2, (b)) +#define GET_IDR7(b, N) GET_GLOBAL_REG(IDR7, (b)) +#define GET_GFAR(b) GET_GLOBAL_REG(GFAR, (b)) +#define GET_GFSR(b) GET_GLOBAL_REG(GFSR, (b)) +#define GET_GFSRRESTORE(b) GET_GLOBAL_REG(GFSRRESTORE, (b)) +#define GET_GFSYNR0(b) GET_GLOBAL_REG(GFSYNR0, (b)) +#define GET_GFSYNR1(b) GET_GLOBAL_REG(GFSYNR1, (b)) +#define GET_GFSYNR2(b) GET_GLOBAL_REG(GFSYNR2, (b)) +#define GET_TLBIVMID(b) GET_GLOBAL_REG(TLBIVMID, (b)) +#define GET_TLBIALLNSNH(b) GET_GLOBAL_REG(TLBIALLNSNH, (b)) +#define GET_TLBIALLH(b) GET_GLOBAL_REG(TLBIALLH, (b)) +#define GET_TLBGSYNC(b) GET_GLOBAL_REG(TLBGSYNC, (b)) +#define GET_TLBGSTATUS(b) GET_GLOBAL_REG(TLBSTATUS, (b)) +#define GET_TLBIVAH(b) GET_GLOBAL_REG(TLBIVAH, (b)) +#define GET_GATS1UR(b) GET_GLOBAL_REG(GATS1UR, (b)) +#define GET_GATS1UW(b) GET_GLOBAL_REG(GATS1UW, (b)) +#define GET_GATS1PR(b) GET_GLOBAL_REG(GATS1PR, (b)) +#define GET_GATS1PW(b) GET_GLOBAL_REG(GATS1PW, (b)) +#define GET_GATS12UR(b) GET_GLOBAL_REG(GATS12UR, (b)) +#define GET_GATS12UW(b) GET_GLOBAL_REG(GATS12UW, (b)) +#define GET_GATS12PR(b) GET_GLOBAL_REG(GATS12PR, (b)) +#define GET_GATS12PW(b) GET_GLOBAL_REG(GATS12PW, (b)) +#define GET_GPAR(b) GET_GLOBAL_REG(GPAR, (b)) +#define GET_GATSR(b) GET_GLOBAL_REG(GATSR, (b)) +#define GET_NSCR0(b) GET_GLOBAL_REG(NSCR0, (b)) +#define GET_NSCR2(b) GET_GLOBAL_REG(NSCR2, (b)) +#define GET_NSACR(b) GET_GLOBAL_REG(NSACR, (b)) +#define GET_PMCR(b, v) GET_GLOBAL_REG(PMCR, (b)) +#define GET_SMR_N(b, N) GET_GLOBAL_REG_N(SMR, N, (b)) +#define GET_S2CR_N(b, N) GET_GLOBAL_REG_N(S2CR, N, (b)) + +/* Global register space 1 setters / getters */ +#define SET_CBAR_N(b, N, v) SET_GLOBAL_REG_N(CBAR, N, (b), (v)) +#define SET_CBFRSYNRA_N(b, N, v) SET_GLOBAL_REG_N(CBFRSYNRA, N, (b), (v)) + +#define GET_CBAR_N(b, N) GET_GLOBAL_REG_N(CBAR, N, (b)) +#define GET_CBFRSYNRA_N(b, N) GET_GLOBAL_REG_N(CBFRSYNRA, N, (b)) + +/* Implementation defined register setters/getters */ +#define SET_PREDICTIONDIS0(b, v) SET_GLOBAL_REG(PREDICTIONDIS0, (b), (v)) +#define SET_PREDICTIONDIS1(b, v) SET_GLOBAL_REG(PREDICTIONDIS1, (b), (v)) +#define SET_S1L1BFBLP0(b, v) SET_GLOBAL_REG(S1L1BFBLP0, (b), (v)) + +/* SSD register setters/getters */ +#define SET_SSDR_N(b, N, v) SET_GLOBAL_REG_N(SSDR_N, N, (b), (v)) + +#define GET_SSDR_N(b, N) GET_GLOBAL_REG_N(SSDR_N, N, (b)) + +/* Context bank register setters/getters */ +#define SET_SCTLR(b, c, v) SET_CTX_REG(CB_SCTLR, (b), (c), (v)) +#define SET_ACTLR(b, c, v) SET_CTX_REG(CB_ACTLR, (b), (c), (v)) +#define SET_RESUME(b, c, v) SET_CTX_REG(CB_RESUME, (b), (c), (v)) +#define SET_TTBR0(b, c, v) SET_CTX_REG(CB_TTBR0, (b), (c), (v)) +#define SET_TTBR1(b, c, v) SET_CTX_REG(CB_TTBR1, (b), (c), (v)) +#define SET_TTBCR(b, c, v) SET_CTX_REG(CB_TTBCR, (b), (c), (v)) +#define SET_CONTEXTIDR(b, c, v) SET_CTX_REG(CB_CONTEXTIDR, (b), (c), (v)) +#define SET_PRRR(b, c, v) SET_CTX_REG(CB_PRRR, (b), (c), (v)) +#define SET_NMRR(b, c, v) SET_CTX_REG(CB_NMRR, (b), (c), (v)) +#define SET_PAR(b, c, v) SET_CTX_REG(CB_PAR, (b), (c), (v)) +#define SET_FSR(b, c, v) SET_CTX_REG(CB_FSR, (b), (c), (v)) +#define SET_FSRRESTORE(b, c, v) SET_CTX_REG(CB_FSRRESTORE, (b), (c), (v)) +#define SET_FAR(b, c, v) SET_CTX_REG(CB_FAR, (b), (c), (v)) +#define SET_FSYNR0(b, c, v) SET_CTX_REG(CB_FSYNR0, (b), (c), (v)) +#define SET_FSYNR1(b, c, v) SET_CTX_REG(CB_FSYNR1, (b), (c), (v)) +#define SET_TLBIVA(b, c, v) SET_CTX_REG(CB_TLBIVA, (b), (c), (v)) +#define SET_TLBIVAA(b, c, v) SET_CTX_REG(CB_TLBIVAA, (b), (c), (v)) +#define SET_TLBIASID(b, c, v) SET_CTX_REG(CB_TLBIASID, (b), (c), (v)) +#define SET_TLBIALL(b, c, v) SET_CTX_REG(CB_TLBIALL, (b), (c), (v)) +#define SET_TLBIVAL(b, c, v) SET_CTX_REG(CB_TLBIVAL, (b), (c), (v)) +#define SET_TLBIVAAL(b, c, v) SET_CTX_REG(CB_TLBIVAAL, (b), (c), (v)) +#define SET_TLBSYNC(b, c, v) SET_CTX_REG(CB_TLBSYNC, (b), (c), (v)) +#define SET_TLBSTATUS(b, c, v) SET_CTX_REG(CB_TLBSTATUS, (b), (c), (v)) +#define SET_ATS1PR(b, c, v) SET_CTX_REG(CB_ATS1PR, (b), (c), (v)) +#define SET_ATS1PW(b, c, v) SET_CTX_REG(CB_ATS1PW, (b), (c), (v)) +#define SET_ATS1UR(b, c, v) SET_CTX_REG(CB_ATS1UR, (b), (c), (v)) +#define SET_ATS1UW(b, c, v) SET_CTX_REG(CB_ATS1UW, (b), (c), (v)) +#define SET_ATSR(b, c, v) SET_CTX_REG(CB_ATSR, (b), (c), (v)) + +#define GET_SCTLR(b, c) GET_CTX_REG(CB_SCTLR, (b), (c)) +#define GET_ACTLR(b, c) GET_CTX_REG(CB_ACTLR, (b), (c)) +#define GET_RESUME(b, c) GET_CTX_REG(CB_RESUME, (b), (c)) +#define GET_TTBR0(b, c) GET_CTX_REG(CB_TTBR0, (b), (c)) +#define GET_TTBR1(b, c) GET_CTX_REG(CB_TTBR1, (b), (c)) +#define GET_TTBCR(b, c) GET_CTX_REG(CB_TTBCR, (b), (c)) +#define GET_CONTEXTIDR(b, c) GET_CTX_REG(CB_CONTEXTIDR, (b), (c)) +#define GET_PRRR(b, c) GET_CTX_REG(CB_PRRR, (b), (c)) +#define GET_NMRR(b, c) GET_CTX_REG(CB_NMRR, (b), (c)) +#define GET_PAR(b, c) GET_CTX_REG(CB_PAR, (b), (c)) +#define GET_FSR(b, c) GET_CTX_REG(CB_FSR, (b), (c)) +#define GET_FSRRESTORE(b, c) GET_CTX_REG(CB_FSRRESTORE, (b), (c)) +#define GET_FAR(b, c) GET_CTX_REG(CB_FAR, (b), (c)) +#define GET_FSYNR0(b, c) GET_CTX_REG(CB_FSYNR0, (b), (c)) +#define GET_FSYNR1(b, c) GET_CTX_REG(CB_FSYNR1, (b), (c)) +#define GET_TLBIVA(b, c) GET_CTX_REG(CB_TLBIVA, (b), (c)) +#define GET_TLBIVAA(b, c) GET_CTX_REG(CB_TLBIVAA, (b), (c)) +#define GET_TLBIASID(b, c) GET_CTX_REG(CB_TLBIASID, (b), (c)) +#define GET_TLBIALL(b, c) GET_CTX_REG(CB_TLBIALL, (b), (c)) +#define GET_TLBIVAL(b, c) GET_CTX_REG(CB_TLBIVAL, (b), (c)) +#define GET_TLBIVAAL(b, c) GET_CTX_REG(CB_TLBIVAAL, (b), (c)) +#define GET_TLBSYNC(b, c) GET_CTX_REG(CB_TLBSYNC, (b), (c)) +#define GET_TLBSTATUS(b, c) GET_CTX_REG(CB_TLBSTATUS, (b), (c)) +#define GET_ATS1PR(b, c) GET_CTX_REG(CB_ATS1PR, (b), (c)) +#define GET_ATS1PW(b, c) GET_CTX_REG(CB_ATS1PW, (b), (c)) +#define GET_ATS1UR(b, c) GET_CTX_REG(CB_ATS1UR, (b), (c)) +#define GET_ATS1UW(b, c) GET_CTX_REG(CB_ATS1UW, (b), (c)) +#define GET_ATSR(b, c) GET_CTX_REG(CB_ATSR, (b), (c)) + +/* Global Register field setters / getters */ +/* Configuration Register: CR0 */ +#define SET_CR0_NSCFG(b, v) SET_GLOBAL_FIELD(b, CR0, NSCFG, v) +#define SET_CR0_WACFG(b, v) SET_GLOBAL_FIELD(b, CR0, WACFG, v) +#define SET_CR0_RACFG(b, v) SET_GLOBAL_FIELD(b, CR0, RACFG, v) +#define SET_CR0_SHCFG(b, v) SET_GLOBAL_FIELD(b, CR0, SHCFG, v) +#define SET_CR0_SMCFCFG(b, v) SET_GLOBAL_FIELD(b, CR0, SMCFCFG, v) +#define SET_CR0_MTCFG(b, v) SET_GLOBAL_FIELD(b, CR0, MTCFG, v) +#define SET_CR0_BSU(b, v) SET_GLOBAL_FIELD(b, CR0, BSU, v) +#define SET_CR0_FB(b, v) SET_GLOBAL_FIELD(b, CR0, FB, v) +#define SET_CR0_PTM(b, v) SET_GLOBAL_FIELD(b, CR0, PTM, v) +#define SET_CR0_VMIDPNE(b, v) SET_GLOBAL_FIELD(b, CR0, VMIDPNE, v) +#define SET_CR0_USFCFG(b, v) SET_GLOBAL_FIELD(b, CR0, USFCFG, v) +#define SET_CR0_GSE(b, v) SET_GLOBAL_FIELD(b, CR0, GSE, v) +#define SET_CR0_STALLD(b, v) SET_GLOBAL_FIELD(b, CR0, STALLD, v) +#define SET_CR0_TRANSIENTCFG(b, v) SET_GLOBAL_FIELD(b, CR0, TRANSIENTCFG, v) +#define SET_CR0_GCFGFIE(b, v) SET_GLOBAL_FIELD(b, CR0, GCFGFIE, v) +#define SET_CR0_GCFGFRE(b, v) SET_GLOBAL_FIELD(b, CR0, GCFGFRE, v) +#define SET_CR0_GFIE(b, v) SET_GLOBAL_FIELD(b, CR0, GFIE, v) +#define SET_CR0_GFRE(b, v) SET_GLOBAL_FIELD(b, CR0, GFRE, v) +#define SET_CR0_CLIENTPD(b, v) SET_GLOBAL_FIELD(b, CR0, CLIENTPD, v) + +#define GET_CR0_NSCFG(b) GET_GLOBAL_FIELD(b, CR0, NSCFG) +#define GET_CR0_WACFG(b) GET_GLOBAL_FIELD(b, CR0, WACFG) +#define GET_CR0_RACFG(b) GET_GLOBAL_FIELD(b, CR0, RACFG) +#define GET_CR0_SHCFG(b) GET_GLOBAL_FIELD(b, CR0, SHCFG) +#define GET_CR0_SMCFCFG(b) GET_GLOBAL_FIELD(b, CR0, SMCFCFG) +#define GET_CR0_MTCFG(b) GET_GLOBAL_FIELD(b, CR0, MTCFG) +#define GET_CR0_BSU(b) GET_GLOBAL_FIELD(b, CR0, BSU) +#define GET_CR0_FB(b) GET_GLOBAL_FIELD(b, CR0, FB) +#define GET_CR0_PTM(b) GET_GLOBAL_FIELD(b, CR0, PTM) +#define GET_CR0_VMIDPNE(b) GET_GLOBAL_FIELD(b, CR0, VMIDPNE) +#define GET_CR0_USFCFG(b) GET_GLOBAL_FIELD(b, CR0, USFCFG) +#define GET_CR0_GSE(b) GET_GLOBAL_FIELD(b, CR0, GSE) +#define GET_CR0_STALLD(b) GET_GLOBAL_FIELD(b, CR0, STALLD) +#define GET_CR0_TRANSIENTCFG(b) GET_GLOBAL_FIELD(b, CR0, TRANSIENTCFG) +#define GET_CR0_GCFGFIE(b) GET_GLOBAL_FIELD(b, CR0, GCFGFIE) +#define GET_CR0_GCFGFRE(b) GET_GLOBAL_FIELD(b, CR0, GCFGFRE) +#define GET_CR0_GFIE(b) GET_GLOBAL_FIELD(b, CR0, GFIE) +#define GET_CR0_GFRE(b) GET_GLOBAL_FIELD(b, CR0, GFRE) +#define GET_CR0_CLIENTPD(b) GET_GLOBAL_FIELD(b, CR0, CLIENTPD) + +/* Configuration Register: CR2 */ +#define SET_CR2_BPVMID(b, v) SET_GLOBAL_FIELD(b, CR2, BPVMID, v) + +#define GET_CR2_BPVMID(b) GET_GLOBAL_FIELD(b, CR2, BPVMID) + +/* Global Address Translation, Stage 1, Privileged Read: GATS1PR */ +#define SET_GATS1PR_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS1PR, ADDR, v) +#define SET_GATS1PR_NDX(b, v) SET_GLOBAL_FIELD(b, GATS1PR, NDX, v) + +#define GET_GATS1PR_ADDR(b) GET_GLOBAL_FIELD(b, GATS1PR, ADDR) +#define GET_GATS1PR_NDX(b) GET_GLOBAL_FIELD(b, GATS1PR, NDX) + +/* Global Address Translation, Stage 1, Privileged Write: GATS1PW */ +#define SET_GATS1PW_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS1PW, ADDR, v) +#define SET_GATS1PW_NDX(b, v) SET_GLOBAL_FIELD(b, GATS1PW, NDX, v) + +#define GET_GATS1PW_ADDR(b) GET_GLOBAL_FIELD(b, GATS1PW, ADDR) +#define GET_GATS1PW_NDX(b) GET_GLOBAL_FIELD(b, GATS1PW, NDX) + +/* Global Address Translation, Stage 1, User Read: GATS1UR */ +#define SET_GATS1UR_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS1UR, ADDR, v) +#define SET_GATS1UR_NDX(b, v) SET_GLOBAL_FIELD(b, GATS1UR, NDX, v) + +#define GET_GATS1UR_ADDR(b) GET_GLOBAL_FIELD(b, GATS1UR, ADDR) +#define GET_GATS1UR_NDX(b) GET_GLOBAL_FIELD(b, GATS1UR, NDX) + +/* Global Address Translation, Stage 1, User Read: GATS1UW */ +#define SET_GATS1UW_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS1UW, ADDR, v) +#define SET_GATS1UW_NDX(b, v) SET_GLOBAL_FIELD(b, GATS1UW, NDX, v) + +#define GET_GATS1UW_ADDR(b) GET_GLOBAL_FIELD(b, GATS1UW, ADDR) +#define GET_GATS1UW_NDX(b) GET_GLOBAL_FIELD(b, GATS1UW, NDX) + +/* Global Address Translation, Stage 1 and 2, Privileged Read: GATS12PR */ +#define SET_GATS12PR_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS12PR, ADDR, v) +#define SET_GATS12PR_NDX(b, v) SET_GLOBAL_FIELD(b, GATS12PR, NDX, v) + +#define GET_GATS12PR_ADDR(b) GET_GLOBAL_FIELD(b, GATS12PR, ADDR) +#define GET_GATS12PR_NDX(b) GET_GLOBAL_FIELD(b, GATS12PR, NDX) + +/* Global Address Translation, Stage 1, Privileged Write: GATS1PW */ +#define SET_GATS12PW_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS12PW, ADDR, v) +#define SET_GATS12PW_NDX(b, v) SET_GLOBAL_FIELD(b, GATS12PW, NDX, v) + +#define GET_GATS12PW_ADDR(b) GET_GLOBAL_FIELD(b, GATS12PW, ADDR) +#define GET_GATS12PW_NDX(b) GET_GLOBAL_FIELD(b, GATS12PW, NDX) + +/* Global Address Translation, Stage 1, User Read: GATS1UR */ +#define SET_GATS12UR_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS12UR, ADDR, v) +#define SET_GATS12UR_NDX(b, v) SET_GLOBAL_FIELD(b, GATS12UR, NDX, v) + +#define GET_GATS12UR_ADDR(b) GET_GLOBAL_FIELD(b, GATS12UR, ADDR) +#define GET_GATS12UR_NDX(b) GET_GLOBAL_FIELD(b, GATS12UR, NDX) + +/* Global Address Translation, Stage 1, User Read: GATS1UW */ +#define SET_GATS12UW_ADDR(b, v) SET_GLOBAL_FIELD(b, GATS12UW, ADDR, v) +#define SET_GATS12UW_NDX(b, v) SET_GLOBAL_FIELD(b, GATS12UW, NDX, v) + +#define GET_GATS12UW_ADDR(b) GET_GLOBAL_FIELD(b, GATS12UW, ADDR) +#define GET_GATS12UW_NDX(b) GET_GLOBAL_FIELD(b, GATS12UW, NDX) + +/* Global Address Translation Status Register: GATSR */ +#define SET_GATSR_ACTIVE(b, v) SET_GLOBAL_FIELD(b, GATSR, ACTIVE, v) + +#define GET_GATSR_ACTIVE(b) GET_GLOBAL_FIELD(b, GATSR, ACTIVE) + +/* Global Fault Address Register: GFAR */ +#define SET_GFAR_FADDR(b, v) SET_GLOBAL_FIELD(b, GFAR, FADDR, v) + +#define GET_GFAR_FADDR(b) GET_GLOBAL_FIELD(b, GFAR, FADDR) + +/* Global Fault Status Register: GFSR */ +#define SET_GFSR_ICF(b, v) SET_GLOBAL_FIELD(b, GFSR, ICF, v) +#define SET_GFSR_USF(b, v) SET_GLOBAL_FIELD(b, GFSR, USF, v) +#define SET_GFSR_SMCF(b, v) SET_GLOBAL_FIELD(b, GFSR, SMCF, v) +#define SET_GFSR_UCBF(b, v) SET_GLOBAL_FIELD(b, GFSR, UCBF, v) +#define SET_GFSR_UCIF(b, v) SET_GLOBAL_FIELD(b, GFSR, UCIF, v) +#define SET_GFSR_CAF(b, v) SET_GLOBAL_FIELD(b, GFSR, CAF, v) +#define SET_GFSR_EF(b, v) SET_GLOBAL_FIELD(b, GFSR, EF, v) +#define SET_GFSR_PF(b, v) SET_GLOBAL_FIELD(b, GFSR, PF, v) +#define SET_GFSR_MULTI(b, v) SET_GLOBAL_FIELD(b, GFSR, MULTI, v) + +#define GET_GFSR_ICF(b) GET_GLOBAL_FIELD(b, GFSR, ICF) +#define GET_GFSR_USF(b) GET_GLOBAL_FIELD(b, GFSR, USF) +#define GET_GFSR_SMCF(b) GET_GLOBAL_FIELD(b, GFSR, SMCF) +#define GET_GFSR_UCBF(b) GET_GLOBAL_FIELD(b, GFSR, UCBF) +#define GET_GFSR_UCIF(b) GET_GLOBAL_FIELD(b, GFSR, UCIF) +#define GET_GFSR_CAF(b) GET_GLOBAL_FIELD(b, GFSR, CAF) +#define GET_GFSR_EF(b) GET_GLOBAL_FIELD(b, GFSR, EF) +#define GET_GFSR_PF(b) GET_GLOBAL_FIELD(b, GFSR, PF) +#define GET_GFSR_MULTI(b) GET_GLOBAL_FIELD(b, GFSR, MULTI) + +/* Global Fault Syndrome Register 0: GFSYNR0 */ +#define SET_GFSYNR0_NESTED(b, v) SET_GLOBAL_FIELD(b, GFSYNR0, NESTED, v) +#define SET_GFSYNR0_WNR(b, v) SET_GLOBAL_FIELD(b, GFSYNR0, WNR, v) +#define SET_GFSYNR0_PNU(b, v) SET_GLOBAL_FIELD(b, GFSYNR0, PNU, v) +#define SET_GFSYNR0_IND(b, v) SET_GLOBAL_FIELD(b, GFSYNR0, IND, v) +#define SET_GFSYNR0_NSSTATE(b, v) SET_GLOBAL_FIELD(b, GFSYNR0, NSSTATE, v) +#define SET_GFSYNR0_NSATTR(b, v) SET_GLOBAL_FIELD(b, GFSYNR0, NSATTR, v) + +#define GET_GFSYNR0_NESTED(b) GET_GLOBAL_FIELD(b, GFSYNR0, NESTED) +#define GET_GFSYNR0_WNR(b) GET_GLOBAL_FIELD(b, GFSYNR0, WNR) +#define GET_GFSYNR0_PNU(b) GET_GLOBAL_FIELD(b, GFSYNR0, PNU) +#define GET_GFSYNR0_IND(b) GET_GLOBAL_FIELD(b, GFSYNR0, IND) +#define GET_GFSYNR0_NSSTATE(b) GET_GLOBAL_FIELD(b, GFSYNR0, NSSTATE) +#define GET_GFSYNR0_NSATTR(b) GET_GLOBAL_FIELD(b, GFSYNR0, NSATTR) + +/* Global Fault Syndrome Register 1: GFSYNR1 */ +#define SET_GFSYNR1_SID(b, v) SET_GLOBAL_FIELD(b, GFSYNR1, SID, v) + +#define GET_GFSYNR1_SID(b) GET_GLOBAL_FIELD(b, GFSYNR1, SID) + +/* Global Physical Address Register: GPAR */ +#define SET_GPAR_F(b, v) SET_GLOBAL_FIELD(b, GPAR, F, v) +#define SET_GPAR_SS(b, v) SET_GLOBAL_FIELD(b, GPAR, SS, v) +#define SET_GPAR_OUTER(b, v) SET_GLOBAL_FIELD(b, GPAR, OUTER, v) +#define SET_GPAR_INNER(b, v) SET_GLOBAL_FIELD(b, GPAR, INNER, v) +#define SET_GPAR_SH(b, v) SET_GLOBAL_FIELD(b, GPAR, SH, v) +#define SET_GPAR_NS(b, v) SET_GLOBAL_FIELD(b, GPAR, NS, v) +#define SET_GPAR_NOS(b, v) SET_GLOBAL_FIELD(b, GPAR, NOS, v) +#define SET_GPAR_PA(b, v) SET_GLOBAL_FIELD(b, GPAR, PA, v) +#define SET_GPAR_TF(b, v) SET_GLOBAL_FIELD(b, GPAR, TF, v) +#define SET_GPAR_AFF(b, v) SET_GLOBAL_FIELD(b, GPAR, AFF, v) +#define SET_GPAR_PF(b, v) SET_GLOBAL_FIELD(b, GPAR, PF, v) +#define SET_GPAR_EF(b, v) SET_GLOBAL_FIELD(b, GPAR, EF, v) +#define SET_GPAR_TLCMCF(b, v) SET_GLOBAL_FIELD(b, GPAR, TLCMCF, v) +#define SET_GPAR_TLBLKF(b, v) SET_GLOBAL_FIELD(b, GPAR, TLBLKF, v) +#define SET_GPAR_UCBF(b, v) SET_GLOBAL_FIELD(b, GPAR, UCBF, v) + +#define GET_GPAR_F(b) GET_GLOBAL_FIELD(b, GPAR, F) +#define GET_GPAR_SS(b) GET_GLOBAL_FIELD(b, GPAR, SS) +#define GET_GPAR_OUTER(b) GET_GLOBAL_FIELD(b, GPAR, OUTER) +#define GET_GPAR_INNER(b) GET_GLOBAL_FIELD(b, GPAR, INNER) +#define GET_GPAR_SH(b) GET_GLOBAL_FIELD(b, GPAR, SH) +#define GET_GPAR_NS(b) GET_GLOBAL_FIELD(b, GPAR, NS) +#define GET_GPAR_NOS(b) GET_GLOBAL_FIELD(b, GPAR, NOS) +#define GET_GPAR_PA(b) GET_GLOBAL_FIELD(b, GPAR, PA) +#define GET_GPAR_TF(b) GET_GLOBAL_FIELD(b, GPAR, TF) +#define GET_GPAR_AFF(b) GET_GLOBAL_FIELD(b, GPAR, AFF) +#define GET_GPAR_PF(b) GET_GLOBAL_FIELD(b, GPAR, PF) +#define GET_GPAR_EF(b) GET_GLOBAL_FIELD(b, GPAR, EF) +#define GET_GPAR_TLCMCF(b) GET_GLOBAL_FIELD(b, GPAR, TLCMCF) +#define GET_GPAR_TLBLKF(b) GET_GLOBAL_FIELD(b, GPAR, TLBLKF) +#define GET_GPAR_UCBF(b) GET_GLOBAL_FIELD(b, GPAR, UCBF) + +/* Identification Register: IDR0 */ +#define SET_IDR0_NUMSMRG(b, v) SET_GLOBAL_FIELD(b, IDR0, NUMSMRG, v) +#define SET_IDR0_NUMSIDB(b, v) SET_GLOBAL_FIELD(b, IDR0, NUMSIDB, v) +#define SET_IDR0_BTM(b, v) SET_GLOBAL_FIELD(b, IDR0, BTM, v) +#define SET_IDR0_CTTW(b, v) SET_GLOBAL_FIELD(b, IDR0, CTTW, v) +#define SET_IDR0_NUMIRPT(b, v) SET_GLOBAL_FIELD(b, IDR0, NUMIRPT, v) +#define SET_IDR0_PTFS(b, v) SET_GLOBAL_FIELD(b, IDR0, PTFS, v) +#define SET_IDR0_SMS(b, v) SET_GLOBAL_FIELD(b, IDR0, SMS, v) +#define SET_IDR0_NTS(b, v) SET_GLOBAL_FIELD(b, IDR0, NTS, v) +#define SET_IDR0_S2TS(b, v) SET_GLOBAL_FIELD(b, IDR0, S2TS, v) +#define SET_IDR0_S1TS(b, v) SET_GLOBAL_FIELD(b, IDR0, S1TS, v) +#define SET_IDR0_SES(b, v) SET_GLOBAL_FIELD(b, IDR0, SES, v) + +#define GET_IDR0_NUMSMRG(b) GET_GLOBAL_FIELD(b, IDR0, NUMSMRG) +#define GET_IDR0_NUMSIDB(b) GET_GLOBAL_FIELD(b, IDR0, NUMSIDB) +#define GET_IDR0_BTM(b) GET_GLOBAL_FIELD(b, IDR0, BTM) +#define GET_IDR0_CTTW(b) GET_GLOBAL_FIELD(b, IDR0, CTTW) +#define GET_IDR0_NUMIRPT(b) GET_GLOBAL_FIELD(b, IDR0, NUMIRPT) +#define GET_IDR0_PTFS(b) GET_GLOBAL_FIELD(b, IDR0, PTFS) +#define GET_IDR0_SMS(b) GET_GLOBAL_FIELD(b, IDR0, SMS) +#define GET_IDR0_NTS(b) GET_GLOBAL_FIELD(b, IDR0, NTS) +#define GET_IDR0_S2TS(b) GET_GLOBAL_FIELD(b, IDR0, S2TS) +#define GET_IDR0_S1TS(b) GET_GLOBAL_FIELD(b, IDR0, S1TS) +#define GET_IDR0_SES(b) GET_GLOBAL_FIELD(b, IDR0, SES) + +/* Identification Register: IDR1 */ +#define SET_IDR1_NUMCB(b, v) SET_GLOBAL_FIELD(b, IDR1, NUMCB, v) +#define SET_IDR1_NUMSSDNDXB(b, v) SET_GLOBAL_FIELD(b, IDR1, NUMSSDNDXB, v) +#define SET_IDR1_SSDTP(b, v) SET_GLOBAL_FIELD(b, IDR1, SSDTP, v) +#define SET_IDR1_SMCD(b, v) SET_GLOBAL_FIELD(b, IDR1, SMCD, v) +#define SET_IDR1_NUMS2CB(b, v) SET_GLOBAL_FIELD(b, IDR1, NUMS2CB, v) +#define SET_IDR1_NUMPAGENDXB(b, v) SET_GLOBAL_FIELD(b, IDR1, NUMPAGENDXB, v) +#define SET_IDR1_PAGESIZE(b, v) SET_GLOBAL_FIELD(b, IDR1, PAGESIZE, v) + +#define GET_IDR1_NUMCB(b) GET_GLOBAL_FIELD(b, IDR1, NUMCB) +#define GET_IDR1_NUMSSDNDXB(b) GET_GLOBAL_FIELD(b, IDR1, NUMSSDNDXB) +#define GET_IDR1_SSDTP(b) GET_GLOBAL_FIELD(b, IDR1, SSDTP) +#define GET_IDR1_SMCD(b) GET_GLOBAL_FIELD(b, IDR1, SMCD) +#define GET_IDR1_NUMS2CB(b) GET_GLOBAL_FIELD(b, IDR1, NUMS2CB) +#define GET_IDR1_NUMPAGENDXB(b) GET_GLOBAL_FIELD(b, IDR1, NUMPAGENDXB) +#define GET_IDR1_PAGESIZE(b) GET_GLOBAL_FIELD(b, IDR1, PAGESIZE) + +/* Identification Register: IDR2 */ +#define SET_IDR2_IAS(b, v) SET_GLOBAL_FIELD(b, IDR2, IAS, v) +#define SET_IDR2_OAS(b, v) SET_GLOBAL_FIELD(b, IDR2, OAS, v) + +#define GET_IDR2_IAS(b) GET_GLOBAL_FIELD(b, IDR2, IAS) +#define GET_IDR2_OAS(b) GET_GLOBAL_FIELD(b, IDR2, OAS) + +/* Identification Register: IDR7 */ +#define SET_IDR7_MINOR(b, v) SET_GLOBAL_FIELD(b, IDR7, MINOR, v) +#define SET_IDR7_MAJOR(b, v) SET_GLOBAL_FIELD(b, IDR7, MAJOR, v) + +#define GET_IDR7_MINOR(b) GET_GLOBAL_FIELD(b, IDR7, MINOR) +#define GET_IDR7_MAJOR(b) GET_GLOBAL_FIELD(b, IDR7, MAJOR) + +/* Stream to Context Register: S2CR_N */ +#define SET_S2CR_CBNDX(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, CBNDX, v) +#define SET_S2CR_SHCFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, SHCFG, v) +#define SET_S2CR_MTCFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, MTCFG, v) +#define SET_S2CR_MEMATTR(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, MEMATTR, v) +#define SET_S2CR_TYPE(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, TYPE, v) +#define SET_S2CR_NSCFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, NSCFG, v) +#define SET_S2CR_RACFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, RACFG, v) +#define SET_S2CR_WACFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, WACFG, v) +#define SET_S2CR_PRIVCFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, PRIVCFG, v) +#define SET_S2CR_INSTCFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, INSTCFG, v) +#define SET_S2CR_TRANSIENTCFG(b, n, v) \ + SET_GLOBAL_FIELD_N(b, n, S2CR, TRANSIENTCFG, v) +#define SET_S2CR_VMID(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, VMID, v) +#define SET_S2CR_BSU(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, BSU, v) +#define SET_S2CR_FB(b, n, v) SET_GLOBAL_FIELD_N(b, n, S2CR, FB, v) + +#define GET_S2CR_CBNDX(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, CBNDX) +#define GET_S2CR_SHCFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, SHCFG) +#define GET_S2CR_MTCFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, MTCFG) +#define GET_S2CR_MEMATTR(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, MEMATTR) +#define GET_S2CR_TYPE(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, TYPE) +#define GET_S2CR_NSCFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, NSCFG) +#define GET_S2CR_RACFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, RACFG) +#define GET_S2CR_WACFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, WACFG) +#define GET_S2CR_PRIVCFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, PRIVCFG) +#define GET_S2CR_INSTCFG(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, INSTCFG) +#define GET_S2CR_TRANSIENTCFG(b, n) \ + GET_GLOBAL_FIELD_N(b, n, S2CR, TRANSIENTCFG) +#define GET_S2CR_VMID(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, VMID) +#define GET_S2CR_BSU(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, BSU) +#define GET_S2CR_FB(b, n) GET_GLOBAL_FIELD_N(b, n, S2CR, FB) + +/* Stream Match Register: SMR_N */ +#define SET_SMR_ID(b, n, v) SET_GLOBAL_FIELD_N(b, n, SMR, ID, v) +#define SET_SMR_MASK(b, n, v) SET_GLOBAL_FIELD_N(b, n, SMR, MASK, v) +#define SET_SMR_VALID(b, n, v) SET_GLOBAL_FIELD_N(b, n, SMR, VALID, v) + +#define GET_SMR_ID(b, n) GET_GLOBAL_FIELD_N(b, n, SMR, ID) +#define GET_SMR_MASK(b, n) GET_GLOBAL_FIELD_N(b, n, SMR, MASK) +#define GET_SMR_VALID(b, n) GET_GLOBAL_FIELD_N(b, n, SMR, VALID) + +/* Global TLB Status: TLBGSTATUS */ +#define SET_TLBGSTATUS_GSACTIVE(b, v) \ + SET_GLOBAL_FIELD(b, TLBGSTATUS, GSACTIVE, v) + +#define GET_TLBGSTATUS_GSACTIVE(b) \ + GET_GLOBAL_FIELD(b, TLBGSTATUS, GSACTIVE) + +/* Invalidate Hyp TLB by VA: TLBIVAH */ +#define SET_TLBIVAH_ADDR(b, v) SET_GLOBAL_FIELD(b, TLBIVAH, ADDR, v) + +#define GET_TLBIVAH_ADDR(b) GET_GLOBAL_FIELD(b, TLBIVAH, ADDR) + +/* Invalidate TLB by VMID: TLBIVMID */ +#define SET_TLBIVMID_VMID(b, v) SET_GLOBAL_FIELD(b, TLBIVMID, VMID, v) + +#define GET_TLBIVMID_VMID(b) GET_GLOBAL_FIELD(b, TLBIVMID, VMID) + +/* Global Register Space 1 Field setters/getters*/ +/* Context Bank Attribute Register: CBAR_N */ +#define SET_CBAR_VMID(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, VMID, v) +#define SET_CBAR_CBNDX(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, CBNDX, v) +#define SET_CBAR_BPSHCFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, BPSHCFG, v) +#define SET_CBAR_HYPC(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, HYPC, v) +#define SET_CBAR_FB(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, FB, v) +#define SET_CBAR_MEMATTR(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, MEMATTR, v) +#define SET_CBAR_TYPE(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, TYPE, v) +#define SET_CBAR_BSU(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, BSU, v) +#define SET_CBAR_RACFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, RACFG, v) +#define SET_CBAR_WACFG(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, WACFG, v) +#define SET_CBAR_IRPTNDX(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBAR, IRPTNDX, v) + +#define GET_CBAR_VMID(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, VMID) +#define GET_CBAR_CBNDX(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, CBNDX) +#define GET_CBAR_BPSHCFG(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, BPSHCFG) +#define GET_CBAR_HYPC(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, HYPC) +#define GET_CBAR_FB(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, FB) +#define GET_CBAR_MEMATTR(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, MEMATTR) +#define GET_CBAR_TYPE(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, TYPE) +#define GET_CBAR_BSU(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, BSU) +#define GET_CBAR_RACFG(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, RACFG) +#define GET_CBAR_WACFG(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, WACFG) +#define GET_CBAR_IRPTNDX(b, n) GET_GLOBAL_FIELD_N(b, n, CBAR, IRPTNDX) + +/* Context Bank Fault Restricted Syndrome Register A: CBFRSYNRA_N */ +#define SET_CBFRSYNRA_SID(b, n, v) SET_GLOBAL_FIELD_N(b, n, CBFRSYNRA, SID, v) + +#define GET_CBFRSYNRA_SID(b, n) GET_GLOBAL_FIELD_N(b, n, CBFRSYNRA, SID) + +/* Stage 1 Context Bank Format Fields */ +#define SET_CB_ACTLR_REQPRIORITY (b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_ACTLR, REQPRIORITY, v) +#define SET_CB_ACTLR_REQPRIORITYCFG(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_ACTLR, REQPRIORITYCFG, v) +#define SET_CB_ACTLR_PRIVCFG(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_ACTLR, PRIVCFG, v) +#define SET_CB_ACTLR_BPRCOSH(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_ACTLR, BPRCOSH, v) +#define SET_CB_ACTLR_BPRCISH(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_ACTLR, BPRCISH, v) +#define SET_CB_ACTLR_BPRCNSH(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_ACTLR, BPRCNSH, v) + +#define GET_CB_ACTLR_REQPRIORITY (b, c) \ + GET_CONTEXT_FIELD(b, c, CB_ACTLR, REQPRIORITY) +#define GET_CB_ACTLR_REQPRIORITYCFG(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_ACTLR, REQPRIORITYCFG) +#define GET_CB_ACTLR_PRIVCFG(b, c) GET_CONTEXT_FIELD(b, c, CB_ACTLR, PRIVCFG) +#define GET_CB_ACTLR_BPRCOSH(b, c) GET_CONTEXT_FIELD(b, c, CB_ACTLR, BPRCOSH) +#define GET_CB_ACTLR_BPRCISH(b, c) GET_CONTEXT_FIELD(b, c, CB_ACTLR, BPRCISH) +#define GET_CB_ACTLR_BPRCNSH(b, c) GET_CONTEXT_FIELD(b, c, CB_ACTLR, BPRCNSH) + +/* Address Translation, Stage 1, Privileged Read: CB_ATS1PR */ +#define SET_CB_ATS1PR_ADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_ATS1PR, ADDR, v) + +#define GET_CB_ATS1PR_ADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_ATS1PR, ADDR) + +/* Address Translation, Stage 1, Privileged Write: CB_ATS1PW */ +#define SET_CB_ATS1PW_ADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_ATS1PW, ADDR, v) + +#define GET_CB_ATS1PW_ADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_ATS1PW, ADDR) + +/* Address Translation, Stage 1, User Read: CB_ATS1UR */ +#define SET_CB_ATS1UR_ADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_ATS1UR, ADDR, v) + +#define GET_CB_ATS1UR_ADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_ATS1UR, ADDR) + +/* Address Translation, Stage 1, User Write: CB_ATS1UW */ +#define SET_CB_ATS1UW_ADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_ATS1UW, ADDR, v) + +#define GET_CB_ATS1UW_ADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_ATS1UW, ADDR) + +/* Address Translation Status Register: CB_ATSR */ +#define SET_CB_ATSR_ACTIVE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_ATSR, ACTIVE, v) + +#define GET_CB_ATSR_ACTIVE(b, c) GET_CONTEXT_FIELD(b, c, CB_ATSR, ACTIVE) + +/* Context ID Register: CB_CONTEXTIDR */ +#define SET_CB_CONTEXTIDR_ASID(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_CONTEXTIDR, ASID, v) +#define SET_CB_CONTEXTIDR_PROCID(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_CONTEXTIDR, PROCID, v) + +#define GET_CB_CONTEXTIDR_ASID(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_CONTEXTIDR, ASID) +#define GET_CB_CONTEXTIDR_PROCID(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_CONTEXTIDR, PROCID) + +/* Fault Address Register: CB_FAR */ +#define SET_CB_FAR_FADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FAR, FADDR, v) + +#define GET_CB_FAR_FADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_FAR, FADDR) + +/* Fault Status Register: CB_FSR */ +#define SET_CB_FSR_TF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, TF, v) +#define SET_CB_FSR_AFF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, AFF, v) +#define SET_CB_FSR_PF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, PF, v) +#define SET_CB_FSR_EF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, EF, v) +#define SET_CB_FSR_TLBMCF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, TLBMCF, v) +#define SET_CB_FSR_TLBLKF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, TLBLKF, v) +#define SET_CB_FSR_SS(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, SS, v) +#define SET_CB_FSR_MULTI(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSR, MULTI, v) + +#define GET_CB_FSR_TF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, TF) +#define GET_CB_FSR_AFF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, AFF) +#define GET_CB_FSR_PF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, PF) +#define GET_CB_FSR_EF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, EF) +#define GET_CB_FSR_TLBMCF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, TLBMCF) +#define GET_CB_FSR_TLBLKF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, TLBLKF) +#define GET_CB_FSR_SS(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, SS) +#define GET_CB_FSR_MULTI(b, c) GET_CONTEXT_FIELD(b, c, CB_FSR, MULTI) + +/* Fault Syndrome Register 0: CB_FSYNR0 */ +#define SET_CB_FSYNR0_PLVL(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, PLVL, v) +#define SET_CB_FSYNR0_S1PTWF(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_FSYNR0, S1PTWF, v) +#define SET_CB_FSYNR0_WNR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, WNR, v) +#define SET_CB_FSYNR0_PNU(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, PNU, v) +#define SET_CB_FSYNR0_IND(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, IND, v) +#define SET_CB_FSYNR0_NSSTATE(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_FSYNR0, NSSTATE, v) +#define SET_CB_FSYNR0_NSATTR(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_FSYNR0, NSATTR, v) +#define SET_CB_FSYNR0_ATOF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, ATOF, v) +#define SET_CB_FSYNR0_PTWF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, PTWF, v) +#define SET_CB_FSYNR0_AFR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_FSYNR0, AFR, v) +#define SET_CB_FSYNR0_S1CBNDX(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_FSYNR0, S1CBNDX, v) + +#define GET_CB_FSYNR0_PLVL(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, PLVL) +#define GET_CB_FSYNR0_S1PTWF(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_FSYNR0, S1PTWF) +#define GET_CB_FSYNR0_WNR(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, WNR) +#define GET_CB_FSYNR0_PNU(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, PNU) +#define GET_CB_FSYNR0_IND(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, IND) +#define GET_CB_FSYNR0_NSSTATE(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_FSYNR0, NSSTATE) +#define GET_CB_FSYNR0_NSATTR(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_FSYNR0, NSATTR) +#define GET_CB_FSYNR0_ATOF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, ATOF) +#define GET_CB_FSYNR0_PTWF(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, PTWF) +#define GET_CB_FSYNR0_AFR(b, c) GET_CONTEXT_FIELD(b, c, CB_FSYNR0, AFR) +#define GET_CB_FSYNR0_S1CBNDX(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_FSYNR0, S1CBNDX) + +/* Normal Memory Remap Register: CB_NMRR */ +#define SET_CB_NMRR_IR0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR0, v) +#define SET_CB_NMRR_IR1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR1, v) +#define SET_CB_NMRR_IR2(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR2, v) +#define SET_CB_NMRR_IR3(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR3, v) +#define SET_CB_NMRR_IR4(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR4, v) +#define SET_CB_NMRR_IR5(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR5, v) +#define SET_CB_NMRR_IR6(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR6, v) +#define SET_CB_NMRR_IR7(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, IR7, v) +#define SET_CB_NMRR_OR0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR0, v) +#define SET_CB_NMRR_OR1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR1, v) +#define SET_CB_NMRR_OR2(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR2, v) +#define SET_CB_NMRR_OR3(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR3, v) +#define SET_CB_NMRR_OR4(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR4, v) +#define SET_CB_NMRR_OR5(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR5, v) +#define SET_CB_NMRR_OR6(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR6, v) +#define SET_CB_NMRR_OR7(b, c, v) SET_CONTEXT_FIELD(b, c, CB_NMRR, OR7, v) + +#define GET_CB_NMRR_IR0(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR0) +#define GET_CB_NMRR_IR1(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR1) +#define GET_CB_NMRR_IR2(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR2) +#define GET_CB_NMRR_IR3(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR3) +#define GET_CB_NMRR_IR4(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR4) +#define GET_CB_NMRR_IR5(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR5) +#define GET_CB_NMRR_IR6(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR6) +#define GET_CB_NMRR_IR7(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, IR7) +#define GET_CB_NMRR_OR0(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, OR0) +#define GET_CB_NMRR_OR1(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, OR1) +#define GET_CB_NMRR_OR2(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, OR2) +#define GET_CB_NMRR_OR3(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, OR3) +#define GET_CB_NMRR_OR4(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, OR4) +#define GET_CB_NMRR_OR5(b, c) GET_CONTEXT_FIELD(b, c, CB_NMRR, OR5) + +/* Physical Address Register: CB_PAR */ +#define SET_CB_PAR_F(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, F, v) +#define SET_CB_PAR_SS(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, SS, v) +#define SET_CB_PAR_OUTER(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, OUTER, v) +#define SET_CB_PAR_INNER(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, INNER, v) +#define SET_CB_PAR_SH(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, SH, v) +#define SET_CB_PAR_NS(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, NS, v) +#define SET_CB_PAR_NOS(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, NOS, v) +#define SET_CB_PAR_PA(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, PA, v) +#define SET_CB_PAR_TF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, TF, v) +#define SET_CB_PAR_AFF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, AFF, v) +#define SET_CB_PAR_PF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, PF, v) +#define SET_CB_PAR_TLBMCF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, TLBMCF, v) +#define SET_CB_PAR_TLBLKF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, TLBLKF, v) +#define SET_CB_PAR_ATOT(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, ATOT, v) +#define SET_CB_PAR_PLVL(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, PLVL, v) +#define SET_CB_PAR_STAGE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PAR, STAGE, v) + +#define GET_CB_PAR_F(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, F) +#define GET_CB_PAR_SS(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, SS) +#define GET_CB_PAR_OUTER(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, OUTER) +#define GET_CB_PAR_INNER(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, INNER) +#define GET_CB_PAR_SH(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, SH) +#define GET_CB_PAR_NS(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, NS) +#define GET_CB_PAR_NOS(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, NOS) +#define GET_CB_PAR_PA(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, PA) +#define GET_CB_PAR_TF(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, TF) +#define GET_CB_PAR_AFF(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, AFF) +#define GET_CB_PAR_PF(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, PF) +#define GET_CB_PAR_TLBMCF(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, TLBMCF) +#define GET_CB_PAR_TLBLKF(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, TLBLKF) +#define GET_CB_PAR_ATOT(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, ATOT) +#define GET_CB_PAR_PLVL(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, PLVL) +#define GET_CB_PAR_STAGE(b, c) GET_CONTEXT_FIELD(b, c, CB_PAR, STAGE) + +/* Primary Region Remap Register: CB_PRRR */ +#define SET_CB_PRRR_TR0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR0, v) +#define SET_CB_PRRR_TR1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR1, v) +#define SET_CB_PRRR_TR2(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR2, v) +#define SET_CB_PRRR_TR3(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR3, v) +#define SET_CB_PRRR_TR4(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR4, v) +#define SET_CB_PRRR_TR5(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR5, v) +#define SET_CB_PRRR_TR6(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR6, v) +#define SET_CB_PRRR_TR7(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, TR7, v) +#define SET_CB_PRRR_DS0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, DS0, v) +#define SET_CB_PRRR_DS1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, DS1, v) +#define SET_CB_PRRR_NS0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NS0, v) +#define SET_CB_PRRR_NS1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NS1, v) +#define SET_CB_PRRR_NOS0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS0, v) +#define SET_CB_PRRR_NOS1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS1, v) +#define SET_CB_PRRR_NOS2(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS2, v) +#define SET_CB_PRRR_NOS3(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS3, v) +#define SET_CB_PRRR_NOS4(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS4, v) +#define SET_CB_PRRR_NOS5(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS5, v) +#define SET_CB_PRRR_NOS6(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS6, v) +#define SET_CB_PRRR_NOS7(b, c, v) SET_CONTEXT_FIELD(b, c, CB_PRRR, NOS7, v) + +#define GET_CB_PRRR_TR0(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR0) +#define GET_CB_PRRR_TR1(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR1) +#define GET_CB_PRRR_TR2(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR2) +#define GET_CB_PRRR_TR3(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR3) +#define GET_CB_PRRR_TR4(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR4) +#define GET_CB_PRRR_TR5(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR5) +#define GET_CB_PRRR_TR6(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR6) +#define GET_CB_PRRR_TR7(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, TR7) +#define GET_CB_PRRR_DS0(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, DS0) +#define GET_CB_PRRR_DS1(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, DS1) +#define GET_CB_PRRR_NS0(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NS0) +#define GET_CB_PRRR_NS1(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NS1) +#define GET_CB_PRRR_NOS0(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS0) +#define GET_CB_PRRR_NOS1(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS1) +#define GET_CB_PRRR_NOS2(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS2) +#define GET_CB_PRRR_NOS3(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS3) +#define GET_CB_PRRR_NOS4(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS4) +#define GET_CB_PRRR_NOS5(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS5) +#define GET_CB_PRRR_NOS6(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS6) +#define GET_CB_PRRR_NOS7(b, c) GET_CONTEXT_FIELD(b, c, CB_PRRR, NOS7) + +/* Transaction Resume: CB_RESUME */ +#define SET_CB_RESUME_TNR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_RESUME, TNR, v) + +#define GET_CB_RESUME_TNR(b, c) GET_CONTEXT_FIELD(b, c, CB_RESUME, TNR) + +/* System Control Register: CB_SCTLR */ +#define SET_CB_SCTLR_M(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, M, v) +#define SET_CB_SCTLR_TRE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, TRE, v) +#define SET_CB_SCTLR_AFE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, AFE, v) +#define SET_CB_SCTLR_AFFD(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, AFFD, v) +#define SET_CB_SCTLR_E(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, E, v) +#define SET_CB_SCTLR_CFRE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, CFRE, v) +#define SET_CB_SCTLR_CFIE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, CFIE, v) +#define SET_CB_SCTLR_CFCFG(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, CFCFG, v) +#define SET_CB_SCTLR_HUPCF(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, HUPCF, v) +#define SET_CB_SCTLR_WXN(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, WXN, v) +#define SET_CB_SCTLR_UWXN(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, UWXN, v) +#define SET_CB_SCTLR_ASIDPNE(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_SCTLR, ASIDPNE, v) +#define SET_CB_SCTLR_TRANSIENTCFG(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_SCTLR, TRANSIENTCFG, v) +#define SET_CB_SCTLR_MEMATTR(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_SCTLR, MEMATTR, v) +#define SET_CB_SCTLR_MTCFG(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, MTCFG, v) +#define SET_CB_SCTLR_SHCFG(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, SHCFG, v) +#define SET_CB_SCTLR_RACFG(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, RACFG, v) +#define SET_CB_SCTLR_WACFG(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, WACFG, v) +#define SET_CB_SCTLR_NSCFG(b, c, v) SET_CONTEXT_FIELD(b, c, CB_SCTLR, NSCFG, v) + +#define GET_CB_SCTLR_M(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, M) +#define GET_CB_SCTLR_TRE(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, TRE) +#define GET_CB_SCTLR_AFE(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, AFE) +#define GET_CB_SCTLR_AFFD(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, AFFD) +#define GET_CB_SCTLR_E(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, E) +#define GET_CB_SCTLR_CFRE(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, CFRE) +#define GET_CB_SCTLR_CFIE(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, CFIE) +#define GET_CB_SCTLR_CFCFG(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, CFCFG) +#define GET_CB_SCTLR_HUPCF(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, HUPCF) +#define GET_CB_SCTLR_WXN(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, WXN) +#define GET_CB_SCTLR_UWXN(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, UWXN) +#define GET_CB_SCTLR_ASIDPNE(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_SCTLR, ASIDPNE) +#define GET_CB_SCTLR_TRANSIENTCFG(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_SCTLR, TRANSIENTCFG) +#define GET_CB_SCTLR_MEMATTR(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_SCTLR, MEMATTR) +#define GET_CB_SCTLR_MTCFG(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, MTCFG) +#define GET_CB_SCTLR_SHCFG(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, SHCFG) +#define GET_CB_SCTLR_RACFG(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, RACFG) +#define GET_CB_SCTLR_WACFG(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, WACFG) +#define GET_CB_SCTLR_NSCFG(b, c) GET_CONTEXT_FIELD(b, c, CB_SCTLR, NSCFG) + +/* Invalidate TLB by ASID: CB_TLBIASID */ +#define SET_CB_TLBIASID_ASID(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_TLBIASID, ASID, v) + +#define GET_CB_TLBIASID_ASID(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_TLBIASID, ASID) + +/* Invalidate TLB by VA: CB_TLBIVA */ +#define SET_CB_TLBIVA_ASID(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TLBIVA, ASID, v) +#define SET_CB_TLBIVA_VA(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TLBIVA, VA, v) + +#define GET_CB_TLBIVA_ASID(b, c) GET_CONTEXT_FIELD(b, c, CB_TLBIVA, ASID) +#define GET_CB_TLBIVA_VA(b, c) GET_CONTEXT_FIELD(b, c, CB_TLBIVA, VA) + +/* Invalidate TLB by VA, All ASID: CB_TLBIVAA */ +#define SET_CB_TLBIVAA_VA(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TLBIVAA, VA, v) + +#define GET_CB_TLBIVAA_VA(b, c) GET_CONTEXT_FIELD(b, c, CB_TLBIVAA, VA) + +/* Invalidate TLB by VA, All ASID, Last Level: CB_TLBIVAAL */ +#define SET_CB_TLBIVAAL_VA(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TLBIVAAL, VA, v) + +#define GET_CB_TLBIVAAL_VA(b, c) GET_CONTEXT_FIELD(b, c, CB_TLBIVAAL, VA) + +/* Invalidate TLB by VA, Last Level: CB_TLBIVAL */ +#define SET_CB_TLBIVAL_ASID(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_TLBIVAL, ASID, v) +#define SET_CB_TLBIVAL_VA(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TLBIVAL, VA, v) + +#define GET_CB_TLBIVAL_ASID(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_TLBIVAL, ASID) +#define GET_CB_TLBIVAL_VA(b, c) GET_CONTEXT_FIELD(b, c, CB_TLBIVAL, VA) + +/* TLB Status: CB_TLBSTATUS */ +#define SET_CB_TLBSTATUS_SACTIVE(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_TLBSTATUS, SACTIVE, v) + +#define GET_CB_TLBSTATUS_SACTIVE(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_TLBSTATUS, SACTIVE) + +/* Translation Table Base Control Register: CB_TTBCR */ +#define SET_CB_TTBCR_T0SZ(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBCR, T0SZ, v) +#define SET_CB_TTBCR_PD0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBCR, PD0, v) +#define SET_CB_TTBCR_PD1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBCR, PD1, v) +#define SET_CB_TTBCR_NSCFG0(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_TTBCR, NSCFG0, v) +#define SET_CB_TTBCR_NSCFG1(b, c, v) \ + SET_CONTEXT_FIELD(b, c, CB_TTBCR, NSCFG1, v) +#define SET_CB_TTBCR_EAE(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBCR, EAE, v) + +#define GET_CB_TTBCR_T0SZ(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBCR, T0SZ) +#define GET_CB_TTBCR_PD0(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBCR, PD0) +#define GET_CB_TTBCR_PD1(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBCR, PD1) +#define GET_CB_TTBCR_NSCFG0(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_TTBCR, NSCFG0) +#define GET_CB_TTBCR_NSCFG1(b, c) \ + GET_CONTEXT_FIELD(b, c, CB_TTBCR, NSCFG1) +#define GET_CB_TTBCR_EAE(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBCR, EAE) + +/* Translation Table Base Register 0: CB_TTBR */ +#define SET_CB_TTBR0_IRGN1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR0, IRGN1, v) +#define SET_CB_TTBR0_S(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR0, S, v) +#define SET_CB_TTBR0_RGN(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR0, RGN, v) +#define SET_CB_TTBR0_NOS(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR0, NOS, v) +#define SET_CB_TTBR0_IRGN0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR0, IRGN0, v) +#define SET_CB_TTBR0_ADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR0, ADDR, v) + +#define GET_CB_TTBR0_IRGN1(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR0, IRGN1) +#define GET_CB_TTBR0_S(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR0, S) +#define GET_CB_TTBR0_RGN(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR0, RGN) +#define GET_CB_TTBR0_NOS(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR0, NOS) +#define GET_CB_TTBR0_IRGN0(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR0, IRGN0) +#define GET_CB_TTBR0_ADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR0, ADDR) + +/* Translation Table Base Register 1: CB_TTBR1 */ +#define SET_CB_TTBR1_IRGN1(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR1, IRGN1, v) +#define SET_CB_TTBR1_0S(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR1, S, v) +#define SET_CB_TTBR1_RGN(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR1, RGN, v) +#define SET_CB_TTBR1_NOS(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR1, NOS, v) +#define SET_CB_TTBR1_IRGN0(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR1, IRGN0, v) +#define SET_CB_TTBR1_ADDR(b, c, v) SET_CONTEXT_FIELD(b, c, CB_TTBR1, ADDR, v) + +#define GET_CB_TTBR1_IRGN1(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR1, IRGN1) +#define GET_CB_TTBR1_0S(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR1, S) +#define GET_CB_TTBR1_RGN(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR1, RGN) +#define GET_CB_TTBR1_NOS(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR1, NOS) +#define GET_CB_TTBR1_IRGN0(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR1, IRGN0) +#define GET_CB_TTBR1_ADDR(b, c) GET_CONTEXT_FIELD(b, c, CB_TTBR1, ADDR) + +/* Global Register Space 0 */ +#define CR0 (0x0000) +#define SCR1 (0x0004) +#define CR2 (0x0008) +#define ACR (0x0010) +#define IDR0 (0x0020) +#define IDR1 (0x0024) +#define IDR2 (0x0028) +#define IDR7 (0x003C) +#define GFAR (0x0040) +#define GFSR (0x0044) +#define GFSRRESTORE (0x004C) +#define GFSYNR0 (0x0050) +#define GFSYNR1 (0x0054) +#define GFSYNR2 (0x0058) +#define TLBIVMID (0x0064) +#define TLBIALLNSNH (0x0068) +#define TLBIALLH (0x006C) +#define TLBGSYNC (0x0070) +#define TLBGSTATUS (0x0074) +#define TLBIVAH (0x0078) +#define GATS1UR (0x0100) +#define GATS1UW (0x0108) +#define GATS1PR (0x0110) +#define GATS1PW (0x0118) +#define GATS12UR (0x0120) +#define GATS12UW (0x0128) +#define GATS12PR (0x0130) +#define GATS12PW (0x0138) +#define GPAR (0x0180) +#define GATSR (0x0188) +#define NSCR0 (0x0400) +#define NSCR2 (0x0408) +#define NSACR (0x0410) +#define SMR (0x0800) +#define S2CR (0x0C00) + +/* Global Register Space 1 */ +#define CBAR (0x1000) +#define CBFRSYNRA (0x1400) + +/* Implementation defined Register Space */ +#define PREDICTIONDIS0 (0x204C) +#define PREDICTIONDIS1 (0x2050) +#define S1L1BFBLP0 (0x215C) + +/* Performance Monitoring Register Space */ +#define PMEVCNTR_N (0x3000) +#define PMEVTYPER_N (0x3400) +#define PMCGCR_N (0x3800) +#define PMCGSMR_N (0x3A00) +#define PMCNTENSET_N (0x3C00) +#define PMCNTENCLR_N (0x3C20) +#define PMINTENSET_N (0x3C40) +#define PMINTENCLR_N (0x3C60) +#define PMOVSCLR_N (0x3C80) +#define PMOVSSET_N (0x3CC0) +#define PMCFGR (0x3E00) +#define PMCR (0x3E04) +#define PMCEID0 (0x3E20) +#define PMCEID1 (0x3E24) +#define PMAUTHSTATUS (0x3FB8) +#define PMDEVTYPE (0x3FCC) + +/* Secure Status Determination Address Space */ +#define SSDR_N (0x4000) + +/* Stage 1 Context Bank Format */ +#define CB_SCTLR (0x000) +#define CB_ACTLR (0x004) +#define CB_RESUME (0x008) +#define CB_TTBR0 (0x020) +#define CB_TTBR1 (0x028) +#define CB_TTBCR (0x030) +#define CB_CONTEXTIDR (0x034) +#define CB_PRRR (0x038) +#define CB_NMRR (0x03C) +#define CB_PAR (0x050) +#define CB_FSR (0x058) +#define CB_FSRRESTORE (0x05C) +#define CB_FAR (0x060) +#define CB_FSYNR0 (0x068) +#define CB_FSYNR1 (0x06C) +#define CB_TLBIVA (0x600) +#define CB_TLBIVAA (0x608) +#define CB_TLBIASID (0x610) +#define CB_TLBIALL (0x618) +#define CB_TLBIVAL (0x620) +#define CB_TLBIVAAL (0x628) +#define CB_TLBSYNC (0x7F0) +#define CB_TLBSTATUS (0x7F4) +#define CB_ATS1PR (0x800) +#define CB_ATS1PW (0x808) +#define CB_ATS1UR (0x810) +#define CB_ATS1UW (0x818) +#define CB_ATSR (0x8F0) +#define CB_PMXEVCNTR_N (0xE00) +#define CB_PMXEVTYPER_N (0xE80) +#define CB_PMCFGR (0xF00) +#define CB_PMCR (0xF04) +#define CB_PMCEID0 (0xF20) +#define CB_PMCEID1 (0xF24) +#define CB_PMCNTENSET (0xF40) +#define CB_PMCNTENCLR (0xF44) +#define CB_PMINTENSET (0xF48) +#define CB_PMINTENCLR (0xF4C) +#define CB_PMOVSCLR (0xF50) +#define CB_PMOVSSET (0xF58) +#define CB_PMAUTHSTATUS (0xFB8) + +/* Global Register Fields */ +/* Configuration Register: CR0 */ +#define CR0_NSCFG (CR0_NSCFG_MASK << CR0_NSCFG_SHIFT) +#define CR0_WACFG (CR0_WACFG_MASK << CR0_WACFG_SHIFT) +#define CR0_RACFG (CR0_RACFG_MASK << CR0_RACFG_SHIFT) +#define CR0_SHCFG (CR0_SHCFG_MASK << CR0_SHCFG_SHIFT) +#define CR0_SMCFCFG (CR0_SMCFCFG_MASK << CR0_SMCFCFG_SHIFT) +#define CR0_MTCFG (CR0_MTCFG_MASK << CR0_MTCFG_SHIFT) +#define CR0_MEMATTR (CR0_MEMATTR_MASK << CR0_MEMATTR_SHIFT) +#define CR0_BSU (CR0_BSU_MASK << CR0_BSU_SHIFT) +#define CR0_FB (CR0_FB_MASK << CR0_FB_SHIFT) +#define CR0_PTM (CR0_PTM_MASK << CR0_PTM_SHIFT) +#define CR0_VMIDPNE (CR0_VMIDPNE_MASK << CR0_VMIDPNE_SHIFT) +#define CR0_USFCFG (CR0_USFCFG_MASK << CR0_USFCFG_SHIFT) +#define CR0_GSE (CR0_GSE_MASK << CR0_GSE_SHIFT) +#define CR0_STALLD (CR0_STALLD_MASK << CR0_STALLD_SHIFT) +#define CR0_TRANSIENTCFG (CR0_TRANSIENTCFG_MASK << CR0_TRANSIENTCFG_SHIFT) +#define CR0_GCFGFIE (CR0_GCFGFIE_MASK << CR0_GCFGFIE_SHIFT) +#define CR0_GCFGFRE (CR0_GCFGFRE_MASK << CR0_GCFGFRE_SHIFT) +#define CR0_GFIE (CR0_GFIE_MASK << CR0_GFIE_SHIFT) +#define CR0_GFRE (CR0_GFRE_MASK << CR0_GFRE_SHIFT) +#define CR0_CLIENTPD (CR0_CLIENTPD_MASK << CR0_CLIENTPD_SHIFT) + +/* Configuration Register: CR2 */ +#define CR2_BPVMID (CR2_BPVMID_MASK << CR2_BPVMID_SHIFT) + +/* Global Address Translation, Stage 1, Privileged Read: GATS1PR */ +#define GATS1PR_ADDR (GATS1PR_ADDR_MASK << GATS1PR_ADDR_SHIFT) +#define GATS1PR_NDX (GATS1PR_NDX_MASK << GATS1PR_NDX_SHIFT) + +/* Global Address Translation, Stage 1, Privileged Write: GATS1PW */ +#define GATS1PW_ADDR (GATS1PW_ADDR_MASK << GATS1PW_ADDR_SHIFT) +#define GATS1PW_NDX (GATS1PW_NDX_MASK << GATS1PW_NDX_SHIFT) + +/* Global Address Translation, Stage 1, User Read: GATS1UR */ +#define GATS1UR_ADDR (GATS1UR_ADDR_MASK << GATS1UR_ADDR_SHIFT) +#define GATS1UR_NDX (GATS1UR_NDX_MASK << GATS1UR_NDX_SHIFT) + +/* Global Address Translation, Stage 1, User Write: GATS1UW */ +#define GATS1UW_ADDR (GATS1UW_ADDR_MASK << GATS1UW_ADDR_SHIFT) +#define GATS1UW_NDX (GATS1UW_NDX_MASK << GATS1UW_NDX_SHIFT) + +/* Global Address Translation, Stage 1 and 2, Privileged Read: GATS1PR */ +#define GATS12PR_ADDR (GATS12PR_ADDR_MASK << GATS12PR_ADDR_SHIFT) +#define GATS12PR_NDX (GATS12PR_NDX_MASK << GATS12PR_NDX_SHIFT) + +/* Global Address Translation, Stage 1 and 2, Privileged Write: GATS1PW */ +#define GATS12PW_ADDR (GATS12PW_ADDR_MASK << GATS12PW_ADDR_SHIFT) +#define GATS12PW_NDX (GATS12PW_NDX_MASK << GATS12PW_NDX_SHIFT) + +/* Global Address Translation, Stage 1 and 2, User Read: GATS1UR */ +#define GATS12UR_ADDR (GATS12UR_ADDR_MASK << GATS12UR_ADDR_SHIFT) +#define GATS12UR_NDX (GATS12UR_NDX_MASK << GATS12UR_NDX_SHIFT) + +/* Global Address Translation, Stage 1 and 2, User Write: GATS1UW */ +#define GATS12UW_ADDR (GATS12UW_ADDR_MASK << GATS12UW_ADDR_SHIFT) +#define GATS12UW_NDX (GATS12UW_NDX_MASK << GATS12UW_NDX_SHIFT) + +/* Global Address Translation Status Register: GATSR */ +#define GATSR_ACTIVE (GATSR_ACTIVE_MASK << GATSR_ACTIVE_SHIFT) + +/* Global Fault Address Register: GFAR */ +#define GFAR_FADDR (GFAR_FADDR_MASK << GFAR_FADDR_SHIFT) + +/* Global Fault Status Register: GFSR */ +#define GFSR_ICF (GFSR_ICF_MASK << GFSR_ICF_SHIFT) +#define GFSR_USF (GFSR_USF_MASK << GFSR_USF_SHIFT) +#define GFSR_SMCF (GFSR_SMCF_MASK << GFSR_SMCF_SHIFT) +#define GFSR_UCBF (GFSR_UCBF_MASK << GFSR_UCBF_SHIFT) +#define GFSR_UCIF (GFSR_UCIF_MASK << GFSR_UCIF_SHIFT) +#define GFSR_CAF (GFSR_CAF_MASK << GFSR_CAF_SHIFT) +#define GFSR_EF (GFSR_EF_MASK << GFSR_EF_SHIFT) +#define GFSR_PF (GFSR_PF_MASK << GFSR_PF_SHIFT) +#define GFSR_MULTI (GFSR_MULTI_MASK << GFSR_MULTI_SHIFT) + +/* Global Fault Syndrome Register 0: GFSYNR0 */ +#define GFSYNR0_NESTED (GFSYNR0_NESTED_MASK << GFSYNR0_NESTED_SHIFT) +#define GFSYNR0_WNR (GFSYNR0_WNR_MASK << GFSYNR0_WNR_SHIFT) +#define GFSYNR0_PNU (GFSYNR0_PNU_MASK << GFSYNR0_PNU_SHIFT) +#define GFSYNR0_IND (GFSYNR0_IND_MASK << GFSYNR0_IND_SHIFT) +#define GFSYNR0_NSSTATE (GFSYNR0_NSSTATE_MASK << GFSYNR0_NSSTATE_SHIFT) +#define GFSYNR0_NSATTR (GFSYNR0_NSATTR_MASK << GFSYNR0_NSATTR_SHIFT) + +/* Global Fault Syndrome Register 1: GFSYNR1 */ +#define GFSYNR1_SID (GFSYNR1_SID_MASK << GFSYNR1_SID_SHIFT) + +/* Global Physical Address Register: GPAR */ +#define GPAR_F (GPAR_F_MASK << GPAR_F_SHIFT) +#define GPAR_SS (GPAR_SS_MASK << GPAR_SS_SHIFT) +#define GPAR_OUTER (GPAR_OUTER_MASK << GPAR_OUTER_SHIFT) +#define GPAR_INNER (GPAR_INNER_MASK << GPAR_INNER_SHIFT) +#define GPAR_SH (GPAR_SH_MASK << GPAR_SH_SHIFT) +#define GPAR_NS (GPAR_NS_MASK << GPAR_NS_SHIFT) +#define GPAR_NOS (GPAR_NOS_MASK << GPAR_NOS_SHIFT) +#define GPAR_PA (GPAR_PA_MASK << GPAR_PA_SHIFT) +#define GPAR_TF (GPAR_TF_MASK << GPAR_TF_SHIFT) +#define GPAR_AFF (GPAR_AFF_MASK << GPAR_AFF_SHIFT) +#define GPAR_PF (GPAR_PF_MASK << GPAR_PF_SHIFT) +#define GPAR_EF (GPAR_EF_MASK << GPAR_EF_SHIFT) +#define GPAR_TLCMCF (GPAR_TLBMCF_MASK << GPAR_TLCMCF_SHIFT) +#define GPAR_TLBLKF (GPAR_TLBLKF_MASK << GPAR_TLBLKF_SHIFT) +#define GPAR_UCBF (GPAR_UCBF_MASK << GFAR_UCBF_SHIFT) + +/* Identification Register: IDR0 */ +#define IDR0_NUMSMRG (IDR0_NUMSMRG_MASK << IDR0_NUMSMGR_SHIFT) +#define IDR0_NUMSIDB (IDR0_NUMSIDB_MASK << IDR0_NUMSIDB_SHIFT) +#define IDR0_BTM (IDR0_BTM_MASK << IDR0_BTM_SHIFT) +#define IDR0_CTTW (IDR0_CTTW_MASK << IDR0_CTTW_SHIFT) +#define IDR0_NUMIRPT (IDR0_NUMIPRT_MASK << IDR0_NUMIRPT_SHIFT) +#define IDR0_PTFS (IDR0_PTFS_MASK << IDR0_PTFS_SHIFT) +#define IDR0_SMS (IDR0_SMS_MASK << IDR0_SMS_SHIFT) +#define IDR0_NTS (IDR0_NTS_MASK << IDR0_NTS_SHIFT) +#define IDR0_S2TS (IDR0_S2TS_MASK << IDR0_S2TS_SHIFT) +#define IDR0_S1TS (IDR0_S1TS_MASK << IDR0_S1TS_SHIFT) +#define IDR0_SES (IDR0_SES_MASK << IDR0_SES_SHIFT) + +/* Identification Register: IDR1 */ +#define IDR1_NUMCB (IDR1_NUMCB_MASK << IDR1_NUMCB_SHIFT) +#define IDR1_NUMSSDNDXB (IDR1_NUMSSDNDXB_MASK << IDR1_NUMSSDNDXB_SHIFT) +#define IDR1_SSDTP (IDR1_SSDTP_MASK << IDR1_SSDTP_SHIFT) +#define IDR1_SMCD (IDR1_SMCD_MASK << IDR1_SMCD_SHIFT) +#define IDR1_NUMS2CB (IDR1_NUMS2CB_MASK << IDR1_NUMS2CB_SHIFT) +#define IDR1_NUMPAGENDXB (IDR1_NUMPAGENDXB_MASK << IDR1_NUMPAGENDXB_SHIFT) +#define IDR1_PAGESIZE (IDR1_PAGESIZE_MASK << IDR1_PAGESIZE_SHIFT) + +/* Identification Register: IDR2 */ +#define IDR2_IAS (IDR2_IAS_MASK << IDR2_IAS_SHIFT) +#define IDR1_OAS (IDR2_OAS_MASK << IDR2_OAS_SHIFT) + +/* Identification Register: IDR7 */ +#define IDR7_MINOR (IDR7_MINOR_MASK << IDR7_MINOR_SHIFT) +#define IDR7_MAJOR (IDR7_MAJOR_MASK << IDR7_MAJOR_SHIFT) + +/* Stream to Context Register: S2CR */ +#define S2CR_CBNDX (S2CR_CBNDX_MASK << S2cR_CBNDX_SHIFT) +#define S2CR_SHCFG (S2CR_SHCFG_MASK << s2CR_SHCFG_SHIFT) +#define S2CR_MTCFG (S2CR_MTCFG_MASK << S2CR_MTCFG_SHIFT) +#define S2CR_MEMATTR (S2CR_MEMATTR_MASK << S2CR_MEMATTR_SHIFT) +#define S2CR_TYPE (S2CR_TYPE_MASK << S2CR_TYPE_SHIFT) +#define S2CR_NSCFG (S2CR_NSCFG_MASK << S2CR_NSCFG_SHIFT) +#define S2CR_RACFG (S2CR_RACFG_MASK << S2CR_RACFG_SHIFT) +#define S2CR_WACFG (S2CR_WACFG_MASK << S2CR_WACFG_SHIFT) +#define S2CR_PRIVCFG (S2CR_PRIVCFG_MASK << S2CR_PRIVCFG_SHIFT) +#define S2CR_INSTCFG (S2CR_INSTCFG_MASK << S2CR_INSTCFG_SHIFT) +#define S2CR_TRANSIENTCFG (S2CR_TRANSIENTCFG_MASK << S2CR_TRANSIENTCFG_SHIFT) +#define S2CR_VMID (S2CR_VMID_MASK << S2CR_VMID_SHIFT) +#define S2CR_BSU (S2CR_BSU_MASK << S2CR_BSU_SHIFT) +#define S2CR_FB (S2CR_FB_MASK << S2CR_FB_SHIFT) + +/* Stream Match Register: SMR */ +#define SMR_ID (SMR_ID_MASK << SMR_ID_SHIFT) +#define SMR_MASK (SMR_MASK_MASK << SMR_MASK_SHIFT) +#define SMR_VALID (SMR_VALID_MASK << SMR_VALID_SHIFT) + +/* Global TLB Status: TLBGSTATUS */ +#define TLBGSTATUS_GSACTIVE (TLBGSTATUS_GSACTIVE_MASK << \ + TLBGSTATUS_GSACTIVE_SHIFT) +/* Invalidate Hyp TLB by VA: TLBIVAH */ +#define TLBIVAH_ADDR (TLBIVAH_ADDR_MASK << TLBIVAH_ADDR_SHIFT) + +/* Invalidate TLB by VMID: TLBIVMID */ +#define TLBIVMID_VMID (TLBIVMID_VMID_MASK << TLBIVMID_VMID_SHIFT) + +/* Context Bank Attribute Register: CBAR */ +#define CBAR_VMID (CBAR_VMID_MASK << CBAR_VMID_SHIFT) +#define CBAR_CBNDX (CBAR_CBNDX_MASK << CBAR_CBNDX_SHIFT) +#define CBAR_BPSHCFG (CBAR_BPSHCFG_MASK << CBAR_BPSHCFG_SHIFT) +#define CBAR_HYPC (CBAR_HYPC_MASK << CBAR_HYPC_SHIFT) +#define CBAR_FB (CBAR_FB_MASK << CBAR_FB_SHIFT) +#define CBAR_MEMATTR (CBAR_MEMATTR_MASK << CBAR_MEMATTR_SHIFT) +#define CBAR_TYPE (CBAR_TYPE_MASK << CBAR_TYPE_SHIFT) +#define CBAR_BSU (CBAR_BSU_MASK << CBAR_BSU_SHIFT) +#define CBAR_RACFG (CBAR_RACFG_MASK << CBAR_RACFG_SHIFT) +#define CBAR_WACFG (CBAR_WACFG_MASK << CBAR_WACFG_SHIFT) +#define CBAR_IRPTNDX (CBAR_IRPTNDX_MASK << CBAR_IRPTNDX_SHIFT) + +/* Context Bank Fault Restricted Syndrome Register A: CBFRSYNRA */ +#define CBFRSYNRA_SID (CBFRSYNRA_SID_MASK << CBFRSYNRA_SID_SHIFT) + +/* Performance Monitoring Register Fields */ + +/* Stage 1 Context Bank Format Fields */ +/* Auxiliary Control Register: CB_ACTLR */ +#define CB_ACTLR_REQPRIORITY \ + (CB_ACTLR_REQPRIORITY_MASK << CB_ACTLR_REQPRIORITY_SHIFT) +#define CB_ACTLR_REQPRIORITYCFG \ + (CB_ACTLR_REQPRIORITYCFG_MASK << CB_ACTLR_REQPRIORITYCFG_SHIFT) +#define CB_ACTLR_PRIVCFG (CB_ACTLR_PRIVCFG_MASK << CB_ACTLR_PRIVCFG_SHIFT) +#define CB_ACTLR_BPRCOSH (CB_ACTLR_BPRCOSH_MASK << CB_ACTLR_BPRCOSH_SHIFT) +#define CB_ACTLR_BPRCISH (CB_ACTLR_BPRCISH_MASK << CB_ACTLR_BPRCISH_SHIFT) +#define CB_ACTLR_BPRCNSH (CB_ACTLR_BPRCNSH_MASK << CB_ACTLR_BPRCNSH_SHIFT) + +/* Address Translation, Stage 1, Privileged Read: CB_ATS1PR */ +#define CB_ATS1PR_ADDR (CB_ATS1PR_ADDR_MASK << CB_ATS1PR_ADDR_SHIFT) + +/* Address Translation, Stage 1, Privileged Write: CB_ATS1PW */ +#define CB_ATS1PW_ADDR (CB_ATS1PW_ADDR_MASK << CB_ATS1PW_ADDR_SHIFT) + +/* Address Translation, Stage 1, User Read: CB_ATS1UR */ +#define CB_ATS1UR_ADDR (CB_ATS1UR_ADDR_MASK << CB_ATS1UR_ADDR_SHIFT) + +/* Address Translation, Stage 1, User Write: CB_ATS1UW */ +#define CB_ATS1UW_ADDR (CB_ATS1UW_ADDR_MASK << CB_ATS1UW_ADDR_SHIFT) + +/* Address Translation Status Register: CB_ATSR */ +#define CB_ATSR_ACTIVE (CB_ATSR_ACTIVE_MASK << CB_ATSR_ACTIVE_SHIFT) + +/* Context ID Register: CB_CONTEXTIDR */ +#define CB_CONTEXTIDR_ASID (CB_CONTEXTIDR_ASID_MASK << \ + CB_CONTEXTIDR_ASID_SHIFT) +#define CB_CONTEXTIDR_PROCID (CB_CONTEXTIDR_PROCID_MASK << \ + CB_CONTEXTIDR_PROCID_SHIFT) + +/* Fault Address Register: CB_FAR */ +#define CB_FAR_FADDR (CB_FAR_FADDR_MASK << CB_FAR_FADDR_SHIFT) + +/* Fault Status Register: CB_FSR */ +#define CB_FSR_TF (CB_FSR_TF_MASK << CB_FSR_TF_SHIFT) +#define CB_FSR_AFF (CB_FSR_AFF_MASK << CB_FSR_AFF_SHIFT) +#define CB_FSR_PF (CB_FSR_PF_MASK << CB_FSR_PF_SHIFT) +#define CB_FSR_EF (CB_FSR_EF_MASK << CB_FSR_EF_SHIFT) +#define CB_FSR_TLBMCF (CB_FSR_TLBMCF_MASK << CB_FSR_TLBMCF_SHIFT) +#define CB_FSR_TLBLKF (CB_FSR_TLBLKF_MASK << CB_FSR_TLBLKF_SHIFT) +#define CB_FSR_SS (CB_FSR_SS_MASK << CB_FSR_SS_SHIFT) +#define CB_FSR_MULTI (CB_FSR_MULTI_MASK << CB_FSR_MULTI_SHIFT) + +/* Fault Syndrome Register 0: CB_FSYNR0 */ +#define CB_FSYNR0_PLVL (CB_FSYNR0_PLVL_MASK << CB_FSYNR0_PLVL_SHIFT) +#define CB_FSYNR0_S1PTWF (CB_FSYNR0_S1PTWF_MASK << CB_FSYNR0_S1PTWF_SHIFT) +#define CB_FSYNR0_WNR (CB_FSYNR0_WNR_MASK << CB_FSYNR0_WNR_SHIFT) +#define CB_FSYNR0_PNU (CB_FSYNR0_PNU_MASK << CB_FSYNR0_PNU_SHIFT) +#define CB_FSYNR0_IND (CB_FSYNR0_IND_MASK << CB_FSYNR0_IND_SHIFT) +#define CB_FSYNR0_NSSTATE (CB_FSYNR0_NSSTATE_MASK << CB_FSYNR0_NSSTATE_SHIFT) +#define CB_FSYNR0_NSATTR (CB_FSYNR0_NSATTR_MASK << CB_FSYNR0_NSATTR_SHIFT) +#define CB_FSYNR0_ATOF (CB_FSYNR0_ATOF_MASK << CB_FSYNR0_ATOF_SHIFT) +#define CB_FSYNR0_PTWF (CB_FSYNR0_PTWF_MASK << CB_FSYNR0_PTWF_SHIFT) +#define CB_FSYNR0_AFR (CB_FSYNR0_AFR_MASK << CB_FSYNR0_AFR_SHIFT) +#define CB_FSYNR0_S1CBNDX (CB_FSYNR0_S1CBNDX_MASK << CB_FSYNR0_S1CBNDX_SHIFT) + +/* Normal Memory Remap Register: CB_NMRR */ +#define CB_NMRR_IR0 (CB_NMRR_IR0_MASK << CB_NMRR_IR0_SHIFT) +#define CB_NMRR_IR1 (CB_NMRR_IR1_MASK << CB_NMRR_IR1_SHIFT) +#define CB_NMRR_IR2 (CB_NMRR_IR2_MASK << CB_NMRR_IR2_SHIFT) +#define CB_NMRR_IR3 (CB_NMRR_IR3_MASK << CB_NMRR_IR3_SHIFT) +#define CB_NMRR_IR4 (CB_NMRR_IR4_MASK << CB_NMRR_IR4_SHIFT) +#define CB_NMRR_IR5 (CB_NMRR_IR5_MASK << CB_NMRR_IR5_SHIFT) +#define CB_NMRR_IR6 (CB_NMRR_IR6_MASK << CB_NMRR_IR6_SHIFT) +#define CB_NMRR_IR7 (CB_NMRR_IR7_MASK << CB_NMRR_IR7_SHIFT) +#define CB_NMRR_OR0 (CB_NMRR_OR0_MASK << CB_NMRR_OR0_SHIFT) +#define CB_NMRR_OR1 (CB_NMRR_OR1_MASK << CB_NMRR_OR1_SHIFT) +#define CB_NMRR_OR2 (CB_NMRR_OR2_MASK << CB_NMRR_OR2_SHIFT) +#define CB_NMRR_OR3 (CB_NMRR_OR3_MASK << CB_NMRR_OR3_SHIFT) +#define CB_NMRR_OR4 (CB_NMRR_OR4_MASK << CB_NMRR_OR4_SHIFT) +#define CB_NMRR_OR5 (CB_NMRR_OR5_MASK << CB_NMRR_OR5_SHIFT) +#define CB_NMRR_OR6 (CB_NMRR_OR6_MASK << CB_NMRR_OR6_SHIFT) +#define CB_NMRR_OR7 (CB_NMRR_OR7_MASK << CB_NMRR_OR7_SHIFT) + +/* Physical Address Register: CB_PAR */ +#define CB_PAR_F (CB_PAR_F_MASK << CB_PAR_F_SHIFT) +#define CB_PAR_SS (CB_PAR_SS_MASK << CB_PAR_SS_SHIFT) +#define CB_PAR_OUTER (CB_PAR_OUTER_MASK << CB_PAR_OUTER_SHIFT) +#define CB_PAR_INNER (CB_PAR_INNER_MASK << CB_PAR_INNER_SHIFT) +#define CB_PAR_SH (CB_PAR_SH_MASK << CB_PAR_SH_SHIFT) +#define CB_PAR_NS (CB_PAR_NS_MASK << CB_PAR_NS_SHIFT) +#define CB_PAR_NOS (CB_PAR_NOS_MASK << CB_PAR_NOS_SHIFT) +#define CB_PAR_PA (CB_PAR_PA_MASK << CB_PAR_PA_SHIFT) +#define CB_PAR_TF (CB_PAR_TF_MASK << CB_PAR_TF_SHIFT) +#define CB_PAR_AFF (CB_PAR_AFF_MASK << CB_PAR_AFF_SHIFT) +#define CB_PAR_PF (CB_PAR_PF_MASK << CB_PAR_PF_SHIFT) +#define CB_PAR_TLBMCF (CB_PAR_TLBMCF_MASK << CB_PAR_TLBMCF_SHIFT) +#define CB_PAR_TLBLKF (CB_PAR_TLBLKF_MASK << CB_PAR_TLBLKF_SHIFT) +#define CB_PAR_ATOT (CB_PAR_ATOT_MASK << CB_PAR_ATOT_SHIFT) +#define CB_PAR_PLVL (CB_PAR_PLVL_MASK << CB_PAR_PLVL_SHIFT) +#define CB_PAR_STAGE (CB_PAR_STAGE_MASK << CB_PAR_STAGE_SHIFT) + +/* Primary Region Remap Register: CB_PRRR */ +#define CB_PRRR_TR0 (CB_PRRR_TR0_MASK << CB_PRRR_TR0_SHIFT) +#define CB_PRRR_TR1 (CB_PRRR_TR1_MASK << CB_PRRR_TR1_SHIFT) +#define CB_PRRR_TR2 (CB_PRRR_TR2_MASK << CB_PRRR_TR2_SHIFT) +#define CB_PRRR_TR3 (CB_PRRR_TR3_MASK << CB_PRRR_TR3_SHIFT) +#define CB_PRRR_TR4 (CB_PRRR_TR4_MASK << CB_PRRR_TR4_SHIFT) +#define CB_PRRR_TR5 (CB_PRRR_TR5_MASK << CB_PRRR_TR5_SHIFT) +#define CB_PRRR_TR6 (CB_PRRR_TR6_MASK << CB_PRRR_TR6_SHIFT) +#define CB_PRRR_TR7 (CB_PRRR_TR7_MASK << CB_PRRR_TR7_SHIFT) +#define CB_PRRR_DS0 (CB_PRRR_DS0_MASK << CB_PRRR_DS0_SHIFT) +#define CB_PRRR_DS1 (CB_PRRR_DS1_MASK << CB_PRRR_DS1_SHIFT) +#define CB_PRRR_NS0 (CB_PRRR_NS0_MASK << CB_PRRR_NS0_SHIFT) +#define CB_PRRR_NS1 (CB_PRRR_NS1_MASK << CB_PRRR_NS1_SHIFT) +#define CB_PRRR_NOS0 (CB_PRRR_NOS0_MASK << CB_PRRR_NOS0_SHIFT) +#define CB_PRRR_NOS1 (CB_PRRR_NOS1_MASK << CB_PRRR_NOS1_SHIFT) +#define CB_PRRR_NOS2 (CB_PRRR_NOS2_MASK << CB_PRRR_NOS2_SHIFT) +#define CB_PRRR_NOS3 (CB_PRRR_NOS3_MASK << CB_PRRR_NOS3_SHIFT) +#define CB_PRRR_NOS4 (CB_PRRR_NOS4_MASK << CB_PRRR_NOS4_SHIFT) +#define CB_PRRR_NOS5 (CB_PRRR_NOS5_MASK << CB_PRRR_NOS5_SHIFT) +#define CB_PRRR_NOS6 (CB_PRRR_NOS6_MASK << CB_PRRR_NOS6_SHIFT) +#define CB_PRRR_NOS7 (CB_PRRR_NOS7_MASK << CB_PRRR_NOS7_SHIFT) + +/* Transaction Resume: CB_RESUME */ +#define CB_RESUME_TNR (CB_RESUME_TNR_MASK << CB_RESUME_TNR_SHIFT) + +/* System Control Register: CB_SCTLR */ +#define CB_SCTLR_M (CB_SCTLR_M_MASK << CB_SCTLR_M_SHIFT) +#define CB_SCTLR_TRE (CB_SCTLR_TRE_MASK << CB_SCTLR_TRE_SHIFT) +#define CB_SCTLR_AFE (CB_SCTLR_AFE_MASK << CB_SCTLR_AFE_SHIFT) +#define CB_SCTLR_AFFD (CB_SCTLR_AFFD_MASK << CB_SCTLR_AFFD_SHIFT) +#define CB_SCTLR_E (CB_SCTLR_E_MASK << CB_SCTLR_E_SHIFT) +#define CB_SCTLR_CFRE (CB_SCTLR_CFRE_MASK << CB_SCTLR_CFRE_SHIFT) +#define CB_SCTLR_CFIE (CB_SCTLR_CFIE_MASK << CB_SCTLR_CFIE_SHIFT) +#define CB_SCTLR_CFCFG (CB_SCTLR_CFCFG_MASK << CB_SCTLR_CFCFG_SHIFT) +#define CB_SCTLR_HUPCF (CB_SCTLR_HUPCF_MASK << CB_SCTLR_HUPCF_SHIFT) +#define CB_SCTLR_WXN (CB_SCTLR_WXN_MASK << CB_SCTLR_WXN_SHIFT) +#define CB_SCTLR_UWXN (CB_SCTLR_UWXN_MASK << CB_SCTLR_UWXN_SHIFT) +#define CB_SCTLR_ASIDPNE (CB_SCTLR_ASIDPNE_MASK << CB_SCTLR_ASIDPNE_SHIFT) +#define CB_SCTLR_TRANSIENTCFG (CB_SCTLR_TRANSIENTCFG_MASK << \ + CB_SCTLR_TRANSIENTCFG_SHIFT) +#define CB_SCTLR_MEMATTR (CB_SCTLR_MEMATTR_MASK << CB_SCTLR_MEMATTR_SHIFT) +#define CB_SCTLR_MTCFG (CB_SCTLR_MTCFG_MASK << CB_SCTLR_MTCFG_SHIFT) +#define CB_SCTLR_SHCFG (CB_SCTLR_SHCFG_MASK << CB_SCTLR_SHCFG_SHIFT) +#define CB_SCTLR_RACFG (CB_SCTLR_RACFG_MASK << CB_SCTLR_RACFG_SHIFT) +#define CB_SCTLR_WACFG (CB_SCTLR_WACFG_MASK << CB_SCTLR_WACFG_SHIFT) +#define CB_SCTLR_NSCFG (CB_SCTLR_NSCFG_MASK << CB_SCTLR_NSCFG_SHIFT) + +/* Invalidate TLB by ASID: CB_TLBIASID */ +#define CB_TLBIASID_ASID (CB_TLBIASID_ASID_MASK << CB_TLBIASID_ASID_SHIFT) + +/* Invalidate TLB by VA: CB_TLBIVA */ +#define CB_TLBIVA_ASID (CB_TLBIVA_ASID_MASK << CB_TLBIVA_ASID_SHIFT) +#define CB_TLBIVA_VA (CB_TLBIVA_VA_MASK << CB_TLBIVA_VA_SHIFT) + +/* Invalidate TLB by VA, All ASID: CB_TLBIVAA */ +#define CB_TLBIVAA_VA (CB_TLBIVAA_VA_MASK << CB_TLBIVAA_VA_SHIFT) + +/* Invalidate TLB by VA, All ASID, Last Level: CB_TLBIVAAL */ +#define CB_TLBIVAAL_VA (CB_TLBIVAAL_VA_MASK << CB_TLBIVAAL_VA_SHIFT) + +/* Invalidate TLB by VA, Last Level: CB_TLBIVAL */ +#define CB_TLBIVAL_ASID (CB_TLBIVAL_ASID_MASK << CB_TLBIVAL_ASID_SHIFT) +#define CB_TLBIVAL_VA (CB_TLBIVAL_VA_MASK << CB_TLBIVAL_VA_SHIFT) + +/* TLB Status: CB_TLBSTATUS */ +#define CB_TLBSTATUS_SACTIVE (CB_TLBSTATUS_SACTIVE_MASK << \ + CB_TLBSTATUS_SACTIVE_SHIFT) + +/* Translation Table Base Control Register: CB_TTBCR */ +#define CB_TTBCR_T0SZ (CB_TTBCR_T0SZ_MASK << CB_TTBCR_T0SZ_SHIFT) +#define CB_TTBCR_PD0 (CB_TTBCR_PD0_MASK << CB_TTBCR_PD0_SHIFT) +#define CB_TTBCR_PD1 (CB_TTBCR_PD1_MASK << CB_TTBCR_PD1_SHIFT) +#define CB_TTBCR_NSCFG0 (CB_TTBCR_NSCFG0_MASK << CB_TTBCR_NSCFG0_SHIFT) +#define CB_TTBCR_NSCFG1 (CB_TTBCR_NSCFG1_MASK << CB_TTBCR_NSCFG1_SHIFT) +#define CB_TTBCR_EAE (CB_TTBCR_EAE_MASK << CB_TTBCR_EAE_SHIFT) + +/* Translation Table Base Register 0: CB_TTBR0 */ +#define CB_TTBR0_IRGN1 (CB_TTBR0_IRGN1_MASK << CB_TTBR0_IRGN1_SHIFT) +#define CB_TTBR0_S (CB_TTBR0_S_MASK << CB_TTBR0_S_SHIFT) +#define CB_TTBR0_RGN (CB_TTBR0_RGN_MASK << CB_TTBR0_RGN_SHIFT) +#define CB_TTBR0_NOS (CB_TTBR0_NOS_MASK << CB_TTBR0_NOS_SHIFT) +#define CB_TTBR0_IRGN0 (CB_TTBR0_IRGN0_MASK << CB_TTBR0_IRGN0_SHIFT) +#define CB_TTBR0_ADDR (CB_TTBR0_ADDR_MASK << CB_TTBR0_ADDR_SHIFT) + +/* Translation Table Base Register 1: CB_TTBR1 */ +#define CB_TTBR1_IRGN1 (CB_TTBR1_IRGN1_MASK << CB_TTBR1_IRGN1_SHIFT) +#define CB_TTBR1_S (CB_TTBR1_S_MASK << CB_TTBR1_S_SHIFT) +#define CB_TTBR1_RGN (CB_TTBR1_RGN_MASK << CB_TTBR1_RGN_SHIFT) +#define CB_TTBR1_NOS (CB_TTBR1_NOS_MASK << CB_TTBR1_NOS_SHIFT) +#define CB_TTBR1_IRGN0 (CB_TTBR1_IRGN0_MASK << CB_TTBR1_IRGN0_SHIFT) +#define CB_TTBR1_ADDR (CB_TTBR1_ADDR_MASK << CB_TTBR1_ADDR_SHIFT) + +/* Global Register Masks */ +/* Configuration Register 0 */ +#define CR0_NSCFG_MASK 0x03 +#define CR0_WACFG_MASK 0x03 +#define CR0_RACFG_MASK 0x03 +#define CR0_SHCFG_MASK 0x03 +#define CR0_SMCFCFG_MASK 0x01 +#define CR0_MTCFG_MASK 0x01 +#define CR0_MEMATTR_MASK 0x0F +#define CR0_BSU_MASK 0x03 +#define CR0_FB_MASK 0x01 +#define CR0_PTM_MASK 0x01 +#define CR0_VMIDPNE_MASK 0x01 +#define CR0_USFCFG_MASK 0x01 +#define CR0_GSE_MASK 0x01 +#define CR0_STALLD_MASK 0x01 +#define CR0_TRANSIENTCFG_MASK 0x03 +#define CR0_GCFGFIE_MASK 0x01 +#define CR0_GCFGFRE_MASK 0x01 +#define CR0_GFIE_MASK 0x01 +#define CR0_GFRE_MASK 0x01 +#define CR0_CLIENTPD_MASK 0x01 + +/* Configuration Register 2 */ +#define CR2_BPVMID_MASK 0xFF + +/* Global Address Translation, Stage 1, Privileged Read: GATS1PR */ +#define GATS1PR_ADDR_MASK 0xFFFFF +#define GATS1PR_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1, Privileged Write: GATS1PW */ +#define GATS1PW_ADDR_MASK 0xFFFFF +#define GATS1PW_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1, User Read: GATS1UR */ +#define GATS1UR_ADDR_MASK 0xFFFFF +#define GATS1UR_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1, User Write: GATS1UW */ +#define GATS1UW_ADDR_MASK 0xFFFFF +#define GATS1UW_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1 and 2, Privileged Read: GATS1PR */ +#define GATS12PR_ADDR_MASK 0xFFFFF +#define GATS12PR_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1 and 2, Privileged Write: GATS1PW */ +#define GATS12PW_ADDR_MASK 0xFFFFF +#define GATS12PW_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1 and 2, User Read: GATS1UR */ +#define GATS12UR_ADDR_MASK 0xFFFFF +#define GATS12UR_NDX_MASK 0xFF + +/* Global Address Translation, Stage 1 and 2, User Write: GATS1UW */ +#define GATS12UW_ADDR_MASK 0xFFFFF +#define GATS12UW_NDX_MASK 0xFF + +/* Global Address Translation Status Register: GATSR */ +#define GATSR_ACTIVE_MASK 0x01 + +/* Global Fault Address Register: GFAR */ +#define GFAR_FADDR_MASK 0xFFFFFFFF + +/* Global Fault Status Register: GFSR */ +#define GFSR_ICF_MASK 0x01 +#define GFSR_USF_MASK 0x01 +#define GFSR_SMCF_MASK 0x01 +#define GFSR_UCBF_MASK 0x01 +#define GFSR_UCIF_MASK 0x01 +#define GFSR_CAF_MASK 0x01 +#define GFSR_EF_MASK 0x01 +#define GFSR_PF_MASK 0x01 +#define GFSR_MULTI_MASK 0x01 + +/* Global Fault Syndrome Register 0: GFSYNR0 */ +#define GFSYNR0_NESTED_MASK 0x01 +#define GFSYNR0_WNR_MASK 0x01 +#define GFSYNR0_PNU_MASK 0x01 +#define GFSYNR0_IND_MASK 0x01 +#define GFSYNR0_NSSTATE_MASK 0x01 +#define GFSYNR0_NSATTR_MASK 0x01 + +/* Global Fault Syndrome Register 1: GFSYNR1 */ +#define GFSYNR1_SID_MASK 0x7FFF +#define GFSYNr1_SSD_IDX_MASK 0x7FFF + +/* Global Physical Address Register: GPAR */ +#define GPAR_F_MASK 0x01 +#define GPAR_SS_MASK 0x01 +#define GPAR_OUTER_MASK 0x03 +#define GPAR_INNER_MASK 0x03 +#define GPAR_SH_MASK 0x01 +#define GPAR_NS_MASK 0x01 +#define GPAR_NOS_MASK 0x01 +#define GPAR_PA_MASK 0xFFFFF +#define GPAR_TF_MASK 0x01 +#define GPAR_AFF_MASK 0x01 +#define GPAR_PF_MASK 0x01 +#define GPAR_EF_MASK 0x01 +#define GPAR_TLBMCF_MASK 0x01 +#define GPAR_TLBLKF_MASK 0x01 +#define GPAR_UCBF_MASK 0x01 + +/* Identification Register: IDR0 */ +#define IDR0_NUMSMRG_MASK 0xFF +#define IDR0_NUMSIDB_MASK 0x0F +#define IDR0_BTM_MASK 0x01 +#define IDR0_CTTW_MASK 0x01 +#define IDR0_NUMIPRT_MASK 0xFF +#define IDR0_PTFS_MASK 0x01 +#define IDR0_SMS_MASK 0x01 +#define IDR0_NTS_MASK 0x01 +#define IDR0_S2TS_MASK 0x01 +#define IDR0_S1TS_MASK 0x01 +#define IDR0_SES_MASK 0x01 + +/* Identification Register: IDR1 */ +#define IDR1_NUMCB_MASK 0xFF +#define IDR1_NUMSSDNDXB_MASK 0x0F +#define IDR1_SSDTP_MASK 0x01 +#define IDR1_SMCD_MASK 0x01 +#define IDR1_NUMS2CB_MASK 0xFF +#define IDR1_NUMPAGENDXB_MASK 0x07 +#define IDR1_PAGESIZE_MASK 0x01 + +/* Identification Register: IDR2 */ +#define IDR2_IAS_MASK 0x0F +#define IDR2_OAS_MASK 0x0F + +/* Identification Register: IDR7 */ +#define IDR7_MINOR_MASK 0x0F +#define IDR7_MAJOR_MASK 0x0F + +/* Stream to Context Register: S2CR */ +#define S2CR_CBNDX_MASK 0xFF +#define S2CR_SHCFG_MASK 0x03 +#define S2CR_MTCFG_MASK 0x01 +#define S2CR_MEMATTR_MASK 0x0F +#define S2CR_TYPE_MASK 0x03 +#define S2CR_NSCFG_MASK 0x03 +#define S2CR_RACFG_MASK 0x03 +#define S2CR_WACFG_MASK 0x03 +#define S2CR_PRIVCFG_MASK 0x03 +#define S2CR_INSTCFG_MASK 0x03 +#define S2CR_TRANSIENTCFG_MASK 0x03 +#define S2CR_VMID_MASK 0xFF +#define S2CR_BSU_MASK 0x03 +#define S2CR_FB_MASK 0x01 + +/* Stream Match Register: SMR */ +#define SMR_ID_MASK 0x7FFF +#define SMR_MASK_MASK 0x7FFF +#define SMR_VALID_MASK 0x01 + +/* Global TLB Status: TLBGSTATUS */ +#define TLBGSTATUS_GSACTIVE_MASK 0x01 + +/* Invalidate Hyp TLB by VA: TLBIVAH */ +#define TLBIVAH_ADDR_MASK 0xFFFFF + +/* Invalidate TLB by VMID: TLBIVMID */ +#define TLBIVMID_VMID_MASK 0xFF + +/* Global Register Space 1 Mask */ +/* Context Bank Attribute Register: CBAR */ +#define CBAR_VMID_MASK 0xFF +#define CBAR_CBNDX_MASK 0x03 +#define CBAR_BPSHCFG_MASK 0x03 +#define CBAR_HYPC_MASK 0x01 +#define CBAR_FB_MASK 0x01 +#define CBAR_MEMATTR_MASK 0x0F +#define CBAR_TYPE_MASK 0x03 +#define CBAR_BSU_MASK 0x03 +#define CBAR_RACFG_MASK 0x03 +#define CBAR_WACFG_MASK 0x03 +#define CBAR_IRPTNDX_MASK 0xFF + +/* Context Bank Fault Restricted Syndrome Register A: CBFRSYNRA */ +#define CBFRSYNRA_SID_MASK 0x7FFF + +/* Stage 1 Context Bank Format Masks */ +/* Auxiliary Control Register: CB_ACTLR */ +#define CB_ACTLR_REQPRIORITY_MASK 0x3 +#define CB_ACTLR_REQPRIORITYCFG_MASK 0x1 +#define CB_ACTLR_PRIVCFG_MASK 0x3 +#define CB_ACTLR_BPRCOSH_MASK 0x1 +#define CB_ACTLR_BPRCISH_MASK 0x1 +#define CB_ACTLR_BPRCNSH_MASK 0x1 + +/* Address Translation, Stage 1, Privileged Read: CB_ATS1PR */ +#define CB_ATS1PR_ADDR_MASK 0xFFFFF + +/* Address Translation, Stage 1, Privileged Write: CB_ATS1PW */ +#define CB_ATS1PW_ADDR_MASK 0xFFFFF + +/* Address Translation, Stage 1, User Read: CB_ATS1UR */ +#define CB_ATS1UR_ADDR_MASK 0xFFFFF + +/* Address Translation, Stage 1, User Write: CB_ATS1UW */ +#define CB_ATS1UW_ADDR_MASK 0xFFFFF + +/* Address Translation Status Register: CB_ATSR */ +#define CB_ATSR_ACTIVE_MASK 0x01 + +/* Context ID Register: CB_CONTEXTIDR */ +#define CB_CONTEXTIDR_ASID_MASK 0xFF +#define CB_CONTEXTIDR_PROCID_MASK 0xFFFFFF + +/* Fault Address Register: CB_FAR */ +#define CB_FAR_FADDR_MASK 0xFFFFFFFF + +/* Fault Status Register: CB_FSR */ +#define CB_FSR_TF_MASK 0x01 +#define CB_FSR_AFF_MASK 0x01 +#define CB_FSR_PF_MASK 0x01 +#define CB_FSR_EF_MASK 0x01 +#define CB_FSR_TLBMCF_MASK 0x01 +#define CB_FSR_TLBLKF_MASK 0x01 +#define CB_FSR_SS_MASK 0x01 +#define CB_FSR_MULTI_MASK 0x01 + +/* Fault Syndrome Register 0: CB_FSYNR0 */ +#define CB_FSYNR0_PLVL_MASK 0x03 +#define CB_FSYNR0_S1PTWF_MASK 0x01 +#define CB_FSYNR0_WNR_MASK 0x01 +#define CB_FSYNR0_PNU_MASK 0x01 +#define CB_FSYNR0_IND_MASK 0x01 +#define CB_FSYNR0_NSSTATE_MASK 0x01 +#define CB_FSYNR0_NSATTR_MASK 0x01 +#define CB_FSYNR0_ATOF_MASK 0x01 +#define CB_FSYNR0_PTWF_MASK 0x01 +#define CB_FSYNR0_AFR_MASK 0x01 +#define CB_FSYNR0_S1CBNDX_MASK 0xFF + +/* Normal Memory Remap Register: CB_NMRR */ +#define CB_NMRR_IR0_MASK 0x03 +#define CB_NMRR_IR1_MASK 0x03 +#define CB_NMRR_IR2_MASK 0x03 +#define CB_NMRR_IR3_MASK 0x03 +#define CB_NMRR_IR4_MASK 0x03 +#define CB_NMRR_IR5_MASK 0x03 +#define CB_NMRR_IR6_MASK 0x03 +#define CB_NMRR_IR7_MASK 0x03 +#define CB_NMRR_OR0_MASK 0x03 +#define CB_NMRR_OR1_MASK 0x03 +#define CB_NMRR_OR2_MASK 0x03 +#define CB_NMRR_OR3_MASK 0x03 +#define CB_NMRR_OR4_MASK 0x03 +#define CB_NMRR_OR5_MASK 0x03 +#define CB_NMRR_OR6_MASK 0x03 +#define CB_NMRR_OR7_MASK 0x03 + +/* Physical Address Register: CB_PAR */ +#define CB_PAR_F_MASK 0x01 +#define CB_PAR_SS_MASK 0x01 +#define CB_PAR_OUTER_MASK 0x03 +#define CB_PAR_INNER_MASK 0x07 +#define CB_PAR_SH_MASK 0x01 +#define CB_PAR_NS_MASK 0x01 +#define CB_PAR_NOS_MASK 0x01 +#define CB_PAR_PA_MASK 0xFFFFF +#define CB_PAR_TF_MASK 0x01 +#define CB_PAR_AFF_MASK 0x01 +#define CB_PAR_PF_MASK 0x01 +#define CB_PAR_TLBMCF_MASK 0x01 +#define CB_PAR_TLBLKF_MASK 0x01 +#define CB_PAR_ATOT_MASK 0x01 +#define CB_PAR_PLVL_MASK 0x03 +#define CB_PAR_STAGE_MASK 0x01 + +/* Primary Region Remap Register: CB_PRRR */ +#define CB_PRRR_TR0_MASK 0x03 +#define CB_PRRR_TR1_MASK 0x03 +#define CB_PRRR_TR2_MASK 0x03 +#define CB_PRRR_TR3_MASK 0x03 +#define CB_PRRR_TR4_MASK 0x03 +#define CB_PRRR_TR5_MASK 0x03 +#define CB_PRRR_TR6_MASK 0x03 +#define CB_PRRR_TR7_MASK 0x03 +#define CB_PRRR_DS0_MASK 0x01 +#define CB_PRRR_DS1_MASK 0x01 +#define CB_PRRR_NS0_MASK 0x01 +#define CB_PRRR_NS1_MASK 0x01 +#define CB_PRRR_NOS0_MASK 0x01 +#define CB_PRRR_NOS1_MASK 0x01 +#define CB_PRRR_NOS2_MASK 0x01 +#define CB_PRRR_NOS3_MASK 0x01 +#define CB_PRRR_NOS4_MASK 0x01 +#define CB_PRRR_NOS5_MASK 0x01 +#define CB_PRRR_NOS6_MASK 0x01 +#define CB_PRRR_NOS7_MASK 0x01 + +/* Transaction Resume: CB_RESUME */ +#define CB_RESUME_TNR_MASK 0x01 + +/* System Control Register: CB_SCTLR */ +#define CB_SCTLR_M_MASK 0x01 +#define CB_SCTLR_TRE_MASK 0x01 +#define CB_SCTLR_AFE_MASK 0x01 +#define CB_SCTLR_AFFD_MASK 0x01 +#define CB_SCTLR_E_MASK 0x01 +#define CB_SCTLR_CFRE_MASK 0x01 +#define CB_SCTLR_CFIE_MASK 0x01 +#define CB_SCTLR_CFCFG_MASK 0x01 +#define CB_SCTLR_HUPCF_MASK 0x01 +#define CB_SCTLR_WXN_MASK 0x01 +#define CB_SCTLR_UWXN_MASK 0x01 +#define CB_SCTLR_ASIDPNE_MASK 0x01 +#define CB_SCTLR_TRANSIENTCFG_MASK 0x03 +#define CB_SCTLR_MEMATTR_MASK 0x0F +#define CB_SCTLR_MTCFG_MASK 0x01 +#define CB_SCTLR_SHCFG_MASK 0x03 +#define CB_SCTLR_RACFG_MASK 0x03 +#define CB_SCTLR_WACFG_MASK 0x03 +#define CB_SCTLR_NSCFG_MASK 0x03 + +/* Invalidate TLB by ASID: CB_TLBIASID */ +#define CB_TLBIASID_ASID_MASK 0xFF + +/* Invalidate TLB by VA: CB_TLBIVA */ +#define CB_TLBIVA_ASID_MASK 0xFF +#define CB_TLBIVA_VA_MASK 0xFFFFF + +/* Invalidate TLB by VA, All ASID: CB_TLBIVAA */ +#define CB_TLBIVAA_VA_MASK 0xFFFFF + +/* Invalidate TLB by VA, All ASID, Last Level: CB_TLBIVAAL */ +#define CB_TLBIVAAL_VA_MASK 0xFFFFF + +/* Invalidate TLB by VA, Last Level: CB_TLBIVAL */ +#define CB_TLBIVAL_ASID_MASK 0xFF +#define CB_TLBIVAL_VA_MASK 0xFFFFF + +/* TLB Status: CB_TLBSTATUS */ +#define CB_TLBSTATUS_SACTIVE_MASK 0x01 + +/* Translation Table Base Control Register: CB_TTBCR */ +#define CB_TTBCR_T0SZ_MASK 0x07 +#define CB_TTBCR_PD0_MASK 0x01 +#define CB_TTBCR_PD1_MASK 0x01 +#define CB_TTBCR_NSCFG0_MASK 0x01 +#define CB_TTBCR_NSCFG1_MASK 0x01 +#define CB_TTBCR_EAE_MASK 0x01 + +/* Translation Table Base Register 0/1: CB_TTBR */ +#define CB_TTBR0_IRGN1_MASK 0x01 +#define CB_TTBR0_S_MASK 0x01 +#define CB_TTBR0_RGN_MASK 0x01 +#define CB_TTBR0_NOS_MASK 0x01 +#define CB_TTBR0_IRGN0_MASK 0x01 +#define CB_TTBR0_ADDR_MASK 0xFFFFFF + +#define CB_TTBR1_IRGN1_MASK 0x1 +#define CB_TTBR1_S_MASK 0x1 +#define CB_TTBR1_RGN_MASK 0x1 +#define CB_TTBR1_NOS_MASK 0X1 +#define CB_TTBR1_IRGN0_MASK 0X1 +#define CB_TTBR1_ADDR_MASK 0xFFFFFF + +/* Global Register Shifts */ +/* Configuration Register: CR0 */ +#define CR0_NSCFG_SHIFT 28 +#define CR0_WACFG_SHIFT 26 +#define CR0_RACFG_SHIFT 24 +#define CR0_SHCFG_SHIFT 22 +#define CR0_SMCFCFG_SHIFT 21 +#define CR0_MTCFG_SHIFT 20 +#define CR0_MEMATTR_SHIFT 16 +#define CR0_BSU_SHIFT 14 +#define CR0_FB_SHIFT 13 +#define CR0_PTM_SHIFT 12 +#define CR0_VMIDPNE_SHIFT 11 +#define CR0_USFCFG_SHIFT 10 +#define CR0_GSE_SHIFT 9 +#define CR0_STALLD_SHIFT 8 +#define CR0_TRANSIENTCFG_SHIFT 6 +#define CR0_GCFGFIE_SHIFT 5 +#define CR0_GCFGFRE_SHIFT 4 +#define CR0_GFIE_SHIFT 2 +#define CR0_GFRE_SHIFT 1 +#define CR0_CLIENTPD_SHIFT 0 + +/* Configuration Register: CR2 */ +#define CR2_BPVMID_SHIFT 0 + +/* Global Address Translation, Stage 1, Privileged Read: GATS1PR */ +#define GATS1PR_ADDR_SHIFT 12 +#define GATS1PR_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1, Privileged Write: GATS1PW */ +#define GATS1PW_ADDR_SHIFT 12 +#define GATS1PW_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1, User Read: GATS1UR */ +#define GATS1UR_ADDR_SHIFT 12 +#define GATS1UR_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1, User Write: GATS1UW */ +#define GATS1UW_ADDR_SHIFT 12 +#define GATS1UW_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1 and 2, Privileged Read: GATS12PR */ +#define GATS12PR_ADDR_SHIFT 12 +#define GATS12PR_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1 and 2, Privileged Write: GATS12PW */ +#define GATS12PW_ADDR_SHIFT 12 +#define GATS12PW_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1 and 2, User Read: GATS12UR */ +#define GATS12UR_ADDR_SHIFT 12 +#define GATS12UR_NDX_SHIFT 0 + +/* Global Address Translation, Stage 1 and 2, User Write: GATS12UW */ +#define GATS12UW_ADDR_SHIFT 12 +#define GATS12UW_NDX_SHIFT 0 + +/* Global Address Translation Status Register: GATSR */ +#define GATSR_ACTIVE_SHIFT 0 + +/* Global Fault Address Register: GFAR */ +#define GFAR_FADDR_SHIFT 0 + +/* Global Fault Status Register: GFSR */ +#define GFSR_ICF_SHIFT 0 +#define GFSR_USF_SHIFT 1 +#define GFSR_SMCF_SHIFT 2 +#define GFSR_UCBF_SHIFT 3 +#define GFSR_UCIF_SHIFT 4 +#define GFSR_CAF_SHIFT 5 +#define GFSR_EF_SHIFT 6 +#define GFSR_PF_SHIFT 7 +#define GFSR_MULTI_SHIFT 31 + +/* Global Fault Syndrome Register 0: GFSYNR0 */ +#define GFSYNR0_NESTED_SHIFT 0 +#define GFSYNR0_WNR_SHIFT 1 +#define GFSYNR0_PNU_SHIFT 2 +#define GFSYNR0_IND_SHIFT 3 +#define GFSYNR0_NSSTATE_SHIFT 4 +#define GFSYNR0_NSATTR_SHIFT 5 + +/* Global Fault Syndrome Register 1: GFSYNR1 */ +#define GFSYNR1_SID_SHIFT 0 + +/* Global Physical Address Register: GPAR */ +#define GPAR_F_SHIFT 0 +#define GPAR_SS_SHIFT 1 +#define GPAR_OUTER_SHIFT 2 +#define GPAR_INNER_SHIFT 4 +#define GPAR_SH_SHIFT 7 +#define GPAR_NS_SHIFT 9 +#define GPAR_NOS_SHIFT 10 +#define GPAR_PA_SHIFT 12 +#define GPAR_TF_SHIFT 1 +#define GPAR_AFF_SHIFT 2 +#define GPAR_PF_SHIFT 3 +#define GPAR_EF_SHIFT 4 +#define GPAR_TLCMCF_SHIFT 5 +#define GPAR_TLBLKF_SHIFT 6 +#define GFAR_UCBF_SHIFT 30 + +/* Identification Register: IDR0 */ +#define IDR0_NUMSMRG_SHIFT 0 +#define IDR0_NUMSIDB_SHIFT 9 +#define IDR0_BTM_SHIFT 13 +#define IDR0_CTTW_SHIFT 14 +#define IDR0_NUMIRPT_SHIFT 16 +#define IDR0_PTFS_SHIFT 24 +#define IDR0_SMS_SHIFT 27 +#define IDR0_NTS_SHIFT 28 +#define IDR0_S2TS_SHIFT 29 +#define IDR0_S1TS_SHIFT 30 +#define IDR0_SES_SHIFT 31 + +/* Identification Register: IDR1 */ +#define IDR1_NUMCB_SHIFT 0 +#define IDR1_NUMSSDNDXB_SHIFT 8 +#define IDR1_SSDTP_SHIFT 12 +#define IDR1_SMCD_SHIFT 15 +#define IDR1_NUMS2CB_SHIFT 16 +#define IDR1_NUMPAGENDXB_SHIFT 28 +#define IDR1_PAGESIZE_SHIFT 31 + +/* Identification Register: IDR2 */ +#define IDR2_IAS_SHIFT 0 +#define IDR2_OAS_SHIFT 4 + +/* Identification Register: IDR7 */ +#define IDR7_MINOR_SHIFT 0 +#define IDR7_MAJOR_SHIFT 4 + +/* Stream to Context Register: S2CR */ +#define S2CR_CBNDX_SHIFT 0 +#define s2CR_SHCFG_SHIFT 8 +#define S2CR_MTCFG_SHIFT 11 +#define S2CR_MEMATTR_SHIFT 12 +#define S2CR_TYPE_SHIFT 16 +#define S2CR_NSCFG_SHIFT 18 +#define S2CR_RACFG_SHIFT 20 +#define S2CR_WACFG_SHIFT 22 +#define S2CR_PRIVCFG_SHIFT 24 +#define S2CR_INSTCFG_SHIFT 26 +#define S2CR_TRANSIENTCFG_SHIFT 28 +#define S2CR_VMID_SHIFT 0 +#define S2CR_BSU_SHIFT 24 +#define S2CR_FB_SHIFT 26 + +/* Stream Match Register: SMR */ +#define SMR_ID_SHIFT 0 +#define SMR_MASK_SHIFT 16 +#define SMR_VALID_SHIFT 31 + +/* Global TLB Status: TLBGSTATUS */ +#define TLBGSTATUS_GSACTIVE_SHIFT 0 + +/* Invalidate Hyp TLB by VA: TLBIVAH */ +#define TLBIVAH_ADDR_SHIFT 12 + +/* Invalidate TLB by VMID: TLBIVMID */ +#define TLBIVMID_VMID_SHIFT 0 + +/* Context Bank Attribute Register: CBAR */ +#define CBAR_VMID_SHIFT 0 +#define CBAR_CBNDX_SHIFT 8 +#define CBAR_BPSHCFG_SHIFT 8 +#define CBAR_HYPC_SHIFT 10 +#define CBAR_FB_SHIFT 11 +#define CBAR_MEMATTR_SHIFT 12 +#define CBAR_TYPE_SHIFT 16 +#define CBAR_BSU_SHIFT 18 +#define CBAR_RACFG_SHIFT 20 +#define CBAR_WACFG_SHIFT 22 +#define CBAR_IRPTNDX_SHIFT 24 + +/* Context Bank Fault Restricted Syndrome Register A: CBFRSYNRA */ +#define CBFRSYNRA_SID_SHIFT 0 + +/* Stage 1 Context Bank Format Shifts */ +/* Auxiliary Control Register: CB_ACTLR */ +#define CB_ACTLR_REQPRIORITY_SHIFT 0 +#define CB_ACTLR_REQPRIORITYCFG_SHIFT 4 +#define CB_ACTLR_PRIVCFG_SHIFT 8 +#define CB_ACTLR_BPRCOSH_SHIFT 28 +#define CB_ACTLR_BPRCISH_SHIFT 29 +#define CB_ACTLR_BPRCNSH_SHIFT 30 + +/* Address Translation, Stage 1, Privileged Read: CB_ATS1PR */ +#define CB_ATS1PR_ADDR_SHIFT 12 + +/* Address Translation, Stage 1, Privileged Write: CB_ATS1PW */ +#define CB_ATS1PW_ADDR_SHIFT 12 + +/* Address Translation, Stage 1, User Read: CB_ATS1UR */ +#define CB_ATS1UR_ADDR_SHIFT 12 + +/* Address Translation, Stage 1, User Write: CB_ATS1UW */ +#define CB_ATS1UW_ADDR_SHIFT 12 + +/* Address Translation Status Register: CB_ATSR */ +#define CB_ATSR_ACTIVE_SHIFT 0 + +/* Context ID Register: CB_CONTEXTIDR */ +#define CB_CONTEXTIDR_ASID_SHIFT 0 +#define CB_CONTEXTIDR_PROCID_SHIFT 8 + +/* Fault Address Register: CB_FAR */ +#define CB_FAR_FADDR_SHIFT 0 + +/* Fault Status Register: CB_FSR */ +#define CB_FSR_TF_SHIFT 1 +#define CB_FSR_AFF_SHIFT 2 +#define CB_FSR_PF_SHIFT 3 +#define CB_FSR_EF_SHIFT 4 +#define CB_FSR_TLBMCF_SHIFT 5 +#define CB_FSR_TLBLKF_SHIFT 6 +#define CB_FSR_SS_SHIFT 30 +#define CB_FSR_MULTI_SHIFT 31 + +/* Fault Syndrome Register 0: CB_FSYNR0 */ +#define CB_FSYNR0_PLVL_SHIFT 0 +#define CB_FSYNR0_S1PTWF_SHIFT 3 +#define CB_FSYNR0_WNR_SHIFT 4 +#define CB_FSYNR0_PNU_SHIFT 5 +#define CB_FSYNR0_IND_SHIFT 6 +#define CB_FSYNR0_NSSTATE_SHIFT 7 +#define CB_FSYNR0_NSATTR_SHIFT 8 +#define CB_FSYNR0_ATOF_SHIFT 9 +#define CB_FSYNR0_PTWF_SHIFT 10 +#define CB_FSYNR0_AFR_SHIFT 11 +#define CB_FSYNR0_S1CBNDX_SHIFT 16 + +/* Normal Memory Remap Register: CB_NMRR */ +#define CB_NMRR_IR0_SHIFT 0 +#define CB_NMRR_IR1_SHIFT 2 +#define CB_NMRR_IR2_SHIFT 4 +#define CB_NMRR_IR3_SHIFT 6 +#define CB_NMRR_IR4_SHIFT 8 +#define CB_NMRR_IR5_SHIFT 10 +#define CB_NMRR_IR6_SHIFT 12 +#define CB_NMRR_IR7_SHIFT 14 +#define CB_NMRR_OR0_SHIFT 16 +#define CB_NMRR_OR1_SHIFT 18 +#define CB_NMRR_OR2_SHIFT 20 +#define CB_NMRR_OR3_SHIFT 22 +#define CB_NMRR_OR4_SHIFT 24 +#define CB_NMRR_OR5_SHIFT 26 +#define CB_NMRR_OR6_SHIFT 28 +#define CB_NMRR_OR7_SHIFT 30 + +/* Physical Address Register: CB_PAR */ +#define CB_PAR_F_SHIFT 0 +#define CB_PAR_SS_SHIFT 1 +#define CB_PAR_OUTER_SHIFT 2 +#define CB_PAR_INNER_SHIFT 4 +#define CB_PAR_SH_SHIFT 7 +#define CB_PAR_NS_SHIFT 9 +#define CB_PAR_NOS_SHIFT 10 +#define CB_PAR_PA_SHIFT 12 +#define CB_PAR_TF_SHIFT 1 +#define CB_PAR_AFF_SHIFT 2 +#define CB_PAR_PF_SHIFT 3 +#define CB_PAR_TLBMCF_SHIFT 5 +#define CB_PAR_TLBLKF_SHIFT 6 +#define CB_PAR_ATOT_SHIFT 31 +#define CB_PAR_PLVL_SHIFT 0 +#define CB_PAR_STAGE_SHIFT 3 + +/* Primary Region Remap Register: CB_PRRR */ +#define CB_PRRR_TR0_SHIFT 0 +#define CB_PRRR_TR1_SHIFT 2 +#define CB_PRRR_TR2_SHIFT 4 +#define CB_PRRR_TR3_SHIFT 6 +#define CB_PRRR_TR4_SHIFT 8 +#define CB_PRRR_TR5_SHIFT 10 +#define CB_PRRR_TR6_SHIFT 12 +#define CB_PRRR_TR7_SHIFT 14 +#define CB_PRRR_DS0_SHIFT 16 +#define CB_PRRR_DS1_SHIFT 17 +#define CB_PRRR_NS0_SHIFT 18 +#define CB_PRRR_NS1_SHIFT 19 +#define CB_PRRR_NOS0_SHIFT 24 +#define CB_PRRR_NOS1_SHIFT 25 +#define CB_PRRR_NOS2_SHIFT 26 +#define CB_PRRR_NOS3_SHIFT 27 +#define CB_PRRR_NOS4_SHIFT 28 +#define CB_PRRR_NOS5_SHIFT 29 +#define CB_PRRR_NOS6_SHIFT 30 +#define CB_PRRR_NOS7_SHIFT 31 + +/* Transaction Resume: CB_RESUME */ +#define CB_RESUME_TNR_SHIFT 0 + +/* System Control Register: CB_SCTLR */ +#define CB_SCTLR_M_SHIFT 0 +#define CB_SCTLR_TRE_SHIFT 1 +#define CB_SCTLR_AFE_SHIFT 2 +#define CB_SCTLR_AFFD_SHIFT 3 +#define CB_SCTLR_E_SHIFT 4 +#define CB_SCTLR_CFRE_SHIFT 5 +#define CB_SCTLR_CFIE_SHIFT 6 +#define CB_SCTLR_CFCFG_SHIFT 7 +#define CB_SCTLR_HUPCF_SHIFT 8 +#define CB_SCTLR_WXN_SHIFT 9 +#define CB_SCTLR_UWXN_SHIFT 10 +#define CB_SCTLR_ASIDPNE_SHIFT 12 +#define CB_SCTLR_TRANSIENTCFG_SHIFT 14 +#define CB_SCTLR_MEMATTR_SHIFT 16 +#define CB_SCTLR_MTCFG_SHIFT 20 +#define CB_SCTLR_SHCFG_SHIFT 22 +#define CB_SCTLR_RACFG_SHIFT 24 +#define CB_SCTLR_WACFG_SHIFT 26 +#define CB_SCTLR_NSCFG_SHIFT 28 + +/* Invalidate TLB by ASID: CB_TLBIASID */ +#define CB_TLBIASID_ASID_SHIFT 0 + +/* Invalidate TLB by VA: CB_TLBIVA */ +#define CB_TLBIVA_ASID_SHIFT 0 +#define CB_TLBIVA_VA_SHIFT 12 + +/* Invalidate TLB by VA, All ASID: CB_TLBIVAA */ +#define CB_TLBIVAA_VA_SHIFT 12 + +/* Invalidate TLB by VA, All ASID, Last Level: CB_TLBIVAAL */ +#define CB_TLBIVAAL_VA_SHIFT 12 + +/* Invalidate TLB by VA, Last Level: CB_TLBIVAL */ +#define CB_TLBIVAL_ASID_SHIFT 0 +#define CB_TLBIVAL_VA_SHIFT 12 + +/* TLB Status: CB_TLBSTATUS */ +#define CB_TLBSTATUS_SACTIVE_SHIFT 0 + +/* Translation Table Base Control Register: CB_TTBCR */ +#define CB_TTBCR_T0SZ_SHIFT 0 +#define CB_TTBCR_PD0_SHIFT 4 +#define CB_TTBCR_PD1_SHIFT 5 +#define CB_TTBCR_NSCFG0_SHIFT 14 +#define CB_TTBCR_NSCFG1_SHIFT 30 +#define CB_TTBCR_EAE_SHIFT 31 + +/* Translation Table Base Register 0/1: CB_TTBR */ +#define CB_TTBR0_IRGN1_SHIFT 0 +#define CB_TTBR0_S_SHIFT 1 +#define CB_TTBR0_RGN_SHIFT 3 +#define CB_TTBR0_NOS_SHIFT 5 +#define CB_TTBR0_IRGN0_SHIFT 6 +#define CB_TTBR0_ADDR_SHIFT 14 + +#define CB_TTBR1_IRGN1_SHIFT 0 +#define CB_TTBR1_S_SHIFT 1 +#define CB_TTBR1_RGN_SHIFT 3 +#define CB_TTBR1_NOS_SHIFT 5 +#define CB_TTBR1_IRGN0_SHIFT 6 +#define CB_TTBR1_ADDR_SHIFT 14 + +#endif diff --git a/arch/arm/mach-msm/include/mach/ion.h b/arch/arm/mach-msm/include/mach/ion.h new file mode 100644 index 0000000000000000000000000000000000000000..9fbc72027f3ba62acb90fb31cc253ef92f9f6294 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/ion.h @@ -0,0 +1,29 @@ +/** + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ION_H_ +#define __MACH_ION_H_ + +enum ion_memory_types { + ION_EBI_TYPE, + ION_SMI_TYPE, +}; + +enum ion_permission_type { + IPT_TYPE_MM_CARVEOUT = 0, + IPT_TYPE_MFC_SHAREDMEM = 1, + IPT_TYPE_MDP_WRITEBACK = 2, +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/irqs-7x00.h b/arch/arm/mach-msm/include/mach/irqs-7x00.h index f1fe70612fe9ba444c645064e962521a420e44ab..a8e1da2d62d10512d7f0de238bfdd23db96d1c2a 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 1f15902655fd6b43116dcd520423181caed16b0e..a624bbf6051062235f34b83518896737a9277a29 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 0000000000000000000000000000000000000000..c90b4ee0fd188c5775487e19cd8f16667bcd3227 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-7xxx.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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_ARMQC_PERFMON (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 + +#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 0000000000000000000000000000000000000000..a5f78f5ccaac3c0513b569475db051fc6d9a7bcb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-8064.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. + */ + +#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 PM8821_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_VCAP_CB_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 + +/* smd/smsm interrupts */ +#define INT_A9_M2A_0 (GIC_SPI_START + 37) /*GSS_TO_APPS_IRQ_0*/ +#define INT_A9_M2A_5 (GIC_SPI_START + 38) /*GSS_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_DSPS_A11_SMSM SPS_MTI_30 +#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-8625.h b/arch/arm/mach-msm/include/mach/irqs-8625.h new file mode 100644 index 0000000000000000000000000000000000000000..3ff73eb8f05ac3688fdc920788d55e3625544be9 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-8625.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_8625_H +#define __ASM_ARCH_MSM_IRQS_8625_H + +#define GIC_PPI_START 16 +#define GIC_SPI_START 32 + +/* As per QGIC2 PPI 16 aka 0 is reserved */ +#define MSM8625_INT_A5_PMU_IRQ (GIC_PPI_START + 1) +#define MSM8625_INT_DEBUG_TIMER_EXP (GIC_PPI_START + 2) +#define MSM8625_INT_GP_TIMER_EXP (GIC_PPI_START + 3) +#define MSM8625_INT_COMMRX (GIC_PPI_START + 4) +#define MSM8625_INT_COMMTX (GIC_PPI_START + 5) + +/* rest of the PPI's not used + */ + +#define MSM8625_INT_A9_M2A_0 (GIC_SPI_START + 0) +#define MSM8625_INT_A9_M2A_1 (GIC_SPI_START + 1) +#define MSM8625_INT_A9_M2A_2 (GIC_SPI_START + 2) +#define MSM8625_INT_A9_M2A_3 (GIC_SPI_START + 3) +#define MSM8625_INT_A9_M2A_4 (GIC_SPI_START + 4) +#define MSM8625_INT_A9_M2A_5 (GIC_SPI_START + 5) +#define MSM8625_INT_A9_M2A_6 (GIC_SPI_START + 6) +#define MSM8625_INT_ACSR_MP_CORE_IPC0 (GIC_SPI_START + 7) +#define MSM8625_INT_ACSR_MP_CORE_IPC1 (GIC_SPI_START + 8) +#define MSM8625_INT_UART1 (GIC_SPI_START + 9) +#define MSM8625_INT_UART2 (GIC_SPI_START + 10) +#define MSM8625_INT_UART3 (GIC_SPI_START + 11) +#define MSM8625_INT_UART1_RX (GIC_SPI_START + 12) +#define MSM8625_INT_UART2_RX (GIC_SPI_START + 13) +#define MSM8625_INT_UART3_RX (GIC_SPI_START + 14) +#define MSM8625_INT_USB_OTG (GIC_SPI_START + 15) +#define MSM8625_INT_DSI_IRQ (GIC_SPI_START + 16) +#define MSM8625_INT_CSI_IRQ_1 (GIC_SPI_START + 17) +#define MSM8625_INT_CSI_IRQ_0 (GIC_SPI_START + 18) +#define MSM8625_INT_MDP (GIC_SPI_START + 19) +#define MSM8625_INT_GRAPHICS (GIC_SPI_START + 20) +#define MSM8625_INT_ADM_AARM (GIC_SPI_START + 21) +#define MSM8625_INT_ADSP_A11 (GIC_SPI_START + 22) +#define MSM8625_INT_ADSP_A9_A11 (GIC_SPI_START + 23) +#define MSM8625_INT_SDC1_0 (GIC_SPI_START + 24) +#define MSM8625_INT_SDC1_1 (GIC_SPI_START + 25) +#define MSM8625_INT_SDC2_0 (GIC_SPI_START + 26) +#define MSM8625_INT_SDC2_1 (GIC_SPI_START + 27) +#define MSM8625_INT_KEYSENSE (GIC_SPI_START + 28) +#define MSM8625_INT_TCHSCRN_SSBI (GIC_SPI_START + 29) +#define MSM8625_INT_TCHSCRN1 (GIC_SPI_START + 30) +#define MSM8625_INT_TCHSCRN2 (GIC_SPI_START + 31) + +#define MSM8625_INT_GPIO_GROUP1 (GIC_SPI_START + 32 + 0) +#define MSM8625_INT_GPIO_GROUP2 (GIC_SPI_START + 32 + 1) +#define MSM8625_INT_PWB_I2C (GIC_SPI_START + 32 + 2) +#define MSM8625_INT_SOFTRESET (GIC_SPI_START + 32 + 3) +#define MSM8625_INT_NAND_WR_ER_DONE (GIC_SPI_START + 32 + 4) +#define MSM8625_INT_NAND_OP_DONE (GIC_SPI_START + 32 + 5) +#define MSM8625_INT_PBUS_ARM11 (GIC_SPI_START + 32 + 6) +#define MSM8625_INT_AXI_MPU_SMI (GIC_SPI_START + 32 + 7) +#define MSM8625_INT_AXI_MPU_EBI1 (GIC_SPI_START + 32 + 8) +#define MSM8625_INT_AD_HSSD (GIC_SPI_START + 32 + 9) +#define MSM8625_INT_NOTUSED (GIC_SPI_START + 32 + 10) +#define MSM8625_INT_ARM11_DMA (GIC_SPI_START + 32 + 11) +#define MSM8625_INT_TSIF_IRQ (GIC_SPI_START + 32 + 12) +#define MSM8625_INT_UART1DM_IRQ (GIC_SPI_START + 32 + 13) +#define MSM8625_INT_UART1DM_RX (GIC_SPI_START + 32 + 14) +#define MSM8625_INT_USB_HS (GIC_SPI_START + 32 + 15) +#define MSM8625_INT_SDC3_0 (GIC_SPI_START + 32 + 16) +#define MSM8625_INT_SDC3_1 (GIC_SPI_START + 32 + 17) +#define MSM8625_INT_SDC4_0 (GIC_SPI_START + 32 + 18) +#define MSM8625_INT_SDC4_1 (GIC_SPI_START + 32 + 19) +#define MSM8625_INT_UART2DM_IRQ (GIC_SPI_START + 32 + 20) +#define MSM8625_INT_UART2DM_RX (GIC_SPI_START + 32 + 21) +#define MSM8625_INT_L2CC_EM (GIC_SPI_START + 32 + 22) +#define MSM8625_INT_L2CC_INTR (GIC_SPI_START + 32 + 23) +#define MSM8625_INT_CE_IRQ (GIC_SPI_START + 32 + 24) + +#define MSM8625_INT_ADSP_A11_SMSM MSM8625_INT_ADSP_A11 +#endif diff --git a/arch/arm/mach-msm/include/mach/irqs-8930.h b/arch/arm/mach-msm/include/mach/irqs-8930.h new file mode 100644 index 0000000000000000000000000000000000000000..bfc32f6a1ea22dfc350bc836e8365fa207c276f1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-8930.h @@ -0,0 +1,292 @@ +/* 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_8930_H +#define __ASM_ARCH_MSM_IRQS_8930_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_103 (GIC_SPI_START + 14) +#define PM8018_SEC_IRQ_106 (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 Q6FW_WDOG_EXPIRED_IRQ (GIC_SPI_START + 35) +#define Q6SW_WDOG_EXPIRED_IRQ (GIC_SPI_START + 36) +#define MSS_TO_APPS_IRQ_0 (GIC_SPI_START + 37) +#define MSS_TO_APPS_IRQ_1 (GIC_SPI_START + 38) +#define MSS_TO_APPS_IRQ_2 (GIC_SPI_START + 39) +#define MSS_TO_APPS_IRQ_3 (GIC_SPI_START + 40) +#define MSS_TO_APPS_IRQ_4 (GIC_SPI_START + 41) +#define MSS_TO_APPS_IRQ_5 (GIC_SPI_START + 42) +#define MSS_TO_APPS_IRQ_6 (GIC_SPI_START + 43) +#define MSS_TO_APPS_IRQ_7 (GIC_SPI_START + 44) +#define MSS_TO_APPS_IRQ_8 (GIC_SPI_START + 45) +#define MSS_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) +/* SPI IRQ 50 is unused */ +#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) +/* SPI IRQ 65 is unused */ +/* SPI IRQ 66 is unused */ +#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) +/* SPI IRQ 71 is unused */ +/* SPI IRQ 72 is unused */ +#define ROT_IRQ (GIC_SPI_START + 73) +#define MMSS_FABRIC_IRQ (GIC_SPI_START + 74) +#define MDP_IRQ (GIC_SPI_START + 75) +/* SPI IRQ 76 is unused */ +#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) +/* SPI IRQ 81 is unused */ +#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 GPS_PPS_OUT (GIC_SPI_START + 111) +/* SPI IRQ 112 is unused */ +/* SPI IRQ 113 is unused */ +/* SPI IRQ 114 is unused */ +/* SPI IRQ 115 is unused */ +#define TLMM_MSM_DIR_CONN_IRQ_11 (GIC_SPI_START + 116) +#define TLMM_MSM_DIR_CONN_IRQ_10 (GIC_SPI_START + 117) +#define BAM_DMA1 (GIC_SPI_START + 118) +#define BAM_DMA2 (GIC_SPI_START + 119) +#define SDC1_IRQ (GIC_SPI_START + 120) +#define SDC2_IRQ (GIC_SPI_START + 121) +#define SDC3_IRQ (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_4LN_IRQ (GIC_SPI_START + 139) +#define MSM8930_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 MSM8930_GSBI1_UARTDM_IRQ (GIC_SPI_START + 146) +#define MSM8930_GSBI1_QUP_IRQ (GIC_SPI_START + 147) +#define MSM8930_GSBI2_UARTDM_IRQ (GIC_SPI_START + 148) +#define MSM8930_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) +#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 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) +#define TSIF2_IRQ (GIC_SPI_START + 164) +#define TSIF1_IRQ (GIC_SPI_START + 165) +/* SPI IRQ 166 is unused */ +#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) +/* SPI IRQ 186 is unused */ +#define SDC5_BAM_IRQ (GIC_SPI_START + 187) +#define SDC5_IRQ_0 (GIC_SPI_START + 188) +#define GSBI9_UARTDM_IRQ (GIC_SPI_START + 189) +#define GSBI9_QUP_IRQ (GIC_SPI_START + 190) +#define GSBI10_UARTDM_IRQ (GIC_SPI_START + 191) +#define GSBI10_QUP_IRQ (GIC_SPI_START + 192) +#define GSBI11_UARTDM_IRQ (GIC_SPI_START + 193) +#define GSBI11_QUP_IRQ (GIC_SPI_START + 194) +#define GSBI12_UARTDM_IRQ (GIC_SPI_START + 195) +#define GSBI12_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 A2_BAM_IRQ (GIC_SPI_START + 209) +/* SPI IRQ 210 is unused */ +/* SPI IRQ 211 is unused */ +/* SPI IRQ 212 is unused */ +#define PPSS_WDOG_TIMER_IRQ (GIC_SPI_START + 213) +#define SPS_SLIMBUS_CORE_EE0_IRQ (GIC_SPI_START + 214) +#define SPS_SLIMBUS_BAM_EE0_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_SEC_IRQ_104 (GIC_SPI_START + 225) +#define PM8018_SEC_IRQ_107 (GIC_SPI_START + 226) +#define USB_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 GFX3D_VBIF_IRPT (GIC_SPI_START + 232) +#define RBIF_IRQ_0 (GIC_SPI_START + 233) +#define RBIF_IRQ_1 (GIC_SPI_START + 234) +#define RBIF_IRQ_2 (GIC_SPI_START + 235) + +/* Backwards compatible IRQ macros. */ +#define INT_ADM_AARM ADM_0_SCSS_0_IRQ + +/* smd/smsm interrupts */ +#define INT_A9_M2A_0 (GIC_SPI_START + 37) /*MSS_TO_APPS_IRQ_0*/ +#define INT_A9_M2A_5 (GIC_SPI_START + 38) /*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_DSPS_A11_SMSM SPS_MTI_30 +#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 81ab2a6792bd1f69eb73ec2e91d14dda6f9b8289..012dd74efeb8e1af78ac418ee5b49d346fd97899 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-2012, Code Aurora Forum. All rights reserved. * * This 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,30 @@ #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) +#define USB_HSIC_IRQ (GIC_SPI_START + 229) +#define MSM8960_CSIPHY_2_2LN_IRQ (GIC_SPI_START + 228) +#define CSI_2_IRQ (GIC_SPI_START + 227) -/* 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 +/* Backwards compatible IRQ macros. */ +#define INT_ADM_AARM ADM_0_SCSS_0_IRQ + +/* smd/smsm interrupts */ +#define INT_A9_M2A_0 (GIC_SPI_START + 37) /*MSS_TO_APPS_IRQ_0*/ +#define INT_A9_M2A_5 (GIC_SPI_START + 38) /*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_DSPS_A11_SMSM SPS_MTI_30 +#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 26adbe0e940631a2dcea140913eb81107f7b2dc5..f0d70f94a09ac731573c1286e011035727401621 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 f65841c74c0b9c9e9d1d23201361cc398771ee9e..c9729f4bbbd6d809e8b77e52d289f2419dd82c68 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-9615.h b/arch/arm/mach-msm/include/mach/irqs-9615.h new file mode 100644 index 0000000000000000000000000000000000000000..39058a6519146be11b108a9b6a7aa9761fa30a77 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-9615.h @@ -0,0 +1,204 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_9615_H +#define __ASM_ARCH_MSM_IRQS_9615_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 FIQ_START 16 +#define GIC_PPI_START 16 +#define GIC_SPI_START 32 + +#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) +/* 14 Reserved */ +#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) +/* 23-28 Reserved */ +#define SSBI2_1_SC_CPU0_SECURE_IRQ (GIC_SPI_START + 29) +#define SSBI2_1_SC_CPU0_NON_SECURE_IRQ (GIC_SPI_START + 30) +/* 31 Reserved */ +#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 Q6FW_WDOG_EXPIRED_IRQ (GIC_SPI_START + 35) +#define Q6SW_WDOG_EXPIRED_IRQ (GIC_SPI_START + 36) +#define MSS_TO_APPS_IRQ_0 (GIC_SPI_START + 37) +#define MSS_TO_APPS_IRQ_1 (GIC_SPI_START + 38) +#define MSS_TO_APPS_IRQ_2 (GIC_SPI_START + 39) +#define MSS_TO_APPS_IRQ_3 (GIC_SPI_START + 40) +#define MSS_TO_APPS_IRQ_4 (GIC_SPI_START + 41) +#define MSS_TO_APPS_IRQ_5 (GIC_SPI_START + 42) +#define MSS_TO_APPS_IRQ_6 (GIC_SPI_START + 43) +#define MSS_TO_APPS_IRQ_7 (GIC_SPI_START + 44) +#define MSS_TO_APPS_IRQ_8 (GIC_SPI_START + 45) +#define MSS_TO_APPS_IRQ_9 (GIC_SPI_START + 46) +/* 47-84 Reserved */ +#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) +/* 93 Reserved */ +#define USB1_HS_BAM_IRQ (GIC_SPI_START + 94) +/* 95,96 unnamed */ +#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) +/* 101,102 unnamed */ +#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) +/* 141-145 Reserved */ +#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 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) +/* 156-167 Reserved */ +#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) +/* 174 Reserved */ +#define CC_SCSS_WDT1CPU0BITEEXPIRED (GIC_SPI_START + 175) +/* 176 Reserved */ +#define CC_SCSS_WDT0CPU0BITEEXPIRED (GIC_SPI_START + 177) +#define TSENS_UPPER_LOWER_INT (GIC_SPI_START + 178) +/* 179-182 Reserved */ +#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) +/* 186-208 Reserved */ +#define A2_BAM_IRQ (GIC_SPI_START + 209) +/* 210-215 Reserved */ +#define QDSS_ETB_IRQ (GIC_SPI_START + 216) +/* 216 Reserved */ +#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 MSM_SPARE0_IRQ (GIC_SPI_START + 225) +#define PMIC_SEC_IRQ_N (GIC_SPI_START + 226) +#define USB_HSIC_BAM_IRQ (GIC_SPI_START + 231) +#define USB_HSIC_IRQ (GIC_SPI_START + 232) + +#define NR_MSM_IRQS 288 +#define NR_GPIO_IRQS 88 +#define NR_PM8018_IRQS 256 +#define NR_WCD9XXX_IRQS 49 +#define NR_TABLA_IRQS NR_WCD9XXX_IRQS +#define NR_BOARD_IRQS (NR_PM8018_IRQS + NR_WCD9XXX_IRQS) +#define NR_TLMM_MSM_DIR_CONN_IRQ 8 /*Need to Verify this Count*/ +#define NR_MSM_GPIOS NR_GPIO_IRQS + +/* Backwards compatible IRQ macros. */ +#define INT_ADM_AARM ADM_0_SCSS_0_IRQ + +/* 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 + +#endif diff --git a/arch/arm/mach-msm/include/mach/irqs-9625.h b/arch/arm/mach-msm/include/mach/irqs-9625.h new file mode 100644 index 0000000000000000000000000000000000000000..91b4d070f41cff18e263773572d3518efe057ba5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-9625.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_9625_H +#define __ASM_ARCH_MSM_IRQS_9625_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 APCC_QGICL2PERFMONIRPTREQ (GIC_SPI_START + 1) +#define SC_SICL2PERFMONIRPTREQ APCC_QGICL2PERFMONIRPTREQ +#define TLMM_MSM_SUMMARY_IRQ (GIC_SPI_START + 16) +#define SPS_BAM_DMA_IRQ (GIC_SPI_START + 208) + +#define NR_MSM_IRQS 288 +#define NR_GPIO_IRQS 88 +#define NR_BOARD_IRQS 0 +#define NR_TLMM_MSM_DIR_CONN_IRQ 8 /*Need to Verify this Count*/ +#define NR_MSM_GPIOS NR_GPIO_IRQS + +#endif diff --git a/arch/arm/mach-msm/include/mach/irqs-copper.h b/arch/arm/mach-msm/include/mach/irqs-copper.h new file mode 100644 index 0000000000000000000000000000000000000000..6d27d697a3bd4992bb4b2a1ef51f2e96b35dd438 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-copper.h @@ -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. + */ + +#ifndef __ASM_ARCH_MSM_IRQS_COPPER_H +#define __ASM_ARCH_MSM_IRQS_COPPER_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 AVS_SVICINT (GIC_PPI_START + 6) +#define AVS_SVICINTSWDONE (GIC_PPI_START + 7) +#define INT_ARMQC_PERFMON (GIC_PPI_START + 10) +/* PPI 15 is unused */ + +#define APCC_QGICL2PERFMONIRPTREQ (GIC_SPI_START + 1) +#define SC_SICL2PERFMONIRPTREQ APCC_QGICL2PERFMONIRPTREQ +#define TLMM_MSM_SUMMARY_IRQ (GIC_SPI_START + 208) +#define SPS_BAM_DMA_IRQ (GIC_SPI_START + 105) + +#define NR_MSM_IRQS 1020 /* Should be 256 - but higher due to bug in sim */ +#define NR_GPIO_IRQS 156 +#define NR_QPNP_IRQS 32768 /* SPARSE_IRQ is required to support this */ +#define NR_BOARD_IRQS NR_QPNP_IRQS +#define NR_TLMM_MSM_DIR_CONN_IRQ 8 +#define NR_MSM_GPIOS NR_GPIO_IRQS + +#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 0000000000000000000000000000000000000000..721db1db50ddc4913cf43fdf74213a3b10b0794c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-fsm9xxx.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 WDT0_ACCSCSSNBARK_INT INT_WDT0_ACCSCSSBARK + +#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 3cd78b165abb22b4ca53896ac825f03262d4ef55..fdf678627789a493d74fce1c835060b1d383a663 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-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -19,24 +19,85 @@ #define MSM_IRQ_BIT(irq) (1 << ((irq) & 31)) -#if defined(CONFIG_ARCH_MSM7X30) +#include "irqs-8625.h" + +#if defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_APQ8064) || \ + defined(CONFIG_ARCH_MSM8930) + +#ifdef CONFIG_ARCH_MSM8960 +#include "irqs-8960.h" +#endif + +#ifdef CONFIG_ARCH_MSM8930 +#include "irqs-8930.h" +#endif + +#ifdef CONFIG_ARCH_APQ8064 +#include "irqs-8064.h" +#endif + +/* For now, use the maximum number of interrupts until a pending GIC issue + * is sorted out */ +#define NR_MSM_IRQS 288 +#define NR_GPIO_IRQS 152 +#define NR_PM8921_IRQS 256 +#define NR_PM8821_IRQS 64 +#define NR_WCD9XXX_IRQS 49 +#define NR_TABLA_IRQS NR_WCD9XXX_IRQS +#define NR_GPIO_EXPANDER_IRQS 64 +#ifdef CONFIG_PCI_MSI +#define NR_PCIE_MSI_IRQS 256 +#define NR_BOARD_IRQS (NR_PM8921_IRQS + NR_PM8821_IRQS + \ + NR_WCD9XXX_IRQS + NR_GPIO_EXPANDER_IRQS + NR_PCIE_MSI_IRQS) +#else +#define NR_BOARD_IRQS (NR_PM8921_IRQS + NR_PM8821_IRQS + \ + NR_WCD9XXX_IRQS + NR_GPIO_EXPANDER_IRQS) +#endif +#define NR_TLMM_MSM_DIR_CONN_IRQ 8 /*Need to Verify this Count*/ +#define NR_MSM_GPIOS NR_GPIO_IRQS + +#else + +#if defined(CONFIG_ARCH_MSMCOPPER) +#include "irqs-copper.h" +#elif defined(CONFIG_ARCH_MSM9615) +#include "irqs-9615.h" +#elif defined(CONFIG_ARCH_MSM9625) +#include "irqs-9625.h" +#elif defined(CONFIG_ARCH_MSM7X30) #include "irqs-7x30.h" #elif defined(CONFIG_ARCH_QSD8X50) #include "irqs-8x50.h" #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_MSM7X01A) || defined(CONFIG_ARCH_MSM7X25) \ + || defined(CONFIG_ARCH_MSM7X27) +#include "irqs-7xxx.h" + +#define NR_GPIO_IRQS 133 +#define NR_MSM_IRQS 256 +#define NR_BOARD_IRQS 256 +#define NR_MSM_GPIOS NR_GPIO_IRQS +#elif defined(CONFIG_ARCH_FSM9XXX) +#include "irqs-fsm9xxx.h" +#include "sirc.h" #else #error "Unknown architecture specification" #endif +#endif + +#if !defined(CONFIG_SPARSE_IRQ) #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 + +#if defined(CONFIG_PCI_MSI) && defined(CONFIG_MSM_PCIE) +#define MSM_PCIE_MSI_INT(n) (NR_MSM_IRQS + NR_GPIO_IRQS + NR_PM8921_IRQS + \ + NR_PM8821_IRQS + NR_TABLA_IRQS + NR_GPIO_EXPANDER_IRQS + (n)) +#endif #endif diff --git a/arch/arm/mach-msm/include/mach/mdm-peripheral.h b/arch/arm/mach-msm/include/mach/mdm-peripheral.h new file mode 100644 index 0000000000000000000000000000000000000000..0f3bd33b528b8863a3cef3ed182883e2a6ca5589 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/mdm-peripheral.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 _ARCH_ARM_MACH_MSM_MDM_PERIPHERAL_H +#define _ARCH_ARM_MACH_MSM_MDM_PERIPHERAL_H_ + +extern void peripheral_connect(void); +extern void peripheral_disconnect(void); + +#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 0000000000000000000000000000000000000000..f0100feb20579f0ad6224173810f97bd57416b6e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/mdm.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 _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 +#define AP2MDM_WAKEUP 135 + +extern void (*charm_intentional_reset)(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/mdm2.h b/arch/arm/mach-msm/include/mach/mdm2.h new file mode 100644 index 0000000000000000000000000000000000000000..997b3be4e6a78fe6e3e532bdb77bd4b794880101 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/mdm2.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_MDM2_H +#define _ARCH_ARM_MACH_MSM_MDM2_H + +struct mdm_platform_data { + char *mdm_version; + int ramdump_delay_ms; + int soft_reset_inverted; + int early_power_on; + int sfr_query; + struct platform_device *peripheral_platform_device; +}; + +#endif + diff --git a/arch/arm/mach-msm/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h new file mode 100644 index 0000000000000000000000000000000000000000..259636478b9f88f5ac184f00178e253a8b6d9fac --- /dev/null +++ b/arch/arm/mach-msm/include/mach/memory.h @@ -0,0 +1,138 @@ +/* arch/arm/mach-msm/include/mach/memory.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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_MEMORY_H +#define __ASM_ARCH_MEMORY_H +#include + +/* physical offset of RAM */ +#define PLAT_PHYS_OFFSET UL(CONFIG_PHYS_OFFSET) + +#define MAX_PHYSMEM_BITS 32 +#define SECTION_SIZE_BITS 28 + +/* Maximum number of Memory Regions +* The largest system can have 4 memory banks, each divided into 8 regions +*/ +#define MAX_NR_REGIONS 32 + +/* The number of regions each memory bank is divided into */ +#define NR_REGIONS_PER_BANK 8 + +/* 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 + +#ifndef __ASSEMBLY__ + +extern unsigned long ebi1_phys_offset; + +#define EBI1_PHYS_OFFSET (ebi1_phys_offset) +#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 + +#endif + +#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(u64, u64); +int platform_physical_active_pages(u64, u64); +int platform_physical_low_power_pages(u64, u64); + +extern int (*change_memory_power)(u64, u64, int); + +#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 + +#if defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) +extern void store_ttbr0(void); +#define finish_arch_switch(prev) do { store_ttbr0(); } while (0) +#endif + +#ifdef CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0 +extern unsigned long membank0_size; +extern unsigned long membank1_start; +void find_membank0_hole(void); + +#define MEMBANK0_PHYS_OFFSET PHYS_OFFSET +#define MEMBANK0_PAGE_OFFSET PAGE_OFFSET + +#define MEMBANK1_PHYS_OFFSET (membank1_start) +#define MEMBANK1_PAGE_OFFSET (MEMBANK0_PAGE_OFFSET + (membank0_size)) + +#define __phys_to_virt(phys) \ + ((MEMBANK1_PHYS_OFFSET && ((phys) >= MEMBANK1_PHYS_OFFSET)) ? \ + (phys) - MEMBANK1_PHYS_OFFSET + MEMBANK1_PAGE_OFFSET : \ + (phys) - MEMBANK0_PHYS_OFFSET + MEMBANK0_PAGE_OFFSET) + +#define __virt_to_phys(virt) \ + ((MEMBANK1_PHYS_OFFSET && ((virt) >= MEMBANK1_PAGE_OFFSET)) ? \ + (virt) - MEMBANK1_PAGE_OFFSET + MEMBANK1_PHYS_OFFSET : \ + (virt) - MEMBANK0_PAGE_OFFSET + MEMBANK0_PHYS_OFFSET) +#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/mpm.h b/arch/arm/mach-msm/include/mach/mpm.h new file mode 100644 index 0000000000000000000000000000000000000000..10a6fb0c93cf4342aa1e8194c4d149004966ba26 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/mpm.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#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; +}; + +extern struct msm_mpm_device_data msm8660_mpm_dev_data; +extern struct msm_mpm_device_data msm8960_mpm_dev_data; +extern struct msm_mpm_device_data msm9615_mpm_dev_data; +extern struct msm_mpm_device_data apq8064_mpm_dev_data; + +void msm_mpm_irq_extn_init(struct msm_mpm_device_data *mpm_data); + +#ifdef CONFIG_MSM_MPM +int msm_mpm_enable_pin(unsigned int pin, unsigned int enable); +int msm_mpm_set_pin_wake(unsigned int pin, unsigned int on); +int msm_mpm_set_pin_type(unsigned int 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); +#else +static inline int msm_mpm_enable_irq(unsigned int irq, unsigned int enable) +{ return -ENODEV; } +static inline int msm_mpm_set_irq_wake(unsigned int irq, unsigned int on) +{ return -ENODEV; } +static inline int msm_mpm_set_irq_type(unsigned int irq, unsigned int flow_type) +{ return -ENODEV; } +static inline int msm_mpm_enable_pin(unsigned int pin, unsigned int enable) +{ return -ENODEV; } +static inline int msm_mpm_set_pin_wake(unsigned int pin, unsigned int on) +{ return -ENODEV; } +static inline int msm_mpm_set_pin_type(unsigned int pin, + unsigned int flow_type) +{ return -ENODEV; } +static inline bool msm_mpm_irqs_detectable(bool from_idle) +{ return false; } +static inline bool msm_mpm_gpio_irqs_detectable(bool from_idle) +{ return false; } +static inline void msm_mpm_enter_sleep(bool from_idle) {} +static inline void msm_mpm_exit_sleep(bool from_idle) {} +#endif + + + +#endif /* __ARCH_ARM_MACH_MSM_MPM_H */ 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 0000000000000000000000000000000000000000..8ac1f54387529e7cb9eba2c80a84f044566b7bd5 --- /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 0000000000000000000000000000000000000000..f835e828ed1beb5d9a7ac5b0682af3cfdb3a8950 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm-krait-l2-accessors.h @@ -0,0 +1,20 @@ +#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. + */ +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); + +#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 0000000000000000000000000000000000000000..623de2ae15c303a2c0a09d74783111f4a345376b --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm72k_otg.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IDPU (1 << 5) +#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 OTGSC_IDPU (1 << 5) + +#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 usb_phy phy; + + /* usb clocks */ + struct clk *alt_core_clk; + struct clk *iface_clk; + struct clk *core_clk; + + /* 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; + int id_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 usb_phy *phy, int on); + /* Reset phy and link */ + void (*reset) (struct usb_phy *phy, 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; + 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 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 0000000000000000000000000000000000000000..e40c07d82ae7c64ee99fd3b1a62ece533d04c684 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_adsp.h @@ -0,0 +1,111 @@ +/* include/asm-arm/arch-msm/msm_adsp.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2010, 2012 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); + +/*Explicitly gererate adsp event */ +int msm_adsp_generate_event(void *data, + struct msm_adsp_module *mod, + unsigned event_id, + unsigned event_length, + unsigned event_size, + void *msg); + +#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 0000000000000000000000000000000000000000..8c4d91bc46c03fc50f85303ffa03583effb6415e --- /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 0000000000000000000000000000000000000000..c54e7d8aac355e349a7a49b5cb40d766198ab043 --- /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 0000000000000000000000000000000000000000..6d7a5339f1c62df0584dfd15cbfe8f0f5d505280 --- /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 0000000000000000000000000000000000000000..956d44e6a0617afaa8f56311408aefbbe0f1fb3a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_bus_board.h @@ -0,0 +1,433 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; + bool il_flag; + const struct msm_bus_board_algorithm *board_algo; + int hw_sel; + void *hw_data; +}; + +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; +extern struct msm_bus_fabric_registration msm_bus_def_fab_pdata; + +extern struct msm_bus_fabric_registration msm_bus_8960_apps_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8960_sys_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8960_mm_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8960_sys_fpb_pdata; +extern struct msm_bus_fabric_registration msm_bus_8960_cpss_fpb_pdata; + +extern struct msm_bus_fabric_registration msm_bus_8064_apps_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8064_sys_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8064_mm_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8064_sys_fpb_pdata; +extern struct msm_bus_fabric_registration msm_bus_8064_cpss_fpb_pdata; + +extern struct msm_bus_fabric_registration msm_bus_9615_sys_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_9615_def_fab_pdata; + +extern struct msm_bus_fabric_registration msm_bus_8930_apps_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8930_sys_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8930_mm_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_8930_sys_fpb_pdata; +extern struct msm_bus_fabric_registration msm_bus_8930_cpss_fpb_pdata; + +void msm_bus_rpm_set_mt_mask(void); +int msm_bus_board_rpm_get_il_ids(uint16_t *id); +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) +#define CHECK_ID(iid, id) (((iid & id) != id) ? -ENXIO : iid) + +/* + * 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< + + +struct l2_cache_line_dump { + unsigned int l2dcrtr0_val; + unsigned int l2dcrtr1_val; + unsigned int cache_line_data[32]; + unsigned int ddr_data[32]; +} __packed; + +struct l2_cache_dump { + unsigned int magic_number; + unsigned int version; + unsigned int tag_size; + unsigned int line_size; + unsigned int total_lines; + struct l2_cache_line_dump cache[8*1024]; + unsigned int l2esr; +} __packed; + + +struct l1_cache_dump { + unsigned int magic; + unsigned int version; + unsigned int flags; + unsigned int cpu_count; + unsigned int i_tag_size; + unsigned int i_line_size; + unsigned int i_num_sets; + unsigned int i_num_ways; + unsigned int d_tag_size; + unsigned int d_line_size; + unsigned int d_num_sets; + unsigned int d_num_ways; + unsigned int spare[32]; + unsigned int lines[]; +} __packed; + + +struct msm_cache_dump_platform_data { + unsigned int l1_size; + unsigned int l2_size; +}; + +#define CACHE_BUFFER_DUMP_SIZE (L1_BUFFER_SIZE + L2_BUFFER_SIZE) + +#define L1C_SERVICE_ID 3 +#define L1C_BUFFER_SET_COMMAND_ID 4 +#define CACHE_BUFFER_DUMP_COMMAND_ID 5 +#define L1C_BUFFER_GET_SIZE_COMMAND_ID 6 +#define L2C_BUFFER_SET_COMMAND_ID 7 +#define L2C_BUFFER_GET_SIZE_COMMAND_ID 8 + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_dcvs.h b/arch/arm/mach-msm/include/mach/msm_dcvs.h new file mode 100644 index 0000000000000000000000000000000000000000..fa7e6f0eaafd620f1a7835c179f2b9160695be60 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_dcvs.h @@ -0,0 +1,150 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_DCVS_H +#define _ARCH_ARM_MACH_MSM_MSM_DCVS_H + +#include + +#define CORE_NAME_MAX (32) +#define CORES_MAX (10) + +enum msm_core_idle_state { + MSM_DCVS_IDLE_ENTER, + MSM_DCVS_IDLE_EXIT, +}; + +enum msm_core_control_event { + MSM_DCVS_ENABLE_IDLE_PULSE, + MSM_DCVS_DISABLE_IDLE_PULSE, + MSM_DCVS_ENABLE_HIGH_LATENCY_MODES, + MSM_DCVS_DISABLE_HIGH_LATENCY_MODES, +}; + +/** + * struct msm_dcvs_idle + * + * API for idle code to register and send idle enter/exit + * notifications to msm_dcvs driver. + */ +struct msm_dcvs_idle { + const char *core_name; + /* Enable/Disable idle state/notifications */ + int (*enable)(struct msm_dcvs_idle *self, + enum msm_core_control_event event); +}; + +/** + * msm_dcvs_idle_source_register + * @drv: Pointer to the source driver + * @return: Handle to be used for sending idle state notifications. + * + * Register the idle driver with the msm_dcvs driver to send idle + * state notifications for the core. + */ +extern int msm_dcvs_idle_source_register(struct msm_dcvs_idle *drv); + +/** + * msm_dcvs_idle_source_unregister + * @drv: Pointer to the source driver + * @return: + * 0 on success + * -EINVAL + * + * Description: Unregister the idle driver with the msm_dcvs driver + */ +extern int msm_dcvs_idle_source_unregister(struct msm_dcvs_idle *drv); + +/** + * msm_dcvs_idle + * @handle: Handle provided back at registration + * @state: The enter/exit idle state the core is in + * @iowaited: iowait in us + * on iMSM_DCVS_IDLE_EXIT. + * @return: + * 0 on success, + * -ENOSYS, + * -EINVAL, + * SCM return values + * + * Send idle state notifications to the msm_dcvs driver + */ +int msm_dcvs_idle(int handle, enum msm_core_idle_state state, + uint32_t iowaited); + +/** + * struct msm_dcvs_core_info + * + * Core specific information used by algorithm. Need to provide this + * before the sink driver can be registered. + */ +struct msm_dcvs_core_info { + struct msm_dcvs_freq_entry *freq_tbl; + struct msm_dcvs_core_param core_param; + struct msm_dcvs_algo_param algo_param; +}; + +/** + * msm_dcvs_register_core + * @core_name: Unique name identifier for the core. + * @group_id: Cores that are to be grouped for synchronized frequency scaling + * @info: The core specific algorithm parameters. + * @return : + * 0 on success, + * -ENOSYS, + * -ENOMEM + * + * Register the core with msm_dcvs driver. Done once at init before calling + * msm_dcvs_freq_sink_register + * Cores that need to run synchronously must share the same group id. + * If a core doesnt care to be in any group, the group_id should be 0. + */ +extern int msm_dcvs_register_core(const char *core_name, uint32_t group_id, + struct msm_dcvs_core_info *info); + +/** + * struct msm_dcvs_freq + * + * API for clock driver code to register and receive frequency change + * request for the core from the msm_dcvs driver. + */ +struct msm_dcvs_freq { + const char *core_name; + /* Callback from msm_dcvs to set the core frequency */ + int (*set_frequency)(struct msm_dcvs_freq *self, + unsigned int freq); + unsigned int (*get_frequency)(struct msm_dcvs_freq *self); +}; + +/** + * msm_dcvs_freq_sink_register + * @drv: The sink driver + * @return: Handle unique to the core. + * + * Register the clock driver code with the msm_dvs driver to get notified about + * frequency change requests. + */ +extern int msm_dcvs_freq_sink_register(struct msm_dcvs_freq *drv); + +/** + * msm_dcvs_freq_sink_unregister + * @drv: The sink driver + * @return: + * 0 on success, + * -EINVAL + * + * Unregister the sink driver for the core. This will cause the source driver + * for the core to stop sending idle pulses. + */ +extern int msm_dcvs_freq_sink_unregister(struct msm_dcvs_freq *drv); + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h b/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h new file mode 100644 index 0000000000000000000000000000000000000000..3cc259526bcd8bee247512bbe56ecc5b5abaec14 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h @@ -0,0 +1,168 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_DCVS_SCM_H +#define _ARCH_ARM_MACH_MSM_MSM_DCVS_SCM_H + +enum msm_dcvs_scm_event { + MSM_DCVS_SCM_IDLE_ENTER, + MSM_DCVS_SCM_IDLE_EXIT, + MSM_DCVS_SCM_QOS_TIMER_EXPIRED, + MSM_DCVS_SCM_CLOCK_FREQ_UPDATE, + MSM_DCVS_SCM_ENABLE_CORE, + MSM_DCVS_SCM_RESET_CORE, +}; + +struct msm_dcvs_algo_param { + uint32_t slack_time_us; + uint32_t scale_slack_time; + uint32_t scale_slack_time_pct; + uint32_t disable_pc_threshold; + uint32_t em_window_size; + uint32_t em_max_util_pct; + uint32_t ss_window_size; + uint32_t ss_util_pct; + uint32_t ss_iobusy_conv; +}; + +struct msm_dcvs_freq_entry { + uint32_t freq; /* Core freq in MHz */ + uint32_t idle_energy; + uint32_t active_energy; +}; + +struct msm_dcvs_core_param { + uint32_t max_time_us; + uint32_t num_freq; /* number of msm_dcvs_freq_entry passed */ +}; + + +#ifdef CONFIG_MSM_DCVS +/** + * Initialize DCVS algorithm in TrustZone. + * Must call before invoking any other DCVS call into TZ. + * + * @size: Size of buffer in bytes + * + * @return: + * 0 on success. + * -EEXIST: DCVS algorithm already initialized. + * -EINVAL: Invalid args. + */ +extern int msm_dcvs_scm_init(size_t size); + +/** + * Create an empty core group + * + * @return: + * 0 on success. + * -ENOMEM: Insufficient memory. + * -EINVAL: Invalid args. + */ +extern int msm_dcvs_scm_create_group(uint32_t id); + +/** + * Registers cores as part of a group + * + * @core_id: The core identifier that will be used for communication with DCVS + * @group_id: The group to which this core will be added to. + * @param: The core parameters + * @freq: Array of frequency and energy values + * + * @return: + * 0 on success. + * -ENOMEM: Insufficient memory. + * -EINVAL: Invalid args. + */ +extern int msm_dcvs_scm_register_core(uint32_t core_id, uint32_t group_id, + struct msm_dcvs_core_param *param, + struct msm_dcvs_freq_entry *freq); + +/** + * Set DCVS algorithm parameters + * + * @core_id: The algorithm parameters specific for the core + * @param: The param data structure + * + * @return: + * 0 on success. + * -EINVAL: Invalid args. + */ +extern int msm_dcvs_scm_set_algo_params(uint32_t core_id, + struct msm_dcvs_algo_param *param); + +/** + * Do an SCM call. + * + * @core_id: The core identifier. + * @event_id: The event that occured. + * Possible values: + * MSM_DCVS_SCM_IDLE_ENTER + * @param0: unused + * @param1: unused + * @ret0: unused + * @ret1: unused + * MSM_DCVS_SCM_IDLE_EXIT + * @param0: Did the core iowait + * @param1: unused + * @ret0: New clock frequency for the core in KHz + * @ret1: New QoS timer value for the core in usec + * MSM_DCVS_SCM_QOS_TIMER_EXPIRED + * @param0: unused + * @param1: unused + * @ret0: New clock frequency for the core in KHz + * @ret1: unused + * MSM_DCVS_SCM_CLOCK_FREQ_UPDATE + * @param0: active clock frequency of the core in KHz + * @param1: time taken in usec to switch to the frequency + * @ret0: New QoS timer value for the core in usec + * @ret1: unused + * MSM_DCVS_SCM_ENABLE_CORE + * @param0: enable(1) or disable(0) core + * @param1: active clock frequency of the core in KHz + * @ret0: New clock frequency for the core in KHz + * @ret1: unused + * MSM_DCVS_SCM_RESET_CORE + * @param0: active clock frequency of the core in KHz + * @param1: unused + * @ret0: New clock frequency for the core in KHz + * @ret1: unused + * @return: + * 0 on success, + * SCM return values + */ +extern int msm_dcvs_scm_event(uint32_t core_id, + enum msm_dcvs_scm_event event_id, + uint32_t param0, uint32_t param1, + uint32_t *ret0, uint32_t *ret1); + +#else +static inline int msm_dcvs_scm_init(uint32_t phy, size_t bytes) +{ return -ENOSYS; } +static inline int msm_dcvs_scm_create_group(uint32_t id) +{ return -ENOSYS; } +static inline int msm_dcvs_scm_register_core(uint32_t core_id, + uint32_t group_id, + struct msm_dcvs_core_param *param, + struct msm_dcvs_freq_entry *freq) +{ return -ENOSYS; } +static inline int msm_dcvs_scm_set_algo_params(uint32_t core_id, + struct msm_dcvs_algo_param *param) +{ return -ENOSYS; } +static inline int msm_dcvs_scm_event(uint32_t core_id, + enum msm_dcvs_scm_event event_id, + uint32_t param0, uint32_t param1, + uint32_t *ret0, uint32_t *ret1) +{ return -ENOSYS; } +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_dsps.h b/arch/arm/mach-msm/include/mach/msm_dsps.h new file mode 100644 index 0000000000000000000000000000000000000000..cfb2024c2dc591203efa0b98116fdb9cff14f988 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_dsps.h @@ -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. + * + */ + +#ifndef _MSM_DSPS_H_ +#define _MSM_DSPS_H_ + +#include +#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; + void (*init)(struct msm_dsps_platform_data *data); + 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 0000000000000000000000000000000000000000..e1660c192a3cab583fb683b80d7e6e5272c8f552 --- /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 1f4fc81b3d8fc9fb90bba2f31c2cce3fe70d4228..3bbaa25da474a2fcd0b99ddd7f65d28ea338ce9b 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,8 +182,17 @@ 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; }; +struct mdp_v4l2_req; +int msm_fb_v4l2_enable(struct mdp_overlay *req, bool enable, void **par); +int msm_fb_v4l2_update(void *par, + unsigned long srcp0_addr, unsigned long srcp0_size, + unsigned long srcp1_addr, unsigned long srcp1_size, + unsigned long srcp2_addr, unsigned long srcp2_size); #endif 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 0000000000000000000000000000000000000000..57e794f33bbc3d00770157b45adcb2be58aacadb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hdmi_audio.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +/* 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 TRUE 1 +#define FALSE 0 + +enum hdmi_supported_sample_rates { + HDMI_SAMPLE_RATE_32KHZ, + HDMI_SAMPLE_RATE_44_1KHZ, + HDMI_SAMPLE_RATE_48KHZ, + HDMI_SAMPLE_RATE_88_2KHZ, + HDMI_SAMPLE_RATE_96KHZ, + HDMI_SAMPLE_RATE_176_4KHZ, + HDMI_SAMPLE_RATE_192KHZ +}; + +int hdmi_audio_enable(bool on , u32 fifo_water_mark); +int hdmi_audio_packet_enable(bool on); +void hdmi_msm_audio_sample_rate_reset(int rate); +int hdmi_msm_audio_get_sample_rate(void); +int hdmi_msm_audio_info_setup(bool enabled, u32 num_of_channels, + u32 channel_allocation, u32 level_shift, bool down_mix); + +#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 0000000000000000000000000000000000000000..4f140cc89495f8104a7b9fa4740aece6aeabcfe2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hsusb.h @@ -0,0 +1,209 @@ +/* linux/include/mach/hsusb.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2012, 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 +#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_gnd(ints) (phy_id_state((ints)) == PHY_ID_GND) +#define phy_id_state_a(ints) (phy_id_state((ints)) == PHY_ID_A) +/* RID_B and RID_C states does not exist with standard ACA */ +#ifdef CONFIG_USB_MSM_STANDARD_ACA +#define phy_id_state_b(ints) 0 +#define phy_id_state_c(ints) 0 +#else +#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) +#endif + +/* + * The following are bit fields describing the usb_request.udc_priv word. + * These bit fields are set by function drivers that wish to queue + * usb_requests with sps/bam parameters. + */ +#define MSM_PIPE_ID_MASK (0x1F) +#define MSM_TX_PIPE_ID_OFS (16) +#define MSM_SPS_MODE BIT(5) +#define MSM_IS_FINITE_TRANSFER BIT(6) +#define MSM_PRODUCER BIT(7) +#define MSM_DISABLE_WB BIT(8) +#define MSM_ETD_IOC BIT(9) +#define MSM_INTERNAL_MEM BIT(10) +#define MSM_VENDOR_ID BIT(16) + +/* 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 *); + int pmic_vbus_irq; + int pmic_id_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; + int bam_disable; + /* 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; + + 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 (*phy_id_setup_init) (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 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; +}; + +int msm_ep_config(struct usb_ep *ep); +int msm_ep_unconfig(struct usb_ep *ep); +int msm_data_fifo_config(struct usb_ep *ep, u32 addr, u32 size); + +#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 0000000000000000000000000000000000000000..82542b2a9e63b0125c889c750e555be35454a36c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h @@ -0,0 +1,286 @@ + /* + * 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_GEN_CONFIG (MSM_USB_BASE + 0x009C) +#define USB_BAM_DISABLE (1 << 13) +#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 0000000000000000000000000000000000000000..dc33c757bda738630a4d632dc69c5399b9204991 --- /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 6c4046c21296c976e7352a8c260bf661be090324..44f0a8b57d82855838d72686b696aae94f9caa93 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 @@ -38,28 +37,34 @@ * */ -#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_DMOV_PHYS 0xA9700000 -#define MSM_DMOV_SIZE SZ_4K +#define MSM_TMR_PHYS MSM_CSR_PHYS +#define MSM_TMR_BASE MSM_CSR_BASE +#define MSM_TMR_SIZE SZ_4K -#define MSM7X00_GPIO1_PHYS 0xA9200000 -#define MSM7X00_GPIO1_SIZE SZ_4K +#define MSM_GPT_BASE MSM_TMR_BASE +#define MSM_DGT_BASE (MSM_TMR_BASE + 0x10) -#define MSM7X00_GPIO2_PHYS 0xA9300000 -#define MSM7X00_GPIO2_SIZE SZ_4K +#define MSM_GPIO1_BASE IOMEM(0xF8003000) +#define MSM_GPIO1_PHYS 0xA9200000 +#define MSM_GPIO1_SIZE SZ_4K -#define MSM_CLK_CTL_BASE IOMEM(0xE0005000) +#define MSM_GPIO2_BASE IOMEM(0xF8004000) +#define MSM_GPIO2_PHYS 0xA9300000 +#define MSM_GPIO2_SIZE SZ_4K + +#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 @@ -84,6 +89,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 @@ -99,17 +107,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) -#ifndef __ASSEMBLY__ +#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 -extern void __iomem *__msm_ioremap_caller(unsigned long phys_addr, size_t size, - unsigned int mtype, void *caller); +#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 f944fe65a657c4847bbeadfa1a7973d4503ea1ca..dfc6f231a8e7d6fbbbe91d8e03fa9bfee34487af 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-2012 Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -35,70 +35,53 @@ * */ -#define MSM_VIC_BASE IOMEM(0xE0000000) -#define MSM_VIC_PHYS 0xC0080000 -#define MSM_VIC_SIZE SZ_4K +#define MSM7X30_VIC_PHYS 0xC0080000 +#define MSM7X30_VIC_SIZE SZ_4K -#define MSM7X30_CSR_PHYS 0xC0100000 -#define MSM7X30_CSR_SIZE SZ_4K +#define MSM7X30_CSR_PHYS 0xC0100000 +#define MSM7X30_CSR_SIZE SZ_4K -#define MSM_DMOV_BASE IOMEM(0xE0002000) -#define MSM_DMOV_PHYS 0xAC400000 -#define MSM_DMOV_SIZE SZ_4K +#define MSM7X30_TMR_PHYS MSM7X30_CSR_PHYS +#define MSM7X30_TMR_SIZE SZ_4K -#define MSM7X30_GPIO1_PHYS 0xAC001000 -#define MSM7X30_GPIO1_SIZE SZ_4K +#define MSM7X30_GPIO1_PHYS 0xAC001000 +#define MSM7X30_GPIO1_SIZE SZ_4K -#define MSM7X30_GPIO2_PHYS 0xAC101000 -#define MSM7X30_GPIO2_SIZE SZ_4K +#define MSM7X30_GPIO2_PHYS 0xAC101000 +#define MSM7X30_GPIO2_SIZE SZ_4K -#define MSM_CLK_CTL_BASE IOMEM(0xE0005000) -#define MSM_CLK_CTL_PHYS 0xAB800000 -#define MSM_CLK_CTL_SIZE SZ_4K +#define MSM7X30_CLK_CTL_PHYS 0xAB800000 +#define MSM7X30_CLK_CTL_SIZE SZ_4K -#define MSM_CLK_CTL_SH2_BASE IOMEM(0xE0006000) -#define MSM_CLK_CTL_SH2_PHYS 0xABA01000 -#define MSM_CLK_CTL_SH2_SIZE SZ_4K +#define MSM7X30_CLK_CTL_SH2_PHYS 0xABA01000 +#define MSM7X30_CLK_CTL_SH2_SIZE SZ_4K -#define MSM_ACC_BASE IOMEM(0xE0007000) -#define MSM_ACC_PHYS 0xC0101000 -#define MSM_ACC_SIZE SZ_4K +#define MSM7X30_ACC0_PHYS 0xC0101000 +#define MSM7X30_ACC0_SIZE SZ_4K -#define MSM_SAW_BASE IOMEM(0xE0008000) -#define MSM_SAW_PHYS 0xC0102000 -#define MSM_SAW_SIZE SZ_4K +#define MSM7X30_SAW0_PHYS 0xC0102000 +#define MSM7X30_SAW0_SIZE SZ_4K -#define MSM_GCC_BASE IOMEM(0xE0009000) -#define MSM_GCC_PHYS 0xC0182000 -#define MSM_GCC_SIZE SZ_4K +#define MSM7X30_APCS_GCC_PHYS 0xC0182000 +#define MSM7X30_APCS_GCC_SIZE SZ_4K -#define MSM_TCSR_BASE IOMEM(0xE000A000) -#define MSM_TCSR_PHYS 0xAB600000 -#define MSM_TCSR_SIZE SZ_4K +#define MSM7X30_TCSR_PHYS 0xAB600000 +#define MSM7X30_TCSR_SIZE SZ_4K -#define MSM_SHARED_RAM_BASE IOMEM(0xE0100000) -#define MSM_SHARED_RAM_PHYS 0x00100000 -#define MSM_SHARED_RAM_SIZE SZ_1M +#define MSM7X30_UART1_PHYS 0xACA00000 +#define MSM7X30_UART1_SIZE SZ_4K -#define MSM_UART1_PHYS 0xACA00000 -#define MSM_UART1_SIZE SZ_4K +#define MSM7X30_UART2_PHYS 0xACB00000 +#define MSM7X30_UART2_SIZE SZ_4K -#define MSM_UART2_PHYS 0xACB00000 -#define MSM_UART2_SIZE SZ_4K +#define MSM7X30_UART3_PHYS 0xACC00000 +#define MSM7X30_UART3_SIZE SZ_4K -#define MSM_UART3_PHYS 0xACC00000 -#define MSM_UART3_SIZE SZ_4K +#define MSM7X30_MDC_PHYS 0xAA500000 +#define MSM7X30_MDC_SIZE SZ_1M -#define MSM_MDC_BASE IOMEM(0xE0200000) -#define MSM_MDC_PHYS 0xAA500000 -#define MSM_MDC_SIZE SZ_1M - -#define MSM_AD5_BASE IOMEM(0xE0300000) -#define MSM_AD5_PHYS 0xA7000000 -#define MSM_AD5_SIZE (SZ_1M*13) - -#define MSM_HSUSB_PHYS 0xA3600000 -#define MSM_HSUSB_SIZE SZ_1K +#define MSM7X30_AD5_PHYS 0xA7000000 +#define MSM7X30_AD5_SIZE (SZ_1M*13) #ifndef __ASSEMBLY__ extern void msm_map_msm7x30_io(void); 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 0000000000000000000000000000000000000000..4c26d08b3fa15f7b9c8879f720cbed80d63dbe76 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-7xxx.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2012, 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 MSM7XXX_VIC_PHYS 0xC0000000 +#define MSM7XXX_VIC_SIZE SZ_4K + +#define MSM7XXX_CSR_PHYS 0xC0100000 +#define MSM7XXX_CSR_SIZE SZ_4K + +#define MSM7XXX_TMR_PHYS MSM7XXX_CSR_PHYS +#define MSM7XXX_TMR_SIZE SZ_4K + +#define MSM7XXX_GPIO1_PHYS 0xA9200000 +#define MSM7XXX_GPIO1_SIZE SZ_4K + +#define MSM7XXX_GPIO2_PHYS 0xA9300000 +#define MSM7XXX_GPIO2_SIZE SZ_4K + +#define MSM7XXX_CLK_CTL_PHYS 0xA8600000 +#define MSM7XXX_CLK_CTL_SIZE SZ_4K + +#define MSM7XXX_L2CC_PHYS 0xC0400000 +#define MSM7XXX_L2CC_SIZE SZ_4K + +#define MSM7XXX_UART1_PHYS 0xA9A00000 +#define MSM7XXX_UART1_SIZE SZ_4K + +#define MSM7XXX_UART2_PHYS 0xA9B00000 +#define MSM7XXX_UART2_SIZE SZ_4K + +#define MSM7XXX_UART3_PHYS 0xA9C00000 +#define MSM7XXX_UART3_SIZE SZ_4K + +#define MSM7XXX_MDC_PHYS 0xAA500000 +#define MSM7XXX_MDC_SIZE SZ_1M + +#define MSM7XXX_AD5_PHYS 0xAC000000 +#define MSM7XXX_AD5_SIZE (SZ_1M*13) + +#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 0000000000000000000000000000000000000000..10e2b74f3dcad795910f3dafd9af073f39afea82 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8064.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2012, 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 + +#define APQ8064_TLMM_PHYS 0x00800000 +#define APQ8064_TLMM_SIZE SZ_16K + +#define APQ8064_ACC0_PHYS 0x02088000 +#define APQ8064_ACC0_SIZE SZ_4K + +#define APQ8064_ACC1_PHYS 0x02098000 +#define APQ8064_ACC1_SIZE SZ_4K + +#define APQ8064_ACC2_PHYS 0x020A8000 +#define APQ8064_ACC2_SIZE SZ_4K + +#define APQ8064_ACC3_PHYS 0x020B8000 +#define APQ8064_ACC3_SIZE SZ_4K + +#define APQ8064_APCS_GCC_PHYS 0x02011000 +#define APQ8064_APCS_GCC_SIZE SZ_4K + +#define APQ8064_CLK_CTL_PHYS 0x00900000 +#define APQ8064_CLK_CTL_SIZE SZ_16K + +#define APQ8064_MMSS_CLK_CTL_PHYS 0x04000000 +#define APQ8064_MMSS_CLK_CTL_SIZE SZ_4K + +#define APQ8064_LPASS_CLK_CTL_PHYS 0x28000000 +#define APQ8064_LPASS_CLK_CTL_SIZE SZ_4K + +#define APQ8064_HFPLL_PHYS 0x00903000 +#define APQ8064_HFPLL_SIZE SZ_4K + +#define APQ8064_IMEM_PHYS 0x2A03F000 +#define APQ8064_IMEM_SIZE SZ_4K + +#define APQ8064_RPM_PHYS 0x00108000 +#define APQ8064_RPM_SIZE SZ_4K + +#define APQ8064_RPM_MPM_PHYS 0x00200000 +#define APQ8064_RPM_MPM_SIZE SZ_4K + +#define APQ8064_SAW0_PHYS 0x02089000 +#define APQ8064_SAW0_SIZE SZ_4K + +#define APQ8064_SAW1_PHYS 0x02099000 +#define APQ8064_SAW1_SIZE SZ_4K + +#define APQ8064_SAW2_PHYS 0x020A9000 +#define APQ8064_SAW2_SIZE SZ_4K + +#define APQ8064_SAW3_PHYS 0x020B9000 +#define APQ8064_SAW3_SIZE SZ_4K + +#define APQ8064_SAW_L2_PHYS 0x02012000 +#define APQ8064_SAW_L2_SIZE SZ_4K +#define APQ8064_QFPROM_PHYS 0x00700000 +#define APQ8064_QFPROM_SIZE SZ_4K + +#define APQ8064_SIC_NON_SECURE_PHYS 0x12100000 +#define APQ8064_SIC_NON_SECURE_SIZE SZ_64K + +#define APQ8064_HDMI_PHYS 0x04A00000 +#define APQ8064_HDMI_SIZE SZ_4K + +#ifdef CONFIG_DEBUG_APQ8064_UART +#define MSM_DEBUG_UART_BASE IOMEM(0xFA740000) +#define MSM_DEBUG_UART_PHYS 0x16640000 +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-8625.h b/arch/arm/mach-msm/include/mach/msm_iomap-8625.h new file mode 100644 index 0000000000000000000000000000000000000000..3435c2add90926e376d2b5ca909a08ac12eb6c26 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8625.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012, 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. + * + * + * 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_8625_H +#define __ASM_ARCH_MSM_IOMAP_8625_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 MSM8625_TMR_PHYS 0xC0800000 +#define MSM8625_TMR_SIZE SZ_4K + +#define MSM8625_TMR0_PHYS 0xC0100000 +#define MSM8625_TMR0_SIZE SZ_4K + +#define MSM8625_CLK_CTL_PHYS 0xA8600000 +#define MSM8625_CLK_CTL_SIZE SZ_4K + +#define MSM8625_QGIC_DIST_PHYS 0xC0000000 +#define MSM8625_QGIC_DIST_SIZE SZ_4K + +#define MSM8625_QGIC_CPU_PHYS 0xC0002000 +#define MSM8625_QGIC_CPU_SIZE SZ_4K + +#define MSM8625_SCU_PHYS 0xC0600000 +#define MSM8625_SCU_SIZE SZ_256 + +#define MSM8625_SAW0_PHYS 0xC0200000 +#define MSM8625_SAW0_SIZE SZ_4K + +#define MSM8625_SAW1_PHYS 0xC0700000 +#define MSM8625_SAW1_SIZE SZ_4K + +#define MSM8625_CFG_CTL_PHYS 0xA9800000 +#define MSM8625_CFG_CTL_SIZE SZ_4K + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-8930.h b/arch/arm/mach-msm/include/mach/msm_iomap-8930.h new file mode 100644 index 0000000000000000000000000000000000000000..f3f8b8fcca3eb033a5ca48398fc0ed33d9508c11 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8930.h @@ -0,0 +1,112 @@ +/* + * 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_8930_H +#define __ASM_ARCH_MSM_IOMAP_8930_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 MSM8930_TMR_PHYS 0x0200A000 +#define MSM8930_TMR_SIZE SZ_4K + +#define MSM8930_TMR0_PHYS 0x0208A000 +#define MSM8930_TMR0_SIZE SZ_4K + +#define MSM8930_RPM_PHYS 0x00108000 +#define MSM8930_RPM_SIZE SZ_4K + +#define MSM8930_RPM_MPM_PHYS 0x00200000 +#define MSM8930_RPM_MPM_SIZE SZ_4K + +#define MSM8930_TCSR_PHYS 0x1A400000 +#define MSM8930_TCSR_SIZE SZ_4K + +#define MSM8930_APCS_GCC_PHYS 0x02011000 +#define MSM8930_APCS_GCC_SIZE SZ_4K + +#define MSM8930_SAW_L2_PHYS 0x02012000 +#define MSM8930_SAW_L2_SIZE SZ_4K + +#define MSM8930_SAW0_PHYS 0x02089000 +#define MSM8930_SAW0_SIZE SZ_4K + +#define MSM8930_SAW1_PHYS 0x02099000 +#define MSM8930_SAW1_SIZE SZ_4K + +#define MSM8930_IMEM_PHYS 0x2A03F000 +#define MSM8930_IMEM_SIZE SZ_4K + +#define MSM8930_ACC0_PHYS 0x02088000 +#define MSM8930_ACC0_SIZE SZ_4K + +#define MSM8930_ACC1_PHYS 0x02098000 +#define MSM8930_ACC1_SIZE SZ_4K + +#define MSM8930_QGIC_DIST_PHYS 0x02000000 +#define MSM8930_QGIC_DIST_SIZE SZ_4K + +#define MSM8930_QGIC_CPU_PHYS 0x02002000 +#define MSM8930_QGIC_CPU_SIZE SZ_4K + +#define MSM8930_CLK_CTL_PHYS 0x00900000 +#define MSM8930_CLK_CTL_SIZE SZ_16K + +#define MSM8930_MMSS_CLK_CTL_PHYS 0x04000000 +#define MSM8930_MMSS_CLK_CTL_SIZE SZ_4K + +#define MSM8930_LPASS_CLK_CTL_PHYS 0x28000000 +#define MSM8930_LPASS_CLK_CTL_SIZE SZ_4K + +#define MSM8930_HFPLL_PHYS 0x00903000 +#define MSM8930_HFPLL_SIZE SZ_4K + +#define MSM8930_TLMM_PHYS 0x00800000 +#define MSM8930_TLMM_SIZE SZ_16K + +#define MSM8930_DMOV_PHYS 0x18320000 +#define MSM8930_DMOV_SIZE SZ_1M + +#define MSM8930_SIC_NON_SECURE_PHYS 0x12100000 +#define MSM8930_SIC_NON_SECURE_SIZE SZ_64K + +#define MSM_GPT_BASE (MSM_TMR_BASE + 0x4) +#define MSM_DGT_BASE (MSM_TMR_BASE + 0x24) + +#define MSM8930_HDMI_PHYS 0x04A00000 +#define MSM8930_HDMI_SIZE SZ_4K + +#ifdef CONFIG_DEBUG_MSM8930_UART +#define MSM_DEBUG_UART_BASE IOMEM(0xFA740000) +#define MSM_DEBUG_UART_PHYS 0x16440000 +#endif + +#define MSM8930_QFPROM_PHYS 0x00700000 +#define MSM8930_QFPROM_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 a1752c0284fca2e25ebdd74d100c6442c8799b1c..54c901f4a84d8253e3db6182756b47b3aa942b01 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-8960.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8960.h @@ -32,18 +32,79 @@ * */ +#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_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_HDMI_PHYS 0x04A00000 +#define MSM8960_HDMI_SIZE SZ_4K + +#ifdef CONFIG_DEBUG_MSM8960_UART +#define MSM_DEBUG_UART_BASE IOMEM(0xFA740000) +#define MSM_DEBUG_UART_PHYS 0x16440000 +#endif + +#define MSM8960_QFPROM_PHYS 0x00700000 +#define MSM8960_QFPROM_SIZE SZ_4K #ifdef CONFIG_DEBUG_MSM8960_UART #define MSM_DEBUG_UART_BASE 0xE1040000 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 da77cc1d545d0158949eda6c3042d57ff9e2566a..a1b32ec67759b69eda3053066435688c54c70280 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,43 +35,43 @@ * */ -#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_DMOV_PHYS 0xA9700000 -#define MSM_DMOV_SIZE SZ_4K +#define MSM_TMR_PHYS MSM_CSR_PHYS +#define MSM_TMR_BASE MSM_CSR_BASE +#define MSM_TMR_SIZE SZ_4K -#define QSD8X50_GPIO1_PHYS 0xA9000000 -#define QSD8X50_GPIO1_SIZE SZ_4K +#define MSM_GPIO1_BASE IOMEM(0xFA003000) +#define MSM_GPIO1_PHYS 0xA9000000 +#define MSM_GPIO1_SIZE SZ_4K -#define QSD8X50_GPIO2_PHYS 0xA9100000 -#define QSD8X50_GPIO2_SIZE SZ_4K +#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 @@ -83,47 +83,12 @@ #define MSM_UART3_PHYS 0xA9C00000 #define MSM_UART3_SIZE SZ_4K -#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 - -#ifndef __ASSEMBLY__ -extern void msm_map_qsd8x50_io(void); -#endif - #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 5aed57dc808c081f13c99f5d6ed0883c4ba46b56..4f90ea57a6189291e533f52c0dbb4fd9129a235b 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-8x60.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8x60.h @@ -35,35 +35,99 @@ * */ -#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 MSM_SAW1_BASE IOMEM(0xFA304000) +#define MSM_SAW1_PHYS 0x02052000 +#define MSM_SAW1_SIZE SZ_4K + +#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 MSM8X60_TMR0_PHYS 0x02040000 -#define MSM8X60_TMR0_SIZE SZ_4K +#define MSM_HDMI_BASE IOMEM(0xFA800000) +#define MSM_HDMI_PHYS 0x04A00000 +#define MSM_HDMI_SIZE SZ_4K #ifdef CONFIG_DEBUG_MSM8660_UART -#define MSM_DEBUG_UART_BASE 0xE1040000 +#define MSM_DEBUG_UART_BASE 0xFBC40000 #define MSM_DEBUG_UART_PHYS 0x19C40000 #endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-9615.h b/arch/arm/mach-msm/include/mach/msm_iomap-9615.h new file mode 100644 index 0000000000000000000000000000000000000000..fc9b198a308500695c94f76d41f9ca6866f06d12 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-9615.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2012, 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_9615_H +#define __ASM_ARCH_MSM_IOMAP_9615_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 MSM9615_TMR_PHYS 0x0200A000 +#define MSM9615_TMR_SIZE SZ_4K + +#define MSM9615_QGIC_DIST_PHYS 0x02000000 +#define MSM9615_QGIC_DIST_SIZE SZ_4K + +#define MSM9615_QGIC_CPU_PHYS 0x02002000 +#define MSM9615_QGIC_CPU_SIZE SZ_4K + +#define MSM9615_TLMM_PHYS 0x00800000 +#define MSM9615_TLMM_SIZE SZ_1M + +#define MSM9615_ACC0_PHYS 0x02008000 +#define MSM9615_ACC0_SIZE SZ_4K + +#define MSM9615_APCS_GCC_PHYS 0x02011000 +#define MSM9615_APCS_GCC_SIZE SZ_4K + +#define MSM9615_SAW0_PHYS 0x02009000 +#define MSM9615_SAW0_SIZE SZ_4K + +#define MSM9615_TCSR_PHYS 0x1A400000 +#define MSM9615_TCSR_SIZE SZ_4K + +#define MSM9615_L2CC_PHYS 0x02040000 +#define MSM9615_L2CC_SIZE SZ_4K + +#define MSM9615_CLK_CTL_PHYS 0x00900000 +#define MSM9615_CLK_CTL_SIZE SZ_16K + +#define MSM9615_LPASS_CLK_CTL_PHYS 0x28000000 +#define MSM9615_LPASS_CLK_CTL_SIZE SZ_4K + +#define MSM9615_RPM_PHYS 0x00108000 +#define MSM9615_RPM_SIZE SZ_4K + +#define MSM9615_RPM_MPM_PHYS 0x00200000 +#define MSM9615_RPM_MPM_SIZE SZ_4K + +#define MSM9615_APCS_GLB_PHYS 0x02010000 +#define MSM9615_APCS_GLB_SIZE SZ_4K + +#define MSM9615_HSUSB_PHYS 0x12500000 +#define MSM9615_HSUSB_SIZE SZ_4K + +#define MSM9615_HSIC_PHYS 0x12540000 +#define MSM9615_HSIC_SIZE SZ_4K + +#define MSM9615_QFPROM_PHYS 0x00700000 +#define MSM9615_QFPROM_SIZE SZ_4K + +#define MSM9615_IMEM_PHYS 0x2B000000 +#define MSM9615_IMEM_SIZE SZ_4K + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-9625.h b/arch/arm/mach-msm/include/mach/msm_iomap-9625.h new file mode 100644 index 0000000000000000000000000000000000000000..493cf360dda0e60c7fcff647a2a23fbe3e4ab78d --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-9625.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012, 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_IOMAP_MSM9625_H +#define __ASM_ARCH_MSM_IOMAP_MSM9625_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 + * io desc array in arch/arm/mach-msm/io.c to reflect your + * changes. + * + */ + +#define MSM9625_SHARED_RAM_PHYS 0x18D00000 + +#define MSM9625_APCS_GCC_PHYS 0xF9011000 +#define MSM9625_APCS_GCC_SIZE SZ_4K + +#define MSM9625_TMR_PHYS 0xF9021000 +#define MSM9625_TMR_SIZE SZ_4K + +#define MSM9625_TLMM_PHYS 0xFD510000 +#define MSM9625_TLMM_SIZE SZ_16K + +#ifdef CONFIG_DEBUG_MSM9625_UART +#define MSM_DEBUG_UART_BASE IOMEM(0xFA71E000) +#define MSM_DEBUG_UART_PHYS 0xF991E000 +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-copper.h b/arch/arm/mach-msm/include/mach/msm_iomap-copper.h new file mode 100644 index 0000000000000000000000000000000000000000..441f82af294889384c5b99111a02400192f5f080 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-copper.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2008-2012, 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_IOMAP_COPPER_H +#define __ASM_ARCH_MSM_IOMAP_COPPER_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 + * io desc array in arch/arm/mach-msm/io.c to reflect your + * changes. + * + */ + +#define COPPER_MSM_SHARED_RAM_PHYS 0x0FA00000 + +#define COPPER_QGIC_DIST_PHYS 0xF9000000 +#define COPPER_QGIC_DIST_SIZE SZ_4K + +#define COPPER_QGIC_CPU_PHYS 0xF9002000 +#define COPPER_QGIC_CPU_SIZE SZ_4K + +#define COPPER_APCS_GCC_PHYS 0xF9011000 +#define COPPER_APCS_GCC_SIZE SZ_4K + +#define COPPER_TLMM_PHYS 0xFD510000 +#define COPPER_TLMM_SIZE SZ_16K + +#ifdef CONFIG_DEBUG_MSMCOPPER_UART +#define MSM_DEBUG_UART_BASE IOMEM(0xFA71E000) +#define MSM_DEBUG_UART_PHYS 0xF991E000 +#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 0000000000000000000000000000000000000000..c30c9e4c9499d1a3ff14930717d0c26ce621eb6e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-fsm9xxx.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 __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_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 + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap.h b/arch/arm/mach-msm/include/mach/msm_iomap.h index 00afdfb8c38fe24eacc2d0c87709fe448b06eed0..75cc43ac070c6821af469572cd2568622d31e5b1 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -37,37 +37,99 @@ * */ -#if defined(CONFIG_ARCH_MSM7X30) +#define MSM_DEBUG_UART_SIZE SZ_4K + +#if defined(CONFIG_DEBUG_MSM_UART1) || defined(CONFIG_DEBUG_MSM_UART2) \ + || defined(CONFIG_DEBUG_MSM_UART3) +#define MSM_DEBUG_UART_BASE 0xFC000000 +#define MSM_DEBUG_UART_PHYS CONFIG_MSM_DEBUG_UART_PHYS +#endif + +#define MSM8625_WARM_BOOT_PHYS 0x0FD00000 + + +#if defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_APQ8064) || \ + defined(CONFIG_ARCH_MSM8930) || defined(CONFIG_ARCH_MSM9615) || \ + defined(CONFIG_ARCH_MSMCOPPER) || defined(CONFIG_ARCH_MSM7X27) || \ + defined(CONFIG_ARCH_MSM7X25) || defined(CONFIG_ARCH_MSM7X01A) || \ + defined(CONFIG_ARCH_MSM8625) || defined(CONFIG_ARCH_MSM7X30) || \ + defined(CONFIG_ARCH_MSM9625) + +/* Unified iomap */ + +#define MSM_TMR_BASE IOMEM(0xFA000000) /* 4K */ +#define MSM_TMR0_BASE IOMEM(0xFA001000) /* 4K */ +#define MSM_QGIC_DIST_BASE IOMEM(0xFA002000) /* 4K */ +#define MSM_QGIC_CPU_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_ACC2_BASE IOMEM(0xFA00D000) /* 4K */ +#define MSM_ACC3_BASE IOMEM(0xFA00E000) /* 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_SHARED_RAM_BASE IOMEM(0xFA300000) /* 2M */ +#define MSM_SIC_NON_SECURE_BASE IOMEM(0xFA600000) /* 64K */ +#define MSM_HDMI_BASE IOMEM(0xFA800000) /* 4K */ +#define MSM_RPM_BASE IOMEM(0xFA801000) /* 4K */ +#define MSM_RPM_MPM_BASE IOMEM(0xFA802000) /* 4K */ +#define MSM_QFPROM_BASE IOMEM(0xFA700000) /* 4K */ +#define MSM_L2CC_BASE IOMEM(0xFA701000) /* 4K */ +#define MSM_APCS_GLB_BASE IOMEM(0xFA702000) /* 4K */ +#define MSM_SAW2_BASE IOMEM(0xFA703000) /* 4k */ +#define MSM_SAW3_BASE IOMEM(0xFA704000) /* 4k */ +#define MSM_VIC_BASE IOMEM(0xFA100000) /* 4K */ +#define MSM_CSR_BASE IOMEM(0xFA101000) /* 4K */ +#define MSM_GPIO1_BASE IOMEM(0xFA102000) /* 4K */ +#define MSM_GPIO2_BASE IOMEM(0xFA103000) /* 4K */ +#define MSM_SCU_BASE IOMEM(0xFA104000) /* 4K */ +#define MSM_CFG_CTL_BASE IOMEM(0xFA105000) /* 4K */ +#define MSM_CLK_CTL_SH2_BASE IOMEM(0xFA106000) /* 4K */ +#define MSM_MDC_BASE IOMEM(0xFA400000) /* 1M */ +#define MSM_AD5_BASE IOMEM(0xFA900000) /* 13M (D00000) + 0xFB600000 */ + +#define MSM_STRONGLY_ORDERED_PAGE 0xFA0F0000 +#define MSM8625_SECONDARY_PHYS 0x0FE00000 + + +#if defined(CONFIG_ARCH_MSM9615) || defined(CONFIG_ARCH_MSM7X27) \ + || defined(CONFIG_ARCH_MSM7X30) +#define MSM_SHARED_RAM_SIZE SZ_1M +#else +#define MSM_SHARED_RAM_SIZE SZ_2M +#endif + +#include "msm_iomap-7xxx.h" #include "msm_iomap-7x30.h" -#elif defined(CONFIG_ARCH_QSD8X50) +#include "msm_iomap-8625.h" +#include "msm_iomap-8960.h" +#include "msm_iomap-8930.h" +#include "msm_iomap-8064.h" +#include "msm_iomap-9615.h" +#include "msm_iomap-copper.h" +#include "msm_iomap-9625.h" + +#else +/* Legacy single-target iomap */ +#if 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" +#error "Target compiled without IO map\n" #endif -#include "msm_iomap-8960.h" - -#define MSM_DEBUG_UART_SIZE SZ_4K -#if defined(CONFIG_DEBUG_MSM_UART1) -#define MSM_DEBUG_UART_BASE 0xE1000000 -#define MSM_DEBUG_UART_PHYS MSM_UART1_PHYS -#elif defined(CONFIG_DEBUG_MSM_UART2) -#define MSM_DEBUG_UART_BASE 0xE1000000 -#define MSM_DEBUG_UART_PHYS MSM_UART2_PHYS -#elif defined(CONFIG_DEBUG_MSM_UART3) -#define MSM_DEBUG_UART_BASE 0xE1000000 -#define MSM_DEBUG_UART_PHYS MSM_UART3_PHYS #endif -/* 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) -#define MSM_GPIO1_BASE IOMEM(0xE0003000) -#define MSM_GPIO2_BASE IOMEM(0xE0004000) - #endif diff --git a/arch/arm/mach-msm/include/mach/msm_ipc_logging.h b/arch/arm/mach-msm/include/mach/msm_ipc_logging.h new file mode 100644 index 0000000000000000000000000000000000000000..0a203a55523b56feb29b66d6c4bafc1ee6c966c5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_ipc_logging.h @@ -0,0 +1,235 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IPC_LOGGING_H +#define _MSM_IPC_LOGGING_H + +#include + +#define MAX_MSG_SIZE 255 + +enum { + TSV_TYPE_MSG_START = 1, + TSV_TYPE_SKB = TSV_TYPE_MSG_START, + TSV_TYPE_STRING, + TSV_TYPE_MSG_END = TSV_TYPE_STRING, +}; + +struct tsv_header { + unsigned char type; + unsigned char size; /* size of data field */ +}; + +struct encode_context { + struct tsv_header hdr; + char buff[MAX_MSG_SIZE]; + int offset; +}; + +struct decode_context { + int output_format; /* 0 = debugfs */ + char *buff; /* output buffer */ + int size; /* size of output buffer */ +}; + +#if defined(CONFIG_MSM_IPC_LOGGING) +/* + * ipc_log_context_create: Create a debug log context + * Should not be called from atomic context + * + * @max_num_pages: Number of pages of logging space required (max. 10) + * @mod_name : Name of the directory entry under DEBUGFS + * + * returns context id on success, NULL on failure + */ +void *ipc_log_context_create(int max_num_pages, const char *modname); + +/* + * msg_encode_start: Start encoding a log message + * + * @ectxt: Temporary storage to hold the encoded message + * @type: Root event type defined by the module which is logging + */ +void msg_encode_start(struct encode_context *ectxt, uint32_t type); + +/* + * tsv_timestamp_write: Writes the current timestamp count + * + * @ectxt: Context initialized by calling msg_encode_start() + */ +int tsv_timestamp_write(struct encode_context *ectxt); + +/* + * tsv_pointer_write: Writes a data pointer + * + * @ectxt: Context initialized by calling msg_encode_start() + * @pointer: Pointer value to write + */ +int tsv_pointer_write(struct encode_context *ectxt, void *pointer); + +/* + * tsv_int32_write: Writes a 32-bit integer value + * + * @ectxt: Context initialized by calling msg_encode_start() + * @n: Integer to write + */ +int tsv_int32_write(struct encode_context *ectxt, int32_t n); + +/* + * tsv_int32_write: Writes a 32-bit integer value + * + * @ectxt: Context initialized by calling msg_encode_start() + * @n: Integer to write + */ +int tsv_byte_array_write(struct encode_context *ectxt, + void *data, int data_size); + +/* + * msg_encode_end: Complete the message encode process + * + * @ectxt: Temporary storage which holds the encoded message + */ +void msg_encode_end(struct encode_context *ectxt); + +/* + * msg_encode_end: Complete the message encode process + * + * @ectxt: Temporary storage which holds the encoded message + */ +void ipc_log_write(void *ctxt, struct encode_context *ectxt); + +/* + * ipc_log_string: Helper function to log a string + * + * @ilctxt: Debug Log Context created using ipc_log_context_create() + * @fmt: Data specified using format specifiers + */ +int ipc_log_string(void *ilctxt, const char *fmt, ...); + +/* + * Print a string to decode context. + * @dctxt Decode context + * @args printf args + */ +#define IPC_SPRINTF_DECODE(dctxt, args...) \ +do { \ + int i; \ + i = scnprintf(dctxt->buff, dctxt->size, args); \ + dctxt->buff += i; \ + dctxt->size -= i; \ +} while (0) + +/* + * tsv_timestamp_read: Reads a timestamp + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_timestamp_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_pointer_read: Reads a data pointer + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_pointer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_int32_read: Reads a 32-bit integer value + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +int32_t tsv_int32_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_int32_read: Reads a 32-bit integer value + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_byte_array_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * add_deserialization_func: Register a deserialization function to + * to unpack the subevents of a main event + * + * @ctxt: Debug log context to which the deserialization function has + * to be registered + * @type: Main/Root event, defined by the module which is logging, to + * which this deserialization function has to be registered. + * @dfune: Deserialization function to be registered + * + * return 0 on success, -ve value on FAILURE + */ +int add_deserialization_func(void *ctxt, int type, + void (*dfunc)(struct encode_context *, + struct decode_context *)); +#else + +void *ipc_log_context_create(int max_num_pages, const char *modname) +{ return NULL; } + +void msg_encode_start(struct encode_context *ectxt, uint32_t type) { } + +int tsv_timestamp_write(struct encode_context *ectxt) +{ return -EINVAL; } + +int tsv_pointer_write(struct encode_context *ectxt, void *pointer) +{ return -EINVAL; } + +int tsv_int32_write(struct encode_context *ectxt, int32_t n) +{ return -EINVAL; } + +int tsv_byte_array_write(struct encode_context *ectxt, + void *data, int data_size) +{ return -EINVAL; } + +void msg_encode_end(struct encode_context *ectxt) { } + +void ipc_log_write(void *ctxt, struct encode_context *ectxt) { } + +int ipc_log_string(void *ilctxt, const char *fmt, ...) +{ return -EINVAL; } + +#define IPC_SPRINTF_DECODE(dctxt, args...) do { } while (0) + +void tsv_timestamp_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +void tsv_pointer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +int32_t tsv_int32_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) +{ return 0; } + +void tsv_byte_array_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +int add_deserialization_func(void *ctxt, int type, + void (*dfunc)(struct encode_context *, + struct decode_context *)) +{ return 0; } + +#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 0000000000000000000000000000000000000000..7afb38d9ed56817321f153e092ef32dfc3a5b0eb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_memtypes.h @@ -0,0 +1,70 @@ +/* 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 +#include + +int __init meminfo_init(unsigned int, unsigned int); +/* Redundant check to prevent this from being included outside of 7x30 */ +#if defined(CONFIG_ARCH_MSM7X30) +unsigned int get_num_populated_chipselects(void); +#endif + +unsigned int get_num_memory_banks(void); +unsigned int get_memory_bank_size(unsigned int); +unsigned int get_memory_bank_start(unsigned int); +int soc_change_memory_power(u64, u64, int); + +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); + void (*reserve_fixed_area)(unsigned long); + int (*paddr_to_memtype)(unsigned int); + unsigned long low_unstable_address; + unsigned long max_unstable_size; + unsigned long bank_size; + unsigned long fixed_area_start; + unsigned long fixed_area_size; +}; + +extern struct reserve_info *reserve_info; + +unsigned long __init reserve_memory_for_fmem(unsigned long, unsigned long); +#endif 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 0000000000000000000000000000000000000000..5812a64fa04d1e5bc1f83af08eeffeb77d79996f --- /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 0000000000000000000000000000000000000000..178b65a2afca0deddf542b13914883bc883c6346 --- /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_pcie.h b/arch/arm/mach-msm/include/mach/msm_pcie.h new file mode 100644 index 0000000000000000000000000000000000000000..008c984ba7e9ebce29a81285d307a9cb8c38ba4c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_pcie.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PCIE_H +#define __ASM_ARCH_MSM_PCIE_H + +#include + +/* gpios */ +enum msm_pcie_gpio { + MSM_PCIE_GPIO_RST_N, + MSM_PCIE_GPIO_PWR_EN, + MSM_PCIE_MAX_GPIO +}; + +/* gpio info structrue */ +struct msm_pcie_gpio_info_t { + char *name; + uint32_t num; + uint32_t on; +}; + +/* msm pcie platfrom data */ +struct msm_pcie_platform { + struct msm_pcie_gpio_info_t *gpio; +}; + +#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 0000000000000000000000000000000000000000..da2abe2413aef9608666966da3fa6d3864680407 --- /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 0000000000000000000000000000000000000000..90a6b568f3104976ff78822d1de530f95128b485 --- /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_rotator_imem.h b/arch/arm/mach-msm/include/mach/msm_rotator_imem.h new file mode 100644 index 0000000000000000000000000000000000000000..580bc81d00a801fdc45f2fa4b876f6d3e5a859af --- /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 0000000000000000000000000000000000000000..28841a9198ff06da92287fc03feb3be4a1d1eb6d --- /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; + spinlock_t 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_rtb.h b/arch/arm/mach-msm/include/mach/msm_rtb.h new file mode 100644 index 0000000000000000000000000000000000000000..74ddfbdeecabee7595cdd166b11d439ac7e80746 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_rtb.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_RTB_H__ +#define __MSM_RTB_H__ + +/* + * These numbers are used from the kernel command line and sysfs + * to control filtering. Remove items from here with extreme caution. + */ +enum logk_event_type { + LOGK_NONE = 0, + LOGK_READL = 1, + LOGK_WRITEL = 2, + LOGK_LOGBUF = 3, + LOGK_HOTPLUG = 4, + LOGK_CTXID = 5, + LOGK_TIMESTAMP = 6, +}; + +#define LOGTYPE_NOPC 0x80 + +struct msm_rtb_platform_data { + unsigned int size; +}; + +#if defined(CONFIG_MSM_RTB) +/* + * returns 1 if data was logged, 0 otherwise + */ +int uncached_logk_pc(enum logk_event_type log_type, void *caller, + void *data); + +/* + * returns 1 if data was logged, 0 otherwise + */ +int uncached_logk(enum logk_event_type log_type, void *data); + +#define ETB_WAYPOINT do { \ + BRANCH_TO_NEXT_ISTR; \ + nop(); \ + BRANCH_TO_NEXT_ISTR; \ + nop(); \ + } while (0) + +#define BRANCH_TO_NEXT_ISTR asm volatile("b .+4\n" : : : "memory") +/* + * both the mb and the isb are needed to ensure enough waypoints for + * etb tracing + */ +#define LOG_BARRIER do { \ + mb(); \ + isb();\ + } while (0) +#else + +static inline int uncached_logk_pc(enum logk_event_type log_type, + void *caller, + void *data) { return 0; } + +static inline int uncached_logk(enum logk_event_type log_type, + void *data) { return 0; } + +#define ETB_WAYPOINT +#define BRANCH_TO_NEXT_ISTR +/* + * Due to a GCC bug, we need to have a nop here in order to prevent an extra + * read from being generated after the write. + */ +#define LOG_BARRIER nop() +#endif +#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 0000000000000000000000000000000000000000..f490b1be4f21a8e58acca3242078e8dcdb1a795e --- /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 0000000000000000000000000000000000000000..b96640d10030f765f6b33207ad0daa6a83a658ac --- /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 0000000000000000000000000000000000000000..577a09789d87eda407d7349c77d436c2e6f7abb4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_serial_hs_lite.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 __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; + int line; +}; + +#endif + diff --git a/arch/arm/mach-msm/include/mach/msm_serial_hsl_regs.h b/arch/arm/mach-msm/include/mach/msm_serial_hsl_regs.h new file mode 100644 index 0000000000000000000000000000000000000000..b465b562a666d048e75489dfcbad75c63cd5ccd1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_serial_hsl_regs.h @@ -0,0 +1,35 @@ +/* + * 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 __ASM_ARCH_MSM_SERIAL_HSL_REGS_H +#define __ASM_ARCH_MSM_SERIAL_HSL_REGS_H + +#ifdef CONFIG_MSM_HAS_DEBUG_UART_HS_V14 +#define UARTDM_MR2_OFFSET 0x4 +#define UARTDM_CSR_OFFSET 0xa0 +#define UARTDM_SR_OFFSET 0xa4 +#define UARTDM_CR_OFFSET 0xa8 +#define UARTDM_ISR_OFFSET 0xb4 +#define UARTDM_NCF_TX_OFFSET 0x40 +#define UARTDM_TF_OFFSET 0x100 +#else +#define UARTDM_MR2_OFFSET 0x4 +#define UARTDM_CSR_OFFSET 0x8 +#define UARTDM_SR_OFFSET 0x8 +#define UARTDM_CR_OFFSET 0x10 +#define UARTDM_ISR_OFFSET 0x14 +#define UARTDM_NCF_TX_OFFSET 0x40 +#define UARTDM_TF_OFFSET 0x70 +#endif + +#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 0000000000000000000000000000000000000000..4153cb2080e3a8b645f0ca83db0bb31ec373dd29 --- /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 029463ec8756fb7d42fa0fdd72d4581a42093a0a..dc633fbbf4a0064fc28ac2e9e9181798a963ef1e 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-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -17,22 +18,155 @@ #ifndef __ASM_ARCH_MSM_SMD_H #define __ASM_ARCH_MSM_SMD_H -typedef struct smd_channel smd_channel_t; +#include +#include -extern int (*msm_check_for_modem_crash)(void); +typedef struct smd_channel smd_channel_t; -/* warning: notify() may be called before open returns */ -int smd_open(const char *name, smd_channel_t **ch, void *priv, - void (*notify)(void *priv, unsigned event)); +#define SMD_MAX_CH_NAME_LEN 20 /* includes null char at end */ #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 5 + +/* + * SMD Processor ID's. + * + * For all processors that have both SMSM and SMD clients, + * the SMSM Processor ID and the SMD Processor ID will + * be the same. In cases where a processor only supports + * SMD, the entry will only exist in this enum. + */ +enum { + SMD_APPS = SMSM_APPS, + SMD_MODEM = SMSM_MODEM, + SMD_Q6 = SMSM_Q6, + SMD_WCNSS = SMSM_WCNSS, + SMD_DSPS = SMSM_DSPS, + SMD_MODEM_Q6_FW, + SMD_RPM, + NUM_SMD_SUBSYSTEMS, +}; + +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_APPS_Q6FW, + SMD_MODEM_Q6FW, + SMD_QDSP_Q6FW, + SMD_DSPS_Q6FW, + SMD_WCNSS_Q6FW, + SMD_APPS_RPM, + SMD_MODEM_RPM, + SMD_QDSP_RPM, + SMD_WCNSS_RPM, + SMD_NUM_TYPE, + SMD_LOOPBACK_TYPE = 100, + +}; + +/* + * SMD IRQ Configuration + * + * Used to initialize IRQ configurations from platform data + * + * @irq_name: irq_name to query platform data + * @irq_id: initialized to -1 in platform data, stores actual irq id on + * successful registration + * @out_base: if not null then settings used for outgoing interrupt + * initialied from platform data + */ + +struct smd_irq_config { + /* incoming interrupt config */ + const char *irq_name; + unsigned long flags; + int irq_id; + const char *device_name; + const void *dev_id; + + /* outgoing interrupt config */ + uint32_t out_bit_pos; + void __iomem *out_base; + uint32_t out_offset; +}; + +/* + * SMD subsystem configurations + * + * SMD subsystems configurations for platform data. This contains the + * M2A and A2M interrupt configurations for both SMD and SMSM per + * subsystem. + * + * @subsys_name: name of subsystem passed to PIL + * @irq_config_id: unique id for each subsystem + * @edge: maps to actual remote subsystem edge + * + */ +struct smd_subsystem_config { + unsigned irq_config_id; + const char *subsys_name; + int edge; + + struct smd_irq_config smd_int; + struct smd_irq_config smsm_int; + +}; + +/* + * Subsystem Restart Configuration + * + * @disable_smsm_reset_handshake + */ +struct smd_subsystem_restart_config { + int disable_smsm_reset_handshake; +}; + +/* + * Shared Memory Regions + * + * the array of these regions is expected to be in ascending order by phys_addr + * + * @phys_addr: physical base address of the region + * @size: size of the region in bytes + */ +struct smd_smem_regions { + void *phys_addr; + unsigned size; +}; + +struct smd_platform { + uint32_t num_ss_configs; + struct smd_subsystem_config *smd_ss_configs; + struct smd_subsystem_restart_config *smd_ssr_config; + uint32_t num_smem_areas; + struct smd_smem_regions *smd_smem_areas; +}; + +#ifdef CONFIG_MSM_SMD +/* warning: notify() may be called before open returns */ +int smd_open(const char *name, smd_channel_t **ch, void *priv, + void (*notify)(void *priv, unsigned event)); 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 +174,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 +187,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 +196,234 @@ 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); +int +smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear); +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); + +/* + * Returns a pointer to the subsystem name or NULL if no + * subsystem name is available. + * + * @type - Edge definition + */ +const char *smd_edge_to_subsystem(uint32_t type); + +/* + * Returns a pointer to the subsystem name given the + * remote processor ID. + * + * @pid Remote processor ID + * @returns Pointer to subsystem name or NULL if not found + */ +const char *smd_pid_to_subsystem(uint32_t pid); + +/* + * Checks to see if a new packet has arrived on the channel. Only to be + * called with interrupts disabled. + * + * @ch: channel to check if a packet has arrived + * + * Returns: + * 0 - packet not available + * 1 - packet available + * -EINVAL - NULL parameter or non-packet based channel provided + */ +int smd_is_pkt_avail(smd_channel_t *ch); + +/* + * SMD initialization function that registers for a SMD platform driver. + * + * returns success on successful driver registration. + */ +int __init msm_smd_init(void); + +#else + +static inline int smd_open(const char *name, smd_channel_t **ch, void *priv, + void (*notify)(void *priv, unsigned event)) +{ + return -ENODEV; +} + +static inline int smd_close(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_read(smd_channel_t *ch, void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_read_from_cb(smd_channel_t *ch, void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_read_user_buffer(smd_channel_t *ch, void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_write(smd_channel_t *ch, const void *data, int len) +{ + return -ENODEV; +} + +static inline int +smd_write_user_buffer(smd_channel_t *ch, const void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_write_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_read_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_cur_packet_size(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_tiocmget(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int +smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + return -ENODEV; +} + +static inline int +smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + return -ENODEV; +} + +static inline int +smd_named_open_on_edge(const char *name, uint32_t edge, smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)) +{ + return -ENODEV; +} + +static inline void smd_enable_read_intr(smd_channel_t *ch) +{ +} + +static inline void smd_disable_read_intr(smd_channel_t *ch) +{ +} + +static inline int smd_write_start(smd_channel_t *ch, int len) +{ + return -ENODEV; +} + +static inline int +smd_write_segment(smd_channel_t *ch, void *data, int len, int user_buf) +{ + return -ENODEV; +} + +static inline int smd_write_end(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline const char *smd_edge_to_subsystem(uint32_t type) +{ + return NULL; +} + +static inline const char *smd_pid_to_subsystem(uint32_t pid) +{ + return NULL; +} + +static inline int smd_is_pkt_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int __init msm_smd_init(void) +{ + return 0; +} +#endif #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 0000000000000000000000000000000000000000..fbb8502e6731ca293474a6ea11b5775fa563aec1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_smsm.h @@ -0,0 +1,259 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ + +#include +#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_DSPS_STATE = 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_LTE_COEX_AWAKE 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_FORCE_SHUTDOWN 0x00002000 +#define SMSM_A2_RESET_BAM 0x00004000 + +#define SMSM_VENDOR 0x00020000 + +#define SMSM_A2_POWER_CONTROL 0x00000002 +#define SMSM_A2_POWER_CONTROL_ACK 0x00000800 + +#define SMSM_WLAN_TX_RINGS_EMPTY 0x00000200 +#define SMSM_WLAN_TX_ENABLE 0x00000400 + +#define SMSM_ERR_SRV_READY 0x00008000 + +#ifdef CONFIG_MSM_SMD +void *smem_alloc(unsigned id, unsigned size); +#else +void *smem_alloc(unsigned id, unsigned size) +{ + return NULL; +} +#endif +void *smem_alloc2(unsigned id, unsigned size_in); +void *smem_get_entry(unsigned id, unsigned *size); +int smsm_change_state(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask); + +/* + * Changes the global interrupt mask. The set and clear masks are re-applied + * every time the global interrupt mask is updated for callback registration + * and de-registration. + * + * The clear mask is applied first, so if a bit is set to 1 in both the clear + * mask and the set mask, the result will be that the interrupt is set. + * + * @smsm_entry SMSM entry to change + * @clear_mask 1 = clear bit, 0 = no-op + * @set_mask 1 = set bit, 0 = no-op + * + * @returns 0 for success, < 0 for error + */ +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); +int smsm_driver_state_notifier_register(struct notifier_block *nb); +int smsm_driver_state_notifier_unregister(struct notifier_block *nb); +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_SMD_LOOPBACK_REGISTER, + SMEM_SSR_REASON_MSS0, + SMEM_SSR_REASON_WCNSS0, + SMEM_SSR_REASON_LPASS0, + SMEM_SSR_REASON_DSPS0, + SMEM_SSR_REASON_VCODEC0, + SMEM_MEM_LAST = SMEM_SSR_REASON_VCODEC0, + 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 0000000000000000000000000000000000000000..51081b67610ad5bbd4cc7a14510288908a2a25f2 --- /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 0000000000000000000000000000000000000000..3af6f71acdc097ed79bbbb1cbae2c6d3ea635982 --- /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 0000000000000000000000000000000000000000..ebb23272058cb338f87c6f20bf09b47b5ed3eb98 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_subsystem_map.h @@ -0,0 +1,83 @@ +/* + * 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 +/* + * Will map 2x the length requested. + */ +#define MSM_SUBSYSTEM_MAP_IOMMU_2X 0x10 + +/* + * Shortcut flags for alignment. + * The flag must be equal to the alignment requested. + * e.g. for 8k alignment the flags must be (0x2000 | other flags) + */ +#define MSM_SUBSYSTEM_ALIGN_IOVA_8K SZ_8K +#define MSM_SUBSYSTEM_ALIGN_IOVA_1M SZ_1M + + +enum msm_subsystem_id { + INVALID_SUBSYS_ID = -1, + MSM_SUBSYSTEM_VIDEO, + MSM_SUBSYSTEM_VIDEO_FWARE, + MSM_SUBSYSTEM_CAMERA, + MSM_SUBSYSTEM_DISPLAY, + MSM_SUBSYSTEM_ROTATOR, + MAX_SUBSYSTEM_ID +}; + +static inline int msm_subsystem_check_id(int subsys_id) +{ + return subsys_id > INVALID_SUBSYS_ID && subsys_id < MAX_SUBSYSTEM_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 0000000000000000000000000000000000000000..763d6a8f11137848c30ade4300a44fd4e2c66f1d --- /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 0000000000000000000000000000000000000000..4b2d537abd6a03342e162ee4005fe912b65d9781 --- /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 0000000000000000000000000000000000000000..62595e3ef9ddc9b1a1da5c171a1f02aea52e8b11 --- /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_tspp.h b/arch/arm/mach-msm/include/mach/msm_tspp.h new file mode 100644 index 0000000000000000000000000000000000000000..6912f0caf9eb7a8df2887e5540cececf7aadb596 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_tspp.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 _MSM_TSPP_H_ +#define _MSM_TSPP_H_ + +struct msm_tspp_platform_data { + int num_gpios; + const struct msm_gpio *gpios; + const char *tsif_pclk; + const char *tsif_ref_clk; +}; + +#endif /* _MSM_TSPP_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 0000000000000000000000000000000000000000..f9795b440f4a7bc632cb1593b78cc1129448a50c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_xo.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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, + 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/ocmem.h b/arch/arm/mach-msm/include/mach/ocmem.h new file mode 100644 index 0000000000000000000000000000000000000000..bf7c3386e473d7c52c0cf66eb637b5b6d37e782a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/ocmem.h @@ -0,0 +1,109 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_OCMEM_H +#define _ARCH_ARM_MACH_MSM_OCMEM_H + +#include +#include +#include + +#define OCMEM_MIN_ALLOC SZ_64K +#define OCMEM_MIN_ALIGN SZ_64K + +/* Maximum number of slots in DM */ +#define OCMEM_MAX_CHUNKS 32 +#define MIN_CHUNK_SIZE (SZ_1K/8) + +struct ocmem_buf { + unsigned long addr; + unsigned long len; +}; + +struct ocmem_buf_attr { + unsigned long paddr; + unsigned long len; +}; + +struct ocmem_chunk { + bool ro; + unsigned long ddr_paddr; + unsigned long size; +}; + +struct ocmem_map_list { + int num_chunks; + struct ocmem_chunk chunks[OCMEM_MAX_CHUNKS]; +}; + +/* List of clients that allocate/interact with OCMEM */ +/* Must be in sync with client_names */ +enum ocmem_client { + /* GMEM clients */ + OCMEM_GRAPHICS = 0x0, + /* TCMEM clients */ + OCMEM_VIDEO, + OCMEM_CAMERA, + /* Dummy Clients */ + OCMEM_HP_AUDIO, + OCMEM_VOICE, + /* IMEM Clients */ + OCMEM_LP_AUDIO, + OCMEM_SENSORS, + OCMEM_BLAST, + OCMEM_CLIENT_MAX, +}; + +/** + * List of OCMEM notification events which will be broadcasted + * to clients that optionally register for these notifications + * on a per allocation basis. + **/ +enum ocmem_notif_type { + OCMEM_MAP_DONE = 1, + OCMEM_MAP_FAIL, + OCMEM_UNMAP_DONE, + OCMEM_UNMAP_FAIL, + OCMEM_ALLOC_GROW, + OCMEM_ALLOC_SHRINK, + OCMEM_NOTIF_TYPE_COUNT, +}; + +/* APIS */ +/* Notification APIs */ +void *ocmem_notifier_register(int client_id, struct notifier_block *nb); + +int ocmem_notifier_unregister(void *notif_hndl, struct notifier_block *nb); + +/* Allocation APIs */ +struct ocmem_buf *ocmem_allocate(int client_id, unsigned long size); + +struct ocmem_buf *ocmem_allocate_nb(int client_id, unsigned long size); + +struct ocmem_buf *ocmem_allocate_range(int client_id, unsigned long min, + unsigned long goal, unsigned long step); + +/* Free APIs */ +int ocmem_free(int client_id, struct ocmem_buf *buf); + +/* Dynamic Resize APIs */ +int ocmem_shrink(int client_id, struct ocmem_buf *buf, + unsigned long new_size); + +int ocmem_expand(int client_id, struct ocmem_buf *buf, + unsigned long new_size); + +/* Priority Enforcement APIs */ +int ocmem_evict(int client_id); + +int ocmem_restore(int client_id); +#endif diff --git a/arch/arm/mach-msm/include/mach/ocmem_priv.h b/arch/arm/mach-msm/include/mach/ocmem_priv.h new file mode 100644 index 0000000000000000000000000000000000000000..daf32a530af71bcf51320e2ef5622c03e6ab4052 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/ocmem_priv.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_OCMEM_CORE_H +#define _ARCH_ARM_MACH_MSM_OCMEM_CORE_H + +/** All interfaces in this header should only be used by OCMEM driver + * Client drivers should use wrappers available in ocmem.h + **/ + +#include "ocmem.h" +#include +#include + +#define OCMEM_PHYS_BASE 0xFEC00000 +#define OCMEM_PHYS_SIZE 0x180000 + +struct ocmem_zone; + +struct ocmem_zone_ops { + unsigned long (*allocate) (struct ocmem_zone *, unsigned long); + int (*free) (struct ocmem_zone *, unsigned long, unsigned long); +}; + +struct ocmem_zone { + int owner; + int active_regions; + int max_regions; + struct list_head region_list; + unsigned long z_start; + unsigned long z_end; + unsigned long z_head; + unsigned long z_tail; + unsigned long z_free; + struct gen_pool *z_pool; + struct ocmem_zone_ops *z_ops; +}; + +struct ocmem_req { + struct rw_semaphore rw_sem; + /* Chain in sched queue */ + struct list_head sched_list; + /* Chain in zone list */ + struct list_head zone_list; + int owner; + int prio; + uint32_t req_id; + unsigned long req_min; + unsigned long req_max; + unsigned long req_step; + /* reverse pointers */ + struct ocmem_zone *zone; + struct ocmem_buf *buffer; + unsigned long state; + /* Request assignments */ + unsigned long req_start; + unsigned long req_end; + unsigned long req_sz; +}; + +struct ocmem_handle { + struct ocmem_buf buffer; + struct mutex handle_mutex; + struct ocmem_req *req; +}; + +struct ocmem_zone *get_zone(unsigned); +unsigned long allocate_head(struct ocmem_zone *, unsigned long); +int free_head(struct ocmem_zone *, unsigned long, unsigned long); +unsigned long allocate_tail(struct ocmem_zone *, unsigned long); +int free_tail(struct ocmem_zone *, unsigned long, unsigned long); +#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 0000000000000000000000000000000000000000..d7a2416fff82e6a2d13b794cb126820cbd515ead --- /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 0000000000000000000000000000000000000000..327c82f8fd573a89115d6a6a9fba030090f4683e --- /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 0000000000000000000000000000000000000000..b143a59b2af0ecfb26d235a0f0b9351b1c48f482 --- /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/proc_comm.h b/arch/arm/mach-msm/include/mach/proc_comm.h similarity index 61% rename from arch/arm/mach-msm/proc_comm.h rename to arch/arm/mach-msm/include/mach/proc_comm.h index 12da4cacd4a8141b7119fe9335cf3eafaee40ce1..8a0a2181802a30f5e8d9d69630900a168e324ffc 100644 --- a/arch/arm/mach-msm/proc_comm.h +++ b/arch/arm/mach-msm/include/mach/proc_comm.h @@ -1,6 +1,6 @@ -/* arch/arm/mach-msm/proc_comm.h +/* arch/arm/mach-msm/include/mach/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/include/mach/qdsp5/qdsp5audplaycmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaycmdi.h new file mode 100644 index 0000000000000000000000000000000000000000..575a286288a2b15e24a3415a645f0f8cb4c5cdf0 --- /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 0000000000000000000000000000000000000000..0bf2468f480b7f7611a0792e54e2073c394666cc --- /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 0000000000000000000000000000000000000000..86216d4c282526f4cb1452c5ba9c5e4f2676de3f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppcmdi.h @@ -0,0 +1,1037 @@ +#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, 2012 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 + +#define SRS_PARAMS_MAX_G 8 +#define SRS_PARAMS_MAX_W 55 +#define SRS_PARAMS_MAX_C 51 +#define SRS_PARAMS_MAX_H 53 +#define SRS_PARAMS_MAX_P 116 +#define SRS_PARAMS_MAX_L 8 + +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; + +/* + * Command Structure to configure post processing params (SRS TruMedia) + */ +struct audpp_cmd_cfg_object_params_srstm_g { + audpp_cmd_cfg_object_params_common common; + unsigned short v[SRS_PARAMS_MAX_G]; +} __packed; +struct audpp_cmd_cfg_object_params_srstm_w { + audpp_cmd_cfg_object_params_common common; + unsigned short v[SRS_PARAMS_MAX_W]; +} __packed; +struct audpp_cmd_cfg_object_params_srstm_c { + audpp_cmd_cfg_object_params_common common; + unsigned short v[SRS_PARAMS_MAX_C]; +} __packed; +struct audpp_cmd_cfg_object_params_srstm_h { + audpp_cmd_cfg_object_params_common common; + unsigned short v[SRS_PARAMS_MAX_H]; +} __packed; +struct audpp_cmd_cfg_object_params_srstm_p { + audpp_cmd_cfg_object_params_common common; + unsigned short v[SRS_PARAMS_MAX_P]; +} __packed; +struct audpp_cmd_cfg_object_params_srstm_l { + audpp_cmd_cfg_object_params_common common; + unsigned short v[SRS_PARAMS_MAX_L]; +} __packed; + +#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 0000000000000000000000000000000000000000..0ba8261582e4d328be5463e864da05a20c829c10 --- /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 0000000000000000000000000000000000000000..234c4ac869a8b6c867967ae7e3284436944592c1 --- /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 0000000000000000000000000000000000000000..8efc916c333e9404ebf1bc930148342d8846543e --- /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 0000000000000000000000000000000000000000..0696066f91b3979473a9265ef080533ec4a26591 --- /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 0000000000000000000000000000000000000000..5045de05aea38fd90d07589a75f6c99ecc38fb0a --- /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 0000000000000000000000000000000000000000..339e4f7dafaf42da5d3ff4e2aa2fe01b015ade4e --- /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 0000000000000000000000000000000000000000..7f25f4703c42e71056dc71b99a36039238f39483 --- /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 0000000000000000000000000000000000000000..993af420a86a09d132ddf3c8306af1175a89fc10 --- /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 0000000000000000000000000000000000000000..4ab6cbf44a7ed07a01e88cc96c24129e0fcd7f81 --- /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 0000000000000000000000000000000000000000..68f8874ab93e3d1c1c9c7f60a2a58fdd61d23887 --- /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 0000000000000000000000000000000000000000..7a66b687d3c7874a55d130b40ffcfed022b06796 --- /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 0000000000000000000000000000000000000000..a890e76f5f5f0ccacb8a55cdf12a404d2d5180dd --- /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 0000000000000000000000000000000000000000..1064b1762651565a297452b55d76ad3f8b7c5e44 --- /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 0000000000000000000000000000000000000000..2d3ab89e9861ddb95f00b1d4c3daee1b0ef37083 --- /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 0000000000000000000000000000000000000000..b3c018fc31a30c0084c7d185379f8efc915b7061 --- /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 0000000000000000000000000000000000000000..4c5d752ab42b14f127fde61a6d48aab214189cf0 --- /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 0000000000000000000000000000000000000000..a628f922b2c3210b929504db43aea0c1e5add7fb --- /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 0000000000000000000000000000000000000000..bf1714e0086030d6b5d5745dab53c524108e727f --- /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 0000000000000000000000000000000000000000..2e6fcdb0aee44b0ce1b570b47239d73556d575d1 --- /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 0000000000000000000000000000000000000000..919da65d23ecf7175a841ca0a35dbec0d77daf64 --- /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 0000000000000000000000000000000000000000..c15faccf57170823255bb3e436a87397f3f32a51 --- /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 0000000000000000000000000000000000000000..a2a15dcadec545887f41d29ba776e70af16299c7 --- /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 0000000000000000000000000000000000000000..559073ce74794d277ea54bd621d14d1b56355023 --- /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 0000000000000000000000000000000000000000..236c6f68bdb3d5a91d928cffd7b544dd1fbad2ef --- /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 0000000000000000000000000000000000000000..7c0abcc97a930dc622137ab3aa8459dd32e5648a --- /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 0000000000000000000000000000000000000000..2a7b89ec2217ed22426854e2504876fae2dbcdcf --- /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 0000000000000000000000000000000000000000..bdec256e2b19bbdaaa073b1ff10fb3e83964223f --- /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 0000000000000000000000000000000000000000..6abeae120cf43dda47e12a26985168217a62baf6 --- /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 0000000000000000000000000000000000000000..100ddea35ab6d31d297a5c6b339dd48d92db8310 --- /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 0000000000000000000000000000000000000000..92dfe12330475a3623faf572b55fa5fc4b7d4ff5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/codec_utils.h @@ -0,0 +1,139 @@ +/* Copyright (c) 2010, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; +}; +struct audio; + +#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; + uint32_t buffer_size; + uint32_t buffer_count; +}; + +#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 0000000000000000000000000000000000000000..d71cf728080075c21a886a4225fbcc646499b5aa --- /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 0000000000000000000000000000000000000000..bfff38473507944c62ddca308a5013ad96363e71 --- /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 0000000000000000000000000000000000000000..c1cb3fe35fd980fc2608a5160da92cd7cbfc05db --- /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 0000000000000000000000000000000000000000..e304e2541330097ec5531b4676bd4f1b668f0e26 --- /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 0000000000000000000000000000000000000000..ac06d3b28396ea685076499ee2c94715623f5600 --- /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 0000000000000000000000000000000000000000..0dced94bc5431b1da14a6ab8ecf232e52eec7cdb --- /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 0000000000000000000000000000000000000000..fa650005d1dc2d9caee962121679bdce23decc20 --- /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 0000000000000000000000000000000000000000..25fe3a01c07bb7797efa092fc218af94897942f5 --- /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 0000000000000000000000000000000000000000..16134e3f60e5f3e076442e2878eeada1bf177dcb --- /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 0000000000000000000000000000000000000000..53128d37154ec6d01e8adc53908b41e3c31099dc --- /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 0000000000000000000000000000000000000000..2eeb5576ab90b9cdd59f000b559aae4b0a23ac51 --- /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 0000000000000000000000000000000000000000..0416f528633e31cf0389c058e5596337919456ed --- /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 0000000000000000000000000000000000000000..b27bd8351ebe51eebb4b0e52bcb6623a7b2d9664 --- /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 0000000000000000000000000000000000000000..f579e1a3d31cd6e25aff162aba87db7ba9b62cf1 --- /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 0000000000000000000000000000000000000000..29da664544c1cb86274877144a2f5581a6f31e3d --- /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 0000000000000000000000000000000000000000..9ba8645ae06b44236d7b20b758d908bf5e4fd2d3 --- /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 0000000000000000000000000000000000000000..32ccbbc42d4c38805d7457fe576d1cff79181a7c --- /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 0000000000000000000000000000000000000000..35c1edbe10eb0ea263d19ba0afd0837cb1e93d60 --- /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 0000000000000000000000000000000000000000..7f9938e34818fd03d2533cd18d3c26d3ff5b7a77 --- /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 0000000000000000000000000000000000000000..cd834a588847f461eabb681134ae74c7152194b6 --- /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 0000000000000000000000000000000000000000..695b19d05b4322fb93cbcbde3b5e9a8c68643323 --- /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 0000000000000000000000000000000000000000..5ca2d6d11e495f0880b6af24d0c04656156426c5 --- /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 0000000000000000000000000000000000000000..62d7a337e884e7e6229a4f68b8e10f6bbf9ebdbb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/apr.h @@ -0,0 +1,152 @@ +/* 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_USM 0x0C +#define APR_SVC_MAX 0x0D + +/* 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 0000000000000000000000000000000000000000..163ba7b7463ba1ab8d01690b3643282b3c3d3502 --- /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/apr_us.h b/arch/arm/mach-msm/include/mach/qdsp6v2/apr_us.h new file mode 100644 index 0000000000000000000000000000000000000000..487e81477f9eef8583f32f25e8fb174d3731a586 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/apr_us.h @@ -0,0 +1,154 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_US_H__ +#define __APR_US_H__ + +#include + +/* ======================================================================= */ +/* Session Level commands */ +#define USM_SESSION_CMD_MEMORY_MAP 0x00012304 +struct usm_stream_cmd_memory_map { + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u16 mempool_id; + u16 reserved; +} __packed; + +#define USM_SESSION_CMD_MEMORY_UNMAP 0x00012305 +struct usm_stream_cmd_memory_unmap { + struct apr_hdr hdr; + u32 buf_add; +} __packed; + +#define USM_SESSION_CMD_RUN 0x00012306 +struct usm_stream_cmd_run { + struct apr_hdr hdr; + u32 flags; + u32 msw_ts; + u32 lsw_ts; +} __packed; + +/* Stream level commands */ +#define USM_STREAM_CMD_OPEN_READ 0x00012309 +struct usm_stream_cmd_open_read { + struct apr_hdr hdr; + u32 uMode; + u32 src_endpoint; + u32 pre_proc_top; + u32 format; +} __packed; + +#define USM_STREAM_CMD_OPEN_WRITE 0x00011271 +struct usm_stream_cmd_open_write { + struct apr_hdr hdr; + u32 format; +} __packed; + + +#define USM_STREAM_CMD_CLOSE 0x0001230A + +/* Encoder configuration definitions */ +#define USM_STREAM_CMD_SET_ENC_PARAM 0x0001230B +/* Decoder configuration definitions */ +#define USM_DATA_CMD_MEDIA_FORMAT_UPDATE 0x00011272 + +/* Encoder/decoder configuration block */ +#define USM_PARAM_ID_ENCDEC_ENC_CFG_BLK 0x0001230D + +/* Parameter structures used in USM_STREAM_CMD_SET_ENCDEC_PARAM command */ +/* common declarations */ +struct usm_cfg_common { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; + u32 dev_id; + u32 data_map; +} __packed; + +/* Max number of static located transparent data (bytes) */ +#define USM_MAX_CFG_DATA_SIZE 20 +struct usm_encode_cfg_blk { + u32 frames_per_buf; + u32 format_id; + /* = sizeof(usm_cfg_common)+|tarnsp_data| */ + u32 cfg_size; + struct usm_cfg_common cfg_common; + /* Transparent configuration data for specific encoder */ + u8 transp_data[USM_MAX_CFG_DATA_SIZE]; +} __packed; + +struct usm_stream_cmd_encdec_cfg_blk { + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct usm_encode_cfg_blk enc_blk; +} __packed; + +struct us_encdec_cfg { + u32 format_id; + struct usm_cfg_common cfg_common; + u16 params_size; + u8 *params; +} __packed; + +struct usm_stream_media_format_update { + struct apr_hdr hdr; + u32 format_id; + /* = sizeof(usm_cfg_common)+|tarnsp_data| */ + u32 cfg_size; + struct usm_cfg_common cfg_common; + /* Transparent configuration data for specific encoder */ + u8 transp_data[USM_MAX_CFG_DATA_SIZE]; +} __packed; + + +#define USM_DATA_CMD_READ 0x0001230E +struct usm_stream_cmd_read { + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u32 uid; + u32 counter; +} __packed; + +#define USM_DATA_EVENT_READ_DONE 0x0001230F + +#define USM_DATA_CMD_WRITE 0x00011273 +struct usm_stream_cmd_write { + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u32 uid; + u32 msw_ts; + u32 lsw_ts; + u32 flags; +} __packed; + +#define USM_DATA_EVENT_WRITE_DONE 0x00011274 + +/* Start/stop US signal detection */ +#define USM_SESSION_CMD_SIGNAL_DETECT_MODE 0x00012719 + +struct usm_session_cmd_detect_info { + struct apr_hdr hdr; + u32 detect_mode; + u32 skip_interval; + u32 algorithm_cfg_size; +} __packed; + +/* US signal detection result */ +#define USM_SESSION_EVENT_SIGNAL_DETECT_RESULT 0x00012720 + +#endif /* __APR_US_H__ */ 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 0000000000000000000000000000000000000000..a55dee6c4315fa3d0997a85a3622e6821624a12c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/audio_acdb.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +enum { + RX_CAL, + TX_CAL, + MAX_AUDPROC_TYPES +}; + +struct acdb_cal_block { + uint32_t cal_size; + uint32_t cal_kvaddr; + uint32_t cal_paddr; +}; + +struct acdb_atomic_cal_block { + atomic_t cal_size; + atomic_t cal_kvaddr; + atomic_t cal_paddr; +}; + +struct acdb_cal_data { + uint32_t num_cal_blocks; + struct acdb_atomic_cal_block *cal_blocks; +}; + +uint32_t get_voice_rx_topology(void); +uint32_t get_voice_tx_topology(void); +uint32_t get_adm_rx_topology(void); +uint32_t get_adm_tx_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_afe_cal(int32_t path, struct acdb_cal_block *cal_block); +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 0000000000000000000000000000000000000000..20c6fc4a7565dfbb594cf7f60080d6750c8c3f6b --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/audio_dev_ctl.h @@ -0,0 +1,221 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 COPP_IGNORE 0xffffffff +#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 reset_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; + u32 voice_session_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; + u32 voice_session_id; +}; + +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, u32 session_id); +int msm_set_voice_mute(int dir, int mute, u32 session_id); +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/scm.h b/arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h similarity index 62% rename from arch/arm/mach-msm/scm.h rename to arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h index 00b31ea58f299720f6c4d8cf36786ae582c20509..94f4ab46936cfded95cde18488617e726d807184 100644 --- a/arch/arm/mach-msm/scm.h +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h @@ -8,18 +8,15 @@ * but WITHOUT ANY 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 - -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); +#ifndef __DSP_DEBUG_H_ +#define __DSP_DEBUG_H_ -#define SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF)) +typedef int (*dsp_state_cb)(int state); +int dsp_debug_register(dsp_state_cb ptr); -extern u32 scm_get_version(void); +#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 0000000000000000000000000000000000000000..674cfe835b167850f385c259f4718a8bcb9bf66f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/q6voice.h @@ -0,0 +1,778 @@ +/* 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 642 + +#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_13K_MODEM 0x00010FC1 +/* Qcelp vocoder modem format */ +#define VSS_MEDIA_ID_EVRC_MODEM 0x00010FC2 +/* 80-VF690-47 CDMA enhanced variable rate vocoder modem format. */ +#define VSS_MEDIA_ID_4GV_NB_MODEM 0x00010FC3 +/* 4GV Narrowband modem format */ +#define VSS_MEDIA_ID_4GV_WB_MODEM 0x00010FC4 +/* 4GV Wideband 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_EFR_MODEM 0x00010FC8 +/*EFR modem format */ +#define VSS_MEDIA_ID_FR_MODEM 0x00010FC9 +/*FR modem format */ +#define VSS_MEDIA_ID_HR_MODEM 0x00010FCA +/*HR modem format */ +#define VSS_MEDIA_ID_PCM_NB 0x00010FCB +/* Linear PCM (16-bit, little-endian). */ +#define VSS_MEDIA_ID_PCM_WB 0x00010FCC +/* Linear wideband PCM vocoder modem format (16 bits, 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 q_min_max_rate { + uint32_t min_rate; + uint32_t max_rate; +}; + +struct mvs_driver_info { + uint32_t media_type; + uint32_t rate; + uint32_t network_type; + uint32_t dtx_mode; + struct q_min_max_rate q_min_max_rate; + 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 */ + + wait_queue_head_t mvm_wait; + wait_queue_head_t cvs_wait; + wait_queue_head_t cvp_wait; + + /* cache the values related to Rx and Tx */ + struct device_data dev_rx; + struct device_data dev_tx; + + /* call status */ + int v_call_status; /* Start or End */ + + u32 mvm_state; + u32 cvs_state; + u32 cvp_state; + + /* Handle to MVM */ + u16 mvm_handle; + /* Handle to CVS */ + u16 cvs_handle; + /* Handle to CVP */ + u16 cvp_handle; + + struct mutex lock; + + struct incall_rec_info rec_info; + + struct incall_music_info music_info; + + u16 session_id; +}; + +#define MAX_VOC_SESSIONS 2 +#define SESSION_ID_BASE 0xFFF0 + +struct common_data { + uint32_t voc_path; + uint32_t adsp_version; + uint32_t device_events; + + /* 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 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; + + struct mutex common_lock; + + struct mvs_driver_info mvs_info; + + struct voice_data voice[MAX_VOC_SESSIONS]; +}; + +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, + struct q_min_max_rate q_min_max_rate); + +int voice_start_record(uint32_t rec_mode, uint32_t set); + +int voice_start_playback(uint32_t set); + +u16 voice_get_session_id(const char *name); +#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 0000000000000000000000000000000000000000..f5bea3150fd527e9d52f661ba0815a21d9601db1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/rtac.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 __RTAC_H__ +#define __RTAC_H__ + +/* Voice Modes */ +#define RTAC_CVP 0 +#define RTAC_CVS 1 +#define RTAC_VOICE_MODES 2 + +void rtac_add_adm_device(u32 port_id, u32 copp_id, u32 path_id, u32 popp_id); +void rtac_remove_adm_device(u32 port_id); +void rtac_remove_popp_from_adm_devices(u32 popp_id); +void rtac_add_voice(u32 cvs_handle, u32 cvp_handle, u32 rx_afe_port, + u32 tx_afe_port, u32 session_id); +void rtac_remove_voice(u32 cvs_handle); +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 diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/usf.h b/arch/arm/mach-msm/include/mach/qdsp6v2/usf.h new file mode 100644 index 0000000000000000000000000000000000000000..bd303b2f79ebf642638081d7ce0b580c829998d2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/usf.h @@ -0,0 +1,268 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __USF_H__ +#define __USF_H__ + +#include +#include + +#define USF_IOCTL_MAGIC 'U' + +#define US_SET_TX_INFO _IOW(USF_IOCTL_MAGIC, 0, \ + struct us_tx_info_type) +#define US_START_TX _IO(USF_IOCTL_MAGIC, 1) +#define US_GET_TX_UPDATE _IOWR(USF_IOCTL_MAGIC, 2, \ + struct us_tx_update_info_type) +#define US_SET_RX_INFO _IOW(USF_IOCTL_MAGIC, 3, \ + struct us_rx_info_type) +#define US_SET_RX_UPDATE _IOWR(USF_IOCTL_MAGIC, 4, \ + struct us_rx_update_info_type) +#define US_START_RX _IO(USF_IOCTL_MAGIC, 5) + +#define US_STOP_TX _IO(USF_IOCTL_MAGIC, 6) +#define US_STOP_RX _IO(USF_IOCTL_MAGIC, 7) + +#define US_SET_DETECTION _IOWR(USF_IOCTL_MAGIC, 8, \ + struct us_detect_info_type) + +#define US_GET_VERSION _IOWR(USF_IOCTL_MAGIC, 9, \ + struct us_version_info_type) + +/* Special timeout values */ +#define USF_NO_WAIT_TIMEOUT 0x00000000 +/* Infinitive */ +#define USF_INFINITIVE_TIMEOUT 0xffffffff +/* Default value, used by the driver */ +#define USF_DEFAULT_TIMEOUT 0xfffffffe + +/* US detection place (HW|FW) */ +enum us_detect_place_enum { +/* US is detected in HW */ + US_DETECT_HW, +/* US is detected in FW */ + US_DETECT_FW +}; + +/* US detection mode */ +enum us_detect_mode_enum { +/* US detection is disabled */ + US_DETECT_DISABLED_MODE, +/* US detection is enabled in continue mode */ + US_DETECT_CONTINUE_MODE, +/* US detection is enabled in one shot mode */ + US_DETECT_SHOT_MODE +}; + +/* Encoder (TX), decoder (RX) supported US data formats */ +#define USF_POINT_EPOS_FORMAT 0 +#define USF_RAW_FORMAT 1 + +/* Types of events, produced by the calculators */ +#define USF_NO_EVENT 0 +#define USF_TSC_EVENT 1 +#define USF_MOUSE_EVENT 2 +#define USF_KEYBOARD_EVENT 4 +#define USF_ALL_EVENTS (USF_TSC_EVENT | USF_MOUSE_EVENT | USF_KEYBOARD_EVENT) + +/* min, max array dimension */ +#define MIN_MAX_DIM 2 + +/* coordinates (x,y,z) array dimension */ +#define COORDINATES_DIM 3 + +/* tilts (x,y) array dimension */ +#define TILTS_DIM 2 + +/* Max size of the client name */ +#define USF_MAX_CLIENT_NAME_SIZE 20 +/* Info structure common for TX and RX */ +struct us_xx_info_type { +/* Input: general info */ +/* Name of the client - event calculator */ + const char *client_name; +/* Selected device identification, accepted in the kernel's CAD */ + uint32_t dev_id; +/* 0 - point_epos type; (e.g. 1 - gr_mmrd) */ + uint32_t stream_format; +/* Required sample rate in Hz */ + uint32_t sample_rate; +/* Size of a buffer (bytes) for US data transfer between the module and USF */ + uint32_t buf_size; +/* Number of the buffers for the US data transfer */ + uint16_t buf_num; +/* Number of the microphones (TX) or speakers(RX) */ + uint16_t port_cnt; +/* Microphones(TX) or speakers(RX) indexes in their enumeration */ + uint8_t port_id[4]; +/* Bits per sample 16 or 32 */ + uint16_t bits_per_sample; +/* Input: Transparent info for encoder in the LPASS */ +/* Parameters data size in bytes */ + uint16_t params_data_size; +/* Pointer to the parameters */ + uint8_t *params_data; +}; + +/* Input events sources */ +enum us_input_event_src_type { + US_INPUT_SRC_PEN, + US_INPUT_SRC_FINGER, + US_INPUT_SRC_UNDEF +}; + +struct us_input_info_type { + /* Touch screen dimensions: min & max;for input module */ + int tsc_x_dim[MIN_MAX_DIM]; + int tsc_y_dim[MIN_MAX_DIM]; + int tsc_z_dim[MIN_MAX_DIM]; + /* Touch screen tilt dimensions: min & max;for input module */ + int tsc_x_tilt[MIN_MAX_DIM]; + int tsc_y_tilt[MIN_MAX_DIM]; + /* Touch screen pressure limits: min & max; for input module */ + int tsc_pressure[MIN_MAX_DIM]; + /* Bitmap of types of events (USF_X_EVENT), produced by calculator */ + uint16_t event_types; + /* Input event source */ + enum us_input_event_src_type event_src; + /* Bitmap of types of events from devs, conflicting with USF */ + uint16_t conflicting_event_types; +}; + +struct us_tx_info_type { + /* Common info */ + struct us_xx_info_type us_xx_info; + /* Info specific for TX*/ + struct us_input_info_type input_info; +}; + +struct us_rx_info_type { + /* Common info */ + struct us_xx_info_type us_xx_info; + /* Info specific for RX*/ +}; + + +#define USF_PIX_COORDINATE 0 /* unit is pixel */ +#define USF_CMM_COORDINATE 1 /* unit is 0.01 mm */ +struct point_event_type { +/* Pen coordinates (x, y, z) in units, defined by */ + int coordinates[COORDINATES_DIM]; + /* {x;y} in transparent units */ + int inclinations[TILTS_DIM]; +/* [0-1023] (10bits); 0 - pen up */ + uint32_t pressure; +/* 0 - mapped in the display pixel. 1 - raw in 0.01 mm (only for log); */ + uint8_t coordinates_type; +}; + +/* Mouse buttons, supported by USF */ +#define USF_BUTTON_LEFT_MASK 1 +#define USF_BUTTON_MIDDLE_MASK 2 +#define USF_BUTTON_RIGHT_MASK 4 +struct mouse_event_type { +/* The mouse relative movement (dX, dY, dZ) */ + int rels[COORDINATES_DIM]; +/* Bitmap of mouse buttons states: 1 - down, 0 - up; */ + uint16_t buttons_states; +}; + +struct key_event_type { +/* Calculated MS key- see input.h. */ + uint32_t key; +/* Keyboard's key state: 1 - down, 0 - up; */ + uint8_t key_state; +}; + +struct usf_event_type { +/* Event sequence number */ + uint32_t seq_num; +/* Event generation system time */ + uint32_t timestamp; +/* Destination input event type (e.g. touch screen, mouse, key) */ + uint16_t event_type; + union { + struct point_event_type point_event; + struct mouse_event_type mouse_event; + struct key_event_type key_event; + } event_data; +}; + +struct us_tx_update_info_type { +/* Input general: */ +/* Number of calculated events */ + uint16_t event_counter; +/* Calculated events or NULL */ + struct usf_event_type *event; +/* Pointer (read index) to the end of available region */ +/* in the shared US data memory */ + uint32_t free_region; +/* Time (sec) to wait for data or special values: */ +/* USF_NO_WAIT_TIMEOUT, USF_INFINITIVE_TIMEOUT, USF_DEFAULT_TIMEOUT */ + uint32_t timeout; +/* Events (from conflicting devs) to be disabled/enabled */ + uint16_t event_filters; + +/* Input transparent data: */ +/* Parameters size */ + uint16_t params_data_size; +/* Pointer to the parameters */ + uint8_t *params_data; +/* Output parameters: */ +/* Pointer (write index) to the end of ready US data region */ +/* in the shared memory */ + uint32_t ready_region; +}; + +struct us_rx_update_info_type { +/* Input general: */ +/* Pointer (write index) to the end of ready US data region */ +/* in the shared memory */ + uint32_t ready_region; +/* Input transparent data: */ +/* Parameters size */ + uint16_t params_data_size; +/* pPointer to the parameters */ + uint8_t *params_data; +/* Output parameters: */ +/* Pointer (read index) to the end of available region */ +/* in the shared US data memory */ + uint32_t free_region; +}; + +struct us_detect_info_type { +/* US detection place (HW|FW) */ +/* NA in the Active and OFF states */ + enum us_detect_place_enum us_detector; +/* US detection mode */ + enum us_detect_mode_enum us_detect_mode; +/* US data dropped during this time (msec) */ + uint32_t skip_time; +/* Transparent data size */ + uint16_t params_data_size; +/* Pointer to the transparent data */ + uint8_t *params_data; +/* Time (sec) to wait for US presence event */ + uint32_t detect_timeout; +/* Out parameter: US presence */ + bool is_us; +}; + +struct us_version_info_type { +/* Size of memory for the version string */ + uint16_t buf_size; +/* Pointer to the memory for the version string */ + char *pbuf; +}; + +#endif /* __USF_H__ */ diff --git a/arch/arm/mach-msm/include/mach/qdss.h b/arch/arm/mach-msm/include/mach/qdss.h new file mode 100644 index 0000000000000000000000000000000000000000..05d85770fe9153449ad7715112c87effa6a9e3f6 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdss.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_QDSS_H +#define __MACH_QDSS_H + +struct qdss_source { + struct list_head link; + const char *name; + uint32_t fport_mask; +}; + +struct msm_qdss_platform_data { + struct qdss_source *src_table; + size_t size; + uint8_t afamily; +}; + +#ifdef CONFIG_MSM_QDSS +extern struct qdss_source *qdss_get(const char *name); +extern void qdss_put(struct qdss_source *src); +extern int qdss_enable(struct qdss_source *src); +extern void qdss_disable(struct qdss_source *src); +extern void qdss_disable_sink(void); +extern int qdss_clk_enable(void); +extern void qdss_clk_disable(void); +#else +static inline struct qdss_source *qdss_get(const char *name) { return NULL; } +static inline void qdss_put(struct qdss_source *src) {} +static inline int qdss_enable(struct qdss_source *src) { return -ENOSYS; } +static inline void qdss_disable(struct qdss_source *src) {} +static inline void qdss_disable_sink(void) {} +static inline int qdss_clk_enable(void) { return -ENOSYS; } +static inline void qdss_clk_disable(void) {} +#endif + +#ifdef CONFIG_MSM_JTAG +extern void msm_jtag_save_state(void); +extern void msm_jtag_restore_state(void); +#else +static inline void msm_jtag_save_state(void) {} +static inline void msm_jtag_restore_state(void) {} +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/qpnp-int.h b/arch/arm/mach-msm/include/mach/qpnp-int.h new file mode 100644 index 0000000000000000000000000000000000000000..a79d2fc2b863f1e6199c0605b90e489e261c54d4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qpnp-int.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef QPNPINT_H +#define QPNPINT_H + +#include + +struct qpnp_irq_spec { + uint8_t slave; /* 0-15 */ + uint8_t per; /* 0-255 */ + uint8_t irq; /* 0-7 */ +}; + +struct qpnp_local_int { + /* mask - Invoke PMIC Arbiter local mask handler */ + int (*mask)(struct spmi_controller *spmi_ctrl, + struct qpnp_irq_spec *spec, + uint32_t priv_d); + /* unmask - Invoke PMIC Arbiter local unmask handler */ + int (*unmask)(struct spmi_controller *spmi_ctrl, + struct qpnp_irq_spec *spec, + uint32_t priv_d); + /* register_priv_data - Return per irq priv data */ + int (*register_priv_data)(struct spmi_controller *spmi_ctrl, + struct qpnp_irq_spec *spec, + uint32_t *priv_d); +}; + +#ifdef CONFIG_MSM_QPNP_INT +/** + * qpnpint_of_init() - Device Tree irq initialization + * + * Standard Device Tree init routine to be called from + * of_irq_init(). + */ +int __init qpnpint_of_init(struct device_node *node, + struct device_node *parent); + +/** + * qpnpint_register_controller() - Register local interrupt callbacks + * + * Used by the PMIC Arbiter driver or equivalent to register + * callbacks for interrupt events. + */ +int qpnpint_register_controller(unsigned int busno, + struct qpnp_local_int *li_cb); + +/** + * qpnpint_handle_irq - Main interrupt handling routine + * + * Pass a PMIC Arbiter interrupt to Linux. + */ +int qpnpint_handle_irq(struct spmi_controller *spmi_ctrl, + struct qpnp_irq_spec *spec); +#else +static inline int __init qpnpint_of_init(struct device_node *node, + struct device_node *parent) +{ + return -ENXIO; +} +static inline int qpnpint_register_controller(unsigned int busno, + struct qpnp_local_int *li_cb) +{ + return -ENXIO; +} + +static inline int qpnpint_handle_irq(struct spmi_controller *spmi_ctrl, + struct qpnp_irq_spec *spec) +{ + return -ENXIO; +} +#endif /* CONFIG_MSM_QPNP_INT */ +#endif /* QPNPINT_H */ diff --git a/arch/arm/mach-msm/include/mach/qpnp.h b/arch/arm/mach-msm/include/mach/qpnp.h new file mode 100644 index 0000000000000000000000000000000000000000..1d2e4403bf8f6f59fbdcfb3c02822f7fbc3ed02f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qpnp.h @@ -0,0 +1,19 @@ + /* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +struct resource *qpnp_get_resource(struct spmi_device *dev, + unsigned int node_idx, unsigned int type, + unsigned int res_num); +int qpnp_get_irq(struct spmi_device *dev, unsigned int node_idx, + unsigned int res_num); 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 0000000000000000000000000000000000000000..75b70f3b5913e7f9f5f4fe451ce039c287be6e02 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/remote_spinlock.h @@ -0,0 +1,299 @@ +/* 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. + * + */ + +/* + * 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 +#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 * +#define SMEM_SPINLOCK_PID_APPS 1 + +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; +} + +static inline int __raw_remote_dek_spin_release(raw_remote_spinlock_t *lock, + uint32_t pid) +{ + return -EINVAL; +} + +static inline void __raw_remote_sfpb_spin_lock(raw_remote_spinlock_t *lock) +{ + do { + writel_relaxed(SMEM_SPINLOCK_PID_APPS, lock); + smp_mb(); + } while (readl_relaxed(lock) != SMEM_SPINLOCK_PID_APPS); +} + +static inline int __raw_remote_sfpb_spin_trylock(raw_remote_spinlock_t *lock) +{ + return 1; +} + +static inline void __raw_remote_sfpb_spin_unlock(raw_remote_spinlock_t *lock) +{ + writel_relaxed(0, lock); + smp_mb(); +} + +/** + * Release spinlock if it is owned by @pid. + * + * This is only to be used for situations where the processor owning + * the spinlock has crashed and the spinlock must be released. + * + * @lock - lock structure + * @pid - processor ID of processor to release + */ +static inline int __raw_remote_gen_spin_release(raw_remote_spinlock_t *lock, + uint32_t pid) +{ + int ret = 1; + + if (readl_relaxed(&lock->lock) == pid) { + writel_relaxed(0, &lock->lock); + wmb(); + ret = 0; + } + return ret; +} + +#if defined(CONFIG_MSM_SMD) || defined(CONFIG_MSM_REMOTE_SPINLOCK_SFPB) +int _remote_spin_lock_init(remote_spinlock_id_t, _remote_spinlock_t *lock); +void _remote_spin_release_all(uint32_t pid); +#else +static inline +int _remote_spin_lock_init(remote_spinlock_id_t id, _remote_spinlock_t *lock) +{ + return -EINVAL; +} +static inline void _remote_spin_release_all(uint32_t pid) {} +#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) +#define _remote_spin_release(lock, pid) __raw_remote_dek_spin_release(*lock,\ + pid) +#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) +#define _remote_spin_release(lock, pid) __raw_remote_gen_spin_release(*lock,\ + pid) +#elif defined(CONFIG_MSM_REMOTE_SPINLOCK_SFPB) +/* Use SFPB Hardware Mutex Registers */ +#define _remote_spin_lock(lock) __raw_remote_sfpb_spin_lock(*lock) +#define _remote_spin_unlock(lock) __raw_remote_sfpb_spin_unlock(*lock) +#define _remote_spin_trylock(lock) __raw_remote_sfpb_spin_trylock(*lock) +#define _remote_spin_release(lock, pid) __raw_remote_gen_spin_release(*lock,\ + pid) +#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) +#define _remote_spin_release(lock, pid) __raw_remote_gen_spin_release(*lock, \ + pid) +#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 0000000000000000000000000000000000000000..84df9bcc03c9b77660b6bcc1cfc183193182cb2a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/restart.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 _ASM_ARCH_MSM_RESTART_H_ +#define _ASM_ARCH_MSM_RESTART_H_ + +#define RESTART_NORMAL 0x0 +#define RESTART_DLOAD 0x1 + +#ifdef CONFIG_MSM_NATIVE_RESTART +void msm_set_restart_mode(int mode); +#else +#define msm_set_restart_mode(mode) +#endif + +extern int pmic_reset_irq; + +#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 0000000000000000000000000000000000000000..88d76502a2b1546cd1a723de4fbd7e713187ceb9 --- /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, 2012 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_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_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 0000000000000000000000000000000000000000..86f04bf2a07879e03b7ae50516b56baac21e90bb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpc_pmapp.h @@ -0,0 +1,76 @@ +/* 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); +int pmapp_vreg_lpm_pincntrl_vote(const char *voter_id, uint vreg_id, + uint clock_id, uint vote); +#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 0000000000000000000000000000000000000000..e1dc841809b5acbaf65eb6eacd22d456a6190394 --- /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-8064.h b/arch/arm/mach-msm/include/mach/rpm-8064.h new file mode 100644 index 0000000000000000000000000000000000000000..c4c6b0a50b17be09f5bb0a31ff47331d24a97d11 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-8064.h @@ -0,0 +1,432 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_8064_H +#define __ARCH_ARM_MACH_MSM_RPM_8064_H + +/* RPM control message RAM enums */ +enum { + MSM_RPM_8064_CTRL_VERSION_MAJOR, + MSM_RPM_8064_CTRL_VERSION_MINOR, + MSM_RPM_8064_CTRL_VERSION_BUILD, + + MSM_RPM_8064_CTRL_REQ_CTX_0, + MSM_RPM_8064_CTRL_REQ_CTX_7 = MSM_RPM_8064_CTRL_REQ_CTX_0 + 7, + MSM_RPM_8064_CTRL_REQ_SEL_0, + MSM_RPM_8064_CTRL_REQ_SEL_3 = MSM_RPM_8064_CTRL_REQ_SEL_0 + 3, + MSM_RPM_8064_CTRL_ACK_CTX_0, + MSM_RPM_8064_CTRL_ACK_CTX_7 = MSM_RPM_8064_CTRL_ACK_CTX_0 + 7, + MSM_RPM_8064_CTRL_ACK_SEL_0, + MSM_RPM_8064_CTRL_ACK_SEL_7 = MSM_RPM_8064_CTRL_ACK_SEL_0 + 7, +}; + +/* RPM resource select enums defined for RPM core + NOT IN SEQUENTIAL ORDER */ +enum { + MSM_RPM_8064_SEL_NOTIFICATION = 0, + MSM_RPM_8064_SEL_INVALIDATE = 1, + MSM_RPM_8064_SEL_TRIGGER_TIMED = 2, + MSM_RPM_8064_SEL_RPM_CTL = 3, + + MSM_RPM_8064_SEL_CXO_CLK = 5, + MSM_RPM_8064_SEL_PXO_CLK = 6, + MSM_RPM_8064_SEL_QDSS_CLK = 7, + MSM_RPM_8064_SEL_APPS_FABRIC_CLK = 8, + MSM_RPM_8064_SEL_SYSTEM_FABRIC_CLK = 9, + MSM_RPM_8064_SEL_MM_FABRIC_CLK = 10, + MSM_RPM_8064_SEL_DAYTONA_FABRIC_CLK = 11, + MSM_RPM_8064_SEL_SFPB_CLK = 12, + MSM_RPM_8064_SEL_CFPB_CLK = 13, + MSM_RPM_8064_SEL_MMFPB_CLK = 14, + MSM_RPM_8064_SEL_EBI1_CLK = 16, + + MSM_RPM_8064_SEL_APPS_FABRIC_CFG_HALT = 18, + MSM_RPM_8064_SEL_APPS_FABRIC_CFG_CLKMOD = 19, + MSM_RPM_8064_SEL_APPS_FABRIC_CFG_IOCTL = 20, + MSM_RPM_8064_SEL_APPS_FABRIC_ARB = 21, + + MSM_RPM_8064_SEL_SYS_FABRIC_CFG_HALT = 22, + MSM_RPM_8064_SEL_SYS_FABRIC_CFG_CLKMOD = 23, + MSM_RPM_8064_SEL_SYS_FABRIC_CFG_IOCTL = 24, + MSM_RPM_8064_SEL_SYSTEM_FABRIC_ARB = 25, + + MSM_RPM_8064_SEL_MMSS_FABRIC_CFG_HALT = 26, + MSM_RPM_8064_SEL_MMSS_FABRIC_CFG_CLKMOD = 27, + MSM_RPM_8064_SEL_MMSS_FABRIC_CFG_IOCTL = 28, + MSM_RPM_8064_SEL_MM_FABRIC_ARB = 29, + + MSM_RPM_8064_SEL_PM8921_S1 = 30, + MSM_RPM_8064_SEL_PM8921_S2 = 31, + MSM_RPM_8064_SEL_PM8921_S3 = 32, + MSM_RPM_8064_SEL_PM8921_S4 = 33, + MSM_RPM_8064_SEL_PM8921_S5 = 34, + MSM_RPM_8064_SEL_PM8921_S6 = 35, + MSM_RPM_8064_SEL_PM8921_S7 = 36, + MSM_RPM_8064_SEL_PM8921_S8 = 37, + MSM_RPM_8064_SEL_PM8921_L1 = 38, + MSM_RPM_8064_SEL_PM8921_L2 = 39, + MSM_RPM_8064_SEL_PM8921_L3 = 40, + MSM_RPM_8064_SEL_PM8921_L4 = 41, + MSM_RPM_8064_SEL_PM8921_L5 = 42, + MSM_RPM_8064_SEL_PM8921_L6 = 43, + MSM_RPM_8064_SEL_PM8921_L7 = 44, + MSM_RPM_8064_SEL_PM8921_L8 = 45, + MSM_RPM_8064_SEL_PM8921_L9 = 46, + MSM_RPM_8064_SEL_PM8921_L10 = 47, + MSM_RPM_8064_SEL_PM8921_L11 = 48, + MSM_RPM_8064_SEL_PM8921_L12 = 49, + MSM_RPM_8064_SEL_PM8921_L13 = 50, + MSM_RPM_8064_SEL_PM8921_L14 = 51, + MSM_RPM_8064_SEL_PM8921_L15 = 52, + MSM_RPM_8064_SEL_PM8921_L16 = 53, + MSM_RPM_8064_SEL_PM8921_L17 = 54, + MSM_RPM_8064_SEL_PM8921_L18 = 55, + MSM_RPM_8064_SEL_PM8921_L19 = 56, + MSM_RPM_8064_SEL_PM8921_L20 = 57, + MSM_RPM_8064_SEL_PM8921_L21 = 58, + MSM_RPM_8064_SEL_PM8921_L22 = 59, + MSM_RPM_8064_SEL_PM8921_L23 = 60, + MSM_RPM_8064_SEL_PM8921_L24 = 61, + MSM_RPM_8064_SEL_PM8921_L25 = 62, + MSM_RPM_8064_SEL_PM8921_L26 = 63, + MSM_RPM_8064_SEL_PM8921_L27 = 64, + MSM_RPM_8064_SEL_PM8921_L28 = 65, + MSM_RPM_8064_SEL_PM8921_L29 = 66, + MSM_RPM_8064_SEL_PM8921_CLK1 = 67, + MSM_RPM_8064_SEL_PM8921_CLK2 = 68, + MSM_RPM_8064_SEL_PM8921_LVS1 = 69, + MSM_RPM_8064_SEL_PM8921_LVS2 = 70, + MSM_RPM_8064_SEL_PM8921_LVS3 = 71, + MSM_RPM_8064_SEL_PM8921_LVS4 = 72, + MSM_RPM_8064_SEL_PM8921_LVS5 = 73, + MSM_RPM_8064_SEL_PM8921_LVS6 = 74, + MSM_RPM_8064_SEL_PM8921_LVS7 = 75, + MSM_RPM_8064_SEL_PM8821_S1 = 76, + MSM_RPM_8064_SEL_PM8821_S2 = 77, + MSM_RPM_8064_SEL_PM8821_L1 = 78, + + MSM_RPM_8064_SEL_NCP = 80, + MSM_RPM_8064_SEL_CXO_BUFFERS = 81, + MSM_RPM_8064_SEL_USB_OTG_SWITCH = 82, + MSM_RPM_8064_SEL_HDMI_SWITCH = 83, + MSM_RPM_8064_SEL_DDR_DMM = 84, + + MSM_RPM_8064_SEL_LAST = MSM_RPM_8064_SEL_DDR_DMM, +}; + +/* RPM resource (4 byte) word ID enum */ +enum { + MSM_RPM_8064_ID_NOTIFICATION_CONFIGURED_0 = 0, + MSM_RPM_8064_ID_NOTIFICATION_CONFIGURED_3 = + MSM_RPM_8064_ID_NOTIFICATION_CONFIGURED_0 + 3, + + MSM_RPM_8064_ID_NOTIFICATION_REGISTERED_0 = 4, + MSM_RPM_8064_ID_NOTIFICATION_REGISTERED_3 = + MSM_RPM_8064_ID_NOTIFICATION_REGISTERED_0 + 3, + + MSM_RPM_8064_ID_INVALIDATE_0 = 8, + MSM_RPM_8064_ID_INVALIDATE_7 = + MSM_RPM_8064_ID_INVALIDATE_0 + 7, + + MSM_RPM_8064_ID_TRIGGER_TIMED_TO = 16, + MSM_RPM_8064_ID_TRIGGER_TIMED_SCLK_COUNT = 17, + + MSM_RPM_8064_ID_RPM_CTL = 18, + + /* TRIGGER_CLEAR/SET deprecated in these 24 RESERVED bytes */ + MSM_RPM_8064_ID_RESERVED_0 = 19, + MSM_RPM_8064_ID_RESERVED_5 = + MSM_RPM_8064_ID_RESERVED_0 + 5, + + MSM_RPM_8064_ID_CXO_CLK = 25, + MSM_RPM_8064_ID_PXO_CLK = 26, + MSM_RPM_8064_ID_APPS_FABRIC_CLK = 27, + MSM_RPM_8064_ID_SYSTEM_FABRIC_CLK = 28, + MSM_RPM_8064_ID_MM_FABRIC_CLK = 29, + MSM_RPM_8064_ID_DAYTONA_FABRIC_CLK = 30, + MSM_RPM_8064_ID_SFPB_CLK = 31, + MSM_RPM_8064_ID_CFPB_CLK = 32, + MSM_RPM_8064_ID_MMFPB_CLK = 33, + MSM_RPM_8064_ID_EBI1_CLK = 34, + + MSM_RPM_8064_ID_APPS_FABRIC_CFG_HALT_0 = 35, + MSM_RPM_8064_ID_APPS_FABRIC_CFG_HALT_1 = 36, + MSM_RPM_8064_ID_APPS_FABRIC_CFG_CLKMOD_0 = 37, + MSM_RPM_8064_ID_APPS_FABRIC_CFG_CLKMOD_1 = 38, + MSM_RPM_8064_ID_APPS_FABRIC_CFG_CLKMOD_2 = 39, + MSM_RPM_8064_ID_APPS_FABRIC_CFG_IOCTL = 40, + MSM_RPM_8064_ID_APPS_FABRIC_ARB_0 = 41, + MSM_RPM_8064_ID_APPS_FABRIC_ARB_11 = + MSM_RPM_8064_ID_APPS_FABRIC_ARB_0 + 11, + + MSM_RPM_8064_ID_SYS_FABRIC_CFG_HALT_0 = 53, + MSM_RPM_8064_ID_SYS_FABRIC_CFG_HALT_1 = 54, + MSM_RPM_8064_ID_SYS_FABRIC_CFG_CLKMOD_0 = 55, + MSM_RPM_8064_ID_SYS_FABRIC_CFG_CLKMOD_1 = 56, + MSM_RPM_8064_ID_SYS_FABRIC_CFG_CLKMOD_2 = 57, + MSM_RPM_8064_ID_SYS_FABRIC_CFG_IOCTL = 58, + MSM_RPM_8064_ID_SYSTEM_FABRIC_ARB_0 = 59, + MSM_RPM_8064_ID_SYSTEM_FABRIC_ARB_29 = + MSM_RPM_8064_ID_SYSTEM_FABRIC_ARB_0 + 29, + + MSM_RPM_8064_ID_MMSS_FABRIC_CFG_HALT_0 = 89, + MSM_RPM_8064_ID_MMSS_FABRIC_CFG_HALT_1 = 90, + MSM_RPM_8064_ID_MMSS_FABRIC_CFG_CLKMOD_0 = 91, + MSM_RPM_8064_ID_MMSS_FABRIC_CFG_CLKMOD_1 = 92, + MSM_RPM_8064_ID_MMSS_FABRIC_CFG_CLKMOD_2 = 93, + MSM_RPM_8064_ID_MMSS_FABRIC_CFG_IOCTL = 94, + MSM_RPM_8064_ID_MM_FABRIC_ARB_0 = 95, + MSM_RPM_8064_ID_MM_FABRIC_ARB_20 = + MSM_RPM_8064_ID_MM_FABRIC_ARB_0 + 20, + + MSM_RPM_8064_ID_PM8921_S1_0 = 116, + MSM_RPM_8064_ID_PM8921_S1_1 = 117, + MSM_RPM_8064_ID_PM8921_S2_0 = 118, + MSM_RPM_8064_ID_PM8921_S2_1 = 119, + MSM_RPM_8064_ID_PM8921_S3_0 = 120, + MSM_RPM_8064_ID_PM8921_S3_1 = 121, + MSM_RPM_8064_ID_PM8921_S4_0 = 122, + MSM_RPM_8064_ID_PM8921_S4_1 = 123, + MSM_RPM_8064_ID_PM8921_S5_0 = 124, + MSM_RPM_8064_ID_PM8921_S5_1 = 125, + MSM_RPM_8064_ID_PM8921_S6_0 = 126, + MSM_RPM_8064_ID_PM8921_S6_1 = 127, + MSM_RPM_8064_ID_PM8921_S7_0 = 128, + MSM_RPM_8064_ID_PM8921_S7_1 = 129, + MSM_RPM_8064_ID_PM8921_S8_0 = 130, + MSM_RPM_8064_ID_PM8921_S8_1 = 131, + MSM_RPM_8064_ID_PM8921_L1_0 = 132, + MSM_RPM_8064_ID_PM8921_L1_1 = 133, + MSM_RPM_8064_ID_PM8921_L2_0 = 134, + MSM_RPM_8064_ID_PM8921_L2_1 = 135, + MSM_RPM_8064_ID_PM8921_L3_0 = 136, + MSM_RPM_8064_ID_PM8921_L3_1 = 137, + MSM_RPM_8064_ID_PM8921_L4_0 = 138, + MSM_RPM_8064_ID_PM8921_L4_1 = 139, + MSM_RPM_8064_ID_PM8921_L5_0 = 140, + MSM_RPM_8064_ID_PM8921_L5_1 = 141, + MSM_RPM_8064_ID_PM8921_L6_0 = 142, + MSM_RPM_8064_ID_PM8921_L6_1 = 143, + MSM_RPM_8064_ID_PM8921_L7_0 = 144, + MSM_RPM_8064_ID_PM8921_L7_1 = 145, + MSM_RPM_8064_ID_PM8921_L8_0 = 146, + MSM_RPM_8064_ID_PM8921_L8_1 = 147, + MSM_RPM_8064_ID_PM8921_L9_0 = 148, + MSM_RPM_8064_ID_PM8921_L9_1 = 149, + MSM_RPM_8064_ID_PM8921_L10_0 = 150, + MSM_RPM_8064_ID_PM8921_L10_1 = 151, + MSM_RPM_8064_ID_PM8921_L11_0 = 152, + MSM_RPM_8064_ID_PM8921_L11_1 = 153, + MSM_RPM_8064_ID_PM8921_L12_0 = 154, + MSM_RPM_8064_ID_PM8921_L12_1 = 155, + MSM_RPM_8064_ID_PM8921_L13_0 = 156, + MSM_RPM_8064_ID_PM8921_L13_1 = 157, + MSM_RPM_8064_ID_PM8921_L14_0 = 158, + MSM_RPM_8064_ID_PM8921_L14_1 = 159, + MSM_RPM_8064_ID_PM8921_L15_0 = 160, + MSM_RPM_8064_ID_PM8921_L15_1 = 161, + MSM_RPM_8064_ID_PM8921_L16_0 = 162, + MSM_RPM_8064_ID_PM8921_L16_1 = 163, + MSM_RPM_8064_ID_PM8921_L17_0 = 164, + MSM_RPM_8064_ID_PM8921_L17_1 = 165, + MSM_RPM_8064_ID_PM8921_L18_0 = 166, + MSM_RPM_8064_ID_PM8921_L18_1 = 167, + MSM_RPM_8064_ID_PM8921_L19_0 = 168, + MSM_RPM_8064_ID_PM8921_L19_1 = 169, + MSM_RPM_8064_ID_PM8921_L20_0 = 170, + MSM_RPM_8064_ID_PM8921_L20_1 = 171, + MSM_RPM_8064_ID_PM8921_L21_0 = 172, + MSM_RPM_8064_ID_PM8921_L21_1 = 173, + MSM_RPM_8064_ID_PM8921_L22_0 = 174, + MSM_RPM_8064_ID_PM8921_L22_1 = 175, + MSM_RPM_8064_ID_PM8921_L23_0 = 176, + MSM_RPM_8064_ID_PM8921_L23_1 = 177, + MSM_RPM_8064_ID_PM8921_L24_0 = 178, + MSM_RPM_8064_ID_PM8921_L24_1 = 179, + MSM_RPM_8064_ID_PM8921_L25_0 = 180, + MSM_RPM_8064_ID_PM8921_L25_1 = 181, + MSM_RPM_8064_ID_PM8921_L26_0 = 182, + MSM_RPM_8064_ID_PM8921_L26_1 = 183, + MSM_RPM_8064_ID_PM8921_L27_0 = 184, + MSM_RPM_8064_ID_PM8921_L27_1 = 185, + MSM_RPM_8064_ID_PM8921_L28_0 = 186, + MSM_RPM_8064_ID_PM8921_L28_1 = 187, + MSM_RPM_8064_ID_PM8921_L29_0 = 188, + MSM_RPM_8064_ID_PM8921_L29_1 = 189, + MSM_RPM_8064_ID_PM8921_CLK1_0 = 190, + MSM_RPM_8064_ID_PM8921_CLK1_1 = 191, + MSM_RPM_8064_ID_PM8921_CLK2_0 = 192, + MSM_RPM_8064_ID_PM8921_CLK2_1 = 193, + MSM_RPM_8064_ID_PM8921_LVS1 = 194, + MSM_RPM_8064_ID_PM8921_LVS2 = 195, + MSM_RPM_8064_ID_PM8921_LVS3 = 196, + MSM_RPM_8064_ID_PM8921_LVS4 = 197, + MSM_RPM_8064_ID_PM8921_LVS5 = 198, + MSM_RPM_8064_ID_PM8921_LVS6 = 199, + MSM_RPM_8064_ID_PM8921_LVS7 = 200, + MSM_RPM_8064_ID_PM8821_S1_0 = 201, + MSM_RPM_8064_ID_PM8821_S1_1 = 202, + MSM_RPM_8064_ID_PM8821_S2_0 = 203, + MSM_RPM_8064_ID_PM8821_S2_1 = 204, + MSM_RPM_8064_ID_PM8821_L1_0 = 205, + MSM_RPM_8064_ID_PM8821_L1_1 = 206, + MSM_RPM_8064_ID_NCP_0 = 207, + MSM_RPM_8064_ID_NCP_1 = 208, + MSM_RPM_8064_ID_CXO_BUFFERS = 209, + MSM_RPM_8064_ID_USB_OTG_SWITCH = 210, + MSM_RPM_8064_ID_HDMI_SWITCH = 211, + MSM_RPM_8064_ID_DDR_DMM_0 = 212, + MSM_RPM_8064_ID_DDR_DMM_1 = 213, + MSM_RPM_8064_ID_QDSS_CLK = 214, + + MSM_RPM_8064_ID_LAST = MSM_RPM_8064_ID_QDSS_CLK, +}; + + +/* RPM status ID enum */ +enum { + MSM_RPM_8064_STATUS_ID_VERSION_MAJOR = 0, + MSM_RPM_8064_STATUS_ID_VERSION_MINOR = 1, + MSM_RPM_8064_STATUS_ID_VERSION_BUILD = 2, + MSM_RPM_8064_STATUS_ID_SUPPORTED_RESOURCES_0 = 3, + MSM_RPM_8064_STATUS_ID_SUPPORTED_RESOURCES_1 = 4, + MSM_RPM_8064_STATUS_ID_SUPPORTED_RESOURCES_2 = 5, + MSM_RPM_8064_STATUS_ID_RESERVED_SUPPORTED_RESOURCES_0 = 6, + MSM_RPM_8064_STATUS_ID_SEQUENCE = 7, + MSM_RPM_8064_STATUS_ID_RPM_CTL = 8, + MSM_RPM_8064_STATUS_ID_CXO_CLK = 9, + MSM_RPM_8064_STATUS_ID_PXO_CLK = 10, + MSM_RPM_8064_STATUS_ID_APPS_FABRIC_CLK = 11, + MSM_RPM_8064_STATUS_ID_SYSTEM_FABRIC_CLK = 12, + MSM_RPM_8064_STATUS_ID_MM_FABRIC_CLK = 13, + MSM_RPM_8064_STATUS_ID_DAYTONA_FABRIC_CLK = 14, + MSM_RPM_8064_STATUS_ID_SFPB_CLK = 15, + MSM_RPM_8064_STATUS_ID_CFPB_CLK = 16, + MSM_RPM_8064_STATUS_ID_MMFPB_CLK = 17, + MSM_RPM_8064_STATUS_ID_EBI1_CLK = 18, + MSM_RPM_8064_STATUS_ID_APPS_FABRIC_CFG_HALT = 19, + MSM_RPM_8064_STATUS_ID_APPS_FABRIC_CFG_CLKMOD = 20, + MSM_RPM_8064_STATUS_ID_APPS_FABRIC_CFG_IOCTL = 21, + MSM_RPM_8064_STATUS_ID_APPS_FABRIC_ARB = 22, + MSM_RPM_8064_STATUS_ID_SYS_FABRIC_CFG_HALT = 23, + MSM_RPM_8064_STATUS_ID_SYS_FABRIC_CFG_CLKMOD = 24, + MSM_RPM_8064_STATUS_ID_SYS_FABRIC_CFG_IOCTL = 25, + MSM_RPM_8064_STATUS_ID_SYSTEM_FABRIC_ARB = 26, + MSM_RPM_8064_STATUS_ID_MMSS_FABRIC_CFG_HALT = 27, + MSM_RPM_8064_STATUS_ID_MMSS_FABRIC_CFG_CLKMOD = 28, + MSM_RPM_8064_STATUS_ID_MMSS_FABRIC_CFG_IOCTL = 29, + MSM_RPM_8064_STATUS_ID_MM_FABRIC_ARB = 30, + MSM_RPM_8064_STATUS_ID_PM8921_S1_0 = 31, + MSM_RPM_8064_STATUS_ID_PM8921_S1_1 = 32, + MSM_RPM_8064_STATUS_ID_PM8921_S2_0 = 33, + MSM_RPM_8064_STATUS_ID_PM8921_S2_1 = 34, + MSM_RPM_8064_STATUS_ID_PM8921_S3_0 = 35, + MSM_RPM_8064_STATUS_ID_PM8921_S3_1 = 36, + MSM_RPM_8064_STATUS_ID_PM8921_S4_0 = 37, + MSM_RPM_8064_STATUS_ID_PM8921_S4_1 = 38, + MSM_RPM_8064_STATUS_ID_PM8921_S5_0 = 39, + MSM_RPM_8064_STATUS_ID_PM8921_S5_1 = 40, + MSM_RPM_8064_STATUS_ID_PM8921_S6_0 = 41, + MSM_RPM_8064_STATUS_ID_PM8921_S6_1 = 42, + MSM_RPM_8064_STATUS_ID_PM8921_S7_0 = 43, + MSM_RPM_8064_STATUS_ID_PM8921_S7_1 = 44, + MSM_RPM_8064_STATUS_ID_PM8921_S8_0 = 45, + MSM_RPM_8064_STATUS_ID_PM8921_S8_1 = 46, + MSM_RPM_8064_STATUS_ID_PM8921_L1_0 = 47, + MSM_RPM_8064_STATUS_ID_PM8921_L1_1 = 48, + MSM_RPM_8064_STATUS_ID_PM8921_L2_0 = 49, + MSM_RPM_8064_STATUS_ID_PM8921_L2_1 = 50, + MSM_RPM_8064_STATUS_ID_PM8921_L3_0 = 51, + MSM_RPM_8064_STATUS_ID_PM8921_L3_1 = 52, + MSM_RPM_8064_STATUS_ID_PM8921_L4_0 = 53, + MSM_RPM_8064_STATUS_ID_PM8921_L4_1 = 54, + MSM_RPM_8064_STATUS_ID_PM8921_L5_0 = 55, + MSM_RPM_8064_STATUS_ID_PM8921_L5_1 = 56, + MSM_RPM_8064_STATUS_ID_PM8921_L6_0 = 57, + MSM_RPM_8064_STATUS_ID_PM8921_L6_1 = 58, + MSM_RPM_8064_STATUS_ID_PM8921_L7_0 = 59, + MSM_RPM_8064_STATUS_ID_PM8921_L7_1 = 60, + MSM_RPM_8064_STATUS_ID_PM8921_L8_0 = 61, + MSM_RPM_8064_STATUS_ID_PM8921_L8_1 = 62, + MSM_RPM_8064_STATUS_ID_PM8921_L9_0 = 63, + MSM_RPM_8064_STATUS_ID_PM8921_L9_1 = 64, + MSM_RPM_8064_STATUS_ID_PM8921_L10_0 = 65, + MSM_RPM_8064_STATUS_ID_PM8921_L10_1 = 66, + MSM_RPM_8064_STATUS_ID_PM8921_L11_0 = 67, + MSM_RPM_8064_STATUS_ID_PM8921_L11_1 = 68, + MSM_RPM_8064_STATUS_ID_PM8921_L12_0 = 69, + MSM_RPM_8064_STATUS_ID_PM8921_L12_1 = 70, + MSM_RPM_8064_STATUS_ID_PM8921_L13_0 = 71, + MSM_RPM_8064_STATUS_ID_PM8921_L13_1 = 72, + MSM_RPM_8064_STATUS_ID_PM8921_L14_0 = 73, + MSM_RPM_8064_STATUS_ID_PM8921_L14_1 = 74, + MSM_RPM_8064_STATUS_ID_PM8921_L15_0 = 75, + MSM_RPM_8064_STATUS_ID_PM8921_L15_1 = 76, + MSM_RPM_8064_STATUS_ID_PM8921_L16_0 = 77, + MSM_RPM_8064_STATUS_ID_PM8921_L16_1 = 78, + MSM_RPM_8064_STATUS_ID_PM8921_L17_0 = 79, + MSM_RPM_8064_STATUS_ID_PM8921_L17_1 = 80, + MSM_RPM_8064_STATUS_ID_PM8921_L18_0 = 81, + MSM_RPM_8064_STATUS_ID_PM8921_L18_1 = 82, + MSM_RPM_8064_STATUS_ID_PM8921_L19_0 = 83, + MSM_RPM_8064_STATUS_ID_PM8921_L19_1 = 84, + MSM_RPM_8064_STATUS_ID_PM8921_L20_0 = 85, + MSM_RPM_8064_STATUS_ID_PM8921_L20_1 = 86, + MSM_RPM_8064_STATUS_ID_PM8921_L21_0 = 87, + MSM_RPM_8064_STATUS_ID_PM8921_L21_1 = 88, + MSM_RPM_8064_STATUS_ID_PM8921_L22_0 = 89, + MSM_RPM_8064_STATUS_ID_PM8921_L22_1 = 90, + MSM_RPM_8064_STATUS_ID_PM8921_L23_0 = 91, + MSM_RPM_8064_STATUS_ID_PM8921_L23_1 = 92, + MSM_RPM_8064_STATUS_ID_PM8921_L24_0 = 93, + MSM_RPM_8064_STATUS_ID_PM8921_L24_1 = 94, + MSM_RPM_8064_STATUS_ID_PM8921_L25_0 = 95, + MSM_RPM_8064_STATUS_ID_PM8921_L25_1 = 96, + MSM_RPM_8064_STATUS_ID_PM8921_L26_0 = 97, + MSM_RPM_8064_STATUS_ID_PM8921_L26_1 = 98, + MSM_RPM_8064_STATUS_ID_PM8921_L27_0 = 99, + MSM_RPM_8064_STATUS_ID_PM8921_L27_1 = 100, + MSM_RPM_8064_STATUS_ID_PM8921_L28_0 = 101, + MSM_RPM_8064_STATUS_ID_PM8921_L28_1 = 102, + MSM_RPM_8064_STATUS_ID_PM8921_L29_0 = 103, + MSM_RPM_8064_STATUS_ID_PM8921_L29_1 = 104, + MSM_RPM_8064_STATUS_ID_PM8921_CLK1_0 = 105, + MSM_RPM_8064_STATUS_ID_PM8921_CLK1_1 = 106, + MSM_RPM_8064_STATUS_ID_PM8921_CLK2_0 = 107, + MSM_RPM_8064_STATUS_ID_PM8921_CLK2_1 = 108, + MSM_RPM_8064_STATUS_ID_PM8921_LVS1 = 109, + MSM_RPM_8064_STATUS_ID_PM8921_LVS2 = 110, + MSM_RPM_8064_STATUS_ID_PM8921_LVS3 = 111, + MSM_RPM_8064_STATUS_ID_PM8921_LVS4 = 112, + MSM_RPM_8064_STATUS_ID_PM8921_LVS5 = 113, + MSM_RPM_8064_STATUS_ID_PM8921_LVS6 = 114, + MSM_RPM_8064_STATUS_ID_PM8921_LVS7 = 115, + MSM_RPM_8064_STATUS_ID_PM8821_S1_0 = 116, + MSM_RPM_8064_STATUS_ID_PM8821_S1_1 = 117, + MSM_RPM_8064_STATUS_ID_PM8821_S2_0 = 118, + MSM_RPM_8064_STATUS_ID_PM8821_S2_1 = 119, + MSM_RPM_8064_STATUS_ID_PM8821_L1_0 = 120, + MSM_RPM_8064_STATUS_ID_PM8821_L1_1 = 121, + MSM_RPM_8064_STATUS_ID_NCP_0 = 122, + MSM_RPM_8064_STATUS_ID_NCP_1 = 123, + MSM_RPM_8064_STATUS_ID_CXO_BUFFERS = 124, + MSM_RPM_8064_STATUS_ID_USB_OTG_SWITCH = 125, + MSM_RPM_8064_STATUS_ID_HDMI_SWITCH = 126, + MSM_RPM_8064_STATUS_ID_DDR_DMM_0 = 127, + MSM_RPM_8064_STATUS_ID_DDR_DMM_1 = 128, + MSM_RPM_8064_STATUS_ID_EBI1_CH0_RANGE = 129, + MSM_RPM_8064_STATUS_ID_EBI1_CH1_RANGE = 130, + + MSM_RPM_8064_STATUS_ID_LAST = MSM_RPM_8064_STATUS_ID_EBI1_CH1_RANGE, +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_8064_H */ 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 0000000000000000000000000000000000000000..5e3b40444cdc52ce7050e992e54591ddaa548dcc --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-8660.h @@ -0,0 +1,455 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_8660_CTRL_VERSION_MAJOR, + MSM_RPM_8660_CTRL_VERSION_MINOR, + MSM_RPM_8660_CTRL_VERSION_BUILD, + + MSM_RPM_8660_CTRL_REQ_CTX_0, + MSM_RPM_8660_CTRL_REQ_CTX_7 = MSM_RPM_8660_CTRL_REQ_CTX_0 + 7, + MSM_RPM_8660_CTRL_REQ_SEL_0, + MSM_RPM_8660_CTRL_REQ_SEL_7 = MSM_RPM_8660_CTRL_REQ_SEL_0 + 7, + MSM_RPM_8660_CTRL_ACK_CTX_0, + MSM_RPM_8660_CTRL_ACK_CTX_7 = MSM_RPM_8660_CTRL_ACK_CTX_0 + 7, + MSM_RPM_8660_CTRL_ACK_SEL_0, + MSM_RPM_8660_CTRL_ACK_SEL_7 = MSM_RPM_8660_CTRL_ACK_SEL_0 + 7, +}; + +enum { + MSM_RPM_8660_SEL_NOTIFICATION, + MSM_RPM_8660_SEL_INVALIDATE, + MSM_RPM_8660_SEL_TRIGGER_TIMED, + MSM_RPM_8660_SEL_TRIGGER_SET, + MSM_RPM_8660_SEL_TRIGGER_CLEAR, + + MSM_RPM_8660_SEL_CXO_CLK, + MSM_RPM_8660_SEL_PXO_CLK, + MSM_RPM_8660_SEL_PLL_4, + MSM_RPM_8660_SEL_APPS_FABRIC_CLK, + MSM_RPM_8660_SEL_SYSTEM_FABRIC_CLK, + MSM_RPM_8660_SEL_MM_FABRIC_CLK, + MSM_RPM_8660_SEL_DAYTONA_FABRIC_CLK, + MSM_RPM_8660_SEL_SFPB_CLK, + MSM_RPM_8660_SEL_CFPB_CLK, + MSM_RPM_8660_SEL_MMFPB_CLK, + MSM_RPM_8660_SEL_SMI_CLK, + MSM_RPM_8660_SEL_EBI1_CLK, + + MSM_RPM_8660_SEL_APPS_L2_CACHE_CTL, + + MSM_RPM_8660_SEL_APPS_FABRIC_HALT, + MSM_RPM_8660_SEL_APPS_FABRIC_CLOCK_MODE, + MSM_RPM_8660_SEL_APPS_FABRIC_IOCTL, + MSM_RPM_8660_SEL_APPS_FABRIC_ARB, + + MSM_RPM_8660_SEL_SYSTEM_FABRIC_HALT, + MSM_RPM_8660_SEL_SYSTEM_FABRIC_CLOCK_MODE, + MSM_RPM_8660_SEL_SYSTEM_FABRIC_IOCTL, + MSM_RPM_8660_SEL_SYSTEM_FABRIC_ARB, + + MSM_RPM_8660_SEL_MM_FABRIC_HALT, + MSM_RPM_8660_SEL_MM_FABRIC_CLOCK_MODE, + MSM_RPM_8660_SEL_MM_FABRIC_IOCTL, + MSM_RPM_8660_SEL_MM_FABRIC_ARB, + + MSM_RPM_8660_SEL_SMPS0B, + MSM_RPM_8660_SEL_SMPS1B, + MSM_RPM_8660_SEL_SMPS2B, + MSM_RPM_8660_SEL_SMPS3B, + MSM_RPM_8660_SEL_SMPS4B, + MSM_RPM_8660_SEL_LDO0B, + MSM_RPM_8660_SEL_LDO1B, + MSM_RPM_8660_SEL_LDO2B, + MSM_RPM_8660_SEL_LDO3B, + MSM_RPM_8660_SEL_LDO4B, + MSM_RPM_8660_SEL_LDO5B, + MSM_RPM_8660_SEL_LDO6B, + MSM_RPM_8660_SEL_LVS0B, + MSM_RPM_8660_SEL_LVS1B, + MSM_RPM_8660_SEL_LVS2B, + MSM_RPM_8660_SEL_LVS3B, + MSM_RPM_8660_SEL_MVS, + + MSM_RPM_8660_SEL_SMPS0, + MSM_RPM_8660_SEL_SMPS1, + MSM_RPM_8660_SEL_SMPS2, + MSM_RPM_8660_SEL_SMPS3, + MSM_RPM_8660_SEL_SMPS4, + + MSM_RPM_8660_SEL_LDO0, + MSM_RPM_8660_SEL_LDO1, + MSM_RPM_8660_SEL_LDO2, + MSM_RPM_8660_SEL_LDO3, + MSM_RPM_8660_SEL_LDO4, + MSM_RPM_8660_SEL_LDO5, + MSM_RPM_8660_SEL_LDO6, + MSM_RPM_8660_SEL_LDO7, + MSM_RPM_8660_SEL_LDO8, + MSM_RPM_8660_SEL_LDO9, + MSM_RPM_8660_SEL_LDO10, + MSM_RPM_8660_SEL_LDO11, + MSM_RPM_8660_SEL_LDO12, + MSM_RPM_8660_SEL_LDO13, + MSM_RPM_8660_SEL_LDO14, + MSM_RPM_8660_SEL_LDO15, + MSM_RPM_8660_SEL_LDO16, + MSM_RPM_8660_SEL_LDO17, + MSM_RPM_8660_SEL_LDO18, + MSM_RPM_8660_SEL_LDO19, + MSM_RPM_8660_SEL_LDO20, + MSM_RPM_8660_SEL_LDO21, + MSM_RPM_8660_SEL_LDO22, + MSM_RPM_8660_SEL_LDO23, + MSM_RPM_8660_SEL_LDO24, + MSM_RPM_8660_SEL_LDO25, + MSM_RPM_8660_SEL_LVS0, + MSM_RPM_8660_SEL_LVS1, + MSM_RPM_8660_SEL_NCP, + + MSM_RPM_8660_SEL_CXO_BUFFERS, + + MSM_RPM_8660_SEL_LAST = MSM_RPM_8660_SEL_CXO_BUFFERS, +}; + + +enum { + MSM_RPM_8660_ID_NOTIFICATION_CONFIGURED_0, + MSM_RPM_8660_ID_NOTIFICATION_CONFIGURED_7 = + MSM_RPM_8660_ID_NOTIFICATION_CONFIGURED_0 + 7, + + MSM_RPM_8660_ID_NOTIFICATION_REGISTERED_0, + MSM_RPM_8660_ID_NOTIFICATION_REGISTERED_7 = + MSM_RPM_8660_ID_NOTIFICATION_REGISTERED_0 + 7, + + MSM_RPM_8660_ID_INVALIDATE_0, + MSM_RPM_8660_ID_INVALIDATE_7 = + MSM_RPM_8660_ID_INVALIDATE_0 + 7, + + MSM_RPM_8660_ID_TRIGGER_TIMED_TO, + MSM_RPM_8660_ID_TRIGGER_TIMED_SCLK_COUNT, + + MSM_RPM_8660_ID_TRIGGER_SET_FROM, + MSM_RPM_8660_ID_TRIGGER_SET_TO, + MSM_RPM_8660_ID_TRIGGER_SET_TRIGGER, + + MSM_RPM_8660_ID_TRIGGER_CLEAR_FROM, + MSM_RPM_8660_ID_TRIGGER_CLEAR_TO, + MSM_RPM_8660_ID_TRIGGER_CLEAR_TRIGGER, + + MSM_RPM_8660_ID_CXO_CLK, + MSM_RPM_8660_ID_PXO_CLK, + MSM_RPM_8660_ID_PLL_4, + MSM_RPM_8660_ID_APPS_FABRIC_CLK, + MSM_RPM_8660_ID_SYSTEM_FABRIC_CLK, + MSM_RPM_8660_ID_MM_FABRIC_CLK, + MSM_RPM_8660_ID_DAYTONA_FABRIC_CLK, + MSM_RPM_8660_ID_SFPB_CLK, + MSM_RPM_8660_ID_CFPB_CLK, + MSM_RPM_8660_ID_MMFPB_CLK, + MSM_RPM_8660_ID_SMI_CLK, + MSM_RPM_8660_ID_EBI1_CLK, + + MSM_RPM_8660_ID_APPS_L2_CACHE_CTL, + + MSM_RPM_8660_ID_APPS_FABRIC_HALT_0, + MSM_RPM_8660_ID_APPS_FABRIC_HALT_1, + MSM_RPM_8660_ID_APPS_FABRIC_CLOCK_MODE_0, + MSM_RPM_8660_ID_APPS_FABRIC_CLOCK_MODE_1, + MSM_RPM_8660_ID_APPS_FABRIC_CLOCK_MODE_2, + MSM_RPM_8660_ID_APPS_FABRIC_RESERVED_A, + MSM_RPM_8660_ID_APPS_FABRIC_ARB_0, + MSM_RPM_8660_ID_APPS_FABRIC_ARB_5 = + MSM_RPM_8660_ID_APPS_FABRIC_ARB_0 + 5, + MSM_RPM_8660_ID_APPS_FABRIC_RESERVED_B_0, + MSM_RPM_8660_ID_APPS_FABRIC_RESERVED_B_5 = + MSM_RPM_8660_ID_APPS_FABRIC_RESERVED_B_0 + 5, + + MSM_RPM_8660_ID_SYSTEM_FABRIC_HALT_0, + MSM_RPM_8660_ID_SYSTEM_FABRIC_HALT_1, + MSM_RPM_8660_ID_SYSTEM_FABRIC_CLOCK_MODE_0, + MSM_RPM_8660_ID_SYSTEM_FABRIC_CLOCK_MODE_1, + MSM_RPM_8660_ID_SYSTEM_FABRIC_CLOCK_MODE_2, + MSM_RPM_8660_ID_SYSTEM_FABRIC_RESERVED_A, + MSM_RPM_8660_ID_SYSTEM_FABRIC_ARB_0, + MSM_RPM_8660_ID_SYSTEM_FABRIC_ARB_21 = + MSM_RPM_8660_ID_SYSTEM_FABRIC_ARB_0 + 21, + MSM_RPM_8660_ID_SYSTEM_FABRIC_RESERVED_B_0, + MSM_RPM_8660_ID_SYSTEM_FABRIC_RESERVED_B_13 = + MSM_RPM_8660_ID_SYSTEM_FABRIC_RESERVED_B_0 + 13, + + MSM_RPM_8660_ID_MM_FABRIC_HALT_0, + MSM_RPM_8660_ID_MM_FABRIC_HALT_1, + MSM_RPM_8660_ID_MM_FABRIC_CLOCK_MODE_0, + MSM_RPM_8660_ID_MM_FABRIC_CLOCK_MODE_1, + MSM_RPM_8660_ID_MM_FABRIC_CLOCK_MODE_2, + MSM_RPM_8660_ID_MM_FABRIC_RESERVED_A, + MSM_RPM_8660_ID_MM_FABRIC_ARB_0, + MSM_RPM_8660_ID_MM_FABRIC_ARB_22 = + MSM_RPM_8660_ID_MM_FABRIC_ARB_0 + 22, + + /* pmic 8901 */ + MSM_RPM_8660_ID_SMPS0B_0, + MSM_RPM_8660_ID_SMPS0B_1, + MSM_RPM_8660_ID_SMPS1B_0, + MSM_RPM_8660_ID_SMPS1B_1, + MSM_RPM_8660_ID_SMPS2B_0, + MSM_RPM_8660_ID_SMPS2B_1, + MSM_RPM_8660_ID_SMPS3B_0, + MSM_RPM_8660_ID_SMPS3B_1, + MSM_RPM_8660_ID_SMPS4B_0, + MSM_RPM_8660_ID_SMPS4B_1, + MSM_RPM_8660_ID_LDO0B_0, + MSM_RPM_8660_ID_LDO0B_1, + MSM_RPM_8660_ID_LDO1B_0, + MSM_RPM_8660_ID_LDO1B_1, + MSM_RPM_8660_ID_LDO2B_0, + MSM_RPM_8660_ID_LDO2B_1, + MSM_RPM_8660_ID_LDO3B_0, + MSM_RPM_8660_ID_LDO3B_1, + MSM_RPM_8660_ID_LDO4B_0, + MSM_RPM_8660_ID_LDO4B_1, + MSM_RPM_8660_ID_LDO5B_0, + MSM_RPM_8660_ID_LDO5B_1, + MSM_RPM_8660_ID_LDO6B_0, + MSM_RPM_8660_ID_LDO6B_1, + MSM_RPM_8660_ID_LVS0B, + MSM_RPM_8660_ID_LVS1B, + MSM_RPM_8660_ID_LVS2B, + MSM_RPM_8660_ID_LVS3B, + MSM_RPM_8660_ID_MVS, + + /* pmic 8058 */ + MSM_RPM_8660_ID_SMPS0_0, + MSM_RPM_8660_ID_SMPS0_1, + MSM_RPM_8660_ID_SMPS1_0, + MSM_RPM_8660_ID_SMPS1_1, + MSM_RPM_8660_ID_SMPS2_0, + MSM_RPM_8660_ID_SMPS2_1, + MSM_RPM_8660_ID_SMPS3_0, + MSM_RPM_8660_ID_SMPS3_1, + MSM_RPM_8660_ID_SMPS4_0, + MSM_RPM_8660_ID_SMPS4_1, + MSM_RPM_8660_ID_LDO0_0, + MSM_RPM_8660_ID_LDO0_1, + MSM_RPM_8660_ID_LDO1_0, + MSM_RPM_8660_ID_LDO1_1, + MSM_RPM_8660_ID_LDO2_0, + MSM_RPM_8660_ID_LDO2_1, + MSM_RPM_8660_ID_LDO3_0, + MSM_RPM_8660_ID_LDO3_1, + MSM_RPM_8660_ID_LDO4_0, + MSM_RPM_8660_ID_LDO4_1, + MSM_RPM_8660_ID_LDO5_0, + MSM_RPM_8660_ID_LDO5_1, + MSM_RPM_8660_ID_LDO6_0, + MSM_RPM_8660_ID_LDO6_1, + MSM_RPM_8660_ID_LDO7_0, + MSM_RPM_8660_ID_LDO7_1, + MSM_RPM_8660_ID_LDO8_0, + MSM_RPM_8660_ID_LDO8_1, + MSM_RPM_8660_ID_LDO9_0, + MSM_RPM_8660_ID_LDO9_1, + MSM_RPM_8660_ID_LDO10_0, + MSM_RPM_8660_ID_LDO10_1, + MSM_RPM_8660_ID_LDO11_0, + MSM_RPM_8660_ID_LDO11_1, + MSM_RPM_8660_ID_LDO12_0, + MSM_RPM_8660_ID_LDO12_1, + MSM_RPM_8660_ID_LDO13_0, + MSM_RPM_8660_ID_LDO13_1, + MSM_RPM_8660_ID_LDO14_0, + MSM_RPM_8660_ID_LDO14_1, + MSM_RPM_8660_ID_LDO15_0, + MSM_RPM_8660_ID_LDO15_1, + MSM_RPM_8660_ID_LDO16_0, + MSM_RPM_8660_ID_LDO16_1, + MSM_RPM_8660_ID_LDO17_0, + MSM_RPM_8660_ID_LDO17_1, + MSM_RPM_8660_ID_LDO18_0, + MSM_RPM_8660_ID_LDO18_1, + MSM_RPM_8660_ID_LDO19_0, + MSM_RPM_8660_ID_LDO19_1, + MSM_RPM_8660_ID_LDO20_0, + MSM_RPM_8660_ID_LDO20_1, + MSM_RPM_8660_ID_LDO21_0, + MSM_RPM_8660_ID_LDO21_1, + MSM_RPM_8660_ID_LDO22_0, + MSM_RPM_8660_ID_LDO22_1, + MSM_RPM_8660_ID_LDO23_0, + MSM_RPM_8660_ID_LDO23_1, + MSM_RPM_8660_ID_LDO24_0, + MSM_RPM_8660_ID_LDO24_1, + MSM_RPM_8660_ID_LDO25_0, + MSM_RPM_8660_ID_LDO25_1, + MSM_RPM_8660_ID_LVS0, + MSM_RPM_8660_ID_LVS1, + MSM_RPM_8660_ID_NCP_0, + MSM_RPM_8660_ID_NCP_1, + + MSM_RPM_8660_ID_CXO_BUFFERS, + + MSM_RPM_8660_ID_LAST = MSM_RPM_8660_ID_CXO_BUFFERS +}; + +enum { + MSM_RPM_8660_STATUS_ID_VERSION_MAJOR, + MSM_RPM_8660_STATUS_ID_VERSION_MINOR, + MSM_RPM_8660_STATUS_ID_VERSION_BUILD, + MSM_RPM_8660_STATUS_ID_SUPPORTED_RESOURCES_0, + MSM_RPM_8660_STATUS_ID_SUPPORTED_RESOURCES_1, + MSM_RPM_8660_STATUS_ID_SUPPORTED_RESOURCES_2, + MSM_RPM_8660_STATUS_ID_RESERVED_0, + MSM_RPM_8660_STATUS_ID_RESERVED_4 = + MSM_RPM_8660_STATUS_ID_RESERVED_0 + 4, + MSM_RPM_8660_STATUS_ID_SEQUENCE, + + MSM_RPM_8660_STATUS_ID_CXO_CLK, + MSM_RPM_8660_STATUS_ID_PXO_CLK, + MSM_RPM_8660_STATUS_ID_PLL_4, + MSM_RPM_8660_STATUS_ID_APPS_FABRIC_CLK, + MSM_RPM_8660_STATUS_ID_SYSTEM_FABRIC_CLK, + MSM_RPM_8660_STATUS_ID_MM_FABRIC_CLK, + MSM_RPM_8660_STATUS_ID_DAYTONA_FABRIC_CLK, + MSM_RPM_8660_STATUS_ID_SFPB_CLK, + MSM_RPM_8660_STATUS_ID_CFPB_CLK, + MSM_RPM_8660_STATUS_ID_MMFPB_CLK, + MSM_RPM_8660_STATUS_ID_SMI_CLK, + MSM_RPM_8660_STATUS_ID_EBI1_CLK, + + MSM_RPM_8660_STATUS_ID_APPS_L2_CACHE_CTL, + + MSM_RPM_8660_STATUS_ID_APPS_FABRIC_HALT, + MSM_RPM_8660_STATUS_ID_APPS_FABRIC_CLOCK_MODE, + MSM_RPM_8660_STATUS_ID_APPS_FABRIC_RESERVED, + MSM_RPM_8660_STATUS_ID_APPS_FABRIC_ARB, + + MSM_RPM_8660_STATUS_ID_SYSTEM_FABRIC_HALT, + MSM_RPM_8660_STATUS_ID_SYSTEM_FABRIC_CLOCK_MODE, + MSM_RPM_8660_STATUS_ID_SYSTEM_FABRIC_RESERVED, + MSM_RPM_8660_STATUS_ID_SYSTEM_FABRIC_ARB, + + MSM_RPM_8660_STATUS_ID_MM_FABRIC_HALT, + MSM_RPM_8660_STATUS_ID_MM_FABRIC_CLOCK_MODE, + MSM_RPM_8660_STATUS_ID_MM_FABRIC_RESERVED, + MSM_RPM_8660_STATUS_ID_MM_FABRIC_ARB, + + /* pmic 8901 */ + MSM_RPM_8660_STATUS_ID_SMPS0B_0, + MSM_RPM_8660_STATUS_ID_SMPS0B_1, + MSM_RPM_8660_STATUS_ID_SMPS1B_0, + MSM_RPM_8660_STATUS_ID_SMPS1B_1, + MSM_RPM_8660_STATUS_ID_SMPS2B_0, + MSM_RPM_8660_STATUS_ID_SMPS2B_1, + MSM_RPM_8660_STATUS_ID_SMPS3B_0, + MSM_RPM_8660_STATUS_ID_SMPS3B_1, + MSM_RPM_8660_STATUS_ID_SMPS4B_0, + MSM_RPM_8660_STATUS_ID_SMPS4B_1, + MSM_RPM_8660_STATUS_ID_LDO0B_0, + MSM_RPM_8660_STATUS_ID_LDO0B_1, + MSM_RPM_8660_STATUS_ID_LDO1B_0, + MSM_RPM_8660_STATUS_ID_LDO1B_1, + MSM_RPM_8660_STATUS_ID_LDO2B_0, + MSM_RPM_8660_STATUS_ID_LDO2B_1, + MSM_RPM_8660_STATUS_ID_LDO3B_0, + MSM_RPM_8660_STATUS_ID_LDO3B_1, + MSM_RPM_8660_STATUS_ID_LDO4B_0, + MSM_RPM_8660_STATUS_ID_LDO4B_1, + MSM_RPM_8660_STATUS_ID_LDO5B_0, + MSM_RPM_8660_STATUS_ID_LDO5B_1, + MSM_RPM_8660_STATUS_ID_LDO6B_0, + MSM_RPM_8660_STATUS_ID_LDO6B_1, + MSM_RPM_8660_STATUS_ID_LVS0B, + MSM_RPM_8660_STATUS_ID_LVS1B, + MSM_RPM_8660_STATUS_ID_LVS2B, + MSM_RPM_8660_STATUS_ID_LVS3B, + MSM_RPM_8660_STATUS_ID_MVS, + + /* pmic 8058 */ + MSM_RPM_8660_STATUS_ID_SMPS0_0, + MSM_RPM_8660_STATUS_ID_SMPS0_1, + MSM_RPM_8660_STATUS_ID_SMPS1_0, + MSM_RPM_8660_STATUS_ID_SMPS1_1, + MSM_RPM_8660_STATUS_ID_SMPS2_0, + MSM_RPM_8660_STATUS_ID_SMPS2_1, + MSM_RPM_8660_STATUS_ID_SMPS3_0, + MSM_RPM_8660_STATUS_ID_SMPS3_1, + MSM_RPM_8660_STATUS_ID_SMPS4_0, + MSM_RPM_8660_STATUS_ID_SMPS4_1, + MSM_RPM_8660_STATUS_ID_LDO0_0, + MSM_RPM_8660_STATUS_ID_LDO0_1, + MSM_RPM_8660_STATUS_ID_LDO1_0, + MSM_RPM_8660_STATUS_ID_LDO1_1, + MSM_RPM_8660_STATUS_ID_LDO2_0, + MSM_RPM_8660_STATUS_ID_LDO2_1, + MSM_RPM_8660_STATUS_ID_LDO3_0, + MSM_RPM_8660_STATUS_ID_LDO3_1, + MSM_RPM_8660_STATUS_ID_LDO4_0, + MSM_RPM_8660_STATUS_ID_LDO4_1, + MSM_RPM_8660_STATUS_ID_LDO5_0, + MSM_RPM_8660_STATUS_ID_LDO5_1, + MSM_RPM_8660_STATUS_ID_LDO6_0, + MSM_RPM_8660_STATUS_ID_LDO6_1, + MSM_RPM_8660_STATUS_ID_LDO7_0, + MSM_RPM_8660_STATUS_ID_LDO7_1, + MSM_RPM_8660_STATUS_ID_LDO8_0, + MSM_RPM_8660_STATUS_ID_LDO8_1, + MSM_RPM_8660_STATUS_ID_LDO9_0, + MSM_RPM_8660_STATUS_ID_LDO9_1, + MSM_RPM_8660_STATUS_ID_LDO10_0, + MSM_RPM_8660_STATUS_ID_LDO10_1, + MSM_RPM_8660_STATUS_ID_LDO11_0, + MSM_RPM_8660_STATUS_ID_LDO11_1, + MSM_RPM_8660_STATUS_ID_LDO12_0, + MSM_RPM_8660_STATUS_ID_LDO12_1, + MSM_RPM_8660_STATUS_ID_LDO13_0, + MSM_RPM_8660_STATUS_ID_LDO13_1, + MSM_RPM_8660_STATUS_ID_LDO14_0, + MSM_RPM_8660_STATUS_ID_LDO14_1, + MSM_RPM_8660_STATUS_ID_LDO15_0, + MSM_RPM_8660_STATUS_ID_LDO15_1, + MSM_RPM_8660_STATUS_ID_LDO16_0, + MSM_RPM_8660_STATUS_ID_LDO16_1, + MSM_RPM_8660_STATUS_ID_LDO17_0, + MSM_RPM_8660_STATUS_ID_LDO17_1, + MSM_RPM_8660_STATUS_ID_LDO18_0, + MSM_RPM_8660_STATUS_ID_LDO18_1, + MSM_RPM_8660_STATUS_ID_LDO19_0, + MSM_RPM_8660_STATUS_ID_LDO19_1, + MSM_RPM_8660_STATUS_ID_LDO20_0, + MSM_RPM_8660_STATUS_ID_LDO20_1, + MSM_RPM_8660_STATUS_ID_LDO21_0, + MSM_RPM_8660_STATUS_ID_LDO21_1, + MSM_RPM_8660_STATUS_ID_LDO22_0, + MSM_RPM_8660_STATUS_ID_LDO22_1, + MSM_RPM_8660_STATUS_ID_LDO23_0, + MSM_RPM_8660_STATUS_ID_LDO23_1, + MSM_RPM_8660_STATUS_ID_LDO24_0, + MSM_RPM_8660_STATUS_ID_LDO24_1, + MSM_RPM_8660_STATUS_ID_LDO25_0, + MSM_RPM_8660_STATUS_ID_LDO25_1, + MSM_RPM_8660_STATUS_ID_LVS0, + MSM_RPM_8660_STATUS_ID_LVS1, + MSM_RPM_8660_STATUS_ID_NCP_0, + MSM_RPM_8660_STATUS_ID_NCP_1, + + MSM_RPM_8660_STATUS_ID_CXO_BUFFERS, + + MSM_RPM_8660_STATUS_ID_LAST = + MSM_RPM_8660_STATUS_ID_CXO_BUFFERS +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_8660_H */ diff --git a/arch/arm/mach-msm/include/mach/rpm-8930.h b/arch/arm/mach-msm/include/mach/rpm-8930.h new file mode 100644 index 0000000000000000000000000000000000000000..5ec3a7466798e24498bf04dfddacf5c8308c8f98 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-8930.h @@ -0,0 +1,363 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_8930_H +#define __ARCH_ARM_MACH_MSM_RPM_8930_H + +/* RPM control message RAM enums */ +enum { + MSM_RPM_8930_CTRL_VERSION_MAJOR, + MSM_RPM_8930_CTRL_VERSION_MINOR, + MSM_RPM_8930_CTRL_VERSION_BUILD, + + MSM_RPM_8930_CTRL_REQ_CTX_0, + MSM_RPM_8930_CTRL_REQ_CTX_7 = MSM_RPM_8930_CTRL_REQ_CTX_0 + 7, + MSM_RPM_8930_CTRL_REQ_SEL_0, + MSM_RPM_8930_CTRL_REQ_SEL_3 = MSM_RPM_8930_CTRL_REQ_SEL_0 + 3, + MSM_RPM_8930_CTRL_ACK_CTX_0, + MSM_RPM_8930_CTRL_ACK_CTX_7 = MSM_RPM_8930_CTRL_ACK_CTX_0 + 7, + MSM_RPM_8930_CTRL_ACK_SEL_0, + MSM_RPM_8930_CTRL_ACK_SEL_7 = MSM_RPM_8930_CTRL_ACK_SEL_0 + 7, +}; + +/* RPM resource select enums defined for RPM core + NOT IN SEQUENTIAL ORDER */ +enum { + MSM_RPM_8930_SEL_NOTIFICATION = 0, + MSM_RPM_8930_SEL_INVALIDATE = 1, + MSM_RPM_8930_SEL_TRIGGER_TIMED_0 = 2, + MSM_RPM_8930_SEL_RPM_CTL = 3, + MSM_RPM_8930_SEL_CXO_CLK = 5, + MSM_RPM_8930_SEL_PXO_CLK = 6, + MSM_RPM_8930_SEL_QDSS_CLK = 7, + MSM_RPM_8930_SEL_APPS_FABRIC_CLK = 8, + MSM_RPM_8930_SEL_SYSTEM_FABRIC_CLK = 9, + MSM_RPM_8930_SEL_MM_FABRIC_CLK = 10, + MSM_RPM_8930_SEL_DAYTONA_FABRIC_CLK = 11, + MSM_RPM_8930_SEL_SFPB_CLK = 12, + MSM_RPM_8930_SEL_CFPB_CLK = 13, + MSM_RPM_8930_SEL_MMFPB_CLK = 14, + MSM_RPM_8930_SEL_EBI1_CLK = 16, + MSM_RPM_8930_SEL_APPS_FABRIC_CFG_HALT = 18, + MSM_RPM_8930_SEL_APPS_FABRIC_CFG_CLKMOD = 19, + MSM_RPM_8930_SEL_APPS_FABRIC_CFG_IOCTL = 20, + MSM_RPM_8930_SEL_APPS_FABRIC_ARB = 21, + MSM_RPM_8930_SEL_SYS_FABRIC_CFG_HALT = 22, + MSM_RPM_8930_SEL_SYS_FABRIC_CFG_CLKMOD = 23, + MSM_RPM_8930_SEL_SYS_FABRIC_CFG_IOCTL = 24, + MSM_RPM_8930_SEL_SYSTEM_FABRIC_ARB = 25, + MSM_RPM_8930_SEL_MMSS_FABRIC_CFG_HALT = 26, + MSM_RPM_8930_SEL_MMSS_FABRIC_CFG_CLKMOD = 27, + MSM_RPM_8930_SEL_MMSS_FABRIC_CFG_IOCTL = 28, + MSM_RPM_8930_SEL_MM_FABRIC_ARB = 29, + MSM_RPM_8930_SEL_PM8038_S1 = 30, + MSM_RPM_8930_SEL_PM8038_S2 = 31, + MSM_RPM_8930_SEL_PM8038_S3 = 32, + MSM_RPM_8930_SEL_PM8038_S4 = 33, + MSM_RPM_8930_SEL_PM8038_S5 = 34, + MSM_RPM_8930_SEL_PM8038_S6 = 35, + MSM_RPM_8930_SEL_PM8038_L1 = 36, + MSM_RPM_8930_SEL_PM8038_L2 = 37, + MSM_RPM_8930_SEL_PM8038_L3 = 38, + MSM_RPM_8930_SEL_PM8038_L4 = 39, + MSM_RPM_8930_SEL_PM8038_L5 = 40, + MSM_RPM_8930_SEL_PM8038_L6 = 41, + MSM_RPM_8930_SEL_PM8038_L7 = 42, + MSM_RPM_8930_SEL_PM8038_L8 = 43, + MSM_RPM_8930_SEL_PM8038_L9 = 44, + MSM_RPM_8930_SEL_PM8038_L10 = 45, + MSM_RPM_8930_SEL_PM8038_L11 = 46, + MSM_RPM_8930_SEL_PM8038_L12 = 47, + MSM_RPM_8930_SEL_PM8038_L13 = 48, + MSM_RPM_8930_SEL_PM8038_L14 = 49, + MSM_RPM_8930_SEL_PM8038_L15 = 50, + MSM_RPM_8930_SEL_PM8038_L16 = 51, + MSM_RPM_8930_SEL_PM8038_L17 = 52, + MSM_RPM_8930_SEL_PM8038_L18 = 53, + MSM_RPM_8930_SEL_PM8038_L19 = 54, + MSM_RPM_8930_SEL_PM8038_L20 = 55, + MSM_RPM_8930_SEL_PM8038_L21 = 56, + MSM_RPM_8930_SEL_PM8038_L22 = 57, + MSM_RPM_8930_SEL_PM8038_L23 = 58, + MSM_RPM_8930_SEL_PM8038_L24 = 59, + MSM_RPM_8930_SEL_PM8038_L25 = 60, + MSM_RPM_8930_SEL_PM8038_L26 = 61, + MSM_RPM_8930_SEL_PM8038_L27 = 62, + MSM_RPM_8930_SEL_PM8038_CLK1 = 63, + MSM_RPM_8930_SEL_PM8038_CLK2 = 64, + MSM_RPM_8930_SEL_PM8038_LVS1 = 65, + MSM_RPM_8930_SEL_PM8038_LVS2 = 66, + MSM_RPM_8930_SEL_NCP = 80, + MSM_RPM_8930_SEL_CXO_BUFFERS = 81, + MSM_RPM_8930_SEL_USB_OTG_SWITCH = 82, + MSM_RPM_8930_SEL_HDMI_SWITCH = 83, + MSM_RPM_8930_SEL_DDR_DMM = 84, + MSM_RPM_8930_SEL_VOLTAGE_CORNER = 87, + MSM_RPM_8930_SEL_LAST = MSM_RPM_8930_SEL_VOLTAGE_CORNER, +}; + +/* RPM resource (4 byte) word ID enum */ +enum { + MSM_RPM_8930_ID_NOTIFICATION_CONFIGURED_0 = 0, + MSM_RPM_8930_ID_NOTIFICATION_CONFIGURED_3 = + MSM_RPM_8930_ID_NOTIFICATION_CONFIGURED_0 + 3, + + MSM_RPM_8930_ID_NOTIFICATION_REGISTERED_0 = 4, + MSM_RPM_8930_ID_NOTIFICATION_REGISTERED_3 = + MSM_RPM_8930_ID_NOTIFICATION_REGISTERED_0 + 3, + + MSM_RPM_8930_ID_INVALIDATE_0 = 8, + MSM_RPM_8930_ID_INVALIDATE_7 = + MSM_RPM_8930_ID_INVALIDATE_0 + 7, + + MSM_RPM_8930_ID_TRIGGER_TIMED_TO_ = 16, + MSM_RPM_8930_ID_TRIGGER_TIMED_SCLK_COUNT = 17, + MSM_RPM_8930_ID_RPM_CTL = 18, + MSM_RPM_8930_ID_RESERVED_0 = 19, + MSM_RPM_8930_ID_RESERVED_5 = + MSM_RPM_8930_ID_RESERVED_0 + 5, + MSM_RPM_8930_ID_CXO_CLK = 25, + MSM_RPM_8930_ID_PXO_CLK = 26, + MSM_RPM_8930_ID_APPS_FABRIC_CLK = 27, + MSM_RPM_8930_ID_SYSTEM_FABRIC_CLK = 28, + MSM_RPM_8930_ID_MM_FABRIC_CLK = 29, + MSM_RPM_8930_ID_DAYTONA_FABRIC_CLK = 30, + MSM_RPM_8930_ID_SFPB_CLK = 31, + MSM_RPM_8930_ID_CFPB_CLK = 32, + MSM_RPM_8930_ID_MMFPB_CLK = 33, + MSM_RPM_8930_ID_EBI1_CLK = 34, + MSM_RPM_8930_ID_APPS_FABRIC_CFG_HALT_0 = 35, + MSM_RPM_8930_ID_APPS_FABRIC_CFG_HALT_1 = 36, + MSM_RPM_8930_ID_APPS_FABRIC_CFG_CLKMOD_0 = 37, + MSM_RPM_8930_ID_APPS_FABRIC_CFG_CLKMOD_1 = 38, + MSM_RPM_8930_ID_APPS_FABRIC_CFG_CLKMOD_2 = 39, + MSM_RPM_8930_ID_APPS_FABRIC_CFG_IOCTL = 40, + MSM_RPM_8930_ID_APPS_FABRIC_ARB_0 = 41, + MSM_RPM_8930_ID_APPS_FABRIC_ARB_5 = + MSM_RPM_8930_ID_APPS_FABRIC_ARB_0 + 5, + MSM_RPM_8930_ID_SYS_FABRIC_CFG_HALT_0 = 47, + MSM_RPM_8930_ID_SYS_FABRIC_CFG_HALT_1 = 48, + MSM_RPM_8930_ID_SYS_FABRIC_CFG_CLKMOD_0 = 49, + MSM_RPM_8930_ID_SYS_FABRIC_CFG_CLKMOD_1 = 50, + MSM_RPM_8930_ID_SYS_FABRIC_CFG_CLKMOD_2 = 51, + MSM_RPM_8930_ID_SYS_FABRIC_CFG_IOCTL = 52, + MSM_RPM_8930_ID_SYSTEM_FABRIC_ARB_0 = 53, + MSM_RPM_8930_ID_SYSTEM_FABRIC_ARB_19 = + MSM_RPM_8930_ID_SYSTEM_FABRIC_ARB_0 + 19, + MSM_RPM_8930_ID_MMSS_FABRIC_CFG_HALT_0 = 73, + MSM_RPM_8930_ID_MMSS_FABRIC_CFG_HALT_1 = 74, + MSM_RPM_8930_ID_MMSS_FABRIC_CFG_CLKMOD_0 = 75, + MSM_RPM_8930_ID_MMSS_FABRIC_CFG_CLKMOD_1 = 76, + MSM_RPM_8930_ID_MMSS_FABRIC_CFG_CLKMOD_2 = 77, + MSM_RPM_8930_ID_MMSS_FABRIC_CFG_IOCTL = 78, + MSM_RPM_8930_ID_MM_FABRIC_ARB_0 = 79, + MSM_RPM_8930_ID_MM_FABRIC_ARB_10 = + MSM_RPM_8930_ID_MM_FABRIC_ARB_0 + 10, + + MSM_RPM_8930_ID_PM8038_S1_0 = 90, + MSM_RPM_8930_ID_PM8038_S1_1 = 91, + MSM_RPM_8930_ID_PM8038_S2_0 = 92, + MSM_RPM_8930_ID_PM8038_S2_1 = 93, + MSM_RPM_8930_ID_PM8038_S3_0 = 94, + MSM_RPM_8930_ID_PM8038_S3_1 = 95, + MSM_RPM_8930_ID_PM8038_S4_0 = 96, + MSM_RPM_8930_ID_PM8038_S4_1 = 97, + MSM_RPM_8930_ID_PM8038_S5_0 = 98, + MSM_RPM_8930_ID_PM8038_S5_1 = 99, + MSM_RPM_8930_ID_PM8038_S6_0 = 100, + MSM_RPM_8930_ID_PM8038_S6_1 = 101, + MSM_RPM_8930_ID_PM8038_L1_0 = 102, + MSM_RPM_8930_ID_PM8038_L1_1 = 103, + MSM_RPM_8930_ID_PM8038_L2_0 = 104, + MSM_RPM_8930_ID_PM8038_L2_1 = 105, + MSM_RPM_8930_ID_PM8038_L3_0 = 106, + MSM_RPM_8930_ID_PM8038_L3_1 = 107, + MSM_RPM_8930_ID_PM8038_L4_0 = 108, + MSM_RPM_8930_ID_PM8038_L4_1 = 109, + MSM_RPM_8930_ID_PM8038_L5_0 = 110, + MSM_RPM_8930_ID_PM8038_L5_1 = 111, + MSM_RPM_8930_ID_PM8038_L6_0 = 112, + MSM_RPM_8930_ID_PM8038_L6_1 = 113, + MSM_RPM_8930_ID_PM8038_L7_0 = 114, + MSM_RPM_8930_ID_PM8038_L7_1 = 115, + MSM_RPM_8930_ID_PM8038_L8_0 = 116, + MSM_RPM_8930_ID_PM8038_L8_1 = 117, + MSM_RPM_8930_ID_PM8038_L9_0 = 118, + MSM_RPM_8930_ID_PM8038_L9_1 = 119, + MSM_RPM_8930_ID_PM8038_L10_0 = 120, + MSM_RPM_8930_ID_PM8038_L10_1 = 121, + MSM_RPM_8930_ID_PM8038_L11_0 = 122, + MSM_RPM_8930_ID_PM8038_L11_1 = 123, + MSM_RPM_8930_ID_PM8038_L12_0 = 124, + MSM_RPM_8930_ID_PM8038_L12_1 = 125, + MSM_RPM_8930_ID_PM8038_L13_0 = 126, + MSM_RPM_8930_ID_PM8038_L13_1 = 127, + MSM_RPM_8930_ID_PM8038_L14_0 = 128, + MSM_RPM_8930_ID_PM8038_L14_1 = 129, + MSM_RPM_8930_ID_PM8038_L15_0 = 130, + MSM_RPM_8930_ID_PM8038_L15_1 = 131, + MSM_RPM_8930_ID_PM8038_L16_0 = 132, + MSM_RPM_8930_ID_PM8038_L16_1 = 133, + MSM_RPM_8930_ID_PM8038_L17_0 = 134, + MSM_RPM_8930_ID_PM8038_L17_1 = 135, + MSM_RPM_8930_ID_PM8038_L18_0 = 136, + MSM_RPM_8930_ID_PM8038_L18_1 = 137, + MSM_RPM_8930_ID_PM8038_L19_0 = 138, + MSM_RPM_8930_ID_PM8038_L19_1 = 139, + MSM_RPM_8930_ID_PM8038_L20_0 = 140, + MSM_RPM_8930_ID_PM8038_L20_1 = 141, + MSM_RPM_8930_ID_PM8038_L21_0 = 142, + MSM_RPM_8930_ID_PM8038_L21_1 = 143, + MSM_RPM_8930_ID_PM8038_L22_0 = 144, + MSM_RPM_8930_ID_PM8038_L22_1 = 145, + MSM_RPM_8930_ID_PM8038_L23_0 = 146, + MSM_RPM_8930_ID_PM8038_L23_1 = 147, + MSM_RPM_8930_ID_PM8038_L24_0 = 148, + MSM_RPM_8930_ID_PM8038_L24_1 = 149, + MSM_RPM_8930_ID_PM8038_L25_0 = 150, + MSM_RPM_8930_ID_PM8038_L25_1 = 151, + MSM_RPM_8930_ID_PM8038_L26_0 = 152, + MSM_RPM_8930_ID_PM8038_L26_1 = 153, + MSM_RPM_8930_ID_PM8038_L27_0 = 154, + MSM_RPM_8930_ID_PM8038_L27_1 = 155, + MSM_RPM_8930_ID_PM8038_CLK1_0 = 156, + MSM_RPM_8930_ID_PM8038_CLK1_1 = 157, + MSM_RPM_8930_ID_PM8038_CLK2_0 = 158, + MSM_RPM_8930_ID_PM8038_CLK2_1 = 159, + MSM_RPM_8930_ID_PM8038_LVS1 = 160, + MSM_RPM_8930_ID_PM8038_LVS2 = 161, + MSM_RPM_8930_ID_NCP_0 = 162, + MSM_RPM_8930_ID_NCP_1 = 163, + MSM_RPM_8930_ID_CXO_BUFFERS = 164, + MSM_RPM_8930_ID_USB_OTG_SWITCH = 165, + MSM_RPM_8930_ID_HDMI_SWITCH = 166, + MSM_RPM_8930_ID_DDR_DMM_0 = 167, + MSM_RPM_8930_ID_DDR_DMM_1 = 168, + MSM_RPM_8930_ID_QDSS_CLK = 168, + MSM_RPM_8930_ID_VOLTAGE_CORNER = 169, + MSM_RPM_8930_ID_LAST = MSM_RPM_8930_ID_VOLTAGE_CORNER, +}; + +/* RPM status ID enum */ +enum { + MSM_RPM_8930_STATUS_ID_VERSION_MAJOR = 0, + MSM_RPM_8930_STATUS_ID_VERSION_MINOR = 1, + MSM_RPM_8930_STATUS_ID_VERSION_BUILD = 2, + MSM_RPM_8930_STATUS_ID_SUPPORTED_RESOURCES_0 = 3, + MSM_RPM_8930_STATUS_ID_SUPPORTED_RESOURCES_1 = 4, + MSM_RPM_8930_STATUS_ID_SUPPORTED_RESOURCES_2 = 5, + MSM_RPM_8930_STATUS_ID_RESERVED_SUPPORTED_RESOURCES_0 = 6, + MSM_RPM_8930_STATUS_ID_SEQUENCE = 7, + MSM_RPM_8930_STATUS_ID_RPM_CTL = 8, + MSM_RPM_8930_STATUS_ID_CXO_CLK = 9, + MSM_RPM_8930_STATUS_ID_PXO_CLK = 10, + MSM_RPM_8930_STATUS_ID_APPS_FABRIC_CLK = 11, + MSM_RPM_8930_STATUS_ID_SYSTEM_FABRIC_CLK = 12, + MSM_RPM_8930_STATUS_ID_MM_FABRIC_CLK = 13, + MSM_RPM_8930_STATUS_ID_DAYTONA_FABRIC_CLK = 14, + MSM_RPM_8930_STATUS_ID_SFPB_CLK = 15, + MSM_RPM_8930_STATUS_ID_CFPB_CLK = 16, + MSM_RPM_8930_STATUS_ID_MMFPB_CLK = 17, + MSM_RPM_8930_STATUS_ID_EBI1_CLK = 18, + MSM_RPM_8930_STATUS_ID_APPS_FABRIC_CFG_HALT = 19, + MSM_RPM_8930_STATUS_ID_APPS_FABRIC_CFG_CLKMOD = 20, + MSM_RPM_8930_STATUS_ID_APPS_FABRIC_CFG_IOCTL = 21, + MSM_RPM_8930_STATUS_ID_APPS_FABRIC_ARB = 22, + MSM_RPM_8930_STATUS_ID_SYS_FABRIC_CFG_HALT = 23, + MSM_RPM_8930_STATUS_ID_SYS_FABRIC_CFG_CLKMOD = 24, + MSM_RPM_8930_STATUS_ID_SYS_FABRIC_CFG_IOCTL = 25, + MSM_RPM_8930_STATUS_ID_SYSTEM_FABRIC_ARB = 26, + MSM_RPM_8930_STATUS_ID_MMSS_FABRIC_CFG_HALT = 27, + MSM_RPM_8930_STATUS_ID_MMSS_FABRIC_CFG_CLKMOD = 28, + MSM_RPM_8930_STATUS_ID_MMSS_FABRIC_CFG_IOCTL = 29, + MSM_RPM_8930_STATUS_ID_MM_FABRIC_ARB = 30, + MSM_RPM_8930_STATUS_ID_PM8038_S1_0 = 31, + MSM_RPM_8930_STATUS_ID_PM8038_S1_1 = 32, + MSM_RPM_8930_STATUS_ID_PM8038_S2_0 = 33, + MSM_RPM_8930_STATUS_ID_PM8038_S2_1 = 34, + MSM_RPM_8930_STATUS_ID_PM8038_S3_0 = 35, + MSM_RPM_8930_STATUS_ID_PM8038_S3_1 = 36, + MSM_RPM_8930_STATUS_ID_PM8038_S4_0 = 37, + MSM_RPM_8930_STATUS_ID_PM8038_S4_1 = 38, + MSM_RPM_8930_STATUS_ID_PM8038_L1_0 = 43, + MSM_RPM_8930_STATUS_ID_PM8038_L1_1 = 44, + MSM_RPM_8930_STATUS_ID_PM8038_L2_0 = 45, + MSM_RPM_8930_STATUS_ID_PM8038_L2_1 = 46, + MSM_RPM_8930_STATUS_ID_PM8038_L3_0 = 47, + MSM_RPM_8930_STATUS_ID_PM8038_L3_1 = 48, + MSM_RPM_8930_STATUS_ID_PM8038_L4_0 = 49, + MSM_RPM_8930_STATUS_ID_PM8038_L4_1 = 50, + MSM_RPM_8930_STATUS_ID_PM8038_L5_0 = 51, + MSM_RPM_8930_STATUS_ID_PM8038_L5_1 = 52, + MSM_RPM_8930_STATUS_ID_PM8038_L6_0 = 53, + MSM_RPM_8930_STATUS_ID_PM8038_L6_1 = 54, + MSM_RPM_8930_STATUS_ID_PM8038_L7_0 = 55, + MSM_RPM_8930_STATUS_ID_PM8038_L7_1 = 56, + MSM_RPM_8930_STATUS_ID_PM8038_L8_0 = 57, + MSM_RPM_8930_STATUS_ID_PM8038_L8_1 = 58, + MSM_RPM_8930_STATUS_ID_PM8038_L9_0 = 59, + MSM_RPM_8930_STATUS_ID_PM8038_L9_1 = 60, + MSM_RPM_8930_STATUS_ID_PM8038_L10_0 = 61, + MSM_RPM_8930_STATUS_ID_PM8038_L10_1 = 62, + MSM_RPM_8930_STATUS_ID_PM8038_L11_0 = 63, + MSM_RPM_8930_STATUS_ID_PM8038_L11_1 = 64, + MSM_RPM_8930_STATUS_ID_PM8038_L12_0 = 65, + MSM_RPM_8930_STATUS_ID_PM8038_L12_1 = 66, + MSM_RPM_8930_STATUS_ID_PM8038_L13_0 = 67, + MSM_RPM_8930_STATUS_ID_PM8038_L13_1 = 68, + MSM_RPM_8930_STATUS_ID_PM8038_L14_0 = 69, + MSM_RPM_8930_STATUS_ID_PM8038_L14_1 = 70, + MSM_RPM_8930_STATUS_ID_PM8038_L15_0 = 71, + MSM_RPM_8930_STATUS_ID_PM8038_L15_1 = 72, + MSM_RPM_8930_STATUS_ID_PM8038_L16_0 = 73, + MSM_RPM_8930_STATUS_ID_PM8038_L16_1 = 74, + MSM_RPM_8930_STATUS_ID_PM8038_L17_0 = 75, + MSM_RPM_8930_STATUS_ID_PM8038_L17_1 = 76, + MSM_RPM_8930_STATUS_ID_PM8038_L18_0 = 77, + MSM_RPM_8930_STATUS_ID_PM8038_L18_1 = 78, + MSM_RPM_8930_STATUS_ID_PM8038_L19_0 = 79, + MSM_RPM_8930_STATUS_ID_PM8038_L19_1 = 80, + MSM_RPM_8930_STATUS_ID_PM8038_L20_0 = 81, + MSM_RPM_8930_STATUS_ID_PM8038_L20_1 = 82, + MSM_RPM_8930_STATUS_ID_PM8038_L21_0 = 83, + MSM_RPM_8930_STATUS_ID_PM8038_L21_1 = 84, + MSM_RPM_8930_STATUS_ID_PM8038_L22_0 = 85, + MSM_RPM_8930_STATUS_ID_PM8038_L22_1 = 86, + MSM_RPM_8930_STATUS_ID_PM8038_L23_0 = 87, + MSM_RPM_8930_STATUS_ID_PM8038_L23_1 = 88, + MSM_RPM_8930_STATUS_ID_PM8038_L24_0 = 89, + MSM_RPM_8930_STATUS_ID_PM8038_L24_1 = 90, + MSM_RPM_8930_STATUS_ID_PM8038_L25_0 = 91, + MSM_RPM_8930_STATUS_ID_PM8038_L25_1 = 92, + MSM_RPM_8930_STATUS_ID_PM8038_L26_0 = 93, + MSM_RPM_8930_STATUS_ID_PM8038_L26_1 = 94, + MSM_RPM_8930_STATUS_ID_PM8038_L27_0 = 95, + MSM_RPM_8930_STATUS_ID_PM8038_L27_1 = 96, + MSM_RPM_8930_STATUS_ID_PM8038_CLK1_0 = 97, + MSM_RPM_8930_STATUS_ID_PM8038_CLK1_1 = 98, + MSM_RPM_8930_STATUS_ID_PM8038_CLK2_0 = 99, + MSM_RPM_8930_STATUS_ID_PM8038_CLK2_1 = 100, + MSM_RPM_8930_STATUS_ID_PM8038_LVS1 = 101, + MSM_RPM_8930_STATUS_ID_PM8038_LVS2 = 102, + MSM_RPM_8930_STATUS_ID_NCP_0 = 103, + MSM_RPM_8930_STATUS_ID_NCP_1 = 104, + MSM_RPM_8930_STATUS_ID_CXO_BUFFERS = 105, + MSM_RPM_8930_STATUS_ID_USB_OTG_SWITCH = 106, + MSM_RPM_8930_STATUS_ID_HDMI_SWITCH = 107, + MSM_RPM_8930_STATUS_ID_DDR_DMM_0 = 108, + MSM_RPM_8930_STATUS_ID_DDR_DMM_1 = 109, + MSM_RPM_8930_STATUS_ID_QDSS_CLK = 110, + MSM_RPM_8930_STATUS_ID_VOLTAGE_CORNER = 111, + MSM_RPM_8930_STATUS_ID_LAST = MSM_RPM_8930_STATUS_ID_VOLTAGE_CORNER, +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_8930_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 0000000000000000000000000000000000000000..6fe8832e4d88f0c570fc6c0473d5f6d40facc7cd --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-8960.h @@ -0,0 +1,416 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_8960_CTRL_VERSION_MAJOR, + MSM_RPM_8960_CTRL_VERSION_MINOR, + MSM_RPM_8960_CTRL_VERSION_BUILD, + + MSM_RPM_8960_CTRL_REQ_CTX_0, + MSM_RPM_8960_CTRL_REQ_CTX_7 = MSM_RPM_8960_CTRL_REQ_CTX_0 + 7, + MSM_RPM_8960_CTRL_REQ_SEL_0, + MSM_RPM_8960_CTRL_REQ_SEL_3 = MSM_RPM_8960_CTRL_REQ_SEL_0 + 3, + MSM_RPM_8960_CTRL_ACK_CTX_0, + MSM_RPM_8960_CTRL_ACK_CTX_7 = MSM_RPM_8960_CTRL_ACK_CTX_0 + 7, + MSM_RPM_8960_CTRL_ACK_SEL_0, + MSM_RPM_8960_CTRL_ACK_SEL_7 = MSM_RPM_8960_CTRL_ACK_SEL_0 + 7, +}; + +/* RPM resource select enums defined for RPM core + NOT IN SEQUENTIAL ORDER */ +enum { + MSM_RPM_8960_SEL_NOTIFICATION = 0, + MSM_RPM_8960_SEL_INVALIDATE = 1, + MSM_RPM_8960_SEL_TRIGGER_TIMED = 2, + MSM_RPM_8960_SEL_RPM_CTL = 3, + + MSM_RPM_8960_SEL_CXO_CLK = 5, + MSM_RPM_8960_SEL_PXO_CLK = 6, + MSM_RPM_8960_SEL_QDSS_CLK = 7, + MSM_RPM_8960_SEL_APPS_FABRIC_CLK = 8, + MSM_RPM_8960_SEL_SYSTEM_FABRIC_CLK = 9, + MSM_RPM_8960_SEL_MM_FABRIC_CLK = 10, + MSM_RPM_8960_SEL_DAYTONA_FABRIC_CLK = 11, + MSM_RPM_8960_SEL_SFPB_CLK = 12, + MSM_RPM_8960_SEL_CFPB_CLK = 13, + MSM_RPM_8960_SEL_MMFPB_CLK = 14, + MSM_RPM_8960_SEL_EBI1_CLK = 16, + + MSM_RPM_8960_SEL_APPS_FABRIC_CFG_HALT = 18, + MSM_RPM_8960_SEL_APPS_FABRIC_CFG_CLKMOD = 19, + MSM_RPM_8960_SEL_APPS_FABRIC_CFG_IOCTL = 20, + MSM_RPM_8960_SEL_APPS_FABRIC_ARB = 21, + + MSM_RPM_8960_SEL_SYS_FABRIC_CFG_HALT = 22, + MSM_RPM_8960_SEL_SYS_FABRIC_CFG_CLKMOD = 23, + MSM_RPM_8960_SEL_SYS_FABRIC_CFG_IOCTL = 24, + MSM_RPM_8960_SEL_SYSTEM_FABRIC_ARB = 25, + + MSM_RPM_8960_SEL_MMSS_FABRIC_CFG_HALT = 26, + MSM_RPM_8960_SEL_MMSS_FABRIC_CFG_CLKMOD = 27, + MSM_RPM_8960_SEL_MMSS_FABRIC_CFG_IOCTL = 28, + MSM_RPM_8960_SEL_MM_FABRIC_ARB = 29, + + MSM_RPM_8960_SEL_PM8921_S1 = 30, + MSM_RPM_8960_SEL_PM8921_S2 = 31, + MSM_RPM_8960_SEL_PM8921_S3 = 32, + MSM_RPM_8960_SEL_PM8921_S4 = 33, + MSM_RPM_8960_SEL_PM8921_S5 = 34, + MSM_RPM_8960_SEL_PM8921_S6 = 35, + MSM_RPM_8960_SEL_PM8921_S7 = 36, + MSM_RPM_8960_SEL_PM8921_S8 = 37, + MSM_RPM_8960_SEL_PM8921_L1 = 38, + MSM_RPM_8960_SEL_PM8921_L2 = 39, + MSM_RPM_8960_SEL_PM8921_L3 = 40, + MSM_RPM_8960_SEL_PM8921_L4 = 41, + MSM_RPM_8960_SEL_PM8921_L5 = 42, + MSM_RPM_8960_SEL_PM8921_L6 = 43, + MSM_RPM_8960_SEL_PM8921_L7 = 44, + MSM_RPM_8960_SEL_PM8921_L8 = 45, + MSM_RPM_8960_SEL_PM8921_L9 = 46, + MSM_RPM_8960_SEL_PM8921_L10 = 47, + MSM_RPM_8960_SEL_PM8921_L11 = 48, + MSM_RPM_8960_SEL_PM8921_L12 = 49, + MSM_RPM_8960_SEL_PM8921_L13 = 50, + MSM_RPM_8960_SEL_PM8921_L14 = 51, + MSM_RPM_8960_SEL_PM8921_L15 = 52, + MSM_RPM_8960_SEL_PM8921_L16 = 53, + MSM_RPM_8960_SEL_PM8921_L17 = 54, + MSM_RPM_8960_SEL_PM8921_L18 = 55, + MSM_RPM_8960_SEL_PM8921_L19 = 56, + MSM_RPM_8960_SEL_PM8921_L20 = 57, + MSM_RPM_8960_SEL_PM8921_L21 = 58, + MSM_RPM_8960_SEL_PM8921_L22 = 59, + MSM_RPM_8960_SEL_PM8921_L23 = 60, + MSM_RPM_8960_SEL_PM8921_L24 = 61, + MSM_RPM_8960_SEL_PM8921_L25 = 62, + MSM_RPM_8960_SEL_PM8921_L26 = 63, + MSM_RPM_8960_SEL_PM8921_L27 = 64, + MSM_RPM_8960_SEL_PM8921_L28 = 65, + MSM_RPM_8960_SEL_PM8921_L29 = 66, + MSM_RPM_8960_SEL_PM8921_CLK1 = 67, + MSM_RPM_8960_SEL_PM8921_CLK2 = 68, + MSM_RPM_8960_SEL_PM8921_LVS1 = 69, + MSM_RPM_8960_SEL_PM8921_LVS2 = 70, + MSM_RPM_8960_SEL_PM8921_LVS3 = 71, + MSM_RPM_8960_SEL_PM8921_LVS4 = 72, + MSM_RPM_8960_SEL_PM8921_LVS5 = 73, + MSM_RPM_8960_SEL_PM8921_LVS6 = 74, + MSM_RPM_8960_SEL_PM8921_LVS7 = 75, + + MSM_RPM_8960_SEL_NCP = 80, + MSM_RPM_8960_SEL_CXO_BUFFERS = 81, + MSM_RPM_8960_SEL_USB_OTG_SWITCH = 82, + MSM_RPM_8960_SEL_HDMI_SWITCH = 83, + MSM_RPM_8960_SEL_DDR_DMM = 84, + + MSM_RPM_8960_SEL_LAST = MSM_RPM_8960_SEL_DDR_DMM, +}; + +/* RPM resource (4 byte) word ID enum */ +enum { + MSM_RPM_8960_ID_NOTIFICATION_CONFIGURED_0 = 0, + MSM_RPM_8960_ID_NOTIFICATION_CONFIGURED_3 = + MSM_RPM_8960_ID_NOTIFICATION_CONFIGURED_0 + 3, + + MSM_RPM_8960_ID_NOTIFICATION_REGISTERED_0 = 4, + MSM_RPM_8960_ID_NOTIFICATION_REGISTERED_3 = + MSM_RPM_8960_ID_NOTIFICATION_REGISTERED_0 + 3, + + MSM_RPM_8960_ID_INVALIDATE_0 = 8, + MSM_RPM_8960_ID_INVALIDATE_7 = + MSM_RPM_8960_ID_INVALIDATE_0 + 7, + + MSM_RPM_8960_ID_TRIGGER_TIMED_TO = 16, + MSM_RPM_8960_ID_TRIGGER_TIMED_SCLK_COUNT = 17, + + MSM_RPM_8960_ID_RPM_CTL = 18, + + /* TRIGGER_CLEAR/SET deprecated in these 24 RESERVED bytes */ + MSM_RPM_8960_ID_RESERVED_0 = 19, + MSM_RPM_8960_ID_RESERVED_5 = + MSM_RPM_8960_ID_RESERVED_0 + 5, + + MSM_RPM_8960_ID_CXO_CLK = 25, + MSM_RPM_8960_ID_PXO_CLK = 26, + MSM_RPM_8960_ID_APPS_FABRIC_CLK = 27, + MSM_RPM_8960_ID_SYSTEM_FABRIC_CLK = 28, + MSM_RPM_8960_ID_MM_FABRIC_CLK = 29, + MSM_RPM_8960_ID_DAYTONA_FABRIC_CLK = 30, + MSM_RPM_8960_ID_SFPB_CLK = 31, + MSM_RPM_8960_ID_CFPB_CLK = 32, + MSM_RPM_8960_ID_MMFPB_CLK = 33, + MSM_RPM_8960_ID_EBI1_CLK = 34, + + MSM_RPM_8960_ID_APPS_FABRIC_CFG_HALT_0 = 35, + MSM_RPM_8960_ID_APPS_FABRIC_CFG_HALT_1 = 36, + MSM_RPM_8960_ID_APPS_FABRIC_CFG_CLKMOD_0 = 37, + MSM_RPM_8960_ID_APPS_FABRIC_CFG_CLKMOD_1 = 38, + MSM_RPM_8960_ID_APPS_FABRIC_CFG_CLKMOD_2 = 39, + MSM_RPM_8960_ID_APPS_FABRIC_CFG_IOCTL = 40, + MSM_RPM_8960_ID_APPS_FABRIC_ARB_0 = 41, + MSM_RPM_8960_ID_APPS_FABRIC_ARB_11 = + MSM_RPM_8960_ID_APPS_FABRIC_ARB_0 + 11, + + MSM_RPM_8960_ID_SYS_FABRIC_CFG_HALT_0 = 53, + MSM_RPM_8960_ID_SYS_FABRIC_CFG_HALT_1 = 54, + MSM_RPM_8960_ID_SYS_FABRIC_CFG_CLKMOD_0 = 55, + MSM_RPM_8960_ID_SYS_FABRIC_CFG_CLKMOD_1 = 56, + MSM_RPM_8960_ID_SYS_FABRIC_CFG_CLKMOD_2 = 57, + MSM_RPM_8960_ID_SYS_FABRIC_CFG_IOCTL = 58, + MSM_RPM_8960_ID_SYSTEM_FABRIC_ARB_0 = 59, + MSM_RPM_8960_ID_SYSTEM_FABRIC_ARB_28 = + MSM_RPM_8960_ID_SYSTEM_FABRIC_ARB_0 + 28, + + MSM_RPM_8960_ID_MMSS_FABRIC_CFG_HALT_0 = 88, + MSM_RPM_8960_ID_MMSS_FABRIC_CFG_HALT_1 = 89, + MSM_RPM_8960_ID_MMSS_FABRIC_CFG_CLKMOD_0 = 90, + MSM_RPM_8960_ID_MMSS_FABRIC_CFG_CLKMOD_1 = 91, + MSM_RPM_8960_ID_MMSS_FABRIC_CFG_CLKMOD_2 = 92, + MSM_RPM_8960_ID_MMSS_FABRIC_CFG_IOCTL = 93, + MSM_RPM_8960_ID_MM_FABRIC_ARB_0 = 94, + MSM_RPM_8960_ID_MM_FABRIC_ARB_22 = + MSM_RPM_8960_ID_MM_FABRIC_ARB_0 + 22, + + MSM_RPM_8960_ID_PM8921_S1_0 = 117, + MSM_RPM_8960_ID_PM8921_S1_1 = 118, + MSM_RPM_8960_ID_PM8921_S2_0 = 119, + MSM_RPM_8960_ID_PM8921_S2_1 = 120, + MSM_RPM_8960_ID_PM8921_S3_0 = 121, + MSM_RPM_8960_ID_PM8921_S3_1 = 122, + MSM_RPM_8960_ID_PM8921_S4_0 = 123, + MSM_RPM_8960_ID_PM8921_S4_1 = 124, + MSM_RPM_8960_ID_PM8921_S5_0 = 125, + MSM_RPM_8960_ID_PM8921_S5_1 = 126, + MSM_RPM_8960_ID_PM8921_S6_0 = 127, + MSM_RPM_8960_ID_PM8921_S6_1 = 128, + MSM_RPM_8960_ID_PM8921_S7_0 = 129, + MSM_RPM_8960_ID_PM8921_S7_1 = 130, + MSM_RPM_8960_ID_PM8921_S8_0 = 131, + MSM_RPM_8960_ID_PM8921_S8_1 = 132, + MSM_RPM_8960_ID_PM8921_L1_0 = 133, + MSM_RPM_8960_ID_PM8921_L1_1 = 134, + MSM_RPM_8960_ID_PM8921_L2_0 = 135, + MSM_RPM_8960_ID_PM8921_L2_1 = 136, + MSM_RPM_8960_ID_PM8921_L3_0 = 137, + MSM_RPM_8960_ID_PM8921_L3_1 = 138, + MSM_RPM_8960_ID_PM8921_L4_0 = 139, + MSM_RPM_8960_ID_PM8921_L4_1 = 140, + MSM_RPM_8960_ID_PM8921_L5_0 = 141, + MSM_RPM_8960_ID_PM8921_L5_1 = 142, + MSM_RPM_8960_ID_PM8921_L6_0 = 143, + MSM_RPM_8960_ID_PM8921_L6_1 = 144, + MSM_RPM_8960_ID_PM8921_L7_0 = 145, + MSM_RPM_8960_ID_PM8921_L7_1 = 146, + MSM_RPM_8960_ID_PM8921_L8_0 = 147, + MSM_RPM_8960_ID_PM8921_L8_1 = 148, + MSM_RPM_8960_ID_PM8921_L9_0 = 149, + MSM_RPM_8960_ID_PM8921_L9_1 = 150, + MSM_RPM_8960_ID_PM8921_L10_0 = 151, + MSM_RPM_8960_ID_PM8921_L10_1 = 152, + MSM_RPM_8960_ID_PM8921_L11_0 = 153, + MSM_RPM_8960_ID_PM8921_L11_1 = 154, + MSM_RPM_8960_ID_PM8921_L12_0 = 155, + MSM_RPM_8960_ID_PM8921_L12_1 = 156, + MSM_RPM_8960_ID_PM8921_L13_0 = 157, + MSM_RPM_8960_ID_PM8921_L13_1 = 158, + MSM_RPM_8960_ID_PM8921_L14_0 = 159, + MSM_RPM_8960_ID_PM8921_L14_1 = 160, + MSM_RPM_8960_ID_PM8921_L15_0 = 161, + MSM_RPM_8960_ID_PM8921_L15_1 = 162, + MSM_RPM_8960_ID_PM8921_L16_0 = 163, + MSM_RPM_8960_ID_PM8921_L16_1 = 164, + MSM_RPM_8960_ID_PM8921_L17_0 = 165, + MSM_RPM_8960_ID_PM8921_L17_1 = 166, + MSM_RPM_8960_ID_PM8921_L18_0 = 167, + MSM_RPM_8960_ID_PM8921_L18_1 = 168, + MSM_RPM_8960_ID_PM8921_L19_0 = 169, + MSM_RPM_8960_ID_PM8921_L19_1 = 170, + MSM_RPM_8960_ID_PM8921_L20_0 = 171, + MSM_RPM_8960_ID_PM8921_L20_1 = 172, + MSM_RPM_8960_ID_PM8921_L21_0 = 173, + MSM_RPM_8960_ID_PM8921_L21_1 = 174, + MSM_RPM_8960_ID_PM8921_L22_0 = 175, + MSM_RPM_8960_ID_PM8921_L22_1 = 176, + MSM_RPM_8960_ID_PM8921_L23_0 = 177, + MSM_RPM_8960_ID_PM8921_L23_1 = 178, + MSM_RPM_8960_ID_PM8921_L24_0 = 179, + MSM_RPM_8960_ID_PM8921_L24_1 = 180, + MSM_RPM_8960_ID_PM8921_L25_0 = 181, + MSM_RPM_8960_ID_PM8921_L25_1 = 182, + MSM_RPM_8960_ID_PM8921_L26_0 = 183, + MSM_RPM_8960_ID_PM8921_L26_1 = 184, + MSM_RPM_8960_ID_PM8921_L27_0 = 185, + MSM_RPM_8960_ID_PM8921_L27_1 = 186, + MSM_RPM_8960_ID_PM8921_L28_0 = 187, + MSM_RPM_8960_ID_PM8921_L28_1 = 188, + MSM_RPM_8960_ID_PM8921_L29_0 = 189, + MSM_RPM_8960_ID_PM8921_L29_1 = 190, + MSM_RPM_8960_ID_PM8921_CLK1_0 = 191, + MSM_RPM_8960_ID_PM8921_CLK1_1 = 192, + MSM_RPM_8960_ID_PM8921_CLK2_0 = 193, + MSM_RPM_8960_ID_PM8921_CLK2_1 = 194, + MSM_RPM_8960_ID_PM8921_LVS1 = 195, + MSM_RPM_8960_ID_PM8921_LVS2 = 196, + MSM_RPM_8960_ID_PM8921_LVS3 = 197, + MSM_RPM_8960_ID_PM8921_LVS4 = 198, + MSM_RPM_8960_ID_PM8921_LVS5 = 199, + MSM_RPM_8960_ID_PM8921_LVS6 = 200, + MSM_RPM_8960_ID_PM8921_LVS7 = 201, + MSM_RPM_8960_ID_NCP_0 = 202, + MSM_RPM_8960_ID_NCP_1 = 203, + MSM_RPM_8960_ID_CXO_BUFFERS = 204, + MSM_RPM_8960_ID_USB_OTG_SWITCH = 205, + MSM_RPM_8960_ID_HDMI_SWITCH = 206, + MSM_RPM_8960_ID_DDR_DMM_0 = 207, + MSM_RPM_8960_ID_DDR_DMM_1 = 208, + MSM_RPM_8960_ID_QDSS_CLK = 209, + + MSM_RPM_8960_ID_LAST = MSM_RPM_8960_ID_QDSS_CLK, +}; + +/* RPM status ID enum */ +enum { + MSM_RPM_8960_STATUS_ID_VERSION_MAJOR = 0, + MSM_RPM_8960_STATUS_ID_VERSION_MINOR = 1, + MSM_RPM_8960_STATUS_ID_VERSION_BUILD = 2, + MSM_RPM_8960_STATUS_ID_SUPPORTED_RESOURCES_0 = 3, + MSM_RPM_8960_STATUS_ID_SUPPORTED_RESOURCES_1 = 4, + MSM_RPM_8960_STATUS_ID_SUPPORTED_RESOURCES_2 = 5, + MSM_RPM_8960_STATUS_ID_RESERVED_SUPPORTED_RESOURCES_0 = 6, + MSM_RPM_8960_STATUS_ID_SEQUENCE = 7, + MSM_RPM_8960_STATUS_ID_RPM_CTL = 8, + MSM_RPM_8960_STATUS_ID_CXO_CLK = 9, + MSM_RPM_8960_STATUS_ID_PXO_CLK = 10, + MSM_RPM_8960_STATUS_ID_APPS_FABRIC_CLK = 11, + MSM_RPM_8960_STATUS_ID_SYSTEM_FABRIC_CLK = 12, + MSM_RPM_8960_STATUS_ID_MM_FABRIC_CLK = 13, + MSM_RPM_8960_STATUS_ID_DAYTONA_FABRIC_CLK = 14, + MSM_RPM_8960_STATUS_ID_SFPB_CLK = 15, + MSM_RPM_8960_STATUS_ID_CFPB_CLK = 16, + MSM_RPM_8960_STATUS_ID_MMFPB_CLK = 17, + MSM_RPM_8960_STATUS_ID_EBI1_CLK = 18, + MSM_RPM_8960_STATUS_ID_APPS_FABRIC_CFG_HALT = 19, + MSM_RPM_8960_STATUS_ID_APPS_FABRIC_CFG_CLKMOD = 20, + MSM_RPM_8960_STATUS_ID_APPS_FABRIC_CFG_IOCTL = 21, + MSM_RPM_8960_STATUS_ID_APPS_FABRIC_ARB = 22, + MSM_RPM_8960_STATUS_ID_SYS_FABRIC_CFG_HALT = 23, + MSM_RPM_8960_STATUS_ID_SYS_FABRIC_CFG_CLKMOD = 24, + MSM_RPM_8960_STATUS_ID_SYS_FABRIC_CFG_IOCTL = 25, + MSM_RPM_8960_STATUS_ID_SYSTEM_FABRIC_ARB = 26, + MSM_RPM_8960_STATUS_ID_MMSS_FABRIC_CFG_HALT = 27, + MSM_RPM_8960_STATUS_ID_MMSS_FABRIC_CFG_CLKMOD = 28, + MSM_RPM_8960_STATUS_ID_MMSS_FABRIC_CFG_IOCTL = 29, + MSM_RPM_8960_STATUS_ID_MM_FABRIC_ARB = 30, + MSM_RPM_8960_STATUS_ID_PM8921_S1_0 = 31, + MSM_RPM_8960_STATUS_ID_PM8921_S1_1 = 32, + MSM_RPM_8960_STATUS_ID_PM8921_S2_0 = 33, + MSM_RPM_8960_STATUS_ID_PM8921_S2_1 = 34, + MSM_RPM_8960_STATUS_ID_PM8921_S3_0 = 35, + MSM_RPM_8960_STATUS_ID_PM8921_S3_1 = 36, + MSM_RPM_8960_STATUS_ID_PM8921_S4_0 = 37, + MSM_RPM_8960_STATUS_ID_PM8921_S4_1 = 38, + MSM_RPM_8960_STATUS_ID_PM8921_S5_0 = 39, + MSM_RPM_8960_STATUS_ID_PM8921_S5_1 = 40, + MSM_RPM_8960_STATUS_ID_PM8921_S6_0 = 41, + MSM_RPM_8960_STATUS_ID_PM8921_S6_1 = 42, + MSM_RPM_8960_STATUS_ID_PM8921_S7_0 = 43, + MSM_RPM_8960_STATUS_ID_PM8921_S7_1 = 44, + MSM_RPM_8960_STATUS_ID_PM8921_S8_0 = 45, + MSM_RPM_8960_STATUS_ID_PM8921_S8_1 = 46, + MSM_RPM_8960_STATUS_ID_PM8921_L1_0 = 47, + MSM_RPM_8960_STATUS_ID_PM8921_L1_1 = 48, + MSM_RPM_8960_STATUS_ID_PM8921_L2_0 = 49, + MSM_RPM_8960_STATUS_ID_PM8921_L2_1 = 50, + MSM_RPM_8960_STATUS_ID_PM8921_L3_0 = 51, + MSM_RPM_8960_STATUS_ID_PM8921_L3_1 = 52, + MSM_RPM_8960_STATUS_ID_PM8921_L4_0 = 53, + MSM_RPM_8960_STATUS_ID_PM8921_L4_1 = 54, + MSM_RPM_8960_STATUS_ID_PM8921_L5_0 = 55, + MSM_RPM_8960_STATUS_ID_PM8921_L5_1 = 56, + MSM_RPM_8960_STATUS_ID_PM8921_L6_0 = 57, + MSM_RPM_8960_STATUS_ID_PM8921_L6_1 = 58, + MSM_RPM_8960_STATUS_ID_PM8921_L7_0 = 59, + MSM_RPM_8960_STATUS_ID_PM8921_L7_1 = 60, + MSM_RPM_8960_STATUS_ID_PM8921_L8_0 = 61, + MSM_RPM_8960_STATUS_ID_PM8921_L8_1 = 62, + MSM_RPM_8960_STATUS_ID_PM8921_L9_0 = 63, + MSM_RPM_8960_STATUS_ID_PM8921_L9_1 = 64, + MSM_RPM_8960_STATUS_ID_PM8921_L10_0 = 65, + MSM_RPM_8960_STATUS_ID_PM8921_L10_1 = 66, + MSM_RPM_8960_STATUS_ID_PM8921_L11_0 = 67, + MSM_RPM_8960_STATUS_ID_PM8921_L11_1 = 68, + MSM_RPM_8960_STATUS_ID_PM8921_L12_0 = 69, + MSM_RPM_8960_STATUS_ID_PM8921_L12_1 = 70, + MSM_RPM_8960_STATUS_ID_PM8921_L13_0 = 71, + MSM_RPM_8960_STATUS_ID_PM8921_L13_1 = 72, + MSM_RPM_8960_STATUS_ID_PM8921_L14_0 = 73, + MSM_RPM_8960_STATUS_ID_PM8921_L14_1 = 74, + MSM_RPM_8960_STATUS_ID_PM8921_L15_0 = 75, + MSM_RPM_8960_STATUS_ID_PM8921_L15_1 = 76, + MSM_RPM_8960_STATUS_ID_PM8921_L16_0 = 77, + MSM_RPM_8960_STATUS_ID_PM8921_L16_1 = 78, + MSM_RPM_8960_STATUS_ID_PM8921_L17_0 = 79, + MSM_RPM_8960_STATUS_ID_PM8921_L17_1 = 80, + MSM_RPM_8960_STATUS_ID_PM8921_L18_0 = 81, + MSM_RPM_8960_STATUS_ID_PM8921_L18_1 = 82, + MSM_RPM_8960_STATUS_ID_PM8921_L19_0 = 83, + MSM_RPM_8960_STATUS_ID_PM8921_L19_1 = 84, + MSM_RPM_8960_STATUS_ID_PM8921_L20_0 = 85, + MSM_RPM_8960_STATUS_ID_PM8921_L20_1 = 86, + MSM_RPM_8960_STATUS_ID_PM8921_L21_0 = 87, + MSM_RPM_8960_STATUS_ID_PM8921_L21_1 = 88, + MSM_RPM_8960_STATUS_ID_PM8921_L22_0 = 89, + MSM_RPM_8960_STATUS_ID_PM8921_L22_1 = 90, + MSM_RPM_8960_STATUS_ID_PM8921_L23_0 = 91, + MSM_RPM_8960_STATUS_ID_PM8921_L23_1 = 92, + MSM_RPM_8960_STATUS_ID_PM8921_L24_0 = 93, + MSM_RPM_8960_STATUS_ID_PM8921_L24_1 = 94, + MSM_RPM_8960_STATUS_ID_PM8921_L25_0 = 95, + MSM_RPM_8960_STATUS_ID_PM8921_L25_1 = 96, + MSM_RPM_8960_STATUS_ID_PM8921_L26_0 = 97, + MSM_RPM_8960_STATUS_ID_PM8921_L26_1 = 98, + MSM_RPM_8960_STATUS_ID_PM8921_L27_0 = 99, + MSM_RPM_8960_STATUS_ID_PM8921_L27_1 = 100, + MSM_RPM_8960_STATUS_ID_PM8921_L28_0 = 101, + MSM_RPM_8960_STATUS_ID_PM8921_L28_1 = 102, + MSM_RPM_8960_STATUS_ID_PM8921_L29_0 = 103, + MSM_RPM_8960_STATUS_ID_PM8921_L29_1 = 104, + MSM_RPM_8960_STATUS_ID_PM8921_CLK1_0 = 105, + MSM_RPM_8960_STATUS_ID_PM8921_CLK1_1 = 106, + MSM_RPM_8960_STATUS_ID_PM8921_CLK2_0 = 107, + MSM_RPM_8960_STATUS_ID_PM8921_CLK2_1 = 108, + MSM_RPM_8960_STATUS_ID_PM8921_LVS1 = 109, + MSM_RPM_8960_STATUS_ID_PM8921_LVS2 = 110, + MSM_RPM_8960_STATUS_ID_PM8921_LVS3 = 111, + MSM_RPM_8960_STATUS_ID_PM8921_LVS4 = 112, + MSM_RPM_8960_STATUS_ID_PM8921_LVS5 = 113, + MSM_RPM_8960_STATUS_ID_PM8921_LVS6 = 114, + MSM_RPM_8960_STATUS_ID_PM8921_LVS7 = 115, + MSM_RPM_8960_STATUS_ID_NCP_0 = 116, + MSM_RPM_8960_STATUS_ID_NCP_1 = 117, + MSM_RPM_8960_STATUS_ID_CXO_BUFFERS = 118, + MSM_RPM_8960_STATUS_ID_USB_OTG_SWITCH = 119, + MSM_RPM_8960_STATUS_ID_HDMI_SWITCH = 120, + MSM_RPM_8960_STATUS_ID_DDR_DMM_0 = 121, + MSM_RPM_8960_STATUS_ID_DDR_DMM_1 = 122, + MSM_RPM_8960_STATUS_ID_EBI1_CH0_RANGE = 123, + MSM_RPM_8960_STATUS_ID_EBI1_CH1_RANGE = 124, + + MSM_RPM_8960_STATUS_ID_LAST = MSM_RPM_8960_STATUS_ID_EBI1_CH1_RANGE, +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_8960_H */ diff --git a/arch/arm/mach-msm/include/mach/rpm-9615.h b/arch/arm/mach-msm/include/mach/rpm-9615.h new file mode 100644 index 0000000000000000000000000000000000000000..6ae7bae5e7c7e7eeff61addc3a952ea2d62dcb53 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-9615.h @@ -0,0 +1,238 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_9615_H +#define __ARCH_ARM_MACH_MSM_RPM_9615_H + +/* RPM control message RAM enums */ +enum { + MSM_RPM_9615_CTRL_VERSION_MAJOR, + MSM_RPM_9615_CTRL_VERSION_MINOR, + MSM_RPM_9615_CTRL_VERSION_BUILD, + + MSM_RPM_9615_CTRL_REQ_CTX_0, + MSM_RPM_9615_CTRL_REQ_CTX_7 = MSM_RPM_9615_CTRL_REQ_CTX_0 + 7, + MSM_RPM_9615_CTRL_REQ_SEL_0, + MSM_RPM_9615_CTRL_REQ_SEL_3 = MSM_RPM_9615_CTRL_REQ_SEL_0 + 3, + MSM_RPM_9615_CTRL_ACK_CTX_0, + MSM_RPM_9615_CTRL_ACK_CTX_7 = MSM_RPM_9615_CTRL_ACK_CTX_0 + 7, + MSM_RPM_9615_CTRL_ACK_SEL_0, + MSM_RPM_9615_CTRL_ACK_SEL_7 = MSM_RPM_9615_CTRL_ACK_SEL_0 + 7, +}; + +enum { + MSM_RPM_9615_SEL_NOTIFICATION = 0, + MSM_RPM_9615_SEL_INVALIDATE = 1, + MSM_RPM_9615_SEL_TRIGGER_TIMED = 2, + MSM_RPM_9615_SEL_RPM_CTL = 3, + + MSM_RPM_9615_SEL_CXO_CLK = 5, + MSM_RPM_9615_SEL_SYSTEM_FABRIC_CLK = 9, + MSM_RPM_9615_SEL_DAYTONA_FABRIC_CLK = 11, + MSM_RPM_9615_SEL_SFPB_CLK = 12, + MSM_RPM_9615_SEL_CFPB_CLK = 13, + MSM_RPM_9615_SEL_EBI1_CLK = 16, + + MSM_RPM_9615_SEL_SYS_FABRIC_CFG_HALT = 22, + MSM_RPM_9615_SEL_SYS_FABRIC_CFG_CLKMOD = 23, + MSM_RPM_9615_SEL_SYS_FABRIC_CFG_IOCTL = 24, + MSM_RPM_9615_SEL_SYSTEM_FABRIC_ARB = 25, + + MSM_RPM_9615_SEL_PM8018_S1 = 30, + MSM_RPM_9615_SEL_PM8018_S2 = 31, + MSM_RPM_9615_SEL_PM8018_S3 = 32, + MSM_RPM_9615_SEL_PM8018_S4 = 33, + MSM_RPM_9615_SEL_PM8018_S5 = 34, + MSM_RPM_9615_SEL_PM8018_L1 = 35, + MSM_RPM_9615_SEL_PM8018_L2 = 36, + MSM_RPM_9615_SEL_PM8018_L3 = 37, + MSM_RPM_9615_SEL_PM8018_L4 = 38, + MSM_RPM_9615_SEL_PM8018_L5 = 39, + MSM_RPM_9615_SEL_PM8018_L6 = 40, + MSM_RPM_9615_SEL_PM8018_L7 = 41, + MSM_RPM_9615_SEL_PM8018_L8 = 42, + MSM_RPM_9615_SEL_PM8018_L9 = 43, + MSM_RPM_9615_SEL_PM8018_L10 = 44, + MSM_RPM_9615_SEL_PM8018_L11 = 45, + MSM_RPM_9615_SEL_PM8018_L12 = 46, + MSM_RPM_9615_SEL_PM8018_L13 = 47, + MSM_RPM_9615_SEL_PM8018_L14 = 48, + MSM_RPM_9615_SEL_PM8018_LVS1 = 49, + + MSM_RPM_9615_SEL_NCP = 80, + MSM_RPM_9615_SEL_CXO_BUFFERS = 81, + MSM_RPM_9615_SEL_USB_OTG_SWITCH = 82, + MSM_RPM_9615_SEL_HDMI_SWITCH = 83, + + MSM_RPM_9615_SEL_LAST = MSM_RPM_9615_SEL_HDMI_SWITCH, +}; + +/* RPM resource (4 byte) word ID enum */ +enum { + MSM_RPM_9615_ID_NOTIFICATION_CONFIGURED_0 = 0, + MSM_RPM_9615_ID_NOTIFICATION_CONFIGURED_3 = + MSM_RPM_9615_ID_NOTIFICATION_CONFIGURED_0 + 3, + + MSM_RPM_9615_ID_NOTIFICATION_REGISTERED_0 = 4, + MSM_RPM_9615_ID_NOTIFICATION_REGISTERED_3 = + MSM_RPM_9615_ID_NOTIFICATION_REGISTERED_0 + 3, + + MSM_RPM_9615_ID_INVALIDATE_0 = 8, + MSM_RPM_9615_ID_INVALIDATE_7 = + MSM_RPM_9615_ID_INVALIDATE_0 + 7, + + MSM_RPM_9615_ID_TRIGGER_TIMED_TO = 16, + MSM_RPM_9615_ID_TRIGGER_TIMED_SCLK_COUNT = 17, + + MSM_RPM_9615_ID_RPM_CTL = 18, + + /* TRIGGER_CLEAR/SET deprecated in these 24 RESERVED bytes */ + MSM_RPM_9615_ID_RESERVED_0 = 19, + MSM_RPM_9615_ID_RESERVED_5 = + MSM_RPM_9615_ID_RESERVED_0 + 5, + + MSM_RPM_9615_ID_CXO_CLK = 25, + MSM_RPM_9615_ID_SYSTEM_FABRIC_CLK = 26, + MSM_RPM_9615_ID_DAYTONA_FABRIC_CLK = 27, + MSM_RPM_9615_ID_SFPB_CLK = 28, + MSM_RPM_9615_ID_CFPB_CLK = 29, + MSM_RPM_9615_ID_EBI1_CLK = 30, + + MSM_RPM_9615_ID_SYS_FABRIC_CFG_HALT_0 = 31, + MSM_RPM_9615_ID_SYS_FABRIC_CFG_HALT_1 = 32, + MSM_RPM_9615_ID_SYS_FABRIC_CFG_CLKMOD_0 = 33, + MSM_RPM_9615_ID_SYS_FABRIC_CFG_CLKMOD_1 = 34, + MSM_RPM_9615_ID_SYS_FABRIC_CFG_CLKMOD_2 = 35, + MSM_RPM_9615_ID_SYS_FABRIC_CFG_IOCTL = 36, + MSM_RPM_9615_ID_SYSTEM_FABRIC_ARB_0 = 37, + MSM_RPM_9615_ID_SYSTEM_FABRIC_ARB_26 = + MSM_RPM_9615_ID_SYSTEM_FABRIC_ARB_0 + 26, + + MSM_RPM_9615_ID_PM8018_S1_0 = 64, + MSM_RPM_9615_ID_PM8018_S1_1 = 65, + MSM_RPM_9615_ID_PM8018_S2_0 = 66, + MSM_RPM_9615_ID_PM8018_S2_1 = 67, + MSM_RPM_9615_ID_PM8018_S3_0 = 68, + MSM_RPM_9615_ID_PM8018_S3_1 = 69, + MSM_RPM_9615_ID_PM8018_S4_0 = 70, + MSM_RPM_9615_ID_PM8018_S4_1 = 71, + MSM_RPM_9615_ID_PM8018_S5_0 = 72, + MSM_RPM_9615_ID_PM8018_S5_1 = 73, + MSM_RPM_9615_ID_PM8018_L1_0 = 74, + MSM_RPM_9615_ID_PM8018_L1_1 = 75, + MSM_RPM_9615_ID_PM8018_L2_0 = 76, + MSM_RPM_9615_ID_PM8018_L2_1 = 77, + MSM_RPM_9615_ID_PM8018_L3_0 = 78, + MSM_RPM_9615_ID_PM8018_L3_1 = 79, + MSM_RPM_9615_ID_PM8018_L4_0 = 80, + MSM_RPM_9615_ID_PM8018_L4_1 = 81, + MSM_RPM_9615_ID_PM8018_L5_0 = 82, + MSM_RPM_9615_ID_PM8018_L5_1 = 83, + MSM_RPM_9615_ID_PM8018_L6_0 = 84, + MSM_RPM_9615_ID_PM8018_L6_1 = 85, + MSM_RPM_9615_ID_PM8018_L7_0 = 86, + MSM_RPM_9615_ID_PM8018_L7_1 = 87, + MSM_RPM_9615_ID_PM8018_L8_0 = 88, + MSM_RPM_9615_ID_PM8018_L8_1 = 89, + MSM_RPM_9615_ID_PM8018_L9_0 = 90, + MSM_RPM_9615_ID_PM8018_L9_1 = 91, + MSM_RPM_9615_ID_PM8018_L10_0 = 92, + MSM_RPM_9615_ID_PM8018_L10_1 = 93, + MSM_RPM_9615_ID_PM8018_L11_0 = 94, + MSM_RPM_9615_ID_PM8018_L11_1 = 95, + MSM_RPM_9615_ID_PM8018_L12_0 = 96, + MSM_RPM_9615_ID_PM8018_L12_1 = 97, + MSM_RPM_9615_ID_PM8018_L13_0 = 98, + MSM_RPM_9615_ID_PM8018_L13_1 = 99, + MSM_RPM_9615_ID_PM8018_L14_0 = 100, + MSM_RPM_9615_ID_PM8018_L14_1 = 101, + MSM_RPM_9615_ID_PM8018_LVS1 = 102, + + MSM_RPM_9615_ID_NCP_0 = 103, + MSM_RPM_9615_ID_NCP_1 = 104, + MSM_RPM_9615_ID_CXO_BUFFERS = 105, + MSM_RPM_9615_ID_USB_OTG_SWITCH = 106, + MSM_RPM_9615_ID_HDMI_SWITCH = 107, + + MSM_RPM_9615_ID_LAST = MSM_RPM_9615_ID_HDMI_SWITCH, +}; + +/* RPM status ID enum */ +enum { + MSM_RPM_9615_STATUS_ID_VERSION_MAJOR = 0, + MSM_RPM_9615_STATUS_ID_VERSION_MINOR = 1, + MSM_RPM_9615_STATUS_ID_VERSION_BUILD = 2, + MSM_RPM_9615_STATUS_ID_SUPPORTED_RESOURCES_0 = 3, + MSM_RPM_9615_STATUS_ID_SUPPORTED_RESOURCES_1 = 4, + MSM_RPM_9615_STATUS_ID_SUPPORTED_RESOURCES_2 = 5, + MSM_RPM_9615_STATUS_ID_RESERVED_SUPPORTED_RESOURCES_0 = 6, + MSM_RPM_9615_STATUS_ID_SEQUENCE = 7, + MSM_RPM_9615_STATUS_ID_RPM_CTL = 8, + MSM_RPM_9615_STATUS_ID_CXO_CLK = 9, + MSM_RPM_9615_STATUS_ID_SYSTEM_FABRIC_CLK = 10, + MSM_RPM_9615_STATUS_ID_DAYTONA_FABRIC_CLK = 11, + MSM_RPM_9615_STATUS_ID_SFPB_CLK = 12, + MSM_RPM_9615_STATUS_ID_CFPB_CLK = 13, + MSM_RPM_9615_STATUS_ID_EBI1_CLK = 14, + MSM_RPM_9615_STATUS_ID_SYS_FABRIC_CFG_HALT = 15, + MSM_RPM_9615_STATUS_ID_SYS_FABRIC_CFG_CLKMOD = 16, + MSM_RPM_9615_STATUS_ID_SYS_FABRIC_CFG_IOCTL = 17, + MSM_RPM_9615_STATUS_ID_SYSTEM_FABRIC_ARB = 18, + MSM_RPM_9615_STATUS_ID_PM8018_S1_0 = 19, + MSM_RPM_9615_STATUS_ID_PM8018_S1_1 = 20, + MSM_RPM_9615_STATUS_ID_PM8018_S2_0 = 21, + MSM_RPM_9615_STATUS_ID_PM8018_S2_1 = 22, + MSM_RPM_9615_STATUS_ID_PM8018_S3_0 = 23, + MSM_RPM_9615_STATUS_ID_PM8018_S3_1 = 24, + MSM_RPM_9615_STATUS_ID_PM8018_S4_0 = 25, + MSM_RPM_9615_STATUS_ID_PM8018_S4_1 = 26, + MSM_RPM_9615_STATUS_ID_PM8018_S5_0 = 27, + MSM_RPM_9615_STATUS_ID_PM8018_S5_1 = 28, + MSM_RPM_9615_STATUS_ID_PM8018_L1_0 = 29, + MSM_RPM_9615_STATUS_ID_PM8018_L1_1 = 30, + MSM_RPM_9615_STATUS_ID_PM8018_L2_0 = 31, + MSM_RPM_9615_STATUS_ID_PM8018_L2_1 = 32, + MSM_RPM_9615_STATUS_ID_PM8018_L3_0 = 33, + MSM_RPM_9615_STATUS_ID_PM8018_L3_1 = 34, + MSM_RPM_9615_STATUS_ID_PM8018_L4_0 = 35, + MSM_RPM_9615_STATUS_ID_PM8018_L4_1 = 36, + MSM_RPM_9615_STATUS_ID_PM8018_L5_0 = 37, + MSM_RPM_9615_STATUS_ID_PM8018_L5_1 = 38, + MSM_RPM_9615_STATUS_ID_PM8018_L6_0 = 39, + MSM_RPM_9615_STATUS_ID_PM8018_L6_1 = 40, + MSM_RPM_9615_STATUS_ID_PM8018_L7_0 = 41, + MSM_RPM_9615_STATUS_ID_PM8018_L7_1 = 42, + MSM_RPM_9615_STATUS_ID_PM8018_L8_0 = 43, + MSM_RPM_9615_STATUS_ID_PM8018_L8_1 = 44, + MSM_RPM_9615_STATUS_ID_PM8018_L9_0 = 45, + MSM_RPM_9615_STATUS_ID_PM8018_L9_1 = 46, + MSM_RPM_9615_STATUS_ID_PM8018_L10_0 = 47, + MSM_RPM_9615_STATUS_ID_PM8018_L10_1 = 48, + MSM_RPM_9615_STATUS_ID_PM8018_L11_0 = 49, + MSM_RPM_9615_STATUS_ID_PM8018_L11_1 = 50, + MSM_RPM_9615_STATUS_ID_PM8018_L12_0 = 51, + MSM_RPM_9615_STATUS_ID_PM8018_L12_1 = 52, + MSM_RPM_9615_STATUS_ID_PM8018_L13_0 = 53, + MSM_RPM_9615_STATUS_ID_PM8018_L13_1 = 54, + MSM_RPM_9615_STATUS_ID_PM8018_L14_0 = 55, + MSM_RPM_9615_STATUS_ID_PM8018_L14_1 = 56, + MSM_RPM_9615_STATUS_ID_PM8018_LVS1 = 57, + MSM_RPM_9615_STATUS_ID_NCP_0 = 58, + MSM_RPM_9615_STATUS_ID_NCP_1 = 59, + MSM_RPM_9615_STATUS_ID_CXO_BUFFERS = 60, + MSM_RPM_9615_STATUS_ID_USB_OTG_SWITCH = 61, + MSM_RPM_9615_STATUS_ID_HDMI_SWITCH = 62, + + MSM_RPM_9615_STATUS_ID_LAST = MSM_RPM_9615_STATUS_ID_HDMI_SWITCH, +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_9615_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 0000000000000000000000000000000000000000..85dcd897a26ec63cfdb7b552a07962dcdb7a6e2a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-8660.h @@ -0,0 +1,183 @@ +/* + * 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_INCLUDE_MACH_RPM_REGULATOR_8660_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_8660_H + +#define RPM_VREG_PIN_CTRL_PM8058_A0 0x01 +#define RPM_VREG_PIN_CTRL_PM8058_A1 0x02 +#define RPM_VREG_PIN_CTRL_PM8058_D0 0x04 +#define RPM_VREG_PIN_CTRL_PM8058_D1 0x08 + +#define RPM_VREG_PIN_CTRL_PM8901_A0 0x01 +#define RPM_VREG_PIN_CTRL_PM8901_A1 0x02 +#define RPM_VREG_PIN_CTRL_PM8901_D0 0x04 +#define RPM_VREG_PIN_CTRL_PM8901_D1 0x08 + + +/** + * enum rpm_vreg_pin_fn_8660 - RPM regulator pin function choices + * %RPM_VREG_PIN_FN_8660_ENABLE: pin control switches between disable and + * enable + * %RPM_VREG_PIN_FN_8660_MODE: pin control switches between LPM and HPM + * %RPM_VREG_PIN_FN_8660_SLEEP_B: regulator is forced into LPM when + * sleep_b signal is asserted + * %RPM_VREG_PIN_FN_8660_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_8660 { + RPM_VREG_PIN_FN_8660_ENABLE = 0, + RPM_VREG_PIN_FN_8660_MODE, + RPM_VREG_PIN_FN_8660_SLEEP_B, + RPM_VREG_PIN_FN_8660_NONE, +}; + +/** + * enum rpm_vreg_force_mode_8660 - RPM regulator force mode choices + * %RPM_VREG_FORCE_MODE_8660_PIN_CTRL: allow pin control usage + * %RPM_VREG_FORCE_MODE_8660_NONE: do not force any mode + * %RPM_VREG_FORCE_MODE_8660_LPM: force into low power mode + * %RPM_VREG_FORCE_MODE_8660_HPM: force into high power mode + * + * Force mode is used to override aggregation with other masters and to set + * special operating modes. + */ +enum rpm_vreg_force_mode_8660 { + RPM_VREG_FORCE_MODE_8660_PIN_CTRL = 0, + RPM_VREG_FORCE_MODE_8660_NONE = 0, + RPM_VREG_FORCE_MODE_8660_LPM, + RPM_VREG_FORCE_MODE_8660_HPM, +}; + +enum rpm_vreg_id_8660 { + RPM_VREG_ID_PM8058_L0, + 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_8660_MAX_REAL = RPM_VREG_ID_PM8901_MVS0, + + /* The following are IDs for regulator devices to enable pin control. */ + RPM_VREG_ID_PM8058_L0_PC, + RPM_VREG_ID_PM8058_L1_PC, + RPM_VREG_ID_PM8058_L2_PC, + RPM_VREG_ID_PM8058_L3_PC, + RPM_VREG_ID_PM8058_L4_PC, + RPM_VREG_ID_PM8058_L5_PC, + RPM_VREG_ID_PM8058_L6_PC, + RPM_VREG_ID_PM8058_L7_PC, + RPM_VREG_ID_PM8058_L8_PC, + RPM_VREG_ID_PM8058_L9_PC, + RPM_VREG_ID_PM8058_L10_PC, + RPM_VREG_ID_PM8058_L11_PC, + RPM_VREG_ID_PM8058_L12_PC, + RPM_VREG_ID_PM8058_L13_PC, + RPM_VREG_ID_PM8058_L14_PC, + RPM_VREG_ID_PM8058_L15_PC, + RPM_VREG_ID_PM8058_L16_PC, + RPM_VREG_ID_PM8058_L17_PC, + RPM_VREG_ID_PM8058_L18_PC, + RPM_VREG_ID_PM8058_L19_PC, + RPM_VREG_ID_PM8058_L20_PC, + RPM_VREG_ID_PM8058_L21_PC, + RPM_VREG_ID_PM8058_L22_PC, + RPM_VREG_ID_PM8058_L23_PC, + RPM_VREG_ID_PM8058_L24_PC, + RPM_VREG_ID_PM8058_L25_PC, + RPM_VREG_ID_PM8058_S0_PC, + RPM_VREG_ID_PM8058_S1_PC, + RPM_VREG_ID_PM8058_S2_PC, + RPM_VREG_ID_PM8058_S3_PC, + RPM_VREG_ID_PM8058_S4_PC, + RPM_VREG_ID_PM8058_LVS0_PC, + RPM_VREG_ID_PM8058_LVS1_PC, + + RPM_VREG_ID_PM8901_L0_PC, + RPM_VREG_ID_PM8901_L1_PC, + RPM_VREG_ID_PM8901_L2_PC, + RPM_VREG_ID_PM8901_L3_PC, + RPM_VREG_ID_PM8901_L4_PC, + RPM_VREG_ID_PM8901_L5_PC, + RPM_VREG_ID_PM8901_L6_PC, + RPM_VREG_ID_PM8901_S0_PC, + RPM_VREG_ID_PM8901_S1_PC, + RPM_VREG_ID_PM8901_S2_PC, + RPM_VREG_ID_PM8901_S3_PC, + RPM_VREG_ID_PM8901_S4_PC, + RPM_VREG_ID_PM8901_LVS0_PC, + RPM_VREG_ID_PM8901_LVS1_PC, + RPM_VREG_ID_PM8901_LVS2_PC, + RPM_VREG_ID_PM8901_LVS3_PC, + RPM_VREG_ID_PM8901_MVS0_PC, + RPM_VREG_ID_8660_MAX = RPM_VREG_ID_PM8901_MVS0_PC, +}; + +/* Minimum high power mode loads in uA. */ +#define RPM_VREG_8660_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_8660_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_8660_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_8660_SMPS_HPM_MIN_LOAD 50000 +#define RPM_VREG_8660_FTSMPS_HPM_MIN_LOAD 100000 + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator-8930.h b/arch/arm/mach-msm/include/mach/rpm-regulator-8930.h new file mode 100644 index 0000000000000000000000000000000000000000..684f9d3b1355ea8887713529dd9396a9ac82399e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-8930.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_INCLUDE_MACH_RPM_REGULATOR_8930_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_8930_H + +/* Pin control input signals. */ +#define RPM_VREG_PIN_CTRL_PM8038_D1 0x01 +#define RPM_VREG_PIN_CTRL_PM8038_A0 0x02 +#define RPM_VREG_PIN_CTRL_PM8038_A1 0x04 +#define RPM_VREG_PIN_CTRL_PM8038_A2 0x08 + +/** + * enum rpm_vreg_pin_fn_8930 - RPM regulator pin function choices + * %RPM_VREG_PIN_FN_8930_DONT_CARE: do not care about pin control state of + * the regulator; allow another master + * processor to specify pin control + * %RPM_VREG_PIN_FN_8930_ENABLE: pin control switches between disable and + * enable + * %RPM_VREG_PIN_FN_8930_MODE: pin control switches between LPM and HPM + * %RPM_VREG_PIN_FN_8930_SLEEP_B: regulator is forced into LPM when + * sleep_b signal is asserted + * %RPM_VREG_PIN_FN_8930_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_8930 { + RPM_VREG_PIN_FN_8930_DONT_CARE, + RPM_VREG_PIN_FN_8930_ENABLE, + RPM_VREG_PIN_FN_8930_MODE, + RPM_VREG_PIN_FN_8930_SLEEP_B, + RPM_VREG_PIN_FN_8930_NONE, +}; + +/** + * enum rpm_vreg_force_mode_8930 - RPM regulator force mode choices + * %RPM_VREG_FORCE_MODE_8930_PIN_CTRL: allow pin control usage + * %RPM_VREG_FORCE_MODE_8930_NONE: do not force any mode + * %RPM_VREG_FORCE_MODE_8930_LPM: force into low power mode + * %RPM_VREG_FORCE_MODE_8930_AUTO: allow regulator to automatically select + * its own mode based on realtime current + * draw (only available for SMPS + * regulators) + * %RPM_VREG_FORCE_MODE_8930_HPM: force into high power mode + * %RPM_VREG_FORCE_MODE_8930_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_8930 { + RPM_VREG_FORCE_MODE_8930_PIN_CTRL = 0, + RPM_VREG_FORCE_MODE_8930_NONE = 0, + RPM_VREG_FORCE_MODE_8930_LPM, + RPM_VREG_FORCE_MODE_8930_AUTO, /* SMPS only */ + RPM_VREG_FORCE_MODE_8930_HPM, + RPM_VREG_FORCE_MODE_8930_BYPASS, /* LDO only */ +}; + +/** + * enum rpm_vreg_power_mode_8930 - power mode for SMPS regulators + * %RPM_VREG_POWER_MODE_8930_HYSTERETIC: Use hysteretic mode for HPM and when + * usage goes high in AUTO + * %RPM_VREG_POWER_MODE_8930_PWM: Use PWM mode for HPM and when usage + * goes high in AUTO + */ +enum rpm_vreg_power_mode_8930 { + RPM_VREG_POWER_MODE_8930_HYSTERETIC, + RPM_VREG_POWER_MODE_8930_PWM, +}; + +/** + * enum rpm_vreg_id - RPM regulator ID numbers (both real and pin control) + */ +enum rpm_vreg_id_8930 { + RPM_VREG_ID_PM8038_L1, + RPM_VREG_ID_PM8038_L2, + RPM_VREG_ID_PM8038_L3, + RPM_VREG_ID_PM8038_L4, + RPM_VREG_ID_PM8038_L5, + RPM_VREG_ID_PM8038_L6, + RPM_VREG_ID_PM8038_L7, + RPM_VREG_ID_PM8038_L8, + RPM_VREG_ID_PM8038_L9, + RPM_VREG_ID_PM8038_L10, + RPM_VREG_ID_PM8038_L11, + RPM_VREG_ID_PM8038_L12, + RPM_VREG_ID_PM8038_L14, + RPM_VREG_ID_PM8038_L15, + RPM_VREG_ID_PM8038_L16, + RPM_VREG_ID_PM8038_L17, + RPM_VREG_ID_PM8038_L18, + RPM_VREG_ID_PM8038_L19, + RPM_VREG_ID_PM8038_L20, + RPM_VREG_ID_PM8038_L21, + RPM_VREG_ID_PM8038_L22, + RPM_VREG_ID_PM8038_L23, + RPM_VREG_ID_PM8038_L24, + RPM_VREG_ID_PM8038_L26, + RPM_VREG_ID_PM8038_L27, + RPM_VREG_ID_PM8038_S1, + RPM_VREG_ID_PM8038_S2, + RPM_VREG_ID_PM8038_S3, + RPM_VREG_ID_PM8038_S4, + RPM_VREG_ID_PM8038_S5, + RPM_VREG_ID_PM8038_S6, + RPM_VREG_ID_PM8038_LVS1, + RPM_VREG_ID_PM8038_LVS2, + RPM_VREG_ID_PM8038_VDD_DIG_CORNER, + RPM_VREG_ID_PM8038_MAX_REAL = RPM_VREG_ID_PM8038_VDD_DIG_CORNER, + + /* The following are IDs for regulator devices to enable pin control. */ + RPM_VREG_ID_PM8038_L2_PC, + RPM_VREG_ID_PM8038_L3_PC, + RPM_VREG_ID_PM8038_L4_PC, + RPM_VREG_ID_PM8038_L5_PC, + RPM_VREG_ID_PM8038_L6_PC, + RPM_VREG_ID_PM8038_L7_PC, + RPM_VREG_ID_PM8038_L8_PC, + RPM_VREG_ID_PM8038_L9_PC, + RPM_VREG_ID_PM8038_L10_PC, + RPM_VREG_ID_PM8038_L11_PC, + RPM_VREG_ID_PM8038_L12_PC, + RPM_VREG_ID_PM8038_L14_PC, + RPM_VREG_ID_PM8038_L15_PC, + RPM_VREG_ID_PM8038_L17_PC, + RPM_VREG_ID_PM8038_L18_PC, + RPM_VREG_ID_PM8038_L21_PC, + RPM_VREG_ID_PM8038_L22_PC, + RPM_VREG_ID_PM8038_L23_PC, + RPM_VREG_ID_PM8038_L26_PC, + RPM_VREG_ID_PM8038_S1_PC, + RPM_VREG_ID_PM8038_S2_PC, + RPM_VREG_ID_PM8038_S3_PC, + RPM_VREG_ID_PM8038_S4_PC, + RPM_VREG_ID_PM8038_LVS1_PC, + RPM_VREG_ID_PM8038_LVS2_PC, + RPM_VREG_ID_PM8038_MAX = RPM_VREG_ID_PM8038_LVS2_PC, +}; + +/* Minimum high power mode loads in uA. */ +#define RPM_VREG_8930_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_8930_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_8930_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_8930_LDO_600_HPM_MIN_LOAD 10000 +#define RPM_VREG_8930_LDO_1200_HPM_MIN_LOAD 10000 +#define RPM_VREG_8930_SMPS_1500_HPM_MIN_LOAD 100000 +#define RPM_VREG_8930_SMPS_2000_HPM_MIN_LOAD 100000 + +#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 0000000000000000000000000000000000000000..6de47bd2fe4589cbea21aa40455d4f6bd5b2b37f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-8960.h @@ -0,0 +1,187 @@ +/* + * 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_INCLUDE_MACH_RPM_REGULATOR_8960_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_8960_H + +/* Pin control input signals. */ +#define RPM_VREG_PIN_CTRL_PM8921_D1 0x01 +#define RPM_VREG_PIN_CTRL_PM8921_A0 0x02 +#define RPM_VREG_PIN_CTRL_PM8921_A1 0x04 +#define RPM_VREG_PIN_CTRL_PM8921_A2 0x08 + +/** + * enum rpm_vreg_pin_fn_8960 - RPM regulator pin function choices + * %RPM_VREG_PIN_FN_8960_DONT_CARE: do not care about pin control state of + * the regulator; allow another master + * processor to specify pin control + * %RPM_VREG_PIN_FN_8960_ENABLE: pin control switches between disable and + * enable + * %RPM_VREG_PIN_FN_8960_MODE: pin control switches between LPM and HPM + * %RPM_VREG_PIN_FN_8960_SLEEP_B: regulator is forced into LPM when + * sleep_b signal is asserted + * %RPM_VREG_PIN_FN_8960_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_8960 { + RPM_VREG_PIN_FN_8960_DONT_CARE, + RPM_VREG_PIN_FN_8960_ENABLE, + RPM_VREG_PIN_FN_8960_MODE, + RPM_VREG_PIN_FN_8960_SLEEP_B, + RPM_VREG_PIN_FN_8960_NONE, +}; + +/** + * enum rpm_vreg_force_mode_8960 - RPM regulator force mode choices + * %RPM_VREG_FORCE_MODE_8960_PIN_CTRL: allow pin control usage + * %RPM_VREG_FORCE_MODE_8960_NONE: do not force any mode + * %RPM_VREG_FORCE_MODE_8960_LPM: force into low power mode + * %RPM_VREG_FORCE_MODE_8960_AUTO: allow regulator to automatically select + * its own mode based on realtime current + * draw (only available for SMPS + * regulators) + * %RPM_VREG_FORCE_MODE_8960_HPM: force into high power mode + * %RPM_VREG_FORCE_MODE_8960_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_8960 { + RPM_VREG_FORCE_MODE_8960_PIN_CTRL = 0, + RPM_VREG_FORCE_MODE_8960_NONE = 0, + RPM_VREG_FORCE_MODE_8960_LPM, + RPM_VREG_FORCE_MODE_8960_AUTO, /* SMPS only */ + RPM_VREG_FORCE_MODE_8960_HPM, + RPM_VREG_FORCE_MODE_8960_BYPASS, /* LDO only */ +}; + +/** + * enum rpm_vreg_power_mode_8960 - power mode for SMPS regulators + * %RPM_VREG_POWER_MODE_8960_HYSTERETIC: Use hysteretic mode for HPM and when + * usage goes high in AUTO + * %RPM_VREG_POWER_MODE_8960_PWM: Use PWM mode for HPM and when usage + * goes high in AUTO + */ +enum rpm_vreg_power_mode_8960 { + RPM_VREG_POWER_MODE_8960_HYSTERETIC, + RPM_VREG_POWER_MODE_8960_PWM, +}; + +/** + * enum rpm_vreg_id - RPM regulator ID numbers (both real and pin control) + */ +enum rpm_vreg_id_8960 { + 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_8960_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_8960_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_LDO_600_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_LDO_1200_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_SMPS_1500_HPM_MIN_LOAD 100000 +#define RPM_VREG_8960_SMPS_2000_HPM_MIN_LOAD 100000 + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator-9615.h b/arch/arm/mach-msm/include/mach/rpm-regulator-9615.h new file mode 100644 index 0000000000000000000000000000000000000000..f5fa8caa70e5d0990a269c3b514ae30fc929b4ac --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-9615.h @@ -0,0 +1,138 @@ +/* + * 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_INCLUDE_MACH_RPM_REGULATOR_9615_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_9615_H + +/* Pin control input signals. */ +#define RPM_VREG_PIN_CTRL_PM8018_D1 0x01 +#define RPM_VREG_PIN_CTRL_PM8018_A0 0x02 +#define RPM_VREG_PIN_CTRL_PM8018_A1 0x04 +#define RPM_VREG_PIN_CTRL_PM8018_A2 0x08 + +/** + * enum rpm_vreg_pin_fn_9615 - RPM regulator pin function choices + * %RPM_VREG_PIN_FN_9615_DONT_CARE: do not care about pin control state of + * the regulator; allow another master + * processor to specify pin control + * %RPM_VREG_PIN_FN_9615_ENABLE: pin control switches between disable and + * enable + * %RPM_VREG_PIN_FN_9615_MODE: pin control switches between LPM and HPM + * %RPM_VREG_PIN_FN_9615_SLEEP_B: regulator is forced into LPM when + * sleep_b signal is asserted + * %RPM_VREG_PIN_FN_9615_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_9615 { + RPM_VREG_PIN_FN_9615_DONT_CARE, + RPM_VREG_PIN_FN_9615_ENABLE, + RPM_VREG_PIN_FN_9615_MODE, + RPM_VREG_PIN_FN_9615_SLEEP_B, + RPM_VREG_PIN_FN_9615_NONE, +}; + +/** + * enum rpm_vreg_force_mode_9615 - RPM regulator force mode choices + * %RPM_VREG_FORCE_MODE_9615_PIN_CTRL: allow pin control usage + * %RPM_VREG_FORCE_MODE_9615_NONE: do not force any mode + * %RPM_VREG_FORCE_MODE_9615_LPM: force into low power mode + * %RPM_VREG_FORCE_MODE_9615_AUTO: allow regulator to automatically select + * its own mode based on realtime current + * draw (only available for SMPS + * regulators) + * %RPM_VREG_FORCE_MODE_9615_HPM: force into high power mode + * %RPM_VREG_FORCE_MODE_9615_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_9615 { + RPM_VREG_FORCE_MODE_9615_PIN_CTRL = 0, + RPM_VREG_FORCE_MODE_9615_NONE = 0, + RPM_VREG_FORCE_MODE_9615_LPM, + RPM_VREG_FORCE_MODE_9615_AUTO, /* SMPS only */ + RPM_VREG_FORCE_MODE_9615_HPM, + RPM_VREG_FORCE_MODE_9615_BYPASS, /* LDO only */ +}; + +/** + * enum rpm_vreg_power_mode_9615 - power mode for SMPS regulators + * %RPM_VREG_POWER_MODE_9615_HYSTERETIC: Use hysteretic mode for HPM and when + * usage goes high in AUTO + * %RPM_VREG_POWER_MODE_9615_PWM: Use PWM mode for HPM and when usage + * goes high in AUTO + */ +enum rpm_vreg_power_mode_9615 { + RPM_VREG_POWER_MODE_9615_HYSTERETIC, + RPM_VREG_POWER_MODE_9615_PWM, +}; + +/** + * enum rpm_vreg_id - RPM regulator ID numbers (both real and pin control) + */ +enum rpm_vreg_id_9615 { + RPM_VREG_ID_PM8018_L2, + RPM_VREG_ID_PM8018_L3, + RPM_VREG_ID_PM8018_L4, + RPM_VREG_ID_PM8018_L5, + RPM_VREG_ID_PM8018_L6, + RPM_VREG_ID_PM8018_L7, + RPM_VREG_ID_PM8018_L8, + RPM_VREG_ID_PM8018_L9, + RPM_VREG_ID_PM8018_L10, + RPM_VREG_ID_PM8018_L11, + RPM_VREG_ID_PM8018_L12, + RPM_VREG_ID_PM8018_L13, + RPM_VREG_ID_PM8018_L14, + RPM_VREG_ID_PM8018_S1, + RPM_VREG_ID_PM8018_S2, + RPM_VREG_ID_PM8018_S3, + RPM_VREG_ID_PM8018_S4, + RPM_VREG_ID_PM8018_S5, + RPM_VREG_ID_PM8018_LVS1, + RPM_VREG_ID_PM8018_MAX_REAL = RPM_VREG_ID_PM8018_LVS1, + + /* The following are IDs for regulator devices to enable pin control. */ + RPM_VREG_ID_PM8018_L2_PC, + RPM_VREG_ID_PM8018_L3_PC, + RPM_VREG_ID_PM8018_L4_PC, + RPM_VREG_ID_PM8018_L5_PC, + RPM_VREG_ID_PM8018_L6_PC, + RPM_VREG_ID_PM8018_L7_PC, + RPM_VREG_ID_PM8018_L8_PC, + RPM_VREG_ID_PM8018_L13_PC, + RPM_VREG_ID_PM8018_L14_PC, + RPM_VREG_ID_PM8018_S1_PC, + RPM_VREG_ID_PM8018_S2_PC, + RPM_VREG_ID_PM8018_S3_PC, + RPM_VREG_ID_PM8018_S4_PC, + RPM_VREG_ID_PM8018_S5_PC, + RPM_VREG_ID_PM8018_LVS1_PC, + RPM_VREG_ID_PM8018_MAX = RPM_VREG_ID_PM8018_LVS1_PC, +}; + +/* Minimum high power mode loads in uA. */ +#define RPM_VREG_9615_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_9615_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_9615_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_9615_LDO_1200_HPM_MIN_LOAD 10000 +#define RPM_VREG_9615_SMPS_1500_HPM_MIN_LOAD 100000 + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator-copper.h b/arch/arm/mach-msm/include/mach/rpm-regulator-copper.h new file mode 100644 index 0000000000000000000000000000000000000000..2006ad34f2d896eafc0e70d3111fc59fa54bcf91 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-copper.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 __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_COPPER_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_COPPER_H + +/** + * enum rpm_vreg_id - RPM regulator ID numbers (both real and pin control) + */ +enum rpm_vreg_id_copper { + RPM_VREG_ID_PM8941_S1, + RPM_VREG_ID_PM8941_S2, + RPM_VREG_ID_PM8941_L12, + RPM_VREG_ID_PM8941_MAX, +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator-smd.h b/arch/arm/mach-msm/include/mach/rpm-regulator-smd.h new file mode 100644 index 0000000000000000000000000000000000000000..2eb59f5905a5aebba49b51fd6e8f50851cdeac5c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-smd.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_INCLUDE_MACH_RPM_REGULATOR_SMD_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_SMD_H + +#include + +struct rpm_regulator; + +#if defined(CONFIG_MSM_RPM_REGULATOR_SMD) || defined(CONFIG_MSM_RPM_REGULATOR) + +struct rpm_regulator *rpm_regulator_get(struct device *dev, const char *supply); + +void rpm_regulator_put(struct rpm_regulator *regulator); + +int rpm_regulator_enable(struct rpm_regulator *regulator); + +int rpm_regulator_disable(struct rpm_regulator *regulator); + +int rpm_regulator_set_voltage(struct rpm_regulator *regulator, int min_uV, + int max_uV); + +int __init rpm_regulator_smd_driver_init(void); + +#else + +static inline struct rpm_regulator *rpm_regulator_get(struct device *dev, + const char *supply) { return NULL; } + +static inline void rpm_regulator_put(struct rpm_regulator *regulator) { } + +static inline int rpm_regulator_enable(struct rpm_regulator *regulator) + { return 0; } + +static inline int rpm_regulator_disable(struct rpm_regulator *regulator) + { return 0; } + +static inline int rpm_regulator_set_voltage(struct rpm_regulator *regulator, + int min_uV, int max_uV) { return 0; } + +static inline int __init rpm_regulator_smd_driver_init(void) { return 0; } + +#endif /* CONFIG_MSM_RPM_REGULATOR_SMD */ + +#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 0000000000000000000000000000000000000000..a0102578a7f900794a74b3f33362bb618dc94b98 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator.h @@ -0,0 +1,234 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_INCLUDE_MACH_RPM_REGULATOR_H +#define __ARCH_ARM_MACH_MSM_INCLUDE_MACH_RPM_REGULATOR_H + +#include + +#define RPM_REGULATOR_DEV_NAME "rpm-regulator" + +#include +#include +#include +#include +#include + +/** + * enum rpm_vreg_version - supported RPM regulator versions + */ +enum rpm_vreg_version { + RPM_VREG_VERSION_8660, + RPM_VREG_VERSION_8960, + RPM_VREG_VERSION_9615, + RPM_VREG_VERSION_8930, + RPM_VREG_VERSION_MAX = RPM_VREG_VERSION_8930, +}; + +#define RPM_VREG_PIN_CTRL_NONE 0x00 + +/** + * 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_voltage_corner - possible voltage corner values + * + * These should be used in regulator_set_voltage and rpm_vreg_set_voltage calls + * for corner type regulators as if they had units of uV. + */ +enum rpm_vreg_voltage_corner { + RPM_VREG_CORNER_NONE = 1, + RPM_VREG_CORNER_LOW, + RPM_VREG_CORNER_NOMINAL, + RPM_VREG_CORNER_HIGH, +}; + +/** + * 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_VOTER4, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER5, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER6, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER_COUNT, +}; + +/** + * 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 + * @enable_time: time in us taken to enable a regulator to the maximum + * allowed voltage for the system. This is dependent upon + * the load and capacitance for a regulator on the board. + * @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. + * @sleep_set_force_mode: force mode to use in sleep-set requests + * @power_mode: mode to use as HPM (typically PWM or hysteretic) when + * utilizing Auto mode selection + * @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; + int id; + int sleep_selectable; + int system_uA; + int enable_time; + unsigned pull_down_enable; + enum rpm_vreg_freq freq; + unsigned pin_ctrl; + int pin_fn; + int force_mode; + int sleep_set_force_mode; + int power_mode; + int default_uV; + unsigned peak_uA; + unsigned avg_uA; + enum rpm_vreg_state state; +}; + +/** + * struct rpm_regulator_consumer_mapping - mapping used by private consumers + */ +struct rpm_regulator_consumer_mapping { + const char *dev_name; + const char *supply; + int vreg_id; + enum rpm_vreg_voter voter; + int sleep_also; +}; + +/** + * 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_version version; + int vreg_id_vdd_mem; + int vreg_id_vdd_dig; + struct rpm_regulator_consumer_mapping *consumer_map; + int consumer_map_len; +}; + +#ifdef CONFIG_MSM_RPM_REGULATOR +/** + * 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. + * + * Consumers can vote to disable a regulator with this function by passing + * min_uV = 0 and max_uV = 0. + * + * Voltage switch type regulators may be controlled via rpm_vreg_set_voltage + * as well. For this type of regulator, max_uV > 0 is treated as an enable + * request and max_uV == 0 is treated as a disable request. + */ +int rpm_vreg_set_voltage(int 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(int vreg_id, enum rpm_vreg_freq freq); + +#else + +/* + * These stubs exist to allow consumers of these APIs to compile and run + * in absence of a real RPM regulator driver. It is assumed that they are + * aware of the state of their regulators and have either set them + * correctly by some other means or don't care about their state at all. + */ +static inline int rpm_vreg_set_voltage(int vreg_id, enum rpm_vreg_voter voter, + int min_uV, int max_uV, int sleep_also) +{ + return 0; +} + +static inline int rpm_vreg_set_frequency(int vreg_id, enum rpm_vreg_freq freq) +{ + return 0; +} + +#endif /* CONFIG_MSM_RPM_REGULATOR */ + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-smd.h b/arch/arm/mach-msm/include/mach/rpm-smd.h new file mode 100644 index 0000000000000000000000000000000000000000..ff58fed150a4e14f371bbb9e45e7292189d5415f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-smd.h @@ -0,0 +1,254 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SMD_H +#define __ARCH_ARM_MACH_MSM_RPM_SMD_H + +/** + * enum msm_rpm_set - RPM enumerations for sleep/active set + * %MSM_RPM_CTX_SET_0: Set resource parameters for active mode. + * %MSM_RPM_CTX_SET_SLEEP: Set resource parameters for sleep. + */ +enum msm_rpm_set { + MSM_RPM_CTX_ACTIVE_SET, + MSM_RPM_CTX_SLEEP_SET, +}; + +struct msm_rpm_request; + +struct msm_rpm_kvp { + uint32_t key; + uint32_t length; + uint8_t *data; +}; +#ifdef CONFIG_MSM_RPM_SMD +/** + * msm_rpm_request() - Creates a parent element to identify the + * resource on the RPM, that stores the KVPs for different fields modified + * for a hardware resource + * + * @set: if the device is setting the active/sleep set parameter + * for the resource + * @rsc_type: unsigned 32 bit integer that identifies the type of the resource + * @rsc_id: unsigned 32 bit that uniquely identifies a resource within a type + * @num_elements: number of KVPs pairs associated with the resource + * + * returns pointer to a msm_rpm_request on success, NULL on error + */ +struct msm_rpm_request *msm_rpm_create_request( + enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, int num_elements); + +/** + * msm_rpm_request_noirq() - Creates a parent element to identify the + * resource on the RPM, that stores the KVPs for different fields modified + * for a hardware resource. This function is similar to msm_rpm_create_request + * except that it has to be called with interrupts masked. + * + * @set: if the device is setting the active/sleep set parameter + * for the resource + * @rsc_type: unsigned 32 bit integer that identifies the type of the resource + * @rsc_id: unsigned 32 bit that uniquely identifies a resource within a type + * @num_elements: number of KVPs pairs associated with the resource + * + * returns pointer to a msm_rpm_request on success, NULL on error + */ +struct msm_rpm_request *msm_rpm_create_request_noirq( + enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, int num_elements); + +/** + * msm_rpm_add_kvp_data() - Adds a Key value pair to a existing RPM resource. + * + * @handle: RPM resource handle to which the data should be appended + * @key: unsigned integer identify the parameter modified + * @data: byte array that contains the value corresponding to key. + * @size: size of data in bytes. + * + * returns 0 on success or errno + */ +int msm_rpm_add_kvp_data(struct msm_rpm_request *handle, + uint32_t key, const uint8_t *data, int size); + +/** + * msm_rpm_add_kvp_data_noirq() - Adds a Key value pair to a existing RPM + * resource. This function is similar to msm_rpm_add_kvp_data except that it + * has to be called with interrupts masked. + * + * @handle: RPM resource handle to which the data should be appended + * @key: unsigned integer identify the parameter modified + * @data: byte array that contains the value corresponding to key. + * @size: size of data in bytes. + * + * returns 0 on success or errno + */ +int msm_rpm_add_kvp_data_noirq(struct msm_rpm_request *handle, + uint32_t key, const uint8_t *data, int size); + +/** msm_rpm_free_request() - clean up the RPM request handle created with + * msm_rpm_create_request + * + * @handle: RPM resource handle to be cleared. + */ + +void msm_rpm_free_request(struct msm_rpm_request *handle); + +/** + * msm_rpm_send_request() - Send the RPM messages using SMD. The function + * assigns a message id before sending the data out to the RPM. RPM hardware + * uses the message id to acknowledge the messages. + * + * @handle: pointer to the msm_rpm_request for the resource being modified. + * + * returns non-zero message id on success and zero on a failed transaction. + * The drivers use message id to wait for ACK from RPM. + */ +int msm_rpm_send_request(struct msm_rpm_request *handle); + +/** + * msm_rpm_send_request_noirq() - Send the RPM messages using SMD. The + * function assigns a message id before sending the data out to the RPM. + * RPM hardware uses the message id to acknowledge the messages. This function + * is similar to msm_rpm_send_request except that it has to be called with + * interrupts masked. + * + * @handle: pointer to the msm_rpm_request for the resource being modified. + * + * returns non-zero message id on success and zero on a failed transaction. + * The drivers use message id to wait for ACK from RPM. + */ +int msm_rpm_send_request_noirq(struct msm_rpm_request *handle); + +/** + * msm_rpm_wait_for_ack() - A blocking call that waits for acknowledgment of + * a message from RPM. + * + * @msg_id: the return from msm_rpm_send_requests + * + * returns 0 on success or errno + */ +int msm_rpm_wait_for_ack(uint32_t msg_id); + +/** + * msm_rpm_wait_for_ack_noirq() - A blocking call that waits for acknowledgment + * of a message from RPM. This function is similar to msm_rpm_wait_for_ack + * except that it has to be called with interrupts masked. + * + * @msg_id: the return from msm_rpm_send_request + * + * returns 0 on success or errno + */ +int msm_rpm_wait_for_ack_noirq(uint32_t msg_id); + +/** + * msm_rpm_send_message() -Wrapper function for clients to send data given an + * array of key value pairs. + * + * @set: if the device is setting the active/sleep set parameter + * for the resource + * @rsc_type: unsigned 32 bit integer that identifies the type of the resource + * @rsc_id: unsigned 32 bit that uniquely identifies a resource within a type + * @kvp: array of KVP data. + * @nelem: number of KVPs pairs associated with the message. + * + * returns 0 on success and errno on failure. + */ +int msm_rpm_send_message(enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, struct msm_rpm_kvp *kvp, int nelems); + +/** + * msm_rpm_send_message_noirq() -Wrapper function for clients to send data + * given an array of key value pairs. This function is similar to the + * msm_rpm_send_message() except that it has to be called with interrupts + * disabled. Clients should choose the irq version when possible for system + * performance. + * + * @set: if the device is setting the active/sleep set parameter + * for the resource + * @rsc_type: unsigned 32 bit integer that identifies the type of the resource + * @rsc_id: unsigned 32 bit that uniquely identifies a resource within a type + * @kvp: array of KVP data. + * @nelem: number of KVPs pairs associated with the message. + * + * returns 0 on success and errno on failure. + */ +int msm_rpm_send_message_noirq(enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, struct msm_rpm_kvp *kvp, int nelems); + +/** + * msm_rpm_driver_init() - Initialization function that registers for a + * rpm platform driver. + * + * returns 0 on success. + */ +int __init msm_rpm_driver_init(void); + +#else + +static inline struct msm_rpm_request *msm_rpm_create_request( + enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, int num_elements) +{ + return NULL; +} + +static inline struct msm_rpm_request *msm_rpm_create_request_noirq( + enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, int num_elements) +{ + return NULL; + +} +static inline uint32_t msm_rpm_add_kvp_data(struct msm_rpm_request *handle, + uint32_t key, const uint8_t *data, int count) +{ + return 0; +} +static inline uint32_t msm_rpm_add_kvp_data_noirq( + struct msm_rpm_request *handle, uint32_t key, + const uint8_t *data, int count) +{ + return 0; +} + +static inline void msm_rpm_free_request(struct msm_rpm_request *handle) +{ + return ; +} + +static inline int msm_rpm_send_request(struct msm_rpm_request *handle) +{ + return 0; +} + +static inline int msm_rpm_send_request_noirq(struct msm_rpm_request *handle) +{ + return 0; + +} +static inline int msm_rpm_wait_for_ack(uint32_t msg_id) +{ + return 0; + +} +static inline int msm_rpm_wait_for_ack_noirq(uint32_t msg_id) +{ + return 0; +} + +static inline int __init msm_rpm_driver_init(void) +{ + return 0; +} +#endif +#endif /*__ARCH_ARM_MACH_MSM_RPM_SMD_H*/ 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 0000000000000000000000000000000000000000..98621be75c689837df2754d44a00da15142178d3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm.h @@ -0,0 +1,941 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#include +#include +#include +#include +#include + +#define SEL_MASK_SIZE (5) + +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, +}; + +/* 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_SEL_0, + MSM_RPM_CTRL_ACK_CTX_0, + MSM_RPM_CTRL_ACK_SEL_0, + + MSM_RPM_CTRL_LAST, +}; + +enum { + MSM_RPM_ID_NOTIFICATION_CONFIGURED_0 = 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_0, + MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT, + + MSM_RPM_ID_RPM_CTL, + + /* TRIGGER_CLEAR/SET deprecated in these 24 RESERVED bytes */ + MSM_RPM_ID_RESERVED_0, + MSM_RPM_ID_RESERVED_5 = + MSM_RPM_ID_RESERVED_0 + 5, + + MSM_RPM_ID_CXO_CLK, + MSM_RPM_ID_PXO_CLK, + 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_EBI1_CLK, + + MSM_RPM_ID_APPS_FABRIC_CFG_HALT_0, + MSM_RPM_ID_APPS_FABRIC_HALT_0 = + MSM_RPM_ID_APPS_FABRIC_CFG_HALT_0, + MSM_RPM_ID_APPS_FABRIC_CFG_HALT_1, + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_0, + MSM_RPM_ID_APPS_FABRIC_CLOCK_MODE_0 = + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_0, + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_1, + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_2, + MSM_RPM_ID_APPS_FABRIC_CFG_IOCTL, + MSM_RPM_ID_APPS_FABRIC_ARB_0, + MSM_RPM_ID_APPS_FABRIC_ARB_11 = + MSM_RPM_ID_APPS_FABRIC_ARB_0 + 11, + + MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0, + MSM_RPM_ID_SYSTEM_FABRIC_HALT_0 = + MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0, + MSM_RPM_ID_SYS_FABRIC_CFG_HALT_1, + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_0, + MSM_RPM_ID_SYSTEM_FABRIC_CLOCK_MODE_0 = + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_0, + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_1, + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_2, + MSM_RPM_ID_SYS_FABRIC_CFG_IOCTL, + MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + MSM_RPM_ID_SYSTEM_FABRIC_ARB_29 = + MSM_RPM_ID_SYSTEM_FABRIC_ARB_0 + 29, + + MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_0, + MSM_RPM_ID_MM_FABRIC_HALT_0 = + MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_0, + MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_1, + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_0, + MSM_RPM_ID_MM_FABRIC_CLOCK_MODE_0 = + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_0, + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_1, + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_2, + MSM_RPM_ID_MMSS_FABRIC_CFG_IOCTL, + MSM_RPM_ID_MM_FABRIC_ARB_0, + MSM_RPM_ID_MM_FABRIC_ARB_22 = + MSM_RPM_ID_MM_FABRIC_ARB_0 + 22, + + MSM_RPM_ID_PM8921_S1_0, + MSM_RPM_ID_PM8921_S1_1, + MSM_RPM_ID_PM8921_S2_0, + MSM_RPM_ID_PM8921_S2_1, + MSM_RPM_ID_PM8921_S3_0, + MSM_RPM_ID_PM8921_S3_1, + MSM_RPM_ID_PM8921_S4_0, + MSM_RPM_ID_PM8921_S4_1, + MSM_RPM_ID_PM8921_S5_0, + MSM_RPM_ID_PM8921_S5_1, + MSM_RPM_ID_PM8921_S6_0, + MSM_RPM_ID_PM8921_S6_1, + MSM_RPM_ID_PM8921_S7_0, + MSM_RPM_ID_PM8921_S7_1, + MSM_RPM_ID_PM8921_S8_0, + MSM_RPM_ID_PM8921_S8_1, + MSM_RPM_ID_PM8921_L1_0, + MSM_RPM_ID_PM8921_L1_1, + MSM_RPM_ID_PM8921_L2_0, + MSM_RPM_ID_PM8921_L2_1, + MSM_RPM_ID_PM8921_L3_0, + MSM_RPM_ID_PM8921_L3_1, + MSM_RPM_ID_PM8921_L4_0, + MSM_RPM_ID_PM8921_L4_1, + MSM_RPM_ID_PM8921_L5_0, + MSM_RPM_ID_PM8921_L5_1, + MSM_RPM_ID_PM8921_L6_0, + MSM_RPM_ID_PM8921_L6_1, + MSM_RPM_ID_PM8921_L7_0, + MSM_RPM_ID_PM8921_L7_1, + MSM_RPM_ID_PM8921_L8_0, + MSM_RPM_ID_PM8921_L8_1, + MSM_RPM_ID_PM8921_L9_0, + MSM_RPM_ID_PM8921_L9_1, + MSM_RPM_ID_PM8921_L10_0, + MSM_RPM_ID_PM8921_L10_1, + MSM_RPM_ID_PM8921_L11_0, + MSM_RPM_ID_PM8921_L11_1, + MSM_RPM_ID_PM8921_L12_0, + MSM_RPM_ID_PM8921_L12_1, + MSM_RPM_ID_PM8921_L13_0, + MSM_RPM_ID_PM8921_L13_1, + MSM_RPM_ID_PM8921_L14_0, + MSM_RPM_ID_PM8921_L14_1, + MSM_RPM_ID_PM8921_L15_0, + MSM_RPM_ID_PM8921_L15_1, + MSM_RPM_ID_PM8921_L16_0, + MSM_RPM_ID_PM8921_L16_1, + MSM_RPM_ID_PM8921_L17_0, + MSM_RPM_ID_PM8921_L17_1, + MSM_RPM_ID_PM8921_L18_0, + MSM_RPM_ID_PM8921_L18_1, + MSM_RPM_ID_PM8921_L19_0, + MSM_RPM_ID_PM8921_L19_1, + MSM_RPM_ID_PM8921_L20_0, + MSM_RPM_ID_PM8921_L20_1, + MSM_RPM_ID_PM8921_L21_0, + MSM_RPM_ID_PM8921_L21_1, + MSM_RPM_ID_PM8921_L22_0, + MSM_RPM_ID_PM8921_L22_1, + MSM_RPM_ID_PM8921_L23_0, + MSM_RPM_ID_PM8921_L23_1, + MSM_RPM_ID_PM8921_L24_0, + MSM_RPM_ID_PM8921_L24_1, + MSM_RPM_ID_PM8921_L25_0, + MSM_RPM_ID_PM8921_L25_1, + MSM_RPM_ID_PM8921_L26_0, + MSM_RPM_ID_PM8921_L26_1, + MSM_RPM_ID_PM8921_L27_0, + MSM_RPM_ID_PM8921_L27_1, + MSM_RPM_ID_PM8921_L28_0, + MSM_RPM_ID_PM8921_L28_1, + MSM_RPM_ID_PM8921_L29_0, + MSM_RPM_ID_PM8921_L29_1, + MSM_RPM_ID_PM8921_CLK1_0, + MSM_RPM_ID_PM8921_CLK1_1, + MSM_RPM_ID_PM8921_CLK2_0, + MSM_RPM_ID_PM8921_CLK2_1, + MSM_RPM_ID_PM8921_LVS1, + MSM_RPM_ID_PM8921_LVS2, + MSM_RPM_ID_PM8921_LVS3, + MSM_RPM_ID_PM8921_LVS4, + MSM_RPM_ID_PM8921_LVS5, + MSM_RPM_ID_PM8921_LVS6, + MSM_RPM_ID_PM8921_LVS7, + MSM_RPM_ID_NCP_0, + MSM_RPM_ID_NCP_1, + MSM_RPM_ID_CXO_BUFFERS, + MSM_RPM_ID_USB_OTG_SWITCH, + MSM_RPM_ID_HDMI_SWITCH, + MSM_RPM_ID_DDR_DMM_0, + MSM_RPM_ID_DDR_DMM_1, + MSM_RPM_ID_QDSS_CLK, + + /* 8660 specific ids */ + 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_PLL_4, + MSM_RPM_ID_SMI_CLK, + MSM_RPM_ID_APPS_L2_CACHE_CTL, + + /* 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, + + /* 9615 specific */ + MSM_RPM_ID_PM8018_S1_0, + MSM_RPM_ID_PM8018_S1_1, + MSM_RPM_ID_PM8018_S2_0, + MSM_RPM_ID_PM8018_S2_1, + MSM_RPM_ID_PM8018_S3_0, + MSM_RPM_ID_PM8018_S3_1, + MSM_RPM_ID_PM8018_S4_0, + MSM_RPM_ID_PM8018_S4_1, + MSM_RPM_ID_PM8018_S5_0, + MSM_RPM_ID_PM8018_S5_1, + MSM_RPM_ID_PM8018_L1_0, + MSM_RPM_ID_PM8018_L1_1, + MSM_RPM_ID_PM8018_L2_0, + MSM_RPM_ID_PM8018_L2_1, + MSM_RPM_ID_PM8018_L3_0, + MSM_RPM_ID_PM8018_L3_1, + MSM_RPM_ID_PM8018_L4_0, + MSM_RPM_ID_PM8018_L4_1, + MSM_RPM_ID_PM8018_L5_0, + MSM_RPM_ID_PM8018_L5_1, + MSM_RPM_ID_PM8018_L6_0, + MSM_RPM_ID_PM8018_L6_1, + MSM_RPM_ID_PM8018_L7_0, + MSM_RPM_ID_PM8018_L7_1, + MSM_RPM_ID_PM8018_L8_0, + MSM_RPM_ID_PM8018_L8_1, + MSM_RPM_ID_PM8018_L9_0, + MSM_RPM_ID_PM8018_L9_1, + MSM_RPM_ID_PM8018_L10_0, + MSM_RPM_ID_PM8018_L10_1, + MSM_RPM_ID_PM8018_L11_0, + MSM_RPM_ID_PM8018_L11_1, + MSM_RPM_ID_PM8018_L12_0, + MSM_RPM_ID_PM8018_L12_1, + MSM_RPM_ID_PM8018_L13_0, + MSM_RPM_ID_PM8018_L13_1, + MSM_RPM_ID_PM8018_L14_0, + MSM_RPM_ID_PM8018_L14_1, + MSM_RPM_ID_PM8018_LVS1, + + /* 8930 specific */ + MSM_RPM_ID_PM8038_S1_0, + MSM_RPM_ID_PM8038_S1_1, + MSM_RPM_ID_PM8038_S2_0, + MSM_RPM_ID_PM8038_S2_1, + MSM_RPM_ID_PM8038_S3_0, + MSM_RPM_ID_PM8038_S3_1, + MSM_RPM_ID_PM8038_S4_0, + MSM_RPM_ID_PM8038_S4_1, + MSM_RPM_ID_PM8038_S5_0, + MSM_RPM_ID_PM8038_S5_1, + MSM_RPM_ID_PM8038_S6_0, + MSM_RPM_ID_PM8038_S6_1, + MSM_RPM_ID_PM8038_L1_0, + MSM_RPM_ID_PM8038_L1_1, + MSM_RPM_ID_PM8038_L2_0, + MSM_RPM_ID_PM8038_L2_1, + MSM_RPM_ID_PM8038_L3_0, + MSM_RPM_ID_PM8038_L3_1, + MSM_RPM_ID_PM8038_L4_0, + MSM_RPM_ID_PM8038_L4_1, + MSM_RPM_ID_PM8038_L5_0, + MSM_RPM_ID_PM8038_L5_1, + MSM_RPM_ID_PM8038_L6_0, + MSM_RPM_ID_PM8038_L6_1, + MSM_RPM_ID_PM8038_L7_0, + MSM_RPM_ID_PM8038_L7_1, + MSM_RPM_ID_PM8038_L8_0, + MSM_RPM_ID_PM8038_L8_1, + MSM_RPM_ID_PM8038_L9_0, + MSM_RPM_ID_PM8038_L9_1, + MSM_RPM_ID_PM8038_L10_0, + MSM_RPM_ID_PM8038_L10_1, + MSM_RPM_ID_PM8038_L11_0, + MSM_RPM_ID_PM8038_L11_1, + MSM_RPM_ID_PM8038_L12_0, + MSM_RPM_ID_PM8038_L12_1, + MSM_RPM_ID_PM8038_L13_0, + MSM_RPM_ID_PM8038_L13_1, + MSM_RPM_ID_PM8038_L14_0, + MSM_RPM_ID_PM8038_L14_1, + MSM_RPM_ID_PM8038_L15_0, + MSM_RPM_ID_PM8038_L15_1, + MSM_RPM_ID_PM8038_L16_0, + MSM_RPM_ID_PM8038_L16_1, + MSM_RPM_ID_PM8038_L17_0, + MSM_RPM_ID_PM8038_L17_1, + MSM_RPM_ID_PM8038_L18_0, + MSM_RPM_ID_PM8038_L18_1, + MSM_RPM_ID_PM8038_L19_0, + MSM_RPM_ID_PM8038_L19_1, + MSM_RPM_ID_PM8038_L20_0, + MSM_RPM_ID_PM8038_L20_1, + MSM_RPM_ID_PM8038_L21_0, + MSM_RPM_ID_PM8038_L21_1, + MSM_RPM_ID_PM8038_L22_0, + MSM_RPM_ID_PM8038_L22_1, + MSM_RPM_ID_PM8038_L23_0, + MSM_RPM_ID_PM8038_L23_1, + MSM_RPM_ID_PM8038_L24_0, + MSM_RPM_ID_PM8038_L24_1, + MSM_RPM_ID_PM8038_L25_0, + MSM_RPM_ID_PM8038_L25_1, + MSM_RPM_ID_PM8038_L26_0, + MSM_RPM_ID_PM8038_L26_1, + MSM_RPM_ID_PM8038_L27_0, + MSM_RPM_ID_PM8038_L27_1, + MSM_RPM_ID_PM8038_CLK1_0, + MSM_RPM_ID_PM8038_CLK1_1, + MSM_RPM_ID_PM8038_CLK2_0, + MSM_RPM_ID_PM8038_CLK2_1, + MSM_RPM_ID_PM8038_LVS1, + MSM_RPM_ID_PM8038_LVS2, + MSM_RPM_ID_VOLTAGE_CORNER, + + /* 8064 specific */ + MSM_RPM_ID_PM8821_S1_0, + MSM_RPM_ID_PM8821_S1_1, + MSM_RPM_ID_PM8821_S2_0, + MSM_RPM_ID_PM8821_S2_1, + MSM_RPM_ID_PM8821_L1_0, + MSM_RPM_ID_PM8821_L1_1, + + MSM_RPM_ID_LAST, +}; + +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_SUPPORTED_RESOURCES_0, + MSM_RPM_STATUS_ID_SEQUENCE, + MSM_RPM_STATUS_ID_RPM_CTL, + MSM_RPM_STATUS_ID_CXO_CLK, + MSM_RPM_STATUS_ID_PXO_CLK, + 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_EBI1_CLK, + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_HALT, + MSM_RPM_STATUS_ID_APPS_FABRIC_HALT = + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_HALT, + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_CLKMOD, + MSM_RPM_STATUS_ID_APPS_FABRIC_CLOCK_MODE = + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_CLKMOD, + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_IOCTL, + MSM_RPM_STATUS_ID_APPS_FABRIC_ARB, + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_HALT, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_HALT = + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_HALT, + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_CLKMOD, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_CLOCK_MODE = + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_CLKMOD, + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_IOCTL, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_ARB, + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_HALT, + MSM_RPM_STATUS_ID_MM_FABRIC_HALT = + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_HALT, + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_CLKMOD, + MSM_RPM_STATUS_ID_MM_FABRIC_CLOCK_MODE = + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_CLKMOD, + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_IOCTL, + MSM_RPM_STATUS_ID_MM_FABRIC_ARB, + MSM_RPM_STATUS_ID_PM8921_S1_0, + MSM_RPM_STATUS_ID_PM8921_S1_1, + MSM_RPM_STATUS_ID_PM8921_S2_0, + MSM_RPM_STATUS_ID_PM8921_S2_1, + MSM_RPM_STATUS_ID_PM8921_S3_0, + MSM_RPM_STATUS_ID_PM8921_S3_1, + MSM_RPM_STATUS_ID_PM8921_S4_0, + MSM_RPM_STATUS_ID_PM8921_S4_1, + MSM_RPM_STATUS_ID_PM8921_S5_0, + MSM_RPM_STATUS_ID_PM8921_S5_1, + MSM_RPM_STATUS_ID_PM8921_S6_0, + MSM_RPM_STATUS_ID_PM8921_S6_1, + MSM_RPM_STATUS_ID_PM8921_S7_0, + MSM_RPM_STATUS_ID_PM8921_S7_1, + MSM_RPM_STATUS_ID_PM8921_S8_0, + MSM_RPM_STATUS_ID_PM8921_S8_1, + MSM_RPM_STATUS_ID_PM8921_L1_0, + MSM_RPM_STATUS_ID_PM8921_L1_1, + MSM_RPM_STATUS_ID_PM8921_L2_0, + MSM_RPM_STATUS_ID_PM8921_L2_1, + MSM_RPM_STATUS_ID_PM8921_L3_0, + MSM_RPM_STATUS_ID_PM8921_L3_1, + MSM_RPM_STATUS_ID_PM8921_L4_0, + MSM_RPM_STATUS_ID_PM8921_L4_1, + MSM_RPM_STATUS_ID_PM8921_L5_0, + MSM_RPM_STATUS_ID_PM8921_L5_1, + MSM_RPM_STATUS_ID_PM8921_L6_0, + MSM_RPM_STATUS_ID_PM8921_L6_1, + MSM_RPM_STATUS_ID_PM8921_L7_0, + MSM_RPM_STATUS_ID_PM8921_L7_1, + MSM_RPM_STATUS_ID_PM8921_L8_0, + MSM_RPM_STATUS_ID_PM8921_L8_1, + MSM_RPM_STATUS_ID_PM8921_L9_0, + MSM_RPM_STATUS_ID_PM8921_L9_1, + MSM_RPM_STATUS_ID_PM8921_L10_0, + MSM_RPM_STATUS_ID_PM8921_L10_1, + MSM_RPM_STATUS_ID_PM8921_L11_0, + MSM_RPM_STATUS_ID_PM8921_L11_1, + MSM_RPM_STATUS_ID_PM8921_L12_0, + MSM_RPM_STATUS_ID_PM8921_L12_1, + MSM_RPM_STATUS_ID_PM8921_L13_0, + MSM_RPM_STATUS_ID_PM8921_L13_1, + MSM_RPM_STATUS_ID_PM8921_L14_0, + MSM_RPM_STATUS_ID_PM8921_L14_1, + MSM_RPM_STATUS_ID_PM8921_L15_0, + MSM_RPM_STATUS_ID_PM8921_L15_1, + MSM_RPM_STATUS_ID_PM8921_L16_0, + MSM_RPM_STATUS_ID_PM8921_L16_1, + MSM_RPM_STATUS_ID_PM8921_L17_0, + MSM_RPM_STATUS_ID_PM8921_L17_1, + MSM_RPM_STATUS_ID_PM8921_L18_0, + MSM_RPM_STATUS_ID_PM8921_L18_1, + MSM_RPM_STATUS_ID_PM8921_L19_0, + MSM_RPM_STATUS_ID_PM8921_L19_1, + MSM_RPM_STATUS_ID_PM8921_L20_0, + MSM_RPM_STATUS_ID_PM8921_L20_1, + MSM_RPM_STATUS_ID_PM8921_L21_0, + MSM_RPM_STATUS_ID_PM8921_L21_1, + MSM_RPM_STATUS_ID_PM8921_L22_0, + MSM_RPM_STATUS_ID_PM8921_L22_1, + MSM_RPM_STATUS_ID_PM8921_L23_0, + MSM_RPM_STATUS_ID_PM8921_L23_1, + MSM_RPM_STATUS_ID_PM8921_L24_0, + MSM_RPM_STATUS_ID_PM8921_L24_1, + MSM_RPM_STATUS_ID_PM8921_L25_0, + MSM_RPM_STATUS_ID_PM8921_L25_1, + MSM_RPM_STATUS_ID_PM8921_L26_0, + MSM_RPM_STATUS_ID_PM8921_L26_1, + MSM_RPM_STATUS_ID_PM8921_L27_0, + MSM_RPM_STATUS_ID_PM8921_L27_1, + MSM_RPM_STATUS_ID_PM8921_L28_0, + MSM_RPM_STATUS_ID_PM8921_L28_1, + MSM_RPM_STATUS_ID_PM8921_L29_0, + MSM_RPM_STATUS_ID_PM8921_L29_1, + MSM_RPM_STATUS_ID_PM8921_CLK1_0, + MSM_RPM_STATUS_ID_PM8921_CLK1_1, + MSM_RPM_STATUS_ID_PM8921_CLK2_0, + MSM_RPM_STATUS_ID_PM8921_CLK2_1, + MSM_RPM_STATUS_ID_PM8921_LVS1, + MSM_RPM_STATUS_ID_PM8921_LVS2, + MSM_RPM_STATUS_ID_PM8921_LVS3, + MSM_RPM_STATUS_ID_PM8921_LVS4, + MSM_RPM_STATUS_ID_PM8921_LVS5, + MSM_RPM_STATUS_ID_PM8921_LVS6, + MSM_RPM_STATUS_ID_PM8921_LVS7, + MSM_RPM_STATUS_ID_NCP_0, + MSM_RPM_STATUS_ID_NCP_1, + MSM_RPM_STATUS_ID_CXO_BUFFERS, + MSM_RPM_STATUS_ID_USB_OTG_SWITCH, + MSM_RPM_STATUS_ID_HDMI_SWITCH, + MSM_RPM_STATUS_ID_DDR_DMM_0, + MSM_RPM_STATUS_ID_DDR_DMM_1, + MSM_RPM_STATUS_ID_EBI1_CH0_RANGE, + MSM_RPM_STATUS_ID_EBI1_CH1_RANGE, + MSM_RPM_STATUS_ID_QDSS_CLK, + + /* 8660 Specific */ + MSM_RPM_STATUS_ID_PLL_4, + MSM_RPM_STATUS_ID_SMI_CLK, + MSM_RPM_STATUS_ID_APPS_L2_CACHE_CTL, + 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, + 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, + + /* 9615 Specific */ + MSM_RPM_STATUS_ID_PM8018_S1_0, + MSM_RPM_STATUS_ID_PM8018_S1_1, + MSM_RPM_STATUS_ID_PM8018_S2_0, + MSM_RPM_STATUS_ID_PM8018_S2_1, + MSM_RPM_STATUS_ID_PM8018_S3_0, + MSM_RPM_STATUS_ID_PM8018_S3_1, + MSM_RPM_STATUS_ID_PM8018_S4_0, + MSM_RPM_STATUS_ID_PM8018_S4_1, + MSM_RPM_STATUS_ID_PM8018_S5_0, + MSM_RPM_STATUS_ID_PM8018_S5_1, + MSM_RPM_STATUS_ID_PM8018_L1_0, + MSM_RPM_STATUS_ID_PM8018_L1_1, + MSM_RPM_STATUS_ID_PM8018_L2_0, + MSM_RPM_STATUS_ID_PM8018_L2_1, + MSM_RPM_STATUS_ID_PM8018_L3_0, + MSM_RPM_STATUS_ID_PM8018_L3_1, + MSM_RPM_STATUS_ID_PM8018_L4_0, + MSM_RPM_STATUS_ID_PM8018_L4_1, + MSM_RPM_STATUS_ID_PM8018_L5_0, + MSM_RPM_STATUS_ID_PM8018_L5_1, + MSM_RPM_STATUS_ID_PM8018_L6_0, + MSM_RPM_STATUS_ID_PM8018_L6_1, + MSM_RPM_STATUS_ID_PM8018_L7_0, + MSM_RPM_STATUS_ID_PM8018_L7_1, + MSM_RPM_STATUS_ID_PM8018_L8_0, + MSM_RPM_STATUS_ID_PM8018_L8_1, + MSM_RPM_STATUS_ID_PM8018_L9_0, + MSM_RPM_STATUS_ID_PM8018_L9_1, + MSM_RPM_STATUS_ID_PM8018_L10_0, + MSM_RPM_STATUS_ID_PM8018_L10_1, + MSM_RPM_STATUS_ID_PM8018_L11_0, + MSM_RPM_STATUS_ID_PM8018_L11_1, + MSM_RPM_STATUS_ID_PM8018_L12_0, + MSM_RPM_STATUS_ID_PM8018_L12_1, + MSM_RPM_STATUS_ID_PM8018_L13_0, + MSM_RPM_STATUS_ID_PM8018_L13_1, + MSM_RPM_STATUS_ID_PM8018_L14_0, + MSM_RPM_STATUS_ID_PM8018_L14_1, + MSM_RPM_STATUS_ID_PM8018_LVS1, + + /* 8930 specific */ + MSM_RPM_STATUS_ID_PM8038_S1_0, + MSM_RPM_STATUS_ID_PM8038_S1_1, + MSM_RPM_STATUS_ID_PM8038_S2_0, + MSM_RPM_STATUS_ID_PM8038_S2_1, + MSM_RPM_STATUS_ID_PM8038_S3_0, + MSM_RPM_STATUS_ID_PM8038_S3_1, + MSM_RPM_STATUS_ID_PM8038_S4_0, + MSM_RPM_STATUS_ID_PM8038_S4_1, + MSM_RPM_STATUS_ID_PM8038_S5_0, + MSM_RPM_STATUS_ID_PM8038_S5_1, + MSM_RPM_STATUS_ID_PM8038_S6_0, + MSM_RPM_STATUS_ID_PM8038_S6_1, + MSM_RPM_STATUS_ID_PM8038_L1_0, + MSM_RPM_STATUS_ID_PM8038_L1_1, + MSM_RPM_STATUS_ID_PM8038_L2_0, + MSM_RPM_STATUS_ID_PM8038_L2_1, + MSM_RPM_STATUS_ID_PM8038_L3_0, + MSM_RPM_STATUS_ID_PM8038_L3_1, + MSM_RPM_STATUS_ID_PM8038_L4_0, + MSM_RPM_STATUS_ID_PM8038_L4_1, + MSM_RPM_STATUS_ID_PM8038_L5_0, + MSM_RPM_STATUS_ID_PM8038_L5_1, + MSM_RPM_STATUS_ID_PM8038_L6_0, + MSM_RPM_STATUS_ID_PM8038_L6_1, + MSM_RPM_STATUS_ID_PM8038_L7_0, + MSM_RPM_STATUS_ID_PM8038_L7_1, + MSM_RPM_STATUS_ID_PM8038_L8_0, + MSM_RPM_STATUS_ID_PM8038_L8_1, + MSM_RPM_STATUS_ID_PM8038_L9_0, + MSM_RPM_STATUS_ID_PM8038_L9_1, + MSM_RPM_STATUS_ID_PM8038_L10_0, + MSM_RPM_STATUS_ID_PM8038_L10_1, + MSM_RPM_STATUS_ID_PM8038_L11_0, + MSM_RPM_STATUS_ID_PM8038_L11_1, + MSM_RPM_STATUS_ID_PM8038_L12_0, + MSM_RPM_STATUS_ID_PM8038_L12_1, + MSM_RPM_STATUS_ID_PM8038_L13_0, + MSM_RPM_STATUS_ID_PM8038_L13_1, + MSM_RPM_STATUS_ID_PM8038_L14_0, + MSM_RPM_STATUS_ID_PM8038_L14_1, + MSM_RPM_STATUS_ID_PM8038_L15_0, + MSM_RPM_STATUS_ID_PM8038_L15_1, + MSM_RPM_STATUS_ID_PM8038_L16_0, + MSM_RPM_STATUS_ID_PM8038_L16_1, + MSM_RPM_STATUS_ID_PM8038_L17_0, + MSM_RPM_STATUS_ID_PM8038_L17_1, + MSM_RPM_STATUS_ID_PM8038_L18_0, + MSM_RPM_STATUS_ID_PM8038_L18_1, + MSM_RPM_STATUS_ID_PM8038_L19_0, + MSM_RPM_STATUS_ID_PM8038_L19_1, + MSM_RPM_STATUS_ID_PM8038_L20_0, + MSM_RPM_STATUS_ID_PM8038_L20_1, + MSM_RPM_STATUS_ID_PM8038_L21_0, + MSM_RPM_STATUS_ID_PM8038_L21_1, + MSM_RPM_STATUS_ID_PM8038_L22_0, + MSM_RPM_STATUS_ID_PM8038_L22_1, + MSM_RPM_STATUS_ID_PM8038_L23_0, + MSM_RPM_STATUS_ID_PM8038_L23_1, + MSM_RPM_STATUS_ID_PM8038_L24_0, + MSM_RPM_STATUS_ID_PM8038_L24_1, + MSM_RPM_STATUS_ID_PM8038_L25_0, + MSM_RPM_STATUS_ID_PM8038_L25_1, + MSM_RPM_STATUS_ID_PM8038_L26_0, + MSM_RPM_STATUS_ID_PM8038_L26_1, + MSM_RPM_STATUS_ID_PM8038_L27_0, + MSM_RPM_STATUS_ID_PM8038_L27_1, + MSM_RPM_STATUS_ID_PM8038_CLK1_0, + MSM_RPM_STATUS_ID_PM8038_CLK1_1, + MSM_RPM_STATUS_ID_PM8038_CLK2_0, + MSM_RPM_STATUS_ID_PM8038_CLK2_1, + MSM_RPM_STATUS_ID_PM8038_LVS1, + MSM_RPM_STATUS_ID_PM8038_LVS2, + MSM_RPM_STATUS_ID_VOLTAGE_CORNER, + + /* 8064 specific */ + MSM_RPM_STATUS_ID_PM8821_S1_0, + MSM_RPM_STATUS_ID_PM8821_S1_1, + MSM_RPM_STATUS_ID_PM8821_S2_0, + MSM_RPM_STATUS_ID_PM8821_S2_1, + MSM_RPM_STATUS_ID_PM8821_L1_0, + MSM_RPM_STATUS_ID_PM8821_L1_1, + + MSM_RPM_STATUS_ID_LAST, +}; + +static inline uint32_t msm_rpm_get_ctx_mask(unsigned int ctx) +{ + return 1UL << ctx; +} + +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[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(t, i, s, c) \ + [MSM_RPM_ID_##i] = \ + {\ + .id = MSM_RPM_##t##_ID_##i, \ + .sel = MSM_RPM_##t##_SEL_##s, \ + .count = c, \ + } + +#define MSM_RPM_STATUS_ID_VALID BIT(31) + +#define MSM_RPM_STATUS_ID_MAP(t, i) \ + [MSM_RPM_STATUS_ID_## i] = (MSM_RPM_##t##_STATUS_ID_##i \ + | MSM_RPM_STATUS_ID_VALID) + +#define MSM_RPM_CTRL_MAP(t, i) \ + [MSM_RPM_CTRL_##i] = MSM_RPM_##t##_CTRL_##i + + +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_wakeup; + void *ipc_rpm_reg; + unsigned int ipc_rpm_val; + struct msm_rpm_map_data target_id[MSM_RPM_ID_LAST]; + unsigned int target_status[MSM_RPM_STATUS_ID_LAST]; + unsigned int target_ctrl_id[MSM_RPM_CTRL_LAST]; + unsigned int sel_invalidate, sel_notification, sel_last; + unsigned int ver[3]; +}; + +extern struct msm_rpm_platform_data msm8660_rpm_data; +extern struct msm_rpm_platform_data msm8960_rpm_data; +extern struct msm_rpm_platform_data msm9615_rpm_data; +extern struct msm_rpm_platform_data msm8930_rpm_data; +extern struct msm_rpm_platform_data apq8064_rpm_data; + +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 0000000000000000000000000000000000000000..5393da1020dafcc5eb303687220a3099d32ccb7c --- /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 0000000000000000000000000000000000000000..7cc5f7a5acd957f82f93e2dd71a8f8cd6daee31d --- /dev/null +++ b/arch/arm/mach-msm/include/mach/scm.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IO 0x5 +#define SCM_SVC_INFO 0x6 +#define SCM_SVC_SSD 0x7 +#define SCM_SVC_FUSE 0x8 +#define SCM_SVC_PWR 0x9 +#define SCM_SVC_CP 0xC +#define SCM_SVC_DCVS 0xD +#define SCM_SVC_TZSCHEDULER 0xFC + +#ifdef CONFIG_MSM_SCM +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); + +extern s32 scm_call_atomic1(u32 svc, u32 cmd, u32 arg1); +extern s32 scm_call_atomic2(u32 svc, u32 cmd, u32 arg1, u32 arg2); +extern s32 scm_call_atomic4_3(u32 svc, u32 cmd, u32 arg1, u32 arg2, u32 arg3, + u32 arg4, u32 *ret1, u32 *ret2); + +#define SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF)) + +extern u32 scm_get_version(void); +extern int scm_is_call_available(u32 svc_id, u32 cmd_id); +extern int scm_get_feat_version(u32 feat); + +#else + +static inline int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, + size_t cmd_len, void *resp_buf, size_t resp_len) +{ + return 0; +} + +static inline s32 scm_call_atomic1(u32 svc, u32 cmd, u32 arg1) +{ + return 0; +} + +static inline s32 scm_call_atomic2(u32 svc, u32 cmd, u32 arg1, u32 arg2) +{ + return 0; +} + +static inline s32 scm_call_atomic4_3(u32 svc, u32 cmd, u32 arg1, u32 arg2, + u32 arg3, u32 arg4, u32 *ret1, u32 *ret2) +{ + return 0; +} + +static inline u32 scm_get_version(void) +{ + return 0; +} + +static inline int scm_is_call_available(u32 svc_id, u32 cmd_id) +{ + return 0; +} + +static inline int scm_get_feat_version(u32 feat) +{ + return 0; +} + +#endif +#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 0000000000000000000000000000000000000000..8b8ee5a6363361301c8d17487f53e8419e0bc428 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_al.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. + * + */ + +/* + * 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 + +#ifdef CONFIG_MSM_SDIO_AL + +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); + +#else + +static int __maybe_unused sdio_open(const char *name, struct sdio_channel **ch, + void *priv, void (*notify)(void *priv, unsigned channel_event)) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_close(struct sdio_channel *ch) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_read(struct sdio_channel *ch, void *data, + int len) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_write(struct sdio_channel *ch, const void *data, + int len) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_write_avail(struct sdio_channel *ch) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_read_avail(struct sdio_channel *ch) +{ + return -ENODEV; +} +#endif + +#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 0000000000000000000000000000000000000000..4bcd607896c341c68d21c726660f2f886b4eb9ff --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_cmux.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. + * + */ + +/* + * SDIO CMUX API + */ + +#ifndef __SDIO_CMUX__ +#define __SDIO_CMUX__ + +#ifdef CONFIG_MSM_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_CSVT_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); + +#else + +static int __maybe_unused 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) +{ + return -ENODEV; +} +static int __maybe_unused sdio_cmux_close(int id) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_cmux_write_avail(int id) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_cmux_write(int id, void *data, int len) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_cmux_tiocmget(int id) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_cmux_tiocmset(int id, unsigned int set, + unsigned int clear) +{ + return -ENODEV; +} + +static int __maybe_unused is_remote_open(int id) +{ + return -ENODEV; +} + +static int __maybe_unused sdio_cmux_is_channel_reset(int id) +{ + return -ENODEV; +} +#endif +#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 0000000000000000000000000000000000000000..afc1b11c8259a3fa8288fde3c2d6344cddadd0ac --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_dmux.h @@ -0,0 +1,83 @@ +/* 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 + +#ifdef CONFIG_MSM_SDIO_DMUX +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); + +#else + +static int __maybe_unused msm_sdio_dmux_open(uint32_t id, void *priv, + void (*receive_cb)(void *, struct sk_buff *), + void (*write_done)(void *, struct sk_buff *)) +{ + return -ENODEV; +} + +static int __maybe_unused msm_sdio_is_channel_in_reset(uint32_t id) +{ + return -ENODEV; +} + +static int __maybe_unused msm_sdio_dmux_close(uint32_t id) +{ + return -ENODEV; +} + +static int __maybe_unused msm_sdio_dmux_write(uint32_t id, struct sk_buff *skb) +{ + return -ENODEV; +} + +static int __maybe_unused msm_sdio_dmux_is_ch_full(uint32_t id) +{ + return -ENODEV; +} + +static int __maybe_unused msm_sdio_dmux_is_ch_low(uint32_t id) +{ + return -ENODEV; +} + +#endif + +#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 0000000000000000000000000000000000000000..b47001f09b197c7132cd190af5703ed2a595c09a --- /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/sirc-fsm9xxx.h b/arch/arm/mach-msm/include/mach/sirc-fsm9xxx.h new file mode 100644 index 0000000000000000000000000000000000000000..b86221103dae0502063169e58ab56f9e570f346d --- /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 ef55868a5b8ababa515882678db7bfb9c07e2bf6..607bab5da5bd19b5617a57d4df55a51d70d5f818 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 0000000000000000000000000000000000000000..a94ae76a5a63bc97e60ad22ebae5f73470dfdeb5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/smem_log.h @@ -0,0 +1,231 @@ +/* Copyright (c) 2008-2009, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_PROC_ID_WCNSS 0xC0000000 + +#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/socinfo.h b/arch/arm/mach-msm/include/mach/socinfo.h new file mode 100644 index 0000000000000000000000000000000000000000..c0ad65b8b499c064c1c6ee21a783bf15c1fcfce5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/socinfo.h @@ -0,0 +1,312 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#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) + +#ifdef CONFIG_OF +#define early_machine_is_copper() \ + of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,msmcopper") +#define machine_is_copper() \ + of_machine_is_compatible("qcom,msmcopper") +#define machine_is_copper_sim() \ + of_machine_is_compatible("qcom,msmcopper-sim") +#define machine_is_copper_rumi() \ + of_machine_is_compatible("qcom,msmcopper-rumi") +#define early_machine_is_msm9625() \ + of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,msm9625") +#define machine_is_msm9625() \ + of_machine_is_compatible("qcom,msm9625") +#else +#define early_machine_is_copper() 0 +#define machine_is_copper() 0 +#define machine_is_copper_sim() 0 +#define machine_is_copper_rumi() 0 +#define early_machine_is_msm9625() 0 +#define machine_is_msm9625() 0 +#endif + +#define PLATFORM_SUBTYPE_SGLTE 6 + +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_7X25AB, + MSM_CPU_8064, + MSM_CPU_8930, + MSM_CPU_7X27AA, + MSM_CPU_9615, + MSM_CPU_COPPER, + MSM_CPU_8627, + MSM_CPU_8625, + MSM_CPU_9625 +}; + +enum msm_cpu socinfo_get_msm_cpu(void); +uint32_t socinfo_get_id(void); +uint32_t socinfo_get_version(void); +uint32_t socinfo_get_raw_id(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; +const int read_msm_cpu_type(void); +const int get_core_count(void); +const int cpu_is_krait_v1(void); + +static inline int cpu_is_msm7x01(void) +{ +#ifdef CONFIG_ARCH_MSM7X01A + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X01; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x25(void) +{ +#ifdef CONFIG_ARCH_MSM7X25 + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x27(void) +{ +#if defined(CONFIG_ARCH_MSM7X27) && !defined(CONFIG_ARCH_MSM7X27A) + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X27; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x27a(void) +{ +#ifdef CONFIG_ARCH_MSM7X27A + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X27A; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x27aa(void) +{ +#ifdef CONFIG_ARCH_MSM7X27A + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X27AA; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x25a(void) +{ +#ifdef CONFIG_ARCH_MSM7X27A + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25A; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x25aa(void) +{ +#ifdef CONFIG_ARCH_MSM7X27A + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25AA; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x25ab(void) +{ +#ifdef CONFIG_ARCH_MSM7X27A + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25AB; +#else + return 0; +#endif +} + +static inline int cpu_is_msm7x30(void) +{ +#ifdef CONFIG_ARCH_MSM7X30 + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X30; +#else + return 0; +#endif +} + +static inline int cpu_is_qsd8x50(void) +{ +#ifdef CONFIG_ARCH_QSD8X50 + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_8X50; +#else + return 0; +#endif +} + +static inline int cpu_is_msm8x55(void) +{ +#ifdef CONFIG_ARCH_MSM7X30 + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_8X55; +#else + return 0; +#endif +} + +static inline int cpu_is_msm8x60(void) +{ +#ifdef CONFIG_ARCH_MSM8X60 + return read_msm_cpu_type() == MSM_CPU_8X60; +#else + return 0; +#endif +} + +static inline int cpu_is_msm8960(void) +{ +#ifdef CONFIG_ARCH_MSM8960 + return read_msm_cpu_type() == MSM_CPU_8960; +#else + return 0; +#endif +} + +static inline int cpu_is_apq8064(void) +{ +#ifdef CONFIG_ARCH_APQ8064 + return read_msm_cpu_type() == MSM_CPU_8064; +#else + return 0; +#endif +} + +static inline int cpu_is_msm8930(void) +{ +#ifdef CONFIG_ARCH_MSM8930 + return (read_msm_cpu_type() == MSM_CPU_8930) || + (read_msm_cpu_type() == MSM_CPU_8627); +#else + return 0; +#endif +} + +static inline int cpu_is_msm8627(void) +{ +/* 8930 and 8627 will share the same CONFIG_ARCH type unless otherwise needed */ +#ifdef CONFIG_ARCH_MSM8930 + return read_msm_cpu_type() == MSM_CPU_8627; +#else + return 0; +#endif +} + +static inline int cpu_is_fsm9xxx(void) +{ +#ifdef CONFIG_ARCH_FSM9XXX + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == FSM_CPU_9XXX; +#else + return 0; +#endif +} + +static inline int cpu_is_msm9615(void) +{ +#ifdef CONFIG_ARCH_MSM9615 + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_9615; +#else + return 0; +#endif +} + +static inline int cpu_is_msm8625(void) +{ +#ifdef CONFIG_ARCH_MSM8625 + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_8625; +#else + return 0; +#endif +} + +#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 0000000000000000000000000000000000000000..319a3ec512e6694198c4ec32d5a492204fb0a60f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sps.h @@ -0,0 +1,1393 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_NWD 0x1000 /* notify when done */ +#define SPS_IOVEC_FLAG_CMD 0x0800 /* command descriptor */ +#define SPS_IOVEC_FLAG_LOCK 0x0400 /* pipe lock */ +#define SPS_IOVEC_FLAG_UNLOCK 0x0200 /* pipe unlock */ +#define SPS_IOVEC_FLAG_IMME 0x0100 /* immediate command descriptor */ +#define SPS_IOVEC_FLAG_NO_SUBMIT 0x0002 /* 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 IRQ is registered for apps wakeup */ +#define SPS_BAM_OPT_IRQ_WAKEUP (1UL << 3) + +/* 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, + /* NWD bit written with EOT for BAM2BAM producer pipe */ + SPS_O_WRITE_NWD = 0x00040000, + + /* 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 enum indicates the cases when callback the user of BAM */ +enum sps_callback_case { + SPS_CALLBACK_BAM_ERROR_IRQ = 1, /* BAM ERROR IRQ */ + SPS_CALLBACK_BAM_HRESP_ERR_IRQ, /* Erroneous HResponse */ +}; + +/* + * This enum indicates the command type in a command element + */ +enum sps_command_type { + SPS_WRITE_COMMAND = 0, + SPS_READ_COMMAND, +}; + +/** + * 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; +}; + +/** + * This data type corresponds to the native Command Element + * supported by SPS hardware + * + * @addr - register address. + * @command - command type. + * @data - for write command: content to be written into peripheral register. + * for read command: dest addr to write peripheral register value to. + * @mask - register mask. + * @reserved - for future usage. + * + */ +struct sps_command_element { + u32 addr:24; + u32 command:8; + u32 data; + u32 mask; + u32 reserved; +}; + +/* + * 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. + * + * @callback - callback function for BAM user. + * @user - pointer to user data. + * + * @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; + + /* Feedback to BAM user */ + void (*callback)(enum sps_callback_case, void *); + void *user; + + /* 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. + * @lock_group - The lock group this pipe belongs to. + * + * @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; + + u32 lock_group; + + /* 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 */ + +#ifdef CONFIG_SPS +/** + * 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); + +/** + * Find the handle of a BAM device based on the physical address + * + * This function finds a BAM device in the BAM registration list that + * matches the specified physical address, and returns its handle. + * + * @phys_addr - physical address of the BAM + * + * @h - device handle of the BAM + * + * @return 0 on success, negative value on error + * + */ +int sps_phy2h(u32 phys_addr, u32 *handle); + +/** + * Setup desc/data FIFO for bam-to-bam connection + * + * @mem_buffer - Pointer to struct for allocated memory properties. + * + * @addr - address of FIFO + * + * @size - FIFO size + * + * @use_offset - use address offset instead of absolute address + * + * @return 0 on success, negative value on error + * + */ +int sps_setup_bam2bam_fifo(struct sps_mem_buffer *mem_buffer, + u32 addr, u32 size, int use_offset); + +/** + * Get the number of unused descriptors in the descriptor FIFO + * of a pipe + * + * @h - client context for SPS connection end point + * + * @desc_num - number of unused descriptors + * + * @return 0 on success, negative value on error + * + */ +int sps_get_unused_desc_num(struct sps_pipe *h, u32 *desc_num); + +#else +static inline int sps_register_bam_device(const struct sps_bam_props + *bam_props, u32 *dev_handle) +{ + return -EPERM; +} + +static inline int sps_deregister_bam_device(u32 dev_handle) +{ + return -EPERM; +} + +static inline struct sps_pipe *sps_alloc_endpoint(void) +{ + return NULL; +} + +static inline int sps_free_endpoint(struct sps_pipe *h) +{ + return -EPERM; +} + +static inline int sps_get_config(struct sps_pipe *h, struct sps_connect *config) +{ + return -EPERM; +} + +static inline int sps_alloc_mem(struct sps_pipe *h, enum sps_mem mem, + struct sps_mem_buffer *mem_buffer) +{ + return -EPERM; +} + +static inline int sps_free_mem(struct sps_pipe *h, + struct sps_mem_buffer *mem_buffer) +{ + return -EPERM; +} + +static inline int sps_connect(struct sps_pipe *h, struct sps_connect *connect) +{ + return -EPERM; +} + +static inline int sps_disconnect(struct sps_pipe *h) +{ + return -EPERM; +} + +static inline int sps_register_event(struct sps_pipe *h, + struct sps_register_event *reg) +{ + return -EPERM; +} + +static inline int sps_transfer_one(struct sps_pipe *h, u32 addr, u32 size, + void *user, u32 flags) +{ + return -EPERM; +} + +static inline int sps_get_event(struct sps_pipe *h, + struct sps_event_notify *event) +{ + return -EPERM; +} + +static inline int sps_get_iovec(struct sps_pipe *h, struct sps_iovec *iovec) +{ + return -EPERM; +} + +static inline int sps_flow_on(struct sps_pipe *h) +{ + return -EPERM; +} + +static inline int sps_flow_off(struct sps_pipe *h, enum sps_flow_off mode) +{ + return -EPERM; +} + +static inline int sps_transfer(struct sps_pipe *h, + struct sps_transfer *transfer) +{ + return -EPERM; +} + +static inline int sps_is_pipe_empty(struct sps_pipe *h, u32 *empty) +{ + return -EPERM; +} + +static inline int sps_device_reset(u32 dev) +{ + return -EPERM; +} + +static inline int sps_set_config(struct sps_pipe *h, struct sps_connect *config) +{ + return -EPERM; +} + +static inline int sps_set_owner(struct sps_pipe *h, enum sps_owner owner, + struct sps_satellite *connect) +{ + return -EPERM; +} + +static inline int sps_get_free_count(struct sps_pipe *h, u32 *count) +{ + return -EPERM; +} + +static inline int sps_alloc_dma_chan(const struct sps_alloc_dma_chan *alloc, + struct sps_dma_chan *chan) +{ + return -EPERM; +} + +static inline int sps_free_dma_chan(struct sps_dma_chan *chan) +{ + return -EPERM; +} + +static inline u32 sps_dma_get_bam_handle(void) +{ + return 0; +} + +static inline void sps_dma_free_bam_handle(u32 h) +{ +} + +static inline int sps_timer_ctrl(struct sps_pipe *h, + struct sps_timer_ctrl *timer_ctrl, + struct sps_timer_result *timer_result) +{ + return -EPERM; +} + +static inline int sps_phy2h(u32 phys_addr, u32 *handle) +{ + return -EPERM; +} + +static inline int sps_setup_bam2bam_fifo(struct sps_mem_buffer *mem_buffer, + u32 addr, u32 size, int use_offset) +{ + return -EPERM; +} + +static inline int sps_get_unused_desc_num(struct sps_pipe *h, u32 *desc_num) +{ + return -EPERM; +} +#endif + +#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 0000000000000000000000000000000000000000..37d4eeca4eb75e0acac9b3b0892c16e597c7cfc1 --- /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 0000000000000000000000000000000000000000..51ace969a55c1a03b5bec97759a5c8f2a7573959 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/subsystem_restart.h @@ -0,0 +1,71 @@ +/* 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_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 f5fb2ec87ffe62010a5c06b0a725777e93f39da5..490c17d525bd7a8628a7888e9d42724c296138ed 100644 --- a/arch/arm/mach-msm/include/mach/system.h +++ b/arch/arm/mach-msm/include/mach/system.h @@ -17,3 +17,6 @@ * 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 a62e6b215aec6b1848980389636c3123c4f38d4d..542aba311264bfda61dc0c395292bf373779d80a 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_HAVE_ARCH_HAS_CURRENT_TIMER +#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 0000000000000000000000000000000000000000..362acbbe53033d0d20a2200106867d7fde2eea10 --- /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 c14011fe832d2fa8f014c0c5a7805204e5949387..dc20df59d7c5ec928cac704c5a6551bc371f85c7 100644 --- a/arch/arm/mach-msm/include/mach/uncompress.h +++ b/arch/arm/mach-msm/include/mach/uncompress.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2011-2012, 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,9 +16,11 @@ #ifndef __ASM_ARCH_MSM_UNCOMPRESS_H #define __ASM_ARCH_MSM_UNCOMPRESS_H -#include +#include +#include #include #include +#include #define UART_CSR (*(volatile uint32_t *)(MSM_DEBUG_UART_PHYS + 0x08)) #define UART_TF (*(volatile uint32_t *)(MSM_DEBUG_UART_PHYS + 0x0c)) @@ -29,28 +31,33 @@ #define UART_DM_NCHAR (*((volatile uint32_t *)(MSM_DEBUG_UART_PHYS + 0x40))) #define UART_DM_TF (*((volatile uint32_t *)(MSM_DEBUG_UART_PHYS + 0x70))) +#ifndef CONFIG_DEBUG_ICEDCC static void putc(int c) { #if defined(MSM_DEBUG_UART_PHYS) + unsigned long base = MSM_DEBUG_UART_PHYS; + #ifdef CONFIG_MSM_HAS_DEBUG_UART_HS /* * Wait for TX_READY to be set; but skip it if we have a * TX underrun. */ - if (UART_DM_SR & 0x08) - while (!(UART_DM_ISR & 0x80)) + if (!(__raw_readl_no_log(base + UARTDM_SR_OFFSET) & 0x08)) + while (!(__raw_readl_no_log(base + UARTDM_ISR_OFFSET) & 0x80)) cpu_relax(); - UART_DM_CR = 0x300; - UART_DM_NCHAR = 0x1; - UART_DM_TF = c; + __raw_writel_no_log(0x300, base + UARTDM_CR_OFFSET); + __raw_writel_no_log(0x1, base + UARTDM_NCF_TX_OFFSET); + __raw_writel_no_log(c, base + UARTDM_TF_OFFSET); #else - while (!(UART_CSR & 0x04)) + /* Wait for TX_READY to be set */ + while (!(__raw_readl_no_log(base + 0x08) & 0x04)) cpu_relax(); - UART_TF = c; + __raw_writel_no_log(c, base + 0x0c); #endif #endif } +#endif static inline void flush(void) { diff --git a/arch/arm/mach-msm/include/mach/usb_bam.h b/arch/arm/mach-msm/include/mach/usb_bam.h new file mode 100644 index 0000000000000000000000000000000000000000..ec135a38ea2eb63cc356f7f4afb2a778940bdc40 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/usb_bam.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_BAM_H_ +#define _USB_BAM_H_ + +/** + * SPS Pipes direction. + * + * USB_TO_PEER_PERIPHERAL USB (as Producer) to other + * peer peripheral. + * PEER_PERIPHERAL_TO_USB Other Peripheral to + * USB (as consumer). + */ +enum usb_bam_pipe_dir { + USB_TO_PEER_PERIPHERAL, + PEER_PERIPHERAL_TO_USB, +}; + +#ifdef CONFIG_USB_BAM +/** + * Connect USB-to-Periperal SPS connection. + * + * This function returns the allocated pipes number. + * + * @idx - Connection index. + * + * @src_pipe_idx - allocated pipe index - USB as a + * source (output) + * + * @dst_pipe_idx - allocated pipe index - USB as a + * destination (output) + * + * @return 0 on success, negative value on error + * + */ +int usb_bam_connect(u8 idx, u8 *src_pipe_idx, u8 *dst_pipe_idx); + +/** + * Register a wakeup callback from peer BAM. + * + * @idx - Connection index. + * + * @callback - the callback function + * + * @return 0 on success, negative value on error + * + */ +int usb_bam_register_wake_cb(u8 idx, + int (*callback)(void *), void* param); +#else +static inline int usb_bam_connect(u8 idx, u8 *src_pipe_idx, u8 *dst_pipe_idx) +{ + return -ENODEV; +} + +static inline int usb_bam_register_wake_cb(u8 idx, + int (*callback)(void *), void* param) +{ + return -ENODEV; +} +#endif +#endif /* _USB_BAM_H_ */ + diff --git a/arch/arm/mach-msm/include/mach/usb_bridge.h b/arch/arm/mach-msm/include/mach/usb_bridge.h new file mode 100644 index 0000000000000000000000000000000000000000..1a1c23b19ede76277c2d5647440e66b751bc1b91 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/usb_bridge.h @@ -0,0 +1,156 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_BRIDGE_H__ +#define __LINUX_USB_BRIDGE_H__ + +#include +#include + +/* bridge device 0: DUN + * bridge device 1 : Tethered RMNET + */ +#define MAX_BRIDGE_DEVICES 2 + +struct bridge_ops { + int (*send_pkt)(void *, void *, size_t actual); + void (*send_cbits)(void *, unsigned int); + + /* flow control */ + void (*unthrottle_tx)(void *); +}; + +#define TX_THROTTLED BIT(0) +#define RX_THROTTLED BIT(1) + +struct bridge { + /* context of the gadget port using bridge driver */ + void *ctx; + + /* bridge device array index mapped to the gadget port array index. + * data bridge[ch_id] <-- bridge --> gadget port[ch_id] + */ + unsigned int ch_id; + + /* flow control bits */ + unsigned long flags; + + /* data/ctrl bridge callbacks */ + struct bridge_ops ops; +}; + +/** + * timestamp_info: stores timestamp info for skb life cycle during data + * transfer for tethered rmnet/DUN. + * @created: stores timestamp at the time of creation of SKB. + * @rx_queued: stores timestamp when SKB queued to HW to receive + * data. + * @rx_done: stores timestamp when skb queued to h/w is completed. + * @rx_done_sent: stores timestamp when SKB is sent from gadget rmnet/DUN + * driver to bridge rmnet/DUN driver or vice versa. + * @tx_queued: stores timestamp when SKB is queued to send data. + * + * note that size of this struct shouldnt exceed 48bytes that's the max skb->cb + * holds. + */ +struct timestamp_info { + struct data_bridge *dev; + + unsigned int created; + unsigned int rx_queued; + unsigned int rx_done; + unsigned int rx_done_sent; + unsigned int tx_queued; +}; + +/* Maximum timestamp message length */ +#define DBG_DATA_MSG 128UL + +/* Maximum timestamp messages */ +#define DBG_DATA_MAX 32UL + +/* timestamp buffer descriptor */ +struct timestamp_buf { + char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ + unsigned idx; /* index */ + rwlock_t lck; /* lock */ +}; + +#if defined(CONFIG_USB_QCOM_MDM_BRIDGE) || \ + defined(CONFIG_USB_QCOM_MDM_BRIDGE_MODULE) + +/* Bridge APIs called by gadget driver */ +int ctrl_bridge_open(struct bridge *); +void ctrl_bridge_close(unsigned int); +int ctrl_bridge_write(unsigned int, char *, size_t); +int ctrl_bridge_set_cbits(unsigned int, unsigned int); +unsigned int ctrl_bridge_get_cbits_tohost(unsigned int); +int data_bridge_open(struct bridge *brdg); +void data_bridge_close(unsigned int); +int data_bridge_write(unsigned int , struct sk_buff *); +int data_bridge_unthrottle_rx(unsigned int); + +/* defined in control bridge */ +int ctrl_bridge_probe(struct usb_interface *, struct usb_host_endpoint *, int); +void ctrl_bridge_disconnect(unsigned int); +int ctrl_bridge_resume(unsigned int); +int ctrl_bridge_suspend(unsigned int); + +#else + +static inline int __maybe_unused ctrl_bridge_open(struct bridge *brdg) +{ + return -ENODEV; +} + +static inline void __maybe_unused ctrl_bridge_close(unsigned int id) { } + +static inline int __maybe_unused ctrl_bridge_write(unsigned int id, + char *data, size_t size) +{ + return -ENODEV; +} + +static inline int __maybe_unused ctrl_bridge_set_cbits(unsigned int id, + unsigned int cbits) +{ + return -ENODEV; +} + +static inline unsigned int __maybe_unused +ctrl_bridge_get_cbits_tohost(unsigned int id) +{ + return -ENODEV; +} + +static inline int __maybe_unused data_bridge_open(struct bridge *brdg) +{ + return -ENODEV; +} + +static inline void __maybe_unused data_bridge_close(unsigned int id) { } + +static inline int __maybe_unused data_bridge_write(unsigned int id, + struct sk_buff *skb) +{ + return -ENODEV; +} + +static inline int __maybe_unused data_bridge_unthrottle_rx(unsigned int id) +{ + return -ENODEV; +} + +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/usb_gadget_xport.h b/arch/arm/mach-msm/include/mach/usb_gadget_xport.h new file mode 100644 index 0000000000000000000000000000000000000000..be119892ccc38379317ef97dc1a16616d4d6e8a4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/usb_gadget_xport.h @@ -0,0 +1,105 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_XPORT_H__ +#define __LINUX_USB_GADGET_XPORT_H__ + +enum transport_type { + USB_GADGET_XPORT_UNDEF, + USB_GADGET_XPORT_TTY, + USB_GADGET_XPORT_SDIO, + USB_GADGET_XPORT_SMD, + USB_GADGET_XPORT_BAM, + USB_GADGET_XPORT_BAM2BAM, + USB_GADGET_XPORT_HSIC, + USB_GADGET_XPORT_HSUART, + USB_GADGET_XPORT_NONE, +}; + +#define XPORT_STR_LEN 10 + +static char *xport_to_str(enum transport_type t) +{ + switch (t) { + case USB_GADGET_XPORT_TTY: + return "TTY"; + case USB_GADGET_XPORT_SDIO: + return "SDIO"; + case USB_GADGET_XPORT_SMD: + return "SMD"; + case USB_GADGET_XPORT_BAM: + return "BAM"; + case USB_GADGET_XPORT_BAM2BAM: + return "BAM2BAM"; + case USB_GADGET_XPORT_HSIC: + return "HSIC"; + case USB_GADGET_XPORT_HSUART: + return "HSUART"; + case USB_GADGET_XPORT_NONE: + return "NONE"; + default: + return "UNDEFINED"; + } +} + +static enum transport_type str_to_xport(const char *name) +{ + if (!strncasecmp("TTY", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_TTY; + if (!strncasecmp("SDIO", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_SDIO; + if (!strncasecmp("SMD", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_SMD; + if (!strncasecmp("BAM", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_BAM; + if (!strncasecmp("BAM2BAM", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_BAM2BAM; + if (!strncasecmp("HSIC", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_HSIC; + if (!strncasecmp("HSUART", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_HSUART; + if (!strncasecmp("", name, XPORT_STR_LEN)) + return USB_GADGET_XPORT_NONE; + + return USB_GADGET_XPORT_UNDEF; +} + +enum gadget_type { + USB_GADGET_SERIAL, + USB_GADGET_RMNET, +}; + +#define NUM_RMNET_HSIC_PORTS 1 +#define NUM_DUN_HSIC_PORTS 1 +#define NUM_PORTS (NUM_RMNET_HSIC_PORTS \ + + NUM_DUN_HSIC_PORTS) + +#define NUM_RMNET_HSUART_PORTS 1 +#define NUM_DUN_HSUART_PORTS 1 +#define NUM_HSUART_PORTS (NUM_RMNET_HSUART_PORTS \ + + NUM_DUN_HSUART_PORTS) + +int ghsic_ctrl_connect(void *, int); +void ghsic_ctrl_disconnect(void *, int); +int ghsic_ctrl_setup(unsigned int, enum gadget_type); +int ghsic_data_connect(void *, int); +void ghsic_data_disconnect(void *, int); +int ghsic_data_setup(unsigned int, enum gadget_type); + +int ghsuart_ctrl_connect(void *, int); +void ghsuart_ctrl_disconnect(void *, int); +int ghsuart_ctrl_setup(unsigned int, enum gadget_type); +int ghsuart_data_connect(void *, int); +void ghsuart_data_disconnect(void *, int); +int ghsuart_data_setup(unsigned int, enum gadget_type); +#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 0000000000000000000000000000000000000000..d1e36056eaf049f9a018442b8de84a93dd44d1db --- /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/io.c b/arch/arm/mach-msm/io.c index a1e7b11688500fb0e08040dbe4ddaf7f95f6eabf..86e06a43ecc8ef73f048a47363e4baddc76fa9a6 100644 --- a/arch/arm/mach-msm/io.c +++ b/arch/arm/mach-msm/io.c @@ -3,7 +3,7 @@ * MSM7K, QSD io support * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -26,10 +26,11 @@ #include #include #include +#include #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, \ @@ -38,25 +39,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 __init 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(DMOV), - MSM_CHIP_DEVICE(GPIO1, MSM7X00), - MSM_CHIP_DEVICE(GPIO2, MSM7X00), - MSM_DEVICE(CLK_CTL), + MSM_CHIP_DEVICE(VIC, MSM7XXX), + MSM_CHIP_DEVICE(CSR, MSM7XXX), + MSM_CHIP_DEVICE(TMR, MSM7XXX), + MSM_CHIP_DEVICE(GPIO1, MSM7XXX), + MSM_CHIP_DEVICE(GPIO2, MSM7XXX), + MSM_CHIP_DEVICE(CLK_CTL, MSM7XXX), + MSM_CHIP_DEVICE(AD5, MSM7XXX), + MSM_CHIP_DEVICE(MDC, MSM7XXX), #if defined(CONFIG_DEBUG_MSM_UART1) || defined(CONFIG_DEBUG_MSM_UART2) || \ defined(CONFIG_DEBUG_MSM_UART3) 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(MSM7XXX_L2CC_PHYS), + .length = MSM7XXX_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, }, @@ -64,34 +88,39 @@ 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(DMOV), - MSM_CHIP_DEVICE(GPIO1, QSD8X50), - MSM_CHIP_DEVICE(GPIO2, QSD8X50), + MSM_DEVICE(CSR), + MSM_DEVICE(TMR), + MSM_DEVICE(GPIO1), + MSM_DEVICE(GPIO2), MSM_DEVICE(CLK_CTL), MSM_DEVICE(SIRC), MSM_DEVICE(SCPLL), MSM_DEVICE(AD5), MSM_DEVICE(MDC), + MSM_DEVICE(TCSR), #if defined(CONFIG_DEBUG_MSM_UART1) || defined(CONFIG_DEBUG_MSM_UART2) || \ defined(CONFIG_DEBUG_MSM_UART3) 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, }, @@ -99,26 +128,48 @@ 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(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_DEBUG_MSM8660_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)); + init_consistent_dma_size(14*SZ_1M); } #endif /* CONFIG_ARCH_MSM8X60 */ @@ -126,63 +177,301 @@ 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(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(IMEM, MSM8960), + MSM_CHIP_DEVICE(HDMI, MSM8960), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, #ifdef CONFIG_DEBUG_MSM8960_UART MSM_DEVICE(DEBUG_UART), #endif + MSM_CHIP_DEVICE(QFPROM, MSM8960), }; 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_MSM8930 +static struct map_desc msm8930_io_desc[] __initdata = { + MSM_CHIP_DEVICE(QGIC_DIST, MSM8930), + MSM_CHIP_DEVICE(QGIC_CPU, MSM8930), + MSM_CHIP_DEVICE(ACC0, MSM8930), + MSM_CHIP_DEVICE(ACC1, MSM8930), + MSM_CHIP_DEVICE(TMR, MSM8930), + MSM_CHIP_DEVICE(TMR0, MSM8930), + MSM_CHIP_DEVICE(RPM_MPM, MSM8930), + MSM_CHIP_DEVICE(CLK_CTL, MSM8930), + MSM_CHIP_DEVICE(MMSS_CLK_CTL, MSM8930), + MSM_CHIP_DEVICE(LPASS_CLK_CTL, MSM8930), + MSM_CHIP_DEVICE(RPM, MSM8930), + MSM_CHIP_DEVICE(TLMM, MSM8930), + MSM_CHIP_DEVICE(HFPLL, MSM8930), + MSM_CHIP_DEVICE(SAW0, MSM8930), + MSM_CHIP_DEVICE(SAW1, MSM8930), + MSM_CHIP_DEVICE(SAW_L2, MSM8930), + MSM_CHIP_DEVICE(SIC_NON_SECURE, MSM8930), + MSM_CHIP_DEVICE(APCS_GCC, MSM8930), + MSM_CHIP_DEVICE(IMEM, MSM8930), + MSM_CHIP_DEVICE(HDMI, MSM8930), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +#ifdef CONFIG_DEBUG_MSM8930_UART + MSM_DEVICE(DEBUG_UART), +#endif + MSM_CHIP_DEVICE(QFPROM, MSM8930), +}; + +void __init msm_map_msm8930_io(void) +{ + msm_map_io(msm8930_io_desc, ARRAY_SIZE(msm8930_io_desc)); +} +#endif /* CONFIG_ARCH_MSM8930 */ + +#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), + MSM_CHIP_DEVICE(TLMM, APQ8064), + MSM_CHIP_DEVICE(ACC0, APQ8064), + MSM_CHIP_DEVICE(ACC1, APQ8064), + MSM_CHIP_DEVICE(ACC2, APQ8064), + MSM_CHIP_DEVICE(ACC3, APQ8064), + MSM_CHIP_DEVICE(HFPLL, APQ8064), + MSM_CHIP_DEVICE(CLK_CTL, APQ8064), + MSM_CHIP_DEVICE(MMSS_CLK_CTL, APQ8064), + MSM_CHIP_DEVICE(LPASS_CLK_CTL, APQ8064), + MSM_CHIP_DEVICE(APCS_GCC, APQ8064), + MSM_CHIP_DEVICE(RPM, APQ8064), + MSM_CHIP_DEVICE(RPM_MPM, APQ8064), + MSM_CHIP_DEVICE(SAW0, APQ8064), + MSM_CHIP_DEVICE(SAW1, APQ8064), + MSM_CHIP_DEVICE(SAW2, APQ8064), + MSM_CHIP_DEVICE(SAW3, APQ8064), + MSM_CHIP_DEVICE(SAW_L2, APQ8064), + MSM_CHIP_DEVICE(IMEM, APQ8064), + MSM_CHIP_DEVICE(HDMI, APQ8064), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, + MSM_CHIP_DEVICE(QFPROM, APQ8064), + MSM_CHIP_DEVICE(SIC_NON_SECURE, APQ8064), +#ifdef CONFIG_DEBUG_APQ8064_UART + MSM_DEVICE(DEBUG_UART), +#endif +}; + +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_MSMCOPPER +static struct map_desc msm_copper_io_desc[] __initdata = { + MSM_CHIP_DEVICE(QGIC_DIST, COPPER), + MSM_CHIP_DEVICE(QGIC_CPU, COPPER), + MSM_CHIP_DEVICE(APCS_GCC, COPPER), + MSM_CHIP_DEVICE(TLMM, COPPER), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +#ifdef CONFIG_DEBUG_MSMCOPPER_UART + MSM_DEVICE(DEBUG_UART), +#endif +}; + +void __init msm_map_copper_io(void) +{ + msm_shared_ram_phys = COPPER_MSM_SHARED_RAM_PHYS; + msm_map_io(msm_copper_io_desc, ARRAY_SIZE(msm_copper_io_desc)); +} +#endif /* CONFIG_ARCH_MSMCOPPER */ + #ifdef CONFIG_ARCH_MSM7X30 static struct map_desc msm7x30_io_desc[] __initdata = { - MSM_DEVICE(VIC), + MSM_CHIP_DEVICE(VIC, MSM7X30), MSM_CHIP_DEVICE(CSR, MSM7X30), - MSM_DEVICE(DMOV), + MSM_CHIP_DEVICE(TMR, MSM7X30), MSM_CHIP_DEVICE(GPIO1, MSM7X30), MSM_CHIP_DEVICE(GPIO2, MSM7X30), + MSM_CHIP_DEVICE(CLK_CTL, MSM7X30), + MSM_CHIP_DEVICE(CLK_CTL_SH2, MSM7X30), + MSM_CHIP_DEVICE(AD5, MSM7X30), + MSM_CHIP_DEVICE(MDC, MSM7X30), + MSM_CHIP_DEVICE(ACC0, MSM7X30), + MSM_CHIP_DEVICE(SAW0, MSM7X30), + MSM_CHIP_DEVICE(APCS_GCC, MSM7X30), + MSM_CHIP_DEVICE(TCSR, MSM7X30), +#if defined(CONFIG_DEBUG_MSM_UART1) || defined(CONFIG_DEBUG_MSM_UART2) || \ + defined(CONFIG_DEBUG_MSM_UART3) + MSM_DEVICE(DEBUG_UART), +#endif + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +}; + +void __init msm_map_msm7x30_io(void) +{ + 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(CLK_CTL_SH2), - MSM_DEVICE(AD5), - MSM_DEVICE(MDC), MSM_DEVICE(ACC), MSM_DEVICE(SAW), MSM_DEVICE(GCC), - MSM_DEVICE(TCSR), + MSM_DEVICE(GRFC), + MSM_DEVICE(QFP_FUSE), + MSM_DEVICE(HH), #if defined(CONFIG_DEBUG_MSM_UART1) || defined(CONFIG_DEBUG_MSM_UART2) || \ defined(CONFIG_DEBUG_MSM_UART3) 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, }, }; -void __init msm_map_msm7x30_io(void) +void __init msm_map_fsm9xxx_io(void) { - iotable_init(msm7x30_io_desc, ARRAY_SIZE(msm7x30_io_desc)); + msm_map_io(fsm9xxx_io_desc, ARRAY_SIZE(fsm9xxx_io_desc)); } -#endif /* CONFIG_ARCH_MSM7X30 */ +#endif /* CONFIG_ARCH_FSM9XXX */ + +#ifdef CONFIG_ARCH_MSM9615 +static struct map_desc msm9615_io_desc[] __initdata = { + MSM_CHIP_DEVICE(QGIC_DIST, MSM9615), + MSM_CHIP_DEVICE(QGIC_CPU, MSM9615), + MSM_CHIP_DEVICE(ACC0, MSM9615), + MSM_CHIP_DEVICE(TMR, MSM9615), + MSM_CHIP_DEVICE(TLMM, MSM9615), + MSM_CHIP_DEVICE(SAW0, MSM9615), + MSM_CHIP_DEVICE(APCS_GCC, MSM9615), + MSM_CHIP_DEVICE(TCSR, MSM9615), + MSM_CHIP_DEVICE(L2CC, MSM9615), + MSM_CHIP_DEVICE(CLK_CTL, MSM9615), + MSM_CHIP_DEVICE(LPASS_CLK_CTL, MSM9615), + MSM_CHIP_DEVICE(RPM, MSM9615), + MSM_CHIP_DEVICE(RPM_MPM, MSM9615), + MSM_CHIP_DEVICE(APCS_GLB, MSM9615), + MSM_CHIP_DEVICE(IMEM, MSM9615), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, + MSM_CHIP_DEVICE(QFPROM, MSM9615), +}; + +void __init msm_map_msm9615_io(void) +{ + msm_map_io(msm9615_io_desc, ARRAY_SIZE(msm9615_io_desc)); +} +#endif /* CONFIG_ARCH_MSM9615 */ + +#ifdef CONFIG_ARCH_MSM8625 +static struct map_desc msm8625_io_desc[] __initdata = { + MSM_CHIP_DEVICE(CSR, MSM7XXX), + MSM_CHIP_DEVICE(GPIO1, MSM7XXX), + MSM_CHIP_DEVICE(GPIO2, MSM7XXX), + MSM_CHIP_DEVICE(QGIC_DIST, MSM8625), + MSM_CHIP_DEVICE(QGIC_CPU, MSM8625), + MSM_CHIP_DEVICE(TMR, MSM8625), + MSM_CHIP_DEVICE(TMR0, MSM8625), + MSM_CHIP_DEVICE(SCU, MSM8625), + MSM_CHIP_DEVICE(CFG_CTL, MSM8625), + MSM_CHIP_DEVICE(CLK_CTL, MSM8625), + MSM_CHIP_DEVICE(SAW0, MSM8625), + MSM_CHIP_DEVICE(SAW1, MSM8625), + MSM_CHIP_DEVICE(AD5, MSM7XXX), + MSM_CHIP_DEVICE(MDC, MSM7XXX), +#if defined(CONFIG_DEBUG_MSM_UART1) || defined(CONFIG_DEBUG_MSM_UART2) || \ + defined(CONFIG_DEBUG_MSM_UART3) + MSM_DEVICE(DEBUG_UART), +#endif +#ifdef CONFIG_CACHE_L2X0 + { + .virtual = (unsigned long) MSM_L2CC_BASE, + .pfn = __phys_to_pfn(MSM7XXX_L2CC_PHYS), + .length = MSM7XXX_L2CC_SIZE, + .type = MT_DEVICE, + }, +#endif + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +}; + +void __init msm_map_msm8625_io(void) +{ + msm_map_io(msm8625_io_desc, ARRAY_SIZE(msm8625_io_desc)); +} +#else +void __init msm_map_msm8625_io(void) { return; } +#endif /* CONFIG_ARCH_MSM8625 */ + +#ifdef CONFIG_ARCH_MSM9625 +static struct map_desc msm9625_io_desc[] __initdata = { + MSM_CHIP_DEVICE(APCS_GCC, MSM9625), + MSM_CHIP_DEVICE(TLMM, MSM9625), + MSM_CHIP_DEVICE(TMR, MSM9625), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +#ifdef CONFIG_DEBUG_MSM9625_UART + MSM_DEVICE(DEBUG_UART), +#endif +}; -void __iomem *__msm_ioremap_caller(unsigned long phys_addr, size_t size, - unsigned int mtype, void *caller) +void __init msm_map_msm9625_io(void) { - if (mtype == MT_DEVICE) { - /* The peripherals in the 88000000 - D0000000 range - * are only accessible by type MT_DEVICE_NONSHARED. - * Adjust mtype as necessary to make this "just work." - */ - if ((phys_addr >= 0x88000000) && (phys_addr < 0xD0000000)) - mtype = MT_DEVICE_NONSHARED; - } - - return __arm_ioremap_caller(phys_addr, size, mtype, caller); + msm_shared_ram_phys = MSM9625_SHARED_RAM_PHYS; + msm_map_io(msm9625_io_desc, ARRAY_SIZE(msm9625_io_desc)); } +#endif /* CONFIG_ARCH_MSM9625 */ diff --git a/arch/arm/mach-msm/iommu_domains.c b/arch/arm/mach-msm/iommu_domains.c new file mode 100644 index 0000000000000000000000000000000000000000..fec27bd067105ec61cd13b39c397336f0b5ecacd --- /dev/null +++ b/arch/arm/mach-msm/iommu_domains.c @@ -0,0 +1,443 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/* dummy 64K for overmapping */ +char iommu_dummy[2*SZ_64K-4]; + +struct msm_iova_data { + struct rb_node node; + struct mem_pool *pools; + int npools; + struct iommu_domain *domain; + int domain_num; +}; + +static struct rb_root domain_root; +DEFINE_MUTEX(domain_mutex); +static atomic_t domain_nums = ATOMIC_INIT(-1); + +int msm_iommu_map_extra(struct iommu_domain *domain, + unsigned long start_iova, + unsigned long size, + unsigned long page_size, + int cached) +{ + int i, ret_value = 0; + unsigned long order = get_order(page_size); + unsigned long aligned_size = ALIGN(size, page_size); + unsigned long nrpages = aligned_size >> (PAGE_SHIFT + order); + unsigned long phy_addr = ALIGN(virt_to_phys(iommu_dummy), page_size); + unsigned long temp_iova = start_iova; + + for (i = 0; i < nrpages; i++) { + int ret = iommu_map(domain, temp_iova, phy_addr, page_size, + cached); + if (ret) { + pr_err("%s: could not map %lx in domain %p, error: %d\n", + __func__, start_iova, domain, ret); + ret_value = -EAGAIN; + goto out; + } + temp_iova += page_size; + } + return ret_value; +out: + for (; i > 0; --i) { + temp_iova -= page_size; + iommu_unmap(domain, start_iova, page_size); + } + return ret_value; +} + +void msm_iommu_unmap_extra(struct iommu_domain *domain, + unsigned long start_iova, + unsigned long size, + unsigned long page_size) +{ + int i; + unsigned long order = get_order(page_size); + unsigned long aligned_size = ALIGN(size, page_size); + unsigned long nrpages = aligned_size >> (PAGE_SHIFT + order); + unsigned long temp_iova = start_iova; + + for (i = 0; i < nrpages; ++i) { + iommu_unmap(domain, temp_iova, page_size); + temp_iova += page_size; + } +} + +static int msm_iommu_map_iova_phys(struct iommu_domain *domain, + unsigned long iova, + unsigned long phys, + unsigned long size, + int cached) +{ + int ret; + struct scatterlist *sglist; + + sglist = vmalloc(sizeof(*sglist)); + if (!sglist) { + ret = -ENOMEM; + goto err1; + } + + sg_init_table(sglist, 1); + sglist->length = size; + sglist->offset = 0; + sglist->dma_address = phys; + + ret = iommu_map_range(domain, iova, sglist, size, cached); + if (ret) { + pr_err("%s: could not map extra %lx in domain %p\n", + __func__, iova, domain); + } + + vfree(sglist); +err1: + return ret; + +} + +int msm_iommu_map_contig_buffer(unsigned long phys, + unsigned int domain_no, + unsigned int partition_no, + unsigned long size, + unsigned long align, + unsigned long cached, + unsigned long *iova_val) +{ + unsigned long iova; + int ret; + + if (size & (align - 1)) + return -EINVAL; + + ret = msm_allocate_iova_address(domain_no, partition_no, size, align, + &iova); + + if (ret) + return -ENOMEM; + + ret = msm_iommu_map_iova_phys(msm_get_iommu_domain(domain_no), iova, + phys, size, cached); + + if (ret) + msm_free_iova_address(iova, domain_no, partition_no, size); + else + *iova_val = iova; + + return ret; +} + +void msm_iommu_unmap_contig_buffer(unsigned long iova, + unsigned int domain_no, + unsigned int partition_no, + unsigned long size) +{ + iommu_unmap_range(msm_get_iommu_domain(domain_no), iova, size); + msm_free_iova_address(iova, domain_no, partition_no, size); +} + +static struct msm_iova_data *find_domain(int domain_num) +{ + struct rb_root *root = &domain_root; + struct rb_node *p = root->rb_node; + + mutex_lock(&domain_mutex); + + while (p) { + struct msm_iova_data *node; + + node = rb_entry(p, struct msm_iova_data, node); + if (domain_num < node->domain_num) + p = p->rb_left; + else if (domain_num > domain_num) + p = p->rb_right; + else { + mutex_unlock(&domain_mutex); + return node; + } + } + mutex_unlock(&domain_mutex); + return NULL; +} + +static int add_domain(struct msm_iova_data *node) +{ + struct rb_root *root = &domain_root; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + + mutex_lock(&domain_mutex); + while (*p) { + struct msm_iova_data *tmp; + parent = *p; + + tmp = rb_entry(parent, struct msm_iova_data, node); + + if (node->domain_num < tmp->domain_num) + p = &(*p)->rb_left; + else if (node->domain_num > tmp->domain_num) + p = &(*p)->rb_right; + else + BUG(); + } + rb_link_node(&node->node, parent, p); + rb_insert_color(&node->node, root); + mutex_unlock(&domain_mutex); + return 0; +} + +struct iommu_domain *msm_get_iommu_domain(int domain_num) +{ + struct msm_iova_data *data; + + data = find_domain(domain_num); + + if (data) + return data->domain; + else + return NULL; +} + +int msm_allocate_iova_address(unsigned int iommu_domain, + unsigned int partition_no, + unsigned long size, + unsigned long align, + unsigned long *iova) +{ + struct msm_iova_data *data; + struct mem_pool *pool; + unsigned long va; + + data = find_domain(iommu_domain); + + if (!data) + return -EINVAL; + + if (partition_no >= data->npools) + return -EINVAL; + + pool = &data->pools[partition_no]; + + if (!pool->gpool) + return -EINVAL; + + va = gen_pool_alloc_aligned(pool->gpool, size, ilog2(align)); + if (va) { + pool->free -= size; + /* Offset because genpool can't handle 0 addresses */ + if (pool->paddr == 0) + va -= SZ_4K; + *iova = va; + return 0; + } + + return -ENOMEM; +} + +void msm_free_iova_address(unsigned long iova, + unsigned int iommu_domain, + unsigned int partition_no, + unsigned long size) +{ + struct msm_iova_data *data; + struct mem_pool *pool; + + data = find_domain(iommu_domain); + + if (!data) { + WARN(1, "Invalid domain %d\n", iommu_domain); + return; + } + + if (partition_no >= data->npools) { + WARN(1, "Invalid partition %d for domain %d\n", + partition_no, iommu_domain); + return; + } + + pool = &data->pools[partition_no]; + + if (!pool) + return; + + pool->free += size; + + /* Offset because genpool can't handle 0 addresses */ + if (pool->paddr == 0) + iova += SZ_4K; + + gen_pool_free(pool->gpool, iova, size); +} + +int msm_register_domain(struct msm_iova_layout *layout) +{ + int i; + struct msm_iova_data *data; + struct mem_pool *pools; + + if (!layout) + return -EINVAL; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + + if (!data) + return -ENOMEM; + + pools = kmalloc(sizeof(struct mem_pool) * layout->npartitions, + GFP_KERNEL); + + if (!pools) + goto out; + + for (i = 0; i < layout->npartitions; i++) { + if (layout->partitions[i].size == 0) + continue; + + pools[i].gpool = gen_pool_create(PAGE_SHIFT, -1); + + if (!pools[i].gpool) + continue; + + pools[i].paddr = layout->partitions[i].start; + pools[i].size = layout->partitions[i].size; + + /* + * genalloc can't handle a pool starting at address 0. + * For now, solve this problem by offsetting the value + * put in by 4k. + * gen pool address = actual address + 4k + */ + if (pools[i].paddr == 0) + layout->partitions[i].start += SZ_4K; + + if (gen_pool_add(pools[i].gpool, + layout->partitions[i].start, + layout->partitions[i].size, -1)) { + gen_pool_destroy(pools[i].gpool); + pools[i].gpool = NULL; + continue; + } + } + + data->pools = pools; + data->npools = layout->npartitions; + data->domain_num = atomic_inc_return(&domain_nums); + data->domain = iommu_domain_alloc(&platform_bus_type, + layout->domain_flags); + + add_domain(data); + + return data->domain_num; + +out: + kfree(data); + + return -EINVAL; +} + +int msm_use_iommu() +{ + return iommu_present(&platform_bus_type); +} + +static int __init iommu_domain_probe(struct platform_device *pdev) +{ + struct iommu_domains_pdata *p = pdev->dev.platform_data; + int i, j; + + if (!p) + return -ENODEV; + + for (i = 0; i < p->ndomains; i++) { + struct msm_iova_layout l; + struct msm_iova_partition *part; + struct msm_iommu_domain *domains; + + domains = p->domains; + l.npartitions = domains[i].npools; + part = kmalloc( + sizeof(struct msm_iova_partition) * l.npartitions, + GFP_KERNEL); + + if (!part) { + pr_info("%s: could not allocate space for domain %d", + __func__, i); + continue; + } + + for (j = 0; j < l.npartitions; j++) { + part[j].start = p->domains[i].iova_pools[j].paddr; + part[j].size = p->domains[i].iova_pools[j].size; + } + + l.partitions = part; + + msm_register_domain(&l); + + kfree(part); + } + + for (i = 0; i < p->nnames; i++) { + struct device *ctx = msm_iommu_get_ctx( + p->domain_names[i].name); + struct iommu_domain *domain; + + if (!ctx) + continue; + + domain = msm_get_iommu_domain(p->domain_names[i].domain); + + if (!domain) + continue; + + if (iommu_attach_device(domain, ctx)) { + WARN(1, "%s: could not attach domain %p to context %s." + " iommu programming will not occur.\n", + __func__, domain, + p->domain_names[i].name); + continue; + } + } + + return 0; +} + +static struct platform_driver iommu_domain_driver = { + .driver = { + .name = "iommu_domains", + .owner = THIS_MODULE + }, +}; + +static int __init msm_subsystem_iommu_init(void) +{ + return platform_driver_probe(&iommu_domain_driver, iommu_domain_probe); +} +device_initcall(msm_subsystem_iommu_init); diff --git a/arch/arm/mach-msm/ipc_logging.c b/arch/arm/mach-msm/ipc_logging.c new file mode 100644 index 0000000000000000000000000000000000000000..2cd30ded331fddb048af467b7cdf53e862dcb876 --- /dev/null +++ b/arch/arm/mach-msm/ipc_logging.c @@ -0,0 +1,556 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "ipc_logging.h" + +static LIST_HEAD(ipc_log_context_list); +DEFINE_SPINLOCK(ipc_log_context_list_lock); +static atomic_t next_log_id = ATOMIC_INIT(0); + +static struct ipc_log_page *get_first_page(struct ipc_log_context *ilctxt) +{ + struct ipc_log_page_header *p_pghdr; + struct ipc_log_page *pg = NULL; + + if (!ilctxt) + return NULL; + p_pghdr = list_first_entry(&ilctxt->page_list, + struct ipc_log_page_header, list); + pg = container_of(p_pghdr, struct ipc_log_page, hdr); + return pg; +} + +static struct ipc_log_page *get_next_page(struct ipc_log_context *ilctxt, + struct ipc_log_page *cur_pg) +{ + struct ipc_log_page_header *p_pghdr; + struct ipc_log_page *pg = NULL; + + if (!ilctxt || !cur_pg) + return NULL; + + if (ilctxt->last_page == cur_pg) + return ilctxt->first_page; + + p_pghdr = list_first_entry(&cur_pg->hdr.list, + struct ipc_log_page_header, list); + pg = container_of(p_pghdr, struct ipc_log_page, hdr); + + return pg; +} + +/* If data == NULL, drop the log of size data_size*/ +static void ipc_log_read(struct ipc_log_context *ilctxt, + void *data, int data_size) +{ + int bytes_to_read; + + bytes_to_read = MIN(((PAGE_SIZE - sizeof(struct ipc_log_page_header)) + - ilctxt->read_page->hdr.read_offset), + data_size); + if (data) + memcpy(data, (ilctxt->read_page->data + + ilctxt->read_page->hdr.read_offset), bytes_to_read); + if (bytes_to_read != data_size) { + ilctxt->read_page->hdr.read_offset = 0xFFFF; + ilctxt->read_page = get_next_page(ilctxt, ilctxt->read_page); + ilctxt->read_page->hdr.read_offset = 0; + if (data) + memcpy((data + bytes_to_read), + (ilctxt->read_page->data + + ilctxt->read_page->hdr.read_offset), + (data_size - bytes_to_read)); + bytes_to_read = (data_size - bytes_to_read); + } + ilctxt->read_page->hdr.read_offset += bytes_to_read; + ilctxt->write_avail += data_size; +} + +/* + * Reads a message. + * + * If a message is read successfully, then the the message context + * will be set to: + * .hdr message header .size and .type values + * .offset beginning of message data + * + * @ectxt Message context and if NULL, drops the message. + * + * @returns 0 - no message available + * 1 - message read + */ +int msg_read(struct ipc_log_context *ilctxt, + struct encode_context *ectxt) +{ + struct tsv_header hdr; + + ipc_log_read(ilctxt, &hdr, sizeof(hdr)); + if (ectxt) { + ectxt->hdr.type = hdr.type; + ectxt->hdr.size = hdr.size; + ectxt->offset = sizeof(hdr); + ipc_log_read(ilctxt, (ectxt->buff + ectxt->offset), + (int)hdr.size); + } else { + ipc_log_read(ilctxt, NULL, (int)hdr.size); + } + return sizeof(hdr) + (int)hdr.size; +} + +/* + * Commits messages to the FIFO. If the FIFO is full, then enough + * messages are dropped to create space for the new message. + */ +void ipc_log_write(void *ctxt, struct encode_context *ectxt) +{ + struct ipc_log_context *ilctxt = (struct ipc_log_context *)ctxt; + int bytes_to_write; + unsigned long flags; + + if (!ilctxt || !ectxt) { + pr_err("%s: Invalid ipc_log or encode context\n", __func__); + return; + } + + spin_lock_irqsave(&ipc_log_context_list_lock, flags); + spin_lock(&ilctxt->ipc_log_context_lock); + while (ilctxt->write_avail < ectxt->offset) + msg_read(ilctxt, NULL); + + bytes_to_write = MIN(((PAGE_SIZE - sizeof(struct ipc_log_page_header)) + - ilctxt->write_page->hdr.write_offset), + ectxt->offset); + memcpy((ilctxt->write_page->data + + ilctxt->write_page->hdr.write_offset), + ectxt->buff, bytes_to_write); + if (bytes_to_write != ectxt->offset) { + ilctxt->write_page->hdr.write_offset = 0xFFFF; + ilctxt->write_page = get_next_page(ilctxt, ilctxt->write_page); + ilctxt->write_page->hdr.write_offset = 0; + memcpy((ilctxt->write_page->data + + ilctxt->write_page->hdr.write_offset), + (ectxt->buff + bytes_to_write), + (ectxt->offset - bytes_to_write)); + bytes_to_write = (ectxt->offset - bytes_to_write); + } + ilctxt->write_page->hdr.write_offset += bytes_to_write; + ilctxt->write_avail -= ectxt->offset; + complete(&ilctxt->read_avail); + spin_unlock(&ilctxt->ipc_log_context_lock); + spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); +} +EXPORT_SYMBOL(ipc_log_write); + +/* + * Starts a new message after which you can add serialized data and + * then complete the message by calling msg_encode_end(). + */ +void msg_encode_start(struct encode_context *ectxt, uint32_t type) +{ + if (!ectxt) { + pr_err("%s: Invalid encode context\n", __func__); + return; + } + + ectxt->hdr.type = type; + ectxt->hdr.size = 0; + ectxt->offset = sizeof(ectxt->hdr); +} +EXPORT_SYMBOL(msg_encode_start); + +/* + * Completes the message + */ +void msg_encode_end(struct encode_context *ectxt) +{ + if (!ectxt) { + pr_err("%s: Invalid encode context\n", __func__); + return; + } + + /* finalize data size */ + ectxt->hdr.size = ectxt->offset - sizeof(ectxt->hdr); + BUG_ON(ectxt->hdr.size > MAX_MSG_SIZE); + memcpy(ectxt->buff, &ectxt->hdr, sizeof(ectxt->hdr)); +} +EXPORT_SYMBOL(msg_encode_end); + +/* + * Helper funtion used to write data to a message context. + * + * @ectxt context initialized by calling msg_encode_start() + * @data data to write + * @size number of bytes of data to write + */ +static inline int tsv_write_data(struct encode_context *ectxt, + void *data, uint32_t size) +{ + if (!ectxt) { + pr_err("%s: Invalid encode context\n", __func__); + return -EINVAL; + } + if ((ectxt->offset + size) > MAX_MSG_SIZE) { + pr_err("%s: No space to encode further\n", __func__); + return -EINVAL; + } + + memcpy((void *)(ectxt->buff + ectxt->offset), data, size); + ectxt->offset += size; + return 0; +} + +/* + * Helper function that writes a type to the context. + * + * @ectxt context initialized by calling msg_encode_start() + * @type primitive type + * @size size of primitive in bytes + */ +static inline int tsv_write_header(struct encode_context *ectxt, + uint32_t type, uint32_t size) +{ + struct tsv_header hdr; + + hdr.type = (unsigned char)type; + hdr.size = (unsigned char)size; + return tsv_write_data(ectxt, &hdr, sizeof(hdr)); +} + +/* + * Writes the current timestamp count. + * + * @ectxt context initialized by calling msg_encode_start() + */ +int tsv_timestamp_write(struct encode_context *ectxt) +{ + int ret; + unsigned long long t_now = sched_clock(); + + ret = tsv_write_header(ectxt, TSV_TYPE_TIMESTAMP, sizeof(t_now)); + if (ret) + return ret; + return tsv_write_data(ectxt, &t_now, sizeof(t_now)); +} +EXPORT_SYMBOL(tsv_timestamp_write); + +/* + * Writes a data pointer. + * + * @ectxt context initialized by calling msg_encode_start() + * @pointer pointer value to write + */ +int tsv_pointer_write(struct encode_context *ectxt, void *pointer) +{ + int ret; + ret = tsv_write_header(ectxt, TSV_TYPE_POINTER, sizeof(pointer)); + if (ret) + return ret; + return tsv_write_data(ectxt, &pointer, sizeof(pointer)); +} +EXPORT_SYMBOL(tsv_pointer_write); + +/* + * Writes a 32-bit integer value. + * + * @ectxt context initialized by calling msg_encode_start() + * @n integer to write + */ +int tsv_int32_write(struct encode_context *ectxt, int32_t n) +{ + int ret; + ret = tsv_write_header(ectxt, TSV_TYPE_INT32, sizeof(n)); + if (ret) + return ret; + return tsv_write_data(ectxt, &n, sizeof(n)); +} +EXPORT_SYMBOL(tsv_int32_write); + +/* + * Writes a byte array. + * + * @ectxt context initialized by calling msg_write_start() + * @data Beginning address of data + * @data_size Size of data to be written + */ +int tsv_byte_array_write(struct encode_context *ectxt, + void *data, int data_size) +{ + int ret; + ret = tsv_write_header(ectxt, TSV_TYPE_BYTE_ARRAY, data_size); + if (ret) + return ret; + return tsv_write_data(ectxt, data, data_size); +} +EXPORT_SYMBOL(tsv_byte_array_write); + +/* + * Helper function to log a string + * + * @ilctxt ipc_log_context created using ipc_log_context_create() + * @fmt Data specified using format specifiers + */ +int ipc_log_string(void *ilctxt, const char *fmt, ...) +{ + struct encode_context ectxt; + int avail_size, data_size, hdr_size = sizeof(struct tsv_header); + va_list arg_list; + + if (!ilctxt) + return -EINVAL; + + msg_encode_start(&ectxt, TSV_TYPE_STRING); + tsv_timestamp_write(&ectxt); + avail_size = (MAX_MSG_SIZE - (ectxt.offset + hdr_size)); + va_start(arg_list, fmt); + data_size = vsnprintf((ectxt.buff + ectxt.offset + hdr_size), + avail_size, fmt, arg_list); + va_end(arg_list); + tsv_write_header(&ectxt, TSV_TYPE_BYTE_ARRAY, data_size); + ectxt.offset += data_size; + msg_encode_end(&ectxt); + ipc_log_write(ilctxt, &ectxt); + return 0; +} + +/* + * Helper funtion used to read data from a message context. + * + * @ectxt context initialized by calling msg_read() + * @data data to read + * @size number of bytes of data to read + */ +static void tsv_read_data(struct encode_context *ectxt, + void *data, uint32_t size) +{ + BUG_ON((ectxt->offset + size) > MAX_MSG_SIZE); + memcpy(data, (ectxt->buff + ectxt->offset), size); + ectxt->offset += size; +} + +/* + * Helper function that reads a type from the context and updates the + * context pointers. + * + * @ectxt context initialized by calling msg_read() + * @hdr type header + */ +static void tsv_read_header(struct encode_context *ectxt, + struct tsv_header *hdr) +{ + BUG_ON((ectxt->offset + sizeof(*hdr)) > MAX_MSG_SIZE); + memcpy(hdr, (ectxt->buff + ectxt->offset), sizeof(*hdr)); + ectxt->offset += sizeof(*hdr); +} + +/* + * Reads a timestamp. + * + * @ectxt context initialized by calling msg_read() + * @dctxt deserialization context + * @format output format (appended to %6u.%09u timestamp format) + */ +void tsv_timestamp_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) +{ + struct tsv_header hdr; + unsigned long long val; + unsigned long nanosec_rem; + + tsv_read_header(ectxt, &hdr); + BUG_ON(hdr.type != TSV_TYPE_TIMESTAMP); + tsv_read_data(ectxt, &val, sizeof(val)); + nanosec_rem = do_div(val, 1000000000U); + IPC_SPRINTF_DECODE(dctxt, "[%6u.%09lu]%s", + (unsigned)val, nanosec_rem, format); +} +EXPORT_SYMBOL(tsv_timestamp_read); + +/* + * Reads a data pointer. + * + * @ectxt context initialized by calling msg_read() + * @dctxt deserialization context + * @format output format + */ +void tsv_pointer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) +{ + struct tsv_header hdr; + void *val; + + tsv_read_header(ectxt, &hdr); + BUG_ON(hdr.type != TSV_TYPE_POINTER); + tsv_read_data(ectxt, &val, sizeof(val)); + + IPC_SPRINTF_DECODE(dctxt, format, val); +} +EXPORT_SYMBOL(tsv_pointer_read); + +/* + * Reads a 32-bit integer value. + * + * @ectxt context initialized by calling msg_read() + * @dctxt deserialization context + * @format output format + */ +int32_t tsv_int32_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) +{ + struct tsv_header hdr; + int32_t val; + + tsv_read_header(ectxt, &hdr); + BUG_ON(hdr.type != TSV_TYPE_INT32); + tsv_read_data(ectxt, &val, sizeof(val)); + + IPC_SPRINTF_DECODE(dctxt, format, val); + return val; +} +EXPORT_SYMBOL(tsv_int32_read); + +/* + * Reads a byte array/string. + * + * @ectxt context initialized by calling msg_read() + * @dctxt deserialization context + * @format output format + */ +void tsv_byte_array_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) +{ + struct tsv_header hdr; + + tsv_read_header(ectxt, &hdr); + BUG_ON(hdr.type != TSV_TYPE_BYTE_ARRAY); + tsv_read_data(ectxt, dctxt->buff, hdr.size); + dctxt->buff += hdr.size; + dctxt->size -= hdr.size; +} +EXPORT_SYMBOL(tsv_byte_array_read); + +int add_deserialization_func(void *ctxt, int type, + void (*dfunc)(struct encode_context *, + struct decode_context *)) +{ + struct ipc_log_context *ilctxt = (struct ipc_log_context *)ctxt; + struct dfunc_info *df_info; + unsigned long flags; + + if (!ilctxt || !dfunc) + return -EINVAL; + + df_info = kmalloc(sizeof(struct dfunc_info), GFP_KERNEL); + if (!df_info) + return -ENOSPC; + + spin_lock_irqsave(&ipc_log_context_list_lock, flags); + spin_lock(&ilctxt->ipc_log_context_lock); + df_info->type = type; + df_info->dfunc = dfunc; + list_add_tail(&df_info->list, &ilctxt->dfunc_info_list); + spin_unlock(&ilctxt->ipc_log_context_lock); + spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); + return 0; +} +EXPORT_SYMBOL(add_deserialization_func); + +void *ipc_log_context_create(int max_num_pages, + const char *mod_name) +{ + struct ipc_log_context *ctxt; + struct ipc_log_page *pg = NULL; + int page_cnt, local_log_id; + unsigned long flags; + + ctxt = kzalloc(sizeof(struct ipc_log_context), GFP_KERNEL); + if (!ctxt) { + pr_err("%s: cannot create ipc_log_context\n", __func__); + return 0; + } + + local_log_id = atomic_add_return(1, &next_log_id); + init_completion(&ctxt->read_avail); + INIT_LIST_HEAD(&ctxt->page_list); + INIT_LIST_HEAD(&ctxt->dfunc_info_list); + spin_lock_init(&ctxt->ipc_log_context_lock); + for (page_cnt = 0; page_cnt < max_num_pages; page_cnt++) { + pg = kzalloc(sizeof(struct ipc_log_page), GFP_KERNEL); + if (!pg) { + pr_err("%s: cannot create ipc_log_page\n", __func__); + goto release_ipc_log_context; + } + pg->hdr.magic = IPC_LOGGING_MAGIC_NUM; + pg->hdr.nmagic = ~(IPC_LOGGING_MAGIC_NUM); + pg->hdr.log_id = (uint32_t)local_log_id; + pg->hdr.page_num = page_cnt; + pg->hdr.read_offset = 0xFFFF; + pg->hdr.write_offset = 0xFFFF; + spin_lock_irqsave(&ctxt->ipc_log_context_lock, flags); + list_add_tail(&pg->hdr.list, &ctxt->page_list); + spin_unlock_irqrestore(&ctxt->ipc_log_context_lock, flags); + } + ctxt->first_page = get_first_page(ctxt); + ctxt->last_page = pg; + ctxt->write_page = ctxt->first_page; + ctxt->read_page = ctxt->first_page; + ctxt->write_page->hdr.write_offset = 0; + ctxt->read_page->hdr.read_offset = 0; + ctxt->write_avail = max_num_pages * (PAGE_SIZE - + sizeof(struct ipc_log_page_header)); + + create_ctx_debugfs(ctxt, mod_name); + + spin_lock_irqsave(&ipc_log_context_list_lock, flags); + list_add_tail(&ctxt->list, &ipc_log_context_list); + spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); + return (void *)ctxt; + +release_ipc_log_context: + while (page_cnt-- > 0) { + pg = get_first_page(ctxt); + list_del(&pg->hdr.list); + kfree(pg); + } + kfree(ctxt); + return 0; +} +EXPORT_SYMBOL(ipc_log_context_create); + +static int __init ipc_logging_init(void) +{ + check_and_create_debugfs(); + return 0; +} + +module_init(ipc_logging_init); + +MODULE_DESCRIPTION("ipc logging"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/ipc_logging.h b/arch/arm/mach-msm/ipc_logging.h new file mode 100644 index 0000000000000000000000000000000000000000..5e614abc1849e638d845693ebe610330e4b249ea --- /dev/null +++ b/arch/arm/mach-msm/ipc_logging.h @@ -0,0 +1,98 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _IPC_LOGGING_H +#define _IPC_LOGGING_H + +struct ipc_log_page_header { + uint32_t magic; + uint32_t nmagic; /* inverse of magic number */ + uint32_t log_id; /* owner of log */ + uint32_t page_num; + uint16_t read_offset; + uint16_t write_offset; + struct list_head list; +}; + +struct ipc_log_page { + struct ipc_log_page_header hdr; + char data[PAGE_SIZE - sizeof(struct ipc_log_page_header)]; +}; + +struct ipc_log_context { + struct list_head list; + struct list_head page_list; + struct ipc_log_page *first_page; + struct ipc_log_page *last_page; + struct ipc_log_page *write_page; + struct ipc_log_page *read_page; + uint32_t write_avail; + struct dentry *dent; + struct list_head dfunc_info_list; + spinlock_t ipc_log_context_lock; + struct completion read_avail; +}; + +struct dfunc_info { + struct list_head list; + int type; + void (*dfunc) (struct encode_context *, struct decode_context *); +}; + +enum { + TSV_TYPE_INVALID, + TSV_TYPE_TIMESTAMP, + TSV_TYPE_POINTER, + TSV_TYPE_INT32, + TSV_TYPE_BYTE_ARRAY, +}; + +enum { + OUTPUT_DEBUGFS, +}; + +#define IPC_LOGGING_MAGIC_NUM 0x52784425 +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define IS_MSG_TYPE(x) (((x) > TSV_TYPE_MSG_START) && \ + ((x) < TSV_TYPE_MSG_END)) + +extern spinlock_t ipc_log_context_list_lock; + +extern int msg_read(struct ipc_log_context *ilctxt, + struct encode_context *ectxt); + +static inline int is_ilctxt_empty(struct ipc_log_context *ilctxt) +{ + if (!ilctxt) + return -EINVAL; + + return ((ilctxt->read_page == ilctxt->write_page) && + (ilctxt->read_page->hdr.read_offset == + ilctxt->write_page->hdr.write_offset)); +} + +#if (defined(CONFIG_DEBUG_FS)) +void check_and_create_debugfs(void); + +void create_ctx_debugfs(struct ipc_log_context *ctxt, + const char *mod_name); +#else +void check_and_create_debugfs(void) +{ +} + +void create_ctx_debugfs(struct ipc_log_context *ctxt, const char *mod_name) +{ +} +#endif + +#endif diff --git a/arch/arm/mach-msm/ipc_logging_debug.c b/arch/arm/mach-msm/ipc_logging_debug.c new file mode 100644 index 0000000000000000000000000000000000000000..5f7a95e000aafa21f60803dcf9997817e1eda37c --- /dev/null +++ b/arch/arm/mach-msm/ipc_logging_debug.c @@ -0,0 +1,225 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "ipc_logging.h" + +static DEFINE_MUTEX(ipc_log_debugfs_init_lock); +static struct dentry *root_dent; +#define MAX_MSG_DECODED_SIZE (MAX_MSG_SIZE*4) + +static void *get_deserialization_func(struct ipc_log_context *ilctxt, + int type) +{ + struct dfunc_info *df_info = NULL; + + if (!ilctxt) + return NULL; + + list_for_each_entry(df_info, &ilctxt->dfunc_info_list, list) { + if (df_info->type == type) + return df_info->dfunc; + } + return NULL; +} + +static int deserialize_log(struct ipc_log_context *ilctxt, + char *buff, int size) +{ + struct encode_context ectxt; + struct decode_context dctxt; + void (*deserialize_func)(struct encode_context *ectxt, + struct decode_context *dctxt); + unsigned long flags; + + dctxt.output_format = OUTPUT_DEBUGFS; + dctxt.buff = buff; + dctxt.size = size; + spin_lock_irqsave(&ipc_log_context_list_lock, flags); + spin_lock(&ilctxt->ipc_log_context_lock); + while (dctxt.size >= MAX_MSG_DECODED_SIZE && + !is_ilctxt_empty(ilctxt)) { + msg_read(ilctxt, &ectxt); + deserialize_func = get_deserialization_func(ilctxt, + ectxt.hdr.type); + spin_unlock(&ilctxt->ipc_log_context_lock); + spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); + if (deserialize_func) + deserialize_func(&ectxt, &dctxt); + else + pr_err("%s: unknown message 0x%x\n", + __func__, ectxt.hdr.type); + spin_lock_irqsave(&ipc_log_context_list_lock, flags); + spin_lock(&ilctxt->ipc_log_context_lock); + } + if ((size - dctxt.size) == 0) + init_completion(&ilctxt->read_avail); + spin_unlock(&ilctxt->ipc_log_context_lock); + spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); + return size - dctxt.size; +} + +static int debug_log(struct ipc_log_context *ilctxt, + char *buff, int size, int cont) +{ + int i = 0; + + if (size < MAX_MSG_DECODED_SIZE) { + pr_err("%s: buffer size %d < %d\n", __func__, size, + MAX_MSG_DECODED_SIZE); + return -ENOMEM; + } + do { + i = deserialize_log(ilctxt, buff, size - 1); + if (cont && i == 0) { + wait_for_completion_interruptible(&ilctxt->read_avail); + if (signal_pending(current)) + break; + } + } while (cont && i == 0); + + return i; +} + +/* + * VFS Read operation helper which dispatches the call to the debugfs + * read command stored in file->private_data. + * + * @file File structure + * @buff user buffer + * @count size of user buffer + * @ppos file position to read from (only a value of 0 is accepted) + * @cont 1 = continuous mode (don't return 0 to signal end-of-file) + * + * @returns ==0 end of file + * >0 number of bytes read + * <0 error + */ +static ssize_t debug_read_helper(struct file *file, char __user *buff, + size_t count, loff_t *ppos, int cont) +{ + struct ipc_log_context *ilctxt = file->private_data; + char *buffer; + int bsize; + + buffer = kmalloc(count, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + bsize = debug_log(ilctxt, buffer, count, cont); + if (bsize > 0) { + if (copy_to_user(buff, buffer, bsize)) { + kfree(buffer); + return -EFAULT; + } + *ppos += bsize; + } + kfree(buffer); + return bsize; +} + +static ssize_t debug_read(struct file *file, char __user *buff, + size_t count, loff_t *ppos) +{ + return debug_read_helper(file, buff, count, ppos, 0); +} + +static ssize_t debug_read_cont(struct file *file, char __user *buff, + size_t count, loff_t *ppos) +{ + return debug_read_helper(file, buff, count, ppos, 1); +} + +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, + struct ipc_log_context *ilctxt, + const struct file_operations *fops) +{ + debugfs_create_file(name, mode, dent, ilctxt, fops); +} + +static void dfunc_string(struct encode_context *ectxt, + struct decode_context *dctxt) +{ + tsv_timestamp_read(ectxt, dctxt, " "); + tsv_byte_array_read(ectxt, dctxt, ""); +} + +void check_and_create_debugfs(void) +{ + mutex_lock(&ipc_log_debugfs_init_lock); + if (!root_dent) { + root_dent = debugfs_create_dir("ipc_logging", 0); + + if (IS_ERR(root_dent)) { + pr_err("%s: unable to create debugfs %ld\n", + __func__, IS_ERR(root_dent)); + root_dent = NULL; + } + } + mutex_unlock(&ipc_log_debugfs_init_lock); +} +EXPORT_SYMBOL(check_and_create_debugfs); + +void create_ctx_debugfs(struct ipc_log_context *ctxt, + const char *mod_name) +{ + if (!root_dent) + check_and_create_debugfs(); + + if (root_dent) { + ctxt->dent = debugfs_create_dir(mod_name, root_dent); + if (!IS_ERR(ctxt->dent)) { + debug_create("log", 0444, ctxt->dent, + ctxt, &debug_ops); + debug_create("log_cont", 0444, ctxt->dent, + ctxt, &debug_ops_cont); + } + } + add_deserialization_func((void *)ctxt, + TSV_TYPE_STRING, dfunc_string); +} +EXPORT_SYMBOL(create_ctx_debugfs); diff --git a/arch/arm/mach-msm/ipc_router.c b/arch/arm/mach-msm/ipc_router.c new file mode 100644 index 0000000000000000000000000000000000000000..6fa435a43eaf71045b23c30bdfc1619738fe50f8 --- /dev/null +++ b/arch/arm/mach-msm/ipc_router.c @@ -0,0 +1,2555 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#include "ipc_router.h" +#include "modem_notifier.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; + struct msm_ipc_router_xprt_info *xprt_info; +}; + +#define RP_HASH_SIZE 32 +struct msm_ipc_router_remote_port { + struct list_head list; + uint32_t node_id; + uint32_t port_id; + uint32_t restart_state; + 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; + uint32_t abort_data_read; + 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; + uint32_t neighbor_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 1 + +/* 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 DECLARE_COMPLETION(msm_ipc_local_router_up); +#define IPC_ROUTER_INIT_TIMEOUT (10 * HZ) + +static uint32_t next_port_id; +static DEFINE_MUTEX(next_port_id_lock); +static atomic_t pending_close_count = ATOMIC_INIT(0); +static wait_queue_head_t subsystem_restart_wait; +static struct workqueue_struct *msm_ipc_router_workqueue; + +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 (!(xprt_info->abort_data_read) && + list_empty(&xprt_info->pkt_list)) { + mutex_unlock(&xprt_info->rx_lock); + wait_event(xprt_info->read_wait, + ((xprt_info->abort_data_read) || + !list_empty(&xprt_info->pkt_list))); + mutex_lock(&xprt_info->rx_lock); + } + if (xprt_info->abort_data_read) { + mutex_unlock(&xprt_info->rx_lock); + return NULL; + } + + 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); + snprintf(port_ptr->rx_wakelock_name, MAX_WAKELOCK_NAME_SZ, + "msm_ipc_read%08x:%08x", + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + wake_lock_init(&port_ptr->port_rx_wake_lock, + WAKE_LOCK_SUSPEND, port_ptr->rx_wakelock_name); + + 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) { + if (rport_ptr->restart_state != RESTART_NORMAL) + rport_ptr = NULL; + 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->restart_state = RESTART_NORMAL; + 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_router_xprt_info *xprt_info) +{ + 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; + server_port->xprt_info = xprt_info; + 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, xprt_info->xprt); + 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_ctl_msg(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *ctl) +{ + struct msm_ipc_router_xprt_info *fwd_xprt_info; + + if (!xprt_info || !ctl) + return -EINVAL; + + mutex_lock(&xprt_info_list_lock); + list_for_each_entry(fwd_xprt_info, &xprt_info_list, list) { + if (xprt_info->xprt->link_id != fwd_xprt_info->xprt->link_id) + msm_ipc_router_send_control_msg(fwd_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, + fwd_xprt_info->xprt); + 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, fwd_xprt_info->xprt); + mutex_unlock(&fwd_xprt_info->tx_lock); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + + return 0; +} + +static void reset_remote_port_info(uint32_t node_id, uint32_t port_id) +{ + struct msm_ipc_router_remote_port *rport_ptr; + + rport_ptr = msm_ipc_router_lookup_remote_port(node_id, port_id); + if (!rport_ptr) { + pr_err("%s: No such remote port %08x:%08x\n", + __func__, node_id, port_id); + return; + } + mutex_lock(&rport_ptr->quota_lock); + rport_ptr->restart_state = RESTART_PEND; + wake_up(&rport_ptr->quota_wait); + mutex_unlock(&rport_ptr->quota_lock); + return; +} + +static void msm_ipc_cleanup_remote_server_info( + struct msm_ipc_router_xprt_info *xprt_info) +{ + struct msm_ipc_server *svr, *tmp_svr; + struct msm_ipc_server_port *svr_port, *tmp_svr_port; + int i; + union rr_control_msg ctl; + + if (!xprt_info) { + pr_err("%s: Invalid xprt_info\n", __func__); + return; + } + + ctl.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_SERVER; + mutex_lock(&server_list_lock); + for (i = 0; i < SRV_HASH_SIZE; i++) { + list_for_each_entry_safe(svr, tmp_svr, &server_list[i], list) { + ctl.srv.service = svr->name.service; + ctl.srv.instance = svr->name.instance; + list_for_each_entry_safe(svr_port, tmp_svr_port, + &svr->server_port_list, list) { + if (svr_port->xprt_info != xprt_info) + continue; + D("Remove server %08x:%08x - %08x:%08x", + ctl.srv.service, ctl.srv.instance, + svr_port->server_addr.node_id, + svr_port->server_addr.port_id); + reset_remote_port_info( + svr_port->server_addr.node_id, + svr_port->server_addr.port_id); + ctl.srv.node_id = svr_port->server_addr.node_id; + ctl.srv.port_id = svr_port->server_addr.port_id; + relay_ctl_msg(xprt_info, &ctl); + broadcast_ctl_msg_locally(&ctl); + list_del(&svr_port->list); + kfree(svr_port); + } + if (list_empty(&svr->server_port_list)) { + list_del(&svr->list); + kfree(svr); + } + } + } + mutex_unlock(&server_list_lock); +} + +static void msm_ipc_cleanup_remote_client_info( + struct msm_ipc_router_xprt_info *xprt_info) +{ + struct msm_ipc_routing_table_entry *rt_entry; + struct msm_ipc_router_remote_port *rport_ptr; + int i, j; + union rr_control_msg ctl; + + if (!xprt_info) { + pr_err("%s: Invalid xprt_info\n", __func__); + return; + } + + ctl.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT; + mutex_lock(&routing_table_lock); + for (i = 0; i < RT_HASH_SIZE; i++) { + list_for_each_entry(rt_entry, &routing_table[i], list) { + mutex_lock(&rt_entry->lock); + if (rt_entry->xprt_info != xprt_info) { + mutex_unlock(&rt_entry->lock); + continue; + } + for (j = 0; j < RP_HASH_SIZE; j++) { + list_for_each_entry(rport_ptr, + &rt_entry->remote_port_list[j], list) { + if (rport_ptr->restart_state == + RESTART_PEND) + continue; + mutex_lock(&rport_ptr->quota_lock); + rport_ptr->restart_state = RESTART_PEND; + wake_up(&rport_ptr->quota_wait); + mutex_unlock(&rport_ptr->quota_lock); + ctl.cli.node_id = rport_ptr->node_id; + ctl.cli.port_id = rport_ptr->port_id; + broadcast_ctl_msg_locally(&ctl); + } + } + mutex_unlock(&rt_entry->lock); + } + } + mutex_unlock(&routing_table_lock); +} + +static void msm_ipc_cleanup_remote_port_info(uint32_t node_id) +{ + struct msm_ipc_routing_table_entry *rt_entry, *tmp_rt_entry; + struct msm_ipc_router_remote_port *rport_ptr, *tmp_rport_ptr; + int i, j; + + mutex_lock(&routing_table_lock); + for (i = 0; i < RT_HASH_SIZE; i++) { + list_for_each_entry_safe(rt_entry, tmp_rt_entry, + &routing_table[i], list) { + mutex_lock(&rt_entry->lock); + if (rt_entry->neighbor_node_id != node_id) { + mutex_unlock(&rt_entry->lock); + continue; + } + for (j = 0; j < RP_HASH_SIZE; j++) { + list_for_each_entry_safe(rport_ptr, + tmp_rport_ptr, + &rt_entry->remote_port_list[j], list) { + list_del(&rport_ptr->list); + kfree(rport_ptr); + } + } + mutex_unlock(&rt_entry->lock); + } + } + mutex_unlock(&routing_table_lock); +} + +static void msm_ipc_cleanup_routing_table( + struct msm_ipc_router_xprt_info *xprt_info) +{ + int i; + struct msm_ipc_routing_table_entry *rt_entry; + + if (!xprt_info) { + pr_err("%s: Invalid xprt_info\n", __func__); + return; + } + + mutex_lock(&routing_table_lock); + for (i = 0; i < RT_HASH_SIZE; i++) { + list_for_each_entry(rt_entry, &routing_table[i], list) { + mutex_lock(&rt_entry->lock); + if (rt_entry->xprt_info == xprt_info) + rt_entry->xprt_info = NULL; + mutex_unlock(&rt_entry->lock); + } + } + mutex_unlock(&routing_table_lock); +} + +static void modem_reset_cleanup(struct msm_ipc_router_xprt_info *xprt_info) +{ + + if (!xprt_info) { + pr_err("%s: Invalid xprt_info\n", __func__); + return; + } + + msm_ipc_cleanup_remote_server_info(xprt_info); + msm_ipc_cleanup_remote_client_info(xprt_info); + msm_ipc_cleanup_routing_table(xprt_info); +} + +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); + if (!temp_ptr) { + pr_err("%s: pkt_fragment_q is empty\n", __func__); + return -EINVAL; + } + hdr = (struct rr_header *)temp_ptr->data; + if (!hdr) { + pr_err("%s: No data inside the skb\n", __func__); + return -EINVAL; + } + 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; + } + add_routing_table_entry(rt_entry); + } + mutex_lock(&rt_entry->lock); + rt_entry->neighbor_node_id = xprt_info->remote_node_id; + rt_entry->xprt_info = xprt_info; + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + msm_ipc_cleanup_remote_port_info(xprt_info->remote_node_id); + + 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->neighbor_node_id = xprt_info->remote_node_id; + 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, xprt_info); + 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; + struct msm_ipc_router_remote_port *rport_ptr; + 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; + } + + rport_ptr = msm_ipc_router_lookup_remote_port(hdr->src_node_id, + hdr->src_port_id); + if (!rport_ptr) { + rport_ptr = msm_ipc_router_create_remote_port( + hdr->src_node_id, + hdr->src_port_id); + if (!rport_ptr) { + pr_err("%s: Remote port %08x:%08x creation failed\n", + __func__, hdr->src_node_id, hdr->src_port_id); + 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, + NULL); + 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); + if (!head_skb) { + pr_err("%s: pkt_fragment_q is empty\n", __func__); + return -EINVAL; + } + 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); + if (!head_skb) { + pr_err("%s: pkt_fragment_q is empty\n", __func__); + return -EINVAL; + } + 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->restart_state != RESTART_NORMAL) + break; + 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 (rport_ptr->restart_state != RESTART_NORMAL) { + mutex_unlock(&rport_ptr->quota_lock); + return -ENETRESET; + } + 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, xprt_info->xprt); + 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) { + 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); + + 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); + } + + wake_lock_destroy(&port_ptr->port_rx_wake_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, + uint32_t lookup_mask) +{ + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int key, 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; + } + + mutex_lock(&server_list_lock); + if (!lookup_mask) + lookup_mask = 0xFFFFFFFF; + for (key = 0; key < SRV_HASH_SIZE; key++) { + list_for_each_entry(server, &server_list[key], list) { + if ((server->name.service != srv_name->service) || + ((server->name.instance & lookup_mask) != + srv_name->instance)) + continue; + + 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(xprt_info->xprt); + 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; + 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, "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; +} + +static void msm_ipc_router_remove_xprt(struct msm_ipc_router_xprt *xprt) +{ + struct msm_ipc_router_xprt_info *xprt_info; + + if (xprt && xprt->priv) { + xprt_info = xprt->priv; + + xprt_info->abort_data_read = 1; + wake_up(&xprt_info->read_wait); + + mutex_lock(&xprt_info_list_lock); + list_del(&xprt_info->list); + mutex_unlock(&xprt_info_list_lock); + + flush_workqueue(xprt_info->workqueue); + destroy_workqueue(xprt_info->workqueue); + wake_lock_destroy(&xprt_info->wakelock); + + xprt->priv = 0; + kfree(xprt_info); + } +} + + +struct msm_ipc_router_xprt_work { + struct msm_ipc_router_xprt *xprt; + struct work_struct work; +}; + +static void xprt_open_worker(struct work_struct *work) +{ + struct msm_ipc_router_xprt_work *xprt_work = + container_of(work, struct msm_ipc_router_xprt_work, work); + + msm_ipc_router_add_xprt(xprt_work->xprt); + kfree(xprt_work); +} + +static void xprt_close_worker(struct work_struct *work) +{ + struct msm_ipc_router_xprt_work *xprt_work = + container_of(work, struct msm_ipc_router_xprt_work, work); + + modem_reset_cleanup(xprt_work->xprt->priv); + msm_ipc_router_remove_xprt(xprt_work->xprt); + + if (atomic_dec_return(&pending_close_count) == 0) + wake_up(&subsystem_restart_wait); + + kfree(xprt_work); +} + +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 msm_ipc_router_xprt_work *xprt_work; + struct rr_packet *pkt; + unsigned long ret; + + if (!msm_ipc_router_workqueue) { + ret = wait_for_completion_timeout(&msm_ipc_local_router_up, + IPC_ROUTER_INIT_TIMEOUT); + if (!ret || !msm_ipc_router_workqueue) { + pr_err("%s: IPC Router not initialized\n", __func__); + return; + } + } + + switch (event) { + case IPC_ROUTER_XPRT_EVENT_OPEN: + D("open event for '%s'\n", xprt->name); + xprt_work = kmalloc(sizeof(struct msm_ipc_router_xprt_work), + GFP_ATOMIC); + xprt_work->xprt = xprt; + INIT_WORK(&xprt_work->work, xprt_open_worker); + queue_work(msm_ipc_router_workqueue, &xprt_work->work); + break; + + case IPC_ROUTER_XPRT_EVENT_CLOSE: + D("close event for '%s'\n", xprt->name); + atomic_inc(&pending_close_count); + xprt_work = kmalloc(sizeof(struct msm_ipc_router_xprt_work), + GFP_ATOMIC); + xprt_work->xprt = xprt; + INIT_WORK(&xprt_work->work, xprt_close_worker); + queue_work(msm_ipc_router_workqueue, &xprt_work->work); + break; + } + + 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 modem_restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data); +static struct notifier_block msm_ipc_router_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 msm_ipc_router_modem_restart_late_init(void) +{ + restart_notifier_handle = subsys_notif_register_notifier("modem", + &msm_ipc_router_nb); + return 0; +} +late_initcall(msm_ipc_router_modem_restart_late_init); + +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; + msm_ipc_router_workqueue = + create_singlethread_workqueue("msm_ipc_router"); + if (!msm_ipc_router_workqueue) + return -ENOMEM; + + 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); + init_waitqueue_head(&subsystem_restart_wait); + ret = msm_ipc_router_init_sockets(); + if (ret < 0) + pr_err("%s: Init sockets failed\n", __func__); + + complete_all(&msm_ipc_local_router_up); + 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 0000000000000000000000000000000000000000..a90be23cb6a8fcc77bb1d6224fb9b790b7addffd --- /dev/null +++ b/arch/arm/mach-msm/ipc_router.h @@ -0,0 +1,220 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#define MAX_WAKELOCK_NAME_SZ 32 + +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; + char rx_wakelock_name[MAX_WAKELOCK_NAME_SZ]; + 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 *default_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)(struct msm_ipc_router_xprt *xprt); + int (*read)(void *data, uint32_t len, + struct msm_ipc_router_xprt *xprt); + int (*write_avail)(struct msm_ipc_router_xprt *xprt); + int (*write)(void *data, uint32_t len, + struct msm_ipc_router_xprt *xprt); + int (*close)(struct msm_ipc_router_xprt *xprt); +}; + +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, + uint32_t lookup_mask); +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); + +#if defined CONFIG_MSM_IPC_ROUTER_SMD_XPRT +extern void *msm_ipc_load_default_node(void); + +extern void msm_ipc_unload_default_node(void *pil); +#else +static inline void *msm_ipc_load_default_node(void) +{ return NULL; } + +static inline void msm_ipc_unload_default_node(void *pil) { } +#endif + +#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 0000000000000000000000000000000000000000..5649784faf7eb6cd63b748e848218cd51e79ed3f --- /dev/null +++ b/arch/arm/mach-msm/ipc_router_smd_xprt.c @@ -0,0 +1,507 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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)) + +#define NUM_SMD_XPRTS 3 +#define XPRT_NAME_LEN (SMD_MAX_CH_NAME_LEN + 12) + +struct msm_ipc_router_smd_xprt { + struct msm_ipc_router_xprt xprt; + smd_channel_t *channel; + struct workqueue_struct *smd_xprt_wq; + wait_queue_head_t write_avail_wait_q; + struct rr_packet *in_pkt; + int is_partial_in_pkt; + struct delayed_work read_work; + spinlock_t ss_reset_lock; /*Subsystem reset lock*/ + int ss_reset; +}; + +struct msm_ipc_router_smd_xprt_work { + struct msm_ipc_router_xprt *xprt; + struct work_struct work; +}; + +static void smd_xprt_read_data(struct work_struct *work); +static void smd_xprt_open_event(struct work_struct *work); +static void smd_xprt_close_event(struct work_struct *work); + +struct msm_ipc_router_smd_xprt_config { + char ch_name[SMD_MAX_CH_NAME_LEN]; + char xprt_name[XPRT_NAME_LEN]; + uint32_t edge; + uint32_t link_id; +}; + +struct msm_ipc_router_smd_xprt_config smd_xprt_cfg[] = { + {"RPCRPY_CNTL", "ipc_rtr_smd_rpcrpy_cntl", SMD_APPS_MODEM, 1}, + {"IPCRTR", "ipc_rtr_smd_ipcrtr", SMD_APPS_MODEM, 1}, + {"IPCRTR", "ipc_rtr_q6_ipcrtr", SMD_APPS_QDSP, 1}, +}; + +static struct msm_ipc_router_smd_xprt smd_remote_xprt[NUM_SMD_XPRTS]; + +static int find_smd_xprt_cfg(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < NUM_SMD_XPRTS; i++) { + if (!strncmp(pdev->name, smd_xprt_cfg[i].ch_name, 20) && + (pdev->id == smd_xprt_cfg[i].edge)) + return i; + } + + return -ENODEV; +} + +static int msm_ipc_router_smd_remote_write_avail( + struct msm_ipc_router_xprt *xprt) +{ + struct msm_ipc_router_smd_xprt *smd_xprtp = + container_of(xprt, struct msm_ipc_router_smd_xprt, xprt); + + return smd_write_avail(smd_xprtp->channel); +} + +static int msm_ipc_router_smd_remote_write(void *data, + uint32_t len, + struct msm_ipc_router_xprt *xprt) +{ + 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; + int ret, num_retries = 0; + unsigned long flags; + struct msm_ipc_router_smd_xprt *smd_xprtp = + container_of(xprt, struct msm_ipc_router_smd_xprt, xprt); + + if (!pkt) + return -EINVAL; + + if (!len || pkt->length != len) + return -EINVAL; + + align_sz = ALIGN_SIZE(pkt->length); + while ((ret = smd_write_start(smd_xprtp->channel, + (len + align_sz))) < 0) { + spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags); + if (smd_xprtp->ss_reset) { + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, + flags); + pr_err("%s: %s chnl reset\n", __func__, xprt->name); + return -ENETRESET; + } + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags); + if (num_retries >= 5) { + pr_err("%s: Error %d @smd_write_start for %s\n", + __func__, ret, xprt->name); + return ret; + } + msleep(50); + num_retries++; + } + + 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_xprtp->channel)) + smd_enable_read_intr(smd_xprtp->channel); + + wait_event(smd_xprtp->write_avail_wait_q, + (smd_write_avail(smd_xprtp->channel) || + smd_xprtp->ss_reset)); + smd_disable_read_intr(smd_xprtp->channel); + spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags); + if (smd_xprtp->ss_reset) { + spin_unlock_irqrestore( + &smd_xprtp->ss_reset_lock, flags); + pr_err("%s: %s chnl reset\n", + __func__, xprt->name); + return -ENETRESET; + } + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, + flags); + + sz_written = smd_write_segment(smd_xprtp->channel, + ipc_rtr_pkt->data + offset, + (ipc_rtr_pkt->len - offset), 0); + offset += sz_written; + sz_written = 0; + } + D("%s: Wrote %d bytes over %s\n", + __func__, offset, xprt->name); + } + + if (align_sz) { + if (smd_write_avail(smd_xprtp->channel) < align_sz) + smd_enable_read_intr(smd_xprtp->channel); + + wait_event(smd_xprtp->write_avail_wait_q, + ((smd_write_avail(smd_xprtp->channel) >= + align_sz) || smd_xprtp->ss_reset)); + smd_disable_read_intr(smd_xprtp->channel); + spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags); + if (smd_xprtp->ss_reset) { + spin_unlock_irqrestore( + &smd_xprtp->ss_reset_lock, flags); + pr_err("%s: %s chnl reset\n", + __func__, xprt->name); + return -ENETRESET; + } + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, + flags); + + smd_write_segment(smd_xprtp->channel, + &align_data, align_sz, 0); + D("%s: Wrote %d align bytes over %s\n", + __func__, align_sz, xprt->name); + } + if (!smd_write_end(smd_xprtp->channel)) + D("%s: Finished writing\n", __func__); + return len; +} + +static int msm_ipc_router_smd_remote_close(struct msm_ipc_router_xprt *xprt) +{ + struct msm_ipc_router_smd_xprt *smd_xprtp = + container_of(xprt, struct msm_ipc_router_smd_xprt, xprt); + + return smd_close(smd_xprtp->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; + unsigned long flags; + struct delayed_work *rwork = to_delayed_work(work); + struct msm_ipc_router_smd_xprt *smd_xprtp = + container_of(rwork, struct msm_ipc_router_smd_xprt, read_work); + + spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags); + if (smd_xprtp->ss_reset) { + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags); + if (smd_xprtp->in_pkt) + release_pkt(smd_xprtp->in_pkt); + smd_xprtp->is_partial_in_pkt = 0; + pr_err("%s: %s channel reset\n", + __func__, smd_xprtp->xprt.name); + return; + } + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags); + + D("%s pkt_size: %d, read_avail: %d\n", __func__, + smd_cur_packet_size(smd_xprtp->channel), + smd_read_avail(smd_xprtp->channel)); + while ((pkt_size = smd_cur_packet_size(smd_xprtp->channel)) && + smd_read_avail(smd_xprtp->channel)) { + if (!smd_xprtp->is_partial_in_pkt) { + smd_xprtp->in_pkt = kzalloc(sizeof(struct rr_packet), + GFP_KERNEL); + if (!smd_xprtp->in_pkt) { + pr_err("%s: Couldn't alloc rr_packet\n", + __func__); + return; + } + + smd_xprtp->in_pkt->pkt_fragment_q = + kmalloc(sizeof(struct sk_buff_head), + GFP_KERNEL); + if (!smd_xprtp->in_pkt->pkt_fragment_q) { + pr_err("%s: Couldn't alloc pkt_fragment_q\n", + __func__); + kfree(smd_xprtp->in_pkt); + return; + } + skb_queue_head_init(smd_xprtp->in_pkt->pkt_fragment_q); + smd_xprtp->is_partial_in_pkt = 1; + D("%s: Allocated rr_packet\n", __func__); + } + + if (((pkt_size >= MIN_FRAG_SZ) && + (smd_read_avail(smd_xprtp->channel) < MIN_FRAG_SZ)) || + ((pkt_size < MIN_FRAG_SZ) && + (smd_read_avail(smd_xprtp->channel) < pkt_size))) + return; + + sz = smd_read_avail(smd_xprtp->channel); + do { + ipc_rtr_pkt = alloc_skb(sz, GFP_KERNEL); + if (!ipc_rtr_pkt) { + if (sz <= (PAGE_SIZE/2)) { + queue_delayed_work( + smd_xprtp->smd_xprt_wq, + &smd_xprtp->read_work, + 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_xprtp->channel, data, sz); + if (sz_read != sz) { + pr_err("%s: Couldn't read %s completely\n", + __func__, smd_xprtp->xprt.name); + kfree_skb(ipc_rtr_pkt); + release_pkt(smd_xprtp->in_pkt); + smd_xprtp->is_partial_in_pkt = 0; + return; + } + skb_queue_tail(smd_xprtp->in_pkt->pkt_fragment_q, ipc_rtr_pkt); + smd_xprtp->in_pkt->length += sz_read; + if (sz_read != pkt_size) + smd_xprtp->is_partial_in_pkt = 1; + else + smd_xprtp->is_partial_in_pkt = 0; + + if (!smd_xprtp->is_partial_in_pkt) { + D("%s: Packet size read %d\n", + __func__, smd_xprtp->in_pkt->length); + msm_ipc_router_xprt_notify(&smd_xprtp->xprt, + IPC_ROUTER_XPRT_EVENT_DATA, + (void *)smd_xprtp->in_pkt); + release_pkt(smd_xprtp->in_pkt); + smd_xprtp->in_pkt = NULL; + } + } +} + +static void smd_xprt_open_event(struct work_struct *work) +{ + struct msm_ipc_router_smd_xprt_work *xprt_work = + container_of(work, struct msm_ipc_router_smd_xprt_work, work); + + msm_ipc_router_xprt_notify(xprt_work->xprt, + IPC_ROUTER_XPRT_EVENT_OPEN, NULL); + D("%s: Notified IPC Router of %s OPEN\n", + __func__, xprt_work->xprt->name); + kfree(xprt_work); +} + +static void smd_xprt_close_event(struct work_struct *work) +{ + struct msm_ipc_router_smd_xprt_work *xprt_work = + container_of(work, struct msm_ipc_router_smd_xprt_work, work); + + msm_ipc_router_xprt_notify(xprt_work->xprt, + IPC_ROUTER_XPRT_EVENT_CLOSE, NULL); + D("%s: Notified IPC Router of %s CLOSE\n", + __func__, xprt_work->xprt->name); + kfree(xprt_work); +} + +static void msm_ipc_router_smd_remote_notify(void *_dev, unsigned event) +{ + unsigned long flags; + struct msm_ipc_router_smd_xprt *smd_xprtp; + struct msm_ipc_router_smd_xprt_work *xprt_work; + + smd_xprtp = (struct msm_ipc_router_smd_xprt *)_dev; + if (!smd_xprtp) + return; + + switch (event) { + case SMD_EVENT_DATA: + if (smd_read_avail(smd_xprtp->channel)) + queue_delayed_work(smd_xprtp->smd_xprt_wq, + &smd_xprtp->read_work, 0); + if (smd_write_avail(smd_xprtp->channel)) + wake_up(&smd_xprtp->write_avail_wait_q); + break; + + case SMD_EVENT_OPEN: + spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags); + smd_xprtp->ss_reset = 0; + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags); + xprt_work = kmalloc(sizeof(struct msm_ipc_router_smd_xprt_work), + GFP_ATOMIC); + if (!xprt_work) { + pr_err("%s: Couldn't notify %d event to IPC Router\n", + __func__, event); + return; + } + xprt_work->xprt = &smd_xprtp->xprt; + INIT_WORK(&xprt_work->work, smd_xprt_open_event); + queue_work(smd_xprtp->smd_xprt_wq, &xprt_work->work); + break; + + case SMD_EVENT_CLOSE: + spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags); + smd_xprtp->ss_reset = 1; + spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags); + wake_up(&smd_xprtp->write_avail_wait_q); + xprt_work = kmalloc(sizeof(struct msm_ipc_router_smd_xprt_work), + GFP_ATOMIC); + if (!xprt_work) { + pr_err("%s: Couldn't notify %d event to IPC Router\n", + __func__, event); + return; + } + xprt_work->xprt = &smd_xprtp->xprt; + INIT_WORK(&xprt_work->work, smd_xprt_close_event); + queue_work(smd_xprtp->smd_xprt_wq, &xprt_work->work); + break; + } +} + +static int msm_ipc_router_smd_remote_probe(struct platform_device *pdev) +{ + int rc; + int id; /*Index into the smd_xprt_cfg table*/ + + id = find_smd_xprt_cfg(pdev); + if (id < 0) { + pr_err("%s: called for unknown ch %s\n", + __func__, pdev->name); + return id; + } + + smd_remote_xprt[id].smd_xprt_wq = + create_singlethread_workqueue(pdev->name); + if (!smd_remote_xprt[id].smd_xprt_wq) { + pr_err("%s: WQ creation failed for %s\n", + __func__, pdev->name); + return -EFAULT; + } + + smd_remote_xprt[id].xprt.name = smd_xprt_cfg[id].xprt_name; + smd_remote_xprt[id].xprt.link_id = smd_xprt_cfg[id].link_id; + smd_remote_xprt[id].xprt.read_avail = NULL; + smd_remote_xprt[id].xprt.read = NULL; + smd_remote_xprt[id].xprt.write_avail = + msm_ipc_router_smd_remote_write_avail; + smd_remote_xprt[id].xprt.write = msm_ipc_router_smd_remote_write; + smd_remote_xprt[id].xprt.close = msm_ipc_router_smd_remote_close; + smd_remote_xprt[id].xprt.priv = NULL; + + init_waitqueue_head(&smd_remote_xprt[id].write_avail_wait_q); + smd_remote_xprt[id].in_pkt = NULL; + smd_remote_xprt[id].is_partial_in_pkt = 0; + INIT_DELAYED_WORK(&smd_remote_xprt[id].read_work, smd_xprt_read_data); + spin_lock_init(&smd_remote_xprt[id].ss_reset_lock); + smd_remote_xprt[id].ss_reset = 0; + + rc = smd_named_open_on_edge(smd_xprt_cfg[id].ch_name, + smd_xprt_cfg[id].edge, + &smd_remote_xprt[id].channel, + &smd_remote_xprt[id], + msm_ipc_router_smd_remote_notify); + if (rc < 0) { + pr_err("%s: Channel open failed for %s\n", + __func__, smd_xprt_cfg[id].ch_name); + destroy_workqueue(smd_remote_xprt[id].smd_xprt_wq); + return rc; + } + + smd_disable_read_intr(smd_remote_xprt[id].channel); + + smsm_change_state(SMSM_APPS_STATE, 0, SMSM_RPCINIT); + + return 0; +} + +void *msm_ipc_load_default_node(void) +{ + void *pil = NULL; + const char *peripheral; + + peripheral = smd_edge_to_subsystem(SMD_APPS_MODEM); + if (peripheral && !strncmp(peripheral, "modem", 6)) { + pil = pil_get(peripheral); + if (IS_ERR(pil)) { + pr_err("%s: Failed to load %s\n", + __func__, peripheral); + pil = NULL; + } + } + return pil; +} +EXPORT_SYMBOL(msm_ipc_load_default_node); + +void msm_ipc_unload_default_node(void *pil) +{ + if (pil) + pil_put(pil); +} +EXPORT_SYMBOL(msm_ipc_unload_default_node); + +static struct platform_driver msm_ipc_router_smd_remote_driver[] = { + { + .probe = msm_ipc_router_smd_remote_probe, + .driver = { + .name = "RPCRPY_CNTL", + .owner = THIS_MODULE, + }, + }, + { + .probe = msm_ipc_router_smd_remote_probe, + .driver = { + .name = "IPCRTR", + .owner = THIS_MODULE, + }, + }, +}; + +static int __init msm_ipc_router_smd_init(void) +{ + int i, ret, rc = 0; + BUG_ON(ARRAY_SIZE(smd_xprt_cfg) != NUM_SMD_XPRTS); + for (i = 0; i < ARRAY_SIZE(msm_ipc_router_smd_remote_driver); i++) { + ret = platform_driver_register( + &msm_ipc_router_smd_remote_driver[i]); + if (ret) { + pr_err("%s: Failed to register platform driver for" + " xprt%d. Continuing...\n", __func__, i); + rc = ret; + } + } + return rc; +} + +module_init(msm_ipc_router_smd_init); +MODULE_DESCRIPTION("IPC 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 0000000000000000000000000000000000000000..d82ffe54cd55a4197ef593ab927bd5c5d7adc122 --- /dev/null +++ b/arch/arm/mach-msm/ipc_socket.c @@ -0,0 +1,548 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_ANDROID_PARANOID_NETWORK +#include +#endif + +#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)) + +static int sockets_enabled; +static struct proto msm_ipc_proto; +static const struct proto_ops msm_ipc_proto_ops; + +#ifdef CONFIG_ANDROID_PARANOID_NETWORK +static inline int check_permissions(void) +{ + int rc = 0; + if (!current_euid() || in_egroup_p(AID_NET_RAW)) + rc = 1; + return rc; +} +# else +static inline int check_permissions(void) +{ + return 1; +} +#endif + +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 (!check_permissions()) { + pr_err("%s: Do not have permissions\n", __func__); + return -EPERM; + } + + 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_load_default_node(); + msm_ipc_sk(sk)->port = port_ptr; + msm_ipc_sk(sk)->default_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); + + 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, + server_arg.lookup_mask); + 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)->default_pil; + int ret; + + lock_sock(sk); + ret = msm_ipc_router_close_port(port_ptr); + msm_ipc_unload_default_node(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 1b54f807c2d0d573dc87e15bb1ba90102577a580..489faa33e209ec72a8da328a847d9433663a7836 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,16 @@ #include #include +#include +#include +#include #include #include +#include +#include "fiq.h" #include "smd_private.h" enum { @@ -71,7 +76,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 +110,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 +129,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 +165,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 +225,18 @@ 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, + [INT_ADSP_A11] = SMSM_FAKE_IRQ, +}; +#endif /* CONFIG_ARCH_FSM9XXX */ static inline void msm_irq_write_all_regs(void __iomem *base, unsigned int val) { @@ -228,8 +248,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 +285,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 +303,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 +341,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 +366,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 +601,123 @@ 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(); } + +static inline void msm_vic_handle_irq(void __iomem *base_addr, struct pt_regs + *regs) +{ + u32 irqnr; + + do { + /* 0xD0 has irq# or old irq# if the irq has been handled + * 0xD4 has irq# or -1 if none pending *but* if you just + * read 0xD4 you never get the first irq for some reason + */ + irqnr = readl_relaxed(base_addr + 0xD0); + irqnr = readl_relaxed(base_addr + 0xD4); + if (irqnr == -1) + break; + handle_IRQ(irqnr, regs); + } while (1); +} + +/* enable imprecise aborts */ +#define local_cpsie_enable() __asm__ __volatile__("cpsie a @ enable") + +asmlinkage void __exception_irq_entry vic_handle_irq(struct pt_regs *regs) +{ + local_cpsie_enable(); + msm_vic_handle_irq((void __iomem *)MSM_VIC_BASE, regs); +} + +#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 ea514be390c6112887d253839323fb940a55628c..280160aa018c86ae1c328ac09a20b1057d36935b 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 0000000000000000000000000000000000000000..8b0fbc04794434e033839d5ff04cb4d1314da38b --- /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.c b/arch/arm/mach-msm/jtag.c new file mode 100644 index 0000000000000000000000000000000000000000..8dae9c63a529f86a6b4c691eb92b14366ab2d352 --- /dev/null +++ b/arch/arm/mach-msm/jtag.c @@ -0,0 +1,1171 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "qdss-priv.h" +#include "cp14.h" + +/* no of dbg regs + 1 (for storing the reg count) */ +#define MAX_DBG_REGS (90) +#define MAX_DBG_STATE_SIZE (MAX_DBG_REGS * num_possible_cpus()) +/* no of etm regs + 1 (for storing the reg count) */ +#define MAX_ETM_REGS (78) +#define MAX_ETM_STATE_SIZE (MAX_ETM_REGS * num_possible_cpus()) + +#define OSLOCK_MAGIC (0xC5ACCE55) +#define DBGDSCR_MASK (0x6C30FC3C) +#define CPMR_ETMCLKEN (0x8) +#define TZ_DBG_ETM_FEAT_ID (0x8) +#define TZ_DBG_ETM_VER (0x400000) + + +uint32_t msm_jtag_save_cntr[NR_CPUS]; +uint32_t msm_jtag_restore_cntr[NR_CPUS]; + +struct dbg_ctx { + uint8_t arch; + bool save_restore_enabled; + uint8_t nr_wp; + uint8_t nr_bp; + uint8_t nr_ctx_cmp; + uint32_t *state; +}; +static struct dbg_ctx dbg; + +struct etm_ctx { + uint8_t arch; + bool save_restore_enabled; + uint8_t nr_addr_cmp; + uint8_t nr_cntr; + uint8_t nr_ext_inp; + uint8_t nr_ext_out; + uint8_t nr_ctxid_cmp; + uint32_t *state; +}; +static struct etm_ctx etm; + +static int dbg_read_bxr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + state[i++] = dbg_read(DBGBVR0); + state[i++] = dbg_read(DBGBCR0); + break; + case 1: + state[i++] = dbg_read(DBGBVR1); + state[i++] = dbg_read(DBGBCR1); + break; + case 2: + state[i++] = dbg_read(DBGBVR2); + state[i++] = dbg_read(DBGBCR2); + break; + case 3: + state[i++] = dbg_read(DBGBVR3); + state[i++] = dbg_read(DBGBCR3); + break; + case 4: + state[i++] = dbg_read(DBGBVR4); + state[i++] = dbg_read(DBGBCR4); + break; + case 5: + state[i++] = dbg_read(DBGBVR5); + state[i++] = dbg_read(DBGBCR5); + break; + case 6: + state[i++] = dbg_read(DBGBVR6); + state[i++] = dbg_read(DBGBCR6); + break; + case 7: + state[i++] = dbg_read(DBGBVR7); + state[i++] = dbg_read(DBGBCR7); + break; + case 8: + state[i++] = dbg_read(DBGBVR8); + state[i++] = dbg_read(DBGBCR8); + break; + case 9: + state[i++] = dbg_read(DBGBVR9); + state[i++] = dbg_read(DBGBCR9); + break; + case 10: + state[i++] = dbg_read(DBGBVR10); + state[i++] = dbg_read(DBGBCR10); + break; + case 11: + state[i++] = dbg_read(DBGBVR11); + state[i++] = dbg_read(DBGBCR11); + break; + case 12: + state[i++] = dbg_read(DBGBVR12); + state[i++] = dbg_read(DBGBCR12); + break; + case 13: + state[i++] = dbg_read(DBGBVR13); + state[i++] = dbg_read(DBGBCR13); + break; + case 14: + state[i++] = dbg_read(DBGBVR14); + state[i++] = dbg_read(DBGBCR14); + break; + case 15: + state[i++] = dbg_read(DBGBVR15); + state[i++] = dbg_read(DBGBCR15); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int dbg_write_bxr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + dbg_write(state[i++], DBGBVR0); + dbg_write(state[i++], DBGBCR0); + break; + case 1: + dbg_write(state[i++], DBGBVR1); + dbg_write(state[i++], DBGBCR1); + break; + case 2: + dbg_write(state[i++], DBGBVR2); + dbg_write(state[i++], DBGBCR2); + break; + case 3: + dbg_write(state[i++], DBGBVR3); + dbg_write(state[i++], DBGBCR3); + break; + case 4: + dbg_write(state[i++], DBGBVR4); + dbg_write(state[i++], DBGBCR4); + break; + case 5: + dbg_write(state[i++], DBGBVR5); + dbg_write(state[i++], DBGBCR5); + break; + case 6: + dbg_write(state[i++], DBGBVR6); + dbg_write(state[i++], DBGBCR6); + break; + case 7: + dbg_write(state[i++], DBGBVR7); + dbg_write(state[i++], DBGBCR7); + break; + case 8: + dbg_write(state[i++], DBGBVR8); + dbg_write(state[i++], DBGBCR8); + break; + case 9: + dbg_write(state[i++], DBGBVR9); + dbg_write(state[i++], DBGBCR9); + break; + case 10: + dbg_write(state[i++], DBGBVR10); + dbg_write(state[i++], DBGBCR10); + break; + case 11: + dbg_write(state[i++], DBGBVR11); + dbg_write(state[i++], DBGBCR11); + break; + case 12: + dbg_write(state[i++], DBGBVR12); + dbg_write(state[i++], DBGBCR12); + break; + case 13: + dbg_write(state[i++], DBGBVR13); + dbg_write(state[i++], DBGBCR13); + break; + case 14: + dbg_write(state[i++], DBGBVR14); + dbg_write(state[i++], DBGBCR14); + break; + case 15: + dbg_write(state[i++], DBGBVR15); + dbg_write(state[i++], DBGBCR15); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int dbg_read_wxr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + state[i++] = dbg_read(DBGWVR0); + state[i++] = dbg_read(DBGWCR0); + break; + case 1: + state[i++] = dbg_read(DBGWVR1); + state[i++] = dbg_read(DBGWCR1); + break; + case 2: + state[i++] = dbg_read(DBGWVR2); + state[i++] = dbg_read(DBGWCR2); + break; + case 3: + state[i++] = dbg_read(DBGWVR3); + state[i++] = dbg_read(DBGWCR3); + break; + case 4: + state[i++] = dbg_read(DBGWVR4); + state[i++] = dbg_read(DBGWCR4); + break; + case 5: + state[i++] = dbg_read(DBGWVR5); + state[i++] = dbg_read(DBGWCR5); + break; + case 6: + state[i++] = dbg_read(DBGWVR6); + state[i++] = dbg_read(DBGWCR6); + break; + case 7: + state[i++] = dbg_read(DBGWVR7); + state[i++] = dbg_read(DBGWCR7); + break; + case 8: + state[i++] = dbg_read(DBGWVR8); + state[i++] = dbg_read(DBGWCR8); + break; + case 9: + state[i++] = dbg_read(DBGWVR9); + state[i++] = dbg_read(DBGWCR9); + break; + case 10: + state[i++] = dbg_read(DBGWVR10); + state[i++] = dbg_read(DBGWCR10); + break; + case 11: + state[i++] = dbg_read(DBGWVR11); + state[i++] = dbg_read(DBGWCR11); + break; + case 12: + state[i++] = dbg_read(DBGWVR12); + state[i++] = dbg_read(DBGWCR12); + break; + case 13: + state[i++] = dbg_read(DBGWVR13); + state[i++] = dbg_read(DBGWCR13); + break; + case 14: + state[i++] = dbg_read(DBGWVR14); + state[i++] = dbg_read(DBGWCR14); + break; + case 15: + state[i++] = dbg_read(DBGWVR15); + state[i++] = dbg_read(DBGWCR15); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int dbg_write_wxr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + dbg_write(state[i++], DBGWVR0); + dbg_write(state[i++], DBGWCR0); + break; + case 1: + dbg_write(state[i++], DBGWVR1); + dbg_write(state[i++], DBGWCR1); + break; + case 2: + dbg_write(state[i++], DBGWVR2); + dbg_write(state[i++], DBGWCR2); + break; + case 3: + dbg_write(state[i++], DBGWVR3); + dbg_write(state[i++], DBGWCR3); + break; + case 4: + dbg_write(state[i++], DBGWVR4); + dbg_write(state[i++], DBGWCR4); + break; + case 5: + dbg_write(state[i++], DBGWVR5); + dbg_write(state[i++], DBGWCR5); + break; + case 6: + dbg_write(state[i++], DBGWVR6); + dbg_write(state[i++], DBGWCR6); + break; + case 7: + dbg_write(state[i++], DBGWVR7); + dbg_write(state[i++], DBGWCR7); + break; + case 8: + dbg_write(state[i++], DBGWVR8); + dbg_write(state[i++], DBGWCR8); + break; + case 9: + dbg_write(state[i++], DBGWVR9); + dbg_write(state[i++], DBGWCR9); + break; + case 10: + dbg_write(state[i++], DBGWVR10); + dbg_write(state[i++], DBGWCR10); + break; + case 11: + dbg_write(state[i++], DBGWVR11); + dbg_write(state[i++], DBGWCR11); + break; + case 12: + dbg_write(state[i++], DBGWVR12); + dbg_write(state[i++], DBGWCR12); + break; + case 13: + dbg_write(state[i++], DBGWVR13); + dbg_write(state[i++], DBGWCR13); + break; + case 14: + dbg_write(state[i++], DBGWVR14); + dbg_write(state[i++], DBGWCR14); + break; + case 15: + dbg_write(state[i++], DBGWVR15); + dbg_write(state[i++], DBGWCR15); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static inline bool dbg_arch_supported(uint8_t arch) +{ + switch (arch) { + case ARM_DEBUG_ARCH_V7_1: + case ARM_DEBUG_ARCH_V7: + case ARM_DEBUG_ARCH_V7B: + break; + default: + return false; + } + return true; +} + + +static inline void dbg_save_state(int cpu) +{ + int i, j, cnt; + + i = cpu * MAX_DBG_REGS; + + switch (dbg.arch) { + case ARM_DEBUG_ARCH_V7_1: + /* Set OS lock to inform the debugger that the OS is in the + * process of saving debug registers. It prevents accidental + * modification of the debug regs by the external debugger. + */ + dbg_write(OSLOCK_MAGIC, DBGOSLAR); + isb(); + + /* We skip saving DBGBXVRn since not supported on Krait */ + + dbg.state[i++] = dbg_read(DBGWFAR); + for (j = 0; j < dbg.nr_bp; j++) + i = dbg_read_bxr(dbg.state, i, j); + for (j = 0; j < dbg.nr_wp; j++) + i = dbg_read_wxr(dbg.state, i, j); + dbg.state[i++] = dbg_read(DBGVCR); + dbg.state[i++] = dbg_read(DBGCLAIMCLR); + dbg.state[i++] = dbg_read(DBGDTRTXext); + dbg.state[i++] = dbg_read(DBGDTRRXext); + dbg.state[i++] = dbg_read(DBGDSCRext); + + /* Set the OS double lock */ + isb(); + dbg_write(0x1, DBGOSDLR); + isb(); + break; + case ARM_DEBUG_ARCH_V7B: + case ARM_DEBUG_ARCH_V7: + /* Set OS lock to inform the debugger that the OS is in the + * process of saving dbg registers. It prevents accidental + * modification of the dbg regs by the external debugger + * and resets the internal counter. + */ + dbg_write(OSLOCK_MAGIC, DBGOSLAR); + isb(); + + cnt = dbg_read(DBGOSSRR); /* save count for restore */ + /* MAX_DBG_REGS = no of dbg regs + 1 (for storing the reg count) + * check for state overflow, if not enough space, don't save + */ + if (cnt >= MAX_DBG_REGS) + cnt = 0; + dbg.state[i++] = cnt; + for (j = 0; j < cnt; j++) + dbg.state[i++] = dbg_read(DBGOSSRR); + break; + default: + pr_err_ratelimited("unsupported dbg arch %d in %s\n", dbg.arch, + __func__); + } +} + +static inline void dbg_restore_state(int cpu) +{ + int i, j, cnt; + + i = cpu * MAX_DBG_REGS; + + switch (dbg.arch) { + case ARM_DEBUG_ARCH_V7_1: + /* Clear the OS double lock */ + isb(); + dbg_write(0x0, DBGOSDLR); + isb(); + + /* Set OS lock. Lock will already be set after power collapse + * but this write is included to ensure it is set. + */ + dbg_write(OSLOCK_MAGIC, DBGOSLAR); + isb(); + + /* We skip restoring DBGBXVRn since not supported on Krait */ + + dbg_write(dbg.state[i++], DBGWFAR); + for (j = 0; j < dbg.nr_bp; j++) + i = dbg_write_bxr(dbg.state, i, j); + for (j = 0; j < dbg.nr_wp; j++) + i = dbg_write_wxr(dbg.state, i, j); + dbg_write(dbg.state[i++], DBGVCR); + dbg_write(dbg.state[i++], DBGCLAIMSET); + dbg_write(dbg.state[i++], DBGDTRTXext); + dbg_write(dbg.state[i++], DBGDTRRXext); + dbg_write(dbg.state[i++] & DBGDSCR_MASK, DBGDSCRext); + + isb(); + dbg_write(0x0, DBGOSLAR); + isb(); + break; + case ARM_DEBUG_ARCH_V7B: + case ARM_DEBUG_ARCH_V7: + /* Clear sticky bit */ + dbg_read(DBGPRSR); + isb(); + + /* Set OS lock. Lock will already be set after power collapse + * but this write is required to reset the internal counter used + * for DBG state restore. + */ + dbg_write(OSLOCK_MAGIC, DBGOSLAR); + isb(); + + dbg_read(DBGOSSRR); /* dummy read of OSSRR */ + cnt = dbg.state[i++]; + for (j = 0; j < cnt; j++) { + /* DBGDSCR special case + * DBGDSCR = DBGDSCR & DBGDSCR_MASK + */ + if (j == 20) + dbg_write(dbg.state[i++] & DBGDSCR_MASK, + DBGOSSRR); + else + dbg_write(dbg.state[i++], DBGOSSRR); + } + + /* Clear the OS lock */ + isb(); + dbg_write(0x0, DBGOSLAR); + isb(); + break; + default: + pr_err_ratelimited("unsupported dbg arch %d in %s\n", dbg.arch, + __func__); + } + +} + +static int etm_read_acxr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + state[i++] = etm_read(ETMACVR0); + state[i++] = etm_read(ETMACTR0); + break; + case 1: + state[i++] = etm_read(ETMACVR1); + state[i++] = etm_read(ETMACTR1); + break; + case 2: + state[i++] = etm_read(ETMACVR2); + state[i++] = etm_read(ETMACTR2); + break; + case 3: + state[i++] = etm_read(ETMACVR3); + state[i++] = etm_read(ETMACTR3); + break; + case 4: + state[i++] = etm_read(ETMACVR4); + state[i++] = etm_read(ETMACTR4); + break; + case 5: + state[i++] = etm_read(ETMACVR5); + state[i++] = etm_read(ETMACTR5); + break; + case 6: + state[i++] = etm_read(ETMACVR6); + state[i++] = etm_read(ETMACTR6); + break; + case 7: + state[i++] = etm_read(ETMACVR7); + state[i++] = etm_read(ETMACTR7); + break; + case 8: + state[i++] = etm_read(ETMACVR8); + state[i++] = etm_read(ETMACTR8); + break; + case 9: + state[i++] = etm_read(ETMACVR9); + state[i++] = etm_read(ETMACTR9); + break; + case 10: + state[i++] = etm_read(ETMACVR10); + state[i++] = etm_read(ETMACTR10); + break; + case 11: + state[i++] = etm_read(ETMACVR11); + state[i++] = etm_read(ETMACTR11); + break; + case 12: + state[i++] = etm_read(ETMACVR12); + state[i++] = etm_read(ETMACTR12); + break; + case 13: + state[i++] = etm_read(ETMACVR13); + state[i++] = etm_read(ETMACTR13); + break; + case 14: + state[i++] = etm_read(ETMACVR14); + state[i++] = etm_read(ETMACTR14); + break; + case 15: + state[i++] = etm_read(ETMACVR15); + state[i++] = etm_read(ETMACTR15); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_write_acxr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + etm_write(state[i++], ETMACVR0); + etm_write(state[i++], ETMACTR0); + break; + case 1: + etm_write(state[i++], ETMACVR1); + etm_write(state[i++], ETMACTR1); + break; + case 2: + etm_write(state[i++], ETMACVR2); + etm_write(state[i++], ETMACTR2); + break; + case 3: + etm_write(state[i++], ETMACVR3); + etm_write(state[i++], ETMACTR3); + break; + case 4: + etm_write(state[i++], ETMACVR4); + etm_write(state[i++], ETMACTR4); + break; + case 5: + etm_write(state[i++], ETMACVR5); + etm_write(state[i++], ETMACTR5); + break; + case 6: + etm_write(state[i++], ETMACVR6); + etm_write(state[i++], ETMACTR6); + break; + case 7: + etm_write(state[i++], ETMACVR7); + etm_write(state[i++], ETMACTR7); + break; + case 8: + etm_write(state[i++], ETMACVR8); + etm_write(state[i++], ETMACTR8); + break; + case 9: + etm_write(state[i++], ETMACVR9); + etm_write(state[i++], ETMACTR9); + break; + case 10: + etm_write(state[i++], ETMACVR10); + etm_write(state[i++], ETMACTR10); + break; + case 11: + etm_write(state[i++], ETMACVR11); + etm_write(state[i++], ETMACTR11); + break; + case 12: + etm_write(state[i++], ETMACVR12); + etm_write(state[i++], ETMACTR12); + break; + case 13: + etm_write(state[i++], ETMACVR13); + etm_write(state[i++], ETMACTR13); + break; + case 14: + etm_write(state[i++], ETMACVR14); + etm_write(state[i++], ETMACTR14); + break; + case 15: + etm_write(state[i++], ETMACVR15); + etm_write(state[i++], ETMACTR15); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_read_cntx(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + state[i++] = etm_read(ETMCNTRLDVR0); + state[i++] = etm_read(ETMCNTENR0); + state[i++] = etm_read(ETMCNTRLDEVR0); + state[i++] = etm_read(ETMCNTVR0); + break; + case 1: + state[i++] = etm_read(ETMCNTRLDVR1); + state[i++] = etm_read(ETMCNTENR1); + state[i++] = etm_read(ETMCNTRLDEVR1); + state[i++] = etm_read(ETMCNTVR1); + break; + case 2: + state[i++] = etm_read(ETMCNTRLDVR2); + state[i++] = etm_read(ETMCNTENR2); + state[i++] = etm_read(ETMCNTRLDEVR2); + state[i++] = etm_read(ETMCNTVR2); + break; + case 3: + state[i++] = etm_read(ETMCNTRLDVR3); + state[i++] = etm_read(ETMCNTENR3); + state[i++] = etm_read(ETMCNTRLDEVR3); + state[i++] = etm_read(ETMCNTVR3); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_write_cntx(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + etm_write(state[i++], ETMCNTRLDVR0); + etm_write(state[i++], ETMCNTENR0); + etm_write(state[i++], ETMCNTRLDEVR0); + etm_write(state[i++], ETMCNTVR0); + break; + case 1: + etm_write(state[i++], ETMCNTRLDVR1); + etm_write(state[i++], ETMCNTENR1); + etm_write(state[i++], ETMCNTRLDEVR1); + etm_write(state[i++], ETMCNTVR1); + break; + case 2: + etm_write(state[i++], ETMCNTRLDVR2); + etm_write(state[i++], ETMCNTENR2); + etm_write(state[i++], ETMCNTRLDEVR2); + etm_write(state[i++], ETMCNTVR2); + break; + case 3: + etm_write(state[i++], ETMCNTRLDVR3); + etm_write(state[i++], ETMCNTENR3); + etm_write(state[i++], ETMCNTRLDEVR3); + etm_write(state[i++], ETMCNTVR3); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_read_extoutevr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + state[i++] = etm_read(ETMEXTOUTEVR0); + break; + case 1: + state[i++] = etm_read(ETMEXTOUTEVR1); + break; + case 2: + state[i++] = etm_read(ETMEXTOUTEVR2); + break; + case 3: + state[i++] = etm_read(ETMEXTOUTEVR3); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_write_extoutevr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + etm_write(state[i++], ETMEXTOUTEVR0); + break; + case 1: + etm_write(state[i++], ETMEXTOUTEVR1); + break; + case 2: + etm_write(state[i++], ETMEXTOUTEVR2); + break; + case 3: + etm_write(state[i++], ETMEXTOUTEVR3); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_read_cidcvr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + state[i++] = etm_read(ETMCIDCVR0); + break; + case 1: + state[i++] = etm_read(ETMCIDCVR1); + break; + case 2: + state[i++] = etm_read(ETMCIDCVR2); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static int etm_write_cidcvr(uint32_t *state, int i, int j) +{ + switch (j) { + case 0: + etm_write(state[i++], ETMCIDCVR0); + break; + case 1: + etm_write(state[i++], ETMCIDCVR1); + break; + case 2: + etm_write(state[i++], ETMCIDCVR2); + break; + default: + pr_err_ratelimited("idx %d out of bounds in %s\n", j, __func__); + } + return i; +} + +static inline void etm_clk_disable(void) +{ + uint32_t cpmr; + + isb(); + asm volatile("mrc p15, 7, %0, c15, c0, 5" : "=r" (cpmr)); + cpmr &= ~CPMR_ETMCLKEN; + asm volatile("mcr p15, 7, %0, c15, c0, 5" : : "r" (cpmr)); +} + +static inline void etm_clk_enable(void) +{ + uint32_t cpmr; + + asm volatile("mrc p15, 7, %0, c15, c0, 5" : "=r" (cpmr)); + cpmr |= CPMR_ETMCLKEN; + asm volatile("mcr p15, 7, %0, c15, c0, 5" : : "r" (cpmr)); + isb(); +} + +static inline bool etm_arch_supported(uint8_t arch) +{ + switch (arch) { + case ETM_ARCH_V3_3: + case PFT_ARCH_V1_1: + break; + default: + return false; + } + return true; +} + +static inline void etm_save_state(int cpu) +{ + int i, j, cnt; + + i = cpu * MAX_ETM_REGS; + + /* Vote for ETM power/clock enable */ + etm_clk_enable(); + + switch (etm.arch) { + case PFT_ARCH_V1_1: + /* Set OS lock to inform the debugger that the OS is in the + * process of saving etm registers. It prevents accidental + * modification of the etm regs by the external debugger. + * + * We don't poll for ETMSR[1] since it doesn't get set + */ + etm_write(OSLOCK_MAGIC, ETMOSLAR); + isb(); + + etm.state[i++] = etm_read(ETMCR); + etm.state[i++] = etm_read(ETMTRIGGER); + etm.state[i++] = etm_read(ETMSR); + etm.state[i++] = etm_read(ETMTSSCR); + etm.state[i++] = etm_read(ETMTEEVR); + etm.state[i++] = etm_read(ETMTECR1); + etm.state[i++] = etm_read(ETMFFLR); + for (j = 0; j < etm.nr_addr_cmp; j++) + i = etm_read_acxr(etm.state, i, j); + for (j = 0; j < etm.nr_cntr; j++) + i = etm_read_cntx(etm.state, i, j); + etm.state[i++] = etm_read(ETMSQ12EVR); + etm.state[i++] = etm_read(ETMSQ21EVR); + etm.state[i++] = etm_read(ETMSQ23EVR); + etm.state[i++] = etm_read(ETMSQ31EVR); + etm.state[i++] = etm_read(ETMSQ32EVR); + etm.state[i++] = etm_read(ETMSQ13EVR); + etm.state[i++] = etm_read(ETMSQR); + for (j = 0; j < etm.nr_ext_out; j++) + i = etm_read_extoutevr(etm.state, i, j); + for (j = 0; j < etm.nr_ctxid_cmp; j++) + i = etm_read_cidcvr(etm.state, i, j); + etm.state[i++] = etm_read(ETMCIDCMR); + etm.state[i++] = etm_read(ETMSYNCFR); + etm.state[i++] = etm_read(ETMEXTINSELR); + etm.state[i++] = etm_read(ETMTSEVR); + etm.state[i++] = etm_read(ETMAUXCR); + etm.state[i++] = etm_read(ETMTRACEIDR); + etm.state[i++] = etm_read(ETMVMIDCVR); + etm.state[i++] = etm_read(ETMCLAIMCLR); + break; + case ETM_ARCH_V3_3: + /* In ETMv3.3, it is possible for the coresight lock to be + * implemented for CP14 interface but we currently assume that + * it is not, so no need to unlock and lock coresight lock + * (ETMLAR). + * + * Also since save and restore is not conditional i.e. always + * occurs when enabled, there is no need to clear the sticky + * PDSR bit while saving. It will be cleared during boot up/init + * and then by the restore procedure. + */ + + /* Set OS lock to inform the debugger that the OS is in the + * process of saving etm registers. It prevents accidental + * modification of the etm regs by the external debugger + * and resets the internal counter. + */ + etm_write(OSLOCK_MAGIC, ETMOSLAR); + isb(); + + cnt = etm_read(ETMOSSRR); /* save count for restore */ + /* MAX_ETM_REGS = no of etm regs + 1 (for storing the reg count) + * check for state overflow, if not enough space, don't save + */ + if (cnt >= MAX_ETM_REGS) + cnt = 0; + etm.state[i++] = cnt; + for (j = 0; j < cnt; j++) + etm.state[i++] = etm_read(ETMOSSRR); + break; + default: + pr_err_ratelimited("unsupported etm arch %d in %s\n", etm.arch, + __func__); + } + + /* Vote for ETM power/clock disable */ + etm_clk_disable(); +} + +static inline void etm_restore_state(int cpu) +{ + int i, j, cnt; + + i = cpu * MAX_ETM_REGS; + + /* Vote for ETM power/clock enable */ + etm_clk_enable(); + + switch (etm.arch) { + case PFT_ARCH_V1_1: + /* Set OS lock. Lock will already be set after power collapse + * but this write is included to ensure it is set. + * + * We don't poll for ETMSR[1] since it doesn't get set + */ + etm_write(OSLOCK_MAGIC, ETMOSLAR); + isb(); + + etm_write(etm.state[i++], ETMCR); + etm_write(etm.state[i++], ETMTRIGGER); + etm_write(etm.state[i++], ETMSR); + etm_write(etm.state[i++], ETMTSSCR); + etm_write(etm.state[i++], ETMTEEVR); + etm_write(etm.state[i++], ETMTECR1); + etm_write(etm.state[i++], ETMFFLR); + for (j = 0; j < etm.nr_addr_cmp; j++) + i = etm_write_acxr(etm.state, i, j); + for (j = 0; j < etm.nr_cntr; j++) + i = etm_write_cntx(etm.state, i, j); + etm_write(etm.state[i++], ETMSQ12EVR); + etm_write(etm.state[i++], ETMSQ21EVR); + etm_write(etm.state[i++], ETMSQ23EVR); + etm_write(etm.state[i++], ETMSQ31EVR); + etm_write(etm.state[i++], ETMSQ32EVR); + etm_write(etm.state[i++], ETMSQ13EVR); + etm_write(etm.state[i++], ETMSQR); + for (j = 0; j < etm.nr_ext_out; j++) + i = etm_write_extoutevr(etm.state, i, j); + for (j = 0; j < etm.nr_ctxid_cmp; j++) + i = etm_write_cidcvr(etm.state, i, j); + etm_write(etm.state[i++], ETMCIDCMR); + etm_write(etm.state[i++], ETMSYNCFR); + etm_write(etm.state[i++], ETMEXTINSELR); + etm_write(etm.state[i++], ETMTSEVR); + etm_write(etm.state[i++], ETMAUXCR); + etm_write(etm.state[i++], ETMTRACEIDR); + etm_write(etm.state[i++], ETMVMIDCVR); + etm_write(etm.state[i++], ETMCLAIMSET); + + /* Clear the OS lock */ + isb(); + etm_write(0x0, ETMOSLAR); + isb(); + break; + case ETM_ARCH_V3_3: + /* In ETMv3.3, it is possible for the coresight lock to be + * implemented for CP14 interface but we currently assume that + * it is not, so no need to unlock and lock coresight lock + * (ETMLAR). + */ + + /* Clear sticky bit */ + etm_read(ETMPDSR); + isb(); + + /* Set OS lock. Lock will already be set after power collapse + * but this write is required to reset the internal counter used + * for ETM state restore. + */ + etm_write(OSLOCK_MAGIC, ETMOSLAR); + isb(); + + etm_read(ETMOSSRR); /* dummy read of OSSRR */ + cnt = etm.state[i++]; + for (j = 0; j < cnt; j++) + etm_write(etm.state[i++], ETMOSSRR); + + /* Clear the OS lock */ + isb(); + etm_write(0x0, ETMOSLAR); + isb(); + break; + default: + pr_err_ratelimited("unsupported etm arch %d in %s\n", etm.arch, + __func__); + } + + /* Vote for ETM power/clock disable */ + etm_clk_disable(); +} + +/** + * msm_jtag_save_state - save debug and etm registers + * + * Debug and etm registers are saved before power collapse if debug + * and etm architecture is supported respectively and TZ isn't supporting + * the save and restore of debug and etm registers. + * + * CONTEXT: + * Called with preemption off and interrupts locked from: + * 1. per_cpu idle thread context for idle power collapses + * or + * 2. per_cpu idle thread context for hotplug/suspend power collapse + * for nonboot cpus + * or + * 3. suspend thread context for suspend power collapse for core0 + * + * In all cases we will run on the same cpu for the entire duration. + */ +void msm_jtag_save_state(void) +{ + int cpu; + + cpu = raw_smp_processor_id(); + + msm_jtag_save_cntr[cpu]++; + /* ensure counter is updated before moving forward */ + mb(); + + if (dbg.save_restore_enabled) + dbg_save_state(cpu); + if (etm.save_restore_enabled) + etm_save_state(cpu); +} +EXPORT_SYMBOL(msm_jtag_save_state); + +/** + * msm_jtag_restore_state - restore debug and etm registers + * + * Debug and etm registers are restored after power collapse if debug + * and etm architecture is supported respectively and TZ isn't supporting + * the save and restore of debug and etm registers. + * + * CONTEXT: + * Called with preemption off and interrupts locked from: + * 1. per_cpu idle thread context for idle power collapses + * or + * 2. per_cpu idle thread context for hotplug/suspend power collapse + * for nonboot cpus + * or + * 3. suspend thread context for suspend power collapse for core0 + * + * In all cases we will run on the same cpu for the entire duration. + */ +void msm_jtag_restore_state(void) +{ + int cpu; + + cpu = raw_smp_processor_id(); + + msm_jtag_restore_cntr[cpu]++; + /* ensure counter is updated before moving forward */ + mb(); + + if (dbg.save_restore_enabled) + dbg_restore_state(cpu); + if (etm.save_restore_enabled) + etm_restore_state(cpu); +} +EXPORT_SYMBOL(msm_jtag_restore_state); + +static int __init msm_jtag_dbg_init(void) +{ + int ret; + uint32_t dbgdidr; + + /* This will run on core0 so use it to populate parameters */ + + /* Populate dbg_ctx data */ + + dbgdidr = dbg_read(DBGDIDR); + dbg.arch = BMVAL(dbgdidr, 16, 19); + dbg.nr_ctx_cmp = BMVAL(dbgdidr, 20, 23) + 1; + dbg.nr_bp = BMVAL(dbgdidr, 24, 27) + 1; + dbg.nr_wp = BMVAL(dbgdidr, 28, 31) + 1; + + if (dbg_arch_supported(dbg.arch)) { + if (scm_get_feat_version(TZ_DBG_ETM_FEAT_ID) < TZ_DBG_ETM_VER) { + dbg.save_restore_enabled = true; + } else { + pr_info("dbg save-restore supported by TZ\n"); + goto dbg_out; + } + } else { + pr_info("dbg arch %u not supported\n", dbg.arch); + goto dbg_out; + } + + /* Allocate dbg state save space */ + dbg.state = kzalloc(MAX_DBG_STATE_SIZE * sizeof(uint32_t), GFP_KERNEL); + if (!dbg.state) { + ret = -ENOMEM; + goto dbg_err; + } +dbg_out: + return 0; +dbg_err: + return ret; +} +arch_initcall(msm_jtag_dbg_init); + +static int __init msm_jtag_etm_init(void) +{ + int ret; + uint32_t etmidr; + uint32_t etmccr; + + /* Vote for ETM power/clock enable */ + etm_clk_enable(); + + /* Clear sticky bit in PDSR - required for ETMv3.3 (8660) */ + etm_read(ETMPDSR); + isb(); + + /* Populate etm_ctx data */ + + etmidr = etm_read(ETMIDR); + etm.arch = BMVAL(etmidr, 4, 11); + + etmccr = etm_read(ETMCCR); + etm.nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2; + etm.nr_cntr = BMVAL(etmccr, 13, 15); + etm.nr_ext_inp = BMVAL(etmccr, 17, 19); + etm.nr_ext_out = BMVAL(etmccr, 20, 22); + etm.nr_ctxid_cmp = BMVAL(etmccr, 24, 25); + + if (etm_arch_supported(etm.arch)) { + if (scm_get_feat_version(TZ_DBG_ETM_FEAT_ID) < TZ_DBG_ETM_VER) { + etm.save_restore_enabled = true; + } else { + pr_info("etm save-restore supported by TZ\n"); + goto etm_out; + } + } else { + pr_info("etm arch %u not supported\n", etm.arch); + goto etm_out; + } + + /* Vote for ETM power/clock disable */ + etm_clk_disable(); + + /* Allocate etm state save space */ + etm.state = kzalloc(MAX_ETM_STATE_SIZE * sizeof(uint32_t), GFP_KERNEL); + if (!etm.state) { + ret = -ENOMEM; + goto etm_err; + } +etm_out: + etm_clk_disable(); + return 0; +etm_err: + return ret; +} +arch_initcall(msm_jtag_etm_init); 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 0000000000000000000000000000000000000000..711cdbb8d63b907f6d6ff2c33e731765b9392bff --- /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.tv64 = 40 * NSEC_PER_USEC, + .poll_time.tv64 = 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.tv64 = 40 * NSEC_PER_USEC, + .poll_time.tv64 = 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.tv64 = 40 * NSEC_PER_USEC, + .poll_time.tv64 = 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/lpass-8660.c b/arch/arm/mach-msm/lpass-8660.c new file mode 100644 index 0000000000000000000000000000000000000000..10183609ff04acf603b21c3a6a032a7c67170939 --- /dev/null +++ b/arch/arm/mach-msm/lpass-8660.c @@ -0,0 +1,166 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 Q6SS_WDOG_ENABLE 0x28882024 +#define Q6SS_SOFT_INTR_WAKEUP 0x288A001C +#define MODULE_NAME "lpass_8x60" +#define SCM_Q6_NMI_CMD 0x1 + +/* Subsystem restart: QDSP6 data, functions */ +static void *q6_ramdump_dev; +static void q6_fatal_fn(struct work_struct *); +static DECLARE_WORK(q6_fatal_work, q6_fatal_fn); +static void __iomem *q6_wakeup_intr; + +static void q6_fatal_fn(struct work_struct *work) +{ + pr_err("%s: Watchdog bite received from Q6!\n", MODULE_NAME); + subsystem_restart("lpass"); + enable_irq(LPASS_Q6SS_WDOG_EXPIRED); +} + +static void send_q6_nmi(void) +{ + /* Send NMI to QDSP6 via an SCM call. */ + scm_call_atomic1(SCM_SVC_UTIL, SCM_Q6_NMI_CMD, 0x1); + + /* Wakeup the Q6 */ + if (q6_wakeup_intr) + writel_relaxed(0x2000, q6_wakeup_intr); + else + pr_warn("lpass-8660: Unable to send wakeup interrupt to Q6.\n"); + + /* Q6 requires atleast 100ms to dump caches etc.*/ + mdelay(100); + + 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); + + 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(); +} + +static irqreturn_t lpass_wdog_bite_irq(int irq, void *dev_id) +{ + int ret; + + ret = schedule_work(&q6_fatal_work); + disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED); + + 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 void __exit lpass_fatal_exit(void) +{ + iounmap(q6_wakeup_intr); + free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL); +} + +static int __init lpass_fatal_init(void) +{ + int ret; + + ret = request_irq(LPASS_Q6SS_WDOG_EXPIRED, lpass_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; + } + + q6_wakeup_intr = ioremap_nocache(Q6SS_SOFT_INTR_WAKEUP, 8); + + if (!q6_wakeup_intr) + pr_warn("lpass-8660: Unable to ioremap q6 wakeup address."); + + ret = ssr_register_subsystem(&subsys_8x60_q6); +out: + return ret; +} + +module_init(lpass_fatal_init); +module_exit(lpass_fatal_exit); + diff --git a/arch/arm/mach-msm/lpass-8960.c b/arch/arm/mach-msm/lpass-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..c58b0e1024d407118f8aeb16112a413427fd54b5 --- /dev/null +++ b/arch/arm/mach-msm/lpass-8960.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "smd_private.h" +#include "ramdump.h" +#include "sysmon.h" + +#define SCM_Q6_NMI_CMD 0x1 +#define MODULE_NAME "lpass_8960" +#define MAX_BUF_SIZE 0x51 + +/* Subsystem restart: QDSP6 data, functions */ +static void lpass_fatal_fn(struct work_struct *); +static DECLARE_WORK(lpass_fatal_work, lpass_fatal_fn); +struct lpass_ssr { + void *lpass_ramdump_dev; +} lpass_ssr; + +static struct lpass_ssr lpass_ssr_8960; +static int q6_crash_shutdown; + +static int riva_notifier_cb(struct notifier_block *this, unsigned long code, + void *ss_handle) +{ + int ret; + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("%s: R-Notify: Shutdown started\n", __func__); + ret = sysmon_send_event(SYSMON_SS_LPASS, "wcnss", + SUBSYS_BEFORE_SHUTDOWN); + if (ret < 0) + pr_err("%s: sysmon_send_event error %d", __func__, + ret); + break; + } + return NOTIFY_DONE; +} + +static void *ssr_notif_hdle; +static struct notifier_block rnb = { + .notifier_call = riva_notifier_cb, +}; + +static int modem_notifier_cb(struct notifier_block *this, unsigned long code, + void *ss_handle) +{ + int ret; + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("%s: M-Notify: Shutdown started\n", __func__); + ret = sysmon_send_event(SYSMON_SS_LPASS, "modem", + SUBSYS_BEFORE_SHUTDOWN); + if (ret < 0) + pr_err("%s: sysmon_send_event error %d", __func__, + ret); + break; + } + return NOTIFY_DONE; +} + +static void *ssr_modem_notif_hdle; +static struct notifier_block mnb = { + .notifier_call = modem_notifier_cb, +}; + +static void lpass_log_failure_reason(void) +{ + char *reason; + char buffer[MAX_BUF_SIZE]; + unsigned size; + + reason = smem_get_entry(SMEM_SSR_REASON_LPASS0, &size); + + if (!reason) { + pr_err("%s: subsystem failure reason: (unknown, smem_get_entry failed).", + MODULE_NAME); + return; + } + + if (reason[0] == '\0') { + pr_err("%s: subsystem failure reason: (unknown, init value found)", + MODULE_NAME); + return; + } + + size = size < MAX_BUF_SIZE ? size : (MAX_BUF_SIZE-1); + memcpy(buffer, reason, size); + buffer[size] = '\0'; + pr_err("%s: subsystem failure reason: %s", MODULE_NAME, buffer); + memset((void *)reason, 0x0, size); + wmb(); +} + +static void lpass_fatal_fn(struct work_struct *work) +{ + pr_err("%s %s: Watchdog bite received from Q6!\n", MODULE_NAME, + __func__); + lpass_log_failure_reason(); + panic(MODULE_NAME ": Resetting the SoC"); +} + +static void lpass_smsm_state_cb(void *data, uint32_t old_state, + uint32_t new_state) +{ + /* Ignore if we're the one that set SMSM_RESET */ + if (q6_crash_shutdown) + return; + + if (new_state & SMSM_RESET) { + pr_err("%s: LPASS SMSM state changed to SMSM_RESET," + " new_state = 0x%x, old_state = 0x%x\n", __func__, + new_state, old_state); + lpass_log_failure_reason(); + panic(MODULE_NAME ": Resetting the SoC"); + } +} + +static void send_q6_nmi(void) +{ + /* Send NMI to QDSP6 via an SCM call. */ + uint32_t cmd = 0x1; + + scm_call(SCM_SVC_UTIL, SCM_Q6_NMI_CMD, + &cmd, sizeof(cmd), NULL, 0); + + /* Q6 requires worstcase 100ms to dump caches etc.*/ + mdelay(100); + pr_debug("%s: Q6 NMI was sent.\n", __func__); +} + +static int lpass_shutdown(const struct subsys_data *subsys) +{ + send_q6_nmi(); + pil_force_shutdown("q6"); + disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED); + + return 0; +} + +static int lpass_powerup(const struct subsys_data *subsys) +{ + int ret = pil_force_boot("q6"); + enable_irq(LPASS_Q6SS_WDOG_EXPIRED); + return ret; +} +/* RAM segments - address and size for 8960 */ +static struct ramdump_segment q6_segments[] = { {0x8da00000, 0x8f200000 - + 0x8da00000}, {0x28400000, 0x20000} }; +static int lpass_ramdump(int enable, const struct subsys_data *subsys) +{ + pr_debug("%s: enable[%d]\n", __func__, enable); + if (enable) + return do_ramdump(lpass_ssr_8960.lpass_ramdump_dev, + q6_segments, + ARRAY_SIZE(q6_segments)); + else + return 0; +} + +static void lpass_crash_shutdown(const struct subsys_data *subsys) +{ + q6_crash_shutdown = 1; + send_q6_nmi(); +} + +static irqreturn_t lpass_wdog_bite_irq(int irq, void *dev_id) +{ + int ret; + + pr_debug("%s: rxed irq[0x%x]", __func__, irq); + disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED); + ret = schedule_work(&lpass_fatal_work); + + return IRQ_HANDLED; +} + +static struct subsys_data lpass_8960 = { + .name = "lpass", + .shutdown = lpass_shutdown, + .powerup = lpass_powerup, + .ramdump = lpass_ramdump, + .crash_shutdown = lpass_crash_shutdown +}; + +static int __init lpass_restart_init(void) +{ + return ssr_register_subsystem(&lpass_8960); +} + +static int __init lpass_fatal_init(void) +{ + int ret; + + ret = smsm_state_cb_register(SMSM_Q6_STATE, SMSM_RESET, + lpass_smsm_state_cb, 0); + + if (ret < 0) + pr_err("%s: Unable to register SMSM callback! (%d)\n", + __func__, ret); + + ret = request_irq(LPASS_Q6SS_WDOG_EXPIRED, lpass_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; + } + ret = lpass_restart_init(); + if (ret < 0) { + pr_err("%s: Unable to reg with lpass ssr. (%d)\n", + __func__, ret); + goto out; + } + + lpass_ssr_8960.lpass_ramdump_dev = create_ramdump_device("lpass"); + + if (!lpass_ssr_8960.lpass_ramdump_dev) { + pr_err("%s: Unable to create ramdump device.\n", + __func__); + ret = -ENOMEM; + goto out; + } + ssr_notif_hdle = subsys_notif_register_notifier("riva", + &rnb); + if (IS_ERR(ssr_notif_hdle) < 0) { + ret = PTR_ERR(ssr_notif_hdle); + pr_err("%s: subsys_register_notifier for Riva: err = %d\n", + __func__, ret); + free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL); + goto out; + } + + ssr_modem_notif_hdle = subsys_notif_register_notifier("modem", + &mnb); + if (IS_ERR(ssr_modem_notif_hdle) < 0) { + ret = PTR_ERR(ssr_modem_notif_hdle); + pr_err("%s: subsys_register_notifier for Modem: err = %d\n", + __func__, ret); + subsys_notif_unregister_notifier(ssr_notif_hdle, &rnb); + free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL); + goto out; + } + + pr_info("%s: lpass SSR driver init'ed.\n", __func__); +out: + return ret; +} + +static void __exit lpass_fatal_exit(void) +{ + subsys_notif_unregister_notifier(ssr_notif_hdle, &rnb); + subsys_notif_unregister_notifier(ssr_modem_notif_hdle, &mnb); + free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL); +} + +module_init(lpass_fatal_init); +module_exit(lpass_fatal_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/lpm_levels.c b/arch/arm/mach-msm/lpm_levels.c new file mode 100644 index 0000000000000000000000000000000000000000..80b82cbcf6219a651f158f7fff3ef804de54ee1e --- /dev/null +++ b/arch/arm/mach-msm/lpm_levels.c @@ -0,0 +1,243 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "rpm_resources.h" +#include "pm.h" + +static struct msm_rpmrs_level *msm_lpm_levels; +static int msm_lpm_level_count; + +static int msm_lpm_enter_sleep(uint32_t sclk_count, void *limits, + bool from_idle, bool notify_rpm) +{ + /* TODO */ + return 0; +} + +static void msm_lpm_exit_sleep(void *limits, bool from_idle, + bool notify_rpm, bool collapsed) +{ + /* TODO */ + return; +} + +static bool msm_rpmrs_irqs_detectable(struct msm_rpmrs_limits *limits, + bool irqs_detect, bool gpio_detect) +{ + /* TODO */ + return true; +} + +void msm_rpmrs_show_resources(void) +{ + /* TODO */ + return; +} + +static void *msm_lpm_lowest_limits(bool from_idle, + enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us, + uint32_t sleep_us, uint32_t *power) +{ + unsigned int cpu = smp_processor_id(); + struct msm_rpmrs_level *best_level = NULL; + bool irqs_detectable = false; + bool gpio_detectable = false; + uint32_t pwr; + int i; + + if (!msm_lpm_levels) + return NULL; + + 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_lpm_level_count; i++) { + struct msm_rpmrs_level *level = &msm_lpm_levels[i]; + + 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) { + pwr = level->energy_overhead; + } else if (sleep_us <= level->time_overhead_us) { + pwr = level->energy_overhead / sleep_us; + } else if ((sleep_us >> 10) > level->time_overhead_us) { + pwr = level->steady_state_power; + } else { + pwr = level->steady_state_power; + pwr -= (level->time_overhead_us * + level->steady_state_power)/sleep_us; + pwr += level->energy_overhead / sleep_us; + } + + if (!best_level || best_level->rs_limits.power[cpu] >= pwr) { + + level->rs_limits.latency_us[cpu] = level->latency_us; + level->rs_limits.power[cpu] = pwr; + best_level = level; + + if (power) + *power = pwr; + } + } + + return best_level ? &best_level->rs_limits : NULL; +} +static struct msm_pm_sleep_ops msm_lpm_ops = { + .lowest_limits = msm_lpm_lowest_limits, + .enter_sleep = msm_lpm_enter_sleep, + .exit_sleep = msm_lpm_exit_sleep, +}; + +static int __devinit msm_lpm_levels_probe(struct platform_device *pdev) +{ + struct msm_rpmrs_level *levels = NULL; + struct msm_rpmrs_level *level = NULL; + struct device_node *node = NULL; + char *key = NULL; + uint32_t val = 0; + int ret = 0; + uint32_t num_levels = 0; + int idx = 0; + + for_each_child_of_node(pdev->dev.of_node, node) + num_levels++; + + levels = kzalloc(num_levels * sizeof(struct msm_rpmrs_level), + GFP_KERNEL); + if (!levels) + return -ENOMEM; + + for_each_child_of_node(pdev->dev.of_node, node) { + level = &levels[idx++]; + level->available = false; + + key = "qcom,mode"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->sleep_mode = val; + + key = "qcom,xo"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->rs_limits.pxo = val; + + key = "qcom,l2"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->rs_limits.l2_cache = val; + + key = "qcom,vdd-dig-upper-bound"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->rs_limits.vdd_dig_upper_bound = val; + + key = "qcom,vdd-dig-lower-bound"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->rs_limits.vdd_dig = val; + + key = "qcom,vdd-mem-upper-bound"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->rs_limits.vdd_mem_upper_bound = val; + + key = "qcom,vdd-mem-lower-bound"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->rs_limits.vdd_mem = val; + + key = "qcom,latency-us"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->latency_us = val; + + key = "qcom,ss-power"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->steady_state_power = val; + + key = "qcom,energy-overhead"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->energy_overhead = val; + + key = "qcom,time-overhead"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + level->time_overhead_us = val; + + level->available = true; + } + + msm_lpm_levels = levels; + msm_lpm_level_count = idx; + + msm_pm_set_sleep_ops(&msm_lpm_ops); + + return 0; +fail: + pr_err("%s: Error in name %s key %s\n", __func__, node->full_name, key); + kfree(levels); + return -EFAULT; +} + +static struct of_device_id msm_lpm_levels_match_table[] = { + {.compatible = "qcom,lpm-levels"}, + {}, +}; + +static struct platform_driver msm_lpm_levels_driver = { + .probe = msm_lpm_levels_probe, + .driver = { + .name = "lpm-levels", + .owner = THIS_MODULE, + .of_match_table = msm_lpm_levels_match_table, + }, +}; + +static int __init msm_lpm_levels_module_init(void) +{ + return platform_driver_register(&msm_lpm_levels_driver); +} +late_initcall(msm_lpm_levels_module_init); diff --git a/arch/arm/mach-msm/mdm.c b/arch/arm/mach-msm/mdm.c new file mode 100644 index 0000000000000000000000000000000000000000..cbdc92ab7ff7aa86d84363ed8f05c79524044531 --- /dev/null +++ b/arch/arm/mach-msm/mdm.c @@ -0,0 +1,483 @@ +/* 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 +#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 int charm_subsys_shutdown(const struct subsys_data *crashed_subsys) +{ + charm_ready = 0; + power_down_charm(); + 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); + 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) +{ + int i; + + CHARM_DBG("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n", + __func__); + if (get_restart_level() == RESET_SOC) + pm8xxx_stay_on(); + + charm_disable_irqs(); + gpio_set_value(AP2MDM_ERRFATAL, 1); + gpio_set_value(AP2MDM_WAKEUP, 1); + for (i = CHARM_MODEM_TIMEOUT; i > 0; i -= CHARM_MODEM_DELTA) { + pet_watchdog(); + mdelay(CHARM_MODEM_DELTA); + if (gpio_get_value(MDM2AP_STATUS) == 0) + break; + } + if (i <= 0) + pr_err("%s: MDM2AP_STATUS never went low\n", __func__); + return NOTIFY_DONE; +} + +static struct notifier_block charm_panic_blk = { + .notifier_call = charm_panic_prep, +}; + +static int first_boot = 1; + +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; + charm_ready = 1; + + gpio_set_value(AP2MDM_KPDPWR_N, 0); + if (!first_boot) + complete(&charm_boot); + else + first_boot = 0; + 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"); + if (get_restart_level() == RESET_SOC) + pm8xxx_stay_on(); + 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 && (gpio_get_value(MDM2AP_STATUS) == 1)) { + 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__); + } + 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_request(AP2MDM_WAKEUP, "AP2MDM_WAKEUP"); + + gpio_direction_output(AP2MDM_STATUS, 1); + gpio_direction_output(AP2MDM_ERRFATAL, 0); + gpio_direction_output(AP2MDM_WAKEUP, 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); + gpio_set_value(AP2MDM_WAKEUP, 1); + + 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); + } + gpio_set_value(AP2MDM_WAKEUP, 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/mdm2.c b/arch/arm/mach-msm/mdm2.c new file mode 100644 index 0000000000000000000000000000000000000000..bd7bd9e9fc19814eca2a2906376f4b05bd4f6b89 --- /dev/null +++ b/arch/arm/mach-msm/mdm2.c @@ -0,0 +1,237 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm_watchdog.h" +#include "devices.h" +#include "clock.h" +#include "mdm_private.h" + +#define MDM_MODEM_TIMEOUT 6000 +#define MDM_HOLD_TIME 4000 +#define MDM_MODEM_DELTA 100 + +static int mdm_debug_on; +static int power_on_count; +static int hsic_peripheral_status; +static DEFINE_MUTEX(hsic_status_lock); + +static void mdm_peripheral_connect(struct mdm_modem_drv *mdm_drv) +{ + if (!mdm_drv->pdata->peripheral_platform_device) + return; + + mutex_lock(&hsic_status_lock); + if (hsic_peripheral_status) + goto out; + platform_device_add(mdm_drv->pdata->peripheral_platform_device); + hsic_peripheral_status = 1; +out: + mutex_unlock(&hsic_status_lock); +} + +static void mdm_peripheral_disconnect(struct mdm_modem_drv *mdm_drv) +{ + if (!mdm_drv->pdata->peripheral_platform_device) + return; + + mutex_lock(&hsic_status_lock); + if (!hsic_peripheral_status) + goto out; + platform_device_del(mdm_drv->pdata->peripheral_platform_device); + hsic_peripheral_status = 0; +out: + mutex_unlock(&hsic_status_lock); +} + +static void mdm_power_down_common(struct mdm_modem_drv *mdm_drv) +{ + int soft_reset_direction = + mdm_drv->pdata->soft_reset_inverted ? 1 : 0; + + gpio_direction_output(mdm_drv->ap2mdm_soft_reset_gpio, + soft_reset_direction); + mdm_peripheral_disconnect(mdm_drv); +} + +static void mdm_do_first_power_on(struct mdm_modem_drv *mdm_drv) +{ + int soft_reset_direction = + mdm_drv->pdata->soft_reset_inverted ? 0 : 1; + + if (power_on_count != 1) { + pr_err("%s: Calling fn when power_on_count != 1\n", + __func__); + return; + } + + pr_err("%s: Powering on modem for the first time\n", __func__); + mdm_peripheral_disconnect(mdm_drv); + + /* If the device has a kpd pwr gpio then toggle it. */ + if (mdm_drv->ap2mdm_kpdpwr_n_gpio > 0) { + /* Pull AP2MDM_KPDPWR gpio high and wait for PS_HOLD to settle, + * then pull it back low. + */ + pr_debug("%s: Pulling AP2MDM_KPDPWR gpio high\n", __func__); + gpio_direction_output(mdm_drv->ap2mdm_kpdpwr_n_gpio, 1); + msleep(1000); + gpio_direction_output(mdm_drv->ap2mdm_kpdpwr_n_gpio, 0); + } + + /* De-assert the soft reset line. */ + pr_debug("%s: De-asserting soft reset gpio\n", __func__); + gpio_direction_output(mdm_drv->ap2mdm_soft_reset_gpio, + soft_reset_direction); + + mdm_peripheral_connect(mdm_drv); + msleep(200); +} + +static void mdm_do_soft_power_on(struct mdm_modem_drv *mdm_drv) +{ + int soft_reset_direction = + mdm_drv->pdata->soft_reset_inverted ? 0 : 1; + + /* De-assert the soft reset line. */ + pr_err("%s: soft resetting mdm modem\n", __func__); + + mdm_peripheral_disconnect(mdm_drv); + + gpio_direction_output(mdm_drv->ap2mdm_soft_reset_gpio, + soft_reset_direction == 1 ? 0 : 1); + usleep_range(5000, 10000); + gpio_direction_output(mdm_drv->ap2mdm_soft_reset_gpio, + soft_reset_direction == 1 ? 1 : 0); + + mdm_peripheral_connect(mdm_drv); + msleep(200); +} + +static void mdm_power_on_common(struct mdm_modem_drv *mdm_drv) +{ + power_on_count++; + + /* this gpio will be used to indicate apq readiness, + * de-assert it now so that it can be asserted later. + * May not be used. + */ + if (mdm_drv->ap2mdm_wakeup_gpio > 0) + gpio_direction_output(mdm_drv->ap2mdm_wakeup_gpio, 0); + + /* + * If we did an "early power on" then ignore the very next + * power-on request because it would the be first request from + * user space but we're already powered on. Ignore it. + */ + if (mdm_drv->pdata->early_power_on && + (power_on_count == 2)) + return; + + if (power_on_count == 1) + mdm_do_first_power_on(mdm_drv); + else + mdm_do_soft_power_on(mdm_drv); +} + +static void debug_state_changed(int value) +{ + mdm_debug_on = value; +} + +static void mdm_status_changed(struct mdm_modem_drv *mdm_drv, int value) +{ + pr_debug("%s: value:%d\n", __func__, value); + + if (value) { + mdm_peripheral_disconnect(mdm_drv); + mdm_peripheral_connect(mdm_drv); + if (mdm_drv->ap2mdm_wakeup_gpio > 0) + gpio_direction_output(mdm_drv->ap2mdm_wakeup_gpio, 1); + } +} + +static struct mdm_ops mdm_cb = { + .power_on_mdm_cb = mdm_power_on_common, + .reset_mdm_cb = mdm_power_on_common, + .power_down_mdm_cb = mdm_power_down_common, + .debug_state_changed_cb = debug_state_changed, + .status_cb = mdm_status_changed, +}; + +static int __init mdm_modem_probe(struct platform_device *pdev) +{ + return mdm_common_create(pdev, &mdm_cb); +} + +static int __devexit mdm_modem_remove(struct platform_device *pdev) +{ + return mdm_common_modem_remove(pdev); +} + +static void mdm_modem_shutdown(struct platform_device *pdev) +{ + mdm_common_modem_shutdown(pdev); +} + +static struct platform_driver mdm_modem_driver = { + .remove = mdm_modem_remove, + .shutdown = mdm_modem_shutdown, + .driver = { + .name = "mdm2_modem", + .owner = THIS_MODULE + }, +}; + +static int __init mdm_modem_init(void) +{ + return platform_driver_probe(&mdm_modem_driver, mdm_modem_probe); +} + +static void __exit mdm_modem_exit(void) +{ + platform_driver_unregister(&mdm_modem_driver); +} + +module_init(mdm_modem_init); +module_exit(mdm_modem_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("mdm modem driver"); +MODULE_VERSION("2.0"); +MODULE_ALIAS("mdm_modem"); diff --git a/arch/arm/mach-msm/mdm_common.c b/arch/arm/mach-msm/mdm_common.c new file mode 100644 index 0000000000000000000000000000000000000000..ffff78222fd9cebcf9411c70a24c43d29eda53d6 --- /dev/null +++ b/arch/arm/mach-msm/mdm_common.c @@ -0,0 +1,607 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm_watchdog.h" +#include "mdm_private.h" +#include "sysmon.h" + +#define MDM_MODEM_TIMEOUT 6000 +#define MDM_MODEM_DELTA 100 +#define MDM_BOOT_TIMEOUT 60000L +#define MDM_RDUMP_TIMEOUT 60000L + +static int mdm_debug_on; +static struct workqueue_struct *mdm_queue; +static struct workqueue_struct *mdm_sfr_queue; + +#define EXTERNAL_MODEM "external_modem" + +static struct mdm_modem_drv *mdm_drv; + +DECLARE_COMPLETION(mdm_needs_reload); +DECLARE_COMPLETION(mdm_boot); +DECLARE_COMPLETION(mdm_ram_dumps); + +static int first_boot = 1; + +#define RD_BUF_SIZE 100 +#define SFR_MAX_RETRIES 10 +#define SFR_RETRY_INTERVAL 1000 + +static void mdm_restart_reason_fn(struct work_struct *work) +{ + int ret, ntries = 0; + char sfr_buf[RD_BUF_SIZE]; + + do { + msleep(SFR_RETRY_INTERVAL); + ret = sysmon_get_reason(SYSMON_SS_EXT_MODEM, + sfr_buf, sizeof(sfr_buf)); + if (ret) { + /* + * The sysmon device may not have been probed as yet + * after the restart. + */ + pr_err("%s: Error retrieving mdm restart reason, ret = %d, " + "%d/%d tries\n", __func__, ret, + ntries + 1, SFR_MAX_RETRIES); + } else { + pr_err("mdm restart reason: %s\n", sfr_buf); + break; + } + } while (++ntries < SFR_MAX_RETRIES); +} + +static DECLARE_WORK(sfr_reason_work, mdm_restart_reason_fn); + +long mdm_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; + } + + pr_debug("%s: Entering ioctl cmd = %d\n", __func__, _IOC_NR(cmd)); + switch (cmd) { + case WAKE_CHARM: + pr_info("%s: Powering on mdm\n", __func__); + mdm_drv->ops->power_on_mdm_cb(mdm_drv); + break; + case CHECK_FOR_BOOT: + if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) + put_user(1, (unsigned long __user *) arg); + else + put_user(0, (unsigned long __user *) arg); + break; + case NORMAL_BOOT_DONE: + pr_debug("%s: check if mdm is booted up\n", __func__); + get_user(status, (unsigned long __user *) arg); + if (status) { + pr_debug("%s: normal boot failed\n", __func__); + mdm_drv->mdm_boot_status = -EIO; + } else { + pr_info("%s: normal boot done\n", __func__); + mdm_drv->mdm_boot_status = 0; + } + mdm_drv->mdm_ready = 1; + + if (mdm_drv->ops->normal_boot_done_cb != NULL) + mdm_drv->ops->normal_boot_done_cb(mdm_drv); + + if (!first_boot) + complete(&mdm_boot); + else + first_boot = 0; + break; + case RAM_DUMP_DONE: + pr_debug("%s: mdm done collecting RAM dumps\n", __func__); + get_user(status, (unsigned long __user *) arg); + if (status) + mdm_drv->mdm_ram_dump_status = -EIO; + else { + pr_info("%s: ramdump collection completed\n", __func__); + mdm_drv->mdm_ram_dump_status = 0; + } + complete(&mdm_ram_dumps); + break; + case WAIT_FOR_RESTART: + pr_debug("%s: wait for mdm to need images reloaded\n", + __func__); + ret = wait_for_completion_interruptible(&mdm_needs_reload); + if (!ret) + put_user(mdm_drv->boot_type, + (unsigned long __user *) arg); + INIT_COMPLETION(mdm_needs_reload); + break; + case GET_DLOAD_STATUS: + pr_debug("getting status of mdm2ap_errfatal_gpio\n"); + if (gpio_get_value(mdm_drv->mdm2ap_errfatal_gpio) == 1 && + !mdm_drv->mdm_ready) + put_user(1, (unsigned long __user *) arg); + else + put_user(0, (unsigned long __user *) arg); + break; + default: + pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd)); + ret = -EINVAL; + break; + } + + return ret; +} + +static void mdm_status_fn(struct work_struct *work) +{ + int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio); + + pr_debug("%s: status:%d\n", __func__, value); + if (mdm_drv->mdm_ready && mdm_drv->ops->status_cb) + mdm_drv->ops->status_cb(mdm_drv, value); +} + +static DECLARE_WORK(mdm_status_work, mdm_status_fn); + +static void mdm_disable_irqs(void) +{ + disable_irq_nosync(mdm_drv->mdm_errfatal_irq); + disable_irq_nosync(mdm_drv->mdm_status_irq); +} + +static irqreturn_t mdm_errfatal(int irq, void *dev_id) +{ + pr_debug("%s: mdm got errfatal interrupt\n", __func__); + if (mdm_drv->mdm_ready && + (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 1)) { + pr_info("%s: Reseting the mdm due to an errfatal\n", __func__); + mdm_drv->mdm_ready = 0; + subsystem_restart(EXTERNAL_MODEM); + } + return IRQ_HANDLED; +} + +static int mdm_modem_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations mdm_modem_fops = { + .owner = THIS_MODULE, + .open = mdm_modem_open, + .unlocked_ioctl = mdm_modem_ioctl, +}; + + +static struct miscdevice mdm_modem_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mdm", + .fops = &mdm_modem_fops +}; + +static int mdm_panic_prep(struct notifier_block *this, + unsigned long event, void *ptr) +{ + int i; + + pr_debug("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n", + __func__); + mdm_disable_irqs(); + gpio_set_value(mdm_drv->ap2mdm_errfatal_gpio, 1); + + for (i = MDM_MODEM_TIMEOUT; i > 0; i -= MDM_MODEM_DELTA) { + pet_watchdog(); + mdelay(MDM_MODEM_DELTA); + if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) + break; + } + if (i <= 0) { + pr_err("%s: MDM2AP_STATUS never went low\n", __func__); + /* Reset the modem so that it will go into download mode. */ + if (mdm_drv && mdm_drv->ops->reset_mdm_cb) + mdm_drv->ops->reset_mdm_cb(mdm_drv); + } + return NOTIFY_DONE; +} + +static struct notifier_block mdm_panic_blk = { + .notifier_call = mdm_panic_prep, +}; + +static irqreturn_t mdm_status_change(int irq, void *dev_id) +{ + int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio); + + pr_debug("%s: mdm sent status change interrupt\n", __func__); + if (value == 0 && mdm_drv->mdm_ready == 1) { + pr_info("%s: unexpected reset external modem\n", __func__); + mdm_drv->mdm_unexpected_reset_occurred = 1; + mdm_drv->mdm_ready = 0; + subsystem_restart(EXTERNAL_MODEM); + } else if (value == 1) { + pr_info("%s: status = 1: mdm is now ready\n", __func__); + queue_work(mdm_queue, &mdm_status_work); + } + return IRQ_HANDLED; +} + +static int mdm_subsys_shutdown(const struct subsys_data *crashed_subsys) +{ + gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 1); + if (mdm_drv->pdata->ramdump_delay_ms > 0) { + /* Wait for the external modem to complete + * its preparation for ramdumps. + */ + msleep(mdm_drv->pdata->ramdump_delay_ms); + } + if (!mdm_drv->mdm_unexpected_reset_occurred) + mdm_drv->ops->reset_mdm_cb(mdm_drv); + else + mdm_drv->mdm_unexpected_reset_occurred = 0; + + return 0; +} + +static int mdm_subsys_powerup(const struct subsys_data *crashed_subsys) +{ + gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0); + gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1); + mdm_drv->ops->power_on_mdm_cb(mdm_drv); + mdm_drv->boot_type = CHARM_NORMAL_BOOT; + complete(&mdm_needs_reload); + if (!wait_for_completion_timeout(&mdm_boot, + msecs_to_jiffies(MDM_BOOT_TIMEOUT))) { + mdm_drv->mdm_boot_status = -ETIMEDOUT; + pr_info("%s: mdm modem restart timed out.\n", __func__); + } else { + pr_info("%s: mdm modem has been restarted\n", __func__); + + /* Log the reason for the restart */ + if (mdm_drv->pdata->sfr_query) + queue_work(mdm_sfr_queue, &sfr_reason_work); + } + INIT_COMPLETION(mdm_boot); + return mdm_drv->mdm_boot_status; +} + +static int mdm_subsys_ramdumps(int want_dumps, + const struct subsys_data *crashed_subsys) +{ + mdm_drv->mdm_ram_dump_status = 0; + if (want_dumps) { + mdm_drv->boot_type = CHARM_RAM_DUMPS; + complete(&mdm_needs_reload); + if (!wait_for_completion_timeout(&mdm_ram_dumps, + msecs_to_jiffies(MDM_RDUMP_TIMEOUT))) { + mdm_drv->mdm_ram_dump_status = -ETIMEDOUT; + pr_info("%s: mdm modem ramdumps timed out.\n", + __func__); + } else + pr_info("%s: mdm modem ramdumps completed.\n", + __func__); + INIT_COMPLETION(mdm_ram_dumps); + mdm_drv->ops->power_down_mdm_cb(mdm_drv); + } + return mdm_drv->mdm_ram_dump_status; +} + +static struct subsys_data mdm_subsystem = { + .shutdown = mdm_subsys_shutdown, + .ramdump = mdm_subsys_ramdumps, + .powerup = mdm_subsys_powerup, + .name = EXTERNAL_MODEM, +}; + +static int mdm_debug_on_set(void *data, u64 val) +{ + mdm_debug_on = val; + if (mdm_drv->ops->debug_state_changed_cb) + mdm_drv->ops->debug_state_changed_cb(mdm_debug_on); + return 0; +} + +static int mdm_debug_on_get(void *data, u64 *val) +{ + *val = mdm_debug_on; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(mdm_debug_on_fops, + mdm_debug_on_get, + mdm_debug_on_set, "%llu\n"); + +static int mdm_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("mdm_dbg", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("debug_on", 0644, dent, NULL, + &mdm_debug_on_fops); + return 0; +} + +static void mdm_modem_initialize_data(struct platform_device *pdev, + struct mdm_ops *mdm_ops) +{ + struct resource *pres; + + /* MDM2AP_ERRFATAL */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "MDM2AP_ERRFATAL"); + if (pres) + mdm_drv->mdm2ap_errfatal_gpio = pres->start; + + /* AP2MDM_ERRFATAL */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "AP2MDM_ERRFATAL"); + if (pres) + mdm_drv->ap2mdm_errfatal_gpio = pres->start; + + /* MDM2AP_STATUS */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "MDM2AP_STATUS"); + if (pres) + mdm_drv->mdm2ap_status_gpio = pres->start; + + /* AP2MDM_STATUS */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "AP2MDM_STATUS"); + if (pres) + mdm_drv->ap2mdm_status_gpio = pres->start; + + /* MDM2AP_WAKEUP */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "MDM2AP_WAKEUP"); + if (pres) + mdm_drv->mdm2ap_wakeup_gpio = pres->start; + + /* AP2MDM_WAKEUP */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "AP2MDM_WAKEUP"); + if (pres) + mdm_drv->ap2mdm_wakeup_gpio = pres->start; + + /* AP2MDM_SOFT_RESET */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "AP2MDM_SOFT_RESET"); + if (pres) + mdm_drv->ap2mdm_soft_reset_gpio = pres->start; + + /* AP2MDM_KPDPWR_N */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "AP2MDM_KPDPWR_N"); + if (pres) + mdm_drv->ap2mdm_kpdpwr_n_gpio = pres->start; + + /* AP2MDM_PMIC_PWR_EN */ + pres = platform_get_resource_byname(pdev, IORESOURCE_IO, + "AP2MDM_PMIC_PWR_EN"); + if (pres) + mdm_drv->ap2mdm_pmic_pwr_en_gpio = pres->start; + + mdm_drv->boot_type = CHARM_NORMAL_BOOT; + + mdm_drv->ops = mdm_ops; + mdm_drv->pdata = pdev->dev.platform_data; +} + +int mdm_common_create(struct platform_device *pdev, + struct mdm_ops *p_mdm_cb) +{ + int ret = -1, irq; + + mdm_drv = kzalloc(sizeof(struct mdm_modem_drv), GFP_KERNEL); + if (mdm_drv == NULL) { + pr_err("%s: kzalloc fail.\n", __func__); + goto alloc_err; + } + + mdm_modem_initialize_data(pdev, p_mdm_cb); + if (mdm_drv->ops->debug_state_changed_cb) + mdm_drv->ops->debug_state_changed_cb(mdm_debug_on); + + gpio_request(mdm_drv->ap2mdm_status_gpio, "AP2MDM_STATUS"); + gpio_request(mdm_drv->ap2mdm_errfatal_gpio, "AP2MDM_ERRFATAL"); + if (mdm_drv->ap2mdm_kpdpwr_n_gpio > 0) + gpio_request(mdm_drv->ap2mdm_kpdpwr_n_gpio, "AP2MDM_KPDPWR_N"); + gpio_request(mdm_drv->mdm2ap_status_gpio, "MDM2AP_STATUS"); + gpio_request(mdm_drv->mdm2ap_errfatal_gpio, "MDM2AP_ERRFATAL"); + + if (mdm_drv->ap2mdm_pmic_pwr_en_gpio > 0) + gpio_request(mdm_drv->ap2mdm_pmic_pwr_en_gpio, + "AP2MDM_PMIC_PWR_EN"); + if (mdm_drv->ap2mdm_soft_reset_gpio > 0) + gpio_request(mdm_drv->ap2mdm_soft_reset_gpio, + "AP2MDM_SOFT_RESET"); + + if (mdm_drv->ap2mdm_wakeup_gpio > 0) + gpio_request(mdm_drv->ap2mdm_wakeup_gpio, "AP2MDM_WAKEUP"); + + gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1); + gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0); + + if (mdm_drv->ap2mdm_wakeup_gpio > 0) + gpio_direction_output(mdm_drv->ap2mdm_wakeup_gpio, 0); + + gpio_direction_input(mdm_drv->mdm2ap_status_gpio); + gpio_direction_input(mdm_drv->mdm2ap_errfatal_gpio); + + mdm_queue = create_singlethread_workqueue("mdm_queue"); + if (!mdm_queue) { + pr_err("%s: could not create workqueue. All mdm " + "functionality will be disabled\n", + __func__); + ret = -ENOMEM; + goto fatal_err; + } + + mdm_sfr_queue = alloc_workqueue("mdm_sfr_queue", 0, 0); + if (!mdm_sfr_queue) { + pr_err("%s: could not create workqueue mdm_sfr_queue." + " All mdm functionality will be disabled\n", + __func__); + ret = -ENOMEM; + destroy_workqueue(mdm_queue); + goto fatal_err; + } + + atomic_notifier_chain_register(&panic_notifier_list, &mdm_panic_blk); + mdm_debugfs_init(); + + /* Register subsystem handlers */ + ssr_register_subsystem(&mdm_subsystem); + + /* ERR_FATAL irq. */ + irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_errfatal_gpio); + 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, mdm_errfatal, + IRQF_TRIGGER_RISING , "mdm 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; + } + mdm_drv->mdm_errfatal_irq = irq; + +errfatal_err: + + /* status irq */ + irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_status_gpio); + 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, mdm_status_change, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, + "mdm status", mdm_drv); + + 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; + } + mdm_drv->mdm_status_irq = irq; + +status_err: + /* + * If AP2MDM_PMIC_PWR_EN gpio is used, pull it high. It remains + * high until the whole phone is shut down. + */ + if (mdm_drv->ap2mdm_pmic_pwr_en_gpio > 0) + gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 1); + + /* Perform early powerup of the external modem in order to + * allow tabla devices to be found. + */ + if (mdm_drv->pdata->early_power_on) + mdm_drv->ops->power_on_mdm_cb(mdm_drv); + + pr_info("%s: Registering mdm modem\n", __func__); + return misc_register(&mdm_modem_misc); + +fatal_err: + gpio_free(mdm_drv->ap2mdm_status_gpio); + gpio_free(mdm_drv->ap2mdm_errfatal_gpio); + if (mdm_drv->ap2mdm_kpdpwr_n_gpio > 0) + gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio); + if (mdm_drv->ap2mdm_pmic_pwr_en_gpio > 0) + gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio); + gpio_free(mdm_drv->mdm2ap_status_gpio); + gpio_free(mdm_drv->mdm2ap_errfatal_gpio); + if (mdm_drv->ap2mdm_soft_reset_gpio > 0) + gpio_free(mdm_drv->ap2mdm_soft_reset_gpio); + + if (mdm_drv->ap2mdm_wakeup_gpio > 0) + gpio_free(mdm_drv->ap2mdm_wakeup_gpio); + + kfree(mdm_drv); + ret = -ENODEV; + +alloc_err: + return ret; +} + +int mdm_common_modem_remove(struct platform_device *pdev) +{ + int ret; + + gpio_free(mdm_drv->ap2mdm_status_gpio); + gpio_free(mdm_drv->ap2mdm_errfatal_gpio); + if (mdm_drv->ap2mdm_kpdpwr_n_gpio > 0) + gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio); + if (mdm_drv->ap2mdm_pmic_pwr_en_gpio > 0) + gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio); + gpio_free(mdm_drv->mdm2ap_status_gpio); + gpio_free(mdm_drv->mdm2ap_errfatal_gpio); + if (mdm_drv->ap2mdm_soft_reset_gpio > 0) + gpio_free(mdm_drv->ap2mdm_soft_reset_gpio); + + if (mdm_drv->ap2mdm_wakeup_gpio > 0) + gpio_free(mdm_drv->ap2mdm_wakeup_gpio); + + kfree(mdm_drv); + + ret = misc_deregister(&mdm_modem_misc); + return ret; +} + +void mdm_common_modem_shutdown(struct platform_device *pdev) +{ + mdm_disable_irqs(); + + mdm_drv->ops->power_down_mdm_cb(mdm_drv); + if (mdm_drv->ap2mdm_pmic_pwr_en_gpio > 0) + gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 0); +} + diff --git a/arch/arm/mach-msm/mdm_private.h b/arch/arm/mach-msm/mdm_private.h new file mode 100644 index 0000000000000000000000000000000000000000..f157d88341f3da574bd49e85a57a0b5548c53b0b --- /dev/null +++ b/arch/arm/mach-msm/mdm_private.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PRIVATE_H +#define _ARCH_ARM_MACH_MSM_MDM_PRIVATE_H + +struct mdm_modem_drv; + +struct mdm_ops { + void (*power_on_mdm_cb)(struct mdm_modem_drv *mdm_drv); + void (*reset_mdm_cb)(struct mdm_modem_drv *mdm_drv); + void (*normal_boot_done_cb)(struct mdm_modem_drv *mdm_drv); + void (*power_down_mdm_cb)(struct mdm_modem_drv *mdm_drv); + void (*debug_state_changed_cb)(int value); + void (*status_cb)(struct mdm_modem_drv *mdm_drv, int value); +}; + +/* Private mdm2 data structure */ +struct mdm_modem_drv { + unsigned mdm2ap_errfatal_gpio; + unsigned ap2mdm_errfatal_gpio; + unsigned mdm2ap_status_gpio; + unsigned ap2mdm_status_gpio; + unsigned mdm2ap_wakeup_gpio; + unsigned ap2mdm_wakeup_gpio; + unsigned ap2mdm_kpdpwr_n_gpio; + unsigned ap2mdm_soft_reset_gpio; + unsigned ap2mdm_pmic_pwr_en_gpio; + + int mdm_errfatal_irq; + int mdm_status_irq; + int mdm_ready; + int mdm_boot_status; + int mdm_ram_dump_status; + enum charm_boot_type boot_type; + int mdm_debug_on; + int mdm_unexpected_reset_occurred; + + struct mdm_ops *ops; + struct mdm_platform_data *pdata; +}; + +int mdm_common_create(struct platform_device *pdev, + struct mdm_ops *mdm_cb); +int mdm_common_modem_remove(struct platform_device *pdev); +void mdm_common_modem_shutdown(struct platform_device *pdev); +void mdm_common_set_debug_state(int value); + +#endif + diff --git a/arch/arm/mach-msm/memory.c b/arch/arm/mach-msm/memory.c new file mode 100644 index 0000000000000000000000000000000000000000..40845d78c2e7e9837de222742c39b8544dadebf9 --- /dev/null +++ b/arch/arm/mach-msm/memory.c @@ -0,0 +1,441 @@ +/* arch/arm/mach-msm/memory.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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 + +/* fixme */ +#include +#include <../../mm/mm.h> +#include + +void *strongly_ordered_page; +char strongly_ordered_mem[PAGE_SIZE*2-4]; + +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); + +/* These cache related routines make the assumption (if outer cache is + * available) 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) +{ + dmac_flush_range((void *)vstart, (void *) (vstart + length)); + outer_flush_range(pstart, pstart + length); +} + +void clean_caches(unsigned long vstart, + unsigned long length, unsigned long pstart) +{ + dmac_clean_range((void *)vstart, (void *) (vstart + length)); + outer_clean_range(pstart, pstart + length); +} + +void invalidate_caches(unsigned long vstart, + unsigned long length, unsigned long pstart) +{ + dmac_inv_range((void *)vstart, (void *) (vstart + length)); + outer_inv_range(pstart, pstart + length); +} + +void * __init 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 (*change_memory_power)(u64, u64, int); + +int platform_physical_remove_pages(u64 start, u64 size) +{ + if (!change_memory_power) + return 0; + return change_memory_power(start, size, MEMORY_DEEP_POWERDOWN); +} + +int platform_physical_active_pages(u64 start, u64 size) +{ + if (!change_memory_power) + return 0; + return change_memory_power(start, size, MEMORY_ACTIVE); +} + +int platform_physical_low_power_pages(u64 start, u64 size) +{ + if (!change_memory_power) + return 0; + return change_memory_power(start, size, MEMORY_SELF_REFRESH); +} + +char *memtype_name[] = { + "SMI_KERNEL", + "SMI", + "EBI0", + "EBI1" +}; + +struct reserve_info *reserve_info; + +static unsigned long stable_size(struct membank *mb, + unsigned long unstable_limit) +{ + unsigned long upper_limit = mb->start + mb->size; + + if (!unstable_limit) + return mb->size; + + /* Check for 32 bit roll-over */ + if (upper_limit >= mb->start) { + /* If we didn't roll over we can safely make the check below */ + if (upper_limit <= unstable_limit) + return mb->size; + } + + if (mb->start >= unstable_limit) + return 0; + return unstable_limit - mb->start; +} + +/* stable size of all memory banks contiguous to and below this one */ +static unsigned long total_stable_size(unsigned long bank) +{ + int i; + struct membank *mb = &meminfo.bank[bank]; + int memtype = reserve_info->paddr_to_memtype(mb->start); + unsigned long size; + + size = stable_size(mb, reserve_info->low_unstable_address); + for (i = bank - 1, mb = &meminfo.bank[bank - 1]; i >= 0; i--, mb--) { + if (mb->start + mb->size != (mb + 1)->start) + break; + if (reserve_info->paddr_to_memtype(mb->start) != memtype) + break; + size += stable_size(mb, reserve_info->low_unstable_address); + } + return size; +} + +static void __init calculate_reserve_limits(void) +{ + int i; + struct membank *mb; + int memtype; + struct memtype_reserve *mt; + unsigned long size; + + 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]; + size = total_stable_size(i); + mt->limit = max(mt->limit, 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; + unsigned long size; + + 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 memory bank(s) of the proper size + * as we have limited the size of the memory pool for + * each memory type to the largest total size of the memory + * banks which are contiguous and of the correct memory type. + * 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. However, do not use unstable memory. + */ + 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; + size = total_stable_size(i); + if (size >= mt->size) { + size = stable_size(mb, + reserve_info->low_unstable_address); + if (!size) + continue; + /* mt->size may be larger than size, all this + * means is that we are carving the memory pool + * out of multiple contiguous memory banks. + */ + mt->start = mb->start + (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]); + } +} + +#define MAX_FIXED_AREA_SIZE 0x11000000 + +void __init msm_reserve(void) +{ + unsigned long msm_fixed_area_size; + unsigned long msm_fixed_area_start; + + memory_pool_init(); + reserve_info->calculate_reserve_sizes(); + + msm_fixed_area_size = reserve_info->fixed_area_size; + msm_fixed_area_start = reserve_info->fixed_area_start; + if (msm_fixed_area_size) + if (msm_fixed_area_start > reserve_info->low_unstable_address + - MAX_FIXED_AREA_SIZE) + reserve_info->low_unstable_address = + msm_fixed_area_start; + + 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, __builtin_return_address(0)); +} +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, + __builtin_return_address(0)); + + if (!paddr && pmem_memtype == PMEM_MEMTYPE_SMI) + paddr = _allocate_contiguous_memory_nomap(size, + ebi1_memtype, align, __builtin_return_address(0)); + + 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); + +unsigned int msm_ttbr0; + +void store_ttbr0(void) +{ + /* Store TTBR0 for post-mortem debugging purposes. */ + asm("mrc p15, 0, %0, c2, c0, 0\n" + : "=r" (msm_ttbr0)); +} + +int request_fmem_c_region(void *unused) +{ + return fmem_set_state(FMEM_C_STATE); +} + +int release_fmem_c_region(void *unused) +{ + return fmem_set_state(FMEM_T_STATE); +} diff --git a/arch/arm/mach-msm/memory_topology.c b/arch/arm/mach-msm/memory_topology.c new file mode 100644 index 0000000000000000000000000000000000000000..70aaf4a7ea35b225ae18f4b5ba7e06b1c3e6e898 --- /dev/null +++ b/arch/arm/mach-msm/memory_topology.c @@ -0,0 +1,268 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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_private.h" + +#if defined(CONFIG_ARCH_MSM8960) +#include "rpm_resources.h" +#endif + +static struct mem_region_t { + u64 start; + u64 size; + /* reserved for future use */ + u64 num_partitions; + int state; +} mem_regions[MAX_NR_REGIONS]; + +static struct mutex mem_regions_mutex; +static unsigned int nr_mem_regions; +static int mem_regions_mask; + +enum { + STATE_POWER_DOWN = 0x0, + STATE_ACTIVE = 0x2, + STATE_DEFAULT = STATE_ACTIVE +}; + +static int default_mask = ~0x0; + +/* Return the number of chipselects populated with a memory bank */ +/* This is 7x30 only and will be re-implemented in the future */ + +#if defined(CONFIG_ARCH_MSM7X30) +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; +} +#endif + +#if (defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_MSM8930)) \ + && defined(CONFIG_ENABLE_DMM) +static int rpm_change_memory_state(int retention_mask, + int active_mask) +{ + int ret; + struct msm_rpm_iv_pair cmd[2]; + struct msm_rpm_iv_pair status[2]; + + cmd[0].id = MSM_RPM_ID_DDR_DMM_0; + cmd[1].id = MSM_RPM_ID_DDR_DMM_1; + + status[0].id = MSM_RPM_STATUS_ID_DDR_DMM_0; + status[1].id = MSM_RPM_STATUS_ID_DDR_DMM_1; + + cmd[0].value = retention_mask; + cmd[1].value = active_mask; + + ret = msm_rpm_set(MSM_RPM_CTX_SET_0, cmd, 2); + if (ret < 0) { + pr_err("rpm set failed"); + return -EINVAL; + } + + ret = msm_rpm_get_status(status, 2); + if (ret < 0) { + pr_err("rpm status failed"); + return -EINVAL; + } + if (status[0].value == retention_mask && + status[1].value == active_mask) + return 0; + else { + pr_err("rpm failed to change memory state"); + return -EINVAL; + } +} + +static int switch_memory_state(int mask, int new_state, int start_region, + int end_region) +{ + int final_mask = 0; + int i; + + mutex_lock(&mem_regions_mutex); + + for (i = start_region; i <= end_region; i++) { + if (new_state == mem_regions[i].state) + goto no_change; + /* All region states must be the same to change them */ + if (mem_regions[i].state != mem_regions[start_region].state) + goto no_change; + } + + if (new_state == STATE_POWER_DOWN) + final_mask = mem_regions_mask & mask; + else if (new_state == STATE_ACTIVE) + final_mask = mem_regions_mask | ~mask; + else + goto no_change; + + pr_info("request memory %d to %d state switch (%d->%d)\n", + start_region, end_region, mem_regions[start_region].state, + new_state); + if (rpm_change_memory_state(final_mask, final_mask) == 0) { + for (i = start_region; i <= end_region; i++) + mem_regions[i].state = new_state; + mem_regions_mask = final_mask; + + pr_info("completed memory %d to %d state switch to %d\n", + start_region, end_region, new_state); + mutex_unlock(&mem_regions_mutex); + return 0; + } + + pr_err("failed memory %d to %d state switch (%d->%d)\n", + start_region, end_region, mem_regions[start_region].state, + new_state); + +no_change: + mutex_unlock(&mem_regions_mutex); + return -EINVAL; +} +#else + +static int switch_memory_state(int mask, int new_state, int start_region, + int end_region) +{ + return -EINVAL; +} +#endif + +/* The hotplug code expects the number of bytes that switched state successfully + * as the return value, so a return value of zero indicates an error +*/ +int soc_change_memory_power(u64 start, u64 size, int change) +{ + int i = 0; + int mask = default_mask; + u64 end = start + size; + int start_region = 0; + int end_region = 0; + + if (change != STATE_ACTIVE && change != STATE_POWER_DOWN) { + pr_info("requested state transition invalid\n"); + return 0; + } + /* Find the memory regions that fall within the range */ + for (i = 0; i < nr_mem_regions; i++) { + if (mem_regions[i].start <= start && + mem_regions[i].start >= + mem_regions[start_region].start) { + start_region = i; + } + if (end <= mem_regions[i].start + mem_regions[i].size) { + end_region = i; + break; + } + } + + /* Set the bitmask for each region in the range */ + for (i = start_region; i <= end_region; i++) + mask &= ~(0x1 << i); + + if (!switch_memory_state(mask, change, start_region, end_region)) + return size; + else + return 0; +} + +unsigned int get_num_memory_banks(void) +{ + return nr_mem_regions; +} + +unsigned int get_memory_bank_size(unsigned int id) +{ + BUG_ON(id >= nr_mem_regions); + return mem_regions[id].size; +} + +unsigned int get_memory_bank_start(unsigned int id) +{ + BUG_ON(id >= nr_mem_regions); + return mem_regions[id].start; +} + +int __init meminfo_init(unsigned int type, unsigned int min_bank_size) +{ + unsigned int i, j; + unsigned long bank_size; + unsigned long bank_start; + unsigned long region_size; + struct smem_ram_ptable *ram_ptable; + /* physical memory banks */ + unsigned int nr_mem_banks = 0; + /* logical memory regions for dmm */ + nr_mem_regions = 0; + + ram_ptable = smem_alloc(SMEM_USABLE_RAM_PARTITION_TABLE, + sizeof(struct smem_ram_ptable)); + + if (!ram_ptable) { + pr_err("Could not read ram partition table\n"); + return -EINVAL; + } + + pr_info("meminfo_init: smem ram ptable found: ver: %d len: %d\n", + ram_ptable->version, ram_ptable->len); + + for (i = 0; i < ram_ptable->len; i++) { + /* A bank is valid only if is greater than min_bank_size. If + * non-valid memory (e.g. modem memory) became greater than + * min_bank_size, there is currently no way to differentiate. + */ + if (ram_ptable->parts[i].type == type && + ram_ptable->parts[i].size >= min_bank_size) { + bank_start = ram_ptable->parts[i].start; + bank_size = ram_ptable->parts[i].size; + region_size = bank_size / NR_REGIONS_PER_BANK; + + for (j = 0; j < NR_REGIONS_PER_BANK; j++) { + mem_regions[nr_mem_regions].start = + bank_start; + mem_regions[nr_mem_regions].size = + region_size; + mem_regions[nr_mem_regions].state = + STATE_DEFAULT; + bank_start += region_size; + nr_mem_regions++; + } + nr_mem_banks++; + } + } + mutex_init(&mem_regions_mutex); + mem_regions_mask = default_mask; + pr_info("Found %d memory banks grouped into %d memory regions\n", + nr_mem_banks, nr_mem_regions); + return 0; +} diff --git a/arch/arm/mach-msm/mkrpcsym.pl b/arch/arm/mach-msm/mkrpcsym.pl new file mode 100644 index 0000000000000000000000000000000000000000..f4abb5fc36ca7acbfc7d1e99eada93d5c7f79ea3 --- /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-8660.c b/arch/arm/mach-msm/modem-8660.c new file mode 100644 index 0000000000000000000000000000000000000000..0b7b768856aa0d50b3d9c96c417a5c1b43bbd672 --- /dev/null +++ b/arch/arm/mach-msm/modem-8660.c @@ -0,0 +1,288 @@ +/* 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 MODULE_NAME "modem_8660" +#define MODEM_WDOG_ENABLE 0x10020008 +#define MODEM_CLEANUP_DELAY_MS 20 + +#define SUBSYS_FATAL_DEBUG + +#if defined(SUBSYS_FATAL_DEBUG) +static void debug_crash_modem_fn(struct work_struct *); +static int reset_modem; +static int ignore_smsm_ack; + +static DECLARE_DELAYED_WORK(debug_crash_modem_work, + debug_crash_modem_fn); + +module_param(reset_modem, int, 0644); +#endif + +/* Subsystem restart: Modem data, functions */ +static void *modem_ramdump_dev; +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"); + enable_irq(MARM_WDOG_EXPIRED); +} + +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"); + enable_irq(MARM_WDOG_EXPIRED); + + } else if (modem_state & reset_smsm_states) { + + pr_err("%s: User-invoked system reset/powerdown.", + MODULE_NAME); + kernel_restart(NULL); + + } 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_relaxed(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) { + if (ignore_smsm_ack) { + ignore_smsm_ack = 0; + goto out; + } + pr_err("%s: Modem error fatal'ed.", MODULE_NAME); + subsystem_restart("modem"); + } +out: + return NOTIFY_DONE; +} + +static int modem_shutdown(const struct subsys_data *crashed_subsys) +{ + void __iomem *modem_wdog_addr; + + /* If the modem didn't already crash, setting SMSM_RESET + * here will help flush caches etc. The ignore_smsm_ack + * flag is set to ignore the SMSM_RESET notification + * that is generated due to the modem settings its own + * SMSM_RESET bit in response to the apps setting the + * apps SMSM_RESET bit. + */ + if (!(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RESET)) { + ignore_smsm_ack = 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 here to allow the modem to clean up caches etc. */ + msleep(MODEM_CLEANUP_DELAY_MS); + pil_force_shutdown("modem"); + disable_irq_nosync(MARM_WDOG_EXPIRED); + + + + return 0; +} + +static int 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 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 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 to allow the modem to clean up caches etc. */ + mdelay(5); +} + +static irqreturn_t modem_wdog_bite_irq(int irq, void *dev_id) +{ + int ret; + + ret = schedule_work(&modem_fatal_work); + disable_irq_nosync(MARM_WDOG_EXPIRED); + + return IRQ_HANDLED; +} + +static struct subsys_data subsys_8660_modem = { + .name = "modem", + .shutdown = modem_shutdown, + .powerup = modem_powerup, + .ramdump = modem_ramdump, + .crash_shutdown = modem_crash_shutdown +}; + +static int __init modem_8660_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, modem_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; + } + + modem_ramdump_dev = create_ramdump_device("modem"); + + if (!modem_ramdump_dev) { + ret = -ENOMEM; + goto out; + } + + ret = ssr_register_subsystem(&subsys_8660_modem); +out: + return ret; +} + +static void __exit modem_8660_exit(void) +{ + free_irq(MARM_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(modem_8660_init); +module_exit(modem_8660_exit); + diff --git a/arch/arm/mach-msm/modem-8960.c b/arch/arm/mach-msm/modem-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..492200763468450b3cadf52bdfd381b34a4b4a8d --- /dev/null +++ b/arch/arm/mach-msm/modem-8960.c @@ -0,0 +1,396 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_private.h" +#include "modem_notifier.h" +#include "ramdump.h" + +static int crash_shutdown; + +#define MAX_SSR_REASON_LEN 81U +#define Q6_FW_WDOG_ENABLE 0x08882024 +#define Q6_SW_WDOG_ENABLE 0x08982024 + +static void log_modem_sfr(void) +{ + u32 size; + char *smem_reason, reason[MAX_SSR_REASON_LEN]; + + smem_reason = smem_get_entry(SMEM_SSR_REASON_MSS0, &size); + if (!smem_reason || !size) { + pr_err("modem subsystem failure reason: (unknown, smem_get_entry failed).\n"); + return; + } + if (!smem_reason[0]) { + pr_err("modem subsystem failure reason: (unknown, init string found).\n"); + return; + } + + size = min(size, MAX_SSR_REASON_LEN-1); + memcpy(reason, smem_reason, size); + reason[size] = '\0'; + pr_err("modem subsystem failure reason: %s.\n", reason); + + smem_reason[0] = '\0'; + wmb(); +} + +static void modem_wdog_check(struct work_struct *work) +{ + void __iomem *q6_sw_wdog_addr; + u32 regval; + + q6_sw_wdog_addr = ioremap_nocache(Q6_SW_WDOG_ENABLE, 4); + if (!q6_sw_wdog_addr) + panic("Unable to check modem watchdog status.\n"); + + regval = readl_relaxed(q6_sw_wdog_addr); + if (!regval) { + pr_err("modem-8960: Modem watchdog wasn't activated!. Restarting the modem now.\n"); + log_modem_sfr(); + subsystem_restart("modem"); + } + + iounmap(q6_sw_wdog_addr); +} + +static DECLARE_DELAYED_WORK(modem_wdog_check_work, modem_wdog_check); + +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"); + log_modem_sfr(); + 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 { + log_modem_sfr(); + subsystem_restart("modem"); + } +} + +static void modem_fw_fatal_fn(struct work_struct *work) +{ + pr_err("Watchdog bite received from modem FW!\n"); + log_modem_sfr(); + 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("Probable fatal error on the modem.\n"); + log_modem_sfr(); + subsystem_restart("modem"); + } +} + +static int modem_shutdown(const struct subsys_data *subsys) +{ + void __iomem *q6_fw_wdog_addr; + void __iomem *q6_sw_wdog_addr; + + /* + * Cancel any pending wdog_check work items, since we're shutting + * down anyway. + */ + cancel_delayed_work(&modem_wdog_check_work); + + /* + * Disable the modem watchdog since it keeps running even after the + * modem is shutdown. + */ + q6_fw_wdog_addr = ioremap_nocache(Q6_FW_WDOG_ENABLE, 4); + if (!q6_fw_wdog_addr) + return -ENOMEM; + + q6_sw_wdog_addr = ioremap_nocache(Q6_SW_WDOG_ENABLE, 4); + if (!q6_sw_wdog_addr) { + iounmap(q6_fw_wdog_addr); + return -ENOMEM; + } + + writel_relaxed(0x0, q6_fw_wdog_addr); + writel_relaxed(0x0, q6_sw_wdog_addr); + mb(); + iounmap(q6_sw_wdog_addr); + iounmap(q6_fw_wdog_addr); + + pil_force_shutdown("modem"); + pil_force_shutdown("modem_fw"); + disable_irq_nosync(Q6FW_WDOG_EXPIRED_IRQ); + disable_irq_nosync(Q6SW_WDOG_EXPIRED_IRQ); + + return 0; +} + +#define MODEM_WDOG_CHECK_TIMEOUT_MS 10000 + +static int modem_powerup(const struct subsys_data *subsys) +{ + pil_force_boot("modem_fw"); + pil_force_boot("modem"); + enable_irq(Q6FW_WDOG_EXPIRED_IRQ); + enable_irq(Q6SW_WDOG_EXPIRED_IRQ); + schedule_delayed_work(&modem_wdog_check_work, + msecs_to_jiffies(MODEM_WDOG_CHECK_TIMEOUT_MS)); + return 0; +} + +void modem_crash_shutdown(const struct subsys_data *subsys) +{ + crash_shutdown = 1; + smsm_reset_modem(SMSM_RESET); +} + +/* FIXME: Get address, size from PIL */ +static struct ramdump_segment modemsw_segments[] = { + {0x89000000, 0x8D400000 - 0x89000000}, +}; + +static struct ramdump_segment modemfw_segments[] = { + {0x8D400000, 0x8DA00000 - 0x8D400000}, +}; + +static struct ramdump_segment smem_segments[] = { + {0x80000000, 0x00200000}, +}; + +static void *modemfw_ramdump_dev; +static void *modemsw_ramdump_dev; +static void *smem_ramdump_dev; + +static int modem_ramdump(int enable, + const struct subsys_data *crashed_subsys) +{ + int ret = 0; + + if (enable) { + ret = do_ramdump(modemsw_ramdump_dev, modemsw_segments, + ARRAY_SIZE(modemsw_segments)); + + if (ret < 0) { + pr_err("Unable to dump modem sw memory (rc = %d).\n", + ret); + goto out; + } + + ret = do_ramdump(modemfw_ramdump_dev, modemfw_segments, + ARRAY_SIZE(modemfw_segments)); + + if (ret < 0) { + pr_err("Unable to dump modem fw memory (rc = %d).\n", + ret); + goto out; + } + + ret = do_ramdump(smem_ramdump_dev, smem_segments, + ARRAY_SIZE(smem_segments)); + + if (ret < 0) { + pr_err("Unable to dump smem memory (rc = %d).\n", ret); + goto out; + } + } + +out: + return ret; +} + +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); + disable_irq_nosync(Q6FW_WDOG_EXPIRED_IRQ); + break; + case Q6FW_WDOG_EXPIRED_IRQ: + ret = schedule_work(&modem_fw_fatal_work); + disable_irq_nosync(Q6SW_WDOG_EXPIRED_IRQ); + 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) + subsystem_restart("modem"); + + 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; + + if (!cpu_is_msm8960() && !cpu_is_msm8930() && !cpu_is_msm9615()) + return -ENODEV; + + 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; + } + + modemfw_ramdump_dev = create_ramdump_device("modem_fw"); + + if (!modemfw_ramdump_dev) { + pr_err("%s: Unable to create modem fw ramdump device. (%d)\n", + __func__, -ENOMEM); + ret = -ENOMEM; + goto out; + } + + modemsw_ramdump_dev = create_ramdump_device("modem_sw"); + + if (!modemsw_ramdump_dev) { + pr_err("%s: Unable to create modem sw ramdump device. (%d)\n", + __func__, -ENOMEM); + ret = -ENOMEM; + goto out; + } + + smem_ramdump_dev = create_ramdump_device("smem"); + + if (!smem_ramdump_dev) { + pr_err("%s: Unable to create smem ramdump device. (%d)\n", + __func__, -ENOMEM); + ret = -ENOMEM; + goto out; + } + + ret = modem_debugfs_init(); + + pr_info("%s: 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 0000000000000000000000000000000000000000..d92098b694895dfe3aa30d8d29ff090af12f40b8 --- /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 0000000000000000000000000000000000000000..1bd2d6d958031161c13554dd1b14f360b63fa6aa --- /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-8625.c b/arch/arm/mach-msm/mpm-8625.c new file mode 100644 index 0000000000000000000000000000000000000000..fa966d2570b03dc301b91be1d69d8c92c1db04ea --- /dev/null +++ b/arch/arm/mach-msm/mpm-8625.c @@ -0,0 +1,304 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "mpm-8625.h" + +#define NUM_REGS_ENABLE 2 +/* (NR_MSM_IRQS/32) 96 max irqs supported */ +#define NUM_REGS_DISABLE 3 +#define GIC_IRQ_MASK(irq) BIT(irq % 32) +#define GIC_IRQ_INDEX(irq) (irq / 32) + +enum { + IRQ_DEBUG_SLEEP_INT_TRIGGER = BIT(0), + IRQ_DEBUG_SLEEP_INT = BIT(1), + IRQ_DEBUG_SLEEP_ABORT = BIT(2), + IRQ_DEBUG_SLEEP = BIT(3), + IRQ_DEBUG_SLEEP_REQUEST = BIT(4) +}; + +static int msm_gic_irq_debug_mask; +module_param_named(debug_mask, msm_gic_irq_debug_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); + +static uint32_t msm_gic_irq_smsm_wake_enable[NUM_REGS_ENABLE]; +static uint32_t msm_gic_irq_idle_disable[NUM_REGS_DISABLE]; + + /* + * Some of the interrupts which will not be considered as wake capable + * should be marked as FAKE. + * Interrupts: GPIO, Timers etc.. + */ +#define SMSM_FAKE_IRQ (0xff) + + /* msm_gic_irq_to_smsm: IRQ's those will be monitored by Modem */ +static uint8_t msm_gic_irq_to_smsm[NR_IRQS] = { + [MSM8625_INT_USB_OTG] = 4, + [MSM8625_INT_PWB_I2C] = 5, + [MSM8625_INT_SDC1_0] = 6, + [MSM8625_INT_SDC1_1] = 7, + [MSM8625_INT_SDC2_0] = 8, + [MSM8625_INT_SDC2_1] = 9, + [MSM8625_INT_ADSP_A9_A11] = 10, + [MSM8625_INT_UART1] = 11, + [MSM8625_INT_UART2] = 12, + [MSM8625_INT_UART3] = 13, + [MSM8625_INT_UART1_RX] = 14, + [MSM8625_INT_UART2_RX] = 15, + [MSM8625_INT_UART3_RX] = 16, + [MSM8625_INT_UART1DM_IRQ] = 17, + [MSM8625_INT_UART1DM_RX] = 18, + [MSM8625_INT_KEYSENSE] = 19, + [MSM8625_INT_AD_HSSD] = 20, + [MSM8625_INT_NAND_WR_ER_DONE] = 21, + [MSM8625_INT_NAND_OP_DONE] = 22, + [MSM8625_INT_TCHSCRN1] = 23, + [MSM8625_INT_TCHSCRN2] = 24, + [MSM8625_INT_TCHSCRN_SSBI] = 25, + [MSM8625_INT_USB_HS] = 26, + [MSM8625_INT_UART2DM_RX] = 27, + [MSM8625_INT_UART2DM_IRQ] = 28, + [MSM8625_INT_SDC4_1] = 29, + [MSM8625_INT_SDC4_0] = 30, + [MSM8625_INT_SDC3_1] = 31, + [MSM8625_INT_SDC3_0] = 32, + + /* fake wakeup interrupts */ + [MSM8625_INT_GPIO_GROUP1] = SMSM_FAKE_IRQ, + [MSM8625_INT_GPIO_GROUP2] = SMSM_FAKE_IRQ, + [MSM8625_INT_A9_M2A_0] = SMSM_FAKE_IRQ, + [MSM8625_INT_A9_M2A_1] = SMSM_FAKE_IRQ, + [MSM8625_INT_A9_M2A_5] = SMSM_FAKE_IRQ, + [MSM8625_INT_GP_TIMER_EXP] = SMSM_FAKE_IRQ, + [MSM8625_INT_DEBUG_TIMER_EXP] = SMSM_FAKE_IRQ, + [MSM8625_INT_ADSP_A11] = SMSM_FAKE_IRQ, +}; + +static void msm_gic_mask_irq(struct irq_data *d) +{ + unsigned int index = GIC_IRQ_INDEX(d->irq); + uint32_t mask; + int smsm_irq = msm_gic_irq_to_smsm[d->irq]; + + mask = GIC_IRQ_MASK(d->irq); + + if (smsm_irq == 0) { + msm_gic_irq_idle_disable[index] &= ~mask; + } else { + mask = GIC_IRQ_MASK(smsm_irq - 1); + msm_gic_irq_smsm_wake_enable[0] &= ~mask; + } +} + +static void msm_gic_unmask_irq(struct irq_data *d) +{ + unsigned int index = GIC_IRQ_INDEX(d->irq); + uint32_t mask; + int smsm_irq = msm_gic_irq_to_smsm[d->irq]; + + mask = GIC_IRQ_MASK(d->irq); + + if (smsm_irq == 0) { + msm_gic_irq_idle_disable[index] |= mask; + } else { + mask = GIC_IRQ_MASK(smsm_irq - 1); + msm_gic_irq_smsm_wake_enable[0] |= mask; + } +} + +static int msm_gic_set_irq_wake(struct irq_data *d, unsigned int on) +{ + uint32_t mask; + int smsm_irq = msm_gic_irq_to_smsm[d->irq]; + + if (smsm_irq == 0) { + pr_err("bad wake up irq %d\n", d->irq); + return -EINVAL; + } + + if (smsm_irq == SMSM_FAKE_IRQ) + return 0; + + mask = GIC_IRQ_MASK(smsm_irq - 1); + if (on) + msm_gic_irq_smsm_wake_enable[1] |= mask; + else + msm_gic_irq_smsm_wake_enable[1] &= ~mask; + + return 0; +} + +void __init msm_gic_irq_extn_init(void __iomem *db, void __iomem *cb) +{ + gic_arch_extn.irq_mask = msm_gic_mask_irq; + gic_arch_extn.irq_unmask = msm_gic_unmask_irq; + gic_arch_extn.irq_disable = msm_gic_mask_irq; + gic_arch_extn.irq_set_wake = msm_gic_set_irq_wake; +} + +/* Power APIs */ + + /* + * Iterate over the disable list + */ + +int msm_gic_irq_idle_sleep_allowed(void) +{ + uint32_t i, disable = 0; + + for (i = 0; i < NUM_REGS_DISABLE; i++) + disable |= msm_gic_irq_idle_disable[i]; + + return !disable; +} + + /* + * Prepare interrupt subsystem for entering sleep -- phase 1 + * If modem_wake is true, return currently enabled interrupt + * mask in *irq_mask + */ +void msm_gic_irq_enter_sleep1(bool modem_wake, int from_idle, uint32_t + *irq_mask) +{ + if (modem_wake) { + *irq_mask = msm_gic_irq_smsm_wake_enable[!from_idle]; + if (msm_gic_irq_debug_mask & IRQ_DEBUG_SLEEP) + pr_info("%s irq_mask %x\n", __func__, *irq_mask); + } +} + + /* + * Prepare interrupt susbsytem 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_gic_irq_enter_sleep2(bool modem_wake, int from_idle) +{ + 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_gic_irq_debug_mask & IRQ_DEBUG_SLEEP) + pr_info("%s interrupts pending\n", __func__); + + /* check the pending interrupts */ + if (msm_gic_spi_ppi_pending()) { + if (msm_gic_irq_debug_mask & IRQ_DEBUG_SLEEP_ABORT) + pr_info("%s aborted....\n", __func__); + return -EAGAIN; + } + + if (modem_wake) { + /* save the contents of GIC CPU interface and Distributor + * Disable all the Interrupts, if we enter from idle pc + */ + msm_gic_save(modem_wake, from_idle); + irq_set_irq_type(MSM8625_INT_A9_M2A_6, IRQF_TRIGGER_RISING); + enable_irq(MSM8625_INT_A9_M2A_6); + pr_debug("%s going for sleep now\n", __func__); + } + + return 0; +} + + /* + * Restore interrupt subsystem from sleep -- phase 1 + * Configure the interrupt hardware. + */ +void msm_gic_irq_exit_sleep1(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs) +{ + /* Restore GIC contents, which were saved */ + msm_gic_restore(); + + /* Disable A9_M2A_6 */ + disable_irq(MSM8625_INT_A9_M2A_6); + + if (msm_gic_irq_debug_mask & IRQ_DEBUG_SLEEP) + pr_info("%s %x %x %x now\n", __func__, irq_mask, + pending_irqs, wakeup_reason); +} + + /* + * Restore interrupt subsystem from sleep -- phase 2 + * Poke the specified pending interrupts into interrupt hardware. + */ +void msm_gic_irq_exit_sleep2(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending) +{ + int i, smsm_irq, smsm_mask; + struct irq_desc *desc; + + if (msm_gic_irq_debug_mask & IRQ_DEBUG_SLEEP) + pr_info("%s %x %x %x now\n", __func__, irq_mask, + pending, wakeup_reason); + + for (i = 0; pending && i < ARRAY_SIZE(msm_gic_irq_to_smsm); i++) { + smsm_irq = msm_gic_irq_to_smsm[i]; + + if (smsm_irq == 0) + continue; + + smsm_mask = BIT(smsm_irq - 1); + if (!(pending & smsm_mask)) + continue; + + pending &= ~smsm_mask; + + if (msm_gic_irq_debug_mask & IRQ_DEBUG_SLEEP_INT) + pr_info("%s, irq %d, still pending %x now\n", + __func__, i, pending); + /* Peding IRQ */ + desc = i ? irq_to_desc(i) : NULL; + + /* Check if the pending */ + if (desc && !irqd_is_level_type(&desc->irq_data)) { + /* Mark the IRQ as pending, if not Level */ + irq_set_pending(i); + check_irq_resend(desc, i); + } + } +} + + /* + * Restore interrupt subsystem from sleep -- phase 3 + * Print debug information + */ +void msm_gic_irq_exit_sleep3(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs) +{ + if (msm_gic_irq_debug_mask & IRQ_DEBUG_SLEEP) + pr_info("%s, irq_mask %x pending_irqs %x, wakeup_reason %x," + "state %x now\n", __func__, irq_mask, + pending_irqs, wakeup_reason, + smsm_get_state(SMSM_MODEM_STATE)); +} diff --git a/arch/arm/mach-msm/mpm-8625.h b/arch/arm/mach-msm/mpm-8625.h new file mode 100644 index 0000000000000000000000000000000000000000..4ada9e2560bed7749aa18378dbb8f5dc8a5da8a5 --- /dev/null +++ b/arch/arm/mach-msm/mpm-8625.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_8625_H_ +#define _ARCH_ARM_MACH_MSM_MPM_8625_H_ + +void msm_gic_irq_extn_init(void __iomem *, void __iomem *); + +unsigned int msm_gic_spi_ppi_pending(void); +int msm_gic_irq_idle_sleep_allowed(void); +void msm_gic_irq_enter_sleep1(bool modem_wake, int from_idle, uint32_t + *irq_mask); +int msm_gic_irq_enter_sleep2(bool modem_wake, int from_idle); +void msm_gic_irq_exit_sleep1(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs); +void msm_gic_irq_exit_sleep2(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending); +void msm_gic_irq_exit_sleep3(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs); +#endif diff --git a/arch/arm/mach-msm/mpm.c b/arch/arm/mach-msm/mpm.c new file mode 100644 index 0000000000000000000000000000000000000000..b395b61349c7a9e3155dff9baf2c85477186e55b --- /dev/null +++ b/arch/arm/mach-msm/mpm.c @@ -0,0 +1,546 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/****************************************************************************** + * 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 struct msm_mpm_device_data msm_mpm_dev_data; +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 (index >= MSM_MPM_REG_WIDTH) + return -EFAULT; + + 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(unsigned int 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(unsigned int 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(unsigned int 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 __init msm_mpm_irq_extn_init(struct msm_mpm_device_data *mpm_data) +{ + 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); + + if (!mpm_data) { +#ifdef CONFIG_MSM_MPM + BUG(); +#endif + return; + } + + memcpy(&msm_mpm_dev_data, mpm_data, sizeof(struct msm_mpm_device_data)); + + msm_mpm_dev_data.irqs_m2a = + kzalloc(msm_mpm_dev_data.irqs_m2a_size * sizeof(uint16_t), + GFP_KERNEL); + BUG_ON(!msm_mpm_dev_data.irqs_m2a); + memcpy(msm_mpm_dev_data.irqs_m2a, mpm_data->irqs_m2a, + msm_mpm_dev_data.irqs_m2a_size * sizeof(uint16_t)); + msm_mpm_dev_data.bypassed_apps_irqs = + kzalloc(msm_mpm_dev_data.bypassed_apps_irqs_size * + sizeof(uint16_t), GFP_KERNEL); + BUG_ON(!msm_mpm_dev_data.bypassed_apps_irqs); + memcpy(msm_mpm_dev_data.bypassed_apps_irqs, + mpm_data->bypassed_apps_irqs, + msm_mpm_dev_data.bypassed_apps_irqs_size * sizeof(uint16_t)); +} + +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/mpp.c b/arch/arm/mach-msm/mpp.c new file mode 100644 index 0000000000000000000000000000000000000000..bd70e9a02c811d6197759ff2b47770330ccda3c0 --- /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 +#include + +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-buspm-dev.c b/arch/arm/mach-msm/msm-buspm-dev.c new file mode 100644 index 0000000000000000000000000000000000000000..296418d04260b88184a6096efb39ccf36c6c0ebf --- /dev/null +++ b/arch/arm/mach-msm/msm-buspm-dev.c @@ -0,0 +1,256 @@ +/* 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 "msm-buspm-dev.h" + +#define MSM_BUSPM_DRV_NAME "msm-buspm-dev" + +/* + * Allocate kernel buffer. + * Currently limited to one buffer per file descriptor. If alloc() is + * called twice for the same descriptor, the original buffer is freed. + * There is also no locking protection so the same descriptor can not be shared. + */ + +static inline void *msm_buspm_dev_get_vaddr(struct file *filp) +{ + struct msm_buspm_map_dev *dev = filp->private_data; + + return (dev) ? dev->vaddr : NULL; +} + +static inline unsigned long msm_buspm_dev_get_paddr(struct file *filp) +{ + struct msm_buspm_map_dev *dev = filp->private_data; + + return (dev) ? dev->paddr : 0L; +} + +static void msm_buspm_dev_free(struct file *filp) +{ + struct msm_buspm_map_dev *dev = filp->private_data; + + if (dev) { + pr_debug("freeing memory at 0x%p\n", dev->vaddr); + free_contiguous_memory(dev->vaddr); + dev->paddr = 0L; + dev->vaddr = NULL; + } +} + +static int msm_buspm_dev_open(struct inode *inode, struct file *filp) +{ + struct msm_buspm_map_dev *dev; + + if (capable(CAP_SYS_ADMIN)) { + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev) + filp->private_data = dev; + else + return -ENOMEM; + } else { + return -EPERM; + } + + return 0; +} + +static int +msm_buspm_dev_alloc(struct file *filp, struct buspm_alloc_params data) +{ + unsigned long paddr; + void *vaddr; + struct msm_buspm_map_dev *dev = filp->private_data; + + /* If buffer already allocated, then free it */ + if (dev->vaddr) + msm_buspm_dev_free(filp); + + /* Allocate uncached memory */ + vaddr = allocate_contiguous_ebi(data.size, PAGE_SIZE, 0); + paddr = (vaddr) ? memory_pool_node_paddr(vaddr) : 0L; + + if (vaddr == NULL) { + pr_err("allocation of 0x%x bytes failed", data.size); + return -ENOMEM; + } + + dev->vaddr = vaddr; + dev->paddr = paddr; + dev->buflen = data.size; + filp->f_pos = 0; + pr_debug("virt addr = 0x%p\n", dev->vaddr); + pr_debug("phys addr = 0x%lx\n", dev->paddr); + + return 0; +} + +static long +msm_buspm_dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct buspm_xfer_req xfer; + struct buspm_alloc_params alloc_data; + unsigned long paddr; + int retval = 0; + void *buf = msm_buspm_dev_get_vaddr(filp); + unsigned char *dbgbuf = buf; + + switch (cmd) { + case MSM_BUSPM_IOC_FREE: + pr_debug("cmd = 0x%x (FREE)\n", cmd); + msm_buspm_dev_free(filp); + break; + + case MSM_BUSPM_IOC_ALLOC: + pr_debug("cmd = 0x%x (ALLOC)\n", cmd); + retval = __get_user(alloc_data.size, (size_t __user *)arg); + + if (retval == 0) + retval = msm_buspm_dev_alloc(filp, alloc_data); + break; + + case MSM_BUSPM_IOC_RD_PHYS_ADDR: + pr_debug("Read Physical Address\n"); + paddr = msm_buspm_dev_get_paddr(filp); + if (paddr == 0L) { + retval = -EINVAL; + } else { + pr_debug("phys addr = 0x%lx\n", paddr); + retval = __put_user(paddr, + (unsigned long __user *)arg); + } + break; + + case MSM_BUSPM_IOC_RDBUF: + pr_debug("Read Buffer: 0x%x%x%x%x\n", + dbgbuf[0], dbgbuf[1], dbgbuf[2], dbgbuf[3]); + + if (!buf) { + retval = -EINVAL; + break; + } + + if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) { + retval = -EFAULT; + break; + } + + if ((xfer.size <= sizeof(buf)) && + (copy_to_user((void __user *)xfer.data, buf, + xfer.size))) { + retval = -EFAULT; + break; + } + break; + + case MSM_BUSPM_IOC_WRBUF: + pr_debug("Write Buffer\n"); + + if (!buf) { + retval = -EINVAL; + break; + } + + if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) { + retval = -EFAULT; + break; + } + + if ((sizeof(buf) <= xfer.size) && + (copy_from_user(buf, (void __user *)xfer.data, + xfer.size))) { + retval = -EFAULT; + break; + } + break; + + default: + pr_debug("Unknown command 0x%x\n", cmd); + retval = -EINVAL; + break; + } + + return retval; +} + +static int msm_buspm_dev_release(struct inode *inode, struct file *filp) +{ + struct msm_buspm_map_dev *dev = filp->private_data; + + msm_buspm_dev_free(filp); + kfree(dev); + filp->private_data = NULL; + + return 0; +} + +static int msm_buspm_dev_mmap(struct file *filp, struct vm_area_struct *vma) +{ + pr_debug("vma = 0x%p\n", vma); + + /* Mappings are uncached */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) + return -EFAULT; + + return 0; +} + +static const struct file_operations msm_buspm_dev_fops = { + .owner = THIS_MODULE, + .mmap = msm_buspm_dev_mmap, + .open = msm_buspm_dev_open, + .unlocked_ioctl = msm_buspm_dev_ioctl, + .llseek = noop_llseek, + .release = msm_buspm_dev_release, +}; + +struct miscdevice msm_buspm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = MSM_BUSPM_DRV_NAME, + .fops = &msm_buspm_dev_fops, +}; + +static int __init msm_buspm_dev_init(void) +{ + int ret = 0; + + ret = misc_register(&msm_buspm_misc); + if (ret < 0) + pr_err("%s: Cannot register misc device\n", __func__); + + return ret; +} + +static void __exit msm_buspm_dev_exit(void) +{ + misc_deregister(&msm_buspm_misc); +} +module_init(msm_buspm_dev_init); +module_exit(msm_buspm_dev_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:"MSM_BUSPM_DRV_NAME); diff --git a/arch/arm/mach-msm/msm-buspm-dev.h b/arch/arm/mach-msm/msm-buspm-dev.h new file mode 100644 index 0000000000000000000000000000000000000000..583908714b956b619fb60e50dfba32f745ae4fb6 --- /dev/null +++ b/arch/arm/mach-msm/msm-buspm-dev.h @@ -0,0 +1,50 @@ +/* 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_BUSPM_DEV_H__ +#define __MSM_BUSPM_DEV_H__ + +#include + +struct msm_buspm_map_dev { + void *vaddr; + unsigned long paddr; + size_t buflen; +}; + +/* Read/write data into kernel buffer */ +struct buspm_xfer_req { + int size; /* Size of this request, in bytes */ + void *data; /* Data buffer to transfer data to/from */ +}; + +struct buspm_alloc_params { + int size; +}; + +#define MSM_BUSPM_IOC_MAGIC 'p' + +#define MSM_BUSPM_IOC_FREE \ + _IOW(MSM_BUSPM_IOC_MAGIC, 0, void *) + +#define MSM_BUSPM_IOC_ALLOC \ + _IOW(MSM_BUSPM_IOC_MAGIC, 1, size_t) + +#define MSM_BUSPM_IOC_RDBUF \ + _IOW(MSM_BUSPM_IOC_MAGIC, 2, struct buspm_xfer_req) + +#define MSM_BUSPM_IOC_WRBUF \ + _IOW(MSM_BUSPM_IOC_MAGIC, 3, struct buspm_xfer_req) + +#define MSM_BUSPM_IOC_RD_PHYS_ADDR \ + _IOR(MSM_BUSPM_IOC_MAGIC, 4, unsigned long) +#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 0000000000000000000000000000000000000000..469564a754addcd828c5faebaf489b928fd7dd72 --- /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 0000000000000000000000000000000000000000..3d341e31864621c07802a88d7223a1df8fec0f23 --- /dev/null +++ b/arch/arm/mach-msm/msm-krait-l2-accessors.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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_RAW_SPINLOCK(l2_access_lock); + +u32 set_get_l2_indirect_reg(u32 reg_addr, u32 val) +{ + unsigned long flags; + u32 ret_val; + /* CP15 registers are not emulated on RUMI3. */ + if (machine_is_msm8960_rumi3()) + return 0; + + raw_spin_lock_irqsave(&l2_access_lock, flags); + + mb(); + asm volatile ("mcr p15, 3, %[l2cpselr], c15, c0, 6\n\t" + "isb\n\t" + "mcr p15, 3, %[l2cpdr], c15, c0, 7\n\t" + "isb\n\t" + "mrc p15, 3, %[l2cpdr_read], c15, c0, 7\n\t" + : [l2cpdr_read]"=r" (ret_val) + : [l2cpselr]"r" (reg_addr), [l2cpdr]"r" (val) + ); + raw_spin_unlock_irqrestore(&l2_access_lock, flags); + + return ret_val; +} +EXPORT_SYMBOL(set_get_l2_indirect_reg); + +void set_l2_indirect_reg(u32 reg_addr, u32 val) +{ + unsigned long flags; + /* CP15 registers are not emulated on RUMI3. */ + if (machine_is_msm8960_rumi3()) + return; + + raw_spin_lock_irqsave(&l2_access_lock, flags); + mb(); + asm volatile ("mcr p15, 3, %[l2cpselr], c15, c0, 6\n\t" + "isb\n\t" + "mcr p15, 3, %[l2cpdr], c15, c0, 7\n\t" + "isb\n\t" + : + : [l2cpselr]"r" (reg_addr), [l2cpdr]"r" (val) + ); + raw_spin_unlock_irqrestore(&l2_access_lock, flags); +} +EXPORT_SYMBOL(set_l2_indirect_reg); + +u32 get_l2_indirect_reg(u32 reg_addr) +{ + u32 val; + unsigned long flags; + /* CP15 registers are not emulated on RUMI3. */ + if (machine_is_msm8960_rumi3()) + return 0; + + raw_spin_lock_irqsave(&l2_access_lock, flags); + asm volatile ("mcr p15, 3, %[l2cpselr], c15, c0, 6\n\t" + "isb\n\t" + "mrc p15, 3, %[l2cpdr], c15, c0, 7\n\t" + : [l2cpdr]"=r" (val) + : [l2cpselr]"r" (reg_addr) + ); + raw_spin_unlock_irqrestore(&l2_access_lock, flags); + + return val; +} +EXPORT_SYMBOL(get_l2_indirect_reg); diff --git a/arch/arm/mach-msm/msm_bus/Makefile b/arch/arm/mach-msm/msm_bus/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..061998cb6ad08323c6b305cb284f9b82ac09967c --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/Makefile @@ -0,0 +1,10 @@ +# +# 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_ARCH_MSM9615) += msm_bus_board_9615.o +obj-$(CONFIG_ARCH_APQ8064) += msm_bus_board_8064.o +obj-$(CONFIG_ARCH_MSM8930) += msm_bus_board_8930.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 0000000000000000000000000000000000000000..07082b7e29629cd8f69c5d30f865097125b93e3a --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_arb.c @@ -0,0 +1,719 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "AXI: %s(): " fmt, __func__ + +#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)\ + DIV_ROUND_UP((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 (ZERO_OR_NULL_PTR(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 (ZERO_OR_NULL_PTR(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); + if (ZERO_OR_NULL_PTR(info)) { + MSM_BUS_ERR("Node %d not found\n", dest); + return -ENXIO; + } + + 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_ERR("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); + if (ZERO_OR_NULL_PTR(info)) { + MSM_BUS_ERR("Node %d not found\n", dest); + return -ENXIO; + } + + 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)) { + /* 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_ERR("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); + 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 -ENXIO; + } + } + + 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 -ENXIO; + } + + 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]; + + /** + * To select the right clock, AND the context with + * client active flag. + */ + info->pnode[index].sel_clk = &info->pnode[index].clk[ctx & + cl_active_flag]; + *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 & + cl_active_flag]; + + 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); + 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; + struct msm_bus_fabric_device *deffab; + + deffab = msm_bus_get_fabric_device(MSM_BUS_FAB_DEFAULT); + if (!deffab) { + MSM_BUS_ERR("Error finding default fabric\n"); + return -ENXIO; + } + + nfab = msm_bus_get_num_fab(); + if (nfab < deffab->board_algo->board_nfab) { + 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 (ZERO_OR_NULL_PTR(pnode)) { + MSM_BUS_ERR("Invalid Pnode ptr!\n"); + continue; + } else + client->src_pnode = pnode; + + 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); + if (src == -ENXIO) { + MSM_BUS_ERR("Master %d not supported. Client cannot be" + " registered\n", + pdata->usecase->vectors[i].src); + goto err; + } + dest = msm_bus_board_get_iid(pdata->usecase->vectors[i].dst); + if (dest == -ENXIO) { + MSM_BUS_ERR("Slave %d not supported. Client cannot be" + " registered\n", + pdata->usecase->vectors[i].dst); + goto err; + } + 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] == -ENXIO) { + 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); + return -ENXIO; + } + + mutex_lock(&msm_bus_lock); + if (client->curr == index) + goto err; + + curr = client->curr; + pdata = client->pdata; + + if (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); + if (src == -ENXIO) { + MSM_BUS_ERR("Master %d not supported. Request cannot" + " be updated\n", client->pdata->usecase-> + vectors[i].src); + goto err; + } + + if (msm_bus_board_get_iid(client->pdata->usecase[index]. + vectors[i].dst) == -ENXIO) { + MSM_BUS_ERR("Slave %d not supported. Request cannot" + " be updated\n", client->pdata->usecase-> + vectors[i].dst); + } + + 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, NULL, 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; +} + +int msm_bus_board_get_iid(int id) +{ + struct msm_bus_fabric_device *deffab; + + deffab = msm_bus_get_fabric_device(MSM_BUS_FAB_DEFAULT); + if (!deffab) { + MSM_BUS_ERR("Error finding default fabric\n"); + return -ENXIO; + } + + return deffab->board_algo->get_iid(id); +} + +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_8064.c b/arch/arm/mach-msm/msm_bus/msm_bus_board_8064.c new file mode 100644 index 0000000000000000000000000000000000000000..5a3d722c7ccd68c2df2b903fe3dd237b0b8e8c9f --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_8064.c @@ -0,0 +1,975 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 54 +#define NSLAVES 75 +#define NFAB_8064 5 + +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_TIERED_SLAVE_MM_IMEM = 1, + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_0, + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_1, + + MSM_BUS_TIERED_SLAVE_EBI1_CH0 = 1, + MSM_BUS_TIERED_SLAVE_EBI1_CH1, + MSM_BUS_TIERED_SLAVE_KMPSS_L2, +}; + +enum msm_bus_8064_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_GSS_NAV, + MSM_BUS_MASTER_PORT_PCIE, + MSM_BUS_MASTER_PORT_RIVA, + MSM_BUS_MASTER_PORT_SATA, + 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_SYSTEM_MASTER_PORT_CRYPTO, + + MSM_BUS_MASTER_PORT_MDP_PORT0 = 0, + MSM_BUS_MASTER_PORT_MDP_PORT1, + MSM_BUS_MASTER_PORT_GRAPHICS_3D_PORT0, + MSM_BUS_MASTER_PORT_ROTATOR, + MSM_BUS_MASTER_PORT_GRAPHICS_3D_PORT1, + MSM_BUS_MASTER_PORT_JPEG_DEC, + MSM_BUS_MASTER_PORT_VIDEO_CAP, + MSM_BUS_MASTER_PORT_VFE, + MSM_BUS_MASTER_PORT_VPE, + MSM_BUS_MASTER_PORT_JPEG_ENC, + MSM_BUS_MASTER_PORT_VIDEO_DEC, + MSM_BUS_MMSS_MASTER_PORT_APPS_FAB, + MSM_BUS_MASTER_PORT_VIDEO_ENC, + + 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_8064_slave_ports_type { + MSM_BUS_SLAVE_PORT_MM_IMEM = 0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_1, + + 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_PCIE, + MSM_BUS_SLAVE_PORT_KMPSS, + MSM_BUS_SLAVE_PORT_GSS, + 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, + MSM_BUS_SLAVE_PORT_SATA, + MSM_BUS_SLAVE_PORT_CRYPTO, +}; + +static int tier2[] = {MSM_BUS_BW_TIER2,}; +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, + MSM_BUS_SLAVE_PORT_EBI1_CH1, +}; +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, + MSM_BUS_TIERED_SLAVE_EBI1_CH1, +}; +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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_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_gss_nav[] = {MSM_BUS_MASTER_PORT_GSS_NAV,}; +static int mport_pcie[] = {MSM_BUS_MASTER_PORT_PCIE,}; +static int mport_lpass_proc[] = {MSM_BUS_MASTER_PORT_LPASS_PROC,}; +static int mport_sata[] = {MSM_BUS_MASTER_PORT_SATA,}; + +static int mport_riva[] = {MSM_BUS_MASTER_PORT_RIVA,}; +static int mport_crypto[] = {MSM_BUS_SYSTEM_MASTER_PORT_CRYPTO,}; +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_crypto[] = {MSM_BUS_SLAVE_PORT_CRYPTO,}; +static int sport_riva[] = {MSM_BUS_SLAVE_PORT_RIVA,}; +static int sport_sata[] = {MSM_BUS_SLAVE_PORT_SATA,}; +static int sport_kmpss[] = {MSM_BUS_SLAVE_PORT_KMPSS,}; +static int sport_gss[] = {MSM_BUS_SLAVE_PORT_GSS,}; +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_GSS_NAV, + .masterp = mport_gss_nav, + .num_mports = ARRAY_SIZE(mport_gss_nav), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_PCIE, + .masterp = mport_pcie, + .num_mports = ARRAY_SIZE(mport_pcie), + .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_SATA, + .masterp = mport_sata, + .num_mports = ARRAY_SIZE(mport_sata), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_CRYPTO, + .masterp = mport_crypto, + .num_mports = ARRAY_SIZE(mport_crypto), + .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, + .slaveclk[DUAL_CTX] = "dfab_clk", + .slaveclk[ACTIVE_CTX] = "dfab_a_clk", + }, + { + .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_CRYPTO, + .slavep = sport_crypto, + .num_sports = ARRAY_SIZE(sport_crypto), + .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_SATA, + .slavep = sport_sata, + .num_sports = ARRAY_SIZE(sport_sata), + .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_GSS, + .slavep = sport_gss, + .num_sports = ARRAY_SIZE(sport_gss), + .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 mport_mdp1[] = {MSM_BUS_MASTER_PORT_MDP_PORT1,}; +static int mport_rotator[] = {MSM_BUS_MASTER_PORT_ROTATOR,}; +static int mport_graphics_3d_port0[] = {MSM_BUS_MASTER_PORT_GRAPHICS_3D_PORT0,}; +static int mport_graphics_3d_port1[] = {MSM_BUS_MASTER_PORT_GRAPHICS_3D_PORT1,}; +static int mport_jpeg_dec[] = {MSM_BUS_MASTER_PORT_JPEG_DEC,}; +static int mport_video_cap[] = {MSM_BUS_MASTER_PORT_VIDEO_CAP,}; +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_video_enc[] = {MSM_BUS_MASTER_PORT_VIDEO_ENC,}; +static int mport_video_dec[] = {MSM_BUS_MASTER_PORT_VIDEO_DEC,}; +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 = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MDP_PORT1, + .masterp = mport_mdp1, + .num_mports = ARRAY_SIZE(mport_mdp1), + .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_port0, + .num_mports = ARRAY_SIZE(mport_graphics_3d_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_GRAPHICS_3D_PORT1, + .masterp = mport_graphics_3d_port1, + .num_mports = ARRAY_SIZE(mport_graphics_3d_port1), + .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_VIDEO_CAP, + .masterp = mport_video_cap, + .num_mports = ARRAY_SIZE(mport_video_cap), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_VIDEO_ENC, + .masterp = mport_video_enc, + .num_mports = ARRAY_SIZE(mport_video_enc), + .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_VIDEO_DEC, + .masterp = mport_video_dec, + .num_mports = ARRAY_SIZE(mport_video_dec), + .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, + }, +}; + +static void msm_bus_board_assign_iids(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) { + WARN(fabreg->info[i].id >= NMASTERS, + "id %d exceeds array size!\n", + fabreg->info[i].id); + master_iids[fabreg->info[i].id] = + fabreg->info[i].priv_id; + } else { + WARN((fabreg->info[i].id - SLAVE_ID_KEY) >= + NSLAVES, "id %d exceeds array size!\n", + fabreg->info[i].id); + slave_iids[fabreg->info[i].id - (SLAVE_ID_KEY)] + = fabreg->info[i].priv_id; + } + } else + fabreg->info[i].priv_id = fabreg->info[i].id; + } +} + +static int msm_bus_board_8064_get_iid(int id) +{ + if ((id < SLAVE_ID_KEY && id >= NMASTERS) || + id >= (SLAVE_ID_KEY + NSLAVES)) { + MSM_BUS_ERR("Cannot get iid. Invalid id %d passed\n", id); + return -EINVAL; + } + + return CHECK_ID(((id < SLAVE_ID_KEY) ? master_iids[id] : + slave_iids[id - SLAVE_ID_KEY]), id); +} + +static struct msm_bus_board_algorithm msm_bus_board_algo = { + .board_nfab = NFAB_8064, + .get_iid = msm_bus_board_8064_get_iid, + .assign_iids = msm_bus_board_assign_iids, +}; + +struct msm_bus_fabric_registration msm_bus_8064_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_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, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8064_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + .nmasters = 15, + .nslaves = 15, + .ntieredslaves = 3, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8064_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_MM_FABRIC_ARB_0, + .nmasters = 13, + .nslaves = 3, + .ntieredslaves = 3, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8064_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8064_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; 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 0000000000000000000000000000000000000000..296c6dcf9e141e1e120b892e4a7a82a0b33163ea --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_8660.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 "msm_bus_core.h" + +#define NMASTERS 39 +#define NSLAVES 68 +#define NFAB_8660 5 + +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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_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, + }, +}; + +static void msm_bus_board_assign_iids(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; + } +} + +static int msm_bus_board_8660_get_iid(int id) +{ + if ((id < SLAVE_ID_KEY && id >= NMASTERS) || + id >= (SLAVE_ID_KEY + NSLAVES)) { + MSM_BUS_ERR("Cannot get iid. Invalid id %d passed\n", id); + return -EINVAL; + } + + return CHECK_ID(((id < SLAVE_ID_KEY) ? master_iids[id] : + slave_iids[id - SLAVE_ID_KEY]), id); +} + +static struct msm_bus_board_algorithm msm_bus_board_algo = { + .board_nfab = NFAB_8660, + .get_iid = msm_bus_board_8660_get_iid, + .assign_iids = msm_bus_board_assign_iids, +}; + +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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_APPS_FABRIC_HALT_0, + .offset = MSM_RPM_ID_APPS_FABRIC_ARB_0, + .nmasters = 4, + .nslaves = 4, + .ntieredslaves = 2, + .board_algo = &msm_bus_board_algo, +}; + +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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_SYSTEM_FABRIC_HALT_0, + .offset = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + .nmasters = 17, + .nslaves = 9, + .ntieredslaves = 2, + .board_algo = &msm_bus_board_algo, +}; + +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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_MM_FABRIC_HALT_0, + .offset = MSM_RPM_ID_MM_FABRIC_ARB_0, + .nmasters = 14, + .nslaves = 4, + .ntieredslaves = 3, + .board_algo = &msm_bus_board_algo, +}; + +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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +int msm_bus_board_rpm_get_il_ids(uint16_t id[]) +{ + return -ENXIO; +} diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_board_8930.c b/arch/arm/mach-msm/msm_bus/msm_bus_board_8930.c new file mode 100644 index 0000000000000000000000000000000000000000..0f37c6de347326558f4ca4cbf10a6ad02b409cb5 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_8930.c @@ -0,0 +1,880 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 +#define NFAB_8930 5 + +enum msm_bus_fabric_tiered_slave_type { + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS_0 = 1, + MSM_BUS_TIERED_SLAVE_SYSTEM_IMEM, + + MSM_BUS_TIERED_SLAVE_MM_IMEM = 1, + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_0, + + MSM_BUS_TIERED_SLAVE_EBI1_CH0 = 1, + MSM_BUS_TIERED_SLAVE_KMPSS_L2, +}; + +enum msm_bus_8930_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_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_MASTER_PORT_GRAPHICS_3D, + MSM_BUS_MASTER_PORT_ROTATOR, + MSM_BUS_MASTER_PORT_VFE, + MSM_BUS_MASTER_PORT_VPE, + MSM_BUS_MASTER_PORT_JPEG_ENC, + 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_SYSTEM_0, +}; + +enum msm_bus_8930_slave_ports_type { + MSM_BUS_SLAVE_PORT_MM_IMEM = 0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0, + + MSM_BUS_SLAVE_PORT_EBI1_CH0 = 0, + 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_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 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_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_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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_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 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, +}; +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, +}; +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, +}; + +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_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, + .slaveclk[DUAL_CTX] = "dfab_clk", + .slaveclk[ACTIVE_CTX] = "dfab_a_clk", + }, + { + .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 mport_mdp1[] = {MSM_BUS_MASTER_PORT_MDP_PORT1,}; +static int mport_rotator[] = {MSM_BUS_MASTER_PORT_ROTATOR,}; +static int mport_graphics_3d[] = {MSM_BUS_MASTER_PORT_GRAPHICS_3D,}; +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_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, +}; + +static int mmss_sport_apps_fab[] = { + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0, +}; +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, +}; +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 = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MDP_PORT1, + .masterp = mport_mdp1, + .num_mports = ARRAY_SIZE(mport_mdp1), + .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_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), + }, + { + .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, + }, +}; + +static void msm_bus_board_assign_iids(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; + } +} + +static int msm_bus_board_8930_get_iid(int id) +{ + if ((id < SLAVE_ID_KEY && id >= NMASTERS) || + id >= (SLAVE_ID_KEY + NSLAVES)) { + MSM_BUS_ERR("Cannot get iid. Invalid id %d passed\n", id); + return -EINVAL; + } + + return CHECK_ID(((id < SLAVE_ID_KEY) ? master_iids[id] : + slave_iids[id - SLAVE_ID_KEY]), id); +} + +static struct msm_bus_board_algorithm msm_bus_board_algo = { + .board_nfab = NFAB_8930, + .get_iid = msm_bus_board_8930_get_iid, + .assign_iids = msm_bus_board_assign_iids, +}; + +struct msm_bus_fabric_registration msm_bus_8930_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_APPS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_APPS_FABRIC_ARB_0, + .nmasters = 4, + .nslaves = 4, + .ntieredslaves = 2, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8930_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + .nmasters = 14, + .nslaves = 11, + .ntieredslaves = 2, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8930_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_MM_FABRIC_ARB_0, + .nmasters = 10, + .nslaves = 2, + .ntieredslaves = 2, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8930_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8930_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; 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 0000000000000000000000000000000000000000..7ede23d46cdb40b9139078781c0596999fc4ad61 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_8960.c @@ -0,0 +1,955 @@ +/* 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 +#define NFAB_8960 5 + +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 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, + MSM_BUS_SLAVE_PORT_EBI1_CH1, +}; +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, + MSM_BUS_TIERED_SLAVE_EBI1_CH1, +}; +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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_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, + .slaveclk[DUAL_CTX] = "dfab_clk", + .slaveclk[ACTIVE_CTX] = "dfab_a_clk", + }, + { + .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 mport_mdp1[] = {MSM_BUS_MASTER_PORT_MDP_PORT1,}; +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 = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MDP_PORT1, + .masterp = mport_mdp1, + .num_mports = ARRAY_SIZE(mport_mdp1), + .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, + }, +}; + +static void msm_bus_board_assign_iids(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; + } +} + +static int msm_bus_board_8960_get_iid(int id) +{ + if ((id < SLAVE_ID_KEY && id >= NMASTERS) || + id >= (SLAVE_ID_KEY + NSLAVES)) { + MSM_BUS_ERR("Cannot get iid. Invalid id %d passed\n", id); + return -EINVAL; + } + + return CHECK_ID(((id < SLAVE_ID_KEY) ? master_iids[id] : + slave_iids[id - SLAVE_ID_KEY]), id); +} + +static struct msm_bus_board_algorithm msm_bus_board_algo = { + .board_nfab = NFAB_8960, + .get_iid = msm_bus_board_8960_get_iid, + .assign_iids = msm_bus_board_assign_iids, +}; + +struct msm_bus_fabric_registration msm_bus_8960_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_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, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8960_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_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, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8960_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_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, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8960_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_8960_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +int msm_bus_board_rpm_get_il_ids(uint16_t id[]) +{ + id[0] = MSM_RPM_STATUS_ID_EBI1_CH0_RANGE; + id[1] = MSM_RPM_STATUS_ID_EBI1_CH1_RANGE; + return 0; +} diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_board_9615.c b/arch/arm/mach-msm/msm_bus/msm_bus_board_9615.c new file mode 100644 index 0000000000000000000000000000000000000000..34cb2db255222edfedab2c2bc25e266f6b4c9a61 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_9615.c @@ -0,0 +1,313 @@ +/* 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 14 +#define NSLAVES 12 +#define NFAB_9615 2 + +enum msm_bus_fabric_tiered_slave_type { + MSM_BUS_TIERED_SLAVE_EBI1_CH0 = 1, +}; + +enum msm_bus_9615_master_ports_type { + MSM_BUS_MASTER_PORT_SPS = 0, + MSM_BUS_MASTER_PORT_APSS_PROC, + 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_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_ADM_AHB_CI, +}; + +enum msm_bus_9615_slave_ports_type { + MSM_BUS_SLAVE_PORT_SPS = 0, + MSM_BUS_SLAVE_PORT_EBI1_CH0, + MSM_BUS_SLAVE_PORT_APSS_L2, + MSM_BUS_SLAVE_PORT_SYSTEM_IMEM, + MSM_BUS_SLAVE_PORT_CORESIGHT, + MSM_BUS_SLAVE_PORT_APSS, + MSM_BUS_SLAVE_PORT_MSS, + MSM_BUS_SLAVE_PORT_LPASS, + MSM_BUS_SYSTEM_SLAVE_PORT_CPSS_FPB, + MSM_BUS_SYSTEM_SLAVE_PORT_SYSTEM_FPB, +}; + +static int tier2[] = {MSM_BUS_BW_TIER2,}; +static uint32_t master_iids[NMASTERS]; +static uint32_t slave_iids[NSLAVES]; + +static int sport_ebi1_ch0[] = {MSM_BUS_SLAVE_PORT_EBI1_CH0,}; +static int sport_apss_l2[] = {MSM_BUS_SLAVE_PORT_APSS_L2,}; + +static int tiered_slave_ebi1_ch0[] = {MSM_BUS_TIERED_SLAVE_EBI1_CH0,}; + +static int mport_sps[] = {MSM_BUS_MASTER_PORT_SPS,}; +static int mport_apss_proc[] = {MSM_BUS_MASTER_PORT_APSS_PROC,}; +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 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_adm_ahb_ci[] = {MSM_BUS_SYSTEM_MASTER_PORT_ADM_AHB_CI,}; +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_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_apss[] = {MSM_BUS_SLAVE_PORT_APSS,}; +static int sport_mss[] = {MSM_BUS_SLAVE_PORT_MSS,}; +static int sport_lpass[] = {MSM_BUS_SLAVE_PORT_LPASS,}; + +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_MASTER_AMPSS_M0, + .masterp = mport_apss_proc, + .num_mports = ARRAY_SIZE(mport_apss_proc), + .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_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_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, + .slaveclk[DUAL_CTX] = "dfab_clk", + .slaveclk[ACTIVE_CTX] = "dfab_a_clk", + }, + { + .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] = "mem_clk", + .slaveclk[ACTIVE_CTX] = "mem_a_clk", + }, + { + .id = MSM_BUS_SLAVE_SYSTEM_IMEM, + .slavep = sport_system_imem, + .num_sports = ARRAY_SIZE(sport_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_AMPSS, + .slavep = sport_apss, + .num_sports = ARRAY_SIZE(sport_apss), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_AMPSS_L2, + .slavep = sport_apss_l2, + .num_sports = ARRAY_SIZE(sport_apss_l2), + .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, + }, +}; + +static void msm_bus_board_assign_iids(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; + } +} + +static int msm_bus_board_9615_get_iid(int id) +{ + if ((id < SLAVE_ID_KEY && id >= NMASTERS) || + id >= (SLAVE_ID_KEY + NSLAVES)) { + MSM_BUS_ERR("Cannot get iid. Invalid id %d passed\n", id); + return -EINVAL; + } + + return ((id < SLAVE_ID_KEY) ? master_iids[id] : slave_iids[id - + SLAVE_ID_KEY]); +} + +static struct msm_bus_board_algorithm msm_bus_board_algo = { + .board_nfab = NFAB_9615, + .get_iid = msm_bus_board_9615_get_iid, + .assign_iids = msm_bus_board_assign_iids, +}; + +struct msm_bus_fabric_registration msm_bus_9615_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] = "bus_clk", + .fabclk[ACTIVE_CTX] = "bus_a_clk", + .haltid = MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + .nmasters = 12, + .nslaves = 10, + .ntieredslaves = 1, + .board_algo = &msm_bus_board_algo, +}; + +struct msm_bus_fabric_registration msm_bus_9615_def_fab_pdata = { + .id = MSM_BUS_FAB_DEFAULT, + .name = "msm_def_fab", + .ahb = 1, + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, + .board_algo = &msm_bus_board_algo, +}; + +int msm_bus_board_rpm_get_il_ids(uint16_t id[]) +{ + return -ENXIO; +} 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 0000000000000000000000000000000000000000..28f30736798f8c2f2342755e9b6f63ef72e30a1c --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_config.c @@ -0,0 +1,78 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "AXI: %s(): " fmt, __func__ + +#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 0000000000000000000000000000000000000000..4d73b0357f5834279257b95fa18187263331dd40 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_core.c @@ -0,0 +1,117 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "AXI: %s(): " fmt, __func__ + +#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 0000000000000000000000000000000000000000..341fda8ffebac9b52ed1fe03e4e69071880123fd --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_core.h @@ -0,0 +1,217 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#define MSM_BUS_DBG(msg, ...) \ + pr_debug(msg, ## __VA_ARGS__) +#define MSM_BUS_ERR(msg, ...) \ + pr_err(msg, ## __VA_ARGS__) +#define MSM_BUS_WARN(msg, ...) \ + pr_warn(msg, ## __VA_ARGS__) +#define MSM_FAB_ERR(msg, ...) \ + dev_err(&fabric->fabdev.dev, msg, ## __VA_ARGS__) + +#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) + +#define INTERLEAVED_BW(fab_pdata, bw, ports) \ + ((fab_pdata->il_flag) ? DIV_ROUND_UP((bw), (ports)) : (bw)) +#define INTERLEAVED_VAL(fab_pdata, n) \ + ((fab_pdata->il_flag) ? (n) : 1) + +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; + int hw_sel; + const char *slaveclk[NUM_CTX]; + const char *memclk; + unsigned int buswidth; + unsigned int ws; + unsigned int mode; +}; + +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; + void *hw_data; +}; + +struct msm_bus_hw_algorithm { + int (*allocate_commit_data)(struct msm_bus_fabric_registration + *fab_pdata, void **cdata, int ctx); + void *(*allocate_hw_data)(struct platform_device *pdev, + struct msm_bus_fabric_registration *fab_pdata); + void (*node_init)(void *hw_data, struct msm_bus_inode_info *info); + void (*free_commit_data)(void *cdata); + void (*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 (*fill_cdata_buffer)(int *curr, char *buf, const int max_size, + void *cdata, int nmasters, int nslaves, int ntslaves); + int (*commit)(struct msm_bus_fabric_registration + *fab_pdata, void *hw_data, void **cdata); + int (*port_unhalt)(uint32_t haltid, uint8_t mport); + int (*port_halt)(uint32_t haltid, uint8_t mport); +}; + +struct msm_bus_fabric_device { + int id; + const char *name; + struct device dev; + const struct msm_bus_fab_algorithm *algo; + const struct msm_bus_board_algorithm *board_algo; + struct msm_bus_hw_algorithm hw_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); + 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); +}; + +struct msm_bus_board_algorithm { + const int board_nfab; + void (*assign_iids)(struct msm_bus_fabric_registration *fabreg, + int fabid); + int (*get_iid)(int id); +}; + +/** + * 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); + +void msm_bus_rpm_fill_cdata_buffer(int *curr, char *buf, const int max_size, + void *cdata, int nmasters, int nslaves, int ntslaves); + +int msm_bus_hw_fab_init(struct msm_bus_fabric_registration *pdata, + struct msm_bus_hw_algorithm *hw_algo); +int msm_bus_rpm_hw_init(struct msm_bus_fabric_registration *pdata, + struct msm_bus_hw_algorithm *hw_algo); +int msm_bus_noc_hw_init(struct msm_bus_fabric_registration *pdata, + struct msm_bus_hw_algorithm *hw_algo); +int msm_bus_bimc_hw_init(struct msm_bus_fabric_registration *pdata, + struct msm_bus_hw_algorithm *hw_algo); +#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 0000000000000000000000000000000000000000..76f85c6385f5ae4bab829eed136e7af62576f252 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_dbg.c @@ -0,0 +1,715 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "AXI: %s(): " fmt, __func__ + +#include +#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, 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, *fablist_temp; + struct msm_bus_cldata *cldata = NULL, *cldata_temp; + + debugfs_remove_recursive(dir); + list_for_each_entry_safe(cldata, cldata_temp, &cl_list, list) { + list_del(&cldata->list); + kfree(cldata); + } + mutex_lock(&msm_bus_dbg_fablist_lock); + list_for_each_entry_safe(fablist, fablist_temp, &fabdata_list, list) { + list_del(&fablist->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 0000000000000000000000000000000000000000..8c015d11d0d74b1edef0152055aa53c5a783d0d7 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_fabric.c @@ -0,0 +1,752 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "AXI: %s(): " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +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; + struct msm_bus_fabric_registration *pdata; + void *hw_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_BUS_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); + radix_tree_preload_end(); + 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_sys("msm_bus", + 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; + } + +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) +{ + struct msm_bus_fabnodeinfo *fabnodeinfo; + MSM_BUS_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 == NULL) { + MSM_FAB_ERR("msm_bus_fabric_add_fab: " + "No Node Info\n"); + MSM_FAB_ERR("axi: Cannot register fabric!\n"); + return -ENOMEM; + } + + fabnodeinfo->info = info; + fabnodeinfo->info->num_pnodes = -1; + list_add_tail(&fabnodeinfo->list, &fabric->gateways); + return 0; +} + +/** + * register_fabric_info() - Create the internal fabric structure and + * build the topology tree from platform specific data + * @pdev: Platform device for getting base addresses + * @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 platform_device *pdev, + struct msm_bus_fabric *fabric) +{ + int i = 0, ret = 0, err = 0; + + MSM_BUS_DBG("id:%d pdata-id: %d len: %d\n", fabric->fabdev.id, + fabric->pdata->id, fabric->pdata->len); + fabric->hw_data = fabric->fabdev.hw_algo.allocate_hw_data(pdev, + fabric->pdata); + if (ZERO_OR_NULL_PTR(fabric->hw_data)) { + MSM_BUS_ERR("Couldn't allocate hw_data for fab: %d\n", + fabric->fabdev.id); + goto error; + } + + 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); + if (info == NULL) { + MSM_BUS_ERR("Error allocating info\n"); + return -ENOMEM; + } + + 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_sys("msm_bus", + 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_sys("msm_bus", + 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; + } + + if (fabric->fabdev.hw_algo.node_init == NULL) + continue; + + fabric->fabdev.hw_algo.node_init(fabric->hw_data, info); + if (ret) { + MSM_BUS_ERR("Unable to init node info, ret: %d\n", ret); + kfree(info); + } + } + + MSM_BUS_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_BUS_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; + + /** + * Integration for clock rates is not required if context is not + * same as client's active-only flag + */ + if (ctx != cl_active_flag) + goto skip_set_clks; + + /* 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_BUS_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_BUS_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]; + if (nodeclk->clk) { + 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) { + rate = *pclk; + 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 != rate) { + nodeclk->dirty = true; + nodeclk->rate = rate; + } + } + if (!status && slave->memclk.clk) { + rate = *slave->link_info.sel_clk; + 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; + + /* Temporarily stub out arbitration settings for copper */ + if (machine_is_copper()) + return; + + sel_cdata = fabric->cdata[ctx]; + + /* If it's an ahb fabric, don't calculate arb values */ + if (fabric->ahb) { + MSM_BUS_DBG("AHB fabric, skipping bw calculation\n"); + return; + } + if (!add_bw) { + MSM_BUS_DBG("No bandwidth delta. Skipping commit\n"); + return; + } + + fabdev->hw_algo.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_rate(info->nodeclk[i].clk, info-> + nodeclk[i].rate); + if (enable && !(info->nodeclk[i].enable)) { + clk_prepare_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_unprepare(info->nodeclk[i].clk); + info->nodeclk[i].dirty = false; + info->nodeclk[i].enable = false; + } + } + + if (info->memclk.dirty) { + status = clk_set_rate(info->memclk.clk, info->memclk.rate); + if (enable && !(info->memclk.enable)) { + clk_prepare_enable(info->memclk.clk); + info->memclk.dirty = false; + info->memclk.enable = true; + } else if (info->memclk.rate == 0 && (!enable) && + (info->memclk.enable)) { + clk_disable_unprepare(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_hw_commit() - Commit the arbitration data to Hardware. + * @fabric: Fabric for which the data should be committed + * */ +static int msm_bus_fabric_hw_commit(struct msm_bus_fabric_device *fabdev) +{ + int status = 0; + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + + /* + * 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; + } + + status = fabdev->hw_algo.commit(fabric->pdata, fabric->hw_data, + (void **)fabric->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_inode_info *info = NULL; + uint8_t mport; + uint32_t haltid = 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; + mport = info->node_info->masterp[0]; + + return fabdev->hw_algo.port_halt(haltid, mport); +} + +/** + * 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_inode_info *info = NULL; + uint8_t mport; + uint32_t haltid = 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; + mport = info->node_info->masterp[0]; + return fabdev->hw_algo.port_unhalt(haltid, mport); +} + +/** + * 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_BUS_ERR("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_hw_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_hw_init(struct msm_bus_fabric_registration *pdata, + struct msm_bus_hw_algorithm *hw_algo) +{ + int ret = 0; + ret = msm_bus_rpm_hw_init(pdata, hw_algo); + if (ret) { + MSM_BUS_ERR("RPM initialization failed\n"); + ret = -EINVAL; + } + + return ret; +} + +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 (ZERO_OR_NULL_PTR(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; + ret = msm_bus_fabric_hw_init(pdata, &fabric->fabdev.hw_algo); + if (ret) { + MSM_BUS_ERR("Error initializing hardware for fabric: %d\n", + fabric->fabdev.id); + goto err; + } + + fabric->ahb = pdata->ahb; + fabric->pdata = pdata; + fabric->pdata->board_algo->assign_iids(fabric->pdata, + fabric->fabdev.id); + fabric->fabdev.board_algo = fabric->pdata->board_algo; + + /* + * 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; + } + + for (ctx = 0; ctx < NUM_CTX; ctx++) { + if (pdata->fabclk[ctx]) { + fabric->info.nodeclk[ctx].clk = clk_get( + &fabric->fabdev.dev, 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(pdev, 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 = fabric->fabdev.hw_algo.allocate_commit_data( + fabric->pdata, &fabric->cdata[ctx], ctx); + if (ret) { + MSM_BUS_ERR("Failed to alloc commit data for " + "fab: %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) { + fabdev->hw_algo.free_commit_data(fabric->cdata[DUAL_CTX]); + fabdev->hw_algo.free_commit_data(fabric->cdata[ACTIVE_CTX]); + } + + kfree(fabric->info.node_info); + kfree(fabric->hw_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 0000000000000000000000000000000000000000..4653431dd2c5341a77750b05c6cedc63fc8f4696 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_rpm.c @@ -0,0 +1,964 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "AXI: %s(): " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" +#include "../rpm_resources.h" + +void msm_bus_rpm_set_mt_mask() +{ +#ifdef CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED + struct msm_rpm_iv_pair mt[1]; + int mask = MSM_RPMRS_MASK_RPM_CTL_MULTI_TIER; + mt[0].id = MSM_RPM_ID_RPM_CTL; + mt[0].value = 2; + msm_rpmrs_set_bits_noirq(MSM_RPM_CTX_SET_0, mt, 1, + &mask); +#endif +} + +bool msm_bus_rpm_is_mem_interleaved(void) +{ + int status = 0; + struct msm_rpm_iv_pair il[2]; + uint16_t id[2]; + + il[0].value = 0; + il[1].value = 0; + status = msm_bus_board_rpm_get_il_ids(id); + if (status) { + MSM_BUS_DBG("Dynamic check not supported, " + "default: Interleaved memory\n"); + goto inter; + } + + il[0].id = id[0]; + il[1].id = id[1]; + status = msm_rpm_get_status(il, ARRAY_SIZE(il)); + if (status) { + MSM_BUS_ERR("Status read for interleaving returned: %d\n" + "Using interleaved memory by default\n", + status); + goto inter; + } + + /* + * If the start address of EBI1-CH0 is the same as + * the start address of EBI1-CH1, the memory is interleaved. + * The start addresses are stored in the 16 MSBs of the status + * register + */ + if ((il[0].value & 0xFFFF0000) != (il[1].value & 0xFFFF0000)) { + MSM_BUS_DBG("Non-interleaved memory\n"); + return false; + } + +inter: + MSM_BUS_DBG("Interleaved memory\n"); + return true; +} + +#ifndef CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED +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)) + +static 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; +} + +static 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 + */ +static int msm_bus_rpm_allocate_commit_data(struct msm_bus_fabric_registration + *fab_pdata, void **cdata, int ctx) +{ + struct commit_data **cd = (struct commit_data **)cdata; + *cd = kzalloc(sizeof(struct commit_data), GFP_KERNEL); + if (!*cd) { + MSM_BUS_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_BUS_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_BUS_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_BUS_DBG("Couldn't alloc memory for" + " slaves\n"); + kfree((*cd)->bwsum); + kfree((*cd)->arb); + kfree(*cd); + return -ENOMEM; + } + + return 0; +} + +static 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 + */ +static void *msm_bus_rpm_allocate_rpm_data(struct platform_device *pdev, + 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 (void *)rpm_data; +} + +#define BWMASK 0x7FFF +#define TIERMASK 0x8000 +#define GET_TIER(n) (((n) & TIERMASK) >> 15) + +static 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, tiers, ports; + struct commit_data *sel_cd = (struct commit_data *)sel_cdata; + + add_bw = INTERLEAVED_BW(fab_pdata, add_bw, info->node_info->num_mports); + ports = INTERLEAVED_VAL(fab_pdata, info->node_info->num_mports); + tiers = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_tiers); + for (i = 0; i < tiers; i++) { + for (j = 0; j < ports; j++) { + uint16_t hop_tier; + /* + * For interleaved gateway ports and slave ports, + * there is one-one mapping between gateway port and + * the slave port + */ + if (info->node_info->gateway && i != j && + (hop->node_info->num_sports > 1)) + continue; + + 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; + + /* + * Make sure gateway to slave port bandwidth + * is not divided when slave is interleaved + */ + if (info->node_info->gateway + && hop->node_info->num_sports > 1) + tieredbw += add_bw; + else + tieredbw += INTERLEAVED_BW(fab_pdata, + add_bw, hop->node_info-> + num_sports); + + /* 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 */ + ports = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_sports); + for (i = 0; i < ports; 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) +static int msm_bus_rpm_compare_cdata( + struct msm_bus_fabric_registration *fab_pdata, + struct commit_data *cd1, struct commit_data *cd2) +{ + size_t n; + int ret; + n = sizeof(uint16_t) * fab_pdata->nslaves; + ret = memcmp(cd1->bwsum, cd2->bwsum, n); + if (ret) { + MSM_BUS_DBG("Commit Data bwsum not equal\n"); + return ret; + } + + n = sizeof(uint16_t *) * ((fab_pdata->ntieredslaves * + fab_pdata->nmasters) + 1); + ret = memcmp(cd1->arb, cd2->arb, n); + if (ret) { + MSM_BUS_DBG("Commit Data arb[%d] not equal\n", n); + return ret; + } + + return 0; +} + +static int msm_bus_rpm_commit_arb(struct msm_bus_fabric_registration + *fab_pdata, int ctx, struct msm_rpm_iv_pair *rpm_data, + struct commit_data *cd, bool valid) +{ + int i, j, offset = 0, status = 0, count, index = 0; + /* + * 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 - 1); 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_BUS_DBG("rpm data for fab: %d\n", fab_pdata->id); + for (i = 0; i < count; i++) + MSM_BUS_DBG("%d %x\n", rpm_data[i].id, rpm_data[i].value); + + MSM_BUS_DBG("Commit Data: Fab: %d BWSum:\n", fab_pdata->id); + for (i = 0; i < fab_pdata->nslaves; i++) + MSM_BUS_DBG("fab_slaves:0x%x\n", cd->bwsum[i]); + MSM_BUS_DBG("Commit Data: Fab: %d Arb:\n", fab_pdata->id); + for (i = 0; i < fab_pdata->ntieredslaves; i++) { + MSM_BUS_DBG("tiered-slave: %d\n", i); + for (j = 0; j < fab_pdata->nmasters; j++) + MSM_BUS_DBG(" 0x%x\n", + cd->arb[(i * fab_pdata->nmasters) + j]); + } + + MSM_BUS_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 (valid) { + if (ctx == ACTIVE_CTX) { + status = msm_rpm_set(MSM_RPM_CTX_SET_0, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_set returned: %d\n", + status); + } else if (ctx == DUAL_CTX) { + status = msm_rpm_set(MSM_RPM_CTX_SET_SLEEP, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_set returned: %d\n", + status); + } + } else { + if (ctx == ACTIVE_CTX) { + status = msm_rpm_clear(MSM_RPM_CTX_SET_0, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_clear returned: %d\n", + status); + } else if (ctx == DUAL_CTX) { + status = msm_rpm_clear(MSM_RPM_CTX_SET_SLEEP, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_clear returned: %d\n", + status); + } + } + } + + return status; +} + +#else + +#define NUM_TIERS 2 +#define RPM_SHIFT24(n) ((n) << 24) +#define RPM_SHIFT16(n) ((n) << 16) +#define RPM_SHIFT8(n) ((n) << 8) +struct commit_data { + uint16_t *bwsum; + uint8_t *arb[NUM_TIERS]; + unsigned long *actarb[NUM_TIERS]; +}; + +#define MODE_BIT(val) ((val) & 0x80) +#define MODE0_IMM(val) ((val) & 0xF) +#define MODE0_SHIFT(val) (((val) & 0x70) >> 4) +#define MODE1_STEP 48 /* 48 MB */ +#define MODE1_OFFSET 512 /* 512 MB */ +#define MODE1_IMM(val) ((val) & 0x7F) +#define __CLZ(x) ((8 * sizeof(uint32_t)) - 1 - __fls(x)) + +static uint8_t msm_bus_set_bw_bytes(unsigned long val) +{ + unsigned int shift; + unsigned int intVal; + unsigned char result; + + /* Convert to MB */ + intVal = (unsigned int)((val + ((1 << 20) - 1)) >> 20); + /** + * Divide by 2^20 and round up + * A value graeter than 0x1E0 will round up to 512 and overflow + * Mode 0 so it should be made Mode 1 + */ + if (0x1E0 > intVal) { + /** + * MODE 0 + * Compute the shift value + * Shift value is 32 - the number of leading zeroes - + * 4 to save the most significant 4 bits of the value + */ + shift = 32 - 4 - min((uint8_t)28, (uint8_t)__CLZ(intVal)); + + /* Add min value - 1 to force a round up when shifting right */ + intVal += (1 << shift) - 1; + + /* Recompute the shift value in case there was an overflow */ + shift = 32 - 4 - min((uint8_t)28, (uint8_t)__CLZ(intVal)); + + /* Clear the mode bit (msb) and fill in the fields */ + result = ((0x70 & (shift << 4)) | + (0x0F & (intVal >> shift))); + } else { + /* MODE 1 */ + result = (unsigned char)(0x80 | + ((intVal - MODE1_OFFSET + MODE1_STEP - 1) / + MODE1_STEP)); + } + + return result; +} + +uint64_t msm_bus_get_bw(unsigned long val) +{ + return MODE_BIT(val) ? + /* Mode 1 */ + (MODE1_IMM(val) * MODE1_STEP + MODE1_OFFSET) : + /* Mode 0 */ + (MODE0_IMM(val) << MODE0_SHIFT(val)); +} + +uint64_t msm_bus_get_bw_bytes(unsigned long val) +{ + return msm_bus_get_bw(val) << 20; +} + +static uint8_t msm_bus_create_bw_tier_pair_bytes(uint8_t type, + unsigned long bw) +{ + return msm_bus_set_bw_bytes(bw); +}; + +uint8_t msm_bus_create_bw_tier_pair(uint8_t type, unsigned long bw) +{ + return msm_bus_create_bw_tier_pair_bytes(type, bw); +}; + +static int msm_bus_rpm_allocate_commit_data(struct msm_bus_fabric_registration + *fab_pdata, void **cdata, int ctx) +{ + struct commit_data **cd = (struct commit_data **)cdata; + int i; + + *cd = kzalloc(sizeof(struct commit_data), GFP_KERNEL); + if (!*cd) { + MSM_BUS_DBG("Couldn't alloc mem for cdata\n"); + goto cdata_err; + } + + (*cd)->bwsum = kzalloc((sizeof(uint16_t) * fab_pdata->nslaves), + GFP_KERNEL); + if (!(*cd)->bwsum) { + MSM_BUS_DBG("Couldn't alloc mem for slaves\n"); + goto bwsum_err; + } + + for (i = 0; i < NUM_TIERS; i++) { + (*cd)->arb[i] = kzalloc(((sizeof(uint8_t *)) * + (fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1), + GFP_KERNEL); + if (!(*cd)->arb[i]) { + MSM_BUS_DBG("Couldn't alloc memory for" + " slaves\n"); + goto arb_err; + } + + (*cd)->actarb[i] = kzalloc(((sizeof(unsigned long *)) * + (fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1), + GFP_KERNEL); + if (!(*cd)->actarb[i]) { + MSM_BUS_DBG("Couldn't alloc memory for" + " slaves\n"); + kfree((*cd)->arb[i]); + goto arb_err; + } + } + + + return 0; + +arb_err: + for (i = i - 1; i >= 0; i--) { + kfree((*cd)->arb[i]); + kfree((*cd)->actarb[i]); + } +bwsum_err: + kfree((*cd)->bwsum); +cdata_err: + kfree(*cd); + return -ENOMEM; +} + +static void free_commit_data(void *cdata) +{ + int i; + struct commit_data *cd = (struct commit_data *)cdata; + kfree(cd->bwsum); + for (i = 0; i < NUM_TIERS; i++) { + kfree(cd->arb[i]); + kfree(cd->actarb[i]); + } + kfree(cd); +} + +static void *msm_bus_rpm_allocate_rpm_data(struct platform_device *pdev, + struct msm_bus_fabric_registration *fab_pdata) +{ + struct msm_rpm_iv_pair *rpm_data; + uint16_t count = (((fab_pdata->nmasters * fab_pdata->ntieredslaves * + NUM_TIERS)/2) + fab_pdata->nslaves + 1)/2; + + rpm_data = kmalloc((sizeof(struct msm_rpm_iv_pair) * count), + GFP_KERNEL); + return (void *)rpm_data; +} + +static int msm_bus_rpm_compare_cdata( + struct msm_bus_fabric_registration *fab_pdata, + struct commit_data *cd1, struct commit_data *cd2) +{ + size_t n; + int i, ret; + n = sizeof(uint16_t) * fab_pdata->nslaves; + ret = memcmp(cd1->bwsum, cd2->bwsum, n); + if (ret) { + MSM_BUS_DBG("Commit Data bwsum not equal\n"); + return ret; + } + + n = sizeof(uint8_t *) * ((fab_pdata->ntieredslaves * + fab_pdata->nmasters) + 1); + for (i = 0; i < NUM_TIERS; i++) { + ret = memcmp(cd1->arb[i], cd2->arb[i], n); + if (ret) { + MSM_BUS_DBG("Commit Data arb[%d] not equal\n", i); + return ret; + } + } + + return 0; +} + +static int msm_bus_rpm_commit_arb(struct msm_bus_fabric_registration + *fab_pdata, int ctx, struct msm_rpm_iv_pair *rpm_data, + struct commit_data *cd, bool valid) +{ + int i, j, k, offset = 0, status = 0, count, index = 0; + /* + * 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][num_tiers] + * 2. bwsum[nslaves] + */ + count = (((fab_pdata->nmasters * fab_pdata->ntieredslaves * NUM_TIERS) + /2) + 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 - 1); i += 2) { + rpm_data[index].id = offset + index; + rpm_data[index].value = RPM_SHIFT16(*(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 = RPM_SHIFT8(*cd->arb[1]) | + *(cd->arb[0]); + rpm_data[index].value = RPM_SHIFT16(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) { + uint16_t tv1, tv0; + rpm_data[index].id = offset + index; + tv0 = RPM_SHIFT8(*(cd->arb[1] + i)) | (*(cd->arb[0] + i)); + tv1 = RPM_SHIFT8(*(cd->arb[1] + i + 1)) | (*(cd->arb[0] + i + + 1)); + rpm_data[index].value = RPM_SHIFT16(tv1) | tv0; + index++; + } + + MSM_BUS_DBG("rpm data for fab: %d\n", fab_pdata->id); + for (i = 0; i < count; i++) + MSM_BUS_DBG("%d %x\n", rpm_data[i].id, rpm_data[i].value); + + MSM_BUS_DBG("Commit Data: Fab: %d BWSum:\n", fab_pdata->id); + for (i = 0; i < fab_pdata->nslaves; i++) + MSM_BUS_DBG("fab_slaves:0x%x\n", cd->bwsum[i]); + MSM_BUS_DBG("Commit Data: Fab: %d Arb:\n", fab_pdata->id); + for (k = 0; k < NUM_TIERS; k++) { + MSM_BUS_DBG("Tier: %d\n", k); + for (i = 0; i < fab_pdata->ntieredslaves; i++) { + MSM_BUS_DBG("tiered-slave: %d\n", i); + for (j = 0; j < fab_pdata->nmasters; j++) + MSM_BUS_DBG(" 0x%x\n", + cd->arb[k][(i * fab_pdata->nmasters) + + j]); + } + } + + MSM_BUS_DBG("calling msm_rpm_set: %d\n", status); + msm_bus_dbg_commit_data(fab_pdata->name, (void *)cd, fab_pdata-> + nmasters, fab_pdata->nslaves, fab_pdata->ntieredslaves, + MSM_BUS_DBG_OP); + if (fab_pdata->rpm_enabled) { + if (valid) { + if (ctx == ACTIVE_CTX) { + status = msm_rpm_set(MSM_RPM_CTX_SET_0, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_set returned: %d\n", + status); + } else if (ctx == DUAL_CTX) { + status = msm_rpm_set(MSM_RPM_CTX_SET_SLEEP, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_set returned: %d\n", + status); + } + } else { + if (ctx == ACTIVE_CTX) { + status = msm_rpm_clear(MSM_RPM_CTX_SET_0, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_clear returned: %d\n", + status); + } else if (ctx == DUAL_CTX) { + status = msm_rpm_clear(MSM_RPM_CTX_SET_SLEEP, + rpm_data, count); + MSM_BUS_DBG("msm_rpm_clear returned: %d\n", + status); + } + } + } + + return status; +} + +#define FORMAT_BW(x) \ + ((x < 0) ? \ + -(msm_bus_get_bw_bytes(msm_bus_create_bw_tier_pair_bytes(0, -(x)))) : \ + (msm_bus_get_bw_bytes(msm_bus_create_bw_tier_pair_bytes(0, x)))) + +static uint16_t msm_bus_pack_bwsum_bytes(unsigned long bw) +{ + return (bw + ((1 << 20) - 1)) >> 20; +}; + +static 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, tiers, ports; + struct commit_data *sel_cd = (struct commit_data *)sel_cdata; + + add_bw = INTERLEAVED_BW(fab_pdata, add_bw, info->node_info->num_mports); + ports = INTERLEAVED_VAL(fab_pdata, info->node_info->num_mports); + tiers = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_tiers); + for (i = 0; i < tiers; i++) { + for (j = 0; j < ports; j++) { + uint16_t hop_tier; + /* + * For interleaved gateway ports and slave ports, + * there is one-one mapping between gateway port and + * the slave port + */ + if (info->node_info->gateway && i != j + && hop->node_info->num_sports > 1) + continue; + + 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; + if (master_tiers) + tier = master_tiers[0] - 1; + else + tier = MSM_BUS_BW_TIER2 - 1; + + tieredbw = sel_cd->actarb[tier][index]; + /* + * Make sure gateway to slave port bandwidth + * is not divided when slave is interleaved + */ + if (info->node_info->gateway + && hop->node_info->num_sports > 1) + tieredbw += add_bw; + else + tieredbw += INTERLEAVED_BW(fab_pdata, + add_bw, hop->node_info-> + num_sports); + + /* Update Arb for fab,get HW Mport from enum */ + sel_cd->arb[tier][index] = + msm_bus_create_bw_tier_pair_bytes(0, tieredbw); + sel_cd->actarb[tier][index] = tieredbw; + MSM_BUS_DBG("tier:%d mport: %d tiered_bw:%lu " + "bwsum: %ld\n", hop_tier, info->node_info-> + masterp[i], tieredbw, *hop->link_info.sel_bw); + } + } + } + + /* Update bwsum for slaves on fabric */ + + ports = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_sports); + for (i = 0; i < ports; i++) { + sel_cd->bwsum[hop->node_info->slavep[i]] + = msm_bus_pack_bwsum_bytes((*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)); + } +} + + +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, k, 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 (k = 0; k < NUM_TIERS; k++) { + *curr += scnprintf(buf + *curr, max_size - *curr, + "\nTier %d:\n", k); + for (c = 0; c < ntslaves; c++) { + *curr += scnprintf(buf + *curr, max_size - *curr, + "TSlave %d:\n", c); + for (j = 0; j < nmasters; j++) + *curr += scnprintf(buf + *curr, max_size - + *curr, " 0x%x\t", + cd->arb[k][(c * nmasters) + j]); + } + } +} +#endif + +/** +* msm_bus_rpm_commit() - Commit the arbitration data to RPM +* @fabric: Fabric for which the data should be committed +**/ +static int msm_bus_rpm_commit(struct msm_bus_fabric_registration + *fab_pdata, void *hw_data, void **cdata) +{ + + int ret; + bool valid; + struct commit_data *dual_cd, *act_cd; + struct msm_rpm_iv_pair *rpm_data = (struct msm_rpm_iv_pair *)hw_data; + dual_cd = (struct commit_data *)cdata[DUAL_CTX]; + act_cd = (struct commit_data *)cdata[ACTIVE_CTX]; + + /* + * If the arb data for active set and sleep set is + * different, commit both sets. + * If the arb data for active set and sleep set is + * the same, invalidate the sleep set. + */ + ret = msm_bus_rpm_compare_cdata(fab_pdata, act_cd, dual_cd); + if (!ret) + /* Invalidate sleep set.*/ + valid = false; + else + valid = true; + + ret = msm_bus_rpm_commit_arb(fab_pdata, DUAL_CTX, rpm_data, + dual_cd, valid); + if (ret) + MSM_BUS_ERR("Error comiting fabric:%d in %d ctx\n", + fab_pdata->id, DUAL_CTX); + + valid = true; + ret = msm_bus_rpm_commit_arb(fab_pdata, ACTIVE_CTX, rpm_data, act_cd, + valid); + if (ret) + MSM_BUS_ERR("Error comiting fabric:%d in %d ctx\n", + fab_pdata->id, ACTIVE_CTX); + + return ret; +} + +static int msm_bus_rpm_port_halt(uint32_t haltid, uint8_t mport) +{ + int status = 0; + struct msm_bus_halt_vector hvector = {0, 0}; + struct msm_rpm_iv_pair rpm_data[2]; + + 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_BUS_DBG("ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_0, rpm_data[0].id, rpm_data[0].value); + MSM_BUS_DBG("ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_0, rpm_data[1].id, rpm_data[1].value); + + status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, 2); + if (status) + MSM_BUS_DBG("msm_rpm_set returned: %d\n", status); + return status; +} + +static int msm_bus_rpm_port_unhalt(uint32_t haltid, uint8_t mport) +{ + int status = 0; + struct msm_bus_halt_vector hvector = {0, 0}; + struct msm_rpm_iv_pair rpm_data[2]; + + 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_BUS_DBG("unalt: ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_SLEEP, rpm_data[0].id, rpm_data[0].value); + MSM_BUS_DBG("unhalt: ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_SLEEP, rpm_data[1].id, rpm_data[1].value); + + status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, 2); + if (status) + MSM_BUS_DBG("msm_rpm_set returned: %d\n", status); + return status; +} + +int msm_bus_rpm_hw_init(struct msm_bus_fabric_registration *pdata, + struct msm_bus_hw_algorithm *hw_algo) +{ + pdata->il_flag = msm_bus_rpm_is_mem_interleaved(); + hw_algo->allocate_commit_data = msm_bus_rpm_allocate_commit_data; + hw_algo->allocate_hw_data = msm_bus_rpm_allocate_rpm_data; + hw_algo->node_init = NULL; + hw_algo->free_commit_data = free_commit_data; + hw_algo->update_bw = msm_bus_rpm_update_bw; + hw_algo->commit = msm_bus_rpm_commit; + hw_algo->port_halt = msm_bus_rpm_port_halt; + hw_algo->port_unhalt = msm_bus_rpm_port_unhalt; + if (!pdata->ahb) + pdata->rpm_enabled = 1; + return 0; +} diff --git a/arch/arm/mach-msm/msm_cache_dump.c b/arch/arm/mach-msm/msm_cache_dump.c new file mode 100644 index 0000000000000000000000000000000000000000..9759d5acabf348083ffaa6d2e20e8ed2d56fe793 --- /dev/null +++ b/arch/arm/mach-msm/msm_cache_dump.c @@ -0,0 +1,148 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +#define L2_DUMP_OFFSET 0x14 + +static unsigned long msm_cache_dump_addr; + +/* + * These should not actually be dereferenced. There's no + * need for a virtual mapping, but the physical address is + * necessary. + */ +static struct l1_cache_dump *l1_dump; +static struct l2_cache_dump *l2_dump; + +static int msm_cache_dump_panic(struct notifier_block *this, + unsigned long event, void *ptr) +{ +#ifdef CONFIG_MSM_CACHE_DUMP_ON_PANIC + /* + * Clear the bootloader magic so the dumps aren't overwritten + */ + __raw_writel(0, MSM_IMEM_BASE + L2_DUMP_OFFSET); + + scm_call_atomic1(L1C_SERVICE_ID, CACHE_BUFFER_DUMP_COMMAND_ID, 2); + scm_call_atomic1(L1C_SERVICE_ID, CACHE_BUFFER_DUMP_COMMAND_ID, 1); +#endif + return 0; +} + +static struct notifier_block msm_cache_dump_blk = { + .notifier_call = msm_cache_dump_panic, + /* + * higher priority to ensure this runs before another panic handler + * flushes the caches. + */ + .priority = 1, +}; + +static int msm_cache_dump_probe(struct platform_device *pdev) +{ + struct msm_cache_dump_platform_data *d = pdev->dev.platform_data; + int ret; + struct { + unsigned long buf; + unsigned long size; + } l1_cache_data; + void *temp; + unsigned long total_size = d->l1_size + d->l2_size; + + msm_cache_dump_addr = allocate_contiguous_ebi_nomap(total_size, SZ_4K); + + if (!msm_cache_dump_addr) { + pr_err("%s: Could not get memory for cache dumping\n", + __func__); + return -ENOMEM; + } + + temp = ioremap(msm_cache_dump_addr, total_size); + memset(temp, 0xFF, total_size); + iounmap(temp); + + l1_cache_data.buf = msm_cache_dump_addr; + l1_cache_data.size = d->l1_size; + + ret = scm_call(L1C_SERVICE_ID, L1C_BUFFER_SET_COMMAND_ID, + &l1_cache_data, sizeof(l1_cache_data), NULL, 0); + + if (ret) + pr_err("%s: could not register L1 buffer ret = %d.\n", + __func__, ret); + + l1_dump = (struct l1_cache_dump *)msm_cache_dump_addr; + +#if defined(CONFIG_MSM_CACHE_DUMP_ON_PANIC) + l1_cache_data.buf = msm_cache_dump_addr + d->l1_size; + l1_cache_data.size = d->l2_size; + + ret = scm_call(L1C_SERVICE_ID, L2C_BUFFER_SET_COMMAND_ID, + &l1_cache_data, sizeof(l1_cache_data), NULL, 0); + + if (ret) + pr_err("%s: could not register L2 buffer ret = %d.\n", + __func__, ret); +#endif + __raw_writel(msm_cache_dump_addr + d->l1_size, + MSM_IMEM_BASE + L2_DUMP_OFFSET); + + + l2_dump = (struct l2_cache_dump *)(msm_cache_dump_addr + d->l1_size); + + atomic_notifier_chain_register(&panic_notifier_list, + &msm_cache_dump_blk); + return 0; +} + +static int msm_cache_dump_remove(struct platform_device *pdev) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + &msm_cache_dump_blk); + return 0; +} + +static struct platform_driver msm_cache_dump_driver = { + .remove = __devexit_p(msm_cache_dump_remove), + .driver = { + .name = "msm_cache_dump", + .owner = THIS_MODULE + }, +}; + +static int __init msm_cache_dump_init(void) +{ + return platform_driver_probe(&msm_cache_dump_driver, + msm_cache_dump_probe); +} + +static void __exit msm_cache_dump_exit(void) +{ + platform_driver_unregister(&msm_cache_dump_driver); +} +late_initcall(msm_cache_dump_init); +module_exit(msm_cache_dump_exit) diff --git a/arch/arm/mach-msm/msm_dcvs.c b/arch/arm/mach-msm/msm_dcvs.c new file mode 100644 index 0000000000000000000000000000000000000000..0c158de57ac85e5f957fd83c458625a7d20c89d0 --- /dev/null +++ b/arch/arm/mach-msm/msm_dcvs.c @@ -0,0 +1,791 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 CORE_HANDLE_OFFSET (0xA0) +#define __err(f, ...) pr_err("MSM_DCVS: %s: " f, __func__, __VA_ARGS__) +#define __info(f, ...) pr_info("MSM_DCVS: %s: " f, __func__, __VA_ARGS__) +#define MAX_PENDING (5) + +enum { + MSM_DCVS_DEBUG_NOTIFIER = BIT(0), + MSM_DCVS_DEBUG_IDLE_PULSE = BIT(1), + MSM_DCVS_DEBUG_FREQ_CHANGE = BIT(2), +}; + +struct core_attribs { + struct kobj_attribute idle_enabled; + struct kobj_attribute freq_change_enabled; + struct kobj_attribute actual_freq; + struct kobj_attribute freq_change_us; + + struct kobj_attribute max_time_us; + + struct kobj_attribute slack_time_us; + struct kobj_attribute scale_slack_time; + struct kobj_attribute scale_slack_time_pct; + struct kobj_attribute disable_pc_threshold; + struct kobj_attribute em_window_size; + struct kobj_attribute em_max_util_pct; + struct kobj_attribute ss_window_size; + struct kobj_attribute ss_util_pct; + struct kobj_attribute ss_iobusy_conv; + + struct attribute_group attrib_group; +}; + +struct dcvs_core { + char core_name[CORE_NAME_MAX]; + uint32_t new_freq[MAX_PENDING]; + uint32_t actual_freq; + uint32_t freq_change_us; + + uint32_t max_time_us; /* core param */ + + struct msm_dcvs_algo_param algo_param; + struct msm_dcvs_idle *idle_driver; + struct msm_dcvs_freq *freq_driver; + + /* private */ + int64_t time_start; + struct mutex lock; + spinlock_t cpu_lock; + struct task_struct *task; + struct core_attribs attrib; + uint32_t handle; + uint32_t group_id; + uint32_t freq_pending; + struct hrtimer timer; + int32_t timer_disabled; + /* track if kthread for change_freq is active */ + int32_t change_freq_activated; +}; + +static int msm_dcvs_debug; +static int msm_dcvs_enabled = 1; +module_param_named(enable, msm_dcvs_enabled, int, S_IRUGO | S_IWUSR | S_IWGRP); + +static struct dentry *debugfs_base; + +static struct dcvs_core core_list[CORES_MAX]; +static DEFINE_MUTEX(core_list_lock); + +static struct kobject *cores_kobj; +static struct dcvs_core *core_handles[CORES_MAX]; + +/* Change core frequency, called with core mutex locked */ +static int __msm_dcvs_change_freq(struct dcvs_core *core) +{ + int ret = 0; + unsigned long flags = 0; + unsigned int requested_freq = 0; + unsigned int prev_freq = 0; + int64_t time_start = 0; + int64_t time_end = 0; + uint32_t slack_us = 0; + uint32_t ret1 = 0; + + if (!core->freq_driver || !core->freq_driver->set_frequency) { + /* Core may have unregistered or hotplugged */ + return -ENODEV; + } +repeat: + spin_lock_irqsave(&core->cpu_lock, flags); + if (unlikely(!core->freq_pending)) { + spin_unlock_irqrestore(&core->cpu_lock, flags); + return ret; + } + requested_freq = core->new_freq[core->freq_pending - 1]; + if (unlikely(core->freq_pending > 1) && + (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE)) { + int i; + for (i = 0; i < core->freq_pending - 1; i++) { + __info("Core %s missing freq %u\n", + core->core_name, core->new_freq[i]); + } + } + time_start = core->time_start; + core->time_start = 0; + core->freq_pending = 0; + /** + * Cancel the timers, we dont want the timer firing as we are + * changing the clock rate. Dont let idle_exit and others setup + * timers as well. + */ + hrtimer_cancel(&core->timer); + core->timer_disabled = 1; + spin_unlock_irqrestore(&core->cpu_lock, flags); + + if (requested_freq == core->actual_freq) + return ret; + + /** + * Call the frequency sink driver to change the frequency + * We will need to get back the actual frequency in KHz and + * the record the time taken to change it. + */ + ret = core->freq_driver->set_frequency(core->freq_driver, + requested_freq); + if (ret <= 0) { + __err("Core %s failed to set freq %u\n", + core->core_name, requested_freq); + /* continue to call TZ to get updated slack timer */ + } else { + prev_freq = core->actual_freq; + core->actual_freq = ret; + } + + time_end = ktime_to_ns(ktime_get()); + if (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE) + __info("Core %s Time end %llu Time start: %llu\n", + core->core_name, time_end, time_start); + time_end -= time_start; + do_div(time_end, NSEC_PER_USEC); + core->freq_change_us = (uint32_t)time_end; + + /** + * Disable low power modes if the actual frequency is > + * disable_pc_threshold. + */ + if (core->actual_freq > + core->algo_param.disable_pc_threshold) { + core->idle_driver->enable(core->idle_driver, + MSM_DCVS_DISABLE_HIGH_LATENCY_MODES); + if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) + __info("Disabling LPM for %s\n", core->core_name); + } else if (core->actual_freq <= + core->algo_param.disable_pc_threshold) { + core->idle_driver->enable(core->idle_driver, + MSM_DCVS_ENABLE_HIGH_LATENCY_MODES); + if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) + __info("Enabling LPM for %s\n", core->core_name); + } + + /** + * Update algorithm with new freq and time taken to change + * to this frequency and that will get us the new slack + * timer + */ + ret = msm_dcvs_scm_event(core->handle, MSM_DCVS_SCM_CLOCK_FREQ_UPDATE, + core->actual_freq, (uint32_t)time_end, &slack_us, &ret1); + if (!ret) { + /* Reset the slack timer */ + if (slack_us) { + core->timer_disabled = 0; + ret = hrtimer_start(&core->timer, + ktime_set(0, slack_us * 1000), + HRTIMER_MODE_REL_PINNED); + if (ret) + __err("Failed to register timer for core %s\n", + core->core_name); + } + } else { + __err("Error sending core (%s) freq change (%u)\n", + core->core_name, core->actual_freq); + } + + if (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE) + __info("Freq %u requested for core %s (actual %u prev %u) " + "change time %u us slack time %u us\n", + requested_freq, core->core_name, + core->actual_freq, prev_freq, + core->freq_change_us, slack_us); + + /** + * By the time we are done with freq changes, we could be asked to + * change again. Check before exiting. + */ + if (core->freq_pending) + goto repeat; + + core->change_freq_activated = 0; + return ret; +} + +static int msm_dcvs_do_freq(void *data) +{ + struct dcvs_core *core = (struct dcvs_core *)data; + static struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1}; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + set_current_state(TASK_UNINTERRUPTIBLE); + + while (!kthread_should_stop()) { + mutex_lock(&core->lock); + __msm_dcvs_change_freq(core); + mutex_unlock(&core->lock); + + schedule(); + + if (kthread_should_stop()) + break; + + set_current_state(TASK_UNINTERRUPTIBLE); + } + + __set_current_state(TASK_RUNNING); + + return 0; +} + +static int msm_dcvs_update_freq(struct dcvs_core *core, + enum msm_dcvs_scm_event event, uint32_t param0, + uint32_t *ret1, int *freq_changed) +{ + int ret = 0; + unsigned long flags = 0; + uint32_t new_freq = 0; + + spin_lock_irqsave(&core->cpu_lock, flags); + ret = msm_dcvs_scm_event(core->handle, event, param0, + core->actual_freq, &new_freq, ret1); + if (ret) { + __err("Error (%d) sending SCM event %d for core %s\n", + ret, event, core->core_name); + goto freq_done; + } + + if ((core->actual_freq != new_freq) && + (core->new_freq[core->freq_pending] != new_freq)) { + if (core->freq_pending >= MAX_PENDING - 1) + core->freq_pending = MAX_PENDING - 1; + core->new_freq[core->freq_pending++] = new_freq; + core->time_start = ktime_to_ns(ktime_get()); + + /* Schedule the frequency change */ + if (!core->task) + __err("Uninitialized task for core %s\n", + core->core_name); + else { + if (freq_changed) + *freq_changed = 1; + core->change_freq_activated = 1; + wake_up_process(core->task); + } + } else { + if (freq_changed) + *freq_changed = 0; + } +freq_done: + spin_unlock_irqrestore(&core->cpu_lock, flags); + + return ret; +} + +static enum hrtimer_restart msm_dcvs_core_slack_timer(struct hrtimer *timer) +{ + int ret = 0; + struct dcvs_core *core = container_of(timer, struct dcvs_core, timer); + uint32_t ret1; + uint32_t ret2; + + if (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE) + __info("Slack timer fired for core %s\n", core->core_name); + + /** + * Timer expired, notify TZ + * Dont care about the third arg. + */ + ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_QOS_TIMER_EXPIRED, 0, + &ret1, &ret2); + if (ret) + __err("Timer expired for core %s but failed to notify.\n", + core->core_name); + + return HRTIMER_NORESTART; +} + +/* Helper functions and macros for sysfs nodes for a core */ +#define CORE_FROM_ATTRIBS(attr, name) \ + container_of(container_of(attr, struct core_attribs, name), \ + struct dcvs_core, attrib); + +#define DCVS_PARAM_SHOW(_name, v) \ +static ssize_t msm_dcvs_attr_##_name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ +{ \ + struct dcvs_core *core = CORE_FROM_ATTRIBS(attr, _name); \ + return snprintf(buf, PAGE_SIZE, "%d\n", v); \ +} + +#define DCVS_ALGO_PARAM(_name) \ +static ssize_t msm_dcvs_attr_##_name##_show(struct kobject *kobj,\ + struct kobj_attribute *attr, char *buf) \ +{ \ + struct dcvs_core *core = CORE_FROM_ATTRIBS(attr, _name); \ + return snprintf(buf, PAGE_SIZE, "%d\n", core->algo_param._name); \ +} \ +static ssize_t msm_dcvs_attr_##_name##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, const char *buf, size_t count) \ +{ \ + int ret = 0; \ + uint32_t val = 0; \ + struct dcvs_core *core = CORE_FROM_ATTRIBS(attr, _name); \ + mutex_lock(&core->lock); \ + ret = kstrtouint(buf, 10, &val); \ + if (ret) { \ + __err("Invalid input %s for %s\n", buf, __stringify(_name));\ + } else { \ + uint32_t old_val = core->algo_param._name; \ + core->algo_param._name = val; \ + ret = msm_dcvs_scm_set_algo_params(core->handle, \ + &core->algo_param); \ + if (ret) { \ + core->algo_param._name = old_val; \ + __err("Error(%d) in setting %d for algo param %s\n",\ + ret, val, __stringify(_name)); \ + } \ + } \ + mutex_unlock(&core->lock); \ + return count; \ +} + +#define DCVS_RO_ATTRIB(i, _name) \ + core->attrib._name.attr.name = __stringify(_name); \ + core->attrib._name.attr.mode = S_IRUGO; \ + core->attrib._name.show = msm_dcvs_attr_##_name##_show; \ + core->attrib._name.store = NULL; \ + core->attrib.attrib_group.attrs[i] = &core->attrib._name.attr; + +#define DCVS_RW_ATTRIB(i, _name) \ + core->attrib._name.attr.name = __stringify(_name); \ + core->attrib._name.attr.mode = S_IRUGO | S_IWUSR; \ + core->attrib._name.show = msm_dcvs_attr_##_name##_show; \ + core->attrib._name.store = msm_dcvs_attr_##_name##_store; \ + core->attrib.attrib_group.attrs[i] = &core->attrib._name.attr; + +/** + * Function declarations for different attributes. + * Gets used when setting the attribute show and store parameters. + */ +DCVS_PARAM_SHOW(idle_enabled, (core->idle_driver != NULL)) +DCVS_PARAM_SHOW(freq_change_enabled, (core->freq_driver != NULL)) +DCVS_PARAM_SHOW(actual_freq, (core->actual_freq)) +DCVS_PARAM_SHOW(freq_change_us, (core->freq_change_us)) +DCVS_PARAM_SHOW(max_time_us, (core->max_time_us)) + +DCVS_ALGO_PARAM(slack_time_us) +DCVS_ALGO_PARAM(scale_slack_time) +DCVS_ALGO_PARAM(scale_slack_time_pct) +DCVS_ALGO_PARAM(disable_pc_threshold) +DCVS_ALGO_PARAM(em_window_size) +DCVS_ALGO_PARAM(em_max_util_pct) +DCVS_ALGO_PARAM(ss_window_size) +DCVS_ALGO_PARAM(ss_util_pct) +DCVS_ALGO_PARAM(ss_iobusy_conv) + +static int msm_dcvs_setup_core_sysfs(struct dcvs_core *core) +{ + int ret = 0; + struct kobject *core_kobj = NULL; + const int attr_count = 15; + + BUG_ON(!cores_kobj); + + core->attrib.attrib_group.attrs = + kzalloc(attr_count * sizeof(struct attribute *), GFP_KERNEL); + + if (!core->attrib.attrib_group.attrs) { + ret = -ENOMEM; + goto done; + } + + DCVS_RO_ATTRIB(0, idle_enabled); + DCVS_RO_ATTRIB(1, freq_change_enabled); + DCVS_RO_ATTRIB(2, actual_freq); + DCVS_RO_ATTRIB(3, freq_change_us); + DCVS_RO_ATTRIB(4, max_time_us); + + DCVS_RW_ATTRIB(5, slack_time_us); + DCVS_RW_ATTRIB(6, scale_slack_time); + DCVS_RW_ATTRIB(7, scale_slack_time_pct); + DCVS_RW_ATTRIB(8, disable_pc_threshold); + DCVS_RW_ATTRIB(9, em_window_size); + DCVS_RW_ATTRIB(10, em_max_util_pct); + DCVS_RW_ATTRIB(11, ss_window_size); + DCVS_RW_ATTRIB(12, ss_util_pct); + DCVS_RW_ATTRIB(13, ss_iobusy_conv); + + core->attrib.attrib_group.attrs[14] = NULL; + + core_kobj = kobject_create_and_add(core->core_name, cores_kobj); + if (!core_kobj) { + ret = -ENOMEM; + goto done; + } + + ret = sysfs_create_group(core_kobj, &core->attrib.attrib_group); + if (ret) + __err("Cannot create core %s attr group\n", core->core_name); + else if (msm_dcvs_debug & MSM_DCVS_DEBUG_NOTIFIER) + __info("Setting up attributes for core %s\n", core->core_name); + +done: + if (ret) { + kfree(core->attrib.attrib_group.attrs); + kobject_del(core_kobj); + } + + return ret; +} + +/* Return the core if found or add to list if @add_to_list is true */ +static struct dcvs_core *msm_dcvs_get_core(const char *name, int add_to_list) +{ + struct dcvs_core *core = NULL; + int i; + int empty = -1; + + if (!name[0] || + (strnlen(name, CORE_NAME_MAX - 1) == CORE_NAME_MAX - 1)) + return core; + + mutex_lock(&core_list_lock); + for (i = 0; i < CORES_MAX; i++) { + core = &core_list[i]; + if ((empty < 0) && !core->core_name[0]) { + empty = i; + continue; + } + if (!strncmp(name, core->core_name, CORE_NAME_MAX)) + break; + } + + /* Check for core_list full */ + if ((i == CORES_MAX) && (empty < 0)) { + mutex_unlock(&core_list_lock); + return NULL; + } + + if (i == CORES_MAX && add_to_list) { + core = &core_list[empty]; + strlcpy(core->core_name, name, CORE_NAME_MAX); + mutex_init(&core->lock); + spin_lock_init(&core->cpu_lock); + core->handle = empty + CORE_HANDLE_OFFSET; + hrtimer_init(&core->timer, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); + core->timer.function = msm_dcvs_core_slack_timer; + } + mutex_unlock(&core_list_lock); + + return core; +} + +int msm_dcvs_register_core(const char *core_name, uint32_t group_id, + struct msm_dcvs_core_info *info) +{ + int ret = -EINVAL; + struct dcvs_core *core = NULL; + + if (!core_name || !core_name[0]) + return ret; + + core = msm_dcvs_get_core(core_name, true); + if (!core) + return ret; + + mutex_lock(&core->lock); + if (group_id) { + /** + * Create a group for cores, if this core is part of a group + * if the group_id is 0, the core is not part of a group. + * If the group_id already exits, it will through an error + * which we will ignore. + */ + ret = msm_dcvs_scm_create_group(group_id); + if (ret == -ENOMEM) + goto bail; + } + core->group_id = group_id; + + core->max_time_us = info->core_param.max_time_us; + memcpy(&core->algo_param, &info->algo_param, + sizeof(struct msm_dcvs_algo_param)); + + ret = msm_dcvs_scm_register_core(core->handle, group_id, + &info->core_param, info->freq_tbl); + if (ret) + goto bail; + + ret = msm_dcvs_scm_set_algo_params(core->handle, &info->algo_param); + if (ret) + goto bail; + + ret = msm_dcvs_setup_core_sysfs(core); + if (ret) { + __err("Unable to setup core %s sysfs\n", core->core_name); + core_handles[core->handle - CORE_HANDLE_OFFSET] = NULL; + goto bail; + } + +bail: + mutex_unlock(&core->lock); + return ret; +} +EXPORT_SYMBOL(msm_dcvs_register_core); + +int msm_dcvs_freq_sink_register(struct msm_dcvs_freq *drv) +{ + int ret = -EINVAL; + struct dcvs_core *core = NULL; + uint32_t ret1; + uint32_t ret2; + + if (!drv || !drv->core_name) + return ret; + + core = msm_dcvs_get_core(drv->core_name, true); + if (!core) + return ret; + + mutex_lock(&core->lock); + if (core->freq_driver && (msm_dcvs_debug & MSM_DCVS_DEBUG_NOTIFIER)) + __info("Frequency notifier for %s being replaced\n", + core->core_name); + core->freq_driver = drv; + core->task = kthread_create(msm_dcvs_do_freq, (void *)core, + "msm_dcvs/%d", core->handle); + if (IS_ERR(core->task)) { + mutex_unlock(&core->lock); + return -EFAULT; + } + + if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) + __info("Enabling idle pulse for %s\n", core->core_name); + + if (core->idle_driver) { + core->actual_freq = core->freq_driver->get_frequency(drv); + /* Notify TZ to start receiving idle info for the core */ + ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_ENABLE_CORE, 1, + &ret1, &ret2); + core->idle_driver->enable(core->idle_driver, + MSM_DCVS_ENABLE_IDLE_PULSE); + } + + mutex_unlock(&core->lock); + + return core->handle; +} +EXPORT_SYMBOL(msm_dcvs_freq_sink_register); + +int msm_dcvs_freq_sink_unregister(struct msm_dcvs_freq *drv) +{ + int ret = -EINVAL; + struct dcvs_core *core = NULL; + uint32_t ret1; + uint32_t ret2; + + if (!drv || !drv->core_name) + return ret; + + core = msm_dcvs_get_core(drv->core_name, false); + if (!core) + return ret; + + mutex_lock(&core->lock); + if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) + __info("Disabling idle pulse for %s\n", core->core_name); + if (core->idle_driver) { + core->idle_driver->enable(core->idle_driver, + MSM_DCVS_DISABLE_IDLE_PULSE); + /* Notify TZ to stop receiving idle info for the core */ + ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_ENABLE_CORE, 0, + &ret1, &ret2); + hrtimer_cancel(&core->timer); + core->idle_driver->enable(core->idle_driver, + MSM_DCVS_ENABLE_HIGH_LATENCY_MODES); + if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) + __info("Enabling LPM for %s\n", core->core_name); + } + core->freq_pending = 0; + core->freq_driver = NULL; + mutex_unlock(&core->lock); + kthread_stop(core->task); + + return 0; +} +EXPORT_SYMBOL(msm_dcvs_freq_sink_unregister); + +int msm_dcvs_idle_source_register(struct msm_dcvs_idle *drv) +{ + int ret = -EINVAL; + struct dcvs_core *core = NULL; + + if (!drv || !drv->core_name) + return ret; + + core = msm_dcvs_get_core(drv->core_name, true); + if (!core) + return ret; + + mutex_lock(&core->lock); + if (core->idle_driver && (msm_dcvs_debug & MSM_DCVS_DEBUG_NOTIFIER)) + __info("Idle notifier for %s being replaced\n", + core->core_name); + core->idle_driver = drv; + mutex_unlock(&core->lock); + + return core->handle; +} +EXPORT_SYMBOL(msm_dcvs_idle_source_register); + +int msm_dcvs_idle_source_unregister(struct msm_dcvs_idle *drv) +{ + int ret = -EINVAL; + struct dcvs_core *core = NULL; + + if (!drv || !drv->core_name) + return ret; + + core = msm_dcvs_get_core(drv->core_name, false); + if (!core) + return ret; + + mutex_lock(&core->lock); + core->idle_driver = NULL; + mutex_unlock(&core->lock); + + return 0; +} +EXPORT_SYMBOL(msm_dcvs_idle_source_unregister); + +int msm_dcvs_idle(int handle, enum msm_core_idle_state state, uint32_t iowaited) +{ + int ret = 0; + struct dcvs_core *core = NULL; + uint32_t timer_interval_us = 0; + uint32_t r0, r1; + uint32_t freq_changed = 0; + + if (handle >= CORE_HANDLE_OFFSET && + (handle - CORE_HANDLE_OFFSET) < CORES_MAX) + core = &core_list[handle - CORE_HANDLE_OFFSET]; + + BUG_ON(!core); + + if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) + __info("Core %s idle state %d\n", core->core_name, state); + + switch (state) { + case MSM_DCVS_IDLE_ENTER: + hrtimer_cancel(&core->timer); + ret = msm_dcvs_scm_event(core->handle, + MSM_DCVS_SCM_IDLE_ENTER, 0, 0, &r0, &r1); + if (ret) + __err("Error (%d) sending idle enter for %s\n", + ret, core->core_name); + break; + + case MSM_DCVS_IDLE_EXIT: + hrtimer_cancel(&core->timer); + ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_IDLE_EXIT, + iowaited, &timer_interval_us, &freq_changed); + if (ret) + __err("Error (%d) sending idle exit for %s\n", + ret, core->core_name); + /* only start slack timer if change_freq won't */ + if (freq_changed || core->change_freq_activated) + break; + if (timer_interval_us && !core->timer_disabled) { + ret = hrtimer_start(&core->timer, + ktime_set(0, timer_interval_us * 1000), + HRTIMER_MODE_REL_PINNED); + + if (ret) + __err("Failed to register timer for core %s\n", + core->core_name); + } + break; + } + + return ret; +} +EXPORT_SYMBOL(msm_dcvs_idle); + +static int __init msm_dcvs_late_init(void) +{ + struct kobject *module_kobj = NULL; + int ret = 0; + + 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 err; + } + + cores_kobj = kobject_create_and_add("cores", module_kobj); + if (!cores_kobj) { + __err("Cannot create %s kobject\n", "cores"); + ret = -ENOMEM; + goto err; + } + + debugfs_base = debugfs_create_dir("msm_dcvs", NULL); + if (!debugfs_base) { + __err("Cannot create debugfs base %s\n", "msm_dcvs"); + ret = -ENOENT; + goto err; + } + + if (!debugfs_create_u32("debug_mask", S_IRUGO | S_IWUSR, + debugfs_base, &msm_dcvs_debug)) { + __err("Cannot create debugfs entry %s\n", "debug_mask"); + ret = -ENOMEM; + goto err; + } + +err: + if (ret) { + kobject_del(cores_kobj); + cores_kobj = NULL; + debugfs_remove(debugfs_base); + } + + return ret; +} +late_initcall(msm_dcvs_late_init); + +static int __init msm_dcvs_early_init(void) +{ + int ret = 0; + + if (!msm_dcvs_enabled) { + __info("Not enabled (%d)\n", msm_dcvs_enabled); + return 0; + } + + ret = msm_dcvs_scm_init(10 * 1024); + if (ret) + __err("Unable to initialize DCVS err=%d\n", ret); + + return ret; +} +postcore_initcall(msm_dcvs_early_init); diff --git a/arch/arm/mach-msm/msm_dcvs_idle.c b/arch/arm/mach-msm/msm_dcvs_idle.c new file mode 100644 index 0000000000000000000000000000000000000000..179e17015fd209390bc3668e8dfe602fbc0c69f6 --- /dev/null +++ b/arch/arm/mach-msm/msm_dcvs_idle.c @@ -0,0 +1,170 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 cpu_idle_info { + int cpu; + int enabled; + int handle; + struct msm_dcvs_idle dcvs_notifier; +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct cpu_idle_info, cpu_idle_info); +static DEFINE_PER_CPU_SHARED_ALIGNED(u64, iowait_on_cpu); +static char core_name[NR_CPUS][10]; +static struct pm_qos_request qos_req; +static uint32_t latency; + +static int msm_dcvs_idle_notifier(struct msm_dcvs_idle *self, + enum msm_core_control_event event) +{ + struct cpu_idle_info *info = container_of(self, + struct cpu_idle_info, dcvs_notifier); + + switch (event) { + case MSM_DCVS_ENABLE_IDLE_PULSE: + info->enabled = true; + break; + + case MSM_DCVS_DISABLE_IDLE_PULSE: + info->enabled = false; + break; + + case MSM_DCVS_ENABLE_HIGH_LATENCY_MODES: + pm_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE); + break; + + case MSM_DCVS_DISABLE_HIGH_LATENCY_MODES: + pm_qos_update_request(&qos_req, latency); + break; + } + + return 0; +} + +static int msm_cpuidle_notifier(struct notifier_block *self, unsigned long cmd, + void *v) +{ + struct cpu_idle_info *info = + &per_cpu(cpu_idle_info, smp_processor_id()); + u64 io_wait_us = 0; + u64 prev_io_wait_us = 0; + u64 last_update_time = 0; + u64 val = 0; + uint32_t iowaited = 0; + + if (!info->enabled) + return NOTIFY_OK; + + switch (cmd) { + case CPU_PM_ENTER: + val = get_cpu_iowait_time_us(smp_processor_id(), + &last_update_time); + /* val could be -1 when NOHZ is not enabled */ + if (val == (u64)-1) + val = 0; + per_cpu(iowait_on_cpu, smp_processor_id()) = val; + msm_dcvs_idle(info->handle, MSM_DCVS_IDLE_ENTER, 0); + break; + + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + prev_io_wait_us = per_cpu(iowait_on_cpu, smp_processor_id()); + val = get_cpu_iowait_time_us(smp_processor_id(), + &last_update_time); + if (val == (u64)-1) + val = 0; + io_wait_us = val; + iowaited = (io_wait_us - prev_io_wait_us); + msm_dcvs_idle(info->handle, MSM_DCVS_IDLE_EXIT, iowaited); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block idle_nb = { + .notifier_call = msm_cpuidle_notifier, +}; + +static int msm_dcvs_idle_probe(struct platform_device *pdev) +{ + int cpu; + struct cpu_idle_info *info = NULL; + struct msm_dcvs_idle *inotify = NULL; + + for_each_possible_cpu(cpu) { + info = &per_cpu(cpu_idle_info, cpu); + info->cpu = cpu; + inotify = &info->dcvs_notifier; + snprintf(core_name[cpu], 10, "cpu%d", cpu); + inotify->core_name = core_name[cpu]; + inotify->enable = msm_dcvs_idle_notifier; + info->handle = msm_dcvs_idle_source_register(inotify); + BUG_ON(info->handle < 0); + } + + latency = *((uint32_t *)pdev->dev.platform_data); + pm_qos_add_request(&qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + return cpu_pm_register_notifier(&idle_nb); +} + +static int msm_dcvs_idle_remove(struct platform_device *pdev) +{ + int ret = 0; + int rc = 0; + int cpu = 0; + struct msm_dcvs_idle *inotify = NULL; + struct cpu_idle_info *info = NULL; + + rc = cpu_pm_unregister_notifier(&idle_nb); + + for_each_possible_cpu(cpu) { + info = &per_cpu(cpu_idle_info, cpu); + inotify = &info->dcvs_notifier; + ret = msm_dcvs_idle_source_unregister(inotify); + if (ret) { + rc = -EFAULT; + pr_err("Error de-registering core %d idle notifier.\n", + cpu); + } + } + + return rc; +} + +static struct platform_driver idle_pdrv = { + .probe = msm_dcvs_idle_probe, + .remove = __devexit_p(msm_dcvs_idle_remove), + .driver = { + .name = "msm_cpu_idle", + .owner = THIS_MODULE, + }, +}; + +static int msm_dcvs_idle_init(void) +{ + return platform_driver_register(&idle_pdrv); +} +late_initcall(msm_dcvs_idle_init); diff --git a/arch/arm/mach-msm/msm_dcvs_scm.c b/arch/arm/mach-msm/msm_dcvs_scm.c new file mode 100644 index 0000000000000000000000000000000000000000..6095e08139b0841a5989d269731359896352ff20 --- /dev/null +++ b/arch/arm/mach-msm/msm_dcvs_scm.c @@ -0,0 +1,161 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 DCVS_CMD_CREATE_GROUP 1 +#define DCVS_CMD_REGISTER_CORE 2 +#define DCVS_CMD_SET_ALGO_PARAM 3 +#define DCVS_CMD_EVENT 4 +#define DCVS_CMD_INIT 5 + +struct scm_register_core { + uint32_t core_id; + uint32_t group_id; + phys_addr_t core_param_phy; + phys_addr_t freq_phy; +}; + +struct scm_algo { + uint32_t core_id; + phys_addr_t algo_phy; +}; + +struct scm_init { + uint32_t phy; + uint32_t size; +}; + +int msm_dcvs_scm_init(size_t size) +{ + int ret = 0; + struct scm_init init; + uint32_t p = 0; + + /* Allocate word aligned non-cacheable memory */ + p = allocate_contiguous_ebi_nomap(size, 4); + if (!p) + return -ENOMEM; + + init.phy = p; + init.size = size; + + ret = scm_call(SCM_SVC_DCVS, DCVS_CMD_INIT, + &init, sizeof(init), NULL, 0); + + /* Not freed if the initialization succeeds */ + if (ret) + free_contiguous_memory_by_paddr(p); + + return ret; +} +EXPORT_SYMBOL(msm_dcvs_scm_init); + +int msm_dcvs_scm_create_group(uint32_t id) +{ + int ret = 0; + + ret = scm_call(SCM_SVC_DCVS, DCVS_CMD_CREATE_GROUP, + &id, sizeof(uint32_t), NULL, 0); + + return ret; +} +EXPORT_SYMBOL(msm_dcvs_scm_create_group); + +int msm_dcvs_scm_register_core(uint32_t core_id, uint32_t group_id, + struct msm_dcvs_core_param *param, + struct msm_dcvs_freq_entry *freq) +{ + int ret = 0; + struct scm_register_core reg_data; + struct msm_dcvs_core_param *p = NULL; + struct msm_dcvs_freq_entry *f = NULL; + + p = kzalloc(PAGE_ALIGN(sizeof(struct msm_dcvs_core_param)), GFP_KERNEL); + if (!p) + return -ENOMEM; + + f = kzalloc(PAGE_ALIGN(sizeof(struct msm_dcvs_freq_entry) * + param->num_freq), GFP_KERNEL); + if (!f) { + kfree(p); + return -ENOMEM; + } + + memcpy(p, param, sizeof(struct msm_dcvs_core_param)); + memcpy(f, freq, sizeof(struct msm_dcvs_freq_entry) * param->num_freq); + + reg_data.core_id = core_id; + reg_data.group_id = group_id; + reg_data.core_param_phy = virt_to_phys(p); + reg_data.freq_phy = virt_to_phys(f); + + ret = scm_call(SCM_SVC_DCVS, DCVS_CMD_REGISTER_CORE, + ®_data, sizeof(reg_data), NULL, 0); + + kfree(f); + kfree(p); + + return ret; +} +EXPORT_SYMBOL(msm_dcvs_scm_register_core); + +int msm_dcvs_scm_set_algo_params(uint32_t core_id, + struct msm_dcvs_algo_param *param) +{ + int ret = 0; + struct scm_algo algo; + struct msm_dcvs_algo_param *p = NULL; + + p = kzalloc(PAGE_ALIGN(sizeof(struct msm_dcvs_algo_param)), GFP_KERNEL); + if (!p) + return -ENOMEM; + + memcpy(p, param, sizeof(struct msm_dcvs_algo_param)); + + algo.core_id = core_id; + algo.algo_phy = virt_to_phys(p); + + ret = scm_call(SCM_SVC_DCVS, DCVS_CMD_SET_ALGO_PARAM, + &algo, sizeof(algo), NULL, 0); + + kfree(p); + + return ret; +} +EXPORT_SYMBOL(msm_dcvs_scm_set_algo_params); + +int msm_dcvs_scm_event(uint32_t core_id, + enum msm_dcvs_scm_event event_id, + uint32_t param0, uint32_t param1, + uint32_t *ret0, uint32_t *ret1) +{ + int ret = -EINVAL; + + if (!ret0 || !ret1) + return ret; + + ret = scm_call_atomic4_3(SCM_SVC_DCVS, DCVS_CMD_EVENT, + core_id, event_id, param0, param1, ret0, ret1); + + return ret; +} +EXPORT_SYMBOL(msm_dcvs_scm_event); diff --git a/arch/arm/mach-msm/msm_dsps.c b/arch/arm/mach-msm/msm_dsps.c new file mode 100644 index 0000000000000000000000000000000000000000..057665b1747559dca80e976d91a3a918d120b091 --- /dev/null +++ b/arch/arm/mach-msm/msm_dsps.c @@ -0,0 +1,912 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#include +#include +#include +#include + +#include "timer.h" + +#define DRV_NAME "msm_dsps" +#define DRV_VERSION "3.02" + +#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; + +/** + * self-initiated shutdown flag + */ +static int dsps_crash_shutdown_g; + + +static void dsps_fatal_handler(struct work_struct *work); + +/** + * 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; + } + msleep(20); + 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; + + /* Read the timer value from the MSM sclk. The MSM slow clock & DSPS + * timers are in sync, so these are the same value */ + val = msm_timer_get_sclk_ticks(); + 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; +} + +static DECLARE_WORK(dsps_fatal_work, dsps_fatal_handler); + +/** + * Watchdog interrupt handler + * + */ +static irqreturn_t dsps_wdog_bite_irq(int irq, void *dev_id) +{ + pr_debug("%s\n", __func__); + (void)schedule_work(&dsps_fatal_work); + disable_irq_nosync(irq); + return IRQ_HANDLED; +} + +/** + * 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; + case DSPS_IOCTL_RESET: + dsps_fatal_handler(NULL); + ret = 0; + 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; + struct resource *ppss_wdog; + 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)); + + ppss_wdog = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "ppss_wdog"); + if (ppss_wdog) { + ret = request_irq(ppss_wdog->start, dsps_wdog_bite_irq, + IRQF_TRIGGER_RISING, "dsps_wdog", NULL); + if (ret) { + pr_err("%s: request_irq fail %d\n", __func__, ret); + goto request_irq_err; + } + } else { + pr_debug("%s: ppss_wdog not supported.\n", __func__); + } + + if (drv->pdata->init) + drv->pdata->init(drv->pdata); + + return 0; + +request_irq_err: + iounmap(drv->ppss_base); + +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, +}; + +/** + * Fatal error handler + * Resets DSPS. + */ +static void dsps_fatal_handler(struct work_struct *work) +{ + uint32_t dsps_state; + + dsps_state = smsm_get_state(SMSM_DSPS_STATE); + + pr_debug("%s: DSPS state 0x%x\n", __func__, dsps_state); + + if (dsps_state & SMSM_RESET) { + pr_err("%s: DSPS fatal error detected. Resetting\n", + __func__); + panic("DSPS fatal error detected."); + } else { + pr_debug("%s: User-initiated DSPS reset. Resetting\n", + __func__); + panic("User-initiated DSPS reset."); + } +} + + +/** + * SMSM state change callback + * + */ +static void dsps_smsm_state_cb(void *data, uint32_t old_state, + uint32_t new_state) +{ + pr_debug("%s\n", __func__); + if (dsps_crash_shutdown_g == 1) { + pr_debug("%s: SMSM_RESET state change ignored\n", + __func__); + dsps_crash_shutdown_g = 0; + return; + } + + if (new_state & SMSM_RESET) { + pr_err + ("%s: SMSM_RESET state detected. restarting the DSPS\n", + __func__); + panic("SMSM_RESET state detected."); + } +} + +/** + * Shutdown function + * called by the restart notifier + * + */ +static int dsps_shutdown(const struct subsys_data *subsys) +{ + pr_debug("%s\n", __func__); + dsps_unload(); + return 0; +} + +/** + * Powerup function + * called by the restart notifier + * + */ +static int dsps_powerup(const struct subsys_data *subsys) +{ + pr_debug("%s\n", __func__); + if (dsps_load(drv->pdata->pil_name) != 0) { + pr_err("%s: fail to restart DSPS after reboot\n", + __func__); + return 1; + } + return 0; +} + +/** + * Crash shutdown function + * called by the restart notifier + * + */ +static void dsps_crash_shutdown(const struct subsys_data *subsys) +{ + pr_debug("%s\n", __func__); + dsps_crash_shutdown_g = 1; + smsm_change_state(SMSM_DSPS_STATE, SMSM_RESET, SMSM_RESET); +} + +/** + * Ramdump function + * called by the restart notifier + * + */ +static int dsps_ramdump(int enable, const struct subsys_data *subsys) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static struct subsys_data dsps_ssrops = { + .name = "dsps", + .shutdown = dsps_shutdown, + .powerup = dsps_powerup, + .ramdump = dsps_ramdump, + .crash_shutdown = dsps_crash_shutdown +}; + +/** + * 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; + + 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; + } + + ret = dsps_alloc_resources(pdev); + if (ret) { + pr_err("%s: failed to allocate dsps resources.\n", __func__); + goto cdev_add_err; + } + + ret = + smsm_state_cb_register(SMSM_DSPS_STATE, SMSM_RESET, + dsps_smsm_state_cb, 0); + if (ret) { + pr_err("%s: smsm_state_cb_register fail %d\n", __func__, + ret); + goto smsm_register_err; + } + + ret = ssr_register_subsystem(&dsps_ssrops); + if (ret) { + pr_err("%s: ssr_register_subsystem fail %d\n", __func__, + ret); + goto ssr_register_err; + } + + return 0; + +ssr_register_err: + smsm_state_cb_deregister(SMSM_DSPS_STATE, SMSM_RESET, + dsps_smsm_state_cb, + 0); +smsm_register_err: + cdev_del(drv->cdev); +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 0000000000000000000000000000000000000000..c97585605b4c033cf65ed94565624e820a015aa3 --- /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 0000000000000000000000000000000000000000..4597cf0da810f74581cd8c77d52e98b17cbde0ce --- /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_rq_stats.c b/arch/arm/mach-msm/msm_rq_stats.c new file mode 100644 index 0000000000000000000000000000000000000000..738819f98041ceda31e12096500b9751865fa910 --- /dev/null +++ b/arch/arm/mach-msm/msm_rq_stats.c @@ -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. + * + */ +/* + * Qualcomm MSM Runqueue Stats Interface for Userspace + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LONG_SIZE 24 +#define DEFAULT_RQ_POLL_JIFFIES 1 +#define DEFAULT_DEF_TIMER_JIFFIES 5 + +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 snprintf(buf, PAGE_SIZE, "%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 = snprintf(buf, MAX_LONG_SIZE, "%u\n", + jiffies_to_msecs(rq_info.rq_poll_jiffies)); + 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) +{ + unsigned 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_jiffies = msecs_to_jiffies(val); + spin_unlock_irqrestore(&rq_lock, flags); + + mutex_unlock(&lock_poll_ms); + + return count; +} + +static ssize_t show_def_timer_ms(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, MAX_LONG_SIZE, "%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_jiffies = msecs_to_jiffies(val); + + rq_info.def_start_time = ktime_to_ns(ktime_get()); + 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; + + 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 rel2; + } + + rq_info.attr_group = kzalloc(sizeof(struct attribute_group), + GFP_KERNEL); + if (!rq_info.attr_group) + goto rel3; + 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_device(0)->kobj); + if (!rq_info.kobj) + goto rel3; + + 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; + +rel3: + kfree(rq_info.attr_group); + kfree(rq_info.kobj); +rel2: + for (i = 0; i < attr_count - 1; i++) + kfree(attribs[i]); +rel: + kfree(attribs); + + return -ENOMEM; +} + +static int __init msm_rq_stats_init(void) +{ + int ret; + + /* Bail out if this is not an SMP Target */ + if (!is_smp()) { + rq_info.init = 0; + return -ENOSYS; + } + + rq_wq = create_singlethread_workqueue("rq_stats"); + BUG_ON(!rq_wq); + INIT_WORK(&rq_info.def_timer_work, def_work_fn); + spin_lock_init(&rq_lock); + rq_info.rq_poll_jiffies = DEFAULT_RQ_POLL_JIFFIES; + rq_info.def_timer_jiffies = DEFAULT_DEF_TIMER_JIFFIES; + rq_info.rq_poll_last_jiffy = 0; + rq_info.def_timer_last_jiffy = 0; + ret = init_rq_attribs(); + + rq_info.init = 1; + return ret; +} +late_initcall(msm_rq_stats_init); diff --git a/arch/arm/mach-msm/msm_rtb.c b/arch/arm/mach-msm/msm_rtb.c new file mode 100644 index 0000000000000000000000000000000000000000..9dbf9c1ebed02c550dbb41f83a3839c9a472548f --- /dev/null +++ b/arch/arm/mach-msm/msm_rtb.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 SENTINEL_BYTE_1 0xFF +#define SENTINEL_BYTE_2 0xAA +#define SENTINEL_BYTE_3 0xFF + +/* Write + * 1) 3 bytes sentinel + * 2) 1 bytes of log type + * 3) 4 bytes of where the caller came from + * 4) 4 bytes index + * 4) 4 bytes extra data from the caller + * + * Total = 16 bytes. + */ +struct msm_rtb_layout { + unsigned char sentinel[3]; + unsigned char log_type; + void *caller; + unsigned long idx; + void *data; +} __attribute__ ((__packed__)); + + +struct msm_rtb_state { + struct msm_rtb_layout *rtb; + unsigned long phys; + int nentries; + int size; + int enabled; + int initialized; + uint32_t filter; + int step_size; +}; + +#if defined(CONFIG_MSM_RTB_SEPARATE_CPUS) +DEFINE_PER_CPU(atomic_t, msm_rtb_idx_cpu); +#else +static atomic_t msm_rtb_idx; +#endif + +struct msm_rtb_state msm_rtb = { + .filter = 1 << LOGK_LOGBUF, + .enabled = 1, +}; + +module_param_named(filter, msm_rtb.filter, uint, 0644); +module_param_named(enable, msm_rtb.enabled, int, 0644); + +static int msm_rtb_panic_notifier(struct notifier_block *this, + unsigned long event, void *ptr) +{ + msm_rtb.enabled = 0; + return NOTIFY_DONE; +} + +static struct notifier_block msm_rtb_panic_blk = { + .notifier_call = msm_rtb_panic_notifier, +}; + +int msm_rtb_event_should_log(enum logk_event_type log_type) +{ + return msm_rtb.initialized && msm_rtb.enabled && + ((1 << (log_type & ~LOGTYPE_NOPC)) & msm_rtb.filter); +} +EXPORT_SYMBOL(msm_rtb_event_should_log); + +static void msm_rtb_emit_sentinel(struct msm_rtb_layout *start) +{ + start->sentinel[0] = SENTINEL_BYTE_1; + start->sentinel[1] = SENTINEL_BYTE_2; + start->sentinel[2] = SENTINEL_BYTE_3; +} + +static void msm_rtb_write_type(enum logk_event_type log_type, + struct msm_rtb_layout *start) +{ + start->log_type = (char)log_type; +} + +static void msm_rtb_write_caller(void *caller, struct msm_rtb_layout *start) +{ + start->caller = caller; +} + +static void msm_rtb_write_idx(unsigned long idx, + struct msm_rtb_layout *start) +{ + start->idx = idx; +} + +static void msm_rtb_write_data(void *data, struct msm_rtb_layout *start) +{ + start->data = data; +} + +static void uncached_logk_pc_idx(enum logk_event_type log_type, void *caller, + void *data, int idx) +{ + struct msm_rtb_layout *start; + + start = &msm_rtb.rtb[idx & (msm_rtb.nentries - 1)]; + + msm_rtb_emit_sentinel(start); + msm_rtb_write_type(log_type, start); + msm_rtb_write_caller(caller, start); + msm_rtb_write_idx(idx, start); + msm_rtb_write_data(data, start); + mb(); + + return; +} + +static void uncached_logk_timestamp(int idx) +{ + unsigned long long timestamp; + void *timestamp_upper, *timestamp_lower; + timestamp = sched_clock(); + timestamp_lower = (void *)lower_32_bits(timestamp); + timestamp_upper = (void *)upper_32_bits(timestamp); + + uncached_logk_pc_idx(LOGK_TIMESTAMP|LOGTYPE_NOPC, timestamp_lower, + timestamp_upper, idx); +} + +#if defined(CONFIG_MSM_RTB_SEPARATE_CPUS) +static int msm_rtb_get_idx(void) +{ + int cpu, i, offset; + atomic_t *index; + + /* + * ideally we would use get_cpu but this is a close enough + * approximation for our purposes. + */ + cpu = raw_smp_processor_id(); + + index = &per_cpu(msm_rtb_idx_cpu, cpu); + + i = atomic_add_return(msm_rtb.step_size, index); + i -= msm_rtb.step_size; + + /* Check if index has wrapped around */ + offset = (i & (msm_rtb.nentries - 1)) - + ((i - msm_rtb.step_size) & (msm_rtb.nentries - 1)); + if (offset < 0) { + uncached_logk_timestamp(i); + i = atomic_add_return(msm_rtb.step_size, index); + i -= msm_rtb.step_size; + } + + return i; +} +#else +static int msm_rtb_get_idx(void) +{ + int i, offset; + + i = atomic_inc_return(&msm_rtb_idx); + i--; + + /* Check if index has wrapped around */ + offset = (i & (msm_rtb.nentries - 1)) - + ((i - 1) & (msm_rtb.nentries - 1)); + if (offset < 0) { + uncached_logk_timestamp(i); + i = atomic_inc_return(&msm_rtb_idx); + i--; + } + + return i; +} +#endif + +int uncached_logk_pc(enum logk_event_type log_type, void *caller, + void *data) +{ + int i; + + if (!msm_rtb_event_should_log(log_type)) + return 0; + + i = msm_rtb_get_idx(); + + uncached_logk_pc_idx(log_type, caller, data, i); + + return 1; +} +EXPORT_SYMBOL(uncached_logk_pc); + +noinline int uncached_logk(enum logk_event_type log_type, void *data) +{ + return uncached_logk_pc(log_type, __builtin_return_address(0), data); +} +EXPORT_SYMBOL(uncached_logk); + +int msm_rtb_probe(struct platform_device *pdev) +{ + struct msm_rtb_platform_data *d = pdev->dev.platform_data; +#if defined(CONFIG_MSM_RTB_SEPARATE_CPUS) + unsigned int cpu; +#endif + + msm_rtb.size = d->size; + + if (msm_rtb.size <= 0 || msm_rtb.size > SZ_1M) + return -EINVAL; + + /* + * The ioremap call is made separately to store the physical + * address of the buffer. This is necessary for cases where + * the only way to access the buffer is a physical address. + */ + msm_rtb.phys = allocate_contiguous_ebi_nomap(msm_rtb.size, SZ_4K); + + if (!msm_rtb.phys) + return -ENOMEM; + + msm_rtb.rtb = ioremap(msm_rtb.phys, msm_rtb.size); + + if (!msm_rtb.rtb) { + free_contiguous_memory_by_paddr(msm_rtb.phys); + return -ENOMEM; + } + + msm_rtb.nentries = msm_rtb.size / sizeof(struct msm_rtb_layout); + + /* Round this down to a power of 2 */ + msm_rtb.nentries = __rounddown_pow_of_two(msm_rtb.nentries); + + memset(msm_rtb.rtb, 0, msm_rtb.size); + + +#if defined(CONFIG_MSM_RTB_SEPARATE_CPUS) + for_each_possible_cpu(cpu) { + atomic_t *a = &per_cpu(msm_rtb_idx_cpu, cpu); + atomic_set(a, cpu); + } + msm_rtb.step_size = num_possible_cpus(); +#else + atomic_set(&msm_rtb_idx, 0); + msm_rtb.step_size = 1; +#endif + + atomic_notifier_chain_register(&panic_notifier_list, + &msm_rtb_panic_blk); + msm_rtb.initialized = 1; + return 0; +} + +static struct platform_driver msm_rtb_driver = { + .driver = { + .name = "msm_rtb", + .owner = THIS_MODULE + }, +}; + +static int __init msm_rtb_init(void) +{ + return platform_driver_probe(&msm_rtb_driver, msm_rtb_probe); +} + +static void __exit msm_rtb_exit(void) +{ + platform_driver_unregister(&msm_rtb_driver); +} +module_init(msm_rtb_init) +module_exit(msm_rtb_exit) 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 0000000000000000000000000000000000000000..82093673c49b2b2af8b390683d26ebc6f1958031 --- /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 0000000000000000000000000000000000000000..5595ba07e34e5df1dfaebd535168a1b9632583fb --- /dev/null +++ b/arch/arm/mach-msm/msm_vibrator.c @@ -0,0 +1,162 @@ +/* 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 +#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 0000000000000000000000000000000000000000..2cff7f0b4615ea8d78368d2f54d0c15152fc17bb --- /dev/null +++ b/arch/arm/mach-msm/msm_watchdog.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_watchdog.h" +#include "timer.h" + +#define MODULE_NAME "msm_watchdog" + +#define TCSR_WDT_CFG 0x30 + +#define WDT0_RST 0x38 +#define WDT0_EN 0x40 +#define WDT0_STS 0x44 +#define WDT0_BARK_TIME 0x4C +#define WDT0_BITE_TIME 0x5C + +#define WDT_HZ 32768 + +struct msm_watchdog_dump msm_dump_cpu_ctx; + +static void __iomem *msm_tmr0_base; + +static unsigned long delay_time; +static unsigned long bark_time; +static unsigned long long last_pet; +static bool has_vic; + +/* + * 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); + +static int appsbark_fiq; + +/* + * 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 struct msm_watchdog_pdata __percpu **percpu_pdata; + +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); + +/* Called from the FIQ bark handler */ +void msm_wdog_bark_fin(void) +{ + pr_crit("\nApps Watchdog bark received - Calling Panic\n"); + panic("Apps Watchdog Bark received\n"); +} + +static int msm_watchdog_suspend(struct device *dev) +{ + if (!enable) + return 0; + + __raw_writel(1, msm_tmr0_base + WDT0_RST); + __raw_writel(0, msm_tmr0_base + WDT0_EN); + mb(); + return 0; +} + +static int msm_watchdog_resume(struct device *dev) +{ + if (!enable) + return 0; + + __raw_writel(1, msm_tmr0_base + WDT0_EN); + __raw_writel(1, msm_tmr0_base + WDT0_RST); + mb(); + return 0; +} + +static int panic_wdog_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + if (panic_timeout == 0) { + __raw_writel(0, msm_tmr0_base + WDT0_EN); + mb(); + } else { + __raw_writel(WDT_HZ * (panic_timeout + 4), + msm_tmr0_base + WDT0_BARK_TIME); + __raw_writel(WDT_HZ * (panic_timeout + 4), + msm_tmr0_base + WDT0_BITE_TIME); + __raw_writel(1, msm_tmr0_base + WDT0_RST); + } + return NOTIFY_DONE; +} + +static struct notifier_block panic_blk = { + .notifier_call = panic_wdog_handler, +}; + +struct wdog_disable_work_data { + struct work_struct work; + struct completion complete; +}; + +static void wdog_disable_work(struct work_struct *work) +{ + struct wdog_disable_work_data *work_data = + container_of(work, struct wdog_disable_work_data, work); + __raw_writel(0, msm_tmr0_base + WDT0_EN); + mb(); + if (has_vic) { + free_irq(WDT0_ACCSCSSNBARK_INT, 0); + } else { + disable_percpu_irq(WDT0_ACCSCSSNBARK_INT); + if (!appsbark_fiq) { + free_percpu_irq(WDT0_ACCSCSSNBARK_INT, + percpu_pdata); + free_percpu(percpu_pdata); + } + } + enable = 0; + atomic_notifier_chain_unregister(&panic_notifier_list, &panic_blk); + cancel_delayed_work(&dogwork_struct); + /* may be suspended after the first write above */ + __raw_writel(0, msm_tmr0_base + WDT0_EN); + complete(&work_data->complete); + pr_info("MSM Watchdog deactivated.\n"); +} + +static int wdog_enable_set(const char *val, struct kernel_param *kp) +{ + int ret = 0; + int old_val = runtime_disable; + struct wdog_disable_work_data work_data; + + 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; + + if (runtime_disable == 1) { + if (old_val) + goto done; + init_completion(&work_data.complete); + INIT_WORK_ONSTACK(&work_data.work, wdog_disable_work); + schedule_work_on(0, &work_data.work); + wait_for_completion(&work_data.complete); + } else { + runtime_disable = old_val; + ret = -EINVAL; + } +done: + mutex_unlock(&disable_lock); + return ret; +} + +unsigned min_slack_ticks = UINT_MAX; +unsigned long long min_slack_ns = ULLONG_MAX; + +void pet_watchdog(void) +{ + int slack; + unsigned long long time_ns; + unsigned long long slack_ns; + unsigned long long bark_time_ns = bark_time * 1000000ULL; + + if (!enable) + return; + + slack = __raw_readl(msm_tmr0_base + WDT0_STS) >> 3; + slack = ((bark_time*WDT_HZ)/1000) - slack; + if (slack < min_slack_ticks) + min_slack_ticks = slack; + __raw_writel(1, msm_tmr0_base + WDT0_RST); + time_ns = sched_clock(); + slack_ns = (last_pet + bark_time_ns) - time_ns; + if (slack_ns < min_slack_ns) + min_slack_ns = slack_ns; + last_pet = time_ns; +} + +static void pet_watchdog_work(struct work_struct *work) +{ + pet_watchdog(); + + if (enable) + schedule_delayed_work_on(0, &dogwork_struct, delay_time); +} + +static int msm_watchdog_remove(struct platform_device *pdev) +{ + if (enable) { + __raw_writel(0, msm_tmr0_base + WDT0_EN); + mb(); + if (has_vic) { + free_irq(WDT0_ACCSCSSNBARK_INT, 0); + } else { + disable_percpu_irq(WDT0_ACCSCSSNBARK_INT); + if (!appsbark_fiq) { + free_percpu_irq(WDT0_ACCSCSSNBARK_INT, + percpu_pdata); + free_percpu(percpu_pdata); + } + } + enable = 0; + /* In case we got suspended mid-exit */ + __raw_writel(0, msm_tmr0_base + WDT0_EN); + } + printk(KERN_INFO "MSM Watchdog Exit - Deactivated\n"); + return 0; +} + +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(NULL); + + 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(NULL); + } + + panic("Apps watchdog bark received!"); + return IRQ_HANDLED; +} + +#define SCM_SET_REGSAVE_CMD 0x2 + +static void configure_bark_dump(void) +{ + int ret; + struct { + unsigned addr; + int len; + } cmd_buf; + + 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. + */ + } + } +} + +struct fiq_handler wdog_fh = { + .name = MODULE_NAME, +}; + +static void init_watchdog_work(struct work_struct *work) +{ + u64 timeout = (bark_time * WDT_HZ)/1000; + void *stack; + int ret; + + if (has_vic) { + ret = request_irq(WDT0_ACCSCSSNBARK_INT, wdog_bark_handler, 0, + "apps_wdog_bark", NULL); + if (ret) + return; + } else if (appsbark_fiq) { + claim_fiq(&wdog_fh); + set_fiq_handler(&msm_wdog_fiq_start, msm_wdog_fiq_length); + stack = (void *)__get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER); + if (!stack) { + pr_info("No free pages available - %s fails\n", + __func__); + return; + } + + msm_wdog_fiq_setup(stack); + gic_set_irq_secure(WDT0_ACCSCSSNBARK_INT); + } else { + percpu_pdata = alloc_percpu(struct msm_watchdog_pdata *); + if (!percpu_pdata) { + pr_err("%s: memory allocation failed for percpu data\n", + __func__); + return; + } + + /* Must request irq before sending scm command */ + ret = request_percpu_irq(WDT0_ACCSCSSNBARK_INT, + wdog_bark_handler, "apps_wdog_bark", percpu_pdata); + if (ret) { + free_percpu(percpu_pdata); + return; + } + } + + configure_bark_dump(); + + __raw_writel(timeout, msm_tmr0_base + WDT0_BARK_TIME); + __raw_writel(timeout + 3*WDT_HZ, msm_tmr0_base + WDT0_BITE_TIME); + + schedule_delayed_work_on(0, &dogwork_struct, delay_time); + + atomic_notifier_chain_register(&panic_notifier_list, + &panic_blk); + + __raw_writel(1, msm_tmr0_base + WDT0_EN); + __raw_writel(1, msm_tmr0_base + WDT0_RST); + last_pet = sched_clock(); + + if (!has_vic) + enable_percpu_irq(WDT0_ACCSCSSNBARK_INT, IRQ_TYPE_EDGE_RISING); + + printk(KERN_INFO "MSM Watchdog Initialized\n"); + + return; +} + +static int msm_watchdog_probe(struct platform_device *pdev) +{ + struct msm_watchdog_pdata *pdata = pdev->dev.platform_data; + + if (!enable || !pdata || !pdata->pet_time || !pdata->bark_time) { + printk(KERN_INFO "MSM Watchdog Not Initialized\n"); + return -ENODEV; + } + + bark_time = pdata->bark_time; + has_vic = pdata->has_vic; + if (!pdata->has_secure) { + appsbark = 1; + appsbark_fiq = pdata->use_kernel_fiq; + } + + msm_tmr0_base = msm_timer_get_timer0_base(); + + /* + * This is only temporary till SBLs turn on the XPUs + * This initialization will be done in SBLs on a later releases + */ + if (cpu_is_msm9615()) + __raw_writel(0xF, MSM_TCSR_BASE + TCSR_WDT_CFG); + + if (pdata->needs_expired_enable) + __raw_writel(0x1, MSM_CLK_CTL_BASE + 0x3820); + + delay_time = msecs_to_jiffies(pdata->pet_time); + schedule_work_on(0, &init_dogwork_struct); + return 0; +} + +static const struct dev_pm_ops msm_watchdog_dev_pm_ops = { + .suspend_noirq = msm_watchdog_suspend, + .resume_noirq = msm_watchdog_resume, +}; + +static struct platform_driver msm_watchdog_driver = { + .probe = msm_watchdog_probe, + .remove = msm_watchdog_remove, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .pm = &msm_watchdog_dev_pm_ops, + }, +}; + +static int init_watchdog(void) +{ + return platform_driver_register(&msm_watchdog_driver); +} + +late_initcall(init_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 0000000000000000000000000000000000000000..00ff0b61d0b4412f9f48d2bc2520065512dffcf1 --- /dev/null +++ b/arch/arm/mach-msm/msm_watchdog.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +struct msm_watchdog_pdata { + /* pet interval period in ms */ + unsigned int pet_time; + /* bark timeout in ms */ + unsigned int bark_time; + bool has_secure; + bool needs_expired_enable; + bool has_vic; + /* You have to be running in secure mode to use FIQ */ + bool use_kernel_fiq; +}; + +struct msm_watchdog_dump { + uint32_t magic; + uint32_t curr_cpsr; + uint32_t usr_r0; + uint32_t usr_r1; + uint32_t usr_r2; + uint32_t usr_r3; + uint32_t usr_r4; + uint32_t usr_r5; + uint32_t usr_r6; + uint32_t usr_r7; + uint32_t usr_r8; + uint32_t usr_r9; + uint32_t usr_r10; + uint32_t usr_r11; + uint32_t usr_r12; + uint32_t usr_r13; + uint32_t usr_r14; + uint32_t irq_spsr; + uint32_t irq_r13; + uint32_t irq_r14; + uint32_t svc_spsr; + uint32_t svc_r13; + uint32_t svc_r14; + uint32_t abt_spsr; + uint32_t abt_r13; + uint32_t abt_r14; + uint32_t und_spsr; + uint32_t und_r13; + uint32_t und_r14; + uint32_t fiq_spsr; + uint32_t fiq_r8; + uint32_t fiq_r9; + uint32_t fiq_r10; + uint32_t fiq_r11; + uint32_t fiq_r12; + uint32_t fiq_r13; + uint32_t fiq_r14; +}; + +void msm_wdog_fiq_setup(void *stack); +extern unsigned int msm_wdog_fiq_length, msm_wdog_fiq_start; + +#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_watchdog_asm.S b/arch/arm/mach-msm/msm_watchdog_asm.S new file mode 100644 index 0000000000000000000000000000000000000000..c0377d61cdb71ef82fc9b5401fb72297236e3afe --- /dev/null +++ b/arch/arm/mach-msm/msm_watchdog_asm.S @@ -0,0 +1,84 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 VERSION_ID 0x1 +#define MAGIC 0xDEAD0000 | VERSION_ID + + .text + + .align 3 + +ENTRY(msm_wdog_fiq_start) + mov sp, r8 @get stack + ldr r8, Ldump_cpu_ctx + @ store magic to indicate a valid dump + ldr r9, Lmagic + str r9, [r8], #4 + @ get the current cpsr + mrs r9, cpsr + str r9, [r8],#4 + @ get the USR r0-r7 + stmia r8!, {r0-r7} + mov r4, r8 + mov r5, #PSR_I_BIT | PSR_F_BIT | SYSTEM_MODE + msr cpsr_c, r5 @ select SYSTEM mode + stmia r4!, {r8-r14} + mov r5, #PSR_I_BIT | PSR_F_BIT | IRQ_MODE + msr cpsr_c, r5 @ select IRQ mode + mrs r5, spsr + str r5, [r4], #4 + stmia r4!, {r13-r14} + mov r5, #PSR_I_BIT | PSR_F_BIT | SVC_MODE + msr cpsr_c, r5 @ select SVC mode + mrs r5, spsr + str r5, [r4], #4 + stmia r4!, {r13-r14} + mov r5, #PSR_I_BIT | PSR_F_BIT | ABT_MODE + msr cpsr_c, r5 @ select ABT mode + mrs r5, spsr + str r5, [r4], #4 + stmia r4!, {r13-r14} + mov r5, #PSR_I_BIT | PSR_F_BIT | UND_MODE + msr cpsr_c, r5 @ select UND mode + mrs r5, spsr + str r5, [r4], #4 + stmia r4!, {r13-r14} + mov r5, #PSR_I_BIT | PSR_F_BIT | FIQ_MODE + msr cpsr_c, r5 @ select FIQ mode + mrs r5, spsr + str r5, [r4], #4 + stmia r4!, {r8-r14} + dsb + mov r5, #PSR_F_BIT | SVC_MODE + msr cpsr_c, r5 @ select SVC mode + ldr r2, Lwatchdog_bark_fin + blx r2 +Ldump_cpu_ctx: + .word msm_dump_cpu_ctx +Lmagic: + .word MAGIC +Lwatchdog_bark_fin: + .word msm_wdog_bark_fin +ENTRY(msm_wdog_fiq_length) + .word . - msm_wdog_fiq_start + +/* setup the stack */ +ENTRY(msm_wdog_fiq_setup) + mrs r3, cpsr + msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT) + mov r8, r0 + msr cpsr_c, r3 + bx lr diff --git a/arch/arm/mach-msm/msm_xo.c b/arch/arm/mach-msm/msm_xo.c new file mode 100644 index 0000000000000000000000000000000000000000..407231ae0ccf4f9303dab3209ace569082f8f4f6 --- /dev/null +++ b/arch/arm/mach-msm/msm_xo.c @@ -0,0 +1,386 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "rpm_resources.h" + +static DEFINE_MUTEX(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_to_str[NUM_MSM_XO_IDS] = { + [MSM_XO_TCXO_D0] = "D0", + [MSM_XO_TCXO_D1] = "D1", + [MSM_XO_TCXO_A0] = "A0", + [MSM_XO_TCXO_A1] = "A1", + [MSM_XO_TCXO_A2] = "A2", + [MSM_XO_CORE] = "CORE", +}; + +static const char *msm_xo_mode_to_str[NUM_MSM_XO_MODES] = { + [MSM_XO_MODE_ON] = "ON", + [MSM_XO_MODE_PIN_CTRL] = "PIN", + [MSM_XO_MODE_OFF] = "OFF", +}; + +static int msm_xo_debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t msm_xo_debugfs_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + int r; + char buf[10]; + struct msm_xo_voter *xo = filp->private_data; + + r = snprintf(buf, sizeof(buf), "%s\n", msm_xo_mode_to_str[xo->mode]); + return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); +} + +static ssize_t msm_xo_debugfs_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + struct msm_xo_voter *xo = filp->private_data; + char buf[10], *b; + int i, ret; + + if (cnt > sizeof(buf) - 1) + return -EINVAL; + + if (copy_from_user(&buf, ubuf, cnt)) + return -EFAULT; + buf[cnt] = '\0'; + b = strstrip(buf); + + for (i = 0; i < ARRAY_SIZE(msm_xo_mode_to_str); i++) + if (!strncasecmp(b, msm_xo_mode_to_str[i], sizeof(buf))) { + ret = msm_xo_mode_vote(xo, i); + return ret ? : cnt; + } + + return -EINVAL; +} + +static const struct file_operations msm_xo_debugfs_fops = { + .open = msm_xo_debugfs_open, + .read = msm_xo_debugfs_read, + .write = msm_xo_debugfs_write, +}; + +static struct dentry *xo_debugfs_root; +static struct msm_xo_voter *xo_debugfs_voters[NUM_MSM_XO_IDS]; + +static int __init msm_xo_init_debugfs_voters(void) +{ + int i; + + xo_debugfs_root = debugfs_create_dir("msm_xo", NULL); + if (!xo_debugfs_root) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++) { + xo_debugfs_voters[i] = msm_xo_get(i, "debugfs"); + if (IS_ERR(xo_debugfs_voters[i])) + goto err; + debugfs_create_file(msm_xo_to_str[i], S_IRUGO | S_IWUSR, + xo_debugfs_root, xo_debugfs_voters[i], + &msm_xo_debugfs_fops); + } + return 0; +err: + while (--i >= 0) + msm_xo_put(xo_debugfs_voters[i]); + debugfs_remove_recursive(xo_debugfs_root); + return -ENOMEM; +} + +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, "CXO %-16s%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) +{ + int i; + + mutex_lock(&msm_xo_lock); + for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++) + msm_xo_dump_xo(m, &msm_xo_sources[i], msm_xo_to_str[i]); + mutex_unlock(&msm_xo_lock); + + 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) +{ + msm_xo_init_debugfs_voters(); + if (!debugfs_create_file("xo_voters", S_IRUGO, NULL, NULL, + &msm_xo_voters_ops)) + return -ENOMEM; + return 0; +} +#else +static int __init msm_xo_debugfs_init(void) { return 0; } +#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; + 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) | + /* + * 8660 RPM has XO_CORE at bit 18 and 8960 RPM has + * XO_CORE at bit 20. Since the opposite bit is + * reserved in both cases, just set both and be + * done with it. + */ + ((msm_xo_sources[MSM_XO_CORE].mode ? 1 : 0) << 20) | + ((msm_xo_sources[MSM_XO_CORE].mode ? 1 : 0) << 18); + ret = msm_rpm_set(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; + int is_d0 = xo == &msm_xo_sources[MSM_XO_TCXO_D0]; + int needs_workaround = cpu_is_msm8960() || cpu_is_apq8064() || + cpu_is_msm8930() || cpu_is_msm9615(); + + 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; + } + /* TODO: Remove once RPM separates the concept of D0 and CXO */ + if (is_d0 && needs_workaround) { + static struct clk *xo_clk; + + if (!xo_clk) { + xo_clk = clk_get_sys("msm_xo", "xo"); + BUG_ON(IS_ERR(xo_clk)); + } + /* Ignore transitions from pin to on or vice versa */ + if (mode && xo_voter->mode == MSM_XO_MODE_OFF) + clk_enable(xo_clk); + else if (!mode) + clk_disable(xo_clk); + } + 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; + + if (!xo_voter) + return 0; + + if (mode >= NUM_MSM_XO_MODES || IS_ERR(xo_voter)) + return -EINVAL; + + mutex_lock(&msm_xo_lock); + ret = __msm_xo_mode_vote(xo_voter, mode); + mutex_unlock(&msm_xo_lock); + + 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; + 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 */ + mutex_lock(&msm_xo_lock); + xo_voter->xo->votes[MSM_XO_MODE_OFF]++; + list_add(&xo_voter->list, &xo_voter->xo->voters); + mutex_unlock(&msm_xo_lock); + + 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) +{ + if (!xo_voter || IS_ERR(xo_voter)) + return; + + mutex_lock(&msm_xo_lock); + __msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF); + xo_voter->xo->votes[MSM_XO_MODE_OFF]--; + list_del(&xo_voter->list); + mutex_unlock(&msm_xo_lock); + + kfree(xo_voter->name); + kfree(xo_voter); +} +EXPORT_SYMBOL(msm_xo_put); + +int __init msm_xo_init(void) +{ + int i, ret; + struct msm_rpm_iv_pair cmd[1]; + + for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++) + INIT_LIST_HEAD(&msm_xo_sources[i].voters); + + cmd[0].id = MSM_RPM_ID_CXO_BUFFERS; + cmd[0].value = 0; + ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, cmd, ARRAY_SIZE(cmd)); + if (ret) + return ret; + msm_xo_debugfs_init(); + return 0; +} diff --git a/arch/arm/mach-msm/nand_partitions.c b/arch/arm/mach-msm/nand_partitions.c new file mode 100644 index 0000000000000000000000000000000000000000..499ad992ba160e43df5d7b16fdbd96f0cd0f1587 --- /dev/null +++ b/arch/arm/mach-msm/nand_partitions.c @@ -0,0 +1,202 @@ +/* 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,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 +#ifdef CONFIG_MSM_SMD +#include "smd_private.h" +#endif + +/* 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 18 + +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]; +}; + +#ifdef CONFIG_MSM_SMD +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; +} +#else +static int get_nand_partitions(void) +{ + + if (msm_nand_data.nr_parts) + return 0; + + printk(KERN_WARNING "%s: no partition table found!", __func__); + + return -ENODEV; +} +#endif + +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 0000000000000000000000000000000000000000..d38b4167ce45d2126020bbdbf3b3be73dff040ac --- /dev/null +++ b/arch/arm/mach-msm/no-pm.c @@ -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. + */ + +#include + +#include +#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_cpu_enter_lowpower(unsigned cpu) +{ + asm("wfi" + : + : + : "memory", "cc"); +} + +void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns) { } + +void msm_pm_set_irq_extns(struct msm_pm_irq_calls *irq_calls) {} + +int msm_pm_idle_prepare(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + return -ENOSYS; +} + +int msm_pm_idle_enter(enum msm_pm_sleep_mode sleep_mode) +{ + return -ENOSYS; +} + diff --git a/arch/arm/mach-msm/nohlt.c b/arch/arm/mach-msm/nohlt.c new file mode 100644 index 0000000000000000000000000000000000000000..532d57d6eb73389ac079edf04f57f3179ced7568 --- /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/ocmem.c b/arch/arm/mach-msm/ocmem.c new file mode 100644 index 0000000000000000000000000000000000000000..69e39dff4928bb3c065b7fff3cc1c0a871b24117 --- /dev/null +++ b/arch/arm/mach-msm/ocmem.c @@ -0,0 +1,314 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +struct ocmem_partition { + const char *name; + int id; + unsigned long p_start; + unsigned long p_size; + unsigned long p_min; + unsigned int p_tail; +}; + +struct ocmem_plat_data { + void __iomem *vbase; + unsigned long size; + unsigned long base; + struct ocmem_partition *parts; + unsigned nr_parts; +}; + +struct ocmem_zone zones[OCMEM_CLIENT_MAX]; + +struct ocmem_zone *get_zone(unsigned id) +{ + if (id < OCMEM_GRAPHICS || id >= OCMEM_CLIENT_MAX) + return NULL; + else + return &zones[id]; +} + +static struct ocmem_plat_data *ocmem_pdata; + +#define CLIENT_NAME_MAX 10 +/* Must be in sync with enum ocmem_client */ +static const char *client_names[OCMEM_CLIENT_MAX] = { + "graphics", + "video", + "camera", + "hp_audio", + "voice", + "lp_audio", + "sensors", + "blast", +}; + +struct ocmem_quota_table { + const char *name; + int id; + unsigned long start; + unsigned long size; + unsigned long min; + unsigned int tail; +}; + +/* This static table will go away with device tree support */ +static struct ocmem_quota_table qt[OCMEM_CLIENT_MAX] = { + /* name, id, start, size, min, tail */ + { "graphics", OCMEM_GRAPHICS, 0x0, 0x100000, 0x80000, 0}, + { "video", OCMEM_VIDEO, 0x100000, 0x80000, 0x55000, 1}, + { "camera", OCMEM_CAMERA, 0x0, 0x0, 0x0, 0}, + { "voice", OCMEM_VOICE, 0x0, 0x0, 0x0, 0 }, + { "hp_audio", OCMEM_HP_AUDIO, 0x0, 0x0, 0x0, 0}, + { "lp_audio", OCMEM_LP_AUDIO, 0x80000, 0xA0000, 0xA0000, 0}, + { "blast", OCMEM_BLAST, 0x120000, 0x20000, 0x20000, 0}, + { "sensors", OCMEM_SENSORS, 0x140000, 0x40000, 0x40000, 0}, +}; + +static inline int get_id(const char *name) +{ + int i = 0; + for (i = 0 ; i < OCMEM_CLIENT_MAX; i++) { + if (strncmp(name, client_names[i], CLIENT_NAME_MAX) == 0) + return i; + } + return -EINVAL; +} + +static struct ocmem_plat_data *parse_static_config(struct platform_device *pdev) +{ + struct ocmem_plat_data *pdata = NULL; + struct ocmem_partition *parts = NULL; + struct device *dev = &pdev->dev; + unsigned nr_parts = 0; + int i; + int j; + + pdata = devm_kzalloc(dev, sizeof(struct ocmem_plat_data), + GFP_KERNEL); + + if (!pdata) { + dev_err(dev, "Unable to allocate memory for" + " platform data\n"); + return NULL; + } + + for (i = 0 ; i < ARRAY_SIZE(qt); i++) + if (qt[i].size != 0x0) + nr_parts++; + + if (nr_parts == 0x0) { + dev_err(dev, "No valid ocmem partitions\n"); + return NULL; + } else + dev_info(dev, "Total partitions = %d\n", nr_parts); + + parts = devm_kzalloc(dev, sizeof(struct ocmem_partition) * nr_parts, + GFP_KERNEL); + + if (!parts) { + dev_err(dev, "Unable to allocate memory for" + " partition data\n"); + return NULL; + } + + for (i = 0, j = 0; i < ARRAY_SIZE(qt); i++) { + if (qt[i].size == 0x0) { + dev_dbg(dev, "Skipping creation of pool for %s\n", + qt[i].name); + continue; + } + parts[j].id = qt[i].id; + parts[j].p_size = qt[i].size; + parts[j].p_start = qt[i].start; + parts[j].p_min = qt[i].min; + parts[j].p_tail = qt[i].tail; + j++; + } + BUG_ON(j != nr_parts); + pdata->nr_parts = nr_parts; + pdata->parts = parts; + pdata->base = OCMEM_PHYS_BASE; + pdata->size = OCMEM_PHYS_SIZE; + return pdata; +} + +static struct ocmem_plat_data *parse_dt_config(struct platform_device *pdev) +{ + return NULL; +} + +static int ocmem_zone_init(struct platform_device *pdev) +{ + + int ret = -1; + int i = 0; + unsigned active_zones = 0; + + struct ocmem_zone *zone = NULL; + struct ocmem_zone_ops *z_ops = NULL; + struct device *dev = &pdev->dev; + unsigned long start; + struct ocmem_plat_data *pdata = NULL; + + pdata = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->nr_parts; i++) { + struct ocmem_partition *part = &pdata->parts[i]; + zone = get_zone(part->id); + + dev_dbg(dev, "Partition %d, start %lx, size %lx for %s\n", + i, part->p_start, part->p_size, + client_names[part->id]); + + if (part->p_size > pdata->size) { + dev_alert(dev, "Quota > ocmem_size for id:%d\n", + part->id); + continue; + } + + zone->z_pool = gen_pool_create(PAGE_SHIFT, -1); + + if (!zone->z_pool) { + dev_alert(dev, "Creating pool failed for id:%d\n", + part->id); + return -EBUSY; + } + + start = pdata->base + part->p_start; + ret = gen_pool_add(zone->z_pool, start, + part->p_size, -1); + + if (ret < 0) { + gen_pool_destroy(zone->z_pool); + dev_alert(dev, "Unable to back pool %d with " + "buffer:%lx\n", part->id, part->p_size); + return -EBUSY; + } + + /* Initialize zone allocators */ + z_ops = devm_kzalloc(dev, sizeof(struct ocmem_zone_ops), + GFP_KERNEL); + if (!z_ops) { + pr_alert("ocmem: Unable to allocate memory for" + "zone ops:%d\n", i); + return -EBUSY; + } + + /* Initialize zone parameters */ + zone->z_start = start; + zone->z_head = zone->z_start; + zone->z_end = start + part->p_size; + zone->z_tail = zone->z_end; + zone->z_free = part->p_size; + zone->owner = part->id; + zone->active_regions = 0; + zone->max_regions = 0; + INIT_LIST_HEAD(&zone->region_list); + zone->z_ops = z_ops; + if (part->p_tail) { + z_ops->allocate = allocate_tail; + z_ops->free = free_tail; + } else { + z_ops->allocate = allocate_head; + z_ops->free = free_head; + } + active_zones++; + + if (active_zones == 1) + pr_info("Physical OCMEM zone layout:\n"); + + pr_info(" zone %s\t: 0x%08lx - 0x%08lx (%4ld KB)\n", + client_names[part->id], zone->z_start, + zone->z_end, part->p_size/SZ_1K); + } + + dev_info(dev, "Total active zones = %d\n", active_zones); + return 0; +} + +static int __devinit msm_ocmem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + if (!pdev->dev.of_node->child) { + dev_info(dev, "Missing Configuration in Device Tree\n"); + ocmem_pdata = parse_static_config(pdev); + } else { + ocmem_pdata = parse_dt_config(pdev); + } + + /* Check if we have some configuration data to start */ + if (!ocmem_pdata) + return -ENODEV; + + /* Sanity Checks */ + BUG_ON(!IS_ALIGNED(ocmem_pdata->size, PAGE_SIZE)); + BUG_ON(!IS_ALIGNED(ocmem_pdata->base, PAGE_SIZE)); + + platform_set_drvdata(pdev, ocmem_pdata); + + if (ocmem_zone_init(pdev)) + return -EBUSY; + + dev_info(dev, "initialized successfully\n"); + return 0; +} + +static int __devexit msm_ocmem_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id msm_ocmem_dt_match[] = { + { .compatible = "qcom,msm_ocmem", + }, + {} +}; + +static struct platform_driver msm_ocmem_driver = { + .probe = msm_ocmem_probe, + .remove = __devexit_p(msm_ocmem_remove), + .driver = { + .name = "msm_ocmem", + .owner = THIS_MODULE, + .of_match_table = msm_ocmem_dt_match, + }, +}; + +static int __init ocmem_init(void) +{ + return platform_driver_register(&msm_ocmem_driver); +} +subsys_initcall(ocmem_init); + +static void __exit ocmem_exit(void) +{ + platform_driver_unregister(&msm_ocmem_driver); +} +module_exit(ocmem_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Support for On-Chip Memory on MSM"); diff --git a/arch/arm/mach-msm/ocmem_allocator.c b/arch/arm/mach-msm/ocmem_allocator.c new file mode 100644 index 0000000000000000000000000000000000000000..71cacda4dc793ed7680d7ab7df64ddd19bcfd437 --- /dev/null +++ b/arch/arm/mach-msm/ocmem_allocator.c @@ -0,0 +1,105 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +/* All allocator operations are serialized by ocmem driver */ + +/* The allocators work as follows: + Constraints: + 1) There is no IOMMU access to OCMEM hence successive allocations + in the zone must be physically contiguous + 2) Allocations must be freed in reverse order within a zone. + + z->z_start: Fixed pointer to the start of a zone + z->z_end: Fixed pointer to the end of a zone + + z->z_head: Movable pointer to the next free area when growing at head + Fixed on zones that grow from tail + + z->z_tail: Movable pointer to the next free area when growing at tail + Fixed on zones that grow from head + + z->z_free: Free space in a zone that is updated on an allocation/free + + reserve: Enable libgenpool to simulate tail allocations +*/ + +unsigned long allocate_head(struct ocmem_zone *z, unsigned long size) +{ + + unsigned long offset; + + offset = gen_pool_alloc(z->z_pool, size); + + if (!offset) + return -ENOMEM; + + z->z_head += size; + z->z_free -= size; + return offset; +} + +unsigned long allocate_tail(struct ocmem_zone *z, unsigned long size) +{ + unsigned long offset; + unsigned long reserve; + unsigned long head; + + if (z->z_tail < (z->z_head + size)) + return -ENOMEM; + + reserve = z->z_tail - z->z_head - size; + if (reserve) { + head = gen_pool_alloc(z->z_pool, reserve); + offset = gen_pool_alloc(z->z_pool, size); + gen_pool_free(z->z_pool, head, reserve); + } else + offset = gen_pool_alloc(z->z_pool, size); + + if (!offset) + return -ENOMEM; + + z->z_tail -= size; + z->z_free -= size; + return offset; +} + +int free_head(struct ocmem_zone *z, unsigned long offset, + unsigned long size) +{ + if (offset > z->z_head) { + pr_err("ocmem: Detected out of order free " + "leading to fragmentation\n"); + return -EINVAL; + } + gen_pool_free(z->z_pool, offset, size); + z->z_head -= size; + z->z_free += size; + return 0; +} + +int free_tail(struct ocmem_zone *z, unsigned long offset, + unsigned long size) +{ + if (offset > z->z_tail) { + pr_err("ocmem: Detected out of order free " + "leading to fragmentation\n"); + return -EINVAL; + } + gen_pool_free(z->z_pool, offset, size); + z->z_tail += size; + z->z_free += size; + return 0; +} 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 0000000000000000000000000000000000000000..bcf6e57d588a5cc50183236b7f6c8ec54dd65b8d --- /dev/null +++ b/arch/arm/mach-msm/oem_rapi_client.c @@ -0,0 +1,357 @@ +/* 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 +#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/pcie.c b/arch/arm/mach-msm/pcie.c new file mode 100644 index 0000000000000000000000000000000000000000..f0809d35790690139a036584e1724b4aba02b8c9 --- /dev/null +++ b/arch/arm/mach-msm/pcie.c @@ -0,0 +1,671 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 PCIe controller driver. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie.h" + +/* Root Complex Port vendor/device IDs */ +#define PCIE_VENDOR_ID_RCP 0x17cb +#define PCIE_DEVICE_ID_RCP 0x0101 + +#define PCIE20_PARF_PCS_DEEMPH 0x34 +#define PCIE20_PARF_PCS_SWING 0x38 +#define PCIE20_PARF_PHY_CTRL 0x40 +#define PCIE20_PARF_PHY_REFCLK 0x4C +#define PCIE20_PARF_CONFIG_BITS 0x50 + +#define PCIE20_ELBI_SYS_CTRL 0x04 + +#define PCIE20_CAP 0x70 +#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) + +#define PCIE20_COMMAND_STATUS 0x04 +#define PCIE20_BUSNUMBERS 0x18 +#define PCIE20_MEMORY_BASE_LIMIT 0x20 + +#define PCIE20_PLR_IATU_VIEWPORT 0x900 +#define PCIE20_PLR_IATU_CTRL1 0x904 +#define PCIE20_PLR_IATU_CTRL2 0x908 +#define PCIE20_PLR_IATU_LBAR 0x90C +#define PCIE20_PLR_IATU_UBAR 0x910 +#define PCIE20_PLR_IATU_LAR 0x914 +#define PCIE20_PLR_IATU_LTAR 0x918 +#define PCIE20_PLR_IATU_UTAR 0x91c + +#define PCIE_RESET (MSM_CLK_CTL_BASE + 0x22dc) +#define PCIE_SFAB_AXI_S5_FCLK_CTL (MSM_CLK_CTL_BASE + 0x2154) + +#define MSM_PCIE_DEV_BAR_ADDR PCIBIOS_MIN_MEM +#define MSM_PCIE_DEV_CFG_ADDR 0x01000000 + +#define RD 0 +#define WR 1 + +/* debug mask sys interface */ +static int msm_pcie_debug_mask; +module_param_named(debug_mask, msm_pcie_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +/* resources from device file */ +enum msm_pcie_res { + MSM_PCIE_RES_PARF, + MSM_PCIE_RES_ELBI, + MSM_PCIE_RES_PCIE20, + MSM_PCIE_RES_AXI_BAR, + MSM_PCIE_RES_AXI_CONF, + MSM_PCIE_MAX_RES +}; + +/* msm pcie device data */ +static struct msm_pcie_dev_t msm_pcie_dev; + +/* regulators */ +static struct msm_pcie_vreg_info_t msm_pcie_vreg_info[MSM_PCIE_MAX_VREG] = { + {NULL, "vp_pcie", 1050000, 1050000, 40900}, + {NULL, "vptx_pcie", 1050000, 1050000, 18200}, + {NULL, "vdd_pcie_vph", 0, 0, 0}, + {NULL, "pcie_ext_3p3v", 0, 0, 0} +}; + +/* clocks */ +static struct msm_pcie_clk_info_t msm_pcie_clk_info[MSM_PCIE_MAX_CLK] = { + {NULL, "bus_clk"}, + {NULL, "iface_clk"}, + {NULL, "ref_clk"} +}; + +/* resources */ +static struct msm_pcie_res_info_t msm_pcie_res_info[MSM_PCIE_MAX_RES] = { + {"parf", 0, 0, 0}, + {"elbi", 0, 0, 0}, + {"pcie20", 0, 0, 0}, + {"axi_bar", 0, 0, 0}, + {"axi_conf", 0, 0, 0}, +}; + +int msm_pcie_get_debug_mask(void) +{ + return msm_pcie_debug_mask; +} + +static void msm_pcie_write_mask(void __iomem *addr, + uint32_t clear_mask, uint32_t set_mask) +{ + uint32_t val; + + val = (readl_relaxed(addr) & ~clear_mask) | set_mask; + writel_relaxed(val, addr); + wmb(); /* ensure data is written to hardware register */ +} + +static int msm_pcie_is_link_up(void) +{ + return readl_relaxed(msm_pcie_dev.pcie20 + PCIE20_CAP_LINKCTRLSTATUS) & + BIT(29); +} + +static inline int msm_pcie_oper_conf(struct pci_bus *bus, u32 devfn, int oper, + int where, int size, u32 *val) +{ + uint32_t word_offset, byte_offset, mask; + uint32_t rd_val, wr_val; + struct msm_pcie_dev_t *dev = &msm_pcie_dev; + void __iomem *config_base; + + /* + * Only buses 0 and 1 are supported. RC port on bus 0 and EP in bus 1. + * For downstream bus (1), make sure link is up + */ + if ((bus->number > 1) || (devfn != 0)) { + PCIE_DBG("invalid %s - bus %d devfn %d\n", + (oper == RD) ? "rd" : "wr", bus->number, devfn); + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } else if ((bus->number != 0) && !msm_pcie_is_link_up()) { + PCIE_DBG("%s fail, link down - bus %d devfn %d\n", + (oper == RD) ? "rd" : "wr", bus->number, devfn); + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + word_offset = where & ~0x3; + byte_offset = where & 0x3; + mask = (~0 >> (8 * (4 - size))) << (8 * byte_offset); + + config_base = (bus->number == 0) ? dev->pcie20 : dev->axi_conf; + rd_val = readl_relaxed(config_base + word_offset); + + if (oper == RD) { + *val = ((rd_val & mask) >> (8 * byte_offset)); + + PCIE_DBG("%d:0x%02x + 0x%04x[%d] -> 0x%08x; rd 0x%08x\n", + bus->number, devfn, where, size, *val, rd_val); + } else { + wr_val = (rd_val & ~mask) | + ((*val << (8 * byte_offset)) & mask); + writel_relaxed(wr_val, config_base + word_offset); + wmb(); /* ensure config data is written to hardware register */ + + PCIE_DBG("%d:0x%02x + 0x%04x[%d] <- 0x%08x;" + " rd 0x%08x val 0x%08x\n", bus->number, + devfn, where, size, wr_val, rd_val, *val); + } + + return 0; +} + +static int msm_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) +{ + return msm_pcie_oper_conf(bus, devfn, RD, where, size, val); +} + +static int msm_pcie_wr_conf(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + return msm_pcie_oper_conf(bus, devfn, WR, where, size, &val); +} + +static struct pci_ops msm_pcie_ops = { + .read = msm_pcie_rd_conf, + .write = msm_pcie_wr_conf, +}; + +static int __init msm_pcie_gpio_init(void) +{ + int rc, i; + struct msm_pcie_gpio_info_t *info; + + for (i = 0; i < MSM_PCIE_MAX_GPIO; i++) { + info = &msm_pcie_dev.gpio[i]; + + rc = gpio_request(info->num, info->name); + if (rc) { + pr_err("can't get gpio %s; %d\n", info->name, rc); + break; + } + + rc = gpio_direction_output(info->num, 0); + if (rc) { + pr_err("can't set gpio direction %s; %d\n", + info->name, rc); + gpio_free(info->num); + break; + } + } + + if (rc) + while (i--) + gpio_free(msm_pcie_dev.gpio[i].num); + + return rc; +} + +static void msm_pcie_gpio_deinit(void) +{ + int i; + + for (i = 0; i < MSM_PCIE_MAX_GPIO; i++) + gpio_free(msm_pcie_dev.gpio[i].num); +} + +static int __init msm_pcie_vreg_init(struct device *dev) +{ + int i, rc = 0; + struct regulator *vreg; + struct msm_pcie_vreg_info_t *info; + + for (i = 0; i < MSM_PCIE_MAX_VREG; i++) { + info = &msm_pcie_dev.vreg[i]; + + vreg = regulator_get(dev, info->name); + if (!vreg || IS_ERR(vreg)) { + rc = (PTR_ERR(vreg)) ? PTR_ERR(vreg) : -ENODEV; + pr_err("can't get %s; %d\n", info->name, rc); + break; + } + + if (info->max_v) { + rc = regulator_set_voltage(vreg, + info->min_v, info->max_v); + if (rc) { + pr_err("can't set voltage %s; %d\n", + info->name, rc); + regulator_put(vreg); + break; + } + } + + if (info->opt_mode) { + rc = regulator_set_optimum_mode(vreg, info->opt_mode); + if (rc < 0) { + pr_err("can't set mode %s; %d\n", + info->name, rc); + regulator_put(vreg); + break; + } + } + + rc = regulator_enable(vreg); + if (rc) { + pr_err("can't enable %s, %d\n", info->name, rc); + regulator_put(vreg); + break; + } + info->hdl = vreg; + } + + if (rc) + while (i--) { + regulator_disable(msm_pcie_dev.vreg[i].hdl); + regulator_put(msm_pcie_dev.vreg[i].hdl); + msm_pcie_dev.vreg[i].hdl = NULL; + } + + return rc; +} + +static void msm_pcie_vreg_deinit(void) +{ + int i; + + for (i = 0; i < MSM_PCIE_MAX_VREG; i++) { + regulator_disable(msm_pcie_dev.vreg[i].hdl); + regulator_put(msm_pcie_dev.vreg[i].hdl); + msm_pcie_dev.vreg[i].hdl = NULL; + } +} + +static int __init msm_pcie_clk_init(struct device *dev) +{ + int i, rc = 0; + struct clk *clk_hdl; + struct msm_pcie_clk_info_t *info; + + for (i = 0; i < MSM_PCIE_MAX_CLK; i++) { + info = &msm_pcie_dev.clk[i]; + + clk_hdl = clk_get(dev, info->name); + if (!clk_hdl || IS_ERR(clk_hdl)) { + rc = (PTR_ERR(clk_hdl)) ? PTR_ERR(clk_hdl) : -ENODEV; + pr_err("can't get clk %s; %d\n", info->name, rc); + break; + } + clk_prepare_enable(clk_hdl); + info->hdl = clk_hdl; + } + + if (rc) + while (i--) { + clk_disable_unprepare(msm_pcie_dev.clk[i].hdl); + clk_put(msm_pcie_dev.clk[i].hdl); + msm_pcie_dev.clk[i].hdl = NULL; + } + + return rc; +} + +static void msm_pcie_clk_deinit(void) +{ + int i; + + for (i = 0; i < MSM_PCIE_MAX_CLK; i++) { + clk_disable_unprepare(msm_pcie_dev.clk[i].hdl); + clk_put(msm_pcie_dev.clk[i].hdl); + msm_pcie_dev.clk[i].hdl = NULL; + } +} + +static void __init msm_pcie_config_controller(void) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev; + struct msm_pcie_res_info_t *axi_bar = &dev->res[MSM_PCIE_RES_AXI_BAR]; + struct msm_pcie_res_info_t *axi_conf = &dev->res[MSM_PCIE_RES_AXI_CONF]; + + /* + * program and enable address translation region 0 (device config + * address space); region type config; + * axi config address range to device config address range + */ + writel_relaxed(0, dev->pcie20 + PCIE20_PLR_IATU_VIEWPORT); + /* ensure that hardware locks the region before programming it */ + wmb(); + + writel_relaxed(4, dev->pcie20 + PCIE20_PLR_IATU_CTRL1); + writel_relaxed(BIT(31), dev->pcie20 + PCIE20_PLR_IATU_CTRL2); + writel_relaxed(axi_conf->start, dev->pcie20 + PCIE20_PLR_IATU_LBAR); + writel_relaxed(0, dev->pcie20 + PCIE20_PLR_IATU_UBAR); + writel_relaxed(axi_conf->end, dev->pcie20 + PCIE20_PLR_IATU_LAR); + writel_relaxed(MSM_PCIE_DEV_CFG_ADDR, + dev->pcie20 + PCIE20_PLR_IATU_LTAR); + writel_relaxed(0, dev->pcie20 + PCIE20_PLR_IATU_UTAR); + /* ensure that hardware registers the configuration */ + wmb(); + + /* + * program and enable address translation region 2 (device resource + * address space); region type memory; + * axi device bar address range to device bar address range + */ + writel_relaxed(2, dev->pcie20 + PCIE20_PLR_IATU_VIEWPORT); + /* ensure that hardware locks the region before programming it */ + wmb(); + + writel_relaxed(0, dev->pcie20 + PCIE20_PLR_IATU_CTRL1); + writel_relaxed(BIT(31), dev->pcie20 + PCIE20_PLR_IATU_CTRL2); + writel_relaxed(axi_bar->start, dev->pcie20 + PCIE20_PLR_IATU_LBAR); + writel_relaxed(0, dev->pcie20 + PCIE20_PLR_IATU_UBAR); + writel_relaxed(axi_bar->end, dev->pcie20 + PCIE20_PLR_IATU_LAR); + writel_relaxed(MSM_PCIE_DEV_BAR_ADDR, + dev->pcie20 + PCIE20_PLR_IATU_LTAR); + writel_relaxed(0, dev->pcie20 + PCIE20_PLR_IATU_UTAR); + /* ensure that hardware registers the configuration */ + wmb(); +} + +static int __init msm_pcie_get_resources(struct platform_device *pdev) +{ + int i, rc = 0; + struct resource *res; + struct msm_pcie_res_info_t *info; + struct msm_pcie_dev_t *dev = &msm_pcie_dev; + + for (i = 0; i < MSM_PCIE_MAX_RES; i++) { + info = &dev->res[i]; + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, info->name); + if (!res) { + pr_err("can't get %s resource\n", info->name); + rc = -ENOMEM; + break; + } + + info->base = ioremap(res->start, resource_size(res)); + if (!info->base) { + pr_err("can't remap %s\n", info->name); + rc = -ENOMEM; + break; + } + + info->start = res->start; + info->end = res->end; + } + + if (rc) { + while (i--) { + iounmap(dev->res[i].base); + dev->res[i].base = NULL; + } + } else { + dev->parf = dev->res[MSM_PCIE_RES_PARF].base; + dev->elbi = dev->res[MSM_PCIE_RES_ELBI].base; + dev->pcie20 = dev->res[MSM_PCIE_RES_PCIE20].base; + dev->axi_conf = dev->res[MSM_PCIE_RES_AXI_CONF].base; + } + + return rc; +} + +static void msm_pcie_release_resources(void) +{ + int i; + + for (i = 0; i < MSM_PCIE_MAX_RES; i++) { + iounmap(msm_pcie_dev.res[i].base); + msm_pcie_dev.res[i].base = NULL; + } + + msm_pcie_dev.parf = NULL; + msm_pcie_dev.elbi = NULL; + msm_pcie_dev.pcie20 = NULL; + msm_pcie_dev.axi_conf = NULL; +} + +static int __init msm_pcie_setup(int nr, struct pci_sys_data *sys) +{ + int rc; + struct msm_pcie_dev_t *dev = &msm_pcie_dev; + uint32_t val; + + PCIE_DBG("bus %d\n", nr); + if (nr != 0) + return 0; + + /* assert PCIe reset link to keep EP in reset */ + gpio_set_value_cansleep(dev->gpio[MSM_PCIE_GPIO_RST_N].num, + dev->gpio[MSM_PCIE_GPIO_RST_N].on); + + /* enable power */ + rc = msm_pcie_vreg_init(&dev->pdev->dev); + if (rc) + goto out; + + /* assert PCIe PARF reset while powering the core */ + msm_pcie_write_mask(PCIE_RESET, 0, BIT(2)); + + /* enable clocks */ + rc = msm_pcie_clk_init(&dev->pdev->dev); + if (rc) + goto clk_fail; + + /* enable pcie power; wait 3ms for clock to stabilize */ + gpio_set_value_cansleep(dev->gpio[MSM_PCIE_GPIO_PWR_EN].num, + dev->gpio[MSM_PCIE_GPIO_PWR_EN].on); + usleep(3000); + + /* + * de-assert PCIe PARF reset; + * wait 1us before accessing PARF registers + */ + msm_pcie_write_mask(PCIE_RESET, BIT(2), 0); + udelay(1); + + /* enable PCIe clocks and resets */ + msm_pcie_write_mask(dev->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); + + /* PARF programming */ + writel_relaxed(0x282828, dev->parf + PCIE20_PARF_PCS_DEEMPH); + writel_relaxed(0x7F7F, dev->parf + PCIE20_PARF_PCS_SWING); + writel_relaxed((4<<24), dev->parf + PCIE20_PARF_CONFIG_BITS); + /* ensure that hardware registers the PARF configuration */ + wmb(); + + /* enable reference clock */ + msm_pcie_write_mask(dev->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); + + /* enable access to PCIe slave port on system fabric */ + writel_relaxed(BIT(4), PCIE_SFAB_AXI_S5_FCLK_CTL); + /* ensure that access is enabled before proceeding */ + wmb(); + + /* de-assert PICe PHY, Core, POR and AXI clk domain resets */ + msm_pcie_write_mask(PCIE_RESET, BIT(5), 0); + msm_pcie_write_mask(PCIE_RESET, BIT(4), 0); + msm_pcie_write_mask(PCIE_RESET, BIT(3), 0); + msm_pcie_write_mask(PCIE_RESET, BIT(0), 0); + + /* wait 150ms for clock acquisition */ + udelay(150); + + /* de-assert PCIe reset link to bring EP out of reset */ + gpio_set_value_cansleep(dev->gpio[MSM_PCIE_GPIO_RST_N].num, + !dev->gpio[MSM_PCIE_GPIO_RST_N].on); + + /* enable link training */ + msm_pcie_write_mask(dev->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0)); + + /* poll for link to come up for upto 100ms */ + rc = readl_poll_timeout( + (msm_pcie_dev.pcie20 + PCIE20_CAP_LINKCTRLSTATUS), + val, (val & BIT(29)), 10000, 100000); + if (rc) { + pr_err("link initialization failed\n"); + goto link_fail; + } else + pr_info("link initialized\n"); + + msm_pcie_config_controller(); + rc = msm_pcie_irq_init(dev); + if (!rc) + goto out; + +link_fail: + msm_pcie_clk_deinit(); +clk_fail: + msm_pcie_vreg_deinit(); +out: + return (rc) ? 0 : 1; +} + +static struct pci_bus __init *msm_pcie_scan_bus(int nr, + struct pci_sys_data *sys) +{ + struct pci_bus *bus = NULL; + + PCIE_DBG("bus %d\n", nr); + if (nr == 0) + bus = pci_scan_bus(sys->busnr, &msm_pcie_ops, sys); + + return bus; +} + +static int __init msm_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + PCIE_DBG("slot %d pin %d\n", slot, pin); + return (pin <= 4) ? (PCIE20_INTA + pin - 1) : 0; +} + +static struct hw_pci msm_pci __initdata = { + .nr_controllers = 1, + .swizzle = pci_std_swizzle, + .setup = msm_pcie_setup, + .scan = msm_pcie_scan_bus, + .map_irq = msm_pcie_map_irq, +}; + +static int __init msm_pcie_probe(struct platform_device *pdev) +{ + const struct msm_pcie_platform *pdata; + int rc; + + PCIE_DBG("\n"); + + msm_pcie_dev.pdev = pdev; + pdata = pdev->dev.platform_data; + msm_pcie_dev.gpio = pdata->gpio; + msm_pcie_dev.vreg = msm_pcie_vreg_info; + msm_pcie_dev.clk = msm_pcie_clk_info; + msm_pcie_dev.res = msm_pcie_res_info; + + rc = msm_pcie_get_resources(msm_pcie_dev.pdev); + if (rc) + return rc; + + rc = msm_pcie_gpio_init(); + if (rc) { + msm_pcie_release_resources(); + return rc; + } + + /* kick start ARM PCI configuration framework */ + pci_common_init(&msm_pci); + return 0; +} + +static int __exit msm_pcie_remove(struct platform_device *pdev) +{ + PCIE_DBG("\n"); + + msm_pcie_irq_deinit(&msm_pcie_dev); + msm_pcie_vreg_deinit(); + msm_pcie_clk_deinit(); + msm_pcie_gpio_deinit(); + msm_pcie_release_resources(); + + msm_pcie_dev.pdev = NULL; + msm_pcie_dev.vreg = NULL; + msm_pcie_dev.clk = NULL; + msm_pcie_dev.gpio = NULL; + return 0; +} + +static struct platform_driver msm_pcie_driver = { + .remove = __exit_p(msm_pcie_remove), + .driver = { + .name = "msm_pcie", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_pcie_init(void) +{ + PCIE_DBG("\n"); + pcibios_min_io = 0x10000000; + pcibios_min_mem = 0x10000000; + return platform_driver_probe(&msm_pcie_driver, msm_pcie_probe); +} +subsys_initcall(msm_pcie_init); + +/* RC do not represent the right class; set it to PCI_CLASS_BRIDGE_PCI */ +static void __devinit msm_pcie_fixup_header(struct pci_dev *dev) +{ + PCIE_DBG("hdr_type %d\n", dev->hdr_type); + if (dev->hdr_type == 1) + dev->class = (dev->class & 0xff) | (PCI_CLASS_BRIDGE_PCI << 8); +} +DECLARE_PCI_FIXUP_HEADER(PCIE_VENDOR_ID_RCP, PCIE_DEVICE_ID_RCP, + msm_pcie_fixup_header); + +/* + * actual physical (BAR) address of the device resources starts from 0x10xxxxxx; + * the system axi address for the device resources starts from 0x08xxxxxx; + * correct the device resource structure here; address translation unit handles + * the required translations + */ +static void __devinit msm_pcie_fixup_final(struct pci_dev *dev) +{ + int i; + + PCIE_DBG("vendor 0x%x 0x%x\n", dev->vendor, dev->device); + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + if (dev->resource[i].start & 0xFF000000) { + dev->resource[i].start &= 0x00FFFFFF; + dev->resource[i].start |= 0x08000000; + dev->resource[i].end &= 0x00FFFFFF; + dev->resource[i].end |= 0x08000000; + } + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, msm_pcie_fixup_final); diff --git a/arch/arm/mach-msm/pcie.h b/arch/arm/mach-msm/pcie.h new file mode 100644 index 0000000000000000000000000000000000000000..4866ec5c0cce26f46e2009d8b75904ed313ef266 --- /dev/null +++ b/arch/arm/mach-msm/pcie.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PCIE_H +#define __ARCH_ARM_MACH_MSM_PCIE_H + +#include +#include +#include +#include +#include +#include + +#define MSM_PCIE_MAX_VREG 4 +#define MSM_PCIE_MAX_CLK 3 + +#define PCIE_DBG(x...) do { \ + if (msm_pcie_get_debug_mask()) \ + pr_info(x); \ + } while (0) + +/* voltage regulator info structrue */ +struct msm_pcie_vreg_info_t { + struct regulator *hdl; + char *name; + uint32_t max_v; + uint32_t min_v; + uint32_t opt_mode; +}; + +/* clock info structure */ +struct msm_pcie_clk_info_t { + struct clk *hdl; + char *name; +}; + +/* resource info structure */ +struct msm_pcie_res_info_t { + char *name; + uint32_t start; + uint32_t end; + void __iomem *base; +}; + +/* msm pcie device structure */ +struct msm_pcie_dev_t { + struct platform_device *pdev; + + struct msm_pcie_vreg_info_t *vreg; + struct msm_pcie_gpio_info_t *gpio; + struct msm_pcie_clk_info_t *clk; + struct msm_pcie_res_info_t *res; + + void __iomem *parf; + void __iomem *elbi; + void __iomem *pcie20; + void __iomem *axi_conf; +}; + +extern uint32_t msm_pcie_irq_init(struct msm_pcie_dev_t *dev); +extern void msm_pcie_irq_deinit(struct msm_pcie_dev_t *dev); +extern int msm_pcie_get_debug_mask(void); + +#endif diff --git a/arch/arm/mach-msm/pcie_irq.c b/arch/arm/mach-msm/pcie_irq.c new file mode 100644 index 0000000000000000000000000000000000000000..d91556168f9f5b62022fdb24c8ae53cf7736f6dc --- /dev/null +++ b/arch/arm/mach-msm/pcie_irq.c @@ -0,0 +1,170 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 PCIe controller IRQ driver. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "pcie.h" + +/* Any address will do here, as it won't be dereferenced */ +#define MSM_PCIE_MSI_PHY 0xa0000000 + +#define PCIE20_MSI_CTRL_ADDR (0x820) +#define PCIE20_MSI_CTRL_UPPER_ADDR (0x824) +#define PCIE20_MSI_CTRL_INTR_EN (0x828) +#define PCIE20_MSI_CTRL_INTR_MASK (0x82C) +#define PCIE20_MSI_CTRL_INTR_STATUS (0x830) + +#define PCIE20_MSI_CTRL_MAX 8 + +static DECLARE_BITMAP(msi_irq_in_use, NR_PCIE_MSI_IRQS); + +irqreturn_t handle_msi_irq(int irq, void *data) +{ + int i, j; + unsigned long val; + struct msm_pcie_dev_t *dev = data; + void __iomem *ctrl_status; + + /* check for set bits, clear it by setting that bit + and trigger corresponding irq */ + for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) { + ctrl_status = dev->pcie20 + + PCIE20_MSI_CTRL_INTR_STATUS + (i * 12); + + val = readl_relaxed(ctrl_status); + while (val) { + j = find_first_bit(&val, 32); + writel_relaxed(BIT(j), ctrl_status); + /* ensure that interrupt is cleared (acked) */ + wmb(); + + generic_handle_irq(MSM_PCIE_MSI_INT(j + (32 * i))); + val = readl_relaxed(ctrl_status); + } + } + + return IRQ_HANDLED; +} + +uint32_t __init msm_pcie_irq_init(struct msm_pcie_dev_t *dev) +{ + int i, rc; + + PCIE_DBG("\n"); + + /* program MSI controller and enable all interrupts */ + writel_relaxed(MSM_PCIE_MSI_PHY, dev->pcie20 + PCIE20_MSI_CTRL_ADDR); + writel_relaxed(0, dev->pcie20 + PCIE20_MSI_CTRL_UPPER_ADDR); + + for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) + writel_relaxed(~0, dev->pcie20 + + PCIE20_MSI_CTRL_INTR_EN + (i * 12)); + + /* ensure that hardware is configured before proceeding */ + wmb(); + + /* register handler for physical MSI interrupt line */ + rc = request_irq(PCIE20_INT_MSI, handle_msi_irq, IRQF_TRIGGER_RISING, + "msm_pcie_msi", dev); + if (rc) + pr_err("Unable to allocate msi interrupt\n"); + + return rc; +} + +void __exit msm_pcie_irq_deinit(struct msm_pcie_dev_t *dev) +{ + free_irq(PCIE20_INT_MSI, dev); +} + +void msm_pcie_destroy_irq(unsigned int irq) +{ + int pos = irq - MSM_PCIE_MSI_INT(0); + + dynamic_irq_cleanup(irq); + clear_bit(pos, msi_irq_in_use); +} + +/* hookup to linux pci msi framework */ +void arch_teardown_msi_irq(unsigned int irq) +{ + PCIE_DBG("irq %d deallocated\n", irq); + msm_pcie_destroy_irq(irq); +} + +static void msm_pcie_msi_nop(struct irq_data *d) +{ + return; +} + +static struct irq_chip pcie_msi_chip = { + .name = "msm-pcie-msi", + .irq_ack = msm_pcie_msi_nop, + .irq_enable = unmask_msi_irq, + .irq_disable = mask_msi_irq, + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, +}; + +static int msm_pcie_create_irq(void) +{ + int irq, pos; + +again: + pos = find_first_zero_bit(msi_irq_in_use, NR_PCIE_MSI_IRQS); + irq = MSM_PCIE_MSI_INT(pos); + if (irq >= (MSM_PCIE_MSI_INT(0) + NR_PCIE_MSI_IRQS)) + return -ENOSPC; + + if (test_and_set_bit(pos, msi_irq_in_use)) + goto again; + + dynamic_irq_init(irq); + return irq; +} + +/* hookup to linux pci msi framework */ +int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) +{ + int irq; + struct msi_msg msg; + + irq = msm_pcie_create_irq(); + if (irq < 0) + return irq; + + PCIE_DBG("irq %d allocated\n", irq); + + irq_set_msi_desc(irq, desc); + + /* write msi vector and data */ + msg.address_hi = 0; + msg.address_lo = MSM_PCIE_MSI_PHY; + msg.data = irq - MSM_PCIE_MSI_INT(0); + write_msi_msg(irq, &msg); + + irq_set_chip_and_handler(irq, &pcie_msi_chip, handle_simple_irq); + set_irq_flags(irq, IRQF_VALID); + return 0; +} diff --git a/arch/arm/mach-msm/peripheral-loader.c b/arch/arm/mach-msm/peripheral-loader.c new file mode 100644 index 0000000000000000000000000000000000000000..bfbf4bc19fa0a525fa89c00c32e52d9f170e3b83 --- /dev/null +++ b/arch/arm/mach-msm/peripheral-loader.c @@ -0,0 +1,687 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "peripheral-loader.h" + +enum pil_state { + PIL_OFFLINE, + PIL_ONLINE, +}; + +static const char *pil_states[] = { + [PIL_OFFLINE] = "OFFLINE", + [PIL_ONLINE] = "ONLINE", +}; + +struct pil_device { + struct pil_desc *desc; + int count; + enum pil_state state; + struct mutex lock; + struct device dev; + struct module *owner; +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + struct delayed_work proxy; + struct wake_lock wlock; + char wake_name[32]; +}; + +#define to_pil_device(d) container_of(d, struct pil_device, dev) + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", to_pil_device(dev)->desc->name); +} + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + enum pil_state state = to_pil_device(dev)->state; + return snprintf(buf, PAGE_SIZE, "%s\n", pil_states[state]); +} + +static struct device_attribute pil_attrs[] = { + __ATTR_RO(name), + __ATTR_RO(state), + { }, +}; + +struct bus_type pil_bus_type = { + .name = "pil", + .dev_attrs = pil_attrs, +}; + +static int __find_peripheral(struct device *dev, void *data) +{ + struct pil_device *pdev = to_pil_device(dev); + return !strncmp(pdev->desc->name, data, INT_MAX); +} + +static struct pil_device *find_peripheral(const char *str) +{ + struct device *dev; + + if (!str) + return NULL; + + dev = bus_find_device(&pil_bus_type, NULL, (void *)str, + __find_peripheral); + return dev ? to_pil_device(dev) : NULL; +} + +static void pil_proxy_work(struct work_struct *work) +{ + struct pil_device *pil; + + pil = container_of(work, struct pil_device, proxy.work); + pil->desc->ops->proxy_unvote(pil->desc); + wake_unlock(&pil->wlock); +} + +static int pil_proxy_vote(struct pil_device *pil) +{ + if (pil->desc->ops->proxy_vote) { + wake_lock(&pil->wlock); + return pil->desc->ops->proxy_vote(pil->desc); + } + return 0; +} + +static void pil_proxy_unvote(struct pil_device *pil, unsigned long timeout) +{ + if (pil->desc->ops->proxy_unvote) + schedule_delayed_work(&pil->proxy, msecs_to_jiffies(timeout)); +} + +#define IOMAP_SIZE SZ_4M + +static int load_segment(const struct elf32_phdr *phdr, unsigned num, + struct pil_device *pil) +{ + int ret = 0, count, paddr; + char fw_name[30]; + const struct firmware *fw = NULL; + const u8 *data; + + if (memblock_overlaps_memory(phdr->p_paddr, phdr->p_memsz)) { + dev_err(&pil->dev, "%s: kernel memory would be overwritten " + "[%#08lx, %#08lx)\n", pil->desc->name, + (unsigned long)phdr->p_paddr, + (unsigned long)(phdr->p_paddr + phdr->p_memsz)); + return -EPERM; + } + + if (phdr->p_filesz) { + snprintf(fw_name, ARRAY_SIZE(fw_name), "%s.b%02d", + pil->desc->name, num); + ret = request_firmware(&fw, fw_name, &pil->dev); + if (ret) { + dev_err(&pil->dev, "%s: Failed to locate blob %s\n", + pil->desc->name, fw_name); + return ret; + } + + if (fw->size != phdr->p_filesz) { + dev_err(&pil->dev, "%s: Blob size %u doesn't match " + "%u\n", pil->desc->name, 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->dev, "%s: Failed to map memory\n", + pil->desc->name); + 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->dev, "%s: Failed to map memory\n", + pil->desc->name); + ret = -ENOMEM; + goto release_fw; + } + memset(buf, 0, size); + iounmap(buf); + + count -= size; + paddr += size; + } + + if (pil->desc->ops->verify_blob) { + ret = pil->desc->ops->verify_blob(pil->desc, phdr->p_paddr, + phdr->p_memsz); + if (ret) + dev_err(&pil->dev, "%s: Blob%u failed verification\n", + pil->desc->name, 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); +} + +/* Sychronize request_firmware() with suspend */ +static DECLARE_RWSEM(pil_pm_rwsem); + +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; + unsigned long proxy_timeout = pil->desc->proxy_timeout; + + down_read(&pil_pm_rwsem); + snprintf(fw_name, sizeof(fw_name), "%s.mdt", pil->desc->name); + ret = request_firmware(&fw, fw_name, &pil->dev); + if (ret) { + dev_err(&pil->dev, "%s: Failed to locate %s\n", + pil->desc->name, fw_name); + goto out; + } + + if (fw->size < sizeof(*ehdr)) { + dev_err(&pil->dev, "%s: Not big enough to be an elf header\n", + pil->desc->name); + ret = -EIO; + goto release_fw; + } + + ehdr = (struct elf32_hdr *)fw->data; + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + dev_err(&pil->dev, "%s: Not an elf header\n", pil->desc->name); + ret = -EIO; + goto release_fw; + } + + if (ehdr->e_phnum == 0) { + dev_err(&pil->dev, "%s: No loadable segments\n", + pil->desc->name); + ret = -EIO; + goto release_fw; + } + if (sizeof(struct elf32_phdr) * ehdr->e_phnum + + sizeof(struct elf32_hdr) > fw->size) { + dev_err(&pil->dev, "%s: Program headers not within mdt\n", + pil->desc->name); + ret = -EIO; + goto release_fw; + } + + ret = pil->desc->ops->init_image(pil->desc, fw->data, fw->size); + if (ret) { + dev_err(&pil->dev, "%s: Invalid firmware metadata\n", + pil->desc->name); + goto release_fw; + } + + phdr = (const struct elf32_phdr *)(fw->data + sizeof(struct elf32_hdr)); + 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->dev, "%s: Failed to load segment %d\n", + pil->desc->name, i); + goto release_fw; + } + } + + ret = pil_proxy_vote(pil); + if (ret) { + dev_err(&pil->dev, "%s: Failed to proxy vote\n", + pil->desc->name); + goto release_fw; + } + + ret = pil->desc->ops->auth_and_reset(pil->desc); + if (ret) { + dev_err(&pil->dev, "%s: Failed to bring out of reset\n", + pil->desc->name); + proxy_timeout = 0; /* Remove proxy vote immediately on error */ + goto err_boot; + } + dev_info(&pil->dev, "%s: Brought out of reset\n", pil->desc->name); +err_boot: + pil_proxy_unvote(pil, proxy_timeout); +release_fw: + release_firmware(fw); +out: + up_read(&pil_pm_rwsem); + return ret; +} + +static void pil_set_state(struct pil_device *pil, enum pil_state state) +{ + if (pil->state != state) { + pil->state = state; + sysfs_notify(&pil->dev.kobj, NULL, "state"); + } +} + +/** + * 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; + + if (!name) + return NULL; + + pil = retval = find_peripheral(name); + if (!pil) + return ERR_PTR(-ENODEV); + if (!try_module_get(pil->owner)) { + put_device(&pil->dev); + return ERR_PTR(-ENODEV); + } + + pil_d = pil_get(pil->desc->depends_on); + if (IS_ERR(pil_d)) { + retval = pil_d; + goto err_depends; + } + + mutex_lock(&pil->lock); + if (!pil->count) { + ret = load_image(pil); + if (ret) { + retval = ERR_PTR(ret); + goto err_load; + } + } + pil->count++; + pil_set_state(pil, PIL_ONLINE); + mutex_unlock(&pil->lock); +out: + return retval; +err_load: + mutex_unlock(&pil->lock); + pil_put(pil_d); +err_depends: + put_device(&pil->dev); + module_put(pil->owner); + goto out; +} +EXPORT_SYMBOL(pil_get); + +static void pil_shutdown(struct pil_device *pil) +{ + pil->desc->ops->shutdown(pil->desc); + flush_delayed_work(&pil->proxy); + pil_set_state(pil, PIL_OFFLINE); +} + +/** + * 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, *pil = peripheral_handle; + + if (IS_ERR_OR_NULL(pil)) + return; + + mutex_lock(&pil->lock); + if (WARN(!pil->count, "%s: %s: Reference count mismatch\n", + pil->desc->name, __func__)) + goto err_out; + if (!--pil->count) + pil_shutdown(pil); + mutex_unlock(&pil->lock); + + pil_d = find_peripheral(pil->desc->depends_on); + module_put(pil->owner); + if (pil_d) { + pil_put(pil_d); + put_device(&pil_d->dev); + } + put_device(&pil->dev); + return; +err_out: + mutex_unlock(&pil->lock); + return; +} +EXPORT_SYMBOL(pil_put); + +void pil_force_shutdown(const char *name) +{ + struct pil_device *pil; + + pil = find_peripheral(name); + if (!pil) { + pr_err("%s: Couldn't find %s\n", __func__, name); + return; + } + + mutex_lock(&pil->lock); + if (!WARN(!pil->count, "%s: %s: Reference count mismatch\n", + pil->desc->name, __func__)) + pil_shutdown(pil); + mutex_unlock(&pil->lock); + + put_device(&pil->dev); +} +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) { + pr_err("%s: Couldn't find %s\n", __func__, name); + return -EINVAL; + } + + mutex_lock(&pil->lock); + if (!WARN(!pil->count, "%s: %s: Reference count mismatch\n", + pil->desc->name, __func__)) + ret = load_image(pil); + if (!ret) + pil_set_state(pil, PIL_ONLINE); + mutex_unlock(&pil->lock); + put_device(&pil->dev); + + return ret; +} +EXPORT_SYMBOL(pil_force_boot); + +#ifdef CONFIG_DEBUG_FS +static 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->desc->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 __init 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; +} + +static void __exit msm_pil_debugfs_exit(void) +{ + debugfs_remove_recursive(pil_base_dir); +} + +static int msm_pil_debugfs_add(struct pil_device *pil) +{ + if (!pil_base_dir) + return -ENOMEM; + + pil->dentry = debugfs_create_file(pil->desc->name, S_IRUGO | S_IWUSR, + pil_base_dir, pil, &msm_pil_debugfs_fops); + return !pil->dentry ? -ENOMEM : 0; +} + +static void msm_pil_debugfs_remove(struct pil_device *pil) +{ + debugfs_remove(pil->dentry); +} +#else +static int __init msm_pil_debugfs_init(void) { return 0; }; +static void __exit msm_pil_debugfs_exit(void) { return 0; }; +static int msm_pil_debugfs_add(struct pil_device *pil) { return 0; } +static void msm_pil_debugfs_remove(struct pil_device *pil) { } +#endif + +static int __msm_pil_shutdown(struct device *dev, void *data) +{ + pil_shutdown(to_pil_device(dev)); + return 0; +} + +static int msm_pil_shutdown_at_boot(void) +{ + return bus_for_each_dev(&pil_bus_type, NULL, NULL, __msm_pil_shutdown); +} +late_initcall(msm_pil_shutdown_at_boot); + +static void pil_device_release(struct device *dev) +{ + struct pil_device *pil = to_pil_device(dev); + wake_lock_destroy(&pil->wlock); + mutex_destroy(&pil->lock); + kfree(pil); +} + +struct pil_device *msm_pil_register(struct pil_desc *desc) +{ + int err; + static atomic_t pil_count = ATOMIC_INIT(-1); + struct pil_device *pil; + + /* Ignore users who don't make any sense */ + if (WARN(desc->ops->proxy_unvote && !desc->ops->proxy_vote, + "invalid proxy voting. ignoring\n")) + ((struct pil_reset_ops *)desc->ops)->proxy_unvote = NULL; + + WARN(desc->ops->proxy_unvote && !desc->proxy_timeout, + "A proxy timeout of 0 ms was specified for %s. Specify one in " + "desc->proxy_timeout.\n", desc->name); + + pil = kzalloc(sizeof(*pil), GFP_KERNEL); + if (!pil) + return ERR_PTR(-ENOMEM); + + mutex_init(&pil->lock); + pil->desc = desc; + pil->owner = desc->owner; + pil->dev.parent = desc->dev; + pil->dev.bus = &pil_bus_type; + pil->dev.release = pil_device_release; + + snprintf(pil->wake_name, sizeof(pil->wake_name), "pil-%s", desc->name); + wake_lock_init(&pil->wlock, WAKE_LOCK_SUSPEND, pil->wake_name); + INIT_DELAYED_WORK(&pil->proxy, pil_proxy_work); + + dev_set_name(&pil->dev, "pil%d", atomic_inc_return(&pil_count)); + err = device_register(&pil->dev); + if (err) { + put_device(&pil->dev); + wake_lock_destroy(&pil->wlock); + mutex_destroy(&pil->lock); + kfree(pil); + return ERR_PTR(err); + } + + err = msm_pil_debugfs_add(pil); + if (err) { + device_unregister(&pil->dev); + return ERR_PTR(err); + } + + return pil; +} +EXPORT_SYMBOL(msm_pil_register); + +void msm_pil_unregister(struct pil_device *pil) +{ + if (IS_ERR_OR_NULL(pil)) + return; + + if (get_device(&pil->dev)) { + mutex_lock(&pil->lock); + WARN_ON(pil->count); + flush_delayed_work_sync(&pil->proxy); + msm_pil_debugfs_remove(pil); + device_unregister(&pil->dev); + mutex_unlock(&pil->lock); + put_device(&pil->dev); + } +} +EXPORT_SYMBOL(msm_pil_unregister); + +static int pil_pm_notify(struct notifier_block *b, unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&pil_pm_rwsem); + break; + case PM_POST_SUSPEND: + up_write(&pil_pm_rwsem); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block pil_pm_notifier = { + .notifier_call = pil_pm_notify, +}; + +static int __init msm_pil_init(void) +{ + int ret = msm_pil_debugfs_init(); + if (ret) + return ret; + register_pm_notifier(&pil_pm_notifier); + return bus_register(&pil_bus_type); +} +subsys_initcall(msm_pil_init); + +static void __exit msm_pil_exit(void) +{ + bus_unregister(&pil_bus_type); + unregister_pm_notifier(&pil_pm_notifier); + msm_pil_debugfs_exit(); +} +module_exit(msm_pil_exit); + +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 0000000000000000000000000000000000000000..e3b250b089f8b1377d6b4c243bda1c135cc0056a --- /dev/null +++ b/arch/arm/mach-msm/peripheral-loader.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PERIPHERAL_LOADER_H +#define __MSM_PERIPHERAL_LOADER_H + +struct device; +struct module; + +/** + * struct pil_desc - PIL descriptor + * @name: string used for pil_get() + * @depends_on: booted before this peripheral + * @dev: parent device + * @ops: callback functions + * @owner: module the descriptor belongs to + * @proxy_timeout: delay in ms until proxy vote is removed + */ +struct pil_desc { + const char *name; + const char *depends_on; + struct device *dev; + const struct pil_reset_ops *ops; + struct module *owner; + unsigned long proxy_timeout; +}; + +/** + * struct pil_reset_ops - PIL operations + * @init_image: prepare an image for authentication + * @verify_blob: authenticate a program segment, called once for each loadable + * program segment (optional) + * @proxy_vote: make proxy votes before auth_and_reset (optional) + * @auth_and_reset: boot the processor + * @proxy_unvote: remove any proxy votes (optional) + * @shutdown: shutdown the processor + */ +struct pil_reset_ops { + int (*init_image)(struct pil_desc *pil, const u8 *metadata, + size_t size); + int (*verify_blob)(struct pil_desc *pil, u32 phy_addr, size_t size); + int (*proxy_vote)(struct pil_desc *pil); + int (*auth_and_reset)(struct pil_desc *pil); + void (*proxy_unvote)(struct pil_desc *pil); + int (*shutdown)(struct pil_desc *pil); +}; + +struct pil_device; + +extern struct pil_device *msm_pil_register(struct pil_desc *desc); +extern void msm_pil_unregister(struct pil_device *pil); + +#endif diff --git a/arch/arm/mach-msm/pil-dsps.c b/arch/arm/mach-msm/pil-dsps.c new file mode 100644 index 0000000000000000000000000000000000000000..81f5330fe81d90f335914264bd28243076a5cc27 --- /dev/null +++ b/arch/arm/mach-msm/pil-dsps.c @@ -0,0 +1,146 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "peripheral-loader.h" +#include "scm-pas.h" + +#define PPSS_RESET (MSM_CLK_CTL_BASE + 0x2594) +#define PPSS_RESET_PROC_RESET 0x2 +#define PPSS_RESET_RESET 0x1 +#define PPSS_PROC_CLK_CTL (MSM_CLK_CTL_BASE + 0x2588) +#define CLK_BRANCH_ENA 0x10 +#define PPSS_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2580) +#define CLK_HALT_DFAB_STATE (MSM_CLK_CTL_BASE + 0x2FC8) + +static int init_image_dsps(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + /* Bring memory and bus interface out of reset */ + writel_relaxed(PPSS_RESET_PROC_RESET, PPSS_RESET); + writel_relaxed(CLK_BRANCH_ENA, PPSS_HCLK_CTL); + mb(); + return 0; +} + +static int reset_dsps(struct pil_desc *pil) +{ + writel_relaxed(CLK_BRANCH_ENA, PPSS_PROC_CLK_CTL); + while (readl_relaxed(CLK_HALT_DFAB_STATE) & BIT(18)) + cpu_relax(); + /* Bring DSPS out of reset */ + writel_relaxed(0x0, PPSS_RESET); + return 0; +} + +static int shutdown_dsps(struct pil_desc *pil) +{ + writel_relaxed(PPSS_RESET_PROC_RESET | PPSS_RESET_RESET, PPSS_RESET); + usleep_range(1000, 2000); + writel_relaxed(PPSS_RESET_PROC_RESET, PPSS_RESET); + writel_relaxed(0x0, PPSS_PROC_CLK_CTL); + return 0; +} + +struct pil_reset_ops pil_dsps_ops = { + .init_image = init_image_dsps, + .auth_and_reset = reset_dsps, + .shutdown = shutdown_dsps, +}; + +static int init_image_dsps_trusted(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + return pas_init_image(PAS_DSPS, metadata, size); +} + +static int reset_dsps_trusted(struct pil_desc *pil) +{ + return pas_auth_and_reset(PAS_DSPS); +} + +static int shutdown_dsps_trusted(struct pil_desc *pil) +{ + return pas_shutdown(PAS_DSPS); +} + +struct pil_reset_ops pil_dsps_ops_trusted = { + .init_image = init_image_dsps_trusted, + .auth_and_reset = reset_dsps_trusted, + .shutdown = shutdown_dsps_trusted, +}; + +static int __devinit pil_dsps_driver_probe(struct platform_device *pdev) +{ + struct pil_desc *desc; + struct pil_device *pil; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->name = pdev->dev.platform_data; + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + if (pas_supported(PAS_DSPS) > 0) { + desc->ops = &pil_dsps_ops_trusted; + dev_info(&pdev->dev, "using secure boot\n"); + } else { + desc->ops = &pil_dsps_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + } + pil = msm_pil_register(desc); + if (IS_ERR(pil)) + return PTR_ERR(pil); + platform_set_drvdata(pdev, pil); + return 0; +} + +static int __devexit pil_dsps_driver_exit(struct platform_device *pdev) +{ + struct pil_device *pil = platform_get_drvdata(pdev); + msm_pil_unregister(pil); + return 0; +} + +static struct platform_driver pil_dsps_driver = { + .probe = pil_dsps_driver_probe, + .remove = __devexit_p(pil_dsps_driver_exit), + .driver = { + .name = "pil_dsps", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_dsps_init(void) +{ + return platform_driver_register(&pil_dsps_driver); +} +module_init(pil_dsps_init); + +static void __exit pil_dsps_exit(void) +{ + platform_driver_unregister(&pil_dsps_driver); +} +module_exit(pil_dsps_exit); + +MODULE_DESCRIPTION("Support for booting sensors (DSPS) images"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-gss.c b/arch/arm/mach-msm/pil-gss.c new file mode 100644 index 0000000000000000000000000000000000000000..dc7baa1663b5391d861c4b17b1ac6a70043d9f4b --- /dev/null +++ b/arch/arm/mach-msm/pil-gss.c @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "peripheral-loader.h" +#include "scm-pas.h" + +#define GSS_CSR_AHB_CLK_SEL 0x0 +#define GSS_CSR_RESET 0x4 +#define GSS_CSR_CLK_BLK_CONFIG 0x8 +#define GSS_CSR_CLK_ENABLE 0xC +#define GSS_CSR_BOOT_REMAP 0x14 +#define GSS_CSR_POWER_UP_DOWN 0x18 +#define GSS_CSR_CFG_HID 0x2C + +#define GSS_SLP_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C60) +#define GSS_RESET (MSM_CLK_CTL_BASE + 0x2C64) +#define GSS_CLAMP_ENA (MSM_CLK_CTL_BASE + 0x2C68) +#define GSS_CXO_SRC_CTL (MSM_CLK_CTL_BASE + 0x2C74) + +#define PLL5_STATUS (MSM_CLK_CTL_BASE + 0x30F8) +#define PLL_ENA_GSS (MSM_CLK_CTL_BASE + 0x3480) + +#define PLL5_VOTE BIT(5) +#define PLL_STATUS BIT(16) +#define REMAP_ENABLE BIT(16) +#define A5_POWER_STATUS BIT(4) +#define A5_POWER_ENA BIT(0) +#define NAV_POWER_ENA BIT(1) +#define XO_CLK_BRANCH_ENA BIT(0) +#define SLP_CLK_BRANCH_ENA BIT(4) +#define A5_RESET BIT(0) + +struct gss_data { + void __iomem *base; + void __iomem *qgic2_base; + unsigned long start_addr; + struct clk *xo; + struct pil_device *pil; +}; + +static int pil_gss_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct gss_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} + +static int make_gss_proxy_votes(struct pil_desc *pil) +{ + int ret; + struct gss_data *drv = dev_get_drvdata(pil->dev); + + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to enable XO\n"); + return ret; + } + return 0; +} + +static void remove_gss_proxy_votes(struct pil_desc *pil) +{ + struct gss_data *drv = dev_get_drvdata(pil->dev); + clk_disable_unprepare(drv->xo); +} + +static void gss_init(struct gss_data *drv) +{ + void __iomem *base = drv->base; + + /* Supply clocks to GSS. */ + writel_relaxed(XO_CLK_BRANCH_ENA, GSS_CXO_SRC_CTL); + writel_relaxed(SLP_CLK_BRANCH_ENA, GSS_SLP_CLK_CTL); + + /* Deassert GSS reset and clamps. */ + writel_relaxed(0x0, GSS_RESET); + writel_relaxed(0x0, GSS_CLAMP_ENA); + mb(); + + /* + * Configure clock source and dividers for 288MHz core, 144MHz AXI and + * 72MHz AHB, all derived from the 288MHz PLL. + */ + writel_relaxed(0x341, base + GSS_CSR_CLK_BLK_CONFIG); + writel_relaxed(0x1, base + GSS_CSR_AHB_CLK_SEL); + + /* Assert all GSS resets. */ + writel_relaxed(0x7F, base + GSS_CSR_RESET); + + /* Enable all bus clocks and wait for resets to propagate. */ + writel_relaxed(0x1F, base + GSS_CSR_CLK_ENABLE); + mb(); + udelay(1); + + /* Release subsystem from reset, but leave A5 in reset. */ + writel_relaxed(A5_RESET, base + GSS_CSR_RESET); +} + +static void cfg_qgic2_bus_access(void *data) +{ + struct gss_data *drv = data; + int i; + + /* + * Apply a 8064 v1.0 workaround to configure QGIC bus access. + * This must be done from Krait 0 to configure the Master ID + * correctly. + */ + writel_relaxed(0x2, drv->base + GSS_CSR_CFG_HID); + for (i = 0; i <= 3; i++) + readl_relaxed(drv->qgic2_base); +} + +static int pil_gss_shutdown(struct pil_desc *pil) +{ + struct gss_data *drv = dev_get_drvdata(pil->dev); + void __iomem *base = drv->base; + u32 regval; + int ret; + + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to enable XO\n"); + return ret; + } + + /* Make sure bus port is halted. */ + msm_bus_axi_porthalt(MSM_BUS_MASTER_GSS_NAV); + + /* + * Vote PLL on in GSS's voting register and wait for it to enable. + * The PLL must be enable to switch the GFMUX to a low-power source. + */ + writel_relaxed(PLL5_VOTE, PLL_ENA_GSS); + while ((readl_relaxed(PLL5_STATUS) & PLL_STATUS) == 0) + cpu_relax(); + + /* Perform one-time GSS initialization. */ + gss_init(drv); + + /* Assert A5 reset. */ + regval = readl_relaxed(base + GSS_CSR_RESET); + regval |= A5_RESET; + writel_relaxed(regval, base + GSS_CSR_RESET); + + /* Power down A5 and NAV. */ + regval = readl_relaxed(base + GSS_CSR_POWER_UP_DOWN); + regval &= ~(A5_POWER_ENA|NAV_POWER_ENA); + writel_relaxed(regval, base + GSS_CSR_POWER_UP_DOWN); + + /* Select XO clock source and increase dividers to save power. */ + regval = readl_relaxed(base + GSS_CSR_CLK_BLK_CONFIG); + regval |= 0x3FF; + writel_relaxed(regval, base + GSS_CSR_CLK_BLK_CONFIG); + + /* Disable bus clocks. */ + writel_relaxed(0x1F, base + GSS_CSR_CLK_ENABLE); + + /* Clear GSS PLL votes. */ + writel_relaxed(0, PLL_ENA_GSS); + mb(); + + clk_disable_unprepare(drv->xo); + + return 0; +} + +static int pil_gss_reset(struct pil_desc *pil) +{ + struct gss_data *drv = dev_get_drvdata(pil->dev); + void __iomem *base = drv->base; + unsigned long start_addr = drv->start_addr; + int ret; + + /* Unhalt bus port. */ + ret = msm_bus_axi_portunhalt(MSM_BUS_MASTER_GSS_NAV); + if (ret) { + dev_err(pil->dev, "Failed to unhalt bus port\n"); + return ret; + } + + /* Vote PLL on in GSS's voting register and wait for it to enable. */ + writel_relaxed(PLL5_VOTE, PLL_ENA_GSS); + while ((readl_relaxed(PLL5_STATUS) & PLL_STATUS) == 0) + cpu_relax(); + + /* Perform GSS initialization. */ + gss_init(drv); + + /* Configure boot address and enable remap. */ + writel_relaxed(REMAP_ENABLE | (start_addr >> 16), + base + GSS_CSR_BOOT_REMAP); + + /* Power up A5 core. */ + writel_relaxed(A5_POWER_ENA, base + GSS_CSR_POWER_UP_DOWN); + while (!(readl_relaxed(base + GSS_CSR_POWER_UP_DOWN) & A5_POWER_STATUS)) + cpu_relax(); + + if (cpu_is_apq8064() && + ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 1) && + (SOCINFO_VERSION_MINOR(socinfo_get_version()) == 0))) { + ret = smp_call_function_single(0, cfg_qgic2_bus_access, drv, 1); + if (ret) { + pr_err("Failed to configure QGIC2 bus access\n"); + pil_gss_shutdown(pil); + return ret; + } + } + + /* Release A5 from reset. */ + writel_relaxed(0x0, base + GSS_CSR_RESET); + + return 0; +} + +static struct pil_reset_ops pil_gss_ops = { + .init_image = pil_gss_init_image, + .auth_and_reset = pil_gss_reset, + .shutdown = pil_gss_shutdown, + .proxy_vote = make_gss_proxy_votes, + .proxy_unvote = remove_gss_proxy_votes, +}; + +static int pil_gss_init_image_trusted(struct pil_desc *pil, + const u8 *metadata, size_t size) +{ + return pas_init_image(PAS_GSS, metadata, size); +} + +static int pil_gss_shutdown_trusted(struct pil_desc *pil) +{ + struct gss_data *drv = dev_get_drvdata(pil->dev); + int ret; + + /* + * CXO is used in the secure shutdown code to configure the processor + * for low power mode. + */ + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to enable XO\n"); + return ret; + } + + msm_bus_axi_porthalt(MSM_BUS_MASTER_GSS_NAV); + ret = pas_shutdown(PAS_GSS); + clk_disable_unprepare(drv->xo); + + return ret; +} + +static int pil_gss_reset_trusted(struct pil_desc *pil) +{ + int err; + + err = msm_bus_axi_portunhalt(MSM_BUS_MASTER_GSS_NAV); + if (err) { + dev_err(pil->dev, "Failed to unhalt bus port\n"); + goto out; + } + + err = pas_auth_and_reset(PAS_GSS); + if (err) + goto halt_port; + + return 0; + +halt_port: + msm_bus_axi_porthalt(MSM_BUS_MASTER_GSS_NAV); +out: + return err; +} + +static struct pil_reset_ops pil_gss_ops_trusted = { + .init_image = pil_gss_init_image_trusted, + .auth_and_reset = pil_gss_reset_trusted, + .shutdown = pil_gss_shutdown_trusted, + .proxy_vote = make_gss_proxy_votes, + .proxy_unvote = remove_gss_proxy_votes, +}; + +static int __devinit pil_gss_probe(struct platform_device *pdev) +{ + struct gss_data *drv; + struct resource *res; + struct pil_desc *desc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!drv->base) + return -ENOMEM; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -EINVAL; + + drv->qgic2_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!drv->qgic2_base) + return -ENOMEM; + + drv->xo = devm_clk_get(&pdev->dev, "xo"); + if (IS_ERR(drv->xo)) + return PTR_ERR(drv->xo); + + desc->name = "gss"; + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + desc->proxy_timeout = 10000; + + if (pas_supported(PAS_GSS) > 0) { + desc->ops = &pil_gss_ops_trusted; + dev_info(&pdev->dev, "using secure boot\n"); + } else { + desc->ops = &pil_gss_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + } + + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) { + return PTR_ERR(drv->pil); + } + return 0; +} + +static int __devexit pil_gss_remove(struct platform_device *pdev) +{ + struct gss_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct platform_driver pil_gss_driver = { + .probe = pil_gss_probe, + .remove = __devexit_p(pil_gss_remove), + .driver = { + .name = "pil_gss", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_gss_init(void) +{ + return platform_driver_register(&pil_gss_driver); +} +module_init(pil_gss_init); + +static void __exit pil_gss_exit(void) +{ + platform_driver_unregister(&pil_gss_driver); +} +module_exit(pil_gss_exit); + +MODULE_DESCRIPTION("Support for booting the GSS processor"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-modem.c b/arch/arm/mach-msm/pil-modem.c new file mode 100644 index 0000000000000000000000000000000000000000..83444965285da541e03870d36618d6625b77c74f --- /dev/null +++ b/arch/arm/mach-msm/pil-modem.c @@ -0,0 +1,311 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "scm-pas.h" + +#define MARM_BOOT_CONTROL 0x0010 +#define MARM_RESET (MSM_CLK_CTL_BASE + 0x2BD4) +#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) + +struct modem_data { + void __iomem *base; + unsigned long start_addr; + struct pil_device *pil; + struct clk *xo; +}; + +static int make_modem_proxy_votes(struct pil_desc *pil) +{ + int ret; + struct modem_data *drv = dev_get_drvdata(pil->dev); + + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to enable XO\n"); + return ret; + } + return 0; +} + +static void remove_modem_proxy_votes(struct pil_desc *pil) +{ + struct modem_data *drv = dev_get_drvdata(pil->dev); + clk_disable_unprepare(drv->xo); +} + +static int modem_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct modem_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} + +static int modem_reset(struct pil_desc *pil) +{ + u32 reg; + const struct modem_data *drv = dev_get_drvdata(pil->dev); + + /* Put modem AHB0,1,2 clocks into reset */ + writel_relaxed(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET); + writel_relaxed(BIT(7), MAHB1_CLK_CTL); + writel_relaxed(BIT(7), MAHB2_CLK_CTL); + + /* Vote for pll8 on behalf of the modem */ + reg = readl_relaxed(PLL_ENA_MARM); + reg |= BIT(8); + writel_relaxed(reg, PLL_ENA_MARM); + + /* Wait for PLL8 to enable */ + while (!(readl_relaxed(PLL8_STATUS) & BIT(16))) + cpu_relax(); + + /* Set MAHB1 divider to Div-5 to run MAHB1,2 and sfab at 79.8 Mhz*/ + writel_relaxed(0x4, MAHB1_NS); + + /* Vote for modem AHB1 and 2 clocks to be on on behalf of the modem */ + reg = readl_relaxed(MARM_CLK_BRANCH_ENA_VOTE); + reg |= BIT(0) | BIT(1); + writel_relaxed(reg, MARM_CLK_BRANCH_ENA_VOTE); + + /* Source marm_clk off of PLL8 */ + reg = readl_relaxed(MARM_CLK_SRC_CTL); + if ((reg & 0x1) == 0) { + writel_relaxed(0x3, MARM_CLK_SRC1_NS); + reg |= 0x1; + } else { + writel_relaxed(0x3, MARM_CLK_SRC0_NS); + reg &= ~0x1; + } + writel_relaxed(reg | 0x2, MARM_CLK_SRC_CTL); + + /* + * Force core on and periph on signals to remain active during halt + * for marm_clk and mahb2_clk + */ + writel_relaxed(0x6F, MARM_CLK_FS); + writel_relaxed(0x6F, MAHB2_CLK_FS); + + /* + * Enable all of the marm_clk branches, cxo sourced marm branches, + * and sleep clock branches + */ + writel_relaxed(0x10, MARM_CLK_CTL); + writel_relaxed(0x10, MAHB0_CLK_CTL); + writel_relaxed(0x10, SFAB_MSS_S_HCLK_CTL); + writel_relaxed(0x10, MSS_MODEM_CXO_CLK_CTL); + writel_relaxed(0x10, MSS_SLP_CLK_CTL); + writel_relaxed(0x10, MSS_MARM_SYS_REF_CLK_CTL); + + /* Wait for above clocks to be turned on */ + while (readl_relaxed(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 */ + writel_relaxed(0x0, MAHB2_CLK_CTL); + writel_relaxed(0x0, MAHB1_CLK_CTL); + writel_relaxed(0x0, MAHB0_SFAB_PORT_RESET); + mb(); + + /* Setup exception vector table base address */ + writel_relaxed(drv->start_addr | 0x1, drv->base + MARM_BOOT_CONTROL); + + /* Wait for vector table to be setup */ + mb(); + + /* Bring modem out of reset */ + writel_relaxed(0x0, MARM_RESET); + + return 0; +} + +static int modem_shutdown(struct pil_desc *pil) +{ + u32 reg; + + /* Put modem into reset */ + writel_relaxed(0x1, MARM_RESET); + mb(); + + /* Put modem AHB0,1,2 clocks into reset */ + writel_relaxed(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET); + writel_relaxed(BIT(7), MAHB1_CLK_CTL); + writel_relaxed(BIT(7), MAHB2_CLK_CTL); + mb(); + + /* + * Disable all of the marm_clk branches, cxo sourced marm branches, + * and sleep clock branches + */ + writel_relaxed(0x0, MARM_CLK_CTL); + writel_relaxed(0x0, MAHB0_CLK_CTL); + writel_relaxed(0x0, SFAB_MSS_S_HCLK_CTL); + writel_relaxed(0x0, MSS_MODEM_CXO_CLK_CTL); + writel_relaxed(0x0, MSS_SLP_CLK_CTL); + writel_relaxed(0x0, MSS_MARM_SYS_REF_CLK_CTL); + + /* Disable marm_clk */ + reg = readl_relaxed(MARM_CLK_SRC_CTL); + reg &= ~0x2; + writel_relaxed(reg, MARM_CLK_SRC_CTL); + + /* Clear modem's votes for ahb clocks */ + writel_relaxed(0x0, MARM_CLK_BRANCH_ENA_VOTE); + + /* Clear modem's votes for PLLs */ + writel_relaxed(0x0, PLL_ENA_MARM); + + return 0; +} + +static struct pil_reset_ops pil_modem_ops = { + .init_image = modem_init_image, + .auth_and_reset = modem_reset, + .shutdown = modem_shutdown, + .proxy_vote = make_modem_proxy_votes, + .proxy_unvote = remove_modem_proxy_votes, +}; + +static int modem_init_image_trusted(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + return pas_init_image(PAS_MODEM, metadata, size); +} + +static int modem_reset_trusted(struct pil_desc *pil) +{ + return pas_auth_and_reset(PAS_MODEM); +} + +static int modem_shutdown_trusted(struct pil_desc *pil) +{ + return pas_shutdown(PAS_MODEM); +} + +static struct pil_reset_ops pil_modem_ops_trusted = { + .init_image = modem_init_image_trusted, + .auth_and_reset = modem_reset_trusted, + .shutdown = modem_shutdown_trusted, + .proxy_vote = make_modem_proxy_votes, + .proxy_unvote = remove_modem_proxy_votes, +}; + +static int __devinit pil_modem_driver_probe(struct platform_device *pdev) +{ + struct modem_data *drv; + struct resource *res; + struct pil_desc *desc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!drv->base) + return -ENOMEM; + + drv->xo = devm_clk_get(&pdev->dev, "xo"); + if (IS_ERR(drv->xo)) + return PTR_ERR(drv->xo); + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->name = "modem"; + desc->depends_on = "q6"; + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + desc->proxy_timeout = 10000; + + if (pas_supported(PAS_MODEM) > 0) { + desc->ops = &pil_modem_ops_trusted; + dev_info(&pdev->dev, "using secure boot\n"); + } else { + desc->ops = &pil_modem_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + } + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) { + return PTR_ERR(drv->pil); + } + return 0; +} + +static int __devexit pil_modem_driver_exit(struct platform_device *pdev) +{ + struct modem_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct platform_driver pil_modem_driver = { + .probe = pil_modem_driver_probe, + .remove = __devexit_p(pil_modem_driver_exit), + .driver = { + .name = "pil_modem", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_modem_init(void) +{ + return platform_driver_register(&pil_modem_driver); +} +module_init(pil_modem_init); + +static void __exit pil_modem_exit(void) +{ + platform_driver_unregister(&pil_modem_driver); +} +module_exit(pil_modem_exit); + +MODULE_DESCRIPTION("Support for booting modem processors"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-pronto.c b/arch/arm/mach-msm/pil-pronto.c new file mode 100644 index 0000000000000000000000000000000000000000..58d51762e03b4b9367f39042c7bc0616e266e0cf --- /dev/null +++ b/arch/arm/mach-msm/pil-pronto.c @@ -0,0 +1,349 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "peripheral-loader.h" +#include "scm-pas.h" + +#define PRONTO_PMU_COMMON_GDSCR 0x24 +#define PRONTO_PMU_COMMON_GDSCR_SW_COLLAPSE BIT(0) +#define CLK_DIS_WAIT 12 +#define EN_FEW_WAIT 16 +#define EN_REST_WAIT 20 + +#define PRONTO_PMU_COMMON_CPU_CBCR 0x30 +#define PRONTO_PMU_COMMON_CPU_CBCR_CLK_EN BIT(0) +#define PRONTO_PMU_COMMON_CPU_CLK_OFF BIT(31) + +#define PRONTO_PMU_COMMON_AHB_CBCR 0x34 +#define PRONTO_PMU_COMMON_AHB_CBCR_CLK_EN BIT(0) +#define PRONTO_PMU_COMMON_AHB_CLK_OFF BIT(31) + +#define PRONTO_PMU_COMMON_CSR 0x1040 +#define PRONTO_PMU_COMMON_CSR_A2XB_CFG_EN BIT(0) + +#define PRONTO_PMU_SOFT_RESET 0x104C +#define PRONTO_PMU_SOFT_RESET_CRCM_CCPU_SOFT_RESET BIT(10) + +#define PRONTO_PMU_CCPU_CTL 0x2000 +#define PRONTO_PMU_CCPU_CTL_REMAP_EN BIT(2) +#define PRONTO_PMU_CCPU_CTL_HIGH_IVT BIT(0) + +#define PRONTO_PMU_CCPU_BOOT_REMAP_ADDR 0x2004 + +#define CLK_CTL_WCNSS_RESTART_BIT BIT(0) + +#define AXI_HALTREQ 0x0 +#define AXI_HALTACK 0x4 +#define AXI_IDLE 0x8 + +#define HALT_ACK_TIMEOUT_US 500000 +#define CLK_UPDATE_TIMEOUT_US 500000 + +struct pronto_data { + void __iomem *base; + void __iomem *reset_base; + void __iomem *axi_halt_base; + unsigned long start_addr; + struct pil_device *pil; + struct clk *cxo; + struct regulator *vreg; +}; + +static int pil_pronto_make_proxy_vote(struct pil_desc *pil) +{ + struct pronto_data *drv = dev_get_drvdata(pil->dev); + int ret; + + ret = regulator_enable(drv->vreg); + if (ret) { + dev_err(pil->dev, "failed to enable pll supply\n"); + goto err; + } + ret = clk_prepare_enable(drv->cxo); + if (ret) { + dev_err(pil->dev, "failed to enable cxo\n"); + goto err_clk; + } + return 0; +err_clk: + regulator_disable(drv->vreg); +err: + return ret; +} + +static void pil_pronto_remove_proxy_vote(struct pil_desc *pil) +{ + struct pronto_data *drv = dev_get_drvdata(pil->dev); + regulator_disable(drv->vreg); + clk_disable_unprepare(drv->cxo); +} + +static int pil_pronto_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct pronto_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} + +static int pil_pronto_reset(struct pil_desc *pil) +{ + u32 reg; + int rc; + struct pronto_data *drv = dev_get_drvdata(pil->dev); + void __iomem *base = drv->base; + unsigned long start_addr = drv->start_addr; + + /* Deassert reset to Pronto */ + reg = readl_relaxed(drv->reset_base); + reg &= ~CLK_CTL_WCNSS_RESTART_BIT; + writel_relaxed(reg, drv->reset_base); + mb(); + + /* Configure boot address */ + writel_relaxed(start_addr >> 16, base + + PRONTO_PMU_CCPU_BOOT_REMAP_ADDR); + + /* Use the high vector table */ + reg = readl_relaxed(base + PRONTO_PMU_CCPU_CTL); + reg |= PRONTO_PMU_CCPU_CTL_REMAP_EN | PRONTO_PMU_CCPU_CTL_HIGH_IVT; + writel_relaxed(reg, base + PRONTO_PMU_CCPU_CTL); + + /* Turn on AHB clock of common_ss */ + reg = readl_relaxed(base + PRONTO_PMU_COMMON_AHB_CBCR); + reg |= PRONTO_PMU_COMMON_AHB_CBCR_CLK_EN; + writel_relaxed(reg, base + PRONTO_PMU_COMMON_AHB_CBCR); + + /* Turn on CPU clock of common_ss */ + reg = readl_relaxed(base + PRONTO_PMU_COMMON_CPU_CBCR); + reg |= PRONTO_PMU_COMMON_CPU_CBCR_CLK_EN; + writel_relaxed(reg, base + PRONTO_PMU_COMMON_CPU_CBCR); + + /* Enable A2XB bridge */ + reg = readl_relaxed(base + PRONTO_PMU_COMMON_CSR); + reg |= PRONTO_PMU_COMMON_CSR_A2XB_CFG_EN; + writel_relaxed(reg, base + PRONTO_PMU_COMMON_CSR); + + /* Enable common_ss power */ + reg = readl_relaxed(base + PRONTO_PMU_COMMON_GDSCR); + reg &= ~PRONTO_PMU_COMMON_GDSCR_SW_COLLAPSE; + writel_relaxed(reg, base + PRONTO_PMU_COMMON_GDSCR); + + /* Wait for AHB clock to be on */ + rc = readl_tight_poll_timeout(base + PRONTO_PMU_COMMON_AHB_CBCR, + reg, + !(reg & PRONTO_PMU_COMMON_AHB_CLK_OFF), + CLK_UPDATE_TIMEOUT_US); + if (rc) { + dev_err(pil->dev, "pronto common ahb clk enable timeout\n"); + return rc; + } + + /* Wait for CPU clock to be on */ + rc = readl_tight_poll_timeout(base + PRONTO_PMU_COMMON_CPU_CBCR, + reg, + !(reg & PRONTO_PMU_COMMON_CPU_CLK_OFF), + CLK_UPDATE_TIMEOUT_US); + if (rc) { + dev_err(pil->dev, "pronto common cpu clk enable timeout\n"); + return rc; + } + + /* Deassert ARM9 software reset */ + reg = readl_relaxed(base + PRONTO_PMU_SOFT_RESET); + reg &= ~PRONTO_PMU_SOFT_RESET_CRCM_CCPU_SOFT_RESET; + writel_relaxed(reg, base + PRONTO_PMU_SOFT_RESET); + + return 0; +} + +static int pil_pronto_shutdown(struct pil_desc *pil) +{ + struct pronto_data *drv = dev_get_drvdata(pil->dev); + int ret; + u32 reg, status; + + /* Halt A2XB */ + writel_relaxed(1, drv->axi_halt_base + AXI_HALTREQ); + ret = readl_poll_timeout(drv->axi_halt_base + AXI_HALTACK, + status, status, 50, HALT_ACK_TIMEOUT_US); + if (ret) + dev_err(pil->dev, "Port halt timeout\n"); + else if (!readl_relaxed(drv->axi_halt_base + AXI_IDLE)) + dev_err(pil->dev, "Port halt failed\n"); + + writel_relaxed(0, drv->axi_halt_base + AXI_HALTREQ); + + /* Assert reset to Pronto */ + reg = readl_relaxed(drv->reset_base); + reg |= CLK_CTL_WCNSS_RESTART_BIT; + writel_relaxed(reg, drv->reset_base); + + /* Wait for reset to complete */ + mb(); + usleep_range(1000, 2000); + + /* Deassert reset to Pronto */ + reg = readl_relaxed(drv->reset_base); + reg &= ~CLK_CTL_WCNSS_RESTART_BIT; + writel_relaxed(reg, drv->reset_base); + mb(); + + return 0; +} + +static struct pil_reset_ops pil_pronto_ops = { + .init_image = pil_pronto_init_image, + .auth_and_reset = pil_pronto_reset, + .shutdown = pil_pronto_shutdown, + .proxy_vote = pil_pronto_make_proxy_vote, + .proxy_unvote = pil_pronto_remove_proxy_vote, +}; + +static int __devinit pil_pronto_probe(struct platform_device *pdev) +{ + struct pronto_data *drv; + struct resource *res; + struct pil_desc *desc; + int ret; + uint32_t regval; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!drv->base) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -EINVAL; + + drv->reset_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!res) + return -EINVAL; + + drv->axi_halt_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name", + &desc->name); + if (ret) + return ret; + + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + desc->proxy_timeout = 10000; + + /* TODO: need to add secure boot when the support is available */ + desc->ops = &pil_pronto_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + + drv->vreg = devm_regulator_get(&pdev->dev, "vdd_pronto_pll"); + if (IS_ERR(drv->vreg)) { + dev_err(&pdev->dev, "failed to get pronto pll supply"); + return PTR_ERR(drv->vreg); + } + + ret = regulator_set_voltage(drv->vreg, 1800000, 1800000); + if (ret) { + dev_err(&pdev->dev, "failed to set pll supply voltage\n"); + return ret; + } + + ret = regulator_set_optimum_mode(drv->vreg, 18000); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set pll supply mode\n"); + return ret; + } + + drv->cxo = devm_clk_get(&pdev->dev, "xo"); + if (IS_ERR(drv->cxo)) + return PTR_ERR(drv->cxo); + + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) + return PTR_ERR(drv->pil); + + /* Initialize common_ss GDSCR to wait 4 cycles between states */ + regval = readl_relaxed(drv->base + PRONTO_PMU_COMMON_GDSCR) + & PRONTO_PMU_COMMON_GDSCR_SW_COLLAPSE; + regval |= (2 << EN_REST_WAIT) | (2 << EN_FEW_WAIT) + | (2 << CLK_DIS_WAIT); + writel_relaxed(regval, drv->base + PRONTO_PMU_COMMON_GDSCR); + + return 0; +} + +static int __devexit pil_pronto_remove(struct platform_device *pdev) +{ + struct pronto_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct of_device_id msm_pil_pronto_match[] = { + {.compatible = "qcom,pil-pronto"}, + {} +}; + +static struct platform_driver pil_pronto_driver = { + .probe = pil_pronto_probe, + .remove = __devexit_p(pil_pronto_remove), + .driver = { + .name = "pil_pronto", + .owner = THIS_MODULE, + .of_match_table = msm_pil_pronto_match, + }, +}; + +static int __init pil_pronto_init(void) +{ + return platform_driver_register(&pil_pronto_driver); +} +module_init(pil_pronto_init); + +static void __exit pil_pronto_exit(void) +{ + platform_driver_unregister(&pil_pronto_driver); +} +module_exit(pil_pronto_exit); + +MODULE_DESCRIPTION("Support for booting PRONTO (WCNSS) processors"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-q6v3.c b/arch/arm/mach-msm/pil-q6v3.c new file mode 100644 index 0000000000000000000000000000000000000000..28b9deed97ad799844f1b36d5f07230e01fb5ec2 --- /dev/null +++ b/arch/arm/mach-msm/pil-q6v3.c @@ -0,0 +1,277 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "scm-pas.h" + +#define QDSP6SS_RST_EVB 0x0000 +#define QDSP6SS_STRAP_TCM 0x001C +#define QDSP6SS_STRAP_AHB 0x0020 + +#define LCC_Q6_FUNC (MSM_LPASS_CLK_CTL_BASE + 0x001C) +#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 + +struct q6v3_data { + void __iomem *base; + unsigned long start_addr; + struct pil_device *pil; + struct clk *pll; +}; + +static int pil_q6v3_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct q6v3_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} + +static void pil_q6v3_remove_proxy_votes(struct pil_desc *pil) +{ + struct q6v3_data *drv = dev_get_drvdata(pil->dev); + clk_disable_unprepare(drv->pll); +} + +static int pil_q6v3_make_proxy_votes(struct pil_desc *pil) +{ + int ret; + struct q6v3_data *drv = dev_get_drvdata(pil->dev); + + ret = clk_prepare_enable(drv->pll); + if (ret) { + dev_err(pil->dev, "Failed to enable PLL\n"); + return ret; + } + return 0; +} + +static int pil_q6v3_reset(struct pil_desc *pil) +{ + u32 reg; + struct q6v3_data *drv = dev_get_drvdata(pil->dev); + + /* Put Q6 into reset */ + reg = readl_relaxed(LCC_Q6_FUNC); + reg |= Q6SS_SS_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES | STOP_CORE | + CORE_ARES; + reg &= ~CORE_GFM4_CLK_EN; + writel_relaxed(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; + writel_relaxed(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); + writel_relaxed(reg, LCC_Q6_FUNC); + + /* Wait for clocks to be enabled */ + mb(); + /* Program boot address */ + writel_relaxed((drv->start_addr >> 12) & 0xFFFFF, + drv->base + QDSP6SS_RST_EVB); + + writel_relaxed(Q6_STRAP_TCM_CONFIG | Q6_STRAP_TCM_BASE, + drv->base + QDSP6SS_STRAP_TCM); + writel_relaxed(Q6_STRAP_AHB_UPPER | Q6_STRAP_AHB_LOWER, + drv->base + QDSP6SS_STRAP_AHB); + + /* Wait for addresses to be programmed before starting Q6 */ + mb(); + + /* Start Q6 instruction execution */ + reg &= ~STOP_CORE; + writel_relaxed(reg, LCC_Q6_FUNC); + + return 0; +} + +static int pil_q6v3_shutdown(struct pil_desc *pil) +{ + u32 reg; + + /* Put Q6 into reset */ + reg = readl_relaxed(LCC_Q6_FUNC); + reg |= Q6SS_SS_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES | STOP_CORE | + CORE_ARES; + reg &= ~CORE_GFM4_CLK_EN; + writel_relaxed(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); + writel_relaxed(reg, LCC_Q6_FUNC); + + reg |= CLAMP_IO; + writel_relaxed(reg, LCC_Q6_FUNC); + + return 0; +} + +static struct pil_reset_ops pil_q6v3_ops = { + .init_image = pil_q6v3_init_image, + .auth_and_reset = pil_q6v3_reset, + .shutdown = pil_q6v3_shutdown, + .proxy_vote = pil_q6v3_make_proxy_votes, + .proxy_unvote = pil_q6v3_remove_proxy_votes, +}; + +static int pil_q6v3_init_image_trusted(struct pil_desc *pil, + const u8 *metadata, size_t size) +{ + return pas_init_image(PAS_Q6, metadata, size); +} + +static int pil_q6v3_reset_trusted(struct pil_desc *pil) +{ + return pas_auth_and_reset(PAS_Q6); +} + +static int pil_q6v3_shutdown_trusted(struct pil_desc *pil) +{ + return pas_shutdown(PAS_Q6); +} + +static struct pil_reset_ops pil_q6v3_ops_trusted = { + .init_image = pil_q6v3_init_image_trusted, + .auth_and_reset = pil_q6v3_reset_trusted, + .shutdown = pil_q6v3_shutdown_trusted, + .proxy_vote = pil_q6v3_make_proxy_votes, + .proxy_unvote = pil_q6v3_remove_proxy_votes, +}; + +static int __devinit pil_q6v3_driver_probe(struct platform_device *pdev) +{ + struct q6v3_data *drv; + struct resource *res; + struct pil_desc *desc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!drv->base) + return -ENOMEM; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->pll = devm_clk_get(&pdev->dev, "pll4"); + if (IS_ERR(drv->pll)) + return PTR_ERR(drv->pll); + + desc->name = "q6"; + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + desc->proxy_timeout = 10000; + + if (pas_supported(PAS_Q6) > 0) { + desc->ops = &pil_q6v3_ops_trusted; + dev_info(&pdev->dev, "using secure boot\n"); + } else { + desc->ops = &pil_q6v3_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + } + + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) { + return PTR_ERR(drv->pil); + } + return 0; +} + +static int __devexit pil_q6v3_driver_exit(struct platform_device *pdev) +{ + struct q6v3_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct platform_driver pil_q6v3_driver = { + .probe = pil_q6v3_driver_probe, + .remove = __devexit_p(pil_q6v3_driver_exit), + .driver = { + .name = "pil_qdsp6v3", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_q6v3_init(void) +{ + return platform_driver_register(&pil_q6v3_driver); +} +module_init(pil_q6v3_init); + +static void __exit pil_q6v3_exit(void) +{ + platform_driver_unregister(&pil_q6v3_driver); +} +module_exit(pil_q6v3_exit); + +MODULE_DESCRIPTION("Support for booting QDSP6v3 (Hexagon) processors"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-q6v4.c b/arch/arm/mach-msm/pil-q6v4.c new file mode 100644 index 0000000000000000000000000000000000000000..131a74bf2eb540615da05d8bda1e5d56fb8d29c2 --- /dev/null +++ b/arch/arm/mach-msm/pil-q6v4.c @@ -0,0 +1,466 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "peripheral-loader.h" +#include "pil-q6v4.h" +#include "scm-pas.h" + +#define QDSP6SS_RST_EVB 0x0 +#define QDSP6SS_RESET 0x04 +#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 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) + +struct q6v4_data { + void __iomem *base; + void __iomem *modem_base; + unsigned long start_addr; + struct regulator *vreg; + struct regulator *pll_supply; + bool vreg_enabled; + struct clk *xo; + struct pil_device *pil; +}; + +static int pil_q6v4_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct q6v4_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} + +static int pil_q6v4_make_proxy_votes(struct pil_desc *pil) +{ + const struct q6v4_data *drv = dev_get_drvdata(pil->dev); + int ret; + + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to enable XO\n"); + goto err; + } + if (drv->pll_supply) { + ret = regulator_enable(drv->pll_supply); + if (ret) { + dev_err(pil->dev, "Failed to enable pll supply\n"); + goto err_regulator; + } + } + return 0; +err_regulator: + clk_disable_unprepare(drv->xo); +err: + return ret; +} + +static void pil_q6v4_remove_proxy_votes(struct pil_desc *pil) +{ + const struct q6v4_data *drv = dev_get_drvdata(pil->dev); + if (drv->pll_supply) + regulator_disable(drv->pll_supply); + clk_disable_unprepare(drv->xo); +} + +static int pil_q6v4_power_up(struct device *dev) +{ + int err; + struct q6v4_data *drv = dev_get_drvdata(dev); + + err = regulator_set_voltage(drv->vreg, 375000, 375000); + if (err) { + dev_err(dev, "Failed to set regulator's voltage step.\n"); + return err; + } + err = regulator_enable(drv->vreg); + if (err) { + dev_err(dev, "Failed to enable regulator.\n"); + return err; + } + + /* + * Q6 hardware requires a two step voltage ramp-up. + * Delay between the steps. + */ + udelay(100); + + err = regulator_set_voltage(drv->vreg, 1050000, 1050000); + if (err) { + dev_err(dev, "Failed to set regulator's voltage.\n"); + return err; + } + drv->vreg_enabled = true; + return 0; +} + +static DEFINE_MUTEX(pil_q6v4_modem_lock); +static unsigned pil_q6v4_modem_count; + +/* Bring modem subsystem out of reset */ +static void pil_q6v4_init_modem(void __iomem *base, void __iomem *jtag_clk) +{ + mutex_lock(&pil_q6v4_modem_lock); + if (!pil_q6v4_modem_count) { + /* 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); + + /* De-assert MSS reset */ + writel_relaxed(0x0, MSS_RESET); + mb(); + udelay(10); + /* Enable MSS */ + writel_relaxed(0x7, base); + } + + /* Enable JTAG clocks */ + /* TODO: Remove if/when Q6 software enables them? */ + writel_relaxed(0x10, jtag_clk); + + pil_q6v4_modem_count++; + mutex_unlock(&pil_q6v4_modem_lock); +} + +/* Put modem subsystem back into reset */ +static void pil_q6v4_shutdown_modem(void) +{ + mutex_lock(&pil_q6v4_modem_lock); + if (pil_q6v4_modem_count) + pil_q6v4_modem_count--; + if (pil_q6v4_modem_count == 0) + writel_relaxed(0x1, MSS_RESET); + mutex_unlock(&pil_q6v4_modem_lock); +} + +static int pil_q6v4_reset(struct pil_desc *pil) +{ + u32 reg, err; + const struct q6v4_data *drv = dev_get_drvdata(pil->dev); + const struct pil_q6v4_pdata *pdata = pil->dev->platform_data; + + err = pil_q6v4_power_up(pil->dev); + if (err) + return err; + /* Enable Q6 ACLK */ + writel_relaxed(0x10, pdata->aclk_reg); + + if (drv->modem_base) + pil_q6v4_init_modem(drv->modem_base, pdata->jtag_clk_reg); + + /* Unhalt bus port */ + err = msm_bus_axi_portunhalt(pdata->bus_port); + if (err) + dev_err(pil->dev, "Failed to unhalt bus port\n"); + + /* Deassert Q6SS_SS_ARES */ + reg = readl_relaxed(drv->base + QDSP6SS_RESET); + reg &= ~(Q6SS_SS_ARES); + writel_relaxed(reg, drv->base + QDSP6SS_RESET); + + /* Program boot address */ + writel_relaxed((drv->start_addr >> 8) & 0xFFFFFF, + drv->base + QDSP6SS_RST_EVB); + + /* Program TCM and AHB address ranges */ + writel_relaxed(pdata->strap_tcm_base, drv->base + QDSP6SS_STRAP_TCM); + writel_relaxed(pdata->strap_ahb_upper | pdata->strap_ahb_lower, + drv->base + QDSP6SS_STRAP_AHB); + + /* Turn off Q6 core clock */ + writel_relaxed(Q6SS_SRC_SWITCH_CLK_OVR, + drv->base + QDSP6SS_GFMUX_CTL); + + /* Put memories to sleep */ + writel_relaxed(Q6SS_CLAMP_IO, drv->base + QDSP6SS_PWR_CTL); + + /* Assert resets */ + reg = readl_relaxed(drv->base + QDSP6SS_RESET); + reg |= (Q6SS_CORE_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES + | Q6SS_STOP_CORE_ARES); + writel_relaxed(reg, drv->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, drv->base + QDSP6SS_PWR_CTL); + + /* Turn on Q6 core clock */ + reg = Q6SS_CLK_ENA | Q6SS_SRC_SWITCH_CLK_OVR; + writel_relaxed(reg, drv->base + QDSP6SS_GFMUX_CTL); + + /* Remove Q6SS_CLAMP_IO */ + reg = readl_relaxed(drv->base + QDSP6SS_PWR_CTL); + reg &= ~Q6SS_CLAMP_IO; + writel_relaxed(reg, drv->base + QDSP6SS_PWR_CTL); + + /* Bring Q6 core out of reset and start execution. */ + writel_relaxed(0x0, drv->base + QDSP6SS_RESET); + + return 0; +} + +static int pil_q6v4_shutdown(struct pil_desc *pil) +{ + u32 reg; + struct q6v4_data *drv = dev_get_drvdata(pil->dev); + const struct pil_q6v4_pdata *pdata = pil->dev->platform_data; + + /* Make sure bus port is halted */ + msm_bus_axi_porthalt(pdata->bus_port); + + /* Turn off Q6 core clock */ + writel_relaxed(Q6SS_SRC_SWITCH_CLK_OVR, + drv->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, drv->base + QDSP6SS_RESET); + + /* Turn off Q6 memories */ + writel_relaxed(Q6SS_CLAMP_IO, drv->base + QDSP6SS_PWR_CTL); + + if (drv->modem_base) + pil_q6v4_shutdown_modem(); + + if (drv->vreg_enabled) { + regulator_disable(drv->vreg); + drv->vreg_enabled = false; + } + + return 0; +} + +static struct pil_reset_ops pil_q6v4_ops = { + .init_image = pil_q6v4_init_image, + .auth_and_reset = pil_q6v4_reset, + .shutdown = pil_q6v4_shutdown, + .proxy_vote = pil_q6v4_make_proxy_votes, + .proxy_unvote = pil_q6v4_remove_proxy_votes, +}; + +static int pil_q6v4_init_image_trusted(struct pil_desc *pil, + const u8 *metadata, size_t size) +{ + const struct pil_q6v4_pdata *pdata = pil->dev->platform_data; + return pas_init_image(pdata->pas_id, metadata, size); +} + +static int pil_q6v4_reset_trusted(struct pil_desc *pil) +{ + const struct pil_q6v4_pdata *pdata = pil->dev->platform_data; + int err; + + err = pil_q6v4_power_up(pil->dev); + if (err) + return err; + + /* Unhalt bus port */ + err = msm_bus_axi_portunhalt(pdata->bus_port); + if (err) + dev_err(pil->dev, "Failed to unhalt bus port\n"); + return pas_auth_and_reset(pdata->pas_id); +} + +static int pil_q6v4_shutdown_trusted(struct pil_desc *pil) +{ + int ret; + struct q6v4_data *drv = dev_get_drvdata(pil->dev); + struct pil_q6v4_pdata *pdata = pil->dev->platform_data; + + /* Make sure bus port is halted */ + msm_bus_axi_porthalt(pdata->bus_port); + + ret = pas_shutdown(pdata->pas_id); + if (ret) + return ret; + + if (drv->vreg_enabled) { + regulator_disable(drv->vreg); + drv->vreg_enabled = false; + } + + return ret; +} + +static struct pil_reset_ops pil_q6v4_ops_trusted = { + .init_image = pil_q6v4_init_image_trusted, + .auth_and_reset = pil_q6v4_reset_trusted, + .shutdown = pil_q6v4_shutdown_trusted, + .proxy_vote = pil_q6v4_make_proxy_votes, + .proxy_unvote = pil_q6v4_remove_proxy_votes, +}; + +static int __devinit pil_q6v4_driver_probe(struct platform_device *pdev) +{ + const struct pil_q6v4_pdata *pdata = pdev->dev.platform_data; + struct q6v4_data *drv; + struct resource *res; + struct pil_desc *desc; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!drv->base) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + drv->modem_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!drv->modem_base) + return -ENOMEM; + } + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + drv->pll_supply = devm_regulator_get(&pdev->dev, "pll_vdd"); + if (IS_ERR(drv->pll_supply)) { + drv->pll_supply = NULL; + } else { + ret = regulator_set_voltage(drv->pll_supply, 1800000, 1800000); + if (ret) { + dev_err(&pdev->dev, "failed to set pll voltage\n"); + return ret; + } + + ret = regulator_set_optimum_mode(drv->pll_supply, 100000); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set pll optimum mode\n"); + return ret; + } + } + + desc->name = pdata->name; + desc->depends_on = pdata->depends; + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + desc->proxy_timeout = 10000; + + if (pas_supported(pdata->pas_id) > 0) { + desc->ops = &pil_q6v4_ops_trusted; + dev_info(&pdev->dev, "using secure boot\n"); + } else { + desc->ops = &pil_q6v4_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + } + + drv->vreg = devm_regulator_get(&pdev->dev, "core_vdd"); + if (IS_ERR(drv->vreg)) + return PTR_ERR(drv->vreg); + + ret = regulator_set_optimum_mode(drv->vreg, 100000); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set regulator's mode.\n"); + return ret; + } + + drv->xo = devm_clk_get(&pdev->dev, "xo"); + if (IS_ERR(drv->xo)) + return PTR_ERR(drv->xo); + + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) + return PTR_ERR(drv->pil); + return 0; +} + +static int __devexit pil_q6v4_driver_exit(struct platform_device *pdev) +{ + struct q6v4_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct platform_driver pil_q6v4_driver = { + .probe = pil_q6v4_driver_probe, + .remove = __devexit_p(pil_q6v4_driver_exit), + .driver = { + .name = "pil_qdsp6v4", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_q6v4_init(void) +{ + return platform_driver_register(&pil_q6v4_driver); +} +module_init(pil_q6v4_init); + +static void __exit pil_q6v4_exit(void) +{ + platform_driver_unregister(&pil_q6v4_driver); +} +module_exit(pil_q6v4_exit); + +MODULE_DESCRIPTION("Support for booting QDSP6v4 (Hexagon) processors"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-q6v4.h b/arch/arm/mach-msm/pil-q6v4.h new file mode 100644 index 0000000000000000000000000000000000000000..b0b97d0fc5ae43ba8aa502d1bbf1d10ce57e8fc4 --- /dev/null +++ b/arch/arm/mach-msm/pil-q6v4.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PIL_Q6V4_H +#define __MSM_PIL_Q6V4_H + +struct pil_q6v4_pdata { + const unsigned long strap_tcm_base; + const unsigned long strap_ahb_upper; + const unsigned long strap_ahb_lower; + void __iomem *aclk_reg; + void __iomem *jtag_clk_reg; + const char *name; + const char *depends; + const unsigned pas_id; + int bus_port; +}; +#endif diff --git a/arch/arm/mach-msm/pil-q6v5-lpass.c b/arch/arm/mach-msm/pil-q6v5-lpass.c new file mode 100644 index 0000000000000000000000000000000000000000..311f8a78021d29eda515f96c148d72e1eb859797 --- /dev/null +++ b/arch/arm/mach-msm/pil-q6v5-lpass.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" +#include "pil-q6v5.h" + +#define QDSP6SS_RST_EVB 0x010 +#define PROXY_TIMEOUT_MS 10000 + +static int pil_lpass_shutdown(struct pil_desc *pil) +{ + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + + pil_q6v5_halt_axi_port(pil, drv->axi_halt_base); + + /* + * If the shutdown function is called before the reset function, clocks + * will not be enabled yet. Enable them here so that register writes + * performed during the shutdown succeed. + */ + if (drv->is_booted == false) + pil_q6v5_enable_clks(pil); + + pil_q6v5_shutdown(pil); + pil_q6v5_disable_clks(pil); + + drv->is_booted = false; + + return 0; +} + +static int pil_lpass_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + int ret; + + ret = pil_q6v5_enable_clks(pil); + if (ret) + return ret; + + /* Program Image Address */ + writel_relaxed(((drv->start_addr >> 4) & 0x0FFFFFF0), + drv->reg_base + QDSP6SS_RST_EVB); + + ret = pil_q6v5_reset(pil); + if (ret) { + pil_q6v5_disable_clks(pil); + return ret; + } + + drv->is_booted = true; + + return 0; +} + +static struct pil_reset_ops pil_lpass_ops = { + .init_image = pil_q6v5_init_image, + .proxy_vote = pil_q6v5_make_proxy_votes, + .proxy_unvote = pil_q6v5_remove_proxy_votes, + .auth_and_reset = pil_lpass_reset, + .shutdown = pil_lpass_shutdown, +}; + +static int __devinit pil_lpass_driver_probe(struct platform_device *pdev) +{ + struct q6v5_data *drv; + struct pil_desc *desc; + + desc = pil_q6v5_init(pdev); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + drv = platform_get_drvdata(pdev); + if (drv == NULL) + return -ENODEV; + + desc->ops = &pil_lpass_ops; + desc->owner = THIS_MODULE; + desc->proxy_timeout = PROXY_TIMEOUT_MS; + + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) + return PTR_ERR(drv->pil); + + return 0; +} + +static int __devexit pil_lpass_driver_exit(struct platform_device *pdev) +{ + struct q6v5_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct of_device_id lpass_match_table[] = { + { .compatible = "qcom,pil-q6v5-lpass" }, + {} +}; + +static struct platform_driver pil_lpass_driver = { + .probe = pil_lpass_driver_probe, + .remove = __devexit_p(pil_lpass_driver_exit), + .driver = { + .name = "pil-q6v5-lpass", + .of_match_table = lpass_match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init pil_lpass_init(void) +{ + return platform_driver_register(&pil_lpass_driver); +} +module_init(pil_lpass_init); + +static void __exit pil_lpass_exit(void) +{ + platform_driver_unregister(&pil_lpass_driver); +} +module_exit(pil_lpass_exit); + +MODULE_DESCRIPTION("Support for booting low-power audio subsystems with QDSP6v5 (Hexagon) processors"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-q6v5.c b/arch/arm/mach-msm/pil-q6v5.c new file mode 100644 index 0000000000000000000000000000000000000000..6a969908c48860819195556c41ef308cccd4e1fc --- /dev/null +++ b/arch/arm/mach-msm/pil-q6v5.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "pil-q6v5.h" + +/* QDSP6SS Register Offsets */ +#define QDSP6SS_RESET 0x014 +#define QDSP6SS_GFMUX_CTL 0x020 +#define QDSP6SS_PWR_CTL 0x030 + +/* AXI Halt Register Offsets */ +#define AXI_HALTREQ 0x0 +#define AXI_HALTACK 0x4 +#define AXI_IDLE 0x8 + +#define HALT_ACK_TIMEOUT_US 100000 + +/* QDSP6SS_RESET */ +#define Q6SS_CORE_ARES BIT(1) +#define Q6SS_ETM_ISDB_ARES BIT(3) +#define Q6SS_STOP_CORE BIT(4) + +/* QDSP6SS_GFMUX_CTL */ +#define Q6SS_CLK_ENA BIT(1) + +/* QDSP6SS_PWR_CTL */ +#define Q6SS_L2DATA_SLP_NRET_N BIT(0) +#define Q6SS_L2TAG_SLP_NRET_N BIT(16) +#define Q6SS_ETB_SLP_NRET_N BIT(17) +#define Q6SS_L2DATA_STBY_N BIT(18) +#define Q6SS_SLP_RET_N BIT(19) +#define Q6SS_CLAMP_IO BIT(20) +#define QDSS_BHS_ON BIT(21) + +int pil_q6v5_make_proxy_votes(struct pil_desc *pil) +{ + int ret; + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to enable XO\n"); + return ret; + } + return 0; +} +EXPORT_SYMBOL(pil_q6v5_make_proxy_votes); + +void pil_q6v5_remove_proxy_votes(struct pil_desc *pil) +{ + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + clk_disable_unprepare(drv->xo); +} +EXPORT_SYMBOL(pil_q6v5_remove_proxy_votes); + +void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base) +{ + int ret; + u32 status; + + /* Assert halt request */ + writel_relaxed(1, halt_base + AXI_HALTREQ); + + /* Wait for halt */ + ret = readl_poll_timeout(halt_base + AXI_HALTACK, + status, status != 0, 50, HALT_ACK_TIMEOUT_US); + if (ret) + dev_warn(pil->dev, "Port %p halt timeout\n", halt_base); + else if (!readl_relaxed(halt_base + AXI_IDLE)) + dev_warn(pil->dev, "Port %p halt failed\n", halt_base); + + /* Clear halt request (port will remain halted until reset) */ + writel_relaxed(0, halt_base + AXI_HALTREQ); +} +EXPORT_SYMBOL(pil_q6v5_halt_axi_port); + +int pil_q6v5_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} +EXPORT_SYMBOL(pil_q6v5_init_image); + +int pil_q6v5_enable_clks(struct pil_desc *pil) +{ + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + int ret; + + ret = clk_reset(drv->core_clk, CLK_RESET_DEASSERT); + if (ret) + goto err_reset; + ret = clk_prepare_enable(drv->core_clk); + if (ret) + goto err_core_clk; + ret = clk_prepare_enable(drv->bus_clk); + if (ret) + goto err_bus_clk; + + return 0; + +err_bus_clk: + clk_disable_unprepare(drv->core_clk); +err_core_clk: + clk_reset(drv->core_clk, CLK_RESET_ASSERT); +err_reset: + return ret; +} +EXPORT_SYMBOL(pil_q6v5_enable_clks); + +void pil_q6v5_disable_clks(struct pil_desc *pil) +{ + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + + clk_disable_unprepare(drv->bus_clk); + clk_disable_unprepare(drv->core_clk); + clk_reset(drv->core_clk, CLK_RESET_ASSERT); +} +EXPORT_SYMBOL(pil_q6v5_disable_clks); + +void pil_q6v5_shutdown(struct pil_desc *pil) +{ + u32 val; + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + + /* Turn off core clock */ + val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); + val &= ~Q6SS_CLK_ENA; + writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); + + /* Clamp IO */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_CLAMP_IO; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn off Q6 memories */ + val &= ~(Q6SS_L2DATA_SLP_NRET_N | Q6SS_SLP_RET_N | + Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLP_NRET_N | + Q6SS_L2DATA_STBY_N); + writel_relaxed(Q6SS_CLAMP_IO, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Assert Q6 resets */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val = (Q6SS_CORE_ARES | Q6SS_ETM_ISDB_ARES); + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Kill power at block headswitch (affects LPASS only) */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val &= ~QDSS_BHS_ON; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); +} +EXPORT_SYMBOL(pil_q6v5_shutdown); + +int pil_q6v5_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = dev_get_drvdata(pil->dev); + u32 val; + + /* Assert resets, stop core */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val |= (Q6SS_CORE_ARES | Q6SS_ETM_ISDB_ARES | Q6SS_STOP_CORE); + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Enable power block headswitch (only affects LPASS) */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= QDSS_BHS_ON; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on memories */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_L2DATA_SLP_NRET_N | Q6SS_SLP_RET_N | + Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLP_NRET_N | + Q6SS_L2DATA_STBY_N; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Remove IO clamp */ + val &= ~Q6SS_CLAMP_IO; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Bring core out of reset */ + val = Q6SS_STOP_CORE; + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Turn on core clock */ + val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); + val |= Q6SS_CLK_ENA; + writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); + + /* Start core execution */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val &= ~Q6SS_STOP_CORE; + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + return 0; +} +EXPORT_SYMBOL(pil_q6v5_reset); + +struct pil_desc __devinit *pil_q6v5_init(struct platform_device *pdev) +{ + struct q6v5_data *drv; + struct resource *res; + struct pil_desc *desc; + int ret; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return ERR_PTR(-ENOMEM); + platform_set_drvdata(pdev, drv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return ERR_PTR(-EINVAL); + drv->reg_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!drv->reg_base) + return ERR_PTR(-ENOMEM); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + drv->axi_halt_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!drv->axi_halt_base) + return ERR_PTR(-ENOMEM); + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name", + &desc->name); + if (ret) + return ERR_PTR(ret); + + drv->xo = devm_clk_get(&pdev->dev, "xo"); + if (IS_ERR(drv->xo)) + return ERR_CAST(drv->xo); + + drv->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(drv->bus_clk)) + return ERR_CAST(drv->bus_clk); + + drv->core_clk = devm_clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(drv->core_clk)) + return ERR_CAST(drv->core_clk); + + desc->dev = &pdev->dev; + + return desc; +} +EXPORT_SYMBOL(pil_q6v5_init); diff --git a/arch/arm/mach-msm/pil-q6v5.h b/arch/arm/mach-msm/pil-q6v5.h new file mode 100644 index 0000000000000000000000000000000000000000..a9a8d07c2607e49b73e3e43742aadcf70794e1fc --- /dev/null +++ b/arch/arm/mach-msm/pil-q6v5.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PIL_Q6V5_H +#define __MSM_PIL_Q6V5_H + +struct regulator; +struct clk; +struct pil_device; +struct pil_desc; +struct platform_device; + +struct q6v5_data { + void __iomem *reg_base; + struct clk *xo; + struct clk *bus_clk; + struct clk *core_clk; + void __iomem *axi_halt_base; + void __iomem *rmb_base; + unsigned long start_addr; + struct regulator *vreg; + bool is_booted; + int self_auth; + struct pil_device *pil; +}; + +int pil_q6v5_make_proxy_votes(struct pil_desc *pil); +void pil_q6v5_remove_proxy_votes(struct pil_desc *pil); +void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base); +int pil_q6v5_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size); +void pil_q6v5_shutdown(struct pil_desc *pil); +int pil_q6v5_reset(struct pil_desc *pil); +int pil_q6v5_enable_clks(struct pil_desc *pil); +void pil_q6v5_disable_clks(struct pil_desc *pil); +struct pil_desc *pil_q6v5_init(struct platform_device *pdev); + +#endif diff --git a/arch/arm/mach-msm/pil-riva.c b/arch/arm/mach-msm/pil-riva.c new file mode 100644 index 0000000000000000000000000000000000000000..8a16b43f6aec5601a4b6de13aef6628364a12668 --- /dev/null +++ b/arch/arm/mach-msm/pil-riva.c @@ -0,0 +1,384 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "scm-pas.h" + +#define RIVA_PMU_A2XB_CFG 0xB8 +#define RIVA_PMU_A2XB_CFG_EN BIT(0) + +#define RIVA_PMU_CFG 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_EN 0x2C +#define RIVA_PMU_OVRD_EN_CCPU_RESET BIT(0) +#define RIVA_PMU_OVRD_EN_CCPU_CLK BIT(1) + +#define RIVA_PMU_OVRD_VAL 0x30 +#define RIVA_PMU_OVRD_VAL_CCPU_RESET BIT(0) +#define RIVA_PMU_OVRD_VAL_CCPU_CLK BIT(1) + +#define RIVA_PMU_CCPU_CTL 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 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_RESET (MSM_CLK_CTL_BASE + 0x35E0) + +#define RIVA_PMU_ROOT_CLK_SEL 0xC8 +#define RIVA_PMU_ROOT_CLK_SEL_3 BIT(2) + +#define RIVA_PMU_CLK_ROOT3 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) + +struct riva_data { + void __iomem *base; + unsigned long start_addr; + struct clk *xo; + struct regulator *pll_supply; + struct pil_device *pil; +}; + +static bool cxo_is_needed(struct riva_data *drv) +{ + u32 reg = readl_relaxed(drv->base + RIVA_PMU_CFG); + return (reg & RIVA_PMU_CFG_IRIS_XO_MODE) + != RIVA_PMU_CFG_IRIS_XO_MODE_48; +} + +static int pil_riva_make_proxy_vote(struct pil_desc *pil) +{ + struct riva_data *drv = dev_get_drvdata(pil->dev); + int ret; + + ret = regulator_enable(drv->pll_supply); + if (ret) { + dev_err(pil->dev, "failed to enable pll supply\n"); + goto err; + } + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "failed to enable xo\n"); + goto err_clk; + } + return 0; +err_clk: + regulator_disable(drv->pll_supply); +err: + return ret; +} + +static void pil_riva_remove_proxy_vote(struct pil_desc *pil) +{ + struct riva_data *drv = dev_get_drvdata(pil->dev); + regulator_disable(drv->pll_supply); + clk_disable_unprepare(drv->xo); +} + +static int pil_riva_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + struct riva_data *drv = dev_get_drvdata(pil->dev); + drv->start_addr = ehdr->e_entry; + return 0; +} + +static int pil_riva_reset(struct pil_desc *pil) +{ + u32 reg, sel; + struct riva_data *drv = dev_get_drvdata(pil->dev); + void __iomem *base = drv->base; + unsigned long start_addr = drv->start_addr; + bool use_cxo = cxo_is_needed(drv); + + /* Enable A2XB bridge */ + reg = readl_relaxed(base + RIVA_PMU_A2XB_CFG); + reg |= RIVA_PMU_A2XB_CFG_EN; + writel_relaxed(reg, base + RIVA_PMU_A2XB_CFG); + + /* Program PLL 13 to 960 MHz */ + reg = readl_relaxed(RIVA_PLL_MODE); + reg &= ~(PLL_MODE_BYPASSNL | PLL_MODE_OUTCTRL | PLL_MODE_RESET_N); + writel_relaxed(reg, RIVA_PLL_MODE); + + if (use_cxo) + writel_relaxed(0x40000C00 | 50, RIVA_PLL_L_VAL); + else + writel_relaxed(0x40000C00 | 40, RIVA_PLL_L_VAL); + writel_relaxed(0, RIVA_PLL_M_VAL); + writel_relaxed(1, RIVA_PLL_N_VAL); + writel_relaxed(0x01495227, RIVA_PLL_CONFIG); + + reg = readl_relaxed(RIVA_PLL_MODE); + reg &= ~(PLL_MODE_REF_XO_SEL); + reg |= use_cxo ? PLL_MODE_REF_XO_SEL_CXO : PLL_MODE_REF_XO_SEL_RF; + writel_relaxed(reg, RIVA_PLL_MODE); + + /* Enable PLL 13 */ + reg |= PLL_MODE_BYPASSNL; + writel_relaxed(reg, RIVA_PLL_MODE); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + usleep_range(10, 20); + + reg |= PLL_MODE_RESET_N; + writel_relaxed(reg, RIVA_PLL_MODE); + reg |= PLL_MODE_OUTCTRL; + writel_relaxed(reg, RIVA_PLL_MODE); + + /* Wait for PLL to settle */ + mb(); + usleep_range(50, 100); + + /* Configure cCPU for 240 MHz */ + sel = readl_relaxed(base + RIVA_PMU_ROOT_CLK_SEL); + reg = readl_relaxed(base + RIVA_PMU_CLK_ROOT3); + if (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_relaxed(reg, base + RIVA_PMU_CLK_ROOT3); + reg |= RIVA_PMU_CLK_ROOT3_ENA; + writel_relaxed(reg, base + RIVA_PMU_CLK_ROOT3); + reg = readl_relaxed(base + RIVA_PMU_ROOT_CLK_SEL); + reg ^= RIVA_PMU_ROOT_CLK_SEL_3; + writel_relaxed(reg, base + RIVA_PMU_ROOT_CLK_SEL); + + /* Use the high vector table */ + reg = readl_relaxed(base + RIVA_PMU_CCPU_CTL); + reg |= RIVA_PMU_CCPU_CTL_HIGH_IVT | RIVA_PMU_CCPU_CTL_REMAP_EN; + writel_relaxed(reg, base + RIVA_PMU_CCPU_CTL); + + /* Set base memory address */ + writel_relaxed(start_addr >> 16, base + RIVA_PMU_CCPU_BOOT_REMAP_ADDR); + + /* Clear warmboot bit indicating this is a cold boot */ + reg = readl_relaxed(base + RIVA_PMU_CFG); + reg &= ~(RIVA_PMU_CFG_WARM_BOOT); + writel_relaxed(reg, base + RIVA_PMU_CFG); + + /* Enable the cCPU clock */ + reg = readl_relaxed(base + RIVA_PMU_OVRD_VAL); + reg |= RIVA_PMU_OVRD_VAL_CCPU_CLK; + writel_relaxed(reg, base + RIVA_PMU_OVRD_VAL); + + /* Take cCPU out of reset */ + reg |= RIVA_PMU_OVRD_VAL_CCPU_RESET; + writel_relaxed(reg, base + RIVA_PMU_OVRD_VAL); + + return 0; +} + +static int pil_riva_shutdown(struct pil_desc *pil) +{ + struct riva_data *drv = dev_get_drvdata(pil->dev); + u32 reg; + + /* Put cCPU and cCPU clock into reset */ + reg = readl_relaxed(drv->base + RIVA_PMU_OVRD_VAL); + reg &= ~(RIVA_PMU_OVRD_VAL_CCPU_RESET | RIVA_PMU_OVRD_VAL_CCPU_CLK); + writel_relaxed(reg, drv->base + RIVA_PMU_OVRD_VAL); + reg = readl_relaxed(drv->base + RIVA_PMU_OVRD_EN); + reg |= RIVA_PMU_OVRD_EN_CCPU_RESET | RIVA_PMU_OVRD_EN_CCPU_CLK; + writel_relaxed(reg, drv->base + RIVA_PMU_OVRD_EN); + mb(); + + /* Assert reset to Riva */ + writel_relaxed(1, RIVA_RESET); + mb(); + usleep_range(1000, 2000); + + /* Deassert reset to Riva */ + writel_relaxed(0, RIVA_RESET); + mb(); + + return 0; +} + +static struct pil_reset_ops pil_riva_ops = { + .init_image = pil_riva_init_image, + .auth_and_reset = pil_riva_reset, + .shutdown = pil_riva_shutdown, + .proxy_vote = pil_riva_make_proxy_vote, + .proxy_unvote = pil_riva_remove_proxy_vote, +}; + +static int pil_riva_init_image_trusted(struct pil_desc *pil, + const u8 *metadata, size_t size) +{ + return pas_init_image(PAS_RIVA, metadata, size); +} + +static int pil_riva_reset_trusted(struct pil_desc *pil) +{ + return pas_auth_and_reset(PAS_RIVA); +} + +static int pil_riva_shutdown_trusted(struct pil_desc *pil) +{ + return pas_shutdown(PAS_RIVA); +} + +static struct pil_reset_ops pil_riva_ops_trusted = { + .init_image = pil_riva_init_image_trusted, + .auth_and_reset = pil_riva_reset_trusted, + .shutdown = pil_riva_shutdown_trusted, + .proxy_vote = pil_riva_make_proxy_vote, + .proxy_unvote = pil_riva_remove_proxy_vote, +}; + +static int __devinit pil_riva_probe(struct platform_device *pdev) +{ + struct riva_data *drv; + struct resource *res; + struct pil_desc *desc; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!drv->base) + return -ENOMEM; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + drv->pll_supply = devm_regulator_get(&pdev->dev, "pll_vdd"); + if (IS_ERR(drv->pll_supply)) { + dev_err(&pdev->dev, "failed to get pll supply\n"); + return PTR_ERR(drv->pll_supply); + } + if (regulator_count_voltages(drv->pll_supply) > 0) { + ret = regulator_set_voltage(drv->pll_supply, 1800000, 1800000); + if (ret) { + dev_err(&pdev->dev, + "failed to set pll supply voltage\n"); + return ret; + } + + ret = regulator_set_optimum_mode(drv->pll_supply, 100000); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to set pll supply optimum mode\n"); + return ret; + } + } + + desc->name = "wcnss"; + desc->dev = &pdev->dev; + desc->owner = THIS_MODULE; + desc->proxy_timeout = 10000; + + if (pas_supported(PAS_RIVA) > 0) { + desc->ops = &pil_riva_ops_trusted; + dev_info(&pdev->dev, "using secure boot\n"); + } else { + desc->ops = &pil_riva_ops; + dev_info(&pdev->dev, "using non-secure boot\n"); + } + + drv->xo = devm_clk_get(&pdev->dev, "cxo"); + if (IS_ERR(drv->xo)) + return PTR_ERR(drv->xo); + + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) + return PTR_ERR(drv->pil); + return 0; +} + +static int __devexit pil_riva_remove(struct platform_device *pdev) +{ + struct riva_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + return 0; +} + +static struct platform_driver pil_riva_driver = { + .probe = pil_riva_probe, + .remove = __devexit_p(pil_riva_remove), + .driver = { + .name = "pil_riva", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_riva_init(void) +{ + return platform_driver_register(&pil_riva_driver); +} +module_init(pil_riva_init); + +static void __exit pil_riva_exit(void) +{ + platform_driver_unregister(&pil_riva_driver); +} +module_exit(pil_riva_exit); + +MODULE_DESCRIPTION("Support for booting RIVA (WCNSS) processors"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-tzapps.c b/arch/arm/mach-msm/pil-tzapps.c new file mode 100644 index 0000000000000000000000000000000000000000..2345453809df47b2149eea867eea5139125a86d5 --- /dev/null +++ b/arch/arm/mach-msm/pil-tzapps.c @@ -0,0 +1,96 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" +#include "scm-pas.h" + +static int pil_tzapps_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + return pas_init_image(PAS_TZAPPS, metadata, size); +} + +static int pil_tzapps_reset(struct pil_desc *pil) +{ + return pas_auth_and_reset(PAS_TZAPPS); +} + +static int pil_tzapps_shutdown(struct pil_desc *pil) +{ + return pas_shutdown(PAS_TZAPPS); +} + +static struct pil_reset_ops pil_tzapps_ops = { + .init_image = pil_tzapps_init_image, + .auth_and_reset = pil_tzapps_reset, + .shutdown = pil_tzapps_shutdown, +}; + +static int __devinit pil_tzapps_driver_probe(struct platform_device *pdev) +{ + struct pil_desc *desc; + struct pil_device *pil; + + if (pas_supported(PAS_TZAPPS) < 0) + return -ENOSYS; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->name = "tzapps"; + desc->dev = &pdev->dev; + desc->ops = &pil_tzapps_ops; + desc->owner = THIS_MODULE; + pil = msm_pil_register(desc); + if (IS_ERR(pil)) + return PTR_ERR(pil); + platform_set_drvdata(pdev, pil); + return 0; +} + +static int __devexit pil_tzapps_driver_exit(struct platform_device *pdev) +{ + struct pil_device *pil = platform_get_drvdata(pdev); + msm_pil_unregister(pil); + return 0; +} + +static struct platform_driver pil_tzapps_driver = { + .probe = pil_tzapps_driver_probe, + .remove = __devexit_p(pil_tzapps_driver_exit), + .driver = { + .name = "pil_tzapps", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_tzapps_init(void) +{ + return platform_driver_register(&pil_tzapps_driver); +} +module_init(pil_tzapps_init); + +static void __exit pil_tzapps_exit(void) +{ + platform_driver_unregister(&pil_tzapps_driver); +} +module_exit(pil_tzapps_exit); + +MODULE_DESCRIPTION("Support for booting TZApps images"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/pil-vidc.c b/arch/arm/mach-msm/pil-vidc.c new file mode 100644 index 0000000000000000000000000000000000000000..ceb9bcd2969d2db5a5f2266042d5820a1d7e368c --- /dev/null +++ b/arch/arm/mach-msm/pil-vidc.c @@ -0,0 +1,146 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" +#include "scm-pas.h" + +struct vidc_data { + struct clk *smmu_iface; + struct clk *core; + struct pil_device *pil; +}; + +static int pil_vidc_init_image(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + return pas_init_image(PAS_VIDC, metadata, size); +} + +static int pil_vidc_reset(struct pil_desc *pil) +{ + int ret; + struct vidc_data *drv = dev_get_drvdata(pil->dev); + + ret = clk_prepare_enable(drv->smmu_iface); + if (ret) + goto err_smmu; + ret = clk_prepare_enable(drv->core); + if (ret) + goto err_core; + ret = pas_auth_and_reset(PAS_VIDC); + + clk_disable_unprepare(drv->core); +err_core: + clk_disable_unprepare(drv->smmu_iface); +err_smmu: + return ret; +} + +static int pil_vidc_shutdown(struct pil_desc *pil) +{ + return pas_shutdown(PAS_VIDC); +} + +static struct pil_reset_ops pil_vidc_ops = { + .init_image = pil_vidc_init_image, + .auth_and_reset = pil_vidc_reset, + .shutdown = pil_vidc_shutdown, +}; + +static int __devinit pil_vidc_driver_probe(struct platform_device *pdev) +{ + struct pil_desc *desc; + struct vidc_data *drv; + int ret; + + if (pas_supported(PAS_VIDC) < 0) + return -ENOSYS; + + desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + drv->smmu_iface = clk_get(&pdev->dev, "smmu_iface_clk"); + if (IS_ERR(drv->smmu_iface)) { + dev_err(&pdev->dev, "failed to get smmu interface clock\n"); + ret = PTR_ERR(drv->smmu_iface); + goto err_smmu; + } + drv->core = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(drv->core)) { + dev_err(&pdev->dev, "failed to get core clock\n"); + ret = PTR_ERR(drv->core); + goto err_core; + } + + desc->name = "vidc"; + desc->dev = &pdev->dev; + desc->ops = &pil_vidc_ops; + desc->owner = THIS_MODULE; + drv->pil = msm_pil_register(desc); + if (IS_ERR(drv->pil)) { + ret = PTR_ERR(drv->pil); + goto err_register; + } + return 0; + +err_register: + clk_put(drv->core); +err_core: + clk_put(drv->smmu_iface); +err_smmu: + return ret; +} + +static int __devexit pil_vidc_driver_exit(struct platform_device *pdev) +{ + struct vidc_data *drv = platform_get_drvdata(pdev); + msm_pil_unregister(drv->pil); + clk_put(drv->smmu_iface); + clk_put(drv->core); + return 0; +} + +static struct platform_driver pil_vidc_driver = { + .probe = pil_vidc_driver_probe, + .remove = __devexit_p(pil_vidc_driver_exit), + .driver = { + .name = "pil_vidc", + .owner = THIS_MODULE, + }, +}; + +static int __init pil_vidc_init(void) +{ + return platform_driver_register(&pil_vidc_driver); +} +module_init(pil_vidc_init); + +static void __exit pil_vidc_exit(void) +{ + platform_driver_unregister(&pil_vidc_driver); +} +module_exit(pil_vidc_exit); + +MODULE_DESCRIPTION("Support for secure booting vidc images"); +MODULE_LICENSE("GPL v2"); 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 0000000000000000000000000000000000000000..0a856003f965241ce6b973ab6fc331f494eb6e7f --- /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 0000000000000000000000000000000000000000..57ac85d50e2e5ddc80824b6713a47a47ac39250f --- /dev/null +++ b/arch/arm/mach-msm/ping_mdm_rpc_client.c @@ -0,0 +1,814 @@ +/* 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 +#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-8625.c b/arch/arm/mach-msm/platsmp-8625.c new file mode 100644 index 0000000000000000000000000000000000000000..915047a380c253799af00fd67a93e7ce1f0f01bb --- /dev/null +++ b/arch/arm/mach-msm/platsmp-8625.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "pm.h" + +#define MSM_CORE1_RESET 0xA8600590 +#define MSM_CORE1_STATUS_MSK 0x02800000 + +/* + * control for which core is the next to come out of the secondary + * boot "holding pen" + */ +int pen_release = -1; + +static bool cold_boot_done; + +static uint32_t *msm8625_boot_vector; +static void __iomem *reset_core1_base; + +/* + * Write pen_release in a way that is guaranteed to be visible to all + * observers, irrespective of whether they're taking part in coherency + * or not. This is necessary for the hotplug code to work reliably. + */ +static void __cpuinit write_pen_release(int val) +{ + pen_release = val; + smp_wmb(); + __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release)); + outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1)); +} + +static void __iomem *scu_base_addr(void) +{ + return MSM_SCU_BASE; +} + +static DEFINE_SPINLOCK(boot_lock); + +/* + * MP_CORE_IPC will be used to generate interrupt and can be used by either + * of core. + * To bring core1 out of GDFS we need to raise the SPI using the MP_CORE_IPC. + */ +static void raise_clear_spi(unsigned int cpu, bool set) +{ + int value; + + value = __raw_readl(MSM_CSR_BASE + 0x54); + if (set) + __raw_writel(value | BIT(cpu), MSM_CSR_BASE + 0x54); + else + __raw_writel(value & ~BIT(cpu), MSM_CSR_BASE + 0x54); + mb(); +} + +static void clear_pending_spi(unsigned int irq) +{ + struct irq_data *d = irq_get_irq_data(irq); + struct irq_chip *c = irq_data_get_irq_chip(d); + + c->irq_mask(d); + local_irq_disable(); + /* Clear the IRQ from the ENABLE_SET */ + gic_clear_spi_pending(irq); + local_irq_enable(); +} + +void __cpuinit platform_secondary_init(unsigned int cpu) +{ + pr_debug("CPU%u: Booted secondary processor\n", cpu); + + WARN_ON(msm_platform_secondary_init(cpu)); + + /* + * 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 + */ + write_pen_release(-1); + + /* clear the IPC1(SPI-8) pending SPI */ + if (power_collapsed) { + raise_clear_spi(1, false); + clear_pending_spi(MSM8625_INT_ACSR_MP_CORE_IPC1); + power_collapsed = 0; + } + + /* + * Synchronise with the boot thread. + */ + spin_lock(&boot_lock); + spin_unlock(&boot_lock); +} + +static int __cpuinit msm8625_release_secondary(void) +{ + void __iomem *base_ptr; + int value = 0; + unsigned long timeout; + + /* + * loop to ensure that the GHS_STATUS_CORE1 bit in the + * MPA5_STATUS_REG(0x3c) is set. The timeout for the while + * loop can be set as 20us as of now + */ + timeout = jiffies + usecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + value = __raw_readl(MSM_CFG_CTL_BASE + 0x3c); + if ((value & MSM_CORE1_STATUS_MSK) == + MSM_CORE1_STATUS_MSK) + break; + udelay(1); + } + + if (!value) { + pr_err("Core 1 cannot be brought out of Reset!!!\n"); + return -ENODEV; + } + + base_ptr = ioremap_nocache(MSM_CORE1_RESET, SZ_4); + if (!base_ptr) + return -ENODEV; + /* Reset core 1 out of reset */ + __raw_writel(0x0, base_ptr); + mb(); + + reset_core1_base = base_ptr; + + return 0; +} + +void __iomem *core1_reset_base(void) +{ + return reset_core1_base; +} + +int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + unsigned long timeout; + + preset_lpj = loops_per_jiffy; + + if (cold_boot_done == false) { + if (msm8625_release_secondary()) { + pr_err("Failed to release secondary core\n"); + return -ENODEV; + } + cold_boot_done = true; + } + + /* + * Set synchronisation state between this boot processor + * and the secondary one + */ + spin_lock(&boot_lock); + + /* + * This is really belt and braces; we hold unintended secondary + * CPUs in the holding pen until we're ready for them. However, + * since we haven't sent them a soft interrupt, they shouldn't + * be there. + */ + write_pen_release(cpu); + + /* + * 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. + * + * power_collapsed is the flag which will be updated for Powercollapse. + * Once we are out of PC, as Core1 will be in the state of GDFS which + * needs to be brought out by raising an SPI. + */ + + if (power_collapsed) { + core1_gic_configure_and_raise(); + raise_clear_spi(1, true); + } else { + gic_raise_softirq(cpumask_of(cpu), 1); + } + + timeout = jiffies + (1 * HZ); + while (time_before(jiffies, timeout)) { + smp_rmb(); + if (pen_release == -1) + 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 0; +} + +/* + * Initialise the CPU possible map early - this describes the CPUs + * which may be present or become present in the system. + */ +void __init smp_init_cpus(void) +{ + void __iomem *scu_base = scu_base_addr(); + + unsigned int i, ncores; + + ncores = scu_base ? scu_get_core_count(scu_base) : 1; + + for (i = 0; i < ncores; i++) + set_cpu_possible(i, true); + + set_smp_cross_call(gic_raise_softirq); +} + +static void __init msm8625_boot_vector_init(uint32_t *boot_vector, + unsigned long entry) +{ + if (!boot_vector) + return; + msm8625_boot_vector = boot_vector; + + msm8625_boot_vector[0] = 0xE51FF004; /* ldr pc, 4 */ + msm8625_boot_vector[1] = entry; +} + +void __init platform_smp_prepare_cpus(unsigned int max_cpus) +{ + int i, value; + void __iomem *second_ptr; + + /* + * Initialise the present map, which describes the set of CPUs + * actually populated at the present time. + */ + for (i = 0; i < max_cpus; i++) + set_cpu_present(i, true); + + scu_enable(scu_base_addr()); + + /* + * Write the address of secondary startup into the + * boot remapper register. The secondary CPU branches to this address. + */ + __raw_writel(MSM8625_SECONDARY_PHYS, (MSM_CFG_CTL_BASE + 0x34)); + mb(); + + second_ptr = ioremap_nocache(MSM8625_SECONDARY_PHYS, SZ_8); + if (!second_ptr) { + pr_err("failed to ioremap for secondary core\n"); + return; + } + + msm8625_boot_vector_init(second_ptr, + virt_to_phys(msm_secondary_startup)); + iounmap(second_ptr); + + /* Enable boot remapper address: bit 26 for core1 */ + value = __raw_readl(MSM_CFG_CTL_BASE + 0x30); + __raw_writel(value | (0x4 << 24), MSM_CFG_CTL_BASE + 0x30) ; + mb(); +} diff --git a/arch/arm/mach-msm/platsmp.c b/arch/arm/mach-msm/platsmp.c index db0117ec55f4ad32a10d154d1eb2c41654a73bac..b40c0c70415e7366cfd257a100b5bc28d8cd9f18 100644 --- a/arch/arm/mach-msm/platsmp.c +++ b/arch/arm/mach-msm/platsmp.c @@ -1,19 +1,18 @@ /* * Copyright (C) 2002 ARM Ltd. * All Rights Reserved - * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * 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 @@ -22,18 +21,20 @@ #include #include +#include +#include #include +#include "pm.h" #include "scm-boot.h" +#include "spm.h" #define VDD_SC1_ARRAY_CLAMP_GFS_CTL 0x15A0 #define SCSS_CPU1CORE_RESET 0xD80 #define SCSS_DBG_STATUS_CORE_PWRDUP 0xE64 -/* Mask for edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ -#define GIC_PPI_EDGE_MASK 0xFFFFD7FF - extern void msm_secondary_startup(void); + /* * control for which core is the next to come out of the secondary * boot "holding pen". @@ -42,16 +43,9 @@ volatile int pen_release = -1; static DEFINE_SPINLOCK(boot_lock); -static inline int get_core_count(void) -{ - /* 1 + the PART[1:0] field of MIDR */ - return ((read_cpuid_id() >> 4) & 3) + 1; -} - void __cpuinit platform_secondary_init(unsigned int cpu) { - /* Configure edge-triggered PPIs */ - writel(GIC_PPI_EDGE_MASK, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); + WARN_ON(msm_platform_secondary_init(cpu)); /* * if any interrupts are already enabled for the primary @@ -60,13 +54,6 @@ void __cpuinit platform_secondary_init(unsigned int cpu) */ 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(); - /* * Synchronise with the boot thread. */ @@ -74,34 +61,128 @@ void __cpuinit platform_secondary_init(unsigned int cpu) spin_unlock(&boot_lock); } -static __cpuinit void prepare_cold_cpu(unsigned int cpu) +static int __cpuinit scorpion_release_secondary(void) { - int ret; - ret = scm_set_boot_addr(virt_to_phys(msm_secondary_startup), - SCM_FLAG_COLDBOOT_CPU1); - if (ret == 0) { - void __iomem *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); - } - } else - printk(KERN_DEBUG "Failed to set secondary core boot " - "address\n"); + void *base_ptr = ioremap_nocache(0x00902000, SZ_4K*2); + if (!base_ptr) + return -EINVAL; + + writel_relaxed(0, base_ptr + VDD_SC1_ARRAY_CLAMP_GFS_CTL); + dmb(); + writel_relaxed(0, base_ptr + SCSS_CPU1CORE_RESET); + writel_relaxed(3, base_ptr + SCSS_DBG_STATUS_CORE_PWRDUP); + mb(); + iounmap(base_ptr); + + return 0; } +static int __cpuinit krait_release_secondary_sim(unsigned long base, int cpu) +{ + void *base_ptr = ioremap_nocache(base + (cpu * 0x10000), SZ_4K); + if (!base_ptr) + return -ENODEV; + + if (machine_is_msm8960_sim() || machine_is_msm8960_rumi3()) { + writel_relaxed(0x10, base_ptr+0x04); + writel_relaxed(0x80, base_ptr+0x04); + } + + if (machine_is_apq8064_sim()) + writel_relaxed(0xf0000, base_ptr+0x04); + + if (machine_is_copper_sim()) { + writel_relaxed(0x800, base_ptr+0x04); + writel_relaxed(0x3FFF, base_ptr+0x14); + } + + mb(); + iounmap(base_ptr); + return 0; +} + +static int __cpuinit krait_release_secondary(unsigned long base, int cpu) +{ + void *base_ptr = ioremap_nocache(base + (cpu * 0x10000), SZ_4K); + if (!base_ptr) + return -ENODEV; + + msm_spm_turn_on_cpu_rail(cpu); + + 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); + return 0; +} + +static int __cpuinit release_secondary(unsigned int cpu) +{ + BUG_ON(cpu >= get_core_count()); + + if (cpu_is_msm8x60()) + return scorpion_release_secondary(); + + if (machine_is_msm8960_sim() || machine_is_msm8960_rumi3() || + machine_is_apq8064_sim()) + return krait_release_secondary_sim(0x02088000, cpu); + + if (machine_is_copper_sim()) + return krait_release_secondary_sim(0xf9088000, cpu); + + if (cpu_is_msm8960() || cpu_is_msm8930() || cpu_is_apq8064()) + return krait_release_secondary(0x02088000, cpu); + + WARN(1, "unknown CPU case in release_secondary\n"); + return -EINVAL; +} + +DEFINE_PER_CPU(int, cold_boot_done); +static int cold_boot_flags[] = { + 0, + SCM_FLAG_COLDBOOT_CPU1, + SCM_FLAG_COLDBOOT_CPU2, + SCM_FLAG_COLDBOOT_CPU3, +}; + int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) { + int ret; + int flag = 0; unsigned long timeout; - static int cold_boot_done; - /* Only need to bring cpu out of reset this way once */ - if (cold_boot_done == false) { - prepare_cold_cpu(cpu); - cold_boot_done = true; + pr_debug("Starting secondary CPU %d\n", cpu); + + /* Set preset_lpj to avoid subsequent lpj recalculations */ + preset_lpj = loops_per_jiffy; + + if (cpu > 0 && cpu < ARRAY_SIZE(cold_boot_flags)) + flag = cold_boot_flags[cpu]; + else + __WARN(); + + if (per_cpu(cold_boot_done, cpu) == false) { + ret = scm_set_boot_addr((void *) + virt_to_phys(msm_secondary_startup), + flag); + if (ret == 0) + release_secondary(cpu); + else + printk(KERN_DEBUG "Failed to set secondary core boot " + "address\n"); + per_cpu(cold_boot_done, cpu) = true; } /* @@ -121,6 +202,8 @@ int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) pen_release = cpu_logical_map(cpu); __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release)); outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1)); + __asm__("sev"); + mb(); /* * Send the secondary CPU a soft interrupt, thereby causing @@ -135,6 +218,8 @@ int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) if (pen_release == -1) break; + dmac_inv_range((void *)&pen_release, + (void *)(&pen_release+sizeof(pen_release))); udelay(10); } @@ -146,12 +231,9 @@ int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) return pen_release != -1 ? -ENOSYS : 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. + * which may be present or become present in the system. */ void __init smp_init_cpus(void) { @@ -166,7 +248,7 @@ void __init smp_init_cpus(void) for (i = 0; i < ncores; i++) set_cpu_possible(i, true); - set_smp_cross_call(gic_raise_softirq); + set_smp_cross_call(gic_raise_softirq); } void __init platform_smp_prepare_cpus(unsigned int max_cpus) diff --git a/arch/arm/mach-msm/pm-8x60.c b/arch/arm/mach-msm/pm-8x60.c new file mode 100644 index 0000000000000000000000000000000000000000..163caa5d08da38e6673a0400b4358f6f2b84d71f --- /dev/null +++ b/arch/arm/mach-msm/pm-8x60.c @@ -0,0 +1,1059 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +#ifdef CONFIG_VFP +#include +#endif + +#include "acpuclock.h" +#include "clock.h" +#include "avs.h" +#include +#include "idle.h" +#include "pm.h" +#include "scm-boot.h" +#include "spm.h" +#include "timer.h" +#include "pm-boot.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_CLK = BIT(5), + 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 + *****************************************************************************/ +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_RETENTION] = "retention", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] = + "standalone_power_collapse", +}; + +static struct msm_pm_sleep_ops pm_sleep_ops; +/* + * 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_sleep_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) { + strlcat(buf, "\n", PAGE_SIZE); + 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_sleep_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 = NULL; + 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_sleep_modes[idx].suspend_supported) + && (!msm_pm_sleep_modes[idx].idle_supported)) + continue; + + if (!msm_pm_sleep_mode_labels[i] || + !msm_pm_sleep_mode_labels[i][0]) + 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_sleep_modes[idx].idle_supported) + continue; + if ((k == MSM_PM_MODE_ATTR_SUSPEND) && + !msm_pm_sleep_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: + if (ret) { + if (mode && mode->kobj) + kobject_del(mode->kobj); + kfree(mode); + } + + 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; +} + +/****************************************************************************** + * 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); + + +/****************************************************************************** + * + *****************************************************************************/ + +static void *msm_pm_idle_rs_limits; +static bool msm_pm_use_qtimer; + +static void msm_pm_swfi(void) +{ + msm_pm_config_hw_before_swfi(); + msm_arch_idle(); +} + + +static void msm_pm_retention(void) +{ + int ret = 0; + + msm_pm_config_hw_before_swfi(); + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_RETENTION, false); + WARN_ON(ret); + msm_arch_idle(); + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false); + WARN_ON(ret); +} + +#ifdef CONFIG_CACHE_L2X0 +static inline bool msm_pm_l2x0_power_collapse(void) +{ + bool collapsed = 0; + + l2cc_suspend(); + collapsed = msm_pm_collapse(); + l2cc_resume(); + + return collapsed; +} +#else +static inline bool msm_pm_l2x0_power_collapse(void) +{ + return msm_pm_collapse(); +} +#endif + +static bool __ref msm_pm_spm_power_collapse( + unsigned int cpu, bool from_idle, bool notify_rpm) +{ + void *entry; + bool collapsed = 0; + int ret; + unsigned int saved_gic_cpu_ctrl; + + saved_gic_cpu_ctrl = readl_relaxed(MSM_QGIC_CPU_BASE + GIC_CPU_CTRL); + mb(); + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: notify_rpm %d\n", + cpu, __func__, (int) notify_rpm); + + ret = msm_spm_set_low_power_mode( + MSM_SPM_MODE_POWER_COLLAPSE, notify_rpm); + WARN_ON(ret); + + entry = (!cpu || from_idle) ? + msm_pm_collapse_exit : msm_secondary_startup; + msm_pm_boot_config_before_pc(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", + cpu, __func__, entry); + +#ifdef CONFIG_VFP + vfp_pm_suspend(); +#endif + + collapsed = msm_pm_l2x0_power_collapse(); + + msm_pm_boot_config_after_pc(cpu); + + if (collapsed) { +#ifdef CONFIG_VFP + vfp_pm_resume(); +#endif + cpu_init(); + writel(0xF0, MSM_QGIC_CPU_BASE + GIC_CPU_PRIMASK); + writel_relaxed(saved_gic_cpu_ctrl, + MSM_QGIC_CPU_BASE + GIC_CPU_CTRL); + mb(); + 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", + cpu, __func__, collapsed); + + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false); + WARN_ON(ret); + return collapsed; +} + +static bool msm_pm_power_collapse_standalone(bool from_idle) +{ + unsigned int cpu = smp_processor_id(); + unsigned int avsdscr_setting; + bool collapsed; + + avsdscr_setting = avs_get_avsdscr(); + avs_disable(); + collapsed = msm_pm_spm_power_collapse(cpu, from_idle, false); + avs_reset_delays(avsdscr_setting); + return collapsed; +} + +static bool msm_pm_power_collapse(bool from_idle) +{ + unsigned int cpu = smp_processor_id(); + unsigned long saved_acpuclk_rate; + unsigned int avsdscr_setting; + bool collapsed; + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: idle %d\n", + 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", cpu, __func__); + + avsdscr_setting = avs_get_avsdscr(); + avs_disable(); + + if (cpu_online(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", + cpu, __func__, saved_acpuclk_rate); + + collapsed = msm_pm_spm_power_collapse(cpu, from_idle, true); + + if (cpu_online(cpu)) { + if (MSM_PM_DEBUG_CLOCK & msm_pm_debug_mask) + pr_info("CPU%u: %s: restore clock rate to %lu\n", + cpu, __func__, saved_acpuclk_rate); + if (acpuclk_set_rate(cpu, saved_acpuclk_rate, SETRATE_PC) < 0) + pr_err("CPU%u: %s: failed to restore clock rate(%lu)\n", + cpu, __func__, saved_acpuclk_rate); + } else { + unsigned int gic_dist_enabled; + unsigned int gic_dist_pending; + gic_dist_enabled = readl_relaxed( + MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_CLEAR); + gic_dist_pending = readl_relaxed( + MSM_QGIC_DIST_BASE + GIC_DIST_PENDING_SET); + mb(); + gic_dist_pending &= gic_dist_enabled; + + if (gic_dist_pending) + pr_err("CPU %d interrupted during hotplug.Pending int 0x%x\n", + cpu, gic_dist_pending); + } + + + 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", cpu, __func__); + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: return\n", cpu, __func__); + return collapsed; +} + +static void msm_pm_qtimer_available(void) +{ + if (machine_is_copper()) + msm_pm_use_qtimer = true; +} + +static int64_t msm_pm_timer_enter_idle(void) +{ + if (msm_pm_use_qtimer) + return ktime_to_ns(tick_nohz_get_sleep_length()); + + return msm_timer_enter_idle(); +} + +static void msm_pm_timer_exit_idle(bool timer_halted) +{ + if (msm_pm_use_qtimer) + return; + + msm_timer_exit_idle((int) timer_halted); +} + +static int64_t msm_pm_timer_enter_suspend(int64_t *period) +{ + int time = 0; + + if (msm_pm_use_qtimer) + return sched_clock(); + + time = msm_timer_get_sclk_time(period); + if (!time) + pr_err("%s: Unable to read sclk.\n", __func__); + + return time; +} + +static int64_t msm_pm_timer_exit_suspend(int64_t time, int64_t period) +{ + if (msm_pm_use_qtimer) + return sched_clock() - time; + + 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; + } + + return time; +} + +/****************************************************************************** + * External Idle/Suspend Functions + *****************************************************************************/ + +void arch_idle(void) +{ + return; +} + +int msm_pm_idle_prepare(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + uint32_t latency_us; + uint32_t sleep_us; + int i; + unsigned int power_usage = -1; + int ret = 0; + + 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 = &drv->states[i]; + struct cpuidle_state_usage *st_usage = &dev->states_usage[i]; + enum msm_pm_sleep_mode mode; + bool allow; + void *rs_limits = NULL; + uint32_t power; + int idx; + + mode = (enum msm_pm_sleep_mode) cpuidle_get_statedata(st_usage); + idx = MSM_PM_MODE(dev->cpu, mode); + + allow = msm_pm_sleep_modes[idx].idle_enabled && + msm_pm_sleep_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; + /* fall through */ + + case MSM_PM_SLEEP_MODE_RETENTION: + if (!allow) + break; + /* fall through */ + + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + if (!allow) + break; + + if (pm_sleep_ops.lowest_limits) + rs_limits = pm_sleep_ops.lowest_limits(true, + mode, latency_us, sleep_us, + &power); + + 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 (!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) { + if (power < power_usage) { + power_usage = power; + ret = mode; + } + + if (MSM_PM_SLEEP_MODE_POWER_COLLAPSE == mode) + msm_pm_idle_rs_limits = rs_limits; + } + } + + return ret; +} + +int msm_pm_idle_enter(enum msm_pm_sleep_mode sleep_mode) +{ + int64_t time; + int exit_stat; + + 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(); + exit_stat = MSM_PM_STAT_IDLE_WFI; + break; + + case MSM_PM_SLEEP_MODE_RETENTION: + msm_pm_retention(); + exit_stat = MSM_PM_STAT_RETENTION; + break; + + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE: + msm_pm_power_collapse_standalone(true); + exit_stat = MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE; + break; + + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: { + int64_t timer_expiration = 0; + bool timer_halted = false; + uint32_t sleep_delay; + int ret = -ENODEV; + int notify_rpm = + (sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE); + int collapsed; + + timer_expiration = msm_pm_timer_enter_idle(); + + 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 (MSM_PM_DEBUG_IDLE_CLK & msm_pm_debug_mask) + clock_debug_print_enabled(); + + if (pm_sleep_ops.enter_sleep) + ret = pm_sleep_ops.enter_sleep(sleep_delay, + msm_pm_idle_rs_limits, + true, notify_rpm); + if (!ret) { + collapsed = msm_pm_power_collapse(true); + timer_halted = true; + + if (pm_sleep_ops.exit_sleep) + pm_sleep_ops.exit_sleep(msm_pm_idle_rs_limits, + true, notify_rpm, collapsed); + } + msm_pm_timer_exit_idle(timer_halted); + exit_stat = MSM_PM_STAT_IDLE_POWER_COLLAPSE; + break; + } + + default: + __WARN(); + goto cpuidle_enter_bail; + } + + time = ktime_to_ns(ktime_get()) - time; + msm_pm_add_stat(exit_stat, time); + + do_div(time, 1000); + return (int) time; + +cpuidle_enter_bail: + return 0; +} + +static struct msm_pm_sleep_status_data *msm_pm_slp_sts; + +static DEFINE_PER_CPU_SHARED_ALIGNED(enum msm_pm_sleep_mode, + msm_pm_last_slp_mode); + +bool msm_pm_verify_cpu_pc(unsigned int cpu) +{ + enum msm_pm_sleep_mode mode = per_cpu(msm_pm_last_slp_mode, cpu); + + if (msm_pm_slp_sts) + if ((mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE) || + (mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)) + return true; + + return false; +} + +void msm_pm_cpu_enter_lowpower(unsigned int cpu) +{ + int i; + bool allow[MSM_PM_SLEEP_MODE_NR]; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct msm_pm_platform_data *mode; + + mode = &msm_pm_sleep_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__); + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) { + per_cpu(msm_pm_last_slp_mode, cpu) + = MSM_PM_SLEEP_MODE_POWER_COLLAPSE; + msm_pm_power_collapse(false); + } else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) { + per_cpu(msm_pm_last_slp_mode, cpu) + = MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE; + msm_pm_power_collapse_standalone(false); + } else if (allow[MSM_PM_SLEEP_MODE_RETENTION]) { + per_cpu(msm_pm_last_slp_mode, cpu) + = MSM_PM_SLEEP_MODE_RETENTION; + msm_pm_retention(); + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + per_cpu(msm_pm_last_slp_mode, cpu) + = MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT; + msm_pm_swfi(); + } else + per_cpu(msm_pm_last_slp_mode, cpu) = MSM_PM_SLEEP_MODE_NR; +} + +int msm_pm_wait_cpu_shutdown(unsigned int cpu) +{ + + int timeout = 10; + + if (!msm_pm_slp_sts) + return 0; + + while (timeout--) { + + /* + * Check for the SPM of the core being hotplugged to set + * its sleep state.The SPM sleep state indicates that the + * core has been power collapsed. + */ + + int acc_sts = __raw_readl(msm_pm_slp_sts->base_addr + + cpu * msm_pm_slp_sts->cpu_offset); + mb(); + + if (acc_sts & msm_pm_slp_sts->mask) + return 0; + + usleep(100); + } + pr_warn("%s(): Timed out waiting for CPU %u SPM to enter sleep state", + __func__, cpu); + return -EBUSY; +} + +static int msm_pm_enter(suspend_state_t state) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + int i; + int64_t period = 0; + int64_t time = msm_pm_timer_enter_suspend(&period); + + 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_sleep_modes[MSM_PM_MODE(0, i)]; + allow[i] = mode->suspend_supported && mode->suspend_enabled; + } + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) { + void *rs_limits = NULL; + int ret = -ENODEV; + uint32_t power; + + 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 (pm_sleep_ops.lowest_limits) + rs_limits = pm_sleep_ops.lowest_limits(false, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, -1, + -1, &power); + + if (rs_limits) { + if (pm_sleep_ops.enter_sleep) + ret = pm_sleep_ops.enter_sleep( + msm_pm_max_sleep_time, + rs_limits, false, true); + if (!ret) { + int collapsed = msm_pm_power_collapse(false); + if (pm_sleep_ops.exit_sleep) { + pm_sleep_ops.exit_sleep(rs_limits, + false, true, collapsed); + } + } + } else { + pr_err("%s: cannot find the lowest power limit\n", + __func__); + } + time = msm_pm_timer_exit_suspend(time, period); + msm_pm_add_stat(MSM_PM_STAT_SUSPEND, time); + } 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_RETENTION]) { + if (MSM_PM_DEBUG_SUSPEND & msm_pm_debug_mask) + pr_info("%s: retention\n", __func__); + msm_pm_retention(); + } 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, +}; + +/****************************************************************************** + * Initialization routine + *****************************************************************************/ +void __init msm_pm_init_sleep_status_data( + struct msm_pm_sleep_status_data *data) +{ + msm_pm_slp_sts = data; +} + +void msm_pm_set_sleep_ops(struct msm_pm_sleep_ops *ops) +{ + if (ops) + pm_sleep_ops = *ops; +} + +static int __init msm_pm_init(void) +{ + pgd_t *pc_pgd; + pmd_t *pmd; + unsigned long pmdval; + enum msm_pm_time_stats_id enable_stats[] = { + MSM_PM_STAT_IDLE_WFI, + MSM_PM_STAT_RETENTION, + MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_POWER_COLLAPSE, + MSM_PM_STAT_SUSPEND, + }; + unsigned long exit_phys; + + /* Page table for cores to come back up safely. */ + pc_pgd = pgd_alloc(&init_mm); + if (!pc_pgd) + return -ENOMEM; + + exit_phys = virt_to_phys(msm_pm_collapse_exit); + + pmd = pmd_offset(pud_offset(pc_pgd + pgd_index(exit_phys),exit_phys), + exit_phys); + pmdval = (exit_phys & PGDIR_MASK) | + PMD_TYPE_SECT | PMD_SECT_AP_WRITE; + pmd[0] = __pmd(pmdval); + pmd[1] = __pmd(pmdval + (1 << (PGDIR_SHIFT - 1))); + + msm_saved_state_phys = + allocate_contiguous_ebi_nomap(CPU_SAVED_STATE_SIZE * + num_possible_cpus(), 4); + if (!msm_saved_state_phys) + return -ENOMEM; + msm_saved_state = ioremap_nocache(msm_saved_state_phys, + CPU_SAVED_STATE_SIZE * + num_possible_cpus()); + if (!msm_saved_state) + return -ENOMEM; + + /* 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); + clean_caches((unsigned long)&msm_pm_pc_pgd, sizeof(msm_pm_pc_pgd), + virt_to_phys(&msm_pm_pc_pgd)); + + msm_pm_mode_sysfs_add(); + msm_pm_add_stats(enable_stats, ARRAY_SIZE(enable_stats)); + + msm_spm_allow_x_cpu_set_vdd(false); + + suspend_set_ops(&msm_pm_ops); + msm_pm_qtimer_available(); + msm_cpuidle_init(); + + return 0; +} + +late_initcall(msm_pm_init); diff --git a/arch/arm/mach-msm/pm-boot.c b/arch/arm/mach-msm/pm-boot.c new file mode 100644 index 0000000000000000000000000000000000000000..f6105af135af3dbc0e1e8a44373846310d8dcfba --- /dev/null +++ b/arch/arm/mach-msm/pm-boot.c @@ -0,0 +1,272 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "scm-boot.h" +#include "idle.h" +#include "pm-boot.h" + +static uint32_t *msm_pm_reset_vector; +static uint32_t saved_vector[2]; +static void (*msm_pm_boot_before_pc)(unsigned int cpu, unsigned long entry); +static void (*msm_pm_boot_after_pc)(unsigned int cpu); + +static void msm_pm_write_boot_vector(unsigned int cpu, unsigned long address) +{ + msm_pm_boot_vector[cpu] = address; + clean_caches((unsigned long)&msm_pm_boot_vector[cpu], + sizeof(msm_pm_boot_vector[cpu]), + virt_to_phys(&msm_pm_boot_vector[cpu])); +} + +#ifdef CONFIG_MSM_SCM +static int __init msm_pm_tz_boot_init(void) +{ + int flag = 0; + if (num_possible_cpus() == 1) + flag = SCM_FLAG_WARMBOOT_CPU0; + else if (num_possible_cpus() == 2) + flag = SCM_FLAG_WARMBOOT_CPU0 | SCM_FLAG_WARMBOOT_CPU1; + else if (num_possible_cpus() == 4) + flag = SCM_FLAG_WARMBOOT_CPU0 | SCM_FLAG_WARMBOOT_CPU1 | + SCM_FLAG_WARMBOOT_CPU2 | SCM_FLAG_WARMBOOT_CPU3; + else + __WARN(); + + return scm_set_boot_addr((void *)virt_to_phys(msm_pm_boot_entry), flag); +} + +static void msm_pm_config_tz_before_pc(unsigned int cpu, + unsigned long entry) +{ + msm_pm_write_boot_vector(cpu, entry); +} +#else +static int __init msm_pm_tz_boot_init(void) +{ + return 0; +}; + +static inline void msm_pm_config_tz_before_pc(unsigned int cpu, + unsigned long entry) {} +#endif + +static int __init msm_pm_boot_reset_vector_init(uint32_t *reset_vector) +{ + if (!reset_vector) + return -ENODEV; + msm_pm_reset_vector = reset_vector; + mb(); + + return 0; +} + +static void msm_pm_config_rst_vector_before_pc(unsigned int cpu, + unsigned long entry) +{ + 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] = entry; +} + +static void msm_pm_config_rst_vector_after_pc(unsigned int cpu) +{ + msm_pm_reset_vector[0] = saved_vector[0]; + msm_pm_reset_vector[1] = saved_vector[1]; +} + +void msm_pm_boot_config_before_pc(unsigned int cpu, unsigned long entry) +{ + if (msm_pm_boot_before_pc) + msm_pm_boot_before_pc(cpu, entry); +} + +void msm_pm_boot_config_after_pc(unsigned int cpu) +{ + if (msm_pm_boot_after_pc) + msm_pm_boot_after_pc(cpu); +} +#define BOOT_REMAP_ENABLE BIT(0) + +int __init msm_pm_boot_init(struct msm_pm_boot_platform_data *pdata) +{ + int ret = 0; + unsigned long entry; + void __iomem *warm_boot_ptr; + + switch (pdata->mode) { + case MSM_PM_BOOT_CONFIG_TZ: + ret = msm_pm_tz_boot_init(); + msm_pm_boot_before_pc = msm_pm_config_tz_before_pc; + msm_pm_boot_after_pc = NULL; + break; + case MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS: + pdata->v_addr = ioremap(pdata->p_addr, PAGE_SIZE); + /* Fall through */ + case MSM_PM_BOOT_CONFIG_RESET_VECTOR_VIRT: + + if (!pdata->v_addr) + return -ENODEV; + + ret = msm_pm_boot_reset_vector_init(pdata->v_addr); + msm_pm_boot_before_pc + = msm_pm_config_rst_vector_before_pc; + msm_pm_boot_after_pc + = msm_pm_config_rst_vector_after_pc; + break; + case MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR: + if (!cpu_is_msm8625()) { + void *remapped; + + /* + * Set the boot remap address and enable remapping of + * reset vector + */ + if (!pdata->p_addr || !pdata->v_addr) + return -ENODEV; + + remapped = ioremap_nocache(pdata->p_addr, SZ_8); + ret = msm_pm_boot_reset_vector_init(remapped); + + __raw_writel((pdata->p_addr | BOOT_REMAP_ENABLE), + pdata->v_addr); + + msm_pm_boot_before_pc + = msm_pm_config_rst_vector_before_pc; + msm_pm_boot_after_pc + = msm_pm_config_rst_vector_after_pc; + } else { + warm_boot_ptr = ioremap_nocache( + MSM8625_WARM_BOOT_PHYS, SZ_64); + ret = msm_pm_boot_reset_vector_init(warm_boot_ptr); + + entry = virt_to_phys(msm_pm_boot_entry); + + /* Below sequence is a work around for cores + * to come out of GDFS properly on 8625 target. + * On 8625 while cores coming out of GDFS observed + * the memory corruption at very first memory read. + */ + msm_pm_reset_vector[0] = 0xE59F000C; /* ldr r0, 0x14 */ + msm_pm_reset_vector[1] = 0xE59F1008; /* ldr r1, 0x14 */ + msm_pm_reset_vector[2] = 0xE1500001; /* cmp r0, r1 */ + msm_pm_reset_vector[3] = 0x1AFFFFFB; /* bne 0x0 */ + msm_pm_reset_vector[4] = 0xE12FFF10; /* bx r0 */ + msm_pm_reset_vector[5] = entry; /* 0x14 */ + + /* Here upper 16bits[16:31] used by CORE1 + * lower 16bits[0:15] used by CORE0 + */ + entry = (MSM8625_WARM_BOOT_PHYS | + ((MSM8625_WARM_BOOT_PHYS & 0xFFFF0000) >> 16)); + + /* write 'entry' to boot remapper register */ + __raw_writel(entry, (pdata->v_addr + + MPA5_BOOT_REMAP_ADDR)); + + /* Enable boot remapper for C0 [bit:25th] */ + __raw_writel(readl_relaxed(pdata->v_addr + + MPA5_CFG_CTL_REG) | BIT(25), + pdata->v_addr + MPA5_CFG_CTL_REG); + + /* Enable boot remapper for C1 [bit:26th] */ + __raw_writel(readl_relaxed(pdata->v_addr + + MPA5_CFG_CTL_REG) | BIT(26), + pdata->v_addr + MPA5_CFG_CTL_REG); + msm_pm_boot_before_pc = msm_pm_write_boot_vector; + } + break; + default: + __WARN(); + } + + return ret; +} + +static int __devinit msm_pm_boot_probe(struct platform_device *pdev) +{ + struct msm_pm_boot_platform_data pdata; + char *key = NULL; + uint32_t val = 0; + int ret = 0; + int flag = 0; + + key = "qcom,mode"; + ret = of_property_read_u32(pdev->dev.of_node, key, &val); + if (ret) { + pr_err("Unable to read boot mode Err(%d).\n", ret); + return -ENODEV; + } + pdata.mode = val; + + key = "qcom,phy-addr"; + ret = of_property_read_u32(pdev->dev.of_node, key, &val); + if (ret && pdata.mode == MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS) + goto fail; + if (!ret) { + pdata.p_addr = val; + flag++; + } + + key = "qcom,virt-addr"; + ret = of_property_read_u32(pdev->dev.of_node, key, &val); + if (ret && pdata.mode == MSM_PM_BOOT_CONFIG_RESET_VECTOR_VIRT) + goto fail; + if (!ret) { + pdata.v_addr = (void *)val; + flag++; + } + + if (pdata.mode == MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR && (flag != 2)) { + key = "addresses for boot remap"; + goto fail; + } + + return msm_pm_boot_init(&pdata); + +fail: + pr_err("Error reading %s\n", key); + return -EFAULT; +} + +static struct of_device_id msm_pm_match_table[] = { + {.compatible = "qcom,pm-boot"}, + {}, +}; + +static struct platform_driver msm_pm_boot_driver = { + .probe = msm_pm_boot_probe, + .driver = { + .name = "pm-boot", + .owner = THIS_MODULE, + .of_match_table = msm_pm_match_table, + }, +}; + +static int __init msm_pm_boot_module_init(void) +{ + return platform_driver_register(&msm_pm_boot_driver); +} +module_init(msm_pm_boot_module_init); diff --git a/arch/arm/mach-msm/pm-boot.h b/arch/arm/mach-msm/pm-boot.h new file mode 100644 index 0000000000000000000000000000000000000000..30b67c216ca8edb09342996a407c1833edefee69 --- /dev/null +++ b/arch/arm/mach-msm/pm-boot.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_BOOT_H +#define _ARCH_ARM_MACH_MSM_PM_BOOT_H + +/* 8x25 specific macros */ +#define MPA5_CFG_CTL_REG 0x30 +#define MPA5_BOOT_REMAP_ADDR 0x34 +/* end */ + +enum { + MSM_PM_BOOT_CONFIG_TZ = 0, + MSM_PM_BOOT_CONFIG_RESET_VECTOR_PHYS = 1, + MSM_PM_BOOT_CONFIG_RESET_VECTOR_VIRT = 2, + MSM_PM_BOOT_CONFIG_REMAP_BOOT_ADDR = 3, +}; + +struct msm_pm_boot_platform_data { + int mode; + phys_addr_t p_addr; + void __iomem *v_addr; +}; + +#ifdef CONFIG_PM +int __init msm_pm_boot_init(struct msm_pm_boot_platform_data *pdata); +#else +static inline int __init msm_pm_boot_init( + struct msm_pm_boot_platform_data *pdata) +{ + return 0; +} +#endif + +void msm_pm_boot_config_before_pc(unsigned int cpu, unsigned long entry); +void msm_pm_boot_config_after_pc(unsigned int cpu); + +#endif diff --git a/arch/arm/mach-msm/pm-data.c b/arch/arm/mach-msm/pm-data.c new file mode 100644 index 0000000000000000000000000000000000000000..6f4743f58980f083cad68016563c1621601f4d39 --- /dev/null +++ b/arch/arm/mach-msm/pm-data.c @@ -0,0 +1,128 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include "pm.h" + +struct msm_pm_platform_data msm_pm_sleep_modes[] = { + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_RETENTION)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 0, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_RETENTION)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 0, + .idle_enabled = 1, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 0, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_RETENTION)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 0, + .idle_enabled = 1, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 0, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_RETENTION)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + }, + + [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 0, + .idle_enabled = 1, + .suspend_enabled = 0, + }, +}; diff --git a/arch/arm/mach-msm/pm-stats.c b/arch/arm/mach-msm/pm-stats.c new file mode 100644 index 0000000000000000000000000000000000000000..936820a9a2cdf73f9d8d4e3a6fb9c2e458a1e455 --- /dev/null +++ b/arch/arm/mach-msm/pm-stats.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "pm.h" + +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; + bool enabled; +}; + +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. + */ +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; + + if (!stats[id].enabled) + goto add_bail; + + 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; + + if (i >= CONFIG_MSM_IDLE_STATS_BUCKET_COUNT) + 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; + +add_bail: + 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; + + /* Skip the disabled ones */ + if (!stats[id].enabled) { + *p = '\0'; + p++; + goto again; + } + + 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]); + +again: + *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 < sizeof(MSM_PM_STATS_RESET)) { + ret = -EINVAL; + goto write_proc_failed; + } + + if (copy_from_user(buf, buffer, sizeof(MSM_PM_STATS_RESET))) { + ret = -EFAULT; + goto write_proc_failed; + } + + if (memcmp(buf, MSM_PM_STATS_RESET, sizeof(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 + +void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats, int size) +{ + unsigned int cpu; + struct proc_dir_entry *d_entry; + int i = 0; + + 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_SPIN].name = "idle-spin"; + stats[MSM_PM_STAT_IDLE_SPIN].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_RETENTION].name = "retention"; + stats[MSM_PM_STAT_RETENTION].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_FAILED_STANDALONE_POWER_COLLAPSE].name = + "idle-failed-standalone-power-collapse"; + stats[MSM_PM_STAT_IDLE_FAILED_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_IDLE_FAILED_POWER_COLLAPSE].name = + "idle-failed-power-collapse"; + stats[MSM_PM_STAT_IDLE_FAILED_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; + + stats[MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend"; + stats[MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; + + stats[MSM_PM_STAT_NOT_IDLE].name = "not-idle"; + stats[MSM_PM_STAT_NOT_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; + + for (i = 0; i < size; i++) + stats[enable_stats[i]].enabled = true; + + } + + 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; + } +} diff --git a/arch/arm/mach-msm/pm.h b/arch/arm/mach-msm/pm.h new file mode 100644 index 0000000000000000000000000000000000000000..70d54da8cf2b30538a05f1c460b59220864f5497 --- /dev/null +++ b/arch/arm/mach-msm/pm.h @@ -0,0 +1,141 @@ +/* arch/arm/mach-msm/pm.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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 void msm_secondary_startup(void); +#else +#define msm_secondary_startup NULL +#endif + +extern int power_collapsed; + +struct msm_pm_irq_calls { + unsigned int (*irq_pending)(void); + int (*idle_sleep_allowed)(void); + void (*enter_sleep1)(bool modem_wake, int from_idle, uint32_t + *irq_mask); + int (*enter_sleep2)(bool modem_wake, int from_idle); + void (*exit_sleep1)(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs); + void (*exit_sleep2)(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs); + void (*exit_sleep3)(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs); +}; + +enum msm_pm_sleep_mode { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT = 0, + MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT = 1, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE = 2, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE = 3, + MSM_PM_SLEEP_MODE_APPS_SLEEP = 4, + MSM_PM_SLEEP_MODE_RETENTION = MSM_PM_SLEEP_MODE_APPS_SLEEP, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND = 5, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN = 6, + 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 */ +}; + +extern struct msm_pm_platform_data msm_pm_sleep_modes[]; + +struct msm_pm_sleep_status_data { + void *base_addr; + uint32_t cpu_offset; + uint32_t mask; +}; + +struct msm_pm_sleep_ops { + void *(*lowest_limits)(bool from_idle, + enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us, + uint32_t sleep_us, uint32_t *power); + int (*enter_sleep)(uint32_t sclk_count, void *limits, + bool from_idle, bool notify_rpm); + void (*exit_sleep)(void *limits, bool from_idle, + bool notify_rpm, bool collapsed); +}; + +void msm_pm_set_platform_data(struct msm_pm_platform_data *data, int count); +int msm_pm_idle_prepare(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index); +void msm_pm_set_irq_extns(struct msm_pm_irq_calls *irq_calls); +int msm_pm_idle_enter(enum msm_pm_sleep_mode sleep_mode); +void msm_pm_cpu_enter_lowpower(unsigned int cpu); + +void __init msm_pm_init_sleep_status_data( + struct msm_pm_sleep_status_data *sleep_data); + + +#ifdef CONFIG_MSM_PM8X60 +void msm_pm_set_rpm_wakeup_irq(unsigned int irq); +int msm_pm_wait_cpu_shutdown(unsigned int cpu); +bool msm_pm_verify_cpu_pc(unsigned int cpu); +void msm_pm_set_sleep_ops(struct msm_pm_sleep_ops *ops); +#else +static inline void msm_pm_set_rpm_wakeup_irq(unsigned int irq) {} +static inline int msm_pm_wait_cpu_shutdown(unsigned int cpu) { return 0; } +static inline bool msm_pm_verify_cpu_pc(unsigned int cpu) { return true; } +static inline void msm_pm_set_sleep_ops(struct msm_pm_sleep_ops *ops) {} +#endif +#ifdef CONFIG_HOTPLUG_CPU +int msm_platform_secondary_init(unsigned int cpu); +#else +static inline int msm_platform_secondary_init(unsigned int cpu) { return 0; } +#endif + +enum msm_pm_time_stats_id { + MSM_PM_STAT_REQUESTED_IDLE = 0, + MSM_PM_STAT_IDLE_SPIN, + MSM_PM_STAT_IDLE_WFI, + MSM_PM_STAT_RETENTION, + MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE, + 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 +}; + +#ifdef CONFIG_MSM_IDLE_STATS +void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats, int size); +void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t); +#else +static inline void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats, + int size) {} +static inline void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t) {} +#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 0000000000000000000000000000000000000000..bac6ef8e4bd4115bf35b7a96a477386d8960cd99 --- /dev/null +++ b/arch/arm/mach-msm/pm2.c @@ -0,0 +1,1756 @@ +/* arch/arm/mach-msm/pm2.c + * + * MSM Power Management Routines + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2012 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 +#ifdef CONFIG_HAS_WAKELOCK +#include +#endif +#include +#include +#ifdef CONFIG_CPU_V7 +#include +#include +#endif +#include +#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 +#include +#include + +#include "smd_private.h" +#include "smd_rpcrouter.h" +#include "acpuclock.h" +#include "clock.h" +#include "idle.h" +#include "irq.h" +#include "gpio.h" +#include "timer.h" +#include "pm.h" +#include "spm.h" +#include "sirc.h" +#include "pm-boot.h" +#include "devices-msm7x2xa.h" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +enum { + MSM_PM_DEBUG_SUSPEND = BIT(0), + MSM_PM_DEBUG_POWER_COLLAPSE = BIT(1), + MSM_PM_DEBUG_STATE = BIT(2), + MSM_PM_DEBUG_CLOCK = BIT(3), + MSM_PM_DEBUG_RESET_VECTOR = BIT(4), + MSM_PM_DEBUG_SMSM_STATE = BIT(5), + MSM_PM_DEBUG_IDLE = BIT(6), + MSM_PM_DEBUG_HOTPLUG = BIT(7), +}; + +static int msm_pm_debug_mask; +int power_collapsed; +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_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_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 msm_pm_irq_calls *msm_pm_irq_extns; + +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]; +}; + +/* + * 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); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_LATENCY])) { + u32 arg = mode->latency; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_RESIDENCY])) { + u32 arg = mode->residency; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } + + break; + } + + if (ret > 0) { + strlcat(buf, "\n", PAGE_SIZE); + 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); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_LATENCY])) { + kp.arg = &mode->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 = &mode->residency; + ret = param_set_ulong(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 = NULL; + 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) { + printk(KERN_ERR + "%s: cannot create kobject attribute group\n", + __func__); + goto mode_sysfs_add_cpu_exit; + } + } + + ret = 0; + +mode_sysfs_add_cpu_exit: + if (ret) { + if (mode && mode->kobj) + kobject_del(mode->kobj); + kfree(mode); + } + + return ret; +} + +/* + * 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; + unsigned int cpu; + 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_exit; + } + + 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_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; +} + +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_irq_extns(struct msm_pm_irq_calls *irq_calls) +{ + /* sanity check */ + BUG_ON(irq_calls == NULL || irq_calls->irq_pending == NULL || + irq_calls->idle_sleep_allowed == NULL || + irq_calls->enter_sleep1 == NULL || + irq_calls->enter_sleep2 == NULL || + irq_calls->exit_sleep1 == NULL || + irq_calls->exit_sleep2 == NULL || + irq_calls->exit_sleep3 == NULL); + + msm_pm_irq_extns = irq_calls; +} + +/****************************************************************************** + * Sleep Limitations + *****************************************************************************/ +enum { + SLEEP_LIMIT_NONE = 0, + SLEEP_LIMIT_NO_TCXO_SHUTDOWN = 2, + SLEEP_LIMIT_MASK = 0x03, +}; + +static uint32_t msm_pm_sleep_limit = SLEEP_LIMIT_NONE; +#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_APCS_GCC_BASE + 0x020) +#define APPS_PWRDOWN (MSM_ACC0_BASE + 0x01c) +#define APPS_SECOP (MSM_TCSR_BASE + 0x038) +#define APPS_STANDBY_CTL NULL +#else +#define APPS_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c) +#define APPS_PWRDOWN (MSM_CSR_BASE + 0x440) +#define APPS_STANDBY_CTL (MSM_CSR_BASE + 0x108) +#define APPS_SECOP NULL +#endif + +/* + * Configure hardware registers in preparation for Apps power down. + */ +static void msm_pm_config_hw_before_power_down(void) +{ + if (cpu_is_msm7x30() || cpu_is_msm8x55()) { + __raw_writel(4, APPS_SECOP); + } else if (cpu_is_msm7x27()) { + __raw_writel(0x1f, APPS_CLK_SLEEP_EN); + } else if (cpu_is_msm7x27a() || cpu_is_msm7x27aa() || + cpu_is_msm7x25a() || cpu_is_msm7x25aa() || + cpu_is_msm7x25ab()) { + __raw_writel(0x7, APPS_CLK_SLEEP_EN); + } else if (cpu_is_qsd8x50()) { + __raw_writel(0x1f, APPS_CLK_SLEEP_EN); + mb(); + __raw_writel(0, APPS_STANDBY_CTL); + } + mb(); + __raw_writel(1, APPS_PWRDOWN); + mb(); +} + +/* + * Program the top csr from core0 context to put the + * core1 into GDFS, as core1 is not running yet. + */ +static void configure_top_csr(void) +{ + void __iomem *base_ptr; + unsigned int value = 0; + + base_ptr = core1_reset_base(); + if (!base_ptr) + return; + + /* bring the core1 out of reset */ + __raw_writel(0x3, base_ptr); + mb(); + /* + * override DBGNOPOWERDN and program the GDFS + * count val + */ + + __raw_writel(0x00030002, (MSM_CFG_CTL_BASE + 0x38)); + mb(); + + /* Initialize the SPM0 and SPM1 registers */ + msm_spm_reinit(); + + /* enable TCSR for core1 */ + value = __raw_readl((MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG)); + value |= BIT(22); + __raw_writel(value, MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG); + mb(); + + /* set reset bit for SPM1 */ + value = __raw_readl((MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG)); + value |= BIT(20); + __raw_writel(value, MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG); + mb(); + + /* set CLK_OFF bit */ + value = __raw_readl((MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG)); + value |= BIT(18); + __raw_writel(value, MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG); + mb(); + + /* set clamps bit */ + value = __raw_readl((MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG)); + value |= BIT(21); + __raw_writel(value, MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG); + mb(); + + /* set power_up bit */ + value = __raw_readl((MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG)); + value |= BIT(19); + __raw_writel(value, MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG); + mb(); + + /* Disable TSCR for core0 */ + value = __raw_readl((MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG)); + value &= ~BIT(22); + __raw_writel(value, MSM_CFG_CTL_BASE + MPA5_CFG_CTL_REG); + mb(); + __raw_writel(0x0, base_ptr); + mb(); +} + +/* + * Clear hardware registers after Apps powers up. + */ +static void msm_pm_config_hw_after_power_up(void) +{ + + if (cpu_is_msm7x30() || cpu_is_msm8x55()) { + __raw_writel(0, APPS_SECOP); + mb(); + __raw_writel(0, APPS_PWRDOWN); + mb(); + msm_spm_reinit(); + } else if (cpu_is_msm8625()) { + __raw_writel(0, APPS_PWRDOWN); + mb(); + + if (power_collapsed) { + /* + * enable the SCU while coming out of power + * collapse. + */ + scu_enable(MSM_SCU_BASE); + /* + * Program the top csr to put the core1 into GDFS. + */ + configure_top_csr(); + } + } else { + __raw_writel(0, APPS_PWRDOWN); + mb(); + __raw_writel(0, APPS_CLK_SLEEP_EN); + mb(); + } +} + +/* + * Configure hardware registers in preparation for SWFI. + */ +static void msm_pm_config_hw_before_swfi(void) +{ + if (cpu_is_qsd8x50()) { + __raw_writel(0x1f, APPS_CLK_SLEEP_EN); + mb(); + } else if (cpu_is_msm7x27()) { + __raw_writel(0x0f, APPS_CLK_SLEEP_EN); + mb(); + } else if (cpu_is_msm7x27a() || cpu_is_msm7x27aa() || + cpu_is_msm7x25a() || cpu_is_msm7x25aa() || + cpu_is_msm7x25ab()) { + __raw_writel(0x7, APPS_CLK_SLEEP_EN); + mb(); + } +} + +/* + * 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); + + +/****************************************************************************** + * 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 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; + int collapsed = 0; + int ret; + int val; + int modem_early_exit = 0; + + 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)); + + if (cpu_is_msm8625()) { + /* Program the SPM */ + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE, + false); + WARN_ON(ret); + } + + msm_pm_irq_extns->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_pm_irq_extns->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; + } + + msm_pm_boot_config_before_pc(smp_processor_id(), + virt_to_phys(msm_pm_collapse_exit)); + +#ifdef CONFIG_VFP + if (from_idle) + vfp_pm_suspend(); +#endif + +#ifdef CONFIG_CACHE_L2X0 + if (!cpu_is_msm8625()) + l2cc_suspend(); + else + apps_power_collapse = 1; +#endif + + collapsed = msm_pm_collapse(); + + /* + * TBD: Currently recognise the MODEM early exit + * path by reading the MPA5_GDFS_CNT_VAL register. + */ + if (cpu_is_msm8625()) { + /* + * on system reset, default value of MPA5_GDFS_CNT_VAL + * is = 0x0, later modem reprogram this value to + * 0x00030004. Once APPS did a power collapse and + * coming out of it expected value of this register + * always be 0x00030004. Incase if APPS sees the value + * as 0x00030002 consider this case as a modem early + * exit. + */ + val = __raw_readl(MSM_CFG_CTL_BASE + 0x38); + if (val != 0x00030002) + power_collapsed = 1; + else + modem_early_exit = 1; + } + +#ifdef CONFIG_CACHE_L2X0 + if (!cpu_is_msm8625()) + l2cc_resume(); + else + apps_power_collapse = 0; +#endif + + msm_pm_boot_config_after_pc(smp_processor_id()); + + if (collapsed) { +#ifdef CONFIG_VFP + if (from_idle) + vfp_pm_resume(); +#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_pm_irq_extns->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 && !modem_early_exit) { + 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_pm_irq_extns->exit_sleep2(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + msm_pm_irq_extns->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(); + + if (cpu_is_msm8625()) { + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, + false); + WARN_ON(ret); + } + + 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: + if (cpu_is_msm8625()) { + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, + false); + WARN_ON(ret); + } + + return ret; +} + +/* + * Power collapse the Apps processor without involving Modem. + * + * Return value: + * 0: success + */ +static int __ref msm_pm_power_collapse_standalone(bool from_idle) +{ + int collapsed = 0; + int ret; + void *entry; + + 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); + + entry = (!smp_processor_id() || from_idle) ? + msm_pm_collapse_exit : msm_secondary_startup; + + msm_pm_boot_config_before_pc(smp_processor_id(), + virt_to_phys(entry)); + +#ifdef CONFIG_VFP + vfp_pm_suspend(); +#endif + +#ifdef CONFIG_CACHE_L2X0 + if (!cpu_is_msm8625()) + l2cc_suspend(); +#endif + + collapsed = msm_pm_collapse(); + +#ifdef CONFIG_CACHE_L2X0 + if (!cpu_is_msm8625()) + l2cc_resume(); +#endif + + msm_pm_boot_config_after_pc(smp_processor_id()); + + if (collapsed) { +#ifdef CONFIG_VFP + vfp_pm_resume(); +#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 !collapsed; +} + +/* + * 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; + } + + if (!cpu_is_msm8625()) + 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; +} + +static int64_t msm_pm_timer_enter_suspend(int64_t *period) +{ + int time = 0; + + time = msm_timer_get_sclk_time(period); + if (!time) + pr_err("%s: Unable to read sclk.\n", __func__); + return time; +} + +static int64_t msm_pm_timer_exit_suspend(int64_t time, int64_t period) +{ + + 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; + } + return time; +} + +/****************************************************************************** + * 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; + + int64_t timer_expiration; + int latency_qos; + int ret; + int i; + unsigned int cpu; + int64_t t1; + static DEFINE_PER_CPU(int64_t, t2); + int exit_stat; + + if (!atomic_read(&msm_pm_init_done)) + return; + + cpu = smp_processor_id(); + latency_qos = pm_qos_request(PM_QOS_CPU_DMA_LATENCY); + /* get the next timer expiration */ + timer_expiration = ktime_to_ns(tick_nohz_get_sleep_length()); + + t1 = ktime_to_ns(ktime_get()); + msm_pm_add_stat(MSM_PM_STAT_NOT_IDLE, t1 - __get_cpu_var(t2)); + msm_pm_add_stat(MSM_PM_STAT_REQUESTED_IDLE, timer_expiration); + exit_stat = MSM_PM_STAT_IDLE_SPIN; + + for (i = 0; i < ARRAY_SIZE(allow); i++) + allow[i] = true; + + if (num_online_cpus() > 1 || + (timer_expiration < msm_pm_idle_sleep_min_time) || +#ifdef CONFIG_HAS_WAKELOCK + has_wake_lock(WAKE_LOCK_IDLE) || +#endif + !msm_pm_irq_extns->idle_sleep_allowed()) { + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + } + + for (i = 0; i < ARRAY_SIZE(allow); i++) { + struct msm_pm_platform_data *mode = + &msm_pm_modes[MSM_PM_MODE(cpu, 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]) { + /* Sync the timer with SCLK, it is needed only for modem + * assissted pollapse case. + */ + int64_t next_timer_exp = msm_timer_enter_idle(); + uint32_t sleep_delay; + bool low_power = false; + + sleep_delay = (uint32_t) msm_pm_convert_and_cap_time( + next_timer_exp, 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); + msm_timer_exit_idle(low_power); + + 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; + } + } else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) { + ret = msm_pm_power_collapse_standalone(true); + exit_stat = ret ? + MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE : + MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE; + } else if (allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]) { + ret = msm_pm_swfi(true); + if (ret) + while (!msm_pm_irq_extns->irq_pending()) + udelay(1); + exit_stat = ret ? MSM_PM_STAT_IDLE_SPIN : MSM_PM_STAT_IDLE_WFI; + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + msm_pm_swfi(false); + exit_stat = MSM_PM_STAT_IDLE_WFI; + } else { + while (!msm_pm_irq_extns->irq_pending()) + udelay(1); + exit_stat = MSM_PM_STAT_IDLE_SPIN; + } + + __get_cpu_var(t2) = ktime_to_ns(ktime_get()); + msm_pm_add_stat(exit_stat, __get_cpu_var(t2) - t1); +} + +/* + * Suspend the Apps processor. + * + * Return value: + * -EPERM: Suspend happened by a not permitted core + * -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 = -EPERM; + int i; + int64_t period = 0; + int64_t time = 0; + + /* Must executed by CORE0 */ + if (smp_processor_id()) { + __WARN(); + goto suspend_exit; + } + + time = msm_pm_timer_enter_suspend(&period); + + 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; + + for (i = 0; i < ARRAY_SIZE(allow); i++) { + struct msm_pm_platform_data *mode; + mode = &msm_pm_modes[MSM_PM_MODE(0, i)]; + if (!mode->suspend_supported || !mode->suspend_enabled) + allow[i] = false; + } + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] || + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) { + enum msm_pm_time_stats_id id; + + 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); + + if (ret) + id = MSM_PM_STAT_FAILED_SUSPEND; + else { + id = MSM_PM_STAT_SUSPEND; + msm_pm_sleep_limit = sleep_limit; + } + + time = msm_pm_timer_exit_suspend(time, period); + msm_pm_add_stat(id, time); + } else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) { + ret = msm_pm_power_collapse_standalone(false); + } else if (allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]) { + ret = msm_pm_swfi(true); + if (ret) + while (!msm_pm_irq_extns->irq_pending()) + udelay(1); + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + msm_pm_swfi(false); + } + +suspend_exit: + 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, +}; + +/* Hotplug the "non boot" CPU's and put + * the cores into low power mode + */ +void msm_pm_cpu_enter_lowpower(unsigned int cpu) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + int i; + + 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; + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_HOTPLUG, KERN_INFO, + "CPU%u: %s: shutting down cpu\n", cpu, __func__); + + 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(false); + } else { + MSM_PM_DPRINTK(MSM_PM_DEBUG_HOTPLUG, KERN_INFO, + "CPU%u: %s: shutting down failed!!!\n", cpu, __func__); + } +} + +/****************************************************************************** + * 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) +{ + int ret; + int val; + enum msm_pm_time_stats_id enable_stats[] = { + 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_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE, + MSM_PM_STAT_SUSPEND, + MSM_PM_STAT_FAILED_SUSPEND, + MSM_PM_STAT_NOT_IDLE, + }; + +#ifdef CONFIG_CPU_V7 + pgd_t *pc_pgd; + pmd_t *pmd; + unsigned long pmdval; + unsigned long exit_phys; + + exit_phys = virt_to_phys(msm_pm_collapse_exit); + + /* Page table for cores to come back up safely. */ + pc_pgd = pgd_alloc(&init_mm); + if (!pc_pgd) + return -ENOMEM; + pmd = pmd_offset(pud_offset(pc_pgd + pgd_index(exit_phys), exit_phys), + exit_phys); + pmdval = (exit_phys & PGDIR_MASK) | + PMD_TYPE_SECT | PMD_SECT_AP_WRITE; + pmd[0] = __pmd(pmdval); + pmd[1] = __pmd(pmdval + (1 << (PGDIR_SHIFT - 1))); + + msm_saved_state_phys = + allocate_contiguous_ebi_nomap(CPU_SAVED_STATE_SIZE * + num_possible_cpus(), 4); + if (!msm_saved_state_phys) + return -ENOMEM; + msm_saved_state = ioremap_nocache(msm_saved_state_phys, + CPU_SAVED_STATE_SIZE * + num_possible_cpus()); + if (!msm_saved_state) + return -ENOMEM; + + /* 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); + clean_caches((unsigned long)&msm_pm_pc_pgd, sizeof(msm_pm_pc_pgd), + virt_to_phys(&msm_pm_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; + } + + 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; + } + + if (cpu_is_msm8625()) { + target_type = TARGET_IS_8625; + clean_caches((unsigned long)&target_type, sizeof(target_type), + virt_to_phys(&target_type)); + + /* + * Configure the MPA5_GDFS_CNT_VAL register for + * DBGPWRUPEREQ_OVERRIDE[17:16] = Override the + * DBGNOPOWERDN for each cpu. + * MPA5_GDFS_CNT_VAL[9:0] = Delay counter for + * GDFS control. + */ + val = 0x00030002; + __raw_writel(val, (MSM_CFG_CTL_BASE + 0x38)); + + l2x0_base_addr = MSM_L2CC_BASE; + } + +#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); + + suspend_set_ops(&msm_pm_ops); + + msm_pm_mode_sysfs_add(); + msm_pm_add_stats(enable_stats, ARRAY_SIZE(enable_stats)); + + atomic_set(&msm_pm_init_done, 1); + 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 0000000000000000000000000000000000000000..1e927109ad0c284c1bc5174723160a6f1547540f --- /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; + bp->len += 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], MSM_RPC_UNINTERRUPTIBLE); + + 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; + + 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 0000000000000000000000000000000000000000..e6ef960d9772c5f501d2f4eb81b8c5aa531e8e24 --- /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 + +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/pmic_debugfs.c b/arch/arm/mach-msm/pmic_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..c52cf9b14c2e21112a0b78776cfa0e97656b3c97 --- /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 0000000000000000000000000000000000000000..1f82468171c3e1455049cf31df69c036d23d0483 --- /dev/null +++ b/arch/arm/mach-msm/pmu.c @@ -0,0 +1,62 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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 = ARRAY_SIZE(l2_pmu_resource), +}; + +#endif + +static struct platform_device cpu_pmu_device = { + .name = "cpu-arm-pmu", + .id = ARM_PMU_DEVICE_CPU, + .resource = cpu_pmu_resource, + .num_resources = ARRAY_SIZE(cpu_pmu_resource), +}; + +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 9980dc736e7bb690c31f93560efcdd2925d74ffe..421e7defe8255578b205fe6dcf1f8b0b669abbd1 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-2012, 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 -#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_APCS_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 __devinit 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_test.c b/arch/arm/mach-msm/proc_comm_test.c new file mode 100644 index 0000000000000000000000000000000000000000..e4eca119e9acc5c3bda24939cf85c3b9237673b2 --- /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 + +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/proccomm-regulator.c b/arch/arm/mach-msm/proccomm-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..21a4f846fe3e45aae75fd9dd42b800ca2dd9e79c --- /dev/null +++ b/arch/arm/mach-msm/proccomm-regulator.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "proccomm-regulator.h" + +#define MV_TO_UV(mv) ((mv)*1000) +#define UV_TO_MV(uv) (((uv)+999)/1000) + +/* + * Wrappers for the msm_proc_comm() calls. + * Does basic impedance matching between what the proccomm interface + * expects and how the driver sees the world. + */ + +/* Converts a proccomm error to an errno value. */ +static int _pcom_err_to_linux_errno(unsigned error) +{ + if (!error) /* 0 == no error */ + return 0; + else if (error & 0x1F) /* bits 0..4 => parameter 1..5 out of range */ + return -EDOM; + else if (error & 0x100) /* bit 8 => feature not supported */ + return -ENOSYS; + else /* anything else non-zero: unknown error */ + return -EINVAL; +} + +/* vreg_switch: (vreg ID, on/off) => (return code, ) */ +static int _vreg_switch(int vreg_id, bool enable) +{ + unsigned _id = (unsigned)vreg_id; + unsigned _enable = !!enable; + + return msm_proc_comm(PCOM_VREG_SWITCH, &_id, &_enable); +} + +/* vreg_set_level: (vreg ID, mV) => (return code, ) */ +static int _vreg_set_level(int vreg_id, int level_mV) +{ + unsigned _id = (unsigned)vreg_id; + unsigned _level = (unsigned)level_mV; + int rc; + + rc = msm_proc_comm(PCOM_VREG_SET_LEVEL, &_id, &_level); + + if (rc) + return rc; + + return _pcom_err_to_linux_errno(_id); +} + +/* vreg_pull_down: (pull down, vreg ID) => (, ) */ +/* Returns error code from msm_proc_comm. */ +static int _vreg_pull_down(int vreg_id, bool pull_down) +{ + unsigned _id = (unsigned)vreg_id; + unsigned _enable = !!pull_down; + + return msm_proc_comm(PCOM_VREG_PULLDOWN, &_enable, &_id); +} + +struct proccomm_regulator_drvdata { + struct regulator_desc rdesc; + int rise_time; + int last_voltage; + bool enabled; + bool negative; +}; + +static int proccomm_vreg_enable(struct regulator_dev *rdev) +{ + struct proccomm_regulator_drvdata *ddata; + int rc; + + ddata = rdev_get_drvdata(rdev); + rc = _vreg_switch(rdev_get_id(rdev), VREG_SWITCH_ENABLE); + + if (rc) { + dev_err(rdev_get_dev(rdev), + "could not enable regulator %d (%s): %d\n", + rdev_get_id(rdev), ddata->rdesc.name, rc); + } else { + dev_dbg(rdev_get_dev(rdev), + "enabled regulator %d (%s)\n", + rdev_get_id(rdev), ddata->rdesc.name); + ddata->enabled = 1; + } + + return rc; +} + +static int proccomm_vreg_disable(struct regulator_dev *rdev) +{ + struct proccomm_regulator_drvdata *ddata; + int rc; + + ddata = rdev_get_drvdata(rdev); + rc = _vreg_switch(rdev_get_id(rdev), VREG_SWITCH_DISABLE); + + if (rc) { + dev_err(rdev_get_dev(rdev), + "could not disable regulator %d (%s): %d\n", + rdev_get_id(rdev), ddata->rdesc.name, rc); + } else { + dev_dbg(rdev_get_dev(rdev), + "disabled regulator %d (%s)\n", + rdev_get_id(rdev), ddata->rdesc.name); + ddata->enabled = 0; + } + + return rc; +} + +static int proccomm_vreg_is_enabled(struct regulator_dev *rdev) +{ + struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); + + return ddata->enabled; +} + +static int proccomm_vreg_rise_time(struct regulator_dev *rdev) +{ + struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); + + return ddata->rise_time; +} + +static int proccomm_vreg_get_voltage(struct regulator_dev *rdev) +{ + + struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); + + return MV_TO_UV(ddata->last_voltage); +} + +static int proccomm_vreg_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *sel) +{ + struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); + int level_mV = UV_TO_MV(min_uV); + int rc; + + rc = _vreg_set_level(rdev_get_id(rdev), + ddata->negative ? -level_mV : level_mV); + + if (rc) { + dev_err(rdev_get_dev(rdev), + "could not set voltage for regulator %d (%s) " + "to %d mV: %d\n", + rdev_get_id(rdev), ddata->rdesc.name, level_mV, rc); + } else { + dev_dbg(rdev_get_dev(rdev), + "voltage for regulator %d (%s) set to %d mV\n", + rdev_get_id(rdev), ddata->rdesc.name, level_mV); + ddata->last_voltage = level_mV; + } + + return rc; +} + +static struct regulator_ops proccomm_regulator_ops = { + .enable = proccomm_vreg_enable, + .disable = proccomm_vreg_disable, + .is_enabled = proccomm_vreg_is_enabled, + .get_voltage = proccomm_vreg_get_voltage, + .set_voltage = proccomm_vreg_set_voltage, + .enable_time = proccomm_vreg_rise_time, +}; + +/* + * Create and register a struct regulator_dev based on the information in + * a struct proccomm_regulator_info. + * Fills in the rdev field in struct proccomm_regulator_info. + */ +static struct regulator_dev *__devinit create_proccomm_rdev( + struct proccomm_regulator_info *info, struct device *parent) +{ + const char *name; + struct proccomm_regulator_drvdata *d; + struct regulator_dev *rdev; + int rc = 0; + + if (info->id < 0) { + dev_err(parent, "invalid regulator id %d\n", info->id); + rc = -EINVAL; + goto out; + } + + name = info->init_data.constraints.name; + + if (!name) { + dev_err(parent, + "could not register regulator with id %d: " + "no name specified\n", info->id); + rc = -EINVAL; + goto out; + } + + if (info->pulldown > 0) { + rc = _vreg_pull_down(info->id, info->pulldown); + if (rc) { + dev_err(parent, + "probing for regulator %d (%s) failed\n", + info->id, name); + goto out; + } + } + + d = kzalloc(sizeof(*d), GFP_KERNEL); + + if (!d) { + dev_err(parent, + "could not allocate struct proccomm_regulator_drvdata " + "for regulator %d (%s)\n", info->id, name); + rc = -ENOMEM; + goto out; + } + + d->rdesc.name = name; + d->rdesc.id = info->id; + d->rdesc.ops = &proccomm_regulator_ops; + d->rdesc.type = REGULATOR_VOLTAGE; + d->rdesc.owner = THIS_MODULE; + d->rise_time = info->rise_time; + d->enabled = 0; + d->negative = info->negative; + d->rdesc.n_voltages = info->n_voltages; + + rdev = regulator_register(&d->rdesc, parent, &info->init_data, d, NULL); + + if (IS_ERR(rdev)) { + rc = PTR_ERR(rdev); + dev_err(parent, "error registering regulator %d (%s): %d\n", + info->id, name, rc); + goto clean; + } + + dev_dbg(parent, "registered regulator %d (%s)\n", info->id, name); + + return rdev; + +clean: + kfree(d); +out: + return ERR_PTR(rc); +} + +/* + * Unregister and destroy a struct regulator_dev created by + * create_proccomm_rdev. + */ +static void destroy_proccomm_rdev(struct regulator_dev *rdev) +{ + struct proccomm_regulator_drvdata *d; + + if (!rdev) + return; + + d = rdev_get_drvdata(rdev); + + regulator_unregister(rdev); + + dev_dbg(rdev_get_dev(rdev)->parent, + "unregistered regulator %d (%s)\n", + d->rdesc.id, d->rdesc.name); + + kfree(d); +} + + +static int __devinit proccomm_vreg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct proccomm_regulator_platform_data *pdata = dev->platform_data; + struct regulator_dev **rdevs; + int rc = 0; + size_t i = 0; + + if (!pdata) { + dev_err(dev, "invalid platform data\n"); + rc = -EINVAL; + goto check_fail; + } + + if (pdata->nregs == 0) { + dev_err(dev, "registering an empty regulator list; " + "this is probably not what you want\n"); + rc = -EINVAL; + goto check_fail; + } + + rdevs = kcalloc(pdata->nregs, sizeof(*rdevs), GFP_KERNEL); + + if (!rdevs) { + dev_err(dev, "could not allocate storage for " + "struct regulator_dev array\n"); + rc = -ENOMEM; + goto check_fail; + } + + platform_set_drvdata(pdev, rdevs); + + dev_dbg(dev, "registering %d proccomm regulators\n", pdata->nregs); + + for (i = 0; i < pdata->nregs; i++) { + rdevs[i] = create_proccomm_rdev(&pdata->regs[i], dev); + if (IS_ERR(rdevs[i])) { + rc = PTR_ERR(rdevs[i]); + goto backout; + } + } + + dev_dbg(dev, "%d proccomm regulators registered\n", pdata->nregs); + + return rc; + +backout: + while (--i >= 0) + destroy_proccomm_rdev(rdevs[i]); + + kfree(rdevs); + +check_fail: + return rc; +} + +static int __devexit proccomm_vreg_remove(struct platform_device *pdev) +{ + struct proccomm_regulator_platform_data *pdata; + struct regulator_dev **rdevs; + size_t i; + + pdata = pdev->dev.platform_data; + rdevs = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->nregs; i++) + destroy_proccomm_rdev(rdevs[i]); + + kfree(rdevs); + + return 0; +} + +static struct platform_driver proccomm_vreg_driver = { + .probe = proccomm_vreg_probe, + .remove = __devexit_p(proccomm_vreg_remove), + .driver = { + .name = PROCCOMM_REGULATOR_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init proccomm_vreg_init(void) +{ + return platform_driver_register(&proccomm_vreg_driver); +} +postcore_initcall(proccomm_vreg_init); + +static void __exit proccomm_vreg_exit(void) +{ + platform_driver_unregister(&proccomm_vreg_driver); +} +module_exit(proccomm_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ProcComm regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PROCCOMM_REGULATOR_DEV_NAME); diff --git a/arch/arm/mach-msm/proccomm-regulator.h b/arch/arm/mach-msm/proccomm-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..46d3b13d4c2fc3d384d151434070bec89e7d1559 --- /dev/null +++ b/arch/arm/mach-msm/proccomm-regulator.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_PROCCOMM_REGULATOR_H__ +#define __ARCH_ARM_MACH_MSM_PROCCOMM_REGULATOR_H__ + +#include + +#define PROCCOMM_REGULATOR_DEV_NAME "proccomm-regulator" + +/** + * struct proccomm_regulator_info - A description of one proccomm regulator + * @init_data: Initialization data for the regulator. + * Must contain: + * - A list of struct regulator_consumer_supply indicating + * supply names for the regulator + * - A filled out struct regulation_constraints containing: + * - The name of the regulator + * - The minimum and maximum voltages supported + * - The supported modes (REGULATOR_MODE_NORMAL) + * - The supported operations, currently limited to: + * REGULATOR_CHANGE_STATUS + * REGULATOR_CHANGE_VOLTAGE + * - The input voltage, if the regulator is powered by another + * - Properly set always_on, boot_on, and apply_uV flags + * - The name of the supply regulator, if applicable + * @id: The proccomm ID of this regulator. + * @rise_time: The time that the regulator takes to initialize, + * in microseconds. Set to 0 to disable rise-time checking. + * @pulldown: Whether the regulator should be pulled down when off. + * 1 to pull down the regulator. + * 0 to leave the regulator floating. + * -1 to indicate no preference. + */ +struct proccomm_regulator_info { + struct regulator_init_data init_data; + int id; + int rise_time; + int pulldown; + int negative; + int n_voltages; +}; + +/** + * struct proccomm_regulator_platform_data - proccomm driver platform data. + * + * Contains a description of a set of proccomm-controlled regulators. + * Pass this in the platform_data field when instantiating the driver. + * + * @regs: An array of struct proccomm_regulator_info describing + * the regulators to register. + * @nregs: The number of regulators to register. + */ +struct proccomm_regulator_platform_data { + struct proccomm_regulator_info *regs; + size_t nregs; +}; + +#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 + +#endif diff --git a/arch/arm/mach-msm/qdsp5/Makefile b/arch/arm/mach-msm/qdsp5/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..2ce0031afd98e02312659e5833a6db9599fa7c5d --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/Makefile @@ -0,0 +1,20 @@ +obj-y += adsp.o adsp_driver.o adsp_info.o adsp_rm.o dsp_debug.o adsp_debug.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 +obj-$(CONFIG_DEBUG_FS) += audio_voice_lb.o +else +obj-y += audio_in.o snd_pcm_client.o +endif +obj-$(CONFIG_MSM7X27A_AUDIO) += audio_aac_in.o 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 +obj-$(CONFIG_ARCH_MSM7X27A) += audio_mvs.o +obj-$(CONFIG_ARCH_MSM7X27A) += audio_lpa.o diff --git a/arch/arm/mach-msm/qdsp5/adsp.c b/arch/arm/mach-msm/qdsp5/adsp.c new file mode 100644 index 0000000000000000000000000000000000000000..6f5ccbfafa83fb415d0506508693c08201f509a1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp.c @@ -0,0 +1,1457 @@ +/* arch/arm/mach-msm/qdsp5/adsp.c + * + * Register/Interrupt access for userspace aDSP library. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2012, 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 + +#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 "adsp.h" + +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); + +static struct workqueue_struct *msm_adsp_probe_work_queue; +static void adsp_probe_work(struct work_struct *work); +static DECLARE_WORK(msm_adsp_probe_work, adsp_probe_work); + +/* 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; + + mutex_lock(&adsp_info.lock); + 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"); + mutex_unlock(&adsp_info.lock); + return -ETIMEDOUT; + + } + init_info_cmd_sent++; + } + mutex_unlock(&adsp_info.lock); + + 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) + return -EINVAL; + + if (module->clk && clk_rate) + return clk_set_rate(module->clk, clk_rate); + + return -EINVAL; +} + +int msm_adsp_generate_event(void *data, + struct msm_adsp_module *mod, + unsigned event_id, + unsigned event_length, + unsigned event_size, + void *msg) +{ + unsigned long flags; + void (*func)(void *, size_t); + + if (!mod) + return -EINVAL; + + if (event_size == sizeof(uint32_t)) + func = read_event_32; + else if (event_size == sizeof(uint16_t)) + func = read_event_16; + else + return -EINVAL; + + spin_lock_irqsave(&adsp_cmd_lock, flags); + read_event_addr = msg; + read_event_size = event_length; + mod->ops->event(data, event_id, event_length, func); + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + return 0; +} + +int msm_adsp_enable(struct msm_adsp_module *module) +{ + int rc = 0; + + if (!module) + return -EINVAL; + + 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_prepare_enable(module->clk); + + mutex_lock(&adsp_open_lock); + if (adsp_open_count++ == 0) { + enable_irq(adsp_info.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; + + if (!module) + return -EINVAL; + + 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; + + if (!module) + return -EINVAL; + + 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_unprepare(module->clk); + mutex_lock(&adsp_open_lock); + if (--adsp_open_count == 0) { + disable_irq(adsp_info.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; + + if (!module) + return -EINVAL; + + 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; + + adsp_info.int_adsp = platform_get_irq(pdev, 0); + if (adsp_info.int_adsp < 0) { + MM_ERR("no irq resource?\n"); + return -ENODEV; + } + + 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); + mutex_init(&adsp_info.lock); + + rc = request_irq(adsp_info.int_adsp, adsp_irq_handler, + IRQF_TRIGGER_RISING, "adsp", 0); + if (rc < 0) + goto fail_request_irq; + disable_irq(adsp_info.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; + } + + /* schedule start of kernel thread later using work queue */ + queue_work(msm_adsp_probe_work_queue, &msm_adsp_probe_work); + + 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_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(adsp_info.int_adsp); + free_irq(adsp_info.int_adsp, 0); +fail_request_irq: + kfree(adsp_modules); + kfree(adsp_info.init_info_ptr); + return rc; +} + +static void adsp_probe_work(struct work_struct *work) +{ + /* start the kernel thread to process the callbacks */ + kthread_run(adsp_rpc_thread, NULL, "kadspd"); +} + +#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 const 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); + } + 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 + + msm_adsp_probe_work_queue = create_workqueue("msm_adsp_probe"); + if (msm_adsp_probe_work_queue == NULL) + return -ENOMEM; + 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 0000000000000000000000000000000000000000..0f16111c9a95a76deb843a3e1b3cc79bbf3c4b50 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp.h @@ -0,0 +1,356 @@ +/* arch/arm/mach-msm/qdsp5/adsp.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2010, 2012 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); +void q5audio_dsp_not_responding(void); + +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; + struct mutex lock; + + /* Interrupt value */ + int int_adsp; +}; + +#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 0000000000000000000000000000000000000000..322ba68cb36f65fedd474eb4ae3f2e9c0371115f --- /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 0000000000000000000000000000000000000000..f947cd7a86d387e03eb3d0f39dceee5bb50c460e --- /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 0000000000000000000000000000000000000000..6f8d3f43bf2e87a0a2a917a581813bae0fc8428f --- /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_debug.c b/arch/arm/mach-msm/qdsp5/adsp_debug.c new file mode 100644 index 0000000000000000000000000000000000000000..03deab9f1584520e64bef70e708d5b8a10ed569e --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_debug.c @@ -0,0 +1,97 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "adsp.h" + +#define MAX_LEN 64 +#ifdef CONFIG_DEBUG_FS +static struct dentry *adsp_dentry; +#endif +static char l_buf[MAX_LEN]; +static unsigned int crash_enable; + +static ssize_t q5_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_DBG("q5 debugfs opened\n"); + return 0; +} + +static ssize_t q5_debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int len; + + if (count < 0) + return 0; + len = count > (MAX_LEN - 1) ? (MAX_LEN - 1) : count; + if (copy_from_user(l_buf, buf, len)) { + MM_INFO("Unable to copy data from user space\n"); + return -EFAULT; + } + l_buf[len] = 0; + if (l_buf[len - 1] == '\n') { + l_buf[len - 1] = 0; + len--; + } + if (!strncmp(l_buf, "boom", MAX_LEN)) { + q5audio_dsp_not_responding(); + } else if (!strncmp(l_buf, "enable", MAX_LEN)) { + crash_enable = 1; + MM_INFO("Crash enabled : %d\n", crash_enable); + } else if (!strncmp(l_buf, "disable", MAX_LEN)) { + crash_enable = 0; + MM_INFO("Crash disabled : %d\n", crash_enable); + } else + MM_INFO("Unknown Command\n"); + + return count; +} + +static const struct file_operations q5_debug_fops = { + .write = q5_debug_write, + .open = q5_debug_open, +}; + +static int __init q5_debug_init(void) +{ +#ifdef CONFIG_DEBUG_FS + adsp_dentry = debugfs_create_file("q5_debug", S_IFREG | S_IRUGO, + NULL, (void *) NULL, &q5_debug_fops); +#endif /* CONFIG_DEBUG_FS */ + return 0; +} +device_initcall(q5_debug_init); + 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 0000000000000000000000000000000000000000..6860d84f446778b32f79ce06bfecaab69e973615 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_driver.c @@ -0,0 +1,668 @@ +/* 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 + +#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 0000000000000000000000000000000000000000..dea52bbf3678845d181557739dacd2808b1dc5cc --- /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-2012 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, NULL, 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 0000000000000000000000000000000000000000..8fb2e06c79cbdbb4d5e275ab49043c7aaa777837 --- /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 0000000000000000000000000000000000000000..87d5dc389dff579d7645f2c1d2bb89e3bee56462 --- /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 0000000000000000000000000000000000000000..06b70de38ed796d1520dc0a0a3f31dbaa2935330 --- /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 0000000000000000000000000000000000000000..81147f700d348ce764e2e164bf85a8a0eecd05de --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_rm.c @@ -0,0 +1,193 @@ +/* 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 "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 0000000000000000000000000000000000000000..68ae3802ecdec0e68b7b5538f2ba32ad499976a3 --- /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 0000000000000000000000000000000000000000..dcd3d968af80f2468a54edfd5b02676bfa7773d1 --- /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 0000000000000000000000000000000000000000..492fa0edd9504d07aca1658deeb47a57de9edaa3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c @@ -0,0 +1,272 @@ +/* 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 + +#define MAX_FLUSH_SIZE 160 + +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, col_addr = 0, skip; + int 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; + /*Invalidate cache before accessing the cached pmem buffer*/ + if (filp) { + pmem_addr.vaddr = subframe_pkt_addr; + pmem_addr.length = (((subframe_pkt_size*2) + 31) & (~31)) + 32; + pmem_addr.offset = offset; + if (pmem_cache_maint (filp, PMEM_INV_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 -EINVAL; + } + } + /* 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 = 16; + skip = 0; + start_pos = 6; + col_addr = 17; + } + 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 (col_addr) { + frame_buffer_high += 2; + frame_buffer_low += 2; + /* Patch the Co-located buffers.*/ + frame_buffer_size = (72 * frame_header_pkt[xdim_pos] * + frame_header_pkt[ydim_pos]) >> 16; + ptr_to_high_low_short((void *)frame_buffer_size, + &frame_buffer_size_high, + &frame_buffer_size_low); + for (i = 0; i < col_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; + } + } + /*Flush the cached pmem subframe packet before sending to DSP*/ + if (filp) { + pmem_addr.vaddr = subframe_pkt_addr; + pmem_addr.length = MAX_FLUSH_SIZE; + 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 0000000000000000000000000000000000000000..936b7af81bb753ae667982b8178172c603d72973 --- /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 0000000000000000000000000000000000000000..725819f85d6bb0056dcb1463fa7bb453d4533a07 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_aac.c @@ -0,0 +1,1915 @@ +/* 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-2012 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 + +#include "audmgr.h" + +#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; + void *map_v_read; + void *map_v_write; + + /* 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; + audio->stopped = 1; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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_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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap(audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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 = allocate_contiguous_ebi_nomap(PCM_BUFSZ_MIN * + PCM_BUF_MAX_COUNT, SZ_4K); + if (!audio->read_phys) { + MM_ERR("could not allocate read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->map_v_read = ioremap(audio->read_phys, + PCM_BUFSZ_MIN * PCM_BUF_MAX_COUNT); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + free_contiguous_memory_by_paddr(audio->read_phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->read_data = audio->map_v_read; + 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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_aac_in.c b/arch/arm/mach-msm/qdsp5/audio_aac_in.c new file mode 100644 index 0000000000000000000000000000000000000000..79a828a97a1377d3339953d27723bbccf3a8713b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_aac_in.c @@ -0,0 +1,1459 @@ +/* arch/arm/mach-msm/qdsp5/audio_aac_in.c + * + * aac audio input device + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This code is based in part on arch/arm/mach-msm/qdsp5v2/audio_aac_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 "audmgr.h" + +#include +#include +#include +#include + +#include +#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 AAC_FRAME_SIZE 1536 /* 36 bytes data */ +/*Tunnel mode : 1536 bytes data + 8 byte header*/ +#define FRAME_SIZE (AAC_FRAME_SIZE + FRAME_HEADER_SIZE) +/* 1536 bytes data + 24 meta field*/ +#define NT_FRAME_SIZE (AAC_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 (32 * 1024 + NT_FRAME_HEADER_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 */ + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t used; + uint32_t mfield_sz; +}; + +struct audio_aac_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 (1536 bytes) */ + uint32_t bit_rate; /* bit rate for AAC */ + uint32_t record_quality; /* record quality (bits/sample/channel) */ + uint32_t enc_type; /* 1 for AAC */ + uint32_t mode; /* T or NT 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 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; + void *map_v_read; + void *map_v_write; + + 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 aac_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 audaac_in_dsp_enable(struct audio_aac_in *audio, int enable); +static int audaac_in_encparam_config(struct audio_aac_in *audio); +static int audaac_in_encmem_config(struct audio_aac_in *audio); +static int audaac_in_dsp_read_buffer(struct audio_aac_in *audio, + uint32_t read_cnt); +static void audaac_in_flush(struct audio_aac_in *audio); + +static void audaac_in_get_dsp_frames(struct audio_aac_in *audio); +static int audpcm_config(struct audio_aac_in *audio); +static void audaac_out_flush(struct audio_aac_in *audio); +static int audaac_in_routing_mode_config(struct audio_aac_in *audio); +static void audrec_pcm_send_data(struct audio_aac_in *audio, unsigned needed); +static void audaac_nt_in_get_dsp_frames(struct audio_aac_in *audio); +static void audaac_in_flush(struct audio_aac_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; + } +} + +/* 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; +} + +/* must be called with audio->lock held */ +static int audaac_in_enable(struct audio_aac_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_AAC; + 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; + audaac_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audaac_in_disable(struct audio_aac_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + + audaac_in_dsp_enable(audio, 0); + + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + audio->stopped = 1; + wake_up(&audio->wait); + 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 audaac_in_get_dsp_frames(struct audio_aac_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++; + + audaac_in_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_aac_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_aac_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_aac_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 audaac_in_routing_mode_config(struct audio_aac_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_aac_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"); + audaac_in_routing_mode_config(audio); + } else { + audaac_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 + audaac_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) + audaac_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"); + audaac_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); + + audaac_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); + + audaac_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); + } +} + +struct msm_adsp_ops audpre_aac_adsp_ops = { + .event = audpre_dsp_event, +}; + +struct msm_adsp_ops audrec_aac_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audaac_in_dsp_enable(struct audio_aac_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 audaac_in_encmem_config(struct audio_aac_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:1536 bytes aac packet + 4 halfword header + * NT:1536 bytes aac 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 += (AAC_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 audaac_in_encparam_config(struct audio_aac_in *audio) +{ + struct audrec_cmd_arecparam_aac_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; + cmd.rec_quality = audio->record_quality; + + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audaac_flush_command(struct audio_aac_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 audaac_in_dsp_read_buffer(struct audio_aac_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 audaac_ioport_reset(struct audio_aac_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->wait); + mutex_lock(&audio->read_lock); + audaac_in_flush(audio); + mutex_unlock(&audio->read_lock); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audaac_out_flush(audio); + mutex_unlock(&audio->write_lock); +} + +static void audaac_in_flush(struct audio_aac_in *audio) +{ + int i; + unsigned long flags; + + audio->dsp_cnt = 0; + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + 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_aac_in *audio) +{ + int i; + unsigned long flags; + + audio->out_head = 0; + audio->out_count = 0; + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out_tail = 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; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static long audaac_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_aac_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 = 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: { + rc = audaac_in_disable(audio); + 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_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 != (AAC_FRAME_SIZE + 14)) + rc = -EINVAL; + break; + } + audio->buffer_size = cfg.buffer_size; + 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; + 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_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); + 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_aac_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_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 aac_encoded_meta_out) - \ + sizeof(uint16_t))); + meta_field.metadata_len = + sizeof(struct aac_encoded_meta_out); + if (copy_to_user((char *)start, (char *)&meta_field, + sizeof(struct aac_encoded_meta_out))) { + rc = -EFAULT; + break; + } + if (nt_frame->nflag_lsw & 0x0001) { + MM_DBG("recieved EOS in read call\n"); + audio->eos_ack = 1; + } + buf += sizeof(struct aac_encoded_meta_out); + count -= sizeof(struct aac_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); + audaac_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_aac_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 audaac_in_fsync(struct file *file, loff_t a, loff_t b, int datasync) + +{ + struct audio_aac_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_aac_process_eos(struct audio_aac_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 audaac_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_aac_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 = 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_AAC_EOS_FLG_OFFSET] & + AUDPREPROC_AAC_EOS_FLG_MASK) { + eos_condition = AUDPREPROC_AAC_EOS_SET; + MM_DBG("EOS SET\n"); + if (mfield_size == count) { + buf += mfield_size; + eos_condition = 0; + goto exit; + } 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) + audrec_pcm_send_data(audio, 0); + else { + audrec_pcm_send_data(audio, 1); + audio->flush_ack = 0; + } + if (eos_condition == AUDPREPROC_AAC_EOS_SET) + rc = audrec_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_aac_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audaac_in_disable(audio); + audaac_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)) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->out_phys); + audio->out_data = NULL; + } + + if (audio->data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +struct audio_aac_in the_audio_aac_in; + +static int audaac_in_open(struct inode *inode, struct file *file) +{ + struct audio_aac_in *audio = &the_audio_aac_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_11025; + audio->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_11025; + + /* 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; + + audio->channel_mode = AUDREC_CMD_STEREO_MODE_MONO; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audio->buffer_size = (AAC_FRAME_SIZE + 14); + else + audio->buffer_size = (FRAME_SIZE - 8); + audio->enc_type = AUDREC_CMD_TYPE_0_INDEX_AAC | audio->mode; + + 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_aac_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_aac_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; + + audaac_in_flush(audio); + audaac_out_flush(audio); + + audio->phys = allocate_contiguous_ebi_nomap(dma_size, SZ_4K); + if (audio->phys) { + audio->map_v_read = ioremap( + audio->phys, dma_size); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map DMA buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto evt_error; + } + audio->data = audio->map_v_read; + } else { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + goto evt_error; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + + audio->out_data = NULL; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + audio->out_phys = allocate_contiguous_ebi_nomap(BUFFER_SIZE, + SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + goto evt_error; + } else { + audio->map_v_write = ioremap( + audio->out_phys, BUFFER_SIZE); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address\n"); + rc = -ENOMEM; + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + free_contiguous_memory_by_paddr(\ + audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + MM_DBG("wr 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_aac_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, +}; + +static struct miscdevice audaac_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac_in", + .fops = &audio_aac_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); + 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(&audaac_in_misc); +} +device_initcall(audaac_in_init); 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 0000000000000000000000000000000000000000..1a1002e705fcec4fce9600035f8ea732506177de --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_amrnb.c @@ -0,0 +1,1630 @@ +/* linux/arch/arm/mach-msm/qdsp5/audio_amrnb.c + * + * amrnb audio decoder device + * + * Copyright (c) 2008-2009, 2011-2012 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 +#include +#include +#include + +#include "audmgr.h" + +#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 */ + void *map_v_read; + void *map_v_write; + + + 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; + audio->stopped = 1; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + atomic_set(&audio->out_bytes, 0); +} + +static void audamrnb_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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); + 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 = allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read buf\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap( + audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, freeing \ + instance 0x%08x freeing\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..ac2b5ca9fc81cb9abed80ba5ef1d5398be79d430 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_amrnb_in.c @@ -0,0 +1,1484 @@ +/* arch/arm/mach-msm/qdsp5/audio_amrnb_in.c + * + * amrnb encoder device + * + * Copyright (c) 2009, 2011-2012 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 +#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 AMRNB_FRAME_SIZE 36 /* 36 bytes data */ +/*Tunnel mode : 1536 bytes data + 8 byte header*/ +#define FRAME_SIZE (AMRNB_FRAME_SIZE + FRAME_HEADER_SIZE) +/* 1536 bytes data + 24 meta field*/ +#define NT_FRAME_SIZE (AMRNB_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_AMRNB_EOS_FLG_OFFSET 0x0A +#define AUDPREPROC_AMRNB_EOS_FLG_MASK 0x01 +#define AUDPREPROC_AMRNB_EOS_NONE 0x0 /* No EOS detected */ +#define AUDPREPROC_AMRNB_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_amrnb_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; + uint8_t mfield; /* meta field embedded in data */ + uint8_t wflush; /*write flush */ + uint8_t rflush; /*read flush*/ + uint32_t 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; + uint32_t enc_type; /* 0 for WAV ,1 for AAC,10 for AMRNB */ + uint32_t mode; /* T or NT Mode*/ + 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() */ + + 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; + void *map_v_write; + + uint8_t opened; + uint8_t enabled; + uint8_t running; + uint8_t 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 amrnb_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 audamrnb_in_dsp_enable(struct audio_amrnb_in *audio, int enable); +static int audamrnb_in_encparam_config(struct audio_amrnb_in *audio); +static int audamrnb_in_encmem_config(struct audio_amrnb_in *audio); +static int audamrnb_in_dsp_read_buffer(struct audio_amrnb_in *audio, + uint32_t read_cnt); +static void audamrnb_in_flush(struct audio_amrnb_in *audio); + +static void audamrnb_in_get_dsp_frames(struct audio_amrnb_in *audio); +static int audpcm_config(struct audio_amrnb_in *audio); +static void audamrnb_out_flush(struct audio_amrnb_in *audio); +static int audamrnb_in_routing_mode_config(struct audio_amrnb_in *audio); +static void audrec_pcm_send_data(struct audio_amrnb_in *audio, unsigned needed); +static void audamrnb_nt_in_get_dsp_frames(struct audio_amrnb_in *audio); +static void audamrnb_in_flush(struct audio_amrnb_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 audamrnb_in_enable(struct audio_amrnb_in *audio) +{ + struct audmgr_config cfg; + int32_t 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_AMR_NB; + 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; + audamrnb_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audamrnb_in_disable(struct audio_amrnb_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + + audamrnb_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 audamrnb_in_get_dsp_frames(struct audio_amrnb_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); + + /* Send Complete Transcoded Data, not actual frame part */ + audio->in[index].size = FRAME_SIZE - (sizeof(*frame)); + /* 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++; + + audamrnb_in_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audamrnb_nt_in_get_dsp_frames(struct audio_amrnb_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_amrnb_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_amrnb_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 audamrnb_in_routing_mode_config(struct audio_amrnb_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_amrnb_in *audio = data; + 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"); + audamrnb_in_routing_mode_config(audio); + } else { + audamrnb_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 + audamrnb_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) + audamrnb_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"); + audamrnb_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); + + audamrnb_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); + + audamrnb_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); + } +} + +struct msm_adsp_ops audpre_amrnb_adsp_ops = { + .event = audpre_dsp_event, +}; + +struct msm_adsp_ops audrec_amrnb_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audamrnb_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 & 0xFF) | + (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 audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audamrnb_in_encmem_config(struct audio_amrnb_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + uint8_t n; + uint16_t 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 amrnb packet + 4 halfword header + * NT:36 bytes amrnb 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 += (AMRNB_FRAME_SIZE/2) + header_len; + MM_DBG("0x%8x\n", (uint32_t)(audio->in[n].data - header_len*2)); + } + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audamrnb_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 = audio->samp_rate_index; /* 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 audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audamrnb_flush_command(struct audio_amrnb_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 audamrnb_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 audio_send_queue_recbs(audio, &cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ + +static void audamrnb_ioport_reset(struct audio_amrnb_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); + audamrnb_in_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audamrnb_out_flush(audio); + mutex_unlock(&audio->read_lock); +} + +static void audamrnb_in_flush(struct audio_amrnb_in *audio) +{ + uint8_t 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 audamrnb_out_flush(struct audio_amrnb_in *audio) +{ + uint8_t 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 audamrnb_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_amrnb_in *audio = file->private_data; + int32_t 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 = audamrnb_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_INFO("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 = audamrnb_in_disable(audio); + audio->stopped = 1; + break; + } + case AUDIO_FLUSH: { + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audamrnb_ioport_reset(audio); + if (audio->running) { + audamrnb_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)); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + cfg.buffer_size = OUT_BUFFER_SIZE; + cfg.buffer_count = OUT_FRAME_NUM; + } else { + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = 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 != (AMRNB_FRAME_SIZE + 14)) { + rc = -EINVAL; + break; + } + } + audio->buffer_size = cfg.buffer_size; + 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 audamrnb_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; + int32_t rc = 0; + struct amrnb_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 amrnb_encoded_meta_out) - \ + sizeof(uint16_t))); + meta_field.metadata_len = + sizeof(struct amrnb_encoded_meta_out); + if (copy_to_user((char *)start, (char *)&meta_field, + sizeof(struct amrnb_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 amrnb_encoded_meta_out); + count -= sizeof(struct amrnb_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); + audamrnb_in_dsp_read_buffer(audio, + audio->dsp_cnt++); + } + } + } else { + MM_ERR("short read\n"); + break; + } + + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static void audrec_pcm_send_data(struct audio_amrnb_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 audamrnb_in_fsync(struct file *file, loff_t a, loff_t b, int datasync) + +{ + struct audio_amrnb_in *audio = file->private_data; + int32_t 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_amrnb_process_eos(struct audio_amrnb_in *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int32_t 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 audamrnb_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_amrnb_in *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + char *cpy_ptr; + int32_t rc = 0, eos_condition = AUDPREPROC_AMRNB_EOS_NONE; + unsigned short mfield_size = 0; + int32_t 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_AMRNB_EOS_FLG_OFFSET] & + AUDPREPROC_AMRNB_EOS_FLG_MASK) { + eos_condition = AUDPREPROC_AMRNB_EOS_SET; + MM_DBG("EOS SET\n"); + if (mfield_size == count) { + buf += mfield_size; + eos_condition = 0; + goto exit; + } else + cpy_ptr[AUDPREPROC_AMRNB_EOS_FLG_OFFSET] &= + ~AUDPREPROC_AMRNB_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_AMRNB_EOS_SET) + rc = audrec_amrnb_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + return write_count; +error: + mutex_unlock(&audio->write_lock); + return rc; +} + +static int audamrnb_in_release(struct inode *inode, struct file *file) +{ + struct audio_amrnb_in *audio = file->private_data; + int32_t dma_size = 0; + mutex_lock(&audio->lock); + audamrnb_in_disable(audio); + audamrnb_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)) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->out_phys); + audio->out_data = NULL; + } + if (audio->data) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + dma_size = DMASZ; + else + dma_size = NT_DMASZ; + + dma_free_coherent(NULL, + dma_size, audio->data, audio->phys); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +struct audio_amrnb_in the_audio_amrnb_in; + +static int audamrnb_in_open(struct inode *inode, struct file *file) +{ + struct audio_amrnb_in *audio = &the_audio_amrnb_in; + int32_t rc; + int encid; + int32_t 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 = (AMRNB_FRAME_SIZE + 14); + else + audio->buffer_size = (FRAME_SIZE - 8); + audio->enc_type = AUDREC_CMD_TYPE_0_INDEX_AMRNB | audio->mode; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = audmgr_open(&audio->audmgr); + if (rc) + goto done; + } + 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; + + 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; + } + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = msm_adsp_get("AUDPREPROCTASK", &audio->audpre, + &audpre_amrnb_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; + + audamrnb_in_flush(audio); + audamrnb_out_flush(audio); + /* used dma_allco_coherent for backward compatibility with 7x27 */ + audio->data = dma_alloc_coherent(NULL, dma_size, + &audio->phys, GFP_KERNEL); + if (!audio->data) { + MM_ERR("Unable to allocate DMA buffer\n"); + goto evt_error; + } + + audio->out_data = NULL; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + audio->out_phys = allocate_contiguous_ebi_nomap(BUFFER_SIZE, + SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + dma_free_coherent(NULL, + dma_size, audio->data, audio->phys); + goto evt_error; + } else { + audio->map_v_write = ioremap( + audio->out_phys, BUFFER_SIZE); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address\n"); + rc = -ENOMEM; + dma_free_coherent(NULL, + dma_size, audio->data, audio->phys); + free_contiguous_memory_by_paddr(\ + audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + MM_DBG("wr buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, + (uint32_t)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", + (uint32_t)audio->out[0].data, + (uint32_t)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_fops = { + .owner = THIS_MODULE, + .open = audamrnb_in_open, + .release = audamrnb_in_release, + .read = audamrnb_in_read, + .write = audamrnb_in_write, + .fsync = audamrnb_in_fsync, + .unlocked_ioctl = audamrnb_in_ioctl, +}; + +struct miscdevice audamrnb_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 int32_t debug_bufmax = 1024; + static char buffer[1024]; + int32_t 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 audamrnb_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); + init_waitqueue_head(&the_audio_amrnb_in.wait_enable); + mutex_init(&the_audio_amrnb_in.write_lock); + init_waitqueue_head(&the_audio_amrnb_in.write_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(&audamrnb_in_misc); +} + +static void __exit audamrnb_in_exit(void) +{ + misc_deregister(&audamrnb_in_misc); +} + +module_init(audamrnb_in_init); +module_exit(audamrnb_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 0000000000000000000000000000000000000000..b85b153a0db2688704f3bbb0f9414eb86d66fe62 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_amrwb.c @@ -0,0 +1,1698 @@ +/* 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, 2011-2012 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 +#include +#include +#include +#include +#include + +#include "audmgr.h" + +#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 */ + void *map_v_read; + void *map_v_write; + 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; + audio->stopped = 1; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + atomic_set(&audio->out_bytes, 0); +} + +static void audamrwb_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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); + 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 = allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map mem for read buf\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..489929ba1ceffacdbc452c1fa9aa9a97c512e85c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_evrc.c @@ -0,0 +1,1623 @@ +/* arch/arm/mach-msm/audio_evrc.c + * + * Copyright (c) 2008-2009, 2011-2012 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 +#include +#include +#include + +#include "audmgr.h" + +/* 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 */ + void *map_v_read; + void *map_v_write; + + 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; + audio->stopped = 1; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + atomic_set(&audio->out_bytes, 0); +} + +static void audevrc_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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); + 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map mem" + " for read buf\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..e13b072fb3149c05ad9613cf85a4b1042ddb9219 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_evrc_in.c @@ -0,0 +1,1396 @@ +/* arch/arm/mach-msm/qdsp5/audio_evrc_in.c + * + * evrc audio input device + * + * Copyright (c) 2011-2012, 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 +#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; + + void *map_v_read; + void *map_v_write; + + 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); + audio->stopped = 1; + wake_up(&audio->wait); + 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->wait); + mutex_lock(&audio->read_lock); + audevrc_in_flush(audio); + mutex_unlock(&audio->read_lock); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audevrc_out_flush(audio); + mutex_unlock(&audio->write_lock); +} + +static void audevrc_in_flush(struct audio_evrc_in *audio) +{ + int i; + unsigned long flags; + + audio->dsp_cnt = 0; + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + 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; + unsigned long flags; + + audio->out_head = 0; + audio->out_count = 0; + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out_tail = 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; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- 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); + 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,loff_t a, loff_t b, 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)) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->out_phys); + audio->out_data = NULL; + } + if (audio->data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + 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->phys = allocate_contiguous_ebi_nomap(dma_size, SZ_4K); + if (!audio->phys) { + MM_ERR("could not allocate physical read buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->map_v_read = ioremap(audio->phys, dma_size); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map physical address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto evt_error; + } + audio->data = audio->map_v_read; + 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_phys = allocate_contiguous_ebi_nomap(BUFFER_SIZE, + SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate physical write buffers\n"); + rc = -ENOMEM; + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + goto evt_error; + } else { + audio->map_v_write = ioremap( + audio->out_phys, BUFFER_SIZE); + + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address\n"); + rc = -ENOMEM; + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + free_contiguous_memory_by_paddr(\ + audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + MM_DBG("wr 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 0000000000000000000000000000000000000000..2ab7cadfdeb01d7601e9370e6ef2dd765817f9f9 --- /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 0000000000000000000000000000000000000000..6fc5d6bf29962a9b98349349901c5edea5538320 --- /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_lpa.c b/arch/arm/mach-msm/qdsp5/audio_lpa.c new file mode 100644 index 0000000000000000000000000000000000000000..8754337e90948ccebc25388f005c64281616fab3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_lpa.c @@ -0,0 +1,1487 @@ + +/* audio_lpa.c - low power audio driver + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * Based on the PCM decoder driver in arch/arm/mach-msm/qdsp5/audio_pcm.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 +#include +#include + +#include "audmgr.h" + +/* 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 + +#define MSM_MAX_VOLUME 0x2000 +/* 17 added to avoid more deviation */ +#define MSM_VOLUME_STEP (MSM_MAX_VOLUME+17) +#define MSM_VOLUME_FACTOR (10000) + +/* Size must be power of 2 */ +#define MAX_BUF 2 +#define BUFSZ (524288) + +#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 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 audpcm_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +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 */ + 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; + struct msm_mapped_buffer *map_v_write; + + 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 long 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; + int buffer_count; + int buffer_size; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +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 audlpa_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); +static void audpcm_async_send_data(struct audio *audio, + unsigned needed); + + +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"); + } + + 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; + audio->stopped = 1; + 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: + audpcm_async_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 audlpadec_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 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); +} + +/* ------------------- 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; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} +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); + audpcm_async_flush(audio); + mutex_unlock(&audio->write_lock); + } else + audpcm_async_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); + audpcm_async_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); + 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 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 = 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); + audpcm_async_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 = MSM_VOLUME_STEP * arg; + audio->volume /= MSM_VOLUME_FACTOR; + + if (audio->volume > MSM_MAX_VOLUME) + audio->volume = MSM_MAX_VOLUME; + + if (audio->running) + audpp_set_volume_and_pan(audio->dec_id, + audio->volume, 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_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 { + rc = -EINVAL; + 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; + MM_DBG("AUDIO_SET_CONFIG\n"); + 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; + 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_16) + config.bits = 16; + 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; + MM_DBG("AUDIO_GET_CONFIG\n"); + 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 = 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_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 audlpa_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 audlpa_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; + audpcm_async_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 audlpa_fsync(struct file *file, loff_t a, loff_t b, int datasync) +{ + struct audio *audio = file->private_data; + + if (!audio->running) + return -EINVAL; + + return audlpa_async_fsync(audio); +} + +static void audpcm_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("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); + audpcm_async_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->map_v_write); + free_contiguous_memory_by_paddr(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 %lx\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; + +#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_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; + + audio->buffer_size = BUFSZ; + audio->buffer_count = MAX_BUF; + rc = audmgr_open(&audio->audmgr); + if (rc) + goto err; + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audlpadec_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"); + 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->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; + audpcm_async_flush(audio); + + file->private_data = audio; + audio->opened = 1; + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_pcm_lp_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: + 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_lp_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audlpa_fsync, +}; + +struct miscdevice audio_lpa_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_lp_dec", + .fops = &audio_pcm_lp_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_lpa_misc); +} + +device_initcall(audio_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 0000000000000000000000000000000000000000..93461070862ee7cae0a71d5978d115993c2632a1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_mp3.c @@ -0,0 +1,2360 @@ +/* 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-2012, 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 + +#include "audmgr.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 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 */ + void *map_v_read; + void *map_v_write; + + 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; + audio->stopped = 1; + 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; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audio_flush(struct audio *audio) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + 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; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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_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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + + if (IS_ERR(audio->map_v_read)) { + MM_ERR("map of read buf failed\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + } + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, + SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap( + audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write \ + buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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_mvs.c b/arch/arm/mach-msm/qdsp5/audio_mvs.c new file mode 100644 index 0000000000000000000000000000000000000000..5ccd18b6dcc55a9105ae17a1e6af4ae62d52006a --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_mvs.c @@ -0,0 +1,1704 @@ +/* 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 + +#define MVS_PROG 0x30000014 +#define MVS_VERS 0x00030001 +#define MVS_VERS_COMP_VER2 0x00060001 +#define MVS_VERS_COMP_VER3 0x00030001 + + +#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_PCM_WB_UL 23 +#define MVS_FRAME_MODE_PCM_WB_DL 24 +#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; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + 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) { + MM_DBG("RPC write for set amr mode done\n"); + + /* 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) { + MM_DBG("RPC write for set dtx done\n"); + + rc = 0; + } + } else { + MM_ERR("RPC write for set amr mode failed %d\n", 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_PCM_WB: { + audio->rate_type = MVS_AMR_MODE_UNDEF; + audio->frame_mode = MVS_FRAME_MODE_PCM_WB_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) { + MM_DBG("RPC write for set voc mode done\n"); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_VOC_RX; + + rc = 0; + } else { + MM_ERR("RPC write for set voc mode failed %d\n", 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); + + MM_DBG("mode of g711:%d\n", 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) { + MM_DBG("RPC write for set g711 mode done\n"); + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_G711_DL; + + rc = 0; + } else { + MM_ERR("RPC write for set g711 mode failed %d\n", 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); + + MM_DBG("mode of g729:%d\n", + 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) { + MM_DBG("RPC write for set g729 mode done\n"); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_G729A_DL; + + rc = 0; + } else { + MM_ERR("RPC write for set g729 mode failed %d\n", 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); + + MM_DBG("mode of g722:%d\n", + 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) { + MM_DBG("RPC write for set g722 mode done\n"); + + /* 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); + + MM_DBG("mode of g711A:%d\n", + 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) { + MM_DBG("RPC write for set g711A mode done\n"); + + /* 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) { + MM_DBG("RPC write for set dtx done\n"); + + rc = 0; + } + rc = 0; + } else { + MM_ERR("RPC write for set g711A mode failed %d\n", 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); + + MM_DBG("mode of EFR, FR and HR:%d\n", + 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) { + MM_DBG("RPC write for set EFR, FR and HR mode done\n"); + + /* 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 { + MM_ERR("RPC write for set EFR, FR" + "and HR mode failed %d\n", rc); + } + break; + } + default: + rc = -EINVAL; + MM_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; + + MM_DBG("\n"); + + /* 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) { + MM_DBG("RPC write for enable done\n"); + + rc = wait_event_timeout(audio->mode_wait, + (audio->rpc_status != RPC_STATUS_FAILURE), + 10 * HZ); + + if (rc > 0) { + MM_DBG("Wait event for enable succeeded\n"); + rc = audio_mvs_setup_mode(audio); + if (rc < 0) { + MM_ERR("Unknown MVS mode %d\n", + audio->mvs_mode); + } + MM_ERR("rc value after mode setup: %d\n", rc); + } 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 int audio_mvs_start(struct audio_mvs_info_type *audio) +{ + int rc = 0; + struct audio_mvs_acquire_msg acquire_msg; + + MM_DBG("\n"); + + /* 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) { + 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) { + + rc = audio_mvs_setup(audio); + + if (rc == 0) + audio->state = AUDIO_MVS_STARTED; + + } else { + MM_ERR("Wait event for acquire failed %d\n", rc); + + rc = -EBUSY; + } + } else { + MM_ERR("RPC write for acquire failed %d\n", 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; + + MM_DBG("\n"); + + /* 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) { + MM_DBG("RPC write for release done\n"); + + rc = wait_event_timeout(audio->mode_wait, + (audio->rpc_status != RPC_STATUS_FAILURE), + 1 * HZ); + + if (rc > 0) { + MM_DBG("Wait event for release succeeded\n"); + + audio->state = AUDIO_MVS_STOPPED; + + /* Un-block read in case it is waiting for data. */ + wake_up(&audio->out_wait); + rc = 0; + } else { + MM_ERR("Wait event for release failed %d\n", rc); + } + } else { + MM_ERR("RPC write for release failed %d\n", 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; + + MM_DBG("\n"); + + switch (procedure) { + case MVS_EVENT_CB_TYPE_PROC: { + struct audio_mvs_cb_func_args *args = data; + struct rpc_reply_hdr reply_hdr; + + MM_DBG("MVS CB CB_FUNC_ID 0x%x\n", + be32_to_cpu(args->cb_func_id)); + + if (be32_to_cpu(args->valid_ptr)) { + uint32_t event_type = be32_to_cpu(args->event); + + MM_DBG("MVS CB event type %d\n", + 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); + + MM_DBG("MVS CB command status %d\n", + 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); + + MM_DBG("MVS CB mode status %d\n", mode_status); + + if (mode_status == AUDIO_MVS_MODE_READY) { + audio->rpc_status = RPC_STATUS_SUCCESS; + wake_up(&audio->mode_wait); + } + } else { + MM_ERR("MVS CB unknown event type %d\n", + event_type); + } + } else { + MM_ERR("MVS CB event pointer not valid\n"); + } + + /* 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) + MM_ERR("RPC write for response failed %d\n", 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; + + MM_DBG("MVS UL CB_FUNC_ID 0x%x\n", + be32_to_cpu(*args)); + args++; + + pkt_len = be32_to_cpu(*args); + MM_DBG("UL pkt_len %d\n", 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; + + MM_DBG("UL valid_ptr 0x%x\n", + be32_to_cpu(*args)); + args++; + + frame_mode = be32_to_cpu(*args); + MM_DBG("UL frame_mode %d\n", + frame_mode); + args++; + + MM_DBG("UL frame_mode %d\n", + be32_to_cpu(*args)); + args++; + + MM_DBG("UL frame_mode %d\n", + be32_to_cpu(*args)); + args++; + + MM_DBG("UL mvs_mode %d\n", + be32_to_cpu(*args)); + args++; + + MM_DBG("UL buf_free_cnt %d\n", + 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); + + MM_DBG("UL AMR frame_type %d\n", + 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); + + MM_DBG("UL G711 frame_type %d\n", + 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); + + MM_DBG("UL G729 frame_type %d\n", + 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); + + MM_DBG("UL G722 frame_type %d\n", + 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); + + MM_DBG("UL G711A frame_type %d\n", + 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); + + MM_DBG("UL EFR,FR,HR frame_type %d\n", + be32_to_cpu(*args)); + } else { + MM_DBG("UL Unknown frame mode %d\n", + frame_mode); + } + + list_add_tail(&buf_node->list, &audio->out_queue); + } else { + MM_ERR("UL data dropped, read is slow\n"); + } + + 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) + MM_ERR("RPC write for UL response failed %d\n", + 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; + + MM_DBG("MVS DL CB CB_FUNC_ID 0x%x\n", + be32_to_cpu(args->cb_func_id)); + + frame_mode = be32_to_cpu(args->frame_mode); + MM_DBG("DL frame_mode %d\n", 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); + + MM_DBG("frame mode %d buf_node->frame.len %d\n", + frame_mode, buf_node->frame.len); + 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 { + MM_ERR("DL Unknown frame mode %d\n", + frame_mode); + } + list_add_tail(&buf_node->list, &audio->free_in_queue); + } else { + MM_DBG("No DL data available to send to MVS\n"); + 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) + 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 = data; + struct rpc_request_hdr *rpc_hdr = NULL; + + MM_DBG("\n"); + + while (!kthread_should_stop()) { + + int 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 { + 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); + + MM_ERR("RPC reply status denied\n"); + } + } 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 { + MM_ERR("Unexpected RPC type %d\n", rpc_type); + } + } + + kfree(rpc_hdr); + rpc_hdr = NULL; + } + + MM_DBG("MVS thread stopped\n"); + + 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; + + MM_DBG("\n"); + + /* 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 { + MM_ERR("No memory for IO buffers\n"); + 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 { + MM_ERR("No memory for IO buffers\n"); + 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; + + MM_DBG("\n"); + + 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_release(struct inode *inode, struct file *file) +{ + + struct audio_mvs_info_type *audio = file->private_data; + + MM_DBG("\n"); + + 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); + + MM_DBG("Release done\n"); + 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; + + MM_DBG("\n"); + + 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 { + MM_ERR("Copy to user retuned %d", rc); + + rc = -EFAULT; + } + + list_add_tail(&buf_node->list, + &audio->free_out_queue); + } else { + MM_ERR("Read count %d < sizeof(frame) %d", + count, + sizeof(struct msm_audio_mvs_frame)); + + rc = -ENOMEM; + } + } else { + MM_ERR("Read performed in state %d\n", + audio->state); + + rc = -EPERM; + } + mutex_unlock(&audio->out_lock); + + } else if (rc == 0) { + MM_ERR("No UL data available\n"); + + rc = -ETIMEDOUT; + } else { + MM_ERR("Read was interrupted\n"); + + 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; + + MM_DBG("\n"); + + 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 { + MM_ERR("No free DL buffs\n"); + } + } else { + MM_ERR("Write count %d < sizeof(frame) %d", + count, + sizeof(struct msm_audio_mvs_frame)); + + rc = -ENOMEM; + } + } else { + MM_ERR("Write performed in invalid state %d\n", + 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; + + MM_DBG("\n"); + + switch (cmd) { + case AUDIO_GET_MVS_CONFIG: { + struct msm_audio_mvs_config config; + + MM_DBG("IOCTL GET_MVS_CONFIG\n"); + + 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 + MM_ERR("Config copy failed %d\n", rc); + + break; + } + + case AUDIO_SET_MVS_CONFIG: { + struct msm_audio_mvs_config config; + + MM_DBG("IOCTL SET_MVS_CONFIG\n"); + + 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 { + MM_ERR("Set confg called in state %d\n", + audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + } else { + MM_ERR("Config copy failed %d\n", rc); + } + + break; + } + + case AUDIO_START: { + MM_DBG("IOCTL START\n"); + + 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 { + MM_ERR("Start called in invalid state %d\n", + audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + + break; + } + + case AUDIO_STOP: { + MM_DBG("IOCTL STOP\n"); + + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_STARTED) { + rc = audio_mvs_stop(audio); + } else { + MM_ERR("Stop called in invalid state %d\n", + audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + break; + } + + default: { + MM_ERR("Unknown IOCTL %d\n", cmd); + } + } + + return rc; +} + +static int audio_mvs_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + MM_DBG("\n"); + + 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_VER2, + MSM_RPC_UNINTERRUPTIBLE); + + if (IS_ERR(audio_mvs_info.rpc_endpt)) { + MM_ERR("MVS RPC connect failed ver 0x%x\n", + MVS_VERS_COMP_VER2); + audio_mvs_info.rpc_endpt = msm_rpc_connect_compatible(MVS_PROG, + MVS_VERS_COMP_VER3, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(audio_mvs_info.rpc_endpt)) { + MM_ERR("MVS RPC connect failed ver 0x%x\n", + MVS_VERS_COMP_VER3); + } else { + MM_DBG("MVS RPC connect succeeded ver 0x%x\n", + MVS_VERS_COMP_VER3); + audio_mvs_info.rpc_prog = MVS_PROG; + audio_mvs_info.rpc_ver = MVS_VERS_COMP_VER3; + } + } else { + MM_DBG("MVS RPC connect succeeded ver 0x%x\n", + MVS_VERS_COMP_VER2); + audio_mvs_info.rpc_prog = MVS_PROG; + audio_mvs_info.rpc_ver = MVS_VERS_COMP_VER2; + } + audio_mvs_info.task = kthread_run(audio_mvs_thread, + &audio_mvs_info, + "audio_mvs"); + if (IS_ERR(audio_mvs_info.task)) { + MM_ERR("MVS thread create failed\n"); + 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; + } + + 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 { + MM_ERR("MVS thread and RPC end point do not exist\n"); + + rc = -ENODEV; + } + } else { + MM_ERR("MVS driver exists, state %d\n", + audio_mvs_info.state); + + rc = -EBUSY; + } + + mutex_unlock(&audio_mvs_info.lock); + +done: + 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) +{ + return misc_register(&audio_mvs_misc); +} + +static void __exit audio_mvs_exit(void) +{ + MM_DBG("\n"); + + 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/qdsp5/audio_out.c b/arch/arm/mach-msm/qdsp5/audio_out.c new file mode 100644 index 0000000000000000000000000000000000000000..91ce29d2e12f36231d2e1cbf351be23fa4acdcf2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_out.c @@ -0,0 +1,1154 @@ +/* arch/arm/mach-msm/qdsp5/audio_out.c + * + * pcm audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2012 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 "evlog.h" + +#define LOG_AUDIO_EVENTS 1 +#define LOG_AUDIO_FAULTS 0 + +#define SRS_ID_GLOBAL 0x00000001 +#define SRS_ID_WOWHD 0x00000002 +#define SRS_ID_CSHP 0x00000003 +#define SRS_ID_HPF 0x00000004 +#define SRS_ID_PEQ 0x00000005 +#define SRS_ID_HL 0x00000006 + +#define SRS_MASK_G 1 +#define SRS_MASK_W 2 +#define SRS_MASK_C 4 +#define SRS_MASK_HP 8 +#define SRS_MASK_P 16 +#define SRS_MASK_HL 32 + + +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; + + int srs_enable; + int srs_needs_commit; + int srs_feature_mask; + audpp_cmd_cfg_object_params_qconcert qconcert_plus; + + int status; + int opened; + struct mutex lock; + + struct audpp_event_callback ecb; + + struct audpp_cmd_cfg_object_params_srstm_g g; + struct audpp_cmd_cfg_object_params_srstm_w w; + struct audpp_cmd_cfg_object_params_srstm_c c; + struct audpp_cmd_cfg_object_params_srstm_h h; + struct audpp_cmd_cfg_object_params_srstm_p p; + struct audpp_cmd_cfg_object_params_srstm_l l; +} 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); +static int audio_enable_srs_trumedia(struct audio_copp *audio_copp, int enable); +/* 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); + + audio->stopped = 1; + 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); + audio_enable_srs_trumedia(audio_copp, true); +} +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_srs_trumedia(struct audio_copp *audio_copp, int enable) +{ + + if (!audio_copp->srs_needs_commit) + return 0; + + audio_copp->srs_enable = enable; + + MM_DBG("Enable SRS flags 0x%x enable %d\n", + audio_copp->srs_feature_mask, enable); + if (is_audpp_enable()) { + MM_DBG("Updating audpp for srs\n"); + if (audio_copp->srs_feature_mask & SRS_MASK_W) + audpp_dsp_set_rx_srs_trumedia_w(&audio_copp->w); + if (audio_copp->srs_feature_mask & SRS_MASK_C) + audpp_dsp_set_rx_srs_trumedia_c(&audio_copp->c); + if (audio_copp->srs_feature_mask & SRS_MASK_HP) + audpp_dsp_set_rx_srs_trumedia_h(&audio_copp->h); + if (audio_copp->srs_feature_mask & SRS_MASK_P) + audpp_dsp_set_rx_srs_trumedia_p(&audio_copp->p); + if (audio_copp->srs_feature_mask & SRS_MASK_HL) + audpp_dsp_set_rx_srs_trumedia_l(&audio_copp->l); + if (audio_copp->srs_feature_mask & SRS_MASK_G) + audpp_dsp_set_rx_srs_trumedia_g(&audio_copp->g); + + audio_copp->srs_needs_commit = 0; + audio_copp->srs_feature_mask = 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); + 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); + } + 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; + 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, loff_t a, loff_t b, 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; + uint32_t to_set, size = 0; + void *tmpbuf, *srs_params = NULL; + + 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); + enable = (enable_mask & SRS_ENABLE) ? 1 : 0; + audio_enable_srs_trumedia(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; + + case AUDIO_SET_SRS_TRUMEDIA_PARAM: { + prev_state = audio_copp->srs_enable; + audio_copp->srs_enable = 0; + + if (copy_from_user(&to_set, (void *)arg, sizeof(uint32_t))) { + rc = -EFAULT; + break; + } + switch (to_set) { + case SRS_ID_GLOBAL: + srs_params = (void *)audio_copp->g.v; + size = sizeof(audio_copp->g.v); + audio_copp->srs_feature_mask |= SRS_MASK_G; + break; + case SRS_ID_WOWHD: + srs_params = (void *)audio_copp->w.v; + size = sizeof(audio_copp->w.v); + audio_copp->srs_feature_mask |= SRS_MASK_W; + break; + case SRS_ID_CSHP: + srs_params = (void *)audio_copp->c.v; + size = sizeof(audio_copp->c.v); + audio_copp->srs_feature_mask |= SRS_MASK_C; + break; + case SRS_ID_HPF: + srs_params = (void *)audio_copp->h.v; + size = sizeof(audio_copp->h.v); + audio_copp->srs_feature_mask |= SRS_MASK_HP; + break; + case SRS_ID_PEQ: + srs_params = (void *)audio_copp->p.v; + size = sizeof(audio_copp->p.v); + audio_copp->srs_feature_mask |= SRS_MASK_P; + break; + case SRS_ID_HL: + srs_params = (void *)audio_copp->l.v; + size = sizeof(audio_copp->l.v); + audio_copp->srs_feature_mask |= SRS_MASK_HL; + break; + default: + MM_ERR("SRS TruMedia error: invalid ioctl\n"); + rc = -EINVAL; + } + + if (rc >= 0) { + tmpbuf = kzalloc(sizeof(uint32_t) + size , GFP_KERNEL); + if (!tmpbuf) { + MM_ERR("SRS TruMedia error: no kernel mem\n"); + rc = -ENOMEM; + } else { + if (copy_from_user(tmpbuf, (void *)arg, + sizeof(uint32_t) + size)) + rc = -EFAULT; + memcpy(srs_params, + &(((uint32_t *)tmpbuf)[1]), size); + kfree(tmpbuf); + } + } + + MM_DBG("Ioctl SRS flags=0x%x\n", audio_copp->srs_feature_mask); + if (rc < 0) + MM_ERR("SRS TruMedia error setting params failed.\n"); + else{ + audio_copp->srs_needs_commit = 1; + audio_copp->srs_enable = prev_state; + } + 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 0000000000000000000000000000000000000000..49c781f0a0905ccf8c83b0ce412e257320bde75b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_pcm.c @@ -0,0 +1,1707 @@ + +/* audio_pcm.c - pcm audio decoder driver + * + * Copyright (c) 2009-2012, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audmgr.h" + +/* 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; + void *map_v_write; + + 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; + audio->stopped = 1; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + 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_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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, + SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap( + audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write\ + buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->phys); + audpp_adec_free(audio->dec_id); + MM_DBG("audio instance 0x%08x\ + freeing\n", (int)audio); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..851980db60733d2300cecf12a74462291e40ab11 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_pcm_in.c @@ -0,0 +1,956 @@ +/* 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) +#define MSM_AUD_BUFFER_UPDATE_WAIT_MS 2000 + +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); + + audio->stopped = 1; + 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); + 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_timeout( + audio->wait, (audio->in_count > 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 && !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 0000000000000000000000000000000000000000..f4367598b0c380f8abc934a577b4858f62406ab5 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_qcelp.c @@ -0,0 +1,1620 @@ +/* arch/arm/mach-msm/qdsp5/audio_qcelp.c + * + * qcelp 13k audio decoder device + * + * Copyright (c) 2008-2009, 2011-2012 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 +#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 */ + void *map_v_read; + void *map_v_write; + 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; + audio->stopped = 1; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audqcelp_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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); + 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read buf\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..92036e58a655d8f5404d2993c15680ae3237a735 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_qcelp_in.c @@ -0,0 +1,1400 @@ +/* arch/arm/mach-msm/qdsp5/audio_qcelp_in.c + * + * qcelp audio input device + * + * Copyright (c) 2011-2012, 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 +#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; + + void *map_v_read; + void *map_v_write; + + 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); + + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + audio->stopped = 1; + wake_up(&audio->wait); + 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->wait); + mutex_lock(&audio->read_lock); + audqcelp_in_flush(audio); + mutex_unlock(&audio->read_lock); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audqcelp_out_flush(audio); + mutex_unlock(&audio->write_lock); +} + +static void audqcelp_in_flush(struct audio_qcelp_in *audio) +{ + int i; + unsigned long flags; + + audio->eos_ack = 0; + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + 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; + unsigned long flags; + + audio->out_head = 0; + audio->out_count = 0; + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out_tail = 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; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- 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); + 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, loff_t a, loff_t b, 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)) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->out_phys); + audio->out_data = NULL; + } + + if (audio->data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + 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->phys = allocate_contiguous_ebi_nomap(dma_size, SZ_4K); + if (!audio->phys) { + MM_ERR("could not allocate physical read buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->map_v_read = ioremap(audio->phys, dma_size); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map physical address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto evt_error; + } + audio->data = audio->map_v_read; + 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_phys = allocate_contiguous_ebi_nomap(BUFFER_SIZE, + SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate physical write buffers\n"); + rc = -ENOMEM; + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + goto evt_error; + } else { + audio->map_v_write = ioremap( + audio->out_phys, BUFFER_SIZE); + + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address\n"); + rc = -ENOMEM; + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + free_contiguous_memory_by_paddr(\ + audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + MM_DBG("wr 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_voice_lb.c b/arch/arm/mach-msm/qdsp5/audio_voice_lb.c new file mode 100644 index 0000000000000000000000000000000000000000..08fa4872db2f9fc2c6b2efa5505ce70199619e52 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_voice_lb.c @@ -0,0 +1,369 @@ +/* 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 "audmgr_new.h" + +#define VOICELOOPBACK_PROG 0x300000B8 +#define VOICELOOP_VERS 0x00010001 + +#define VOICELOOPBACK_START_PROC 2 +#define VOICELOOPBACK_STOP_PROC 3 + +#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) + +#define MAX_LEN 32 + +struct audio { + struct msm_rpc_endpoint *rpc_endpt; + uint32_t rpc_prog; + uint32_t rpc_ver; + uint32_t rpc_status; + struct audmgr audmgr; + + struct dentry *dentry; + + struct mutex lock; + + struct task_struct *task; + + wait_queue_head_t wait; + int enabled; + int thread_exit; +}; + +static struct audio the_audio; + +static int audio_voice_loopback_thread(void *data) +{ + struct audio *audio = data; + struct rpc_request_hdr *rpc_hdr = NULL; + int rpc_hdr_len; + + MM_DBG("\n"); + + while (!kthread_should_stop()) { + if (rpc_hdr != NULL) { + kfree(rpc_hdr); + rpc_hdr = NULL; + } + + if (audio->thread_exit) + break; + + 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 { + 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; + + reply_status = + be32_to_cpu(rpc_reply->reply_stat); + + if (reply_status == RPC_ACCEPTSTAT_SUCCESS) + audio->rpc_status = \ + RPC_STATUS_SUCCESS; + else { + audio->rpc_status = \ + RPC_STATUS_REJECT; + MM_ERR("RPC reply status denied\n"); + } + wake_up(&audio->wait); + } else { + MM_ERR("Unexpected RPC type %d\n", rpc_type); + } + } + } + kfree(rpc_hdr); + rpc_hdr = NULL; + + MM_DBG("Audio Voice Looopback thread stopped\n"); + + return 0; +} + +static int audio_voice_loopback_start(struct audio *audio) +{ + int rc = 0; + struct audmgr_config cfg; + struct rpc_request_hdr rpc_hdr; + + MM_DBG("\n"); + + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; + cfg.def_method = RPC_AUD_DEF_METHOD_VOICE; + cfg.codec = RPC_AUD_DEF_CODEC_VOC_CDMA; + cfg.snd_method = RPC_SND_METHOD_VOICE; + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) { + MM_ERR("audmgr open failed, freeing instance\n"); + rc = -EINVAL; + goto done; + } + + memset(&rpc_hdr, 0, sizeof(rpc_hdr)); + + msm_rpc_setup_req(&rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + VOICELOOPBACK_START_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &rpc_hdr, + sizeof(rpc_hdr)); + if (rc >= 0) { + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != RPC_STATUS_FAILURE), + 1 * HZ); + if (rc > 0) { + if (audio->rpc_status != RPC_STATUS_SUCCESS) { + MM_ERR("Start loopback failed %d\n", rc); + rc = -EBUSY; + } else { + rc = 0; + } + } else { + MM_ERR("Wait event for acquire failed %d\n", rc); + rc = -EBUSY; + } + } else { + audmgr_disable(&audio->audmgr); + MM_ERR("RPC write for start loopback failed %d\n", rc); + rc = -EBUSY; + } +done: + return rc; +} + +static int audio_voice_loopback_stop(struct audio *audio) +{ + int rc = 0; + struct rpc_request_hdr rpc_hdr; + + MM_DBG("\n"); + + memset(&rpc_hdr, 0, sizeof(rpc_hdr)); + + msm_rpc_setup_req(&rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + VOICELOOPBACK_STOP_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + audio->thread_exit = 1; + rc = msm_rpc_write(audio->rpc_endpt, + &rpc_hdr, + sizeof(rpc_hdr)); + if (rc >= 0) { + + 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; + } else { + MM_ERR("Wait event for release failed %d\n", rc); + } + } else { + MM_ERR("RPC write for release failed %d\n", rc); + } + + audmgr_disable(&audio->audmgr); + + return rc; +} + +static int audio_voice_loopback_open(struct audio *audio_info) +{ + int rc = 0; + + MM_DBG("\n"); + + rc = audmgr_open(&audio_info->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance\n"); + rc = -EINVAL; + goto done; + } + + audio_info->rpc_endpt = msm_rpc_connect_compatible(VOICELOOPBACK_PROG, + VOICELOOP_VERS, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(audio_info->rpc_endpt)) { + MM_ERR("VOICE LOOPBACK RPC connect\ + failed ver 0x%x\n", + VOICELOOP_VERS); + rc = PTR_ERR(audio_info->rpc_endpt); + audio_info->rpc_endpt = NULL; + rc = -EINVAL; + } else { + MM_DBG("VOICE LOOPBACK connect succeeded ver 0x%x\n", + VOICELOOP_VERS); + audio_info->thread_exit = 0; + audio_info->task = kthread_run(audio_voice_loopback_thread, + audio_info, + "audio_voice_loopback"); + if (IS_ERR(audio_info->task)) { + MM_ERR("voice loopback thread create failed\n"); + rc = PTR_ERR(audio_info->task); + audio_info->task = NULL; + msm_rpc_close(audio_info->rpc_endpt); + audio_info->rpc_endpt = NULL; + rc = -EINVAL; + } + audio_info->rpc_prog = VOICELOOPBACK_PROG; + audio_info->rpc_ver = VOICELOOP_VERS; + } +done: + return rc; +} + +static int audio_voice_loopback_close(struct audio *audio_info) +{ + MM_DBG("\n"); + msm_rpc_close(audio_info->rpc_endpt); + audio_info->rpc_endpt = NULL; + audmgr_close(&audio_info->audmgr); + audio_info->task = NULL; + return 0; +} + +static ssize_t audio_voice_loopback_debug_write(struct file *file, + const char __user *buf, + size_t cnt, loff_t *ppos) +{ + char lbuf[MAX_LEN]; + int rc = 0; + + if (cnt > (MAX_LEN - 1)) + return -EINVAL; + + memset(&lbuf[0], 0, sizeof(lbuf)); + + rc = copy_from_user(lbuf, buf, cnt); + if (rc) { + MM_ERR("Unable to copy data from user space\n"); + return -EFAULT; + } + + lbuf[cnt] = '\0'; + + if (!strncmp(&lbuf[0], "1", cnt-1)) { + mutex_lock(&the_audio.lock); + if (!the_audio.enabled) { + rc = audio_voice_loopback_open(&the_audio); + if (!rc) { + rc = audio_voice_loopback_start(&the_audio); + if (rc < 0) { + the_audio.enabled = 0; + audio_voice_loopback_close(&the_audio); + } else { + the_audio.enabled = 1; + } + } + } + mutex_unlock(&the_audio.lock); + } else if (!strncmp(lbuf, "0", cnt-1)) { + mutex_lock(&the_audio.lock); + if (the_audio.enabled) { + audio_voice_loopback_stop(&the_audio); + audio_voice_loopback_close(&the_audio); + the_audio.enabled = 0; + } + mutex_unlock(&the_audio.lock); + } else { + rc = -EINVAL; + } + + if (rc == 0) { + rc = cnt; + } else { + MM_INFO("rc = %d\n", rc); + MM_INFO("\nWrong command: Use =>\n"); + MM_INFO("-------------------------\n"); + MM_INFO("To Start Loopback:: echo \"1\">/sys/kernel/debug/\ + voice_loopback\n"); + MM_INFO("To Stop Loopback:: echo \"0\">/sys/kernel/debug/\ + voice_loopback\n"); + MM_INFO("------------------------\n"); + } + + return rc; +} + +static ssize_t audio_voice_loopback_debug_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + MM_DBG("Audio Voiceloop debugfs opened\n"); + return 0; +} + +static const struct file_operations voice_loopback_debug_fops = { + .write = audio_voice_loopback_debug_write, + .open = audio_voice_loopback_debug_open, +}; + +static int __init audio_init(void) +{ + int rc = 0; + memset(&the_audio, 0, sizeof(the_audio)); + + mutex_init(&the_audio.lock); + + init_waitqueue_head(&the_audio.wait); + + the_audio.dentry = debugfs_create_file("voice_loopback", + S_IFREG | S_IRUGO, + NULL, + NULL, &voice_loopback_debug_fops); + if (IS_ERR(the_audio.dentry)) + MM_ERR("debugfs_create_file failed\n"); + + return rc; +} +late_initcall(audio_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 0000000000000000000000000000000000000000..2011c42f21f99a5629833e45c45b7acae30f5076 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_voicememo.c @@ -0,0 +1,965 @@ +/* arch/arm/mach-msm/qdsp5/audio_voicememo.c + * + * Voice Memo device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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; + mutex_lock(&audio->dsp_lock); + audio->fill_next = 0; + mutex_unlock(&audio->dsp_lock); +} + +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); + audio->stopped = 1; + 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"); + mutex_lock(&audio->lock); + audio->stopped = 1; + wake_up(&audio->read_wait); + audmgr_disable(&audio->audmgr); + audvoicememo_ioport_reset(audio); + audio->stopped = 0; + audio->enabled = 0; + mutex_unlock(&audio->lock); + } + 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); + 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; + audio->stopped = 0; + 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 0000000000000000000000000000000000000000..162a6f10e4824f9af07642493038c53720909a52 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_wma.c @@ -0,0 +1,1779 @@ +/* audio_wma.c - wma audio decoder driver + * + * Copyright (c) 2009, 2011-2012, 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 +#include +#include +#include +#include + +#include "audmgr.h" + +/* 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 */ + void *map_v_read; + void *map_v_write; + + 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; + audio->stopped = 1; + 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); + 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); + } + +} + +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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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_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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("map of read buf failed\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap(audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..641b1c7774398389249a4d4f65be84902be7ce6d --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_wmapro.c @@ -0,0 +1,1766 @@ +/* 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-2012, 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 +#include +#include +#include +#include +#include +#include + +#include "audmgr.h" + +/* 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 */ + void *map_v_read; + void *map_v_write; + + 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; + audio->stopped = 1; + 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); + 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); + } + +} + +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) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + 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; + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +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_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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + + if (IS_ERR(audio->map_v_read)) { + MM_ERR("map of read buf failed\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = audio->map_v_read; + 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, loff_t a, loff_t b, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap(audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..231a28cdde84485fc6e31413460a2c3578374c54 --- /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 0000000000000000000000000000000000000000..34c8488cd089a10a35813ad82fc557973940d315 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audmgr.h @@ -0,0 +1,280 @@ +/* arch/arm/mach-msm/qdsp5/audmgr.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, 2012 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_rx_srs_trumedia_g + (struct audpp_cmd_cfg_object_params_srstm_g *srstm); +int audpp_dsp_set_rx_srs_trumedia_w + (struct audpp_cmd_cfg_object_params_srstm_w *srstm); +int audpp_dsp_set_rx_srs_trumedia_c + (struct audpp_cmd_cfg_object_params_srstm_c *srstm); +int audpp_dsp_set_rx_srs_trumedia_h + (struct audpp_cmd_cfg_object_params_srstm_h *srstm); +int audpp_dsp_set_rx_srs_trumedia_p + (struct audpp_cmd_cfg_object_params_srstm_p *srstm); +int audpp_dsp_set_rx_srs_trumedia_l + (struct audpp_cmd_cfg_object_params_srstm_l *srstm); + +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 0000000000000000000000000000000000000000..3604405b39cb6eb4ae3e1f8f9a75706d54f4f268 --- /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 0000000000000000000000000000000000000000..1616ad0b5241f2b9a016a491d07d3d20e25b7f99 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audpp.c @@ -0,0 +1,1014 @@ + +/* 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, 2012 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_SRS_PARAMS 2 +#define AUDPP_SRS_PARAMS_G 0 +#define AUDPP_SRS_PARAMS_W 1 +#define AUDPP_SRS_PARAMS_C 2 +#define AUDPP_SRS_PARAMS_H 3 +#define AUDPP_SRS_PARAMS_P 4 +#define AUDPP_SRS_PARAMS_L 5 + +#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]; + + spinlock_t avsync_lock; + + 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_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); +} + +static void audpp_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audpp_state *audpp = data; + unsigned long flags; + uint16_t msg[8]; + int cid = 0; + + if (id == AUDPP_MSG_AVSYNC_MSG) { + spin_lock_irqsave(&audpp->avsync_lock, flags); + 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; + spin_unlock_irqrestore(&audpp->avsync_lock, flags); + 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"); + if (!audpp->enabled) { + audpp->enabled = 1; + audpp_broadcast(audpp, id, msg); + } else { + cid = msg[1]; + audpp_fake_event(audpp, cid, + id, AUDPP_MSG_ENA_ENA); + } + + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + if (audpp->open_count == 0) { + MM_INFO("DISABLE\n"); + audpp->enabled = 0; + wake_up(&audpp->event_wait); + audpp_broadcast(audpp, id, msg); + } else { + cid = msg[1]; + audpp_fake_event(audpp, cid, + id, AUDPP_MSG_ENA_DIS); + audpp->func[cid] = NULL; + audpp->private[cid] = NULL; + } + } 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, +}; + +int audpp_enable(int id, audpp_event_func func, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + uint16_t msg[8]; + 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 { + if (audpp->enabled) { + msg[0] = AUDPP_MSG_ENA_ENA; + msg[1] = id; + res = msm_adsp_generate_event(audpp, audpp->mod, + AUDPP_MSG_CFG_MSG, sizeof(msg), + sizeof(uint16_t), (void *)msg); + if (res < 0) + goto out; + } + } + + 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; + uint16_t msg[8]; + 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; + + msg[0] = AUDPP_MSG_ENA_DIS; + msg[1] = id; + rc = msm_adsp_generate_event(audpp, audpp->mod, + AUDPP_MSG_CFG_MSG, sizeof(msg), + sizeof(uint16_t), (void *)msg); + if (rc < 0) + goto out; + + 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; + + spin_lock_irqsave(&the_audpp_state.avsync_lock, 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; + spin_unlock_irqrestore(&the_audpp_state.avsync_lock, 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) +{ + struct audpp_state *audpp = &the_audpp_state; + uint16_t *avsync = audpp->avsync; + unsigned val; + unsigned long flags; + unsigned mask; + + if (BAD_ID(id)) + return 0; + + mask = 1 << id; + id = id * AUDPP_AVSYNC_INFO_SIZE + 2; + spin_lock_irqsave(&audpp->avsync_lock, flags); + if (avsync[0] & mask) + val = (avsync[id] << 16) | avsync[id + 1]; + else + val = 0; + spin_unlock_irqrestore(&audpp->avsync_lock, flags); + + return val; +} +EXPORT_SYMBOL(audpp_avsync_sample_count); + +unsigned audpp_avsync_byte_count(int id) +{ + struct audpp_state *audpp = &the_audpp_state; + uint16_t *avsync = audpp->avsync; + unsigned val; + unsigned long flags; + unsigned mask; + + if (BAD_ID(id)) + return 0; + + mask = 1 << id; + id = id * AUDPP_AVSYNC_INFO_SIZE + 5; + spin_lock_irqsave(&audpp->avsync_lock, flags); + if (avsync[0] & mask) + val = (avsync[id] << 16) | avsync[id + 1]; + else + val = 0; + spin_unlock_irqrestore(&audpp->avsync_lock, 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); + +int audpp_dsp_set_rx_srs_trumedia_g( + struct audpp_cmd_cfg_object_params_srstm_g *srstm) +{ + struct audpp_cmd_cfg_object_params_srstm_g cmd; + + MM_DBG("%s\n", __func__); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_SRS_PARAMS; + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_SRS_PARAMS_G; + + memcpy(cmd.v, srstm->v, sizeof(srstm->v)); + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_srs_trumedia_g); + +int audpp_dsp_set_rx_srs_trumedia_w( + struct audpp_cmd_cfg_object_params_srstm_w *srstm) +{ + struct audpp_cmd_cfg_object_params_srstm_w cmd; + + MM_DBG("%s\n", __func__); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_SRS_PARAMS; + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_SRS_PARAMS_W; + + memcpy(cmd.v, srstm->v, sizeof(srstm->v)); + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_srs_trumedia_w); + +int audpp_dsp_set_rx_srs_trumedia_c( + struct audpp_cmd_cfg_object_params_srstm_c *srstm) +{ + struct audpp_cmd_cfg_object_params_srstm_c cmd; + + MM_DBG("%s\n", __func__); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_SRS_PARAMS; + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_SRS_PARAMS_C; + + memcpy(cmd.v, srstm->v, sizeof(srstm->v)); + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_srs_trumedia_c); + +int audpp_dsp_set_rx_srs_trumedia_h( + struct audpp_cmd_cfg_object_params_srstm_h *srstm) +{ + struct audpp_cmd_cfg_object_params_srstm_h cmd; + + MM_DBG("%s\n", __func__); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_SRS_PARAMS; + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_SRS_PARAMS_H; + + memcpy(cmd.v, srstm->v, sizeof(srstm->v)); + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_srs_trumedia_h); + +int audpp_dsp_set_rx_srs_trumedia_p( + struct audpp_cmd_cfg_object_params_srstm_p *srstm) +{ + struct audpp_cmd_cfg_object_params_srstm_p cmd; + + MM_DBG("%s\n", __func__); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_SRS_PARAMS; + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_SRS_PARAMS_P; + + memcpy(cmd.v, srstm->v, sizeof(srstm->v)); + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_srs_trumedia_p); + +int audpp_dsp_set_rx_srs_trumedia_l( + struct audpp_cmd_cfg_object_params_srstm_l *srstm) +{ + struct audpp_cmd_cfg_object_params_srstm_l cmd; + + MM_DBG("%s\n", __func__); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_SRS_PARAMS; + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_SRS_PARAMS_L; + + memcpy(cmd.v, srstm->v, sizeof(srstm->v)); + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_srs_trumedia_l); + +/* 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); + + spin_lock_init(&audpp->avsync_lock); + + 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 0000000000000000000000000000000000000000..230429f141db147594d09eeec8849b16d7f86037 --- /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 0000000000000000000000000000000000000000..d5cb168b98ed30ec4668f1a0f41b72f31fae51c1 --- /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/dsp_debug.c b/arch/arm/mach-msm/qdsp5/dsp_debug.c new file mode 100644 index 0000000000000000000000000000000000000000..331ba00cad5c0d826c479fabcca1e61a317e9389 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/dsp_debug.c @@ -0,0 +1,235 @@ +/* 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 "dsp_debug.h" + +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); +static dsp_state_cb cb_ptr; + +#define MAX_LEN 64 +#define HDR_LEN 20 +#define NUM_DSP_RAM_BANKS 3 + +static char l_buf[MAX_LEN]; +#ifdef CONFIG_DEBUG_FS +static struct dentry *dsp_dentry; +#endif + +void q5audio_dsp_not_responding(void) +{ + if (cb_ptr) + cb_ptr(DSP_STATE_CRASHED); + + MM_DBG("entered q5audio_dsp_not_responding\n"); + if (atomic_add_return(1, &dsp_crash_count) != 1) { + MM_ERR("q5audio_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 { + MM_ERR("q5audio_dsp_not_responding() - no waiter?\n"); + } + + if (cb_ptr) + cb_ptr(DSP_STATE_CRASH_DUMP_DONE); +} + +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 (!strncmp(cmd, "wait-for-crash", sizeof("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; + } + } + } else if (!strncmp(cmd, "boom", sizeof("boom"))) { + q5audio_dsp_not_responding(); + } else if (!strncmp(cmd, "continue-crash", sizeof("continue-crash"))) { + dsp_has_crashed = 2; + wake_up(&dsp_wait); + } else { + MM_ERR("[%s:%s] unknown dsp_debug command: %s\n", __MM_FILE__, + __func__, cmd); + } + + return count; +} + +static ssize_t dsp_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + size_t actual = 0; + static void *dsp_addr; + static unsigned copy_ok_count; + + MM_INFO("pos = %lld\n", *pos); + if (*pos >= DSP_RAM_SIZE * NUM_DSP_RAM_BANKS) + return 0; + + if (*pos == 0) + dsp_addr = (*pos + RAMA_BASE); + else if (*pos == DSP_RAM_SIZE) + dsp_addr = RAMB_BASE; + else if (*pos >= DSP_RAM_SIZE * 2) + dsp_addr = RAMC_BASE; + + MM_INFO("dsp_addr = %p\n", dsp_addr); + while (count >= PAGE_SIZE) { + if (copy_to_user(buf, dsp_addr, PAGE_SIZE)) { + MM_ERR("[%s:%s] copy error @ %p\n", __MM_FILE__, + __func__, buf); + return -EFAULT; + } + copy_ok_count += PAGE_SIZE; + dsp_addr = (char *)dsp_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, +}; + +#ifdef CONFIG_DEBUG_FS +static struct miscdevice dsp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "dsp_debug", + .fops = &dsp_fops, +}; +#endif + +static ssize_t dsp_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_DBG("adsp debugfs opened\n"); + return 0; +} + +static ssize_t dsp_debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int len; + + if (count < 0) + return 0; + len = count > (MAX_LEN - 1) ? (MAX_LEN - 1) : count; + if (copy_from_user(l_buf + HDR_LEN, buf, len)) { + MM_ERR("Unable to copy data from user space\n"); + return -EFAULT; + } + l_buf[len + HDR_LEN] = 0; + if (l_buf[len + HDR_LEN - 1] == '\n') { + l_buf[len + HDR_LEN - 1] = 0; + len--; + } + if (!strncmp(l_buf + HDR_LEN, "boom", 64)) { + q5audio_dsp_not_responding(); + } else if (!strncmp(l_buf + HDR_LEN, "continue-crash", + sizeof("continue-crash"))) { + dsp_has_crashed = 2; + wake_up(&dsp_wait); + } else + MM_ERR("Unknown command\n"); + + return count; +} +static const struct file_operations dsp_debug_fops = { + .write = dsp_debug_write, + .open = dsp_debug_open, +}; + +static int __init dsp_init(void) +{ + init_waitqueue_head(&dsp_wait); +#ifdef CONFIG_DEBUG_FS + dsp_dentry = debugfs_create_file("dsp_debug", S_IFREG | S_IRUGO, + NULL, (void *) NULL, &dsp_debug_fops); + + return misc_register(&dsp_misc); +#else + return 0; +#endif /* CONFIG_DEBUG_FS */ +} + +device_initcall(dsp_init); diff --git a/arch/arm/mach-msm/qdsp5/dsp_debug.h b/arch/arm/mach-msm/qdsp5/dsp_debug.h new file mode 100644 index 0000000000000000000000000000000000000000..bd40682c9cef07a8f8f73e75a9cd475542bc7041 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/dsp_debug.h @@ -0,0 +1,28 @@ +/* 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 __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 + +#define RAMA_BASE MSM_AD5_BASE +#define RAMB_BASE ((RAMA_BASE) + (0x200000)) +#define RAMC_BASE ((RAMB_BASE) + (0x200000)) +#define DSP_RAM_SIZE 0x40000 + +#endif diff --git a/arch/arm/mach-msm/qdsp5/evlog.h b/arch/arm/mach-msm/qdsp5/evlog.h new file mode 100644 index 0000000000000000000000000000000000000000..1f0f16bada258301a5e5619ba626f953b3abb8c3 --- /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 0000000000000000000000000000000000000000..f1db012b0a0ac609ffd61acfe6aac8e47a7889cb --- /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 0000000000000000000000000000000000000000..ba7efc30f2f2ed570c8db53369eaa88f5f25aa82 --- /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/qdsp5/snd_pcm_client.c b/arch/arm/mach-msm/qdsp5/snd_pcm_client.c new file mode 100644 index 0000000000000000000000000000000000000000..b58d3a2b6c2ed2869a08a6b9de99d0be65dd642c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/snd_pcm_client.c @@ -0,0 +1,522 @@ +/* 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 SND_VOC_PCM_INTERFACE_PROG 0x30000002 +#define SND_VOC_PCM_INTERFACE_VERS 0x00020004 + +/* Supply always 160 words of PCM samples (20ms data) */ +#define MAX_VOC_FRAME_SIZE 160 +#define VOC_FRAME_DURATION 20 +/* Buffering for Maximum 8 frames between userspace and driver */ +#define MAX_VOC_FRAMES 8 +#define BUFSZ ((MAX_VOC_FRAME_SIZE*2)*MAX_VOC_FRAMES) +#define SND_VOC_PCM_CLIENT_INPUT_FN_TYPE_PROC 3 +#define SND_VOC_REGISTER_PCM_INPUT_CLIENT_PROC 24 + +#define START_CALLBACK_ID 0x12345678 +#define STOP_CALLBACK_ID 0xffffffff + +#define MAX_WAIT_CONSUME (MAX_VOC_FRAMES * VOC_FRAME_DURATION) +/* PCM Interfaces */ +enum voice_pcm_interface_type { + VOICE_PCM_INTERFACE_TX_INPUT = 3, /* PCM Inject input to PreProc */ +}; + +enum voice_pcm_interface_reg_status_type { + SUCCESS = 0, /* Success 0, else failure */ +}; + +/* status used by PCM input callbacks to indicate availability of PCM Data */ +enum voice_pcm_data_status_type { + VOICE_PCM_DATA_STATUS_AVAILABLE, /* Data available for PCM input */ + VOICE_PCM_DATA_STATUS_UNAVAILABLE, /* Data not available */ + VOICE_PCM_DATA_STATUS_MAX +}; + +/* Argument needed to register PCM input client */ +struct snd_voice_pcm_interface_ipclnt_reg_args { + /* Interface number specifies the PCM inject point */ + enum voice_pcm_interface_type interface; + /* Non-NULL indicates start,NULL indicates stop */ + uint32_t callback_id; +}; + +struct snd_voice_pcm_interface_ipclnt_reg_status { + enum voice_pcm_interface_reg_status_type status; +}; + +struct snd_voice_pcm_interface_ipclnt_fn_type_args { + uint32_t callback_id; + uint32_t pcm_data_ptr_not_null; + uint32_t pcm_data_max_length; +}; + +struct snd_voice_pcm_interface_ipclnt_fn_type_reply { + enum voice_pcm_data_status_type status; + struct { + uint32_t pcm_data_len; + struct { + uint16_t pcm_data_ignore; + uint16_t pcm_data_valid; + } pcm_data_val[MAX_VOC_FRAME_SIZE]; + } pcm_data; +}; + +struct buffer { + void *data; + unsigned size; + unsigned used; +}; + +struct audio { + struct buffer out[MAX_VOC_FRAMES]; + + uint8_t out_head; + uint8_t out_tail; + + atomic_t out_bytes; + /* data allocated for various buffers */ + char *data; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t wait; + wait_queue_head_t stop_wait; + + int buffer_finished; + int opened; + int enabled; + int running; + int stopped; /* set when stopped */ + + struct msm_rpc_client *client; +}; + +static struct audio the_audio; + +static int snd_voice_pcm_interface_ipclnt_reg_args( + struct msm_rpc_client *client, void *buf, void *data) +{ + struct snd_voice_pcm_interface_ipclnt_reg_args *arg; + int size = 0; + + arg = (struct snd_voice_pcm_interface_ipclnt_reg_args *)data; + *((int *)buf) = cpu_to_be32(arg->interface); + size += sizeof(int); + buf += sizeof(int); + *((int *)buf) = cpu_to_be32(arg->callback_id); + size += sizeof(int); + + return size; +} + +static int snd_voice_pcm_interface_ipclnt_reg_status( + struct msm_rpc_client *client, void *buf, void *data) +{ + struct snd_voice_pcm_interface_ipclnt_reg_status *result = + (struct snd_voice_pcm_interface_ipclnt_reg_status *)buf; + + *((int *)data) = be32_to_cpu(result->status); + return 0; +} + +static void process_callback(struct audio *audio, + void *buffer, int in_size) +{ + uint32_t accept_status = RPC_ACCEPTSTAT_SUCCESS; + struct rpc_request_hdr *req; + struct snd_voice_pcm_interface_ipclnt_fn_type_args arg, *buf_ptr; + struct snd_voice_pcm_interface_ipclnt_fn_type_reply *reply; + struct buffer *frame; + uint32_t status; + uint32_t pcm_data_len; + + req = (struct rpc_request_hdr *)buffer; + buf_ptr = (struct snd_voice_pcm_interface_ipclnt_fn_type_args *)\ + (req + 1); + arg.callback_id = be32_to_cpu(buf_ptr->callback_id); + arg.pcm_data_ptr_not_null = be32_to_cpu(buf_ptr->pcm_data_ptr_not_null); + arg.pcm_data_max_length = be32_to_cpu(buf_ptr->pcm_data_max_length); + + MM_DBG("callback_id = 0x%8x pcm_data_ptr_not_null = 0x%8x"\ + "pcm_data_max_length = 0x%8x\n", arg.callback_id,\ + arg.pcm_data_ptr_not_null, arg.pcm_data_max_length); + /* Flag interface as running */ + if (!audio->running) + audio->running = 1; + if (!arg.pcm_data_ptr_not_null) { + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + msm_rpc_start_accepted_reply(audio->client, + be32_to_cpu(req->xid), accept_status); + msm_rpc_send_accepted_reply(audio->client, 0); + return; + } + reply = (struct snd_voice_pcm_interface_ipclnt_fn_type_reply *) + msm_rpc_start_accepted_reply(audio->client, + be32_to_cpu(req->xid), accept_status); + frame = audio->out + audio->out_tail; + /* If Data available, send data */ + if (frame->used) { + int i; + unsigned short *src = frame->data; + atomic_add(frame->used, &audio->out_bytes); + status = VOICE_PCM_DATA_STATUS_AVAILABLE; + pcm_data_len = MAX_VOC_FRAME_SIZE; + xdr_send_int32(&audio->client->cb_xdr, &status); + xdr_send_int32(&audio->client->cb_xdr, &pcm_data_len); + /* Expected cb_xdr buffer size is more than PCM buffer size */ + for (i = 0; i < MAX_VOC_FRAME_SIZE; i++, ++src) + xdr_send_int16(&audio->client->cb_xdr, src); + frame->used = 0; + audio->out_tail = ((++audio->out_tail) % MAX_VOC_FRAMES); + wake_up(&audio->wait); + } else { + status = VOICE_PCM_DATA_STATUS_UNAVAILABLE; + pcm_data_len = 0; + xdr_send_int32(&audio->client->cb_xdr, &status); + xdr_send_int32(&audio->client->cb_xdr, &pcm_data_len); + wake_up(&audio->wait); + /* Flag all buffer completed */ + if (audio->stopped) { + audio->buffer_finished = 1; + wake_up(&audio->stop_wait); + } + } + MM_DBG("Provided PCM data = 0x%8x\n", reply->status); + msm_rpc_send_accepted_reply(audio->client, 0); + return; +} + +static int pcm_interface_process_callback_routine(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + struct rpc_request_hdr *req; + struct audio *audio = &the_audio; + int rc = 0; + + req = (struct rpc_request_hdr *)buffer; + + MM_DBG("proc id = 0x%8x xid = 0x%8x size = 0x%8x\n", + be32_to_cpu(req->procedure), be32_to_cpu(req->xid), in_size); + switch (be32_to_cpu(req->procedure)) { + /* Procedure which called every 20ms for PCM samples request*/ + case SND_VOC_PCM_CLIENT_INPUT_FN_TYPE_PROC: + process_callback(audio, buffer, in_size); + break; + default: + MM_ERR("Not supported proceudure 0x%8x\n", + be32_to_cpu(req->procedure)); + /* Not supported RPC Procedure, send nagative code */ + msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid), + RPC_ACCEPTSTAT_PROC_UNAVAIL); + msm_rpc_send_accepted_reply(client, 0); + } + return rc; +} + +static void audio_flush(struct audio *audio) +{ + int cnt; + for (cnt = 0; cnt < MAX_VOC_FRAMES; cnt++) + audio->out[cnt].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->stopped = 0; + audio->running = 0; + audio->buffer_finished = 0; +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + int rc; + struct snd_voice_pcm_interface_ipclnt_reg_args arg; + struct snd_voice_pcm_interface_ipclnt_reg_status result; + + /* voice_pcm_interface_type */ + arg.interface = VOICE_PCM_INTERFACE_TX_INPUT; + /* Should be non-zero, unique */ + arg.callback_id = START_CALLBACK_ID; + /* Start Voice PCM interface */ + rc = msm_rpc_client_req(audio->client, + SND_VOC_REGISTER_PCM_INPUT_CLIENT_PROC, + snd_voice_pcm_interface_ipclnt_reg_args, &arg, + snd_voice_pcm_interface_ipclnt_reg_status, + &result, -1); + MM_DBG("input client registration status rc 0x%8x result 0x%8x\n", + rc, result.status); + /* If error in server side */ + if (rc == 0) + if (result.status != SUCCESS) + rc = -ENODEV; + return rc; +} +static int audio_disable(struct audio *audio) +{ + int rc; + struct snd_voice_pcm_interface_ipclnt_reg_args arg; + struct snd_voice_pcm_interface_ipclnt_reg_status result; + + /* Wait till all buffers consumed to prevent data loss + Also ensure if client stops due to vocoder disable + do not loop forever */ + rc = wait_event_interruptible_timeout(audio->stop_wait, + !(audio->running) || (audio->buffer_finished == 1), + msecs_to_jiffies(MAX_WAIT_CONSUME)); + if (rc < 0) + return 0; + /* voice_pcm_interface_type */ + arg.interface = VOICE_PCM_INTERFACE_TX_INPUT; + arg.callback_id = STOP_CALLBACK_ID; /* Should be zero */ + /* Stop Voice PCM interface */ + rc = msm_rpc_client_req(audio->client, + SND_VOC_REGISTER_PCM_INPUT_CLIENT_PROC, + snd_voice_pcm_interface_ipclnt_reg_args, &arg, + snd_voice_pcm_interface_ipclnt_reg_status, + &result, -1); + MM_DBG("input client de-registration status rc 0x%8x result 0x%8x\n", + rc, result.status); + /* If error in server side */ + if (rc == 0) + if (result.status != SUCCESS) + rc = -ENODEV; + return rc; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + + 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; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + rc = audio_enable(audio); + if (rc == 0) + audio->enabled = 1; + break; + case AUDIO_STOP: + if (audio->enabled) { + audio->stopped = 1; + rc = audio_disable(audio); + if (rc == 0) { + audio->enabled = 0; + audio->running = 0; + wake_up(&audio->wait); + } else + audio->stopped = 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.type == 0) { + /* Selection for different PCM intect point */ + } else { + rc = -EINVAL; + break; + } + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = MAX_VOC_FRAME_SIZE * 2; + config.buffer_count = MAX_VOC_FRAMES; + config.sample_rate = 8000; + config.channel_count = 1; + config.type = 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; + } + default: { + rc = -EINVAL; + MM_ERR(" Unsupported ioctl 0x%8x\n", cmd); + } + } + mutex_unlock(&audio->lock); + return rc; +} +static ssize_t audio_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +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; + int rc = 0; + + mutex_lock(&audio->write_lock); + /* Ensure to copy only till frame boundary */ + while (count >= (MAX_VOC_FRAME_SIZE*2)) { + frame = audio->out + audio->out_head; + rc = wait_event_interruptible_timeout(audio->wait,\ + (frame->used == 0) || (audio->stopped), + msecs_to_jiffies(MAX_WAIT_CONSUME)); + + if (rc < 0) + break; + if (audio->stopped) { + rc = -EBUSY; + break; + } + if (rc == 0) { + rc = -ETIMEDOUT; + 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 = ((++audio->out_head) % MAX_VOC_FRAMES); + count -= xfer; + buf += xfer; + } + mutex_unlock(&audio->write_lock); + MM_DBG("write done 0x%8x\n", (unsigned int)(buf - start)); + if (rc < 0) + return rc; + return buf - start; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = &the_audio; + int rc, cnt; + + mutex_lock(&audio->lock); + + if (audio->opened) { + MM_ERR("busy as driver already in open state\n"); + rc = -EBUSY; + goto done; + } + + if (!audio->data) { + audio->data = kmalloc(BUFSZ, GFP_KERNEL); + if (!audio->data) { + MM_ERR("could not allocate buffers\n"); + rc = -ENOMEM; + goto done; + } + } + + audio->client = msm_rpc_register_client("voice_pcm_interface_client", + SND_VOC_PCM_INTERFACE_PROG, + SND_VOC_PCM_INTERFACE_VERS, 1, + pcm_interface_process_callback_routine); + if (IS_ERR(audio->client)) { + MM_ERR("Failed to register voice pcm interface client"\ + "to 0x%8x\n", SND_VOC_PCM_INTERFACE_PROG); + kfree(audio->data); + audio->data = NULL; + rc = -ENODEV; + goto done; + } + MM_INFO("voice pcm client registred %p\n", audio->client); + for (cnt = 0; cnt < MAX_VOC_FRAMES; cnt++) { + audio->out[cnt].data = (audio->data +\ + ((MAX_VOC_FRAME_SIZE * 2) * cnt)); + audio->out[cnt].size = MAX_VOC_FRAME_SIZE * 2; + MM_DBG("data ptr = %p\n", audio->out[cnt].data); + } + file->private_data = audio; + audio_flush(audio); + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + if (audio->enabled) { + audio->stopped = 1; + audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + wake_up(&audio->wait); + } + msm_rpc_unregister_client(audio->client); + kfree(audio->data); + audio->data = NULL; + audio->opened = 0; + mutex_unlock(&audio->lock); + return 0; +} + +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, +}; + +static struct miscdevice audio_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "snd_pcm_client", + .fops = &audio_fops, +}; + +static int __init audio_init(void) +{ + mutex_init(&the_audio.lock); + mutex_init(&the_audio.write_lock); + init_waitqueue_head(&the_audio.wait); + init_waitqueue_head(&the_audio.stop_wait); + return misc_register(&audio_misc); +} +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5v2/Makefile b/arch/arm/mach-msm/qdsp5v2/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3ae3c1b1fb402d2d5cf078324b1c0b9a417794be --- /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 0000000000000000000000000000000000000000..acd9c4c1bcc1ad1b70b3c6ea2e0591157c095c8f --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp.c @@ -0,0 +1,1225 @@ +/* + * Register/Interrupt access for userspace aDSP library. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009,2011-2012 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 "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 */ + +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_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(adsp_info.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(adsp_info.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.int_adsp = platform_get_irq(pdev, 0); + if (adsp_info.int_adsp < 0) { + MM_ERR("no irq resource?\n"); + return -ENODEV; + } + + 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(adsp_info.int_adsp, adsp_irq_handler, + IRQF_TRIGGER_RISING, "adsp", 0); + if (rc < 0) + goto fail_request_irq; + disable_irq(adsp_info.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_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(adsp_info.int_adsp); + free_irq(adsp_info.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, + }, +}; + +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_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 0000000000000000000000000000000000000000..5aceff9777c9a09bd382529e3a103507fa415b70 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp.h @@ -0,0 +1,339 @@ +/* + * 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; + + /* Interrupt value */ + int int_adsp; +}; + +#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 0000000000000000000000000000000000000000..2a4e4ec6387a75e44d041e51948ec41bdde71046 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp_driver.c @@ -0,0 +1,656 @@ +/* + * 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 +#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 0000000000000000000000000000000000000000..40263673eb2b10381bd8a017345c0c696aec9a7b --- /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 0000000000000000000000000000000000000000..20c9898ef8f87c88499002f9f67d2ec5454ce871 --- /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 0000000000000000000000000000000000000000..733b7a178dbacea6852939c989e2f3b325c09661 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_a2dp_in.c @@ -0,0 +1,991 @@ +/* Copyright (c) 2010-2012, 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 +#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; + void *msm_map; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int abort; /* set when error, like sample rate mismatch */ + char *build_id; +}; + +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)); + if (audio->build_id[17] == '1') { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + } else { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + } + 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->msm_map); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (audio->phys) { + audio->msm_map = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->msm_map)) { + MM_ERR("could not map the phys address to kernel" + "space\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto done; + } + audio->data = (u8 *)audio->msm_map; + } 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; + } + audio->build_id = socinfo_get_build_id(); + MM_DBG("Modem build id = %s\n", audio->build_id); + 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 0000000000000000000000000000000000000000..05bca03d6a9f57866c32842e7f189ad8c3d7e0f2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_aac.c @@ -0,0 +1,2037 @@ +/* + * aac audio decoder device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, SZ_4K); + if (audio->phys) { + audio->map_v_write = + ioremap(audio->phys, + pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = (u8 *)audio->map_v_write; + 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 = allocate_contiguous_ebi_nomap(PCM_BUFSZ_MIN + * PCM_BUF_MAX_COUNT, SZ_4K); + if (!audio->read_phys) { + MM_ERR("could not allocate read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->map_v_read = ioremap(audio->read_phys, + PCM_BUFSZ_MIN * PCM_BUF_MAX_COUNT); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map read phys address, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + free_contiguous_memory_by_paddr(audio->read_phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->read_data = audio->map_v_read; + 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..8aee9466da7c8f7cba921a8856c90db40b82bfb0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_aac_in.c @@ -0,0 +1,1482 @@ +/* + * aac audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 + +/* 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; + void *map_v_read; + void *map_v_write; + + 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 */ + char *build_id; +}; + +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)); + if (audio->build_id[17] == '1') { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG_2 command"); + } else { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG command"); + } + 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, loff_t ppos1, loff_t ppos2, 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->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + audio->data = NULL; + } + if (audio->out_data) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (audio->phys) { + audio->map_v_read = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map DMA buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto done; + } + audio->data = audio->map_v_read; + } 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 = allocate_contiguous_ebi_nomap(BUFFER_SIZE, SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->map_v_write = ioremap( + audio->out_phys, BUFFER_SIZE); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, (int)audio->out_data); + } + audio->build_id = socinfo_get_build_id(); + MM_DBG("Modem build id = %s\n", audio->build_id); + + /* 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..89957a4b22caee21f8eefea88a5dc636ffbe6a79 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_acdb.c @@ -0,0 +1,3448 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/* 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; + void *map_v_fluence; + + 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; + void *map_v_get_blk; + char *build_id; +}; + +static struct acdb_data acdb_data; + +struct acdb_cache_node { + u32 node_status; + s32 stream_id; + u32 phys_addr_acdb_values; + void *map_v_addr; + 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; + void *map_v_rtc; +}; + +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->map_v_rtc); + free_contiguous_memory_by_paddr(rtc_read->phys); + } + if (rtc_write->viraddr != NULL || ((void *)rtc_write->phys) != NULL) { + iounmap(rtc_write->map_v_rtc); + free_contiguous_memory_by_paddr(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; + if (acdb_data.build_id[17] == '1') { + 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 = allocate_contiguous_ebi_nomap(PMEM_RTC_ACDB_QUERY_MEM, + SZ_4K); + + if (!rtc_read->phys) { + MM_ERR("ACDB Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + rtc_read->map_v_rtc = ioremap(rtc_read->phys, + PMEM_RTC_ACDB_QUERY_MEM); + + if (IS_ERR(rtc_read->map_v_rtc)) { + MM_ERR("ACDB Could not map physical address\n"); + result = -ENOMEM; + goto error; + } + rtc_read->viraddr = rtc_read->map_v_rtc; + memset(rtc_read->viraddr, 0, PMEM_RTC_ACDB_QUERY_MEM); + + rtc_write->phys = allocate_contiguous_ebi_nomap(PMEM_RTC_ACDB_QUERY_MEM, + SZ_4K); + + if (!rtc_write->phys) { + MM_ERR("ACDB Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + rtc_write->map_v_rtc = ioremap(rtc_write->phys, + PMEM_RTC_ACDB_QUERY_MEM); + + if (IS_ERR(rtc_write->map_v_rtc)) { + MM_ERR("ACDB Could not map physical address\n"); + result = -ENOMEM; + goto error; + } + rtc_write->viraddr = rtc_write->map_v_rtc; + 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->map_v_rtc); + free_contiguous_memory_by_paddr(rtc_read->phys); + } + if (rtc_write->viraddr != NULL || ((void *)rtc_write->phys) != NULL) { + iounmap(rtc_write->map_v_rtc); + free_contiguous_memory_by_paddr(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) && + ((audcal_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"); + } + if (acdb_data.build_id[17] != '0') { + 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 = + allocate_contiguous_ebi_nomap(ACDB_BUF_SIZE, + SZ_4K); + + if (!acdb_cache_tx[i].phys_addr_acdb_values) { + MM_ERR("ACDB=> Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + acdb_cache_tx[i].map_v_addr = ioremap( + acdb_cache_tx[i].phys_addr_acdb_values, + ACDB_BUF_SIZE); + if (IS_ERR(acdb_cache_tx[i].map_v_addr)) { + MM_ERR("ACDB=> Could not map physical address\n"); + result = -ENOMEM; + free_contiguous_memory_by_paddr( + acdb_cache_tx[i].phys_addr_acdb_values); + goto error; + } + acdb_cache_tx[i].virt_addr_acdb_values = + acdb_cache_tx[i].map_v_addr; + 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[err].map_v_addr); + free_contiguous_memory_by_paddr( + acdb_cache_tx[err].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 = + allocate_contiguous_ebi_nomap( + ACDB_BUF_SIZE, SZ_4K); + + if (!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].map_v_addr = + ioremap(acdb_cache_rx[i].phys_addr_acdb_values, + ACDB_BUF_SIZE); + if (IS_ERR(acdb_cache_rx[i].map_v_addr)) { + MM_ERR("ACDB=> Could not map physical address\n"); + result = -ENOMEM; + free_contiguous_memory_by_paddr( + acdb_cache_rx[i].phys_addr_acdb_values); + goto error; + } + acdb_cache_rx[i].virt_addr_acdb_values = + acdb_cache_rx[i].map_v_addr; + 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[err].map_v_addr); + free_contiguous_memory_by_paddr( + acdb_cache_rx[err].phys_addr_acdb_values); + } + return result; +} + +static u32 allocate_memory_acdb_get_blk(void) +{ + u32 result = 0; + acdb_data.get_blk_paddr = allocate_contiguous_ebi_nomap( + ACDB_BUF_SIZE, SZ_4K); + if (!acdb_data.get_blk_paddr) { + MM_ERR("ACDB=> Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + acdb_data.map_v_get_blk = ioremap(acdb_data.get_blk_paddr, + ACDB_BUF_SIZE); + if (IS_ERR(acdb_data.map_v_get_blk)) { + MM_ERR("ACDB=> Could not map physical address\n"); + result = -ENOMEM; + free_contiguous_memory_by_paddr( + acdb_data.get_blk_paddr); + goto error; + } + acdb_data.get_blk_kvaddr = acdb_data.map_v_get_blk; + 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].map_v_addr); + free_contiguous_memory_by_paddr( + 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].map_v_addr); + free_contiguous_memory_by_paddr( + acdb_cache_tx[i].phys_addr_acdb_values); + } +} + +static void free_memory_acdb_get_blk(void) +{ + iounmap(acdb_data.map_v_get_blk); + free_contiguous_memory_by_paddr(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 *) allocate_contiguous_ebi_nomap( + PBE_BUF_SIZE, SZ_4K); + if (!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 = allocate_contiguous_ebi_nomap( + FLUENCE_BUF_SIZE, SZ_4K); + if (!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); + free_contiguous_memory_by_paddr((int32_t)acdb_data.pbe_extbuff); + result = -ENOMEM; + goto done; + } + acdb_data.map_v_fluence = ioremap( + acdb_data.fluence_extbuff, + FLUENCE_BUF_SIZE); + if (IS_ERR(acdb_data.map_v_fluence)) { + 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); + free_contiguous_memory_by_paddr( + (int32_t)acdb_data.pbe_extbuff); + free_contiguous_memory_by_paddr( + (int32_t)acdb_data.fluence_extbuff); + result = -ENOMEM; + goto done; + } else + acdb_data.fluence_extbuff_virt = + acdb_data.map_v_fluence; +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 { + 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; + } + 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) { + if (--acdb_cache_rx[acdb_data.\ + device_info->dev_id].stream_id <= 0) { + acdb_data.acdb_state &= ~AUDPP_READY; + acdb_cache_rx[acdb_data.device_info->dev_id]\ + .stream_id = 0; + 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 (acdb_data.build_id[17] == '1') { + 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.build_id[17] != '0') { + 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; + if (acdb_data.build_id[17] != '0') { + 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; + } + + acdb_data.build_id = socinfo_get_build_id(); + MM_INFO("build id used is = %s\n", acdb_data.build_id); + +#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].map_v_addr); + free_contiguous_memory_by_paddr( + acdb_cache_tx[i].phys_addr_acdb_values); + } + iounmap(acdb_cache_rx[i].map_v_addr); + free_contiguous_memory_by_paddr( + 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); + free_contiguous_memory_by_paddr( + (int32_t)acdb_data.pbe_extbuff); + iounmap(acdb_data.map_v_fluence); + free_contiguous_memory_by_paddr( + (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 0000000000000000000000000000000000000000..95f0547ed010fce2339cf65c396d92012c221699 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_adpcm.c @@ -0,0 +1,1754 @@ +/* Copyright (c) 2009-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audadpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +/* 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("read buf map fail\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 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); +} + +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 = allocate_contiguous_ebi_nomap(pmem_sz, + SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap(audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..55c49b34ecc13c4245db87ba0de5aa37e014d291 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_amrnb.c @@ -0,0 +1,1645 @@ +/* + * amrnb audio decoder device + * + * Copyright (c) 2008-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrnb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +/* 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 = allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read phys address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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); + 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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + free_contiguous_memory_by_paddr(audio->phys); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..790c510163b415c1116fdf0602b4637304662aa5 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_amrnb_in.c @@ -0,0 +1,903 @@ +/* + * amrnb audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 + +/* 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; + void *map_v_read; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + char *build_id; +}; + +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)); + if (audio->build_id[17] == '1') { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG_2 command"); + } else { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG command"); + } + 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->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (audio->phys) { + audio->map_v_read = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map DMA buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto done; + } + audio->data = audio->map_v_read; + } 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; + } + audio->build_id = socinfo_get_build_id(); + MM_DBG("Modem build id = %s\n", audio->build_id); + + 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 0000000000000000000000000000000000000000..a653d5bffb36c8c724398816859a11823950cb3b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_amrwb.c @@ -0,0 +1,1727 @@ +/* amrwb audio decoder device + * + * Copyright (c) 2008-2012, 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 +#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 */ + + void *map_v_read; + void *map_v_write; + + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrwb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +/* 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 = allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("Error could not map read" + " phys address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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); + 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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..b6d6e5e4346db58bd142b525740b7620acc24a5a --- /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 0000000000000000000000000000000000000000..7306e986d18d5f723bd78ae3153b4f6c200cecd8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_evrc.c @@ -0,0 +1,1640 @@ +/* + * Copyright (c) 2008-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audevrc_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +/* 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read" + " phy address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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); + 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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("failed to map write physical address, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..1ee502969a1469908d436c8a12c7c41a40def1db --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c @@ -0,0 +1,1507 @@ +/* + * evrc audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 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; + void *map_v_read; + void *map_v_write; + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + char *build_id; +}; + +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)); + if (audio->build_id[17] == '1') { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG_2 command"); + } else { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG command"); + } + 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, loff_t ppos1, loff_t ppos2, 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->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + audio->data = NULL; + } + if (audio->out_data) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (audio->phys) { + audio->map_v_read = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read physical address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto done; + } + audio->data = audio->map_v_read; + } 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 = allocate_contiguous_ebi_nomap(BUFFER_SIZE, + SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->map_v_write = ioremap(audio->out_phys, BUFFER_SIZE); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could map write buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(audio->out_phys); + goto evt_error; + } + audio->mfield = META_OUT_SIZE; + file->private_data = audio; + audio->opened = 1; + audio->out_frame_cnt++; + audio->build_id = socinfo_get_build_id(); + MM_DBG("Modem build id = %s\n", audio->build_id); + +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 0000000000000000000000000000000000000000..af65c802587568b7c73792492d8e15063694f200 --- /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 0000000000000000000000000000000000000000..785ed8ee5d6cd2c0db75243ea277d5173a4c15cb --- /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 0000000000000000000000000000000000000000..d5fb2e9d2399d2e6fd015f6e0c12736b157dce51 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_lpa.c @@ -0,0 +1,1748 @@ +/* low power audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 MAX_BUF 4 +#define BUFSZ (524288) + +#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); + /*restore the POPP gain to 0x2000 + this is needed to avoid use cases + where POPP volume is lowered during + NON HLB playback, when device moved + from NON HLB to HLB POPP is not + disabled but POPP gain will be retained + as the old one which result + in lower volume*/ + audio->vol_pan.volume = 0x2000; + audpp_dsp_set_vol_pan( + audio->dec_id, + &audio->vol_pan, POPP); + } 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)) { + MM_DBG("audlpa_async_send_buffer - sending the" + "rest of the buffer bassedon AV sync"); + 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; + } else if ((signed)(temp >= 0) && + ((signed)(next_buf->buf.data_len - + temp) < 0)) { + MM_DBG("audlpa_async_send_buffer - else case:" + "sending the rest of the buffer bassedon" + "AV sync"); + cmd.buf_ptr = (unsigned) next_buf->paddr; + cmd.buf_size = next_buf->buf.data_len >> 1; + cmd.partition_number = 0; + audio->bytecount_given = audio->bytecount_head + + next_buf->buf.data_len; + 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->device_switch != + DEVICE_SWITCH_STATE_COMPLETE) { + 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) { + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + rc = audpp_pause(audio->dec_id, (int) arg); + if (rc < 0) { + MM_ERR("%s: pause cmd failed rc=%d\n", + __func__, rc); + rc = -EINTR; + break; + } + } + 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; + audio->buffer_count = config.buffer_count; + audio->buffer_size = config.buffer_size; + 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 = audio->buffer_count; + config.buffer_size = audio->buffer_size; + 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, loff_t ppos1, loff_t ppos2, 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; + audio->buffer_size = BUFSZ; + audio->buffer_count = MAX_BUF; + + 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 0000000000000000000000000000000000000000..53ae0a470d755098d5cff3459dccc201c40008ab --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_mp3.c @@ -0,0 +1,2521 @@ +/* mp3 audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 +#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 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; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + void *map_v_read; + void *map_v_write; + + 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read buffer" + " physical address\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + } + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, + SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap( + audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("failed to map write physical" + " address , freeing instance" + "0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..fae240120570c577b266204087208860015f6d45 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_mvs.c @@ -0,0 +1,1754 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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[Q5V2_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 q5v2_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) { + /* PCM don't have frame_type */ + buf_node->frame.frame_type = 0; + } else if (frame_mode == MVS_FRAME_MODE_VOC_TX) { + /* Extracting EVRC current buffer frame rate*/ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL EVRC frame_type %d\n", + __func__, be32_to_cpu(*args)); + } 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(buf_node->frame.frame_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 q5v2_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 q5v2_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 q5v2_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 q5v2_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 q5v2_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 0000000000000000000000000000000000000000..288a717ca03766b2cc1a0b0345159e192bf8c767 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_out.c @@ -0,0 +1,728 @@ +/* + * pcm audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 + +#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; + void *map_v_write; + 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, loff_t ppos1, loff_t ppos2, 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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (the_audio.phys) { + the_audio.map_v_write = ioremap(the_audio.phys, DMASZ); + if (IS_ERR(the_audio.map_v_write)) { + MM_ERR("could not map physical buffers\n"); + free_contiguous_memory_by_paddr(the_audio.phys); + return -ENOMEM; + } + the_audio.data = the_audio.map_v_write; + } else { + MM_ERR("could not allocate physical 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 0000000000000000000000000000000000000000..4b308b05a4b8b8ee8aa94aa4e8dcb9ff23ebf2fb --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_pcm.c @@ -0,0 +1,1707 @@ +/* arch/arm/mach-msm/qdsp5v2/audio_pcm.c + * + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 +#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 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; + 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; + void *map_v_write; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(pmem_sz, + SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap( + audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys\ + address freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..ce67ebbafdd73e5bfe91469948848def9ddd534b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c @@ -0,0 +1,976 @@ +/* + * pcm audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 + +/* 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; + void *map_v_read; + + 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; + char *build_id; +}; + +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)); + if (audio->build_id[17] == '1') { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG_2 command"); + } else { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG command"); + } + 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 (audio->build_id[17] == '1') { + audio->enc_type = ENC_TYPE_EXT_WAV | audio->mode; + 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; + } + } else if (audio->build_id[17] == '0') { + audio->enc_type = ENC_TYPE_WAV | audio->mode; + if (cfg.channel_count == 1) { + cfg.channel_count = AUDREC_CMD_MODE_MONO; + audio->buffer_size = MONO_DATA_SIZE_1024; + } else if (cfg.channel_count == 2) { + cfg.channel_count = AUDREC_CMD_MODE_STEREO; + audio->buffer_size = STEREO_DATA_SIZE_1024; + } + } else { + MM_ERR("wrong build_id = %s\n", audio->build_id); + return -ENODEV; + } + 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->map_v_read); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (audio->phys) { + audio->map_v_read = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map read phys buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto done; + } + audio->data = audio->map_v_read; + } 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; + audio->build_id = socinfo_get_build_id(); + MM_DBG("Modem build id = %s\n", audio->build_id); +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 0000000000000000000000000000000000000000..c53922bcbd58aece7a744f7fff593e0387bd94a0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_qcelp.c @@ -0,0 +1,1639 @@ +/* + * qcelp 13k audio decoder device + * + * Copyright (c) 2008-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audqcelp_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +/* 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("failed to map read buf\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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); + 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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (!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->map_v_write = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write phys address, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..e1af2ad7f73b2c588f4f64a1da480e165ab1a24f --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c @@ -0,0 +1,1513 @@ +/* + * qcelp audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 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; + void *map_v_read; + void *map_v_write; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + char *build_id; +}; + +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)); + if (audio->build_id[17] == '1') { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG_2 command"); + } else { + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + MM_ERR("sending AUDPREPROC_AUDREC_CMD_ENC_CFG command"); + } + 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, loff_t ppos1, loff_t ppos2, 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->map_v_read); + free_contiguous_memory_by_paddr(audio->phys); + audio->data = NULL; + } + if (audio->out_data) { + iounmap(audio->map_v_write); + free_contiguous_memory_by_paddr(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 = allocate_contiguous_ebi_nomap(DMASZ, SZ_4K); + if (audio->phys) { + audio->map_v_read = ioremap(audio->phys, DMASZ); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("could not map DMA buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + goto done; + } + audio->data = audio->map_v_read; + } 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 = allocate_contiguous_ebi_nomap(BUFFER_SIZE, SZ_4K); + if (!audio->out_phys) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->map_v_write = ioremap(audio->out_phys, BUFFER_SIZE); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->out_phys); + goto evt_error; + } + audio->out_data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(audio->out_phys); + goto evt_error; + } + audio->mfield = META_OUT_SIZE; + file->private_data = audio; + audio->opened = 1; + audio->out_frame_cnt++; + audio->build_id = socinfo_get_build_id(); + MM_DBG("Modem build id = %s\n", audio->build_id); +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 0000000000000000000000000000000000000000..80adebd086ec76d40202e3064d4cd15f7b56863c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_wma.c @@ -0,0 +1,1797 @@ +/* Copyright (c) 2009-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwma_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif +/* 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("read buf alloc fail\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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); + 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 = allocate_contiguous_ebi_nomap(pmem_sz, SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap(audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..ca072b39d43d930f922bffede68e41d1ac20a962 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_wmapro.c @@ -0,0 +1,1815 @@ +/* Copyright (c) 2009-2012, 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 +#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 */ + void *map_v_read; + void *map_v_write; + + 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); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwmapro_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +/* 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 = + allocate_contiguous_ebi_nomap( + config.buffer_size * + config.buffer_count, + SZ_4K); + if (!audio->read_phys) { + rc = -ENOMEM; + break; + } + audio->map_v_read = ioremap( + audio->read_phys, + config.buffer_size * + config.buffer_count); + if (IS_ERR(audio->map_v_read)) { + MM_ERR("read buf map fail\n"); + rc = -ENOMEM; + free_contiguous_memory_by_paddr( + audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->read_data = + audio->map_v_read; + 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, loff_t ppos1, loff_t ppos2, 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->map_v_write); + free_contiguous_memory_by_paddr(audio->phys); + if (audio->read_data) { + iounmap(audio->map_v_read); + free_contiguous_memory_by_paddr(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); + 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 = allocate_contiguous_ebi_nomap(pmem_sz, SZ_4K); + if (audio->phys) { + audio->map_v_write = ioremap(audio->phys, pmem_sz); + if (IS_ERR(audio->map_v_write)) { + MM_ERR("could not map write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + free_contiguous_memory_by_paddr(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->data = audio->map_v_write; + 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->map_v_write); + free_contiguous_memory_by_paddr(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 0000000000000000000000000000000000000000..31ce6431442555fff2215ddb6fcf489ba2232f68 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audpp.c @@ -0,0 +1,1140 @@ +/* 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]; + uint16_t n = 0; + msg[0] = arg; + audpp->func[id] (audpp->private[id], event, msg); + if (audpp->enabled == 1) { + 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, + AUDPP_MSG_CFG_MSG, 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 0000000000000000000000000000000000000000..09611a552cedf02fb9ff88fb463d68f67fd57a40 --- /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 0000000000000000000000000000000000000000..4cc834d10a695c6d25f6bf4c01bc34bb2b1085bb --- /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 0000000000000000000000000000000000000000..c4e0feeaa0a5ed8ae4afb5e9464c04e73b0977b4 --- /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 0000000000000000000000000000000000000000..e38f164eb12e69205f0c74c72c29cb46ac66bc0e --- /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 0000000000000000000000000000000000000000..0b20be0b27938f9d21e983a4ccb1e2d6e3987b19 --- /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 0000000000000000000000000000000000000000..d7935a727c906ec46156d7bac4a2cbf67853e069 --- /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 0000000000000000000000000000000000000000..b15d4c461b75ab760a3fbc7bbfc9ab114c3f89e1 --- /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 0000000000000000000000000000000000000000..c0a48c826a5762a37b9dcecac1a0ddff5714671a --- /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 0000000000000000000000000000000000000000..a5da912b29bdf38c8cbef6c32bef4b10d36dc454 --- /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 0000000000000000000000000000000000000000..dbeda82afa2262abc72167d328f3c1b4959ff08e --- /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 0000000000000000000000000000000000000000..939cc8b38489b6a2bf317910cc53562d6af86233 --- /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 0000000000000000000000000000000000000000..cd93345d6019eea10baf478bffa3d6068805cfe2 --- /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 0000000000000000000000000000000000000000..d9003cdd75ef3d79efa34f705ffc2d33791885c1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/timpani_profile_7x30.h @@ -0,0 +1,623 @@ +/* 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(0xB4, 0xFF, 0xCE)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0xFF, 0x5A)}, \ + {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 0000000000000000000000000000000000000000..026acb33aa4619dfd2499f5157e26e61cd96de1b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/voice.c @@ -0,0 +1,752 @@ +/* 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); + } + if (v->voc_state == VOICE_CHANGE) { + MM_DBG("voc_state is in VOICE_CHANGE\n"); + v->voc_state = VOICE_ACQUIRE; + } + } + 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 0000000000000000000000000000000000000000..9a5561261bfc57a051e83c34e6187a5c32618c44 --- /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 0000000000000000000000000000000000000000..9e1d5b6cdfc451ff24627b635c367a3cf5084c34 --- /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 0000000000000000000000000000000000000000..e7756e14831598318c32a563652af72329671459 --- /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 0000000000000000000000000000000000000000..688f57e341020a5f2c8c0ad13456b74f98a61b97 --- /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 0000000000000000000000000000000000000000..ab1df3992e4a15fef2e80d561751b5b70c6bd50a --- /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 0000000000000000000000000000000000000000..86ab9aeab713d50442ecf12163daed9728e50cb8 --- /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 0000000000000000000000000000000000000000..fe6c049a35fb5e6ed33aab4e9cf0661e9f6bacfd --- /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 0000000000000000000000000000000000000000..b877977ac1ffe6936004db7182ba4517b58964b3 --- /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 0000000000000000000000000000000000000000..1df4f5d2f671320a787b602b93ffd91092dabf99 --- /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 0000000000000000000000000000000000000000..286d85d8ce592970e712665bec4572951eae1332 --- /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 0000000000000000000000000000000000000000..d88b7ad36621d3f2079876d9f137847806ed106a --- /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 0000000000000000000000000000000000000000..e828e9c14a71e96a945bf8d2d6301579c90fce86 --- /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 0000000000000000000000000000000000000000..52de785b011c897259464a25ba98b4ce6f223ee5 --- /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 0000000000000000000000000000000000000000..348aad163ffe4f329a1790ff5e92da3bd68fd457 --- /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 0000000000000000000000000000000000000000..62c1122b0e176ad6c296f3fff6342c4ee87d3e93 --- /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 0000000000000000000000000000000000000000..88f19b70e5b1c0d4d2174d54d6acb4df75af3149 --- /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 0000000000000000000000000000000000000000..0781eda6efd9f49da36ece2a5a08723bc7c2e8ed --- /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 0000000000000000000000000000000000000000..6ef21952f25dfd9d7699a7ab5183b6a782314074 --- /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 0000000000000000000000000000000000000000..6743c6c77413c23c2946950c47fd973f8aabcff7 --- /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 0000000000000000000000000000000000000000..f713e3dff01b7a09628cbf18921efad777fff876 --- /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 0000000000000000000000000000000000000000..aa8a6990393591a53a6b2565b9c8e54c31eb4de6 --- /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 0000000000000000000000000000000000000000..a13084f61275e0a8ab5cab9f8edc2f035cd0a40b --- /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 0000000000000000000000000000000000000000..1a2476b2ffc0e2a18b2cdb4ef875cbcfaf190c10 --- /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 0000000000000000000000000000000000000000..906c5341a9c067171ae8aa93bcaaff2a3c32124d --- /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 0000000000000000000000000000000000000000..4195454776c243b84bb2bbd9445beb55a5b9a229 --- /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 0000000000000000000000000000000000000000..b6805973f36bf12a8c1954661fea5c3314ebb538 --- /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 0000000000000000000000000000000000000000..378432b66c07c76620757fac3005c4cd1dcb0c84 --- /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 0000000000000000000000000000000000000000..1176eb9c10271036cb083513fa25abcd527e1a3d --- /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 0000000000000000000000000000000000000000..dfb1fefdae9324c74733dd1856150f9145c1bfd1 --- /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 0000000000000000000000000000000000000000..6abc60c66ec970746287834f84fdfb0d2d6f3aef --- /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 0000000000000000000000000000000000000000..25d1e4f45e3a5eea23533aa7c43559b19f4f6499 --- /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 0000000000000000000000000000000000000000..638269398ea5938af4cfb85796fa81f22fec8c3e --- /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 0000000000000000000000000000000000000000..922f8cd39cdcfe7d60d5cde68dce91e95918ccd6 --- /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 +#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 0000000000000000000000000000000000000000..cf2748807bea17d7bdcf956a87076740cc23d0ff --- /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 0000000000000000000000000000000000000000..9fc412bc81a803cf6487657c83e127288cc3750a --- /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 0000000000000000000000000000000000000000..16f6204febb7cf9d4c0c074b5a2c0d26547aca62 --- /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 0000000000000000000000000000000000000000..c79f0c4111645f949b6e169e747655abe76ba5a9 --- /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 0000000000000000000000000000000000000000..bd5d3f60b3873a6197d89697203cc3c6d0d016c8 --- /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 0000000000000000000000000000000000000000..c6bddb883eae0b25ef89da062601d8475b73a6ac --- /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 0000000000000000000000000000000000000000..2e91cb2fed868336b5c4696496ef28c7e1d02e75 --- /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 0000000000000000000000000000000000000000..bf6f115f91ea41a00a4b72f9a29be096cdec703a --- /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 0000000000000000000000000000000000000000..d316ab0e0de96eef684221c7bf39bb321b83f140 --- /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 0000000000000000000000000000000000000000..ca0ab1a5654970f6a68136b7e7ba5c3fc61cc747 --- /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 0000000000000000000000000000000000000000..f6533a40c2bf838be5900cb76727c9b223ddecf1 --- /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 0000000000000000000000000000000000000000..cee8f04554ae61d02079f0df1ce3163f7c7647ee --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/Makefile @@ -0,0 +1,22 @@ +obj-y += rtac.o +ifdef CONFIG_ARCH_MSM8X60 +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 +ifndef CONFIG_ARCH_MSM9615 +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 audio_utils_aio.o +obj-$(CONFIG_MSM_QDSP6_CODECS) += q6audio_v1.o q6audio_v1_aio.o +obj-$(CONFIG_MSM_ULTRASOUND) += ultrasound/ +obj-y += audio_mp3.o audio_amrnb.o audio_amrwb.o audio_evrc.o audio_qcelp.o amrwb_in.o +endif 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 0000000000000000000000000000000000000000..6e79a754b16bebbec9531f4ba39268d3fa91b87f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/aac_in.c @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 + +/* ------------------- 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; + } + /* For aac-lc, min_bit_rate = min(24Kbps, 0.5*SR*num_chan); + max_bi_rate = min(192Kbps, 6*SR*num_chan); + min_sample_rate = 8000Hz, max_rate=48000 */ + if ((cfg.bit_rate < 4000) || (cfg.bit_rate > 192000)) { + pr_err("%s: ERROR in setting bitrate = %d\n", + __func__, cfg.bit_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: Could not allocate memory for aac" + "driver\n", __func__); + 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_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s: Could not allocate memory for" + "audio client\n", __func__); + 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 0000000000000000000000000000000000000000..63a07748ac93b51e7693bda78b0cae7bad1e5766 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/amrnb_in.c @@ -0,0 +1,285 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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)) + +/* ------------------- 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 Could not allocate memory for amrnb" + "driver\n", __func__); + 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_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s: Could not allocate memory for audio" + "client\n", __func__); + 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/amrwb_in.c b/arch/arm/mach-msm/qdsp6v2/amrwb_in.c new file mode 100644 index 0000000000000000000000000000000000000000..d0462e09307ca20493e08e8e56c789b155af9653 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/amrwb_in.c @@ -0,0 +1,282 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 + ((61+sizeof(struct meta_out_dsp)) * 10)) + +/* ------------------- device --------------------- */ +static long amrwb_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_amrwb_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; + } + + rc = q6asm_enc_cfg_blk_amrwb(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 amrwb 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_AMRWB_ENC_CONFIG: { + if (copy_to_user((void *)arg, audio->enc_cfg, + sizeof(struct msm_audio_amrwb_enc_config))) + rc = -EFAULT; + break; + } + case AUDIO_SET_AMRWB_ENC_CONFIG: { + struct msm_audio_amrwb_enc_config cfg; + struct msm_audio_amrwb_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + if (copy_from_user(&cfg, (void *) arg, + sizeof(struct msm_audio_amrwb_enc_config))) { + rc = -EFAULT; + break; + } + if (cfg.band_mode > 8) { + pr_err("%s:session id %d: invalid band mode\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + /* ToDo: AMR WB encoder accepts values between 0-8 + while openmax provides value between 9-17 + as per spec */ + enc_cfg->band_mode = cfg.band_mode; + enc_cfg->dtx_enable = (cfg.dtx_enable ? 1 : 0); + /* Currently DSP does not support different frameformat */ + 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 amrwb_in_open(struct inode *inode, struct file *file) +{ + struct q6audio_in *audio = NULL; + struct msm_audio_amrwb_enc_config *enc_cfg; + int rc = 0; + + audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL); + + if (audio == NULL) { + pr_err("%s: Could not allocate memory for amrwb driver\n", + __func__); + return -ENOMEM; + } + /* Allocate memory for encoder config param */ + audio->enc_cfg = kzalloc(sizeof(struct msm_audio_amrwb_enc_config), + GFP_KERNEL); + if (audio->enc_cfg == NULL) { + pr_err("%s:session id %d: Could not allocate memory for amrwb" + "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 = 8; + enc_cfg->dtx_enable = 0; + audio->pcm_cfg.channel_count = 1; + audio->pcm_cfg.sample_rate = 16000; + audio->buf_cfg.meta_info_enable = 0x01; + audio->buf_cfg.frames_per_buf = 0x01; + + audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s:audio[%p]: Could not allocate memory for audio" + "client\n", __func__, audio); + kfree(audio->enc_cfg); + kfree(audio); + return -ENOMEM; + } + + /* open amrwb 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_AMRWB, + 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_AMRWB); + 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 = amrwb_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 = amrwb_in_open, + .release = audio_in_release, + .read = audio_in_read, + .write = audio_in_write, + .unlocked_ioctl = audio_in_ioctl, +}; + +struct miscdevice audio_amrwb_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrwb_in", + .fops = &audio_in_fops, +}; + +static int __init amrwb_in_init(void) +{ + return misc_register(&audio_amrwb_in_misc); +} + +device_initcall(amrwb_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 0000000000000000000000000000000000000000..2403c0269b27d587a1ac393824591ddcc8b8087c --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/apr.c @@ -0,0 +1,688 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +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_USM || + 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 (!strncmp(dest, "ADSP", 4)) + dest_id = APR_DEST_QDSP6; + else if (!strncmp(dest, "MODEM", 5)) { + 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)) { + pr_info("%s: Wait for Lpass to bootup\n", __func__); + rc = wait_event_interruptible_timeout(dsp_wait, + (atomic_read(&dsp_state) == 1), (1 * HZ)); + if (rc == 0) { + pr_err("%s: DSP is not Up\n", __func__); + return NULL; + } + pr_info("%s: Lpass Up\n", __func__); + } else if ((dest_id == APR_DEST_MODEM) && + (atomic_read(&modem_state) == 0)) { + pr_info("%s: Wait for modem to bootup\n", __func__); + rc = wait_event_interruptible_timeout(modem_wait, + (atomic_read(&modem_state) == 1), (1 * HZ)); + if (rc == 0) { + pr_err("%s: Modem is not Up\n", __func__); + return NULL; + } + pr_info("%s: modem Up\n", __func__); + } + + if (!strncmp(svc_name, "AFE", 3)) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 0; + svc_id = APR_SVC_AFE; + } else if (!strncmp(svc_name, "ASM", 3)) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 1; + svc_id = APR_SVC_ASM; + } else if (!strncmp(svc_name, "ADM", 3)) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 2; + svc_id = APR_SVC_ADM; + } else if (!strncmp(svc_name, "CORE", 4)) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 3; + svc_id = APR_SVC_ADSP_CORE; + } else if (!strncmp(svc_name, "TEST", 4)) { + 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 (!strncmp(svc_name, "VSM", 3)) { + client_id = APR_CLIENT_VOICE; + svc_idx = 0; + svc_id = APR_SVC_VSM; + } else if (!strncmp(svc_name, "VPM", 3)) { + client_id = APR_CLIENT_VOICE; + svc_idx = 1; + svc_id = APR_SVC_VPM; + } else if (!strncmp(svc_name, "MVS", 3)) { + client_id = APR_CLIENT_VOICE; + svc_idx = 2; + svc_id = APR_SVC_MVS; + } else if (!strncmp(svc_name, "MVM", 3)) { + 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 (!strncmp(svc_name, "CVS", 3)) { + 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 (!strncmp(svc_name, "CVP", 3)) { + 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 (!strncmp(svc_name, "SRD", 3)) { + client_id = APR_CLIENT_VOICE; + svc_idx = 6; + svc_id = APR_SVC_SRD; + } else if (!strncmp(svc_name, "USM", 3)) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 8; + svc_id = APR_SVC_USM; + } 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 (IS_ERR(q6.pil)) { + rc = PTR_ERR(q6.pil); + pr_err("APR: Unable to load q6 image, error:%d\n", rc); + 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); + + if (apr_reset_workqueue == NULL) { + pr_err("%s: apr_reset_workqueue is NULL\n", __func__); + return; + } + + apr_reset_worker = kzalloc(sizeof(struct apr_reset_work), + GFP_ATOMIC); + + if (apr_reset_worker == 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) +{ + int ret = 0; + init_waitqueue_head(&dsp_wait); + init_waitqueue_head(&modem_wait); + atomic_set(&dsp_state, 1); + atomic_set(&modem_state, 1); + subsys_notif_register_notifier("modem", &mnb); + subsys_notif_register_notifier("lpass", &lnb); + return ret; +} +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 0000000000000000000000000000000000000000..03f0513888efca5a2c9ae1ae1bfef5e73f07f9ff --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/apr_tal.c @@ -0,0 +1,280 @@ +/* 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_err("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); + apr_tal_close(&apr_svc_ch[dl][dest][svc]); + 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 0000000000000000000000000000000000000000..485234f6f4309df9c6e303041ca39f124c80b407 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_aac.c @@ -0,0 +1,295 @@ +/* aac audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2010-2012, 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 "audio_utils_aio.h" + +#define AUDIO_AAC_DUAL_MONO_INVALID -1 +#define PCM_BUFSZ_MIN_AAC ((8*1024) + sizeof(struct dec_meta_out)) + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_aac_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + switch (cmd) { + case AUDIO_START: { + struct asm_aac_cfg aac_cfg; + struct msm_audio_aac_config *aac_config; + uint32_t sbr_ps = 0x00; + pr_debug("%s: AUDIO_START session_id[%d]\n", __func__, + audio->ac->session); + if (audio->feedback == NON_TUNNEL_MODE) { + /* Configure PCM output block */ + rc = q6asm_enc_cfg_blk_pcm(audio->ac, 0, 0); + 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"); + aac_config = (struct msm_audio_aac_config *)audio->codec_cfg; + if (aac_config->sbr_ps_on_flag) + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + else if (aac_config->sbr_on_flag) + aac_cfg.aot = AAC_ENC_MODE_AAC_P; + else + aac_cfg.aot = AAC_ENC_MODE_AAC_LC; + + switch (aac_config->format) { + case AUDIO_AAC_FORMAT_ADTS: + aac_cfg.format = 0x00; + break; + case AUDIO_AAC_FORMAT_LOAS: + aac_cfg.format = 0x01; + break; + case AUDIO_AAC_FORMAT_ADIF: + aac_cfg.format = 0x02; + break; + default: + case AUDIO_AAC_FORMAT_RAW: + aac_cfg.format = 0x03; + } + aac_cfg.ep_config = aac_config->ep_config; + aac_cfg.section_data_resilience = + aac_config->aac_section_data_resilience_flag; + aac_cfg.scalefactor_data_resilience = + aac_config->aac_scalefactor_data_resilience_flag; + aac_cfg.spectral_data_resilience = + 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 = audio_aio_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_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_config; + pr_debug("%s: AUDIO_SET_AAC_CONFIG\n", __func__); + if (copy_from_user(audio->codec_cfg, (void *)arg, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } else { + uint16_t sce_left = 1, sce_right = 2; + aac_config = audio->codec_cfg; + if ((aac_config->dual_mono_mode < + AUDIO_AAC_DUAL_MONO_PL_PR) || + (aac_config->dual_mono_mode > + AUDIO_AAC_DUAL_MONO_PL_SR)) { + pr_err("%s:AUDIO_SET_AAC_CONFIG: Invalid" + "dual_mono mode =%d\n", __func__, + aac_config->dual_mono_mode); + } else { + /* convert the data from user into sce_left + * and sce_right based on the definitions + */ + pr_debug("%s: AUDIO_SET_AAC_CONFIG: modify" + "dual_mono mode =%d\n", __func__, + aac_config->dual_mono_mode); + switch (aac_config->dual_mono_mode) { + case AUDIO_AAC_DUAL_MONO_PL_PR: + sce_left = 1; + sce_right = 1; + break; + case AUDIO_AAC_DUAL_MONO_SL_SR: + sce_left = 2; + sce_right = 2; + break; + case AUDIO_AAC_DUAL_MONO_SL_PR: + sce_left = 2; + sce_right = 1; + break; + case AUDIO_AAC_DUAL_MONO_PL_SR: + default: + sce_left = 1; + sce_right = 2; + break; + } + rc = q6asm_cfg_dual_mono_aac(audio->ac, + sce_left, sce_right); + if (rc < 0) + pr_err("%s: asm cmd dualmono failed" + " rc=%d\n", __func__, rc); + } + } + break; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + if (rc) + pr_err("%s[%p]:Failed in utils_ioctl: %d\n", + __func__, audio, rc); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + struct msm_audio_aac_config *aac_config = 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_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for aac decode driver\n"); + return -ENOMEM; + } + audio->codec_cfg = kzalloc(sizeof(struct msm_audio_aac_config), + GFP_KERNEL); + if (audio->codec_cfg == NULL) { + pr_err("%s:Could not allocate memory for aac" + "config\n", __func__); + kfree(audio); + return -ENOMEM; + } + aac_config = audio->codec_cfg; + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN_AAC; + aac_config->dual_mono_mode = AUDIO_AAC_DUAL_MONO_INVALID; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio->codec_cfg); + kfree(audio); + return -ENOMEM; + } + + /* 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; + } + rc = audio_aio_open(audio, file); + +#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, + &audio_aac_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:aacdec success mode[%d]session[%d]\n", __func__, + audio->feedback, + audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->codec_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_aac_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_aac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac", + .fops = &audio_aac_fops, +}; + +static int __init audio_aac_init(void) +{ + return misc_register(&audio_aac_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 0000000000000000000000000000000000000000..e7a81d3f5a6f984d2b7f8c3e6e72585aac775579 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_acdb.c @@ -0,0 +1,941 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 12 + +struct sidetone_atomic_cal { + atomic_t enable; + atomic_t gain; +}; + + +struct acdb_data { + struct mutex acdb_mutex; + + /* ANC Cal */ + struct acdb_atomic_cal_block anc_cal; + + /* AudProc Cal */ + atomic_t asm_topology; + atomic_t adm_topology[MAX_AUDPROC_TYPES]; + struct acdb_atomic_cal_block audproc_cal[MAX_AUDPROC_TYPES]; + struct acdb_atomic_cal_block audstrm_cal[MAX_AUDPROC_TYPES]; + struct acdb_atomic_cal_block audvol_cal[MAX_AUDPROC_TYPES]; + + /* VocProc Cal */ + atomic_t voice_rx_topology; + atomic_t voice_tx_topology; + struct acdb_atomic_cal_block vocproc_cal[MAX_NETWORKS]; + struct acdb_atomic_cal_block vocstrm_cal[MAX_NETWORKS]; + struct acdb_atomic_cal_block vocvol_cal[MAX_NETWORKS]; + /* size of cal block tables above*/ + atomic_t vocproc_cal_size; + atomic_t vocstrm_cal_size; + atomic_t vocvol_cal_size; + /* Total size of cal data for all networks */ + atomic_t vocproc_total_cal_size; + atomic_t vocstrm_total_cal_size; + atomic_t vocvol_total_cal_size; + + /* AFE cal */ + struct acdb_atomic_cal_block afe_cal[MAX_AUDPROC_TYPES]; + + /* Sidetone Cal */ + struct sidetone_atomic_cal sidetone_cal; + + /* Allocation information */ + struct ion_client *ion_client; + struct ion_handle *ion_handle; + atomic_t map_handle; + atomic64_t paddr; + atomic64_t kvaddr; + atomic64_t mem_len; +}; + +static struct acdb_data acdb_data; +static atomic_t usage_count; + +uint32_t get_voice_rx_topology(void) +{ + return atomic_read(&acdb_data.voice_rx_topology); +} + +void store_voice_rx_topology(uint32_t topology) +{ + atomic_set(&acdb_data.voice_rx_topology, topology); +} + +uint32_t get_voice_tx_topology(void) +{ + return atomic_read(&acdb_data.voice_tx_topology); +} + +void store_voice_tx_topology(uint32_t topology) +{ + atomic_set(&acdb_data.voice_tx_topology, topology); +} + +uint32_t get_adm_rx_topology(void) +{ + return atomic_read(&acdb_data.adm_topology[RX_CAL]); +} + +void store_adm_rx_topology(uint32_t topology) +{ + atomic_set(&acdb_data.adm_topology[RX_CAL], topology); +} + +uint32_t get_adm_tx_topology(void) +{ + return atomic_read(&acdb_data.adm_topology[TX_CAL]); +} + +void store_adm_tx_topology(uint32_t topology) +{ + atomic_set(&acdb_data.adm_topology[TX_CAL], topology); +} + +uint32_t get_asm_topology(void) +{ + return atomic_read(&acdb_data.asm_topology); +} + +void store_asm_topology(uint32_t topology) +{ + atomic_set(&acdb_data.asm_topology, topology); +} + +void get_all_voice_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = + atomic_read(&acdb_data.vocproc_cal[0].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.vocproc_cal[0].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.vocproc_total_cal_size) + + atomic_read(&acdb_data.vocstrm_total_cal_size) + + atomic_read(&acdb_data.vocvol_total_cal_size); +} + +void get_all_cvp_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = + atomic_read(&acdb_data.vocproc_cal[0].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.vocproc_cal[0].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.vocproc_total_cal_size) + + atomic_read(&acdb_data.vocvol_total_cal_size); +} + +void get_all_vocproc_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = + atomic_read(&acdb_data.vocproc_cal[0].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.vocproc_cal[0].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.vocproc_total_cal_size); +} + +void get_all_vocstrm_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = + atomic_read(&acdb_data.vocstrm_cal[0].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.vocstrm_cal[0].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.vocstrm_total_cal_size); +} + +void get_all_vocvol_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = + atomic_read(&acdb_data.vocvol_cal[0].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.vocvol_cal[0].cal_paddr); + cal_block->cal_size = + atomic_read(&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; + } + + cal_block->cal_kvaddr = + atomic_read(&acdb_data.anc_cal.cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.anc_cal.cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.anc_cal.cal_size); +done: + return; +} + +void store_anc_cal(struct cal_block *cal_block) +{ + pr_debug("%s,\n", __func__); + + if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_block->cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + goto done; + } + + atomic_set(&acdb_data.anc_cal.cal_kvaddr, + cal_block->cal_offset + atomic64_read(&acdb_data.kvaddr)); + atomic_set(&acdb_data.anc_cal.cal_paddr, + cal_block->cal_offset + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.anc_cal.cal_size, + cal_block->cal_size); +done: + return; +} + +void store_afe_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_block->cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + goto done; + } + if ((path >= MAX_AUDPROC_TYPES) || (path < 0)) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + atomic_set(&acdb_data.afe_cal[path].cal_kvaddr, + cal_block->cal_offset + atomic64_read(&acdb_data.kvaddr)); + atomic_set(&acdb_data.afe_cal[path].cal_paddr, + cal_block->cal_offset + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.afe_cal[path].cal_size, + cal_block->cal_size); +done: + return; +} + +void get_afe_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; + } + + cal_block->cal_kvaddr = + atomic_read(&acdb_data.afe_cal[path].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.afe_cal[path].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.afe_cal[path].cal_size); +done: + return; +} + +void store_audproc_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_block->cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + atomic_set(&acdb_data.audproc_cal[path].cal_kvaddr, + cal_block->cal_offset + atomic64_read(&acdb_data.kvaddr)); + atomic_set(&acdb_data.audproc_cal[path].cal_paddr, + cal_block->cal_offset + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.audproc_cal[path].cal_size, + cal_block->cal_size); +done: + 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; + } + + cal_block->cal_kvaddr = + atomic_read(&acdb_data.audproc_cal[path].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.audproc_cal[path].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.audproc_cal[path].cal_size); +done: + return; +} + +void store_audstrm_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_block->cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + atomic_set(&acdb_data.audstrm_cal[path].cal_kvaddr, + cal_block->cal_offset + atomic64_read(&acdb_data.kvaddr)); + atomic_set(&acdb_data.audstrm_cal[path].cal_paddr, + cal_block->cal_offset + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.audstrm_cal[path].cal_size, + cal_block->cal_size); +done: + 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; + } + + cal_block->cal_kvaddr = + atomic_read(&acdb_data.audstrm_cal[path].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.audstrm_cal[path].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.audstrm_cal[path].cal_size); +done: + return; +} + +void store_audvol_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block->cal_offset > atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_block->cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + atomic_set(&acdb_data.audvol_cal[path].cal_kvaddr, + cal_block->cal_offset + atomic64_read(&acdb_data.kvaddr)); + atomic_set(&acdb_data.audvol_cal[path].cal_paddr, + cal_block->cal_offset + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.audvol_cal[path].cal_size, + cal_block->cal_size); +done: + 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; + } + + cal_block->cal_kvaddr = + atomic_read(&acdb_data.audvol_cal[path].cal_kvaddr); + cal_block->cal_paddr = + atomic_read(&acdb_data.audvol_cal[path].cal_paddr); + cal_block->cal_size = + atomic_read(&acdb_data.audvol_cal[path].cal_size); +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; + } + + atomic_set(&acdb_data.vocproc_total_cal_size, 0); + for (i = 0; i < len; i++) { + if (cal_blocks[i].cal_offset > + atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_blocks[i].cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + atomic_set(&acdb_data.vocproc_cal[i].cal_size, 0); + } else { + atomic_add(cal_blocks[i].cal_size, + &acdb_data.vocproc_total_cal_size); + atomic_set(&acdb_data.vocproc_cal[i].cal_size, + cal_blocks[i].cal_size); + atomic_set(&acdb_data.vocproc_cal[i].cal_paddr, + cal_blocks[i].cal_offset + + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.vocproc_cal[i].cal_kvaddr, + cal_blocks[i].cal_offset + + atomic64_read(&acdb_data.kvaddr)); + } + } + atomic_set(&acdb_data.vocproc_cal_size, len); +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; + } + + cal_data->num_cal_blocks = atomic_read(&acdb_data.vocproc_cal_size); + cal_data->cal_blocks = &acdb_data.vocproc_cal[0]; +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; + } + + atomic_set(&acdb_data.vocstrm_total_cal_size, 0); + for (i = 0; i < len; i++) { + if (cal_blocks[i].cal_offset > + atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_blocks[i].cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + atomic_set(&acdb_data.vocstrm_cal[i].cal_size, 0); + } else { + atomic_add(cal_blocks[i].cal_size, + &acdb_data.vocstrm_total_cal_size); + atomic_set(&acdb_data.vocstrm_cal[i].cal_size, + cal_blocks[i].cal_size); + atomic_set(&acdb_data.vocstrm_cal[i].cal_paddr, + cal_blocks[i].cal_offset + + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.vocstrm_cal[i].cal_kvaddr, + cal_blocks[i].cal_offset + + atomic64_read(&acdb_data.kvaddr)); + } + } + atomic_set(&acdb_data.vocstrm_cal_size, len); +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; + } + + cal_data->num_cal_blocks = atomic_read(&acdb_data.vocstrm_cal_size); + cal_data->cal_blocks = &acdb_data.vocstrm_cal[0]; +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; + } + + atomic_set(&acdb_data.vocvol_total_cal_size, 0); + for (i = 0; i < len; i++) { + if (cal_blocks[i].cal_offset > + atomic64_read(&acdb_data.mem_len)) { + pr_err("%s: offset %d is > mem_len %ld\n", + __func__, cal_blocks[i].cal_offset, + (long)atomic64_read(&acdb_data.mem_len)); + atomic_set(&acdb_data.vocvol_cal[i].cal_size, 0); + } else { + atomic_add(cal_blocks[i].cal_size, + &acdb_data.vocvol_total_cal_size); + atomic_set(&acdb_data.vocvol_cal[i].cal_size, + cal_blocks[i].cal_size); + atomic_set(&acdb_data.vocvol_cal[i].cal_paddr, + cal_blocks[i].cal_offset + + atomic64_read(&acdb_data.paddr)); + atomic_set(&acdb_data.vocvol_cal[i].cal_kvaddr, + cal_blocks[i].cal_offset + + atomic64_read(&acdb_data.kvaddr)); + } + } + atomic_set(&acdb_data.vocvol_cal_size, len); +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; + } + + cal_data->num_cal_blocks = atomic_read(&acdb_data.vocvol_cal_size); + cal_data->cal_blocks = &acdb_data.vocvol_cal[0]; +done: + return; +} + +void store_sidetone_cal(struct sidetone_cal *cal_data) +{ + pr_debug("%s\n", __func__); + + atomic_set(&acdb_data.sidetone_cal.enable, cal_data->enable); + atomic_set(&acdb_data.sidetone_cal.gain, cal_data->gain); +} + + +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; + } + + cal_data->enable = atomic_read(&acdb_data.sidetone_cal.enable); + cal_data->gain = atomic_read(&acdb_data.sidetone_cal.gain); +done: + return; +} + +static int acdb_open(struct inode *inode, struct file *f) +{ + s32 result = 0; + pr_debug("%s\n", __func__); + + if (atomic64_read(&acdb_data.mem_len)) { + pr_debug("%s: ACDB opened but memory allocated, " + "using existing allocation!\n", + __func__); + } + + atomic_inc(&usage_count); + return result; +} + +static int deregister_memory(void) +{ + if (atomic64_read(&acdb_data.mem_len)) { + mutex_lock(&acdb_data.acdb_mutex); + ion_unmap_kernel(acdb_data.ion_client, acdb_data.ion_handle); + ion_free(acdb_data.ion_client, acdb_data.ion_handle); + ion_client_destroy(acdb_data.ion_client); + mutex_unlock(&acdb_data.acdb_mutex); + atomic64_set(&acdb_data.mem_len, 0); + } + return 0; +} + +static int register_memory(void) +{ + int result; + unsigned long paddr; + void *kvptr; + unsigned long kvaddr; + unsigned long mem_len; + + mutex_lock(&acdb_data.acdb_mutex); + acdb_data.ion_client = + msm_ion_client_create(UINT_MAX, "audio_acdb_client"); + if (IS_ERR_OR_NULL(acdb_data.ion_client)) { + pr_err("%s: Could not register ION client!!!\n", __func__); + result = PTR_ERR(acdb_data.ion_client); + goto err; + } + + acdb_data.ion_handle = ion_import_fd(acdb_data.ion_client, + atomic_read(&acdb_data.map_handle)); + if (IS_ERR_OR_NULL(acdb_data.ion_handle)) { + pr_err("%s: Could not import map handle!!!\n", __func__); + result = PTR_ERR(acdb_data.ion_handle); + goto err_ion_client; + } + + result = ion_phys(acdb_data.ion_client, acdb_data.ion_handle, + &paddr, (size_t *)&mem_len); + if (result != 0) { + pr_err("%s: Could not get phys addr!!!\n", __func__); + goto err_ion_handle; + } + + kvptr = ion_map_kernel(acdb_data.ion_client, + acdb_data.ion_handle, 0); + if (IS_ERR_OR_NULL(kvptr)) { + pr_err("%s: Could not get kernel virt addr!!!\n", __func__); + result = PTR_ERR(kvptr); + goto err_ion_handle; + } + kvaddr = (unsigned long)kvptr; + mutex_unlock(&acdb_data.acdb_mutex); + + atomic64_set(&acdb_data.paddr, paddr); + atomic64_set(&acdb_data.kvaddr, kvaddr); + atomic64_set(&acdb_data.mem_len, mem_len); + pr_debug("%s done! paddr = 0x%lx, " + "kvaddr = 0x%lx, len = x%lx\n", + __func__, + (long)atomic64_read(&acdb_data.paddr), + (long)atomic64_read(&acdb_data.kvaddr), + (long)atomic64_read(&acdb_data.mem_len)); + + return result; +err_ion_handle: + ion_free(acdb_data.ion_client, acdb_data.ion_handle); +err_ion_client: + ion_client_destroy(acdb_data.ion_client); +err: + atomic64_set(&acdb_data.mem_len, 0); + mutex_unlock(&acdb_data.acdb_mutex); + return result; +} +static long acdb_ioctl(struct file *f, + unsigned int cmd, unsigned long arg) +{ + int32_t result = 0; + int32_t size; + int32_t map_fd; + uint32_t topology; + struct cal_block data[MAX_NETWORKS]; + pr_debug("%s\n", __func__); + + switch (cmd) { + case AUDIO_REGISTER_PMEM: + pr_debug("AUDIO_REGISTER_PMEM\n"); + if (atomic_read(&acdb_data.mem_len)) { + deregister_memory(); + pr_debug("Remove the existing memory\n"); + } + + if (copy_from_user(&map_fd, (void *)arg, sizeof(map_fd))) { + pr_err("%s: fail to copy memory handle!\n", __func__); + result = -EFAULT; + } else { + atomic_set(&acdb_data.map_handle, map_fd); + result = register_memory(); + } + goto done; + + case AUDIO_DEREGISTER_PMEM: + pr_debug("AUDIO_DEREGISTER_PMEM\n"); + deregister_memory(); + 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_RX_TOPOLOGY: + if (copy_from_user(&topology, (void *)arg, + sizeof(topology))) { + pr_err("%s: fail to copy topology!\n", __func__); + result = -EFAULT; + } + store_adm_rx_topology(topology); + goto done; + case AUDIO_SET_ADM_TX_TOPOLOGY: + if (copy_from_user(&topology, (void *)arg, + sizeof(topology))) { + pr_err("%s: fail to copy topology!\n", __func__); + result = -EFAULT; + } + store_adm_tx_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: + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audproc_cal(TX_CAL, data); + break; + case AUDIO_SET_AUDPROC_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(RX_CAL, data); + break; + case AUDIO_SET_AUDPROC_TX_STREAM_CAL: + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audstrm_cal(TX_CAL, data); + break; + case AUDIO_SET_AUDPROC_RX_STREAM_CAL: + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audstrm_cal(RX_CAL, data); + break; + case AUDIO_SET_AUDPROC_TX_VOL_CAL: + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audvol_cal(TX_CAL, data); + break; + case AUDIO_SET_AUDPROC_RX_VOL_CAL: + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audvol_cal(RX_CAL, data); + break; + case AUDIO_SET_AFE_TX_CAL: + if (size > sizeof(struct cal_block)) + pr_err("%s: More AFE Cal then expected, " + "size received: %d\n", __func__, size); + store_afe_cal(TX_CAL, data); + break; + case AUDIO_SET_AFE_RX_CAL: + if (size > sizeof(struct cal_block)) + pr_err("%s: More AFE Cal then expected, " + "size received: %d\n", __func__, size); + store_afe_cal(RX_CAL, 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__); + + if (atomic64_read(&acdb_data.mem_len)) { + if (size <= atomic64_read(&acdb_data.mem_len)) { + vma->vm_page_prot = pgprot_noncached( + vma->vm_page_prot); + result = remap_pfn_range(vma, + vma->vm_start, + atomic64_read(&acdb_data.paddr) >> PAGE_SHIFT, + size, + vma->vm_page_prot); + } else { + pr_err("%s: Not enough memory!\n", __func__); + result = -ENOMEM; + } + } else { + pr_err("%s: memory is not allocated, yet!\n", __func__); + result = -ENODEV; + } + + return result; +} + +static int acdb_release(struct inode *inode, struct file *f) +{ + s32 result = 0; + + atomic_dec(&usage_count); + atomic_read(&usage_count); + + pr_debug("%s: ref count %d!\n", __func__, + atomic_read(&usage_count)); + + if (atomic_read(&usage_count) >= 1) + result = -EBUSY; + else + result = deregister_memory(); + + 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_amrnb.c b/arch/arm/mach-msm/qdsp6v2/audio_amrnb.c new file mode 100644 index 0000000000000000000000000000000000000000..f4316d09974312274e344a6efd1bd9ca93c94bb0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_amrnb.c @@ -0,0 +1,161 @@ +/* amrnb audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 "audio_utils_aio.h" + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_amrnb_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + pr_debug("%s[%p]: AUDIO_START session_id[%d]\n", __func__, + audio, audio->ac->session); + 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; + } + } + + rc = audio_aio_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; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_amrnb_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for wma decode driver\n"); + return -ENOMEM; + } + + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + + /* 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_AMRNB); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + 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_AMRNB); + 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; + } + rc = audio_aio_open(audio, file); + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_amrnb_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audio_amrnb_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:amrnb decoder open success, session_id = %d\n", __func__, + audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_amrnb_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_amrnb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrnb", + .fops = &audio_amrnb_fops, +}; + +static int __init audio_amrnb_init(void) +{ + return misc_register(&audio_amrnb_misc); +} + +device_initcall(audio_amrnb_init); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_amrwb.c b/arch/arm/mach-msm/qdsp6v2/audio_amrwb.c new file mode 100644 index 0000000000000000000000000000000000000000..28c173294dbdedbfff264a65033b573170bba629 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_amrwb.c @@ -0,0 +1,164 @@ +/* amrwb audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 "audio_utils_aio.h" + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_amrwb_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + pr_debug("%s[%p]: AUDIO_START session_id[%d]\n", __func__, + audio, audio->ac->session); + 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; + } + } + + rc = audio_aio_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("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__, + audio->ac->session, + audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_amrwb_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for aac decode driver\n"); + return -ENOMEM; + } + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + + /* 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_AMRWB); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + 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_AMRWB); + 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; + } + rc = audio_aio_open(audio, file); + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_amrwb_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audio_amrwb_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s: AMRWB dec 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_amrwb_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_amrwb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrwb", + .fops = &audio_amrwb_fops, +}; + +static int __init audio_amrwb_init(void) +{ + return misc_register(&audio_amrwb_misc); +} + +device_initcall(audio_amrwb_init); 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 0000000000000000000000000000000000000000..aaae7762a7371ca32a34b4af48bf7997d7f7ca6c --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_dev_ctl.c @@ -0,0 +1,1731 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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); +static struct workqueue_struct *msm_reset_device_work_queue; +static void reset_device_work(struct work_struct *work); +static DECLARE_WORK(msm_reset_device_work, reset_device_work); + +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; + +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); + +static void reset_device_work(struct work_struct *work) +{ + msm_reset_all_device(); +} + +int reset_device(void) +{ + queue_work(msm_reset_device_work_queue, &msm_reset_device_work); + return 0; +} +EXPORT_SYMBOL(reset_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] == COPP_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] = COPP_IGNORE; +#ifdef CONFIG_MSM8X60_RTAC + rtac_remove_adm_device(copp_id, session_id); +#endif + 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] != COPP_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; + } +#ifdef CONFIG_MSM8X60_RTAC + rtac_remove_adm_device( + routing_info.copp_list[session_id][i], session_id); +#endif + routing_info.copp_list[session_id][i] = COPP_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] != COPP_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] = COPP_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, u32 session_id) +{ + 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_id); + } else + return -EPERM; + return 0; +} +EXPORT_SYMBOL(msm_set_voice_mute); + +int msm_set_voice_vol(int dir, s32 volume, u32 session_id) +{ + 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_id); + } 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_id); + } 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] == COPP_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, num_copps; + 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, ADM_PATH_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, COPP_IGNORE, + (sizeof(unsigned int) * AFE_MAX_PORTS)); + num_copps = 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, ADM_PATH_PLAYBACK, num_copps, + 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; + } +#ifdef CONFIG_MSM8X60_RTAC + for (i = 0; i < num_copps; i++) + rtac_add_adm_device(payload.copp_ids[i], popp_id); +#endif + } 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, ADM_PATH_LIVE_REC, 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, ADM_PATH_LIVE_REC, 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); +#ifdef CONFIG_MSM8X60_RTAC + rtac_add_adm_device(copp_id, popp_id); +#endif + + } 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], ADM_PATH_LIVE_REC, 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, ADM_PATH_LIVE_REC, 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], ADM_PATH_LIVE_REC, 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, ADM_PATH_LIVE_REC, 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], ADM_PATH_LIVE_REC, 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], ADM_PATH_LIVE_REC, 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, ADM_PATH_LIVE_REC, 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; + } + } + + 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) + || evt_id == AUDDEV_EVT_DEVICE_VOL_MUTE_CHG) + 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) { + evt_payload->voc_vm_info.voice_session_id = + session_id; + + 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)); + + evt_payload->voice_session_id = session_id; + } 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; + msm_reset_device_work_queue = create_workqueue("reset_device"); + if (msm_reset_device_work_queue == NULL) + return -ENOMEM; + 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, COPP_IGNORE, + (sizeof(unsigned int) * MAX_SESSIONS * AFE_MAX_PORTS)); + return misc_register(&audio_dev_ctrl_misc); +} + +static void __exit audio_dev_ctrl_exit(void) +{ + destroy_workqueue(msm_reset_device_work_queue); +} +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_evrc.c b/arch/arm/mach-msm/qdsp6v2/audio_evrc.c new file mode 100644 index 0000000000000000000000000000000000000000..ec5162dfffa7b0ae79da456dd45d724e86ee5493 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_evrc.c @@ -0,0 +1,170 @@ +/* evrc audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 "audio_utils_aio.h" + + + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_evrc_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + pr_debug("%s[%p]: AUDIO_START session_id[%d]\n", __func__, + audio, audio->ac->session); + 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; + } + } + + rc = audio_aio_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("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__, + audio->ac->session, + audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_evrc_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio_aio), 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->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + + /* 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_EVRC); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + 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_EVRC); + 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; + } + rc = audio_aio_open(audio, file); + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_evrc_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audio_evrc_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:dec success mode[%d]session[%d]\n", __func__, + audio->feedback, + audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_evrc_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_evrc_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc", + .fops = &audio_evrc_fops, +}; + +static int __init audio_evrc_init(void) +{ + return misc_register(&audio_evrc_misc); +} + +device_initcall(audio_evrc_init); 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 0000000000000000000000000000000000000000..db0a96ebe8c3c8fd4362f7e40c9c8a0bf46a5367 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_lpa.c @@ -0,0 +1,1481 @@ +/* low power audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 4 +#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_ion_region { + struct list_head list; + struct ion_handle *handle; + struct ion_client *client; + 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_ion_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); +static void audlpa_unmap_ion_region(struct audio *audio); +static void audlpa_async_send_data(struct audio *audio, unsigned needed, + uint32_t token); +static int audlpa_pause(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 && (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_ion_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_ion_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audlpa_ion_region *region_elt; + struct audlpa_ion_region t = {.vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->ion_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("%s[%p]:region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + __func__, audio, vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, region_elt->len); + return -EINVAL; + } + } + + return 0; +} +static int audlpa_ion_add(struct audio *audio, + struct msm_audio_ion_info *info) +{ + ion_phys_addr_t paddr; + size_t len; + unsigned long kvaddr; + struct audlpa_ion_region *region; + int rc = -EINVAL; + struct ion_handle *handle; + struct ion_client *client; + unsigned long ionflag; + void *temp_ptr; + + pr_debug("%s[%p]:\n", __func__, audio); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + client = msm_ion_client_create(UINT_MAX, "Audio_LPA_Client"); + if (IS_ERR_OR_NULL(client)) { + pr_err("Unable to create ION client\n"); + goto client_error; + } + + handle = ion_import_fd(client, info->fd); + if (IS_ERR_OR_NULL(handle)) { + pr_err("%s: could not get handle of the given fd\n", __func__); + goto import_error; + } + + rc = ion_handle_get_flags(client, handle, &ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", __func__); + goto flag_error; + } + + temp_ptr = ion_map_kernel(client, handle, ionflag); + if (IS_ERR_OR_NULL(temp_ptr)) { + pr_err("%s: could not get virtual address\n", __func__); + goto map_error; + } + kvaddr = (unsigned long) temp_ptr; + + rc = ion_phys(client, handle, &paddr, &len); + if (rc) { + pr_err("%s: could not get physical address\n", __func__); + goto ion_error; + } + + rc = audlpa_ion_check(audio, info->vaddr, len); + if (rc < 0) { + pr_err("%s: audlpa_ion_check failed\n", __func__); + goto ion_error; + } + + region->client = client; + region->handle = handle; + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->ref_cnt = 0; + pr_debug("%s[%p]:add region paddr %lx vaddr %p, len %lu kvaddr %lx\n", + __func__, audio, + region->paddr, region->vaddr, region->len, region->kvaddr); + list_add_tail(®ion->list, &audio->ion_region_queue); + + rc = q6asm_memory_map(audio->ac, (uint32_t)paddr, IN, (uint32_t)len, 1); + if (rc < 0) { + pr_err("%s[%p]: memory map failed\n", __func__, audio); + goto ion_error; + } else { + goto end; + } + +ion_error: + ion_unmap_kernel(client, handle); +map_error: + ion_free(client, handle); +flag_error: +import_error: + ion_client_destroy(client); +client_error: + kfree(region); +end: + return rc; +} + +static int audlpa_ion_remove(struct audio *audio, + struct msm_audio_ion_info *info) +{ + struct audlpa_ion_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + list_for_each_safe(ptr, next, &audio->ion_region_queue) { + region = list_entry(ptr, struct audlpa_ion_region, list); + + if (region != NULL && (region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("%s[%p]:region %p in use ref_cnt %d\n", + __func__, audio, region, + region->ref_cnt); + break; + } + rc = q6asm_memory_unmap(audio->ac, + (uint32_t) region->paddr, IN); + if (rc < 0) + pr_err("%s[%p]: memory unmap failed\n", + __func__, audio); + + list_del(®ion->list); + ion_unmap_kernel(region->client, region->handle); + ion_free(region->client, region->handle); + ion_client_destroy(region->client); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audlpa_ion_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audlpa_ion_region **region) +{ + struct audlpa_ion_region *region_elt; + int match_count = 0; + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->ion_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 + * ion buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("%s[%p]:multiple hits for vaddr %p, len %ld\n", + __func__, audio, addr, len); + list_for_each_entry(region_elt, &audio->ion_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%s[%p]:%p, %ld --> %p\n", + __func__, audio, + region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + return *region ? 0 : -1; +} +static unsigned long audlpa_ion_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audlpa_ion_region *region; + unsigned long paddr; + int ret; + + ret = audlpa_ion_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("%s[%p]:lookup (%p, %ld) failed\n", + __func__, audio, 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_ion_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 */ + kfree(buf_node); + } + 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; + case RESET_EVENTS: + reset_device(); + 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 softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_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, &softpause); + if (rc < 0) + pr_err("%s: Send SoftPause Param failed rc=%d\n", + __func__, rc); + rc = q6asm_set_softvolume(audio->ac, &softvol); + if (rc < 0) + pr_err("%s: Send SoftVolume 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_ION: { + struct msm_audio_ion_info info; + pr_debug("%s: AUDIO_REGISTER_ION\n", __func__); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audlpa_ion_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_ION: { + struct msm_audio_ion_info info; + pr_debug("%s: AUDIO_DEREGISTER_ION\n", __func__); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audlpa_ion_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, loff_t ppos1, loff_t ppos2, int datasync) +{ + struct audio *audio = file->private_data; + + return audlpa_async_fsync(audio); +} + +void audlpa_reset_ion_region(struct audio *audio) +{ + struct audlpa_ion_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->ion_region_queue) { + region = list_entry(ptr, struct audlpa_ion_region, list); + list_del(®ion->list); + ion_unmap_kernel(region->client, region->handle); + ion_free(region->client, region->handle); + ion_client_destroy(region->client); + kfree(region); + } + + return; +} + +static void audlpa_unmap_ion_region(struct audio *audio) +{ + struct audlpa_ion_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("%s[%p]:\n", __func__, audio); + list_for_each_safe(ptr, next, &audio->ion_region_queue) { + region = list_entry(ptr, struct audlpa_ion_region, list); + pr_debug("%s[%p]: phy_address = 0x%lx\n", + __func__, audio, 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_ion_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_ion_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); + 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->ion_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); + 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 0000000000000000000000000000000000000000..34b53f2ee3de9f9a42f0ab52e6c1b0cdd5e39eff --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_lpa.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +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 ion_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_mp3.c b/arch/arm/mach-msm/qdsp6v2/audio_mp3.c new file mode 100644 index 0000000000000000000000000000000000000000..93a87395861c5d1fcc71b6a46cdafcd403c3cb9b --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_mp3.c @@ -0,0 +1,165 @@ +/* mp3 audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 "audio_utils_aio.h" + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_mp3_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + switch (cmd) { + case AUDIO_START: { + pr_debug("%s[%p]: AUDIO_START session_id[%d]\n", __func__, + audio, audio->ac->session); + 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; + } + } + + rc = audio_aio_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; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_mp3_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for mp3 decode driver\n"); + return -ENOMEM; + } + + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + + /* 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_MP3); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + /* open MP3 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_MP3); + 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; + } + rc = audio_aio_open(audio, file); + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_mp3_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audio_mp3_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:mp3dec success mode[%d]session[%d]\n", __func__, + audio->feedback, + audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_mp3_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_mp3_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mp3", + .fops = &audio_mp3_fops, +}; + +static int __init audio_mp3_init(void) +{ + return misc_register(&audio_mp3_misc); +} + +device_initcall(audio_mp3_init); 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 0000000000000000000000000000000000000000..92530561e567c284c319d51a4959edc076936b33 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c @@ -0,0 +1,304 @@ +/* aac audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 "audio_utils_aio.h" + +#define AUDIO_AAC_DUAL_MONO_INVALID -1 + + +/* Default number of pre-allocated event packets */ +#define PCM_BUFSZ_MIN_AACM ((8*1024) + sizeof(struct dec_meta_out)) + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_aac_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + struct asm_aac_cfg aac_cfg; + struct msm_audio_aac_config *aac_config; + uint32_t sbr_ps = 0x00; + pr_debug("%s: AUDIO_START session_id[%d]\n", __func__, + audio->ac->session); + if (audio->feedback == NON_TUNNEL_MODE) { + /* Configure PCM output block */ + rc = q6asm_enc_cfg_blk_pcm(audio->ac, + 0, /*native sampling rate*/ + 0 /*native 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"); + aac_config = (struct msm_audio_aac_config *)audio->codec_cfg; + if (aac_config->sbr_ps_on_flag) + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + else if (aac_config->sbr_on_flag) + aac_cfg.aot = AAC_ENC_MODE_AAC_P; + else + aac_cfg.aot = AAC_ENC_MODE_AAC_LC; + + switch (aac_config->format) { + case AUDIO_AAC_FORMAT_ADTS: + aac_cfg.format = 0x00; + break; + case AUDIO_AAC_FORMAT_LOAS: + aac_cfg.format = 0x01; + break; + case AUDIO_AAC_FORMAT_ADIF: + aac_cfg.format = 0x02; + break; + default: + case AUDIO_AAC_FORMAT_RAW: + aac_cfg.format = 0x03; + } + aac_cfg.ep_config = aac_config->ep_config; + aac_cfg.section_data_resilience = + aac_config->aac_section_data_resilience_flag; + aac_cfg.scalefactor_data_resilience = + aac_config->aac_scalefactor_data_resilience_flag; + aac_cfg.spectral_data_resilience = + aac_config->aac_spectral_data_resilience_flag; + aac_cfg.ch_cfg = 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_multi_aac(audio->ac, &aac_cfg); + if (rc < 0) { + pr_err("cmd media format block failed\n"); + break; + } + if (!cpu_is_msm8x60()) { + rc = q6asm_set_encdec_chan_map(audio->ac, 2); + if (rc < 0) { + pr_err("%s: cmd set encdec_chan_map failed\n", + __func__); + break; + } + } + rc = audio_aio_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_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_config; + if (copy_from_user(audio->codec_cfg, (void *)arg, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + } else { + uint16_t sce_left = 1, sce_right = 2; + aac_config = audio->codec_cfg; + if ((aac_config->dual_mono_mode < + AUDIO_AAC_DUAL_MONO_PL_PR) || + (aac_config->dual_mono_mode > + AUDIO_AAC_DUAL_MONO_PL_SR)) { + pr_err("%s:AUDIO_SET_AAC_CONFIG: Invalid" + "dual_mono mode =%d\n", __func__, + aac_config->dual_mono_mode); + } else { + /* convert the data from user into sce_left + * and sce_right based on the definitions + */ + pr_debug("%s: AUDIO_SET_AAC_CONFIG: modify" + "dual_mono mode =%d\n", __func__, + aac_config->dual_mono_mode); + switch (aac_config->dual_mono_mode) { + case AUDIO_AAC_DUAL_MONO_PL_PR: + sce_left = 1; + sce_right = 1; + break; + case AUDIO_AAC_DUAL_MONO_SL_SR: + sce_left = 2; + sce_right = 2; + break; + case AUDIO_AAC_DUAL_MONO_SL_PR: + sce_left = 2; + sce_right = 1; + break; + case AUDIO_AAC_DUAL_MONO_PL_SR: + default: + sce_left = 1; + sce_right = 2; + break; + } + rc = q6asm_cfg_dual_mono_aac(audio->ac, + sce_left, sce_right); + if (rc < 0) + pr_err("%s: asm cmd dualmono failed" + " rc=%d\n", __func__, rc); + } break; + } + break; + } + default: + pr_debug("Calling utils ioctl\n"); + rc = audio->codec_ioctl(file, cmd, arg); + } + return rc; +} + + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + struct msm_audio_aac_config *aac_config = 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_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for aac decode driver\n"); + return -ENOMEM; + } + + audio->codec_cfg = kzalloc(sizeof(struct msm_audio_aac_config), + GFP_KERNEL); + if (audio->codec_cfg == NULL) { + pr_err("%s: Could not allocate memory for aac" + "config\n", __func__); + kfree(audio); + return -ENOMEM; + } + + aac_config = audio->codec_cfg; + + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN_AACM; + aac_config->dual_mono_mode = AUDIO_AAC_DUAL_MONO_INVALID; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio->codec_cfg); + kfree(audio); + return -ENOMEM; + } + + /* 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_MULTI_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_MULTI_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; + } + rc = audio_aio_open(audio, file); + +#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, + &audio_aac_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:AAC 5.1 Decoder OPEN success mode[%d]session[%d]\n", + __func__, audio->feedback, audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->codec_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_aac_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_multiaac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_multi_aac", + .fops = &audio_aac_fops, +}; + +static int __init audio_aac_init(void) +{ + return misc_register(&audio_multiaac_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 0000000000000000000000000000000000000000..6e7961cae0ec4f616d8ed47a138d471f4cdc38e8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_mvs.c @@ -0,0 +1,1167 @@ +/* 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 q6_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 q_min_max_rate min_max_rate; + + 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 in_wait; + 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.header.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.header.packet_rate = (*voc_pkt) & 0x0F; + 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.header.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.header.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_G711: + 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: G711A + * Bits 0-1: Frame type + * Bits 2-3: Frame rate + * + * Header format: G711 + * Bits 2-3: Frame rate + */ + if (audio->mvs_mode == MVS_MODE_G711A) + buf_node->frame.header.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 + */ + if (audio->mvs_mode == MVS_MODE_G711A) + buf_node->frame.header.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; + } + + case MVS_MODE_IS733: + case MVS_MODE_4GV_NB: + case MVS_MODE_4GV_WB: { + /* Remove the DSP frame info header. + * Header format: + * Bits 0-3: frame rate + */ + buf_node->frame.header.packet_rate = (*voc_pkt) & 0x0F; + 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_EFR: + case MVS_MODE_FR: + case MVS_MODE_HR: { + /* + * Remove the DSP frame info header + * Header Format + * Bit 0: bfi unused for uplink + * Bit 1-2: sid applies to both uplink and downlink + * Bit 3: taf unused for uplink + * MVS_MODE_HR + * Bit 4: ufi unused for uplink + */ + buf_node->frame.header.gsm_frame_type.sid = + ((*voc_pkt) & 0x06) >> 1; + 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; + } + + default: { + buf_node->frame.header.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.header.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 + */ + *voc_pkt = buf_node->frame.header.packet_rate & 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.header.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.header.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_G711: + 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.header.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.header.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; + } + + case MVS_MODE_IS733: + case MVS_MODE_4GV_NB: + case MVS_MODE_4GV_WB: { + /* Add the DSP frame info header. Header format: + * Bits 0-3 : Frame rate + */ + *voc_pkt = buf_node->frame.header.packet_rate & 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_EFR: + case MVS_MODE_FR: + case MVS_MODE_HR: { + /* + * Remove the DSP frame info header + * Header Format + * Bit 0: bfi applies only for downlink + * Bit 1-2: sid applies for downlink and uplink + * Bit 3: taf applies only for downlink + * MVS_MODE_HR + * Bit 4: ufi applies only for downlink + */ + *voc_pkt = + ((buf_node->frame.header.gsm_frame_type.bfi + & 0x01) | + ((buf_node->frame.header.gsm_frame_type.sid + & 0x03) << 1) | + ((buf_node->frame.header.gsm_frame_type.taf + & 0x01) << 3)); + + if (audio->mvs_mode == MVS_MODE_HR) { + *voc_pkt = (*voc_pkt | + ((buf_node->frame.header.gsm_frame_type.ufi + & 0x01) << 4) | + ((0 & 0x07) << 5)); + } else { + *voc_pkt = (*voc_pkt | + ((0 & 0x0F) << 4)); + } + + 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; + } + + 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); + wake_up(&audio->in_wait); +} + +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_IS733: + media_type = VSS_MEDIA_ID_13K_MODEM; + break; + + case MVS_MODE_IS127: + media_type = VSS_MEDIA_ID_EVRC_MODEM; + break; + + case MVS_MODE_4GV_NB: + media_type = VSS_MEDIA_ID_4GV_NB_MODEM; + break; + + case MVS_MODE_4GV_WB: + media_type = VSS_MEDIA_ID_4GV_WB_MODEM; + break; + + case MVS_MODE_AMR: + media_type = VSS_MEDIA_ID_AMR_NB_MODEM; + break; + + case MVS_MODE_EFR: + media_type = VSS_MEDIA_ID_EFR_MODEM; + break; + + case MVS_MODE_FR: + media_type = VSS_MEDIA_ID_FR_MODEM; + break; + + case MVS_MODE_HR: + media_type = VSS_MEDIA_ID_HR_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_G711: + 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; + + case MVS_MODE_PCM_WB: + media_type = VSS_MEDIA_ID_PCM_WB; + 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_IS733: + case MVS_MODE_IS127: + case MVS_MODE_4GV_NB: + case MVS_MODE_AMR: + case MVS_MODE_EFR: + case MVS_MODE_FR: + case MVS_MODE_HR: + case MVS_MODE_LINEAR_PCM: + case MVS_MODE_G711: + case MVS_MODE_PCM: + case MVS_MODE_G729A: + case MVS_MODE_G711A: + network_type = VSS_NETWORK_ID_VOIP_NB; + break; + + case MVS_MODE_4GV_WB: + case MVS_MODE_AMR_WB: + case MVS_MODE_PCM_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->min_max_rate); + + 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 q6_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 q6_msm_audio_mvs_frame)); + + if (rc == 0) { + rc = buf_node->frame.len + + sizeof(buf_node->frame.header) + + 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 q6_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__); + + rc = wait_event_interruptible_timeout(audio->in_wait, + (!list_empty(&audio->free_in_queue) || + audio->state == AUDIO_MVS_STOPPED), 1 * HZ); + if (rc > 0) { + mutex_lock(&audio->in_lock); + + if (audio->state == AUDIO_MVS_STARTED) { + if (count <= sizeof(struct q6_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 q6_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); + } else if (rc == 0) { + pr_err("%s: No free DL buffs\n", __func__); + + rc = -ETIMEDOUT; + } else { + pr_err("%s: write was interrupted\n", __func__); + + rc = -ERESTARTSYS; + } + + 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; + config.min_max_rate.min_rate = audio->min_max_rate.min_rate; + config.min_max_rate.max_rate = audio->min_max_rate.max_rate; + 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; + audio->min_max_rate.min_rate = + config.min_max_rate.min_rate; + audio->min_max_rate.max_rate = + config.min_max_rate.max_rate; + } 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.in_wait); + 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_qcelp.c b/arch/arm/mach-msm/qdsp6v2/audio_qcelp.c new file mode 100644 index 0000000000000000000000000000000000000000..37f6e6bdb197d34faea302b67e95e3468eb65444 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_qcelp.c @@ -0,0 +1,175 @@ +/* qcelp(v13k) audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011-2012, 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 "audio_utils_aio.h" + +#define FRAME_SIZE_DEC_QCELP ((32) + sizeof(struct dec_meta_in)) + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_qcelp_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + pr_debug("%s[%p]: AUDIO_START session_id[%d]\n", __func__, + audio, audio->ac->session); + 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; + } + } + + rc = audio_aio_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("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__, + audio->ac->session, + audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_qcelp_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio_aio), 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_DEC_QCELP; + 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 = 8000; + audio->pcm_cfg.channel_count = 1; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + + /* 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_V13K); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + 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_V13K); + 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; + } + rc = audio_aio_open(audio, file); + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_qcelp_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audio_qcelp_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:dec 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_qcelp_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_qcelp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp", + .fops = &audio_qcelp_fops, +}; + +static int __init audio_qcelp_init(void) +{ + return misc_register(&audio_qcelp_misc); +} + +device_initcall(audio_qcelp_init); 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 0000000000000000000000000000000000000000..6a23e3720919466c7ff5e5ba0c748f1e38b723d0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_utils.c @@ -0,0 +1,637 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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); + /* Flush if session running */ + if (audio->enabled) { + /* Implicitly issue a pause to the encoder 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; + } + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + q6asm_run(audio->ac, 0x00, 0x00, 0x00); + pr_debug("Rerun the session\n"); + } + 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); + atomic_set(&audio->out_count, 0); + return 0; +} + +/* 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); + else { /* Register back the flushed read buffer with DSP */ + int cnt = 0; + while (cnt++ < audio->str_cfg.buffer_count) + q6asm_read(audio->ac); /* Push buffer to DSP */ + pr_debug("register the read buffer\n"); + } + 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; + } + /* if no PCM data, might have only eos buffer + such case do not hold cpu buffer */ + if ((buf == start) && (count == mfield_size)) { + char eos_buf[sizeof(struct meta_in)]; + /* Processing begining of user buffer */ + if (copy_from_user(eos_buf, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + extract_meta_info(eos_buf, &msw_ts, &lsw_ts, + &nflags); + buf += mfield_size; + /* send the EOS and return */ + pr_debug("%s:session id %d: send EOS" + "0x%8x\n", __func__, + audio->ac->session, nflags); + 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; + 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->codec_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 0000000000000000000000000000000000000000..df963f99fbd15bd33968c9a6973358972ea7c2bd --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_utils.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ +#include +#include "q6audio_common.h" + +#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); +}; + +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_utils_aio.c b/arch/arm/mach-msm/qdsp6v2/audio_utils_aio.c new file mode 100644 index 0000000000000000000000000000000000000000..6a99be2ea506dab69ac2a00f5b523b6aae96910f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_utils_aio.c @@ -0,0 +1,1407 @@ +/* Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 "audio_utils_aio.h" + +#ifdef CONFIG_DEBUG_FS +ssize_t audio_aio_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +ssize_t audio_aio_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_aio *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); +} +#endif + +static int insert_eos_buf(struct q6audio_aio *audio, + struct audio_aio_buffer_node *buf_node) +{ + struct dec_meta_out *eos_buf = buf_node->kvaddr; + pr_debug("%s[%p]:insert_eos_buf\n", __func__, audio); + eos_buf->num_of_frames = 0xFFFFFFFF; + eos_buf->meta_out_dsp[0].offset_to_frame = 0x0; + eos_buf->meta_out_dsp[0].nflags = AUDIO_DEC_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_flush(struct q6audio_aio *audio, + struct audio_aio_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_out_info(struct q6audio_aio *audio, + struct audio_aio_buffer_node *buf_node, int dir) +{ + struct dec_meta_out *meta_data = buf_node->kvaddr; + 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 dec_meta_in)); + else + memset(&buf_node->meta_info.meta_in, + 0, sizeof(struct dec_meta_in)); + pr_debug("%s[%p]:i/p: msw_ts 0x%lx lsw_ts 0x%lx nflags 0x%8x\n", + __func__, audio, + 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)); + meta_data->meta_out_dsp[0].nflags = 0x00000000; + pr_debug("%s[%p]:o/p: msw_ts 0x%8x lsw_ts 0x%8x nflags 0x%8x," + "num_frames = %d\n", + __func__, audio, + ((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 audio_aio_ion_lookup_vaddr(struct q6audio_aio *audio, void *addr, + unsigned long len, + struct audio_aio_ion_region **region) +{ + struct audio_aio_ion_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->ion_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 + * ion buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("%s[%p]:multiple hits for vaddr %p, len %ld\n", + __func__, audio, addr, len); + list_for_each_entry(region_elt, &audio->ion_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%s[%p]:%p, %ld --> %p\n", + __func__, audio, + region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audio_aio_ion_fixup(struct q6audio_aio *audio, void *addr, + unsigned long len, int ref_up, void **kvaddr) +{ + struct audio_aio_ion_region *region; + unsigned long paddr; + int ret; + + ret = audio_aio_ion_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("%s[%p]:lookup (%p, %ld) failed\n", + __func__, audio, addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + pr_debug("%s[%p]:found region %p ref_cnt %d\n", + __func__, audio, 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 int audio_aio_pause(struct q6audio_aio *audio) +{ + int rc = 0; + + pr_debug("%s[%p], enabled = %d\n", __func__, audio, + audio->enabled); + if (audio->enabled) { + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s[%p]: pause cmd failed rc=%d\n", + __func__, audio, rc); + + } else + pr_err("%s[%p]: Driver not enabled\n", __func__, audio); + return rc; +} + +static int audio_aio_flush(struct q6audio_aio *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 = audio_aio_pause(audio); + if (rc < 0) + pr_err("%s[%p}: pause cmd failed rc=%d\n", + __func__, audio, + rc); + else + audio->drv_status |= ADRV_STATUS_PAUSE; + } + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) + pr_err("%s[%p]: flush cmd failed rc=%d\n", + __func__, audio, rc); + /* Not in stop state, reenable the stream */ + if (audio->stopped == 0) { + rc = audio_aio_enable(audio); + if (rc) + pr_err("%s[%p]:audio re-enable failed\n", + __func__, audio); + else { + audio->enabled = 1; + if (audio->drv_status & ADRV_STATUS_PAUSE) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + } + } + } + pr_debug("%s[%p]:in_bytes %d\n", + __func__, audio, atomic_read(&audio->in_bytes)); + pr_debug("%s[%p]:in_samples %d\n", + __func__, audio, atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); + return 0; +} + +static int audio_aio_outport_flush(struct q6audio_aio *audio) +{ + int rc; + + rc = q6asm_cmd(audio->ac, CMD_OUT_FLUSH); + if (rc < 0) + pr_err("%s[%p}: output port flush cmd failed rc=%d\n", + __func__, audio, rc); + return rc; +} + +/* Write buffer to DSP / Handle Ack from DSP */ +void audio_aio_async_write_ack(struct q6audio_aio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audio_aio_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 audio_aio_buffer_node, list); + if (token == used_buf->token) { + list_del(&used_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + pr_debug("%s[%p]:consumed buffer\n", __func__, audio); + event_payload.aio_buf = used_buf->buf; + audio_aio_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[%p]: list is empty, reached EOS in" + "Tunnel\n", __func__, audio); + wake_up(&audio->write_wait); + } + } else { + pr_err("%s[%p]:expected=%lx ret=%x\n", + __func__, audio, used_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +/* Read buffer from DSP / Handle Ack from DSP */ +void audio_aio_async_read_ack(struct q6audio_aio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audio_aio_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 audio_aio_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("%s[%p]:nr of frames 0x%8x len=%d\n", + __func__, audio, + filled_buf->meta_info.meta_out.num_of_frames, + event_payload.aio_buf.data_len); + extract_meta_out_info(audio, filled_buf, 0); + audio->eos_rsp = 0; + } + audio_aio_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(filled_buf); + } else { + pr_err("%s[%p]:expected=%lx ret=%x\n", + __func__, audio, filled_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +/* ------------------- device --------------------- */ +void audio_aio_async_out_flush(struct q6audio_aio *audio) +{ + struct audio_aio_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + unsigned long flags; + + pr_debug("%s[%p}\n", __func__, audio); + /* 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[%p]: EOS followed by flush received,acknowledge"\ + " eos i/p buffer immediately\n", __func__, audio); + audio_aio_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 audio_aio_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audio_aio_post_event(audio, AUDIO_EVENT_WRITE_DONE, payload); + kfree(buf_node); + pr_debug("%s[%p]: Propagate WRITE_DONE during flush\n", + __func__, audio); + } +} + +void audio_aio_async_in_flush(struct q6audio_aio *audio) +{ + struct audio_aio_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + pr_debug("%s[%p]\n", __func__, audio); + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audio_aio_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[%p]: send eos on o/p buffer during" + "flush\n", __func__, audio); + 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_flush(audio, buf_node); + } + audio_aio_post_event(audio, AUDIO_EVENT_READ_DONE, payload); + kfree(buf_node); + pr_debug("%s[%p]: Propagate READ_DONE during flush\n", + __func__, audio); + } +} + +int audio_aio_enable(struct q6audio_aio *audio) +{ + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + return q6asm_run(audio->ac, 0x00, 0x00, 0x00); +} + +int audio_aio_disable(struct q6audio_aio *audio) +{ + int rc = 0; + if (audio->opened) { + audio->enabled = 0; + audio->opened = 0; + pr_debug("%s[%p]: inbytes[%d] insamples[%d]\n", __func__, + audio, 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("%s[%p]:Failed to close the session rc=%d\n", + __func__, audio, rc); + audio->stopped = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + pr_debug("%s[%p]:enabled[%d]\n", __func__, audio, audio->enabled); + return rc; +} + +void audio_aio_reset_ion_region(struct q6audio_aio *audio) +{ + struct audio_aio_ion_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->ion_region_queue) { + region = list_entry(ptr, struct audio_aio_ion_region, list); + list_del(®ion->list); + ion_unmap_kernel(region->client, region->handle); + ion_free(region->client, region->handle); + ion_client_destroy(region->client); + kfree(region); + } + + return; +} + +void audio_aio_reset_event_queue(struct q6audio_aio *audio) +{ + unsigned long flags; + struct audio_aio_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 audio_aio_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 audio_aio_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static void audio_aio_unmap_ion_region(struct q6audio_aio *audio) +{ + struct audio_aio_ion_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("%s[%p]:\n", __func__, audio); + list_for_each_safe(ptr, next, &audio->ion_region_queue) { + region = list_entry(ptr, struct audio_aio_ion_region, list); + pr_debug("%s[%p]: phy_address = 0x%lx\n", + __func__, audio, region->paddr); + if (region != NULL) { + rc = q6asm_memory_unmap(audio->ac, + (uint32_t)region->paddr, IN); + if (rc < 0) + pr_err("%s[%p]: memory unmap failed\n", + __func__, audio); + } + } +} + +int audio_aio_release(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = file->private_data; + pr_debug("%s[%p]\n", __func__, audio); + mutex_lock(&audio->lock); + audio->wflush = 1; + if (audio->enabled) + audio_aio_flush(audio); + audio->wflush = 0; + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audio_aio_unmap_ion_region(audio); + audio_aio_disable(audio); + audio_aio_reset_ion_region(audio); + audio->event_abort = 1; + wake_up(&audio->event_wait); + audio_aio_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->codec_cfg); + kfree(audio); + return 0; +} + +int audio_aio_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ + int rc = 0; + struct q6audio_aio *audio = file->private_data; + + if (!audio->enabled || audio->feedback) + return -EINVAL; + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + pr_debug("%s[%p]:\n", __func__, audio); + + 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[%p]: wait event for list_empty failed, rc = %d\n", + __func__, audio, rc); + goto done; + } + + rc = q6asm_cmd(audio->ac, CMD_EOS); + + if (rc < 0) + pr_err("%s[%p]: q6asm_cmd failed, rc = %d", + __func__, audio, rc); + + rc = wait_event_interruptible(audio->write_wait, + (audio->eos_rsp || audio->wflush || + audio->stopped)); + + if (rc < 0) { + pr_err("%s[%p]: wait event for eos_rsp failed, rc = %d\n", + __func__, audio, rc); + goto done; + } + + if (audio->eos_rsp == 1) { + rc = audio_aio_enable(audio); + if (rc) + pr_err("%s[%p]: audio enable failed\n", + __func__, audio); + 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 audio_aio_events_pending(struct q6audio_aio *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 long audio_aio_process_event_req(struct q6audio_aio *audio, + void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audio_aio_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, + audio_aio_events_pending + (audio), + msecs_to_jiffies + (timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible(audio->event_wait, + audio_aio_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 audio_aio_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("%s[%p]:Unexpected path\n", __func__, audio); + 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("%s[%p]:posted AUDIO_EVENT_WRITE_DONE to user\n", + __func__, audio); + mutex_lock(&audio->write_lock); + audio_aio_ion_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("%s[%p]:posted AUDIO_EVENT_READ_DONE to user\n", + __func__, audio); + mutex_lock(&audio->read_lock); + audio_aio_ion_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("%s[%p]:Send flush command to release read buffers"\ + " held up in DSP\n", __func__, audio); + audio_aio_flush(audio); + } + + if (copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_aio_ion_check(struct q6audio_aio *audio, + void *vaddr, unsigned long len) +{ + struct audio_aio_ion_region *region_elt; + struct audio_aio_ion_region t = {.vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->ion_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("%s[%p]:region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + __func__, audio, vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audio_aio_ion_add(struct q6audio_aio *audio, + struct msm_audio_ion_info *info) +{ + ion_phys_addr_t paddr; + size_t len; + unsigned long kvaddr; + struct audio_aio_ion_region *region; + int rc = -EINVAL; + struct ion_handle *handle; + struct ion_client *client; + unsigned long ionflag; + void *temp_ptr; + + pr_debug("%s[%p]:\n", __func__, audio); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + client = msm_ion_client_create(UINT_MAX, "Audio_Dec_Client"); + if (IS_ERR_OR_NULL(client)) { + pr_err("Unable to create ION client\n"); + goto client_error; + } + + handle = ion_import_fd(client, info->fd); + if (IS_ERR_OR_NULL(handle)) { + pr_err("%s: could not get handle of the given fd\n", __func__); + goto import_error; + } + + rc = ion_handle_get_flags(client, handle, &ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", __func__); + goto flag_error; + } + + temp_ptr = ion_map_kernel(client, handle, ionflag); + if (IS_ERR_OR_NULL(temp_ptr)) { + pr_err("%s: could not get virtual address\n", __func__); + goto map_error; + } + kvaddr = (unsigned long)temp_ptr; + + rc = ion_phys(client, handle, &paddr, &len); + if (rc) { + pr_err("%s: could not get physical address\n", __func__); + goto ion_error; + } + + rc = audio_aio_ion_check(audio, info->vaddr, len); + if (rc < 0) { + pr_err("%s: audio_aio_ion_check failed\n", __func__); + goto ion_error; + } + + region->client = client; + region->handle = handle; + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->ref_cnt = 0; + pr_debug("%s[%p]:add region paddr %lx vaddr %p, len %lu kvaddr %lx\n", + __func__, audio, + region->paddr, region->vaddr, region->len, region->kvaddr); + list_add_tail(®ion->list, &audio->ion_region_queue); + rc = q6asm_memory_map(audio->ac, (uint32_t) paddr, IN, (uint32_t) len, + 1); + if (rc < 0) { + pr_err("%s[%p]: memory map failed\n", __func__, audio); + goto ion_error; + } else { + goto end; + } + +ion_error: + ion_unmap_kernel(client, handle); +map_error: + ion_free(client, handle); +flag_error: +import_error: + ion_client_destroy(client); +client_error: + kfree(region); +end: + return rc; +} + +static int audio_aio_ion_remove(struct q6audio_aio *audio, + struct msm_audio_ion_info *info) +{ + struct audio_aio_ion_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("%s[%p]:info fd %d vaddr %p\n", + __func__, audio, info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->ion_region_queue) { + region = list_entry(ptr, struct audio_aio_ion_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("%s[%p]:region %p in use ref_cnt %d\n", + __func__, audio, region, + region->ref_cnt); + break; + } + pr_debug("%s[%p]:remove region fd %d vaddr %p\n", + __func__, audio, info->fd, info->vaddr); + rc = q6asm_memory_unmap(audio->ac, + (uint32_t) region->paddr, IN); + if (rc < 0) + pr_err("%s[%p]: memory unmap failed\n", + __func__, audio); + + list_del(®ion->list); + ion_unmap_kernel(region->client, region->handle); + ion_free(region->client, region->handle); + ion_client_destroy(region->client); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static void audio_aio_async_write(struct q6audio_aio *audio, + struct audio_aio_buffer_node *buf_node) +{ + int rc; + struct audio_client *ac; + struct audio_aio_write_param param; + + pr_debug("%s[%p]: Send write buff %p phy %lx len %d" + "meta_enable = %d\n", + __func__, audio, buf_node, buf_node->paddr, + buf_node->buf.data_len, + audio->buf_cfg.meta_info_enable); + + ac = audio->ac; + /* Offset with appropriate meta */ + if (audio->feedback) { + /* Non Tunnel mode */ + param.paddr = buf_node->paddr + sizeof(struct dec_meta_in); + param.len = buf_node->buf.data_len - sizeof(struct dec_meta_in); + } else { + /* Tunnel mode */ + param.paddr = buf_node->paddr; + param.len = buf_node->buf.data_len; + } + 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[%p]:failed\n", __func__, audio); +} + +void audio_aio_post_event(struct q6audio_aio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audio_aio_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 audio_aio_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audio_aio_event), GFP_ATOMIC); + if (!e_node) { + pr_err("%s[%p]:No mem to post event %d\n", + __func__, audio, 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 audio_aio_async_read(struct q6audio_aio *audio, + struct audio_aio_buffer_node *buf_node) +{ + struct audio_client *ac; + struct audio_aio_read_param param; + int rc; + + pr_debug("%s[%p]: Send read buff %p phy %lx len %d\n", + __func__, audio, 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[%p]:failed\n", __func__, audio); +} + +static int audio_aio_buf_add(struct q6audio_aio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audio_aio_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("%s[%p]:node %p dir %x buf_addr %p buf_len %d data_len" + "%d\n", __func__, audio, buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + buf_node->paddr = audio_aio_ion_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_out_info(audio, buf_node, 1); + /* Not a EOS buffer */ + if (!(buf_node->meta_info.meta_in.nflags & AUDIO_DEC_EOS_SET)) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audio_aio_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); + } else if (buf_node->meta_info.meta_in.nflags + & AUDIO_DEC_EOS_SET) { + if (!audio->wflush) { + pr_debug("%s[%p]:Send EOS cmd at i/p\n", + __func__, audio); + /* 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; + audio_aio_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); + audio_aio_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[%p]: propagate READ_DONE as EOS done\n",\ + __func__, audio); + audio_aio_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(buf_node); + } + } + return 0; +} + +static void audio_aio_ioport_reset(struct q6audio_aio *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("%s[%p]:fsync in progress\n", __func__, audio); + audio->drv_ops.out_flush(audio); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } +} + +int audio_aio_open(struct q6audio_aio *audio, struct file *file) +{ + int rc = 0; + int i; + struct audio_aio_event *e_node = NULL; + + /* 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_count = PCM_BUF_COUNT; + audio->pcm_cfg.sample_rate = 48000; + audio->pcm_cfg.channel_count = 2; + + /* Only AIO interface */ + if (file->f_flags & O_NONBLOCK) { + pr_debug("%s[%p]:set to aio interface\n", __func__, audio); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.out_flush = audio_aio_async_out_flush; + audio->drv_ops.in_flush = audio_aio_async_in_flush; + q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE); + } else { + pr_err("%s[%p]:SIO interface not supported\n", + __func__, audio); + 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->ion_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; + audio->codec_ioctl = audio_aio_ioctl; + + for (i = 0; i < AUDIO_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audio_aio_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + pr_err("%s[%p]:event pkt alloc failed\n", + __func__, audio); + break; + } + } + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->codec_cfg); + kfree(audio); + return rc; +} + +long audio_aio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case 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))) + rc = -EFAULT; + break; + } + case AUDIO_GET_EVENT: { + pr_debug("%s[%p]:AUDIO_GET_EVENT\n", __func__, audio); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audio_aio_process_event_req(audio, + (void __user *)arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + break; + } + case AUDIO_ABORT_GET_EVENT: { + audio->event_abort = 1; + wake_up(&audio->event_wait); + break; + } + case AUDIO_ASYNC_WRITE: { + mutex_lock(&audio->write_lock); + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else { + if (audio->enabled) + rc = audio_aio_buf_add(audio, 1, + (void __user *)arg); + else + rc = -EPERM; + } + mutex_unlock(&audio->write_lock); + break; + } + case AUDIO_ASYNC_READ: { + mutex_lock(&audio->read_lock); + if ((audio->feedback) && (audio->enabled)) + rc = audio_aio_buf_add(audio, 0, + (void __user *)arg); + else + rc = -EPERM; + mutex_unlock(&audio->read_lock); + break; + } + case AUDIO_OUTPORT_FLUSH: { + pr_debug("%s[%p]:AUDIO_OUTPORT_FLUSH\n", __func__, audio); + mutex_lock(&audio->read_lock); + rc = audio_aio_outport_flush(audio); + if (rc < 0) { + pr_err("%s[%p]: AUDIO_OUTPORT_FLUSH failed\n", + __func__, audio); + rc = -EINTR; + } + mutex_unlock(&audio->read_lock); + break; + } + case AUDIO_STOP: { + pr_debug("%s[%p]: AUDIO_STOP session_id[%d]\n", __func__, + audio, audio->ac->session); + mutex_lock(&audio->lock); + audio->stopped = 1; + audio_aio_flush(audio); + audio->enabled = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + if (rc < 0) { + pr_err("%s[%p]:Audio Stop procedure failed rc=%d\n", + __func__, audio, rc); + mutex_unlock(&audio->lock); + break; + } + mutex_unlock(&audio->lock); + break; + } + case AUDIO_PAUSE: { + pr_debug("%s[%p]:AUDIO_PAUSE %ld\n", __func__, audio, arg); + mutex_lock(&audio->lock); + if (arg == 1) { + rc = audio_aio_pause(audio); + if (rc < 0) + pr_err("%s[%p]: pause FAILED rc=%d\n", + __func__, audio, rc); + audio->drv_status |= ADRV_STATUS_PAUSE; + } else if (arg == 0) { + if (audio->drv_status & ADRV_STATUS_PAUSE) { + rc = audio_aio_enable(audio); + if (rc) + pr_err("%s[%p]: audio enable failed\n", + __func__, audio); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + } + mutex_unlock(&audio->lock); + break; + } + case AUDIO_FLUSH: { + pr_debug("%s[%p]: AUDIO_FLUSH sessionid[%d]\n", __func__, + audio, audio->ac->session); + mutex_lock(&audio->lock); + audio->rflush = 1; + audio->wflush = 1; + /* Flush DSP */ + rc = audio_aio_flush(audio); + /* Flush input / Output buffer in software*/ + audio_aio_ioport_reset(audio); + if (rc < 0) { + pr_err("%s[%p]:AUDIO_FLUSH interrupted\n", + __func__, audio); + rc = -EINTR; + } else { + audio->rflush = 0; + audio->wflush = 0; + } + audio->eos_flag = 0; + audio->eos_rsp = 0; + mutex_unlock(&audio->lock); + break; + } + case AUDIO_REGISTER_ION: { + struct msm_audio_ion_info info; + pr_debug("%s[%p]:AUDIO_REGISTER_ION\n", __func__, audio); + mutex_lock(&audio->lock); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audio_aio_ion_add(audio, &info); + mutex_unlock(&audio->lock); + break; + } + case AUDIO_DEREGISTER_ION: { + struct msm_audio_ion_info info; + mutex_lock(&audio->lock); + pr_debug("%s[%p]:AUDIO_DEREGISTER_ION\n", __func__, audio); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audio_aio_ion_remove(audio, &info); + mutex_unlock(&audio->lock); + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + mutex_lock(&audio->lock); + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->str_cfg.buffer_size; + cfg.buffer_count = audio->str_cfg.buffer_count; + pr_debug("%s[%p]:GET STREAM CFG %d %d\n", + __func__, audio, cfg.buffer_size, cfg.buffer_count); + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + pr_debug("%s[%p]:SET STREAM CONFIG\n", __func__, audio); + mutex_lock(&audio->lock); + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + rc = 0; + mutex_unlock(&audio->lock); + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + mutex_lock(&audio->lock); + if (copy_to_user((void *)arg, &audio->pcm_cfg, sizeof(cfg))) + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + pr_err("%s[%p]:AUDIO_SET_CONFIG\n", __func__, audio); + mutex_lock(&audio->lock); + if (copy_from_user(&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + if (audio->feedback != NON_TUNNEL_MODE) { + pr_err("%s[%p]:Not sufficient permission to" + "change the playback mode\n", __func__, audio); + rc = -EACCES; + mutex_unlock(&audio->lock); + 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; + mutex_unlock(&audio->lock); + break; + } + case AUDIO_SET_BUF_CFG: { + struct msm_audio_buf_cfg cfg; + mutex_lock(&audio->lock); + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + if ((audio->feedback == NON_TUNNEL_MODE) && + !cfg.meta_info_enable) { + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + + audio->buf_cfg.meta_info_enable = cfg.meta_info_enable; + pr_debug("%s[%p]:session id %d: Set-buf-cfg: meta[%d]", + __func__, audio, + audio->ac->session, cfg.meta_info_enable); + mutex_unlock(&audio->lock); + break; + } + case AUDIO_GET_BUF_CFG: { + pr_debug("%s[%p]:session id %d: Get-buf-cfg: meta[%d]" + "framesperbuf[%d]\n", __func__, audio, + audio->ac->session, audio->buf_cfg.meta_info_enable, + audio->buf_cfg.frames_per_buf); + + mutex_lock(&audio->lock); + if (copy_to_user((void *)arg, &audio->buf_cfg, + sizeof(struct msm_audio_buf_cfg))) + rc = -EFAULT; + mutex_unlock(&audio->lock); + break; + } + case AUDIO_GET_SESSION_ID: { + mutex_lock(&audio->lock); + if (copy_to_user((void *)arg, &audio->ac->session, + sizeof(unsigned short))) { + rc = -EFAULT; + } + mutex_unlock(&audio->lock); + break; + } + default: + rc = -EINVAL; + } + return rc; +} diff --git a/arch/arm/mach-msm/qdsp6v2/audio_utils_aio.h b/arch/arm/mach-msm/qdsp6v2/audio_utils_aio.h new file mode 100644 index 0000000000000000000000000000000000000000..77288da2c7509f675d1fa0103e9cc7122a4ffb98 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_utils_aio.h @@ -0,0 +1,211 @@ +/* Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 "q6audio_common.h" + +#define TUNNEL_MODE 0x0000 +#define NON_TUNNEL_MODE 0x0001 + +#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */ +#define ADRV_STATUS_FSYNC 0x00000008 +#define ADRV_STATUS_PAUSE 0x00000010 +#define AUDIO_DEC_EOS_SET 0x00000001 +#define AUDIO_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_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_in { + unsigned char reserved[18]; + unsigned short offset; + struct timestamp ntimestamp; + unsigned int 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 dec_meta_in meta_in; +} __packed; + +#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*1536) + sizeof(struct dec_meta_in)) + +struct audio_aio_ion_region { + struct list_head list; + struct ion_handle *handle; + struct ion_client *client; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audio_aio_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio_aio_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_aio; +struct audio_aio_drv_operations { + void (*out_flush) (struct q6audio_aio *); + void (*in_flush) (struct q6audio_aio *); +}; + +struct q6audio_aio { + 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; + void *codec_cfg; + + 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 ion_region_queue; /* protected by lock */ + struct audio_aio_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 */ + long (*codec_ioctl)(struct file *, unsigned int, unsigned long); +}; + +void audio_aio_async_write_ack(struct q6audio_aio *audio, uint32_t token, + uint32_t *payload); + +void audio_aio_async_read_ack(struct q6audio_aio *audio, uint32_t token, + uint32_t *payload); + +int audio_aio_open(struct q6audio_aio *audio, struct file *file); +int audio_aio_enable(struct q6audio_aio *audio); +void audio_aio_post_event(struct q6audio_aio *audio, int type, + union msm_audio_event_payload payload); +int audio_aio_release(struct inode *inode, struct file *file); +long audio_aio_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +int audio_aio_fsync(struct file *file, loff_t start, loff_t end, int datasync); +void audio_aio_async_out_flush(struct q6audio_aio *audio); +void audio_aio_async_in_flush(struct q6audio_aio *audio); +#ifdef CONFIG_DEBUG_FS +ssize_t audio_aio_debug_open(struct inode *inode, struct file *file); +ssize_t audio_aio_debug_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos); +#endif 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 0000000000000000000000000000000000000000..021d58bb7a992a1d3350c2058a8e2256d9be8b58 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_wma.c @@ -0,0 +1,210 @@ +/* wma audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 "audio_utils_aio.h" + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_wma_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + struct asm_wma_cfg wma_cfg; + struct msm_audio_wma_config_v2 *wma_config; + pr_debug("%s[%p]: AUDIO_START session_id[%d]\n", __func__, + audio, audio->ac->session); + 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_config = (struct msm_audio_wma_config_v2 *)audio->codec_cfg; + wma_cfg.format_tag = wma_config->format_tag; + wma_cfg.ch_cfg = wma_config->numchannels; + wma_cfg.sample_rate = wma_config->samplingrate; + wma_cfg.avg_bytes_per_sec = wma_config->avgbytespersecond; + wma_cfg.block_align = wma_config->block_align; + wma_cfg.valid_bits_per_sample = + wma_config->validbitspersample; + wma_cfg.ch_mask = wma_config->channelmask; + wma_cfg.encode_opt = 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 = audio_aio_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_GET_WMA_CONFIG_V2: { + if (copy_to_user((void *)arg, audio->codec_cfg, + sizeof(struct msm_audio_wma_config_v2))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_SET_WMA_CONFIG_V2: { + if (copy_from_user(audio->codec_cfg, (void *)arg, + sizeof(struct msm_audio_wma_config_v2))) { + rc = -EFAULT; + break; + } + break; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + if (rc) + pr_err("Failed in utils_ioctl: %d\n", rc); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#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_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for wma decode driver\n"); + return -ENOMEM; + } + audio->codec_cfg = kzalloc(sizeof(struct msm_audio_wma_config_v2), + GFP_KERNEL); + if (audio->codec_cfg == NULL) { + pr_err("%s:Could not allocate memory for wma" + "config\n", __func__); + kfree(audio); + return -ENOMEM; + } + + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio->codec_cfg); + kfree(audio); + return -ENOMEM; + } + /* 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; + } + rc = audio_aio_open(audio, file); + +#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, + &audio_wma_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:wmadec success mode[%d]session[%d]\n", __func__, + audio->feedback, + audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->codec_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wma_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_wma_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wma", + .fops = &audio_wma_fops, +}; + +static int __init audio_wma_init(void) +{ + return misc_register(&audio_wma_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 0000000000000000000000000000000000000000..4fcdcc139d77105ba890b9094c201a2d61d8940e --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_wmapro.c @@ -0,0 +1,269 @@ +/* wmapro audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2012, 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 "audio_utils_aio.h" + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations audio_wmapro_debug_fops = { + .read = audio_aio_debug_read, + .open = audio_aio_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio_aio *audio = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_START: { + struct asm_wmapro_cfg wmapro_cfg; + struct msm_audio_wmapro_config *wmapro_config; + pr_debug("%s: AUDIO_START session_id[%d]\n", __func__, + audio->ac->session); + 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; + } + } + wmapro_config = (struct msm_audio_wmapro_config *) + audio->codec_cfg; + if ((wmapro_config->formattag == 0x162) || + (wmapro_config->formattag == 0x163) || + (wmapro_config->formattag == 0x166) || + (wmapro_config->formattag == 0x167)) { + wmapro_cfg.format_tag = wmapro_config->formattag; + } else { + pr_err("%s:AUDIO_START failed: formattag = %d\n", + __func__, wmapro_config->formattag); + rc = -EINVAL; + break; + } + if ((wmapro_config->numchannels == 1) || + (wmapro_config->numchannels == 2)) { + wmapro_cfg.ch_cfg = wmapro_config->numchannels; + } else { + pr_err("%s:AUDIO_START failed: channels = %d\n", + __func__, wmapro_config->numchannels); + rc = -EINVAL; + break; + } + if ((wmapro_config->samplingrate <= 48000) || + (wmapro_config->samplingrate > 0)) { + wmapro_cfg.sample_rate = + wmapro_config->samplingrate; + } else { + pr_err("%s:AUDIO_START failed: sample_rate = %d\n", + __func__, wmapro_config->samplingrate); + rc = -EINVAL; + break; + } + wmapro_cfg.avg_bytes_per_sec = + wmapro_config->avgbytespersecond; + if ((wmapro_config->asfpacketlength <= 13376) || + (wmapro_config->asfpacketlength > 0)) { + wmapro_cfg.block_align = + wmapro_config->asfpacketlength; + } else { + pr_err("%s:AUDIO_START failed: block_align = %d\n", + __func__, wmapro_config->asfpacketlength); + rc = -EINVAL; + break; + } + if ((wmapro_config->validbitspersample == 16) || + (wmapro_config->validbitspersample == 24)) { + wmapro_cfg.valid_bits_per_sample = + wmapro_config->validbitspersample; + } else { + pr_err("%s:AUDIO_START failed: bitspersample = %d\n", + __func__, + wmapro_config->validbitspersample); + rc = -EINVAL; + break; + } + if ((wmapro_config->channelmask == 4) || + (wmapro_config->channelmask == 3)) { + wmapro_cfg.ch_mask = wmapro_config->channelmask; + } else { + pr_err("%s:AUDIO_START failed: channel_mask = %d\n", + __func__, wmapro_config->channelmask); + rc = -EINVAL; + break; + } + wmapro_cfg.encode_opt = wmapro_config->encodeopt; + wmapro_cfg.adv_encode_opt = + wmapro_config->advancedencodeopt; + wmapro_cfg.adv_encode_opt2 = + 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 = audio_aio_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_GET_WMAPRO_CONFIG: { + if (copy_to_user((void *)arg, audio->codec_cfg, + sizeof(struct msm_audio_wmapro_config))) { + rc = -EFAULT; + } + break; + } + case AUDIO_SET_WMAPRO_CONFIG: { + if (copy_from_user(audio->codec_cfg, (void *)arg, + sizeof(struct msm_audio_wmapro_config))) { + rc = -EFAULT; + break; + } + break; + } + default: + pr_debug("%s[%p]: Calling utils ioctl\n", __func__, audio); + rc = audio->codec_ioctl(file, cmd, arg); + if (rc) + pr_err("Failed in utils_ioctl: %d\n", rc); + } + return rc; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio_aio *audio = NULL; + int rc = 0; + +#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_aio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for wma decode driver\n"); + return -ENOMEM; + } + audio->codec_cfg = kzalloc(sizeof(struct msm_audio_wmapro_config), + GFP_KERNEL); + if (audio->codec_cfg == NULL) { + pr_err("%s: Could not allocate memory for wmapro" + "config\n", __func__); + kfree(audio); + return -ENOMEM; + } + + + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audio_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio->codec_cfg); + kfree(audio); + return -ENOMEM; + } + + /* 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; + } + rc = audio_aio_open(audio, file); + +#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, + &audio_wmapro_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + pr_info("%s:wmapro decoder open success, session_id = %d\n", __func__, + audio->ac->session); + return rc; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->codec_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wmapro_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_aio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_aio_fsync, +}; + +struct miscdevice audio_wmapro_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wmapro", + .fops = &audio_wmapro_fops, +}; + +static int __init audio_wmapro_init(void) +{ + return misc_register(&audio_wmapro_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 0000000000000000000000000000000000000000..0181cb53fd44622033f76ac712dfaade2bd72f3e --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/board-msm8x60-audio.c @@ -0,0 +1,2718 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 +#define SNDDEV_GPIO_HS_MIC4_SEL 296 + +#define DSP_RAM_BASE_8x60 0x46700000 +#define DSP_RAM_SIZE_8x60 0x2000000 +static int dspcrashd_pdata_8x60 = 0xDEADDEAD; + +static struct resource resources_dspcrashd_8x60[] = { + { + .name = "msm_dspcrashd", + .start = DSP_RAM_BASE_8x60, + .end = DSP_RAM_BASE_8x60 + DSP_RAM_SIZE_8x60, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_dspcrashd_8x60 = { + .name = "msm_dspcrashd", + .num_resources = ARRAY_SIZE(resources_dspcrashd_8x60), + .resource = resources_dspcrashd_8x60, + .dev = { .platform_data = &dspcrashd_pdata_8x60 }, +}; + +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; + + struct pm8xxx_mpp_config_data class_d0_mpp = { + .type = PM8XXX_MPP_TYPE_D_OUTPUT, + .level = PM8901_MPP_DIG_LEVEL_MSMIO, + }; + + if (enable) { + class_d0_mpp.control = PM8XXX_MPP_DOUT_CTRL_HIGH; + rc = pm8xxx_mpp_config(PM8901_MPP_PM_TO_SYS(PM8901_MPP_3), + &class_d0_mpp); + 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__); + class_d0_mpp.control = PM8XXX_MPP_DOUT_CTRL_LOW; + pm8xxx_mpp_config(PM8901_MPP_PM_TO_SYS(PM8901_MPP_3), + &class_d0_mpp); + return rc; + } + + gpio_direction_output(SNDDEV_GPIO_CLASS_D0_EN, 1); + gpio_set_value_cansleep(SNDDEV_GPIO_CLASS_D0_EN, 1); + + } else { + class_d0_mpp.control = PM8XXX_MPP_DOUT_CTRL_LOW; + pm8xxx_mpp_config(PM8901_MPP_PM_TO_SYS(PM8901_MPP_3), + &class_d0_mpp); + gpio_set_value_cansleep(SNDDEV_GPIO_CLASS_D0_EN, 0); + gpio_free(SNDDEV_GPIO_CLASS_D0_EN); + } + return 0; +} + +static int config_class_d1_gpio(int enable) +{ + int rc; + + if (enable) { + rc = gpio_request(SNDDEV_GPIO_CLASS_D1_EN, "CLASSD1_EN"); + + if (rc) { + pr_err("%s: Right Channel spkr gpio request" + " failed\n", __func__); + return rc; + } + + gpio_direction_output(SNDDEV_GPIO_CLASS_D1_EN, 1); + gpio_set_value_cansleep(SNDDEV_GPIO_CLASS_D1_EN, 1); + + } else { + gpio_set_value_cansleep(SNDDEV_GPIO_CLASS_D1_EN, 0); + gpio_free(SNDDEV_GPIO_CLASS_D1_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; + } + rc = config_class_d1_gpio(1); + if (rc) { + pr_err("%s: d1 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); + config_class_d1_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_amic_sec_power(void) +{ +#ifdef CONFIG_PMIC8058_OTHC + int ret; + + if (machine_is_msm8x60_fluid()) { + + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, + OTHC_SIGNAL_ALWAYS_ON); + if (ret) + pr_err("%s: Enabling amic2 power failed\n", __func__); + + ret = gpio_request(SNDDEV_GPIO_HS_MIC4_SEL, + "HS_MIC4_SEL"); + if (ret) { + pr_err("%s: spkr pamp gpio %d request failed\n", + __func__, SNDDEV_GPIO_HS_MIC4_SEL); + return ret; + } + gpio_direction_output(SNDDEV_GPIO_HS_MIC4_SEL, 1); + } +#endif + + msm_snddev_enable_amic_power(); + return 0; +} + +static void msm_snddev_disable_amic_sec_power(void) +{ +#ifdef CONFIG_PMIC8058_OTHC + int ret; + if (machine_is_msm8x60_fluid()) { + + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, + OTHC_SIGNAL_OFF); + if (ret) + pr_err("%s: Disabling amic2 power failed\n", __func__); + + gpio_free(SNDDEV_GPIO_HS_MIC4_SEL); + } +#endif + + msm_snddev_disable_amic_power(); +} + +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 adie_codec_action_unit + fluid_dual_mic_endfire_8KHz_osr256_actions[] = + FLUID_AMIC_DUAL_8000_OSR_256; + +static struct adie_codec_hwsetting_entry fluid_dual_mic_endfire_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = fluid_dual_mic_endfire_8KHz_osr256_actions, + .action_sz = + ARRAY_SIZE(fluid_dual_mic_endfire_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile fluid_dual_mic_endfire_profile = { + .path_type = ADIE_CODEC_TX, + .settings = fluid_dual_mic_endfire_settings, + .setting_sz = ARRAY_SIZE(fluid_dual_mic_endfire_settings), +}; + +static struct snddev_icodec_data snddev_fluid_dual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &fluid_dual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_sec_power, + .pamp_off = msm_snddev_disable_amic_sec_power, +}; + +static struct platform_device msm_fluid_hs_dual_mic_endfire_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_fluid_dual_mic_endfire_data }, +}; + +static struct snddev_icodec_data snddev_fluid_dual_mic_spkr_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_endfire_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &fluid_dual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_sec_power, + .pamp_off = msm_snddev_disable_amic_sec_power, +}; + +static struct platform_device msm_fluid_spkr_dual_mic_endfire_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_fluid_dual_mic_spkr_endfire_data }, +}; + +static struct adie_codec_action_unit + fluid_dual_mic_broadside_8KHz_osr256_actions[] = + FLUID_AMIC_DUAL_BROADSIDE_8000_OSR_256; + +static struct adie_codec_hwsetting_entry fluid_dual_mic_broadside_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = fluid_dual_mic_broadside_8KHz_osr256_actions, + .action_sz = + ARRAY_SIZE(fluid_dual_mic_broadside_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile fluid_dual_mic_broadside_profile = { + .path_type = ADIE_CODEC_TX, + .settings = fluid_dual_mic_broadside_settings, + .setting_sz = ARRAY_SIZE(fluid_dual_mic_broadside_settings), +}; + +static struct snddev_icodec_data snddev_fluid_dual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_broadside_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &fluid_dual_mic_broadside_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_power, + .pamp_off = msm_snddev_disable_amic_power, +}; + +static struct platform_device msm_fluid_hs_dual_mic_broadside_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_fluid_dual_mic_broadside_data }, +}; + +static struct snddev_icodec_data snddev_fluid_dual_mic_spkr_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_broadside_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &fluid_dual_mic_broadside_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_power, + .pamp_off = msm_snddev_disable_amic_power, +}; + +static struct platform_device msm_fluid_spkr_dual_mic_broadside_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_fluid_dual_mic_spkr_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[] = + SPKR_R_RX; + +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_fluid_hs_dual_mic_endfire_device, + &msm_fluid_spkr_dual_mic_endfire_device, + &msm_fluid_hs_dual_mic_broadside_device, + &msm_fluid_spkr_dual_mic_broadside_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, + &msm_device_dspcrashd_8x60, +}; + +#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)); + } + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() + || machine_is_msm8x60_fusion() + || machine_is_msm8x60_fusn_ffa()) { + 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 0000000000000000000000000000000000000000..3635fbd41e9ef382670b034aa1f74af6ef8be0ad --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/dsp_debug.c @@ -0,0 +1,259 @@ +/* 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 + +#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); +} + +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; + void *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 = ioremap(DSP_NMI_ADDR, 0x16); + 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; + if (!ptr) { + pr_err("Unable to map DSP NMI\n"); + return -EFAULT; + } + writel(0x1, (void *)ptr); + iounmap(mem_buffer); + } 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; +} + +static unsigned copy_ok_count; +static uint32_t dsp_ram_size; +static uint32_t dsp_ram_base; + +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; + void *mem_buffer; + + if ((dsp_ram_base == 0) || (dsp_ram_size == 0)) { + pr_err("[%s:%s] Memory Invalid or not initialized, Base = 0x%x," + " size = 0x%x\n", __MM_FILE__, + __func__, dsp_ram_base, dsp_ram_size); + return -EINVAL; + } + + 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 = ioremap(addr, mapsize); + 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; + 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(mem_buffer); + pr_err("[%s:%s] copy error @ %p\n", __MM_FILE__, + __func__, buf); + return -EFAULT; + } + copy_ok_count += PAGE_SIZE; + iounmap(mem_buffer); + 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 int dspcrashd_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + int *pdata; + + pdata = pdev->dev.platform_data; + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "msm_dspcrashd"); + if (!res) { + pr_err("%s: failed to get resources for dspcrashd\n", __func__); + return -ENODEV; + } + + dsp_ram_base = res->start; + dsp_ram_size = res->end - res->start; + pr_info("%s: Platform driver values: Base = 0x%x, Size = 0x%x," + "pdata = 0x%x\n", __func__, + dsp_ram_base, dsp_ram_size, *pdata); + return rc; +} + +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 struct platform_driver dspcrashd_driver = { + .probe = dspcrashd_probe, + .driver = { .name = "msm_dspcrashd"} +}; + +static int __init dsp_init(void) +{ + int rc = 0; + init_waitqueue_head(&dsp_wait); + rc = platform_driver_register(&dspcrashd_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for dspcrashd failed\n", + __func__); + } + return misc_register(&dsp_misc); +} + +static int __exit dsp_exit(void) +{ + platform_driver_unregister(&dspcrashd_driver); + return 0; +} + +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 0000000000000000000000000000000000000000..b95d6599b075d3732111f64fd5d594eeaaabdf39 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/evrc_in.c @@ -0,0 +1,292 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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)) + +/* ------------------- 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: Could not allocate memory for evrc" + "driver\n", __func__); + 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_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s: Could not allocate memory for audio" + "client\n", __func__); + 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 0000000000000000000000000000000000000000..9cf2723ccb7c3903983b7f0f9eaec53505c42846 --- /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 0000000000000000000000000000000000000000..c19fd85db3fbd25a11a754d649187711ec8913fb --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/lpa_if_hdmi.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. + */ + +#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 DMA_ALLOC_BUF_SZ (SZ_4K * 16) + +#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[6]; + 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; + u32 dma_period_sz; + unsigned int num_periods; +}; + +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))); + + 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++; + lpa_if->dma_buf = lpa_if->dma_buf % lpa_if->cfg.buffer_count; + + if (lpa_if->dma_buf == lpa_if->cpu_buf) + pr_err("Err:both dma_buf and cpu_buf are on same index\n"); + 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); + + hdmi_audio_enable(1, HDMI_AUDIO_FIFO_WATER_MARK); + + hdmi_audio_packet_enable(1); + 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 = lpa_if->dma_period_sz * lpa_if->num_periods; + dma_params.period_size = lpa_if->dma_period_sz; + dma_params.channels = 2; + + lpa_if->dma_ch = 4; + 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; + unsigned int i; + 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; + case AUDIO_SET_CONFIG: { + /* Setting default rate as 48khz */ + unsigned int cur_sample_rate = + HDMI_SAMPLE_RATE_48KHZ; + struct msm_audio_config config; + + pr_debug("AUDIO_SET_CONFIG\n"); + if (copy_from_user(&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + lpa_if->dma_period_sz = config.buffer_size; + if ((lpa_if->dma_period_sz * lpa_if->num_periods) > + DMA_ALLOC_BUF_SZ) { + pr_err("Dma buffer size greater than allocated size\n"); + return -EINVAL; + } + pr_debug("Dma_period_sz %d\n", lpa_if->dma_period_sz); + if (lpa_if->dma_period_sz < (2 * SZ_4K)) + lpa_if->num_periods = 6; + pr_debug("No. of Periods %d\n", lpa_if->num_periods); + + lpa_if->cfg.buffer_count = lpa_if->num_periods; + lpa_if->cfg.buffer_size = lpa_if->dma_period_sz * + lpa_if->num_periods; + + for (i = 0; i < lpa_if->cfg.buffer_count; i++) { + lpa_if->audio_buf[i].phys = + lpa_if->buffer_phys + i * lpa_if->dma_period_sz; + lpa_if->audio_buf[i].data = + lpa_if->buffer + i * lpa_if->dma_period_sz; + lpa_if->audio_buf[i].size = lpa_if->dma_period_sz; + lpa_if->audio_buf[i].used = 0; + } + + pr_debug("Sample rate %d\n", config.sample_rate); + switch (config.sample_rate) { + case 48000: + cur_sample_rate = HDMI_SAMPLE_RATE_48KHZ; + break; + case 44100: + cur_sample_rate = HDMI_SAMPLE_RATE_44_1KHZ; + break; + case 32000: + cur_sample_rate = HDMI_SAMPLE_RATE_32KHZ; + break; + case 88200: + cur_sample_rate = HDMI_SAMPLE_RATE_88_2KHZ; + break; + case 96000: + cur_sample_rate = HDMI_SAMPLE_RATE_96KHZ; + break; + case 176400: + cur_sample_rate = HDMI_SAMPLE_RATE_176_4KHZ; + break; + case 192000: + cur_sample_rate = HDMI_SAMPLE_RATE_192KHZ; + break; + default: + cur_sample_rate = HDMI_SAMPLE_RATE_48KHZ; + } + if (cur_sample_rate != hdmi_msm_audio_get_sample_rate()) + hdmi_msm_audio_sample_rate_reset(cur_sample_rate); + else + pr_debug("Previous sample rate and current" + "sample rate are same\n"); + 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) +{ + pr_debug("\n"); + + file->private_data = lpa_if_ptr; + dma_buf_index = 0; + lpa_if_ptr->cpu_buf = 2; + lpa_if_ptr->dma_buf = 0; + lpa_if_ptr->num_periods = 4; + + core_req_bus_bandwith(AUDIO_IF_BUS_ID, 100000, 0); + mb(); + + return 0; +} + +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 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; + struct sched_param s = { .sched_priority = 1 }; + int old_prio = current->rt_priority; + int old_policy = current->policy; + int cap_nice = cap_raised(current_cap(), CAP_SYS_NICE); + + /* 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) + pr_err("sched_setscheduler failed\n"); + } + 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; + + } + mb(); + 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 > lpa_if->dma_period_sz) + xfer = lpa_if->dma_period_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++; + lpa_if->cpu_buf = lpa_if->cpu_buf % lpa_if->cfg.buffer_count; + } + rc = buf - start; +end: + mutex_unlock(&lpa_if->lock); + /* restore old scheduling policy */ + if (!rt_policy(old_policy)) { + struct sched_param v = { .sched_priority = old_prio }; + if ((sched_setscheduler(current, old_policy, &v)) < 0) + pr_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); + } + } + return rc; +} + +static int lpa_if_release(struct inode *inode, struct file *file) +{ + struct lpa_if *lpa_if = file->private_data; + + hdmi_audio_packet_enable(0); + + wait_for_dma_cnt_stop(lpa_if->dma_ch); + + hdmi_audio_enable(0, HDMI_AUDIO_FIFO_WATER_MARK); + + if (lpa_if->config) { + unregister_dma_irq_handler(lpa_if->dma_ch); + dai_stop_hdmi(lpa_if->dma_ch); + lpa_if->config = 0; + } + core_req_bus_bandwith(AUDIO_IF_BUS_ID, 0, 0); + + if (hdmi_msm_audio_get_sample_rate() != HDMI_SAMPLE_RATE_48KHZ) + hdmi_msm_audio_sample_rate_reset(HDMI_SAMPLE_RATE_48KHZ); + + 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 0000000000000000000000000000000000000000..3890816c3d862a0086750179e182d4eb5c834417 --- /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 0000000000000000000000000000000000000000..667628cae505f8776ff37c679c13333e97dc56b9 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/pcm_in.c @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010-2012, 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 + +#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; + struct wake_lock wakelock; + struct wake_lock idlelock; +}; + +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; + case RESET_EVENTS: + reset_device(); + break; + default: + break; + } + spin_unlock_irqrestore(&pcm->dsp_lock, flags); +} +static void pcm_in_prevent_sleep(struct pcm *audio) +{ + pr_debug("%s:\n", __func__); + wake_lock(&audio->wakelock); + wake_lock(&audio->idlelock); +} + +static void pcm_in_allow_sleep(struct pcm *audio) +{ + pr_debug("%s:\n", __func__); + wake_unlock(&audio->wakelock); + wake_unlock(&audio->idlelock); +} + +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; + } + pcm_in_prevent_sleep(pcm); + 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; + char name[24]; + + 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); + snprintf(name, sizeof name, "pcm_in_%x", pcm->ac->session); + wake_lock_init(&pcm->wakelock, WAKE_LOCK_SUSPEND, name); + snprintf(name, sizeof name, "pcm_in_idle_%x", pcm->ac->session); + wake_lock_init(&pcm->idlelock, WAKE_LOCK_IDLE, name); + + 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); + pcm_in_allow_sleep(pcm); + wake_lock_destroy(&pcm->wakelock); + wake_lock_destroy(&pcm->idlelock); + 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 0000000000000000000000000000000000000000..733d5e334af1766795e94e3d043c1cd1de2564d0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/pcm_out.c @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2010-2012, 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; +}; + +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; + case RESET_EVENTS: + reset_device(); + 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); +} + +static void audio_allow_sleep(struct pcm *audio) +{ + pr_debug("%s:\n", __func__); + wake_unlock(&audio->wakelock); +} + +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); + + 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)), 1 * 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); + 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/q6audio_common.h b/arch/arm/mach-msm/qdsp6v2/q6audio_common.h new file mode 100644 index 0000000000000000000000000000000000000000..e108de5ef826d403c3a56933fd3aed14a0d67ad4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6audio_common.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 Decoders */ +#ifndef __Q6_AUDIO_COMMON_H__ +#define __Q6_AUDIO_COMMON_H__ + +#include +#include + +void q6_audio_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +void audio_aio_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *audio); + + +/* For Encoders */ +void q6asm_in_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +void audio_in_get_dsp_frames(void *audio, + uint32_t token, uint32_t *payload); + +#endif /*__Q6_AUDIO_COMMON_H__*/ diff --git a/arch/arm/mach-msm/qdsp6v2/q6audio_v1.c b/arch/arm/mach-msm/qdsp6v2/q6audio_v1.c new file mode 100644 index 0000000000000000000000000000000000000000..f49d6e071ea1c80248d8c390414f508ceed690b1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6audio_v1.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "audio_utils.h" + +void q6asm_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); +} + +void audio_in_get_dsp_frames(/*struct q6audio_in *audio,*/void *aud, + uint32_t token, uint32_t *payload) +{ + struct q6audio_in *audio = (struct q6audio_in *)aud; + 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); + } +} diff --git a/arch/arm/mach-msm/qdsp6v2/q6audio_v1_aio.c b/arch/arm/mach-msm/qdsp6v2/q6audio_v1_aio.c new file mode 100644 index 0000000000000000000000000000000000000000..112de625cfa5829368cb3e91ea9c5301cb0c6d30 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6audio_v1_aio.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "audio_utils_aio.h" + +void q6_audio_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio_aio *audio = (struct q6audio_aio *)priv; + + pr_debug("%s:opcode = %x token = 0x%x\n", __func__, opcode, token); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + case ASM_DATA_EVENT_READ_DONE: + case ASM_DATA_CMDRSP_EOS: + case ASM_DATA_CMD_MEDIA_FORMAT_UPDATE: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + case ASM_DATA_EVENT_ENC_SR_CM_NOTIFY: + audio_aio_cb(opcode, token, payload, audio); + break; + default: + pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode); + break; + } +} + +void audio_aio_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + union msm_audio_event_payload e_payload; + struct q6audio_aio *audio = (struct q6audio_aio *)priv; + + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + pr_debug("%s[%p]:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n", + __func__, audio, token); + audio_aio_async_write_ack(audio, token, payload); + break; + case ASM_DATA_EVENT_READ_DONE: + pr_debug("%s[%p]:ASM_DATA_EVENT_READ_DONE token = 0x%x\n", + __func__, audio, token); + audio_aio_async_read_ack(audio, token, payload); + break; + case ASM_DATA_CMDRSP_EOS: + /* EOS Handle */ + pr_debug("%s[%p]:ASM_DATA_CMDRSP_EOS\n", __func__, audio); + 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)) { + audio_aio_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[%p]:payload0[%x] payloa1d[%x]opcode= 0x%x\n", + __func__, audio, payload[0], payload[1], opcode); + break; + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + case ASM_DATA_EVENT_ENC_SR_CM_NOTIFY: + pr_debug("%s[%p]: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + + "payload[0]-sr = %d, payload[1]-chl = %d, " + "payload[2] = %d, payload[3] = %d\n", __func__, + audio, payload[0], payload[1], payload[2], + payload[3]); + pr_debug("%s[%p]: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "sr(prev) = %d, chl(prev) = %d,", + __func__, audio, 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; + audio_aio_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload); + break; + default: + break; + } +} diff --git a/arch/arm/mach-msm/qdsp6v2/q6core.c b/arch/arm/mach-msm/qdsp6v2/q6core.c new file mode 100644 index 0000000000000000000000000000000000000000..edb1e7ddf82695db2cb054b4dd02f20aa7396fe4 --- /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 0000000000000000000000000000000000000000..cb25d6b82ec72d1df699eceffcd0335cf131a1d8 --- /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 0000000000000000000000000000000000000000..12a02c500d06dc92444a573736d56ef37a27bf72 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6voice.c @@ -0,0 +1,2948 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "q6core.h" + + +#define TIMEOUT_MS 3000 +#define SNDDEV_CAP_TTY 0x20 +#define CMD_STATUS_SUCCESS 0 +#define CMD_STATUS_FAIL 1 + +/* Voice session creates passive control sessions for MVM and CVS. */ +#define VOC_PATH_PASSIVE 0 + +/* VoIP session creates full control sessions for MVM and CVS. */ +#define VOC_PATH_FULL 1 + +#define ADSP_VERSION_CVD 0x60300000 + +#define BUFFER_PAYLOAD_SIZE 4000 + +#define VOC_REC_NONE 0xFF + +struct common_data common; + +static bool is_adsp_support_cvd(void) +{ + return (common.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(void) +{ + void *apr_mvm = NULL; + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + apr_mvm = common.apr_mvm; + else + apr_mvm = common.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(void *apr_mvm) +{ + pr_debug("%s: apr_mvm 0x%x\n", __func__, (unsigned int)apr_mvm); + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + common.apr_mvm = apr_mvm; + else + common.apr_q6_mvm = apr_mvm; +} + +static void *voice_get_apr_cvs(void) +{ + void *apr_cvs = NULL; + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + apr_cvs = common.apr_cvs; + else + apr_cvs = common.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(void *apr_cvs) +{ + pr_debug("%s: apr_cvs 0x%x\n", __func__, (unsigned int)apr_cvs); + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + common.apr_cvs = apr_cvs; + else + common.apr_q6_cvs = apr_cvs; + rtac_set_voice_handle(RTAC_CVS, apr_cvs); +} + +static void *voice_get_apr_cvp(void) +{ + void *apr_cvp = NULL; + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + apr_cvp = common.apr_cvp; + else + apr_cvp = common.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(void *apr_cvp) +{ + pr_debug("%s: apr_cvp 0x%x\n", __func__, (unsigned int)apr_cvp); + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + common.apr_cvp = apr_cvp; + else + common.apr_q6_cvp = apr_cvp; + rtac_set_voice_handle(RTAC_CVP, apr_cvp); +} + +static u16 voice_get_mvm_handle(struct voice_data *v) +{ + pr_debug("%s: mvm_handle %d\n", __func__, v->mvm_handle); + + return v->mvm_handle; +} + +static void voice_set_mvm_handle(struct voice_data *v, u16 mvm_handle) +{ + pr_debug("%s: session 0x%x, mvm_handle %d\n", + __func__, (unsigned int)v, mvm_handle); + + v->mvm_handle = mvm_handle; +} + +static u16 voice_get_cvs_handle(struct voice_data *v) +{ + pr_debug("%s: cvs_handle %d\n", __func__, v->cvs_handle); + + return v->cvs_handle; +} + +static void voice_set_cvs_handle(struct voice_data *v, u16 cvs_handle) +{ + pr_debug("%s: session 0x%x, cvs_handle %d\n", + __func__, (unsigned int)v, cvs_handle); + + v->cvs_handle = cvs_handle; +} + +static u16 voice_get_cvp_handle(struct voice_data *v) +{ + pr_debug("%s: cvp_handle %d\n", __func__, v->cvp_handle); + + return v->cvp_handle; +} + +static void voice_set_cvp_handle(struct voice_data *v, u16 cvp_handle) +{ + pr_debug("%s: session 0x%x, cvp_handle %d\n", + __func__, (unsigned int)v, cvp_handle); + + v->cvp_handle = cvp_handle; +} + +u16 voice_get_session_id(const char *name) +{ + u16 session_id = 0; + + if (name != NULL) { + if (!strncmp(name, "Voice session", 13)) + session_id = common.voice[VOC_PATH_PASSIVE].session_id; + else + session_id = common.voice[VOC_PATH_FULL].session_id; + } + + pr_debug("%s: %s has session id 0x%x\n", __func__, name, session_id); + + return session_id; +} + +static struct voice_data *voice_get_session(u16 session_id) +{ + struct voice_data *v = NULL; + + if (session_id == 0) { + mutex_lock(&common.common_lock); + + pr_debug("%s: NULL id, voc_path is %d\n", + __func__, common.voc_path); + + if (common.voc_path == VOC_PATH_PASSIVE) + v = &common.voice[VOC_PATH_PASSIVE]; + else + v = &common.voice[VOC_PATH_FULL]; + + mutex_unlock(&common.common_lock); + } else if ((session_id >= SESSION_ID_BASE) && + (session_id < SESSION_ID_BASE + MAX_VOC_SESSIONS)) { + v = &common.voice[session_id - SESSION_ID_BASE]; + } else { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + } + + pr_debug("%s: session_id 0x%x session handle 0x%x\n", + __func__, session_id, (unsigned int)v); + + return v; +} + +static bool is_voice_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_PASSIVE].session_id); +} + +static bool is_voip_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_FULL].session_id); +} + +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(void) +{ + int rc = 0; + void *apr_mvm; + void *apr_cvs; + void *apr_cvp; + + if (common.adsp_version == 0) { + common.adsp_version = core_get_adsp_version(); + pr_info("adsp_ver fetched:%x\n", common.adsp_version); + } + + mutex_lock(&common.common_lock); + + apr_mvm = voice_get_apr_mvm(); + apr_cvs = voice_get_apr_cvs(); + apr_cvp = voice_get_apr_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"); + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) { + apr_mvm = apr_register("MODEM", "MVM", + modem_mvm_callback, 0xFFFFFFFF, + &common); + } else { + apr_mvm = apr_register("ADSP", "MVM", + modem_mvm_callback, 0xFFFFFFFF, + &common); + } + + if (apr_mvm == NULL) { + pr_err("Unable to register MVM %d\n", + is_adsp_support_cvd()); + rc = -ENODEV; + goto done; + } + + voice_set_apr_mvm(apr_mvm); + } + + if (apr_cvs == NULL) { + pr_debug("start to register CVS callback\n"); + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) { + apr_cvs = apr_register("MODEM", "CVS", + modem_cvs_callback, 0xFFFFFFFF, + &common); + } else { + apr_cvs = apr_register("ADSP", "CVS", + modem_cvs_callback, 0xFFFFFFFF, + &common); + } + + if (apr_cvs == NULL) { + pr_err("Unable to register CVS %d\n", + is_adsp_support_cvd()); + rc = -ENODEV; + goto err; + } + + voice_set_apr_cvs(apr_cvs); + } + + if (apr_cvp == NULL) { + pr_debug("start to register CVP callback\n"); + + if (common.voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) { + apr_cvp = apr_register("MODEM", "CVP", + modem_cvp_callback, 0xFFFFFFFF, + &common); + } else { + apr_cvp = apr_register("ADSP", "CVP", + modem_cvp_callback, 0xFFFFFFFF, + &common); + } + + if (apr_cvp == NULL) { + pr_err("Unable to register CVP %d\n", + is_adsp_support_cvd()); + rc = -ENODEV; + goto err1; + } + + voice_set_apr_cvp(apr_cvp); + } + + mutex_unlock(&common.common_lock); + + return 0; + +err1: + apr_deregister(apr_cvs); + apr_cvs = NULL; + voice_set_apr_cvs(apr_cvs); +err: + apr_deregister(apr_mvm); + apr_mvm = NULL; + voice_set_apr_mvm(apr_mvm); + +done: + mutex_unlock(&common.common_lock); + + 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(); + void *apr_cvs = voice_get_apr_cvs(); + void *apr_cvp = voice_get_apr_cvp(); + 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 (is_voice_session(v->session_id)) { + 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 = v->session_id; + 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 = v->session_id; + 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 (is_voice_session(v->session_id)) { + 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 = v->session_id; + 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 = v->session_id; + 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 = + common.mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.dec_media_type = + common.mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.network_id = + common.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 = v->session_id; + 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(apr_mvm); + + apr_deregister(apr_cvs); + apr_cvs = NULL; + voice_set_apr_cvs(apr_cvs); + + apr_deregister(apr_cvp); + apr_cvp = NULL; + voice_set_apr_cvp(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(); + void *apr_cvs = voice_get_apr_cvs(); + 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 (is_voip_session(v->session_id)) { + 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 = v->session_id; + 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 = v->session_id; + 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 = v->session_id; + 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(); + 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 = v->session_id; + 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_atomic_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(); + 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 = v->session_id; + 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 = atomic_read(&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 *)atomic_read(&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_atomic_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(); + 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 = v->session_id; + 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 = atomic_read(&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 *)atomic_read(&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_atomic_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(); + 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 = v->session_id; + 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 = atomic_read(&cal_blk[index].cal_size); + cal_data_per_network = + (u32 *)atomic_read(&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(); + 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 = v->session_id, + .hdr.dest_port = cvs_handle, + .hdr.token = 0, + .hdr.opcode = VSS_ISTREAM_CMD_SET_ENC_DTX_MODE, + .dtx_mode.enable = common.mvs_info.dtx_mode, + }; + + pr_debug("%s: Setting DTX %d\n", __func__, common.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(); + 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 = v->session_id; + 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 = common.mvs_info.media_type; + cvs_set_media_cmd.media_type.rx_media_id = common.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 (common.mvs_info.media_type) { + case VSS_MEDIA_ID_13K_MODEM: + case VSS_MEDIA_ID_4GV_NB_MODEM: + case VSS_MEDIA_ID_4GV_WB_MODEM: + case VSS_MEDIA_ID_EVRC_MODEM: { + struct cvs_set_cdma_enc_minmax_rate_cmd cvs_set_cdma_rate; + + pr_info("%s: Setting CDMA 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 = v->session_id; + 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 = + common.mvs_info.q_min_max_rate.min_rate; + cvs_set_cdma_rate.cdma_rate.max_rate = + common.mvs_info.q_min_max_rate.max_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 CDMA_SET_ENC_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; + } + + if ((common.mvs_info.media_type == VSS_MEDIA_ID_4GV_NB_MODEM) || + (common.mvs_info.media_type == VSS_MEDIA_ID_4GV_WB_MODEM)) + ret = voice_set_dtx(v); + + 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 = v->session_id; + 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 = common.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 = v->session_id; + 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 = common.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_EFR_MODEM: + case VSS_MEDIA_ID_FR_MODEM: + case VSS_MEDIA_ID_HR_MODEM: + 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(); + 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 = v->session_id; + 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(); + 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 = v->session_id; + 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; + } + rtac_remove_voice(v->cvs_handle); + + 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(); + 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 = v->session_id; + 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 (is_voip_session(v->session_id)) + voice_send_netid_timing_cmd(v); + + rtac_add_voice(v->cvs_handle, v->cvp_handle, + v->dev_rx.dev_port_id, v->dev_tx.dev_port_id, + v->session_id); + + 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(); + 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 = v->session_id; + 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(); + + /* 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 = v->session_id; + 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(); + + /* 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 = v->session_id; + 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(); + 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 = v->session_id; + 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 = common.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 = v->session_id; + 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(); + 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 = v->session_id; + 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; + } + + /* send tty mode if tty device is used */ + voice_send_tty_mode_to_modem(v); + + if (is_voip_session(v->session_id)) + voice_send_netid_timing_cmd(v); + + rtac_add_voice(v->cvs_handle, v->cvp_handle, + v->dev_rx.dev_port_id, v->dev_tx.dev_port_id, + v->session_id); + + 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(); + void *apr_cvp = voice_get_apr_cvp(); + 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 = v->session_id; + 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_info("cvp_destroy_session_cmd pkt size = %d\n", + cvp_destroy_session_cmd.pkt_size); + cvp_destroy_session_cmd.src_port = v->session_id; + 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; + } + rtac_remove_voice(v->cvs_handle); + 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(); + 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 = v->session_id; + 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(); + 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 = v->session_id; + 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(); + 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 = v->session_id; + 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(); + 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 = v->session_id; + 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, i; + u16 cvs_handle; + + pr_debug("%s: rec_mode %d, set %d\n", __func__, rec_mode, set); + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + struct voice_data *v = &common.voice[i]; + + mutex_lock(&v->lock); + + cvs_handle = voice_get_cvs_handle(v); + + if (cvs_handle != 0) { + if (set) + ret = voice_cvs_start_record(v, rec_mode); + else + ret = voice_cvs_stop_record(v); + } else { + /* Cache the value for later. */ + v->rec_info.pending = set; + v->rec_info.rec_mode = rec_mode; + } + + mutex_unlock(&v->lock); + } + + return ret; +} + +static int voice_cvs_start_playback(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(); + 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 = v->session_id; + 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(); + 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 = v->session_id; + 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, i; + u16 cvs_handle; + + pr_debug("%s: Start playback %d\n", __func__, set); + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + struct voice_data *v = &common.voice[i]; + + mutex_lock(&v->lock); + + cvs_handle = voice_get_cvs_handle(v); + + if (cvs_handle != 0) { + if (set) + ret = voice_cvs_start_playback(v); + else + ret = voice_cvs_stop_playback(v); + } else { + /* Cache the value for later. */ + pr_debug("%s: Caching ICP value", __func__); + + v->music_info.pending = set; + } + + mutex_unlock(&v->lock); + } + + return ret; +} + +static void voice_auddev_cb_function(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data) +{ + struct common_data *c = private_data; + struct voice_data *v = NULL; + + struct sidetone_cal sidetone_cal_data; + int rc = 0, i = 0; + int rc1 = 0; + + pr_info("auddev_cb_function, evt_id=%d,\n", evt_id); + + if (evt_payload == NULL) { + pr_err("%s: evt_payload is NULL pointer\n", __func__); + return; + } + + switch (evt_id) { + case AUDDEV_EVT_START_VOICE: + v = voice_get_session(evt_payload->voice_session_id); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + 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(); + if (rc < 0) { + pr_err("%s: voice apr registration" + "failed\n", __func__); + mutex_unlock(&v->lock); + return; + } + rc1 = voice_create_mvm_cvs_session(v); + if (rc1 < 0) { + pr_err("%s: create mvm-cvs failed\n", + __func__); + msleep(100); + rc = voice_apr_register(); + if (rc < 0) { + mutex_unlock(&v->lock); + pr_err("%s: voice apr regn" + "failed\n", __func__); + return; + } + rc1 = voice_create_mvm_cvs_session(v); + if (rc1 < 0) { + mutex_unlock(&v->lock); + pr_err("%s:Retry mvmcvs " + "failed\n", + __func__); + return; + } + } + 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: + /* Device change is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + v = &c->voice[i]; + + 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: + /* Device change is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + v = &c->voice[i]; + + 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(); + 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: + v = voice_get_session( + evt_payload->voc_vm_info.voice_session_id); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + /* cache the mute and volume index value */ + if (evt_payload->voc_vm_info.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: + /* Device change is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + v = &c->voice[i]; + + 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: + v = voice_get_session(evt_payload->voice_session_id); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + /* recover the tx mute and rx volume to the default values */ + v->dev_tx.mute = c->default_mute_val; + v->dev_rx.volume = c->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) +{ + pr_info("%s: %d\n", __func__, set); + + mutex_lock(&common.common_lock); + + if (set) + common.voc_path = VOC_PATH_FULL; + else + common.voc_path = VOC_PATH_PASSIVE; + + mutex_unlock(&common.common_lock); + + return 0; +} +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) +{ + common.mvs_info.ul_cb = ul_cb; + common.mvs_info.dl_cb = dl_cb; + common.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, + struct q_min_max_rate q_min_max_rate) +{ + common.mvs_info.media_type = media_type; + common.mvs_info.rate = rate; + common.mvs_info.network_type = network_type; + common.mvs_info.dtx_mode = dtx_mode; + common.mvs_info.q_min_max_rate = q_min_max_rate; +} + +static int32_t modem_mvm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct common_data *c = priv; + struct voice_data *v = NULL; + int i = 0; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s:Reset event received in Voice service\n", + __func__); + apr_reset(c->apr_mvm); + c->apr_mvm = NULL; + apr_reset(c->apr_q6_mvm); + c->apr_q6_mvm = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].mvm_handle = 0; + + return 0; + } + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + + pr_debug("%s: common data 0x%x, session 0x%x\n", + __func__, (unsigned int)c, (unsigned int)v); + 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 */ + + 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_ISTREAM_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_ISTREAM_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 common_data *c = priv; + struct voice_data *v = NULL; + int i = 0; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s:Reset event received in Voice service\n", + __func__); + apr_reset(c->apr_cvs); + c->apr_cvs = NULL; + apr_reset(c->apr_q6_cvs); + c->apr_q6_cvs = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].cvs_handle = 0; + + return 0; + } + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + + pr_debug("%s: common data 0x%x, session 0x%x\n", + __func__, (unsigned int)c, (unsigned int)v); + 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 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); + } else if (ptr[0] == VOICE_CMD_SET_PARAM) { + rtac_make_voice_callback(RTAC_CVS, ptr, + data->payload_size); + } 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 && c->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; + + c->mvs_info.ul_cb((uint8_t *)voc_pkt, + pkt_len, + c->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)c->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 (c->mvs_info.dl_cb != NULL) { + send_dec_buf.dec_buf.media_id = c->mvs_info.media_type; + + c->mvs_info.dl_cb( + (uint8_t *)&send_dec_buf.dec_buf.packet_data, + &pkt_len, + c->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 = v->session_id; + 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(), + (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__); + } + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVS, data->payload, + data->payload_size); + } 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 common_data *c = priv; + struct voice_data *v = NULL; + int i = 0; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s:Reset event received in Voice service\n", + __func__); + apr_reset(c->apr_cvp); + c->apr_cvp = NULL; + apr_reset(c->apr_q6_cvp); + c->apr_q6_cvp = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].cvp_handle = 0; + + return 0; + } + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + + pr_debug("%s: common data 0x%x, session 0x%x\n", + __func__, (unsigned int)c, (unsigned int)v); + 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 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); + } else if (ptr[0] == VOICE_CMD_SET_PARAM) { + rtac_make_voice_callback(RTAC_CVP, ptr, + data->payload_size); + } else + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + } + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVP, data->payload, + data->payload_size); + } + return 0; +} + + +static int __init voice_init(void) +{ + int rc = 0, i = 0; + + memset(&common, 0, sizeof(struct common_data)); + + /* set default value */ + common.default_mute_val = 1; /* default is mute */ + common.default_vol_val = 0; + common.default_sample_val = 8000; + + common.voc_path = VOC_PATH_PASSIVE; + + /* Initialize MVS info. */ + common.mvs_info.network_type = VSS_NETWORK_ID_DEFAULT; + + mutex_init(&common.common_lock); + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + common.voice[i].session_id = SESSION_ID_BASE + i; + + common.voice[i].dev_rx.volume = common.default_vol_val; + common.voice[i].dev_tx.mute = common.default_mute_val; + + common.voice[i].voc_state = VOC_INIT; + + common.voice[i].rec_info.rec_mode = VOC_REC_NONE; + + init_waitqueue_head(&common.voice[i].mvm_wait); + init_waitqueue_head(&common.voice[i].cvs_wait); + init_waitqueue_head(&common.voice[i].cvp_wait); + + mutex_init(&common.voice[i].lock); + + } + + common.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(common.device_events, AUDDEV_CLNT_VOC, + 0, voice_auddev_cb_function, &common); + + 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 0000000000000000000000000000000000000000..a48df3958ec7a9b3af0e02f1f28c4f5a47143746 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/qcelp_in.c @@ -0,0 +1,290 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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)) + +/* ------------------- 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: Could not allocate memory for qcelp" + "driver\n", __func__); + 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_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s: Could not allocate memory for audio" + "client\n", __func__); + 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 0000000000000000000000000000000000000000..4ce9b03020c2f49fe55a6f0f0ed0e76a0320f1f9 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/rtac.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CONFIG_RTAC + +void rtac_add_adm_device(u32 port_id, u32 copp_id, u32 path_id, u32 popp_id) {} +void rtac_remove_adm_device(u32 port_id) {} +void rtac_remove_popp_from_adm_devices(u32 popp_id) {} +void rtac_set_adm_handle(void *handle) {} +bool rtac_make_adm_callback(uint32_t *payload, u32 payload_size) + {return false; } +void rtac_set_asm_handle(u32 session_id, void *handle) {} +bool rtac_make_asm_callback(u32 session_id, uint32_t *payload, + u32 payload_size) {return false; } +void rtac_add_voice(u32 cvs_handle, u32 cvp_handle, u32 rx_afe_port, + u32 tx_afe_port, u32 session_id) {} +void rtac_remove_voice(u32 cvs_handle) {} +void rtac_set_voice_handle(u32 mode, void *handle) {} +bool rtac_make_voice_callback(u32 mode, uint32_t *payload, + u32 payload_size) {return false; } + +#else + +#define VOICE_CMD_SET_PARAM 0x00011006 +#define VOICE_CMD_GET_PARAM 0x00011007 +#define VOICE_EVT_GET_PARAM_ACK 0x00011008 + +/* 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]; + + +/* ADM info & APR */ +struct rtac_adm_data { + uint32_t topology_id; + 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_topology_id; + uint32_t rx_topology_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; +static u32 voice_session_id[RTAC_MAX_ACTIVE_VOICE_COMBOS]; + + +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; +} + +/* 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 copp_id, u32 path_id, u32 popp_id) +{ + u32 i = 0; + pr_debug("%s: port_id = %d, popp_id = %d\n", __func__, port_id, + popp_id); + + 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++; + + if (path_id == ADM_PATH_PLAYBACK) + rtac_adm_data.device[i].topology_id = + get_adm_rx_topology(); + else + rtac_adm_data.device[i].topology_id = + get_adm_tx_topology(); + rtac_adm_data.device[i].afe_port = port_id; + rtac_adm_data.device[i].copp = copp_id; + rtac_adm_data.device[i].popp[ + rtac_adm_data.device[i].num_of_popp++] = popp_id; +done: + mutex_unlock(&rtac_adm_mutex); + return; +} + +static void shift_adm_devices(u32 dev_idx) +{ + for (; dev_idx < rtac_adm_data.num_of_dev; dev_idx++) { + memcpy(&rtac_adm_data.device[dev_idx], + &rtac_adm_data.device[dev_idx + 1], + sizeof(rtac_adm_data.device[dev_idx])); + memset(&rtac_adm_data.device[dev_idx + 1], 0, + sizeof(rtac_adm_data.device[dev_idx])); + } +} + +static void shift_popp(u32 copp_idx, u32 popp_idx) +{ + for (; popp_idx < rtac_adm_data.device[copp_idx].num_of_popp; + popp_idx++) { + memcpy(&rtac_adm_data.device[copp_idx].popp[popp_idx], + &rtac_adm_data.device[copp_idx].popp[popp_idx + 1], + sizeof(uint32_t)); + memset(&rtac_adm_data.device[copp_idx].popp[popp_idx + 1], 0, + sizeof(uint32_t)); + } +} + +void rtac_remove_adm_device(u32 port_id) +{ + s32 i; + pr_debug("%s: port_id = %d\n", __func__, port_id); + + 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) { + memset(&rtac_adm_data.device[i], 0, + sizeof(rtac_adm_data.device[i])); + rtac_adm_data.num_of_dev--; + + if (rtac_adm_data.num_of_dev >= 1) { + shift_adm_devices(i); + break; + } + } + } + + mutex_unlock(&rtac_adm_mutex); + return; +} + +void rtac_remove_popp_from_adm_devices(u32 popp_id) +{ + s32 i, j; + pr_debug("%s: popp_id = %d\n", __func__, popp_id); + + mutex_lock(&rtac_adm_mutex); + + for (i = 0; i < rtac_adm_data.num_of_dev; i++) { + for (j = 0; j < rtac_adm_data.device[i].num_of_popp; j++) { + if (rtac_adm_data.device[i].popp[j] == popp_id) { + rtac_adm_data.device[i].popp[j] = 0; + rtac_adm_data.device[i].num_of_popp--; + shift_popp(i, j); + } + } + } + + mutex_unlock(&rtac_adm_mutex); +} + +/* Voice Info */ +static void set_rtac_voice_data(int idx, u32 cvs_handle, u32 cvp_handle, + u32 rx_afe_port, u32 tx_afe_port, + u32 session_id) +{ + rtac_voice_data.voice[idx].tx_topology_id = get_voice_tx_topology(); + rtac_voice_data.voice[idx].rx_topology_id = get_voice_rx_topology(); + rtac_voice_data.voice[idx].tx_afe_port = tx_afe_port; + rtac_voice_data.voice[idx].rx_afe_port = rx_afe_port; + rtac_voice_data.voice[idx].cvs_handle = cvs_handle; + rtac_voice_data.voice[idx].cvp_handle = cvp_handle; + + /* Store session ID for voice RTAC */ + voice_session_id[idx] = session_id; +} + +void rtac_add_voice(u32 cvs_handle, u32 cvp_handle, u32 rx_afe_port, + u32 tx_afe_port, u32 session_id) +{ + 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].cvs_handle == + cvs_handle) { + set_rtac_voice_data(i, cvs_handle, cvp_handle, + rx_afe_port, tx_afe_port, + session_id); + goto done; + } + } + } + + /* Add device */ + rtac_voice_data.num_of_voice_combos++; + set_rtac_voice_data(i, cvs_handle, cvp_handle, + rx_afe_port, tx_afe_port, + session_id); +done: + mutex_unlock(&rtac_voice_mutex); + return; +} + +static 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])); + voice_session_id[idx] = voice_session_id[idx + 1]; + } +} + +void rtac_remove_voice(u32 cvs_handle) +{ + 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].cvs_handle == cvs_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])); + voice_session_id[rtac_voice_data.num_of_voice_combos] + = 0; + break; + } + } + mutex_unlock(&rtac_voice_mutex); + return; +} + +static int get_voice_index(u32 cvs_handle) +{ + u32 i; + + for (i = 0; i < rtac_voice_data.num_of_voice_combos; i++) { + if (rtac_voice_data.voice[i].cvs_handle == cvs_handle) + return i; + } + + pr_err("%s: No voice index for CVS handle %d found returning 0\n", + __func__, cvs_handle); + return 0; +} + + +/* ADM APR */ +void rtac_set_adm_handle(void *handle) +{ + pr_debug("%s: handle = %d\n", __func__, (unsigned int)handle); + + 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) +{ + pr_debug("%s:cmd_state = %d\n", __func__, + atomic_read(&rtac_adm_apr_data.cmd_state)); + if (atomic_read(&rtac_adm_apr_data.cmd_state) != 1) + return false; + + /* 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_index = 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 > 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_index = 0; port_index < AFE_MAX_PORTS; port_index++) { + if (adm_get_copp_id(port_index) == copp_id) + break; + } + if (port_index >= AFE_MAX_PORTS) { + pr_err("%s: Could not find port index for copp = %d\n", + __func__, copp_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 = copp_id; + adm_params.dest_svc = APR_SVC_ADM; + adm_params.dest_domain = APR_DOMAIN_ADSP; + adm_params.dest_port = copp_id; + adm_params.token = copp_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, copp = %d\n", + __func__, port_index, copp_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, copp = %d\n", + __func__, port_index, copp_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 > 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 > (SESSION_MAX + 1)) { + pr_err("%s: Invalid Session = %d\n", __func__, session_id); + goto done; + } + + mutex_lock(&rtac_asm_apr_mutex); + if (session_id < SESSION_MAX+1) { + 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)); + if (session_id < SESSION_MAX+1) + 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 >= 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 > 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 = voice_session_id[ + get_voice_index(dest_port)]; + 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 long rtac_ioctl(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_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, + .unlocked_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__); + + /* 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"); + +#endif 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 0000000000000000000000000000000000000000..f75af162dacb01527ddaa4cc4440719f5649a947 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.c @@ -0,0 +1,382 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_sys(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 0000000000000000000000000000000000000000..b102de0ba5f6dbe90dc36dd05a27375cd112ecc4 --- /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 0000000000000000000000000000000000000000..9b8346d3508a6a45a52a2b963f4e1521d1036a6f --- /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 Closed\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 0000000000000000000000000000000000000000..cc69033f08b7301eba44fc1b9a8a7b120f915af9 --- /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 0000000000000000000000000000000000000000..5d10779ca24fa3070c8ce4941c45120665833aab --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_icodec.c @@ -0,0 +1,1113 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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) + +bool 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_sys(NULL, "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_sys(NULL, "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; + afe_config.mi2s.format = MSM_AFE_I2S_FORMAT_LPCM; + 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 (trc < 0) + pr_err("%s: afe open failed, trc = %d\n", __func__, trc); + + /* 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_sys(NULL, "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_sys(NULL, "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; + afe_config.mi2s.format = MSM_AFE_I2S_FORMAT_LPCM; + 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); + pr_err("%s: rx_active is set, return EBUSY\n", + __func__); + rc = -EBUSY; + goto error; + } + rc = snddev_icodec_open_rx(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 (!IS_ERR_VALUE(rc)) + drv->rx_active = 1; + else + pr_err("%s: set_device_volume_impl" + " error(rx) = %d\n", __func__, rc); + } + 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); + pr_err("%s: tx_active is set, return EBUSY\n", + __func__); + rc = -EBUSY; + goto error; + } + rc = snddev_icodec_open_tx(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 (!IS_ERR_VALUE(rc)) + drv->tx_active = 1; + else + pr_err("%s: set_device_volume_impl" + " error(tx) = %d\n", __func__, rc); + } + 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); + pr_err("%s: rx_active not set, return\n", __func__); + rc = -EPERM; + goto error; + } + rc = snddev_icodec_close_rx(icodec); + if (!IS_ERR_VALUE(rc)) + drv->rx_active = 0; + else + pr_err("%s: close rx failed, rc = %d\n", __func__, rc); + 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); + pr_err("%s: tx_active not set, return\n", __func__); + rc = -EPERM; + goto error; + } + rc = snddev_icodec_close_tx(icodec); + if (!IS_ERR_VALUE(rc)) + drv->tx_active = 0; + else + pr_err("%s: close tx failed, rc = %d\n", __func__, rc); + 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) { + pr_err("%s: adie_codec_freq_supported() failed\n", __func__); + rc = -EINVAL; + goto error; + } else { + if (snddev_icodec_check_freq(rate) != 0) { + pr_err("%s: check_freq failed\n", __func__); + 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 0000000000000000000000000000000000000000..8d5613f1c164fd632d1d66b7275ff2dbca524307 --- /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 0000000000000000000000000000000000000000..75a7411f8562e2b65d9723ee82eb39e4571b24c9 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_sys(NULL, "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_sys(NULL, "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; + afe_config.mi2s.format = MSM_AFE_I2S_FORMAT_LPCM; + + 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 0000000000000000000000000000000000000000..d369c969ac1afa81235b9cc4d6f1970cc1163cbd --- /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 0000000000000000000000000000000000000000..f48aa0ec7137bce658a03e3827cf9466b183ef4b --- /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 0000000000000000000000000000000000000000..dec4d0739de25c2b50d74e1bf6d90de59dced47a --- /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 0000000000000000000000000000000000000000..f02e0a0c1ae8ba9dda7dbb1852e1619a48a7ea5e --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/timpani_profile_8x60.h @@ -0,0 +1,3225 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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} } + +/* Fluid AMIC dual */ +#define FLUID_AMIC_DUAL_8000_OSR_256 \ + {{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, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 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(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} } + +/* Fluid AMIC dual broadside */ +#define FLUID_AMIC_DUAL_BROADSIDE_8000_OSR_256 \ + {{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}, \ + /* AUX-IN to TxFE LEFT */ \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xC1)}, \ + /* MIC1 to TxFE RIGHT */ \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 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(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} } + +/* + * 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(0x9A, 0xFF, 0x8C)}, \ + {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} } + +#define FLUID_SPEAKER_PRI_STEREO_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_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_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 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(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(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, 0xC5)}, \ + {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_DELAY_WAIT, 0x4E1F}, \ + {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/qdsp6v2/ultrasound/Makefile b/arch/arm/mach-msm/qdsp6v2/ultrasound/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0be1303fc6030eccb38e479815d4c7f7279b7239 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/ultrasound/Makefile @@ -0,0 +1,2 @@ +obj-y += q6usm.o usf.o usfcdev.o +EXTRA_CFLAGS += -I$(src)/.. diff --git a/arch/arm/mach-msm/qdsp6v2/ultrasound/q6usm.c b/arch/arm/mach-msm/qdsp6v2/ultrasound/q6usm.c new file mode 100644 index 0000000000000000000000000000000000000000..287470007bce10b835fa2ea675063c5bf02df2e5 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/ultrasound/q6usm.c @@ -0,0 +1,1209 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "q6usm.h" + +/* The driver version*/ +#define DRV_VERSION "1.2" + +#define SESSION_MAX 0x02 /* aDSP:USM limit */ + +#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 + +#define WRITEDONE_IDX_STATUS 0 + +/* Standard timeout in the asynchronous ops */ +#define Q6USM_TIMEOUT_JIFFIES (1*HZ) /* 1 sec */ + +static DEFINE_MUTEX(session_lock); + +static struct us_client *session[SESSION_MAX]; +static int32_t q6usm_mmapcallback(struct apr_client_data *data, void *priv); +static int32_t q6usm_callback(struct apr_client_data *data, void *priv); +static void q6usm_add_hdr(struct us_client *usc, struct apr_hdr *hdr, + uint32_t pkt_size, bool cmd_flg); + +struct usm_mmap { + atomic_t ref_cnt; + atomic_t cmd_state; + wait_queue_head_t cmd_wait; + void *apr; +}; + +static struct usm_mmap this_mmap; + +static int q6usm_session_alloc(struct us_client *usc) +{ + int ind = 0; + + mutex_lock(&session_lock); + for (ind = 0; ind < SESSION_MAX; ++ind) { + if (!session[ind]) { + session[ind] = usc; + mutex_unlock(&session_lock); + ++ind; /* session id: 0 reserved */ + pr_debug("%s: session[%d] was allocated\n", + __func__, ind); + return ind; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void q6usm_session_free(struct us_client *usc) +{ + /* Session index was incremented during allocation */ + uint16_t ind = (uint16_t)usc->session - 1; + + pr_debug("%s: to free session[%d]\n", __func__, ind); + if (ind < SESSION_MAX) { + mutex_lock(&session_lock); + session[ind] = 0; + mutex_unlock(&session_lock); + } +} + +int q6usm_us_client_buf_free(unsigned int dir, + struct us_client *usc) +{ + struct us_port_data *port; + int rc = 0; + uint32_t size = 0; + + if ((usc == NULL) || + ((dir != IN) && (dir != OUT))) + return -EINVAL; + + mutex_lock(&usc->cmd_lock); + port = &usc->port[dir]; + if (port == NULL) { + mutex_unlock(&usc->cmd_lock); + return -EINVAL; + } + + if (port->data == NULL) { + mutex_unlock(&usc->cmd_lock); + return 0; + } + + rc = q6usm_memory_unmap(usc, port->phys, dir); + if (rc) + pr_err("%s: CMD Memory_unmap* failed\n", __func__); + + pr_debug("%s: data[%p]phys[%p][%p]\n", __func__, + (void *)port->data, (void *)port->phys, (void *)&port->phys); + size = port->buf_size * port->buf_cnt; + dma_free_coherent(NULL, size, port->data, port->phys); + port->data = NULL; + port->phys = 0; + port->buf_size = 0; + port->buf_cnt = 0; + + mutex_unlock(&usc->cmd_lock); + return 0; +} + +void q6usm_us_client_free(struct us_client *usc) +{ + int loopcnt = 0; + struct us_port_data *port; + + if ((usc == NULL) || + !(usc->session)) + return; + + for (loopcnt = 0; loopcnt <= OUT; ++loopcnt) { + port = &usc->port[loopcnt]; + if (port->data == NULL) + continue; + pr_debug("%s: loopcnt = %d\n", __func__, loopcnt); + q6usm_us_client_buf_free(loopcnt, usc); + } + q6usm_session_free(usc); + apr_deregister(usc->apr); + + 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(usc); + pr_debug("%s:\n", __func__); + return; +} + +struct us_client *q6usm_us_client_alloc( + void (*cb)(uint32_t, uint32_t, uint32_t *, void *), + void *priv) +{ + struct us_client *usc; + int n; + int lcnt = 0; + + usc = kzalloc(sizeof(struct us_client), GFP_KERNEL); + if (usc == NULL) + return NULL; + n = q6usm_session_alloc(usc); + if (n <= 0) + goto fail_session; + usc->session = n; + usc->cb = cb; + usc->priv = priv; + usc->apr = apr_register("ADSP", "USM", \ + (apr_fn)q6usm_callback,\ + ((usc->session) << 8 | 0x0001),\ + usc); + + if (usc->apr == NULL) { + pr_err("%s: Registration with APR failed\n", __func__); + goto fail; + } + 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", "USM", + (apr_fn)q6usm_mmapcallback, + 0x0FFFFFFFF, &this_mmap); + if (this_mmap.apr == NULL) { + pr_err("%s: USM port registration failed\n", + __func__); + goto fail; + } + } + + atomic_inc(&this_mmap.ref_cnt); + init_waitqueue_head(&usc->cmd_wait); + mutex_init(&usc->cmd_lock); + for (lcnt = 0; lcnt <= OUT; ++lcnt) { + mutex_init(&usc->port[lcnt].lock); + spin_lock_init(&usc->port[lcnt].dsp_lock); + } + atomic_set(&usc->cmd_state, 0); + + return usc; +fail: + q6usm_us_client_free(usc); + return NULL; +fail_session: + kfree(usc); + return NULL; +} + +int q6usm_us_client_buf_alloc(unsigned int dir, + struct us_client *usc, + unsigned int bufsz, + unsigned int bufcnt) +{ + int rc = 0; + struct us_port_data *port = NULL; + unsigned int size = bufsz*bufcnt; + + if ((usc == NULL) || + ((dir != IN) && (dir != OUT)) || (size == 0) || + (usc->session <= 0 || usc->session > SESSION_MAX)) { + pr_err("%s: wrong parameters: size=%d; bufcnt=%d\n", + __func__, size, bufcnt); + return -EINVAL; + } + + mutex_lock(&usc->cmd_lock); + + port = &usc->port[dir]; + + port->data = dma_alloc_coherent(NULL, size, &(port->phys), GFP_KERNEL); + if (port->data == NULL) { + pr_err("%s: US region allocation failed\n", __func__); + mutex_unlock(&usc->cmd_lock); + return -ENOMEM; + } + + port->buf_cnt = bufcnt; + port->buf_size = bufsz; + pr_debug("%s: data[%p]; phys[%p]; [%p]\n", __func__, + (void *)port->data, + (void *)port->phys, + (void *)&port->phys); + + rc = q6usm_memory_map(usc, port->phys, dir, size, 1); + if (rc < 0) { + pr_err("%s: CMD Memory_map failed\n", __func__); + mutex_unlock(&usc->cmd_lock); + q6usm_us_client_buf_free(dir, usc); + } else { + mutex_unlock(&usc->cmd_lock); + rc = 0; + } + + return rc; +} + +static int32_t q6usm_mmapcallback(struct apr_client_data *data, void *priv) +{ + uint32_t token; + uint32_t *payload = data->payload; + + 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) { + /* status field check */ + if (payload[1]) { + pr_err("%s: wrong response[%d] on cmd [%d]\n", + __func__, payload[1], payload[0]); + } else { + token = data->token; + switch (payload[0]) { + case USM_SESSION_CMD_MEMORY_MAP: + case USM_SESSION_CMD_MEMORY_UNMAP: + pr_debug("%s: cmd[0x%x]; result[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: wrong command[0x%x]\n", + __func__, payload[0]); + break; + } + } + } + return 0; +} + + +static int32_t q6usm_callback(struct apr_client_data *data, void *priv) +{ + struct us_client *usc = (struct us_client *)priv; + unsigned long dsp_flags; + uint32_t *payload = data->payload; + uint32_t token = data->token; + + if (usc == NULL) { + pr_err("%s: client info is NULL\n", __func__); + return -EINVAL; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + /* status field check */ + if (payload[1]) { + pr_err("%s: wrong response[%d] on cmd [%d]\n", + __func__, payload[1], payload[0]); + if (usc->cb) + usc->cb(data->opcode, token, + (uint32_t *)data->payload, usc->priv); + } else { + switch (payload[0]) { + case USM_SESSION_CMD_RUN: + case USM_STREAM_CMD_CLOSE: + if (token != usc->session) { + pr_err("%s: wrong token[%d]", + __func__, token); + break; + } + case USM_STREAM_CMD_OPEN_READ: + case USM_STREAM_CMD_OPEN_WRITE: + case USM_STREAM_CMD_SET_ENC_PARAM: + case USM_DATA_CMD_MEDIA_FORMAT_UPDATE: + case USM_SESSION_CMD_SIGNAL_DETECT_MODE: + if (atomic_read(&usc->cmd_state)) { + atomic_set(&usc->cmd_state, 0); + wake_up(&usc->cmd_wait); + } + if (usc->cb) + usc->cb(data->opcode, token, + (uint32_t *)data->payload, + usc->priv); + break; + default: + pr_debug("%s: command[0x%x] wrong response\n", + __func__, payload[0]); + break; + } + } + return 0; + } + + switch (data->opcode) { + case USM_DATA_EVENT_READ_DONE: { + struct us_port_data *port = &usc->port[OUT]; + + pr_debug("%s: R-D: stat=%d; buff=%x; size=%d; off=%d\n", + __func__, + payload[READDONE_IDX_STATUS], + payload[READDONE_IDX_BUFFER], + payload[READDONE_IDX_SIZE], + payload[READDONE_IDX_OFFSET]); + pr_debug("msw_ts=%d; lsw_ts=%d; flags=%d; id=%d; num=%d\n", + payload[READDONE_IDX_MSW_TS], + payload[READDONE_IDX_LSW_TS], + payload[READDONE_IDX_FLAGS], + payload[READDONE_IDX_ID], + payload[READDONE_IDX_NUMFRAMES]); + + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (payload[READDONE_IDX_STATUS]) { + pr_err("%s: wrong READDONE[%d]; token[%d]\n", + __func__, + payload[READDONE_IDX_STATUS], + token); + token = USM_WRONG_TOKEN; + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + break; + } + + if (port->expected_token != token) { + u32 cpu_buf = port->cpu_buf; + pr_err("%s: expected[%d] != token[%d]\n", + __func__, port->expected_token, token); + pr_debug("%s: dsp_buf=%d; cpu_buf=%d;\n", + __func__, port->dsp_buf, cpu_buf); + + token = USM_WRONG_TOKEN; + /* To prevent data handle continiue */ + port->expected_token = USM_WRONG_TOKEN; + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + break; + } /* port->expected_token != data->token */ + + port->expected_token = token + 1; + if (port->expected_token == port->buf_cnt) + port->expected_token = 0; + + /* gap support */ + if (port->expected_token != port->cpu_buf) { + port->dsp_buf = port->expected_token; + token = port->dsp_buf; /* for callback */ + } else + port->dsp_buf = token; + + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + break; + } /* case USM_DATA_EVENT_READ_DONE */ + + case USM_DATA_EVENT_WRITE_DONE: { + struct us_port_data *port = &usc->port[IN]; + + pr_debug("%s W-D: code[0x%x]; status[0x%x]; token[%d]", + __func__, + payload[0], payload[1], token); + + if (payload[WRITEDONE_IDX_STATUS]) { + pr_err("%s: wrong WRITEDONE_IDX_STATUS[%d]\n", + __func__, + payload[WRITEDONE_IDX_STATUS]); + break; + } + + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + port->dsp_buf = token + 1; + if (port->dsp_buf == port->buf_cnt) + port->dsp_buf = 0; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + + pr_debug("%s: WRITE_DONE: token=%d; dsp_buf=%d; cpu_buf=%d\n", + __func__, + token, port->dsp_buf, port->cpu_buf); + + break; + } /* case USM_DATA_EVENT_WRITE_DONE */ + + case USM_SESSION_EVENT_SIGNAL_DETECT_RESULT: { + pr_debug("%s: US detect result: result=%d", + __func__, + payload[0]); + + break; + } /* case USM_SESSION_EVENT_SIGNAL_DETECT_RESULT */ + + default: + pr_debug("%s: not supported code [0x%x]", + __func__, data->opcode); + return 0; + + } /* switch */ + + if (usc->cb) + usc->cb(data->opcode, token, + data->payload, usc->priv); + + return 0; +} + +uint32_t q6usm_get_ready_data(int dir, struct us_client *usc) +{ + uint32_t ret = 0xffffffff; + + if ((usc != NULL) && ((dir == IN) || (dir == OUT))) + ret = usc->port[dir].dsp_buf; + return ret; +} + +uint32_t q6usm_get_virtual_address(int dir, + struct us_client *usc, + struct vm_area_struct *vms) +{ + uint32_t ret = 0xffffffff; + + if (vms && (usc != NULL) && ((dir == IN) || (dir == OUT))) { + struct us_port_data *port = &usc->port[dir]; + ret = dma_mmap_coherent(NULL, vms, + port->data, port->phys, + port->buf_size * port->buf_cnt); + } + return ret; +} + +static void q6usm_add_hdr(struct us_client *usc, struct apr_hdr *hdr, + uint32_t pkt_size, bool cmd_flg) +{ + pr_debug("%s: pkt size=%d; cmd_flg=%d\n", + __func__, pkt_size, cmd_flg); + pr_debug("**************\n"); + mutex_lock(&usc->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 *)usc->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_USM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = (usc->session << 8) | 0x0001; + hdr->dest_port = (usc->session << 8) | 0x0001; + if (cmd_flg) { + hdr->token = usc->session; + atomic_set(&usc->cmd_state, 1); + } + hdr->pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, pkt_size); + mutex_unlock(&usc->cmd_lock); + return; +} + +static void q6usm_add_mmaphdr(struct us_client *usc, struct apr_hdr *hdr, + uint32_t pkt_size, bool cmd_flg) +{ + pr_debug("%s: pkt size=%d cmd_flg=%d\n", + __func__, pkt_size, cmd_flg); + pr_debug("**************\n"); + 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; +} + +static uint32_t q6usm_ext2int_format(uint32_t ext_format) +{ + uint32_t int_format = INVALID_FORMAT; + switch (ext_format) { + case FORMAT_USPS_EPOS: + int_format = US_POINT_EPOS_FORMAT; + break; + case FORMAT_USRAW: + int_format = US_RAW_FORMAT; + break; + default: + pr_err("%s: Invalid format[%d]\n", __func__, ext_format); + break; + } + + return int_format; +} + +int q6usm_open_read(struct us_client *usc, + uint32_t format) +{ + uint32_t int_format = INVALID_FORMAT; + int rc = 0x00; + struct usm_stream_cmd_open_read open; + + pr_debug("%s: session[%d]", __func__, usc->session); + + if ((usc == NULL) || (usc->apr == NULL)) { + pr_err("%s: client or its apr is NULL\n", __func__); + return -EINVAL; + } + + q6usm_add_hdr(usc, &open.hdr, sizeof(open), true); + open.hdr.opcode = USM_STREAM_CMD_OPEN_READ; + open.src_endpoint = 0; /* AFE */ + open.pre_proc_top = 0; /* No preprocessing required */ + + int_format = q6usm_ext2int_format(format); + if (int_format == INVALID_FORMAT) + return -EINVAL; + + open.uMode = STREAM_PRIORITY_NORMAL; + open.format = int_format; + + rc = apr_send_pkt(usc->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(usc->cmd_wait, + (atomic_read(&usc->cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: timeout, waited for OPEN_READ rc[%d]\n", + __func__, rc); + goto fail_cmd; + } else + rc = 0; +fail_cmd: + return rc; +} + + +int q6usm_enc_cfg_blk(struct us_client *usc, struct us_encdec_cfg* us_cfg) +{ + uint32_t int_format = INVALID_FORMAT; + struct usm_stream_cmd_encdec_cfg_blk enc_cfg_obj; + struct usm_stream_cmd_encdec_cfg_blk *enc_cfg = &enc_cfg_obj; + int rc = 0; + uint32_t total_cfg_size = + sizeof(struct usm_stream_cmd_encdec_cfg_blk); + uint32_t round_params_size = 0; + uint8_t is_allocated = 0; + + + if ((usc == NULL) || (us_cfg == NULL)) { + pr_err("%s: wrong input", __func__); + return -EINVAL; + } + + int_format = q6usm_ext2int_format(us_cfg->format_id); + if (int_format == INVALID_FORMAT) { + pr_err("%s: wrong input format[%d]", + __func__, us_cfg->format_id); + return -EINVAL; + } + + /* Transparent configuration data is after enc_cfg */ + /* Integer number of u32s is requred */ + round_params_size = ((us_cfg->params_size + 3)/4) * 4; + if (round_params_size > USM_MAX_CFG_DATA_SIZE) { + /* Dynamic allocated encdec_cfg_blk is required */ + /* static part use */ + round_params_size -= USM_MAX_CFG_DATA_SIZE; + total_cfg_size += round_params_size; + enc_cfg = kzalloc(total_cfg_size, GFP_KERNEL); + if (enc_cfg == NULL) { + pr_err("%s: enc_cfg[%d] allocation failed\n", + __func__, total_cfg_size); + return -ENOMEM; + } + is_allocated = 1; + } else + round_params_size = 0; + + q6usm_add_hdr(usc, &enc_cfg->hdr, total_cfg_size - APR_HDR_SIZE, true); + + enc_cfg->hdr.opcode = USM_STREAM_CMD_SET_ENC_PARAM; + enc_cfg->param_id = USM_PARAM_ID_ENCDEC_ENC_CFG_BLK; + enc_cfg->param_size = sizeof(struct usm_encode_cfg_blk)+ + round_params_size; + enc_cfg->enc_blk.frames_per_buf = 1; + enc_cfg->enc_blk.format_id = int_format; + enc_cfg->enc_blk.cfg_size = sizeof(struct usm_cfg_common)+ + USM_MAX_CFG_DATA_SIZE + + round_params_size; + memcpy(&(enc_cfg->enc_blk.cfg_common), &(us_cfg->cfg_common), + sizeof(struct usm_cfg_common)); + + /* Transparent data copy */ + memcpy(enc_cfg->enc_blk.transp_data, us_cfg->params, + us_cfg->params_size); + pr_debug("%s: cfg_size[%d], params_size[%d]\n", + __func__, + enc_cfg->enc_blk.cfg_size, + us_cfg->params_size); + pr_debug("%s: params[%d,%d,%d,%d, %d,%d,%d,%d]\n", + __func__, + enc_cfg->enc_blk.transp_data[0], + enc_cfg->enc_blk.transp_data[1], + enc_cfg->enc_blk.transp_data[2], + enc_cfg->enc_blk.transp_data[3], + enc_cfg->enc_blk.transp_data[4], + enc_cfg->enc_blk.transp_data[5], + enc_cfg->enc_blk.transp_data[6], + enc_cfg->enc_blk.transp_data[7] + ); + pr_debug("%s: srate:%d, ch=%d, bps= %d; dmap:0x%x; dev_id=0x%x\n", + __func__, enc_cfg->enc_blk.cfg_common.sample_rate, + enc_cfg->enc_blk.cfg_common.ch_cfg, + enc_cfg->enc_blk.cfg_common.bits_per_sample, + enc_cfg->enc_blk.cfg_common.data_map, + enc_cfg->enc_blk.cfg_common.dev_id); + + rc = apr_send_pkt(usc->apr, (uint32_t *) enc_cfg); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(usc->cmd_wait, + (atomic_read(&usc->cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: timeout opcode[0x%x]\n", + __func__, enc_cfg->hdr.opcode); + } else + rc = 0; + +fail_cmd: + if (is_allocated == 1) + kfree(enc_cfg); + + return rc; +} + +int q6usm_dec_cfg_blk(struct us_client *usc, struct us_encdec_cfg *us_cfg) +{ + + uint32_t int_format = INVALID_FORMAT; + struct usm_stream_media_format_update dec_cfg_obj; + struct usm_stream_media_format_update *dec_cfg = &dec_cfg_obj; + + int rc = 0; + uint32_t total_cfg_size = sizeof(struct usm_stream_media_format_update); + uint32_t round_params_size = 0; + uint8_t is_allocated = 0; + + + if ((usc == NULL) || (us_cfg == NULL)) { + pr_err("%s: wrong input", __func__); + return -EINVAL; + } + + int_format = q6usm_ext2int_format(us_cfg->format_id); + if (int_format == INVALID_FORMAT) { + pr_err("%s: wrong input format[%d]", + __func__, us_cfg->format_id); + return -EINVAL; + } + + /* Transparent configuration data is after enc_cfg */ + /* Integer number of u32s is requred */ + round_params_size = ((us_cfg->params_size + 3)/4) * 4; + if (round_params_size > USM_MAX_CFG_DATA_SIZE) { + /* Dynamic allocated encdec_cfg_blk is required */ + /* static part use */ + round_params_size -= USM_MAX_CFG_DATA_SIZE; + total_cfg_size += round_params_size; + dec_cfg = kzalloc(total_cfg_size, GFP_KERNEL); + if (dec_cfg == NULL) { + pr_err("%s:dec_cfg[%d] allocation failed\n", + __func__, total_cfg_size); + return -ENOMEM; + } + is_allocated = 1; + } else { /* static transp_data is enough */ + round_params_size = 0; + } + + q6usm_add_hdr(usc, &dec_cfg->hdr, total_cfg_size - APR_HDR_SIZE, true); + + dec_cfg->hdr.opcode = USM_DATA_CMD_MEDIA_FORMAT_UPDATE; + dec_cfg->format_id = int_format; + dec_cfg->cfg_size = sizeof(struct usm_cfg_common) + + USM_MAX_CFG_DATA_SIZE + + round_params_size; + memcpy(&(dec_cfg->cfg_common), &(us_cfg->cfg_common), + sizeof(struct usm_cfg_common)); + /* Transparent data copy */ + memcpy(dec_cfg->transp_data, us_cfg->params, us_cfg->params_size); + pr_debug("%s: cfg_size[%d], params_size[%d]; parambytes[%d,%d,%d,%d]\n", + __func__, + dec_cfg->cfg_size, + us_cfg->params_size, + dec_cfg->transp_data[0], + dec_cfg->transp_data[1], + dec_cfg->transp_data[2], + dec_cfg->transp_data[3] + ); + + rc = apr_send_pkt(usc->apr, (uint32_t *) dec_cfg); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(usc->cmd_wait, + (atomic_read(&usc->cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: timeout opcode[0x%x]\n", + __func__, dec_cfg->hdr.opcode); + } else + rc = 0; + +fail_cmd: + if (is_allocated == 1) + kfree(dec_cfg); + + return rc; +} + +int q6usm_open_write(struct us_client *usc, + uint32_t format) +{ + int rc = 0; + uint32_t int_format = INVALID_FORMAT; + struct usm_stream_cmd_open_write open; + + pr_debug("%s: session[%d]", __func__, usc->session); + + if ((usc == NULL) || (usc->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6usm_add_hdr(usc, &open.hdr, sizeof(open), true); + open.hdr.opcode = USM_STREAM_CMD_OPEN_WRITE; + + int_format = q6usm_ext2int_format(format); + if (int_format == INVALID_FORMAT) { + pr_err("%s: wrong format[%d]", __func__, format); + return -EINVAL; + } + + open.format = int_format; + + rc = apr_send_pkt(usc->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(usc->cmd_wait, + (atomic_read(&usc->cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s:timeout. waited for OPEN_WRITR rc[%d]\n", + __func__, rc); + goto fail_cmd; + } else + rc = 0; + +fail_cmd: + return rc; +} + +int q6usm_run(struct us_client *usc, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct usm_stream_cmd_run run; + int rc = 0; + + if ((usc == NULL) || (usc->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + q6usm_add_hdr(usc, &run.hdr, sizeof(run), true); + + run.hdr.opcode = USM_SESSION_CMD_RUN; + run.flags = flags; + run.msw_ts = msw_ts; + run.lsw_ts = lsw_ts; + + rc = apr_send_pkt(usc->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("%s: Commmand run failed[%d]\n", __func__, rc); + goto fail_cmd; + } + + rc = wait_event_timeout(usc->cmd_wait, + (atomic_read(&usc->cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: timeout. waited for run success rc[%d]\n", + __func__, rc); + } else + rc = 0; + +fail_cmd: + return rc; +} + + +int q6usm_memory_map(struct us_client *usc, uint32_t buf_add, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct usm_stream_cmd_memory_map mem_map; + int rc = 0; + + if ((usc == NULL) || (usc->apr == NULL) || (this_mmap.apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6usm_add_mmaphdr(usc, &mem_map.hdr, + sizeof(struct usm_stream_cmd_memory_map), true); + mem_map.hdr.opcode = USM_SESSION_CMD_MEMORY_MAP; + + mem_map.buf_add = buf_add; + mem_map.buf_size = bufsz * bufcnt; + mem_map.mempool_id = 0; + + pr_debug("%s: buf add[%x] buf_add_parameter[%x]\n", + __func__, mem_map.buf_add, buf_add); + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) &mem_map); + if (rc < 0) { + pr_err("%s: mem_map op[0x%x]rc[%d]\n", + __func__, mem_map.hdr.opcode, rc); + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: timeout. waited for memory_map\n", __func__); + } else + rc = 0; +fail_cmd: + return rc; +} + +int q6usm_memory_unmap(struct us_client *usc, uint32_t buf_add, int dir) +{ + struct usm_stream_cmd_memory_unmap mem_unmap; + int rc = 0; + + if ((usc == NULL) || (usc->apr == NULL) || (this_mmap.apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6usm_add_mmaphdr(usc, &mem_unmap.hdr, + sizeof(struct usm_stream_cmd_memory_unmap), true); + mem_unmap.hdr.opcode = USM_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("%s:mem_unmap op[0x%x]rc[%d]\n", + __func__, mem_unmap.hdr.opcode, rc); + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: timeout. waited for memory_map\n", __func__); + } else + rc = 0; +fail_cmd: + return rc; +} + +int q6usm_read(struct us_client *usc, uint32_t read_ind) +{ + struct usm_stream_cmd_read read; + struct us_port_data *port = NULL; + int rc = 0; + u32 read_counter = 0; + u32 loop_ind = 0; + + if ((usc == NULL) || (usc->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + port = &usc->port[OUT]; + + if (read_ind > port->buf_cnt) { + pr_err("%s: wrong read_ind[%d]\n", + __func__, read_ind); + return -EINVAL; + } + if (read_ind == port->cpu_buf) { + pr_err("%s: no free region\n", __func__); + return 0; + } + + if (read_ind > port->cpu_buf) { /* 1 range */ + read_counter = read_ind - port->cpu_buf; + } else { /* 2 ranges */ + read_counter = (port->buf_cnt - port->cpu_buf) + read_ind; + } + + q6usm_add_hdr(usc, &read.hdr, (sizeof(read) - APR_HDR_SIZE), false); + + read.hdr.opcode = USM_DATA_CMD_READ; + read.buf_size = port->buf_size; + + for (loop_ind = 0; loop_ind < read_counter; ++loop_ind) { + u32 temp_cpu_buf = port->cpu_buf; + + read.buf_add = (uint32_t)(port->phys) + + port->buf_size * (port->cpu_buf); + read.uid = port->cpu_buf; + read.hdr.token = port->cpu_buf; + read.counter = 1; + + ++(port->cpu_buf); + if (port->cpu_buf == port->buf_cnt) + port->cpu_buf = 0; + + rc = apr_send_pkt(usc->apr, (uint32_t *) &read); + + if (rc < 0) { + port->cpu_buf = temp_cpu_buf; + + pr_err("%s:read op[0x%x]rc[%d]\n", + __func__, read.hdr.opcode, rc); + break; + } else + rc = 0; + } /* bufs loop */ + + return rc; +} + +int q6usm_write(struct us_client *usc, uint32_t write_ind) +{ + int rc = 0; + struct usm_stream_cmd_write cmd_write; + struct us_port_data *port = NULL; + u32 current_dsp_buf = 0; + + if ((usc == NULL) || (usc->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + port = &usc->port[IN]; + + current_dsp_buf = port->dsp_buf; + /* free region, caused by new dsp_buf report from DSP, */ + /* can be only extended */ + if (port->cpu_buf >= current_dsp_buf) { + /* 2 -part free region, including empty buffer */ + if ((write_ind <= port->cpu_buf) && + (write_ind > current_dsp_buf)) { + pr_err("%s: wrong w_ind[%d]; d_buf=%d; c_buf=%d\n", + __func__, write_ind, + current_dsp_buf, port->cpu_buf); + return -EINVAL; + } + } else { + /* 1 -part free region */ + if ((write_ind <= port->cpu_buf) || + (write_ind > current_dsp_buf)) { + pr_err("%s: wrong w_ind[%d]; d_buf=%d; c_buf=%d\n", + __func__, write_ind, + current_dsp_buf, port->cpu_buf); + return -EINVAL; + } + } + + q6usm_add_hdr(usc, &cmd_write.hdr, + (sizeof(cmd_write) - APR_HDR_SIZE), false); + + cmd_write.hdr.opcode = USM_DATA_CMD_WRITE; + cmd_write.buf_size = port->buf_size; + + while (port->cpu_buf != write_ind) { + u32 temp_cpu_buf = port->cpu_buf; + + cmd_write.buf_add = (uint32_t)(port->phys) + + port->buf_size * (port->cpu_buf); + cmd_write.uid = port->cpu_buf; + cmd_write.hdr.token = port->cpu_buf; + + pr_debug("%s:buf addr[0x%x] size[%d] token[%d] uid[%d]\n", + __func__, cmd_write.buf_add, cmd_write.buf_size, + cmd_write.hdr.token, cmd_write.uid); + pr_debug("%s: data=0x%p\n", __func__, port->data); + + ++(port->cpu_buf); + if (port->cpu_buf == port->buf_cnt) + port->cpu_buf = 0; + + rc = apr_send_pkt(usc->apr, (uint32_t *) &cmd_write); + + if (rc < 0) { + port->cpu_buf = temp_cpu_buf; + pr_err("%s:write op[0x%x];rc[%d];cpu_buf[%d]\n", + __func__, cmd_write.hdr.opcode, + rc, port->cpu_buf); + break; + } + + rc = 0; + } + + pr_debug("%s:exit: rc=%d; write_ind=%d; cpu_buf=%d; dsp_buf=%d\n", + __func__, rc, write_ind, port->cpu_buf, port->dsp_buf); + + return rc; +} + +bool q6usm_is_write_buf_full(struct us_client *usc, uint32_t* free_region) +{ + struct us_port_data *port = NULL; + u32 cpu_buf = 0; + + if ((usc == NULL) || !free_region) { + pr_err("%s: input data wrong\n", __func__); + return false; + } + port = &usc->port[IN]; + cpu_buf = port->cpu_buf + 1; + if (cpu_buf == port->buf_cnt) + cpu_buf = 0; + + *free_region = port->dsp_buf; + + return cpu_buf == *free_region; +} + +int q6usm_cmd(struct us_client *usc, int cmd) +{ + struct apr_hdr hdr; + int rc = 0; + atomic_t *state; + + if ((usc == NULL) || (usc->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + q6usm_add_hdr(usc, &hdr, (sizeof(hdr) - APR_HDR_SIZE), true); + switch (cmd) { + case CMD_CLOSE: + hdr.opcode = USM_STREAM_CMD_CLOSE; + state = &usc->cmd_state; + break; + + default: + pr_err("%s:Invalid format[%d]\n", __func__, cmd); + goto fail_cmd; + } + + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + usc->session, hdr.opcode); + rc = apr_send_pkt(usc->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("%s: Command 0x%x failed\n", __func__, hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(usc->cmd_wait, (atomic_read(state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s:timeout. waited for response opcode[0x%x]\n", + __func__, hdr.opcode); + } else + rc = 0; +fail_cmd: + return rc; +} + +int q6usm_set_us_detection(struct us_client *usc, + struct usm_session_cmd_detect_info *detect_info, + uint16_t detect_info_size) +{ + int rc = 0; + + if ((usc == NULL) || + (detect_info_size == 0) || + (detect_info == NULL)) { + pr_err("%s: wrong input: usc=0x%p, inf_size=%d; info=0x%p", + __func__, + usc, + detect_info_size, + detect_info); + return -EINVAL; + } + + q6usm_add_hdr(usc, &detect_info->hdr, + detect_info_size - APR_HDR_SIZE, true); + + detect_info->hdr.opcode = USM_SESSION_CMD_SIGNAL_DETECT_MODE; + + rc = apr_send_pkt(usc->apr, (uint32_t *)detect_info); + if (rc < 0) { + pr_err("%s:Comamnd signal detect failed\n", __func__); + return -EINVAL; + } + rc = wait_event_timeout(usc->cmd_wait, + (atomic_read(&usc->cmd_state) == 0), + Q6USM_TIMEOUT_JIFFIES); + if (!rc) { + rc = -ETIME; + pr_err("%s: CMD_SIGNAL_DETECT_MODE: timeout=%d\n", + __func__, Q6USM_TIMEOUT_JIFFIES); + } else + rc = 0; + + return rc; +} + +static int __init q6usm_init(void) +{ + pr_debug("%s\n", __func__); + init_waitqueue_head(&this_mmap.cmd_wait); + memset(session, 0, sizeof(session)); + return 0; +} + +device_initcall(q6usm_init); + +MODULE_DESCRIPTION("Interface with QDSP6:USM"); +MODULE_VERSION(DRV_VERSION); diff --git a/arch/arm/mach-msm/qdsp6v2/ultrasound/q6usm.h b/arch/arm/mach-msm/qdsp6v2/ultrasound/q6usm.h new file mode 100644 index 0000000000000000000000000000000000000000..1338e86b3fb8dcadf6e46deae47925ec48fcd39c --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/ultrasound/q6usm.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_USM_H__ +#define __Q6_USM_H__ + +#include + +/* cyclic buffer with 1 gap support */ +#define USM_MIN_BUF_CNT 3 + +#define FORMAT_USPS_EPOS 0x00000000 +#define FORMAT_USRAW 0x00000001 +#define INVALID_FORMAT 0xffffffff + +#define IN 0x000 +#define OUT 0x001 + +#define USM_WRONG_TOKEN 0xffffffff +#define USM_UNDEF_TOKEN 0xfffffffe + +#define CMD_CLOSE 0x0004 + +/* 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 + +struct us_region { + dma_addr_t phys; + /* If == NULL, the region isn't allocated */ + void *data; + /* number of buffers in the region */ + uint32_t buf_cnt; + /* size of buffer */ + uint32_t buf_size; +}; + +struct us_port_data { + dma_addr_t phys; + /* cyclic region of buffers with 1 gap */ + void *data; + /* number of buffers in the region */ + uint32_t buf_cnt; + /* size of buffer */ + uint32_t buf_size; + /* TX: write index */ + uint32_t dsp_buf; + /* TX: read index */ + uint32_t cpu_buf; + /* expected token from dsp */ + uint32_t expected_token; + /* read or write locks */ + struct mutex lock; + spinlock_t dsp_lock; +}; + +struct us_client { + int session; + /* idx:1 out port, 0: in port*/ + struct us_port_data port[2]; + + struct apr_svc *apr; + struct mutex cmd_lock; + + atomic_t cmd_state; + atomic_t eos_state; + wait_queue_head_t cmd_wait; + + void (*cb)(uint32_t, uint32_t, uint32_t *, void *); + void *priv; +}; + +int q6usm_run(struct us_client *usc, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); +int q6usm_cmd(struct us_client *usc, int cmd); +int q6usm_us_client_buf_alloc(unsigned int dir, struct us_client *usc, + unsigned int bufsz, unsigned int bufcnt); +int q6usm_enc_cfg_blk(struct us_client *usc, struct us_encdec_cfg *us_cfg); +int q6usm_dec_cfg_blk(struct us_client *usc, struct us_encdec_cfg *us_cfg); +int q6usm_read(struct us_client *usc, uint32_t read_ind); +struct us_client *q6usm_us_client_alloc( + void (*cb)(uint32_t, uint32_t, uint32_t *, void *), + void *priv); +int q6usm_open_read(struct us_client *usc, uint32_t format); +void q6usm_us_client_free(struct us_client *usc); +int q6usm_memory_map(struct us_client *usc, uint32_t buf_add, + int dir, uint32_t bufsz, uint32_t bufcnt); +int q6usm_memory_unmap(struct us_client *usc, uint32_t buf_add, + int dir); + +uint32_t q6usm_get_ready_data(int dir, struct us_client *usc); +uint32_t q6usm_get_virtual_address(int dir, struct us_client *usc, + struct vm_area_struct *vms); + +int q6usm_open_write(struct us_client *usc, uint32_t format); +int q6usm_write(struct us_client *usc, uint32_t write_ind); +bool q6usm_is_write_buf_full(struct us_client *usc, uint32_t* free_region); + +int q6usm_set_us_detection(struct us_client *usc, + struct usm_session_cmd_detect_info *detect_info, + uint16_t detect_info_size); + +#endif /* __Q6_USM_H__ */ diff --git a/arch/arm/mach-msm/qdsp6v2/ultrasound/usf.c b/arch/arm/mach-msm/qdsp6v2/ultrasound/usf.c new file mode 100644 index 0000000000000000000000000000000000000000..614339b41eb23688eb87bbbceb0e8f652aa3df5f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/ultrasound/usf.c @@ -0,0 +1,1378 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "q6usm.h" +#include "usfcdev.h" + +/* The driver version*/ +#define DRV_VERSION "1.3.1" + +/* Standard timeout in the asynchronous ops */ +#define USF_TIMEOUT_JIFFIES (1*HZ) /* 1 sec */ + +/* Undefined USF device */ +#define USF_UNDEF_DEV_ID 0xffff + +/* RX memory mapping flag */ +#define USF_VM_WRITE 2 + +/* Number of events, copied from the user space to kernel one */ +#define USF_EVENTS_PORTION_SIZE 20 + +/* Indexes in range definitions */ +#define MIN_IND 0 +#define MAX_IND 1 + +/* The coordinates indexes */ +#define X_IND 0 +#define Y_IND 1 +#define Z_IND 2 + +/* Place for opreation result, received from QDSP6 */ +#define APR_RESULT_IND 1 + +/* Place for US detection result, received from QDSP6 */ +#define APR_US_DETECT_RESULT_IND 0 + +/* The driver states */ +enum usf_state_type { + USF_IDLE_STATE, + USF_OPENED_STATE, + USF_CONFIGURED_STATE, + USF_WORK_STATE, + USF_ERROR_STATE +}; + +/* The US detection status upon FW/HW based US detection results */ +enum usf_us_detect_type { + USF_US_DETECT_UNDEF, + USF_US_DETECT_YES, + USF_US_DETECT_NO +}; + +struct usf_xx_type { + /* Name of the client - event calculator */ + char client_name[USF_MAX_CLIENT_NAME_SIZE]; + /* The driver state in TX or RX direction */ + enum usf_state_type usf_state; + /* wait for q6 events mechanism */ + wait_queue_head_t wait; + /* IF with q6usm info */ + struct us_client *usc; + /* Q6:USM' Encoder/decoder configuration */ + struct us_encdec_cfg encdec_cfg; + /* Shared buffer (with Q6:USM) size */ + uint32_t buffer_size; + /* Number of the shared buffers (with Q6:USM) */ + uint32_t buffer_count; + /* Shared memory (Cyclic buffer with 1 gap) control */ + uint32_t new_region; + uint32_t prev_region; + /* Q6:USM's events handler */ + void (*cb)(uint32_t, uint32_t, uint32_t *, void *); + /* US detection result */ + enum usf_us_detect_type us_detect_type; + /* User's update info isn't acceptable */ + u8 user_upd_info_na; +}; + +struct usf_type { + /* TX device component configuration & control */ + struct usf_xx_type usf_tx; + /* RX device component configuration & control */ + struct usf_xx_type usf_rx; + /* Index into the opened device container */ + /* To prevent mutual usage of the same device */ + uint16_t dev_ind; + /* Event types, supported by device */ + uint16_t event_types; + /* The device is "input" module registered client */ + struct input_dev *input_if; + /* The event source */ + int event_src; + /* Bitmap of types of events, conflicting to USF's ones */ + uint16_t conflicting_event_types; + /* Bitmap of types of events from devs, conflicting with USF */ + uint16_t conflicting_event_filters; +}; + +/* The MAX number of the supported devices */ +#define MAX_DEVS_NUMBER 1 + +/* The opened devices container */ +static const int s_event_src_map[] = { + BTN_TOOL_PEN, /* US_INPUT_SRC_PEN*/ + 0, /* US_INPUT_SRC_FINGER */ + 0, /* US_INPUT_SRC_UNDEF */ +}; + +/* The opened devices container */ +static int s_opened_devs[MAX_DEVS_NUMBER]; + +#define USF_NAME_PREFIX "USF_" +#define USF_NAME_PREFIX_SIZE 4 + +static void usf_rx_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct usf_xx_type *usf_xx = (struct usf_xx_type *) priv; + + if (usf_xx == NULL) { + pr_err("%s: the private data is NULL\n", __func__); + return; + } + + switch (opcode) { + case USM_DATA_EVENT_WRITE_DONE: + wake_up(&usf_xx->wait); + break; + default: + break; + } +} + +static void usf_tx_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct usf_xx_type *usf_xx = (struct usf_xx_type *) priv; + + if (usf_xx == NULL) { + pr_err("%s: the private data is NULL\n", __func__); + return; + } + + switch (opcode) { + case USM_DATA_EVENT_READ_DONE: + if (token == USM_WRONG_TOKEN) + usf_xx->usf_state = USF_ERROR_STATE; + usf_xx->new_region = token; + wake_up(&usf_xx->wait); + break; + + case USM_SESSION_EVENT_SIGNAL_DETECT_RESULT: + usf_xx->us_detect_type = (payload[APR_US_DETECT_RESULT_IND]) ? + USF_US_DETECT_YES : + USF_US_DETECT_NO; + + wake_up(&usf_xx->wait); + break; + + case APR_BASIC_RSP_RESULT: + if (payload[APR_RESULT_IND]) { + usf_xx->usf_state = USF_ERROR_STATE; + usf_xx->new_region = USM_WRONG_TOKEN; + wake_up(&usf_xx->wait); + } + break; + + default: + break; + } +} + +static void release_xx(struct usf_xx_type *usf_xx) +{ + if (usf_xx != NULL) { + if (usf_xx->usc) { + q6usm_us_client_free(usf_xx->usc); + usf_xx->usc = NULL; + } + + if (usf_xx->encdec_cfg.params != NULL) { + kfree(usf_xx->encdec_cfg.params); + usf_xx->encdec_cfg.params = NULL; + } + } +} + +static void usf_disable(struct usf_xx_type *usf_xx) +{ + if (usf_xx != NULL) { + if ((usf_xx->usf_state != USF_IDLE_STATE) && + (usf_xx->usf_state != USF_OPENED_STATE)) { + (void)q6usm_cmd(usf_xx->usc, CMD_CLOSE); + usf_xx->usf_state = USF_OPENED_STATE; + wake_up(&usf_xx->wait); + } + release_xx(usf_xx); + } +} + +static int config_xx(struct usf_xx_type *usf_xx, struct us_xx_info_type *config) +{ + int rc = 0; + uint16_t data_map_size = 0; + + if ((usf_xx == NULL) || + (config == NULL)) + return -EINVAL; + + data_map_size = sizeof(usf_xx->encdec_cfg.cfg_common.data_map); + + if (config->client_name != NULL) { + if (strncpy_from_user(usf_xx->client_name, + config->client_name, + sizeof(usf_xx->client_name) - 1) < 0) { + pr_err("%s: get client name failed\n", __func__); + return -EINVAL; + } + } + + pr_debug("%s: name=%s; buf_size:%d; dev_id:0x%x; sample_rate:%d\n", + __func__, usf_xx->client_name, config->buf_size, + config->dev_id, config->sample_rate); + + pr_debug("%s: buf_num:%d; format:%d; port_cnt:%d; data_size=%d\n", + __func__, config->buf_num, config->stream_format, + config->port_cnt, config->params_data_size); + + pr_debug("%s: p_id[0]=%d, p_id[1]=%d, p_id[2]=%d, p_id[3]=%d\n", + __func__, + config->port_id[0], + config->port_id[1], + config->port_id[2], + config->port_id[3]); + + if (data_map_size < config->port_cnt) { + pr_err("%s: number of supported ports:%d < requested:%d\n", + __func__, + data_map_size, + config->port_cnt); + return -EINVAL; + } + + /* q6usm allocation & configuration */ + usf_xx->buffer_size = config->buf_size; + usf_xx->buffer_count = config->buf_num; + usf_xx->encdec_cfg.cfg_common.bits_per_sample = + config->bits_per_sample; + usf_xx->encdec_cfg.cfg_common.sample_rate = config->sample_rate; + /* AFE port e.g. AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX */ + usf_xx->encdec_cfg.cfg_common.dev_id = config->dev_id; + + usf_xx->encdec_cfg.cfg_common.ch_cfg = config->port_cnt; + memcpy((void *)&usf_xx->encdec_cfg.cfg_common.data_map, + (void *)config->port_id, + config->port_cnt); + if (rc) { + pr_err("%s: ports offsets copy failure\n", __func__); + return -EINVAL; + } + + usf_xx->encdec_cfg.format_id = config->stream_format; + usf_xx->encdec_cfg.params_size = config->params_data_size; + usf_xx->user_upd_info_na = 1; /* it's used in US_GET_TX_UPDATE */ + + if (config->params_data_size > 0) { /* transparent data copy */ + usf_xx->encdec_cfg.params = kzalloc(config->params_data_size, + GFP_KERNEL); + if (usf_xx->encdec_cfg.params == NULL) { + pr_err("%s: params memory alloc[%d] failure\n", + __func__, + config->params_data_size); + return -ENOMEM; + } + rc = copy_from_user(usf_xx->encdec_cfg.params, + config->params_data, + config->params_data_size); + if (rc) { + pr_err("%s: transparent data copy failure\n", + __func__); + kfree(usf_xx->encdec_cfg.params); + usf_xx->encdec_cfg.params = NULL; + return -EFAULT; + } + pr_debug("%s: params_size[%d]; params[%d,%d,%d,%d, %d]\n", + __func__, + config->params_data_size, + usf_xx->encdec_cfg.params[0], + usf_xx->encdec_cfg.params[1], + usf_xx->encdec_cfg.params[2], + usf_xx->encdec_cfg.params[3], + usf_xx->encdec_cfg.params[4] + ); + } + + usf_xx->usc = q6usm_us_client_alloc(usf_xx->cb, (void *)usf_xx); + if (!usf_xx->usc) { + pr_err("%s: Could not allocate q6usm client\n", __func__); + rc = -EFAULT; + } + + return rc; +} + +static bool usf_match(uint16_t event_type_ind, struct input_dev *dev) +{ + bool rc = false; + + rc = (event_type_ind < MAX_EVENT_TYPE_NUM) && + ((dev->name == NULL) || + strncmp(dev->name, USF_NAME_PREFIX, USF_NAME_PREFIX_SIZE)); + pr_debug("%s: name=[%s]; rc=%d\n", + __func__, dev->name, rc); + + return rc; +} + +static bool usf_register_conflicting_events(uint16_t event_types) +{ + bool rc = true; + uint16_t ind = 0; + uint16_t mask = 1; + + for (ind = 0; ind < MAX_EVENT_TYPE_NUM; ++ind) { + if (event_types & mask) { + rc = usfcdev_register(ind, usf_match); + if (!rc) + break; + } + mask = mask << 1; + } + + return rc; +} + +static void usf_unregister_conflicting_events(uint16_t event_types) +{ + uint16_t ind = 0; + uint16_t mask = 1; + + for (ind = 0; ind < MAX_EVENT_TYPE_NUM; ++ind) { + if (event_types & mask) + usfcdev_unregister(ind); + mask = mask << 1; + } +} + +static void usf_set_event_filters(struct usf_type *usf, uint16_t event_filters) +{ + uint16_t ind = 0; + uint16_t mask = 1; + + if (usf->conflicting_event_filters != event_filters) { + for (ind = 0; ind < MAX_EVENT_TYPE_NUM; ++ind) { + if (usf->conflicting_event_types & mask) + usfcdev_set_filter(ind, event_filters&mask); + mask = mask << 1; + } + usf->conflicting_event_filters = event_filters; + } +} + +static int register_input_device(struct usf_type *usf_info, + struct us_input_info_type *input_info) +{ + int rc = 0; + struct input_dev *input_dev = NULL; + bool ret = true; + + if ((usf_info == NULL) || + (input_info == NULL) || + !(input_info->event_types & USF_ALL_EVENTS)) { + pr_err("%s: wrong input parameter(s)\n", __func__); + return -EINVAL; + } + + if (usf_info->input_if != NULL) { + pr_err("%s: input_if is already allocated\n", __func__); + return -EFAULT; + } + + input_dev = input_allocate_device(); + if (input_dev == NULL) { + pr_err("%s: input_allocate_device() failed\n", __func__); + return -ENOMEM; + } + + /* Common part configuration */ + input_dev->name = (const char *)(usf_info->usf_tx.client_name); + input_dev->phys = NULL; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0001; + + if (input_info->event_types & USF_TSC_EVENT) { + /* TSC part configuration */ + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, + input_info->tsc_x_dim[MIN_IND], + input_info->tsc_x_dim[MAX_IND], + 0, 0); + input_set_abs_params(input_dev, ABS_Y, + input_info->tsc_y_dim[MIN_IND], + input_info->tsc_y_dim[MAX_IND], + 0, 0); + input_set_abs_params(input_dev, ABS_DISTANCE, + input_info->tsc_z_dim[MIN_IND], + input_info->tsc_z_dim[MAX_IND], + 0, 0); + + input_set_abs_params(input_dev, ABS_PRESSURE, + input_info->tsc_pressure[MIN_IND], + input_info->tsc_pressure[MAX_IND], 0, 0); + + input_set_abs_params(input_dev, ABS_TILT_X, + input_info->tsc_x_tilt[MIN_IND], + input_info->tsc_x_tilt[MAX_IND], + 0, 0); + input_set_abs_params(input_dev, ABS_TILT_Y, + input_info->tsc_y_tilt[MIN_IND], + input_info->tsc_y_tilt[MAX_IND], + 0, 0); + } + + if (input_info->event_types & USF_MOUSE_EVENT) { + /* Mouse part configuration */ + input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | + BIT_MASK(BTN_MIDDLE); + input_dev->relbit[0] = BIT_MASK(REL_X) | + BIT_MASK(REL_Y) | + BIT_MASK(REL_Z); + } + + if (input_info->event_types & USF_KEYBOARD_EVENT) { + /* Keyboard part configuration */ + input_dev->evbit[0] |= BIT_MASK(EV_KEY); + + /* All keys are permitted */ + memset(input_dev->keybit, 0xff, sizeof(input_dev->keybit)); + } + + if (input_info->event_src < ARRAY_SIZE(s_event_src_map)) + usf_info->event_src = s_event_src_map[input_info->event_src]; + else + usf_info->event_src = 0; + + if (usf_info->event_src) + input_set_capability(input_dev, EV_KEY, usf_info->event_src); + + rc = input_register_device(input_dev); + if (rc) { + pr_err("%s: input_register_device() failed; rc=%d\n", + __func__, rc); + input_free_device(input_dev); + } else { + usf_info->input_if = input_dev; + usf_info->event_types = input_info->event_types; + pr_debug("%s: input device[%s] was registered\n", + __func__, input_dev->name); + ret = usf_register_conflicting_events( + input_info->conflicting_event_types); + if (ret) + usf_info->conflicting_event_types = + input_info->conflicting_event_types; + } + + return rc; +} + +static void notify_tsc_event(struct usf_type *usf_info, + struct point_event_type *pe) +{ + struct input_dev *input_if = usf_info->input_if; + + input_report_abs(input_if, ABS_X, pe->coordinates[X_IND]); + input_report_abs(input_if, ABS_Y, pe->coordinates[Y_IND]); + input_report_abs(input_if, ABS_DISTANCE, pe->coordinates[Z_IND]); + + input_report_abs(input_if, ABS_TILT_X, pe->inclinations[X_IND]); + input_report_abs(input_if, ABS_TILT_Y, pe->inclinations[Y_IND]); + + input_report_abs(input_if, ABS_PRESSURE, pe->pressure); + input_report_key(input_if, BTN_TOUCH, !!(pe->pressure)); + + if (usf_info->event_src) + input_report_key(input_if, usf_info->event_src, 1); + + input_sync(input_if); + + pr_debug("%s: TSC event: xyz[%d;%d;%d], incl[%d;%d], pressure[%d]\n", + __func__, + pe->coordinates[X_IND], + pe->coordinates[Y_IND], + pe->coordinates[Z_IND], + pe->inclinations[X_IND], + pe->inclinations[Y_IND], + pe->pressure); +} + +static void notify_mouse_event(struct input_dev *input_if, + struct mouse_event_type *me) +{ + if (me == NULL) { + pr_err("%s: mouse event is NULL\n", __func__); + return; + } + + input_report_rel(input_if, REL_X, me->rels[X_IND]); + input_report_rel(input_if, REL_Y, me->rels[Y_IND]); + input_report_rel(input_if, REL_Z, me->rels[Z_IND]); + + input_report_key(input_if, BTN_LEFT, + me->buttons_states & USF_BUTTON_LEFT_MASK); + input_report_key(input_if, BTN_MIDDLE, + me->buttons_states & USF_BUTTON_MIDDLE_MASK); + input_report_key(input_if, BTN_RIGHT, + me->buttons_states & USF_BUTTON_RIGHT_MASK); + + input_sync(input_if); + + pr_debug("%s: mouse event: dx[%d], dy[%d], buttons_states[%d]\n", + __func__, me->rels[X_IND], + me->rels[Y_IND], me->buttons_states); +} + +static void notify_key_event(struct input_dev *input_if, + struct key_event_type *ke) +{ + if (ke == NULL) { + pr_err("%s: key event is NULL\n", __func__); + return; + } + + input_report_key(input_if, ke->key, ke->key_state); + input_sync(input_if); + pr_debug("%s: key event: key[%d], state[%d]\n", + __func__, + ke->key, + ke->key_state); + +} + +static void handle_input_event(struct usf_type *usf_info, + uint16_t event_counter, + struct usf_event_type *event) +{ + struct input_dev *input_if = NULL; + uint16_t ind = 0; + uint16_t events_num = 0; + struct usf_event_type usf_events[USF_EVENTS_PORTION_SIZE]; + int rc = 0; + + if ((usf_info == NULL) || (usf_info->input_if == NULL) || + (event == NULL) || (!event_counter)) { + return; + } + + input_if = usf_info->input_if; + + while (event_counter > 0) { + if (event_counter > USF_EVENTS_PORTION_SIZE) { + events_num = USF_EVENTS_PORTION_SIZE; + event_counter -= USF_EVENTS_PORTION_SIZE; + } else { + events_num = event_counter; + event_counter = 0; + } + rc = copy_from_user(usf_events, + event, + events_num * sizeof(struct usf_event_type)); + if (rc) { + pr_err("%s: copy upd_rx_info from user; rc=%d\n", + __func__, rc); + return; + } + for (ind = 0; ind < events_num; ++ind) { + struct usf_event_type *p_event = &usf_events[ind]; + if (p_event->event_type & USF_TSC_EVENT) { + struct point_event_type *pe = + &(p_event->event_data.point_event); + if (pe->coordinates_type == + USF_PIX_COORDINATE) + notify_tsc_event(usf_info, pe); + else + pr_debug("%s: wrong coord type: %d", + __func__, + pe->coordinates_type); + continue; + } + if (p_event->event_type & USF_MOUSE_EVENT) { + notify_mouse_event(input_if, + &(p_event->event_data.mouse_event)); + continue; + } + if (p_event->event_type & USF_KEYBOARD_EVENT) + notify_key_event(input_if, + &(p_event->event_data.key_event)); + } /* loop in the portion */ + } /* all events loop */ +} + +static int usf_start_tx(struct usf_xx_type *usf_xx) +{ + int rc = q6usm_run(usf_xx->usc, 0, 0, 0); + + pr_debug("%s: tx: q6usm_run; rc=%d\n", __func__, rc); + if (!rc) { + if (usf_xx->buffer_count >= USM_MIN_BUF_CNT) { + /* supply all buffers */ + rc = q6usm_read(usf_xx->usc, + usf_xx->buffer_count); + pr_debug("%s: q6usm_read[%d]\n", + __func__, rc); + + if (rc) + pr_err("%s: buf read failed", + __func__); + else + usf_xx->usf_state = + USF_WORK_STATE; + } else + usf_xx->usf_state = + USF_WORK_STATE; + } + + return rc; +} /* usf_start_tx */ + +static int usf_start_rx(struct usf_xx_type *usf_xx) +{ + int rc = q6usm_run(usf_xx->usc, 0, 0, 0); + + pr_debug("%s: rx: q6usm_run; rc=%d\n", + __func__, rc); + if (!rc) + usf_xx->usf_state = USF_WORK_STATE; + + return rc; +} /* usf_start_rx */ + +static int usf_set_us_detection(struct usf_type *usf, unsigned long arg) +{ + uint32_t timeout = 0; + struct us_detect_info_type detect_info; + struct usm_session_cmd_detect_info usm_detect_info; + struct usm_session_cmd_detect_info *p_usm_detect_info = + &usm_detect_info; + uint32_t detect_info_size = sizeof(struct usm_session_cmd_detect_info); + struct usf_xx_type *usf_xx = &usf->usf_tx; + int rc = copy_from_user(&detect_info, + (void *) arg, + sizeof(detect_info)); + + if (rc) { + pr_err("%s: copy detect_info from user; rc=%d\n", + __func__, rc); + return -EFAULT; + } + + if (detect_info.us_detector != US_DETECT_FW) { + pr_err("%s: unsupported detector: %d\n", + __func__, detect_info.us_detector); + return -EINVAL; + } + + if ((detect_info.params_data_size != 0) && + (detect_info.params_data != NULL)) { + uint8_t *p_data = NULL; + + detect_info_size += detect_info.params_data_size; + p_usm_detect_info = kzalloc(detect_info_size, GFP_KERNEL); + if (p_usm_detect_info == NULL) { + pr_err("%s: detect_info[%d] allocation failed\n", + __func__, detect_info_size); + return -ENOMEM; + } + p_data = (uint8_t *)p_usm_detect_info + + sizeof(struct usm_session_cmd_detect_info); + + rc = copy_from_user(p_data, + (void *)detect_info.params_data, + detect_info.params_data_size); + if (rc) { + pr_err("%s: copy params from user; rc=%d\n", + __func__, rc); + kfree(p_usm_detect_info); + return -EFAULT; + } + p_usm_detect_info->algorithm_cfg_size = + detect_info.params_data_size; + } else + usm_detect_info.algorithm_cfg_size = 0; + + p_usm_detect_info->detect_mode = detect_info.us_detect_mode; + p_usm_detect_info->skip_interval = detect_info.skip_time; + + usf_xx->us_detect_type = USF_US_DETECT_UNDEF; + + rc = q6usm_set_us_detection(usf_xx->usc, + p_usm_detect_info, + detect_info_size); + if (rc || (detect_info.detect_timeout == USF_NO_WAIT_TIMEOUT)) { + if (detect_info_size > + sizeof(struct usm_session_cmd_detect_info)) + kfree(p_usm_detect_info); + return rc; + } + + /* Get US detection result */ + if (detect_info.detect_timeout == USF_INFINITIVE_TIMEOUT) { + rc = wait_event_interruptible(usf_xx->wait, + (usf_xx->us_detect_type != + USF_US_DETECT_UNDEF)); + } else { + if (detect_info.detect_timeout == USF_DEFAULT_TIMEOUT) + timeout = USF_TIMEOUT_JIFFIES; + else + timeout = detect_info.detect_timeout * HZ; + } + rc = wait_event_interruptible_timeout(usf_xx->wait, + (usf_xx->us_detect_type != + USF_US_DETECT_UNDEF), + timeout); + /* In the case of timeout, "no US" is assumed */ + if (rc < 0) { + pr_err("%s: Getting US detection failed rc[%d]\n", + __func__, rc); + return rc; + } + + usf->usf_rx.us_detect_type = usf->usf_tx.us_detect_type; + detect_info.is_us = (usf_xx->us_detect_type == USF_US_DETECT_YES); + rc = copy_to_user((void __user *)arg, + &detect_info, + sizeof(detect_info)); + if (rc) { + pr_err("%s: copy detect_info to user; rc=%d\n", + __func__, rc); + rc = -EFAULT; + } + + if (detect_info_size > sizeof(struct usm_session_cmd_detect_info)) + kfree(p_usm_detect_info); + + return rc; +} /* usf_set_us_detection */ + +static int usf_set_tx_info(struct usf_type *usf, unsigned long arg) +{ + struct us_tx_info_type config_tx; + const char *name = NULL; + struct usf_xx_type *usf_xx = &usf->usf_tx; + int rc = copy_from_user(&config_tx, + (void *) arg, + sizeof(config_tx)); + + if (rc) { + pr_err("%s: copy config_tx from user; rc=%d\n", + __func__, rc); + return -EFAULT; + } + + name = config_tx.us_xx_info.client_name; + + usf_xx->new_region = USM_UNDEF_TOKEN; + usf_xx->prev_region = USM_UNDEF_TOKEN; + usf_xx->cb = usf_tx_cb; + + init_waitqueue_head(&usf_xx->wait); + + if (name != NULL) { + int res = strncpy_from_user( + usf_xx->client_name, + name, + sizeof(usf_xx->client_name)-1); + if (res < 0) { + pr_err("%s: get client name failed\n", + __func__); + return -EINVAL; + } + } + + rc = config_xx(usf_xx, &config_tx.us_xx_info); + if (rc) + return rc; + + rc = q6usm_open_read(usf_xx->usc, + usf_xx->encdec_cfg.format_id); + if (rc) + return rc; + + rc = q6usm_us_client_buf_alloc(OUT, usf_xx->usc, + usf_xx->buffer_size, + usf_xx->buffer_count); + if (rc) + return rc; + + rc = q6usm_enc_cfg_blk(usf_xx->usc, + &usf_xx->encdec_cfg); + if (!rc && + (config_tx.input_info.event_types != USF_NO_EVENT)) { + rc = register_input_device(usf, + &config_tx.input_info); + } + + if (!rc) + usf_xx->usf_state = USF_CONFIGURED_STATE; + + return rc; +} /* usf_set_tx_info */ + +static int usf_set_rx_info(struct usf_type *usf, unsigned long arg) +{ + struct us_rx_info_type config_rx; + struct usf_xx_type *usf_xx = &usf->usf_rx; + int rc = copy_from_user(&config_rx, + (void *) arg, + sizeof(config_rx)); + + if (rc) { + pr_err("%s: copy config_rx from user; rc=%d\n", + __func__, rc); + return -EFAULT; + } + + usf_xx->new_region = USM_UNDEF_TOKEN; + usf_xx->prev_region = USM_UNDEF_TOKEN; + + usf_xx->cb = usf_rx_cb; + + rc = config_xx(usf_xx, &config_rx.us_xx_info); + if (rc) + return rc; + + rc = q6usm_open_write(usf_xx->usc, + usf_xx->encdec_cfg.format_id); + if (rc) + return rc; + + if (usf_xx->buffer_size && usf_xx->buffer_count) { + rc = q6usm_us_client_buf_alloc( + IN, + usf_xx->usc, + usf_xx->buffer_size, + usf_xx->buffer_count); + if (rc) + return rc; + } + + rc = q6usm_dec_cfg_blk(usf_xx->usc, + &usf_xx->encdec_cfg); + if (!rc) { + init_waitqueue_head(&usf_xx->wait); + usf_xx->usf_state = USF_CONFIGURED_STATE; + } + + return rc; +} /* usf_set_rx_info */ + + +static int usf_get_tx_update(struct usf_type *usf, unsigned long arg) +{ + struct us_tx_update_info_type upd_tx_info; + unsigned long prev_jiffies = 0; + uint32_t timeout = 0; + struct usf_xx_type *usf_xx = &usf->usf_tx; + int rc = copy_from_user(&upd_tx_info, (void *) arg, + sizeof(upd_tx_info)); + + if (rc) { + pr_err("%s: copy upd_tx_info from user; rc=%d\n", + __func__, rc); + return -EFAULT; + } + + if (!usf_xx->user_upd_info_na) { + usf_set_event_filters(usf, upd_tx_info.event_filters); + handle_input_event(usf, + upd_tx_info.event_counter, + upd_tx_info.event); + + /* Release available regions */ + rc = q6usm_read(usf_xx->usc, + upd_tx_info.free_region); + if (rc) + return rc; + } else + usf_xx->user_upd_info_na = 0; + + /* Get data ready regions */ + if (upd_tx_info.timeout == USF_INFINITIVE_TIMEOUT) { + rc = wait_event_interruptible(usf_xx->wait, + (usf_xx->prev_region != + usf_xx->new_region) || + (usf_xx->usf_state != + USF_WORK_STATE)); + } else { + if (upd_tx_info.timeout == USF_NO_WAIT_TIMEOUT) + rc = (usf_xx->prev_region != usf_xx->new_region); + else { + prev_jiffies = jiffies; + if (upd_tx_info.timeout == USF_DEFAULT_TIMEOUT) { + timeout = USF_TIMEOUT_JIFFIES; + rc = wait_event_timeout( + usf_xx->wait, + (usf_xx->prev_region != + usf_xx->new_region) || + (usf_xx->usf_state != + USF_WORK_STATE), + timeout); + } else { + timeout = upd_tx_info.timeout * HZ; + rc = wait_event_interruptible_timeout( + usf_xx->wait, + (usf_xx->prev_region != + usf_xx->new_region) || + (usf_xx->usf_state != + USF_WORK_STATE), + timeout); + } + } + if (!rc) { + pr_debug("%s: timeout. prev_j=%lu; j=%lu\n", + __func__, prev_jiffies, jiffies); + pr_debug("%s: timeout. prev=%d; new=%d\n", + __func__, usf_xx->prev_region, + usf_xx->new_region); + pr_debug("%s: timeout. free_region=%d;\n", + __func__, upd_tx_info.free_region); + if (usf_xx->prev_region == + usf_xx->new_region) { + pr_err("%s:read data: timeout\n", + __func__); + return -ETIME; + } + } + } + + if ((usf_xx->usf_state != USF_WORK_STATE) || + (rc == -ERESTARTSYS)) { + pr_err("%s: Getting ready region failed " + "work state[%d]; rc[%d]\n", + __func__, usf_xx->usf_state, rc); + return -EINTR; + } + + upd_tx_info.ready_region = usf_xx->new_region; + usf_xx->prev_region = upd_tx_info.ready_region; + + if (upd_tx_info.ready_region == USM_WRONG_TOKEN) { + pr_err("%s: TX path corrupted; prev=%d\n", + __func__, usf_xx->prev_region); + return -EIO; + } + + rc = copy_to_user((void __user *)arg, &upd_tx_info, + sizeof(upd_tx_info)); + if (rc) { + pr_err("%s: copy upd_tx_info to user; rc=%d\n", + __func__, rc); + rc = -EFAULT; + } + + return rc; +} /* usf_get_tx_update */ + +static int usf_set_rx_update(struct usf_xx_type *usf_xx, unsigned long arg) +{ + struct us_rx_update_info_type upd_rx_info; + int rc = copy_from_user(&upd_rx_info, (void *) arg, + sizeof(upd_rx_info)); + + if (rc) { + pr_err("%s: copy upd_rx_info from user; rc=%d\n", + __func__, rc); + return -EFAULT; + } + + /* Send available data regions */ + if (upd_rx_info.ready_region != + usf_xx->buffer_count) { + rc = q6usm_write( + usf_xx->usc, + upd_rx_info.ready_region); + if (rc) + return rc; + } + + /* Get free regions */ + rc = wait_event_timeout( + usf_xx->wait, + !q6usm_is_write_buf_full( + usf_xx->usc, + &upd_rx_info.free_region) || + (usf_xx->usf_state == USF_IDLE_STATE), + USF_TIMEOUT_JIFFIES); + + if (!rc) { + rc = -ETIME; + pr_err("%s:timeout. wait for write buf not full\n", + __func__); + } else { + if (usf_xx->usf_state != + USF_WORK_STATE) { + pr_err("%s: RX: state[%d]\n", + __func__, + usf_xx->usf_state); + rc = -EINTR; + } else { + rc = copy_to_user( + (void __user *)arg, + &upd_rx_info, + sizeof(upd_rx_info)); + if (rc) { + pr_err("%s: copy rx_info to user; rc=%d\n", + __func__, rc); + rc = -EFAULT; + } + } + } + + return rc; +} /* usf_set_rx_update */ + +static void usf_release_input(struct usf_type *usf) +{ + if (usf->input_if != NULL) { + usf_unregister_conflicting_events( + usf->conflicting_event_types); + usf->conflicting_event_types = 0; + input_unregister_device(usf->input_if); + usf->input_if = NULL; + pr_debug("%s input_unregister_device\n", __func__); + } +} /* usf_release_input */ + +static int usf_stop_tx(struct usf_type *usf) +{ + struct usf_xx_type *usf_xx = &usf->usf_tx; + + usf_release_input(usf); + usf_disable(usf_xx); + + return 0; +} /* usf_stop_tx */ + +static int usf_get_version(unsigned long arg) +{ + struct us_version_info_type version_info; + int rc = copy_from_user(&version_info, (void *) arg, + sizeof(version_info)); + + if (rc) { + pr_err("%s: copy version_info from user; rc=%d\n", + __func__, rc); + return -EFAULT; + } + + /* version_info.buf is pointer to place for the version string */ + rc = copy_to_user(version_info.pbuf, + DRV_VERSION, + version_info.buf_size); + if (rc) { + pr_err("%s: copy to version_info.pbuf; rc=%d\n", + __func__, rc); + rc = -EFAULT; + } + + rc = copy_to_user((void __user *)arg, + &version_info, + sizeof(version_info)); + if (rc) { + pr_err("%s: copy version_info to user; rc=%d\n", + __func__, rc); + rc = -EFAULT; + } + + return rc; +} /* usf_get_version */ + +static long usf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + struct usf_type *usf = file->private_data; + struct usf_xx_type *usf_xx = NULL; + + switch (cmd) { + case US_START_TX: { + usf_xx = &usf->usf_tx; + if (usf_xx->usf_state == USF_CONFIGURED_STATE) + rc = usf_start_tx(usf_xx); + else { + pr_err("%s: start_tx: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + return -EBADFD; + } + break; + } + + case US_START_RX: { + usf_xx = &usf->usf_rx; + if (usf_xx->usf_state == USF_CONFIGURED_STATE) + rc = usf_start_rx(usf_xx); + else { + pr_err("%s: start_rx: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + return -EBADFD; + } + break; + } + + case US_SET_TX_INFO: { + usf_xx = &usf->usf_tx; + if (usf_xx->usf_state == USF_OPENED_STATE) + rc = usf_set_tx_info(usf, arg); + else { + pr_err("%s: set_tx_info: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + return -EBADFD; + } + + break; + } /* US_SET_TX_INFO */ + + case US_SET_RX_INFO: { + usf_xx = &usf->usf_rx; + if (usf_xx->usf_state == USF_OPENED_STATE) + rc = usf_set_rx_info(usf, arg); + else { + pr_err("%s: set_rx_info: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + return -EBADFD; + } + + break; + } /* US_SET_RX_INFO */ + + case US_GET_TX_UPDATE: { + struct usf_xx_type *usf_xx = &usf->usf_tx; + if (usf_xx->usf_state == USF_WORK_STATE) + rc = usf_get_tx_update(usf, arg); + else { + pr_err("%s: get_tx_update: wrong state[%d]\n", __func__, + usf_xx->usf_state); + rc = -EBADFD; + } + break; + } /* US_GET_TX_UPDATE */ + + case US_SET_RX_UPDATE: { + struct usf_xx_type *usf_xx = &usf->usf_rx; + if (usf_xx->usf_state == USF_WORK_STATE) + rc = usf_set_rx_update(usf_xx, arg); + else { + pr_err("%s: set_rx_update: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + rc = -EBADFD; + } + break; + } /* US_SET_RX_UPDATE */ + + case US_STOP_TX: { + usf_xx = &usf->usf_tx; + if (usf_xx->usf_state == USF_WORK_STATE) + rc = usf_stop_tx(usf); + else { + pr_err("%s: stop_tx: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + return -EBADFD; + } + break; + } /* US_STOP_TX */ + + case US_STOP_RX: { + usf_xx = &usf->usf_rx; + if (usf_xx->usf_state == USF_WORK_STATE) + usf_disable(usf_xx); + else { + pr_err("%s: stop_rx: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + return -EBADFD; + } + break; + } /* US_STOP_RX */ + + case US_SET_DETECTION: { + struct usf_xx_type *usf_xx = &usf->usf_tx; + if (usf_xx->usf_state == USF_WORK_STATE) + rc = usf_set_us_detection(usf, arg); + else { + pr_err("%s: set us detection: wrong state[%d]\n", + __func__, + usf_xx->usf_state); + rc = -EBADFD; + } + break; + } /* US_SET_DETECTION */ + + case US_GET_VERSION: { + rc = usf_get_version(arg); + break; + } /* US_GET_VERSION */ + + default: + pr_err("%s: unsupported IOCTL command [%d]\n", + __func__, + cmd); + rc = -ENOTTY; + break; + } + + if (rc && + ((cmd == US_SET_TX_INFO) || + (cmd == US_SET_RX_INFO))) + release_xx(usf_xx); + + return rc; +} /* usf_ioctl */ + +static int usf_mmap(struct file *file, struct vm_area_struct *vms) +{ + struct usf_type *usf = file->private_data; + int dir = OUT; + struct usf_xx_type *usf_xx = &usf->usf_tx; + + if (vms->vm_flags & USF_VM_WRITE) { /* RX buf mapping */ + dir = IN; + usf_xx = &usf->usf_rx; + } + + return q6usm_get_virtual_address(dir, usf_xx->usc, vms); +} + +static uint16_t add_opened_dev(int minor) +{ + uint16_t ind = 0; + + for (ind = 0; ind < MAX_DEVS_NUMBER; ++ind) { + if (minor == s_opened_devs[ind]) { + pr_err("%s: device %d is already opened\n", + __func__, minor); + return USF_UNDEF_DEV_ID; + } + + if (s_opened_devs[ind] == 0) { + s_opened_devs[ind] = minor; + pr_debug("%s: device %d is added; ind=%d\n", + __func__, minor, ind); + return ind; + } + } + + pr_err("%s: there is no place for device %d\n", + __func__, minor); + return USF_UNDEF_DEV_ID; +} + +static int usf_open(struct inode *inode, struct file *file) +{ + struct usf_type *usf = NULL; + uint16_t dev_ind = 0; + int minor = MINOR(inode->i_rdev); + + dev_ind = add_opened_dev(minor); + if (dev_ind == USF_UNDEF_DEV_ID) + return -EBUSY; + + usf = kzalloc(sizeof(struct usf_type), GFP_KERNEL); + if (usf == NULL) { + pr_err("%s:usf allocation failed\n", __func__); + return -ENOMEM; + } + + file->private_data = usf; + usf->dev_ind = dev_ind; + + usf->usf_tx.usf_state = USF_OPENED_STATE; + usf->usf_rx.usf_state = USF_OPENED_STATE; + + usf->usf_tx.us_detect_type = USF_US_DETECT_UNDEF; + usf->usf_rx.us_detect_type = USF_US_DETECT_UNDEF; + + pr_debug("%s:usf in open\n", __func__); + return 0; +} + +static int usf_release(struct inode *inode, struct file *file) +{ + struct usf_type *usf = file->private_data; + + pr_debug("%s: release entry\n", __func__); + + usf_release_input(usf); + + usf_disable(&usf->usf_tx); + usf_disable(&usf->usf_rx); + + s_opened_devs[usf->dev_ind] = 0; + + kfree(usf); + pr_debug("%s: release exit\n", __func__); + return 0; +} + +static const struct file_operations usf_fops = { + .owner = THIS_MODULE, + .open = usf_open, + .release = usf_release, + .unlocked_ioctl = usf_ioctl, + .mmap = usf_mmap, +}; + +struct miscdevice usf_misc[MAX_DEVS_NUMBER] = { + { + .minor = MISC_DYNAMIC_MINOR, + .name = "usf1", + .fops = &usf_fops, + }, +}; + +static int __init usf_init(void) +{ + int rc = 0; + uint16_t ind = 0; + + pr_debug("%s: USF SW version %s.\n", __func__, DRV_VERSION); + pr_debug("%s: Max %d devs registration\n", __func__, MAX_DEVS_NUMBER); + + for (ind = 0; ind < MAX_DEVS_NUMBER; ++ind) { + rc = misc_register(&usf_misc[ind]); + if (rc) { + pr_err("%s: misc_register() failed ind=%d; rc = %d\n", + __func__, ind, rc); + break; + } + } + + return rc; +} + +device_initcall(usf_init); + +MODULE_DESCRIPTION("Ultrasound framework driver"); +MODULE_VERSION(DRV_VERSION); diff --git a/arch/arm/mach-msm/qdsp6v2/ultrasound/usfcdev.c b/arch/arm/mach-msm/qdsp6v2/ultrasound/usfcdev.c new file mode 100644 index 0000000000000000000000000000000000000000..b99a9b0494d48371096aea0e03626f49434a74cf --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/ultrasound/usfcdev.c @@ -0,0 +1,236 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "usfcdev.h" + +struct usfcdev_event { + bool (*match_cb)(uint16_t, struct input_dev *dev); + bool registered_event; + bool filter; +}; +static struct usfcdev_event s_usfcdev_events[MAX_EVENT_TYPE_NUM]; + +static bool usfcdev_filter(struct input_handle *handle, + unsigned int type, unsigned int code, int value); +static bool usfcdev_match(struct input_handler *handler, + struct input_dev *dev); +static int usfcdev_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id); +static void usfcdev_disconnect(struct input_handle *handle); + +static const struct input_device_id usfc_tsc_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) }, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) }, + }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(input, usfc_tsc_ids); + +static struct input_handler s_usfc_handlers[MAX_EVENT_TYPE_NUM] = { + { /* TSC handler */ + .filter = usfcdev_filter, + .match = usfcdev_match, + .connect = usfcdev_connect, + .disconnect = usfcdev_disconnect, + /* .minor can be used as index in the container, */ + /* because .fops isn't supported */ + .minor = TSC_EVENT_TYPE_IND, + .name = "usfc_tsc_handler", + .id_table = usfc_tsc_ids, + }, +}; + +/* For each event type, one conflicting device (and handle) is supported */ +static struct input_handle s_usfc_handles[MAX_EVENT_TYPE_NUM] = { + { /* TSC handle */ + .handler = &s_usfc_handlers[TSC_EVENT_TYPE_IND], + .name = "usfc_tsc_handle", + }, +}; + +static bool usfcdev_match(struct input_handler *handler, struct input_dev *dev) +{ + bool rc = false; + int ind = handler->minor; + + pr_debug("%s: name=[%s]; ind=%d\n", __func__, dev->name, ind); + if (s_usfcdev_events[ind].registered_event && + s_usfcdev_events[ind].match_cb) { + rc = (*s_usfcdev_events[ind].match_cb)((uint16_t)ind, dev); + pr_debug("%s: [%s]; rc=%d\n", __func__, dev->name, rc); + } + + return rc; +} + +static int usfcdev_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + int ret = 0; + uint16_t ind = handler->minor; + + s_usfc_handles[ind].dev = dev; + ret = input_register_handle(&s_usfc_handles[ind]); + if (ret) { + pr_err("%s: input_register_handle[%d] failed: ret=%d\n", + __func__, + ind, + ret); + } else { + ret = input_open_device(&s_usfc_handles[ind]); + if (ret) { + pr_err("%s: input_open_device[%d] failed: ret=%d\n", + __func__, + ind, + ret); + input_unregister_handle(&s_usfc_handles[ind]); + } else + pr_debug("%s: device[%d] is opened\n", + __func__, + ind); + } + + return ret; +} + +static void usfcdev_disconnect(struct input_handle *handle) +{ + input_unregister_handle(handle); + pr_debug("%s: handle[%d] is disconnect\n", + __func__, + handle->handler->minor); +} + +static bool usfcdev_filter(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + uint16_t ind = (uint16_t)handle->handler->minor; + + pr_debug("%s: event_type=%d; filter=%d\n", + __func__, + ind, + s_usfcdev_events[ind].filter); + + return s_usfcdev_events[ind].filter; +} + +bool usfcdev_register( + uint16_t event_type_ind, + bool (*match_cb)(uint16_t, struct input_dev *dev)) +{ + int ret = 0; + bool rc = false; + + if ((event_type_ind >= MAX_EVENT_TYPE_NUM) || !match_cb) { + pr_err("%s: wrong input: event_type_ind=%d; match_cb=0x%p\n", + __func__, + event_type_ind, + match_cb); + return false; + } + + if (s_usfcdev_events[event_type_ind].registered_event) { + pr_info("%s: handler[%d] was already registered\n", + __func__, + event_type_ind); + return true; + } + + s_usfcdev_events[event_type_ind].registered_event = true; + s_usfcdev_events[event_type_ind].match_cb = match_cb; + s_usfcdev_events[event_type_ind].filter = false; + ret = input_register_handler(&s_usfc_handlers[event_type_ind]); + if (!ret) { + rc = true; + pr_debug("%s: handler[%d] was registered\n", + __func__, + event_type_ind); + } else { + s_usfcdev_events[event_type_ind].registered_event = false; + s_usfcdev_events[event_type_ind].match_cb = NULL; + pr_err("%s: handler[%d] registration failed: ret=%d\n", + __func__, + event_type_ind, + ret); + } + + return rc; +} + +void usfcdev_unregister(uint16_t event_type_ind) +{ + if (event_type_ind >= MAX_EVENT_TYPE_NUM) { + pr_err("%s: wrong input: event_type_ind=%d\n", + __func__, + event_type_ind); + return; + } + if (s_usfcdev_events[event_type_ind].registered_event) { + input_unregister_handler(&s_usfc_handlers[event_type_ind]); + pr_debug("%s: handler[%d] was unregistered\n", + __func__, + event_type_ind); + s_usfcdev_events[event_type_ind].registered_event = false; + s_usfcdev_events[event_type_ind].match_cb = NULL; + s_usfcdev_events[event_type_ind].filter = false; + } +} + +bool usfcdev_set_filter(uint16_t event_type_ind, bool filter) +{ + bool rc = true; + + if (event_type_ind >= MAX_EVENT_TYPE_NUM) { + pr_err("%s: wrong input: event_type_ind=%d\n", + __func__, + event_type_ind); + return false; + } + + if (s_usfcdev_events[event_type_ind].registered_event) { + s_usfcdev_events[event_type_ind].filter = filter; + pr_debug("%s: event_type[%d]; filter=%d\n", + __func__, + event_type_ind, + filter + ); + } else { + pr_err("%s: event_type[%d] isn't registered\n", + __func__, + event_type_ind); + rc = false; + } + + return rc; +} + +static int __init usfcdev_init(void) +{ + return 0; +} + +device_initcall(usfcdev_init); + +MODULE_DESCRIPTION("Handle of events from devices, conflicting with USF"); diff --git a/arch/arm/mach-msm/qdsp6v2/ultrasound/usfcdev.h b/arch/arm/mach-msm/qdsp6v2/ultrasound/usfcdev.h new file mode 100644 index 0000000000000000000000000000000000000000..042b293cfdbff7042cf96a743e9a2f49444f92d2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/ultrasound/usfcdev.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __USFCDEV_H__ +#define __USFCDEV_H__ + +#include + +/* TSC event type index in the containers of the handlers & handles */ +#define TSC_EVENT_TYPE_IND 0 +/* Number of supported event types to be filtered */ +#define MAX_EVENT_TYPE_NUM 1 + +bool usfcdev_register( + uint16_t event_type_ind, + bool (*match_cb)(uint16_t, struct input_dev *dev)); +void usfcdev_unregister(uint16_t event_type_ind); +bool usfcdev_set_filter(uint16_t event_type_ind, bool filter); +#endif /* __USFCDEV_H__ */ diff --git a/arch/arm/mach-msm/qdss-etb.c b/arch/arm/mach-msm/qdss-etb.c new file mode 100644 index 0000000000000000000000000000000000000000..7837af04453f4cc48c5abe8f639aaf1460818483 --- /dev/null +++ b/arch/arm/mach-msm/qdss-etb.c @@ -0,0 +1,412 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "qdss-priv.h" + +#define etb_writel(etb, val, off) __raw_writel((val), etb.base + off) +#define etb_readl(etb, off) __raw_readl(etb.base + off) + +#define ETB_RAM_DEPTH_REG (0x004) +#define ETB_STATUS_REG (0x00C) +#define ETB_RAM_READ_DATA_REG (0x010) +#define ETB_RAM_READ_POINTER (0x014) +#define ETB_RAM_WRITE_POINTER (0x018) +#define ETB_TRG (0x01C) +#define ETB_CTL_REG (0x020) +#define ETB_RWD_REG (0x024) +#define ETB_FFSR (0x300) +#define ETB_FFCR (0x304) +#define ETB_ITMISCOP0 (0xEE0) +#define ETB_ITTRFLINACK (0xEE4) +#define ETB_ITTRFLIN (0xEE8) +#define ETB_ITATBDATA0 (0xEEC) +#define ETB_ITATBCTR2 (0xEF0) +#define ETB_ITATBCTR1 (0xEF4) +#define ETB_ITATBCTR0 (0xEF8) + + +#define BYTES_PER_WORD 4 +#define ETB_SIZE_WORDS 4096 +#define FRAME_SIZE_WORDS 4 + +#define ETB_LOCK() \ +do { \ + mb(); \ + etb_writel(etb, 0x0, CS_LAR); \ +} while (0) +#define ETB_UNLOCK() \ +do { \ + etb_writel(etb, CS_UNLOCK_MAGIC, CS_LAR); \ + mb(); \ +} while (0) + +struct etb_ctx { + uint8_t *buf; + void __iomem *base; + bool enabled; + bool reading; + spinlock_t spinlock; + atomic_t in_use; + struct device *dev; + struct kobject *kobj; + uint32_t trigger_cntr; +}; + +static struct etb_ctx etb; + +static void __etb_enable(void) +{ + int i; + + ETB_UNLOCK(); + + etb_writel(etb, 0x0, ETB_RAM_WRITE_POINTER); + for (i = 0; i < ETB_SIZE_WORDS; i++) + etb_writel(etb, 0x0, ETB_RWD_REG); + + etb_writel(etb, 0x0, ETB_RAM_WRITE_POINTER); + etb_writel(etb, 0x0, ETB_RAM_READ_POINTER); + + etb_writel(etb, etb.trigger_cntr, ETB_TRG); + etb_writel(etb, BIT(13) | BIT(0), ETB_FFCR); + etb_writel(etb, BIT(0), ETB_CTL_REG); + + ETB_LOCK(); +} + +void etb_enable(void) +{ + unsigned long flags; + + spin_lock_irqsave(&etb.spinlock, flags); + __etb_enable(); + etb.enabled = true; + dev_info(etb.dev, "ETB enabled\n"); + spin_unlock_irqrestore(&etb.spinlock, flags); +} + +static void __etb_disable(void) +{ + int count; + uint32_t ffcr; + + ETB_UNLOCK(); + + ffcr = etb_readl(etb, ETB_FFCR); + ffcr |= (BIT(12) | BIT(6)); + etb_writel(etb, ffcr, ETB_FFCR); + + for (count = TIMEOUT_US; BVAL(etb_readl(etb, ETB_FFCR), 6) != 0 + && count > 0; count--) + udelay(1); + WARN(count == 0, "timeout while flushing ETB, ETB_FFCR: %#x\n", + etb_readl(etb, ETB_FFCR)); + + etb_writel(etb, 0x0, ETB_CTL_REG); + + for (count = TIMEOUT_US; BVAL(etb_readl(etb, ETB_FFSR), 1) != 1 + && count > 0; count--) + udelay(1); + WARN(count == 0, "timeout while disabling ETB, ETB_FFSR: %#x\n", + etb_readl(etb, ETB_FFSR)); + + ETB_LOCK(); +} + +void etb_disable(void) +{ + unsigned long flags; + + spin_lock_irqsave(&etb.spinlock, flags); + __etb_disable(); + etb.enabled = false; + dev_info(etb.dev, "ETB disabled\n"); + spin_unlock_irqrestore(&etb.spinlock, flags); +} + +static void __etb_dump(void) +{ + int i; + uint8_t *buf_ptr; + uint32_t read_data; + uint32_t read_ptr; + uint32_t write_ptr; + uint32_t frame_off; + uint32_t frame_endoff; + + ETB_UNLOCK(); + + read_ptr = etb_readl(etb, ETB_RAM_READ_POINTER); + write_ptr = etb_readl(etb, ETB_RAM_WRITE_POINTER); + + frame_off = write_ptr % FRAME_SIZE_WORDS; + frame_endoff = FRAME_SIZE_WORDS - frame_off; + if (frame_off) { + dev_err(etb.dev, "write_ptr: %lu not aligned to formatter " + "frame size\n", (unsigned long)write_ptr); + dev_err(etb.dev, "frameoff: %lu, frame_endoff: %lu\n", + (unsigned long)frame_off, (unsigned long)frame_endoff); + write_ptr += frame_endoff; + } + + if ((etb_readl(etb, ETB_STATUS_REG) & BIT(0)) == 0) + etb_writel(etb, 0x0, ETB_RAM_READ_POINTER); + else + etb_writel(etb, write_ptr, ETB_RAM_READ_POINTER); + + buf_ptr = etb.buf; + for (i = 0; i < ETB_SIZE_WORDS; i++) { + read_data = etb_readl(etb, ETB_RAM_READ_DATA_REG); + *buf_ptr++ = read_data >> 0; + *buf_ptr++ = read_data >> 8; + *buf_ptr++ = read_data >> 16; + *buf_ptr++ = read_data >> 24; + } + + if (frame_off) { + buf_ptr -= (frame_endoff * BYTES_PER_WORD); + for (i = 0; i < frame_endoff; i++) { + *buf_ptr++ = 0x0; + *buf_ptr++ = 0x0; + *buf_ptr++ = 0x0; + *buf_ptr++ = 0x0; + } + } + + etb_writel(etb, read_ptr, ETB_RAM_READ_POINTER); + + ETB_LOCK(); +} + +void etb_dump(void) +{ + unsigned long flags; + + spin_lock_irqsave(&etb.spinlock, flags); + if (etb.enabled) { + __etb_disable(); + __etb_dump(); + __etb_enable(); + + dev_info(etb.dev, "ETB dumped\n"); + } + spin_unlock_irqrestore(&etb.spinlock, flags); +} + +static int etb_open(struct inode *inode, struct file *file) +{ + if (atomic_cmpxchg(&etb.in_use, 0, 1)) + return -EBUSY; + + dev_dbg(etb.dev, "%s: successfully opened\n", __func__); + return 0; +} + +static ssize_t etb_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + if (etb.reading == false) { + etb_dump(); + etb.reading = true; + } + + if (*ppos + len > ETB_SIZE_WORDS * BYTES_PER_WORD) + len = ETB_SIZE_WORDS * BYTES_PER_WORD - *ppos; + + if (copy_to_user(data, etb.buf + *ppos, len)) { + dev_dbg(etb.dev, "%s: copy_to_user failed\n", __func__); + return -EFAULT; + } + + *ppos += len; + + dev_dbg(etb.dev, "%s: %d bytes copied, %d bytes left\n", + __func__, len, (int) (ETB_SIZE_WORDS * BYTES_PER_WORD - *ppos)); + + return len; +} + +static int etb_release(struct inode *inode, struct file *file) +{ + etb.reading = false; + + atomic_set(&etb.in_use, 0); + + dev_dbg(etb.dev, "%s: released\n", __func__); + + return 0; +} + +static const struct file_operations etb_fops = { + .owner = THIS_MODULE, + .open = etb_open, + .read = etb_read, + .release = etb_release, +}; + +static struct miscdevice etb_misc = { + .name = "msm_etb", + .minor = MISC_DYNAMIC_MINOR, + .fops = &etb_fops, +}; + +#define ETB_ATTR(__name) \ +static struct kobj_attribute __name##_attr = \ + __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store) + +static ssize_t trigger_cntr_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + etb.trigger_cntr = val; + return n; +} +static ssize_t trigger_cntr_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val = etb.trigger_cntr; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETB_ATTR(trigger_cntr); + +static int __devinit etb_sysfs_init(void) +{ + int ret; + + etb.kobj = kobject_create_and_add("etb", qdss_get_modulekobj()); + if (!etb.kobj) { + dev_err(etb.dev, "failed to create ETB sysfs kobject\n"); + ret = -ENOMEM; + goto err_create; + } + + ret = sysfs_create_file(etb.kobj, &trigger_cntr_attr.attr); + if (ret) { + dev_err(etb.dev, "failed to create ETB sysfs trigger_cntr" + " attribute\n"); + goto err_file; + } + + return 0; +err_file: + kobject_put(etb.kobj); +err_create: + return ret; +} + +static void __devexit etb_sysfs_exit(void) +{ + sysfs_remove_file(etb.kobj, &trigger_cntr_attr.attr); + kobject_put(etb.kobj); +} + +static int __devinit etb_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EINVAL; + goto err_res; + } + + etb.base = ioremap_nocache(res->start, resource_size(res)); + if (!etb.base) { + ret = -EINVAL; + goto err_ioremap; + } + + etb.dev = &pdev->dev; + + spin_lock_init(&etb.spinlock); + + ret = misc_register(&etb_misc); + if (ret) + goto err_misc; + + etb.buf = kzalloc(ETB_SIZE_WORDS * BYTES_PER_WORD, GFP_KERNEL); + if (!etb.buf) { + ret = -ENOMEM; + goto err_alloc; + } + + etb_sysfs_init(); + + dev_info(etb.dev, "ETB initialized\n"); + return 0; + +err_alloc: + misc_deregister(&etb_misc); +err_misc: + iounmap(etb.base); +err_ioremap: +err_res: + dev_err(etb.dev, "ETB init failed\n"); + return ret; +} + +static int __devexit etb_remove(struct platform_device *pdev) +{ + if (etb.enabled) + etb_disable(); + etb_sysfs_exit(); + kfree(etb.buf); + misc_deregister(&etb_misc); + iounmap(etb.base); + + return 0; +} + +static struct platform_driver etb_driver = { + .probe = etb_probe, + .remove = __devexit_p(etb_remove), + .driver = { + .name = "msm_etb", + }, +}; + +static int __init etb_init(void) +{ + return platform_driver_register(&etb_driver); +} +module_init(etb_init); + +static void __exit etb_exit(void) +{ + platform_driver_unregister(&etb_driver); +} +module_exit(etb_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Embedded Trace Buffer driver"); diff --git a/arch/arm/mach-msm/qdss-etm.c b/arch/arm/mach-msm/qdss-etm.c new file mode 100644 index 0000000000000000000000000000000000000000..ca6e0c6af8b3654af1874274c6f1dff03cba517b --- /dev/null +++ b/arch/arm/mach-msm/qdss-etm.c @@ -0,0 +1,1329 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "qdss-priv.h" + +#define etm_writel(etm, cpu, val, off) \ + __raw_writel((val), etm.base + (SZ_4K * cpu) + off) +#define etm_readl(etm, cpu, off) \ + __raw_readl(etm.base + (SZ_4K * cpu) + off) + +/* + * Device registers: + * 0x000 - 0x2FC: Trace registers + * 0x300 - 0x314: Management registers + * 0x318 - 0xEFC: Trace registers + * + * Coresight registers + * 0xF00 - 0xF9C: Management registers + * 0xFA0 - 0xFA4: Management registers in PFTv1.0 + * Trace registers in PFTv1.1 + * 0xFA8 - 0xFFC: Management registers + */ + +/* Trace registers (0x000-0x2FC) */ +#define ETMCR (0x000) +#define ETMCCR (0x004) +#define ETMTRIGGER (0x008) +#define ETMSR (0x010) +#define ETMSCR (0x014) +#define ETMTSSCR (0x018) +#define ETMTEEVR (0x020) +#define ETMTECR1 (0x024) +#define ETMFFLR (0x02C) +#define ETMACVRn(n) (0x040 + (n * 4)) +#define ETMACTRn(n) (0x080 + (n * 4)) +#define ETMCNTRLDVRn(n) (0x140 + (n * 4)) +#define ETMCNTENRn(n) (0x150 + (n * 4)) +#define ETMCNTRLDEVRn(n) (0x160 + (n * 4)) +#define ETMCNTVRn(n) (0x170 + (n * 4)) +#define ETMSQ12EVR (0x180) +#define ETMSQ21EVR (0x184) +#define ETMSQ23EVR (0x188) +#define ETMSQ31EVR (0x18C) +#define ETMSQ32EVR (0x190) +#define ETMSQ13EVR (0x194) +#define ETMSQR (0x19C) +#define ETMEXTOUTEVRn(n) (0x1A0 + (n * 4)) +#define ETMCIDCVRn(n) (0x1B0 + (n * 4)) +#define ETMCIDCMR (0x1BC) +#define ETMIMPSPEC0 (0x1C0) +#define ETMIMPSPEC1 (0x1C4) +#define ETMIMPSPEC2 (0x1C8) +#define ETMIMPSPEC3 (0x1CC) +#define ETMIMPSPEC4 (0x1D0) +#define ETMIMPSPEC5 (0x1D4) +#define ETMIMPSPEC6 (0x1D8) +#define ETMIMPSPEC7 (0x1DC) +#define ETMSYNCFR (0x1E0) +#define ETMIDR (0x1E4) +#define ETMCCER (0x1E8) +#define ETMEXTINSELR (0x1EC) +#define ETMTESSEICR (0x1F0) +#define ETMEIBCR (0x1F4) +#define ETMTSEVR (0x1F8) +#define ETMAUXCR (0x1FC) +#define ETMTRACEIDR (0x200) +#define ETMVMIDCVR (0x240) +/* Management registers (0x300-0x314) */ +#define ETMOSLAR (0x300) +#define ETMOSLSR (0x304) +#define ETMOSSRR (0x308) +#define ETMPDCR (0x310) +#define ETMPDSR (0x314) + +#define ETM_MAX_ADDR_CMP (16) +#define ETM_MAX_CNTR (4) +#define ETM_MAX_CTXID_CMP (3) + +#define ETM_MODE_EXCLUDE BIT(0) +#define ETM_MODE_CYCACC BIT(1) +#define ETM_MODE_STALL BIT(2) +#define ETM_MODE_TIMESTAMP BIT(3) +#define ETM_MODE_CTXID BIT(4) +#define ETM_MODE_ALL (0x1F) + +#define ETM_EVENT_MASK (0x1FFFF) +#define ETM_SYNC_MASK (0xFFF) +#define ETM_ALL_MASK (0xFFFFFFFF) + +#define ETM_SEQ_STATE_MAX_VAL (0x2) + +enum { + ETM_ADDR_TYPE_NONE, + ETM_ADDR_TYPE_SINGLE, + ETM_ADDR_TYPE_RANGE, + ETM_ADDR_TYPE_START, + ETM_ADDR_TYPE_STOP, +}; + +#define ETM_LOCK(cpu) \ +do { \ + mb(); \ + etm_writel(etm, cpu, 0x0, CS_LAR); \ +} while (0) +#define ETM_UNLOCK(cpu) \ +do { \ + etm_writel(etm, cpu, CS_UNLOCK_MAGIC, CS_LAR); \ + mb(); \ +} while (0) + + +#ifdef MODULE_PARAM_PREFIX +#undef MODULE_PARAM_PREFIX +#endif +#define MODULE_PARAM_PREFIX "qdss." + +#ifdef CONFIG_MSM_QDSS_ETM_DEFAULT_ENABLE +static int etm_boot_enable = 1; +#else +static int etm_boot_enable; +#endif +module_param_named( + etm_boot_enable, etm_boot_enable, int, S_IRUGO +); + +struct etm_ctx { + void __iomem *base; + bool enabled; + struct wake_lock wake_lock; + struct pm_qos_request qos_req; + struct qdss_source *src; + struct mutex mutex; + struct device *dev; + struct kobject *kobj; + uint8_t arch; + uint8_t nr_addr_cmp; + uint8_t nr_cntr; + uint8_t nr_ext_inp; + uint8_t nr_ext_out; + uint8_t nr_ctxid_cmp; + uint8_t reset; + uint32_t mode; + uint32_t ctrl; + uint32_t trigger_event; + uint32_t startstop_ctrl; + uint32_t enable_event; + uint32_t enable_ctrl1; + uint32_t fifofull_level; + uint8_t addr_idx; + uint32_t addr_val[ETM_MAX_ADDR_CMP]; + uint32_t addr_acctype[ETM_MAX_ADDR_CMP]; + uint32_t addr_type[ETM_MAX_ADDR_CMP]; + uint8_t cntr_idx; + uint32_t cntr_rld_val[ETM_MAX_CNTR]; + uint32_t cntr_event[ETM_MAX_CNTR]; + uint32_t cntr_rld_event[ETM_MAX_CNTR]; + uint32_t cntr_val[ETM_MAX_CNTR]; + uint32_t seq_12_event; + uint32_t seq_21_event; + uint32_t seq_23_event; + uint32_t seq_31_event; + uint32_t seq_32_event; + uint32_t seq_13_event; + uint32_t seq_curr_state; + uint8_t ctxid_idx; + uint32_t ctxid_val[ETM_MAX_CTXID_CMP]; + uint32_t ctxid_mask; + uint32_t sync_freq; + uint32_t timestamp_event; +}; + +static struct etm_ctx etm = { + .trigger_event = 0x406F, + .enable_event = 0x6F, + .enable_ctrl1 = 0x1, + .fifofull_level = 0x28, + .addr_val = {(uint32_t) _stext, (uint32_t) _etext}, + .addr_type = {ETM_ADDR_TYPE_RANGE, ETM_ADDR_TYPE_RANGE}, + .cntr_event = {[0 ... (ETM_MAX_CNTR - 1)] = 0x406F}, + .cntr_rld_event = {[0 ... (ETM_MAX_CNTR - 1)] = 0x406F}, + .seq_12_event = 0x406F, + .seq_21_event = 0x406F, + .seq_23_event = 0x406F, + .seq_31_event = 0x406F, + .seq_32_event = 0x406F, + .seq_13_event = 0x406F, + .sync_freq = 0x80, + .timestamp_event = 0x406F, +}; + + +/* ETM clock is derived from the processor clock and gets enabled on a + * logical OR of below items on Krait (pass2 onwards): + * 1.CPMR[ETMCLKEN] is 1 + * 2.ETMCR[PD] is 0 + * 3.ETMPDCR[PU] is 1 + * 4.Reset is asserted (core or debug) + * 5.APB memory mapped requests (eg. EDAP access) + * + * 1., 2. and 3. above are permanent enables whereas 4. and 5. are temporary + * enables + * + * We rely on 5. to be able to access ETMCR and then use 2. above for ETM + * clock vote in the driver and the save-restore code uses 1. above + * for its vote + */ +static void etm_set_pwrdwn(int cpu) +{ + uint32_t etmcr; + + etmcr = etm_readl(etm, cpu, ETMCR); + etmcr |= BIT(0); + etm_writel(etm, cpu, etmcr, ETMCR); +} + +static void etm_clr_pwrdwn(int cpu) +{ + uint32_t etmcr; + + etmcr = etm_readl(etm, cpu, ETMCR); + etmcr &= ~BIT(0); + etm_writel(etm, cpu, etmcr, ETMCR); +} + +static void etm_set_prog(int cpu) +{ + uint32_t etmcr; + int count; + + etmcr = etm_readl(etm, cpu, ETMCR); + etmcr |= BIT(10); + etm_writel(etm, cpu, etmcr, ETMCR); + + for (count = TIMEOUT_US; BVAL(etm_readl(etm, cpu, ETMSR), 1) != 1 + && count > 0; count--) + udelay(1); + WARN(count == 0, "timeout while setting prog bit, ETMSR: %#x\n", + etm_readl(etm, cpu, ETMSR)); +} + +static void etm_clr_prog(int cpu) +{ + uint32_t etmcr; + int count; + + etmcr = etm_readl(etm, cpu, ETMCR); + etmcr &= ~BIT(10); + etm_writel(etm, cpu, etmcr, ETMCR); + + for (count = TIMEOUT_US; BVAL(etm_readl(etm, cpu, ETMSR), 1) != 0 + && count > 0; count--) + udelay(1); + WARN(count == 0, "timeout while clearing prog bit, ETMSR: %#x\n", + etm_readl(etm, cpu, ETMSR)); +} + +static void __etm_enable(int cpu) +{ + int i; + + ETM_UNLOCK(cpu); + /* Vote for ETM power/clock enable */ + etm_clr_pwrdwn(cpu); + etm_set_prog(cpu); + + etm_writel(etm, cpu, etm.ctrl | BIT(10), ETMCR); + etm_writel(etm, cpu, etm.trigger_event, ETMTRIGGER); + etm_writel(etm, cpu, etm.startstop_ctrl, ETMTSSCR); + etm_writel(etm, cpu, etm.enable_event, ETMTEEVR); + etm_writel(etm, cpu, etm.enable_ctrl1, ETMTECR1); + etm_writel(etm, cpu, etm.fifofull_level, ETMFFLR); + for (i = 0; i < etm.nr_addr_cmp; i++) { + etm_writel(etm, cpu, etm.addr_val[i], ETMACVRn(i)); + etm_writel(etm, cpu, etm.addr_acctype[i], ETMACTRn(i)); + } + for (i = 0; i < etm.nr_cntr; i++) { + etm_writel(etm, cpu, etm.cntr_rld_val[i], ETMCNTRLDVRn(i)); + etm_writel(etm, cpu, etm.cntr_event[i], ETMCNTENRn(i)); + etm_writel(etm, cpu, etm.cntr_rld_event[i], ETMCNTRLDEVRn(i)); + etm_writel(etm, cpu, etm.cntr_val[i], ETMCNTVRn(i)); + } + etm_writel(etm, cpu, etm.seq_12_event, ETMSQ12EVR); + etm_writel(etm, cpu, etm.seq_21_event, ETMSQ21EVR); + etm_writel(etm, cpu, etm.seq_23_event, ETMSQ23EVR); + etm_writel(etm, cpu, etm.seq_31_event, ETMSQ31EVR); + etm_writel(etm, cpu, etm.seq_32_event, ETMSQ32EVR); + etm_writel(etm, cpu, etm.seq_13_event, ETMSQ13EVR); + etm_writel(etm, cpu, etm.seq_curr_state, ETMSQR); + for (i = 0; i < etm.nr_ext_out; i++) + etm_writel(etm, cpu, 0x0000406F, ETMEXTOUTEVRn(i)); + for (i = 0; i < etm.nr_ctxid_cmp; i++) + etm_writel(etm, cpu, etm.ctxid_val[i], ETMCIDCVRn(i)); + etm_writel(etm, cpu, etm.ctxid_mask, ETMCIDCMR); + etm_writel(etm, cpu, etm.sync_freq, ETMSYNCFR); + etm_writel(etm, cpu, 0x00000000, ETMEXTINSELR); + etm_writel(etm, cpu, etm.timestamp_event, ETMTSEVR); + etm_writel(etm, cpu, 0x00000000, ETMAUXCR); + etm_writel(etm, cpu, cpu+1, ETMTRACEIDR); + etm_writel(etm, cpu, 0x00000000, ETMVMIDCVR); + + etm_clr_prog(cpu); + ETM_LOCK(cpu); +} + +static int etm_enable(void) +{ + int ret, cpu; + + if (etm.enabled) { + dev_err(etm.dev, "ETM tracing already enabled\n"); + ret = -EPERM; + goto err; + } + + wake_lock(&etm.wake_lock); + /* 1. causes all online cpus to come out of idle PC + * 2. prevents idle PC until save restore flag is enabled atomically + * + * we rely on the user to prevent hotplug on/off racing with this + * operation and to ensure cores where trace is expected to be turned + * on are already hotplugged on + */ + pm_qos_update_request(&etm.qos_req, 0); + + ret = qdss_enable(etm.src); + if (ret) + goto err_qdss; + + for_each_online_cpu(cpu) + __etm_enable(cpu); + + etm.enabled = true; + + pm_qos_update_request(&etm.qos_req, PM_QOS_DEFAULT_VALUE); + wake_unlock(&etm.wake_lock); + + dev_info(etm.dev, "ETM tracing enabled\n"); + return 0; + +err_qdss: + pm_qos_update_request(&etm.qos_req, PM_QOS_DEFAULT_VALUE); + wake_unlock(&etm.wake_lock); +err: + return ret; +} + +static void __etm_disable(int cpu) +{ + ETM_UNLOCK(cpu); + etm_set_prog(cpu); + + /* program trace enable to low by using always false event */ + etm_writel(etm, cpu, 0x6F | BIT(14), ETMTEEVR); + + /* Vote for ETM power/clock disable */ + etm_set_pwrdwn(cpu); + ETM_LOCK(cpu); +} + +static int etm_disable(void) +{ + int ret, cpu; + + if (!etm.enabled) { + dev_err(etm.dev, "ETM tracing already disabled\n"); + ret = -EPERM; + goto err; + } + + wake_lock(&etm.wake_lock); + /* 1. causes all online cpus to come out of idle PC + * 2. prevents idle PC until save restore flag is disabled atomically + * + * we rely on the user to prevent hotplug on/off racing with this + * operation and to ensure cores where trace is expected to be turned + * off are already hotplugged on + */ + pm_qos_update_request(&etm.qos_req, 0); + + for_each_online_cpu(cpu) + __etm_disable(cpu); + + qdss_disable(etm.src); + + etm.enabled = false; + + pm_qos_update_request(&etm.qos_req, PM_QOS_DEFAULT_VALUE); + wake_unlock(&etm.wake_lock); + + dev_info(etm.dev, "ETM tracing disabled\n"); + return 0; +err: + return ret; +} + +/* Memory mapped writes to clear os lock not supported */ +static void etm_os_unlock(void *unused) +{ + unsigned long value = 0x0; + + asm("mcr p14, 1, %0, c1, c0, 4\n\t" : : "r" (value)); + asm("isb\n\t"); +} + +#define ETM_STORE(__name, mask) \ +static ssize_t __name##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t n) \ +{ \ + unsigned long val; \ + \ + if (sscanf(buf, "%lx", &val) != 1) \ + return -EINVAL; \ + \ + etm.__name = val & mask; \ + return n; \ +} + +#define ETM_SHOW(__name) \ +static ssize_t __name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + char *buf) \ +{ \ + unsigned long val = etm.__name; \ + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); \ +} + +#define ETM_ATTR(__name) \ +static struct kobj_attribute __name##_attr = \ + __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store) +#define ETM_ATTR_RO(__name) \ +static struct kobj_attribute __name##_attr = \ + __ATTR(__name, S_IRUGO, __name##_show, NULL) + +static ssize_t enabled_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int ret = 0; + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + if (val) + ret = etm_enable(); + else + ret = etm_disable(); + mutex_unlock(&etm.mutex); + + if (ret) + return ret; + return n; +} +ETM_SHOW(enabled); +ETM_ATTR(enabled); + +ETM_SHOW(nr_addr_cmp); +ETM_ATTR_RO(nr_addr_cmp); +ETM_SHOW(nr_cntr); +ETM_ATTR_RO(nr_cntr); +ETM_SHOW(nr_ctxid_cmp); +ETM_ATTR_RO(nr_ctxid_cmp); + +/* Reset to trace everything i.e. exclude nothing. */ +static ssize_t reset_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int i; + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + if (val) { + etm.mode = ETM_MODE_EXCLUDE; + etm.ctrl = 0x0; + if (cpu_is_krait_v1()) { + etm.mode |= ETM_MODE_CYCACC; + etm.ctrl |= BIT(12); + } + etm.trigger_event = 0x406F; + etm.startstop_ctrl = 0x0; + etm.enable_event = 0x6F; + etm.enable_ctrl1 = 0x1000000; + etm.fifofull_level = 0x28; + etm.addr_idx = 0x0; + for (i = 0; i < etm.nr_addr_cmp; i++) { + etm.addr_val[i] = 0x0; + etm.addr_acctype[i] = 0x0; + etm.addr_type[i] = ETM_ADDR_TYPE_NONE; + } + etm.cntr_idx = 0x0; + for (i = 0; i < etm.nr_cntr; i++) { + etm.cntr_rld_val[i] = 0x0; + etm.cntr_event[i] = 0x406F; + etm.cntr_rld_event[i] = 0x406F; + etm.cntr_val[i] = 0x0; + } + etm.seq_12_event = 0x406F; + etm.seq_21_event = 0x406F; + etm.seq_23_event = 0x406F; + etm.seq_31_event = 0x406F; + etm.seq_32_event = 0x406F; + etm.seq_13_event = 0x406F; + etm.seq_curr_state = 0x0; + etm.ctxid_idx = 0x0; + for (i = 0; i < etm.nr_ctxid_cmp; i++) + etm.ctxid_val[i] = 0x0; + etm.ctxid_mask = 0x0; + etm.sync_freq = 0x80; + etm.timestamp_event = 0x406F; + } + mutex_unlock(&etm.mutex); + return n; +} +ETM_SHOW(reset); +ETM_ATTR(reset); + +static ssize_t mode_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.mode = val & ETM_MODE_ALL; + + if (etm.mode & ETM_MODE_EXCLUDE) + etm.enable_ctrl1 |= BIT(24); + else + etm.enable_ctrl1 &= ~BIT(24); + + if (etm.mode & ETM_MODE_CYCACC) + etm.ctrl |= BIT(12); + else + etm.ctrl &= ~BIT(12); + + if (etm.mode & ETM_MODE_STALL) + etm.ctrl |= BIT(7); + else + etm.ctrl &= ~BIT(7); + + if (etm.mode & ETM_MODE_TIMESTAMP) + etm.ctrl |= BIT(28); + else + etm.ctrl &= ~BIT(28); + if (etm.mode & ETM_MODE_CTXID) + etm.ctrl |= (BIT(14) | BIT(15)); + else + etm.ctrl &= ~(BIT(14) | BIT(15)); + mutex_unlock(&etm.mutex); + + return n; +} +ETM_SHOW(mode); +ETM_ATTR(mode); + +ETM_STORE(trigger_event, ETM_EVENT_MASK); +ETM_SHOW(trigger_event); +ETM_ATTR(trigger_event); + +ETM_STORE(enable_event, ETM_EVENT_MASK); +ETM_SHOW(enable_event); +ETM_ATTR(enable_event); + +ETM_STORE(fifofull_level, ETM_ALL_MASK); +ETM_SHOW(fifofull_level); +ETM_ATTR(fifofull_level); + +static ssize_t addr_idx_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + if (val >= etm.nr_addr_cmp) + return -EINVAL; + + /* Use mutex to ensure index doesn't change while it gets dereferenced + * multiple times within a mutex block elsewhere. + */ + mutex_lock(&etm.mutex); + etm.addr_idx = val; + mutex_unlock(&etm.mutex); + return n; +} +ETM_SHOW(addr_idx); +ETM_ATTR(addr_idx); + +static ssize_t addr_single_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + uint8_t idx; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE || + etm.addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + etm.addr_val[idx] = val; + etm.addr_type[idx] = ETM_ADDR_TYPE_SINGLE; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t addr_single_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + uint8_t idx; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE || + etm.addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + val = etm.addr_val[idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(addr_single); + +static ssize_t addr_range_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val1, val2; + uint8_t idx; + + if (sscanf(buf, "%lx %lx", &val1, &val2) != 2) + return -EINVAL; + /* lower address comparator cannot have a higher address value */ + if (val1 > val2) + return -EINVAL; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (idx % 2 != 0) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + if (!((etm.addr_type[idx] == ETM_ADDR_TYPE_NONE && + etm.addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (etm.addr_type[idx] == ETM_ADDR_TYPE_RANGE && + etm.addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + etm.addr_val[idx] = val1; + etm.addr_type[idx] = ETM_ADDR_TYPE_RANGE; + etm.addr_val[idx + 1] = val2; + etm.addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; + etm.enable_ctrl1 |= (1 << (idx/2)); + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t addr_range_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val1, val2; + uint8_t idx; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (idx % 2 != 0) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + if (!((etm.addr_type[idx] == ETM_ADDR_TYPE_NONE && + etm.addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (etm.addr_type[idx] == ETM_ADDR_TYPE_RANGE && + etm.addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + val1 = etm.addr_val[idx]; + val2 = etm.addr_val[idx + 1]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2); +} +ETM_ATTR(addr_range); + +static ssize_t addr_start_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + uint8_t idx; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE || + etm.addr_type[idx] == ETM_ADDR_TYPE_START)) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + etm.addr_val[idx] = val; + etm.addr_type[idx] = ETM_ADDR_TYPE_START; + etm.startstop_ctrl |= (1 << idx); + etm.enable_ctrl1 |= BIT(25); + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t addr_start_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + uint8_t idx; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE || + etm.addr_type[idx] == ETM_ADDR_TYPE_START)) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + val = etm.addr_val[idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(addr_start); + +static ssize_t addr_stop_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + uint8_t idx; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE || + etm.addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + etm.addr_val[idx] = val; + etm.addr_type[idx] = ETM_ADDR_TYPE_STOP; + etm.startstop_ctrl |= (1 << (idx + 16)); + etm.enable_ctrl1 |= BIT(25); + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t addr_stop_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + uint8_t idx; + + mutex_lock(&etm.mutex); + idx = etm.addr_idx; + if (!(etm.addr_type[idx] == ETM_ADDR_TYPE_NONE || + etm.addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + mutex_unlock(&etm.mutex); + return -EPERM; + } + + val = etm.addr_val[idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(addr_stop); + +static ssize_t addr_acctype_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.addr_acctype[etm.addr_idx] = val; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t addr_acctype_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + + mutex_lock(&etm.mutex); + val = etm.addr_acctype[etm.addr_idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(addr_acctype); + +static ssize_t cntr_idx_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + if (val >= etm.nr_cntr) + return -EINVAL; + + /* Use mutex to ensure index doesn't change while it gets dereferenced + * multiple times within a mutex block elsewhere. + */ + mutex_lock(&etm.mutex); + etm.cntr_idx = val; + mutex_unlock(&etm.mutex); + return n; +} +ETM_SHOW(cntr_idx); +ETM_ATTR(cntr_idx); + +static ssize_t cntr_rld_val_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.cntr_rld_val[etm.cntr_idx] = val; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t cntr_rld_val_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + mutex_lock(&etm.mutex); + val = etm.cntr_rld_val[etm.cntr_idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(cntr_rld_val); + +static ssize_t cntr_event_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.cntr_event[etm.cntr_idx] = val & ETM_EVENT_MASK; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t cntr_event_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + + mutex_lock(&etm.mutex); + val = etm.cntr_event[etm.cntr_idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(cntr_event); + +static ssize_t cntr_rld_event_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.cntr_rld_event[etm.cntr_idx] = val & ETM_EVENT_MASK; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t cntr_rld_event_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + + mutex_lock(&etm.mutex); + val = etm.cntr_rld_event[etm.cntr_idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(cntr_rld_event); + +static ssize_t cntr_val_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.cntr_val[etm.cntr_idx] = val; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t cntr_val_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + + mutex_lock(&etm.mutex); + val = etm.cntr_val[etm.cntr_idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(cntr_val); + +ETM_STORE(seq_12_event, ETM_EVENT_MASK); +ETM_SHOW(seq_12_event); +ETM_ATTR(seq_12_event); + +ETM_STORE(seq_21_event, ETM_EVENT_MASK); +ETM_SHOW(seq_21_event); +ETM_ATTR(seq_21_event); + +ETM_STORE(seq_23_event, ETM_EVENT_MASK); +ETM_SHOW(seq_23_event); +ETM_ATTR(seq_23_event); + +ETM_STORE(seq_31_event, ETM_EVENT_MASK); +ETM_SHOW(seq_31_event); +ETM_ATTR(seq_31_event); + +ETM_STORE(seq_32_event, ETM_EVENT_MASK); +ETM_SHOW(seq_32_event); +ETM_ATTR(seq_32_event); + +ETM_STORE(seq_13_event, ETM_EVENT_MASK); +ETM_SHOW(seq_13_event); +ETM_ATTR(seq_13_event); + +static ssize_t seq_curr_state_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + if (val > ETM_SEQ_STATE_MAX_VAL) + return -EINVAL; + + etm.seq_curr_state = val; + return n; +} +ETM_SHOW(seq_curr_state); +ETM_ATTR(seq_curr_state); + +static ssize_t ctxid_idx_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + if (val >= etm.nr_ctxid_cmp) + return -EINVAL; + + /* Use mutex to ensure index doesn't change while it gets dereferenced + * multiple times within a mutex block elsewhere. + */ + mutex_lock(&etm.mutex); + etm.ctxid_idx = val; + mutex_unlock(&etm.mutex); + return n; +} +ETM_SHOW(ctxid_idx); +ETM_ATTR(ctxid_idx); + +static ssize_t ctxid_val_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + mutex_lock(&etm.mutex); + etm.ctxid_val[etm.ctxid_idx] = val; + mutex_unlock(&etm.mutex); + return n; +} +static ssize_t ctxid_val_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val; + + mutex_lock(&etm.mutex); + val = etm.ctxid_val[etm.ctxid_idx]; + mutex_unlock(&etm.mutex); + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +ETM_ATTR(ctxid_val); + +ETM_STORE(ctxid_mask, ETM_ALL_MASK); +ETM_SHOW(ctxid_mask); +ETM_ATTR(ctxid_mask); + +ETM_STORE(sync_freq, ETM_SYNC_MASK); +ETM_SHOW(sync_freq); +ETM_ATTR(sync_freq); + +ETM_STORE(timestamp_event, ETM_EVENT_MASK); +ETM_SHOW(timestamp_event); +ETM_ATTR(timestamp_event); + +static struct attribute *etm_attrs[] = { + &nr_addr_cmp_attr.attr, + &nr_cntr_attr.attr, + &nr_ctxid_cmp_attr.attr, + &reset_attr.attr, + &mode_attr.attr, + &trigger_event_attr.attr, + &enable_event_attr.attr, + &fifofull_level_attr.attr, + &addr_idx_attr.attr, + &addr_single_attr.attr, + &addr_range_attr.attr, + &addr_start_attr.attr, + &addr_stop_attr.attr, + &addr_acctype_attr.attr, + &cntr_idx_attr.attr, + &cntr_rld_val_attr.attr, + &cntr_event_attr.attr, + &cntr_rld_event_attr.attr, + &cntr_val_attr.attr, + &seq_12_event_attr.attr, + &seq_21_event_attr.attr, + &seq_23_event_attr.attr, + &seq_31_event_attr.attr, + &seq_32_event_attr.attr, + &seq_13_event_attr.attr, + &seq_curr_state_attr.attr, + &ctxid_idx_attr.attr, + &ctxid_val_attr.attr, + &ctxid_mask_attr.attr, + &sync_freq_attr.attr, + ×tamp_event_attr.attr, + NULL, +}; + +static struct attribute_group etm_attr_grp = { + .attrs = etm_attrs, +}; + +static int __devinit etm_sysfs_init(void) +{ + int ret; + + etm.kobj = kobject_create_and_add("etm", qdss_get_modulekobj()); + if (!etm.kobj) { + dev_err(etm.dev, "failed to create ETM sysfs kobject\n"); + ret = -ENOMEM; + goto err_create; + } + + ret = sysfs_create_file(etm.kobj, &enabled_attr.attr); + if (ret) { + dev_err(etm.dev, "failed to create ETM sysfs enabled" + " attribute\n"); + goto err_file; + } + + if (sysfs_create_group(etm.kobj, &etm_attr_grp)) + dev_err(etm.dev, "failed to create ETM sysfs group\n"); + + return 0; +err_file: + kobject_put(etm.kobj); +err_create: + return ret; +} + +static void __devexit etm_sysfs_exit(void) +{ + sysfs_remove_group(etm.kobj, &etm_attr_grp); + sysfs_remove_file(etm.kobj, &enabled_attr.attr); + kobject_put(etm.kobj); +} + +static bool __devinit etm_arch_supported(uint8_t arch) +{ + switch (arch) { + case PFT_ARCH_V1_1: + break; + default: + return false; + } + return true; +} + +static int __devinit etm_arch_init(void) +{ + int ret, i; + /* use cpu 0 for setup */ + int cpu = 0; + uint32_t etmidr; + uint32_t etmccr; + + /* Unlock OS lock first to allow memory mapped reads and writes */ + etm_os_unlock(NULL); + smp_call_function(etm_os_unlock, NULL, 1); + ETM_UNLOCK(cpu); + /* Vote for ETM power/clock enable */ + etm_clr_pwrdwn(cpu); + /* Set prog bit. It will be set from reset but this is included to + * ensure it is set + */ + etm_set_prog(cpu); + + /* find all capabilities */ + etmidr = etm_readl(etm, cpu, ETMIDR); + etm.arch = BMVAL(etmidr, 4, 11); + if (etm_arch_supported(etm.arch) == false) { + ret = -EINVAL; + goto err; + } + + etmccr = etm_readl(etm, cpu, ETMCCR); + etm.nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2; + etm.nr_cntr = BMVAL(etmccr, 13, 15); + etm.nr_ext_inp = BMVAL(etmccr, 17, 19); + etm.nr_ext_out = BMVAL(etmccr, 20, 22); + etm.nr_ctxid_cmp = BMVAL(etmccr, 24, 25); + + if (cpu_is_krait_v1()) { + /* Krait pass1 doesn't support include filtering and non-cycle + * accurate tracing + */ + etm.mode = (ETM_MODE_EXCLUDE | ETM_MODE_CYCACC); + etm.ctrl = 0x1000; + etm.enable_ctrl1 = 0x1000000; + for (i = 0; i < etm.nr_addr_cmp; i++) { + etm.addr_val[i] = 0x0; + etm.addr_acctype[i] = 0x0; + etm.addr_type[i] = ETM_ADDR_TYPE_NONE; + } + } + + /* Vote for ETM power/clock disable */ + etm_set_pwrdwn(cpu); + ETM_LOCK(cpu); + + return 0; +err: + return ret; +} + +static int __devinit etm_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EINVAL; + goto err_res; + } + + etm.base = ioremap_nocache(res->start, resource_size(res)); + if (!etm.base) { + ret = -EINVAL; + goto err_ioremap; + } + + etm.dev = &pdev->dev; + + mutex_init(&etm.mutex); + 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); + etm.src = qdss_get("msm_etm"); + if (IS_ERR(etm.src)) { + ret = PTR_ERR(etm.src); + goto err_qdssget; + } + + ret = qdss_clk_enable(); + if (ret) + goto err_clk; + + ret = etm_arch_init(); + if (ret) + goto err_arch; + + ret = etm_sysfs_init(); + if (ret) + goto err_sysfs; + + etm.enabled = false; + + qdss_clk_disable(); + + dev_info(etm.dev, "ETM initialized\n"); + + if (etm_boot_enable) + etm_enable(); + + return 0; + +err_sysfs: +err_arch: + qdss_clk_disable(); +err_clk: + qdss_put(etm.src); +err_qdssget: + pm_qos_remove_request(&etm.qos_req); + wake_lock_destroy(&etm.wake_lock); + mutex_destroy(&etm.mutex); + iounmap(etm.base); +err_ioremap: +err_res: + dev_err(etm.dev, "ETM init failed\n"); + return ret; +} + +static int __devexit etm_remove(struct platform_device *pdev) +{ + if (etm.enabled) + etm_disable(); + etm_sysfs_exit(); + qdss_put(etm.src); + pm_qos_remove_request(&etm.qos_req); + wake_lock_destroy(&etm.wake_lock); + mutex_destroy(&etm.mutex); + iounmap(etm.base); + + return 0; +} + +static struct platform_driver etm_driver = { + .probe = etm_probe, + .remove = __devexit_p(etm_remove), + .driver = { + .name = "msm_etm", + }, +}; + +int __init etm_init(void) +{ + return platform_driver_register(&etm_driver); +} +module_init(etm_init); + +void __exit etm_exit(void) +{ + platform_driver_unregister(&etm_driver); +} +module_exit(etm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Program Flow Trace driver"); diff --git a/arch/arm/mach-msm/qdss-funnel.c b/arch/arm/mach-msm/qdss-funnel.c new file mode 100644 index 0000000000000000000000000000000000000000..52eb2b66e09be44102dd1e3634ede0615d8c124e --- /dev/null +++ b/arch/arm/mach-msm/qdss-funnel.c @@ -0,0 +1,232 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qdss-priv.h" + +#define funnel_writel(funnel, id, val, off) \ + __raw_writel((val), funnel.base + (SZ_4K * id) + off) +#define funnel_readl(funnel, id, off) \ + __raw_readl(funnel.base + (SZ_4K * id) + off) + +#define FUNNEL_FUNCTL (0x000) +#define FUNNEL_PRICTL (0x004) +#define FUNNEL_ITATBDATA0 (0xEEC) +#define FUNNEL_ITATBCTR2 (0xEF0) +#define FUNNEL_ITATBCTR1 (0xEF4) +#define FUNNEL_ITATBCTR0 (0xEF8) + + +#define FUNNEL_LOCK(id) \ +do { \ + mb(); \ + funnel_writel(funnel, id, 0x0, CS_LAR); \ +} while (0) +#define FUNNEL_UNLOCK(id) \ +do { \ + funnel_writel(funnel, id, CS_UNLOCK_MAGIC, CS_LAR); \ + mb(); \ +} while (0) + +#define FUNNEL_HOLDTIME_MASK (0xF00) +#define FUNNEL_HOLDTIME_SHFT (0x8) +#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT) + +struct funnel_ctx { + void __iomem *base; + bool enabled; + struct mutex mutex; + struct device *dev; + struct kobject *kobj; + uint32_t priority; +}; + +static struct funnel_ctx funnel; + +static void __funnel_enable(uint8_t id, uint32_t port_mask) +{ + uint32_t functl; + + FUNNEL_UNLOCK(id); + + functl = funnel_readl(funnel, id, FUNNEL_FUNCTL); + functl &= ~FUNNEL_HOLDTIME_MASK; + functl |= FUNNEL_HOLDTIME; + functl |= port_mask; + funnel_writel(funnel, id, functl, FUNNEL_FUNCTL); + funnel_writel(funnel, id, funnel.priority, FUNNEL_PRICTL); + + FUNNEL_LOCK(id); +} + +void funnel_enable(uint8_t id, uint32_t port_mask) +{ + mutex_lock(&funnel.mutex); + __funnel_enable(id, port_mask); + funnel.enabled = true; + dev_info(funnel.dev, "FUNNEL port mask 0x%lx enabled\n", + (unsigned long) port_mask); + mutex_unlock(&funnel.mutex); +} + +static void __funnel_disable(uint8_t id, uint32_t port_mask) +{ + uint32_t functl; + + FUNNEL_UNLOCK(id); + + functl = funnel_readl(funnel, id, FUNNEL_FUNCTL); + functl &= ~port_mask; + funnel_writel(funnel, id, functl, FUNNEL_FUNCTL); + + FUNNEL_LOCK(id); +} + +void funnel_disable(uint8_t id, uint32_t port_mask) +{ + mutex_lock(&funnel.mutex); + __funnel_disable(id, port_mask); + funnel.enabled = false; + dev_info(funnel.dev, "FUNNEL port mask 0x%lx disabled\n", + (unsigned long) port_mask); + mutex_unlock(&funnel.mutex); +} + +#define FUNNEL_ATTR(__name) \ +static struct kobj_attribute __name##_attr = \ + __ATTR(__name, S_IRUGO | S_IWUSR, __name##_show, __name##_store) + +static ssize_t priority_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + funnel.priority = val; + return n; +} +static ssize_t priority_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val = funnel.priority; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +FUNNEL_ATTR(priority); + +static int __devinit funnel_sysfs_init(void) +{ + int ret; + + funnel.kobj = kobject_create_and_add("funnel", qdss_get_modulekobj()); + if (!funnel.kobj) { + dev_err(funnel.dev, "failed to create FUNNEL sysfs kobject\n"); + ret = -ENOMEM; + goto err_create; + } + + ret = sysfs_create_file(funnel.kobj, &priority_attr.attr); + if (ret) { + dev_err(funnel.dev, "failed to create FUNNEL sysfs priority" + " attribute\n"); + goto err_file; + } + + return 0; +err_file: + kobject_put(funnel.kobj); +err_create: + return ret; +} + +static void __devexit funnel_sysfs_exit(void) +{ + sysfs_remove_file(funnel.kobj, &priority_attr.attr); + kobject_put(funnel.kobj); +} + +static int __devinit funnel_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EINVAL; + goto err_res; + } + + funnel.base = ioremap_nocache(res->start, resource_size(res)); + if (!funnel.base) { + ret = -EINVAL; + goto err_ioremap; + } + + funnel.dev = &pdev->dev; + + mutex_init(&funnel.mutex); + + funnel_sysfs_init(); + + dev_info(funnel.dev, "FUNNEL initialized\n"); + return 0; + +err_ioremap: +err_res: + dev_err(funnel.dev, "FUNNEL init failed\n"); + return ret; +} + +static int __devexit funnel_remove(struct platform_device *pdev) +{ + if (funnel.enabled) + funnel_disable(0x0, 0xFF); + funnel_sysfs_exit(); + mutex_destroy(&funnel.mutex); + iounmap(funnel.base); + + return 0; +} + +static struct platform_driver funnel_driver = { + .probe = funnel_probe, + .remove = __devexit_p(funnel_remove), + .driver = { + .name = "msm_funnel", + }, +}; + +static int __init funnel_init(void) +{ + return platform_driver_register(&funnel_driver); +} +module_init(funnel_init); + +static void __exit funnel_exit(void) +{ + platform_driver_unregister(&funnel_driver); +} +module_exit(funnel_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Funnel driver"); diff --git a/arch/arm/mach-msm/qdss-priv.h b/arch/arm/mach-msm/qdss-priv.h new file mode 100644 index 0000000000000000000000000000000000000000..f39bc52a164604cbd4531b16061fde98ecfbf240 --- /dev/null +++ b/arch/arm/mach-msm/qdss-priv.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_QDSS_H_ +#define _ARCH_ARM_MACH_MSM_QDSS_H_ + +#include +#include + +/* Coresight management registers (0xF00-0xFCC) + * 0xFA0 - 0xFA4: Management registers in PFTv1.0 + * Trace registers in PFTv1.1 + */ +#define CS_ITCTRL (0xF00) +#define CS_CLAIMSET (0xFA0) +#define CS_CLAIMCLR (0xFA4) +#define CS_LAR (0xFB0) +#define CS_LSR (0xFB4) +#define CS_AUTHSTATUS (0xFB8) +#define CS_DEVID (0xFC8) +#define CS_DEVTYPE (0xFCC) +/* Peripheral id registers (0xFD0-0xFEC) */ +#define CS_PIDR4 (0xFD0) +#define CS_PIDR5 (0xFD4) +#define CS_PIDR6 (0xFD8) +#define CS_PIDR7 (0xFDC) +#define CS_PIDR0 (0xFE0) +#define CS_PIDR1 (0xFE4) +#define CS_PIDR2 (0xFE8) +#define CS_PIDR3 (0xFEC) +/* Component id registers (0xFF0-0xFFC) */ +#define CS_CIDR0 (0xFF0) +#define CS_CIDR1 (0xFF4) +#define CS_CIDR2 (0xFF8) +#define CS_CIDR3 (0xFFC) + +/* DBGv7 with baseline CP14 registers implemented */ +#define ARM_DEBUG_ARCH_V7B (0x3) +/* DBGv7 with all CP14 registers implemented */ +#define ARM_DEBUG_ARCH_V7 (0x4) +#define ARM_DEBUG_ARCH_V7_1 (0x5) +#define ETM_ARCH_V3_3 (0x23) +#define PFT_ARCH_V1_1 (0x31) + +#define TIMEOUT_US (100) +#define CS_UNLOCK_MAGIC (0xC5ACCE55) + +#define BM(lsb, msb) ((BIT(msb) - BIT(lsb)) + BIT(msb)) +#define BMVAL(val, lsb, msb) ((val & BM(lsb, msb)) >> lsb) +#define BVAL(val, n) ((val & BIT(n)) >> n) + +void etb_enable(void); +void etb_disable(void); +void etb_dump(void); +void tpiu_disable(void); +void funnel_enable(uint8_t id, uint32_t port_mask); +void funnel_disable(uint8_t id, uint32_t port_mask); + +struct kobject *qdss_get_modulekobj(void); + +#endif diff --git a/arch/arm/mach-msm/qdss-tpiu.c b/arch/arm/mach-msm/qdss-tpiu.c new file mode 100644 index 0000000000000000000000000000000000000000..fa1563560275c1813f5c409c98e74143ee871b61 --- /dev/null +++ b/arch/arm/mach-msm/qdss-tpiu.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "qdss-priv.h" + +#define tpiu_writel(tpiu, val, off) __raw_writel((val), tpiu.base + off) +#define tpiu_readl(tpiu, off) __raw_readl(tpiu.base + off) + +#define TPIU_SUPP_PORTSZ (0x000) +#define TPIU_CURR_PORTSZ (0x004) +#define TPIU_SUPP_TRIGMODES (0x100) +#define TPIU_TRIG_CNTRVAL (0x104) +#define TPIU_TRIG_MULT (0x108) +#define TPIU_SUPP_TESTPATM (0x200) +#define TPIU_CURR_TESTPATM (0x204) +#define TPIU_TEST_PATREPCNTR (0x208) +#define TPIU_FFSR (0x300) +#define TPIU_FFCR (0x304) +#define TPIU_FSYNC_CNTR (0x308) +#define TPIU_EXTCTL_INPORT (0x400) +#define TPIU_EXTCTL_OUTPORT (0x404) +#define TPIU_ITTRFLINACK (0xEE4) +#define TPIU_ITTRFLIN (0xEE8) +#define TPIU_ITATBDATA0 (0xEEC) +#define TPIU_ITATBCTR2 (0xEF0) +#define TPIU_ITATBCTR1 (0xEF4) +#define TPIU_ITATBCTR0 (0xEF8) + + +#define TPIU_LOCK() \ +do { \ + mb(); \ + tpiu_writel(tpiu, 0x0, CS_LAR); \ +} while (0) +#define TPIU_UNLOCK() \ +do { \ + tpiu_writel(tpiu, CS_UNLOCK_MAGIC, CS_LAR); \ + mb(); \ +} while (0) + +struct tpiu_ctx { + void __iomem *base; + bool enabled; + struct device *dev; +}; + +static struct tpiu_ctx tpiu; + +static void __tpiu_disable(void) +{ + TPIU_UNLOCK(); + + tpiu_writel(tpiu, 0x3000, TPIU_FFCR); + tpiu_writel(tpiu, 0x3040, TPIU_FFCR); + + TPIU_LOCK(); +} + +void tpiu_disable(void) +{ + __tpiu_disable(); + tpiu.enabled = false; + dev_info(tpiu.dev, "TPIU disabled\n"); +} + +static int __devinit tpiu_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EINVAL; + goto err_res; + } + + tpiu.base = ioremap_nocache(res->start, resource_size(res)); + if (!tpiu.base) { + ret = -EINVAL; + goto err_ioremap; + } + + tpiu.dev = &pdev->dev; + + dev_info(tpiu.dev, "TPIU initialized\n"); + return 0; + +err_ioremap: +err_res: + dev_err(tpiu.dev, "TPIU init failed\n"); + return ret; +} + +static int __devexit tpiu_remove(struct platform_device *pdev) +{ + if (tpiu.enabled) + tpiu_disable(); + iounmap(tpiu.base); + + return 0; +} + +static struct platform_driver tpiu_driver = { + .probe = tpiu_probe, + .remove = __devexit_p(tpiu_remove), + .driver = { + .name = "msm_tpiu", + }, +}; + +static int __init tpiu_init(void) +{ + return platform_driver_register(&tpiu_driver); +} +module_init(tpiu_init); + +static void __exit tpiu_exit(void) +{ + platform_driver_unregister(&tpiu_driver); +} +module_exit(tpiu_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Trace Port Interface Unit driver"); diff --git a/arch/arm/mach-msm/qdss.c b/arch/arm/mach-msm/qdss.c new file mode 100644 index 0000000000000000000000000000000000000000..fd1fc2b21abc88d2e447cefea29d8e4d472a0215 --- /dev/null +++ b/arch/arm/mach-msm/qdss.c @@ -0,0 +1,408 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "qdss-priv.h" + +#define MAX_STR_LEN (65535) + +enum { + QDSS_CLK_OFF, + QDSS_CLK_ON_DBG, + QDSS_CLK_ON_HSDBG, +}; + +/* + * Exclusion rules for structure fields. + * + * S: qdss.sources_mutex protected. + * I: qdss.sink_mutex protected. + * C: qdss.clk_mutex protected. + */ +struct qdss_ctx { + struct kobject *modulekobj; + struct msm_qdss_platform_data *pdata; + struct list_head sources; /* S: sources list */ + struct mutex sources_mutex; + uint8_t sink_count; /* I: sink count */ + struct mutex sink_mutex; + uint8_t max_clk; + uint8_t clk_count; /* C: clk count */ + struct mutex clk_mutex; +}; + +static struct qdss_ctx qdss; + +/** + * qdss_get - get the qdss source handle + * @name: name of the qdss source + * + * Searches the sources list to get the qdss source handle for this source. + * + * CONTEXT: + * Typically called from init or probe functions + * + * RETURNS: + * pointer to struct qdss_source on success, %NULL on failure + */ +struct qdss_source *qdss_get(const char *name) +{ + struct qdss_source *src, *source = NULL; + + mutex_lock(&qdss.sources_mutex); + list_for_each_entry(src, &qdss.sources, link) { + if (src->name) { + if (strncmp(src->name, name, MAX_STR_LEN)) + continue; + source = src; + break; + } + } + mutex_unlock(&qdss.sources_mutex); + + return source ? source : ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL(qdss_get); + +/** + * qdss_put - release the qdss source handle + * @name: name of the qdss source + * + * CONTEXT: + * Typically called from driver remove or exit functions + */ +void qdss_put(struct qdss_source *src) +{ +} +EXPORT_SYMBOL(qdss_put); + +/** + * qdss_enable - enable qdss for the source + * @src: handle for the source making the call + * + * Enables qdss block (relevant funnel ports and sink) if not already + * enabled, otherwise increments the reference count + * + * CONTEXT: + * Might sleep. Uses a mutex lock. Should be called from a non-atomic context. + * + * RETURNS: + * 0 on success, non-zero on failure + */ +int qdss_enable(struct qdss_source *src) +{ + int ret; + + if (!src) + return -EINVAL; + + ret = qdss_clk_enable(); + if (ret) + goto err; + + if ((qdss.pdata)->afamily) { + mutex_lock(&qdss.sink_mutex); + if (qdss.sink_count == 0) { + etb_disable(); + tpiu_disable(); + /* enable ETB first to avoid losing any trace data */ + etb_enable(); + } + qdss.sink_count++; + mutex_unlock(&qdss.sink_mutex); + } + + funnel_enable(0x0, src->fport_mask); + return 0; +err: + return ret; +} +EXPORT_SYMBOL(qdss_enable); + +/** + * qdss_disable - disable qdss for the source + * @src: handle for the source making the call + * + * Disables qdss block (relevant funnel ports and sink) if the reference count + * is one, otherwise decrements the reference count + * + * CONTEXT: + * Might sleep. Uses a mutex lock. Should be called from a non-atomic context. + */ +void qdss_disable(struct qdss_source *src) +{ + if (!src) + return; + + if ((qdss.pdata)->afamily) { + mutex_lock(&qdss.sink_mutex); + if (WARN(qdss.sink_count == 0, "qdss is unbalanced\n")) + goto out; + if (qdss.sink_count == 1) { + etb_dump(); + etb_disable(); + } + qdss.sink_count--; + mutex_unlock(&qdss.sink_mutex); + } + + funnel_disable(0x0, src->fport_mask); + qdss_clk_disable(); + return; +out: + mutex_unlock(&qdss.sink_mutex); +} +EXPORT_SYMBOL(qdss_disable); + +/** + * qdss_disable_sink - force disable the current qdss sink(s) + * + * Force disable the current qdss sink(s) to stop the sink from accepting any + * trace generated subsequent to this call. This function should only be used + * as a way to stop the sink from getting polluted with trace data that is + * uninteresting after an event of interest has occured. + * + * CONTEXT: + * Can be called from atomic or non-atomic context. + */ +void qdss_disable_sink(void) +{ + if ((qdss.pdata)->afamily) { + etb_dump(); + etb_disable(); + } +} +EXPORT_SYMBOL(qdss_disable_sink); + +/** + * qdss_clk_enable - enable qdss clocks + * + * Enables qdss clocks via RPM if they aren't already enabled, otherwise + * increments the reference count. + * + * CONTEXT: + * Might sleep. Uses a mutex lock. Should be called from a non-atomic context. + * + * RETURNS: + * 0 on success, non-zero on failure + */ +int qdss_clk_enable(void) +{ + int ret; + struct msm_rpm_iv_pair iv; + + mutex_lock(&qdss.clk_mutex); + if (qdss.clk_count == 0) { + iv.id = MSM_RPM_ID_QDSS_CLK; + if (qdss.max_clk) + iv.value = QDSS_CLK_ON_HSDBG; + else + iv.value = QDSS_CLK_ON_DBG; + ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, &iv, 1); + if (WARN(ret, "qdss clks not enabled (%d)\n", ret)) + goto err_clk; + } + qdss.clk_count++; + mutex_unlock(&qdss.clk_mutex); + return 0; +err_clk: + mutex_unlock(&qdss.clk_mutex); + return ret; +} +EXPORT_SYMBOL(qdss_clk_enable); + +/** + * qdss_clk_disable - disable qdss clocks + * + * Disables qdss clocks via RPM if the reference count is one, otherwise + * decrements the reference count. + * + * CONTEXT: + * Might sleep. Uses a mutex lock. Should be called from a non-atomic context. + */ +void qdss_clk_disable(void) +{ + int ret; + struct msm_rpm_iv_pair iv; + + mutex_lock(&qdss.clk_mutex); + if (WARN(qdss.clk_count == 0, "qdss clks are unbalanced\n")) + goto out; + if (qdss.clk_count == 1) { + iv.id = MSM_RPM_ID_QDSS_CLK; + iv.value = QDSS_CLK_OFF; + ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, &iv, 1); + WARN(ret, "qdss clks not disabled (%d)\n", ret); + } + qdss.clk_count--; +out: + mutex_unlock(&qdss.clk_mutex); +} +EXPORT_SYMBOL(qdss_clk_disable); + +struct kobject *qdss_get_modulekobj(void) +{ + return qdss.modulekobj; +} + +#define QDSS_ATTR(name) \ +static struct kobj_attribute name##_attr = \ + __ATTR(name, S_IRUGO | S_IWUSR, name##_show, name##_store) + +static ssize_t max_clk_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (sscanf(buf, "%lx", &val) != 1) + return -EINVAL; + + qdss.max_clk = val; + return n; +} +static ssize_t max_clk_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + unsigned long val = qdss.max_clk; + return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); +} +QDSS_ATTR(max_clk); + +static void __devinit qdss_add_sources(struct qdss_source *srcs, size_t num) +{ + mutex_lock(&qdss.sources_mutex); + while (num--) { + list_add_tail(&srcs->link, &qdss.sources); + srcs++; + } + mutex_unlock(&qdss.sources_mutex); +} + +static int __init qdss_sysfs_init(void) +{ + int ret; + + qdss.modulekobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!qdss.modulekobj) { + pr_err("failed to find QDSS sysfs module kobject\n"); + ret = -ENOENT; + goto err; + } + + ret = sysfs_create_file(qdss.modulekobj, &max_clk_attr.attr); + if (ret) { + pr_err("failed to create QDSS sysfs max_clk attribute\n"); + goto err; + } + + return 0; +err: + return ret; +} + +static void __devexit qdss_sysfs_exit(void) +{ + sysfs_remove_file(qdss.modulekobj, &max_clk_attr.attr); +} + +static int __devinit qdss_probe(struct platform_device *pdev) +{ + int ret; + struct qdss_source *src_table; + size_t num_srcs; + + mutex_init(&qdss.sources_mutex); + mutex_init(&qdss.clk_mutex); + mutex_init(&qdss.sink_mutex); + + if (pdev->dev.platform_data == NULL) { + pr_err("%s: platform data is NULL\n", __func__); + ret = -ENODEV; + goto err_pdata; + } + qdss.pdata = pdev->dev.platform_data; + + INIT_LIST_HEAD(&qdss.sources); + src_table = (qdss.pdata)->src_table; + num_srcs = (qdss.pdata)->size; + qdss_add_sources(src_table, num_srcs); + + pr_info("QDSS arch initialized\n"); + return 0; +err_pdata: + mutex_destroy(&qdss.sink_mutex); + mutex_destroy(&qdss.clk_mutex); + mutex_destroy(&qdss.sources_mutex); + pr_err("QDSS init failed\n"); + return ret; +} + +static int __devexit qdss_remove(struct platform_device *pdev) +{ + qdss_sysfs_exit(); + mutex_destroy(&qdss.sink_mutex); + mutex_destroy(&qdss.clk_mutex); + mutex_destroy(&qdss.sources_mutex); + + return 0; +} + +static struct platform_driver qdss_driver = { + .probe = qdss_probe, + .remove = __devexit_p(qdss_remove), + .driver = { + .name = "msm_qdss", + }, +}; + +static int __init qdss_init(void) +{ + return platform_driver_register(&qdss_driver); +} +arch_initcall(qdss_init); + +static int __init qdss_module_init(void) +{ + int ret; + + ret = qdss_sysfs_init(); + if (ret) + goto err_sysfs; + + pr_info("QDSS module initialized\n"); + return 0; +err_sysfs: + return ret; +} +module_init(qdss_module_init); + +static void __exit qdss_exit(void) +{ + platform_driver_unregister(&qdss_driver); +} +module_exit(qdss_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Debug SubSystem Driver"); diff --git a/arch/arm/mach-msm/ramdump.c b/arch/arm/mach-msm/ramdump.c new file mode 100644 index 0000000000000000000000000000000000000000..a18acd6706e18d74d93ad0c884262e82673c7ca5 --- /dev/null +++ b/arch/arm/mach-msm/ramdump.c @@ -0,0 +1,263 @@ +/* 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 "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; + } + + snprintf(rd_dev->name, ARRAY_SIZE(rd_dev->name), "ramdump_%s", + dev_name); + + 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 0000000000000000000000000000000000000000..0b60a4464bb4b4bc5da0168e0e75fc885215c6f3 --- /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 0000000000000000000000000000000000000000..2480433553e252bf76c79b7472a3478389177096 --- /dev/null +++ b/arch/arm/mach-msm/remote_spinlock.c @@ -0,0 +1,192 @@ +/* Copyright (c) 2008-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_private.h" +#include + +static void remote_spin_release_all_locks(uint32_t pid, int count); + +#if defined(CONFIG_MSM_REMOTE_SPINLOCK_SFPB) +#define SFPB_SPINLOCK_COUNT 8 +#define MSM_SFPB_MUTEX_REG_BASE 0x01200600 +#define MSM_SFPB_MUTEX_REG_SIZE (33 * 4) + +static void *hw_mutex_reg_base; +static DEFINE_MUTEX(hw_map_init_lock); + +static int remote_spinlock_init_address(int id, _remote_spinlock_t *lock) +{ + if (id >= SFPB_SPINLOCK_COUNT) + return -EINVAL; + + if (!hw_mutex_reg_base) { + mutex_lock(&hw_map_init_lock); + if (!hw_mutex_reg_base) + hw_mutex_reg_base = ioremap(MSM_SFPB_MUTEX_REG_BASE, + MSM_SFPB_MUTEX_REG_SIZE); + mutex_unlock(&hw_map_init_lock); + BUG_ON(hw_mutex_reg_base == NULL); + } + + *lock = hw_mutex_reg_base + 0x4 + id * 4; + return 0; +} + +void _remote_spin_release_all(uint32_t pid) +{ + remote_spin_release_all_locks(pid, SFPB_SPINLOCK_COUNT); +} + +#else +#define SMEM_SPINLOCK_COUNT 8 +#define SMEM_SPINLOCK_ARRAY_SIZE (SMEM_SPINLOCK_COUNT * sizeof(uint32_t)) + +static int remote_spinlock_init_address(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; +} + +void _remote_spin_release_all(uint32_t pid) +{ + remote_spin_release_all_locks(pid, SMEM_SPINLOCK_COUNT); +} + +#endif + +/** + * Release all spinlocks owned by @pid. + * + * This is only to be used for situations where the processor owning + * spinlocks has crashed and the spinlocks must be released. + * + * @pid - processor ID of processor to release + */ +static void remote_spin_release_all_locks(uint32_t pid, int count) +{ + int n; + _remote_spinlock_t lock; + + for (n = 0; n < count; ++n) { + if (remote_spinlock_init_address(n, &lock) == 0) + _remote_spin_release(&lock, pid); + } +} + +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 lock ID follows "S:" */ + BUG_ON(id[3] != '\0'); + + return remote_spinlock_init_address((((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 0000000000000000000000000000000000000000..8e92456ae4aed243c96ab0ae966d803f204ab052 --- /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 + +#include "smd_private.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 0000000000000000000000000000000000000000..2bfefb253673e371bbb25cd7e79a8eecb08c6b89 --- /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 0000000000000000000000000000000000000000..e45e2c43bd13637096c789fbd5b1e1e562c17a7e --- /dev/null +++ b/arch/arm/mach-msm/restart.c @@ -0,0 +1,267 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_watchdog.h" +#include "timer.h" + +#define WDT0_RST 0x38 +#define WDT0_EN 0x40 +#define WDT0_BARK_TIME 0x4C +#define WDT0_BITE_TIME 0x5C + +#define PSHOLD_CTL_SU (MSM_TLMM_BASE + 0x820) + +#define RESTART_REASON_ADDR 0x65C +#define DLOAD_MODE_ADDR 0x0 + +#define SCM_IO_DISABLE_PMIC_ARBITER 1 + +static int restart_mode; +void *restart_reason; + +int pmic_reset_irq; +static void __iomem *msm_tmr0_base; + +#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(int lower_pshold) +{ + printk(KERN_CRIT "Powering off the SoC\n"); +#ifdef CONFIG_MSM_DLOAD_MODE + set_dload_mode(0); +#endif + pm8xxx_reset_pwr_off(0); + + if (lower_pshold) { + __raw_writel(0, PSHOLD_CTL_SU); + mdelay(10000); + printk(KERN_ERR "Powering off has failed\n"); + } + return; +} + +static void msm_power_off(void) +{ + /* MSM initiated power off, lower ps_hold */ + __msm_power_off(1); +} + +static void cpu_power_off(void *data) +{ + int rc; + + pr_err("PMIC Initiated shutdown %s cpu=%d\n", __func__, + smp_processor_id()); + if (smp_processor_id() == 0) { + /* + * PMIC initiated power off, do not lower ps_hold, pmic will + * shut msm down + */ + __msm_power_off(0); + + pet_watchdog(); + pr_err("Calling scm to disable arbiter\n"); + /* call secure manager to disable arbiter and never return */ + rc = scm_call_atomic1(SCM_SVC_PWR, + SCM_IO_DISABLE_PMIC_ARBITER, 1); + + pr_err("SCM returned even when asked to busy loop rc=%d\n", rc); + pr_err("waiting on pmic to shut msm down\n"); + } + + preempt_disable(); + while (1) + ; +} + +static irqreturn_t resout_irq_handler(int irq, void *dev_id) +{ + pr_warn("%s PMIC Initiated shutdown\n", __func__); + oops_in_progress = 1; + smp_call_function_many(cpu_online_mask, cpu_power_off, NULL, 0); + if (smp_processor_id() == 0) + cpu_power_off(NULL); + preempt_disable(); + while (1) + ; + return IRQ_HANDLED; +} + +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"); + + pm8xxx_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, msm_tmr0_base + 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, msm_tmr0_base + WDT0_RST); + __raw_writel(5*0x31F3, msm_tmr0_base + WDT0_BARK_TIME); + __raw_writel(0x31F3, msm_tmr0_base + WDT0_BITE_TIME); + __raw_writel(1, msm_tmr0_base + WDT0_EN); + + mdelay(10000); + printk(KERN_ERR "Restarting has failed\n"); +} + +static int __init msm_pmic_restart_init(void) +{ + int rc; + + if (pmic_reset_irq != 0) { + rc = request_any_context_irq(pmic_reset_irq, + resout_irq_handler, IRQF_TRIGGER_HIGH, + "restart_from_pmic", NULL); + if (rc < 0) + pr_err("pmic restart irq fail rc = %d\n", rc); + } else { + pr_warn("no pmic restart interrupt specified\n"); + } + + return 0; +} + +late_initcall(msm_pmic_restart_init); + +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; + set_dload_mode(download_mode); +#endif + msm_tmr0_base = msm_timer_get_timer0_base(); + restart_reason = MSM_IMEM_BASE + RESTART_REASON_ADDR; + pm_power_off = msm_power_off; + + return 0; +} +early_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 0000000000000000000000000000000000000000..32b654bfac3d8c973de00cfd5e843bef5406c3b1 --- /dev/null +++ b/arch/arm/mach-msm/rfic-fsm9xxx.c @@ -0,0 +1,397 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_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 0000000000000000000000000000000000000000..4bec55dc1fb391e6e95bf3e3d192fbb3dd3c208b --- /dev/null +++ b/arch/arm/mach-msm/rmt_storage_client.c @@ -0,0 +1,1746 @@ +/* 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 +show_force_sync(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 rmt_storage_force_sync(srv->rpc_client); +} + +/* 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, show_force_sync, NULL); +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 0000000000000000000000000000000000000000..609b1258bfbdb586b54884aed23243a9a23f5e79 --- /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 0000000000000000000000000000000000000000..4692d945ad06aeec069d13a7493d62ea57aceb24 --- /dev/null +++ b/arch/arm/mach-msm/rpc_fsusb.c @@ -0,0 +1,244 @@ +/* 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 + +#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 0000000000000000000000000000000000000000..cd5f6124df58f2add0bc372592757df4f382aff2 --- /dev/null +++ b/arch/arm/mach-msm/rpc_hsusb.c @@ -0,0 +1,660 @@ +/* linux/arch/arm/mach-msm/rpc_hsusb.c + * + * Copyright (c) 2008-2012, 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 + +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_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 0000000000000000000000000000000000000000..0828bb4da856e3b7b04a9da84fba1054b25a0d85 --- /dev/null +++ b/arch/arm/mach-msm/rpc_pmapp.c @@ -0,0 +1,577 @@ +/* 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 + +#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 PMAPP_RPC_VER_7_1 0x00070001 + +#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 +#define PMAPP_VREG_LPM_PINCNTRL_VOTE_PROC 34 + +/* 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_7_1, + 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 > 255) + 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); + +int pmapp_vreg_lpm_pincntrl_vote(const char *voter_id, uint vreg_id, + uint clock_id, uint vote) +{ + if (strnlen(voter_id, PMAPP_CLOCK_VOTER_ID_LEN) + != PMAPP_CLOCK_VOTER_ID_LEN) + return -EINVAL; + + return pmapp_rpc_set_only(*((uint *) voter_id), vreg_id, clock_id, + vote, 4, + PMAPP_VREG_LPM_PINCNTRL_VOTE_PROC); +} +EXPORT_SYMBOL(pmapp_vreg_lpm_pincntrl_vote); 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 0000000000000000000000000000000000000000..5e0f46da379d0edb73fafe46a6ac2c004d30ebe0 --- /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 0000000000000000000000000000000000000000..6d173fb6629809ce5de01e5aea25ac966126742c --- /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,2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 __devinit 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 0000000000000000000000000000000000000000..a7e68543dca62c45ac6b4518624856facac097ff --- /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 0000000000000000000000000000000000000000..056666f5001353494af21c0204b6d631f0bf419c --- /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 0000000000000000000000000000000000000000..94a2d26fc90eaaf6c418a6a55464d16a90ad130d --- /dev/null +++ b/arch/arm/mach-msm/rpcrouter_sdio_xprt.c @@ -0,0 +1,655 @@ +/* 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); + } + modem_reset = 0; + + rc = allocate_sdio_xprt(&sdio_remote_xprt.channel); + if (rc) { + destroy_workqueue(sdio_xprt_read_workqueue); + mutex_unlock(&modem_reset_lock); + 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); + mutex_unlock(&modem_reset_lock); + return rc; + } + 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 0000000000000000000000000000000000000000..e974eb59e0cf10071dc59cb8fa0f4426c3656fc5 --- /dev/null +++ b/arch/arm/mach-msm/rpcrouter_smd_xprt.c @@ -0,0 +1,333 @@ +/* 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 + +#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-notifier.h b/arch/arm/mach-msm/rpm-notifier.h new file mode 100644 index 0000000000000000000000000000000000000000..df8d9b35f4431cc9c936a52e7da1b682be225dcd --- /dev/null +++ b/arch/arm/mach-msm/rpm-notifier.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_NOTIF_H +#define __ARCH_ARM_MACH_MSM_RPM_NOTIF_H + +struct msm_rpm_notifier_data { + uint32_t rsc_type; + uint32_t rsc_id; + uint32_t key; + uint32_t size; + uint8_t *value; +}; + +int msm_rpm_register_notifier(struct notifier_block *nb); +int msm_rpm_unregister_notifier(struct notifier_block *nb); + +#endif /*__ARCH_ARM_MACH_MSM_RPM_NOTIF_H */ diff --git a/arch/arm/mach-msm/rpm-regulator-8660.c b/arch/arm/mach-msm/rpm-regulator-8660.c new file mode 100644 index 0000000000000000000000000000000000000000..6c4a9adf966a2b5ea76b8d6b9e7b0df98368c003 --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-8660.c @@ -0,0 +1,292 @@ +/* + * 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 "rpm-regulator-private.h" + +/* RPM regulator request formats */ +static struct rpm_vreg_parts ldo_parts = { + .request_len = 2, + .mV = REQUEST_MEMBER(0, 0x00000FFF, 0), + .ip = REQUEST_MEMBER(0, 0x00FFF000, 12), + .fm = REQUEST_MEMBER(0, 0x03000000, 24), + .pc = REQUEST_MEMBER(0, 0x3C000000, 26), + .pf = REQUEST_MEMBER(0, 0xC0000000, 30), + .pd = REQUEST_MEMBER(1, 0x00000001, 0), + .ia = REQUEST_MEMBER(1, 0x00001FFE, 1), +}; + +static struct rpm_vreg_parts smps_parts = { + .request_len = 2, + .mV = REQUEST_MEMBER(0, 0x00000FFF, 0), + .ip = REQUEST_MEMBER(0, 0x00FFF000, 12), + .fm = REQUEST_MEMBER(0, 0x03000000, 24), + .pc = REQUEST_MEMBER(0, 0x3C000000, 26), + .pf = REQUEST_MEMBER(0, 0xC0000000, 30), + .pd = REQUEST_MEMBER(1, 0x00000001, 0), + .ia = REQUEST_MEMBER(1, 0x00001FFE, 1), + .freq = REQUEST_MEMBER(1, 0x001FE000, 13), + .freq_clk_src = REQUEST_MEMBER(1, 0x00600000, 21), +}; + +static 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, 0x000000C0, 6), + .hpm = REQUEST_MEMBER(0, 0x00000300, 8), +}; + +static struct rpm_vreg_parts ncp_parts = { + .request_len = 1, + .mV = REQUEST_MEMBER(0, 0x00000FFF, 0), + .enable_state = REQUEST_MEMBER(0, 0x00001000, 12), + .comp_mode = REQUEST_MEMBER(0, 0x00002000, 13), + .freq = REQUEST_MEMBER(0, 0x003FC000, 14), +}; + +/* Physically available PMIC regulator voltage setpoint ranges */ +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 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), +}; + +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 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); + +static struct vreg_set_points *all_set_points[] = { + &pldo_set_points, + &nldo_set_points, + &smps_set_points, + &ftsmps_set_points, + &ncp_set_points, +}; + +#define LDO(_vreg_id, _rpm_id, _name, _name_pc, _ranges, _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_8660_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_LDO, \ + .set_points = &_ranges##_set_points, \ + .part = &ldo_parts, \ + .id = RPM_VREG_ID_##_vreg_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define SMPS(_vreg_id, _rpm_id, _name, _name_pc, _ranges, _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_8660_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_SMPS, \ + .set_points = &_ranges##_set_points, \ + .part = &smps_parts, \ + .id = RPM_VREG_ID_##_vreg_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define LVS(_vreg_id, _rpm_id, _name, _name_pc) \ + [RPM_VREG_ID_##_vreg_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_##_vreg_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define MVS(_vreg_id, _rpm_id, _name, _name_pc) \ + LVS(_vreg_id, _rpm_id, _name, _name_pc) + +#define NCP(_vreg_id, _rpm_id, _name, _name_pc) \ + [RPM_VREG_ID_##_vreg_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_##_rpm_id##_1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_NCP, \ + .set_points = &ncp_set_points, \ + .part = &ncp_parts, \ + .id = RPM_VREG_ID_##_vreg_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +static struct vreg vregs[] = { + LDO(PM8058_L0, LDO0, "8058_l0", "8058_l0_pc", nldo, LDO_150), + LDO(PM8058_L1, LDO1, "8058_l1", "8058_l1_pc", nldo, LDO_300), + LDO(PM8058_L2, LDO2, "8058_l2", "8058_l2_pc", pldo, LDO_300), + LDO(PM8058_L3, LDO3, "8058_l3", "8058_l3_pc", pldo, LDO_150), + LDO(PM8058_L4, LDO4, "8058_l4", "8058_l4_pc", pldo, LDO_50), + LDO(PM8058_L5, LDO5, "8058_l5", "8058_l5_pc", pldo, LDO_300), + LDO(PM8058_L6, LDO6, "8058_l6", "8058_l6_pc", pldo, LDO_50), + LDO(PM8058_L7, LDO7, "8058_l7", "8058_l7_pc", pldo, LDO_50), + LDO(PM8058_L8, LDO8, "8058_l8", "8058_l8_pc", pldo, LDO_300), + LDO(PM8058_L9, LDO9, "8058_l9", "8058_l9_pc", pldo, LDO_300), + LDO(PM8058_L10, LDO10, "8058_l10", "8058_l10_pc", pldo, LDO_300), + LDO(PM8058_L11, LDO11, "8058_l11", "8058_l11_pc", pldo, LDO_150), + LDO(PM8058_L12, LDO12, "8058_l12", "8058_l12_pc", pldo, LDO_150), + LDO(PM8058_L13, LDO13, "8058_l13", "8058_l13_pc", pldo, LDO_300), + LDO(PM8058_L14, LDO14, "8058_l14", "8058_l14_pc", pldo, LDO_300), + LDO(PM8058_L15, LDO15, "8058_l15", "8058_l15_pc", pldo, LDO_300), + LDO(PM8058_L16, LDO16, "8058_l16", "8058_l16_pc", pldo, LDO_300), + LDO(PM8058_L17, LDO17, "8058_l17", "8058_l17_pc", pldo, LDO_150), + LDO(PM8058_L18, LDO18, "8058_l18", "8058_l18_pc", pldo, LDO_150), + LDO(PM8058_L19, LDO19, "8058_l19", "8058_l19_pc", pldo, LDO_150), + LDO(PM8058_L20, LDO20, "8058_l20", "8058_l20_pc", pldo, LDO_150), + LDO(PM8058_L21, LDO21, "8058_l21", "8058_l21_pc", nldo, LDO_150), + LDO(PM8058_L22, LDO22, "8058_l22", "8058_l22_pc", nldo, LDO_300), + LDO(PM8058_L23, LDO23, "8058_l23", "8058_l23_pc", nldo, LDO_300), + LDO(PM8058_L24, LDO24, "8058_l24", "8058_l24_pc", nldo, LDO_150), + LDO(PM8058_L25, LDO25, "8058_l25", "8058_l25_pc", nldo, LDO_150), + + SMPS(PM8058_S0, SMPS0, "8058_s0", "8058_s0_pc", smps, SMPS), + SMPS(PM8058_S1, SMPS1, "8058_s1", "8058_s1_pc", smps, SMPS), + SMPS(PM8058_S2, SMPS2, "8058_s2", "8058_s2_pc", smps, SMPS), + SMPS(PM8058_S3, SMPS3, "8058_s3", "8058_s3_pc", smps, SMPS), + SMPS(PM8058_S4, SMPS4, "8058_s4", "8058_s4_pc", smps, SMPS), + + LVS(PM8058_LVS0, LVS0, "8058_lvs0", "8058_lvs0_pc"), + LVS(PM8058_LVS1, LVS1, "8058_lvs1", "8058_lvs1_pc"), + + NCP(PM8058_NCP, NCP, "8058_ncp", NULL), + + LDO(PM8901_L0, LDO0B, "8901_l0", "8901_l0_pc", nldo, LDO_300), + LDO(PM8901_L1, LDO1B, "8901_l1", "8901_l1_pc", pldo, LDO_300), + LDO(PM8901_L2, LDO2B, "8901_l2", "8901_l2_pc", pldo, LDO_300), + LDO(PM8901_L3, LDO3B, "8901_l3", "8901_l3_pc", pldo, LDO_300), + LDO(PM8901_L4, LDO4B, "8901_l4", "8901_l4_pc", pldo, LDO_300), + LDO(PM8901_L5, LDO5B, "8901_l5", "8901_l5_pc", pldo, LDO_300), + LDO(PM8901_L6, LDO6B, "8901_l6", "8901_l6_pc", pldo, LDO_300), + + SMPS(PM8901_S0, SMPS0B, "8901_s0", "8901_s0_pc", ftsmps, FTSMPS), + SMPS(PM8901_S1, SMPS1B, "8901_s1", "8901_s1_pc", ftsmps, FTSMPS), + SMPS(PM8901_S2, SMPS2B, "8901_s2", "8901_s2_pc", ftsmps, FTSMPS), + SMPS(PM8901_S3, SMPS3B, "8901_s3", "8901_s3_pc", ftsmps, FTSMPS), + SMPS(PM8901_S4, SMPS4B, "8901_s4", "8901_s4_pc", ftsmps, FTSMPS), + + LVS(PM8901_LVS0, LVS0B, "8901_lvs0", "8901_lvs0_pc"), + LVS(PM8901_LVS1, LVS1B, "8901_lvs1", "8901_lvs1_pc"), + LVS(PM8901_LVS2, LVS2B, "8901_lvs2", "8901_lvs2_pc"), + LVS(PM8901_LVS3, LVS3B, "8901_lvs3", "8901_lvs3_pc"), + + MVS(PM8901_MVS0, MVS, "8901_mvs0", "8901_mvs0_pc"), +}; + +static const char *pin_func_label[] = { + [RPM_VREG_PIN_FN_8660_ENABLE] = "on/off", + [RPM_VREG_PIN_FN_8660_MODE] = "HPM/LPM", + [RPM_VREG_PIN_FN_8660_SLEEP_B] = "sleep_b", + [RPM_VREG_PIN_FN_8660_NONE] = "none", +}; + +static const char *force_mode_label[] = { + [RPM_VREG_FORCE_MODE_8660_NONE] = "none", + [RPM_VREG_FORCE_MODE_8660_LPM] = "LPM", + [RPM_VREG_FORCE_MODE_8660_HPM] = "HPM", +}; + +static const char *pin_control_label[] = { + " A0", + " A1", + " D0", + " D1", +}; + +static int is_real_id(int id) +{ + return (id >= 0) && (id <= RPM_VREG_ID_8660_MAX_REAL); +} + +static int pc_id_to_real_id(int id) +{ + int real_id; + + if (id >= RPM_VREG_ID_PM8058_L0_PC && id <= RPM_VREG_ID_PM8058_LVS1_PC) + real_id = id - RPM_VREG_ID_PM8058_L0_PC + RPM_VREG_ID_PM8058_L0; + else + real_id = id - RPM_VREG_ID_PM8901_L0_PC + RPM_VREG_ID_PM8901_L0; + + return real_id; +} + +static struct vreg_config config = { + .vregs = vregs, + .vregs_len = ARRAY_SIZE(vregs), + + .vreg_id_min = RPM_VREG_ID_PM8058_L0, + .vreg_id_max = RPM_VREG_ID_8660_MAX, + + .pin_func_none = RPM_VREG_PIN_FN_8660_NONE, + .pin_func_sleep_b = RPM_VREG_PIN_FN_8660_SLEEP_B, + + .mode_lpm = REGULATOR_MODE_IDLE, + .mode_hpm = REGULATOR_MODE_NORMAL, + + .set_points = all_set_points, + .set_points_len = ARRAY_SIZE(all_set_points), + + .label_pin_ctrl = pin_control_label, + .label_pin_ctrl_len = ARRAY_SIZE(pin_control_label), + .label_pin_func = pin_func_label, + .label_pin_func_len = ARRAY_SIZE(pin_func_label), + .label_force_mode = force_mode_label, + .label_force_mode_len = ARRAY_SIZE(force_mode_label), + + .is_real_id = is_real_id, + .pc_id_to_real_id = pc_id_to_real_id, + + .use_legacy_optimum_mode = 1, + .ia_follows_ip = 1, +}; + +struct vreg_config *get_config_8660(void) +{ + return &config; +} diff --git a/arch/arm/mach-msm/rpm-regulator-8930.c b/arch/arm/mach-msm/rpm-regulator-8930.c new file mode 100644 index 0000000000000000000000000000000000000000..f396fed9764ada7670adb64505a5743f6148145f --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-8930.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "rpm-regulator-private.h" + +/* RPM regulator request formats */ +static struct rpm_vreg_parts ldo_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), +}; + +static struct rpm_vreg_parts smps_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), + .pm = REQUEST_MEMBER(1, 0x00800000, 23), + .freq = REQUEST_MEMBER(1, 0x1F000000, 24), + .freq_clk_src = REQUEST_MEMBER(1, 0x60000000, 29), +}; + +static 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), +}; + +static struct rpm_vreg_parts corner_parts = { + .request_len = 1, + .uV = REQUEST_MEMBER(0, 0x00000003, 0), +}; + +/* Physically available PMIC regulator voltage setpoint ranges */ +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 corner_ranges[] = { + VOLTAGE_RANGE(RPM_VREG_CORNER_NONE, RPM_VREG_CORNER_HIGH, 1), +}; + +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 corner_set_points = SET_POINTS(corner_ranges); + +static struct vreg_set_points *all_set_points[] = { + &pldo_set_points, + &nldo_set_points, + &nldo1200_set_points, + &smps_set_points, + &ftsmps_set_points, + &corner_set_points, +}; + +#define LDO(_id, _name, _name_pc, _ranges, _hpm_min_load) \ + [RPM_VREG_ID_PM8038_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8038_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_PM8038_##_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_8930_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_LDO, \ + .set_points = &_ranges##_set_points, \ + .part = &ldo_parts, \ + .id = RPM_VREG_ID_PM8038_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define SMPS(_id, _name, _name_pc, _ranges, _hpm_min_load) \ + [RPM_VREG_ID_PM8038_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8038_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_PM8038_##_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_8930_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_SMPS, \ + .set_points = &_ranges##_set_points, \ + .part = &smps_parts, \ + .id = RPM_VREG_ID_PM8038_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define LVS(_id, _name, _name_pc) \ + [RPM_VREG_ID_PM8038_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8038_##_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_PM8038_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define CORNER(_id, _rpm_id, _name, _ranges) \ + [RPM_VREG_ID_PM8038_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_CORNER, \ + .set_points = &_ranges##_set_points, \ + .part = &corner_parts, \ + .id = RPM_VREG_ID_PM8038_##_id, \ + .rdesc.name = _name, \ + } + +static struct vreg vregs[] = { + LDO(L1, "8038_l1", NULL, nldo1200, LDO_1200), + LDO(L2, "8038_l2", "8038_l2_pc", nldo, LDO_150), + LDO(L3, "8038_l3", "8038_l3_pc", pldo, LDO_50), + LDO(L4, "8038_l4", "8038_l4_pc", pldo, LDO_50), + LDO(L5, "8038_l5", "8038_l5_pc", pldo, LDO_600), + LDO(L6, "8038_l6", "8038_l6_pc", pldo, LDO_600), + LDO(L7, "8038_l7", "8038_l7_pc", pldo, LDO_600), + LDO(L8, "8038_l8", "8038_l8_pc", pldo, LDO_300), + LDO(L9, "8038_l9", "8038_l9_pc", pldo, LDO_300), + LDO(L10, "8038_l10", "8038_l10_pc", pldo, LDO_600), + LDO(L11, "8038_l11", "8038_l11_pc", pldo, LDO_600), + LDO(L12, "8038_l12", "8038_l12_pc", nldo, LDO_300), + LDO(L14, "8038_l14", "8038_l14_pc", pldo, LDO_50), + LDO(L15, "8038_l15", "8038_l15_pc", pldo, LDO_150), + LDO(L16, "8038_l16", NULL, nldo1200, LDO_1200), + LDO(L17, "8038_l17", "8038_l17_pc", pldo, LDO_150), + LDO(L18, "8038_l18", "8038_l18_pc", pldo, LDO_50), + LDO(L19, "8038_l19", NULL, nldo1200, LDO_1200), + LDO(L20, "8038_l20", NULL, nldo1200, LDO_1200), + LDO(L21, "8038_l21", "8038_l21_pc", pldo, LDO_150), + LDO(L22, "8038_l22", "8038_l22_pc", pldo, LDO_50), + LDO(L23, "8038_l23", "8038_l23_pc", pldo, LDO_50), + LDO(L24, "8038_l24", NULL, nldo1200, LDO_1200), + LDO(L26, "8038_l26", "8038_l26_pc", nldo, LDO_150), + LDO(L27, "8038_l27", NULL, nldo1200, LDO_1200), + + SMPS(S1, "8038_s1", "8038_s1_pc", smps, SMPS_1500), + SMPS(S2, "8038_s2", "8038_s2_pc", smps, SMPS_1500), + SMPS(S3, "8038_s3", "8038_s3_pc", smps, SMPS_1500), + SMPS(S4, "8038_s4", "8038_s4_pc", smps, SMPS_1500), + SMPS(S5, "8038_s5", NULL, ftsmps, SMPS_2000), + SMPS(S6, "8038_s6", NULL, ftsmps, SMPS_2000), + + LVS(LVS1, "8038_lvs1", "8038_lvs1_pc"), + LVS(LVS2, "8038_lvs2", "8038_lvs2_pc"), + + CORNER(VDD_DIG_CORNER, VOLTAGE_CORNER, "vdd_dig_corner", corner), +}; + +static const char *pin_func_label[] = { + [RPM_VREG_PIN_FN_8930_DONT_CARE] = "don't care", + [RPM_VREG_PIN_FN_8930_ENABLE] = "on/off", + [RPM_VREG_PIN_FN_8930_MODE] = "HPM/LPM", + [RPM_VREG_PIN_FN_8930_SLEEP_B] = "sleep_b", + [RPM_VREG_PIN_FN_8930_NONE] = "none", +}; + +static const char *force_mode_label[] = { + [RPM_VREG_FORCE_MODE_8930_NONE] = "none", + [RPM_VREG_FORCE_MODE_8930_LPM] = "LPM", + [RPM_VREG_FORCE_MODE_8930_AUTO] = "auto", + [RPM_VREG_FORCE_MODE_8930_HPM] = "HPM", + [RPM_VREG_FORCE_MODE_8930_BYPASS] = "BYP", +}; + +static const char *power_mode_label[] = { + [RPM_VREG_POWER_MODE_8930_HYSTERETIC] = "HYS", + [RPM_VREG_POWER_MODE_8930_PWM] = "PWM", +}; + +static const char *pin_control_label[] = { + " D1", + " A0", + " A1", + " A2", +}; + +static int is_real_id(int id) +{ + return (id >= 0) && (id <= RPM_VREG_ID_PM8038_MAX_REAL); +} + +static int pc_id_to_real_id(int id) +{ + int real_id = 0; + + if (id >= RPM_VREG_ID_PM8038_L2_PC && id <= RPM_VREG_ID_PM8038_L15_PC) + real_id = id - RPM_VREG_ID_PM8038_L2_PC; + else if (id >= RPM_VREG_ID_PM8038_L17_PC + && id <= RPM_VREG_ID_PM8038_L18_PC) + real_id = id - RPM_VREG_ID_PM8038_L17_PC + + RPM_VREG_ID_PM8038_L17; + else if (id >= RPM_VREG_ID_PM8038_L21_PC + && id <= RPM_VREG_ID_PM8038_L23_PC) + real_id = id - RPM_VREG_ID_PM8038_L21_PC + + RPM_VREG_ID_PM8038_L21; + else if (id == RPM_VREG_ID_PM8038_L26_PC) + real_id = RPM_VREG_ID_PM8038_L26; + else if (id >= RPM_VREG_ID_PM8038_S1_PC + && id <= RPM_VREG_ID_PM8038_S4_PC) + real_id = id - RPM_VREG_ID_PM8038_S1_PC + + RPM_VREG_ID_PM8038_S1; + else if (id >= RPM_VREG_ID_PM8038_LVS1_PC + && id <= RPM_VREG_ID_PM8038_LVS2_PC) + real_id = id - RPM_VREG_ID_PM8038_LVS1_PC + + RPM_VREG_ID_PM8038_LVS1; + + return real_id; +} + +static struct vreg_config config = { + .vregs = vregs, + .vregs_len = ARRAY_SIZE(vregs), + + .vreg_id_min = RPM_VREG_ID_PM8038_L1, + .vreg_id_max = RPM_VREG_ID_PM8038_MAX, + + .pin_func_none = RPM_VREG_PIN_FN_8930_NONE, + .pin_func_sleep_b = RPM_VREG_PIN_FN_8930_SLEEP_B, + + .mode_lpm = REGULATOR_MODE_IDLE, + .mode_hpm = REGULATOR_MODE_NORMAL, + + .set_points = all_set_points, + .set_points_len = ARRAY_SIZE(all_set_points), + + .label_pin_ctrl = pin_control_label, + .label_pin_ctrl_len = ARRAY_SIZE(pin_control_label), + .label_pin_func = pin_func_label, + .label_pin_func_len = ARRAY_SIZE(pin_func_label), + .label_force_mode = force_mode_label, + .label_force_mode_len = ARRAY_SIZE(force_mode_label), + .label_power_mode = power_mode_label, + .label_power_mode_len = ARRAY_SIZE(power_mode_label), + + .is_real_id = is_real_id, + .pc_id_to_real_id = pc_id_to_real_id, +}; + +struct vreg_config *get_config_8930(void) +{ + return &config; +} 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 0000000000000000000000000000000000000000..8726ed4f5667c2eb036e6ba96b604f4c4bc5fc19 --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-8960.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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include "rpm-regulator-private.h" + +/* RPM regulator request formats */ +static struct rpm_vreg_parts ldo_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), +}; + +static struct rpm_vreg_parts smps_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), + .pm = REQUEST_MEMBER(1, 0x00800000, 23), + .freq = REQUEST_MEMBER(1, 0x1F000000, 24), + .freq_clk_src = REQUEST_MEMBER(1, 0x60000000, 29), +}; + +static 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), +}; + +static 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), +}; + +/* Physically available PMIC regulator voltage setpoint ranges */ +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), +}; + +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); + +static struct vreg_set_points *all_set_points[] = { + &pldo_set_points, + &nldo_set_points, + &nldo1200_set_points, + &smps_set_points, + &ftsmps_set_points, + &ncp_set_points, +}; + +#define LDO(_id, _name, _name_pc, _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_8960_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_LDO, \ + .set_points = &_ranges##_set_points, \ + .part = &ldo_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define SMPS(_id, _name, _name_pc, _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_8960_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_SMPS, \ + .set_points = &_ranges##_set_points, \ + .part = &smps_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define LVS(_id, _name, _name_pc) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8921_##_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define MVS(_vreg_id, _name, _name_pc, _rpm_id) \ + [RPM_VREG_ID_PM8921_##_vreg_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_PM8921_##_vreg_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define NCP(_id, _name, _name_pc) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_##_id##_1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_NCP, \ + .set_points = &ncp_set_points, \ + .part = &ncp_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +static struct vreg vregs[] = { + LDO(L1, "8921_l1", "8921_l1_pc", nldo, LDO_150), + LDO(L2, "8921_l2", "8921_l2_pc", nldo, LDO_150), + LDO(L3, "8921_l3", "8921_l3_pc", pldo, LDO_150), + LDO(L4, "8921_l4", "8921_l4_pc", pldo, LDO_50), + LDO(L5, "8921_l5", "8921_l5_pc", pldo, LDO_300), + LDO(L6, "8921_l6", "8921_l6_pc", pldo, LDO_600), + LDO(L7, "8921_l7", "8921_l7_pc", pldo, LDO_150), + LDO(L8, "8921_l8", "8921_l8_pc", pldo, LDO_300), + LDO(L9, "8921_l9", "8921_l9_pc", pldo, LDO_300), + LDO(L10, "8921_l10", "8921_l10_pc", pldo, LDO_600), + LDO(L11, "8921_l11", "8921_l11_pc", pldo, LDO_150), + LDO(L12, "8921_l12", "8921_l12_pc", nldo, LDO_150), + LDO(L14, "8921_l14", "8921_l14_pc", pldo, LDO_50), + LDO(L15, "8921_l15", "8921_l15_pc", pldo, LDO_150), + LDO(L16, "8921_l16", "8921_l16_pc", pldo, LDO_300), + LDO(L17, "8921_l17", "8921_l17_pc", pldo, LDO_150), + LDO(L18, "8921_l18", "8921_l18_pc", nldo, LDO_150), + LDO(L21, "8921_l21", "8921_l21_pc", pldo, LDO_150), + LDO(L22, "8921_l22", "8921_l22_pc", pldo, LDO_150), + LDO(L23, "8921_l23", "8921_l23_pc", pldo, LDO_150), + LDO(L24, "8921_l24", NULL, nldo1200, LDO_1200), + LDO(L25, "8921_l25", NULL, nldo1200, LDO_1200), + LDO(L26, "8921_l26", NULL, nldo1200, LDO_1200), + LDO(L27, "8921_l27", NULL, nldo1200, LDO_1200), + LDO(L28, "8921_l28", NULL, nldo1200, LDO_1200), + LDO(L29, "8921_l29", "8921_l29_pc", pldo, LDO_150), + + SMPS(S1, "8921_s1", "8921_s1_pc", smps, SMPS_1500), + SMPS(S2, "8921_s2", "8921_s2_pc", smps, SMPS_1500), + SMPS(S3, "8921_s3", "8921_s3_pc", smps, SMPS_1500), + SMPS(S4, "8921_s4", "8921_s4_pc", smps, SMPS_1500), + SMPS(S5, "8921_s5", NULL, ftsmps, SMPS_2000), + SMPS(S6, "8921_s6", NULL, ftsmps, SMPS_2000), + SMPS(S7, "8921_s7", "8921_s7_pc", smps, SMPS_1500), + SMPS(S8, "8921_s8", "8921_s8_pc", smps, SMPS_1500), + + LVS(LVS1, "8921_lvs1", "8921_lvs1_pc"), + LVS(LVS2, "8921_lvs2", NULL), + LVS(LVS3, "8921_lvs3", "8921_lvs3_pc"), + LVS(LVS4, "8921_lvs4", "8921_lvs4_pc"), + LVS(LVS5, "8921_lvs5", "8921_lvs5_pc"), + LVS(LVS6, "8921_lvs6", "8921_lvs6_pc"), + LVS(LVS7, "8921_lvs7", "8921_lvs7_pc"), + MVS(USB_OTG, "8921_usb_otg", NULL, USB_OTG_SWITCH), + MVS(HDMI_MVS, "8921_hdmi_mvs", NULL, HDMI_SWITCH), + + NCP(NCP, "8921_ncp", NULL), +}; + +static const char *pin_func_label[] = { + [RPM_VREG_PIN_FN_8960_DONT_CARE] = "don't care", + [RPM_VREG_PIN_FN_8960_ENABLE] = "on/off", + [RPM_VREG_PIN_FN_8960_MODE] = "HPM/LPM", + [RPM_VREG_PIN_FN_8960_SLEEP_B] = "sleep_b", + [RPM_VREG_PIN_FN_8960_NONE] = "none", +}; + +static const char *force_mode_label[] = { + [RPM_VREG_FORCE_MODE_8960_NONE] = "none", + [RPM_VREG_FORCE_MODE_8960_LPM] = "LPM", + [RPM_VREG_FORCE_MODE_8960_AUTO] = "auto", + [RPM_VREG_FORCE_MODE_8960_HPM] = "HPM", + [RPM_VREG_FORCE_MODE_8960_BYPASS] = "BYP", +}; + +static const char *power_mode_label[] = { + [RPM_VREG_POWER_MODE_8960_HYSTERETIC] = "HYS", + [RPM_VREG_POWER_MODE_8960_PWM] = "PWM", +}; + +static const char *pin_control_label[] = { + " D1", + " A0", + " A1", + " A2", +}; + +static int is_real_id(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 struct vreg_config config = { + .vregs = vregs, + .vregs_len = ARRAY_SIZE(vregs), + + .vreg_id_min = RPM_VREG_ID_PM8921_L1, + .vreg_id_max = RPM_VREG_ID_PM8921_MAX, + + .pin_func_none = RPM_VREG_PIN_FN_8960_NONE, + .pin_func_sleep_b = RPM_VREG_PIN_FN_8960_SLEEP_B, + + .mode_lpm = REGULATOR_MODE_IDLE, + .mode_hpm = REGULATOR_MODE_NORMAL, + + .set_points = all_set_points, + .set_points_len = ARRAY_SIZE(all_set_points), + + .label_pin_ctrl = pin_control_label, + .label_pin_ctrl_len = ARRAY_SIZE(pin_control_label), + .label_pin_func = pin_func_label, + .label_pin_func_len = ARRAY_SIZE(pin_func_label), + .label_force_mode = force_mode_label, + .label_force_mode_len = ARRAY_SIZE(force_mode_label), + .label_power_mode = power_mode_label, + .label_power_mode_len = ARRAY_SIZE(power_mode_label), + + .is_real_id = is_real_id, + .pc_id_to_real_id = pc_id_to_real_id, +}; + +struct vreg_config *get_config_8960(void) +{ + return &config; +} diff --git a/arch/arm/mach-msm/rpm-regulator-9615.c b/arch/arm/mach-msm/rpm-regulator-9615.c new file mode 100644 index 0000000000000000000000000000000000000000..23c0ee33ae2ad7f048a7a7d45bf8d2b81084c53d --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-9615.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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include "rpm-regulator-private.h" + +/* RPM regulator request formats */ +static struct rpm_vreg_parts ldo_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), +}; + +static struct rpm_vreg_parts smps_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), + .pm = REQUEST_MEMBER(1, 0x00800000, 23), + .freq = REQUEST_MEMBER(1, 0x1F000000, 24), + .freq_clk_src = REQUEST_MEMBER(1, 0x60000000, 29), +}; + +static 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), +}; + +/* Physically available PMIC regulator voltage setpoint ranges */ +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_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 *all_set_points[] = { + &pldo_set_points, + &nldo_set_points, + &nldo1200_set_points, + &smps_set_points, +}; + +#define LDO(_id, _name, _name_pc, _ranges, _hpm_min_load) \ + [RPM_VREG_ID_PM8018_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8018_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_PM8018_##_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_9615_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_LDO, \ + .set_points = &_ranges##_set_points, \ + .part = &ldo_parts, \ + .id = RPM_VREG_ID_PM8018_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define SMPS(_id, _name, _name_pc, _ranges, _hpm_min_load) \ + [RPM_VREG_ID_PM8018_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8018_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_PM8018_##_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_9615_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = RPM_REGULATOR_TYPE_SMPS, \ + .set_points = &_ranges##_set_points, \ + .part = &smps_parts, \ + .id = RPM_VREG_ID_PM8018_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +#define LVS(_id, _name, _name_pc) \ + [RPM_VREG_ID_PM8018_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8018_##_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = RPM_REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_PM8018_##_id, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _name_pc, \ + } + +static struct vreg vregs[] = { + LDO(L2, "8018_l2", "8018_l2_pc", pldo, LDO_50), + LDO(L3, "8018_l3", "8018_l3_pc", pldo, LDO_50), + LDO(L4, "8018_l4", "8018_l4_pc", pldo, LDO_300), + LDO(L5, "8018_l5", "8018_l5_pc", pldo, LDO_150), + LDO(L6, "8018_l6", "8018_l6_pc", pldo, LDO_150), + LDO(L7, "8018_l7", "8018_l7_pc", pldo, LDO_300), + LDO(L8, "8018_l8", "8018_l8_pc", nldo, LDO_150), + LDO(L9, "8018_l9", NULL, nldo1200, LDO_1200), + LDO(L10, "8018_l10", NULL, nldo1200, LDO_1200), + LDO(L11, "8018_l11", NULL, nldo1200, LDO_1200), + LDO(L12, "8018_l12", NULL, nldo1200, LDO_1200), + LDO(L13, "8018_l13", "8018_l13_pc", pldo, LDO_50), + LDO(L14, "8018_l14", "8018_l14_pc", pldo, LDO_50), + + SMPS(S1, "8018_s1", "8018_s1_pc", smps, SMPS_1500), + SMPS(S2, "8018_s2", "8018_s2_pc", smps, SMPS_1500), + SMPS(S3, "8018_s3", "8018_s3_pc", smps, SMPS_1500), + SMPS(S4, "8018_s4", "8018_s4_pc", smps, SMPS_1500), + SMPS(S5, "8018_s5", "8018_s5_pc", smps, SMPS_1500), + + LVS(LVS1, "8018_lvs1", "8018_lvs1_pc"), +}; + +static const char *pin_control_label[] = { + " D1", + " A0", + " A1", + " A2", +}; + +static const char *pin_func_label[] = { + [RPM_VREG_PIN_FN_9615_DONT_CARE] = "don't care", + [RPM_VREG_PIN_FN_9615_ENABLE] = "on/off", + [RPM_VREG_PIN_FN_9615_MODE] = "HPM/LPM", + [RPM_VREG_PIN_FN_9615_SLEEP_B] = "sleep_b", + [RPM_VREG_PIN_FN_9615_NONE] = "none", +}; + +static const char *force_mode_label[] = { + [RPM_VREG_FORCE_MODE_9615_NONE] = "none", + [RPM_VREG_FORCE_MODE_9615_LPM] = "LPM", + [RPM_VREG_FORCE_MODE_9615_AUTO] = "auto", + [RPM_VREG_FORCE_MODE_9615_HPM] = "HPM", + [RPM_VREG_FORCE_MODE_9615_BYPASS] = "BYP", +}; + +static const char *power_mode_label[] = { + [RPM_VREG_POWER_MODE_9615_HYSTERETIC] = "HYS", + [RPM_VREG_POWER_MODE_9615_PWM] = "PWM", +}; + +static int is_real_id(int id) +{ + return (id >= 0) && (id <= RPM_VREG_ID_PM8018_MAX_REAL); +} + +static int pc_id_to_real_id(int id) +{ + int real_id; + + if (id >= RPM_VREG_ID_PM8018_L2_PC && id <= RPM_VREG_ID_PM8018_L8_PC) + real_id = id - RPM_VREG_ID_PM8018_L2_PC + RPM_VREG_ID_PM8018_L2; + else + real_id = id - RPM_VREG_ID_PM8018_L13_PC + + RPM_VREG_ID_PM8018_L13; + + return real_id; +} + +static struct vreg_config config = { + .vregs = vregs, + .vregs_len = ARRAY_SIZE(vregs), + + .vreg_id_min = RPM_VREG_ID_PM8018_L2, + .vreg_id_max = RPM_VREG_ID_PM8018_MAX, + + .pin_func_none = RPM_VREG_PIN_FN_9615_NONE, + .pin_func_sleep_b = RPM_VREG_PIN_FN_9615_SLEEP_B, + + .mode_lpm = REGULATOR_MODE_IDLE, + .mode_hpm = REGULATOR_MODE_NORMAL, + + .set_points = all_set_points, + .set_points_len = ARRAY_SIZE(all_set_points), + + .label_pin_ctrl = pin_control_label, + .label_pin_ctrl_len = ARRAY_SIZE(pin_control_label), + .label_pin_func = pin_func_label, + .label_pin_func_len = ARRAY_SIZE(pin_func_label), + .label_force_mode = force_mode_label, + .label_force_mode_len = ARRAY_SIZE(force_mode_label), + .label_power_mode = power_mode_label, + .label_power_mode_len = ARRAY_SIZE(power_mode_label), + + .is_real_id = is_real_id, + .pc_id_to_real_id = pc_id_to_real_id, +}; + +struct vreg_config *get_config_9615(void) +{ + return &config; +} diff --git a/arch/arm/mach-msm/rpm-regulator-private.h b/arch/arm/mach-msm/rpm-regulator-private.h new file mode 100644 index 0000000000000000000000000000000000000000..d4f9a8a1668f4702475f90560ae986daa3c1433a --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-private.h @@ -0,0 +1,185 @@ +/* + * 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_INT_H +#define __ARCH_ARM_MACH_MSM_RPM_REGULATOR_INT_H + +#include +#include +#include + +/* Possible RPM regulator request types */ +enum rpm_regulator_type { + RPM_REGULATOR_TYPE_LDO, + RPM_REGULATOR_TYPE_SMPS, + RPM_REGULATOR_TYPE_VS, + RPM_REGULATOR_TYPE_NCP, + RPM_REGULATOR_TYPE_CORNER, + RPM_REGULATOR_TYPE_MAX = RPM_REGULATOR_TYPE_CORNER, +}; + +struct request_member { + int word; + unsigned int mask; + int shift; +}; + +/* Possible RPM regulator request members */ +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 and SS */ + int request_len; +}; + +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; +}; + +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_desc rdesc; + struct regulator_desc rdesc_pc; + struct regulator_dev *rdev; + struct regulator_dev *rdev_pc; + struct vreg_set_points *set_points; + struct rpm_vreg_parts *part; + int type; + int 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]; +}; + +struct vreg_config { + struct vreg *vregs; + int vregs_len; + + int vreg_id_min; + int vreg_id_max; + + int pin_func_none; + int pin_func_sleep_b; + + unsigned int mode_lpm; + unsigned int mode_hpm; + + struct vreg_set_points **set_points; + int set_points_len; + + const char **label_pin_ctrl; + int label_pin_ctrl_len; + const char **label_pin_func; + int label_pin_func_len; + const char **label_force_mode; + int label_force_mode_len; + const char **label_power_mode; + int label_power_mode_len; + + int (*is_real_id) (int vreg_id); + int (*pc_id_to_real_id) (int vreg_id); + + /* Legacy options to be used with MSM8660 */ + int use_legacy_optimum_mode; + int ia_follows_ip; +}; + +#define REQUEST_MEMBER(_word, _mask, _shift) \ + { \ + .word = _word, \ + .mask = _mask, \ + .shift = _shift, \ + } + +#define VOLTAGE_RANGE(_min_uV, _max_uV, _step_uV) \ + { \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .step_uV = _step_uV, \ + } + +#define SET_POINTS(_ranges) \ +{ \ + .range = _ranges, \ + .count = ARRAY_SIZE(_ranges), \ +}; + +#define MICRO_TO_MILLI(uV) ((uV) / 1000) +#define MILLI_TO_MICRO(mV) ((mV) * 1000) + +#if defined(CONFIG_MSM_RPM_REGULATOR) && defined(CONFIG_ARCH_MSM8X60) +struct vreg_config *get_config_8660(void); +#else +static inline struct vreg_config *get_config_8660(void) +{ + return NULL; +} +#endif + +#if defined(CONFIG_MSM_RPM_REGULATOR) && \ + (defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_APQ8064)) +struct vreg_config *get_config_8960(void); +#else +static inline struct vreg_config *get_config_8960(void) +{ + return NULL; +} +#endif + +#if defined(CONFIG_MSM_RPM_REGULATOR) && defined(CONFIG_ARCH_MSM9615) +struct vreg_config *get_config_9615(void); +#else +static inline struct vreg_config *get_config_9615(void) +{ + return NULL; +} +#endif + +#if defined(CONFIG_MSM_RPM_REGULATOR) && defined(CONFIG_ARCH_MSM8930) +struct vreg_config *get_config_8930(void); +#else +static inline struct vreg_config *get_config_8930(void) +{ + return NULL; +} +#endif + +#endif diff --git a/arch/arm/mach-msm/rpm-regulator-smd.c b/arch/arm/mach-msm/rpm-regulator-smd.c new file mode 100644 index 0000000000000000000000000000000000000000..b892d059ec7ded563babf2417ecff0d2ee80c7dc --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-smd.c @@ -0,0 +1,1430 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* Debug Definitions */ + +enum { + RPM_VREG_DEBUG_REQUEST = BIT(0), + RPM_VREG_DEBUG_FULL_REQUEST = BIT(1), + RPM_VREG_DEBUG_DUPLICATE = BIT(2), +}; + +static int rpm_vreg_debug_mask; +module_param_named( + debug_mask, rpm_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +#define vreg_err(req, fmt, ...) \ + pr_err("%s: " fmt, req->rdesc.name, ##__VA_ARGS__) + +/* RPM regulator request types */ +enum rpm_regulator_smd_type { + RPM_REGULATOR_SMD_TYPE_LDO, + RPM_REGULATOR_SMD_TYPE_SMPS, + RPM_REGULATOR_SMD_TYPE_VS, + RPM_REGULATOR_SMD_TYPE_NCP, + RPM_REGULATOR_SMD_TYPE_MAX, +}; + +/* RPM resource parameters */ +enum rpm_regulator_param_index { + RPM_REGULATOR_PARAM_ENABLE, + RPM_REGULATOR_PARAM_VOLTAGE, + RPM_REGULATOR_PARAM_CURRENT, + RPM_REGULATOR_PARAM_MODE_LDO, + RPM_REGULATOR_PARAM_MODE_SMPS, + RPM_REGULATOR_PARAM_PIN_CTRL_ENABLE, + RPM_REGULATOR_PARAM_PIN_CTRL_MODE, + RPM_REGULATOR_PARAM_FREQUENCY, + RPM_REGULATOR_PARAM_HEAD_ROOM, + RPM_REGULATOR_PARAM_QUIET_MODE, + RPM_REGULATOR_PARAM_FREQ_REASON, + RPM_REGULATOR_PARAM_MAX, +}; + +#define RPM_SET_CONFIG_ACTIVE BIT(0) +#define RPM_SET_CONFIG_SLEEP BIT(1) +#define RPM_SET_CONFIG_BOTH (RPM_SET_CONFIG_ACTIVE \ + | RPM_SET_CONFIG_SLEEP) +struct rpm_regulator_param { + char *name; + char *property_name; + u32 key; + u32 min; + u32 max; + u32 supported_regulator_types; +}; + +#define PARAM(_idx, _support_ldo, _support_smps, _support_vs, _support_ncp, \ + _name, _min, _max, _property_name) \ + [RPM_REGULATOR_PARAM_##_idx] = { \ + .name = _name, \ + .property_name = _property_name, \ + .min = _min, \ + .max = _max, \ + .supported_regulator_types = \ + _support_ldo << RPM_REGULATOR_SMD_TYPE_LDO | \ + _support_smps << RPM_REGULATOR_SMD_TYPE_SMPS | \ + _support_vs << RPM_REGULATOR_SMD_TYPE_VS | \ + _support_ncp << RPM_REGULATOR_SMD_TYPE_NCP, \ + } + +static struct rpm_regulator_param params[RPM_REGULATOR_PARAM_MAX] = { + /* ID LDO SMPS VS NCP name min max property-name */ + PARAM(ENABLE, 1, 1, 1, 1, "swen", 0, 1, "qcom,init-enable"), + PARAM(VOLTAGE, 1, 1, 0, 1, "uv", 0, 0x7FFFFFF, "qcom,init-voltage"), + PARAM(CURRENT, 1, 1, 0, 0, "ma", 0, 0x1FFF, "qcom,init-current"), + PARAM(MODE_LDO, 1, 0, 0, 0, "lsmd", 0, 1, "qcom,init-ldo-mode"), + PARAM(MODE_SMPS, 0, 1, 0, 0, "ssmd", 0, 2, "qcom,init-smps-mode"), + PARAM(PIN_CTRL_ENABLE, 1, 1, 1, 0, "pcen", 0, 0xF, "qcom,init-pin-ctrl-enable"), + PARAM(PIN_CTRL_MODE, 1, 1, 1, 0, "pcmd", 0, 0x1F, "qcom,init-pin-ctrl-mode"), + PARAM(FREQUENCY, 0, 1, 0, 1, "freq", 0, 16, "qcom,init-frequency"), + PARAM(HEAD_ROOM, 1, 0, 0, 1, "hr", 0, 0x7FFFFFFF, "qcom,init-head-room"), + PARAM(QUIET_MODE, 0, 1, 0, 0, "qm", 0, 2, "qcom,init-quiet-mode"), + PARAM(FREQ_REASON, 0, 1, 0, 1, "resn", 0, 8, "qcom,init-freq-reason"), +}; + +struct rpm_vreg_request { + u32 param[RPM_REGULATOR_PARAM_MAX]; + u32 valid; + u32 modified; +}; + +struct rpm_vreg { + struct rpm_vreg_request aggr_req_active; + struct rpm_vreg_request aggr_req_sleep; + struct list_head reg_list; + const char *resource_name; + u32 resource_id; + bool allow_atomic; + int regulator_type; + int hpm_min_load; + int enable_time; + struct spinlock slock; + struct mutex mlock; + unsigned long flags; + bool sleep_request_sent; + struct msm_rpm_request *handle_active; + struct msm_rpm_request *handle_sleep; +}; + +struct rpm_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; + struct rpm_vreg *rpm_vreg; + struct list_head list; + bool set_active; + bool set_sleep; + struct rpm_vreg_request req; + int system_load; + int min_uV; + int max_uV; +}; + +/* + * 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 + +/* + * Regulator requests sent in the active set take effect immediately. Requests + * sent in the sleep set take effect when the Apps processor transitions into + * RPM assisted power collapse. For any given regulator, if an active set + * request is present, but not a sleep set request, then the active set request + * is used at all times, even when the Apps processor is power collapsed. + * + * The rpm-regulator-smd takes advantage of this default usage of the active set + * request by only sending a sleep set request if it differs from the + * corresponding active set request. + */ +#define RPM_SET_ACTIVE MSM_RPM_CTX_ACTIVE_SET +#define RPM_SET_SLEEP MSM_RPM_CTX_SLEEP_SET + +static u32 rpm_vreg_string_to_int(const u8 *str) +{ + int i, len; + u32 output = 0; + + len = strnlen(str, sizeof(u32)); + for (i = 0; i < len; i++) + output |= str[i] << (i * 8); + + return output; +} + +static inline void rpm_vreg_lock(struct rpm_vreg *rpm_vreg) +{ + if (rpm_vreg->allow_atomic) + spin_lock_irqsave(&rpm_vreg->slock, rpm_vreg->flags); + else + mutex_lock(&rpm_vreg->mlock); +} + +static inline void rpm_vreg_unlock(struct rpm_vreg *rpm_vreg) +{ + if (rpm_vreg->allow_atomic) + spin_unlock_irqrestore(&rpm_vreg->slock, rpm_vreg->flags); + else + mutex_unlock(&rpm_vreg->mlock); +} + +static inline bool rpm_vreg_active_or_sleep_enabled(struct rpm_vreg *rpm_vreg) +{ + return (rpm_vreg->aggr_req_active.param[RPM_REGULATOR_PARAM_ENABLE] + && (rpm_vreg->aggr_req_active.valid + & BIT(RPM_REGULATOR_PARAM_ENABLE))) + || ((rpm_vreg->aggr_req_sleep.param[RPM_REGULATOR_PARAM_ENABLE]) + && (rpm_vreg->aggr_req_sleep.valid + & BIT(RPM_REGULATOR_PARAM_ENABLE))); +} + +/* + * 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 + +static inline int rpm_vreg_hpm_min_uA(struct rpm_vreg *rpm_vreg) +{ + return rpm_vreg->hpm_min_load; +} + +static inline int rpm_vreg_lpm_max_uA(struct rpm_vreg *rpm_vreg) +{ + return rpm_vreg->hpm_min_load - LOAD_THRESHOLD_STEP; +} + +#define MICRO_TO_MILLI(uV) ((uV) / 1000) +#define MILLI_TO_MICRO(uV) ((uV) * 1000) + +#define DEBUG_PRINT_BUFFER_SIZE 512 +#define REQ_SENT 0 +#define REQ_PREV 1 +#define REQ_CACHED 2 +#define REQ_TYPES 3 + +static void rpm_regulator_req(struct rpm_regulator *regulator, int set, + bool sent) +{ + char buf[DEBUG_PRINT_BUFFER_SIZE]; + size_t buflen = DEBUG_PRINT_BUFFER_SIZE; + struct rpm_vreg *rpm_vreg = regulator->rpm_vreg; + struct rpm_vreg_request *aggr; + bool first; + u32 mask[REQ_TYPES] = {0, 0, 0}; + const char *req_names[REQ_TYPES] = {"sent", "prev", "cached"}; + int pos = 0; + int i, j; + + aggr = (set == RPM_SET_ACTIVE) + ? &rpm_vreg->aggr_req_active : &rpm_vreg->aggr_req_sleep; + + if (rpm_vreg_debug_mask & RPM_VREG_DEBUG_DUPLICATE) { + mask[REQ_SENT] = aggr->modified; + mask[REQ_PREV] = aggr->valid & ~aggr->modified; + } else if (sent + && (rpm_vreg_debug_mask & RPM_VREG_DEBUG_FULL_REQUEST)) { + mask[REQ_SENT] = aggr->modified; + mask[REQ_PREV] = aggr->valid & ~aggr->modified; + } else if (sent && (rpm_vreg_debug_mask & RPM_VREG_DEBUG_REQUEST)) { + mask[REQ_SENT] = aggr->modified; + } + + if (!(mask[REQ_SENT] | mask[REQ_PREV])) + return; + + if (set == RPM_SET_SLEEP && !rpm_vreg->sleep_request_sent) { + mask[REQ_CACHED] = mask[REQ_SENT] | mask[REQ_PREV]; + mask[REQ_SENT] = 0; + mask[REQ_PREV] = 0; + } + + pos += scnprintf(buf + pos, buflen - pos, "%s%s: ", + KERN_INFO, __func__); + + pos += scnprintf(buf + pos, buflen - pos, "%s %u (%s): s=%s", + rpm_vreg->resource_name, rpm_vreg->resource_id, + regulator->rdesc.name, + (set == RPM_SET_ACTIVE ? "act" : "slp")); + + for (i = 0; i < REQ_TYPES; i++) { + if (mask[i]) + pos += scnprintf(buf + pos, buflen - pos, "; %s: ", + req_names[i]); + + first = true; + for (j = 0; j < RPM_REGULATOR_PARAM_MAX; j++) { + if (mask[i] & BIT(j)) { + pos += scnprintf(buf + pos, buflen - pos, + "%s%s=%u", (first ? "" : ", "), + params[j].name, aggr->param[j]); + first = false; + } + } + } + + pos += scnprintf(buf + pos, buflen - pos, "\n"); + printk(buf); +} + +#define RPM_VREG_SET_PARAM(_regulator, _param, _val) \ +{ \ + (_regulator)->req.param[RPM_REGULATOR_PARAM_##_param] = _val; \ + (_regulator)->req.modified |= BIT(RPM_REGULATOR_PARAM_##_param); \ +} \ + +static int rpm_vreg_add_kvp_to_request(struct rpm_vreg *rpm_vreg, + const u32 *param, int idx, u32 set) +{ + struct msm_rpm_request *handle; + + handle = (set == RPM_SET_ACTIVE ? rpm_vreg->handle_active + : rpm_vreg->handle_sleep); + + if (rpm_vreg->allow_atomic) + return msm_rpm_add_kvp_data_noirq(handle, params[idx].key, + (u8 *)¶m[idx], 4); + else + return msm_rpm_add_kvp_data(handle, params[idx].key, + (u8 *)¶m[idx], 4); +} + +static void rpm_vreg_check_modified_requests(const u32 *prev_param, + const u32 *param, u32 prev_valid, u32 *modified) +{ + u32 value_changed = 0; + int i; + + for (i = 0; i < RPM_REGULATOR_PARAM_MAX; i++) { + if (param[i] != prev_param[i]) + value_changed |= BIT(i); + } + + /* + * Only keep bits that are for changed parameters or previously + * invalid parameters. + */ + *modified &= value_changed | ~prev_valid; +} + +static int rpm_vreg_add_modified_requests(struct rpm_regulator *regulator, + u32 set, const u32 *param, u32 modified) +{ + struct rpm_vreg *rpm_vreg = regulator->rpm_vreg; + int rc = 0; + int i; + + for (i = 0; i < RPM_REGULATOR_PARAM_MAX; i++) { + /* Only send requests for modified parameters. */ + if (modified & BIT(i)) { + rc = rpm_vreg_add_kvp_to_request(rpm_vreg, param, i, + set); + if (rc) { + vreg_err(regulator, + "add KVP failed: %s %u; %s, rc=%d\n", + rpm_vreg->resource_name, + rpm_vreg->resource_id, params[i].name, + rc); + return rc; + } + } + } + + return rc; +} + +static int rpm_vreg_send_request(struct rpm_regulator *regulator, u32 set) +{ + struct rpm_vreg *rpm_vreg = regulator->rpm_vreg; + struct msm_rpm_request *handle + = (set == RPM_SET_ACTIVE ? rpm_vreg->handle_active + : rpm_vreg->handle_sleep); + int rc; + + if (rpm_vreg->allow_atomic) + rc = msm_rpm_wait_for_ack_noirq(msm_rpm_send_request_noirq( + handle)); + else + rc = msm_rpm_wait_for_ack(msm_rpm_send_request(handle)); + + if (rc) + vreg_err(regulator, "msm rpm send failed: %s %u; set=%s, " + "rc=%d\n", rpm_vreg->resource_name, + rpm_vreg->resource_id, + (set == RPM_SET_ACTIVE ? "act" : "slp"), rc); + + return rc; +} + +#define RPM_VREG_AGGR_MAX(_idx, _param_aggr, _param_reg) \ +{ \ + _param_aggr[RPM_REGULATOR_PARAM_##_idx] \ + = max(_param_aggr[RPM_REGULATOR_PARAM_##_idx], \ + _param_reg[RPM_REGULATOR_PARAM_##_idx]); \ +} + +#define RPM_VREG_AGGR_SUM(_idx, _param_aggr, _param_reg) \ +{ \ + _param_aggr[RPM_REGULATOR_PARAM_##_idx] \ + += _param_reg[RPM_REGULATOR_PARAM_##_idx]; \ +} + +#define RPM_VREG_AGGR_OR(_idx, _param_aggr, _param_reg) \ +{ \ + _param_aggr[RPM_REGULATOR_PARAM_##_idx] \ + |= _param_reg[RPM_REGULATOR_PARAM_##_idx]; \ +} + +/* + * The RPM treats freq=0 as a special value meaning that this consumer does not + * care what the SMPS switching freqency is. + */ +#define RPM_REGULATOR_FREQ_DONT_CARE 0 + +static inline void rpm_vreg_freqency_aggr(u32 *freq, u32 consumer_freq) +{ + if (consumer_freq != RPM_REGULATOR_FREQ_DONT_CARE + && (consumer_freq < *freq + || *freq == RPM_REGULATOR_FREQ_DONT_CARE)) + *freq = consumer_freq; +} + +/* + * Aggregation is performed on each parameter based on the way that the RPM + * aggregates that type internally between RPM masters. + */ +static void rpm_vreg_aggregate_params(u32 *param_aggr, const u32 *param_reg) +{ + RPM_VREG_AGGR_MAX(ENABLE, param_aggr, param_reg); + RPM_VREG_AGGR_MAX(VOLTAGE, param_aggr, param_reg); + RPM_VREG_AGGR_SUM(CURRENT, param_aggr, param_reg); + RPM_VREG_AGGR_MAX(MODE_LDO, param_aggr, param_reg); + RPM_VREG_AGGR_MAX(MODE_SMPS, param_aggr, param_reg); + RPM_VREG_AGGR_OR(PIN_CTRL_ENABLE, param_aggr, param_reg); + RPM_VREG_AGGR_OR(PIN_CTRL_MODE, param_aggr, param_reg); + rpm_vreg_freqency_aggr(¶m_aggr[RPM_REGULATOR_PARAM_FREQUENCY], + param_reg[RPM_REGULATOR_PARAM_FREQUENCY]); + RPM_VREG_AGGR_MAX(HEAD_ROOM, param_aggr, param_reg); + RPM_VREG_AGGR_MAX(QUIET_MODE, param_aggr, param_reg); + RPM_VREG_AGGR_MAX(FREQ_REASON, param_aggr, param_reg); +} + +static int rpm_vreg_aggregate_requests(struct rpm_regulator *regulator) +{ + struct rpm_vreg *rpm_vreg = regulator->rpm_vreg; + u32 param_active[RPM_REGULATOR_PARAM_MAX]; + u32 param_sleep[RPM_REGULATOR_PARAM_MAX]; + u32 modified_active, modified_sleep; + struct rpm_regulator *reg; + bool sleep_set_differs = false; + bool send_active = false; + bool send_sleep = false; + int rc = 0; + int i; + + memset(param_active, 0, sizeof(param_active)); + memset(param_sleep, 0, sizeof(param_sleep)); + modified_active = rpm_vreg->aggr_req_active.modified; + modified_sleep = rpm_vreg->aggr_req_sleep.modified; + + /* + * Aggregate all of the requests for this regulator in both active + * and sleep sets. + */ + list_for_each_entry(reg, &rpm_vreg->reg_list, list) { + if (reg->set_active) { + rpm_vreg_aggregate_params(param_active, reg->req.param); + modified_active |= reg->req.modified; + } + if (reg->set_sleep) { + rpm_vreg_aggregate_params(param_sleep, reg->req.param); + modified_sleep |= reg->req.modified; + } + } + + /* + * Check if the aggregated sleep set parameter values differ from the + * aggregated active set parameter values. + */ + if (!rpm_vreg->sleep_request_sent) { + for (i = 0; i < RPM_REGULATOR_PARAM_MAX; i++) { + if ((param_active[i] != param_sleep[i]) + && (modified_sleep & BIT(i))) { + sleep_set_differs = true; + break; + } + } + } + + /* Add KVPs to the active set RPM request if they have new values. */ + rpm_vreg_check_modified_requests(rpm_vreg->aggr_req_active.param, + param_active, rpm_vreg->aggr_req_active.valid, + &modified_active); + rc = rpm_vreg_add_modified_requests(regulator, RPM_SET_ACTIVE, + param_active, modified_active); + if (rc) + return rc; + send_active = modified_active; + + /* + * Sleep set configurations are only sent if they differ from the + * active set values. This is because the active set values will take + * effect during rpm assisted power collapse in the absence of sleep set + * values. + * + * However, once a sleep set request is sent for a given regulator, + * additional sleep set requests must be sent in the future even if they + * match the corresponding active set requests. + */ + if (rpm_vreg->sleep_request_sent || sleep_set_differs) { + /* Add KVPs to the sleep set RPM request if they are new. */ + rpm_vreg_check_modified_requests(rpm_vreg->aggr_req_sleep.param, + param_sleep, rpm_vreg->aggr_req_sleep.valid, + &modified_sleep); + rc = rpm_vreg_add_modified_requests(regulator, RPM_SET_SLEEP, + param_sleep, modified_sleep); + if (rc) + return rc; + send_sleep = modified_sleep; + } + + /* Send active set request to the RPM if it contains new KVPs. */ + if (send_active) { + rc = rpm_vreg_send_request(regulator, RPM_SET_ACTIVE); + if (rc) + return rc; + rpm_vreg->aggr_req_active.valid |= modified_active; + } + /* Store the results of the aggregation. */ + rpm_vreg->aggr_req_active.modified = modified_active; + memcpy(rpm_vreg->aggr_req_active.param, param_active, + sizeof(param_active)); + + /* Handle debug printing of the active set request. */ + rpm_regulator_req(regulator, RPM_SET_ACTIVE, send_active); + if (send_active) + rpm_vreg->aggr_req_active.modified = 0; + + /* Send sleep set request to the RPM if it contains new KVPs. */ + if (send_sleep) { + rc = rpm_vreg_send_request(regulator, RPM_SET_SLEEP); + if (rc) + return rc; + else + rpm_vreg->sleep_request_sent = true; + rpm_vreg->aggr_req_sleep.valid |= modified_sleep; + } + /* Store the results of the aggregation. */ + rpm_vreg->aggr_req_sleep.modified = modified_sleep; + memcpy(rpm_vreg->aggr_req_sleep.param, param_sleep, + sizeof(param_sleep)); + + /* Handle debug printing of the sleep set request. */ + rpm_regulator_req(regulator, RPM_SET_SLEEP, send_sleep); + if (send_sleep) + rpm_vreg->aggr_req_sleep.modified = 0; + + /* + * Loop over all requests for this regulator to update the valid and + * modified values for use in future aggregation. + */ + list_for_each_entry(reg, &rpm_vreg->reg_list, list) { + reg->req.valid |= reg->req.modified; + reg->req.modified = 0; + } + + return rc; +} + +static int rpm_vreg_is_enabled(struct regulator_dev *rdev) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + + return reg->req.param[RPM_REGULATOR_PARAM_ENABLE]; +} + +static int rpm_vreg_enable(struct regulator_dev *rdev) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + int rc; + u32 prev_enable; + + rpm_vreg_lock(reg->rpm_vreg); + + prev_enable = reg->req.param[RPM_REGULATOR_PARAM_ENABLE]; + RPM_VREG_SET_PARAM(reg, ENABLE, 1); + rc = rpm_vreg_aggregate_requests(reg); + if (rc) { + vreg_err(reg, "enable failed, rc=%d", rc); + RPM_VREG_SET_PARAM(reg, ENABLE, prev_enable); + } + + rpm_vreg_unlock(reg->rpm_vreg); + + return rc; +} + +static int rpm_vreg_disable(struct regulator_dev *rdev) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + int rc; + u32 prev_enable; + + rpm_vreg_lock(reg->rpm_vreg); + + prev_enable = reg->req.param[RPM_REGULATOR_PARAM_ENABLE]; + RPM_VREG_SET_PARAM(reg, ENABLE, 0); + rc = rpm_vreg_aggregate_requests(reg); + if (rc) { + vreg_err(reg, "enable failed, rc=%d", rc); + RPM_VREG_SET_PARAM(reg, ENABLE, prev_enable); + } + + rpm_vreg_unlock(reg->rpm_vreg); + + return rc; +} + +static int rpm_vreg_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + int rc = 0; + u32 prev_voltage; + + rpm_vreg_lock(reg->rpm_vreg); + + prev_voltage = reg->req.param[RPM_REGULATOR_PARAM_VOLTAGE]; + RPM_VREG_SET_PARAM(reg, VOLTAGE, min_uV); + + /* Only send a new voltage if the regulator is currently enabled. */ + if (rpm_vreg_active_or_sleep_enabled(reg->rpm_vreg)) + rc = rpm_vreg_aggregate_requests(reg); + + if (rc) { + vreg_err(reg, "set voltage failed, rc=%d", rc); + RPM_VREG_SET_PARAM(reg, VOLTAGE, prev_voltage); + } + + rpm_vreg_unlock(reg->rpm_vreg); + + return rc; +} + +static int rpm_vreg_get_voltage(struct regulator_dev *rdev) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + int uV; + + uV = reg->req.param[RPM_REGULATOR_PARAM_VOLTAGE]; + if (uV == 0) + uV = VOLTAGE_UNKNOWN; + + return uV; +} + +static int rpm_vreg_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + int uV = 0; + + if (selector == 0) + uV = reg->min_uV; + else if (selector == 1) + uV = reg->max_uV; + + return uV; +} + +static int rpm_vreg_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + int rc = 0; + u32 prev_current; + int prev_uA; + + rpm_vreg_lock(reg->rpm_vreg); + + prev_current = reg->req.param[RPM_REGULATOR_PARAM_CURRENT]; + prev_uA = MILLI_TO_MICRO(prev_current); + + if (mode == REGULATOR_MODE_NORMAL) { + /* Make sure that request current is in HPM range. */ + if (prev_uA < rpm_vreg_hpm_min_uA(reg->rpm_vreg)) + RPM_VREG_SET_PARAM(reg, CURRENT, + MICRO_TO_MILLI(rpm_vreg_hpm_min_uA(reg->rpm_vreg))); + } else if (REGULATOR_MODE_IDLE) { + /* Make sure that request current is in LPM range. */ + if (prev_uA > rpm_vreg_lpm_max_uA(reg->rpm_vreg)) + RPM_VREG_SET_PARAM(reg, CURRENT, + MICRO_TO_MILLI(rpm_vreg_lpm_max_uA(reg->rpm_vreg))); + } else { + vreg_err(reg, "invalid mode: %u\n", mode); + rpm_vreg_unlock(reg->rpm_vreg); + return -EINVAL; + } + + /* Only send a new mode value if the regulator is currently enabled. */ + if (rpm_vreg_active_or_sleep_enabled(reg->rpm_vreg)) + rc = rpm_vreg_aggregate_requests(reg); + + if (rc) { + vreg_err(reg, "set mode failed, rc=%d", rc); + RPM_VREG_SET_PARAM(reg, CURRENT, prev_current); + } + + rpm_vreg_unlock(reg->rpm_vreg); + + return rc; +} + +static unsigned int rpm_vreg_get_mode(struct regulator_dev *rdev) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + + return (reg->req.param[RPM_REGULATOR_PARAM_CURRENT] + >= MICRO_TO_MILLI(reg->rpm_vreg->hpm_min_load)) + ? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE; +} + +static unsigned int rpm_vreg_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + u32 load_mA; + + load_uA += reg->system_load; + + load_mA = MICRO_TO_MILLI(load_uA); + if (load_mA > params[RPM_REGULATOR_PARAM_CURRENT].max) + load_mA = params[RPM_REGULATOR_PARAM_CURRENT].max; + + rpm_vreg_lock(reg->rpm_vreg); + RPM_VREG_SET_PARAM(reg, CURRENT, MICRO_TO_MILLI(load_uA)); + rpm_vreg_unlock(reg->rpm_vreg); + + return (load_uA >= reg->rpm_vreg->hpm_min_load) + ? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE; +} + +static int rpm_vreg_enable_time(struct regulator_dev *rdev) +{ + struct rpm_regulator *reg = rdev_get_drvdata(rdev); + + return reg->rpm_vreg->enable_time; +} + +/** + * rpm_regulator_get() - lookup and obtain a handle to an RPM regulator + * @dev: device for regulator consumer + * @supply: supply name + * + * Returns a struct rpm_regulator corresponding to the regulator producer, + * or ERR_PTR() containing errno. + * + * This function may only be called from nonatomic context. + */ +struct rpm_regulator *rpm_regulator_get(struct device *dev, const char *supply) +{ + struct rpm_regulator *framework_reg; + struct rpm_regulator *priv_reg = NULL; + struct regulator *regulator; + struct rpm_vreg *rpm_vreg; + + regulator = regulator_get(dev, supply); + if (regulator == NULL) { + pr_err("could not find regulator for: dev=%s, id=%s\n", + (dev ? dev_name(dev) : ""), (supply ? supply : "")); + return ERR_PTR(-ENODEV); + } + + framework_reg = regulator_get_drvdata(regulator); + if (framework_reg == NULL) { + pr_err("regulator structure not found.\n"); + regulator_put(regulator); + return ERR_PTR(-ENODEV); + } + regulator_put(regulator); + + rpm_vreg = framework_reg->rpm_vreg; + + priv_reg = kzalloc(sizeof(struct rpm_regulator), GFP_KERNEL); + if (priv_reg == NULL) { + vreg_err(framework_reg, "could not allocate memory for " + "regulator\n"); + rpm_vreg_unlock(rpm_vreg); + return ERR_PTR(-ENOMEM); + } + + /* + * Allocate a regulator_dev struct so that framework callback functions + * can be called from the private API functions. + */ + priv_reg->rdev = kzalloc(sizeof(struct regulator_dev), GFP_KERNEL); + if (priv_reg->rdev == NULL) { + vreg_err(framework_reg, "could not allocate memory for " + "regulator_dev\n"); + kfree(priv_reg); + rpm_vreg_unlock(rpm_vreg); + return ERR_PTR(-ENOMEM); + } + priv_reg->rdev->reg_data = priv_reg; + priv_reg->rpm_vreg = rpm_vreg; + priv_reg->rdesc.name = framework_reg->rdesc.name; + priv_reg->set_active = framework_reg->set_active; + priv_reg->set_sleep = framework_reg->set_sleep; + priv_reg->min_uV = framework_reg->min_uV; + priv_reg->max_uV = framework_reg->max_uV; + priv_reg->system_load = framework_reg->system_load; + + might_sleep_if(!rpm_vreg->allow_atomic); + rpm_vreg_lock(rpm_vreg); + list_add(&priv_reg->list, &rpm_vreg->reg_list); + rpm_vreg_unlock(rpm_vreg); + + return priv_reg; +} +EXPORT_SYMBOL_GPL(rpm_regulator_get); + +static int rpm_regulator_check_input(struct rpm_regulator *regulator) +{ + if (regulator == NULL || regulator->rpm_vreg == NULL) { + pr_err("invalid rpm_regulator pointer\n"); + return -EINVAL; + } + + might_sleep_if(!regulator->rpm_vreg->allow_atomic); + + return 0; +} + +/** + * rpm_regulator_put() - free the RPM regulator handle + * @regulator: RPM regulator handle + * + * Parameter reaggregation does not take place when rpm_regulator_put is called. + * Therefore, regulator enable state and voltage must be configured + * appropriately before calling rpm_regulator_put. + * + * This function may be called from either atomic or nonatomic context. If this + * function is called from atomic context, then the regulator being operated on + * must be configured via device tree with qcom,allow-atomic == 1. + */ +void rpm_regulator_put(struct rpm_regulator *regulator) +{ + struct rpm_vreg *rpm_vreg; + int rc = rpm_regulator_check_input(regulator); + + if (rc) + return; + + rpm_vreg = regulator->rpm_vreg; + + might_sleep_if(!rpm_vreg->allow_atomic); + rpm_vreg_lock(rpm_vreg); + list_del(®ulator->list); + rpm_vreg_unlock(rpm_vreg); + + kfree(regulator->rdev); + kfree(regulator); +} +EXPORT_SYMBOL_GPL(rpm_regulator_put); + +/** + * rpm_regulator_enable() - enable regulator output + * @regulator: RPM regulator handle + * + * Returns 0 on success or errno on failure. + * + * This function may be called from either atomic or nonatomic context. If this + * function is called from atomic context, then the regulator being operated on + * must be configured via device tree with qcom,allow-atomic == 1. + */ +int rpm_regulator_enable(struct rpm_regulator *regulator) +{ + int rc = rpm_regulator_check_input(regulator); + + if (rc) + return rc; + + return rpm_vreg_enable(regulator->rdev); +} +EXPORT_SYMBOL_GPL(rpm_regulator_enable); + +/** + * rpm_regulator_disable() - disable regulator output + * @regulator: RPM regulator handle + * + * Returns 0 on success or errno on failure. + * + * The enable state of the regulator is determined by aggregating the requests + * of all consumers. Therefore, it is possible that the regulator will remain + * enabled even after rpm_regulator_disable is called. + * + * This function may be called from either atomic or nonatomic context. If this + * function is called from atomic context, then the regulator being operated on + * must be configured via device tree with qcom,allow-atomic == 1. + */ +int rpm_regulator_disable(struct rpm_regulator *regulator) +{ + int rc = rpm_regulator_check_input(regulator); + + if (rc) + return rc; + + return rpm_vreg_disable(regulator->rdev); +} +EXPORT_SYMBOL_GPL(rpm_regulator_disable); + +/** + * rpm_regulator_set_voltage() - set regulator output voltage + * @regulator: RPM regulator handle + * @min_uV: minimum required voltage in uV + * @max_uV: maximum acceptable voltage in uV + * + * Sets a voltage regulator to the desired output voltage. This can be set + * while the regulator is disabled or enabled. If the regulator is enabled then + * the voltage will change to the new value immediately; otherwise, if the + * regulator is disabled, then the regulator will output at the new voltage when + * enabled. + * + * The min_uV to max_uV voltage range requested must intersect with the + * voltage constraint range configured for the regulator. + * + * Returns 0 on success or errno on failure. + * + * The final voltage value that is sent to the RPM is aggregated based upon the + * values requested by all consumers of the regulator. This corresponds to the + * maximum min_uV value. + * + * This function may be called from either atomic or nonatomic context. If this + * function is called from atomic context, then the regulator being operated on + * must be configured via device tree with qcom,allow-atomic == 1. + */ +int rpm_regulator_set_voltage(struct rpm_regulator *regulator, int min_uV, + int max_uV) +{ + int rc = rpm_regulator_check_input(regulator); + int uV = min_uV; + + if (rc) + return rc; + + if (regulator->rpm_vreg->regulator_type == RPM_REGULATOR_SMD_TYPE_VS) { + vreg_err(regulator, "unsupported regulator type: %d\n", + regulator->rpm_vreg->regulator_type); + return -EINVAL; + } + + if (min_uV > max_uV) { + vreg_err(regulator, "min_uV=%d must be less than max_uV=%d\n", + min_uV, max_uV); + return -EINVAL; + } + + if (uV < regulator->min_uV && max_uV >= regulator->min_uV) + uV = regulator->min_uV; + + if (uV < regulator->min_uV || uV > regulator->max_uV) { + vreg_err(regulator, "request v=[%d, %d] is outside allowed " + "v=[%d, %d]\n", min_uV, max_uV, regulator->min_uV, + regulator->max_uV); + return -EINVAL; + } + + return rpm_vreg_set_voltage(regulator->rdev, uV, uV, NULL); +} +EXPORT_SYMBOL_GPL(rpm_regulator_set_voltage); + +static struct regulator_ops ldo_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = rpm_vreg_is_enabled, + .set_voltage = rpm_vreg_set_voltage, + .get_voltage = rpm_vreg_get_voltage, + .list_voltage = rpm_vreg_list_voltage, + .set_mode = rpm_vreg_set_mode, + .get_mode = rpm_vreg_get_mode, + .get_optimum_mode = rpm_vreg_get_optimum_mode, + .enable_time = rpm_vreg_enable_time, +}; + +static struct regulator_ops smps_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = rpm_vreg_is_enabled, + .set_voltage = rpm_vreg_set_voltage, + .get_voltage = rpm_vreg_get_voltage, + .list_voltage = rpm_vreg_list_voltage, + .set_mode = rpm_vreg_set_mode, + .get_mode = rpm_vreg_get_mode, + .get_optimum_mode = rpm_vreg_get_optimum_mode, + .enable_time = rpm_vreg_enable_time, +}; + +static struct regulator_ops switch_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = rpm_vreg_is_enabled, + .enable_time = rpm_vreg_enable_time, +}; + +static struct regulator_ops ncp_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = rpm_vreg_is_enabled, + .set_voltage = rpm_vreg_set_voltage, + .get_voltage = rpm_vreg_get_voltage, + .list_voltage = rpm_vreg_list_voltage, + .enable_time = rpm_vreg_enable_time, +}; + +static struct regulator_ops *vreg_ops[] = { + [RPM_REGULATOR_SMD_TYPE_LDO] = &ldo_ops, + [RPM_REGULATOR_SMD_TYPE_SMPS] = &smps_ops, + [RPM_REGULATOR_SMD_TYPE_VS] = &switch_ops, + [RPM_REGULATOR_SMD_TYPE_NCP] = &ncp_ops, +}; + +static int __devexit rpm_vreg_device_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpm_regulator *reg; + + reg = platform_get_drvdata(pdev); + if (reg) { + rpm_vreg_lock(reg->rpm_vreg); + regulator_unregister(reg->rdev); + list_del(®->list); + kfree(reg); + rpm_vreg_unlock(reg->rpm_vreg); + } else { + dev_err(dev, "%s: drvdata missing\n", __func__); + return -EINVAL; + } + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static int __devexit rpm_vreg_resource_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpm_regulator *reg, *reg_temp; + struct rpm_vreg *rpm_vreg; + + rpm_vreg = platform_get_drvdata(pdev); + if (rpm_vreg) { + rpm_vreg_lock(rpm_vreg); + list_for_each_entry_safe(reg, reg_temp, &rpm_vreg->reg_list, + list) { + /* Only touch data for private consumers. */ + if (reg->rdev->desc == NULL) { + list_del(®->list); + kfree(reg->rdev); + kfree(reg); + } else { + dev_err(dev, "%s: not all child devices have " + "been removed\n", __func__); + } + } + rpm_vreg_unlock(rpm_vreg); + + msm_rpm_free_request(rpm_vreg->handle_active); + msm_rpm_free_request(rpm_vreg->handle_sleep); + + kfree(rpm_vreg); + } else { + dev_err(dev, "%s: drvdata missing\n", __func__); + return -EINVAL; + } + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* + * This probe is called for child rpm-regulator devices which have + * properties which are required to configure individual regulator + * framework regulators for a given RPM regulator resource. + */ +static int __devinit rpm_vreg_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct regulator_init_data *init_data; + struct rpm_vreg *rpm_vreg; + struct rpm_regulator *reg; + int rc = 0; + int i, regulator_type; + u32 val; + + if (!dev->of_node) { + dev_err(dev, "%s: device tree information missing\n", __func__); + return -ENODEV; + } + + if (pdev->dev.parent == NULL) { + dev_err(dev, "%s: parent device missing\n", __func__); + return -ENODEV; + } + + rpm_vreg = dev_get_drvdata(pdev->dev.parent); + if (rpm_vreg == NULL) { + dev_err(dev, "%s: rpm_vreg not found in parent device\n", + __func__); + return -ENODEV; + } + + reg = kzalloc(sizeof(struct rpm_regulator), GFP_KERNEL); + if (reg == NULL) { + dev_err(dev, "%s: could not allocate memory for reg\n", + __func__); + return -ENOMEM; + } + + regulator_type = rpm_vreg->regulator_type; + reg->rpm_vreg = rpm_vreg; + reg->rdesc.ops = vreg_ops[regulator_type]; + reg->rdesc.owner = THIS_MODULE; + reg->rdesc.type = REGULATOR_VOLTAGE; + + if (regulator_type == RPM_REGULATOR_SMD_TYPE_VS) + reg->rdesc.n_voltages = 0; + else + reg->rdesc.n_voltages = 2; + + rc = of_property_read_u32(node, "qcom,set", &val); + if (rc) { + dev_err(dev, "%s: sleep set and/or active set must be " + "configured via qcom,set property, rc=%d\n", __func__, + rc); + goto fail_free_reg; + } else if (!(val & RPM_SET_CONFIG_BOTH)) { + dev_err(dev, "%s: qcom,set=%u property is invalid\n", __func__, + val); + rc = -EINVAL; + goto fail_free_reg; + } + + reg->set_active = !!(val & RPM_SET_CONFIG_ACTIVE); + reg->set_sleep = !!(val & RPM_SET_CONFIG_SLEEP); + + init_data = of_get_regulator_init_data(dev); + if (init_data == NULL) { + dev_err(dev, "%s: unable to allocate memory\n", __func__); + rc = -ENOMEM; + goto fail_free_reg; + } + if (init_data->constraints.name == NULL) { + dev_err(dev, "%s: regulator name not specified\n", __func__); + rc = -EINVAL; + goto fail_free_reg; + } + + init_data->constraints.input_uV = init_data->constraints.max_uV; + + if (of_get_property(node, "parent-supply", NULL)) + init_data->supply_regulator = "parent"; + + /* + * Fill in ops and mode masks based on callbacks specified for + * this type of regulator. + */ + if (reg->rdesc.ops->enable) + init_data->constraints.valid_ops_mask + |= REGULATOR_CHANGE_STATUS; + if (reg->rdesc.ops->get_voltage) + init_data->constraints.valid_ops_mask + |= REGULATOR_CHANGE_VOLTAGE; + if (reg->rdesc.ops->get_mode) { + init_data->constraints.valid_ops_mask + |= REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_DRMS; + init_data->constraints.valid_modes_mask + |= REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + } + + reg->rdesc.name = init_data->constraints.name; + reg->min_uV = init_data->constraints.min_uV; + reg->max_uV = init_data->constraints.max_uV; + + /* Initialize the param array based on optional properties. */ + for (i = 0; i < RPM_REGULATOR_PARAM_MAX; i++) { + rc = of_property_read_u32(node, params[i].property_name, &val); + if (rc == 0) { + if (params[i].supported_regulator_types + & BIT(regulator_type)) { + if (val < params[i].min + || val > params[i].max) { + pr_warn("%s: device tree property: " + "%s=%u is outsided allowed " + "range [%u, %u]\n", + reg->rdesc.name, + params[i].property_name, val, + params[i].min, params[i].max); + continue; + } + reg->req.param[i] = val; + reg->req.modified |= BIT(i); + } else { + pr_warn("%s: regulator type=%d does not support" + " device tree property: %s\n", + reg->rdesc.name, regulator_type, + params[i].property_name); + } + } + } + + of_property_read_u32(node, "qcom,system_load", ®->system_load); + + rpm_vreg_lock(rpm_vreg); + list_add(®->list, &rpm_vreg->reg_list); + rpm_vreg_unlock(rpm_vreg); + + reg->rdev = regulator_register(®->rdesc, dev, init_data, reg, node); + if (IS_ERR(reg->rdev)) { + rc = PTR_ERR(reg->rdev); + reg->rdev = NULL; + pr_err("regulator_register failed: %s, rc=%d\n", + reg->rdesc.name, rc); + goto fail_remove_from_list; + } + + platform_set_drvdata(pdev, reg); + + pr_debug("successfully probed: %s\n", reg->rdesc.name); + + return 0; + +fail_remove_from_list: + rpm_vreg_lock(rpm_vreg); + list_del(®->list); + rpm_vreg_unlock(rpm_vreg); + +fail_free_reg: + kfree(reg); + return rc; +} + +/* + * This probe is called for parent rpm-regulator devices which have + * properties which are required to identify a given RPM resource. + */ +static int __devinit rpm_vreg_resource_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct rpm_vreg *rpm_vreg; + int val = 0; + u32 resource_type; + int rc; + + if (!dev->of_node) { + dev_err(dev, "%s: device tree information missing\n", __func__); + return -ENODEV; + } + + /* Create new rpm_vreg entry. */ + rpm_vreg = kzalloc(sizeof(struct rpm_vreg), GFP_KERNEL); + if (rpm_vreg == NULL) { + dev_err(dev, "%s: could not allocate memory for vreg\n", + __func__); + return -ENOMEM; + } + + /* Required device tree properties: */ + rc = of_property_read_string(node, "qcom,resource-name", + &rpm_vreg->resource_name); + if (rc) { + dev_err(dev, "%s: qcom,resource-name missing in DT node\n", + __func__); + goto fail_free_vreg; + } + resource_type = rpm_vreg_string_to_int(rpm_vreg->resource_name); + + rc = of_property_read_u32(node, "qcom,resource-id", + &rpm_vreg->resource_id); + if (rc) { + dev_err(dev, "%s: qcom,resource-id missing in DT node\n", + __func__); + goto fail_free_vreg; + } + + rc = of_property_read_u32(node, "qcom,regulator-type", + &rpm_vreg->regulator_type); + if (rc) { + dev_err(dev, "%s: qcom,regulator-type missing in DT node\n", + __func__); + goto fail_free_vreg; + } + + if ((rpm_vreg->regulator_type < 0) + || (rpm_vreg->regulator_type >= RPM_REGULATOR_SMD_TYPE_MAX)) { + dev_err(dev, "%s: invalid regulator type: %d\n", __func__, + rpm_vreg->regulator_type); + rc = -EINVAL; + goto fail_free_vreg; + } + + /* Optional device tree properties: */ + of_property_read_u32(node, "qcom,allow-atomic", &val); + rpm_vreg->allow_atomic = !!val; + of_property_read_u32(node, "qcom,enable-time", &rpm_vreg->enable_time); + of_property_read_u32(node, "qcom,hpm-min-load", + &rpm_vreg->hpm_min_load); + + rpm_vreg->handle_active = msm_rpm_create_request(RPM_SET_ACTIVE, + resource_type, rpm_vreg->resource_id, RPM_REGULATOR_PARAM_MAX); + if (rpm_vreg->handle_active == NULL + || IS_ERR(rpm_vreg->handle_active)) { + rc = PTR_ERR(rpm_vreg->handle_active); + dev_err(dev, "%s: failed to create active RPM handle, rc=%d\n", + __func__, rc); + goto fail_free_vreg; + } + + rpm_vreg->handle_sleep = msm_rpm_create_request(RPM_SET_SLEEP, + resource_type, rpm_vreg->resource_id, RPM_REGULATOR_PARAM_MAX); + if (rpm_vreg->handle_sleep == NULL || IS_ERR(rpm_vreg->handle_sleep)) { + rc = PTR_ERR(rpm_vreg->handle_sleep); + dev_err(dev, "%s: failed to create sleep RPM handle, rc=%d\n", + __func__, rc); + goto fail_free_handle_active; + } + + INIT_LIST_HEAD(&rpm_vreg->reg_list); + + if (rpm_vreg->allow_atomic) + spin_lock_init(&rpm_vreg->slock); + else + mutex_init(&rpm_vreg->mlock); + + platform_set_drvdata(pdev, rpm_vreg); + + rc = of_platform_populate(node, NULL, NULL, dev); + if (rc) { + dev_err(dev, "%s: failed to add child nodes, rc=%d\n", __func__, + rc); + goto fail_unset_drvdata; + } + + pr_debug("successfully probed: %s (%08X) %u\n", rpm_vreg->resource_name, + resource_type, rpm_vreg->resource_id); + + return rc; + +fail_unset_drvdata: + platform_set_drvdata(pdev, NULL); + msm_rpm_free_request(rpm_vreg->handle_sleep); + +fail_free_handle_active: + msm_rpm_free_request(rpm_vreg->handle_active); + +fail_free_vreg: + kfree(rpm_vreg); + + return rc; +} + +static struct of_device_id rpm_vreg_match_table_device[] = { + { .compatible = "qcom,rpm-regulator-smd", }, + {} +}; + +static struct of_device_id rpm_vreg_match_table_resource[] = { + { .compatible = "qcom,rpm-regulator-smd-resource", }, + {} +}; + +static struct platform_driver rpm_vreg_device_driver = { + .probe = rpm_vreg_device_probe, + .remove = __devexit_p(rpm_vreg_device_remove), + .driver = { + .name = "qcom,rpm-regulator-smd", + .owner = THIS_MODULE, + .of_match_table = rpm_vreg_match_table_device, + }, +}; + +static struct platform_driver rpm_vreg_resource_driver = { + .probe = rpm_vreg_resource_probe, + .remove = __devexit_p(rpm_vreg_resource_remove), + .driver = { + .name = "qcom,rpm-regulator-smd-resource", + .owner = THIS_MODULE, + .of_match_table = rpm_vreg_match_table_resource, + }, +}; + +/** + * rpm_regulator_smd_driver_init() - initialized SMD RPM regulator driver + * + * This function registers the SMD RPM regulator platform drivers. + * + * Returns 0 on success or errno on failure. + */ +int __init rpm_regulator_smd_driver_init(void) +{ + static bool initialized; + int i, rc; + + if (initialized) + return 0; + else + initialized = true; + + /* Store parameter string names as integers */ + for (i = 0; i < RPM_REGULATOR_PARAM_MAX; i++) + params[i].key = rpm_vreg_string_to_int(params[i].name); + + rc = platform_driver_register(&rpm_vreg_device_driver); + if (rc) + return rc; + + return platform_driver_register(&rpm_vreg_resource_driver); +} +EXPORT_SYMBOL_GPL(rpm_regulator_smd_driver_init); + +static void __exit rpm_vreg_exit(void) +{ + platform_driver_unregister(&rpm_vreg_device_driver); + platform_driver_unregister(&rpm_vreg_resource_driver); +} + +module_init(rpm_regulator_smd_driver_init); +module_exit(rpm_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM SMD RPM regulator driver"); diff --git a/arch/arm/mach-msm/rpm-regulator.c b/arch/arm/mach-msm/rpm-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..f663695b7c617e7dba7d4e666a351989e722c1ab --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator.c @@ -0,0 +1,1796 @@ +/* + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "rpm_resources.h" +#include "rpm-regulator-private.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 +); + +/* Used for access via the rpm_regulator_* API. */ +struct rpm_regulator { + int vreg_id; + enum rpm_vreg_voter voter; + int sleep_also; + int min_uV; + int max_uV; +}; + +struct vreg_config *(*get_config[])(void) = { + [RPM_VREG_VERSION_8660] = get_config_8660, + [RPM_VREG_VERSION_8960] = get_config_8960, + [RPM_VREG_VERSION_9615] = get_config_9615, + [RPM_VREG_VERSION_8930] = get_config_8930, +}; + +static struct rpm_regulator_consumer_mapping *consumer_map; +static int consumer_map_len; + +#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) + +#define USES_PART(_vreg, _part) (_vreg->part->_part.mask) + +#define vreg_err(vreg, fmt, ...) \ + pr_err("%s: " fmt, vreg->rdesc.name, ##__VA_ARGS__) + +#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 + +static const char *label_freq[] = { + [RPM_VREG_FREQ_NONE] = " N/A", + [RPM_VREG_FREQ_19p20] = "19.2", + [RPM_VREG_FREQ_9p60] = "9.60", + [RPM_VREG_FREQ_6p40] = "6.40", + [RPM_VREG_FREQ_4p80] = "4.80", + [RPM_VREG_FREQ_3p84] = "3.84", + [RPM_VREG_FREQ_3p20] = "3.20", + [RPM_VREG_FREQ_2p74] = "2.74", + [RPM_VREG_FREQ_2p40] = "2.40", + [RPM_VREG_FREQ_2p13] = "2.13", + [RPM_VREG_FREQ_1p92] = "1.92", + [RPM_VREG_FREQ_1p75] = "1.75", + [RPM_VREG_FREQ_1p60] = "1.60", + [RPM_VREG_FREQ_1p48] = "1.48", + [RPM_VREG_FREQ_1p37] = "1.37", + [RPM_VREG_FREQ_1p28] = "1.28", + [RPM_VREG_FREQ_1p20] = "1.20", +}; + +static const char *label_corner[] = { + [RPM_VREG_CORNER_NONE] = "NONE", + [RPM_VREG_CORNER_LOW] = "LOW", + [RPM_VREG_CORNER_NOMINAL] = "NOM", + [RPM_VREG_CORNER_HIGH] = "HIGH", +}; + +/* + * 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 + +/* rpm_version keeps track of the version for the currently running driver. */ +enum rpm_vreg_version rpm_version = -1; + +/* config holds all configuration data of the currently running driver. */ +static struct vreg_config *config; + +/* These regulator ID values are specified in the board file. */ +static int vreg_id_vdd_mem, vreg_id_vdd_dig; + +static inline int vreg_id_is_vdd_mem_or_dig(int id) +{ + return id == vreg_id_vdd_mem || id == vreg_id_vdd_dig; +} + +#define DEBUG_PRINT_BUFFER_SIZE 512 + +static void rpm_regulator_req(struct vreg *vreg, int set) +{ + int uV, mV, fm, pm, pc, pf, pd, freq, state, i; + const char *pf_label = "", *fm_label = "", *pc_total = ""; + const char *pc_en[4] = {"", "", "", ""}; + const char *pm_label = "", *freq_label = "", *corner_label = ""; + char buf[DEBUG_PRINT_BUFFER_SIZE]; + size_t buflen = DEBUG_PRINT_BUFFER_SIZE; + int pos = 0; + + /* 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; + + uV = GET_PART(vreg, uV); + mV = GET_PART(vreg, mV); + if (vreg->type == RPM_REGULATOR_TYPE_NCP) { + uV = -uV; + mV = -mV; + } + + 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); + freq = GET_PART(vreg, freq); + state = GET_PART(vreg, enable_state); + + if (pf >= 0 && pf < config->label_pin_func_len) + pf_label = config->label_pin_func[pf]; + + if (fm >= 0 && fm < config->label_force_mode_len) + fm_label = config->label_force_mode[fm]; + + if (pm >= 0 && pm < config->label_power_mode_len) + pm_label = config->label_power_mode[pm]; + + if (freq >= 0 && freq < ARRAY_SIZE(label_freq)) + freq_label = label_freq[freq]; + + for (i = 0; i < config->label_pin_ctrl_len; i++) + if (pc & (1 << i)) + pc_en[i] = config->label_pin_ctrl[i]; + + if (pc == RPM_VREG_PIN_CTRL_NONE) + pc_total = " none"; + + pos += scnprintf(buf + pos, buflen - pos, "%s%s: ", + KERN_INFO, __func__); + + pos += scnprintf(buf + pos, buflen - pos, "%s %-9s: s=%c", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg->rdesc.name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S')); + + if (USES_PART(vreg, uV) && vreg->type != RPM_REGULATOR_TYPE_CORNER) + pos += scnprintf(buf + pos, buflen - pos, ", v=%7d uV", uV); + if (USES_PART(vreg, mV)) + pos += scnprintf(buf + pos, buflen - pos, ", v=%4d mV", mV); + if (USES_PART(vreg, enable_state)) + pos += scnprintf(buf + pos, buflen - pos, ", state=%s (%d)", + (state == 1 ? "on" : "off"), state); + if (USES_PART(vreg, ip)) + pos += scnprintf(buf + pos, buflen - pos, + ", ip=%4d mA", GET_PART(vreg, ip)); + if (USES_PART(vreg, fm)) + pos += scnprintf(buf + pos, buflen - pos, + ", fm=%s (%d)", fm_label, fm); + if (USES_PART(vreg, pc)) + pos += scnprintf(buf + pos, buflen - pos, + ", pc=%s%s%s%s%s (%X)", pc_en[0], pc_en[1], + pc_en[2], pc_en[3], pc_total, pc); + if (USES_PART(vreg, pf)) + pos += scnprintf(buf + pos, buflen - pos, + ", pf=%s (%d)", pf_label, pf); + if (USES_PART(vreg, pd)) + pos += scnprintf(buf + pos, buflen - pos, + ", pd=%s (%d)", (pd == 1 ? "Y" : "N"), pd); + if (USES_PART(vreg, ia)) + pos += scnprintf(buf + pos, buflen - pos, + ", ia=%4d mA", GET_PART(vreg, ia)); + if (USES_PART(vreg, freq)) { + if (vreg->type == RPM_REGULATOR_TYPE_NCP) + pos += scnprintf(buf + pos, buflen - pos, + ", freq=%2d", freq); + else + pos += scnprintf(buf + pos, buflen - pos, + ", freq=%s MHz (%2d)", freq_label, freq); + } + if (USES_PART(vreg, pm)) + pos += scnprintf(buf + pos, buflen - pos, + ", pm=%s (%d)", pm_label, pm); + if (USES_PART(vreg, freq_clk_src)) + pos += scnprintf(buf + pos, buflen - pos, + ", clk_src=%d", GET_PART(vreg, freq_clk_src)); + if (USES_PART(vreg, comp_mode)) + pos += scnprintf(buf + pos, buflen - pos, + ", comp=%d", GET_PART(vreg, comp_mode)); + if (USES_PART(vreg, hpm)) + pos += scnprintf(buf + pos, buflen - pos, + ", hpm=%d", GET_PART(vreg, hpm)); + if (USES_PART(vreg, uV) && vreg->type == RPM_REGULATOR_TYPE_CORNER) { + if (uV >= 0 && uV < (ARRAY_SIZE(label_corner) - 1)) + corner_label = label_corner[uV+1]; + pos += scnprintf(buf + pos, buflen - pos, ", corner=%s (%d)", + corner_label, uV); + } + + pos += scnprintf(buf + pos, buflen - pos, "; req[0]={%d, 0x%08X}", + vreg->req[0].id, vreg->req[0].value); + if (vreg->part->request_len > 1) + pos += scnprintf(buf + pos, buflen - pos, + ", req[1]={%d, 0x%08X}", vreg->req[1].id, + vreg->req[1].value); + + pos += scnprintf(buf + pos, buflen - pos, "\n"); + printk(buf); +} + +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->rdesc.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->rdesc.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->rdesc.name, (set == 0 ? 'A' : 'S'), + vreg->req[0].id, vreg->req[0].value); +} + +/* Spin lock needed for sleep-selectable regulators. */ +static DEFINE_SPINLOCK(rpm_noirq_lock); + +static int voltage_from_req(struct vreg *vreg) +{ + int uV = 0; + + if (vreg->part->uV.mask) + uV = GET_PART(vreg, uV); + else if (vreg->part->mV.mask) + uV = MILLI_TO_MICRO(GET_PART(vreg, mV)); + else if (vreg->part->enable_state.mask) + uV = GET_PART(vreg, enable_state); + + return uV; +} + +static void voltage_to_req(int uV, struct vreg *vreg) +{ + if (vreg->part->uV.mask) + SET_PART(vreg, uV, uV); + else if (vreg->part->mV.mask) + SET_PART(vreg, mV, MICRO_TO_MILLI(uV)); + else if (vreg->part->enable_state.mask) + SET_PART(vreg, enable_state, 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; + + /* Set the force mode field based on which set is being requested. */ + if (set == MSM_RPM_CTX_SET_0) + SET_PART(vreg, fm, vreg->pdata.force_mode); + else + SET_PART(vreg, fm, vreg->pdata.sleep_set_force_mode); + + 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(&rpm_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 if (vreg->part->mV.mask) { + s_val[vreg->part->mV.word] = 0 << vreg->part->mV.shift; + s_mask[vreg->part->mV.word] = vreg->part->mV.mask; + } else if (vreg->part->enable_state.mask) { + s_val[vreg->part->enable_state.word] + = 0 << vreg->part->enable_state.shift; + s_mask[vreg->part->enable_state.word] + = vreg->part->enable_state.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(&rpm_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. + * + * Consumers can vote to disable a regulator with this function by passing + * min_uV = 0 and max_uV = 0. + * + * Voltage switch type regulators may be controlled via rpm_vreg_set_voltage + * as well. For this type of regulator, max_uV > 0 is treated as an enable + * request and max_uV == 0 is treated as a disable request. + */ +int rpm_vreg_set_voltage(int 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; + + if (!config) { + pr_err("rpm-regulator driver has not probed yet.\n"); + return -ENODEV; + } + + if (vreg_id < config->vreg_id_min || vreg_id > config->vreg_id_max) { + pr_err("invalid regulator id=%d\n", vreg_id); + return -EINVAL; + } + + vreg = &config->vregs[vreg_id]; + + if (!vreg->pdata.sleep_selectable) { + vreg_err(vreg, "regulator is not marked sleep selectable\n"); + return -EINVAL; + } + + /* Allow min_uV == max_uV == 0 to represent a disable request. */ + if ((min_uV != 0 || max_uV != 0) + && (vreg->part->uV.mask || vreg->part->mV.mask)) { + /* + * 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; + } + + range = &vreg->set_points->range[0]; + /* 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 (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point; " + "next set point: %d\n", + min_uV, max_uV, uV); + return -EINVAL; + } + } + + if (vreg->type == RPM_REGULATOR_TYPE_CORNER) { + /* + * Translate from enum values which work as inputs in the + * rpm_vreg_set_voltage function to the actual corner values + * sent to the RPM. + */ + if (uV > 0) + uV -= RPM_VREG_CORNER_NONE; + } + + 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 if (vreg->part->mV.mask) { + val[vreg->part->mV.word] + = MICRO_TO_MILLI(uV) << vreg->part->mV.shift; + mask[vreg->part->mV.word] = vreg->part->mV.mask; + } else if (vreg->part->enable_state.mask) { + /* + * Translate max_uV > 0 into an enable request for regulator + * types which to not support voltage setting, e.g. voltage + * switches. + */ + val[vreg->part->enable_state.word] + = (max_uV > 0 ? 1 : 0) << vreg->part->enable_state.shift; + mask[vreg->part->enable_state.word] + = vreg->part->enable_state.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(int vreg_id, enum rpm_vreg_freq freq) +{ + unsigned int mask[2] = {0}, val[2] = {0}; + struct vreg *vreg; + int rc; + + if (!config) { + pr_err("rpm-regulator driver has not probed yet.\n"); + return -ENODEV; + } + + if (vreg_id < config->vreg_id_min || vreg_id > config->vreg_id_max) { + pr_err("invalid regulator id=%d\n", vreg_id); + return -EINVAL; + } + + vreg = &config->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); + +#define MAX_NAME_LEN 64 +/** + * rpm_regulator_get() - lookup and obtain a handle to an RPM regulator + * @dev: device for regulator consumer + * @supply: supply name + * + * Returns a struct rpm_regulator corresponding to the regulator producer, + * or ERR_PTR() containing errno. + * + * This function may only be called from nonatomic context. The mapping between + * tuples and rpm_regulators struct pointers is specified via + * rpm-regulator platform data. + */ +struct rpm_regulator *rpm_regulator_get(struct device *dev, const char *supply) +{ + struct rpm_regulator_consumer_mapping *mapping = NULL; + const char *devname = NULL; + struct rpm_regulator *regulator; + int i; + + if (!config) { + pr_err("rpm-regulator driver has not probed yet.\n"); + return ERR_PTR(-ENODEV); + } + + if (consumer_map == NULL || consumer_map_len == 0) { + pr_err("No private consumer mapping has been specified.\n"); + return ERR_PTR(-ENODEV); + } + + if (supply == NULL) { + pr_err("supply name must be specified\n"); + return ERR_PTR(-EINVAL); + } + + if (dev) + devname = dev_name(dev); + + for (i = 0; i < consumer_map_len; i++) { + /* If the mapping has a device set up it must match */ + if (consumer_map[i].dev_name && + (!devname || strncmp(consumer_map[i].dev_name, devname, + MAX_NAME_LEN))) + continue; + + if (strncmp(consumer_map[i].supply, supply, MAX_NAME_LEN) + == 0) { + mapping = &consumer_map[i]; + break; + } + } + + if (mapping == NULL) { + pr_err("could not find mapping for dev=%s, supply=%s\n", + (devname ? devname : "(null)"), supply); + return ERR_PTR(-ENODEV); + } + + regulator = kzalloc(sizeof(struct rpm_regulator), GFP_KERNEL); + if (regulator == NULL) { + pr_err("could not allocate memory for regulator\n"); + return ERR_PTR(-ENOMEM); + } + + regulator->vreg_id = mapping->vreg_id; + regulator->voter = mapping->voter; + regulator->sleep_also = mapping->sleep_also; + + return regulator; +} +EXPORT_SYMBOL_GPL(rpm_regulator_get); + +static int rpm_regulator_check_input(struct rpm_regulator *regulator) +{ + int rc = 0; + + if (regulator == NULL) { + rc = -EINVAL; + pr_err("invalid (null) rpm_regulator pointer\n"); + } else if (IS_ERR(regulator)) { + rc = PTR_ERR(regulator); + pr_err("invalid rpm_regulator pointer, rc=%d\n", rc); + } + + return rc; +} + +/** + * rpm_regulator_put() - free the RPM regulator handle + * @regulator: RPM regulator handle + * + * Parameter reaggregation does not take place when rpm_regulator_put is called. + * Therefore, regulator enable state and voltage must be configured + * appropriately before calling rpm_regulator_put. + * + * This function may be called from either atomic or nonatomic context. + */ +void rpm_regulator_put(struct rpm_regulator *regulator) +{ + kfree(regulator); +} +EXPORT_SYMBOL_GPL(rpm_regulator_put); + +/** + * rpm_regulator_enable() - enable regulator output + * @regulator: RPM regulator handle + * + * Returns 0 on success or errno on failure. + * + * This function may be called from either atomic or nonatomic context. This + * function may only be called for regulators which have the sleep_selectable + * flag set in their configuration data. + * + * rpm_regulator_set_voltage must be called before rpm_regulator_enable because + * enabling is defined by the RPM interface to be requesting the desired + * non-zero regulator output voltage. + */ +int rpm_regulator_enable(struct rpm_regulator *regulator) +{ + int rc = rpm_regulator_check_input(regulator); + struct vreg *vreg; + + if (rc) + return rc; + + if (regulator->vreg_id < config->vreg_id_min + || regulator->vreg_id > config->vreg_id_max) { + pr_err("invalid regulator id=%d\n", regulator->vreg_id); + return -EINVAL; + } + + vreg = &config->vregs[regulator->vreg_id]; + + /* + * Handle voltage switches which can be enabled without + * rpm_regulator_set_voltage ever being called. + */ + if (regulator->min_uV == 0 && regulator->max_uV == 0 + && vreg->part->uV.mask == 0 && vreg->part->mV.mask == 0) { + regulator->min_uV = 1; + regulator->max_uV = 1; + } + + if (regulator->min_uV == 0 && regulator->max_uV == 0) { + pr_err("Voltage must be set with rpm_regulator_set_voltage " + "before calling rpm_regulator_enable; vreg_id=%d, " + "voter=%d\n", regulator->vreg_id, regulator->voter); + return -EINVAL; + } + + rc = rpm_vreg_set_voltage(regulator->vreg_id, regulator->voter, + regulator->min_uV, regulator->max_uV, regulator->sleep_also); + + if (rc) + pr_err("rpm_vreg_set_voltage failed, rc=%d\n", rc); + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_regulator_enable); + +/** + * rpm_regulator_disable() - disable regulator output + * @regulator: RPM regulator handle + * + * Returns 0 on success or errno on failure. + * + * The enable state of the regulator is determined by aggregating the requests + * of all consumers. Therefore, it is possible that the regulator will remain + * enabled even after rpm_regulator_disable is called. + * + * This function may be called from either atomic or nonatomic context. This + * function may only be called for regulators which have the sleep_selectable + * flag set in their configuration data. + */ +int rpm_regulator_disable(struct rpm_regulator *regulator) +{ + int rc = rpm_regulator_check_input(regulator); + + if (rc) + return rc; + + rc = rpm_vreg_set_voltage(regulator->vreg_id, regulator->voter, 0, 0, + regulator->sleep_also); + + if (rc) + pr_err("rpm_vreg_set_voltage failed, rc=%d\n", rc); + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_regulator_disable); + +/** + * rpm_regulator_set_voltage() - set regulator output voltage + * @regulator: RPM regulator handle + * @min_uV: minimum required voltage in uV + * @max_uV: maximum acceptable voltage in uV + * + * Sets a voltage regulator to the desired output voltage. This can be set + * while the regulator is disabled or enabled. If the regulator is disabled, + * then rpm_regulator_set_voltage will both enable the regulator and set it to + * output at the requested voltage. + * + * The min_uV to max_uV voltage range requested must intersect with the + * voltage constraint range configured for the regulator. + * + * Returns 0 on success or errno on failure. + * + * The final voltage value that is sent to the RPM is aggregated based upon the + * values requested by all consumers of the regulator. This corresponds to the + * maximum min_uV value. + * + * This function may be called from either atomic or nonatomic context. This + * function may only be called for regulators which have the sleep_selectable + * flag set in their configuration data. + */ +int rpm_regulator_set_voltage(struct rpm_regulator *regulator, int min_uV, + int max_uV) +{ + int rc = rpm_regulator_check_input(regulator); + + if (rc) + return rc; + + rc = rpm_vreg_set_voltage(regulator->vreg_id, regulator->voter, min_uV, + max_uV, regulator->sleep_also); + + if (rc) { + pr_err("rpm_vreg_set_voltage failed, rc=%d\n", rc); + } else { + regulator->min_uV = min_uV; + regulator->max_uV = max_uV; + } + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_regulator_set_voltage); + +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(&rpm_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(&rpm_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 RPM_REGULATOR_TYPE_LDO: + case RPM_REGULATOR_TYPE_SMPS: + case RPM_REGULATOR_TYPE_CORNER: + /* 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 RPM_REGULATOR_TYPE_VS: + case RPM_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 rpm_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 RPM_REGULATOR_TYPE_LDO: + case RPM_REGULATOR_TYPE_SMPS: + case RPM_REGULATOR_TYPE_CORNER: + /* 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 RPM_REGULATOR_TYPE_VS: + case RPM_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 rpm_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 (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point; " + "next set point: %d\n", + min_uV, max_uV, uV); + return -EINVAL; + } + + if (vreg->type == RPM_REGULATOR_TYPE_CORNER) { + /* + * Translate from enum values which work as inputs in the + * regulator_set_voltage function to the actual corner values + * sent to the RPM. + */ + uV -= RPM_VREG_CORNER_NONE; + } + + 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 == RPM_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); + + if (mode == config->mode_hpm) { + /* 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; + + if (config->ia_follows_ip) { + val[vreg->part->ia.word] + |= MICRO_TO_MILLI(vreg_hpm_min_uA(vreg)) + << vreg->part->ia.shift; + mask[vreg->part->ia.word] + |= vreg->part->ia.mask; + } + } + } else if (mode == config->mode_lpm) { + /* 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; + + if (config->ia_follows_ip) { + val[vreg->part->ia.word] + |= MICRO_TO_MILLI(vreg_lpm_max_uA(vreg)) + << vreg->part->ia.shift; + mask[vreg->part->ia.word] + |= vreg->part->ia.mask; + } + } + } else { + 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))); + if (config->ia_follows_ip) + SET_PART(vreg, ia, + MICRO_TO_MILLI(saturate_avg_load(vreg, load_uA))); + mutex_unlock(&vreg->pc_lock); + + if (load_uA >= vreg->hpm_min_load) + mode = config->mode_hpm; + else + mode = config->mode_lpm; + + return mode; +} + +static unsigned int vreg_legacy_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + + if (MICRO_TO_MILLI(load_uA) <= 0) { + /* + * vreg_legacy_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 vreg->mode; + } + + return vreg_get_optimum_mode(rdev, input_uV, output_uV, load_uA); +} + +/* + * 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}; + int pin_fn, 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 = config->pin_func_none; + if (vreg->pdata.pin_fn == config->pin_func_sleep_b) + pin_fn = config->pin_func_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; +} + +static int vreg_enable_time(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->pdata.enable_time; +} + +/* Real regulator operations. */ +static struct regulator_ops ldo_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_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, + .enable_time = vreg_enable_time, +}; + +static struct regulator_ops smps_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_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, + .enable_time = vreg_enable_time, +}; + +static struct regulator_ops switch_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = vreg_is_enabled, + .enable_time = vreg_enable_time, +}; + +static struct regulator_ops ncp_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = vreg_is_enabled, + .set_voltage = vreg_set_voltage, + .get_voltage = vreg_get_voltage, + .list_voltage = vreg_list_voltage, + .enable_time = vreg_enable_time, +}; + +static struct regulator_ops corner_ops = { + .enable = rpm_vreg_enable, + .disable = rpm_vreg_disable, + .is_enabled = vreg_is_enabled, + .set_voltage = vreg_set_voltage, + .get_voltage = vreg_get_voltage, + .list_voltage = vreg_list_voltage, + .enable_time = vreg_enable_time, +}; + +/* 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, +}; + +struct regulator_ops *vreg_ops[] = { + [RPM_REGULATOR_TYPE_LDO] = &ldo_ops, + [RPM_REGULATOR_TYPE_SMPS] = &smps_ops, + [RPM_REGULATOR_TYPE_VS] = &switch_ops, + [RPM_REGULATOR_TYPE_NCP] = &ncp_ops, + [RPM_REGULATOR_TYPE_CORNER] = &corner_ops, +}; + +static int __devinit +rpm_vreg_init_regulator(const struct rpm_regulator_init_data *pdata, + struct device *dev) +{ + struct regulator_desc *rdesc = NULL; + struct regulator_dev *rdev; + struct vreg *vreg; + unsigned pin_ctrl; + int id, pin_fn; + int rc = 0; + + if (!pdata) { + pr_err("platform data missing\n"); + return -EINVAL; + } + + id = pdata->id; + + if (id < config->vreg_id_min || id > config->vreg_id_max) { + pr_err("invalid regulator id: %d\n", id); + return -ENODEV; + } + + if (!config->is_real_id(pdata->id)) + id = config->pc_id_to_real_id(pdata->id); + vreg = &config->vregs[id]; + + if (config->is_real_id(pdata->id)) + rdesc = &vreg->rdesc; + else + rdesc = &vreg->rdesc_pc; + + if (vreg->type < 0 || vreg->type > RPM_REGULATOR_TYPE_MAX) { + pr_err("%s: invalid regulator type: %d\n", + vreg->rdesc.name, vreg->type); + return -EINVAL; + } + + mutex_lock(&vreg->pc_lock); + + if (vreg->set_points) + rdesc->n_voltages = vreg->set_points->n_voltages; + else + rdesc->n_voltages = 0; + + rdesc->id = pdata->id; + rdesc->owner = THIS_MODULE; + rdesc->type = REGULATOR_VOLTAGE; + + if (config->is_real_id(pdata->id)) { + /* + * Real regulator; do not modify pin control and pin function + * values. + */ + rdesc->ops = vreg_ops[vreg->type]; + 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->save_uV = vreg->pdata.default_uV; + if (vreg->pdata.peak_uA >= vreg->hpm_min_load) + vreg->mode = config->mode_hpm; + else + vreg->mode = config->mode_lpm; + + /* 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, config->pin_func_none); + SET_PART(vreg, pc, RPM_VREG_PIN_CTRL_NONE); + } + } else { + if ((pdata->pin_ctrl & RPM_VREG_PIN_CTRL_ALL) + == RPM_VREG_PIN_CTRL_NONE + && pdata->pin_fn != config->pin_func_sleep_b) { + pr_err("%s: no pin control input specified\n", + vreg->rdesc.name); + mutex_unlock(&vreg->pc_lock); + return -EINVAL; + } + rdesc->ops = &pin_control_ops; + vreg->pdata.pin_ctrl = pdata->pin_ctrl; + vreg->pdata.pin_fn = pdata->pin_fn; + + /* Initialize the RPM request. */ + pin_fn = config->pin_func_none; + /* Allow pf=sleep_b to be specified by platform data. */ + if (vreg->pdata.pin_fn == config->pin_func_sleep_b) + pin_fn = config->pin_func_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, NULL); + if (IS_ERR(rdev)) { + rc = PTR_ERR(rdev); + pr_err("regulator_register failed: %s, rc=%d\n", + vreg->rdesc.name, rc); + return rc; + } else { + if (config->is_real_id(pdata->id)) + vreg->rdev = rdev; + else + vreg->rdev_pc = rdev; + } + +bail: + if (rc) + pr_err("error for %s, rc=%d\n", vreg->rdesc.name, rc); + + return rc; +} + +static void rpm_vreg_set_point_init(void) +{ + struct vreg_set_points **set_points; + int i, j, temp; + + set_points = config->set_points; + + /* Calculate the number of set points available for each regulator. */ + for (i = 0; i < config->set_points_len; i++) { + temp = 0; + 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; + temp += set_points[i]->range[j].n_voltages; + } + set_points[i]->n_voltages = temp; + } +} + +static int __devinit rpm_vreg_probe(struct platform_device *pdev) +{ + struct rpm_regulator_platform_data *platform_data; + static struct rpm_regulator_consumer_mapping *prev_consumer_map; + static int prev_consumer_map_len; + int rc = 0; + int i, id; + + platform_data = pdev->dev.platform_data; + if (!platform_data) { + pr_err("rpm-regulator requires platform data\n"); + return -EINVAL; + } + + if (rpm_version >= 0 && rpm_version <= RPM_VREG_VERSION_MAX + && platform_data->version != rpm_version) { + pr_err("rpm version %d does not match previous version %d\n", + platform_data->version, rpm_version); + return -EINVAL; + } + + if (platform_data->version < 0 + || platform_data->version > RPM_VREG_VERSION_MAX) { + pr_err("rpm version %d is invalid\n", platform_data->version); + return -EINVAL; + } + + if (rpm_version < 0 || rpm_version > RPM_VREG_VERSION_MAX) { + rpm_version = platform_data->version; + config = get_config[platform_data->version](); + vreg_id_vdd_mem = platform_data->vreg_id_vdd_mem; + vreg_id_vdd_dig = platform_data->vreg_id_vdd_dig; + if (!config) { + pr_err("rpm version %d is not available\n", + platform_data->version); + return -ENODEV; + } + if (config->use_legacy_optimum_mode) + for (i = 0; i < ARRAY_SIZE(vreg_ops); i++) + vreg_ops[i]->get_optimum_mode + = vreg_legacy_get_optimum_mode; + rpm_vreg_set_point_init(); + /* First time probed; initialize pin control mutexes. */ + for (i = 0; i < config->vregs_len; i++) + mutex_init(&config->vregs[i].pc_lock); + } + + /* Copy the list of private API consumers. */ + if (platform_data->consumer_map_len > 0) { + if (consumer_map_len == 0) { + consumer_map_len = platform_data->consumer_map_len; + consumer_map = kmemdup(platform_data->consumer_map, + sizeof(struct rpm_regulator_consumer_mapping) + * consumer_map_len, GFP_KERNEL); + if (consumer_map == NULL) { + pr_err("memory allocation failed\n"); + consumer_map_len = 0; + return -ENOMEM; + } + } else { + /* Concatenate new map with the existing one. */ + prev_consumer_map = consumer_map; + prev_consumer_map_len = consumer_map_len; + consumer_map_len += platform_data->consumer_map_len; + consumer_map = kmalloc( + sizeof(struct rpm_regulator_consumer_mapping) + * consumer_map_len, GFP_KERNEL); + if (consumer_map == NULL) { + pr_err("memory allocation failed\n"); + consumer_map_len = 0; + return -ENOMEM; + } + memcpy(consumer_map, prev_consumer_map, + sizeof(struct rpm_regulator_consumer_mapping) + * prev_consumer_map_len); + memcpy(&consumer_map[prev_consumer_map_len], + platform_data->consumer_map, + sizeof(struct rpm_regulator_consumer_mapping) + * platform_data->consumer_map_len); + } + + } + + /* 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--) { + id = platform_data->init_data[i].id; + if (config->is_real_id(id)) { + regulator_unregister(config->vregs[id].rdev); + config->vregs[id].rdev = NULL; + } else { + regulator_unregister(config->vregs[ + config->pc_id_to_real_id(id)].rdev_pc); + config->vregs[id].rdev_pc = NULL; + } + } + + 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 (config->is_real_id(id)) { + regulator_unregister(config->vregs[id].rdev); + config->vregs[id].rdev = NULL; + } else { + regulator_unregister(config->vregs[ + config->pc_id_to_real_id(id)].rdev_pc); + config->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) +{ + return platform_driver_register(&rpm_vreg_driver); +} + +static void __exit rpm_vreg_exit(void) +{ + int i; + + platform_driver_unregister(&rpm_vreg_driver); + + kfree(consumer_map); + + for (i = 0; i < config->vregs_len; i++) + mutex_destroy(&config->vregs[i].pc_lock); +} + +postcore_initcall(rpm_vreg_init); +module_exit(rpm_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM RPM regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" RPM_REGULATOR_DEV_NAME); diff --git a/arch/arm/mach-msm/rpm-smd.c b/arch/arm/mach-msm/rpm-smd.c new file mode 100644 index 0000000000000000000000000000000000000000..75f4d928fc6a59c23e27d17c78eeb8f350a080b5 --- /dev/null +++ b/arch/arm/mach-msm/rpm-smd.c @@ -0,0 +1,826 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "rpm-notifier.h" + +struct msm_rpm_driver_data { + const char *ch_name; + uint32_t ch_type; + smd_channel_t *ch_info; + struct work_struct work; + spinlock_t smd_lock_write; + spinlock_t smd_lock_read; + struct completion smd_open; +}; + +#define DEFAULT_BUFFER_SIZE 256 +#define GFP_FLAG(noirq) (noirq ? GFP_ATOMIC : GFP_KERNEL) +#define INV_HDR "resource does not exist" +#define ERR "err\0" +#define MAX_ERR_BUFFER_SIZE 60 + +static struct atomic_notifier_head msm_rpm_sleep_notifier; +static bool standalone; + +int msm_rpm_register_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&msm_rpm_sleep_notifier, nb); +} + +int msm_rpm_unregister_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&msm_rpm_sleep_notifier, nb); +} + +static struct workqueue_struct *msm_rpm_smd_wq; + +enum { + MSM_RPM_MSG_REQUEST_TYPE = 0, + MSM_RPM_MSG_TYPE_NR, +}; + +static const uint32_t msm_rpm_request_service[MSM_RPM_MSG_TYPE_NR] = { + 0x716572, /* 'req\0' */ +}; + +/*the order of fields matter and reflect the order expected by the RPM*/ +struct rpm_request_header { + uint32_t service_type; + uint32_t request_len; +}; + +struct rpm_message_header { + uint32_t msg_id; + enum msm_rpm_set set; + uint32_t resource_type; + uint32_t resource_id; + uint32_t data_len; +}; + +struct msm_rpm_kvp_data { + uint32_t key; + uint32_t nbytes; /* number of bytes */ + uint8_t *value; + bool valid; +}; + +static atomic_t msm_rpm_msg_id = ATOMIC_INIT(0); + +static struct msm_rpm_driver_data msm_rpm_data; + +struct msm_rpm_request { + struct rpm_request_header req_hdr; + struct rpm_message_header msg_hdr; + struct msm_rpm_kvp_data *kvp; + uint32_t num_elements; + uint32_t write_idx; + uint8_t *buf; + uint32_t numbytes; +}; + +/* + * Data related to message acknowledgement + */ + +LIST_HEAD(msm_rpm_wait_list); + +struct msm_rpm_wait_data { + struct list_head list; + uint32_t msg_id; + bool ack_recd; + int errno; + struct completion ack; +}; +DEFINE_SPINLOCK(msm_rpm_list_lock); + +struct msm_rpm_ack_msg { + uint32_t req; + uint32_t req_len; + uint32_t rsc_id; + uint32_t msg_len; + uint32_t id_ack; +}; + +static int irq_process; + +LIST_HEAD(msm_rpm_ack_list); + +static void msm_rpm_notify_sleep_chain(struct rpm_message_header *hdr, + struct msm_rpm_kvp_data *kvp) +{ + struct msm_rpm_notifier_data notif; + + notif.rsc_type = hdr->resource_type; + notif.rsc_id = hdr->resource_id; + notif.key = kvp->key; + notif.size = kvp->nbytes; + notif.value = kvp->value; + atomic_notifier_call_chain(&msm_rpm_sleep_notifier, 0, ¬if); +} + +static int msm_rpm_add_kvp_data_common(struct msm_rpm_request *handle, + uint32_t key, const uint8_t *data, int size, bool noirq) +{ + int i; + int data_size, msg_size; + + if (!handle) + return -EINVAL; + + data_size = ALIGN(size, SZ_4); + msg_size = data_size + sizeof(struct rpm_request_header); + + for (i = 0; i < handle->write_idx; i++) { + if (handle->kvp[i].key != key) + continue; + if (handle->kvp[i].nbytes != data_size) { + kfree(handle->kvp[i].value); + handle->kvp[i].value = NULL; + } else { + if (!memcmp(handle->kvp[i].value, data, data_size)) + return 0; + } + break; + } + + if (i >= handle->num_elements) + return -ENOMEM; + + if (i == handle->write_idx) + handle->write_idx++; + + if (!handle->kvp[i].value) { + handle->kvp[i].value = kzalloc(data_size, GFP_FLAG(noirq)); + + if (!handle->kvp[i].value) + return -ENOMEM; + } else { + /* We enter the else case, if a key already exists but the + * data doesn't match. In which case, we should zero the data + * out. + */ + memset(handle->kvp[i].value, 0, data_size); + } + + if (!handle->kvp[i].valid) + handle->msg_hdr.data_len += msg_size; + else + handle->msg_hdr.data_len += (data_size - handle->kvp[i].nbytes); + + handle->kvp[i].nbytes = data_size; + handle->kvp[i].key = key; + memcpy(handle->kvp[i].value, data, size); + handle->kvp[i].valid = true; + + if (handle->msg_hdr.set == MSM_RPM_CTX_SLEEP_SET) + msm_rpm_notify_sleep_chain(&handle->msg_hdr, &handle->kvp[i]); + + return 0; + +} + +static struct msm_rpm_request *msm_rpm_create_request_common( + enum msm_rpm_set set, uint32_t rsc_type, uint32_t rsc_id, + int num_elements, bool noirq) +{ + struct msm_rpm_request *cdata; + + cdata = kzalloc(sizeof(struct msm_rpm_request), + GFP_FLAG(noirq)); + + if (!cdata) { + printk(KERN_INFO"%s():Cannot allocate memory for client data\n", + __func__); + goto cdata_alloc_fail; + } + + cdata->msg_hdr.set = set; + cdata->msg_hdr.resource_type = rsc_type; + cdata->msg_hdr.resource_id = rsc_id; + cdata->msg_hdr.data_len = 0; + + cdata->num_elements = num_elements; + cdata->write_idx = 0; + + cdata->kvp = kzalloc(sizeof(struct msm_rpm_kvp_data) * num_elements, + GFP_FLAG(noirq)); + + if (!cdata->kvp) { + pr_warn("%s(): Cannot allocate memory for key value data\n", + __func__); + goto kvp_alloc_fail; + } + + cdata->buf = kzalloc(DEFAULT_BUFFER_SIZE, GFP_FLAG(noirq)); + + if (!cdata->buf) + goto buf_alloc_fail; + + cdata->numbytes = DEFAULT_BUFFER_SIZE; + return cdata; + +buf_alloc_fail: + kfree(cdata->kvp); +kvp_alloc_fail: + kfree(cdata); +cdata_alloc_fail: + return NULL; + +} + +void msm_rpm_free_request(struct msm_rpm_request *handle) +{ + int i; + + if (!handle) + return; + for (i = 0; i < handle->write_idx; i++) + kfree(handle->kvp[i].value); + kfree(handle->kvp); + kfree(handle); +} +EXPORT_SYMBOL(msm_rpm_free_request); + +struct msm_rpm_request *msm_rpm_create_request( + enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, int num_elements) +{ + return msm_rpm_create_request_common(set, rsc_type, rsc_id, + num_elements, false); +} +EXPORT_SYMBOL(msm_rpm_create_request); + +struct msm_rpm_request *msm_rpm_create_request_noirq( + enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, int num_elements) +{ + return msm_rpm_create_request_common(set, rsc_type, rsc_id, + num_elements, true); +} +EXPORT_SYMBOL(msm_rpm_create_request_noirq); + +int msm_rpm_add_kvp_data(struct msm_rpm_request *handle, + uint32_t key, const uint8_t *data, int size) +{ + return msm_rpm_add_kvp_data_common(handle, key, data, size, false); + +} +EXPORT_SYMBOL(msm_rpm_add_kvp_data); + +int msm_rpm_add_kvp_data_noirq(struct msm_rpm_request *handle, + uint32_t key, const uint8_t *data, int size) +{ + return msm_rpm_add_kvp_data_common(handle, key, data, size, true); +} +EXPORT_SYMBOL(msm_rpm_add_kvp_data_noirq); + +/* Runs in interrupt context */ +static void msm_rpm_notify(void *data, unsigned event) +{ + struct msm_rpm_driver_data *pdata = (struct msm_rpm_driver_data *)data; + BUG_ON(!pdata); + + if (!(pdata->ch_info)) + return; + + switch (event) { + case SMD_EVENT_DATA: + queue_work(msm_rpm_smd_wq, &pdata->work); + break; + case SMD_EVENT_OPEN: + complete(&pdata->smd_open); + break; + case SMD_EVENT_CLOSE: + case SMD_EVENT_STATUS: + case SMD_EVENT_REOPEN_READY: + break; + default: + pr_info("Unknown SMD event\n"); + + } +} + +static struct msm_rpm_wait_data *msm_rpm_get_entry_from_msg_id(uint32_t msg_id) +{ + struct list_head *ptr; + struct msm_rpm_wait_data *elem; + unsigned long flags; + + spin_lock_irqsave(&msm_rpm_list_lock, flags); + + list_for_each(ptr, &msm_rpm_wait_list) { + elem = list_entry(ptr, struct msm_rpm_wait_data, list); + if (elem && (elem->msg_id == msg_id)) + break; + elem = NULL; + } + spin_unlock_irqrestore(&msm_rpm_list_lock, flags); + return elem; +} + +static int msm_rpm_get_next_msg_id(void) +{ + int id; + + do { + id = atomic_inc_return(&msm_rpm_msg_id); + } while ((id == 0) || msm_rpm_get_entry_from_msg_id(id)); + + return id; +} + +static int msm_rpm_add_wait_list(uint32_t msg_id) +{ + unsigned long flags; + struct msm_rpm_wait_data *data = + kzalloc(sizeof(struct msm_rpm_wait_data), GFP_ATOMIC); + + if (!data) + return -ENOMEM; + + init_completion(&data->ack); + data->ack_recd = false; + data->msg_id = msg_id; + spin_lock_irqsave(&msm_rpm_list_lock, flags); + list_add(&data->list, &msm_rpm_wait_list); + spin_unlock_irqrestore(&msm_rpm_list_lock, flags); + + return 0; +} + +static void msm_rpm_free_list_entry(struct msm_rpm_wait_data *elem) +{ + unsigned long flags; + + spin_lock_irqsave(&msm_rpm_list_lock, flags); + list_del(&elem->list); + spin_unlock_irqrestore(&msm_rpm_list_lock, flags); + kfree(elem); +} + +static void msm_rpm_process_ack(uint32_t msg_id, int errno) +{ + struct list_head *ptr; + struct msm_rpm_wait_data *elem; + unsigned long flags; + + spin_lock_irqsave(&msm_rpm_list_lock, flags); + + list_for_each(ptr, &msm_rpm_wait_list) { + elem = list_entry(ptr, struct msm_rpm_wait_data, list); + if (elem && (elem->msg_id == msg_id)) { + elem->errno = errno; + elem->ack_recd = true; + complete(&elem->ack); + break; + } + elem = NULL; + } + WARN_ON(!elem); + + spin_unlock_irqrestore(&msm_rpm_list_lock, flags); +} + +struct msm_rpm_kvp_packet { + uint32_t id; + uint32_t len; + uint32_t val; +}; + +static inline uint32_t msm_rpm_get_msg_id_from_ack(uint8_t *buf) +{ + return ((struct msm_rpm_ack_msg *)buf)->id_ack; +} + +static inline int msm_rpm_get_error_from_ack(uint8_t *buf) +{ + uint8_t *tmp; + uint32_t req_len = ((struct msm_rpm_ack_msg *)buf)->req_len; + + int rc = -ENODEV; + + req_len -= sizeof(struct msm_rpm_ack_msg); + req_len += 2 * sizeof(uint32_t); + if (!req_len) + return 0; + + tmp = buf + sizeof(struct msm_rpm_ack_msg); + + BUG_ON(memcmp(tmp, ERR, sizeof(uint32_t))); + + tmp += 2 * sizeof(uint32_t); + + if (!(memcmp(tmp, INV_HDR, min(req_len, sizeof(INV_HDR))-1))) + rc = -EINVAL; + + return rc; +} + +static void msm_rpm_read_smd_data(char *buf) +{ + int pkt_sz; + int bytes_read = 0; + + pkt_sz = smd_cur_packet_size(msm_rpm_data.ch_info); + + BUG_ON(pkt_sz > MAX_ERR_BUFFER_SIZE); + + if (pkt_sz != smd_read_avail(msm_rpm_data.ch_info)) + return; + + BUG_ON(pkt_sz == 0); + + do { + int len; + + len = smd_read(msm_rpm_data.ch_info, buf + bytes_read, pkt_sz); + pkt_sz -= len; + bytes_read += len; + + } while (pkt_sz > 0); + + BUG_ON(pkt_sz < 0); +} + +static void msm_rpm_smd_work(struct work_struct *work) +{ + uint32_t msg_id; + int errno; + char buf[MAX_ERR_BUFFER_SIZE] = {0}; + unsigned long flags; + + while (smd_is_pkt_avail(msm_rpm_data.ch_info) && !irq_process) { + spin_lock_irqsave(&msm_rpm_data.smd_lock_read, flags); + msm_rpm_read_smd_data(buf); + spin_unlock_irqrestore(&msm_rpm_data.smd_lock_read, flags); + msg_id = msm_rpm_get_msg_id_from_ack(buf); + errno = msm_rpm_get_error_from_ack(buf); + msm_rpm_process_ack(msg_id, errno); + } +} + +static int msm_rpm_send_data(struct msm_rpm_request *cdata, + int msg_type, bool noirq) +{ + uint8_t *tmpbuff; + int i, ret, msg_size; + unsigned long flags; + + int req_hdr_sz, msg_hdr_sz; + + if (!cdata->msg_hdr.data_len) + return 0; + req_hdr_sz = sizeof(cdata->req_hdr); + msg_hdr_sz = sizeof(cdata->msg_hdr); + + cdata->req_hdr.service_type = msm_rpm_request_service[msg_type]; + + cdata->msg_hdr.msg_id = msm_rpm_get_next_msg_id(); + + cdata->req_hdr.request_len = cdata->msg_hdr.data_len + msg_hdr_sz; + msg_size = cdata->req_hdr.request_len + req_hdr_sz; + + /* populate data_len */ + if (msg_size > cdata->numbytes) { + kfree(cdata->buf); + cdata->numbytes = msg_size; + cdata->buf = kzalloc(msg_size, GFP_FLAG(noirq)); + } + + if (!cdata->buf) + return 0; + + tmpbuff = cdata->buf; + + memcpy(tmpbuff, &cdata->req_hdr, req_hdr_sz + msg_hdr_sz); + + tmpbuff += req_hdr_sz + msg_hdr_sz; + + for (i = 0; (i < cdata->write_idx); i++) { + /* Sanity check */ + BUG_ON((tmpbuff - cdata->buf) > cdata->numbytes); + + if (!cdata->kvp[i].valid) + continue; + + memcpy(tmpbuff, &cdata->kvp[i].key, sizeof(uint32_t)); + tmpbuff += sizeof(uint32_t); + + memcpy(tmpbuff, &cdata->kvp[i].nbytes, sizeof(uint32_t)); + tmpbuff += sizeof(uint32_t); + + memcpy(tmpbuff, cdata->kvp[i].value, cdata->kvp[i].nbytes); + tmpbuff += cdata->kvp[i].nbytes; + } + + if (standalone) { + for (i = 0; (i < cdata->write_idx); i++) + cdata->kvp[i].valid = false; + + cdata->msg_hdr.data_len = 0; + ret = cdata->msg_hdr.msg_id; + return ret; + } + + msm_rpm_add_wait_list(cdata->msg_hdr.msg_id); + + spin_lock_irqsave(&msm_rpm_data.smd_lock_write, flags); + + ret = smd_write_avail(msm_rpm_data.ch_info); + + if (ret < 0) { + pr_warn("%s(): SMD not initialized\n", __func__); + spin_unlock_irqrestore(&msm_rpm_data.smd_lock_write, flags); + return 0; + } + + while ((ret < msg_size)) { + if (!noirq) { + spin_unlock_irqrestore(&msm_rpm_data.smd_lock_write, + flags); + cpu_relax(); + spin_lock_irqsave(&msm_rpm_data.smd_lock_write, flags); + } else + udelay(5); + ret = smd_write_avail(msm_rpm_data.ch_info); + } + + ret = smd_write(msm_rpm_data.ch_info, &cdata->buf[0], msg_size); + spin_unlock_irqrestore(&msm_rpm_data.smd_lock_write, flags); + + if (ret == msg_size) { + for (i = 0; (i < cdata->write_idx); i++) + cdata->kvp[i].valid = false; + cdata->msg_hdr.data_len = 0; + ret = cdata->msg_hdr.msg_id; + } else if (ret < msg_size) { + struct msm_rpm_wait_data *rc; + ret = 0; + pr_info("Failed to write data msg_size:%d ret:%d\n", + msg_size, ret); + rc = msm_rpm_get_entry_from_msg_id(cdata->msg_hdr.msg_id); + if (rc) + msm_rpm_free_list_entry(rc); + } + return ret; +} + +int msm_rpm_send_request(struct msm_rpm_request *handle) +{ + return msm_rpm_send_data(handle, MSM_RPM_MSG_REQUEST_TYPE, false); +} +EXPORT_SYMBOL(msm_rpm_send_request); + +int msm_rpm_send_request_noirq(struct msm_rpm_request *handle) +{ + return msm_rpm_send_data(handle, MSM_RPM_MSG_REQUEST_TYPE, true); +} +EXPORT_SYMBOL(msm_rpm_send_request_noirq); + +int msm_rpm_wait_for_ack(uint32_t msg_id) +{ + struct msm_rpm_wait_data *elem; + int rc = 0; + + if (!msg_id) + return -EINVAL; + + if (standalone) + return 0; + + elem = msm_rpm_get_entry_from_msg_id(msg_id); + if (!elem) + return 0; + + rc = wait_for_completion_timeout(&elem->ack, msecs_to_jiffies(1)); + if (!rc) { + pr_warn("%s(): Timed out after 1 ms\n", __func__); + rc = -ETIMEDOUT; + } else { + rc = elem->errno; + msm_rpm_free_list_entry(elem); + } + return rc; +} +EXPORT_SYMBOL(msm_rpm_wait_for_ack); + +int msm_rpm_wait_for_ack_noirq(uint32_t msg_id) +{ + struct msm_rpm_wait_data *elem; + unsigned long flags; + int rc = 0; + uint32_t id = 0; + int count = 0; + + if (!msg_id) + return -EINVAL; + + if (standalone) + return 0; + + spin_lock_irqsave(&msm_rpm_data.smd_lock_read, flags); + irq_process = true; + + elem = msm_rpm_get_entry_from_msg_id(msg_id); + + if (!elem) + /* Should this be a bug + * Is it ok for another thread to read the msg? + */ + goto wait_ack_cleanup; + + while ((id != msg_id) && (count++ < 10)) { + if (smd_is_pkt_avail(msm_rpm_data.ch_info)) { + int errno; + char buf[MAX_ERR_BUFFER_SIZE] = {}; + + msm_rpm_read_smd_data(buf); + id = msm_rpm_get_msg_id_from_ack(buf); + errno = msm_rpm_get_error_from_ack(buf); + msm_rpm_process_ack(id, errno); + } else + udelay(100); + } + + if (count == 10) { + rc = -ETIMEDOUT; + pr_warn("%s(): Timed out after 1ms\n", __func__); + } else { + rc = elem->errno; + msm_rpm_free_list_entry(elem); + } +wait_ack_cleanup: + irq_process = false; + spin_unlock_irqrestore(&msm_rpm_data.smd_lock_read, flags); + return rc; +} +EXPORT_SYMBOL(msm_rpm_wait_for_ack_noirq); + +int msm_rpm_send_message(enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, struct msm_rpm_kvp *kvp, int nelems) +{ + int i, rc; + struct msm_rpm_request *req = + msm_rpm_create_request(set, rsc_type, rsc_id, nelems); + if (!req) + return -ENOMEM; + + for (i = 0; i < nelems; i++) { + rc = msm_rpm_add_kvp_data(req, kvp[i].key, + kvp[i].data, kvp[i].length); + if (rc) + goto bail; + } + + rc = msm_rpm_wait_for_ack(msm_rpm_send_request(req)); +bail: + msm_rpm_free_request(req); + return rc; +} +EXPORT_SYMBOL(msm_rpm_send_message); + +int msm_rpm_send_message_noirq(enum msm_rpm_set set, uint32_t rsc_type, + uint32_t rsc_id, struct msm_rpm_kvp *kvp, int nelems) +{ + int i, rc; + struct msm_rpm_request *req = + msm_rpm_create_request_noirq(set, rsc_type, rsc_id, nelems); + if (!req) + return -ENOMEM; + + for (i = 0; i < nelems; i++) { + rc = msm_rpm_add_kvp_data_noirq(req, kvp[i].key, + kvp[i].data, kvp[i].length); + if (rc) + goto bail; + } + + rc = msm_rpm_wait_for_ack_noirq(msm_rpm_send_request_noirq(req)); +bail: + msm_rpm_free_request(req); + return rc; +} +EXPORT_SYMBOL(msm_rpm_send_message_noirq); +static bool msm_rpm_set_standalone(void) +{ + if (machine_is_copper()) { + pr_warn("%s(): Running in standalone mode, requests " + "will not be sent to RPM\n", __func__); + standalone = true; + } + return standalone; +} + +static int __devinit msm_rpm_dev_probe(struct platform_device *pdev) +{ + char *key = NULL; + int ret; + + key = "rpm-channel-name"; + ret = of_property_read_string(pdev->dev.of_node, key, + &msm_rpm_data.ch_name); + if (ret) + goto fail; + + key = "rpm-channel-type"; + ret = of_property_read_u32(pdev->dev.of_node, key, + &msm_rpm_data.ch_type); + if (ret) + goto fail; + + init_completion(&msm_rpm_data.smd_open); + spin_lock_init(&msm_rpm_data.smd_lock_write); + spin_lock_init(&msm_rpm_data.smd_lock_read); + INIT_WORK(&msm_rpm_data.work, msm_rpm_smd_work); + + if (smd_named_open_on_edge(msm_rpm_data.ch_name, msm_rpm_data.ch_type, + &msm_rpm_data.ch_info, &msm_rpm_data, + msm_rpm_notify)) { + pr_info("Cannot open RPM channel %s %d\n", msm_rpm_data.ch_name, + msm_rpm_data.ch_type); + + msm_rpm_set_standalone(); + BUG_ON(!standalone); + complete(&msm_rpm_data.smd_open); + } + + ret = wait_for_completion_timeout(&msm_rpm_data.smd_open, + msecs_to_jiffies(5)); + + BUG_ON(!ret); + + smd_disable_read_intr(msm_rpm_data.ch_info); + + if (!standalone) { + msm_rpm_smd_wq = create_singlethread_workqueue("rpm-smd"); + if (!msm_rpm_smd_wq) + return -EINVAL; + } + + of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + return 0; +fail: + pr_err("%s(): Failed to read node: %s, key=%s\n", __func__, + pdev->dev.of_node->full_name, key); + return -EINVAL; +} + +static struct of_device_id msm_rpm_match_table[] = { + {.compatible = "qcom,rpm-smd"}, + {}, +}; + +static struct platform_driver msm_rpm_device_driver = { + .probe = msm_rpm_dev_probe, + .driver = { + .name = "rpm-smd", + .owner = THIS_MODULE, + .of_match_table = msm_rpm_match_table, + }, +}; + +int __init msm_rpm_driver_init(void) +{ + static bool registered; + + if (registered) + return 0; + registered = true; + + return platform_driver_register(&msm_rpm_device_driver); +} +EXPORT_SYMBOL(msm_rpm_driver_init); +late_initcall(msm_rpm_driver_init); diff --git a/arch/arm/mach-msm/rpm.c b/arch/arm/mach-msm/rpm.c new file mode 100644 index 0000000000000000000000000000000000000000..44e50dd985d6f81ce66f7ed0cc5775222726b61c --- /dev/null +++ b/arch/arm/mach-msm/rpm.c @@ -0,0 +1,1023 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/****************************************************************************** + * 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[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 uint32_t msm_rpm_sel_mask_size; +static struct msm_rpm_platform_data msm_rpm_data; + +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 unsigned int target_enum(unsigned int id) +{ + BUG_ON(id >= MSM_RPM_ID_LAST); + return msm_rpm_data.target_id[id].id; +} + +static inline unsigned int target_status(unsigned int id) +{ + BUG_ON(id >= MSM_RPM_STATUS_ID_LAST); + return msm_rpm_data.target_status[id]; +} + +static inline unsigned int target_ctrl(unsigned int id) +{ + BUG_ON(id >= MSM_RPM_CTRL_LAST); + return msm_rpm_data.target_ctrl_id[id]; +} + +static inline uint32_t msm_rpm_read(unsigned int page, unsigned int reg) +{ + return __raw_readl(msm_rpm_data.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_data.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_data.sel_last + 1 : + msm_rpm_data.target_id[id].sel; +} + +/* + * 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_data.sel_last) { + pr_err("%s(): RPM ID %d not defined for target\n", + __func__, req[i].id); + 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_data.ipc_rpm_val, + msm_rpm_data.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[SEL_MASK_SIZE] = {0}; + + ctx_mask_ack = msm_rpm_read(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_ACK_CTX_0)); + msm_rpm_read_contiguous(MSM_RPM_PAGE_CTRL, + target_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, + target_ctrl(MSM_RPM_CTRL_ACK_SEL_0), + msm_rpm_sel_mask_size); + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_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, + target_enum(msm_rpm_request->req[i].id)); + + msm_rpm_write_contiguous_zeros(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_ACK_SEL_0), + msm_rpm_sel_mask_size); + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_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 void msm_rpm_err_fatal(void) +{ + /* Tell RPM that we're handling the interrupt */ + __raw_writel(0x1, msm_rpm_data.ipc_rpm_reg); + panic("RPM error fataled"); +} + +static irqreturn_t msm_rpm_err_interrupt(int irq, void *dev_id) +{ + msm_rpm_err_fatal(); + return IRQ_HANDLED; +} + +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_data.irq_ack) && + msm_rpm_request) { + if (allow_async_completion) + spin_unlock(&msm_rpm_irq_lock); + if (gic_is_spi_pending(msm_rpm_data.irq_err)) + msm_rpm_err_fatal(); + gic_clear_spi_pending(msm_rpm_data.irq_err); + 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_data.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 = 0; + uint32_t sel_masks_ack[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(target_enum(req[i].id) >= MSM_RPM_ID_LAST); + msm_rpm_write(MSM_RPM_PAGE_REQ, + target_enum(req[i].id), req[i].value); + } + + msm_rpm_write_contiguous(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_REQ_SEL_0), + sel_masks, msm_rpm_sel_mask_size); + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_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_data.irq_ack; + unsigned long flags; + uint32_t ctx_mask = msm_rpm_get_ctx_mask(ctx); + uint32_t ctx_mask_ack = 0; + uint32_t sel_masks_ack[SEL_MASK_SIZE]; + struct irq_chip *irq_chip, *err_chip; + 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_chip = irq_get_chip(irq); + if (!irq_chip) { + spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); + return -ENOSPC; + } + irq_chip->irq_mask(irq_get_irq_data(irq)); + err_chip = irq_get_chip(msm_rpm_data.irq_err); + if (!err_chip) { + irq_chip->irq_unmask(irq_get_irq_data(irq)); + spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); + return -ENOSPC; + } + err_chip->irq_mask(irq_get_irq_data(msm_rpm_data.irq_err)); + + 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(target_enum(req[i].id) >= MSM_RPM_ID_LAST); + msm_rpm_write(MSM_RPM_PAGE_REQ, + target_enum(req[i].id), req[i].value); + } + + msm_rpm_write_contiguous(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_REQ_SEL_0), + sel_masks, msm_rpm_sel_mask_size); + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_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); + + err_chip->irq_unmask(irq_get_irq_data(msm_rpm_data.irq_err)); + irq_chip->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 + * -EINVAL: invalid or invalid id in array + * -ENOSPC: request rejected + * -ENODEV: RPM driver not initialized + */ +static int msm_rpm_set_common( + int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) +{ + uint32_t sel_masks[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 { + mutex_lock(&msm_rpm_mutex); + rc = msm_rpm_set_exclusive(ctx, sel_masks, req, count); + mutex_unlock(&msm_rpm_mutex); + } + +set_common_exit: + return rc; +} + +/* + * Return value: + * 0: success + * -EINVAL: invalid or invalid id in array + * -ENODEV: RPM driver not initialized. + */ +static int msm_rpm_clear_common( + int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) +{ + uint32_t sel_masks[SEL_MASK_SIZE] = {}; + struct msm_rpm_iv_pair r[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_data.sel_invalidate)] |= + msm_rpm_get_sel_mask(msm_rpm_data.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 { + mutex_lock(&msm_rpm_mutex); + 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) +{ + unsigned int sel_notif = msm_rpm_data.sel_notification; + + if (memcmp(curr_cfg, new_cfg, sizeof(*new_cfg))) { + uint32_t sel_masks[SEL_MASK_SIZE] = {}; + int rc; + + sel_masks[msm_rpm_get_sel_mask_reg(sel_notif)] + |= msm_rpm_get_sel_mask(sel_notif); + + 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 + * -ENODEV: RPM driver not initialized + */ +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, + target_status(MSM_RPM_STATUS_ID_SEQUENCE)); + + for (i = 0; i < count; i++) { + int target_status_id; + + if (status[i].id >= MSM_RPM_STATUS_ID_LAST) { + pr_err("%s(): Status ID beyond limits\n", __func__); + rc = -EINVAL; + goto get_status_exit; + } + + target_status_id = target_status(status[i].id); + if (target_status_id >= MSM_RPM_STATUS_ID_LAST) { + pr_err("%s(): Status id %d not defined for target\n", + __func__, + target_status_id); + rc = -EINVAL; + goto get_status_exit; + } + + status[i].value = msm_rpm_read(MSM_RPM_PAGE_STATUS, + target_status_id); + } + + seq_end = msm_rpm_read(MSM_RPM_PAGE_STATUS, + target_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 + * -EINVAL: invalid or invalid id in array + * -ENOSPC: request rejected + * -ENODEV: RPM driver not initialized + */ +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 + * -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 + * -EINVAL: invalid id in array + * -ENODEV: RPM driver not initialized + */ +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; + + mutex_lock(&msm_rpm_mutex); + + 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 + * -ENODEV: RPM driver not initialized + */ +int msm_rpm_unregister_notification(struct msm_rpm_notification *n) +{ + unsigned long flags; + unsigned int ctx; + struct msm_rpm_notif_config cfg; + int rc = 0; + int i; + + mutex_lock(&msm_rpm_mutex); + 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); + + return rc; +} +EXPORT_SYMBOL(msm_rpm_unregister_notification); + +static uint32_t fw_major, fw_minor, fw_build; + +static ssize_t driver_version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", + msm_rpm_data.ver[0], msm_rpm_data.ver[1], msm_rpm_data.ver[2]); +} + +static ssize_t fw_version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", + fw_major, fw_minor, fw_build); +} + +static struct kobj_attribute driver_version_attr = __ATTR_RO(driver_version); +static struct kobj_attribute fw_version_attr = __ATTR_RO(fw_version); + +static struct attribute *driver_attributes[] = { + &driver_version_attr.attr, + &fw_version_attr.attr, + NULL +}; + +static struct attribute_group driver_attr_group = { + .attrs = driver_attributes, +}; + +static int __devinit msm_rpm_probe(struct platform_device *pdev) +{ + return sysfs_create_group(&pdev->dev.kobj, &driver_attr_group); +} + +static int __devexit msm_rpm_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &driver_attr_group); + return 0; +} + +static struct platform_driver msm_rpm_platform_driver = { + .probe = msm_rpm_probe, + .remove = __devexit_p(msm_rpm_remove), + .driver = { + .name = "msm_rpm", + .owner = THIS_MODULE, + }, +}; + +static void __init msm_rpm_populate_map(struct msm_rpm_platform_data *data) +{ + int i, j; + struct msm_rpm_map_data *src = NULL; + struct msm_rpm_map_data *dst = NULL; + + for (i = 0; i < MSM_RPM_ID_LAST;) { + src = &data->target_id[i]; + dst = &msm_rpm_data.target_id[i]; + + dst->id = MSM_RPM_ID_LAST; + dst->sel = msm_rpm_data.sel_last + 1; + + /* + * copy the target specific id of the current and also of + * all the #count id's that follow the current. + * [MSM_RPM_ID_PM8921_S1_0] = { MSM_RPM_8960_ID_PM8921_S1_0, + * MSM_RPM_8960_SEL_PM8921_S1, + * 2}, + * [MSM_RPM_ID_PM8921_S1_1] = { 0, 0, 0 }, + * should translate to + * [MSM_RPM_ID_PM8921_S1_0] = { MSM_RPM_8960_ID_PM8921_S1_0, + * MSM_RPM_8960_SEL_PM8921, + * 2 }, + * [MSM_RPM_ID_PM8921_S1_1] = { MSM_RPM_8960_ID_PM8921_S1_0 + 1, + * MSM_RPM_8960_SEL_PM8921, + * 0 }, + */ + for (j = 0; j < src->count; j++) { + dst = &msm_rpm_data.target_id[i + j]; + dst->id = src->id + j; + dst->sel = src->sel; + } + + i += (src->count) ? src->count : 1; + } + + for (i = 0; i < MSM_RPM_STATUS_ID_LAST; i++) { + if (data->target_status[i] & MSM_RPM_STATUS_ID_VALID) + msm_rpm_data.target_status[i] &= + ~MSM_RPM_STATUS_ID_VALID; + else + msm_rpm_data.target_status[i] = MSM_RPM_STATUS_ID_LAST; + } +} + +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; +} + +int __init msm_rpm_init(struct msm_rpm_platform_data *data) +{ + int rc; + + memcpy(&msm_rpm_data, data, sizeof(struct msm_rpm_platform_data)); + msm_rpm_sel_mask_size = msm_rpm_data.sel_last / 32 + 1; + BUG_ON(SEL_MASK_SIZE < msm_rpm_sel_mask_size); + + fw_major = msm_rpm_read(MSM_RPM_PAGE_STATUS, + target_status(MSM_RPM_STATUS_ID_VERSION_MAJOR)); + fw_minor = msm_rpm_read(MSM_RPM_PAGE_STATUS, + target_status(MSM_RPM_STATUS_ID_VERSION_MINOR)); + fw_build = msm_rpm_read(MSM_RPM_PAGE_STATUS, + target_status(MSM_RPM_STATUS_ID_VERSION_BUILD)); + pr_info("%s: RPM firmware %u.%u.%u\n", __func__, + fw_major, fw_minor, fw_build); + + if (fw_major != msm_rpm_data.ver[0]) { + pr_err("%s: RPM version %u.%u.%u incompatible with " + "this driver version %u.%u.%u\n", __func__, + fw_major, fw_minor, fw_build, + msm_rpm_data.ver[0], + msm_rpm_data.ver[1], + msm_rpm_data.ver[2]); + return -EFAULT; + } + + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_VERSION_MAJOR), msm_rpm_data.ver[0]); + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_VERSION_MINOR), msm_rpm_data.ver[1]); + msm_rpm_write(MSM_RPM_PAGE_CTRL, + target_ctrl(MSM_RPM_CTRL_VERSION_BUILD), msm_rpm_data.ver[2]); + + rc = request_irq(data->irq_ack, 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__, data->irq_ack, rc); + return rc; + } + + rc = irq_set_irq_wake(data->irq_ack, 1); + if (rc) { + pr_err("%s: failed to set wakeup irq %u: %d\n", + __func__, data->irq_ack, rc); + return rc; + } + + rc = request_irq(data->irq_err, msm_rpm_err_interrupt, + IRQF_TRIGGER_RISING, "rpm_err", NULL); + if (rc) { + pr_err("%s: failed to request error interrupt: %d\n", + __func__, rc); + return rc; + } + + rc = request_irq(data->irq_wakeup, + msm_pm_rpm_wakeup_interrupt, IRQF_TRIGGER_RISING, + "pm_drv", msm_pm_rpm_wakeup_interrupt); + if (rc) { + pr_err("%s: failed to request irq %u: %d\n", + __func__, data->irq_wakeup, rc); + return rc; + } + + rc = irq_set_irq_wake(data->irq_wakeup, 1); + if (rc) { + pr_err("%s: failed to set wakeup irq %u: %d\n", + __func__, data->irq_wakeup, rc); + return rc; + } + + msm_rpm_populate_map(data); + + return platform_driver_register(&msm_rpm_platform_driver); +} diff --git a/arch/arm/mach-msm/rpm_log.c b/arch/arm/mach-msm/rpm_log.c new file mode 100644 index 0000000000000000000000000000000000000000..3ed55da2848a8c0bc78d57c5fc9e0ea446e2ac7d --- /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 0000000000000000000000000000000000000000..37349b60f0a75515fd9d26829ae081db48d5b911 --- /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 0000000000000000000000000000000000000000..5314cee6d16c6782c44fbf6b6c457f2f7cbda41a --- /dev/null +++ b/arch/arm/mach-msm/rpm_resources.c @@ -0,0 +1,1106 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "rpm_resources.h" +#include "spm.h" +#include "idle.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 ssize_t msm_rpmrs_resource_attr_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf); +static ssize_t msm_rpmrs_resource_attr_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); + +static int vdd_dig_vlevels[MSM_RPMRS_VDD_DIG_LAST]; +static int vdd_mem_vlevels[MSM_RPMRS_VDD_MEM_LAST]; +static int vdd_mask; + +#define MSM_RPMRS_MAX_RS_REGISTER_COUNT 2 + +#define RPMRS_ATTR(_name) \ + __ATTR(_name, S_IRUGO|S_IWUSR, \ + msm_rpmrs_resource_attr_show, msm_rpmrs_resource_attr_store) + +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); + + struct kobj_attribute ko_attr; +}; + +static struct msm_rpmrs_resource msm_rpmrs_pxo = { + .size = 1, + .name = "pxo", + .beyond_limits = msm_rpmrs_pxo_beyond_limits, + .aggregate = msm_rpmrs_aggregate_pxo, + .restore = msm_rpmrs_restore_pxo, + .ko_attr = RPMRS_ATTR(pxo), +}; + +static struct msm_rpmrs_resource msm_rpmrs_l2_cache = { + .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, + .ko_attr = RPMRS_ATTR(L2_cache), +}; + +static struct msm_rpmrs_resource msm_rpmrs_vdd_mem = { + .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, + .ko_attr = RPMRS_ATTR(vdd_mem), +}; + +static struct msm_rpmrs_resource msm_rpmrs_vdd_dig = { + .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, + .ko_attr = RPMRS_ATTR(vdd_dig), +}; + +static struct msm_rpmrs_resource msm_rpmrs_rpm_ctl = { + .size = 1, + .name = "rpm_ctl", + .beyond_limits = NULL, + .aggregate = NULL, + .restore = NULL, + .ko_attr = RPMRS_ATTR(rpm_ctl), +}; + +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_ctl, +}; + +static uint32_t msm_rpmrs_buffer[MSM_RPM_ID_LAST]; +static DECLARE_BITMAP(msm_rpmrs_buffered, MSM_RPM_ID_LAST); +static DECLARE_BITMAP(msm_rpmrs_listed, MSM_RPM_ID_LAST); +static DEFINE_SPINLOCK(msm_rpmrs_lock); + +#define MSM_RPMRS_VDD(v) ((v) & (vdd_mask)) + +/****************************************************************************** + * Attribute Definitions + *****************************************************************************/ +static struct attribute *msm_rpmrs_attributes[] = { + &msm_rpmrs_pxo.ko_attr.attr, + &msm_rpmrs_l2_cache.ko_attr.attr, + &msm_rpmrs_vdd_mem.ko_attr.attr, + &msm_rpmrs_vdd_dig.ko_attr.attr, + NULL, +}; +static struct attribute *msm_rpmrs_mode_attributes[] = { + &msm_rpmrs_rpm_ctl.ko_attr.attr, + NULL, +}; + +static struct attribute_group msm_rpmrs_attribute_group = { + .attrs = msm_rpmrs_attributes, +}; + +static struct attribute_group msm_rpmrs_mode_attribute_group = { + .attrs = msm_rpmrs_mode_attributes, +}; + +#define GET_RS_FROM_ATTR(attr) \ + (container_of(attr, struct msm_rpmrs_resource, ko_attr)) + + +/****************************************************************************** + * 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); + } +} + +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; +} + +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 = vdd_mem_vlevels[MSM_RPMRS_VDD_MEM_ACTIVE]; + else if (rs->enable_low_power == 1) + vdd_mem = vdd_mem_vlevels[MSM_RPMRS_VDD_MEM_RET_HIGH]; + else + vdd_mem = vdd_mem_vlevels[MSM_RPMRS_VDD_MEM_RET_LOW]; + + if (MSM_RPMRS_VDD(buffered_value) > MSM_RPMRS_VDD(vdd_mem)) + vdd_mem = MSM_RPMRS_VDD(buffered_value); + } else { + vdd_mem = vdd_mem_vlevels[MSM_RPMRS_VDD_MEM_ACTIVE]; + } + + return vdd_mem > vdd_mem_vlevels[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 (vdd_mem_vlevels[limits->vdd_mem] > MSM_RPMRS_VDD(*buf)) { + *buf &= ~vdd_mask; + *buf |= vdd_mem_vlevels[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 = vdd_dig_vlevels[MSM_RPMRS_VDD_DIG_ACTIVE]; + else if (rs->enable_low_power == 1) + vdd_dig = vdd_dig_vlevels[MSM_RPMRS_VDD_DIG_RET_HIGH]; + else + vdd_dig = vdd_dig_vlevels[MSM_RPMRS_VDD_DIG_RET_LOW]; + + if (MSM_RPMRS_VDD(buffered_value) > MSM_RPMRS_VDD(vdd_dig)) + vdd_dig = MSM_RPMRS_VDD(buffered_value); + } else { + vdd_dig = vdd_dig_vlevels[MSM_RPMRS_VDD_DIG_ACTIVE]; + } + + return vdd_dig > vdd_dig_vlevels[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 (vdd_dig_vlevels[limits->vdd_dig] > MSM_RPMRS_VDD(*buf)) { + *buf &= ~vdd_mask; + *buf |= vdd_dig_vlevels[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; +} + +/****************************************************************************** + * Buffering Functions + *****************************************************************************/ + +static bool msm_rpmrs_irqs_detectable(struct msm_rpmrs_limits *limits, + bool irqs_detect, bool gpio_detect) +{ + + if (vdd_dig_vlevels[limits->vdd_dig_upper_bound] <= + vdd_dig_vlevels[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) || + (vdd_dig_vlevels[limits->vdd_dig] <= + vdd_dig_vlevels[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; + msm_pm_set_l2_flush_flag(1); + 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; +} +static void msm_rpmrs_L2_restore(struct msm_rpmrs_limits *limits, + bool notify_rpm, bool collapsed) +{ + msm_spm_l2_set_low_power_mode(MSM_SPM_MODE_DISABLED, notify_rpm); + msm_pm_set_l2_flush_flag(0); +} +#else +static int msm_rpmrs_flush_L2(struct msm_rpmrs_limits *limits, int notify_rpm) +{ + return 0; +} +static void msm_rpmrs_L2_restore(struct msm_rpmrs_limits *limits, + bool notify_rpm, bool collapsed) +{ +} +#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); + + 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); + + while (i < MSM_RPM_ID_LAST) { + 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, 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); + +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); + /* special case active-set signal for MSM_RPMRS_ID_RPM_CTL */ + if (GET_RS_FROM_ATTR(attr)->rs[0].id == + msm_rpmrs_rpm_ctl.rs[0].id) + temp = GET_RS_FROM_ATTR(attr)->rs[0].value; + else + 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) { + strlcat(buf, "\n", PAGE_SIZE); + 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; + + /* special case active-set signal for MSM_RPMRS_ID_RPM_CTL */ + if (GET_RS_FROM_ATTR(attr)->rs[0].id == + msm_rpmrs_rpm_ctl.rs[0].id) { + struct msm_rpm_iv_pair req; + req.id = msm_rpmrs_rpm_ctl.rs[0].id; + req.value = GET_RS_FROM_ATTR(attr)->enable_low_power; + GET_RS_FROM_ATTR(attr)->rs[0].value = req.value; + + rc = msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &req, 1); + if (rc) { + pr_err("%s: failed to request RPM_CTL to %d: %d\n", + __func__, req.value, rc); + } + } + + 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 = NULL; + struct kobject *low_power_kobj = NULL; + struct kobject *mode_kobj = NULL; + int rc = 0; + + 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_kobj = kobject_create_and_add( + "enable_low_power", module_kobj); + if (!low_power_kobj) { + pr_err("%s: cannot create kobject\n", __func__); + rc = -ENOMEM; + goto resource_sysfs_add_exit; + } + + mode_kobj = kobject_create_and_add( + "mode", module_kobj); + if (!mode_kobj) { + pr_err("%s: cannot create kobject\n", __func__); + rc = -ENOMEM; + goto resource_sysfs_add_exit; + } + + rc = sysfs_create_group(low_power_kobj, &msm_rpmrs_attribute_group); + if (rc) { + pr_err("%s: cannot create kobject attribute group\n", __func__); + goto resource_sysfs_add_exit; + } + + rc = sysfs_create_group(mode_kobj, &msm_rpmrs_mode_attribute_group); + if (rc) { + pr_err("%s: cannot create kobject attribute group\n", __func__); + goto resource_sysfs_add_exit; + } + + rc = 0; +resource_sysfs_add_exit: + if (rc) { + if (low_power_kobj) + sysfs_remove_group(low_power_kobj, + &msm_rpmrs_attribute_group); + kobject_del(low_power_kobj); + kobject_del(mode_kobj); + } + + 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); +} + +/* Allow individual bits of an rpm resource be set, currently used only for + * active context resource viz. RPM_CTL. The API is generic enough to possibly + * extend it to other resources as well in the future. + */ +int msm_rpmrs_set_bits_noirq(int ctx, struct msm_rpm_iv_pair *req, int count, + int *mask) +{ + unsigned long flags; + int i, j; + int rc = -1; + struct msm_rpmrs_resource *rs; + + if (ctx != MSM_RPM_CTX_SET_0) + return -ENOSYS; + + 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 == req[0].id && rs->size == count) { + for (j = 0; j < rs->size; j++) { + rs->rs[j].value &= ~mask[j]; + rs->rs[j].value |= req[j].value & mask[j]; + } + break; + } + } + + if (i != ARRAY_SIZE(msm_rpmrs_resources)) { + rc = msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &rs->rs[0], rs->size); + if (rc) { + for (j = 0; j < rs->size; j++) { + pr_err("%s: failed to request %d to %d: %d\n", + __func__, + rs->rs[j].id, rs->rs[j].value, rc); + } + } + } + spin_unlock_irqrestore(&msm_rpmrs_lock, flags); + + return rc; + +} + +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) + 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); +} + +static void *msm_rpmrs_lowest_limits(bool from_idle, + enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us, + uint32_t sleep_us, uint32_t *power) +{ + unsigned int cpu = smp_processor_id(); + struct msm_rpmrs_level *best_level = NULL; + bool irqs_detectable = false; + bool gpio_detectable = false; + int i; + uint32_t pwr; + + 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]; + + if (!level->available) + continue; + + if (sleep_mode != level->sleep_mode) + continue; + + if (latency_us < level->latency_us) + continue; + + if (sleep_us <= level->time_overhead_us) + continue; + + if (!msm_rpmrs_irqs_detectable(&level->rs_limits, + irqs_detectable, gpio_detectable)) + continue; + + if (MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE == sleep_mode) + if (!cpu && msm_rpm_local_request_is_outstanding()) + break; + + + if (sleep_us <= 1) { + pwr = level->energy_overhead; + } else if (sleep_us <= level->time_overhead_us) { + pwr = level->energy_overhead / sleep_us; + } else if ((sleep_us >> 10) > level->time_overhead_us) { + pwr = level->steady_state_power; + } else { + pwr = level->steady_state_power; + pwr -= (level->time_overhead_us * + level->steady_state_power)/sleep_us; + pwr += level->energy_overhead / sleep_us; + } + + if (!best_level || + best_level->rs_limits.power[cpu] >= pwr) { + level->rs_limits.latency_us[cpu] = level->latency_us; + level->rs_limits.power[cpu] = pwr; + best_level = level; + if (power) + *power = pwr; + } + } + + return best_level ? &best_level->rs_limits : NULL; +} + +static int msm_rpmrs_enter_sleep(uint32_t sclk_count, void *limits, + bool from_idle, bool notify_rpm) +{ + int rc = 0; + + 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); + } + + rc = msm_rpmrs_flush_L2(limits, notify_rpm); + return rc; +} + +static void msm_rpmrs_exit_sleep(void *limits, bool from_idle, + bool notify_rpm, bool collapsed) +{ + + /* Disable L2 for now, we dont want L2 to do retention by default */ + msm_rpmrs_L2_restore(limits, notify_rpm, collapsed); + + if (msm_rpmrs_use_mpm(limits)) + msm_mpm_exit_sleep(from_idle); +} + +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, +}; + +int __init msm_rpmrs_levels_init(struct msm_rpmrs_platform_data *data) +{ + int i, k; + struct msm_rpmrs_level *levels = data->levels; + + msm_rpmrs_level_count = data->num_levels; + + msm_rpmrs_levels = kzalloc(sizeof(struct msm_rpmrs_level) * + msm_rpmrs_level_count, GFP_KERNEL); + if (!msm_rpmrs_levels) + return -ENOMEM; + + memcpy(msm_rpmrs_levels, levels, + msm_rpmrs_level_count * sizeof(struct msm_rpmrs_level)); + + memcpy(vdd_dig_vlevels, data->vdd_dig_levels, + (MSM_RPMRS_VDD_DIG_MAX + 1) * sizeof(vdd_dig_vlevels[0])); + + memcpy(vdd_mem_vlevels, data->vdd_mem_levels, + (MSM_RPMRS_VDD_MEM_MAX + 1) * sizeof(vdd_mem_vlevels[0])); + vdd_mask = data->vdd_mask; + + msm_rpmrs_pxo.rs[0].id = data->rpmrs_target_id[MSM_RPMRS_ID_PXO_CLK]; + msm_rpmrs_l2_cache.rs[0].id = + data->rpmrs_target_id[MSM_RPMRS_ID_L2_CACHE_CTL]; + msm_rpmrs_vdd_mem.rs[0].id = + data->rpmrs_target_id[MSM_RPMRS_ID_VDD_MEM_0]; + msm_rpmrs_vdd_mem.rs[1].id = + data->rpmrs_target_id[MSM_RPMRS_ID_VDD_MEM_1]; + msm_rpmrs_vdd_dig.rs[0].id = + data->rpmrs_target_id[MSM_RPMRS_ID_VDD_DIG_0]; + msm_rpmrs_vdd_dig.rs[1].id = + data->rpmrs_target_id[MSM_RPMRS_ID_VDD_DIG_1]; + msm_rpmrs_rpm_ctl.rs[0].id = + data->rpmrs_target_id[MSM_RPMRS_ID_RPM_CTL]; + + /* 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++) { + if (msm_rpmrs_resources[i]->rs[k].id >= + MSM_RPM_ID_LAST) + continue; + set_bit(msm_rpmrs_resources[i]->rs[k].id, + msm_rpmrs_listed); + } + } + + return 0; +} + +static int __init msm_rpmrs_init(void) +{ + struct msm_rpm_iv_pair req; + int rc; + + BUG_ON(!msm_rpmrs_levels); + + if (cpu_is_msm8x60()) { + req.id = msm_rpmrs_l2_cache.rs[0].id; + 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_l2_cache.rs[0].id; + 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; + } + } + + rc = msm_rpmrs_resource_sysfs_add(); + +init_exit: + return rc; +} +device_initcall(msm_rpmrs_init); + +static struct msm_pm_sleep_ops msm_rpmrs_ops = { + .lowest_limits = msm_rpmrs_lowest_limits, + .enter_sleep = msm_rpmrs_enter_sleep, + .exit_sleep = msm_rpmrs_exit_sleep, +}; + +static int __init msm_rpmrs_l2_init(void) +{ + if (cpu_is_msm8960() || cpu_is_msm8930() || cpu_is_apq8064()) { + + msm_pm_set_l2_flush_flag(0); + + 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); + + } else if (cpu_is_msm9615()) { + msm_rpmrs_l2_cache.beyond_limits = NULL; + msm_rpmrs_l2_cache.aggregate = NULL; + msm_rpmrs_l2_cache.restore = NULL; + } + + msm_pm_set_sleep_ops(&msm_rpmrs_ops); + + return 0; +} +early_initcall(msm_rpmrs_l2_init); diff --git a/arch/arm/mach-msm/rpm_resources.h b/arch/arm/mach-msm/rpm_resources.h new file mode 100644 index 0000000000000000000000000000000000000000..d5944057fb8f850b49b1a7713349fb9faf944eca --- /dev/null +++ b/arch/arm/mach-msm/rpm_resources.h @@ -0,0 +1,142 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" + +enum { + MSM_RPMRS_ID_PXO_CLK = 0, + MSM_RPMRS_ID_L2_CACHE_CTL = 1, + MSM_RPMRS_ID_VDD_DIG_0 = 2, + MSM_RPMRS_ID_VDD_DIG_1 = 3, + MSM_RPMRS_ID_VDD_MEM_0 = 4, + MSM_RPMRS_ID_VDD_MEM_1 = 5, + MSM_RPMRS_ID_RPM_CTL = 6, + MSM_RPMRS_ID_LAST, +}; + +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_MASK_RPM_CTL_CPU_HALT = 1, + MSM_RPMRS_MASK_RPM_CTL_MULTI_TIER = 2, +}; + +enum { + MSM_RPMRS_VDD_MEM_RET_LOW = 0, + MSM_RPMRS_VDD_MEM_RET_HIGH = 1, + MSM_RPMRS_VDD_MEM_ACTIVE = 2, + MSM_RPMRS_VDD_MEM_MAX = 3, + MSM_RPMRS_VDD_MEM_LAST, +}; + +enum { + MSM_RPMRS_VDD_DIG_RET_LOW = 0, + MSM_RPMRS_VDD_DIG_RET_HIGH = 1, + MSM_RPMRS_VDD_DIG_ACTIVE = 2, + MSM_RPMRS_VDD_DIG_MAX = 3, + MSM_RPMRS_VDD_DIG_LAST, +}; + +#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_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]; +}; + +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; +}; + +struct msm_rpmrs_platform_data { + struct msm_rpmrs_level *levels; + unsigned int num_levels; + unsigned int vdd_mem_levels[MSM_RPMRS_VDD_MEM_LAST]; + unsigned int vdd_dig_levels[MSM_RPMRS_VDD_DIG_LAST]; + unsigned int vdd_mask; + unsigned int rpmrs_target_id[MSM_RPMRS_ID_LAST]; +}; + +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); +int msm_rpmrs_set_bits_noirq(int ctx, struct msm_rpm_iv_pair *req, int count, + int *mask); + +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); +int msm_rpmrs_levels_init(struct msm_rpmrs_platform_data *data); + +#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 0000000000000000000000000000000000000000..a831bd5a35f8abad370f15995f766d836f2edfcf --- /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 0000000000000000000000000000000000000000..918d4fba7af2440b4ca7bc486ab8746961939fab --- /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 0000000000000000000000000000000000000000..6762648f12d27b33774ca3b514a30f9be1b3e142 --- /dev/null +++ b/arch/arm/mach-msm/saw-regulator.c @@ -0,0 +1,237 @@ +/* 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 "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, NULL); + 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 45cee3e469a5f45cb45b8d6fc6ae51965d06eb3d..e377633255dac878c9c1f8b0028e3de29bdea99b 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 7be32ff5d6877244b7fe7f0a49aa29a804f90a87..221ffca96239df53e2db5cb1f22974d404731ec2 100644 --- a/arch/arm/mach-msm/scm-boot.h +++ b/arch/arm/mach-msm/scm-boot.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010, 2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -12,11 +12,19 @@ #ifndef __MACH_SCM_BOOT_H #define __MACH_SCM_BOOT_H -#define SCM_BOOT_ADDR 0x1 -#define SCM_FLAG_COLDBOOT_CPU1 0x1 -#define SCM_FLAG_WARMBOOT_CPU1 0x2 -#define SCM_FLAG_WARMBOOT_CPU0 0x4 +#define SCM_BOOT_ADDR 0x1 +#define SCM_FLAG_COLDBOOT_CPU1 0x01 +#define SCM_FLAG_COLDBOOT_CPU2 0x08 +#define SCM_FLAG_COLDBOOT_CPU3 0x20 +#define SCM_FLAG_WARMBOOT_CPU1 0x02 +#define SCM_FLAG_WARMBOOT_CPU0 0x04 +#define SCM_FLAG_WARMBOOT_CPU2 0x10 +#define SCM_FLAG_WARMBOOT_CPU3 0x40 -int scm_set_boot_addr(phys_addr_t addr, int flags); +#ifdef CONFIG_MSM_SCM +int scm_set_boot_addr(void *addr, int flags); +#else +static inline int scm_set_boot_addr(void *addr, int flags) { return 0; } +#endif #endif diff --git a/arch/arm/mach-msm/scm-io.c b/arch/arm/mach-msm/scm-io.c new file mode 100644 index 0000000000000000000000000000000000000000..28614d348a6c25a14e30c5a6249ae4150c674d72 --- /dev/null +++ b/arch/arm/mach-msm/scm-io.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 +#include +#include + +#include +#include +#include + +#define SCM_IO_READ 0x1 +#define SCM_IO_WRITE 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 r; + r = scm_call_atomic1(SCM_SVC_IO, SCM_IO_READ, addr); + __iormb(); + return r; +} + +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) +{ + __iowmb(); + scm_call_atomic2(SCM_SVC_IO, SCM_IO_WRITE, addr, v); +} + +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-pas.c b/arch/arm/mach-msm/scm-pas.c new file mode 100644 index 0000000000000000000000000000000000000000..4096d9c7fd1ba63d3f9a553255d0d9b2f4c775c5 --- /dev/null +++ b/arch/arm/mach-msm/scm-pas.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "scm-pas: " fmt + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "scm-pas.h" + +#define PAS_INIT_IMAGE_CMD 1 +#define PAS_AUTH_AND_RESET_CMD 5 +#define PAS_SHUTDOWN_CMD 6 +#define PAS_IS_SUPPORTED_CMD 7 + +int pas_init_image(enum pas_id id, const u8 *metadata, size_t size) +{ + int ret; + struct pas_init_image_req { + u32 proc; + u32 image_addr; + } request; + u32 scm_ret = 0; + /* Make memory physically contiguous */ + void *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), &scm_ret, sizeof(scm_ret)); + kfree(mdata_buf); + + if (ret) + return ret; + return scm_ret; +} +EXPORT_SYMBOL(pas_init_image); + +static struct msm_bus_paths scm_pas_bw_tbl[] = { + { + .vectors = (struct msm_bus_vectors[]){ + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + }, + }, + .num_paths = 1, + }, + { + .vectors = (struct msm_bus_vectors[]){ + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = 492 * 8 * 1000000UL, + .ab = 492 * 8 * 100000UL, + }, + }, + .num_paths = 1, + }, +}; + +static struct msm_bus_scale_pdata scm_pas_bus_pdata = { + .usecase = scm_pas_bw_tbl, + .num_usecases = ARRAY_SIZE(scm_pas_bw_tbl), + .name = "scm_pas", +}; + +static uint32_t scm_perf_client; +static struct clk *scm_bus_clk; + +static DEFINE_MUTEX(scm_pas_bw_mutex); +static int scm_pas_bw_count; + +static int scm_pas_enable_bw(void) +{ + int ret = 0; + + if (!scm_perf_client || !scm_bus_clk) + return -EINVAL; + + mutex_lock(&scm_pas_bw_mutex); + if (!scm_pas_bw_count) { + ret = msm_bus_scale_client_update_request(scm_perf_client, 1); + if (ret) { + pr_err("bandwidth request failed (%d)\n", ret); + } else { + ret = clk_prepare_enable(scm_bus_clk); + if (ret) + pr_err("clock enable failed\n"); + } + } + if (ret) + msm_bus_scale_client_update_request(scm_perf_client, 0); + else + scm_pas_bw_count++; + mutex_unlock(&scm_pas_bw_mutex); + return ret; +} + +static void scm_pas_disable_bw(void) +{ + mutex_lock(&scm_pas_bw_mutex); + if (scm_pas_bw_count-- == 1) { + msm_bus_scale_client_update_request(scm_perf_client, 0); + clk_disable_unprepare(scm_bus_clk); + } + mutex_unlock(&scm_pas_bw_mutex); +} + +int pas_auth_and_reset(enum pas_id id) +{ + int ret, bus_ret; + u32 proc = id, scm_ret = 0; + + bus_ret = scm_pas_enable_bw(); + ret = scm_call(SCM_SVC_PIL, PAS_AUTH_AND_RESET_CMD, &proc, + sizeof(proc), &scm_ret, sizeof(scm_ret)); + if (ret) + scm_ret = ret; + if (!bus_ret) + scm_pas_disable_bw(); + + return scm_ret; +} +EXPORT_SYMBOL(pas_auth_and_reset); + +int pas_shutdown(enum pas_id id) +{ + int ret; + u32 proc = id, scm_ret = 0; + + ret = scm_call(SCM_SVC_PIL, PAS_SHUTDOWN_CMD, &proc, sizeof(proc), + &scm_ret, sizeof(scm_ret)); + if (ret) + return ret; + + return scm_ret; +} +EXPORT_SYMBOL(pas_shutdown); + +static bool secure_pil = true; +module_param(secure_pil, bool, S_IRUGO); +MODULE_PARM_DESC(secure_pil, "Use secure PIL"); + +int pas_supported(enum pas_id id) +{ + int ret; + u32 periph = id, ret_val = 0; + + if (!secure_pil) + return 0; + + /* + * 8660 SCM doesn't support querying secure PIL support so just return + * true if not overridden on the command line. + */ + if (cpu_is_msm8x60()) + return 1; + + if (scm_is_call_available(SCM_SVC_PIL, PAS_IS_SUPPORTED_CMD) <= 0) + return 0; + + ret = scm_call(SCM_SVC_PIL, PAS_IS_SUPPORTED_CMD, &periph, + sizeof(periph), &ret_val, sizeof(ret_val)); + if (ret) + return ret; + + return ret_val; +} +EXPORT_SYMBOL(pas_supported); + +static int __init scm_pas_init(void) +{ + scm_perf_client = msm_bus_scale_register_client(&scm_pas_bus_pdata); + if (!scm_perf_client) + pr_warn("unable to register bus client\n"); + scm_bus_clk = clk_get_sys("scm", "bus_clk"); + if (!IS_ERR(scm_bus_clk)) { + clk_set_rate(scm_bus_clk, 64000000); + } else { + scm_bus_clk = NULL; + pr_warn("unable to get bus clock\n"); + } + return 0; +} +module_init(scm_pas_init); diff --git a/arch/arm/mach-msm/scm-pas.h b/arch/arm/mach-msm/scm-pas.h new file mode 100644 index 0000000000000000000000000000000000000000..2fe71a93ff11c8cfb20e2887607f9ef7ce13924c --- /dev/null +++ b/arch/arm/mach-msm/scm-pas.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SCM_PAS_H +#define __MSM_SCM_PAS_H + +enum pas_id { + PAS_MODEM, + PAS_Q6, + PAS_DSPS, + PAS_TZAPPS, + PAS_MODEM_SW, + PAS_MODEM_FW, + PAS_RIVA, + PAS_SECAPP, + PAS_GSS, + PAS_VIDC, +}; + +extern int pas_init_image(enum pas_id id, const u8 *metadata, size_t size); +extern int pas_auth_and_reset(enum pas_id id); +extern int pas_shutdown(enum pas_id id); +extern int pas_supported(enum pas_id id); +#endif diff --git a/arch/arm/mach-msm/scm.c b/arch/arm/mach-msm/scm.c index bafabb502580e87cd4473e2261582bd796404897..ac4899045962d7181c83a377e258860e669351ca 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-2012, Code Aurora Forum. All rights reserved. * * This 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 @@ -210,6 +203,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 @@ -227,6 +235,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) @@ -243,17 +252,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: @@ -262,6 +269,105 @@ int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len, } EXPORT_SYMBOL(scm_call); +#define SCM_CLASS_REGISTER (0x2 << 8) +#define SCM_MASK_IRQS BIT(5) +#define SCM_ATOMIC(svc, cmd, n) (((((svc) << 10)|((cmd) & 0x3ff)) << 12) | \ + SCM_CLASS_REGISTER | \ + SCM_MASK_IRQS | \ + (n & 0xf)) + +/** + * scm_call_atomic1() - Send an atomic SCM command with one argument + * @svc_id: service identifier + * @cmd_id: command identifier + * @arg1: first argument + * + * This shall only be used with commands that are guaranteed to be + * uninterruptable, atomic and SMP safe. + */ +s32 scm_call_atomic1(u32 svc, u32 cmd, u32 arg1) +{ + int context_id; + register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 1); + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = arg1; + + asm volatile( + __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) + : "r3"); + return r0; +} +EXPORT_SYMBOL(scm_call_atomic1); + +/** + * scm_call_atomic2() - Send an atomic SCM command with two arguments + * @svc_id: service identifier + * @cmd_id: command identifier + * @arg1: first argument + * @arg2: second argument + * + * This shall only be used with commands that are guaranteed to be + * uninterruptable, atomic and SMP safe. + */ +s32 scm_call_atomic2(u32 svc, u32 cmd, u32 arg1, u32 arg2) +{ + int context_id; + register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 2); + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = arg1; + register u32 r3 asm("r3") = arg2; + + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r0") + __asmeq("%2", "r1") + __asmeq("%3", "r2") + __asmeq("%4", "r3") + "smc #0 @ switch to secure world\n" + : "=r" (r0) + : "r" (r0), "r" (r1), "r" (r2), "r" (r3)); + return r0; +} +EXPORT_SYMBOL(scm_call_atomic2); + +s32 scm_call_atomic4_3(u32 svc, u32 cmd, u32 arg1, u32 arg2, + u32 arg3, u32 arg4, u32 *ret1, u32 *ret2) +{ + int ret; + int context_id; + register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 4); + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = arg1; + register u32 r3 asm("r3") = arg2; + register u32 r4 asm("r4") = arg3; + register u32 r5 asm("r5") = arg4; + + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r1") + __asmeq("%2", "r2") + __asmeq("%3", "r0") + __asmeq("%4", "r1") + __asmeq("%5", "r2") + __asmeq("%6", "r3") + "smc #0 @ switch to secure world\n" + : "=r" (r0), "=r" (r1), "=r" (r2) + : "r" (r0), "r" (r1), "r" (r2), "r" (r3), "r" (r4), "r" (r5)); + ret = r0; + if (ret1) + *ret1 = r1; + if (ret2) + *ret2 = r2; + return r0; +} +EXPORT_SYMBOL(scm_call_atomic4_3); + u32 scm_get_version(void) { int context_id; @@ -294,3 +400,43 @@ u32 scm_get_version(void) return version; } EXPORT_SYMBOL(scm_get_version); + +#define IS_CALL_AVAIL_CMD 1 +int scm_is_call_available(u32 svc_id, u32 cmd_id) +{ + int ret; + u32 svc_cmd = (svc_id << 10) | cmd_id; + u32 ret_val = 0; + + ret = scm_call(SCM_SVC_INFO, IS_CALL_AVAIL_CMD, &svc_cmd, + sizeof(svc_cmd), &ret_val, sizeof(ret_val)); + if (ret) + return ret; + + return ret_val; +} +EXPORT_SYMBOL(scm_is_call_available); + +#define GET_FEAT_VERSION_CMD 3 +int scm_get_feat_version(u32 feat) +{ + if (scm_is_call_available(SCM_SVC_INFO, GET_FEAT_VERSION_CMD)) { + u32 version; + if (!scm_call(SCM_SVC_INFO, GET_FEAT_VERSION_CMD, &feat, + sizeof(feat), &version, sizeof(version))) + return version; + } + return 0; +} +EXPORT_SYMBOL(scm_get_feat_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 0000000000000000000000000000000000000000..356ce9009fc11292d1b82417497b1eb343690eac --- /dev/null +++ b/arch/arm/mach-msm/sdio_al.c @@ -0,0 +1,4365 @@ +/* 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 "sdio_al_private.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 "../../../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 256 + +/** 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) +#define LOW_LATENCY_THRESHOLD (1) + +/* Extra bytes to ensure getting the rx threshold interrupt on stream channels + when restoring the threshold after sleep */ +#define THRESHOLD_CHANGE_EXTRA_BYTES (100) + +/** 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_CLOSE_FLUSH_TIMEOUT_MSEC (10000) +#define RX_FLUSH_BUFFER_SIZE (16*1024) + +#define SDIO_TEST_POSTFIX "_TEST" + +#define DATA_DEBUG(x, y...) \ + do { \ + if (sdio_al->debug.debug_data_on) \ + pr_info(y); \ + sdio_al_log(x, y); \ + } while (0) + +#define LPM_DEBUG(x, y...) \ + do { \ + if (sdio_al->debug.debug_lpm_on) \ + pr_info(y); \ + sdio_al_log(x, y); \ + } while (0) + +#define sdio_al_loge(x, y...) \ + do { \ + pr_err(y); \ + sdio_al_log(x, y); \ + } while (0) + +#define sdio_al_logi(x, y...) \ + do { \ + pr_info(y); \ + sdio_al_log(x, y); \ + } while (0) + +#define CLOSE_DEBUG(x, y...) \ + do { \ + if (sdio_al->debug.debug_close_on) \ + pr_info(y); \ + sdio_al_log(x, y); \ + } while (0) + +/* 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; + u8 debug_close_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_close_on; + struct dentry *sdio_al_debug_info; + struct dentry *sdio_al_debug_log_buffers[MAX_NUM_OF_SDIO_DEVICES + 1]; +}; + +/* 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 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]; +}; + +#define SDIO_AL_DEBUG_LOG_SIZE 3000 +struct sdio_al_local_log { + char buffer[SDIO_AL_DEBUG_LOG_SIZE]; + unsigned int buf_cur_pos; + spinlock_t log_lock; +}; + +#define SDIO_AL_DEBUG_TMP_LOG_SIZE 250 +static int sdio_al_log(struct sdio_al_local_log *, const char *fmt, ...); + +/** + * 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_local_log gen_log; + struct sdio_al_local_log device_log[MAX_NUM_OF_SDIO_DEVICES]; + 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; + int skip_print_info; +}; + +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 sdio_al_local_log *dev_log; + struct mmc_card *card; + struct mmc_host *host; + 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 is_suspended; + + int flashless_boot_on; + int ch_close_supported; + int state; + int (*lpm_callback)(void *, int); + + int print_after_interrupt; + + u8 *rx_flush_buf; +}; + +/* + * Host operation: + * lower 16bits are operation code + * upper 16bits are operation state + */ +#define PEER_OPERATION(op_code , op_state) ((op_code) | ((op_state) << 16)) +#define GET_PEER_OPERATION_CODE(op) ((op) & 0xffff) +#define GET_PEER_OPERATION_STATE(op) ((op) >> 16) + +enum peer_op_code { + PEER_OP_CODE_CLOSE = 1 +}; + +enum peer_op_state { + PEER_OP_STATE_INIT = 0, + PEER_OP_STATE_START = 1 +}; + + +/* + * 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); + +/* + * Enables / disables open close debug messages + */ +static int debug_close_on = 1; +module_param(debug_close_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, struct sdio_channel *ch); +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); +static int sdio_read_internal(struct sdio_channel *ch, void *data, int len); +static int sdio_read_from_closed_ch(struct sdio_channel *ch, int len); +static void stop_and_del_timer(struct sdio_al_device *sdio_al_dev); + +#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, +}; + +struct debugfs_blob_wrapper sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES + 1]; + +/* +* +* 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) +{ + int i, blob_errs = 0; + + 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_close_on = debugfs_create_u8( + "debug_close_on", + S_IRUGO | S_IWUGO, + sdio_al->debug.sdio_al_debug_root, + &sdio_al->debug.debug_close_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); + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { + char temp[18]; + + scnprintf(temp, 18, "sdio_al_log_dev_%d", i + 1); + sdio_al->debug.sdio_al_debug_log_buffers[i] = + debugfs_create_blob(temp, + S_IRUGO | S_IWUGO, + sdio_al->debug.sdio_al_debug_root, + &sdio_al_dbgfs_log[i]); + } + + sdio_al->debug.sdio_al_debug_log_buffers[MAX_NUM_OF_SDIO_DEVICES] = + debugfs_create_blob("sdio_al_gen_log", + S_IRUGO | S_IWUGO, + sdio_al->debug.sdio_al_debug_root, + &sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES]); + + for (i = 0; i < (MAX_NUM_OF_SDIO_DEVICES + 1); ++i) { + if (!sdio_al->debug.sdio_al_debug_log_buffers[i]) { + pr_err(MODULE_NAME ": Failed to create debugfs buffer" + " entry for " + "sdio_al->debug.sdio_al_debug_log_buffers[%d]", + i); + blob_errs = 1; + } + } + + if (blob_errs) { + for (i = 0; i < (MAX_NUM_OF_SDIO_DEVICES + 1); ++i) + if (sdio_al->debug.sdio_al_debug_log_buffers[i]) + debugfs_remove( + sdio_al-> + debug.sdio_al_debug_log_buffers[i]); + } + + + if ((!sdio_al->debug.sdio_al_debug_data_on) && + (!sdio_al->debug.sdio_al_debug_lpm_on) && + (!sdio_al->debug.sdio_al_debug_close_on) && + (!sdio_al->debug.sdio_al_debug_info) && + blob_errs) { + debugfs_remove(sdio_al->debug.sdio_al_debug_root); + sdio_al->debug.sdio_al_debug_root = NULL; + return -ENOENT; + } + + sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES].data = + sdio_al->gen_log.buffer; + sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES].size = + SDIO_AL_DEBUG_LOG_SIZE; + + return 0; +} + +static void sdio_al_debugfs_cleanup(void) +{ + int i; + + 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_close_on); + debugfs_remove(sdio_al->debug.sdio_al_debug_info); + + for (i = 0; i < (MAX_NUM_OF_SDIO_DEVICES + 1); ++i) + debugfs_remove(sdio_al->debug.sdio_al_debug_log_buffers[i]); + + debugfs_remove(sdio_al->debug.sdio_al_debug_root); +} +#endif + +static int sdio_al_log(struct sdio_al_local_log *log, const char *fmt, ...) +{ + va_list args; + int r; + char *tp, *log_buf; + unsigned int *log_cur_pos; + struct timeval kt; + unsigned long flags; + static char sdio_al_log_tmp[SDIO_AL_DEBUG_TMP_LOG_SIZE]; + + spin_lock_irqsave(&log->log_lock, flags); + + kt = ktime_to_timeval(ktime_get()); + r = scnprintf(sdio_al_log_tmp, SDIO_AL_DEBUG_TMP_LOG_SIZE, + "[%8ld.%6ld] ", kt.tv_sec, kt.tv_usec); + + va_start(args, fmt); + r += vscnprintf(&sdio_al_log_tmp[r], (SDIO_AL_DEBUG_TMP_LOG_SIZE - r), + fmt, args); + va_end(args); + + log_buf = log->buffer; + log_cur_pos = &(log->buf_cur_pos); + + for (tp = sdio_al_log_tmp; tp < (sdio_al_log_tmp + r); tp++) { + log_buf[(*log_cur_pos)++] = *tp; + if ((*log_cur_pos) == SDIO_AL_DEBUG_LOG_SIZE) + *log_cur_pos = 0; + } + + spin_unlock_irqrestore(&log->log_lock, flags); + + return r; +} + +static int sdio_al_verify_func1(struct sdio_al_device *sdio_al_dev, + char const *func) +{ + if (sdio_al_dev == NULL) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "sdio_al_dev\n", func); + return -ENODEV; + } + + if (sdio_al_dev->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " + "signature\n", func); + return -ENODEV; + } + + if (!sdio_al_dev->card) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " + "card\n", func); + return -ENODEV; + } + if (!sdio_al_dev->card->sdio_func[0]) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " + "func1\n", func); + return -ENODEV; + } + return 0; +} + +static int sdio_al_claim_mutex(struct sdio_al_device *sdio_al_dev, + char const *func) +{ + if (!sdio_al_dev) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "device\n", func); + return -ENODEV; + } + + if (sdio_al_dev->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " + "device signature\n", func); + return -ENODEV; + } + + if (!sdio_al_dev->host) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " + "host\n", func); + return -ENODEV; + } + + mmc_claim_host(sdio_al_dev->host); + + return 0; +} + +static int sdio_al_release_mutex(struct sdio_al_device *sdio_al_dev, + char const *func) +{ + if (!sdio_al_dev) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "device\n", func); + return -ENODEV; + } + + if (sdio_al_dev->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " + "device signature\n", func); + return -ENODEV; + } + + if (!sdio_al_dev->host) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " + "host\n", func); + return -ENODEV; + } + + mmc_release_host(sdio_al_dev->host); + + return 0; +} + +static int sdio_al_claim_mutex_and_verify_dev( + struct sdio_al_device *sdio_al_dev, + char const *func) +{ + if (sdio_al_claim_mutex(sdio_al_dev, __func__)) + return -ENODEV; + + if (sdio_al_dev->state != CARD_INSERTED) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " + "device state %d\n", func, sdio_al_dev->state); + sdio_al_release_mutex(sdio_al_dev, __func__); + 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) { + sdio_al_loge(&sdio_al->gen_log, 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(sdio_al_dev->dev_log, MODULE_NAME ": %s - device %d " + "registered for wakeup callback\n", __func__, + sdio_al_dev->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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - " + "device_handle is NULL\n", __func__); + return; + } + + sdio_al_dev->lpm_callback = NULL; + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": %s - device %d " + "unregister for wakeup callback\n", __func__, + sdio_al_dev->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 (!sdio_al_dev) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - sdio_al_dev" + " is NULL\n", __func__); + return; + } + + if (is_vote_for_sleep) { + pr_debug(MODULE_NAME ": %s - sdio vote for Sleep", __func__); + wake_unlock(&sdio_al_dev->wake_lock); + } else { + pr_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(sdio_al_dev->dev_log, MODULE_NAME ": %s - " + "is_vote_for_sleep=%d for card#%d, " + "calling callback...", __func__, + is_vote_for_sleep, + sdio_al_dev->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 = NULL; + 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Invalid " + "lpm_chan for card %d\n", + sdio_al_dev->host->index); + return -EINVAL; + } + + if (!sdio_al_dev->card || + !sdio_al_dev->card->sdio_func[sdio_al_dev->lpm_chan+1]) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": NULL card or lpm_func\n"); + return -ENODEV; + } + lpm_func = sdio_al_dev->card->sdio_func[sdio_al_dev->lpm_chan+1]; + + 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->host->index); + + ret = sdio_memcpy_toio(lpm_func, SDIOC_SW_MAILBOX_ADDR+offset, + &sdio_al_dev->is_ok_to_sleep, sizeof(u32)); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":failed to " + "write lpm info for card %d\n", + sdio_al_dev->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_func1(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) { + sdio_al_logi(sdio_al_dev->dev_log, 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 */ + pr_debug(MODULE_NAME ":Inactivity timer expired." + " Going to sleep\n"); + /* Stop mailbox timer */ + stop_and_del_timer(sdio_al_dev); + /* 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->state != SDIO_CHANNEL_STATE_OPEN) && + (ch->state != SDIO_CHANNEL_STATE_CLOSED)) { + pr_debug(MODULE_NAME ":continue for channel %s in" + " state %d\n", ch->name, ch->state); + continue; + } + if (ch->is_packet_mode == false) { + ch->read_threshold = LOW_LATENCY_THRESHOLD; + set_pipe_threshold(sdio_al_dev, + ch->rx_pipe_index, + ch->read_threshold); + } + } + /* Prevent modem to go to sleep until we get the PROG_DONE on + the dummy CMD52 */ + msmsdcc_set_pwrsave(sdio_al_dev->host, 0); + /* Mark HOST_OK_TOSLEEP */ + sdio_al_dev->is_ok_to_sleep = 1; + write_lpm_info(sdio_al_dev); + + msmsdcc_lpm_enable(host); + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":Finished sleep sequence" + " for card %d. Sleep now.\n", + sdio_al_dev->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_al_claim_mutex 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 = NULL; + struct sdio_mailbox *mailbox = sdio_al_dev->mailbox; + struct mmc_host *host = sdio_al_dev->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; + int is_closing = 0; + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + return 0; + } + + if (sdio_al_verify_func1(sdio_al_dev, __func__)) + return -ENODEV; + func1 = sdio_al_dev->card->sdio_func[0]; + + pr_debug(MODULE_NAME ":start %s from_isr = %d for card %d.\n" + , __func__, from_isr, sdio_al_dev->host->index); + + pr_debug(MODULE_NAME ":before sdio_memcpy_fromio.\n"); + memset(mailbox, 0, sizeof(struct sdio_mailbox)); + ret = sdio_memcpy_fromio(func1, mailbox, + HW_MAILBOX_ADDR, sizeof(*mailbox)); + pr_debug(MODULE_NAME ":after sdio_memcpy_fromio.\n"); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to read " + "Mailbox for card %d, goto error state\n", + sdio_al_dev->host->index); + sdio_al_get_into_err_state(sdio_al_dev); + goto exit_err; + } + + 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 (overflow_pipe || underflow_pipe) + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_al_device" + " (card %d) is in invalid state %d\n", + sdio_al_dev->host->index, + sdio_al_dev->state); + return -ENODEV; + } + + pr_debug(MODULE_NAME ":card %d: eot=0x%x, thresh=0x%x\n", + sdio_al_dev->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->state == SDIO_CHANNEL_STATE_CLOSING) + is_closing = true; /* used to prevent sleep */ + + old_read_avail = ch->read_avail; + read_avail = mailbox->pipe_bytes_avail[ch->rx_pipe_index]; + + if ((ch->state == SDIO_CHANNEL_STATE_CLOSED) && + (read_avail > 0)) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ":%s: Invalid read_avail 0x%x, for CLOSED ch %s\n", + __func__, read_avail, ch->name); + sdio_read_from_closed_ch(ch, read_avail); + } + if ((ch->state != SDIO_CHANNEL_STATE_OPEN) && + (ch->state != SDIO_CHANNEL_STATE_CLOSING)) + continue; + + if (read_avail > INVALID_DATA_AVAILABLE) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + if ((eot_pipe & (1<rx_pipe_index)) && + sdio_al_dev->print_after_interrupt) { + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME + ":Interrupt on ch %s, " + "card %d", ch->name, + sdio_al_dev->host->index); + } + new_packet_size = check_pending_rx_packet(ch, eot_pipe); + } else { + if ((thresh_pipe & (1<rx_pipe_index)) && + sdio_al_dev->print_after_interrupt) { + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME + ":Interrupt on ch %s, " + "card %d", ch->name, + sdio_al_dev->host->index); + } + ch->read_avail = read_avail; + + /* + * Restore default thresh for non packet channels. + * in case it IS low latency channel then read_threshold + * and def_read_threshold are both + * LOW_LATENCY_THRESHOLD + */ + if ((ch->read_threshold != ch->def_read_threshold) && + (read_avail >= ch->threshold_change_cnt)) { + if (!ch->is_low_latency_ch) { + ch->read_threshold = + ch->def_read_threshold; + set_pipe_threshold(sdio_al_dev, + ch->rx_pipe_index, + ch->read_threshold); + } + } + } + + 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++; + } + } + sdio_al_dev->print_after_interrupt = 0; + + /* Update Write available */ + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + + if ((ch->state != SDIO_CHANNEL_STATE_OPEN) && + (ch->state != SDIO_CHANNEL_STATE_CLOSING)) + continue; + + new_write_avail = mailbox->pipe_bytes_avail[ch->tx_pipe_index]; + + if (new_write_avail > INVALID_DATA_AVAILABLE) { + sdio_al_loge(sdio_al_dev->dev_log, 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 (strncmp(ch->name, "SDIO_SMEM", CHANNEL_NAME_SIZE)) + any_write_pending |= + (new_write_avail < ch->ch_config.max_tx_threshold); + } + /* notify clients */ + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + + if ((ch->state != SDIO_CHANNEL_STATE_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(sdio_al_dev->dev_log, MODULE_NAME ":Nothing to " + "Notify for card %d, is_closing=%d\n", + sdio_al_dev->host->index, is_closing); + if (is_closing) + restart_inactive_time(sdio_al_dev); + else if (is_inactive_time_expired(sdio_al_dev)) + sdio_al_sleep(sdio_al_dev, host); + } else { + DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":Notify bitmask" + " for card %d rx=0x%x, tx=0x%x.\n", + sdio_al_dev->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) { + sdio_al_loge(&sdio_al->gen_log, 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->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)) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": failed to allocate item for " + "rx_pending list. rx_avail=%d, " + "rx_pending=%d.\n", + rx_avail, rx_pending); + new_packet_size = 0; + 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); + } else { + sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: ch " + "%s: unexpected empty list!!\n", + __func__, ch->name); + } + + 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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "sdio_al_work\n", __func__); + return; + } + + sdio_al_dev = sdio_al_work->sdio_al_dev; + if (sdio_al_dev == NULL) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "sdio_al_dev\n", __func__); + return; + } + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":Bootloader Worker Started" + ", wait for bootloader_done event..\n"); + wait_event(sdio_al_dev->wait_mbox, + sdio_al_dev->bootloader_done); + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":Got bootloader_done " + "event..\n"); + /* Do polling until MDM is up */ + for (i = 0; i < 5000; ++i) { + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return; + if (is_user_irq_enabled(sdio_al_dev, func_num)) { + sdio_al_release_mutex(sdio_al_dev, __func__); + sdio_al_dev->bootloader_done = 0; + ret = sdio_al_client_setup(sdio_al_dev); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": sdio_al_client_setup failed, " + "for card %d ret=%d\n", + sdio_al_dev->host->index, ret); + sdio_al_get_into_err_state(sdio_al_dev); + } + goto done; + } + sdio_al_release_mutex(sdio_al_dev, __func__); + msleep(100); + } + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Timeout waiting for " + "user_irq for card %d\n", + sdio_al_dev->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->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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": worker: NULL " + "sdio_al_work\n"); + return; + } + + sdio_al_dev = sdio_al_work->sdio_al_dev; + if (sdio_al_dev == NULL) { + sdio_al_loge(&sdio_al->gen_log, 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_dev->is_ready) + break; + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + break; + if (sdio_al_dev->is_ok_to_sleep) { + ret = sdio_al_wake_up(sdio_al_dev, 1, NULL); + if (ret) { + sdio_al_release_mutex(sdio_al_dev, __func__); + return; + } + } + ret = read_mailbox(sdio_al_dev, false); + sdio_al_release_mutex(sdio_al_dev, __func__); + 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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME + ":%s: R5_ERROR for card %d", + __func__, card->host->index); + return -EIO; + } + if (cmd.resp[0] & R5_FUNCTION_NUMBER) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME + ":%s: R5_FUNCTION_NUMBER for card %d", + __func__, card->host->index); + return -EINVAL; + } + if (cmd.resp[0] & R5_OUT_OF_RANGE) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME + ":%s: R5_OUT_OF_RANGE for card %d", + __func__, card->host->index); + 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 (!ch) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "channel\n", __func__); + return -ENODEV; + } + + if (!ch->sdio_al_dev) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "sdio_al_dev\n", __func__); + return -ENODEV; + } + + if (len == 0) { + sdio_al_loge(ch->sdio_al_dev->dev_log, 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) { + sdio_al_loge(ch->sdio_al_dev->dev_log, + MODULE_NAME ":%s: sdio_memcpy_toio " + "failed for channel %s\n", + __func__, ch->name); + sdio_al_get_into_err_state(ch->sdio_al_dev); + 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) { + sdio_al_loge(ch->sdio_al_dev->dev_log, 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; + + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + /* + * 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": Enable_mask_irq for card %d failed, " + "ret=%d\n", + sdio_al_dev->host->index, ret); + sdio_al_release_mutex(sdio_al_dev, __func__); + return ret; + } + + sdio_al_release_mutex(sdio_al_dev, __func__); + + /* + * 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_al_device *bootloader_dev = sdio_al->bootloader_dev; + struct sdio_func *func1 = NULL; + + if (sdio_al_claim_mutex_and_verify_dev(bootloader_dev, __func__)) + return -ENODEV; + + if (bootloader_dev->flashless_boot_on) { + sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":Already " + "in boot process.\n"); + sdio_al_release_mutex(bootloader_dev, __func__); + return 0; + } + + bootloader_dev->sdioc_boot_sw_header + = kzalloc(sizeof(*bootloader_dev->sdioc_boot_sw_header), + GFP_KERNEL); + if (bootloader_dev->sdioc_boot_sw_header == NULL) { + sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":fail to " + "allocate sdioc boot sw header.\n"); + sdio_al_release_mutex(bootloader_dev, __func__); + return -ENOMEM; + } + + if (sdio_al_verify_func1(bootloader_dev, __func__)) { + sdio_al_release_mutex(bootloader_dev, __func__); + goto exit_err; + } + func1 = bootloader_dev->card->sdio_func[0]; + + ret = sdio_memcpy_fromio(func1, + bootloader_dev->sdioc_boot_sw_header, + SDIOC_SW_HEADER_ADDR, + sizeof(struct peer_sdioc_boot_sw_header)); + if (ret) { + sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":fail to " + "read sdioc boot sw header.\n"); + sdio_al_release_mutex(bootloader_dev, __func__); + goto exit_err; + } + + if (bootloader_dev->sdioc_boot_sw_header->signature != + (u32) PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE) { + sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":invalid " + "mailbox signature 0x%x.\n", + bootloader_dev->sdioc_boot_sw_header->signature); + sdio_al_release_mutex(bootloader_dev, __func__); + 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)) { + sdio_al_loge(bootloader_dev->dev_log, 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_al_release_mutex(bootloader_dev, __func__); + ret = -EIO; + goto exit_err; + } + + sdio_al_logi(bootloader_dev->dev_log, MODULE_NAME ": SDIOC BOOT SW " + "version 0x%x\n", + bootloader_dev->sdioc_boot_sw_header->version); + + bootloader_dev->flashless_boot_on = true; + + sdio_al_release_mutex(bootloader_dev, __func__); + + ret = sdio_al_wait_for_bootloader_comp(bootloader_dev); + if (ret) { + sdio_al_loge(bootloader_dev->dev_log, 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) { + sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME + ": sdio_downloader_setup failed, err=%d\n", ret); + goto exit_err; + } + + sdio_al_logi(bootloader_dev->dev_log, MODULE_NAME ":In Flashless boot," + " waiting for its completion\n"); + + +exit_err: + sdio_al_logi(&sdio_al->gen_log, 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; + struct sdio_func *func1 = NULL; + + pr_debug(MODULE_NAME ":reading sdioc sw header.\n"); + + if (sdio_al_verify_func1(sdio_al_dev, __func__)) + return -ENODEV; + + func1 = sdio_al_dev->card->sdio_func[0]; + + ret = sdio_memcpy_fromio(func1, header, + SDIOC_SW_HEADER_ADDR, sizeof(*header)); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":fail to read " + "sdioc sw header.\n"); + goto exit_err; + } + + if (header->signature == (u32)PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE) { + sdio_al_logi(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": HOST(0x%x) and CLIENT(0x%x) " + "testing VERSION don't match\n", + test_version, + sdioc_test_version); + msleep(500); + BUG(); + } + } + + if ((header->signature != (u32) PEER_SDIOC_SW_MAILBOX_SIGNATURE) && + (header->signature != (u32) PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE)) { + sdio_al_loge(sdio_al_dev->dev_log, 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)) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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; + } + } + sdio_al_dev->ch_close_supported = (header->version & 0x000F) >= + (sdio_al->pdata->peer_sdioc_version_minor & 0xF); + + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":SDIOC SW version 0x%x," + " sdio_al major 0x%x minor 0x%x\n", header->version, + sdio_al->sdioc_major, + sdio_al->pdata->peer_sdioc_version_minor); + + 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; + ch->func = NULL; + ch->rx_pipe_index = ch->num*2; + ch->tx_pipe_index = ch->num*2+1; + + 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->state = SDIO_CHANNEL_STATE_IDLE; + ch->sdio_al_dev = sdio_al_dev; + if (sdio_al_dev->card->sdio_func[ch->num+1]) { + ch->func = + sdio_al_dev->card->sdio_func[ch->num+1]; + } else { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": NULL func for channel %s\n", + ch->name); + goto exit_err; + } + } else { + ch->state = SDIO_CHANNEL_STATE_INVALID; + } + + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":Channel=%s, " + "state=%d\n", ch->name, ch->state); + } + + 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) { + sdio_al_loge(&sdio_al->gen_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sw mailbox " + "channel not ready.\n"); + goto exit_err; + } + + ch->read_threshold = LOW_LATENCY_THRESHOLD; + ch->is_low_latency_ch = ch_config->is_low_latency_ch; + /* Threshold on 50% of the maximum size , sdioc uses double-buffer */ + ch->write_threshold = (ch_config->max_tx_threshold * 5) / 10; + ch->threshold_change_cnt = ch->ch_config.max_rx_threshold - + ch->read_threshold + THRESHOLD_CHANGE_EXTRA_BYTES; + + if (ch->is_low_latency_ch) + ch->def_read_threshold = LOW_LATENCY_THRESHOLD; + else /* Aggregation up to 90% of the maximum size */ + ch->def_read_threshold = (ch_config->max_rx_threshold * 9) / 10; + + 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; + } + /* 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; + + CLOSE_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":ch %s " + "read_threshold=%d, write_threshold=%d," + " min_write_avail=%d, max_rx_threshold=%d," + " max_tx_threshold=%d\n", ch->name, ch->read_threshold, + ch->write_threshold, ch->min_write_avail, + ch_config->max_rx_threshold, + ch_config->max_tx_threshold); + + ch->peer_tx_buf_size = ch_config->tx_buf_size; + + kfree(sw_mailbox); + + return 0; + +exit_err: + sdio_al_logi(sdio_al_dev->dev_log, 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_func1(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<dev_log, MODULE_NAME ": " + "enable_mask_irq fail\n"); + goto exit_err; + } + + if (enable) + mask &= (~func_mask); /* 0 = enable */ + else + mask |= (func_mask); /* 1 = disable */ + + pr_debug(MODULE_NAME ":enable_mask_irq, writing mask = 0x%x\n", mask); + + sdio_writel(func1, mask, addr, &ret); + +exit_err: + return ret; +} + +/** + * Enable/Disable Threshold interrupt of a pipe. + * + */ +static int enable_threshold_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_func1(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]; + + sdio_writel(func1, threshold, + PIPES_THRESHOLD_ADDR+pipe_index*4, &ret); + if (ret) + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(&sdio_al->gen_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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->state = SDIO_CHANNEL_STATE_OPEN; + pr_debug(MODULE_NAME ":channel %s is in OPEN state now\n", ch->name); + + 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->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); + } +} + +/** + * Stop and delete the timer + */ +static void stop_and_del_timer(struct sdio_al_device *sdio_al_dev) +{ + if (sdio_al_dev->is_timer_initialized) { + sdio_al_dev->poll_delay_msec = 0; + del_timer_sync(&sdio_al_dev->timer); + } +} + +/** + * 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, struct sdio_channel *ch) +{ + int ret = 0; + struct sdio_func *wk_func = NULL; + unsigned long time_to_wait; + struct mmc_host *host = sdio_al_dev->host; + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + return -ENODEV; + } + + if (!sdio_al_dev->is_ok_to_sleep) { + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":card %d " + "already awake, no need to wake up\n", + sdio_al_dev->host->index); + return 0; + } + + /* Wake up sequence */ + if (not_from_int) { + if (ch) { + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": Wake up" + " card %d (not by interrupt), ch %s", + sdio_al_dev->host->index, + ch->name); + } else { + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": Wake up" + " card %d (not by interrupt)", + sdio_al_dev->host->index); + } + } else { + LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": Wake up card " + "%d by interrupt", + sdio_al_dev->host->index); + sdio_al_dev->print_after_interrupt = 1; + } + + sdio_al_vote_for_sleep(sdio_al_dev, 0); + + msmsdcc_lpm_disable(host); + msmsdcc_set_pwrsave(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); + } + + pr_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(sdio_al_dev->dev_log, MODULE_NAME ": " + "get_mdm2ap_status() is 0\n"); + + /* Enable Wake up Function */ + if (!sdio_al_dev->card || + !sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": NULL card or wk_func\n"); + return -ENODEV; + } + wk_func = sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]; + ret = sdio_al_enable_func_retry(wk_func, "wakeup func"); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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; + } + 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(sdio_al_dev->dev_log, MODULE_NAME "Finished Wake up sequence" + " for card %d", sdio_al_dev->host->index); + + msmsdcc_set_pwrsave(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(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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": NULL device"); + return; + } + + if (sdio_al_dev->is_ok_to_sleep) + sdio_al_wake_up(sdio_al_dev, 0, NULL); + 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": NULL " + "sdio_al_dev for data %lu\n", data); + return; + } + if (sdio_al_dev->state != CARD_INSERTED) { + sdio_al_loge(sdio_al_dev->dev_log, 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 (sdio_al_verify_func1(sdio_al_dev, __func__)) + return -ENODEV; + func1 = card->sdio_func[0]; + + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":sdio_al_setup for " + "card %d\n", sdio_al_dev->host->index); + + ret = sdio_al->pdata->config_mdm2ap_status(1); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, 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); + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":claim IRQ for card " + "%d\n", sdio_al_dev->host->index); + + ret = sdio_claim_irq(func1, sdio_func_irq); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to claim" + " IRQ for card %d\n", + sdio_al_dev->host->index); + return ret; + } + + 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; +} + +/** + * Driver Tear-Down. + * + */ +static void sdio_al_tear_down(void) +{ + int i, j; + 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_claim_mutex_and_verify_dev(sdio_al_dev, + __func__)) { + if (!sdio_al_dev->card || + !sdio_al_dev->card->sdio_func[0]) { + sdio_al_loge(sdio_al_dev->dev_log, + MODULE_NAME + ": %s: Invalid func1", + __func__); + return; + } + func1 = sdio_al_dev->card->sdio_func[0]; + sdio_release_irq(func1); + sdio_disable_func(func1); + sdio_al_release_mutex(sdio_al_dev, __func__); + } + } + + for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) + sdio_al_dev->channel[j].signature = 0x0; + sdio_al_dev->signature = 0; + + kfree(sdio_al_dev->sdioc_sw_header); + kfree(sdio_al_dev->mailbox); + kfree(sdio_al_dev->rx_flush_buf); + kfree(sdio_al_dev); + } + + 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].state == + SDIO_CHANNEL_STATE_INVALID) + continue; + if (strncmp(sdio_al_dev->channel[i].name, name, + CHANNEL_NAME_SIZE) == 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].state == + SDIO_CHANNEL_STATE_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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":Can't find " + "channel name %s\n", name); + return -EINVAL; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + if ((ch->state != SDIO_CHANNEL_STATE_IDLE) && + (ch->state != SDIO_CHANNEL_STATE_CLOSED)) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Wrong ch %s " + "state %d\n", name, ch->state); + ret = -EPERM; + 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, ch); + 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_open %s " + "err=%d\n", name, -ret); + goto exit_err; + } + + CLOSE_DEBUG(sdio_al_dev->dev_log, 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) { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME + ":setting channel %s as " + "lpm_chan\n", name); + sdio_al_dev->lpm_chan = ch->num; + } + } else { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ": " + "setting channel %s as lpm_chan\n", + name); + sdio_al_dev->lpm_chan = ch->num; + } + } + +exit_err: + sdio_al_release_mutex(sdio_al_dev, __func__); + return ret; +} +EXPORT_SYMBOL(sdio_open); + +/** + * Request peer operation + * note: sanity checks of parameters done by caller + * called under bus locked + */ +static int peer_set_operation(u32 opcode, + struct sdio_al_device *sdio_al_dev, + struct sdio_channel *ch) +{ + int ret; + int offset; + struct sdio_func *wk_func = NULL; + u32 peer_operation; + int loop_count = 0; + + if (!sdio_al_dev->card || + !sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": NULL card or wk_func\n"); + ret = -ENODEV; + goto exit; + } + wk_func = sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]; + + /* calculate offset of peer_operation field in sw mailbox struct */ + offset = offsetof(struct peer_sdioc_sw_mailbox, ch_config) + + sizeof(struct peer_sdioc_channel_config) * ch->num + + offsetof(struct peer_sdioc_channel_config, peer_operation); + + ret = sdio_al_wake_up(sdio_al_dev, 1, ch); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to " + "wake up\n"); + goto exit; + } + /* request operation from MDM peer */ + peer_operation = PEER_OPERATION(opcode, PEER_OP_STATE_INIT); + ret = sdio_memcpy_toio(ch->func, SDIOC_SW_MAILBOX_ADDR+offset, + &peer_operation, sizeof(u32)); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":failed to " + "request close operation\n"); + goto exit; + } + ret = sdio_al_enable_func_retry(wk_func, "wk_func"); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to enable" + " Func#%d\n", wk_func->num); + goto exit; + } + pr_debug(MODULE_NAME ":%s: wk_func enabled on ch %s\n", + __func__, ch->name); + /* send "start" operation to MDM */ + peer_operation = PEER_OPERATION(opcode, PEER_OP_STATE_START); + ret = sdio_memcpy_toio(ch->func, SDIOC_SW_MAILBOX_ADDR+offset, + &peer_operation, sizeof(u32)); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":failed to " + "send start close operation\n"); + goto exit; + } + ret = sdio_disable_func(wk_func); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to " + "disable Func#%d\n", wk_func->num); + goto exit; + } + /* poll for peer operation ack */ + while (peer_operation != 0) { + ret = sdio_memcpy_fromio(ch->func, + &peer_operation, + SDIOC_SW_MAILBOX_ADDR+offset, + sizeof(u32)); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ":failed to request ack on close" + " operation, loop_count = %d\n", + loop_count); + goto exit; + } + loop_count++; + if (loop_count > 10) { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":%s: " + "peer_operation=0x%x wait loop" + " %d on ch %s\n", __func__, + peer_operation, loop_count, ch->name); + } + } +exit: + return ret; +} + +static int channel_close(struct sdio_channel *ch, int flush_flag) +{ + int ret; + struct sdio_al_device *sdio_al_dev = NULL; + int flush_len; + ulong flush_expires; + + if (!ch) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " + "channel\n", __func__); + return -ENODEV; + } + + if (!ch->func) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: NULL func" + " on channel:%d\n", __func__, ch->num); + return -ENODEV; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + if (!sdio_al_dev->ch_close_supported) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: Not " + "supported by mdm, ch %s\n", + __func__, ch->name); + ret = -ENOTSUPP; + goto error_exit; + } + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + ret = -ENODEV; + goto error_exit; + } + if (ch->state != SDIO_CHANNEL_STATE_OPEN) { + sdio_al_loge(sdio_al_dev->dev_log, + MODULE_NAME ":%s: ch %s is not in " + "open state (%d)\n", + __func__, ch->name, ch->state); + ret = -ENODEV; + goto error_exit; + } + ch->state = SDIO_CHANNEL_STATE_CLOSING; + ret = peer_set_operation(PEER_OP_CODE_CLOSE, sdio_al_dev, ch); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: " + "peer_set_operation() failed: %d\n", + __func__, ret); + ret = -ENODEV; + goto error_exit; + } + /* udate poll time for opened channels */ + if (ch->poll_delay_msec > 0) { + sdio_al_dev->poll_delay_msec = + get_min_poll_time_msec(sdio_al_dev); + } + sdio_al_release_mutex(ch->sdio_al_dev, __func__); + + flush_expires = jiffies + + msecs_to_jiffies(SDIO_CLOSE_FLUSH_TIMEOUT_MSEC); + /* flush rx packets of the channel */ + if (flush_flag) { + do { + while (ch->read_avail > 0) { + flush_len = ch->read_avail; + ret = sdio_read_internal(ch, + sdio_al_dev->rx_flush_buf, + flush_len); + if (ret) { + sdio_al_loge(&sdio_al->gen_log, + MODULE_NAME ":%s sdio_read" + " failed: %d, ch %s\n", + __func__, ret, + ch->name); + return ret; + } + + if (time_after(jiffies, flush_expires) != 0) { + sdio_al_loge(&sdio_al->gen_log, + MODULE_NAME ":%s flush rx " + "packets timeout: ch %s\n", + __func__, ch->name); + sdio_al_get_into_err_state(sdio_al_dev); + return -EBUSY; + } + } + msleep(100); + if (ch->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(&sdio_al->gen_log, + MODULE_NAME ":%s: after sleep," + " invalid signature" + " 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + if (sdio_al_claim_mutex_and_verify_dev(ch->sdio_al_dev, + __func__)) + return -ENODEV; + + ret = read_mailbox(sdio_al_dev, false); + if (ret) { + sdio_al_loge(&sdio_al->gen_log, + MODULE_NAME ":%s: failed to" + " read mailbox", __func__); + goto error_exit; + } + sdio_al_release_mutex(ch->sdio_al_dev, __func__); + } while (ch->read_avail > 0); + } + if (sdio_al_claim_mutex_and_verify_dev(ch->sdio_al_dev, + __func__)) + return -ENODEV; + /* disable function to be able to open the channel again */ + ret = sdio_disable_func(ch->func); + if (ret) { + sdio_al_loge(&sdio_al->gen_log, + MODULE_NAME ":Fail to disable Func#%d\n", + ch->func->num); + goto error_exit; + } + ch->state = SDIO_CHANNEL_STATE_CLOSED; + CLOSE_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":%s: Ch %s closed " + "successfully\n", __func__, ch->name); + +error_exit: + sdio_al_release_mutex(ch->sdio_al_dev, __func__); + + return ret; +} + +/** + * Close SDIO Channel. + * + */ +int sdio_close(struct sdio_channel *ch) +{ + return channel_close(ch, true); +} +EXPORT_SYMBOL(sdio_close); + +/** + * Get the number of available bytes to write. + * + */ +int sdio_write_avail(struct sdio_channel *ch) +{ + if (!ch) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " + "channel\n", __func__); + return -ENODEV; + } + if (ch->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: " + "Invalid signature 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + if (ch->state != SDIO_CHANNEL_STATE_OPEN) { + sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: " + "channel %s state is not open (%d)\n", + __func__, ch->name, ch->state); + 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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " + "channel\n", __func__); + return -ENODEV; + } + if (ch->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: " + "Invalid signature 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + if (ch->state != SDIO_CHANNEL_STATE_OPEN) { + sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: " + "channel %s state is not open (%d)\n", + __func__, ch->name, ch->state); + 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); + +static int sdio_read_from_closed_ch(struct sdio_channel *ch, int len) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = NULL; + + if (!ch) { + sdio_al_loge(ch->sdio_al_dev->dev_log, + MODULE_NAME ":%s: NULL channel\n", __func__); + return -ENODEV; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + ret = sdio_memcpy_fromio(ch->func, sdio_al_dev->rx_flush_buf, + PIPE_RX_FIFO_ADDR, len); + + if (ret) { + sdio_al_loge(ch->sdio_al_dev->dev_log, + MODULE_NAME ":ch %s: %s err=%d, len=%d\n", + ch->name, __func__, -ret, len); + sdio_al_dev->is_err = true; + sdio_al_release_mutex(sdio_al_dev, __func__); + return ret; + } + + restart_inactive_time(sdio_al_dev); + + sdio_al_release_mutex(sdio_al_dev, __func__); + + return 0; +} + +/** + * Internal read from SDIO Channel. + * + * Reading from the pipe will trigger interrupt if there are + * other pending packets on the SDIO-Client. + * + */ +static int sdio_read_internal(struct sdio_channel *ch, void *data, int len) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = NULL; + + if (!ch) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " + "channel\n", __func__); + return -ENODEV; + } + if (!data) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL data\n", + __func__); + return -ENODEV; + } + if (len == 0) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":channel %s trying" + " to read 0 bytes\n", ch->name); + return -EINVAL; + } + + if (ch->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: Invalid " + "signature 0x%x\n", __func__, ch->signature); + return -ENODEV; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + ret = -ENODEV; + goto exit; + } + + /* 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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->state != SDIO_CHANNEL_STATE_OPEN) && + (ch->state != SDIO_CHANNEL_STATE_CLOSING)) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s wrong " + "channel %s state %d\n", + __func__, ch->name, ch->state); + ret = -EINVAL; + goto exit; + } + + DATA_DEBUG(sdio_al_dev->dev_log, 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)) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_read ch " + "%s len != read_avail\n", ch->name); + ret = -EINVAL; + goto exit; + } + + if (len > ch->read_avail) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":ERR ch %s: " + "reading more bytes (%d) than the avail(%d).\n", + ch->name, len, ch->read_avail); + ret = -ENOMEM; + goto exit; + } + + ret = sdio_memcpy_fromio(ch->func, data, PIPE_RX_FIFO_ADDR, len); + + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":ch %s: " + "sdio_read err=%d, len=%d, read_avail=%d, " + "last_read_avail=%d, last_old_read_avail=%d\n", + ch->name, -ret, len, ch->read_avail, + ch->statistics.last_read_avail, + ch->statistics.last_old_read_avail); + sdio_al_get_into_err_state(sdio_al_dev); + goto exit; + } + + 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(sdio_al_dev->dev_log, 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); + +exit: + sdio_al_release_mutex(sdio_al_dev, __func__); + + return ret; +} + +/** + * 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) +{ + if (!ch) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " + "channel\n", __func__); + return -ENODEV; + } + if (ch->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: " + "Invalid signature 0x%x\n", __func__, ch->signature); + return -ENODEV; + } + if (ch->state == SDIO_CHANNEL_STATE_OPEN) { + return sdio_read_internal(ch, data, len); + } else { + sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME + ":%s: Invalid channel %s state %d\n", + __func__, ch->name, ch->state); + } + return -ENODEV; +} +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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " + "channel\n", __func__); + return -ENODEV; + } + if (!data) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL data\n", + __func__); + return -ENODEV; + } + if (len == 0) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":channel %s trying" + " to write 0 bytes\n", ch->name); + return -EINVAL; + } + + if (ch->signature != SDIO_AL_SIGNATURE) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: Invalid " + "signature 0x%x\n", __func__, ch->signature); + return -ENODEV; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + WARN_ON(len > ch->write_avail); + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + ret = -ENODEV; + goto exit; + } + + if (ch->state != SDIO_CHANNEL_STATE_OPEN) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":writing to " + "closed channel %s\n", ch->name); + ret = -EINVAL; + goto exit; + } + + if (sdio_al_dev->is_ok_to_sleep) { + ret = sdio_al_wake_up(sdio_al_dev, 1, ch); + if (ret) + goto exit; + } else { + restart_inactive_time(sdio_al_dev); + } + + DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":start ch %s write %d " + "avail %d.\n", ch->name, len, ch->write_avail); + + if (len > ch->write_avail) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":ERR ch %s: " + "write more bytes (%d) than available %d.\n", + ch->name, len, ch->write_avail); + ret = -ENOMEM; + goto exit; + } + + ret = sdio_ch_write(ch, data, len); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_write " + "on channel %s err=%d\n", ch->name, -ret); + goto exit; + } + + ch->total_tx_bytes += len; + DATA_DEBUG(sdio_al_dev->dev_log, 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); + +exit: + sdio_al_release_mutex(sdio_al_dev, __func__); + + 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 void sdio_al_close_all_channels(struct sdio_al_device *sdio_al_dev) +{ + int j; + int ret; + struct sdio_channel *ch = NULL; + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s", __func__); + + if (!sdio_al_dev) { + sdio_al_loge(sdio_al_dev->dev_log, + MODULE_NAME ": %s: NULL device", __func__); + return; + } + for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) { + ch = &sdio_al_dev->channel[j]; + + if (ch->state == SDIO_CHANNEL_STATE_OPEN) { + sdio_al_loge(sdio_al_dev->dev_log, + MODULE_NAME ": %s: Call to sdio_close() for" + " ch %s\n", __func__, ch->name); + ret = channel_close(ch, false); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, + MODULE_NAME ": %s: failed sdio_close()" + " for ch %s (%d)\n", + __func__, ch->name, ret); + } + } else { + pr_debug(MODULE_NAME ": %s: skip sdio_close() ch %s" + " (state=%d)\n", __func__, + ch->name, ch->state); + } + } +} + +static void sdio_al_invalidate_sdio_clients(struct sdio_al_device *sdio_al_dev, + struct platform_device **pdev_arr) +{ + int j; + + pr_debug(MODULE_NAME ": %s: Notifying SDIO clients for card %d", + __func__, sdio_al_dev->host->index); + for (j = 0; j < SDIO_AL_MAX_CHANNELS; ++j) { + if (sdio_al_dev->channel[j].state == + SDIO_CHANNEL_STATE_INVALID) + continue; + pdev_arr[j] = sdio_al_dev->channel[j].pdev; + sdio_al_dev->channel[j].signature = 0x0; + sdio_al_dev->channel[j].state = + SDIO_CHANNEL_STATE_INVALID; + } +} + +static void sdio_al_modem_reset_operations(struct sdio_al_device + *sdio_al_dev) +{ + int ret = 0; + struct platform_device *pdev_arr[SDIO_AL_MAX_CHANNELS]; + int j; + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s", __func__); + + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return; + + if (sdio_al_dev->state == CARD_REMOVED) { + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: " + "card %d is already removed", __func__, + sdio_al_dev->host->index); + goto exit_err; + } + + if (sdio_al_dev->state == MODEM_RESTART) { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ": %s: " + "card %d was already notified for modem reset", + __func__, sdio_al_dev->host->index); + goto exit_err; + } + + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ": %s: Set the " + "state to MODEM_RESTART for card %d", + __func__, sdio_al_dev->host->index); + sdio_al_dev->state = MODEM_RESTART; + sdio_al_dev->is_ready = false; + + /* Stop mailbox timer */ + stop_and_del_timer(sdio_al_dev); + + 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->host->index); + ret = sdio_al_wake_up(sdio_al_dev, 1, NULL); + } + + if (!ret && (!sdio_al_dev->is_err) && sdio_al_dev->card && + sdio_al_dev->card->sdio_func[0]) { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME + ": %s: sdio_release_irq for card %d", + __func__, + sdio_al_dev->host->index); + sdio_release_irq(sdio_al_dev->card->sdio_func[0]); + } + + memset(pdev_arr, 0, sizeof(pdev_arr)); + sdio_al_invalidate_sdio_clients(sdio_al_dev, pdev_arr); + + sdio_al_release_mutex(sdio_al_dev, __func__); + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Notifying SDIO " + "clients for card %d", + __func__, sdio_al_dev->host->index); + for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) { + if (!pdev_arr[j]) + continue; + platform_device_unregister(pdev_arr[j]); + } + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Finished Notifying " + "SDIO clients for card %d", + __func__, sdio_al_dev->host->index); + + return; + +exit_err: + sdio_al_release_mutex(sdio_al_dev, __func__); + return; +} + +#ifdef CONFIG_MSM_SUBSYSTEM_RESTART +static void sdio_al_reset(void) +{ + int i; + struct sdio_al_device *sdio_al_dev; + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s", __func__); + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; i++) { + 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]; + sdio_al_modem_reset_operations(sdio_al->devices[i]); + } + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s completed", __func__); +} +#endif + +static void msm_sdio_al_shutdown(struct platform_device *pdev) +{ + int i; + struct sdio_al_device *sdio_al_dev; + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME + "Initiating msm_sdio_al_shutdown..."); + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; i++) { + 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_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return; + + if (sdio_al_dev->ch_close_supported) + sdio_al_close_all_channels(sdio_al_dev); + + sdio_al_release_mutex(sdio_al_dev, __func__); + + sdio_al_modem_reset_operations(sdio_al_dev); + } + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: " + "msm_sdio_al_shutdown complete.", __func__); +} + +static struct platform_driver msm_sdio_al_driver = { + .probe = msm_sdio_al_probe, + .remove = __exit_p(msm_sdio_al_remove), + .shutdown = msm_sdio_al_shutdown, + .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_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + 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].state == SDIO_CHANNEL_STATE_INVALID) + continue; + if (sdio_al->unittest_mode) { + 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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ":NULL platform device for ch %s", + sdio_al_dev->channel[i].name); + sdio_al_dev->channel[i].state = + SDIO_CHANNEL_STATE_INVALID; + continue; + } + ret = platform_device_add(sdio_al_dev->channel[i].pdev); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ":platform_device_add failed, " + "ret=%d\n", ret); + sdio_al_dev->channel[i].state = + SDIO_CHANNEL_STATE_INVALID; + } + } + +exit: + sdio_al_release_mutex(sdio_al_dev, __func__); + 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_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + if (!sdio_al_dev->card || !sdio_al_dev->card->sdio_func[0]) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":NULL card or " + "func1\n"); + sdio_al_release_mutex(sdio_al_dev, __func__); + return -ENODEV; + } + func1 = sdio_al_dev->card->sdio_func[0]; + + /* Read the header signature to determine the status of the MDM + * SDIO Client + */ + signature = sdio_readl(func1, SDIOC_SW_HEADER_ADDR, &ret); + sdio_al_release_mutex(sdio_al_dev, __func__); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":setup " + "bootloader on card %d\n", + sdio_al_dev->host->index); + return sdio_al_bootloader_setup(); + } else { + sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":wait " + "for bootloader completion " + "on card %d\n", + sdio_al_dev->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: + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Invalid " + "signature 0x%x\n", signature); + return -EINVAL; + } + + return 0; +} + +static void clean_sdio_al_device_data(struct sdio_al_device *sdio_al_dev) +{ + sdio_al_dev->is_ready = 0; + sdio_al_dev->bootloader_done = 0; + sdio_al_dev->lpm_chan = 0; + sdio_al_dev->is_ok_to_sleep = 0; + sdio_al_dev->inactivity_time = 0; + sdio_al_dev->poll_delay_msec = 0; + sdio_al_dev->is_timer_initialized = 0; + sdio_al_dev->is_err = 0; + sdio_al_dev->is_suspended = 0; + sdio_al_dev->flashless_boot_on = 0; + sdio_al_dev->ch_close_supported = 0; + sdio_al_dev->print_after_interrupt = 0; + memset(sdio_al_dev->sdioc_sw_header, 0, + sizeof(*sdio_al_dev->sdioc_sw_header)); + memset(sdio_al_dev->mailbox, 0, sizeof(*sdio_al_dev->mailbox)); + memset(sdio_al_dev->rx_flush_buf, 0, + sizeof(*sdio_al_dev->rx_flush_buf)); +} + +/* + * 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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL func\n", + __func__); + return -ENODEV; + } + card = func->card; + + if (!card) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL card\n", + __func__); + return -ENODEV; + } + + if (!card->sdio_func[0]) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "func1\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]->host == card->host) { + sdio_al_dev = sdio_al->devices[i]; + if (sdio_al_dev->state == CARD_INSERTED) + return 0; + clean_sdio_al_device_data(sdio_al_dev); + break; + } + } + + if (!sdio_al_dev) { + sdio_al_dev = kzalloc(sizeof(struct sdio_al_device), + GFP_KERNEL); + if (sdio_al_dev == NULL) + return -ENOMEM; + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES ; ++i) + if (sdio_al->devices[i] == NULL) { + sdio_al->devices[i] = sdio_al_dev; + sdio_al_dev->dev_log = &sdio_al->device_log[i]; + spin_lock_init(&sdio_al_dev->dev_log->log_lock); + #ifdef CONFIG_DEBUG_FS + sdio_al_dbgfs_log[i].data = + sdio_al_dev->dev_log->buffer; + sdio_al_dbgfs_log[i].size = + SDIO_AL_DEBUG_LOG_SIZE; + #endif + break; + } + if (i == MAX_NUM_OF_SDIO_DEVICES) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":No space " + "in devices array for the device\n"); + return -ENOMEM; + } + } + + dev_info(&card->dev, "SDIO Card claimed.\n"); + sdio_al->skip_print_info = 0; + + sdio_al_dev->state = CARD_INSERTED; + + if (card->host->index == SDIO_BOOTLOADER_CARD_INDEX) + sdio_al->bootloader_dev = sdio_al_dev; + + 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->host = card->host; + + if (!sdio_al_dev->mailbox) { + sdio_al_dev->mailbox = kzalloc(sizeof(struct sdio_mailbox), + GFP_KERNEL); + if (sdio_al_dev->mailbox == NULL) + return -ENOMEM; + } + + if (!sdio_al_dev->sdioc_sw_header) { + 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; + } + + if (!sdio_al_dev->rx_flush_buf) { + sdio_al_dev->rx_flush_buf = kzalloc(RX_FLUSH_BUFFER_SIZE, + GFP_KERNEL); + if (sdio_al_dev->rx_flush_buf == NULL) { + sdio_al_loge(&sdio_al->gen_log, + MODULE_NAME ":Fail to allocate " + "rx_flush_buf for card %d\n", + card->host->index); + 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); + + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + /* Init Func#1 */ + ret = sdio_al_enable_func_retry(card->sdio_func[0], "Init Func#1"); + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, 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) { + sdio_al_loge(sdio_al_dev->dev_log, 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_al_release_mutex(sdio_al_dev, __func__); + return ret; +} + +static void sdio_al_sdio_remove(struct sdio_func *func) +{ + struct sdio_al_device *sdio_al_dev = NULL; + int i; + struct mmc_card *card = NULL; + struct platform_device *pdev_arr[SDIO_AL_MAX_CHANNELS]; + + if (!func) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL func\n", + __func__); + return; + } + card = func->card; + + if (!card) { + sdio_al_loge(&sdio_al->gen_log, 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]; + break; + } + } + if (sdio_al_dev == NULL) { + pr_debug(MODULE_NAME ":%s :NULL sdio_al_dev for card %d\n", + __func__, card->host->index); + return; + } + + if (sdio_al_claim_mutex(sdio_al_dev, __func__)) + return; + + if (sdio_al_dev->state == CARD_REMOVED) { + sdio_al_release_mutex(sdio_al_dev, __func__); + return; + } + + if (!card->sdio_func[0]) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " + "func1\n", __func__); + sdio_al_release_mutex(sdio_al_dev, __func__); + return; + } + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s for card %d\n", + __func__, card->host->index); + + sdio_al_dev->state = CARD_REMOVED; + + memset(pdev_arr, 0, sizeof(pdev_arr)); + sdio_al_invalidate_sdio_clients(sdio_al_dev, pdev_arr); + + sdio_al_logi(&sdio_al->gen_log, 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 */ + + stop_and_del_timer(sdio_al_dev); + + sdio_al_release_mutex(sdio_al_dev, __func__); + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Notifying SDIO " + "clients for card %d", + __func__, sdio_al_dev->host->index); + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + if (!pdev_arr[i]) + continue; + platform_device_unregister(pdev_arr[i]); + } + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Finished Notifying " + "SDIO clients for card %d", + __func__, sdio_al_dev->host->index); + + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s: vote for sleep for " + "card %d\n", __func__, card->host->index); + sdio_al_vote_for_sleep(sdio_al_dev, 1); + + sdio_al_logi(&sdio_al->gen_log, 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); + + sdio_al_logi(&sdio_al->gen_log, 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) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": mailbox is " + "NULL\n"); + return; + } + + sdio_al_loge(&sdio_al->gen_log, 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)); + } + + sdio_al_loge(&sdio_al->gen_log, 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; + char buf[50]; + + if (sdio_al->skip_print_info == 1) + return; + + sdio_al->skip_print_info = 1; + + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - SDIO DEBUG INFO\n", + __func__); + + if (!sdio_al) { + sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - ERROR - " + "sdio_al is NULL\n", __func__); + return; + } + + sdio_al_loge(&sdio_al->gen_log, 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->host) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": Host" + " is NULL\n);"); + continue; + } + + snprintf(buf, sizeof(buf), "Card#%d: Shadow HW MB", + sdio_al_dev->host->index); + + /* printing Shadowing HW Mailbox*/ + mailbox = sdio_al_dev->mailbox; + sdio_print_mailbox(buf, mailbox); + + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": Card#%d: " + "is_ok_to_sleep=%d\n", + sdio_al_dev->host->index, + sdio_al_dev->is_ok_to_sleep); + + + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": Card#%d: " + "Shadow channels SW MB:", + sdio_al_dev->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->state == SDIO_CHANNEL_STATE_INVALID) + continue; + + ch_config = &sdio_al_dev->channel[i].ch_config; + + sdio_al_loge(sdio_al_dev->dev_log, 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); + + sdio_al_loge(sdio_al_dev->dev_log, 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_func1(sdio_al_dev, __func__)) + continue; + + if (!sdio_al_dev->host) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": Host is NULL"); + continue; + } + + if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": %s - for Card#%d, is lpm_chan==" + "INVALID_SDIO_CHAN. continuing...", + __func__, sdio_al_dev->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) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": %s - lpm_func is NULL for card#%d" + " continuing...\n", __func__, + sdio_al_dev->host->index); + continue; + } + + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return; + ret = sdio_memcpy_fromio(lpm_func, + &is_ok_to_sleep, + SDIOC_SW_MAILBOX_ADDR+offset, + sizeof(int)); + sdio_al_release_mutex(sdio_al_dev, __func__); + + if (ret) + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": %s - fail to read " + "is_HOST_ok_to_sleep from mailbox for card %d", + __func__, sdio_al_dev->host->index); + else + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": Card#%d: " + "is_HOST_ok_to_sleep=%d\n", + sdio_al_dev->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_dev) + continue; + + /* Reading HW Mailbox */ + hw_mailbox = sdio_al_dev->mailbox; + + if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) + return; + + if (!sdio_al_dev->card || !sdio_al_dev->card->sdio_func[0]) { + sdio_al_release_mutex(sdio_al_dev, __func__); + return; + } + func1 = sdio_al_dev->card->sdio_func[0]; + ret = sdio_memcpy_fromio(func1, hw_mailbox, + HW_MAILBOX_ADDR, sizeof(*hw_mailbox)); + sdio_al_release_mutex(sdio_al_dev, __func__); + + if (ret) { + sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME + ": fail to read " + "mailbox for card#%d. " + "continuing...\n", + sdio_al_dev->host->index); + continue; + } + + snprintf(buf, sizeof(buf), "Card#%d: Current HW MB", + sdio_al_dev->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) +{ + if (notif_type != SUBSYS_BEFORE_SHUTDOWN) { + sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: got " + "notification %ld", __func__, notif_type); + return NOTIFY_DONE; + } + + sdio_al_reset(); + 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; + sdio_al->debug.debug_close_on = debug_close_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); + + spin_lock_init(&sdio_al->gen_log.log_lock); + +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 0000000000000000000000000000000000000000..f48c32b8f4bd24d1fa5aef690b31557ec62e34db --- /dev/null +++ b/arch/arm/mach-msm/sdio_al_dloader.c @@ -0,0 +1,2541 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "sdio_al_private.h" +#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 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; + } + + /* 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; + + status = sdio_dld_create_thread(); + if (status) { + del_timer_sync(&sdio_dld->timer); + del_timer_sync(&sdio_dld->push_timer); + sdio_dld_dealloc_local_buffers(); + pr_err(MODULE_NAME ": %s, failed in sdio_dld_create_thread()." + "status=%d\n", __func__, status); + return status; + } + 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 0000000000000000000000000000000000000000..36d9ec13cd3af9c6537439b0c9ea953f62e97140 --- /dev/null +++ b/arch/arm/mach-msm/sdio_al_private.h @@ -0,0 +1,267 @@ +/* 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 +#define MAX_NUM_OF_SDIO_DEVICES 2 +#define TEST_CH_NAME_SIZE (CHANNEL_NAME_SIZE + SDIO_TEST_POSTFIX_SIZE) + +struct sdio_al_device; /* Forward Declaration */ + +enum sdio_channel_state { + SDIO_CHANNEL_STATE_INVALID, /* before reading software header */ + SDIO_CHANNEL_STATE_IDLE, /* channel valid, not opened */ + SDIO_CHANNEL_STATE_CLOSED, /* was closed */ + SDIO_CHANNEL_STATE_OPEN, /* opened */ + SDIO_CHANNEL_STATE_CLOSING, /* during flush, when closing */ +}; +/** + * 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 peer_operation; + u32 is_low_latency_ch; + u32 reserved[23]; +}; + + +/** + * 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[TEST_CH_NAME_SIZE]; + int read_threshold; + int write_threshold; + int def_read_threshold; + int threshold_change_cnt; + int min_write_avail; + int poll_delay_msec; + int is_packet_mode; + int is_low_latency_ch; + + struct peer_sdioc_channel_config ch_config; + + /* Channel Info */ + int num; + + void (*notify)(void *priv, unsigned channel_event); + void *priv; + + int state; + + 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 0000000000000000000000000000000000000000..c97588fed8f17c15bb5c7704655c9efff2c44fac --- /dev/null +++ b/arch/arm/mach-msm/sdio_al_test.c @@ -0,0 +1,6500 @@ +/* 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 "sdio_al_private.h" +#include + +#include +enum lpm_test_msg_type { + LPM_NO_MSG, /* 0 */ + LPM_MSG_SEND, /* 1 */ + LPM_MSG_REC, /* 2 */ + LPM_SLEEP, /* 3 */ + LPM_WAKEUP, /* 4 */ + LPM_NOTIFY /* 5 */ +}; + +#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 RMNT_PACKET_SIZE (4*1024) +#define DUN_PACKET_SIZE (2*1024) +#define CSVT_PACKET_SIZE 1700 + +#define TEST_DBG(x...) if (test_ctx->runtime_debug) pr_info(x) + +#define LPM_TEST_NUM_OF_PACKETS 100 +#define LPM_MAX_OPEN_CHAN_PER_DEV 4 +#define LPM_ARRAY_SIZE (7*LPM_TEST_NUM_OF_PACKETS*LPM_MAX_OPEN_CHAN_PER_DEV) +#define SDIO_LPM_TEST "sdio_lpm_test_reading_task" +#define LPM_TEST_CONFIG_SIGNATURE 0xDEADBABE +#define LPM_MSG_NAME_SIZE 20 +#define MAX_STR_SIZE 10 +#define MAX_AVG_RTT_TIME_USEC 2500 +#define SDIO_RMNT_RTT_PACKET_SIZE 32 +#define SDIO_CSVT_RTT_PACKET_SIZE 1900 + +#define A2_HEADER_OVERHEAD 8 + +enum rx_process_state { + RX_PROCESS_PACKET_INIT, + RX_PROCESS_A2_HEADER, + RX_PROCESS_PACKET_DATA, +}; + +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_CLOSE_CHANNEL, + SDIO_TEST_A2_VALIDATION, + /* The following tests are not part of the 9k tests and should be + * kept last in case new tests are added + */ + SDIO_TEST_PERF, + SDIO_TEST_RTT, + SDIO_TEST_MODEM_RESET, +}; + +struct lpm_task { + struct task_struct *lpm_task; + const char *task_name; +}; + +struct lpm_entry_type { + enum lpm_test_msg_type msg_type; + char msg_name[LPM_MSG_NAME_SIZE]; + u32 counter; + u32 current_ms; + u32 read_avail_mask; + char chan_name[CHANNEL_NAME_SIZE]; +}; + +struct lpm_msg { + u32 signature; + u32 counter; + u32 reserve1; + u32 reserve2; +}; + +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_CSVT, + 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 sdio_test_device { + int open_channels_counter_to_recv; + int open_channels_counter_to_send; + struct lpm_entry_type *lpm_arr; + int array_size; + void *sdio_al_device; + spinlock_t lpm_array_lock; + unsigned long lpm_array_lock_flags; + u32 next_avail_entry_in_array; + struct lpm_task lpm_test_task; + u32 next_mask_id; + u32 read_avail_mask; + int modem_result_per_dev; + int final_result_per_dev; +}; + +struct test_channel { + struct sdio_channel *ch; + + char name[CHANNEL_NAME_SIZE]; + int ch_id; + + struct sdio_test_device *test_device; + + 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; + atomic_t card_detected_event; + + 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; + unsigned int packet_length; + int random_packet_size; + int next_index_in_sent_msg_per_chan; + int channel_mask_id; + int modem_result_per_chan; + int notify_counter_per_chan; + int max_burst_size; /* number of writes before close/open */ + int card_removed; +}; + +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 dentry *rpc_sender_test; + struct dentry *rpc_qmi_diag_sender_test; + struct dentry *smem_test; + struct dentry *smem_rpc_test; + struct dentry *rmnet_a2_validation_test; + struct dentry *dun_a2_validation_test; + struct dentry *rmnet_a2_perf_test; + struct dentry *dun_a2_perf_test; + struct dentry *csvt_a2_perf_test; + struct dentry *rmnet_dun_a2_perf_test; + struct dentry *rpc_sender_rmnet_a2_perf_test; + struct dentry *all_channels_test; + struct dentry *host_sender_no_lp_diag_test; + struct dentry *host_sender_no_lp_diag_rpc_test; + struct dentry *rmnet_small_packets_test; + struct dentry *rmnet_rtt_test; + struct dentry *csvt_rtt_test; + struct dentry *modem_reset_rpc_test; + struct dentry *modem_reset_rmnet_test; + struct dentry *modem_reset_channels_4bit_dev_test; + struct dentry *modem_reset_channels_8bit_dev_test; + struct dentry *modem_reset_all_channels_test; + struct dentry *open_close_test; + struct dentry *open_close_dun_rmnet_test; + struct dentry *close_chan_lpm_test; + struct dentry *lpm_test_client_wakes_host_test; + struct dentry *lpm_test_host_wakes_client_test; + struct dentry *lpm_test_random_single_channel_test; + struct dentry *lpm_test_random_multi_channel_test; +}; + +struct test_context { + dev_t dev_num; + struct device *dev; + struct cdev *cdev; + int number_of_active_devices; + int max_number_of_devices; + + struct sdio_test_device test_dev_arr[MAX_NUM_OF_SDIO_DEVICES]; + + 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; + uint32_t smem_counter; + + struct platform_device *csvt_app_pdev; + + wait_queue_head_t wait_q; + int test_completed; + int test_result; + struct sdio_al_test_debug debug; + + struct wake_lock wake_lock; + + unsigned int lpm_pseudo_random_seed; +}; + +/* FORWARD DECLARATIONS */ +static int set_params_loopback_9k(struct test_channel *tch); +static int set_params_smem_test(struct test_channel *tch); +static int set_params_a2_validation(struct test_channel *tch); +static int set_params_a2_perf(struct test_channel *tch); +static int set_params_8k_sender_no_lp(struct test_channel *tch); +static int set_params_a2_small_pkts(struct test_channel *tch); +static int set_params_rtt(struct test_channel *tch); +static int set_params_loopback_9k_close(struct test_channel *tch); +static int close_channel_lpm_test(int channel_num); +static int set_params_lpm_test(struct test_channel *tch, + enum sdio_test_case_type test, + int timer_interval_ms); +static void set_pseudo_random_seed(void); +static int set_params_modem_reset(struct test_channel *tch); +static int test_start(void); +static void rx_cleanup(struct test_channel *test_ch, int *rx_packet_count); +static void sdio_al_test_cleanup_channels(void); +static void notify(void *priv, unsigned channel_event); +#ifdef CONFIG_MSM_SDIO_SMEM +static int sdio_smem_open(struct sdio_smem_client *sdio_smem); +#endif + +/* + * 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; + +static void sdio_al_test_initial_dev_and_chan(struct test_context *test_ctx) +{ + int i = 0; + + if (!test_ctx) { + pr_err(TEST_MODULE_NAME ":%s - test_ctx is NULL.\n", __func__); + return; + } + + for (i = 0 ; i < MAX_NUM_OF_SDIO_DEVICES ; ++i) + test_ctx->test_dev_arr[i].sdio_al_device = NULL; + + 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; + } + + sdio_al_test_cleanup_channels(); +} + +#ifdef CONFIG_DEBUG_FS + +static int message_repeat; + +static int sdio_al_test_extract_number(const char __user *buf, + size_t count) +{ + int ret = 0; + int number = -1; + char local_buf[MAX_STR_SIZE] = {0}; + char *start = NULL; + + if (count > MAX_STR_SIZE) { + pr_err(TEST_MODULE_NAME ": %s - MAX_STR_SIZE(%d) < count(%d). " + "Please choose smaller number\n", + __func__, MAX_STR_SIZE, (int)count); + return -EINVAL; + } + + if (copy_from_user(local_buf, buf, count)) { + pr_err(TEST_MODULE_NAME ": %s - copy_from_user() failed\n", + __func__); + return -EINVAL; + } + + /* adding null termination to the string */ + local_buf[count] = '\0'; + + /* stripping leading and trailing white spaces */ + start = strstrip(local_buf); + + ret = kstrtoint(start, 10, &number); + + if (ret) { + pr_err(TEST_MODULE_NAME " : %s - kstrtoint() failed\n", + __func__); + return ret; + } + + return number; +} + +static int sdio_al_test_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + message_repeat = 1; + return 0; +} + +static void sdio_al_test_cleanup_channels(void) +{ + int channel_num; + int dummy = 0; + + for (channel_num = 0 ; channel_num < SDIO_MAX_CHANNELS ; + ++channel_num) { + if (channel_num == SDIO_SMEM) + continue; + + rx_cleanup(test_ctx->test_ch_arr[channel_num], &dummy); + } + + return; +} + +/* RPC SENDER TEST */ +static ssize_t rpc_sender_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RPC SENDER TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rpc_sender_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRPC_SENDER_TEST\n" + "===============\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rpc_sender_test_ops = { + .open = sdio_al_test_open, + .write = rpc_sender_test_write, + .read = rpc_sender_test_read, +}; + +/* RPC, QMI & DIAG SENDER TEST */ +static ssize_t rpc_qmi_diag_sender_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RPC, QMI AND DIAG SENDER TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + 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]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rpc_qmi_diag_sender_test_read(struct file *file, + char __user + *buffer, size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRPC_QMI_DIAG_SENDER_TEST\n" + "========================\n" + "Description:\n" + "TBD\n"); + + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rpc_qmi_diag_sender_test_ops = { + .open = sdio_al_test_open, + .write = rpc_qmi_diag_sender_test_write, + .read = rpc_qmi_diag_sender_test_read, +}; + +/* SMEM TEST */ +static ssize_t smem_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- SMEM TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_smem_test(test_ctx->test_ch_arr[SDIO_SMEM]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t smem_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nSMEM_TEST\n" + "=========\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations smem_test_ops = { + .open = sdio_al_test_open, + .write = smem_test_write, + .read = smem_test_read, +}; + +/* SMEM & RPC TEST */ +static ssize_t smem_rpc_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- SMEM AND RPC TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]); + set_params_smem_test(test_ctx->test_ch_arr[SDIO_SMEM]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t smem_rpc_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nSMEM_RPC_TEST\n" + "=============\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations smem_rpc_test_ops = { + .open = sdio_al_test_open, + .write = smem_rpc_test_write, + .read = smem_rpc_test_read, +}; + +/* RMNET A2 VALIDATION TEST */ +static ssize_t rmnet_a2_validation_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RMNET A2 VALIDATION TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_validation(test_ctx->test_ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rmnet_a2_validation_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRMNET_A2_VALIDATION_TEST\n" + "=========================\n" + "Description:\n" + "In this test, the HOST sends multiple packets to the\n" + "CLIENT and validates the packets loop backed from A2\n" + "for the RMNET channel.\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rmnet_a2_validation_test_ops = { + .open = sdio_al_test_open, + .write = rmnet_a2_validation_test_write, + .read = rmnet_a2_validation_test_read, +}; + +/* DUN A2 VALIDATION TEST */ +static ssize_t dun_a2_validation_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- DUN A2 VALIDATION TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_validation(test_ctx->test_ch_arr[SDIO_DUN]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t dun_a2_validation_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nDUN_A2_VALIDATION_TEST\n" + "=========================\n" + "Description:\n" + "In this test, the HOST sends multiple packets to the\n" + "CLIENT and validates the packets loop backed from A2\n" + "for the DUN channel.\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations dun_a2_validation_test_ops = { + .open = sdio_al_test_open, + .write = dun_a2_validation_test_write, + .read = dun_a2_validation_test_read, +}; + +/* RMNET A2 PERFORMANCE TEST */ +static ssize_t rmnet_a2_perf_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RMNET A2 PERFORMANCE TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rmnet_a2_perf_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRMNET_A2_PERFORMANCE_TEST\n" + "=========================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rmnet_a2_perf_test_ops = { + .open = sdio_al_test_open, + .write = rmnet_a2_perf_test_write, + .read = rmnet_a2_perf_test_read, +}; + +/* DUN A2 PERFORMANCE TEST */ +static ssize_t dun_a2_perf_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- DUN A2 PERFORMANCE TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_DUN]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t dun_a2_perf_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nDUN_A2_PERFORMANCE_TEST\n" + "=======================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations dun_a2_perf_test_ops = { + .open = sdio_al_test_open, + .write = dun_a2_perf_test_write, + .read = dun_a2_perf_test_read, +}; + +/* CSVT A2 PERFORMANCE TEST */ +static ssize_t csvt_a2_perf_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- CSVT A2 PERFORMANCE TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_CSVT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t csvt_a2_perf_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nCSVT_A2_PERFORMANCE_TEST\n" + "========================\n" + "Description:\n" + "Loopback test on the CSVT Channel, in order to check " + "throughput performance.\n" + "Packet size that are sent on the CSVT channel in this " + "test is %d.bytes\n\n" + "END OF DESCRIPTION\n", CSVT_PACKET_SIZE); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations csvt_a2_perf_test_ops = { + .open = sdio_al_test_open, + .write = csvt_a2_perf_test_write, + .read = csvt_a2_perf_test_read, +}; + +/* RMNET DUN A2 PERFORMANCE TEST */ +static ssize_t rmnet_dun_a2_perf_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RMNET AND DUN A2 PERFORMANCE TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT]); + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_DUN]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rmnet_dun_a2_perf_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRMNET_DUN_A2_PERFORMANCE_TEST\n" + "=============================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rmnet_dun_a2_perf_test_ops = { + .open = sdio_al_test_open, + .write = rmnet_dun_a2_perf_test_write, + .read = rmnet_dun_a2_perf_test_read, +}; + +/* RPC SENDER & RMNET A2 PERFORMANCE TEST */ +static ssize_t rpc_sender_rmnet_a2_perf_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "--RPC SENDER AND RMNET A2 " + "PERFORMANCE --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]); + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rpc_sender_rmnet_a2_perf_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRPC_SENDER_RMNET_A2_PERFORMANCE_TEST\n" + "====================================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rpc_sender_rmnet_a2_perf_test_ops = { + .open = sdio_al_test_open, + .write = rpc_sender_rmnet_a2_perf_test_write, + .read = rpc_sender_rmnet_a2_perf_test_read, +}; + +/* ALL CHANNELS TEST */ +static ssize_t all_channels_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- ALL THE CHANNELS TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + 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_a2_perf(test_ctx->test_ch_arr[SDIO_CSVT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t all_channels_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nALL_CHANNELS_TEST\n" + "=================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations all_channels_test_ops = { + .open = sdio_al_test_open, + .write = all_channels_test_write, + .read = all_channels_test_read, +}; + +/* HOST SENDER NO LP DIAG TEST */ +static ssize_t host_sender_no_lp_diag_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- HOST SENDER NO LP FOR DIAG TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_8k_sender_no_lp(test_ctx->test_ch_arr[SDIO_DIAG]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t host_sender_no_lp_diag_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nHOST_SENDER_NO_LP_DIAG_TEST\n" + "===========================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations host_sender_no_lp_diag_test_ops = { + .open = sdio_al_test_open, + .write = host_sender_no_lp_diag_test_write, + .read = host_sender_no_lp_diag_test_read, +}; + +/* HOST SENDER NO LP DIAG, RPC TEST */ +static ssize_t host_sender_no_lp_diag_rpc_test_write( + struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- HOST SENDER NO LP FOR DIAG, RPC " + "TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + 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_RPC]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t host_sender_no_lp_diag_rpc_test_read( + struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nHOST_SENDER_NO_LP_DIAG_RPC_TEST\n" + "===================================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations host_sender_no_lp_diag_rpc_test_ops = { + .open = sdio_al_test_open, + .write = host_sender_no_lp_diag_rpc_test_write, + .read = host_sender_no_lp_diag_rpc_test_read, +}; + +/* RMNET SMALL PACKETS TEST */ +static ssize_t rmnet_small_packets_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RMNET SMALL PACKETS (5-128) TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_a2_small_pkts(test_ctx->test_ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rmnet_small_packets_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRMNET_SMALL_PACKETS_TEST\n" + "========================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rmnet_small_packets_test_ops = { + .open = sdio_al_test_open, + .write = rmnet_small_packets_test_write, + .read = rmnet_small_packets_test_read, +}; + +/* RMNET RTT TEST */ +static ssize_t rmnet_rtt_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- RMNET RTT TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_rtt(test_ctx->test_ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t rmnet_rtt_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nRMNET_RTT_TEST\n" + "==============\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations rmnet_rtt_test_ops = { + .open = sdio_al_test_open, + .write = rmnet_rtt_test_write, + .read = rmnet_rtt_test_read, +}; + +/* CSVT RTT TEST */ +static ssize_t csvt_rtt_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- CSVT RTT TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_rtt(test_ctx->test_ch_arr[SDIO_CSVT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t csvt_rtt_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nCSVT_RTT_TEST\n" + "==============\n" + "Description:\n" + "In this test the HOST send a message of %d bytes " + "to the CLIENT\n\n" + "END OF DESCRIPTION\n", SDIO_CSVT_RTT_PACKET_SIZE); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations csvt_rtt_test_ops = { + .open = sdio_al_test_open, + .write = csvt_rtt_test_write, + .read = csvt_rtt_test_read, +}; + +/* MODEM RESET RPC TEST */ +static ssize_t modem_reset_rpc_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- MODEM RESET - RPC CHANNEL TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_RPC]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t modem_reset_rpc_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nMODEM_RESET_RPC_TEST\n" + "====================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations modem_reset_rpc_test_ops = { + .open = sdio_al_test_open, + .write = modem_reset_rpc_test_write, + .read = modem_reset_rpc_test_read, +}; + +/* MODEM RESET RMNET TEST */ +static ssize_t modem_reset_rmnet_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- MODEM RESET - RMNT CHANNEL TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t modem_reset_rmnet_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nMODEM_RESET_RMNET_TEST\n" + "======================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations modem_reset_rmnet_test_ops = { + .open = sdio_al_test_open, + .write = modem_reset_rmnet_test_write, + .read = modem_reset_rmnet_test_read, +}; + +/* MODEM RESET - CHANNELS IN 4BIT DEVICE TEST */ +static ssize_t modem_reset_channels_4bit_dev_test_write( + struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- MODEM RESET - ALL CHANNELS IN " + "4BIT DEVICE TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_RPC]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_QMI]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_DIAG]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t modem_reset_channels_4bit_dev_test_read( + struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nMODEM_RESET_CHANNELS_4BIT_DEV_TEST\n" + "==================================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations modem_reset_channels_4bit_dev_test_ops = { + .open = sdio_al_test_open, + .write = modem_reset_channels_4bit_dev_test_write, + .read = modem_reset_channels_4bit_dev_test_read, +}; + +/* MODEM RESET - CHANNELS IN 8BIT DEVICE TEST */ +static ssize_t modem_reset_channels_8bit_dev_test_write( + struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- MODEM RESET - ALL CHANNELS IN " + "8BIT DEVICE TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_RMNT]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_DUN]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t modem_reset_channels_8bit_dev_test_read( + struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nMODEM_RESET_CHANNELS_8BIT_DEV_TEST\n" + "==================================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations modem_reset_channels_8bit_dev_test_ops = { + .open = sdio_al_test_open, + .write = modem_reset_channels_8bit_dev_test_write, + .read = modem_reset_channels_8bit_dev_test_read, +}; + +/* MODEM RESET - ALL CHANNELS TEST */ +static ssize_t modem_reset_all_channels_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- MODEM RESET - ALL CHANNELS TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_RPC]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_QMI]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_DIAG]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_RMNT]); + set_params_modem_reset(test_ctx->test_ch_arr[SDIO_DUN]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t modem_reset_all_channels_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nMODEM_RESET_ALL_CHANNELS_TEST\n" + "=============================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations modem_reset_all_channels_test_ops = { + .open = sdio_al_test_open, + .write = modem_reset_all_channels_test_write, + .read = modem_reset_all_channels_test_read, +}; + +/* HOST SENDER WITH OPEN/CLOSE TEST */ +static ssize_t open_close_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + struct test_channel **ch_arr = test_ctx->test_ch_arr; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- HOST SENDER WITH OPEN/CLOSE TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_loopback_9k_close(ch_arr[SDIO_DIAG]); + set_params_loopback_9k_close(ch_arr[SDIO_RPC]); + set_params_loopback_9k_close(ch_arr[SDIO_SMEM]); + set_params_loopback_9k_close(ch_arr[SDIO_QMI]); + set_params_loopback_9k_close(ch_arr[SDIO_RMNT]); + set_params_loopback_9k_close(ch_arr[SDIO_DUN]); + set_params_loopback_9k_close(ch_arr[SDIO_CSVT]); + + ret = test_start(); + + if (ret) + break; + + pr_info(TEST_MODULE_NAME " -- correctness test for" + "DIAG "); + set_params_loopback_9k(ch_arr[SDIO_DIAG]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t open_close_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nOPEN_CLOSE_TEST\n" + "============================\n" + "Description:\n" + "In this test the host sends 5k packets to the modem in the " + "following sequence: Send a random burst of packets on " + "Diag and Rmnet channels, read 0 or a random number " + "of packets, close and re-open the channel. At the end of the " + "test, the channel is verified by running a loopback test\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations open_close_test_ops = { + .open = sdio_al_test_open, + .write = open_close_test_write, + .read = open_close_test_read, +}; + +/* HOST SENDER WITH OPEN/CLOSE FOR DUN & RMNET TEST */ +static ssize_t open_close_dun_rmnet_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + struct test_channel **ch_arr = test_ctx->test_ch_arr; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- HOST SENDER WITH OPEN/CLOSE FOR " + "DUN AND RMNET TEST --"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_loopback_9k_close(ch_arr[SDIO_DUN]); + set_params_loopback_9k_close(ch_arr[SDIO_RMNT]); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t open_close_dun_rmnet_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nOPEN_CLOSE_DUN_RMNET_TEST\n" + "============================\n" + "Description:\n" + "In this test the host sends 5k packets to the modem in the " + "following sequence: Send a random burst of packets on " + "DUN and Rmnet channels, read 0 or a random number " + "of packets, close and re-open the channel.\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations open_close_dun_rmnet_test_ops = { + .open = sdio_al_test_open, + .write = open_close_dun_rmnet_test_write, + .read = open_close_dun_rmnet_test_read, +}; + +/* CLOSE CHANNEL & LPM TEST HOST WAKES THE CLIENT TEST */ +static ssize_t close_chan_lpm_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int channel_num = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- CLOSE CHANNEL & LPM TEST " + "HOST WAKES THE CLIENT TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + for (channel_num = 0 ; channel_num < SDIO_MAX_CHANNELS ; + channel_num++) { + + ret = close_channel_lpm_test(channel_num); + + if (ret) + break; + + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_HOST_WAKER, 120); + + ret = test_start(); + + if (ret) + break; + } + + if (ret) { + pr_err(TEST_MODULE_NAME " -- Close channel & LPM Test " + "FAILED: %d --\n", ret); + } else { + pr_err(TEST_MODULE_NAME " -- Close channel & LPM Test " + "PASSED\n"); + } + } + + return count; +} + +static ssize_t close_chan_lpm_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nCLOSE_CHAN_LPM_TEST\n" + "===================\n" + "Description:\n" + "TBD\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations close_chan_lpm_test_ops = { + .open = sdio_al_test_open, + .write = close_chan_lpm_test_write, + .read = close_chan_lpm_test_read, +}; + +/* LPM TEST FOR DEVICE 1. CLIENT WAKES THE HOST TEST */ +static ssize_t lpm_test_client_wakes_host_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- LPM TEST FOR DEVICE 1. CLIENT " + "WAKES THE HOST TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_CLIENT_WAKER, 90); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t lpm_test_client_wakes_host_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nLPM_TEST_CLIENT_WAKES_HOST_TEST\n" + "===============================\n" + "Description:\n" + "In this test, the HOST is going into LPM mode,\n" + "and the CLIENT is responsible to send it a message\n" + "in order to wake it up\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations lpm_test_client_wakes_host_test_ops = { + .open = sdio_al_test_open, + .write = lpm_test_client_wakes_host_test_write, + .read = lpm_test_client_wakes_host_test_read, +}; + +/* LPM TEST FOR DEVICE 1. HOST WAKES THE CLIENT TEST */ +static ssize_t lpm_test_host_wakes_client_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- LPM TEST FOR DEVICE 1. HOST " + "WAKES THE CLIENT TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_HOST_WAKER, 120); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t lpm_test_host_wakes_client_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nLPM_TEST_HOST_WAKES_CLIENT_TEST\n" + "===============================\n" + "Description:\n" + "In this test, the CLIENT goes into LPM mode, and the\n" + "HOST is responsible to send it a message\n" + "in order to wake it up\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations lpm_test_host_wakes_client_test_ops = { + .open = sdio_al_test_open, + .write = lpm_test_host_wakes_client_test_write, + .read = lpm_test_host_wakes_client_test_read, +}; + +/* LPM TEST RANDOM, SINGLE CHANNEL TEST */ +static ssize_t lpm_test_random_single_channel_test_write( + struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- LPM TEST RANDOM SINGLE " + "CHANNEL TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_pseudo_random_seed(); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_RANDOM, 0); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t lpm_test_random_single_channel_test_read( + struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nLPM_TEST_RANDOM_SINGLE_CHANNEL_TEST\n" + "===================================\n" + "Description:\n" + "In this test, the HOST and CLIENT " + "send messages to each other,\n" + "random in time, over RPC channel only.\n" + "All events are being recorded, and later on,\n" + "they are being analysed by the HOST and by the CLIENT\n," + "in order to check if the LPM mechanism worked properly,\n" + "meaning:" + " When all the relevant conditions are met, a device should:\n" + "1. Go to sleep\n" + "2. Wake up\n" + "3. Stay awake\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations lpm_test_random_single_channel_test_ops = { + .open = sdio_al_test_open, + .write = lpm_test_random_single_channel_test_write, + .read = lpm_test_random_single_channel_test_read, +}; + +/* LPM TEST RANDOM, MULTI CHANNEL TEST */ +static ssize_t lpm_test_random_multi_channel_test_write( + struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + + pr_info(TEST_MODULE_NAME "-- LPM TEST RANDOM MULTI CHANNEL TEST --\n"); + + number = sdio_al_test_extract_number(buf, count); + + if (number < 0) { + pr_err(TEST_MODULE_NAME " : %s - sdio_al_test_extract_number() " + "failed. number = %d\n", __func__, number); + return count; + } + + for (i = 0 ; i < number ; ++i) { + pr_info(TEST_MODULE_NAME " - Cycle # %d / %d\n", i+1, number); + pr_info(TEST_MODULE_NAME " ==================="); + + sdio_al_test_initial_dev_and_chan(test_ctx); + + set_pseudo_random_seed(); + + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_RANDOM, 0); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_DIAG], + SDIO_TEST_LPM_RANDOM, 0); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_QMI], + SDIO_TEST_LPM_RANDOM, 0); + + ret = test_start(); + + if (ret) + break; + } + + return count; +} + +static ssize_t lpm_test_random_multi_channel_test_read( + struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nLPM_TEST_RANDOM_MULTI_CHANNEL_TEST\n" + "==================================\n" + "Description:\n" + "In this test, the HOST and CLIENT " + "send messages to each other,\n" + "random in time, over RPC, QMI AND DIAG channels\n" + "(i.e, on both SDIO devices).\n" + "All events are being recorded, and later on,\n" + "they are being analysed by the HOST and by the CLIENT,\n" + "in order to check if the LPM mechanism worked properly,\n" + "meaning:" + " When all the relevant conditions are met, a device should:\n" + "1. Go to sleep\n" + "2. Wake up\n" + "3. Stay awake\n\n" + "END OF DESCRIPTION\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations lpm_test_random_multi_channel_test_ops = { + .open = sdio_al_test_open, + .write = lpm_test_random_multi_channel_test_write, + .read = lpm_test_random_multi_channel_test_read, +}; + +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); + + test_ctx->debug.rpc_sender_test = + debugfs_create_file("10_rpc_sender_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rpc_sender_test_ops); + + test_ctx->debug.rpc_qmi_diag_sender_test = + debugfs_create_file("20_rpc_qmi_diag_sender_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rpc_qmi_diag_sender_test_ops); + + test_ctx->debug.rmnet_a2_validation_test = + debugfs_create_file("30_rmnet_a2_validation_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rmnet_a2_validation_test_ops); + + test_ctx->debug.dun_a2_validation_test = + debugfs_create_file("40_dun_a2_validation_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &dun_a2_validation_test_ops); + + test_ctx->debug.rmnet_a2_perf_test = + debugfs_create_file("50_rmnet_a2_perf_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rmnet_a2_perf_test_ops); + + test_ctx->debug.dun_a2_perf_test = + debugfs_create_file("60_dun_a2_perf_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &dun_a2_perf_test_ops); + + test_ctx->debug.csvt_a2_perf_test = + debugfs_create_file("71_csvt_a2_perf_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &csvt_a2_perf_test_ops); + + test_ctx->debug.rmnet_dun_a2_perf_test = + debugfs_create_file("70_rmnet_dun_a2_perf_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rmnet_dun_a2_perf_test_ops); + + test_ctx->debug.rpc_sender_rmnet_a2_perf_test = + debugfs_create_file("80_rpc_sender_rmnet_a2_perf_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rpc_sender_rmnet_a2_perf_test_ops); + + test_ctx->debug.smem_test = + debugfs_create_file("90_smem_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &smem_test_ops); + + test_ctx->debug.smem_rpc_test = + debugfs_create_file("100_smem_rpc_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &smem_rpc_test_ops); + + test_ctx->debug.all_channels_test = + debugfs_create_file("150_all_channels_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &all_channels_test_ops); + + test_ctx->debug.host_sender_no_lp_diag_test = + debugfs_create_file("160_host_sender_no_lp_diag_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &host_sender_no_lp_diag_test_ops); + + test_ctx->debug.host_sender_no_lp_diag_rpc_test = + debugfs_create_file("170_host_sender_no_lp_diag_rpc_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &host_sender_no_lp_diag_rpc_test_ops); + + test_ctx->debug.rmnet_small_packets_test = + debugfs_create_file("180_rmnet_small_packets_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rmnet_small_packets_test_ops); + + test_ctx->debug.rmnet_rtt_test = + debugfs_create_file("190_rmnet_rtt_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &rmnet_rtt_test_ops); + + test_ctx->debug.csvt_rtt_test = + debugfs_create_file("191_csvt_rtt_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &csvt_rtt_test_ops); + + test_ctx->debug.modem_reset_rpc_test = + debugfs_create_file("220_modem_reset_rpc_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &modem_reset_rpc_test_ops); + + test_ctx->debug.modem_reset_rmnet_test = + debugfs_create_file("230_modem_reset_rmnet_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &modem_reset_rmnet_test_ops); + + test_ctx->debug.modem_reset_channels_4bit_dev_test = + debugfs_create_file("240_modem_reset_channels_4bit_dev_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &modem_reset_channels_4bit_dev_test_ops); + + test_ctx->debug.modem_reset_channels_8bit_dev_test = + debugfs_create_file("250_modem_reset_channels_8bit_dev_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &modem_reset_channels_8bit_dev_test_ops); + + test_ctx->debug.modem_reset_all_channels_test = + debugfs_create_file("260_modem_reset_all_channels_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &modem_reset_all_channels_test_ops); + + test_ctx->debug.open_close_test = + debugfs_create_file("270_open_close_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &open_close_test_ops); + + test_ctx->debug.open_close_dun_rmnet_test = + debugfs_create_file("271_open_close_dun_rmnet_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &open_close_dun_rmnet_test_ops); + + test_ctx->debug.close_chan_lpm_test = + debugfs_create_file("280_close_chan_lpm_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &close_chan_lpm_test_ops); + + test_ctx->debug.lpm_test_client_wakes_host_test = + debugfs_create_file("600_lpm_test_client_wakes_host_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &lpm_test_client_wakes_host_test_ops); + + test_ctx->debug.lpm_test_host_wakes_client_test = + debugfs_create_file("610_lpm_test_host_wakes_client_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &lpm_test_host_wakes_client_test_ops); + + test_ctx->debug.lpm_test_random_single_channel_test = + debugfs_create_file("620_lpm_test_random_single_channel_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &lpm_test_random_single_channel_test_ops); + + test_ctx->debug.lpm_test_random_multi_channel_test = + debugfs_create_file("630_lpm_test_random_multi_channel_test", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + NULL, + &lpm_test_random_multi_channel_test_ops); + + 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_TEST", + strnlen("SDIO_RPC_TEST", CHANNEL_NAME_SIZE))) + return SDIO_RPC; + else if (!strncmp(name, "SDIO_QMI_TEST", + strnlen("SDIO_QMI_TEST", TEST_CH_NAME_SIZE))) + return SDIO_QMI; + else if (!strncmp(name, "SDIO_RMNT_TEST", + strnlen("SDIO_RMNT_TEST", TEST_CH_NAME_SIZE))) + return SDIO_RMNT; + else if (!strncmp(name, "SDIO_DIAG_TEST", + strnlen("SDIO_DIAG", TEST_CH_NAME_SIZE))) + return SDIO_DIAG; + else if (!strncmp(name, "SDIO_DUN_TEST", + strnlen("SDIO_DUN_TEST", TEST_CH_NAME_SIZE))) + return SDIO_DUN; + else if (!strncmp(name, "SDIO_SMEM_TEST", + strnlen("SDIO_SMEM_TEST", TEST_CH_NAME_SIZE))) + return SDIO_SMEM; + else if (!strncmp(name, "SDIO_CSVT_TEST", + strnlen("SDIO_CSVT_TEST", TEST_CH_NAME_SIZE))) + return SDIO_CSVT; + else + return SDIO_MAX_CHANNELS; + + return SDIO_MAX_CHANNELS; +} + +/** + * Allocate and add SDIO_SMEM platform device + */ +#ifdef CONFIG_MSM_SDIO_SMEM +static int add_sdio_smem(void) +{ + int ret = 0; + + test_ctx->smem_pdev = platform_device_alloc("SDIO_SMEM", -1); + ret = platform_device_add(test_ctx->smem_pdev); + if (ret) { + pr_err(TEST_MODULE_NAME ": platform_device_add failed, " + "ret=%d\n", ret); + return ret; + } + return 0; +} +#endif + +static int open_sdio_ch(struct test_channel *tch) +{ + int ret = 0; + + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s NULL tch\n", __func__); + return -EINVAL; + } + + if (!tch->ch_ready) { + TEST_DBG(TEST_MODULE_NAME ":openning channel %s\n", + tch->name); + if (tch->ch_id == SDIO_SMEM) { +#ifdef CONFIG_MSM_SDIO_SMEM + if (!test_ctx->smem_pdev) + ret = add_sdio_smem(); + else + ret = sdio_smem_open(test_ctx->sdio_smem); + if (ret) { + pr_err(TEST_MODULE_NAME + ":openning channel %s failed\n", + tch->name); + tch->ch_ready = false; + return -EINVAL; + } +#endif + } else { + tch->ch_ready = true; + ret = sdio_open(tch->name , &tch->ch, tch, + notify); + if (ret) { + pr_err(TEST_MODULE_NAME + ":openning channel %s failed\n", + tch->name); + tch->ch_ready = false; + return -EINVAL; + } + } + } + return ret; +} + +static int close_sdio_ch(struct test_channel *tch) +{ + int ret = 0; + + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s NULL tch\n", __func__); + return -EINVAL; + } + + if (tch->ch_id == SDIO_SMEM) { +#ifdef CONFIG_MSM_SDIO_SMEM + TEST_DBG(TEST_MODULE_NAME":%s closing channel %s", + __func__, tch->name); + ret = sdio_smem_unregister_client(); + test_ctx->smem_counter = 0; +#endif + } else { + ret = sdio_close(tch->ch); + } + + if (ret) { + pr_err(TEST_MODULE_NAME":%s close channel %s" + " failed\n", __func__, tch->name); + } else { + TEST_DBG(TEST_MODULE_NAME":%s close channel %s" + " success\n", __func__, tch->name); + tch->ch_ready = false; + } + return ret; +} + +/** + * 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 ": %s - Channel %s test is " + "not completed", __func__, tch->name); + return; + } + } + pr_info(TEST_MODULE_NAME ": %s - Test is completed", __func__); + 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)) % 500); +} + +/* this function must be locked before accessing it */ +static void lpm_test_update_entry(struct test_channel *tch, + enum lpm_test_msg_type msg_type, + char *msg_name, + int counter) +{ + u32 index = 0; + static int print_full = 1; + struct sdio_test_device *test_device; + + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s - NULL test channel\n", __func__); + return; + } + + test_device = tch->test_device; + + if (!test_device) { + pr_err(TEST_MODULE_NAME ": %s - NULL test device\n", __func__); + return; + } + + if (!test_device->lpm_arr) { + pr_err(TEST_MODULE_NAME ": %s - NULL lpm_arr\n", __func__); + return; + } + + if (test_device->next_avail_entry_in_array >= + test_device->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 *)test_device->lpm_arr, + sizeof(test_device->lpm_arr), false); + print_full = 0; + } + return; + } + + index = test_device->next_avail_entry_in_array; + if ((msg_type == LPM_MSG_SEND) || (msg_type == LPM_MSG_REC)) + test_device->lpm_arr[index].counter = counter; + else + test_device->lpm_arr[index].counter = 0; + + test_device->lpm_arr[index].msg_type = msg_type; + memcpy(test_device->lpm_arr[index].msg_name, msg_name, + LPM_MSG_NAME_SIZE); + test_device->lpm_arr[index].current_ms = + jiffies_to_msecs(get_jiffies_64()); + + test_device->lpm_arr[index].read_avail_mask = + test_device->read_avail_mask; + + if ((msg_type == LPM_SLEEP) || (msg_type == LPM_WAKEUP)) + memcpy(test_device->lpm_arr[index].chan_name, "DEVICE ", + CHANNEL_NAME_SIZE); + else + memcpy(test_device->lpm_arr[index].chan_name, tch->name, + CHANNEL_NAME_SIZE); + + test_device->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, channel %s\n", + __func__, test_ch->name); + + 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 for" + "channel %s\n", test_ch->name); + break; + } + } + + return test_ch->buf[1]; + +exit_err: + return 0; +} + +static void print_random_lpm_test_array(struct sdio_test_device *test_dev) +{ + int i; + + if (!test_dev) { + pr_err(TEST_MODULE_NAME ": %s - NULL test device\n", __func__); + return; + } + + for (i = 0 ; i < test_dev->next_avail_entry_in_array ; ++i) { + if (i == 0) + pr_err(TEST_MODULE_NAME ": index %4d, chan=%2s, " + "code=%1d=%4s, msg#%1d, ms from before=-1, " + "read_mask=0x%d, ms=%2u", + i, + test_dev->lpm_arr[i].chan_name, + test_dev->lpm_arr[i].msg_type, + test_dev->lpm_arr[i].msg_name, + test_dev->lpm_arr[i].counter, + test_dev->lpm_arr[i].read_avail_mask, + test_dev->lpm_arr[i].current_ms); + else + pr_err(TEST_MODULE_NAME ": index " + "%4d, %2s, code=%1d=%4s, msg#%1d, ms from " + "before=%2u, read_mask=0x%d, ms=%2u", + i, + test_dev->lpm_arr[i].chan_name, + test_dev->lpm_arr[i].msg_type, + test_dev->lpm_arr[i].msg_name, + test_dev->lpm_arr[i].counter, + test_dev->lpm_arr[i].current_ms - + test_dev->lpm_arr[i-1].current_ms, + test_dev->lpm_arr[i].read_avail_mask, + test_dev->lpm_arr[i].current_ms); + + udelay(1000); + } +} + +static int check_random_lpm_test_array(struct sdio_test_device *test_dev) +{ + int i = 0, j = 0; + unsigned int delta_ms = 0; + int arr_ind = 0; + int ret = 0; + int notify_counter = 0; + int sleep_counter = 0; + int wakeup_counter = 0; + int lpm_activity_counter = 0; + + if (!test_dev) { + pr_err(TEST_MODULE_NAME ": %s - NULL test device\n", __func__); + return -ENODEV; + } + + for (i = 0; i < test_dev->next_avail_entry_in_array; i++) { + notify_counter = 0; + sleep_counter = 0; + wakeup_counter = 0; + + if ((test_dev->lpm_arr[i].msg_type == LPM_MSG_SEND) || + (test_dev->lpm_arr[i].msg_type == LPM_MSG_REC)) { + /* find the next message in the array */ + arr_ind = test_dev->next_avail_entry_in_array; + for (j = i+1; j < arr_ind; j++) { + if ((test_dev->lpm_arr[j].msg_type == + LPM_MSG_SEND) || + (test_dev->lpm_arr[j].msg_type == + LPM_MSG_REC) || + (test_dev->lpm_arr[j].msg_type == + LPM_NOTIFY)) + break; + if (test_dev->lpm_arr[j].msg_type == + LPM_SLEEP) + sleep_counter++; + if (test_dev->lpm_arr[j].msg_type == + LPM_WAKEUP) + wakeup_counter++; + } + if (j == arr_ind) { + ret = 0; + break; + } + + delta_ms = test_dev->lpm_arr[j].current_ms - + test_dev->lpm_arr[i].current_ms; + if (delta_ms < 30) { + if ((sleep_counter == 0) + && (wakeup_counter == 0)) { + continue; + } else { + pr_err(TEST_MODULE_NAME "%s: lpm " + "activity while delta is less " + "than 30, i=%d, j=%d, " + "sleep_counter=%d, " + "wakeup_counter=%d", + __func__, i, j, + sleep_counter, wakeup_counter); + ret = -ENODEV; + break; + } + } else { + if ((delta_ms > 90) && + (test_dev->lpm_arr[i]. + read_avail_mask == 0)) { + if (j != i+3) { + pr_err(TEST_MODULE_NAME + "%s: unexpected " + "lpm activity " + "while delta is " + "bigger than " + "90, i=%d, " + "j=%d, " + "notify_counter" + "=%d", + __func__, i, j, + notify_counter); + ret = -ENODEV; + break; + } + lpm_activity_counter++; + } + } + } + } + + pr_info(TEST_MODULE_NAME ": %s - lpm_activity_counter=%d", + __func__, lpm_activity_counter); + + return ret; +} + +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 sdio_test_device *test_dev; + struct lpm_msg lpm_msg; + int ret = 0; + int host_result = 0; + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return -ENODEV; + } + + pr_err(TEST_MODULE_NAME ": %s - STARTED. channel %s\n", + __func__, test_ch->name); + + test_dev = test_ch->test_device; + + if (!test_dev) { + pr_err(TEST_MODULE_NAME ": %s - NULL Test Device\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)); + + /* + * when reading from channel, we want to turn off the bit + * mask that implies that there is pending data on that channel + */ + if (test_ch->test_device != NULL) { + spin_lock_irqsave(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + + test_ch->notify_counter_per_chan--; + + /* + * if the channel has no pending data, turn off the + * pending data bit mask of the channel + */ + if (test_ch->notify_counter_per_chan == 0) { + test_ch->test_device->read_avail_mask = + test_ch->test_device->read_avail_mask & + ~test_ch->channel_mask_id; + } + + last_msg_index = lpm_msg.counter; + lpm_test_update_entry(test_ch, + LPM_MSG_REC, + "RECEIVE", + last_msg_index); + + spin_unlock_irqrestore(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + } + } + + pr_info(TEST_MODULE_NAME ":%s: Finished to recieve all (%d) " + "packets from the modem %s. Waiting for result_msg", + __func__, test_ch->config_msg.num_packets, test_ch->name); + + /* Wait for the resault message from the modem */ + test_ch->modem_result_per_chan = wait_for_result_msg(test_ch); + + /* + * the DEVICE modem result is a failure if one of the channels on + * that device, got modem_result = 0. this is why we bitwise "AND" each + * time another channel completes its task + */ + test_dev->modem_result_per_dev &= test_ch->modem_result_per_chan; + + /* + * when reading from channel, we want to turn off the bit + * mask that implies that there is pending data on that channel + */ + spin_lock_irqsave(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + + test_dev->open_channels_counter_to_recv--; + + /* turning off the read_avail bit of the channel */ + test_ch->test_device->read_avail_mask = + test_ch->test_device->read_avail_mask & + ~test_ch->channel_mask_id; + + spin_unlock_irqrestore(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + + /* Wait for all the packets to be sent to the modem */ + while (1) { + spin_lock_irqsave(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + + if (test_ch->next_index_in_sent_msg_per_chan >= + test_ch->config_msg.num_packets - 1) { + + spin_unlock_irqrestore(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + break; + } else { + pr_info(TEST_MODULE_NAME ":%s: Didn't finished to send " + "all packets, " + "next_index_in_sent_msg_per_chan = %d ", + __func__, + test_ch->next_index_in_sent_msg_per_chan); + } + spin_unlock_irqrestore(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + msleep(60); + } + + /* + * if device has still open channels to test, then the test on the + * device is still running but the test on current channel is completed + */ + if (test_dev->open_channels_counter_to_recv != 0 || + test_dev->open_channels_counter_to_send != 0) { + test_ch->test_completed = 1; + return 0; + } else { + test_ctx->number_of_active_devices--; + sdio_al_unregister_lpm_cb(test_ch->sdio_al_device); + + if (test_ch->test_type == SDIO_TEST_LPM_RANDOM) + host_result = check_random_lpm_test_array(test_dev); + + if (host_result || + !test_dev->modem_result_per_dev || + test_ctx->runtime_debug) + print_random_lpm_test_array(test_dev); + + pr_info(TEST_MODULE_NAME ": %s - host_result=%d.(0 for " + "SUCCESS) device_modem_result=%d (1 for SUCCESS)", + __func__, host_result, test_dev->modem_result_per_dev); + + test_ch->test_completed = 1; + if (test_dev->modem_result_per_dev && !host_result) { + pr_info(TEST_MODULE_NAME ": %s - Random LPM " + "TEST_PASSED for device %d of %d\n", + __func__, + (test_ctx->max_number_of_devices- + test_ctx->number_of_active_devices), + test_ctx->max_number_of_devices); + test_dev->final_result_per_dev = 1; /* PASSED */ + } else { + pr_info(TEST_MODULE_NAME ": %s - Random LPM " + "TEST_FAILED for device %d of %d\n", + __func__, + (test_ctx->max_number_of_devices- + test_ctx->number_of_active_devices), + test_ctx->max_number_of_devices); + test_dev->final_result_per_dev = 0; /* FAILED */ + } + + check_test_completion(); + + kfree(test_ch->test_device->lpm_arr); + + return 0; + } + +exit_err: + pr_info(TEST_MODULE_NAME ": TEST FAIL for chan %s.\n", + test_ch->name); + test_ch->test_completed = 1; + test_dev->open_channels_counter_to_recv--; + test_dev->next_avail_entry_in_array = 0; + test_ch->next_index_in_sent_msg_per_chan = 0; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return -ENODEV; +} + +static int lpm_test_create_read_thread(struct test_channel *test_ch) +{ + struct sdio_test_device *test_dev; + + pr_info(TEST_MODULE_NAME ": %s - STARTED channel %s\n", + __func__, test_ch->name); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL test channel\n", __func__); + return -ENODEV; + } + + test_dev = test_ch->test_device; + + if (!test_dev) { + pr_err(TEST_MODULE_NAME ": %s - NULL test device\n", __func__); + return -ENODEV; + } + + test_dev->lpm_test_task.task_name = SDIO_LPM_TEST; + + test_dev->lpm_test_task.lpm_task = + kthread_create(lpm_test_main_task, + (void *)(test_ch), + test_dev->lpm_test_task.task_name); + + if (IS_ERR(test_dev->lpm_test_task.lpm_task)) { + pr_err(TEST_MODULE_NAME ": %s - kthread_create() failed\n", + __func__); + return -ENOMEM; + } + + wake_up_process(test_dev->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; + struct sdio_test_device *test_dev; + + pr_info(MODULE_NAME ": %s - STARTED\n", __func__); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return; + } + + test_dev = test_ch->test_device; + + if (!test_dev) { + pr_err(TEST_MODULE_NAME ": %s - NULL Test Device\n", __func__); + return; + } + + 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 (1) { + + struct lpm_msg msg; + u32 ret = 0; + + /* sleeping period is dependent on number of open channels */ + test_ch->config_msg.test_param = + test_ctx->lpm_pseudo_random_seed; + + local_ms = test_dev->open_channels_counter_to_send * + test_ctx->lpm_pseudo_random_seed; + TEST_DBG(TEST_MODULE_NAME ":%s: SLEEPING for %d ms", + __func__, local_ms); + msleep(local_ms); + + msg.counter = test_ch->next_index_in_sent_msg_per_chan; + 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: for chan %s, write, " + "msg # %d\n", + __func__, + test_ch->name, + test_ch->next_index_in_sent_msg_per_chan); + + if (test_ch->test_type == SDIO_TEST_LPM_RANDOM) { + spin_lock_irqsave(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + lpm_test_update_entry(test_ch, LPM_MSG_SEND, + "SEND ", + test_ch-> + next_index_in_sent_msg_per_chan); + + test_ch->next_index_in_sent_msg_per_chan++; + + if (test_ch->next_index_in_sent_msg_per_chan == + test_ch->config_msg.num_packets) { + spin_unlock_irqrestore( + &test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + break; + } + + spin_unlock_irqrestore(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + } + } + + spin_lock_irqsave(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + test_dev->open_channels_counter_to_send--; + spin_unlock_irqrestore(&test_dev->lpm_array_lock, + test_dev->lpm_array_lock_flags); + + pr_info(TEST_MODULE_NAME ": %s: - Finished to send all (%d) " + "packets to the modem on channel %s", + __func__, test_ch->config_msg.num_packets, test_ch->name); + + return; +} + +static void lpm_test(struct test_channel *test_ch) +{ + pr_info(TEST_MODULE_NAME ": %s - START channel %s\n", __func__, + test_ch->name); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL test channel\n", __func__); + return; + } + + test_ch->modem_result_per_chan = 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 (test_ch->modem_result_per_chan == 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); +} + +/** + * Writes number of packets into test channel + * @test_ch: test channel control struct + * @burst_size: number of packets to send + */ +static int write_packet_burst(struct test_channel *test_ch, + int burst_size) +{ + int ret = 0; + int packet_count = 0; + unsigned int random_num = 0; + int size = test_ch->packet_length; /* first packet size */ + u32 write_avail = 0; + + while (packet_count < burst_size) { + /* wait for data ready event */ + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":%s write_avail=%d,size=%d on chan" + " %s\n", __func__, + write_avail, size, test_ch->name); + if (write_avail < size) { + TEST_DBG(TEST_MODULE_NAME ":%s wait for event on" + " chan %s\n", __func__, test_ch->name); + 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 < size) { + pr_info(TEST_MODULE_NAME ":%s not enough write" + " avail %d, need %d on chan %s\n", + __func__, write_avail, size, + test_ch->name); + continue; + } + ret = sdio_write(test_ch->ch, test_ch->buf, size); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s sdio_write " + "failed (%d) on chan %s\n", __func__, + ret, test_ch->name); + break; + } + udelay(1000); /*low bus usage while running number of channels*/ + TEST_DBG(TEST_MODULE_NAME ":%s() successfully write %d bytes" + ", packet_count=%d on chan %s\n", __func__, + size, packet_count, test_ch->name); + test_ch->tx_bytes += size; + packet_count++; + /* get next packet size */ + random_num = get_random_int(); + size = (random_num % test_ch->packet_length) + 1; + } + return ret; +} + +/** + * Reads packet from test channel and checks that packet number + * encoded into the packet is equal to packet_counter + * This function is applicable for packet mode channels only + * + * @test_ch: test channel + * @size: expected packet size + * @packet_counter: number to validate readed packet + */ +static int read_data_from_packet_ch(struct test_channel *test_ch, + unsigned int size, + int packet_counter) +{ + u32 read_avail = 0; + int ret = 0; + + if (!test_ch || !test_ch->ch) { + pr_err(TEST_MODULE_NAME + ":%s: NULL channel\n", __func__); + return -EINVAL; + } + + if (!test_ch->ch->is_packet_mode) { + pr_err(TEST_MODULE_NAME + ":%s:not packet mode ch %s\n", + __func__, test_ch->name); + return -EINVAL; + } + read_avail = sdio_read_avail(test_ch->ch); + /* wait for read data ready event */ + if (read_avail < size) { + TEST_DBG(TEST_MODULE_NAME ":%s() wait for rx data on " + "chan %s\n", __func__, 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); + TEST_DBG(TEST_MODULE_NAME ":%s read_avail=%d bytes on chan %s\n", + __func__, read_avail, test_ch->name); + + if (read_avail != size) { + pr_err(TEST_MODULE_NAME + ":read_avail size %d for chan %s not as " + "expected size %d\n", + read_avail, test_ch->name, size); + return -EINVAL; + } + + ret = sdio_read(test_ch->ch, test_ch->buf, read_avail); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s() sdio_read for chan %s (%d)\n", + __func__, test_ch->name, -ret); + return ret; + } + if ((test_ch->buf[0] != packet_counter) && (size != 1)) { + pr_err(TEST_MODULE_NAME ":Read WRONG DATA" + " for chan %s, size=%d\n", + test_ch->name, size); + return -EINVAL; + } + return 0; +} + + +/** + * Reads packet from test channel and checks that packet number + * encoded into the packet is equal to packet_counter + * This function is applicable for streaming mode channels only + * + * @test_ch: test channel + * @size: expected packet size + * @packet_counter: number to validate readed packet + */ +static int read_data_from_stream_ch(struct test_channel *test_ch, + unsigned int size, + int packet_counter) +{ + u32 read_avail = 0; + int ret = 0; + + if (!test_ch || !test_ch->ch) { + pr_err(TEST_MODULE_NAME + ":%s: NULL channel\n", __func__); + return -EINVAL; + } + + if (test_ch->ch->is_packet_mode) { + pr_err(TEST_MODULE_NAME + ":%s:not streaming mode ch %s\n", + __func__, test_ch->name); + return -EINVAL; + } + read_avail = sdio_read_avail(test_ch->ch); + /* wait for read data ready event */ + if (read_avail < size) { + TEST_DBG(TEST_MODULE_NAME ":%s() wait for rx data on " + "chan %s\n", __func__, 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); + TEST_DBG(TEST_MODULE_NAME ":%s read_avail=%d bytes on chan %s\n", + __func__, read_avail, test_ch->name); + + if (read_avail < size) { + pr_err(TEST_MODULE_NAME + ":read_avail size %d for chan %s not as " + "expected size %d\n", + read_avail, test_ch->name, size); + return -EINVAL; + } + + ret = sdio_read(test_ch->ch, test_ch->buf, size + A2_HEADER_OVERHEAD); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s() sdio_read for chan %s (%d)\n", + __func__, test_ch->name, -ret); + return ret; + } + if ((test_ch->buf[A2_HEADER_OVERHEAD/4] != packet_counter) && + (size != 1)) { + pr_err(TEST_MODULE_NAME ":Read WRONG DATA" + " for chan %s, size=%d, packet_counter=%d\n", + test_ch->name, size, packet_counter); + print_hex_dump(KERN_INFO, TEST_MODULE_NAME ": rmnet:", + 0, 32, 2, + (void *)test_ch->buf, + size + A2_HEADER_OVERHEAD, false); + return -EINVAL; + } + return 0; +} + +/** + * Test close channel feature for SDIO_SMEM channel: + * close && re-open the SDIO_SMEM channel. + */ +#ifdef CONFIG_MSM_SDIO_SMEM +static void open_close_smem_test(struct test_channel *test_ch) +{ + int i = 0; + int ret = 0; + + pr_info(TEST_MODULE_NAME ":%s\n", __func__); + + for (i = 0; i < 100 ; ++i) { + ret = close_sdio_ch(test_ch); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s close_sdio_ch for ch %s" + " failed\n", + __func__, test_ch->name); + goto exit_err; + } + ret = open_sdio_ch(test_ch); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s open_sdio_ch for ch %s " + " failed\n", + __func__, test_ch->name); + goto exit_err; + } + } + + pr_info(TEST_MODULE_NAME ":%s TEST PASS for chan %s.\n", __func__, + 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 ":%s TEST FAIL for chan %s.\n", __func__, + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return; +} +#endif + +/** + * Test close channel feature: + * 1. write random packet number into channel + * 2. read some data from channel (do this only for second half of + * requested packets to send). + * 3. close && re-open then repeat 1. + * + * Total packets to send: test_ch->config_msg.num_packets. + * Burst size is random in [1..test_ch->max_burst_size] range + * Packet size is random in [1..test_ch->packet_length] + */ +static void open_close_test(struct test_channel *test_ch) +{ + int ret = 0; + u32 read_avail = 0; + int total_packet_count = 0; + int size = 0; + u16 *buf16 = NULL; + int i; + int max_packet_count = 0; + unsigned int random_num = 0; + int curr_burst_size = 0; + + if (!test_ch || !test_ch->ch) { + pr_err(TEST_MODULE_NAME ":%s NULL channel\n", + __func__); + return; + } + + curr_burst_size = test_ch->max_burst_size; + size = test_ch->packet_length; + buf16 = (u16 *) test_ch->buf; + + /* the test sends configured number of packets in + 2 portions: first without reading between write bursts, + second with it */ + max_packet_count = test_ch->config_msg.num_packets / 2; + + pr_info(TEST_MODULE_NAME ":%s channel %s, total packets:%d," + " max packet size %d, max burst size:%d\n", + __func__, test_ch->name, + test_ch->config_msg.num_packets, test_ch->packet_length, + test_ch->max_burst_size); + for (i = 0 ; i < size / 2 ; i++) + buf16[i] = (u16) (i & 0xFFFF); + + for (i = 0; i < 2 ; i++) { + total_packet_count = 0; + while (total_packet_count < max_packet_count) { + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":%s exit test\n", + __func__); + return; + } + test_ch->buf[0] = total_packet_count; + random_num = get_random_int(); + curr_burst_size = (random_num % + test_ch->max_burst_size) + 1; + + /* limit burst size to send + * no more than configured packets */ + if (curr_burst_size + total_packet_count > + max_packet_count) { + curr_burst_size = max_packet_count - + total_packet_count; + } + TEST_DBG(TEST_MODULE_NAME ":%s Current burst size:%d" + " on chan %s\n", __func__, + curr_burst_size, test_ch->name); + ret = write_packet_burst(test_ch, curr_burst_size); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s write burst failed (%d), ch %s\n", + __func__, ret, test_ch->name); + goto exit_err; + } + if (i > 0) { + /* read from channel */ + if (test_ch->ch->is_packet_mode) + ret = read_data_from_packet_ch(test_ch, + size, + total_packet_count); + else + ret = read_data_from_stream_ch(test_ch, + size, + total_packet_count); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s read" + " failed:%d, chan %s\n", + __func__, ret, + test_ch->name); + goto exit_err; + } + } + TEST_DBG(TEST_MODULE_NAME ":%s before close, ch %s\n", + __func__, test_ch->name); + ret = close_sdio_ch(test_ch); + if (ret) { + pr_err(TEST_MODULE_NAME":%s close channel %s" + " failed (%d)\n", + __func__, test_ch->name, ret); + goto exit_err; + } else { + TEST_DBG(TEST_MODULE_NAME":%s close channel %s" + " success\n", __func__, + test_ch->name); + total_packet_count += curr_burst_size; + atomic_set(&test_ch->rx_notify_count, 0); + atomic_set(&test_ch->tx_notify_count, 0); + atomic_set(&test_ch->any_notify_count, 0); + } + TEST_DBG(TEST_MODULE_NAME ":%s before open, ch %s\n", + __func__, test_ch->name); + ret = open_sdio_ch(test_ch); + if (ret) { + pr_err(TEST_MODULE_NAME":%s open channel %s" + " failed (%d)\n", + __func__, test_ch->name, ret); + goto exit_err; + } else { + read_avail = sdio_read_avail(test_ch->ch); + if (read_avail > 0) { + pr_err(TEST_MODULE_NAME": after open" + " ch %s read_availis not zero" + " (%d bytes)\n", + test_ch->name, read_avail); + goto exit_err; + } + } + TEST_DBG(TEST_MODULE_NAME ":%s total tx = %d," + " packet# = %d, size = %d for ch %s\n", + __func__, test_ch->tx_bytes, + total_packet_count, size, + test_ch->name); + } /* end of while */ + } + pr_info(TEST_MODULE_NAME ":%s Test end: total rx bytes = 0x%x," + " total tx bytes = 0x%x for chan %s\n", __func__, + test_ch->rx_bytes, test_ch->tx_bytes, test_ch->name); + pr_info(TEST_MODULE_NAME ":%s TEST PASS for chan %s.\n", __func__, + 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 ":%s TEST FAIL for chan %s.\n", __func__, + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return; +} + +/** + * 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) { + if (time_msec) { + throughput = (total_bytes / time_msec) * 8 / 1000; + pr_err(TEST_MODULE_NAME ": %s - Performance = " + "%d Mbit/sec for chan %s\n", + __func__, throughput, test_ch->name); + } else { + pr_err(TEST_MODULE_NAME ": %s - time_msec = 0 Couldn't " + "calculate performence for chan %s\n", + __func__, 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; +} + +/** + * rx_cleanup + * This function reads all the messages sent by the modem until + * the read_avail is 0 after 1 second of sleep. + * The function returns the number of packets that was received. + */ +static void rx_cleanup(struct test_channel *test_ch, int *rx_packet_count) +{ + int read_avail = 0; + int ret = 0; + int counter = 0; + + if (!test_ch || !test_ch->ch) { + pr_err(TEST_MODULE_NAME ":%s NULL channel\n", + __func__); + return; + } + + 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 no pending messages, wait to see if the modem sends data */ + if (read_avail == 0) { + msleep(1000); + read_avail = sdio_read_avail(test_ch->ch); + } + + while ((read_avail > 0) && (counter < 10)) { + TEST_DBG(TEST_MODULE_NAME ": read_avail=%d for ch %s\n", + read_avail, test_ch->name); + + ret = sdio_read(test_ch->ch, test_ch->buf, read_avail); + if (ret) { + pr_info(TEST_MODULE_NAME ": sdio_read size %d " + " err=%d for chan %s\n", + read_avail, -ret, test_ch->name); + break; + } + (*rx_packet_count)++; + test_ch->rx_bytes += read_avail; + read_avail = sdio_read_avail(test_ch->ch); + if (read_avail == 0) { + msleep(1000); + counter++; + read_avail = sdio_read_avail(test_ch->ch); + } + } + pr_info(TEST_MODULE_NAME ": finished cleanup for ch %s, " + "rx_packet_count=%d, total rx bytes=%d\n", + test_ch->name, *rx_packet_count, test_ch->rx_bytes); +} + + +/** + * A2 RTT Test + * This function sends a packet and calculate the RTT time of + * this packet. + * The test also calculte Min, Max and Average RTT + */ +static void a2_rtt_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; + u16 *buf16 = NULL; + int i; + int max_packets = 0; + u32 packet_size = 0; + s64 start_time, end_time; + int delta_usec = 0; + int time_average = 0; + int min_delta_usec = 0xFFFF; + int max_delta_usec = 0; + int total_time = 0; + int expected_read_size = 0; + int delay_ms = 0; + int slow_rtt_counter = 0; + int read_avail_so_far = 0; + + if (test_ch) { + /* + * Cleanup the pending RX data (such as loopback of the + * config msg) + */ + rx_cleanup(test_ch, &rx_packet_count); + rx_packet_count = 0; + } else { + return; + } + + max_packets = test_ch->config_msg.num_packets; + packet_size = test_ch->packet_length; + buf16 = (u16 *) test_ch->buf; + + for (i = 0; i < packet_size / 2; i++) + buf16[i] = (u16) (i & 0xFFFF); + + pr_info(TEST_MODULE_NAME ": A2 RTT TEST START for chan %s\n", + test_ch->name); + + switch (test_ch->ch_id) { + case SDIO_RMNT: + delay_ms = 100; + break; + case SDIO_CSVT: + delay_ms = 0; + break; + default: + pr_err(TEST_MODULE_NAME ": %s - ch_id invalid.\n", + __func__); + return; + } + + while (tx_packet_count < max_packets) { + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + start_time = 0; + end_time = 0; + read_avail_so_far = 0; + + if (delay_ms) + msleep(delay_ms); + + /* wait for data ready event */ + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":ch %s: write_avail=%d\n", + test_ch->name, write_avail); + if (write_avail == 0) { + 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 ":channel %s, write_avail=%d\n", + test_ch->name, write_avail); + if (write_avail > 0) { + TEST_DBG(TEST_MODULE_NAME ":tx size = %d for chan %s\n", + packet_size, test_ch->name); + test_ch->buf[0] = tx_packet_count; + + start_time = ktime_to_us(ktime_get()); + ret = sdio_write(test_ch->ch, test_ch->buf, + packet_size); + if (ret) { + pr_err(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 += packet_size; + } else { + pr_err(TEST_MODULE_NAME ": Invalid write_avail" + " %d for chan %s\n", + write_avail, test_ch->name); + goto exit_err; + } + + expected_read_size = packet_size + A2_HEADER_OVERHEAD; + + while (read_avail_so_far < expected_read_size) { + + read_avail = sdio_read_avail(test_ch->ch); + + if (!read_avail) { + wait_event(test_ch->wait_q, + atomic_read(&test_ch-> + rx_notify_count)); + + atomic_dec(&test_ch->rx_notify_count); + continue; + } + + read_avail_so_far += read_avail; + + if (read_avail_so_far > expected_read_size) { + pr_err(TEST_MODULE_NAME ": %s - Invalid " + "read_avail(%d) read_avail_so_far(%d) " + "can't be larger than " + "expected_read_size(%d).", + __func__, + read_avail, + read_avail_so_far, + expected_read_size); + goto exit_err; + } + + /* + * must read entire pending bytes, so later, we will + * get a notification when more data arrives + */ + ret = sdio_read(test_ch->ch, test_ch->buf, + read_avail); + + if (ret) { + pr_info(TEST_MODULE_NAME ": sdio_read size %d " + " err=%d for chan %s\n", + read_avail, -ret, + test_ch->name); + goto exit_err; + } + } + + end_time = ktime_to_us(ktime_get()); + rx_packet_count++; + test_ch->rx_bytes += expected_read_size; + + delta_usec = (int)(end_time - start_time); + total_time += delta_usec; + if (delta_usec < min_delta_usec) + min_delta_usec = delta_usec; + if (delta_usec > max_delta_usec) + max_delta_usec = delta_usec; + + /* checking the RTT per channel criteria */ + if (delta_usec > MAX_AVG_RTT_TIME_USEC) { + pr_err(TEST_MODULE_NAME ": %s - " + "msg # %d - rtt time (%d usec) is " + "longer than %d usec\n", + __func__, + tx_packet_count, + delta_usec, + MAX_AVG_RTT_TIME_USEC); + slow_rtt_counter++; + } + + TEST_DBG(TEST_MODULE_NAME + ":RTT time=%d for packet #%d for chan %s\n", + delta_usec, tx_packet_count, test_ch->name); + } /* while (tx_packet_count < max_packets ) */ + + pr_info(TEST_MODULE_NAME ": %s - tx_packet_count = %d\n", + __func__, tx_packet_count); + + pr_info(TEST_MODULE_NAME ": %s - total rx bytes = 0x%x, " + "rx_packet# = %d for chan %s.\n", + __func__, test_ch->rx_bytes, rx_packet_count, test_ch->name); + + pr_info(TEST_MODULE_NAME ": %s - total tx bytes = 0x%x, " + "tx_packet# = %d for chan %s.\n", + __func__, test_ch->tx_bytes, tx_packet_count, test_ch->name); + + pr_info(TEST_MODULE_NAME ": %s - slow_rtt_counter = %d for " + "chan %s.\n", + __func__, slow_rtt_counter, test_ch->name); + + if (tx_packet_count) { + time_average = total_time / tx_packet_count; + pr_info(TEST_MODULE_NAME ":Average RTT time = %d for chan %s\n", + time_average, test_ch->name); + } else { + pr_err(TEST_MODULE_NAME ": %s - tx_packet_count=0. couldn't " + "calculate average rtt time", __func__); + } + + pr_info(TEST_MODULE_NAME ":MIN RTT time = %d for chan %s\n", + min_delta_usec, test_ch->name); + pr_info(TEST_MODULE_NAME ":MAX RTT time = %d for chan %s\n", + max_delta_usec, test_ch->name); + + pr_info(TEST_MODULE_NAME ": A2 RTT TEST END for chan %s.\n", + test_ch->name); + + if (ret) + goto exit_err; + + if (time_average == 0 || time_average > MAX_AVG_RTT_TIME_USEC) { + pr_err(TEST_MODULE_NAME ": %s - average_time = %d. Invalid " + "value", + __func__, time_average); + goto exit_err; + + } + + 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_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; +} + +/** + * Process Rx Data - Helper for A2 Validation Test + * @test_ch(in/out) : Test channel that contains Rx data buffer to process. + * + * @rx_unprocessed_bytes(in) : Number of bytes to process in the buffer. + * + * @rx_process_packet_state(in/out) : + * Current processing state (used to identify what to process + * next in a partial packet) + * + * @rx_packet_size(in/out) : + * Number of bytes remaining in the packet to be processed. + * + * @rx_packet_count(in/out) : + * Number of packets processed. + */ +static int process_rx_data(struct test_channel *test_ch, + u32 rx_unprocessed_bytes, + int *rx_process_packet_state, + u16 *rx_packet_size, + int *rx_packet_count) +{ + u8 *buf = (u8 *)test_ch->buf; + int eop = 0; + int i = 0; + int ret = 0; + u32 *ptr = 0; + u16 size = 0; + + /* process rx data */ + while (rx_unprocessed_bytes) { + TEST_DBG(TEST_MODULE_NAME ": unprocessed bytes : %u\n", + rx_unprocessed_bytes); + + switch (*rx_process_packet_state) { + case RX_PROCESS_PACKET_INIT: + /* process the A2 header */ + TEST_DBG(TEST_MODULE_NAME ": " + "RX_PROCESS_PACKET_INIT\n"); + *rx_process_packet_state = RX_PROCESS_PACKET_INIT; + if (rx_unprocessed_bytes < 4) + break; + + i += 4; + rx_unprocessed_bytes -= 4; + + case RX_PROCESS_A2_HEADER: + /* process the rest of A2 header */ + TEST_DBG(TEST_MODULE_NAME ": RX_PROCESS_A2_HEADER\n"); + *rx_process_packet_state = RX_PROCESS_A2_HEADER; + if (rx_unprocessed_bytes < 4) + break; + + ptr = (u32 *)&buf[i]; + /* + * upper 2 bytes of the last 4 bytes of A2 header + * contains the size of the packet + */ + *rx_packet_size = *ptr >> 0x10; + + i += 4; + rx_unprocessed_bytes -= 4; + + case RX_PROCESS_PACKET_DATA: + /* process the2_2_ packet data */ + TEST_DBG(TEST_MODULE_NAME ": RX_PROCESS_PACKET_DATA " + "- packet size - %u\n", *rx_packet_size); + *rx_process_packet_state = RX_PROCESS_PACKET_DATA; + + size = *rx_packet_size; + if (*rx_packet_size <= rx_unprocessed_bytes) { + eop = *rx_packet_size; + *rx_packet_size = 0; + } else { + eop = rx_unprocessed_bytes; + *rx_packet_size = *rx_packet_size - + rx_unprocessed_bytes; + } + + /* no more bytes available to process */ + if (!eop) + break; + /* + * end of packet is starting from + * the current position + */ + eop = eop + i; + TEST_DBG(TEST_MODULE_NAME ": size - %u, " + "packet size - %u eop - %d\n", + size, *rx_packet_size, eop); + + /* validate the data */ + for (; i < eop; i++) { + if (buf[i] != (test_ch->rx_bytes % 256)) { + pr_err(TEST_MODULE_NAME ": " + "Corrupt data. buf:%u, " + "data:%u\n", buf[i], + test_ch->rx_bytes % 256); + ret = -EINVAL; + goto err; + } + rx_unprocessed_bytes--; + test_ch->rx_bytes++; + } + + /* have more data to be processed */ + if (*rx_packet_size) + break; + + /* + * A2 sends data in 4 byte alignment, + * skip the padding + */ + if (size % 4) { + i += 4 - (size % 4); + rx_unprocessed_bytes -= 4 - (size % 4); + } + *rx_packet_count = *rx_packet_count + 1; + + /* re init the state to process new packet */ + *rx_process_packet_state = RX_PROCESS_PACKET_INIT; + break; + default: + pr_err(TEST_MODULE_NAME ": Invalid case: %d\n", + *rx_process_packet_state); + ret = -EINVAL; + goto err; + } + TEST_DBG(TEST_MODULE_NAME ": Continue processing " + "if more data is available\n"); + } + +err: + return ret; +} + +/** + * A2 Validation Test + * Send packets and validate the returned packets. + * Transmit one packet at a time, while process multiple rx + * packets in a single transaction. + * A transaction is of size min(random number, write_avail). + * A packet consists of a min of 1 byte to channel supported max. + */ +static void a2_validation_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 initial_rx_packet_count = 0; + u32 size = 0; + u8 *buf8 = (u8 *)test_ch->buf; + int i = 0; + int max_packets = test_ch->config_msg.num_packets; + u16 tx_packet_size = 0; + u16 rx_packet_size = 0; + u32 random_num = 0; + int rx_process_packet_state = RX_PROCESS_PACKET_INIT; + + pr_info(TEST_MODULE_NAME ": A2 VALIDATION TEST START for chan %s\n", + test_ch->name); + + /* Wait for the initial rx messages before starting the test. */ + rx_cleanup(test_ch, &initial_rx_packet_count); + + test_ch->tx_bytes = 0; + test_ch->rx_bytes = 0; + + /* Continue till we have transmitted and received all packets */ + while ((tx_packet_count < max_packets) || + (rx_packet_count < max_packets)) { + + 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 ": Random tx packet size =%u", 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 ": write_avail=%d, " + "read_avail=%d for chan %s\n", + 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); + } + + /* Transmit data */ + write_avail = sdio_write_avail(test_ch->ch); + if ((tx_packet_count < max_packets) && (write_avail > 0)) { + tx_packet_size = min(size, write_avail) ; + TEST_DBG(TEST_MODULE_NAME ": tx size = %u, " + "write_avail = %u tx_packet# = %d\n", + tx_packet_size, write_avail, + tx_packet_count); + memset(test_ch->buf, 0, test_ch->buf_size); + /* populate the buffer */ + for (i = 0; i < tx_packet_size; i++) { + buf8[i] = test_ch->tx_bytes % 256; + test_ch->tx_bytes++; + } + + ret = sdio_write(test_ch->ch, test_ch->buf, + tx_packet_size); + if (ret) { + pr_err(TEST_MODULE_NAME ":sdio_write err=%d" + " for chan %s\n", + -ret, test_ch->name); + goto exit_err; + } + tx_packet_count++; + } + + /* Receive data */ + read_avail = sdio_read_avail(test_ch->ch); + if (read_avail > 0) { + TEST_DBG(TEST_MODULE_NAME ": rx size = %u, " + "rx_packet#=%d.\n", + read_avail, rx_packet_count); + memset(test_ch->buf, 0, test_ch->buf_size); + + ret = sdio_read(test_ch->ch, test_ch->buf, + read_avail); + if (ret) { + pr_err(TEST_MODULE_NAME ": sdio_read " + "size %d err=%d for chan %s\n", + size, -ret, test_ch->name); + goto exit_err; + } + + /* Process data */ + ret = process_rx_data(test_ch, read_avail, + &rx_process_packet_state, + &rx_packet_size, + &rx_packet_count); + + if (ret != 0) + goto exit_err; + } + TEST_DBG(TEST_MODULE_NAME ": Continue loop ...\n"); + } + + if (test_ch->tx_bytes != test_ch->rx_bytes) { + pr_err(TEST_MODULE_NAME ": Total number of bytes " + "transmitted (%u) does not match the total " + "number of bytes received (%u).", test_ch->tx_bytes, + test_ch->rx_bytes); + goto exit_err; + } + + pr_info(TEST_MODULE_NAME ": A2 VALIDATION TEST END for chan %s.\n", + 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; +} + +/** + * 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; + unsigned 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 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); + + test_ch->modem_result_per_chan = wait_for_result_msg(test_ch); + + if (test_ch->modem_result_per_chan) { + 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; +} + + +/** + * Modem reset Test + * The test verifies that it finished sending all the packets + * while there might be modem reset in the middle + */ +static void modem_reset_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 max_packets = 10000; + u32 packet_size = test_ch->buf_size; + int is_err = 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 ": Modem Reset TEST START for chan %s\n", + test_ch->name); + + while (tx_packet_count < max_packets) { + + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + + if (test_ch->card_removed) { + pr_info(TEST_MODULE_NAME ": card removal was detected " + "for chan %s, tx_total=0x%x\n", + test_ch->name, test_ch->tx_bytes); + wait_event(test_ch->wait_q, + atomic_read(&test_ch->card_detected_event)); + atomic_set(&test_ch->card_detected_event, 0); + pr_info(TEST_MODULE_NAME ": card_detected_event " + "for chan %s\n", test_ch->name); + if (test_ch->card_removed) + continue; + is_err = 0; + /* Need to wait for the modem to be ready */ + msleep(5000); + pr_info(TEST_MODULE_NAME ": sending the config message " + "for chan %s\n", test_ch->name); + send_config_msg(test_ch); + } + + /* 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); + } + if (atomic_read(&test_ch->card_detected_event)) { + atomic_set(&test_ch->card_detected_event, 0); + pr_info(TEST_MODULE_NAME ": card_detected_event " + "for chan %s, tx_total=0x%x\n", + test_ch->name, test_ch->tx_bytes); + if (test_ch->card_removed) + continue; + /* Need to wait for the modem to be ready */ + msleep(5000); + is_err = 0; + pr_info(TEST_MODULE_NAME ": sending the config message " + "for chan %s\n", test_ch->name); + send_config_msg(test_ch); + } + + 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) ; + pr_debug(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; + + TEST_DBG(TEST_MODULE_NAME ":channel %s, sdio_write, " + "size=%d\n", test_ch->name, size); + if (is_err) { + msleep(100); + continue; + } + 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); + is_err = 1; + msleep(20); + continue; + } + tx_packet_count++; + test_ch->tx_bytes += size; + test_ch->config_msg.num_packets--; + } + + 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); + TEST_DBG(TEST_MODULE_NAME ":channel %s, sdio_read, " + "size=%d\n", test_ch->name, size); + if (is_err) { + msleep(100); + continue; + } + 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); + is_err = 1; + msleep(20); + continue; + } + 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); + + udelay(500); + + } /* while (tx_packet_count < max_packets ) */ + + 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); + + pr_err(TEST_MODULE_NAME ": Modem Reset 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; +} + +/** + * 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; + case SDIO_TEST_RTT: + a2_rtt_test(test_ch); + break; + case SDIO_TEST_CLOSE_CHANNEL: + if (test_ch->ch_id != SDIO_SMEM) + open_close_test(test_ch); + break; + case SDIO_TEST_MODEM_RESET: + modem_reset_test(test_ch); + break; + case SDIO_TEST_A2_VALIDATION: + a2_validation_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 ": %s - notify event=%d.\n", + __func__, channel_event); + + if (test_ch->ch == NULL) { + pr_info(TEST_MODULE_NAME ": %s - notify before ch ready.\n", + __func__); + 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 ": %s - SDIO_EVENT_DATA_READ_AVAIL, " + "any_notify_count=%d, rx_notify_count=%d\n", + __func__, + atomic_read(&test_ch->any_notify_count), + atomic_read(&test_ch->rx_notify_count)); + /* + * when there is pending data on a channel we would like to + * turn on the bit mask that implies that there is pending + * data for that channel on that deivce + */ + if (test_ch->test_device != NULL && + test_ch->test_type == SDIO_TEST_LPM_RANDOM) { + spin_lock_irqsave(&test_ch->test_device->lpm_array_lock, + test_ch->test_device-> + lpm_array_lock_flags); + test_ch->test_device->read_avail_mask |= + test_ch->channel_mask_id; + test_ch->notify_counter_per_chan++; + + lpm_test_update_entry(test_ch, LPM_NOTIFY, "NOTIFY", 0); + spin_unlock_irqrestore(&test_ch->test_device-> + lpm_array_lock, + test_ch->test_device-> + lpm_array_lock_flags); + } + 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 ": %s - SDIO_EVENT_DATA_WRITE_AVAIL, " + "any_notify_count=%d, tx_notify_count=%d\n", + __func__, + 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]; + int i; + int *smem_buf = (int *)test_ctx->smem_buf; + uint32_t val = 0; + int ret = 0; + + pr_debug(TEST_MODULE_NAME ":%s: Received event %d\n", __func__, event); + + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s NULL tch\n", __func__); + return -EINVAL; + } + + switch (event) { + case SDIO_SMEM_EVENT_READ_DONE: + tch->rx_bytes += SMEM_MAX_XFER_SIZE; + for (i = 0; i < SMEM_MAX_XFER_SIZE;) { + val = (int)*smem_buf; + if ((val != test_ctx->smem_counter) && tch->is_used) { + pr_err(TEST_MODULE_NAME ":%s: Invalid value %d " + "expected %d in smem arr", + __func__, val, test_ctx->smem_counter); + pr_err(TEST_MODULE_NAME ":SMEM test FAILED\n"); + tch->test_completed = 1; + tch->test_result = TEST_FAILED; + check_test_completion(); + ret = -EINVAL; + goto exit; + } + i += 4; + smem_buf++; + test_ctx->smem_counter++; + } + if (tch->rx_bytes >= 40000000) { + if ((!tch->test_completed) && tch->is_used) { + 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: + if (tch->is_used) { + pr_err(TEST_MODULE_NAME ":Read overflow, " + "SMEM test FAILED\n"); + tch->test_completed = 1; + tch->test_result = TEST_FAILED; + ret = -EIO; + } + break; + default: + if (tch->is_used) { + pr_err(TEST_MODULE_NAME ":Unhandled event %d\n", event); + ret = -EINVAL; + } + break; + } +exit: + return ret; +} + +static int sdio_smem_open(struct sdio_smem_client *sdio_smem) +{ + int ret = 0; + + if (!sdio_smem) { + pr_info(TEST_MODULE_NAME "%s: NULL sdio_smem_client\n", + __func__); + return -EINVAL; + } + + if (test_ctx->test_ch_arr[SDIO_SMEM]->ch_ready) { + pr_info(TEST_MODULE_NAME "%s: SDIO_SMEM channel is already opened\n", + __func__); + return 0; + } + + test_ctx->test_ch_arr[SDIO_SMEM]->ch_ready = 1; + sdio_smem->buf = test_ctx->smem_buf; + sdio_smem->size = SMEM_MAX_XFER_SIZE; + 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 int sdio_smem_test_probe(struct platform_device *pdev) +{ + test_ctx->sdio_smem = container_of(pdev, struct sdio_smem_client, + plat_dev); + + return sdio_smem_open(test_ctx->sdio_smem); +} + +static struct platform_driver sdio_smem_client_drv = { + .probe = sdio_smem_test_probe, + .driver = { + .name = "SDIO_SMEM_CLIENT", + .owner = THIS_MODULE, + }, +}; +#endif + +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; + + if (tch->test_type == SDIO_TEST_LPM_RANDOM) { + spin_lock_irqsave(&tch->test_device-> + lpm_array_lock, + tch->test_device-> + lpm_array_lock_flags); + if (is_vote_for_sleep == 1) + lpm_test_update_entry(tch, + LPM_SLEEP, + "SLEEP ", 0); + else + lpm_test_update_entry(tch, + LPM_WAKEUP, + "WAKEUP", 0); + + spin_unlock_irqrestore(&tch->test_device-> + lpm_array_lock, + tch->test_device-> + lpm_array_lock_flags); + break; + } + } + } + + return 0; +} + +static int sdio_test_find_dev(struct test_channel *tch) +{ + int j; + int null_index = -1; + + for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES; ++j) { + + struct sdio_test_device *test_dev = + &test_ctx->test_dev_arr[j]; + + if (test_dev->sdio_al_device == NULL) { + if (null_index == -1) + null_index = j; + continue; + } + + if (test_dev->sdio_al_device == + tch->ch->sdio_al_dev) { + test_dev->open_channels_counter_to_recv++; + test_dev->open_channels_counter_to_send++; + tch->test_device = test_dev; + /* setting mask id for pending data for + this channel */ + tch->channel_mask_id = test_dev->next_mask_id; + test_dev->next_mask_id *= 2; + pr_info(TEST_MODULE_NAME ": %s - channel %s " + "got read_mask_id = 0x%x. device " + "next_mask_id=0x%x", + __func__, tch->name, tch->channel_mask_id, + test_dev->next_mask_id); + break; + } + } + + /* + * happens ones a new device is "discovered" while testing. i.e + * if testing a few channels, a new deivce will be "discovered" once + * the first channel of a device is being tested + */ + if (j == MAX_NUM_OF_SDIO_DEVICES) { + + struct sdio_test_device *test_dev = + &test_ctx-> + test_dev_arr[null_index]; + test_dev->sdio_al_device = + tch->ch->sdio_al_dev; + + test_ctx->number_of_active_devices++; + test_ctx->max_number_of_devices++; + test_dev->open_channels_counter_to_recv++; + test_dev->open_channels_counter_to_send++; + test_dev->next_avail_entry_in_array = 0; + tch->test_device = test_dev; + tch->test_device->array_size = + LPM_ARRAY_SIZE; + test_dev->modem_result_per_dev = 1; + tch->modem_result_per_chan = 0; + test_dev->next_avail_entry_in_array = 0; + + spin_lock_init(&test_dev-> + lpm_array_lock); + + if (tch->test_type == SDIO_TEST_LPM_RANDOM) { + pr_err(MODULE_NAME ": %s - " + "Allocating Msg Array for " + "Maximum open channels for device (%d) " + "Channels. Array has %d entries", + __func__, + LPM_MAX_OPEN_CHAN_PER_DEV, + test_dev->array_size); + + test_dev->lpm_arr = + kzalloc(sizeof( + struct lpm_entry_type) * + tch-> + test_device->array_size, + GFP_KERNEL); + + if (!test_dev->lpm_arr) { + pr_err(MODULE_NAME ": %s - " + "lpm_arr is NULL", + __func__); + return -ENOMEM; + } + } + + /* + * in new device, initialize next_mask_id, and setting + * mask_id to the channel + */ + test_dev->next_mask_id = 0x1; + tch->channel_mask_id = test_dev->next_mask_id; + test_dev->next_mask_id *= 2; + pr_info(TEST_MODULE_NAME ": %s - channel %s got " + "read_mask_id = 0x%x. device next_mask_id=0x%x", + __func__, + tch->name, + tch->channel_mask_id, + test_dev->next_mask_id); + } + + return 0; +} + +static void check_test_result(void) +{ + int result = 1; + int i = 0; + + test_ctx->max_number_of_devices = 0; + + pr_info(TEST_MODULE_NAME ": %s - Woke Up\n", __func__); + + 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_type == SDIO_TEST_LPM_RANDOM) + result &= tch->test_device->final_result_per_dev; + else + if (tch->test_result == TEST_FAILED) { + pr_info(TEST_MODULE_NAME ": %s - " + "Test FAILED\n", __func__); + test_ctx->test_result = TEST_FAILED; + pr_err(TEST_MODULE_NAME ": %s - " + "test_result %d", + __func__, test_ctx->test_result); + return; + } + } + + if (result == 0) { + pr_info(TEST_MODULE_NAME ": %s - Test FAILED\n", __func__); + test_ctx->test_result = TEST_FAILED; + pr_err(TEST_MODULE_NAME ": %s - " + "test_result %d", + __func__, test_ctx->test_result); + return; + } + + pr_info(TEST_MODULE_NAME ": %s - Test PASSED", __func__); + test_ctx->test_result = TEST_PASSED; + pr_err(TEST_MODULE_NAME ": %s - " + "test_result %d", + __func__, test_ctx->test_result); + return; +} + +/** + * 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; + test_ctx->number_of_active_devices = 0; + + pr_err(TEST_MODULE_NAME ": %s - test_result %d", + __func__, test_ctx->test_result); + + memset(test_ctx->test_dev_arr, 0, + sizeof(struct sdio_test_device)*MAX_NUM_OF_SDIO_DEVICES); + + /* 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); + + /* in case there are values left from previous tests */ + tch->notify_counter_per_chan = 0; + tch->next_index_in_sent_msg_per_chan = 0; + + memset(tch->buf, 0x00, tch->buf_size); + tch->test_result = TEST_NO_RESULT; + + tch->test_completed = 0; + + ret = open_sdio_ch(tch); + if (ret) + continue; + + if (tch->ch_id != SDIO_SMEM) { + ret = sdio_test_find_dev(tch); + + if (ret) { + pr_err(TEST_MODULE_NAME ": %s - " + "sdio_test_find_dev() returned with " + "error", __func__); + return -ENODEV; + } + + tch->sdio_al_device = tch->ch->sdio_al_dev; + } + + 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); + } + + /* + * make some space between opening the channels and sending the + * config messages + */ + msleep(100); + + /* + * try to delay send_config_msg of all channels to after the point + * when we open them all + */ + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + + if ((!tch) || (!tch->is_used)) + continue; + + 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)) { + 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)) + continue; + + if (tch->ch_id == SDIO_SMEM) { +#ifdef CONFIG_MSM_SDIO_SMEM + if (tch->test_type == SDIO_TEST_CLOSE_CHANNEL) + open_close_smem_test(tch); +#endif + } else { + queue_work(tch->workqueue, &tch->test_work.work); + } + + } + + pr_info(TEST_MODULE_NAME ": %s - Waiting for the test completion\n", + __func__); + + wait_event(test_ctx->wait_q, test_ctx->test_completed); + check_test_result(); + + /* + * Close the channels and zero the is_used flag so that if the modem + * will be reset after the test completion we won't re-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; + if (!tch->ch_ready) { + tch->is_used = 0; + continue; + } + + close_sdio_ch(tch); + tch->is_used = 0; + } + + if (test_ctx->test_result == TEST_PASSED) + return 0; + else + return -EINVAL; +} + +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_loopback_9k_close(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_CLOSE_CHANNEL; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + tch->config_msg.num_packets = 5000; + tch->config_msg.num_iterations = 1; + tch->max_burst_size = 10; + switch (tch->ch_id) { + case SDIO_DUN: + case SDIO_RPC: + tch->packet_length = 128; /* max is 2K*/ + break; + case SDIO_DIAG: + case SDIO_RMNT: + default: + tch->packet_length = 512; /* max is 4k */ + } + 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; + + switch (tch->ch_id) { + case SDIO_DIAG: + tch->packet_length = 512; + break; + case SDIO_DUN: + tch->packet_length = DUN_PACKET_SIZE; + break; + case SDIO_CSVT: + tch->packet_length = CSVT_PACKET_SIZE; + break; + default: + tch->packet_length = MAX_XFER_SIZE; + break; + } + + pr_info(TEST_MODULE_NAME ": %s: packet_length=%d", __func__, + tch->packet_length); + + 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_rtt(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_RTT; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + + switch (tch->ch_id) { + case SDIO_RMNT: + tch->packet_length = SDIO_RMNT_RTT_PACKET_SIZE; + break; + case SDIO_CSVT: + tch->packet_length = SDIO_CSVT_RTT_PACKET_SIZE; + break; + default: + pr_err(TEST_MODULE_NAME ": %s - ch_id invalid.\n", __func__); + return -EINVAL; + } + + pr_info(TEST_MODULE_NAME ": %s: packet_length=%d", __func__, + tch->packet_length); + + tch->config_msg.num_packets = 200; + 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_modem_reset(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_MODEM_RESET; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + tch->packet_length = 512; + if (tch->ch_id == SDIO_RPC) + tch->packet_length = 128; + else if ((tch->ch_id == SDIO_RMNT) || (tch->ch_id == SDIO_DUN)) + tch->packet_length = MAX_XFER_SIZE; + + tch->config_msg.num_packets = 50000; + tch->config_msg.num_iterations = 1; + + tch->timer_interval_ms = 0; + + return 0; +} + +static int set_params_a2_validation(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_A2_VALIDATION; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + + if (tch->ch_id == SDIO_RMNT) + tch->packet_length = RMNT_PACKET_SIZE; + else if (tch->ch_id == SDIO_DUN) + tch->packet_length = DUN_PACKET_SIZE; + else + tch->packet_length = MAX_XFER_SIZE; + + tch->config_msg.num_packets = 10000; + tch->config_msg.num_iterations = 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 = LPM_TEST_NUM_OF_PACKETS; + tch->config_msg.num_iterations = 1; + 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 channel %s. to prevent real sleeping\n", + __func__, tch->name); + 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; +} + +static void set_pseudo_random_seed(void) +{ + /* Set the seed accoring to the kernel command parameters if any or + get a random value */ + if (seed != 0) { + test_ctx->lpm_pseudo_random_seed = seed; + } else { + test_ctx->lpm_pseudo_random_seed = + (unsigned int)(get_jiffies_64() & 0xFFFF); + test_ctx->lpm_pseudo_random_seed = + pseudo_random_seed(&test_ctx->lpm_pseudo_random_seed); + } + + pr_info(TEST_MODULE_NAME ":%s: seed is %u", + __func__, test_ctx->lpm_pseudo_random_seed); +} + +/* + for each channel + 1. open channel + 2. close channel +*/ +static int close_channel_lpm_test(int channel_num) +{ + int ret = 0; + struct test_channel *tch = NULL; + tch = test_ctx->test_ch_arr[channel_num]; + + if (!tch) { + pr_info(TEST_MODULE_NAME ":%s ch#%d is NULL\n", + __func__, channel_num); + return 0; + } + + ret = open_sdio_ch(tch); + if (ret) { + pr_err(TEST_MODULE_NAME":%s open channel %s" + " failed\n", __func__, tch->name); + return ret; + } else { + pr_info(TEST_MODULE_NAME":%s open channel %s" + " success\n", __func__, tch->name); + } + ret = close_sdio_ch(tch); + if (ret) { + pr_err(TEST_MODULE_NAME":%s close channel %s" + " failed\n", __func__, tch->name); + return ret; + } else { + pr_info(TEST_MODULE_NAME":%s close channel %s" + " success\n", __func__, tch->name); + } + + tch->is_used = 0; + + return ret; +} + +/** + * 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) +{ + sdio_al_test_initial_dev_and_chan(test_ctx); + + if (strict_strtol(buf, 10, &test_ctx->testcase)) + return -EINVAL; + + switch (test_ctx->testcase) { + 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 size; + } + + return size; +} + +/** + * Test Channel Init. + */ +int test_channel_init(char *name) +{ + struct test_channel *test_ch; + int ch_id = 0; + int ret; + + pr_debug(TEST_MODULE_NAME ":%s.\n", __func__); + pr_info(TEST_MODULE_NAME ": init test channel %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; + + strncpy(test_ch->name, name, + strnlen(name, TEST_CH_NAME_SIZE)-SDIO_TEST_POSTFIX_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_client_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 { + test_ch = test_ctx->test_ch_arr[ch_id]; + pr_info(TEST_MODULE_NAME ":%s: ch %s was detected again\n", + __func__, test_ch->name); + test_ch->card_removed = 0; + if ((test_ch->is_used) && + (test_ch->test_type == SDIO_TEST_MODEM_RESET)) { + if (test_ch->ch_id == SDIO_SMEM) { +#ifdef CONFIG_MSM_SDIO_SMEM + ret = add_sdio_smem(); + if (ret) { + test_ch->ch_ready = false; + return 0; + } +#endif + } else { + ret = open_sdio_ch(test_ch); + if (ret) { + pr_info(TEST_MODULE_NAME + ":%s: open channel %s failed\n", + __func__, test_ch->name); + return 0; + } + ret = sdio_test_find_dev(test_ch); + + if (ret) { + pr_err(TEST_MODULE_NAME ": %s - " + "sdio_test_find_dev() returned " + "with error", __func__); + return -ENODEV; + } + + test_ch->sdio_al_device = + test_ch->ch->sdio_al_dev; + } + atomic_set(&test_ch->card_detected_event, 1); + wake_up(&test_ch->wait_q); + } + } + + return 0; +} + +static int sdio_test_channel_probe(struct platform_device *pdev) +{ + if (!pdev) + return -EIO; + return test_channel_init((char *)pdev->name); +} + +static int sdio_test_channel_remove(struct platform_device *pdev) +{ + int ch_id; + + if (!pdev) + return -EIO; + + ch_id = channel_name_to_id((char *)pdev->name); + if (test_ctx->test_ch_arr[ch_id] == NULL) + return 0; + + pr_info(TEST_MODULE_NAME "%s: remove ch %s\n", + __func__, test_ctx->test_ch_arr[ch_id]->name); + + if ((ch_id == SDIO_SMEM) && (test_ctx->smem_pdev)) { + platform_device_unregister(test_ctx->smem_pdev); + test_ctx->smem_pdev = NULL; + } + + test_ctx->test_ch_arr[ch_id]->ch_ready = 0; + test_ctx->test_ch_arr[ch_id]->card_removed = 1; + + return 0; + +} + +static int sdio_test_channel_csvt_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (!pdev) + return -ENODEV; + + test_ctx->csvt_app_pdev = platform_device_alloc("SDIO_CSVT_TEST_APP", + -1); + ret = platform_device_add(test_ctx->csvt_app_pdev); + if (ret) { + pr_err(MODULE_NAME ":platform_device_add failed, " + "ret=%d\n", ret); + return ret; + } + + return sdio_test_channel_probe(pdev); +} + +static int sdio_test_channel_csvt_remove(struct platform_device *pdev) +{ + if (!pdev) + return -ENODEV; + + platform_device_unregister(test_ctx->csvt_app_pdev); + + return sdio_test_channel_remove(pdev); +} + +static struct platform_driver sdio_rpc_drv = { + .probe = sdio_test_channel_probe, + .remove = sdio_test_channel_remove, + .driver = { + .name = "SDIO_RPC_TEST", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_qmi_drv = { + .probe = sdio_test_channel_probe, + .remove = sdio_test_channel_remove, + .driver = { + .name = "SDIO_QMI_TEST", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_diag_drv = { + .probe = sdio_test_channel_probe, + .remove = sdio_test_channel_remove, + .driver = { + .name = "SDIO_DIAG_TEST", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_smem_drv = { + .probe = sdio_test_channel_probe, + .remove = sdio_test_channel_remove, + .driver = { + .name = "SDIO_SMEM_TEST", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_rmnt_drv = { + .probe = sdio_test_channel_probe, + .remove = sdio_test_channel_remove, + .driver = { + .name = "SDIO_RMNT_TEST", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_dun_drv = { + .probe = sdio_test_channel_probe, + .remove = sdio_test_channel_remove, + .driver = { + .name = "SDIO_DUN_TEST", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_csvt_drv = { + .probe = sdio_test_channel_csvt_probe, + .remove = sdio_test_channel_csvt_remove, + .driver = { + .name = "SDIO_CSVT_TEST", + .owner = THIS_MODULE, + }, +}; + +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(struct test_context), 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"); + + platform_driver_register(&sdio_rpc_drv); + platform_driver_register(&sdio_qmi_drv); + platform_driver_register(&sdio_diag_drv); + platform_driver_register(&sdio_smem_drv); + platform_driver_register(&sdio_rmnt_drv); + platform_driver_register(&sdio_dun_drv); + platform_driver_register(&sdio_csvt_drv); + + 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); + + platform_driver_unregister(&sdio_rpc_drv); + platform_driver_unregister(&sdio_qmi_drv); + platform_driver_unregister(&sdio_diag_drv); + platform_driver_unregister(&sdio_smem_drv); + platform_driver_unregister(&sdio_rmnt_drv); + platform_driver_unregister(&sdio_dun_drv); + platform_driver_unregister(&sdio_csvt_drv); + + 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 0000000000000000000000000000000000000000..d04a0b03b75fcc295b3df14ae025ee6bb5b5c6b6 --- /dev/null +++ b/arch/arm/mach-msm/sdio_cmux.c @@ -0,0 +1,885 @@ +/* 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 +#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); + +static DEFINE_MUTEX(probe_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->receive_cb = NULL; + mutex_lock(&ch->tx_lock); + ch->write_done = NULL; + mutex_unlock(&ch->tx_lock); + ch->is_local_open = 0; + ch->priv = 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; + if (logical_ch[id].is_channel_reset) { + sdio_cmux_write_cmd(id, OPEN); + 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) + && (r != -ENODEV) + && (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; + } else if (r == -ENODEV) { + pr_err("%s: aborting_tx because sdio_write" + " returned %d\n", __func__, r); + r = 0; + abort_tx = 1; + } + 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; + + mutex_lock(&probe_lock); + 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 error0; + } + abort_tx = 0; + mutex_unlock(&modem_reset_lock); + mutex_unlock(&probe_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"); + mutex_unlock(&probe_lock); + return 0; + +error2: + destroy_workqueue(sdio_cdemux_wq); +error1: + destroy_workqueue(sdio_cmux_wq); +error0: + mutex_unlock(&probe_lock); + 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 0000000000000000000000000000000000000000..586e8905590ba7a4428174bd3c076af80a0f8123 --- /dev/null +++ b/arch/arm/mach-msm/sdio_ctl.c @@ -0,0 +1,545 @@ +/* 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 10 +#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 uint32_t cmux_ch_id[] = { + 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_CSVT_CTL_0 +}; + +static int get_ctl_dev_index(int id) +{ + int dev_index; + for (dev_index = 0; dev_index < NUM_SDIO_CTL_PORTS; dev_index++) { + if (cmux_ch_id[dev_index] == id) + return dev_index; + } + return -ENODEV; +} + +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; + int dev_index; + + if (id < 0 || id > cmux_ch_id[NUM_SDIO_CTL_PORTS - 1]) + return; + dev_index = get_ctl_dev_index(id); + if (dev_index < 0) { + pr_err("%s: Ch%d is not exported to user-space\n", + __func__, id); + return; + } + + if (!data || size <= 0) { + wake_up(&sdio_ctl_devp[dev_index]->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[dev_index]->rx_lock); + list_add_tail(&list_elem->list, &sdio_ctl_devp[dev_index]->rx_list); + sdio_ctl_devp[dev_index]->read_avail += size; + mutex_unlock(&sdio_ctl_devp[dev_index]->rx_lock); + wake_up(&sdio_ctl_devp[dev_index]->read_wait_queue); +} + +static void sdio_ctl_write_done(void *data, int size, void *priv) +{ + int id = ((struct sdio_ctl_dev *)(priv))->id; + int dev_index; + if (id < 0 || id > cmux_ch_id[NUM_SDIO_CTL_PORTS - 1]) + return; + + dev_index = get_ctl_dev_index(id); + if (dev_index < 0) { + pr_err("%s: Ch%d is not exported to user-space\n", + __func__, id); + return; + } + wake_up(&sdio_ctl_devp[dev_index]->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 = cmux_ch_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", cmux_ch_id[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 0000000000000000000000000000000000000000..71b4e9b9229dcc7c16e5e5a05ee6c901664131db --- /dev/null +++ b/arch/arm/mach-msm/sdio_dmux.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. + * + */ + +/* + * SDIO DMUX module. + */ + +#define DEBUG + +#include +#include +#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; + sdio_ch[hdr->ch_id].num_tx_pkts = 0; + + 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; + static int workqueue_pinned; + + if (!workqueue_pinned) { + struct cpumask cpus; + + cpumask_clear(&cpus); + cpumask_set_cpu(0, &cpus); + + if (sched_setaffinity(current->pid, &cpus)) + pr_err("%s: sdio_dmux set CPU affinity failed\n", + __func__); + workqueue_pinned = 1; + } + + 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; + do { + ch_id = ((struct sdio_mux_hdr *) + skb->data)->ch_id; + spin_lock(&sdio_ch[ch_id].lock); + sdio_ch[ch_id].num_tx_pkts--; + spin_unlock(&sdio_ch[ch_id].lock); + dev_kfree_skb_any(skb); + } while ((skb = __skb_dequeue(&sdio_mux_write_pool))); + 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) +{ + int ret; + + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + + 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); + } + + 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; + } + + fatal_error = 0; + 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; + + /* 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); + } + + /* cancel any pending writes */ + spin_lock_irqsave(&sdio_mux_write_lock, write_lock_flags); + while ((skb = __skb_dequeue(&sdio_mux_write_pool))) { + i = ((struct sdio_mux_hdr *)skb->data)->ch_id; + 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); + + 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 0000000000000000000000000000000000000000..4416a7909b2ee785768284b7ee9a5554755a82ed --- /dev/null +++ b/arch/arm/mach-msm/sdio_smem.c @@ -0,0 +1,175 @@ +/* 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 + +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 int sdio_ch_opened; + +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; + + if (!sdio_ch_opened) + return; + + 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); + goto read_err; + } + + if (read_avail == 0) + return; + + err = sdio_read(channel, + &data[client.size - bytes_avail], + read_avail); + if (err) { + pr_err("sdio_read error (%d)", err); + goto read_err; + } + + 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); + + return; + +read_err: + if (sdio_ch_opened) + client.cb_func(SDIO_SMEM_EVENT_READ_ERR); + return; +} + +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; + + sdio_ch_opened = 1; + err = sdio_open("SDIO_SMEM", &channel, NULL, sdio_smem_notify); + if (err) { + sdio_ch_opened = 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) +{ + int err = 0; + + sdio_ch_opened = 0; + err = sdio_close(channel); + if (err) { + pr_err("sdio_close error (%d)\n", err); + return err; + } + pr_debug("SDIO SMEM channel closed\n"); + flush_workqueue(workq); + destroy_workqueue(workq); + bytes_avail = 0; + client.buf = NULL; + client.cb_func = NULL; + client.size = 0; + + 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)); + sdio_ch_opened = 0; + 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 0000000000000000000000000000000000000000..41bc27006bce8b73d17747999c7377b62cfb951d --- /dev/null +++ b/arch/arm/mach-msm/sdio_tty.c @@ -0,0 +1,824 @@ +/* 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 + +#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 +#define MAX_SDIO_TTY_DEVS 2 +#define MAX_SDIO_TTY_DEV_NAME_SIZE 25 + +/* Configurations per channel device */ +/* CSVT */ +#define SDIO_TTY_CSVT_DEV "sdio_tty_csvt_0" +#define SDIO_TTY_CSVT_TEST_DEV "sdio_tty_csvt_test_0" +#define SDIO_TTY_CH_CSVT "SDIO_CSVT" + +enum sdio_tty_state { + TTY_INITIAL = 0, + TTY_REGISTERED = 1, + TTY_OPENED = 2, + TTY_CLOSED = 3, +}; + +enum sdio_tty_devices { + SDIO_CSVT, + SDIO_CSVT_TEST_APP, +}; + +static const struct platform_device_id sdio_tty_id_table[] = { + { "SDIO_CSVT", SDIO_CSVT }, + { "SDIO_CSVT_TEST_APP", SDIO_CSVT_TEST_APP }, + { }, +}; +MODULE_DEVICE_TABLE(platform, sdio_tty_id_table); + +struct sdio_tty { + struct sdio_channel *ch; + char *sdio_ch_name; + char tty_dev_name[MAX_SDIO_TTY_DEV_NAME_SIZE]; + int device_id; + 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; + int tty_open_count; + int total_rx; + int total_tx; +}; + +static struct sdio_tty *sdio_tty[MAX_SDIO_TTY_DEVS]; + +#ifdef CONFIG_DEBUG_FS +struct dentry *sdio_tty_debug_root; +struct dentry *sdio_tty_debug_info; +#endif + +#define DEBUG_MSG(sdio_tty_drv, x...) if (sdio_tty_drv->debug_msg_on) pr_info(x) + +/* + * Enable sdio_tty debug messages + * By default the sdio_tty debug messages are turned off + */ +static int csvt_debug_msg_on; +module_param(csvt_debug_msg_on, int, 0); + +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 for dev %s", + __func__, sdio_tty_drv->tty_dev_name); + return; + } + + /* Read the data from the 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 for " + "dev %s, exit", __func__, + sdio_tty_drv->tty_dev_name); + 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 for dev %s", __func__, + read_avail, sdio_tty_drv->tty_dev_name); + + if (read_avail == 0) { + DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME + ": %s: read_avail is 0 for dev %s", + __func__, sdio_tty_drv->tty_dev_name); + 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) " + "for dev %s", __func__, read_avail, + SDIO_TTY_MAX_PACKET_SIZE, + sdio_tty_drv->tty_dev_name); + 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) " + "for dev %s", __func__, ret, + sdio_tty_drv->tty_dev_name); + 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) for dev %s\n", + __func__, total_push, read_avail, + sdio_tty_drv->tty_dev_name); + } + + tty_flip_buffer_push(sdio_tty_drv->tty_str); + sdio_tty_drv->total_rx += read_avail; + + DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Rx: %d, " + "Total Rx = %d bytes for dev %s", __func__, + read_avail, sdio_tty_drv->total_rx, + sdio_tty_drv->tty_dev_name); + } +} + +/** + * 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; + } + + 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; + } + + write_avail = sdio_write_avail(sdio_tty_drv->ch); + DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: write_avail=%d " + "for dev %s", __func__, write_avail, + sdio_tty_drv->tty_dev_name); + + 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: Write Callback " + "called with %d bytes for dev %s\n", __func__, count, + sdio_tty_drv->tty_dev_name); + 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 for dev %s\n", + __func__, sdio_tty_drv->tty_dev_name); + 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) for dev %s, setting to " + "max_packet_size\n", __func__, write_avail, + SDIO_TTY_MAX_PACKET_SIZE, + sdio_tty_drv->tty_dev_name); + 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) " + "for dev %s, writing only %d bytes\n", + __func__, write_avail, count, + sdio_tty_drv->tty_dev_name, 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 for " + "dev %s, ret=%d\n", __func__, + sdio_tty_drv->tty_dev_name, ret); + return 0; + } + + sdio_tty_drv->total_tx += len; + + DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Tx: %d, " + "Total Tx = %d for dev %s", __func__, len, + sdio_tty_drv->total_tx, sdio_tty_drv->tty_dev_name); + 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 for dev %s\n", __func__, event, + sdio_tty_drv->tty_dev_name); + + 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; + int i = 0; + struct sdio_tty *sdio_tty_drv = NULL; + + if (!tty) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", __func__); + return -ENODEV; + } + + for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { + if (sdio_tty[i] == NULL) + continue; + if (!strncmp(sdio_tty[i]->tty_dev_name, tty->name, + MAX_SDIO_TTY_DEV_NAME_SIZE)) { + sdio_tty_drv = sdio_tty[i]; + break; + } + } + + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return -ENODEV; + } + + sdio_tty_drv->tty_open_count++; + if (sdio_tty_drv->sdio_tty_state == TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: tty dev(%s) is already open", + __func__, sdio_tty_drv->tty_dev_name); + return -EBUSY; + } + + 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 " + "for dev %s", __func__, sdio_tty_drv->tty_dev_name); + 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 " + "for dev %s", __func__, sdio_tty_drv->tty_dev_name); + return -ENOMEM; + } + + 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 " + "for dev %s\n", __func__, ret, + sdio_tty_drv->tty_dev_name); + destroy_workqueue(sdio_tty_drv->workq); + return ret; + } + + pr_info(SDIO_TTY_MODULE_NAME ": %s: SDIO_TTY channel(%s) " + "opened\n", __func__, sdio_tty_drv->sdio_ch_name); + + 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(%s) opened\n", + __func__, sdio_tty_drv->tty_dev_name); + + 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; + } + if (--sdio_tty_drv->tty_open_count != 0) + 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 device(%s) closed\n", + __func__, sdio_tty_drv->tty_dev_name); +} + +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, +}; + +int sdio_tty_init_tty(char *tty_name, char *sdio_ch_name, + enum sdio_tty_devices device_id, int debug_msg_on) +{ + int ret = 0; + int i = 0; + struct device *tty_dev = NULL; + struct sdio_tty *sdio_tty_drv = NULL; + + 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 " + "for dev %s", __func__, tty_name); + return -ENOMEM; + } + + for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { + if (sdio_tty[i] == NULL) { + sdio_tty[i] = sdio_tty_drv; + break; + } + } + + if (i == MAX_SDIO_TTY_DEVS) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: tty dev(%s) creation failed," + " max limit(%d) reached.", __func__, tty_name, + MAX_SDIO_TTY_DEVS); + kfree(sdio_tty_drv); + return -ENODEV; + } + + snprintf(sdio_tty_drv->tty_dev_name, MAX_SDIO_TTY_DEV_NAME_SIZE, + "%s%d", tty_name, 0); + sdio_tty_drv->sdio_ch_name = sdio_ch_name; + sdio_tty_drv->device_id = device_id; + pr_info(SDIO_TTY_MODULE_NAME ": %s: dev=%s, id=%d, channel=%s\n", + __func__, sdio_tty_drv->tty_dev_name, sdio_tty_drv->device_id, + sdio_tty_drv->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 for dev %s", + __func__, sdio_tty_drv->tty_dev_name); + kfree(sdio_tty_drv); + return -ENODEV; + } + + 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 for dev %s\n", __func__, + sdio_tty_drv->tty_dev_name); + + sdio_tty_drv->tty_drv = NULL; + kfree(sdio_tty_drv); + return -ENODEV; + } + + 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 for dev %s\n", __func__, + sdio_tty_drv->tty_dev_name); + tty_unregister_driver(sdio_tty_drv->tty_drv); + put_tty_driver(sdio_tty_drv->tty_drv); + kfree(sdio_tty_drv); + return -ENODEV; + } + + sdio_tty_drv->sdio_tty_state = TTY_REGISTERED; + if (debug_msg_on) { + pr_info(SDIO_TTY_MODULE_NAME ": %s: turn on debug msg for %s", + __func__, sdio_tty_drv->tty_dev_name); + sdio_tty_drv->debug_msg_on = debug_msg_on; + } + return 0; +} + +int sdio_tty_uninit_tty(void *sdio_tty_handle) +{ + int ret = 0; + int i = 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_OPENED) { + flush_workqueue(sdio_tty_drv->workq); + destroy_workqueue(sdio_tty_drv->workq); + + kfree(sdio_tty_drv->read_buf); + sdio_tty_drv->read_buf = NULL; + } + + 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 for dev %s\n", + __func__, sdio_tty_drv->tty_dev_name); + } + put_tty_driver(sdio_tty_drv->tty_drv); + sdio_tty_drv->sdio_tty_state = TTY_INITIAL; + sdio_tty_drv->tty_drv = NULL; + } + + for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { + if (sdio_tty[i] == NULL) + continue; + if (sdio_tty[i]->device_id == sdio_tty_drv->device_id) { + sdio_tty[i] = NULL; + break; + } + } + + DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Freeing sdio_tty " + "structure, dev=%s", __func__, + sdio_tty_drv->tty_dev_name); + kfree(sdio_tty_drv); + + return 0; +} + +static int sdio_tty_probe(struct platform_device *pdev) +{ + const struct platform_device_id *id = platform_get_device_id(pdev); + enum sdio_tty_devices device_id = id->driver_data; + char *device_name = NULL; + char *channel_name = NULL; + int debug_msg_on = 0; + int ret = 0; + + pr_debug(SDIO_TTY_MODULE_NAME ": %s for %s", __func__, pdev->name); + + switch (device_id) { + case SDIO_CSVT: + device_name = SDIO_TTY_CSVT_DEV; + channel_name = SDIO_TTY_CH_CSVT; + debug_msg_on = csvt_debug_msg_on; + break; + case SDIO_CSVT_TEST_APP: + device_name = SDIO_TTY_CSVT_TEST_DEV; + channel_name = SDIO_TTY_CH_CSVT; + debug_msg_on = csvt_debug_msg_on; + break; + default: + pr_err(SDIO_TTY_MODULE_NAME ": %s Invalid device:%s, id:%d", + __func__, pdev->name, device_id); + ret = -ENODEV; + break; + } + + if (device_name) { + ret = sdio_tty_init_tty(device_name, channel_name, + device_id, debug_msg_on); + if (ret) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_init_tty " + "failed for dev:%s", __func__, device_name); + } + } + return ret; +} + +static int sdio_tty_remove(struct platform_device *pdev) +{ + const struct platform_device_id *id = platform_get_device_id(pdev); + enum sdio_tty_devices device_id = id->driver_data; + struct sdio_tty *sdio_tty_drv = NULL; + int i = 0; + int ret = 0; + + pr_debug(SDIO_TTY_MODULE_NAME ": %s for %s", __func__, pdev->name); + + for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { + if (sdio_tty[i] == NULL) + continue; + if (sdio_tty[i]->device_id == device_id) { + sdio_tty_drv = sdio_tty[i]; + break; + } + } + + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return -ENODEV; + } + + ret = sdio_tty_uninit_tty(sdio_tty_drv); + if (ret) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_uninit_tty " + "failed for %s", __func__, pdev->name); + } + return ret; +} + +static struct platform_driver sdio_tty_pdrv = { + .probe = sdio_tty_probe, + .remove = sdio_tty_remove, + .id_table = sdio_tty_id_table, + .driver = { + .name = "SDIO_TTY", + .owner = THIS_MODULE, + }, +}; + +#ifdef CONFIG_DEBUG_FS +void sdio_tty_print_info(void) +{ + int i = 0; + + for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { + if (sdio_tty[i] == NULL) + continue; + pr_info(SDIO_TTY_MODULE_NAME ": %s: Total Rx=%d, Tx = %d " + "for dev %s", __func__, sdio_tty[i]->total_rx, + sdio_tty[i]->total_tx, sdio_tty[i]->tty_dev_name); + } +} + +static int tty_debug_info_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t tty_debug_info_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + sdio_tty_print_info(); + return count; +} + +const struct file_operations tty_debug_info_ops = { + .open = tty_debug_info_open, + .write = tty_debug_info_write, +}; +#endif + +/* + * Module Init. + * + * Register SDIO TTY driver. + * + */ +static int __init sdio_tty_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&sdio_tty_pdrv); + if (ret) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: platform_driver_register " + "failed", __func__); + } +#ifdef CONFIG_DEBUG_FS + else { + sdio_tty_debug_root = debugfs_create_dir("sdio_tty", NULL); + if (sdio_tty_debug_root) { + sdio_tty_debug_info = debugfs_create_file( + "sdio_tty_debug", + S_IRUGO | S_IWUGO, + sdio_tty_debug_root, + NULL, + &tty_debug_info_ops); + } + } +#endif + return ret; +}; + +/* + * Module Exit. + * + * Unregister SDIO TTY driver. + * + */ +static void __exit sdio_tty_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove(sdio_tty_debug_info); + debugfs_remove(sdio_tty_debug_root); +#endif + platform_driver_unregister(&sdio_tty_pdrv); +} + +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/sirc-fsm9xxx.c b/arch/arm/mach-msm/sirc-fsm9xxx.c new file mode 100644 index 0000000000000000000000000000000000000000..71fa60ae22b7bdd2957d2203397a0c6a83bf3e7a --- /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 689e78c95f38809010b3346da986c3a7931f5ca2..04124c5dcbf1ade07fbc94a099e33d0ed21ab470 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 0000000000000000000000000000000000000000..f719969018d326dfb122ca813df57d51b235951a --- /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 657be73297db9361830633f01f061a1b9acdca28..54512abc9387a9e3107a9ba5bd647b8b87f044c5 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-2012, 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,596 @@ #include #include #include -#include #include - +#include +#include +#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) \ + || defined(CONFIG_ARCH_MSM9615) || defined(CONFIG_ARCH_APQ8064) #define CONFIG_QDSP6 1 #endif -void (*msm_hw_reset_hook)(void); +#if defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) \ + || defined(CONFIG_ARCH_APQ8064) +#define CONFIG_DSPS 1 +#endif + +#if defined(CONFIG_ARCH_MSM8960) \ + || defined(CONFIG_ARCH_APQ8064) +#define CONFIG_WCNSS 1 +#define CONFIG_DSPS_SMSM 1 +#endif #define MODULE_NAME "msm_smd" +#define SMEM_VERSION 0x000B +#define SMD_VERSION 0x00020000 +#define SMSM_SNAPSHOT_CNT 64 +#define SMSM_SNAPSHOT_SIZE ((SMSM_NUM_ENTRIES + 1) * 4) + +uint32_t SMSM_NUM_ENTRIES = 8; +uint32_t SMSM_NUM_HOSTS = 3; + +/* Legacy SMSM interrupt notifications */ +#define LEGACY_MODEM_SMSM_MASK (SMSM_RESET | SMSM_INIT | SMSM_SMDINIT \ + | SMSM_RUN | SMSM_SYSTEM_DOWNLOAD) 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, + MSM_SMx_POWER_INFO = 1U << 4, }; -static int msm_smd_debug_mask; +struct smsm_shared_info { + uint32_t *state; + uint32_t *intr_mask; + uint32_t *intr_mux; +}; + +static struct smsm_shared_info smsm_info; +static struct kfifo smsm_snapshot_fifo; +static struct wake_lock smsm_snapshot_wakelock; +static int smsm_snapshot_count; +static DEFINE_SPINLOCK(smsm_snapshot_count_lock); + +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; + uint32_t intr_mask_set; + uint32_t intr_mask_clear; +}; + +struct interrupt_config_item { + /* must be initialized */ + irqreturn_t (*irq_handler)(int req, void *data); + /* outgoing interrupt config (set from platform data) */ + uint32_t out_bit_pos; + void __iomem *out_base; + uint32_t out_offset; +}; + +struct interrupt_config { + struct interrupt_config_item smd; + struct interrupt_config_item smsm; +}; + +static irqreturn_t smd_modem_irq_handler(int irq, void *data); +static irqreturn_t smsm_modem_irq_handler(int irq, void *data); +static irqreturn_t smd_dsp_irq_handler(int irq, void *data); +static irqreturn_t smsm_dsp_irq_handler(int irq, void *data); +static irqreturn_t smd_dsps_irq_handler(int irq, void *data); +static irqreturn_t smsm_dsps_irq_handler(int irq, void *data); +static irqreturn_t smd_wcnss_irq_handler(int irq, void *data); +static irqreturn_t smsm_wcnss_irq_handler(int irq, void *data); +static irqreturn_t smd_rpm_irq_handler(int irq, void *data); +static irqreturn_t smsm_irq_handler(int irq, void *data); + +static struct interrupt_config private_intr_config[NUM_SMD_SUBSYSTEMS] = { + [SMD_MODEM] = { + .smd.irq_handler = smd_modem_irq_handler, + .smsm.irq_handler = smsm_modem_irq_handler, + }, + [SMD_Q6] = { + .smd.irq_handler = smd_dsp_irq_handler, + .smsm.irq_handler = smsm_dsp_irq_handler, + }, + [SMD_DSPS] = { + .smd.irq_handler = smd_dsps_irq_handler, + .smsm.irq_handler = smsm_dsps_irq_handler, + }, + [SMD_WCNSS] = { + .smd.irq_handler = smd_wcnss_irq_handler, + .smsm.irq_handler = smsm_wcnss_irq_handler, + }, + [SMD_RPM] = { + .smd.irq_handler = smd_rpm_irq_handler, + .smsm.irq_handler = NULL, /* does not support smsm */ + }, +}; -struct shared_info { - int ready; - unsigned state; +struct smem_area { + void *phys_addr; + unsigned size; + void __iomem *virt_addr; }; +static uint32_t num_smem_areas; +static struct smem_area *smem_areas; +static void *smem_range_check(void *base, unsigned offset); + +struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS]; -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) +#define SMx_POWER_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMx_POWER_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) +#define SMx_POWER_INFO(x...) do { } while (0) +#endif + static unsigned last_heap_free = 0xffffffff; -static inline void notify_other_smsm(void) -{ - msm_a2m_int(5); -#ifdef CONFIG_QDSP6 - msm_a2m_int(8); +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_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMD_INT \ + (smd_write_intr(1 << 8, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1 << 5, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMSM_INT \ + (smd_write_intr(1 << 8, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2DSPS_SMSM_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_A2DSPS_SMSM_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#elif defined(CONFIG_ARCH_MSM9615) +#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 +#define MSM_TRIG_A2DSPS_SMSM_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#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_A2DSPS_SMSM_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#elif defined(CONFIG_ARCH_MSM7X01A) || defined(CONFIG_ARCH_MSM7x25) +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (0) * 4)) +#define MSM_TRIG_A2Q6_SMD_INT +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (5) * 4)) +#define MSM_TRIG_A2Q6_SMSM_INT +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2DSPS_SMSM_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#elif defined(CONFIG_ARCH_MSM7X27) || defined(CONFIG_ARCH_MSM7X27A) +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (0) * 4)) +#define MSM_TRIG_A2Q6_SMD_INT +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (5) * 4)) +#define MSM_TRIG_A2Q6_SMSM_INT +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2DSPS_SMSM_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#else /* use platform device / device tree configuration */ +#define MSM_TRIG_A2M_SMD_INT +#define MSM_TRIG_A2Q6_SMD_INT +#define MSM_TRIG_A2M_SMSM_INT +#define MSM_TRIG_A2Q6_SMSM_INT +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2DSPS_SMSM_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#endif + +/* + * stub out legacy macros if they are not being used so that the legacy + * code compiles even though it is not used + * + * these definitions should not be used in active code and will cause + * an early failure + */ +#ifndef INT_A9_M2A_0 +#define INT_A9_M2A_0 -1 +#endif +#ifndef INT_A9_M2A_5 +#define INT_A9_M2A_5 -1 +#endif +#ifndef INT_ADSP_A11 +#define INT_ADSP_A11 -1 #endif +#ifndef INT_ADSP_A11_SMSM +#define INT_ADSP_A11_SMSM -1 +#endif +#ifndef INT_DSPS_A11 +#define INT_DSPS_A11 -1 +#endif +#ifndef INT_DSPS_A11_SMSM +#define INT_DSPS_A11_SMSM -1 +#endif +#ifndef INT_WCNSS_A11 +#define INT_WCNSS_A11 -1 +#endif +#ifndef INT_WCNSS_A11_SMSM +#define INT_WCNSS_A11_SMSM -1 +#endif + +#define SMD_LOOPBACK_CID 100 + +#define SMEM_SPINLOCK_SMEM_ALLOC "S:3" +static remote_spinlock_t remote_spinlock; + +static LIST_HEAD(smd_ch_list_loopback); +static void smd_fake_irq_handler(unsigned long arg); +static void smsm_cb_snapshot(uint32_t use_wakelock); + +static struct workqueue_struct *smsm_cb_wq; +static void notify_smsm_cb_clients_worker(struct work_struct *work); +static DECLARE_WORK(smsm_cb_work, notify_smsm_cb_clients_worker); +static DEFINE_MUTEX(smsm_lock); +static struct smsm_state_info *smsm_states; +static int spinlocks_initialized; +static RAW_NOTIFIER_HEAD(smsm_driver_state_notifier_list); +static DEFINE_MUTEX(smsm_driver_state_notifier_lock); +static void smsm_driver_state_notify(uint32_t state, void *data); + +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 + */ + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 1) { + __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 inline void notify_modem_smd(void) { - msm_a2m_int(0); + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_MODEM].smd; + if (intr->out_base) { + ++interrupt_stats[SMD_MODEM].smd_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_MODEM].smd_out_hardcode_count; + MSM_TRIG_A2M_SMD_INT; + } } static inline void notify_dsp_smd(void) { - msm_a2m_int(8); + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_Q6].smd; + if (intr->out_base) { + ++interrupt_stats[SMD_Q6].smd_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_Q6].smd_out_hardcode_count; + MSM_TRIG_A2Q6_SMD_INT; + } +} + +static inline void notify_dsps_smd(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_DSPS].smd; + if (intr->out_base) { + ++interrupt_stats[SMD_DSPS].smd_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_DSPS].smd_out_hardcode_count; + MSM_TRIG_A2DSPS_SMD_INT; + } +} + +static inline void notify_wcnss_smd(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_WCNSS].smd; + wakeup_v1_riva(); + + if (intr->out_base) { + ++interrupt_stats[SMD_WCNSS].smd_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_WCNSS].smd_out_hardcode_count; + MSM_TRIG_A2WCNSS_SMD_INT; + } +} + +static inline void notify_rpm_smd(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_RPM].smd; + + if (intr->out_base) { + ++interrupt_stats[SMD_RPM].smd_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_modem_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_MODEM].smsm; + if (intr->out_base) { + ++interrupt_stats[SMD_MODEM].smsm_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_MODEM].smsm_out_hardcode_count; + MSM_TRIG_A2M_SMSM_INT; + } +} + +static inline void notify_dsp_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_Q6].smsm; + if (intr->out_base) { + ++interrupt_stats[SMD_Q6].smsm_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_Q6].smsm_out_hardcode_count; + MSM_TRIG_A2Q6_SMSM_INT; + } +} + +static inline void notify_dsps_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_DSPS].smsm; + if (intr->out_base) { + ++interrupt_stats[SMD_DSPS].smsm_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_DSPS].smsm_out_hardcode_count; + MSM_TRIG_A2DSPS_SMSM_INT; + } +} + +static inline void notify_wcnss_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_WCNSS].smsm; + wakeup_v1_riva(); + + if (intr->out_base) { + ++interrupt_stats[SMD_WCNSS].smsm_out_config_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } else { + ++interrupt_stats[SMD_WCNSS].smsm_out_hardcode_count; + MSM_TRIG_A2WCNSS_SMSM_INT; + } +} + +static void notify_other_smsm(uint32_t smsm_entry, uint32_t notify_mask) +{ + /* 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)) + notify_modem_smsm(); + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_Q6)) + & notify_mask)) { + uint32_t mux_val; + + if (cpu_is_qsd8x50() && 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)); + } + notify_dsp_smsm(); + } + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_WCNSS)) + & notify_mask)) { + notify_wcnss_smsm(); + } + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_DSPS)) + & notify_mask)) { + notify_dsps_smsm(); + } + + /* + * Notify local SMSM callback clients without wakelock since this + * code is used by power management during power-down/-up sequencing + * on DEM-based targets. Grabbing a wakelock in this case will + * abort the power-down sequencing. + */ + smsm_cb_snapshot(0); } -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,24 +625,376 @@ 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_shared_v2_word_access { + struct smd_half_channel_word_access ch0; + struct smd_half_channel_word_access ch1; +}; + +struct smd_channel { + volatile void *send; /* some variant of smd_half_channel */ + volatile void *recv; /* some variant of smd_half_channel */ + 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; + + /* + * private internal functions to access *send and *recv. + * never to be exported outside of smd + */ + struct smd_half_channel_access *half_ch; +}; + +struct edge_to_pid { + uint32_t local_pid; + uint32_t remote_pid; + char subsys_name[SMD_MAX_CH_NAME_LEN]; +}; + +/** + * Maps edge type to local and remote processor ID's. + */ +static struct edge_to_pid edge_to_pids[] = { + [SMD_APPS_MODEM] = {SMD_APPS, SMD_MODEM, "modem"}, + [SMD_APPS_QDSP] = {SMD_APPS, SMD_Q6, "q6"}, + [SMD_MODEM_QDSP] = {SMD_MODEM, SMD_Q6}, + [SMD_APPS_DSPS] = {SMD_APPS, SMD_DSPS, "dsps"}, + [SMD_MODEM_DSPS] = {SMD_MODEM, SMD_DSPS}, + [SMD_QDSP_DSPS] = {SMD_Q6, SMD_DSPS}, + [SMD_APPS_WCNSS] = {SMD_APPS, SMD_WCNSS, "wcnss"}, + [SMD_MODEM_WCNSS] = {SMD_MODEM, SMD_WCNSS}, + [SMD_QDSP_WCNSS] = {SMD_Q6, SMD_WCNSS}, + [SMD_DSPS_WCNSS] = {SMD_DSPS, SMD_WCNSS}, + [SMD_APPS_Q6FW] = {SMD_APPS, SMD_MODEM_Q6_FW}, + [SMD_MODEM_Q6FW] = {SMD_MODEM, SMD_MODEM_Q6_FW}, + [SMD_QDSP_Q6FW] = {SMD_Q6, SMD_MODEM_Q6_FW}, + [SMD_DSPS_Q6FW] = {SMD_DSPS, SMD_MODEM_Q6_FW}, + [SMD_WCNSS_Q6FW] = {SMD_WCNSS, SMD_MODEM_Q6_FW}, + [SMD_APPS_RPM] = {SMD_APPS, SMD_RPM}, + [SMD_MODEM_RPM] = {SMD_MODEM, SMD_RPM}, + [SMD_QDSP_RPM] = {SMD_Q6, SMD_RPM}, + [SMD_WCNSS_RPM] = {SMD_WCNSS, SMD_RPM}, +}; + +struct restart_notifier_block { + unsigned processor; + char *name; + struct notifier_block nb; +}; + +static int disable_smsm_reset_handshake; +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 LIST_HEAD(smd_ch_list_rpm); 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 >= ARRAY_SIZE(edge_to_pids) || + edge_to_pids[type].local_pid != SMD_APPS) + 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; +} + +/* + * Returns a pointer to the subsystem name or NULL if no + * subsystem name is available. + * + * @type - Edge definition + */ +const char *smd_edge_to_subsystem(uint32_t type) +{ + const char *subsys = NULL; + + if (type < ARRAY_SIZE(edge_to_pids)) { + subsys = edge_to_pids[type].subsys_name; + if (subsys[0] == 0x0) + subsys = NULL; + } + return subsys; +} +EXPORT_SYMBOL(smd_edge_to_subsystem); + +/* + * Returns a pointer to the subsystem name given the + * remote processor ID. + * + * @pid Remote processor ID + * @returns Pointer to subsystem name or NULL if not found + */ +const char *smd_pid_to_subsystem(uint32_t pid) +{ + const char *subsys = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(edge_to_pids); ++i) { + if (pid == edge_to_pids[i].remote_pid && + edge_to_pids[i].subsys_name[0] != 0x0 + ) { + subsys = edge_to_pids[i].subsys_name; + break; + } + } + + return subsys; +} +EXPORT_SYMBOL(smd_pid_to_subsystem); + +static void smd_reset_edge(struct smd_half_channel *ch, unsigned new_state) +{ + if (ch->state != SMD_SS_CLOSED) { + ch->state = new_state; + ch->fDSR = 0; + ch->fCTS = 0; + ch->fCD = 0; + ch->fSTATE = 1; + } +} + +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)) + smd_reset_edge(local_ch, new_state); + + /* + * ModemFW is in the same subsystem as ModemSW, but has + * separate SMD edges that need to be reset. + */ + if (pid == SMSM_MODEM && + pid_is_on_edge(shared2, type, SMD_MODEM_Q6_FW, + &local_ch, &remote_ch)) + smd_reset_edge(local_ch, new_state); + } +} + + +void smd_channel_reset(uint32_t restart_pid) +{ + struct smd_alloc_elm *shared; + 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; + } + + /* release any held spinlocks */ + remote_spin_release(&remote_spinlock, restart_pid); + remote_spin_release_all(restart_pid); + + /* reset SMSM entry */ + if (smsm_info.state) { + writel_relaxed(0, SMSM_STATE_ADDR(restart_pid)); + + /* restart SMSM init handshake */ + if (restart_pid == SMSM_MODEM) { + smsm_change_state(SMSM_APPS_STATE, + SMSM_INIT | SMSM_SMD_LOOPBACK | SMSM_RESET, + 0); + } + + /* notify SMSM processors */ + smsm_irq_handler(0, 0); + notify_modem_smsm(); + notify_dsp_smsm(); + notify_dsps_smsm(); + notify_wcnss_smsm(); + } + + /* 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) { - return (ch->recv->head - ch->recv->tail) & ch->fifo_mask; + return (ch->half_ch->get_head(ch->recv) - + ch->half_ch->get_tail(ch->recv)) & ch->fifo_mask; } /* how many bytes we are free to write */ static int smd_stream_write_avail(struct smd_channel *ch) { - return ch->fifo_mask - - ((ch->send->head - ch->send->tail) & ch->fifo_mask); + return ch->fifo_mask - ((ch->half_ch->get_head(ch->send) - + ch->half_ch->get_tail(ch->send)) & ch->fifo_mask); } static int smd_packet_read_avail(struct smd_channel *ch) @@ -179,15 +1017,16 @@ 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->half_ch->get_state(ch->recv) == SMD_SS_OPENED || + ch->half_ch->get_state(ch->recv) == SMD_SS_FLUSHING) + && (ch->half_ch->get_state(ch->send) == SMD_SS_OPENED); } /* provide a pointer and length to readable data in the fifo */ static unsigned ch_read_buffer(struct smd_channel *ch, void **ptr) { - unsigned head = ch->recv->head; - unsigned tail = ch->recv->tail; + unsigned head = ch->half_ch->get_head(ch->recv); + unsigned tail = ch->half_ch->get_tail(ch->recv); *ptr = (void *) (ch->recv_data + tail); if (tail <= head) @@ -196,24 +1035,32 @@ 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->half_ch->get_fBLOCKREADINTR(ch->recv); +} + /* 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; - ch->send->fTAIL = 1; + ch->half_ch->set_tail(ch->recv, + (ch->half_ch->get_tail(ch->recv) + count) & ch->fifo_mask); + wmb(); + ch->half_ch->set_fTAIL(ch->send, 1); } /* basic read interface to ch_read_{buffer,done} used * 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 +1069,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,24 +1102,25 @@ 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 */ static unsigned ch_write_buffer(struct smd_channel *ch, void **ptr) { - unsigned head = ch->send->head; - unsigned tail = ch->send->tail; + unsigned head = ch->half_ch->get_head(ch->send); + unsigned tail = ch->half_ch->get_tail(ch->send); *ptr = (void *) (ch->send_data + head); if (head < tail) { @@ -280,23 +1139,25 @@ static unsigned ch_write_buffer(struct smd_channel *ch, void **ptr) 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; - ch->send->fHEAD = 1; + ch->half_ch->set_head(ch->send, + (ch->half_ch->get_head(ch->send) + count) & ch->fifo_mask); + wmb(); + ch->half_ch->set_fHEAD(ch->send, 1); } static void ch_set_state(struct smd_channel *ch, unsigned n) { if (n == SMD_SS_OPENED) { - ch->send->fDSR = 1; - ch->send->fCTS = 1; - ch->send->fCD = 1; + ch->half_ch->set_fDSR(ch->send, 1); + ch->half_ch->set_fCTS(ch->send, 1); + ch->half_ch->set_fCD(ch->send, 1); } else { - ch->send->fDSR = 0; - ch->send->fCTS = 0; - ch->send->fCD = 0; + ch->half_ch->set_fDSR(ch->send, 0); + ch->half_ch->set_fCTS(ch->send, 0); + ch->half_ch->set_fCD(ch->send, 0); } - ch->send->state = n; - ch->send->fSTATE = 1; + ch->half_ch->set_state(ch->send, n); + ch->half_ch->set_fSTATE(ch->send, 1); ch->notify_other_cpu(); } @@ -314,84 +1175,169 @@ 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->half_ch->get_state(ch->send) == SMD_SS_CLOSING || + ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) { + ch->half_ch->set_tail(ch->recv, 0); + ch->half_ch->set_head(ch->send, 0); + ch->half_ch->set_fBLOCKREADINTR(ch->send, 0); + ch_set_state(ch, SMD_SS_OPENING); + } + break; case SMD_SS_OPENED: - if (ch->send->state != SMD_SS_OPENED) + if (ch->half_ch->get_state(ch->send) == 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); - } -} - -static void handle_smd_irq(struct list_head *list, void (*notify)(void)) + break; + case SMD_SS_CLOSED: + if (ch->half_ch->get_state(ch->send) == SMD_SS_OPENED) { + ch_set_state(ch, SMD_SS_CLOSING); + ch->current_packet = 0; + ch->pending_pkt_sz = 0; + ch->notify(ch->priv, SMD_EVENT_CLOSE); + } + break; + case SMD_SS_CLOSING: + if (ch->half_ch->get_state(ch->send) == 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->half_ch->get_fSTATE(ch->recv)) + ch->half_ch->set_fSTATE(ch->recv, 0); + tmp = ch->half_ch->get_state(ch->recv); + 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; + if (ch->half_ch->get_fHEAD(ch->recv)) { + ch->half_ch->set_fHEAD(ch->recv, 0); ch_flags |= 1; - do_notify |= 1; } - if (ch->recv->fTAIL) { - ch->recv->fTAIL = 0; + if (ch->half_ch->get_fTAIL(ch->recv)) { + ch->half_ch->set_fTAIL(ch->recv, 0); ch_flags |= 2; - do_notify |= 1; } - if (ch->recv->fSTATE) { - ch->recv->fSTATE = 0; + if (ch->half_ch->get_fSTATE(ch->recv)) { + ch->half_ch->set_fSTATE(ch->recv, 0); ch_flags |= 4; - do_notify |= 1; } } - tmp = ch->recv->state; - if (tmp != ch->last_state) + tmp = ch->half_ch->get_state(ch->recv); + if (tmp != ch->last_state) { + SMx_POWER_INFO("SMD ch%d '%s' State change %d->%d\n", + ch->n, ch->name, ch->last_state, tmp); smd_state_change(ch, ch->last_state, tmp); - if (ch_flags) { + state_change = 1; + } + if (ch_flags & 0x3) { ch->update_state(ch); + SMx_POWER_INFO("SMD ch%d '%s' Data event r%d/w%d\n", + ch->n, ch->name, + ch->read_avail(ch), + ch->fifo_size - ch->write_avail(ch)); ch->notify(ch->priv, SMD_EVENT_DATA); } + if (ch_flags & 0x4 && !state_change) { + SMx_POWER_INFO("SMD ch%d '%s' State update\n", + ch->n, ch->name); + ch->notify(ch->priv, SMD_EVENT_STATUS); + } } - if (do_notify) - notify(); spin_unlock_irqrestore(&smd_lock, flags); do_smd_probe(); } static irqreturn_t smd_modem_irq_handler(int irq, void *data) { + SMx_POWER_INFO("SMD Int Modem->Apps\n"); + ++interrupt_stats[SMD_MODEM].smd_in_count; handle_smd_irq(&smd_ch_list_modem, notify_modem_smd); + handle_smd_irq_closing_list(); return IRQ_HANDLED; } -#if defined(CONFIG_QDSP6) static irqreturn_t smd_dsp_irq_handler(int irq, void *data) { + SMx_POWER_INFO("SMD Int LPASS->Apps\n"); + ++interrupt_stats[SMD_Q6].smd_in_count; handle_smd_irq(&smd_ch_list_dsp, notify_dsp_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +static irqreturn_t smd_dsps_irq_handler(int irq, void *data) +{ + SMx_POWER_INFO("SMD Int DSPS->Apps\n"); + ++interrupt_stats[SMD_DSPS].smd_in_count; + handle_smd_irq(&smd_ch_list_dsps, notify_dsps_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +static irqreturn_t smd_wcnss_irq_handler(int irq, void *data) +{ + SMx_POWER_INFO("SMD Int WCNSS->Apps\n"); + ++interrupt_stats[SMD_WCNSS].smd_in_count; + handle_smd_irq(&smd_ch_list_wcnss, notify_wcnss_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +static irqreturn_t smd_rpm_irq_handler(int irq, void *data) +{ + SMx_POWER_INFO("SMD Int RPM->Apps\n"); + ++interrupt_stats[SMD_RPM].smd_in_count; + handle_smd_irq(&smd_ch_list_rpm, notify_rpm_smd); + handle_smd_irq_closing_list(); return IRQ_HANDLED; } -#endif 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(&smd_ch_list_rpm, notify_rpm_smd); + handle_smd_irq_closing_list(); } static DECLARE_TASKLET(smd_fake_irq_tasklet, smd_fake_irq_handler, 0); @@ -399,9 +1345,11 @@ static DECLARE_TASKLET(smd_fake_irq_tasklet, smd_fake_irq_handler, 0); static inline int smd_need_int(struct smd_channel *ch) { if (ch_is_open(ch)) { - if (ch->recv->fHEAD || ch->recv->fTAIL || ch->recv->fSTATE) + if (ch->half_ch->get_fHEAD(ch->recv) || + ch->half_ch->get_fTAIL(ch->recv) || + ch->half_ch->get_fSTATE(ch->recv)) return 1; - if (ch->recv->state != ch->last_state) + if (ch->half_ch->get_state(ch->recv) != ch->last_state) return 1; } return 0; @@ -426,68 +1374,82 @@ 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)) + if (!ch_is_open(ch)) { + len = orig_len; 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 +1457,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 +1481,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 +1526,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,7 +1539,107 @@ 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) +{ + int r; + + if (len < 0) + return -EINVAL; + + if (len > ch->current_packet) + len = ch->current_packet; + + r = ch_read(ch, data, len, user_buf); + if (r > 0) + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(); + + ch->current_packet -= r; + update_packet_state(ch); + + return r; +} + +#if (defined(CONFIG_MSM_SMD_PKG4) || defined(CONFIG_MSM_SMD_PKG3)) +static int smd_alloc_v2(struct smd_channel *ch) +{ + void *buffer; + unsigned buffer_sz; + + if (is_word_access_ch(ch->type)) { + struct smd_shared_v2_word_access *shared2; + shared2 = smem_alloc(SMEM_SMD_BASE_ID + ch->n, + sizeof(*shared2)); + if (!shared2) { + SMD_INFO("smem_alloc failed ch=%d\n", ch->n); + return -EINVAL; + } + ch->send = &shared2->ch0; + ch->recv = &shared2->ch1; + } else { + struct smd_shared_v2 *shared2; + shared2 = smem_alloc(SMEM_SMD_BASE_ID + ch->n, + sizeof(*shared2)); + if (!shared2) { + SMD_INFO("smem_alloc failed ch=%d\n", ch->n); + return -EINVAL; + } + ch->send = &shared2->ch0; + ch->recv = &shared2->ch1; + } + ch->half_ch = get_half_ch_funcs(ch->type); + + buffer = smem_get_entry(SMEM_SMD_FIFO_BASE_ID + ch->n, &buffer_sz); + if (!buffer) { + SMD_INFO("smem_get_entry failed\n"); + return -EINVAL; + } + + /* buffer must be a power-of-two size */ + if (buffer_sz & (buffer_sz - 1)) { + SMD_INFO("Buffer size: %u not power of two\n", buffer_sz); + return -EINVAL; + } + buffer_sz /= 2; + 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) +{ + return -EINVAL; +} + +#else /* define v1 for older targets */ +static int smd_alloc_v2(struct smd_channel *ch) +{ + return -EINVAL; +} + +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 -EINVAL; + } + ch->send = &shared1->ch0; + ch->recv = &shared1->ch1; + ch->send_data = shared1->data0; + ch->recv_data = shared1->data1; + ch->fifo_size = SMD_BUF_SIZE; + ch->half_ch = get_half_ch_funcs(ch->type); + return 0; +} + +#endif + +static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm) { struct smd_channel *ch; @@ -565,102 +1648,157 @@ static int smd_alloc_channel(const char *name, uint32_t cid, uint32_t type) pr_err("smd_alloc_channel() out of memory\n"); return -1; } - ch->n = cid; + ch->n = alloc_elm->cid; + ch->type = SMD_CHANNEL_TYPE(alloc_elm->type); - if (_smd_alloc_channel(ch)) { + if (smd_alloc_v2(ch) && smd_alloc_v1(ch)) { kfree(ch); return -1; } ch->fifo_mask = ch->fifo_size - 1; - ch->type = type; - if ((type & SMD_TYPE_MASK) == SMD_TYPE_APPS_MODEM) + /* probe_worker guarentees ch->type will be a valid type */ + if (ch->type == SMD_APPS_MODEM) ch->notify_other_cpu = notify_modem_smd; - else + else if (ch->type == SMD_APPS_QDSP) ch->notify_other_cpu = notify_dsp_smd; - - if (smd_is_packet(cid, type)) { + else if (ch->type == SMD_APPS_DSPS) + ch->notify_other_cpu = notify_dsps_smd; + else if (ch->type == SMD_APPS_WCNSS) + ch->notify_other_cpu = notify_wcnss_smd; + else if (ch->type == SMD_APPS_RPM) + ch->notify_other_cpu = notify_rpm_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; + + mutex_lock(&smd_creation_mutex); + 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); + list_add(&ch->ch_list, &smd_ch_closed_list); + ch->notify(ch->priv, SMD_EVENT_REOPEN_READY); + ch->notify = do_nothing_notify; + } + spin_unlock_irqrestore(&smd_lock, flags); + mutex_unlock(&smd_creation_mutex); +} + +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,20 +1809,49 @@ 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); - if (!ch) - return -ENODEV; + SMD_DBG("smd_open('%s', %p, %p)\n", name, priv, notify); + + ch = smd_get_channel(name, edge); + if (!ch) { + /* check closing list for port */ + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry(ch, &smd_ch_closing_list, ch_list) { + if (!strncmp(name, ch->name, 20) && + (edge == ch->type)) { + /* channel exists, but is being closed */ + spin_unlock_irqrestore(&smd_lock, flags); + return -EAGAIN; + } + } + + /* check closing workqueue list for port */ + list_for_each_entry(ch, &smd_ch_to_close_list, ch_list) { + if (!strncmp(name, ch->name, 20) && + (edge == ch->type)) { + /* channel exists, but is being closed */ + spin_unlock_irqrestore(&smd_lock, flags); + return -EAGAIN; + } + } + spin_unlock_irqrestore(&smd_lock, flags); + + /* one final check to handle closing->closed race condition */ + ch = smd_get_channel(name, edge); + if (!ch) + return -ENODEV; + } if (notify == 0) notify = do_nothing_notify; @@ -694,34 +1861,51 @@ 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->half_ch->set_state(ch->send, SMD_SS_OPENED); + ch->half_ch->set_fDSR(ch->send, 1); + ch->half_ch->set_fCTS(ch->send, 1); + ch->half_ch->set_fCD(ch->send, 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 if (SMD_CHANNEL_TYPE(ch->type) == SMD_APPS_RPM) + list_add(&ch->ch_list, &smd_ch_list_rpm); + 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,96 +1914,444 @@ 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); - - mutex_lock(&smd_creation_mutex); - list_add(&ch->ch_list, &smd_ch_closed_list); - mutex_unlock(&smd_creation_mutex); + if (ch->n == SMD_LOOPBACK_CID) { + ch->half_ch->set_fDSR(ch->send, 0); + ch->half_ch->set_fCTS(ch->send, 0); + ch->half_ch->set_fCD(ch->send, 0); + ch->half_ch->set_state(ch->send, SMD_SS_CLOSED); + } else + ch_set_state(ch, SMD_SS_CLOSED); + + if (ch->half_ch->get_state(ch->recv) == 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_read(smd_channel_t *ch, void *data, int len) +int smd_write_start(smd_channel_t *ch, int len) { - return ch->read(ch, data, len); -} + int ret; + unsigned hdr[5]; -int smd_write(smd_channel_t *ch, const void *data, int len) -{ - return ch->write(ch, data, len); -} + 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; + } -int smd_write_atomic(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; -} + 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; -int smd_read_avail(smd_channel_t *ch) -{ - return ch->read_avail(ch); -} + 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; + } -int smd_write_avail(smd_channel_t *ch) -{ - return ch->write_avail(ch); -} + hdr[0] = len; + hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0; -int smd_wait_until_readable(smd_channel_t *ch, int bytes) -{ - return -1; -} -int smd_wait_until_writable(smd_channel_t *ch, int bytes) -{ - return -1; + 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_cur_packet_size(smd_channel_t *ch) +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) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->read(ch, data, len, 0); +} +EXPORT_SYMBOL(smd_read); + +int smd_read_user_buffer(smd_channel_t *ch, void *data, int len) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + 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) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + 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) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->pending_pkt_sz ? -EBUSY : ch->write(ch, data, len, 0); +} +EXPORT_SYMBOL(smd_write); + +int smd_write_user_buffer(smd_channel_t *ch, const void *data, int len) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + 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) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->read_avail(ch); +} +EXPORT_SYMBOL(smd_read_avail); + +int smd_write_avail(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->write_avail(ch); +} +EXPORT_SYMBOL(smd_write_avail); + +void smd_enable_read_intr(smd_channel_t *ch) +{ + if (ch) + ch->half_ch->set_fBLOCKREADINTR(ch->send, 0); +} +EXPORT_SYMBOL(smd_enable_read_intr); + +void smd_disable_read_intr(smd_channel_t *ch) +{ + if (ch) + ch->half_ch->set_fBLOCKREADINTR(ch->send, 1); +} +EXPORT_SYMBOL(smd_disable_read_intr); + +int smd_wait_until_readable(smd_channel_t *ch, int bytes) +{ + return -1; +} + +int smd_wait_until_writable(smd_channel_t *ch, int bytes) +{ + return -1; +} + +int smd_cur_packet_size(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + return ch->current_packet; } +EXPORT_SYMBOL(smd_cur_packet_size); + +int smd_tiocmget(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return (ch->half_ch->get_fDSR(ch->recv) ? TIOCM_DSR : 0) | + (ch->half_ch->get_fCTS(ch->recv) ? TIOCM_CTS : 0) | + (ch->half_ch->get_fCD(ch->recv) ? TIOCM_CD : 0) | + (ch->half_ch->get_fRI(ch->recv) ? TIOCM_RI : 0) | + (ch->half_ch->get_fCTS(ch->send) ? TIOCM_RTS : 0) | + (ch->half_ch->get_fDSR(ch->send) ? TIOCM_DTR : 0); +} +EXPORT_SYMBOL(smd_tiocmget); + +/* this api will be called while holding smd_lock */ +int +smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + if (set & TIOCM_DTR) + ch->half_ch->set_fDSR(ch->send, 1); + + if (set & TIOCM_RTS) + ch->half_ch->set_fCTS(ch->send, 1); + + if (clear & TIOCM_DTR) + ch->half_ch->set_fDSR(ch->send, 0); + + if (clear & TIOCM_RTS) + ch->half_ch->set_fCTS(ch->send, 0); + + ch->half_ch->set_fSTATE(ch->send, 1); + barrier(); + ch->notify_other_cpu(); + + return 0; +} +EXPORT_SYMBOL(smd_tiocmset_from_cb); + +int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + unsigned long flags; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&smd_lock, flags); + smd_tiocmset_from_cb(ch, set, clear); + spin_unlock_irqrestore(&smd_lock, flags); + + return 0; +} +EXPORT_SYMBOL(smd_tiocmset); + +int smd_is_pkt_avail(smd_channel_t *ch) +{ + if (!ch || !ch->is_pkt_ch) + return -EINVAL; + + if (ch->current_packet) + return 1; + + update_packet_state(ch); + + return ch->current_packet ? 1 : 0; +} +EXPORT_SYMBOL(smd_is_pkt_avail); + + +/* -------------------------------------------------------------------------- */ +/* + * Shared Memory Range Check + * + * Takes a physical address and an offset and checks if the resulting physical + * address would fit into one of the aux smem regions. If so, returns the + * corresponding virtual address. Otherwise returns NULL. Expects the array + * of smem regions to be in ascending physical address order. + * + * @base: physical base address to check + * @offset: offset from the base to get the final address + */ +static void *smem_range_check(void *base, unsigned offset) +{ + int i; + void *phys_addr; + unsigned size; -/* ------------------------------------------------------------------------- */ + for (i = 0; i < num_smem_areas; ++i) { + phys_addr = smem_areas[i].phys_addr; + size = smem_areas[i].size; + if (base < phys_addr) + return NULL; + if (base > phys_addr + size) + continue; + if (base >= phys_addr && base + offset < phys_addr + size) + return smem_areas[i].virt_addr + offset; + } + return NULL; +} + +/* 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); + +/* smem_alloc2 returns the pointer to smem item. If it is not allocated, + * it allocates it and then returns the pointer to it. + */ +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; +} +EXPORT_SYMBOL(smem_alloc2); -void *smem_item(unsigned id, unsigned *size) +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; + int use_spinlocks = spinlocks_initialized; + void *ret = 0; + unsigned long flags = 0; if (id >= SMEM_NUM_ITEMS) - return 0; + return ret; + if (use_spinlocks) + remote_spin_lock_irqsave(&remote_spinlock, flags); + /* toc is in device memory and cannot be speculatively accessed */ if (toc[id].allocated) { *size = toc[id].size; - return (void *) (MSM_SHARED_RAM_BASE + toc[id].offset); + barrier(); + if (!(toc[id].reserved & BASE_ADDR_MASK)) + ret = (void *) (MSM_SHARED_RAM_BASE + toc[id].offset); + else + ret = smem_range_check( + (void *)(toc[id].reserved & BASE_ADDR_MASK), + toc[id].offset); } else { *size = 0; } + if (use_spinlocks) + remote_spin_unlock_irqrestore(&remote_spinlock, flags); - return 0; + return ret; } +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 +2364,1116 @@ void *smem_find(unsigned id, unsigned size_in) return ptr; } +EXPORT_SYMBOL(smem_find); + +static int smsm_cb_init(void) +{ + 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; + } + + smsm_cb_wq = create_singlethread_workqueue("smsm_cb_wq"); + if (!smsm_cb_wq) { + pr_err("%s: smsm_cb_wq creation failed\n", __func__); + kfree(smsm_states); + return -EFAULT; + } + + mutex_lock(&smsm_lock); + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + state_info = &smsm_states[n]; + state_info->last_value = __raw_readl(SMSM_STATE_ADDR(n)); + state_info->intr_mask_set = 0x0; + state_info->intr_mask_clear = 0x0; + INIT_LIST_HEAD(&state_info->callbacks); + } + mutex_unlock(&smsm_lock); + + 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; + } + spinlocks_initialized = 1; + + 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; + } + + i = kfifo_alloc(&smsm_snapshot_fifo, + sizeof(uint32_t) * SMSM_NUM_ENTRIES * SMSM_SNAPSHOT_CNT, + GFP_KERNEL); + if (i) { + pr_err("%s: SMSM state fifo alloc failed %d\n", __func__, i); + return i; + } + wake_lock_init(&smsm_snapshot_wakelock, WAKE_LOCK_SUSPEND, + "smsm_snapshot"); + + 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(0x0, + SMSM_INTR_MASK_ADDR(i, SMSM_APPS)); + + /* Configure legacy modem bits */ + __raw_writel(LEGACY_MODEM_SMSM_MASK, + SMSM_INTR_MASK_ADDR(SMSM_MODEM_STATE, + 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(); + smsm_driver_state_notify(SMSM_INIT, NULL); + 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 void smsm_cb_snapshot(uint32_t use_wakelock) +{ + int n; + uint32_t new_state; + unsigned long flags; + int ret; + + ret = kfifo_avail(&smsm_snapshot_fifo); + if (ret < SMSM_SNAPSHOT_SIZE) { + pr_err("%s: SMSM snapshot full %d\n", __func__, ret); + return; + } + + /* + * To avoid a race condition with notify_smsm_cb_clients_worker, the + * following sequence must be followed: + * 1) increment snapshot count + * 2) insert data into FIFO + * + * Potentially in parallel, the worker: + * a) verifies >= 1 snapshots are in FIFO + * b) processes snapshot + * c) decrements reference count + * + * This order ensures that 1 will always occur before abc. + */ + if (use_wakelock) { + spin_lock_irqsave(&smsm_snapshot_count_lock, flags); + if (smsm_snapshot_count == 0) { + SMx_POWER_INFO("SMSM snapshot wake lock\n"); + wake_lock(&smsm_snapshot_wakelock); + } + ++smsm_snapshot_count; + spin_unlock_irqrestore(&smsm_snapshot_count_lock, flags); + } + + /* queue state entries */ + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + new_state = __raw_readl(SMSM_STATE_ADDR(n)); + + ret = kfifo_in(&smsm_snapshot_fifo, + &new_state, sizeof(new_state)); + if (ret != sizeof(new_state)) { + pr_err("%s: SMSM snapshot failure %d\n", __func__, ret); + goto restore_snapshot_count; + } + } + + /* queue wakelock usage flag */ + ret = kfifo_in(&smsm_snapshot_fifo, + &use_wakelock, sizeof(use_wakelock)); + if (ret != sizeof(use_wakelock)) { + pr_err("%s: SMSM snapshot failure %d\n", __func__, ret); + goto restore_snapshot_count; + } + + queue_work(smsm_cb_wq, &smsm_cb_work); + return; + +restore_snapshot_count: + if (use_wakelock) { + spin_lock_irqsave(&smsm_snapshot_count_lock, flags); + if (smsm_snapshot_count) { + --smsm_snapshot_count; + if (smsm_snapshot_count == 0) { + SMx_POWER_INFO("SMSM snapshot wake unlock\n"); + wake_unlock(&smsm_snapshot_wakelock); + } + } else { + pr_err("%s: invalid snapshot count\n", __func__); + } + spin_unlock_irqrestore(&smsm_snapshot_count_lock, flags); + } +} static irqreturn_t smsm_irq_handler(int irq, void *data) { unsigned long flags; - unsigned apps, modm; + + if (irq == INT_ADSP_A11_SMSM) { + uint32_t mux_val; + static uint32_t prev_smem_q6_apps_smsm; + + if (smsm_info.intr_mux && cpu_is_qsd8x50()) { + 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; + } + + spin_lock_irqsave(&smem_lock, flags); + smsm_cb_snapshot(1); + spin_unlock_irqrestore(&smem_lock, flags); + return IRQ_HANDLED; + } 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)); + + old_apps = apps = __raw_readl(SMSM_STATE_ADDR(SMSM_APPS_STATE)); + + 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. */ + if (!disable_smsm_reset_handshake) + apps &= ~SMSM_RESET; + /* 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) { + pr_err("\nSMSM: Modem SMSM state changed to SMSM_RESET."); + if (!disable_smsm_reset_handshake) { + apps |= SMSM_RESET; + flush_cache_all(); + outer_flush_all(); + } + modem_queue_start_reset_notify(); - apps = raw_smsm_get_state(SMSM_STATE_APPS); - modm = raw_smsm_get_state(SMSM_STATE_MODEM); + } else if (modm & SMSM_INIT) { + if (!(apps & SMSM_INIT)) { + apps |= SMSM_INIT; + modem_queue_smsm_init_notify(); + } - if (msm_smd_debug_mask & MSM_SMSM_DEBUG) - pr_info("\n", apps, modm); - if (modm & SMSM_RESET) - handle_modem_crash(); + 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(); + } - do_smd_probe(); + 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)); + } + smsm_cb_snapshot(1); + } 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) +static irqreturn_t smsm_modem_irq_handler(int irq, void *data) +{ + SMx_POWER_INFO("SMSM Int Modem->Apps\n"); + ++interrupt_stats[SMD_MODEM].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +static irqreturn_t smsm_dsp_irq_handler(int irq, void *data) +{ + SMx_POWER_INFO("SMSM Int LPASS->Apps\n"); + ++interrupt_stats[SMD_Q6].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +static irqreturn_t smsm_dsps_irq_handler(int irq, void *data) { - unsigned long addr = smd_info.state + item * 4; + SMx_POWER_INFO("SMSM Int DSPS->Apps\n"); + ++interrupt_stats[SMD_DSPS].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +static irqreturn_t smsm_wcnss_irq_handler(int irq, void *data) +{ + SMx_POWER_INFO("SMSM Int WCNSS->Apps\n"); + ++interrupt_stats[SMD_WCNSS].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +/* + * Changes the global interrupt mask. The set and clear masks are re-applied + * every time the global interrupt mask is updated for callback registration + * and de-registration. + * + * The clear mask is applied first, so if a bit is set to 1 in both the clear + * mask and the set mask, the result will be that the interrupt is set. + * + * @smsm_entry SMSM entry to change + * @clear_mask 1 = clear bit, 0 = no-op + * @set_mask 1 = set bit, 0 = no-op + * + * @returns 0 for success, < 0 for error + */ +int smsm_change_intr_mask(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) +{ + 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); + smsm_states[smsm_entry].intr_mask_clear = clear_mask; + smsm_states[smsm_entry].intr_mask_set = set_mask; - 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); + +uint32_t smsm_get_state(uint32_t smsm_entry) +{ + uint32_t rv = 0; -#ifdef CONFIG_ARCH_MSM_SCORPION + /* 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); -int smsm_set_sleep_duration(uint32_t delay) +/** + * Performs SMSM callback client notifiction. + */ +void notify_smsm_cb_clients_worker(struct work_struct *work) { - struct msm_dem_slave_data *ptr; + struct smsm_state_cb_info *cb_info; + struct smsm_state_info *state_info; + int n; + uint32_t new_state; + uint32_t state_changes; + uint32_t use_wakelock; + int ret; + unsigned long flags; - ptr = smem_find(SMEM_APPS_DEM_SLAVE_DATA, sizeof(*ptr)); - if (ptr == NULL) { - pr_err("smsm_set_sleep_duration \n"); - return -EIO; + if (!smd_initialized) + return; + + while (kfifo_len(&smsm_snapshot_fifo) >= SMSM_SNAPSHOT_SIZE) { + mutex_lock(&smsm_lock); + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + state_info = &smsm_states[n]; + + ret = kfifo_out(&smsm_snapshot_fifo, &new_state, + sizeof(new_state)); + if (ret != sizeof(new_state)) { + pr_err("%s: snapshot underflow %d\n", + __func__, ret); + mutex_unlock(&smsm_lock); + return; + } + + state_changes = state_info->last_value ^ new_state; + if (state_changes) { + SMx_POWER_INFO("SMSM Change %d: %08x->%08x\n", + n, 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; + } + } + + /* read wakelock flag */ + ret = kfifo_out(&smsm_snapshot_fifo, &use_wakelock, + sizeof(use_wakelock)); + if (ret != sizeof(use_wakelock)) { + pr_err("%s: snapshot underflow %d\n", + __func__, ret); + mutex_unlock(&smsm_lock); + return; + } + mutex_unlock(&smsm_lock); + + if (use_wakelock) { + spin_lock_irqsave(&smsm_snapshot_count_lock, flags); + if (smsm_snapshot_count) { + --smsm_snapshot_count; + if (smsm_snapshot_count == 0) { + SMx_POWER_INFO("SMSM snapshot" + " wake unlock\n"); + wake_unlock(&smsm_snapshot_wakelock); + } + } else { + pr_err("%s: invalid snapshot count\n", + __func__); + } + spin_unlock_irqrestore(&smsm_snapshot_count_lock, + flags); + } } - 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; } -#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; + struct smsm_state_info *state; + struct smsm_state_cb_info *cb_info; + struct smsm_state_cb_info *cb_found = 0; + uint32_t new_mask = 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; + + mutex_lock(&smsm_lock); + + 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; + + state = &smsm_states[smsm_entry]; + list_for_each_entry(cb_info, + &state->callbacks, cb_list) { + if (!ret && (cb_info->notify == notify) && + (cb_info->data == data)) { + cb_info->mask |= mask; + cb_found = cb_info; + ret = 1; + } + new_mask |= cb_info->mask; + } + + 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, + &state->callbacks); + new_mask |= mask; + } + + /* update interrupt notification mask */ + if (smsm_entry == SMSM_MODEM_STATE) + new_mask |= LEGACY_MODEM_SMSM_MASK; + + if (smsm_info.intr_mask) { + unsigned long flags; + + spin_lock_irqsave(&smem_lock, flags); + new_mask = (new_mask & ~state->intr_mask_clear) + | state->intr_mask_set; + __raw_writel(new_mask, + SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); + } + +cleanup: + mutex_unlock(&smsm_lock); + 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; + struct smsm_state_cb_info *cb_info; + struct smsm_state_cb_info *cb_tmp; + struct smsm_state_info *state; + uint32_t new_mask = 0; + int ret = 0; - /* 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; - break; + if (smsm_entry >= SMSM_NUM_ENTRIES) + return -EINVAL; + + mutex_lock(&smsm_lock); + + if (!smsm_states) { + /* smsm not yet initialized */ + mutex_unlock(&smsm_lock); + return -ENODEV; + } + + state = &smsm_states[smsm_entry]; + list_for_each_entry_safe(cb_info, cb_tmp, + &state->callbacks, cb_list) { + if (!ret && (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; + continue; + } } + new_mask |= cb_info->mask; + } + + /* update interrupt notification mask */ + if (smsm_entry == SMSM_MODEM_STATE) + new_mask |= LEGACY_MODEM_SMSM_MASK; + + if (smsm_info.intr_mask) { + unsigned long flags; + + spin_lock_irqsave(&smem_lock, flags); + new_mask = (new_mask & ~state->intr_mask_clear) + | state->intr_mask_set; + __raw_writel(new_mask, + SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); } - smd_info.ready = 1; + mutex_unlock(&smsm_lock); + return ret; +} +EXPORT_SYMBOL(smsm_state_cb_deregister); + +int smsm_driver_state_notifier_register(struct notifier_block *nb) +{ + int ret; + if (!nb) + return -EINVAL; + mutex_lock(&smsm_driver_state_notifier_lock); + ret = raw_notifier_chain_register(&smsm_driver_state_notifier_list, nb); + mutex_unlock(&smsm_driver_state_notifier_lock); + return ret; +} +EXPORT_SYMBOL(smsm_driver_state_notifier_register); + +int smsm_driver_state_notifier_unregister(struct notifier_block *nb) +{ + int ret; + if (!nb) + return -EINVAL; + mutex_lock(&smsm_driver_state_notifier_lock); + ret = raw_notifier_chain_unregister(&smsm_driver_state_notifier_list, + nb); + mutex_unlock(&smsm_driver_state_notifier_lock); + return ret; +} +EXPORT_SYMBOL(smsm_driver_state_notifier_unregister); + +static void smsm_driver_state_notify(uint32_t state, void *data) +{ + mutex_lock(&smsm_driver_state_notifier_lock); + raw_notifier_call_chain(&smsm_driver_state_notifier_list, + state, data); + mutex_unlock(&smsm_driver_state_notifier_lock); +} + +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); + r = request_irq(INT_A9_M2A_5, smsm_modem_irq_handler, + 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_dsp_irq_handler, + flags, "smsm_dev", smsm_dsp_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 - /* check for any SMD channels that may already exist */ - do_smd_probe(); +#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_dsp_irq_handler); + return r; + } - /* 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 = enable_irq_wake(INT_DSPS_A11); + if (r < 0) + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_ADSP_A11\n"); #endif +#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_dsp_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"); + + r = request_irq(INT_WCNSS_A11_SMSM, smsm_wcnss_irq_handler, + flags, "smsm_dev", smsm_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_dsp_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 + +#if defined(CONFIG_DSPS_SMSM) + r = request_irq(INT_DSPS_A11_SMSM, smsm_dsps_irq_handler, + flags, "smsm_dev", smsm_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_dsp_irq_handler); + free_irq(INT_DSPS_A11, smd_dsps_irq_handler); + free_irq(INT_WCNSS_A11, smd_wcnss_irq_handler); + free_irq(INT_WCNSS_A11_SMSM, smsm_wcnss_irq_handler); + return r; + } + + r = enable_irq_wake(INT_DSPS_A11_SMSM); + if (r < 0) + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_DSPS_A11_SMSM\n"); +#endif + SMD_INFO("smd_core_init() done\n"); + + return 0; +} + +static int intr_init(struct interrupt_config_item *private_irq, + struct smd_irq_config *platform_irq, + struct platform_device *pdev + ) +{ + int irq_id; + int ret; + int ret_wake; + + private_irq->out_bit_pos = platform_irq->out_bit_pos; + private_irq->out_offset = platform_irq->out_offset; + private_irq->out_base = platform_irq->out_base; + + irq_id = platform_get_irq_byname( + pdev, + platform_irq->irq_name + ); + SMD_DBG("smd: %s: register irq: %s id: %d\n", __func__, + platform_irq->irq_name, irq_id); + ret = request_irq(irq_id, + private_irq->irq_handler, + platform_irq->flags, + platform_irq->device_name, + (void *)platform_irq->dev_id + ); + if (ret < 0) { + platform_irq->irq_id = ret; + } else { + platform_irq->irq_id = irq_id; + ret_wake = enable_irq_wake(irq_id); + if (ret_wake < 0) { + pr_err("smd: enable_irq_wake failed on %s", + platform_irq->irq_name); + } + } + + return ret; +} + +int sort_cmp_func(const void *a, const void *b) +{ + struct smem_area *left = (struct smem_area *)(a); + struct smem_area *right = (struct smem_area *)(b); + + return left->phys_addr - right->phys_addr; +} + +int smd_core_platform_init(struct platform_device *pdev) +{ + int i; + int ret; + uint32_t num_ss; + struct smd_platform *smd_platform_data; + struct smd_subsystem_config *smd_ss_config_list; + struct smd_subsystem_config *cfg; + int err_ret = 0; + struct smd_smem_regions *smd_smem_areas; + int smem_idx = 0; + + smd_platform_data = pdev->dev.platform_data; + num_ss = smd_platform_data->num_ss_configs; + smd_ss_config_list = smd_platform_data->smd_ss_configs; + + if (smd_platform_data->smd_ssr_config) + disable_smsm_reset_handshake = smd_platform_data-> + smd_ssr_config->disable_smsm_reset_handshake; + + smd_smem_areas = smd_platform_data->smd_smem_areas; + if (smd_smem_areas) { + num_smem_areas = smd_platform_data->num_smem_areas; + smem_areas = kmalloc(sizeof(struct smem_area) * num_smem_areas, + GFP_KERNEL); + if (!smem_areas) { + pr_err("%s: smem_areas kmalloc failed\n", __func__); + err_ret = -ENOMEM; + goto smem_areas_alloc_fail; + } + + for (smem_idx = 0; smem_idx < num_smem_areas; ++smem_idx) { + smem_areas[smem_idx].phys_addr = + smd_smem_areas[smem_idx].phys_addr; + smem_areas[smem_idx].size = + smd_smem_areas[smem_idx].size; + smem_areas[smem_idx].virt_addr = ioremap_nocache( + (unsigned long)(smem_areas[smem_idx].phys_addr), + smem_areas[smem_idx].size); + if (!smem_areas[smem_idx].virt_addr) { + pr_err("%s: ioremap_nocache() of addr:%p" + " size: %x\n", __func__, + smem_areas[smem_idx].phys_addr, + smem_areas[smem_idx].size); + err_ret = -ENOMEM; + ++smem_idx; + goto smem_failed; + } + } + sort(smem_areas, num_smem_areas, + sizeof(struct smem_area), + sort_cmp_func, NULL); + } + + for (i = 0; i < num_ss; i++) { + cfg = &smd_ss_config_list[i]; + + ret = intr_init( + &private_intr_config[cfg->irq_config_id].smd, + &cfg->smd_int, + pdev + ); + + if (ret < 0) { + err_ret = ret; + pr_err("smd: register irq failed on %s\n", + cfg->smd_int.irq_name); + goto intr_failed; + } + + /* only init smsm structs if this edge supports smsm */ + if (cfg->smsm_int.irq_id) + ret = intr_init( + &private_intr_config[cfg->irq_config_id].smsm, + &cfg->smsm_int, + pdev + ); + + if (ret < 0) { + err_ret = ret; + pr_err("smd: register irq failed on %s\n", + cfg->smsm_int.irq_name); + goto intr_failed; + } + + if (cfg->subsys_name) + strlcpy(edge_to_pids[cfg->edge].subsys_name, + cfg->subsys_name, SMD_MAX_CH_NAME_LEN); + } + + + SMD_INFO("smd_core_platform_init() done\n"); return 0; + +intr_failed: + pr_err("smd: deregistering IRQs\n"); + for (i = 0; i < num_ss; ++i) { + cfg = &smd_ss_config_list[i]; + + if (cfg->smd_int.irq_id >= 0) + free_irq(cfg->smd_int.irq_id, + (void *)cfg->smd_int.dev_id + ); + if (cfg->smsm_int.irq_id >= 0) + free_irq(cfg->smsm_int.irq_id, + (void *)cfg->smsm_int.dev_id + ); + } +smem_failed: + for (smem_idx = smem_idx - 1; smem_idx >= 0; --smem_idx) + iounmap(smem_areas[smem_idx].virt_addr); + kfree(smem_areas); +smem_areas_alloc_fail: + return err_ret; } 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(); + int ret; + SMD_INFO("smd probe\n"); INIT_WORK(&probe_work, smd_channel_probe_worker); - if (smd_core_init()) { - pr_err("smd_core_init() failed\n"); + 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; } - do_smd_probe(); + if (pdev) { + if (pdev->dev.of_node) { + pr_err("SMD: Device tree not currently supported\n"); + return -ENODEV; + } else if (pdev->dev.platform_data) { + ret = smd_core_platform_init(pdev); + if (ret) { + pr_err( + "SMD: smd_core_platform_init() failed\n"); + return -ENODEV; + } + } else { + ret = smd_core_init(); + if (ret) { + pr_err("smd_core_init() failed\n"); + return -ENODEV; + } + } + } else { + pr_err("SMD: PDEV not found\n"); + return -ENODEV; + } - msm_check_for_modem_crash = check_for_modem_crash; + smd_initialized = 1; - msm_init_last_radio_log(THIS_MODULE); + smd_alloc_loopback_channel(); + smsm_irq_handler(0, 0); + tasklet_schedule(&smd_fake_irq_tasklet); - smd_initialized = 1; + return 0; +} + +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data); +static struct restart_notifier_block restart_notifiers[] = { + {SMD_MODEM, "modem", .nb.notifier_call = restart_notifier_cb}, + {SMD_Q6, "lpass", .nb.notifier_call = restart_notifier_cb}, + {SMD_WCNSS, "riva", .nb.notifier_call = restart_notifier_cb}, + {SMD_DSPS, "dsps", .nb.notifier_call = restart_notifier_cb}, + {SMD_MODEM, "gss", .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, @@ -1029,8 +3483,14 @@ static struct platform_driver msm_smd_driver = { }, }; -static int __init msm_smd_init(void) +int __init msm_smd_init(void) { + static bool registered; + + if (registered) + return 0; + + registered = true; return platform_driver_register(&msm_smd_driver); } diff --git a/arch/arm/mach-msm/smd_debug.c b/arch/arm/mach-msm/smd_debug.c index c56df9e932aec303aca92252040fe2d9344b5391..d64bcf2d5b916bfd6185457b70f9bef5dde34196 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-2012, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -16,6 +17,8 @@ #include #include +#include +#include #include @@ -45,60 +48,198 @@ 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 dump_ch(char *buf, int max, struct smd_channel *ch) +static int debug_int_stats(char *buf, int max) { - volatile struct smd_half_channel *s = ch->send; - volatile struct smd_half_channel *r = ch->recv; + int i = 0; + int subsys; + struct interrupt_stat *stats = interrupt_stats; + const char *subsys_name; - 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, - chstate(s->state), s->tail, s->head, - s->fDSR ? 'D' : 'd', - s->fCTS ? 'C' : 'c', - s->fCD ? 'C' : 'c', - s->fRI ? 'I' : 'i', - s->fHEAD ? 'W' : 'w', - s->fTAIL ? 'R' : 'r', - s->fSTATE ? 'S' : 's', - chstate(r->state), r->tail, r->head, - r->fDSR ? 'D' : 'd', - r->fCTS ? 'R' : 'r', - r->fCD ? 'C' : 'c', - r->fRI ? 'I' : 'i', - r->fHEAD ? 'W' : 'w', - r->fTAIL ? 'R' : 'r', - r->fSTATE ? 'S' : 's', - ch->name - ); + i += scnprintf(buf + i, max - i, + " Subsystem | In | Out (Hardcoded) |" + " Out (Configured) |\n"); + + for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) { + subsys_name = smd_pid_to_subsystem(subsys); + if (subsys_name) { + i += scnprintf(buf + i, max - i, + "%-10s %4s | %9u | %9u | %9u |\n", + smd_pid_to_subsystem(subsys), "smd", + stats->smd_in_count, + stats->smd_out_hardcode_count, + stats->smd_out_config_count); + + i += scnprintf(buf + i, max - i, + "%-10s %4s | %9u | %9u | %9u |\n", + smd_pid_to_subsystem(subsys), "smsm", + stats->smsm_in_count, + stats->smsm_out_hardcode_count, + stats->smsm_out_config_count); + } + ++stats; + } + + return i; +} + +static int debug_int_stats_reset(char *buf, int max) +{ + int i = 0; + int subsys; + struct interrupt_stat *stats = interrupt_stats; + + i += scnprintf(buf + i, max - i, "Resetting interrupt stats.\n"); + + for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) { + stats->smd_in_count = 0; + stats->smd_out_hardcode_count = 0; + stats->smd_out_config_count = 0; + stats->smsm_in_count = 0; + stats->smsm_out_hardcode_count = 0; + stats->smsm_out_config_count = 0; + ++stats; + } + + return i; +} + +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 debug_read_stat(char *buf, int max) +static int debug_read_diag_msg(char *buf, int max) { char *msg; int i = 0; msg = smem_find(ID_DIAG_ERR_MSG, SZ_DIAG_ERR_MSG); - 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); @@ -106,6 +247,278 @@ static int debug_read_stat(char *buf, int max) return i; } +static int dump_ch(char *buf, int max, int n, + void *half_ch_s, + void *half_ch_r, + struct smd_half_channel_access *half_ch_funcs, + unsigned size) +{ + return scnprintf( + buf, max, + "ch%02d:" + " %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(half_ch_funcs->get_state(half_ch_s)), + half_ch_funcs->get_tail(half_ch_s), + half_ch_funcs->get_head(half_ch_s), + half_ch_funcs->get_fDSR(half_ch_s) ? 'D' : 'd', + half_ch_funcs->get_fCTS(half_ch_s) ? 'C' : 'c', + half_ch_funcs->get_fCD(half_ch_s) ? 'C' : 'c', + half_ch_funcs->get_fRI(half_ch_s) ? 'I' : 'i', + half_ch_funcs->get_fHEAD(half_ch_s) ? 'W' : 'w', + half_ch_funcs->get_fTAIL(half_ch_s) ? 'R' : 'r', + half_ch_funcs->get_fSTATE(half_ch_s) ? 'S' : 's', + half_ch_funcs->get_fBLOCKREADINTR(half_ch_s) ? 'B' : 'b', + chstate(half_ch_funcs->get_state(half_ch_r)), + half_ch_funcs->get_tail(half_ch_r), + half_ch_funcs->get_head(half_ch_r), + half_ch_funcs->get_fDSR(half_ch_r) ? 'D' : 'd', + half_ch_funcs->get_fCTS(half_ch_r) ? 'C' : 'c', + half_ch_funcs->get_fCD(half_ch_r) ? 'C' : 'c', + half_ch_funcs->get_fRI(half_ch_r) ? 'I' : 'i', + half_ch_funcs->get_fHEAD(half_ch_r) ? 'W' : 'w', + half_ch_funcs->get_fTAIL(half_ch_r) ? 'R' : 'r', + half_ch_funcs->get_fSTATE(half_ch_r) ? 'S' : 's', + half_ch_funcs->get_fBLOCKREADINTR(half_ch_r) ? 'B' : 'b', + size + ); +} + +static int debug_read_smsm_state(char *buf, int max) +{ + uint32_t *smsm; + int n, i = 0; + + 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 struct completion smsm_cb_completion; + +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; + complete_all(&smsm_cb_completion); +} + +#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 UT_GT_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); + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + + 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 */ + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + 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 */ + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + 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 */ + INIT_COMPLETION(smsm_cb_completion); + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 3); + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + 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); + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 4); + + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 5); + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + 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 */ + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + 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 */ + INIT_COMPLETION(smsm_cb_completion); + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + UT_EQ_INT((int)smsm_cb_data.data, 0x1234); + + INIT_COMPLETION(smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + 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); + + return i; +} + static int debug_read_mem(char *buf, int max) { unsigned n; @@ -129,29 +542,117 @@ static int debug_read_mem(char *buf, int max) return i; } +#if (!defined(CONFIG_MSM_SMD_PKG4) && !defined(CONFIG_MSM_SMD_PKG3)) static int debug_read_ch(char *buf, int max) { - struct smd_channel *ch; - unsigned long flags; - int i = 0; + void *shared; + int n, i = 0; + struct smd_alloc_elm *ch_tbl; + unsigned ch_type; + unsigned shared_size; + + ch_tbl = smem_find(ID_CH_ALLOC_TBL, sizeof(*ch_tbl) * 64); + if (!ch_tbl) + goto fail; + + for (n = 0; n < SMD_CHANNELS; n++) { + ch_type = SMD_CHANNEL_TYPE(ch_tbl[n].type); + if (is_word_access_ch(ch_type)) + shared_size = + sizeof(struct smd_half_channel_word_access); + else + shared_size = sizeof(struct smd_half_channel); + shared = smem_find(ID_SMD_CHANNELS + n, + 2 * shared_size + SMD_BUF_SIZE); + + if (shared == 0) + continue; + i += dump_ch(buf + i, max - i, n, shared, + (shared + shared_size + + SMD_BUF_SIZE), get_half_ch_funcs(ch_type), + SMD_BUF_SIZE); + } + +fail: + return i; +} +#else +static int debug_read_ch(char *buf, int max) +{ + void *shared, *buffer; + unsigned buffer_sz; + int n, i = 0; + struct smd_alloc_elm *ch_tbl; + unsigned ch_type; + unsigned shared_size; + + ch_tbl = smem_find(ID_CH_ALLOC_TBL, sizeof(*ch_tbl) * 64); + if (!ch_tbl) + goto fail; + + for (n = 0; n < SMD_CHANNELS; n++) { + ch_type = SMD_CHANNEL_TYPE(ch_tbl[n].type); + if (is_word_access_ch(ch_type)) + shared_size = + sizeof(struct smd_half_channel_word_access); + else + shared_size = sizeof(struct smd_half_channel); + + shared = smem_find(ID_SMD_CHANNELS + n, 2 * shared_size); + + if (shared == 0) + continue; + + buffer = smem_get_entry(SMEM_SMD_FIFO_BASE_ID + n, &buffer_sz); - 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); + if (buffer == 0) + continue; + + i += dump_ch(buf + i, max - i, n, shared, + (shared + shared_size), + get_half_ch_funcs(ch_type), + buffer_sz / 2); + } +fail: return i; } +#endif -static int debug_read_version(char *buf, int 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 +660,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 +676,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]; @@ -199,14 +739,18 @@ 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); + int bsize; + + if (*ppos != 0) + return 0; + + bsize = fill(debug_buffer, DEBUG_BUFMAX); return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); } static const struct file_operations debug_ops = { .read = debug_read, .open = simple_open, - .llseek = default_llseek, }; static void debug_create(const char *name, umode_t mode, @@ -216,25 +760,53 @@ static void debug_create(const char *name, umode_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); + debug_create("int_stats", 0444, dent, debug_int_stats); + debug_create("int_stats_reset", 0444, dent, debug_int_stats_reset); + + /* 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); + + init_completion(&smsm_cb_completion); + + return 0; +} + late_initcall(smd_debugfs_init); +late_initcall(smsm_debugfs_init); #endif @@ -259,38 +831,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) { @@ -304,9 +867,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 0000000000000000000000000000000000000000..1aedbf5e1c1752b6a10f8880e373b783a3160a92 --- /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 0000000000000000000000000000000000000000..b9cba8c89ae1b23f8d9aa940a20f03f499b89801 --- /dev/null +++ b/arch/arm/mach-msm/smd_pkt.c @@ -0,0 +1,1049 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#include "smd_private.h" +#ifdef CONFIG_ARCH_FSM9XXX +#define NUM_SMD_PKT_PORTS 4 +#else +#define NUM_SMD_PKT_PORTS 15 +#endif + +#define LOOPBACK_INX (NUM_SMD_PKT_PORTS - 1) + +#define DEVICE_NAME "smdpkt" +#define WAKELOCK_TIMEOUT (2*HZ) + +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 is_open; + int poll_mode; + unsigned ch_size; + uint open_modem_wait; + + int has_reset; + int do_reset_notification; + struct completion ch_allocated; + struct wake_lock pa_wake_lock; /* Packet Arrival Wake lock*/ + struct work_struct packet_arrival_work; + struct spinlock pa_spinlock; +} *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); + +enum { + SMD_PKT_STATUS = 1U << 0, + SMD_PKT_READ = 1U << 1, + SMD_PKT_WRITE = 1U << 2, + SMD_PKT_READ_DUMP_BUFFER = 1U << 3, + SMD_PKT_WRITE_DUMP_BUFFER = 1U << 4, + SMD_PKT_POLL = 1U << 5, +}; + +#define DEBUG + +#ifdef DEBUG +#define D_STATUS(x...) \ +do { \ + if (msm_smd_pkt_debug_mask & SMD_PKT_STATUS) \ + pr_info("Status: "x); \ +} while (0) + +#define D_READ(x...) \ +do { \ + if (msm_smd_pkt_debug_mask & SMD_PKT_READ) \ + pr_info("Read: "x); \ +} while (0) + +#define D_WRITE(x...) \ +do { \ + if (msm_smd_pkt_debug_mask & SMD_PKT_WRITE) \ + pr_info("Write: "x); \ +} while (0) + +#define D_READ_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (msm_smd_pkt_debug_mask & SMD_PKT_READ_DUMP_BUFFER) \ + print_hex_dump(KERN_INFO, prestr, \ + DUMP_PREFIX_NONE, 16, 1, \ + buf, cnt, 1); \ +} while (0) + +#define D_WRITE_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (msm_smd_pkt_debug_mask & SMD_PKT_WRITE_DUMP_BUFFER) \ + print_hex_dump(KERN_INFO, prestr, \ + DUMP_PREFIX_NONE, 16, 1, \ + buf, cnt, 1); \ +} while (0) + +#define D_POLL(x...) \ +do { \ + if (msm_smd_pkt_debug_mask & SMD_PKT_POLL) \ + pr_info("Poll: "x); \ +} while (0) +#else +#define D_STATUS(x...) do {} while (0) +#define D_READ(x...) do {} while (0) +#define D_WRITE(x...) do {} while (0) +#define D_READ_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) +#define D_WRITE_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) +#define D_POLL(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 (i >= NUM_SMD_PKT_PORTS) { + pr_err("%s: unable to match device to valid smd_pkt port\n", + __func__); + return -EINVAL; + } + 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; + } + if (i >= NUM_SMD_PKT_PORTS) { + pr_err("%s: unable to match device to valid smd_pkt port\n", + __func__); + return -EINVAL; + } + return snprintf(buf, PAGE_SIZE, "%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(&smd_pkt_devp->ch_read_wait_queue); + wake_up(&smd_pkt_devp->ch_write_wait_queue); + wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue); + D_STATUS("%s smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i); +} + +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 void packet_arrival_worker(struct work_struct *work) +{ + struct smd_pkt_dev *smd_pkt_devp; + + smd_pkt_devp = container_of(work, struct smd_pkt_dev, + packet_arrival_work); + mutex_lock(&smd_pkt_devp->ch_lock); + if (smd_pkt_devp->ch) { + D_READ("%s locking smd_pkt_dev id:%d wakelock\n", + __func__, smd_pkt_devp->i); + wake_lock_timeout(&smd_pkt_devp->pa_wake_lock, + WAKELOCK_TIMEOUT); + } + mutex_unlock(&smd_pkt_devp->ch_lock); +} + +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: + D_STATUS("%s TIOCMGET command on smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + ret = smd_tiocmget(smd_pkt_devp->ch); + break; + case TIOCMSET: + D_STATUS("%s TIOCSET command on smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + 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: + pr_err("%s: Unrecognized ioctl command %d\n", __func__, cmd); + 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; + int pkt_size; + struct smd_pkt_dev *smd_pkt_devp; + unsigned long flags; + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp) { + pr_err("%s on NULL smd_pkt_dev\n", __func__); + return -EINVAL; + } + + if (!smd_pkt_devp->ch) { + pr_err("%s on a closed smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return -EINVAL; + } + + if (smd_pkt_devp->do_reset_notification) { + /* notify client that a reset occurred */ + pr_err("%s notifying reset for smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return notify_reset(smd_pkt_devp); + } + D_READ("Begin %s on smd_pkt_dev id:%d buffer_size %d\n", + __func__, smd_pkt_devp->i, count); + +wait_for_packet: + r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue, + !smd_pkt_devp->ch || + (smd_cur_packet_size(smd_pkt_devp->ch) > 0 + && smd_read_avail(smd_pkt_devp->ch)) || + smd_pkt_devp->has_reset); + + mutex_lock(&smd_pkt_devp->rx_lock); + if (smd_pkt_devp->has_reset) { + mutex_unlock(&smd_pkt_devp->rx_lock); + pr_err("%s notifying reset for smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return notify_reset(smd_pkt_devp); + } + + if (!smd_pkt_devp->ch) { + mutex_unlock(&smd_pkt_devp->rx_lock); + pr_err("%s on a closed smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return -EINVAL; + } + + if (r < 0) { + mutex_unlock(&smd_pkt_devp->rx_lock); + /* qualify error message */ + if (r != -ERESTARTSYS) { + /* we get this anytime a signal comes in */ + pr_err("%s: wait_event_interruptible on smd_pkt_dev" + " id:%d ret %i\n", + __func__, smd_pkt_devp->i, r); + } + return r; + } + + /* Here we have a whole packet waiting for us */ + pkt_size = smd_cur_packet_size(smd_pkt_devp->ch); + + if (!pkt_size) { + pr_err("%s: No data on smd_pkt_dev id:%d, False wakeup\n", + __func__, smd_pkt_devp->i); + mutex_unlock(&smd_pkt_devp->rx_lock); + goto wait_for_packet; + } + + if (pkt_size > count) { + pr_err("%s: failure on smd_pkt_dev id: %d - packet size %d" + " > buffer size %d,", __func__, smd_pkt_devp->i, + pkt_size, count); + mutex_unlock(&smd_pkt_devp->rx_lock); + return -ETOOSMALL; + } + + bytes_read = 0; + do { + r = smd_read_user_buffer(smd_pkt_devp->ch, + (buf + bytes_read), + (pkt_size - bytes_read)); + if (r < 0) { + mutex_unlock(&smd_pkt_devp->rx_lock); + if (smd_pkt_devp->has_reset) { + pr_err("%s notifying reset for smd_pkt_dev" + " id:%d\n", __func__, smd_pkt_devp->i); + return notify_reset(smd_pkt_devp); + } + pr_err("%s Error while reading %d\n", __func__, r); + return r; + } + bytes_read += r; + if (pkt_size != bytes_read) + wait_event(smd_pkt_devp->ch_read_wait_queue, + smd_read_avail(smd_pkt_devp->ch) || + smd_pkt_devp->has_reset); + if (smd_pkt_devp->has_reset) { + mutex_unlock(&smd_pkt_devp->rx_lock); + pr_err("%s notifying reset for smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return notify_reset(smd_pkt_devp); + } + } while (pkt_size != bytes_read); + D_READ_DUMP_BUFFER("Read: ", (bytes_read > 16 ? 16 : bytes_read), buf); + mutex_unlock(&smd_pkt_devp->rx_lock); + + mutex_lock(&smd_pkt_devp->ch_lock); + spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags); + if (smd_pkt_devp->poll_mode && + !smd_cur_packet_size(smd_pkt_devp->ch)) { + wake_unlock(&smd_pkt_devp->pa_wake_lock); + smd_pkt_devp->poll_mode = 0; + D_READ("%s unlocked smd_pkt_dev id:%d wakelock\n", + __func__, smd_pkt_devp->i); + } + spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags); + mutex_unlock(&smd_pkt_devp->ch_lock); + + D_READ("Finished %s on smd_pkt_dev id:%d %d bytes\n", + __func__, smd_pkt_devp->i, 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, bytes_written; + struct smd_pkt_dev *smd_pkt_devp; + DEFINE_WAIT(write_wait); + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp) { + pr_err("%s on NULL smd_pkt_dev\n", __func__); + return -EINVAL; + } + + if (!smd_pkt_devp->ch) { + pr_err("%s on a closed smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return -EINVAL; + } + + if (smd_pkt_devp->do_reset_notification || smd_pkt_devp->has_reset) { + pr_err("%s notifying reset for smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + /* notify client that a reset occurred */ + return notify_reset(smd_pkt_devp); + } + D_WRITE("Begin %s on smd_pkt_dev id:%d data_size %d\n", + __func__, smd_pkt_devp->i, count); + + mutex_lock(&smd_pkt_devp->tx_lock); + if (!smd_pkt_devp->blocking_write) { + if (smd_write_avail(smd_pkt_devp->ch) < count) { + pr_err("%s: Not enough space in smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + mutex_unlock(&smd_pkt_devp->tx_lock); + return -ENOMEM; + } + } + + r = smd_write_start(smd_pkt_devp->ch, count); + if (r < 0) { + mutex_unlock(&smd_pkt_devp->tx_lock); + pr_err("%s: Error:%d in smd_pkt_dev id:%d @ smd_write_start\n", + __func__, r, smd_pkt_devp->i); + return r; + } + + bytes_written = 0; + do { + prepare_to_wait(&smd_pkt_devp->ch_write_wait_queue, + &write_wait, TASK_UNINTERRUPTIBLE); + if (!smd_write_avail(smd_pkt_devp->ch) && + !smd_pkt_devp->has_reset) { + smd_enable_read_intr(smd_pkt_devp->ch); + schedule(); + } + 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); + pr_err("%s notifying reset for smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return notify_reset(smd_pkt_devp); + } else { + r = smd_write_segment(smd_pkt_devp->ch, + (void *)(buf + bytes_written), + (count - bytes_written), 1); + if (r < 0) { + mutex_unlock(&smd_pkt_devp->tx_lock); + if (smd_pkt_devp->has_reset) { + pr_err("%s notifying reset for" + " smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return notify_reset(smd_pkt_devp); + } + pr_err("%s on smd_pkt_dev id:%d failed r:%d\n", + __func__, smd_pkt_devp->i, r); + return r; + } + bytes_written += r; + } + } while (bytes_written != count); + smd_write_end(smd_pkt_devp->ch); + mutex_unlock(&smd_pkt_devp->tx_lock); + D_WRITE_DUMP_BUFFER("Write: ", + (bytes_written > 16 ? 16 : bytes_written), buf); + D_WRITE("Finished %s on smd_pkt_dev id:%d %d bytes\n", + __func__, smd_pkt_devp->i, 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) { + pr_err("%s on a NULL device\n", __func__); + return POLLERR; + } + + smd_pkt_devp->poll_mode = 1; + poll_wait(file, &smd_pkt_devp->ch_read_wait_queue, wait); + mutex_lock(&smd_pkt_devp->ch_lock); + if (smd_pkt_devp->has_reset || !smd_pkt_devp->ch) { + mutex_unlock(&smd_pkt_devp->ch_lock); + pr_err("%s notifying reset for smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return POLLERR; + } + + if (smd_read_avail(smd_pkt_devp->ch)) { + mask |= POLLIN | POLLRDNORM; + D_POLL("%s sets POLLIN for smd_pkt_dev id: %d\n", + __func__, smd_pkt_devp->i); + } + mutex_unlock(&smd_pkt_devp->ch_lock); + + return mask; +} + +static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp) +{ + int sz; + unsigned long flags; + + if (!smd_pkt_devp) { + pr_err("%s on a NULL device\n", __func__); + return; + } + + if (!smd_pkt_devp->ch) { + pr_err("%s on a closed smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return; + } + + sz = smd_cur_packet_size(smd_pkt_devp->ch); + if (sz == 0) { + D_READ("%s: No packet in smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return; + } + if (!smd_read_avail(smd_pkt_devp->ch)) { + D_READ("%s: packet size is %d in smd_pkt_dev id:%d -" + " but the data isn't here\n", + __func__, sz, smd_pkt_devp->i); + return; + } + + /* here we have a packet of size sz ready */ + wake_up(&smd_pkt_devp->ch_read_wait_queue); + spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags); + wake_lock(&smd_pkt_devp->pa_wake_lock); + spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags); + schedule_work(&smd_pkt_devp->packet_arrival_work); + D_READ("%s: wake_up smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i); +} + +static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp) +{ + int sz; + + if (!smd_pkt_devp) { + pr_err("%s on a NULL device\n", __func__); + return; + } + + if (!smd_pkt_devp->ch) { + pr_err("%s on a closed smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return; + } + + sz = smd_write_avail(smd_pkt_devp->ch); + if (sz) { + D_WRITE("%s: %d bytes write space in smd_pkt_dev id:%d\n", + __func__, sz, smd_pkt_devp->i); + smd_disable_read_intr(smd_pkt_devp->ch); + wake_up(&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) { + pr_err("%s on a closed smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + return; + } + + switch (event) { + case SMD_EVENT_DATA: { + D_STATUS("%s: DATA event in smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + check_and_wakeup_reader(smd_pkt_devp); + if (smd_pkt_devp->blocking_write) + check_and_wakeup_writer(smd_pkt_devp); + break; + } + case SMD_EVENT_OPEN: + D_STATUS("%s: OPEN event in smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + 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: + D_STATUS("%s: CLOSE event in smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + smd_pkt_devp->is_open = 0; + /* 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_apps2", + "smdcntl8", + "smd_sns_adsp", + "smd_cxm_qmi", + "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_apps2", + "DATA40_CNTL", + "SENSOR", + "CXM_QMI_PORT_8064", + "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, + SMD_APPS_QDSP, + SMD_APPS_WCNSS, + 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 (!strncmp(pdev->name, smd_ch_name[i], SMD_MAX_CH_NAME_LEN)) { + complete_all(&smd_pkt_devp[i]->ch_allocated); + D_STATUS("%s allocated SMD ch for smd_pkt_dev id:%d\n", + __func__, i); + 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; + const char *peripheral = NULL; + + smd_pkt_devp = container_of(inode->i_cdev, struct smd_pkt_dev, cdev); + + if (!smd_pkt_devp) { + pr_err("%s on a NULL device\n", __func__); + return -EINVAL; + } + D_STATUS("Begin %s on smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i); + + wake_lock_init(&smd_pkt_devp->pa_wake_lock, WAKE_LOCK_SUSPEND, + smd_pkt_dev_name[smd_pkt_devp->i]); + INIT_WORK(&smd_pkt_devp->packet_arrival_work, packet_arrival_worker); + + file->private_data = smd_pkt_devp; + + mutex_lock(&smd_pkt_devp->ch_lock); + if (smd_pkt_devp->ch == 0) { + init_completion(&smd_pkt_devp->ch_allocated); + smd_pkt_devp->driver.probe = smd_pkt_dummy_probe; + smd_pkt_devp->driver.driver.name = + smd_ch_name[smd_pkt_devp->i]; + smd_pkt_devp->driver.driver.owner = THIS_MODULE; + r = platform_driver_register(&smd_pkt_devp->driver); + if (r) { + pr_err("%s: %s Platform driver reg. failed\n", + __func__, smd_ch_name[smd_pkt_devp->i]); + goto out; + } + + peripheral = smd_edge_to_subsystem( + smd_ch_edge[smd_pkt_devp->i]); + if (peripheral) { + smd_pkt_devp->pil = pil_get(peripheral); + if (IS_ERR(smd_pkt_devp->pil)) { + r = PTR_ERR(smd_pkt_devp->pil); + pr_err("%s failed on smd_pkt_dev id:%d -" + " pil_get failed for %s\n", __func__, + smd_pkt_devp->i, peripheral); + goto release_pd; + } + + /* 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 (!strncmp(smd_ch_name[smd_pkt_devp->i], "LOOPBACK", + SMD_MAX_CH_NAME_LEN)) { + 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 on smd_pkt_dev id:%d" + " allocation failed rc:%d\n", + __func__, smd_pkt_devp->i, 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; + /* close the ch to sync smd's state with smd_pkt */ + smd_close(smd_pkt_devp->ch); + smd_pkt_devp->ch = NULL; + } + + if (r < 0) { + pr_err("%s: wait on smd_pkt_dev id:%d OPEN event failed" + " rc:%d\n", __func__, smd_pkt_devp->i, r); + } else if (!smd_pkt_devp->is_open) { + pr_err("%s: Invalid OPEN event on smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + 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; + D_STATUS("Finished %s on smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + } + } +release_pil: + if (peripheral && (r < 0)) + pil_put(smd_pkt_devp->pil); + +release_pd: + if (r < 0) + platform_driver_unregister(&smd_pkt_devp->driver); +out: + mutex_unlock(&smd_pkt_devp->ch_lock); + + if (r < 0) + wake_lock_destroy(&smd_pkt_devp->pa_wake_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) { + pr_err("%s on a NULL device\n", __func__); + return -EINVAL; + } + D_STATUS("Begin %s on smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + + clean_and_signal(smd_pkt_devp); + + mutex_lock(&smd_pkt_devp->ch_lock); + mutex_lock(&smd_pkt_devp->rx_lock); + mutex_lock(&smd_pkt_devp->tx_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; + smd_pkt_devp->poll_mode = 0; + platform_driver_unregister(&smd_pkt_devp->driver); + if (smd_pkt_devp->pil) + pil_put(smd_pkt_devp->pil); + } + mutex_unlock(&smd_pkt_devp->tx_lock); + mutex_unlock(&smd_pkt_devp->rx_lock); + mutex_unlock(&smd_pkt_devp->ch_lock); + + smd_pkt_devp->has_reset = 0; + smd_pkt_devp->do_reset_notification = 0; + wake_lock_destroy(&smd_pkt_devp->pa_wake_lock); + D_STATUS("Finished %s on smd_pkt_dev id:%d\n", + __func__, smd_pkt_devp->i); + + 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)) { + pr_err("%s: alloc_chrdev_region() failed ret:%i\n", + __func__, r); + goto error0; + } + + smd_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(smd_pkt_classp)) { + pr_err("%s: class_create() failed ENOMEM\n", __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])) { + pr_err("%s: kzalloc() failed for smd_pkt_dev id:%d\n", + __func__, i); + 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; + smd_pkt_devp[i]->poll_mode = 0; + init_waitqueue_head(&smd_pkt_devp[i]->ch_opened_wait_queue); + + spin_lock_init(&smd_pkt_devp[i]->pa_spinlock); + mutex_init(&smd_pkt_devp[i]->ch_lock); + mutex_init(&smd_pkt_devp[i]->rx_lock); + mutex_init(&smd_pkt_devp[i]->tx_lock); + + 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)) { + pr_err("%s: cdev_add() failed for smd_pkt_dev id:%d" + " ret:%i\n", __func__, i, 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)) { + pr_err("%s: device_create() failed for smd_pkt_dev" + " id:%d\n", __func__, i); + 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 for" + " smd_pkt_dev id:%d\n", __func__, i); + } + + INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker); + + D_STATUS("SMD Packet Port Driver Initialized.\n"); + return 0; + + error2: + if (i > 0) { + while (--i >= 0) { + 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) { + 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.c b/arch/arm/mach-msm/smd_private.c new file mode 100644 index 0000000000000000000000000000000000000000..5a78b6f04cd62750f86e7901cfde715e284b20e2 --- /dev/null +++ b/arch/arm/mach-msm/smd_private.c @@ -0,0 +1,303 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "smd_private.h" + +void set_state(volatile void *half_channel, unsigned data) +{ + ((struct smd_half_channel *)(half_channel))->state = data; +} + +unsigned get_state(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->state; +} + +void set_fDSR(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fDSR = data; +} + +unsigned get_fDSR(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fDSR; +} + +void set_fCTS(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fCTS = data; +} + +unsigned get_fCTS(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fCTS; +} + +void set_fCD(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fCD = data; +} + +unsigned get_fCD(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fCD; +} + +void set_fRI(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fRI = data; +} + +unsigned get_fRI(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fRI; +} + +void set_fHEAD(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fHEAD = data; +} + +unsigned get_fHEAD(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fHEAD; +} + +void set_fTAIL(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fTAIL = data; +} + +unsigned get_fTAIL(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fTAIL; +} + +void set_fSTATE(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fSTATE = data; +} + +unsigned get_fSTATE(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fSTATE; +} + +void set_fBLOCKREADINTR(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel *)(half_channel))->fBLOCKREADINTR = data; +} + +unsigned get_fBLOCKREADINTR(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->fBLOCKREADINTR; +} + +void set_tail(volatile void *half_channel, unsigned data) +{ + ((struct smd_half_channel *)(half_channel))->tail = data; +} + +unsigned get_tail(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->tail; +} + +void set_head(volatile void *half_channel, unsigned data) +{ + ((struct smd_half_channel *)(half_channel))->head = data; +} + +unsigned get_head(volatile void *half_channel) +{ + return ((struct smd_half_channel *)(half_channel))->head; +} + +void set_state_word_access(volatile void *half_channel, unsigned data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->state = data; +} + +unsigned get_state_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->state; +} + +void set_fDSR_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fDSR = data; +} + +unsigned get_fDSR_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fDSR; +} + +void set_fCTS_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fCTS = data; +} + +unsigned get_fCTS_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fCTS; +} + +void set_fCD_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fCD = data; +} + +unsigned get_fCD_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fCD; +} + +void set_fRI_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fRI = data; +} + +unsigned get_fRI_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fRI; +} + +void set_fHEAD_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fHEAD = data; +} + +unsigned get_fHEAD_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fHEAD; +} + +void set_fTAIL_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fTAIL = data; +} + +unsigned get_fTAIL_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fTAIL; +} + +void set_fSTATE_word_access(volatile void *half_channel, unsigned char data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->fSTATE = data; +} + +unsigned get_fSTATE_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->fSTATE; +} + +void set_fBLOCKREADINTR_word_access(volatile void *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access *) + (half_channel))->fBLOCKREADINTR = data; +} + +unsigned get_fBLOCKREADINTR_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *) + (half_channel))->fBLOCKREADINTR; +} + +void set_tail_word_access(volatile void *half_channel, unsigned data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->tail = data; +} + +unsigned get_tail_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->tail; +} + +void set_head_word_access(volatile void *half_channel, unsigned data) +{ + ((struct smd_half_channel_word_access *)(half_channel))->head = data; +} + +unsigned get_head_word_access(volatile void *half_channel) +{ + return ((struct smd_half_channel_word_access *)(half_channel))->head; +} + +int is_word_access_ch(unsigned ch_type) +{ + if (ch_type == SMD_APPS_RPM || ch_type == SMD_MODEM_RPM || + ch_type == SMD_QDSP_RPM || ch_type == SMD_WCNSS_RPM) + return 1; + else + return 0; +} + +struct smd_half_channel_access *get_half_ch_funcs(unsigned ch_type) +{ + static struct smd_half_channel_access byte_access = { + .set_state = set_state, + .get_state = get_state, + .set_fDSR = set_fDSR, + .get_fDSR = get_fDSR, + .set_fCTS = set_fCTS, + .get_fCTS = get_fCTS, + .set_fCD = set_fCD, + .get_fCD = get_fCD, + .set_fRI = set_fRI, + .get_fRI = get_fRI, + .set_fHEAD = set_fHEAD, + .get_fHEAD = get_fHEAD, + .set_fTAIL = set_fTAIL, + .get_fTAIL = get_fTAIL, + .set_fSTATE = set_fSTATE, + .get_fSTATE = get_fSTATE, + .set_fBLOCKREADINTR = set_fBLOCKREADINTR, + .get_fBLOCKREADINTR = get_fBLOCKREADINTR, + .set_tail = set_tail, + .get_tail = get_tail, + .set_head = set_head, + .get_head = get_head, + }; + static struct smd_half_channel_access word_access = { + .set_state = set_state_word_access, + .get_state = get_state_word_access, + .set_fDSR = set_fDSR_word_access, + .get_fDSR = get_fDSR_word_access, + .set_fCTS = set_fCTS_word_access, + .get_fCTS = get_fCTS_word_access, + .set_fCD = set_fCD_word_access, + .get_fCD = get_fCD_word_access, + .set_fRI = set_fRI_word_access, + .get_fRI = get_fRI_word_access, + .set_fHEAD = set_fHEAD_word_access, + .get_fHEAD = get_fHEAD_word_access, + .set_fTAIL = set_fTAIL_word_access, + .get_fTAIL = get_fTAIL_word_access, + .set_fSTATE = set_fSTATE_word_access, + .get_fSTATE = get_fSTATE_word_access, + .set_fBLOCKREADINTR = set_fBLOCKREADINTR_word_access, + .get_fBLOCKREADINTR = get_fBLOCKREADINTR_word_access, + .set_tail = set_tail_word_access, + .get_tail = get_tail_word_access, + .set_head = set_head_word_access, + .get_head = get_head_word_access, + }; + + if (is_word_access_ch(ch_type)) + return &word_access; + else + return &byte_access; +} + diff --git a/arch/arm/mach-msm/smd_private.h b/arch/arm/mach-msm/smd_private.h index 727bfe68aa9b0914ee23b0d2dd152fd95680d8b4..9117280e6807084f091484b02e99b15ddf6eed64 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-2012, 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,22 @@ #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 -#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; @@ -34,8 +44,9 @@ struct smem_heap_entry { unsigned allocated; unsigned offset; unsigned size; - unsigned reserved; + unsigned reserved; /* bits 1:0 reserved, bits 31:2 aux smem base addr */ }; +#define BASE_ADDR_MASK 0xfffffffc struct smem_proc_comm { unsigned command; @@ -44,47 +55,33 @@ 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) +/* Don't trigger the error */ +#else +#error No SMD Package Specified; aborting #endif #define SZ_DIAG_ERR_MSG 0xC8 @@ -93,167 +90,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 +127,139 @@ 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_half_channel_word_access { + unsigned state; + unsigned fDSR; + unsigned fCTS; + unsigned fCD; + unsigned fRI; + unsigned fHEAD; + unsigned fTAIL; + unsigned fSTATE; + unsigned fBLOCKREADINTR; + unsigned tail; + unsigned head; }; -struct smd_channel { - volatile struct smd_half_channel *send; - volatile struct smd_half_channel *recv; - unsigned char *send_data; - unsigned char *recv_data; +struct smd_half_channel_access { + void (*set_state)(volatile void *half_channel, unsigned data); + unsigned (*get_state)(volatile void *half_channel); + void (*set_fDSR)(volatile void *half_channel, unsigned char data); + unsigned (*get_fDSR)(volatile void *half_channel); + void (*set_fCTS)(volatile void *half_channel, unsigned char data); + unsigned (*get_fCTS)(volatile void *half_channel); + void (*set_fCD)(volatile void *half_channel, unsigned char data); + unsigned (*get_fCD)(volatile void *half_channel); + void (*set_fRI)(volatile void *half_channel, unsigned char data); + unsigned (*get_fRI)(volatile void *half_channel); + void (*set_fHEAD)(volatile void *half_channel, unsigned char data); + unsigned (*get_fHEAD)(volatile void *half_channel); + void (*set_fTAIL)(volatile void *half_channel, unsigned char data); + unsigned (*get_fTAIL)(volatile void *half_channel); + void (*set_fSTATE)(volatile void *half_channel, unsigned char data); + unsigned (*get_fSTATE)(volatile void *half_channel); + void (*set_fBLOCKREADINTR)(volatile void *half_channel, + unsigned char data); + unsigned (*get_fBLOCKREADINTR)(volatile void *half_channel); + void (*set_tail)(volatile void *half_channel, unsigned data); + unsigned (*get_tail)(volatile void *half_channel); + void (*set_head)(volatile void *half_channel, unsigned data); + unsigned (*get_head)(volatile void *half_channel); +}; - unsigned fifo_mask; - unsigned fifo_size; - unsigned current_packet; - unsigned n; +int is_word_access_ch(unsigned ch_type); - struct list_head ch_list; +struct smd_half_channel_access *get_half_ch_funcs(unsigned ch_type); - void *priv; - void (*notify)(void *priv, unsigned flags); +struct smem_ram_ptn { + char name[16]; + unsigned start; + unsigned size; + + /* RAM Partition attribute: READ_ONLY, READWRITE etc. */ + unsigned attr; + + /* RAM Partition category: EBI0, EBI1, IRAM, IMEM */ + unsigned category; - 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); + /* RAM Partition domain: APPS, MODEM, APPS & MODEM (SHARED) etc. */ + unsigned domain; - void (*update_state)(struct smd_channel *ch); - unsigned last_state; - void (*notify_other_cpu)(void); + /* RAM Partition type: system, bootloader, appsboot, apps etc. */ unsigned type; - char name[32]; - struct platform_device pdev; + /* reserved for future expansion without changing version number */ + unsigned reserved2, reserved3, reserved4, reserved5; +} __attribute__ ((__packed__)); + + +struct smem_ram_ptable { + #define _SMEM_RAM_PTABLE_MAGIC_1 0x9DA5E0A8 + #define _SMEM_RAM_PTABLE_MAGIC_2 0xAF9EC4E2 + unsigned magic[2]; + unsigned version; + unsigned reserved1; + unsigned len; + struct smem_ram_ptn parts[32]; + unsigned buf; +} __attribute__ ((__packed__)); + +/* SMEM RAM Partition */ +enum { + DEFAULT_ATTRB = ~0x0, + READ_ONLY = 0x0, + READWRITE, }; -#define SMD_TYPE_MASK 0x0FF -#define SMD_TYPE_APPS_MODEM 0x000 -#define SMD_TYPE_APPS_DSP 0x001 -#define SMD_TYPE_MODEM_DSP 0x002 +enum { + DEFAULT_CATEGORY = ~0x0, + SMI = 0x0, + EBI1, + EBI2, + QDSP6, + IRAM, + IMEM, + EBI0_CS0, + EBI0_CS1, + EBI1_CS0, + EBI1_CS1, + SDRAM = 0xE, +}; -#define SMD_KIND_MASK 0xF00 -#define SMD_KIND_UNKNOWN 0x000 -#define SMD_KIND_STREAM 0x100 -#define SMD_KIND_PACKET 0x200 +enum { + DEFAULT_DOMAIN = 0x0, + APPS_DOMAIN, + MODEM_DOMAIN, + SHARED_DOMAIN, +}; -extern struct list_head smd_ch_closed_list; -extern struct list_head smd_ch_list_modem; -extern struct list_head smd_ch_list_dsp; +enum { + SYS_MEMORY = 1, /* system memory*/ + BOOT_REGION_MEMORY1, /* boot loader memory 1*/ + BOOT_REGION_MEMORY2, /* boot loader memory 2,reserved*/ + APPSBL_MEMORY, /* apps boot loader memory*/ + APPS_MEMORY, /* apps usage memory*/ +}; -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 *); +void smd_diag(void); -#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 */ +struct interrupt_stat { + uint32_t smd_in_count; + uint32_t smd_out_hardcode_count; + uint32_t smd_out_config_count; + uint32_t smsm_in_count; + uint32_t smsm_out_hardcode_count; + uint32_t smsm_out_config_count; +}; +extern struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS]; #endif diff --git a/arch/arm/mach-msm/smd_qmi.c b/arch/arm/mach-msm/smd_qmi.c new file mode 100644 index 0000000000000000000000000000000000000000..ca10f00d0cf2d1807fee23230741436bc1579716 --- /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 0000000000000000000000000000000000000000..6965cf6fec31b68f3689b810b1bb5db282caba4a --- /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 0000000000000000000000000000000000000000..e9a8b474737f004e362c87fff8cdaceb1bdd4f12 --- /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 0000000000000000000000000000000000000000..983d0c197bb577866e777490c2f97bd4aadece0a --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter.c @@ -0,0 +1,2531 @@ +/* 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); + } + ept->reply_cnt = 0; + 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.*/ + spin_lock_irqsave(&local_endpoints_lock, flags); + list_del(&ept->list); + spin_unlock_irqrestore(&local_endpoints_lock, flags); + 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); + 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; + + list_for_each_entry(ept, &local_endpoints, list) { + if (ept->cid == cid) + return ept; + } + 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 + + spin_lock_irqsave(&local_endpoints_lock, flags); + ept = rpcrouter_lookup_local_endpoint(hdr.dst_cid); + if (!ept) { + spin_unlock_irqrestore(&local_endpoints_lock, flags); + 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(&ept->incomplete_lock); + 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(&ept->incomplete_lock); + goto packet_complete; + } + spin_unlock(&ept->incomplete_lock); + spin_unlock_irqrestore(&local_endpoints_lock, flags); + goto done; + } + } + spin_unlock(&ept->incomplete_lock); + spin_unlock_irqrestore(&local_endpoints_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; + + spin_lock_irqsave(&local_endpoints_lock, flags); + ept = rpcrouter_lookup_local_endpoint(hdr.dst_cid); + if (!ept) { + spin_unlock_irqrestore(&local_endpoints_lock, flags); + DIAG("no local ept for cid %08x\n", hdr.dst_cid); + kfree(frag); + kfree(pkt); + goto done; + } + if (!PACMARK_LAST(pm)) { + spin_lock(&ept->incomplete_lock); + list_add_tail(&pkt->list, &ept->incomplete); + spin_unlock(&ept->incomplete_lock); + spin_unlock_irqrestore(&local_endpoints_lock, flags); + goto done; + } + +packet_complete: + spin_lock(&ept->read_q_lock); + 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(&ept->read_q_lock); + spin_unlock_irqrestore(&local_endpoints_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; + union rr_control_msg ctl; + + ctl.cmd = RPCROUTER_CTRL_CMD_BYE; + mutex_lock(&xprt_info_list_lock); + while (!list_empty(&xprt_info_list)) { + xprt_info = list_first_entry(&xprt_info_list, + struct rpcrouter_xprt_info, list); + xprt_info->abort_data_read = 1; + wake_up(&xprt_info->read_wait); + rpcrouter_send_control_msg(xprt_info, &ctl); + xprt_info->xprt->close(); + list_del(&xprt_info->list); + mutex_unlock(&xprt_info_list_lock); + + flush_workqueue(xprt_info->workqueue); + destroy_workqueue(xprt_info->workqueue); + wake_lock_destroy(&xprt_info->wakelock); + kfree(xprt_info); + + mutex_lock(&xprt_info_list_lock); + } + 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 0000000000000000000000000000000000000000..1da7579aef89cd9ad2635bb0fec99815d1559611 --- /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 0000000000000000000000000000000000000000..d94125e87d3c0b86642f7ec7f170af4ded867854 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_clients.c @@ -0,0 +1,922 @@ +/* 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 + +#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); + spin_lock_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; + unsigned long flags; + + spin_lock_irqsave(&client->cb_list_lock, flags); + list_for_each_entry_safe(cb_item, tmp_cb_item, + &client->cb_list, list) { + list_del(&cb_item->list); + kfree(cb_item); + } + spin_unlock_irqrestore(&client->cb_list_lock, flags); +} + +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; + unsigned long flags; + + if (cb_func == NULL) + return MSM_RPC_CLIENT_NULL_CB_ID; + + spin_lock_irqsave(&client->cb_list_lock, flags); + list_for_each_entry(cb_item, &client->cb_list, list) { + if (cb_item->cb_func == cb_func) { + spin_unlock_irqrestore(&client->cb_list_lock, flags); + return cb_item->cb_id; + } + } + spin_unlock_irqrestore(&client->cb_list_lock, flags); + + 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; + + spin_lock_irqsave(&client->cb_list_lock, flags); + list_add_tail(&cb_item->list, &client->cb_list); + spin_unlock_irqrestore(&client->cb_list_lock, flags); + + 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; + unsigned long flags; + + spin_lock_irqsave(&client->cb_list_lock, flags); + list_for_each_entry(cb_item, &client->cb_list, list) { + if (cb_item->cb_id == cb_id) { + spin_unlock_irqrestore(&client->cb_list_lock, flags); + return cb_item->cb_func; + } + } + spin_unlock_irqrestore(&client->cb_list_lock, flags); + 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; + unsigned long flags; + + if (cb_func == NULL) + return; + + spin_lock_irqsave(&client->cb_list_lock, flags); + 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); + spin_unlock_irqrestore(&client->cb_list_lock, flags); + return; + } + } + spin_unlock_irqrestore(&client->cb_list_lock, flags); +} +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 0000000000000000000000000000000000000000..e84d2130b863581ac81624e439c91177b885ec3c --- /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 0000000000000000000000000000000000000000..3561c2198e204c66985a25582843a4f6e3f22c78 --- /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 0000000000000000000000000000000000000000..179351617543940afbed6dd169db0e906d47cd3b --- /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 0000000000000000000000000000000000000000..68e0f41a35aafd6536ef9b91dbae5cd8f189ddd4 --- /dev/null +++ b/arch/arm/mach-msm/smd_tty.c @@ -0,0 +1,604 @@ +/* arch/arm/mach-msm/smd_tty.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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 "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; + int is_open; + wait_queue_head_t ch_opened_wait_queue; + spinlock_t reset_lock; + struct smd_config *smd; +}; + +/** + * SMD port configuration. + * + * @tty_dev_index Index into smd_tty[] + * @port_name Name of the SMD port + * @dev_name Name of the TTY Device (if NULL, @port_name is used) + * @edge SMD edge + */ +struct smd_config { + uint32_t tty_dev_index; + const char *port_name; + const char *dev_name; + uint32_t edge; +}; + +static struct smd_config smd_configs[] = { + {0, "DS", NULL, SMD_APPS_MODEM}, + {1, "APPS_FM", NULL, SMD_APPS_WCNSS}, + {2, "APPS_RIVA_BT_ACL", NULL, SMD_APPS_WCNSS}, + {3, "APPS_RIVA_BT_CMD", NULL, SMD_APPS_WCNSS}, + {4, "MBALBRIDGE", NULL, SMD_APPS_MODEM}, + {5, "APPS_RIVA_ANT_CMD", NULL, SMD_APPS_WCNSS}, + {6, "APPS_RIVA_ANT_DATA", NULL, SMD_APPS_WCNSS}, + {7, "DATA1", NULL, SMD_APPS_MODEM}, + {11, "DATA11", NULL, SMD_APPS_MODEM}, + {21, "DATA21", NULL, SMD_APPS_MODEM}, + {27, "GPSNMEA", NULL, SMD_APPS_MODEM}, + {36, "LOOPBACK", "LOOPBACK_TTY", SMD_APPS_MODEM}, +}; +#define DS_IDX 0 +#define LOOPBACK_IDX 36 + +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; + unsigned long flags; + + spin_lock_irqsave(&info->reset_lock, flags); + if (info->is_open) { + spin_unlock_irqrestore(&info->reset_lock, flags); + tasklet_hi_schedule(&info->tty_tsklt); + return; + } + spin_unlock_irqrestore(&info->reset_lock, flags); +} + +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: + spin_lock_irqsave(&info->reset_lock, flags); + if (!info->is_open) { + spin_unlock_irqrestore(&info->reset_lock, flags); + break; + } + spin_unlock_irqrestore(&info->reset_lock, flags); + /* 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; + info->is_open = 1; + wake_up_interruptible(&info->ch_opened_wait_queue); + 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; + info->is_open = 0; + wake_up_interruptible(&info->ch_opened_wait_queue); + 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; + unsigned int n = tty->index; + struct smd_tty_info *info; + const char *peripheral = NULL; + + + if (n >= MAX_SMD_TTYS || !smd_tty[n].smd) + return -ENODEV; + + info = smd_tty + n; + + mutex_lock(&smd_tty_lock); + tty->driver_data = info; + + if (info->open_count++ == 0) { + peripheral = smd_edge_to_subsystem(smd_tty[n].smd->edge); + if (peripheral) { + info->pil = pil_get(peripheral); + 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 == LOOPBACK_IDX) { + 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_tty[n].smd->port_name); + if (!info->ch) { + res = smd_named_open_on_edge(smd_tty[n].smd->port_name, + smd_tty[n].smd->edge, + &info->ch, info, + smd_tty_notify); + if (res < 0) { + pr_err("%s: %s open failed %d\n", __func__, + smd_tty[n].smd->port_name, res); + goto release_pil; + } + + res = wait_event_interruptible_timeout( + info->ch_opened_wait_queue, + info->is_open, (2 * HZ)); + if (res == 0) + res = -ETIMEDOUT; + if (res < 0) { + pr_err("%s: wait for %s smd_open failed %d\n", + __func__, smd_tty[n].smd->port_name, + res); + goto release_pil; + } + res = 0; + } + } + +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; + unsigned long flags; + + if (info == 0) + return; + + mutex_lock(&smd_tty_lock); + if (--info->open_count == 0) { + spin_lock_irqsave(&info->reset_lock, flags); + info->is_open = 0; + spin_unlock_irqrestore(&info->reset_lock, flags); + 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; + unsigned long flags; + + spin_lock_irqsave(&info->reset_lock, flags); + if (info->is_open) { + spin_unlock_irqrestore(&info->reset_lock, flags); + tasklet_hi_schedule(&info->tty_tsklt); + return; + } + spin_unlock_irqrestore(&info->reset_lock, flags); +} + +/* + * 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) +{ + int n; + int idx; + + for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) { + idx = smd_configs[n].tty_dev_index; + + if (!smd_configs[n].dev_name) + continue; + + if (pdev->id == smd_configs[n].edge && + !strncmp(pdev->name, smd_configs[n].dev_name, + SMD_MAX_CH_NAME_LEN)) { + complete_all(&smd_tty[idx].ch_allocated); + return 0; + } + } + pr_err("%s: unknown device '%s'\n", __func__, pdev->name); + + return -ENODEV; +} + +static struct tty_driver *smd_tty_driver; + +static int __init smd_tty_init(void) +{ + int ret; + int n; + int idx; + + 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) { + put_tty_driver(smd_tty_driver); + pr_err("%s: driver registration failed %d\n", __func__, ret); + return ret; + } + + for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) { + idx = smd_configs[n].tty_dev_index; + + if (smd_configs[n].dev_name == NULL) + smd_configs[n].dev_name = smd_configs[n].port_name; + + if (idx == DS_IDX) { + /* + * DS port uses the kernel API starting with + * 8660 Fusion. Only register the userspace + * platform device for older targets. + */ + int legacy_ds = 0; + + legacy_ds |= cpu_is_msm7x01() || cpu_is_msm7x25(); + legacy_ds |= cpu_is_msm7x27() || cpu_is_msm7x30(); + legacy_ds |= cpu_is_qsd8x50() || cpu_is_msm8x55(); + /* + * use legacy mode for 8660 Standalone (subtype 0) + */ + legacy_ds |= cpu_is_msm8x60() && + (socinfo_get_platform_subtype() == 0x0); + + if (!legacy_ds) + continue; + } + + tty_register_device(smd_tty_driver, idx, 0); + init_completion(&smd_tty[idx].ch_allocated); + + /* register platform device */ + smd_tty[idx].driver.probe = smd_tty_dummy_probe; + smd_tty[idx].driver.driver.name = smd_configs[n].dev_name; + smd_tty[idx].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[idx].reset_lock); + smd_tty[idx].is_open = 0; + init_waitqueue_head(&smd_tty[idx].ch_opened_wait_queue); + ret = platform_driver_register(&smd_tty[idx].driver); + + if (ret) { + pr_err("%s: init failed %d (%d)\n", __func__, idx, ret); + smd_tty[idx].driver.probe = NULL; + goto out; + } + smd_tty[idx].smd = &smd_configs[n]; + } + INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker); + return 0; + +out: + /* unregister platform devices */ + for (n = 0; n < ARRAY_SIZE(smd_configs); ++n) { + idx = smd_configs[n].tty_dev_index; + + if (smd_tty[idx].driver.probe) { + platform_driver_unregister(&smd_tty[idx].driver); + tty_unregister_device(smd_tty_driver, idx); + } + } + + 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 0000000000000000000000000000000000000000..2e9a97c0dc5c47e2068fccc52bef060bc64d8799 --- /dev/null +++ b/arch/arm/mach-msm/smem_log.c @@ -0,0 +1,2036 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* + * Legacy targets use the 32KHz hardware timer and new targets will use + * the scheduler timer scaled to a 32KHz tick count. + * + * As testing on legacy targets permits, we will move them to use + * sched_clock() and eventually remove the conditiona compilation. + */ +#if defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_MSM8X60) \ + || defined(CONFIG_ARCH_FSM9XXX) +#define TIMESTAMP_ADDR (MSM_TMR_BASE + 0x08) +#elif defined(CONFIG_ARCH_APQ8064) || defined(CONFIG_ARCH_MSM7X01A) || \ + defined(CONFIG_ARCH_MSM7x25) || defined(CONFIG_ARCH_MSM7X27) || \ + defined(CONFIG_ARCH_MSM7X27A) || defined(CONFIG_ARCH_MSM8960) || \ + defined(CONFIG_ARCH_MSM9615) || defined(CONFIG_ARCH_QSD8X50) +#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" }, + { SMEM_LOG_PROC_ID_WCNSS, "WCNSS" }, +}; + +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 + +#ifdef TIMESTAMP_ADDR +/* legacy timestamp using 32.768KHz clock */ +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; +} +#else +static inline unsigned int read_timestamp(void) +{ + unsigned long long val; + + /* SMEM LOG uses a 32.768KHz timestamp */ + val = sched_clock() * 32768U; + do_div(val, 1000000000U); + + return (unsigned int)val; +} +#endif + +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; + + if (!inst->idx) { + pr_err("%s: invalid write index\n", __func__); + return; + } + + 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_alloc2(SMEM_SMEM_LOG_EVENTS, + SMEM_LOG_EVENTS_SIZE); + inst[GEN].idx = (uint32_t *)smem_alloc2(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_alloc2(SMEM_SMEM_STATIC_LOG_EVENTS, + SMEM_STATIC_LOG_EVENTS_SIZE); + inst[STA].idx = (uint32_t *)smem_alloc2(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_alloc2(SMEM_SMEM_LOG_POWER_EVENTS, + SMEM_POWER_LOG_EVENTS_SIZE); + inst[POW].idx = (uint32_t *)smem_alloc2(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 *local_inst; + + local_inst = fp->private_data; + + if (!local_inst->idx) + return -ENODEV; + + remote_spin_lock_irqsave(local_inst->remote_spinlock, flags); + + orig_idx = *local_inst->idx; + idx = orig_idx; + + while (1) { + idx--; + if (idx < 0) + idx = local_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, &local_inst->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(local_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; + if (!inst->idx) + return -ENODEV; + + 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) +{ + 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) { + if (inst[GEN].events) + fp->private_data = &inst[GEN]; + else + return -ENODEV; + } else if (arg == SMIOC_STATIC_LOG) { + if (inst[STA].events) + fp->private_data = &inst[STA]; + else + return -ENODEV; + } 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; + + if (!inst->idx) + return 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; + + if (!inst[GEN].idx || !inst[GEN].events) + return -ENODEV; + + 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; + + if (!inst[GEN].idx || !inst[GEN].events) + return -ENODEV; + + 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; + + if (!inst[STA].idx || !inst[STA].events) + return -ENODEV; + + 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; + + if (!inst[STA].idx || !inst[STA].events) + return -ENODEV; + + 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; + + if (!inst[POW].idx || !inst[POW].events) + return -ENODEV; + + 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; + + if (!inst[POW].idx || !inst[POW].events) + return -ENODEV; + + 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; + int bsize = 0; + int (*fill)(char *, int, uint32_t) = file->private_data; + if (!(*ppos)) { + bsize = fill(debug_buffer, EVENTS_PRINT_SIZE, 0); + + if (bsize < 0) + bsize = scnprintf(debug_buffer, + EVENTS_PRINT_SIZE, "Log not available\n"); + } + 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); + if (bsize < 0) { + if (*ppos == 0) + bsize = scnprintf(buffer, count, "Log not available\n"); + else + bsize = 0; + } + + DBG("%s: count %d bsize %d\n", __func__, count, bsize); + if (copy_to_user(buf, buffer, bsize)) { + kfree(buffer); + return -EFAULT; + } + *ppos += bsize; + 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 smsm_driver_state_notifier(struct notifier_block *this, + unsigned long code, + void *_cmd) +{ + int ret = 0; + if (code & SMSM_INIT) { + if (!smem_log_initialized) + ret = smem_log_initialize(); + } + return ret; +} + +static struct notifier_block nb = { + .notifier_call = smsm_driver_state_notifier, +}; + +static int __init smem_log_init(void) +{ + return smsm_driver_state_notifier_register(&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 0000000000000000000000000000000000000000..b047cf440a1fc6683b78e10c47988b5a878f32d1 --- /dev/null +++ b/arch/arm/mach-msm/socinfo.c @@ -0,0 +1,799 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_MTP = 8, + HW_PLATFORM_LIQUID = 9, + /* Dragonboard platform id is assigned as 10 in CDT */ + HW_PLATFORM_DRAGON = 10, + 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", + [HW_PLATFORM_MTP] = "MTP", + [HW_PLATFORM_LIQUID] = "Liquid", + [HW_PLATFORM_DRAGON] = "Dragon" +}; + +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, + + /* 7x27AA ID */ + [101] = MSM_CPU_7X27AA, + [102] = MSM_CPU_7X27AA, + [103] = MSM_CPU_7X27AA, + + /* 9x15 ID */ + [104] = MSM_CPU_9615, + [105] = MSM_CPU_9615, + [106] = MSM_CPU_9615, + [107] = MSM_CPU_9615, + + /* 8064 IDs */ + [109] = MSM_CPU_8064, + + /* 8930 IDs */ + [116] = MSM_CPU_8930, + [117] = MSM_CPU_8930, + [118] = MSM_CPU_8930, + [119] = MSM_CPU_8930, + + /* 8627 IDs */ + [120] = MSM_CPU_8627, + [121] = MSM_CPU_8627, + + /* 8660A ID */ + [122] = MSM_CPU_8960, + + /* 8260A ID */ + [123] = MSM_CPU_8960, + + /* 8060A ID */ + [124] = MSM_CPU_8960, + + /* Copper IDs */ + [126] = MSM_CPU_COPPER, + + /* 8625 IDs */ + [127] = MSM_CPU_8625, + [128] = MSM_CPU_8625, + [129] = MSM_CPU_8625, + + /* 8064 MPQ ID */ + [130] = MSM_CPU_8064, + + /* 7x25AB IDs */ + [131] = MSM_CPU_7X25AB, + [132] = MSM_CPU_7X25AB, + [133] = MSM_CPU_7X25AB, + + /* 9625 IDs */ + [134] = MSM_CPU_9625, + + /* 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, +}; + +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); + +static void * __init setup_dummy_socinfo(void) +{ + if (machine_is_msm8960_rumi3() || machine_is_msm8960_sim() || + machine_is_msm8960_cdp()) + dummy_socinfo.id = 87; + else if (machine_is_apq8064_rumi3() || machine_is_apq8064_sim()) + dummy_socinfo.id = 109; + else if (machine_is_msm9615_mtp() || machine_is_msm9615_cdp()) + dummy_socinfo.id = 104; + else if (early_machine_is_copper()) { + dummy_socinfo.id = 126; + strlcpy(dummy_socinfo.build_id, "copper - ", + sizeof(dummy_socinfo.build_id)); + } else if (early_machine_is_msm9625()) { + dummy_socinfo.id = 134; + strlcpy(dummy_socinfo.build_id, "msm9625 - ", + sizeof(dummy_socinfo.build_id)); + } else if (machine_is_msm8625_rumi3()) + dummy_socinfo.id = 127; + strlcat(dummy_socinfo.build_id, "Dummy socinfo", + sizeof(dummy_socinfo.build_id)); + 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; +} + +const 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; +} + +const int read_msm_cpu_type(void) +{ + if (machine_is_msm8960_sim() || machine_is_msm8960_rumi3()) + return MSM_CPU_8960; + + if (socinfo_get_msm_cpu() != MSM_CPU_UNKNOWN) + return socinfo_get_msm_cpu(); + + switch (read_cpuid_id()) { + case 0x510F02D0: + case 0x510F02D2: + case 0x510F02D4: + return MSM_CPU_8X60; + + case 0x510F04D0: + case 0x510F04D1: + case 0x510F04D2: + case 0x511F04D0: + case 0x512F04D0: + return MSM_CPU_8960; + + case 0x51404D11: /* We can't get here unless we are in bringup */ + return MSM_CPU_8930; + + case 0x510F06F0: + return MSM_CPU_8064; + + default: + return MSM_CPU_UNKNOWN; + }; +} + +const int cpu_is_krait_v1(void) +{ + switch (read_cpuid_id()) { + case 0x510F04D0: + case 0x510F04D1: + case 0x510F04D2: + return 1; + + default: + 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 0000000000000000000000000000000000000000..b6d532424f59b16f453626b3e3e36ef3f897820c --- /dev/null +++ b/arch/arm/mach-msm/spm-v2.c @@ -0,0 +1,467 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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" + +#define MSM_SPM_PMIC_STATE_IDLE 0 + +#define SAW2_V1_VER_REG 0x04 +#define SAW2_V2_VER_REG 0xfd0 + +#define SAW2_MAJOR_2 2 + + +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 +); + + +static uint32_t msm_spm_reg_offsets_v1[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, +}; + +static uint32_t msm_spm_reg_offsets_v2[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_SPM_STS] = 0x0C, + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, + [MSM_SPM_REG_SAW2_RST] = 0x18, + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, +}; + +/****************************************************************************** + * Internal helper functions + *****************************************************************************/ + +static inline uint32_t msm_spm_drv_get_num_spm_entry( + struct msm_spm_driver_data *dev) +{ + return 32; +} + +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 + dev->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 + + dev->reg_offsets[reg_index]); +} + +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; +} + +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); + + if (dev->major == SAW2_MAJOR_2) + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; + else + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 18) & 0x1; +} + +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; + + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_1] &= ~0x3F; + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_1] |= (vlevel & 0x3F); +} + +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, + uint32_t vlevel) +{ + unsigned int pmic_data = 0; + + pmic_data |= vlevel; + pmic_data |= (dev->vctl_port & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; + + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] |= pmic_data; +} + +static inline void msm_spm_drv_apcs_set_vctl(struct msm_spm_driver_data *dev, + unsigned int vlevel) +{ + if (dev->major == SAW2_MAJOR_2) + return msm_spm_drv_set_vctl2(dev, vlevel); + else + return msm_spm_drv_set_vctl(dev, vlevel); +} + +static inline uint32_t msm_spm_drv_get_sts_pmic_state( + struct msm_spm_driver_data *dev) +{ + if (dev->major == SAW2_MAJOR_2) { + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & + 0x03; + } else { + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS0); + 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) +{ + if (dev->major == SAW2_MAJOR_2) { + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; + } else { + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS1); + return dev->reg_shadow[MSM_SPM_REG_SAW2_STS1] & 0xFF; + } +} + +static inline uint32_t msm_spm_drv_get_saw2_ver(struct msm_spm_driver_data *dev, + uint32_t *major, uint32_t *minor) +{ + int ret = -ENODEV; + uint32_t val = 0; + + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_VERSION); + val = dev->reg_shadow[MSM_SPM_REG_SAW2_VERSION]; + + if (dev->ver_reg == SAW2_V2_VER_REG) { + *major = (val >> 28) & 0xF; + *minor = (val >> 16) & 0xFFF; + ret = 0; + } else if (dev->ver_reg == SAW2_V1_VER_REG) { + *major = (val >> 4) & 0xF; + *minor = val & 0xF; + ret = 0; + } + + return ret; +} + +/****************************************************************************** + * 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 + + dev->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 cmd_w; + uint32_t offset_w = *offset / 4; + uint8_t last_cmd; + + if (!cmd) + return -EINVAL; + + while (1) { + int i; + cmd_w = 0; + last_cmd = 0; + cmd_w = dev->reg_seq_entry_shadow[offset_w]; + + for (i = (*offset % 4) ; i < 4; i++) { + last_cmd = *(cmd++); + cmd_w |= last_cmd << (i * 8); + (*offset)++; + if (last_cmd == 0x0f) + break; + } + + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + + return 0; +} + +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); + + 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__, + dev->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_pmic_arb_present(dev)) + return -ENOSYS; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: requesting vlevel 0x%x\n", + __func__, vlevel); + + msm_spm_drv_apcs_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); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_1); + mb(); + + /* Wait for PMIC state to return to idle or until timeout */ + timeout_us = dev->vctl_timeout_us; + 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; + } + } + + 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 msm_spm_drv_set_phase(struct msm_spm_driver_data *dev, + unsigned int phase_cnt) +{ + unsigned int pmic_data = 0; + unsigned int timeout_us = 0; + + if (dev->major != SAW2_MAJOR_2) + return -ENODEV; + + pmic_data |= phase_cnt & 0xFF; + pmic_data |= (dev->phase_port & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); + mb(); + + /* Wait for PMIC state to return to idle or until timeout */ + timeout_us = dev->vctl_timeout_us; + while (msm_spm_drv_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) { + if (!timeout_us) + goto set_phase_bail; + + if (timeout_us > 10) { + udelay(10); + timeout_us -= 10; + } else { + udelay(timeout_us); + timeout_us = 0; + } + } + + if (msm_spm_drv_get_sts_curr_pmic_data(dev) != phase_cnt) + goto set_phase_bail; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: done, remaining timeout %uus\n", + __func__, timeout_us); + + return 0; + +set_phase_bail: + pr_err("%s: failed, remaining timeout %uus, phase count %d\n", + __func__, timeout_us, msm_spm_drv_get_sts_curr_pmic_data(dev)); + return -EIO; + +} + +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev) +{ + int i; + + for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++) + msm_spm_drv_flush_shadow(dev, i); + + msm_spm_drv_flush_seq_entry(dev); + mb(); +} + +int __devinit 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); + + if (dev->ver_reg == SAW2_V2_VER_REG) + dev->reg_offsets = msm_spm_reg_offsets_v2; + else + dev->reg_offsets = msm_spm_reg_offsets_v1; + + dev->vctl_port = data->vctl_port; + dev->phase_port = data->phase_port; + 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(); + + msm_spm_drv_get_saw2_ver(dev, &dev->major, &dev->minor); + + num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + dev->reg_seq_entry_shadow = + kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, + GFP_KERNEL); + + if (!dev->reg_seq_entry_shadow) + return -ENOMEM; + + return 0; +} diff --git a/arch/arm/mach-msm/spm.c b/arch/arm/mach-msm/spm.c new file mode 100644 index 0000000000000000000000000000000000000000..4654fba0b6fb1a6664d1a6df7ac3dee4009933e9 --- /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 0000000000000000000000000000000000000000..154303b6675f8e338b911c7ae4edf2992287ca8c --- /dev/null +++ b/arch/arm/mach-msm/spm.h @@ -0,0 +1,270 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_CFG, + 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_AVS_LIMIT, + MSM_SPM_REG_SAW2_AVS_DLY, + MSM_SPM_REG_SAW2_SPM_DLY, + MSM_SPM_REG_SAW2_PMIC_DATA_0, + MSM_SPM_REG_SAW2_PMIC_DATA_1, + MSM_SPM_REG_SAW2_PMIC_DATA_2, + MSM_SPM_REG_SAW2_PMIC_DATA_3, + MSM_SPM_REG_SAW2_PMIC_DATA_4, + MSM_SPM_REG_SAW2_PMIC_DATA_5, + MSM_SPM_REG_SAW2_PMIC_DATA_6, + MSM_SPM_REG_SAW2_PMIC_DATA_7, + MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_SAW2_ID, + MSM_SPM_REG_SAW2_SECURE, + MSM_SPM_REG_SAW2_STS0, + MSM_SPM_REG_SAW2_STS1, + MSM_SPM_REG_SAW2_VCTL, + MSM_SPM_REG_SAW2_SEQ_ENTRY, + MSM_SPM_REG_SAW2_SPM_STS, + MSM_SPM_REG_SAW2_AVS_STS, + MSM_SPM_REG_SAW2_PMIC_STS, + MSM_SPM_REG_SAW2_VERSION, + + 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]; + + uint32_t ver_reg; + uint32_t vctl_port; + uint32_t phase_port; + + uint8_t awake_vlevel; + uint32_t vctl_timeout_us; + uint32_t avs_timeout_us; + + uint32_t num_modes; + struct msm_spm_seq_entry *modes; +}; +#endif + +#if defined(CONFIG_MSM_SPM_V1) || defined(CONFIG_MSM_SPM_V2) + +/* Public functions */ + +/** + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode + * @mode: SPM LPM mode to enter + * @notify_rpm: Notify RPM in this mode + */ +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); + +/** + * msm_spm_set_vdd(): Set core voltage + * @cpu: core id + * @vlevel: Encoded PMIC data. + */ +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); + +/** + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core + * @cpu: core id + */ +int msm_spm_turn_on_cpu_rail(unsigned int cpu); + + +/* Internal low power management specific functions */ + +/** + * msm_spm_allow_x_cpu_set_vdd(): Turn on/off cross calling to set voltage + * @allowed: boolean to indicate on/off. + */ +void msm_spm_allow_x_cpu_set_vdd(bool allowed); + +/** + * msm_spm_reinit(): Reinitialize SPM registers + */ +void msm_spm_reinit(void); + +/** + * msm_spm_init(): Board initalization function + * @data: platform specific SPM register configuration data + * @nr_devs: Number of SPM devices being initialized + */ +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs); + +/** + * msm_spm_device_init(): Device tree initialization function + */ +int msm_spm_device_init(void); + +#if defined(CONFIG_MSM_L2_SPM) + +/* Public functions */ + +/** + * msm_spm_l2_set_low_power_mode(): Configure L2 SPM start address + * for low power mode + * @mode: SPM LPM mode to enter + * @notify_rpm: Notify RPM in this mode + */ +int msm_spm_l2_set_low_power_mode(unsigned int mode, bool notify_rpm); + +/** + * msm_spm_apcs_set_vdd(): Set Apps processor core sub-system voltage + * @vlevel: Encoded PMIC data. + */ +int msm_spm_apcs_set_vdd(unsigned int vlevel); + +/** + * msm_spm_apcs_set_phase(): Set number of SMPS phases. + * phase_cnt: Number of phases to be set active + */ +int msm_spm_apcs_set_phase(unsigned int phase_cnt); + +/* Internal low power management specific functions */ + +/** + * msm_spm_l2_init(): Board initialization function + * @data: SPM target specific register configuration + */ +int msm_spm_l2_init(struct msm_spm_platform_data *data); + +/** + * msm_spm_l2_reinit(): Reinitialize L2 SPM registers + */ +void msm_spm_l2_reinit(void); + +#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; +} +static inline void msm_spm_l2_reinit(void) +{ + /* empty */ +} + +static inline int msm_spm_apcs_set_vdd(unsigned int vlevel) +{ + return -ENOSYS; +} + +static inline int msm_spm_apcs_set_phase(unsigned int phase_cnt) +{ + 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 */ +} + +static inline int msm_spm_turn_on_cpu_rail(unsigned int cpu) +{ + return -ENOSYS; +} + +static inline int msm_spm_device_init(void) +{ + return -ENOSYS; +} + +#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 0000000000000000000000000000000000000000..a77efe06660efc7180f3c154b365870ad114fd61 --- /dev/null +++ b/arch/arm/mach-msm/spm_devices.c @@ -0,0 +1,394 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 struct msm_spm_device msm_spm_l2_device; +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); +} +EXPORT_SYMBOL(msm_spm_allow_x_cpu_set_vdd); + +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; +} +EXPORT_SYMBOL(msm_spm_set_vdd); + +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 __devinit 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; + + dev->reg_data.ver_reg = data->ver_reg; + ret = msm_spm_drv_init(&dev->reg_data, data); + + if (ret) + goto spm_failed_init; + + for (i = 0; i < dev->num_modes; i++) { + + /* Default offset is 0 and gets updated as we write more + * sequences into SPM + */ + dev->modes[i].start_addr = offset; + 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; + } + 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_turn_on_cpu_rail(unsigned int cpu) +{ + uint32_t val = 0; + uint32_t timeout = 0; + void *reg = NULL; + void *saw_bases[] = { + 0, + MSM_SAW1_BASE, + MSM_SAW2_BASE, + MSM_SAW3_BASE + }; + + if (cpu == 0 || cpu >= num_possible_cpus()) + return -EINVAL; + + reg = saw_bases[cpu]; + + if (cpu_is_msm8960() || cpu_is_msm8930() || cpu_is_apq8064()) { + val = 0xA4; + reg += 0x14; + timeout = 512; + } else { + return -ENOSYS; + } + + writel_relaxed(val, reg); + mb(); + udelay(timeout); + + return 0; +} +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail); + +void msm_spm_reinit(void) +{ + unsigned int cpu; + for_each_possible_cpu(cpu) + msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu)); +} +EXPORT_SYMBOL(msm_spm_reinit); + +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); +} +EXPORT_SYMBOL(msm_spm_set_low_power_mode); + +/* Board file init function */ +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; +} + +#ifdef CONFIG_MSM_L2_SPM + +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); +} +EXPORT_SYMBOL(msm_spm_l2_set_low_power_mode); + +void msm_spm_l2_reinit(void) +{ + msm_spm_drv_reinit(&msm_spm_l2_device.reg_data); +} +EXPORT_SYMBOL(msm_spm_l2_reinit); + +int msm_spm_apcs_set_vdd(unsigned int vlevel) +{ + return msm_spm_drv_set_vdd(&msm_spm_l2_device.reg_data, vlevel); +} +EXPORT_SYMBOL(msm_spm_apcs_set_vdd); + +int msm_spm_apcs_set_phase(unsigned int phase_cnt) +{ + return msm_spm_drv_set_phase(&msm_spm_l2_device.reg_data, phase_cnt); +} +EXPORT_SYMBOL(msm_spm_apcs_set_phase); + +/* Board file init function */ +int __init msm_spm_l2_init(struct msm_spm_platform_data *data) +{ + return msm_spm_dev_init(&msm_spm_l2_device, data); +} +#endif + +static int __init msm_spm_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + int cpu = 0; + int i = 0; + struct device_node *node = pdev->dev.of_node; + struct msm_spm_platform_data spm_data; + char *key = NULL; + uint32_t val = 0; + struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; + size_t len = 0; + struct msm_spm_device *dev = NULL; + struct resource *res = NULL; + uint32_t mode_count = 0; + + struct spm_of { + char *key; + uint32_t id; + }; + + struct spm_of spm_of_data[] = { + {"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG}, + {"qcom,saw2-avs-ctl", MSM_SPM_REG_SAW2_AVS_CTL}, + {"qcom,saw2-avs-hysteresis", MSM_SPM_REG_SAW2_AVS_HYSTERESIS}, + {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL}, + {"qcom,saw2-pmic-dly", MSM_SPM_REG_SAW2_PMIC_DLY}, + {"qcom,saw2-avs-limit", MSM_SPM_REG_SAW2_AVS_LIMIT}, + {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY}, + {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0}, + {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1}, + {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2}, + {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3}, + {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4}, + {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5}, + {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6}, + {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7}, + }; + + struct mode_of { + char *key; + uint32_t id; + uint32_t notify_rpm; + }; + + struct mode_of mode_of_data[] = { + {"qcom,spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0}, + {"qcom,spm-cmd-ret", MSM_SPM_MODE_POWER_RETENTION, 0}, + {"qcom,spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0}, + {"qcom,spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1}, + }; + + BUG_ON(ARRAY_SIZE(mode_of_data) > MSM_SPM_MODE_NR); + memset(&spm_data, 0, sizeof(struct msm_spm_platform_data)); + memset(&modes, 0, + (MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto fail; + + spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!spm_data.reg_base_addr) + return -ENOMEM; + + key = "qcom,core-id"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + cpu = val; + + key = "qcom,saw2-ver-reg"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + spm_data.ver_reg = val; + + key = "qcom,vctl-timeout-us"; + ret = of_property_read_u32(node, key, &val); + if (!ret) + spm_data.vctl_timeout_us = val; + + /* optional */ + key = "qcom,vctl-port"; + ret = of_property_read_u32(node, key, &val); + if (!ret) + spm_data.vctl_port = val; + + /* optional */ + key = "qcom,phase-port"; + ret = of_property_read_u32(node, key, &val); + if (!ret) + spm_data.phase_port = val; + + for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) { + ret = of_property_read_u32(node, spm_of_data[i].key, &val); + if (ret) + continue; + spm_data.reg_init_values[spm_of_data[i].id] = val; + } + + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) { + key = mode_of_data[i].key; + modes[mode_count].cmd = + (uint8_t *)of_get_property(node, key, &len); + if (!modes[mode_count].cmd) + continue; + modes[mode_count].mode = mode_of_data[i].id; + modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm; + mode_count++; + } + + spm_data.modes = modes; + spm_data.num_modes = mode_count; + + /* + * Device with id 0..NR_CPUS are SPM for apps cores + * Device with id 0xFFFF is for L2 SPM. + */ + if (cpu >= 0 && cpu < num_possible_cpus()) + dev = &per_cpu(msm_cpu_spm_device, cpu); + else + dev = &msm_spm_l2_device; + + ret = msm_spm_dev_init(dev, &spm_data); + if (ret < 0) + pr_warn("%s():failed core-id:%u ret:%d\n", __func__, cpu, ret); + + return ret; + +fail: + pr_err("%s: Failed reading node=%s, key=%s\n", + __func__, node->full_name, key); + return -EFAULT; +} + +static __initdata struct of_device_id msm_spm_match_table[] = { + {.compatible = "qcom,spm-v2"}, + {}, +}; + +static __initdata struct platform_driver msm_spm_device_driver = { + .probe = msm_spm_dev_probe, + .driver = { + .name = "spm-v2", + .owner = THIS_MODULE, + .of_match_table = msm_spm_match_table, + }, +}; + +int __init msm_spm_device_init(void) +{ + return platform_driver_register(&msm_spm_device_driver); +} diff --git a/arch/arm/mach-msm/spm_driver.h b/arch/arm/mach-msm/spm_driver.h new file mode 100644 index 0000000000000000000000000000000000000000..f272adb0df505fe5a2d4ca0fee70e1f44140ea0f --- /dev/null +++ b/arch/arm/mach-msm/spm_driver.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 { + uint32_t major; + uint32_t minor; + uint32_t ver_reg; + uint32_t vctl_port; + uint32_t phase_port; + void __iomem *reg_base_addr; + uint32_t vctl_timeout_us; + uint32_t avs_timeout_us; + uint32_t reg_shadow[MSM_SPM_REG_NR]; + uint32_t *reg_seq_entry_shadow; + uint32_t *reg_offsets; +}; + +int msm_spm_drv_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data); +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev); +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); +int msm_spm_drv_set_phase(struct msm_spm_driver_data *dev, + unsigned int phase_cnt); +#endif diff --git a/arch/arm/mach-msm/subsystem_map.c b/arch/arm/mach-msm/subsystem_map.c new file mode 100644 index 0000000000000000000000000000000000000000..fcb8517f09795737c0a3ce77d062123926532d2d --- /dev/null +++ b/arch/arm/mach-msm/subsystem_map.c @@ -0,0 +1,541 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 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 phys; +}; + +static struct rb_root buffer_root; +static struct rb_root phys_root; +DEFINE_MUTEX(msm_buffer_mutex); + +static unsigned long subsystem_to_domain_tbl[] = { + VIDEO_DOMAIN, + VIDEO_DOMAIN, + CAMERA_DOMAIN, + DISPLAY_DOMAIN, + ROTATOR_DOMAIN, + 0xFFFFFFFF +}; + +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 msm_subsystem_get_domain_no(int subsys_id) +{ + if (subsys_id > INVALID_SUBSYS_ID && subsys_id <= MAX_SUBSYSTEM_ID && + subsys_id < ARRAY_SIZE(subsystem_to_domain_tbl)) + return subsystem_to_domain_tbl[subsys_id]; + else + return subsystem_to_domain_tbl[MAX_SUBSYSTEM_ID]; +} + +static unsigned long msm_subsystem_get_partition_no(int subsys_id) +{ + switch (subsys_id) { + case MSM_SUBSYSTEM_VIDEO_FWARE: + return VIDEO_FIRMWARE_POOL; + case MSM_SUBSYSTEM_VIDEO: + return VIDEO_MAIN_POOL; + case MSM_SUBSYSTEM_CAMERA: + case MSM_SUBSYSTEM_DISPLAY: + case MSM_SUBSYSTEM_ROTATOR: + return GEN_POOL; + default: + return 0xFFFFFFFF; + } +} + +phys_addr_t msm_subsystem_check_iova_mapping(int subsys_id, unsigned long iova) +{ + struct iommu_domain *subsys_domain; + + if (!msm_use_iommu()) + /* + * If there is no iommu, Just return the iova in this case. + */ + return iova; + + subsys_domain = msm_get_iommu_domain(msm_subsystem_get_domain_no + (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; + struct iommu_domain *d = NULL; + int map_size = length; + + 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) { + int min_align; + + length = round_up(length, SZ_4K); + + if (flags & MSM_SUBSYSTEM_MAP_IOMMU_2X) + map_size = 2 * length; + else + map_size = length; + + buf->iova = kzalloc(sizeof(unsigned long)*nsubsys, GFP_ATOMIC); + if (!buf->iova) { + err = ERR_PTR(-ENOMEM); + goto outremovephys; + } + + /* + * The alignment must be specified as the exact value wanted + * e.g. 8k alignment must pass (0x2000 | other flags) + */ + min_align = flags & ~(SZ_4K - 1); + + for (i = 0; i < nsubsys; i++) { + unsigned int domain_no, partition_no; + + if (!msm_use_iommu()) { + buf->iova[i] = phys; + continue; + } + + d = msm_get_iommu_domain( + msm_subsystem_get_domain_no(subsys_ids[i])); + + if (!d) { + pr_err("%s: could not get domain for subsystem" + " %d\n", __func__, subsys_ids[i]); + continue; + } + + domain_no = msm_subsystem_get_domain_no(subsys_ids[i]); + partition_no = msm_subsystem_get_partition_no( + subsys_ids[i]); + + ret = msm_allocate_iova_address(domain_no, + partition_no, + map_size, + max(min_align, SZ_4K), + &iova_start); + + if (ret) { + pr_err("%s: could not allocate iova address\n", + __func__); + continue; + } + + temp_phys = phys; + temp_va = iova_start; + for (j = length; j > 0; j -= SZ_4K, + temp_phys += SZ_4K, + temp_va += SZ_4K) { + ret = iommu_map(d, temp_va, temp_phys, + SZ_4K, + (IOMMU_READ | IOMMU_WRITE)); + 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; + + if (flags & MSM_SUBSYSTEM_MAP_IOMMU_2X) + msm_iommu_map_extra + (d, temp_va, length, SZ_4K, + (IOMMU_READ | IOMMU_WRITE)); + } + + } + + node->buf = buf; + node->subsystems = subsys_ids; + node->length = map_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, SZ_4K); +outdomain: + if (flags & MSM_SUBSYSTEM_MAP_IOVA) { + /* Unmap the rest of the current domain, i */ + for (j -= SZ_4K, temp_va -= SZ_4K; + j > 0; temp_va -= SZ_4K, j -= SZ_4K) + iommu_unmap(d, temp_va, SZ_4K); + + /* Unmap all the other domains */ + for (i--; i >= 0; i--) { + unsigned int domain_no, partition_no; + if (!msm_use_iommu()) + continue; + domain_no = msm_subsystem_get_domain_no(subsys_ids[i]); + partition_no = msm_subsystem_get_partition_no( + subsys_ids[i]); + + temp_va = buf->iova[i]; + for (j = length; j > 0; j -= SZ_4K, + temp_va += SZ_4K) + iommu_unmap(d, temp_va, SZ_4K); + msm_free_iova_address(buf->iova[i], domain_no, + partition_no, length); + } + + 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) +{ + struct msm_buffer_node *node; + int i, j, ret; + unsigned long temp_va; + + if (IS_ERR_OR_NULL(buf)) + goto out; + + 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; + } + + if (buf->iova) { + if (msm_use_iommu()) + for (i = 0; i < node->nsubsys; i++) { + struct iommu_domain *subsys_domain; + unsigned int domain_no, partition_no; + + subsys_domain = msm_get_iommu_domain( + msm_subsystem_get_domain_no( + node->subsystems[i])); + + domain_no = msm_subsystem_get_domain_no( + node->subsystems[i]); + partition_no = msm_subsystem_get_partition_no( + node->subsystems[i]); + + temp_va = buf->iova[i]; + for (j = node->length; j > 0; j -= SZ_4K, + temp_va += SZ_4K) { + ret = iommu_unmap(subsys_domain, + temp_va, + SZ_4K); + WARN(ret, "iommu_unmap returned a " + " non-zero value.\n"); + } + msm_free_iova_address(buf->iova[i], domain_no, + partition_no, node->length); + } + 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 0000000000000000000000000000000000000000..f7db54c1b39308e74f6aa1b9e1e3c564a6513df0 --- /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 0000000000000000000000000000000000000000..027aa5b9c801ae54482aac2666bf017b574391c1 --- /dev/null +++ b/arch/arm/mach-msm/subsystem_restart.c @@ -0,0 +1,588 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "subsys-restart: %s(): " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "smd_private.h" + +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_wq_data { + struct subsys_data *subsys; + struct wake_lock ssr_wake_lock; + char wlname[64]; + int use_restart_order; + struct work_struct work; +}; + +struct restart_log { + struct timeval time; + struct subsys_data *subsys; + struct list_head list; +}; + +static int restart_level; +static int enable_ramdumps; +struct workqueue_struct *ssr_wq; + +static LIST_HEAD(restart_log_list); +static LIST_HEAD(subsystem_list); +static DEFINE_MUTEX(subsystem_list_lock); +static DEFINE_MUTEX(soc_order_reg_lock); +static DEFINE_MUTEX(restart_log_mutex); + +/* 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 int restart_level_set(const char *val, struct kernel_param *kp) +{ + int ret; + int old_val = restart_level; + + if (cpu_is_msm9615()) { + pr_err("Only Phase 1 subsystem restart is supported\n"); + return -EINVAL; + } + + 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("Phase %d behavior activated.\n", restart_level); + break; + + default: + restart_level = old_val; + return -EINVAL; + break; + + } + 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 max_restarts; +module_param(max_restarts, int, 0644); + +static long max_history_time = 3600; +module_param(max_history_time, long, 0644); + +static void do_epoch_check(struct subsys_data *subsys) +{ + int n = 0; + struct timeval *time_first = NULL, *curr_time; + struct restart_log *r_log, *temp; + static int max_restarts_check; + static long max_history_time_check; + + mutex_lock(&restart_log_mutex); + + max_restarts_check = max_restarts; + max_history_time_check = max_history_time; + + /* Check if epoch checking is enabled */ + if (!max_restarts_check) + goto out; + + r_log = kmalloc(sizeof(struct restart_log), GFP_KERNEL); + if (!r_log) + goto out; + r_log->subsys = subsys; + do_gettimeofday(&r_log->time); + curr_time = &r_log->time; + INIT_LIST_HEAD(&r_log->list); + + list_add_tail(&r_log->list, &restart_log_list); + + list_for_each_entry_safe(r_log, temp, &restart_log_list, list) { + + if ((curr_time->tv_sec - r_log->time.tv_sec) > + max_history_time_check) { + + pr_debug("Deleted node with restart_time = %ld\n", + r_log->time.tv_sec); + list_del(&r_log->list); + kfree(r_log); + continue; + } + if (!n) { + time_first = &r_log->time; + pr_debug("Time_first: %ld\n", time_first->tv_sec); + } + n++; + pr_debug("Restart_time: %ld\n", r_log->time.tv_sec); + } + + if (time_first && n >= max_restarts_check) { + if ((curr_time->tv_sec - time_first->tv_sec) < + max_history_time_check) + panic("Subsystems have crashed %d times in less than " + "%ld seconds!", max_restarts_check, + max_history_time_check); + } + +out: + mutex_unlock(&restart_log_mutex); +} + +static void subsystem_restart_wq_func(struct work_struct *work) +{ + struct restart_wq_data *r_work = container_of(work, + struct restart_wq_data, work); + 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->use_restart_order) + 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; + } + + pr_debug("[%p]: Attempting to get shutdown lock!\n", current); + + /* Try to acquire shutdown_lock. If this fails, these subsystems are + * already being restarted - return. + */ + if (!mutex_trylock(shutdown_lock)) + goto out; + + pr_debug("[%p]: Attempting to get powerup lock!\n", 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[%p]: Subsystem died during powerup!", + __func__, current); + + do_epoch_check(subsys); + + /* 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); + + pr_debug("[%p]: Starting restart sequence for %s\n", current, + 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("[%p]: Shutting down %s\n", current, + restart_list[i]->name); + + if (restart_list[i]->shutdown(subsys) < 0) + panic("subsys-restart: %s[%p]: Failed to shutdown %s!", + __func__, current, 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[%p]: Ramdump failed.\n", + restart_list[i]->name, current); + } + + _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("[%p]: Powering up %s\n", current, + restart_list[i]->name); + + if (restart_list[i]->powerup(subsys) < 0) + panic("%s[%p]: Failed to powerup %s!", __func__, + current, restart_list[i]->name); + } + + _send_notification_to_order(restart_list, + restart_list_count, + SUBSYS_AFTER_POWERUP); + + pr_info("[%p]: Restart sequence for %s completed.\n", + current, r_work->subsys->name); + + mutex_unlock(powerup_lock); + + mutex_unlock(&soc_order_reg_lock); + + pr_debug("[%p]: Released powerup lock!\n", current); + +out: + wake_unlock(&r_work->ssr_wake_lock); + wake_lock_destroy(&r_work->ssr_wake_lock); + kfree(r_work); +} + +static void __subsystem_restart(struct subsys_data *subsys) +{ + struct restart_wq_data *data = NULL; + int rc; + + pr_debug("Restarting %s [level=%d]!\n", subsys->name, + restart_level); + + data = kzalloc(sizeof(struct restart_wq_data), GFP_ATOMIC); + if (!data) + panic("%s: Unable to allocate memory to restart %s.", + __func__, subsys->name); + + data->subsys = subsys; + + if (restart_level != RESET_SUBSYS_INDEPENDENT) + data->use_restart_order = 1; + + snprintf(data->wlname, sizeof(data->wlname), "ssr(%s)", subsys->name); + wake_lock_init(&data->ssr_wake_lock, WAKE_LOCK_SUSPEND, data->wlname); + wake_lock(&data->ssr_wake_lock); + + INIT_WORK(&data->work, subsystem_restart_wq_func); + rc = queue_work(ssr_wq, &data->work); + if (rc < 0) + panic("%s: Unable to schedule work to restart %s (%d).", + __func__, subsys->name, rc); +} + +int subsystem_restart(const char *subsys_name) +{ + struct subsys_data *subsys; + + if (!subsys_name) { + pr_err("Invalid subsystem name.\n"); + return -EINVAL; + } + + pr_info("Restart sequence requested for %s, restart_level = %d.\n", + subsys_name, restart_level); + + /* List of subsystems is protected by a lock. New subsystems can + * still come in. + */ + subsys = _find_subsystem(subsys_name); + + if (!subsys) { + pr_warn("Unregistered subsystem %s!\n", subsys_name); + return -EINVAL; + } + + switch (restart_level) { + + case RESET_SUBSYS_COUPLED: + case RESET_SUBSYS_INDEPENDENT: + __subsystem_restart(subsys); + break; + + case RESET_SOC: + panic("subsys-restart: Resetting the SoC - %s crashed.", + subsys->name); + 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 ssr_panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct subsys_data *subsys; + + list_for_each_entry(subsys, &subsystem_list, list) + if (subsys->crash_shutdown) + subsys->crash_shutdown(subsys); + return NOTIFY_DONE; +} + +static struct notifier_block panic_nb = { + .notifier_call = ssr_panic_handler, +}; + +static int __init ssr_init_soc_restart_orders(void) +{ + int i; + + atomic_notifier_chain_register(&panic_notifier_list, + &panic_nb); + + 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() || cpu_is_msm8930() || cpu_is_msm9615() || + cpu_is_apq8064()) { + 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; + + ssr_wq = alloc_workqueue("ssr_wq", 0, 0); + + if (!ssr_wq) + panic("Couldn't allocate workqueue for subsystem restart.\n"); + + 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/sysmon.c b/arch/arm/mach-msm/sysmon.c new file mode 100644 index 0000000000000000000000000000000000000000..1305bd1e7e63adbc9725a1bd563b00a714318043 --- /dev/null +++ b/arch/arm/mach-msm/sysmon.c @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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__ +#undef DEBUG + +#include +#include +#include +#include +#include + +#include +#include + +#include "hsic_sysmon.h" +#include "sysmon.h" + +#define TX_BUF_SIZE 50 +#define RX_BUF_SIZE 500 +#define TIMEOUT_MS 5000 + +enum transports { + TRANSPORT_SMD, + TRANSPORT_HSIC, +}; + +struct sysmon_subsys { + struct mutex lock; + struct smd_channel *chan; + bool chan_open; + struct completion resp_ready; + char rx_buf[RX_BUF_SIZE]; + enum transports transport; +}; + +static struct sysmon_subsys subsys[SYSMON_NUM_SS] = { + [SYSMON_SS_MODEM].transport = TRANSPORT_SMD, + [SYSMON_SS_LPASS].transport = TRANSPORT_SMD, + [SYSMON_SS_WCNSS].transport = TRANSPORT_SMD, + [SYSMON_SS_DSPS].transport = TRANSPORT_SMD, + [SYSMON_SS_Q6FW].transport = TRANSPORT_SMD, + [SYSMON_SS_EXT_MODEM].transport = TRANSPORT_HSIC, +}; + +static const char *notif_name[SUBSYS_NOTIF_TYPE_COUNT] = { + [SUBSYS_BEFORE_SHUTDOWN] = "before_shutdown", + [SUBSYS_AFTER_SHUTDOWN] = "after_shutdown", + [SUBSYS_BEFORE_POWERUP] = "before_powerup", + [SUBSYS_AFTER_POWERUP] = "after_powerup", +}; + +static int sysmon_send_smd(struct sysmon_subsys *ss, const char *tx_buf, + size_t len) +{ + int ret; + + if (!ss->chan_open) + return -ENODEV; + + init_completion(&ss->resp_ready); + pr_debug("Sending SMD message: %s\n", tx_buf); + smd_write(ss->chan, tx_buf, len); + ret = wait_for_completion_timeout(&ss->resp_ready, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static int sysmon_send_hsic(struct sysmon_subsys *ss, const char *tx_buf, + size_t len) +{ + int ret; + size_t actual_len; + + pr_debug("Sending HSIC message: %s\n", tx_buf); + ret = hsic_sysmon_write(HSIC_SYSMON_DEV_EXT_MODEM, + tx_buf, len, TIMEOUT_MS); + if (ret) + return ret; + ret = hsic_sysmon_read(HSIC_SYSMON_DEV_EXT_MODEM, ss->rx_buf, + ARRAY_SIZE(ss->rx_buf), &actual_len, TIMEOUT_MS); + return ret; +} + +static int sysmon_send_msg(struct sysmon_subsys *ss, const char *tx_buf, + size_t len) +{ + int ret; + + switch (ss->transport) { + case TRANSPORT_SMD: + ret = sysmon_send_smd(ss, tx_buf, len); + break; + case TRANSPORT_HSIC: + ret = sysmon_send_hsic(ss, tx_buf, len); + break; + default: + ret = -EINVAL; + } + + if (!ret) + pr_debug("Received response: %s\n", ss->rx_buf); + + return ret; +} + +/** + * sysmon_send_event() - Notify a subsystem of another's state change + * @dest_ss: ID of subsystem the notification should be sent to + * @event_ss: String name of the subsystem that generated the notification + * @notif: ID of the notification type (ex. SUBSYS_BEFORE_SHUTDOWN) + * + * Returns 0 for success, -EINVAL for invalid destination or notification IDs, + * -ENODEV if the transport channel is not open, -ETIMEDOUT if the destination + * subsystem does not respond, and -ENOSYS if the destination subsystem + * responds, but with something other than an acknowledgement. + * + * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0). + */ +int sysmon_send_event(enum subsys_id dest_ss, const char *event_ss, + enum subsys_notif_type notif) +{ + struct sysmon_subsys *ss = &subsys[dest_ss]; + char tx_buf[TX_BUF_SIZE]; + int ret; + + if (dest_ss < 0 || dest_ss >= SYSMON_NUM_SS || + notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT || + event_ss == NULL) + return -EINVAL; + + snprintf(tx_buf, ARRAY_SIZE(tx_buf), "ssr:%s:%s", event_ss, + notif_name[notif]); + + mutex_lock(&ss->lock); + ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf)); + if (ret) + goto out; + + if (strncmp(ss->rx_buf, "ssr:ack", ARRAY_SIZE(ss->rx_buf))) + ret = -ENOSYS; +out: + mutex_unlock(&ss->lock); + return ret; +} + +/** + * sysmon_get_reason() - Retrieve failure reason from a subsystem. + * @dest_ss: ID of subsystem to query + * @buf: Caller-allocated buffer for the returned NUL-terminated reason + * @len: Length of @buf + * + * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if + * the SMD transport channel is not open, -ETIMEDOUT if the destination + * subsystem does not respond, and -ENOSYS if the destination subsystem + * responds with something unexpected. + * + * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0). + */ +int sysmon_get_reason(enum subsys_id dest_ss, char *buf, size_t len) +{ + struct sysmon_subsys *ss = &subsys[dest_ss]; + const char tx_buf[] = "ssr:retrieve:sfr"; + const char expect[] = "ssr:return:"; + size_t prefix_len = ARRAY_SIZE(expect) - 1; + int ret; + + if (dest_ss < 0 || dest_ss >= SYSMON_NUM_SS || + buf == NULL || len == 0) + return -EINVAL; + + mutex_lock(&ss->lock); + ret = sysmon_send_msg(ss, tx_buf, ARRAY_SIZE(tx_buf)); + if (ret) + goto out; + + if (strncmp(ss->rx_buf, expect, prefix_len)) { + ret = -ENOSYS; + goto out; + } + strlcpy(buf, ss->rx_buf + prefix_len, len); +out: + mutex_unlock(&ss->lock); + return ret; +} + +static void sysmon_smd_notify(void *priv, unsigned int smd_event) +{ + struct sysmon_subsys *ss = priv; + + switch (smd_event) { + case SMD_EVENT_DATA: { + if (smd_read_avail(ss->chan) > 0) { + smd_read_from_cb(ss->chan, ss->rx_buf, + ARRAY_SIZE(ss->rx_buf)); + complete(&ss->resp_ready); + } + break; + } + case SMD_EVENT_OPEN: + ss->chan_open = true; + break; + case SMD_EVENT_CLOSE: + ss->chan_open = false; + break; + } +} + +static int sysmon_probe(struct platform_device *pdev) +{ + struct sysmon_subsys *ss; + int ret; + + if (pdev->id < 0 || pdev->id >= SYSMON_NUM_SS) + return -ENODEV; + + ss = &subsys[pdev->id]; + mutex_init(&ss->lock); + + switch (ss->transport) { + case TRANSPORT_SMD: + if (pdev->id >= SMD_NUM_TYPE) + return -EINVAL; + + ret = smd_named_open_on_edge("sys_mon", pdev->id, &ss->chan, ss, + sysmon_smd_notify); + if (ret) { + pr_err("SMD open failed\n"); + return ret; + } + + smd_disable_read_intr(ss->chan); + break; + case TRANSPORT_HSIC: + if (pdev->id < SMD_NUM_TYPE) + return -EINVAL; + + ret = hsic_sysmon_open(HSIC_SYSMON_DEV_EXT_MODEM); + if (ret) { + pr_err("HSIC open failed\n"); + return ret; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int __devexit sysmon_remove(struct platform_device *pdev) +{ + struct sysmon_subsys *ss = &subsys[pdev->id]; + + switch (ss->transport) { + case TRANSPORT_SMD: + smd_close(ss->chan); + break; + case TRANSPORT_HSIC: + hsic_sysmon_close(HSIC_SYSMON_DEV_EXT_MODEM); + break; + } + + return 0; +} + +static struct platform_driver sysmon_driver = { + .probe = sysmon_probe, + .remove = __devexit_p(sysmon_remove), + .driver = { + .name = "sys_mon", + .owner = THIS_MODULE, + }, +}; + +static int __init sysmon_init(void) +{ + return platform_driver_register(&sysmon_driver); +} +subsys_initcall(sysmon_init); + +static void __exit sysmon_exit(void) +{ + platform_driver_unregister(&sysmon_driver); +} +module_exit(sysmon_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("system monitor communication library"); +MODULE_ALIAS("platform:sys_mon"); diff --git a/arch/arm/mach-msm/sysmon.h b/arch/arm/mach-msm/sysmon.h new file mode 100644 index 0000000000000000000000000000000000000000..77c332932d02f49c9959bbbd0eda0cf1cde4a386 --- /dev/null +++ b/arch/arm/mach-msm/sysmon.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SYSMON_H +#define __MSM_SYSMON_H + +#include +#include + +/** + * enum subsys_id - Destination subsystems for events. + */ +enum subsys_id { + /* SMD subsystems */ + SYSMON_SS_MODEM = SMD_APPS_MODEM, + SYSMON_SS_LPASS = SMD_APPS_QDSP, + SYSMON_SS_WCNSS = SMD_APPS_WCNSS, + SYSMON_SS_DSPS = SMD_APPS_DSPS, + SYSMON_SS_Q6FW = SMD_APPS_Q6FW, + + /* Non-SMD subsystems */ + SYSMON_SS_EXT_MODEM = SMD_NUM_TYPE, + SYSMON_NUM_SS +}; + +#ifdef CONFIG_MSM_SYSMON_COMM +int sysmon_send_event(enum subsys_id dest_ss, const char *event_ss, + enum subsys_notif_type notif); +int sysmon_get_reason(enum subsys_id dest_ss, char *buf, size_t len); +#else +static inline int sysmon_send_event(enum subsys_id dest_ss, + const char *event_ss, + enum subsys_notif_type notif) +{ + return 0; +} +static inline int sysmon_get_reason(enum subsys_id dest_ss, char *buf, + size_t len) +{ + return 0; +} +#endif + +#endif diff --git a/arch/arm/mach-msm/timer.c b/arch/arm/mach-msm/timer.c index 812808254936575efb06902de9b4bbe7d4941084..4eef948e77751d47ce73ba82e5762184aad34e01 100644 --- a/arch/arm/mach-msm/timer.c +++ b/arch/arm/mach-msm/timer.c @@ -1,7 +1,6 @@ /* - * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2012, 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,232 +13,1165 @@ * */ +#include #include #include #include +#include #include #include +#include #include +#include +#include #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); + +#ifdef CONFIG_MSM7X00A_USE_GP_TIMER + #define DG_TIMER_RATING 100 +#else + #define DG_TIMER_RATING 300 +#endif + +#ifndef MSM_TMR0_BASE +#define MSM_TMR0_BASE MSM_TMR_BASE +#endif + +#define MSM_DGT_SHIFT (5) #define TIMER_MATCH_VAL 0x0000 #define TIMER_COUNT_VAL 0x0004 #define TIMER_ENABLE 0x0008 -#define TIMER_ENABLE_CLR_ON_MATCH_EN BIT(1) -#define TIMER_ENABLE_EN BIT(0) #define TIMER_CLEAR 0x000C #define DGT_CLK_CTL 0x0034 -#define DGT_CLK_CTL_DIV_4 0x3 +enum { + DGT_CLK_CTL_DIV_1 = 0, + DGT_CLK_CTL_DIV_2 = 1, + DGT_CLK_CTL_DIV_3 = 2, + DGT_CLK_CTL_DIV_4 = 3, +}; +#define TIMER_STATUS 0x0088 +#define TIMER_ENABLE_EN 1 +#define TIMER_ENABLE_CLR_ON_MATCH_EN 2 + +#define LOCAL_TIMER 0 +#define GLOBAL_TIMER 1 + +/* + * global_timer_offset is added to the regbase of a timer to force the memory + * access to come from the CPU0 region. + */ +static int global_timer_offset; +static int msm_global_timer; -#define GPT_HZ 32768 +#define NR_TIMERS ARRAY_SIZE(msm_clocks) + +unsigned int gpt_hz = 32768; +unsigned int sclk_hz = 32768; + +static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt); +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; + unsigned int irq; + void __iomem *regbase; + uint32_t freq; + uint32_t shift; + uint32_t flags; + uint32_t write_delay; + uint32_t rollover_offset; + uint32_t index; + void __iomem *global_counter; + void __iomem *local_counter; + uint32_t status_mask; + union { + struct clock_event_device *evt; + struct clock_event_device __percpu **percpu_evt; + }; +}; + +enum { + MSM_CLOCK_GPT, + MSM_CLOCK_DGT, +}; + +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; +}; + +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] = { + .clockevent = { + .name = "gp_timer", + .features = CLOCK_EVT_FEAT_ONESHOT, + .shift = 32, + .rating = 200, + .set_next_event = msm_timer_set_next_event, + .set_mode = msm_timer_set_mode, + }, + .clocksource = { + .name = "gp_timer", + .rating = 200, + .read = msm_gpt_read, + .mask = CLOCKSOURCE_MASK(32), + .shift = 17, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + }, + .irq = INT_GP_TIMER_EXP, + .regbase = MSM_TMR_BASE + 0x4, + .freq = 32768, + .index = MSM_CLOCK_GPT, + .write_delay = 9, + }, + [MSM_CLOCK_DGT] = { + .clockevent = { + .name = "dg_timer", + .features = CLOCK_EVT_FEAT_ONESHOT, + .shift = 32, + .rating = DG_TIMER_RATING, + .set_next_event = msm_timer_set_next_event, + .set_mode = msm_timer_set_mode, + }, + .clocksource = { + .name = "dg_timer", + .rating = DG_TIMER_RATING, + .read = msm_dgt_read, + .mask = CLOCKSOURCE_MASK(32), + .shift = 24, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + }, + .irq = INT_DEBUG_TIMER_EXP, + .regbase = MSM_TMR_BASE + 0x24, + .index = MSM_CLOCK_DGT, + .write_delay = 9, + } +}; -#define MSM_DGT_SHIFT 5 +static DEFINE_PER_CPU(struct msm_clock_percpu_data[NR_TIMERS], + msm_clocks_percpu); -static void __iomem *event_base; +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 = *(struct clock_event_device **)dev_id; - /* Stop the timer tick */ - if (evt->mode == CLOCK_EVT_MODE_ONESHOT) { - u32 ctrl = readl_relaxed(event_base + TIMER_ENABLE); - ctrl &= ~TIMER_ENABLE_EN; - writel_relaxed(ctrl, event_base + TIMER_ENABLE); - } + 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, t3; + int loop_count = 0; + void __iomem *addr = clock->regbase + TIMER_COUNT_VAL + + global*global_timer_offset; + + if (!(clock->flags & MSM_CLOCK_FLAGS_UNSTABLE_COUNT)) + return __raw_readl_no_log(addr); + + t1 = __raw_readl_no_log(addr); + t2 = __raw_readl_no_log(addr); + if ((t2-t1) <= 1) + return t2; + while (1) { + t1 = __raw_readl_no_log(addr); + t2 = __raw_readl_no_log(addr); + t3 = __raw_readl_no_log(addr); + cpu_relax(); + if ((t3-t2) <= 1) + return t3; + if ((t2-t1) <= 1) + return t2; + if ((t2 >= t1) && (t3 >= t2)) + return t2; + if (++loop_count == 5) { + pr_err("msm_read_timer_count timer %s did not " + "stabilize: %u -> %u -> %u\n", + clock->clockevent.name, t1, t2, t3); + return t3; + } + } +} + +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 >> clock->shift; + + return (msm_read_timer_count(clock, GLOBAL_TIMER) + + clock_state->sleep_offset) >> clock->shift; +} + +static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt) +{ + int i; + + if (!is_smp()) + return container_of(evt, struct msm_clock, clockevent); + + for (i = 0; i < NR_TIMERS; i++) + if (evt == &(msm_clocks[i].clockevent)) + return &msm_clocks[i]; + return &msm_clocks[msm_global_timer]; +} + static int msm_timer_set_next_event(unsigned long cycles, struct clock_event_device *evt) { - u32 ctrl = readl_relaxed(event_base + TIMER_ENABLE); + int i; + struct msm_clock *clock; + struct msm_clock_percpu_data *clock_state; + uint32_t now; + uint32_t alarm; + int late; + + clock = clockevent_to_clock(evt); + 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_no_log(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; - writel_relaxed(0, event_base + TIMER_CLEAR); - writel_relaxed(cycles, event_base + TIMER_MATCH_VAL); - writel_relaxed(ctrl | TIMER_ENABLE_EN, event_base + TIMER_ENABLE); return 0; } static void msm_timer_set_mode(enum clock_event_mode mode, - struct clock_event_device *evt) + struct clock_event_device *evt) { - u32 ctrl; + struct msm_clock *clock; + struct msm_clock_percpu_data *clock_state, *gpt_state; + unsigned long irq_flags; + struct irq_chip *chip; + + clock = clockevent_to_clock(evt); + clock_state = &__get_cpu_var(msm_clocks_percpu)[clock->index]; + gpt_state = &__get_cpu_var(msm_clocks_percpu)[MSM_CLOCK_GPT]; - ctrl = readl_relaxed(event_base + TIMER_ENABLE); - ctrl &= ~(TIMER_ENABLE_EN | TIMER_ENABLE_CLR_ON_MATCH_EN); + local_irq_save(irq_flags); switch (mode) { case CLOCK_EVT_MODE_RESUME: case CLOCK_EVT_MODE_PERIODIC: break; case CLOCK_EVT_MODE_ONESHOT: - /* Timer is enabled in set_next_event */ + 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); + chip = irq_get_chip(clock->irq); + if (chip && chip->irq_unmask) + chip->irq_unmask(irq_get_irq_data(clock->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); + chip = irq_get_chip(clock->irq); + if (chip && chip->irq_mask) + chip->irq_mask(irq_get_irq_data(clock->irq)); + + if (!is_smp() || clock != &msm_clocks[MSM_CLOCK_DGT] + || smp_processor_id()) + __raw_writel(0, clock->regbase + TIMER_ENABLE); + + if (msm_global_timer == MSM_CLOCK_DGT && + clock != &msm_clocks[MSM_CLOCK_GPT]) { + gpt_state->in_sync = 0; + __raw_writel(0, msm_clocks[MSM_CLOCK_GPT].regbase + + TIMER_ENABLE); + } break; } - writel_relaxed(ctrl, event_base + TIMER_ENABLE); + wmb(); + local_irq_restore(irq_flags); } -static struct clock_event_device msm_clockevent = { - .name = "gp_timer", - .features = CLOCK_EVT_FEAT_ONESHOT, - .rating = 200, - .set_next_event = msm_timer_set_next_event, - .set_mode = msm_timer_set_mode, -}; +void __iomem *msm_timer_get_timer0_base(void) +{ + return MSM_TMR_BASE + global_timer_offset; +} -static union { - struct clock_event_device *evt; - struct clock_event_device __percpu **percpu_evt; -} msm_evt; +#define MPM_SCLK_COUNT_VAL 0x0024 -static void __iomem *source_base; +#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) +uint32_t msm_timer_get_sclk_ticks(void) +{ + uint32_t t1, t2; + int loop_count = 10; + int loop_zero_count = 3; + int tmp = USEC_PER_SEC; + do_div(tmp, sclk_hz); + tmp /= (loop_zero_count-1); + + while (loop_zero_count--) { + t1 = __raw_readl_no_log(MSM_RPM_MPM_BASE + MPM_SCLK_COUNT_VAL); + do { + udelay(1); + t2 = t1; + t1 = __raw_readl_no_log( + 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; + } -static notrace cycle_t msm_read_timer_count(struct clocksource *cs) + if (t1) + break; + + udelay(tmp); + } + + if (!loop_zero_count) { + printk(KERN_EMERG "SCLK reads zero\n"); + return 0; + } + + return t1; +} + +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) { - return readl_relaxed(source_base + TIMER_COUNT_VAL); + unsigned t1 = msm_timer_get_sclk_ticks(); + + if (t1 && update != NULL) + update(data, t1, sclk_hz); + return t1; } +#elif defined(CONFIG_MSM_N_WAY_SMSM) -static notrace cycle_t msm_read_timer_count_shift(struct clocksource *cs) +/* 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 + +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) { - /* - * Shift timer count down by a constant due to unreliable lower bits - * on some targets. - */ - return msm_read_timer_count(cs) >> MSM_DGT_SHIFT; + 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; -static struct clocksource msm_clocksource = { - .name = "dg_timer", - .rating = 300, - .read = msm_read_timer_count, - .mask = CLOCKSOURCE_MASK(32), - .flags = CLOCK_SOURCE_IS_CONTINUOUS, -}; + 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; + u64 now = get_jiffies_64(); + + do_div(gpt_period, gpt_hz); + + 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 *= NSEC_PER_SEC; + do_div(tmp, sclk_hz); + *period = tmp; + } + + tmp = (int64_t)clock_value; + tmp *= NSEC_PER_SEC; + do_div(tmp, 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 + +static u32 notrace msm_read_sched_clock(void) +{ + struct msm_clock *clock = &msm_clocks[msm_global_timer]; + struct clocksource *cs = &clock->clocksource; + return cs->read(NULL); +} + +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; +} + +static void __init msm_sched_clock_init(void) +{ + struct msm_clock *clock = &msm_clocks[msm_global_timer]; + + setup_sched_clock(msm_read_sched_clock, 32 - clock->shift, clock->freq); +} #ifdef CONFIG_LOCAL_TIMERS -static int __cpuinit msm_local_timer_setup(struct clock_event_device *evt) +int __cpuinit local_timer_setup(struct clock_event_device *evt) { + static DEFINE_PER_CPU(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_relaxed(0, event_base + TIMER_ENABLE); - writel_relaxed(0, event_base + TIMER_CLEAR); - writel_relaxed(~0, event_base + TIMER_MATCH_VAL); - evt->irq = msm_clockevent.irq; + if (cpu_is_msm8x60() || cpu_is_msm8960() || cpu_is_apq8064() + || cpu_is_msm8930()) + __raw_writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); + + if (__get_cpu_var(first_boot)) { + __raw_writel(0, clock->regbase + TIMER_ENABLE); + __raw_writel(0, clock->regbase + TIMER_CLEAR); + __raw_writel(~0, clock->regbase + TIMER_MATCH_VAL); + __get_cpu_var(first_boot) = false; + if (clock->status_mask) + while (__raw_readl(MSM_TMR_BASE + TIMER_STATUS) & + clock->status_mask) + ; + } + evt->irq = clock->irq; evt->name = "local_timer"; - evt->features = msm_clockevent.features; - evt->rating = msm_clockevent.rating; + evt->features = CLOCK_EVT_FEAT_ONESHOT; + evt->rating = clock->clockevent.rating; evt->set_mode = msm_timer_set_mode; evt->set_next_event = msm_timer_set_next_event; - evt->shift = msm_clockevent.shift; - evt->mult = div_sc(GPT_HZ, NSEC_PER_SEC, evt->shift); - evt->max_delta_ns = clockevent_delta2ns(0xf0000000, evt); + evt->shift = clock->clockevent.shift; + evt->mult = div_sc(clock->freq, NSEC_PER_SEC, evt->shift); + evt->max_delta_ns = + clockevent_delta2ns(0xf0000000 >> clock->shift, evt); evt->min_delta_ns = clockevent_delta2ns(4, evt); - *__this_cpu_ptr(msm_evt.percpu_evt) = evt; + *__this_cpu_ptr(clock->percpu_evt) = evt; + clockevents_register_device(evt); - enable_percpu_irq(evt->irq, 0); + enable_percpu_irq(evt->irq, IRQ_TYPE_EDGE_RISING); + return 0; } -static void msm_local_timer_stop(struct clock_event_device *evt) +void local_timer_stop(struct clock_event_device *evt) { evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt); disable_percpu_irq(evt->irq); } -static struct local_timer_ops msm_local_timer_ops __cpuinitdata = { - .setup = msm_local_timer_setup, - .stop = msm_local_timer_stop, +static struct local_timer_ops msm_lt_ops = { + local_timer_setup, + local_timer_stop, }; #endif /* CONFIG_LOCAL_TIMERS */ -static notrace u32 msm_sched_clock_read(void) -{ - return msm_clocksource.read(&msm_clocksource); -} - static void __init msm_timer_init(void) { - struct clock_event_device *ce = &msm_clockevent; - struct clocksource *cs = &msm_clocksource; + int i; int res; - u32 dgt_hz; - - if (cpu_is_msm7x01()) { - event_base = MSM_CSR_BASE; - source_base = MSM_CSR_BASE + 0x10; - dgt_hz = 19200000 >> MSM_DGT_SHIFT; /* 600 KHz */ - cs->read = msm_read_timer_count_shift; - cs->mask = CLOCKSOURCE_MASK((32 - MSM_DGT_SHIFT)); - } else if (cpu_is_msm7x30()) { - event_base = MSM_CSR_BASE + 0x04; - source_base = MSM_CSR_BASE + 0x24; - dgt_hz = 24576000 / 4; + struct irq_chip *chip; + struct msm_clock *dgt = &msm_clocks[MSM_CLOCK_DGT]; + struct msm_clock *gpt = &msm_clocks[MSM_CLOCK_GPT]; + + if (cpu_is_msm7x01() || cpu_is_msm7x25() || cpu_is_msm7x27() || + cpu_is_msm7x25a() || cpu_is_msm7x27a() || cpu_is_msm7x25aa() || + cpu_is_msm7x27aa() || cpu_is_msm8625() || cpu_is_msm7x25ab()) { + dgt->shift = MSM_DGT_SHIFT; + dgt->freq = 19200000 >> MSM_DGT_SHIFT; + dgt->clockevent.shift = 32 + MSM_DGT_SHIFT; + dgt->clocksource.mask = CLOCKSOURCE_MASK(32 - MSM_DGT_SHIFT); + dgt->clocksource.shift = 24 - MSM_DGT_SHIFT; + gpt->regbase = MSM_TMR_BASE; + dgt->regbase = MSM_TMR_BASE + 0x10; + gpt->flags |= MSM_CLOCK_FLAGS_UNSTABLE_COUNT + | MSM_CLOCK_FLAGS_ODD_MATCH_WRITE + | MSM_CLOCK_FLAGS_DELAYED_WRITE_POST; + if (cpu_is_msm8625()) { + dgt->irq = MSM8625_INT_DEBUG_TIMER_EXP; + gpt->irq = MSM8625_INT_GP_TIMER_EXP; + global_timer_offset = MSM_TMR0_BASE - MSM_TMR_BASE; + } } else if (cpu_is_qsd8x50()) { - event_base = MSM_CSR_BASE; - source_base = MSM_CSR_BASE + 0x10; - dgt_hz = 19200000 / 4; - } else if (cpu_is_msm8x60() || cpu_is_msm8960()) { - event_base = MSM_TMR_BASE + 0x04; - /* Use CPU0's timer as the global clock source. */ - source_base = MSM_TMR0_BASE + 0x24; - dgt_hz = 27000000 / 4; - writel_relaxed(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); - } else - BUG(); - - writel_relaxed(0, event_base + TIMER_ENABLE); - writel_relaxed(0, event_base + TIMER_CLEAR); - writel_relaxed(~0, event_base + TIMER_MATCH_VAL); - ce->cpumask = cpumask_of(0); - - ce->irq = INT_GP_TIMER_EXP; - clockevents_config_and_register(ce, GPT_HZ, 4, 0xffffffff); - if (cpu_is_msm8x60() || cpu_is_msm8960()) { - msm_evt.percpu_evt = alloc_percpu(struct clock_event_device *); - if (!msm_evt.percpu_evt) { - pr_err("memory allocation failed for %s\n", ce->name); - goto err; + dgt->freq = 4800000; + gpt->regbase = MSM_TMR_BASE; + dgt->regbase = MSM_TMR_BASE + 0x10; + } else if (cpu_is_fsm9xxx()) + dgt->freq = 4800000; + else if (cpu_is_msm7x30() || cpu_is_msm8x55()) { + gpt->status_mask = BIT(10); + dgt->status_mask = BIT(2); + dgt->freq = 6144000; + } else if (cpu_is_msm8x60()) { + global_timer_offset = MSM_TMR0_BASE - MSM_TMR_BASE; + gpt->status_mask = BIT(10); + dgt->status_mask = BIT(2); + dgt->freq = 6750000; + __raw_writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); + } else if (cpu_is_msm9615()) { + dgt->freq = 6750000; + __raw_writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); + gpt->status_mask = BIT(10); + dgt->status_mask = BIT(2); + gpt->freq = 32765; + gpt_hz = 32765; + sclk_hz = 32765; + gpt->flags |= MSM_CLOCK_FLAGS_UNSTABLE_COUNT; + dgt->flags |= MSM_CLOCK_FLAGS_UNSTABLE_COUNT; + } else if (cpu_is_msm8960() || cpu_is_apq8064() || cpu_is_msm8930()) { + global_timer_offset = MSM_TMR0_BASE - MSM_TMR_BASE; + dgt->freq = 6750000; + __raw_writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); + gpt->status_mask = BIT(10); + dgt->status_mask = BIT(2); + gpt->freq = 32765; + gpt_hz = 32765; + sclk_hz = 32765; + if (!cpu_is_msm8930()) { + gpt->flags |= MSM_CLOCK_FLAGS_UNSTABLE_COUNT; + dgt->flags |= MSM_CLOCK_FLAGS_UNSTABLE_COUNT; + } + } else { + WARN(1, "Timer running on unknown hardware. Configure this! " + "Assuming default configuration.\n"); + dgt->freq = 6750000; + } + + if (msm_clocks[MSM_CLOCK_GPT].clocksource.rating > DG_TIMER_RATING) + msm_global_timer = MSM_CLOCK_GPT; + else + msm_global_timer = MSM_CLOCK_DGT; + + 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(0, clock->regbase + TIMER_CLEAR); + __raw_writel(~0, clock->regbase + TIMER_MATCH_VAL); + + if ((clock->freq << clock->shift) == gpt_hz) { + clock->rollover_offset = 0; + } else { + uint64_t temp; + + temp = clock->freq << clock->shift; + temp <<= 32; + do_div(temp, gpt_hz); + + clock->rollover_offset = (uint32_t) temp; } - *__this_cpu_ptr(msm_evt.percpu_evt) = ce; - res = request_percpu_irq(ce->irq, msm_timer_interrupt, - ce->name, msm_evt.percpu_evt); - if (!res) { - enable_percpu_irq(ce->irq, 0); + + 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); + /* ticks gets rounded down by one */ + ce->min_delta_ns = + clockevent_delta2ns(clock->write_delay + 4, ce); + ce->cpumask = cpumask_of(0); + + 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); + + ce->irq = clock->irq; + if (cpu_is_msm8x60() || cpu_is_msm8960() || cpu_is_apq8064() || + cpu_is_msm8930() || cpu_is_msm9615() || + cpu_is_msm8625()) { + clock->percpu_evt = alloc_percpu(struct clock_event_device *); + if (!clock->percpu_evt) { + pr_err("msm_timer_init: memory allocation " + "failed for %s\n", ce->name); + continue; + } + + *__this_cpu_ptr(clock->percpu_evt) = ce; + res = request_percpu_irq(ce->irq, msm_timer_interrupt, + ce->name, clock->percpu_evt); + if (!res) + enable_percpu_irq(ce->irq, + IRQ_TYPE_EDGE_RISING); + } else { + clock->evt = ce; + res = request_irq(ce->irq, msm_timer_interrupt, + IRQF_TIMER | IRQF_NOBALANCING | IRQF_TRIGGER_RISING, + ce->name, &clock->evt); + } + + if (res) + pr_err("msm_timer_init: request_irq failed for %s\n", + ce->name); + + chip = irq_get_chip(clock->irq); + if (chip && chip->irq_mask) + chip->irq_mask(irq_get_irq_data(clock->irq)); + + if (clock->status_mask) + while (__raw_readl(MSM_TMR_BASE + TIMER_STATUS) & + clock->status_mask) + ; + + clockevents_register_device(ce); + } + msm_sched_clock_init(); + +#ifdef ARCH_HAS_READ_CURRENT_TIMER + if (is_smp()) { + __raw_writel(1, + msm_clocks[MSM_CLOCK_DGT].regbase + TIMER_ENABLE); + set_delay_fn(read_current_timer_delay_loop); + } +#endif + #ifdef CONFIG_LOCAL_TIMERS - local_timer_register(&msm_local_timer_ops); + local_timer_register(&msm_lt_ops); #endif - } - } else { - msm_evt.evt = ce; - res = request_irq(ce->irq, msm_timer_interrupt, - IRQF_TIMER | IRQF_NOBALANCING | - IRQF_TRIGGER_RISING, ce->name, &msm_evt.evt); - } - - if (res) - pr_err("request_irq failed for %s\n", ce->name); -err: - writel_relaxed(TIMER_ENABLE_EN, source_base + TIMER_ENABLE); - res = clocksource_register_hz(cs, dgt_hz); - if (res) - pr_err("clocksource_register failed\n"); - setup_sched_clock(msm_sched_clock_read, - cpu_is_msm7x01() ? 32 - MSM_DGT_SHIFT : 32, dgt_hz); } 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 0000000000000000000000000000000000000000..5d18bb4e69355ea5b162b554647641ded7835cfe --- /dev/null +++ b/arch/arm/mach-msm/timer.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2008-2009, 2011-2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; + +void __iomem *msm_timer_get_timer0_base(void); +uint32_t msm_timer_get_sclk_ticks(void); +int msm_timer_init_time_sync(void (*timeout)(void)); +#ifndef CONFIG_ARM_ARCH_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); +#else +static inline int64_t msm_timer_enter_idle(void) { return 0; } +static inline void msm_timer_exit_idle(int low_power) { return; } +static inline int64_t msm_timer_get_sclk_time(int64_t *period) { return 0; } +#endif +#endif diff --git a/arch/arm/mach-msm/tz_log.c b/arch/arm/mach-msm/tz_log.c new file mode 100644 index 0000000000000000000000000000000000000000..7426bb2a33984e835755507c491e6622256301da --- /dev/null +++ b/arch/arm/mach-msm/tz_log.c @@ -0,0 +1,564 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 DEBUG_MAX_RW_BUF 4096 + +/* + * Preprocessor Definitions and Constants + */ +#define TZBSP_CPU_COUNT 0x02 +/* + * Number of VMID Tables + */ +#define TZBSP_DIAG_NUM_OF_VMID 16 +/* + * VMID Description length + */ +#define TZBSP_DIAG_VMID_DESC_LEN 7 +/* + * Number of Interrupts + */ +#define TZBSP_DIAG_INT_NUM 32 +/* + * Length of descriptive name associated with Interrupt + */ +#define TZBSP_MAX_INT_DESC 16 +/* + * VMID Table + */ +struct tzdbg_vmid_t { + uint8_t vmid; /* Virtual Machine Identifier */ + uint8_t desc[TZBSP_DIAG_VMID_DESC_LEN]; /* ASCII Text */ +}; +/* + * Boot Info Table + */ +struct tzdbg_boot_info_t { + uint32_t wb_entry_cnt; /* Warmboot entry CPU Counter */ + uint32_t wb_exit_cnt; /* Warmboot exit CPU Counter */ + uint32_t pc_entry_cnt; /* Power Collapse entry CPU Counter */ + uint32_t pc_exit_cnt; /* Power Collapse exit CPU counter */ + uint32_t warm_jmp_addr; /* Last Warmboot Jump Address */ + uint32_t spare; /* Reserved for future use. */ +}; +/* + * Reset Info Table + */ +struct tzdbg_reset_info_t { + uint32_t reset_type; /* Reset Reason */ + uint32_t reset_cnt; /* Number of resets occured/CPU */ +}; +/* + * Interrupt Info Table + */ +struct tzdbg_int_t { + /* + * Type of Interrupt/exception + */ + uint16_t int_info; + /* + * Availability of the slot + */ + uint8_t avail; + /* + * Reserved for future use + */ + uint8_t spare; + /* + * Interrupt # for IRQ and FIQ + */ + uint32_t int_num; + /* + * ASCII text describing type of interrupt e.g: + * Secure Timer, EBI XPU. This string is always null terminated, + * supporting at most TZBSP_MAX_INT_DESC characters. + * Any additional characters are truncated. + */ + uint8_t int_desc[TZBSP_MAX_INT_DESC]; + uint64_t int_count[TZBSP_CPU_COUNT]; /* # of times seen per CPU */ +}; +/* + * Diagnostic Table + */ +struct tzdbg_t { + uint32_t magic_num; + uint32_t version; + /* + * Number of CPU's + */ + uint32_t cpu_count; + /* + * Offset of VMID Table + */ + uint32_t vmid_info_off; + /* + * Offset of Boot Table + */ + uint32_t boot_info_off; + /* + * Offset of Reset info Table + */ + uint32_t reset_info_off; + /* + * Offset of Interrupt info Table + */ + uint32_t int_info_off; + /* + * Ring Buffer Offset + */ + uint32_t ring_off; + /* + * Ring Buffer Length + */ + uint32_t ring_len; + /* + * VMID to EE Mapping + */ + struct tzdbg_vmid_t vmid_info[TZBSP_DIAG_NUM_OF_VMID]; + /* + * Boot Info + */ + struct tzdbg_boot_info_t boot_info[TZBSP_CPU_COUNT]; + /* + * Reset Info + */ + struct tzdbg_reset_info_t reset_info[TZBSP_CPU_COUNT]; + uint32_t num_interrupts; + struct tzdbg_int_t int_info[TZBSP_DIAG_INT_NUM]; + /* + * We need at least 2K for the ring buffer + */ + uint8_t *ring_buffer; /* TZ Ring Buffer */ +}; + +/* + * Enumeration order for VMID's + */ +enum tzdbg_stats_type { + TZDBG_BOOT = 0, + TZDBG_RESET, + TZDBG_INTERRUPT, + TZDBG_VMID, + TZDBG_GENERAL, + TZDBG_LOG, + TZDBG_STATS_MAX, +}; + +struct tzdbg_stat { + char *name; + char *data; +}; + +struct tzdbg { + void __iomem *virt_iobase; + struct tzdbg_t *diag_buf; + char *disp_buf; + int debug_tz[TZDBG_STATS_MAX]; + struct tzdbg_stat stat[TZDBG_STATS_MAX]; +}; + +static struct tzdbg tzdbg = { + + .stat[TZDBG_BOOT].name = "boot", + .stat[TZDBG_RESET].name = "reset", + .stat[TZDBG_INTERRUPT].name = "interrupt", + .stat[TZDBG_VMID].name = "vmid", + .stat[TZDBG_GENERAL].name = "general", + .stat[TZDBG_LOG].name = "log", +}; + + +/* + * Debugfs data structure and functions + */ + +static int _disp_tz_general_stats(void) +{ + int len = 0; + + len += snprintf(tzdbg.disp_buf + len, DEBUG_MAX_RW_BUF - 1, + " Version : 0x%x\n" + " Magic Number : 0x%x\n" + " Number of CPU : %d\n", + tzdbg.diag_buf->version, + tzdbg.diag_buf->magic_num, + tzdbg.diag_buf->cpu_count); + tzdbg.stat[TZDBG_GENERAL].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_vmid_stats(void) +{ + int i, num_vmid; + int len = 0; + struct tzdbg_vmid_t *ptr; + + ptr = (struct tzdbg_vmid_t *)((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->vmid_info_off); + num_vmid = ((tzdbg.diag_buf->boot_info_off - + tzdbg.diag_buf->vmid_info_off)/ + (sizeof(struct tzdbg_vmid_t))); + + for (i = 0; i < num_vmid; i++) { + if (ptr->vmid < 0xFF) { + len += snprintf(tzdbg.disp_buf + len, + (DEBUG_MAX_RW_BUF - 1) - len, + " 0x%x %s\n", + (uint32_t)ptr->vmid, (uint8_t *)ptr->desc); + } + if (len > (DEBUG_MAX_RW_BUF - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + ptr++; + } + + tzdbg.stat[TZDBG_VMID].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_boot_stats(void) +{ + int i; + int len = 0; + struct tzdbg_boot_info_t *ptr; + + ptr = (struct tzdbg_boot_info_t *)((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->boot_info_off); + + for (i = 0; i < tzdbg.diag_buf->cpu_count; i++) { + len += snprintf(tzdbg.disp_buf + len, + (DEBUG_MAX_RW_BUF - 1) - len, + " CPU #: %d\n" + " Warmboot jump address : 0x%x\n" + " Warmboot entry CPU counter: 0x%x\n" + " Warmboot exit CPU counter : 0x%x\n" + " Power Collapse entry CPU counter: 0x%x\n" + " Power Collapse exit CPU counter : 0x%x\n", + i, ptr->warm_jmp_addr, ptr->wb_entry_cnt, + ptr->wb_exit_cnt, ptr->pc_entry_cnt, + ptr->pc_exit_cnt); + + if (len > (DEBUG_MAX_RW_BUF - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + ptr++; + } + tzdbg.stat[TZDBG_BOOT].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_reset_stats(void) +{ + int i; + int len = 0; + struct tzdbg_reset_info_t *ptr; + + ptr = (struct tzdbg_reset_info_t *)((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->reset_info_off); + + for (i = 0; i < tzdbg.diag_buf->cpu_count; i++) { + len += snprintf(tzdbg.disp_buf + len, + (DEBUG_MAX_RW_BUF - 1) - len, + " CPU #: %d\n" + " Reset Type (reason) : 0x%x\n" + " Reset counter : 0x%x\n", + i, ptr->reset_type, ptr->reset_cnt); + + if (len > (DEBUG_MAX_RW_BUF - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + + ptr++; + } + tzdbg.stat[TZDBG_RESET].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_interrupt_stats(void) +{ + int i, j, int_info_size; + int len = 0; + int *num_int; + unsigned char *ptr; + struct tzdbg_int_t *tzdbg_ptr; + + num_int = (uint32_t *)((unsigned char *)tzdbg.diag_buf + + (tzdbg.diag_buf->int_info_off - sizeof(uint32_t))); + ptr = ((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->int_info_off); + int_info_size = ((tzdbg.diag_buf->ring_off - + tzdbg.diag_buf->int_info_off)/(*num_int)); + + for (i = 0; i < (*num_int); i++) { + tzdbg_ptr = (struct tzdbg_int_t *)ptr; + len += snprintf(tzdbg.disp_buf + len, + (DEBUG_MAX_RW_BUF - 1) - len, + " Interrupt Number : 0x%x\n" + " Type of Interrupt : 0x%x\n" + " Description of interrupt : %s\n", + tzdbg_ptr->int_num, + (uint32_t)tzdbg_ptr->int_info, + (uint8_t *)tzdbg_ptr->int_desc); + for (j = 0; j < tzdbg.diag_buf->cpu_count; j++) { + len += snprintf(tzdbg.disp_buf + len, + (DEBUG_MAX_RW_BUF - 1) - len, + " int_count on CPU # %d : %u\n", + (uint32_t)j, + (uint32_t)tzdbg_ptr->int_count[j]); + } + len += snprintf(tzdbg.disp_buf + len, DEBUG_MAX_RW_BUF - 1, + "\n"); + + if (len > (DEBUG_MAX_RW_BUF - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + + ptr += int_info_size; + } + tzdbg.stat[TZDBG_INTERRUPT].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_log_stats(void) +{ + int len = 0; + unsigned char *ptr; + + ptr = (unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->ring_off; + len += snprintf(tzdbg.disp_buf, (DEBUG_MAX_RW_BUF - 1) - len, + "%s\n", ptr); + + tzdbg.stat[TZDBG_LOG].data = tzdbg.disp_buf; + return len; +} + +static ssize_t tzdbgfs_read(struct file *file, char __user *buf, + size_t count, loff_t *offp) +{ + int len = 0; + int *tz_id = file->private_data; + + memcpy_fromio((void *)tzdbg.diag_buf, tzdbg.virt_iobase, + DEBUG_MAX_RW_BUF); + switch (*tz_id) { + case TZDBG_BOOT: + len = _disp_tz_boot_stats(); + break; + case TZDBG_RESET: + len = _disp_tz_reset_stats(); + break; + case TZDBG_INTERRUPT: + len = _disp_tz_interrupt_stats(); + break; + case TZDBG_GENERAL: + len = _disp_tz_general_stats(); + break; + case TZDBG_VMID: + len = _disp_tz_vmid_stats(); + break; + case TZDBG_LOG: + len = _disp_tz_log_stats(); + break; + default: + break; + } + + if (len > count) + len = count; + + return simple_read_from_buffer(buf, len, offp, + tzdbg.stat[(*tz_id)].data, len); +} + +static int tzdbgfs_open(struct inode *inode, struct file *pfile) +{ + pfile->private_data = inode->i_private; + return 0; +} + +const struct file_operations tzdbg_fops = { + .owner = THIS_MODULE, + .read = tzdbgfs_read, + .open = tzdbgfs_open, +}; + +static int tzdbgfs_init(struct platform_device *pdev) +{ + int rc = 0; + int i; + struct dentry *dent_dir; + struct dentry *dent; + + dent_dir = debugfs_create_dir("tzdbg", NULL); + if (dent_dir == NULL) { + dev_err(&pdev->dev, "tzdbg debugfs_create_dir failed\n"); + return -ENOMEM; + } + + for (i = 0; i < TZDBG_STATS_MAX; i++) { + tzdbg.debug_tz[i] = i; + dent = debugfs_create_file(tzdbg.stat[i].name, + S_IRUGO, dent_dir, + &tzdbg.debug_tz[i], &tzdbg_fops); + if (dent == NULL) { + dev_err(&pdev->dev, "TZ debugfs_create_file failed\n"); + rc = -ENOMEM; + goto err; + } + } + tzdbg.disp_buf = kzalloc(DEBUG_MAX_RW_BUF, GFP_KERNEL); + if (tzdbg.disp_buf == NULL) { + pr_err("%s: Can't Allocate memory for tzdbg.disp_buf\n", + __func__); + + goto err; + } + platform_set_drvdata(pdev, dent_dir); + return 0; +err: + debugfs_remove_recursive(dent_dir); + + return rc; +} + +static void tzdbgfs_exit(struct platform_device *pdev) +{ + struct dentry *dent_dir; + + kzfree(tzdbg.disp_buf); + dent_dir = platform_get_drvdata(pdev); + debugfs_remove_recursive(dent_dir); +} + +/* + * Driver functions + */ +static int __devinit tz_log_probe(struct platform_device *pdev) +{ + struct resource *resource; + void __iomem *virt_iobase; + uint32_t tzdiag_phy_iobase; + uint32_t *ptr = NULL; + + /* + * Get address that stores the physical location of 4KB + * diagnostic data + */ + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + dev_err(&pdev->dev, + "%s: ERROR Missing MEM resource\n", __func__); + return -ENXIO; + }; + /* + * Map address that stores the physical location of 4KB + * diagnostic data + */ + virt_iobase = devm_ioremap_nocache(&pdev->dev, resource->start, + resource->end - resource->start + 1); + if (!virt_iobase) { + dev_err(&pdev->dev, + "%s: ERROR could not ioremap: start=%p, len=%u\n", + __func__, (void *) resource->start, + (resource->end - resource->start + 1)); + return -ENXIO; + } + /* + * Retrieve the address of 4KB diagnostic data + */ + tzdiag_phy_iobase = readl_relaxed(virt_iobase); + + /* + * Map the 4KB diagnostic information area + */ + tzdbg.virt_iobase = devm_ioremap_nocache(&pdev->dev, + tzdiag_phy_iobase, DEBUG_MAX_RW_BUF); + + if (!tzdbg.virt_iobase) { + dev_err(&pdev->dev, + "%s: ERROR could not ioremap: start=%p, len=%u\n", + __func__, (void *) tzdiag_phy_iobase, DEBUG_MAX_RW_BUF); + return -ENXIO; + } + + ptr = kzalloc(DEBUG_MAX_RW_BUF, GFP_KERNEL); + if (ptr == NULL) { + pr_err("%s: Can't Allocate memory: ptr\n", + __func__); + return -ENXIO; + } + + tzdbg.diag_buf = (struct tzdbg_t *)ptr; + + if (tzdbgfs_init(pdev)) + goto err; + + return 0; +err: + kfree(tzdbg.diag_buf); + return -ENXIO; +} + + +static int __devexit tz_log_remove(struct platform_device *pdev) +{ + kzfree(tzdbg.diag_buf); + tzdbgfs_exit(pdev); + + return 0; +} + +static struct platform_driver tz_log_driver = { + .probe = tz_log_probe, + .remove = __devexit_p(tz_log_remove), + .driver = { + .name = "tz_log", + .owner = THIS_MODULE, + }, +}; + +static int __init tz_log_init(void) +{ + return platform_driver_register(&tz_log_driver); +} + +static void __exit tz_log_exit(void) +{ + platform_driver_unregister(&tz_log_driver); +} + +module_init(tz_log_init); +module_exit(tz_log_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TZ Log driver"); +MODULE_VERSION("1.1"); +MODULE_ALIAS("platform:tz_log"); diff --git a/arch/arm/mach-msm/vreg.c b/arch/arm/mach-msm/vreg.c index bd66ed04d6dc0edfdc489a56a7f1e9fb84dbafc1..271da867d7e89da4fe2a00537e9f4979d29744ee 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-2012 Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -16,205 +16,310 @@ */ #include +#include #include #include +#include +#include +#include +#include #include -#include +#include #include #include - -#include "proc_comm.h" +#include + +#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; - int status; - unsigned refcnt; + struct list_head list; + struct mutex lock; + const char *name; + u64 refcnt; + unsigned mv; + struct regulator *reg; }; -#define VREG(_name, _id, _status, _refcnt) \ - { .name = _name, .id = _id, .status = _status, .refcnt = _refcnt } - -static struct vreg vregs[] = { - VREG("msma", 0, 0, 0), - VREG("msmp", 1, 0, 0), - VREG("msme1", 2, 0, 0), - VREG("msmc1", 3, 0, 0), - VREG("msmc2", 4, 0, 0), - VREG("gp3", 5, 0, 0), - VREG("msme2", 6, 0, 0), - VREG("gp4", 7, 0, 0), - VREG("gp1", 8, 0, 0), - VREG("tcxo", 9, 0, 0), - VREG("pa", 10, 0, 0), - VREG("rftx", 11, 0, 0), - VREG("rfrx1", 12, 0, 0), - VREG("rfrx2", 13, 0, 0), - VREG("synt", 14, 0, 0), - VREG("wlan", 15, 0, 0), - VREG("usb", 16, 0, 0), - VREG("boost", 17, 0, 0), - VREG("mmc", 18, 0, 0), - VREG("ruim", 19, 0, 0), - VREG("msmc0", 20, 0, 0), - VREG("gp2", 21, 0, 0), - VREG("gp5", 22, 0, 0), - VREG("gp6", 23, 0, 0), - VREG("rf", 24, 0, 0), - VREG("rf_vco", 26, 0, 0), - VREG("mpll", 27, 0, 0), - VREG("s2", 28, 0, 0), - VREG("s3", 29, 0, 0), - VREG("rfubm", 30, 0, 0), - VREG("ncp", 31, 0, 0), - VREG("gp7", 32, 0, 0), - VREG("gp8", 33, 0, 0), - VREG("gp9", 34, 0, 0), - VREG("gp10", 35, 0, 0), - VREG("gp11", 36, 0, 0), - VREG("gp12", 37, 0, 0), - VREG("gp13", 38, 0, 0), - VREG("gp14", 39, 0, 0), - VREG("gp15", 40, 0, 0), - VREG("gp16", 41, 0, 0), - VREG("gp17", 42, 0, 0), - VREG("s4", 43, 0, 0), - VREG("usb2", 44, 0, 0), - VREG("wlan2", 45, 0, 0), - VREG("xo_out", 46, 0, 0), - VREG("lvsw0", 47, 0, 0), - VREG("lvsw1", 48, 0, 0), -}; +static LIST_HEAD(vreg_list); +static DEFINE_MUTEX(vreg_lock); -struct vreg *vreg_get(struct device *dev, const char *id) +#ifdef CONFIG_DEBUG_FS +static void vreg_add_debugfs(struct vreg *vreg); +#else +static inline void vreg_add_debugfs(struct vreg *vreg) { } +#endif + +static struct vreg *vreg_create(const char *id) { - int n; - for (n = 0; n < ARRAY_SIZE(vregs); n++) { - if (!strcmp(vregs[n].name, id)) - return vregs + n; + int rc; + struct vreg *vreg; + + vreg = kzalloc(sizeof(*vreg), GFP_KERNEL); + if (!vreg) { + rc = -ENOMEM; + goto error; } - return ERR_PTR(-ENOENT); + + INIT_LIST_HEAD(&vreg->list); + mutex_init(&vreg->lock); + + vreg->reg = regulator_get(NULL, id); + if (IS_ERR(vreg->reg)) { + rc = PTR_ERR(vreg->reg); + goto free_vreg; + } + + vreg->name = kstrdup(id, GFP_KERNEL); + if (!vreg->name) { + rc = -ENOMEM; + goto put_reg; + } + + list_add_tail(&vreg->list, &vreg_list); + vreg_add_debugfs(vreg); + + return vreg; + +put_reg: + regulator_put(vreg->reg); +free_vreg: + kfree(vreg); +error: + return ERR_PTR(rc); } -void vreg_put(struct vreg *vreg) +static void vreg_destroy(struct vreg *vreg) { + if (!vreg) + return; + + if (vreg->refcnt) + regulator_disable(vreg->reg); + + kfree(vreg->name); + regulator_put(vreg->reg); + kfree(vreg); } -int vreg_enable(struct vreg *vreg) +struct vreg *vreg_get(struct device *dev, const char *id) { - unsigned id = vreg->id; - unsigned enable = 1; + struct vreg *vreg = NULL; - if (vreg->refcnt == 0) - vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable); + if (!id) + return ERR_PTR(-EINVAL); - if ((vreg->refcnt < UINT_MAX) && (!vreg->status)) - vreg->refcnt++; + mutex_lock(&vreg_lock); + list_for_each_entry(vreg, &vreg_list, list) { + if (!strncmp(vreg->name, id, 10)) + goto ret; + } + + vreg = vreg_create(id); - return vreg->status; +ret: + mutex_unlock(&vreg_lock); + return vreg; } +EXPORT_SYMBOL(vreg_get); -int vreg_disable(struct vreg *vreg) +void vreg_put(struct vreg *vreg) { - unsigned id = vreg->id; - unsigned enable = 0; + kfree(vreg->name); + regulator_put(vreg->reg); + list_del(&vreg->list); + kfree(vreg); +} - if (!vreg->refcnt) - return 0; +int vreg_enable(struct vreg *vreg) +{ + int rc = 0; + if (!vreg) + return -ENODEV; + + mutex_lock(&vreg->lock); + if (vreg->refcnt == 0) { + rc = regulator_enable(vreg->reg); + if (!rc) + vreg->refcnt++; + } else { + rc = 0; + if (vreg->refcnt < UINT_MAX) + vreg->refcnt++; + } + mutex_unlock(&vreg->lock); - if (vreg->refcnt == 1) - vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable); + return rc; +} +EXPORT_SYMBOL(vreg_enable); - if (!vreg->status) +int vreg_disable(struct vreg *vreg) +{ + int rc = 0; + if (!vreg) + return -ENODEV; + + mutex_lock(&vreg->lock); + if (vreg->refcnt == 0) { + pr_warn("%s: unbalanced disables for vreg %s\n", + __func__, vreg->name); + rc = -EINVAL; + } else if (vreg->refcnt == 1) { + rc = regulator_disable(vreg->reg); + if (!rc) + vreg->refcnt--; + } else { + rc = 0; vreg->refcnt--; + } + mutex_unlock(&vreg->lock); - return vreg->status; + return rc; } +EXPORT_SYMBOL(vreg_disable); int vreg_set_level(struct vreg *vreg, unsigned mv) { - unsigned id = vreg->id; + unsigned uv; + int rc; - vreg->status = msm_proc_comm(PCOM_VREG_SET_LEVEL, &id, &mv); - return vreg->status; + if (!vreg) + return -EINVAL; + + if (mv > (UINT_MAX / 1000)) + return -ERANGE; + + uv = mv * 1000; + + mutex_lock(&vreg->lock); + rc = regulator_set_voltage(vreg->reg, uv, uv); + if (!rc) + vreg->mv = mv; + mutex_unlock(&vreg->lock); + + return rc; } +EXPORT_SYMBOL(vreg_set_level); #if defined(CONFIG_DEBUG_FS) -static int vreg_debug_set(void *data, u64 val) +static int vreg_debug_enabled_set(void *data, u64 val) { struct vreg *vreg = data; - switch (val) { - case 0: - vreg_disable(vreg); - break; - case 1: - vreg_enable(vreg); - break; - default: - vreg_set_level(vreg, val); - break; - } - return 0; + + if (val == 0) + return vreg_disable(vreg); + else if (val == 1) + return vreg_enable(vreg); + else + return -EINVAL; } -static int vreg_debug_get(void *data, u64 *val) +static int vreg_debug_enabled_get(void *data, u64 *val) { struct vreg *vreg = data; - if (!vreg->status) - *val = 0; - else - *val = 1; + *val = vreg->refcnt; return 0; } -static int vreg_debug_count_set(void *data, u64 val) +static int vreg_debug_voltage_set(void *data, u64 val) { struct vreg *vreg = data; - if (val > UINT_MAX) - val = UINT_MAX; - vreg->refcnt = val; - return 0; + return vreg_set_level(vreg, val); } -static int vreg_debug_count_get(void *data, u64 *val) +static int vreg_debug_voltage_get(void *data, u64 *val) { struct vreg *vreg = data; - - *val = vreg->refcnt; - + *val = vreg->mv; return 0; } -DEFINE_SIMPLE_ATTRIBUTE(vreg_fops, vreg_debug_get, vreg_debug_set, "%llu\n"); -DEFINE_SIMPLE_ATTRIBUTE(vreg_count_fops, vreg_debug_count_get, - vreg_debug_count_set, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(vreg_debug_enabled, vreg_debug_enabled_get, + vreg_debug_enabled_set, "%llu"); +DEFINE_SIMPLE_ATTRIBUTE(vreg_debug_voltage, vreg_debug_voltage_get, + vreg_debug_voltage_set, "%llu"); + +static struct dentry *root; -static int __init vreg_debug_init(void) +static void vreg_add_debugfs(struct vreg *vreg) { - struct dentry *dent; - int n; - char name[32]; - const char *refcnt_name = "_refcnt"; + struct dentry *dir; + + if (!root) + return; + + dir = debugfs_create_dir(vreg->name, root); - dent = debugfs_create_dir("vreg", 0); - if (IS_ERR(dent)) - return 0; + if (IS_ERR_OR_NULL(dir)) + goto err; - for (n = 0; n < ARRAY_SIZE(vregs); n++) { - (void) debugfs_create_file(vregs[n].name, 0644, - dent, vregs + n, &vreg_fops); + if (IS_ERR_OR_NULL(debugfs_create_file("enabled", 0644, dir, vreg, + &vreg_debug_enabled))) + goto destroy; - strlcpy(name, vregs[n].name, sizeof(name)); - strlcat(name, refcnt_name, sizeof(name)); - (void) debugfs_create_file(name, 0644, - dent, vregs + n, &vreg_count_fops); + if (IS_ERR_OR_NULL(debugfs_create_file("voltage", 0644, dir, vreg, + &vreg_debug_voltage))) + goto destroy; + + return; + +destroy: + debugfs_remove_recursive(dir); +err: + pr_warn("%s: could not create debugfs for vreg %s\n", + __func__, vreg->name); +} + +static int __devinit vreg_debug_init(void) +{ + root = debugfs_create_dir("vreg", NULL); + + if (IS_ERR_OR_NULL(root)) { + pr_debug("%s: error initializing debugfs: %ld - " + "disabling debugfs\n", + __func__, root ? PTR_ERR(root) : 0); + root = NULL; } return 0; } - -device_initcall(vreg_debug_init); +static void __devexit vreg_debug_exit(void) +{ + if (root) + debugfs_remove_recursive(root); + root = NULL; +} +#else +static inline int __init vreg_debug_init(void) { return 0; } +static inline void __exit vreg_debug_exit(void) { return 0; } #endif + +static int __init vreg_init(void) +{ + return vreg_debug_init(); +} +module_init(vreg_init); + +static void __exit vreg_exit(void) +{ + struct vreg *vreg, *next; + vreg_debug_exit(); + + mutex_lock(&vreg_lock); + list_for_each_entry_safe(vreg, next, &vreg_list, list) + vreg_destroy(vreg); + mutex_unlock(&vreg_lock); +} +module_exit(vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("vreg.c regulator shim"); +MODULE_VERSION("1.0"); diff --git a/arch/arm/mach-msm/wcnss-ssr-8960.c b/arch/arm/mach-msm/wcnss-ssr-8960.c new file mode 100644 index 0000000000000000000000000000000000000000..90948ea9f0b3939ba0019ee6402c61fe2d062299 --- /dev/null +++ b/arch/arm/mach-msm/wcnss-ssr-8960.c @@ -0,0 +1,247 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "ramdump.h" + +#define MODULE_NAME "wcnss_8960" + +static void riva_smsm_cb_fn(struct work_struct *); +static DECLARE_WORK(riva_smsm_cb_work, riva_smsm_cb_fn); + +static void riva_fatal_fn(struct work_struct *); +static DECLARE_WORK(riva_fatal_work, riva_fatal_fn); + +static struct delayed_work cancel_vote_work; +static void *riva_ramdump_dev; +static int riva_crash; +static int ss_restart_inprogress; +static int enable_riva_ssr; + +static void riva_smsm_cb_fn(struct work_struct *work) +{ + if (!enable_riva_ssr) + panic(MODULE_NAME ": SMSM reset request received from Riva"); + else + subsystem_restart("riva"); +} + +static void smsm_state_cb_hdlr(void *data, uint32_t old_state, + uint32_t new_state) +{ + if (!(new_state & SMSM_RESET)) + return; + + riva_crash = true; + pr_err("%s: smsm state changed to smsm reset\n", MODULE_NAME); + + if (ss_restart_inprogress) { + pr_err("%s: Ignoring smsm reset req, restart in progress\n", + MODULE_NAME); + return; + } + ss_restart_inprogress = true; + schedule_work(&riva_smsm_cb_work); +} + +static void riva_fatal_fn(struct work_struct *work) +{ + if (!enable_riva_ssr) + panic(MODULE_NAME ": Watchdog bite received from Riva"); + else + subsystem_restart("riva"); +} + +static irqreturn_t riva_wdog_bite_irq_hdlr(int irq, void *dev_id) +{ + int ret; + + if (ss_restart_inprogress) { + pr_err("%s: Ignoring riva bite irq, restart in progress\n", + MODULE_NAME); + return IRQ_HANDLED; + } + ss_restart_inprogress = true; + ret = schedule_work(&riva_fatal_work); + return IRQ_HANDLED; +} + +/* SMSM reset Riva */ +static void smsm_riva_reset(void) +{ + /* per SS reset request bit is not available now, + * all SS host modules are setting this bit + * This is still under discussion*/ + smsm_change_state(SMSM_APPS_STATE, SMSM_RESET, SMSM_RESET); +} + +static void riva_post_bootup(struct work_struct *work) +{ + struct platform_device *pdev = wcnss_get_platform_device(); + struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config(); + + pr_debug(MODULE_NAME ": Cancel APPS vote for Iris & Riva\n"); + + wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_OFF); +} + +/* Subsystem handlers */ +static int riva_shutdown(const struct subsys_data *subsys) +{ + pil_force_shutdown("wcnss"); + flush_delayed_work(&cancel_vote_work); + disable_irq_nosync(RIVA_APSS_WDOG_BITE_RESET_RDY_IRQ); + + return 0; +} + +static int riva_powerup(const struct subsys_data *subsys) +{ + struct platform_device *pdev = wcnss_get_platform_device(); + struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config(); + int ret = -1; + + if (pdev && pwlanconfig) + ret = wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_ON); + /* delay PIL operation, this SSR may be happening soon after kernel + * resumes because of a SMSM RESET by Riva when APPS was suspended. + * PIL fails to locate the images without this delay */ + if (!ret) { + msleep(1000); + pil_force_boot("wcnss"); + } + ss_restart_inprogress = false; + enable_irq(RIVA_APSS_WDOG_BITE_RESET_RDY_IRQ); + schedule_delayed_work(&cancel_vote_work, msecs_to_jiffies(5000)); + + return ret; +} + +/* RAM segments for Riva SS; + * We don't specify the full 5MB allocated for Riva. Only 3MB is specified */ +static struct ramdump_segment riva_segments[] = {{0x8f200000, + 0x8f500000 - 0x8f200000} }; + +static int riva_ramdump(int enable, const struct subsys_data *subsys) +{ + pr_debug("%s: enable[%d]\n", MODULE_NAME, enable); + if (enable) + return do_ramdump(riva_ramdump_dev, + riva_segments, + ARRAY_SIZE(riva_segments)); + else + return 0; +} + +/* Riva crash handler */ +static void riva_crash_shutdown(const struct subsys_data *subsys) +{ + pr_err("%s: crash shutdown : %d\n", MODULE_NAME, riva_crash); + if (riva_crash != true) + smsm_riva_reset(); +} + +static struct subsys_data riva_8960 = { + .name = "riva", + .shutdown = riva_shutdown, + .powerup = riva_powerup, + .ramdump = riva_ramdump, + .crash_shutdown = riva_crash_shutdown +}; + +static int enable_riva_ssr_set(const char *val, struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (ret) + return ret; + + if (enable_riva_ssr) + pr_info(MODULE_NAME ": Subsystem restart activated for riva.\n"); + + return 0; +} + +module_param_call(enable_riva_ssr, enable_riva_ssr_set, param_get_int, + &enable_riva_ssr, S_IRUGO | S_IWUSR); + +static int __init riva_restart_init(void) +{ + return ssr_register_subsystem(&riva_8960); +} + +static int __init riva_ssr_module_init(void) +{ + int ret; + + ret = smsm_state_cb_register(SMSM_WCNSS_STATE, SMSM_RESET, + smsm_state_cb_hdlr, 0); + if (ret < 0) { + pr_err("%s: Unable to register smsm callback for Riva Reset!" + " (%d)\n", MODULE_NAME, ret); + goto out; + } + ret = request_irq(RIVA_APSS_WDOG_BITE_RESET_RDY_IRQ, + riva_wdog_bite_irq_hdlr, IRQF_TRIGGER_HIGH, + "riva_wdog", NULL); + + if (ret < 0) { + pr_err("%s: Unable to register for Riva bite interrupt" + " (%d)\n", MODULE_NAME, ret); + goto out; + } + ret = riva_restart_init(); + if (ret < 0) { + pr_err("%s: Unable to register with ssr. (%d)\n", + MODULE_NAME, ret); + goto out; + } + riva_ramdump_dev = create_ramdump_device("riva"); + if (!riva_ramdump_dev) { + pr_err("%s: Unable to create ramdump device.\n", + MODULE_NAME); + ret = -ENOMEM; + goto out; + } + INIT_DELAYED_WORK(&cancel_vote_work, riva_post_bootup); + + pr_info("%s: module initialized\n", MODULE_NAME); +out: + return ret; +} + +static void __exit riva_ssr_module_exit(void) +{ + free_irq(RIVA_APSS_WDOG_BITE_RESET_RDY_IRQ, NULL); +} + +module_init(riva_ssr_module_init); +module_exit(riva_ssr_module_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/xo-fsm9xxx.c b/arch/arm/mach-msm/xo-fsm9xxx.c new file mode 100644 index 0000000000000000000000000000000000000000..cbf3b7afafc29506ddc838982f6189614a9ac7e3 --- /dev/null +++ b/arch/arm/mach-msm/xo-fsm9xxx.c @@ -0,0 +1,289 @@ +/* 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 FSM_XO_IOC_MAGIC 0x93 +#define FSM_XO_IOC_CLKBUF _IO(FSM_XO_IOC_MAGIC, 1) + +#define FSM_XO_DEVICE_READY 0x01 +#define FSM_XO_DEVICE_OFF 0x00 + +/* enum for TCXO clock output buffer definition */ +enum clk_buffer_type { + XO_BUFFER_A0 = 0, + XO_BUFFER_A1 = 1, + XO_BUFFER_LAST +}; + +/* + * This user request structure is used to exchange the pmic device data + * requested to user space applications. The pointer to this structure is + * passed to the the ioctl function. +*/ +struct fsm_xo_req { + enum clk_buffer_type clkBuffer; + u8 clkBufEnable; +}; + +struct fsm_xo_priv_t { + struct mutex lock; + struct regulator *a0; + struct regulator *a1; + u8 a0_enabled; + u8 a1_enabled; +}; + +static struct fsm_xo_priv_t *fsm_xo_priv; + +static int fsm_xo_open(struct inode *inode, struct file *filp) +{ + if (fsm_xo_priv == NULL) + return -ENODEV; + + filp->private_data = fsm_xo_priv; + + return 0; +} + +static int fsm_xo_release(struct inode *inode, struct file *filp) +{ + filp->private_data = NULL; + + return 0; +} + +static inline int fsm_xo_enable_a0(void) +{ + int err = 0; + + if (!fsm_xo_priv->a0_enabled) { + err = regulator_enable(fsm_xo_priv->a0); + if (err != 0) + pr_err("Error = %d enabling xo buffer a0\n", err); + else + fsm_xo_priv->a0_enabled = 1; + } + return err; +} + +static inline int fsm_xo_disable_a0(void) +{ + int err = 0; + + if (fsm_xo_priv->a0_enabled) { + err = regulator_disable(fsm_xo_priv->a0); + if (err != 0) + pr_err("Error = %d disabling xo buffer a0\n", err); + else + fsm_xo_priv->a0_enabled = 0; + } + return err; +} + +static inline int fsm_xo_enable_a1(void) +{ + int err = 0; + + if (!fsm_xo_priv->a1_enabled) { + err = regulator_enable(fsm_xo_priv->a1); + if (err != 0) + pr_err("Error = %d enabling xo buffer a1\n", err); + else + fsm_xo_priv->a1_enabled = 1; + } + return err; +} + +static inline int fsm_xo_disable_a1(void) +{ + int err = 0; + + if (fsm_xo_priv->a1_enabled) { + err = regulator_disable(fsm_xo_priv->a1); + if (err != 0) + pr_err("Error = %d disabling xo buffer a1\n", err); + else + fsm_xo_priv->a1_enabled = 0; + } + return err; +} +static long +fsm_xo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct fsm_xo_req req; + + /* Verify user arguments. */ + if (_IOC_TYPE(cmd) != FSM_XO_IOC_MAGIC) + return -ENOTTY; + + /* Lock for access */ + if (mutex_lock_interruptible(&fsm_xo_priv->lock)) + return -ERESTARTSYS; + + switch (cmd) { + case FSM_XO_IOC_CLKBUF: + 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 from user space\n"); + err = -EFAULT; + break; + } + + if (req.clkBuffer == XO_BUFFER_A0) { + if (req.clkBufEnable) + err = fsm_xo_enable_a0(); + else + err = fsm_xo_disable_a0(); + } else if (req.clkBuffer == XO_BUFFER_A1) { + if (req.clkBufEnable) + err = fsm_xo_enable_a1(); + else + err = fsm_xo_disable_a1(); + } else { + pr_err("Invalid ioctl argument.\n"); + err = -ENOTTY; + } + break; + default: + pr_err("Invalid ioctl command.\n"); + err = -ENOTTY; + break; + } + + mutex_unlock(&fsm_xo_priv->lock); + return err; +} + +static const struct file_operations fsm_xo_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsm_xo_ioctl, + .open = fsm_xo_open, + .release = fsm_xo_release +}; + +static struct miscdevice fsm_xo_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "fsm_xo", + .fops = &fsm_xo_fops +}; + +static int fsm_xo_probe(struct platform_device *pdev) +{ + int ret = 0; + + /* Initialize */ + fsm_xo_priv = kzalloc(sizeof(struct fsm_xo_priv_t), GFP_KERNEL); + + if (fsm_xo_priv == NULL) { + pr_alert("Not enough memory to initialize device\n"); + return -ENOMEM; + } + + fsm_xo_priv->a0 = regulator_get(&pdev->dev, "a0_clk_buffer"); + if (IS_ERR(fsm_xo_priv->a0)) { + pr_err("Error getting a0_clk_buffer\n"); + ret = PTR_ERR(fsm_xo_priv->a0); + fsm_xo_priv->a0 = NULL; + goto err; + } + fsm_xo_priv->a1 = regulator_get(&pdev->dev, "a1_clk_buffer"); + if (IS_ERR(fsm_xo_priv->a1)) { + pr_err("Error getting a1_clk_buffer\n"); + ret = PTR_ERR(fsm_xo_priv->a1); + fsm_xo_priv->a1 = NULL; + goto err; + } + + fsm_xo_priv->a0_enabled = 0; + fsm_xo_priv->a1_enabled = 0; + + /* Enable the clock buffers. AMSS depends on this on the FSM. */ + fsm_xo_enable_a0(); + fsm_xo_enable_a1(); + + mutex_init(&fsm_xo_priv->lock); + + ret = misc_register(&fsm_xo_dev); + if (ret < 0) + goto err; + + return 0; + +err: + if (fsm_xo_priv->a0) + regulator_put(fsm_xo_priv->a0); + if (fsm_xo_priv->a1) + regulator_put(fsm_xo_priv->a1); + + kfree(fsm_xo_priv); + fsm_xo_priv = NULL; + + return ret; +} + +static int __devexit fsm_xo_remove(struct platform_device *pdev) +{ + if (fsm_xo_priv && fsm_xo_priv->a0) + regulator_put(fsm_xo_priv->a0); + if (fsm_xo_priv && fsm_xo_priv->a1) + regulator_put(fsm_xo_priv->a1); + + kfree(fsm_xo_priv); + fsm_xo_priv = NULL; + + misc_deregister(&fsm_xo_dev); + return 0; +} + +static struct platform_driver fsm_xo_driver = { + .probe = fsm_xo_probe, + .remove = fsm_xo_remove, + .driver = { + .name = "fsm_xo_driver", + } +}; + +static int __init fsm_xo_init(void) +{ + return platform_driver_register(&fsm_xo_driver); +} + +static void __exit fsm_xo_exit(void) +{ + platform_driver_unregister(&fsm_xo_driver); +} + +module_init(fsm_xo_init); +module_exit(fsm_xo_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Provide userspace access to XO buffers in PMIC8058."); +MODULE_VERSION("1.00"); diff --git a/arch/arm/mach-omap2/devices.c b/arch/arm/mach-omap2/devices.c index e4336035c0ea85f94ce4e9d4e6b1cd0752e1f5e4..25153d55b460afa45e9e5a5f02010bbc25d1169e 100644 --- a/arch/arm/mach-omap2/devices.c +++ b/arch/arm/mach-omap2/devices.c @@ -297,6 +297,39 @@ static inline void omap_init_mbox(void) { } static inline void omap_init_sti(void) {} +#if defined CONFIG_ARCH_OMAP4 + +static struct platform_device codec_dmic0 = { + .name = "dmic-codec", + .id = 0, +}; + +static struct platform_device codec_dmic1 = { + .name = "dmic-codec", + .id = 1, +}; + +static struct platform_device codec_dmic2 = { + .name = "dmic-codec", + .id = 2, +}; + +static struct platform_device omap_abe_dai = { + .name = "omap-abe-dai", + .id = -1, +}; + +static inline void omap_init_abe(void) +{ + platform_device_register(&codec_dmic0); + platform_device_register(&codec_dmic1); + platform_device_register(&codec_dmic2); + platform_device_register(&omap_abe_dai); +} +#else +static inline void omap_init_abe(void) {} +#endif + #if defined(CONFIG_SND_SOC) || defined(CONFIG_SND_SOC_MODULE) static struct platform_device omap_pcm = { @@ -700,6 +733,7 @@ static int __init omap2_init_devices(void) * please keep these calls, and their implementations above, * in alphabetical order so they're easier to sort through. */ + omap_init_abe(); omap_init_audio(); omap_init_mcpdm(); omap_init_dmic(); diff --git a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c index 6abc75753e42b2048ab7e7a4e4d158064efcce8b..12e3e14de27f821b3aa5553cda3b1d8dcf24652b 100644 --- a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c @@ -763,6 +763,27 @@ static struct omap_hwmod_ocp_if *omap44xx_aess_masters[] = { static struct omap_hwmod_addr_space omap44xx_aess_addrs[] = { { + .name = "dmem", + .pa_start = 0x40180000, + .pa_end = 0x4018ffff + }, + { + .name = "cmem", + .pa_start = 0x401a0000, + .pa_end = 0x401a1fff + }, + { + .name = "smem", + .pa_start = 0x401c0000, + .pa_end = 0x401c5fff + }, + { + .name = "pmem", + .pa_start = 0x401e0000, + .pa_end = 0x401e1fff + }, + { + .name = "mpu", .pa_start = 0x401f1000, .pa_end = 0x401f13ff, .flags = ADDR_TYPE_RT @@ -781,6 +802,27 @@ static struct omap_hwmod_ocp_if omap44xx_l4_abe__aess = { static struct omap_hwmod_addr_space omap44xx_aess_dma_addrs[] = { { + .name = "dmem_dma", + .pa_start = 0x49080000, + .pa_end = 0x4908ffff + }, + { + .name = "cmem_dma", + .pa_start = 0x490a0000, + .pa_end = 0x490a1fff + }, + { + .name = "smem_dma", + .pa_start = 0x490c0000, + .pa_end = 0x490c5fff + }, + { + .name = "pmem_dma", + .pa_start = 0x490e0000, + .pa_end = 0x490e1fff + }, + { + .name = "dma", .pa_start = 0x490f1000, .pa_end = 0x490f13ff, .flags = ADDR_TYPE_RT @@ -5555,7 +5597,7 @@ static __initdata struct omap_hwmod *omap44xx_hwmods[] = { &omap44xx_mpu_private_hwmod, /* aess class */ -/* &omap44xx_aess_hwmod, */ + &omap44xx_aess_hwmod, /* bandgap class */ &omap44xx_bandgap_hwmod, diff --git a/arch/arm/mach-s5pv210/mach-goni.c b/arch/arm/mach-s5pv210/mach-goni.c index 32395664e87917f640cdc70b74f287304a939b6a..6f02117ae5d90178bcac979641068118c8122c79 100644 --- a/arch/arm/mach-s5pv210/mach-goni.c +++ b/arch/arm/mach-s5pv210/mach-goni.c @@ -233,6 +233,11 @@ static void __init goni_radio_init(void) gpio_request_one(gpio, GPIOF_OUT_INIT_HIGH, "FM_RST"); } +static u8 read_chg(void) +{ + return gpio_get_value(S5PV210_GPJ0(5)); +} + /* TSP */ static struct mxt_platform_data qt602240_platform_data = { .x_line = 17, @@ -244,6 +249,7 @@ static struct mxt_platform_data qt602240_platform_data = { .voltage = 2800000, /* 2.8V */ .orient = MXT_DIAGONAL, .irqflags = IRQF_TRIGGER_FALLING, + .read_chg = &read_chg, }; static struct s3c2410_platform_i2c i2c2_data __initdata = { diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 7c8a7d8467bf0b40fff40eb01a6533a83e10a18f..cb245ee03c33487dabefbebcef0e8cd491487e1f 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 @@ -756,6 +759,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 @@ -918,3 +934,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 37da2cc8f6187c4ca6fa0058b92a6b4d178f3d14..1c415af8f894f2f623af908a756b3d0ebc6930ca 100644 --- a/arch/arm/mm/Makefile +++ b/arch/arm/mm/Makefile @@ -93,6 +93,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 @@ -101,3 +102,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-l2x0.c b/arch/arm/mm/cache-l2x0.c index eaa6847eea4f43fa332c83a02124680fb594c5a9..cb9fc766cdf87ee0465867ed75c9ac4e24e9e092 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-2012, Code Aurora Forum. All rights reserved. * * 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 @@ -36,6 +37,7 @@ static u32 l2x0_cache_id; static unsigned int l2x0_sets; static unsigned int l2x0_ways; static unsigned long sync_reg_offset = L2X0_CACHE_SYNC; +static void pl310_save(void); static inline bool is_pl310_rev(int rev) { @@ -131,7 +133,7 @@ static inline void l2x0_flush_line(unsigned long addr) } #endif -static void l2x0_cache_sync(void) +void l2x0_cache_sync(void) { unsigned long flags; @@ -418,9 +420,9 @@ void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask) writel_relaxed(1, l2x0_base + L2X0_CTRL); } - outer_cache.inv_range = l2x0_inv_range; - outer_cache.clean_range = l2x0_clean_range; - outer_cache.flush_range = l2x0_flush_range; + 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; @@ -429,6 +431,9 @@ void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask) 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", l2x0_ways, l2x0_cache_id, aux, l2x0_size); + + /* Save the L2X0 contents, as they are not modified else where */ + pl310_save(); } #ifdef CONFIG_OF @@ -499,8 +504,9 @@ static void __init pl310_of_setup(const struct device_node *np, l2x0_base + L2X0_ADDR_FILTER_START); } } +#endif -static void __init pl310_save(void) +static void pl310_save(void) { u32 l2x0_revision = readl_relaxed(l2x0_base + L2X0_CACHE_ID) & L2X0_CACHE_ID_RTL_MASK; @@ -574,6 +580,7 @@ static void pl310_resume(void) l2x0_resume(); } +#ifdef CONFIG_OF static const struct l2x0_of_data pl310_data = { pl310_of_setup, pl310_save, @@ -629,3 +636,15 @@ int __init l2x0_of_init(u32 aux_val, u32 aux_mask) return 0; } #endif + +void l2cc_suspend(void) +{ + l2x0_disable(); + dmb(); +} + +void l2cc_resume(void) +{ + pl310_resume(); + dmb(); +} diff --git a/arch/arm/mm/context.c b/arch/arm/mm/context.c index ee9bb363d6064aa89276301940ff3980a15602d3..847ea19e8d63a573543d51321bd32ab43a29e7c2 100644 --- a/arch/arm/mm/context.c +++ b/arch/arm/mm/context.c @@ -14,8 +14,11 @@ #include #include +#include #include +#include + static DEFINE_RAW_SPINLOCK(cpu_asid_lock); unsigned int cpu_last_asid = ASID_FIRST_VERSION; #ifdef CONFIG_SMP @@ -37,6 +40,67 @@ DEFINE_PER_CPU(struct mm_struct *, current_mm); asm(" mcr p15, 0, %0, c13, c0, 1\n" : : "r" (asid)) #endif +static void write_contextidr(u32 contextidr) +{ + uncached_logk(LOGK_CTXID, (void *)contextidr); + asm("mcr p15, 0, %0, c13, c0, 1" : : "r" (contextidr)); + isb(); +} + +#ifdef CONFIG_PID_IN_CONTEXTIDR +static u32 read_contextidr(void) +{ + u32 contextidr; + asm("mrc p15, 0, %0, c13, c0, 1" : "=r" (contextidr)); + return contextidr; +} + +static int contextidr_notifier(struct notifier_block *unused, unsigned long cmd, + void *t) +{ + unsigned long flags; + u32 contextidr; + pid_t pid; + struct thread_info *thread = t; + + if (cmd != THREAD_NOTIFY_SWITCH) + return NOTIFY_DONE; + + pid = task_pid_nr(thread->task); + local_irq_save(flags); + contextidr = read_contextidr(); + contextidr &= ~ASID_MASK; + contextidr |= pid << ASID_BITS; + write_contextidr(contextidr); + local_irq_restore(flags); + + return NOTIFY_OK; +} + +static struct notifier_block contextidr_notifier_block = { + .notifier_call = contextidr_notifier, +}; + +static int __init contextidr_notifier_init(void) +{ + return thread_register_notifier(&contextidr_notifier_block); +} +arch_initcall(contextidr_notifier_init); + +static void set_asid(unsigned int asid) +{ + u32 contextidr = read_contextidr(); + contextidr &= ASID_MASK; + contextidr |= asid & ~ASID_MASK; + write_contextidr(contextidr); +} +#else +static void set_asid(unsigned int asid) +{ + write_contextidr(asid); +} +#endif + /* * We fork()ed a process, and we need a new context for the child * to run in. We reserve version 0 for initial tasks so we will @@ -52,8 +116,7 @@ void __init_new_context(struct task_struct *tsk, struct mm_struct *mm) static void flush_context(void) { /* set the reserved ASID before flushing the TLB */ - cpu_set_asid(0); - isb(); + set_asid(0); local_flush_tlb_all(); if (icache_is_vivt_asid_tagged()) { __flush_icache_all(); @@ -114,8 +177,7 @@ static void reset_context(void *info) set_mm_context(mm, asid); /* set the new ASID */ - cpu_set_asid(mm->context.id); - isb(); + set_asid(mm->context.id); } #else diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index db23ae4aaaaba3384d000181e4dfb1c9ac476e60..702408cabf92789c83d144a57425dba7b5cf424b 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -128,7 +128,7 @@ static void __dma_free_buffer(struct page *page, size_t size) */ static pte_t **consistent_pte; -#define DEFAULT_CONSISTENT_DMA_SIZE SZ_2M +#define DEFAULT_CONSISTENT_DMA_SIZE (7*SZ_2M) unsigned long consistent_base = CONSISTENT_END - DEFAULT_CONSISTENT_DMA_SIZE; @@ -467,18 +467,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); @@ -486,6 +490,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 */ @@ -494,7 +499,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 0000000000000000000000000000000000000000..3797e211a4d8abf9869989400647757c61746f45 --- /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 5bb48356d217292a074b006cc432447da6880ffd..ed03b339884b5d5440af2eb63523a83bc7d032f7 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -25,6 +25,15 @@ #include #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" @@ -509,6 +518,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 + +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; +} + struct fsr_info { int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs); int sig; @@ -536,6 +588,75 @@ hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *) fsr_info[nr].name = name; } +#ifdef CONFIG_MSM_KRAIT_TBB_ABORT_HANDLER +static int krait_tbb_fixup(unsigned int fsr, struct pt_regs *regs) +{ + int base_cond, cond = 0; + unsigned int p1, cpsr_z, cpsr_c, cpsr_n, cpsr_v; + + if ((read_cpuid_id() & 0xFFFFFFFC) != 0x510F04D0) + return 0; + + if (!thumb_mode(regs)) + return 0; + + /* If ITSTATE is 0, return quickly */ + if ((regs->ARM_cpsr & PSR_IT_MASK) == 0) + return 0; + + cpsr_n = (regs->ARM_cpsr & PSR_N_BIT) ? 1 : 0; + cpsr_z = (regs->ARM_cpsr & PSR_Z_BIT) ? 1 : 0; + cpsr_c = (regs->ARM_cpsr & PSR_C_BIT) ? 1 : 0; + cpsr_v = (regs->ARM_cpsr & PSR_V_BIT) ? 1 : 0; + + p1 = (regs->ARM_cpsr & BIT(12)) ? 1 : 0; + + base_cond = (regs->ARM_cpsr >> 13) & 0x07; + + switch (base_cond) { + case 0x0: /* equal */ + cond = cpsr_z; + break; + + case 0x1: /* carry set */ + cond = cpsr_c; + break; + + case 0x2: /* minus / negative */ + cond = cpsr_n; + break; + + case 0x3: /* overflow */ + cond = cpsr_v; + break; + + case 0x4: /* unsigned higher */ + cond = (cpsr_c == 1) && (cpsr_z == 0); + break; + + case 0x5: /* signed greater / equal */ + cond = (cpsr_n == cpsr_v); + break; + + case 0x6: /* signed greater */ + cond = (cpsr_z == 0) && (cpsr_n == cpsr_v); + break; + + case 0x7: /* always */ + cond = 1; + break; + }; + + if (cond == p1) { + pr_debug("Conditional abort fixup, PC=%08x, base=%d, cond=%d\n", + (unsigned int) regs->ARM_pc, base_cond, cond); + regs->ARM_pc += 2; + return 1; + } + return 0; +} +#endif + /* * Dispatch a data abort to the relevant handler. */ @@ -545,6 +666,16 @@ 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 + +#ifdef CONFIG_MSM_KRAIT_TBB_ABORT_HANDLER + if (krait_tbb_fixup(fsr, regs)) + return; +#endif + if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) return; @@ -577,6 +708,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/fsr-2level.c b/arch/arm/mm/fsr-2level.c index 18ca74c0f341062665242a685044049ce2c3bc34..3b016e65b348fa3b591bcccf5169acbe2d96fff9 100644 --- a/arch/arm/mm/fsr-2level.c +++ b/arch/arm/mm/fsr-2level.c @@ -30,7 +30,7 @@ static struct fsr_info 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" }, diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c index 8f5813bbffb560b15b44974ff3543f0b5457e026..59e252be6d917e20ae6c05111f47e62c2d54514b 100644 --- a/arch/arm/mm/init.c +++ b/arch/arm/mm/init.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include @@ -122,7 +124,14 @@ void show_mem(unsigned int filter) else shared += page_count(page) - 1; page++; +#ifdef CONFIG_SPARSEMEM + pfn1++; + if (!(pfn1 % PAGES_PER_SECTION)) + page = pfn_to_page(pfn1); + } while (pfn1 < pfn2); +#else } while (page < end); +#endif } printk("%d pages of RAM\n", total); @@ -226,6 +235,29 @@ static void __init arm_adjust_dma_zone(unsigned long *size, unsigned long *hole, } #endif +#ifdef CONFIG_ARCH_POPULATES_NODE_MAP +static void __init arm_bootmem_free_apnm(unsigned long max_low, + unsigned long max_high) +{ + unsigned long max_zone_pfns[MAX_NR_ZONES]; + struct memblock_region *reg; + + memset(max_zone_pfns, 0, sizeof(max_zone_pfns)); + + max_zone_pfns[0] = max_low; +#ifdef CONFIG_HIGHMEM + max_zone_pfns[ZONE_HIGHMEM] = max_high; +#endif + for_each_memblock(memory, reg) { + unsigned long start = memblock_region_memory_base_pfn(reg); + unsigned long end = memblock_region_memory_end_pfn(reg); + + add_active_range(0, start, end); + } + free_area_init_nodes(max_zone_pfns); +} + +#else static void __init arm_bootmem_free(unsigned long min, unsigned long max_low, unsigned long max_high) { @@ -283,6 +315,7 @@ static void __init arm_bootmem_free(unsigned long min, unsigned long max_low, free_area_init_node(0, zone_size, min, zhole_size); } +#endif #ifdef CONFIG_HAVE_ARCH_PFN_VALID int pfn_valid(unsigned long pfn) @@ -299,11 +332,12 @@ static void __init arm_memory_present(void) #else static void __init arm_memory_present(void) { - struct memblock_region *reg; - - for_each_memblock(memory, reg) - memory_present(0, memblock_region_memory_base_pfn(reg), - memblock_region_memory_end_pfn(reg)); + struct meminfo *mi = &meminfo; + int i; + for_each_bank(i, mi) { + memory_present(0, bank_pfn_start(&mi->bank[i]), + bank_pfn_end(&mi->bank[i])); + } } #endif @@ -322,10 +356,37 @@ phys_addr_t __init arm_memblock_steal(phys_addr_t size, phys_addr_t align) return phys; } +static int __init meminfo_cmp(const void *_a, const void *_b) +{ + const struct membank *a = _a, *b = _b; + long cmp = bank_pfn_start(a) - bank_pfn_start(b); + return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; +} + +#ifdef CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0 +unsigned long membank0_size; +EXPORT_SYMBOL(membank0_size); +unsigned long membank1_start; +EXPORT_SYMBOL(membank1_start); + +void __init find_membank0_hole(void) +{ + sort(&meminfo.bank, meminfo.nr_banks, + sizeof(meminfo.bank[0]), meminfo_cmp, NULL); + + membank0_size = meminfo.bank[0].size; + membank1_start = meminfo.bank[1].start; +} +#endif + void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc) { int i; +#ifndef CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0 + sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL); +#endif + for (i = 0; i < mi->nr_banks; i++) memblock_add(mi->bank[i].start, mi->bank[i].size); @@ -369,6 +430,28 @@ 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 + void __init bootmem_init(void) { unsigned long min, max_low, max_high; @@ -390,12 +473,16 @@ void __init bootmem_init(void) */ sparse_init(); +#ifdef CONFIG_ARCH_POPULATES_NODE_MAP + arm_bootmem_free_apnm(max_low, max_high); +#else /* * Now free the memory - free_area_init_node needs * the sparse mem_map arrays initialized by sparse_init() * for memmap_init_zone(), otherwise all PFNs are invalid. */ arm_bootmem_free(min, max_low, max_high); +#endif /* * This doesn't seem to be used by the Linux memory manager any @@ -466,7 +553,10 @@ free_memmap(unsigned long start_pfn, unsigned long end_pfn) } /* - * The mem_map array can get very big. Free the unused area of the memory map. + * The mem_map array can get very big. Free as much of the unused portion of + * the mem_map that we are allowed to. The page migration code moves pages + * in blocks that are rounded per the MAX_ORDER_NR_PAGES definition, so we + * can't free mem_map entries that may be dereferenced in this manner. */ static void __init free_unused_memmap(struct meminfo *mi) { @@ -480,7 +570,8 @@ static void __init free_unused_memmap(struct meminfo *mi) for_each_bank(i, mi) { struct membank *bank = &mi->bank[i]; - bank_start = bank_pfn_start(bank); + bank_start = round_down(bank_pfn_start(bank), + MAX_ORDER_NR_PAGES); #ifdef CONFIG_SPARSEMEM /* @@ -504,12 +595,8 @@ static void __init free_unused_memmap(struct meminfo *mi) if (prev_bank_end && prev_bank_end < bank_start) free_memmap(prev_bank_end, bank_start); - /* - * Align up here since the VM subsystem insists that the - * memmap entries are valid from the bank end aligned to - * MAX_ORDER_NR_PAGES. - */ - prev_bank_end = ALIGN(bank_pfn_end(bank), MAX_ORDER_NR_PAGES); + prev_bank_end = round_up(bank_pfn_end(bank), + MAX_ORDER_NR_PAGES); } #ifdef CONFIG_SPARSEMEM @@ -584,6 +671,9 @@ void __init mem_init(void) extern u32 dtcm_end; extern u32 itcm_end; #endif +#ifdef CONFIG_FIX_MOVABLE_ZONE + struct zone *zone; +#endif max_mapnr = pfn_to_page(max_pfn + PHYS_PFN_OFFSET) - mem_map; @@ -619,8 +709,23 @@ void __init mem_init(void) else if (!page_count(page)) free_pages++; page++; +#ifdef CONFIG_SPARSEMEM + pfn1++; + if (!(pfn1 % PAGES_PER_SECTION)) + page = pfn_to_page(pfn1); + } while (pfn1 < pfn2); +#else } while (page < end); +#endif + } + +#ifdef CONFIG_FIX_MOVABLE_ZONE + for_each_zone(zone) { + if (zone_idx(zone) == ZONE_MOVABLE) + total_unmovable_pages = totalram_pages - + zone->spanned_pages; } +#endif /* * Since our memory may not be contiguous, calculate the @@ -719,6 +824,7 @@ void __init mem_init(void) void free_initmem(void) { + unsigned long reclaimed_initmem; #ifdef CONFIG_HAVE_TCM extern char __tcm_start, __tcm_end; @@ -729,23 +835,61 @@ void free_initmem(void) #endif poison_init_mem(__init_begin, __init_end - __init_begin); - if (!machine_is_integrator() && !machine_is_cintegrator()) - totalram_pages += free_area(__phys_to_pfn(__pa(__init_begin)), + if (!machine_is_integrator() && !machine_is_cintegrator()) { + reclaimed_initmem = free_area(__phys_to_pfn(__pa(__init_begin)), __phys_to_pfn(__pa(__init_end)), "init"); + totalram_pages += reclaimed_initmem; +#ifdef CONFIG_FIX_MOVABLE_ZONE + total_unmovable_pages += reclaimed_initmem; +#endif + } +} + +#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; + + return __add_pages(nid, zone, start_pfn, nr_pages); +} + +int arch_physical_active_memory(u64 start, u64 size) +{ + return platform_physical_active_pages(start, size); } +int arch_physical_remove_memory(u64 start, u64 size) +{ + return platform_physical_remove_pages(start, size); +} + +int arch_physical_low_power_memory(u64 start, u64 size) +{ + return platform_physical_low_power_pages(start, size); +} +#endif + #ifdef CONFIG_BLK_DEV_INITRD static int keep_initrd; void free_initrd_mem(unsigned long start, unsigned long end) { + unsigned long reclaimed_initrd_mem; + if (!keep_initrd) { poison_init_mem((void *)start, PAGE_ALIGN(end) - start); - totalram_pages += free_area(__phys_to_pfn(__pa(start)), - __phys_to_pfn(__pa(end)), - "initrd"); + reclaimed_initrd_mem = free_area(__phys_to_pfn(__pa(start)), + __phys_to_pfn(__pa(end)), + "initrd"); + totalram_pages += reclaimed_initrd_mem; +#ifdef CONFIG_FIX_MOVABLE_ZONE + total_unmovable_pages += reclaimed_initrd_mem; +#endif } } diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c index 4f55f5062ab71432153fa3775303ccba85d83420..8df41e27636b62583688411c1162ddc798808c83 100644 --- a/arch/arm/mm/ioremap.c +++ b/arch/arm/mm/ioremap.c @@ -46,6 +46,14 @@ int ioremap_page(unsigned long virt, unsigned long phys, } EXPORT_SYMBOL(ioremap_page); +int ioremap_pages(unsigned long virt, unsigned long phys, unsigned long size, + const struct mem_type *mtype) +{ + return ioremap_page_range(virt, virt + size, phys, + __pgprot(mtype->prot_pte)); +} +EXPORT_SYMBOL(ioremap_pages); + void __check_kvm_seq(struct mm_struct *mm) { unsigned int seq; diff --git a/arch/arm/mm/mm.h b/arch/arm/mm/mm.h index 27f4a619b35d1c0039d7883c7804994c0540cdc6..411fbd92873b4d9779de4f4160cae9e0bcc46fd5 100644 --- a/arch/arm/mm/mm.h +++ b/arch/arm/mm/mm.h @@ -67,5 +67,7 @@ extern u32 arm_dma_limit; #define arm_dma_limit ((u32)~0) #endif +struct map_desc; + void __init bootmem_init(void); void arm_mm_memblock_reserve(void); diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 137858b2974135fdc456b72fbacd9c718249696e..e6b733bebd89e49072c2e5363c2998eb6a9e4cf5 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -52,6 +53,9 @@ pmd_t *top_pmd; #define CPOLICY_WRITEBACK 3 #define CPOLICY_WRITEALLOC 4 +#define RX_AREA_START _text +#define RX_AREA_END __start_rodata + static unsigned int cachepolicy __initdata = CPOLICY_WRITEBACK; static unsigned int ecc_mask __initdata = 0; pgprot_t pgprot_user; @@ -257,6 +261,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, @@ -442,6 +458,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; #endif @@ -461,6 +479,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; } } @@ -513,6 +534,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) { @@ -576,6 +600,7 @@ static void __init early_pte_install(pmd_t *pmd, pte_t *pte, unsigned long prot) BUG_ON(pmd_bad(*pmd)); } +#ifdef CONFIG_HIGHMEM static pte_t * __init early_pte_alloc_and_install(pmd_t *pmd, unsigned long addr, unsigned long prot) { @@ -586,6 +611,7 @@ static pte_t * __init early_pte_alloc_and_install(pmd_t *pmd, BUG_ON(pmd_bad(*pmd)); return pte_offset_kernel(pmd, addr); } +#endif static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr, unsigned long end, unsigned long pfn, @@ -842,6 +868,14 @@ void __init sanity_check_meminfo(void) { int i, j, highmem = 0; +#ifdef CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0 + find_membank0_hole(); +#endif + +#if (defined CONFIG_HIGHMEM) && (defined CONFIG_FIX_MOVABLE_ZONE) + if (movable_reserved_size && __pa(vmalloc_min) > movable_reserved_start) + vmalloc_min = __va(movable_reserved_start); +#endif for (i = 0, j = 0; i < meminfo.nr_banks; i++) { struct membank *bank = &meminfo.bank[j]; *bank = meminfo.bank[i]; @@ -1105,6 +1139,105 @@ static void __init kmap_init(void) #endif } +#ifdef CONFIG_STRICT_MEMORY_RWX +static struct { + pmd_t *pmd_to_flush; + pmd_t *pmd; + unsigned long addr; + pmd_t saved_pmd; + bool made_writeable; +} mem_unprotect; + +static DEFINE_SPINLOCK(mem_text_writeable_lock); + +void mem_text_writeable_spinlock(unsigned long *flags) +{ + spin_lock_irqsave(&mem_text_writeable_lock, *flags); +} + +void mem_text_writeable_spinunlock(unsigned long *flags) +{ + spin_unlock_irqrestore(&mem_text_writeable_lock, *flags); +} + +/* + * mem_text_address_writeable() and mem_text_address_restore() + * should be called as a pair. They are used to make the + * specified address in the kernel text section temporarily writeable + * when it has been marked read-only by STRICT_MEMORY_RWX. + * Used by kprobes and other debugging tools to set breakpoints etc. + * mem_text_address_writeable() is invoked before writing. + * After the write, mem_text_address_restore() must be called + * to restore the original state. + * This is only effective when used on the kernel text section + * marked as MEMORY_RX by map_lowmem() + * + * They must each be called with mem_text_writeable_lock locked + * by the caller, with no unlocking between the calls. + * The caller should release mem_text_writeable_lock immediately + * after the call to mem_text_address_restore(). + * Only the write and associated cache operations should be performed + * between the calls. + */ + +/* this function must be called with mem_text_writeable_lock held */ +void mem_text_address_writeable(unsigned long addr) +{ + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->active_mm; + pgd_t *pgd = pgd_offset(mm, addr); + pud_t *pud = pud_offset(pgd, addr); + + mem_unprotect.made_writeable = 0; + + if ((addr < (unsigned long)RX_AREA_START) || + (addr >= (unsigned long)RX_AREA_END)) + return; + + mem_unprotect.pmd = pmd_offset(pud, addr); + mem_unprotect.pmd_to_flush = mem_unprotect.pmd; + mem_unprotect.addr = addr & PAGE_MASK; + + if (addr & SECTION_SIZE) + mem_unprotect.pmd++; + + mem_unprotect.saved_pmd = *mem_unprotect.pmd; + if ((mem_unprotect.saved_pmd & PMD_TYPE_MASK) != PMD_TYPE_SECT) + return; + + *mem_unprotect.pmd &= ~PMD_SECT_APX; + + flush_pmd_entry(mem_unprotect.pmd_to_flush); + flush_tlb_kernel_page(mem_unprotect.addr); + mem_unprotect.made_writeable = 1; +} + +/* this function must be called with mem_text_writeable_lock held */ +void mem_text_address_restore(void) +{ + if (mem_unprotect.made_writeable) { + *mem_unprotect.pmd = mem_unprotect.saved_pmd; + flush_pmd_entry(mem_unprotect.pmd_to_flush); + flush_tlb_kernel_page(mem_unprotect.addr); + } +} +#endif + +void mem_text_write_kernel_word(unsigned long *addr, unsigned long word) +{ + unsigned long flags; + + mem_text_writeable_spinlock(&flags); + mem_text_address_writeable((unsigned long)addr); + *addr = word; + flush_icache_range((unsigned long)addr, + ((unsigned long)addr + sizeof(long))); + mem_text_address_restore(); + mem_text_writeable_spinunlock(&flags); +} +EXPORT_SYMBOL(mem_text_write_kernel_word); + +extern char __init_data[]; static void __init map_lowmem(void) { @@ -1125,8 +1258,46 @@ static void __init map_lowmem(void) map.pfn = __phys_to_pfn(start); map.virtual = __phys_to_virt(start); +#ifdef CONFIG_STRICT_MEMORY_RWX + if (start <= __pa(_text) && __pa(_text) < end) { + map.length = SECTION_SIZE; + map.type = MT_MEMORY; + + create_mapping(&map, false); + + map.pfn = __phys_to_pfn(start + SECTION_SIZE); + map.virtual = __phys_to_virt(start + SECTION_SIZE); + map.length = (unsigned long)RX_AREA_END - map.virtual; + map.type = MT_MEMORY_RX; + + create_mapping(&map, false); + + map.pfn = __phys_to_pfn(__pa(__start_rodata)); + map.virtual = (unsigned long)__start_rodata; + map.length = __init_begin - __start_rodata; + map.type = MT_MEMORY_R; + + create_mapping(&map, false); + + map.pfn = __phys_to_pfn(__pa(__init_begin)); + map.virtual = (unsigned long)__init_begin; + map.length = __init_data - __init_begin; + map.type = MT_MEMORY; + + create_mapping(&map, false); + + map.pfn = __phys_to_pfn(__pa(__init_data)); + map.virtual = (unsigned long)__init_data; + map.length = __phys_to_virt(end) - (unsigned int)__init_data; + map.type = MT_MEMORY_RW; + } else { + map.length = end - start; + map.type = MT_MEMORY_RW; + } +#else map.length = end - start; map.type = MT_MEMORY; +#endif create_mapping(&map, false); } diff --git a/arch/arm/mm/proc-macros.S b/arch/arm/mm/proc-macros.S index 2d8ff3ad86d3e1a2b9d9abd83a1bdd3ab42eba3a..5829bb379b2eb683aec077f7e85c657d1e5dbf97 100644 --- a/arch/arm/mm/proc-macros.S +++ b/arch/arm/mm/proc-macros.S @@ -306,6 +306,8 @@ ENTRY(\name\()_cache_fns) .long \name\()_flush_kern_dcache_area .long \name\()_dma_map_area .long \name\()_dma_unmap_area + .long \name\()_dma_inv_range + .long \name\()_dma_clean_range .long \name\()_dma_flush_range .size \name\()_cache_fns, . - \name\()_cache_fns .endm diff --git a/arch/arm/mm/proc-v6.S b/arch/arm/mm/proc-v6.S index 5900cd520e8456a9563b387f14f606d081ccaa56..501397a3d1789e8281852a884faea08369d2692b 100644 --- a/arch/arm/mm/proc-v6.S +++ b/arch/arm/mm/proc-v6.S @@ -107,6 +107,12 @@ ENTRY(cpu_v6_switch_mm) mcr p15, 0, r2, c7, c5, 6 @ flush BTAC/BTB mcr p15, 0, r2, c7, c10, 4 @ drain write buffer mcr p15, 0, r0, c2, c0, 0 @ set TTB 0 +#ifdef CONFIG_PID_IN_CONTEXTIDR + mrc p15, 0, r2, c13, c0, 1 @ read current context ID + bic r2, r2, #0xff @ extract the PID + and r1, r1, #0xff + orr r1, r1, r2 @ insert the PID into r1 +#endif mcr p15, 0, r1, c13, c0, 1 @ set context ID #endif mov pc, lr diff --git a/arch/arm/mm/proc-v7-2level.S b/arch/arm/mm/proc-v7-2level.S index 3a4b3e7b888c8c878e1e40984e97848151b530ca..1fda38b09c91dcfbe14a659b659367c65d41d88e 100644 --- a/arch/arm/mm/proc-v7-2level.S +++ b/arch/arm/mm/proc-v7-2level.S @@ -48,6 +48,12 @@ ENTRY(cpu_v7_switch_mm) #endif #ifdef CONFIG_ARM_ERRATA_754322 dsb +#endif +#ifdef CONFIG_PID_IN_CONTEXTIDR + mrc p15, 0, r2, c13, c0, 1 @ read current context ID + bic r2, r2, #0xff @ extract the PID + and r1, r1, #0xff + orr r1, r1, r2 @ insert the PID into r1 #endif mcr p15, 0, r2, c13, c0, 1 @ set reserved context ID isb @@ -140,7 +146,11 @@ ENDPROC(cpu_v7_set_pte_ext) * NOS = PRRR[24+n] = 1 - not outer shareable */ .equ PRRR, 0xff0a81a8 +#ifdef CONFIG_ARCH_MSM_SCORPIONMP +.equ NMRR, 0x40e080e0 +#else .equ NMRR, 0x40e040e0 +#endif /* * Macro for setting up the TTBRx and TTBCR registers. diff --git a/arch/arm/mm/proc-v7.S b/arch/arm/mm/proc-v7.S index c2e2b66f72b5cd08648085c28ffc5d8c2ee8359a..47dab27f62fd4cc58cf536313c10a224376e856e 100644 --- a/arch/arm/mm/proc-v7.S +++ b/arch/arm/mm/proc-v7.S @@ -56,6 +56,9 @@ ENTRY(cpu_v7_reset) bic r1, r1, #0x1 @ ...............m THUMB( bic r1, r1, #1 << 30 ) @ SCTLR.TE (Thumb exceptions) mcr p15, 0, r1, c1, c0, 0 @ disable 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) @@ -255,6 +258,31 @@ __v7_setup: mcr p15, 0, r5, c10, c2, 0 @ write PRRR mcr p15, 0, r6, c10, c2, 1 @ write NMRR #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 + #ifndef CONFIG_ARM_THUMBEE mrc p15, 0, r0, c0, c1, 0 @ read ID_PFR0 for ThumbEE and r0, r0, #(0xf << 12) @ ThumbEE enabled field diff --git a/arch/arm/mm/tlb-v7.S b/arch/arm/mm/tlb-v7.S index 845f461f8ec16847e69414f5c966a9ca4f961db6..0e885782f437178e341d473e6082d1d96175a0e8 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 @@ -67,7 +75,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 0000000000000000000000000000000000000000..f2d9457fbce6c1482fbdbfc9102fc1a0c8f30a99 --- /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_len, attr); + else + ret = iommu_unmap(domain, va, map_len); + + 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 0000000000000000000000000000000000000000..5f3c024757d1a5d5366b127ec321f7d8be86158f --- /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 0000000000000000000000000000000000000000..dee51fab8a49eff7a8dcfc5985aa164446220f17 --- /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/mm/vmregion.c b/arch/arm/mm/vmregion.c index a631016e1f8f6927961b12fc8377d32d7ea1662a..73e82f66bbcf86ff33a618a0f49aa7459bdeb936 100644 --- a/arch/arm/mm/vmregion.c +++ b/arch/arm/mm/vmregion.c @@ -46,8 +46,8 @@ arm_vmregion_alloc(struct arm_vmregion_head *head, size_t align, struct arm_vmregion *c, *new; if (head->vm_end - head->vm_start < size) { - printk(KERN_WARNING "%s: allocation too big (requested %#x)\n", - __func__, size); + printk(KERN_WARNING "%s: allocation too big (requested %#x, end:%lx, start:%lx)\n", + __func__, size, head->vm_end, head->vm_start); goto out; } diff --git a/arch/arm/oprofile/common.c b/arch/arm/oprofile/common.c index 4e0a371630b38fb3a950b9063f30a53d3ed0b5a7..338e8c2e59e666b3152bfde267fea44adf8395e3 100644 --- a/arch/arm/oprofile/common.c +++ b/arch/arm/oprofile/common.c @@ -36,10 +36,18 @@ char *op_name_from_perf_id(void) return "arm/armv6"; case ARM_PERF_PMU_ID_V6MP: return "arm/mpcore"; + case ARM_PERF_PMU_ID_CA5: + return "arm/armv7"; case ARM_PERF_PMU_ID_CA8: 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 0000000000000000000000000000000000000000..716e0873d3903fa640a671ec1c188495da0cc32b --- /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 0000000000000000000000000000000000000000..3de4d8bea7d712a629bb53e40b0b32f2f4352fe7 --- /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 0000000000000000000000000000000000000000..796dc8b43536dd6529f8f6f7b38627a172ecdbb5 --- /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 0000000000000000000000000000000000000000..29f9ac0f6dd30798416fefb9ba26c5ae1b53931b --- /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 0000000000000000000000000000000000000000..48309bed82e5b4f927db1d2875996fc340570e84 --- /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 0000000000000000000000000000000000000000..89f67fc5060f117c9a3cd08817254fc6a988f97b --- /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 0000000000000000000000000000000000000000..c8bebd842d1927e99a983b6a8b7a63019d0d2ebc --- /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 0000000000000000000000000000000000000000..4222844f12147d0846602d442e547789090a10bc --- /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 0000000000000000000000000000000000000000..aacc353741c966b551132ebeb767253b7b87e40b --- /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 0000000000000000000000000000000000000000..5417fc7f6a5d30f992095ebf20c34f23a1a0555e --- /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 0000000000000000000000000000000000000000..614eedc92181a6f440419daf5146ea23a8f228b3 --- /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 0000000000000000000000000000000000000000..1a9bb8ba330644efc56e83272bcff9a01aa73f92 --- /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/plat-omap/include/plat/dmic.h b/arch/arm/plat-omap/include/plat/dmic.h new file mode 100644 index 0000000000000000000000000000000000000000..1b0e49e778617177010f4e8948498eeed3af2d30 --- /dev/null +++ b/arch/arm/plat-omap/include/plat/dmic.h @@ -0,0 +1,79 @@ +/* + * dmic.h -- OMAP Digital Microphone Controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ASM_ARCH_OMAP_DMIC_H +#define __ASM_ARCH_OMAP_DMIC_H + +#define OMAP44XX_DMIC_L3_BASE 0x4902e000 + +#define OMAP_DMIC_REVISION 0x00 +#define OMAP_DMIC_SYSCONFIG 0x10 +#define OMAP_DMIC_IRQSTATUS_RAW 0x24 +#define OMAP_DMIC_IRQSTATUS 0x28 +#define OMAP_DMIC_IRQENABLE_SET 0x2C +#define OMAP_DMIC_IRQENABLE_CLR 0x30 +#define OMAP_DMIC_IRQWAKE_EN 0x34 +#define OMAP_DMIC_DMAENABLE_SET 0x38 +#define OMAP_DMIC_DMAENABLE_CLR 0x3C +#define OMAP_DMIC_DMAWAKEEN 0x40 +#define OMAP_DMIC_CTRL 0x44 +#define OMAP_DMIC_DATA 0x48 +#define OMAP_DMIC_FIFO_CTRL 0x4C +#define OMAP_DMIC_FIFO_DMIC1R_DATA 0x50 +#define OMAP_DMIC_FIFO_DMIC1L_DATA 0x54 +#define OMAP_DMIC_FIFO_DMIC2R_DATA 0x58 +#define OMAP_DMIC_FIFO_DMIC2L_DATA 0x5C +#define OMAP_DMIC_FIFO_DMIC3R_DATA 0x60 +#define OMAP_DMIC_FIFO_DMIC3L_DATA 0x64 + +/* + * DMIC_IRQ bit fields + * IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR + */ + +#define OMAP_DMIC_IRQ (1 << 0) +#define OMAP_DMIC_IRQ_FULL (1 << 1) +#define OMAP_DMIC_IRQ_ALMST_EMPTY (1 << 2) +#define OMAP_DMIC_IRQ_EMPTY (1 << 3) +#define OMAP_DMIC_IRQ_MASK 0x07 + +/* + * DMIC_DMAENABLE bit fields + */ + +#define OMAP_DMIC_DMA_ENABLE 0x1 + +/* + * DMIC_CTRL bit fields + */ + +#define OMAP_DMIC_UP1_ENABLE 0x0001 +#define OMAP_DMIC_UP2_ENABLE 0x0002 +#define OMAP_DMIC_UP3_ENABLE 0x0004 +#define OMAP_DMIC_UP_ENABLE_MASK 0x0007 +#define OMAP_DMIC_FORMAT 0x0008 +#define OMAP_DMIC_POLAR1 0x0010 +#define OMAP_DMIC_POLAR2 0x0020 +#define OMAP_DMIC_POLAR3 0x0040 +#define OMAP_DMIC_POLAR_MASK 0x0070 +#define OMAP_DMIC_CLK_DIV_SHIFT 7 +#define OMAP_DMIC_CLK_DIV_MASK 0x0380 +#define OMAP_DMIC_RESET 0x0400 + +#define OMAP_DMIC_ENABLE_MASK 0x007 + +#define OMAP_DMICOUTFORMAT_LJUST (0 << 3) +#define OMAP_DMICOUTFORMAT_RJUST (1 << 3) + +/* + * DMIC_FIFO_CTRL bit fields + */ + +#define OMAP_DMIC_THRES_MAX 0xF + +#endif diff --git a/arch/arm/plat-omap/include/plat/mcpdm.h b/arch/arm/plat-omap/include/plat/mcpdm.h new file mode 100644 index 0000000000000000000000000000000000000000..1ed2b8f678c009a643652646e0bbd94cde6378c6 --- /dev/null +++ b/arch/arm/plat-omap/include/plat/mcpdm.h @@ -0,0 +1,30 @@ +/* + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __OMAP_PLAT_MCPDM_H__ +#define __OMAP_PLAT_MCPDM_H__ + +#include + +struct omap_mcpdm_platform_data { + int (*device_enable) (struct platform_device *pdev); + int (*device_shutdown) (struct platform_device *pdev); + int (*device_idle) (struct platform_device *pdev); +}; + +#endif diff --git a/arch/arm/tools/mach-types b/arch/arm/tools/mach-types index f9c9f33f8cbe2f651277793400d8287eec0073c9..d283705c0b10bb3071f3c860e4e79ac70d949dd7 100644 --- a/arch/arm/tools/mach-types +++ b/arch/arm/tools/mach-types @@ -468,6 +468,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 @@ -481,6 +482,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 @@ -498,6 +501,8 @@ vvbox_sdlite2 MACH_VVBOX_SDLITE2 VVBOX_SDLITE2 2858 vvbox_sdpro4 MACH_VVBOX_SDPRO4 VVBOX_SDPRO4 2859 mx257sx MACH_MX257SX MX257SX 2861 goni MACH_GONI GONI 2862 +msm8x55_svlte_ffa MACH_MSM8X55_SVLTE_FFA MSM8X55_SVLTE_FFA 2863 +msm8x55_svlte_surf MACH_MSM8X55_SVLTE_SURF MSM8X55_SVLTE_SURF 2864 bv07 MACH_BV07 BV07 2882 openrd_ultimate MACH_OPENRD_ULTIMATE OPENRD_ULTIMATE 2884 devixp MACH_DEVIXP DEVIXP 2885 @@ -522,15 +527,20 @@ mx53_smd MACH_MX53_SMD MX53_SMD 3011 msm8x60_rumi3 MACH_MSM8X60_RUMI3 MSM8X60_RUMI3 3016 msm8x60_ffa MACH_MSM8X60_FFA MSM8X60_FFA 3017 cm_a510 MACH_CM_A510 CM_A510 3020 +fsm9xxx_surf MACH_FSM9XXX_SURF FSM9XXX_SURF 3028 +fsm9xxx_ffa MACH_FSM9XXX_FFA FSM9XXX_FFA 3029 tx28 MACH_TX28 TX28 3043 pcontrol_g20 MACH_PCONTROL_G20 PCONTROL_G20 3062 vpr200 MACH_VPR200 VPR200 3087 torbreck MACH_TORBRECK TORBRECK 3090 prima2_evb MACH_PRIMA2_EVB PRIMA2_EVB 3103 +msm8x60_fluid MACH_MSM8X60_FLUID MSM8X60_FLUID 3124 paz00 MACH_PAZ00 PAZ00 3128 acmenetusfoxg20 MACH_ACMENETUSFOXG20 ACMENETUSFOXG20 3129 +msm8x60_fusion MACH_MSM8X60_FUSION MSM8X60_FUSION 3181 ag5evm MACH_AG5EVM AG5EVM 3189 tsunagi MACH_TSUNAGI TSUNAGI 3197 +msm8x60_fusn_ffa MACH_MSM8X60_FUSN_FFA MSM8X60_FUSN_FFA 3199 ics_if_voip MACH_ICS_IF_VOIP ICS_IF_VOIP 3206 wlf_cragg_6410 MACH_WLF_CRAGG_6410 WLF_CRAGG_6410 3207 trimslice MACH_TRIMSLICE TRIMSLICE 3209 @@ -696,6 +706,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_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 @@ -879,6 +890,7 @@ bct MACH_BCT BCT 3582 tuscan MACH_TUSCAN TUSCAN 3583 xbt_sam9g45 MACH_XBT_SAM9G45 XBT_SAM9G45 3584 enbw_cmc MACH_ENBW_CMC ENBW_CMC 3585 +msm8x60_dragon MACH_MSM8X60_DRAGON MSM8X60_DRAGON 3586 ch104mx257 MACH_CH104MX257 CH104MX257 3587 openpri MACH_OPENPRI OPENPRI 3588 am335xevm MACH_AM335XEVM AM335XEVM 3589 @@ -965,6 +977,7 @@ pia_am35x MACH_PIA_AM35X PIA_AM35X 3671 cedar MACH_CEDAR CEDAR 3672 picasso_e MACH_PICASSO_E PICASSO_E 3673 samsung_e60 MACH_SAMSUNG_E60 SAMSUNG_E60 3674 +msm9615_cdp MACH_MSM9615_CDP MSM9615_CDP 3675 sdvr_mini MACH_SDVR_MINI SDVR_MINI 3676 omap3_ij3k MACH_OMAP3_IJ3K OMAP3_IJ3K 3677 modasmc1 MACH_MODASMC1 MODASMC1 3678 @@ -1038,6 +1051,7 @@ ptip_murnau MACH_PTIP_MURNAU PTIP_MURNAU 3752 ptip_classic MACH_PTIP_CLASSIC PTIP_CLASSIC 3753 mx53grb MACH_MX53GRB MX53GRB 3754 gagarin MACH_GAGARIN GAGARIN 3755 +msm7627a_qrd1 MACH_MSM7627A_QRD1 MSM7627A_QRD1 3756 nas2big MACH_NAS2BIG NAS2BIG 3757 superfemto MACH_SUPERFEMTO SUPERFEMTO 3758 teufel MACH_TEUFEL TEUFEL 3759 @@ -1169,3 +1183,16 @@ elite_ulk MACH_ELITE_ULK ELITE_ULK 3888 pov2 MACH_POV2 POV2 3889 ipod_touch_2g MACH_IPOD_TOUCH_2G IPOD_TOUCH_2G 3890 da850_pqab MACH_DA850_PQAB DA850_PQAB 3891 +msm7627a_evb MACH_MSM7627A_EVB MSM7627A_EVB 3934 +apq8064_cdp MACH_APQ8064_CDP APQ8064_CDP 3948 +apq8064_mtp MACH_APQ8064_MTP APQ8064_MTP 3949 +apq8064_liquid MACH_APQ8064_LIQUID APQ8064_LIQUID 3951 +mpq8064_cdp MACH_MPQ8064_CDP MPQ8064_CDP 3993 +mpq8064_hrd MACH_MPQ8064_HRD MPQ8064_HRD 3994 +mpq8064_dtv MACH_MPQ8064_DTV MPQ8064_DTV 3995 +msm7627a_qrd3 MACH_MSM7627A_QRD3 MSM7627A_QRD3 4005 +msm8625_surf MACH_MSM8625_SURF MSM8625_SURF 4037 +msm8625_evb MACH_MSM8625_EVB MSM8625_EVB 4042 +msm8625_qrd7 MACH_MSM8625_QRD7 MSM8625_QRD7 4095 +msm8625_ffa MACH_MSM8625_FFA MSM8625_FFA 4166 +msm8625_evt MACH_MSM8625_EVT MSM8625_EVT 4193 diff --git a/arch/arm/vfp/vfphw.S b/arch/arm/vfp/vfphw.S index 2d30c7f6edd32ddd5b93e6b8356ae679ce983041..bd3d7717e85837edb797cad036ef82939d8c029a 100644 --- a/arch/arm/vfp/vfphw.S +++ b/arch/arm/vfp/vfphw.S @@ -184,6 +184,22 @@ look_for_VFP_exceptions: tst r5, #FPSCR_IXE bne process_exception +#ifdef CONFIG_ARCH_MSM_KRAIT + @ Krait does not set FPEXC.DEX for unsupported short vector instructions + mrc p15, 0, r2, c0, c0, 0 + ldr r4, =0xff00fc00 + and r4, r2, r4 + ldr r2, =0x51000400 + cmp r2, r4 + bne skip + + tst r5, #FPSCR_LENGTH_MASK + beq skip + orr r1, r1, #FPEXC_DEX + b process_exception +skip: +#endif + @ Fall into hand on to next handler - appropriate coproc instr @ not recognised by VFP diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index 1ef803aa7a5c3be43295e371dc916b14ca881c1c..4387287417108a0496a0e2e8843cfd19854edfe2 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c @@ -445,7 +445,7 @@ static void vfp_enable(void *unused) } #ifdef CONFIG_CPU_PM -static int vfp_pm_suspend(void) +int vfp_pm_suspend(void) { struct thread_info *ti = current_thread_info(); u32 fpexc = fmrx(FPEXC); @@ -471,7 +471,7 @@ static int vfp_pm_suspend(void) return 0; } -static void vfp_pm_resume(void) +void vfp_pm_resume(void) { /* ensure we have access to the vfp */ vfp_enable(NULL); @@ -719,7 +719,8 @@ static int __init vfp_init(void) if ((fmrx(MVFR1) & 0x000fff00) == 0x00011100) elf_hwcap |= HWCAP_NEON; #endif - if ((fmrx(MVFR1) & 0xf0000000) == 0x10000000) + if ((fmrx(MVFR1) & 0xf0000000) == 0x10000000 || + (read_cpuid_id() & 0xff00fc00) == 0x51000400) elf_hwcap |= HWCAP_VFPv4; } } diff --git a/arch/powerpc/include/asm/time.h b/arch/powerpc/include/asm/time.h index 2136f58a54e80a32fd014dd66b84196b9f0bc1df..0c6db93318e3f7743139d75215cc31dafbc9326f 100644 --- a/arch/powerpc/include/asm/time.h +++ b/arch/powerpc/include/asm/time.h @@ -206,5 +206,7 @@ extern void secondary_cpu_time_init(void); DECLARE_PER_CPU(u64, decrementers_next_tb); +extern void decrementer_check_overflow(void); + #endif /* __KERNEL__ */ #endif /* __POWERPC_TIME_H */ diff --git a/arch/powerpc/kernel/time.c b/arch/powerpc/kernel/time.c index 2c42cd72d0f5b1a33eb501da54a8e5edbb08e63e..f8df241e8edf7578a76a1164dcf39871bb462c75 100644 --- a/arch/powerpc/kernel/time.c +++ b/arch/powerpc/kernel/time.c @@ -769,6 +769,15 @@ static void __init clocksource_init(void) clock->name, clock->mult, clock->shift); } +void decrementer_check_overflow(void) +{ + u64 now = get_tb_or_rtc(); + struct decrementer_clock *decrementer = &__get_cpu_var(decrementers); + + if (now >= decrementer->next_tb) + set_dec(1); +} + static int decrementer_set_next_event(unsigned long evt, struct clock_event_device *dev) { diff --git a/arch/sparc/kernel/module.c b/arch/sparc/kernel/module.c index 276359e1ff56505c2b15a1dd364565963f75935d..f34b238a66a7ac7fd2c1d00e22bf8363013d5abe 100644 --- a/arch/sparc/kernel/module.c +++ b/arch/sparc/kernel/module.c @@ -20,6 +20,8 @@ #include "entry.h" +#include "entry.h" + #ifdef CONFIG_SPARC64 #include diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 146bb6218eec3daa3cdbe0472c04bcfac7500e8a..f493e52f62588168bcb9e6dbf4edb31c4509c322 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -749,7 +749,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/block/blk-core.c b/block/blk-core.c index 1f61b74867e41d3f74f61aeec539e8b00157dacf..038d11f8ad121d9f2aefd35916922724f58dca4e 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -1546,7 +1546,7 @@ generic_make_request_checks(struct bio *bio) goto end_io; } - if (unlikely(!(bio->bi_rw & REQ_DISCARD) && + if (unlikely(!(bio->bi_rw & (REQ_DISCARD | REQ_SANITIZE)) && nr_sectors > queue_max_hw_sectors(q))) { printk(KERN_ERR "bio too big device %s (%u > %u)\n", bdevname(bio->bi_bdev, b), @@ -1594,6 +1594,14 @@ generic_make_request_checks(struct bio *bio) goto end_io; } + if ((bio->bi_rw & REQ_SANITIZE) && + (!blk_queue_sanitize(q))) { + pr_info("%s - got a SANITIZE request but the queue " + "doesn't support sanitize requests", __func__); + err = -EOPNOTSUPP; + goto end_io; + } + if (blk_throtl_bio(q, bio)) return false; /* throttled, will be resubmitted later */ @@ -1699,7 +1707,8 @@ void submit_bio(int rw, struct bio *bio) * If it's a regular read/write or a barrier with data attached, * go through the normal accounting stuff before submission. */ - if (bio_has_data(bio) && !(rw & REQ_DISCARD)) { + if (bio_has_data(bio) && + (!(rw & (REQ_DISCARD | REQ_SANITIZE)))) { if (rw & WRITE) { count_vm_events(PGPGOUT, count); } else { @@ -1745,7 +1754,7 @@ EXPORT_SYMBOL(submit_bio); */ int blk_rq_check_limits(struct request_queue *q, struct request *rq) { - if (rq->cmd_flags & REQ_DISCARD) + if (rq->cmd_flags & (REQ_DISCARD | REQ_SANITIZE)) return 0; if (blk_rq_sectors(rq) > queue_max_sectors(q) || diff --git a/block/blk-lib.c b/block/blk-lib.c index 2b461b496a788c11d00616efaf16df121e620160..280d63e6c30c0ba99469862ef9c75b367ddb2528 100644 --- a/block/blk-lib.c +++ b/block/blk-lib.c @@ -114,6 +114,57 @@ int blkdev_issue_discard(struct block_device *bdev, sector_t sector, } EXPORT_SYMBOL(blkdev_issue_discard); +/** + * blkdev_issue_sanitize - queue a sanitize request + * @bdev: blockdev to issue sanitize for + * @gfp_mask: memory allocation flags (for bio_alloc) + * + * Description: + * Issue a sanitize request for the specified block device + */ +int blkdev_issue_sanitize(struct block_device *bdev, gfp_t gfp_mask) +{ + DECLARE_COMPLETION_ONSTACK(wait); + struct request_queue *q = bdev_get_queue(bdev); + int type = REQ_WRITE | REQ_SANITIZE; + struct bio_batch bb; + struct bio *bio; + int ret = 0; + + if (!q) + return -ENXIO; + + if (!blk_queue_sanitize(q)) { + pr_err("%s - card doesn't support sanitize", __func__); + return -EOPNOTSUPP; + } + + bio = bio_alloc(gfp_mask, 1); + if (!bio) + return -ENOMEM; + + atomic_set(&bb.done, 1); + bb.flags = 1 << BIO_UPTODATE; + bb.wait = &wait; + + bio->bi_end_io = bio_batch_end_io; + bio->bi_bdev = bdev; + bio->bi_private = &bb; + + atomic_inc(&bb.done); + submit_bio(type, bio); + + /* Wait for bios in-flight */ + if (!atomic_dec_and_test(&bb.done)) + wait_for_completion(&wait); + + if (!test_bit(BIO_UPTODATE, &bb.flags)) + ret = -EIO; + + return ret; +} +EXPORT_SYMBOL(blkdev_issue_sanitize); + /** * blkdev_issue_zeroout - generate number of zero filed write bios * @bdev: blockdev to issue diff --git a/block/blk-merge.c b/block/blk-merge.c index 160035f548823482968bcd58298fcf9c309e7d62..4fced1be5d9730ca3bfcdb98472430eb6f7bc3a0 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -382,6 +382,12 @@ static int attempt_merge(struct request_queue *q, struct request *req, if ((req->cmd_flags & REQ_SECURE) != (next->cmd_flags & REQ_SECURE)) return 0; + /* + * Don't merge file system requests and sanitize requests + */ + if ((req->cmd_flags & REQ_SANITIZE) != (next->cmd_flags & REQ_SANITIZE)) + return 0; + /* * not contiguous */ diff --git a/block/elevator.c b/block/elevator.c index f016855a46b094628190f68e698ae21a936912e6..74fd51b154fd4b4d03ce9514b988e03df6e0f6cc 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -72,7 +72,43 @@ static int elv_iosched_allow_merge(struct request *rq, struct bio *bio) */ bool elv_rq_merge_ok(struct request *rq, struct bio *bio) { - if (!blk_rq_merge_ok(rq, bio)) + if (!rq_mergeable(rq)) + return 0; + + /* + * Don't merge file system requests and discard requests + */ + if ((bio->bi_rw & REQ_DISCARD) != (rq->bio->bi_rw & REQ_DISCARD)) + return 0; + + /* + * Don't merge discard requests and secure discard requests + */ + if ((bio->bi_rw & REQ_SECURE) != (rq->bio->bi_rw & REQ_SECURE)) + return 0; + + /* + * Don't merge sanitize requests + */ + if ((bio->bi_rw & REQ_SANITIZE) != (rq->bio->bi_rw & REQ_SANITIZE)) + return 0; + + /* + * different data direction or already started, don't merge + */ + if (bio_data_dir(bio) != rq_data_dir(rq)) + return 0; + + /* + * must be same device and not a special request + */ + if (rq->rq_disk != bio->bi_bdev->bd_disk || rq->special) + return 0; + + /* + * only merge integrity protected bio into ditto rq + */ + if (bio_integrity(bio) != blk_integrity_rq(rq)) return 0; if (!elv_iosched_allow_merge(rq, bio)) @@ -592,7 +628,7 @@ void __elv_add_request(struct request_queue *q, struct request *rq, int where) if (rq->cmd_flags & REQ_SOFTBARRIER) { /* barriers are scheduling boundary, update end_sector */ if (rq->cmd_type == REQ_TYPE_FS || - (rq->cmd_flags & REQ_DISCARD)) { + (rq->cmd_flags & (REQ_DISCARD | REQ_SANITIZE))) { q->end_sector = rq_end_sector(rq); q->boundary_rq = rq; } diff --git a/block/ioctl.c b/block/ioctl.c index ba15b2dbfb98ea55911109543f35889ea7b615da..dd76ba09aa732f4256c29971ed6636ca39460133 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -132,6 +132,11 @@ static int blk_ioctl_discard(struct block_device *bdev, uint64_t start, return blkdev_issue_discard(bdev, start, len, GFP_KERNEL, flags); } +static int blk_ioctl_sanitize(struct block_device *bdev) +{ + return blkdev_issue_sanitize(bdev, GFP_KERNEL); +} + static int put_ushort(unsigned long arg, unsigned short val) { return put_user(val, (unsigned short __user *)arg); @@ -234,6 +239,10 @@ int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, set_device_ro(bdev, n); return 0; + case BLKSANITIZE: + ret = blk_ioctl_sanitize(bdev); + break; + case BLKDISCARD: case BLKSECDISCARD: { uint64_t range[2]; diff --git a/drivers/Kconfig b/drivers/Kconfig index a765f400a60f12e5d594c4dfb6ba7e40e4cfa1cc..286a4d4b53f353eb3617fc10e78807367e3a3529 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -50,6 +50,10 @@ source "drivers/i2c/Kconfig" source "drivers/spi/Kconfig" +source "drivers/spmi/Kconfig" + +source "drivers/slimbus/Kconfig" + source "drivers/hsi/Kconfig" source "drivers/pps/Kconfig" @@ -142,4 +146,6 @@ source "drivers/virt/Kconfig" source "drivers/devfreq/Kconfig" +source "drivers/gud/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index b5d2823d8d31e606e71b2788621fc277555ab26d..bea505cd15ee6c7021f878490a35818909eb3b60 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -53,6 +53,8 @@ obj-$(CONFIG_ATA) += ata/ obj-$(CONFIG_TARGET_CORE) += target/ obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ +obj-$(CONFIG_SPMI) += spmi/ +obj-$(CONFIG_SLIMBUS) += slimbus/ obj-y += hsi/ obj-y += net/ obj-$(CONFIG_ATM) += atm/ @@ -135,3 +137,6 @@ obj-$(CONFIG_VIRT_DRIVERS) += virt/ obj-$(CONFIG_HYPERV) += hv/ obj-$(CONFIG_PM_DEVFREQ) += devfreq/ + +#MobiCore +obj-$(CONFIG_MOBICORE_SUPPORT) += gud/ diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 1131dd73d5d027ce556c454ca003aa70e500a185..4201aba462fe3e418750d693910df85a928522fd 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -192,6 +192,20 @@ config DMA_SHARED_BUFFER APIs extension; the file's descriptor can then be passed on to other driver. +config GENLOCK + bool "Enable a generic cross-process locking mechanism" + depends on ANON_INODES + help + Enable a generic cross-process locking API to provide protection + for shared memory objects such as graphics buffers. + +config GENLOCK_MISCDEVICE + bool "Enable a misc-device for userspace to access the genlock engine" + depends on GENLOCK + help + Create a miscdevice for the purposes of allowing userspace to create + and interact with locks created using genlock. + config SYNC bool "Synchronization framework" default n diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 0e4d3dadd6b15ae12ec8597e339e1660d580c9b5..f81ab9082f11b783f4b4b80e81b5da85ae2bc2ca 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -4,12 +4,13 @@ obj-y := core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o + topology.o sys.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-y += power/ obj-$(CONFIG_HAS_DMA) += dma-mapping.o obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf.o +obj-$(CONFIG_GENLOCK) += genlock.o obj-$(CONFIG_ISA) += isa.o obj-$(CONFIG_FW_LOADER) += firmware_class.o obj-$(CONFIG_NUMA) += node.o diff --git a/drivers/base/base.h b/drivers/base/base.h index 6ee17bb391a93be6c9d9898d48839615f2288a2e..f372baf8ed015ef68b609fc08adbcb1a1ab9389c 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -99,6 +99,7 @@ extern int hypervisor_init(void); static inline int hypervisor_init(void) { return 0; } #endif extern int platform_bus_init(void); +extern int system_bus_init(void); extern void cpu_dev_init(void); extern int bus_add_device(struct device *dev); diff --git a/drivers/base/core.c b/drivers/base/core.c index e28ce9898af4e1bab331fa3b9411b2bbc6575856..8e01c9417f4db3d2475b7bc75077c28b71faf6bb 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -649,6 +649,7 @@ void device_initialize(struct device *dev) { dev->kobj.kset = devices_kset; kobject_init(&dev->kobj, &device_ktype); + INIT_LIST_HEAD(&dev->deferred_probe); INIT_LIST_HEAD(&dev->dma_pools); mutex_init(&dev->mutex); lockdep_set_novalidate_class(&dev->mutex); diff --git a/drivers/base/genlock.c b/drivers/base/genlock.c new file mode 100644 index 0000000000000000000000000000000000000000..5e1d7af5e5295a6d45ec0a65c32660d93959bd1e --- /dev/null +++ b/drivers/base/genlock.c @@ -0,0 +1,819 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/* Lock states - can either be unlocked, held as an exclusive write lock or a + * shared read lock + */ + +#define _UNLOCKED 0 +#define _RDLOCK GENLOCK_RDLOCK +#define _WRLOCK GENLOCK_WRLOCK + +#define GENLOCK_LOG_ERR(fmt, args...) \ +pr_err("genlock: %s: " fmt, __func__, ##args) + +struct genlock { + struct list_head active; /* List of handles holding lock */ + spinlock_t lock; /* Spinlock to protect the lock internals */ + wait_queue_head_t queue; /* Holding pen for processes pending lock */ + struct file *file; /* File structure for exported lock */ + int state; /* Current state of the lock */ + struct kref refcount; +}; + +struct genlock_handle { + struct genlock *lock; /* Lock currently attached to the handle */ + struct list_head entry; /* List node for attaching to a lock */ + struct file *file; /* File structure associated with handle */ + int active; /* Number of times the active lock has been + taken */ +}; + +/* + * Create a spinlock to protect against a race condition when a lock gets + * released while another process tries to attach it + */ + +static DEFINE_SPINLOCK(genlock_file_lock); + +static void genlock_destroy(struct kref *kref) +{ + struct genlock *lock = container_of(kref, struct genlock, + refcount); + + /* + * Clear the private data for the file descriptor in case the fd is + * still active after the lock gets released + */ + + spin_lock(&genlock_file_lock); + if (lock->file) + lock->file->private_data = NULL; + spin_unlock(&genlock_file_lock); + + kfree(lock); +} + +/* + * Release the genlock object. Called when all the references to + * the genlock file descriptor are released + */ + +static int genlock_release(struct inode *inodep, struct file *file) +{ + struct genlock *lock = file->private_data; + /* + * Clear the refrence back to this file structure to avoid + * somehow reusing the lock after the file has been destroyed + */ + + if (lock) + lock->file = NULL; + + return 0; +} + +static const struct file_operations genlock_fops = { + .release = genlock_release, +}; + +/** + * genlock_create_lock - Create a new lock + * @handle - genlock handle to attach the lock to + * + * Returns: a pointer to the genlock + */ + +struct genlock *genlock_create_lock(struct genlock_handle *handle) +{ + struct genlock *lock; + + if (IS_ERR_OR_NULL(handle)) { + GENLOCK_LOG_ERR("Invalid handle\n"); + return ERR_PTR(-EINVAL); + } + + if (handle->lock != NULL) { + GENLOCK_LOG_ERR("Handle already has a lock attached\n"); + return ERR_PTR(-EINVAL); + } + + lock = kzalloc(sizeof(*lock), GFP_KERNEL); + if (lock == NULL) { + GENLOCK_LOG_ERR("Unable to allocate memory for a lock\n"); + return ERR_PTR(-ENOMEM); + } + + INIT_LIST_HEAD(&lock->active); + init_waitqueue_head(&lock->queue); + spin_lock_init(&lock->lock); + + lock->state = _UNLOCKED; + + /* + * Create an anonyonmous inode for the object that can exported to + * other processes + */ + + lock->file = anon_inode_getfile("genlock", &genlock_fops, + lock, O_RDWR); + + /* Attach the new lock to the handle */ + handle->lock = lock; + kref_init(&lock->refcount); + + return lock; +} +EXPORT_SYMBOL(genlock_create_lock); + +/* + * Get a file descriptor reference to a lock suitable for sharing with + * other processes + */ + +static int genlock_get_fd(struct genlock *lock) +{ + int ret; + + if (!lock->file) { + GENLOCK_LOG_ERR("No file attached to the lock\n"); + return -EINVAL; + } + + ret = get_unused_fd_flags(0); + if (ret < 0) + return ret; + fd_install(ret, lock->file); + return ret; +} + +/** + * genlock_attach_lock - Attach an existing lock to a handle + * @handle - Pointer to a genlock handle to attach the lock to + * @fd - file descriptor for the exported lock + * + * Returns: A pointer to the attached lock structure + */ + +struct genlock *genlock_attach_lock(struct genlock_handle *handle, int fd) +{ + struct file *file; + struct genlock *lock; + + if (IS_ERR_OR_NULL(handle)) { + GENLOCK_LOG_ERR("Invalid handle\n"); + return ERR_PTR(-EINVAL); + } + + if (handle->lock != NULL) { + GENLOCK_LOG_ERR("Handle already has a lock attached\n"); + return ERR_PTR(-EINVAL); + } + + file = fget(fd); + if (file == NULL) { + GENLOCK_LOG_ERR("Bad file descriptor\n"); + return ERR_PTR(-EBADF); + } + + /* + * take a spinlock to avoid a race condition if the lock is + * released and then attached + */ + + spin_lock(&genlock_file_lock); + lock = file->private_data; + spin_unlock(&genlock_file_lock); + + fput(file); + + if (lock == NULL) { + GENLOCK_LOG_ERR("File descriptor is invalid\n"); + return ERR_PTR(-EINVAL); + } + + handle->lock = lock; + kref_get(&lock->refcount); + + return lock; +} +EXPORT_SYMBOL(genlock_attach_lock); + +/* Helper function that returns 1 if the specified handle holds the lock */ + +static int handle_has_lock(struct genlock *lock, struct genlock_handle *handle) +{ + struct genlock_handle *h; + + list_for_each_entry(h, &lock->active, entry) { + if (h == handle) + return 1; + } + + return 0; +} + +/* If the lock just became available, signal the next entity waiting for it */ + +static void _genlock_signal(struct genlock *lock) +{ + if (list_empty(&lock->active)) { + /* If the list is empty, then the lock is free */ + lock->state = _UNLOCKED; + /* Wake up the first process sitting in the queue */ + wake_up(&lock->queue); + } +} + +/* Attempt to release the handle's ownership of the lock */ + +static int _genlock_unlock(struct genlock *lock, struct genlock_handle *handle) +{ + int ret = -EINVAL; + unsigned long irqflags; + + spin_lock_irqsave(&lock->lock, irqflags); + + if (lock->state == _UNLOCKED) { + GENLOCK_LOG_ERR("Trying to unlock an unlocked handle\n"); + goto done; + } + + /* Make sure this handle is an owner of the lock */ + if (!handle_has_lock(lock, handle)) { + GENLOCK_LOG_ERR("handle does not have lock attached to it\n"); + goto done; + } + /* If the handle holds no more references to the lock then + release it (maybe) */ + + if (--handle->active == 0) { + list_del(&handle->entry); + _genlock_signal(lock); + } + + ret = 0; + +done: + spin_unlock_irqrestore(&lock->lock, irqflags); + return ret; +} + +/* Attempt to acquire the lock for the handle */ + +static int _genlock_lock(struct genlock *lock, struct genlock_handle *handle, + int op, int flags, uint32_t timeout) +{ + unsigned long irqflags; + int ret = 0; + unsigned long ticks = msecs_to_jiffies(timeout); + + spin_lock_irqsave(&lock->lock, irqflags); + + /* Sanity check - no blocking locks in a debug context. Even if it + * succeed to not block, the mere idea is too dangerous to continue + */ + + if (in_interrupt() && !(flags & GENLOCK_NOBLOCK)) + BUG(); + + /* Fast path - the lock is unlocked, so go do the needful */ + + if (lock->state == _UNLOCKED) + goto dolock; + + if (handle_has_lock(lock, handle)) { + + /* + * If the handle already holds the lock and the lock type is + * a read lock then just increment the active pointer. This + * allows the handle to do recursive read locks. Recursive + * write locks are not allowed in order to support + * synchronization within a process using a single gralloc + * handle. + */ + + if (lock->state == _RDLOCK && op == _RDLOCK) { + handle->active++; + goto done; + } + + /* + * If the handle holds a write lock then the owner can switch + * to a read lock if they want. Do the transition atomically + * then wake up any pending waiters in case they want a read + * lock too. In order to support synchronization within a + * process the caller must explicity request to convert the + * lock type with the GENLOCK_WRITE_TO_READ flag. + */ + + if (flags & GENLOCK_WRITE_TO_READ) { + if (lock->state == _WRLOCK && op == _RDLOCK) { + lock->state = _RDLOCK; + wake_up(&lock->queue); + goto done; + } else { + GENLOCK_LOG_ERR("Invalid state to convert" + "write to read\n"); + ret = -EINVAL; + goto done; + } + } + } else { + + /* + * Check to ensure the caller has not attempted to convert a + * write to a read without holding the lock. + */ + + if (flags & GENLOCK_WRITE_TO_READ) { + GENLOCK_LOG_ERR("Handle must have lock to convert" + "write to read\n"); + ret = -EINVAL; + goto done; + } + + /* + * If we request a read and the lock is held by a read, then go + * ahead and share the lock + */ + + if (op == GENLOCK_RDLOCK && lock->state == _RDLOCK) + goto dolock; + } + + /* Treat timeout 0 just like a NOBLOCK flag and return if the + lock cannot be aquired without blocking */ + + if (flags & GENLOCK_NOBLOCK || timeout == 0) { + ret = -EAGAIN; + goto done; + } + + /* + * Wait while the lock remains in an incompatible state + * state op wait + * ------------------- + * unlocked n/a no + * read read no + * read write yes + * write n/a yes + */ + + while ((lock->state == _RDLOCK && op == _WRLOCK) || + lock->state == _WRLOCK) { + signed long elapsed; + + spin_unlock_irqrestore(&lock->lock, irqflags); + + elapsed = wait_event_interruptible_timeout(lock->queue, + lock->state == _UNLOCKED || + (lock->state == _RDLOCK && op == _RDLOCK), + ticks); + + spin_lock_irqsave(&lock->lock, irqflags); + + if (elapsed <= 0) { + ret = (elapsed < 0) ? elapsed : -ETIMEDOUT; + goto done; + } + + ticks = (unsigned long) elapsed; + } + +dolock: + /* We can now get the lock, add ourselves to the list of owners */ + + list_add_tail(&handle->entry, &lock->active); + lock->state = op; + handle->active++; + +done: + spin_unlock_irqrestore(&lock->lock, irqflags); + return ret; + +} + +/** + * genlock_lock - Acquire or release a lock (depreciated) + * @handle - pointer to the genlock handle that is requesting the lock + * @op - the operation to perform (RDLOCK, WRLOCK, UNLOCK) + * @flags - flags to control the operation + * @timeout - optional timeout to wait for the lock to come free + * + * Returns: 0 on success or error code on failure + */ + +int genlock_lock(struct genlock_handle *handle, int op, int flags, + uint32_t timeout) +{ + struct genlock *lock; + unsigned long irqflags; + + int ret = 0; + + if (IS_ERR_OR_NULL(handle)) { + GENLOCK_LOG_ERR("Invalid handle\n"); + return -EINVAL; + } + + lock = handle->lock; + + if (lock == NULL) { + GENLOCK_LOG_ERR("Handle does not have a lock attached\n"); + return -EINVAL; + } + + switch (op) { + case GENLOCK_UNLOCK: + ret = _genlock_unlock(lock, handle); + break; + case GENLOCK_RDLOCK: + spin_lock_irqsave(&lock->lock, irqflags); + if (handle_has_lock(lock, handle)) { + /* request the WRITE_TO_READ flag for compatibility */ + flags |= GENLOCK_WRITE_TO_READ; + } + spin_unlock_irqrestore(&lock->lock, irqflags); + /* fall through to take lock */ + case GENLOCK_WRLOCK: + ret = _genlock_lock(lock, handle, op, flags, timeout); + break; + default: + GENLOCK_LOG_ERR("Invalid lock operation\n"); + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(genlock_lock); + +/** + * genlock_dreadlock - Acquire or release a lock + * @handle - pointer to the genlock handle that is requesting the lock + * @op - the operation to perform (RDLOCK, WRLOCK, UNLOCK) + * @flags - flags to control the operation + * @timeout - optional timeout to wait for the lock to come free + * + * Returns: 0 on success or error code on failure + */ + +int genlock_dreadlock(struct genlock_handle *handle, int op, int flags, + uint32_t timeout) +{ + struct genlock *lock; + + int ret = 0; + + if (IS_ERR_OR_NULL(handle)) { + GENLOCK_LOG_ERR("Invalid handle\n"); + return -EINVAL; + } + + lock = handle->lock; + + if (lock == NULL) { + GENLOCK_LOG_ERR("Handle does not have a lock attached\n"); + return -EINVAL; + } + + switch (op) { + case GENLOCK_UNLOCK: + ret = _genlock_unlock(lock, handle); + break; + case GENLOCK_RDLOCK: + case GENLOCK_WRLOCK: + ret = _genlock_lock(lock, handle, op, flags, timeout); + break; + default: + GENLOCK_LOG_ERR("Invalid lock operation\n"); + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(genlock_dreadlock); + +/** + * genlock_wait - Wait for the lock to be released + * @handle - pointer to the genlock handle that is waiting for the lock + * @timeout - optional timeout to wait for the lock to get released + */ + +int genlock_wait(struct genlock_handle *handle, uint32_t timeout) +{ + struct genlock *lock; + unsigned long irqflags; + int ret = 0; + unsigned long ticks = msecs_to_jiffies(timeout); + + if (IS_ERR_OR_NULL(handle)) { + GENLOCK_LOG_ERR("Invalid handle\n"); + return -EINVAL; + } + + lock = handle->lock; + + if (lock == NULL) { + GENLOCK_LOG_ERR("Handle does not have a lock attached\n"); + return -EINVAL; + } + + spin_lock_irqsave(&lock->lock, irqflags); + + /* + * if timeout is 0 and the lock is already unlocked, then success + * otherwise return -EAGAIN + */ + + if (timeout == 0) { + ret = (lock->state == _UNLOCKED) ? 0 : -EAGAIN; + goto done; + } + + while (lock->state != _UNLOCKED) { + signed long elapsed; + + spin_unlock_irqrestore(&lock->lock, irqflags); + + elapsed = wait_event_interruptible_timeout(lock->queue, + lock->state == _UNLOCKED, ticks); + + spin_lock_irqsave(&lock->lock, irqflags); + + if (elapsed <= 0) { + ret = (elapsed < 0) ? elapsed : -ETIMEDOUT; + break; + } + + ticks = (unsigned long) elapsed; + } + +done: + spin_unlock_irqrestore(&lock->lock, irqflags); + return ret; +} + +static void genlock_release_lock(struct genlock_handle *handle) +{ + unsigned long flags; + + if (handle == NULL || handle->lock == NULL) + return; + + spin_lock_irqsave(&handle->lock->lock, flags); + + /* If the handle is holding the lock, then force it closed */ + + if (handle_has_lock(handle->lock, handle)) { + list_del(&handle->entry); + _genlock_signal(handle->lock); + } + spin_unlock_irqrestore(&handle->lock->lock, flags); + + kref_put(&handle->lock->refcount, genlock_destroy); + handle->lock = NULL; + handle->active = 0; +} + +/* + * Release function called when all references to a handle are released + */ + +static int genlock_handle_release(struct inode *inodep, struct file *file) +{ + struct genlock_handle *handle = file->private_data; + + genlock_release_lock(handle); + kfree(handle); + + return 0; +} + +static const struct file_operations genlock_handle_fops = { + .release = genlock_handle_release +}; + +/* + * Allocate a new genlock handle + */ + +static struct genlock_handle *_genlock_get_handle(void) +{ + struct genlock_handle *handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (handle == NULL) { + GENLOCK_LOG_ERR("Unable to allocate memory for the handle\n"); + return ERR_PTR(-ENOMEM); + } + + return handle; +} + +/** + * genlock_get_handle - Create a new genlock handle + * + * Returns: A pointer to a new genlock handle + */ + +struct genlock_handle *genlock_get_handle(void) +{ + struct genlock_handle *handle = _genlock_get_handle(); + if (IS_ERR(handle)) + return handle; + + handle->file = anon_inode_getfile("genlock-handle", + &genlock_handle_fops, handle, O_RDWR); + + return handle; +} +EXPORT_SYMBOL(genlock_get_handle); + +/** + * genlock_put_handle - release a reference to a genlock handle + * @handle - A pointer to the handle to release + */ + +void genlock_put_handle(struct genlock_handle *handle) +{ + if (handle) + fput(handle->file); +} +EXPORT_SYMBOL(genlock_put_handle); + +/** + * genlock_get_handle_fd - Get a handle reference from a file descriptor + * @fd - The file descriptor for a genlock handle + */ + +struct genlock_handle *genlock_get_handle_fd(int fd) +{ + struct file *file = fget(fd); + + if (file == NULL) + return ERR_PTR(-EINVAL); + + return file->private_data; +} +EXPORT_SYMBOL(genlock_get_handle_fd); + +#ifdef CONFIG_GENLOCK_MISCDEVICE + +static long genlock_dev_ioctl(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + struct genlock_lock param; + struct genlock_handle *handle = filep->private_data; + struct genlock *lock; + int ret; + + if (IS_ERR_OR_NULL(handle)) + return -EINVAL; + + switch (cmd) { + case GENLOCK_IOC_NEW: { + lock = genlock_create_lock(handle); + if (IS_ERR(lock)) + return PTR_ERR(lock); + + return 0; + } + case GENLOCK_IOC_EXPORT: { + if (handle->lock == NULL) { + GENLOCK_LOG_ERR("Handle does not have a lock" + "attached\n"); + return -EINVAL; + } + + ret = genlock_get_fd(handle->lock); + if (ret < 0) + return ret; + + param.fd = ret; + + if (copy_to_user((void __user *) arg, ¶m, + sizeof(param))) + return -EFAULT; + + return 0; + } + case GENLOCK_IOC_ATTACH: { + if (copy_from_user(¶m, (void __user *) arg, + sizeof(param))) + return -EFAULT; + + lock = genlock_attach_lock(handle, param.fd); + if (IS_ERR(lock)) + return PTR_ERR(lock); + + return 0; + } + case GENLOCK_IOC_LOCK: { + if (copy_from_user(¶m, (void __user *) arg, + sizeof(param))) + return -EFAULT; + + return genlock_lock(handle, param.op, param.flags, + param.timeout); + } + case GENLOCK_IOC_DREADLOCK: { + if (copy_from_user(¶m, (void __user *) arg, + sizeof(param))) + return -EFAULT; + + return genlock_dreadlock(handle, param.op, param.flags, + param.timeout); + } + case GENLOCK_IOC_WAIT: { + if (copy_from_user(¶m, (void __user *) arg, + sizeof(param))) + return -EFAULT; + + return genlock_wait(handle, param.timeout); + } + case GENLOCK_IOC_RELEASE: { + /* + * Return error - this ioctl has been deprecated. + * Locks should only be released when the handle is + * destroyed + */ + GENLOCK_LOG_ERR("Deprecated RELEASE ioctl called\n"); + return -EINVAL; + } + default: + GENLOCK_LOG_ERR("Invalid ioctl\n"); + return -EINVAL; + } +} + +static int genlock_dev_release(struct inode *inodep, struct file *file) +{ + struct genlock_handle *handle = file->private_data; + + genlock_release_lock(handle); + kfree(handle); + + return 0; +} + +static int genlock_dev_open(struct inode *inodep, struct file *file) +{ + struct genlock_handle *handle = _genlock_get_handle(); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + handle->file = file; + file->private_data = handle; + return 0; +} + +static const struct file_operations genlock_dev_fops = { + .open = genlock_dev_open, + .release = genlock_dev_release, + .unlocked_ioctl = genlock_dev_ioctl, +}; + +static struct miscdevice genlock_dev; + +static int genlock_dev_init(void) +{ + genlock_dev.minor = MISC_DYNAMIC_MINOR; + genlock_dev.name = "genlock"; + genlock_dev.fops = &genlock_dev_fops; + genlock_dev.parent = NULL; + + return misc_register(&genlock_dev); +} + +static void genlock_dev_close(void) +{ + misc_deregister(&genlock_dev); +} + +module_init(genlock_dev_init); +module_exit(genlock_dev_close); + +#endif diff --git a/drivers/base/sys.c b/drivers/base/sys.c new file mode 100644 index 0000000000000000000000000000000000000000..35e09f092bd94dd65651cba845ce81d5a4d95fc5 --- /dev/null +++ b/drivers/base/sys.c @@ -0,0 +1,382 @@ +/* + * sys.c - pseudo-bus for system 'devices' (cpus, PICs, timers, etc) + * + * Copyright (c) 2002-3 Patrick Mochel + * 2002-3 Open Source Development Lab + * + * This file is released under the GPLv2 + * + * This exports a 'system' bus type. + * By default, a 'sys' bus gets added to the root of the system. There will + * always be core system devices. Devices can use sysdev_register() to + * add themselves as children of the system bus. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base.h" + +#define to_sysdev(k) container_of(k, struct sys_device, kobj) +#define to_sysdev_attr(a) container_of(a, struct sysdev_attribute, attr) + +extern struct kset *system_kset; + +static ssize_t +sysdev_show(struct kobject *kobj, struct attribute *attr, char *buffer) +{ + struct sys_device *sysdev = to_sysdev(kobj); + struct sysdev_attribute *sysdev_attr = to_sysdev_attr(attr); + + if (sysdev_attr->show) + return sysdev_attr->show(sysdev, sysdev_attr, buffer); + return -EIO; +} + + +static ssize_t +sysdev_store(struct kobject *kobj, struct attribute *attr, + const char *buffer, size_t count) +{ + struct sys_device *sysdev = to_sysdev(kobj); + struct sysdev_attribute *sysdev_attr = to_sysdev_attr(attr); + + if (sysdev_attr->store) + return sysdev_attr->store(sysdev, sysdev_attr, buffer, count); + return -EIO; +} + +static const struct sysfs_ops sysfs_ops = { + .show = sysdev_show, + .store = sysdev_store, +}; + +static struct kobj_type ktype_sysdev = { + .sysfs_ops = &sysfs_ops, +}; + + +int sysdev_create_file(struct sys_device *s, struct sysdev_attribute *a) +{ + return sysfs_create_file(&s->kobj, &a->attr); +} + + +void sysdev_remove_file(struct sys_device *s, struct sysdev_attribute *a) +{ + sysfs_remove_file(&s->kobj, &a->attr); +} + +EXPORT_SYMBOL_GPL(sysdev_create_file); +EXPORT_SYMBOL_GPL(sysdev_remove_file); + +#define to_sysdev_class(k) container_of(k, struct sysdev_class, kset.kobj) +#define to_sysdev_class_attr(a) container_of(a, \ + struct sysdev_class_attribute, attr) + +static ssize_t sysdev_class_show(struct kobject *kobj, struct attribute *attr, + char *buffer) +{ + struct sysdev_class *class = to_sysdev_class(kobj); + struct sysdev_class_attribute *class_attr = to_sysdev_class_attr(attr); + + if (class_attr->show) + return class_attr->show(class, class_attr, buffer); + return -EIO; +} + +static ssize_t sysdev_class_store(struct kobject *kobj, struct attribute *attr, + const char *buffer, size_t count) +{ + struct sysdev_class *class = to_sysdev_class(kobj); + struct sysdev_class_attribute *class_attr = to_sysdev_class_attr(attr); + + if (class_attr->store) + return class_attr->store(class, class_attr, buffer, count); + return -EIO; +} + +static const struct sysfs_ops sysfs_class_ops = { + .show = sysdev_class_show, + .store = sysdev_class_store, +}; + +static struct kobj_type ktype_sysdev_class = { + .sysfs_ops = &sysfs_class_ops, +}; + +int sysdev_class_create_file(struct sysdev_class *c, + struct sysdev_class_attribute *a) +{ + return sysfs_create_file(&c->kset.kobj, &a->attr); +} +EXPORT_SYMBOL_GPL(sysdev_class_create_file); + +void sysdev_class_remove_file(struct sysdev_class *c, + struct sysdev_class_attribute *a) +{ + sysfs_remove_file(&c->kset.kobj, &a->attr); +} +EXPORT_SYMBOL_GPL(sysdev_class_remove_file); + +int sysdev_class_register(struct sysdev_class *cls) +{ + int retval; + + pr_debug("Registering sysdev class '%s'\n", cls->name); + + INIT_LIST_HEAD(&cls->drivers); + memset(&cls->kset.kobj, 0x00, sizeof(struct kobject)); + cls->kset.kobj.parent = &system_kset->kobj; + cls->kset.kobj.ktype = &ktype_sysdev_class; + cls->kset.kobj.kset = system_kset; + + retval = kobject_set_name(&cls->kset.kobj, "%s", cls->name); + if (retval) + return retval; + + retval = kset_register(&cls->kset); + if (!retval && cls->attrs) + retval = sysfs_create_files(&cls->kset.kobj, + (const struct attribute **)cls->attrs); + return retval; +} + +void sysdev_class_unregister(struct sysdev_class *cls) +{ + pr_debug("Unregistering sysdev class '%s'\n", + kobject_name(&cls->kset.kobj)); + if (cls->attrs) + sysfs_remove_files(&cls->kset.kobj, + (const struct attribute **)cls->attrs); + kset_unregister(&cls->kset); +} + +EXPORT_SYMBOL_GPL(sysdev_class_register); +EXPORT_SYMBOL_GPL(sysdev_class_unregister); + +static DEFINE_MUTEX(sysdev_drivers_lock); + +/* + * @dev != NULL means that we're unwinding because some drv->add() + * failed for some reason. You need to grab sysdev_drivers_lock before + * calling this. + */ +static void __sysdev_driver_remove(struct sysdev_class *cls, + struct sysdev_driver *drv, + struct sys_device *from_dev) +{ + struct sys_device *dev = from_dev; + + list_del_init(&drv->entry); + if (!cls) + return; + + if (!drv->remove) + goto kset_put; + + if (dev) + list_for_each_entry_continue_reverse(dev, &cls->kset.list, + kobj.entry) + drv->remove(dev); + else + list_for_each_entry(dev, &cls->kset.list, kobj.entry) + drv->remove(dev); + +kset_put: + kset_put(&cls->kset); +} + +/** + * sysdev_driver_register - Register auxiliary driver + * @cls: Device class driver belongs to. + * @drv: Driver. + * + * @drv is inserted into @cls->drivers to be + * called on each operation on devices of that class. The refcount + * of @cls is incremented. + */ +int sysdev_driver_register(struct sysdev_class *cls, struct sysdev_driver *drv) +{ + struct sys_device *dev = NULL; + int err = 0; + + if (!cls) { + WARN(1, KERN_WARNING "sysdev: invalid class passed to %s!\n", + __func__); + return -EINVAL; + } + + /* Check whether this driver has already been added to a class. */ + if (drv->entry.next && !list_empty(&drv->entry)) + WARN(1, KERN_WARNING "sysdev: class %s: driver (%p) has already" + " been registered to a class, something is wrong, but " + "will forge on!\n", cls->name, drv); + + mutex_lock(&sysdev_drivers_lock); + if (cls && kset_get(&cls->kset)) { + list_add_tail(&drv->entry, &cls->drivers); + + /* If devices of this class already exist, tell the driver */ + if (drv->add) { + list_for_each_entry(dev, &cls->kset.list, kobj.entry) { + err = drv->add(dev); + if (err) + goto unwind; + } + } + } else { + err = -EINVAL; + WARN(1, KERN_ERR "%s: invalid device class\n", __func__); + } + + goto unlock; + +unwind: + __sysdev_driver_remove(cls, drv, dev); + +unlock: + mutex_unlock(&sysdev_drivers_lock); + return err; +} + +/** + * sysdev_driver_unregister - Remove an auxiliary driver. + * @cls: Class driver belongs to. + * @drv: Driver. + */ +void sysdev_driver_unregister(struct sysdev_class *cls, + struct sysdev_driver *drv) +{ + mutex_lock(&sysdev_drivers_lock); + __sysdev_driver_remove(cls, drv, NULL); + mutex_unlock(&sysdev_drivers_lock); +} +EXPORT_SYMBOL_GPL(sysdev_driver_register); +EXPORT_SYMBOL_GPL(sysdev_driver_unregister); + +/** + * sysdev_register - add a system device to the tree + * @sysdev: device in question + * + */ +int sysdev_register(struct sys_device *sysdev) +{ + int error; + struct sysdev_class *cls = sysdev->cls; + + if (!cls) + return -EINVAL; + + pr_debug("Registering sys device of class '%s'\n", + kobject_name(&cls->kset.kobj)); + + /* initialize the kobject to 0, in case it had previously been used */ + memset(&sysdev->kobj, 0x00, sizeof(struct kobject)); + + /* Make sure the kset is set */ + sysdev->kobj.kset = &cls->kset; + + /* Register the object */ + error = kobject_init_and_add(&sysdev->kobj, &ktype_sysdev, NULL, + "%s%d", kobject_name(&cls->kset.kobj), + sysdev->id); + + if (!error) { + struct sysdev_driver *drv; + + pr_debug("Registering sys device '%s'\n", + kobject_name(&sysdev->kobj)); + + mutex_lock(&sysdev_drivers_lock); + /* Generic notification is implicit, because it's that + * code that should have called us. + */ + + /* Notify class auxiliary drivers */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->add) + drv->add(sysdev); + } + mutex_unlock(&sysdev_drivers_lock); + kobject_uevent(&sysdev->kobj, KOBJ_ADD); + } + + return error; +} + +void sysdev_unregister(struct sys_device *sysdev) +{ + struct sysdev_driver *drv; + + mutex_lock(&sysdev_drivers_lock); + list_for_each_entry(drv, &sysdev->cls->drivers, entry) { + if (drv->remove) + drv->remove(sysdev); + } + mutex_unlock(&sysdev_drivers_lock); + + kobject_put(&sysdev->kobj); +} + +EXPORT_SYMBOL_GPL(sysdev_register); +EXPORT_SYMBOL_GPL(sysdev_unregister); + +#define to_ext_attr(x) container_of(x, struct sysdev_ext_attribute, attr) + +ssize_t sysdev_store_ulong(struct sys_device *sysdev, + struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + char *end; + unsigned long new = simple_strtoul(buf, &end, 0); + if (end == buf) + return -EINVAL; + *(unsigned long *)(ea->var) = new; + /* Always return full write size even if we didn't consume all */ + return size; +} +EXPORT_SYMBOL_GPL(sysdev_store_ulong); + +ssize_t sysdev_show_ulong(struct sys_device *sysdev, + struct sysdev_attribute *attr, + char *buf) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + return snprintf(buf, PAGE_SIZE, "%lx\n", *(unsigned long *)(ea->var)); +} +EXPORT_SYMBOL_GPL(sysdev_show_ulong); + +ssize_t sysdev_store_int(struct sys_device *sysdev, + struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + char *end; + long new = simple_strtol(buf, &end, 0); + if (end == buf || new > INT_MAX || new < INT_MIN) + return -EINVAL; + *(int *)(ea->var) = new; + /* Always return full write size even if we didn't consume all */ + return size; +} +EXPORT_SYMBOL_GPL(sysdev_store_int); + +ssize_t sysdev_show_int(struct sys_device *sysdev, + struct sysdev_attribute *attr, + char *buf) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + return snprintf(buf, PAGE_SIZE, "%d\n", *(int *)(ea->var)); +} +EXPORT_SYMBOL_GPL(sysdev_show_int); + diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 5ccf142ef0b8139f47a95b75b13bdb5086b034a7..ea1c27a36ca8ba0e13ad51e3cdb7ce2b891aa422 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 @@ -188,7 +217,7 @@ config BT_MRVL The core driver to support Marvell Bluetooth devices. This driver is required if you want to support - Marvell Bluetooth devices, such as 8688/8787/8797. + Marvell Bluetooth devices, such as 8688/8787. Say Y here to compile Marvell Bluetooth driver into the kernel or say M to compile it as module. @@ -201,12 +230,20 @@ config BT_MRVL_SDIO The driver for Marvell Bluetooth chipsets with SDIO interface. This driver is required if you want to use Marvell Bluetooth - devices with SDIO interface. Currently SD8688/SD8787/SD8797 - chipsets are supported. + devices with SDIO interface. Currently SD8688/SD8787 chipsets are + supported. 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 f4460f4f4b788c1144f962227b5fd711490eecdd..a20a056b77bd1a8c423bc0873ea688fac8c1865c 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 57fd867553d7ab1dc5fcf1737e3c19e8d8181257..55bc2facb2583c394f3d4b08856e4bd660747557 100644 --- a/drivers/bluetooth/ath3k.c +++ b/drivers/bluetooth/ath3k.c @@ -30,7 +30,6 @@ #include #define VERSION "1.0" -#define ATH3K_FIRMWARE "ath3k-1.fw" #define ATH3K_DNLOAD 0x01 #define ATH3K_GETSTATE 0x05 @@ -63,20 +62,12 @@ static struct usb_device_id ath3k_table[] = { /* Atheros AR3011 with sflash firmware*/ { USB_DEVICE(0x0CF3, 0x3002) }, - { USB_DEVICE(0x13d3, 0x3304) }, - { USB_DEVICE(0x0930, 0x0215) }, - { USB_DEVICE(0x0489, 0xE03D) }, /* Atheros AR9285 Malbec with sflash firmware */ { USB_DEVICE(0x03F0, 0x311D) }, /* Atheros AR3012 with sflash firmware*/ { USB_DEVICE(0x0CF3, 0x3004) }, - { USB_DEVICE(0x0CF3, 0x311D) }, - { USB_DEVICE(0x13d3, 0x3375) }, - { USB_DEVICE(0x04CA, 0x3005) }, - { USB_DEVICE(0x13d3, 0x3362) }, - { USB_DEVICE(0x0CF3, 0xE004) }, /* Atheros AR5BBU12 with sflash firmware */ { USB_DEVICE(0x0489, 0xE02C) }, @@ -93,11 +84,6 @@ static struct usb_device_id ath3k_blist_tbl[] = { /* Atheros AR3012 with sflash firmware*/ { USB_DEVICE(0x0cf3, 0x3004), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x0cf3, 0x311D), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x13d3, 0x3375), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x04ca, 0x3005), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x13d3, 0x3362), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x0cf3, 0xe004), .driver_info = BTUSB_ATH3012 }, { } /* Terminating entry */ }; @@ -117,7 +103,7 @@ static int ath3k_load_firmware(struct usb_device *udev, pipe = usb_sndctrlpipe(udev, 0); - send_buf = kmalloc(BULK_SIZE, GFP_KERNEL); + send_buf = kmalloc(BULK_SIZE, GFP_ATOMIC); if (!send_buf) { BT_ERR("Can't allocate memory chunk for firmware"); return -ENOMEM; @@ -188,7 +174,7 @@ static int ath3k_load_fwfile(struct usb_device *udev, count = firmware->size; - send_buf = kmalloc(BULK_SIZE, GFP_KERNEL); + send_buf = kmalloc(BULK_SIZE, GFP_ATOMIC); if (!send_buf) { BT_ERR("Can't allocate memory chunk for firmware"); return -ENOMEM; @@ -412,15 +398,9 @@ static int ath3k_probe(struct usb_interface *intf, return 0; } - ret = request_firmware(&firmware, ATH3K_FIRMWARE, &udev->dev); - if (ret < 0) { - if (ret == -ENOENT) - BT_ERR("Firmware file \"%s\" not found", - ATH3K_FIRMWARE); - else - BT_ERR("Firmware file \"%s\" request failed (err=%d)", - ATH3K_FIRMWARE, ret); - return ret; + if (request_firmware(&firmware, "ath3k-1.fw", &udev->dev) < 0) { + BT_ERR("Error loading firmware"); + return -EIO; } ret = ath3k_load_firmware(udev, firmware); @@ -441,10 +421,22 @@ static struct usb_driver ath3k_driver = { .id_table = ath3k_table, }; -module_usb_driver(ath3k_driver); +static int __init ath3k_init(void) +{ + BT_INFO("Atheros AR30xx firmware driver ver %s", VERSION); + return usb_register(&ath3k_driver); +} + +static void __exit ath3k_exit(void) +{ + usb_deregister(&ath3k_driver); +} + +module_init(ath3k_init); +module_exit(ath3k_exit); MODULE_AUTHOR("Atheros Communications"); MODULE_DESCRIPTION("Atheros AR30xx firmware driver"); MODULE_VERSION(VERSION); MODULE_LICENSE("GPL"); -MODULE_FIRMWARE(ATH3K_FIRMWARE); +MODULE_FIRMWARE("ath3k-1.fw"); diff --git a/drivers/bluetooth/bcm203x.c b/drivers/bluetooth/bcm203x.c index 1e742a50e2cda0e6e6a618068441db9bc808f5ff..8b1b643a519b330a8119ad5bde789ec105edfdfc 100644 --- a/drivers/bluetooth/bcm203x.c +++ b/drivers/bluetooth/bcm203x.c @@ -24,7 +24,6 @@ #include -#include #include #include #include @@ -66,7 +65,6 @@ struct bcm203x_data { unsigned long state; struct work_struct work; - atomic_t shutdown; struct urb *urb; unsigned char *buffer; @@ -99,7 +97,6 @@ static void bcm203x_complete(struct urb *urb) data->state = BCM203X_SELECT_MEMORY; - /* use workqueue to have a small delay */ schedule_work(&data->work); break; @@ -158,10 +155,7 @@ static void bcm203x_work(struct work_struct *work) struct bcm203x_data *data = container_of(work, struct bcm203x_data, work); - if (atomic_read(&data->shutdown)) - return; - - if (usb_submit_urb(data->urb, GFP_KERNEL) < 0) + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) BT_ERR("Can't submit URB"); } @@ -249,7 +243,6 @@ static int bcm203x_probe(struct usb_interface *intf, const struct usb_device_id usb_set_intfdata(intf, data); - /* use workqueue to have a small delay */ schedule_work(&data->work); return 0; @@ -261,9 +254,6 @@ static void bcm203x_disconnect(struct usb_interface *intf) BT_DBG("intf %p", intf); - atomic_inc(&data->shutdown); - cancel_work_sync(&data->work); - usb_kill_urb(data->urb); usb_set_intfdata(intf, NULL); @@ -281,7 +271,26 @@ static struct usb_driver bcm203x_driver = { .id_table = bcm203x_table, }; -module_usb_driver(bcm203x_driver); +static int __init bcm203x_init(void) +{ + int err; + + BT_INFO("Broadcom Blutonium firmware driver ver %s", VERSION); + + err = usb_register(&bcm203x_driver); + if (err < 0) + BT_ERR("Failed to register USB driver"); + + return err; +} + +static void __exit bcm203x_exit(void) +{ + usb_deregister(&bcm203x_driver); +} + +module_init(bcm203x_init); +module_exit(bcm203x_exit); MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Broadcom Blutonium firmware driver ver " VERSION); diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c index b8ac1c549a1c75507700c8f608f844233314ea7e..005919ab043c16bb4e699b4f58ee5a4fdbd94c6e 100644 --- a/drivers/bluetooth/bfusb.c +++ b/drivers/bluetooth/bfusb.c @@ -411,7 +411,7 @@ static void bfusb_rx_complete(struct urb *urb) static int bfusb_open(struct hci_dev *hdev) { - struct bfusb_data *data = hci_get_drvdata(hdev); + struct bfusb_data *data = hdev->driver_data; unsigned long flags; int i, err; @@ -437,7 +437,7 @@ static int bfusb_open(struct hci_dev *hdev) static int bfusb_flush(struct hci_dev *hdev) { - struct bfusb_data *data = hci_get_drvdata(hdev); + struct bfusb_data *data = hdev->driver_data; BT_DBG("hdev %p bfusb %p", hdev, data); @@ -448,7 +448,7 @@ static int bfusb_flush(struct hci_dev *hdev) static int bfusb_close(struct hci_dev *hdev) { - struct bfusb_data *data = hci_get_drvdata(hdev); + struct bfusb_data *data = hdev->driver_data; unsigned long flags; BT_DBG("hdev %p bfusb %p", hdev, data); @@ -483,7 +483,7 @@ static int bfusb_send_frame(struct sk_buff *skb) if (!test_bit(HCI_RUNNING, &hdev->flags)) return -EBUSY; - data = hci_get_drvdata(hdev); + data = hdev->driver_data; switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: @@ -544,6 +544,15 @@ static int bfusb_send_frame(struct sk_buff *skb) return 0; } +static void bfusb_destruct(struct hci_dev *hdev) +{ + struct bfusb_data *data = hdev->driver_data; + + BT_DBG("hdev %p bfusb %p", hdev, data); + + kfree(data); +} + static int bfusb_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) { return -ENOIOCTLCMD; @@ -559,23 +568,22 @@ static int bfusb_load_firmware(struct bfusb_data *data, BT_INFO("BlueFRITZ! USB loading firmware"); - buf = kmalloc(BFUSB_MAX_BLOCK_SIZE + 3, GFP_KERNEL); - if (!buf) { - BT_ERR("Can't allocate memory chunk for firmware"); - return -ENOMEM; - } - pipe = usb_sndctrlpipe(data->udev, 0); if (usb_control_msg(data->udev, pipe, USB_REQ_SET_CONFIGURATION, 0, 1, 0, NULL, 0, USB_CTRL_SET_TIMEOUT) < 0) { BT_ERR("Can't change to loading configuration"); - kfree(buf); return -EBUSY; } data->udev->toggle[0] = data->udev->toggle[1] = 0; + buf = kmalloc(BFUSB_MAX_BLOCK_SIZE + 3, GFP_ATOMIC); + if (!buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + pipe = usb_sndbulkpipe(data->udev, data->bulk_out_ep); while (count) { @@ -696,15 +704,18 @@ static int bfusb_probe(struct usb_interface *intf, const struct usb_device_id *i data->hdev = hdev; hdev->bus = HCI_USB; - hci_set_drvdata(hdev, data); + hdev->driver_data = data; SET_HCIDEV_DEV(hdev, &intf->dev); hdev->open = bfusb_open; hdev->close = bfusb_close; hdev->flush = bfusb_flush; hdev->send = bfusb_send_frame; + hdev->destruct = bfusb_destruct; hdev->ioctl = bfusb_ioctl; + hdev->owner = THIS_MODULE; + if (hci_register_dev(hdev) < 0) { BT_ERR("Can't register HCI device"); hci_free_dev(hdev); @@ -739,9 +750,10 @@ static void bfusb_disconnect(struct usb_interface *intf) bfusb_close(hdev); - hci_unregister_dev(hdev); + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + hci_free_dev(hdev); - kfree(data); } static struct usb_driver bfusb_driver = { @@ -751,7 +763,26 @@ static struct usb_driver bfusb_driver = { .id_table = bfusb_table, }; -module_usb_driver(bfusb_driver); +static int __init bfusb_init(void) +{ + int err; + + BT_INFO("BlueFRITZ! USB driver ver %s", VERSION); + + err = usb_register(&bfusb_driver); + if (err < 0) + BT_ERR("Failed to register BlueFRITZ! USB driver"); + + return err; +} + +static void __exit bfusb_exit(void) +{ + usb_deregister(&bfusb_driver); +} + +module_init(bfusb_init); +module_exit(bfusb_exit); MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("BlueFRITZ! USB driver ver " VERSION); diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c index 1fcd92380356bb70571a573cb105778d06712237..4104b7feae6741c585af6d87db7c1333478e6891 100644 --- a/drivers/bluetooth/bluecard_cs.c +++ b/drivers/bluetooth/bluecard_cs.c @@ -561,7 +561,7 @@ static irqreturn_t bluecard_interrupt(int irq, void *dev_inst) static int bluecard_hci_set_baud_rate(struct hci_dev *hdev, int baud) { - bluecard_info_t *info = hci_get_drvdata(hdev); + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); struct sk_buff *skb; /* Ericsson baud rate command */ @@ -609,7 +609,7 @@ static int bluecard_hci_set_baud_rate(struct hci_dev *hdev, int baud) static int bluecard_hci_flush(struct hci_dev *hdev) { - bluecard_info_t *info = hci_get_drvdata(hdev); + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); /* Drop TX queue */ skb_queue_purge(&(info->txq)); @@ -620,7 +620,7 @@ static int bluecard_hci_flush(struct hci_dev *hdev) static int bluecard_hci_open(struct hci_dev *hdev) { - bluecard_info_t *info = hci_get_drvdata(hdev); + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); unsigned int iobase = info->p_dev->resource[0]->start; if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) @@ -640,7 +640,7 @@ static int bluecard_hci_open(struct hci_dev *hdev) static int bluecard_hci_close(struct hci_dev *hdev) { - bluecard_info_t *info = hci_get_drvdata(hdev); + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); unsigned int iobase = info->p_dev->resource[0]->start; if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) @@ -667,7 +667,7 @@ static int bluecard_hci_send_frame(struct sk_buff *skb) return -ENODEV; } - info = hci_get_drvdata(hdev); + info = (bluecard_info_t *)(hdev->driver_data); switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: @@ -691,6 +691,11 @@ static int bluecard_hci_send_frame(struct sk_buff *skb) } +static void bluecard_hci_destruct(struct hci_dev *hdev) +{ +} + + static int bluecard_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) { return -ENOIOCTLCMD; @@ -729,15 +734,18 @@ static int bluecard_open(bluecard_info_t *info) info->hdev = hdev; hdev->bus = HCI_PCCARD; - hci_set_drvdata(hdev, info); + hdev->driver_data = info; SET_HCIDEV_DEV(hdev, &info->p_dev->dev); hdev->open = bluecard_hci_open; hdev->close = bluecard_hci_close; hdev->flush = bluecard_hci_flush; hdev->send = bluecard_hci_send_frame; + hdev->destruct = bluecard_hci_destruct; hdev->ioctl = bluecard_hci_ioctl; + hdev->owner = THIS_MODULE; + id = inb(iobase + 0x30); if ((id & 0x0f) == 0x02) @@ -836,7 +844,9 @@ static int bluecard_close(bluecard_info_t *info) /* Turn FPGA off */ outb(0x80, iobase + 0x30); - hci_unregister_dev(hdev); + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + hci_free_dev(hdev); return 0; @@ -920,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 0000000000000000000000000000000000000000..0d111411256036ffebe9b0f38715661276ef82d3 --- /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 0000000000000000000000000000000000000000..3bf49d1b4512c59be9cc76c6151c580a5875fb1f --- /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/bpa10x.c b/drivers/bluetooth/bpa10x.c index d894340a7601ee789eff108b1dddd29e173b157f..751b338d904a7530dba3ba901cf81c75e0a9d198 100644 --- a/drivers/bluetooth/bpa10x.c +++ b/drivers/bluetooth/bpa10x.c @@ -66,7 +66,7 @@ struct hci_vendor_hdr { static int bpa10x_recv(struct hci_dev *hdev, int queue, void *buf, int count) { - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; BT_DBG("%s queue %d buffer %p count %d", hdev->name, queue, buf, count); @@ -189,7 +189,7 @@ static void bpa10x_tx_complete(struct urb *urb) static void bpa10x_rx_complete(struct urb *urb) { struct hci_dev *hdev = urb->context; - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; int err; BT_DBG("%s urb %p status %d count %d", hdev->name, @@ -219,7 +219,7 @@ static void bpa10x_rx_complete(struct urb *urb) static inline int bpa10x_submit_intr_urb(struct hci_dev *hdev) { - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; struct urb *urb; unsigned char *buf; unsigned int pipe; @@ -260,7 +260,7 @@ static inline int bpa10x_submit_intr_urb(struct hci_dev *hdev) static inline int bpa10x_submit_bulk_urb(struct hci_dev *hdev) { - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; struct urb *urb; unsigned char *buf; unsigned int pipe; @@ -301,7 +301,7 @@ static inline int bpa10x_submit_bulk_urb(struct hci_dev *hdev) static int bpa10x_open(struct hci_dev *hdev) { - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; int err; BT_DBG("%s", hdev->name); @@ -329,7 +329,7 @@ static int bpa10x_open(struct hci_dev *hdev) static int bpa10x_close(struct hci_dev *hdev) { - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; BT_DBG("%s", hdev->name); @@ -343,7 +343,7 @@ static int bpa10x_close(struct hci_dev *hdev) static int bpa10x_flush(struct hci_dev *hdev) { - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; BT_DBG("%s", hdev->name); @@ -355,7 +355,7 @@ static int bpa10x_flush(struct hci_dev *hdev) static int bpa10x_send_frame(struct sk_buff *skb) { struct hci_dev *hdev = (struct hci_dev *) skb->dev; - struct bpa10x_data *data = hci_get_drvdata(hdev); + struct bpa10x_data *data = hdev->driver_data; struct usb_ctrlrequest *dr; struct urb *urb; unsigned int pipe; @@ -432,6 +432,17 @@ static int bpa10x_send_frame(struct sk_buff *skb) return 0; } +static void bpa10x_destruct(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree_skb(data->rx_skb[0]); + kfree_skb(data->rx_skb[1]); + kfree(data); +} + static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct bpa10x_data *data; @@ -459,7 +470,7 @@ static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id * } hdev->bus = HCI_USB; - hci_set_drvdata(hdev, data); + hdev->driver_data = data; data->hdev = hdev; @@ -469,6 +480,9 @@ static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id * hdev->close = bpa10x_close; hdev->flush = bpa10x_flush; hdev->send = bpa10x_send_frame; + hdev->destruct = bpa10x_destruct; + + hdev->owner = THIS_MODULE; set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); @@ -498,9 +512,6 @@ static void bpa10x_disconnect(struct usb_interface *intf) hci_unregister_dev(data->hdev); hci_free_dev(data->hdev); - kfree_skb(data->rx_skb[0]); - kfree_skb(data->rx_skb[1]); - kfree(data); } static struct usb_driver bpa10x_driver = { @@ -510,7 +521,20 @@ static struct usb_driver bpa10x_driver = { .id_table = bpa10x_table, }; -module_usb_driver(bpa10x_driver); +static int __init bpa10x_init(void) +{ + BT_INFO("Digianswer Bluetooth USB driver ver %s", VERSION); + + return usb_register(&bpa10x_driver); +} + +static void __exit bpa10x_exit(void) +{ + usb_deregister(&bpa10x_driver); +} + +module_init(bpa10x_init); +module_exit(bpa10x_exit); MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Digianswer Bluetooth USB driver ver " VERSION); diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c index 308c8599ab55ca480c364cab3b40bc6f41d94faf..0c8a655874914d0604784d09c32c3b1651de03f5 100644 --- a/drivers/bluetooth/bt3c_cs.c +++ b/drivers/bluetooth/bt3c_cs.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -388,7 +389,7 @@ static irqreturn_t bt3c_interrupt(int irq, void *dev_inst) static int bt3c_hci_flush(struct hci_dev *hdev) { - bt3c_info_t *info = hci_get_drvdata(hdev); + bt3c_info_t *info = (bt3c_info_t *)(hdev->driver_data); /* Drop TX queue */ skb_queue_purge(&(info->txq)); @@ -427,7 +428,7 @@ static int bt3c_hci_send_frame(struct sk_buff *skb) return -ENODEV; } - info = hci_get_drvdata(hdev); + info = (bt3c_info_t *) (hdev->driver_data); switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: @@ -455,6 +456,11 @@ static int bt3c_hci_send_frame(struct sk_buff *skb) } +static void bt3c_hci_destruct(struct hci_dev *hdev) +{ +} + + static int bt3c_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) { return -ENOIOCTLCMD; @@ -574,15 +580,18 @@ static int bt3c_open(bt3c_info_t *info) info->hdev = hdev; hdev->bus = HCI_PCCARD; - hci_set_drvdata(hdev, info); + hdev->driver_data = info; SET_HCIDEV_DEV(hdev, &info->p_dev->dev); hdev->open = bt3c_hci_open; hdev->close = bt3c_hci_close; hdev->flush = bt3c_hci_flush; hdev->send = bt3c_hci_send_frame; + hdev->destruct = bt3c_hci_destruct; hdev->ioctl = bt3c_hci_ioctl; + hdev->owner = THIS_MODULE; + /* Load firmware */ err = request_firmware(&firmware, "BT3CPCC.bin", &info->p_dev->dev); if (err < 0) { @@ -627,7 +636,9 @@ static int bt3c_close(bt3c_info_t *info) bt3c_hci_close(hdev); - hci_unregister_dev(hdev); + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + hci_free_dev(hdev); return 0; @@ -750,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 428dbb7574bd75aa43d84ba5af353a134d1abfd3..fd6305bf953e4084c9b7557a26acb002deb101fe 100644 --- a/drivers/bluetooth/btmrvl_debugfs.c +++ b/drivers/bluetooth/btmrvl_debugfs.c @@ -45,6 +45,12 @@ struct btmrvl_debugfs_data { struct dentry *txdnldready; }; +static int btmrvl_open_generic(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + static ssize_t btmrvl_hscfgcmd_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { @@ -58,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; @@ -87,7 +91,7 @@ static ssize_t btmrvl_hscfgcmd_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_hscfgcmd_fops = { .read = btmrvl_hscfgcmd_read, .write = btmrvl_hscfgcmd_write, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -104,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; @@ -128,7 +130,7 @@ static ssize_t btmrvl_psmode_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_psmode_fops = { .read = btmrvl_psmode_read, .write = btmrvl_psmode_write, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -145,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; @@ -174,7 +174,7 @@ static ssize_t btmrvl_pscmd_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_pscmd_fops = { .read = btmrvl_pscmd_read, .write = btmrvl_pscmd_write, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -191,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; @@ -215,7 +213,7 @@ static ssize_t btmrvl_gpiogap_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_gpiogap_fops = { .read = btmrvl_gpiogap_read, .write = btmrvl_gpiogap_write, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -232,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) { @@ -259,7 +255,7 @@ static ssize_t btmrvl_hscmd_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_hscmd_fops = { .read = btmrvl_hscmd_read, .write = btmrvl_hscmd_write, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -276,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; @@ -299,7 +293,7 @@ static ssize_t btmrvl_hsmode_read(struct file *file, char __user * userbuf, static const struct file_operations btmrvl_hsmode_fops = { .read = btmrvl_hsmode_read, .write = btmrvl_hsmode_write, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -317,7 +311,7 @@ static ssize_t btmrvl_curpsmode_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_curpsmode_fops = { .read = btmrvl_curpsmode_read, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -335,7 +329,7 @@ static ssize_t btmrvl_psstate_read(struct file *file, char __user * userbuf, static const struct file_operations btmrvl_psstate_fops = { .read = btmrvl_psstate_read, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -353,7 +347,7 @@ static ssize_t btmrvl_hsstate_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_hsstate_fops = { .read = btmrvl_hsstate_read, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; @@ -372,13 +366,13 @@ static ssize_t btmrvl_txdnldready_read(struct file *file, char __user *userbuf, static const struct file_operations btmrvl_txdnldready_fops = { .read = btmrvl_txdnldready_read, - .open = simple_open, + .open = btmrvl_open_generic, .llseek = default_llseek, }; void btmrvl_debugfs_init(struct hci_dev *hdev) { - struct btmrvl_private *priv = hci_get_drvdata(hdev); + struct btmrvl_private *priv = hdev->driver_data; struct btmrvl_debugfs_data *dbg; if (!hdev->debugfs) @@ -395,34 +389,36 @@ void btmrvl_debugfs_init(struct hci_dev *hdev) dbg->config_dir = debugfs_create_dir("config", hdev->debugfs); dbg->psmode = debugfs_create_file("psmode", 0644, dbg->config_dir, - priv, &btmrvl_psmode_fops); + hdev->driver_data, &btmrvl_psmode_fops); dbg->pscmd = debugfs_create_file("pscmd", 0644, dbg->config_dir, - priv, &btmrvl_pscmd_fops); + hdev->driver_data, &btmrvl_pscmd_fops); dbg->gpiogap = debugfs_create_file("gpiogap", 0644, dbg->config_dir, - priv, &btmrvl_gpiogap_fops); + hdev->driver_data, &btmrvl_gpiogap_fops); dbg->hsmode = debugfs_create_file("hsmode", 0644, dbg->config_dir, - priv, &btmrvl_hsmode_fops); + hdev->driver_data, &btmrvl_hsmode_fops); dbg->hscmd = debugfs_create_file("hscmd", 0644, dbg->config_dir, - priv, &btmrvl_hscmd_fops); + hdev->driver_data, &btmrvl_hscmd_fops); dbg->hscfgcmd = debugfs_create_file("hscfgcmd", 0644, dbg->config_dir, - priv, &btmrvl_hscfgcmd_fops); + hdev->driver_data, &btmrvl_hscfgcmd_fops); dbg->status_dir = debugfs_create_dir("status", hdev->debugfs); dbg->curpsmode = debugfs_create_file("curpsmode", 0444, - dbg->status_dir, priv, - &btmrvl_curpsmode_fops); + dbg->status_dir, + hdev->driver_data, + &btmrvl_curpsmode_fops); dbg->psstate = debugfs_create_file("psstate", 0444, dbg->status_dir, - priv, &btmrvl_psstate_fops); + hdev->driver_data, &btmrvl_psstate_fops); dbg->hsstate = debugfs_create_file("hsstate", 0444, dbg->status_dir, - priv, &btmrvl_hsstate_fops); + hdev->driver_data, &btmrvl_hsstate_fops); dbg->txdnldready = debugfs_create_file("txdnldready", 0444, - dbg->status_dir, priv, - &btmrvl_txdnldready_fops); + dbg->status_dir, + hdev->driver_data, + &btmrvl_txdnldready_fops); } void btmrvl_debugfs_remove(struct hci_dev *hdev) { - struct btmrvl_private *priv = hci_get_drvdata(hdev); + struct btmrvl_private *priv = hdev->driver_data; struct btmrvl_debugfs_data *dbg = priv->debugfs_data; if (!dbg) diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c index d1209adc882dd00ad39811f44a4fa2c076274ee9..548d1d9e4ddad7328c2ac9d68ec7d17de949d15a 100644 --- a/drivers/bluetooth/btmrvl_main.c +++ b/drivers/bluetooth/btmrvl_main.c @@ -18,8 +18,6 @@ * this warranty disclaimer. **/ -#include - #include #include @@ -387,6 +385,10 @@ static int btmrvl_ioctl(struct hci_dev *hdev, return -ENOIOCTLCMD; } +static void btmrvl_destruct(struct hci_dev *hdev) +{ +} + static int btmrvl_send_frame(struct sk_buff *skb) { struct hci_dev *hdev = (struct hci_dev *) skb->dev; @@ -394,13 +396,12 @@ static int btmrvl_send_frame(struct sk_buff *skb) BT_DBG("type=%d, len=%d", skb->pkt_type, skb->len); - if (!hdev) { + if (!hdev || !hdev->driver_data) { BT_ERR("Frame for unknown HCI device"); return -ENODEV; } - priv = hci_get_drvdata(hdev); - + priv = (struct btmrvl_private *) hdev->driver_data; if (!test_bit(HCI_RUNNING, &hdev->flags)) { BT_ERR("Failed testing HCI_RUNING, flags=%lx", hdev->flags); print_hex_dump_bytes("data: ", DUMP_PREFIX_OFFSET, @@ -431,7 +432,7 @@ static int btmrvl_send_frame(struct sk_buff *skb) static int btmrvl_flush(struct hci_dev *hdev) { - struct btmrvl_private *priv = hci_get_drvdata(hdev); + struct btmrvl_private *priv = hdev->driver_data; skb_queue_purge(&priv->adapter->tx_queue); @@ -440,7 +441,7 @@ static int btmrvl_flush(struct hci_dev *hdev) static int btmrvl_close(struct hci_dev *hdev) { - struct btmrvl_private *priv = hci_get_drvdata(hdev); + struct btmrvl_private *priv = hdev->driver_data; if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) return 0; @@ -472,6 +473,8 @@ static int btmrvl_service_main_thread(void *data) init_waitqueue_entry(&wait, current); + current->flags |= PF_NOFREEZE; + for (;;) { add_wait_queue(&thread->wait_q, &wait); @@ -543,14 +546,16 @@ int btmrvl_register_hdev(struct btmrvl_private *priv) } priv->btmrvl_dev.hcidev = hdev; - hci_set_drvdata(hdev, priv); + hdev->driver_data = priv; hdev->bus = HCI_SDIO; hdev->open = btmrvl_open; hdev->close = btmrvl_close; hdev->flush = btmrvl_flush; hdev->send = btmrvl_send_frame; + hdev->destruct = btmrvl_destruct; hdev->ioctl = btmrvl_ioctl; + hdev->owner = THIS_MODULE; btmrvl_send_module_cfg_cmd(priv, MODULE_BRINGUP_REQ); diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c index 27b74b0d547b540043fd318917e2cb5d6ebe0591..7f521d4ac657e13764e9917fd45f393b0933eee3 100644 --- a/drivers/bluetooth/btmrvl_sdio.c +++ b/drivers/bluetooth/btmrvl_sdio.c @@ -23,7 +23,6 @@ #include #include -#include #include #include @@ -65,7 +64,7 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_8688 = { .io_port_1 = 0x01, .io_port_2 = 0x02, }; -static const struct btmrvl_sdio_card_reg btmrvl_reg_87xx = { +static const struct btmrvl_sdio_card_reg btmrvl_reg_8787 = { .cfg = 0x00, .host_int_mask = 0x02, .host_intstatus = 0x03, @@ -82,7 +81,7 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_87xx = { .io_port_2 = 0x7a, }; -static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = { +static const struct btmrvl_sdio_device btmrvl_sdio_sd6888 = { .helper = "sd8688_helper.bin", .firmware = "sd8688.bin", .reg = &btmrvl_reg_8688, @@ -92,27 +91,17 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = { static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = { .helper = NULL, .firmware = "mrvl/sd8787_uapsta.bin", - .reg = &btmrvl_reg_87xx, - .sd_blksz_fw_dl = 256, -}; - -static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = { - .helper = NULL, - .firmware = "mrvl/sd8797_uapsta.bin", - .reg = &btmrvl_reg_87xx, + .reg = &btmrvl_reg_8787, .sd_blksz_fw_dl = 256, }; static const struct sdio_device_id btmrvl_sdio_ids[] = { /* Marvell SD8688 Bluetooth device */ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x9105), - .driver_data = (unsigned long) &btmrvl_sdio_sd8688 }, + .driver_data = (unsigned long) &btmrvl_sdio_sd6888 }, /* Marvell SD8787 Bluetooth device */ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911A), .driver_data = (unsigned long) &btmrvl_sdio_sd8787 }, - /* Marvell SD8797 Bluetooth device */ - { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x912A), - .driver_data = (unsigned long) &btmrvl_sdio_sd8797 }, { } /* Terminating entry */ }; @@ -1086,4 +1075,3 @@ MODULE_LICENSE("GPL v2"); MODULE_FIRMWARE("sd8688_helper.bin"); MODULE_FIRMWARE("sd8688.bin"); MODULE_FIRMWARE("mrvl/sd8787_uapsta.bin"); -MODULE_FIRMWARE("mrvl/sd8797_uapsta.bin"); diff --git a/drivers/bluetooth/btsdio.c b/drivers/bluetooth/btsdio.c index e10ea03470510f876bd3b09ef572630c4ef83e29..792e32d29a1de981c3bf84ad15a4e7899a94ec5a 100644 --- a/drivers/bluetooth/btsdio.c +++ b/drivers/bluetooth/btsdio.c @@ -189,7 +189,7 @@ static void btsdio_interrupt(struct sdio_func *func) static int btsdio_open(struct hci_dev *hdev) { - struct btsdio_data *data = hci_get_drvdata(hdev); + struct btsdio_data *data = hdev->driver_data; int err; BT_DBG("%s", hdev->name); @@ -225,7 +225,7 @@ static int btsdio_open(struct hci_dev *hdev) static int btsdio_close(struct hci_dev *hdev) { - struct btsdio_data *data = hci_get_drvdata(hdev); + struct btsdio_data *data = hdev->driver_data; BT_DBG("%s", hdev->name); @@ -246,7 +246,7 @@ static int btsdio_close(struct hci_dev *hdev) static int btsdio_flush(struct hci_dev *hdev) { - struct btsdio_data *data = hci_get_drvdata(hdev); + struct btsdio_data *data = hdev->driver_data; BT_DBG("%s", hdev->name); @@ -258,7 +258,7 @@ static int btsdio_flush(struct hci_dev *hdev) static int btsdio_send_frame(struct sk_buff *skb) { struct hci_dev *hdev = (struct hci_dev *) skb->dev; - struct btsdio_data *data = hci_get_drvdata(hdev); + struct btsdio_data *data = hdev->driver_data; BT_DBG("%s", hdev->name); @@ -289,6 +289,15 @@ static int btsdio_send_frame(struct sk_buff *skb) return 0; } +static void btsdio_destruct(struct hci_dev *hdev) +{ + struct btsdio_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree(data); +} + static int btsdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { @@ -321,7 +330,7 @@ static int btsdio_probe(struct sdio_func *func, } hdev->bus = HCI_SDIO; - hci_set_drvdata(hdev, data); + hdev->driver_data = data; if (id->class == SDIO_CLASS_BT_AMP) hdev->dev_type = HCI_AMP; @@ -336,6 +345,9 @@ static int btsdio_probe(struct sdio_func *func, hdev->close = btsdio_close; hdev->flush = btsdio_flush; hdev->send = btsdio_send_frame; + hdev->destruct = btsdio_destruct; + + hdev->owner = THIS_MODULE; err = hci_register_dev(hdev); if (err < 0) { @@ -366,7 +378,6 @@ static void btsdio_remove(struct sdio_func *func) hci_unregister_dev(hdev); hci_free_dev(hdev); - kfree(data); } static struct sdio_driver btsdio_driver = { diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c index c4fc2f3fc32cad477afb98051100f4bc9d023b3f..f8a0708e23110446de1f8a4ccc5e344306b88820 100644 --- a/drivers/bluetooth/btuart_cs.c +++ b/drivers/bluetooth/btuart_cs.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -396,7 +397,7 @@ static void btuart_change_speed(btuart_info_t *info, unsigned int speed) static int btuart_hci_flush(struct hci_dev *hdev) { - btuart_info_t *info = hci_get_drvdata(hdev); + btuart_info_t *info = (btuart_info_t *)(hdev->driver_data); /* Drop TX queue */ skb_queue_purge(&(info->txq)); @@ -434,7 +435,7 @@ static int btuart_hci_send_frame(struct sk_buff *skb) return -ENODEV; } - info = hci_get_drvdata(hdev); + info = (btuart_info_t *)(hdev->driver_data); switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: @@ -458,6 +459,11 @@ static int btuart_hci_send_frame(struct sk_buff *skb) } +static void btuart_hci_destruct(struct hci_dev *hdev) +{ +} + + static int btuart_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) { return -ENOIOCTLCMD; @@ -492,15 +498,18 @@ static int btuart_open(btuart_info_t *info) info->hdev = hdev; hdev->bus = HCI_PCCARD; - hci_set_drvdata(hdev, info); + hdev->driver_data = info; SET_HCIDEV_DEV(hdev, &info->p_dev->dev); hdev->open = btuart_hci_open; hdev->close = btuart_hci_close; hdev->flush = btuart_hci_flush; hdev->send = btuart_hci_send_frame; + hdev->destruct = btuart_hci_destruct; hdev->ioctl = btuart_hci_ioctl; + hdev->owner = THIS_MODULE; + spin_lock_irqsave(&(info->lock), flags); /* Reset UART */ @@ -556,7 +565,9 @@ static int btuart_close(btuart_info_t *info) spin_unlock_irqrestore(&(info->lock), flags); - hci_unregister_dev(hdev); + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + hci_free_dev(hdev); return 0; @@ -678,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 9217121362e10820da89b5678311b61ad6cac849..e4b6b6b48b7649ed6b3f40f1b74f79f16a617bc9 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -37,13 +37,13 @@ #define VERSION "0.6" -static bool ignore_dga; -static bool ignore_csr; -static bool ignore_sniffer; -static bool disable_scofix; -static bool force_scofix; +static int ignore_dga; +static int ignore_csr; +static int ignore_sniffer; +static int disable_scofix; +static int force_scofix; -static bool reset = 1; +static int reset = 1; static struct usb_driver btusb_driver; @@ -61,7 +61,7 @@ static struct usb_device_id btusb_table[] = { { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, /* Broadcom SoftSailing reporting vendor specific */ - { USB_DEVICE(0x0a5c, 0x21e1) }, + { USB_DEVICE(0x05ac, 0x21e1) }, /* Apple MacBookPro 7,1 */ { USB_DEVICE(0x05ac, 0x8213) }, @@ -100,17 +100,6 @@ static struct usb_device_id btusb_table[] = { /* Canyon CN-BTU1 with HID interfaces */ { USB_DEVICE(0x0c10, 0x0000) }, - /* Broadcom BCM20702A0 */ - { USB_DEVICE(0x0489, 0xe042) }, - { USB_DEVICE(0x0a5c, 0x21e3) }, - { USB_DEVICE(0x0a5c, 0x21e6) }, - { USB_DEVICE(0x0a5c, 0x21e8) }, - { USB_DEVICE(0x0a5c, 0x21f3) }, - { USB_DEVICE(0x413c, 0x8197) }, - - /* Foxconn - Hon Hai */ - { USB_DEVICE(0x0489, 0xe033) }, - { } /* Terminating entry */ }; @@ -125,20 +114,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 }, - { USB_DEVICE(0x0930, 0x0215), .driver_info = BTUSB_IGNORE }, - { USB_DEVICE(0x0489, 0xe03d), .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, 0x311d), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x13d3, 0x3375), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x04ca, 0x3005), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x13d3, 0x3362), .driver_info = BTUSB_ATH3012 }, - { USB_DEVICE(0x0cf3, 0xe004), .driver_info = BTUSB_ATH3012 }, /* Atheros AR5BBU12 with sflash firmware */ { USB_DEVICE(0x0489, 0xe02c), .driver_info = BTUSB_IGNORE }, @@ -255,7 +236,7 @@ static int inc_tx(struct btusb_data *data) static void btusb_intr_complete(struct urb *urb) { struct hci_dev *hdev = urb->context; - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; int err; BT_DBG("%s urb %p status %d count %d", hdev->name, @@ -283,9 +264,7 @@ static void btusb_intr_complete(struct urb *urb) err = usb_submit_urb(urb, GFP_ATOMIC); if (err < 0) { - /* -EPERM: urb is being killed; - * -ENODEV: device got disconnected */ - if (err != -EPERM && err != -ENODEV) + if (err != -EPERM) BT_ERR("%s urb %p failed to resubmit (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); @@ -294,7 +273,7 @@ static void btusb_intr_complete(struct urb *urb) static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; struct urb *urb; unsigned char *buf; unsigned int pipe; @@ -329,8 +308,7 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) err = usb_submit_urb(urb, mem_flags); if (err < 0) { - if (err != -EPERM && err != -ENODEV) - BT_ERR("%s urb %p submission failed (%d)", + BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); } @@ -343,7 +321,7 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) static void btusb_bulk_complete(struct urb *urb) { struct hci_dev *hdev = urb->context; - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; int err; BT_DBG("%s urb %p status %d count %d", hdev->name, @@ -371,9 +349,7 @@ static void btusb_bulk_complete(struct urb *urb) err = usb_submit_urb(urb, GFP_ATOMIC); if (err < 0) { - /* -EPERM: urb is being killed; - * -ENODEV: device got disconnected */ - if (err != -EPERM && err != -ENODEV) + if (err != -EPERM) BT_ERR("%s urb %p failed to resubmit (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); @@ -382,7 +358,7 @@ static void btusb_bulk_complete(struct urb *urb) static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; struct urb *urb; unsigned char *buf; unsigned int pipe; @@ -415,8 +391,7 @@ static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags) err = usb_submit_urb(urb, mem_flags); if (err < 0) { - if (err != -EPERM && err != -ENODEV) - BT_ERR("%s urb %p submission failed (%d)", + BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); } @@ -429,7 +404,7 @@ static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags) static void btusb_isoc_complete(struct urb *urb) { struct hci_dev *hdev = urb->context; - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; int i, err; BT_DBG("%s urb %p status %d count %d", hdev->name, @@ -464,16 +439,14 @@ static void btusb_isoc_complete(struct urb *urb) err = usb_submit_urb(urb, GFP_ATOMIC); if (err < 0) { - /* -EPERM: urb is being killed; - * -ENODEV: device got disconnected */ - if (err != -EPERM && err != -ENODEV) + if (err != -EPERM) BT_ERR("%s urb %p failed to resubmit (%d)", hdev->name, urb, -err); usb_unanchor_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; @@ -496,7 +469,7 @@ static inline void __fill_isoc_descriptor(struct urb *urb, int len, int mtu) static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; struct urb *urb; unsigned char *buf; unsigned int pipe; @@ -522,10 +495,15 @@ static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags) pipe = usb_rcvisocpipe(data->udev, data->isoc_rx_ep->bEndpointAddress); - usb_fill_int_urb(urb, data->udev, pipe, buf, size, btusb_isoc_complete, - hdev, data->isoc_rx_ep->bInterval); + urb->dev = data->udev; + urb->pipe = pipe; + urb->context = hdev; + urb->complete = btusb_isoc_complete; + urb->interval = data->isoc_rx_ep->bInterval; urb->transfer_flags = URB_FREE_BUFFER | URB_ISO_ASAP; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = size; __fill_isoc_descriptor(urb, size, le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize)); @@ -534,8 +512,7 @@ static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags) err = usb_submit_urb(urb, mem_flags); if (err < 0) { - if (err != -EPERM && err != -ENODEV) - BT_ERR("%s urb %p submission failed (%d)", + BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); } @@ -549,7 +526,7 @@ static void btusb_tx_complete(struct urb *urb) { struct sk_buff *skb = urb->context; struct hci_dev *hdev = (struct hci_dev *) skb->dev; - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; BT_DBG("%s urb %p status %d count %d", hdev->name, urb, urb->status, urb->actual_length); @@ -596,7 +573,7 @@ static void btusb_isoc_tx_complete(struct urb *urb) static int btusb_open(struct hci_dev *hdev) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; int err; BT_DBG("%s", hdev->name); @@ -646,7 +623,7 @@ static void btusb_stop_traffic(struct btusb_data *data) static int btusb_close(struct hci_dev *hdev) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; int err; BT_DBG("%s", hdev->name); @@ -676,7 +653,7 @@ static int btusb_close(struct hci_dev *hdev) static int btusb_flush(struct hci_dev *hdev) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; BT_DBG("%s", hdev->name); @@ -688,7 +665,7 @@ static int btusb_flush(struct hci_dev *hdev) static int btusb_send_frame(struct sk_buff *skb) { struct hci_dev *hdev = (struct hci_dev *) skb->dev; - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; struct usb_ctrlrequest *dr; struct urb *urb; unsigned int pipe; @@ -726,7 +703,8 @@ static int btusb_send_frame(struct sk_buff *skb) break; case HCI_ACLDATA_PKT: - if (!data->bulk_tx_ep) + if (!data->bulk_tx_ep || (hdev->conn_hash.acl_num < 1 && + hdev->conn_hash.le_num < 1)) return -ENODEV; urb = usb_alloc_urb(0, GFP_ATOMIC); @@ -782,23 +760,31 @@ static int btusb_send_frame(struct sk_buff *skb) err = usb_submit_urb(urb, GFP_ATOMIC); if (err < 0) { - if (err != -EPERM && err != -ENODEV) - BT_ERR("%s urb %p submission failed (%d)", - hdev->name, urb, -err); + BT_ERR("%s urb %p submission failed", hdev->name, urb); kfree(urb->setup_packet); usb_unanchor_urb(urb); } else { usb_mark_last_busy(data->udev); } -done: usb_free_urb(urb); + +done: return err; } +static void btusb_destruct(struct hci_dev *hdev) +{ + struct btusb_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree(data); +} + static void btusb_notify(struct hci_dev *hdev, unsigned int evt) { - struct btusb_data *data = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; BT_DBG("%s evt %d", hdev->name, evt); @@ -808,9 +794,9 @@ 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 = hci_get_drvdata(hdev); + struct btusb_data *data = hdev->driver_data; struct usb_interface *intf = data->isoc; struct usb_endpoint_descriptor *ep_desc; int i, err; @@ -998,7 +984,7 @@ static int btusb_probe(struct usb_interface *intf, } hdev->bus = HCI_USB; - hci_set_drvdata(hdev, data); + hdev->driver_data = data; data->hdev = hdev; @@ -1008,8 +994,11 @@ static int btusb_probe(struct usb_interface *intf, hdev->close = btusb_close; hdev->flush = btusb_flush; hdev->send = btusb_send_frame; + hdev->destruct = btusb_destruct; hdev->notify = btusb_notify; + hdev->owner = THIS_MODULE; + /* Interface numbers are hardcoded in the specification */ data->isoc = usb_ifnum_to_if(data->udev, 1); @@ -1091,6 +1080,9 @@ static void btusb_disconnect(struct usb_interface *intf) return; hdev = data->hdev; + + __hci_dev_hold(hdev); + usb_set_intfdata(data->intf, NULL); if (data->isoc) @@ -1103,8 +1095,9 @@ static void btusb_disconnect(struct usb_interface *intf) else if (data->isoc) usb_driver_release_interface(&btusb_driver, data->isoc); + __hci_dev_put(hdev); + hci_free_dev(hdev); - kfree(data); } #ifdef CONFIG_PM @@ -1118,7 +1111,7 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message) return 0; spin_lock_irq(&data->txlock); - if (!(PMSG_IS_AUTO(message) && data->tx_in_flight)) { + if (!((message.event & PM_EVENT_AUTO) && data->tx_in_flight)) { set_bit(BTUSB_SUSPENDING, &data->flags); spin_unlock_irq(&data->txlock); } else { @@ -1220,7 +1213,20 @@ static struct usb_driver btusb_driver = { .supports_autosuspend = 1, }; -module_usb_driver(btusb_driver); +static int __init btusb_init(void) +{ + BT_INFO("Generic Bluetooth USB driver ver %s", VERSION); + + return usb_register(&btusb_driver); +} + +static void __exit btusb_exit(void) +{ + usb_deregister(&btusb_driver); +} + +module_init(btusb_init); +module_exit(btusb_exit); module_param(ignore_dga, bool, 0644); MODULE_PARM_DESC(ignore_dga, "Ignore devices with id 08fd:0001"); diff --git a/drivers/bluetooth/btwilink.c b/drivers/bluetooth/btwilink.c index 88694697f34f68386cdb4273289ccea7baaeaa5b..65d27aff553ae1bdbef834ffc7145e6d9f1f656e 100644 --- a/drivers/bluetooth/btwilink.c +++ b/drivers/bluetooth/btwilink.c @@ -29,7 +29,6 @@ #include #include -#include /* Bluetooth Driver Version */ #define VERSION "1.0" @@ -125,13 +124,6 @@ static long st_receive(void *priv_data, struct sk_buff *skb) /* ------- Interfaces to HCI layer ------ */ /* protocol structure registered with shared transport */ static struct st_proto_s ti_st_proto[MAX_BT_CHNL_IDS] = { - { - .chnl_id = HCI_EVENT_PKT, /* HCI Events */ - .hdr_len = sizeof(struct hci_event_hdr), - .offset_len_in_hdr = offsetof(struct hci_event_hdr, plen), - .len_size = 1, /* sizeof(plen) in struct hci_event_hdr */ - .reserve = 8, - }, { .chnl_id = HCI_ACLDATA_PKT, /* ACL */ .hdr_len = sizeof(struct hci_acl_hdr), @@ -146,6 +138,13 @@ static struct st_proto_s ti_st_proto[MAX_BT_CHNL_IDS] = { .len_size = 1, /* sizeof(dlen) in struct hci_sco_hdr */ .reserve = 8, }, + { + .chnl_id = HCI_EVENT_PKT, /* HCI Events */ + .hdr_len = sizeof(struct hci_event_hdr), + .offset_len_in_hdr = offsetof(struct hci_event_hdr, plen), + .len_size = 1, /* sizeof(plen) in struct hci_event_hdr */ + .reserve = 8, + }, }; /* Called from HCI core to initialize the device */ @@ -161,7 +160,7 @@ static int ti_st_open(struct hci_dev *hdev) return -EBUSY; /* provide contexts for callbacks from ST */ - hst = hci_get_drvdata(hdev); + hst = hdev->driver_data; for (i = 0; i < MAX_BT_CHNL_IDS; i++) { ti_st_proto[i].priv_data = hst; @@ -236,12 +235,12 @@ static int ti_st_open(struct hci_dev *hdev) static int ti_st_close(struct hci_dev *hdev) { int err, i; - struct ti_st *hst = hci_get_drvdata(hdev); + struct ti_st *hst = hdev->driver_data; if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) return 0; - for (i = MAX_BT_CHNL_IDS-1; i >= 0; i--) { + for (i = 0; i < MAX_BT_CHNL_IDS; i++) { err = st_unregister(&ti_st_proto[i]); if (err) BT_ERR("st_unregister(%d) failed with error %d", @@ -264,7 +263,7 @@ static int ti_st_send_frame(struct sk_buff *skb) if (!test_bit(HCI_RUNNING, &hdev->flags)) return -EBUSY; - hst = hci_get_drvdata(hdev); + hst = hdev->driver_data; /* Prepend skb with frame type */ memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); @@ -291,6 +290,14 @@ static int ti_st_send_frame(struct sk_buff *skb) return 0; } +static void ti_st_destruct(struct hci_dev *hdev) +{ + BT_DBG("%s", hdev->name); + /* do nothing here, since platform remove + * would free the hdev->driver_data + */ +} + static int bt_ti_probe(struct platform_device *pdev) { static struct ti_st *hst; @@ -312,11 +319,13 @@ static int bt_ti_probe(struct platform_device *pdev) hst->hdev = hdev; hdev->bus = HCI_UART; - hci_set_drvdata(hdev, hst); + hdev->driver_data = hst; hdev->open = ti_st_open; hdev->close = ti_st_close; hdev->flush = NULL; hdev->send = ti_st_send_frame; + hdev->destruct = ti_st_destruct; + hdev->owner = THIS_MODULE; err = hci_register_dev(hdev); if (err < 0) { diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c index 6e8d9618968443df06710d52666aaf5e5c2af3bd..26ee0cf88d20487c0c830b5dec0c753bbf11a3b8 100644 --- a/drivers/bluetooth/dtl1_cs.c +++ b/drivers/bluetooth/dtl1_cs.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -82,6 +83,9 @@ typedef struct dtl1_info_t { static int dtl1_config(struct pcmcia_device *link); +static void dtl1_release(struct pcmcia_device *link); + +static void dtl1_detach(struct pcmcia_device *p_dev); /* Transmit states */ @@ -363,7 +367,7 @@ static int dtl1_hci_open(struct hci_dev *hdev) static int dtl1_hci_flush(struct hci_dev *hdev) { - dtl1_info_t *info = hci_get_drvdata(hdev); + dtl1_info_t *info = (dtl1_info_t *)(hdev->driver_data); /* Drop TX queue */ skb_queue_purge(&(info->txq)); @@ -395,7 +399,7 @@ static int dtl1_hci_send_frame(struct sk_buff *skb) return -ENODEV; } - info = hci_get_drvdata(hdev); + info = (dtl1_info_t *)(hdev->driver_data); switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: @@ -438,6 +442,11 @@ static int dtl1_hci_send_frame(struct sk_buff *skb) } +static void dtl1_hci_destruct(struct hci_dev *hdev) +{ +} + + static int dtl1_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) { return -ENOIOCTLCMD; @@ -474,15 +483,18 @@ static int dtl1_open(dtl1_info_t *info) info->hdev = hdev; hdev->bus = HCI_PCCARD; - hci_set_drvdata(hdev, info); + hdev->driver_data = info; SET_HCIDEV_DEV(hdev, &info->p_dev->dev); hdev->open = dtl1_hci_open; hdev->close = dtl1_hci_close; hdev->flush = dtl1_hci_flush; hdev->send = dtl1_hci_send_frame; + hdev->destruct = dtl1_hci_destruct; hdev->ioctl = dtl1_hci_ioctl; + hdev->owner = THIS_MODULE; + spin_lock_irqsave(&(info->lock), flags); /* Reset UART */ @@ -539,7 +551,9 @@ static int dtl1_close(dtl1_info_t *info) spin_unlock_irqrestore(&(info->lock), flags); - hci_unregister_dev(hdev); + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + hci_free_dev(hdev); return 0; @@ -567,8 +581,8 @@ static void dtl1_detach(struct pcmcia_device *link) { dtl1_info_t *info = link->priv; - dtl1_close(info); - pcmcia_disable_device(link); + dtl1_release(link); + kfree(info); } @@ -607,11 +621,22 @@ static int dtl1_config(struct pcmcia_device *link) return 0; failed: - dtl1_detach(link); + dtl1_release(link); return -ENODEV; } -static const struct pcmcia_device_id dtl1_ids[] = { + +static void dtl1_release(struct pcmcia_device *link) +{ + dtl1_info_t *info = link->priv; + + dtl1_close(info); + + pcmcia_disable_device(link); +} + + +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_ath.c b/drivers/bluetooth/hci_ath.c index 12172a6a95c440467666d1599f13bab4fe45253f..4093935ddf42619e44fef9b87428c34454104fe8 100644 --- a/drivers/bluetooth/hci_ath.c +++ b/drivers/bluetooth/hci_ath.c @@ -112,7 +112,7 @@ static int ath_open(struct hci_uart *hu) BT_DBG("hu %p", hu); - ath = kzalloc(sizeof(*ath), GFP_KERNEL); + ath = kzalloc(sizeof(*ath), GFP_ATOMIC); if (!ath) return -ENOMEM; diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c index 661a8dc4d2f8b346e390053ee45d63cb7249346e..9c5b2dc38e29455884618b4dfb5c8256d5fa5496 100644 --- a/drivers/bluetooth/hci_bcsp.c +++ b/drivers/bluetooth/hci_bcsp.c @@ -49,8 +49,8 @@ #define VERSION "0.3" -static bool txcrc = 1; -static bool hciextn = 1; +static int txcrc = 1; +static int hciextn = 1; #define BCSP_TXWINSIZE 4 @@ -692,7 +692,7 @@ static int bcsp_open(struct hci_uart *hu) BT_DBG("hu %p", hu); - bcsp = kzalloc(sizeof(*bcsp), GFP_KERNEL); + bcsp = kzalloc(sizeof(*bcsp), GFP_ATOMIC); if (!bcsp) return -ENOMEM; diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c index 748329468d26ca6432f6e04dc2d2ab92afd62e4f..2fcd8b387d694b74130956fe836b5c211dbee20a 100644 --- a/drivers/bluetooth/hci_h4.c +++ b/drivers/bluetooth/hci_h4.c @@ -69,7 +69,7 @@ static int h4_open(struct hci_uart *hu) BT_DBG("hu %p", hu); - h4 = kzalloc(sizeof(*h4), GFP_KERNEL); + h4 = kzalloc(sizeof(*h4), GFP_ATOMIC); if (!h4) return -ENOMEM; diff --git a/drivers/bluetooth/hci_ibs.c b/drivers/bluetooth/hci_ibs.c new file mode 100644 index 0000000000000000000000000000000000000000..2a6f3f84b9353f700794a072fb490b6bd173e4ca --- /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 98a8c05d4f23a038dfa020cd98930c9d52283844..121bf7c98ded216b669f7999cac066aa93e16025 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 @@ -48,6 +48,8 @@ #define VERSION "2.2" +static bool reset = 0; + static struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; int hci_uart_register_proto(struct hci_uart_proto *p) @@ -172,7 +174,7 @@ static int hci_uart_open(struct hci_dev *hdev) /* Reset device */ static int hci_uart_flush(struct hci_dev *hdev) { - struct hci_uart *hu = hci_get_drvdata(hdev); + struct hci_uart *hu = (struct hci_uart *) hdev->driver_data; struct tty_struct *tty = hu->tty; BT_DBG("hdev %p tty %p", hdev, tty); @@ -218,7 +220,7 @@ static int hci_uart_send_frame(struct sk_buff *skb) if (!test_bit(HCI_RUNNING, &hdev->flags)) return -EBUSY; - hu = hci_get_drvdata(hdev); + hu = (struct hci_uart *) hdev->driver_data; BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, skb->len); @@ -229,6 +231,15 @@ static int hci_uart_send_frame(struct sk_buff *skb) return 0; } +static void hci_uart_destruct(struct hci_dev *hdev) +{ + if (!hdev) + return; + + BT_DBG("%s", hdev->name); + kfree(hdev->driver_data); +} + /* ------ LDISC part ------ */ /* hci_uart_tty_open * @@ -299,14 +310,12 @@ static void hci_uart_tty_close(struct tty_struct *tty) hci_uart_close(hdev); if (test_and_clear_bit(HCI_UART_PROTO_SET, &hu->flags)) { + hu->proto->close(hu); if (hdev) { hci_unregister_dev(hdev); hci_free_dev(hdev); } - hu->proto->close(hu); } - - kfree(hu); } } @@ -350,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) @@ -359,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); @@ -382,24 +393,22 @@ static int hci_uart_register_dev(struct hci_uart *hu) hu->hdev = hdev; hdev->bus = HCI_UART; - hci_set_drvdata(hdev, hu); + hdev->driver_data = hu; hdev->open = hci_uart_open; hdev->close = hci_uart_close; hdev->flush = hci_uart_flush; hdev->send = hci_uart_send_frame; + hdev->destruct = hci_uart_destruct; hdev->parent = hu->tty->dev; - if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) - set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + hdev->owner = THIS_MODULE; - if (!test_bit(HCI_UART_RESET_ON_INIT, &hu->hdev_flags)) + if (!reset) set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); - if (test_bit(HCI_UART_CREATE_AMP, &hu->hdev_flags)) - hdev->dev_type = HCI_AMP; - else - hdev->dev_type = HCI_BREDR; + if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); if (hci_register_dev(hdev) < 0) { BT_ERR("Can't register HCI device"); @@ -461,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; @@ -558,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; } @@ -578,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))) @@ -587,6 +609,9 @@ static void __exit hci_uart_exit(void) module_init(hci_uart_init); module_exit(hci_uart_exit); +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Bluetooth HCI UART driver ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c index b874c0efde247be90f38d0a2772813ea56fd6afa..38595e782d02e1c39dcbd82cc0fa0c94b0babfd2 100644 --- a/drivers/bluetooth/hci_ll.c +++ b/drivers/bluetooth/hci_ll.c @@ -125,7 +125,7 @@ static int ll_open(struct hci_uart *hu) BT_DBG("hu %p", hu); - ll = kzalloc(sizeof(*ll), GFP_KERNEL); + ll = kzalloc(sizeof(*ll), GFP_ATOMIC); if (!ll) return -ENOMEM; @@ -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 0000000000000000000000000000000000000000..b6de881cb6eb368d40306498690e0c8fb1d4e69e --- /dev/null +++ b/drivers/bluetooth/hci_smd.c @@ -0,0 +1,561 @@ +/* + * HCI_SMD (HCI Shared Memory Driver) is Qualcomm's Shared memory driver + * for the BT HCI protocol. + * + * Copyright (c) 2000-2001, 2011-2012 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 +#include +#include +#include +#include + +#define EVENT_CHANNEL "APPS_RIVA_BT_CMD" +#define DATA_CHANNEL "APPS_RIVA_BT_ACL" +/* release wakelock in 500ms, not immediately, because higher layers + * don't always take wakelocks when they should + * This is derived from the implementation for UART transport + */ + +#define RX_Q_MONITOR (500) /* 500 milli second */ + + +static int hcismd_set; +static DEFINE_MUTEX(hci_smd_enable); + +static int hcismd_set_enable(const char *val, struct kernel_param *kp); +module_param_call(hcismd_set, hcismd_set_enable, NULL, &hcismd_set, 0644); + +static void hci_dev_smd_open(struct work_struct *worker); +static void hci_dev_restart(struct work_struct *worker); + +struct hci_smd_data { + struct hci_dev *hdev; + + struct smd_channel *event_channel; + struct smd_channel *data_channel; + struct wake_lock wake_lock_tx; + struct wake_lock wake_lock_rx; + struct timer_list rx_q_timer; + struct tasklet_struct rx_task; +}; +static struct hci_smd_data hs; + +/* Rx queue monitor timer function */ +static int is_rx_q_empty(unsigned long arg) +{ + struct hci_dev *hdev = (struct hci_dev *) arg; + struct sk_buff_head *list_ = &hdev->rx_q; + struct sk_buff *list = ((struct sk_buff *)list_)->next; + BT_DBG("%s Rx timer triggered", hdev->name); + + if (list == (struct sk_buff *)list_) { + BT_DBG("%s RX queue empty", hdev->name); + return 1; + } else{ + BT_DBG("%s RX queue not empty", hdev->name); + return 0; + } +} + +static void release_lock(void) +{ + struct hci_smd_data *hsmd = &hs; + BT_DBG("Releasing Rx Lock"); + if (is_rx_q_empty((unsigned long)hsmd->hdev) && + wake_lock_active(&hs.wake_lock_rx)) + wake_unlock(&hs.wake_lock_rx); +} + +/* Rx timer callback function */ +static void schedule_timer(unsigned long arg) +{ + struct hci_dev *hdev = (struct hci_dev *) arg; + struct hci_smd_data *hsmd = &hs; + BT_DBG("%s Schedule Rx timer", hdev->name); + + if (is_rx_q_empty(arg) && wake_lock_active(&hs.wake_lock_rx)) { + BT_DBG("%s RX queue empty", hdev->name); + /* + * Since the queue is empty, its ideal + * to release the wake lock on Rx + */ + wake_unlock(&hs.wake_lock_rx); + } else{ + BT_DBG("%s RX queue not empty", hdev->name); + /* + * Restart the timer to monitor whether the Rx queue is + * empty for releasing the Rx wake lock + */ + mod_timer(&hsmd->rx_q_timer, + jiffies + msecs_to_jiffies(RX_Q_MONITOR)); + } +} + +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) +{ + if (NULL != hdev->driver_data) + kfree(hdev->driver_data); +} + +static void hci_smd_recv_data(void) +{ + int len = 0; + int rc = 0; + struct sk_buff *skb = NULL; + struct hci_smd_data *hsmd = &hs; + wake_lock(&hs.wake_lock_rx); + + len = smd_read_avail(hsmd->data_channel); + if (len > HCI_MAX_FRAME_SIZE) { + BT_ERR("Frame larger than the allowed size, flushing frame"); + smd_read(hsmd->data_channel, NULL, len); + goto out_data; + } + + if (len <= 0) + goto out_data; + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + BT_ERR("Error in allocating socket buffer"); + smd_read(hsmd->data_channel, NULL, len); + goto out_data; + } + + rc = smd_read(hsmd->data_channel, skb_put(skb, len), len); + if (rc < len) { + BT_ERR("Error in reading from the channel"); + goto out_data; + } + + 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"); + /* + * skb is getting freed in hci_recv_frame, making it + * to null to avoid multiple access + */ + skb = NULL; + goto out_data; + } + + /* + * Start the timer to monitor whether the Rx queue is + * empty for releasing the Rx wake lock + */ + BT_DBG("Rx Timer is starting"); + mod_timer(&hsmd->rx_q_timer, + jiffies + msecs_to_jiffies(RX_Q_MONITOR)); + +out_data: + release_lock(); + if (rc) + kfree_skb(skb); +} + +static void hci_smd_recv_event(void) +{ + int len = 0; + int rc = 0; + struct sk_buff *skb = NULL; + struct hci_smd_data *hsmd = &hs; + wake_lock(&hs.wake_lock_rx); + + len = smd_read_avail(hsmd->event_channel); + if (len > HCI_MAX_FRAME_SIZE) { + BT_ERR("Frame larger than the allowed size, flushing frame"); + rc = smd_read(hsmd->event_channel, NULL, len); + goto out_event; + } + + while (len > 0) { + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + BT_ERR("Error in allocating socket buffer"); + smd_read(hsmd->event_channel, NULL, len); + goto out_event; + } + + rc = smd_read(hsmd->event_channel, skb_put(skb, len), len); + if (rc < len) { + BT_ERR("Error in reading from the event channel"); + goto out_event; + } + + 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"); + /* + * skb is getting freed in hci_recv_frame, making it + * to null to avoid multiple access + */ + skb = NULL; + goto out_event; + } + + len = smd_read_avail(hsmd->event_channel); + /* + * Start the timer to monitor whether the Rx queue is + * empty for releasing the Rx wake lock + */ + BT_DBG("Rx Timer is starting"); + mod_timer(&hsmd->rx_q_timer, + jiffies + msecs_to_jiffies(RX_Q_MONITOR)); + } +out_event: + release_lock(); + if (rc) + kfree_skb(skb); +} + +static int hci_smd_send_frame(struct sk_buff *skb) +{ + int len; + int avail; + int ret = 0; + wake_lock(&hs.wake_lock_tx); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + avail = smd_write_avail(hs.event_channel); + if (!avail) { + BT_ERR("No space available for smd frame"); + ret = -ENOSPC; + } + len = smd_write(hs.event_channel, skb->data, skb->len); + if (len < skb->len) { + BT_ERR("Failed to write Command %d", len); + ret = -ENODEV; + } + break; + case HCI_ACLDATA_PKT: + case HCI_SCODATA_PKT: + avail = smd_write_avail(hs.data_channel); + if (!avail) { + BT_ERR("No space available for smd frame"); + ret = -ENOSPC; + } + len = smd_write(hs.data_channel, skb->data, skb->len); + if (len < skb->len) { + BT_ERR("Failed to write Data %d", len); + ret = -ENODEV; + } + break; + default: + BT_ERR("Uknown packet type"); + ret = -ENODEV; + break; + } + + kfree_skb(skb); + wake_unlock(&hs.wake_lock_tx); + return ret; +} + +static void hci_smd_rx(unsigned long arg) +{ + struct hci_smd_data *hsmd = &hs; + + while ((smd_read_avail(hsmd->event_channel) > 0) || + (smd_read_avail(hsmd->data_channel) > 0)) { + hci_smd_recv_event(); + hci_smd_recv_data(); + } +} + +static void hci_smd_notify_event(void *data, unsigned int event) +{ + struct hci_dev *hdev = hs.hdev; + struct hci_smd_data *hsmd = &hs; + struct work_struct *reset_worker; + struct work_struct *open_worker; + + int len = 0; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + len = smd_read_avail(hsmd->event_channel); + if (len > 0) + tasklet_hi_schedule(&hs.rx_task); + else if (len < 0) + BT_ERR("Failed to read event from smd %d", len); + + break; + case SMD_EVENT_OPEN: + BT_INFO("opening HCI-SMD channel :%s", EVENT_CHANNEL); + hci_smd_open(hdev); + open_worker = kzalloc(sizeof(*open_worker), GFP_ATOMIC); + if (!open_worker) { + BT_ERR("Out of memory"); + break; + } + INIT_WORK(open_worker, hci_dev_smd_open); + schedule_work(open_worker); + break; + case SMD_EVENT_CLOSE: + BT_INFO("Closing HCI-SMD channel :%s", EVENT_CHANNEL); + hci_smd_close(hdev); + reset_worker = kzalloc(sizeof(*reset_worker), GFP_ATOMIC); + if (!reset_worker) { + BT_ERR("Out of memory"); + break; + } + INIT_WORK(reset_worker, hci_dev_restart); + schedule_work(reset_worker); + break; + default: + break; + } +} + +static void hci_smd_notify_data(void *data, unsigned int event) +{ + struct hci_dev *hdev = hs.hdev; + struct hci_smd_data *hsmd = &hs; + int len = 0; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + len = smd_read_avail(hsmd->data_channel); + if (len > 0) + tasklet_hi_schedule(&hs.rx_task); + else if (len < 0) + BT_ERR("Failed to read data from smd %d", len); + break; + case SMD_EVENT_OPEN: + BT_INFO("opening HCI-SMD channel :%s", DATA_CHANNEL); + hci_smd_open(hdev); + break; + case SMD_EVENT_CLOSE: + BT_INFO("Closing HCI-SMD channel :%s", DATA_CHANNEL); + hci_smd_close(hdev); + break; + default: + break; + } + +} + +static int hci_smd_hci_register_dev(struct hci_smd_data *hsmd) +{ + struct hci_dev *hdev; + + hdev = hsmd->hdev; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + hsmd->hdev = NULL; + return -ENODEV; + } + return 0; +} + +static int hci_smd_register_smd(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 = NULL; + 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; + + + tasklet_init(&hsmd->rx_task, + hci_smd_rx, (unsigned long) hsmd); + /* + * Setup the timer to monitor whether the Rx queue is empty, + * to control the wake lock release + */ + setup_timer(&hsmd->rx_q_timer, schedule_timer, + (unsigned long) hsmd->hdev); + + /* 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); + hsmd->hdev = NULL; + 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"); + hci_free_dev(hdev); + hsmd->hdev = NULL; + return -ENODEV; + } + + /* Disable the read interrupts on the channel */ + smd_disable_read_intr(hsmd->event_channel); + smd_disable_read_intr(hsmd->data_channel); + return 0; +} + +static void hci_smd_deregister_dev(struct hci_smd_data *hsmd) +{ + tasklet_kill(&hs.rx_task); + + if (hsmd->hdev) { + if (hci_unregister_dev(hsmd->hdev) < 0) + BT_ERR("Can't unregister HCI device %s", + hsmd->hdev->name); + + hci_free_dev(hsmd->hdev); + hsmd->hdev = NULL; + } + + smd_close(hs.event_channel); + smd_close(hs.data_channel); + + if (wake_lock_active(&hs.wake_lock_rx)) + wake_unlock(&hs.wake_lock_rx); + if (wake_lock_active(&hs.wake_lock_tx)) + wake_unlock(&hs.wake_lock_tx); + + /*Destroy the timer used to monitor the Rx queue for emptiness */ + if (hs.rx_q_timer.function) { + del_timer_sync(&hs.rx_q_timer); + hs.rx_q_timer.function = NULL; + hs.rx_q_timer.data = 0; + } +} + +static void hci_dev_restart(struct work_struct *worker) +{ + mutex_lock(&hci_smd_enable); + hci_smd_deregister_dev(&hs); + hci_smd_register_smd(&hs); + mutex_unlock(&hci_smd_enable); + kfree(worker); +} + +static void hci_dev_smd_open(struct work_struct *worker) +{ + mutex_lock(&hci_smd_enable); + hci_smd_hci_register_dev(&hs); + mutex_unlock(&hci_smd_enable); + kfree(worker); +} + +static int hcismd_set_enable(const char *val, struct kernel_param *kp) +{ + int ret = 0; + + mutex_lock(&hci_smd_enable); + + ret = param_set_int(val, kp); + + if (ret) + goto done; + + switch (hcismd_set) { + + case 1: + hci_smd_register_smd(&hs); + break; + case 0: + hci_smd_deregister_dev(&hs); + break; + default: + ret = -EFAULT; + } + +done: + mutex_unlock(&hci_smd_enable); + return ret; +} +static int __init hci_smd_init(void) +{ + wake_lock_init(&hs.wake_lock_rx, WAKE_LOCK_SUSPEND, + "msm_smd_Rx"); + wake_lock_init(&hs.wake_lock_tx, WAKE_LOCK_SUSPEND, + "msm_smd_Tx"); + return 0; +} +module_init(hci_smd_init); + +static void __exit hci_smd_exit(void) +{ + wake_lock_destroy(&hs.wake_lock_rx); + wake_lock_destroy(&hs.wake_lock_tx); +} +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 6cf6ab22ad21f7e081f1d806f44638adc8e6a627..dc482397e47c54817389a38784850d20d30d2d84 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,18 +35,17 @@ #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 -#define HCI_UART_RESET_ON_INIT 1 -#define HCI_UART_CREATE_AMP 2 struct hci_uart; @@ -75,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 @@ -104,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/bluetooth/hci_vhci.c b/drivers/bluetooth/hci_vhci.c index 158bfe507da7fb19f4149ce63806bd9e8c510409..67c180c2c1e0fbf27951862e30a0787deba4d4a0 100644 --- a/drivers/bluetooth/hci_vhci.c +++ b/drivers/bluetooth/hci_vhci.c @@ -41,8 +41,6 @@ #define VERSION "1.3" -static bool amp; - struct vhci_data { struct hci_dev *hdev; @@ -61,7 +59,7 @@ static int vhci_open_dev(struct hci_dev *hdev) static int vhci_close_dev(struct hci_dev *hdev) { - struct vhci_data *data = hci_get_drvdata(hdev); + struct vhci_data *data = hdev->driver_data; if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) return 0; @@ -73,7 +71,7 @@ static int vhci_close_dev(struct hci_dev *hdev) static int vhci_flush(struct hci_dev *hdev) { - struct vhci_data *data = hci_get_drvdata(hdev); + struct vhci_data *data = hdev->driver_data; skb_queue_purge(&data->readq); @@ -93,7 +91,7 @@ static int vhci_send_frame(struct sk_buff *skb) if (!test_bit(HCI_RUNNING, &hdev->flags)) return -EBUSY; - data = hci_get_drvdata(hdev); + data = hdev->driver_data; memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); skb_queue_tail(&data->readq, skb); @@ -103,6 +101,11 @@ static int vhci_send_frame(struct sk_buff *skb) return 0; } +static void vhci_destruct(struct hci_dev *hdev) +{ + kfree(hdev->driver_data); +} + static inline ssize_t vhci_get_user(struct vhci_data *data, const char __user *buf, size_t count) { @@ -234,15 +237,15 @@ static int vhci_open(struct inode *inode, struct file *file) data->hdev = hdev; hdev->bus = HCI_VIRTUAL; - hci_set_drvdata(hdev, data); - - if (amp) - hdev->dev_type = HCI_AMP; + hdev->driver_data = data; hdev->open = vhci_open_dev; hdev->close = vhci_close_dev; hdev->flush = vhci_flush; hdev->send = vhci_send_frame; + hdev->destruct = vhci_destruct; + + hdev->owner = THIS_MODULE; if (hci_register_dev(hdev) < 0) { BT_ERR("Can't register HCI device"); @@ -261,11 +264,13 @@ static int vhci_release(struct inode *inode, struct file *file) struct vhci_data *data = file->private_data; struct hci_dev *hdev = data->hdev; - hci_unregister_dev(hdev); + if (hci_unregister_dev(hdev) < 0) { + BT_ERR("Can't unregister HCI device %s", hdev->name); + } + hci_free_dev(hdev); file->private_data = NULL; - kfree(data); return 0; } @@ -301,9 +306,6 @@ static void __exit vhci_exit(void) module_init(vhci_init); module_exit(vhci_exit); -module_param(amp, bool, 0644); -MODULE_PARM_DESC(amp, "Create AMP controller device"); - MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index f48cd688f4c06f4e73da6e8f6c7d7e10b8e8f15d..00a07a02a9a1c0f5e56284d60713e68300d9493a 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 @@ -629,5 +631,46 @@ config TILE_SROM device appear much like a simple EEPROM, and knows how to partition a single ROM for multiple purposes. +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 8f18891755b9857185892f5db895260399f9d36b..c38c26c282d4c6650bef01490f617522585c2052 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 @@ -65,3 +64,6 @@ obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o +obj-$(CONFIG_MSM_ROTATOR) += msm_rotator.o +obj-$(CONFIG_MMC_GENERIC_CSDIO) += csdio.o +obj-$(CONFIG_DIAG_CHAR) += diag/ \ No newline at end of file diff --git a/drivers/char/csdio.c b/drivers/char/csdio.c new file mode 100644 index 0000000000000000000000000000000000000000..ca7e98675de32035eea212e761f4558a40871380 --- /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 a787accdcb14b2556bcb59ea5693c1d04e198307..7643f50d527199150abd464b7a793064c0684e61 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 0000000000000000000000000000000000000000..53df29b7611a11c4e4b6a6795b06b401dd2c33d1 --- /dev/null +++ b/drivers/char/diag/Kconfig @@ -0,0 +1,41 @@ +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 + +menu "HSIC support for DIAG" + +config DIAG_HSIC_PIPE + depends on USB_QCOM_DIAG_BRIDGE + default y + bool "Enable 9K DIAG traffic over HSIC" + help + HSIC Transport Layer for DIAG Router +endmenu diff --git a/drivers/char/diag/Makefile b/drivers/char/diag/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3181d29d92c9511fb9f5c8949a96be45aca54efa --- /dev/null +++ b/drivers/char/diag/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_DIAG_CHAR) := diagchar.o +obj-$(CONFIG_DIAG_SDIO_PIPE) += diagfwd_sdio.o +obj-$(CONFIG_DIAG_HSIC_PIPE) += diagfwd_hsic.o +diagchar-objs := diagchar_core.o diagchar_hdlc.o diagfwd.o diagmem.o diagfwd_cntl.o diag_dci.o diff --git a/drivers/char/diag/diag_dci.c b/drivers/char/diag/diag_dci.c new file mode 100644 index 0000000000000000000000000000000000000000..5cbf888caa45ddd1304f0116fb7bdb75c64497f1 --- /dev/null +++ b/drivers/char/diag/diag_dci.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "diag_dci.h" + +unsigned int dci_max_reg = 100; +unsigned int dci_max_clients = 10; + +static void diag_smd_dci_send_req(int proc_num) +{ + void *buf = NULL; + smd_channel_t *smd_ch = NULL; + int i, r, found = 1; + int cmd_code_len = 1; + + if (driver->in_busy_dci) + return; + + if (proc_num == MODEM_PROC) { + buf = driver->buf_in_dci; + smd_ch = driver->ch_dci; + } + + 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 DCI sending pkt upto %d bytes", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: DCI pkt > %d bytes", MAX_IN_BUF_SIZE); + return; + } + } + if (buf && r > 0) { + smd_read(smd_ch, buf, r); + pr_debug("diag: data received ---\n"); + for (i = 0; i < r; i++) + pr_debug("\t %x \t", *(((unsigned char *)buf)+i)); + + if (*(uint8_t *)(buf+4) != DCI_CMD_CODE) + cmd_code_len = 4; /* delayed response */ + driver->write_ptr_dci->length = + (int)(*(uint16_t *)(buf+2)) - (4+cmd_code_len); + pr_debug("diag: len = %d\n", (int)(*(uint16_t *)(buf+2)) + - (4+cmd_code_len)); + /* look up DCI client with tag */ + for (i = 0; i < dci_max_reg; i++) { + if (driver->dci_tbl[i].tag == + *(int *)(buf+(4+cmd_code_len))) { + found = 0; + break; + } + } + if (found) + pr_alert("diag: No matching PID for DCI data\n"); + pr_debug("\n diag PID = %d", driver->dci_tbl[i].pid); + if (driver->dci_tbl[i].pid == 0) + pr_alert("diag: Receiving DCI process deleted\n"); + *(int *)(buf+4+cmd_code_len) = driver->dci_tbl[i].uid; + /* update len after adding UID */ + driver->write_ptr_dci->length = + driver->write_ptr_dci->length + 4; + pr_debug("diag: data receivd, wake process\n"); + driver->in_busy_dci = 1; + diag_update_sleeping_process(driver->dci_tbl[i].pid, + DCI_DATA_TYPE); + /* delete immediate response entry */ + if (driver->buf_in_dci[8+cmd_code_len] != 0x80) + driver->dci_tbl[i].pid = 0; + for (i = 0; i < dci_max_reg; i++) + if (driver->dci_tbl[i].pid != 0) + pr_debug("diag: PID = %d, UID = %d, tag = %d\n", + driver->dci_tbl[i].pid, driver->dci_tbl[i].uid, + driver->dci_tbl[i].tag); + pr_debug("diag: completed clearing table\n"); + } +} + +void diag_read_smd_dci_work_fn(struct work_struct *work) +{ + diag_smd_dci_send_req(MODEM_PROC); +} + +static void diag_smd_dci_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_dci_work)); +} + +static int diag_dci_probe(struct platform_device *pdev) +{ + int err = 0; + + if (pdev->id == SMD_APPS_MODEM) { + err = smd_open("DIAG_2", &driver->ch_dci, driver, + diag_smd_dci_notify); + if (err) + pr_err("diag: cannot open DCI port, Id = %d, err =" + " %d\n", pdev->id, err); + } + return err; +} + + +int diag_send_dci_pkt(struct diag_master_table entry, unsigned char *buf, + int len, int index) +{ + int i; + + /* remove UID from user space pkt before sending to peripheral */ + buf = buf + 4; + len = len - 4; + mutex_lock(&driver->dci_mutex); + /* prepare DCI packet */ + driver->apps_dci_buf[0] = CONTROL_CHAR; /* start */ + driver->apps_dci_buf[1] = 1; /* version */ + *(uint16_t *)(driver->apps_dci_buf + 2) = len + 4 + 1; /* length */ + driver->apps_dci_buf[4] = DCI_CMD_CODE; /* DCI ID */ + *(int *)(driver->apps_dci_buf + 5) = driver->dci_tbl[index].tag; + for (i = 0; i < len; i++) + driver->apps_dci_buf[i+9] = *(buf+i); + driver->apps_dci_buf[9+len] = CONTROL_CHAR; /* end */ + + if (entry.client_id == MODEM_PROC && driver->ch_dci) { + smd_write(driver->ch_dci, driver->apps_dci_buf, len + 10); + i = DIAG_DCI_NO_ERROR; + } else { + pr_alert("diag: check DCI channel\n"); + i = DIAG_DCI_SEND_DATA_FAIL; + } + mutex_unlock(&driver->dci_mutex); + return i; +} + +int diag_register_dci_transaction(int uid) +{ + int i, new_dci_client = 1, ret = -1; + + for (i = 0; i < dci_max_reg; i++) { + if (driver->dci_tbl[i].pid == current->tgid) { + new_dci_client = 0; + break; + } + } + mutex_lock(&driver->dci_mutex); + if (new_dci_client) + driver->num_dci_client++; + if (driver->num_dci_client > MAX_DCI_CLIENT) { + pr_info("diag: Max DCI Client limit reached\n"); + driver->num_dci_client--; + mutex_unlock(&driver->dci_mutex); + return ret; + } + /* Make an entry in kernel DCI table */ + driver->dci_tag++; + for (i = 0; i < dci_max_reg; i++) { + if (driver->dci_tbl[i].pid == 0) { + driver->dci_tbl[i].pid = current->tgid; + driver->dci_tbl[i].uid = uid; + driver->dci_tbl[i].tag = driver->dci_tag; + ret = i; + break; + } + } + mutex_unlock(&driver->dci_mutex); + return ret; +} + +int diag_process_dci_client(unsigned char *buf, int len) +{ + unsigned char *temp = buf; + uint16_t subsys_cmd_code; + int subsys_id, cmd_code, i, ret = -1, index = -1; + struct diag_master_table entry; + + /* enter this UID into kernel table and return index */ + index = diag_register_dci_transaction(*(int *)temp); + if (index < 0) { + pr_alert("diag: registering new DCI transaction failed\n"); + return DIAG_DCI_NO_REG; + } + temp += 4; + /* Check for registered peripheral and fwd pkt to apropriate proc */ + cmd_code = (int)(*(char *)buf); + temp++; + subsys_id = (int)(*(char *)temp); + temp++; + subsys_cmd_code = *(uint16_t *)temp; + temp += 2; + pr_debug("diag: %d %d %d", cmd_code, subsys_id, subsys_cmd_code); + for (i = 0; i < diag_max_reg; 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) { + ret = diag_send_dci_pkt(entry, buf, len, index); + } 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) { + ret = diag_send_dci_pkt(entry, buf, len, + index); + } + } else if (entry.cmd_code == 255 && + entry.subsys_id == 255) { + if (entry.cmd_code_lo <= + cmd_code && + entry. + cmd_code_hi >= cmd_code) { + ret = diag_send_dci_pkt(entry, buf, len, + index); + } + } + } + } + return ret; +} + +static int diag_dci_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int diag_dci_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops diag_dci_dev_pm_ops = { + .runtime_suspend = diag_dci_runtime_suspend, + .runtime_resume = diag_dci_runtime_resume, +}; + +struct platform_driver msm_diag_dci_driver = { + .probe = diag_dci_probe, + .driver = { + .name = "DIAG_2", + .owner = THIS_MODULE, + .pm = &diag_dci_dev_pm_ops, + }, +}; + +int diag_dci_init(void) +{ + int success = 0; + + driver->dci_tag = 0; + driver->dci_client_id = 0; + driver->num_dci_client = 0; + driver->in_busy_dci = 0; + mutex_init(&driver->dci_mutex); + if (driver->buf_in_dci == NULL) { + driver->buf_in_dci = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_dci == NULL) + goto err; + } + if (driver->write_ptr_dci == NULL) { + driver->write_ptr_dci = kzalloc( + sizeof(struct diag_write_device), GFP_KERNEL); + if (driver->write_ptr_dci == NULL) + goto err; + } + if (driver->dci_tbl == NULL) { + driver->dci_tbl = kzalloc(dci_max_reg * + sizeof(struct diag_dci_tbl), GFP_KERNEL); + if (driver->dci_tbl == NULL) + goto err; + } + if (driver->apps_dci_buf == NULL) { + driver->apps_dci_buf = kzalloc(APPS_BUF_SIZE, GFP_KERNEL); + if (driver->apps_dci_buf == NULL) + goto err; + } + success = platform_driver_register(&msm_diag_dci_driver); + if (success) { + pr_err("diag: Could not register DCI driver\n"); + goto err; + } + return DIAG_DCI_NO_ERROR; +err: + pr_err("diag: Could not initialize diag DCI buffers"); + kfree(driver->dci_tbl); + kfree(driver->apps_dci_buf); + kfree(driver->buf_in_dci); + kfree(driver->write_ptr_dci); + return DIAG_DCI_NO_REG; +} + +void diag_dci_exit(void) +{ + smd_close(driver->ch_dci); + driver->ch_dci = 0; + platform_driver_unregister(&msm_diag_dci_driver); + kfree(driver->dci_tbl); + kfree(driver->apps_dci_buf); + kfree(driver->buf_in_dci); + kfree(driver->write_ptr_dci); +} + diff --git a/drivers/char/diag/diag_dci.h b/drivers/char/diag/diag_dci.h new file mode 100644 index 0000000000000000000000000000000000000000..cc6e0cf79b1a1658b23491d8f336ff405326a692 --- /dev/null +++ b/drivers/char/diag/diag_dci.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef DIAG_DCI_H +#define DIAG_DCI_H +#define MAX_DCI_CLIENT 10 +#define DCI_CMD_CODE 0x93 + +extern unsigned int dci_max_reg; +extern unsigned int dci_max_clients; +struct diag_dci_tbl { + int pid; + int uid; + int tag; +}; + +#define DIAG_CON_APSS (0x0001) /* Bit mask for APSS */ +#define DIAG_CON_MPSS (0x0002) /* Bit mask for MPSS */ +#define DIAG_CON_LPASS (0x0004) /* Bit mask for LPASS */ +#define DIAG_CON_WCNSS (0x0008) /* Bit mask for WCNSS */ + +enum { + DIAG_DCI_NO_ERROR = 1001, /* No error */ + DIAG_DCI_NO_REG, /* Could not register */ + DIAG_DCI_NO_MEM, /* Failed memory allocation */ + DIAG_DCI_NOT_SUPPORTED, /* This particular client is not supported */ + DIAG_DCI_HUGE_PACKET, /* Request/Response Packet too huge */ + DIAG_DCI_SEND_DATA_FAIL,/* writing to kernel or peripheral fails */ + DIAG_DCI_TABLE_ERR /* Error dealing with registration tables */ +}; + +int diag_dci_init(void); +void diag_dci_exit(void); +void diag_read_smd_dci_work_fn(struct work_struct *); +int diag_process_dci_client(unsigned char *buf, int len); +int diag_send_dci_pkt(struct diag_master_table entry, unsigned char *buf, + int len, int index); +#endif diff --git a/drivers/char/diag/diagchar.h b/drivers/char/diag/diagchar.h new file mode 100644 index 0000000000000000000000000000000000000000..49d687d99a714f22515cc9caee1170fa1b31be88 --- /dev/null +++ b/drivers/char/diag/diagchar.h @@ -0,0 +1,283 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 APPS_BUF_SIZE 2000 +#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 HSIC_DATA 6 +#define MODEM_PROC 0 +#define APPS_PROC 1 +#define QDSP_PROC 2 +#define WCNSS_PROC 3 +#define MSG_MASK_SIZE 9500 +#define LOG_MASK_SIZE 8000 +#define EVENT_MASK_SIZE 1000 +#define USER_SPACE_DATA 8000 +#define PKT_SIZE 4096 +#define MAX_EQUIP_ID 15 +#define DIAG_CTRL_MSG_LOG_MASK 9 +#define DIAG_CTRL_MSG_EVENT_MASK 10 +#define DIAG_CTRL_MSG_F3_MASK 11 +#define CONTROL_CHAR 0x7E + +/* Maximum number of pkt reg supported at initialization*/ +extern unsigned int diag_max_reg; +extern unsigned int diag_threshold_reg; + +#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; + int polling_reg_flag; + struct diag_write_device *buf_tbl; + int use_device_tree; + /* DCI related variables */ + struct diag_dci_tbl *dci_tbl; + int dci_tag; + int dci_client_id; + struct mutex dci_mutex; + int num_dci_client; + unsigned char *apps_dci_buf; + int dci_state; + /* 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; + /* Buffers for masks */ + struct mutex diag_cntl_mutex; + struct diag_ctrl_event_mask *event_mask; + struct diag_ctrl_log_mask *log_mask; + struct diag_ctrl_msg_mask *msg_mask; + /* 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_1; + unsigned char *buf_in_wcnss_2; + unsigned char *buf_in_wcnss_cntl; + unsigned char *buf_in_dci; + unsigned char *usb_buf_out; + unsigned char *apps_rsp_buf; + unsigned char *user_space_data; + /* buffer for updating mask to peripherals */ + unsigned char *buf_msg_mask_update; + unsigned char *buf_log_mask_update; + unsigned char *buf_event_mask_update; + smd_channel_t *ch; + smd_channel_t *ch_cntl; + smd_channel_t *ch_dci; + 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_1; + int in_busy_wcnss_2; + int in_busy_dci; + 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; + struct workqueue_struct *diag_cntl_wq; + struct work_struct diag_modem_mask_update_work; + struct work_struct diag_qdsp_mask_update_work; + struct work_struct diag_wcnss_mask_update_work; + struct work_struct diag_read_smd_dci_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_1; + struct diag_request *write_ptr_wcnss_2; + struct diag_write_device *write_ptr_dci; + int logging_mode; + int mask_check; + 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_close_sdio_work; + struct diag_request *usb_read_mdm_ptr; + struct diag_request *write_ptr_mdm; +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + unsigned char *buf_in_hsic; + unsigned char *usb_buf_mdm_out; + int hsic_initialized; + int hsic_ch; + int hsic_device_enabled; + int hsic_device_opened; + int hsic_suspend; + int read_len_mdm; + int in_busy_hsic_read_on_device; + int in_busy_hsic_write_on_device; + int in_busy_hsic_write; + int in_busy_hsic_read; + int usb_mdm_connected; + struct usb_diag_ch *mdm_ch; + struct workqueue_struct *diag_hsic_wq; + struct work_struct diag_read_mdm_work; + struct work_struct diag_read_hsic_work; + struct work_struct diag_disconnect_work; + struct work_struct diag_usb_read_complete_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 0000000000000000000000000000000000000000..58a86760f797cdd2197fd12f9c5e1fb4ff3821fe --- /dev/null +++ b/drivers/char/diag/diagchar_core.c @@ -0,0 +1,1306 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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" +#include "diag_dci.h" +#ifdef CONFIG_DIAG_SDIO_PIPE +#include "diagfwd_sdio.h" +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE +#include "diagfwd_hsic.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 = 4096; /*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_reg = 600; +unsigned int diag_threshold_reg = 750; + +/* 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; + strlcpy(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] = 0x0; + 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; + } + + /* clean up any DCI registrations for this client + * This will specially help in case of ungraceful exit of any DCI client + * This call will remove any pending registrations of such client + */ + diagchar_ioctl(NULL, DIAG_IOCTL_DCI_DEINIT, 0); +#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(); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_cancel_hsic(); + diagfwd_connect_hsic(0); +#endif + } +#endif /* DIAG over USB */ + /* Delete the pkt response table entry for the exiting process */ + for (i = 0; i < diag_max_reg; 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; +} + +int diag_find_polling_reg(int i) +{ + uint16_t subsys_id, cmd_code_lo, cmd_code_hi; + + subsys_id = driver->table[i].subsys_id; + cmd_code_lo = driver->table[i].cmd_code_lo; + cmd_code_hi = driver->table[i].cmd_code_hi; + if (driver->table[i].cmd_code == 0x0C) + return 1; + else if (driver->table[i].cmd_code == 0xFF) { + if (subsys_id == 0x04 && cmd_code_hi == 0x0E && + cmd_code_lo == 0x0E) + return 1; + else if (subsys_id == 0x08 && cmd_code_hi == 0x02 && + cmd_code_lo == 0x02) + return 1; + else if (subsys_id == 0x32 && cmd_code_hi == 0x03 && + cmd_code_lo == 0x03) + return 1; + } + return 0; +} + +void diag_clear_reg(int proc_num) +{ + int i; + + mutex_lock(&driver->diagchar_mutex); + /* reset polling flag */ + driver->polling_reg_flag = 0; + for (i = 0; i < diag_max_reg; i++) { + if (driver->table[i].client_id == proc_num) { + driver->table[i].process_id = 0; + } + } + /* re-scan the registration table */ + for (i = 0; i < diag_max_reg; i++) { + if (diag_find_polling_reg(i) == 1) { + driver->polling_reg_flag = 1; + break; + } + } + mutex_unlock(&driver->diagchar_mutex); +} + +void diag_add_reg(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; + + /* check if incoming reg is polling & polling is yet not registered */ + if (driver->polling_reg_flag == 0) + if (diag_find_polling_reg(j) == 1) + driver->polling_reg_flag = 1; + 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; + uint16_t support_list = 0; + + 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_reg; i++) { + if (driver->table[i].process_id == 0) { + diag_add_reg(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_reg) { + /* Increase table size by amount required */ + diag_max_reg += pkt_params->count - + count_entries; + /* Make sure size doesnt go beyond threshold */ + if (diag_max_reg > diag_threshold_reg) { + diag_max_reg = diag_threshold_reg; + pr_info("diag: best case memory allocation\n"); + } + temp_buf = krealloc(driver->table, + diag_max_reg*sizeof(struct + diag_master_table), GFP_KERNEL); + if (!temp_buf) { + diag_max_reg -= 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_reg; j++) { + diag_add_reg(j, pkt_params->params, + &success, &count_entries); + if (pkt_params->count > count_entries) { + pkt_params->params++; + } else { + mutex_unlock(&driver->diagchar_mutex); + return success; + } + } + mutex_unlock(&driver->diagchar_mutex); + } 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_DCI_REG) { + if (driver->dci_state == DIAG_DCI_NO_REG) + return DIAG_DCI_NO_REG; + /* use the 'list' later on to notify user space */ + if (driver->num_dci_client >= MAX_DCI_CLIENT) + return DIAG_DCI_NO_REG; + mutex_lock(&driver->dci_mutex); + driver->num_dci_client++; + pr_debug("diag: id = %d\n", driver->dci_client_id); + driver->dci_client_id++; + mutex_unlock(&driver->dci_mutex); + return driver->dci_client_id; + } else if (iocmd == DIAG_IOCTL_DCI_DEINIT) { + success = -1; + /* Delete this process from DCI table */ + mutex_lock(&driver->dci_mutex); + for (i = 0; i < dci_max_reg; i++) { + if (driver->dci_tbl[i].pid == current->tgid) { + pr_debug("diag: delete %d\n", current->tgid); + driver->dci_tbl[i].pid = 0; + success = i; + } + } + /* if any registrations were deleted successfully OR a valid + client_id was sent in DEINIT call , then its DCI client */ + if (success >= 0 || ioarg) + driver->num_dci_client--; + driver->num_dci_client--; + mutex_unlock(&driver->dci_mutex); + for (i = 0; i < dci_max_reg; i++) + if (driver->dci_tbl[i].pid != 0) + pr_debug("diag: PID = %d, UID = %d, tag = %d\n", + driver->dci_tbl[i].pid, driver->dci_tbl[i].uid, driver->dci_tbl[i].tag); + pr_debug("diag: complete deleting registrations\n"); + return success; + } else if (iocmd == DIAG_IOCTL_DCI_SUPPORT) { + if (driver->ch_dci) + support_list = support_list | DIAG_CON_MPSS; + *(uint16_t *)ioarg = support_list; + return DIAG_DCI_NO_ERROR; + } 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; + if (driver->logging_mode == MEMORY_DEVICE_MODE) + driver->mask_check = 1; + if (driver->logging_mode == UART_MODE) { + driver->mask_check = 0; + driver->logging_mode = MEMORY_DEVICE_MODE; + } + 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; + driver->in_busy_wcnss_1 = 1; + driver->in_busy_wcnss_2 = 1; +#ifdef CONFIG_DIAG_SDIO_PIPE + driver->in_busy_sdio = 1; +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_disconnect_hsic(0); +#endif + } 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; + driver->in_busy_wcnss_1 = 0; + driver->in_busy_wcnss_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)); + if (driver->ch_wcnss) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_wcnss_work)); +#ifdef CONFIG_DIAG_SDIO_PIPE + driver->in_busy_sdio = 0; + /* Poll SDIO channel to check for data */ + if (driver->sdio_ch) + queue_work(driver->diag_sdio_wq, + &(driver->diag_read_sdio_work)); +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_connect_hsic(0); +#endif + } +#ifdef CONFIG_DIAG_OVER_USB + else if (temp == USB_MODE && driver->logging_mode + == NO_LOGGING_MODE) { + diagfwd_disconnect(); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_disconnect_hsic(0); +#endif + } else if (temp == NO_LOGGING_MODE && driver->logging_mode + == USB_MODE) { + diagfwd_connect(); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_connect_hsic(0); +#endif + } 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_1 = 0; + driver->in_busy_qdsp_2 = 0; + driver->in_busy_wcnss_1 = 0; + driver->in_busy_wcnss_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)); + if (driver->ch_wcnss) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_wcnss_work)); +#ifdef CONFIG_DIAG_SDIO_PIPE + driver->in_busy_sdio = 0; + /* Poll SDIO channel to check for data */ + if (driver->sdio_ch) + queue_work(driver->diag_sdio_wq, + &(driver->diag_read_sdio_work)); +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_cancel_hsic(); + diagfwd_connect_hsic(0); +#endif + } else if (temp == MEMORY_DEVICE_MODE && + driver->logging_mode == USB_MODE) { + diagfwd_connect(); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_cancel_hsic(); + diagfwd_connect_hsic(0); +#endif + } +#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] & USER_SPACE_LOG_TYPE) && (driver-> + logging_mode == MEMORY_DEVICE_MODE)) { + pr_debug("diag: process woken up\n"); + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & USER_SPACE_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 lpass 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 wncss data */ + if (driver->in_busy_wcnss_1 == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_wcnss_1->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> + buf_in_wcnss_1), + driver->write_ptr_wcnss_1->length); + driver->in_busy_wcnss_1 = 0; + } + if (driver->in_busy_wcnss_2 == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_wcnss_2->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> + buf_in_wcnss_2), + driver->write_ptr_wcnss_2->length); + driver->in_busy_wcnss_2 = 0; + } +#ifdef CONFIG_DIAG_SDIO_PIPE + /* copy 9K data over SDIO */ + if (driver->in_busy_sdio == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_mdm->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + *(driver->buf_in_sdio), + driver->write_ptr_mdm->length); + driver->in_busy_sdio = 0; + } +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + pr_debug("diag: Copy data to user space %d\n", + driver->in_busy_hsic_write_on_device); + if (driver->in_busy_hsic_write_on_device == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_mdm->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + *(driver->buf_in_hsic), + driver->write_ptr_mdm->length); + pr_debug("diag: data copied\n"); + /* call the write complete function */ + diagfwd_write_complete_hsic(); + } +#endif + /* copy number of data fields */ + COPY_USER_SPACE_OR_EXIT(buf+4, num_data, 4); + ret -= 4; + driver->data_ready[index] ^= USER_SPACE_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)); + if (driver->ch_wcnss) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_wcnss_work)); +#ifdef CONFIG_DIAG_SDIO_PIPE + if (driver->sdio_ch) + queue_work(driver->diag_sdio_wq, + &(driver->diag_read_sdio_work)); +#endif + APPEND_DEBUG('n'); + goto exit; + } else if (driver->data_ready[index] & USER_SPACE_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] ^= USER_SPACE_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; + } + + if (driver->data_ready[index] & DCI_DATA_TYPE) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & DCI_DATA_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + COPY_USER_SPACE_OR_EXIT(buf+4, + driver->write_ptr_dci->length, 4); + /* check delayed vs immediate response */ + if (*(uint8_t *)(driver->buf_in_dci+4) == DCI_CMD_CODE) + COPY_USER_SPACE_OR_EXIT(buf+8, + *(driver->buf_in_dci + 5), driver->write_ptr_dci->length); + else + COPY_USER_SPACE_OR_EXIT(buf+8, + *(driver->buf_in_dci + 8), driver->write_ptr_dci->length); + driver->in_busy_dci = 0; + driver->data_ready[index] ^= DCI_DATA_TYPE; + if (driver->ch_dci) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_dci_work)); + 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 == DCI_DATA_TYPE) { + err = copy_from_user(driver->user_space_data, buf + 4, + payload_size); + if (err) { + pr_alert("diag: copy failed for DCI data\n"); + return DIAG_DCI_SEND_DATA_FAIL; + } + err = diag_process_dci_client(driver->user_space_data, + payload_size); + return err; + } + if (pkt_type == USER_SPACE_LOG_TYPE) { + err = copy_from_user(driver->user_space_data, buf + 4, + payload_size); + /* Check masks for On-Device logging */ + if (driver->mask_check) { + if (!mask_request_validate(driver->user_space_data)) { + pr_alert("diag: mask request Invalid\n"); + return -EFAULT; + } + } + buf = buf + 4; +#ifdef DIAG_DEBUG + pr_debug("diag: user space data %d\n", payload_size); + for (i = 0; i < payload_size; i++) + pr_debug("\t %x", *((driver->user_space_data)+i)); +#endif +#ifdef CONFIG_DIAG_SDIO_PIPE + /* send masks to 9k too */ + if (driver->sdio_ch) { + wait_event_interruptible(driver->wait_q, + (sdio_write_avail(driver->sdio_ch) >= + payload_size)); + if (driver->sdio_ch && (payload_size > 0)) { + sdio_write(driver->sdio_ch, (void *) + (driver->user_space_data), payload_size); + } + } +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + /* send masks to 9k too */ + if (driver->hsic_ch && (payload_size > 0)) { + /* wait sending mask updates if HSIC ch not ready */ + if (driver->in_busy_hsic_write) + wait_event_interruptible(driver->wait_q, + (driver->in_busy_hsic_write != 1)); + driver->in_busy_hsic_write = 1; + driver->in_busy_hsic_read_on_device = 0; + err = diag_bridge_write(driver->user_space_data, + payload_size); + if (err) { + pr_err("diag: err sending mask to MDM: %d\n", + err); + /* + * If the error is recoverable, then clear + * the write flag, so we will resubmit a + * write on the next frame. Otherwise, don't + * resubmit a write on the next frame. + */ + if ((-ESHUTDOWN) != err) + driver->in_busy_hsic_write = 0; + } + } +#endif + /* send masks to 8k now */ + diag_process_hdlc((void *)(driver->user_space_data), + payload_size); + return 0; + } + + if (payload_size > itemsize) { + pr_err("diag: Dropping packet, packet payload size crosses" + "4KB limit. Current payload size %d\n", + payload_size); + driver->dropped_count++; + return -EBADMSG; + } + + 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[0]; + + if (packet_id == 0x4B) { + subsys_id = mask_buf[1]; + ss_cmd = *(uint16_t *)(mask_buf + 2); + /* 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; + driver->dci_state = DIAG_DCI_NO_ERROR; + 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; + driver->mask_check = 0; + 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); + INIT_WORK(&(driver->diag_read_smd_dci_work), + diag_read_smd_dci_work_fn); + diag_debugfs_init(); + diagfwd_init(); + diagfwd_cntl_init(); + driver->dci_state = diag_dci_init(); + diag_sdio_fn(INIT); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_hsic_init(); +#endif + 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: + diag_debugfs_cleanup(); + diagchar_cleanup(); + diagfwd_exit(); + diagfwd_cntl_exit(); + diag_sdio_fn(EXIT); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_hsic_exit(); +#endif + return -1; +} + +static void 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); +#ifdef CONFIG_DIAG_HSIC_PIPE + diagfwd_hsic_exit(); +#endif + diag_debugfs_cleanup(); + 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 0000000000000000000000000000000000000000..74dcb6bd249d76087c0e7c4d7fab01b4b25d7758 --- /dev/null +++ b/drivers/char/diag/diagchar_hdlc.c @@ -0,0 +1,224 @@ +/* Copyright (c) 2008-2009, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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" +#include "diagchar.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 0000000000000000000000000000000000000000..116c980e120498b3434556a2683758c2d336f92d --- /dev/null +++ b/drivers/char/diag/diagchar_hdlc.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2008-2009, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 ESC_MASK 0x20 + +#endif diff --git a/drivers/char/diag/diagfwd.c b/drivers/char/diag/diagfwd.c new file mode 100644 index 0000000000000000000000000000000000000000..4ac2643e0d8fff9110f7a93f6ed8eba396d64053 --- /dev/null +++ b/drivers/char/diag/diagfwd.c @@ -0,0 +1,2102 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_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 +#include "diag_dci.h" + +#define MODE_CMD 41 +#define RESET_ID 2 +#define ALL_EQUIP_ID 100 +#define ALL_SSID -1 +#define MAX_SSID_PER_RANGE 100 + +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; +smd_channel_t *ch_temp, *chqdsp_temp, *ch_wcnss_temp; +int diag_event_num_bytes; +int diag_event_config; +struct diag_send_desc_type send = { NULL, NULL, DIAG_STATE_START, 0 }; +struct diag_hdlc_dest_type enc = { NULL, NULL, 0 }; +struct mask_info { + int equip_id; + int num_items; + int index; +}; + +#define CREATE_MSG_MASK_TBL_ROW(XX) \ +do { \ + *(int *)(msg_mask_tbl_ptr) = MSG_SSID_ ## XX; \ + msg_mask_tbl_ptr += 4; \ + *(int *)(msg_mask_tbl_ptr) = MSG_SSID_ ## XX ## _LAST; \ + msg_mask_tbl_ptr += 4; \ + /* increment by MAX_SSID_PER_RANGE cells */ \ + msg_mask_tbl_ptr += MAX_SSID_PER_RANGE * sizeof(int); \ +} while (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 + APPS_BUF_SIZE - 1);\ + 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)); \ + driver->in_busy_1 = 1; \ + diag_device_write(driver->buf_in_1, MODEM_DATA, \ + driver->write_ptr_1); \ + memset(driver->apps_rsp_buf, '\0', APPS_BUF_SIZE); \ + } \ +} while (0) + +#define CHK_OVERFLOW(bufStart, start, end, length) \ +((bufStart <= start) && (end - start >= length)) ? 1 : 0 + +/* Determine if this device uses a device tree */ +#ifdef CONFIG_OF +static int has_device_tree(void) +{ + struct device_node *node; + + node = of_find_node_by_path("/"); + if (node) { + of_node_put(node); + return 1; + } + return 0; +} +#else +static int has_device_tree(void) +{ + return 0; +} +#endif + +int chk_config_get_id(void) +{ + /* For all Fusion targets, Modem will always be present */ + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) + return 0; + + if (driver->use_device_tree) { + if (machine_is_copper()) + return MSM8974_TOOLS_ID; + else + return 0; + } else { + switch (socinfo_get_msm_cpu()) { + case MSM_CPU_8X60: + return APQ8060_TOOLS_ID; + case MSM_CPU_8960: + return AO8960_TOOLS_ID; + case MSM_CPU_8064: + return APQ8064_TOOLS_ID; + case MSM_CPU_8930: + return MSM8930_TOOLS_ID; + case MSM_CPU_COPPER: + return MSM8974_TOOLS_ID; + case MSM_CPU_8625: + return MSM8625_TOOLS_ID; + default: + return 0; + } + } +} + +/* + * This will return TRUE for targets which support apps only mode and hence SSR. + * This applies to 8960 and newer targets. + */ +int chk_apps_only(void) +{ + if (driver->use_device_tree) + return 1; + + switch (socinfo_get_msm_cpu()) { + case MSM_CPU_8960: + case MSM_CPU_8064: + case MSM_CPU_8930: + case MSM_CPU_8627: + case MSM_CPU_9615: + case MSM_CPU_COPPER: + return 1; + default: + return 0; + } +} + +/* + * This will return TRUE for targets which support apps as master. + * Thus, SW DLOAD and Mode Reset are supported on apps processor. + * This applies to 8960 and newer targets. + */ +int chk_apps_master(void) +{ + if (driver->use_device_tree) + return 1; + else if (cpu_is_msm8960() || cpu_is_msm8930() || cpu_is_msm9615() || + cpu_is_apq8064() || cpu_is_msm8627()) + return 1; + else + return 0; +} + +int chk_polling_response(void) +{ + if (!(driver->polling_reg_flag) && chk_apps_master()) + /* + * If the apps processor is master and no other processor + * has registered to respond for polling + */ + return 1; + else if (!(driver->ch) && !(chk_apps_master())) + /* + * If the apps processor is not the master and the modem + * is not up + */ + return 1; + else + 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] |= USER_SPACE_LOG_TYPE; + pr_debug("diag: wake up logging process\n"); + 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_1 = 0; + driver->in_busy_wcnss_2 = 0; + queue_work(driver->diag_wq, &(driver-> + diag_read_smd_wcnss_work)); + } +#ifdef CONFIG_DIAG_SDIO_PIPE + else if (proc_num == SDIO_DATA) { + driver->in_busy_sdio = 0; + queue_work(driver->diag_sdio_wq, + &(driver->diag_read_sdio_work)); + } +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + else if (proc_num == HSIC_DATA) { + driver->in_busy_hsic_read = 0; + driver->in_busy_hsic_write_on_device = 0; + if (driver->hsic_ch) + queue_work(driver->diag_hsic_wq, + &(driver->diag_read_hsic_work)); + } +#endif + 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 sdio data " + "while USB write\n"); + } +#endif +#ifdef CONFIG_DIAG_HSIC_PIPE + else if (proc_num == HSIC_DATA) { + if (driver->hsic_device_enabled) { + write_ptr->buf = buf; + err = usb_diag_write(driver->mdm_ch, write_ptr); + } else + pr_err("diag: Incorrect hsic data " + "while USB write\n"); + } +#endif + APPEND_DEBUG('d'); + } +#endif /* DIAG OVER USB */ + return err; +} + +void __diag_smd_wcnss_send_req(void) +{ + void *buf = NULL; + int *in_busy_wcnss_ptr = NULL; + struct diag_request *write_ptr_wcnss = NULL; + + if (!driver->in_busy_wcnss_1) { + buf = driver->buf_in_wcnss_1; + write_ptr_wcnss = driver->write_ptr_wcnss_1; + in_busy_wcnss_ptr = &(driver->in_busy_wcnss_1); + } else if (!driver->in_busy_wcnss_2) { + buf = driver->buf_in_wcnss_2; + write_ptr_wcnss = driver->write_ptr_wcnss_2; + in_busy_wcnss_ptr = &(driver->in_busy_wcnss_2); + } + + 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; + pr_info("diag: F3 message mask table\n"); + 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 += MAX_SSID_PER_RANGE*4; + + } +#endif +} + +void diag_create_msg_mask_table(void) +{ + uint8_t *msg_mask_tbl_ptr = driver->msg_masks; + + CREATE_MSG_MASK_TBL_ROW(0); + CREATE_MSG_MASK_TBL_ROW(1); + CREATE_MSG_MASK_TBL_ROW(2); + CREATE_MSG_MASK_TBL_ROW(3); + CREATE_MSG_MASK_TBL_ROW(4); + CREATE_MSG_MASK_TBL_ROW(5); + CREATE_MSG_MASK_TBL_ROW(6); + CREATE_MSG_MASK_TBL_ROW(7); + CREATE_MSG_MASK_TBL_ROW(8); + CREATE_MSG_MASK_TBL_ROW(9); + CREATE_MSG_MASK_TBL_ROW(10); + CREATE_MSG_MASK_TBL_ROW(11); + CREATE_MSG_MASK_TBL_ROW(12); + CREATE_MSG_MASK_TBL_ROW(13); + CREATE_MSG_MASK_TBL_ROW(14); + CREATE_MSG_MASK_TBL_ROW(15); + CREATE_MSG_MASK_TBL_ROW(16); + CREATE_MSG_MASK_TBL_ROW(17); + CREATE_MSG_MASK_TBL_ROW(18); + CREATE_MSG_MASK_TBL_ROW(19); + CREATE_MSG_MASK_TBL_ROW(20); + CREATE_MSG_MASK_TBL_ROW(21); + CREATE_MSG_MASK_TBL_ROW(22); +} + +static void diag_set_msg_mask(int rt_mask) +{ + int first_ssid, last_ssid, i; + uint8_t *parse_ptr, *ptr = driver->msg_masks; + + mutex_lock(&driver->diagchar_mutex); + while (*(uint32_t *)(ptr + 4)) { + first_ssid = *(uint32_t *)ptr; + ptr += 4; + last_ssid = *(uint32_t *)ptr; + ptr += 4; + parse_ptr = ptr; + pr_debug("diag: updating range %d %d\n", first_ssid, last_ssid); + for (i = 0; i < last_ssid - first_ssid + 1; i++) { + *(int *)parse_ptr = rt_mask; + parse_ptr += 4; + } + ptr += MAX_SSID_PER_RANGE * 4; + } + mutex_unlock(&driver->diagchar_mutex); +} + +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))) { + pr_debug("diag: update ssid start %d," + " end %d\n", start, end); + 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 += MAX_SSID_PER_RANGE*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; + pr_debug("diag: adding NEW ssid start %d, end %d\n", + start, end); + 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(); + +} + +void diag_toggle_event_mask(int toggle) +{ + uint8_t *ptr = driver->event_masks; + + mutex_lock(&driver->diagchar_mutex); + if (toggle) + memset(ptr, 0xFF, EVENT_MASK_SIZE); + else + memset(ptr, 0, EVENT_MASK_SIZE); + mutex_unlock(&driver->diagchar_mutex); +} + +static void diag_update_event_mask(uint8_t *buf, int toggle, int num_bytes) +{ + 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_bytes)) + memcpy(ptr, temp , num_bytes); + else + printk(KERN_CRIT "Not enough buffer space " + "for EVENT_MASK\n"); + mutex_unlock(&driver->diagchar_mutex); +} + +static void diag_disable_log_mask(void) +{ + int i = 0; + struct mask_info *parse_ptr = (struct mask_info *)(driver->log_masks); + + pr_debug("diag: disable log masks\n"); + mutex_lock(&driver->diagchar_mutex); + for (i = 0; i < MAX_EQUIP_ID; i++) { + pr_debug("diag: equip id %d\n", parse_ptr->equip_id); + if (!(parse_ptr->equip_id)) /* Reached a null entry */ + break; + memset(driver->log_masks + parse_ptr->index, 0, + (parse_ptr->num_items + 7)/8); + parse_ptr++; + } + mutex_unlock(&driver->diagchar_mutex); +} + +static void diag_update_log_mask(int equip_id, uint8_t *buf, int num_items) +{ + uint8_t *temp = buf; + int i = 0; + unsigned char *ptr_data; + int offset = (sizeof(struct mask_info))*MAX_EQUIP_ID; + struct mask_info *ptr = (struct mask_info *)(driver->log_masks); + + pr_debug("diag: received equip id = %d\n", equip_id); + 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->num_items = num_items; + 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 + pr_err("diag: 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 data_type) +{ + 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] |= data_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, PKT_TYPE); + } else { + if (len > 0) { + if (entry.client_id == MODEM_PROC && driver->ch) { + if (chk_apps_master() && + (int)(*(char *)buf) == MODE_CMD) + if ((int)(*(char *)(buf+1)) == + RESET_ID) + return; + 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"); + } + } + } +} + +void diag_modem_mask_update_fn(struct work_struct *work) +{ + diag_send_msg_mask_update(driver->ch_cntl, ALL_SSID, + ALL_SSID, MODEM_PROC); + diag_send_log_mask_update(driver->ch_cntl, ALL_EQUIP_ID); + diag_send_event_mask_update(driver->ch_cntl, diag_event_num_bytes); +} + +void diag_qdsp_mask_update_fn(struct work_struct *work) +{ + diag_send_msg_mask_update(driver->chqdsp_cntl, ALL_SSID, + ALL_SSID, QDSP_PROC); + diag_send_log_mask_update(driver->chqdsp_cntl, ALL_EQUIP_ID); + diag_send_event_mask_update(driver->chqdsp_cntl, diag_event_num_bytes); +} + +void diag_wcnss_mask_update_fn(struct work_struct *work) +{ + diag_send_msg_mask_update(driver->ch_wcnss_cntl, ALL_SSID, + ALL_SSID, WCNSS_PROC); + diag_send_log_mask_update(driver->ch_wcnss_cntl, ALL_EQUIP_ID); + diag_send_event_mask_update(driver->ch_wcnss_cntl, + diag_event_num_bytes); +} + +void diag_send_log_mask_update(smd_channel_t *ch, int equip_id) +{ + void *buf = driver->buf_log_mask_update; + int header_size = sizeof(struct diag_ctrl_log_mask); + struct mask_info *ptr = (struct mask_info *)driver->log_masks; + int i, size, wr_size = -ENOMEM, retry_count = 0, timer; + + mutex_lock(&driver->diag_cntl_mutex); + for (i = 0; i < MAX_EQUIP_ID; i++) { + size = (ptr->num_items+7)/8; + /* reached null entry */ + if ((ptr->equip_id == 0) && (ptr->index == 0)) + break; + driver->log_mask->cmd_type = DIAG_CTRL_MSG_LOG_MASK; + driver->log_mask->num_items = ptr->num_items; + driver->log_mask->data_len = 11 + size; + driver->log_mask->stream_id = 1; /* 2, if dual stream */ + driver->log_mask->status = 3; /* status for valid mask */ + driver->log_mask->equip_id = ptr->equip_id; + driver->log_mask->log_mask_size = size; + /* send only desired update, NOT ALL */ + if (equip_id == ALL_EQUIP_ID || equip_id == + driver->log_mask->equip_id) { + memcpy(buf, driver->log_mask, header_size); + memcpy(buf+header_size, driver->log_masks+ptr->index, + size); + if (ch) { + while (retry_count < 3) { + wr_size = smd_write(ch, buf, + header_size + size); + if (wr_size == -ENOMEM) { + retry_count++; + for (timer = 0; timer < 5; + timer++) + udelay(2000); + } else + break; + } + if (wr_size != header_size + size) + pr_err("diag: log mask update failed" + " %d, tried %d", wr_size, header_size + size); + else + pr_debug("diag: updated log equip ID %d" + ",len %d\n", driver->log_mask->equip_id, + driver->log_mask->log_mask_size); + } else + pr_err("diag: ch not valid for log update\n"); + } + ptr++; + } + mutex_unlock(&driver->diag_cntl_mutex); +} + +void diag_send_event_mask_update(smd_channel_t *ch, int num_bytes) +{ + void *buf = driver->buf_event_mask_update; + int header_size = sizeof(struct diag_ctrl_event_mask); + int wr_size = -ENOMEM, retry_count = 0, timer; + + mutex_lock(&driver->diag_cntl_mutex); + if (num_bytes == 0) { + pr_debug("diag: event mask not set yet, so no update\n"); + mutex_unlock(&driver->diag_cntl_mutex); + return; + } + /* send event mask update */ + driver->event_mask->cmd_type = DIAG_CTRL_MSG_EVENT_MASK; + driver->event_mask->data_len = 7 + num_bytes; + driver->event_mask->stream_id = 1; /* 2, if dual stream */ + driver->event_mask->status = 3; /* status for valid mask */ + driver->event_mask->event_config = diag_event_config; /* event config */ + driver->event_mask->event_mask_size = num_bytes; + memcpy(buf, driver->event_mask, header_size); + memcpy(buf+header_size, driver->event_masks, num_bytes); + if (ch) { + while (retry_count < 3) { + wr_size = smd_write(ch, buf, header_size + num_bytes); + if (wr_size == -ENOMEM) { + retry_count++; + for (timer = 0; timer < 5; timer++) + udelay(2000); + } else + break; + } + if (wr_size != header_size + num_bytes) + pr_err("diag: error writing event mask %d, tried %d\n", + wr_size, header_size + num_bytes); + } else + pr_err("diag: ch not valid for event update\n"); + mutex_unlock(&driver->diag_cntl_mutex); +} + +void diag_send_msg_mask_update(smd_channel_t *ch, int updated_ssid_first, + int updated_ssid_last, int proc) +{ + void *buf = driver->buf_msg_mask_update; + int first, last, size = -ENOMEM, retry_count = 0, timer; + int header_size = sizeof(struct diag_ctrl_msg_mask); + uint8_t *ptr = driver->msg_masks; + + mutex_lock(&driver->diag_cntl_mutex); + while (*(uint32_t *)(ptr + 4)) { + first = *(uint32_t *)ptr; + ptr += 4; + last = *(uint32_t *)ptr; + ptr += 4; + if ((updated_ssid_first >= first && updated_ssid_last <= last) + || (updated_ssid_first == ALL_SSID)) { + /* send f3 mask update */ + driver->msg_mask->cmd_type = DIAG_CTRL_MSG_F3_MASK; + driver->msg_mask->msg_mask_size = last - first + 1; + driver->msg_mask->data_len = 11 + + 4 * (driver->msg_mask->msg_mask_size); + driver->msg_mask->stream_id = 1; /* 2, if dual stream */ + driver->msg_mask->status = 3; /* status valid mask */ + driver->msg_mask->msg_mode = 0; /* Legcay mode */ + driver->msg_mask->ssid_first = first; + driver->msg_mask->ssid_last = last; + memcpy(buf, driver->msg_mask, header_size); + memcpy(buf+header_size, ptr, + 4 * (driver->msg_mask->msg_mask_size)); + if (ch) { + while (retry_count < 3) { + size = smd_write(ch, buf, header_size + + 4*(driver->msg_mask->msg_mask_size)); + if (size == -ENOMEM) { + retry_count++; + for (timer = 0; timer < 5; + timer++) + udelay(2000); + } else + break; + } + if (size != header_size + + 4*(driver->msg_mask->msg_mask_size)) + pr_err("diag: proc %d, msg mask update " + "fail %d, tried %d\n", proc, size, + header_size + 4*(driver->msg_mask->msg_mask_size)); + else + pr_debug("diag: sending mask update for" + "ssid first %d, last %d on PROC %d\n", first, last, proc); + } else + pr_err("diag: proc %d, ch invalid msg mask" + "update\n", proc); + } + ptr += MAX_SSID_PER_RANGE*4; + } + mutex_unlock(&driver->diag_cntl_mutex); +} + +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, rt_mask; + unsigned char *temp = buf; + int data_type; +#if defined(CONFIG_DIAG_OVER_USB) + int payload_length; + unsigned char *ptr; +#endif + + /* Set log masks */ + 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) + if (chk_apps_only()) { + 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+i); + if (driver->ch_cntl) + diag_send_log_mask_update(driver->ch_cntl, + *(int *)buf); + if (driver->chqdsp_cntl) + diag_send_log_mask_update(driver->chqdsp_cntl, + *(int *)buf); + if (driver->ch_wcnss_cntl) + diag_send_log_mask_update(driver->ch_wcnss_cntl, + *(int *)buf); + ENCODE_RSP_AND_SEND(12 + payload_length - 1); + return 0; + } else + buf = temp; +#endif + } /* Disable log masks */ + else if (*buf == 0x73 && *(int *)(buf+4) == 0) { + /* Disable mask for each log code */ + diag_disable_log_mask(); + diag_update_userspace_clients(LOG_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + if (chk_apps_only()) { + driver->apps_rsp_buf[0] = 0x73; + driver->apps_rsp_buf[1] = 0x0; + driver->apps_rsp_buf[2] = 0x0; + driver->apps_rsp_buf[3] = 0x0; + *(int *)(driver->apps_rsp_buf + 4) = 0x0; + if (driver->ch_cntl) + diag_send_log_mask_update(driver->ch_cntl, + ALL_EQUIP_ID); + if (driver->chqdsp_cntl) + diag_send_log_mask_update(driver->chqdsp_cntl, + ALL_EQUIP_ID); + if (driver->ch_wcnss_cntl) + diag_send_log_mask_update(driver->ch_wcnss_cntl, + ALL_EQUIP_ID); + ENCODE_RSP_AND_SEND(7); + return 0; + } +#endif + } /* Set runtime 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); + pr_debug("diag: received mask update for ssid_first = %d," + " ssid_last = %d", ssid_first, ssid_last); + diag_update_msg_mask(ssid_first, ssid_last , buf + 8); + diag_update_userspace_clients(MSG_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + if (chk_apps_only()) { + for (i = 0; i < 8 + ssid_range; i++) + *(driver->apps_rsp_buf + i) = *(buf+i); + *(driver->apps_rsp_buf + 6) = 0x1; + if (driver->ch_cntl) + diag_send_msg_mask_update(driver->ch_cntl, + ssid_first, ssid_last, MODEM_PROC); + if (driver->chqdsp_cntl) + diag_send_msg_mask_update(driver->chqdsp_cntl, + ssid_first, ssid_last, QDSP_PROC); + if (driver->ch_wcnss_cntl) + diag_send_msg_mask_update(driver->ch_wcnss_cntl, + ssid_first, ssid_last, WCNSS_PROC); + ENCODE_RSP_AND_SEND(8 + ssid_range - 1); + return 0; + } else + buf = temp; +#endif + } /* Set ALL runtime message mask */ + else if ((*buf == 0x7d) && (*(buf+1) == 0x5)) { + rt_mask = *(int *)(buf + 4); + diag_set_msg_mask(rt_mask); + diag_update_userspace_clients(MSG_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + if (chk_apps_only()) { + driver->apps_rsp_buf[0] = 0x7d; /* cmd_code */ + driver->apps_rsp_buf[1] = 0x5; /* set subcommand */ + driver->apps_rsp_buf[2] = 1; /* success */ + driver->apps_rsp_buf[3] = 0; /* rsvd */ + *(int *)(driver->apps_rsp_buf + 4) = rt_mask; + /* send msg mask update to peripheral */ + if (driver->ch_cntl) + diag_send_msg_mask_update(driver->ch_cntl, + ALL_SSID, ALL_SSID, MODEM_PROC); + if (driver->chqdsp_cntl) + diag_send_msg_mask_update(driver->chqdsp_cntl, + ALL_SSID, ALL_SSID, QDSP_PROC); + if (driver->ch_wcnss_cntl) + diag_send_msg_mask_update(driver->ch_wcnss_cntl, + ALL_SSID, ALL_SSID, WCNSS_PROC); + ENCODE_RSP_AND_SEND(7); + return 0; + } else + buf = temp; +#endif + } else if (*buf == 0x82) { /* event mask change */ + buf += 4; + diag_event_num_bytes = (*(uint16_t *)buf)/8+1; + diag_update_event_mask(buf, 1, (*(uint16_t *)buf)/8+1); + diag_update_userspace_clients(EVENT_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + if (chk_apps_only()) { + driver->apps_rsp_buf[0] = 0x82; + 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; + memcpy(driver->apps_rsp_buf+6, driver->event_masks, + EVENT_LAST_ID/8+1); + if (driver->ch_cntl) + diag_send_event_mask_update(driver->ch_cntl, + diag_event_num_bytes); + if (driver->chqdsp_cntl) + diag_send_event_mask_update(driver->chqdsp_cntl, + diag_event_num_bytes); + if (driver->ch_wcnss_cntl) + diag_send_event_mask_update( + driver->ch_wcnss_cntl, diag_event_num_bytes); + ENCODE_RSP_AND_SEND(6 + EVENT_LAST_ID/8); + return 0; + } else + buf = temp; +#endif + } else if (*buf == 0x60) { + diag_event_config = *(buf+1); + diag_toggle_event_mask(*(buf+1)); + diag_update_userspace_clients(EVENT_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + if (chk_apps_only()) { + driver->apps_rsp_buf[0] = 0x60; + driver->apps_rsp_buf[1] = 0x0; + driver->apps_rsp_buf[2] = 0x0; + if (driver->ch_cntl) + diag_send_event_mask_update(driver->ch_cntl, + diag_event_num_bytes); + if (driver->chqdsp_cntl) + diag_send_event_mask_update(driver->chqdsp_cntl, + diag_event_num_bytes); + if (driver->ch_wcnss_cntl) + diag_send_event_mask_update( + driver->ch_wcnss_cntl, diag_event_num_bytes); + ENCODE_RSP_AND_SEND(2); + return 0; + } +#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 (chk_apps_master() && 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_reg; 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; + } + } + } + } +#if defined(CONFIG_DIAG_OVER_USB) + /* Check for the command/respond msg for the maximum packet length */ + if ((*buf == 0x4b) && (*(buf+1) == 0x12) && + (*(uint16_t *)(buf+2) == 0x0055)) { + for (i = 0; i < 4; i++) + *(driver->apps_rsp_buf+i) = *(buf+i); + *(uint32_t *)(driver->apps_rsp_buf+4) = PKT_SIZE; + ENCODE_RSP_AND_SEND(7); + return 0; + } + /* Check for Apps Only & get event mask request */ + else if (!(driver->ch) && chk_apps_only() && *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 */ + else if (!(driver->ch) && chk_apps_only() + && (*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_apps_only() + && (*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; + *(uint16_t *)(driver->apps_rsp_buf + 84) = MSG_SSID_19; + *(uint16_t *)(driver->apps_rsp_buf + 86) = MSG_SSID_19_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 88) = MSG_SSID_20; + *(uint16_t *)(driver->apps_rsp_buf + 90) = MSG_SSID_20_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 92) = MSG_SSID_21; + *(uint16_t *)(driver->apps_rsp_buf + 94) = MSG_SSID_21_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 96) = MSG_SSID_22; + *(uint16_t *)(driver->apps_rsp_buf + 98) = MSG_SSID_22_LAST; + ENCODE_RSP_AND_SEND(99); + return 0; + } + /* Check for Apps Only Respond to Get Subsys Build mask */ + else if (!(driver->ch) && chk_apps_only() + && (*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; + case MSG_SSID_19: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_19[i/4]; + break; + case MSG_SSID_20: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_20[i/4]; + break; + case MSG_SSID_21: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_21[i/4]; + break; + case MSG_SSID_22: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_22[i/4]; + break; + } + ENCODE_RSP_AND_SEND(8 + ssid_range - 1); + return 0; + } + /* Check for download command */ + else if ((cpu_is_msm8x60() || chk_apps_master()) && (*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 polling for Apps only DIAG */ + else if ((*buf == 0x4b) && (*(buf+1) == 0x32) && + (*(buf+2) == 0x03)) { + /* If no one has registered for polling */ + if (chk_polling_response()) { + /* Respond to polling for Apps only DIAG */ + 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; + } + } + /* Check for ID for NO MODEM present */ + else if (chk_polling_response()) { + /* respond to 0x0 command */ + 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; + + if (index > 490) { + pr_err("diag: error response too huge, aborting\n"); + return; + } + 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_apps_only()) { + diag_send_error_rsp(hdlc.dest_idx); + type = 0; + } + /* implies this packet is NOT meant for apps */ + if (!(driver->ch) && type == 1) { + if (chk_apps_only()) { + 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 +/* 2+1 for modem ; 2 for LPASS ; 1 for WCNSS */ +#define N_LEGACY_WRITE (driver->poolsize + 6) +#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_1 = 0; + driver->in_busy_wcnss_2 = 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 SMD CNTL channels to check for data */ + diag_smd_cntl_notify(NULL, SMD_EVENT_DATA); + diag_smd_qdsp_cntl_notify(NULL, SMD_EVENT_DATA); + diag_smd_wcnss_cntl_notify(NULL, SMD_EVENT_DATA); + /* 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->debug_flag = 1; + usb_diag_free_req(driver->legacy_ch); + if (driver->logging_mode == USB_MODE) { + 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 = 1; + driver->in_busy_wcnss_2 = 1; + } +#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 == driver->buf_in_wcnss_1) { + driver->in_busy_wcnss_1 = 0; + APPEND_DEBUG('r'); + queue_work(driver->diag_wq, + &(driver->diag_read_smd_wcnss_work)); + } else if (buf == driver->buf_in_wcnss_2) { + driver->in_busy_wcnss_2 = 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) +{ + if (event == SMD_EVENT_CLOSE) { + pr_info("diag: clean modem registration\n"); + diag_clear_reg(MODEM_PROC); + driver->ch = 0; + return; + } else if (event == SMD_EVENT_OPEN) { + driver->ch = ch_temp; + } + 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) +{ + if (event == SMD_EVENT_CLOSE) { + pr_info("diag: clean lpass registration\n"); + diag_clear_reg(QDSP_PROC); + driver->chqdsp = 0; + return; + } else if (event == SMD_EVENT_OPEN) { + driver->chqdsp = chqdsp_temp; + } + queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work)); +} +#endif + +static void diag_smd_wcnss_notify(void *ctxt, unsigned event) +{ + if (event == SMD_EVENT_CLOSE) { + pr_info("diag: clean wcnss registration\n"); + diag_clear_reg(WCNSS_PROC); + driver->ch_wcnss = 0; + return; + } else if (event == SMD_EVENT_OPEN) { + driver->ch_wcnss = ch_wcnss_temp; + } + 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); + ch_temp = driver->ch; + } +#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); + chqdsp_temp = driver->chqdsp; + } +#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); + ch_wcnss_temp = driver->ch_wcnss; + } + 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; + driver->use_device_tree = has_device_tree(); + mutex_init(&driver->diag_cntl_mutex); + + if (driver->event_mask == NULL) { + driver->event_mask = kzalloc(sizeof( + struct diag_ctrl_event_mask), GFP_KERNEL); + if (driver->event_mask == NULL) + goto err; + kmemleak_not_leak(driver->event_mask); + } + if (driver->msg_mask == NULL) { + driver->msg_mask = kzalloc(sizeof( + struct diag_ctrl_msg_mask), GFP_KERNEL); + if (driver->msg_mask == NULL) + goto err; + kmemleak_not_leak(driver->msg_mask); + } + if (driver->log_mask == NULL) { + driver->log_mask = kzalloc(sizeof( + struct diag_ctrl_log_mask), GFP_KERNEL); + if (driver->log_mask == NULL) + goto err; + kmemleak_not_leak(driver->log_mask); + } + if (driver->buf_in_1 == NULL) { + driver->buf_in_1 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_1 == NULL) + goto err; + kmemleak_not_leak(driver->buf_in_1); + } + if (driver->buf_in_2 == NULL) { + driver->buf_in_2 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_2 == NULL) + goto err; + kmemleak_not_leak(driver->buf_in_2); + } + 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; + kmemleak_not_leak(driver->buf_in_qdsp_1); + } + 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; + kmemleak_not_leak(driver->buf_in_qdsp_2); + } + if (driver->buf_in_wcnss_1 == NULL) { + driver->buf_in_wcnss_1 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_wcnss_1 == NULL) + goto err; + kmemleak_not_leak(driver->buf_in_wcnss_1); + } + if (driver->buf_in_wcnss_2 == NULL) { + driver->buf_in_wcnss_2 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_wcnss_2 == NULL) + goto err; + kmemleak_not_leak(driver->buf_in_wcnss_2); + } + + if (driver->buf_msg_mask_update == NULL) { + driver->buf_msg_mask_update = kzalloc(APPS_BUF_SIZE, + GFP_KERNEL); + if (driver->buf_msg_mask_update == NULL) + goto err; + kmemleak_not_leak(driver->buf_msg_mask_update); + } + if (driver->buf_log_mask_update == NULL) { + driver->buf_log_mask_update = kzalloc(APPS_BUF_SIZE, + GFP_KERNEL); + if (driver->buf_log_mask_update == NULL) + goto err; + kmemleak_not_leak(driver->buf_log_mask_update); + } + if (driver->buf_event_mask_update == NULL) { + driver->buf_event_mask_update = kzalloc(APPS_BUF_SIZE, + GFP_KERNEL); + if (driver->buf_event_mask_update == NULL) + goto err; + kmemleak_not_leak(driver->buf_event_mask_update); + } + if (driver->usb_buf_out == NULL && + (driver->usb_buf_out = kzalloc(USB_MAX_OUT_BUF, + GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->usb_buf_out); + if (driver->hdlc_buf == NULL + && (driver->hdlc_buf = kzalloc(HDLC_MAX, GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->hdlc_buf); + if (driver->user_space_data == NULL) + driver->user_space_data = kzalloc(USER_SPACE_DATA, GFP_KERNEL); + if (driver->user_space_data == NULL) + goto err; + kmemleak_not_leak(driver->user_space_data); + if (driver->msg_masks == NULL + && (driver->msg_masks = kzalloc(MSG_MASK_SIZE, + GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->msg_masks); + diag_create_msg_mask_table(); + diag_event_num_bytes = 0; + if (driver->log_masks == NULL && + (driver->log_masks = kzalloc(LOG_MASK_SIZE, GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->log_masks); + driver->log_masks_length = (sizeof(struct mask_info))*MAX_EQUIP_ID; + if (driver->event_masks == NULL && + (driver->event_masks = kzalloc(EVENT_MASK_SIZE, + GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->event_masks); + if (driver->client_map == NULL && + (driver->client_map = kzalloc + ((driver->num_clients) * sizeof(struct diag_client_map), + GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->client_map); + 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; + kmemleak_not_leak(driver->buf_tbl); + if (driver->data_ready == NULL && + (driver->data_ready = kzalloc(driver->num_clients * sizeof(int) + , GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->data_ready); + if (driver->table == NULL && + (driver->table = kzalloc(diag_max_reg* + sizeof(struct diag_master_table), + GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->table); + 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; + kmemleak_not_leak(driver->write_ptr_1); + } + 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; + kmemleak_not_leak(driver->write_ptr_2); + } + 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; + kmemleak_not_leak(driver->write_ptr_qdsp_1); + } + 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; + kmemleak_not_leak(driver->write_ptr_qdsp_2); + } + if (driver->write_ptr_wcnss_1 == NULL) { + driver->write_ptr_wcnss_1 = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_wcnss_1 == NULL) + goto err; + kmemleak_not_leak(driver->write_ptr_wcnss_1); + } + if (driver->write_ptr_wcnss_2 == NULL) { + driver->write_ptr_wcnss_2 = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_wcnss_2 == NULL) + goto err; + kmemleak_not_leak(driver->write_ptr_wcnss_2); + } + + 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; + kmemleak_not_leak(driver->usb_read_ptr); + } + if (driver->pkt_buf == NULL && + (driver->pkt_buf = kzalloc(PKT_SIZE, + GFP_KERNEL)) == NULL) + goto err; + kmemleak_not_leak(driver->pkt_buf); + if (driver->apps_rsp_buf == NULL) { + driver->apps_rsp_buf = kzalloc(APPS_BUF_SIZE, GFP_KERNEL); + if (driver->apps_rsp_buf == NULL) + goto err; + kmemleak_not_leak(driver->apps_rsp_buf); + } + 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); + INIT_WORK(&(driver->diag_modem_mask_update_work), + diag_modem_mask_update_fn); + INIT_WORK(&(driver->diag_qdsp_mask_update_work), + diag_qdsp_mask_update_fn); + INIT_WORK(&(driver->diag_wcnss_mask_update_work), + diag_wcnss_mask_update_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->event_mask); + kfree(driver->log_mask); + kfree(driver->msg_mask); + 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_1); + kfree(driver->buf_in_wcnss_2); + kfree(driver->buf_msg_mask_update); + kfree(driver->buf_log_mask_update); + kfree(driver->buf_event_mask_update); + 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_1); + kfree(driver->write_ptr_wcnss_2); + kfree(driver->usb_read_ptr); + kfree(driver->apps_rsp_buf); + kfree(driver->user_space_data); + 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(&msm_diag_dci_driver); + platform_driver_unregister(&diag_smd_lite_driver); + kfree(driver->event_mask); + kfree(driver->log_mask); + kfree(driver->msg_mask); + 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_1); + kfree(driver->buf_in_wcnss_2); + kfree(driver->buf_msg_mask_update); + kfree(driver->buf_log_mask_update); + kfree(driver->buf_event_mask_update); + 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_1); + kfree(driver->write_ptr_wcnss_2); + kfree(driver->usb_read_ptr); + kfree(driver->apps_rsp_buf); + kfree(driver->user_space_data); + destroy_workqueue(driver->diag_wq); +} diff --git a/drivers/char/diag/diagfwd.h b/drivers/char/diag/diagfwd.h new file mode 100644 index 0000000000000000000000000000000000000000..f5de2ac676e3b3226a09943f07f993a5e8f41c77 --- /dev/null +++ b/drivers/char/diag/diagfwd.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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[]); +void diag_clear_reg(int); +int chk_config_get_id(void); +int chk_apps_only(void); +int chk_apps_master(void); +int chk_polling_response(void); +void diag_send_event_mask_update(smd_channel_t *, int num_bytes); +void diag_send_msg_mask_update(smd_channel_t *, int ssid_first, + int ssid_last, int proc); +void diag_send_log_mask_update(smd_channel_t *, int); +void diag_update_sleeping_process(int process_id, int data_type); +/* 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]; +extern int diag_event_num_bytes; +extern struct platform_driver msm_diag_dci_driver; +#endif diff --git a/drivers/char/diag/diagfwd_cntl.c b/drivers/char/diag/diagfwd_cntl.c new file mode 100644 index 0000000000000000000000000000000000000000..de1a5b5415064ce962f20bcd123b98581ff317e6 --- /dev/null +++ b/drivers/char/diag/diagfwd_cntl.c @@ -0,0 +1,604 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include "diagchar.h" +#include "diagfwd.h" +#include "diagfwd_cntl.h" +#ifdef CONFIG_DEBUG_FS +#include +#endif + +#define HDR_SIZ 8 + +void diag_smd_cntl_notify(void *ctxt, unsigned event) +{ + int r1, r2; + + if (!(driver->ch_cntl)) + return; + + switch (event) { + case SMD_EVENT_DATA: + r1 = smd_read_avail(driver->ch_cntl); + r2 = smd_cur_packet_size(driver->ch_cntl); + if (r1 > 0 && r1 == r2) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_cntl_work)); + else + pr_debug("diag: incomplete pkt on Modem CNTL ch\n"); + break; + case SMD_EVENT_OPEN: + queue_work(driver->diag_cntl_wq, + &(driver->diag_modem_mask_update_work)); + break; + } +} + +void diag_smd_qdsp_cntl_notify(void *ctxt, unsigned event) +{ + int r1, r2; + + if (!(driver->chqdsp_cntl)) + return; + + switch (event) { + case SMD_EVENT_DATA: + r1 = smd_read_avail(driver->chqdsp_cntl); + r2 = smd_cur_packet_size(driver->chqdsp_cntl); + if (r1 > 0 && r1 == r2) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_qdsp_cntl_work)); + else + pr_debug("diag: incomplete pkt on LPASS CNTL ch\n"); + break; + case SMD_EVENT_OPEN: + queue_work(driver->diag_cntl_wq, + &(driver->diag_qdsp_mask_update_work)); + break; + } +} + +void diag_smd_wcnss_cntl_notify(void *ctxt, unsigned event) +{ + int r1, r2; + + if (!(driver->ch_wcnss_cntl)) + return; + + switch (event) { + case SMD_EVENT_DATA: + r1 = smd_read_avail(driver->ch_wcnss_cntl); + r2 = smd_cur_packet_size(driver->ch_wcnss_cntl); + if (r1 > 0 && r1 == r2) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_wcnss_cntl_work)); + else + pr_debug("diag: incomplete pkt on WCNSS CNTL ch\n"); + break; + case SMD_EVENT_OPEN: + queue_work(driver->diag_cntl_wq, + &(driver->diag_wcnss_mask_update_work)); + break; + } +} + +static void diag_smd_cntl_send_req(int proc_num) +{ + int data_len = 0, type = -1, count_bytes = 0, j, r, flag = 0; + 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 (pkt_params == NULL) { + pr_alert("diag: Memory allocation failure\n"); + return; + } + + 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) { + kfree(pkt_params); + 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); + if (type < DIAG_CTRL_MSG_REG || + type > DIAG_CTRL_MSG_F3_MASK_V2) { + pr_alert("diag: Invalid Msg type %d proc %d", + type, proc_num); + break; + } + if (data_len < 0 || data_len > r) { + pr_alert("diag: Invalid data len %d proc %d", + data_len, proc_num); + break; + } + 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); + if (temp == NULL) { + pr_alert("diag: Memory alloc fail\n"); + kfree(pkt_params); + return; + } + 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; + flag = 1; + diagchar_ioctl(NULL, DIAG_IOCTL_COMMAND_REG, + (unsigned long)pkt_params); + kfree(temp); + } + buf = buf + HDR_SIZ + data_len; + } + } + kfree(pkt_params); + if (flag) { + /* Poll SMD CNTL channels to check for data */ + if (proc_num == MODEM_PROC) + diag_smd_cntl_notify(NULL, SMD_EVENT_DATA); + else if (proc_num == QDSP_PROC) + diag_smd_qdsp_cntl_notify(NULL, SMD_EVENT_DATA); + else if (proc_num == WCNSS_PROC) + diag_smd_wcnss_cntl_notify(NULL, SMD_EVENT_DATA); + } +} + +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 int diag_smd_cntl_probe(struct platform_device *pdev) +{ + int r = 0; + + /* open control ports only on 8960 & newer targets */ + if (chk_apps_only()) { + 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) +{ + driver->polling_reg_flag = 0; + driver->diag_cntl_wq = create_singlethread_workqueue("diag_cntl_wq"); + if (driver->buf_in_cntl == NULL) { + driver->buf_in_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_cntl == NULL) + goto err; + kmemleak_not_leak(driver->buf_in_cntl); + } + 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; + kmemleak_not_leak(driver->buf_in_qdsp_cntl); + } + 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; + kmemleak_not_leak(driver->buf_in_wcnss_cntl); + } + 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); + if (driver->diag_cntl_wq) + destroy_workqueue(driver->diag_cntl_wq); +} + +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; + destroy_workqueue(driver->diag_cntl_wq); + 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); +} + +#ifdef CONFIG_DEBUG_FS +#define DEBUG_BUF_SIZE 4096 +static struct dentry *diag_dbgfs_dent; +static int diag_dbgfs_table_index; + +static ssize_t diag_dbgfs_read_status(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + int ret; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) { + pr_err("diag: %s, Error allocating memory\n", __func__); + return -ENOMEM; + } + + ret = scnprintf(buf, DEBUG_BUF_SIZE, + "modem ch: 0x%x\n" + "lpass ch: 0x%x\n" + "riva ch: 0x%x\n" + "dci ch: 0x%x\n" + "modem cntl_ch: 0x%x\n" + "lpass cntl_ch: 0x%x\n" + "riva cntl_ch: 0x%x\n" + "CPU Tools id: %d\n" + "Apps only: %d\n" + "Apps master: %d\n" + "Check Polling Response: %d\n" + "polling_reg_flag: %d\n" + "uses device tree: %d\n" + "in_busy_1: %d\n" + "in_busy_2: %d\n" + "in_busy_qdsp_1: %d\n" + "in_busy_qdsp_2: %d\n" + "in_busy_wcnss_1: %d\n" + "in_busy_wcnss_2: %d\n" + "in_busy_dci: %d\n", + (unsigned int)driver->ch, + (unsigned int)driver->chqdsp, + (unsigned int)driver->ch_wcnss, + (unsigned int)driver->ch_dci, + (unsigned int)driver->ch_cntl, + (unsigned int)driver->chqdsp_cntl, + (unsigned int)driver->ch_wcnss_cntl, + chk_config_get_id(), + chk_apps_only(), + chk_apps_master(), + chk_polling_response(), + driver->polling_reg_flag, + driver->use_device_tree, + driver->in_busy_1, + driver->in_busy_2, + driver->in_busy_qdsp_1, + driver->in_busy_qdsp_2, + driver->in_busy_wcnss_1, + driver->in_busy_wcnss_2, + driver->in_busy_dci); + +#ifdef CONFIG_DIAG_OVER_USB + ret += scnprintf(buf+ret, DEBUG_BUF_SIZE, + "usb_connected: %d\n", + driver->usb_connected); +#endif + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +static ssize_t diag_dbgfs_read_workpending(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) { + pr_err("diag: %s, Error allocating memory\n", __func__); + return -ENOMEM; + } + + ret = scnprintf(buf, DEBUG_BUF_SIZE, + "Pending status for work_stucts:\n" + "diag_drain_work: %d\n" + "diag_read_smd_work: %d\n" + "diag_read_smd_cntl_work: %d\n" + "diag_read_smd_qdsp_work: %d\n" + "diag_read_smd_qdsp_cntl_work: %d\n" + "diag_read_smd_wcnss_work: %d\n" + "diag_read_smd_wcnss_cntl_work: %d\n" + "diag_modem_mask_update_work: %d\n" + "diag_qdsp_mask_update_work: %d\n" + "diag_wcnss_mask_update_work: %d\n" + "diag_read_smd_dci_work: %d\n", + work_pending(&(driver->diag_drain_work)), + work_pending(&(driver->diag_read_smd_work)), + work_pending(&(driver->diag_read_smd_cntl_work)), + work_pending(&(driver->diag_read_smd_qdsp_work)), + work_pending(&(driver->diag_read_smd_qdsp_cntl_work)), + work_pending(&(driver->diag_read_smd_wcnss_work)), + work_pending(&(driver->diag_read_smd_wcnss_cntl_work)), + work_pending(&(driver->diag_modem_mask_update_work)), + work_pending(&(driver->diag_qdsp_mask_update_work)), + work_pending(&(driver->diag_wcnss_mask_update_work)), + work_pending(&(driver->diag_read_smd_dci_work))); + +#ifdef CONFIG_DIAG_OVER_USB + ret += scnprintf(buf+ret, DEBUG_BUF_SIZE, + "diag_proc_hdlc_work: %d\n" + "diag_read_work: %d\n", + work_pending(&(driver->diag_proc_hdlc_work)), + work_pending(&(driver->diag_read_work))); +#endif + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +static ssize_t diag_dbgfs_read_table(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + int ret = 0; + int i; + int bytes_remaining; + int bytes_in_buffer = 0; + int bytes_written; + int buf_size = (DEBUG_BUF_SIZE < count) ? DEBUG_BUF_SIZE : count; + + if (diag_dbgfs_table_index >= diag_max_reg) { + /* Done. Reset to prepare for future requests */ + diag_dbgfs_table_index = 0; + return 0; + } + + buf = kzalloc(sizeof(char) * buf_size, GFP_KERNEL); + if (!buf) { + pr_err("diag: %s, Error allocating memory\n", __func__); + return -ENOMEM; + } + + bytes_remaining = buf_size; + for (i = diag_dbgfs_table_index; i < diag_max_reg; i++) { + /* Do not process empty entries in the table */ + if (driver->table[i].process_id == 0) + continue; + + bytes_written = scnprintf(buf+bytes_in_buffer, bytes_remaining, + "i: %3d, cmd_code: %4x, subsys_id: %4x, " + "client: %2d, cmd_code_lo: %4x, " + "cmd_code_hi: %4x, process_id: %5d\n", + i, + driver->table[i].cmd_code, + driver->table[i].subsys_id, + driver->table[i].client_id, + driver->table[i].cmd_code_lo, + driver->table[i].cmd_code_hi, + driver->table[i].process_id); + + bytes_in_buffer += bytes_written; + + /* Check if there is room to add another table entry */ + bytes_remaining = buf_size - bytes_in_buffer; + if (bytes_remaining < bytes_written) + break; + } + diag_dbgfs_table_index = i; + + *ppos = 0; + ret = simple_read_from_buffer(ubuf, count, ppos, buf, bytes_in_buffer); + + kfree(buf); + return ret; +} + +#ifdef CONFIG_DIAG_HSIC_PIPE +static ssize_t diag_dbgfs_read_hsic(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + int ret; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) { + pr_err("diag: %s, Error allocating memory\n", __func__); + return -ENOMEM; + } + + ret = scnprintf(buf, DEBUG_BUF_SIZE, + "hsic initialized: %d\n" + "hsic ch: %d\n" + "hsic enabled: %d\n" + "hsic_opened: %d\n" + "hisc_suspend: %d\n" + "in_busy_hsic_read_on_mdm: %d\n" + "in_busy_hsic_write_on_mdm: %d\n" + "in_busy_hsic_write: %d\n" + "in_busy_hsic_read: %d\n" + "usb_mdm_connected: %d\n" + "diag_read_mdm_work: %d\n" + "diag_read_hsic_work: %d\n" + "diag_disconnect_work: %d\n" + "diag_usb_read_complete_work: %d\n", + driver->hsic_initialized, + driver->hsic_ch, + driver->hsic_device_enabled, + driver->hsic_device_opened, + driver->hsic_suspend, + driver->in_busy_hsic_read_on_device, + driver->in_busy_hsic_write_on_device, + driver->in_busy_hsic_write, + driver->in_busy_hsic_read, + driver->usb_mdm_connected, + work_pending(&(driver->diag_read_mdm_work)), + work_pending(&(driver->diag_read_hsic_work)), + work_pending(&(driver->diag_disconnect_work)), + work_pending(&(driver->diag_usb_read_complete_work))); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +const struct file_operations diag_dbgfs_hsic_ops = { + .read = diag_dbgfs_read_hsic, +}; +#endif + +const struct file_operations diag_dbgfs_status_ops = { + .read = diag_dbgfs_read_status, +}; + +const struct file_operations diag_dbgfs_table_ops = { + .read = diag_dbgfs_read_table, +}; + +const struct file_operations diag_dbgfs_workpending_ops = { + .read = diag_dbgfs_read_workpending, +}; + +void diag_debugfs_init(void) +{ + diag_dbgfs_dent = debugfs_create_dir("diag", 0); + if (IS_ERR(diag_dbgfs_dent)) + return; + + debugfs_create_file("status", 0444, diag_dbgfs_dent, 0, + &diag_dbgfs_status_ops); + + debugfs_create_file("table", 0444, diag_dbgfs_dent, 0, + &diag_dbgfs_table_ops); + + debugfs_create_file("work_pending", 0444, diag_dbgfs_dent, 0, + &diag_dbgfs_workpending_ops); + +#ifdef CONFIG_DIAG_HSIC_PIPE + debugfs_create_file("hsic", 0444, diag_dbgfs_dent, 0, + &diag_dbgfs_hsic_ops); +#endif + + diag_dbgfs_table_index = 0; +} + +void diag_debugfs_cleanup(void) +{ + if (diag_dbgfs_dent) { + debugfs_remove_recursive(diag_dbgfs_dent); + diag_dbgfs_dent = NULL; + } +} +#else +void diag_debugfs_init(void) { } +void diag_debugfs_cleanup(void) { } +#endif diff --git a/drivers/char/diag/diagfwd_cntl.h b/drivers/char/diag/diagfwd_cntl.h new file mode 100644 index 0000000000000000000000000000000000000000..743ddc15df01c78d18168f1563b240e9056b15c6 --- /dev/null +++ b/drivers/char/diag/diagfwd_cntl.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +/* Message registration commands */ +#define DIAG_CTRL_MSG_REG 1 +/* Message passing for DTR events */ +#define DIAG_CTRL_MSG_DTR 2 +/* Control Diag sleep vote, buffering etc */ +#define DIAG_CTRL_MSG_DIAGMODE 3 +/* Diag data based on "light" diag mask */ +#define DIAG_CTRL_MSG_DIAGDATA 4 +/* Send diag internal feature mask 'diag_int_feature_mask' */ +#define DIAG_CTRL_MSG_FEATURE 8 +/* Send Diag log mask for a particular equip id */ +#define DIAG_CTRL_MSG_EQUIP_LOG_MASK 9 +/* Send Diag event mask */ +#define DIAG_CTRL_MSG_EVENT_MASK_V2 10 +/* Send Diag F3 mask */ +#define DIAG_CTRL_MSG_F3_MASK_V2 11 + +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; +}; + +struct diag_ctrl_event_mask { + uint32_t cmd_type; + uint32_t data_len; + uint8_t stream_id; + uint8_t status; + uint8_t event_config; + uint32_t event_mask_size; + /* Copy event mask here */ +} __packed; + +struct diag_ctrl_log_mask { + uint32_t cmd_type; + uint32_t data_len; + uint8_t stream_id; + uint8_t status; + uint8_t equip_id; + uint32_t num_items; /* Last log code for this equip_id */ + uint32_t log_mask_size; /* Size of log mask stored in log_mask[] */ + /* Copy log mask here */ +} __packed; + +struct diag_ctrl_msg_mask { + uint32_t cmd_type; + uint32_t data_len; + uint8_t stream_id; + uint8_t status; + uint8_t msg_mode; + uint16_t ssid_first; /* Start of range of supported SSIDs */ + uint16_t ssid_last; /* Last SSID in range */ + uint32_t msg_mask_size; /* ssid_last - ssid_first + 1 */ + /* Copy msg mask here */ +} __packed; + +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 *); +void diag_smd_cntl_notify(void *ctxt, unsigned event); +void diag_smd_qdsp_cntl_notify(void *ctxt, unsigned event); +void diag_smd_wcnss_cntl_notify(void *ctxt, unsigned event); + +void diag_debugfs_init(void); +void diag_debugfs_cleanup(void); + +#endif diff --git a/drivers/char/diag/diagfwd_hsic.c b/drivers/char/diag/diagfwd_hsic.c new file mode 100644 index 0000000000000000000000000000000000000000..a3c6f26baed61448dde7109424c2708d0bf6db10 --- /dev/null +++ b/drivers/char/diag/diagfwd_hsic.c @@ -0,0 +1,604 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_hsic.h" + +static void diag_read_hsic_work_fn(struct work_struct *work) +{ + if (!driver->hsic_ch) { + pr_err("DIAG in %s: driver->hsic_ch == 0\n", __func__); + return; + } + + /* If there is no hsic data being read from the hsic and there + * is no hsic data being written to the device + */ + if (!driver->in_busy_hsic_read && + !driver->in_busy_hsic_write_on_device) { + /* + * Initiate the read from the hsic. The hsic read is + * asynchronous. Once the read is complete the read + * callback function will be called. + */ + int err; + driver->in_busy_hsic_read = 1; + APPEND_DEBUG('i'); + pr_debug("diag: read from HSIC\n"); + err = diag_bridge_read((char *)driver->buf_in_hsic, + IN_BUF_SIZE); + if (err) { + pr_err("DIAG: Error initiating HSIC read, err: %d\n", + err); + /* + * If the error is recoverable, then clear + * the read flag, so we will resubmit a + * read on the next frame. Otherwise, don't + * resubmit a read on the next frame. + */ + if ((-ESHUTDOWN) != err) + driver->in_busy_hsic_read = 0; + } + } + + /* + * If for some reason there was no hsic data, set up + * the next read + */ + if (!driver->in_busy_hsic_read) + queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work); +} + +static void diag_hsic_read_complete_callback(void *ctxt, char *buf, + int buf_size, int actual_size) +{ + /* The read of the data from the HSIC bridge is complete */ + driver->in_busy_hsic_read = 0; + + if (!driver->hsic_ch) { + pr_err("DIAG in %s: driver->hsic_ch == 0\n", __func__); + return; + } + + APPEND_DEBUG('j'); + if (actual_size > 0) { + if (!buf) { + pr_err("Out of diagmem for HSIC\n"); + } else { + driver->write_ptr_mdm->length = actual_size; + /* + * Set flag to denote hsic data is currently + * being written to the usb mdm channel. + * driver->buf_in_hsic was given to + * diag_bridge_read(), so buf here should be + * driver->buf_in_hsic + */ + driver->in_busy_hsic_write_on_device = 1; + pr_debug("diag: write to device\n"); + diag_device_write((void *)buf, HSIC_DATA, + driver->write_ptr_mdm); + } + } else { + pr_debug("%s: actual_size: %d\n", __func__, actual_size); + } + + /* + * If for some reason there was no hsic data to write to the + * mdm channel, set up another read + */ + if (!driver->in_busy_hsic_write_on_device && ((driver->logging_mode + == MEMORY_DEVICE_MODE) || (driver->usb_mdm_connected && + !driver->hsic_suspend))) + queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work); +} + +static void diag_hsic_write_complete_callback(void *ctxt, char *buf, + int buf_size, int actual_size) +{ + /* The write of the data to the HSIC bridge is complete */ + driver->in_busy_hsic_write = 0; + + if (!driver->hsic_ch) { + pr_err("DIAG in %s: driver->hsic_ch == 0\n", __func__); + return; + } + + if (actual_size < 0) + pr_err("DIAG in %s: actual_size: %d\n", __func__, actual_size); + + if (driver->usb_mdm_connected) + queue_work(driver->diag_hsic_wq, &driver->diag_read_mdm_work); +} + +static int diag_hsic_suspend(void *ctxt) +{ + pr_debug("diag: hsic_suspend\n"); + if (driver->in_busy_hsic_write) + return -EBUSY; + + /* Don't allow suspend if in MEMORY_DEVICE_MODE */ + if (driver->logging_mode == MEMORY_DEVICE_MODE) + return -EBUSY; + + driver->hsic_suspend = 1; + + return 0; +} + +static void diag_hsic_resume(void *ctxt) +{ + pr_debug("diag: hsic_resume\n"); + driver->hsic_suspend = 0; + + if (!driver->in_busy_hsic_write_on_device && (driver->logging_mode + == MEMORY_DEVICE_MODE || driver->usb_mdm_connected)) + queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work); +} + +static struct diag_bridge_ops hsic_diag_bridge_ops = { + .ctxt = NULL, + .read_complete_cb = diag_hsic_read_complete_callback, + .write_complete_cb = diag_hsic_write_complete_callback, + .suspend = diag_hsic_suspend, + .resume = diag_hsic_resume, +}; + +static int diag_hsic_close(void) +{ + if (driver->hsic_device_enabled) { + driver->hsic_ch = 0; + if (driver->hsic_device_opened) { + driver->hsic_device_opened = 0; + diag_bridge_close(); + } + pr_debug("diag: in %s: closed successfully\n", __func__); + } else { + pr_debug("diag: in %s: already closed\n", __func__); + } + + return 0; +} + +/* diagfwd_cancel_hsic is called to cancel outstanding read/writes */ +int diagfwd_cancel_hsic(void) +{ + int err; + + if (driver->hsic_device_enabled) { + if (driver->hsic_device_opened) { + driver->hsic_ch = 0; + driver->hsic_device_opened = 0; + diag_bridge_close(); + err = diag_bridge_open(&hsic_diag_bridge_ops); + if (err) { + pr_err("DIAG: HSIC channel open error: %d\n", + err); + } else { + pr_debug("DIAG: opened HSIC channel\n"); + driver->hsic_device_opened = 1; + driver->hsic_ch = 1; + } + } + } + + return 0; +} + +/* diagfwd_connect_hsic is called when the USB mdm channel is connected */ +int diagfwd_connect_hsic(int process_cable) +{ + int err; + + pr_debug("DIAG in %s\n", __func__); + + /* If the usb cable is being connected */ + if (process_cable) { + err = usb_diag_alloc_req(driver->mdm_ch, N_MDM_WRITE, + N_MDM_READ); + if (err) + pr_err("DIAG: unable to alloc USB req on mdm" + " ch err:%d\n", err); + + driver->usb_mdm_connected = 1; + } + + driver->in_busy_hsic_write_on_device = 0; + driver->in_busy_hsic_read_on_device = 0; + driver->in_busy_hsic_write = 0; + driver->in_busy_hsic_read = 0; + + /* If the hsic (diag_bridge) platform device is not open */ + if (driver->hsic_device_enabled) { + if (!driver->hsic_device_opened) { + err = diag_bridge_open(&hsic_diag_bridge_ops); + if (err) { + pr_err("DIAG: HSIC channel open error: %d\n", + err); + } else { + pr_debug("DIAG: opened HSIC channel\n"); + driver->hsic_device_opened = 1; + } + } else { + pr_debug("DIAG: HSIC channel already open\n"); + } + + /* + * Turn on communication over usb mdm and hsic, if the hsic + * device driver is enabled and opened + */ + if (driver->hsic_device_opened) + driver->hsic_ch = 1; + + /* Poll USB mdm channel to check for data */ + if (driver->logging_mode == USB_MODE) + queue_work(driver->diag_hsic_wq, + &driver->diag_read_mdm_work); + + /* Poll HSIC channel to check for data */ + queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work); + } else { + /* The hsic device driver has not yet been enabled */ + pr_info("DIAG: HSIC channel not yet enabled\n"); + } + + return 0; +} + +/* + * diagfwd_disconnect_hsic is called when the USB mdm channel + * is disconnected + */ +int diagfwd_disconnect_hsic(int process_cable) +{ + pr_debug("DIAG in %s\n", __func__); + + /* If the usb cable is being disconnected */ + if (process_cable) { + driver->usb_mdm_connected = 0; + usb_diag_free_req(driver->mdm_ch); + } + + if (driver->logging_mode != MEMORY_DEVICE_MODE) { + driver->in_busy_hsic_write_on_device = 1; + driver->in_busy_hsic_read_on_device = 1; + driver->in_busy_hsic_write = 1; + driver->in_busy_hsic_read = 1; + /* Turn off communication over usb mdm and hsic */ + return diag_hsic_close(); + } + return 0; +} + +/* + * diagfwd_write_complete_hsic is called after the asynchronous + * usb_diag_write() on mdm channel is complete + */ +int diagfwd_write_complete_hsic(void) +{ + /* + * Clear flag to denote that the write of the hsic data on the + * usb mdm channel is complete + */ + driver->in_busy_hsic_write_on_device = 0; + if (!driver->hsic_ch) { + pr_err("DIAG in %s: driver->hsic_ch == 0\n", __func__); + return 0; + } + + APPEND_DEBUG('q'); + + /* Read data from the hsic */ + queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work); + + return 0; +} + +/* Called after the asychronous usb_diag_read() on mdm channel is complete */ +static int diagfwd_read_complete_hsic(struct diag_request *diag_read_ptr) +{ + /* The read of the usb driver on the mdm (not hsic) has completed */ + driver->in_busy_hsic_read_on_device = 0; + driver->read_len_mdm = diag_read_ptr->actual; + + if (!driver->hsic_ch) { + pr_err("DIAG in %s: driver->hsic_ch == 0\n", __func__); + return 0; + } + + /* + * The read of the usb driver on the mdm channel has completed. + * If there is no write on the hsic in progress, check if the + * read has data to pass on to the hsic. If so, pass the usb + * mdm data on to the hsic. + */ + if (!driver->in_busy_hsic_write && driver->usb_buf_mdm_out && + (driver->read_len_mdm > 0)) { + + /* + * Initiate the hsic write. The hsic write is + * asynchronous. When complete the write + * complete callback function will be called + */ + int err; + driver->in_busy_hsic_write = 1; + err = diag_bridge_write(driver->usb_buf_mdm_out, + driver->read_len_mdm); + if (err) { + pr_err("DIAG: mdm data on hsic write err: %d\n", err); + /* + * If the error is recoverable, then clear + * the write flag, so we will resubmit a + * write on the next frame. Otherwise, don't + * resubmit a write on the next frame. + */ + if ((-ESHUTDOWN) != err) + driver->in_busy_hsic_write = 0; + } + } + + /* + * If there is no write of the usb mdm data on the + * hsic channel + */ + if (!driver->in_busy_hsic_write) + queue_work(driver->diag_hsic_wq, &driver->diag_read_mdm_work); + + return 0; +} + +static void diagfwd_hsic_notifier(void *priv, unsigned event, + struct diag_request *d_req) +{ + switch (event) { + case USB_DIAG_CONNECT: + diagfwd_connect_hsic(1); + break; + case USB_DIAG_DISCONNECT: + queue_work(driver->diag_hsic_wq, &driver->diag_disconnect_work); + break; + case USB_DIAG_READ_DONE: + queue_work(driver->diag_hsic_wq, + &driver->diag_usb_read_complete_work); + break; + case USB_DIAG_WRITE_DONE: + diagfwd_write_complete_hsic(); + break; + default: + pr_err("DIAG in %s: Unknown event from USB diag:%u\n", + __func__, event); + break; + } +} + +static void diag_usb_read_complete_fn(struct work_struct *w) +{ + diagfwd_read_complete_hsic(driver->usb_read_mdm_ptr); +} + +static void diag_disconnect_work_fn(struct work_struct *w) +{ + diagfwd_disconnect_hsic(1); +} + +static void diag_read_mdm_work_fn(struct work_struct *work) +{ + if (!driver->hsic_ch) { + pr_err("DIAG in %s: driver->hsic_ch == 0\n", __func__); + return; + } + + /* + * If there is no data being read from the usb mdm channel + * and there is no mdm channel data currently being written + * to the hsic + */ + if (!driver->in_busy_hsic_read_on_device && + !driver->in_busy_hsic_write) { + APPEND_DEBUG('x'); + + /* Setup the next read from usb mdm channel */ + driver->in_busy_hsic_read_on_device = 1; + 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'); + } + + /* + * If for some reason there was no mdm channel read initiated, + * queue up the reading of data from the mdm channel + */ + if (!driver->in_busy_hsic_read_on_device) + queue_work(driver->diag_hsic_wq, &driver->diag_read_mdm_work); +} + +static int diag_hsic_probe(struct platform_device *pdev) +{ + int err = 0; + pr_debug("diag: in %s\n", __func__); + if (!driver->hsic_device_enabled) { + driver->read_len_mdm = 0; + if (driver->buf_in_hsic == NULL) + driver->buf_in_hsic = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_hsic == 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; +#ifdef CONFIG_DIAG_OVER_USB + INIT_WORK(&(driver->diag_read_mdm_work), diag_read_mdm_work_fn); +#endif + INIT_WORK(&(driver->diag_read_hsic_work), + diag_read_hsic_work_fn); + driver->hsic_device_enabled = 1; + } + + /* + * The probe function was called after the usb was connected + * on the legacy channel OR ODL is turned on. Communication over usb + * mdm and hsic needs to be turned on. + */ + if (driver->usb_mdm_connected || (driver->logging_mode == + MEMORY_DEVICE_MODE)) { + /* The hsic (diag_bridge) platform device driver is enabled */ + err = diag_bridge_open(&hsic_diag_bridge_ops); + if (err) { + pr_err("diag: could not open HSIC, err: %d\n", err); + driver->hsic_device_opened = 0; + return err; + } + + pr_info("diag: opened HSIC channel\n"); + driver->hsic_device_opened = 1; + driver->hsic_ch = 1; + driver->in_busy_hsic_write_on_device = 0; + driver->in_busy_hsic_read_on_device = 0; + driver->in_busy_hsic_write = 0; + driver->in_busy_hsic_read = 0; + + if (driver->usb_mdm_connected) { + /* Poll USB mdm channel to check for data */ + queue_work(driver->diag_hsic_wq, + &driver->diag_read_mdm_work); + } + + /* Poll HSIC channel to check for data */ + queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work); + } + + return err; +err: + pr_err("DIAG could not initialize buf for HSIC\n"); + kfree(driver->buf_in_hsic); + kfree(driver->usb_buf_mdm_out); + kfree(driver->write_ptr_mdm); + kfree(driver->usb_read_mdm_ptr); + if (driver->diag_hsic_wq) + destroy_workqueue(driver->diag_hsic_wq); + + return -ENOMEM; +} + +static int diag_hsic_remove(struct platform_device *pdev) +{ + pr_debug("DIAG: %s called\n", __func__); + diag_hsic_close(); + return 0; +} + +static int diagfwd_hsic_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int diagfwd_hsic_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops diagfwd_hsic_dev_pm_ops = { + .runtime_suspend = diagfwd_hsic_runtime_suspend, + .runtime_resume = diagfwd_hsic_runtime_resume, +}; + +static struct platform_driver msm_hsic_ch_driver = { + .probe = diag_hsic_probe, + .remove = diag_hsic_remove, + .driver = { + .name = "diag_bridge", + .owner = THIS_MODULE, + .pm = &diagfwd_hsic_dev_pm_ops, + }, +}; + +void diagfwd_hsic_init(void) +{ + int ret; + + pr_debug("DIAG in %s\n", __func__); + + driver->diag_hsic_wq = create_singlethread_workqueue("diag_hsic_wq"); + INIT_WORK(&(driver->diag_disconnect_work), diag_disconnect_work_fn); + INIT_WORK(&(driver->diag_usb_read_complete_work), + diag_usb_read_complete_fn); + +#ifdef CONFIG_DIAG_OVER_USB + driver->mdm_ch = usb_diag_open(DIAG_MDM, driver, diagfwd_hsic_notifier); + if (IS_ERR(driver->mdm_ch)) { + pr_err("DIAG Unable to open USB diag MDM channel\n"); + goto err; + } +#endif + ret = platform_driver_register(&msm_hsic_ch_driver); + if (ret) + pr_err("DIAG could not register HSIC device, ret: %d\n", ret); + else + driver->hsic_initialized = 1; + + return; +err: + pr_err("DIAG could not initialize for HSIC execution\n"); +} + +void diagfwd_hsic_exit(void) +{ + pr_debug("DIAG in %s\n", __func__); + + if (driver->hsic_initialized) + diag_hsic_close(); + +#ifdef CONFIG_DIAG_OVER_USB + if (driver->usb_mdm_connected) + usb_diag_free_req(driver->mdm_ch); +#endif + platform_driver_unregister(&msm_hsic_ch_driver); +#ifdef CONFIG_DIAG_OVER_USB + usb_diag_close(driver->mdm_ch); +#endif + kfree(driver->buf_in_hsic); + kfree(driver->usb_buf_mdm_out); + kfree(driver->write_ptr_mdm); + kfree(driver->usb_read_mdm_ptr); + destroy_workqueue(driver->diag_hsic_wq); + + driver->hsic_device_enabled = 0; +} diff --git a/drivers/char/diag/diagfwd_hsic.h b/drivers/char/diag/diagfwd_hsic.h new file mode 100644 index 0000000000000000000000000000000000000000..a47ee26bcfb0c140e200c8447ff8a17f240dd633 --- /dev/null +++ b/drivers/char/diag/diagfwd_hsic.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_HSIC_H +#define DIAGFWD_HSIC_H + +#include +#define N_MDM_WRITE 1 /* Upgrade to 2 with ping pong buffer */ +#define N_MDM_READ 1 + +void __init diagfwd_hsic_init(void); +int diagfwd_connect_hsic(int); +int diagfwd_disconnect_hsic(int); +int diagfwd_write_complete_hsic(void); +int diagfwd_cancel_hsic(void); +void diagfwd_hsic_exit(void); + +#endif diff --git a/drivers/char/diag/diagfwd_sdio.c b/drivers/char/diag/diagfwd_sdio.c new file mode 100644 index 0000000000000000000000000000000000000000..a145c0665e76e60b2a8a9f0acc21e8abefb4f353 --- /dev/null +++ b/drivers/char/diag/diagfwd_sdio.c @@ -0,0 +1,296 @@ +/* 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" + " packets more than %d bytes\n", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: SDIO sending" + " in packets more than %d bytes\n", 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); + if (((!driver->usb_connected) && (driver-> + logging_mode == USB_MODE)) || (driver-> + logging_mode == NO_LOGGING_MODE)) { + /* Drop the diag payload */ + driver->in_busy_sdio = 0; + return; + } + 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(); +} + +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_close(void) +{ + queue_work(driver->diag_sdio_wq, &(driver->diag_close_sdio_work)); + return 0; +} + +static void diag_close_sdio_work_fn(struct work_struct *work) +{ + pr_debug("diag: sdio close called\n"); + if (sdio_close(driver->sdio_ch)) + pr_err("diag: could not close SDIO channel\n"); + else + driver->sdio_ch = NULL; /* channel successfully closed */ +} + +int diagfwd_connect_sdio(void) +{ + int err; + + err = usb_diag_alloc_req(driver->mdm_ch, N_MDM_WRITE, + N_MDM_READ); + if (err) + pr_err("diag: unable to alloc USB req on mdm ch\n"); + + driver->in_busy_sdio = 0; + if (!driver->sdio_ch) { + err = sdio_open("SDIO_DIAG", &driver->sdio_ch, driver, + diag_sdio_notify); + if (err) + pr_info("diag: could not open SDIO channel\n"); + else + pr_info("diag: opened SDIO channel\n"); + } else { + pr_info("diag: SDIO channel already open\n"); + } + + /* 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) +{ + usb_diag_free_req(driver->mdm_ch); + if (driver->sdio_ch && (driver->logging_mode == USB_MODE)) { + driver->in_busy_sdio = 1; + diag_sdio_close(); + } + 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) || + !(driver->sdio_ch))); + if (!(driver->sdio_ch)) { + pr_alert("diag: sdio channel not valid"); + return; + } + 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 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) +{ + pr_debug("\n diag: sdio remove called"); + /* Disable SDIO channel to prevent further read/write */ + driver->sdio_ch = NULL; + return 0; +} + +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_close_sdio_work), diag_close_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 0000000000000000000000000000000000000000..40982c33783209cfcc178776f10e7a69e0d9e598 --- /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 0000000000000000000000000000000000000000..0b5c27a5c12f79e5db4f478aab9142916429d25c --- /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 0000000000000000000000000000000000000000..43829ae16e8e9f1b618c1d309a708f399981f4f2 --- /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 0689bf6b01833495967faf00e68c3b1136a44b9c..68616b8ad1804f78aad9ef62f8edd770ded5b7a0 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -250,3 +250,16 @@ config UML_RANDOM (check your distro, or download from http://sourceforge.net/projects/gkernel/). rngd periodically reads /dev/hwrng and injects the entropy into /dev/random. + +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 b2ff5265a99637690d874b09a267775a2bfdda66..c24305dedd100731ed6ba26dbbbd6e3e98db1803 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -22,3 +22,4 @@ 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_PPC4XX) += ppc4xx-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 0000000000000000000000000000000000000000..7e6670d397d31a8eca78cadee40bd6798bffb1e6 --- /dev/null +++ b/drivers/char/hw_random/msm_rng.c @@ -0,0 +1,266 @@ +/* + * 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 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_HW_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_prepare_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_relaxed(base + PRNG_STATUS_OFFSET) & 0x00000001)) + break; /* no data to read so just bail */ + + /* read FIFO */ + val = readl_relaxed(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_unprepare(msm_rng_dev->prng_clk); + + return currsize; +} + +static struct hwrng msm_rng = { + .name = DRIVER_NAME, + .read = msm_rng_read, +}; + +static int __devinit msm_rng_enable_hw(struct msm_rng_device *msm_rng_dev) +{ + unsigned long val = 0; + unsigned long reg_val = 0; + int ret = 0; + + /* Enable the PRNG CLK */ + ret = clk_prepare_enable(msm_rng_dev->prng_clk); + if (ret) { + dev_err(&(msm_rng_dev->pdev)->dev, + "failed to enable clock in probe\n"); + return -EPERM; + } + /* Enable PRNG h/w only if it is NOT ON */ + val = readl_relaxed(msm_rng_dev->base + PRNG_CONFIG_OFFSET) & + PRNG_HW_ENABLE; + /* PRNG H/W is not ON */ + if (val != PRNG_HW_ENABLE) { + val = readl_relaxed(msm_rng_dev->base + PRNG_LFSR_CFG_OFFSET) & + PRNG_LFSR_CFG_MASK; + val |= PRNG_LFSR_CFG_MASK; + writel_relaxed(val, msm_rng_dev->base + PRNG_LFSR_CFG_OFFSET); + + /* The PRNG CONFIG register should be first written */ + mb(); + + reg_val = readl_relaxed(msm_rng_dev->base + PRNG_CONFIG_OFFSET) + & PRNG_CONFIG_MASK; + reg_val |= PRNG_HW_ENABLE; + writel_relaxed(reg_val, msm_rng_dev->base + PRNG_CONFIG_OFFSET); + + /* The PRNG clk should be disabled only after we enable the + * PRNG h/w by writing to the PRNG CONFIG register. + */ + mb(); + } + + clk_disable_unprepare(msm_rng_dev->prng_clk); + + return 0; +} + +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; + + 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(&pdev->dev, "core_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); + + /* Enable rng h/w */ + error = msm_rng_enable_hw(msm_rng_dev); + + if (error) + goto rollback_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 rollback_clk; + } + + return 0; + +rollback_clk: + 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 of_device_id qrng_match[] = { + { .compatible = "qcom,msm-rng", + }, + {} +}; + +static struct platform_driver rng_driver = { + .probe = msm_rng_probe, + .remove = __devexit_p(msm_rng_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = qrng_match, + } +}; + +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 0000000000000000000000000000000000000000..6cd18060073966d9bac86620145f2a328a693402 --- /dev/null +++ b/drivers/char/msm_rotator.c @@ -0,0 +1,1802 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +#ifdef CONFIG_MSM_BUS_SCALING +#include +#include +#endif +#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_SW_RESET (MSM_ROTATOR_BASE+0x0074) +#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_SRCP2_ADDR (MSM_ROTATOR_BASE+0x1114) +#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_OUTP2_ADDR (MSM_ROTATOR_BASE+0x1170) +#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 +#define MAX_DOWNSCALE_RATIO 3 + +#define ROTATOR_REVISION_V0 0 +#define ROTATOR_REVISION_V1 1 +#define ROTATOR_REVISION_V2 2 +#define ROTATOR_REVISION_NONE 0xffffffff + +uint32_t rotator_hw_revision; + +/* + * rotator_hw_revision: + * 0 == 7x30 + * 1 == 8x60 + * 2 == 8960 + * + */ +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_mem_planes { + unsigned int num_planes; + unsigned int plane_size[4]; + unsigned int total_size; +}; + +#define checkoffset(offset, size, max_size) \ + ((size) > (max_size) || (offset) > ((max_size) - (size))) + +struct msm_rotator_fd_info { + int pid; + int ref_cnt; + struct list_head list; +}; + +struct msm_rotator_dev { + void __iomem *io_base; + int irq; + struct msm_rotator_img_info *img_info[MAX_SESSIONS]; + struct clk *core_clk; + struct msm_rotator_fd_info *fd_info[MAX_SESSIONS]; + struct list_head fd_list; + struct clk *pclk; + 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; + struct ion_client *client; + #ifdef CONFIG_MSM_BUS_SCALING + uint32_t bus_client_handle; + #endif +}; + +#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_iommu_map_buf(int mem_id, unsigned char src, + unsigned long *start, unsigned long *len, + struct ion_handle **pihdl) +{ + if (!msm_rotator_dev->client) + return -EINVAL; + + *pihdl = ion_import_fd(msm_rotator_dev->client, mem_id); + if (IS_ERR_OR_NULL(*pihdl)) { + pr_err("ion_import_fd() failed\n"); + return PTR_ERR(*pihdl); + } + pr_debug("%s(): ion_hdl %p, ion_buf %p\n", __func__, *pihdl, + ion_share(msm_rotator_dev->client, *pihdl)); + + if (ion_map_iommu(msm_rotator_dev->client, + *pihdl, ROTATOR_DOMAIN, GEN_POOL, + SZ_4K, 0, start, len, 0, ION_IOMMU_UNMAP_DELAYED)) { + pr_err("ion_map_iommu() failed\n"); + return -EINVAL; + } + + pr_debug("%s(): mem_id %d, start 0x%lx, len 0x%lx\n", + __func__, mem_id, *start, *len); + return 0; +} + +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_prepare_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_unprepare(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_prepare_enable(msm_rotator_dev->core_clk); + if (msm_rotator_dev->pclk != NULL) + clk_prepare_enable(msm_rotator_dev->pclk); +} + +/* disable clocks needed by rotator block */ +static void disable_rot_clks(void) +{ + if (msm_rotator_dev->core_clk != NULL) + clk_disable_unprepare(msm_rotator_dev->core_clk); + if (msm_rotator_dev->pclk != NULL) + clk_disable_unprepare(msm_rotator_dev->pclk); + 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 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 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_CB_CR_H2V2: + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CR_CB_GH2V2: + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2_TILE: + return 1; + + case MDP_RGB_888: + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + 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_get_plane_sizes(uint32_t format, uint32_t w, uint32_t h, + struct msm_rotator_mem_planes *p) +{ + /* + * 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}; + int i; + + if (p == NULL) + return -EINVAL; + + if ((w > MSM_ROTATOR_MAX_W) || (h > MSM_ROTATOR_MAX_H)) + return -ERANGE; + + memset(p, 0, sizeof(*p)); + + switch (format) { + case MDP_XRGB_8888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_BGRA_8888: + case MDP_RGBX_8888: + case MDP_RGB_888: + case MDP_RGB_565: + case MDP_BGR_565: + case MDP_YCRYCB_H2V1: + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + p->num_planes = 1; + p->plane_size[0] = w * h * get_bpp(format); + break; + case MDP_Y_CRCB_H2V1: + case MDP_Y_CBCR_H2V1: + p->num_planes = 2; + p->plane_size[0] = w * h; + p->plane_size[1] = w * h; + break; + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + p->num_planes = 2; + p->plane_size[0] = w * h; + p->plane_size[1] = w * h / 2; + break; + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2_TILE: + p->num_planes = 2; + p->plane_size[0] = tile_size(w, h, &tile); + p->plane_size[1] = tile_size(w, h/2, &tile); + break; + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CR_CB_H2V2: + p->num_planes = 3; + p->plane_size[0] = w * h; + p->plane_size[1] = (w / 2) * (h / 2); + p->plane_size[2] = (w / 2) * (h / 2); + break; + case MDP_Y_CR_CB_GH2V2: + p->num_planes = 3; + p->plane_size[0] = ALIGN(w, 16) * h; + p->plane_size[1] = ALIGN(w / 2, 16) * (h / 2); + p->plane_size[2] = ALIGN(w / 2, 16) * (h / 2); + break; + default: + return -EINVAL; + } + + for (i = 0; i < p->num_planes; i++) + p->total_size += p->plane_size[i]; + + return 0; +} + +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; + + 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(in_chroma_paddr, MSM_ROTATOR_SRCP1_ADDR); + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP0_ADDR); + iowrite32(out_chroma_paddr + + ((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 */ + info->downscale_ratio << 2 | /* downscale v ratio */ + info->downscale_ratio, /* downscale h ratio */ + 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, + unsigned int in_chroma2_paddr) +{ + uint32_t dst_format; + int is_tile = 0; + + switch (info->src.format) { + case MDP_Y_CRCB_H2V2_TILE: + is_tile = 1; + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CR_CB_GH2V2: + case MDP_Y_CRCB_H2V2: + dst_format = MDP_Y_CRCB_H2V2; + break; + case MDP_Y_CBCR_H2V2_TILE: + is_tile = 1; + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CBCR_H2V2: + dst_format = MDP_Y_CBCR_H2V2; + break; + default: + return -EINVAL; + } + if (info->dst.format != dst_format) + return -EINVAL; + + /* rotator expects YCbCr for planar input format */ + if ((info->src.format == MDP_Y_CR_CB_H2V2 || + info->src.format == MDP_Y_CR_CB_GH2V2) && + rotator_hw_revision < ROTATOR_REVISION_V2) + swap(in_chroma_paddr, in_chroma2_paddr); + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + iowrite32(in_chroma_paddr, MSM_ROTATOR_SRCP1_ADDR); + iowrite32(in_chroma2_paddr, MSM_ROTATOR_SRCP2_ADDR); + + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP0_ADDR); + iowrite32(out_chroma_paddr + + ((info->dst_y * info->dst.width)/2 + info->dst_x), + MSM_ROTATOR_OUTP1_ADDR); + + if (new_session) { + if (in_chroma2_paddr) { + if (info->src.format == MDP_Y_CR_CB_GH2V2) { + iowrite32(ALIGN(info->src.width, 16) | + ALIGN((info->src.width / 2), 16) << 16, + MSM_ROTATOR_SRC_YSTRIDE1); + iowrite32(ALIGN((info->src.width / 2), 16), + MSM_ROTATOR_SRC_YSTRIDE2); + } else { + iowrite32(info->src.width | + (info->src.width / 2) << 16, + MSM_ROTATOR_SRC_YSTRIDE1); + iowrite32((info->src.width / 2), + MSM_ROTATOR_SRC_YSTRIDE2); + } + } else { + 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 (dst_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 */ + info->downscale_ratio << 2 | /* downscale v ratio */ + info->downscale_ratio, /* downscale h ratio */ + MSM_ROTATOR_SUB_BLOCK_CFG); + + iowrite32((is_tile ? 2 : 0) << 29 | /* frame format */ + (use_imem ? 0 : 1) << 22 | /* tile size */ + (in_chroma2_paddr ? 1 : 2) << 19 | /* fetch planes */ + 0 << 18 | /* unpack align */ + 1 << 17 | /* unpack tight */ + 1 << 13 | /* unpack count 0=1 component */ + 0 << 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, + unsigned int out_chroma_paddr) +{ + int bpp; + uint32_t dst_format; + + if (info->src.format == MDP_YCRYCB_H2V1) + dst_format = MDP_Y_CRCB_H2V1; + else + return -EINVAL; + + if (info->dst.format != 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); + iowrite32(out_chroma_paddr + + ((info->dst_y * info->dst.width)/2 + info->dst_x), + MSM_ROTATOR_OUTP1_ADDR); + + if (new_session) { + iowrite32(info->src.width * bpp, + 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); + + iowrite32(GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, 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 */ + info->downscale_ratio << 2 | /* downscale v ratio */ + info->downscale_ratio, /* downscale h ratio */ + 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 */ + info->downscale_ratio << 2 | /* downscale v ratio */ + info->downscale_ratio, /* downscale h ratio */ + 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: + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + 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(struct msmfb_data *fbd, unsigned char src, + unsigned long *start, unsigned long *len, struct file **p_file, + int *p_need, struct ion_handle **p_ihdl) +{ + int ret = 0; +#ifdef CONFIG_FB + struct file *file = NULL; + int put_needed, fb_num; +#endif +#ifdef CONFIG_ANDROID_PMEM + unsigned long vstart; +#endif + + *p_need = 0; + +#ifdef CONFIG_FB + if (fbd->flags & MDP_MEMORY_ID_TYPE_FB) { + file = fget_light(fbd->memory_id, &put_needed); + if (file == NULL) { + pr_err("fget_light returned NULL\n"); + return -EINVAL; + } + + 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, + ROTATOR_SUBSYSTEM_ID)) { + pr_err("get_fb_phys_info() failed\n"); + ret = -1; + } else { + *p_file = file; + *p_need = put_needed; + } + } else { + pr_err("invalid FB_MAJOR failed\n"); + ret = -1; + } + if (ret) + fput_light(file, put_needed); + return ret; + } +#endif + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + return msm_rotator_iommu_map_buf(fbd->memory_id, src, start, + len, p_ihdl); +#endif +#ifdef CONFIG_ANDROID_PMEM + if (!get_pmem_file(fbd->memory_id, start, &vstart, len, p_file)) + return 0; + else + return -ENOMEM; +#endif + +} + +static void put_img(struct file *p_file, struct ion_handle *p_ihdl) +{ +#ifdef CONFIG_ANDROID_PMEM + if (p_file != NULL) + put_pmem_file(p_file); +#endif +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + if (!IS_ERR_OR_NULL(p_ihdl)) { + pr_debug("%s(): p_ihdl %p\n", __func__, p_ihdl); + ion_unmap_iommu(msm_rotator_dev->client, + p_ihdl, ROTATOR_DOMAIN, GEN_POOL); + + ion_free(msm_rotator_dev->client, p_ihdl); + } +#endif +} +static int msm_rotator_do_rotate(unsigned long arg) +{ + unsigned int status, format; + struct msm_rotator_data_info info; + unsigned int in_paddr, out_paddr; + unsigned long src_len, dst_len; + int use_imem = 0, rc = 0, s; + struct file *srcp0_file = NULL, *dstp0_file = NULL; + struct file *srcp1_file = NULL, *dstp1_file = NULL; + struct ion_handle *srcp0_ihdl = NULL, *dstp0_ihdl = NULL; + struct ion_handle *srcp1_ihdl = NULL, *dstp1_ihdl = NULL; + int ps0_need, p_need; + unsigned int in_chroma_paddr = 0, out_chroma_paddr = 0; + unsigned int in_chroma2_paddr = 0; + struct msm_rotator_img_info *img_info; + struct msm_rotator_mem_planes src_planes, dst_planes; + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) + return -EFAULT; + + 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) { + pr_err("%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; + } + + img_info = msm_rotator_dev->img_info[s]; + if (msm_rotator_get_plane_sizes(img_info->src.format, + img_info->src.width, + img_info->src.height, + &src_planes)) { + pr_err("%s: invalid src format\n", __func__); + rc = -EINVAL; + goto do_rotate_unlock_mutex; + } + if (msm_rotator_get_plane_sizes(img_info->dst.format, + img_info->dst.width, + img_info->dst.height, + &dst_planes)) { + pr_err("%s: invalid dst format\n", __func__); + rc = -EINVAL; + goto do_rotate_unlock_mutex; + } + + rc = get_img(&info.src, 1, (unsigned long *)&in_paddr, + (unsigned long *)&src_len, &srcp0_file, &ps0_need, + &srcp0_ihdl); + if (rc) { + pr_err("%s: in get_img() failed id=0x%08x\n", + DRIVER_NAME, info.src.memory_id); + goto do_rotate_unlock_mutex; + } + + rc = get_img(&info.dst, 0, (unsigned long *)&out_paddr, + (unsigned long *)&dst_len, &dstp0_file, &p_need, + &dstp0_ihdl); + if (rc) { + pr_err("%s: out get_img() failed id=0x%08x\n", + DRIVER_NAME, info.dst.memory_id); + 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) && + (src_planes.num_planes == 2)) { + if (checkoffset(info.src.offset, + src_planes.plane_size[0], + src_len)) { + pr_err("%s: invalid src buffer (len=%lu offset=%x)\n", + __func__, src_len, info.src.offset); + rc = -ERANGE; + goto do_rotate_unlock_mutex; + } + if (checkoffset(info.dst.offset, + dst_planes.plane_size[0], + dst_len)) { + pr_err("%s: invalid dst buffer (len=%lu offset=%x)\n", + __func__, dst_len, info.dst.offset); + rc = -ERANGE; + goto do_rotate_unlock_mutex; + } + + rc = get_img(&info.src_chroma, 1, + (unsigned long *)&in_chroma_paddr, + (unsigned long *)&src_len, &srcp1_file, &p_need, + &srcp1_ihdl); + if (rc) { + pr_err("%s: in chroma get_img() failed id=0x%08x\n", + DRIVER_NAME, info.src_chroma.memory_id); + goto do_rotate_unlock_mutex; + } + + rc = get_img(&info.dst_chroma, 0, + (unsigned long *)&out_chroma_paddr, + (unsigned long *)&dst_len, &dstp1_file, &p_need, + &dstp1_ihdl); + if (rc) { + pr_err("%s: out chroma get_img() failed id=0x%08x\n", + DRIVER_NAME, info.dst_chroma.memory_id); + goto do_rotate_unlock_mutex; + } + + if (checkoffset(info.src_chroma.offset, + src_planes.plane_size[1], + src_len)) { + pr_err("%s: invalid chr src buf len=%lu offset=%x\n", + __func__, src_len, info.src_chroma.offset); + rc = -ERANGE; + goto do_rotate_unlock_mutex; + } + + if (checkoffset(info.dst_chroma.offset, + src_planes.plane_size[1], + dst_len)) { + pr_err("%s: invalid chr dst buf len=%lu offset=%x\n", + __func__, dst_len, info.dst_chroma.offset); + rc = -ERANGE; + goto do_rotate_unlock_mutex; + } + + in_chroma_paddr += info.src_chroma.offset; + out_chroma_paddr += info.dst_chroma.offset; + } else { + if (checkoffset(info.src.offset, + src_planes.total_size, + src_len)) { + pr_err("%s: invalid src buffer (len=%lu offset=%x)\n", + __func__, src_len, info.src.offset); + rc = -ERANGE; + goto do_rotate_unlock_mutex; + } + if (checkoffset(info.dst.offset, + dst_planes.total_size, + dst_len)) { + pr_err("%s: invalid dst buffer (len=%lu offset=%x)\n", + __func__, dst_len, info.dst.offset); + rc = -ERANGE; + goto do_rotate_unlock_mutex; + } + } + + in_paddr += info.src.offset; + out_paddr += info.dst.offset; + + if (!in_chroma_paddr && src_planes.num_planes >= 2) + in_chroma_paddr = in_paddr + src_planes.plane_size[0]; + if (!out_chroma_paddr && dst_planes.num_planes >= 2) + out_chroma_paddr = out_paddr + dst_planes.plane_size[0]; + if (src_planes.num_planes >= 3) + in_chroma2_paddr = in_chroma_paddr + src_planes.plane_size[1]; + + 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: + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + 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: + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CR_CB_GH2V2: + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2_TILE: + 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, + in_chroma2_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, + out_chroma_paddr); + break; + default: + rc = -EINVAL; + pr_err("%s(): Unsupported format %u\n", __func__, format); + goto do_rotate_exit; + } + + if (rc != 0) { + msm_rotator_dev->last_session_idx = INVALID_SESSION; + pr_err("%s(): Invalid session error\n", __func__); + 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) { + pr_err("%s(): AXI Bus Error, issuing SW_RESET\n", __func__); + iowrite32(0x1, MSM_ROTATOR_SW_RESET); + 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); +do_rotate_unlock_mutex: + put_img(dstp1_file, dstp1_ihdl); + put_img(srcp1_file, srcp1_ihdl); + put_img(dstp0_file, dstp0_ihdl); + + /* only source may use frame buffer */ + if (info.src.flags & MDP_MEMORY_ID_TYPE_FB) + fput_light(srcp0_file, ps0_need); + else + put_img(srcp0_file, srcp0_ihdl); + mutex_unlock(&msm_rotator_dev->rotator_lock); + dev_dbg(msm_rotator_dev->device, "%s() returning rc = %d\n", + __func__, rc); + return rc; +} + +static void msm_rotator_set_perf_level(u32 wh, u32 is_rgb) +{ + u32 perf_level; + + if (is_rgb) + perf_level = 1; + else if (wh <= (640 * 480)) + perf_level = 2; + else if (wh <= (736 * 1280)) + perf_level = 3; + else + perf_level = 4; + +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_scale_client_update_request(msm_rotator_dev->bus_client_handle, + perf_level); +#endif + +} + +static int msm_rotator_start(unsigned long arg, + struct msm_rotator_fd_info *fd_info) +{ + struct msm_rotator_img_info info; + int rc = 0; + int s, is_rgb = 0; + int first_free_index = INVALID_SESSION; + unsigned int dst_w, dst_h; + + 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.downscale_ratio > MAX_DOWNSCALE_RATIO)) { + pr_err("%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + if (info.rotations & MDP_ROT_90) { + dst_w = info.src_rect.h >> info.downscale_ratio; + dst_h = info.src_rect.w >> info.downscale_ratio; + } else { + dst_w = info.src_rect.w >> info.downscale_ratio; + dst_h = info.src_rect.h >> info.downscale_ratio; + } + + if (checkoffset(info.src_rect.x, info.src_rect.w, info.src.width) || + checkoffset(info.src_rect.y, info.src_rect.h, info.src.height) || + checkoffset(info.dst_x, dst_w, info.dst.width) || + checkoffset(info.dst_y, dst_h, info.dst.height)) { + pr_err("%s: Invalid src or dst rect\n", __func__); + return -ERANGE; + } + + 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: + is_rgb = 1; + info.dst.format = info.src.format; + break; + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + info.dst.format = info.src.format; + break; + case MDP_YCRYCB_H2V1: + info.dst.format = MDP_Y_CRCB_H2V1; + break; + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CBCR_H2V2_TILE: + info.dst.format = MDP_Y_CBCR_H2V2; + break; + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CR_CB_GH2V2: + case MDP_Y_CRCB_H2V2_TILE: + info.dst.format = MDP_Y_CRCB_H2V2; + break; + default: + return -EINVAL; + } + + mutex_lock(&msm_rotator_dev->rotator_lock); + + msm_rotator_set_perf_level((info.src.width*info.src.height), is_rgb); + + 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->fd_info[s] = fd_info; + + 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->fd_info[first_free_index] = fd_info; + } else if (s == MAX_SESSIONS) { + dev_dbg(msm_rotator_dev->device, "%s: all sessions in use\n", + __func__); + rc = -EBUSY; + } + + if (rc == 0 && copy_to_user((void __user *)arg, &info, sizeof(info))) + rc = -EFAULT; + +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->fd_info[s] = NULL; + break; + } + } + + if (s == MAX_SESSIONS) + rc = -EINVAL; +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_scale_client_update_request(msm_rotator_dev->bus_client_handle, + 0); +#endif + mutex_unlock(&msm_rotator_dev->rotator_lock); + return rc; +} + +static int +msm_rotator_open(struct inode *inode, struct file *filp) +{ + struct msm_rotator_fd_info *tmp, *fd_info = NULL; + int i; + + if (filp->private_data) + return -EBUSY; + + mutex_lock(&msm_rotator_dev->rotator_lock); + for (i = 0; i < MAX_SESSIONS; i++) { + if (msm_rotator_dev->fd_info[i] == NULL) + break; + } + + if (i == MAX_SESSIONS) { + mutex_unlock(&msm_rotator_dev->rotator_lock); + return -EBUSY; + } + + list_for_each_entry(tmp, &msm_rotator_dev->fd_list, list) { + if (tmp->pid == current->pid) { + fd_info = tmp; + break; + } + } + + if (!fd_info) { + fd_info = kzalloc(sizeof(*fd_info), GFP_KERNEL); + if (!fd_info) { + mutex_unlock(&msm_rotator_dev->rotator_lock); + pr_err("%s: insufficient memory to alloc resources\n", + __func__); + return -ENOMEM; + } + list_add(&fd_info->list, &msm_rotator_dev->fd_list); + fd_info->pid = current->pid; + } + fd_info->ref_cnt++; + mutex_unlock(&msm_rotator_dev->rotator_lock); + + filp->private_data = fd_info; + + return 0; +} + +static int +msm_rotator_close(struct inode *inode, struct file *filp) +{ + struct msm_rotator_fd_info *fd_info; + int s; + + fd_info = (struct msm_rotator_fd_info *)filp->private_data; + + mutex_lock(&msm_rotator_dev->rotator_lock); + if (--fd_info->ref_cnt > 0) { + mutex_unlock(&msm_rotator_dev->rotator_lock); + return 0; + } + + for (s = 0; s < MAX_SESSIONS; s++) { + if (msm_rotator_dev->img_info[s] != NULL && + msm_rotator_dev->fd_info[s] == fd_info) { + pr_debug("%s: freeing rotator session %p (pid %d)\n", + __func__, msm_rotator_dev->img_info[s], + fd_info->pid); + kfree(msm_rotator_dev->img_info[s]); + msm_rotator_dev->img_info[s] = NULL; + msm_rotator_dev->fd_info[s] = NULL; + if (msm_rotator_dev->last_session_idx == s) + msm_rotator_dev->last_session_idx = + INVALID_SESSION; + } + } + list_del(&fd_info->list); + kfree(fd_info); + mutex_unlock(&msm_rotator_dev->rotator_lock); + + return 0; +} + +static long msm_rotator_ioctl(struct file *file, unsigned cmd, + unsigned long arg) +{ + struct msm_rotator_fd_info *fd_info; + + if (_IOC_TYPE(cmd) != MSM_ROTATOR_IOCTL_MAGIC) + return -ENOTTY; + + fd_info = (struct msm_rotator_fd_info *)file->private_data; + + switch (cmd) { + case MSM_ROTATOR_IOCTL_START: + return msm_rotator_start(arg, fd_info); + 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); + INIT_LIST_HEAD(&msm_rotator_dev->fd_list); + 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; + +#ifdef CONFIG_MSM_BUS_SCALING + if (!msm_rotator_dev->bus_client_handle && pdata && + pdata->bus_scale_table) { + msm_rotator_dev->bus_client_handle = + msm_bus_scale_register_client( + pdata->bus_scale_table); + if (!msm_rotator_dev->bus_client_handle) { + pr_err("%s not able to get bus scale handle\n", + __func__); + } + } +#endif + + 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_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_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_rate(msm_rotator_dev->core_clk, + pdata->rotator_clks[i].clk_rate); + } + } + + msm_rotator_dev->regulator = regulator_get(&msm_rotator_dev->pdev->dev, + "vdd"); + 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); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + msm_rotator_dev->client = msm_ion_client_create(-1, pdev->name); +#endif + 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_prepare_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_unprepare(msm_rotator_dev->imem_clk); +#endif + if (ver != pdata->hardware_version_number) + pr_debug("%s: invalid HW version ver 0x%x\n", + DRIVER_NAME, ver); + + rotator_hw_revision = ver; + rotator_hw_revision >>= 16; /* bit 31:16 */ + rotator_hw_revision &= 0xff; + + pr_info("%s: rotator_hw_revision=%x\n", + __func__, rotator_hw_revision); + + 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->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; + +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_scale_unregister_client(msm_rotator_dev->bus_client_handle); +#endif + 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_unprepare(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); + if (msm_rotator_dev->regulator) + regulator_put(msm_rotator_dev->regulator); + msm_rotator_dev->core_clk = NULL; + msm_rotator_dev->pclk = 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_unprepare(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_prepare_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 8eca55deb3a35c4a0a6ffc41130e3422d22bd926..adb2926bd528106d2874aa3b4fceeffb4682f7e3 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 a048199ce866555f19b98fcd93ff10f3890c1eb3..8122ed1fd304d4333d7356ff18ae1ae30609edd7 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -62,4 +62,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 ea3a1e02a824de2b1a80feaa5ebc0e0125162c1c..c113cf1a603067cf865f87a9a76df94de60b43aa 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 0000000000000000000000000000000000000000..3a6e8c4f8b3f362f1f5fd456e60daa89fa92d49c --- /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 0000000000000000000000000000000000000000..7d62de463de1532108ad286f8fb39571b5d8161a --- /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 0000000000000000000000000000000000000000..ec8d93e535d0987ce7459145574e8cbecf469a98 --- /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 0000000000000000000000000000000000000000..cbfcbd869d912a85c9bcc965a73044cac34c0295 --- /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 0000000000000000000000000000000000000000..94bb4403ef9bcd0bbe7f776d373cf3f0aca7c3e4 --- /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/clk/clkdev.c b/drivers/clk/clkdev.c index 6db161f64ae0e9ab42a17f04608ab3ac18638141..fdff32e9c0563087b7cadd457d23e6c7445a74a3 100644 --- a/drivers/clk/clkdev.c +++ b/drivers/clk/clkdev.c @@ -83,6 +83,31 @@ struct clk *clk_get(struct device *dev, const char *con_id) } EXPORT_SYMBOL(clk_get); +static void devm_clk_release(struct device *dev, void *res) +{ + clk_put(*(struct clk **)res); +} + +struct clk *devm_clk_get(struct device *dev, const char *id) +{ + struct clk **ptr, *clk; + + ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + clk = clk_get(dev, id); + if (!IS_ERR(clk)) { + *ptr = clk; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return clk; +} +EXPORT_SYMBOL(devm_clk_get); + void clk_put(struct clk *clk) { __clk_put(clk); diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 35835b7cc2df0406fc8d1af31da26db586fa8d3c..46756c5184765b7d41b0abf661931f7a2a7f2bf4 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -53,3 +53,4 @@ obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o ################################################################################## # PowerPC platform drivers obj-$(CONFIG_CPU_FREQ_MAPLE) += maple-cpufreq.o +obj-$(CONFIG_MSM_DCVS) += cpufreq_gov_msm.o diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 7f2f149ae40fd4efa933052aa1caf9e1c3834b45..e9d654b42f3d017d7df14fc78e5ce1c336b9dcbb 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); @@ -277,14 +281,31 @@ void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state) trace_cpu_frequency(freqs->new, freqs->cpu); srcu_notifier_call_chain(&cpufreq_transition_notifier_list, CPUFREQ_POSTCHANGE, freqs); - if (likely(policy) && likely(policy->cpu == freqs->cpu)) + if (likely(policy) && likely(policy->cpu == freqs->cpu)) { policy->cur = freqs->new; + sysfs_notify(&policy->kobj, NULL, "scaling_cur_freq"); + } break; } } EXPORT_SYMBOL_GPL(cpufreq_notify_transition); +/** + * cpufreq_notify_utilization - notify CPU userspace about CPU utilization + * change + * + * This function is called everytime the CPU load is evaluated by the + * ondemand governor. It notifies userspace of cpu load changes via sysfs. + */ +void cpufreq_notify_utilization(struct cpufreq_policy *policy, + unsigned int util) +{ + if (policy) + policy->util = util; + if (policy->util >= MIN_CPU_UTIL_NOTIFY) + sysfs_notify(&policy->kobj, NULL, "cpu_utilization"); +} /********************************************************************* * SYSFS INTERFACE * @@ -372,6 +393,7 @@ show_one(cpuinfo_transition_latency, cpuinfo.transition_latency); show_one(scaling_min_freq, min); show_one(scaling_max_freq, max); show_one(scaling_cur_freq, cur); +show_one(cpu_utilization, util); static int __cpufreq_set_policy(struct cpufreq_policy *data, struct cpufreq_policy *policy); @@ -461,6 +483,8 @@ static ssize_t store_scaling_governor(struct cpufreq_policy *policy, policy->user_policy.policy = policy->policy; policy->user_policy.governor = policy->governor; + sysfs_notify(&policy->kobj, NULL, "scaling_governor"); + if (ret) return ret; else @@ -586,6 +610,7 @@ cpufreq_freq_attr_ro(scaling_cur_freq); cpufreq_freq_attr_ro(bios_limit); cpufreq_freq_attr_ro(related_cpus); cpufreq_freq_attr_ro(affected_cpus); +cpufreq_freq_attr_ro(cpu_utilization); cpufreq_freq_attr_rw(scaling_min_freq); cpufreq_freq_attr_rw(scaling_max_freq); cpufreq_freq_attr_rw(scaling_governor); @@ -598,6 +623,7 @@ static struct attribute *default_attrs[] = { &scaling_min_freq.attr, &scaling_max_freq.attr, &affected_cpus.attr, + &cpu_utilization.attr, &related_cpus.attr, &scaling_governor.attr, &scaling_driver.attr, @@ -696,12 +722,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) { @@ -1051,8 +1087,12 @@ static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif #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, @@ -1076,8 +1116,12 @@ static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif 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_dev = get_cpu_device(j); kobj = &cpu_dev->kobj; @@ -1594,8 +1638,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_gov_msm.c b/drivers/cpufreq/cpufreq_gov_msm.c new file mode 100644 index 0000000000000000000000000000000000000000..9c49f803aa631c19f4290b0f806ed20d4edaac11 --- /dev/null +++ b/drivers/cpufreq/cpufreq_gov_msm.c @@ -0,0 +1,176 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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_gov { + int cpu; + unsigned int cur_freq; + unsigned int min_freq; + unsigned int max_freq; + struct msm_dcvs_freq gov_notifier; + struct cpufreq_policy *policy; +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct mutex, gov_mutex); +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_gov, msm_gov_info); +static char core_name[NR_CPUS][10]; + +static void msm_gov_check_limits(struct cpufreq_policy *policy) +{ + struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu); + + if (policy->max < gov->cur_freq) + __cpufreq_driver_target(policy, policy->max, + CPUFREQ_RELATION_H); + else if (policy->min > gov->min_freq) + __cpufreq_driver_target(policy, policy->min, + CPUFREQ_RELATION_L); + else + __cpufreq_driver_target(policy, gov->cur_freq, + CPUFREQ_RELATION_L); + + gov->cur_freq = policy->cur; + gov->min_freq = policy->min; + gov->max_freq = policy->max; +} + +static int msm_dcvs_freq_set(struct msm_dcvs_freq *self, + unsigned int freq) +{ + int ret = -EINVAL; + struct msm_gov *gov = + container_of(self, struct msm_gov, gov_notifier); + + mutex_lock(&per_cpu(gov_mutex, gov->cpu)); + + if (freq < gov->min_freq) + freq = gov->min_freq; + if (freq > gov->max_freq) + freq = gov->max_freq; + + ret = __cpufreq_driver_target(gov->policy, freq, CPUFREQ_RELATION_L); + gov->cur_freq = gov->policy->cur; + + mutex_unlock(&per_cpu(gov_mutex, gov->cpu)); + + if (!ret) + return gov->cur_freq; + + return ret; +} + +static unsigned int msm_dcvs_freq_get(struct msm_dcvs_freq *self) +{ + struct msm_gov *gov = + container_of(self, struct msm_gov, gov_notifier); + + return gov->cur_freq; +} + +static int cpufreq_governor_msm(struct cpufreq_policy *policy, + unsigned int event) +{ + unsigned int cpu = policy->cpu; + int ret = 0; + int handle = 0; + struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu); + struct msm_dcvs_freq *dcvs_notifier = + &(per_cpu(msm_gov_info, cpu).gov_notifier); + + switch (event) { + case CPUFREQ_GOV_START: + if (!cpu_online(cpu)) + return -EINVAL; + BUG_ON(!policy->cur); + mutex_lock(&per_cpu(gov_mutex, cpu)); + per_cpu(msm_gov_info, cpu).cpu = cpu; + gov->policy = policy; + dcvs_notifier->core_name = core_name[cpu]; + dcvs_notifier->set_frequency = msm_dcvs_freq_set; + dcvs_notifier->get_frequency = msm_dcvs_freq_get; + handle = msm_dcvs_freq_sink_register(dcvs_notifier); + BUG_ON(handle < 0); + msm_gov_check_limits(policy); + mutex_unlock(&per_cpu(gov_mutex, cpu)); + break; + + case CPUFREQ_GOV_STOP: + mutex_lock(&per_cpu(gov_mutex, cpu)); + msm_dcvs_freq_sink_unregister(dcvs_notifier); + mutex_unlock(&per_cpu(gov_mutex, cpu)); + break; + + case CPUFREQ_GOV_LIMITS: + mutex_lock(&per_cpu(gov_mutex, cpu)); + msm_gov_check_limits(policy); + mutex_unlock(&per_cpu(gov_mutex, cpu)); + break; + }; + + return ret; +} + +struct cpufreq_governor cpufreq_gov_msm = { + .name = "msm-dcvs", + .governor = cpufreq_governor_msm, + .owner = THIS_MODULE, +}; + +static int __devinit msm_gov_probe(struct platform_device *pdev) +{ + int ret = 0; + int cpu; + uint32_t group_id = 0x43505530; /* CPU0 */ + struct msm_dcvs_core_info *core = NULL; + + core = pdev->dev.platform_data; + + for_each_possible_cpu(cpu) { + mutex_init(&per_cpu(gov_mutex, cpu)); + snprintf(core_name[cpu], 10, "cpu%d", cpu); + ret = msm_dcvs_register_core(core_name[cpu], group_id, core); + if (ret) + pr_err("Unable to register core for %d\n", cpu); + } + + return cpufreq_register_governor(&cpufreq_gov_msm); +} + +static int __devexit msm_gov_remove(struct platform_device *pdev) +{ + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver msm_gov_driver = { + .probe = msm_gov_probe, + .remove = __devexit_p(msm_gov_remove), + .driver = { + .name = "msm_dcvs_gov", + .owner = THIS_MODULE, + }, +}; + +static int __init cpufreq_gov_msm_init(void) +{ + return platform_driver_register(&msm_gov_driver); +} +late_initcall(cpufreq_gov_msm_init); diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c index 836e9b062e5ec4a2e08c935d4c49f17a79ef124b..5997405d7f9ef42b1efe7808fb4d4021b34d734e 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 @@ -56,6 +60,9 @@ static unsigned int min_sampling_rate; #define MIN_LATENCY_MULTIPLIER (100) #define TRANSITION_LATENCY_LIMIT (10 * 1000 * 1000) +#define POWERSAVE_BIAS_MAXLEVEL (1000) +#define POWERSAVE_BIAS_MINLEVEL (-1000) + static void do_dbs_timer(struct work_struct *work); static int cpufreq_governor_dbs(struct cpufreq_policy *policy, unsigned int event); @@ -96,6 +103,9 @@ struct cpu_dbs_info_s { }; static DEFINE_PER_CPU(struct cpu_dbs_info_s, od_cpu_dbs_info); +static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info); +static inline void dbs_timer_exit(struct cpu_dbs_info_s *dbs_info); + static unsigned int dbs_enable; /* number of CPUs using this policy */ /* @@ -103,13 +113,17 @@ 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; unsigned int down_differential; unsigned int ignore_nice; unsigned int sampling_down_factor; - unsigned int powersave_bias; + int powersave_bias; unsigned int io_is_busy; } dbs_tuners_ins = { .up_threshold = DEF_FREQUENCY_UP_THRESHOLD, @@ -172,10 +186,11 @@ static unsigned int powersave_bias_target(struct cpufreq_policy *policy, unsigned int freq_next, unsigned int relation) { - unsigned int freq_req, freq_reduc, freq_avg; + unsigned int freq_req, freq_avg; unsigned int freq_hi, freq_lo; unsigned int index = 0; unsigned int jiffies_total, jiffies_hi, jiffies_lo; + int freq_reduc; struct cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, policy->cpu); @@ -218,6 +233,26 @@ static unsigned int powersave_bias_target(struct cpufreq_policy *policy, return freq_hi; } +static int ondemand_powersave_bias_setspeed(struct cpufreq_policy *policy, + struct cpufreq_policy *altpolicy, + int level) +{ + if (level == POWERSAVE_BIAS_MAXLEVEL) { + /* maximum powersave; set to lowest frequency */ + __cpufreq_driver_target(policy, + (altpolicy) ? altpolicy->min : policy->min, + CPUFREQ_RELATION_L); + return 1; + } else if (level == POWERSAVE_BIAS_MINLEVEL) { + /* minimum powersave; set to highest frequency */ + __cpufreq_driver_target(policy, + (altpolicy) ? altpolicy->max : policy->max, + CPUFREQ_RELATION_H); + return 1; + } + return 0; +} + static void ondemand_powersave_bias_init_cpu(int cpu) { struct cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, cpu); @@ -253,9 +288,15 @@ 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); + +static ssize_t show_powersave_bias +(struct kobject *kobj, struct attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", dbs_tuners_ins.powersave_bias); +} /** * update_sampling_rate - update sampling rate effective immediately if needed. @@ -353,6 +394,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) { @@ -409,24 +467,112 @@ static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, static ssize_t store_powersave_bias(struct kobject *a, struct attribute *b, const char *buf, size_t count) { - unsigned int input; - int ret; - ret = sscanf(buf, "%u", &input); + int input = 0; + int bypass = 0; + int ret, cpu, reenable_timer, j; + struct cpu_dbs_info_s *dbs_info; + + struct cpumask cpus_timer_done; + cpumask_clear(&cpus_timer_done); + + ret = sscanf(buf, "%d", &input); if (ret != 1) return -EINVAL; - if (input > 1000) - input = 1000; + if (input >= POWERSAVE_BIAS_MAXLEVEL) { + input = POWERSAVE_BIAS_MAXLEVEL; + bypass = 1; + } else if (input <= POWERSAVE_BIAS_MINLEVEL) { + input = POWERSAVE_BIAS_MINLEVEL; + bypass = 1; + } + + if (input == dbs_tuners_ins.powersave_bias) { + /* no change */ + return count; + } + + reenable_timer = ((dbs_tuners_ins.powersave_bias == + POWERSAVE_BIAS_MAXLEVEL) || + (dbs_tuners_ins.powersave_bias == + POWERSAVE_BIAS_MINLEVEL)); dbs_tuners_ins.powersave_bias = input; - ondemand_powersave_bias_init(); + if (!bypass) { + if (reenable_timer) { + /* reinstate dbs timer */ + for_each_online_cpu(cpu) { + if (lock_policy_rwsem_write(cpu) < 0) + continue; + + dbs_info = &per_cpu(od_cpu_dbs_info, cpu); + + for_each_cpu(j, &cpus_timer_done) { + if (!dbs_info->cur_policy) { + pr_err("Dbs policy is NULL\n"); + goto skip_this_cpu; + } + if (cpumask_test_cpu(j, dbs_info-> + cur_policy->cpus)) + goto skip_this_cpu; + } + + cpumask_set_cpu(cpu, &cpus_timer_done); + if (dbs_info->cur_policy) { + /* restart dbs timer */ + dbs_timer_init(dbs_info); + } +skip_this_cpu: + unlock_policy_rwsem_write(cpu); + } + } + ondemand_powersave_bias_init(); + } else { + /* running at maximum or minimum frequencies; cancel + dbs timer as periodic load sampling is not necessary */ + for_each_online_cpu(cpu) { + if (lock_policy_rwsem_write(cpu) < 0) + continue; + + dbs_info = &per_cpu(od_cpu_dbs_info, cpu); + + for_each_cpu(j, &cpus_timer_done) { + if (!dbs_info->cur_policy) { + pr_err("Dbs policy is NULL\n"); + goto skip_this_cpu_bypass; + } + if (cpumask_test_cpu(j, dbs_info-> + cur_policy->cpus)) + goto skip_this_cpu_bypass; + } + + cpumask_set_cpu(cpu, &cpus_timer_done); + + if (dbs_info->cur_policy) { + /* cpu using ondemand, cancel dbs timer */ + mutex_lock(&dbs_info->timer_mutex); + dbs_timer_exit(dbs_info); + + ondemand_powersave_bias_setspeed( + dbs_info->cur_policy, + NULL, + input); + + mutex_unlock(&dbs_info->timer_mutex); + } +skip_this_cpu_bypass: + unlock_policy_rwsem_write(cpu); + } + } + return count; } 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); @@ -435,6 +581,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, @@ -462,7 +609,11 @@ static void dbs_freq_increase(struct cpufreq_policy *p, unsigned int freq) static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info) { + /* Extrapolated load of this CPU */ + unsigned int load_at_max_freq = 0; unsigned int max_load_freq; + /* Current load across this CPU */ + unsigned int cur_load = 0; struct cpufreq_policy *policy; unsigned int j; @@ -489,7 +640,7 @@ static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info) struct cpu_dbs_info_s *j_dbs_info; cputime64_t cur_wall_time, cur_idle_time, cur_iowait_time; unsigned int idle_time, wall_time, iowait_time; - unsigned int load, load_freq; + unsigned int load_freq; int freq_avg; j_dbs_info = &per_cpu(od_cpu_dbs_info, j); @@ -539,16 +690,20 @@ static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info) if (unlikely(!wall_time || wall_time < idle_time)) continue; - load = 100 * (wall_time - idle_time) / wall_time; + cur_load = 100 * (wall_time - idle_time) / wall_time; freq_avg = __cpufreq_driver_getavg(policy, j); if (freq_avg <= 0) freq_avg = policy->cur; - load_freq = load * freq_avg; + load_freq = cur_load * freq_avg; if (load_freq > max_load_freq) max_load_freq = load_freq; } + /* calculate the scaled load across CPU */ + load_at_max_freq = (cur_load * policy->cur)/policy->cpuinfo.max_freq; + + cpufreq_notify_utilization(policy, load_at_max_freq); /* Check for frequency increase */ if (max_load_freq > dbs_tuners_ins.up_threshold * policy->cur) { @@ -676,6 +831,100 @@ 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) { + /* CPU not using ondemand governor */ + unlock_policy_rwsem_write(cpu); + return; + } + + 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; + + if ((dbs_tuners_ins.powersave_bias == POWERSAVE_BIAS_MAXLEVEL) || + (dbs_tuners_ins.powersave_bias == POWERSAVE_BIAS_MINLEVEL)) { + /* nothing to do */ + return; + } + + 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) { @@ -734,10 +983,17 @@ 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); - dbs_timer_init(this_dbs_info); + + if (!ondemand_powersave_bias_setspeed( + this_dbs_info->cur_policy, + NULL, + dbs_tuners_ins.powersave_bias)) + dbs_timer_init(this_dbs_info); break; case CPUFREQ_GOV_STOP: @@ -746,6 +1002,11 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, mutex_lock(&dbs_mutex); mutex_destroy(&this_dbs_info->timer_mutex); dbs_enable--; + /* If device is being removed, policy is no longer + * valid. */ + this_dbs_info->cur_policy = NULL; + if (!cpu) + input_unregister_handler(&dbs_input_handler); mutex_unlock(&dbs_mutex); if (!dbs_enable) sysfs_remove_group(cpufreq_global_kobject, @@ -761,6 +1022,11 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, else if (policy->min > this_dbs_info->cur_policy->cur) __cpufreq_driver_target(this_dbs_info->cur_policy, policy->min, CPUFREQ_RELATION_L); + else if (dbs_tuners_ins.powersave_bias != 0) + ondemand_powersave_bias_setspeed( + this_dbs_info->cur_policy, + policy, + dbs_tuners_ins.powersave_bias); mutex_unlock(&this_dbs_info->timer_mutex); break; } @@ -770,6 +1036,7 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, static int __init cpufreq_gov_dbs_init(void) { u64 idle_time; + unsigned int i; int cpu = get_cpu(); idle_time = get_cpu_idle_time_us(cpu, NULL); @@ -791,12 +1058,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 72f0093ac4fdd105e43adbb329d3f488a3c635c4..1a9a6a5eb57d5e4c888b5b81332e579c4b13496b 100644 --- a/drivers/cpufreq/cpufreq_stats.c +++ b/drivers/cpufreq/cpufreq_stats.c @@ -349,6 +349,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/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index 746f1e6b04e0290e0c1e9ca1c759927ea9394f62..3157a865c78af828a5f15a1b9f959eb4919699e3 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -126,14 +126,6 @@ struct menu_device { #define LOAD_INT(x) ((x) >> FSHIFT) #define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) -static int get_loadavg(void) -{ - unsigned long this = this_cpu_load(); - - - return LOAD_INT(this) * 10 + LOAD_FRAC(this) / 10; -} - static inline int which_bucket(unsigned int duration) { int bucket = 0; diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index dd414d9350ef90b9e83fcc75ba371745b8505823..fb1ffd0fa463a82977074d25ddaa5dfa06495bfb 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -285,6 +285,49 @@ 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 || ARCH_MSM9615 + default n + help + This driver supports Qualcomm Crypto Engine in MSM7x30, MSM8660 + MSM8x55, MSM8960 and MSM9615 + To compile this driver as a module, choose M here: the + For MSM7x30 MSM8660 and MSM8x55 the module is called qce + For MSM8960 and MSM9615 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 and MSM9615. + 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. + config CRYPTO_DEV_TEGRA_AES tristate "Support for TEGRA AES hw engine" depends on ARCH_TEGRA diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index f3e64eadd7afad21c2134c1ae8c3513dbd4b2622..780620c61f3cc793f6938b422d8d4ce0584060ee 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 0000000000000000000000000000000000000000..61406b9531c325aee511b1c5d863bb4c3ded0d85 --- /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/ota_crypto.c b/drivers/crypto/msm/ota_crypto.c new file mode 100644 index 0000000000000000000000000000000000000000..b129c052714e416fe3327b87dfa61e6a9b3c712e --- /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 "qce.h" +#include "qce_ota.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 0000000000000000000000000000000000000000..55cf651f568964d55c30c465e407eb2f3b1805b9 --- /dev/null +++ b/drivers/crypto/msm/qce.c @@ -0,0 +1,2709 @@ +/* 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 + +#include "qce.h" +#include "qcryptohw_30.h" +#include "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 + +#define QCE_FIFO_SIZE 0x8000 + +/* 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_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 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 qce_split_and_insert_dm_desc(struct dmov_desc *pdesc, + unsigned int plen, unsigned int paddr, int *index) +{ + while (plen > QCE_FIFO_SIZE) { + pdesc->len = QCE_FIFO_SIZE; + if (paddr > 0) { + pdesc->addr = paddr; + paddr += QCE_FIFO_SIZE; + } + plen -= pdesc->len; + if (plen > 0) { + *index = (*index) + 1; + if ((*index) >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + } + } + if ((plen > 0) && (plen <= QCE_FIFO_SIZE)) { + 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_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 > QCE_FIFO_SIZE) { + if (qce_split_and_insert_dm_desc(pdesc, + pdesc->len, sg_dma_address(sg), + &pce_dev->ce_in_src_desc_index)) + return -EIO; + } + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + if (pdesc->len > QCE_FIFO_SIZE) { + if (qce_split_and_insert_dm_desc(pdesc, + pdesc->len, pdesc->addr, + &pce_dev->ce_in_src_desc_index)) + return -EIO; + } + } 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 > QCE_FIFO_SIZE) { + if (qce_split_and_insert_dm_desc(pdesc, + pdesc->len, sg_dma_address(sg), + &pce_dev->ce_in_src_desc_index)) + return -EIO; + } + } + 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; + 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, 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; + if (total > QCE_FIFO_SIZE) { + qce_split_and_insert_dm_desc(pdesc, total, 0, + &pce_dev->ce_in_dst_desc_index); + pdesc = pce_dev->ce_in_dst_desc + pce_dev->ce_in_dst_desc_index; + pdesc->len |= ADM_DESC_LAST; + } else + 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: 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); + } +}; +#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; + if (pdesc->len > QCE_FIFO_SIZE) { + if (qce_split_and_insert_dm_desc(pdesc, + pdesc->len, sg_dma_address(sg), + &pce_dev->ce_out_dst_desc_index)) + return -EIO; + } + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + if (pdesc->len > QCE_FIFO_SIZE) { + if (qce_split_and_insert_dm_desc(pdesc, + pdesc->len, pdesc->addr, + &pce_dev->ce_out_dst_desc_index)) + return -EIO; + } + } 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 > QCE_FIFO_SIZE) { + if (qce_split_and_insert_dm_desc(pdesc, + pdesc->len, sg_dma_address(sg), + &pce_dev->ce_out_dst_desc_index)) + return -EIO; + } + } + 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; + 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, 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; + if (total > QCE_FIFO_SIZE) { + qce_split_and_insert_dm_desc(pdesc, total, 0, + &pce_dev->ce_out_src_desc_index); + pdesc = pce_dev->ce_out_src_desc + + pce_dev->ce_out_src_desc_index; + pdesc->len |= ADM_DESC_LAST; + } else + 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; + 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 + * 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 */ + 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); + vaddr = vaddr + ADM_CE_BLOCK_SIZE; + + /* 9. ce_in channel command pointer list. */ + vaddr = (unsigned char *) ALIGN(((unsigned int) vaddr), 16); + 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), 16); + 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 = (2 * 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++; + /* + * 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); + /* + * 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++; + /* + * 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); + + + 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, "core_clk"); + if (IS_ERR(ce_clk)) { + kfree(pce_dev); + *rc = PTR_ERR(ce_clk); + return NULL; + } + pce_dev->ce_clk = ce_clk; + *rc = clk_enable(pce_dev->ce_clk); + if (*rc) { + kfree(pce_dev); + return NULL; + } + + 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, + 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; + } + _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; + 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, 2*PAGE_SIZE, pce_dev->coh_vmem, + pce_dev->coh_pmem); + kfree(pce_dev->chan_ce_in_cmd); + kfree(pce_dev->chan_ce_out_cmd); + + clk_put(pce_dev->ce_clk); + kfree(handle); + 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.15"); + diff --git a/drivers/crypto/msm/qce.h b/drivers/crypto/msm/qce.h new file mode 100644 index 0000000000000000000000000000000000000000..edd2089b4558b3223c41d11387494629fb7bbae0 --- /dev/null +++ b/drivers/crypto/msm/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 0xFF00 + +/* 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/qce40.c b/drivers/crypto/msm/qce40.c new file mode 100644 index 0000000000000000000000000000000000000000..c203fc515de31f3854698032b4718ae39c941ebe --- /dev/null +++ b/drivers/crypto/msm/qce40.c @@ -0,0 +1,2609 @@ +/* Qualcomm Crypto Engine driver. + * + * Copyright (c) 2011 - 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "qce.h" +#include "qce40.h" +#include "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) +#define QCE_FIFO_SIZE 0x8000 + +/* + * 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 */ + int memsize; /* Memory allocated */ + + void __iomem *iobase; /* Virtual io base of CE HW */ + unsigned int phy_iobase; /* Physical io base of CE HW */ + + struct clk *ce_core_src_clk; /* Handle to CE src clk*/ + struct clk *ce_core_clk; /* Handle to CE clk */ + struct clk *ce_clk; /* Handle to CE clk */ + + 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; + struct ce_dm_data ce_dm; +}; + +/* Standard initialization vector for SHA-1, source: FIPS 180-2 */ +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 +}; + +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]; + memcpy(iv, swap_iv, AES_IV_LENGTH); +} + +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; + unsigned int ret; + + val = (uint32_t)(*((uint32_t *)pce_dev->ce_dm.buffer.version)); + if (((val & 0xfffffff) != 0x0000043) && + ((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); + pce_dev->ce_dm.ce_block_size = 64; + + /* Configure the crypto register to support 64byte CRCI if it + * is not XPU protected and the HW version of device is greater + * than 0x42. + * Crypto config register returns a 0 when it is XPU protected. + */ + + ret = readl_relaxed(pce_dev->iobase + CRYPTO_CONFIG_REG); + if (ret) { + val = BIT(CRYPTO_MASK_DOUT_INTR) | + BIT(CRYPTO_MASK_DIN_INTR) | + BIT(CRYPTO_MASK_OP_DONE_INTR) | + BIT(CRYPTO_MASK_ERR_INTR) | + (CRYPTO_REQ_SIZE_ENUM_64_BYTES << + CRYPTO_REQ_SIZE) | + (CRYPTO_FIFO_ENUM_64_BYTES << + CRYPTO_FIFO_THRESHOLD); + + writel_relaxed(val, pce_dev->iobase + + CRYPTO_CONFIG_REG); + } /* end of if (ret) */ + } else { + if (rev == 0x40) { + dev_info(pce_dev->pdev, + "Qualcomm Crypto 4.0 device found at 0x%x\n", + pce_dev->phy_iobase); + pce_dev->ce_dm.ce_block_size = 16; + } + } + + dev_info(pce_dev->pdev, + "IO base 0x%x\n, ce_in channel %d , " + "ce_out channel %d\n, " + "crci_in %d, crci_out %d\n", + (unsigned int) pce_dev->iobase, + pce_dev->ce_dm.chan_ce_in, pce_dev->ce_dm.chan_ce_out, + pce_dev->ce_dm.crci_in, pce_dev->ce_dm.crci_out); + + return 0; +}; + + +static void _check_probe_done_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->ce_dm.chan_ce_in_status = -1; + } else { + _probe_ce_engine(pce_dev); + pce_dev->ce_dm.chan_ce_in_status = 0; + } + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; +}; + +static int _init_ce_engine(struct qce_device *pce_dev) +{ + int status; + /* 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 before configuring + * ce engine. The ce engine configuration settings should not be lost + * becasue of clk reset. + */ + mb(); + + /* + * Clear ACCESS_VIOL bit in CRYPTO_STATUS REGISTER + */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + *((uint32_t *)(pce_dev->ce_dm.buffer.status)) = status & (~0x40000); + /* + * Ensure ce configuration is completed. + */ + mb(); + + pce_dev->ce_dm.chan_ce_in_cmd->complete_func = + _check_probe_done_call_back; + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + pce_dev->ce_dm.cmdptrlist.probe_ce_hw; + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IN_PROG; + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_COMP; + msm_dmov_enqueue_cmd(pce_dev->ce_dm.chan_ce_in, + pce_dev->ce_dm.chan_ce_in_cmd); + + return 0; +}; + +static int _ce_setup_hash_cmdrptrlist(struct qce_device *pce_dev, + struct qce_sha_req *sreq) +{ + struct ce_cmdptrlists_ops *cmdptrlist = &pce_dev->ce_dm.cmdptrlist; + + switch (sreq->alg) { + case QCE_HASH_SHA1: + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = cmdptrlist->auth_sha1; + break; + + case QCE_HASH_SHA256: + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = cmdptrlist->auth_sha256; + break; + case QCE_HASH_SHA1_HMAC: + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->auth_sha1_hmac; + break; + + case QCE_HASH_SHA256_HMAC: + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->auth_sha256_hmac; + break; + case QCE_HASH_AES_CMAC: + if (sreq->authklen == AES128_KEY_SIZE) + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->auth_aes_128_cmac; + else + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->auth_aes_256_cmac; + break; + + default: + break; + } + + return 0; +} + +static int _ce_setup_hash(struct qce_device *pce_dev, struct qce_sha_req *sreq) +{ + uint32_t diglen; + int i; + uint32_t auth_cfg = 0; + bool sha1 = false; + + if (sreq->alg == QCE_HASH_AES_CMAC) { + + memcpy(pce_dev->ce_dm.buffer.auth_key, sreq->authkey, + sreq->authklen); + 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)) { + + memcpy(pce_dev->ce_dm.buffer.auth_key, sreq->authkey, + sreq->authklen); + 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) + memcpy(pce_dev->ce_dm.buffer.auth_iv, + _std_init_vector_sha1_uint8, diglen); + else + memcpy(pce_dev->ce_dm.buffer.auth_iv, + _std_init_vector_sha256_uint8, diglen); + } else { + memcpy(pce_dev->ce_dm.buffer.auth_iv, sreq->digest, + diglen); + } + + /* write auth_bytecnt 0/1/2/3, start with 0 */ + for (i = 0; i < 4; i++) + *(((uint32_t *)(pce_dev->ce_dm.buffer.auth_byte_count) + i)) = + sreq->auth_data[i]; + + /* 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 auth seg cfg */ + *((uint32_t *)(pce_dev->ce_dm.buffer.auth_seg_cfg_size_start)) = + auth_cfg; + /* write auth seg size */ + *((uint32_t *)(pce_dev->ce_dm.buffer.auth_seg_cfg_size_start) + 1) = + sreq->size; + + /* write auth seg size start*/ + *((uint32_t *)(pce_dev->ce_dm.buffer.auth_seg_cfg_size_start)+2) = 0; + + /* write seg size */ + *((uint32_t *)(pce_dev->ce_dm.buffer.seg_size)) = sreq->size; + + _ce_setup_hash_cmdrptrlist(pce_dev, sreq); + + return 0; +} + +static int _ce_setup_cipher_cmdrptrlist(struct qce_device *pce_dev, + struct qce_req *creq) +{ + struct ce_cmdptrlists_ops *cmdptrlist = + &pce_dev->ce_dm.cmdptrlist; + + if (creq->alg != CIPHER_ALG_AES) { + switch (creq->alg) { + case CIPHER_ALG_DES: + if (creq->mode == QCE_MODE_ECB) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_des_ecb; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_des_cbc; + } + break; + + case CIPHER_ALG_3DES: + if (creq->mode == QCE_MODE_ECB) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_3des_ecb; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_3des_cbc; + } + break; + default: + break; + } + } else { + switch (creq->mode) { + case QCE_MODE_ECB: + if (creq->encklen == AES128_KEY_SIZE) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_128_ecb; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_256_ecb; + } + break; + + case QCE_MODE_CBC: + if (creq->encklen == AES128_KEY_SIZE) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_128_cbc_ctr; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_256_cbc_ctr; + } + break; + + case QCE_MODE_CTR: + if (creq->encklen == AES128_KEY_SIZE) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_128_cbc_ctr; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_256_cbc_ctr; + } + break; + + case QCE_MODE_XTS: + if (creq->encklen == AES128_KEY_SIZE) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_128_xts; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->cipher_aes_256_xts; + } + break; + case QCE_MODE_CCM: + if (creq->encklen == AES128_KEY_SIZE) { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->aead_aes_128_ccm; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->cmdptr = + cmdptrlist->aead_aes_256_ccm; + } + break; + default: + break; + } + } + + switch (creq->mode) { + case QCE_MODE_CCM: + pce_dev->ce_dm.chan_ce_out_cmd->cmdptr = + cmdptrlist->aead_ce_out; + break; + case QCE_MODE_ECB: + pce_dev->ce_dm.chan_ce_out_cmd->cmdptr = + cmdptrlist->cipher_ce_out; + break; + default: + pce_dev->ce_dm.chan_ce_out_cmd->cmdptr = + cmdptrlist->cipher_ce_out_get_iv; + break; + } + + 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 enck_size_in_word = creq->encklen / sizeof(uint32_t); + uint32_t encr_cfg = 0; + uint32_t ivsize = creq->ivsize; + struct ce_reg_buffer_addr *buffer = &pce_dev->ce_dm.buffer; + + if (creq->mode == QCE_MODE_XTS) + memcpy(buffer->encr_key, creq->enckey, + creq->encklen/2); + else + memcpy(buffer->encr_key, creq->enckey, creq->encklen); + + if ((creq->op == QCE_REQ_AEAD) && (creq->mode == QCE_MODE_CCM)) { + uint32_t noncelen32 = MAX_NONCE/sizeof(uint32_t); + uint32_t auth_cfg = 0; + + /* write nonce */ + memcpy(buffer->auth_nonce_info, creq->nonce, MAX_NONCE); + memcpy(buffer->auth_key, creq->enckey, creq->encklen); + + 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); + *((uint32_t *)(buffer->auth_seg_cfg_size_start)) = auth_cfg; + + if (creq->dir == QCE_ENCRYPT) + *((uint32_t *)(buffer->auth_seg_cfg_size_start) + 1) = + totallen_in; + else + *((uint32_t *)(buffer->auth_seg_cfg_size_start) + 1) = + (totallen_in - creq->authsize); + *((uint32_t *)(buffer->auth_seg_cfg_size_start) + 2) = 0; + } + + *((uint32_t *)(buffer->auth_seg_cfg_size_start) + 2) = 0; + + 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) + memcpy(buffer->encr_cntr_iv, creq->iv, ivsize); + + 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) + memcpy(buffer->encr_cntr_iv, creq->iv, ivsize); + + 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) { + memcpy(buffer->encr_xts_key, (creq->enckey + + creq->encklen/2), creq->encklen/2); + *((uint32_t *)(buffer->encr_xts_du_size)) = + creq->cryptlen; + + } + if (creq->mode != QCE_MODE_ECB) { + if (creq->mode == QCE_MODE_XTS) + _byte_stream_swap_to_net_words( + (uint32_t *)(buffer->encr_cntr_iv), + creq->iv, ivsize); + else + memcpy(buffer->encr_cntr_iv, creq->iv, + ivsize); + } + /* set number of counter bits */ + *((uint32_t *)(buffer->encr_mask)) = (uint32_t)0xffffffff; + + 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); + break; + } /* end of switch (creq->encklen) */ + + encr_cfg |= CRYPTO_ENCR_ALG_AES << CRYPTO_ENCR_ALG; + } /* 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 */ + *((uint32_t *)(buffer->encr_seg_cfg_size_start)) = encr_cfg; + /* write encr seg size */ + if ((creq->mode == QCE_MODE_CCM) && (creq->dir == QCE_DECRYPT)) + *((uint32_t *)(buffer->encr_seg_cfg_size_start) + 1) = + (creq->cryptlen + creq->authsize); + else + *((uint32_t *)(buffer->encr_seg_cfg_size_start) + 1) = + creq->cryptlen; + + + *((uint32_t *)(buffer->encr_seg_cfg_size_start) + 2) = + (coffset & 0xffff); + + *((uint32_t *)(buffer->seg_size)) = totallen_in; + + _ce_setup_cipher_cmdrptrlist(pce_dev, creq); + return 0; +}; + +static int _aead_complete(struct qce_device *pce_dev) +{ + struct aead_request *areq; + + areq = (struct aead_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); + + dma_unmap_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + + /* check MAC */ + if (pce_dev->mode == QCE_MODE_CCM) { + uint32_t result; + + result = + (uint32_t)(*((uint32_t *)pce_dev->ce_dm.buffer.status)); + result &= (1 << CRYPTO_MAC_FAILED); + result |= (pce_dev->ce_dm.chan_ce_in_status | + pce_dev->ce_dm.chan_ce_out_status); + pce_dev->qce_cb(areq, pce_dev->ce_dm.buffer.auth_result, NULL, + result); + } + return 0; +}; + +static void _sha_complete(struct qce_device *pce_dev) +{ + struct ahash_request *areq; + + areq = (struct ahash_request *) pce_dev->areq; + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + DMA_TO_DEVICE); + + pce_dev->qce_cb(areq, pce_dev->ce_dm.buffer.auth_result, + pce_dev->ce_dm.buffer.auth_byte_count, + pce_dev->ce_dm.chan_ce_in_status); + +}; + +static int _ablk_cipher_complete(struct qce_device *pce_dev) +{ + struct ablkcipher_request *areq; + + 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); + + if (pce_dev->mode == QCE_MODE_ECB) { + pce_dev->qce_cb(areq, NULL, NULL, + pce_dev->ce_dm.chan_ce_in_status | + pce_dev->ce_dm.chan_ce_out_status); + } else { + + pce_dev->qce_cb(areq, NULL, pce_dev->ce_dm.buffer.encr_cntr_iv, + pce_dev->ce_dm.chan_ce_in_status | + pce_dev->ce_dm.chan_ce_out_status); + } + + return 0; +}; + +static int _ablk_cipher_use_pmem_complete(struct qce_device *pce_dev) +{ + struct ablkcipher_request *areq; + + areq = (struct ablkcipher_request *) pce_dev->areq; + + if (pce_dev->mode == QCE_MODE_ECB) { + pce_dev->qce_cb(areq, NULL, NULL, + pce_dev->ce_dm.chan_ce_in_status | + pce_dev->ce_dm.chan_ce_out_status); + } else { + pce_dev->qce_cb(areq, NULL, pce_dev->ce_dm.buffer.encr_cntr_iv, + pce_dev->ce_dm.chan_ce_in_status | + pce_dev->ce_dm.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 > QCE_FIFO_SIZE) { + pdesc->len = QCE_FIFO_SIZE; + if (paddr > 0) { + pdesc->addr = paddr; + paddr += QCE_FIFO_SIZE; + } + plen -= pdesc->len; + if (plen > 0) { + *index = (*index) + 1; + if ((*index) >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + } + } + if ((plen > 0) && (plen <= QCE_FIFO_SIZE)) { + 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_dm.ce_in_src_desc + + pce_dev->ce_dm.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 > QCE_FIFO_SIZE) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_dm.ce_in_src_desc_index); + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + if (pdesc->len > QCE_FIFO_SIZE) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + pdesc->addr, + &pce_dev->ce_dm.ce_in_src_desc_index); + } else { + pce_dev->ce_dm.ce_in_src_desc_index++; + if (pce_dev->ce_dm.ce_in_src_desc_index >= + QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + pdesc->len = len; + pdesc->addr = sg_dma_address(sg); + if (pdesc->len > QCE_FIFO_SIZE) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_dm.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_dm.ce_in_src_desc + + pce_dev->ce_dm.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_dm.ce_in_src_desc_index++; + if (pce_dev->ce_dm.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_dm.ce_in_src_desc_index = 0; + pce_dev->ce_dm.ce_in_dst_desc_index = 0; + pdesc = pce_dev->ce_dm.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_dm.ce_in_src_desc + + pce_dev->ce_dm.ce_in_src_desc_index; + pdesc->len |= ADM_DESC_LAST; + + pdesc = pce_dev->ce_dm.ce_in_dst_desc; + if (total > QCE_FIFO_SIZE) { + qce_split_and_insert_dm_desc(pdesc, total, 0, + &pce_dev->ce_dm.ce_in_dst_desc_index); + pdesc = pce_dev->ce_dm.ce_in_dst_desc + + pce_dev->ce_dm.ce_in_dst_desc_index; + pdesc->len |= ADM_DESC_LAST; + } else + pdesc->len = ADM_DESC_LAST | total; + + pcmd = (dmov_sg *) pce_dev->ce_dm.cmdlist.ce_data_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_dm.ce_in_src_desc_index; i++) { + pdesc = pce_dev->ce_dm.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_dm.ce_in_dst_desc_index; i++) { + pdesc = pce_dev->ce_dm.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_dm.ce_out_src_desc_index; i++) { + pdesc = pce_dev->ce_dm.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_dm.ce_out_dst_desc_index; i++) { + pdesc = pce_dev->ce_dm.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_dm.ce_out_dst_desc + + pce_dev->ce_dm.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 > QCE_FIFO_SIZE) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_dm.ce_out_dst_desc_index); + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + if (pdesc->len > QCE_FIFO_SIZE) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + pdesc->addr, + &pce_dev->ce_dm.ce_out_dst_desc_index); + + } else { + pce_dev->ce_dm.ce_out_dst_desc_index++; + if (pce_dev->ce_dm.ce_out_dst_desc_index >= + QCE_MAX_NUM_DESC) + return -EIO; + pdesc++; + pdesc->len = len; + pdesc->addr = sg_dma_address(sg); + if (pdesc->len > QCE_FIFO_SIZE) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_dm.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_dm.ce_out_dst_desc + + pce_dev->ce_dm.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_dm.ce_out_dst_desc_index++; + if (pce_dev->ce_dm.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_dm.ce_out_dst_desc_index = 0; + pce_dev->ce_dm.ce_out_src_desc_index = 0; + pdesc = pce_dev->ce_dm.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_dm.ce_out_dst_desc + + pce_dev->ce_dm.ce_out_dst_desc_index; + pdesc->len |= ADM_DESC_LAST; + + pdesc = pce_dev->ce_dm.ce_out_src_desc + + pce_dev->ce_dm.ce_out_src_desc_index; + if (total > QCE_FIFO_SIZE) { + qce_split_and_insert_dm_desc(pdesc, total, 0, + &pce_dev->ce_dm.ce_out_src_desc_index); + pdesc = pce_dev->ce_dm.ce_out_src_desc + + pce_dev->ce_dm.ce_out_src_desc_index; + pdesc->len |= ADM_DESC_LAST; + } else + pdesc->len = ADM_DESC_LAST | total; + + pcmd = (dmov_sg *) pce_dev->ce_dm.cmdlist.ce_data_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->ce_dm.chan_ce_in_status = -1; + } else { + pce_dev->ce_dm.chan_ce_in_status = 0; + } + + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->ce_dm.chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.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->ce_dm.chan_ce_out_status = -1; + } else { + pce_dev->ce_dm.chan_ce_out_status = 0; + }; + + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->ce_dm.chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.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->ce_dm.chan_ce_in_status = -1; + } else { + pce_dev->ce_dm.chan_ce_in_status = 0; + } + pce_dev->ce_dm.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->ce_dm.chan_ce_in_status = -1; + } else { + pce_dev->ce_dm.chan_ce_in_status = 0; + } + + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->ce_dm.chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.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->ce_dm.chan_ce_out_status = -1; + } else { + pce_dev->ce_dm.chan_ce_out_status = 0; + }; + + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->ce_dm.chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.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->ce_dm.chan_ce_in_status = -1; + } else { + pce_dev->ce_dm.chan_ce_in_status = 0; + } + + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->ce_dm.chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.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->ce_dm.chan_ce_out_status = -1; + } else { + pce_dev->ce_dm.chan_ce_out_status = 0; + }; + + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->ce_dm.chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_use_pmem_complete(pce_dev); + } +}; + +static int qce_setup_cmd_buffers(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + struct ce_reg_buffers *addr = (struct ce_reg_buffers *)(*pvaddr); + struct ce_reg_buffer_addr *buffer = &pce_dev->ce_dm.buffer; + + /* + * Designate chunks of the allocated memory to various + * buffer pointers + */ + buffer->reset_buf_64 = addr->reset_buf_64; + buffer->version = addr->version; + buffer->encr_seg_cfg_size_start = addr->encr_seg_cfg_size_start; + buffer->encr_key = addr->encr_key; + buffer->encr_xts_key = addr->encr_xts_key; + buffer->encr_xts_du_size = addr->encr_xts_du_size; + buffer->encr_cntr_iv = addr->encr_cntr_iv; + buffer->encr_mask = addr->encr_mask; + buffer->auth_seg_cfg_size_start = addr->auth_seg_cfg_size_start; + buffer->auth_key = addr->auth_key; + buffer->auth_iv = addr->auth_iv; + buffer->auth_result = addr->auth_result; + buffer->auth_nonce_info = addr->auth_nonce_info; + buffer->auth_byte_count = addr->auth_byte_count; + buffer->seg_size = addr->seg_size; + buffer->go_proc = addr->go_proc; + buffer->status = addr->status; + buffer->pad = addr->pad; + + memset(buffer->reset_buf_64, 0, 64); + *((uint32_t *)buffer->encr_mask) = (uint32_t)(0xffffffff); + *((uint32_t *)buffer->go_proc) = (uint32_t)(1 << CRYPTO_GO); + + *pvaddr += sizeof(struct ce_reg_buffers); + + return 0; + +} + +static int _setup_cipher_cmdlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + dmov_s *pscmd = (dmov_s *)(*pvaddr); + + /* + * Designate chunks of the allocated memory to various + * command list pointers related to cipher operation + */ + pce_dev->ce_dm.cmdlist.set_cipher_cfg = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_SEG_CFG_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 3; + pscmd->src = + GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_seg_cfg_size_start); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_aes_128_key = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_aes_256_key = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_des_key = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 2; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_3des_key = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 6; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_aes_128_xts_key = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_XTS_KEY0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_xts_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_aes_256_xts_key = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_XTS_KEY0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_xts_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_xts_du_size = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_XTS_DU_SIZE_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_xts_du_size); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_aes_iv = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_CNTR0_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_cntr_iv); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_des_iv = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_CNTR0_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 2; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_cntr_iv); + pscmd++; + + pce_dev->ce_dm.cmdlist.get_cipher_iv = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_CNTR0_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_cntr_iv); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_cipher_mask = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_CNTR_MASK_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.encr_mask); + pscmd++; + + /* RESET CIPHER AND AUTH REGISTERS COMMAND LISTS*/ + + pce_dev->ce_dm.cmdlist.reset_cipher_key = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + pce_dev->ce_dm.cmdlist.reset_cipher_xts_key = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_XTS_KEY0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + pce_dev->ce_dm.cmdlist.reset_cipher_iv = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_CNTR0_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + pce_dev->ce_dm.cmdlist.reset_cipher_cfg = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_ENCR_SEG_CFG_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + *pvaddr = (unsigned char *) pscmd; + + return 0; +} + +static int _setup_auth_cmdlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + dmov_s *pscmd = (dmov_s *)(*pvaddr); + + /* + * Designate chunks of the allocated memory to various + * command list pointers related to authentication operation + */ + pce_dev->ce_dm.cmdlist.set_auth_cfg = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_SEG_CFG_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 3; + pscmd->src = + GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_seg_cfg_size_start); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_key_128 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_key_256 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_key_512 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 16; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_key); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_iv_16 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_iv); + pscmd++; + + pce_dev->ce_dm.cmdlist.get_auth_result_16 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_result); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_iv_20 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 5; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_iv); + pscmd++; + + pce_dev->ce_dm.cmdlist.get_auth_result_20 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 5; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_result); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_iv_32 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_iv); + pscmd++; + + + pce_dev->ce_dm.cmdlist.get_auth_result_32 = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 8; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_result); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_byte_count = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_BYTECNT0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_byte_count); + pscmd++; + + pce_dev->ce_dm.cmdlist.get_auth_byte_count = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_AUTH_BYTECNT0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_byte_count); + pscmd++; + + pce_dev->ce_dm.cmdlist.set_auth_nonce_info = pscmd; + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | + CMD_SRC_SWAP_SHORTS | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_INFO_NONCE0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.auth_nonce_info); + pscmd++; + + /* RESET CIPHER AND AUTH REGISTERS COMMAND LISTS*/ + + pce_dev->ce_dm.cmdlist.reset_auth_key = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_KEY0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 16; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + pce_dev->ce_dm.cmdlist.reset_auth_iv = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 16; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + pce_dev->ce_dm.cmdlist.reset_auth_cfg = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_SEG_CFG_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + + pce_dev->ce_dm.cmdlist.reset_auth_byte_count = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_AUTH_BYTECNT0_REG + + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE * 4; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.reset_buf_64); + pscmd++; + + /* WAIT UNTIL MAC OP IS DONE*/ + + pce_dev->ce_dm.cmdlist.get_status_wait = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_STATUS_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.status); + pscmd++; + + *pvaddr = (unsigned char *) pscmd; + + return 0; +} + +static int qce_setup_cmdlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + dmov_sg *pcmd; + dmov_s *pscmd; + unsigned char *vaddr = *pvaddr; + struct dmov_desc *pdesc; + int i = 0; + + /* + * Designate chunks of the allocated memory to various + * command list pointers related to operation define + * in ce_cmdlists structure. + */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + *pvaddr = (unsigned char *) vaddr; + + _setup_cipher_cmdlists(pce_dev, pvaddr); + _setup_auth_cmdlists(pce_dev, pvaddr); + + pscmd = (dmov_s *)(*pvaddr); + + /* GET HW VERSION COMMAND LIST */ + pce_dev->ce_dm.cmdlist.get_hw_version = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE | CMD_OCB; + pscmd->src = (unsigned) (CRYPTO_VERSION_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.version); + pscmd++; + + + /* SET SEG SIZE REGISTER and OCB COMMAND LIST */ + pce_dev->ce_dm.cmdlist.set_seg_size_ocb = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE | CMD_OCB; + pscmd->dst = (unsigned) (CRYPTO_SEG_SIZE_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.seg_size); + pscmd++; + + + /* OCU COMMAND LIST */ + pce_dev->ce_dm.cmdlist.get_status_ocu = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE | CMD_OCU; + pscmd->src = (unsigned) (CRYPTO_STATUS_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->dst = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.status); + pscmd++; + + /* CLEAR STATUS COMMAND LIST */ + pce_dev->ce_dm.cmdlist.clear_status = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE | CMD_OCU; + pscmd->dst = (unsigned) (CRYPTO_STATUS_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.status); + pscmd++; + + /* SET GO_PROC REGISTERS COMMAND LIST */ + pce_dev->ce_dm.cmdlist.set_go_proc = pscmd; + pscmd->cmd = CMD_LC | CMD_MODE_SINGLE; + pscmd->dst = (unsigned) (CRYPTO_GOPROC_REG + pce_dev->phy_iobase); + pscmd->len = CRYPTO_REG_SIZE; + pscmd->src = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.go_proc); + pscmd++; + + pcmd = (dmov_sg *)pscmd; + pce_dev->ce_dm.cmdlist.ce_data_in = pcmd; + /* swap byte and half word , dst crci , scatter gather */ + pcmd->cmd = CMD_DST_SWAP_BYTES | CMD_DST_SWAP_SHORTS | + CMD_DST_CRCI(pce_dev->ce_dm.crci_in) | CMD_MODE_SG; + + pdesc = pce_dev->ce_dm.ce_in_src_desc; + pdesc->addr = 0; /* to be filled in each operation */ + pdesc->len = 0; /* to be filled in each operation */ + + pdesc = pce_dev->ce_dm.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->src_dscr = GET_PHYS_ADDR(pce_dev->ce_dm.ce_in_src_desc); + pcmd->dst_dscr = GET_PHYS_ADDR(pce_dev->ce_dm.ce_in_dst_desc); + pcmd->_reserved = LI_SG_CMD | SRC_INDEX_SG_CMD(0) | + DST_INDEX_SG_CMD(0); + + + pcmd++; + pce_dev->ce_dm.cmdlist.ce_data_out = pcmd; + /* swap byte, half word, source crci, scatter gather */ + pcmd->cmd = CMD_SRC_SWAP_BYTES | CMD_SRC_SWAP_SHORTS | + CMD_SRC_CRCI(pce_dev->ce_dm.crci_out) | CMD_MODE_SG; + + pdesc = pce_dev->ce_dm.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++; + } + + pdesc = pce_dev->ce_dm.ce_out_dst_desc; + pdesc->addr = 0; /* to be filled in each operation */ + pdesc->len = 0; /* to be filled in each operation */ + + pcmd->src_dscr = GET_PHYS_ADDR(pce_dev->ce_dm.ce_out_src_desc); + pcmd->dst_dscr = GET_PHYS_ADDR(pce_dev->ce_dm.ce_out_dst_desc); + pcmd->_reserved = LI_SG_CMD | SRC_INDEX_SG_CMD(0) | + DST_INDEX_SG_CMD(0); + pcmd++; + + *pvaddr = (unsigned char *) pcmd; + + return 0; +} + +static int _setup_cipher_cmdptrlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + uint32_t * cmd_ptr_vaddr = (uint32_t *)(*pvaddr); + struct ce_cmdlists *cmdlist = &pce_dev->ce_dm.cmdlist; + struct ce_cmdptrlists_ops *cmdptrlist = &pce_dev->ce_dm.cmdptrlist; + + /* + * Designate chunks of the allocated memory to various + * command list pointers related to cipher operations defined + * in ce_cmdptrlists_ops structure. + */ + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_aes_128_cbc_ctr = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_128_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_aes_256_cbc_ctr = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_256_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_aes_128_ecb = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_128_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *)ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_aes_256_ecb = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_256_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *)ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_aes_128_xts = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_128_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_128_xts_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_xts_du_size); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_aes_256_xts = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_256_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_256_xts_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_xts_du_size); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *)ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_des_cbc = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_des_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_des_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *)ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_des_ecb = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_des_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_3des_cbc = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_3des_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_des_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_3des_ecb = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_3des_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_ce_out = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_out); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->cipher_ce_out_get_iv = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_out); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_cipher_iv); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + *pvaddr = (unsigned char *) cmd_ptr_vaddr; + + return 0; +} + +static int _setup_auth_cmdptrlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + uint32_t * cmd_ptr_vaddr = (uint32_t *)(*pvaddr); + struct ce_cmdlists *cmdlist = &pce_dev->ce_dm.cmdlist; + struct ce_cmdptrlists_ops *cmdptrlist = &pce_dev->ce_dm.cmdptrlist; + + /* + * Designate chunks of the allocated memory to various + * command list pointers related to authentication operations + * defined in ce_cmdptrlists_ops structure. + */ + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->auth_sha1 = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_iv_20); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_in); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_result_20); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->auth_sha256 = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_iv_32); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_in); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_result_32); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->auth_sha1_hmac = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_key_512); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_iv_20); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_in); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_result_20); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->auth_sha256_hmac = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_key_512); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_iv_32); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_in); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_result_32); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->auth_aes_128_cmac = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_key_128); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_in); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_result_16); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->auth_aes_256_cmac = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_key_256); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_in); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_auth_result_16); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + *pvaddr = (unsigned char *) cmd_ptr_vaddr; + + return 0; +} + +static int _setup_aead_cmdptrlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + uint32_t * cmd_ptr_vaddr = (uint32_t *)(*pvaddr); + struct ce_cmdlists *cmdlist = &pce_dev->ce_dm.cmdlist; + struct ce_cmdptrlists_ops *cmdptrlist = &pce_dev->ce_dm.cmdptrlist; + + /* + * Designate chunks of the allocated memory to various + * command list pointers related to aead operations + * defined in ce_cmdptrlists_ops structure. + */ + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->aead_aes_128_ccm = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_key_128); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_nonce_info); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_128_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->aead_aes_256_ccm = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_seg_size_ocb); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->reset_auth_byte_count); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_key_256); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_nonce_info); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_auth_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_cfg); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_256_key); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_aes_iv); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_cipher_mask); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->set_go_proc); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->ce_data_in); + + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->aead_ce_out = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->ce_data_out); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_status_wait); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + *pvaddr = (unsigned char *) cmd_ptr_vaddr; + + return 0; +} + +static int qce_setup_cmdptrlists(struct qce_device *pce_dev, + unsigned char **pvaddr) +{ + uint32_t * cmd_ptr_vaddr = (uint32_t *)(*pvaddr); + struct ce_cmdlists *cmdlist = &pce_dev->ce_dm.cmdlist; + struct ce_cmdptrlists_ops *cmdptrlist = &pce_dev->ce_dm.cmdptrlist; + /* + * Designate chunks of the allocated memory to various + * command list pointers related to operations defined + * in ce_cmdptrlists_ops structure. + */ + cmd_ptr_vaddr = (uint32_t *) ALIGN(((unsigned int) cmd_ptr_vaddr), 16); + cmdptrlist->probe_ce_hw = QCE_SET_CMD_PTR(cmd_ptr_vaddr); + + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->get_hw_version); + *cmd_ptr_vaddr++ = QCE_SET_CMD_PTR(cmdlist->clear_status); + *cmd_ptr_vaddr++ = QCE_SET_LAST_CMD_PTR(cmdlist->get_status_ocu); + + *pvaddr = (unsigned char *) cmd_ptr_vaddr; + + _setup_cipher_cmdptrlists(pce_dev, pvaddr); + _setup_auth_cmdptrlists(pce_dev, pvaddr); + _setup_aead_cmdptrlists(pce_dev, pvaddr); + + return 0; +} + + +static int qce_setup_ce_dm_data(struct qce_device *pce_dev) +{ + unsigned char *vaddr; + + /* 1. ce_in channel data xfer command src descriptors, 128 entries */ + vaddr = pce_dev->coh_vmem; + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_dm.ce_in_src_desc = (struct dmov_desc *) vaddr; + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 2. ce_in channel data xfer command dst descriptors, 128 entries */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_dm.ce_in_dst_desc = (struct dmov_desc *) vaddr; + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + + /* 3. ce_out channel data xfer command src descriptors, 128 entries */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_dm.ce_out_src_desc = (struct dmov_desc *) vaddr; + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 4. ce_out channel data xfer command dst descriptors, 128 entries. */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_dm.ce_out_dst_desc = (struct dmov_desc *) vaddr; + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + qce_setup_cmd_buffers(pce_dev, &vaddr); + qce_setup_cmdlists(pce_dev, &vaddr); + qce_setup_cmdptrlists(pce_dev, &vaddr); + + pce_dev->ce_dm.buffer.ignore_data = vaddr; + + pce_dev->ce_dm.phy_ce_pad = GET_PHYS_ADDR(pce_dev->ce_dm.buffer.pad); + pce_dev->ce_dm.phy_ce_out_ignore = + GET_PHYS_ADDR(pce_dev->ce_dm.buffer.ignore_data); + + pce_dev->ce_dm.chan_ce_in_cmd->user = (void *) pce_dev; + pce_dev->ce_dm.chan_ce_in_cmd->exec_func = NULL; + + pce_dev->ce_dm.chan_ce_out_cmd->user = (void *) pce_dev; + pce_dev->ce_dm.chan_ce_out_cmd->exec_func = NULL; + + return 0; +} + +static int _qce_start_dma(struct qce_device *pce_dev, bool ce_in, bool ce_out) +{ + + if (ce_in) + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IN_PROG; + else + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_COMP; + + if (ce_out) + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_IN_PROG; + else + pce_dev->ce_dm.chan_ce_out_state = QCE_CHAN_STATE_COMP; + + if (ce_in) + msm_dmov_enqueue_cmd(pce_dev->ce_dm.chan_ce_in, + pce_dev->ce_dm.chan_ce_in_cmd); + if (ce_out) + msm_dmov_enqueue_cmd(pce_dev->ce_dm.chan_ce_out, + pce_dev->ce_dm.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; + int rc = 0; + int ce_block_size; + + ce_block_size = pce_dev->ce_dm.ce_block_size; + if (q_req->dir == QCE_ENCRYPT) { + uint32_t pad_mac_len_out; + + q_req->cryptlen = areq->cryptlen; + totallen_in = q_req->cryptlen + areq->assoclen; + pad_len_in = ALIGN(totallen_in, ce_block_size) - totallen_in; + + out_len = areq->cryptlen + authsize; + totallen_out = q_req->cryptlen + authsize + areq->assoclen; + pad_mac_len_out = ALIGN(authsize, ce_block_size) - authsize; + totallen_out += pad_mac_len_out; + pad_len_out = ALIGN(totallen_out, ce_block_size) - + totallen_out + pad_mac_len_out; + + } else { + q_req->cryptlen = areq->cryptlen - authsize; + totallen_in = areq->cryptlen + areq->assoclen; + pad_len_in = ALIGN(totallen_in, ce_block_size) - totallen_in; + + out_len = q_req->cryptlen; + totallen_out = totallen_in; + pad_len_out = ALIGN(totallen_out, ce_block_size) - totallen_out; + pad_len_out += authsize; + } + + _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->ce_dm.phy_ce_pad, + pad_len_in) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* ignore associated data */ + if (_chain_pm_buffer_out(pce_dev, pce_dev->ce_dm.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->ce_dm.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, ce_block_size)); + _ce_out_final(pce_dev, ALIGN(totallen_out, 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->ce_dm.chan_ce_in_cmd->complete_func = _aead_ce_in_call_back; + pce_dev->ce_dm.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, pce_dev->ce_dm.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->ce_dm.phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + if (_chain_pm_buffer_out(pce_dev, pce_dev->ce_dm.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->ce_dm.chan_ce_in_cmd->complete_func = + _ablk_cipher_ce_in_call_back_pmem; + pce_dev->ce_dm.chan_ce_out_cmd->complete_func = + _ablk_cipher_ce_out_call_back_pmem; + } else { + pce_dev->ce_dm.chan_ce_in_cmd->complete_func = + _ablk_cipher_ce_in_call_back; + pce_dev->ce_dm.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, pce_dev->ce_dm.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->ce_dm.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->ce_dm.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; + struct clk *ce_core_src_clk; + int ret = 0; + + 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->ce_dm.chan_ce_in_cmd = kzalloc(sizeof(struct msm_dmov_cmd), + GFP_KERNEL); + pce_dev->ce_dm.chan_ce_out_cmd = kzalloc(sizeof(struct msm_dmov_cmd), + GFP_KERNEL); + if (pce_dev->ce_dm.chan_ce_in_cmd == NULL || + pce_dev->ce_dm.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->ce_dm.chan_ce_in = resource->start; + pce_dev->ce_dm.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->ce_dm.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->ce_dm.crci_out = resource->start; + pce_dev->memsize = 2 * PAGE_SIZE; + pce_dev->coh_vmem = dma_alloc_coherent(pce_dev->pdev, + pce_dev->memsize, &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 CE3 src core clk. */ + ce_core_src_clk = clk_get(pce_dev->pdev, "ce3_core_src_clk"); + if (!IS_ERR(ce_core_src_clk)) { + pce_dev->ce_core_src_clk = ce_core_src_clk; + + /* Set the core src clk @100Mhz */ + ret = clk_set_rate(pce_dev->ce_core_src_clk, 100000000); + if (ret) { + clk_put(pce_dev->ce_core_src_clk); + goto err; + } + } else + pce_dev->ce_core_src_clk = NULL; + + /* Get CE core clk */ + ce_core_clk = clk_get(pce_dev->pdev, "core_clk"); + if (IS_ERR(ce_core_clk)) { + *rc = PTR_ERR(ce_core_clk); + if (pce_dev->ce_core_src_clk != NULL) + clk_put(pce_dev->ce_core_src_clk); + goto err; + } + pce_dev->ce_core_clk = ce_core_clk; + /* Get CE clk */ + ce_clk = clk_get(pce_dev->pdev, "iface_clk"); + if (IS_ERR(ce_clk)) { + *rc = PTR_ERR(ce_clk); + if (pce_dev->ce_core_src_clk != NULL) + clk_put(pce_dev->ce_core_src_clk); + clk_put(pce_dev->ce_core_clk); + goto err; + } + pce_dev->ce_clk = ce_clk; + + /* Enable CE core clk */ + *rc = clk_prepare_enable(pce_dev->ce_core_clk); + if (*rc) { + if (pce_dev->ce_core_src_clk != NULL) + clk_put(pce_dev->ce_core_src_clk); + clk_put(pce_dev->ce_core_clk); + clk_put(pce_dev->ce_clk); + goto err; + } else { + /* Enable CE clk */ + *rc = clk_prepare_enable(pce_dev->ce_clk); + if (*rc) { + clk_disable_unprepare(pce_dev->ce_core_clk); + if (pce_dev->ce_core_src_clk != NULL) + clk_put(pce_dev->ce_core_src_clk); + clk_put(pce_dev->ce_core_clk); + clk_put(pce_dev->ce_clk); + goto err; + + } + } + qce_setup_ce_dm_data(pce_dev); + + pce_dev->ce_dm.chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->ce_dm.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, pce_dev->memsize, + pce_dev->coh_vmem, pce_dev->coh_pmem); +err_dm_chan_cmd: + kfree(pce_dev->ce_dm.chan_ce_in_cmd); + kfree(pce_dev->ce_dm.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, pce_dev->memsize, + pce_dev->coh_vmem, pce_dev->coh_pmem); + clk_disable_unprepare(pce_dev->ce_clk); + clk_disable_unprepare(pce_dev->ce_core_clk); + + if (pce_dev->ce_core_src_clk != NULL) + clk_put(pce_dev->ce_core_src_clk); + + clk_put(pce_dev->ce_clk); + clk_put(pce_dev->ce_core_clk); + + kfree(pce_dev->ce_dm.chan_ce_in_cmd); + kfree(pce_dev->ce_dm.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.17"); diff --git a/drivers/crypto/msm/qce40.h b/drivers/crypto/msm/qce40.h new file mode 100644 index 0000000000000000000000000000000000000000..809ba7f869f2bb1f0ca70c02f0950933f418e0db --- /dev/null +++ b/drivers/crypto/msm/qce40.h @@ -0,0 +1,240 @@ +/* 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_QCE40_H_ +#define _DRIVERS_CRYPTO_MSM_QCE40_H_ + + +#define GET_VIRT_ADDR(x) \ + ((uint32_t)pce_dev->coh_vmem + \ + ((uint32_t)x - pce_dev->coh_pmem)) +#define GET_PHYS_ADDR(x) \ + (pce_dev->coh_pmem + ((unsigned char *)x - \ + pce_dev->coh_vmem)) + +/* Sets the adddress of a command list in command pointer list */ +#define QCE_SET_CMD_PTR(x) \ + (uint32_t)(DMOV_CMD_ADDR(GET_PHYS_ADDR((unsigned char *)x))) + +/* Sets the adddress of the last command list in command pointer list */ +#define SET_LAST_CMD_PTR(x) \ + ((DMOV_CMD_ADDR(x)) | CMD_PTR_LP) + +/* Get the adddress of the last command list in command pointer list */ +#define QCE_SET_LAST_CMD_PTR(x) \ + SET_LAST_CMD_PTR((GET_PHYS_ADDR((unsigned char *)x))) + + +/* MAX Data xfer block size between DM and CE */ +#define MAX_ADM_CE_BLOCK_SIZE 64 +#define ADM_DESC_LENGTH_MASK 0xffff +#define ADM_DESC_LENGTH(x) (x & ADM_DESC_LENGTH_MASK) + +#define ADM_STATUS_OK 0x80000002 + +/* QCE max number of descriptor in a descriptor list */ +#define QCE_MAX_NUM_DESC 128 + +#define CRYPTO_REG_SIZE 0x4 + +struct dmov_desc { + uint32_t addr; + uint32_t len; +}; + +/* 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 buffers */ +struct ce_reg_buffer_addr { + + unsigned char *reset_buf_64; + unsigned char *version; + + unsigned char *encr_seg_cfg_size_start; + unsigned char *encr_key; + unsigned char *encr_xts_key; + unsigned char *encr_cntr_iv; + unsigned char *encr_mask; + unsigned char *encr_xts_du_size; + + unsigned char *auth_seg_cfg_size_start; + unsigned char *auth_key; + unsigned char *auth_iv; + unsigned char *auth_result; + unsigned char *auth_nonce_info; + unsigned char *auth_byte_count; + + unsigned char *seg_size; + unsigned char *go_proc; + unsigned char *status; + + unsigned char *pad; + unsigned char *ignore_data; +}; + +/* CE buffers */ +struct ce_reg_buffers { + + unsigned char reset_buf_64[64]; + unsigned char version[CRYPTO_REG_SIZE]; + + unsigned char encr_seg_cfg_size_start[3 * CRYPTO_REG_SIZE]; + unsigned char encr_key[8 * CRYPTO_REG_SIZE]; + unsigned char encr_xts_key[8 * CRYPTO_REG_SIZE]; + unsigned char encr_cntr_iv[4 * CRYPTO_REG_SIZE]; + unsigned char encr_mask[CRYPTO_REG_SIZE]; + unsigned char encr_xts_du_size[CRYPTO_REG_SIZE]; + + unsigned char auth_seg_cfg_size_start[3 * CRYPTO_REG_SIZE]; + unsigned char auth_key[16 * CRYPTO_REG_SIZE]; + unsigned char auth_iv[16 * CRYPTO_REG_SIZE]; + unsigned char auth_result[16 * CRYPTO_REG_SIZE]; + unsigned char auth_nonce_info[4 * CRYPTO_REG_SIZE]; + unsigned char auth_byte_count[4 * CRYPTO_REG_SIZE]; + + unsigned char seg_size[CRYPTO_REG_SIZE]; + unsigned char go_proc[CRYPTO_REG_SIZE]; + unsigned char status[CRYPTO_REG_SIZE]; + + unsigned char pad[2 * MAX_ADM_CE_BLOCK_SIZE]; +}; + +/* CE Command lists */ +struct ce_cmdlists { + dmov_s *get_hw_version; + dmov_s *clear_status; + dmov_s *get_status_ocu; + + dmov_s *set_cipher_cfg; + + dmov_s *set_cipher_aes_128_key; + dmov_s *set_cipher_aes_256_key; + dmov_s *set_cipher_des_key; + dmov_s *set_cipher_3des_key; + + dmov_s *set_cipher_aes_128_xts_key; + dmov_s *set_cipher_aes_256_xts_key; + dmov_s *set_cipher_xts_du_size; + + dmov_s *set_cipher_aes_iv; + dmov_s *set_cipher_aes_xts_iv; + dmov_s *set_cipher_des_iv; + dmov_s *get_cipher_iv; + + dmov_s *set_cipher_mask; + + dmov_s *set_auth_cfg; + dmov_s *set_auth_key_128; + dmov_s *set_auth_key_256; + dmov_s *set_auth_key_512; + dmov_s *set_auth_iv_16; + dmov_s *get_auth_result_16; + dmov_s *set_auth_iv_20; + dmov_s *get_auth_result_20; + dmov_s *set_auth_iv_32; + dmov_s *get_auth_result_32; + dmov_s *set_auth_byte_count; + dmov_s *get_auth_byte_count; + + dmov_s *set_auth_nonce_info; + + dmov_s *reset_cipher_key; + dmov_s *reset_cipher_xts_key; + dmov_s *reset_cipher_iv; + dmov_s *reset_cipher_cfg; + dmov_s *reset_auth_key; + dmov_s *reset_auth_iv; + dmov_s *reset_auth_cfg; + dmov_s *reset_auth_byte_count; + + dmov_s *set_seg_size_ocb; + dmov_s *get_status_wait; + dmov_s *set_go_proc; + + dmov_sg *ce_data_in; + dmov_sg *ce_data_out; +}; + +/* Command pointer lists */ +struct ce_cmdptrlists_ops { + + uint32_t probe_ce_hw; + uint32_t cipher_aes_128_cbc_ctr; + uint32_t cipher_aes_256_cbc_ctr; + uint32_t cipher_aes_128_ecb; + uint32_t cipher_aes_256_ecb; + uint32_t cipher_aes_128_xts; + uint32_t cipher_aes_256_xts; + uint32_t cipher_des_cbc; + uint32_t cipher_des_ecb; + uint32_t cipher_3des_cbc; + uint32_t cipher_3des_ecb; + uint32_t auth_sha1; + uint32_t auth_sha256; + uint32_t auth_sha1_hmac; + uint32_t auth_sha256_hmac; + uint32_t auth_aes_128_cmac; + uint32_t auth_aes_256_cmac; + uint32_t aead_aes_128_ccm; + uint32_t aead_aes_256_ccm; + + uint32_t cipher_ce_out; + uint32_t cipher_ce_out_get_iv; + uint32_t aead_ce_out; +}; + +/* DM data structure with buffers, commandlists & commmand pointer lists */ +struct ce_dm_data { + 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 crci_in; /* CRCI for CE DM IN Channel */ + unsigned int crci_out; /* CRCI for CE DM OUT Channel */ + + 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 */ + + struct dmov_desc *ce_out_src_desc; + struct dmov_desc *ce_out_dst_desc; + struct dmov_desc *ce_in_src_desc; + struct dmov_desc *ce_in_dst_desc; + + int ce_out_src_desc_index; + int ce_out_dst_desc_index; + int ce_in_src_desc_index; + int ce_in_dst_desc_index; + + int ce_block_size; + + dma_addr_t phy_ce_out_ignore; + dma_addr_t phy_ce_pad; + + struct ce_reg_buffer_addr buffer; + struct ce_cmdlists cmdlist; + struct ce_cmdptrlists_ops cmdptrlist; + + struct msm_dmov_cmd *chan_ce_in_cmd; + struct msm_dmov_cmd *chan_ce_out_cmd; +}; +#endif /* _DRIVERS_CRYPTO_MSM_QCE40_H */ diff --git a/drivers/crypto/msm/qce_ota.h b/drivers/crypto/msm/qce_ota.h new file mode 100644 index 0000000000000000000000000000000000000000..72af585752581ef8c9137643a88cf15ef9bea570 --- /dev/null +++ b/drivers/crypto/msm/qce_ota.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. + * + */ + +/* Qualcomm Crypto Engine driver OTA APIi */ + +#ifndef __CRYPTO_MSM_QCE_OTA_H +#define __CRYPTO_MSM_QCE_OTA_H + +#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/qcedev.c b/drivers/crypto/msm/qcedev.c new file mode 100644 index 0000000000000000000000000000000000000000..fff494c1f86a844326afed1d352c7a84c79bc5bf --- /dev/null +++ b/drivers/crypto/msm/qcedev.c @@ -0,0 +1,2228 @@ +/* Qualcomm CE device driver. + * + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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_handle; + +struct qcedev_cipher_req { + struct ablkcipher_request creq; + void *cookie; +}; + +struct qcedev_sha_req { + struct ahash_request sreq; + void *cookie; +}; + +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_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_handle *handle; + int err; +}; + +static DEFINE_MUTEX(send_cmd_lock); +static DEFINE_MUTEX(sent_bw_req); +/********************************************************************** + * 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; + + uint32_t ce_lock_count; + uint32_t high_bw_req_count; + + /* CE features/algorithms supported by HW engine*/ + struct ce_hw_support ce_support; + + uint32_t bus_scale_handle; + + /* 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; +}; + +struct qcedev_handle { + /* qcedev control handle */ + struct qcedev_control *cntl; + /* qce internal sha context*/ + struct qcedev_sha_ctxt sha_ctxt; +}; + +/*------------------------------------------------------------------------- +* 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 void qcedev_ce_high_bw_req(struct qcedev_control *podev, + bool high_bw_req) +{ + int ret = 0; + + mutex_lock(&sent_bw_req); + if (high_bw_req) { + if (podev->high_bw_req_count == 0) + ret = msm_bus_scale_client_update_request( + podev->bus_scale_handle, 1); + if (ret) + pr_err("%s Unable to set to high bandwidth\n", + __func__); + podev->high_bw_req_count++; + } else { + if (podev->high_bw_req_count == 1) + ret = msm_bus_scale_client_update_request( + podev->bus_scale_handle, 0); + if (ret) + pr_err("%s Unable to set to low bandwidth\n", + __func__); + podev->high_bw_req_count--; + } + mutex_unlock(&sent_bw_req); +} + + +static int qcedev_unlock_ce(struct qcedev_control *podev) +{ + int ret = 0; + + mutex_lock(&send_cmd_lock); + if (podev->ce_lock_count == 1) { + int response = 0; + + if (qcedev_scm_cmd(podev->platform_support.shared_ce_resource, + QCEDEV_CE_UNLOCK_CMD, &response)) { + pr_err("Failed to release CE lock\n"); + ret = -EIO; + } + } + if (ret == 0) { + if (podev->ce_lock_count) + podev->ce_lock_count--; + else { + /* We should never be here */ + ret = -EIO; + pr_err("CE hardware is already unlocked\n"); + } + } + mutex_unlock(&send_cmd_lock); + + return ret; +} + +static int qcedev_lock_ce(struct qcedev_control *podev) +{ + int ret = 0; + + mutex_lock(&send_cmd_lock); + if (podev->ce_lock_count == 0) { + 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)) { + ret = -EUSERS; + } else { + if (response < 0) + ret = -EINVAL; + } + } + if (ret == 0) + podev->ce_lock_count++; + mutex_unlock(&send_cmd_lock); + return ret; +} + +#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); + +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_handle *handle; + struct qcedev_control *podev; + + podev = qcedev_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; + } + + handle = kzalloc(sizeof(struct qcedev_handle), GFP_KERNEL); + if (handle == NULL) { + pr_err("Failed to allocate memory %ld\n", + PTR_ERR(handle)); + return -ENOMEM; + } + + handle->cntl = podev; + file->private_data = handle; + if (podev->platform_support.bus_scale_table != NULL) + qcedev_ce_high_bw_req(podev, true); + return 0; +} + +static int qcedev_release(struct inode *inode, struct file *file) +{ + struct qcedev_control *podev; + struct qcedev_handle *handle; + + handle = file->private_data; + podev = handle->cntl; + if (podev != NULL && podev->magic != QCEDEV_MAGIC) { + pr_err("%s: invalid handle %p\n", + __func__, podev); + } + kzfree(handle); + file->private_data = NULL; + if (podev->platform_support.bus_scale_table != NULL) + qcedev_ce_high_bw_req(podev, false); + 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); + } + + 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; + struct qcedev_handle *handle; + + uint32_t *auth32 = (uint32_t *)authdata; + + areq = (struct qcedev_sha_req *) cookie; + handle = (struct qcedev_handle *) areq->cookie; + pdev = handle->cntl; + + if (digest) + memcpy(&handle->sha_ctxt.digest[0], digest, 32); + + if (authdata) { + handle->sha_ctxt.auth_data[0] = auth32[0]; + handle->sha_ctxt.auth_data[1] = auth32[1]; + handle->sha_ctxt.auth_data[2] = auth32[2]; + handle->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_handle *handle; + struct qcedev_control *podev; + struct qcedev_async_req *qcedev_areq; + + areq = (struct qcedev_cipher_req *) cookie; + handle = (struct qcedev_handle *) areq->cookie; + podev = handle->cntl; + qcedev_areq = podev->active_command; + + if (iv) + memcpy(&qcedev_areq->cipher_op_req.iv[0], iv, + qcedev_areq->cipher_op_req.ivlen); + tasklet_schedule(&podev->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->cipher_req.cookie = qcedev_areq->handle; + 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: + return -EINVAL; + }; + + 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: + return -EINVAL; + }; + + 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_async_req *qcedev_areq; + struct qce_sha_req sreq; + int ret = 0; + struct qcedev_handle *handle; + + /* start the command on the podev->active_command */ + qcedev_areq = podev->active_command; + handle = qcedev_areq->handle; + + 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 = &handle->sha_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 = &handle->sha_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 = &handle->sha_ctxt.authkey[0]; + sreq.authklen = qcedev_areq->sha_op_req.authklen; + break; + default: + break; + }; + + qcedev_areq->sha_req.cookie = handle; + + sreq.qce_cb = qcedev_sha_req_cb; + if (qcedev_areq->sha_op_req.alg != QCEDEV_ALG_AES_CMAC) { + sreq.auth_data[0] = handle->sha_ctxt.auth_data[0]; + sreq.auth_data[1] = handle->sha_ctxt.auth_data[1]; + sreq.auth_data[2] = handle->sha_ctxt.auth_data[2]; + sreq.auth_data[3] = handle->sha_ctxt.auth_data[3]; + sreq.digest = &handle->sha_ctxt.digest[0]; + sreq.first_blk = handle->sha_ctxt.first_blk; + sreq.last_blk = handle->sha_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; + + 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_handle *handle) +{ + struct qcedev_control *podev; + unsigned long flags = 0; + int ret = 0; + struct qcedev_stat *pstat; + + qcedev_areq->err = 0; + podev = handle->cntl; + + if (podev->platform_support.ce_shared) { + 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); + } 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); + + if (podev->platform_support.ce_shared) + 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_handle *handle) +{ + struct qcedev_sha_ctxt *sha_ctxt = &handle->sha_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_handle *handle) +{ + 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 = handle->sha_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 = &handle->sha_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++; + } + handle->sha_ctxt.trailing_buf_len = total; + + return 0; + } + + + k_buf_src = kmalloc(total + CACHE_LINE_SIZE * 2, + GFP_KERNEL); + if (k_buf_src == NULL) { + pr_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); + k_src = k_align_src; + + /* check for trailing buffer from previous updates and append it */ + if (t_buf > 0) { + memcpy(k_src, &handle->sha_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(&handle->sha_ctxt.trailing_buf[0], 0, 64); + memcpy(&handle->sha_ctxt.trailing_buf[0], + (k_src - trailing_buf_len), + trailing_buf_len); + } + handle->sha_ctxt.trailing_buf_len = trailing_buf_len; + + err = submit_req(qcedev_areq, handle); + + handle->sha_ctxt.last_blk = 0; + handle->sha_ctxt.first_blk = 0; + + kfree(k_buf_src); + return err; +} + +static int qcedev_sha_update(struct qcedev_async_req *qcedev_areq, + struct qcedev_handle *handle) +{ + 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) { + pr_err("%s:Can't Allocate mem:saved_req 0x%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, + handle); + + 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, + handle); + 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, handle); + + return err; +} + +static int qcedev_sha_final(struct qcedev_async_req *qcedev_areq, + struct qcedev_handle *handle) +{ + int err = 0; + struct scatterlist sg_src; + uint32_t total; + + uint8_t *k_buf_src = NULL; + uint8_t *k_align_src = NULL; + + handle->sha_ctxt.first_blk = 0; + handle->sha_ctxt.last_blk = 1; + + total = handle->sha_ctxt.trailing_buf_len; + + if (total) { + k_buf_src = kmalloc(total + CACHE_LINE_SIZE * 2, + GFP_KERNEL); + if (k_buf_src == NULL) { + pr_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); + memcpy(k_align_src, &handle->sha_ctxt.trailing_buf[0], total); + } + handle->sha_ctxt.last_blk = 1; + handle->sha_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, handle); + + handle->sha_ctxt.first_blk = 0; + handle->sha_ctxt.last_blk = 0; + handle->sha_ctxt.auth_data[0] = 0; + handle->sha_ctxt.auth_data[1] = 0; + handle->sha_ctxt.trailing_buf_len = 0; + memset(&handle->sha_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_handle *handle) +{ + 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(&handle->sha_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) { + pr_err("%s: Can't Allocate memory: k_buf_src 0x%x\n", + __func__, (uint32_t)k_buf_src); + 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; + handle->sha_ctxt.diglen = qcedev_areq->sha_op_req.diglen; + err = submit_req(qcedev_areq, handle); + + kfree(k_buf_src); + return err; +} + +static int qcedev_set_hmac_auth_key(struct qcedev_async_req *areq, + struct qcedev_handle *handle) +{ + 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(&handle->sha_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, handle); + err = qcedev_sha_update(&authkey_areq, handle); + if (!err) + err = qcedev_sha_final(&authkey_areq, handle); + else + return err; + memcpy(&handle->sha_ctxt.authkey[0], + &handle->sha_ctxt.digest[0], + handle->sha_ctxt.diglen); + } + return err; +} + +static int qcedev_hmac_get_ohash(struct qcedev_async_req *qcedev_areq, + struct qcedev_handle *handle) +{ + 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) { + pr_err("%s: Can't Allocate memory: k_src 0x%x\n", + __func__, (uint32_t)k_src); + return -ENOMEM; + } + + /* check for trailing buffer from previous updates and append it */ + memcpy(k_src, &handle->sha_ctxt.trailing_buf[0], + handle->sha_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(&handle->sha_ctxt.trailing_buf[0], 0, sha_block_size); + memcpy(&handle->sha_ctxt.trailing_buf[0], &handle->sha_ctxt.digest[0], + sha_digest_size); + handle->sha_ctxt.trailing_buf_len = sha_digest_size; + + handle->sha_ctxt.first_blk = 1; + handle->sha_ctxt.last_blk = 0; + handle->sha_ctxt.auth_data[0] = 0; + handle->sha_ctxt.auth_data[1] = 0; + + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA1_HMAC) { + memcpy(&handle->sha_ctxt.digest[0], + &_std_init_vector_sha1_uint8[0], SHA1_DIGEST_SIZE); + handle->sha_ctxt.diglen = SHA1_DIGEST_SIZE; + } + + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA256_HMAC) { + memcpy(&handle->sha_ctxt.digest[0], + &_std_init_vector_sha256_uint8[0], SHA256_DIGEST_SIZE); + handle->sha_ctxt.diglen = SHA256_DIGEST_SIZE; + } + err = submit_req(qcedev_areq, handle); + + handle->sha_ctxt.last_blk = 0; + handle->sha_ctxt.first_blk = 0; + + kfree(k_src); + return err; +} + +static int qcedev_hmac_update_iokey(struct qcedev_async_req *areq, + struct qcedev_handle *handle, 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(&handle->sha_ctxt.trailing_buf[0], 0, sha_block_size); + for (i = 0; i < sha_block_size; i++) + handle->sha_ctxt.trailing_buf[i] = + (handle->sha_ctxt.authkey[i] ^ constant); + + handle->sha_ctxt.trailing_buf_len = sha_block_size; + return 0; +} + +static int qcedev_hmac_init(struct qcedev_async_req *areq, + struct qcedev_handle *handle) +{ + int err; + struct qcedev_control *podev = handle->cntl; + + qcedev_sha_init(areq, handle); + err = qcedev_set_hmac_auth_key(areq, handle); + if (err) + return err; + if (!podev->ce_support.sha_hmac) + qcedev_hmac_update_iokey(areq, handle, true); + return 0; +} + +static int qcedev_hmac_final(struct qcedev_async_req *areq, + struct qcedev_handle *handle) +{ + int err; + struct qcedev_control *podev = handle->cntl; + + err = qcedev_sha_final(areq, handle); + if (podev->ce_support.sha_hmac) + return err; + + qcedev_hmac_update_iokey(areq, handle, false); + err = qcedev_hmac_get_ohash(areq, handle); + if (err) + return err; + err = qcedev_sha_final(areq, handle); + + return err; +} + +static int qcedev_hash_init(struct qcedev_async_req *areq, + struct qcedev_handle *handle) +{ + if ((areq->sha_op_req.alg == QCEDEV_ALG_SHA1) || + (areq->sha_op_req.alg == QCEDEV_ALG_SHA256)) + return qcedev_sha_init(areq, handle); + else + return qcedev_hmac_init(areq, handle); +} + +static int qcedev_hash_update(struct qcedev_async_req *qcedev_areq, + struct qcedev_handle *handle) +{ + return qcedev_sha_update(qcedev_areq, handle); +} + +static int qcedev_hash_final(struct qcedev_async_req *areq, + struct qcedev_handle *handle) +{ + if ((areq->sha_op_req.alg == QCEDEV_ALG_SHA1) || + (areq->sha_op_req.alg == QCEDEV_ALG_SHA256)) + return qcedev_sha_final(areq, handle); + else + return qcedev_hmac_final(areq, handle); +} + +#ifdef CONFIG_ANDROID_PMEM +static int qcedev_pmem_ablk_cipher_max_xfer(struct qcedev_async_req *areq, + struct qcedev_handle *handle) +{ + 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) { + pr_err("%s: Can't Allocate memory:sg_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) { + pr_err("%s: Can't Allocate memory: sg_dst 0x%x\n", + __func__, (uint32_t)sg_dst); + 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, handle); + + 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_handle *handle) +{ + 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) { + pr_err(KERN_ERR "%s:Can't Allocate mem:saved_req 0x%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, + handle); + + 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, + handle); + 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, handle); + + /* 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; + +} +#else +static int qcedev_pmem_ablk_cipher(struct qcedev_async_req *qcedev_areq, + struct qcedev_handle *handle) +{ + return -EPERM; +} +#endif/*CONFIG_ANDROID_PMEM*/ + +static int qcedev_vbuf_ablk_cipher_max_xfer(struct qcedev_async_req *areq, + int *di, struct qcedev_handle *handle, + 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, handle); + + /* 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_handle *handle) +{ + 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) { + pr_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) { + pr_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, handle, 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, handle, 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, handle, + 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_handle *handle; + struct qcedev_control *podev; + struct qcedev_async_req qcedev_areq; + struct qcedev_stat *pstat; + + handle = file->private_data; + podev = handle->cntl; + qcedev_areq.handle = handle; + if (podev == NULL || podev->magic != QCEDEV_MAGIC) { + pr_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: + if (podev->platform_support.ce_shared) + err = qcedev_lock_ce(podev); + else + err = -ENOTTY; + break; + case QCEDEV_IOCTL_UNLOCK_CE: + if (podev->platform_support.ce_shared) + err = qcedev_unlock_ce(podev); + else + err = -ENOTTY; + 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) + err = qcedev_pmem_ablk_cipher(&qcedev_areq, handle); + else + err = qcedev_vbuf_ablk_cipher(&qcedev_areq, handle); + 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, handle); + 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, handle); + if (err) + return err; + } else { + err = qcedev_hash_update(&qcedev_areq, handle); + if (err) + return err; + } + + memcpy(&qcedev_areq.sha_op_req.digest[0], + &handle->sha_ctxt.digest[0], + handle->sha_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, handle); + if (err) + return err; + qcedev_areq.sha_op_req.diglen = handle->sha_ctxt.diglen; + memcpy(&qcedev_areq.sha_op_req.digest[0], + &handle->sha_ctxt.digest[0], + handle->sha_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, handle); + err = qcedev_hash_update(&qcedev_areq, handle); + if (err) + return err; + err = qcedev_hash_final(&qcedev_areq, handle); + if (err) + return err; + qcedev_areq.sha_op_req.diglen = handle->sha_ctxt.diglen; + memcpy(&qcedev_areq.sha_op_req.digest[0], + &handle->sha_ctxt.digest[0], + handle->sha_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) { + pr_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->platform_support.bus_scale_table = + platform_support->bus_scale_table; + podev->ce_lock_count = 0; + podev->high_bw_req_count = 0; + 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); + + if (podev->platform_support.bus_scale_table != NULL) { + podev->bus_scale_handle = + msm_bus_scale_register_client( + (struct msm_bus_scale_pdata *) + podev->platform_support.bus_scale_table); + if (!podev->bus_scale_handle) { + printk(KERN_ERR "%s not able to get bus scale\n", + __func__); + rc = -ENOMEM; + goto err; + } + } + rc = misc_register(&podev->miscdevice); + + if (rc >= 0) + return 0; + else + if (podev->platform_support.bus_scale_table != NULL) + msm_bus_scale_unregister_client( + podev->bus_scale_handle); +err: + + 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->platform_support.bus_scale_table != NULL) + msm_bus_scale_unregister_client(podev->bus_scale_handle); + + 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.26"); + +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 0000000000000000000000000000000000000000..168edaa83dd744ae93ba5ef92a12fc7b49a8c777 --- /dev/null +++ b/drivers/crypto/msm/qcrypto.c @@ -0,0 +1,3367 @@ +/* Qualcomm Crypto driver + * + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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; + + uint32_t bus_scale_handle; + /* 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; + uint32_t high_bw_req_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 DEFINE_MUTEX(sent_bw_req); + +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 qcrypto_ce_high_bw_req(struct crypto_priv *cp, bool high_bw_req) +{ + int ret = 0; + + mutex_lock(&sent_bw_req); + if (high_bw_req) { + if (cp->high_bw_req_count == 0) + ret = msm_bus_scale_client_update_request( + cp->bus_scale_handle, 1); + if (ret) + pr_err("%s Unable to set to high bandwidth\n", + __func__); + cp->high_bw_req_count++; + } else { + if (cp->high_bw_req_count == 1) + ret = msm_bus_scale_client_update_request( + cp->bus_scale_handle, 0); + if (ret) + pr_err("%s Unable to set to low bandwidth\n", + __func__); + cp->high_bw_req_count--; + } + mutex_unlock(&sent_bw_req); +} + +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); + if (ctx->cp->platform_support.bus_scale_table != NULL) + qcrypto_ce_high_bw_req(ctx->cp, true); + + 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; + if (sha_ctx->cp->platform_support.bus_scale_table != NULL) + qcrypto_ce_high_bw_req(sha_ctx->cp, true); + + 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; + } + if (sha_ctx->cp->platform_support.bus_scale_table != NULL) + qcrypto_ce_high_bw_req(sha_ctx->cp, false); +}; + + +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); + + if (sha_ctx->cp->platform_support.bus_scale_table != NULL) + qcrypto_ce_high_bw_req(sha_ctx->cp, true); + + 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 void _qcrypto_cra_ablkcipher_exit(struct crypto_tfm *tfm) +{ + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + + if (ctx->cp->platform_support.bus_scale_table != NULL) + qcrypto_ce_high_bw_req(ctx->cp, false); +}; + +static void _qcrypto_cra_aead_exit(struct crypto_tfm *tfm) +{ + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + + if (ctx->cp->platform_support.bus_scale_table != NULL) + qcrypto_ce_high_bw_req(ctx->cp, false); +}; + +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; + + if (cp->platform_support.bus_scale_table != NULL) + msm_bus_scale_unregister_client(cp->bus_scale_handle); + + 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; + + if (i > MAX_NONCE) + return -EINVAL; + + 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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_ablkcipher_exit, + .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_exit = _qcrypto_cra_aead_exit, + .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_exit = _qcrypto_cra_aead_exit, + .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_exit = _qcrypto_cra_aead_exit, + .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_exit = _qcrypto_cra_aead_exit, + .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_exit = _qcrypto_cra_aead_exit, + .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) { + pr_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->platform_support.bus_scale_table = + platform_support->bus_scale_table; + cp->high_bw_req_count = 0; + 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); + + if (cp->platform_support.bus_scale_table != NULL) { + cp->bus_scale_handle = + msm_bus_scale_register_client( + (struct msm_bus_scale_pdata *) + cp->platform_support.bus_scale_table); + if (!cp->bus_scale_handle) { + printk(KERN_ERR "%s not able to get bus scale\n", + __func__); + rc = -ENOMEM; + goto err; + } + } + + /* 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) +{ + pr_debug("%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.21"); diff --git a/drivers/crypto/msm/qcryptohw_30.h b/drivers/crypto/msm/qcryptohw_30.h new file mode 100644 index 0000000000000000000000000000000000000000..edbee714215577487a0794358b3afc08321cb3ea --- /dev/null +++ b/drivers/crypto/msm/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/qcryptohw_40.h b/drivers/crypto/msm/qcryptohw_40.h new file mode 100644 index 0000000000000000000000000000000000000000..367bdaaf7ae90797a441db7bdac69d644eb73de5 --- /dev/null +++ b/drivers/crypto/msm/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/gpio/Kconfig b/drivers/gpio/Kconfig index e03653d6935778423a5f3dd44d4ac06722d5f15f..ef077a552bc2ac1ca57667f4022ce05f5ef3df24 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -124,6 +124,14 @@ config GPIO_MSM_V2 Qualcomm MSM chips. Most of the pins on the MSM can be selected for GPIO, and are controlled by this driver. +config GPIO_FSM9XXX + tristate "Qualcomm FSM GPIO" + depends on GPIOLIB && ARCH_MSM + help + Say yes here to support the GPIO interface on Qualcomm FSM chips. + Most of the pins on the MSM can be selected for GPIO, and are + controlled by this driver. + config GPIO_MXC def_bool y depends on ARCH_MXC @@ -514,4 +522,44 @@ config GPIO_TPS65910 help Select this option to enable GPIO driver for the TPS65910 chip family. + +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. + +config GPIO_QPNP + depends on ARCH_MSMCOPPER + depends on OF_SPMI + depends on MSM_QPNP_INT + tristate "Qualcomm QPNP GPIO support" + help + Say 'y' here to include support for the Qualcomm QPNP gpio + support. QPNP is a SPMI based PMIC implementation. + +config GPIO_QPNP_DEBUG + depends on GPIO_QPNP + depends on DEBUG_FS + bool "Qualcomm QPNP GPIO debug support" + help + Say 'y' here to include debug support for the Qualcomm + QPNP gpio support endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 007f54bd0081203dcf04ec775254b6b72c8d422f..babd44db52c1b99c8ca5ed3ff47836329df6df1e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o obj-$(CONFIG_GPIO_MSM_V1) += gpio-msm-v1.o obj-$(CONFIG_GPIO_MSM_V2) += gpio-msm-v2.o +obj-$(CONFIG_GPIO_FSM9XXX) += gpio-fsm9xxx.o obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o obj-$(CONFIG_PLAT_NOMADIK) += gpio-nomadik.o @@ -42,7 +43,11 @@ obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o obj-$(CONFIG_GPIO_PCH) += gpio-pch.o obj-$(CONFIG_GPIO_PL061) += gpio-pl061.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 obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o +obj-$(CONFIG_GPIO_QPNP) += qpnp-gpio.o obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o obj-$(CONFIG_PLAT_SAMSUNG) += gpio-samsung.o obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o diff --git a/drivers/gpio/gpio-fsm9xxx.c b/drivers/gpio/gpio-fsm9xxx.c new file mode 100644 index 0000000000000000000000000000000000000000..ad7e1fd0248988a21e74462ee391c5ac370d026a --- /dev/null +++ b/drivers/gpio/gpio-fsm9xxx.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +/* see 80-VA736-2 Rev C pp 695-751 +** +** These are actually the *shadow* gpio registers, since the +** real ones (which allow full access) are only available to the +** ARM9 side of the world. +** +** Since the _BASE need to be page-aligned when we're mapping them +** to virtual addresses, adjust for the additional offset in these +** macros. +*/ + +#if defined(CONFIG_ARCH_FSM9XXX) +#define MSM_GPIO1_REG(off) (MSM_TLMM_BASE + (off)) +#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 */ + +#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/drivers/gpio/gpio-msm-v1.c b/drivers/gpio/gpio-msm-v1.c index 52a4d4286ebad60d9a3cc678b6fd85b514812343..8718c9a2cfaa0adc66ca66b9182dbade37ff8f46 100644 --- a/drivers/gpio/gpio-msm-v1.c +++ b/drivers/gpio/gpio-msm-v1.c @@ -1,6 +1,7 @@ -/* +/* linux/arch/arm/mach-msm/gpio.c + * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2012, 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,9 +20,13 @@ #include #include #include -#include -#include +#include +#include +#include #include +#include +#include + /* see 80-VA736-2 Rev C pp 695-751 ** @@ -34,257 +39,187 @@ ** macros. */ +#if defined(CONFIG_ARCH_MSM7X30) #define MSM_GPIO1_REG(off) (MSM_GPIO1_BASE + (off)) #define MSM_GPIO2_REG(off) (MSM_GPIO2_BASE + 0x400 + (off)) -#define MSM_GPIO1_SHADOW_REG(off) (MSM_GPIO1_BASE + 0x800 + (off)) -#define MSM_GPIO2_SHADOW_REG(off) (MSM_GPIO2_BASE + 0xC00 + (off)) +#else +#define MSM_GPIO1_REG(off) (MSM_GPIO1_BASE + 0x800 + (off)) +#define MSM_GPIO2_REG(off) (MSM_GPIO2_BASE + 0xC00 + (off)) +#endif + +#if defined(CONFIG_ARCH_MSM7X00A) || defined(CONFIG_ARCH_MSM7X25) ||\ + defined(CONFIG_ARCH_MSM7X27) -/* - * MSM7X00 registers - */ /* output value */ -#define MSM7X00_GPIO_OUT_0 MSM_GPIO1_SHADOW_REG(0x00) /* gpio 15-0 */ -#define MSM7X00_GPIO_OUT_1 MSM_GPIO2_SHADOW_REG(0x00) /* gpio 42-16 */ -#define MSM7X00_GPIO_OUT_2 MSM_GPIO1_SHADOW_REG(0x04) /* gpio 67-43 */ -#define MSM7X00_GPIO_OUT_3 MSM_GPIO1_SHADOW_REG(0x08) /* gpio 94-68 */ -#define MSM7X00_GPIO_OUT_4 MSM_GPIO1_SHADOW_REG(0x0C) /* gpio 106-95 */ -#define MSM7X00_GPIO_OUT_5 MSM_GPIO1_SHADOW_REG(0x50) /* gpio 107-121 */ +#define MSM_GPIO_OUT_0 MSM_GPIO1_REG(0x00) /* gpio 15-0 */ +#define MSM_GPIO_OUT_1 MSM_GPIO2_REG(0x00) /* gpio 42-16 */ +#define MSM_GPIO_OUT_2 MSM_GPIO1_REG(0x04) /* gpio 67-43 */ +#define MSM_GPIO_OUT_3 MSM_GPIO1_REG(0x08) /* gpio 94-68 */ +#define MSM_GPIO_OUT_4 MSM_GPIO1_REG(0x0C) /* gpio 106-95 */ +#define MSM_GPIO_OUT_5 MSM_GPIO1_REG(0x50) /* gpio 107-121 */ /* same pin map as above, output enable */ -#define MSM7X00_GPIO_OE_0 MSM_GPIO1_SHADOW_REG(0x10) -#define MSM7X00_GPIO_OE_1 MSM_GPIO2_SHADOW_REG(0x08) -#define MSM7X00_GPIO_OE_2 MSM_GPIO1_SHADOW_REG(0x14) -#define MSM7X00_GPIO_OE_3 MSM_GPIO1_SHADOW_REG(0x18) -#define MSM7X00_GPIO_OE_4 MSM_GPIO1_SHADOW_REG(0x1C) -#define MSM7X00_GPIO_OE_5 MSM_GPIO1_SHADOW_REG(0x54) +#define MSM_GPIO_OE_0 MSM_GPIO1_REG(0x10) +#define MSM_GPIO_OE_1 MSM_GPIO2_REG(0x08) +#define MSM_GPIO_OE_2 MSM_GPIO1_REG(0x14) +#define MSM_GPIO_OE_3 MSM_GPIO1_REG(0x18) +#define MSM_GPIO_OE_4 MSM_GPIO1_REG(0x1C) +#define MSM_GPIO_OE_5 MSM_GPIO1_REG(0x54) /* same pin map as above, input read */ -#define MSM7X00_GPIO_IN_0 MSM_GPIO1_SHADOW_REG(0x34) -#define MSM7X00_GPIO_IN_1 MSM_GPIO2_SHADOW_REG(0x20) -#define MSM7X00_GPIO_IN_2 MSM_GPIO1_SHADOW_REG(0x38) -#define MSM7X00_GPIO_IN_3 MSM_GPIO1_SHADOW_REG(0x3C) -#define MSM7X00_GPIO_IN_4 MSM_GPIO1_SHADOW_REG(0x40) -#define MSM7X00_GPIO_IN_5 MSM_GPIO1_SHADOW_REG(0x44) +#define MSM_GPIO_IN_0 MSM_GPIO1_REG(0x34) +#define MSM_GPIO_IN_1 MSM_GPIO2_REG(0x20) +#define MSM_GPIO_IN_2 MSM_GPIO1_REG(0x38) +#define MSM_GPIO_IN_3 MSM_GPIO1_REG(0x3C) +#define MSM_GPIO_IN_4 MSM_GPIO1_REG(0x40) +#define MSM_GPIO_IN_5 MSM_GPIO1_REG(0x44) /* same pin map as above, 1=edge 0=level interrup */ -#define MSM7X00_GPIO_INT_EDGE_0 MSM_GPIO1_SHADOW_REG(0x60) -#define MSM7X00_GPIO_INT_EDGE_1 MSM_GPIO2_SHADOW_REG(0x50) -#define MSM7X00_GPIO_INT_EDGE_2 MSM_GPIO1_SHADOW_REG(0x64) -#define MSM7X00_GPIO_INT_EDGE_3 MSM_GPIO1_SHADOW_REG(0x68) -#define MSM7X00_GPIO_INT_EDGE_4 MSM_GPIO1_SHADOW_REG(0x6C) -#define MSM7X00_GPIO_INT_EDGE_5 MSM_GPIO1_SHADOW_REG(0xC0) +#define MSM_GPIO_INT_EDGE_0 MSM_GPIO1_REG(0x60) +#define MSM_GPIO_INT_EDGE_1 MSM_GPIO2_REG(0x50) +#define MSM_GPIO_INT_EDGE_2 MSM_GPIO1_REG(0x64) +#define MSM_GPIO_INT_EDGE_3 MSM_GPIO1_REG(0x68) +#define MSM_GPIO_INT_EDGE_4 MSM_GPIO1_REG(0x6C) +#define MSM_GPIO_INT_EDGE_5 MSM_GPIO1_REG(0xC0) /* same pin map as above, 1=positive 0=negative */ -#define MSM7X00_GPIO_INT_POS_0 MSM_GPIO1_SHADOW_REG(0x70) -#define MSM7X00_GPIO_INT_POS_1 MSM_GPIO2_SHADOW_REG(0x58) -#define MSM7X00_GPIO_INT_POS_2 MSM_GPIO1_SHADOW_REG(0x74) -#define MSM7X00_GPIO_INT_POS_3 MSM_GPIO1_SHADOW_REG(0x78) -#define MSM7X00_GPIO_INT_POS_4 MSM_GPIO1_SHADOW_REG(0x7C) -#define MSM7X00_GPIO_INT_POS_5 MSM_GPIO1_SHADOW_REG(0xBC) +#define MSM_GPIO_INT_POS_0 MSM_GPIO1_REG(0x70) +#define MSM_GPIO_INT_POS_1 MSM_GPIO2_REG(0x58) +#define MSM_GPIO_INT_POS_2 MSM_GPIO1_REG(0x74) +#define MSM_GPIO_INT_POS_3 MSM_GPIO1_REG(0x78) +#define MSM_GPIO_INT_POS_4 MSM_GPIO1_REG(0x7C) +#define MSM_GPIO_INT_POS_5 MSM_GPIO1_REG(0xBC) /* same pin map as above, interrupt enable */ -#define MSM7X00_GPIO_INT_EN_0 MSM_GPIO1_SHADOW_REG(0x80) -#define MSM7X00_GPIO_INT_EN_1 MSM_GPIO2_SHADOW_REG(0x60) -#define MSM7X00_GPIO_INT_EN_2 MSM_GPIO1_SHADOW_REG(0x84) -#define MSM7X00_GPIO_INT_EN_3 MSM_GPIO1_SHADOW_REG(0x88) -#define MSM7X00_GPIO_INT_EN_4 MSM_GPIO1_SHADOW_REG(0x8C) -#define MSM7X00_GPIO_INT_EN_5 MSM_GPIO1_SHADOW_REG(0xB8) +#define MSM_GPIO_INT_EN_0 MSM_GPIO1_REG(0x80) +#define MSM_GPIO_INT_EN_1 MSM_GPIO2_REG(0x60) +#define MSM_GPIO_INT_EN_2 MSM_GPIO1_REG(0x84) +#define MSM_GPIO_INT_EN_3 MSM_GPIO1_REG(0x88) +#define MSM_GPIO_INT_EN_4 MSM_GPIO1_REG(0x8C) +#define MSM_GPIO_INT_EN_5 MSM_GPIO1_REG(0xB8) /* same pin map as above, write 1 to clear interrupt */ -#define MSM7X00_GPIO_INT_CLEAR_0 MSM_GPIO1_SHADOW_REG(0x90) -#define MSM7X00_GPIO_INT_CLEAR_1 MSM_GPIO2_SHADOW_REG(0x68) -#define MSM7X00_GPIO_INT_CLEAR_2 MSM_GPIO1_SHADOW_REG(0x94) -#define MSM7X00_GPIO_INT_CLEAR_3 MSM_GPIO1_SHADOW_REG(0x98) -#define MSM7X00_GPIO_INT_CLEAR_4 MSM_GPIO1_SHADOW_REG(0x9C) -#define MSM7X00_GPIO_INT_CLEAR_5 MSM_GPIO1_SHADOW_REG(0xB4) +#define MSM_GPIO_INT_CLEAR_0 MSM_GPIO1_REG(0x90) +#define MSM_GPIO_INT_CLEAR_1 MSM_GPIO2_REG(0x68) +#define MSM_GPIO_INT_CLEAR_2 MSM_GPIO1_REG(0x94) +#define MSM_GPIO_INT_CLEAR_3 MSM_GPIO1_REG(0x98) +#define MSM_GPIO_INT_CLEAR_4 MSM_GPIO1_REG(0x9C) +#define MSM_GPIO_INT_CLEAR_5 MSM_GPIO1_REG(0xB4) /* same pin map as above, 1=interrupt pending */ -#define MSM7X00_GPIO_INT_STATUS_0 MSM_GPIO1_SHADOW_REG(0xA0) -#define MSM7X00_GPIO_INT_STATUS_1 MSM_GPIO2_SHADOW_REG(0x70) -#define MSM7X00_GPIO_INT_STATUS_2 MSM_GPIO1_SHADOW_REG(0xA4) -#define MSM7X00_GPIO_INT_STATUS_3 MSM_GPIO1_SHADOW_REG(0xA8) -#define MSM7X00_GPIO_INT_STATUS_4 MSM_GPIO1_SHADOW_REG(0xAC) -#define MSM7X00_GPIO_INT_STATUS_5 MSM_GPIO1_SHADOW_REG(0xB0) - -/* - * QSD8X50 registers - */ -/* output value */ -#define QSD8X50_GPIO_OUT_0 MSM_GPIO1_SHADOW_REG(0x00) /* gpio 15-0 */ -#define QSD8X50_GPIO_OUT_1 MSM_GPIO2_SHADOW_REG(0x00) /* gpio 42-16 */ -#define QSD8X50_GPIO_OUT_2 MSM_GPIO1_SHADOW_REG(0x04) /* gpio 67-43 */ -#define QSD8X50_GPIO_OUT_3 MSM_GPIO1_SHADOW_REG(0x08) /* gpio 94-68 */ -#define QSD8X50_GPIO_OUT_4 MSM_GPIO1_SHADOW_REG(0x0C) /* gpio 103-95 */ -#define QSD8X50_GPIO_OUT_5 MSM_GPIO1_SHADOW_REG(0x10) /* gpio 121-104 */ -#define QSD8X50_GPIO_OUT_6 MSM_GPIO1_SHADOW_REG(0x14) /* gpio 152-122 */ -#define QSD8X50_GPIO_OUT_7 MSM_GPIO1_SHADOW_REG(0x18) /* gpio 164-153 */ - -/* same pin map as above, output enable */ -#define QSD8X50_GPIO_OE_0 MSM_GPIO1_SHADOW_REG(0x20) -#define QSD8X50_GPIO_OE_1 MSM_GPIO2_SHADOW_REG(0x08) -#define QSD8X50_GPIO_OE_2 MSM_GPIO1_SHADOW_REG(0x24) -#define QSD8X50_GPIO_OE_3 MSM_GPIO1_SHADOW_REG(0x28) -#define QSD8X50_GPIO_OE_4 MSM_GPIO1_SHADOW_REG(0x2C) -#define QSD8X50_GPIO_OE_5 MSM_GPIO1_SHADOW_REG(0x30) -#define QSD8X50_GPIO_OE_6 MSM_GPIO1_SHADOW_REG(0x34) -#define QSD8X50_GPIO_OE_7 MSM_GPIO1_SHADOW_REG(0x38) - -/* same pin map as above, input read */ -#define QSD8X50_GPIO_IN_0 MSM_GPIO1_SHADOW_REG(0x50) -#define QSD8X50_GPIO_IN_1 MSM_GPIO2_SHADOW_REG(0x20) -#define QSD8X50_GPIO_IN_2 MSM_GPIO1_SHADOW_REG(0x54) -#define QSD8X50_GPIO_IN_3 MSM_GPIO1_SHADOW_REG(0x58) -#define QSD8X50_GPIO_IN_4 MSM_GPIO1_SHADOW_REG(0x5C) -#define QSD8X50_GPIO_IN_5 MSM_GPIO1_SHADOW_REG(0x60) -#define QSD8X50_GPIO_IN_6 MSM_GPIO1_SHADOW_REG(0x64) -#define QSD8X50_GPIO_IN_7 MSM_GPIO1_SHADOW_REG(0x68) - -/* same pin map as above, 1=edge 0=level interrup */ -#define QSD8X50_GPIO_INT_EDGE_0 MSM_GPIO1_SHADOW_REG(0x70) -#define QSD8X50_GPIO_INT_EDGE_1 MSM_GPIO2_SHADOW_REG(0x50) -#define QSD8X50_GPIO_INT_EDGE_2 MSM_GPIO1_SHADOW_REG(0x74) -#define QSD8X50_GPIO_INT_EDGE_3 MSM_GPIO1_SHADOW_REG(0x78) -#define QSD8X50_GPIO_INT_EDGE_4 MSM_GPIO1_SHADOW_REG(0x7C) -#define QSD8X50_GPIO_INT_EDGE_5 MSM_GPIO1_SHADOW_REG(0x80) -#define QSD8X50_GPIO_INT_EDGE_6 MSM_GPIO1_SHADOW_REG(0x84) -#define QSD8X50_GPIO_INT_EDGE_7 MSM_GPIO1_SHADOW_REG(0x88) +#define MSM_GPIO_INT_STATUS_0 MSM_GPIO1_REG(0xA0) +#define MSM_GPIO_INT_STATUS_1 MSM_GPIO2_REG(0x70) +#define MSM_GPIO_INT_STATUS_2 MSM_GPIO1_REG(0xA4) +#define MSM_GPIO_INT_STATUS_3 MSM_GPIO1_REG(0xA8) +#define MSM_GPIO_INT_STATUS_4 MSM_GPIO1_REG(0xAC) +#define MSM_GPIO_INT_STATUS_5 MSM_GPIO1_REG(0xB0) -/* same pin map as above, 1=positive 0=negative */ -#define QSD8X50_GPIO_INT_POS_0 MSM_GPIO1_SHADOW_REG(0x90) -#define QSD8X50_GPIO_INT_POS_1 MSM_GPIO2_SHADOW_REG(0x58) -#define QSD8X50_GPIO_INT_POS_2 MSM_GPIO1_SHADOW_REG(0x94) -#define QSD8X50_GPIO_INT_POS_3 MSM_GPIO1_SHADOW_REG(0x98) -#define QSD8X50_GPIO_INT_POS_4 MSM_GPIO1_SHADOW_REG(0x9C) -#define QSD8X50_GPIO_INT_POS_5 MSM_GPIO1_SHADOW_REG(0xA0) -#define QSD8X50_GPIO_INT_POS_6 MSM_GPIO1_SHADOW_REG(0xA4) -#define QSD8X50_GPIO_INT_POS_7 MSM_GPIO1_SHADOW_REG(0xA8) +#endif -/* same pin map as above, interrupt enable */ -#define QSD8X50_GPIO_INT_EN_0 MSM_GPIO1_SHADOW_REG(0xB0) -#define QSD8X50_GPIO_INT_EN_1 MSM_GPIO2_SHADOW_REG(0x60) -#define QSD8X50_GPIO_INT_EN_2 MSM_GPIO1_SHADOW_REG(0xB4) -#define QSD8X50_GPIO_INT_EN_3 MSM_GPIO1_SHADOW_REG(0xB8) -#define QSD8X50_GPIO_INT_EN_4 MSM_GPIO1_SHADOW_REG(0xBC) -#define QSD8X50_GPIO_INT_EN_5 MSM_GPIO1_SHADOW_REG(0xC0) -#define QSD8X50_GPIO_INT_EN_6 MSM_GPIO1_SHADOW_REG(0xC4) -#define QSD8X50_GPIO_INT_EN_7 MSM_GPIO1_SHADOW_REG(0xC8) +#if defined(CONFIG_ARCH_MSM7X30) -/* same pin map as above, write 1 to clear interrupt */ -#define QSD8X50_GPIO_INT_CLEAR_0 MSM_GPIO1_SHADOW_REG(0xD0) -#define QSD8X50_GPIO_INT_CLEAR_1 MSM_GPIO2_SHADOW_REG(0x68) -#define QSD8X50_GPIO_INT_CLEAR_2 MSM_GPIO1_SHADOW_REG(0xD4) -#define QSD8X50_GPIO_INT_CLEAR_3 MSM_GPIO1_SHADOW_REG(0xD8) -#define QSD8X50_GPIO_INT_CLEAR_4 MSM_GPIO1_SHADOW_REG(0xDC) -#define QSD8X50_GPIO_INT_CLEAR_5 MSM_GPIO1_SHADOW_REG(0xE0) -#define QSD8X50_GPIO_INT_CLEAR_6 MSM_GPIO1_SHADOW_REG(0xE4) -#define QSD8X50_GPIO_INT_CLEAR_7 MSM_GPIO1_SHADOW_REG(0xE8) - -/* same pin map as above, 1=interrupt pending */ -#define QSD8X50_GPIO_INT_STATUS_0 MSM_GPIO1_SHADOW_REG(0xF0) -#define QSD8X50_GPIO_INT_STATUS_1 MSM_GPIO2_SHADOW_REG(0x70) -#define QSD8X50_GPIO_INT_STATUS_2 MSM_GPIO1_SHADOW_REG(0xF4) -#define QSD8X50_GPIO_INT_STATUS_3 MSM_GPIO1_SHADOW_REG(0xF8) -#define QSD8X50_GPIO_INT_STATUS_4 MSM_GPIO1_SHADOW_REG(0xFC) -#define QSD8X50_GPIO_INT_STATUS_5 MSM_GPIO1_SHADOW_REG(0x100) -#define QSD8X50_GPIO_INT_STATUS_6 MSM_GPIO1_SHADOW_REG(0x104) -#define QSD8X50_GPIO_INT_STATUS_7 MSM_GPIO1_SHADOW_REG(0x108) - -/* - * MSM7X30 registers - */ /* output value */ -#define MSM7X30_GPIO_OUT_0 MSM_GPIO1_REG(0x00) /* gpio 15-0 */ -#define MSM7X30_GPIO_OUT_1 MSM_GPIO2_REG(0x00) /* gpio 43-16 */ -#define MSM7X30_GPIO_OUT_2 MSM_GPIO1_REG(0x04) /* gpio 67-44 */ -#define MSM7X30_GPIO_OUT_3 MSM_GPIO1_REG(0x08) /* gpio 94-68 */ -#define MSM7X30_GPIO_OUT_4 MSM_GPIO1_REG(0x0C) /* gpio 106-95 */ -#define MSM7X30_GPIO_OUT_5 MSM_GPIO1_REG(0x50) /* gpio 133-107 */ -#define MSM7X30_GPIO_OUT_6 MSM_GPIO1_REG(0xC4) /* gpio 150-134 */ -#define MSM7X30_GPIO_OUT_7 MSM_GPIO1_REG(0x214) /* gpio 181-151 */ +#define MSM_GPIO_OUT_0 MSM_GPIO1_REG(0x00) /* gpio 15-0 */ +#define MSM_GPIO_OUT_1 MSM_GPIO2_REG(0x00) /* gpio 43-16 */ +#define MSM_GPIO_OUT_2 MSM_GPIO1_REG(0x04) /* gpio 67-44 */ +#define MSM_GPIO_OUT_3 MSM_GPIO1_REG(0x08) /* gpio 94-68 */ +#define MSM_GPIO_OUT_4 MSM_GPIO1_REG(0x0C) /* gpio 106-95 */ +#define MSM_GPIO_OUT_5 MSM_GPIO1_REG(0x50) /* gpio 133-107 */ +#define MSM_GPIO_OUT_6 MSM_GPIO1_REG(0xC4) /* gpio 150-134 */ +#define MSM_GPIO_OUT_7 MSM_GPIO1_REG(0x214) /* gpio 181-151 */ /* same pin map as above, output enable */ -#define MSM7X30_GPIO_OE_0 MSM_GPIO1_REG(0x10) -#define MSM7X30_GPIO_OE_1 MSM_GPIO2_REG(0x08) -#define MSM7X30_GPIO_OE_2 MSM_GPIO1_REG(0x14) -#define MSM7X30_GPIO_OE_3 MSM_GPIO1_REG(0x18) -#define MSM7X30_GPIO_OE_4 MSM_GPIO1_REG(0x1C) -#define MSM7X30_GPIO_OE_5 MSM_GPIO1_REG(0x54) -#define MSM7X30_GPIO_OE_6 MSM_GPIO1_REG(0xC8) -#define MSM7X30_GPIO_OE_7 MSM_GPIO1_REG(0x218) +#define MSM_GPIO_OE_0 MSM_GPIO1_REG(0x10) +#define MSM_GPIO_OE_1 MSM_GPIO2_REG(0x08) +#define MSM_GPIO_OE_2 MSM_GPIO1_REG(0x14) +#define MSM_GPIO_OE_3 MSM_GPIO1_REG(0x18) +#define MSM_GPIO_OE_4 MSM_GPIO1_REG(0x1C) +#define MSM_GPIO_OE_5 MSM_GPIO1_REG(0x54) +#define MSM_GPIO_OE_6 MSM_GPIO1_REG(0xC8) +#define MSM_GPIO_OE_7 MSM_GPIO1_REG(0x218) /* same pin map as above, input read */ -#define MSM7X30_GPIO_IN_0 MSM_GPIO1_REG(0x34) -#define MSM7X30_GPIO_IN_1 MSM_GPIO2_REG(0x20) -#define MSM7X30_GPIO_IN_2 MSM_GPIO1_REG(0x38) -#define MSM7X30_GPIO_IN_3 MSM_GPIO1_REG(0x3C) -#define MSM7X30_GPIO_IN_4 MSM_GPIO1_REG(0x40) -#define MSM7X30_GPIO_IN_5 MSM_GPIO1_REG(0x44) -#define MSM7X30_GPIO_IN_6 MSM_GPIO1_REG(0xCC) -#define MSM7X30_GPIO_IN_7 MSM_GPIO1_REG(0x21C) +#define MSM_GPIO_IN_0 MSM_GPIO1_REG(0x34) +#define MSM_GPIO_IN_1 MSM_GPIO2_REG(0x20) +#define MSM_GPIO_IN_2 MSM_GPIO1_REG(0x38) +#define MSM_GPIO_IN_3 MSM_GPIO1_REG(0x3C) +#define MSM_GPIO_IN_4 MSM_GPIO1_REG(0x40) +#define MSM_GPIO_IN_5 MSM_GPIO1_REG(0x44) +#define MSM_GPIO_IN_6 MSM_GPIO1_REG(0xCC) +#define MSM_GPIO_IN_7 MSM_GPIO1_REG(0x21C) /* same pin map as above, 1=edge 0=level interrup */ -#define MSM7X30_GPIO_INT_EDGE_0 MSM_GPIO1_REG(0x60) -#define MSM7X30_GPIO_INT_EDGE_1 MSM_GPIO2_REG(0x50) -#define MSM7X30_GPIO_INT_EDGE_2 MSM_GPIO1_REG(0x64) -#define MSM7X30_GPIO_INT_EDGE_3 MSM_GPIO1_REG(0x68) -#define MSM7X30_GPIO_INT_EDGE_4 MSM_GPIO1_REG(0x6C) -#define MSM7X30_GPIO_INT_EDGE_5 MSM_GPIO1_REG(0xC0) -#define MSM7X30_GPIO_INT_EDGE_6 MSM_GPIO1_REG(0xD0) -#define MSM7X30_GPIO_INT_EDGE_7 MSM_GPIO1_REG(0x240) +#define MSM_GPIO_INT_EDGE_0 MSM_GPIO1_REG(0x60) +#define MSM_GPIO_INT_EDGE_1 MSM_GPIO2_REG(0x50) +#define MSM_GPIO_INT_EDGE_2 MSM_GPIO1_REG(0x64) +#define MSM_GPIO_INT_EDGE_3 MSM_GPIO1_REG(0x68) +#define MSM_GPIO_INT_EDGE_4 MSM_GPIO1_REG(0x6C) +#define MSM_GPIO_INT_EDGE_5 MSM_GPIO1_REG(0xC0) +#define MSM_GPIO_INT_EDGE_6 MSM_GPIO1_REG(0xD0) +#define MSM_GPIO_INT_EDGE_7 MSM_GPIO1_REG(0x240) /* same pin map as above, 1=positive 0=negative */ -#define MSM7X30_GPIO_INT_POS_0 MSM_GPIO1_REG(0x70) -#define MSM7X30_GPIO_INT_POS_1 MSM_GPIO2_REG(0x58) -#define MSM7X30_GPIO_INT_POS_2 MSM_GPIO1_REG(0x74) -#define MSM7X30_GPIO_INT_POS_3 MSM_GPIO1_REG(0x78) -#define MSM7X30_GPIO_INT_POS_4 MSM_GPIO1_REG(0x7C) -#define MSM7X30_GPIO_INT_POS_5 MSM_GPIO1_REG(0xBC) -#define MSM7X30_GPIO_INT_POS_6 MSM_GPIO1_REG(0xD4) -#define MSM7X30_GPIO_INT_POS_7 MSM_GPIO1_REG(0x228) +#define MSM_GPIO_INT_POS_0 MSM_GPIO1_REG(0x70) +#define MSM_GPIO_INT_POS_1 MSM_GPIO2_REG(0x58) +#define MSM_GPIO_INT_POS_2 MSM_GPIO1_REG(0x74) +#define MSM_GPIO_INT_POS_3 MSM_GPIO1_REG(0x78) +#define MSM_GPIO_INT_POS_4 MSM_GPIO1_REG(0x7C) +#define MSM_GPIO_INT_POS_5 MSM_GPIO1_REG(0xBC) +#define MSM_GPIO_INT_POS_6 MSM_GPIO1_REG(0xD4) +#define MSM_GPIO_INT_POS_7 MSM_GPIO1_REG(0x228) /* same pin map as above, interrupt enable */ -#define MSM7X30_GPIO_INT_EN_0 MSM_GPIO1_REG(0x80) -#define MSM7X30_GPIO_INT_EN_1 MSM_GPIO2_REG(0x60) -#define MSM7X30_GPIO_INT_EN_2 MSM_GPIO1_REG(0x84) -#define MSM7X30_GPIO_INT_EN_3 MSM_GPIO1_REG(0x88) -#define MSM7X30_GPIO_INT_EN_4 MSM_GPIO1_REG(0x8C) -#define MSM7X30_GPIO_INT_EN_5 MSM_GPIO1_REG(0xB8) -#define MSM7X30_GPIO_INT_EN_6 MSM_GPIO1_REG(0xD8) -#define MSM7X30_GPIO_INT_EN_7 MSM_GPIO1_REG(0x22C) +#define MSM_GPIO_INT_EN_0 MSM_GPIO1_REG(0x80) +#define MSM_GPIO_INT_EN_1 MSM_GPIO2_REG(0x60) +#define MSM_GPIO_INT_EN_2 MSM_GPIO1_REG(0x84) +#define MSM_GPIO_INT_EN_3 MSM_GPIO1_REG(0x88) +#define MSM_GPIO_INT_EN_4 MSM_GPIO1_REG(0x8C) +#define MSM_GPIO_INT_EN_5 MSM_GPIO1_REG(0xB8) +#define MSM_GPIO_INT_EN_6 MSM_GPIO1_REG(0xD8) +#define MSM_GPIO_INT_EN_7 MSM_GPIO1_REG(0x22C) /* same pin map as above, write 1 to clear interrupt */ -#define MSM7X30_GPIO_INT_CLEAR_0 MSM_GPIO1_REG(0x90) -#define MSM7X30_GPIO_INT_CLEAR_1 MSM_GPIO2_REG(0x68) -#define MSM7X30_GPIO_INT_CLEAR_2 MSM_GPIO1_REG(0x94) -#define MSM7X30_GPIO_INT_CLEAR_3 MSM_GPIO1_REG(0x98) -#define MSM7X30_GPIO_INT_CLEAR_4 MSM_GPIO1_REG(0x9C) -#define MSM7X30_GPIO_INT_CLEAR_5 MSM_GPIO1_REG(0xB4) -#define MSM7X30_GPIO_INT_CLEAR_6 MSM_GPIO1_REG(0xDC) -#define MSM7X30_GPIO_INT_CLEAR_7 MSM_GPIO1_REG(0x230) +#define MSM_GPIO_INT_CLEAR_0 MSM_GPIO1_REG(0x90) +#define MSM_GPIO_INT_CLEAR_1 MSM_GPIO2_REG(0x68) +#define MSM_GPIO_INT_CLEAR_2 MSM_GPIO1_REG(0x94) +#define MSM_GPIO_INT_CLEAR_3 MSM_GPIO1_REG(0x98) +#define MSM_GPIO_INT_CLEAR_4 MSM_GPIO1_REG(0x9C) +#define MSM_GPIO_INT_CLEAR_5 MSM_GPIO1_REG(0xB4) +#define MSM_GPIO_INT_CLEAR_6 MSM_GPIO1_REG(0xDC) +#define MSM_GPIO_INT_CLEAR_7 MSM_GPIO1_REG(0x230) /* same pin map as above, 1=interrupt pending */ -#define MSM7X30_GPIO_INT_STATUS_0 MSM_GPIO1_REG(0xA0) -#define MSM7X30_GPIO_INT_STATUS_1 MSM_GPIO2_REG(0x70) -#define MSM7X30_GPIO_INT_STATUS_2 MSM_GPIO1_REG(0xA4) -#define MSM7X30_GPIO_INT_STATUS_3 MSM_GPIO1_REG(0xA8) -#define MSM7X30_GPIO_INT_STATUS_4 MSM_GPIO1_REG(0xAC) -#define MSM7X30_GPIO_INT_STATUS_5 MSM_GPIO1_REG(0xB0) -#define MSM7X30_GPIO_INT_STATUS_6 MSM_GPIO1_REG(0xE0) -#define MSM7X30_GPIO_INT_STATUS_7 MSM_GPIO1_REG(0x234) +#define MSM_GPIO_INT_STATUS_0 MSM_GPIO1_REG(0xA0) +#define MSM_GPIO_INT_STATUS_1 MSM_GPIO2_REG(0x70) +#define MSM_GPIO_INT_STATUS_2 MSM_GPIO1_REG(0xA4) +#define MSM_GPIO_INT_STATUS_3 MSM_GPIO1_REG(0xA8) +#define MSM_GPIO_INT_STATUS_4 MSM_GPIO1_REG(0xAC) +#define MSM_GPIO_INT_STATUS_5 MSM_GPIO1_REG(0xB0) +#define MSM_GPIO_INT_STATUS_6 MSM_GPIO1_REG(0xE0) +#define MSM_GPIO_INT_STATUS_7 MSM_GPIO1_REG(0x234) + +#endif + +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) -#define MSM_GPIO_BANK(soc, bank, first, last) \ +#define MSM_GPIO_BANK(bank, first, last) \ { \ .regs = { \ - .out = soc##_GPIO_OUT_##bank, \ - .in = soc##_GPIO_IN_##bank, \ - .int_status = soc##_GPIO_INT_STATUS_##bank, \ - .int_clear = soc##_GPIO_INT_CLEAR_##bank, \ - .int_en = soc##_GPIO_INT_EN_##bank, \ - .int_edge = soc##_GPIO_INT_EDGE_##bank, \ - .int_pos = soc##_GPIO_INT_POS_##bank, \ - .oe = soc##_GPIO_OE_##bank, \ + .out = MSM_GPIO_OUT_##bank, \ + .in = MSM_GPIO_IN_##bank, \ + .int_status = MSM_GPIO_INT_STATUS_##bank, \ + .int_clear = MSM_GPIO_INT_CLEAR_##bank, \ + .int_en = MSM_GPIO_INT_EN_##bank, \ + .int_edge = MSM_GPIO_INT_EDGE_##bank, \ + .int_pos = MSM_GPIO_INT_POS_##bank, \ + .oe = MSM_GPIO_OE_##bank, \ }, \ .chip = { \ .base = (first), \ @@ -329,11 +264,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; } @@ -342,13 +277,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); @@ -365,10 +300,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; } @@ -380,7 +315,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; } @@ -394,7 +331,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; } @@ -402,9 +341,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) @@ -415,6 +357,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); } @@ -438,84 +381,88 @@ static void msm_gpio_free(struct gpio_chip *chip, unsigned offset) #define msm_gpio_free NULL #endif -static struct msm_gpio_chip *msm_gpio_chips; -static int msm_gpio_count; - -static struct msm_gpio_chip msm_gpio_chips_msm7x01[] = { - MSM_GPIO_BANK(MSM7X00, 0, 0, 15), - MSM_GPIO_BANK(MSM7X00, 1, 16, 42), - MSM_GPIO_BANK(MSM7X00, 2, 43, 67), - MSM_GPIO_BANK(MSM7X00, 3, 68, 94), - MSM_GPIO_BANK(MSM7X00, 4, 95, 106), - MSM_GPIO_BANK(MSM7X00, 5, 107, 121), -}; - -static struct msm_gpio_chip msm_gpio_chips_msm7x30[] = { - MSM_GPIO_BANK(MSM7X30, 0, 0, 15), - MSM_GPIO_BANK(MSM7X30, 1, 16, 43), - MSM_GPIO_BANK(MSM7X30, 2, 44, 67), - MSM_GPIO_BANK(MSM7X30, 3, 68, 94), - MSM_GPIO_BANK(MSM7X30, 4, 95, 106), - MSM_GPIO_BANK(MSM7X30, 5, 107, 133), - MSM_GPIO_BANK(MSM7X30, 6, 134, 150), - MSM_GPIO_BANK(MSM7X30, 7, 151, 181), -}; - -static struct msm_gpio_chip msm_gpio_chips_qsd8x50[] = { - MSM_GPIO_BANK(QSD8X50, 0, 0, 15), - MSM_GPIO_BANK(QSD8X50, 1, 16, 42), - MSM_GPIO_BANK(QSD8X50, 2, 43, 67), - MSM_GPIO_BANK(QSD8X50, 3, 68, 94), - MSM_GPIO_BANK(QSD8X50, 4, 95, 103), - MSM_GPIO_BANK(QSD8X50, 5, 104, 121), - MSM_GPIO_BANK(QSD8X50, 6, 122, 152), - MSM_GPIO_BANK(QSD8X50, 7, 153, 164), +struct msm_gpio_chip msm_gpio_chips[] = { +#if defined(CONFIG_ARCH_MSM7X00A) + MSM_GPIO_BANK(0, 0, 15), + MSM_GPIO_BANK(1, 16, 42), + MSM_GPIO_BANK(2, 43, 67), + MSM_GPIO_BANK(3, 68, 94), + MSM_GPIO_BANK(4, 95, 106), + MSM_GPIO_BANK(5, 107, 121), +#elif defined(CONFIG_ARCH_MSM7X25) || defined(CONFIG_ARCH_MSM7X27) + MSM_GPIO_BANK(0, 0, 15), + MSM_GPIO_BANK(1, 16, 42), + MSM_GPIO_BANK(2, 43, 67), + MSM_GPIO_BANK(3, 68, 94), + MSM_GPIO_BANK(4, 95, 106), + MSM_GPIO_BANK(5, 107, 132), +#elif defined(CONFIG_ARCH_MSM7X30) + MSM_GPIO_BANK(0, 0, 15), + MSM_GPIO_BANK(1, 16, 43), + MSM_GPIO_BANK(2, 44, 67), + MSM_GPIO_BANK(3, 68, 94), + MSM_GPIO_BANK(4, 95, 106), + MSM_GPIO_BANK(5, 107, 133), + MSM_GPIO_BANK(6, 134, 150), + MSM_GPIO_BANK(7, 151, 181), +#elif defined(CONFIG_ARCH_QSD8X50) + MSM_GPIO_BANK(0, 0, 15), + MSM_GPIO_BANK(1, 16, 42), + MSM_GPIO_BANK(2, 43, 67), + MSM_GPIO_BANK(3, 68, 94), + MSM_GPIO_BANK(4, 95, 103), + MSM_GPIO_BANK(5, 104, 121), + MSM_GPIO_BANK(6, 122, 152), + MSM_GPIO_BANK(7, 153, 164), +#endif }; 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); @@ -532,17 +479,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) { @@ -550,12 +497,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; } @@ -564,10 +512,13 @@ static void msm_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) { int i, j, mask; unsigned val; + struct irq_chip *chip = irq_desc_get_chip(desc); - for (i = 0; i < msm_gpio_count; i++) { + chained_irq_enter(chip, 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; @@ -580,35 +531,267 @@ static void msm_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) msm_chip->chip.base + j); } } - desc->irq_data.chip->irq_ack(&desc->irq_data); + + chained_irq_exit(chip, 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 int __init msm_init_gpio(void) +static void msm_gpio_sleep_int(unsigned long arg) { - int i, j = 0; + 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(); +} - if (cpu_is_msm7x01()) { - msm_gpio_chips = msm_gpio_chips_msm7x01; - msm_gpio_count = ARRAY_SIZE(msm_gpio_chips_msm7x01); - } else if (cpu_is_msm7x30()) { - msm_gpio_chips = msm_gpio_chips_msm7x30; - msm_gpio_count = ARRAY_SIZE(msm_gpio_chips_msm7x30); - } else if (cpu_is_qsd8x50()) { - msm_gpio_chips = msm_gpio_chips_qsd8x50; - msm_gpio_count = ARRAY_SIZE(msm_gpio_chips_qsd8x50); - } else { - return 0; +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); + } +} + + +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; +} + +static int __devinit msm_gpio_probe(struct platform_device *dev) +{ + int i, j = 0; + int grp_irq; + for (i = FIRST_GPIO_IRQ; i < FIRST_GPIO_IRQ + NR_GPIO_IRQS; i++) { if (i - FIRST_GPIO_IRQ >= msm_gpio_chips[j].chip.base + @@ -620,17 +803,35 @@ static int __init msm_init_gpio(void) set_irq_flags(i, IRQF_VALID); } - for (i = 0; i < msm_gpio_count; i++) { + for (i = 0; i < dev->num_resources; i++) { + grp_irq = platform_get_irq(dev, i); + if (grp_irq < 0) + return -ENXIO; + + irq_set_chained_handler(grp_irq, msm_gpio_irq_handler); + irq_set_irq_wake(grp_irq, (i + 1)); + } + + 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); - irq_set_irq_wake(INT_GPIO_GROUP1, 1); - irq_set_irq_wake(INT_GPIO_GROUP2, 2); + mb(); return 0; } -postcore_initcall(msm_init_gpio); +static struct platform_driver msm_gpio_driver = { + .probe = msm_gpio_probe, + .driver = { + .name = "msmgpio", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_gpio_init(void) +{ + return platform_driver_register(&msm_gpio_driver); +} +postcore_initcall(msm_gpio_init); diff --git a/drivers/gpio/gpio-msm-v2.c b/drivers/gpio/gpio-msm-v2.c index 5cb1227d69cfe596d53f285ea3ddd57214eae7a0..ad436e09dcea5419d21bf8fecdd2a2d099815d3f 100644 --- a/drivers/gpio/gpio-msm-v2.c +++ b/drivers/gpio/gpio-msm-v2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This 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,53 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 #include -#include #include +#include +#include /* 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 +65,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 +152,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 +165,10 @@ 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); + struct irq_domain domain; }; static DEFINE_SPINLOCK(tlmm_lock); @@ -117,22 +180,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 +207,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 +221,53 @@ 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) +#ifdef CONFIG_OF +static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { - return msm_gpiomux_get(chip->base + offset); + struct msm_gpio_dev *g_dev = to_msm_gpio_dev(chip); + struct irq_domain *domain = &g_dev->domain; + return domain->irq_base + (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); + struct msm_gpio_dev *g_dev = to_msm_gpio_dev(chip); + struct irq_domain *domain = &g_dev->domain; + return irq - domain->irq_base; } - +#else static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { - return MSM_GPIO_TO_INT(chip->base + offset); + return MSM_GPIO_TO_INT(offset - chip->base); } static inline int msm_irq_to_gpio(struct gpio_chip *chip, unsigned irq) { return irq - MSM_GPIO_TO_INT(chip->base); } +#endif + +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); +} static struct msm_gpio_dev msm_gpio = { .gpio_chip = { + .label = "msmgpio", .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 +278,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 +310,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 +356,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 +379,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 +401,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 +441,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 +466,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,82 +488,270 @@ 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) +/* + * This lock class tells lockdep that GPIO irqs are in a different + * category than their parent, so it won't report false recursion. + */ +static struct lock_class_key msm_gpio_lock_class; + +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; for (i = 0; i < msm_gpio.gpio_chip.ngpio; ++i) { irq = msm_gpio_to_irq(&msm_gpio.gpio_chip, i); + irq_set_lockdep_class(irq, &msm_gpio_lock_class); irq_set_chip_and_handler(irq, &msm_gpio_irq_chip, handle_level_irq); 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; + + if (!msm_show_resume_irq_mask) + return; - rc = platform_driver_register(&msm_gpio_driver); - if (!rc) { - rc = platform_device_register(&msm_device_gpio); - if (rc) - platform_driver_unregister(&msm_gpio_driver); + 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); + +#ifdef CONFIG_OF +static int msm_gpio_domain_dt_translate(struct irq_domain *d, + struct device_node *controller, + const u32 *intspec, + unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (d->of_node != controller) + return -EINVAL; + if (intsize != 2) + return -EINVAL; + + /* hwirq value */ + *out_hwirq = intspec[0]; + + /* irq flags */ + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; + return 0; +} + +static struct irq_domain_ops msm_gpio_irq_domain_ops = { + .dt_translate = msm_gpio_domain_dt_translate, +}; + +int __init msm_gpio_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain = &msm_gpio.domain; + int start; + + start = irq_domain_find_free_range(0, NR_MSM_GPIOS); + domain->irq_base = irq_alloc_descs(start, 0, NR_MSM_GPIOS, + numa_node_id()); + if (IS_ERR_VALUE(domain->irq_base)) { + WARN(1, "Cannot allocate irq_descs @ IRQ%d\n", start); + return domain->irq_base; + } + + domain->irq_base = irq_domain_find_free_range(0, NR_MSM_GPIOS); + domain->nr_irq = NR_MSM_GPIOS; + domain->of_node = of_node_get(node); + domain->priv = &msm_gpio; + domain->ops = &msm_gpio_irq_domain_ops; + irq_domain_add(domain); + pr_debug("%s: irq_base = %u\n", __func__, domain->irq_base); + + return 0; +} +#endif + 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/drivers/gpio/gpio-pm8xxx-rpc.c b/drivers/gpio/gpio-pm8xxx-rpc.c new file mode 100644 index 0000000000000000000000000000000000000000..1acc7411b29403f2825e912033ee83d73bca4283 --- /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/gpio-sx150x.c b/drivers/gpio/gpio-sx150x.c index a4f73534394e4f37e13b6998e50c64dde43b7f3e..93b94bd379cff5968172a1ffa5ff1be2c587abed 100644 --- a/drivers/gpio/gpio-sx150x.c +++ b/drivers/gpio/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/gpio/pm8xxx-gpio.c b/drivers/gpio/pm8xxx-gpio.c new file mode 100644 index 0000000000000000000000000000000000000000..cb874e859bde448f455c570eb213d0f92715f7ea --- /dev/null +++ b/drivers/gpio/pm8xxx-gpio.c @@ -0,0 +1,462 @@ +/* + * Qualcomm PMIC8XXX GPIO driver + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* 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"); + ret = -ENOMEM; + goto free_chip; + } + + 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 = 0; + 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->bank1); +free_chip: + 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(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 0000000000000000000000000000000000000000..affe980286691d03418897be6df7680dc603c896 --- /dev/null +++ b/drivers/gpio/pm8xxx-mpp.c @@ -0,0 +1,335 @@ +/* + * Qualcomm PM8XXX Multi-Purpose Pin (MPP) driver + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* 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 = 0; + 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/qpnp-gpio.c b/drivers/gpio/qpnp-gpio.c new file mode 100644 index 0000000000000000000000000000000000000000..d9a23e1ce50455e96b7e242bed62b58014bfc05f --- /dev/null +++ b/drivers/gpio/qpnp-gpio.c @@ -0,0 +1,1090 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#define Q_REG_ADDR(q_spec, reg_index) \ + ((q_spec)->offset + reg_index) + +#define Q_REG_STATUS1 0x8 +#define Q_NUM_CTL_REGS 7 + +/* type registers base address offsets */ +#define Q_REG_TYPE 0x10 +#define Q_REG_SUBTYPE 0x11 + +/* gpio peripheral type and subtype values */ +#define Q_GPIO_TYPE 0x10 +#define Q_GPIO_SUBTYPE_GPIO_4CH 0x1 +#define Q_GPIO_SUBTYPE_GPIOC_4CH 0x5 +#define Q_GPIO_SUBTYPE_GPIO_8CH 0x9 +#define Q_GPIO_SUBTYPE_GPIOC_8CH 0xD + +/* control register base address offsets */ +#define Q_REG_MODE_CTL 0x40 +#define Q_REG_DIG_PULL_CTL 0x42 +#define Q_REG_DIG_IN_CTL 0x43 +#define Q_REG_DIG_VIN_CTL 0x44 +#define Q_REG_DIG_OUT_CTL 0x45 +#define Q_REG_EN_CTL 0x46 + +/* control register regs array indices */ +#define Q_REG_I_MODE_CTL 0 +#define Q_REG_I_DIG_PULL_CTL 2 +#define Q_REG_I_DIG_IN_CTL 3 +#define Q_REG_I_DIG_VIN_CTL 4 +#define Q_REG_I_DIG_OUT_CTL 5 +#define Q_REG_I_EN_CTL 6 + +/* control reg: mode */ +#define Q_REG_OUT_INVERT_SHIFT 0 +#define Q_REG_OUT_INVERT_MASK 0x1 +#define Q_REG_SRC_SEL_SHIFT 1 +#define Q_REG_SRC_SEL_MASK 0xE +#define Q_REG_MODE_SEL_SHIFT 4 +#define Q_REG_MODE_SEL_MASK 0x70 + +/* control reg: dig_vin */ +#define Q_REG_VIN_SHIFT 0 +#define Q_REG_VIN_MASK 0x7 + +/* control reg: dig_pull */ +#define Q_REG_PULL_SHIFT 0 +#define Q_REG_PULL_MASK 0x7 + +/* control reg: dig_out */ +#define Q_REG_OUT_STRENGTH_SHIFT 0 +#define Q_REG_OUT_STRENGTH_MASK 0x3 +#define Q_REG_OUT_TYPE_SHIFT 4 +#define Q_REG_OUT_TYPE_MASK 0x30 + +/* control reg: en */ +#define Q_REG_MASTER_EN_SHIFT 7 +#define Q_REG_MASTER_EN_MASK 0x80 + +enum qpnp_gpio_param_type { + Q_GPIO_CFG_DIRECTION, + Q_GPIO_CFG_OUTPUT_TYPE, + Q_GPIO_CFG_INVERT, + Q_GPIO_CFG_PULL, + Q_GPIO_CFG_VIN_SEL, + Q_GPIO_CFG_OUT_STRENGTH, + Q_GPIO_CFG_SRC_SELECT, + Q_GPIO_CFG_MASTER_EN, + Q_GPIO_CFG_INVALID, +}; + +#define Q_NUM_PARAMS Q_GPIO_CFG_INVALID + +/* param error checking */ +#define QPNP_GPIO_DIR_INVALID 3 +#define QPNP_GPIO_INVERT_INVALID 2 +#define QPNP_GPIO_OUT_BUF_INVALID 3 +#define QPNP_GPIO_VIN_INVALID 8 +#define QPNP_GPIO_PULL_INVALID 6 +#define QPNP_GPIO_OUT_STRENGTH_INVALID 4 +#define QPNP_GPIO_SRC_INVALID 8 +#define QPNP_GPIO_MASTER_INVALID 2 + +struct qpnp_gpio_spec { + uint8_t slave; /* 0-15 */ + uint16_t offset; /* 0-255 */ + uint32_t gpio_chip_idx; /* offset from gpio_chip base */ + uint32_t pmic_gpio; /* PMIC gpio number */ + int irq; /* logical IRQ number */ + u8 regs[Q_NUM_CTL_REGS]; /* Control regs */ + u8 type; /* peripheral type */ + u8 subtype; /* peripheral subtype */ + struct device_node *node; + enum qpnp_gpio_param_type params[Q_NUM_PARAMS]; + struct qpnp_gpio_chip *q_chip; +}; + +struct qpnp_gpio_chip { + struct gpio_chip gpio_chip; + struct spmi_device *spmi; + struct qpnp_gpio_spec **pmic_gpios; + struct qpnp_gpio_spec **chip_gpios; + uint32_t pmic_gpio_lowest; + uint32_t pmic_gpio_highest; + struct device_node *int_ctrl; + struct list_head chip_list; + struct dentry *dfs_dir; +}; + +static LIST_HEAD(qpnp_gpio_chips); +static DEFINE_MUTEX(qpnp_gpio_chips_lock); + +static inline void qpnp_pmic_gpio_set_spec(struct qpnp_gpio_chip *q_chip, + uint32_t pmic_gpio, + struct qpnp_gpio_spec *spec) +{ + q_chip->pmic_gpios[pmic_gpio - q_chip->pmic_gpio_lowest] = spec; +} + +static inline struct qpnp_gpio_spec *qpnp_pmic_gpio_get_spec( + struct qpnp_gpio_chip *q_chip, + uint32_t pmic_gpio) +{ + if (pmic_gpio < q_chip->pmic_gpio_lowest || + pmic_gpio > q_chip->pmic_gpio_highest) + return NULL; + + return q_chip->pmic_gpios[pmic_gpio - q_chip->pmic_gpio_lowest]; +} + +static inline struct qpnp_gpio_spec *qpnp_chip_gpio_get_spec( + struct qpnp_gpio_chip *q_chip, + uint32_t chip_gpio) +{ + if (chip_gpio > q_chip->gpio_chip.ngpio) + return NULL; + + return q_chip->chip_gpios[chip_gpio]; +} + +static inline void qpnp_chip_gpio_set_spec(struct qpnp_gpio_chip *q_chip, + uint32_t chip_gpio, + struct qpnp_gpio_spec *spec) +{ + q_chip->chip_gpios[chip_gpio] = spec; +} + +static int qpnp_gpio_check_config(struct qpnp_gpio_spec *q_spec, + struct qpnp_gpio_cfg *param) +{ + int gpio = q_spec->pmic_gpio; + + if (param->direction >= QPNP_GPIO_DIR_INVALID) + pr_err("invalid direction for gpio %d\n", gpio); + else if (param->invert >= QPNP_GPIO_INVERT_INVALID) + pr_err("invalid invert polarity for gpio %d\n", gpio); + else if (param->src_select >= QPNP_GPIO_SRC_INVALID) + pr_err("invalid source select for gpio %d\n", gpio); + else if (param->out_strength >= QPNP_GPIO_OUT_STRENGTH_INVALID || + param->out_strength == 0) + pr_err("invalid out strength for gpio %d\n", gpio); + else if (param->output_type >= QPNP_GPIO_OUT_BUF_INVALID) + pr_err("invalid out type for gpio %d\n", gpio); + else if ((param->output_type == QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS || + param->output_type == QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS) && + (q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_4CH || + (q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_8CH))) + pr_err("invalid out type for gpio %d\n" + "gpioc does not support open-drain\n", gpio); + else if (param->vin_sel >= QPNP_GPIO_VIN_INVALID) + pr_err("invalid vin select value for gpio %d\n", gpio); + else if (param->pull >= QPNP_GPIO_PULL_INVALID) + pr_err("invalid pull value for gpio %d\n", gpio); + else if (param->master_en >= QPNP_GPIO_MASTER_INVALID) + pr_err("invalid master_en value for gpio %d\n", gpio); + else + return 0; + + return -EINVAL; +} + +static inline u8 q_reg_get(u8 *reg, int shift, int mask) +{ + return (*reg & mask) >> shift; +} + +static inline void q_reg_set(u8 *reg, int shift, int mask, int value) +{ + *reg |= (value << shift) & mask; +} + +static inline void q_reg_clr_set(u8 *reg, int shift, int mask, int value) +{ + *reg &= ~mask; + *reg |= (value << shift) & mask; +} + +static int qpnp_gpio_cache_regs(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec) +{ + int rc; + struct device *dev = &q_chip->spmi->dev; + + rc = spmi_ext_register_readl(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_MODE_CTL), + &q_spec->regs[Q_REG_I_MODE_CTL], + Q_NUM_CTL_REGS); + if (rc) + dev_err(dev, "%s: unable to read control regs\n", __func__); + + return rc; +} + +static int _qpnp_gpio_config(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec, + struct qpnp_gpio_cfg *param) +{ + struct device *dev = &q_chip->spmi->dev; + int rc; + + rc = qpnp_gpio_check_config(q_spec, param); + if (rc) + goto gpio_cfg; + + /* set direction */ + q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_MODE_SEL_SHIFT, Q_REG_MODE_SEL_MASK, + param->direction); + + /* output specific configuration */ + q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_OUT_INVERT_SHIFT, Q_REG_OUT_INVERT_MASK, + param->invert); + q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_SRC_SEL_SHIFT, Q_REG_SRC_SEL_MASK, + param->src_select); + q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], + Q_REG_OUT_STRENGTH_SHIFT, Q_REG_OUT_STRENGTH_MASK, + param->out_strength); + q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], + Q_REG_OUT_TYPE_SHIFT, Q_REG_OUT_TYPE_MASK, + param->output_type); + + /* config applicable for both input / output */ + q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_VIN_CTL], + Q_REG_VIN_SHIFT, Q_REG_VIN_MASK, + param->vin_sel); + q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_PULL_CTL], + Q_REG_PULL_SHIFT, Q_REG_PULL_MASK, + param->pull); + q_reg_clr_set(&q_spec->regs[Q_REG_I_EN_CTL], + Q_REG_MASTER_EN_SHIFT, Q_REG_MASTER_EN_MASK, + param->master_en); + + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_MODE_CTL), + &q_spec->regs[Q_REG_I_MODE_CTL], Q_NUM_CTL_REGS); + if (rc) { + dev_err(&q_chip->spmi->dev, "%s: unable to write master" + " enable\n", __func__); + goto gpio_cfg; + } + + return 0; + +gpio_cfg: + dev_err(dev, "%s: unable to set default config for" + " pmic gpio %d\n", __func__, q_spec->pmic_gpio); + + return rc; +} + +int qpnp_gpio_config(int gpio, struct qpnp_gpio_cfg *param) +{ + int rc, chip_offset; + struct qpnp_gpio_chip *q_chip; + struct qpnp_gpio_spec *q_spec = NULL; + struct gpio_chip *gpio_chip; + + if (param == NULL) + return -EINVAL; + + mutex_lock(&qpnp_gpio_chips_lock); + list_for_each_entry(q_chip, &qpnp_gpio_chips, chip_list) { + gpio_chip = &q_chip->gpio_chip; + if (gpio >= gpio_chip->base + && gpio < gpio_chip->base + gpio_chip->ngpio) { + chip_offset = gpio - gpio_chip->base; + q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset); + if (WARN_ON(!q_spec)) { + mutex_unlock(&qpnp_gpio_chips_lock); + return -ENODEV; + } + break; + } + } + mutex_unlock(&qpnp_gpio_chips_lock); + + rc = _qpnp_gpio_config(q_chip, q_spec, param); + + return rc; +} +EXPORT_SYMBOL(qpnp_gpio_config); + +int qpnp_gpio_map_gpio(uint16_t slave_id, uint32_t pmic_gpio) +{ + struct qpnp_gpio_chip *q_chip; + struct qpnp_gpio_spec *q_spec = NULL; + + mutex_lock(&qpnp_gpio_chips_lock); + list_for_each_entry(q_chip, &qpnp_gpio_chips, chip_list) { + if (q_chip->spmi->sid != slave_id) + continue; + if (q_chip->pmic_gpio_lowest <= pmic_gpio && + q_chip->pmic_gpio_highest >= pmic_gpio) { + q_spec = qpnp_pmic_gpio_get_spec(q_chip, pmic_gpio); + mutex_unlock(&qpnp_gpio_chips_lock); + if (WARN_ON(!q_spec)) + return -ENODEV; + return q_chip->gpio_chip.base + q_spec->gpio_chip_idx; + } + } + mutex_unlock(&qpnp_gpio_chips_lock); + return -EINVAL; +} +EXPORT_SYMBOL(qpnp_gpio_map_gpio); + +static int qpnp_gpio_to_irq(struct gpio_chip *gpio_chip, unsigned offset) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (!q_spec) + return -EINVAL; + + return q_spec->irq; +} + +static int qpnp_gpio_get(struct gpio_chip *gpio_chip, unsigned offset) +{ + int rc, ret_val; + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec = NULL; + u8 buf[1]; + + if (WARN_ON(!q_chip)) + return -ENODEV; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return -ENODEV; + + /* gpio val is from RT status iff input is enabled */ + if ((q_spec->regs[Q_REG_I_MODE_CTL] & Q_REG_MODE_SEL_MASK) + == QPNP_GPIO_DIR_IN) { + /* INT_RT_STS */ + rc = spmi_ext_register_readl(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_STATUS1), + &buf[0], 1); + return buf[0]; + + } else { + ret_val = (q_spec->regs[Q_REG_I_MODE_CTL] & + Q_REG_OUT_INVERT_MASK) >> Q_REG_OUT_INVERT_SHIFT; + return ret_val; + } + + return 0; +} + +static int __qpnp_gpio_set(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec, int value) +{ + int rc; + + if (!q_chip || !q_spec) + return -EINVAL; + + if (value) + q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_OUT_INVERT_SHIFT, Q_REG_OUT_INVERT_MASK, 1); + else + q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_OUT_INVERT_SHIFT, Q_REG_OUT_INVERT_MASK, 0); + + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_I_MODE_CTL), + &q_spec->regs[Q_REG_I_MODE_CTL], 1); + if (rc) + dev_err(&q_chip->spmi->dev, "%s: spmi write failed\n", + __func__); + return rc; +} + + +static void qpnp_gpio_set(struct gpio_chip *gpio_chip, + unsigned offset, int value) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + if (WARN_ON(!q_chip)) + return; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return; + + __qpnp_gpio_set(q_chip, q_spec, value); +} + +static int qpnp_gpio_set_direction(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec, int direction) +{ + int rc; + + if (!q_chip || !q_spec) + return -EINVAL; + + if (direction >= QPNP_GPIO_DIR_INVALID) { + pr_err("invalid direction specification %d\n", direction); + return -EINVAL; + } + + q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_MODE_SEL_SHIFT, + Q_REG_MODE_SEL_MASK, + direction); + + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_I_MODE_CTL), + &q_spec->regs[Q_REG_I_MODE_CTL], 1); + return rc; +} + +static int qpnp_gpio_direction_input(struct gpio_chip *gpio_chip, + unsigned offset) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + if (WARN_ON(!q_chip)) + return -ENODEV; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return -ENODEV; + + return qpnp_gpio_set_direction(q_chip, q_spec, QPNP_GPIO_DIR_IN); +} + +static int qpnp_gpio_direction_output(struct gpio_chip *gpio_chip, + unsigned offset, + int val) +{ + int rc; + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + + if (WARN_ON(!q_chip)) + return -ENODEV; + + q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); + if (WARN_ON(!q_spec)) + return -ENODEV; + + rc = __qpnp_gpio_set(q_chip, q_spec, val); + if (rc) + return rc; + + rc = qpnp_gpio_set_direction(q_chip, q_spec, QPNP_GPIO_DIR_OUT); + + return rc; +} + +static int qpnp_gpio_of_gpio_xlate(struct gpio_chip *gpio_chip, + struct device_node *np, + const void *gpio_spec, u32 *flags) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev); + struct qpnp_gpio_spec *q_spec; + const __be32 *gpio = gpio_spec; + u32 n = be32_to_cpup(gpio); + + if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) { + pr_err("of_gpio_n_cells < 2\n"); + return -EINVAL; + } + + q_spec = qpnp_pmic_gpio_get_spec(q_chip, n); + if (!q_spec) { + pr_err("no such PMIC gpio %u in device topology\n", n); + return -EINVAL; + } + + if (flags) + *flags = be32_to_cpu(gpio[1]); + + return q_spec->gpio_chip_idx; +} + +static int qpnp_gpio_apply_config(struct qpnp_gpio_chip *q_chip, + struct qpnp_gpio_spec *q_spec) +{ + struct qpnp_gpio_cfg param; + struct device_node *node = q_spec->node; + int rc; + + param.direction = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_MODE_SEL_SHIFT, + Q_REG_MODE_SEL_MASK); + param.output_type = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], + Q_REG_OUT_TYPE_SHIFT, + Q_REG_OUT_TYPE_MASK); + param.invert = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_OUT_INVERT_MASK, + Q_REG_OUT_INVERT_MASK); + param.pull = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_PULL_SHIFT, Q_REG_PULL_MASK); + param.vin_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_VIN_CTL], + Q_REG_VIN_SHIFT, Q_REG_VIN_MASK); + param.out_strength = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], + Q_REG_OUT_STRENGTH_SHIFT, + Q_REG_OUT_STRENGTH_MASK); + param.src_select = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], + Q_REG_SRC_SEL_SHIFT, Q_REG_SRC_SEL_MASK); + param.master_en = q_reg_get(&q_spec->regs[Q_REG_I_EN_CTL], + Q_REG_MASTER_EN_SHIFT, + Q_REG_MASTER_EN_MASK); + + of_property_read_u32(node, "qcom,direction", + ¶m.direction); + of_property_read_u32(node, "qcom,output-type", + ¶m.output_type); + of_property_read_u32(node, "qcom,invert", + ¶m.invert); + of_property_read_u32(node, "qcom,pull", + ¶m.pull); + of_property_read_u32(node, "qcom,vin-sel", + ¶m.vin_sel); + of_property_read_u32(node, "qcom,out-strength", + ¶m.out_strength); + of_property_read_u32(node, "qcom,src-select", + ¶m.src_select); + rc = of_property_read_u32(node, "qcom,master-en", + ¶m.master_en); + + rc = _qpnp_gpio_config(q_chip, q_spec, ¶m); + + return rc; +} + +static int qpnp_gpio_free_chip(struct qpnp_gpio_chip *q_chip) +{ + struct spmi_device *spmi = q_chip->spmi; + int rc, i; + + if (q_chip->chip_gpios) + for (i = 0; i < spmi->num_dev_node; i++) + kfree(q_chip->chip_gpios[i]); + + mutex_lock(&qpnp_gpio_chips_lock); + list_del(&q_chip->chip_list); + mutex_unlock(&qpnp_gpio_chips_lock); + rc = gpiochip_remove(&q_chip->gpio_chip); + if (rc) + dev_err(&q_chip->spmi->dev, "%s: unable to remove gpio\n", + __func__); + kfree(q_chip->chip_gpios); + kfree(q_chip->pmic_gpios); + kfree(q_chip); + return rc; +} + +#ifdef CONFIG_GPIO_QPNP_DEBUG +struct qpnp_gpio_reg { + uint32_t addr; + uint32_t idx; + uint32_t shift; + uint32_t mask; +}; + +static struct dentry *driver_dfs_dir; + +static int qpnp_gpio_reg_attr(enum qpnp_gpio_param_type type, + struct qpnp_gpio_reg *cfg) +{ + switch (type) { + case Q_GPIO_CFG_DIRECTION: + cfg->addr = Q_REG_MODE_CTL; + cfg->idx = Q_REG_I_MODE_CTL; + cfg->shift = Q_REG_MODE_SEL_SHIFT; + cfg->mask = Q_REG_MODE_SEL_MASK; + break; + case Q_GPIO_CFG_OUTPUT_TYPE: + cfg->addr = Q_REG_DIG_OUT_CTL; + cfg->idx = Q_REG_I_DIG_OUT_CTL; + cfg->shift = Q_REG_OUT_TYPE_SHIFT; + cfg->mask = Q_REG_OUT_TYPE_MASK; + break; + case Q_GPIO_CFG_INVERT: + cfg->addr = Q_REG_MODE_CTL; + cfg->idx = Q_REG_I_MODE_CTL; + cfg->shift = Q_REG_OUT_INVERT_SHIFT; + cfg->mask = Q_REG_OUT_INVERT_MASK; + break; + case Q_GPIO_CFG_PULL: + cfg->addr = Q_REG_DIG_PULL_CTL; + cfg->idx = Q_REG_I_DIG_PULL_CTL; + cfg->shift = Q_REG_PULL_SHIFT; + cfg->mask = Q_REG_PULL_MASK; + break; + case Q_GPIO_CFG_VIN_SEL: + cfg->addr = Q_REG_DIG_VIN_CTL; + cfg->idx = Q_REG_I_DIG_VIN_CTL; + cfg->shift = Q_REG_VIN_SHIFT; + cfg->mask = Q_REG_VIN_MASK; + break; + case Q_GPIO_CFG_OUT_STRENGTH: + cfg->addr = Q_REG_DIG_OUT_CTL; + cfg->idx = Q_REG_I_DIG_OUT_CTL; + cfg->shift = Q_REG_OUT_STRENGTH_SHIFT; + cfg->mask = Q_REG_OUT_STRENGTH_MASK; + break; + case Q_GPIO_CFG_SRC_SELECT: + cfg->addr = Q_REG_MODE_CTL; + cfg->idx = Q_REG_I_MODE_CTL; + cfg->shift = Q_REG_SRC_SEL_SHIFT; + cfg->mask = Q_REG_SRC_SEL_MASK; + break; + case Q_GPIO_CFG_MASTER_EN: + cfg->addr = Q_REG_EN_CTL; + cfg->idx = Q_REG_I_EN_CTL; + cfg->shift = Q_REG_MASTER_EN_SHIFT; + cfg->mask = Q_REG_MASTER_EN_MASK; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int qpnp_gpio_debugfs_get(void *data, u64 *val) +{ + enum qpnp_gpio_param_type *idx = data; + struct qpnp_gpio_spec *q_spec; + struct qpnp_gpio_reg cfg = {}; + int rc; + + rc = qpnp_gpio_reg_attr(*idx, &cfg); + if (rc) + return rc; + q_spec = container_of(idx, struct qpnp_gpio_spec, params[*idx]); + *val = q_reg_get(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask); + return 0; +} + +static int qpnp_gpio_check_reg_val(enum qpnp_gpio_param_type idx, + struct qpnp_gpio_spec *q_spec, + uint32_t val) +{ + switch (idx) { + case Q_GPIO_CFG_DIRECTION: + if (val >= QPNP_GPIO_DIR_INVALID) + return -EINVAL; + break; + case Q_GPIO_CFG_OUTPUT_TYPE: + if ((val >= QPNP_GPIO_OUT_BUF_INVALID) || + ((val == QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS || + val == QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS) && + (q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_4CH || + (q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_8CH)))) + return -EINVAL; + break; + case Q_GPIO_CFG_INVERT: + if (val >= QPNP_GPIO_INVERT_INVALID) + return -EINVAL; + break; + case Q_GPIO_CFG_PULL: + if (val >= QPNP_GPIO_PULL_INVALID) + return -EINVAL; + break; + case Q_GPIO_CFG_VIN_SEL: + if (val >= QPNP_GPIO_VIN_INVALID) + return -EINVAL; + break; + case Q_GPIO_CFG_OUT_STRENGTH: + if (val >= QPNP_GPIO_OUT_STRENGTH_INVALID || + val == 0) + return -EINVAL; + break; + case Q_GPIO_CFG_SRC_SELECT: + if (val >= QPNP_GPIO_SRC_INVALID) + return -EINVAL; + break; + case Q_GPIO_CFG_MASTER_EN: + if (val >= QPNP_GPIO_MASTER_INVALID) + return -EINVAL; + break; + default: + pr_err("invalid param type %u specified\n", idx); + return -EINVAL; + } + return 0; +} + +static int qpnp_gpio_debugfs_set(void *data, u64 val) +{ + enum qpnp_gpio_param_type *idx = data; + struct qpnp_gpio_spec *q_spec; + struct qpnp_gpio_chip *q_chip; + struct qpnp_gpio_reg cfg = {}; + int rc; + + q_spec = container_of(idx, struct qpnp_gpio_spec, params[*idx]); + q_chip = q_spec->q_chip; + + rc = qpnp_gpio_check_reg_val(*idx, q_spec, val); + if (rc) + return rc; + + rc = qpnp_gpio_reg_attr(*idx, &cfg); + if (rc) + return rc; + q_reg_clr_set(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask, val); + rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, cfg.addr), + &q_spec->regs[cfg.idx], 1); + + return rc; +} +DEFINE_SIMPLE_ATTRIBUTE(qpnp_gpio_fops, qpnp_gpio_debugfs_get, + qpnp_gpio_debugfs_set, "%llu\n"); + +#define DEBUGFS_BUF_SIZE 11 /* supports 2^32 in decimal */ + +struct qpnp_gpio_debugfs_args { + enum qpnp_gpio_param_type type; + const char *filename; +}; + +static struct qpnp_gpio_debugfs_args dfs_args[] = { + { Q_GPIO_CFG_DIRECTION, "direction" }, + { Q_GPIO_CFG_OUTPUT_TYPE, "output_type" }, + { Q_GPIO_CFG_INVERT, "invert" }, + { Q_GPIO_CFG_PULL, "pull" }, + { Q_GPIO_CFG_VIN_SEL, "vin_sel" }, + { Q_GPIO_CFG_OUT_STRENGTH, "out_strength" }, + { Q_GPIO_CFG_SRC_SELECT, "src_select" }, + { Q_GPIO_CFG_MASTER_EN, "master_en" } +}; + +static int qpnp_gpio_debugfs_create(struct qpnp_gpio_chip *q_chip) +{ + struct spmi_device *spmi = q_chip->spmi; + struct device *dev = &spmi->dev; + struct qpnp_gpio_spec *q_spec; + enum qpnp_gpio_param_type *params; + enum qpnp_gpio_param_type type; + char pmic_gpio[DEBUGFS_BUF_SIZE]; + const char *filename; + struct dentry *dfs, *dfs_io_dir; + int i, j; + + BUG_ON(Q_NUM_PARAMS != ARRAY_SIZE(dfs_args)); + + q_chip->dfs_dir = debugfs_create_dir(dev->of_node->name, + driver_dfs_dir); + if (q_chip->dfs_dir == NULL) { + dev_err(dev, "%s: cannot register chip debugfs directory %s\n", + __func__, dev->of_node->name); + return -ENODEV; + } + + for (i = 0; i < spmi->num_dev_node; i++) { + q_spec = qpnp_chip_gpio_get_spec(q_chip, i); + params = q_spec->params; + snprintf(pmic_gpio, DEBUGFS_BUF_SIZE, "%u", q_spec->pmic_gpio); + dfs_io_dir = debugfs_create_dir(pmic_gpio, + q_chip->dfs_dir); + if (dfs_io_dir == NULL) + goto dfs_err; + + for (j = 0; j < Q_NUM_PARAMS; j++) { + type = dfs_args[j].type; + filename = dfs_args[j].filename; + + params[type] = type; + dfs = debugfs_create_file( + filename, + S_IRUGO | S_IWUSR, + dfs_io_dir, + &q_spec->params[type], + &qpnp_gpio_fops); + if (dfs == NULL) + goto dfs_err; + } + } + return 0; +dfs_err: + dev_err(dev, "%s: cannot register debugfs for pmic gpio %u on" + " chip %s\n", __func__, + q_spec->pmic_gpio, dev->of_node->name); + debugfs_remove_recursive(q_chip->dfs_dir); + return -ENFILE; +} +#else +static int qpnp_gpio_debugfs_create(struct qpnp_gpio_chip *q_chip) +{ + return 0; +} +#endif + +static int qpnp_gpio_probe(struct spmi_device *spmi) +{ + struct qpnp_gpio_chip *q_chip; + struct resource *res; + struct qpnp_gpio_spec *q_spec; + int i, rc; + int lowest_gpio = UINT_MAX, highest_gpio = 0; + u32 intspec[3], gpio; + char buf[2]; + + q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL); + if (!q_chip) { + dev_err(&spmi->dev, "%s: Can't allocate gpio_chip\n", + __func__); + return -ENOMEM; + } + q_chip->spmi = spmi; + dev_set_drvdata(&spmi->dev, q_chip); + + mutex_lock(&qpnp_gpio_chips_lock); + list_add(&q_chip->chip_list, &qpnp_gpio_chips); + mutex_unlock(&qpnp_gpio_chips_lock); + + /* first scan through nodes to find the range required for allocation */ + for (i = 0; i < spmi->num_dev_node; i++) { + rc = of_property_read_u32(spmi->dev_node[i].of_node, + "qcom,gpio-num", &gpio); + if (rc) { + dev_err(&spmi->dev, "%s: unable to get" + " qcom,gpio-num property\n", __func__); + goto err_probe; + } + + if (gpio < lowest_gpio) + lowest_gpio = gpio; + if (gpio > highest_gpio) + highest_gpio = gpio; + } + + if (highest_gpio < lowest_gpio) { + dev_err(&spmi->dev, "%s: no device nodes specified in" + " topology\n", __func__); + rc = -EINVAL; + goto err_probe; + } else if (lowest_gpio == 0) { + dev_err(&spmi->dev, "%s: 0 is not a valid PMIC GPIO\n", + __func__); + rc = -EINVAL; + goto err_probe; + } + + q_chip->pmic_gpio_lowest = lowest_gpio; + q_chip->pmic_gpio_highest = highest_gpio; + + /* allocate gpio lookup tables */ + q_chip->pmic_gpios = kzalloc(sizeof(struct qpnp_gpio_spec *) * + highest_gpio - lowest_gpio + 1, + GFP_KERNEL); + q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_gpio_spec *) * + spmi->num_dev_node, GFP_KERNEL); + if (!q_chip->pmic_gpios || !q_chip->chip_gpios) { + dev_err(&spmi->dev, "%s: unable to allocate memory\n", + __func__); + rc = -ENOMEM; + goto err_probe; + } + + /* get interrupt controller device_node */ + q_chip->int_ctrl = of_irq_find_parent(spmi->dev.of_node); + if (!q_chip->int_ctrl) { + dev_err(&spmi->dev, "%s: Can't find interrupt parent\n", + __func__); + rc = -EINVAL; + goto err_probe; + } + + /* now scan through again and populate the lookup table */ + for (i = 0; i < spmi->num_dev_node; i++) { + res = qpnp_get_resource(spmi, i, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&spmi->dev, "%s: node %s is missing has no" + " base address definition\n", + __func__, spmi->dev_node[i].of_node->full_name); + } + + rc = of_property_read_u32(spmi->dev_node[i].of_node, + "qcom,gpio-num", &gpio); + if (rc) { + dev_err(&spmi->dev, "%s: unable to get" + " qcom,gpio-num property\n", __func__); + goto err_probe; + } + + q_spec = kzalloc(sizeof(struct qpnp_gpio_spec), + GFP_KERNEL); + if (!q_spec) { + dev_err(&spmi->dev, "%s: unable to allocate" + " memory\n", + __func__); + rc = -ENOMEM; + goto err_probe; + } + + q_spec->slave = spmi->sid; + q_spec->offset = res->start; + q_spec->gpio_chip_idx = i; + q_spec->pmic_gpio = gpio; + q_spec->node = spmi->dev_node[i].of_node; + q_spec->q_chip = q_chip; + + rc = spmi_ext_register_readl(spmi->ctrl, q_spec->slave, + Q_REG_ADDR(q_spec, Q_REG_TYPE), &buf[0], 2); + if (rc) { + dev_err(&spmi->dev, "%s: unable to read type regs\n", + __func__); + goto err_probe; + } + q_spec->type = buf[0]; + q_spec->subtype = buf[1]; + + /* call into irq_domain to get irq mapping */ + intspec[0] = q_chip->spmi->sid; + intspec[1] = (q_spec->offset >> 8) & 0xFF; + intspec[2] = 0; + q_spec->irq = irq_create_of_mapping(q_chip->int_ctrl, + intspec, 3); + if (!q_spec->irq) { + dev_err(&spmi->dev, "%s: invalid irq for gpio" + " %u\n", __func__, gpio); + rc = -EINVAL; + goto err_probe; + } + /* initialize lookup table params */ + qpnp_pmic_gpio_set_spec(q_chip, gpio, q_spec); + qpnp_chip_gpio_set_spec(q_chip, i, q_spec); + } + + q_chip->gpio_chip.base = -1; + q_chip->gpio_chip.ngpio = spmi->num_dev_node; + q_chip->gpio_chip.label = "qpnp-gpio"; + q_chip->gpio_chip.direction_input = qpnp_gpio_direction_input; + q_chip->gpio_chip.direction_output = qpnp_gpio_direction_output; + q_chip->gpio_chip.to_irq = qpnp_gpio_to_irq; + q_chip->gpio_chip.get = qpnp_gpio_get; + q_chip->gpio_chip.set = qpnp_gpio_set; + q_chip->gpio_chip.dev = &spmi->dev; + q_chip->gpio_chip.of_xlate = qpnp_gpio_of_gpio_xlate; + q_chip->gpio_chip.of_gpio_n_cells = 2; + q_chip->gpio_chip.can_sleep = 0; + + rc = gpiochip_add(&q_chip->gpio_chip); + if (rc) { + dev_err(&spmi->dev, "%s: Can't add gpio chip, rc = %d\n", + __func__, rc); + goto err_probe; + } + + /* now configure gpio config defaults if they exist */ + for (i = 0; i < spmi->num_dev_node; i++) { + q_spec = qpnp_chip_gpio_get_spec(q_chip, i); + if (WARN_ON(!q_spec)) { + rc = -ENODEV; + goto err_probe; + } + + rc = qpnp_gpio_cache_regs(q_chip, q_spec); + if (rc) + goto err_probe; + + rc = qpnp_gpio_apply_config(q_chip, q_spec); + if (rc) + goto err_probe; + } + + dev_dbg(&spmi->dev, "%s: gpio_chip registered between %d-%u\n", + __func__, q_chip->gpio_chip.base, + (q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1); + + rc = qpnp_gpio_debugfs_create(q_chip); + if (rc) { + dev_err(&spmi->dev, "%s: debugfs creation failed\n", __func__); + goto err_probe; + } + + return 0; + +err_probe: + qpnp_gpio_free_chip(q_chip); + return rc; +} + +static int qpnp_gpio_remove(struct spmi_device *spmi) +{ + struct qpnp_gpio_chip *q_chip = dev_get_drvdata(&spmi->dev); + + debugfs_remove_recursive(q_chip->dfs_dir); + + return qpnp_gpio_free_chip(q_chip); +} + +static struct of_device_id spmi_match_table[] = { + { .compatible = "qcom,qpnp-gpio", + }, + {} +}; + +static const struct spmi_device_id qpnp_gpio_id[] = { + { "qcom,qpnp-gpio", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spmi, qpnp_gpio_id); + +static struct spmi_driver qpnp_gpio_driver = { + .driver = { + .name = "qcom,qpnp-gpio", + .of_match_table = spmi_match_table, + }, + .probe = qpnp_gpio_probe, + .remove = qpnp_gpio_remove, + .id_table = qpnp_gpio_id, +}; + +static int __init qpnp_gpio_init(void) +{ +#ifdef CONFIG_GPIO_QPNP_DEBUG + driver_dfs_dir = debugfs_create_dir("qpnp_gpio", NULL); + if (driver_dfs_dir == NULL) + pr_err("Cannot register top level debugfs directory\n"); +#endif + + return spmi_driver_register(&qpnp_gpio_driver); +} + +static void __exit qpnp_gpio_exit(void) +{ +#ifdef CONFIG_GPIO_QPNP_DEBUG + debugfs_remove_recursive(driver_dfs_dir); +#endif + spmi_driver_unregister(&qpnp_gpio_driver); +} + +MODULE_DESCRIPTION("QPNP PMIC gpio driver"); +MODULE_LICENSE("GPL v2"); + +module_init(qpnp_gpio_init); +module_exit(qpnp_gpio_exit); diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index ca2d3b34dbf59285a55a5c0f762258786b99fe5c..01cef64bf4d295e3d7c2805da1b3cf993ee4dbb4 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/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 9d24d65f0c3e54491badaa0e983d2e1ba5edfce1..b114875ec7d4be85cf420af522e46b92f337b439 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -3737,6 +3737,10 @@ # define GEN6_RCPBUNIT_CLOCK_GATE_DISABLE (1 << 12) # define GEN6_RCCUNIT_CLOCK_GATE_DISABLE (1 << 11) +#define GEN6_UCGCTL2 0x9404 +# define GEN6_RCPBUNIT_CLOCK_GATE_DISABLE (1 << 12) +# define GEN6_RCCUNIT_CLOCK_GATE_DISABLE (1 << 11) + #define GEN6_RPNSWREQ 0xA008 #define GEN6_TURBO_DISABLE (1<<31) #define GEN6_FREQUENCY(x) ((x)<<25) diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index f7eb5d8b9fd3d1b0957d2912da9886b73ac50874..48dae40ef2e00d18b6b30b8fc55b057fbaf4cd0f 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -439,6 +439,9 @@ struct radeon_connector { struct radeon_i2c_chan *ddc_bus; /* some systems have an hdmi and vga port with a shared ddc line */ bool shared_ddc; + /* for some Radeon chip families we apply an additional EDID header + check as part of the DDC probe */ + bool requires_extended_probe; bool use_digital; /* we need to mind the EDID between detect and get modes due to analog/digital/tvencoder */ @@ -526,7 +529,8 @@ extern void radeon_i2c_put_byte(struct radeon_i2c_chan *i2c, u8 val); extern void radeon_router_select_ddc_port(struct radeon_connector *radeon_connector); extern void radeon_router_select_cd_port(struct radeon_connector *radeon_connector); -extern bool radeon_ddc_probe(struct radeon_connector *radeon_connector); +extern bool radeon_ddc_probe(struct radeon_connector *radeon_connector, + bool requires_extended_probe); extern int radeon_ddc_get_modes(struct radeon_connector *radeon_connector); extern struct drm_encoder *radeon_best_encoder(struct drm_connector *connector); diff --git a/drivers/gpu/ion/Kconfig b/drivers/gpu/ion/Kconfig index b5bfdb47fd0957a55ce1780d928f06b3ed2e2f72..5bb254b1655ccc0f9bccefb3c6d5d2131490991a 100644 --- a/drivers/gpu/ion/Kconfig +++ b/drivers/gpu/ion/Kconfig @@ -11,3 +11,8 @@ config ION_TEGRA help Choose this option if you wish to use ion on an nVidia Tegra. +config ION_MSM + tristate "Ion for MSM" + depends on ARCH_MSM && ION + help + Choose this option if you wish to use ion on an MSM target. diff --git a/drivers/gpu/ion/Makefile b/drivers/gpu/ion/Makefile index 73fe3fa107067fb26bcd1007da3f9442242ede0c..c9e8a944052e910915939beaa4650a1e1568ecbe 100644 --- a/drivers/gpu/ion/Makefile +++ b/drivers/gpu/ion/Makefile @@ -1,2 +1,3 @@ -obj-$(CONFIG_ION) += ion.o ion_heap.o ion_system_heap.o ion_carveout_heap.o +obj-$(CONFIG_ION) += ion.o ion_heap.o ion_system_heap.o ion_carveout_heap.o ion_iommu_heap.o ion_cp_heap.o obj-$(CONFIG_ION_TEGRA) += tegra/ +obj-$(CONFIG_ION_MSM) += msm/ diff --git a/drivers/gpu/ion/ion.c b/drivers/gpu/ion/ion.c index f8cb55fc2442d42795d741db240e46d09cfe98c4..7e84aa716321c290b32429fbe0c104e551c5cfce 100644 --- a/drivers/gpu/ion/ion.c +++ b/drivers/gpu/ion/ion.c @@ -2,6 +2,7 @@ * drivers/gpu/ion/ion.c * * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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,15 +15,14 @@ * */ +#include #include #include #include #include #include #include -#include #include -#include #include #include #include @@ -31,8 +31,8 @@ #include #include #include -#include +#include #include "ion_priv.h" #define DEBUG @@ -51,12 +51,14 @@ struct ion_device { struct rb_root heaps; long (*custom_ioctl) (struct ion_client *client, unsigned int cmd, unsigned long arg); - struct rb_root clients; + struct rb_root user_clients; + struct rb_root kernel_clients; struct dentry *debug_root; }; /** * struct ion_client - a process/hw block local address space + * @ref: for reference counting the client * @node: node in the tree of all clients * @dev: backpointer to ion device * @handles: an rb tree of all the handles in this client @@ -70,12 +72,13 @@ struct ion_device { * as well as the handles themselves, and should be held while modifying either. */ struct ion_client { + struct kref ref; struct rb_node node; struct ion_device *dev; struct rb_root handles; struct mutex lock; unsigned int heap_mask; - const char *name; + char *name; struct task_struct *task; pid_t pid; struct dentry *debug_root; @@ -89,6 +92,7 @@ struct ion_client { * @node: node in the client's handle rbtree * @kmap_cnt: count of times this client has mapped to kernel * @dmap_cnt: count of times this client has mapped for dma + * @usermap_cnt: count of times this client has mapped for userspace * * Modifications to node, map_cnt or mapping should be protected by the * lock in the client. Other fields are never changed after initialization. @@ -99,8 +103,31 @@ struct ion_handle { struct ion_buffer *buffer; struct rb_node node; unsigned int kmap_cnt; + unsigned int dmap_cnt; + unsigned int usermap_cnt; + unsigned int iommu_map_cnt; }; +static void ion_iommu_release(struct kref *kref); + +static int ion_validate_buffer_flags(struct ion_buffer *buffer, + unsigned long flags) +{ + if (buffer->kmap_cnt || buffer->dmap_cnt || buffer->umap_cnt || + buffer->iommu_map_cnt) { + if (buffer->flags != flags) { + pr_err("%s: buffer was already mapped with flags %lx," + " cannot map with flags %lx\n", __func__, + buffer->flags, flags); + return 1; + } + + } else { + buffer->flags = flags; + } + return 0; +} + /* this function should only be called while dev->lock is held */ static void ion_buffer_add(struct ion_device *dev, struct ion_buffer *buffer) @@ -127,6 +154,61 @@ static void ion_buffer_add(struct ion_device *dev, rb_insert_color(&buffer->node, &dev->buffers); } +static void ion_iommu_add(struct ion_buffer *buffer, + struct ion_iommu_map *iommu) +{ + struct rb_node **p = &buffer->iommu_maps.rb_node; + struct rb_node *parent = NULL; + struct ion_iommu_map *entry; + + while (*p) { + parent = *p; + entry = rb_entry(parent, struct ion_iommu_map, node); + + if (iommu->key < entry->key) { + p = &(*p)->rb_left; + } else if (iommu->key > entry->key) { + p = &(*p)->rb_right; + } else { + pr_err("%s: buffer %p already has mapping for domain %d" + " and partition %d\n", __func__, + buffer, + iommu_map_domain(iommu), + iommu_map_partition(iommu)); + BUG(); + } + } + + rb_link_node(&iommu->node, parent, p); + rb_insert_color(&iommu->node, &buffer->iommu_maps); + +} + +static struct ion_iommu_map *ion_iommu_lookup(struct ion_buffer *buffer, + unsigned int domain_no, + unsigned int partition_no) +{ + struct rb_node **p = &buffer->iommu_maps.rb_node; + struct rb_node *parent = NULL; + struct ion_iommu_map *entry; + uint64_t key = domain_no; + key = key << 32 | partition_no; + + while (*p) { + parent = *p; + entry = rb_entry(parent, struct ion_iommu_map, node); + + if (key < entry->key) + p = &(*p)->rb_left; + else if (key > entry->key) + p = &(*p)->rb_right; + else + return entry; + } + + return NULL; +} + /* this function should only be called while dev->lock is held */ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap, struct ion_device *dev, @@ -135,7 +217,6 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap, unsigned long flags) { struct ion_buffer *buffer; - struct sg_table *table; int ret; buffer = kzalloc(sizeof(struct ion_buffer), GFP_KERNEL); @@ -150,15 +231,6 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap, kfree(buffer); return ERR_PTR(ret); } - - table = buffer->heap->ops->map_dma(buffer->heap, buffer); - if (IS_ERR_OR_NULL(table)) { - heap->ops->free(buffer); - kfree(buffer); - return ERR_PTR(PTR_ERR(table)); - } - buffer->sg_table = table; - buffer->dev = dev; buffer->size = len; mutex_init(&buffer->lock); @@ -166,15 +238,44 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap, return buffer; } +/** + * Check for delayed IOMMU unmapping. Also unmap any outstanding + * mappings which would otherwise have been leaked. + */ +static void ion_iommu_delayed_unmap(struct ion_buffer *buffer) +{ + struct ion_iommu_map *iommu_map; + struct rb_node *node; + const struct rb_root *rb = &(buffer->iommu_maps); + unsigned long ref_count; + unsigned int delayed_unmap; + + mutex_lock(&buffer->lock); + + while ((node = rb_first(rb)) != 0) { + iommu_map = rb_entry(node, struct ion_iommu_map, node); + ref_count = atomic_read(&iommu_map->ref.refcount); + delayed_unmap = iommu_map->flags & ION_IOMMU_UNMAP_DELAYED; + + if ((delayed_unmap && ref_count > 1) || !delayed_unmap) { + pr_err("%s: Virtual memory address leak in domain %u, partition %u\n", + __func__, iommu_map->domain_info[DI_DOMAIN_NUM], + iommu_map->domain_info[DI_PARTITION_NUM]); + } + /* set ref count to 1 to force release */ + kref_init(&iommu_map->ref); + kref_put(&iommu_map->ref, ion_iommu_release); + } + + mutex_unlock(&buffer->lock); +} + static void ion_buffer_destroy(struct kref *kref) { struct ion_buffer *buffer = container_of(kref, struct ion_buffer, ref); struct ion_device *dev = buffer->dev; - if (WARN_ON(buffer->kmap_cnt > 0)) - buffer->heap->ops->unmap_kernel(buffer->heap, buffer); - - buffer->heap->ops->unmap_dma(buffer->heap, buffer); + ion_iommu_delayed_unmap(buffer); buffer->heap->ops->free(buffer); mutex_lock(&dev->lock); rb_erase(&buffer->node, &dev->buffers); @@ -209,26 +310,17 @@ static struct ion_handle *ion_handle_create(struct ion_client *client, return handle; } -static void ion_handle_kmap_put(struct ion_handle *); - +/* Client lock must be locked when calling */ static void ion_handle_destroy(struct kref *kref) { struct ion_handle *handle = container_of(kref, struct ion_handle, ref); - struct ion_client *client = handle->client; - struct ion_buffer *buffer = handle->buffer; - - mutex_lock(&client->lock); - - mutex_lock(&buffer->lock); - while (buffer->kmap_cnt) - ion_handle_kmap_put(handle); - mutex_unlock(&buffer->lock); - + /* XXX Can a handle be destroyed while it's map count is non-zero?: + if (handle->map_cnt) unmap + */ + WARN_ON(handle->kmap_cnt || handle->dmap_cnt || handle->usermap_cnt); + ion_buffer_put(handle->buffer); if (!RB_EMPTY_NODE(&handle->node)) - rb_erase(&handle->node, &client->handles); - mutex_unlock(&client->lock); - - ion_buffer_put(buffer); + rb_erase(&handle->node, &handle->client->handles); kfree(handle); } @@ -307,6 +399,12 @@ struct ion_handle *ion_alloc(struct ion_client *client, size_t len, struct ion_handle *handle; struct ion_device *dev = client->dev; struct ion_buffer *buffer = NULL; + unsigned long secure_allocation = flags & ION_SECURE; + const unsigned int MAX_DBG_STR_LEN = 64; + char dbg_str[MAX_DBG_STR_LEN]; + unsigned int dbg_str_idx = 0; + + dbg_str[0] = '\0'; /* * traverse the list of heaps available in this system in priority @@ -314,11 +412,6 @@ struct ion_handle *ion_alloc(struct ion_client *client, size_t len, * request of the caller allocate from it. Repeat until allocate has * succeeded or all heaps have been tried */ - if (WARN_ON(!len)) - return ERR_PTR(-EINVAL); - - len = PAGE_ALIGN(len); - mutex_lock(&dev->lock); for (n = rb_first(&dev->heaps); n != NULL; n = rb_next(n)) { struct ion_heap *heap = rb_entry(n, struct ion_heap, node); @@ -328,35 +421,59 @@ struct ion_handle *ion_alloc(struct ion_client *client, size_t len, /* if the caller didn't specify this heap type */ if (!((1 << heap->id) & flags)) continue; + /* Do not allow un-secure heap if secure is specified */ + if (secure_allocation && (heap->type != ION_HEAP_TYPE_CP)) + continue; buffer = ion_buffer_create(heap, dev, len, align, flags); if (!IS_ERR_OR_NULL(buffer)) break; + if (dbg_str_idx < MAX_DBG_STR_LEN) { + unsigned int len_left = MAX_DBG_STR_LEN-dbg_str_idx-1; + int ret_value = snprintf(&dbg_str[dbg_str_idx], + len_left, "%s ", heap->name); + if (ret_value >= len_left) { + /* overflow */ + dbg_str[MAX_DBG_STR_LEN-1] = '\0'; + dbg_str_idx = MAX_DBG_STR_LEN; + } else if (ret_value >= 0) { + dbg_str_idx += ret_value; + } else { + /* error */ + dbg_str[MAX_DBG_STR_LEN-1] = '\0'; + } + } } mutex_unlock(&dev->lock); - if (buffer == NULL) - return ERR_PTR(-ENODEV); - - if (IS_ERR(buffer)) + if (IS_ERR_OR_NULL(buffer)) { + pr_debug("ION is unable to allocate 0x%x bytes (alignment: " + "0x%x) from heap(s) %sfor client %s with heap " + "mask 0x%x\n", + len, align, dbg_str, client->name, client->heap_mask); return ERR_PTR(PTR_ERR(buffer)); + } handle = ion_handle_create(client, buffer); + if (IS_ERR_OR_NULL(handle)) + goto end; + /* * ion_buffer_create will create a buffer with a ref_cnt of 1, * and ion_handle_create will take a second reference, drop one here */ ion_buffer_put(buffer); - if (!IS_ERR(handle)) { - mutex_lock(&client->lock); - ion_handle_add(client, handle); - mutex_unlock(&client->lock); - } - + mutex_lock(&client->lock); + ion_handle_add(client, handle); + mutex_unlock(&client->lock); + return handle; +end: + ion_buffer_put(buffer); return handle; } +EXPORT_SYMBOL(ion_alloc); void ion_free(struct ion_client *client, struct ion_handle *handle) { @@ -366,13 +483,46 @@ void ion_free(struct ion_client *client, struct ion_handle *handle) mutex_lock(&client->lock); valid_handle = ion_handle_validate(client, handle); - mutex_unlock(&client->lock); - if (!valid_handle) { - WARN("%s: invalid handle passed to free.\n", __func__); + mutex_unlock(&client->lock); + WARN(1, "%s: invalid handle passed to free.\n", __func__); return; } ion_handle_put(handle); + mutex_unlock(&client->lock); +} +EXPORT_SYMBOL(ion_free); + +static void ion_client_get(struct ion_client *client); +static int ion_client_put(struct ion_client *client); + +static bool _ion_map(int *buffer_cnt, int *handle_cnt) +{ + bool map; + + BUG_ON(*handle_cnt != 0 && *buffer_cnt == 0); + + if (*buffer_cnt) + map = false; + else + map = true; + if (*handle_cnt == 0) + (*buffer_cnt)++; + (*handle_cnt)++; + return map; +} + +static bool _ion_unmap(int *buffer_cnt, int *handle_cnt) +{ + BUG_ON(*handle_cnt == 0); + (*handle_cnt)--; + if (*handle_cnt != 0) + return false; + BUG_ON(*buffer_cnt == 0); + (*buffer_cnt)--; + if (*buffer_cnt == 0) + return true; + return false; } int ion_phys(struct ion_client *client, struct ion_handle *handle, @@ -399,85 +549,284 @@ int ion_phys(struct ion_client *client, struct ion_handle *handle, ret = buffer->heap->ops->phys(buffer->heap, buffer, addr, len); return ret; } +EXPORT_SYMBOL(ion_phys); -static void *ion_buffer_kmap_get(struct ion_buffer *buffer) +void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle, + unsigned long flags) { + struct ion_buffer *buffer; void *vaddr; - if (buffer->kmap_cnt) { - buffer->kmap_cnt++; - return buffer->vaddr; + mutex_lock(&client->lock); + if (!ion_handle_validate(client, handle)) { + pr_err("%s: invalid handle passed to map_kernel.\n", + __func__); + mutex_unlock(&client->lock); + return ERR_PTR(-EINVAL); + } + + buffer = handle->buffer; + mutex_lock(&buffer->lock); + + if (!handle->buffer->heap->ops->map_kernel) { + pr_err("%s: map_kernel is not implemented by this heap.\n", + __func__); + mutex_unlock(&buffer->lock); + mutex_unlock(&client->lock); + return ERR_PTR(-ENODEV); + } + + if (ion_validate_buffer_flags(buffer, flags)) { + vaddr = ERR_PTR(-EEXIST); + goto out; + } + + if (_ion_map(&buffer->kmap_cnt, &handle->kmap_cnt)) { + vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer, + flags); + if (IS_ERR_OR_NULL(vaddr)) + _ion_unmap(&buffer->kmap_cnt, &handle->kmap_cnt); + buffer->vaddr = vaddr; + } else { + vaddr = buffer->vaddr; } - vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer); - if (IS_ERR_OR_NULL(vaddr)) - return vaddr; - buffer->vaddr = vaddr; - buffer->kmap_cnt++; + +out: + mutex_unlock(&buffer->lock); + mutex_unlock(&client->lock); return vaddr; } +EXPORT_SYMBOL(ion_map_kernel); -static void *ion_handle_kmap_get(struct ion_handle *handle) +static struct ion_iommu_map *__ion_iommu_map(struct ion_buffer *buffer, + int domain_num, int partition_num, unsigned long align, + unsigned long iova_length, unsigned long flags, + unsigned long *iova) { - struct ion_buffer *buffer = handle->buffer; - void *vaddr; + struct ion_iommu_map *data; + int ret; - if (handle->kmap_cnt) { - handle->kmap_cnt++; - return buffer->vaddr; - } - vaddr = ion_buffer_kmap_get(buffer); - if (IS_ERR_OR_NULL(vaddr)) - return vaddr; - handle->kmap_cnt++; - return vaddr; + data = kmalloc(sizeof(*data), GFP_ATOMIC); + + if (!data) + return ERR_PTR(-ENOMEM); + + data->buffer = buffer; + iommu_map_domain(data) = domain_num; + iommu_map_partition(data) = partition_num; + + ret = buffer->heap->ops->map_iommu(buffer, data, + domain_num, + partition_num, + align, + iova_length, + flags); + + if (ret) + goto out; + + kref_init(&data->ref); + *iova = data->iova_addr; + + ion_iommu_add(buffer, data); + + return data; + +out: + kfree(data); + return ERR_PTR(ret); } -static void ion_buffer_kmap_put(struct ion_buffer *buffer) +int ion_map_iommu(struct ion_client *client, struct ion_handle *handle, + int domain_num, int partition_num, unsigned long align, + unsigned long iova_length, unsigned long *iova, + unsigned long *buffer_size, + unsigned long flags, unsigned long iommu_flags) { - buffer->kmap_cnt--; - if (!buffer->kmap_cnt) { - buffer->heap->ops->unmap_kernel(buffer->heap, buffer); - buffer->vaddr = NULL; + struct ion_buffer *buffer; + struct ion_iommu_map *iommu_map; + int ret = 0; + + if (ION_IS_CACHED(flags)) { + pr_err("%s: Cannot map iommu as cached.\n", __func__); + return -EINVAL; + } + + mutex_lock(&client->lock); + if (!ion_handle_validate(client, handle)) { + pr_err("%s: invalid handle passed to map_kernel.\n", + __func__); + mutex_unlock(&client->lock); + return -EINVAL; + } + + buffer = handle->buffer; + mutex_lock(&buffer->lock); + + if (!handle->buffer->heap->ops->map_iommu) { + pr_err("%s: map_iommu is not implemented by this heap.\n", + __func__); + ret = -ENODEV; + goto out; + } + + /* + * If clients don't want a custom iova length, just use whatever + * the buffer size is + */ + if (!iova_length) + iova_length = buffer->size; + + if (buffer->size > iova_length) { + pr_debug("%s: iova length %lx is not at least buffer size" + " %x\n", __func__, iova_length, buffer->size); + ret = -EINVAL; + goto out; + } + + if (buffer->size & ~PAGE_MASK) { + pr_debug("%s: buffer size %x is not aligned to %lx", __func__, + buffer->size, PAGE_SIZE); + ret = -EINVAL; + goto out; + } + + if (iova_length & ~PAGE_MASK) { + pr_debug("%s: iova_length %lx is not aligned to %lx", __func__, + iova_length, PAGE_SIZE); + ret = -EINVAL; + goto out; + } + + iommu_map = ion_iommu_lookup(buffer, domain_num, partition_num); + _ion_map(&buffer->iommu_map_cnt, &handle->iommu_map_cnt); + if (!iommu_map) { + iommu_map = __ion_iommu_map(buffer, domain_num, partition_num, + align, iova_length, flags, iova); + if (IS_ERR_OR_NULL(iommu_map)) { + _ion_unmap(&buffer->iommu_map_cnt, + &handle->iommu_map_cnt); + } else { + iommu_map->flags = iommu_flags; + + if (iommu_map->flags & ION_IOMMU_UNMAP_DELAYED) + kref_get(&iommu_map->ref); + } + } else { + if (iommu_map->flags != iommu_flags) { + pr_err("%s: handle %p is already mapped with iommu flags %lx, trying to map with flags %lx\n", + __func__, handle, + iommu_map->flags, iommu_flags); + _ion_unmap(&buffer->iommu_map_cnt, + &handle->iommu_map_cnt); + ret = -EINVAL; + } else if (iommu_map->mapped_size != iova_length) { + pr_err("%s: handle %p is already mapped with length" + " %x, trying to map with length %lx\n", + __func__, handle, iommu_map->mapped_size, + iova_length); + _ion_unmap(&buffer->iommu_map_cnt, + &handle->iommu_map_cnt); + ret = -EINVAL; + } else { + kref_get(&iommu_map->ref); + *iova = iommu_map->iova_addr; + } } + *buffer_size = buffer->size; +out: + mutex_unlock(&buffer->lock); + mutex_unlock(&client->lock); + return ret; } +EXPORT_SYMBOL(ion_map_iommu); -static void ion_handle_kmap_put(struct ion_handle *handle) +static void ion_iommu_release(struct kref *kref) { - struct ion_buffer *buffer = handle->buffer; + struct ion_iommu_map *map = container_of(kref, struct ion_iommu_map, + ref); + struct ion_buffer *buffer = map->buffer; - handle->kmap_cnt--; - if (!handle->kmap_cnt) - ion_buffer_kmap_put(buffer); + rb_erase(&map->node, &buffer->iommu_maps); + buffer->heap->ops->unmap_iommu(map); + kfree(map); } -void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle) +void ion_unmap_iommu(struct ion_client *client, struct ion_handle *handle, + int domain_num, int partition_num) { + struct ion_iommu_map *iommu_map; struct ion_buffer *buffer; - void *vaddr; + + mutex_lock(&client->lock); + buffer = handle->buffer; + + mutex_lock(&buffer->lock); + + iommu_map = ion_iommu_lookup(buffer, domain_num, partition_num); + + if (!iommu_map) { + WARN(1, "%s: (%d,%d) was never mapped for %p\n", __func__, + domain_num, partition_num, buffer); + goto out; + } + + _ion_unmap(&buffer->iommu_map_cnt, &handle->iommu_map_cnt); + kref_put(&iommu_map->ref, ion_iommu_release); + +out: + mutex_unlock(&buffer->lock); + + mutex_unlock(&client->lock); + +} +EXPORT_SYMBOL(ion_unmap_iommu); + +struct scatterlist *ion_map_dma(struct ion_client *client, + struct ion_handle *handle, + unsigned long flags) +{ + struct ion_buffer *buffer; + struct scatterlist *sglist; mutex_lock(&client->lock); if (!ion_handle_validate(client, handle)) { - pr_err("%s: invalid handle passed to map_kernel.\n", + pr_err("%s: invalid handle passed to map_dma.\n", __func__); mutex_unlock(&client->lock); return ERR_PTR(-EINVAL); } - buffer = handle->buffer; + mutex_lock(&buffer->lock); - if (!handle->buffer->heap->ops->map_kernel) { + if (!handle->buffer->heap->ops->map_dma) { pr_err("%s: map_kernel is not implemented by this heap.\n", __func__); + mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); return ERR_PTR(-ENODEV); } - mutex_lock(&buffer->lock); - vaddr = ion_handle_kmap_get(handle); + if (ion_validate_buffer_flags(buffer, flags)) { + sglist = ERR_PTR(-EEXIST); + goto out; + } + + if (_ion_map(&buffer->dmap_cnt, &handle->dmap_cnt)) { + sglist = buffer->heap->ops->map_dma(buffer->heap, buffer); + if (IS_ERR_OR_NULL(sglist)) + _ion_unmap(&buffer->dmap_cnt, &handle->dmap_cnt); + buffer->sglist = sglist; + } else { + sglist = buffer->sglist; + } + +out: mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); - return vaddr; + return sglist; } +EXPORT_SYMBOL(ion_map_dma); void ion_unmap_kernel(struct ion_client *client, struct ion_handle *handle) { @@ -486,63 +835,264 @@ void ion_unmap_kernel(struct ion_client *client, struct ion_handle *handle) mutex_lock(&client->lock); buffer = handle->buffer; mutex_lock(&buffer->lock); - ion_handle_kmap_put(handle); + if (_ion_unmap(&buffer->kmap_cnt, &handle->kmap_cnt)) { + buffer->heap->ops->unmap_kernel(buffer->heap, buffer); + buffer->vaddr = NULL; + } mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); } +EXPORT_SYMBOL(ion_unmap_kernel); -static int ion_debug_client_show(struct seq_file *s, void *unused) +void ion_unmap_dma(struct ion_client *client, struct ion_handle *handle) { - struct ion_client *client = s->private; - struct rb_node *n; - size_t sizes[ION_NUM_HEAPS] = {0}; - const char *names[ION_NUM_HEAPS] = {0}; - int i; + struct ion_buffer *buffer; mutex_lock(&client->lock); - for (n = rb_first(&client->handles); n; n = rb_next(n)) { - struct ion_handle *handle = rb_entry(n, struct ion_handle, - node); - enum ion_heap_type type = handle->buffer->heap->type; - - if (!names[type]) - names[type] = handle->buffer->heap->name; - sizes[type] += handle->buffer->size; + buffer = handle->buffer; + mutex_lock(&buffer->lock); + if (_ion_unmap(&buffer->dmap_cnt, &handle->dmap_cnt)) { + buffer->heap->ops->unmap_dma(buffer->heap, buffer); + buffer->sglist = NULL; } + mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); - - seq_printf(s, "%16.16s: %16.16s\n", "heap_name", "size_in_bytes"); - for (i = 0; i < ION_NUM_HEAPS; i++) { - if (!names[i]) - continue; - seq_printf(s, "%16.16s: %16u\n", names[i], sizes[i]); - } - return 0; } +EXPORT_SYMBOL(ion_unmap_dma); -static int ion_debug_client_open(struct inode *inode, struct file *file) +struct ion_buffer *ion_share(struct ion_client *client, + struct ion_handle *handle) { - return single_open(file, ion_debug_client_show, inode->i_private); -} + bool valid_handle; -static const struct file_operations debug_client_fops = { - .open = ion_debug_client_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; + mutex_lock(&client->lock); + valid_handle = ion_handle_validate(client, handle); + mutex_unlock(&client->lock); + if (!valid_handle) { + WARN("%s: invalid handle passed to share.\n", __func__); + return ERR_PTR(-EINVAL); + } -struct ion_client *ion_client_create(struct ion_device *dev, - unsigned int heap_mask, - const char *name) + /* do not take an extra reference here, the burden is on the caller + * to make sure the buffer doesn't go away while it's passing it + * to another client -- ion_free should not be called on this handle + * until the buffer has been imported into the other client + */ + return handle->buffer; +} +EXPORT_SYMBOL(ion_share); + +struct ion_handle *ion_import(struct ion_client *client, + struct ion_buffer *buffer) { - struct ion_client *client; + struct ion_handle *handle = NULL; + + mutex_lock(&client->lock); + /* if a handle exists for this buffer just take a reference to it */ + handle = ion_handle_lookup(client, buffer); + if (!IS_ERR_OR_NULL(handle)) { + ion_handle_get(handle); + goto end; + } + handle = ion_handle_create(client, buffer); + if (IS_ERR_OR_NULL(handle)) + goto end; + ion_handle_add(client, handle); +end: + mutex_unlock(&client->lock); + return handle; +} +EXPORT_SYMBOL(ion_import); + +static int check_vaddr_bounds(unsigned long start, unsigned long end) +{ + struct mm_struct *mm = current->active_mm; + struct vm_area_struct *vma; + int ret = 1; + + if (end < start) + goto out; + + down_read(&mm->mmap_sem); + vma = find_vma(mm, start); + if (vma && vma->vm_start < end) { + if (start < vma->vm_start) + goto out_up; + if (end > vma->vm_end) + goto out_up; + ret = 0; + } + +out_up: + up_read(&mm->mmap_sem); +out: + return ret; +} + +int ion_do_cache_op(struct ion_client *client, struct ion_handle *handle, + void *uaddr, unsigned long offset, unsigned long len, + unsigned int cmd) +{ + struct ion_buffer *buffer; + int ret = -EINVAL; + + mutex_lock(&client->lock); + if (!ion_handle_validate(client, handle)) { + pr_err("%s: invalid handle passed to do_cache_op.\n", + __func__); + mutex_unlock(&client->lock); + return -EINVAL; + } + buffer = handle->buffer; + mutex_lock(&buffer->lock); + + if (!ION_IS_CACHED(buffer->flags)) { + ret = 0; + goto out; + } + + if (!handle->buffer->heap->ops->cache_op) { + pr_err("%s: cache_op is not implemented by this heap.\n", + __func__); + ret = -ENODEV; + goto out; + } + + + ret = buffer->heap->ops->cache_op(buffer->heap, buffer, uaddr, + offset, len, cmd); + +out: + mutex_unlock(&buffer->lock); + mutex_unlock(&client->lock); + return ret; + +} + +static const struct file_operations ion_share_fops; + +struct ion_handle *ion_import_fd(struct ion_client *client, int fd) +{ + struct file *file = fget(fd); + struct ion_handle *handle; + + if (!file) { + pr_err("%s: imported fd not found in file table.\n", __func__); + return ERR_PTR(-EINVAL); + } + if (file->f_op != &ion_share_fops) { + pr_err("%s: imported file %s is not a shared ion" + " file.", __func__, file->f_dentry->d_name.name); + handle = ERR_PTR(-EINVAL); + goto end; + } + handle = ion_import(client, file->private_data); +end: + fput(file); + return handle; +} +EXPORT_SYMBOL(ion_import_fd); + +static int ion_debug_client_show(struct seq_file *s, void *unused) +{ + struct ion_client *client = s->private; + struct rb_node *n; + struct rb_node *n2; + + seq_printf(s, "%16.16s: %16.16s : %16.16s : %12.12s : %12.12s : %s\n", + "heap_name", "size_in_bytes", "handle refcount", + "buffer", "physical", "[domain,partition] - virt"); + + mutex_lock(&client->lock); + for (n = rb_first(&client->handles); n; n = rb_next(n)) { + struct ion_handle *handle = rb_entry(n, struct ion_handle, + node); + enum ion_heap_type type = handle->buffer->heap->type; + + seq_printf(s, "%16.16s: %16x : %16d : %12p", + handle->buffer->heap->name, + handle->buffer->size, + atomic_read(&handle->ref.refcount), + handle->buffer); + + if (type == ION_HEAP_TYPE_SYSTEM_CONTIG || + type == ION_HEAP_TYPE_CARVEOUT || + type == ION_HEAP_TYPE_CP) + seq_printf(s, " : %12lx", handle->buffer->priv_phys); + else + seq_printf(s, " : %12s", "N/A"); + + for (n2 = rb_first(&handle->buffer->iommu_maps); n2; + n2 = rb_next(n2)) { + struct ion_iommu_map *imap = + rb_entry(n2, struct ion_iommu_map, node); + seq_printf(s, " : [%d,%d] - %8lx", + imap->domain_info[DI_DOMAIN_NUM], + imap->domain_info[DI_PARTITION_NUM], + imap->iova_addr); + } + seq_printf(s, "\n"); + } + + seq_printf(s, "%16.16s %d\n", "client refcount:", + atomic_read(&client->ref.refcount)); + mutex_unlock(&client->lock); + + return 0; +} + +static int ion_debug_client_open(struct inode *inode, struct file *file) +{ + return single_open(file, ion_debug_client_show, inode->i_private); +} + +static const struct file_operations debug_client_fops = { + .open = ion_debug_client_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct ion_client *ion_client_lookup(struct ion_device *dev, + struct task_struct *task) +{ + struct rb_node *n = dev->user_clients.rb_node; + struct ion_client *client; + + mutex_lock(&dev->lock); + while (n) { + client = rb_entry(n, struct ion_client, node); + if (task == client->task) { + ion_client_get(client); + mutex_unlock(&dev->lock); + return client; + } else if (task < client->task) { + n = n->rb_left; + } else if (task > client->task) { + n = n->rb_right; + } + } + mutex_unlock(&dev->lock); + return NULL; +} + +struct ion_client *ion_client_create(struct ion_device *dev, + unsigned int heap_mask, + const char *name) +{ + struct ion_client *client; struct task_struct *task; struct rb_node **p; struct rb_node *parent = NULL; struct ion_client *entry; - char debug_name[64]; pid_t pid; + unsigned int name_len; + + if (!name) { + pr_err("%s: Name cannot be null\n", __func__); + return ERR_PTR(-EINVAL); + } + name_len = strnlen(name, 64); get_task_struct(current->group_leader); task_lock(current->group_leader); @@ -557,37 +1107,71 @@ struct ion_client *ion_client_create(struct ion_device *dev, } task_unlock(current->group_leader); + /* if this isn't a kernel thread, see if a client already + exists */ + if (task) { + client = ion_client_lookup(dev, task); + if (!IS_ERR_OR_NULL(client)) { + put_task_struct(current->group_leader); + return client; + } + } + client = kzalloc(sizeof(struct ion_client), GFP_KERNEL); if (!client) { - if (task) - put_task_struct(current->group_leader); + put_task_struct(current->group_leader); return ERR_PTR(-ENOMEM); } client->dev = dev; client->handles = RB_ROOT; mutex_init(&client->lock); - client->name = name; + + client->name = kzalloc(name_len+1, GFP_KERNEL); + if (!client->name) { + put_task_struct(current->group_leader); + kfree(client); + return ERR_PTR(-ENOMEM); + } else { + strlcpy(client->name, name, name_len+1); + } + client->heap_mask = heap_mask; client->task = task; client->pid = pid; + kref_init(&client->ref); mutex_lock(&dev->lock); - p = &dev->clients.rb_node; - while (*p) { - parent = *p; - entry = rb_entry(parent, struct ion_client, node); - - if (client < entry) - p = &(*p)->rb_left; - else if (client > entry) - p = &(*p)->rb_right; + if (task) { + p = &dev->user_clients.rb_node; + while (*p) { + parent = *p; + entry = rb_entry(parent, struct ion_client, node); + + if (task < entry->task) + p = &(*p)->rb_left; + else if (task > entry->task) + p = &(*p)->rb_right; + } + rb_link_node(&client->node, parent, p); + rb_insert_color(&client->node, &dev->user_clients); + } else { + p = &dev->kernel_clients.rb_node; + while (*p) { + parent = *p; + entry = rb_entry(parent, struct ion_client, node); + + if (client < entry) + p = &(*p)->rb_left; + else if (client > entry) + p = &(*p)->rb_right; + } + rb_link_node(&client->node, parent, p); + rb_insert_color(&client->node, &dev->kernel_clients); } - rb_link_node(&client->node, parent, p); - rb_insert_color(&client->node, &dev->clients); - snprintf(debug_name, 64, "%u", client->pid); - client->debug_root = debugfs_create_file(debug_name, 0664, + + client->debug_root = debugfs_create_file(name, 0664, dev->debug_root, client, &debug_client_fops); mutex_unlock(&dev->lock); @@ -595,8 +1179,9 @@ struct ion_client *ion_client_create(struct ion_device *dev, return client; } -void ion_client_destroy(struct ion_client *client) +static void _ion_client_destroy(struct kref *kref) { + struct ion_client *client = container_of(kref, struct ion_client, ref); struct ion_device *dev = client->dev; struct rb_node *n; @@ -607,201 +1192,279 @@ void ion_client_destroy(struct ion_client *client) ion_handle_destroy(&handle->ref); } mutex_lock(&dev->lock); - if (client->task) + if (client->task) { + rb_erase(&client->node, &dev->user_clients); put_task_struct(client->task); - rb_erase(&client->node, &dev->clients); + } else { + rb_erase(&client->node, &dev->kernel_clients); + } debugfs_remove_recursive(client->debug_root); mutex_unlock(&dev->lock); + kfree(client->name); kfree(client); } -struct sg_table *ion_sg_table(struct ion_client *client, - struct ion_handle *handle) +static void ion_client_get(struct ion_client *client) +{ + kref_get(&client->ref); +} + +static int ion_client_put(struct ion_client *client) +{ + return kref_put(&client->ref, _ion_client_destroy); +} + +void ion_client_destroy(struct ion_client *client) +{ + if (client) + ion_client_put(client); +} +EXPORT_SYMBOL(ion_client_destroy); + +int ion_handle_get_flags(struct ion_client *client, struct ion_handle *handle, + unsigned long *flags) { struct ion_buffer *buffer; - struct sg_table *table; mutex_lock(&client->lock); if (!ion_handle_validate(client, handle)) { - pr_err("%s: invalid handle passed to map_dma.\n", - __func__); + pr_err("%s: invalid handle passed to %s.\n", + __func__, __func__); mutex_unlock(&client->lock); - return ERR_PTR(-EINVAL); + return -EINVAL; } buffer = handle->buffer; - table = buffer->sg_table; + mutex_lock(&buffer->lock); + *flags = buffer->flags; + mutex_unlock(&buffer->lock); mutex_unlock(&client->lock); - return table; -} - -static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment, - enum dma_data_direction direction) -{ - struct dma_buf *dmabuf = attachment->dmabuf; - struct ion_buffer *buffer = dmabuf->priv; - - return buffer->sg_table; -} -static void ion_unmap_dma_buf(struct dma_buf_attachment *attachment, - struct sg_table *table, - enum dma_data_direction direction) -{ + return 0; } +EXPORT_SYMBOL(ion_handle_get_flags); -static int ion_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) +int ion_handle_get_size(struct ion_client *client, struct ion_handle *handle, + unsigned long *size) { - struct ion_buffer *buffer = dmabuf->priv; - int ret; + struct ion_buffer *buffer; - if (!buffer->heap->ops->map_user) { - pr_err("%s: this heap does not define a method for mapping " - "to userspace\n", __func__); + mutex_lock(&client->lock); + if (!ion_handle_validate(client, handle)) { + pr_err("%s: invalid handle passed to %s.\n", + __func__, __func__); + mutex_unlock(&client->lock); return -EINVAL; } - + buffer = handle->buffer; mutex_lock(&buffer->lock); - /* now map it to userspace */ - ret = buffer->heap->ops->map_user(buffer->heap, buffer, vma); + *size = buffer->size; mutex_unlock(&buffer->lock); + mutex_unlock(&client->lock); - if (ret) - pr_err("%s: failure mapping buffer to userspace\n", - __func__); - - return ret; + return 0; } +EXPORT_SYMBOL(ion_handle_get_size); -static void ion_dma_buf_release(struct dma_buf *dmabuf) +static int ion_share_release(struct inode *inode, struct file* file) { - struct ion_buffer *buffer = dmabuf->priv; - ion_buffer_put(buffer); -} + struct ion_buffer *buffer = file->private_data; -static void *ion_dma_buf_kmap(struct dma_buf *dmabuf, unsigned long offset) -{ - struct ion_buffer *buffer = dmabuf->priv; - return buffer->vaddr + offset; + pr_debug("%s: %d\n", __func__, __LINE__); + /* drop the reference to the buffer -- this prevents the + buffer from going away because the client holding it exited + while it was being passed */ + ion_buffer_put(buffer); + return 0; } -static void ion_dma_buf_kunmap(struct dma_buf *dmabuf, unsigned long offset, - void *ptr) +static void ion_vma_open(struct vm_area_struct *vma) { - return; -} -static int ion_dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start, - size_t len, - enum dma_data_direction direction) -{ - struct ion_buffer *buffer = dmabuf->priv; - void *vaddr; + struct ion_buffer *buffer = vma->vm_file->private_data; + struct ion_handle *handle = vma->vm_private_data; + struct ion_client *client; - if (!buffer->heap->ops->map_kernel) { - pr_err("%s: map kernel is not implemented by this heap.\n", - __func__); - return -ENODEV; + pr_debug("%s: %d\n", __func__, __LINE__); + /* check that the client still exists and take a reference so + it can't go away until this vma is closed */ + client = ion_client_lookup(buffer->dev, current->group_leader); + if (IS_ERR_OR_NULL(client)) { + vma->vm_private_data = NULL; + return; } - + ion_handle_get(handle); mutex_lock(&buffer->lock); - vaddr = ion_buffer_kmap_get(buffer); + buffer->umap_cnt++; mutex_unlock(&buffer->lock); - if (IS_ERR(vaddr)) - return PTR_ERR(vaddr); - if (!vaddr) - return -ENOMEM; - return 0; + pr_debug("%s: %d client_cnt %d handle_cnt %d alloc_cnt %d\n", + __func__, __LINE__, + atomic_read(&client->ref.refcount), + atomic_read(&handle->ref.refcount), + atomic_read(&buffer->ref.refcount)); } -static void ion_dma_buf_end_cpu_access(struct dma_buf *dmabuf, size_t start, - size_t len, - enum dma_data_direction direction) +static void ion_vma_close(struct vm_area_struct *vma) { - struct ion_buffer *buffer = dmabuf->priv; + struct ion_handle *handle = vma->vm_private_data; + struct ion_buffer *buffer = vma->vm_file->private_data; + struct ion_client *client; + pr_debug("%s: %d\n", __func__, __LINE__); + /* this indicates the client is gone, nothing to do here */ + if (!handle) + return; + client = handle->client; mutex_lock(&buffer->lock); - ion_buffer_kmap_put(buffer); + buffer->umap_cnt--; mutex_unlock(&buffer->lock); + + if (buffer->heap->ops->unmap_user) + buffer->heap->ops->unmap_user(buffer->heap, buffer); + + + pr_debug("%s: %d client_cnt %d handle_cnt %d alloc_cnt %d\n", + __func__, __LINE__, + atomic_read(&client->ref.refcount), + atomic_read(&handle->ref.refcount), + atomic_read(&buffer->ref.refcount)); + mutex_lock(&client->lock); + ion_handle_put(handle); + mutex_unlock(&client->lock); + ion_client_put(client); + pr_debug("%s: %d client_cnt %d handle_cnt %d alloc_cnt %d\n", + __func__, __LINE__, + atomic_read(&client->ref.refcount), + atomic_read(&handle->ref.refcount), + atomic_read(&buffer->ref.refcount)); } -struct dma_buf_ops dma_buf_ops = { - .map_dma_buf = ion_map_dma_buf, - .unmap_dma_buf = ion_unmap_dma_buf, - .mmap = ion_mmap, - .release = ion_dma_buf_release, - .begin_cpu_access = ion_dma_buf_begin_cpu_access, - .end_cpu_access = ion_dma_buf_end_cpu_access, - .kmap_atomic = ion_dma_buf_kmap, - .kunmap_atomic = ion_dma_buf_kunmap, - .kmap = ion_dma_buf_kmap, - .kunmap = ion_dma_buf_kunmap, +static struct vm_operations_struct ion_vm_ops = { + .open = ion_vma_open, + .close = ion_vma_close, }; -int ion_share_dma_buf(struct ion_client *client, struct ion_handle *handle) +static int ion_share_mmap(struct file *file, struct vm_area_struct *vma) { - struct ion_buffer *buffer; - struct dma_buf *dmabuf; - bool valid_handle; - int fd; + struct ion_buffer *buffer = file->private_data; + unsigned long size = vma->vm_end - vma->vm_start; + struct ion_client *client; + struct ion_handle *handle; + int ret; + unsigned long flags = file->f_flags & O_DSYNC ? + ION_SET_CACHE(UNCACHED) : + ION_SET_CACHE(CACHED); - mutex_lock(&client->lock); - valid_handle = ion_handle_validate(client, handle); - mutex_unlock(&client->lock); - if (!valid_handle) { - WARN("%s: invalid handle passed to share.\n", __func__); + + pr_debug("%s: %d\n", __func__, __LINE__); + /* make sure the client still exists, it's possible for the client to + have gone away but the map/share fd still to be around, take + a reference to it so it can't go away while this mapping exists */ + client = ion_client_lookup(buffer->dev, current->group_leader); + if (IS_ERR_OR_NULL(client)) { + pr_err("%s: trying to mmap an ion handle in a process with no " + "ion client\n", __func__); return -EINVAL; } - buffer = handle->buffer; - ion_buffer_get(buffer); - dmabuf = dma_buf_export(buffer, &dma_buf_ops, buffer->size, O_RDWR); - if (IS_ERR(dmabuf)) { - ion_buffer_put(buffer); - return PTR_ERR(dmabuf); + if ((size > buffer->size) || (size + (vma->vm_pgoff << PAGE_SHIFT) > + buffer->size)) { + pr_err("%s: trying to map larger area than handle has available" + "\n", __func__); + ret = -EINVAL; + goto err; } - fd = dma_buf_fd(dmabuf, O_CLOEXEC); - if (fd < 0) { - dma_buf_put(dmabuf); - ion_buffer_put(buffer); + + /* find the handle and take a reference to it */ + handle = ion_import(client, buffer); + if (IS_ERR_OR_NULL(handle)) { + ret = -EINVAL; + goto err; } - return fd; -} -struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd) -{ - struct dma_buf *dmabuf; - struct ion_buffer *buffer; - struct ion_handle *handle; + if (!handle->buffer->heap->ops->map_user) { + pr_err("%s: this heap does not define a method for mapping " + "to userspace\n", __func__); + ret = -EINVAL; + goto err1; + } - dmabuf = dma_buf_get(fd); - if (IS_ERR_OR_NULL(dmabuf)) - return ERR_PTR(PTR_ERR(dmabuf)); - /* if this memory came from ion */ + mutex_lock(&buffer->lock); + + if (ion_validate_buffer_flags(buffer, flags)) { + ret = -EEXIST; + mutex_unlock(&buffer->lock); + goto err1; + } + + /* now map it to userspace */ + ret = buffer->heap->ops->map_user(buffer->heap, buffer, vma, + flags); - if (dmabuf->ops != &dma_buf_ops) { - pr_err("%s: can not import dmabuf from another exporter\n", + buffer->umap_cnt++; + if (ret) { + pr_err("%s: failure mapping buffer to userspace\n", __func__); - dma_buf_put(dmabuf); - return ERR_PTR(-EINVAL); + goto err2; } - buffer = dmabuf->priv; + mutex_unlock(&buffer->lock); + + vma->vm_ops = &ion_vm_ops; + /* move the handle into the vm_private_data so we can access it from + vma_open/close */ + vma->vm_private_data = handle; + pr_debug("%s: %d client_cnt %d handle_cnt %d alloc_cnt %d\n", + __func__, __LINE__, + atomic_read(&client->ref.refcount), + atomic_read(&handle->ref.refcount), + atomic_read(&buffer->ref.refcount)); + return 0; +err2: + buffer->umap_cnt--; + mutex_unlock(&buffer->lock); + /* drop the reference to the handle */ +err1: mutex_lock(&client->lock); - /* if a handle exists for this buffer just take a reference to it */ - handle = ion_handle_lookup(client, buffer); - if (!IS_ERR_OR_NULL(handle)) { - ion_handle_get(handle); - goto end; - } - handle = ion_handle_create(client, buffer); - if (IS_ERR_OR_NULL(handle)) - goto end; - ion_handle_add(client, handle); -end: + ion_handle_put(handle); mutex_unlock(&client->lock); - dma_buf_put(dmabuf); - return handle; +err: + /* drop the reference to the client */ + ion_client_put(client); + return ret; +} + +static const struct file_operations ion_share_fops = { + .owner = THIS_MODULE, + .release = ion_share_release, + .mmap = ion_share_mmap, +}; + +static int ion_ioctl_share(struct file *parent, struct ion_client *client, + struct ion_handle *handle) +{ + int fd = get_unused_fd(); + struct file *file; + + if (fd < 0) + return -ENFILE; + + file = anon_inode_getfile("ion_share_fd", &ion_share_fops, + handle->buffer, O_RDWR); + if (IS_ERR_OR_NULL(file)) + goto err; + + if (parent->f_flags & O_DSYNC) + file->f_flags |= O_DSYNC; + + ion_buffer_get(handle->buffer); + fd_install(fd, file); + + return fd; + +err: + put_unused_fd(fd); + return -ENFILE; } static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) @@ -818,13 +1481,11 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) data.handle = ion_alloc(client, data.len, data.align, data.flags); - if (IS_ERR(data.handle)) - return PTR_ERR(data.handle); + if (IS_ERR_OR_NULL(data.handle)) + return -ENOMEM; - if (copy_to_user((void __user *)arg, &data, sizeof(data))) { - ion_free(client, data.handle); + if (copy_to_user((void __user *)arg, &data, sizeof(data))) return -EFAULT; - } break; } case ION_IOC_FREE: @@ -843,29 +1504,46 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ion_free(client, data.handle); break; } + case ION_IOC_MAP: case ION_IOC_SHARE: { struct ion_fd_data data; if (copy_from_user(&data, (void __user *)arg, sizeof(data))) return -EFAULT; - data.fd = ion_share_dma_buf(client, data.handle); + mutex_lock(&client->lock); + if (!ion_handle_validate(client, data.handle)) { + pr_err("%s: invalid handle passed to share ioctl.\n", + __func__); + mutex_unlock(&client->lock); + return -EINVAL; + } + data.fd = ion_ioctl_share(filp, client, data.handle); + mutex_unlock(&client->lock); if (copy_to_user((void __user *)arg, &data, sizeof(data))) return -EFAULT; + if (data.fd < 0) + return data.fd; break; } case ION_IOC_IMPORT: { struct ion_fd_data data; + int ret = 0; if (copy_from_user(&data, (void __user *)arg, sizeof(struct ion_fd_data))) return -EFAULT; - data.handle = ion_import_dma_buf(client, data.fd); - if (IS_ERR(data.handle)) + + data.handle = ion_import_fd(client, data.fd); + if (IS_ERR(data.handle)) { + ret = PTR_ERR(data.handle); data.handle = NULL; + } if (copy_to_user((void __user *)arg, &data, sizeof(struct ion_fd_data))) return -EFAULT; + if (ret < 0) + return ret; break; } case ION_IOC_CUSTOM: @@ -880,6 +1558,66 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return -EFAULT; return dev->custom_ioctl(client, data.cmd, data.arg); } + case ION_IOC_CLEAN_CACHES: + case ION_IOC_INV_CACHES: + case ION_IOC_CLEAN_INV_CACHES: + { + struct ion_flush_data data; + unsigned long start, end; + struct ion_handle *handle = NULL; + int ret; + + if (copy_from_user(&data, (void __user *)arg, + sizeof(struct ion_flush_data))) + return -EFAULT; + + start = (unsigned long) data.vaddr; + end = (unsigned long) data.vaddr + data.length; + + if (check_vaddr_bounds(start, end)) { + pr_err("%s: virtual address %p is out of bounds\n", + __func__, data.vaddr); + return -EINVAL; + } + + if (!data.handle) { + handle = ion_import_fd(client, data.fd); + if (IS_ERR_OR_NULL(handle)) { + pr_info("%s: Could not import handle: %d\n", + __func__, (int)handle); + return -EINVAL; + } + } + + ret = ion_do_cache_op(client, + data.handle ? data.handle : handle, + data.vaddr, data.offset, data.length, + cmd); + + if (!data.handle) + ion_free(client, handle); + + if (ret < 0) + return ret; + break; + + } + case ION_IOC_GET_FLAGS: + { + struct ion_flag_data data; + int ret; + if (copy_from_user(&data, (void __user *)arg, + sizeof(struct ion_flag_data))) + return -EFAULT; + + ret = ion_handle_get_flags(client, data.handle, &data.flags); + if (ret < 0) + return ret; + if (copy_to_user((void __user *)arg, &data, + sizeof(struct ion_flag_data))) + return -EFAULT; + break; + } default: return -ENOTTY; } @@ -891,7 +1629,7 @@ static int ion_release(struct inode *inode, struct file *file) struct ion_client *client = file->private_data; pr_debug("%s: %d\n", __func__, __LINE__); - ion_client_destroy(client); + ion_client_put(client); return 0; } @@ -900,9 +1638,11 @@ static int ion_open(struct inode *inode, struct file *file) struct miscdevice *miscdev = file->private_data; struct ion_device *dev = container_of(miscdev, struct ion_device, dev); struct ion_client *client; + char debug_name[64]; pr_debug("%s: %d\n", __func__, __LINE__); - client = ion_client_create(dev, -1, "user"); + snprintf(debug_name, 64, "%u", task_pid_nr(current->group_leader)); + client = ion_client_create(dev, -1, debug_name); if (IS_ERR_OR_NULL(client)) return PTR_ERR(client); file->private_data = client; @@ -918,7 +1658,7 @@ static const struct file_operations ion_fops = { }; static size_t ion_debug_heap_total(struct ion_client *client, - enum ion_heap_type type) + enum ion_heap_ids id) { size_t size = 0; struct rb_node *n; @@ -928,38 +1668,190 @@ static size_t ion_debug_heap_total(struct ion_client *client, struct ion_handle *handle = rb_entry(n, struct ion_handle, node); - if (handle->buffer->heap->type == type) + if (handle->buffer->heap->id == id) size += handle->buffer->size; } mutex_unlock(&client->lock); return size; } +/** + * Searches through a clients handles to find if the buffer is owned + * by this client. Used for debug output. + * @param client pointer to candidate owner of buffer + * @param buf pointer to buffer that we are trying to find the owner of + * @return 1 if found, 0 otherwise + */ +static int ion_debug_find_buffer_owner(const struct ion_client *client, + const struct ion_buffer *buf) +{ + struct rb_node *n; + + for (n = rb_first(&client->handles); n; n = rb_next(n)) { + const struct ion_handle *handle = rb_entry(n, + const struct ion_handle, + node); + if (handle->buffer == buf) + return 1; + } + return 0; +} + +/** + * Adds mem_map_data pointer to the tree of mem_map + * Used for debug output. + * @param mem_map The mem_map tree + * @param data The new data to add to the tree + */ +static void ion_debug_mem_map_add(struct rb_root *mem_map, + struct mem_map_data *data) +{ + struct rb_node **p = &mem_map->rb_node; + struct rb_node *parent = NULL; + struct mem_map_data *entry; + + while (*p) { + parent = *p; + entry = rb_entry(parent, struct mem_map_data, node); + + if (data->addr < entry->addr) { + p = &(*p)->rb_left; + } else if (data->addr > entry->addr) { + p = &(*p)->rb_right; + } else { + pr_err("%s: mem_map_data already found.", __func__); + BUG(); + } + } + rb_link_node(&data->node, parent, p); + rb_insert_color(&data->node, mem_map); +} + +/** + * Search for an owner of a buffer by iterating over all ION clients. + * @param dev ion device containing pointers to all the clients. + * @param buffer pointer to buffer we are trying to find the owner of. + * @return name of owner. + */ +const char *ion_debug_locate_owner(const struct ion_device *dev, + const struct ion_buffer *buffer) +{ + struct rb_node *j; + const char *client_name = NULL; + + for (j = rb_first(&dev->user_clients); j && !client_name; + j = rb_next(j)) { + struct ion_client *client = rb_entry(j, struct ion_client, + node); + if (ion_debug_find_buffer_owner(client, buffer)) + client_name = client->name; + } + for (j = rb_first(&dev->kernel_clients); j && !client_name; + j = rb_next(j)) { + struct ion_client *client = rb_entry(j, struct ion_client, + node); + if (ion_debug_find_buffer_owner(client, buffer)) + client_name = client->name; + } + return client_name; +} + +/** + * Create a mem_map of the heap. + * @param s seq_file to log error message to. + * @param heap The heap to create mem_map for. + * @param mem_map The mem map to be created. + */ +void ion_debug_mem_map_create(struct seq_file *s, struct ion_heap *heap, + struct rb_root *mem_map) +{ + struct ion_device *dev = heap->dev; + struct rb_node *n; + + for (n = rb_first(&dev->buffers); n; n = rb_next(n)) { + struct ion_buffer *buffer = + rb_entry(n, struct ion_buffer, node); + if (buffer->heap->id == heap->id) { + struct mem_map_data *data = + kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + seq_printf(s, "ERROR: out of memory. " + "Part of memory map will not be logged\n"); + break; + } + data->addr = buffer->priv_phys; + data->addr_end = buffer->priv_phys + buffer->size-1; + data->size = buffer->size; + data->client_name = ion_debug_locate_owner(dev, buffer); + ion_debug_mem_map_add(mem_map, data); + } + } +} + +/** + * Free the memory allocated by ion_debug_mem_map_create + * @param mem_map The mem map to free. + */ +static void ion_debug_mem_map_destroy(struct rb_root *mem_map) +{ + if (mem_map) { + struct rb_node *n; + while ((n = rb_first(mem_map)) != 0) { + struct mem_map_data *data = + rb_entry(n, struct mem_map_data, node); + rb_erase(&data->node, mem_map); + kfree(data); + } + } +} + +/** + * Print heap debug information. + * @param s seq_file to log message to. + * @param heap pointer to heap that we will print debug information for. + */ +static void ion_heap_print_debug(struct seq_file *s, struct ion_heap *heap) +{ + if (heap->ops->print_debug) { + struct rb_root mem_map = RB_ROOT; + ion_debug_mem_map_create(s, heap, &mem_map); + heap->ops->print_debug(heap, s, &mem_map); + ion_debug_mem_map_destroy(&mem_map); + } +} + static int ion_debug_heap_show(struct seq_file *s, void *unused) { struct ion_heap *heap = s->private; struct ion_device *dev = heap->dev; struct rb_node *n; + mutex_lock(&dev->lock); seq_printf(s, "%16.s %16.s %16.s\n", "client", "pid", "size"); - - for (n = rb_first(&dev->clients); n; n = rb_next(n)) { + for (n = rb_first(&dev->user_clients); n; n = rb_next(n)) { struct ion_client *client = rb_entry(n, struct ion_client, node); - size_t size = ion_debug_heap_total(client, heap->type); + char task_comm[TASK_COMM_LEN]; + size_t size = ion_debug_heap_total(client, heap->id); if (!size) continue; - if (client->task) { - char task_comm[TASK_COMM_LEN]; - get_task_comm(task_comm, client->task); - seq_printf(s, "%16.s %16u %16u\n", task_comm, - client->pid, size); - } else { - seq_printf(s, "%16.s %16u %16u\n", client->name, - client->pid, size); - } + get_task_comm(task_comm, client->task); + seq_printf(s, "%16.s %16u %16x\n", task_comm, client->pid, + size); + } + + for (n = rb_first(&dev->kernel_clients); n; n = rb_next(n)) { + struct ion_client *client = rb_entry(n, struct ion_client, + node); + size_t size = ion_debug_heap_total(client, heap->id); + if (!size) + continue; + seq_printf(s, "%16.s %16u %16x\n", client->name, client->pid, + size); } + ion_heap_print_debug(s, heap); + mutex_unlock(&dev->lock); return 0; } @@ -981,11 +1873,6 @@ void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap) struct rb_node *parent = NULL; struct ion_heap *entry; - if (!heap->ops->allocate || !heap->ops->free || !heap->ops->map_dma || - !heap->ops->unmap_dma) - pr_err("%s: can not add heap with invalid ops struct.\n", - __func__); - heap->dev = dev; mutex_lock(&dev->lock); while (*p) { @@ -1011,6 +1898,135 @@ void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap) mutex_unlock(&dev->lock); } +int ion_secure_heap(struct ion_device *dev, int heap_id) +{ + struct rb_node *n; + int ret_val = 0; + + /* + * traverse the list of heaps available in this system + * and find the heap that is specified. + */ + mutex_lock(&dev->lock); + for (n = rb_first(&dev->heaps); n != NULL; n = rb_next(n)) { + struct ion_heap *heap = rb_entry(n, struct ion_heap, node); + if (heap->type != ION_HEAP_TYPE_CP) + continue; + if (ION_HEAP(heap->id) != heap_id) + continue; + if (heap->ops->secure_heap) + ret_val = heap->ops->secure_heap(heap); + else + ret_val = -EINVAL; + break; + } + mutex_unlock(&dev->lock); + return ret_val; +} + +int ion_unsecure_heap(struct ion_device *dev, int heap_id) +{ + struct rb_node *n; + int ret_val = 0; + + /* + * traverse the list of heaps available in this system + * and find the heap that is specified. + */ + mutex_lock(&dev->lock); + for (n = rb_first(&dev->heaps); n != NULL; n = rb_next(n)) { + struct ion_heap *heap = rb_entry(n, struct ion_heap, node); + if (heap->type != ION_HEAP_TYPE_CP) + continue; + if (ION_HEAP(heap->id) != heap_id) + continue; + if (heap->ops->secure_heap) + ret_val = heap->ops->unsecure_heap(heap); + else + ret_val = -EINVAL; + break; + } + mutex_unlock(&dev->lock); + return ret_val; +} + +static int ion_debug_leak_show(struct seq_file *s, void *unused) +{ + struct ion_device *dev = s->private; + struct rb_node *n; + struct rb_node *n2; + + /* mark all buffers as 1 */ + seq_printf(s, "%16.s %16.s %16.s %16.s\n", "buffer", "heap", "size", + "ref cnt"); + mutex_lock(&dev->lock); + for (n = rb_first(&dev->buffers); n; n = rb_next(n)) { + struct ion_buffer *buf = rb_entry(n, struct ion_buffer, + node); + + buf->marked = 1; + } + + /* now see which buffers we can access */ + for (n = rb_first(&dev->kernel_clients); n; n = rb_next(n)) { + struct ion_client *client = rb_entry(n, struct ion_client, + node); + + mutex_lock(&client->lock); + for (n2 = rb_first(&client->handles); n2; n2 = rb_next(n2)) { + struct ion_handle *handle = rb_entry(n2, + struct ion_handle, node); + + handle->buffer->marked = 0; + + } + mutex_unlock(&client->lock); + + } + + for (n = rb_first(&dev->user_clients); n; n = rb_next(n)) { + struct ion_client *client = rb_entry(n, struct ion_client, + node); + + mutex_lock(&client->lock); + for (n2 = rb_first(&client->handles); n2; n2 = rb_next(n2)) { + struct ion_handle *handle = rb_entry(n2, + struct ion_handle, node); + + handle->buffer->marked = 0; + + } + mutex_unlock(&client->lock); + + } + /* And anyone still marked as a 1 means a leaked handle somewhere */ + for (n = rb_first(&dev->buffers); n; n = rb_next(n)) { + struct ion_buffer *buf = rb_entry(n, struct ion_buffer, + node); + + if (buf->marked == 1) + seq_printf(s, "%16.x %16.s %16.x %16.d\n", + (int)buf, buf->heap->name, buf->size, + atomic_read(&buf->ref.refcount)); + } + mutex_unlock(&dev->lock); + return 0; +} + +static int ion_debug_leak_open(struct inode *inode, struct file *file) +{ + return single_open(file, ion_debug_leak_show, inode->i_private); +} + +static const struct file_operations debug_leak_fops = { + .open = ion_debug_leak_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + + struct ion_device *ion_device_create(long (*custom_ioctl) (struct ion_client *client, unsigned int cmd, @@ -1041,7 +2057,10 @@ struct ion_device *ion_device_create(long (*custom_ioctl) idev->buffers = RB_ROOT; mutex_init(&idev->lock); idev->heaps = RB_ROOT; - idev->clients = RB_ROOT; + idev->user_clients = RB_ROOT; + idev->kernel_clients = RB_ROOT; + debugfs_create_file("check_leaked_fds", 0664, idev->debug_root, idev, + &debug_leak_fops); return idev; } @@ -1051,19 +2070,3 @@ void ion_device_destroy(struct ion_device *dev) /* XXX need to free the heaps and clients ? */ kfree(dev); } - -void __init ion_reserve(struct ion_platform_data *data) -{ - int i, ret; - - for (i = 0; i < data->nr; i++) { - if (data->heaps[i].size == 0) - continue; - ret = memblock_reserve(data->heaps[i].base, - data->heaps[i].size); - if (ret) - pr_err("memblock reserve of %x@%lx failed\n", - data->heaps[i].size, - data->heaps[i].base); - } -} diff --git a/drivers/gpu/ion/ion_carveout_heap.c b/drivers/gpu/ion/ion_carveout_heap.c index b4fcb3c9247950c59e7f213a75b02d3896769164..1fdc1f992adcb03526560351a61facbc7b1802a8 100644 --- a/drivers/gpu/ion/ion_carveout_heap.c +++ b/drivers/gpu/ion/ion_carveout_heap.c @@ -2,6 +2,7 @@ * drivers/gpu/ion/ion_carveout_heap.c * * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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,14 +24,25 @@ #include #include #include +#include +#include #include "ion_priv.h" +#include #include +#include struct ion_carveout_heap { struct ion_heap heap; struct gen_pool *pool; ion_phys_addr_t base; + unsigned long allocated_bytes; + unsigned long total_size; + int (*request_region)(void *); + int (*release_region)(void *); + atomic_t map_count; + void *bus_id; + unsigned int has_outer_cache; }; ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap, @@ -39,11 +51,22 @@ ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap, { struct ion_carveout_heap *carveout_heap = container_of(heap, struct ion_carveout_heap, heap); - unsigned long offset = gen_pool_alloc(carveout_heap->pool, size); + unsigned long offset = gen_pool_alloc_aligned(carveout_heap->pool, + size, ilog2(align)); - if (!offset) + if (!offset) { + if ((carveout_heap->total_size - + carveout_heap->allocated_bytes) >= size) + pr_debug("%s: heap %s has enough memory (%lx) but" + " the allocation of size %lx still failed." + " Memory is probably fragmented.", + __func__, heap->name, + carveout_heap->total_size - + carveout_heap->allocated_bytes, size); return ION_CARVEOUT_ALLOCATE_FAIL; + } + carveout_heap->allocated_bytes += size; return offset; } @@ -56,6 +79,7 @@ void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr, if (addr == ION_CARVEOUT_ALLOCATE_FAIL) return; gen_pool_free(carveout_heap->pool, addr, size); + carveout_heap->allocated_bytes -= size; } static int ion_carveout_heap_phys(struct ion_heap *heap, @@ -87,37 +111,306 @@ static void ion_carveout_heap_free(struct ion_buffer *buffer) struct scatterlist *ion_carveout_heap_map_dma(struct ion_heap *heap, struct ion_buffer *buffer) { - return ERR_PTR(-EINVAL); + struct scatterlist *sglist; + + sglist = vmalloc(sizeof(struct scatterlist)); + if (!sglist) + return ERR_PTR(-ENOMEM); + + sg_init_table(sglist, 1); + sglist->length = buffer->size; + sglist->offset = 0; + sglist->dma_address = buffer->priv_phys; + + return sglist; } void ion_carveout_heap_unmap_dma(struct ion_heap *heap, struct ion_buffer *buffer) { - return; + if (buffer->sglist) + vfree(buffer->sglist); +} + +static int ion_carveout_request_region(struct ion_carveout_heap *carveout_heap) +{ + int ret_value = 0; + if (atomic_inc_return(&carveout_heap->map_count) == 1) { + if (carveout_heap->request_region) { + ret_value = carveout_heap->request_region( + carveout_heap->bus_id); + if (ret_value) { + pr_err("Unable to request SMI region"); + atomic_dec(&carveout_heap->map_count); + } + } + } + return ret_value; +} + +static int ion_carveout_release_region(struct ion_carveout_heap *carveout_heap) +{ + int ret_value = 0; + if (atomic_dec_and_test(&carveout_heap->map_count)) { + if (carveout_heap->release_region) { + ret_value = carveout_heap->release_region( + carveout_heap->bus_id); + if (ret_value) + pr_err("Unable to release SMI region"); + } + } + return ret_value; } void *ion_carveout_heap_map_kernel(struct ion_heap *heap, - struct ion_buffer *buffer) + struct ion_buffer *buffer, + unsigned long flags) { - return __arm_ioremap(buffer->priv_phys, buffer->size, - MT_MEMORY_NONCACHED); + struct ion_carveout_heap *carveout_heap = + container_of(heap, struct ion_carveout_heap, heap); + void *ret_value; + + if (ion_carveout_request_region(carveout_heap)) + return NULL; + + if (ION_IS_CACHED(flags)) + ret_value = ioremap_cached(buffer->priv_phys, buffer->size); + else + ret_value = ioremap(buffer->priv_phys, buffer->size); + + if (!ret_value) + ion_carveout_release_region(carveout_heap); + return ret_value; } void ion_carveout_heap_unmap_kernel(struct ion_heap *heap, struct ion_buffer *buffer) { + struct ion_carveout_heap *carveout_heap = + container_of(heap, struct ion_carveout_heap, heap); + __arm_iounmap(buffer->vaddr); buffer->vaddr = NULL; + + ion_carveout_release_region(carveout_heap); return; } int ion_carveout_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer, - struct vm_area_struct *vma) + struct vm_area_struct *vma, unsigned long flags) { - return remap_pfn_range(vma, vma->vm_start, - __phys_to_pfn(buffer->priv_phys) + vma->vm_pgoff, - buffer->size, - pgprot_noncached(vma->vm_page_prot)); + struct ion_carveout_heap *carveout_heap = + container_of(heap, struct ion_carveout_heap, heap); + int ret_value = 0; + + if (ion_carveout_request_region(carveout_heap)) + return -EINVAL; + + if (!ION_IS_CACHED(flags)) + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + ret_value = remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(buffer->priv_phys) + vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + + if (ret_value) + ion_carveout_release_region(carveout_heap); + return ret_value; +} + +void ion_carveout_heap_unmap_user(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + struct ion_carveout_heap *carveout_heap = + container_of(heap, struct ion_carveout_heap, heap); + ion_carveout_release_region(carveout_heap); +} + +int ion_carveout_cache_ops(struct ion_heap *heap, struct ion_buffer *buffer, + void *vaddr, unsigned int offset, unsigned int length, + unsigned int cmd) +{ + void (*outer_cache_op)(phys_addr_t, phys_addr_t); + struct ion_carveout_heap *carveout_heap = + container_of(heap, struct ion_carveout_heap, heap); + + switch (cmd) { + case ION_IOC_CLEAN_CACHES: + dmac_clean_range(vaddr, vaddr + length); + outer_cache_op = outer_clean_range; + break; + case ION_IOC_INV_CACHES: + dmac_inv_range(vaddr, vaddr + length); + outer_cache_op = outer_inv_range; + break; + case ION_IOC_CLEAN_INV_CACHES: + dmac_flush_range(vaddr, vaddr + length); + outer_cache_op = outer_flush_range; + break; + default: + return -EINVAL; + } + + if (carveout_heap->has_outer_cache) { + unsigned long pstart = buffer->priv_phys + offset; + outer_cache_op(pstart, pstart + length); + } + return 0; +} + +static int ion_carveout_print_debug(struct ion_heap *heap, struct seq_file *s, + const struct rb_root *mem_map) +{ + struct ion_carveout_heap *carveout_heap = + container_of(heap, struct ion_carveout_heap, heap); + + seq_printf(s, "total bytes currently allocated: %lx\n", + carveout_heap->allocated_bytes); + seq_printf(s, "total heap size: %lx\n", carveout_heap->total_size); + + if (mem_map) { + unsigned long base = carveout_heap->base; + unsigned long size = carveout_heap->total_size; + unsigned long end = base+size; + unsigned long last_end = base; + struct rb_node *n; + + seq_printf(s, "\nMemory Map\n"); + seq_printf(s, "%16.s %14.s %14.s %14.s\n", + "client", "start address", "end address", + "size (hex)"); + + for (n = rb_first(mem_map); n; n = rb_next(n)) { + struct mem_map_data *data = + rb_entry(n, struct mem_map_data, node); + const char *client_name = "(null)"; + + if (last_end < data->addr) { + seq_printf(s, "%16.s %14lx %14lx %14lu (%lx)\n", + "FREE", last_end, data->addr-1, + data->addr-last_end, + data->addr-last_end); + } + + if (data->client_name) + client_name = data->client_name; + + seq_printf(s, "%16.s %14lx %14lx %14lu (%lx)\n", + client_name, data->addr, + data->addr_end, + data->size, data->size); + last_end = data->addr_end+1; + } + if (last_end < end) { + seq_printf(s, "%16.s %14lx %14lx %14lu (%lx)\n", "FREE", + last_end, end-1, end-last_end, end-last_end); + } + } + return 0; +} + +int ion_carveout_heap_map_iommu(struct ion_buffer *buffer, + struct ion_iommu_map *data, + unsigned int domain_num, + unsigned int partition_num, + unsigned long align, + unsigned long iova_length, + unsigned long flags) +{ + struct iommu_domain *domain; + int ret = 0; + unsigned long extra; + struct scatterlist *sglist = 0; + int prot = IOMMU_WRITE | IOMMU_READ; + prot |= ION_IS_CACHED(flags) ? IOMMU_CACHE : 0; + + data->mapped_size = iova_length; + + if (!msm_use_iommu()) { + data->iova_addr = buffer->priv_phys; + return 0; + } + + extra = iova_length - buffer->size; + + ret = msm_allocate_iova_address(domain_num, partition_num, + data->mapped_size, align, + &data->iova_addr); + + if (ret) + goto out; + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + ret = -ENOMEM; + goto out1; + } + + sglist = vmalloc(sizeof(*sglist)); + if (!sglist) + goto out1; + + sg_init_table(sglist, 1); + sglist->length = buffer->size; + sglist->offset = 0; + sglist->dma_address = buffer->priv_phys; + + ret = iommu_map_range(domain, data->iova_addr, sglist, + buffer->size, prot); + if (ret) { + pr_err("%s: could not map %lx in domain %p\n", + __func__, data->iova_addr, domain); + goto out1; + } + + if (extra) { + unsigned long extra_iova_addr = data->iova_addr + buffer->size; + ret = msm_iommu_map_extra(domain, extra_iova_addr, extra, + SZ_4K, prot); + if (ret) + goto out2; + } + vfree(sglist); + return ret; + +out2: + iommu_unmap_range(domain, data->iova_addr, buffer->size); +out1: + vfree(sglist); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); + +out: + + return ret; +} + +void ion_carveout_heap_unmap_iommu(struct ion_iommu_map *data) +{ + unsigned int domain_num; + unsigned int partition_num; + struct iommu_domain *domain; + + if (!msm_use_iommu()) + return; + + domain_num = iommu_map_domain(data); + partition_num = iommu_map_partition(data); + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + WARN(1, "Could not get domain %d. Corruption?\n", domain_num); + return; + } + + iommu_unmap_range(domain, data->iova_addr, data->mapped_size); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); + + return; } static struct ion_heap_ops carveout_heap_ops = { @@ -126,12 +419,20 @@ static struct ion_heap_ops carveout_heap_ops = { .phys = ion_carveout_heap_phys, .map_user = ion_carveout_heap_map_user, .map_kernel = ion_carveout_heap_map_kernel, + .unmap_user = ion_carveout_heap_unmap_user, .unmap_kernel = ion_carveout_heap_unmap_kernel, + .map_dma = ion_carveout_heap_map_dma, + .unmap_dma = ion_carveout_heap_unmap_dma, + .cache_op = ion_carveout_cache_ops, + .print_debug = ion_carveout_print_debug, + .map_iommu = ion_carveout_heap_map_iommu, + .unmap_iommu = ion_carveout_heap_unmap_iommu, }; struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data) { struct ion_carveout_heap *carveout_heap; + int ret; carveout_heap = kzalloc(sizeof(struct ion_carveout_heap), GFP_KERNEL); if (!carveout_heap) @@ -143,11 +444,32 @@ struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data) return ERR_PTR(-ENOMEM); } carveout_heap->base = heap_data->base; - gen_pool_add(carveout_heap->pool, carveout_heap->base, heap_data->size, - -1); + ret = gen_pool_add(carveout_heap->pool, carveout_heap->base, + heap_data->size, -1); + if (ret < 0) { + gen_pool_destroy(carveout_heap->pool); + kfree(carveout_heap); + return ERR_PTR(-EINVAL); + } carveout_heap->heap.ops = &carveout_heap_ops; carveout_heap->heap.type = ION_HEAP_TYPE_CARVEOUT; + carveout_heap->allocated_bytes = 0; + carveout_heap->total_size = heap_data->size; + carveout_heap->has_outer_cache = heap_data->has_outer_cache; + + if (heap_data->extra_data) { + struct ion_co_heap_pdata *extra_data = + heap_data->extra_data; + if (extra_data->setup_region) + carveout_heap->bus_id = extra_data->setup_region(); + if (extra_data->request_region) + carveout_heap->request_region = + extra_data->request_region; + if (extra_data->release_region) + carveout_heap->release_region = + extra_data->release_region; + } return &carveout_heap->heap; } diff --git a/drivers/gpu/ion/ion_cp_heap.c b/drivers/gpu/ion/ion_cp_heap.c new file mode 100644 index 0000000000000000000000000000000000000000..a857988a88b3fef5f3c52851ae0f3a84ace4f35a --- /dev/null +++ b/drivers/gpu/ion/ion_cp_heap.c @@ -0,0 +1,1019 @@ +/* + * drivers/gpu/ion/ion_cp_heap.c + * + * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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 "ion_priv.h" + +#include +#include + +/** + * struct ion_cp_heap - container for the heap and shared heap data + + * @heap: the heap information structure + * @pool: memory pool to allocate from. + * @base: the base address of the memory pool. + * @permission_type: Identifier for the memory used by SCM for protecting + * and unprotecting memory. + * @secure_base: Base address used when securing a heap that is shared. + * @secure_size: Size used when securing a heap that is shared. + * @lock: mutex to protect shared access. + * @heap_protected: Indicates whether heap has been protected or not. + * @allocated_bytes: the total number of allocated bytes from the pool. + * @total_size: the total size of the memory pool. + * @request_region: function pointer to call when first mapping of memory + * occurs. + * @release_region: function pointer to call when last mapping of memory + * unmapped. + * @bus_id: token used with request/release region. + * @kmap_cached_count: the total number of times this heap has been mapped in + * kernel space (cached). + * @kmap_uncached_count:the total number of times this heap has been mapped in + * kernel space (un-cached). + * @umap_count: the total number of times this heap has been mapped in + * user space. + * @iommu_iova: saved iova when mapping full heap at once. + * @iommu_partition: partition used to map full heap. + * @reusable: indicates if the memory should be reused via fmem. + * @reserved_vrange: reserved virtual address range for use with fmem + * @iommu_map_all: Indicates whether we should map whole heap into IOMMU. + * @iommu_2x_map_domain: Indicates the domain to use for overmapping. + * @has_outer_cache: set to 1 if outer cache is used, 0 otherwise. +*/ +struct ion_cp_heap { + struct ion_heap heap; + struct gen_pool *pool; + ion_phys_addr_t base; + unsigned int permission_type; + ion_phys_addr_t secure_base; + size_t secure_size; + struct mutex lock; + unsigned int heap_protected; + unsigned long allocated_bytes; + unsigned long total_size; + int (*request_region)(void *); + int (*release_region)(void *); + void *bus_id; + unsigned long kmap_cached_count; + unsigned long kmap_uncached_count; + unsigned long umap_count; + unsigned long iommu_iova[MAX_DOMAINS]; + unsigned long iommu_partition[MAX_DOMAINS]; + int reusable; + void *reserved_vrange; + int iommu_map_all; + int iommu_2x_map_domain; + unsigned int has_outer_cache; +}; + +enum { + HEAP_NOT_PROTECTED = 0, + HEAP_PROTECTED = 1, +}; + +static int ion_cp_protect_mem(unsigned int phy_base, unsigned int size, + unsigned int permission_type); + +static int ion_cp_unprotect_mem(unsigned int phy_base, unsigned int size, + unsigned int permission_type); + +/** + * Get the total number of kernel mappings. + * Must be called with heap->lock locked. + */ +static unsigned long ion_cp_get_total_kmap_count( + const struct ion_cp_heap *cp_heap) +{ + return cp_heap->kmap_cached_count + cp_heap->kmap_uncached_count; +} + +/** + * Protects memory if heap is unsecured heap. Also ensures that we are in + * the correct FMEM state if this heap is a reusable heap. + * Must be called with heap->lock locked. + */ +static int ion_cp_protect(struct ion_heap *heap) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + int ret_value = 0; + + if (cp_heap->heap_protected == HEAP_NOT_PROTECTED) { + /* Make sure we are in C state when the heap is protected. */ + if (cp_heap->reusable && !cp_heap->allocated_bytes) { + ret_value = fmem_set_state(FMEM_C_STATE); + if (ret_value) + goto out; + } + + ret_value = ion_cp_protect_mem(cp_heap->secure_base, + cp_heap->secure_size, cp_heap->permission_type); + if (ret_value) { + pr_err("Failed to protect memory for heap %s - " + "error code: %d\n", heap->name, ret_value); + + if (cp_heap->reusable && !cp_heap->allocated_bytes) { + if (fmem_set_state(FMEM_T_STATE) != 0) + pr_err("%s: unable to transition heap to T-state\n", + __func__); + } + } else { + cp_heap->heap_protected = HEAP_PROTECTED; + pr_debug("Protected heap %s @ 0x%lx\n", + heap->name, cp_heap->base); + } + } +out: + return ret_value; +} + +/** + * Unprotects memory if heap is secure heap. Also ensures that we are in + * the correct FMEM state if this heap is a reusable heap. + * Must be called with heap->lock locked. + */ +static void ion_cp_unprotect(struct ion_heap *heap) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + if (cp_heap->heap_protected == HEAP_PROTECTED) { + int error_code = ion_cp_unprotect_mem( + cp_heap->secure_base, cp_heap->secure_size, + cp_heap->permission_type); + if (error_code) { + pr_err("Failed to un-protect memory for heap %s - " + "error code: %d\n", heap->name, error_code); + } else { + cp_heap->heap_protected = HEAP_NOT_PROTECTED; + pr_debug("Un-protected heap %s @ 0x%x\n", heap->name, + (unsigned int) cp_heap->base); + + if (cp_heap->reusable && !cp_heap->allocated_bytes) { + if (fmem_set_state(FMEM_T_STATE) != 0) + pr_err("%s: unable to transition heap to T-state", + __func__); + } + } + } +} + +ion_phys_addr_t ion_cp_allocate(struct ion_heap *heap, + unsigned long size, + unsigned long align, + unsigned long flags) +{ + unsigned long offset; + unsigned long secure_allocation = flags & ION_SECURE; + + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + mutex_lock(&cp_heap->lock); + if (!secure_allocation && cp_heap->heap_protected == HEAP_PROTECTED) { + mutex_unlock(&cp_heap->lock); + pr_err("ION cannot allocate un-secure memory from protected" + " heap %s\n", heap->name); + return ION_CP_ALLOCATE_FAIL; + } + + if (secure_allocation && + (cp_heap->umap_count > 0 || cp_heap->kmap_cached_count > 0)) { + mutex_unlock(&cp_heap->lock); + pr_err("ION cannot allocate secure memory from heap with " + "outstanding mappings: User space: %lu, kernel space " + "(cached): %lu\n", cp_heap->umap_count, + cp_heap->kmap_cached_count); + return ION_CP_ALLOCATE_FAIL; + } + + /* + * if this is the first reusable allocation, transition + * the heap + */ + if (cp_heap->reusable && !cp_heap->allocated_bytes) { + if (fmem_set_state(FMEM_C_STATE) != 0) { + mutex_unlock(&cp_heap->lock); + return ION_RESERVED_ALLOCATE_FAIL; + } + } + + cp_heap->allocated_bytes += size; + mutex_unlock(&cp_heap->lock); + + offset = gen_pool_alloc_aligned(cp_heap->pool, + size, ilog2(align)); + + if (!offset) { + mutex_lock(&cp_heap->lock); + cp_heap->allocated_bytes -= size; + if ((cp_heap->total_size - + cp_heap->allocated_bytes) >= size) + pr_debug("%s: heap %s has enough memory (%lx) but" + " the allocation of size %lx still failed." + " Memory is probably fragmented.\n", + __func__, heap->name, + cp_heap->total_size - + cp_heap->allocated_bytes, size); + + if (cp_heap->reusable && !cp_heap->allocated_bytes && + cp_heap->heap_protected == HEAP_NOT_PROTECTED) { + if (fmem_set_state(FMEM_T_STATE) != 0) + pr_err("%s: unable to transition heap to T-state\n", + __func__); + } + mutex_unlock(&cp_heap->lock); + + return ION_CP_ALLOCATE_FAIL; + } + + return offset; +} + +static void iommu_unmap_all(unsigned long domain_num, + struct ion_cp_heap *cp_heap) +{ + unsigned long left_to_unmap = cp_heap->total_size; + unsigned long page_size = SZ_64K; + + struct iommu_domain *domain = msm_get_iommu_domain(domain_num); + if (domain) { + unsigned long temp_iova = cp_heap->iommu_iova[domain_num]; + + while (left_to_unmap) { + iommu_unmap(domain, temp_iova, page_size); + temp_iova += page_size; + left_to_unmap -= page_size; + } + if (domain_num == cp_heap->iommu_2x_map_domain) + msm_iommu_unmap_extra(domain, temp_iova, + cp_heap->total_size, SZ_64K); + } else { + pr_err("Unable to get IOMMU domain %lu\n", domain_num); + } +} + +void ion_cp_free(struct ion_heap *heap, ion_phys_addr_t addr, + unsigned long size) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + if (addr == ION_CP_ALLOCATE_FAIL) + return; + gen_pool_free(cp_heap->pool, addr, size); + + mutex_lock(&cp_heap->lock); + cp_heap->allocated_bytes -= size; + + if (cp_heap->reusable && !cp_heap->allocated_bytes && + cp_heap->heap_protected == HEAP_NOT_PROTECTED) { + if (fmem_set_state(FMEM_T_STATE) != 0) + pr_err("%s: unable to transition heap to T-state\n", + __func__); + } + + /* Unmap everything if we previously mapped the whole heap at once. */ + if (!cp_heap->allocated_bytes) { + unsigned int i; + for (i = 0; i < MAX_DOMAINS; ++i) { + if (cp_heap->iommu_iova[i]) { + unsigned long vaddr_len = cp_heap->total_size; + + if (i == cp_heap->iommu_2x_map_domain) + vaddr_len <<= 1; + iommu_unmap_all(i, cp_heap); + + msm_free_iova_address(cp_heap->iommu_iova[i], i, + cp_heap->iommu_partition[i], + vaddr_len); + } + cp_heap->iommu_iova[i] = 0; + cp_heap->iommu_partition[i] = 0; + } + } + mutex_unlock(&cp_heap->lock); +} + +static int ion_cp_heap_phys(struct ion_heap *heap, + struct ion_buffer *buffer, + ion_phys_addr_t *addr, size_t *len) +{ + *addr = buffer->priv_phys; + *len = buffer->size; + return 0; +} + +static int ion_cp_heap_allocate(struct ion_heap *heap, + struct ion_buffer *buffer, + unsigned long size, unsigned long align, + unsigned long flags) +{ + buffer->priv_phys = ion_cp_allocate(heap, size, align, flags); + return buffer->priv_phys == ION_CP_ALLOCATE_FAIL ? -ENOMEM : 0; +} + +static void ion_cp_heap_free(struct ion_buffer *buffer) +{ + struct ion_heap *heap = buffer->heap; + + ion_cp_free(heap, buffer->priv_phys, buffer->size); + buffer->priv_phys = ION_CP_ALLOCATE_FAIL; +} + +struct scatterlist *ion_cp_heap_create_sglist(struct ion_buffer *buffer) +{ + struct scatterlist *sglist; + + sglist = vmalloc(sizeof(*sglist)); + if (!sglist) + return ERR_PTR(-ENOMEM); + + sg_init_table(sglist, 1); + sglist->length = buffer->size; + sglist->offset = 0; + sglist->dma_address = buffer->priv_phys; + + return sglist; +} + +struct scatterlist *ion_cp_heap_map_dma(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + return ion_cp_heap_create_sglist(buffer); +} + +void ion_cp_heap_unmap_dma(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + if (buffer->sglist) + vfree(buffer->sglist); +} + +/** + * Call request region for SMI memory of this is the first mapping. + */ +static int ion_cp_request_region(struct ion_cp_heap *cp_heap) +{ + int ret_value = 0; + if ((cp_heap->umap_count + ion_cp_get_total_kmap_count(cp_heap)) == 0) + if (cp_heap->request_region) + ret_value = cp_heap->request_region(cp_heap->bus_id); + return ret_value; +} + +/** + * Call release region for SMI memory of this is the last un-mapping. + */ +static int ion_cp_release_region(struct ion_cp_heap *cp_heap) +{ + int ret_value = 0; + if ((cp_heap->umap_count + ion_cp_get_total_kmap_count(cp_heap)) == 0) + if (cp_heap->release_region) + ret_value = cp_heap->release_region(cp_heap->bus_id); + return ret_value; +} + +void *ion_map_fmem_buffer(struct ion_buffer *buffer, unsigned long phys_base, + void *virt_base, unsigned long flags) +{ + int ret; + unsigned int offset = buffer->priv_phys - phys_base; + unsigned long start = ((unsigned long)virt_base) + offset; + const struct mem_type *type = ION_IS_CACHED(flags) ? + get_mem_type(MT_DEVICE_CACHED) : + get_mem_type(MT_DEVICE); + + if (phys_base > buffer->priv_phys) + return NULL; + + + ret = ioremap_pages(start, buffer->priv_phys, buffer->size, type); + + if (!ret) + return (void *)start; + else + return NULL; +} + +void *ion_cp_heap_map_kernel(struct ion_heap *heap, + struct ion_buffer *buffer, + unsigned long flags) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + void *ret_value = NULL; + + mutex_lock(&cp_heap->lock); + if ((cp_heap->heap_protected == HEAP_NOT_PROTECTED) || + ((cp_heap->heap_protected == HEAP_PROTECTED) && + !ION_IS_CACHED(flags))) { + + if (ion_cp_request_region(cp_heap)) { + mutex_unlock(&cp_heap->lock); + return NULL; + } + + if (cp_heap->reusable) { + ret_value = ion_map_fmem_buffer(buffer, cp_heap->base, + cp_heap->reserved_vrange, flags); + + } else { + if (ION_IS_CACHED(flags)) + ret_value = ioremap_cached(buffer->priv_phys, + buffer->size); + else + ret_value = ioremap(buffer->priv_phys, + buffer->size); + } + + if (!ret_value) { + ion_cp_release_region(cp_heap); + } else { + if (ION_IS_CACHED(buffer->flags)) + ++cp_heap->kmap_cached_count; + else + ++cp_heap->kmap_uncached_count; + } + } + mutex_unlock(&cp_heap->lock); + return ret_value; +} + +void ion_cp_heap_unmap_kernel(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + if (cp_heap->reusable) + unmap_kernel_range((unsigned long)buffer->vaddr, buffer->size); + else + __arm_iounmap(buffer->vaddr); + + buffer->vaddr = NULL; + + mutex_lock(&cp_heap->lock); + if (ION_IS_CACHED(buffer->flags)) + --cp_heap->kmap_cached_count; + else + --cp_heap->kmap_uncached_count; + ion_cp_release_region(cp_heap); + mutex_unlock(&cp_heap->lock); + + return; +} + +int ion_cp_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer, + struct vm_area_struct *vma, unsigned long flags) +{ + int ret_value = -EAGAIN; + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + mutex_lock(&cp_heap->lock); + if (cp_heap->heap_protected == HEAP_NOT_PROTECTED) { + if (ion_cp_request_region(cp_heap)) { + mutex_unlock(&cp_heap->lock); + return -EINVAL; + } + + if (!ION_IS_CACHED(flags)) + vma->vm_page_prot = pgprot_writecombine( + vma->vm_page_prot); + + ret_value = remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(buffer->priv_phys) + vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + + if (ret_value) + ion_cp_release_region(cp_heap); + else + ++cp_heap->umap_count; + } + mutex_unlock(&cp_heap->lock); + return ret_value; +} + +void ion_cp_heap_unmap_user(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + mutex_lock(&cp_heap->lock); + --cp_heap->umap_count; + ion_cp_release_region(cp_heap); + mutex_unlock(&cp_heap->lock); +} + +int ion_cp_cache_ops(struct ion_heap *heap, struct ion_buffer *buffer, + void *vaddr, unsigned int offset, unsigned int length, + unsigned int cmd) +{ + void (*outer_cache_op)(phys_addr_t, phys_addr_t); + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + switch (cmd) { + case ION_IOC_CLEAN_CACHES: + dmac_clean_range(vaddr, vaddr + length); + outer_cache_op = outer_clean_range; + break; + case ION_IOC_INV_CACHES: + dmac_inv_range(vaddr, vaddr + length); + outer_cache_op = outer_inv_range; + break; + case ION_IOC_CLEAN_INV_CACHES: + dmac_flush_range(vaddr, vaddr + length); + outer_cache_op = outer_flush_range; + break; + default: + return -EINVAL; + } + + if (cp_heap->has_outer_cache) { + unsigned long pstart = buffer->priv_phys + offset; + outer_cache_op(pstart, pstart + length); + } + return 0; +} + +static int ion_cp_print_debug(struct ion_heap *heap, struct seq_file *s, + const struct rb_root *mem_map) +{ + unsigned long total_alloc; + unsigned long total_size; + unsigned long umap_count; + unsigned long kmap_count; + unsigned long heap_protected; + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + mutex_lock(&cp_heap->lock); + total_alloc = cp_heap->allocated_bytes; + total_size = cp_heap->total_size; + umap_count = cp_heap->umap_count; + kmap_count = ion_cp_get_total_kmap_count(cp_heap); + heap_protected = cp_heap->heap_protected == HEAP_PROTECTED; + mutex_unlock(&cp_heap->lock); + + seq_printf(s, "total bytes currently allocated: %lx\n", total_alloc); + seq_printf(s, "total heap size: %lx\n", total_size); + seq_printf(s, "umapping count: %lx\n", umap_count); + seq_printf(s, "kmapping count: %lx\n", kmap_count); + seq_printf(s, "heap protected: %s\n", heap_protected ? "Yes" : "No"); + seq_printf(s, "reusable: %s\n", cp_heap->reusable ? "Yes" : "No"); + + if (mem_map) { + unsigned long base = cp_heap->base; + unsigned long size = cp_heap->total_size; + unsigned long end = base+size; + unsigned long last_end = base; + struct rb_node *n; + + seq_printf(s, "\nMemory Map\n"); + seq_printf(s, "%16.s %14.s %14.s %14.s\n", + "client", "start address", "end address", + "size (hex)"); + + for (n = rb_first(mem_map); n; n = rb_next(n)) { + struct mem_map_data *data = + rb_entry(n, struct mem_map_data, node); + const char *client_name = "(null)"; + + if (last_end < data->addr) { + seq_printf(s, "%16.s %14lx %14lx %14lu (%lx)\n", + "FREE", last_end, data->addr-1, + data->addr-last_end, + data->addr-last_end); + } + + if (data->client_name) + client_name = data->client_name; + + seq_printf(s, "%16.s %14lx %14lx %14lu (%lx)\n", + client_name, data->addr, + data->addr_end, + data->size, data->size); + last_end = data->addr_end+1; + } + if (last_end < end) { + seq_printf(s, "%16.s %14lx %14lx %14lu (%lx)\n", "FREE", + last_end, end-1, end-last_end, end-last_end); + } + } + + return 0; +} + +int ion_cp_secure_heap(struct ion_heap *heap) +{ + int ret_value; + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + mutex_lock(&cp_heap->lock); + if (cp_heap->umap_count == 0 && cp_heap->kmap_cached_count == 0) { + ret_value = ion_cp_protect(heap); + } else { + pr_err("ION cannot secure heap with outstanding mappings: " + "User space: %lu, kernel space (cached): %lu\n", + cp_heap->umap_count, cp_heap->kmap_cached_count); + ret_value = -EINVAL; + } + + mutex_unlock(&cp_heap->lock); + return ret_value; +} + +int ion_cp_unsecure_heap(struct ion_heap *heap) +{ + int ret_value = 0; + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + mutex_lock(&cp_heap->lock); + ion_cp_unprotect(heap); + mutex_unlock(&cp_heap->lock); + return ret_value; +} + +static int iommu_map_all(unsigned long domain_num, struct ion_cp_heap *cp_heap, + int partition, unsigned long prot) +{ + unsigned long left_to_map = cp_heap->total_size; + unsigned long page_size = SZ_64K; + int ret_value = 0; + unsigned long virt_addr_len = cp_heap->total_size; + struct iommu_domain *domain = msm_get_iommu_domain(domain_num); + + /* If we are mapping into the video domain we need to map twice the + * size of the heap to account for prefetch issue in video core. + */ + if (domain_num == cp_heap->iommu_2x_map_domain) + virt_addr_len <<= 1; + + if (cp_heap->total_size & (SZ_64K-1)) { + pr_err("Heap size is not aligned to 64K, cannot map into IOMMU\n"); + ret_value = -EINVAL; + } + if (cp_heap->base & (SZ_64K-1)) { + pr_err("Heap physical address is not aligned to 64K, cannot map into IOMMU\n"); + ret_value = -EINVAL; + } + if (!ret_value && domain) { + unsigned long temp_phys = cp_heap->base; + unsigned long temp_iova; + + ret_value = msm_allocate_iova_address(domain_num, partition, + virt_addr_len, SZ_64K, + &temp_iova); + + if (ret_value) { + pr_err("%s: could not allocate iova from domain %lu, partition %d\n", + __func__, domain_num, partition); + goto out; + } + cp_heap->iommu_iova[domain_num] = temp_iova; + + while (left_to_map) { + int ret = iommu_map(domain, temp_iova, temp_phys, + page_size, prot); + if (ret) { + pr_err("%s: could not map %lx in domain %p, error: %d\n", + __func__, temp_iova, domain, ret); + ret_value = -EAGAIN; + goto free_iova; + } + temp_iova += page_size; + temp_phys += page_size; + left_to_map -= page_size; + } + if (domain_num == cp_heap->iommu_2x_map_domain) + ret_value = msm_iommu_map_extra(domain, temp_iova, + cp_heap->total_size, + SZ_64K, prot); + if (ret_value) + goto free_iova; + } else { + pr_err("Unable to get IOMMU domain %lu\n", domain_num); + ret_value = -ENOMEM; + } + goto out; + +free_iova: + msm_free_iova_address(cp_heap->iommu_iova[domain_num], domain_num, + partition, virt_addr_len); +out: + return ret_value; +} + +static int ion_cp_heap_map_iommu(struct ion_buffer *buffer, + struct ion_iommu_map *data, + unsigned int domain_num, + unsigned int partition_num, + unsigned long align, + unsigned long iova_length, + unsigned long flags) +{ + struct iommu_domain *domain; + int ret = 0; + unsigned long extra; + struct scatterlist *sglist = 0; + struct ion_cp_heap *cp_heap = + container_of(buffer->heap, struct ion_cp_heap, heap); + int prot = IOMMU_WRITE | IOMMU_READ; + prot |= ION_IS_CACHED(flags) ? IOMMU_CACHE : 0; + + data->mapped_size = iova_length; + + if (!msm_use_iommu()) { + data->iova_addr = buffer->priv_phys; + return 0; + } + + if (cp_heap->iommu_iova[domain_num]) { + /* Already mapped. */ + unsigned long offset = buffer->priv_phys - cp_heap->base; + data->iova_addr = cp_heap->iommu_iova[domain_num] + offset; + return 0; + } else if (cp_heap->iommu_map_all) { + ret = iommu_map_all(domain_num, cp_heap, partition_num, prot); + if (!ret) { + unsigned long offset = + buffer->priv_phys - cp_heap->base; + data->iova_addr = + cp_heap->iommu_iova[domain_num] + offset; + cp_heap->iommu_partition[domain_num] = partition_num; + /* + clear delayed map flag so that we don't interfere + with this feature (we are already delaying). + */ + data->flags &= ~ION_IOMMU_UNMAP_DELAYED; + return 0; + } else { + cp_heap->iommu_iova[domain_num] = 0; + cp_heap->iommu_partition[domain_num] = 0; + return ret; + } + } + + extra = iova_length - buffer->size; + + ret = msm_allocate_iova_address(domain_num, partition_num, + data->mapped_size, align, + &data->iova_addr); + + if (ret) + goto out; + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + ret = -ENOMEM; + goto out1; + } + + sglist = ion_cp_heap_create_sglist(buffer); + if (IS_ERR_OR_NULL(sglist)) { + ret = -ENOMEM; + goto out1; + } + ret = iommu_map_range(domain, data->iova_addr, sglist, + buffer->size, prot); + if (ret) { + pr_err("%s: could not map %lx in domain %p\n", + __func__, data->iova_addr, domain); + goto out1; + } + + if (extra) { + unsigned long extra_iova_addr = data->iova_addr + buffer->size; + ret = msm_iommu_map_extra(domain, extra_iova_addr, extra, + SZ_4K, prot); + if (ret) + goto out2; + } + vfree(sglist); + return ret; + +out2: + iommu_unmap_range(domain, data->iova_addr, buffer->size); +out1: + if (!IS_ERR_OR_NULL(sglist)) + vfree(sglist); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); +out: + return ret; +} + +static void ion_cp_heap_unmap_iommu(struct ion_iommu_map *data) +{ + unsigned int domain_num; + unsigned int partition_num; + struct iommu_domain *domain; + struct ion_cp_heap *cp_heap = + container_of(data->buffer->heap, struct ion_cp_heap, heap); + + if (!msm_use_iommu()) + return; + + + domain_num = iommu_map_domain(data); + + /* If we are mapping everything we'll wait to unmap until everything + is freed. */ + if (cp_heap->iommu_iova[domain_num]) + return; + + partition_num = iommu_map_partition(data); + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + WARN(1, "Could not get domain %d. Corruption?\n", domain_num); + return; + } + + iommu_unmap_range(domain, data->iova_addr, data->mapped_size); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); + + return; +} + +static struct ion_heap_ops cp_heap_ops = { + .allocate = ion_cp_heap_allocate, + .free = ion_cp_heap_free, + .phys = ion_cp_heap_phys, + .map_user = ion_cp_heap_map_user, + .unmap_user = ion_cp_heap_unmap_user, + .map_kernel = ion_cp_heap_map_kernel, + .unmap_kernel = ion_cp_heap_unmap_kernel, + .map_dma = ion_cp_heap_map_dma, + .unmap_dma = ion_cp_heap_unmap_dma, + .cache_op = ion_cp_cache_ops, + .print_debug = ion_cp_print_debug, + .secure_heap = ion_cp_secure_heap, + .unsecure_heap = ion_cp_unsecure_heap, + .map_iommu = ion_cp_heap_map_iommu, + .unmap_iommu = ion_cp_heap_unmap_iommu, +}; + +struct ion_heap *ion_cp_heap_create(struct ion_platform_heap *heap_data) +{ + struct ion_cp_heap *cp_heap; + int ret; + + cp_heap = kzalloc(sizeof(*cp_heap), GFP_KERNEL); + if (!cp_heap) + return ERR_PTR(-ENOMEM); + + mutex_init(&cp_heap->lock); + + cp_heap->pool = gen_pool_create(12, -1); + if (!cp_heap->pool) + goto free_heap; + + cp_heap->base = heap_data->base; + ret = gen_pool_add(cp_heap->pool, cp_heap->base, heap_data->size, -1); + if (ret < 0) + goto destroy_pool; + + cp_heap->allocated_bytes = 0; + cp_heap->umap_count = 0; + cp_heap->kmap_cached_count = 0; + cp_heap->kmap_uncached_count = 0; + cp_heap->total_size = heap_data->size; + cp_heap->heap.ops = &cp_heap_ops; + cp_heap->heap.type = ION_HEAP_TYPE_CP; + cp_heap->heap_protected = HEAP_NOT_PROTECTED; + cp_heap->secure_base = cp_heap->base; + cp_heap->secure_size = heap_data->size; + cp_heap->has_outer_cache = heap_data->has_outer_cache; + if (heap_data->extra_data) { + struct ion_cp_heap_pdata *extra_data = + heap_data->extra_data; + cp_heap->reusable = extra_data->reusable; + cp_heap->reserved_vrange = extra_data->virt_addr; + cp_heap->permission_type = extra_data->permission_type; + if (extra_data->secure_size) { + cp_heap->secure_base = extra_data->secure_base; + cp_heap->secure_size = extra_data->secure_size; + } + if (extra_data->setup_region) + cp_heap->bus_id = extra_data->setup_region(); + if (extra_data->request_region) + cp_heap->request_region = extra_data->request_region; + if (extra_data->release_region) + cp_heap->release_region = extra_data->release_region; + cp_heap->iommu_map_all = + extra_data->iommu_map_all; + cp_heap->iommu_2x_map_domain = + extra_data->iommu_2x_map_domain; + + } + + return &cp_heap->heap; + +destroy_pool: + gen_pool_destroy(cp_heap->pool); + +free_heap: + kfree(cp_heap); + + return ERR_PTR(-ENOMEM); +} + +void ion_cp_heap_destroy(struct ion_heap *heap) +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + + gen_pool_destroy(cp_heap->pool); + kfree(cp_heap); + cp_heap = NULL; +} + +void ion_cp_heap_get_base(struct ion_heap *heap, unsigned long *base, + unsigned long *size) \ +{ + struct ion_cp_heap *cp_heap = + container_of(heap, struct ion_cp_heap, heap); + *base = cp_heap->base; + *size = cp_heap->total_size; +} + +/* SCM related code for locking down memory for content protection */ + +#define SCM_CP_LOCK_CMD_ID 0x1 +#define SCM_CP_PROTECT 0x1 +#define SCM_CP_UNPROTECT 0x0 + +struct cp_lock_msg { + unsigned int start; + unsigned int end; + unsigned int permission_type; + unsigned char lock; +} __attribute__ ((__packed__)); + + +static int ion_cp_protect_mem(unsigned int phy_base, unsigned int size, + unsigned int permission_type) +{ + struct cp_lock_msg cmd; + cmd.start = phy_base; + cmd.end = phy_base + size; + cmd.permission_type = permission_type; + cmd.lock = SCM_CP_PROTECT; + + return scm_call(SCM_SVC_CP, SCM_CP_LOCK_CMD_ID, + &cmd, sizeof(cmd), NULL, 0); +} + +static int ion_cp_unprotect_mem(unsigned int phy_base, unsigned int size, + unsigned int permission_type) +{ + struct cp_lock_msg cmd; + cmd.start = phy_base; + cmd.end = phy_base + size; + cmd.permission_type = permission_type; + cmd.lock = SCM_CP_UNPROTECT; + + return scm_call(SCM_SVC_CP, SCM_CP_LOCK_CMD_ID, + &cmd, sizeof(cmd), NULL, 0); +} diff --git a/drivers/gpu/ion/ion_heap.c b/drivers/gpu/ion/ion_heap.c index 8ce3c1907badca3c695b905831555b860cbf4420..6ea49dbd0f6488c99434d78a0dfd10cd8fab0b0e 100644 --- a/drivers/gpu/ion/ion_heap.c +++ b/drivers/gpu/ion/ion_heap.c @@ -2,6 +2,7 @@ * drivers/gpu/ion/ion_heap.c * * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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 @@ -32,6 +33,12 @@ struct ion_heap *ion_heap_create(struct ion_platform_heap *heap_data) case ION_HEAP_TYPE_CARVEOUT: heap = ion_carveout_heap_create(heap_data); break; + case ION_HEAP_TYPE_IOMMU: + heap = ion_iommu_heap_create(heap_data); + break; + case ION_HEAP_TYPE_CP: + heap = ion_cp_heap_create(heap_data); + break; default: pr_err("%s: Invalid heap type %d\n", __func__, heap_data->type); @@ -65,6 +72,12 @@ void ion_heap_destroy(struct ion_heap *heap) case ION_HEAP_TYPE_CARVEOUT: ion_carveout_heap_destroy(heap); break; + case ION_HEAP_TYPE_IOMMU: + ion_iommu_heap_destroy(heap); + break; + case ION_HEAP_TYPE_CP: + ion_cp_heap_destroy(heap); + break; default: pr_err("%s: Invalid heap type %d\n", __func__, heap->type); diff --git a/drivers/gpu/ion/ion_iommu_heap.c b/drivers/gpu/ion/ion_iommu_heap.c new file mode 100644 index 0000000000000000000000000000000000000000..621144b111889ee495828658904c257fdb55f600 --- /dev/null +++ b/drivers/gpu/ion/ion_iommu_heap.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ion_priv.h" + +#include +#include +#include +#include + +struct ion_iommu_heap { + struct ion_heap heap; + unsigned int has_outer_cache; +}; + +struct ion_iommu_priv_data { + struct page **pages; + int nrpages; + unsigned long size; + struct scatterlist *iommu_sglist; +}; + +static int ion_iommu_heap_allocate(struct ion_heap *heap, + struct ion_buffer *buffer, + unsigned long size, unsigned long align, + unsigned long flags) +{ + int ret, i; + struct ion_iommu_priv_data *data = NULL; + + if (msm_use_iommu()) { + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->size = PFN_ALIGN(size); + data->nrpages = data->size >> PAGE_SHIFT; + data->pages = kzalloc(sizeof(struct page *)*data->nrpages, + GFP_KERNEL); + if (!data->pages) { + ret = -ENOMEM; + goto err1; + } + data->iommu_sglist = vmalloc(sizeof(*data->iommu_sglist) * + data->nrpages); + if (!data->iommu_sglist) { + ret = -ENOMEM; + goto err1; + } + + sg_init_table(data->iommu_sglist, data->nrpages); + + for (i = 0; i < data->nrpages; i++) { + data->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!data->pages[i]) + goto err2; + + sg_set_page(&data->iommu_sglist[i], data->pages[i], + PAGE_SIZE, 0); + } + + + buffer->priv_virt = data; + return 0; + + } else { + return -ENOMEM; + } + + +err2: + vfree(data->iommu_sglist); + data->iommu_sglist = NULL; + + for (i = 0; i < data->nrpages; i++) { + if (data->pages[i]) + __free_page(data->pages[i]); + } + kfree(data->pages); +err1: + kfree(data); + return ret; +} + +static void ion_iommu_heap_free(struct ion_buffer *buffer) +{ + struct ion_iommu_priv_data *data = buffer->priv_virt; + int i; + + if (!data) + return; + + for (i = 0; i < data->nrpages; i++) + __free_page(data->pages[i]); + + vfree(data->iommu_sglist); + data->iommu_sglist = NULL; + + kfree(data->pages); + kfree(data); +} + +void *ion_iommu_heap_map_kernel(struct ion_heap *heap, + struct ion_buffer *buffer, + unsigned long flags) +{ + struct ion_iommu_priv_data *data = buffer->priv_virt; + pgprot_t page_prot = PAGE_KERNEL; + + if (!data) + return NULL; + + if (!ION_IS_CACHED(flags)) + page_prot = pgprot_noncached(page_prot); + + buffer->vaddr = vmap(data->pages, data->nrpages, VM_IOREMAP, page_prot); + + return buffer->vaddr; +} + +void ion_iommu_heap_unmap_kernel(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + if (!buffer->vaddr) + return; + + vunmap(buffer->vaddr); + buffer->vaddr = NULL; +} + +int ion_iommu_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer, + struct vm_area_struct *vma, unsigned long flags) +{ + struct ion_iommu_priv_data *data = buffer->priv_virt; + int i; + unsigned long curr_addr; + if (!data) + return -EINVAL; + + if (!ION_IS_CACHED(flags)) + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + curr_addr = vma->vm_start; + for (i = 0; i < data->nrpages && curr_addr < vma->vm_end; i++) { + if (vm_insert_page(vma, curr_addr, data->pages[i])) { + /* + * This will fail the mmap which will + * clean up the vma space properly. + */ + return -EINVAL; + } + curr_addr += PAGE_SIZE; + } + return 0; +} + +int ion_iommu_heap_map_iommu(struct ion_buffer *buffer, + struct ion_iommu_map *data, + unsigned int domain_num, + unsigned int partition_num, + unsigned long align, + unsigned long iova_length, + unsigned long flags) +{ + struct iommu_domain *domain; + int ret = 0; + unsigned long extra; + struct ion_iommu_priv_data *buffer_data = buffer->priv_virt; + int prot = IOMMU_WRITE | IOMMU_READ; + prot |= ION_IS_CACHED(flags) ? IOMMU_CACHE : 0; + + BUG_ON(!msm_use_iommu()); + + data->mapped_size = iova_length; + extra = iova_length - buffer->size; + + ret = msm_allocate_iova_address(domain_num, partition_num, + data->mapped_size, align, + &data->iova_addr); + + if (!data->iova_addr) + goto out; + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + ret = -ENOMEM; + goto out1; + } + + ret = iommu_map_range(domain, data->iova_addr, + buffer_data->iommu_sglist, buffer->size, prot); + if (ret) { + pr_err("%s: could not map %lx in domain %p\n", + __func__, data->iova_addr, domain); + goto out1; + } + + if (extra) { + unsigned long extra_iova_addr = data->iova_addr + buffer->size; + ret = msm_iommu_map_extra(domain, extra_iova_addr, extra, SZ_4K, + prot); + if (ret) + goto out2; + } + return ret; + +out2: + iommu_unmap_range(domain, data->iova_addr, buffer->size); +out1: + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + buffer->size); + +out: + + return ret; +} + +void ion_iommu_heap_unmap_iommu(struct ion_iommu_map *data) +{ + unsigned int domain_num; + unsigned int partition_num; + struct iommu_domain *domain; + + BUG_ON(!msm_use_iommu()); + + domain_num = iommu_map_domain(data); + partition_num = iommu_map_partition(data); + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + WARN(1, "Could not get domain %d. Corruption?\n", domain_num); + return; + } + + iommu_unmap_range(domain, data->iova_addr, data->mapped_size); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); + + return; +} + +static int ion_iommu_cache_ops(struct ion_heap *heap, struct ion_buffer *buffer, + void *vaddr, unsigned int offset, unsigned int length, + unsigned int cmd) +{ + void (*outer_cache_op)(phys_addr_t, phys_addr_t); + struct ion_iommu_heap *iommu_heap = + container_of(heap, struct ion_iommu_heap, heap); + + switch (cmd) { + case ION_IOC_CLEAN_CACHES: + dmac_clean_range(vaddr, vaddr + length); + outer_cache_op = outer_clean_range; + break; + case ION_IOC_INV_CACHES: + dmac_inv_range(vaddr, vaddr + length); + outer_cache_op = outer_inv_range; + break; + case ION_IOC_CLEAN_INV_CACHES: + dmac_flush_range(vaddr, vaddr + length); + outer_cache_op = outer_flush_range; + break; + default: + return -EINVAL; + } + + if (iommu_heap->has_outer_cache) { + unsigned long pstart; + unsigned int i; + struct ion_iommu_priv_data *data = buffer->priv_virt; + if (!data) + return -ENOMEM; + + for (i = 0; i < data->nrpages; ++i) { + pstart = page_to_phys(data->pages[i]); + outer_cache_op(pstart, pstart + PAGE_SIZE); + } + } + return 0; +} + +static struct scatterlist *ion_iommu_heap_map_dma(struct ion_heap *heap, + struct ion_buffer *buffer) +{ + struct ion_iommu_priv_data *data = buffer->priv_virt; + return data->iommu_sglist; +} + +static void ion_iommu_heap_unmap_dma(struct ion_heap *heap, + struct ion_buffer *buffer) +{ +} + +static struct ion_heap_ops iommu_heap_ops = { + .allocate = ion_iommu_heap_allocate, + .free = ion_iommu_heap_free, + .map_user = ion_iommu_heap_map_user, + .map_kernel = ion_iommu_heap_map_kernel, + .unmap_kernel = ion_iommu_heap_unmap_kernel, + .map_iommu = ion_iommu_heap_map_iommu, + .unmap_iommu = ion_iommu_heap_unmap_iommu, + .cache_op = ion_iommu_cache_ops, + .map_dma = ion_iommu_heap_map_dma, + .unmap_dma = ion_iommu_heap_unmap_dma, +}; + +struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *heap_data) +{ + struct ion_iommu_heap *iommu_heap; + + iommu_heap = kzalloc(sizeof(struct ion_iommu_heap), GFP_KERNEL); + if (!iommu_heap) + return ERR_PTR(-ENOMEM); + + iommu_heap->heap.ops = &iommu_heap_ops; + iommu_heap->heap.type = ION_HEAP_TYPE_IOMMU; + iommu_heap->has_outer_cache = heap_data->has_outer_cache; + + return &iommu_heap->heap; +} + +void ion_iommu_heap_destroy(struct ion_heap *heap) +{ + struct ion_iommu_heap *iommu_heap = + container_of(heap, struct ion_iommu_heap, heap); + + kfree(iommu_heap); + iommu_heap = NULL; +} diff --git a/drivers/gpu/ion/ion_priv.h b/drivers/gpu/ion/ion_priv.h index cf4a960101d4d38434e2a8dc0724db6cfcfc8303..6d636ee294646737c3958f345541045d461f61c4 100644 --- a/drivers/gpu/ion/ion_priv.h +++ b/drivers/gpu/ion/ion_priv.h @@ -2,6 +2,7 @@ * drivers/gpu/ion/ion_priv.h * * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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 +23,56 @@ #include #include #include +#include +#include + +struct ion_mapping; + +struct ion_dma_mapping { + struct kref ref; + struct scatterlist *sglist; +}; + +struct ion_kernel_mapping { + struct kref ref; + void *vaddr; +}; + +enum { + DI_PARTITION_NUM = 0, + DI_DOMAIN_NUM = 1, + DI_MAX, +}; + +/** + * struct ion_iommu_map - represents a mapping of an ion buffer to an iommu + * @iova_addr - iommu virtual address + * @node - rb node to exist in the buffer's tree of iommu mappings + * @domain_info - contains the partition number and domain number + * domain_info[1] = domain number + * domain_info[0] = partition number + * @ref - for reference counting this mapping + * @mapped_size - size of the iova space mapped + * (may not be the same as the buffer size) + * @flags - iommu domain/partition specific flags. + * + * Represents a mapping of one ion buffer to a particular iommu domain + * and address range. There may exist other mappings of this buffer in + * different domains or address ranges. All mappings will have the same + * cacheability and security. + */ +struct ion_iommu_map { + unsigned long iova_addr; + struct rb_node node; + union { + int domain_info[DI_MAX]; + uint64_t key; + }; + struct ion_buffer *buffer; + struct kref ref; + int mapped_size; + unsigned long flags; +}; struct ion_buffer *ion_handle_buffer(struct ion_handle *handle); @@ -41,7 +92,7 @@ struct ion_buffer *ion_handle_buffer(struct ion_handle *handle); * @kmap_cnt: number of times the buffer is mapped to the kernel * @vaddr: the kenrel mapping if kmap_cnt is not zero * @dmap_cnt: number of times the buffer is mapped for dma - * @sg_table: the sg table for the buffer if dmap_cnt is not zero + * @sglist: the scatterlist for the buffer is dmap_cnt is not zero */ struct ion_buffer { struct kref ref; @@ -58,7 +109,11 @@ struct ion_buffer { int kmap_cnt; void *vaddr; int dmap_cnt; - struct sg_table *sg_table; + struct scatterlist *sglist; + int umap_cnt; + unsigned int iommu_map_cnt; + struct rb_root iommu_maps; + int marked; }; /** @@ -72,6 +127,7 @@ struct ion_buffer { * @map_kernel map memory to the kernel * @unmap_kernel unmap memory to the kernel * @map_user map memory to userspace + * @unmap_user unmap memory to userspace */ struct ion_heap_ops { int (*allocate) (struct ion_heap *heap, @@ -80,13 +136,30 @@ struct ion_heap_ops { void (*free) (struct ion_buffer *buffer); int (*phys) (struct ion_heap *heap, struct ion_buffer *buffer, ion_phys_addr_t *addr, size_t *len); - struct sg_table *(*map_dma) (struct ion_heap *heap, + struct scatterlist *(*map_dma) (struct ion_heap *heap, struct ion_buffer *buffer); void (*unmap_dma) (struct ion_heap *heap, struct ion_buffer *buffer); - void * (*map_kernel) (struct ion_heap *heap, struct ion_buffer *buffer); + void * (*map_kernel) (struct ion_heap *heap, struct ion_buffer *buffer, + unsigned long flags); void (*unmap_kernel) (struct ion_heap *heap, struct ion_buffer *buffer); int (*map_user) (struct ion_heap *mapper, struct ion_buffer *buffer, - struct vm_area_struct *vma); + struct vm_area_struct *vma, unsigned long flags); + void (*unmap_user) (struct ion_heap *mapper, struct ion_buffer *buffer); + int (*cache_op)(struct ion_heap *heap, struct ion_buffer *buffer, + void *vaddr, unsigned int offset, + unsigned int length, unsigned int cmd); + int (*map_iommu)(struct ion_buffer *buffer, + struct ion_iommu_map *map_data, + unsigned int domain_num, + unsigned int partition_num, + unsigned long align, + unsigned long iova_length, + unsigned long flags); + void (*unmap_iommu)(struct ion_iommu_map *data); + int (*print_debug)(struct ion_heap *heap, struct seq_file *s, + const struct rb_root *mem_map); + int (*secure_heap)(struct ion_heap *heap); + int (*unsecure_heap)(struct ion_heap *heap); }; /** @@ -114,6 +187,26 @@ struct ion_heap { const char *name; }; +/** + * struct mem_map_data - represents information about the memory map for a heap + * @node: rb node used to store in the tree of mem_map_data + * @addr: start address of memory region. + * @addr: end address of memory region. + * @size: size of memory region + * @client_name: name of the client who owns this buffer. + * + */ +struct mem_map_data { + struct rb_node node; + unsigned long addr; + unsigned long addr_end; + unsigned long size; + const char *client_name; +}; + +#define iommu_map_domain(__m) ((__m)->domain_info[1]) +#define iommu_map_partition(__m) ((__m)->domain_info[0]) + /** * ion_device_create - allocates and returns an ion device * @custom_ioctl: arch specific ioctl function if applicable @@ -155,6 +248,16 @@ void ion_system_contig_heap_destroy(struct ion_heap *); struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *); void ion_carveout_heap_destroy(struct ion_heap *); + +struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *); +void ion_iommu_heap_destroy(struct ion_heap *); + +struct ion_heap *ion_cp_heap_create(struct ion_platform_heap *); +void ion_cp_heap_destroy(struct ion_heap *); + +struct ion_heap *ion_reusable_heap_create(struct ion_platform_heap *); +void ion_reusable_heap_destroy(struct ion_heap *); + /** * kernel api to allocate/free from carveout -- used when carveout is * used to back an architecture specific custom heap @@ -163,10 +266,58 @@ ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap, unsigned long size, unsigned long align); void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr, unsigned long size); + + +struct ion_heap *msm_get_contiguous_heap(void); /** - * The carveout heap returns physical addresses, since 0 may be a valid + * The carveout/cp heap returns physical addresses, since 0 may be a valid * physical address, this is used to indicate allocation failed */ #define ION_CARVEOUT_ALLOCATE_FAIL -1 +#define ION_CP_ALLOCATE_FAIL -1 + +/** + * The reserved heap returns physical addresses, since 0 may be a valid + * physical address, this is used to indicate allocation failed + */ +#define ION_RESERVED_ALLOCATE_FAIL -1 + +/** + * ion_map_fmem_buffer - map fmem allocated memory into the kernel + * @buffer - buffer to map + * @phys_base - physical base of the heap + * @virt_base - virtual base of the heap + * @flags - flags for the heap + * + * Map fmem allocated memory into the kernel address space. This + * is designed to be used by other heaps that need fmem behavior. + * The virtual range must be pre-allocated. + */ +void *ion_map_fmem_buffer(struct ion_buffer *buffer, unsigned long phys_base, + void *virt_base, unsigned long flags); + +/** + * ion_do_cache_op - do cache operations. + * + * @client - pointer to ION client. + * @handle - pointer to buffer handle. + * @uaddr - virtual address to operate on. + * @offset - offset from physical address. + * @len - Length of data to do cache operation on. + * @cmd - Cache operation to perform: + * ION_IOC_CLEAN_CACHES + * ION_IOC_INV_CACHES + * ION_IOC_CLEAN_INV_CACHES + * + * Returns 0 on success + */ +int ion_do_cache_op(struct ion_client *client, struct ion_handle *handle, + void *uaddr, unsigned long offset, unsigned long len, + unsigned int cmd); + +void ion_cp_heap_get_base(struct ion_heap *heap, unsigned long *base, + unsigned long *size); + +void ion_mem_map_show(struct ion_heap *heap); #endif /* _ION_PRIV_H */ diff --git a/drivers/gpu/ion/ion_system_heap.c b/drivers/gpu/ion/ion_system_heap.c index 116e9231a6b49b473c7f9fb3fe38edcc9785269a..08b271bc8b0b0c6c0488479a9bb3ad61dbd63a60 100644 --- a/drivers/gpu/ion/ion_system_heap.c +++ b/drivers/gpu/ion/ion_system_heap.c @@ -2,6 +2,7 @@ * drivers/gpu/ion/ion_system_heap.c * * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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 @@ -20,111 +21,291 @@ #include #include #include +#include +#include +#include #include "ion_priv.h" +#include +#include + +static atomic_t system_heap_allocated; +static atomic_t system_contig_heap_allocated; +static unsigned int system_heap_has_outer_cache; +static unsigned int system_heap_contig_has_outer_cache; static int ion_system_heap_allocate(struct ion_heap *heap, struct ion_buffer *buffer, unsigned long size, unsigned long align, unsigned long flags) { - struct sg_table *table; - struct scatterlist *sg; - int i, j; - int npages = PAGE_ALIGN(size) / PAGE_SIZE; - - table = kmalloc(sizeof(struct sg_table), GFP_KERNEL); - if (!table) + buffer->priv_virt = vmalloc_user(size); + if (!buffer->priv_virt) return -ENOMEM; - i = sg_alloc_table(table, npages, GFP_KERNEL); - if (i) - goto err0; - for_each_sg(table->sgl, sg, table->nents, i) { - struct page *page; - page = alloc_page(GFP_KERNEL); - if (!page) - goto err1; - sg_set_page(sg, page, PAGE_SIZE, 0); - } - buffer->priv_virt = table; + + atomic_add(size, &system_heap_allocated); return 0; -err1: - for_each_sg(table->sgl, sg, i, j) - __free_page(sg_page(sg)); - sg_free_table(table); -err0: - kfree(table); - return -ENOMEM; } void ion_system_heap_free(struct ion_buffer *buffer) { - int i; - struct scatterlist *sg; - struct sg_table *table = buffer->priv_virt; - - for_each_sg(table->sgl, sg, table->nents, i) - __free_page(sg_page(sg)); - if (buffer->sg_table) - sg_free_table(buffer->sg_table); - kfree(buffer->sg_table); + vfree(buffer->priv_virt); + atomic_sub(buffer->size, &system_heap_allocated); } -struct sg_table *ion_system_heap_map_dma(struct ion_heap *heap, - struct ion_buffer *buffer) +struct scatterlist *ion_system_heap_map_dma(struct ion_heap *heap, + struct ion_buffer *buffer) { - return buffer->priv_virt; + struct scatterlist *sglist; + struct page *page; + int i; + int npages = PAGE_ALIGN(buffer->size) / PAGE_SIZE; + void *vaddr = buffer->priv_virt; + + sglist = vmalloc(npages * sizeof(struct scatterlist)); + if (!sglist) + return ERR_PTR(-ENOMEM); + memset(sglist, 0, npages * sizeof(struct scatterlist)); + sg_init_table(sglist, npages); + for (i = 0; i < npages; i++) { + page = vmalloc_to_page(vaddr); + if (!page) + goto end; + sg_set_page(&sglist[i], page, PAGE_SIZE, 0); + vaddr += PAGE_SIZE; + } + /* XXX do cache maintenance for dma? */ + return sglist; +end: + vfree(sglist); + return NULL; } void ion_system_heap_unmap_dma(struct ion_heap *heap, struct ion_buffer *buffer) { - return; + /* XXX undo cache maintenance for dma? */ + if (buffer->sglist) + vfree(buffer->sglist); } void *ion_system_heap_map_kernel(struct ion_heap *heap, - struct ion_buffer *buffer) + struct ion_buffer *buffer, + unsigned long flags) { - struct scatterlist *sg; - int i; - void *vaddr; - struct sg_table *table = buffer->priv_virt; - struct page **pages = kmalloc(sizeof(struct page *) * table->nents, - GFP_KERNEL); - - for_each_sg(table->sgl, sg, table->nents, i) - pages[i] = sg_page(sg); - vaddr = vmap(pages, table->nents, VM_MAP, PAGE_KERNEL); - kfree(pages); - - return vaddr; + if (ION_IS_CACHED(flags)) + return buffer->priv_virt; + else { + pr_err("%s: cannot map system heap uncached\n", __func__); + return ERR_PTR(-EINVAL); + } } void ion_system_heap_unmap_kernel(struct ion_heap *heap, struct ion_buffer *buffer) { - vunmap(buffer->vaddr); +} + +void ion_system_heap_unmap_iommu(struct ion_iommu_map *data) +{ + unsigned int domain_num; + unsigned int partition_num; + struct iommu_domain *domain; + + if (!msm_use_iommu()) + return; + + domain_num = iommu_map_domain(data); + partition_num = iommu_map_partition(data); + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + WARN(1, "Could not get domain %d. Corruption?\n", domain_num); + return; + } + + iommu_unmap_range(domain, data->iova_addr, data->mapped_size); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); + + return; } int ion_system_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer, - struct vm_area_struct *vma) + struct vm_area_struct *vma, unsigned long flags) { - struct sg_table *table = buffer->priv_virt; - unsigned long addr = vma->vm_start; - unsigned long offset = vma->vm_pgoff; - struct scatterlist *sg; - int i; + if (ION_IS_CACHED(flags)) + return remap_vmalloc_range(vma, buffer->priv_virt, + vma->vm_pgoff); + else { + pr_err("%s: cannot map system heap uncached\n", __func__); + return -EINVAL; + } +} + +int ion_system_heap_cache_ops(struct ion_heap *heap, struct ion_buffer *buffer, + void *vaddr, unsigned int offset, unsigned int length, + unsigned int cmd) +{ + void (*outer_cache_op)(phys_addr_t, phys_addr_t); + + switch (cmd) { + case ION_IOC_CLEAN_CACHES: + dmac_clean_range(vaddr, vaddr + length); + outer_cache_op = outer_clean_range; + break; + case ION_IOC_INV_CACHES: + dmac_inv_range(vaddr, vaddr + length); + outer_cache_op = outer_inv_range; + break; + case ION_IOC_CLEAN_INV_CACHES: + dmac_flush_range(vaddr, vaddr + length); + outer_cache_op = outer_flush_range; + break; + default: + return -EINVAL; + } + + if (system_heap_has_outer_cache) { + unsigned long pstart; + void *vend; + void *vtemp; + unsigned long ln = 0; + vend = buffer->priv_virt + buffer->size; + vtemp = buffer->priv_virt + offset; - for_each_sg(table->sgl, sg, table->nents, i) { - if (offset) { - offset--; - continue; + if ((vtemp+length) > vend) { + pr_err("Trying to flush outside of mapped range.\n"); + pr_err("End of mapped range: %p, trying to flush to " + "address %p\n", vend, vtemp+length); + WARN(1, "%s: called with heap name %s, buffer size 0x%x, " + "vaddr 0x%p, offset 0x%x, length: 0x%x\n", + __func__, heap->name, buffer->size, vaddr, + offset, length); + return -EINVAL; + } + + for (; ln < length && vtemp < vend; + vtemp += PAGE_SIZE, ln += PAGE_SIZE) { + struct page *page = vmalloc_to_page(vtemp); + if (!page) { + WARN(1, "Could not find page for virt. address %p\n", + vtemp); + return -EINVAL; + } + pstart = page_to_phys(page); + /* + * If page -> phys is returning NULL, something + * has really gone wrong... + */ + if (!pstart) { + WARN(1, "Could not translate %p to physical address\n", + vtemp); + return -EINVAL; + } + + outer_cache_op(pstart, pstart + PAGE_SIZE); } - vm_insert_page(vma, addr, sg_page(sg)); - addr += PAGE_SIZE; } return 0; } +static int ion_system_print_debug(struct ion_heap *heap, struct seq_file *s, + const struct rb_root *unused) +{ + seq_printf(s, "total bytes currently allocated: %lx\n", + (unsigned long) atomic_read(&system_heap_allocated)); + + return 0; +} + +int ion_system_heap_map_iommu(struct ion_buffer *buffer, + struct ion_iommu_map *data, + unsigned int domain_num, + unsigned int partition_num, + unsigned long align, + unsigned long iova_length, + unsigned long flags) +{ + int ret = 0, i; + struct iommu_domain *domain; + unsigned long extra; + unsigned long extra_iova_addr; + struct page *page; + int npages = buffer->size >> PAGE_SHIFT; + void *vaddr = buffer->priv_virt; + struct scatterlist *sglist = 0; + int prot = IOMMU_WRITE | IOMMU_READ; + prot |= ION_IS_CACHED(flags) ? IOMMU_CACHE : 0; + + if (!ION_IS_CACHED(flags)) + return -EINVAL; + + if (!msm_use_iommu()) + return -EINVAL; + + data->mapped_size = iova_length; + extra = iova_length - buffer->size; + + ret = msm_allocate_iova_address(domain_num, partition_num, + data->mapped_size, align, + &data->iova_addr); + + if (ret) + goto out; + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + ret = -ENOMEM; + goto out1; + } + + + sglist = vmalloc(sizeof(*sglist) * npages); + if (!sglist) { + ret = -ENOMEM; + goto out1; + } + + sg_init_table(sglist, npages); + for (i = 0; i < npages; i++) { + page = vmalloc_to_page(vaddr); + if (!page) + goto out1; + sg_set_page(&sglist[i], page, PAGE_SIZE, 0); + vaddr += PAGE_SIZE; + } + + ret = iommu_map_range(domain, data->iova_addr, sglist, + buffer->size, prot); + + if (ret) { + pr_err("%s: could not map %lx in domain %p\n", + __func__, data->iova_addr, domain); + goto out1; + } + + extra_iova_addr = data->iova_addr + buffer->size; + if (extra) { + ret = msm_iommu_map_extra(domain, extra_iova_addr, extra, SZ_4K, + prot); + if (ret) + goto out2; + } + vfree(sglist); + return ret; + +out2: + iommu_unmap_range(domain, data->iova_addr, buffer->size); +out1: + vfree(sglist); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); +out: + return ret; +} + static struct ion_heap_ops vmalloc_ops = { .allocate = ion_system_heap_allocate, .free = ion_system_heap_free, @@ -133,9 +314,13 @@ static struct ion_heap_ops vmalloc_ops = { .map_kernel = ion_system_heap_map_kernel, .unmap_kernel = ion_system_heap_unmap_kernel, .map_user = ion_system_heap_map_user, + .cache_op = ion_system_heap_cache_ops, + .print_debug = ion_system_print_debug, + .map_iommu = ion_system_heap_map_iommu, + .unmap_iommu = ion_system_heap_unmap_iommu, }; -struct ion_heap *ion_system_heap_create(struct ion_platform_heap *unused) +struct ion_heap *ion_system_heap_create(struct ion_platform_heap *pheap) { struct ion_heap *heap; @@ -144,6 +329,7 @@ struct ion_heap *ion_system_heap_create(struct ion_platform_heap *unused) return ERR_PTR(-ENOMEM); heap->ops = &vmalloc_ops; heap->type = ION_HEAP_TYPE_SYSTEM; + system_heap_has_outer_cache = pheap->has_outer_cache; return heap; } @@ -161,12 +347,14 @@ static int ion_system_contig_heap_allocate(struct ion_heap *heap, buffer->priv_virt = kzalloc(len, GFP_KERNEL); if (!buffer->priv_virt) return -ENOMEM; + atomic_add(len, &system_contig_heap_allocated); return 0; } void ion_system_contig_heap_free(struct ion_buffer *buffer) { kfree(buffer->priv_virt); + atomic_sub(buffer->size, &system_contig_heap_allocated); } static int ion_system_contig_heap_phys(struct ion_heap *heap, @@ -178,34 +366,161 @@ static int ion_system_contig_heap_phys(struct ion_heap *heap, return 0; } -struct sg_table *ion_system_contig_heap_map_dma(struct ion_heap *heap, +struct scatterlist *ion_system_contig_heap_map_dma(struct ion_heap *heap, struct ion_buffer *buffer) { - struct sg_table *table; - int ret; + struct scatterlist *sglist; - table = kzalloc(sizeof(struct sg_table), GFP_KERNEL); - if (!table) + sglist = vmalloc(sizeof(struct scatterlist)); + if (!sglist) return ERR_PTR(-ENOMEM); - ret = sg_alloc_table(table, 1, GFP_KERNEL); - if (ret) { - kfree(table); - return ERR_PTR(ret); - } - sg_set_page(table->sgl, virt_to_page(buffer->priv_virt), buffer->size, - 0); - return table; + sg_init_table(sglist, 1); + sg_set_page(sglist, virt_to_page(buffer->priv_virt), buffer->size, 0); + return sglist; } int ion_system_contig_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer, - struct vm_area_struct *vma) + struct vm_area_struct *vma, + unsigned long flags) { unsigned long pfn = __phys_to_pfn(virt_to_phys(buffer->priv_virt)); - return remap_pfn_range(vma, vma->vm_start, pfn + vma->vm_pgoff, + + if (ION_IS_CACHED(flags)) + return remap_pfn_range(vma, vma->vm_start, pfn + vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot); + else { + pr_err("%s: cannot map system heap uncached\n", __func__); + return -EINVAL; + } +} + +int ion_system_contig_heap_cache_ops(struct ion_heap *heap, + struct ion_buffer *buffer, void *vaddr, + unsigned int offset, unsigned int length, + unsigned int cmd) +{ + void (*outer_cache_op)(phys_addr_t, phys_addr_t); + + switch (cmd) { + case ION_IOC_CLEAN_CACHES: + dmac_clean_range(vaddr, vaddr + length); + outer_cache_op = outer_clean_range; + break; + case ION_IOC_INV_CACHES: + dmac_inv_range(vaddr, vaddr + length); + outer_cache_op = outer_inv_range; + break; + case ION_IOC_CLEAN_INV_CACHES: + dmac_flush_range(vaddr, vaddr + length); + outer_cache_op = outer_flush_range; + break; + default: + return -EINVAL; + } + + if (system_heap_contig_has_outer_cache) { + unsigned long pstart; + + pstart = virt_to_phys(buffer->priv_virt) + offset; + if (!pstart) { + WARN(1, "Could not do virt to phys translation on %p\n", + buffer->priv_virt); + return -EINVAL; + } + + outer_cache_op(pstart, pstart + PAGE_SIZE); + } + + return 0; +} + +static int ion_system_contig_print_debug(struct ion_heap *heap, + struct seq_file *s, + const struct rb_root *unused) +{ + seq_printf(s, "total bytes currently allocated: %lx\n", + (unsigned long) atomic_read(&system_contig_heap_allocated)); + + return 0; +} + +int ion_system_contig_heap_map_iommu(struct ion_buffer *buffer, + struct ion_iommu_map *data, + unsigned int domain_num, + unsigned int partition_num, + unsigned long align, + unsigned long iova_length, + unsigned long flags) +{ + int ret = 0; + struct iommu_domain *domain; + unsigned long extra; + struct scatterlist *sglist = 0; + struct page *page = 0; + int prot = IOMMU_WRITE | IOMMU_READ; + prot |= ION_IS_CACHED(flags) ? IOMMU_CACHE : 0; + + if (!ION_IS_CACHED(flags)) + return -EINVAL; + + if (!msm_use_iommu()) { + data->iova_addr = virt_to_phys(buffer->vaddr); + return 0; + } + + data->mapped_size = iova_length; + extra = iova_length - buffer->size; + + ret = msm_allocate_iova_address(domain_num, partition_num, + data->mapped_size, align, + &data->iova_addr); + + if (ret) + goto out; + + domain = msm_get_iommu_domain(domain_num); + + if (!domain) { + ret = -ENOMEM; + goto out1; + } + page = virt_to_page(buffer->vaddr); + + sglist = vmalloc(sizeof(*sglist)); + if (!sglist) + goto out1; + + sg_init_table(sglist, 1); + sg_set_page(sglist, page, buffer->size, 0); + + ret = iommu_map_range(domain, data->iova_addr, sglist, + buffer->size, prot); + if (ret) { + pr_err("%s: could not map %lx in domain %p\n", + __func__, data->iova_addr, domain); + goto out1; + } + + if (extra) { + unsigned long extra_iova_addr = data->iova_addr + buffer->size; + ret = msm_iommu_map_extra(domain, extra_iova_addr, extra, SZ_4K, + prot); + if (ret) + goto out2; + } + vfree(sglist); + return ret; +out2: + iommu_unmap_range(domain, data->iova_addr, buffer->size); +out1: + vfree(sglist); + msm_free_iova_address(data->iova_addr, domain_num, partition_num, + data->mapped_size); +out: + return ret; } static struct ion_heap_ops kmalloc_ops = { @@ -217,9 +532,13 @@ static struct ion_heap_ops kmalloc_ops = { .map_kernel = ion_system_heap_map_kernel, .unmap_kernel = ion_system_heap_unmap_kernel, .map_user = ion_system_contig_heap_map_user, + .cache_op = ion_system_contig_heap_cache_ops, + .print_debug = ion_system_contig_print_debug, + .map_iommu = ion_system_contig_heap_map_iommu, + .unmap_iommu = ion_system_heap_unmap_iommu, }; -struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *unused) +struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *pheap) { struct ion_heap *heap; @@ -228,6 +547,7 @@ struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *unused) return ERR_PTR(-ENOMEM); heap->ops = &kmalloc_ops; heap->type = ION_HEAP_TYPE_SYSTEM_CONTIG; + system_heap_contig_has_outer_cache = pheap->has_outer_cache; return heap; } diff --git a/drivers/gpu/ion/msm/Makefile b/drivers/gpu/ion/msm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..bedd8d22779e10df8d4b6373c984c31280097dd9 --- /dev/null +++ b/drivers/gpu/ion/msm/Makefile @@ -0,0 +1 @@ +obj-y += msm_ion.o diff --git a/drivers/gpu/ion/msm/msm_ion.c b/drivers/gpu/ion/msm/msm_ion.c new file mode 100644 index 0000000000000000000000000000000000000000..f6a4cf49a6c4b22095dfe9674c069104ed479c4a --- /dev/null +++ b/drivers/gpu/ion/msm/msm_ion.c @@ -0,0 +1,347 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../ion_priv.h" + +static struct ion_device *idev; +static int num_heaps; +static struct ion_heap **heaps; + +struct ion_client *msm_ion_client_create(unsigned int heap_mask, + const char *name) +{ + return ion_client_create(idev, heap_mask, name); +} +EXPORT_SYMBOL(msm_ion_client_create); + +int msm_ion_secure_heap(int heap_id) +{ + return ion_secure_heap(idev, heap_id); +} +EXPORT_SYMBOL(msm_ion_secure_heap); + +int msm_ion_unsecure_heap(int heap_id) +{ + return ion_unsecure_heap(idev, heap_id); +} +EXPORT_SYMBOL(msm_ion_unsecure_heap); + +int msm_ion_do_cache_op(struct ion_client *client, struct ion_handle *handle, + void *vaddr, unsigned long len, unsigned int cmd) +{ + return ion_do_cache_op(client, handle, vaddr, 0, len, cmd); +} +EXPORT_SYMBOL(msm_ion_do_cache_op); + +static unsigned long msm_ion_get_base(unsigned long size, int memory_type, + unsigned int align) +{ + switch (memory_type) { + case ION_EBI_TYPE: + return allocate_contiguous_ebi_nomap(size, align); + break; + case ION_SMI_TYPE: + return allocate_contiguous_memory_nomap(size, MEMTYPE_SMI, + align); + break; + default: + pr_err("%s: Unknown memory type %d\n", __func__, memory_type); + return 0; + } +} + +static struct ion_platform_heap *find_heap(const struct ion_platform_heap + heap_data[], + unsigned int nr_heaps, + int heap_id) +{ + unsigned int i; + for (i = 0; i < nr_heaps; ++i) { + const struct ion_platform_heap *heap = &heap_data[i]; + if (heap->id == heap_id) + return (struct ion_platform_heap *) heap; + } + return 0; +} + +static void ion_set_base_address(struct ion_platform_heap *heap, + struct ion_platform_heap *shared_heap, + struct ion_co_heap_pdata *co_heap_data, + struct ion_cp_heap_pdata *cp_data) +{ + if (cp_data->reusable) { + const struct fmem_data *fmem_info = fmem_get_info(); + + if (!fmem_info) { + pr_err("fmem info pointer NULL!\n"); + BUG(); + } + + heap->base = fmem_info->phys - fmem_info->reserved_size_low; + cp_data->virt_addr = fmem_info->virt; + pr_info("ION heap %s using FMEM\n", shared_heap->name); + } else { + heap->base = msm_ion_get_base(heap->size + shared_heap->size, + shared_heap->memory_type, + co_heap_data->align); + } + if (heap->base) { + shared_heap->base = heap->base + heap->size; + cp_data->secure_base = heap->base; + cp_data->secure_size = heap->size + shared_heap->size; + } else { + pr_err("%s: could not get memory for heap %s (id %x)\n", + __func__, heap->name, heap->id); + } +} + +static void allocate_co_memory(struct ion_platform_heap *heap, + struct ion_platform_heap heap_data[], + unsigned int nr_heaps) +{ + struct ion_co_heap_pdata *co_heap_data = + (struct ion_co_heap_pdata *) heap->extra_data; + + if (co_heap_data->adjacent_mem_id != INVALID_HEAP_ID) { + struct ion_platform_heap *shared_heap = + find_heap(heap_data, nr_heaps, + co_heap_data->adjacent_mem_id); + if (shared_heap) { + struct ion_cp_heap_pdata *cp_data = + (struct ion_cp_heap_pdata *) shared_heap->extra_data; + if (cp_data->fixed_position == FIXED_MIDDLE) { + const struct fmem_data *fmem_info = + fmem_get_info(); + + if (!fmem_info) { + pr_err("fmem info pointer NULL!\n"); + BUG(); + } + + cp_data->virt_addr = fmem_info->virt; + if (!cp_data->secure_base) { + cp_data->secure_base = heap->base; + cp_data->secure_size = + heap->size + shared_heap->size; + } + } else if (!heap->base) { + ion_set_base_address(heap, shared_heap, + co_heap_data, cp_data); + } + } + } +} + +/* Fixup heaps in board file to support two heaps being adjacent to each other. + * A flag (adjacent_mem_id) in the platform data tells us that the heap phy + * memory location must be adjacent to the specified heap. We do this by + * carving out memory for both heaps and then splitting up the memory to the + * two heaps. The heap specifying the "adjacent_mem_id" get the base of the + * memory while heap specified in "adjacent_mem_id" get base+size as its + * base address. + * Note: Modifies platform data and allocates memory. + */ +static void msm_ion_heap_fixup(struct ion_platform_heap heap_data[], + unsigned int nr_heaps) +{ + unsigned int i; + + for (i = 0; i < nr_heaps; i++) { + struct ion_platform_heap *heap = &heap_data[i]; + if (heap->type == ION_HEAP_TYPE_CARVEOUT) { + if (heap->extra_data) + allocate_co_memory(heap, heap_data, nr_heaps); + } + } +} + +static void msm_ion_allocate(struct ion_platform_heap *heap) +{ + + if (!heap->base && heap->extra_data) { + unsigned int align = 0; + switch (heap->type) { + case ION_HEAP_TYPE_CARVEOUT: + align = + ((struct ion_co_heap_pdata *) heap->extra_data)->align; + break; + case ION_HEAP_TYPE_CP: + { + struct ion_cp_heap_pdata *data = + (struct ion_cp_heap_pdata *) + heap->extra_data; + if (data->reusable) { + const struct fmem_data *fmem_info = + fmem_get_info(); + heap->base = fmem_info->phys; + data->virt_addr = fmem_info->virt; + pr_info("ION heap %s using FMEM\n", heap->name); + } else if (data->mem_is_fmem) { + const struct fmem_data *fmem_info = + fmem_get_info(); + heap->base = fmem_info->phys + fmem_info->size; + } + align = data->align; + break; + } + default: + break; + } + if (align && !heap->base) { + heap->base = msm_ion_get_base(heap->size, + heap->memory_type, + align); + if (!heap->base) + pr_err("%s: could not get memory for heap %s " + "(id %x)\n", __func__, heap->name, heap->id); + } + } +} + +static int is_heap_overlapping(const struct ion_platform_heap *heap1, + const struct ion_platform_heap *heap2) +{ + unsigned long heap1_base = heap1->base; + unsigned long heap2_base = heap2->base; + unsigned long heap1_end = heap1->base + heap1->size - 1; + unsigned long heap2_end = heap2->base + heap2->size - 1; + + if (heap1_base == heap2_base) + return 1; + if (heap1_base < heap2_base && heap1_end >= heap2_base) + return 1; + if (heap2_base < heap1_base && heap2_end >= heap1_base) + return 1; + return 0; +} + +static void check_for_heap_overlap(const struct ion_platform_heap heap_list[], + unsigned long nheaps) +{ + unsigned long i; + unsigned long j; + + for (i = 0; i < nheaps; ++i) { + const struct ion_platform_heap *heap1 = &heap_list[i]; + if (!heap1->base) + continue; + for (j = i + 1; j < nheaps; ++j) { + const struct ion_platform_heap *heap2 = &heap_list[j]; + if (!heap2->base) + continue; + if (is_heap_overlapping(heap1, heap2)) { + panic("Memory in heap %s overlaps with heap %s\n", + heap1->name, heap2->name); + } + } + } +} + +static int msm_ion_probe(struct platform_device *pdev) +{ + struct ion_platform_data *pdata = pdev->dev.platform_data; + int err; + int i; + + num_heaps = pdata->nr; + + heaps = kcalloc(pdata->nr, sizeof(struct ion_heap *), GFP_KERNEL); + + if (!heaps) { + err = -ENOMEM; + goto out; + } + + idev = ion_device_create(NULL); + if (IS_ERR_OR_NULL(idev)) { + err = PTR_ERR(idev); + goto freeheaps; + } + + msm_ion_heap_fixup(pdata->heaps, num_heaps); + + /* create the heaps as specified in the board file */ + for (i = 0; i < num_heaps; i++) { + struct ion_platform_heap *heap_data = &pdata->heaps[i]; + msm_ion_allocate(heap_data); + + heap_data->has_outer_cache = pdata->has_outer_cache; + heaps[i] = ion_heap_create(heap_data); + if (IS_ERR_OR_NULL(heaps[i])) { + heaps[i] = 0; + continue; + } else { + if (heap_data->size) + pr_info("ION heap %s created at %lx " + "with size %x\n", heap_data->name, + heap_data->base, + heap_data->size); + else + pr_info("ION heap %s created\n", + heap_data->name); + } + + ion_device_add_heap(idev, heaps[i]); + } + + check_for_heap_overlap(pdata->heaps, num_heaps); + platform_set_drvdata(pdev, idev); + return 0; + +freeheaps: + kfree(heaps); +out: + return err; +} + +static int msm_ion_remove(struct platform_device *pdev) +{ + struct ion_device *idev = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < num_heaps; i++) + ion_heap_destroy(heaps[i]); + + ion_device_destroy(idev); + kfree(heaps); + return 0; +} + +static struct platform_driver msm_ion_driver = { + .probe = msm_ion_probe, + .remove = msm_ion_remove, + .driver = { .name = "ion-msm" } +}; + +static int __init msm_ion_init(void) +{ + return platform_driver_register(&msm_ion_driver); +} + +static void __exit msm_ion_exit(void) +{ + platform_driver_unregister(&msm_ion_driver); +} + +subsys_initcall(msm_ion_init); +module_exit(msm_ion_exit); + diff --git a/drivers/gpu/msm/Kconfig b/drivers/gpu/msm/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..ba63fbcbbb408210d905eed2591b40930cb6c7de --- /dev/null +++ b/drivers/gpu/msm/Kconfig @@ -0,0 +1,98 @@ +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 KGSL_PER_PROCESS_PAGE_TABLE + bool "Enable Per Process page tables for the KGSL driver" + default n + depends on !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 + ---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 + +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 0000000000000000000000000000000000000000..6cdb5f194ddf78ce72bcf51ad1c25fde6343b1df --- /dev/null +++ b/drivers/gpu/msm/Makefile @@ -0,0 +1,46 @@ +ccflags-y := -Iinclude/drm -Idrivers/gpu/msm + +msm_kgsl_core-y = \ + kgsl.o \ + kgsl_trace.o \ + kgsl_sharedmem.o \ + kgsl_pwrctrl.o \ + kgsl_pwrscale.o \ + kgsl_mmu.o \ + kgsl_gpummu.o \ + kgsl_iommu.o \ + kgsl_snapshot.o + +msm_kgsl_core-$(CONFIG_DEBUG_FS) += kgsl_debugfs.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_kgsl_core-$(CONFIG_MSM_SLEEP_STATS_DEVICE) += kgsl_pwrscale_idlestats.o +msm_kgsl_core-$(CONFIG_MSM_DCVS) += kgsl_pwrscale_msm.o + +msm_adreno-y += \ + adreno_ringbuffer.o \ + adreno_drawctxt.o \ + adreno_postmortem.o \ + adreno_snapshot.o \ + adreno_a2xx.o \ + adreno_a2xx_trace.o \ + adreno_a2xx_snapshot.o \ + adreno_a3xx.o \ + adreno_a3xx_trace.o \ + adreno_a3xx_snapshot.o \ + adreno.o + +msm_adreno-$(CONFIG_DEBUG_FS) += adreno_debugfs.o + +msm_z180-y += \ + z180.o \ + z180_trace.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/a2xx_reg.h b/drivers/gpu/msm/a2xx_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..41cb601800835d907f7a1eb25767e816dd86a9f5 --- /dev/null +++ b/drivers/gpu/msm/a2xx_reg.h @@ -0,0 +1,438 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ADDRESS_MASK 0x0001fffc +#define RBBM_READ_ERROR_REQUESTER (1<<30) +#define RBBM_READ_ERROR_ERROR (1<<31) + +#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_RAM_RADDR 0x01F9 +#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_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_FLOW_CONTROL 0x0D01 +#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_COHER_BASE_PM4 0xA2A +#define REG_COHER_STATUS_PM4 0xA2B +#define REG_COHER_SIZE_PM4 0xA29 + +/*registers added in adreno220*/ +#define REG_A220_PC_INDX_OFFSET REG_VGT_INDX_OFFSET +#define REG_A220_PC_VERTEX_REUSE_BLOCK_CNTL REG_VGT_VERTEX_REUSE_BLOCK_CNTL +#define REG_A220_PC_MAX_VTX_INDX REG_VGT_MAX_VTX_INDX +#define REG_A220_RB_LRZ_VSC_CONTROL 0x2209 +#define REG_A220_GRAS_CONTROL 0x2210 +#define REG_A220_VSC_BIN_SIZE 0x0C01 +#define REG_A220_VSC_PIPE_DATA_LENGTH_7 0x0C1D + +/*registers added in adreno225*/ +#define REG_A225_RB_COLOR_INFO3 0x2005 +#define REG_A225_PC_MULTI_PRIM_IB_RESET_INDX 0x2103 +#define REG_A225_GRAS_UCP0X 0x2340 +#define REG_A225_GRAS_UCP5W 0x2357 +#define REG_A225_GRAS_UCP_ENABLED 0x2360 + +/* Debug registers used by snapshot */ +#define REG_PA_SU_DEBUG_CNTL 0x0C80 +#define REG_PA_SU_DEBUG_DATA 0x0C81 +#define REG_RB_DEBUG_CNTL 0x0F26 +#define REG_RB_DEBUG_DATA 0x0F27 +#define REG_PC_DEBUG_CNTL 0x0C38 +#define REG_PC_DEBUG_DATA 0x0C39 +#define REG_GRAS_DEBUG_CNTL 0x0C80 +#define REG_GRAS_DEBUG_DATA 0x0C81 +#define REG_SQ_DEBUG_MISC 0x0D05 +#define REG_SQ_DEBUG_INPUT_FSM 0x0DAE +#define REG_SQ_DEBUG_CONST_MGR_FSM 0x0DAF +#define REG_SQ_DEBUG_EXP_ALLOC 0x0DB3 +#define REG_SQ_DEBUG_FSM_ALU_0 0x0DB1 +#define REG_SQ_DEBUG_FSM_ALU_1 0x0DB2 +#define REG_SQ_DEBUG_PTR_BUFF 0x0DB4 +#define REG_SQ_DEBUG_GPR_VTX 0x0DB5 +#define REG_SQ_DEBUG_GPR_PIX 0x0DB6 +#define REG_SQ_DEBUG_TB_STATUS_SEL 0x0DB7 +#define REG_SQ_DEBUG_VTX_TB_0 0x0DB8 +#define REG_SQ_DEBUG_VTX_TB_1 0x0DB9 +#define REG_SQ_DEBUG_VTX_TB_STATE_MEM 0x0DBB +#define REG_SQ_DEBUG_TP_FSM 0x0DB0 +#define REG_SQ_DEBUG_VTX_TB_STATUS_REG 0x0DBA +#define REG_SQ_DEBUG_PIX_TB_0 0x0DBC +#define REG_SQ_DEBUG_PIX_TB_STATUS_REG_0 0x0DBD +#define REG_SQ_DEBUG_PIX_TB_STATUS_REG_1 0x0DBE +#define REG_SQ_DEBUG_PIX_TB_STATUS_REG_2 0x0DBF +#define REG_SQ_DEBUG_PIX_TB_STATUS_REG_3 0x0DC0 +#define REG_SQ_DEBUG_PIX_TB_STATE_MEM 0x0DC1 +#define REG_SQ_DEBUG_MISC_0 0x2309 +#define REG_SQ_DEBUG_MISC_1 0x230A + +#endif /* __A200_REG_H */ diff --git a/drivers/gpu/msm/a3xx_reg.h b/drivers/gpu/msm/a3xx_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..35af06e6176b324be1dacb07ef948dab8fe8b444 --- /dev/null +++ b/drivers/gpu/msm/a3xx_reg.h @@ -0,0 +1,514 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _A300_REG_H +#define _A300_REG_H + +/* Interrupt bit positions within RBBM_INT_0 */ + +#define A3XX_INT_RBBM_GPU_IDLE 0 +#define A3XX_INT_RBBM_AHB_ERROR 1 +#define A3XX_INT_RBBM_REG_TIMEOUT 2 +#define A3XX_INT_RBBM_ME_MS_TIMEOUT 3 +#define A3XX_INT_RBBM_PFP_MS_TIMEOUT 4 +#define A3XX_INT_RBBM_ATB_BUS_OVERFLOW 5 +#define A3XX_INT_VFD_ERROR 6 +#define A3XX_INT_CP_SW_INT 7 +#define A3XX_INT_CP_T0_PACKET_IN_IB 8 +#define A3XX_INT_CP_OPCODE_ERROR 9 +#define A3XX_INT_CP_RESERVED_BIT_ERROR 10 +#define A3XX_INT_CP_HW_FAULT 11 +#define A3XX_INT_CP_DMA 12 +#define A3XX_INT_CP_IB2_INT 13 +#define A3XX_INT_CP_IB1_INT 14 +#define A3XX_INT_CP_RB_INT 15 +#define A3XX_INT_CP_REG_PROTECT_FAULT 16 +#define A3XX_INT_CP_RB_DONE_TS 17 +#define A3XX_INT_CP_VS_DONE_TS 18 +#define A3XX_INT_CP_PS_DONE_TS 19 +#define A3XX_INT_CACHE_FLUSH_TS 20 +#define A3XX_INT_CP_AHB_ERROR_HALT 21 +#define A3XX_INT_MISC_HANG_DETECT 24 +#define A3XX_INT_UCHE_OOB_ACCESS 25 + +/* Register definitions */ + +#define A3XX_RBBM_HW_VERSION 0x000 +#define A3XX_RBBM_HW_RELEASE 0x001 +#define A3XX_RBBM_HW_CONFIGURATION 0x002 +#define A3XX_RBBM_SP_HYST_CNT 0x012 +#define A3XX_RBBM_SW_RESET_CMD 0x018 +#define A3XX_RBBM_AHB_CTL0 0x020 +#define A3XX_RBBM_AHB_CTL1 0x021 +#define A3XX_RBBM_AHB_CMD 0x022 +#define A3XX_RBBM_AHB_ERROR_STATUS 0x027 +#define A3XX_RBBM_GPR0_CTL 0x02E +/* This the same register as on A2XX, just in a different place */ +#define A3XX_RBBM_STATUS 0x030 +#define A3XX_RBBM_WAIT_IDLE_CLOCKS_CTL 0x33 +#define A3XX_RBBM_INTERFACE_HANG_INT_CTL 0x50 +#define A3XX_RBBM_INTERFACE_HANG_MASK_CTL0 0x51 +#define A3XX_RBBM_INTERFACE_HANG_MASK_CTL1 0x54 +#define A3XX_RBBM_INTERFACE_HANG_MASK_CTL2 0x57 +#define A3XX_RBBM_INTERFACE_HANG_MASK_CTL3 0x5A +#define A3XX_RBBM_INT_CLEAR_CMD 0x061 +#define A3XX_RBBM_INT_0_MASK 0x063 +#define A3XX_RBBM_INT_0_STATUS 0x064 +#define A3XX_RBBM_GPU_BUSY_MASKED 0x88 +#define A3XX_RBBM_RBBM_CTL 0x100 +#define A3XX_RBBM_RBBM_CTL 0x100 +#define A3XX_RBBM_PERFCTR_PWR_1_LO 0x0EC +#define A3XX_RBBM_PERFCTR_PWR_1_HI 0x0ED +#define A3XX_RBBM_DEBUG_BUS_CTL 0x111 +#define A3XX_RBBM_DEBUG_BUS_DATA_STATUS 0x112 +/* Following two are same as on A2XX, just in a different place */ +#define A3XX_CP_PFP_UCODE_ADDR 0x1C9 +#define A3XX_CP_PFP_UCODE_DATA 0x1CA +#define A3XX_CP_ROQ_ADDR 0x1CC +#define A3XX_CP_ROQ_DATA 0x1CD +#define A3XX_CP_MEQ_ADDR 0x1DA +#define A3XX_CP_MEQ_DATA 0x1DB +#define A3XX_CP_HW_FAULT 0x45C +#define A3XX_CP_AHB_FAULT 0x54D +#define A3XX_CP_PROTECT_CTRL 0x45E +#define A3XX_CP_PROTECT_STATUS 0x45F +#define A3XX_CP_PROTECT_REG_0 0x460 +#define A3XX_CP_PROTECT_REG_1 0x461 +#define A3XX_CP_PROTECT_REG_2 0x462 +#define A3XX_CP_PROTECT_REG_3 0x463 +#define A3XX_CP_PROTECT_REG_4 0x464 +#define A3XX_CP_PROTECT_REG_5 0x465 +#define A3XX_CP_PROTECT_REG_6 0x466 +#define A3XX_CP_PROTECT_REG_7 0x467 +#define A3XX_CP_PROTECT_REG_8 0x468 +#define A3XX_CP_PROTECT_REG_9 0x469 +#define A3XX_CP_PROTECT_REG_A 0x46A +#define A3XX_CP_PROTECT_REG_B 0x46B +#define A3XX_CP_PROTECT_REG_C 0x46C +#define A3XX_CP_PROTECT_REG_D 0x46D +#define A3XX_CP_PROTECT_REG_E 0x46E +#define A3XX_CP_PROTECT_REG_F 0x46F +#define A3XX_CP_SCRATCH_REG2 0x57A +#define A3XX_CP_SCRATCH_REG3 0x57B +#define A3XX_VSC_BIN_SIZE 0xC01 +#define A3XX_VSC_SIZE_ADDRESS 0xC02 +#define A3XX_VSC_PIPE_CONFIG_0 0xC06 +#define A3XX_VSC_PIPE_DATA_ADDRESS_0 0xC07 +#define A3XX_VSC_PIPE_DATA_LENGTH_0 0xC08 +#define A3XX_VSC_PIPE_CONFIG_1 0xC09 +#define A3XX_VSC_PIPE_DATA_ADDRESS_1 0xC0A +#define A3XX_VSC_PIPE_DATA_LENGTH_1 0xC0B +#define A3XX_VSC_PIPE_CONFIG_2 0xC0C +#define A3XX_VSC_PIPE_DATA_ADDRESS_2 0xC0D +#define A3XX_VSC_PIPE_DATA_LENGTH_2 0xC0E +#define A3XX_VSC_PIPE_CONFIG_3 0xC0F +#define A3XX_VSC_PIPE_DATA_ADDRESS_3 0xC10 +#define A3XX_VSC_PIPE_DATA_LENGTH_3 0xC11 +#define A3XX_VSC_PIPE_CONFIG_4 0xC12 +#define A3XX_VSC_PIPE_DATA_ADDRESS_4 0xC13 +#define A3XX_VSC_PIPE_DATA_LENGTH_4 0xC14 +#define A3XX_VSC_PIPE_CONFIG_5 0xC15 +#define A3XX_VSC_PIPE_DATA_ADDRESS_5 0xC16 +#define A3XX_VSC_PIPE_DATA_LENGTH_5 0xC17 +#define A3XX_VSC_PIPE_CONFIG_6 0xC18 +#define A3XX_VSC_PIPE_DATA_ADDRESS_6 0xC19 +#define A3XX_VSC_PIPE_DATA_LENGTH_6 0xC1A +#define A3XX_VSC_PIPE_CONFIG_7 0xC1B +#define A3XX_VSC_PIPE_DATA_ADDRESS_7 0xC1C +#define A3XX_VSC_PIPE_DATA_LENGTH_7 0xC1D +#define A3XX_GRAS_CL_USER_PLANE_X0 0xCA0 +#define A3XX_GRAS_CL_USER_PLANE_Y0 0xCA1 +#define A3XX_GRAS_CL_USER_PLANE_Z0 0xCA2 +#define A3XX_GRAS_CL_USER_PLANE_W0 0xCA3 +#define A3XX_GRAS_CL_USER_PLANE_X1 0xCA4 +#define A3XX_GRAS_CL_USER_PLANE_Y1 0xCA5 +#define A3XX_GRAS_CL_USER_PLANE_Z1 0xCA6 +#define A3XX_GRAS_CL_USER_PLANE_W1 0xCA7 +#define A3XX_GRAS_CL_USER_PLANE_X2 0xCA8 +#define A3XX_GRAS_CL_USER_PLANE_Y2 0xCA9 +#define A3XX_GRAS_CL_USER_PLANE_Z2 0xCAA +#define A3XX_GRAS_CL_USER_PLANE_W2 0xCAB +#define A3XX_GRAS_CL_USER_PLANE_X3 0xCAC +#define A3XX_GRAS_CL_USER_PLANE_Y3 0xCAD +#define A3XX_GRAS_CL_USER_PLANE_Z3 0xCAE +#define A3XX_GRAS_CL_USER_PLANE_W3 0xCAF +#define A3XX_GRAS_CL_USER_PLANE_X4 0xCB0 +#define A3XX_GRAS_CL_USER_PLANE_Y4 0xCB1 +#define A3XX_GRAS_CL_USER_PLANE_Z4 0xCB2 +#define A3XX_GRAS_CL_USER_PLANE_W4 0xCB3 +#define A3XX_GRAS_CL_USER_PLANE_X5 0xCB4 +#define A3XX_GRAS_CL_USER_PLANE_Y5 0xCB5 +#define A3XX_GRAS_CL_USER_PLANE_Z5 0xCB6 +#define A3XX_GRAS_CL_USER_PLANE_W5 0xCB7 +#define A3XX_VPC_VPC_DEBUG_RAM_SEL 0xE61 +#define A3XX_VPC_VPC_DEBUG_RAM_READ 0xE62 +#define A3XX_UCHE_CACHE_INVALIDATE0_REG 0xEA0 +#define A3XX_GRAS_CL_CLIP_CNTL 0x2040 +#define A3XX_GRAS_CL_GB_CLIP_ADJ 0x2044 +#define A3XX_GRAS_CL_VPORT_XOFFSET 0x2048 +#define A3XX_GRAS_CL_VPORT_ZOFFSET 0x204C +#define A3XX_GRAS_CL_VPORT_ZSCALE 0x204D +#define A3XX_GRAS_SU_POINT_MINMAX 0x2068 +#define A3XX_GRAS_SU_POINT_SIZE 0x2069 +#define A3XX_GRAS_SU_POLY_OFFSET_SCALE 0x206C +#define A3XX_GRAS_SU_POLY_OFFSET_OFFSET 0x206D +#define A3XX_GRAS_SU_MODE_CONTROL 0x2070 +#define A3XX_GRAS_SC_CONTROL 0x2072 +#define A3XX_GRAS_SC_SCREEN_SCISSOR_TL 0x2074 +#define A3XX_GRAS_SC_SCREEN_SCISSOR_BR 0x2075 +#define A3XX_GRAS_SC_WINDOW_SCISSOR_TL 0x2079 +#define A3XX_GRAS_SC_WINDOW_SCISSOR_BR 0x207A +#define A3XX_RB_MODE_CONTROL 0x20C0 +#define A3XX_RB_RENDER_CONTROL 0x20C1 +#define A3XX_RB_MSAA_CONTROL 0x20C2 +#define A3XX_RB_MRT_CONTROL0 0x20C4 +#define A3XX_RB_MRT_BUF_INFO0 0x20C5 +#define A3XX_RB_MRT_BLEND_CONTROL0 0x20C7 +#define A3XX_RB_MRT_BLEND_CONTROL1 0x20CB +#define A3XX_RB_MRT_BLEND_CONTROL2 0x20CF +#define A3XX_RB_MRT_BLEND_CONTROL3 0x20D3 +#define A3XX_RB_BLEND_RED 0x20E4 +#define A3XX_RB_COPY_CONTROL 0x20EC +#define A3XX_RB_COPY_DEST_INFO 0x20EF +#define A3XX_RB_DEPTH_CONTROL 0x2100 +#define A3XX_RB_STENCIL_CONTROL 0x2104 +#define A3XX_PC_VSTREAM_CONTROL 0x21E4 +#define A3XX_PC_VERTEX_REUSE_BLOCK_CNTL 0x21EA +#define A3XX_PC_PRIM_VTX_CNTL 0x21EC +#define A3XX_PC_RESTART_INDEX 0x21ED +#define A3XX_HLSQ_CONTROL_0_REG 0x2200 +#define A3XX_HLSQ_VS_CONTROL_REG 0x2204 +#define A3XX_HLSQ_CONST_FSPRESV_RANGE_REG 0x2207 +#define A3XX_HLSQ_CL_NDRANGE_0_REG 0x220A +#define A3XX_HLSQ_CL_NDRANGE_2_REG 0x220C +#define A3XX_HLSQ_CL_CONTROL_0_REG 0x2211 +#define A3XX_HLSQ_CL_CONTROL_1_REG 0x2212 +#define A3XX_HLSQ_CL_KERNEL_CONST_REG 0x2214 +#define A3XX_HLSQ_CL_KERNEL_GROUP_X_REG 0x2215 +#define A3XX_HLSQ_CL_KERNEL_GROUP_Z_REG 0x2217 +#define A3XX_HLSQ_CL_WG_OFFSET_REG 0x221A +#define A3XX_VFD_CONTROL_0 0x2240 +#define A3XX_VFD_INDEX_MIN 0x2242 +#define A3XX_VFD_INDEX_MAX 0x2243 +#define A3XX_VFD_FETCH_INSTR_0_0 0x2246 +#define A3XX_VFD_FETCH_INSTR_0_4 0x224E +#define A3XX_VFD_FETCH_INSTR_1_F 0x2265 +#define A3XX_VFD_DECODE_INSTR_0 0x2266 +#define A3XX_VFD_VS_THREADING_THRESHOLD 0x227E +#define A3XX_VPC_ATTR 0x2280 +#define A3XX_VPC_VARY_CYLWRAP_ENABLE_1 0x228B +#define A3XX_SP_SP_CTRL_REG 0x22C0 +#define A3XX_SP_VS_CTRL_REG0 0x22C4 +#define A3XX_SP_VS_CTRL_REG1 0x22C5 +#define A3XX_SP_VS_PARAM_REG 0x22C6 +#define A3XX_SP_VS_OUT_REG_7 0x22CE +#define A3XX_SP_VS_VPC_DST_REG_0 0x22D0 +#define A3XX_SP_VS_OBJ_OFFSET_REG 0x22D4 +#define A3XX_SP_VS_PVT_MEM_ADDR_REG 0x22D7 +#define A3XX_SP_VS_PVT_MEM_SIZE_REG 0x22D8 +#define A3XX_SP_VS_LENGTH_REG 0x22DF +#define A3XX_SP_FS_CTRL_REG0 0x22E0 +#define A3XX_SP_FS_CTRL_REG1 0x22E1 +#define A3XX_SP_FS_OBJ_OFFSET_REG 0x22E2 +#define A3XX_SP_FS_PVT_MEM_ADDR_REG 0x22E5 +#define A3XX_SP_FS_PVT_MEM_SIZE_REG 0x22E6 +#define A3XX_SP_FS_FLAT_SHAD_MODE_REG_0 0x22E8 +#define A3XX_SP_FS_FLAT_SHAD_MODE_REG_1 0x22E9 +#define A3XX_SP_FS_OUTPUT_REG 0x22EC +#define A3XX_SP_FS_MRT_REG_0 0x22F0 +#define A3XX_SP_FS_IMAGE_OUTPUT_REG_0 0x22F4 +#define A3XX_SP_FS_IMAGE_OUTPUT_REG_3 0x22F7 +#define A3XX_SP_FS_LENGTH_REG 0x22FF +#define A3XX_TPL1_TP_VS_TEX_OFFSET 0x2340 +#define A3XX_TPL1_TP_FS_TEX_OFFSET 0x2342 +#define A3XX_TPL1_TP_FS_BORDER_COLOR_BASE_ADDR 0x2343 +#define A3XX_VBIF_FIXED_SORT_EN 0x300C +#define A3XX_VBIF_FIXED_SORT_SEL0 0x300D +#define A3XX_VBIF_FIXED_SORT_SEL1 0x300E +#define A3XX_VBIF_ABIT_SORT 0x301C +#define A3XX_VBIF_ABIT_SORT_CONF 0x301D +#define A3XX_VBIF_GATE_OFF_WRREQ_EN 0x302A +#define A3XX_VBIF_IN_RD_LIM_CONF0 0x302C +#define A3XX_VBIF_IN_RD_LIM_CONF1 0x302D +#define A3XX_VBIF_IN_WR_LIM_CONF0 0x3030 +#define A3XX_VBIF_IN_WR_LIM_CONF1 0x3031 +#define A3XX_VBIF_OUT_RD_LIM_CONF0 0x3034 +#define A3XX_VBIF_OUT_WR_LIM_CONF0 0x3035 +#define A3XX_VBIF_DDR_OUT_MAX_BURST 0x3036 +#define A3XX_VBIF_ARB_CTL 0x303C +#define A3XX_VBIF_OUT_AXI_AOOO_EN 0x305E +#define A3XX_VBIF_OUT_AXI_AOOO 0x305F +#define A3XX_VBIF_ERR_PENDING 0x3064 +#define A3XX_VBIF_ERR_MASK 0x3066 +#define A3XX_VBIF_ERR_CLEAR 0x3067 +#define A3XX_VBIF_ERR_INFO 0x3068 + +/* Bit flags for RBBM_CTL */ +#define RBBM_RBBM_CTL_RESET_PWR_CTR1 (1 << 1) +#define RBBM_RBBM_CTL_ENABLE_PWR_CTR1 (1 << 17) + +/* Various flags used by the context switch code */ + +#define SP_MULTI 0 +#define SP_BUFFER_MODE 1 +#define SP_TWO_VTX_QUADS 0 +#define SP_PIXEL_BASED 0 +#define SP_R8G8B8A8_UNORM 8 +#define SP_FOUR_PIX_QUADS 1 + +#define HLSQ_DIRECT 0 +#define HLSQ_BLOCK_ID_SP_VS 4 +#define HLSQ_SP_VS_INSTR 0 +#define HLSQ_SP_FS_INSTR 0 +#define HLSQ_BLOCK_ID_SP_FS 6 +#define HLSQ_TWO_PIX_QUADS 0 +#define HLSQ_TWO_VTX_QUADS 0 +#define HLSQ_BLOCK_ID_TP_TEX 2 +#define HLSQ_TP_TEX_SAMPLERS 0 +#define HLSQ_TP_TEX_MEMOBJ 1 +#define HLSQ_BLOCK_ID_TP_MIPMAP 3 +#define HLSQ_TP_MIPMAP_BASE 1 +#define HLSQ_FOUR_PIX_QUADS 1 + +#define RB_FACTOR_ONE 1 +#define RB_BLEND_OP_ADD 0 +#define RB_FACTOR_ZERO 0 +#define RB_DITHER_DISABLE 0 +#define RB_DITHER_ALWAYS 1 +#define RB_FRAG_NEVER 0 +#define RB_ENDIAN_NONE 0 +#define RB_R8G8B8A8_UNORM 8 +#define RB_RESOLVE_PASS 2 +#define RB_CLEAR_MODE_RESOLVE 1 +#define RB_TILINGMODE_LINEAR 0 +#define RB_REF_NEVER 0 +#define RB_STENCIL_KEEP 0 +#define RB_RENDERING_PASS 0 +#define RB_TILINGMODE_32X32 2 + +#define PC_DRAW_TRIANGLES 2 +#define PC_DI_PT_RECTLIST 8 +#define PC_DI_SRC_SEL_AUTO_INDEX 2 +#define PC_DI_INDEX_SIZE_16_BIT 0 +#define PC_DI_IGNORE_VISIBILITY 0 +#define PC_DI_PT_TRILIST 4 +#define PC_DI_SRC_SEL_IMMEDIATE 1 +#define PC_DI_INDEX_SIZE_32_BIT 1 + +#define UCHE_ENTIRE_CACHE 1 +#define UCHE_OP_INVALIDATE 1 + +/* + * The following are bit field shifts within some of the registers defined + * above. These are used in the context switch code in conjunction with the + * _SET macro + */ + +#define GRAS_CL_CLIP_CNTL_CLIP_DISABLE 16 +#define GRAS_CL_CLIP_CNTL_IJ_PERSP_CENTER 12 +#define GRAS_CL_CLIP_CNTL_PERSP_DIVISION_DISABLE 21 +#define GRAS_CL_CLIP_CNTL_VP_CLIP_CODE_IGNORE 19 +#define GRAS_CL_CLIP_CNTL_VP_XFORM_DISABLE 20 +#define GRAS_CL_CLIP_CNTL_ZFAR_CLIP_DISABLE 17 +#define GRAS_CL_VPORT_XSCALE_VPORT_XSCALE 0 +#define GRAS_CL_VPORT_YSCALE_VPORT_YSCALE 0 +#define GRAS_CL_VPORT_ZSCALE_VPORT_ZSCALE 0 +#define GRAS_SC_CONTROL_RASTER_MODE 12 +#define GRAS_SC_CONTROL_RENDER_MODE 4 +#define GRAS_SC_SCREEN_SCISSOR_BR_BR_X 0 +#define GRAS_SC_SCREEN_SCISSOR_BR_BR_Y 16 +#define GRAS_SC_WINDOW_SCISSOR_BR_BR_X 0 +#define GRAS_SC_WINDOW_SCISSOR_BR_BR_Y 16 +#define HLSQ_CONSTFSPRESERVEDRANGEREG_ENDENTRY 16 +#define HLSQ_CONSTFSPRESERVEDRANGEREG_STARTENTRY 0 +#define HLSQ_CTRL0REG_CHUNKDISABLE 26 +#define HLSQ_CTRL0REG_CONSTSWITCHMODE 27 +#define HLSQ_CTRL0REG_FSSUPERTHREADENABLE 6 +#define HLSQ_CTRL0REG_FSTHREADSIZE 4 +#define HLSQ_CTRL0REG_LAZYUPDATEDISABLE 28 +#define HLSQ_CTRL0REG_RESERVED2 10 +#define HLSQ_CTRL0REG_SPCONSTFULLUPDATE 29 +#define HLSQ_CTRL0REG_SPSHADERRESTART 9 +#define HLSQ_CTRL0REG_TPFULLUPDATE 30 +#define HLSQ_CTRL1REG_RESERVED1 9 +#define HLSQ_CTRL1REG_VSSUPERTHREADENABLE 8 +#define HLSQ_CTRL1REG_VSTHREADSIZE 6 +#define HLSQ_CTRL2REG_PRIMALLOCTHRESHOLD 26 +#define HLSQ_FSCTRLREG_FSCONSTLENGTH 0 +#define HLSQ_FSCTRLREG_FSCONSTSTARTOFFSET 12 +#define HLSQ_FSCTRLREG_FSINSTRLENGTH 24 +#define HLSQ_VSCTRLREG_VSINSTRLENGTH 24 +#define PC_PRIM_VTX_CONTROL_POLYMODE_BACK_PTYPE 8 +#define PC_PRIM_VTX_CONTROL_POLYMODE_FRONT_PTYPE 5 +#define PC_PRIM_VTX_CONTROL_PROVOKING_VTX_LAST 25 +#define PC_PRIM_VTX_CONTROL_STRIDE_IN_VPC 0 +#define PC_DRAW_INITIATOR_PRIM_TYPE 0 +#define PC_DRAW_INITIATOR_SOURCE_SELECT 6 +#define PC_DRAW_INITIATOR_VISIBILITY_CULLING_MODE 9 +#define PC_DRAW_INITIATOR_INDEX_SIZE 0x0B +#define PC_DRAW_INITIATOR_SMALL_INDEX 0x0D +#define PC_DRAW_INITIATOR_PRE_DRAW_INITIATOR_ENABLE 0x0E +#define RB_COPYCONTROL_COPY_GMEM_BASE 14 +#define RB_COPYCONTROL_RESOLVE_CLEAR_MODE 4 +#define RB_COPYDESTBASE_COPY_DEST_BASE 4 +#define RB_COPYDESTINFO_COPY_COMPONENT_ENABLE 14 +#define RB_COPYDESTINFO_COPY_DEST_ENDIAN 18 +#define RB_COPYDESTINFO_COPY_DEST_FORMAT 2 +#define RB_COPYDESTINFO_COPY_DEST_TILE 0 +#define RB_COPYDESTPITCH_COPY_DEST_PITCH 0 +#define RB_DEPTHCONTROL_Z_TEST_FUNC 4 +#define RB_MODECONTROL_RENDER_MODE 8 +#define RB_MODECONTROL_MARB_CACHE_SPLIT_MODE 15 +#define RB_MODECONTROL_PACKER_TIMER_ENABLE 16 +#define RB_MRTBLENDCONTROL_ALPHA_BLEND_OPCODE 21 +#define RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR 24 +#define RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR 16 +#define RB_MRTBLENDCONTROL_CLAMP_ENABLE 29 +#define RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE 5 +#define RB_MRTBLENDCONTROL_RGB_DEST_FACTOR 8 +#define RB_MRTBLENDCONTROL_RGB_SRC_FACTOR 0 +#define RB_MRTBUFBASE_COLOR_BUF_BASE 4 +#define RB_MRTBUFINFO_COLOR_BUF_PITCH 17 +#define RB_MRTBUFINFO_COLOR_FORMAT 0 +#define RB_MRTBUFINFO_COLOR_TILE_MODE 6 +#define RB_MRTCONTROL_COMPONENT_ENABLE 24 +#define RB_MRTCONTROL_DITHER_MODE 12 +#define RB_MRTCONTROL_READ_DEST_ENABLE 3 +#define RB_MRTCONTROL_ROP_CODE 8 +#define RB_MSAACONTROL_MSAA_DISABLE 10 +#define RB_MSAACONTROL_SAMPLE_MASK 16 +#define RB_RENDERCONTROL_ALPHA_TEST_FUNC 24 +#define RB_RENDERCONTROL_BIN_WIDTH 4 +#define RB_RENDERCONTROL_DISABLE_COLOR_PIPE 12 +#define RB_STENCILCONTROL_STENCIL_FAIL 11 +#define RB_STENCILCONTROL_STENCIL_FAIL_BF 23 +#define RB_STENCILCONTROL_STENCIL_FUNC 8 +#define RB_STENCILCONTROL_STENCIL_FUNC_BF 20 +#define RB_STENCILCONTROL_STENCIL_ZFAIL 17 +#define RB_STENCILCONTROL_STENCIL_ZFAIL_BF 29 +#define RB_STENCILCONTROL_STENCIL_ZPASS 14 +#define RB_STENCILCONTROL_STENCIL_ZPASS_BF 26 +#define SP_FSCTRLREG0_FSFULLREGFOOTPRINT 10 +#define SP_FSCTRLREG0_FSICACHEINVALID 2 +#define SP_FSCTRLREG0_FSINOUTREGOVERLAP 18 +#define SP_FSCTRLREG0_FSINSTRBUFFERMODE 1 +#define SP_FSCTRLREG0_FSLENGTH 24 +#define SP_FSCTRLREG0_FSSUPERTHREADMODE 21 +#define SP_FSCTRLREG0_FSTHREADMODE 0 +#define SP_FSCTRLREG0_FSTHREADSIZE 20 +#define SP_FSCTRLREG0_PIXLODENABLE 22 +#define SP_FSCTRLREG1_FSCONSTLENGTH 0 +#define SP_FSCTRLREG1_FSINITIALOUTSTANDING 20 +#define SP_FSCTRLREG1_HALFPRECVAROFFSET 24 +#define SP_FSMRTREG_REGID 0 +#define SP_FSOUTREG_PAD0 2 +#define SP_IMAGEOUTPUTREG_MRTFORMAT 0 +#define SP_IMAGEOUTPUTREG_PAD0 6 +#define SP_OBJOFFSETREG_CONSTOBJECTSTARTOFFSET 16 +#define SP_OBJOFFSETREG_SHADEROBJOFFSETINIC 25 +#define SP_SHADERLENGTH_LEN 0 +#define SP_SPCTRLREG_CONSTMODE 18 +#define SP_SPCTRLREG_SLEEPMODE 20 +#define SP_VSCTRLREG0_VSFULLREGFOOTPRINT 10 +#define SP_VSCTRLREG0_VSICACHEINVALID 2 +#define SP_VSCTRLREG0_VSINSTRBUFFERMODE 1 +#define SP_VSCTRLREG0_VSLENGTH 24 +#define SP_VSCTRLREG0_VSSUPERTHREADMODE 21 +#define SP_VSCTRLREG0_VSTHREADMODE 0 +#define SP_VSCTRLREG0_VSTHREADSIZE 20 +#define SP_VSCTRLREG1_VSINITIALOUTSTANDING 24 +#define SP_VSOUTREG_COMPMASK0 9 +#define SP_VSPARAMREG_POSREGID 0 +#define SP_VSPARAMREG_PSIZEREGID 8 +#define SP_VSPARAMREG_TOTALVSOUTVAR 20 +#define SP_VSVPCDSTREG_OUTLOC0 0 +#define TPL1_TPTEXOFFSETREG_BASETABLEPTR 16 +#define TPL1_TPTEXOFFSETREG_MEMOBJOFFSET 8 +#define TPL1_TPTEXOFFSETREG_SAMPLEROFFSET 0 +#define UCHE_INVALIDATE1REG_OPCODE 0x1C +#define UCHE_INVALIDATE1REG_ALLORPORTION 0x1F +#define VFD_BASEADDR_BASEADDR 0 +#define VFD_CTRLREG0_PACKETSIZE 18 +#define VFD_CTRLREG0_STRMDECINSTRCNT 22 +#define VFD_CTRLREG0_STRMFETCHINSTRCNT 27 +#define VFD_CTRLREG0_TOTALATTRTOVS 0 +#define VFD_CTRLREG1_MAXSTORAGE 0 +#define VFD_CTRLREG1_REGID4INST 24 +#define VFD_CTRLREG1_REGID4VTX 16 +#define VFD_DECODEINSTRUCTIONS_CONSTFILL 4 +#define VFD_DECODEINSTRUCTIONS_FORMAT 6 +#define VFD_DECODEINSTRUCTIONS_LASTCOMPVALID 29 +#define VFD_DECODEINSTRUCTIONS_REGID 12 +#define VFD_DECODEINSTRUCTIONS_SHIFTCNT 24 +#define VFD_DECODEINSTRUCTIONS_SWITCHNEXT 30 +#define VFD_DECODEINSTRUCTIONS_WRITEMASK 0 +#define VFD_FETCHINSTRUCTIONS_BUFSTRIDE 7 +#define VFD_FETCHINSTRUCTIONS_FETCHSIZE 0 +#define VFD_FETCHINSTRUCTIONS_INDEXDECODE 18 +#define VFD_FETCHINSTRUCTIONS_STEPRATE 24 +#define VFD_FETCHINSTRUCTIONS_SWITCHNEXT 17 +#define VFD_THREADINGTHRESHOLD_REGID_VTXCNT 8 +#define VFD_THREADINGTHRESHOLD_RESERVED6 4 +#define VPC_VPCATTR_LMSIZE 28 +#define VPC_VPCATTR_THRHDASSIGN 12 +#define VPC_VPCATTR_TOTALATTR 0 +#define VPC_VPCPACK_NUMFPNONPOSVAR 8 +#define VPC_VPCPACK_NUMNONPOSVSVAR 16 +#define VPC_VPCVARPSREPLMODE_COMPONENT08 0 +#define VPC_VPCVARPSREPLMODE_COMPONENT09 2 +#define VPC_VPCVARPSREPLMODE_COMPONENT0A 4 +#define VPC_VPCVARPSREPLMODE_COMPONENT0B 6 +#define VPC_VPCVARPSREPLMODE_COMPONENT0C 8 +#define VPC_VPCVARPSREPLMODE_COMPONENT0D 10 +#define VPC_VPCVARPSREPLMODE_COMPONENT0E 12 +#define VPC_VPCVARPSREPLMODE_COMPONENT0F 14 +#define VPC_VPCVARPSREPLMODE_COMPONENT10 16 +#define VPC_VPCVARPSREPLMODE_COMPONENT11 18 +#define VPC_VPCVARPSREPLMODE_COMPONENT12 20 +#define VPC_VPCVARPSREPLMODE_COMPONENT13 22 +#define VPC_VPCVARPSREPLMODE_COMPONENT14 24 +#define VPC_VPCVARPSREPLMODE_COMPONENT15 26 +#define VPC_VPCVARPSREPLMODE_COMPONENT16 28 +#define VPC_VPCVARPSREPLMODE_COMPONENT17 30 + +/* RBBM Debug bus block IDs */ +#define RBBM_BLOCK_ID_NONE 0x0 +#define RBBM_BLOCK_ID_CP 0x1 +#define RBBM_BLOCK_ID_RBBM 0x2 +#define RBBM_BLOCK_ID_VBIF 0x3 +#define RBBM_BLOCK_ID_HLSQ 0x4 +#define RBBM_BLOCK_ID_UCHE 0x5 +#define RBBM_BLOCK_ID_PC 0x8 +#define RBBM_BLOCK_ID_VFD 0x9 +#define RBBM_BLOCK_ID_VPC 0xa +#define RBBM_BLOCK_ID_TSE 0xb +#define RBBM_BLOCK_ID_RAS 0xc +#define RBBM_BLOCK_ID_VSC 0xd +#define RBBM_BLOCK_ID_SP_0 0x10 +#define RBBM_BLOCK_ID_SP_1 0x11 +#define RBBM_BLOCK_ID_SP_2 0x12 +#define RBBM_BLOCK_ID_SP_3 0x13 +#define RBBM_BLOCK_ID_TPL1_0 0x18 +#define RBBM_BLOCK_ID_TPL1_1 0x19 +#define RBBM_BLOCK_ID_TPL1_2 0x1a +#define RBBM_BLOCK_ID_TPL1_3 0x1b +#define RBBM_BLOCK_ID_RB_0 0x20 +#define RBBM_BLOCK_ID_RB_1 0x21 +#define RBBM_BLOCK_ID_RB_2 0x22 +#define RBBM_BLOCK_ID_RB_3 0x23 +#define RBBM_BLOCK_ID_MARB_0 0x28 +#define RBBM_BLOCK_ID_MARB_1 0x29 +#define RBBM_BLOCK_ID_MARB_2 0x2a +#define RBBM_BLOCK_ID_MARB_3 0x2b + +#endif diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c new file mode 100644 index 0000000000000000000000000000000000000000..66baee11c2314f646d6c2aa65070c08270c54cd0 --- /dev/null +++ b/drivers/gpu/msm/adreno.c @@ -0,0 +1,1658 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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 "kgsl_iommu.h" + +#include "adreno.h" +#include "adreno_pm4types.h" +#include "adreno_debugfs.h" +#include "adreno_postmortem.h" + +#include "a2xx_reg.h" +#include "a3xx_reg.h" + +#define DRIVER_VERSION_MAJOR 3 +#define DRIVER_VERSION_MINOR 1 + +/* 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)) + +static const struct kgsl_functable adreno_functable; + +static struct adreno_device device_3d0 = { + .dev = { + KGSL_DEVICE_COMMON_INIT(device_3d0.dev), + .name = DEVICE_3D0_NAME, + .id = KGSL_DEVICE_3D0, + .mh = { + .mharb = ADRENO_CFG_MHARB, + /* Remove 1k boundary check in z470 to avoid a GPU + * hang. Notice that this solution won't work if + * both EBI and SMI are used + */ + .mh_intf_cfg1 = 0x00032f07, + /* turn off memory protection unit by setting + acceptable physical address range to include + all pages. */ + .mpu_base = 0x00000000, + .mpu_range = 0xFFFFF000, + }, + .mmu = { + .config = ADRENO_MMU_CONFIG, + }, + .pwrctrl = { + .irq_name = KGSL_3D0_IRQ, + }, + .iomemname = KGSL_3D0_REG_MEMORY, + .ftbl = &adreno_functable, +#ifdef CONFIG_HAS_EARLYSUSPEND + .display_off = { + .level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, + .suspend = kgsl_early_suspend_driver, + .resume = kgsl_late_resume_driver, + }, +#endif + }, + .gmem_base = 0, + .gmem_size = SZ_256K, + .pfp_fw = NULL, + .pm4_fw = NULL, + .wait_timeout = 10000, /* in milliseconds */ + .ib_check_level = 0, +}; + + +/* + * This is the master list of all GPU cores that are supported by this + * driver. + */ + +#define ANY_ID (~0) + +static const struct { + enum adreno_gpurev gpurev; + unsigned int core, major, minor, patchid; + const char *pm4fw; + const char *pfpfw; + struct adreno_gpudev *gpudev; + unsigned int istore_size; + unsigned int pix_shader_start; + unsigned int instruction_size; /* Size of an instruction in dwords */ + unsigned int gmem_size; /* size of gmem for gpu*/ +} adreno_gpulist[] = { + { ADRENO_REV_A200, 0, 2, ANY_ID, ANY_ID, + "yamato_pm4.fw", "yamato_pfp.fw", &adreno_a2xx_gpudev, + 512, 384, 3, SZ_256K }, + { ADRENO_REV_A203, 0, 1, 1, ANY_ID, + "yamato_pm4.fw", "yamato_pfp.fw", &adreno_a2xx_gpudev, + 512, 384, 3, SZ_256K }, + { ADRENO_REV_A205, 0, 1, 0, ANY_ID, + "yamato_pm4.fw", "yamato_pfp.fw", &adreno_a2xx_gpudev, + 512, 384, 3, SZ_256K }, + { ADRENO_REV_A220, 2, 1, ANY_ID, ANY_ID, + "leia_pm4_470.fw", "leia_pfp_470.fw", &adreno_a2xx_gpudev, + 512, 384, 3, SZ_512K }, + /* + * patchlevel 5 (8960v2) needs special pm4 firmware to work around + * a hardware problem. + */ + { ADRENO_REV_A225, 2, 2, 0, 5, + "a225p5_pm4.fw", "a225_pfp.fw", &adreno_a2xx_gpudev, + 1536, 768, 3, SZ_512K }, + { ADRENO_REV_A225, 2, 2, 0, 6, + "a225_pm4.fw", "a225_pfp.fw", &adreno_a2xx_gpudev, + 1536, 768, 3, SZ_512K }, + { ADRENO_REV_A225, 2, 2, ANY_ID, ANY_ID, + "a225_pm4.fw", "a225_pfp.fw", &adreno_a2xx_gpudev, + 1536, 768, 3, SZ_512K }, + /* A3XX doesn't use the pix_shader_start */ + { ADRENO_REV_A305, 3, 0, 5, 0, + "a300_pm4.fw", "a300_pfp.fw", &adreno_a3xx_gpudev, + 512, 0, 2, SZ_256K }, + /* A3XX doesn't use the pix_shader_start */ + { ADRENO_REV_A320, 3, 2, 0, ANY_ID, + "a300_pm4.fw", "a300_pfp.fw", &adreno_a3xx_gpudev, + 512, 0, 2, SZ_512K }, + +}; + +static irqreturn_t adreno_irq_handler(struct kgsl_device *device) +{ + irqreturn_t result; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + result = adreno_dev->gpudev->irq_handler(adreno_dev); + + if (device->requested_state == KGSL_STATE_NONE) { + if (device->pwrctrl.nap_allowed == true) { + kgsl_pwrctrl_request_state(device, 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_pending(&device->idle_timer, + jiffies + device->pwrctrl.interval_timeout); + return result; +} + +static void 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.setstate_memory); +} + +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; + + 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.setstate_memory, + 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_iommu_setstate(struct kgsl_device *device, + uint32_t flags) +{ + unsigned int pt_val, reg_pt_val; + unsigned int link[200]; + unsigned int *cmds = &link[0]; + int sizedwords = 0; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct kgsl_memdesc **reg_map_desc; + void *reg_map_array; + int num_iommu_units, i; + + if (!adreno_dev->drawctxt_active) + return kgsl_mmu_device_setstate(&device->mmu, flags); + num_iommu_units = kgsl_mmu_get_reg_map_desc(&device->mmu, + ®_map_array); + reg_map_desc = reg_map_array; + + if (kgsl_mmu_enable_clk(&device->mmu, + KGSL_IOMMU_CONTEXT_USER)) + goto done; + + if (adreno_is_a225(adreno_dev)) + cmds += adreno_add_change_mh_phys_limit_cmds(cmds, 0xFFFFF000, + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + else + cmds += adreno_add_bank_change_cmds(cmds, + KGSL_IOMMU_CONTEXT_USER, + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + + if (flags & KGSL_MMUFLAGS_PTUPDATE) { + pt_val = kgsl_mmu_pt_get_base_addr(device->mmu.hwpagetable); + /* + * We need to perfrom the following operations for all + * IOMMU units + */ + for (i = 0; i < num_iommu_units; i++) { + reg_pt_val = (pt_val & + (KGSL_IOMMU_TTBR0_PA_MASK << + KGSL_IOMMU_TTBR0_PA_SHIFT)) + + kgsl_mmu_get_pt_lsb(&device->mmu, i, + KGSL_IOMMU_CONTEXT_USER); + /* + * Set address of the new pagetable by writng to IOMMU + * TTBR0 register + */ + *cmds++ = cp_type3_packet(CP_MEM_WRITE, 2); + *cmds++ = reg_map_desc[i]->gpuaddr + + (KGSL_IOMMU_CONTEXT_USER << + KGSL_IOMMU_CTX_SHIFT) + KGSL_IOMMU_TTBR0; + *cmds++ = reg_pt_val; + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + /* + * Read back the ttbr0 register as a barrier to ensure + * above writes have completed + */ + cmds += adreno_add_read_cmds(device, cmds, + reg_map_desc[i]->gpuaddr + + (KGSL_IOMMU_CONTEXT_USER << + KGSL_IOMMU_CTX_SHIFT) + KGSL_IOMMU_TTBR0, + reg_pt_val, + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + + /* set the asid */ + *cmds++ = cp_type3_packet(CP_MEM_WRITE, 2); + *cmds++ = reg_map_desc[i]->gpuaddr + + (KGSL_IOMMU_CONTEXT_USER << + KGSL_IOMMU_CTX_SHIFT) + KGSL_IOMMU_CONTEXTIDR; + *cmds++ = kgsl_mmu_get_hwpagetable_asid(&device->mmu); + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + /* Read back asid to ensure above write completes */ + cmds += adreno_add_read_cmds(device, cmds, + reg_map_desc[i]->gpuaddr + + (KGSL_IOMMU_CONTEXT_USER << + KGSL_IOMMU_CTX_SHIFT) + KGSL_IOMMU_CONTEXTIDR, + kgsl_mmu_get_hwpagetable_asid(&device->mmu), + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + } + /* invalidate all base pointers */ + *cmds++ = cp_type3_packet(CP_INVALIDATE_STATE, 1); + *cmds++ = 0x7fff; + + if (flags & KGSL_MMUFLAGS_TLBFLUSH) + cmds += __adreno_add_idle_indirect_cmds(cmds, + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + } + if (flags & KGSL_MMUFLAGS_TLBFLUSH) { + /* + * tlb flush based on asid, no need to flush entire tlb + */ + for (i = 0; i < num_iommu_units; i++) { + *cmds++ = cp_type3_packet(CP_MEM_WRITE, 2); + *cmds++ = (reg_map_desc[i]->gpuaddr + + (KGSL_IOMMU_CONTEXT_USER << + KGSL_IOMMU_CTX_SHIFT) + + KGSL_IOMMU_CTX_TLBIASID); + *cmds++ = kgsl_mmu_get_hwpagetable_asid(&device->mmu); + cmds += adreno_add_read_cmds(device, cmds, + reg_map_desc[i]->gpuaddr + + (KGSL_IOMMU_CONTEXT_USER << + KGSL_IOMMU_CTX_SHIFT) + + KGSL_IOMMU_CONTEXTIDR, + kgsl_mmu_get_hwpagetable_asid(&device->mmu), + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + } + } + + if (adreno_is_a225(adreno_dev)) + cmds += adreno_add_change_mh_phys_limit_cmds(cmds, + reg_map_desc[num_iommu_units - 1]->gpuaddr - PAGE_SIZE, + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + else + cmds += adreno_add_bank_change_cmds(cmds, + KGSL_IOMMU_CONTEXT_PRIV, + device->mmu.setstate_memory.gpuaddr + + KGSL_IOMMU_SETSTATE_NOP_OFFSET); + + sizedwords += (cmds - &link[0]); + if (sizedwords) + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_PMODE, &link[0], sizedwords); +done: + if (num_iommu_units) + kfree(reg_map_array); +} + +static void adreno_gpummu_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 possible, then set the state via the command stream to avoid + * a CPU idle. Otherwise, use the default setstate which uses register + * writes For CFF dump we must idle and use the registers so that it is + * easier to filter out the mmu accesses from the dump + */ + if (!kgsl_cff_dump_enable && adreno_dev->drawctxt_active) { + if (flags & KGSL_MMUFLAGS_PTUPDATE) { + /* wait for graphics pipe to be idle */ + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + /* set page table base */ + *cmds++ = cp_type0_packet(MH_MMU_PT_BASE, 1); + *cmds++ = kgsl_mmu_pt_get_base_addr( + device->mmu.hwpagetable); + sizedwords += 4; + } + + if (flags & KGSL_MMUFLAGS_TLBFLUSH) { + if (!(flags & KGSL_MMUFLAGS_PTUPDATE)) { + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, + 1); + *cmds++ = 0x00000000; + sizedwords += 2; + } + *cmds++ = cp_type0_packet(MH_MMU_INVALIDATE, 1); + *cmds++ = mh_mmu_invalidate; + sizedwords += 2; + } + + if (flags & KGSL_MMUFLAGS_PTUPDATE && + adreno_is_a20x(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++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = (0x4 << 16) | + (REG_PA_SU_SC_MODE_CNTL - 0x2000); + *cmds++ = 0; /* disable faceness generation */ + *cmds++ = cp_type3_packet(CP_SET_BIN_BASE_OFFSET, 1); + *cmds++ = device->mmu.setstate_memory.gpuaddr; + *cmds++ = cp_type3_packet(CP_DRAW_INDX_BIN, 6); + *cmds++ = 0; /* viz query info */ + *cmds++ = 0x0003C004; /* draw indicator */ + *cmds++ = 0; /* bin base */ + *cmds++ = 3; /* bin size */ + *cmds++ = + device->mmu.setstate_memory.gpuaddr; /* dma base */ + *cmds++ = 6; /* dma size */ + *cmds++ = cp_type3_packet(CP_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.setstate_memory.gpuaddr; + *cmds++ = 6; /* dma size */ + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + sizedwords += 21; + } + + + if (flags & (KGSL_MMUFLAGS_PTUPDATE | KGSL_MMUFLAGS_TLBFLUSH)) { + *cmds++ = cp_type3_packet(CP_INVALIDATE_STATE, 1); + *cmds++ = 0x7fff; /* invalidate all base pointers */ + sizedwords += 2; + } + + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + &link[0], sizedwords); + } else { + kgsl_mmu_device_setstate(&device->mmu, flags); + } +} + +static void adreno_setstate(struct kgsl_device *device, + uint32_t flags) +{ + /* call the mmu specific handler */ + if (KGSL_MMU_TYPE_GPU == kgsl_mmu_get_mmutype()) + return adreno_gpummu_setstate(device, flags); + else if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_get_mmutype()) + return adreno_iommu_setstate(device, flags); +} + +static unsigned int +a3xx_getchipid(struct kgsl_device *device) +{ + unsigned int majorid = 0, minorid = 0, patchid = 0; + + /* + * We could detect the chipID from the hardware but it takes multiple + * registers to find the right combination. Since we traffic exclusively + * in system on chips, we can be (mostly) confident that a SOC version + * will match a GPU (at this juncture at least). So do the lazy/quick + * thing and set the chip_id based on the SoC + */ + + if (cpu_is_apq8064()) { + unsigned int version = socinfo_get_version(); + + /* A320 */ + majorid = 2; + minorid = 0; + + /* + * V1.1 has some GPU work arounds that we need to communicate + * up to user space via the patchid + */ + + if ((SOCINFO_VERSION_MAJOR(version) == 1) && + (SOCINFO_VERSION_MINOR(version) == 1)) + patchid = 1; + else + patchid = 0; + } else if (cpu_is_msm8930()) { + /* A305 */ + majorid = 0; + minorid = 5; + patchid = 0; + } + + return (0x03 << 24) | (majorid << 16) | (minorid << 8) | patchid; +} + +static unsigned int +a2xx_getchipid(struct kgsl_device *device) +{ + unsigned int chipid = 0; + unsigned int coreid, majorid, minorid, patchid, revid; + uint32_t soc_platform_version = socinfo_get_version(); + + 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 */ + /* 8960v3 returns 5 for patch release, but it should be 6 */ + /* 8x25 returns 0 for minor id, but it should be 1 */ + if (cpu_is_qsd8x50()) + patchid = 1; + else if (cpu_is_msm8960() && + SOCINFO_VERSION_MAJOR(soc_platform_version) == 3) + patchid = 6; + else if (cpu_is_msm8625() && minorid == 0) + minorid = 1; + + chipid |= (minorid << 8) | patchid; + + return chipid; +} + +static unsigned int +adreno_getchipid(struct kgsl_device *device) +{ + if (cpu_is_apq8064() || cpu_is_msm8930()) + return a3xx_getchipid(device); + else + return a2xx_getchipid(device); +} + +static inline bool _rev_match(unsigned int id, unsigned int entry) +{ + return (entry == ANY_ID || entry == id); +} + +static void +adreno_identify_gpu(struct adreno_device *adreno_dev) +{ + unsigned int i, core, major, minor, patchid; + + 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; + patchid = (adreno_dev->chip_id & 0xff); + + for (i = 0; i < ARRAY_SIZE(adreno_gpulist); i++) { + if (core == adreno_gpulist[i].core && + _rev_match(major, adreno_gpulist[i].major) && + _rev_match(minor, adreno_gpulist[i].minor) && + _rev_match(patchid, adreno_gpulist[i].patchid)) + break; + } + + if (i == ARRAY_SIZE(adreno_gpulist)) { + adreno_dev->gpurev = ADRENO_REV_UNKNOWN; + return; + } + + adreno_dev->gpurev = adreno_gpulist[i].gpurev; + adreno_dev->gpudev = adreno_gpulist[i].gpudev; + adreno_dev->pfp_fwfile = adreno_gpulist[i].pfpfw; + adreno_dev->pm4_fwfile = adreno_gpulist[i].pm4fw; + adreno_dev->istore_size = adreno_gpulist[i].istore_size; + adreno_dev->pix_shader_start = adreno_gpulist[i].pix_shader_start; + adreno_dev->instruction_size = adreno_gpulist[i].instruction_size; + adreno_dev->gmem_size = adreno_gpulist[i].gmem_size; +} + +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; + + status = adreno_ringbuffer_init(device); + if (status != 0) + goto error; + + status = kgsl_device_platform_probe(device); + 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); + + kgsl_pwrctrl_set_state(device, KGSL_STATE_INIT); + + /* Power up the device */ + kgsl_pwrctrl_enable(device); + + /* Identify the specific GPU */ + adreno_identify_gpu(adreno_dev); + + if (adreno_dev->gpurev == ADRENO_REV_UNKNOWN) { + KGSL_DRV_ERR(device, "Unknown chip ID %x\n", + adreno_dev->chip_id); + goto error_clk_off; + } + + /* Set up the MMU */ + if (adreno_is_a2xx(adreno_dev)) { + /* + * the MH_CLNT_INTF_CTRL_CONFIG registers aren't present + * on older gpus + */ + if (adreno_is_a20x(adreno_dev)) { + device->mh.mh_intf_cfg1 = 0; + device->mh.mh_intf_cfg2 = 0; + } + + kgsl_mh_start(device); + } + + status = kgsl_mmu_start(device); + if (status) + goto error_clk_off; + + /* Start the GPU */ + adreno_dev->gpudev->start(adreno_dev); + + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_ON); + device->ftbl->irqctrl(device, 1); + + status = adreno_ringbuffer_start(&adreno_dev->ringbuffer, init_ram); + if (status == 0) { + mod_timer(&device->idle_timer, jiffies + FIRST_TIMEOUT); + return 0; + } + + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + kgsl_mmu_stop(&device->mmu); +error_clk_off: + kgsl_pwrctrl_disable(device); + + return status; +} + +static int adreno_stop(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + adreno_dev->drawctxt_active = NULL; + + adreno_ringbuffer_stop(&adreno_dev->ringbuffer); + + kgsl_mmu_stop(&device->mmu); + + device->ftbl->irqctrl(device, 0); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + del_timer_sync(&device->idle_timer); + + /* 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 reftimestamp; + unsigned int enable_ts; + unsigned int soptimestamp; + unsigned int eoptimestamp; + unsigned int context_id; + struct kgsl_context *context; + struct adreno_context *adreno_context; + int next = 0; + + 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; + kgsl_sharedmem_readl(&device->memstore, &context_id, + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + current_context)); + context = idr_find(&device->context_idr, context_id); + if (context == NULL) { + KGSL_DRV_ERR(device, "Last context unknown id:%d\n", + context_id); + context_id = KGSL_MEMSTORE_GLOBAL; + } + + timestamp = rb->timestamp[KGSL_MEMSTORE_GLOBAL]; + KGSL_DRV_ERR(device, "Last issued global timestamp: %x\n", timestamp); + + kgsl_sharedmem_readl(&device->memstore, &reftimestamp, + KGSL_MEMSTORE_OFFSET(context_id, + ref_wait_ts)); + kgsl_sharedmem_readl(&device->memstore, &enable_ts, + KGSL_MEMSTORE_OFFSET(context_id, + ts_cmp_enable)); + kgsl_sharedmem_readl(&device->memstore, &soptimestamp, + KGSL_MEMSTORE_OFFSET(context_id, + soptimestamp)); + kgsl_sharedmem_readl(&device->memstore, &eoptimestamp, + KGSL_MEMSTORE_OFFSET(context_id, + eoptimestamp)); + /* Make sure memory is synchronized before restarting the GPU */ + mb(); + KGSL_CTXT_ERR(device, + "Context id that caused a GPU hang: %d\n", context_id); + /* 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_MEMSTORE_OFFSET(context_id, soptimestamp), + soptimestamp); + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, eoptimestamp), + eoptimestamp); + + if (num_rb_contents) { + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, ref_wait_ts), + reftimestamp); + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, 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 */ + + adreno_context = context->devctxt; + + KGSL_CTXT_ERR(device, + "Context that caused a GPU hang: %d\n", adreno_context->id); + + adreno_context->flags |= CTXT_FLAGS_GPU_HANG; + + /* + * Set the reset status of all contexts to + * INNOCENT_CONTEXT_RESET_EXT except for the bad context + * since thats the guilty party + */ + while ((context = idr_get_next(&device->context_idr, &next))) { + if (KGSL_CTX_STAT_GUILTY_CONTEXT_RESET_EXT != + context->reset_status) { + if (context->id != context_id) + context->reset_status = + KGSL_CTX_STAT_INNOCENT_CONTEXT_RESET_EXT; + else + context->reset_status = + KGSL_CTX_STAT_GUILTY_CONTEXT_RESET_EXT; + } + next = next + 1; + } + + /* Restore valid commands in ringbuffer */ + adreno_ringbuffer_restore(rb, rb_buffer, num_rb_contents); + rb->timestamp[KGSL_MEMSTORE_GLOBAL] = timestamp; +done: + vfree(rb_buffer); + return ret; +} + +static int +adreno_dump_and_recover(struct kgsl_device *device) +{ + int result = -ETIMEDOUT; + + if (device->state == KGSL_STATE_HUNG) + goto done; + if (device->state == KGSL_STATE_DUMP_AND_RECOVER) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->recovery_gate); + mutex_lock(&device->mutex); + if (device->state != KGSL_STATE_HUNG) + result = 0; + } else { + kgsl_pwrctrl_set_state(device, KGSL_STATE_DUMP_AND_RECOVER); + INIT_COMPLETION(device->recovery_gate); + /* Detected a hang */ + + + /* + * Trigger an automatic dump of the state to + * the console + */ + adreno_postmortem_dump(device, 0); + + /* + * Make a GPU snapshot. For now, do it after the PM dump so we + * can at least be sure the PM dump will work as it always has + */ + kgsl_device_snapshot(device, 1); + + result = adreno_recover_hang(device); + if (result) + kgsl_pwrctrl_set_state(device, KGSL_STATE_HUNG); + else + kgsl_pwrctrl_set_state(device, KGSL_STATE_ACTIVE); + complete_all(&device->recovery_gate); + } +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->gmem_base; + devinfo.gmem_sizebytes = adreno_dev->gmem_size; + + 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 | + KGSL_FLAGS_PER_CONTEXT_TIMESTAMPS; + } + if (copy_to_user(value, &shadowprop, + sizeof(shadowprop))) { + status = -EFAULT; + break; + } + status = 0; + } + break; + case KGSL_PROP_MMU_ENABLE: + { + int mmu_prop = kgsl_mmu_enabled(); + + if (sizebytes != sizeof(int)) { + status = -EINVAL; + break; + } + if (copy_to_user(value, &mmu_prop, sizeof(mmu_prop))) { + 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; +} + +static int adreno_setproperty(struct kgsl_device *device, + enum kgsl_property_type type, + void *value, + unsigned int sizebytes) +{ + int status = -EINVAL; + + switch (type) { + case KGSL_PROP_PWRCTRL: { + unsigned int enable; + struct kgsl_device_platform_data *pdata = + kgsl_device_get_drvdata(device); + + if (sizebytes != sizeof(enable)) + break; + + if (copy_from_user(&enable, (void __user *) value, + sizeof(enable))) { + status = -EFAULT; + break; + } + + if (enable) { + if (pdata->nap_allowed) + device->pwrctrl.nap_allowed = true; + + kgsl_pwrscale_enable(device); + } else { + device->pwrctrl.nap_allowed = false; + kgsl_pwrscale_disable(device); + } + + status = 0; + } + break; + default: + break; + } + + return status; +} + +static inline void adreno_poke(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + adreno_regwrite(device, REG_CP_RB_WPTR, adreno_dev->ringbuffer.wptr); +} + +/* 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_timeout = + msecs_to_jiffies(adreno_dev->wait_timeout); + unsigned long wait_time; + unsigned long wait_time_part; + unsigned int msecs; + unsigned int msecs_first; + unsigned int msecs_part; + + kgsl_cffdump_regpoll(device->id, + adreno_dev->gpudev->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) { + msecs = adreno_dev->wait_timeout; + msecs_first = (msecs <= 100) ? ((msecs + 4) / 5) : 100; + msecs_part = (msecs - msecs_first + 3) / 4; + wait_time = jiffies + wait_timeout; + wait_time_part = jiffies + msecs_to_jiffies(msecs_first); + adreno_poke(device); + do { + if (time_after(jiffies, wait_time_part)) { + adreno_poke(device); + wait_time_part = jiffies + + msecs_to_jiffies(msecs_part); + } + 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 + wait_timeout; + while (time_before(jiffies, wait_time)) { + adreno_regread(device, adreno_dev->gpudev->reg_rbbm_status, + &rbbm_status); + if (adreno_is_a2xx(adreno_dev)) { + if (rbbm_status == 0x110) + return 0; + } else { + if (!(rbbm_status & 0x80000000)) + 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 + wait_timeout; + 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; + + WARN_ON(device->state == KGSL_STATE_INIT); + /* If the device isn't active, don't force it on. */ + if (device->state == KGSL_STATE_ACTIVE) { + /* 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, + adreno_dev->gpudev->reg_rbbm_status, + &rbbm_status); + + if (adreno_is_a2xx(adreno_dev)) { + if (rbbm_status == 0x110) + status = true; + } else { + if (!(rbbm_status & 0x80000000)) + status = true; + } + } + } else { + status = true; + } + 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; +} + +/* Find a memory structure attached to an adreno context */ + +struct kgsl_memdesc *adreno_find_ctxtmem(struct kgsl_device *device, + unsigned int pt_base, unsigned int gpuaddr, unsigned int size) +{ + struct kgsl_context *context; + struct adreno_context *adreno_context = NULL; + int next = 0; + + while (1) { + context = idr_get_next(&device->context_idr, &next); + if (context == NULL) + break; + + adreno_context = (struct adreno_context *)context->devctxt; + + if (kgsl_mmu_pt_equal(adreno_context->pagetable, pt_base)) { + struct kgsl_memdesc *desc; + + desc = &adreno_context->gpustate; + if (kgsl_gpuaddr_in_memdesc(desc, gpuaddr, size)) + return desc; + + desc = &adreno_context->context_gmem_shadow.gmemshadow; + if (kgsl_gpuaddr_in_memdesc(desc, gpuaddr, size)) + return desc; + } + next = next + 1; + } + + return NULL; +} + +struct kgsl_memdesc *adreno_find_region(struct kgsl_device *device, + unsigned int pt_base, + unsigned int gpuaddr, + unsigned int size) +{ + struct kgsl_mem_entry *entry; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *ringbuffer = &adreno_dev->ringbuffer; + + if (kgsl_gpuaddr_in_memdesc(&ringbuffer->buffer_desc, gpuaddr, size)) + return &ringbuffer->buffer_desc; + + if (kgsl_gpuaddr_in_memdesc(&ringbuffer->memptrs_desc, gpuaddr, size)) + return &ringbuffer->memptrs_desc; + + if (kgsl_gpuaddr_in_memdesc(&device->memstore, gpuaddr, size)) + return &device->memstore; + + if (kgsl_gpuaddr_in_memdesc(&device->mmu.setstate_memory, gpuaddr, + size)) + return &device->mmu.setstate_memory; + + entry = kgsl_get_mem_entry(pt_base, gpuaddr, size); + + if (entry) + return &entry->memdesc; + + return adreno_find_ctxtmem(device, pt_base, gpuaddr, size); +} + +uint8_t *adreno_convertaddr(struct kgsl_device *device, unsigned int pt_base, + unsigned int gpuaddr, unsigned int size) +{ + struct kgsl_memdesc *memdesc; + + memdesc = adreno_find_region(device, pt_base, gpuaddr, size); + + return memdesc ? kgsl_gpuaddr_to_vaddr(memdesc, gpuaddr) : NULL; +} + +void adreno_regread(struct kgsl_device *device, unsigned int offsetwords, + unsigned int *value) +{ + unsigned int *reg; + BUG_ON(offsetwords*sizeof(uint32_t) >= device->reg_len); + reg = (unsigned int *)(device->reg_virt + (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->reg_len); + + if (!in_interrupt()) + kgsl_pre_hwaccess(device); + + kgsl_cffdump_regwrite(device->id, offsetwords << 2, value); + reg = (unsigned int *)(device->reg_virt + (offsetwords << 2)); + + /*ensure previous writes post before this one, + * i.e. act like normal writel() */ + wmb(); + __raw_writel(value, reg); +} + +static unsigned int _get_context_id(struct kgsl_context *k_ctxt) +{ + unsigned int context_id = KGSL_MEMSTORE_GLOBAL; + if (k_ctxt != NULL) { + struct adreno_context *a_ctxt = k_ctxt->devctxt; + if (k_ctxt->id == KGSL_CONTEXT_INVALID || a_ctxt == NULL) + context_id = KGSL_CONTEXT_INVALID; + else if (a_ctxt->flags & CTXT_FLAGS_PER_CONTEXT_TS) + context_id = k_ctxt->id; + } + + return context_id; +} + +static int kgsl_check_interrupt_timestamp(struct kgsl_device *device, + struct kgsl_context *context, unsigned int timestamp) +{ + int status; + unsigned int ref_ts, enableflag; + unsigned int context_id; + + mutex_lock(&device->mutex); + context_id = _get_context_id(context); + /* + * If the context ID is invalid, we are in a race with + * the context being destroyed by userspace so bail. + */ + if (context_id == KGSL_CONTEXT_INVALID) { + KGSL_DRV_WARN(device, "context was detached"); + status = -EINVAL; + goto unlock; + } + + status = kgsl_check_timestamp(device, context, timestamp); + if (!status) { + kgsl_sharedmem_readl(&device->memstore, &enableflag, + KGSL_MEMSTORE_OFFSET(context_id, ts_cmp_enable)); + mb(); + + if (enableflag) { + kgsl_sharedmem_readl(&device->memstore, &ref_ts, + KGSL_MEMSTORE_OFFSET(context_id, + ref_wait_ts)); + mb(); + if (timestamp_cmp(ref_ts, timestamp) >= 0) { + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, + ref_wait_ts), timestamp); + wmb(); + } + } else { + unsigned int cmds[2]; + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, + ref_wait_ts), timestamp); + enableflag = 1; + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, + 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] = cp_type3_packet(CP_NOP, 1); + cmds[1] = 0; + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + &cmds[0], 2); + } + } +unlock: + mutex_unlock(&device->mutex); + + return status; +} + +/* + wait_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_event_interruptible_timeout(wq, condition, timeout, io)\ +({ \ + long __ret = timeout; \ + if (io) \ + __wait_io_event_interruptible_timeout(wq, condition, __ret);\ + else \ + __wait_event_interruptible_timeout(wq, condition, __ret);\ + __ret; \ +}) + +/* MUST be called with the device mutex held */ +static int adreno_waittimestamp(struct kgsl_device *device, + struct kgsl_context *context, + unsigned int timestamp, + unsigned int msecs) +{ + long status = 0; + uint io = 1; + static uint io_cnt; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + int retries; + unsigned int msecs_first; + unsigned int msecs_part; + unsigned int ts_issued; + unsigned int context_id = _get_context_id(context); + + ts_issued = adreno_dev->ringbuffer.timestamp[context_id]; + + /* Don't wait forever, set a max value for now */ + if (msecs == -1) + msecs = adreno_dev->wait_timeout; + + if (timestamp_cmp(timestamp, ts_issued) > 0) { + KGSL_DRV_ERR(device, "Cannot wait for invalid ts <%d:0x%x>, " + "last issued ts <%d:0x%x>\n", + context_id, timestamp, context_id, ts_issued); + status = -EINVAL; + goto done; + } + + /* Keep the first timeout as 100msecs before rewriting + * the WPTR. Less visible impact if the WPTR has not + * been updated properly. + */ + msecs_first = (msecs <= 100) ? ((msecs + 4) / 5) : 100; + msecs_part = (msecs - msecs_first + 3) / 4; + for (retries = 0; retries < 5; retries++) { + /* + * If the context ID is invalid, we are in a race with + * the context being destroyed by userspace so bail. + */ + if (context_id == KGSL_CONTEXT_INVALID) { + KGSL_DRV_WARN(device, "context was detached"); + status = -EINVAL; + goto done; + } + if (kgsl_check_timestamp(device, context, timestamp)) { + /* if the timestamp happens while we're not + * waiting, there's a chance that an interrupt + * will not be generated and thus the timestamp + * work needs to be queued. + */ + queue_work(device->work_queue, &device->ts_expired_ws); + status = 0; + goto done; + } + adreno_poke(device); + io_cnt = (io_cnt + 1) % 100; + if (io_cnt < + pwr->pwrlevels[pwr->active_pwrlevel].io_fraction) + io = 0; + 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_event_interruptible_timeout( + device->wait_queue, + kgsl_check_interrupt_timestamp(device, + context, timestamp), + msecs_to_jiffies(retries ? + msecs_part : msecs_first), io); + mutex_lock(&device->mutex); + + if (status > 0) { + /*completed before the wait finished */ + status = 0; + goto done; + } else if (status < 0) { + /*an error occurred*/ + goto done; + } + /*this wait timed out*/ + } + status = -ETIMEDOUT; + KGSL_DRV_ERR(device, + "Device hang detected while waiting for timestamp: " + "<%d:0x%x>, last submitted timestamp: <%d:0x%x>, " + "wptr: 0x%x\n", + context_id, timestamp, context_id, ts_issued, + 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, + struct kgsl_context *context, enum kgsl_timestamp_type type) +{ + unsigned int timestamp = 0; + unsigned int context_id = _get_context_id(context); + + /* + * If the context ID is invalid, we are in a race with + * the context being destroyed by userspace so bail. + */ + if (context_id == KGSL_CONTEXT_INVALID) { + KGSL_DRV_WARN(device, "context was detached"); + return timestamp; + } + switch (type) { + case KGSL_TIMESTAMP_QUEUED: { + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + timestamp = rb->timestamp[context_id]; + break; + } + case KGSL_TIMESTAMP_CONSUMED: + adreno_regread(device, REG_CP_TIMESTAMP, ×tamp); + break; + case KGSL_TIMESTAMP_RETIRED: + kgsl_sharedmem_readl(&device->memstore, ×tamp, + KGSL_MEMSTORE_OFFSET(context_id, eoptimestamp)); + break; + } + + 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 = -ENOIOCTLCMD; + 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) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + unsigned int cycles; + + /* Get the busy cycles counted since the counter was last reset */ + /* Calling this function also resets and restarts the counter */ + + cycles = adreno_dev->gpudev->busy_cycles(adreno_dev); + + /* 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 = ktime_to_us(ktime_get()); + stats->total_time = tmp - pwr->time; + pwr->time = tmp; + stats->busy_time = adreno_ticks_to_us(cycles, device->pwrctrl. + pwrlevels[device->pwrctrl.active_pwrlevel]. + gpu_freq); + } else { + stats->total_time = 0; + stats->busy_time = 0; + pwr->time = ktime_to_us(ktime_get()); + } +} + +void adreno_irqctrl(struct kgsl_device *device, int state) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + adreno_dev->gpudev->irq_control(adreno_dev, state); +} + +static unsigned int adreno_gpuid(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + /* Standard KGSL gpuid format: + * top word is 0x0002 for 2D or 0x0003 for 3D + * Bottom word is core specific identifer + */ + + return (0x0003 << 16) | ((int) adreno_dev->gpurev); +} + +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, + .gpuid = adreno_gpuid, + .snapshot = adreno_snapshot, + .irq_handler = adreno_irq_handler, + /* Optional functions */ + .setstate = adreno_setstate, + .drawctxt_create = adreno_drawctxt_create, + .drawctxt_destroy = adreno_drawctxt_destroy, + .setproperty = adreno_setproperty, +}; + +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 0000000000000000000000000000000000000000..4ce56a46bcde111a91e9d64181fe93dd4b67e3ce --- /dev/null +++ b/drivers/gpu/msm/adreno.h @@ -0,0 +1,293 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" +#include "kgsl_iommu.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_NONE 0x00000000 +#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 0x2EADBEEF +#define KGSL_CMD_IDENTIFIER 0x2EEDFACE +#define KGSL_START_OF_IB_IDENTIFIER 0x2EADEABE +#define KGSL_END_OF_IB_IDENTIFIER 0x2ABEDEAD + +#ifdef CONFIG_MSM_SCM +#define ADRENO_DEFAULT_PWRSCALE_POLICY (&kgsl_pwrscale_policy_tz) +#elif defined CONFIG_MSM_SLEEP_STATS_DEVICE +#define ADRENO_DEFAULT_PWRSCALE_POLICY (&kgsl_pwrscale_policy_idlestats) +#else +#define ADRENO_DEFAULT_PWRSCALE_POLICY NULL +#endif + +#define ADRENO_ISTORE_START 0x5000 /* Istore offset */ + +enum adreno_gpurev { + ADRENO_REV_UNKNOWN = 0, + ADRENO_REV_A200 = 200, + ADRENO_REV_A203 = 203, + ADRENO_REV_A205 = 205, + ADRENO_REV_A220 = 220, + ADRENO_REV_A225 = 225, + ADRENO_REV_A305 = 305, + ADRENO_REV_A320 = 320, +}; + +struct adreno_gpudev; + +struct adreno_device { + struct kgsl_device dev; /* Must be first field in this struct */ + unsigned int chip_id; + enum adreno_gpurev gpurev; + unsigned long gmem_base; + unsigned int gmem_size; + struct adreno_context *drawctxt_active; + const char *pfp_fwfile; + unsigned int *pfp_fw; + size_t pfp_fw_size; + const char *pm4_fwfile; + unsigned int *pm4_fw; + size_t pm4_fw_size; + struct adreno_ringbuffer ringbuffer; + unsigned int mharb; + struct adreno_gpudev *gpudev; + unsigned int wait_timeout; + unsigned int istore_size; + unsigned int pix_shader_start; + unsigned int instruction_size; + unsigned int ib_check_level; +}; + +struct adreno_gpudev { + /* + * These registers are in a different location on A3XX, so define + * them in the structure and use them as variables. + */ + unsigned int reg_rbbm_status; + unsigned int reg_cp_pfp_ucode_data; + unsigned int reg_cp_pfp_ucode_addr; + + /* GPU specific function hooks */ + int (*ctxt_create)(struct adreno_device *, struct adreno_context *); + void (*ctxt_save)(struct adreno_device *, struct adreno_context *); + void (*ctxt_restore)(struct adreno_device *, struct adreno_context *); + irqreturn_t (*irq_handler)(struct adreno_device *); + void (*irq_control)(struct adreno_device *, int); + void * (*snapshot)(struct adreno_device *, void *, int *, int); + void (*rb_init)(struct adreno_device *, struct adreno_ringbuffer *); + void (*start)(struct adreno_device *); + unsigned int (*busy_cycles)(struct adreno_device *); +}; + +extern struct adreno_gpudev adreno_a2xx_gpudev; +extern struct adreno_gpudev adreno_a3xx_gpudev; + +/* A2XX register sets defined in adreno_a2xx.c */ +extern const unsigned int a200_registers[]; +extern const unsigned int a220_registers[]; +extern const unsigned int a225_registers[]; +extern const unsigned int a200_registers_count; +extern const unsigned int a220_registers_count; +extern const unsigned int a225_registers_count; + +/* A3XX register set defined in adreno_a3xx.c */ +extern const unsigned int a3xx_registers[]; +extern const unsigned int a3xx_registers_count; + +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); + +struct kgsl_memdesc *adreno_find_region(struct kgsl_device *device, + unsigned int pt_base, + unsigned int gpuaddr, + unsigned int size); + +uint8_t *adreno_convertaddr(struct kgsl_device *device, + unsigned int pt_base, unsigned int gpuaddr, unsigned int size); + +struct kgsl_memdesc *adreno_find_ctxtmem(struct kgsl_device *device, + unsigned int pt_base, unsigned int gpuaddr, unsigned int size); + +void *adreno_snapshot(struct kgsl_device *device, void *snapshot, int *remain, + int hang); + +static inline int adreno_is_a200(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A200); +} + +static inline int adreno_is_a203(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A203); +} + +static inline int adreno_is_a205(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A205); +} + +static inline int adreno_is_a20x(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev <= 209); +} + +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); +} + +static inline int adreno_is_a2xx(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev <= 299); +} + +static inline int adreno_is_a3xx(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev >= 300); +} + +static inline int adreno_is_a305(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A305); +} + +static inline int adreno_is_a320(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A320); +} + +static inline int adreno_rb_ctxtswitch(unsigned int *cmd) +{ + return (cmd[0] == cp_nop_packet(1) && + cmd[1] == KGSL_CONTEXT_TO_MEM_IDENTIFIER); +} + +/** + * adreno_encode_istore_size - encode istore size in CP format + * @adreno_dev - The 3D device. + * + * Encode the istore size into the format expected that the + * CP_SET_SHADER_BASES and CP_ME_INIT commands: + * bits 31:29 - istore size as encoded by this function + * bits 27:16 - vertex shader start offset in instructions + * bits 11:0 - pixel shader start offset in instructions. + */ +static inline int adreno_encode_istore_size(struct adreno_device *adreno_dev) +{ + unsigned int size; + /* in a225 the CP microcode multiplies the encoded + * value by 3 while decoding. + */ + if (adreno_is_a225(adreno_dev)) + size = adreno_dev->istore_size/3; + else + size = adreno_dev->istore_size; + + return (ilog2(size) - 5) << 29; +} + +static inline int __adreno_add_idle_indirect_cmds(unsigned int *cmds, + unsigned int nop_gpuaddr) +{ + /* Adding an indirect buffer ensures that the prefetch stalls until + * the commands in indirect buffer have completed. We need to stall + * prefetch with a nop indirect buffer when updating pagetables + * because it provides stabler synchronization */ + *cmds++ = CP_HDR_INDIRECT_BUFFER_PFD; + *cmds++ = nop_gpuaddr; + *cmds++ = 2; + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + return 5; +} + +static inline int adreno_add_change_mh_phys_limit_cmds(unsigned int *cmds, + unsigned int new_phys_limit, + unsigned int nop_gpuaddr) +{ + unsigned int *start = cmds; + + cmds += __adreno_add_idle_indirect_cmds(cmds, nop_gpuaddr); + *cmds++ = cp_type0_packet(MH_MMU_MPU_END, 1); + *cmds++ = new_phys_limit; + cmds += __adreno_add_idle_indirect_cmds(cmds, nop_gpuaddr); + return cmds - start; +} + +static inline int adreno_add_bank_change_cmds(unsigned int *cmds, + int cur_ctx_bank, + unsigned int nop_gpuaddr) +{ + unsigned int *start = cmds; + + cmds += __adreno_add_idle_indirect_cmds(cmds, nop_gpuaddr); + *cmds++ = cp_type0_packet(REG_CP_STATE_DEBUG_INDEX, 1); + *cmds++ = (cur_ctx_bank ? 0 : 0x20); + cmds += __adreno_add_idle_indirect_cmds(cmds, nop_gpuaddr); + return cmds - start; +} + +/* + * adreno_read_cmds - Add pm4 packets to perform read + * @device - Pointer to device structure + * @cmds - Pointer to memory where read commands need to be added + * @addr - gpu address of the read + * @val - The GPU will wait until the data at address addr becomes + * equal to value + */ +static inline int adreno_add_read_cmds(struct kgsl_device *device, + unsigned int *cmds, unsigned int addr, + unsigned int val, unsigned int nop_gpuaddr) +{ + unsigned int *start = cmds; + + *cmds++ = cp_type3_packet(CP_WAIT_REG_MEM, 5); + /* MEM SPACE = memory, FUNCTION = equals */ + *cmds++ = 0x13; + *cmds++ = addr; + *cmds++ = val; + *cmds++ = 0xFFFFFFFF; + *cmds++ = 0xFFFFFFFF; + cmds += __adreno_add_idle_indirect_cmds(cmds, nop_gpuaddr); + return cmds - start; +} + +#endif /*__ADRENO_H */ diff --git a/drivers/gpu/msm/adreno_a2xx.c b/drivers/gpu/msm/adreno_a2xx.c new file mode 100644 index 0000000000000000000000000000000000000000..7ccfd3f87be4c00f44dfd488e5d3dedc0dbd806c --- /dev/null +++ b/drivers/gpu/msm/adreno_a2xx.c @@ -0,0 +1,1992 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even 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 "adreno.h" +#include "adreno_a2xx_trace.h" + +/* + * These are the registers that are dumped with GPU snapshot + * and postmortem. The lists are dword offset pairs in the + * form of {start offset, end offset} inclusive. + */ + +/* A200, A205 */ +const unsigned int a200_registers[] = { + 0x0000, 0x0002, 0x0004, 0x000B, 0x003B, 0x003D, 0x0040, 0x0044, + 0x0046, 0x0047, 0x01C0, 0x01C1, 0x01C3, 0x01C8, 0x01D5, 0x01D9, + 0x01DC, 0x01DD, 0x01EA, 0x01EA, 0x01EE, 0x01F3, 0x01F6, 0x01F7, + 0x01FC, 0x01FF, 0x0391, 0x0392, 0x039B, 0x039E, 0x03B2, 0x03B5, + 0x03B7, 0x03B7, 0x03F8, 0x03FB, 0x0440, 0x0440, 0x0443, 0x0444, + 0x044B, 0x044B, 0x044D, 0x044F, 0x0452, 0x0452, 0x0454, 0x045B, + 0x047F, 0x047F, 0x0578, 0x0587, 0x05C9, 0x05C9, 0x05D0, 0x05D0, + 0x0601, 0x0604, 0x0606, 0x0609, 0x060B, 0x060E, 0x0613, 0x0614, + 0x0A29, 0x0A2B, 0x0A2F, 0x0A31, 0x0A40, 0x0A43, 0x0A45, 0x0A45, + 0x0A4E, 0x0A4F, 0x0C2C, 0x0C2C, 0x0C30, 0x0C30, 0x0C38, 0x0C3C, + 0x0C40, 0x0C40, 0x0C44, 0x0C44, 0x0C80, 0x0C86, 0x0C88, 0x0C94, + 0x0C99, 0x0C9A, 0x0CA4, 0x0CA5, 0x0D00, 0x0D03, 0x0D06, 0x0D06, + 0x0D08, 0x0D0B, 0x0D34, 0x0D35, 0x0DAE, 0x0DC1, 0x0DC8, 0x0DD4, + 0x0DD8, 0x0DD9, 0x0E00, 0x0E00, 0x0E02, 0x0E04, 0x0E17, 0x0E1E, + 0x0EC0, 0x0EC9, 0x0ECB, 0x0ECC, 0x0ED0, 0x0ED0, 0x0ED4, 0x0ED7, + 0x0EE0, 0x0EE2, 0x0F01, 0x0F02, 0x0F0C, 0x0F0C, 0x0F0E, 0x0F12, + 0x0F26, 0x0F2A, 0x0F2C, 0x0F2C, 0x2000, 0x2002, 0x2006, 0x200F, + 0x2080, 0x2082, 0x2100, 0x2109, 0x210C, 0x2114, 0x2180, 0x2184, + 0x21F5, 0x21F7, 0x2200, 0x2208, 0x2280, 0x2283, 0x2293, 0x2294, + 0x2300, 0x2308, 0x2312, 0x2312, 0x2316, 0x231D, 0x2324, 0x2326, + 0x2380, 0x2383, 0x2400, 0x2402, 0x2406, 0x240F, 0x2480, 0x2482, + 0x2500, 0x2509, 0x250C, 0x2514, 0x2580, 0x2584, 0x25F5, 0x25F7, + 0x2600, 0x2608, 0x2680, 0x2683, 0x2693, 0x2694, 0x2700, 0x2708, + 0x2712, 0x2712, 0x2716, 0x271D, 0x2724, 0x2726, 0x2780, 0x2783, + 0x4000, 0x4003, 0x4800, 0x4805, 0x4900, 0x4900, 0x4908, 0x4908, +}; + +const unsigned int a220_registers[] = { + 0x0000, 0x0002, 0x0004, 0x000B, 0x003B, 0x003D, 0x0040, 0x0044, + 0x0046, 0x0047, 0x01C0, 0x01C1, 0x01C3, 0x01C8, 0x01D5, 0x01D9, + 0x01DC, 0x01DD, 0x01EA, 0x01EA, 0x01EE, 0x01F3, 0x01F6, 0x01F7, + 0x01FC, 0x01FF, 0x0391, 0x0392, 0x039B, 0x039E, 0x03B2, 0x03B5, + 0x03B7, 0x03B7, 0x03F8, 0x03FB, 0x0440, 0x0440, 0x0443, 0x0444, + 0x044B, 0x044B, 0x044D, 0x044F, 0x0452, 0x0452, 0x0454, 0x045B, + 0x047F, 0x047F, 0x0578, 0x0587, 0x05C9, 0x05C9, 0x05D0, 0x05D0, + 0x0601, 0x0604, 0x0606, 0x0609, 0x060B, 0x060E, 0x0613, 0x0614, + 0x0A29, 0x0A2B, 0x0A2F, 0x0A31, 0x0A40, 0x0A40, 0x0A42, 0x0A43, + 0x0A45, 0x0A45, 0x0A4E, 0x0A4F, 0x0C30, 0x0C30, 0x0C38, 0x0C39, + 0x0C3C, 0x0C3C, 0x0C80, 0x0C81, 0x0C88, 0x0C93, 0x0D00, 0x0D03, + 0x0D05, 0x0D06, 0x0D08, 0x0D0B, 0x0D34, 0x0D35, 0x0DAE, 0x0DC1, + 0x0DC8, 0x0DD4, 0x0DD8, 0x0DD9, 0x0E00, 0x0E00, 0x0E02, 0x0E04, + 0x0E17, 0x0E1E, 0x0EC0, 0x0EC9, 0x0ECB, 0x0ECC, 0x0ED0, 0x0ED0, + 0x0ED4, 0x0ED7, 0x0EE0, 0x0EE2, 0x0F01, 0x0F02, 0x2000, 0x2002, + 0x2006, 0x200F, 0x2080, 0x2082, 0x2100, 0x2102, 0x2104, 0x2109, + 0x210C, 0x2114, 0x2180, 0x2184, 0x21F5, 0x21F7, 0x2200, 0x2202, + 0x2204, 0x2204, 0x2208, 0x2208, 0x2280, 0x2282, 0x2294, 0x2294, + 0x2300, 0x2308, 0x2309, 0x230A, 0x2312, 0x2312, 0x2316, 0x2316, + 0x2318, 0x231D, 0x2324, 0x2326, 0x2380, 0x2383, 0x2400, 0x2402, + 0x2406, 0x240F, 0x2480, 0x2482, 0x2500, 0x2502, 0x2504, 0x2509, + 0x250C, 0x2514, 0x2580, 0x2584, 0x25F5, 0x25F7, 0x2600, 0x2602, + 0x2604, 0x2606, 0x2608, 0x2608, 0x2680, 0x2682, 0x2694, 0x2694, + 0x2700, 0x2708, 0x2712, 0x2712, 0x2716, 0x2716, 0x2718, 0x271D, + 0x2724, 0x2726, 0x2780, 0x2783, 0x4000, 0x4003, 0x4800, 0x4805, + 0x4900, 0x4900, 0x4908, 0x4908, +}; + +const unsigned int a225_registers[] = { + 0x0000, 0x0002, 0x0004, 0x000B, 0x003B, 0x003D, 0x0040, 0x0044, + 0x0046, 0x0047, 0x013C, 0x013C, 0x0140, 0x014F, 0x01C0, 0x01C1, + 0x01C3, 0x01C8, 0x01D5, 0x01D9, 0x01DC, 0x01DD, 0x01EA, 0x01EA, + 0x01EE, 0x01F3, 0x01F6, 0x01F7, 0x01FC, 0x01FF, 0x0391, 0x0392, + 0x039B, 0x039E, 0x03B2, 0x03B5, 0x03B7, 0x03B7, 0x03F8, 0x03FB, + 0x0440, 0x0440, 0x0443, 0x0444, 0x044B, 0x044B, 0x044D, 0x044F, + 0x0452, 0x0452, 0x0454, 0x045B, 0x047F, 0x047F, 0x0578, 0x0587, + 0x05C9, 0x05C9, 0x05D0, 0x05D0, 0x0601, 0x0604, 0x0606, 0x0609, + 0x060B, 0x060E, 0x0613, 0x0614, 0x0A29, 0x0A2B, 0x0A2F, 0x0A31, + 0x0A40, 0x0A40, 0x0A42, 0x0A43, 0x0A45, 0x0A45, 0x0A4E, 0x0A4F, + 0x0C01, 0x0C1D, 0x0C30, 0x0C30, 0x0C38, 0x0C39, 0x0C3C, 0x0C3C, + 0x0C80, 0x0C81, 0x0C88, 0x0C93, 0x0D00, 0x0D03, 0x0D05, 0x0D06, + 0x0D08, 0x0D0B, 0x0D34, 0x0D35, 0x0DAE, 0x0DC1, 0x0DC8, 0x0DD4, + 0x0DD8, 0x0DD9, 0x0E00, 0x0E00, 0x0E02, 0x0E04, 0x0E17, 0x0E1E, + 0x0EC0, 0x0EC9, 0x0ECB, 0x0ECC, 0x0ED0, 0x0ED0, 0x0ED4, 0x0ED7, + 0x0EE0, 0x0EE2, 0x0F01, 0x0F02, 0x2000, 0x200F, 0x2080, 0x2082, + 0x2100, 0x2109, 0x210C, 0x2114, 0x2180, 0x2184, 0x21F5, 0x21F7, + 0x2200, 0x2202, 0x2204, 0x2206, 0x2208, 0x2210, 0x2220, 0x2222, + 0x2280, 0x2282, 0x2294, 0x2294, 0x2297, 0x2297, 0x2300, 0x230A, + 0x2312, 0x2312, 0x2315, 0x2316, 0x2318, 0x231D, 0x2324, 0x2326, + 0x2340, 0x2357, 0x2360, 0x2360, 0x2380, 0x2383, 0x2400, 0x240F, + 0x2480, 0x2482, 0x2500, 0x2509, 0x250C, 0x2514, 0x2580, 0x2584, + 0x25F5, 0x25F7, 0x2600, 0x2602, 0x2604, 0x2606, 0x2608, 0x2610, + 0x2620, 0x2622, 0x2680, 0x2682, 0x2694, 0x2694, 0x2697, 0x2697, + 0x2700, 0x270A, 0x2712, 0x2712, 0x2715, 0x2716, 0x2718, 0x271D, + 0x2724, 0x2726, 0x2740, 0x2757, 0x2760, 0x2760, 0x2780, 0x2783, + 0x4000, 0x4003, 0x4800, 0x4806, 0x4808, 0x4808, 0x4900, 0x4900, + 0x4908, 0x4908, +}; + +const unsigned int a200_registers_count = ARRAY_SIZE(a200_registers) / 2; +const unsigned int a220_registers_count = ARRAY_SIZE(a220_registers) / 2; +const unsigned int a225_registers_count = ARRAY_SIZE(a225_registers) / 2; + +/* + * + * 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 */ + +/* LOAD_CONSTANT_CONTEXT shadow size */ +#define LCC_SHADOW_SIZE 0x2000 /* 8KB */ + +#define ALU_SHADOW_SIZE LCC_SHADOW_SIZE /* 8KB */ +#define REG_SHADOW_SIZE 0x1000 /* 4KB */ +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES +#define CMD_BUFFER_SIZE 0x9000 /* 36KB */ +#else +#define CMD_BUFFER_SIZE 0x3000 /* 12KB */ +#endif +#define TEX_SHADOW_SIZE (TEX_CONSTANTS*4) /* 768 bytes */ + +#define REG_OFFSET LCC_SHADOW_SIZE +#define CMD_OFFSET (REG_OFFSET + REG_SHADOW_SIZE) +#define TEX_OFFSET (CMD_OFFSET + CMD_BUFFER_SIZE) +#define SHADER_OFFSET ((TEX_OFFSET + TEX_SHADOW_SIZE + 32) & ~31) + +static inline int _shader_shadow_size(struct adreno_device *adreno_dev) +{ + return adreno_dev->istore_size * + (adreno_dev->instruction_size * sizeof(unsigned int)); +} + +static inline int _context_size(struct adreno_device *adreno_dev) +{ + return SHADER_OFFSET + 3*_shader_shadow_size(adreno_dev); +} + +/* A scratchpad used to build commands during context create */ + +static struct tmp_ctx { + unsigned int *start; /* Command & Vertex buffer start */ + unsigned int *cmd; /* Next available dword in C&V buffer */ + + /* address of buffers, needed when creating IB1 command buffers. */ + uint32_t bool_shadow; /* bool constants */ + uint32_t loop_shadow; /* loop constants */ + + uint32_t shader_shared; /* shared shader instruction shadow */ + uint32_t shader_vertex; /* vertex shader instruction shadow */ + uint32_t shader_pixel; /* pixel shader instruction shadow */ + + /* Addresses in command buffer where separately handled registers + * are saved + */ + uint32_t reg_values[33]; + uint32_t chicken_restore; + + uint32_t gmem_base; /* Base gpu address of GMEM */ + +} tmp_ctx; + +/* 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 */ +}; + +#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) + + +static unsigned int *program_shader(unsigned int *cmds, int vtxfrag, + unsigned int *shader_pgm, int dwords) +{ + /* load the patched vertex shader stream */ + *cmds++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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)++ = cp_type3_packet(CP_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) +{ + unsigned int *start = tmp_ctx.cmd; + unsigned int *cmds = start; + + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0; + + *cmds++ = cp_type0_packet(REG_TP0_CHICKEN, 1); + tmp_ctx.chicken_restore = virt2gpu(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_a220[] = { + 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_A220_PC_MAX_VTX_INDX, REG_A220_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_A220_PC_VERTEX_REUSE_BLOCK_CNTL, + REG_A220_PC_VERTEX_REUSE_BLOCK_CNTL, + REG_RB_COPY_CONTROL, REG_RB_DEPTH_CLEAR +}; + +static const unsigned int register_ranges_a225[] = { + REG_RB_SURFACE_INFO, REG_A225_RB_COLOR_INFO3, + 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_A220_PC_MAX_VTX_INDX, REG_A225_PC_MULTI_PRIM_IB_RESET_INDX, + 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_A220_PC_VERTEX_REUSE_BLOCK_CNTL, + REG_A220_PC_VERTEX_REUSE_BLOCK_CNTL, + REG_RB_COPY_CONTROL, REG_RB_DEPTH_CLEAR, + REG_A225_GRAS_UCP0X, REG_A225_GRAS_UCP5W, + REG_A225_GRAS_UCP_ENABLED, REG_A225_GRAS_UCP_ENABLED +}; + + +/* 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) +{ + unsigned int *start = tmp_ctx.cmd; + unsigned int *cmd = start; + + *cmd++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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_a220; + reg_array_size = ARRAY_SIZE(register_ranges_a220); + } else if (adreno_is_a225(adreno_dev)) { + ptr_register_ranges = register_ranges_a225; + reg_array_size = ARRAY_SIZE(register_ranges_a225); + } 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++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* H/w registers are already shadowed; just need to disable shadowing + * to prevent corruption. + */ + *cmd++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = REG_SQ_GPR_MANAGEMENT; + *cmd++ = tmp_ctx.reg_values[0]; + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = REG_TP0_CHICKEN; + *cmd++ = tmp_ctx.reg_values[1]; + + if (adreno_is_a22x(adreno_dev)) { + unsigned int i; + unsigned int j = 2; + for (i = REG_A220_VSC_BIN_SIZE; i <= + REG_A220_VSC_PIPE_DATA_LENGTH_7; i++) { + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = i; + *cmd++ = tmp_ctx.reg_values[j]; + j++; + } + } + + /* Copy Boolean constants */ + cmd = reg_to_mem(cmd, tmp_ctx.bool_shadow, REG_SQ_CF_BOOLEANS, + BOOL_CONSTANTS); + + /* Copy Loop constants */ + cmd = reg_to_mem(cmd, tmp_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); + + tmp_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 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; + + if (!(drawctxt->flags & CTXT_FLAGS_PREAMBLE)) { + /* Store TP0_CHICKEN register */ + *cmds++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmds++ = REG_TP0_CHICKEN; + + *cmds++ = tmp_ctx.chicken_restore; + + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0; + } + + /* Set TP0_CHICKEN to zero */ + *cmds++ = cp_type0_packet(REG_TP0_CHICKEN, 1); + *cmds++ = 0x00000000; + + /* Set PA_SC_AA_CONFIG to 0 */ + *cmds++ = cp_type0_packet(REG_PA_SC_AA_CONFIG, 1); + *cmds++ = 0x00000000; + + /* program shader */ + + /* load shader vtx constants ... 5 dwords */ + *cmds++ = cp_type3_packet(CP_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++ = cp_type0_packet(REG_TC_CNTL_STATUS, 1); + *cmds++ = 0x1; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 4); + *cmds++ = CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_SC_AA_MASK); + *cmds++ = 0x0000ffff; /* REG_PA_SC_AA_MASK */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_COLORCONTROL); + *cmds++ = 0x00000c20; + + /* Repartition shaders */ + *cmds++ = cp_type0_packet(REG_SQ_INST_STORE_MANAGMENT, 1); + *cmds++ = adreno_dev->pix_shader_start; + + /* Invalidate Vertex & Pixel instruction code address and sizes */ + *cmds++ = cp_type3_packet(CP_INVALIDATE_STATE, 1); + *cmds++ = 0x00003F00; + + *cmds++ = cp_type3_packet(CP_SET_SHADER_BASES, 1); + *cmds++ = adreno_encode_istore_size(adreno_dev) + | adreno_dev->pix_shader_start; + + /* 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++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_SQ_PROGRAM_CNTL); + if (adreno_is_a22x(adreno_dev)) + *cmds++ = 0x10018001; + else + *cmds++ = 0x10010001; + *cmds++ = 0x00000008; + + /* resolve */ + + /* PA_CL_VTE_CNTL */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_CL_VTE_CNTL); + /* disable X/Y/Z transforms, X/Y/Z are premultiplied by W */ + *cmds++ = 0x00000b00; + + /* program surface info */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_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. */ + BUG_ON(tmp_ctx.gmem_base & 0xFFF); + *cmds++ = + (shadow-> + format << RB_COLOR_INFO__COLOR_FORMAT__SHIFT) | tmp_ctx.gmem_base; + + /* disable Z */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_DEPTHCONTROL); + if (adreno_is_a22x(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++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_SU_SC_MODE_CNTL); + *cmds++ = 0x00080240; + + /* Use maximum scissor values -- quad vertices already have the + * correct bounds */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_PA_SC_SCREEN_SCISSOR_TL); + *cmds++ = (0 << 16) | 0; + *cmds++ = (0x1fff << 16) | (0x1fff); + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_PA_CL_VPORT_ZSCALE); + *cmds++ = 0xbf800000; /* -1.0f */ + *cmds++ = 0x0; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_COLOR_MASK); + *cmds++ = 0x0000000f; /* R = G = B = 1:enabled */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_COLOR_DEST_MASK); + *cmds++ = 0xffffffff; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_SQ_WRAPPING_0); + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + + /* load the stencil ref value + * $AAM - do this later + */ + + /* load the COPY state */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 6); + *cmds++ = CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_MODECONTROL); + *cmds++ = 0x6; /* EDRAM copy */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_CL_CLIP_CNTL); + *cmds++ = 0x00010000; + + if (adreno_is_a22x(adreno_dev)) { + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_A220_RB_LRZ_VSC_CONTROL); + *cmds++ = 0x0000000; + + *cmds++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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 gmem_shadow_t *shadow) +{ + unsigned int *cmds = shadow->gmem_restore_commands; + unsigned int *start = cmds; + + if (!(drawctxt->flags & CTXT_FLAGS_PREAMBLE)) { + /* Store TP0_CHICKEN register */ + *cmds++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmds++ = REG_TP0_CHICKEN; + *cmds++ = tmp_ctx.chicken_restore; + + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0; + } + + /* Set TP0_CHICKEN to zero */ + *cmds++ = cp_type0_packet(REG_TP0_CHICKEN, 1); + *cmds++ = 0x00000000; + + /* Set PA_SC_AA_CONFIG to 0 */ + *cmds++ = cp_type0_packet(REG_PA_SC_AA_CONFIG, 1); + *cmds++ = 0x00000000; + /* shader constants */ + + /* vertex buffer constants */ + *cmds++ = cp_type3_packet(CP_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++ = cp_type0_packet(REG_TC_CNTL_STATUS, 1); + *cmds++ = 0x1; + + cmds = program_shader(cmds, 0, sys2gmem_vtx_pgm, SYS2GMEM_VTX_PGM_LEN); + + /* Repartition shaders */ + *cmds++ = cp_type0_packet(REG_SQ_INST_STORE_MANAGMENT, 1); + *cmds++ = adreno_dev->pix_shader_start; + + /* Invalidate Vertex & Pixel instruction code address and sizes */ + *cmds++ = cp_type3_packet(CP_INVALIDATE_STATE, 1); + *cmds++ = 0x00000300; /* 0x100 = Vertex, 0x200 = Pixel */ + + *cmds++ = cp_type3_packet(CP_SET_SHADER_BASES, 1); + *cmds++ = adreno_encode_istore_size(adreno_dev) + | adreno_dev->pix_shader_start; + + /* 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++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_SQ_PROGRAM_CNTL); + *cmds++ = 0x10030002; + *cmds++ = 0x00000008; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_SC_AA_MASK); + *cmds++ = 0x0000ffff; /* REG_PA_SC_AA_MASK */ + + if (!adreno_is_a22x(adreno_dev)) { + /* PA_SC_VIZ_QUERY */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_SC_VIZ_QUERY); + *cmds++ = 0x0; /*REG_PA_SC_VIZ_QUERY */ + } + + /* RB_COLORCONTROL */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_COLORCONTROL); + *cmds++ = 0x00000c20; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 4); + *cmds++ = CP_REG(REG_VGT_MAX_VTX_INDX); + *cmds++ = 0x00ffffff; /* mmVGT_MAX_VTX_INDX */ + *cmds++ = 0x0; /* mmVGT_MIN_VTX_INDX */ + *cmds++ = 0x00000000; /* mmVGT_INDX_OFFSET */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_VGT_VERTEX_REUSE_BLOCK_CNTL); + *cmds++ = 0x00000002; /* mmVGT_VERTEX_REUSE_BLOCK_CNTL */ + *cmds++ = 0x00000002; /* mmVGT_OUT_DEALLOC_CNTL */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_SQ_INTERPOLATOR_CNTL); + *cmds++ = 0xffffffff; /* mmSQ_INTERPOLATOR_CNTL */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_SU_SC_MODE_CNTL); + *cmds++ = 0x00080240; + + /* texture constants */ + *cmds++ = + cp_type3_packet(CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_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 + */ + *cmds++ = + (shadow-> + format << RB_COLOR_INFO__COLOR_FORMAT__SHIFT) | tmp_ctx.gmem_base; + + /* RB_DEPTHCONTROL */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_DEPTHCONTROL); + + if (adreno_is_a22x(adreno_dev)) + *cmds++ = 8; /* disable Z */ + else + *cmds++ = 0; /* disable Z */ + + /* Use maximum scissor values -- quad vertices already + * have the correct bounds */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_PA_SC_SCREEN_SCISSOR_TL); + *cmds++ = (0 << 16) | 0; + *cmds++ = ((0x1fff) << 16) | 0x1fff; + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_PA_SC_WINDOW_SCISSOR_TL); + *cmds++ = (unsigned int)((1U << 31) | (0 << 16) | 0); + *cmds++ = ((0x1fff) << 16) | 0x1fff; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_PA_CL_VPORT_ZSCALE); + *cmds++ = 0xbf800000; + *cmds++ = 0x0; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_COLOR_MASK); + *cmds++ = 0x0000000f; /* R = G = B = 1:enabled */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_COLOR_DEST_MASK); + *cmds++ = 0xffffffff; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(REG_SQ_WRAPPING_0); + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + + /* load the stencil ref value + * $AAM - do this later + */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_RB_MODECONTROL); + /* draw pixels with color and depth/stencil component */ + *cmds++ = 0x4; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_PA_CL_CLIP_CNTL); + *cmds++ = 0x00010000; + + if (adreno_is_a22x(adreno_dev)) { + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(REG_A220_RB_LRZ_VSC_CONTROL); + *cmds++ = 0x0000000; + + *cmds++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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; +} + +static void build_regrestore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *start = tmp_ctx.cmd; + unsigned int *cmd = start; + + unsigned int i = 0; + unsigned int reg_array_size = 0; + const unsigned int *ptr_register_ranges; + + *cmd++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* H/W Registers */ + /* deferred cp_type3_packet(CP_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_a220; + reg_array_size = ARRAY_SIZE(register_ranges_a220); + } else if (adreno_is_a225(adreno_dev)) { + ptr_register_ranges = register_ranges_a225; + reg_array_size = ARRAY_SIZE(register_ranges_a225); + } 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] = + cp_type3_packet(CP_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++ = cp_type0_packet(REG_SQ_GPR_MANAGEMENT, 1); + tmp_ctx.reg_values[0] = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0x00040400; + + *cmd++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + *cmd++ = cp_type0_packet(REG_TP0_CHICKEN, 1); + tmp_ctx.reg_values[1] = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0x00000000; + + if (adreno_is_a22x(adreno_dev)) { + unsigned int i; + unsigned int j = 2; + for (i = REG_A220_VSC_BIN_SIZE; i <= + REG_A220_VSC_PIPE_DATA_LENGTH_7; i++) { + *cmd++ = cp_type0_packet(i, 1); + tmp_ctx.reg_values[j] = virt2gpu(cmd, + &drawctxt->gpustate); + *cmd++ = 0x00000000; + j++; + } + } + + /* ALU Constants */ + *cmd++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_SET_CONSTANT, 1 + BOOL_CONSTANTS); + *cmd++ = (2 << 16) | 0; + + /* the next BOOL_CONSTANT dwords is the shadow area for + * boolean constants. + */ + tmp_ctx.bool_shadow = virt2gpu(cmd, &drawctxt->gpustate); + cmd += BOOL_CONSTANTS; + + /* Loop Constants */ + *cmd++ = cp_type3_packet(CP_SET_CONSTANT, 1 + LOOP_CONSTANTS); + *cmd++ = (3 << 16) | 0; + + /* the next LOOP_CONSTANTS dwords is the shadow area for + * loop constants. + */ + tmp_ctx.loop_shadow = virt2gpu(cmd, &drawctxt->gpustate); + cmd += LOOP_CONSTANTS; + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->reg_restore, start, cmd); + + tmp_ctx.cmd = cmd; +} + +static void +build_shader_save_restore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *save, *restore, *fixup; + unsigned int *startSizeVtx, *startSizePix, *startSizeShared; + unsigned int *partition1; + unsigned int *shaderBases, *partition2; + + /* compute vertex, pixel and shared instruction shadow GPU addresses */ + tmp_ctx.shader_vertex = drawctxt->gpustate.gpuaddr + SHADER_OFFSET; + tmp_ctx.shader_pixel = tmp_ctx.shader_vertex + + _shader_shadow_size(adreno_dev); + tmp_ctx.shader_shared = tmp_ctx.shader_pixel + + _shader_shadow_size(adreno_dev); + + /* restore shader partitioning and instructions */ + + restore = cmd; /* start address */ + + /* Invalidate Vertex & Pixel instruction code address and sizes */ + *cmd++ = cp_type3_packet(CP_INVALIDATE_STATE, 1); + *cmd++ = 0x00000300; /* 0x100 = Vertex, 0x200 = Pixel */ + + /* Restore previous shader vertex & pixel instruction bases. */ + *cmd++ = cp_type3_packet(CP_SET_SHADER_BASES, 1); + shaderBases = cmd++; /* TBD #5: shader bases (from fixup) */ + + /* write the shader partition information to a scratch register */ + *cmd++ = cp_type0_packet(REG_SQ_INST_STORE_MANAGMENT, 1); + partition1 = cmd++; /* TBD #4a: partition info (from save) */ + + /* load vertex shader instructions from the shadow. */ + *cmd++ = cp_type3_packet(CP_IM_LOAD, 2); + *cmd++ = tmp_ctx.shader_vertex + 0x0; /* 0x0 = Vertex */ + startSizeVtx = cmd++; /* TBD #1: start/size (from save) */ + + /* load pixel shader instructions from the shadow. */ + *cmd++ = cp_type3_packet(CP_IM_LOAD, 2); + *cmd++ = tmp_ctx.shader_pixel + 0x1; /* 0x1 = Pixel */ + startSizePix = cmd++; /* TBD #2: start/size (from save) */ + + /* load shared shader instructions from the shadow. */ + *cmd++ = cp_type3_packet(CP_IM_LOAD, 2); + *cmd++ = tmp_ctx.shader_shared + 0x2; /* 0x2 = Shared */ + startSizeShared = cmd++; /* TBD #3: start/size (from save) */ + + /* 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++ = cp_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++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = REG_SCRATCH_REG2; + /* AND off invalid bits. */ + *cmd++ = 0x0FFF0FFF; + /* OR in instruction memory size. */ + *cmd++ = adreno_encode_istore_size(adreno_dev); + + /* write the computed value to the SET_SHADER_BASES data field */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = REG_SCRATCH_REG2; + /* TBD #5: shader bases (to restore) */ + *cmd++ = virt2gpu(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++ = cp_type3_packet(CP_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++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = REG_SQ_INST_STORE_MANAGMENT; + /* TBD #4a: partition info (to restore) */ + *cmd++ = virt2gpu(partition1, &drawctxt->gpustate); + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = REG_SQ_INST_STORE_MANAGMENT; + /* TBD #4b: partition info (to fixup) */ + *cmd++ = virt2gpu(partition2, &drawctxt->gpustate); + + + /* store the vertex shader instructions */ + *cmd++ = cp_type3_packet(CP_IM_STORE, 2); + *cmd++ = tmp_ctx.shader_vertex + 0x0; /* 0x0 = Vertex */ + /* TBD #1: start/size (to restore) */ + *cmd++ = virt2gpu(startSizeVtx, &drawctxt->gpustate); + + /* store the pixel shader instructions */ + *cmd++ = cp_type3_packet(CP_IM_STORE, 2); + *cmd++ = tmp_ctx.shader_pixel + 0x1; /* 0x1 = Pixel */ + /* TBD #2: start/size (to restore) */ + *cmd++ = virt2gpu(startSizePix, &drawctxt->gpustate); + + /* store the shared shader instructions if vertex base is nonzero */ + + *cmd++ = cp_type3_packet(CP_IM_STORE, 2); + *cmd++ = tmp_ctx.shader_shared + 0x2; /* 0x2 = Shared */ + /* TBD #3: start/size (to restore) */ + *cmd++ = virt2gpu(startSizeShared, &drawctxt->gpustate); + + + *cmd++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->shader_save, save, cmd); + + tmp_ctx.cmd = cmd; +} + +/* create buffers for saving/restoring registers, constants, & GMEM */ +static int a2xx_create_gpustate_shadow(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + drawctxt->flags |= CTXT_FLAGS_STATE_SHADOW; + + /* build indirect command buffers to save & restore regs/constants */ + build_regrestore_cmds(adreno_dev, drawctxt); + build_regsave_cmds(adreno_dev, drawctxt); + + build_shader_save_restore_cmds(adreno_dev, drawctxt); + + return 0; +} + +/* create buffers for saving/restoring registers, constants, & GMEM */ +static int a2xx_create_gmem_shadow(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + int result; + + calc_gmemsize(&drawctxt->context_gmem_shadow, adreno_dev->gmem_size); + tmp_ctx.gmem_base = adreno_dev->gmem_base; + + result = kgsl_allocate(&drawctxt->context_gmem_shadow.gmemshadow, + drawctxt->pagetable, drawctxt->context_gmem_shadow.size); + + if (result) + return result; + + /* set the gmem shadow flag for the context */ + drawctxt->flags |= CTXT_FLAGS_GMEM_SHADOW; + + /* 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, &drawctxt->context_gmem_shadow, + &tmp_ctx.cmd); + + /* build TP0_CHICKEN register restore command buffer */ + if (!(drawctxt->flags & CTXT_FLAGS_PREAMBLE)) + tmp_ctx.cmd = build_chicken_restore_cmds(drawctxt); + + /* build indirect command buffers to save & restore gmem */ + drawctxt->context_gmem_shadow.gmem_save_commands = tmp_ctx.cmd; + tmp_ctx.cmd = + build_gmem2sys_cmds(adreno_dev, drawctxt, + &drawctxt->context_gmem_shadow); + drawctxt->context_gmem_shadow.gmem_restore_commands = tmp_ctx.cmd; + tmp_ctx.cmd = + build_sys2gmem_cmds(adreno_dev, drawctxt, + &drawctxt->context_gmem_shadow); + + kgsl_cache_range_op(&drawctxt->context_gmem_shadow.gmemshadow, + KGSL_CACHE_OP_FLUSH); + + kgsl_cffdump_syncmem(NULL, + &drawctxt->context_gmem_shadow.gmemshadow, + drawctxt->context_gmem_shadow.gmemshadow.gpuaddr, + drawctxt->context_gmem_shadow.gmemshadow.size, false); + + return 0; +} + +static int a2xx_drawctxt_create(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + int ret; + + /* + * Allocate memory for the GPU state and the context commands. + * Despite the name, this is much more then just storage for + * the gpustate. This contains command space for gmem save + * and texture and vertex buffer storage too + */ + + ret = kgsl_allocate(&drawctxt->gpustate, + drawctxt->pagetable, _context_size(adreno_dev)); + + if (ret) + return ret; + + kgsl_sharedmem_set(&drawctxt->gpustate, 0, 0, + _context_size(adreno_dev)); + + tmp_ctx.cmd = tmp_ctx.start + = (unsigned int *)((char *)drawctxt->gpustate.hostptr + CMD_OFFSET); + + if (!(drawctxt->flags & CTXT_FLAGS_PREAMBLE)) { + ret = a2xx_create_gpustate_shadow(adreno_dev, drawctxt); + if (ret) + goto done; + + drawctxt->flags |= CTXT_FLAGS_SHADER_SAVE; + } + + if (!(drawctxt->flags & CTXT_FLAGS_NOGMEMALLOC)) { + ret = a2xx_create_gmem_shadow(adreno_dev, drawctxt); + if (ret) + goto done; + } + + /* Flush and sync the gpustate memory */ + + kgsl_cache_range_op(&drawctxt->gpustate, + KGSL_CACHE_OP_FLUSH); + + kgsl_cffdump_syncmem(NULL, &drawctxt->gpustate, + drawctxt->gpustate.gpuaddr, + drawctxt->gpustate.size, false); + +done: + if (ret) + kgsl_sharedmem_free(&drawctxt->gpustate); + + return ret; +} + +static void a2xx_drawctxt_save(struct adreno_device *adreno_dev, + struct adreno_context *context) +{ + struct kgsl_device *device = &adreno_dev->dev; + unsigned int cmd[22]; + + if (context == NULL) + return; + + if (context->flags & CTXT_FLAGS_GPU_HANG) + KGSL_CTXT_WARN(device, + "Current active context has caused gpu hang\n"); + + if (!(context->flags & CTXT_FLAGS_PREAMBLE)) { + + /* save registers and constants. */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->reg_save, 3); + + if (context->flags & CTXT_FLAGS_SHADER_SAVE) { + /* save shader partitioning and instructions. */ + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_PMODE, + context->shader_save, 3); + + /* + * fixup shader partitioning parameter for + * SET_SHADER_BASES. + */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->shader_fixup, 3); + + context->flags |= CTXT_FLAGS_SHADER_RESTORE; + } + } + + if ((context->flags & CTXT_FLAGS_GMEM_SAVE) && + (context->flags & CTXT_FLAGS_GMEM_SHADOW)) { + /* save gmem. + * (note: changes shader. shader must already be saved.) + */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + context->context_gmem_shadow.gmem_save, 3); + + /* Restore TP0_CHICKEN */ + if (!(context->flags & CTXT_FLAGS_PREAMBLE)) { + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->chicken_restore, 3); + } + + context->flags |= CTXT_FLAGS_GMEM_RESTORE; + } else if (adreno_is_a225(adreno_dev)) { + unsigned int *cmds = &cmd[0]; + /* + * Issue an empty draw call to avoid possible hangs due to + * repeated idles without intervening draw calls. + * On adreno 225 the PC block has a cache that is only + * flushed on draw calls and repeated idles can make it + * overflow. The gmem save path contains draw calls so + * this workaround isn't needed there. + */ + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = (0x4 << 16) | (REG_PA_SU_SC_MODE_CNTL - 0x2000); + *cmds++ = 0; + *cmds++ = cp_type3_packet(CP_DRAW_INDX, 5); + *cmds++ = 0; + *cmds++ = 1<<14; + *cmds++ = 0; + *cmds++ = device->mmu.setstate_memory.gpuaddr; + *cmds++ = 0; + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + &cmd[0], 11); + } +} + +static void a2xx_drawctxt_restore(struct adreno_device *adreno_dev, + struct adreno_context *context) +{ + struct kgsl_device *device = &adreno_dev->dev; + unsigned int cmds[5]; + + if (context == NULL) { + /* No context - set the default apgetable and thats it */ + kgsl_mmu_setstate(&device->mmu, device->mmu.defaultpagetable); + return; + } + + KGSL_CTXT_INFO(device, "context flags %08x\n", context->flags); + + cmds[0] = cp_nop_packet(1); + cmds[1] = KGSL_CONTEXT_TO_MEM_IDENTIFIER; + cmds[2] = cp_type3_packet(CP_MEM_WRITE, 2); + cmds[3] = device->memstore.gpuaddr + + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, current_context); + cmds[4] = context->id; + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, cmds, 5); + kgsl_mmu_setstate(&device->mmu, context->pagetable); + +#ifndef CONFIG_MSM_KGSL_CFF_DUMP_NO_CONTEXT_MEM_DUMP + kgsl_cffdump_syncmem(NULL, &context->gpustate, + context->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 (context->flags & CTXT_FLAGS_GMEM_RESTORE) { + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + context->context_gmem_shadow.gmem_restore, 3); + + if (!(context->flags & CTXT_FLAGS_PREAMBLE)) { + /* Restore TP0_CHICKEN */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->chicken_restore, 3); + } + + context->flags &= ~CTXT_FLAGS_GMEM_RESTORE; + } + + if (!(context->flags & CTXT_FLAGS_PREAMBLE)) { + + /* restore registers and constants. */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->reg_restore, 3); + + /* restore shader instructions & partitioning. */ + if (context->flags & CTXT_FLAGS_SHADER_RESTORE) { + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_NONE, + context->shader_restore, 3); + } + } + + if (adreno_is_a20x(adreno_dev)) { + cmds[0] = cp_type3_packet(CP_SET_BIN_BASE_OFFSET, 1); + cmds[1] = context->bin_base_offset; + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + cmds, 2); + } +} + +/* + * Interrupt management + * + * a2xx interrupt control is distributed among the various + * hardware components (RB, CP, MMU). The main interrupt + * tells us which component fired the interrupt, but one needs + * to go to the individual component to find out why. The + * following functions provide the broken out support for + * managing the interrupts + */ + +#define RBBM_INT_MASK RBBM_INT_CNTL__RDERR_INT_MASK + +#define CP_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__IB1_INT_MASK | \ + CP_INT_CNTL__RB_INT_MASK) + +#define VALID_STATUS_COUNT_MAX 10 + +static struct { + unsigned int mask; + const char *message; +} kgsl_cp_error_irqs[] = { + { CP_INT_CNTL__T0_PACKET_IN_IB_MASK, + "ringbuffer TO packet in IB interrupt" }, + { CP_INT_CNTL__OPCODE_ERROR_MASK, + "ringbuffer opcode error interrupt" }, + { CP_INT_CNTL__PROTECTED_MODE_ERROR_MASK, + "ringbuffer protected mode error interrupt" }, + { CP_INT_CNTL__RESERVED_BIT_ERROR_MASK, + "ringbuffer reserved bit error interrupt" }, + { CP_INT_CNTL__IB_ERROR_MASK, + "ringbuffer IB error interrupt" }, +}; + +static void a2xx_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; + int i; + + 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); + + trace_kgsl_a2xx_irq_status(device, master_status, status); + + 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 context_id; + kgsl_sharedmem_readl(&device->memstore, + &context_id, + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + current_context)); + if (context_id < KGSL_MEMSTORE_MAX) { + kgsl_sharedmem_writel(&rb->device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, + ts_cmp_enable), 0); + device->last_expired_ctxt_id = context_id; + wmb(); + } + KGSL_CMD_WARN(rb->device, "ringbuffer rb interrupt\n"); + } + + for (i = 0; i < ARRAY_SIZE(kgsl_cp_error_irqs); i++) { + if (status & kgsl_cp_error_irqs[i].mask) { + KGSL_CMD_CRIT(rb->device, "%s\n", + kgsl_cp_error_irqs[i].message); + /* + * on fatal errors, turn off the interrupts to + * avoid storming. This has the side effect of + * forcing a PM dump when the timestamp times out + */ + + kgsl_pwrctrl_irq(rb->device, KGSL_PWRFLAGS_OFF); + } + } + + /* only ack bits we understand */ + status &= 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"); + queue_work(device->work_queue, &device->ts_expired_ws); + wake_up_interruptible_all(&device->wait_queue); + atomic_notifier_call_chain(&(device->ts_notifier_list), + device->id, + NULL); + } +} + +static void a2xx_rbbm_intrcallback(struct kgsl_device *device) +{ + unsigned int status = 0; + unsigned int rderr = 0; + unsigned int addr = 0; + const char *source; + + adreno_regread(device, REG_RBBM_INT_STATUS, &status); + + if (status & RBBM_INT_CNTL__RDERR_INT_MASK) { + adreno_regread(device, REG_RBBM_READ_ERROR, &rderr); + source = (rderr & RBBM_READ_ERROR_REQUESTER) + ? "host" : "cp"; + /* convert to dword address */ + addr = (rderr & RBBM_READ_ERROR_ADDRESS_MASK) >> 2; + + /* + * Log CP_INT_STATUS interrupts from the CP at a + * lower level because they can happen frequently + * and are worked around in a2xx_irq_handler. + */ + if (addr == REG_CP_INT_STATUS && + rderr & RBBM_READ_ERROR_ERROR && + rderr & RBBM_READ_ERROR_REQUESTER) + KGSL_DRV_WARN(device, + "rbbm read error interrupt: %s reg: %04X\n", + source, addr); + else + KGSL_DRV_CRIT(device, + "rbbm read error interrupt: %s reg: %04X\n", + source, addr); + } + + status &= RBBM_INT_MASK; + adreno_regwrite(device, REG_RBBM_INT_ACK, status); +} + +irqreturn_t a2xx_irq_handler(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + irqreturn_t result = IRQ_NONE; + unsigned int status; + + 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) { + a2xx_cp_intrcallback(device); + result = IRQ_HANDLED; + } + + if (status & MASTER_INT_SIGNAL__RBBM_INT_STAT) { + a2xx_rbbm_intrcallback(device); + result = IRQ_HANDLED; + } + + return result; +} + +static void a2xx_irq_control(struct adreno_device *adreno_dev, int state) +{ + struct kgsl_device *device = &adreno_dev->dev; + + if (state) { + adreno_regwrite(device, REG_RBBM_INT_CNTL, RBBM_INT_MASK); + adreno_regwrite(device, REG_CP_INT_CNTL, 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); + } + + /* Force the writes to post before touching the IRQ line */ + wmb(); +} + +static void a2xx_rb_init(struct adreno_device *adreno_dev, + struct adreno_ringbuffer *rb) +{ + unsigned int *cmds, cmds_gpu; + + /* 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, cp_type3_packet(CP_ME_INIT, 18)); + /* 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, + SUBBLOCK_OFFSET(REG_RB_SURFACE_INFO)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_PA_SC_WINDOW_OFFSET)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_VGT_MAX_VTX_INDX)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_SQ_PROGRAM_CNTL)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_RB_DEPTHCONTROL)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_PA_SU_POINT_SIZE)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_PA_SC_LINE_CNTL)); + GSL_RB_WRITE(cmds, cmds_gpu, + SUBBLOCK_OFFSET(REG_PA_SU_POLY_OFFSET_FRONT_SCALE)); + + /* Instruction memory size: */ + GSL_RB_WRITE(cmds, cmds_gpu, + (adreno_encode_istore_size(adreno_dev) + | adreno_dev->pix_shader_start)); + /* 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 + * If iommu is used then protection needs to be turned off + * to enable context bank switching */ + if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_get_mmutype()) + GSL_RB_WRITE(cmds, cmds_gpu, 0); + else + 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); +} + +static unsigned int a2xx_busy_cycles(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + unsigned int reg, val; + + /* Freeze the counter */ + adreno_regwrite(device, REG_CP_PERFMON_CNTL, + REG_PERF_MODE_CNT | REG_PERF_STATE_FREEZE); + + /* Get the value */ + adreno_regread(device, REG_RBBM_PERFCOUNTER1_LO, &val); + + /* Reset the counter */ + adreno_regwrite(device, REG_CP_PERFMON_CNTL, + REG_PERF_MODE_CNT | REG_PERF_STATE_RESET); + + /* Re-Enable the performance monitors */ + 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); + + return val; +} + +static void a2xx_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; + + /* get edram_size value equivalent */ + gmem_size = (adreno_dev->gmem_size >> 14); + while (gmem_size >>= 1) + edram_value++; + + rb_edram_info.val = 0; + + rb_edram_info.f.edram_size = edram_value; + rb_edram_info.f.edram_mapping_mode = 0; /* EDRAM_MAP_UPPER */ + + /* must be aligned to size */ + rb_edram_info.f.edram_range = (adreno_dev->gmem_base >> 14); + + adreno_regwrite(device, REG_RB_EDRAM_INFO, rb_edram_info.val); +} + +static void a2xx_start(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + + /* + * 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_a22x(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); + + if (adreno_is_a225(adreno_dev)) { + /* Enable large instruction store for A225 */ + adreno_regwrite(device, REG_SQ_FLOW_CONTROL, + 0x18000000); + } + + adreno_regwrite(device, REG_RBBM_CNTL, 0x00004442); + + adreno_regwrite(device, REG_SQ_VS_PROGRAM, 0x00000000); + adreno_regwrite(device, REG_SQ_PS_PROGRAM, 0x00000000); + + if (cpu_is_msm8960()) + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE1, 0x200); + else + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE1, 0); + + if (!adreno_is_a22x(adreno_dev)) + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0); + else + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0x80); + + 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); + + a2xx_gmeminit(adreno_dev); +} + +/* Defined in adreno_a2xx_snapshot.c */ +void *a2xx_snapshot(struct adreno_device *adreno_dev, void *snapshot, + int *remain, int hang); + +struct adreno_gpudev adreno_a2xx_gpudev = { + .reg_rbbm_status = REG_RBBM_STATUS, + .reg_cp_pfp_ucode_addr = REG_CP_PFP_UCODE_ADDR, + .reg_cp_pfp_ucode_data = REG_CP_PFP_UCODE_DATA, + + .ctxt_create = a2xx_drawctxt_create, + .ctxt_save = a2xx_drawctxt_save, + .ctxt_restore = a2xx_drawctxt_restore, + .irq_handler = a2xx_irq_handler, + .irq_control = a2xx_irq_control, + .snapshot = a2xx_snapshot, + .rb_init = a2xx_rb_init, + .busy_cycles = a2xx_busy_cycles, + .start = a2xx_start, +}; diff --git a/drivers/gpu/msm/adreno_a2xx_snapshot.c b/drivers/gpu/msm/adreno_a2xx_snapshot.c new file mode 100644 index 0000000000000000000000000000000000000000..236826406462950fdbc5d1f5a61ff4e21e5566bc --- /dev/null +++ b/drivers/gpu/msm/adreno_a2xx_snapshot.c @@ -0,0 +1,341 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "kgsl.h" +#include "adreno.h" +#include "kgsl_snapshot.h" + +#define DEBUG_SECTION_SZ(_dwords) (((_dwords) * sizeof(unsigned int)) \ + + sizeof(struct kgsl_snapshot_debug)) + +/* Dump the SX debug registers into a GPU snapshot debug section */ + +#define SXDEBUG_COUNT 0x1B + +static int a2xx_snapshot_sxdebug(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i; + + if (remain < DEBUG_SECTION_SZ(SXDEBUG_COUNT)) { + SNAPSHOT_ERR_NOMEM(device, "SX DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_SX; + header->size = SXDEBUG_COUNT; + + for (i = 0; i < SXDEBUG_COUNT; i++) { + adreno_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1B00 | i); + adreno_regread(device, REG_RBBM_DEBUG_OUT, &data[i]); + } + + adreno_regwrite(device, REG_RBBM_DEBUG_CNTL, 0); + + return DEBUG_SECTION_SZ(SXDEBUG_COUNT); +} + +#define CPDEBUG_COUNT 0x20 + +static int a2xx_snapshot_cpdebug(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i; + + if (remain < DEBUG_SECTION_SZ(CPDEBUG_COUNT)) { + SNAPSHOT_ERR_NOMEM(device, "CP DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_CP; + header->size = CPDEBUG_COUNT; + + for (i = 0; i < CPDEBUG_COUNT; i++) { + adreno_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1628); + adreno_regread(device, REG_RBBM_DEBUG_OUT, &data[i]); + } + + adreno_regwrite(device, REG_RBBM_DEBUG_CNTL, 0); + + return DEBUG_SECTION_SZ(CPDEBUG_COUNT); +} + +/* + * The contents of the SQ debug sections are dword pairs: + * [register offset]:[value] + * This macro writes both dwords for the given register + */ + +#define SQ_DEBUG_WRITE(_device, _reg, _data, _offset) \ + do { _data[(_offset)++] = (_reg); \ + adreno_regread(_device, (_reg), &_data[(_offset)++]); } while (0) + +#define SQ_DEBUG_BANK_SIZE 23 + +static int a2xx_snapshot_sqdebug(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i, offset = 0; + int size = SQ_DEBUG_BANK_SIZE * 2 * 2; + + if (remain < DEBUG_SECTION_SZ(size)) { + SNAPSHOT_ERR_NOMEM(device, "SQ Debug"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_SQ; + header->size = size; + + for (i = 0; i < 2; i++) { + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_CONST_MGR_FSM+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_EXP_ALLOC+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_FSM_ALU_0+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_FSM_ALU_1+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_GPR_PIX+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_GPR_VTX+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_INPUT_FSM+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_MISC+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_MISC_0+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_MISC_1+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_0+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_STATE_MEM+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, + REG_SQ_DEBUG_PIX_TB_STATUS_REG_0+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, + REG_SQ_DEBUG_PIX_TB_STATUS_REG_1+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, + REG_SQ_DEBUG_PIX_TB_STATUS_REG_2+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, + REG_SQ_DEBUG_PIX_TB_STATUS_REG_3+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PTR_BUFF+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_TB_STATUS_SEL+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_TP_FSM+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_VTX_TB_0+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_VTX_TB_1+i*0x1000, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_VTX_TB_STATE_MEM+i*0x1000, + data, offset); + } + + return DEBUG_SECTION_SZ(size); +} + +#define SQ_DEBUG_THREAD_SIZE 7 + +static int a2xx_snapshot_sqthreaddebug(struct kgsl_device *device, + void *snapshot, int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i, offset = 0; + int size = SQ_DEBUG_THREAD_SIZE * 2 * 16; + + if (remain < DEBUG_SECTION_SZ(size)) { + SNAPSHOT_ERR_NOMEM(device, "SQ THREAD DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_SQTHREAD; + header->size = size; + + for (i = 0; i < 16; i++) { + adreno_regwrite(device, REG_SQ_DEBUG_TB_STATUS_SEL, + i | (6<<4) | (i<<7) | (1<<11) | (1<<12) + | (i<<16) | (6<<20) | (i<<23)); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_VTX_TB_STATE_MEM, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_VTX_TB_STATUS_REG, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_STATE_MEM, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_STATUS_REG_0, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_STATUS_REG_1, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_STATUS_REG_2, + data, offset); + SQ_DEBUG_WRITE(device, REG_SQ_DEBUG_PIX_TB_STATUS_REG_3, + data, offset); + } + + return DEBUG_SECTION_SZ(size); +} + +#define MIUDEBUG_COUNT 0x10 + +static int a2xx_snapshot_miudebug(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i; + + if (remain < DEBUG_SECTION_SZ(MIUDEBUG_COUNT)) { + SNAPSHOT_ERR_NOMEM(device, "MIU DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_MIU; + header->size = MIUDEBUG_COUNT; + + for (i = 0; i < MIUDEBUG_COUNT; i++) { + adreno_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1600 | i); + adreno_regread(device, REG_RBBM_DEBUG_OUT, &data[i]); + } + + adreno_regwrite(device, REG_RBBM_DEBUG_CNTL, 0); + + return DEBUG_SECTION_SZ(MIUDEBUG_COUNT); +} + +/* A2XX GPU snapshot function - this is where all of the A2XX specific + * bits and pieces are grabbed into the snapshot memory + */ + +void *a2xx_snapshot(struct adreno_device *adreno_dev, void *snapshot, + int *remain, int hang) +{ + struct kgsl_device *device = &adreno_dev->dev; + struct kgsl_snapshot_registers regs; + unsigned int pmoverride; + + /* Choose the register set to dump */ + + if (adreno_is_a20x(adreno_dev)) { + regs.regs = (unsigned int *) a200_registers; + regs.count = a200_registers_count; + } else if (adreno_is_a220(adreno_dev)) { + regs.regs = (unsigned int *) a220_registers; + regs.count = a220_registers_count; + } else if (adreno_is_a225(adreno_dev)) { + regs.regs = (unsigned int *) a225_registers; + regs.count = a225_registers_count; + } + + /* Master set of (non debug) registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_REGS, snapshot, remain, + kgsl_snapshot_dump_regs, ®s); + + /* CP_STATE_DEBUG indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_CP_STATE_DEBUG_INDEX, + REG_CP_STATE_DEBUG_DATA, 0x0, 0x14); + + /* CP_ME indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_CP_ME_CNTL, REG_CP_ME_STATUS, + 64, 44); + + /* + * Need to temporarily turn off clock gating for the debug bus to + * work + */ + + adreno_regread(device, REG_RBBM_PM_OVERRIDE2, &pmoverride); + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0xFF); + + /* SX debug registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a2xx_snapshot_sxdebug, NULL); + + /* SU debug indexed registers (only for < 470) */ + if (!adreno_is_a22x(adreno_dev)) + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_PA_SU_DEBUG_CNTL, + REG_PA_SU_DEBUG_DATA, + 0, 0x1B); + + /* CP debug registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a2xx_snapshot_cpdebug, NULL); + + /* MH debug indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, MH_DEBUG_CTRL, MH_DEBUG_DATA, 0x0, 0x40); + + /* Leia only register sets */ + if (adreno_is_a22x(adreno_dev)) { + /* RB DEBUG indexed regisers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_RB_DEBUG_CNTL, REG_RB_DEBUG_DATA, 0, 8); + + /* RB DEBUG indexed registers bank 2 */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_RB_DEBUG_CNTL, REG_RB_DEBUG_DATA + 0x1000, + 0, 8); + + /* PC_DEBUG indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_PC_DEBUG_CNTL, REG_PC_DEBUG_DATA, 0, 8); + + /* GRAS_DEBUG indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_GRAS_DEBUG_CNTL, REG_GRAS_DEBUG_DATA, 0, 4); + + /* MIU debug registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a2xx_snapshot_miudebug, NULL); + + /* SQ DEBUG debug registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a2xx_snapshot_sqdebug, NULL); + + /* + * Reading SQ THREAD causes bad things to happen on a running + * system, so only read it if the GPU is already hung + */ + + if (hang) { + /* SQ THREAD debug registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a2xx_snapshot_sqthreaddebug, NULL); + } + } + + /* Reset the clock gating */ + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, pmoverride); + + return snapshot; +} diff --git a/drivers/gpu/msm/adreno_a2xx_trace.c b/drivers/gpu/msm/adreno_a2xx_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..c91d1a04ad89a94b17bf1f956bbda818bb76f140 --- /dev/null +++ b/drivers/gpu/msm/adreno_a2xx_trace.c @@ -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 "kgsl.h" +#include "adreno.h" + +/* Instantiate tracepoints */ +#define CREATE_TRACE_POINTS +#include "adreno_a2xx_trace.h" diff --git a/drivers/gpu/msm/adreno_a2xx_trace.h b/drivers/gpu/msm/adreno_a2xx_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..2528e153dbdd2d029e19cf50910fc6ac22b07791 --- /dev/null +++ b/drivers/gpu/msm/adreno_a2xx_trace.h @@ -0,0 +1,78 @@ +/* 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. + * + */ + +#if !defined(_ADRENO_A2XX_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _ADRENO_A2XX_TRACE_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM kgsl +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE adreno_a2xx_trace + +#include + +struct kgsl_device; + +/* + * Tracepoint for a2xx irq. Includes status info + */ +TRACE_EVENT(kgsl_a2xx_irq_status, + + TP_PROTO(struct kgsl_device *device, unsigned int master_status, + unsigned int status), + + TP_ARGS(device, master_status, status), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, master_status) + __field(unsigned int, status) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->master_status = master_status; + __entry->status = status; + ), + + TP_printk( + "d_name=%s master=%s status=%s", + __get_str(device_name), + __entry->master_status ? __print_flags(__entry->master_status, + "|", + { MASTER_INT_SIGNAL__MH_INT_STAT, "MH" }, + { MASTER_INT_SIGNAL__SQ_INT_STAT, "SQ" }, + { MASTER_INT_SIGNAL__CP_INT_STAT, "CP" }, + { MASTER_INT_SIGNAL__RBBM_INT_STAT, "RBBM" }) : "None", + __entry->status ? __print_flags(__entry->status, "|", + { CP_INT_CNTL__SW_INT_MASK, "SW" }, + { CP_INT_CNTL__T0_PACKET_IN_IB_MASK, + "T0_PACKET_IN_IB" }, + { CP_INT_CNTL__OPCODE_ERROR_MASK, "OPCODE_ERROR" }, + { CP_INT_CNTL__PROTECTED_MODE_ERROR_MASK, + "PROTECTED_MODE_ERROR" }, + { CP_INT_CNTL__RESERVED_BIT_ERROR_MASK, + "RESERVED_BIT_ERROR" }, + { CP_INT_CNTL__IB_ERROR_MASK, "IB_ERROR" }, + { CP_INT_CNTL__IB2_INT_MASK, "IB2" }, + { CP_INT_CNTL__IB1_INT_MASK, "IB1" }, + { CP_INT_CNTL__RB_INT_MASK, "RB" }) : "None" + ) +); + +#endif /* _ADRENO_A2XX_TRACE_H */ + +/* This part must be outside protection */ +#include diff --git a/drivers/gpu/msm/adreno_a3xx.c b/drivers/gpu/msm/adreno_a3xx.c new file mode 100644 index 0000000000000000000000000000000000000000..09eae174519ef44bf9af156fa6577f26a566f2da --- /dev/null +++ b/drivers/gpu/msm/adreno_a3xx.c @@ -0,0 +1,2680 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even 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 "adreno.h" +#include "kgsl_sharedmem.h" +#include "kgsl_cffdump.h" +#include "a3xx_reg.h" +#include "adreno_a3xx_trace.h" + +/* + * Set of registers to dump for A3XX on postmortem and snapshot. + * Registers in pairs - first value is the start offset, second + * is the stop offset (inclusive) + */ + +const unsigned int a3xx_registers[] = { + 0x0000, 0x0002, 0x0010, 0x0012, 0x0018, 0x0018, 0x0020, 0x0027, + 0x0029, 0x002b, 0x002e, 0x0033, 0x0040, 0x0042, 0x0050, 0x005c, + 0x0060, 0x006c, 0x0080, 0x0082, 0x0084, 0x0088, 0x0090, 0x00e5, + 0x00ea, 0x00ed, 0x0100, 0x0100, 0x0110, 0x0123, 0x01c0, 0x01c1, + 0x01c3, 0x01c5, 0x01c7, 0x01c7, 0x01d5, 0x01d9, 0x01dc, 0x01dd, + 0x01ea, 0x01ea, 0x01ee, 0x01f1, 0x01f5, 0x01f5, 0x01fc, 0x01ff, + 0x0440, 0x0440, 0x0443, 0x0443, 0x0445, 0x0445, 0x044d, 0x044f, + 0x0452, 0x0452, 0x0454, 0x046f, 0x047c, 0x047c, 0x047f, 0x047f, + 0x0578, 0x057f, 0x0600, 0x0602, 0x0605, 0x0607, 0x060a, 0x060e, + 0x0612, 0x0614, 0x0c01, 0x0c02, 0x0c06, 0x0c1d, 0x0c3d, 0x0c3f, + 0x0c48, 0x0c4b, 0x0c80, 0x0c80, 0x0c88, 0x0c8b, 0x0ca0, 0x0cb7, + 0x0cc0, 0x0cc1, 0x0cc6, 0x0cc7, 0x0ce4, 0x0ce5, 0x0e00, 0x0e05, + 0x0e0c, 0x0e0c, 0x0e22, 0x0e23, 0x0e41, 0x0e45, 0x0e64, 0x0e65, + 0x0e80, 0x0e82, 0x0e84, 0x0e89, 0x0ea0, 0x0ea1, 0x0ea4, 0x0ea7, + 0x0ec4, 0x0ecb, 0x0ee0, 0x0ee0, 0x0f00, 0x0f01, 0x0f03, 0x0f09, + 0x2040, 0x2040, 0x2044, 0x2044, 0x2048, 0x204d, 0x2068, 0x2069, + 0x206c, 0x206d, 0x2070, 0x2070, 0x2072, 0x2072, 0x2074, 0x2075, + 0x2079, 0x207a, 0x20c0, 0x20d3, 0x20e4, 0x20ef, 0x2100, 0x2109, + 0x210c, 0x210c, 0x210e, 0x210e, 0x2110, 0x2111, 0x2114, 0x2115, + 0x21e4, 0x21e4, 0x21ea, 0x21ea, 0x21ec, 0x21ed, 0x21f0, 0x21f0, + 0x2200, 0x2212, 0x2214, 0x2217, 0x221a, 0x221a, 0x2240, 0x227e, + 0x2280, 0x228b, 0x22c0, 0x22c0, 0x22c4, 0x22ce, 0x22d0, 0x22d8, + 0x22df, 0x22e6, 0x22e8, 0x22e9, 0x22ec, 0x22ec, 0x22f0, 0x22f7, + 0x22ff, 0x22ff, 0x2340, 0x2343, 0x2348, 0x2349, 0x2350, 0x2356, + 0x2360, 0x2360, 0x2440, 0x2440, 0x2444, 0x2444, 0x2448, 0x244d, + 0x2468, 0x2469, 0x246c, 0x246d, 0x2470, 0x2470, 0x2472, 0x2472, + 0x2474, 0x2475, 0x2479, 0x247a, 0x24c0, 0x24d3, 0x24e4, 0x24ef, + 0x2500, 0x2509, 0x250c, 0x250c, 0x250e, 0x250e, 0x2510, 0x2511, + 0x2514, 0x2515, 0x25e4, 0x25e4, 0x25ea, 0x25ea, 0x25ec, 0x25ed, + 0x25f0, 0x25f0, 0x2600, 0x2612, 0x2614, 0x2617, 0x261a, 0x261a, + 0x2640, 0x267e, 0x2680, 0x268b, 0x26c0, 0x26c0, 0x26c4, 0x26ce, + 0x26d0, 0x26d8, 0x26df, 0x26e6, 0x26e8, 0x26e9, 0x26ec, 0x26ec, + 0x26f0, 0x26f7, 0x26ff, 0x26ff, 0x2740, 0x2743, 0x2748, 0x2749, + 0x2750, 0x2756, 0x2760, 0x2760, 0x300C, 0x300E, 0x301C, 0x301D, + 0x302A, 0x302A, 0x302C, 0x302D, 0x3030, 0x3031, 0x3034, 0x3036, + 0x303C, 0x303C, 0x305E, 0x305F, +}; + +const unsigned int a3xx_registers_count = ARRAY_SIZE(a3xx_registers) / 2; + +/* Simple macro to facilitate bit setting in the gmem2sys and sys2gmem + * functions. + */ + +#define _SET(_shift, _val) ((_val) << (_shift)) + +/* + **************************************************************************** + * + * Context state shadow structure: + * + * +---------------------+------------+-------------+---------------------+---+ + * | ALU Constant Shadow | Reg Shadow | C&V Buffers | Shader Instr Shadow |Tex| + * +---------------------+------------+-------------+---------------------+---+ + * + * 8K - ALU Constant Shadow (8K aligned) + * 4K - H/W Register Shadow (8K aligned) + * 5K - Command and Vertex Buffers + * 8K - Shader Instruction Shadow + * ~6K - Texture Constant Shadow + * + * + *************************************************************************** + */ + +/* Sizes of all sections in state shadow memory */ +#define ALU_SHADOW_SIZE (8*1024) /* 8KB */ +#define REG_SHADOW_SIZE (4*1024) /* 4KB */ +#define CMD_BUFFER_SIZE (5*1024) /* 5KB */ +#define TEX_SIZE_MEM_OBJECTS 896 /* bytes */ +#define TEX_SIZE_MIPMAP 1936 /* bytes */ +#define TEX_SIZE_SAMPLER_OBJ 256 /* bytes */ +#define TEX_SHADOW_SIZE \ + ((TEX_SIZE_MEM_OBJECTS + TEX_SIZE_MIPMAP + \ + TEX_SIZE_SAMPLER_OBJ)*2) /* ~6KB */ +#define SHADER_SHADOW_SIZE (8*1024) /* 8KB */ + +/* Total context size, excluding GMEM shadow */ +#define CONTEXT_SIZE \ + (ALU_SHADOW_SIZE+REG_SHADOW_SIZE + \ + CMD_BUFFER_SIZE+SHADER_SHADOW_SIZE + \ + TEX_SHADOW_SIZE) + +/* Offsets to different sections in context shadow memory */ +#define REG_OFFSET ALU_SHADOW_SIZE +#define CMD_OFFSET (REG_OFFSET+REG_SHADOW_SIZE) +#define SHADER_OFFSET (CMD_OFFSET+CMD_BUFFER_SIZE) +#define TEX_OFFSET (SHADER_OFFSET+SHADER_SHADOW_SIZE) +#define VS_TEX_OFFSET_MEM_OBJECTS TEX_OFFSET +#define VS_TEX_OFFSET_MIPMAP (VS_TEX_OFFSET_MEM_OBJECTS+TEX_SIZE_MEM_OBJECTS) +#define VS_TEX_OFFSET_SAMPLER_OBJ (VS_TEX_OFFSET_MIPMAP+TEX_SIZE_MIPMAP) +#define FS_TEX_OFFSET_MEM_OBJECTS \ + (VS_TEX_OFFSET_SAMPLER_OBJ+TEX_SIZE_SAMPLER_OBJ) +#define FS_TEX_OFFSET_MIPMAP (FS_TEX_OFFSET_MEM_OBJECTS+TEX_SIZE_MEM_OBJECTS) +#define FS_TEX_OFFSET_SAMPLER_OBJ (FS_TEX_OFFSET_MIPMAP+TEX_SIZE_MIPMAP) + +/* The offset for fragment shader data in HLSQ context */ +#define SSIZE (16*1024) + +#define HLSQ_SAMPLER_OFFSET 0x000 +#define HLSQ_MEMOBJ_OFFSET 0x400 +#define HLSQ_MIPMAP_OFFSET 0x800 + +/* Use shadow RAM */ +#define HLSQ_SHADOW_BASE (0x10000+SSIZE*2) + +#define REG_TO_MEM_LOOP_COUNT_SHIFT 18 + +#define BUILD_PC_DRAW_INITIATOR(prim_type, source_select, index_size, \ + vis_cull_mode) \ + (((prim_type) << PC_DRAW_INITIATOR_PRIM_TYPE) | \ + ((source_select) << PC_DRAW_INITIATOR_SOURCE_SELECT) | \ + ((index_size & 1) << PC_DRAW_INITIATOR_INDEX_SIZE) | \ + ((index_size >> 1) << PC_DRAW_INITIATOR_SMALL_INDEX) | \ + ((vis_cull_mode) << PC_DRAW_INITIATOR_VISIBILITY_CULLING_MODE) | \ + (1 << PC_DRAW_INITIATOR_PRE_DRAW_INITIATOR_ENABLE)) + +/* + * List of context registers (starting from dword offset 0x2000). + * Each line contains start and end of a range of registers. + */ +static const unsigned int context_register_ranges[] = { + A3XX_GRAS_CL_CLIP_CNTL, A3XX_GRAS_CL_CLIP_CNTL, + A3XX_GRAS_CL_GB_CLIP_ADJ, A3XX_GRAS_CL_GB_CLIP_ADJ, + A3XX_GRAS_CL_VPORT_XOFFSET, A3XX_GRAS_CL_VPORT_ZSCALE, + A3XX_GRAS_SU_POINT_MINMAX, A3XX_GRAS_SU_POINT_SIZE, + A3XX_GRAS_SU_POLY_OFFSET_SCALE, A3XX_GRAS_SU_POLY_OFFSET_OFFSET, + A3XX_GRAS_SU_MODE_CONTROL, A3XX_GRAS_SU_MODE_CONTROL, + A3XX_GRAS_SC_CONTROL, A3XX_GRAS_SC_CONTROL, + A3XX_GRAS_SC_SCREEN_SCISSOR_TL, A3XX_GRAS_SC_SCREEN_SCISSOR_BR, + A3XX_GRAS_SC_WINDOW_SCISSOR_TL, A3XX_GRAS_SC_WINDOW_SCISSOR_BR, + A3XX_RB_MODE_CONTROL, A3XX_RB_MRT_BLEND_CONTROL3, + A3XX_RB_BLEND_RED, A3XX_RB_COPY_DEST_INFO, + A3XX_RB_DEPTH_CONTROL, A3XX_RB_DEPTH_CONTROL, + A3XX_PC_VSTREAM_CONTROL, A3XX_PC_VSTREAM_CONTROL, + A3XX_PC_VERTEX_REUSE_BLOCK_CNTL, A3XX_PC_VERTEX_REUSE_BLOCK_CNTL, + A3XX_PC_PRIM_VTX_CNTL, A3XX_PC_RESTART_INDEX, + A3XX_HLSQ_CONTROL_0_REG, A3XX_HLSQ_CONST_FSPRESV_RANGE_REG, + A3XX_HLSQ_CL_NDRANGE_0_REG, A3XX_HLSQ_CL_NDRANGE_0_REG, + A3XX_HLSQ_CL_NDRANGE_2_REG, A3XX_HLSQ_CL_CONTROL_1_REG, + A3XX_HLSQ_CL_KERNEL_CONST_REG, A3XX_HLSQ_CL_KERNEL_GROUP_Z_REG, + A3XX_HLSQ_CL_WG_OFFSET_REG, A3XX_HLSQ_CL_WG_OFFSET_REG, + A3XX_VFD_CONTROL_0, A3XX_VFD_VS_THREADING_THRESHOLD, + A3XX_SP_SP_CTRL_REG, A3XX_SP_SP_CTRL_REG, + A3XX_SP_VS_CTRL_REG0, A3XX_SP_VS_OUT_REG_7, + A3XX_SP_VS_VPC_DST_REG_0, A3XX_SP_VS_PVT_MEM_SIZE_REG, + A3XX_SP_VS_LENGTH_REG, A3XX_SP_FS_PVT_MEM_SIZE_REG, + A3XX_SP_FS_FLAT_SHAD_MODE_REG_0, A3XX_SP_FS_FLAT_SHAD_MODE_REG_1, + A3XX_SP_FS_OUTPUT_REG, A3XX_SP_FS_OUTPUT_REG, + A3XX_SP_FS_MRT_REG_0, A3XX_SP_FS_IMAGE_OUTPUT_REG_3, + A3XX_SP_FS_LENGTH_REG, A3XX_SP_FS_LENGTH_REG, + A3XX_TPL1_TP_VS_TEX_OFFSET, A3XX_TPL1_TP_FS_BORDER_COLOR_BASE_ADDR, + A3XX_VPC_ATTR, A3XX_VPC_VARY_CYLWRAP_ENABLE_1, +}; + +/* Global registers that need to be saved separately */ +static const unsigned int global_registers[] = { + A3XX_GRAS_CL_USER_PLANE_X0, A3XX_GRAS_CL_USER_PLANE_Y0, + A3XX_GRAS_CL_USER_PLANE_Z0, A3XX_GRAS_CL_USER_PLANE_W0, + A3XX_GRAS_CL_USER_PLANE_X1, A3XX_GRAS_CL_USER_PLANE_Y1, + A3XX_GRAS_CL_USER_PLANE_Z1, A3XX_GRAS_CL_USER_PLANE_W1, + A3XX_GRAS_CL_USER_PLANE_X2, A3XX_GRAS_CL_USER_PLANE_Y2, + A3XX_GRAS_CL_USER_PLANE_Z2, A3XX_GRAS_CL_USER_PLANE_W2, + A3XX_GRAS_CL_USER_PLANE_X3, A3XX_GRAS_CL_USER_PLANE_Y3, + A3XX_GRAS_CL_USER_PLANE_Z3, A3XX_GRAS_CL_USER_PLANE_W3, + A3XX_GRAS_CL_USER_PLANE_X4, A3XX_GRAS_CL_USER_PLANE_Y4, + A3XX_GRAS_CL_USER_PLANE_Z4, A3XX_GRAS_CL_USER_PLANE_W4, + A3XX_GRAS_CL_USER_PLANE_X5, A3XX_GRAS_CL_USER_PLANE_Y5, + A3XX_GRAS_CL_USER_PLANE_Z5, A3XX_GRAS_CL_USER_PLANE_W5, + A3XX_VSC_BIN_SIZE, + A3XX_VSC_PIPE_CONFIG_0, A3XX_VSC_PIPE_CONFIG_1, + A3XX_VSC_PIPE_CONFIG_2, A3XX_VSC_PIPE_CONFIG_3, + A3XX_VSC_PIPE_CONFIG_4, A3XX_VSC_PIPE_CONFIG_5, + A3XX_VSC_PIPE_CONFIG_6, A3XX_VSC_PIPE_CONFIG_7, + A3XX_VSC_PIPE_DATA_ADDRESS_0, A3XX_VSC_PIPE_DATA_ADDRESS_1, + A3XX_VSC_PIPE_DATA_ADDRESS_2, A3XX_VSC_PIPE_DATA_ADDRESS_3, + A3XX_VSC_PIPE_DATA_ADDRESS_4, A3XX_VSC_PIPE_DATA_ADDRESS_5, + A3XX_VSC_PIPE_DATA_ADDRESS_6, A3XX_VSC_PIPE_DATA_ADDRESS_7, + A3XX_VSC_PIPE_DATA_LENGTH_0, A3XX_VSC_PIPE_DATA_LENGTH_1, + A3XX_VSC_PIPE_DATA_LENGTH_2, A3XX_VSC_PIPE_DATA_LENGTH_3, + A3XX_VSC_PIPE_DATA_LENGTH_4, A3XX_VSC_PIPE_DATA_LENGTH_5, + A3XX_VSC_PIPE_DATA_LENGTH_6, A3XX_VSC_PIPE_DATA_LENGTH_7, + A3XX_VSC_SIZE_ADDRESS +}; + +#define GLOBAL_REGISTER_COUNT ARRAY_SIZE(global_registers) + +/* A scratchpad used to build commands during context create */ +static struct tmp_ctx { + unsigned int *cmd; /* Next available dword in C&V buffer */ + + /* Addresses in comamnd buffer where registers are saved */ + uint32_t reg_values[GLOBAL_REGISTER_COUNT]; + uint32_t gmem_base; /* Base GPU address of GMEM */ +} tmp_ctx; + +#ifndef GSL_CONTEXT_SWITCH_CPU_SYNC +/* + * Function for executing dest = ( (reg & and) ROL rol ) | or + */ +static unsigned int *rmw_regtomem(unsigned int *cmd, + unsigned int reg, unsigned int and, + unsigned int rol, unsigned int or, + unsigned int dest) +{ + /* CP_SCRATCH_REG2 = (CP_SCRATCH_REG2 & 0x00000000) | reg */ + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = (1 << 30) | A3XX_CP_SCRATCH_REG2; + *cmd++ = 0x00000000; /* AND value */ + *cmd++ = reg; /* OR address */ + + /* CP_SCRATCH_REG2 = ( (CP_SCRATCH_REG2 & and) ROL rol ) | or */ + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = (rol << 24) | A3XX_CP_SCRATCH_REG2; + *cmd++ = and; /* AND value */ + *cmd++ = or; /* OR value */ + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_CP_SCRATCH_REG2; + *cmd++ = dest; + + return cmd; +} +#endif + +static void build_regconstantsave_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start; + unsigned int i; + + drawctxt->constant_save_commands[0].hostptr = cmd; + drawctxt->constant_save_commands[0].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + cmd++; + + start = cmd; + + *cmd++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + +#ifndef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + /* + * Context registers are already shadowed; just need to + * disable shadowing to prevent corruption. + */ + + *cmd++ = cp_type3_packet(CP_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = (drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000; + *cmd++ = 4 << 16; /* regs, start=0 */ + *cmd++ = 0x0; /* count = 0 */ + +#else + /* + * Make sure the HW context has the correct register values before + * reading them. + */ + + /* Write context registers into shadow */ + for (i = 0; i < ARRAY_SIZE(context_register_ranges) / 2; i++) { + unsigned int start = context_register_ranges[i * 2]; + unsigned int end = context_register_ranges[i * 2 + 1]; + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = ((end - start + 1) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + start; + *cmd++ = ((drawctxt->gpustate.gpuaddr + REG_OFFSET) + & 0xFFFFE000) + (start - 0x2000) * 4; + } +#endif + + /* Need to handle some of the global registers separately */ + for (i = 0; i < ARRAY_SIZE(global_registers); i++) { + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = global_registers[i]; + *cmd++ = tmp_ctx.reg_values[i]; + } + + /* Save vertex shader constants */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[2].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[2].gpuaddr >> 2; + *cmd++ = 0x0000FFFF; + *cmd++ = 3; /* EXEC_COUNT */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + drawctxt->constant_save_commands[1].hostptr = cmd; + drawctxt->constant_save_commands[1].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + dwords = SP_VS_CTRL_REG1.VSCONSTLENGTH / 4 + src = (HLSQ_SHADOW_BASE + 0x2000) / 4 + + From register spec: + SP_VS_CTRL_REG1.VSCONSTLENGTH [09:00]: 0-512, unit = 128bits. + */ + *cmd++ = 0; /* (dwords << REG_TO_MEM_LOOP_COUNT_SHIFT) | src */ + /* ALU constant shadow base */ + *cmd++ = drawctxt->gpustate.gpuaddr & 0xfffffffc; + + /* Save fragment shader constants */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[3].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[3].gpuaddr >> 2; + *cmd++ = 0x0000FFFF; + *cmd++ = 3; /* EXEC_COUNT */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + drawctxt->constant_save_commands[2].hostptr = cmd; + drawctxt->constant_save_commands[2].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + dwords = SP_FS_CTRL_REG1.FSCONSTLENGTH / 4 + src = (HLSQ_SHADOW_BASE + 0x2000 + SSIZE) / 4 + + From register spec: + SP_FS_CTRL_REG1.FSCONSTLENGTH [09:00]: 0-512, unit = 128bits. + */ + *cmd++ = 0; /* (dwords << REG_TO_MEM_LOOP_COUNT_SHIFT) | src */ + + /* + From fixup: + + base = drawctxt->gpustate.gpuaddr (ALU constant shadow base) + offset = SP_FS_OBJ_OFFSET_REG.CONSTOBJECTSTARTOFFSET + + From register spec: + SP_FS_OBJ_OFFSET_REG.CONSTOBJECTSTARTOFFSET [16:24]: Constant object + start offset in on chip RAM, + 128bit aligned + + dst = base + offset + Because of the base alignment we can use + dst = base | offset + */ + *cmd++ = 0; /* dst */ + + /* Save VS texture memory objects */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = + ((TEX_SIZE_MEM_OBJECTS / 4) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + ((HLSQ_SHADOW_BASE + HLSQ_MEMOBJ_OFFSET) / 4); + *cmd++ = + (drawctxt->gpustate.gpuaddr + + VS_TEX_OFFSET_MEM_OBJECTS) & 0xfffffffc; + + /* Save VS texture mipmap pointers */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = + ((TEX_SIZE_MIPMAP / 4) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + ((HLSQ_SHADOW_BASE + HLSQ_MIPMAP_OFFSET) / 4); + *cmd++ = + (drawctxt->gpustate.gpuaddr + VS_TEX_OFFSET_MIPMAP) & 0xfffffffc; + + /* Save VS texture sampler objects */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = ((TEX_SIZE_SAMPLER_OBJ / 4) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + ((HLSQ_SHADOW_BASE + HLSQ_SAMPLER_OFFSET) / 4); + *cmd++ = + (drawctxt->gpustate.gpuaddr + + VS_TEX_OFFSET_SAMPLER_OBJ) & 0xfffffffc; + + /* Save FS texture memory objects */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = + ((TEX_SIZE_MEM_OBJECTS / 4) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + ((HLSQ_SHADOW_BASE + HLSQ_MEMOBJ_OFFSET + SSIZE) / 4); + *cmd++ = + (drawctxt->gpustate.gpuaddr + + FS_TEX_OFFSET_MEM_OBJECTS) & 0xfffffffc; + + /* Save FS texture mipmap pointers */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = + ((TEX_SIZE_MIPMAP / 4) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + ((HLSQ_SHADOW_BASE + HLSQ_MIPMAP_OFFSET + SSIZE) / 4); + *cmd++ = + (drawctxt->gpustate.gpuaddr + FS_TEX_OFFSET_MIPMAP) & 0xfffffffc; + + /* Save FS texture sampler objects */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = + ((TEX_SIZE_SAMPLER_OBJ / 4) << REG_TO_MEM_LOOP_COUNT_SHIFT) | + ((HLSQ_SHADOW_BASE + HLSQ_SAMPLER_OFFSET + SSIZE) / 4); + *cmd++ = + (drawctxt->gpustate.gpuaddr + + FS_TEX_OFFSET_SAMPLER_OBJ) & 0xfffffffc; + + /* Create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->regconstant_save, start, cmd); + + tmp_ctx.cmd = cmd; +} + +/* Copy GMEM contents to system memory shadow. */ +static unsigned int *build_gmem2sys_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct gmem_shadow_t *shadow) +{ + unsigned int *cmds = tmp_ctx.cmd; + unsigned int *start = cmds; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MODE_CONTROL); + + /* RB_MODE_CONTROL */ + *cmds++ = _SET(RB_MODECONTROL_RENDER_MODE, RB_RESOLVE_PASS) | + _SET(RB_MODECONTROL_MARB_CACHE_SPLIT_MODE, 1) | + _SET(RB_MODECONTROL_PACKER_TIMER_ENABLE, 1); + /* RB_RENDER_CONTROL */ + *cmds++ = _SET(RB_RENDERCONTROL_BIN_WIDTH, shadow->width >> 5) | + _SET(RB_RENDERCONTROL_DISABLE_COLOR_PIPE, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_RB_COPY_CONTROL); + /* RB_COPY_CONTROL */ + *cmds++ = _SET(RB_COPYCONTROL_RESOLVE_CLEAR_MODE, + RB_CLEAR_MODE_RESOLVE) | + _SET(RB_COPYCONTROL_COPY_GMEM_BASE, + tmp_ctx.gmem_base >> 14); + /* RB_COPY_DEST_BASE */ + *cmds++ = _SET(RB_COPYDESTBASE_COPY_DEST_BASE, + shadow->gmemshadow.gpuaddr >> 5); + /* RB_COPY_DEST_PITCH */ + *cmds++ = _SET(RB_COPYDESTPITCH_COPY_DEST_PITCH, + (shadow->pitch * 4) / 32); + /* RB_COPY_DEST_INFO */ + *cmds++ = _SET(RB_COPYDESTINFO_COPY_DEST_TILE, + RB_TILINGMODE_LINEAR) | + _SET(RB_COPYDESTINFO_COPY_DEST_FORMAT, RB_R8G8B8A8_UNORM) | + _SET(RB_COPYDESTINFO_COPY_COMPONENT_ENABLE, 0X0F) | + _SET(RB_COPYDESTINFO_COPY_DEST_ENDIAN, RB_ENDIAN_NONE); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_GRAS_SC_CONTROL); + /* GRAS_SC_CONTROL */ + *cmds++ = _SET(GRAS_SC_CONTROL_RENDER_MODE, 2); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_VFD_CONTROL_0); + /* VFD_CONTROL_0 */ + *cmds++ = _SET(VFD_CTRLREG0_TOTALATTRTOVS, 4) | + _SET(VFD_CTRLREG0_PACKETSIZE, 2) | + _SET(VFD_CTRLREG0_STRMDECINSTRCNT, 1) | + _SET(VFD_CTRLREG0_STRMFETCHINSTRCNT, 1); + /* VFD_CONTROL_1 */ + *cmds++ = _SET(VFD_CTRLREG1_MAXSTORAGE, 1) | + _SET(VFD_CTRLREG1_REGID4VTX, 252) | + _SET(VFD_CTRLREG1_REGID4INST, 252); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_VFD_FETCH_INSTR_0_0); + /* VFD_FETCH_INSTR_0_0 */ + *cmds++ = _SET(VFD_FETCHINSTRUCTIONS_FETCHSIZE, 11) | + _SET(VFD_FETCHINSTRUCTIONS_BUFSTRIDE, 12) | + _SET(VFD_FETCHINSTRUCTIONS_STEPRATE, 1); + /* VFD_FETCH_INSTR_1_0 */ + *cmds++ = _SET(VFD_BASEADDR_BASEADDR, + shadow->quad_vertices.gpuaddr); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_VFD_DECODE_INSTR_0); + /* VFD_DECODE_INSTR_0 */ + *cmds++ = _SET(VFD_DECODEINSTRUCTIONS_WRITEMASK, 0x0F) | + _SET(VFD_DECODEINSTRUCTIONS_CONSTFILL, 1) | + _SET(VFD_DECODEINSTRUCTIONS_FORMAT, 2) | + _SET(VFD_DECODEINSTRUCTIONS_REGID, 5) | + _SET(VFD_DECODEINSTRUCTIONS_SHIFTCNT, 12) | + _SET(VFD_DECODEINSTRUCTIONS_LASTCOMPVALID, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_HLSQ_CONTROL_0_REG); + /* HLSQ_CONTROL_0_REG */ + *cmds++ = _SET(HLSQ_CTRL0REG_FSTHREADSIZE, HLSQ_TWO_PIX_QUADS) | + _SET(HLSQ_CTRL0REG_FSSUPERTHREADENABLE, 1) | + _SET(HLSQ_CTRL0REG_SPSHADERRESTART, 1) | + _SET(HLSQ_CTRL0REG_RESERVED2, 1) | + _SET(HLSQ_CTRL0REG_CHUNKDISABLE, 1) | + _SET(HLSQ_CTRL0REG_CONSTSWITCHMODE, 1) | + _SET(HLSQ_CTRL0REG_LAZYUPDATEDISABLE, 1) | + _SET(HLSQ_CTRL0REG_SPCONSTFULLUPDATE, 1) | + _SET(HLSQ_CTRL0REG_TPFULLUPDATE, 1); + /* HLSQ_CONTROL_1_REG */ + *cmds++ = _SET(HLSQ_CTRL1REG_VSTHREADSIZE, HLSQ_TWO_VTX_QUADS) | + _SET(HLSQ_CTRL1REG_VSSUPERTHREADENABLE, 1) | + _SET(HLSQ_CTRL1REG_RESERVED1, 4); + /* HLSQ_CONTROL_2_REG */ + *cmds++ = _SET(HLSQ_CTRL2REG_PRIMALLOCTHRESHOLD, 31); + /* HLSQ_CONTROL_3_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_HLSQ_VS_CONTROL_REG); + /* HLSQ_VS_CONTROL_REG */ + *cmds++ = _SET(HLSQ_VSCTRLREG_VSINSTRLENGTH, 1); + /* HLSQ_FS_CONTROL_REG */ + *cmds++ = _SET(HLSQ_FSCTRLREG_FSCONSTLENGTH, 1) | + _SET(HLSQ_FSCTRLREG_FSCONSTSTARTOFFSET, 272) | + _SET(HLSQ_FSCTRLREG_FSINSTRLENGTH, 1); + /* HLSQ_CONST_VSPRESV_RANGE_REG */ + *cmds++ = 0x00000000; + /* HLSQ_CONST_FSPRESV_RANGE_REQ */ + *cmds++ = _SET(HLSQ_CONSTFSPRESERVEDRANGEREG_STARTENTRY, 32) | + _SET(HLSQ_CONSTFSPRESERVEDRANGEREG_ENDENTRY, 32); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_FS_LENGTH_REG); + /* SP_FS_LENGTH_REG */ + *cmds++ = _SET(SP_SHADERLENGTH_LEN, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_SP_CTRL_REG); + /* SP_SP_CTRL_REG */ + *cmds++ = _SET(SP_SPCTRLREG_CONSTMODE, 1) | + _SET(SP_SPCTRLREG_SLEEPMODE, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 12); + *cmds++ = CP_REG(A3XX_SP_VS_CTRL_REG0); + /* SP_VS_CTRL_REG0 */ + *cmds++ = _SET(SP_VSCTRLREG0_VSTHREADMODE, SP_MULTI) | + _SET(SP_VSCTRLREG0_VSINSTRBUFFERMODE, SP_BUFFER_MODE) | + _SET(SP_VSCTRLREG0_VSICACHEINVALID, 1) | + _SET(SP_VSCTRLREG0_VSFULLREGFOOTPRINT, 3) | + _SET(SP_VSCTRLREG0_VSTHREADSIZE, SP_TWO_VTX_QUADS) | + _SET(SP_VSCTRLREG0_VSSUPERTHREADMODE, 1) | + _SET(SP_VSCTRLREG0_VSLENGTH, 1); + /* SP_VS_CTRL_REG1 */ + *cmds++ = _SET(SP_VSCTRLREG1_VSINITIALOUTSTANDING, 4); + /* SP_VS_PARAM_REG */ + *cmds++ = _SET(SP_VSPARAMREG_POSREGID, 1) | + _SET(SP_VSPARAMREG_PSIZEREGID, 252); + /* SP_VS_OUT_REG_0 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_1 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_2 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_3 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_4 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_5 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_6 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG_7 */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 7); + *cmds++ = CP_REG(A3XX_SP_VS_VPC_DST_REG_0); + /* SP_VS_VPC_DST_REG_0 */ + *cmds++ = 0x00000000; + /* SP_VS_VPC_DST_REG_1 */ + *cmds++ = 0x00000000; + /* SP_VS_VPC_DST_REG_2 */ + *cmds++ = 0x00000000; + /* SP_VS_VPC_DST_REG_3 */ + *cmds++ = 0x00000000; + /* SP_VS_OBJ_OFFSET_REG */ + *cmds++ = 0x00000000; + /* SP_VS_OBJ_START_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 6); + *cmds++ = CP_REG(A3XX_SP_VS_LENGTH_REG); + /* SP_VS_LENGTH_REG */ + *cmds++ = _SET(SP_SHADERLENGTH_LEN, 1); + /* SP_FS_CTRL_REG0 */ + *cmds++ = _SET(SP_FSCTRLREG0_FSTHREADMODE, SP_MULTI) | + _SET(SP_FSCTRLREG0_FSINSTRBUFFERMODE, SP_BUFFER_MODE) | + _SET(SP_FSCTRLREG0_FSICACHEINVALID, 1) | + _SET(SP_FSCTRLREG0_FSFULLREGFOOTPRINT, 2) | + _SET(SP_FSCTRLREG0_FSINOUTREGOVERLAP, 1) | + _SET(SP_FSCTRLREG0_FSTHREADSIZE, SP_TWO_VTX_QUADS) | + _SET(SP_FSCTRLREG0_FSSUPERTHREADMODE, 1) | + _SET(SP_FSCTRLREG0_FSLENGTH, 1); + /* SP_FS_CTRL_REG1 */ + *cmds++ = _SET(SP_FSCTRLREG1_FSCONSTLENGTH, 1) | + _SET(SP_FSCTRLREG1_FSINITIALOUTSTANDING, 2) | + _SET(SP_FSCTRLREG1_HALFPRECVAROFFSET, 63); + /* SP_FS_OBJ_OFFSET_REG */ + *cmds++ = _SET(SP_OBJOFFSETREG_CONSTOBJECTSTARTOFFSET, 272) | + _SET(SP_OBJOFFSETREG_SHADEROBJOFFSETINIC, 1); + /* SP_FS_OBJ_START_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_SP_FS_FLAT_SHAD_MODE_REG_0); + /* SP_FS_FLAT_SHAD_MODE_REG_0 */ + *cmds++ = 0x00000000; + /* SP_FS_FLAT_SHAD_MODE_REG_1 */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_FS_OUTPUT_REG); + /* SP_FS_OUTPUT_REG */ + *cmds++ = _SET(SP_IMAGEOUTPUTREG_PAD0, SP_PIXEL_BASED); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_SP_FS_MRT_REG_0); + /* SP_FS_MRT_REG_0 */ + *cmds++ = _SET(SP_FSMRTREG_REGID, 1); + /* SP_FS_MRT_REG_1 */ + *cmds++ = 0x00000000; + /* SP_FS_MRT_REG_2 */ + *cmds++ = 0x00000000; + /* SP_FS_MRT_REG_3 */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 11); + *cmds++ = CP_REG(A3XX_VPC_ATTR); + /* VPC_ATTR */ + *cmds++ = _SET(VPC_VPCATTR_THRHDASSIGN, 1) | + _SET(VPC_VPCATTR_LMSIZE, 1); + /* VPC_PACK */ + *cmds++ = 0x00000000; + /* VPC_VARRYING_INTERUPT_MODE_0 */ + *cmds++ = 0x00000000; + /* VPC_VARRYING_INTERUPT_MODE_1 */ + *cmds++ = 0x00000000; + /* VPC_VARRYING_INTERUPT_MODE_2 */ + *cmds++ = 0x00000000; + /* VPC_VARRYING_INTERUPT_MODE_3 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_PS_REPL_MODE_0 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_PS_REPL_MODE_1 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_PS_REPL_MODE_2 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_PS_REPL_MODE_3 */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 10); + *cmds++ = (0 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_SP_VS << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (1 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_SP_VS_INSTR << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + + /* (sy)(rpt3)mov.f32f32 r0.y, (r)r1.y; */ + *cmds++ = 0x00000005; *cmds++ = 0x30044b01; + /* end; */ + *cmds++ = 0x00000000; *cmds++ = 0x03000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 10); + *cmds++ = (0 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_SP_FS << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (1 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_SP_FS_INSTR << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + + /* (sy)(rpt3)mov.f32f32 r0.y, (r)c0.x; */ + *cmds++ = 0x00000000; *cmds++ = 0x30244b01; + /* end; */ + *cmds++ = 0x00000000; *cmds++ = 0x03000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MSAA_CONTROL); + /* RB_MSAA_CONTROL */ + *cmds++ = _SET(RB_MSAACONTROL_MSAA_DISABLE, 1) | + _SET(RB_MSAACONTROL_SAMPLE_MASK, 0xFFFF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_DEPTH_CONTROL); + /* RB_DEPTH_CONTROL */ + *cmds++ = _SET(RB_DEPTHCONTROL_Z_TEST_FUNC, RB_FRAG_NEVER); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MRT_CONTROL0); + /* RB_MRT_CONTROL0 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_ROP_CODE, 12) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_ALWAYS) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL0); + /* RB_MRT_BLEND_CONTROL0 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + /* RB_MRT_CONTROL1 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_DISABLE) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL1); + /* RB_MRT_BLEND_CONTROL1 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + /* RB_MRT_CONTROL2 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_DISABLE) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL2); + /* RB_MRT_BLEND_CONTROL2 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + /* RB_MRT_CONTROL3 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_DISABLE) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL3); + /* RB_MRT_BLEND_CONTROL3 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_VFD_INDEX_MIN); + /* VFD_INDEX_MIN */ + *cmds++ = 0x00000000; + /* VFD_INDEX_MAX */ + *cmds++ = 0xFFFFFFFF; + /* VFD_INSTANCEID_OFFSET */ + *cmds++ = 0x00000000; + /* VFD_INDEX_OFFSET */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_VFD_VS_THREADING_THRESHOLD); + /* VFD_VS_THREADING_THRESHOLD */ + *cmds++ = _SET(VFD_THREADINGTHRESHOLD_RESERVED6, 12) | + _SET(VFD_THREADINGTHRESHOLD_REGID_VTXCNT, 252); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_TPL1_TP_VS_TEX_OFFSET); + /* TPL1_TP_VS_TEX_OFFSET */ + *cmds++ = 0; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_TPL1_TP_FS_TEX_OFFSET); + /* TPL1_TP_FS_TEX_OFFSET */ + *cmds++ = _SET(TPL1_TPTEXOFFSETREG_SAMPLEROFFSET, 16) | + _SET(TPL1_TPTEXOFFSETREG_MEMOBJOFFSET, 16) | + _SET(TPL1_TPTEXOFFSETREG_BASETABLEPTR, 224); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_PC_PRIM_VTX_CNTL); + /* PC_PRIM_VTX_CNTL */ + *cmds++ = _SET(PC_PRIM_VTX_CONTROL_POLYMODE_FRONT_PTYPE, + PC_DRAW_TRIANGLES) | + _SET(PC_PRIM_VTX_CONTROL_POLYMODE_BACK_PTYPE, + PC_DRAW_TRIANGLES) | + _SET(PC_PRIM_VTX_CONTROL_PROVOKING_VTX_LAST, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_GRAS_SC_WINDOW_SCISSOR_TL); + /* GRAS_SC_WINDOW_SCISSOR_TL */ + *cmds++ = 0x00000000; + /* GRAS_SC_WINDOW_SCISSOR_BR */ + *cmds++ = _SET(GRAS_SC_WINDOW_SCISSOR_BR_BR_X, shadow->width - 1) | + _SET(GRAS_SC_WINDOW_SCISSOR_BR_BR_Y, shadow->height - 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_GRAS_SC_SCREEN_SCISSOR_TL); + /* GRAS_SC_SCREEN_SCISSOR_TL */ + *cmds++ = 0x00000000; + /* GRAS_SC_SCREEN_SCISSOR_BR */ + *cmds++ = _SET(GRAS_SC_SCREEN_SCISSOR_BR_BR_X, shadow->width - 1) | + _SET(GRAS_SC_SCREEN_SCISSOR_BR_BR_Y, shadow->height - 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_GRAS_CL_VPORT_XOFFSET); + /* GRAS_CL_VPORT_XOFFSET */ + *cmds++ = 0x00000000; + /* GRAS_CL_VPORT_XSCALE */ + *cmds++ = _SET(GRAS_CL_VPORT_XSCALE_VPORT_XSCALE, 0x3f800000); + /* GRAS_CL_VPORT_YOFFSET */ + *cmds++ = 0x00000000; + /* GRAS_CL_VPORT_YSCALE */ + *cmds++ = _SET(GRAS_CL_VPORT_YSCALE_VPORT_YSCALE, 0x3f800000); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_GRAS_CL_VPORT_ZOFFSET); + /* GRAS_CL_VPORT_ZOFFSET */ + *cmds++ = 0x00000000; + /* GRAS_CL_VPORT_ZSCALE */ + *cmds++ = _SET(GRAS_CL_VPORT_ZSCALE_VPORT_ZSCALE, 0x3f800000); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_GRAS_CL_CLIP_CNTL); + /* GRAS_CL_CLIP_CNTL */ + *cmds++ = _SET(GRAS_CL_CLIP_CNTL_CLIP_DISABLE, 1) | + _SET(GRAS_CL_CLIP_CNTL_ZFAR_CLIP_DISABLE, 1) | + _SET(GRAS_CL_CLIP_CNTL_VP_CLIP_CODE_IGNORE, 1) | + _SET(GRAS_CL_CLIP_CNTL_VP_XFORM_DISABLE, 1) | + _SET(GRAS_CL_CLIP_CNTL_PERSP_DIVISION_DISABLE, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_GRAS_CL_GB_CLIP_ADJ); + /* GRAS_CL_GB_CLIP_ADJ */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + /* + * Resolve using two draw calls with a dummy register + * write in between. This is a HLM workaround + * that should be removed later. + */ + *cmds++ = cp_type3_packet(CP_DRAW_INDX_2, 6); + *cmds++ = 0x00000000; /* Viz query info */ + *cmds++ = BUILD_PC_DRAW_INITIATOR(PC_DI_PT_TRILIST, + PC_DI_SRC_SEL_IMMEDIATE, + PC_DI_INDEX_SIZE_32_BIT, + PC_DI_IGNORE_VISIBILITY); + *cmds++ = 0x00000003; /* Num indices */ + *cmds++ = 0x00000000; /* Index 0 */ + *cmds++ = 0x00000001; /* Index 1 */ + *cmds++ = 0x00000002; /* Index 2 */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_HLSQ_CL_CONTROL_0_REG); + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_DRAW_INDX_2, 6); + *cmds++ = 0x00000000; /* Viz query info */ + *cmds++ = BUILD_PC_DRAW_INITIATOR(PC_DI_PT_TRILIST, + PC_DI_SRC_SEL_IMMEDIATE, + PC_DI_INDEX_SIZE_32_BIT, + PC_DI_IGNORE_VISIBILITY); + *cmds++ = 0x00000003; /* Num indices */ + *cmds++ = 0x00000002; /* Index 0 */ + *cmds++ = 0x00000001; /* Index 1 */ + *cmds++ = 0x00000003; /* Index 2 */ + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_HLSQ_CL_CONTROL_0_REG); + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + /* Create indirect buffer command for above command sequence */ + create_ib1(drawctxt, shadow->gmem_save, start, cmds); + + return cmds; +} + +static void build_shader_save_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start; + + /* Reserve space for boolean values used for COND_EXEC packet */ + drawctxt->cond_execs[0].hostptr = cmd; + drawctxt->cond_execs[0].gpuaddr = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + drawctxt->cond_execs[1].hostptr = cmd; + drawctxt->cond_execs[1].gpuaddr = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + + drawctxt->shader_save_commands[0].hostptr = cmd; + drawctxt->shader_save_commands[0].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + drawctxt->shader_save_commands[1].hostptr = cmd; + drawctxt->shader_save_commands[1].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + + start = cmd; + + /* Save vertex shader */ + + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[0].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[0].gpuaddr >> 2; + *cmd++ = 0x0000FFFF; + *cmd++ = 3; /* EXEC_COUNT */ + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + drawctxt->shader_save_commands[2].hostptr = cmd; + drawctxt->shader_save_commands[2].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + dwords = SP_VS_CTRL_REG0.VS_LENGTH * 8 + + From regspec: + SP_VS_CTRL_REG0.VS_LENGTH [31:24]: VS length, unit = 256bits. + If bit31 is 1, it means overflow + or any long shader. + + src = (HLSQ_SHADOW_BASE + 0x1000)/4 + */ + *cmd++ = 0; /*(dwords << REG_TO_MEM_LOOP_COUNT_SHIFT) | src */ + *cmd++ = (drawctxt->gpustate.gpuaddr + SHADER_OFFSET) & 0xfffffffc; + + /* Save fragment shader */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[1].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[1].gpuaddr >> 2; + *cmd++ = 0x0000FFFF; + *cmd++ = 3; /* EXEC_COUNT */ + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + drawctxt->shader_save_commands[3].hostptr = cmd; + drawctxt->shader_save_commands[3].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + dwords = SP_FS_CTRL_REG0.FS_LENGTH * 8 + + From regspec: + SP_FS_CTRL_REG0.FS_LENGTH [31:24]: FS length, unit = 256bits. + If bit31 is 1, it means overflow + or any long shader. + + fs_offset = SP_FS_OBJ_OFFSET_REG.SHADEROBJOFFSETINIC * 32 + From regspec: + + SP_FS_OBJ_OFFSET_REG.SHADEROBJOFFSETINIC [31:25]: + First instruction of the whole shader will be stored from + the offset in instruction cache, unit = 256bits, a cache line. + It can start from 0 if no VS available. + + src = (HLSQ_SHADOW_BASE + 0x1000 + SSIZE + fs_offset)/4 + */ + *cmd++ = 0; /*(dwords << REG_TO_MEM_LOOP_COUNT_SHIFT) | src */ + *cmd++ = (drawctxt->gpustate.gpuaddr + SHADER_OFFSET + + (SHADER_SHADOW_SIZE / 2)) & 0xfffffffc; + + /* Create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->shader_save, start, cmd); + + tmp_ctx.cmd = cmd; +} + +/* + * Make an IB to modify context save IBs with the correct shader instruction + * and constant sizes and offsets. + */ + +static void build_save_fixup_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start = cmd; + + /* Flush HLSQ lazy updates */ + *cmd++ = cp_type3_packet(CP_EVENT_WRITE, 1); + *cmd++ = 0x7; /* HLSQ_FLUSH */ + *cmd++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + *cmd++ = cp_type0_packet(A3XX_UCHE_CACHE_INVALIDATE0_REG, 2); + *cmd++ = 0x00000000; /* No start addr for full invalidate */ + *cmd++ = (unsigned int) + UCHE_ENTIRE_CACHE << UCHE_INVALIDATE1REG_ALLORPORTION | + UCHE_OP_INVALIDATE << UCHE_INVALIDATE1REG_OPCODE | + 0; /* No end addr for full invalidate */ + + /* Make sure registers are flushed */ + *cmd++ = cp_type3_packet(CP_CONTEXT_UPDATE, 1); + *cmd++ = 0; + +#ifdef GSL_CONTEXT_SWITCH_CPU_SYNC + + /* Save shader sizes */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_VS_CTRL_REG0; + *cmd++ = drawctxt->shader_save_commands[2].gpuaddr; + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_CTRL_REG0; + *cmd++ = drawctxt->shader_save_commands[3].gpuaddr; + + /* Save shader offsets */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_OBJ_OFFSET_REG; + *cmd++ = drawctxt->shader_save_commands[1].gpuaddr; + + /* Save constant sizes */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_VS_CTRL_REG1; + *cmd++ = drawctxt->constant_save_commands[1].gpuaddr; + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_CTRL_REG1; + *cmd++ = drawctxt->constant_save_commands[2].gpuaddr; + + /* Save FS constant offset */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_OBJ_OFFSET_REG; + *cmd++ = drawctxt->constant_save_commands[0].gpuaddr; + + + /* Save VS instruction store mode */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_VS_CTRL_REG0; + *cmd++ = drawctxt->cond_execs[0].gpuaddr; + + /* Save FS instruction store mode */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_CTRL_REG0; + *cmd++ = drawctxt->cond_execs[1].gpuaddr; +#else + + /* Shader save */ + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG0, 0x7f000000, + 11+REG_TO_MEM_LOOP_COUNT_SHIFT, + (HLSQ_SHADOW_BASE + 0x1000) / 4, + drawctxt->shader_save_commands[2].gpuaddr); + + /* CP_SCRATCH_REG2 = (CP_SCRATCH_REG2 & 0x00000000) | SP_FS_CTRL_REG0 */ + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = (1 << 30) | A3XX_CP_SCRATCH_REG2; + *cmd++ = 0x00000000; /* AND value */ + *cmd++ = A3XX_SP_FS_CTRL_REG0; /* OR address */ + /* CP_SCRATCH_REG2 = ( (CP_SCRATCH_REG2 & 0x7f000000) >> 21 ) + | ((HLSQ_SHADOW_BASE+0x1000+SSIZE)/4) */ + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = ((11 + REG_TO_MEM_LOOP_COUNT_SHIFT) << 24) | + A3XX_CP_SCRATCH_REG2; + *cmd++ = 0x7f000000; /* AND value */ + *cmd++ = (HLSQ_SHADOW_BASE + 0x1000 + SSIZE) / 4; /* OR value */ + + /* + * CP_SCRATCH_REG3 = (CP_SCRATCH_REG3 & 0x00000000) | + * SP_FS_OBJ_OFFSET_REG + */ + + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = (1 << 30) | A3XX_CP_SCRATCH_REG3; + *cmd++ = 0x00000000; /* AND value */ + *cmd++ = A3XX_SP_FS_OBJ_OFFSET_REG; /* OR address */ + /* + * CP_SCRATCH_REG3 = ( (CP_SCRATCH_REG3 & 0xfe000000) >> 25 ) | + * 0x00000000 + */ + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = A3XX_CP_SCRATCH_REG3; + *cmd++ = 0xfe000000; /* AND value */ + *cmd++ = 0x00000000; /* OR value */ + /* + * CP_SCRATCH_REG2 = (CP_SCRATCH_REG2 & 0xffffffff) | CP_SCRATCH_REG3 + */ + *cmd++ = cp_type3_packet(CP_REG_RMW, 3); + *cmd++ = (1 << 30) | A3XX_CP_SCRATCH_REG2; + *cmd++ = 0xffffffff; /* AND value */ + *cmd++ = A3XX_CP_SCRATCH_REG3; /* OR address */ + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_CP_SCRATCH_REG2; + *cmd++ = drawctxt->shader_save_commands[3].gpuaddr; + + /* Constant save */ + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG1, 0x000003ff, + 2 + REG_TO_MEM_LOOP_COUNT_SHIFT, + (HLSQ_SHADOW_BASE + 0x2000) / 4, + drawctxt->constant_save_commands[1].gpuaddr); + + cmd = rmw_regtomem(cmd, A3XX_SP_FS_CTRL_REG1, 0x000003ff, + 2 + REG_TO_MEM_LOOP_COUNT_SHIFT, + (HLSQ_SHADOW_BASE + 0x2000 + SSIZE) / 4, + drawctxt->constant_save_commands[2].gpuaddr); + + cmd = rmw_regtomem(cmd, A3XX_SP_FS_OBJ_OFFSET_REG, 0x00ff0000, + 18, drawctxt->gpustate.gpuaddr & 0xfffffe00, + drawctxt->constant_save_commands[2].gpuaddr + + sizeof(unsigned int)); + + /* Modify constant save conditionals */ + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG1, 0x000003ff, + 0, 0, drawctxt->cond_execs[2].gpuaddr); + + cmd = rmw_regtomem(cmd, A3XX_SP_FS_CTRL_REG1, 0x000003ff, + 0, 0, drawctxt->cond_execs[3].gpuaddr); + + /* Save VS instruction store mode */ + + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG0, 0x00000002, + 31, 0, drawctxt->cond_execs[0].gpuaddr); + + /* Save FS instruction store mode */ + cmd = rmw_regtomem(cmd, A3XX_SP_FS_CTRL_REG0, 0x00000002, + 31, 0, drawctxt->cond_execs[1].gpuaddr); + +#endif + + create_ib1(drawctxt, drawctxt->save_fixup, start, cmd); + + tmp_ctx.cmd = cmd; +} + +/****************************************************************************/ +/* Functions to build context restore IBs */ +/****************************************************************************/ + +static unsigned int *build_sys2gmem_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct gmem_shadow_t *shadow) +{ + unsigned int *cmds = tmp_ctx.cmd; + unsigned int *start = cmds; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_HLSQ_CONTROL_0_REG); + /* HLSQ_CONTROL_0_REG */ + *cmds++ = _SET(HLSQ_CTRL0REG_FSTHREADSIZE, HLSQ_FOUR_PIX_QUADS) | + _SET(HLSQ_CTRL0REG_SPSHADERRESTART, 1) | + _SET(HLSQ_CTRL0REG_CHUNKDISABLE, 1) | + _SET(HLSQ_CTRL0REG_SPCONSTFULLUPDATE, 1) | + _SET(HLSQ_CTRL0REG_TPFULLUPDATE, 1); + /* HLSQ_CONTROL_1_REG */ + *cmds++ = _SET(HLSQ_CTRL1REG_VSTHREADSIZE, HLSQ_TWO_VTX_QUADS); + /* HLSQ_CONTROL_2_REG */ + *cmds++ = _SET(HLSQ_CTRL2REG_PRIMALLOCTHRESHOLD, 31); + /* HLSQ_CONTROL3_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BUF_INFO0); + /* RB_MRT_BUF_INFO0 */ + *cmds++ = _SET(RB_MRTBUFINFO_COLOR_FORMAT, RB_R8G8B8A8_UNORM) | + _SET(RB_MRTBUFINFO_COLOR_TILE_MODE, RB_TILINGMODE_32X32) | + _SET(RB_MRTBUFINFO_COLOR_BUF_PITCH, + (shadow->gmem_pitch * 4 * 8) / 256); + /* RB_MRT_BUF_BASE0 */ + *cmds++ = _SET(RB_MRTBUFBASE_COLOR_BUF_BASE, tmp_ctx.gmem_base >> 5); + + /* Texture samplers */ + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 4); + *cmds++ = (16 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_TP_TEX << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (1 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_TP_TEX_SAMPLERS << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + *cmds++ = 0x00000240; + *cmds++ = 0x00000000; + + /* Texture memobjs */ + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 6); + *cmds++ = (16 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_TP_TEX << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (1 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_TP_TEX_MEMOBJ << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + *cmds++ = 0x4cc06880; + *cmds++ = shadow->height | (shadow->width << 14); + *cmds++ = (shadow->pitch*4*8) << 9; + *cmds++ = 0x00000000; + + /* Mipmap bases */ + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 16); + *cmds++ = (224 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_TP_MIPMAP << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (14 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_TP_MIPMAP_BASE << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + *cmds++ = shadow->gmemshadow.gpuaddr; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_HLSQ_VS_CONTROL_REG); + /* HLSQ_VS_CONTROL_REG */ + *cmds++ = _SET(HLSQ_VSCTRLREG_VSINSTRLENGTH, 1); + /* HLSQ_FS_CONTROL_REG */ + *cmds++ = _SET(HLSQ_FSCTRLREG_FSCONSTLENGTH, 1) | + _SET(HLSQ_FSCTRLREG_FSCONSTSTARTOFFSET, 128) | + _SET(HLSQ_FSCTRLREG_FSINSTRLENGTH, 2); + /* HLSQ_CONST_VSPRESV_RANGE_REG */ + *cmds++ = 0x00000000; + /* HLSQ_CONST_FSPRESV_RANGE_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_FS_LENGTH_REG); + /* SP_FS_LENGTH_REG */ + *cmds++ = _SET(SP_SHADERLENGTH_LEN, 2); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 12); + *cmds++ = CP_REG(A3XX_SP_VS_CTRL_REG0); + /* SP_VS_CTRL_REG0 */ + *cmds++ = _SET(SP_VSCTRLREG0_VSTHREADMODE, SP_MULTI) | + _SET(SP_VSCTRLREG0_VSINSTRBUFFERMODE, SP_BUFFER_MODE) | + _SET(SP_VSCTRLREG0_VSICACHEINVALID, 1) | + _SET(SP_VSCTRLREG0_VSFULLREGFOOTPRINT, 2) | + _SET(SP_VSCTRLREG0_VSTHREADSIZE, SP_TWO_VTX_QUADS) | + _SET(SP_VSCTRLREG0_VSLENGTH, 1); + /* SP_VS_CTRL_REG1 */ + *cmds++ = _SET(SP_VSCTRLREG1_VSINITIALOUTSTANDING, 8); + /* SP_VS_PARAM_REG */ + *cmds++ = _SET(SP_VSPARAMREG_POSREGID, 4) | + _SET(SP_VSPARAMREG_PSIZEREGID, 252) | + _SET(SP_VSPARAMREG_TOTALVSOUTVAR, 1); + /* SP_VS_OUT_REG0 */ + *cmds++ = _SET(SP_VSOUTREG_COMPMASK0, 3); + /* SP_VS_OUT_REG1 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG2 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG3 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG4 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG5 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG6 */ + *cmds++ = 0x00000000; + /* SP_VS_OUT_REG7 */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 7); + *cmds++ = CP_REG(A3XX_SP_VS_VPC_DST_REG_0); + /* SP_VS_VPC_DST_REG0 */ + *cmds++ = _SET(SP_VSVPCDSTREG_OUTLOC0, 8); + /* SP_VS_VPC_DST_REG1 */ + *cmds++ = 0x00000000; + /* SP_VS_VPC_DST_REG2 */ + *cmds++ = 0x00000000; + /* SP_VS_VPC_DST_REG3 */ + *cmds++ = 0x00000000; + /* SP_VS_OBJ_OFFSET_REG */ + *cmds++ = 0x00000000; + /* SP_VS_OBJ_START_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 6); + *cmds++ = CP_REG(A3XX_SP_VS_LENGTH_REG); + /* SP_VS_LENGTH_REG */ + *cmds++ = _SET(SP_SHADERLENGTH_LEN, 1); + /* SP_FS_CTRL_REG0 */ + *cmds++ = _SET(SP_FSCTRLREG0_FSTHREADMODE, SP_MULTI) | + _SET(SP_FSCTRLREG0_FSINSTRBUFFERMODE, SP_BUFFER_MODE) | + _SET(SP_FSCTRLREG0_FSICACHEINVALID, 1) | + _SET(SP_FSCTRLREG0_FSFULLREGFOOTPRINT, 2) | + _SET(SP_FSCTRLREG0_FSINOUTREGOVERLAP, 1) | + _SET(SP_FSCTRLREG0_FSTHREADSIZE, SP_FOUR_PIX_QUADS) | + _SET(SP_FSCTRLREG0_PIXLODENABLE, 1) | + _SET(SP_FSCTRLREG0_FSLENGTH, 2); + /* SP_FS_CTRL_REG1 */ + *cmds++ = _SET(SP_FSCTRLREG1_FSCONSTLENGTH, 1) | + _SET(SP_FSCTRLREG1_FSINITIALOUTSTANDING, 2) | + _SET(SP_FSCTRLREG1_HALFPRECVAROFFSET, 63); + /* SP_FS_OBJ_OFFSET_REG */ + *cmds++ = _SET(SP_OBJOFFSETREG_CONSTOBJECTSTARTOFFSET, 128) | + _SET(SP_OBJOFFSETREG_SHADEROBJOFFSETINIC, 1); + /* SP_FS_OBJ_START_REG */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_SP_FS_FLAT_SHAD_MODE_REG_0); + /* SP_FS_FLAT_SHAD_MODE_REG0 */ + *cmds++ = 0x00000000; + /* SP_FS_FLAT_SHAD_MODE_REG1 */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_FS_OUTPUT_REG); + /* SP_FS_OUT_REG */ + *cmds++ = _SET(SP_FSOUTREG_PAD0, SP_PIXEL_BASED); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_SP_FS_MRT_REG_0); + /* SP_FS_MRT_REG0 */ + *cmds++ = _SET(SP_FSMRTREG_REGID, 4); + /* SP_FS_MRT_REG1 */ + *cmds++ = 0; + /* SP_FS_MRT_REG2 */ + *cmds++ = 0; + /* SP_FS_MRT_REG3 */ + *cmds++ = 0; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 11); + *cmds++ = CP_REG(A3XX_VPC_ATTR); + /* VPC_ATTR */ + *cmds++ = _SET(VPC_VPCATTR_TOTALATTR, 2) | + _SET(VPC_VPCATTR_THRHDASSIGN, 1) | + _SET(VPC_VPCATTR_LMSIZE, 1); + /* VPC_PACK */ + *cmds++ = _SET(VPC_VPCPACK_NUMFPNONPOSVAR, 2) | + _SET(VPC_VPCPACK_NUMNONPOSVSVAR, 2); + /* VPC_VARYING_INTERP_MODE_0 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_INTERP_MODE1 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_INTERP_MODE2 */ + *cmds++ = 0x00000000; + /* VPC_VARYING_IINTERP_MODE3 */ + *cmds++ = 0x00000000; + /* VPC_VARRYING_PS_REPL_MODE_0 */ + *cmds++ = _SET(VPC_VPCVARPSREPLMODE_COMPONENT08, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT09, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0A, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0B, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0C, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0D, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0E, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0F, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT10, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT11, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT12, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT13, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT14, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT15, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT16, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT17, 2); + /* VPC_VARRYING_PS_REPL_MODE_1 */ + *cmds++ = _SET(VPC_VPCVARPSREPLMODE_COMPONENT08, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT09, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0A, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0B, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0C, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0D, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0E, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0F, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT10, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT11, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT12, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT13, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT14, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT15, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT16, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT17, 2); + /* VPC_VARRYING_PS_REPL_MODE_2 */ + *cmds++ = _SET(VPC_VPCVARPSREPLMODE_COMPONENT08, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT09, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0A, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0B, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0C, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0D, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0E, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0F, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT10, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT11, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT12, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT13, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT14, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT15, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT16, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT17, 2); + /* VPC_VARRYING_PS_REPL_MODE_3 */ + *cmds++ = _SET(VPC_VPCVARPSREPLMODE_COMPONENT08, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT09, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0A, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0B, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0C, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0D, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0E, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT0F, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT10, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT11, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT12, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT13, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT14, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT15, 2) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT16, 1) | + _SET(VPC_VPCVARPSREPLMODE_COMPONENT17, 2); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_SP_CTRL_REG); + /* SP_SP_CTRL_REG */ + *cmds++ = _SET(SP_SPCTRLREG_SLEEPMODE, 1); + + /* Load vertex shader */ + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 10); + *cmds++ = (0 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_SP_VS << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (1 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_SP_VS_INSTR << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + /* (sy)end; */ + *cmds++ = 0x00000000; *cmds++ = 0x13000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + /* nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000000; + + /* Load fragment shader */ + *cmds++ = cp_type3_packet(CP_LOAD_STATE, 18); + *cmds++ = (0 << CP_LOADSTATE_DSTOFFSET_SHIFT) + | (HLSQ_DIRECT << CP_LOADSTATE_STATESRC_SHIFT) + | (HLSQ_BLOCK_ID_SP_FS << CP_LOADSTATE_STATEBLOCKID_SHIFT) + | (2 << CP_LOADSTATE_NUMOFUNITS_SHIFT); + *cmds++ = (HLSQ_SP_FS_INSTR << CP_LOADSTATE_STATETYPE_SHIFT) + | (0 << CP_LOADSTATE_EXTSRCADDR_SHIFT); + /* (sy)(rpt1)bary.f (ei)r0.z, (r)0, r0.x; */ + *cmds++ = 0x00002000; *cmds++ = 0x57368902; + /* (rpt5)nop; */ + *cmds++ = 0x00000000; *cmds++ = 0x00000500; + /* sam (f32)r0.xyzw, r0.z, s#0, t#0; */ + *cmds++ = 0x00000005; *cmds++ = 0xa0c01f00; + /* (sy)mov.f32f32 r1.x, r0.x; */ + *cmds++ = 0x00000000; *cmds++ = 0x30044004; + /* mov.f32f32 r1.y, r0.y; */ + *cmds++ = 0x00000001; *cmds++ = 0x20044005; + /* mov.f32f32 r1.z, r0.z; */ + *cmds++ = 0x00000002; *cmds++ = 0x20044006; + /* mov.f32f32 r1.w, r0.w; */ + *cmds++ = 0x00000003; *cmds++ = 0x20044007; + /* end; */ + *cmds++ = 0x00000000; *cmds++ = 0x03000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_VFD_CONTROL_0); + /* VFD_CONTROL_0 */ + *cmds++ = _SET(VFD_CTRLREG0_TOTALATTRTOVS, 8) | + _SET(VFD_CTRLREG0_PACKETSIZE, 2) | + _SET(VFD_CTRLREG0_STRMDECINSTRCNT, 2) | + _SET(VFD_CTRLREG0_STRMFETCHINSTRCNT, 2); + /* VFD_CONTROL_1 */ + *cmds++ = _SET(VFD_CTRLREG1_MAXSTORAGE, 2) | + _SET(VFD_CTRLREG1_REGID4VTX, 252) | + _SET(VFD_CTRLREG1_REGID4INST, 252); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_VFD_FETCH_INSTR_0_0); + /* VFD_FETCH_INSTR_0_0 */ + *cmds++ = _SET(VFD_FETCHINSTRUCTIONS_FETCHSIZE, 7) | + _SET(VFD_FETCHINSTRUCTIONS_BUFSTRIDE, 8) | + _SET(VFD_FETCHINSTRUCTIONS_SWITCHNEXT, 1) | + _SET(VFD_FETCHINSTRUCTIONS_STEPRATE, 1); + /* VFD_FETCH_INSTR_1_0 */ + *cmds++ = _SET(VFD_BASEADDR_BASEADDR, + shadow->quad_vertices_restore.gpuaddr); + /* VFD_FETCH_INSTR_0_1 */ + *cmds++ = _SET(VFD_FETCHINSTRUCTIONS_FETCHSIZE, 11) | + _SET(VFD_FETCHINSTRUCTIONS_BUFSTRIDE, 12) | + _SET(VFD_FETCHINSTRUCTIONS_INDEXDECODE, 1) | + _SET(VFD_FETCHINSTRUCTIONS_STEPRATE, 1); + /* VFD_FETCH_INSTR_1_1 */ + *cmds++ = _SET(VFD_BASEADDR_BASEADDR, + shadow->quad_vertices_restore.gpuaddr + 16); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_VFD_DECODE_INSTR_0); + /* VFD_DECODE_INSTR_0 */ + *cmds++ = _SET(VFD_DECODEINSTRUCTIONS_WRITEMASK, 0x0F) | + _SET(VFD_DECODEINSTRUCTIONS_CONSTFILL, 1) | + _SET(VFD_DECODEINSTRUCTIONS_FORMAT, 1) | + _SET(VFD_DECODEINSTRUCTIONS_SHIFTCNT, 8) | + _SET(VFD_DECODEINSTRUCTIONS_LASTCOMPVALID, 1) | + _SET(VFD_DECODEINSTRUCTIONS_SWITCHNEXT, 1); + /* VFD_DECODE_INSTR_1 */ + *cmds++ = _SET(VFD_DECODEINSTRUCTIONS_WRITEMASK, 0x0F) | + _SET(VFD_DECODEINSTRUCTIONS_CONSTFILL, 1) | + _SET(VFD_DECODEINSTRUCTIONS_FORMAT, 2) | + _SET(VFD_DECODEINSTRUCTIONS_REGID, 4) | + _SET(VFD_DECODEINSTRUCTIONS_SHIFTCNT, 12) | + _SET(VFD_DECODEINSTRUCTIONS_LASTCOMPVALID, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_DEPTH_CONTROL); + /* RB_DEPTH_CONTROL */ + *cmds++ = _SET(RB_DEPTHCONTROL_Z_TEST_FUNC, RB_FRAG_NEVER); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_STENCIL_CONTROL); + /* RB_STENCIL_CONTROL */ + *cmds++ = _SET(RB_STENCILCONTROL_STENCIL_FUNC, RB_REF_NEVER) | + _SET(RB_STENCILCONTROL_STENCIL_FAIL, RB_STENCIL_KEEP) | + _SET(RB_STENCILCONTROL_STENCIL_ZPASS, RB_STENCIL_KEEP) | + _SET(RB_STENCILCONTROL_STENCIL_ZFAIL, RB_STENCIL_KEEP) | + _SET(RB_STENCILCONTROL_STENCIL_FUNC_BF, RB_REF_NEVER) | + _SET(RB_STENCILCONTROL_STENCIL_FAIL_BF, RB_STENCIL_KEEP) | + _SET(RB_STENCILCONTROL_STENCIL_ZPASS_BF, RB_STENCIL_KEEP) | + _SET(RB_STENCILCONTROL_STENCIL_ZFAIL_BF, RB_STENCIL_KEEP); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MODE_CONTROL); + /* RB_MODE_CONTROL */ + *cmds++ = _SET(RB_MODECONTROL_RENDER_MODE, RB_RENDERING_PASS) | + _SET(RB_MODECONTROL_MARB_CACHE_SPLIT_MODE, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_RENDER_CONTROL); + /* RB_RENDER_CONTROL */ + *cmds++ = _SET(RB_RENDERCONTROL_BIN_WIDTH, shadow->width >> 5) | + _SET(RB_RENDERCONTROL_ALPHA_TEST_FUNC, 7); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MSAA_CONTROL); + /* RB_MSAA_CONTROL */ + *cmds++ = _SET(RB_MSAACONTROL_MSAA_DISABLE, 1) | + _SET(RB_MSAACONTROL_SAMPLE_MASK, 0xFFFF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MRT_CONTROL0); + /* RB_MRT_CONTROL0 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_ROP_CODE, 12) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_ALWAYS) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL0); + /* RB_MRT_BLENDCONTROL0 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + /* RB_MRT_CONTROL1 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_DISABLE) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL1); + /* RB_MRT_BLENDCONTROL1 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + /* RB_MRT_CONTROL2 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_DISABLE) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL2); + /* RB_MRT_BLENDCONTROL2 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + /* RB_MRT_CONTROL3 */ + *cmds++ = _SET(RB_MRTCONTROL_READ_DEST_ENABLE, 1) | + _SET(RB_MRTCONTROL_DITHER_MODE, RB_DITHER_DISABLE) | + _SET(RB_MRTCONTROL_COMPONENT_ENABLE, 0xF); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_RB_MRT_BLEND_CONTROL3); + /* RB_MRT_BLENDCONTROL3 */ + *cmds++ = _SET(RB_MRTBLENDCONTROL_RGB_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_RGB_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_RGB_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_ALPHA_SRC_FACTOR, RB_FACTOR_ONE) | + _SET(RB_MRTBLENDCONTROL_ALPHA_BLEND_OPCODE, RB_BLEND_OP_ADD) | + _SET(RB_MRTBLENDCONTROL_ALPHA_DEST_FACTOR, RB_FACTOR_ZERO) | + _SET(RB_MRTBLENDCONTROL_CLAMP_ENABLE, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_VFD_INDEX_MIN); + /* VFD_INDEX_MIN */ + *cmds++ = 0x00000000; + /* VFD_INDEX_MAX */ + *cmds++ = 0xFFFFFFFF; + /* VFD_INDEX_OFFSET */ + *cmds++ = 0x00000000; + /* TPL1_TP_VS_TEX_OFFSET */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_VFD_VS_THREADING_THRESHOLD); + /* VFD_VS_THREADING_THRESHOLD */ + *cmds++ = _SET(VFD_THREADINGTHRESHOLD_RESERVED6, 12) | + _SET(VFD_THREADINGTHRESHOLD_REGID_VTXCNT, 252); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_TPL1_TP_VS_TEX_OFFSET); + /* TPL1_TP_VS_TEX_OFFSET */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_TPL1_TP_FS_TEX_OFFSET); + /* TPL1_TP_FS_TEX_OFFSET */ + *cmds++ = _SET(TPL1_TPTEXOFFSETREG_SAMPLEROFFSET, 16) | + _SET(TPL1_TPTEXOFFSETREG_MEMOBJOFFSET, 16) | + _SET(TPL1_TPTEXOFFSETREG_BASETABLEPTR, 224); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_GRAS_SC_CONTROL); + /* GRAS_SC_CONTROL */ + /*cmds++ = _SET(GRAS_SC_CONTROL_RASTER_MODE, 1); + *cmds++ = _SET(GRAS_SC_CONTROL_RASTER_MODE, 1) |*/ + *cmds++ = 0x04001000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_GRAS_SU_MODE_CONTROL); + /* GRAS_SU_MODE_CONTROL */ + *cmds++ = 0x00000000; + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_GRAS_SC_WINDOW_SCISSOR_TL); + /* GRAS_SC_WINDOW_SCISSOR_TL */ + *cmds++ = 0x00000000; + /* GRAS_SC_WINDOW_SCISSOR_BR */ + *cmds++ = _SET(GRAS_SC_WINDOW_SCISSOR_BR_BR_X, shadow->width - 1) | + _SET(GRAS_SC_WINDOW_SCISSOR_BR_BR_Y, shadow->height - 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_GRAS_SC_SCREEN_SCISSOR_TL); + /* GRAS_SC_SCREEN_SCISSOR_TL */ + *cmds++ = 0x00000000; + /* GRAS_SC_SCREEN_SCISSOR_BR */ + *cmds++ = _SET(GRAS_SC_SCREEN_SCISSOR_BR_BR_X, shadow->width - 1) | + _SET(GRAS_SC_SCREEN_SCISSOR_BR_BR_Y, shadow->height - 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 5); + *cmds++ = CP_REG(A3XX_GRAS_CL_VPORT_XOFFSET); + /* GRAS_CL_VPORT_XOFFSET */ + *cmds++ = 0x00000000; + /* GRAS_CL_VPORT_XSCALE */ + *cmds++ = _SET(GRAS_CL_VPORT_XSCALE_VPORT_XSCALE, 0x3F800000); + /* GRAS_CL_VPORT_YOFFSET */ + *cmds++ = 0x00000000; + /* GRAS_CL_VPORT_YSCALE */ + *cmds++ = _SET(GRAS_CL_VPORT_YSCALE_VPORT_YSCALE, 0x3F800000); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 3); + *cmds++ = CP_REG(A3XX_GRAS_CL_VPORT_ZOFFSET); + /* GRAS_CL_VPORT_ZOFFSET */ + *cmds++ = 0x00000000; + /* GRAS_CL_VPORT_ZSCALE */ + *cmds++ = _SET(GRAS_CL_VPORT_ZSCALE_VPORT_ZSCALE, 0x3F800000); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_GRAS_CL_CLIP_CNTL); + /* GRAS_CL_CLIP_CNTL */ + *cmds++ = _SET(GRAS_CL_CLIP_CNTL_IJ_PERSP_CENTER, 1); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_SP_FS_IMAGE_OUTPUT_REG_0); + /* SP_FS_IMAGE_OUTPUT_REG_0 */ + *cmds++ = _SET(SP_IMAGEOUTPUTREG_MRTFORMAT, SP_R8G8B8A8_UNORM); + + *cmds++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmds++ = CP_REG(A3XX_PC_PRIM_VTX_CNTL); + /* PC_PRIM_VTX_CONTROL */ + *cmds++ = _SET(PC_PRIM_VTX_CONTROL_STRIDE_IN_VPC, 2) | + _SET(PC_PRIM_VTX_CONTROL_POLYMODE_FRONT_PTYPE, + PC_DRAW_TRIANGLES) | + _SET(PC_PRIM_VTX_CONTROL_POLYMODE_BACK_PTYPE, + PC_DRAW_TRIANGLES) | + _SET(PC_PRIM_VTX_CONTROL_PROVOKING_VTX_LAST, 1); + + *cmds++ = cp_type3_packet(CP_DRAW_INDX, 3); + *cmds++ = 0x00000000; /* Viz query info */ + *cmds++ = BUILD_PC_DRAW_INITIATOR(PC_DI_PT_RECTLIST, + PC_DI_SRC_SEL_AUTO_INDEX, + PC_DI_INDEX_SIZE_16_BIT, + PC_DI_IGNORE_VISIBILITY); + *cmds++ = 0x00000002; /* Num indices */ + + /* Create indirect buffer command for above command sequence */ + create_ib1(drawctxt, shadow->gmem_restore, start, cmds); + + return cmds; +} + +static void build_regrestore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *start = tmp_ctx.cmd; + unsigned int *cmd = start; + unsigned int *lcc_start; + + int i; + + /* Flush HLSQ lazy updates */ + *cmd++ = cp_type3_packet(CP_EVENT_WRITE, 1); + *cmd++ = 0x7; /* HLSQ_FLUSH */ + *cmd++ = cp_type3_packet(CP_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + *cmd++ = cp_type0_packet(A3XX_UCHE_CACHE_INVALIDATE0_REG, 2); + *cmd++ = 0x00000000; /* No start addr for full invalidate */ + *cmd++ = (unsigned int) + UCHE_ENTIRE_CACHE << UCHE_INVALIDATE1REG_ALLORPORTION | + UCHE_OP_INVALIDATE << UCHE_INVALIDATE1REG_OPCODE | + 0; /* No end addr for full invalidate */ + + lcc_start = cmd; + + /* deferred cp_type3_packet(CP_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 + + for (i = 0; i < ARRAY_SIZE(context_register_ranges) / 2; i++) { + cmd = reg_range(cmd, context_register_ranges[i * 2], + context_register_ranges[i * 2 + 1]); + } + + lcc_start[0] = cp_type3_packet(CP_LOAD_CONSTANT_CONTEXT, + (cmd - lcc_start) - 1); + +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + lcc_start[2] |= (0 << 24) | (4 << 16); /* Disable shadowing. */ +#else + lcc_start[2] |= (1 << 24) | (4 << 16); +#endif + + for (i = 0; i < ARRAY_SIZE(global_registers); i++) { + *cmd++ = cp_type0_packet(global_registers[i], 1); + tmp_ctx.reg_values[i] = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0x00000000; + } + + create_ib1(drawctxt, drawctxt->reg_restore, start, cmd); + tmp_ctx.cmd = cmd; +} + +static void build_constantrestore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start = cmd; + unsigned int mode = 4; /* Indirect mode */ + unsigned int stateblock; + unsigned int numunits; + unsigned int statetype; + + drawctxt->cond_execs[2].hostptr = cmd; + drawctxt->cond_execs[2].gpuaddr = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + drawctxt->cond_execs[3].hostptr = cmd; + drawctxt->cond_execs[3].gpuaddr = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + +#ifndef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + *cmd++ = cp_type3_packet(CP_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = (drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000; + *cmd++ = 4 << 16; + *cmd++ = 0x0; +#endif + /* HLSQ full update */ + *cmd++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmd++ = CP_REG(A3XX_HLSQ_CONTROL_0_REG); + *cmd++ = 0x68000240; /* A3XX_HLSQ_CONTROL_0_REG */ + +#ifndef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + /* Re-enable shadowing */ + *cmd++ = cp_type3_packet(CP_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = (drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000; + *cmd++ = (4 << 16) | (1 << 24); + *cmd++ = 0x0; +#endif + + /* Load vertex shader constants */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[2].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[2].gpuaddr >> 2; + *cmd++ = 0x0000ffff; + *cmd++ = 3; /* EXEC_COUNT */ + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + drawctxt->constant_load_commands[0].hostptr = cmd; + drawctxt->constant_load_commands[0].gpuaddr = virt2gpu(cmd, + &drawctxt->gpustate); + + /* + From fixup: + + mode = 4 (indirect) + stateblock = 4 (Vertex constants) + numunits = SP_VS_CTRL_REG1.VSCONSTLENGTH * 2; (256bit units) + + From register spec: + SP_VS_CTRL_REG1.VSCONSTLENGTH [09:00]: 0-512, unit = 128bits. + + ord1 = (numunits<<22) | (stateblock<<19) | (mode<<16); + */ + + *cmd++ = 0; /* ord1 */ + *cmd++ = ((drawctxt->gpustate.gpuaddr) & 0xfffffffc) | 1; + + /* Load fragment shader constants */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[3].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[3].gpuaddr >> 2; + *cmd++ = 0x0000ffff; + *cmd++ = 3; /* EXEC_COUNT */ + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + drawctxt->constant_load_commands[1].hostptr = cmd; + drawctxt->constant_load_commands[1].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + mode = 4 (indirect) + stateblock = 6 (Fragment constants) + numunits = SP_FS_CTRL_REG1.FSCONSTLENGTH * 2; (256bit units) + + From register spec: + SP_FS_CTRL_REG1.FSCONSTLENGTH [09:00]: 0-512, unit = 128bits. + + ord1 = (numunits<<22) | (stateblock<<19) | (mode<<16); + */ + + *cmd++ = 0; /* ord1 */ + drawctxt->constant_load_commands[2].hostptr = cmd; + drawctxt->constant_load_commands[2].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + base = drawctxt->gpustate.gpuaddr (ALU constant shadow base) + offset = SP_FS_OBJ_OFFSET_REG.CONSTOBJECTSTARTOFFSET + + From register spec: + SP_FS_OBJ_OFFSET_REG.CONSTOBJECTSTARTOFFSET [16:24]: Constant object + start offset in on chip RAM, + 128bit aligned + + ord2 = base + offset | 1 + Because of the base alignment we can use + ord2 = base | offset | 1 + */ + *cmd++ = 0; /* ord2 */ + + /* Restore VS texture memory objects */ + stateblock = 0; + statetype = 1; + numunits = (TEX_SIZE_MEM_OBJECTS / 7) / 4; + + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + *cmd++ = (numunits << 22) | (stateblock << 19) | (mode << 16); + *cmd++ = ((drawctxt->gpustate.gpuaddr + VS_TEX_OFFSET_MEM_OBJECTS) + & 0xfffffffc) | statetype; + + /* Restore VS texture mipmap addresses */ + stateblock = 1; + statetype = 1; + numunits = TEX_SIZE_MIPMAP / 4; + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + *cmd++ = (numunits << 22) | (stateblock << 19) | (mode << 16); + *cmd++ = ((drawctxt->gpustate.gpuaddr + VS_TEX_OFFSET_MIPMAP) + & 0xfffffffc) | statetype; + + /* Restore VS texture sampler objects */ + stateblock = 0; + statetype = 0; + numunits = (TEX_SIZE_SAMPLER_OBJ / 2) / 4; + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + *cmd++ = (numunits << 22) | (stateblock << 19) | (mode << 16); + *cmd++ = ((drawctxt->gpustate.gpuaddr + VS_TEX_OFFSET_SAMPLER_OBJ) + & 0xfffffffc) | statetype; + + /* Restore FS texture memory objects */ + stateblock = 2; + statetype = 1; + numunits = (TEX_SIZE_MEM_OBJECTS / 7) / 4; + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + *cmd++ = (numunits << 22) | (stateblock << 19) | (mode << 16); + *cmd++ = ((drawctxt->gpustate.gpuaddr + FS_TEX_OFFSET_MEM_OBJECTS) + & 0xfffffffc) | statetype; + + /* Restore FS texture mipmap addresses */ + stateblock = 3; + statetype = 1; + numunits = TEX_SIZE_MIPMAP / 4; + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + *cmd++ = (numunits << 22) | (stateblock << 19) | (mode << 16); + *cmd++ = ((drawctxt->gpustate.gpuaddr + FS_TEX_OFFSET_MIPMAP) + & 0xfffffffc) | statetype; + + /* Restore FS texture sampler objects */ + stateblock = 2; + statetype = 0; + numunits = (TEX_SIZE_SAMPLER_OBJ / 2) / 4; + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + *cmd++ = (numunits << 22) | (stateblock << 19) | (mode << 16); + *cmd++ = ((drawctxt->gpustate.gpuaddr + FS_TEX_OFFSET_SAMPLER_OBJ) + & 0xfffffffc) | statetype; + + create_ib1(drawctxt, drawctxt->constant_restore, start, cmd); + tmp_ctx.cmd = cmd; +} + +static void build_shader_restore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start = cmd; + + /* Vertex shader */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[0].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[0].gpuaddr >> 2; + *cmd++ = 1; + *cmd++ = 3; /* EXEC_COUNT */ + + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + drawctxt->shader_load_commands[0].hostptr = cmd; + drawctxt->shader_load_commands[0].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + mode = 4 (indirect) + stateblock = 4 (Vertex shader) + numunits = SP_VS_CTRL_REG0.VS_LENGTH + + From regspec: + SP_VS_CTRL_REG0.VS_LENGTH [31:24]: VS length, unit = 256bits. + If bit31 is 1, it means overflow + or any long shader. + + ord1 = (numunits<<22) | (stateblock<<19) | (mode<<11) + */ + *cmd++ = 0; /*ord1 */ + *cmd++ = (drawctxt->gpustate.gpuaddr + SHADER_OFFSET) & 0xfffffffc; + + /* Fragment shader */ + *cmd++ = cp_type3_packet(CP_COND_EXEC, 4); + *cmd++ = drawctxt->cond_execs[1].gpuaddr >> 2; + *cmd++ = drawctxt->cond_execs[1].gpuaddr >> 2; + *cmd++ = 1; + *cmd++ = 3; /* EXEC_COUNT */ + + *cmd++ = cp_type3_packet(CP_LOAD_STATE, 2); + drawctxt->shader_load_commands[1].hostptr = cmd; + drawctxt->shader_load_commands[1].gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + /* + From fixup: + + mode = 4 (indirect) + stateblock = 6 (Fragment shader) + numunits = SP_FS_CTRL_REG0.FS_LENGTH + + From regspec: + SP_FS_CTRL_REG0.FS_LENGTH [31:24]: FS length, unit = 256bits. + If bit31 is 1, it means overflow + or any long shader. + + ord1 = (numunits<<22) | (stateblock<<19) | (mode<<11) + */ + *cmd++ = 0; /*ord1 */ + *cmd++ = (drawctxt->gpustate.gpuaddr + SHADER_OFFSET + + (SHADER_SHADOW_SIZE / 2)) & 0xfffffffc; + + create_ib1(drawctxt, drawctxt->shader_restore, start, cmd); + tmp_ctx.cmd = cmd; +} + +static void build_hlsqcontrol_restore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start = cmd; + + *cmd++ = cp_type3_packet(CP_SET_CONSTANT, 2); + *cmd++ = CP_REG(A3XX_HLSQ_CONTROL_0_REG); + drawctxt->hlsqcontrol_restore_commands[0].hostptr = cmd; + drawctxt->hlsqcontrol_restore_commands[0].gpuaddr + = virt2gpu(cmd, &drawctxt->gpustate); + *cmd++ = 0; + + /* Create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->hlsqcontrol_restore, start, cmd); + + tmp_ctx.cmd = cmd; +} + +/* IB that modifies the shader and constant sizes and offsets in restore IBs. */ +static void build_restore_fixup_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + unsigned int *cmd = tmp_ctx.cmd; + unsigned int *start = cmd; + +#ifdef GSL_CONTEXT_SWITCH_CPU_SYNC + /* Save shader sizes */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_VS_CTRL_REG0; + *cmd++ = drawctxt->shader_load_commands[0].gpuaddr; + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_CTRL_REG0; + *cmd++ = drawctxt->shader_load_commands[1].gpuaddr; + + /* Save constant sizes */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_VS_CTRL_REG1; + *cmd++ = drawctxt->constant_load_commands[0].gpuaddr; + + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_CTRL_REG1; + *cmd++ = drawctxt->constant_load_commands[1].gpuaddr; + + /* Save constant offsets */ + *cmd++ = cp_type3_packet(CP_REG_TO_MEM, 2); + *cmd++ = A3XX_SP_FS_OBJ_OFFSET_REG; + *cmd++ = drawctxt->constant_load_commands[2].gpuaddr; +#else + /* Save shader sizes */ + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG0, 0x7f000000, + 30, (4 << 19) | (4 << 16), + drawctxt->shader_load_commands[0].gpuaddr); + + cmd = rmw_regtomem(cmd, A3XX_SP_FS_CTRL_REG0, 0x7f000000, + 30, (6 << 19) | (4 << 16), + drawctxt->shader_load_commands[1].gpuaddr); + + /* Save constant sizes */ + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG1, 0x000003ff, + 23, (4 << 19) | (4 << 16), + drawctxt->constant_load_commands[0].gpuaddr); + + cmd = rmw_regtomem(cmd, A3XX_SP_FS_CTRL_REG1, 0x000003ff, + 23, (6 << 19) | (4 << 16), + drawctxt->constant_load_commands[1].gpuaddr); + + /* Modify constant restore conditionals */ + cmd = rmw_regtomem(cmd, A3XX_SP_VS_CTRL_REG1, 0x000003ff, + 0, 0, drawctxt->cond_execs[2].gpuaddr); + + cmd = rmw_regtomem(cmd, A3XX_SP_FS_CTRL_REG1, 0x000003ff, + 0, 0, drawctxt->cond_execs[3].gpuaddr); + + /* Save fragment constant shadow offset */ + cmd = rmw_regtomem(cmd, A3XX_SP_FS_OBJ_OFFSET_REG, 0x00ff0000, + 18, (drawctxt->gpustate.gpuaddr & 0xfffffe00) | 1, + drawctxt->constant_load_commands[2].gpuaddr); +#endif + + /* Use mask value to avoid flushing HLSQ which would cause the HW to + discard all the shader data */ + + cmd = rmw_regtomem(cmd, A3XX_HLSQ_CONTROL_0_REG, 0x9ffffdff, + 0, 0, drawctxt->hlsqcontrol_restore_commands[0].gpuaddr); + + create_ib1(drawctxt, drawctxt->restore_fixup, start, cmd); + + tmp_ctx.cmd = cmd; +} + +static int a3xx_create_gpustate_shadow(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + drawctxt->flags |= CTXT_FLAGS_STATE_SHADOW; + + build_regrestore_cmds(adreno_dev, drawctxt); + build_constantrestore_cmds(adreno_dev, drawctxt); + build_hlsqcontrol_restore_cmds(adreno_dev, drawctxt); + build_regconstantsave_cmds(adreno_dev, drawctxt); + build_shader_save_cmds(adreno_dev, drawctxt); + build_shader_restore_cmds(adreno_dev, drawctxt); + build_restore_fixup_cmds(adreno_dev, drawctxt); + build_save_fixup_cmds(adreno_dev, drawctxt); + + return 0; +} + +/* create buffers for saving/restoring registers, constants, & GMEM */ +static int a3xx_create_gmem_shadow(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + int result; + + calc_gmemsize(&drawctxt->context_gmem_shadow, adreno_dev->gmem_size); + tmp_ctx.gmem_base = adreno_dev->gmem_base; + + result = kgsl_allocate(&drawctxt->context_gmem_shadow.gmemshadow, + drawctxt->pagetable, drawctxt->context_gmem_shadow.size); + + if (result) + return result; + + build_quad_vtxbuff(drawctxt, &drawctxt->context_gmem_shadow, + &tmp_ctx.cmd); + + /* Dow we need to idle? */ + /* adreno_idle(&adreno_dev->dev, KGSL_TIMEOUT_DEFAULT); */ + + tmp_ctx.cmd = build_gmem2sys_cmds(adreno_dev, drawctxt, + &drawctxt->context_gmem_shadow); + tmp_ctx.cmd = build_sys2gmem_cmds(adreno_dev, drawctxt, + &drawctxt->context_gmem_shadow); + + kgsl_cache_range_op(&drawctxt->context_gmem_shadow.gmemshadow, + KGSL_CACHE_OP_FLUSH); + + drawctxt->flags |= CTXT_FLAGS_GMEM_SHADOW; + + return 0; +} + +static int a3xx_drawctxt_create(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt) +{ + int ret; + + /* + * Allocate memory for the GPU state and the context commands. + * Despite the name, this is much more then just storage for + * the gpustate. This contains command space for gmem save + * and texture and vertex buffer storage too + */ + + ret = kgsl_allocate(&drawctxt->gpustate, + drawctxt->pagetable, CONTEXT_SIZE); + + if (ret) + return ret; + + kgsl_sharedmem_set(&drawctxt->gpustate, 0, 0, CONTEXT_SIZE); + tmp_ctx.cmd = drawctxt->gpustate.hostptr + CMD_OFFSET; + + if (!(drawctxt->flags & CTXT_FLAGS_PREAMBLE)) { + ret = a3xx_create_gpustate_shadow(adreno_dev, drawctxt); + if (ret) + goto done; + + drawctxt->flags |= CTXT_FLAGS_SHADER_SAVE; + } + + if (!(drawctxt->flags & CTXT_FLAGS_NOGMEMALLOC)) + ret = a3xx_create_gmem_shadow(adreno_dev, drawctxt); + +done: + if (ret) + kgsl_sharedmem_free(&drawctxt->gpustate); + + return ret; +} + +static void a3xx_drawctxt_save(struct adreno_device *adreno_dev, + struct adreno_context *context) +{ + struct kgsl_device *device = &adreno_dev->dev; + + if (context == NULL) + return; + + if (context->flags & CTXT_FLAGS_GPU_HANG) + KGSL_CTXT_WARN(device, + "Current active context has caused gpu hang\n"); + + if (!(context->flags & CTXT_FLAGS_PREAMBLE)) { + /* Fixup self modifying IBs for save operations */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->save_fixup, 3); + + /* save registers and constants. */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->regconstant_save, 3); + + if (context->flags & CTXT_FLAGS_SHADER_SAVE) { + /* Save shader instructions */ + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_PMODE, context->shader_save, 3); + + context->flags |= CTXT_FLAGS_SHADER_RESTORE; + } + } + + if ((context->flags & CTXT_FLAGS_GMEM_SAVE) && + (context->flags & CTXT_FLAGS_GMEM_SHADOW)) { + /* + * Save GMEM (note: changes shader. shader must + * already be saved.) + */ + + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + context->context_gmem_shadow. + gmem_save, 3); + context->flags |= CTXT_FLAGS_GMEM_RESTORE; + } +} + +static void a3xx_drawctxt_restore(struct adreno_device *adreno_dev, + struct adreno_context *context) +{ + struct kgsl_device *device = &adreno_dev->dev; + unsigned int cmds[5]; + + if (context == NULL) { + /* No context - set the default pagetable and thats it */ + kgsl_mmu_setstate(&device->mmu, device->mmu.defaultpagetable); + return; + } + + KGSL_CTXT_INFO(device, "context flags %08x\n", context->flags); + + cmds[0] = cp_nop_packet(1); + cmds[1] = KGSL_CONTEXT_TO_MEM_IDENTIFIER; + cmds[2] = cp_type3_packet(CP_MEM_WRITE, 2); + cmds[3] = device->memstore.gpuaddr + + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, current_context); + cmds[4] = context->id; + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, cmds, 5); + kgsl_mmu_setstate(&device->mmu, context->pagetable); + + /* + * Restore GMEM. (note: changes shader. + * Shader must not already be restored.) + */ + + if (context->flags & CTXT_FLAGS_GMEM_RESTORE) { + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + context->context_gmem_shadow. + gmem_restore, 3); + context->flags &= ~CTXT_FLAGS_GMEM_RESTORE; + } + + if (!(context->flags & CTXT_FLAGS_PREAMBLE)) { + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->reg_restore, 3); + + /* Fixup self modifying IBs for restore operations */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->restore_fixup, 3); + + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->constant_restore, 3); + + if (context->flags & CTXT_FLAGS_SHADER_RESTORE) + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->shader_restore, 3); + + /* Restore HLSQ_CONTROL_0 register */ + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_NONE, + context->hlsqcontrol_restore, 3); + } +} + +static void a3xx_rb_init(struct adreno_device *adreno_dev, + struct adreno_ringbuffer *rb) +{ + unsigned int *cmds, cmds_gpu; + cmds = adreno_ringbuffer_allocspace(rb, 18); + cmds_gpu = rb->buffer_desc.gpuaddr + sizeof(uint) * (rb->wptr - 18); + + GSL_RB_WRITE(cmds, cmds_gpu, cp_type3_packet(CP_ME_INIT, 17)); + GSL_RB_WRITE(cmds, cmds_gpu, 0x000003f7); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000080); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000100); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000180); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00006600); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000150); + GSL_RB_WRITE(cmds, cmds_gpu, 0x0000014e); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000154); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000001); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + /* Protected mode control - turned off for A3XX */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + + adreno_ringbuffer_submit(rb); +} + +#define VBIF_MAX_CLIENTS 6 + +static void a3xx_vbif_callback(struct adreno_device *adreno_dev, + unsigned int status) +{ + struct kgsl_device *device = &adreno_dev->dev; + int i; + char str[80], *ptr = str; + int slen = sizeof(str) - 1; + + KGSL_DRV_INFO(device, "VBIF error | status=%X\n", + status); + + for (i = 0; i < VBIF_MAX_CLIENTS; i++) { + if (status & (1 << i)) { + unsigned int err; + int ret; + + adreno_regwrite(device, A3XX_VBIF_ERR_INFO, i); + adreno_regread(device, A3XX_VBIF_ERR_INFO, &err); + + ret = snprintf(ptr, slen, "%d:%8.8X ", i, err); + ptr += ret; + slen -= ret; + } + } + + KGSL_DRV_INFO(device, "%s\n", str); + + /* Clear the errors */ + adreno_regwrite(device, A3XX_VBIF_ERR_CLEAR, status); +} + +static void a3xx_err_callback(struct adreno_device *adreno_dev, int bit) +{ + struct kgsl_device *device = &adreno_dev->dev; + const char *err = ""; + + switch (bit) { + case A3XX_INT_RBBM_AHB_ERROR: { + unsigned int reg; + + adreno_regread(device, A3XX_RBBM_AHB_ERROR_STATUS, ®); + + /* + * Return the word address of the erroring register so that it + * matches the register specification + */ + + KGSL_DRV_CRIT(device, + "RBBM | AHB bus error | %s | addr=%x | ports=%x:%x\n", + reg & (1 << 28) ? "WRITE" : "READ", + (reg & 0xFFFFF) >> 2, (reg >> 20) & 0x3, + (reg >> 24) & 0x3); + + /* Clear the error */ + adreno_regwrite(device, A3XX_RBBM_AHB_CMD, (1 << 3)); + return; + } + case A3XX_INT_RBBM_REG_TIMEOUT: + err = "RBBM: AHB register timeout"; + break; + case A3XX_INT_RBBM_ME_MS_TIMEOUT: + err = "RBBM: ME master split timeout"; + break; + case A3XX_INT_RBBM_PFP_MS_TIMEOUT: + err = "RBBM: PFP master split timeout"; + break; + case A3XX_INT_RBBM_ATB_BUS_OVERFLOW: + err = "RBBM: ATB bus oveflow"; + break; + case A3XX_INT_VFD_ERROR: + err = "VFD: Out of bounds access"; + break; + case A3XX_INT_CP_T0_PACKET_IN_IB: + err = "ringbuffer TO packet in IB interrupt"; + break; + case A3XX_INT_CP_OPCODE_ERROR: + err = "ringbuffer opcode error interrupt"; + break; + case A3XX_INT_CP_RESERVED_BIT_ERROR: + err = "ringbuffer reserved bit error interrupt"; + break; + case A3XX_INT_CP_HW_FAULT: + err = "ringbuffer hardware fault"; + break; + case A3XX_INT_CP_REG_PROTECT_FAULT: + err = "ringbuffer protected mode error interrupt"; + break; + case A3XX_INT_CP_AHB_ERROR_HALT: + err = "ringbuffer AHB error interrupt"; + break; + case A3XX_INT_MISC_HANG_DETECT: + err = "MISC: GPU hang detected"; + break; + case A3XX_INT_UCHE_OOB_ACCESS: + err = "UCHE: Out of bounds access"; + break; + } + + KGSL_DRV_CRIT(device, "%s\n", err); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); +} + +static void a3xx_cp_callback(struct adreno_device *adreno_dev, int irq) +{ + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + if (irq == A3XX_INT_CP_RB_INT) { + unsigned int context_id; + kgsl_sharedmem_readl(&adreno_dev->dev.memstore, + &context_id, + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + current_context)); + if (context_id < KGSL_MEMSTORE_MAX) { + kgsl_sharedmem_writel(&rb->device->memstore, + KGSL_MEMSTORE_OFFSET(context_id, + ts_cmp_enable), 0); + wmb(); + } + KGSL_CMD_WARN(rb->device, "ringbuffer rb interrupt\n"); + } + + wake_up_interruptible_all(&rb->device->wait_queue); + + /* Schedule work to free mem and issue ibs */ + queue_work(rb->device->work_queue, &rb->device->ts_expired_ws); + + atomic_notifier_call_chain(&rb->device->ts_notifier_list, + rb->device->id, NULL); +} + +#define A3XX_IRQ_CALLBACK(_c) { .func = _c } + +#define A3XX_INT_MASK \ + ((1 << A3XX_INT_RBBM_AHB_ERROR) | \ + (1 << A3XX_INT_RBBM_REG_TIMEOUT) | \ + (1 << A3XX_INT_RBBM_ATB_BUS_OVERFLOW) | \ + (1 << A3XX_INT_CP_T0_PACKET_IN_IB) | \ + (1 << A3XX_INT_CP_OPCODE_ERROR) | \ + (1 << A3XX_INT_CP_RESERVED_BIT_ERROR) | \ + (1 << A3XX_INT_CP_HW_FAULT) | \ + (1 << A3XX_INT_CP_IB1_INT) | \ + (1 << A3XX_INT_CP_IB2_INT) | \ + (1 << A3XX_INT_CP_RB_INT) | \ + (1 << A3XX_INT_CP_REG_PROTECT_FAULT) | \ + (1 << A3XX_INT_CP_AHB_ERROR_HALT) | \ + (1 << A3XX_INT_UCHE_OOB_ACCESS)) + +static struct { + void (*func)(struct adreno_device *, int); +} a3xx_irq_funcs[] = { + A3XX_IRQ_CALLBACK(NULL), /* 0 - RBBM_GPU_IDLE */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 1 - RBBM_AHB_ERROR */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 2 - RBBM_REG_TIMEOUT */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 3 - RBBM_ME_MS_TIMEOUT */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 4 - RBBM_PFP_MS_TIMEOUT */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 5 - RBBM_ATB_BUS_OVERFLOW */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 6 - RBBM_VFD_ERROR */ + A3XX_IRQ_CALLBACK(NULL), /* 7 - CP_SW */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 8 - CP_T0_PACKET_IN_IB */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 9 - CP_OPCODE_ERROR */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 10 - CP_RESERVED_BIT_ERROR */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 11 - CP_HW_FAULT */ + A3XX_IRQ_CALLBACK(NULL), /* 12 - CP_DMA */ + A3XX_IRQ_CALLBACK(a3xx_cp_callback), /* 13 - CP_IB2_INT */ + A3XX_IRQ_CALLBACK(a3xx_cp_callback), /* 14 - CP_IB1_INT */ + A3XX_IRQ_CALLBACK(a3xx_cp_callback), /* 15 - CP_RB_INT */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 16 - CP_REG_PROTECT_FAULT */ + A3XX_IRQ_CALLBACK(NULL), /* 17 - CP_RB_DONE_TS */ + A3XX_IRQ_CALLBACK(NULL), /* 18 - CP_VS_DONE_TS */ + A3XX_IRQ_CALLBACK(NULL), /* 19 - CP_PS_DONE_TS */ + A3XX_IRQ_CALLBACK(NULL), /* 20 - CP_CACHE_FLUSH_TS */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 21 - CP_AHB_ERROR_FAULT */ + A3XX_IRQ_CALLBACK(NULL), /* 22 - Unused */ + A3XX_IRQ_CALLBACK(NULL), /* 23 - Unused */ + A3XX_IRQ_CALLBACK(NULL), /* 24 - MISC_HANG_DETECT */ + A3XX_IRQ_CALLBACK(a3xx_err_callback), /* 25 - UCHE_OOB_ACCESS */ + /* 26 to 31 - Unused */ +}; + +static irqreturn_t a3xx_irq_handler(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + irqreturn_t ret = IRQ_NONE; + unsigned int status, tmp; + int i; + + adreno_regread(&adreno_dev->dev, A3XX_RBBM_INT_0_STATUS, &status); + + for (tmp = status, i = 0; tmp && i < ARRAY_SIZE(a3xx_irq_funcs); i++) { + if (tmp & 1) { + if (a3xx_irq_funcs[i].func != NULL) { + a3xx_irq_funcs[i].func(adreno_dev, i); + ret = IRQ_HANDLED; + } else { + KGSL_DRV_CRIT(device, + "Unhandled interrupt bit %x\n", i); + } + } + + tmp >>= 1; + } + + trace_kgsl_a3xx_irq_status(device, status); + + if (status) + adreno_regwrite(&adreno_dev->dev, A3XX_RBBM_INT_CLEAR_CMD, + status); + + /* Check for VBIF errors */ + adreno_regread(&adreno_dev->dev, A3XX_VBIF_ERR_PENDING, &status); + + if (status) { + a3xx_vbif_callback(adreno_dev, status); + ret = IRQ_HANDLED; + } + + return ret; +} + +static void a3xx_irq_control(struct adreno_device *adreno_dev, int state) +{ + struct kgsl_device *device = &adreno_dev->dev; + + if (state) { + adreno_regwrite(device, A3XX_RBBM_INT_0_MASK, A3XX_INT_MASK); + + /* Enable VBIF interrupts - write 0 to enable them all */ + adreno_regwrite(device, A3XX_VBIF_ERR_MASK, 0); + /* Clear outstanding VBIF errors */ + adreno_regwrite(device, A3XX_VBIF_ERR_CLEAR, 0x3F); + } else { + adreno_regwrite(device, A3XX_RBBM_INT_0_MASK, 0); + adreno_regwrite(device, A3XX_VBIF_ERR_MASK, 0xFFFFFFFF); + } +} + +static unsigned int a3xx_busy_cycles(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + unsigned int reg, val; + + /* Freeze the counter */ + adreno_regread(device, A3XX_RBBM_RBBM_CTL, ®); + reg &= ~RBBM_RBBM_CTL_ENABLE_PWR_CTR1; + adreno_regwrite(device, A3XX_RBBM_RBBM_CTL, reg); + + /* Read the value */ + adreno_regread(device, A3XX_RBBM_PERFCTR_PWR_1_LO, &val); + + /* Reset the counter */ + reg |= RBBM_RBBM_CTL_RESET_PWR_CTR1; + adreno_regwrite(device, A3XX_RBBM_RBBM_CTL, reg); + + /* Re-enable the counter */ + reg &= ~RBBM_RBBM_CTL_RESET_PWR_CTR1; + reg |= RBBM_RBBM_CTL_ENABLE_PWR_CTR1; + adreno_regwrite(device, A3XX_RBBM_RBBM_CTL, reg); + + return val; +} + +static void a3xx_start(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + + /* Reset the core */ + adreno_regwrite(device, A3XX_RBBM_SW_RESET_CMD, + 0x00000001); + msleep(20); + + /* Set up 16 deep read/write request queues */ + + adreno_regwrite(device, A3XX_VBIF_IN_RD_LIM_CONF0, 0x10101010); + adreno_regwrite(device, A3XX_VBIF_IN_RD_LIM_CONF1, 0x10101010); + adreno_regwrite(device, A3XX_VBIF_OUT_RD_LIM_CONF0, 0x10101010); + adreno_regwrite(device, A3XX_VBIF_OUT_WR_LIM_CONF0, 0x10101010); + adreno_regwrite(device, A3XX_VBIF_DDR_OUT_MAX_BURST, 0x00000303); + adreno_regwrite(device, A3XX_VBIF_IN_WR_LIM_CONF0, 0x10101010); + adreno_regwrite(device, A3XX_VBIF_IN_WR_LIM_CONF1, 0x10101010); + + /* Enable WR-REQ */ + adreno_regwrite(device, A3XX_VBIF_GATE_OFF_WRREQ_EN, 0x000000FF); + + /* Set up round robin arbitration between both AXI ports */ + adreno_regwrite(device, A3XX_VBIF_ARB_CTL, 0x00000030); + + /* Set up AOOO */ + adreno_regwrite(device, A3XX_VBIF_OUT_AXI_AOOO_EN, 0x0000003C); + adreno_regwrite(device, A3XX_VBIF_OUT_AXI_AOOO, 0x003C003C); + + if (cpu_is_apq8064()) { + /* Enable 1K sort */ + adreno_regwrite(device, A3XX_VBIF_ABIT_SORT, 0x000000FF); + adreno_regwrite(device, A3XX_VBIF_ABIT_SORT_CONF, 0x000000A4); + } + /* Make all blocks contribute to the GPU BUSY perf counter */ + adreno_regwrite(device, A3XX_RBBM_GPU_BUSY_MASKED, 0xFFFFFFFF); + + /* Tune the hystersis counters for SP and CP idle detection */ + adreno_regwrite(device, A3XX_RBBM_SP_HYST_CNT, 0x10); + adreno_regwrite(device, A3XX_RBBM_WAIT_IDLE_CLOCKS_CTL, 0x10); + + /* Enable the RBBM error reporting bits. This lets us get + useful information on failure */ + + adreno_regwrite(device, A3XX_RBBM_AHB_CTL0, 0x00000001); + + /* Enable AHB error reporting */ + adreno_regwrite(device, A3XX_RBBM_AHB_CTL1, 0xA6FFFFFF); + + /* Turn on the power counters */ + adreno_regwrite(device, A3XX_RBBM_RBBM_CTL, 0x00030000); + + /* Turn on hang detection - this spews a lot of useful information + * into the RBBM registers on a hang */ + + adreno_regwrite(device, A3XX_RBBM_INTERFACE_HANG_INT_CTL, + (1 << 16) | 0xFFF); + +} + +/* Defined in adreno_a3xx_snapshot.c */ +void *a3xx_snapshot(struct adreno_device *adreno_dev, void *snapshot, + int *remain, int hang); + +struct adreno_gpudev adreno_a3xx_gpudev = { + .reg_rbbm_status = A3XX_RBBM_STATUS, + .reg_cp_pfp_ucode_addr = A3XX_CP_PFP_UCODE_ADDR, + .reg_cp_pfp_ucode_data = A3XX_CP_PFP_UCODE_DATA, + + .ctxt_create = a3xx_drawctxt_create, + .ctxt_save = a3xx_drawctxt_save, + .ctxt_restore = a3xx_drawctxt_restore, + .rb_init = a3xx_rb_init, + .irq_control = a3xx_irq_control, + .irq_handler = a3xx_irq_handler, + .busy_cycles = a3xx_busy_cycles, + .start = a3xx_start, + .snapshot = a3xx_snapshot, +}; diff --git a/drivers/gpu/msm/adreno_a3xx_snapshot.c b/drivers/gpu/msm/adreno_a3xx_snapshot.c new file mode 100644 index 0000000000000000000000000000000000000000..c8c7c441add6467783ffe4f2bc61d0108760ac15 --- /dev/null +++ b/drivers/gpu/msm/adreno_a3xx_snapshot.c @@ -0,0 +1,296 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "kgsl.h" +#include "adreno.h" +#include "kgsl_snapshot.h" +#include "a3xx_reg.h" + +#define DEBUG_SECTION_SZ(_dwords) (((_dwords) * sizeof(unsigned int)) \ + + sizeof(struct kgsl_snapshot_debug)) + +#define VPC_MEMORY_BANKS 4 +#define VPC_MEMORY_SIZE 512 + +static int a3xx_snapshot_vpc_memory(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int size = VPC_MEMORY_BANKS * VPC_MEMORY_SIZE; + int bank, addr, i = 0; + + if (remain < DEBUG_SECTION_SZ(size)) { + SNAPSHOT_ERR_NOMEM(device, "VPC MEMORY"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_VPC_MEMORY; + header->size = size; + + for (bank = 0; bank < VPC_MEMORY_BANKS; bank++) { + for (addr = 0; addr < VPC_MEMORY_SIZE; addr++) { + unsigned int val = bank | (addr << 4); + adreno_regwrite(device, + A3XX_VPC_VPC_DEBUG_RAM_SEL, val); + adreno_regread(device, + A3XX_VPC_VPC_DEBUG_RAM_READ, &data[i++]); + } + } + + return DEBUG_SECTION_SZ(size); +} + +#define CP_MEQ_SIZE 16 +static int a3xx_snapshot_cp_meq(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i; + + if (remain < DEBUG_SECTION_SZ(CP_MEQ_SIZE)) { + SNAPSHOT_ERR_NOMEM(device, "CP MEQ DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_CP_MEQ; + header->size = CP_MEQ_SIZE; + + adreno_regwrite(device, A3XX_CP_MEQ_ADDR, 0x0); + for (i = 0; i < CP_MEQ_SIZE; i++) + adreno_regread(device, A3XX_CP_MEQ_DATA, &data[i]); + + return DEBUG_SECTION_SZ(CP_MEQ_SIZE); +} + +static int a3xx_snapshot_cp_pm4_ram(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i, size = adreno_dev->pm4_fw_size - 1; + + if (remain < DEBUG_SECTION_SZ(size)) { + SNAPSHOT_ERR_NOMEM(device, "CP PM4 RAM DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_CP_PM4_RAM; + header->size = size; + + /* + * Read the firmware from the GPU rather than use our cache in order to + * try to catch mis-programming or corruption in the hardware. We do + * use the cached version of the size, however, instead of trying to + * maintain always changing hardcoded constants + */ + + adreno_regwrite(device, REG_CP_ME_RAM_RADDR, 0x0); + for (i = 0; i < size; i++) + adreno_regread(device, REG_CP_ME_RAM_DATA, &data[i]); + + return DEBUG_SECTION_SZ(size); +} + +static int a3xx_snapshot_cp_pfp_ram(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i, size = adreno_dev->pfp_fw_size - 1; + + if (remain < DEBUG_SECTION_SZ(size)) { + SNAPSHOT_ERR_NOMEM(device, "CP PFP RAM DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_CP_PFP_RAM; + header->size = size; + + /* + * Read the firmware from the GPU rather than use our cache in order to + * try to catch mis-programming or corruption in the hardware. We do + * use the cached version of the size, however, instead of trying to + * maintain always changing hardcoded constants + */ + kgsl_regwrite(device, A3XX_CP_PFP_UCODE_ADDR, 0x0); + for (i = 0; i < size; i++) + adreno_regread(device, A3XX_CP_PFP_UCODE_DATA, &data[i]); + + return DEBUG_SECTION_SZ(size); +} + +#define CP_ROQ_SIZE 128 + +static int a3xx_snapshot_cp_roq(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_debug *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i; + + if (remain < DEBUG_SECTION_SZ(CP_ROQ_SIZE)) { + SNAPSHOT_ERR_NOMEM(device, "CP ROQ DEBUG"); + return 0; + } + + header->type = SNAPSHOT_DEBUG_CP_ROQ; + header->size = CP_ROQ_SIZE; + + adreno_regwrite(device, A3XX_CP_ROQ_ADDR, 0x0); + for (i = 0; i < CP_ROQ_SIZE; i++) + adreno_regread(device, A3XX_CP_ROQ_DATA, &data[i]); + + return DEBUG_SECTION_SZ(CP_ROQ_SIZE); +} + +#define DEBUGFS_BLOCK_SIZE 0x40 + +static int a3xx_snapshot_debugbus_block(struct kgsl_device *device, + void *snapshot, int remain, void *priv) +{ + struct kgsl_snapshot_debugbus *header = snapshot; + unsigned int id = (unsigned int) priv; + unsigned int val; + int i; + unsigned int *data = snapshot + sizeof(*header); + int size = + (DEBUGFS_BLOCK_SIZE * sizeof(unsigned int)) + sizeof(*header); + + if (remain < size) { + SNAPSHOT_ERR_NOMEM(device, "DEBUGBUS"); + return 0; + } + + val = (id << 8) | (1 << 16); + + header->id = id; + header->count = DEBUGFS_BLOCK_SIZE; + + for (i = 0; i < DEBUGFS_BLOCK_SIZE; i++) { + adreno_regwrite(device, A3XX_RBBM_DEBUG_BUS_CTL, val | i); + adreno_regread(device, A3XX_RBBM_DEBUG_BUS_DATA_STATUS, + &data[i]); + } + + return size; +} + +static unsigned int debugbus_blocks[] = { + RBBM_BLOCK_ID_CP, + RBBM_BLOCK_ID_RBBM, + RBBM_BLOCK_ID_VBIF, + RBBM_BLOCK_ID_HLSQ, + RBBM_BLOCK_ID_UCHE, + RBBM_BLOCK_ID_PC, + RBBM_BLOCK_ID_VFD, + RBBM_BLOCK_ID_VPC, + RBBM_BLOCK_ID_TSE, + RBBM_BLOCK_ID_RAS, + RBBM_BLOCK_ID_VSC, + RBBM_BLOCK_ID_SP_0, + RBBM_BLOCK_ID_SP_1, + RBBM_BLOCK_ID_SP_2, + RBBM_BLOCK_ID_SP_3, + RBBM_BLOCK_ID_TPL1_0, + RBBM_BLOCK_ID_TPL1_1, + RBBM_BLOCK_ID_TPL1_2, + RBBM_BLOCK_ID_TPL1_3, + RBBM_BLOCK_ID_RB_0, + RBBM_BLOCK_ID_RB_1, + RBBM_BLOCK_ID_RB_2, + RBBM_BLOCK_ID_RB_3, + RBBM_BLOCK_ID_MARB_0, + RBBM_BLOCK_ID_MARB_1, + RBBM_BLOCK_ID_MARB_2, + RBBM_BLOCK_ID_MARB_3, +}; + +static void *a3xx_snapshot_debugbus(struct kgsl_device *device, + void *snapshot, int *remain) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(debugbus_blocks); i++) { + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUGBUS, snapshot, remain, + a3xx_snapshot_debugbus_block, + (void *) debugbus_blocks[i]); + } + + return snapshot; +} + +/* A3XX GPU snapshot function - this is where all of the A3XX specific + * bits and pieces are grabbed into the snapshot memory + */ + +void *a3xx_snapshot(struct adreno_device *adreno_dev, void *snapshot, + int *remain, int hang) +{ + struct kgsl_device *device = &adreno_dev->dev; + struct kgsl_snapshot_registers regs; + + regs.regs = (unsigned int *) a3xx_registers; + regs.count = a3xx_registers_count; + + /* Master set of (non debug) registers */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_REGS, snapshot, remain, + kgsl_snapshot_dump_regs, ®s); + + /* CP_STATE_DEBUG indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_CP_STATE_DEBUG_INDEX, + REG_CP_STATE_DEBUG_DATA, 0x0, 0x14); + + /* CP_ME indexed registers */ + snapshot = kgsl_snapshot_indexed_registers(device, snapshot, + remain, REG_CP_ME_CNTL, REG_CP_ME_STATUS, + 64, 44); + + /* VPC memory */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a3xx_snapshot_vpc_memory, NULL); + + /* CP MEQ */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a3xx_snapshot_cp_meq, NULL); + + /* CP PFP and PM4 */ + /* Reading these will hang the GPU if it isn't already hung */ + + if (hang) { + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a3xx_snapshot_cp_pfp_ram, NULL); + + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a3xx_snapshot_cp_pm4_ram, NULL); + } + + /* CP ROQ */ + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_DEBUG, snapshot, remain, + a3xx_snapshot_cp_roq, NULL); + + snapshot = a3xx_snapshot_debugbus(device, snapshot, remain); + + return snapshot; +} diff --git a/drivers/gpu/msm/adreno_a3xx_trace.c b/drivers/gpu/msm/adreno_a3xx_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..8b4a80dde8e8516de567b6cbe385e24ff3a58fb9 --- /dev/null +++ b/drivers/gpu/msm/adreno_a3xx_trace.c @@ -0,0 +1,20 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "kgsl.h" +#include "adreno.h" + +/* Instantiate tracepoints */ +#define CREATE_TRACE_POINTS +#include "a3xx_reg.h" +#include "adreno_a3xx_trace.h" diff --git a/drivers/gpu/msm/adreno_a3xx_trace.h b/drivers/gpu/msm/adreno_a3xx_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..44483a8e7aae07d6cc849a1d4455132add6d9456 --- /dev/null +++ b/drivers/gpu/msm/adreno_a3xx_trace.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#if !defined(_ADRENO_A3XX_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _ADRENO_A3XX_TRACE_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM kgsl +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE adreno_a3xx_trace + +#include + +struct kgsl_device; + +/* + * Tracepoint for a3xx irq. Includes status info + */ +TRACE_EVENT(kgsl_a3xx_irq_status, + + TP_PROTO(struct kgsl_device *device, unsigned int status), + + TP_ARGS(device, status), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, status) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->status = status; + ), + + TP_printk( + "d_name=%s status=%s", + __get_str(device_name), + __entry->status ? __print_flags(__entry->status, "|", + { 1 << A3XX_INT_RBBM_AHB_ERROR, "RBBM_GPU_IDLE" }, + { 1 << A3XX_INT_RBBM_AHB_ERROR, "RBBM_AHB_ERR" }, + { 1 << A3XX_INT_RBBM_REG_TIMEOUT, "RBBM_REG_TIMEOUT" }, + { 1 << A3XX_INT_RBBM_ME_MS_TIMEOUT, + "RBBM_ME_MS_TIMEOUT" }, + { 1 << A3XX_INT_RBBM_PFP_MS_TIMEOUT, + "RBBM_PFP_MS_TIMEOUT" }, + { 1 << A3XX_INT_RBBM_ATB_BUS_OVERFLOW, + "RBBM_ATB_BUS_OVERFLOW" }, + { 1 << A3XX_INT_VFD_ERROR, "RBBM_VFD_ERROR" }, + { 1 << A3XX_INT_CP_SW_INT, "CP_SW" }, + { 1 << A3XX_INT_CP_T0_PACKET_IN_IB, + "CP_T0_PACKET_IN_IB" }, + { 1 << A3XX_INT_CP_OPCODE_ERROR, "CP_OPCODE_ERROR" }, + { 1 << A3XX_INT_CP_RESERVED_BIT_ERROR, + "CP_RESERVED_BIT_ERROR" }, + { 1 << A3XX_INT_CP_HW_FAULT, "CP_HW_FAULT" }, + { 1 << A3XX_INT_CP_DMA, "CP_DMA" }, + { 1 << A3XX_INT_CP_IB2_INT, "CP_IB2_INT" }, + { 1 << A3XX_INT_CP_IB1_INT, "CP_IB1_INT" }, + { 1 << A3XX_INT_CP_RB_INT, "CP_RB_INT" }, + { 1 << A3XX_INT_CP_REG_PROTECT_FAULT, + "CP_REG_PROTECT_FAULT" }, + { 1 << A3XX_INT_CP_RB_DONE_TS, "CP_RB_DONE_TS" }, + { 1 << A3XX_INT_CP_VS_DONE_TS, "CP_VS_DONE_TS" }, + { 1 << A3XX_INT_CP_PS_DONE_TS, "CP_PS_DONE_TS" }, + { 1 << A3XX_INT_CACHE_FLUSH_TS, "CACHE_FLUSH_TS" }, + { 1 << A3XX_INT_CP_AHB_ERROR_HALT, + "CP_AHB_ERROR_HALT" }, + { 1 << A3XX_INT_MISC_HANG_DETECT, "MISC_HANG_DETECT" }, + { 1 << A3XX_INT_UCHE_OOB_ACCESS, "UCHE_OOB_ACCESS" }) + : "None" + ) +); + +#endif /* _ADRENO_A3XX_TRACE_H */ + +/* This part must be outside protection */ +#include diff --git a/drivers/gpu/msm/adreno_debugfs.c b/drivers/gpu/msm/adreno_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..822cf144c5e58a52e7663ba91df109a1cb335aeb --- /dev/null +++ b/drivers/gpu/msm/adreno_debugfs.c @@ -0,0 +1,133 @@ +/* Copyright (c) 2002,2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "kgsl.h" +#include "adreno_postmortem.h" +#include "adreno.h" + +#include "a2xx_reg.h" + +unsigned int kgsl_cff_dump_enable; +int adreno_pm_regs_enabled; +int adreno_pm_ib_enabled; + +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) +{ + adreno_pm_regs_enabled = val ? 1 : 0; + return 0; +} + +static int pm_regs_enabled_get(void *data, u64 *val) +{ + *val = adreno_pm_regs_enabled; + return 0; +} + +static int pm_ib_enabled_set(void *data, u64 val) +{ + adreno_pm_ib_enabled = val ? 1 : 0; + return 0; +} + +static int pm_ib_enabled_get(void *data, u64 *val) +{ + *val = adreno_pm_ib_enabled; + return 0; +} + + +DEFINE_SIMPLE_ATTRIBUTE(pm_regs_enabled_fops, + pm_regs_enabled_get, + pm_regs_enabled_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(pm_ib_enabled_fops, + pm_ib_enabled_get, + pm_ib_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"); + +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); + +void adreno_debugfs_init(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + if (!device->d_debugfs || IS_ERR(device->d_debugfs)) + return; + + debugfs_create_file("cff_dump", 0644, device->d_debugfs, device, + &kgsl_cff_dump_enable_fops); + debugfs_create_u32("wait_timeout", 0644, device->d_debugfs, + &adreno_dev->wait_timeout); + debugfs_create_u32("ib_check", 0644, device->d_debugfs, + &adreno_dev->ib_check_level); + + /* 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); + debugfs_create_file("ib_enabled", 0644, pm_d_debugfs, device, + &pm_ib_enabled_fops); +} diff --git a/drivers/gpu/msm/adreno_debugfs.h b/drivers/gpu/msm/adreno_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..5f8d89aadaf0fe43b3286a465f8a92a825ec4cbc --- /dev/null +++ b/drivers/gpu/msm/adreno_debugfs.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2002,2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 adreno_pm_regs_enabled; +extern int adreno_pm_ib_enabled; + +static inline int is_adreno_pm_regs_enabled(void) +{ + return adreno_pm_regs_enabled; +} + +static inline int is_adreno_pm_ib_enabled(void) +{ + return adreno_pm_ib_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 0000000000000000000000000000000000000000..0d15fb92dc2eedf716f101a61f83f9a2a8325800 --- /dev/null +++ b/drivers/gpu/msm/adreno_drawctxt.c @@ -0,0 +1,285 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 "adreno.h" + +#define KGSL_INIT_REFTIMESTAMP 0x7FFFFFFF + +/* quad for copying GMEM to context shadow */ +#define QUAD_LEN 12 +#define QUAD_RESTORE_LEN 14 + +static unsigned int gmem_copy_quad[QUAD_LEN] = { + 0x00000000, 0x00000000, 0x3f800000, + 0x00000000, 0x00000000, 0x3f800000, + 0x00000000, 0x00000000, 0x3f800000, + 0x00000000, 0x00000000, 0x3f800000 +}; + +static unsigned int gmem_restore_quad[QUAD_RESTORE_LEN] = { + 0x00000000, 0x3f800000, 0x3f800000, + 0x00000000, 0x00000000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, + 0x3f800000, 0x3f800000, +}; + +#define TEXCOORD_LEN 8 + +static unsigned int gmem_copy_texcoord[TEXCOORD_LEN] = { + 0x00000000, 0x3f800000, + 0x3f800000, 0x3f800000, + 0x00000000, 0x00000000, + 0x3f800000, 0x00000000 +}; + +/* + * Helper functions + * These are global helper functions used by the GPUs during context switch + */ + +/** + * uint2float - convert a uint to IEEE754 single precision float + * @ uintval - value to convert + */ + +unsigned int uint2float(unsigned int uintval) +{ + unsigned int exp, frac = 0; + + if (uintval == 0) + return 0; + + exp = ilog2(uintval); + + /* Calculate fraction */ + if (23 > exp) + frac = (uintval & (~(1 << exp))) << (23 - exp); + + /* Exp is biased by 127 and shifted 23 bits */ + exp = (exp + 127) << 23; + + return exp | frac; +} + +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_restore_quad[5] = uint2float(shadow->height); + gmem_restore_quad[7] = uint2float(shadow->width); + + memcpy(shadow->quad_vertices.hostptr, gmem_copy_quad, QUAD_LEN << 2); + memcpy(shadow->quad_vertices_restore.hostptr, gmem_restore_quad, + QUAD_RESTORE_LEN << 2); + + memcpy(shadow->quad_texcoords.hostptr, gmem_copy_texcoord, + TEXCOORD_LEN << 2); +} + +/** + * build_quad_vtxbuff - Create a quad for saving/restoring GMEM + * @ context - Pointer to the context being created + * @ shadow - Pointer to the GMEM shadow structure + * @ incmd - Pointer to pointer to the temporary command buffer + */ + +/* quad for saving/restoring gmem */ +void build_quad_vtxbuff(struct adreno_context *drawctxt, + struct gmem_shadow_t *shadow, unsigned int **incmd) +{ + unsigned int *cmd = *incmd; + + /* quad vertex buffer location (in GPU space) */ + shadow->quad_vertices.hostptr = cmd; + shadow->quad_vertices.gpuaddr = virt2gpu(cmd, &drawctxt->gpustate); + + cmd += QUAD_LEN; + + /* Used by A3XX, but define for both to make the code easier */ + shadow->quad_vertices_restore.hostptr = cmd; + shadow->quad_vertices_restore.gpuaddr = + virt2gpu(cmd, &drawctxt->gpustate); + + cmd += QUAD_RESTORE_LEN; + + /* tex coord buffer location (in GPU space) */ + shadow->quad_texcoords.hostptr = cmd; + shadow->quad_texcoords.gpuaddr = virt2gpu(cmd, &drawctxt->gpustate); + + cmd += TEXCOORD_LEN; + + set_gmem_copy_quad(shadow); + *incmd = cmd; +} + +/** + * adreno_drawctxt_create - create a new adreno draw context + * @device - KGSL device to create the context on + * @pagetable - Pagetable for the context + * @context- Generic KGSL context structure + * @flags - flags for the context (passed from user space) + * + * Create a new draw context for the 3D core. Return 0 on success, + * or error code on failure. + */ +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); + int ret; + + drawctxt = kzalloc(sizeof(struct adreno_context), GFP_KERNEL); + + if (drawctxt == NULL) + return -ENOMEM; + + drawctxt->pagetable = pagetable; + drawctxt->bin_base_offset = 0; + drawctxt->id = context->id; + + if (flags & KGSL_CONTEXT_PREAMBLE) + drawctxt->flags |= CTXT_FLAGS_PREAMBLE; + + if (flags & KGSL_CONTEXT_NO_GMEM_ALLOC) + drawctxt->flags |= CTXT_FLAGS_NOGMEMALLOC; + + if (flags & KGSL_CONTEXT_PER_CONTEXT_TS) + drawctxt->flags |= CTXT_FLAGS_PER_CONTEXT_TS; + + ret = adreno_dev->gpudev->ctxt_create(adreno_dev, drawctxt); + if (ret) + goto err; + + kgsl_sharedmem_writel(&device->memstore, + KGSL_MEMSTORE_OFFSET(drawctxt->id, ref_wait_ts), + KGSL_INIT_REFTIMESTAMP); + + context->devctxt = drawctxt; + return 0; +err: + kfree(drawctxt); + return ret; +} + +/** + * adreno_drawctxt_destroy - destroy a draw context + * @device - KGSL device that owns the context + * @context- Generic KGSL context container for the context + * + * Destroy an existing context. Return 0 on success or error + * code on failure. + */ + +/* 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; + + if (context == NULL) + return; + + drawctxt = context->devctxt; + /* 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; +} + +/** + * adreno_drawctxt_set_bin_base_offset - set bin base offset for the context + * @device - KGSL device that owns the context + * @context- Generic KGSL context container for the context + * @offset - Offset to set + * + * Set the bin base offset for A2XX devices. Not valid for A3XX devices. + */ + +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; +} + +/** + * adreno_drawctxt_switch - switch the current draw context + * @adreno_dev - The 3D device that owns the context + * @drawctxt - the 3D context to switch to + * @flags - Flags to accompany the switch (from user space) + * + * Switch the current draw context + */ + +void adreno_drawctxt_switch(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + unsigned int flags) +{ + struct kgsl_device *device = &adreno_dev->dev; + + 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 (adreno_dev->drawctxt_active == drawctxt) + return; + + KGSL_CTXT_INFO(device, "from %p to %p flags %d\n", + adreno_dev->drawctxt_active, drawctxt, flags); + + /* Save the old context */ + adreno_dev->gpudev->ctxt_save(adreno_dev, adreno_dev->drawctxt_active); + + /* Set the new context */ + adreno_dev->gpudev->ctxt_restore(adreno_dev, drawctxt); + adreno_dev->drawctxt_active = drawctxt; +} diff --git a/drivers/gpu/msm/adreno_drawctxt.h b/drivers/gpu/msm/adreno_drawctxt.h new file mode 100644 index 0000000000000000000000000000000000000000..3eb1aba0cdff1a89c2d07342c61febfd850f0e94 --- /dev/null +++ b/drivers/gpu/msm/adreno_drawctxt.h @@ -0,0 +1,179 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 "adreno_pm4types.h" +#include "a2xx_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 +/* preamble packed in cmdbuffer for context switching */ +#define CTXT_FLAGS_PREAMBLE 0x00000800 +/* 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 +/* Specifies there is no need to save GMEM */ +#define CTXT_FLAGS_NOGMEMALLOC 0x00010000 +/* Trash state for context */ +#define CTXT_FLAGS_TRASHSTATE 0x00020000 +/* per context timestamps enabled */ +#define CTXT_FLAGS_PER_CONTEXT_TS 0x00040000 + +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 multiples of 32 in case tiled + * textures are used + */ + + enum COLORFORMATX format; /* Unused on A3XX */ + 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; /* Unused on A3XX */ + unsigned int *gmem_restore_commands; /* Unused on A3XX */ + unsigned int gmem_save[3]; + unsigned int gmem_restore[3]; + struct kgsl_memdesc quad_vertices; + struct kgsl_memdesc quad_texcoords; + struct kgsl_memdesc quad_vertices_restore; +}; + +struct adreno_context { + unsigned int id; + uint32_t flags; + struct kgsl_pagetable *pagetable; + struct kgsl_memdesc gpustate; + unsigned int reg_restore[3]; + unsigned int shader_save[3]; + unsigned int shader_restore[3]; + + /* Information of the GMEM shadow that is created in context create */ + struct gmem_shadow_t context_gmem_shadow; + + /* A2XX specific items */ + unsigned int reg_save[3]; + unsigned int shader_fixup[3]; + unsigned int chicken_restore[3]; + unsigned int bin_base_offset; + + /* A3XX specific items */ + unsigned int regconstant_save[3]; + unsigned int constant_restore[3]; + unsigned int hlsqcontrol_restore[3]; + unsigned int save_fixup[3]; + unsigned int restore_fixup[3]; + struct kgsl_memdesc shader_load_commands[2]; + struct kgsl_memdesc shader_save_commands[4]; + struct kgsl_memdesc constant_save_commands[3]; + struct kgsl_memdesc constant_load_commands[3]; + struct kgsl_memdesc cond_execs[4]; + struct kgsl_memdesc hlsqcontrol_restore_commands[1]; +}; + +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); + +/* GPU context switch helper functions */ + +void build_quad_vtxbuff(struct adreno_context *drawctxt, + struct gmem_shadow_t *shadow, unsigned int **incmd); + +unsigned int uint2float(unsigned int); + +static inline unsigned int virt2gpu(unsigned int *cmd, + struct kgsl_memdesc *memdesc) +{ + return memdesc->gpuaddr + ((char *) cmd - (char *) memdesc->hostptr); +} + +static inline void create_ib1(struct adreno_context *drawctxt, + unsigned int *cmd, + unsigned int *start, + unsigned int *end) +{ + cmd[0] = CP_HDR_INDIRECT_BUFFER_PFD; + cmd[1] = virt2gpu(start, &drawctxt->gpustate); + cmd[2] = end - start; +} + + +static inline unsigned int *reg_range(unsigned int *cmd, unsigned int start, + unsigned int end) +{ + *cmd++ = CP_REG(start); /* h/w regs, start addr */ + *cmd++ = end - start + 1; /* count */ + return cmd; +} + +static inline void calc_gmemsize(struct gmem_shadow_t *shadow, int gmem_size) +{ + int w = 64, h = 64; + + shadow->format = COLORX_8_8_8_8; + + /* convert from bytes to 32-bit words */ + gmem_size = (gmem_size + 3) / 4; + + while ((w * h) < gmem_size) { + if (w < h) + w *= 2; + else + h *= 2; + } + + shadow->pitch = shadow->width = w; + shadow->height = h; + shadow->gmem_pitch = shadow->pitch; + shadow->size = shadow->pitch * shadow->height * 4; +} + +#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 0000000000000000000000000000000000000000..fb44b25118efb8abf8ce79fda2b9707e6d82f7d9 --- /dev/null +++ b/drivers/gpu/msm/adreno_pm4types.h @@ -0,0 +1,232 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 CP_PKT_MASK 0xc0000000 + +#define CP_TYPE0_PKT ((unsigned int)0 << 30) +#define CP_TYPE1_PKT ((unsigned int)1 << 30) +#define CP_TYPE2_PKT ((unsigned int)2 << 30) +#define CP_TYPE3_PKT ((unsigned int)3 << 30) + + +/* type3 packets */ +/* initialize CP's micro-engine */ +#define CP_ME_INIT 0x48 + +/* skip N 32-bit words to get to the next packet */ +#define CP_NOP 0x10 + +/* indirect buffer dispatch. same as IB, but init is pipelined */ +#define CP_INDIRECT_BUFFER_PFD 0x37 + +/* wait for the IDLE state of the engine */ +#define CP_WAIT_FOR_IDLE 0x26 + +/* wait until a register or memory location is a specific value */ +#define CP_WAIT_REG_MEM 0x3c + +/* wait until a register location is equal to a specific value */ +#define CP_WAIT_REG_EQ 0x52 + +/* wait until a register location is >= a specific value */ +#define CP_WAT_REG_GTE 0x53 + +/* wait until a read completes */ +#define CP_WAIT_UNTIL_READ 0x5c + +/* wait until all base/size writes from an IB_PFD packet have completed */ +#define CP_WAIT_IB_PFD_COMPLETE 0x5d + +/* register read/modify/write */ +#define CP_REG_RMW 0x21 + +/* Set binning configuration registers */ +#define CP_SET_BIN_DATA 0x2f + +/* reads register in chip and writes to memory */ +#define CP_REG_TO_MEM 0x3e + +/* write N 32-bit words to memory */ +#define CP_MEM_WRITE 0x3d + +/* write CP_PROG_COUNTER value to memory */ +#define CP_MEM_WRITE_CNTR 0x4f + +/* conditional execution of a sequence of packets */ +#define CP_COND_EXEC 0x44 + +/* conditional write to memory or register */ +#define CP_COND_WRITE 0x45 + +/* generate an event that creates a write to memory when completed */ +#define CP_EVENT_WRITE 0x46 + +/* generate a VS|PS_done event */ +#define CP_EVENT_WRITE_SHD 0x58 + +/* generate a cache flush done event */ +#define CP_EVENT_WRITE_CFL 0x59 + +/* generate a z_pass done event */ +#define CP_EVENT_WRITE_ZPD 0x5b + + +/* initiate fetch of index buffer and draw */ +#define CP_DRAW_INDX 0x22 + +/* draw using supplied indices in packet */ +#define CP_DRAW_INDX_2 0x36 + +/* initiate fetch of index buffer and binIDs and draw */ +#define CP_DRAW_INDX_BIN 0x34 + +/* initiate fetch of bin IDs and draw using supplied indices */ +#define CP_DRAW_INDX_2_BIN 0x35 + + +/* begin/end initiator for viz query extent processing */ +#define CP_VIZ_QUERY 0x23 + +/* fetch state sub-blocks and initiate shader code DMAs */ +#define CP_SET_STATE 0x25 + +/* load constant into chip and to memory */ +#define CP_SET_CONSTANT 0x2d + +/* load sequencer instruction memory (pointer-based) */ +#define CP_IM_LOAD 0x27 + +/* load sequencer instruction memory (code embedded in packet) */ +#define CP_IM_LOAD_IMMEDIATE 0x2b + +/* load constants from a location in memory */ +#define CP_LOAD_CONSTANT_CONTEXT 0x2e + +/* (A2x) sets binning configuration registers */ +#define CP_SET_BIN_DATA 0x2f + +/* selective invalidation of state pointers */ +#define CP_INVALIDATE_STATE 0x3b + + +/* dynamically changes shader instruction memory partition */ +#define CP_SET_SHADER_BASES 0x4A + +/* sets the 64-bit BIN_MASK register in the PFP */ +#define CP_SET_BIN_MASK 0x50 + +/* sets the 64-bit BIN_SELECT register in the PFP */ +#define CP_SET_BIN_SELECT 0x51 + + +/* updates the current context, if needed */ +#define CP_CONTEXT_UPDATE 0x5e + +/* generate interrupt from the command stream */ +#define CP_INTERRUPT 0x40 + + +/* copy sequencer instruction memory to system memory */ +#define CP_IM_STORE 0x2c + +/* + * for a20x + * program an offset that will added to the BIN_BASE value of + * the 3D_DRAW_INDX_BIN packet + */ +#define CP_SET_BIN_BASE_OFFSET 0x4B + +/* + * for a22x + * sets draw initiator flags register in PFP, gets bitwise-ORed into + * every draw initiator + */ +#define CP_SET_DRAW_INIT_FLAGS 0x4B + +#define CP_SET_PROTECTED_MODE 0x5f /* sets the register protection mode */ + +/* + * for a3xx + */ + +#define CP_LOAD_STATE 0x30 /* load high level sequencer command */ + +/* Conditionally load a IB based on a flag */ +#define CP_COND_INDIRECT_BUFFER_PFE 0x3A /* prefetch enabled */ +#define CP_COND_INDIRECT_BUFFER_PFD 0x32 /* prefetch disabled */ + +/* Load a buffer with pre-fetch enabled */ +#define CP_INDIRECT_BUFFER_PFE 0x3F + +#define CP_LOADSTATE_DSTOFFSET_SHIFT 0x00000000 +#define CP_LOADSTATE_STATESRC_SHIFT 0x00000010 +#define CP_LOADSTATE_STATEBLOCKID_SHIFT 0x00000013 +#define CP_LOADSTATE_NUMOFUNITS_SHIFT 0x00000016 +#define CP_LOADSTATE_STATETYPE_SHIFT 0x00000000 +#define CP_LOADSTATE_EXTSRCADDR_SHIFT 0x00000002 + +/* packet header building macros */ +#define cp_type0_packet(regindx, cnt) \ + (CP_TYPE0_PKT | (((cnt)-1) << 16) | ((regindx) & 0x7FFF)) + +#define cp_type0_packet_for_sameregister(regindx, cnt) \ + ((CP_TYPE0_PKT | (((cnt)-1) << 16) | ((1 << 15) | \ + ((regindx) & 0x7FFF))) + +#define cp_type1_packet(reg0, reg1) \ + (CP_TYPE1_PKT | ((reg1) << 12) | (reg0)) + +#define cp_type3_packet(opcode, cnt) \ + (CP_TYPE3_PKT | (((cnt)-1) << 16) | (((opcode) & 0xFF) << 8)) + +#define cp_predicated_type3_packet(opcode, cnt) \ + (CP_TYPE3_PKT | (((cnt)-1) << 16) | (((opcode) & 0xFF) << 8) | 0x1) + +#define cp_nop_packet(cnt) \ + (CP_TYPE3_PKT | (((cnt)-1) << 16) | (CP_NOP << 8)) + +#define pkt_is_type0(pkt) (((pkt) & 0XC0000000) == CP_TYPE0_PKT) + +#define type0_pkt_size(pkt) ((((pkt) >> 16) & 0x3FFF) + 1) +#define type0_pkt_offset(pkt) ((pkt) & 0x7FFF) + +#define pkt_is_type3(pkt) (((pkt) & 0xC0000000) == CP_TYPE3_PKT) + +#define cp_type3_opcode(pkt) (((pkt) >> 8) & 0xFF) +#define type3_pkt_size(pkt) ((((pkt) >> 16) & 0x3FFF) + 1) + +/* packet headers */ +#define CP_HDR_ME_INIT cp_type3_packet(CP_ME_INIT, 18) +#define CP_HDR_INDIRECT_BUFFER_PFD cp_type3_packet(CP_INDIRECT_BUFFER_PFD, 2) +#define CP_HDR_INDIRECT_BUFFER_PFE cp_type3_packet(CP_INDIRECT_BUFFER_PFE, 2) + +/* dword base address of the GFX decode space */ +#define SUBBLOCK_OFFSET(reg) ((unsigned int)((reg) - (0x2000))) + +/* gmem command buffer length */ +#define CP_REG(reg) ((0x4 << 16) | (SUBBLOCK_OFFSET(reg))) + + +/* Return 1 if the command is an indirect buffer of any kind */ +static inline int adreno_cmd_is_ib(unsigned int cmd) +{ + return (cmd == cp_type3_packet(CP_INDIRECT_BUFFER_PFE, 2) || + cmd == cp_type3_packet(CP_INDIRECT_BUFFER_PFD, 2) || + cmd == cp_type3_packet(CP_COND_INDIRECT_BUFFER_PFE, 2) || + cmd == cp_type3_packet(CP_COND_INDIRECT_BUFFER_PFD, 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 0000000000000000000000000000000000000000..7bb65ca8640caca903e23cb6e0c392111ffc0a4c --- /dev/null +++ b/drivers/gpu/msm/adreno_postmortem.c @@ -0,0 +1,956 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 "adreno.h" +#include "adreno_pm4types.h" +#include "adreno_ringbuffer.h" +#include "adreno_postmortem.h" +#include "adreno_debugfs.h" +#include "kgsl_cffdump.h" +#include "kgsl_pwrctrl.h" + +#include "a2xx_reg.h" +#include "a3xx_reg.h" + +#define INVALID_RB_CMD 0xaaaaaaaa +#define NUM_DWORDS_OF_RINGBUFFER_HISTORY 100 + +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[] = { + {CP_COND_EXEC, "CND_EXEC"}, + {CP_CONTEXT_UPDATE, "CX__UPDT"}, + {CP_DRAW_INDX, "DRW_NDX_"}, + {CP_DRAW_INDX_BIN, "DRW_NDXB"}, + {CP_EVENT_WRITE, "EVENT_WT"}, + {CP_IM_LOAD, "IN__LOAD"}, + {CP_IM_LOAD_IMMEDIATE, "IM_LOADI"}, + {CP_IM_STORE, "IM_STORE"}, + {CP_INDIRECT_BUFFER_PFE, "IND_BUF_"}, + {CP_INDIRECT_BUFFER_PFD, "IND_BUFP"}, + {CP_INTERRUPT, "PM4_INTR"}, + {CP_INVALIDATE_STATE, "INV_STAT"}, + {CP_LOAD_CONSTANT_CONTEXT, "LD_CN_CX"}, + {CP_ME_INIT, "ME__INIT"}, + {CP_NOP, "PM4__NOP"}, + {CP_REG_RMW, "REG__RMW"}, + {CP_REG_TO_MEM, "REG2_MEM"}, + {CP_SET_BIN_BASE_OFFSET, "ST_BIN_O"}, + {CP_SET_CONSTANT, "ST_CONST"}, + {CP_SET_PROTECTED_MODE, "ST_PRT_M"}, + {CP_SET_SHADER_BASES, "ST_SHD_B"}, + {CP_WAIT_FOR_IDLE, "WAIT4IDL"}, +}; + +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)) == CP_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)) == CP_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)) == CP_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)) == CP_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]; + int end = registers[range * 2 + 1]; + + 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, 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) +{ + uint8_t *base_addr = adreno_convertaddr(device, pt_base, + ib_base, ib_size*sizeof(uint32_t)); + + 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; + + dump_ib(device, "IB1:", pt_base, base_offset, ib1_base, + ib1_size, dump); + + /* fetch virtual address for given IB base */ + ib1_addr = (uint32_t *)adreno_convertaddr(device, pt_base, + ib1_base, ib1_size*sizeof(uint32_t)); + if (!ib1_addr) + return; + + for (i = 0; i+3 < ib1_size; ) { + value = ib1_addr[i++]; + if (adreno_cmd_is_ib(value)) { + 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); + } +} + +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 (!l->show) + continue; + + 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 void adreno_dump_a3xx(struct kgsl_device *device) +{ + unsigned int r1, r2, r3, rbbm_status; + unsigned int cp_stat, rb_count; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + kgsl_regread(device, adreno_dev->gpudev->reg_rbbm_status, &rbbm_status); + KGSL_LOG_DUMP(device, "RBBM: STATUS = %08X\n", rbbm_status); + + { + struct log_field lines[] = { + {rbbm_status & BIT(0), "HI busy "}, + {rbbm_status & BIT(1), "CP ME busy "}, + {rbbm_status & BIT(2), "CP PFP busy "}, + {rbbm_status & BIT(14), "CP NRT busy "}, + {rbbm_status & BIT(15), "VBIF busy "}, + {rbbm_status & BIT(16), "TSE busy "}, + {rbbm_status & BIT(17), "RAS busy "}, + {rbbm_status & BIT(18), "RB busy "}, + {rbbm_status & BIT(19), "PC DCALL bsy"}, + {rbbm_status & BIT(20), "PC VSD busy "}, + {rbbm_status & BIT(21), "VFD busy "}, + {rbbm_status & BIT(22), "VPC busy "}, + {rbbm_status & BIT(23), "UCHE busy "}, + {rbbm_status & BIT(24), "SP busy "}, + {rbbm_status & BIT(25), "TPL1 busy "}, + {rbbm_status & BIT(26), "MARB busy "}, + {rbbm_status & BIT(27), "VSC busy "}, + {rbbm_status & BIT(28), "ARB busy "}, + {rbbm_status & BIT(29), "HLSQ busy "}, + {rbbm_status & BIT(30), "GPU bsy noHC"}, + {rbbm_status & BIT(31), "GPU busy "}, + }; + adreno_dump_fields(device, " STATUS=", lines, + ARRAY_SIZE(lines)); + } + + kgsl_regread(device, REG_CP_RB_BASE, &r1); + 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" + "| rb_count = %08X\n", r1, r2, r3, rb_count); + + kgsl_regread(device, REG_CP_RB_RPTR, &r1); + kgsl_regread(device, REG_CP_RB_WPTR, &r2); + kgsl_regread(device, REG_CP_RB_RPTR_WR, &r3); + KGSL_LOG_DUMP(device, + " RPTR = %08X | WPTR = %08X | RPTR_WR = %08X" + "\n", r1, r2, r3); + + kgsl_regread(device, REG_CP_IB1_BASE, &r1); + kgsl_regread(device, REG_CP_IB1_BUFSZ, &r2); + KGSL_LOG_DUMP(device, "CP_IB1: BASE = %08X | BUFSZ = %d\n", r1, r2); + + kgsl_regread(device, REG_CP_ME_CNTL, &r1); + kgsl_regread(device, REG_CP_ME_STATUS, &r2); + KGSL_LOG_DUMP(device, "CP_ME: CNTL = %08X | STATUS = %08X\n", r1, r2); + + 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(8), "EVENT_BUSY 8"}, + {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(27), "RB_FFO_BSY27"}, + {cp_stat & BIT(28), "CF_FFO_BSY28"}, + {cp_stat & BIT(29), "PS_FFO_BSY29"}, + {cp_stat & BIT(30), "VS_FFO_BSY30"}, + {cp_stat & BIT(31), "CP_BUSY 31"}, + }; + adreno_dump_fields(device, " CP_STT=", lns, ARRAY_SIZE(lns)); + } +#endif + + kgsl_regread(device, A3XX_RBBM_INT_0_STATUS, &r1); + KGSL_LOG_DUMP(device, "MSTR_INT_SGNL = %08X\n", r1); + { + struct log_field ints[] = { + {r1 & BIT(0), "RBBM_GPU_IDLE 0"}, + {r1 & BIT(1), "RBBM_AHB_ERROR 1"}, + {r1 & BIT(2), "RBBM_REG_TIMEOUT 2"}, + {r1 & BIT(3), "RBBM_ME_MS_TIMEOUT 3"}, + {r1 & BIT(4), "RBBM_PFP_MS_TIMEOUT 4"}, + {r1 & BIT(5), "RBBM_ATB_BUS_OVERFLOW 5"}, + {r1 & BIT(6), "VFD_ERROR 6"}, + {r1 & BIT(7), "CP_SW_INT 7"}, + {r1 & BIT(8), "CP_T0_PACKET_IN_IB 8"}, + {r1 & BIT(9), "CP_OPCODE_ERROR 9"}, + {r1 & BIT(10), "CP_RESERVED_BIT_ERROR 10"}, + {r1 & BIT(11), "CP_HW_FAULT 11"}, + {r1 & BIT(12), "CP_DMA 12"}, + {r1 & BIT(13), "CP_IB2_INT 13"}, + {r1 & BIT(14), "CP_IB1_INT 14"}, + {r1 & BIT(15), "CP_RB_INT 15"}, + {r1 & BIT(16), "CP_REG_PROTECT_FAULT 16"}, + {r1 & BIT(17), "CP_RB_DONE_TS 17"}, + {r1 & BIT(18), "CP_VS_DONE_TS 18"}, + {r1 & BIT(19), "CP_PS_DONE_TS 19"}, + {r1 & BIT(20), "CACHE_FLUSH_TS 20"}, + {r1 & BIT(21), "CP_AHB_ERROR_HALT 21"}, + {r1 & BIT(24), "MISC_HANG_DETECT 24"}, + {r1 & BIT(25), "UCHE_OOB_ACCESS 25"}, + }; + adreno_dump_fields(device, "INT_SGNL=", ints, ARRAY_SIZE(ints)); + } +} + +static void adreno_dump_a2xx(struct kgsl_device *device) +{ + unsigned int r1, r2, r3, rbbm_status; + unsigned int cp_stat, rb_count; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + kgsl_regread(device, adreno_dev->gpudev->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 & 0x001F, 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, &r1); + 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" + "| rb_count = %08X\n", r1, r2, r3, rb_count); + { + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + if (rb->sizedwords != rb_count) + rb_count = rb->sizedwords; + } + + kgsl_regread(device, REG_CP_RB_RPTR, &r1); + kgsl_regread(device, REG_CP_RB_WPTR, &r2); + kgsl_regread(device, REG_CP_RB_RPTR_WR, &r3); + KGSL_LOG_DUMP(device, + " RPTR = %08X | WPTR = %08X | RPTR_WR = %08X" + "\n", r1, r2, r3); + + kgsl_regread(device, REG_CP_IB1_BASE, &r1); + kgsl_regread(device, REG_CP_IB1_BUFSZ, &r2); + KGSL_LOG_DUMP(device, "CP_IB1: BASE = %08X | BUFSZ = %d\n", r1, r2); + + kgsl_regread(device, REG_CP_IB2_BASE, &r1); + kgsl_regread(device, REG_CP_IB2_BUFSZ, &r2); + KGSL_LOG_DUMP(device, "CP_IB2: BASE = %08X | BUFSZ = %d\n", r1, r2); + + 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); + r3 = kgsl_mmu_get_current_ptbase(&device->mmu); + KGSL_LOG_DUMP(device, + " MPU_END = %08X | VA_RANGE = %08X | PT_BASE =" + " %08X\n", r1, r2, r3); + + KGSL_LOG_DUMP(device, "PAGETABLE SIZE: %08X ", + kgsl_mmu_get_ptsize()); + + 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); +} + +static int adreno_dump(struct kgsl_device *device) +{ + unsigned int cp_ib1_base, cp_ib1_bufsz; + unsigned int cp_ib2_base, cp_ib2_bufsz; + unsigned int pt_base, cur_pt_base; + unsigned int cp_rb_base, cp_rb_ctrl, 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 = 0xdeaddead; + struct kgsl_context *context; + unsigned int context_id; + + static struct ib_list ib_list; + + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + mb(); + + if (adreno_is_a2xx(adreno_dev)) + adreno_dump_a2xx(device); + else if (adreno_is_a3xx(adreno_dev)) + adreno_dump_a3xx(device); + + pt_base = kgsl_mmu_get_current_ptbase(&device->mmu); + cur_pt_base = pt_base; + + kgsl_regread(device, REG_CP_RB_BASE, &cp_rb_base); + kgsl_regread(device, REG_CP_RB_CNTL, &cp_rb_ctrl); + rb_count = 2 << (cp_rb_ctrl & (BIT(6) - 1)); + 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_IB1_BASE, &cp_ib1_base); + kgsl_regread(device, REG_CP_IB1_BUFSZ, &cp_ib1_bufsz); + kgsl_regread(device, REG_CP_IB2_BASE, &cp_ib2_base); + kgsl_regread(device, REG_CP_IB2_BUFSZ, &cp_ib2_bufsz); + + kgsl_sharedmem_readl(&device->memstore, + (unsigned int *) &context_id, + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + current_context)); + context = idr_find(&device->context_idr, context_id); + if (context) { + ts_processed = kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED); + KGSL_LOG_DUMP(device, "CTXT: %d TIMESTM RTRD: %08X\n", + context->id, ts_processed); + } else + KGSL_LOG_DUMP(device, "BAD CTXT: %d\n", context_id); + + 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); + + if (adreno_dev->ringbuffer.buffer_desc.gpuaddr != cp_rb_base) + KGSL_LOG_POSTMORTEM_WRITE(device, + "rb address mismatch, should be 0x%08x\n", + adreno_dev->ringbuffer.buffer_desc.gpuaddr); + + rb_vaddr = adreno_dev->ringbuffer.buffer_desc.hostptr; + if (!rb_vaddr) { + KGSL_LOG_POSTMORTEM_WRITE(device, + "rb has no kernel mapping!\n"); + goto error_vfree; + } + + read_idx = (int)cp_rb_rptr - NUM_DWORDS_OF_RINGBUFFER_HISTORY; + 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 += NUM_DWORDS_OF_RINGBUFFER_HISTORY+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 (adreno_cmd_is_ib(this_cmd)) { + uint32_t ib_addr = rb_copy[read_idx++]; + uint32_t ib_size = rb_copy[read_idx++]; + dump_ib1(device, cur_pt_base, (read_idx-3)<<2, ib_addr, + ib_size, &ib_list, 0); + for (; i < ib_list.count; ++i) + dump_ib(device, "IB2:", cur_pt_base, + ib_list.offsets[i], + ib_list.bases[i], + ib_list.sizes[i], 0); + } else if (this_cmd == cp_type0_packet(MH_MMU_PT_BASE, 1)) { + + KGSL_LOG_DUMP(device, "Current pagetable: %x\t" + "pagetable base: %x\n", + kgsl_mmu_get_ptname_from_ptbase(cur_pt_base), + cur_pt_base); + + /* Set cur_pt_base to the new pagetable base */ + cur_pt_base = rb_copy[read_idx++]; + + KGSL_LOG_DUMP(device, "New pagetable: %x\t" + "pagetable base: %x\n", + kgsl_mmu_get_ptname_from_ptbase(cur_pt_base), + cur_pt_base); + } + } + + /* Restore cur_pt_base back to the pt_base of + the process in whose context the GPU hung */ + cur_pt_base = pt_base; + + read_idx = (int)cp_rb_rptr - NUM_DWORDS_OF_RINGBUFFER_HISTORY; + 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 (is_adreno_pm_ib_enabled()) { + for (read_idx = NUM_DWORDS_OF_RINGBUFFER_HISTORY; + read_idx >= 0; --read_idx) { + uint32_t this_cmd = rb_copy[read_idx]; + if (adreno_cmd_is_ib(this_cmd)) { + uint32_t ib_addr = rb_copy[read_idx+1]; + uint32_t ib_size = rb_copy[read_idx+2]; + if (ib_size && cp_ib1_base == ib_addr) { + KGSL_LOG_DUMP(device, + "IB1: base:%8.8X " + "count:%d\n", ib_addr, ib_size); + dump_ib(device, "IB1: ", cur_pt_base, + read_idx<<2, ib_addr, ib_size, + 1); + } + } + } + for (i = 0; i < ib_list.count; ++i) { + uint32_t ib_size = ib_list.sizes[i]; + uint32_t ib_offset = ib_list.offsets[i]; + if (ib_size && cp_ib2_base == ib_list.bases[i]) { + KGSL_LOG_DUMP(device, + "IB2: base:%8.8X count:%d\n", + cp_ib2_base, ib_size); + dump_ib(device, "IB2: ", cur_pt_base, ib_offset, + ib_list.bases[i], ib_size, 1); + } + } + } + + /* Dump the registers if the user asked for it */ + if (is_adreno_pm_regs_enabled()) { + if (adreno_is_a20x(adreno_dev)) + adreno_dump_regs(device, a200_registers, + a200_registers_count); + else if (adreno_is_a22x(adreno_dev)) + adreno_dump_regs(device, a220_registers, + a220_registers_count); + else if (adreno_is_a225(adreno_dev)) + adreno_dump_regs(device, a225_registers, + a225_registers_count); + else if (adreno_is_a3xx(adreno_dev)) + adreno_dump_regs(device, a3xx_registers, + a3xx_registers_count); + } + +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; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + BUG_ON(device == NULL); + + kgsl_cffdump_hang(device->id); + + /* 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); + + } + 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)); + + /* Disable the idle timer so we don't get interrupted */ + del_timer_sync(&device->idle_timer); + mutex_unlock(&device->mutex); + flush_workqueue(device->work_queue); + mutex_lock(&device->mutex); + + /* 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); + + 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 */ + kgsl_pwrctrl_request_state(device, 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 0000000000000000000000000000000000000000..b677800678f981bfb0c14f72c108e348ea7b5329 --- /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 0000000000000000000000000000000000000000..3d462212a6dc6811cfd05a066d47690b23e6186b --- /dev/null +++ b/drivers/gpu/msm/adreno_ringbuffer.c @@ -0,0 +1,1101 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the 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 "adreno_debugfs.h" + +#include "a2xx_reg.h" +#include "a3xx_reg.h" + +#define GSL_RB_NOP_SIZEDWORDS 2 + +void adreno_ringbuffer_submit(struct adreno_ringbuffer *rb) +{ + BUG_ON(rb->wptr == 0); + + /* Let the pwrscale policy know that new commands have + been submitted. */ + kgsl_pwrscale_busy(rb->device); + + /*synchronize memory before informing the hardware of the + *new commands. + */ + mb(); + + adreno_regwrite(rb->device, REG_CP_RB_WPTR, rb->wptr); +} + +static void +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, cp_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)); +} + +unsigned int *adreno_ringbuffer_allocspace(struct adreno_ringbuffer *rb, + unsigned int numcmds) +{ + unsigned int *ptr = NULL; + + 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)) + adreno_ringbuffer_waitspace(rb, numcmds, 1); + } else { + /* wptr behind rptr */ + if ((rb->wptr + numcmds) >= rb->rptr) + 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)) + adreno_ringbuffer_waitspace(rb, numcmds, 1); + } + + 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); + int i, ret = 0; + + if (adreno_dev->pm4_fw == NULL) { + int len; + void *ptr; + + ret = _load_firmware(device, adreno_dev->pm4_fwfile, + &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; + kfree(ptr); + 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); + int i, ret = 0; + + if (adreno_dev->pfp_fw == NULL) { + int len; + void *ptr; + + ret = _load_firmware(device, adreno_dev->pfp_fwfile, + &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; + kfree(ptr); + 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, adreno_dev->gpudev->reg_cp_pfp_ucode_addr, 0); + for (i = 1; i < adreno_dev->pfp_fw_size; i++) + adreno_regwrite(device, + adreno_dev->gpudev->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 rb_cntl; + struct kgsl_device *device = rb->device; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + if (rb->flags & KGSL_FLAGS_STARTED) + return 0; + + if (init_ram) + rb->timestamp[KGSL_MEMSTORE_GLOBAL] = 0; + + kgsl_sharedmem_set(&rb->memptrs_desc, 0, 0, + sizeof(struct kgsl_rbmemptrs)); + + kgsl_sharedmem_set(&rb->buffer_desc, 0, 0xAA, + (rb->sizedwords << 2)); + + if (adreno_is_a2xx(adreno_dev)) { + 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); + + if (adreno_is_a2xx(adreno_dev)) { + /* WPTR polling */ + cp_rb_cntl.f.rb_poll_en = GSL_RB_CNTL_POLL_EN; + } + + /* 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); + + if (adreno_is_a3xx(adreno_dev)) { + /* enable access protection to privileged registers */ + adreno_regwrite(device, A3XX_CP_PROTECT_CTRL, 0x00000007); + + /* RBBM registers */ + adreno_regwrite(device, A3XX_CP_PROTECT_REG_0, 0x63000040); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_1, 0x62000080); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_2, 0x600000CC); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_3, 0x60000108); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_4, 0x64000140); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_5, 0x66000400); + + /* CP registers */ + adreno_regwrite(device, A3XX_CP_PROTECT_REG_6, 0x65000700); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_7, 0x610007D8); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_8, 0x620007E0); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_9, 0x61001178); + adreno_regwrite(device, A3XX_CP_PROTECT_REG_A, 0x64001180); + + /* RB registers */ + adreno_regwrite(device, A3XX_CP_PROTECT_REG_B, 0x60003300); + + /* VBIF registers */ + adreno_regwrite(device, A3XX_CP_PROTECT_REG_C, 0x6B00C000); + } + + if (adreno_is_a2xx(adreno_dev)) { + /* 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_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + 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; + + /* CP ROQ queue sizes (bytes) - RB:16, ST:16, IB1:32, IB2:64 */ + if (adreno_is_a305(adreno_dev) || adreno_is_a320(adreno_dev)) + adreno_regwrite(device, REG_CP_QUEUE_THRESHOLDS, 0x000E0602); + + rb->rptr = 0; + rb->wptr = 0; + + /* clear ME_HALT to start micro engine */ + adreno_regwrite(device, REG_CP_ME_CNTL, 0); + + /* ME init is GPU specific, so jump into the sub-function */ + adreno_dev->gpudev->rb_init(adreno_dev, rb); + + /* idle device to validate ME INIT */ + status = adreno_idle(device, KGSL_TIMEOUT_DEFAULT); + + if (status == 0) + rb->flags |= KGSL_FLAGS_STARTED; + + return status; +} + +void 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; + } +} + +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; +} + +void 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)); +} + +static uint32_t +adreno_ringbuffer_addcmds(struct adreno_ringbuffer *rb, + struct adreno_context *context, + unsigned int flags, unsigned int *cmds, + int sizedwords) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(rb->device); + unsigned int *ringcmds; + unsigned int timestamp; + unsigned int total_sizedwords = sizedwords; + unsigned int i; + unsigned int rcmd_gpu; + unsigned int context_id = KGSL_MEMSTORE_GLOBAL; + unsigned int gpuaddr = rb->device->memstore.gpuaddr; + + if (context != NULL) { + /* + * if the context was not created with per context timestamp + * support, we must use the global timestamp since issueibcmds + * will be returning that one. + */ + if (context->flags & CTXT_FLAGS_PER_CONTEXT_TS) + context_id = context->id; + } + + /* 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; + + if (adreno_is_a3xx(adreno_dev)) + total_sizedwords += 7; + + total_sizedwords += 2; /* scratchpad ts for recovery */ + if (context) { + total_sizedwords += 3; /* sop timestamp */ + total_sizedwords += 4; /* eop timestamp */ + total_sizedwords += 3; /* global timestamp without cache + * flush for non-zero context */ + } else { + total_sizedwords += 4; /* global timestamp for recovery*/ + } + + 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, cp_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, + cp_type3_packet(CP_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, + cp_type3_packet(CP_SET_PROTECTED_MODE, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, 1); + } + + /* always increment the global timestamp. once. */ + rb->timestamp[KGSL_MEMSTORE_GLOBAL]++; + if (context) { + if (context_id == KGSL_MEMSTORE_GLOBAL) + rb->timestamp[context_id] = + rb->timestamp[KGSL_MEMSTORE_GLOBAL]; + else + rb->timestamp[context_id]++; + } + timestamp = rb->timestamp[context_id]; + + /* scratchpad ts for recovery */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, cp_type0_packet(REG_CP_TIMESTAMP, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, rb->timestamp[KGSL_MEMSTORE_GLOBAL]); + + if (adreno_is_a3xx(adreno_dev)) { + /* + * FLush HLSQ lazy updates to make sure there are no + * rsources pending for indirect loads after the timestamp + */ + + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_EVENT_WRITE, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, 0x07); /* HLSQ_FLUSH */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_WAIT_FOR_IDLE, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, 0x00); + } + + if (context) { + /* start-of-pipeline timestamp */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_MEM_WRITE, 2)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (gpuaddr + + KGSL_MEMSTORE_OFFSET(context->id, soptimestamp))); + GSL_RB_WRITE(ringcmds, rcmd_gpu, timestamp); + + /* end-of-pipeline timestamp */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_EVENT_WRITE, 3)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, CACHE_FLUSH_TS); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (gpuaddr + + KGSL_MEMSTORE_OFFSET(context->id, eoptimestamp))); + GSL_RB_WRITE(ringcmds, rcmd_gpu, timestamp); + + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_MEM_WRITE, 2)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (gpuaddr + + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + eoptimestamp))); + GSL_RB_WRITE(ringcmds, rcmd_gpu, + rb->timestamp[KGSL_MEMSTORE_GLOBAL]); + } else { + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_EVENT_WRITE, 3)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, CACHE_FLUSH_TS); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (gpuaddr + + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + eoptimestamp))); + GSL_RB_WRITE(ringcmds, rcmd_gpu, + rb->timestamp[KGSL_MEMSTORE_GLOBAL]); + } + + if (!(flags & KGSL_CMD_FLAGS_NO_TS_CMP)) { + /* Conditional execution based on memory values */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_COND_EXEC, 4)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (gpuaddr + + KGSL_MEMSTORE_OFFSET( + context_id, ts_cmp_enable)) >> 2); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (gpuaddr + + KGSL_MEMSTORE_OFFSET( + context_id, ref_wait_ts)) >> 2); + GSL_RB_WRITE(ringcmds, rcmd_gpu, timestamp); + /* # of conditional command DWORDs */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, 2); + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_INTERRUPT, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, CP_INT_CNTL__RB_INT_MASK); + } + + if (adreno_is_a3xx(adreno_dev)) { + /* Dummy set-constant to trigger context rollover */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + cp_type3_packet(CP_SET_CONSTANT, 2)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, + (0x4<<16)|(A3XX_HLSQ_CL_KERNEL_GROUP_X_REG - 0x2000)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, 0); + } + + adreno_ringbuffer_submit(rb); + + 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, NULL, flags, cmds, sizedwords); +} + +static bool _parse_ibs(struct kgsl_device_private *dev_priv, uint gpuaddr, + int sizedwords); + +static bool +_handle_type3(struct kgsl_device_private *dev_priv, uint *hostaddr) +{ + unsigned int opcode = cp_type3_opcode(*hostaddr); + switch (opcode) { + case CP_INDIRECT_BUFFER_PFD: + case CP_INDIRECT_BUFFER_PFE: + case CP_COND_INDIRECT_BUFFER_PFE: + case CP_COND_INDIRECT_BUFFER_PFD: + return _parse_ibs(dev_priv, hostaddr[1], hostaddr[2]); + case CP_NOP: + case CP_WAIT_FOR_IDLE: + case CP_WAIT_REG_MEM: + case CP_WAIT_REG_EQ: + case CP_WAT_REG_GTE: + case CP_WAIT_UNTIL_READ: + case CP_WAIT_IB_PFD_COMPLETE: + case CP_REG_RMW: + case CP_REG_TO_MEM: + case CP_MEM_WRITE: + case CP_MEM_WRITE_CNTR: + case CP_COND_EXEC: + case CP_COND_WRITE: + case CP_EVENT_WRITE: + case CP_EVENT_WRITE_SHD: + case CP_EVENT_WRITE_CFL: + case CP_EVENT_WRITE_ZPD: + case CP_DRAW_INDX: + case CP_DRAW_INDX_2: + case CP_DRAW_INDX_BIN: + case CP_DRAW_INDX_2_BIN: + case CP_VIZ_QUERY: + case CP_SET_STATE: + case CP_SET_CONSTANT: + case CP_IM_LOAD: + case CP_IM_LOAD_IMMEDIATE: + case CP_LOAD_CONSTANT_CONTEXT: + case CP_INVALIDATE_STATE: + case CP_SET_SHADER_BASES: + case CP_SET_BIN_MASK: + case CP_SET_BIN_SELECT: + case CP_SET_BIN_BASE_OFFSET: + case CP_SET_BIN_DATA: + case CP_CONTEXT_UPDATE: + case CP_INTERRUPT: + case CP_IM_STORE: + case CP_LOAD_STATE: + break; + /* these shouldn't come from userspace */ + case CP_ME_INIT: + case CP_SET_PROTECTED_MODE: + default: + KGSL_CMD_ERR(dev_priv->device, "bad CP opcode %0x\n", opcode); + return false; + break; + } + + return true; +} + +static bool +_handle_type0(struct kgsl_device_private *dev_priv, uint *hostaddr) +{ + unsigned int reg = type0_pkt_offset(*hostaddr); + unsigned int cnt = type0_pkt_size(*hostaddr); + if (reg < 0x0192 || (reg + cnt) >= 0x8000) { + KGSL_CMD_ERR(dev_priv->device, "bad type0 reg: 0x%0x cnt: %d\n", + reg, cnt); + return false; + } + 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 + */ +static bool _parse_ibs(struct kgsl_device_private *dev_priv, + uint gpuaddr, int sizedwords) +{ + static uint level; /* recursion level */ + bool ret = false; + uint *hostaddr, *hoststart; + int dwords_left = sizedwords; /* dwords left in the current command + buffer */ + 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_CMD_ERR(dev_priv->device, + "no mapping for gpuaddr: 0x%08x\n", gpuaddr); + return false; + } + + hostaddr = (uint *)kgsl_gpuaddr_to_vaddr(&entry->memdesc, gpuaddr); + if (hostaddr == NULL) { + KGSL_CMD_ERR(dev_priv->device, + "no mapping for gpuaddr: 0x%08x\n", gpuaddr); + return false; + } + + hoststart = hostaddr; + + level++; + + KGSL_CMD_INFO(dev_priv->device, "ib: gpuaddr:0x%08x, wc:%d, hptr:%p\n", + gpuaddr, sizedwords, hostaddr); + + mb(); + while (dwords_left > 0) { + bool cur_ret = true; + int count = 0; /* dword count including packet header */ + + switch (*hostaddr >> 30) { + case 0x0: /* type-0 */ + count = (*hostaddr >> 16)+2; + cur_ret = _handle_type0(dev_priv, hostaddr); + break; + case 0x1: /* type-1 */ + count = 2; + break; + case 0x3: /* type-3 */ + count = ((*hostaddr >> 16) & 0x3fff) + 2; + cur_ret = _handle_type3(dev_priv, hostaddr); + break; + default: + KGSL_CMD_ERR(dev_priv->device, "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; + } + + if (!cur_ret) { + KGSL_CMD_ERR(dev_priv->device, + "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); + + if (ADRENO_DEVICE(dev_priv->device)->ib_check_level + >= 2) + print_hex_dump(KERN_ERR, + level == 1 ? "IB1:" : "IB2:", + DUMP_PREFIX_OFFSET, 32, 4, hoststart, + sizedwords*4, 0); + goto done; + } + + /* jump to next packet */ + dwords_left -= count; + hostaddr += count; + if (dwords_left < 0) { + KGSL_CMD_ERR(dev_priv->device, + "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); + if (ADRENO_DEVICE(dev_priv->device)->ib_check_level + >= 2) + print_hex_dump(KERN_ERR, + level == 1 ? "IB1:" : "IB2:", + DUMP_PREFIX_OFFSET, 32, 4, hoststart, + sizedwords*4, 0); + goto done; + } + } + + ret = true; +done: + if (!ret) + KGSL_DRV_ERR(dev_priv->device, + "parsing failed: gpuaddr:0x%08x, " + "host:0x%p, wc:%d\n", gpuaddr, hoststart, sizedwords); + + level--; + + return ret; +} + +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; + unsigned int start_index = 0; + + if (device->state & KGSL_STATE_HUNG) + return -EBUSY; + if (!(adreno_dev->ringbuffer.flags & KGSL_FLAGS_STARTED) || + context == NULL || ibdesc == 0 || numibs == 0) + return -EINVAL; + + drawctxt = context->devctxt; + + if (drawctxt->flags & CTXT_FLAGS_GPU_HANG) { + KGSL_CTXT_WARN(device, "Context %p caused a gpu hang.." + " will not accept commands for context %d\n", + drawctxt, drawctxt->id); + return -EDEADLK; + } + + cmds = link = kzalloc(sizeof(unsigned int) * (numibs * 3 + 4), + GFP_KERNEL); + if (!link) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + sizeof(unsigned int) * (numibs * 3 + 4)); + return -ENOMEM; + } + + /*When preamble is enabled, the preamble buffer with state restoration + commands are stored in the first node of the IB chain. We can skip that + if a context switch hasn't occured */ + + if (drawctxt->flags & CTXT_FLAGS_PREAMBLE && + adreno_dev->drawctxt_active == drawctxt) + start_index = 1; + + if (!start_index) { + *cmds++ = cp_nop_packet(1); + *cmds++ = KGSL_START_OF_IB_IDENTIFIER; + } else { + *cmds++ = cp_nop_packet(4); + *cmds++ = KGSL_START_OF_IB_IDENTIFIER; + *cmds++ = CP_HDR_INDIRECT_BUFFER_PFD; + *cmds++ = ibdesc[0].gpuaddr; + *cmds++ = ibdesc[0].sizedwords; + } + for (i = start_index; i < numibs; i++) { + if (unlikely(adreno_dev->ib_check_level >= 1 && + !_parse_ibs(dev_priv, ibdesc[i].gpuaddr, + ibdesc[i].sizedwords))) { + kfree(link); + return -EINVAL; + } + *cmds++ = CP_HDR_INDIRECT_BUFFER_PFD; + *cmds++ = ibdesc[i].gpuaddr; + *cmds++ = ibdesc[i].sizedwords; + } + + *cmds++ = cp_nop_packet(1); + *cmds++ = KGSL_END_OF_IB_IDENTIFIER; + + kgsl_setstate(&device->mmu, + kgsl_mmu_pt_get_flags(device->mmu.hwpagetable, + device->id)); + + adreno_drawctxt_switch(adreno_dev, drawctxt, flags); + + *timestamp = adreno_ringbuffer_addcmds(&adreno_dev->ringbuffer, + drawctxt, + 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; + struct kgsl_context *context; + unsigned int context_id; + + GSL_RB_GET_READPTR(rb, &rb->rptr); + + /* 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, &context_id, + KGSL_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + current_context)); + KGSL_DRV_ERR(device, "Last context id: %d\n", context_id); + context = idr_find(&device->context_idr, context_id); + if (context == NULL) { + KGSL_DRV_ERR(device, + "GPU recovery from hang not possible because last" + " context id is invalid.\n"); + return -EINVAL; + } + retired_timestamp = kgsl_readtimestamp(device, context, + 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 == cp_type3_packet(CP_INTERRUPT, 1) + && val3 == CP_INT_CNTL__RB_INT_MASK) || + (val1 == cp_type3_packet(CP_EVENT_WRITE, 3) + && val2 == CACHE_FLUSH_TS && + val3 == (rb->device->memstore.gpuaddr + + KGSL_MEMSTORE_OFFSET(context_id, + 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 == cp_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; + } + + 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 != cp_type3_packet(CP_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_MEMSTORE_OFFSET(KGSL_MEMSTORE_GLOBAL, + current_context))); + kgsl_sharedmem_readl(&rb->buffer_desc, &value, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + + /* + * If other context switches were already lost and + * and the current context is the one that is hanging, + * then we cannot recover. Print an error message + * and leave. + */ + + if ((copy_rb_contents == 0) && (value == context_id)) { + KGSL_DRV_ERR(device, "GPU recovery could not " + "find the previous context\n"); + return -EINVAL; + } + + /* + * 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 != context_id) { + copy_rb_contents = 1; + temp_rb_buffer[temp_idx++] = cp_nop_packet(1); + temp_rb_buffer[temp_idx++] = + KGSL_CMD_IDENTIFIER; + temp_rb_buffer[temp_idx++] = cp_nop_packet(1); + temp_rb_buffer[temp_idx++] = + KGSL_CONTEXT_TO_MEM_IDENTIFIER; + temp_rb_buffer[temp_idx++] = + cp_type3_packet(CP_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; + 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 0000000000000000000000000000000000000000..ae2e4c79e0bb446ac3db0ac878e91ce2b7f309ad --- /dev/null +++ b/drivers/gpu/msm/adreno_ringbuffer.h @@ -0,0 +1,141 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +/* + * 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 */ + + unsigned int timestamp[KGSL_MEMSTORE_MAX]; +}; + + +#define GSL_RB_WRITE(ring, gpuaddr, data) \ + do { \ + *ring = data; \ + wmb(); \ + kgsl_cffdump_setmem(gpuaddr, data, 4); \ + ring++; \ + gpuaddr += sizeof(uint); \ + } while (0) + +/* enable timestamp (...scratch0) memory shadowing */ +#define GSL_RB_MEMPTRS_SCRATCH_MASK 0x1 + +/* mem rptr */ +#define GSL_RB_CNTL_NO_UPDATE 0x0 /* enable */ +#define GSL_RB_GET_READPTR(rb, data) \ + do { \ + *(data) = rb->memptrs->rptr; \ + } while (0) + +#define GSL_RB_CNTL_POLL_EN 0x0 /* disable */ + +/* + * 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 + +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); + +void adreno_ringbuffer_stop(struct adreno_ringbuffer *rb); + +void adreno_ringbuffer_close(struct adreno_ringbuffer *rb); + +void adreno_ringbuffer_issuecmds(struct kgsl_device *device, + unsigned int flags, + unsigned int *cmdaddr, + int sizedwords); + +void adreno_ringbuffer_submit(struct adreno_ringbuffer *rb); + +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); + +unsigned int *adreno_ringbuffer_allocspace(struct adreno_ringbuffer *rb, + unsigned int numcmds); + +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/adreno_snapshot.c b/drivers/gpu/msm/adreno_snapshot.c new file mode 100644 index 0000000000000000000000000000000000000000..a0907d7a8bf1d6684c35bb2fff1b43c17391932b --- /dev/null +++ b/drivers/gpu/msm/adreno_snapshot.c @@ -0,0 +1,870 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "kgsl.h" +#include "kgsl_sharedmem.h" +#include "kgsl_snapshot.h" + +#include "adreno.h" +#include "adreno_pm4types.h" +#include "a2xx_reg.h" +#include "a3xx_reg.h" + +/* Number of dwords of ringbuffer history to record */ +#define NUM_DWORDS_OF_RINGBUFFER_HISTORY 100 + +/* Maintain a list of the objects we see during parsing */ + +#define SNAPSHOT_OBJ_BUFSIZE 64 + +#define SNAPSHOT_OBJ_TYPE_IB 0 + +/* Keep track of how many bytes are frozen after a snapshot and tell the user */ +static int snapshot_frozen_objsize; + +static struct kgsl_snapshot_obj { + int type; + uint32_t gpuaddr; + uint32_t ptbase; + void *ptr; + int dwords; +} objbuf[SNAPSHOT_OBJ_BUFSIZE]; + +/* Pointer to the next open entry in the object list */ +static int objbufptr; + +/* Push a new buffer object onto the list */ +static void push_object(struct kgsl_device *device, int type, uint32_t ptbase, + uint32_t gpuaddr, int dwords) +{ + int index; + void *ptr; + + /* + * Sometimes IBs can be reused in the same dump. Because we parse from + * oldest to newest, if we come across an IB that has already been used, + * assume that it has been reused and update the list with the newest + * size. + */ + + for (index = 0; index < objbufptr; index++) { + if (objbuf[index].gpuaddr == gpuaddr && + objbuf[index].ptbase == ptbase) { + objbuf[index].dwords = dwords; + return; + } + } + + if (objbufptr == SNAPSHOT_OBJ_BUFSIZE) { + KGSL_DRV_ERR(device, "snapshot: too many snapshot objects\n"); + return; + } + + /* + * adreno_convertaddr verifies that the IB size is valid - at least in + * the context of it being smaller then the allocated memory space + */ + ptr = adreno_convertaddr(device, ptbase, gpuaddr, dwords << 2); + + if (ptr == NULL) { + KGSL_DRV_ERR(device, + "snapshot: Can't find GPU address for %x\n", gpuaddr); + return; + } + + /* Put it on the list of things to parse */ + objbuf[objbufptr].type = type; + objbuf[objbufptr].gpuaddr = gpuaddr; + objbuf[objbufptr].ptbase = ptbase; + objbuf[objbufptr].dwords = dwords; + objbuf[objbufptr++].ptr = ptr; +} + +/* + * Return a 1 if the specified object is already on the list of buffers + * to be dumped + */ + +static int find_object(int type, unsigned int gpuaddr, unsigned int ptbase) +{ + int index; + + for (index = 0; index < objbufptr; index++) { + if (objbuf[index].gpuaddr == gpuaddr && + objbuf[index].ptbase == ptbase && + objbuf[index].type == type) + return 1; + } + + return 0; +} + +/* + * This structure keeps track of type0 writes to VSC_PIPE_DATA_ADDRESS_x and + * VSC_PIPE_DATA_LENGTH_x. When a draw initator is called these registers + * point to buffers that we need to freeze for a snapshot + */ + +static struct { + unsigned int base; + unsigned int size; +} vsc_pipe[8]; + +/* + * This is the cached value of type0 writes to the VSC_SIZE_ADDRESS which + * contains the buffer address of the visiblity stream size buffer during a + * binning pass + */ + +static unsigned int vsc_size_address; + +/* + * This struct keeps track of type0 writes to VFD_FETCH_INSTR_0_X and + * VFD_FETCH_INSTR_1_X registers. When a draw initator is called the addresses + * and sizes in these registers point to VBOs that we need to freeze for a + * snapshot + */ + +static struct { + unsigned int base; + unsigned int stride; +} vbo[16]; + +/* + * This is the cached value of type0 writes to VFD_INDEX_MAX. This will be used + * to calculate the size of the VBOs when the draw initator is called + */ + +static unsigned int vfd_index_max; + +/* + * This is the cached value of type0 writes to VFD_CONTROL_0 which tells us how + * many VBOs are active when the draw initator is called + */ + +static unsigned int vfd_control_0; + +/* + * Cached value of type0 writes to SP_VS_PVT_MEM_ADDR and SP_FS_PVT_MEM_ADDR. + * This is a buffer that contains private stack information for the shader + */ + +static unsigned int sp_vs_pvt_mem_addr; +static unsigned int sp_fs_pvt_mem_addr; + +static void ib_parse_load_state(struct kgsl_device *device, unsigned int *pkt, + unsigned int ptbase) +{ + unsigned int block, source, type; + + /* + * The object here is to find indirect shaders i.e - shaders loaded from + * GPU memory instead of directly in the command. These should be added + * to the list of memory objects to dump. So look at the load state + * call and see if 1) the shader block is a shader (block = 4, 5 or 6) + * 2) that the block is indirect (source = 4). If these all match then + * add the memory address to the list. The size of the object will + * differ depending on the type. Type 0 (instructions) are 8 dwords per + * unit and type 1 (constants) are 2 dwords per unit. + */ + + if (type3_pkt_size(pkt[0]) < 2) + return; + + /* + * pkt[1] 18:16 - source + * pkt[1] 21:19 - state block + * pkt[1] 31:22 - size in units + * pkt[2] 0:1 - type + * pkt[2] 31:2 - GPU memory address + */ + + block = (pkt[1] >> 19) & 0x07; + source = (pkt[1] >> 16) & 0x07; + type = pkt[2] & 0x03; + + if ((block == 4 || block == 5 || block == 6) && source == 4) { + int unitsize = (type == 0) ? 8 : 2; + int ret; + + /* Freeze the GPU buffer containing the shader */ + + ret = kgsl_snapshot_get_object(device, ptbase, + pkt[2] & 0xFFFFFFFC, + (((pkt[1] >> 22) & 0x03FF) * unitsize) << 2, + SNAPSHOT_GPU_OBJECT_SHADER); + snapshot_frozen_objsize += ret; + } +} + +/* + * This opcode sets the base addresses for the visibilty stream buffer and the + * visiblity stream size buffer. + */ + +static void ib_parse_set_bin_data(struct kgsl_device *device, unsigned int *pkt, + unsigned int ptbase) +{ + int ret; + + if (type3_pkt_size(pkt[0]) < 2) + return; + + /* Visiblity stream buffer */ + ret = kgsl_snapshot_get_object(device, ptbase, pkt[1], 0, + SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; + + /* visiblity stream size buffer (fixed size 8 dwords) */ + ret = kgsl_snapshot_get_object(device, ptbase, pkt[2], 32, + SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; +} + +/* + * This opcode writes to GPU memory - if the buffer is written to, there is a + * good chance that it would be valuable to capture in the snapshot, so mark all + * buffers that are written to as frozen + */ + +static void ib_parse_mem_write(struct kgsl_device *device, unsigned int *pkt, + unsigned int ptbase) +{ + int ret; + + if (type3_pkt_size(pkt[0]) < 1) + return; + + /* + * The address is where the data in the rest of this packet is written + * to, but since that might be an offset into the larger buffer we need + * to get the whole thing. Pass a size of 0 kgsl_snapshot_get_object to + * capture the entire buffer. + */ + + ret = kgsl_snapshot_get_object(device, ptbase, pkt[1] & 0xFFFFFFFC, 0, + SNAPSHOT_GPU_OBJECT_GENERIC); + + snapshot_frozen_objsize += ret; +} + +/* + * The DRAW_INDX opcode sends a draw initator which starts a draw operation in + * the GPU, so this is the point where all the registers and buffers become + * "valid". The DRAW_INDX may also have an index buffer pointer that should be + * frozen with the others + */ + +static void ib_parse_draw_indx(struct kgsl_device *device, unsigned int *pkt, + unsigned int ptbase) +{ + int ret, i; + + if (type3_pkt_size(pkt[0]) < 3) + return; + + /* DRAW_IDX may have a index buffer pointer */ + + if (type3_pkt_size(pkt[0]) > 3) { + ret = kgsl_snapshot_get_object(device, ptbase, pkt[4], pkt[5], + SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; + } + + /* + * All of the type0 writes are valid at a draw initiator, so freeze + * the various buffers that we are tracking + */ + + /* First up the visiblity stream buffer */ + + for (i = 0; i < ARRAY_SIZE(vsc_pipe); i++) { + if (vsc_pipe[i].base != 0 && vsc_pipe[i].size != 0) { + ret = kgsl_snapshot_get_object(device, ptbase, + vsc_pipe[i].base, vsc_pipe[i].size, + SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; + } + } + + /* Next the visibility stream size buffer */ + + if (vsc_size_address) { + ret = kgsl_snapshot_get_object(device, ptbase, + vsc_size_address, 32, + SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; + } + + /* Next private shader buffer memory */ + if (sp_vs_pvt_mem_addr) { + ret = kgsl_snapshot_get_object(device, ptbase, + sp_vs_pvt_mem_addr, 8192, + SNAPSHOT_GPU_OBJECT_GENERIC); + + snapshot_frozen_objsize += ret; + } + + if (sp_fs_pvt_mem_addr) { + ret = kgsl_snapshot_get_object(device, ptbase, + sp_fs_pvt_mem_addr, 8192, + SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; + } + + /* Finally: VBOs */ + + /* The number of active VBOs is stored in VFD_CONTROL_O[31:27] */ + for (i = 0; i < (vfd_control_0) >> 27; i++) { + int size; + + /* + * The size of the VBO is the stride stored in + * VFD_FETCH_INSTR_0_X.BUFSTRIDE * VFD_INDEX_MAX. The base + * is stored in VFD_FETCH_INSTR_1_X + */ + + if (vbo[i].base != 0) { + size = vbo[i].stride * vfd_index_max; + + ret = kgsl_snapshot_get_object(device, ptbase, + vbo[i].base, + 0, SNAPSHOT_GPU_OBJECT_GENERIC); + snapshot_frozen_objsize += ret; + } + } +} + +/* + * Parse all the type3 opcode packets that may contain important information, + * such as additional GPU buffers to grab or a draw initator + */ + +static void ib_parse_type3(struct kgsl_device *device, unsigned int *ptr, + unsigned int ptbase) +{ + switch (cp_type3_opcode(*ptr)) { + case CP_LOAD_STATE: + ib_parse_load_state(device, ptr, ptbase); + break; + case CP_SET_BIN_DATA: + ib_parse_set_bin_data(device, ptr, ptbase); + break; + case CP_MEM_WRITE: + ib_parse_mem_write(device, ptr, ptbase); + break; + case CP_DRAW_INDX: + ib_parse_draw_indx(device, ptr, ptbase); + break; + } +} + +/* + * Parse type0 packets found in the stream. Some of the registers that are + * written are clues for GPU buffers that we need to freeze. Register writes + * are considred valid when a draw initator is called, so just cache the values + * here and freeze them when a CP_DRAW_INDX is seen. This protects against + * needlessly caching buffers that won't be used during a draw call + */ + +static void ib_parse_type0(struct kgsl_device *device, unsigned int *ptr, + unsigned int ptbase) +{ + int size = type0_pkt_size(*ptr); + int offset = type0_pkt_offset(*ptr); + int i; + + for (i = 0; i < size; i++, offset++) { + + /* Visiblity stream buffer */ + + if (offset >= A3XX_VSC_PIPE_DATA_ADDRESS_0 && + offset <= A3XX_VSC_PIPE_DATA_LENGTH_7) { + int index = offset - A3XX_VSC_PIPE_DATA_ADDRESS_0; + + /* Each bank of address and length registers are + * interleaved with an empty register: + * + * address 0 + * length 0 + * empty + * address 1 + * length 1 + * empty + * ... + */ + + if ((index % 3) == 0) + vsc_pipe[index / 3].base = ptr[i + 1]; + else if ((index % 3) == 1) + vsc_pipe[index / 3].size = ptr[i + 1]; + } else if ((offset >= A3XX_VFD_FETCH_INSTR_0_0) && + (offset <= A3XX_VFD_FETCH_INSTR_1_F)) { + int index = offset - A3XX_VFD_FETCH_INSTR_0_0; + + /* + * FETCH_INSTR_0_X and FETCH_INSTR_1_X banks are + * interleaved as above but without the empty register + * in between + */ + + if ((index % 2) == 0) + vbo[index >> 1].stride = + (ptr[i + 1] >> 7) & 0x1FF; + else + vbo[index >> 1].base = ptr[i + 1]; + } else { + /* + * Cache various support registers for calculating + * buffer sizes + */ + + switch (offset) { + case A3XX_VFD_CONTROL_0: + vfd_control_0 = ptr[i + 1]; + break; + case A3XX_VFD_INDEX_MAX: + vfd_index_max = ptr[i + 1]; + break; + case A3XX_VSC_SIZE_ADDRESS: + vsc_size_address = ptr[i + 1]; + break; + case A3XX_SP_VS_PVT_MEM_ADDR_REG: + sp_vs_pvt_mem_addr = ptr[i + 1]; + break; + case A3XX_SP_FS_PVT_MEM_ADDR_REG: + sp_fs_pvt_mem_addr = ptr[i + 1]; + break; + } + } + } +} + +/* Add an IB as a GPU object, but first, parse it to find more goodies within */ + +static void ib_add_gpu_object(struct kgsl_device *device, unsigned int ptbase, + unsigned int gpuaddr, unsigned int dwords) +{ + int i, ret, rem = dwords; + unsigned int *src = (unsigned int *) adreno_convertaddr(device, ptbase, + gpuaddr, dwords << 2); + + if (src == NULL) + return; + + for (i = 0; rem != 0; rem--, i++) { + int pktsize; + + if (!pkt_is_type0(src[i]) && !pkt_is_type3(src[i])) + continue; + + pktsize = type3_pkt_size(src[i]); + + if ((pktsize + 1) > rem) + break; + + if (pkt_is_type3(src[i])) { + if (adreno_cmd_is_ib(src[i])) + ib_add_gpu_object(device, ptbase, + src[i + 1], src[i + 2]); + else + ib_parse_type3(device, &src[i], ptbase); + } else if (pkt_is_type0(src[i])) { + ib_parse_type0(device, &src[i], ptbase); + } + + i += pktsize; + rem -= pktsize; + } + + ret = kgsl_snapshot_get_object(device, ptbase, gpuaddr, dwords << 2, + SNAPSHOT_GPU_OBJECT_IB); + + snapshot_frozen_objsize += ret; +} + +/* Snapshot the istore memory */ +static int snapshot_istore(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_istore *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + int count, i; + + count = adreno_dev->istore_size * adreno_dev->instruction_size; + + if (remain < (count * 4) + sizeof(*header)) { + KGSL_DRV_ERR(device, + "snapshot: Not enough memory for the istore section"); + return 0; + } + + header->count = adreno_dev->istore_size; + + for (i = 0; i < count; i++) + kgsl_regread(device, ADRENO_ISTORE_START + i, &data[i]); + + return (count * 4) + sizeof(*header); +} + +/* Snapshot the ringbuffer memory */ +static int snapshot_rb(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_rb *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + unsigned int ptbase, rptr, *rbptr, ibbase; + int index, size, i; + int parse_ibs = 0, ib_parse_start; + int skip_pktsize = 1; + + /* Get the physical address of the MMU pagetable */ + ptbase = kgsl_mmu_get_current_ptbase(&device->mmu); + + /* Get the current read pointers for the RB */ + kgsl_regread(device, REG_CP_RB_RPTR, &rptr); + + /* Address of the last processed IB */ + kgsl_regread(device, REG_CP_IB1_BASE, &ibbase); + + /* + * Figure out the window of ringbuffer data to dump. First we need to + * find where the last processed IB ws submitted + */ + + index = rptr; + rbptr = rb->buffer_desc.hostptr; + + while (index != rb->wptr) { + index--; + + if (index < 0) { + index = rb->sizedwords - 3; + + /* We wrapped without finding what we wanted */ + if (index < rb->wptr) { + index = rb->wptr; + break; + } + } + + if (adreno_cmd_is_ib(rbptr[index]) && + rbptr[index + 1] == ibbase) + break; + } + + /* + * index points at the last submitted IB. We can only trust that the + * memory between the context switch and the hanging IB is valid, so + * the next step is to find the context switch before the submission + */ + + while (index != rb->wptr) { + index--; + + if (index < 0) { + index = rb->sizedwords - 2; + + /* + * Wrapped without finding the context switch. This is + * harmless - we should still have enough data to dump a + * valid state + */ + + if (index < rb->wptr) { + index = rb->wptr; + break; + } + } + + /* Break if the current packet is a context switch identifier */ + if ((rbptr[index] == cp_nop_packet(1)) && + (rbptr[index + 1] == KGSL_CONTEXT_TO_MEM_IDENTIFIER)) + break; + } + + /* + * Index represents the start of the window of interest. We will try + * to dump all buffers between here and the rptr + */ + + ib_parse_start = index; + + /* + * Dump the entire ringbuffer - the parser can choose how much of it to + * process + */ + + size = (rb->sizedwords << 2); + + if (remain < size + sizeof(*header)) { + KGSL_DRV_ERR(device, + "snapshot: Not enough memory for the rb section"); + return 0; + } + + /* Write the sub-header for the section */ + header->start = rb->wptr; + header->end = rb->wptr; + header->wptr = rb->wptr; + header->rbsize = rb->sizedwords; + header->count = rb->sizedwords; + + /* + * Loop through the RB, copying the data and looking for indirect + * buffers and MMU pagetable changes + */ + + index = rb->wptr; + for (i = 0; i < rb->sizedwords; i++) { + *data = rbptr[index]; + + /* + * Sometimes the rptr is located in the middle of a packet. + * try to adust for that by modifying the rptr to match a + * packet boundary. Unfortunately for us, it is hard to tell + * which dwords are legitimate type0 header and which are just + * random data so just walk over type0 packets until we get + * to the first type3, and from that point on start checking the + * size of the packet and adjusting accordingly + */ + + if (skip_pktsize && pkt_is_type3(rbptr[index])) + skip_pktsize = 0; + + if (skip_pktsize == 0) { + unsigned int pktsize = type3_pkt_size(rbptr[index]); + if (index + pktsize > rptr) + rptr = (index + pktsize) % rb->sizedwords; + } + + /* + * Only parse IBs between the start and the rptr or the next + * context switch, whichever comes first + */ + + if (index == ib_parse_start) + parse_ibs = 1; + else if (index == rptr || adreno_rb_ctxtswitch(&rbptr[index])) + parse_ibs = 0; + + if (parse_ibs && adreno_cmd_is_ib(rbptr[index])) { + unsigned int ibaddr = rbptr[index + 1]; + unsigned int ibsize = rbptr[index + 2]; + + /* + * This will return non NULL if the IB happens to be + * part of the context memory (i.e - context switch + * command buffers) + */ + + struct kgsl_memdesc *memdesc = + adreno_find_ctxtmem(device, ptbase, ibaddr, + ibsize); + + /* + * The IB from CP_IB1_BASE and the IBs for legacy + * context switch go into the snapshot all + * others get marked at GPU objects + */ + + if (ibaddr == ibbase || memdesc != NULL) + push_object(device, SNAPSHOT_OBJ_TYPE_IB, + ptbase, ibaddr, ibsize); + else + ib_add_gpu_object(device, ptbase, ibaddr, + ibsize); + } + + index = index + 1; + + if (index == rb->sizedwords) + index = 0; + + data++; + } + + /* Return the size of the section */ + return size + sizeof(*header); +} + +/* Snapshot the memory for an indirect buffer */ +static int snapshot_ib(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_ib *header = snapshot; + struct kgsl_snapshot_obj *obj = priv; + unsigned int *src = obj->ptr; + unsigned int *dst = snapshot + sizeof(*header); + int i; + + if (remain < (obj->dwords << 2) + sizeof(*header)) { + KGSL_DRV_ERR(device, + "snapshot: Not enough memory for the ib section"); + return 0; + } + + /* Write the sub-header for the section */ + header->gpuaddr = obj->gpuaddr; + header->ptbase = obj->ptbase; + header->size = obj->dwords; + + /* Write the contents of the ib */ + for (i = 0; i < obj->dwords; i++, src++, dst++) { + *dst = *src; + + if (pkt_is_type3(*src)) { + if ((obj->dwords - i) < type3_pkt_size(*src) + 1) + continue; + + if (adreno_cmd_is_ib(*src)) + push_object(device, SNAPSHOT_OBJ_TYPE_IB, + obj->ptbase, src[1], src[2]); + else + ib_parse_type3(device, src, obj->ptbase); + } + } + + return (obj->dwords << 2) + sizeof(*header); +} + +/* Dump another item on the current pending list */ +static void *dump_object(struct kgsl_device *device, int obj, void *snapshot, + int *remain) +{ + switch (objbuf[obj].type) { + case SNAPSHOT_OBJ_TYPE_IB: + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_IB, snapshot, remain, + snapshot_ib, &objbuf[obj]); + break; + default: + KGSL_DRV_ERR(device, + "snapshot: Invalid snapshot object type: %d\n", + objbuf[obj].type); + break; + } + + return snapshot; +} + +/* adreno_snapshot - Snapshot the Adreno GPU state + * @device - KGSL device to snapshot + * @snapshot - Pointer to the start of memory to write into + * @remain - A pointer to how many bytes of memory are remaining in the snapshot + * @hang - set if this snapshot was automatically triggered by a GPU hang + * This is a hook function called by kgsl_snapshot to snapshot the + * Adreno specific information for the GPU snapshot. In turn, this function + * calls the GPU specific snapshot function to get core specific information. + */ + +void *adreno_snapshot(struct kgsl_device *device, void *snapshot, int *remain, + int hang) +{ + int i; + uint32_t ptbase, ibbase, ibsize; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + /* Reset the list of objects */ + objbufptr = 0; + + snapshot_frozen_objsize = 0; + + /* Clear the caches for the visibilty stream and VBO parsing */ + + vfd_control_0 = 0; + vfd_index_max = 0; + vsc_size_address = 0; + + memset(vsc_pipe, 0, sizeof(vsc_pipe)); + memset(vbo, 0, sizeof(vbo)); + + /* Get the physical address of the MMU pagetable */ + ptbase = kgsl_mmu_get_current_ptbase(&device->mmu); + + /* Dump the ringbuffer */ + snapshot = kgsl_snapshot_add_section(device, KGSL_SNAPSHOT_SECTION_RB, + snapshot, remain, snapshot_rb, NULL); + + /* + * Make sure that the last IB1 that was being executed is dumped. + * Since this was the last IB1 that was processed, we should have + * already added it to the list during the ringbuffer parse but we + * want to be double plus sure. + */ + + kgsl_regread(device, REG_CP_IB1_BASE, &ibbase); + kgsl_regread(device, REG_CP_IB1_BUFSZ, &ibsize); + + /* + * The problem is that IB size from the register is the unprocessed size + * of the buffer not the original size, so if we didn't catch this + * buffer being directly used in the RB, then we might not be able to + * dump the whle thing. Print a warning message so we can try to + * figure how often this really happens. + */ + + if (!find_object(SNAPSHOT_OBJ_TYPE_IB, ibbase, ptbase) && ibsize) { + push_object(device, SNAPSHOT_OBJ_TYPE_IB, ptbase, + ibbase, ibsize); + KGSL_DRV_ERR(device, "CP_IB1_BASE not found in the ringbuffer. " + "Dumping %x dwords of the buffer.\n", ibsize); + } + + kgsl_regread(device, REG_CP_IB2_BASE, &ibbase); + kgsl_regread(device, REG_CP_IB2_BUFSZ, &ibsize); + + /* + * Add the last parsed IB2 to the list. The IB2 should be found as we + * parse the objects below, but we try to add it to the list first, so + * it too can be parsed. Don't print an error message in this case - if + * the IB2 is found during parsing, the list will be updated with the + * correct size. + */ + + if (!find_object(SNAPSHOT_OBJ_TYPE_IB, ibbase, ptbase) && ibsize) { + push_object(device, SNAPSHOT_OBJ_TYPE_IB, ptbase, + ibbase, ibsize); + } + + /* + * Go through the list of found objects and dump each one. As the IBs + * are parsed, more objects might be found, and objbufptr will increase + */ + for (i = 0; i < objbufptr; i++) + snapshot = dump_object(device, i, snapshot, remain); + + /* + * Only dump the istore on a hang - reading it on a running system + * has a non 0 chance of hanging the GPU + */ + + if (hang) { + snapshot = kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_ISTORE, snapshot, remain, + snapshot_istore, NULL); + } + + /* Add GPU specific sections - registers mainly, but other stuff too */ + if (adreno_dev->gpudev->snapshot) + snapshot = adreno_dev->gpudev->snapshot(adreno_dev, snapshot, + remain, hang); + + if (snapshot_frozen_objsize) + KGSL_DRV_ERR(device, "GPU snapshot froze %dKb of GPU buffers\n", + snapshot_frozen_objsize / 1024); + + return snapshot; +} diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c new file mode 100644 index 0000000000000000000000000000000000000000..e918cc2336b3440dc906cd4585a6e6dceb807b83 --- /dev/null +++ b/drivers/gpu/msm/kgsl.c @@ -0,0 +1,2697 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "kgsl.h" +#include "kgsl_debugfs.h" +#include "kgsl_cffdump.h" +#include "kgsl_log.h" +#include "kgsl_sharedmem.h" +#include "kgsl_device.h" +#include "kgsl_trace.h" + +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "kgsl." + +static int kgsl_pagetable_count = KGSL_PAGETABLE_COUNT; +static char *ksgl_mmu_type; +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"); +module_param_named(mmutype, ksgl_mmu_type, charp, 0); +MODULE_PARM_DESC(ksgl_mmu_type, +"Type of MMU to be used for graphics. Valid values are 'iommu' or 'gpummu' or 'nommu'"); + +static struct ion_client *kgsl_ion_client; + +/** + * kgsl_add_event - Add a new timstamp event for the KGSL device + * @device - KGSL device for the new event + * @ts - the timestamp to trigger the event on + * @cb - callback function to call when the timestamp expires + * @priv - private data for the specific event type + * @owner - driver instance that owns this event + * + * @returns - 0 on success or error code on failure + */ + +static int kgsl_add_event(struct kgsl_device *device, u32 id, u32 ts, + void (*cb)(struct kgsl_device *, void *, u32, u32), void *priv, + struct kgsl_device_private *owner) +{ + struct kgsl_event *event; + struct list_head *n; + unsigned int cur_ts; + struct kgsl_context *context = NULL; + + if (cb == NULL) + return -EINVAL; + + if (id != KGSL_MEMSTORE_GLOBAL) { + context = idr_find(&device->context_idr, id); + if (context == NULL) + return -EINVAL; + } + cur_ts = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED); + + /* Check to see if the requested timestamp has already fired */ + + if (timestamp_cmp(cur_ts, ts) >= 0) { + cb(device, priv, id, cur_ts); + return 0; + } + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (event == NULL) + return -ENOMEM; + + event->context = context; + event->timestamp = ts; + event->priv = priv; + event->func = cb; + event->owner = owner; + + /* + * Add the event in order to the list. Order is by context id + * first and then by timestamp for that context. + */ + + for (n = device->events.next ; n != &device->events; n = n->next) { + struct kgsl_event *e = + list_entry(n, struct kgsl_event, list); + + if (e->context != context) + continue; + + if (timestamp_cmp(e->timestamp, ts) > 0) { + list_add(&event->list, n->prev); + break; + } + } + + if (n == &device->events) + list_add_tail(&event->list, &device->events); + + queue_work(device->work_queue, &device->ts_expired_ws); + return 0; +} + +/** + * kgsl_cancel_events_ctxt - Cancel all events for a context + * @device - KGSL device for the events to cancel + * @ctxt - context whose events we want to cancel + * + */ +static void kgsl_cancel_events_ctxt(struct kgsl_device *device, + struct kgsl_context *context) +{ + struct kgsl_event *event, *event_tmp; + unsigned int id, cur; + + cur = kgsl_readtimestamp(device, context, KGSL_TIMESTAMP_RETIRED); + id = context->id; + + list_for_each_entry_safe(event, event_tmp, &device->events, list) { + if (event->context != context) + continue; + + /* + * "cancel" the events by calling their callback. + * Currently, events are used for lock and memory + * management, so if the process is dying the right + * thing to do is release or free. + */ + if (event->func) + event->func(device, event->priv, id, cur); + + list_del(&event->list); + kfree(event); + } +} + +/** + * kgsl_cancel_events - Cancel all events for a process + * @device - KGSL device for the events to cancel + * @owner - driver instance that owns the events to cancel + * + */ +static void kgsl_cancel_events(struct kgsl_device *device, + struct kgsl_device_private *owner) +{ + struct kgsl_event *event, *event_tmp; + unsigned int id, cur; + + list_for_each_entry_safe(event, event_tmp, &device->events, list) { + if (event->owner != owner) + continue; + + cur = kgsl_readtimestamp(device, event->context, + KGSL_TIMESTAMP_RETIRED); + + id = event->context ? event->context->id : KGSL_MEMSTORE_GLOBAL; + /* + * "cancel" the events by calling their callback. + * Currently, events are used for lock and memory + * management, so if the process is dying the right + * thing to do is release or free. + */ + if (event->func) + event->func(device, event->priv, id, cur); + + list_del(&event->list); + kfree(event); + } +} + +/* kgsl_get_mem_entry - get the mem_entry structure for the specified object + * @ptbase - the pagetable base of the object + * @gpuaddr - the GPU address of the object + * @size - Size of the region to search + */ + +struct kgsl_mem_entry *kgsl_get_mem_entry(unsigned int ptbase, + unsigned int gpuaddr, unsigned int size) +{ + struct kgsl_process_private *priv; + struct kgsl_mem_entry *entry; + + mutex_lock(&kgsl_driver.process_mutex); + + list_for_each_entry(priv, &kgsl_driver.process_list, list) { + if (!kgsl_mmu_pt_equal(priv->pagetable, ptbase)) + continue; + spin_lock(&priv->mem_lock); + entry = kgsl_sharedmem_find_region(priv, gpuaddr, size); + + if (entry) { + spin_unlock(&priv->mem_lock); + mutex_unlock(&kgsl_driver.process_mutex); + return entry; + } + spin_unlock(&priv->mem_lock); + } + mutex_unlock(&kgsl_driver.process_mutex); + + return NULL; +} +EXPORT_SYMBOL(kgsl_get_mem_entry); + +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); + + if (entry->memtype != KGSL_MEM_ENTRY_KERNEL) + kgsl_driver.stats.mapped -= entry->memdesc.size; + + /* + * Ion takes care of freeing the sglist for us (how nice ) so + * unmap the dma before freeing the sharedmem so kgsl_sharedmem_free + * doesn't try to free it again + */ + + if (entry->memtype == KGSL_MEM_ENTRY_ION) { + ion_unmap_dma(kgsl_ion_client, entry->priv_data); + entry->memdesc.sg = NULL; + } + + kgsl_sharedmem_free(&entry->memdesc); + + switch (entry->memtype) { + case KGSL_MEM_ENTRY_PMEM: + case KGSL_MEM_ENTRY_ASHMEM: + if (entry->priv_data) + fput(entry->priv_data); + break; + case KGSL_MEM_ENTRY_ION: + ion_free(kgsl_ion_client, entry->priv_data); + break; + } + + 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) +{ + struct rb_node **node; + struct rb_node *parent = NULL; + + spin_lock(&process->mem_lock); + + node = &process->mem_rb.rb_node; + + while (*node) { + struct kgsl_mem_entry *cur; + + parent = *node; + cur = rb_entry(parent, struct kgsl_mem_entry, node); + + if (entry->memdesc.gpuaddr < cur->memdesc.gpuaddr) + node = &parent->rb_left; + else + node = &parent->rb_right; + } + + rb_link_node(&entry->node, parent, node); + rb_insert_color(&entry->node, &process->mem_rb); + + spin_unlock(&process->mem_lock); + + entry->priv = process; +} + +/* Detach a memory entry from a process and unmap it from the MMU */ + +static void kgsl_mem_entry_detach_process(struct kgsl_mem_entry *entry) +{ + if (entry == NULL) + return; + + entry->priv->stats[entry->memtype].cur -= entry->memdesc.size; + entry->priv = NULL; + + kgsl_mmu_unmap(entry->memdesc.pagetable, &entry->memdesc); + + kgsl_mem_entry_put(entry); +} + +/* 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_above(&dev_priv->device->context_idr, + context, 1, &id); + + if (ret != -EAGAIN) + break; + } + + if (ret) { + kfree(context); + return NULL; + } + + /* MAX - 1, there is one memdesc in memstore for device info */ + if (id >= KGSL_MEMSTORE_MAX) { + KGSL_DRV_ERR(dev_priv->device, "cannot have more than %d " + "ctxts due to memstore limitation\n", + KGSL_MEMSTORE_MAX); + idr_remove(&dev_priv->device->context_idr, id); + kfree(context); + return NULL; + } + + kref_init(&context->refcount); + context->id = id; + context->dev_priv = dev_priv; + + return context; +} + +/** + * kgsl_context_detach - Release the "master" context reference + * @context - The context that will be detached + * + * This is called when a context becomes unusable, because userspace + * has requested for it to be destroyed. The context itself may + * exist a bit longer until its reference count goes to zero. + * Other code referencing the context can detect that it has been + * detached because the context id will be set to KGSL_CONTEXT_INVALID. + */ +void +kgsl_context_detach(struct kgsl_context *context) +{ + int id; + struct kgsl_device *device; + if (context == NULL) + return; + device = context->dev_priv->device; + trace_kgsl_context_detach(device, context); + id = context->id; + + if (device->ftbl->drawctxt_destroy) + device->ftbl->drawctxt_destroy(device, context); + /*device specific drawctxt_destroy MUST clean up devctxt */ + BUG_ON(context->devctxt); + /* + * Cancel events after the device-specific context is + * destroyed, to avoid possibly freeing memory while + * it is still in use by the GPU. + */ + kgsl_cancel_events_ctxt(device, context); + idr_remove(&device->context_idr, id); + context->id = KGSL_CONTEXT_INVALID; + kgsl_context_put(context); +} + +void +kgsl_context_destroy(struct kref *kref) +{ + struct kgsl_context *context = container_of(kref, struct kgsl_context, + refcount); + kfree(context); +} + +void kgsl_timestamp_expired(struct work_struct *work) +{ + struct kgsl_device *device = container_of(work, struct kgsl_device, + ts_expired_ws); + struct kgsl_event *event, *event_tmp; + uint32_t ts_processed; + unsigned int id; + + mutex_lock(&device->mutex); + + /* Process expired events */ + list_for_each_entry_safe(event, event_tmp, &device->events, list) { + ts_processed = kgsl_readtimestamp(device, event->context, + KGSL_TIMESTAMP_RETIRED); + if (timestamp_cmp(ts_processed, event->timestamp) < 0) + continue; + + id = event->context ? event->context->id : KGSL_MEMSTORE_GLOBAL; + + if (event->func) + event->func(device, event->priv, id, ts_processed); + + list_del(&event->list); + kfree(event); + } + + device->last_expired_ctxt_id = KGSL_CONTEXT_INVALID; + + mutex_unlock(&device->mutex); +} +EXPORT_SYMBOL(kgsl_timestamp_expired); + +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) { + kgsl_pwrctrl_request_state(device, 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, + struct kgsl_context *context, unsigned int timestamp) +{ + unsigned int ts_processed; + + ts_processed = kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED); + + return (timestamp_cmp(ts_processed, timestamp) >= 0); +} +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; + kgsl_pwrctrl_request_state(device, 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_sync(&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); + if (device->idle_wakelock.name) + wake_unlock(&device->idle_wakelock); + pm_qos_update_request(&device->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + kgsl_pwrctrl_set_state(device, KGSL_STATE_SUSPEND); + break; + case KGSL_STATE_SLUMBER: + INIT_COMPLETION(device->hwaccess_gate); + kgsl_pwrctrl_set_state(device, KGSL_STATE_SUSPEND); + break; + default: + KGSL_PWR_ERR(device, "suspend fail, device %d\n", + device->id); + goto end; + } + kgsl_pwrctrl_request_state(device, 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) { + kgsl_pwrctrl_set_state(device, KGSL_STATE_SLUMBER); + status = 0; + complete_all(&device->hwaccess_gate); + } + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + + mutex_unlock(&device->mutex); + 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_PWR_WARN(device, "early suspend start\n"); + mutex_lock(&device->mutex); + kgsl_pwrctrl_request_state(device, KGSL_STATE_SLUMBER); + kgsl_pwrctrl_sleep(device); + mutex_unlock(&device->mutex); + KGSL_PWR_WARN(device, "early suspend end\n"); +} +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_PWR_WARN(device, "late resume start\n"); + mutex_lock(&device->mutex); + device->pwrctrl.restore_slumber = 0; + if (device->pwrscale.policy == NULL) + kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO); + kgsl_pwrctrl_wake(device); + mutex_unlock(&device->mutex); + kgsl_check_idle(device); + KGSL_PWR_WARN(device, "late resume end\n"); +} +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); + private->mem_rb = RB_ROOT; + + if (kgsl_mmu_enabled()) + { + unsigned long pt_name; + + pt_name = task_tgid_nr(current); + private->pagetable = kgsl_mmu_getpagetable(pt_name); + if (private->pagetable == NULL) { + kfree(private); + private = NULL; + goto out; + } + } + + 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 rb_node *node; + + if (!private) + return; + + mutex_lock(&kgsl_driver.process_mutex); + + if (--private->refcnt) + goto unlock; + + kgsl_process_uninit_sysfs(private); + + list_del(&private->list); + + for (node = rb_first(&private->mem_rb); node; ) { + entry = rb_entry(node, struct kgsl_mem_entry, node); + node = rb_next(&entry->node); + + rb_erase(&entry->node, &private->mem_rb); + kgsl_mem_entry_detach_process(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 = filep->private_data; + struct kgsl_process_private *private = dev_priv->process_priv; + struct kgsl_device *device = dev_priv->device; + struct kgsl_context *context; + int next = 0; + + filep->private_data = NULL; + + mutex_lock(&device->mutex); + kgsl_check_suspended(device); + + while (1) { + context = idr_get_next(&device->context_idr, &next); + if (context == NULL) + break; + + if (context->dev_priv == dev_priv) + kgsl_context_detach(context); + + next = next + 1; + } + /* + * Clean up any to-be-freed entries that belong to this + * process and this device. This is done after the context + * are destroyed to avoid possibly freeing memory while + * it is still in use by the GPU. + */ + kgsl_cancel_events(device, dev_priv); + + device->open_count--; + if (device->open_count == 0) { + result = device->ftbl->stop(device); + kgsl_pwrctrl_set_state(device, KGSL_STATE_INIT); + } + + 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) { + kgsl_sharedmem_set(&device->memstore, 0, 0, + device->memstore.size); + + result = device->ftbl->start(device, true); + + if (result) { + mutex_unlock(&device->mutex); + goto err_putprocess; + } + kgsl_pwrctrl_set_state(device, KGSL_STATE_ACTIVE); + } + 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 */ +struct kgsl_mem_entry * +kgsl_sharedmem_find_region(struct kgsl_process_private *private, + unsigned int gpuaddr, size_t size) +{ + struct rb_node *node = private->mem_rb.rb_node; + + while (node != NULL) { + struct kgsl_mem_entry *entry; + + entry = rb_entry(node, struct kgsl_mem_entry, node); + + + if (kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr, size)) + return entry; + + if (gpuaddr < entry->memdesc.gpuaddr) + node = node->rb_left; + else if (gpuaddr >= + (entry->memdesc.gpuaddr + entry->memdesc.size)) + node = node->rb_right; + else { + return NULL; + } + } + + return NULL; +} +EXPORT_SYMBOL(kgsl_sharedmem_find_region); + +/*call with private->mem_lock locked */ +static inline struct kgsl_mem_entry * +kgsl_sharedmem_find(struct kgsl_process_private *private, unsigned int gpuaddr) +{ + return kgsl_sharedmem_find_region(private, gpuaddr, 1); +} + +/*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; + } + case KGSL_PROP_GPU_RESET_STAT: + { + /* Return reset status of given context and clear it */ + uint32_t id; + struct kgsl_context *context; + + if (param->sizebytes != sizeof(unsigned int)) { + result = -EINVAL; + break; + } + /* We expect the value passed in to contain the context id */ + if (copy_from_user(&id, param->value, + sizeof(unsigned int))) { + result = -EFAULT; + break; + } + context = kgsl_find_context(dev_priv, id); + if (!context) { + result = -EINVAL; + break; + } + /* + * Copy the reset status to value which also serves as + * the out parameter + */ + if (copy_to_user(param->value, &(context->reset_status), + sizeof(unsigned int))) { + result = -EFAULT; + break; + } + /* Clear reset status once its been queried */ + context->reset_status = KGSL_CTX_STAT_NO_ERROR; + break; + } + default: + result = dev_priv->device->ftbl->getproperty( + dev_priv->device, param->type, + param->value, param->sizebytes); + } + + + return result; +} + +static long kgsl_ioctl_device_setproperty(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + /* The getproperty struct is reused for setproperty too */ + struct kgsl_device_getproperty *param = data; + + if (dev_priv->device->ftbl->setproperty) + result = dev_priv->device->ftbl->setproperty( + dev_priv->device, param->type, + param->value, param->sizebytes); + + return result; +} + +static long _device_waittimestamp(struct kgsl_device_private *dev_priv, + struct kgsl_context *context, + unsigned int timestamp, + unsigned int timeout) +{ + int result = 0; + struct kgsl_device *device = dev_priv->device; + unsigned int context_id = context ? context->id : KGSL_MEMSTORE_GLOBAL; + + /* Set the active count so that suspend doesn't do the wrong thing */ + + device->active_cnt++; + + trace_kgsl_waittimestamp_entry(device, context_id, + kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED), + timestamp, timeout); + + result = device->ftbl->waittimestamp(dev_priv->device, + context, timestamp, timeout); + + trace_kgsl_waittimestamp_exit(device, + kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED), + result); + + /* 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 long kgsl_ioctl_device_waittimestamp(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_device_waittimestamp *param = data; + + return _device_waittimestamp(dev_priv, NULL, + param->timestamp, param->timeout); +} + +static long kgsl_ioctl_device_waittimestamp_ctxtid(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_device_waittimestamp_ctxtid *param = data; + struct kgsl_context *context; + int result; + + context = kgsl_find_context(dev_priv, param->context_id); + if (context == NULL) { + KGSL_DRV_ERR(dev_priv->device, "invalid context_id %d\n", + param->context_id); + return -EINVAL; + } + /* + * A reference count is needed here, because waittimestamp may + * block with the device mutex unlocked and userspace could + * request for the context to be destroyed during that time. + */ + kgsl_context_get(context); + result = _device_waittimestamp(dev_priv, context, + param->timestamp, param->timeout); + kgsl_context_put(context); + 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; + + context = kgsl_find_context(dev_priv, param->drawctxt_id); + if (context == NULL) { + result = -EINVAL; + KGSL_DRV_ERR(dev_priv->device, + "invalid context_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; + } + + result = dev_priv->device->ftbl->issueibcmds(dev_priv, + context, + ibdesc, + param->numibs, + ¶m->timestamp, + param->flags); + + trace_kgsl_issueibcmds(dev_priv->device, param, ibdesc, result); + +free_ibdesc: + kfree(ibdesc); +done: + + return result; +} + +static long _cmdstream_readtimestamp(struct kgsl_device_private *dev_priv, + struct kgsl_context *context, unsigned int type, + unsigned int *timestamp) +{ + *timestamp = kgsl_readtimestamp(dev_priv->device, context, type); + + trace_kgsl_readtimestamp(dev_priv->device, + context ? context->id : KGSL_MEMSTORE_GLOBAL, + type, *timestamp); + + return 0; +} + +static long kgsl_ioctl_cmdstream_readtimestamp(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_cmdstream_readtimestamp *param = data; + + return _cmdstream_readtimestamp(dev_priv, NULL, + param->type, ¶m->timestamp); +} + +static long kgsl_ioctl_cmdstream_readtimestamp_ctxtid(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_cmdstream_readtimestamp_ctxtid *param = data; + struct kgsl_context *context; + + context = kgsl_find_context(dev_priv, param->context_id); + if (context == NULL) { + KGSL_DRV_ERR(dev_priv->device, "invalid context_id %d\n", + param->context_id); + return -EINVAL; + } + + return _cmdstream_readtimestamp(dev_priv, context, + param->type, ¶m->timestamp); +} + +static void kgsl_freemem_event_cb(struct kgsl_device *device, + void *priv, u32 id, u32 timestamp) +{ + struct kgsl_mem_entry *entry = priv; + spin_lock(&entry->priv->mem_lock); + rb_erase(&entry->node, &entry->priv->mem_rb); + spin_unlock(&entry->priv->mem_lock); + trace_kgsl_mem_timestamp_free(device, entry, id, timestamp, 0); + kgsl_mem_entry_detach_process(entry); +} + +static long _cmdstream_freememontimestamp(struct kgsl_device_private *dev_priv, + unsigned int gpuaddr, struct kgsl_context *context, + unsigned int timestamp, unsigned int type) +{ + int result = 0; + struct kgsl_mem_entry *entry = NULL; + struct kgsl_device *device = dev_priv->device; + unsigned int context_id = context ? context->id : KGSL_MEMSTORE_GLOBAL; + + spin_lock(&dev_priv->process_priv->mem_lock); + entry = kgsl_sharedmem_find(dev_priv->process_priv, gpuaddr); + spin_unlock(&dev_priv->process_priv->mem_lock); + + if (!entry) { + KGSL_DRV_ERR(dev_priv->device, + "invalid gpuaddr %08x\n", gpuaddr); + result = -EINVAL; + goto done; + } + trace_kgsl_mem_timestamp_queue(device, entry, context_id, + kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED), + timestamp); + result = kgsl_add_event(dev_priv->device, context_id, timestamp, + kgsl_freemem_event_cb, entry, dev_priv); +done: + return result; +} + +static long kgsl_ioctl_cmdstream_freememontimestamp(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_cmdstream_freememontimestamp *param = data; + + return _cmdstream_freememontimestamp(dev_priv, param->gpuaddr, + NULL, param->timestamp, param->type); +} + +static long kgsl_ioctl_cmdstream_freememontimestamp_ctxtid( + struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_cmdstream_freememontimestamp_ctxtid *param = data; + struct kgsl_context *context; + + context = kgsl_find_context(dev_priv, param->context_id); + if (context == NULL) { + KGSL_DRV_ERR(dev_priv->device, + "invalid drawctxt context_id %d\n", param->context_id); + return -EINVAL; + } + + return _cmdstream_freememontimestamp(dev_priv, param->gpuaddr, + context, param->timestamp, param->type); +} + +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); + if (result) + goto done; + } + trace_kgsl_context_create(dev_priv->device, context, param->flags); + param->drawctxt_id = context->id; +done: + if (result && context) + kgsl_context_detach(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; + } + + kgsl_context_detach(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) + rb_erase(&entry->node, &private->mem_rb); + + spin_unlock(&private->mem_lock); + + if (entry) { + trace_kgsl_mem_free(entry); + kgsl_mem_entry_detach_process(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; + + 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 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; + + KGSL_DEV_ERR_ONCE(dev_priv->device, "IOCTL_KGSL_SHAREDMEM_FROM_VMALLOC" + " is deprecated\n"); + if (!kgsl_mmu_enabled()) + return -ENODEV; + + 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; + } + + /* + * If the user specified a length, use it, otherwise try to + * infer the length if the vma region + */ + if (param->gpuaddr != 0) { + len = param->gpuaddr; + } else { + /* + * For this to work, we have to assume the VMA region is only + * for this single allocation. If it isn't, then bail out + */ + if (vma->vm_pgoff || (param->hostptr != vma->vm_start)) { + KGSL_CORE_ERR("VMA region does not match hostaddr\n"); + result = -EINVAL; + goto error; + } + + len = vma->vm_end - vma->vm_start; + } + + /* Make sure it fits */ + if (len == 0 || param->hostptr + len > vma->vm_end) { + KGSL_CORE_ERR("Invalid memory allocation length %d\n", len); + result = -EINVAL; + goto error; + } + + entry = kgsl_mem_entry_create(); + if (entry == NULL) { + result = -ENOMEM; + goto error; + } + + result = kgsl_sharedmem_page_alloc_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 = kgsl_sharedmem_map_vma(vma, &entry->memdesc); + if (result) { + KGSL_CORE_ERR("kgsl_sharedmem_map_vma failed: %d\n", result); + goto error_free_alloc; + } + + param->gpuaddr = entry->memdesc.gpuaddr; + + entry->memtype = KGSL_MEM_ENTRY_KERNEL; + + kgsl_mem_entry_attach_process(entry, private); + + trace_kgsl_mem_alloc(entry); + /* Process specific statistics */ + kgsl_process_add_stats(private, entry->memtype, len); + + kgsl_check_idle(dev_priv->device); + return 0; + +error_free_alloc: + 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); +} + +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; +#ifdef CONFIG_ANDROID_PMEM + if (!get_pmem_file(fd, start, vstart, len, filep)) + return 0; +#endif + + 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 (phys == 0) { + ret = -EINVAL; + goto err; + } + + 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->priv_data = filep; + + entry->memdesc.pagetable = pagetable; + entry->memdesc.size = size; + entry->memdesc.physaddr = phys + (offset & PAGE_MASK); + entry->memdesc.hostptr = (void *) (virt + (offset & PAGE_MASK)); + + ret = memdesc_sg_phys(&entry->memdesc, + phys + (offset & PAGE_MASK), size); + if (ret) + goto err; + + return 0; +err: +#ifdef CONFIG_ANDROID_PMEM + put_pmem_file(filep); +#endif + return ret; +} + +static int memdesc_sg_virt(struct kgsl_memdesc *memdesc, + void *addr, int size) +{ + int i; + int sglen = PAGE_ALIGN(size) / PAGE_SIZE; + unsigned long paddr = (unsigned long) addr; + + memdesc->sg = kgsl_sg_alloc(sglen); + + if (memdesc->sg == NULL) + return -ENOMEM; + + memdesc->sglen = sglen; + sg_init_table(memdesc->sg, sglen); + + spin_lock(¤t->mm->page_table_lock); + + for (i = 0; i < sglen; i++, paddr += PAGE_SIZE) { + struct page *page; + pmd_t *ppmd; + pte_t *ppte; + pgd_t *ppgd = pgd_offset(current->mm, paddr); + + if (pgd_none(*ppgd) || pgd_bad(*ppgd)) + goto err; + + ppmd = pmd_offset(pud_offset(ppgd, paddr), paddr); + if (pmd_none(*ppmd) || pmd_bad(*ppmd)) + goto err; + + ppte = pte_offset_map(ppmd, paddr); + if (ppte == NULL) + goto err; + + page = pfn_to_page(pte_pfn(*ppte)); + if (!page) + goto err; + + sg_set_page(&memdesc->sg[i], page, PAGE_SIZE, 0); + pte_unmap(ppte); + } + + spin_unlock(¤t->mm->page_table_lock); + + return 0; + +err: + spin_unlock(¤t->mm->page_table_lock); + kgsl_sg_free(memdesc->sg, sglen); + memdesc->sg = NULL; + + return -EINVAL; +} + +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); + + return memdesc_sg_virt(&entry->memdesc, + hostptr + (offset & PAGE_MASK), size); +} + +#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; + unsigned int hostaddr = (unsigned int) hostptr; + + vma = kgsl_get_vma_from_start_addr(hostaddr); + if (vma == NULL) + return -EINVAL; + + if (vma->vm_pgoff || vma->vm_start != hostaddr) { + KGSL_CORE_ERR("Invalid vma region\n"); + 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->priv_data = filep; + entry->memdesc.pagetable = pagetable; + entry->memdesc.size = ALIGN(size, PAGE_SIZE); + entry->memdesc.hostptr = hostptr; + + ret = memdesc_sg_virt(&entry->memdesc, hostptr, size); + if (ret) + goto err; + + 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 int kgsl_setup_ion(struct kgsl_mem_entry *entry, + struct kgsl_pagetable *pagetable, int fd) +{ + struct ion_handle *handle; + struct scatterlist *s; + unsigned long flags; + + if (IS_ERR_OR_NULL(kgsl_ion_client)) + return -ENODEV; + + handle = ion_import_fd(kgsl_ion_client, fd); + if (IS_ERR_OR_NULL(handle)) + return PTR_ERR(handle); + + entry->memtype = KGSL_MEM_ENTRY_ION; + entry->priv_data = handle; + entry->memdesc.pagetable = pagetable; + entry->memdesc.size = 0; + + if (ion_handle_get_flags(kgsl_ion_client, handle, &flags)) + goto err; + + entry->memdesc.sg = ion_map_dma(kgsl_ion_client, handle, flags); + + if (IS_ERR_OR_NULL(entry->memdesc.sg)) + goto err; + + /* Calculate the size of the memdesc from the sglist */ + + entry->memdesc.sglen = 0; + + for (s = entry->memdesc.sg; s != NULL; s = sg_next(s)) { + entry->memdesc.size += s->length; + entry->memdesc.sglen++; + } + + return 0; +err: + ion_free(kgsl_ion_client, handle); + return -ENOMEM; +} + +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; + enum kgsl_user_mem_type memtype; + + entry = kgsl_mem_entry_create(); + + if (entry == NULL) + return -ENOMEM; + + if (_IOC_SIZE(cmd) == sizeof(struct kgsl_sharedmem_from_pmem)) + memtype = KGSL_USER_MEM_TYPE_PMEM; + else + memtype = param->memtype; + + switch (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); + entry->memtype = KGSL_MEM_ENTRY_PMEM; + break; + + case KGSL_USER_MEM_TYPE_ADDR: + KGSL_DEV_ERR_ONCE(dev_priv->device, "User mem type " + "KGSL_USER_MEM_TYPE_ADDR is deprecated\n"); + 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); + entry->memtype = KGSL_MEM_ENTRY_USER; + 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); + + entry->memtype = KGSL_MEM_ENTRY_ASHMEM; + break; + case KGSL_USER_MEM_TYPE_ION: + result = kgsl_setup_ion(entry, private->pagetable, + param->fd); + break; + default: + KGSL_CORE_ERR("Invalid memory type: %x\n", 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); + + KGSL_STATS_ADD(param->len, kgsl_driver.stats.mapped, + kgsl_driver.stats.mapped_max); + + kgsl_process_add_stats(private, entry->memtype, param->len); + + kgsl_mem_entry_attach_process(entry, private); + trace_kgsl_mem_map(entry, param->fd); + + kgsl_check_idle(dev_priv->device); + return result; + +error_put_file_ptr: + switch (entry->memtype) { + case KGSL_MEM_ENTRY_PMEM: + case KGSL_MEM_ENTRY_ASHMEM: + if (entry->priv_data) + fput(entry->priv_data); + break; + case KGSL_MEM_ENTRY_ION: + ion_unmap_dma(kgsl_ion_client, entry->priv_data); + ion_free(kgsl_ion_client, entry->priv_data); + break; + default: + break; + } +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; + goto done; + } + 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); +done: + spin_unlock(&private->mem_lock); + 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; + + result = kgsl_allocate_user(&entry->memdesc, private->pagetable, + param->size, param->flags); + + if (result == 0) { + entry->memtype = KGSL_MEM_ENTRY_KERNEL; + kgsl_mem_entry_attach_process(entry, private); + param->gpuaddr = entry->memdesc.gpuaddr; + + kgsl_process_add_stats(private, entry->memtype, param->size); + trace_kgsl_mem_alloc(entry); + } else + kfree(entry); + + kgsl_check_idle(dev_priv->device); + return result; +} +static long kgsl_ioctl_cff_syncmem(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_cff_syncmem *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_region(private, param->gpuaddr, param->len); + if (entry) + kgsl_cffdump_syncmem(dev_priv, &entry->memdesc, param->gpuaddr, + param->len, true); + else + result = -EINVAL; + spin_unlock(&private->mem_lock); + return result; +} + +static long kgsl_ioctl_cff_user_event(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_cff_user_event *param = data; + + kgsl_cffdump_user_event(param->cff_opcode, param->op1, param->op2, + param->op3, param->op4, param->op5); + + return result; +} + +#ifdef CONFIG_GENLOCK +struct kgsl_genlock_event_priv { + struct genlock_handle *handle; + struct genlock *lock; +}; + +/** + * kgsl_genlock_event_cb - Event callback for a genlock timestamp event + * @device - The KGSL device that expired the timestamp + * @priv - private data for the event + * @context_id - the context id that goes with the timestamp + * @timestamp - the timestamp that triggered the event + * + * Release a genlock lock following the expiration of a timestamp + */ + +static void kgsl_genlock_event_cb(struct kgsl_device *device, + void *priv, u32 context_id, u32 timestamp) +{ + struct kgsl_genlock_event_priv *ev = priv; + int ret; + + ret = genlock_lock(ev->handle, GENLOCK_UNLOCK, 0, 0); + if (ret) + KGSL_CORE_ERR("Error while unlocking genlock: %d\n", ret); + + genlock_put_handle(ev->handle); + + kfree(ev); +} + +/** + * kgsl_add_genlock-event - Create a new genlock event + * @device - KGSL device to create the event on + * @timestamp - Timestamp to trigger the event + * @data - User space buffer containing struct kgsl_genlock_event_priv + * @len - length of the userspace buffer + * @owner - driver instance that owns this event + * @returns 0 on success or error code on error + * + * Attack to a genlock handle and register an event to release the + * genlock lock when the timestamp expires + */ + +static int kgsl_add_genlock_event(struct kgsl_device *device, + u32 context_id, u32 timestamp, void __user *data, int len, + struct kgsl_device_private *owner) +{ + struct kgsl_genlock_event_priv *event; + struct kgsl_timestamp_event_genlock priv; + int ret; + + if (len != sizeof(priv)) + return -EINVAL; + + if (copy_from_user(&priv, data, sizeof(priv))) + return -EFAULT; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + + if (event == NULL) + return -ENOMEM; + + event->handle = genlock_get_handle_fd(priv.handle); + + if (IS_ERR(event->handle)) { + int ret = PTR_ERR(event->handle); + kfree(event); + return ret; + } + + ret = kgsl_add_event(device, context_id, timestamp, + kgsl_genlock_event_cb, event, owner); + if (ret) + kfree(event); + + return ret; +} +#else +static long kgsl_add_genlock_event(struct kgsl_device *device, + u32 context_id, u32 timestamp, void __user *data, int len, + struct kgsl_device_private *owner) +{ + return -EINVAL; +} +#endif + +/** + * kgsl_ioctl_timestamp_event - Register a new timestamp event from userspace + * @dev_priv - pointer to the private device structure + * @cmd - the ioctl cmd passed from kgsl_ioctl + * @data - the user data buffer from kgsl_ioctl + * @returns 0 on success or error code on failure + */ + +static long kgsl_ioctl_timestamp_event(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + struct kgsl_timestamp_event *param = data; + int ret; + + switch (param->type) { + case KGSL_TIMESTAMP_EVENT_GENLOCK: + ret = kgsl_add_genlock_event(dev_priv->device, + param->context_id, param->timestamp, param->priv, + param->len, dev_priv); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +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_DEVICE_WAITTIMESTAMP_CTXTID, + kgsl_ioctl_device_waittimestamp_ctxtid, 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_READTIMESTAMP_CTXTID, + kgsl_ioctl_cmdstream_readtimestamp_ctxtid, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP, + kgsl_ioctl_cmdstream_freememontimestamp, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_CTXTID, + kgsl_ioctl_cmdstream_freememontimestamp_ctxtid, 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), + KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_SYNCMEM, + kgsl_ioctl_cff_syncmem, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_USER_EVENT, + kgsl_ioctl_cff_user_event, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_TIMESTAMP_EVENT, + kgsl_ioctl_timestamp_event, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_SETPROPERTY, + kgsl_ioctl_device_setproperty, 1), +}; + +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; + else if (cmd == IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_OLD) + cmd = IOCTL_KGSL_CMDSTREAM_READTIMESTAMP; + + 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 = -ENOIOCTLCMD; + 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; +} + +/* + * kgsl_gpumem_vm_open is called whenever a vma region is copied or split. + * Increase the refcount to make sure that the accounting stays correct + */ + +static void kgsl_gpumem_vm_open(struct vm_area_struct *vma) +{ + struct kgsl_mem_entry *entry = vma->vm_private_data; + kgsl_mem_entry_get(entry); +} + +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 || !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 = { + .open = kgsl_gpumem_vm_open, + .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 kgsl_device_private *dev_priv = file->private_data; + struct kgsl_process_private *private = dev_priv->process_priv; + struct kgsl_mem_entry *entry = NULL; + struct kgsl_device *device = dev_priv->device; + + /* 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); + entry = kgsl_sharedmem_find(private, vma_offset); + + if (entry) + kgsl_mem_entry_get(entry); + + spin_unlock(&private->mem_lock); + + if (entry == NULL) + return -EINVAL; + + if (!entry->memdesc.ops || + !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 irqreturn_t kgsl_irq_handler(int irq, void *data) +{ + struct kgsl_device *device = data; + + return device->ftbl->irq_handler(device); + +} + +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); + +static void _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; + } + if (minor != KGSL_DEVICE_MAX) { + device_destroy(kgsl_driver.class, + MKDEV(MAJOR(kgsl_driver.major), minor)); + kgsl_driver.devp[minor] = NULL; + } + mutex_unlock(&kgsl_driver.devlock); +} + +static int _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)) { + mutex_lock(&kgsl_driver.devlock); + kgsl_driver.devp[minor] = NULL; + mutex_unlock(&kgsl_driver.devlock); + ret = PTR_ERR(device->dev); + KGSL_CORE_ERR("device_create(%s): %d\n", device->name, ret); + return ret; + } + + dev_set_drvdata(device->parentdev, device); + return 0; +} + +int kgsl_device_platform_probe(struct kgsl_device *device) +{ + int result; + int status = -EINVAL; + struct resource *res; + struct platform_device *pdev = + container_of(device->parentdev, struct platform_device, dev); + + status = _register_device(device); + if (status) + return status; + + /* Initialize logging first, so that failures below actually print. */ + kgsl_device_debugfs_init(device); + + status = kgsl_pwrctrl_init(device); + if (status) + goto error; + + kgsl_ion_client = msm_ion_client_create(UINT_MAX, KGSL_NAME); + + 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 register region\n", + device->id); + status = -EINVAL; + goto error_pwrctrl_close; + } + + device->reg_phys = res->start; + device->reg_len = resource_size(res); + + if (!devm_request_mem_region(device->dev, device->reg_phys, + device->reg_len, device->name)) { + KGSL_DRV_ERR(device, "request_mem_region failed\n"); + status = -ENODEV; + goto error_pwrctrl_close; + } + + device->reg_virt = devm_ioremap(device->dev, device->reg_phys, + device->reg_len); + + if (device->reg_virt == NULL) { + KGSL_DRV_ERR(device, "ioremap failed\n"); + status = -ENODEV; + goto error_pwrctrl_close; + } + /*acquire interrupt */ + device->pwrctrl.interrupt_num = + platform_get_irq_byname(pdev, device->pwrctrl.irq_name); + + if (device->pwrctrl.interrupt_num <= 0) { + KGSL_DRV_ERR(device, "platform_get_irq_byname failed: %d\n", + device->pwrctrl.interrupt_num); + status = -EINVAL; + goto error_pwrctrl_close; + } + + status = devm_request_irq(device->dev, device->pwrctrl.interrupt_num, + kgsl_irq_handler, 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_pwrctrl_close; + } + disable_irq(device->pwrctrl.interrupt_num); + + KGSL_DRV_INFO(device, + "dev_id %d regs phys 0x%08lx size 0x%08x virt %p\n", + device->id, device->reg_phys, device->reg_len, + device->reg_virt); + + result = kgsl_drm_init(pdev); + if (result) + goto error_pwrctrl_close; + + kgsl_cffdump_open(device->id); + + setup_timer(&device->idle_timer, kgsl_timer, (unsigned long) device); + status = kgsl_create_device_workqueue(device); + if (status) + goto error_pwrctrl_close; + + status = kgsl_mmu_init(device); + if (status != 0) { + KGSL_DRV_ERR(device, "kgsl_mmu_init failed %d\n", status); + goto error_dest_work_q; + } + + status = kgsl_allocate_contiguous(&device->memstore, + sizeof(struct kgsl_devmemstore)); + + if (status != 0) { + KGSL_DRV_ERR(device, "kgsl_allocate_contiguous failed %d\n", + status); + goto error_close_mmu; + } + + 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); + + /* Initalize the snapshot engine */ + kgsl_device_snapshot_init(device); + + /* Initialize common sysfs entries */ + kgsl_pwrctrl_init_sysfs(device); + + return 0; + +error_close_mmu: + kgsl_mmu_close(device); +error_dest_work_q: + destroy_workqueue(device->work_queue); + device->work_queue = NULL; +error_pwrctrl_close: + kgsl_pwrctrl_close(device); +error: + _unregister_device(device); + return status; +} +EXPORT_SYMBOL(kgsl_device_platform_probe); + +void kgsl_device_platform_remove(struct kgsl_device *device) +{ + kgsl_device_snapshot_close(device); + + 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); + + kgsl_sharedmem_free(&device->memstore); + + kgsl_mmu_close(device); + + if (device->work_queue) { + destroy_workqueue(device->work_queue); + device->work_queue = NULL; + } + kgsl_pwrctrl_close(device); + + _unregister_device(device); +} +EXPORT_SYMBOL(kgsl_device_platform_remove); + +static int __devinit +kgsl_ptdata_init(void) +{ + kgsl_driver.ptpool = kgsl_mmu_ptpool_init(kgsl_pagetable_count); + + if (!kgsl_driver.ptpool) + return -ENOMEM; + return 0; +} + +static void kgsl_core_exit(void) +{ + kgsl_mmu_ptpool_destroy(kgsl_driver.ptpool); + kgsl_driver.ptpool = NULL; + + kgsl_drm_exit(); + kgsl_cffdump_destroy(); + kgsl_core_debugfs_close(); + kgsl_sharedmem_uninit_sysfs(); + + device_unregister(&kgsl_driver.virtdev); + + if (kgsl_driver.class) { + class_destroy(kgsl_driver.class); + kgsl_driver.class = NULL; + } + + unregister_chrdev_region(kgsl_driver.major, KGSL_DEVICE_MAX); +} + +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(); + + INIT_LIST_HEAD(&kgsl_driver.process_list); + + INIT_LIST_HEAD(&kgsl_driver.pagetable_list); + + kgsl_mmu_set_mmutype(ksgl_mmu_type); + + if (KGSL_MMU_TYPE_GPU == kgsl_mmu_get_mmutype()) { + result = kgsl_ptdata_init(); + 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 0000000000000000000000000000000000000000..b67f4606796a0a36cf087da0206748a52d758e60 --- /dev/null +++ b/drivers/gpu/msm/kgsl.h @@ -0,0 +1,266 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include + +#define KGSL_NAME "kgsl" + +/* The number of memstore arrays limits the number of contexts allowed. + * If more contexts are needed, update multiple for MEMSTORE_SIZE + */ +#define KGSL_MEMSTORE_SIZE ((int)(PAGE_SIZE * 2)) +#define KGSL_MEMSTORE_GLOBAL (0) +#define KGSL_MEMSTORE_MAX (KGSL_MEMSTORE_SIZE / \ + sizeof(struct kgsl_devmemstore) - 1) + +/* Timestamp window used to detect rollovers (half of integer range) */ +#define KGSL_TIMESTAMP_WINDOW 0x80000000 + +/*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 0x10000000 + +/* 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_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_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]; + + /* 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; + + void *ptpool; + + struct { + unsigned int vmalloc; + unsigned int vmalloc_max; + unsigned int page_alloc; + unsigned int page_alloc_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; + +struct kgsl_pagetable; +struct kgsl_memdesc; + +struct kgsl_memdesc_ops { + int (*vmflags)(struct kgsl_memdesc *); + int (*vmfault)(struct kgsl_memdesc *, struct vm_area_struct *, + struct vm_fault *); + void (*free)(struct kgsl_memdesc *memdesc); + int (*map_kernel_mem)(struct kgsl_memdesc *); +}; + +#define KGSL_MEMDESC_GUARD_PAGE BIT(0) + +/* 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 scatterlist *sg; + unsigned int sglen; + struct kgsl_memdesc_ops *ops; + int flags; +}; + +/* List of different memory entry types */ + +#define KGSL_MEM_ENTRY_KERNEL 0 +#define KGSL_MEM_ENTRY_PMEM 1 +#define KGSL_MEM_ENTRY_ASHMEM 2 +#define KGSL_MEM_ENTRY_USER 3 +#define KGSL_MEM_ENTRY_ION 4 +#define KGSL_MEM_ENTRY_MAX 5 + +/* List of flags */ + +#define KGSL_MEM_ENTRY_FROZEN (1 << 0) + +struct kgsl_mem_entry { + struct kref refcount; + struct kgsl_memdesc memdesc; + int memtype; + int flags; + void *priv_data; + struct rb_node node; + unsigned int context_id; + /* 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); + +struct kgsl_mem_entry *kgsl_get_mem_entry(unsigned int ptbase, + 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); +#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, unsigned int size) +{ + if (gpuaddr >= memdesc->gpuaddr && + ((gpuaddr + size) <= (memdesc->gpuaddr + memdesc->size))) { + return 1; + } + return 0; +} + +static inline void *kgsl_memdesc_map(struct kgsl_memdesc *memdesc) +{ + if (memdesc->hostptr == NULL && memdesc->ops->map_kernel_mem) + memdesc->ops->map_kernel_mem(memdesc); + + return memdesc->hostptr; +} + +static inline uint8_t *kgsl_gpuaddr_to_vaddr(struct kgsl_memdesc *memdesc, + unsigned int gpuaddr) +{ + void *hostptr = NULL; + + if ((gpuaddr >= memdesc->gpuaddr) && + (gpuaddr < (memdesc->gpuaddr + memdesc->size))) + hostptr = kgsl_memdesc_map(memdesc); + + return hostptr != NULL ? hostptr + (gpuaddr - memdesc->gpuaddr) : NULL; +} + +static inline int timestamp_cmp(unsigned int a, unsigned int b) +{ + /* check for equal */ + if (a == b) + return 0; + + /* check for greater-than for non-rollover case */ + if ((a > b) && (a - b < KGSL_TIMESTAMP_WINDOW)) + return 1; + + /* check for greater-than for rollover case + * note that <= is required to ensure that consistent + * results are returned for values whose difference is + * equal to the window size + */ + a += KGSL_TIMESTAMP_WINDOW; + b += KGSL_TIMESTAMP_WINDOW; + return ((a > b) && (a - b <= KGSL_TIMESTAMP_WINDOW)) ? 1 : -1; +} + +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 0000000000000000000000000000000000000000..4e354d082637195f9f2d18db7973fd93b6124d5f --- /dev/null +++ b/drivers/gpu/msm/kgsl_cffdump.c @@ -0,0 +1,591 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#include "kgsl.h" +#include "kgsl_cffdump.h" +#include "kgsl_debugfs.h" +#include "kgsl_log.h" +#include "kgsl_sharedmem.h" +#include "adreno_pm4types.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; +} __packed; + +#define CFF_OP_POLL_REG 0x00000004 +struct cff_op_poll_reg { + unsigned char op; + uint addr; + uint value; + uint mask; +} __packed; + +#define CFF_OP_WAIT_IRQ 0x00000005 +struct cff_op_wait_irq { + unsigned char op; +} __packed; + +#define CFF_OP_RMW 0x0000000a + +#define CFF_OP_WRITE_MEM 0x0000000b +struct cff_op_write_mem { + unsigned char op; + uint addr; + uint value; +} __packed; + +#define CFF_OP_WRITE_MEMBUF 0x0000000c +struct cff_op_write_membuf { + unsigned char op; + uint addr; + ushort count; + uint buffer[MEMBUF_SIZE]; +} __packed; + +#define CFF_OP_MEMORY_BASE 0x0000000d +struct cff_op_memory_base { + unsigned char op; + uint base; + uint size; + uint gmemsize; +} __packed; + +#define CFF_OP_HANG 0x0000000e +struct cff_op_hang { + unsigned char op; +} __packed; + +#define CFF_OP_EOF 0xffffffff +struct cff_op_eof { + unsigned char op; +} __packed; + +#define CFF_OP_VERIFY_MEM_FILE 0x00000007 +#define CFF_OP_WRITE_SURFACE_PARAMS 0x00000011 +struct cff_op_user_event { + unsigned char op; + unsigned int op1; + unsigned int op2; + unsigned int op3; + unsigned int op4; + unsigned int op5; +} __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, uint op4, uint op5) +{ + 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_memory_base cff_op_memory_base; + struct cff_op_hang cff_op_hang; + struct cff_op_eof cff_op_eof; + struct cff_op_user_event cff_op_user_event; + 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 ((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_MEMORY_BASE: + cff_op_memory_base.op = opcode; + cff_op_memory_base.base = op1; + cff_op_memory_base.size = op2; + cff_op_memory_base.gmemsize = op3; + data = &cff_op_memory_base; + len = sizeof(cff_op_memory_base); + break; + + case CFF_OP_HANG: + cff_op_hang.op = opcode; + data = &cff_op_hang; + len = sizeof(cff_op_hang); + break; + + case CFF_OP_EOF: + cff_op_eof.op = opcode; + data = &cff_op_eof; + len = sizeof(cff_op_eof); + break; + + case CFF_OP_WRITE_SURFACE_PARAMS: + case CFF_OP_VERIFY_MEM_FILE: + cff_op_user_event.op = opcode; + cff_op_user_event.op1 = op1; + cff_op_user_event.op2 = op2; + cff_op_user_event.op3 = op3; + cff_op_user_event.op4 = op4; + cff_op_user_event.op5 = op5; + data = &cff_op_user_event; + len = sizeof(cff_op_user_event); + 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(0, &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) +{ + kgsl_cffdump_memory_base(device_id, KGSL_PAGETABLE_BASE, + kgsl_mmu_get_ptsize(), SZ_256K); +} + +void kgsl_cffdump_memory_base(enum kgsl_deviceid device_id, unsigned int base, + unsigned int range, unsigned gmemsize) +{ + cffdump_printline(device_id, CFF_OP_MEMORY_BASE, base, + range, gmemsize, 0, 0); +} + +void kgsl_cffdump_hang(enum kgsl_deviceid device_id) +{ + cffdump_printline(device_id, CFF_OP_HANG, 0, 0, 0, 0, 0); +} + +void kgsl_cffdump_close(enum kgsl_deviceid device_id) +{ + cffdump_printline(device_id, CFF_OP_EOF, 0, 0, 0, 0, 0); +} + +void kgsl_cffdump_user_event(unsigned int cff_opcode, unsigned int op1, + unsigned int op2, unsigned int op3, + unsigned int op4, unsigned int op5) +{ + cffdump_printline(-1, cff_opcode, op1, op2, op3, op4, op5); +} + +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; + + 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; + } + src = (uint *)kgsl_gpuaddr_to_vaddr(memdesc, gpuaddr); + if (memdesc->hostptr == NULL) { + KGSL_CORE_ERR("no kernel 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((struct kgsl_memdesc *)memdesc, + KGSL_CACHE_OP_INV); + } + + while (sizebytes > 3) { + cffdump_printline(-1, CFF_OP_WRITE_MEM, gpuaddr, *(uint *)src, + 0, 0, 0); + gpuaddr += 4; + src += 4; + sizebytes -= 4; + } + if (sizebytes > 0) + cffdump_printline(-1, CFF_OP_WRITE_MEM, gpuaddr, *(uint *)src, + 0, 0, 0); +} + +void kgsl_cffdump_setmem(uint addr, uint value, uint sizebytes) +{ + if (!kgsl_cff_dump_enable) + return; + + 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, 0, 0); + addr += 4; + sizebytes -= 4; + } + if (sizebytes > 0) + cffdump_printline(-1, CFF_OP_WRITE_MEM, addr, value, + 0, 0, 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, 0, 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, 0, 0); +} + +void kgsl_cffdump_slavewrite(uint addr, uint value) +{ + if (!kgsl_cff_dump_enable) + return; + + cffdump_printline(-1, CFF_OP_WRITE_REG, addr, value, 0, 0, 0); +} + +int kgsl_cffdump_waitirq(void) +{ + if (!kgsl_cff_dump_enable) + return 0; + + cffdump_printline(-1, CFF_OP_WAIT_IRQ, 0, 0, 0, 0, 0); + + return 1; +} +EXPORT_SYMBOL(kgsl_cffdump_waitirq); + +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 0000000000000000000000000000000000000000..140e4868e0ce13774158cdf698451ed151148a6b --- /dev/null +++ b/drivers/gpu/msm/kgsl_cffdump.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 __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); +void kgsl_cffdump_user_event(unsigned int cff_opcode, unsigned int op1, + unsigned int op2, unsigned int op3, + unsigned int op4, unsigned int op5); +static inline bool kgsl_cffdump_flags_no_memzero(void) { return true; } + +void kgsl_cffdump_memory_base(enum kgsl_deviceid device_id, unsigned int base, + unsigned int range, unsigned int gmemsize); + +void kgsl_cffdump_hang(enum kgsl_deviceid device_id); + +#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 +#define kgsl_cffdump_memory_base(base, range, gmemsize) (void)0 +#define kgsl_cffdump_hang(device_id) (void)0 +#define kgsl_cffdump_user_event(cff_opcode, op1, op2, op3, op4, op5) \ + (void)param + +#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 0000000000000000000000000000000000000000..328dd95e4de20aa0fca3d550d562b6af3258613d --- /dev/null +++ b/drivers/gpu/msm/kgsl_debugfs.c @@ -0,0 +1,88 @@ +/* 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 "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); +} + +void kgsl_core_debugfs_close(void) +{ + debugfs_remove_recursive(kgsl_debugfs_dir); +} diff --git a/drivers/gpu/msm/kgsl_debugfs.h b/drivers/gpu/msm/kgsl_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..5e10988076defe627360ec45422a2ce837cd290b --- /dev/null +++ b/drivers/gpu/msm/kgsl_debugfs.h @@ -0,0 +1,39 @@ +/* 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_core_debugfs_close(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 void kgsl_core_debugfs_close(void) { } +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 0000000000000000000000000000000000000000..203f0c9829058fdfd4e90e1d2cdb8d50235aa8f0 --- /dev/null +++ b/drivers/gpu/msm/kgsl_device.h @@ -0,0 +1,419 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_STATE_SLUMBER 0x00000080 + +#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, + struct kgsl_context *context, unsigned int timestamp, + unsigned int msecs); + unsigned int (*readtimestamp) (struct kgsl_device *device, + struct kgsl_context *context, 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); + void (*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); + unsigned int (*gpuid)(struct kgsl_device *device); + void * (*snapshot)(struct kgsl_device *device, void *snapshot, + int *remain, int hang); + irqreturn_t (*irq_handler)(struct kgsl_device *device); + /* 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); + int (*setproperty) (struct kgsl_device *device, + enum kgsl_property_type type, void *value, + unsigned int sizebytes); +}; + +/* MH register values */ +struct kgsl_mh { + unsigned int mharb; + unsigned int mh_intf_cfg1; + unsigned int mh_intf_cfg2; + uint32_t mpu_base; + int mpu_range; +}; + +struct kgsl_event { + struct kgsl_context *context; + uint32_t timestamp; + void (*func)(struct kgsl_device *, void *, u32, u32); + void *priv; + struct list_head list; + struct kgsl_device_private *owner; +}; + + +struct kgsl_device { + struct device *dev; + const char *name; + unsigned int ver_major; + unsigned int ver_minor; + uint32_t flags; + enum kgsl_deviceid id; + unsigned long reg_phys; + void *reg_virt; + unsigned int reg_len; + struct kgsl_memdesc memstore; + const char *iomemname; + + struct kgsl_mh mh; + 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; + + unsigned int last_expired_ctxt_id; + 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; + + void *snapshot; /* Pointer to the snapshot memory region */ + int snapshot_maxsize; /* Max size of the snapshot region */ + int snapshot_size; /* Current size of the snapshot region */ + u32 snapshot_timestamp; /* Timestamp of the last valid snapshot */ + int snapshot_frozen; /* 1 if the snapshot output is frozen until + it gets read by the user. This avoids + losing the output on multiple hangs */ + struct kobject snapshot_kobj; + + /* + * List of GPU buffers that have been frozen in memory until they can be + * dumped + */ + struct list_head snapshot_obj_list; + + /* 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 pm_qos_req_dma; + struct work_struct ts_expired_ws; + struct list_head events; + s64 on_time; +}; + +void kgsl_timestamp_expired(struct work_struct *work); + +#define KGSL_DEVICE_COMMON_INIT(_dev) \ + .hwaccess_gate = COMPLETION_INITIALIZER((_dev).hwaccess_gate),\ + .suspend_gate = COMPLETION_INITIALIZER((_dev).suspend_gate),\ + .recovery_gate = COMPLETION_INITIALIZER((_dev).recovery_gate),\ + .ts_notifier_list = ATOMIC_NOTIFIER_INIT((_dev).ts_notifier_list),\ + .idle_check_ws = __WORK_INITIALIZER((_dev).idle_check_ws,\ + kgsl_idle_check),\ + .ts_expired_ws = __WORK_INITIALIZER((_dev).ts_expired_ws,\ + kgsl_timestamp_expired),\ + .context_idr = IDR_INIT((_dev).context_idr),\ + .events = LIST_HEAD_INIT((_dev).events),\ + .wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER((_dev).wait_queue),\ + .mutex = __MUTEX_INITIALIZER((_dev).mutex),\ + .state = KGSL_STATE_INIT,\ + .ver_major = DRIVER_VERSION_MAJOR,\ + .ver_minor = DRIVER_VERSION_MINOR,\ + .last_expired_ctxt_id = KGSL_CONTEXT_INVALID + +struct kgsl_context { + struct kref refcount; + uint32_t id; + + /* Pointer to the owning device instance */ + struct kgsl_device_private *dev_priv; + + /* Pointer to the device specific context information */ + void *devctxt; + /* + * Status indicating whether a gpu reset occurred and whether this + * context was responsible for causing it + */ + unsigned int reset_status; +}; + +struct kgsl_process_private { + unsigned int refcnt; + pid_t pid; + spinlock_t mem_lock; + struct rb_root mem_rb; + struct kgsl_pagetable *pagetable; + struct list_head list; + struct kobject kobj; + + struct { + unsigned int cur; + unsigned int max; + } stats[KGSL_MEM_ENTRY_MAX]; +}; + +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_process_add_stats(struct kgsl_process_private *priv, + unsigned int type, size_t size) +{ + priv->stats[type].cur += size; + if (priv->stats[type].max < priv->stats[type].cur) + priv->stats[type].max = priv->stats[type].cur; +} + +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 unsigned int kgsl_gpuid(struct kgsl_device *device) +{ + return device->ftbl->gpuid(device); +} + +static inline unsigned int kgsl_readtimestamp(struct kgsl_device *device, + struct kgsl_context *context, + enum kgsl_timestamp_type type) +{ + return device->ftbl->readtimestamp(device, context, type); +} + +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_singlethread_workqueue(device->name); + if (!device->work_queue) { + KGSL_DRV_ERR(device, + "create_singlethread_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, + struct kgsl_context *context, 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); + +void kgsl_device_platform_remove(struct kgsl_device *device); + +const char *kgsl_pwrstate_to_str(unsigned int state); + +int kgsl_device_snapshot_init(struct kgsl_device *device); +int kgsl_device_snapshot(struct kgsl_device *device, int hang); +void kgsl_device_snapshot_close(struct kgsl_device *device); + +static inline struct kgsl_device_platform_data * +kgsl_device_get_drvdata(struct kgsl_device *dev) +{ + struct platform_device *pdev = + container_of(dev->parentdev, struct platform_device, dev); + + return pdev->dev.platform_data; +} + +/** + * kgsl_context_get - Get context reference count + * @context + * + * Asynchronous code that holds a pointer to a context + * must hold a reference count on it. The kgsl device + * mutex must be held while the context reference count + * is changed. + */ +static inline void +kgsl_context_get(struct kgsl_context *context) +{ + kref_get(&context->refcount); +} + +void kgsl_context_destroy(struct kref *kref); + +/** + * kgsl_context_put - Release context reference count + * @context + * + */ +static inline void +kgsl_context_put(struct kgsl_context *context) +{ + kref_put(&context->refcount, kgsl_context_destroy); +} + +#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 0000000000000000000000000000000000000000..66ac08f61c27d1ec57d9a8fbd2acc7d00d367d17 --- /dev/null +++ b/drivers/gpu/msm/kgsl_drm.c @@ -0,0 +1,1508 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "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_NOT_INITED -1 +#define DRM_KGSL_INITED 1 + +#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; +}; + +static int kgsl_drm_inited = DRM_KGSL_NOT_INITED; + +/* 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); +} + +/* 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]; +}; + +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; + int result = 0; + + /* Return if the memory is already allocated */ + + if (kgsl_gem_memory_allocated(obj) || TYPE_IS_FD(priv->type)) + return 0; + + 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; + } + } + + 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; + result = kgsl_sharedmem_ebimem_user( + &priv->memdesc, + priv->pagetable, + obj->size * priv->bufcount, + 0); + if (result) { + DRM_ERROR( + "Unable to allocate PMEM memory\n"); + return result; + } + } + else + return -EINVAL; + + } else if (TYPE_IS_MEM(priv->type)) { + + if (priv->type == DRM_KGSL_GEM_TYPE_KMEM || + priv->type & DRM_KGSL_GEM_CACHE_MASK) + list_add(&priv->list, &kgsl_mem_list); + + result = kgsl_sharedmem_page_alloc_user(&priv->memdesc, + priv->pagetable, + obj->size * priv->bufcount, 0); + + if (result != 0) { + DRM_ERROR( + "Unable to allocate Vmalloc user memory\n"); + return result; + } + } else + return -EINVAL; + + for (index = 0; index < priv->bufcount; index++) { + priv->bufs[index].offset = index * obj->size; + priv->bufs[index].gpuaddr = + priv->memdesc.gpuaddr + + priv->bufs[index].offset; + } + priv->flags |= DRM_KGSL_GEM_FLAG_MAPPED; + + return 0; +} + +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_sharedmem_free(&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; + +} + +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 get 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_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) +{ + return 0; +} + +int +kgsl_gem_bind_gpu_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return 0; +} + +/* 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; + struct page *page; + int i; + + mutex_lock(&dev->struct_mutex); + + priv = obj->driver_private; + + offset = (unsigned long) vmf->virtual_address - vma->vm_start; + i = offset >> PAGE_SHIFT; + page = sg_page(&(priv->memdesc.sg[i])); + + 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 */ +} + +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, NULL, 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_GEM, + .load = kgsl_drm_load, + .unload = kgsl_drm_unload, + .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; + + /* Only initialize once */ + if (kgsl_drm_inited == DRM_KGSL_INITED) + return 0; + + kgsl_drm_inited = DRM_KGSL_INITED; + + driver.num_ioctls = DRM_ARRAY_SIZE(kgsl_drm_ioctls); + + 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_platform_init(&driver, dev); +} + +void kgsl_drm_exit(void) +{ + kgsl_drm_inited = DRM_KGSL_NOT_INITED; + drm_platform_exit(&driver, driver.kdriver.platform_device); +} diff --git a/drivers/gpu/msm/kgsl_gpummu.c b/drivers/gpu/msm/kgsl_gpummu.c new file mode 100644 index 0000000000000000000000000000000000000000..429d035bd4ffaa2b0795be8f01019b512ce90c79 --- /dev/null +++ b/drivers/gpu/msm/kgsl_gpummu.c @@ -0,0 +1,745 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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" +#include "kgsl_trace.h" + +#define KGSL_PAGETABLE_SIZE \ + ALIGN(KGSL_PAGETABLE_ENTRIES(CONFIG_MSM_KGSL_PAGE_TABLE_SIZE) * \ + KGSL_PAGETABLE_ENTRY_SIZE, PAGE_SIZE) + +static ssize_t +sysfs_show_ptpool_entries(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_ptpool *pool = (struct kgsl_ptpool *) + kgsl_driver.ptpool; + return snprintf(buf, PAGE_SIZE, "%d\n", pool->entries); +} + +static ssize_t +sysfs_show_ptpool_min(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_ptpool *pool = (struct kgsl_ptpool *) + kgsl_driver.ptpool; + return snprintf(buf, PAGE_SIZE, "%d\n", + pool->static_entries); +} + +static ssize_t +sysfs_show_ptpool_chunks(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_ptpool *pool = (struct kgsl_ptpool *) + kgsl_driver.ptpool; + return snprintf(buf, PAGE_SIZE, "%d\n", pool->chunks); +} + +static ssize_t +sysfs_show_ptpool_ptsize(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_ptpool *pool = (struct kgsl_ptpool *) + kgsl_driver.ptpool; + return snprintf(buf, PAGE_SIZE, "%d\n", pool->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. + */ + +static 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 + */ + +static 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 + */ + +static 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_gpummu_ptpool_destroy(void *ptpool) +{ + struct kgsl_ptpool *pool = (struct kgsl_ptpool *)ptpool; + 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); + + kfree(pool); +} + +/** + * kgsl_ptpool_init + * @pool: A pointer to a ptpool structure to initialize + * @entries: The number of inital entries to add to the pool + * + * Initalize a pool and allocate an initial chunk of entries. + */ +void *kgsl_gpummu_ptpool_init(int entries) +{ + int ptsize = KGSL_PAGETABLE_SIZE; + struct kgsl_ptpool *pool; + int ret = 0; + + pool = kzalloc(sizeof(struct kgsl_ptpool), GFP_KERNEL); + if (!pool) { + KGSL_CORE_ERR("Failed to allocate memory " + "for ptpool\n"); + return NULL; + } + + pool->ptsize = ptsize; + mutex_init(&pool->lock); + INIT_LIST_HEAD(&pool->list); + + if (entries) { + ret = kgsl_ptpool_add(pool, entries); + if (ret) + goto err_ptpool_remove; + } + + ret = sysfs_create_group(kgsl_driver.ptkobj, &ptpool_attr_group); + if (ret) { + KGSL_CORE_ERR("sysfs_create_group failed for ptpool " + "statistics: %d\n", ret); + goto err_ptpool_remove; + } + return (void *)pool; + +err_ptpool_remove: + kgsl_gpummu_ptpool_destroy(pool); + return NULL; +} + +int kgsl_gpummu_pt_equal(struct kgsl_pagetable *pt, + unsigned int pt_base) +{ + struct kgsl_gpummu_pt *gpummu_pt = pt ? pt->priv : NULL; + return gpummu_pt && pt_base && (gpummu_pt->base.gpuaddr == pt_base); +} + +void kgsl_gpummu_destroy_pagetable(void *mmu_specific_pt) +{ + struct kgsl_gpummu_pt *gpummu_pt = (struct kgsl_gpummu_pt *) + mmu_specific_pt; + kgsl_ptpool_free((struct kgsl_ptpool *)kgsl_driver.ptpool, + gpummu_pt->base.hostptr); + + kgsl_driver.stats.coherent -= KGSL_PAGETABLE_SIZE; + + kfree(gpummu_pt->tlbflushfilter.base); + + kfree(gpummu_pt); +} + +static inline uint32_t +kgsl_pt_entry_get(unsigned int va_base, uint32_t va) +{ + return (va - va_base) >> PAGE_SHIFT; +} + +static inline void +kgsl_pt_map_set(struct kgsl_gpummu_pt *pt, uint32_t pte, uint32_t val) +{ + uint32_t *baseptr = (uint32_t *)pt->base.hostptr; + BUG_ON(pte*sizeof(uint32_t) >= pt->base.size); + baseptr[pte] = val; +} + +static inline uint32_t +kgsl_pt_map_get(struct kgsl_gpummu_pt *pt, uint32_t pte) +{ + uint32_t *baseptr = (uint32_t *)pt->base.hostptr; + BUG_ON(pte*sizeof(uint32_t) >= pt->base.size); + return baseptr[pte] & GSL_PT_PAGE_ADDR_MASK; +} + +static void kgsl_gpummu_pagefault(struct kgsl_mmu *mmu) +{ + unsigned int reg; + unsigned int ptbase; + + kgsl_regread(mmu->device, MH_MMU_PAGE_FAULT, ®); + kgsl_regread(mmu->device, MH_MMU_PT_BASE, &ptbase); + + KGSL_MEM_CRIT(mmu->device, + "mmu page fault: page=0x%lx pt=%d op=%s axi=%d\n", + reg & ~(PAGE_SIZE - 1), + kgsl_mmu_get_ptname_from_ptbase(ptbase), + reg & 0x02 ? "WRITE" : "READ", (reg >> 4) & 0xF); + trace_kgsl_mmu_pagefault(mmu->device, reg & ~(PAGE_SIZE - 1), + kgsl_mmu_get_ptname_from_ptbase(ptbase), + reg & 0x02 ? "WRITE" : "READ"); +} + +static void *kgsl_gpummu_create_pagetable(void) +{ + struct kgsl_gpummu_pt *gpummu_pt; + + gpummu_pt = kzalloc(sizeof(struct kgsl_gpummu_pt), + GFP_KERNEL); + if (!gpummu_pt) + return NULL; + + gpummu_pt->last_superpte = 0; + + gpummu_pt->tlbflushfilter.size = (CONFIG_MSM_KGSL_PAGE_TABLE_SIZE / + (PAGE_SIZE * GSL_PT_SUPER_PTE * 8)) + 1; + gpummu_pt->tlbflushfilter.base = (unsigned int *) + kzalloc(gpummu_pt->tlbflushfilter.size, GFP_KERNEL); + if (!gpummu_pt->tlbflushfilter.base) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + gpummu_pt->tlbflushfilter.size); + goto err_free_gpummu; + } + GSL_TLBFLUSH_FILTER_RESET(); + + gpummu_pt->base.hostptr = kgsl_ptpool_alloc((struct kgsl_ptpool *) + kgsl_driver.ptpool, + &gpummu_pt->base.physaddr); + + if (gpummu_pt->base.hostptr == NULL) + goto err_flushfilter; + + /* 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); + + gpummu_pt->base.gpuaddr = gpummu_pt->base.physaddr; + gpummu_pt->base.size = KGSL_PAGETABLE_SIZE; + + return (void *)gpummu_pt; + +err_flushfilter: + kfree(gpummu_pt->tlbflushfilter.base); +err_free_gpummu: + kfree(gpummu_pt); + + return NULL; +} + +static void kgsl_gpummu_default_setstate(struct kgsl_mmu *mmu, + uint32_t flags) +{ + struct kgsl_gpummu_pt *gpummu_pt; + if (!kgsl_mmu_enabled()) + return; + + if (flags & KGSL_MMUFLAGS_PTUPDATE) { + kgsl_idle(mmu->device, KGSL_TIMEOUT_DEFAULT); + gpummu_pt = mmu->hwpagetable->priv; + kgsl_regwrite(mmu->device, MH_MMU_PT_BASE, + gpummu_pt->base.gpuaddr); + } + + if (flags & KGSL_MMUFLAGS_TLBFLUSH) { + /* Invalidate all and tc */ + kgsl_regwrite(mmu->device, MH_MMU_INVALIDATE, 0x00000003); + } +} + +static void kgsl_gpummu_setstate(struct kgsl_mmu *mmu, + struct kgsl_pagetable *pagetable) +{ + 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; + /* Since we do a TLB flush the tlb_flags should + * be cleared by calling kgsl_mmu_pt_get_flags + */ + kgsl_mmu_pt_get_flags(pagetable, mmu->device->id); + + /* call device specific set page table */ + kgsl_setstate(mmu, KGSL_MMUFLAGS_TLBFLUSH | + KGSL_MMUFLAGS_PTUPDATE); + } + } +} + +static int kgsl_gpummu_init(struct kgsl_mmu *mmu) +{ + /* + * intialize device mmu + * + * call this with the global lock held + */ + int status = 0; + + /* sub-client MMU lookups require address translation */ + if ((mmu->config & ~0x1) > 0) { + /*make sure virtual address range is a multiple of 64Kb */ + if (CONFIG_MSM_KGSL_PAGE_TABLE_SIZE & ((1 << 16) - 1)) { + KGSL_CORE_ERR("Invalid pagetable size requested " + "for GPUMMU: %x\n", CONFIG_MSM_KGSL_PAGE_TABLE_SIZE); + return -EINVAL; + } + } + + dev_info(mmu->device->dev, "|%s| MMU type set for device is GPUMMU\n", + __func__); + return status; +} + +static int kgsl_gpummu_start(struct kgsl_mmu *mmu) +{ + /* + * intialize device mmu + * + * call this with the global lock held + */ + + struct kgsl_device *device = mmu->device; + struct kgsl_gpummu_pt *gpummu_pt; + + if (mmu->flags & KGSL_FLAGS_STARTED) + return 0; + + /* MMU not enabled */ + if ((mmu->config & 0x1) == 0) + return 0; + + /* setup MMU and sub-client behavior */ + kgsl_regwrite(device, MH_MMU_CONFIG, mmu->config); + + /* idle device */ + kgsl_idle(device, KGSL_TIMEOUT_DEFAULT); + + /* enable axi interrupts */ + kgsl_regwrite(device, MH_INTERRUPT_MASK, + GSL_MMU_INT_MASK | MH_INTERRUPT_MASK__MMU_PAGE_FAULT); + + kgsl_sharedmem_set(&mmu->setstate_memory, 0, 0, + mmu->setstate_memory.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 setstate_memory for other + * purposes (e.g. use it when dummy read cycles are needed + * for other blocks) */ + kgsl_regwrite(device, MH_MMU_TRAN_ERROR, + mmu->setstate_memory.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; + gpummu_pt = mmu->hwpagetable->priv; + kgsl_regwrite(mmu->device, MH_MMU_PT_BASE, + gpummu_pt->base.gpuaddr); + kgsl_regwrite(mmu->device, MH_MMU_VA_RANGE, + (KGSL_PAGETABLE_BASE | + (CONFIG_MSM_KGSL_PAGE_TABLE_SIZE >> 16))); + kgsl_setstate(mmu, KGSL_MMUFLAGS_TLBFLUSH); + mmu->flags |= KGSL_FLAGS_STARTED; + + return 0; +} + +static int +kgsl_gpummu_unmap(void *mmu_specific_pt, + struct kgsl_memdesc *memdesc) +{ + unsigned int numpages; + unsigned int pte, ptefirst, ptelast, superpte; + unsigned int range = kgsl_sg_size(memdesc->sg, memdesc->sglen); + struct kgsl_gpummu_pt *gpummu_pt = mmu_specific_pt; + + /* 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; + + numpages = (range >> PAGE_SHIFT); + if (range & (PAGE_SIZE - 1)) + numpages++; + + ptefirst = kgsl_pt_entry_get(KGSL_PAGETABLE_BASE, gpuaddr); + ptelast = ptefirst + numpages; + + 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 */ + if (!kgsl_pt_map_get(gpummu_pt, pte)) + KGSL_CORE_ERR("pt entry %x is already " + "unmapped for pagetable %p\n", pte, gpummu_pt); +#endif + kgsl_pt_map_set(gpummu_pt, 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); + } + + /* Post all writes to the pagetable */ + wmb(); + + return 0; +} + +#define SUPERPTE_IS_DIRTY(_p) \ +(((_p) & (GSL_PT_SUPER_PTE - 1)) == 0 && \ +GSL_TLBFLUSH_FILTER_ISDIRTY((_p) / GSL_PT_SUPER_PTE)) + +static int +kgsl_gpummu_map(void *mmu_specific_pt, + struct kgsl_memdesc *memdesc, + unsigned int protflags, + unsigned int *tlb_flags) +{ + unsigned int pte; + struct kgsl_gpummu_pt *gpummu_pt = mmu_specific_pt; + struct scatterlist *s; + int flushtlb = 0; + int i; + + pte = kgsl_pt_entry_get(KGSL_PAGETABLE_BASE, memdesc->gpuaddr); + + /* Flush the TLB if the first PTE isn't at the superpte boundary */ + if (pte & (GSL_PT_SUPER_PTE - 1)) + flushtlb = 1; + + for_each_sg(memdesc->sg, s, memdesc->sglen, i) { + unsigned int paddr = kgsl_get_sg_pa(s); + unsigned int j; + + /* Each sg entry might be multiple pages long */ + for (j = paddr; j < paddr + s->length; pte++, j += PAGE_SIZE) { + if (SUPERPTE_IS_DIRTY(pte)) + flushtlb = 1; + kgsl_pt_map_set(gpummu_pt, pte, j | protflags); + } + } + + /* Flush the TLB if the last PTE isn't at the superpte boundary */ + if ((pte + 1) & (GSL_PT_SUPER_PTE - 1)) + flushtlb = 1; + + wmb(); + + if (flushtlb) { + /*set all devices as needing flushing*/ + *tlb_flags = UINT_MAX; + GSL_TLBFLUSH_FILTER_RESET(); + } + + return 0; +} + +static void kgsl_gpummu_stop(struct kgsl_mmu *mmu) +{ + kgsl_regwrite(mmu->device, MH_MMU_CONFIG, 0x00000000); + mmu->flags &= ~KGSL_FLAGS_STARTED; +} + +static int kgsl_gpummu_close(struct kgsl_mmu *mmu) +{ + /* + * close device mmu + * + * call this with the global lock held + */ + if (mmu->setstate_memory.gpuaddr) + kgsl_sharedmem_free(&mmu->setstate_memory); + + if (mmu->defaultpagetable) + kgsl_mmu_putpagetable(mmu->defaultpagetable); + + return 0; +} + +static unsigned int +kgsl_gpummu_get_current_ptbase(struct kgsl_mmu *mmu) +{ + unsigned int ptbase; + kgsl_regread(mmu->device, MH_MMU_PT_BASE, &ptbase); + return ptbase; +} + +static unsigned int +kgsl_gpummu_pt_get_base_addr(struct kgsl_pagetable *pt) +{ + struct kgsl_gpummu_pt *gpummu_pt = pt->priv; + return gpummu_pt->base.gpuaddr; +} + +struct kgsl_mmu_ops gpummu_ops = { + .mmu_init = kgsl_gpummu_init, + .mmu_close = kgsl_gpummu_close, + .mmu_start = kgsl_gpummu_start, + .mmu_stop = kgsl_gpummu_stop, + .mmu_setstate = kgsl_gpummu_setstate, + .mmu_device_setstate = kgsl_gpummu_default_setstate, + .mmu_pagefault = kgsl_gpummu_pagefault, + .mmu_get_current_ptbase = kgsl_gpummu_get_current_ptbase, + .mmu_enable_clk = NULL, + .mmu_disable_clk = NULL, + .mmu_get_hwpagetable_asid = NULL, + .mmu_get_pt_lsb = NULL, + .mmu_get_reg_map_desc = NULL, +}; + +struct kgsl_mmu_pt_ops gpummu_pt_ops = { + .mmu_map = kgsl_gpummu_map, + .mmu_unmap = kgsl_gpummu_unmap, + .mmu_create_pagetable = kgsl_gpummu_create_pagetable, + .mmu_destroy_pagetable = kgsl_gpummu_destroy_pagetable, + .mmu_pt_equal = kgsl_gpummu_pt_equal, + .mmu_pt_get_base_addr = kgsl_gpummu_pt_get_base_addr, +}; diff --git a/drivers/gpu/msm/kgsl_gpummu.h b/drivers/gpu/msm/kgsl_gpummu.h new file mode 100644 index 0000000000000000000000000000000000000000..caa5df172fcb1510c1529067163489caf5255179 --- /dev/null +++ b/drivers/gpu/msm/kgsl_gpummu.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_GPUMMU_H +#define __KGSL_GPUMMU_H + +#define GSL_PT_PAGE_BITS_MASK 0x00000007 +#define GSL_PT_PAGE_ADDR_MASK PAGE_MASK + +#define GSL_MMU_INT_MASK \ + (MH_INTERRUPT_MASK__AXI_READ_ERROR | \ + MH_INTERRUPT_MASK__AXI_WRITE_ERROR) + +/* 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)gpummu_pt->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(gpummu_pt->tlbflushfilter.base,\ + 0, gpummu_pt->tlbflushfilter.size) + +extern struct kgsl_mmu_ops gpummu_ops; +extern struct kgsl_mmu_pt_ops gpummu_pt_ops; + +struct kgsl_tlbflushfilter { + unsigned int *base; + unsigned int size; +}; + +struct kgsl_gpummu_pt { + struct kgsl_memdesc base; + unsigned int last_superpte; + /* Maintain filter to manage tlb flushing */ + struct kgsl_tlbflushfilter tlbflushfilter; +}; + +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_ptpool { + size_t ptsize; + struct mutex lock; + struct list_head list; + int entries; + int static_entries; + int chunks; +}; + +void *kgsl_gpummu_ptpool_init(int entries); +void kgsl_gpummu_ptpool_destroy(void *ptpool); + +#endif /* __KGSL_GPUMMU_H */ diff --git a/drivers/gpu/msm/kgsl_iommu.c b/drivers/gpu/msm/kgsl_iommu.c new file mode 100644 index 0000000000000000000000000000000000000000..febb265a94190adcbc2a177d5136e78f42659d86 --- /dev/null +++ b/drivers/gpu/msm/kgsl_iommu.c @@ -0,0 +1,1020 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_device.h" +#include "kgsl_mmu.h" +#include "kgsl_sharedmem.h" +#include "kgsl_iommu.h" +#include "adreno_pm4types.h" +#include "adreno.h" +#include "kgsl_trace.h" + +static struct kgsl_iommu_unit *get_iommu_unit(struct device *dev) +{ + int i, j, k; + + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + struct kgsl_mmu *mmu; + struct kgsl_iommu *iommu; + + if (kgsl_driver.devp[i] == NULL) + continue; + + mmu = kgsl_get_mmu(kgsl_driver.devp[i]); + if (mmu == NULL || mmu->priv == NULL) + continue; + + iommu = mmu->priv; + + for (j = 0; j < iommu->unit_count; j++) { + struct kgsl_iommu_unit *iommu_unit = + &iommu->iommu_units[j]; + for (k = 0; k < iommu_unit->dev_count; k++) { + if (iommu_unit->dev[k].dev == dev) + return iommu_unit; + } + } + } + + return NULL; +} + +static struct kgsl_iommu_device *get_iommu_device(struct kgsl_iommu_unit *unit, + struct device *dev) +{ + int k; + + for (k = 0; unit && k < unit->dev_count; k++) { + if (unit->dev[k].dev == dev) + return &(unit->dev[k]); + } + + return NULL; +} + +static int kgsl_iommu_fault_handler(struct iommu_domain *domain, + struct device *dev, unsigned long addr, int flags) +{ + struct kgsl_iommu_unit *iommu_unit = get_iommu_unit(dev); + struct kgsl_iommu_device *iommu_dev = get_iommu_device(iommu_unit, dev); + unsigned int ptbase, fsr; + + if (!iommu_dev) { + KGSL_CORE_ERR("Invalid IOMMU device %p\n", dev); + return -ENOSYS; + } + + ptbase = iommu_get_pt_base_addr(domain); + + fsr = KGSL_IOMMU_GET_IOMMU_REG(iommu_unit->reg_map.hostptr, + iommu_dev->ctx_id, FSR); + + KGSL_MEM_CRIT(iommu_dev->kgsldev, + "GPU PAGE FAULT: addr = %lX pid = %d\n", + addr, kgsl_mmu_get_ptname_from_ptbase(ptbase)); + KGSL_MEM_CRIT(iommu_dev->kgsldev, "context = %d FSR = %X\n", + iommu_dev->ctx_id, fsr); + + trace_kgsl_mmu_pagefault(iommu_dev->kgsldev, addr, + kgsl_mmu_get_ptname_from_ptbase(ptbase), 0); + + return 0; +} + +/* + * kgsl_iommu_disable_clk - Disable iommu clocks + * @mmu - Pointer to mmu structure + * + * Disables iommu clocks + * Return - void + */ +static void kgsl_iommu_disable_clk(struct kgsl_mmu *mmu) +{ + struct kgsl_iommu *iommu = mmu->priv; + struct msm_iommu_drvdata *iommu_drvdata; + int i, j; + + for (i = 0; i < iommu->unit_count; i++) { + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[i]; + for (j = 0; j < iommu_unit->dev_count; j++) { + if (!iommu_unit->dev[j].clk_enabled) + continue; + iommu_drvdata = dev_get_drvdata( + iommu_unit->dev[j].dev->parent); + if (iommu_drvdata->clk) + clk_disable_unprepare(iommu_drvdata->clk); + clk_disable_unprepare(iommu_drvdata->pclk); + iommu_unit->dev[j].clk_enabled = false; + } + } +} + +/* + * kgsl_iommu_enable_clk - Enable iommu clocks + * @mmu - Pointer to mmu structure + * @ctx_id - The context bank whose clocks are to be turned on + * + * Enables iommu clocks of a given context + * Return: 0 on success else error code + */ +static int kgsl_iommu_enable_clk(struct kgsl_mmu *mmu, + int ctx_id) +{ + int ret = 0; + int i, j; + struct kgsl_iommu *iommu = mmu->priv; + struct msm_iommu_drvdata *iommu_drvdata; + + for (i = 0; i < iommu->unit_count; i++) { + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[i]; + for (j = 0; j < iommu_unit->dev_count; j++) { + if (iommu_unit->dev[j].clk_enabled || + ctx_id != iommu_unit->dev[j].ctx_id) + continue; + iommu_drvdata = + dev_get_drvdata(iommu_unit->dev[j].dev->parent); + ret = clk_prepare_enable(iommu_drvdata->pclk); + if (ret) + goto done; + if (iommu_drvdata->clk) { + ret = clk_prepare_enable(iommu_drvdata->clk); + if (ret) { + clk_disable_unprepare( + iommu_drvdata->pclk); + goto done; + } + } + iommu_unit->dev[j].clk_enabled = true; + } + } +done: + if (ret) + kgsl_iommu_disable_clk(mmu); + return ret; +} + +/* + * kgsl_iommu_pt_equal - Check if pagetables are equal + * @pt - Pointer to pagetable + * @pt_base - Address of a pagetable that the IOMMU register is + * programmed with + * + * Checks whether the pt_base is equal to the base address of + * the pagetable which is contained in the pt structure + * Return - Non-zero if the pagetable addresses are equal else 0 + */ +static int kgsl_iommu_pt_equal(struct kgsl_pagetable *pt, + unsigned int pt_base) +{ + struct kgsl_iommu_pt *iommu_pt = pt ? pt->priv : NULL; + unsigned int domain_ptbase = iommu_pt ? + iommu_get_pt_base_addr(iommu_pt->domain) : 0; + /* Only compare the valid address bits of the pt_base */ + domain_ptbase &= (KGSL_IOMMU_TTBR0_PA_MASK << + KGSL_IOMMU_TTBR0_PA_SHIFT); + pt_base &= (KGSL_IOMMU_TTBR0_PA_MASK << + KGSL_IOMMU_TTBR0_PA_SHIFT); + return domain_ptbase && pt_base && + (domain_ptbase == pt_base); +} + +/* + * kgsl_iommu_destroy_pagetable - Free up reaources help by a pagetable + * @mmu_specific_pt - Pointer to pagetable which is to be freed + * + * Return - void + */ +static void kgsl_iommu_destroy_pagetable(void *mmu_specific_pt) +{ + struct kgsl_iommu_pt *iommu_pt = mmu_specific_pt; + if (iommu_pt->domain) + iommu_domain_free(iommu_pt->domain); + if (iommu_pt->iommu) { + if ((KGSL_IOMMU_ASID_REUSE == iommu_pt->asid) && + iommu_pt->iommu->asid_reuse) + iommu_pt->iommu->asid_reuse--; + if (!iommu_pt->iommu->asid_reuse || + (KGSL_IOMMU_ASID_REUSE != iommu_pt->asid)) + clear_bit(iommu_pt->asid, iommu_pt->iommu->asids); + } + kfree(iommu_pt); +} + +/* + * kgsl_iommu_create_pagetable - Create a IOMMU pagetable + * + * Allocate memory to hold a pagetable and allocate the IOMMU + * domain which is the actual IOMMU pagetable + * Return - void + */ +void *kgsl_iommu_create_pagetable(void) +{ + struct kgsl_iommu_pt *iommu_pt; + + iommu_pt = kzalloc(sizeof(struct kgsl_iommu_pt), GFP_KERNEL); + if (!iommu_pt) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + sizeof(struct kgsl_iommu_pt)); + return NULL; + } + iommu_pt->domain = iommu_domain_alloc(&platform_bus_type, + MSM_IOMMU_DOMAIN_PT_CACHEABLE); + if (!iommu_pt->domain) { + KGSL_CORE_ERR("Failed to create iommu domain\n"); + kfree(iommu_pt); + return NULL; + } else { + iommu_set_fault_handler(iommu_pt->domain, + kgsl_iommu_fault_handler); + } + + return iommu_pt; +} + +/* + * kgsl_detach_pagetable_iommu_domain - Detach the IOMMU unit from a + * pagetable + * @mmu - Pointer to the device mmu structure + * @priv - Flag indicating whether the private or user context is to be + * detached + * + * Detach the IOMMU unit with the domain that is contained in the + * hwpagetable of the given mmu. After detaching the IOMMU unit is not + * in use because the PTBR will not be set after a detach + * Return - void + */ +static void kgsl_detach_pagetable_iommu_domain(struct kgsl_mmu *mmu) +{ + struct kgsl_iommu_pt *iommu_pt; + struct kgsl_iommu *iommu = mmu->priv; + int i, j; + + BUG_ON(mmu->hwpagetable == NULL); + BUG_ON(mmu->hwpagetable->priv == NULL); + + iommu_pt = mmu->hwpagetable->priv; + + for (i = 0; i < iommu->unit_count; i++) { + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[i]; + for (j = 0; j < iommu_unit->dev_count; j++) { + if (iommu_unit->dev[j].attached) { + iommu_detach_device(iommu_pt->domain, + iommu_unit->dev[j].dev); + iommu_unit->dev[j].attached = false; + KGSL_MEM_INFO(mmu->device, "iommu %p detached " + "from user dev of MMU: %p\n", + iommu_pt->domain, mmu); + } + } + } +} + +/* + * kgsl_attach_pagetable_iommu_domain - Attach the IOMMU unit to a + * pagetable, i.e set the IOMMU's PTBR to the pagetable address and + * setup other IOMMU registers for the device so that it becomes + * active + * @mmu - Pointer to the device mmu structure + * @priv - Flag indicating whether the private or user context is to be + * attached + * + * Attach the IOMMU unit with the domain that is contained in the + * hwpagetable of the given mmu. + * Return - 0 on success else error code + */ +static int kgsl_attach_pagetable_iommu_domain(struct kgsl_mmu *mmu) +{ + struct kgsl_iommu_pt *iommu_pt; + struct kgsl_iommu *iommu = mmu->priv; + int i, j, ret = 0; + + BUG_ON(mmu->hwpagetable == NULL); + BUG_ON(mmu->hwpagetable->priv == NULL); + + iommu_pt = mmu->hwpagetable->priv; + + /* + * Loop through all the iommu devcies under all iommu units and + * attach the domain + */ + for (i = 0; i < iommu->unit_count; i++) { + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[i]; + for (j = 0; j < iommu_unit->dev_count; j++) { + if (!iommu_unit->dev[j].attached) { + ret = iommu_attach_device(iommu_pt->domain, + iommu_unit->dev[j].dev); + if (ret) { + KGSL_MEM_ERR(mmu->device, + "Failed to attach device, err %d\n", + ret); + goto done; + } + iommu_unit->dev[j].attached = true; + KGSL_MEM_INFO(mmu->device, + "iommu pt %p attached to dev %p, ctx_id %d\n", + iommu_pt->domain, iommu_unit->dev[j].dev, + iommu_unit->dev[j].ctx_id); + } + } + } +done: + return ret; +} + +/* + * _get_iommu_ctxs - Get device pointer to IOMMU contexts + * @mmu - Pointer to mmu device + * data - Pointer to the platform data containing information about + * iommu devices for one iommu unit + * unit_id - The IOMMU unit number. This is not a specific ID but just + * a serial number. The serial numbers are treated as ID's of the + * IOMMU units + * + * Return - 0 on success else error code + */ +static int _get_iommu_ctxs(struct kgsl_mmu *mmu, + struct kgsl_device_iommu_data *data, unsigned int unit_id) +{ + struct kgsl_iommu *iommu = mmu->priv; + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[unit_id]; + int i; + + if (data->iommu_ctx_count > KGSL_IOMMU_MAX_DEVS_PER_UNIT) { + KGSL_CORE_ERR("Too many iommu devices defined for an " + "IOMMU unit\n"); + return -EINVAL; + } + + for (i = 0; i < data->iommu_ctx_count; i++) { + if (!data->iommu_ctxs[i].iommu_ctx_name) + continue; + + iommu_unit->dev[iommu_unit->dev_count].dev = + msm_iommu_get_ctx(data->iommu_ctxs[i].iommu_ctx_name); + if (iommu_unit->dev[iommu_unit->dev_count].dev == NULL) { + KGSL_CORE_ERR("Failed to get iommu dev handle for " + "device %s\n", data->iommu_ctxs[i].iommu_ctx_name); + return -EINVAL; + } + if (KGSL_IOMMU_CONTEXT_USER != data->iommu_ctxs[i].ctx_id && + KGSL_IOMMU_CONTEXT_PRIV != data->iommu_ctxs[i].ctx_id) { + KGSL_CORE_ERR("Invalid context ID defined: %d\n", + data->iommu_ctxs[i].ctx_id); + return -EINVAL; + } + iommu_unit->dev[iommu_unit->dev_count].ctx_id = + data->iommu_ctxs[i].ctx_id; + iommu_unit->dev[iommu_unit->dev_count].kgsldev = mmu->device; + + KGSL_DRV_INFO(mmu->device, + "Obtained dev handle %p for iommu context %s\n", + iommu_unit->dev[iommu_unit->dev_count].dev, + data->iommu_ctxs[i].iommu_ctx_name); + + iommu_unit->dev_count++; + } + + return 0; +} + +/* + * kgsl_get_iommu_ctxt - Get device pointer to IOMMU contexts + * @mmu - Pointer to mmu device + * + * Get the device pointers for the IOMMU user and priv contexts of the + * kgsl device + * Return - 0 on success else error code + */ +static int kgsl_get_iommu_ctxt(struct kgsl_mmu *mmu) +{ + struct platform_device *pdev = + container_of(mmu->device->parentdev, struct platform_device, + dev); + struct kgsl_device_platform_data *pdata_dev = pdev->dev.platform_data; + struct kgsl_iommu *iommu = mmu->device->mmu.priv; + int i, ret = 0; + + /* Go through the IOMMU data and get all the context devices */ + if (KGSL_IOMMU_MAX_UNITS < pdata_dev->iommu_count) { + KGSL_CORE_ERR("Too many IOMMU units defined\n"); + ret = -EINVAL; + goto done; + } + + for (i = 0; i < pdata_dev->iommu_count; i++) { + ret = _get_iommu_ctxs(mmu, &pdata_dev->iommu_data[i], i); + if (ret) + break; + } + iommu->unit_count = pdata_dev->iommu_count; +done: + return ret; +} + +/* + * kgsl_set_register_map - Map the IOMMU regsiters in the memory descriptors + * of the respective iommu units + * @mmu - Pointer to mmu structure + * + * Return - 0 on success else error code + */ +static int kgsl_set_register_map(struct kgsl_mmu *mmu) +{ + struct platform_device *pdev = + container_of(mmu->device->parentdev, struct platform_device, + dev); + struct kgsl_device_platform_data *pdata_dev = pdev->dev.platform_data; + struct kgsl_iommu *iommu = mmu->device->mmu.priv; + struct kgsl_iommu_unit *iommu_unit; + int i = 0, ret = 0; + + for (; i < pdata_dev->iommu_count; i++) { + struct kgsl_device_iommu_data data = pdata_dev->iommu_data[i]; + iommu_unit = &iommu->iommu_units[i]; + /* set up the IOMMU register map for the given IOMMU unit */ + if (!data.physstart || !data.physend) { + KGSL_CORE_ERR("The register range for IOMMU unit not" + " specified\n"); + ret = -EINVAL; + goto err; + } + iommu_unit->reg_map.hostptr = ioremap(data.physstart, + data.physend - data.physstart + 1); + if (!iommu_unit->reg_map.hostptr) { + KGSL_CORE_ERR("Failed to map SMMU register address " + "space from %x to %x\n", data.physstart, + data.physend - data.physstart + 1); + ret = -ENOMEM; + i--; + goto err; + } + iommu_unit->reg_map.size = data.physend - data.physstart + 1; + iommu_unit->reg_map.physaddr = data.physstart; + memdesc_sg_phys(&iommu_unit->reg_map, data.physstart, + iommu_unit->reg_map.size); + } + iommu->unit_count = pdata_dev->iommu_count; + return ret; +err: + /* Unmap any mapped IOMMU regions */ + for (; i >= 0; i--) { + iommu_unit = &iommu->iommu_units[i]; + iounmap(iommu_unit->reg_map.hostptr); + iommu_unit->reg_map.size = 0; + iommu_unit->reg_map.physaddr = 0; + } + return ret; +} + +/* + * kgsl_iommu_pt_get_base_addr - Get the address of the pagetable that the + * IOMMU ttbr0 register is programmed with + * @pt - kgsl pagetable pointer that contains the IOMMU domain pointer + * + * Return - actual pagetable address that the ttbr0 register is programmed + * with + */ +static unsigned int kgsl_iommu_pt_get_base_addr(struct kgsl_pagetable *pt) +{ + struct kgsl_iommu_pt *iommu_pt = pt->priv; + return iommu_get_pt_base_addr(iommu_pt->domain); +} + +/* + * kgsl_iommu_get_pt_lsb - Return the lsb of the ttbr0 IOMMU register + * @mmu - Pointer to mmu structure + * @hostptr - Pointer to the IOMMU register map. This is used to match + * the iommu device whose lsb value is to be returned + * @ctx_id - The context bank whose lsb valus is to be returned + * Return - returns the lsb which is the last 14 bits of the ttbr0 IOMMU + * register. ttbr0 is the actual PTBR for of the IOMMU. The last 14 bits + * are only programmed once in the beginning when a domain is attached + * does not change. + */ +static int kgsl_iommu_get_pt_lsb(struct kgsl_mmu *mmu, + unsigned int unit_id, + enum kgsl_iommu_context_id ctx_id) +{ + struct kgsl_iommu *iommu = mmu->priv; + int i, j; + for (i = 0; i < iommu->unit_count; i++) { + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[i]; + for (j = 0; j < iommu_unit->dev_count; j++) + if (unit_id == i && + ctx_id == iommu_unit->dev[j].ctx_id) + return iommu_unit->dev[j].pt_lsb; + } + return 0; +} + +static void kgsl_iommu_setstate(struct kgsl_mmu *mmu, + struct kgsl_pagetable *pagetable) +{ + if (mmu->flags & KGSL_FLAGS_STARTED) { + struct kgsl_iommu *iommu = mmu->priv; + struct kgsl_iommu_pt *iommu_pt = pagetable->priv; + /* page table not current, then setup mmu to use new + * specified page table + */ + if (mmu->hwpagetable != pagetable) { + unsigned int flags = 0; + mmu->hwpagetable = pagetable; + /* force tlb flush if asid is reused */ + if (iommu->asid_reuse && + (KGSL_IOMMU_ASID_REUSE == iommu_pt->asid)) + flags |= KGSL_MMUFLAGS_TLBFLUSH; + flags |= kgsl_mmu_pt_get_flags(mmu->hwpagetable, + mmu->device->id); + kgsl_setstate(mmu, KGSL_MMUFLAGS_PTUPDATE | flags); + } + } +} + +static int kgsl_iommu_init(struct kgsl_mmu *mmu) +{ + /* + * intialize device mmu + * + * call this with the global lock held + */ + int status = 0; + struct kgsl_iommu *iommu; + + iommu = kzalloc(sizeof(struct kgsl_iommu), GFP_KERNEL); + if (!iommu) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + sizeof(struct kgsl_iommu)); + return -ENOMEM; + } + iommu->asids = kzalloc(BITS_TO_LONGS(KGSL_IOMMU_MAX_ASIDS) * + sizeof(unsigned long), GFP_KERNEL); + if (!iommu->asids) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + sizeof(struct kgsl_iommu)); + status = -ENOMEM; + goto done; + } + + mmu->priv = iommu; + status = kgsl_get_iommu_ctxt(mmu); + if (status) + goto done; + status = kgsl_set_register_map(mmu); + if (status) + goto done; + + /* A nop is required in an indirect buffer when switching + * pagetables in-stream */ + kgsl_sharedmem_writel(&mmu->setstate_memory, + KGSL_IOMMU_SETSTATE_NOP_OFFSET, + cp_nop_packet(1)); + + dev_info(mmu->device->dev, "|%s| MMU type set for device is IOMMU\n", + __func__); +done: + if (status) { + kfree(iommu->asids); + kfree(iommu); + mmu->priv = NULL; + } + return status; +} + +/* + * kgsl_iommu_setup_defaultpagetable - Setup the initial defualtpagetable + * for iommu. This function is only called once during first start, successive + * start do not call this funciton. + * @mmu - Pointer to mmu structure + * + * Create the initial defaultpagetable and setup the iommu mappings to it + * Return - 0 on success else error code + */ +static int kgsl_iommu_setup_defaultpagetable(struct kgsl_mmu *mmu) +{ + int status = 0; + int i = 0; + struct kgsl_iommu *iommu = mmu->priv; + struct kgsl_iommu_pt *iommu_pt; + + mmu->defaultpagetable = kgsl_mmu_getpagetable(KGSL_MMU_GLOBAL_PT); + /* Return error if the default pagetable doesn't exist */ + if (mmu->defaultpagetable == NULL) { + status = -ENOMEM; + goto err; + } + /* Map the IOMMU regsiters to only defaultpagetable */ + for (i = 0; i < iommu->unit_count; i++) { + iommu->iommu_units[i].reg_map.priv |= KGSL_MEMFLAGS_GLOBAL; + status = kgsl_mmu_map(mmu->defaultpagetable, + &(iommu->iommu_units[i].reg_map), + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + if (status) { + iommu->iommu_units[i].reg_map.priv &= + ~KGSL_MEMFLAGS_GLOBAL; + goto err; + } + } + /* + * The dafault pagetable always has asid 0 assigned by the iommu driver + * and asid 1 is assigned to the private context. + */ + iommu_pt = mmu->defaultpagetable->priv; + iommu_pt->asid = 0; + set_bit(0, iommu->asids); + set_bit(1, iommu->asids); + return status; +err: + for (i--; i >= 0; i--) { + kgsl_mmu_unmap(mmu->defaultpagetable, + &(iommu->iommu_units[i].reg_map)); + iommu->iommu_units[i].reg_map.priv &= ~KGSL_MEMFLAGS_GLOBAL; + } + if (mmu->defaultpagetable) { + kgsl_mmu_putpagetable(mmu->defaultpagetable); + mmu->defaultpagetable = NULL; + } + return status; +} + +static int kgsl_iommu_start(struct kgsl_mmu *mmu) +{ + int status; + struct kgsl_iommu *iommu = mmu->priv; + int i, j; + + if (mmu->flags & KGSL_FLAGS_STARTED) + return 0; + + if (mmu->defaultpagetable == NULL) { + status = kgsl_iommu_setup_defaultpagetable(mmu); + if (status) + return -ENOMEM; + } + /* We use the GPU MMU to control access to IOMMU registers on a225, + * hence we still keep the MMU active on a225 */ + if (adreno_is_a225(ADRENO_DEVICE(mmu->device))) { + struct kgsl_mh *mh = &(mmu->device->mh); + kgsl_regwrite(mmu->device, MH_MMU_CONFIG, 0x00000001); + kgsl_regwrite(mmu->device, MH_MMU_MPU_END, + mh->mpu_base + + iommu->iommu_units + [iommu->unit_count - 1].reg_map.gpuaddr - + PAGE_SIZE); + } else { + kgsl_regwrite(mmu->device, MH_MMU_CONFIG, 0x00000000); + } + + mmu->hwpagetable = mmu->defaultpagetable; + + status = kgsl_attach_pagetable_iommu_domain(mmu); + if (status) { + mmu->hwpagetable = NULL; + goto done; + } + status = kgsl_iommu_enable_clk(mmu, KGSL_IOMMU_CONTEXT_USER); + if (status) { + KGSL_CORE_ERR("clk enable failed\n"); + goto done; + } + status = kgsl_iommu_enable_clk(mmu, KGSL_IOMMU_CONTEXT_PRIV); + if (status) { + KGSL_CORE_ERR("clk enable failed\n"); + goto done; + } + /* Get the lsb value of pagetables set in the IOMMU ttbr0 register as + * that value should not change when we change pagetables, so while + * changing pagetables we can use this lsb value of the pagetable w/o + * having to read it again + */ + for (i = 0; i < iommu->unit_count; i++) { + struct kgsl_iommu_unit *iommu_unit = &iommu->iommu_units[i]; + for (j = 0; j < iommu_unit->dev_count; j++) + iommu_unit->dev[j].pt_lsb = KGSL_IOMMMU_PT_LSB( + KGSL_IOMMU_GET_IOMMU_REG( + iommu_unit->reg_map.hostptr, + iommu_unit->dev[j].ctx_id, + TTBR0)); + } + iommu->asid = KGSL_IOMMU_GET_IOMMU_REG( + iommu->iommu_units[0].reg_map.hostptr, + KGSL_IOMMU_CONTEXT_USER, + CONTEXTIDR); + + kgsl_iommu_disable_clk(mmu); + mmu->flags |= KGSL_FLAGS_STARTED; + +done: + if (status) { + kgsl_iommu_disable_clk(mmu); + kgsl_detach_pagetable_iommu_domain(mmu); + } + return status; +} + +static int +kgsl_iommu_unmap(void *mmu_specific_pt, + struct kgsl_memdesc *memdesc) +{ + int ret; + unsigned int range = kgsl_sg_size(memdesc->sg, memdesc->sglen); + struct kgsl_iommu_pt *iommu_pt = mmu_specific_pt; + + /* 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; + + ret = iommu_unmap_range(iommu_pt->domain, gpuaddr, range); + if (ret) + KGSL_CORE_ERR("iommu_unmap_range(%p, %x, %d) failed " + "with err: %d\n", iommu_pt->domain, gpuaddr, + range, ret); + + return 0; +} + +static int +kgsl_iommu_map(void *mmu_specific_pt, + struct kgsl_memdesc *memdesc, + unsigned int protflags, + unsigned int *tlb_flags) +{ + int ret; + unsigned int iommu_virt_addr; + struct kgsl_iommu_pt *iommu_pt = mmu_specific_pt; + int size = kgsl_sg_size(memdesc->sg, memdesc->sglen); + + BUG_ON(NULL == iommu_pt); + + + iommu_virt_addr = memdesc->gpuaddr; + + ret = iommu_map_range(iommu_pt->domain, iommu_virt_addr, memdesc->sg, + size, (IOMMU_READ | IOMMU_WRITE)); + if (ret) { + KGSL_CORE_ERR("iommu_map_range(%p, %x, %p, %d, %d) " + "failed with err: %d\n", iommu_pt->domain, + iommu_virt_addr, memdesc->sg, size, + (IOMMU_READ | IOMMU_WRITE), ret); + return ret; + } + +#ifdef CONFIG_KGSL_PER_PROCESS_PAGE_TABLE + /* + * Flushing only required if per process pagetables are used. With + * global case, flushing will happen inside iommu_map function + */ + if (!ret) + *tlb_flags = UINT_MAX; +#endif + return ret; +} + +static void kgsl_iommu_stop(struct kgsl_mmu *mmu) +{ + /* + * stop device mmu + * + * call this with the global lock held + */ + + if (mmu->flags & KGSL_FLAGS_STARTED) { + kgsl_regwrite(mmu->device, MH_MMU_CONFIG, 0x00000000); + /* detach iommu attachment */ + kgsl_detach_pagetable_iommu_domain(mmu); + mmu->hwpagetable = NULL; + + mmu->flags &= ~KGSL_FLAGS_STARTED; + } +} + +static int kgsl_iommu_close(struct kgsl_mmu *mmu) +{ + struct kgsl_iommu *iommu = mmu->priv; + int i; + for (i = 0; i < iommu->unit_count; i++) { + if (iommu->iommu_units[i].reg_map.gpuaddr) + kgsl_mmu_unmap(mmu->defaultpagetable, + &(iommu->iommu_units[i].reg_map)); + if (iommu->iommu_units[i].reg_map.hostptr) + iounmap(iommu->iommu_units[i].reg_map.hostptr); + kgsl_sg_free(iommu->iommu_units[i].reg_map.sg, + iommu->iommu_units[i].reg_map.sglen); + } + if (mmu->defaultpagetable) + kgsl_mmu_putpagetable(mmu->defaultpagetable); + kfree(iommu->asids); + kfree(iommu); + + return 0; +} + +static unsigned int +kgsl_iommu_get_current_ptbase(struct kgsl_mmu *mmu) +{ + unsigned int pt_base; + struct kgsl_iommu *iommu = mmu->priv; + /* Return the current pt base by reading IOMMU pt_base register */ + kgsl_iommu_enable_clk(mmu, KGSL_IOMMU_CONTEXT_USER); + pt_base = readl_relaxed(iommu->iommu_units[0].reg_map.hostptr + + (KGSL_IOMMU_CONTEXT_USER << KGSL_IOMMU_CTX_SHIFT) + + KGSL_IOMMU_TTBR0); + kgsl_iommu_disable_clk(mmu); + return pt_base & (KGSL_IOMMU_TTBR0_PA_MASK << + KGSL_IOMMU_TTBR0_PA_SHIFT); +} + +/* + * kgsl_iommu_get_hwpagetable_asid - Returns asid(application space ID) for a + * pagetable + * @mmu - Pointer to mmu structure + * + * Allocates an asid to a IOMMU domain if it does not already have one. asid's + * are unique identifiers for pagetable that can be used to selectively flush + * tlb entries of the IOMMU unit. + * Return - asid to be used with the IOMMU domain + */ +static int kgsl_iommu_get_hwpagetable_asid(struct kgsl_mmu *mmu) +{ + struct kgsl_iommu *iommu = mmu->priv; + struct kgsl_iommu_pt *iommu_pt = mmu->hwpagetable->priv; + + /* + * If the iommu pagetable does not have any asid assigned and is not the + * default pagetable then assign asid. + */ + if (!iommu_pt->asid && iommu_pt != mmu->defaultpagetable->priv) { + iommu_pt->asid = find_first_zero_bit(iommu->asids, + KGSL_IOMMU_MAX_ASIDS); + /* No free bits means reuse asid */ + if (iommu_pt->asid >= KGSL_IOMMU_MAX_ASIDS) { + iommu_pt->asid = KGSL_IOMMU_ASID_REUSE; + iommu->asid_reuse++; + } + set_bit(iommu_pt->asid, iommu->asids); + /* + * Store pointer to asids list so that during pagetable destroy + * the asid assigned to this pagetable may be cleared + */ + iommu_pt->iommu = iommu; + } + /* Return the asid + the constant part of asid that never changes */ + return (iommu_pt->asid & (KGSL_IOMMU_CONTEXTIDR_ASID_MASK << + KGSL_IOMMU_CONTEXTIDR_ASID_SHIFT)) + + (iommu->asid & ~(KGSL_IOMMU_CONTEXTIDR_ASID_MASK << + KGSL_IOMMU_CONTEXTIDR_ASID_SHIFT)); +} + +/* + * kgsl_iommu_default_setstate - Change the IOMMU pagetable or flush IOMMU tlb + * of the primary context bank + * @mmu - Pointer to mmu structure + * @flags - Flags indicating whether pagetable has to chnage or tlb is to be + * flushed or both + * + * Based on flags set the new pagetable fo the IOMMU unit or flush it's tlb or + * do both by doing direct register writes to the IOMMu registers through the + * cpu + * Return - void + */ +static void kgsl_iommu_default_setstate(struct kgsl_mmu *mmu, + uint32_t flags) +{ + struct kgsl_iommu *iommu = mmu->priv; + int temp; + int i; + unsigned int pt_base = kgsl_iommu_pt_get_base_addr( + mmu->hwpagetable); + unsigned int pt_val; + + if (kgsl_iommu_enable_clk(mmu, KGSL_IOMMU_CONTEXT_USER)) { + KGSL_DRV_ERR(mmu->device, "Failed to enable iommu clocks\n"); + return; + } + /* Mask off the lsb of the pt base address since lsb will not change */ + pt_base &= (KGSL_IOMMU_TTBR0_PA_MASK << KGSL_IOMMU_TTBR0_PA_SHIFT); + if (flags & KGSL_MMUFLAGS_PTUPDATE) { + kgsl_idle(mmu->device, KGSL_TIMEOUT_DEFAULT); + for (i = 0; i < iommu->unit_count; i++) { + /* get the lsb value which should not change when + * changing ttbr0 */ + pt_val = kgsl_iommu_get_pt_lsb(mmu, i, + KGSL_IOMMU_CONTEXT_USER); + pt_val += pt_base; + + KGSL_IOMMU_SET_IOMMU_REG( + iommu->iommu_units[i].reg_map.hostptr, + KGSL_IOMMU_CONTEXT_USER, TTBR0, pt_val); + + mb(); + temp = KGSL_IOMMU_GET_IOMMU_REG( + iommu->iommu_units[i].reg_map.hostptr, + KGSL_IOMMU_CONTEXT_USER, TTBR0); + /* Set asid */ + KGSL_IOMMU_SET_IOMMU_REG( + iommu->iommu_units[i].reg_map.hostptr, + KGSL_IOMMU_CONTEXT_USER, CONTEXTIDR, + kgsl_iommu_get_hwpagetable_asid(mmu)); + mb(); + temp = KGSL_IOMMU_GET_IOMMU_REG( + iommu->iommu_units[i].reg_map.hostptr, + KGSL_IOMMU_CONTEXT_USER, CONTEXTIDR); + } + } + /* Flush tlb */ + if (flags & KGSL_MMUFLAGS_TLBFLUSH) { + for (i = 0; i < iommu->unit_count; i++) { + KGSL_IOMMU_SET_IOMMU_REG( + iommu->iommu_units[i].reg_map.hostptr, + KGSL_IOMMU_CONTEXT_USER, CTX_TLBIASID, + kgsl_iommu_get_hwpagetable_asid(mmu)); + mb(); + } + } + /* Disable smmu clock */ + kgsl_iommu_disable_clk(mmu); +} + +/* + * kgsl_iommu_get_reg_map_desc - Returns an array of pointers that contain + * the address of memory descriptors which map the IOMMU registers + * @mmu - Pointer to mmu structure + * @reg_map_desc - Out parameter in which the address of the array containing + * pointers to register map descriptors is returned. The caller is supposed + * to free this array + * + * Return - The number of iommu units which is also the number of register + * mapped descriptor arrays which the out parameter will have + */ +static int kgsl_iommu_get_reg_map_desc(struct kgsl_mmu *mmu, + void **reg_map_desc) +{ + struct kgsl_iommu *iommu = mmu->priv; + void **reg_desc_ptr; + int i; + + /* + * Alocate array of pointers that will hold address of the register map + * descriptors + */ + reg_desc_ptr = kmalloc(iommu->unit_count * + sizeof(struct kgsl_memdesc *), GFP_KERNEL); + if (!reg_desc_ptr) { + KGSL_CORE_ERR("Failed to kmalloc(%d)\n", + iommu->unit_count * sizeof(struct kgsl_memdesc *)); + return -ENOMEM; + } + + for (i = 0; i < iommu->unit_count; i++) + reg_desc_ptr[i] = &(iommu->iommu_units[i].reg_map); + + *reg_map_desc = reg_desc_ptr; + return i; +} + +struct kgsl_mmu_ops iommu_ops = { + .mmu_init = kgsl_iommu_init, + .mmu_close = kgsl_iommu_close, + .mmu_start = kgsl_iommu_start, + .mmu_stop = kgsl_iommu_stop, + .mmu_setstate = kgsl_iommu_setstate, + .mmu_device_setstate = kgsl_iommu_default_setstate, + .mmu_pagefault = NULL, + .mmu_get_current_ptbase = kgsl_iommu_get_current_ptbase, + .mmu_enable_clk = kgsl_iommu_enable_clk, + .mmu_disable_clk = kgsl_iommu_disable_clk, + .mmu_get_hwpagetable_asid = kgsl_iommu_get_hwpagetable_asid, + .mmu_get_pt_lsb = kgsl_iommu_get_pt_lsb, + .mmu_get_reg_map_desc = kgsl_iommu_get_reg_map_desc, +}; + +struct kgsl_mmu_pt_ops iommu_pt_ops = { + .mmu_map = kgsl_iommu_map, + .mmu_unmap = kgsl_iommu_unmap, + .mmu_create_pagetable = kgsl_iommu_create_pagetable, + .mmu_destroy_pagetable = kgsl_iommu_destroy_pagetable, + .mmu_pt_equal = kgsl_iommu_pt_equal, + .mmu_pt_get_base_addr = kgsl_iommu_pt_get_base_addr, +}; diff --git a/drivers/gpu/msm/kgsl_iommu.h b/drivers/gpu/msm/kgsl_iommu.h new file mode 100644 index 0000000000000000000000000000000000000000..efc3d9cf1de10c7c97cb2b4abc701f21189c2a27 --- /dev/null +++ b/drivers/gpu/msm/kgsl_iommu.h @@ -0,0 +1,134 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IOMMU_H +#define __KGSL_IOMMU_H + +#include + +/* IOMMU registers and masks */ +#define KGSL_IOMMU_TTBR0 0x10 +#define KGSL_IOMMU_TTBR1 0x14 +#define KGSL_IOMMU_FSR 0x20 + +#define KGSL_IOMMU_TTBR0_PA_MASK 0x0003FFFF +#define KGSL_IOMMU_TTBR0_PA_SHIFT 14 +#define KGSL_IOMMU_CTX_TLBIALL 0x800 +#define KGSL_IOMMU_CONTEXTIDR 0x8 +#define KGSL_IOMMU_CONTEXTIDR_ASID_MASK 0xFF +#define KGSL_IOMMU_CONTEXTIDR_ASID_SHIFT 0 +#define KGSL_IOMMU_CTX_TLBIASID 0x804 +#define KGSL_IOMMU_CTX_SHIFT 12 + +#define KGSL_IOMMU_MAX_ASIDS 256 +#define KGSL_IOMMU_ASID_REUSE 2 + +/* + * Max number of iommu units that the gpu core can have + * On APQ8064, KGSL can control a maximum of 2 IOMMU units. + */ +#define KGSL_IOMMU_MAX_UNITS 2 + +/* Max number of iommu contexts per IOMMU unit */ +#define KGSL_IOMMU_MAX_DEVS_PER_UNIT 2 + +/* Macros to read/write IOMMU registers */ +#define KGSL_IOMMU_SET_IOMMU_REG(base_addr, ctx, REG, val) \ + writel_relaxed(val, base_addr + \ + (ctx << KGSL_IOMMU_CTX_SHIFT) + \ + KGSL_IOMMU_##REG) + +#define KGSL_IOMMU_GET_IOMMU_REG(base_addr, ctx, REG) \ + readl_relaxed(base_addr + \ + (ctx << KGSL_IOMMU_CTX_SHIFT) + \ + KGSL_IOMMU_##REG) + +/* Gets the lsb value of pagetable */ +#define KGSL_IOMMMU_PT_LSB(pt_val) \ + (pt_val & ~(KGSL_IOMMU_TTBR0_PA_MASK << \ + KGSL_IOMMU_TTBR0_PA_SHIFT)) + +/* offset at which a nop command is placed in setstate_memory */ +#define KGSL_IOMMU_SETSTATE_NOP_OFFSET 1024 + +/* + * struct kgsl_iommu_device - Structure holding data about iommu contexts + * @dev: Device pointer to iommu context + * @attached: Indicates whether this iommu context is presently attached to + * a pagetable/domain or not + * @pt_lsb: The LSB of IOMMU_TTBR0 register which is the pagetable + * register + * @ctx_id: This iommu units context id. It can be either 0 or 1 + * @clk_enabled: If set indicates that iommu clocks of this iommu context + * are on, else the clocks are off + */ +struct kgsl_iommu_device { + struct device *dev; + bool attached; + unsigned int pt_lsb; + enum kgsl_iommu_context_id ctx_id; + bool clk_enabled; + struct kgsl_device *kgsldev; +}; + +/* + * struct kgsl_iommu_unit - Structure holding data about iommu units. An IOMMU + * units is basically a separte IOMMU h/w block with it's own IOMMU contexts + * @dev: Pointer to array of struct kgsl_iommu_device which has information + * about the IOMMU contexts under this IOMMU unit + * @dev_count: Number of IOMMU contexts that are valid in the previous feild + * @reg_map: Memory descriptor which holds the mapped address of this IOMMU + * units register range + */ +struct kgsl_iommu_unit { + struct kgsl_iommu_device dev[KGSL_IOMMU_MAX_DEVS_PER_UNIT]; + unsigned int dev_count; + struct kgsl_memdesc reg_map; +}; + +/* + * struct kgsl_iommu - Structure holding iommu data for kgsl driver + * @dev: Array of kgsl_iommu_device which contain information about + * iommu contexts owned by graphics cores + * @unit_count: Number of IOMMU units that are available for this + * instance of the IOMMU driver + * @iommu_last_cmd_ts: The timestamp of last command submitted that + * aceeses iommu registers + * @device: Pointer to kgsl device + * @asids: A bit structure indicating which id's are presently used + * @asid: Contains the initial value of IOMMU_CONTEXTIDR when a domain + * is first attached + * asid_reuse: Holds the number of times the reuse asid is reused + */ +struct kgsl_iommu { + struct kgsl_iommu_unit iommu_units[KGSL_IOMMU_MAX_UNITS]; + unsigned int unit_count; + unsigned int iommu_last_cmd_ts; + struct kgsl_device *device; + unsigned long *asids; + unsigned int asid; + unsigned int asid_reuse; +}; + +/* + * struct kgsl_iommu_pt - Iommu pagetable structure private to kgsl driver + * @domain: Pointer to the iommu domain that contains the iommu pagetable + * @iommu: Pointer to iommu structure + * @asid: The asid assigned to this domain + */ +struct kgsl_iommu_pt { + struct iommu_domain *domain; + struct kgsl_iommu *iommu; + unsigned int asid; +}; + +#endif diff --git a/drivers/gpu/msm/kgsl_log.h b/drivers/gpu/msm/kgsl_log.h new file mode 100644 index 0000000000000000000000000000000000000000..6fd28abccbf89209ccef235601aa83abe281e782 --- /dev/null +++ b/drivers/gpu/msm/kgsl_log.h @@ -0,0 +1,112 @@ +/* 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_DEV_ERR_ONCE(_dev, fmt, args...) \ +({ \ + static bool kgsl_dev_err_once; \ + \ + if (!kgsl_dev_err_once) { \ + kgsl_dev_err_once = true; \ + dev_crit(_dev->dev, "|%s| " fmt, __func__, ##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 0000000000000000000000000000000000000000..ff9f0b8088806a5f14b12ed01f734761b151d15d --- /dev/null +++ b/drivers/gpu/msm/kgsl_mmu.c @@ -0,0 +1,823 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_mmu.h" +#include "kgsl_device.h" +#include "kgsl_sharedmem.h" +#include "adreno_postmortem.h" + +#define KGSL_MMU_ALIGN_SHIFT 13 +#define KGSL_MMU_ALIGN_MASK (~((1 << KGSL_MMU_ALIGN_SHIFT) - 1)) + +static enum kgsl_mmutype kgsl_mmu_type; + +static void pagetable_remove_sysfs_objects(struct kgsl_pagetable *pagetable); + +static int kgsl_cleanup_pt(struct kgsl_pagetable *pt) +{ + int i; + /* For IOMMU only unmap the global structures to global pt */ + if ((KGSL_MMU_TYPE_NONE != kgsl_mmu_type) && + (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_type) && + (KGSL_MMU_GLOBAL_PT != pt->name)) + return 0; + 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 int kgsl_setup_pt(struct kgsl_pagetable *pt) +{ + int i = 0; + int status = 0; + + /* For IOMMU only map the global structures to global pt */ + if ((KGSL_MMU_TYPE_NONE != kgsl_mmu_type) && + (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_type) && + (KGSL_MMU_GLOBAL_PT != pt->name)) + return 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 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); + + if (pagetable->kgsl_pool) + gen_pool_destroy(pagetable->kgsl_pool); + if (pagetable->pool) + gen_pool_destroy(pagetable->pool); + + pagetable->pt_ops->mmu_destroy_pagetable(pagetable->priv); + + 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 += snprintf(buf, PAGE_SIZE, "%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 += snprintf(buf, PAGE_SIZE, "%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 += snprintf(buf, PAGE_SIZE, "0x%x\n", + kgsl_mmu_get_ptsize()); + } + + 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 += snprintf(buf, PAGE_SIZE, "%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 += snprintf(buf, PAGE_SIZE, "%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; +} + +unsigned int kgsl_mmu_get_ptsize(void) +{ + /* + * For IOMMU, we could do up to 4G virtual range if we wanted to, but + * it makes more sense to return a smaller range and leave the rest of + * the virtual range for future improvements + */ + + if (KGSL_MMU_TYPE_GPU == kgsl_mmu_type) + return CONFIG_MSM_KGSL_PAGE_TABLE_SIZE; + else if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_type) + return SZ_2G; + else + return 0; +} + +int +kgsl_mmu_get_ptname_from_ptbase(unsigned int pt_base) +{ + struct kgsl_pagetable *pt; + int ptid = -1; + + spin_lock(&kgsl_driver.ptlock); + list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) { + if (pt->pt_ops->mmu_pt_equal(pt, pt_base)) { + ptid = (int) pt->name; + break; + } + } + spin_unlock(&kgsl_driver.ptlock); + + return ptid; +} +EXPORT_SYMBOL(kgsl_mmu_get_ptname_from_ptbase); + +int kgsl_mmu_init(struct kgsl_device *device) +{ + int status = 0; + struct kgsl_mmu *mmu = &device->mmu; + + mmu->device = device; + status = kgsl_allocate_contiguous(&mmu->setstate_memory, PAGE_SIZE); + if (status) + return status; + kgsl_sharedmem_set(&mmu->setstate_memory, 0, 0, + mmu->setstate_memory.size); + + if (KGSL_MMU_TYPE_NONE == kgsl_mmu_type) { + dev_info(device->dev, "|%s| MMU type set for device is " + "NOMMU\n", __func__); + goto done; + } else if (KGSL_MMU_TYPE_GPU == kgsl_mmu_type) + mmu->mmu_ops = &gpummu_ops; + else if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_type) + mmu->mmu_ops = &iommu_ops; + + status = mmu->mmu_ops->mmu_init(mmu); +done: + if (status) + kgsl_sharedmem_free(&mmu->setstate_memory); + return status; +} +EXPORT_SYMBOL(kgsl_mmu_init); + +int kgsl_mmu_start(struct kgsl_device *device) +{ + struct kgsl_mmu *mmu = &device->mmu; + + if (kgsl_mmu_type == KGSL_MMU_TYPE_NONE) { + kgsl_regwrite(device, MH_MMU_CONFIG, 0); + /* Setup gpuaddr of global mappings */ + if (!mmu->setstate_memory.gpuaddr) + kgsl_setup_pt(NULL); + return 0; + } else { + return mmu->mmu_ops->mmu_start(mmu); + } +} +EXPORT_SYMBOL(kgsl_mmu_start); + +static void mh_axi_error(struct kgsl_device *device, const char* type) +{ + unsigned int reg, gpu_err, phys_err, pt_base; + + kgsl_regread(device, MH_AXI_ERROR, ®); + pt_base = kgsl_mmu_get_current_ptbase(&device->mmu); + /* + * Read gpu virtual and physical addresses that + * caused the error from the debug data. + */ + kgsl_regwrite(device, MH_DEBUG_CTRL, 44); + kgsl_regread(device, MH_DEBUG_DATA, &gpu_err); + kgsl_regwrite(device, MH_DEBUG_CTRL, 45); + kgsl_regread(device, MH_DEBUG_DATA, &phys_err); + KGSL_MEM_CRIT(device, + "axi %s error: %08x pt %08x gpu %08x phys %08x\n", + type, reg, pt_base, gpu_err, phys_err); +} + +void kgsl_mh_intrcallback(struct kgsl_device *device) +{ + unsigned int status = 0; + + kgsl_regread(device, MH_INTERRUPT_STATUS, &status); + + if (status & MH_INTERRUPT_MASK__AXI_READ_ERROR) + mh_axi_error(device, "read"); + if (status & MH_INTERRUPT_MASK__AXI_WRITE_ERROR) + mh_axi_error(device, "write"); + if (status & MH_INTERRUPT_MASK__MMU_PAGE_FAULT) + device->mmu.mmu_ops->mmu_pagefault(&device->mmu); + + status &= KGSL_MMU_INT_MASK; + kgsl_regwrite(device, MH_INTERRUPT_CLEAR, status); +} +EXPORT_SYMBOL(kgsl_mh_intrcallback); + +static struct kgsl_pagetable *kgsl_mmu_createpagetableobject( + unsigned int name) +{ + int status = 0; + struct kgsl_pagetable *pagetable = NULL; + unsigned long flags; + unsigned int ptsize; + + 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); + + ptsize = kgsl_mmu_get_ptsize(); + + pagetable->name = name; + pagetable->max_entries = KGSL_PAGETABLE_ENTRIES(ptsize); + + /* + * create a separate kgsl pool for IOMMU, global mappings can be mapped + * just once from this pool of the defaultpagetable + */ + if ((KGSL_MMU_TYPE_IOMMU == kgsl_mmu_get_mmutype()) && + (KGSL_MMU_GLOBAL_PT == name)) { + pagetable->kgsl_pool = gen_pool_create(KGSL_MMU_ALIGN_SHIFT, + -1); + if (pagetable->kgsl_pool == NULL) { + KGSL_CORE_ERR("gen_pool_create(%d) failed\n", + KGSL_MMU_ALIGN_SHIFT); + goto err_alloc; + } + if (gen_pool_add(pagetable->kgsl_pool, + KGSL_IOMMU_GLOBAL_MEM_BASE, + KGSL_IOMMU_GLOBAL_MEM_SIZE, -1)) { + KGSL_CORE_ERR("gen_pool_add failed\n"); + goto err_kgsl_pool; + } + } + + pagetable->pool = gen_pool_create(KGSL_MMU_ALIGN_SHIFT, -1); + if (pagetable->pool == NULL) { + KGSL_CORE_ERR("gen_pool_create(%d) failed\n", + KGSL_MMU_ALIGN_SHIFT); + goto err_kgsl_pool; + } + + if (gen_pool_add(pagetable->pool, KGSL_PAGETABLE_BASE, + ptsize, -1)) { + KGSL_CORE_ERR("gen_pool_add failed\n"); + goto err_pool; + } + + if (KGSL_MMU_TYPE_GPU == kgsl_mmu_type) + pagetable->pt_ops = &gpummu_pt_ops; + else if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_type) + pagetable->pt_ops = &iommu_pt_ops; + + pagetable->priv = pagetable->pt_ops->mmu_create_pagetable(); + if (!pagetable->priv) + goto err_pool; + + status = kgsl_setup_pt(pagetable); + if (status) + goto err_mmu_create; + + 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_mmu_create: + pagetable->pt_ops->mmu_destroy_pagetable(pagetable->priv); +err_pool: + gen_pool_destroy(pagetable->pool); +err_kgsl_pool: + if (pagetable->kgsl_pool) + gen_pool_destroy(pagetable->kgsl_pool); +err_alloc: + kfree(pagetable); + + return NULL; +} + +struct kgsl_pagetable *kgsl_mmu_getpagetable(unsigned long name) +{ + struct kgsl_pagetable *pt; + + if (KGSL_MMU_TYPE_NONE == kgsl_mmu_type) + return (void *)(-1); + +#ifndef CONFIG_KGSL_PER_PROCESS_PAGE_TABLE + name = KGSL_MMU_GLOBAL_PT; +#endif + 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); +} +EXPORT_SYMBOL(kgsl_mmu_putpagetable); + +void kgsl_setstate(struct kgsl_mmu *mmu, uint32_t flags) +{ + struct kgsl_device *device = mmu->device; + if (KGSL_MMU_TYPE_NONE == kgsl_mmu_type) + return; + else if (device->ftbl->setstate) + device->ftbl->setstate(device, flags); + else if (mmu->mmu_ops->mmu_device_setstate) + mmu->mmu_ops->mmu_device_setstate(mmu, flags); +} +EXPORT_SYMBOL(kgsl_setstate); + +void kgsl_mh_start(struct kgsl_device *device) +{ + struct kgsl_mh *mh = &device->mh; + /* force mmu off to for now*/ + kgsl_regwrite(device, MH_MMU_CONFIG, 0); + kgsl_idle(device, KGSL_TIMEOUT_DEFAULT); + + /* define physical memory range accessible by the core */ + kgsl_regwrite(device, MH_MMU_MPU_BASE, mh->mpu_base); + kgsl_regwrite(device, MH_MMU_MPU_END, + mh->mpu_base + mh->mpu_range); + kgsl_regwrite(device, MH_ARBITER_CONFIG, mh->mharb); + + if (mh->mh_intf_cfg1 != 0) + kgsl_regwrite(device, MH_CLNT_INTF_CTRL_CONFIG1, + mh->mh_intf_cfg1); + + if (mh->mh_intf_cfg2 != 0) + kgsl_regwrite(device, MH_CLNT_INTF_CTRL_CONFIG2, + mh->mh_intf_cfg2); + + /* + * Interrupts are enabled on a per-device level when + * kgsl_pwrctrl_irq() is called + */ +} + +static inline struct gen_pool * +_get_pool(struct kgsl_pagetable *pagetable, unsigned int flags) +{ + if (pagetable->kgsl_pool && + (KGSL_MEMFLAGS_GLOBAL & flags)) + return pagetable->kgsl_pool; + return pagetable->pool; +} + +int +kgsl_mmu_map(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, + unsigned int protflags) +{ + int ret; + struct gen_pool *pool; + int size; + + if (kgsl_mmu_type == KGSL_MMU_TYPE_NONE) { + if (memdesc->sglen == 1) { + memdesc->gpuaddr = sg_dma_address(memdesc->sg); + if (!memdesc->gpuaddr) + memdesc->gpuaddr = sg_phys(memdesc->sg); + if (!memdesc->gpuaddr) { + KGSL_CORE_ERR("Unable to get a valid physical " + "address for memdesc\n"); + return -EINVAL; + } + return 0; + } else { + KGSL_CORE_ERR("Memory is not contigious " + "(sglen = %d)\n", memdesc->sglen); + return -EINVAL; + } + } + + size = kgsl_sg_size(memdesc->sg, memdesc->sglen); + + /* Allocate from kgsl pool if it exists for global mappings */ + pool = _get_pool(pagetable, memdesc->priv); + + memdesc->gpuaddr = gen_pool_alloc(pool, size); + if (memdesc->gpuaddr == 0) { + KGSL_CORE_ERR("gen_pool_alloc(%d) failed from pool: %s\n", + size, + (pool == pagetable->kgsl_pool) ? + "kgsl_pool" : "general_pool"); + KGSL_CORE_ERR(" [%d] allocated=%d, entries=%d\n", + pagetable->name, pagetable->stats.mapped, + pagetable->stats.entries); + return -ENOMEM; + } + + if (KGSL_MMU_TYPE_IOMMU != kgsl_mmu_get_mmutype()) + spin_lock(&pagetable->lock); + ret = pagetable->pt_ops->mmu_map(pagetable->priv, memdesc, protflags, + &pagetable->tlb_flags); + if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_get_mmutype()) + spin_lock(&pagetable->lock); + + if (ret) + goto err_free_gpuaddr; + + /* Keep track of the statistics for the sysfs files */ + + KGSL_STATS_ADD(1, pagetable->stats.entries, + pagetable->stats.max_entries); + + KGSL_STATS_ADD(size, pagetable->stats.mapped, + pagetable->stats.max_mapped); + + spin_unlock(&pagetable->lock); + + return 0; + +err_free_gpuaddr: + spin_unlock(&pagetable->lock); + gen_pool_free(pool, memdesc->gpuaddr, size); + memdesc->gpuaddr = 0; + return ret; +} +EXPORT_SYMBOL(kgsl_mmu_map); + +int +kgsl_mmu_unmap(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc) +{ + struct gen_pool *pool; + int size; + + if (memdesc->size == 0 || memdesc->gpuaddr == 0) + return 0; + + if (kgsl_mmu_type == KGSL_MMU_TYPE_NONE) { + memdesc->gpuaddr = 0; + return 0; + } + + size = kgsl_sg_size(memdesc->sg, memdesc->sglen); + + if (KGSL_MMU_TYPE_IOMMU != kgsl_mmu_get_mmutype()) + spin_lock(&pagetable->lock); + pagetable->pt_ops->mmu_unmap(pagetable->priv, memdesc); + if (KGSL_MMU_TYPE_IOMMU == kgsl_mmu_get_mmutype()) + spin_lock(&pagetable->lock); + /* Remove the statistics */ + pagetable->stats.entries--; + pagetable->stats.mapped -= size; + + spin_unlock(&pagetable->lock); + + pool = _get_pool(pagetable, memdesc->priv); + gen_pool_free(pool, memdesc->gpuaddr, size); + + /* + * Don't clear the gpuaddr on global mappings because they + * may be in use by other pagetables + */ + if (!(memdesc->priv & KGSL_MEMFLAGS_GLOBAL)) + memdesc->gpuaddr = 0; + 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; + } + /* Not all global mappings are needed for all MMU types */ + if (!memdesc->size) + return 0; + + gpuaddr = memdesc->gpuaddr; + memdesc->priv |= KGSL_MEMFLAGS_GLOBAL; + + 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_close(struct kgsl_device *device) +{ + struct kgsl_mmu *mmu = &device->mmu; + + kgsl_sharedmem_free(&mmu->setstate_memory); + if (kgsl_mmu_type == KGSL_MMU_TYPE_NONE) + return 0; + else + return mmu->mmu_ops->mmu_close(mmu); +} +EXPORT_SYMBOL(kgsl_mmu_close); + +int kgsl_mmu_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; +} +EXPORT_SYMBOL(kgsl_mmu_pt_get_flags); + +void kgsl_mmu_ptpool_destroy(void *ptpool) +{ + if (KGSL_MMU_TYPE_GPU == kgsl_mmu_type) + kgsl_gpummu_ptpool_destroy(ptpool); + ptpool = 0; +} +EXPORT_SYMBOL(kgsl_mmu_ptpool_destroy); + +void *kgsl_mmu_ptpool_init(int entries) +{ + if (KGSL_MMU_TYPE_GPU == kgsl_mmu_type) + return kgsl_gpummu_ptpool_init(entries); + else + return (void *)(-1); +} +EXPORT_SYMBOL(kgsl_mmu_ptpool_init); + +int kgsl_mmu_enabled(void) +{ + if (KGSL_MMU_TYPE_NONE != kgsl_mmu_type) + return 1; + else + return 0; +} +EXPORT_SYMBOL(kgsl_mmu_enabled); + +enum kgsl_mmutype kgsl_mmu_get_mmutype(void) +{ + return kgsl_mmu_type; +} +EXPORT_SYMBOL(kgsl_mmu_get_mmutype); + +void kgsl_mmu_set_mmutype(char *mmutype) +{ + /* Set the default MMU - GPU on <=8960 and nothing on >= 8064 */ + kgsl_mmu_type = + cpu_is_apq8064() ? KGSL_MMU_TYPE_NONE : KGSL_MMU_TYPE_GPU; + + /* Use the IOMMU if it is found */ + if (iommu_present(&platform_bus_type)) + kgsl_mmu_type = KGSL_MMU_TYPE_IOMMU; + + if (mmutype && !strncmp(mmutype, "gpummu", 6)) + kgsl_mmu_type = KGSL_MMU_TYPE_GPU; + if (iommu_present(&platform_bus_type) && mmutype && + !strncmp(mmutype, "iommu", 5)) + kgsl_mmu_type = KGSL_MMU_TYPE_IOMMU; + if (mmutype && !strncmp(mmutype, "nommu", 5)) + kgsl_mmu_type = KGSL_MMU_TYPE_NONE; +} +EXPORT_SYMBOL(kgsl_mmu_set_mmutype); diff --git a/drivers/gpu/msm/kgsl_mmu.h b/drivers/gpu/msm/kgsl_mmu.h new file mode 100644 index 0000000000000000000000000000000000000000..2db327b5c67d3984f19d49d053223e736b8c5e30 --- /dev/null +++ b/drivers/gpu/msm/kgsl_mmu.h @@ -0,0 +1,297 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +/* + * These defines control the split between ttbr1 and ttbr0 pagetables of IOMMU + * and what ranges of memory we map to them + */ +#define KGSL_IOMMU_GLOBAL_MEM_BASE 0xC0000000 +#define KGSL_IOMMU_GLOBAL_MEM_SIZE SZ_4M +#define KGSL_IOMMU_TTBR1_SPLIT 2 + +#define KGSL_MMU_ALIGN_SHIFT 13 +#define KGSL_MMU_ALIGN_MASK (~((1 << KGSL_MMU_ALIGN_SHIFT) - 1)) + +/* 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 + +struct kgsl_device; + +#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 +#define MH_ARBITER_CONFIG 0x0A40 +#define MH_DEBUG_CTRL 0x0A4E +#define MH_DEBUG_DATA 0x0A4F +#define MH_AXI_HALT_CONTROL 0x0A50 +#define MH_CLNT_INTF_CTRL_CONFIG1 0x0A54 +#define MH_CLNT_INTF_CTRL_CONFIG2 0x0A55 + +/* 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 + +#define KGSL_MMU_INT_MASK \ + (MH_INTERRUPT_MASK__AXI_READ_ERROR | \ + MH_INTERRUPT_MASK__AXI_WRITE_ERROR | \ + MH_INTERRUPT_MASK__MMU_PAGE_FAULT) + +enum kgsl_mmutype { + KGSL_MMU_TYPE_GPU = 0, + KGSL_MMU_TYPE_IOMMU, + KGSL_MMU_TYPE_NONE +}; + +struct kgsl_pagetable { + spinlock_t lock; + struct kref refcount; + unsigned int max_entries; + struct gen_pool *pool; + struct gen_pool *kgsl_pool; + struct list_head list; + unsigned int name; + struct kobject *kobj; + + struct { + unsigned int entries; + unsigned int mapped; + unsigned int max_mapped; + unsigned int max_entries; + } stats; + const struct kgsl_mmu_pt_ops *pt_ops; + unsigned int tlb_flags; + void *priv; +}; + +struct kgsl_mmu; + +struct kgsl_mmu_ops { + int (*mmu_init) (struct kgsl_mmu *mmu); + int (*mmu_close) (struct kgsl_mmu *mmu); + int (*mmu_start) (struct kgsl_mmu *mmu); + void (*mmu_stop) (struct kgsl_mmu *mmu); + void (*mmu_setstate) (struct kgsl_mmu *mmu, + struct kgsl_pagetable *pagetable); + void (*mmu_device_setstate) (struct kgsl_mmu *mmu, + uint32_t flags); + void (*mmu_pagefault) (struct kgsl_mmu *mmu); + unsigned int (*mmu_get_current_ptbase) + (struct kgsl_mmu *mmu); + void (*mmu_disable_clk) + (struct kgsl_mmu *mmu); + int (*mmu_enable_clk) + (struct kgsl_mmu *mmu, int ctx_id); + int (*mmu_get_hwpagetable_asid)(struct kgsl_mmu *mmu); + int (*mmu_get_pt_lsb)(struct kgsl_mmu *mmu, + unsigned int unit_id, + enum kgsl_iommu_context_id ctx_id); + int (*mmu_get_reg_map_desc)(struct kgsl_mmu *mmu, + void **reg_map_desc); +}; + +struct kgsl_mmu_pt_ops { + int (*mmu_map) (void *mmu_pt, + struct kgsl_memdesc *memdesc, + unsigned int protflags, + unsigned int *tlb_flags); + int (*mmu_unmap) (void *mmu_pt, + struct kgsl_memdesc *memdesc); + void *(*mmu_create_pagetable) (void); + void (*mmu_destroy_pagetable) (void *pt); + int (*mmu_pt_equal) (struct kgsl_pagetable *pt, + unsigned int pt_base); + unsigned int (*mmu_pt_get_base_addr) + (struct kgsl_pagetable *pt); +}; + +struct kgsl_mmu { + unsigned int refcnt; + uint32_t flags; + struct kgsl_device *device; + unsigned int config; + struct kgsl_memdesc setstate_memory; + /* current page table object being used by device mmu */ + struct kgsl_pagetable *defaultpagetable; + struct kgsl_pagetable *hwpagetable; + const struct kgsl_mmu_ops *mmu_ops; + void *priv; +}; + +#include "kgsl_gpummu.h" + +extern struct kgsl_mmu_ops iommu_ops; +extern struct kgsl_mmu_pt_ops iommu_pt_ops; + +struct kgsl_pagetable *kgsl_mmu_getpagetable(unsigned long name); +void kgsl_mmu_putpagetable(struct kgsl_pagetable *pagetable); +void kgsl_mh_start(struct kgsl_device *device); +void kgsl_mh_intrcallback(struct kgsl_device *device); +int kgsl_mmu_init(struct kgsl_device *device); +int kgsl_mmu_start(struct kgsl_device *device); +int kgsl_mmu_close(struct kgsl_device *device); +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); +unsigned int kgsl_virtaddr_to_physaddr(void *virtaddr); +void kgsl_setstate(struct kgsl_mmu *mmu, uint32_t flags); +int kgsl_mmu_get_ptname_from_ptbase(unsigned int pt_base); +int kgsl_mmu_pt_get_flags(struct kgsl_pagetable *pt, + enum kgsl_deviceid id); +void kgsl_mmu_ptpool_destroy(void *ptpool); +void *kgsl_mmu_ptpool_init(int entries); +int kgsl_mmu_enabled(void); +void kgsl_mmu_set_mmutype(char *mmutype); +enum kgsl_mmutype kgsl_mmu_get_mmutype(void); +unsigned int kgsl_mmu_get_ptsize(void); + +/* + * Static inline functions of MMU that simply call the SMMU specific + * function using a function pointer. These functions can be thought + * of as wrappers around the actual function + */ + +static inline unsigned int kgsl_mmu_get_current_ptbase(struct kgsl_mmu *mmu) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_get_current_ptbase) + return mmu->mmu_ops->mmu_get_current_ptbase(mmu); + else + return 0; +} + +static inline void kgsl_mmu_setstate(struct kgsl_mmu *mmu, + struct kgsl_pagetable *pagetable) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_setstate) + mmu->mmu_ops->mmu_setstate(mmu, pagetable); +} + +static inline void kgsl_mmu_device_setstate(struct kgsl_mmu *mmu, + uint32_t flags) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_device_setstate) + mmu->mmu_ops->mmu_device_setstate(mmu, flags); +} + +static inline void kgsl_mmu_stop(struct kgsl_mmu *mmu) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_stop) + mmu->mmu_ops->mmu_stop(mmu); +} + +static inline int kgsl_mmu_pt_equal(struct kgsl_pagetable *pt, + unsigned int pt_base) +{ + if (KGSL_MMU_TYPE_NONE == kgsl_mmu_get_mmutype()) + return 1; + else + return pt->pt_ops->mmu_pt_equal(pt, pt_base); +} + +static inline unsigned int kgsl_mmu_pt_get_base_addr(struct kgsl_pagetable *pt) +{ + if (KGSL_MMU_TYPE_NONE == kgsl_mmu_get_mmutype()) + return 0; + else + return pt->pt_ops->mmu_pt_get_base_addr(pt); +} + +static inline int kgsl_mmu_get_reg_map_desc(struct kgsl_mmu *mmu, + void **reg_map_desc) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_get_reg_map_desc) + return mmu->mmu_ops->mmu_get_reg_map_desc(mmu, reg_map_desc); + else + return 0; +} + +static inline int kgsl_mmu_get_pt_lsb(struct kgsl_mmu *mmu, + unsigned int unit_id, + enum kgsl_iommu_context_id ctx_id) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_get_pt_lsb) + return mmu->mmu_ops->mmu_get_pt_lsb(mmu, unit_id, ctx_id); + else + return 0; +} + +static inline int kgsl_mmu_get_hwpagetable_asid(struct kgsl_mmu *mmu) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_get_hwpagetable_asid) + return mmu->mmu_ops->mmu_get_hwpagetable_asid(mmu); + else + return 0; +} + +static inline int kgsl_mmu_enable_clk(struct kgsl_mmu *mmu, + int ctx_id) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_enable_clk) + return mmu->mmu_ops->mmu_enable_clk(mmu, ctx_id); + else + return 0; +} + +static inline void kgsl_mmu_disable_clk(struct kgsl_mmu *mmu) +{ + if (mmu->mmu_ops && mmu->mmu_ops->mmu_disable_clk) + mmu->mmu_ops->mmu_disable_clk(mmu); +} + +#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 0000000000000000000000000000000000000000..8701b672f8ba7990affcd215a6ccac03c1da6e05 --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrctrl.c @@ -0,0 +1,948 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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_device.h" +#include "kgsl_trace.h" + +#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 GPU_SWFI_LATENCY 3 +#define UPDATE_BUSY_VAL 1000000 +#define UPDATE_BUSY 50 + +struct clk_pair { + const char *name; + uint map; +}; + +struct clk_pair clks[KGSL_MAX_CLKS] = { + { + .name = "src_clk", + .map = KGSL_CLK_SRC, + }, + { + .name = "core_clk", + .map = KGSL_CLK_CORE, + }, + { + .name = "iface_clk", + .map = KGSL_CLK_IFACE, + }, + { + .name = "mem_clk", + .map = KGSL_CLK_MEM, + }, + { + .name = "mem_iface_clk", + .map = KGSL_CLK_MEM_IFACE, + }, +}; + +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) { + struct kgsl_pwrlevel *pwrlevel = &pwr->pwrlevels[new_level]; + int diff = new_level - pwr->active_pwrlevel; + int d = (diff > 0) ? 1 : -1; + int level = pwr->active_pwrlevel; + pwr->active_pwrlevel = new_level; + if ((test_bit(KGSL_PWRFLAGS_CLK_ON, &pwr->power_flags)) || + (device->state == KGSL_STATE_NAP)) { + /* + * On some platforms, instability is caused on + * changing clock freq when the core is busy. + * Idle the gpu core before changing the clock freq. + */ + if (pwr->idle_needed == true) + device->ftbl->idle(device, + KGSL_TIMEOUT_DEFAULT); + /* Don't shift by more than one level at a time to + * avoid glitches. + */ + while (level != new_level) { + level += d; + clk_set_rate(pwr->grp_clks[0], + pwr->pwrlevels[level].gpu_freq); + } + } + if (test_bit(KGSL_PWRFLAGS_AXI_ON, &pwr->power_flags)) { + if (pwr->pcl) + msm_bus_scale_client_update_request(pwr->pcl, + pwrlevel->bus_freq); + else if (pwr->ebi1_clk) + clk_set_rate(pwr->ebi1_clk, pwrlevel->bus_freq); + } + trace_kgsl_pwrlevel(device, pwr->active_pwrlevel, + pwrlevel->gpu_freq); + } +} +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; + + if (device == NULL) + return 0; + 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; + if (device == NULL) + return 0; + 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; + if (device == NULL) + return 0; + 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; + int rc; + + if (device == NULL) + return 0; + pwr = &device->pwrctrl; + + 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); + if (device == NULL) + return 0; + return snprintf(buf, PAGE_SIZE, "%d\n", device->pwrctrl.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; + const long div = 1000/HZ; + static unsigned int org_interval_timeout = 1; + int rc; + + if (device == NULL) + return 0; + pwr = &device->pwrctrl; + + 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); + if (device == NULL) + return 0; + return snprintf(buf, PAGE_SIZE, "%d\n", + device->pwrctrl.interval_timeout); +} + +static int kgsl_pwrctrl_gpubusy_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_busy *b = &device->pwrctrl.busy; + ret = snprintf(buf, 17, "%7d %7d\n", + b->on_time_old, b->time_old); + if (!test_bit(KGSL_PWRFLAGS_AXI_ON, &device->pwrctrl.power_flags)) { + b->on_time_old = 0; + b->time_old = 0; + } + return ret; +} + +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, 0664, kgsl_pwrctrl_pwrnap_show, kgsl_pwrctrl_pwrnap_store); +DEVICE_ATTR(idle_timer, 0644, kgsl_pwrctrl_idle_timer_show, + kgsl_pwrctrl_idle_timer_store); +DEVICE_ATTR(gpubusy, 0644, kgsl_pwrctrl_gpubusy_show, + NULL); + +static const struct device_attribute *pwrctrl_attr_list[] = { + &dev_attr_gpuclk, + &dev_attr_max_gpuclk, + &dev_attr_pwrnap, + &dev_attr_idle_timer, + &dev_attr_gpubusy, + 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); +} + +/* Track the amount of time the gpu is on vs the total system time. * + * Regularly update the percentage of busy time displayed by sysfs. */ +static void kgsl_pwrctrl_busy_time(struct kgsl_device *device, bool on_time) +{ + struct kgsl_busy *b = &device->pwrctrl.busy; + int elapsed; + if (b->start.tv_sec == 0) + do_gettimeofday(&(b->start)); + do_gettimeofday(&(b->stop)); + elapsed = (b->stop.tv_sec - b->start.tv_sec) * 1000000; + elapsed += b->stop.tv_usec - b->start.tv_usec; + b->time += elapsed; + if (on_time) + b->on_time += elapsed; + /* Update the output regularly and reset the counters. */ + if ((b->time > UPDATE_BUSY_VAL) || + !test_bit(KGSL_PWRFLAGS_AXI_ON, &device->pwrctrl.power_flags)) { + b->on_time_old = b->on_time; + b->time_old = b->time; + b->on_time = 0; + b->time = 0; + } + do_gettimeofday(&(b->start)); +} + +void kgsl_pwrctrl_clk(struct kgsl_device *device, int state, + int requested_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)) { + trace_kgsl_clk(device, state); + for (i = KGSL_MAX_CLKS - 1; i > 0; i--) + if (pwr->grp_clks[i]) + clk_disable(pwr->grp_clks[i]); + /* High latency clock maintenance. */ + if ((pwr->pwrlevels[0].gpu_freq > 0) && + (requested_state != KGSL_STATE_NAP)) { + clk_set_rate(pwr->grp_clks[0], + pwr->pwrlevels[pwr->num_pwrlevels - 1]. + gpu_freq); + for (i = KGSL_MAX_CLKS - 1; i > 0; i--) + if (pwr->grp_clks[i]) + clk_unprepare(pwr->grp_clks[i]); + } + kgsl_pwrctrl_busy_time(device, true); + } + } else if (state == KGSL_PWRFLAGS_ON) { + if (!test_and_set_bit(KGSL_PWRFLAGS_CLK_ON, + &pwr->power_flags)) { + trace_kgsl_clk(device, state); + /* High latency clock maintenance. */ + if ((pwr->pwrlevels[0].gpu_freq > 0) && + (device->state != KGSL_STATE_NAP)) { + for (i = KGSL_MAX_CLKS - 1; i > 0; i--) + if (pwr->grp_clks[i]) + clk_prepare(pwr->grp_clks[i]); + 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]); + kgsl_pwrctrl_busy_time(device, false); + } + } +} + +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)) { + trace_kgsl_bus(device, state); + if (pwr->ebi1_clk) { + clk_set_rate(pwr->ebi1_clk, 0); + clk_disable_unprepare(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)) { + trace_kgsl_bus(device, state); + if (pwr->ebi1_clk) { + clk_prepare_enable(pwr->ebi1_clk); + clk_set_rate(pwr->ebi1_clk, + pwr->pwrlevels[pwr->active_pwrlevel]. + bus_freq); + } + if (pwr->pcl) + msm_bus_scale_client_update_request(pwr->pcl, + pwr->pwrlevels[pwr->active_pwrlevel]. + bus_freq); + } + } +} + +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)) { + trace_kgsl_rail(device, state); + 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)) { + trace_kgsl_rail(device, state); + if (pwr->gpu_reg) { + int status = regulator_enable(pwr->gpu_reg); + if (status) + KGSL_DRV_ERR(device, "regulator_enable " + "failed: %d\n", status); + } + } + } +} + +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)) { + trace_kgsl_irq(device, state); + enable_irq(pwr->interrupt_num); + } + } else if (state == KGSL_PWRFLAGS_OFF) { + if (test_and_clear_bit(KGSL_PWRFLAGS_IRQ_ON, + &pwr->power_flags)) { + trace_kgsl_irq(device, state); + if (in_interrupt()) + disable_irq_nosync(pwr->interrupt_num); + else + 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 = pdev->dev.platform_data; + + /*acquire clocks */ + for (i = 0; i < KGSL_MAX_CLKS; i++) { + if (pdata->clk_map & clks[i].map) { + clk = clk_get(&pdev->dev, clks[i].name); + if (IS_ERR(clk)) + goto clk_err; + pwr->grp_clks[i] = clk; + } + } + /* Make sure we have a source clk for freq setting */ + if (pwr->grp_clks[0] == NULL) + pwr->grp_clks[0] = pwr->grp_clks[1]; + + /* put the AXI bus into asynchronous mode with the graphics cores */ + if (pdata->set_grp_async != NULL) + pdata->set_grp_async(); + + if (pdata->num_levels > KGSL_MAX_PWRLEVELS) { + KGSL_PWR_ERR(device, "invalid power level count: %d\n", + pdata->num_levels); + result = -EINVAL; + goto done; + } + pwr->num_pwrlevels = pdata->num_levels; + pwr->active_pwrlevel = pdata->init_level; + pwr->default_pwrlevel = pdata->init_level; + for (i = 0; i < pdata->num_levels; i++) { + pwr->pwrlevels[i].gpu_freq = + (pdata->pwrlevel[i].gpu_freq > 0) ? + clk_round_rate(pwr->grp_clks[0], + pdata->pwrlevel[i]. + gpu_freq) : 0; + pwr->pwrlevels[i].bus_freq = + pdata->pwrlevel[i].bus_freq; + pwr->pwrlevels[i].io_fraction = + pdata->pwrlevel[i].io_fraction; + } + /* 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(&pdev->dev, "vdd"); + if (IS_ERR(pwr->gpu_reg)) + pwr->gpu_reg = NULL; + + pwr->power_flags = 0; + + pwr->nap_allowed = pdata->nap_allowed; + pwr->idle_needed = pdata->idle_needed; + pwr->interval_timeout = pdata->idle_timeout; + pwr->strtstp_sleepwake = pdata->strtstp_sleepwake; + pwr->ebi1_clk = clk_get(&pdev->dev, "bus_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->bus_scale_table != NULL) { + pwr->pcl = msm_bus_scale_register_client(pdata-> + bus_scale_table); + if (!pwr->pcl) { + KGSL_PWR_ERR(device, + "msm_bus_scale_register_client failed: " + "id %d table %p", device->id, + pdata->bus_scale_table); + result = -EINVAL; + goto done; + } + } + + + pm_runtime_enable(device->parentdev); + register_early_suspend(&device->display_off); + return result; + +clk_err: + result = PTR_ERR(clk); + KGSL_PWR_ERR(device, "clk_get(%s) failed: %d\n", + clks[i].name, 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); + + pm_runtime_disable(device->parentdev); + unregister_early_suspend(&device->display_off); + + 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); + WARN_ON(device == NULL); + if (device == NULL) + return; + + mutex_lock(&device->mutex); + if (device->state & (KGSL_STATE_ACTIVE | KGSL_STATE_NAP)) { + kgsl_pwrscale_idle(device); + + if (kgsl_pwrctrl_sleep(device) != 0) { + mod_timer(&device->idle_timer, + jiffies + + device->pwrctrl.interval_timeout); + /* If the GPU has been too busy to sleep, make sure * + * that is acurately reflected in the % busy numbers. */ + device->pwrctrl.busy.no_nap_cnt++; + if (device->pwrctrl.busy.no_nap_cnt > UPDATE_BUSY) { + kgsl_pwrctrl_busy_time(device, true); + device->pwrctrl.busy.no_nap_cnt = 0; + } + } + } else if (device->state & (KGSL_STATE_HUNG | + KGSL_STATE_DUMP_AND_RECOVER)) { + kgsl_pwrctrl_request_state(device, 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) { + if (device->pwrctrl.restore_slumber || + device->pwrctrl.strtstp_sleepwake) + kgsl_pwrctrl_request_state(device, KGSL_STATE_SLUMBER); + else + kgsl_pwrctrl_request_state(device, 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)); + switch (device->state) { + case KGSL_STATE_ACTIVE: + return; + case KGSL_STATE_NAP: + case KGSL_STATE_SLEEP: + case KGSL_STATE_SLUMBER: + kgsl_pwrctrl_wake(device); + break; + case KGSL_STATE_SUSPEND: + kgsl_check_suspended(device); + break; + case KGSL_STATE_INIT: + case KGSL_STATE_HUNG: + case KGSL_STATE_DUMP_AND_RECOVER: + if (test_bit(KGSL_PWRFLAGS_CLK_ON, + &device->pwrctrl.power_flags)) + break; + else + KGSL_PWR_ERR(device, + "hw access while clocks off from state %d\n", + device->state); + break; + default: + KGSL_PWR_ERR(device, "hw access while in unknown state %d\n", + device->state); + break; + } +} +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); + } else if (device->state == KGSL_STATE_DUMP_AND_RECOVER) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->recovery_gate); + mutex_lock(&device->mutex); + } else if (device->state == KGSL_STATE_SLUMBER) + kgsl_pwrctrl_wake(device); +} + +static int +_nap(struct kgsl_device *device) +{ + switch (device->state) { + case KGSL_STATE_ACTIVE: + if (!device->ftbl->isidle(device)) { + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + return -EBUSY; + } + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_OFF, KGSL_STATE_NAP); + kgsl_mmu_disable_clk(&device->mmu); + kgsl_pwrctrl_set_state(device, KGSL_STATE_NAP); + if (device->idle_wakelock.name) + wake_unlock(&device->idle_wakelock); + case KGSL_STATE_NAP: + case KGSL_STATE_SLEEP: + case KGSL_STATE_SLUMBER: + break; + default: + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + break; + } + return 0; +} + +static void +_sleep_accounting(struct kgsl_device *device) +{ + kgsl_pwrctrl_busy_time(device, false); + device->pwrctrl.busy.start.tv_sec = 0; + device->pwrctrl.time = 0; + kgsl_pwrscale_sleep(device); +} + +static int +_sleep(struct kgsl_device *device) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + switch (device->state) { + case KGSL_STATE_ACTIVE: + if (!device->ftbl->isidle(device)) { + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + return -EBUSY; + } + /* fall through */ + case KGSL_STATE_NAP: + 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); + _sleep_accounting(device); + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_OFF, KGSL_STATE_SLEEP); + kgsl_mmu_disable_clk(&device->mmu); + kgsl_pwrctrl_set_state(device, KGSL_STATE_SLEEP); + wake_unlock(&device->idle_wakelock); + pm_qos_update_request(&device->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + break; + case KGSL_STATE_SLEEP: + case KGSL_STATE_SLUMBER: + break; + default: + KGSL_PWR_WARN(device, "unhandled state %s\n", + kgsl_pwrstate_to_str(device->state)); + break; + } + return 0; +} + +static int +_slumber(struct kgsl_device *device) +{ + switch (device->state) { + case KGSL_STATE_ACTIVE: + if (!device->ftbl->isidle(device)) { + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + device->pwrctrl.restore_slumber = true; + return -EBUSY; + } + /* fall through */ + case KGSL_STATE_NAP: + case KGSL_STATE_SLEEP: + del_timer_sync(&device->idle_timer); + if (!device->pwrctrl.strtstp_sleepwake) + kgsl_pwrctrl_pwrlevel_change(device, + KGSL_PWRLEVEL_NOMINAL); + device->pwrctrl.restore_slumber = true; + device->ftbl->suspend_context(device); + device->ftbl->stop(device); + _sleep_accounting(device); + kgsl_pwrctrl_set_state(device, KGSL_STATE_SLUMBER); + if (device->idle_wakelock.name) + wake_unlock(&device->idle_wakelock); + pm_qos_update_request(&device->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + break; + case KGSL_STATE_SLUMBER: + break; + default: + KGSL_PWR_WARN(device, "unhandled state %s\n", + kgsl_pwrstate_to_str(device->state)); + break; + } + return 0; +} + +/******************************************************************/ +/* Caller must hold the device mutex. */ +int kgsl_pwrctrl_sleep(struct kgsl_device *device) +{ + int status = 0; + KGSL_PWR_INFO(device, "sleep device %d\n", device->id); + + /* Work through the legal state transitions */ + switch (device->requested_state) { + case KGSL_STATE_NAP: + status = _nap(device); + break; + case KGSL_STATE_SLEEP: + status = _sleep(device); + break; + case KGSL_STATE_SLUMBER: + status = _slumber(device); + break; + default: + KGSL_PWR_INFO(device, "bad state request 0x%x\n", + device->requested_state); + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + status = -EINVAL; + break; + } + return status; +} +EXPORT_SYMBOL(kgsl_pwrctrl_sleep); + +/******************************************************************/ +/* Caller must hold the device mutex. */ +void kgsl_pwrctrl_wake(struct kgsl_device *device) +{ + int status; + kgsl_pwrctrl_request_state(device, KGSL_STATE_ACTIVE); + switch (device->state) { + case KGSL_STATE_SLUMBER: + status = device->ftbl->start(device, 0); + if (status) { + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + KGSL_DRV_ERR(device, "start failed %d\n", status); + break; + } + /* fall through */ + case KGSL_STATE_SLEEP: + kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_ON); + kgsl_pwrscale_wake(device); + /* fall through */ + case KGSL_STATE_NAP: + /* Turn on the core clocks */ + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_ON, KGSL_STATE_ACTIVE); + /* Enable state before turning on irq */ + kgsl_pwrctrl_set_state(device, KGSL_STATE_ACTIVE); + 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); + if (device->pwrctrl.restore_slumber == false) + pm_qos_update_request(&device->pm_qos_req_dma, + GPU_SWFI_LATENCY); + case KGSL_STATE_ACTIVE: + break; + default: + KGSL_PWR_WARN(device, "unhandled state %s\n", + kgsl_pwrstate_to_str(device->state)); + kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE); + break; + } +} +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_STATE_ACTIVE); + 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_STATE_SLEEP); + kgsl_mmu_disable_clk(&device->mmu); + kgsl_pwrctrl_pwrrail(device, KGSL_PWRFLAGS_OFF); +} +EXPORT_SYMBOL(kgsl_pwrctrl_disable); + +void kgsl_pwrctrl_set_state(struct kgsl_device *device, unsigned int state) +{ + trace_kgsl_pwr_set_state(device, state); + device->state = state; + device->requested_state = KGSL_STATE_NONE; +} +EXPORT_SYMBOL(kgsl_pwrctrl_set_state); + +void kgsl_pwrctrl_request_state(struct kgsl_device *device, unsigned int state) +{ + if (state != KGSL_STATE_NONE && state != device->requested_state) + trace_kgsl_pwr_request_state(device, state); + device->requested_state = state; +} +EXPORT_SYMBOL(kgsl_pwrctrl_request_state); + +const char *kgsl_pwrstate_to_str(unsigned int state) +{ + switch (state) { + case KGSL_STATE_NONE: + return "NONE"; + case KGSL_STATE_INIT: + return "INIT"; + case KGSL_STATE_ACTIVE: + return "ACTIVE"; + case KGSL_STATE_NAP: + return "NAP"; + case KGSL_STATE_SLEEP: + return "SLEEP"; + case KGSL_STATE_SUSPEND: + return "SUSPEND"; + case KGSL_STATE_HUNG: + return "HUNG"; + case KGSL_STATE_DUMP_AND_RECOVER: + return "DNR"; + case KGSL_STATE_SLUMBER: + return "SLUMBER"; + default: + break; + } + return "UNKNOWN"; +} +EXPORT_SYMBOL(kgsl_pwrstate_to_str); + diff --git a/drivers/gpu/msm/kgsl_pwrctrl.h b/drivers/gpu/msm/kgsl_pwrctrl.h new file mode 100644 index 0000000000000000000000000000000000000000..1e5c21c2c2fe9494c618d179159e832fd6e9abb4 --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrctrl.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ON 1 +#define KGSL_PWRFLAGS_OFF 0 + +#define KGSL_PWRLEVEL_TURBO 0 +#define KGSL_PWRLEVEL_NOMINAL 1 +#define KGSL_PWRLEVEL_LAST_OFFSET 2 + +#define KGSL_MAX_CLKS 5 + +struct platform_device; + +struct kgsl_busy { + struct timeval start; + struct timeval stop; + int on_time; + int time; + int on_time_old; + int time_old; + unsigned int no_nap_cnt; +}; + +struct kgsl_pwrctrl { + int interrupt_num; + 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 default_pwrlevel; + unsigned int num_pwrlevels; + unsigned int interval_timeout; + bool strtstp_sleepwake; + struct regulator *gpu_reg; + uint32_t pcl; + unsigned int nap_allowed; + unsigned int idle_needed; + const char *irq_name; + s64 time; + struct kgsl_busy busy; + unsigned int restore_slumber; +}; + +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; +} + +void kgsl_pwrctrl_set_state(struct kgsl_device *device, unsigned int state); +void kgsl_pwrctrl_request_state(struct kgsl_device *device, unsigned int state); +#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 0000000000000000000000000000000000000000..6fb93260d97cbeb9f8cfa5aef0f758a198d926ca --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale.c @@ -0,0 +1,374 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even 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" + +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 +#ifdef CONFIG_MSM_SLEEP_STATS_DEVICE + &kgsl_pwrscale_policy_idlestats, +#endif +#ifdef CONFIG_MSM_DCVS + &kgsl_pwrscale_policy_msm, +#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", + device->pwrscale.policy->name); + if (device->pwrscale.enabled == 0) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + " (disabled)"); + ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); + } else + ret = snprintf(buf, PAGE_SIZE, "none\n"); + + return ret; +} + +PWRSCALE_ATTR(policy, 0664, 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 +}; + +#define PWRSCALE_ACTIVE(_d) \ + ((_d)->pwrscale.policy && (_d)->pwrscale.enabled) + +void kgsl_pwrscale_sleep(struct kgsl_device *device) +{ + if (PWRSCALE_ACTIVE(device) && 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 (PWRSCALE_ACTIVE(device) && 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 (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->busy) + if ((!device->pwrscale.gpu_busy) && + (device->requested_state != KGSL_STATE_SLUMBER)) + device->pwrscale.policy->busy(device, + &device->pwrscale); + device->pwrscale.gpu_busy = 1; +} + +void kgsl_pwrscale_idle(struct kgsl_device *device) +{ + if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->idle) + if (device->requested_state != KGSL_STATE_SLUMBER && + device->requested_state != KGSL_STATE_SLEEP) + device->pwrscale.policy->idle(device, + &device->pwrscale); + device->pwrscale.gpu_busy = 0; +} +EXPORT_SYMBOL(kgsl_pwrscale_idle); + +void kgsl_pwrscale_disable(struct kgsl_device *device) +{ + device->pwrscale.enabled = 0; +} +EXPORT_SYMBOL(kgsl_pwrscale_disable); + +void kgsl_pwrscale_enable(struct kgsl_device *device) +{ + device->pwrscale.enabled = 1; +} +EXPORT_SYMBOL(kgsl_pwrscale_enable); + +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); + kgsl_pwrctrl_pwrlevel_change(device, + device->pwrctrl.thermal_pwrlevel); + } + 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->pwrctrl.num_pwrlevels < 3) { + ret = -EINVAL; + goto done; + } + + if (device->pwrscale.policy != NULL) + _kgsl_pwrscale_detach_policy(device); + + device->pwrscale.policy = policy; + + /* Pwrscale is enabled by default at attach time */ + kgsl_pwrscale_enable(device); + + 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 0000000000000000000000000000000000000000..34698cddd8fd2687ab1acd5f27fc19e52188475a --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale.h @@ -0,0 +1,82 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; + int gpu_busy; + int enabled; +}; + +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; +extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats; +extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_msm; + +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); + +void kgsl_pwrscale_enable(struct kgsl_device *device); +void kgsl_pwrscale_disable(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_idlestats.c b/drivers/gpu/msm/kgsl_pwrscale_idlestats.c new file mode 100644 index 0000000000000000000000000000000000000000..71af8932cdae98ed0b674b0103642f2d0a56c0bc --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale_idlestats.c @@ -0,0 +1,231 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_pwrscale.h" +#include "kgsl_device.h" + +#define MAX_CORES 4 +struct _cpu_info { + spinlock_t lock; + struct notifier_block cpu_nb; + u64 start[MAX_CORES]; + u64 end[MAX_CORES]; + int curr_freq[MAX_CORES]; + int max_freq[MAX_CORES]; +}; + +struct idlestats_priv { + char name[32]; + struct msm_idle_stats_device idledev; + struct kgsl_device *device; + struct msm_idle_pulse pulse; + struct _cpu_info cpu_info; +}; + +static int idlestats_cpufreq_notifier( + struct notifier_block *nb, + unsigned long val, void *data) +{ + struct _cpu_info *cpu = container_of(nb, + struct _cpu_info, cpu_nb); + struct cpufreq_freqs *freq = data; + + if (val != CPUFREQ_POSTCHANGE) + return 0; + + spin_lock(&cpu->lock); + if (freq->cpu < num_possible_cpus()) + cpu->curr_freq[freq->cpu] = freq->new / 1000; + spin_unlock(&cpu->lock); + + return 0; +} + +static void idlestats_get_sample(struct msm_idle_stats_device *idledev, + struct msm_idle_pulse *pulse) +{ + struct kgsl_power_stats stats; + struct idlestats_priv *priv = container_of(idledev, + struct idlestats_priv, idledev); + struct kgsl_device *device = priv->device; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + mutex_lock(&device->mutex); + /* If the GPU is asleep, don't wake it up - assume that we + are idle */ + + if (device->state == KGSL_STATE_ACTIVE) { + device->ftbl->power_stats(device, &stats); + pulse->busy_start_time = pwr->time - stats.busy_time; + pulse->busy_interval = stats.busy_time; + } else { + pulse->busy_start_time = pwr->time; + pulse->busy_interval = 0; + } + pulse->wait_interval = 0; + mutex_unlock(&device->mutex); +} + +static void idlestats_busy(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct idlestats_priv *priv = pwrscale->priv; + int i, busy, nr_cpu = 1; + + if (priv->pulse.busy_start_time != 0) { + priv->pulse.wait_interval = 0; + /* Calculate the total CPU busy time for this GPU pulse */ + for (i = 0; i < num_possible_cpus(); i++) { + spin_lock(&priv->cpu_info.lock); + if (cpu_online(i)) { + priv->cpu_info.end[i] = + (u64)ktime_to_us(ktime_get()) - + get_cpu_idle_time_us(i, NULL); + busy = priv->cpu_info.end[i] - + priv->cpu_info.start[i]; + /* Normalize the busy time by frequency */ + busy = priv->cpu_info.curr_freq[i] * + (busy / priv->cpu_info.max_freq[i]); + priv->pulse.wait_interval += busy; + nr_cpu++; + } + spin_unlock(&priv->cpu_info.lock); + } + priv->pulse.wait_interval /= nr_cpu; + msm_idle_stats_idle_end(&priv->idledev, &priv->pulse); + } + priv->pulse.busy_start_time = ktime_to_us(ktime_get()); +} + +static void idlestats_idle(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + int i, nr_cpu; + struct kgsl_power_stats stats; + struct idlestats_priv *priv = pwrscale->priv; + + /* This is called from within a mutex protected function, so + no additional locking required */ + device->ftbl->power_stats(device, &stats); + + /* If total_time is zero, then we don't have + any interesting statistics to store */ + if (stats.total_time == 0) { + priv->pulse.busy_start_time = 0; + return; + } + + priv->pulse.busy_interval = stats.busy_time; + nr_cpu = num_possible_cpus(); + for (i = 0; i < nr_cpu; i++) + if (cpu_online(i)) + priv->cpu_info.start[i] = + (u64)ktime_to_us(ktime_get()) - + get_cpu_idle_time_us(i, NULL); + + msm_idle_stats_idle_start(&priv->idledev); +} + +static void idlestats_sleep(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct idlestats_priv *priv = pwrscale->priv; + msm_idle_stats_update_event(&priv->idledev, + MSM_IDLE_STATS_EVENT_IDLE_TIMER_EXPIRED); +} + +static void idlestats_wake(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + /* Use highest perf level on wake-up from + sleep for better performance */ + kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO); +} + +static int idlestats_init(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct idlestats_priv *priv; + struct cpufreq_policy cpu_policy; + int ret, i; + + priv = pwrscale->priv = kzalloc(sizeof(struct idlestats_priv), + GFP_KERNEL); + if (pwrscale->priv == NULL) + return -ENOMEM; + + snprintf(priv->name, sizeof(priv->name), "idle_stats_%s", + device->name); + + priv->device = device; + + priv->idledev.name = (const char *) priv->name; + priv->idledev.get_sample = idlestats_get_sample; + + spin_lock_init(&priv->cpu_info.lock); + priv->cpu_info.cpu_nb.notifier_call = + idlestats_cpufreq_notifier; + ret = cpufreq_register_notifier(&priv->cpu_info.cpu_nb, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret) + goto err; + for (i = 0; i < num_possible_cpus(); i++) { + cpufreq_frequency_table_cpuinfo(&cpu_policy, + cpufreq_frequency_get_table(i)); + priv->cpu_info.max_freq[i] = cpu_policy.max / 1000; + priv->cpu_info.curr_freq[i] = cpu_policy.max / 1000; + } + ret = msm_idle_stats_register_device(&priv->idledev); +err: + if (ret) { + kfree(pwrscale->priv); + pwrscale->priv = NULL; + } + + return ret; +} + +static void idlestats_close(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct idlestats_priv *priv = pwrscale->priv; + + if (pwrscale->priv == NULL) + return; + + cpufreq_unregister_notifier(&priv->cpu_info.cpu_nb, + CPUFREQ_TRANSITION_NOTIFIER); + msm_idle_stats_deregister_device(&priv->idledev); + + kfree(pwrscale->priv); + pwrscale->priv = NULL; +} + +struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats = { + .name = "idlestats", + .init = idlestats_init, + .idle = idlestats_idle, + .busy = idlestats_busy, + .sleep = idlestats_sleep, + .wake = idlestats_wake, + .close = idlestats_close +}; diff --git a/drivers/gpu/msm/kgsl_pwrscale_msm.c b/drivers/gpu/msm/kgsl_pwrscale_msm.c new file mode 100644 index 0000000000000000000000000000000000000000..61d4b2d188505c16f1eb3eb9f92e735e8ae77029 --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale_msm.c @@ -0,0 +1,200 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "kgsl.h" +#include "kgsl_pwrscale.h" +#include "kgsl_device.h" +#include "a2xx_reg.h" + +struct msm_priv { + struct kgsl_device *device; + int enabled; + int handle; + unsigned int cur_freq; + struct msm_dcvs_idle idle_source; + struct msm_dcvs_freq freq_sink; + struct msm_dcvs_core_info *core_info; +}; + +static int msm_idle_enable(struct msm_dcvs_idle *self, + enum msm_core_control_event event) +{ + struct msm_priv *priv = container_of(self, struct msm_priv, + idle_source); + + switch (event) { + case MSM_DCVS_ENABLE_IDLE_PULSE: + priv->enabled = true; + break; + case MSM_DCVS_DISABLE_IDLE_PULSE: + priv->enabled = false; + break; + case MSM_DCVS_ENABLE_HIGH_LATENCY_MODES: + case MSM_DCVS_DISABLE_HIGH_LATENCY_MODES: + break; + } + return 0; +} + +/* Set the requested frequency if it is within 5MHz (delta) of a + * supported frequency. + */ +static int msm_set_freq(struct msm_dcvs_freq *self, + unsigned int freq) +{ + int i, delta = 5000000; + struct msm_priv *priv = container_of(self, struct msm_priv, + freq_sink); + struct kgsl_device *device = priv->device; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + /* msm_dcvs manager uses frequencies in kHz */ + freq *= 1000; + for (i = 0; i < pwr->num_pwrlevels; i++) + if (abs(pwr->pwrlevels[i].gpu_freq - freq) < delta) + break; + if (i == pwr->num_pwrlevels) + return 0; + + mutex_lock(&device->mutex); + kgsl_pwrctrl_pwrlevel_change(device, i); + priv->cur_freq = pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq; + mutex_unlock(&device->mutex); + + /* return current frequency in kHz */ + return priv->cur_freq / 1000; +} + +static unsigned int msm_get_freq(struct msm_dcvs_freq *self) +{ + struct msm_priv *priv = container_of(self, struct msm_priv, + freq_sink); + /* return current frequency in kHz */ + return priv->cur_freq / 1000; +} + +static void msm_busy(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct msm_priv *priv = pwrscale->priv; + if (priv->enabled) + msm_dcvs_idle(priv->handle, MSM_DCVS_IDLE_EXIT, 0); + return; +} + +static void msm_idle(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct msm_priv *priv = pwrscale->priv; + unsigned int rb_rptr, rb_wptr; + kgsl_regread(device, REG_CP_RB_RPTR, &rb_rptr); + kgsl_regread(device, REG_CP_RB_WPTR, &rb_wptr); + + if (priv->enabled && (rb_rptr == rb_wptr)) + msm_dcvs_idle(priv->handle, MSM_DCVS_IDLE_ENTER, 0); + + return; +} + +static void msm_sleep(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + /* do we need to reset any parameters here? */ +} + +static int msm_init(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct msm_priv *priv; + struct msm_dcvs_freq_entry *tbl; + int i, ret, low_level; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct platform_device *pdev = + container_of(device->parentdev, struct platform_device, dev); + struct kgsl_device_platform_data *pdata = pdev->dev.platform_data; + + priv = pwrscale->priv = kzalloc(sizeof(struct msm_priv), + GFP_KERNEL); + if (pwrscale->priv == NULL) + return -ENOMEM; + + priv->core_info = pdata->core_info; + tbl = priv->core_info->freq_tbl; + /* Fill in frequency table from low to high, reversing order. */ + low_level = pwr->num_pwrlevels - KGSL_PWRLEVEL_LAST_OFFSET; + for (i = 0; i <= low_level; i++) + tbl[i].freq = + pwr->pwrlevels[low_level - i].gpu_freq / 1000; + ret = msm_dcvs_register_core(device->name, 0, priv->core_info); + if (ret) { + KGSL_PWR_ERR(device, "msm_dcvs_register_core failed"); + goto err; + } + + priv->device = device; + priv->idle_source.enable = msm_idle_enable; + priv->idle_source.core_name = device->name; + priv->handle = msm_dcvs_idle_source_register(&priv->idle_source); + if (priv->handle < 0) { + ret = priv->handle; + KGSL_PWR_ERR(device, "msm_dcvs_idle_source_register failed\n"); + goto err; + } + + priv->freq_sink.core_name = device->name; + priv->freq_sink.set_frequency = msm_set_freq; + priv->freq_sink.get_frequency = msm_get_freq; + ret = msm_dcvs_freq_sink_register(&priv->freq_sink); + if (ret >= 0) { + if (device->ftbl->isidle(device)) { + device->pwrscale.gpu_busy = 0; + msm_dcvs_idle(priv->handle, MSM_DCVS_IDLE_ENTER, 0); + } else { + device->pwrscale.gpu_busy = 1; + } + return 0; + } + + KGSL_PWR_ERR(device, "msm_dcvs_freq_sink_register failed\n"); + msm_dcvs_idle_source_unregister(&priv->idle_source); + +err: + kfree(pwrscale->priv); + pwrscale->priv = NULL; + + return ret; +} + +static void msm_close(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct msm_priv *priv = pwrscale->priv; + + if (pwrscale->priv == NULL) + return; + msm_dcvs_idle_source_unregister(&priv->idle_source); + msm_dcvs_freq_sink_unregister(&priv->freq_sink); + kfree(pwrscale->priv); + pwrscale->priv = NULL; +} + +struct kgsl_pwrscale_policy kgsl_pwrscale_policy_msm = { + .name = "msm", + .init = msm_init, + .idle = msm_idle, + .busy = msm_busy, + .sleep = msm_sleep, + .close = msm_close, +}; diff --git a/drivers/gpu/msm/kgsl_pwrscale_trustzone.c b/drivers/gpu/msm/kgsl_pwrscale_trustzone.c new file mode 100644 index 0000000000000000000000000000000000000000..ad1e7ed751d748422f394696a24cfe4fbf06b071 --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale_trustzone.c @@ -0,0 +1,215 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#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; + unsigned int skip_cnt; +}; +spinlock_t tz_lock; + +#define SWITCH_OFF 200 +#define SWITCH_OFF_RESET_TH 40 +#define SKIP_COUNTER 500 +#define TZ_RESET_ID 0x3 +#define TZ_UPDATE_ID 0x4 + +#ifdef CONFIG_MSM_SCM +/* Trap into the TrustZone, and call funcs there. */ +static int __secure_tz_entry(u32 cmd, u32 val, u32 id) +{ + int ret; + spin_lock(&tz_lock); + __iowmb(); + ret = scm_call_atomic2(SCM_SVC_IO, cmd, val, id); + spin_unlock(&tz_lock); + return ret; +} +#else +static int __secure_tz_entry(u32 cmd, u32 val, u32 id) +{ + return 0; +} +#endif /* CONFIG_MSM_SCM */ + +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 && + device->pwrctrl.restore_slumber == 0) + kgsl_pwrctrl_pwrlevel_change(device, + device->pwrctrl.default_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, idle; + + /* 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 == 0) { + if (priv->no_switch_cnt > SWITCH_OFF) { + priv->skip_cnt++; + if (priv->skip_cnt > SKIP_COUNTER) { + priv->no_switch_cnt -= SWITCH_OFF_RESET_TH; + priv->skip_cnt = 0; + } + return; + } + priv->no_switch_cnt++; + } else { + priv->no_switch_cnt = 0; + } + + idle = stats.total_time - stats.busy_time; + idle = (idle > 0) ? idle : 0; + val = __secure_tz_entry(TZ_UPDATE_ID, idle, device->id); + if (val) + kgsl_pwrctrl_pwrlevel_change(device, + pwr->active_pwrlevel + val); +} + +static void tz_busy(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + device->on_time = ktime_to_us(ktime_get()); +} + +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, device->id); + 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() || cpu_is_msm8960() || cpu_is_apq8064() || + cpu_is_msm8930())) + return -EINVAL; + + priv = pwrscale->priv = kzalloc(sizeof(struct tz_priv), GFP_KERNEL); + if (pwrscale->priv == NULL) + return -ENOMEM; + + priv->governor = TZ_GOVERNOR_ONDEMAND; + spin_lock_init(&tz_lock); + 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, + .busy = tz_busy, + .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 0000000000000000000000000000000000000000..a2dd6492d1586caf3e61f129900438e77d9b4d33 --- /dev/null +++ b/drivers/gpu/msm/kgsl_sharedmem.c @@ -0,0 +1,817 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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_sharedmem.h" +#include "kgsl_cffdump.h" +#include "kgsl_device.h" + +/* An attribute for showing per-process memory statistics */ +struct kgsl_mem_entry_attribute { + struct attribute attr; + int memtype; + ssize_t (*show)(struct kgsl_process_private *priv, + int type, char *buf); +}; + +#define to_mem_entry_attr(a) \ +container_of(a, struct kgsl_mem_entry_attribute, attr) + +#define __MEM_ENTRY_ATTR(_type, _name, _show) \ +{ \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .memtype = _type, \ + .show = _show, \ +} + +/* + * A structure to hold the attributes for a particular memory type. + * For each memory type in each process we store the current and maximum + * memory usage and display the counts in sysfs. This structure and + * the following macro allow us to simplify the definition for those + * adding new memory types + */ + +struct mem_entry_stats { + int memtype; + struct kgsl_mem_entry_attribute attr; + struct kgsl_mem_entry_attribute max_attr; +}; + + +#define MEM_ENTRY_STAT(_type, _name) \ +{ \ + .memtype = _type, \ + .attr = __MEM_ENTRY_ATTR(_type, _name, mem_entry_show), \ + .max_attr = __MEM_ENTRY_ATTR(_type, _name##_max, \ + mem_entry_max_show), \ +} + + +/* + * One page allocation for a guard region to protect against over-zealous + * GPU pre-fetch + */ + +static struct page *kgsl_guard_page; + +/** + * Given a kobj, find the process structure attached to it + */ + +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; +} + +/** + * Show the current amount of memory allocated for the given memtype + */ + +static ssize_t +mem_entry_show(struct kgsl_process_private *priv, int type, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", priv->stats[type].cur); +} + +/** + * Show the maximum memory allocated for the given memtype through the life of + * the process + */ + +static ssize_t +mem_entry_max_show(struct kgsl_process_private *priv, int type, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", priv->stats[type].max); +} + + +static void mem_entry_sysfs_release(struct kobject *kobj) +{ +} + +static ssize_t mem_entry_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct kgsl_mem_entry_attribute *pattr = to_mem_entry_attr(attr); + struct kgsl_process_private *priv; + ssize_t ret; + + mutex_lock(&kgsl_driver.process_mutex); + priv = _get_priv_from_kobj(kobj); + + if (priv && pattr->show) + ret = pattr->show(priv, pattr->memtype, buf); + else + ret = -EIO; + + mutex_unlock(&kgsl_driver.process_mutex); + return ret; +} + +static const struct sysfs_ops mem_entry_sysfs_ops = { + .show = mem_entry_sysfs_show, +}; + +static struct kobj_type ktype_mem_entry = { + .sysfs_ops = &mem_entry_sysfs_ops, + .default_attrs = NULL, + .release = mem_entry_sysfs_release +}; + +static struct mem_entry_stats mem_stats[] = { + MEM_ENTRY_STAT(KGSL_MEM_ENTRY_KERNEL, kernel), +#ifdef CONFIG_ANDROID_PMEM + MEM_ENTRY_STAT(KGSL_MEM_ENTRY_PMEM, pmem), +#endif +#ifdef CONFIG_ASHMEM + MEM_ENTRY_STAT(KGSL_MEM_ENTRY_ASHMEM, ashmem), +#endif + MEM_ENTRY_STAT(KGSL_MEM_ENTRY_USER, user), +#ifdef CONFIG_ION + MEM_ENTRY_STAT(KGSL_MEM_ENTRY_ION, ion), +#endif +}; + +void +kgsl_process_uninit_sysfs(struct kgsl_process_private *private) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mem_stats); i++) { + sysfs_remove_file(&private->kobj, &mem_stats[i].attr.attr); + sysfs_remove_file(&private->kobj, + &mem_stats[i].max_attr.attr); + } + + kobject_put(&private->kobj); +} + +void +kgsl_process_init_sysfs(struct kgsl_process_private *private) +{ + unsigned char name[16]; + int i, ret; + + snprintf(name, sizeof(name), "%d", private->pid); + + if (kobject_init_and_add(&private->kobj, &ktype_mem_entry, + kgsl_driver.prockobj, name)) + return; + + for (i = 0; i < ARRAY_SIZE(mem_stats); i++) { + /* We need to check the value of sysfs_create_file, but we + * don't really care if it passed or not */ + + ret = sysfs_create_file(&private->kobj, + &mem_stats[i].attr.attr); + ret = sysfs_create_file(&private->kobj, + &mem_stats[i].max_attr.attr); + } +} + +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, "page_alloc", 10)) + val = kgsl_driver.stats.page_alloc; + else if (!strncmp(attr->attr.name, "page_alloc_max", 14)) + val = kgsl_driver.stats.page_alloc_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 += snprintf(buf + len, PAGE_SIZE - len, "%d ", + kgsl_driver.stats.histogram[i]); + + len += snprintf(buf + len, PAGE_SIZE - 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(page_alloc, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(page_alloc_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_page_alloc, + &dev_attr_page_alloc_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; + } +} + +static void outer_cache_range_op_sg(struct scatterlist *sg, int sglen, int op) +{ + struct scatterlist *s; + int i; + + for_each_sg(sg, s, sglen, i) { + unsigned int paddr = kgsl_get_sg_pa(s); + _outer_cache_range_op(op, paddr, s->length); + } +} + +#else +static void outer_cache_range_op_sg(struct scatterlist *sg, int sglen, int op) +{ +} +#endif + +static int kgsl_page_alloc_vmfault(struct kgsl_memdesc *memdesc, + struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + unsigned long offset; + struct page *page; + int i; + + offset = (unsigned long) vmf->virtual_address - vma->vm_start; + + i = offset >> PAGE_SHIFT; + page = sg_page(&memdesc->sg[i]); + if (page == NULL) + return VM_FAULT_SIGBUS; + + get_page(page); + + vmf->page = page; + return 0; +} + +static int kgsl_page_alloc_vmflags(struct kgsl_memdesc *memdesc) +{ + return VM_RESERVED | VM_DONTEXPAND; +} + +static void kgsl_page_alloc_free(struct kgsl_memdesc *memdesc) +{ + int i = 0; + struct scatterlist *sg; + int sglen = memdesc->sglen; + + /* Don't free the guard page if it was used */ + if (memdesc->flags & KGSL_MEMDESC_GUARD_PAGE) + sglen--; + + kgsl_driver.stats.page_alloc -= memdesc->size; + + if (memdesc->hostptr) { + vunmap(memdesc->hostptr); + kgsl_driver.stats.vmalloc -= memdesc->size; + } + if (memdesc->sg) + for_each_sg(memdesc->sg, sg, sglen, i) + __free_page(sg_page(sg)); +} + +static int kgsl_contiguous_vmflags(struct kgsl_memdesc *memdesc) +{ + return VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND; +} + +/* + * kgsl_page_alloc_map_kernel - Map the memory in memdesc to kernel address + * space + * + * @memdesc - The memory descriptor which contains information about the memory + * + * Return: 0 on success else error code + */ +static int kgsl_page_alloc_map_kernel(struct kgsl_memdesc *memdesc) +{ + if (!memdesc->hostptr) { + pgprot_t page_prot = pgprot_writecombine(PAGE_KERNEL); + struct page **pages = NULL; + struct scatterlist *sg; + int sglen = memdesc->sglen; + int i; + + /* Don't map the guard page if it exists */ + if (memdesc->flags & KGSL_MEMDESC_GUARD_PAGE) + sglen--; + + /* create a list of pages to call vmap */ + pages = vmalloc(sglen * sizeof(struct page *)); + if (!pages) { + KGSL_CORE_ERR("vmalloc(%d) failed\n", + sglen * sizeof(struct page *)); + return -ENOMEM; + } + for_each_sg(memdesc->sg, sg, sglen, i) + pages[i] = sg_page(sg); + memdesc->hostptr = vmap(pages, sglen, + VM_IOREMAP, page_prot); + KGSL_STATS_ADD(memdesc->size, kgsl_driver.stats.vmalloc, + kgsl_driver.stats.vmalloc_max); + vfree(pages); + } + if (!memdesc->hostptr) + return -ENOMEM; + + return 0; +} + +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); +} + +/* Global - also used by kgsl_drm.c */ +struct kgsl_memdesc_ops kgsl_page_alloc_ops = { + .free = kgsl_page_alloc_free, + .vmflags = kgsl_page_alloc_vmflags, + .vmfault = kgsl_page_alloc_vmfault, + .map_kernel_mem = kgsl_page_alloc_map_kernel, +}; +EXPORT_SYMBOL(kgsl_page_alloc_ops); + +static struct kgsl_memdesc_ops kgsl_ebimem_ops = { + .free = kgsl_ebimem_free, + .vmflags = kgsl_contiguous_vmflags, + .vmfault = kgsl_contiguous_vmfault, +}; + +static struct kgsl_memdesc_ops kgsl_coherent_ops = { + .free = kgsl_coherent_free, +}; + +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; + } + + outer_cache_range_op_sg(memdesc->sg, memdesc->sglen, op); +} +EXPORT_SYMBOL(kgsl_cache_range_op); + +static int +_kgsl_sharedmem_page_alloc(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, unsigned int protflags) +{ + int order, ret = 0; + int sglen = PAGE_ALIGN(size) / PAGE_SIZE; + int i; + + /* + * Add guard page to the end of the allocation when the + * IOMMU is in use. + */ + + if (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_IOMMU) + sglen++; + + memdesc->size = size; + memdesc->pagetable = pagetable; + memdesc->priv = KGSL_MEMFLAGS_CACHED; + memdesc->ops = &kgsl_page_alloc_ops; + + memdesc->sg = kgsl_sg_alloc(sglen); + + if (memdesc->sg == NULL) { + KGSL_CORE_ERR("vmalloc(%d) failed\n", + sglen * sizeof(struct scatterlist)); + ret = -ENOMEM; + goto done; + } + + kmemleak_not_leak(memdesc->sg); + + memdesc->sglen = sglen; + sg_init_table(memdesc->sg, sglen); + + for (i = 0; i < PAGE_ALIGN(size) / PAGE_SIZE; i++) { + struct page *page = alloc_page(GFP_KERNEL | __GFP_ZERO | + __GFP_HIGHMEM); + if (!page) { + ret = -ENOMEM; + memdesc->sglen = i; + goto done; + } + flush_dcache_page(page); + sg_set_page(&memdesc->sg[i], page, PAGE_SIZE, 0); + } + + /* ADd the guard page to the end of the sglist */ + + if (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_IOMMU) { + if (kgsl_guard_page == NULL) + kgsl_guard_page = alloc_page(GFP_KERNEL | __GFP_ZERO | + __GFP_HIGHMEM); + + if (kgsl_guard_page != NULL) { + sg_set_page(&memdesc->sg[sglen - 1], kgsl_guard_page, + PAGE_SIZE, 0); + memdesc->flags |= KGSL_MEMDESC_GUARD_PAGE; + } else + memdesc->sglen--; + } + + outer_cache_range_op_sg(memdesc->sg, memdesc->sglen, + KGSL_CACHE_OP_FLUSH); + + ret = kgsl_mmu_map(pagetable, memdesc, protflags); + + if (ret) + goto done; + + KGSL_STATS_ADD(size, kgsl_driver.stats.page_alloc, + kgsl_driver.stats.page_alloc_max); + + order = get_order(size); + + if (order < 16) + kgsl_driver.stats.histogram[order]++; + +done: + if (ret) + kgsl_sharedmem_free(memdesc); + + return ret; +} + +int +kgsl_sharedmem_page_alloc(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size) +{ + int ret = 0; + BUG_ON(size == 0); + + size = ALIGN(size, PAGE_SIZE * 2); + + ret = _kgsl_sharedmem_page_alloc(memdesc, pagetable, size, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + if (!ret) + ret = kgsl_page_alloc_map_kernel(memdesc); + if (ret) + kgsl_sharedmem_free(memdesc); + return ret; +} +EXPORT_SYMBOL(kgsl_sharedmem_page_alloc); + +int +kgsl_sharedmem_page_alloc_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, int flags) +{ + unsigned int protflags; + + BUG_ON(size == 0); + + protflags = GSL_PT_PAGE_RV; + if (!(flags & KGSL_MEMFLAGS_GPUREADONLY)) + protflags |= GSL_PT_PAGE_WV; + + return _kgsl_sharedmem_page_alloc(memdesc, pagetable, size, + protflags); +} +EXPORT_SYMBOL(kgsl_sharedmem_page_alloc_user); + +int +kgsl_sharedmem_alloc_coherent(struct kgsl_memdesc *memdesc, size_t size) +{ + int result = 0; + + size = ALIGN(size, PAGE_SIZE); + + memdesc->size = size; + memdesc->ops = &kgsl_coherent_ops; + + 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); + result = -ENOMEM; + goto err; + } + + result = memdesc_sg_phys(memdesc, memdesc->physaddr, size); + if (result) + goto err; + + /* Record statistics */ + + KGSL_STATS_ADD(size, kgsl_driver.stats.coherent, + kgsl_driver.stats.coherent_max); + +err: + if (result) + kgsl_sharedmem_free(memdesc); + + return result; +} +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 && memdesc->ops->free) + memdesc->ops->free(memdesc); + + kgsl_sg_free(memdesc->sg, memdesc->sglen); + + 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 = 0; + + memdesc->size = size; + memdesc->pagetable = pagetable; + memdesc->ops = &kgsl_ebimem_ops; + 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; + } + + result = memdesc_sg_phys(memdesc, memdesc->physaddr, size); + + if (result) + goto err; + + result = kgsl_mmu_map(pagetable, memdesc, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + + if (result) + goto err; + + KGSL_STATS_ADD(size, kgsl_driver.stats.coherent, + kgsl_driver.stats.coherent_max); + +err: + if (result) + kgsl_sharedmem_free(memdesc); + + 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) +{ + uint32_t *src; + BUG_ON(memdesc == NULL || memdesc->hostptr == NULL || dst == NULL); + WARN_ON(offsetbytes % sizeof(uint32_t) != 0); + if (offsetbytes % sizeof(uint32_t) != 0) + return -EINVAL; + + WARN_ON(offsetbytes + sizeof(uint32_t) > memdesc->size); + if (offsetbytes + sizeof(uint32_t) > memdesc->size) + return -ERANGE; + src = (uint32_t *)(memdesc->hostptr + offsetbytes); + *dst = *src; + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_readl); + +int +kgsl_sharedmem_writel(const struct kgsl_memdesc *memdesc, + unsigned int offsetbytes, + uint32_t src) +{ + uint32_t *dst; + BUG_ON(memdesc == NULL || memdesc->hostptr == NULL); + WARN_ON(offsetbytes % sizeof(uint32_t) != 0); + if (offsetbytes % sizeof(uint32_t) != 0) + return -EINVAL; + + WARN_ON(offsetbytes + sizeof(uint32_t) > memdesc->size); + if (offsetbytes + sizeof(uint32_t) > memdesc->size) + return -ERANGE; + kgsl_cffdump_setmem(memdesc->gpuaddr + offsetbytes, + src, sizeof(uint32_t)); + dst = (uint32_t *)(memdesc->hostptr + offsetbytes); + *dst = src; + 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->gpuaddr + offsetbytes, value, + sizebytes); + memset(memdesc->hostptr + offsetbytes, value, sizebytes); + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_set); + +/* + * kgsl_sharedmem_map_vma - Map a user vma to physical memory + * + * @vma - The user vma to map + * @memdesc - The memory descriptor which contains information about the + * physical memory + * + * Return: 0 on success else error code + */ +int +kgsl_sharedmem_map_vma(struct vm_area_struct *vma, + const struct kgsl_memdesc *memdesc) +{ + unsigned long addr = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + int ret, i = 0; + + if (!memdesc->sg || (size != memdesc->size) || + (memdesc->sglen != (size / PAGE_SIZE))) + return -EINVAL; + + for (; addr < vma->vm_end; addr += PAGE_SIZE, i++) { + ret = vm_insert_page(vma, addr, sg_page(&memdesc->sg[i])); + if (ret) + return ret; + } + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_map_vma); diff --git a/drivers/gpu/msm/kgsl_sharedmem.h b/drivers/gpu/msm/kgsl_sharedmem.h new file mode 100644 index 0000000000000000000000000000000000000000..034ade4d1180b1f9cf5f90d6e0424d57473ce314 --- /dev/null +++ b/drivers/gpu/msm/kgsl_sharedmem.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#include +#include "kgsl_mmu.h" +#include +#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 +/** Set if the memdesc is mapped into all pagetables */ +#define KGSL_MEMFLAGS_GLOBAL 0x00000002 + +extern struct kgsl_memdesc_ops kgsl_page_alloc_ops; + +int kgsl_sharedmem_page_alloc(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size); + +int kgsl_sharedmem_page_alloc_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 unsigned int kgsl_get_sg_pa(struct scatterlist *sg) +{ + /* + * Try sg_dma_address first to support ion carveout + * regions which do not work with sg_phys(). + */ + unsigned int pa = sg_dma_address(sg); + if (pa == 0) + pa = sg_phys(sg); + return pa; +} + +int +kgsl_sharedmem_map_vma(struct vm_area_struct *vma, + const struct kgsl_memdesc *memdesc); + +/* + * For relatively small sglists, it is preferable to use kzalloc + * rather than going down the vmalloc rat hole. If the size of + * the sglist is < PAGE_SIZE use kzalloc otherwise fallback to + * vmalloc + */ + +static inline void *kgsl_sg_alloc(unsigned int sglen) +{ + if ((sglen * sizeof(struct scatterlist)) < PAGE_SIZE) + return kzalloc(sglen * sizeof(struct scatterlist), GFP_KERNEL); + else + return vmalloc(sglen * sizeof(struct scatterlist)); +} + +static inline void kgsl_sg_free(void *ptr, unsigned int sglen) +{ + if ((sglen * sizeof(struct scatterlist)) < PAGE_SIZE) + kfree(ptr); + else + vfree(ptr); +} + +static inline int +memdesc_sg_phys(struct kgsl_memdesc *memdesc, + unsigned int physaddr, unsigned int size) +{ + memdesc->sg = kgsl_sg_alloc(1); + + kmemleak_not_leak(memdesc->sg); + + memdesc->sglen = 1; + sg_init_table(memdesc->sg, 1); + memdesc->sg[0].length = size; + memdesc->sg[0].offset = 0; + memdesc->sg[0].dma_address = physaddr; + return 0; +} + +static inline int +kgsl_allocate(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size) +{ + if (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_NONE) + return kgsl_sharedmem_ebimem(memdesc, pagetable, size); + return kgsl_sharedmem_page_alloc(memdesc, pagetable, size); +} + +static inline int +kgsl_allocate_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, unsigned int flags) +{ + if (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_NONE) + return kgsl_sharedmem_ebimem_user(memdesc, pagetable, size, + flags); + return kgsl_sharedmem_page_alloc_user(memdesc, pagetable, size, flags); +} + +static inline int +kgsl_allocate_contiguous(struct kgsl_memdesc *memdesc, size_t size) +{ + int ret = kgsl_sharedmem_alloc_coherent(memdesc, size); + if (!ret && (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_NONE)) + memdesc->gpuaddr = memdesc->physaddr; + return ret; +} + +static inline int kgsl_sg_size(struct scatterlist *sg, int sglen) +{ + int i, size = 0; + struct scatterlist *s; + + for_each_sg(sg, s, sglen, i) { + size += s->length; + } + + return size; +} +#endif /* __KGSL_SHAREDMEM_H */ diff --git a/drivers/gpu/msm/kgsl_snapshot.c b/drivers/gpu/msm/kgsl_snapshot.c new file mode 100644 index 0000000000000000000000000000000000000000..8935b29ec8116b086cb1b3a2e8e55d2ba80f8f6b --- /dev/null +++ b/drivers/gpu/msm/kgsl_snapshot.c @@ -0,0 +1,774 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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_log.h" +#include "kgsl_device.h" +#include "kgsl_sharedmem.h" +#include "kgsl_snapshot.h" + +/* Placeholder for the list of memory objects frozen after a hang */ + +struct kgsl_snapshot_object { + unsigned int gpuaddr; + unsigned int ptbase; + unsigned int size; + unsigned int offset; + int type; + struct kgsl_mem_entry *entry; + struct list_head node; +}; + +/* idr_for_each function to count the number of contexts */ + +static int snapshot_context_count(int id, void *ptr, void *data) +{ + int *count = data; + *count = *count + 1; + + return 0; +} + +/* + * To simplify the iterator loop use a global pointer instead of trying + * to pass around double star references to the snapshot data + */ + +static void *_ctxtptr; + +static int snapshot_context_info(int id, void *ptr, void *data) +{ + struct kgsl_snapshot_linux_context *header = _ctxtptr; + struct kgsl_context *context = ptr; + struct kgsl_device *device = context->dev_priv->device; + + header->id = id; + + /* Future-proof for per-context timestamps - for now, just + * return the global timestamp for all contexts + */ + + header->timestamp_queued = kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_QUEUED); + header->timestamp_retired = kgsl_readtimestamp(device, context, + KGSL_TIMESTAMP_RETIRED); + + _ctxtptr += sizeof(struct kgsl_snapshot_linux_context); + + return 0; +} + +/* Snapshot the Linux specific information */ +static int snapshot_os(struct kgsl_device *device, + void *snapshot, int remain, void *priv) +{ + struct kgsl_snapshot_linux *header = snapshot; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct task_struct *task; + pid_t pid; + int hang = (int) priv; + int ctxtcount = 0; + int size = sizeof(*header); + + /* Figure out how many active contexts there are - these will + * be appended on the end of the structure */ + + idr_for_each(&device->context_idr, snapshot_context_count, &ctxtcount); + + size += ctxtcount * sizeof(struct kgsl_snapshot_linux_context); + + /* Make sure there is enough room for the data */ + if (remain < size) { + SNAPSHOT_ERR_NOMEM(device, "OS"); + return 0; + } + + memset(header, 0, sizeof(*header)); + + header->osid = KGSL_SNAPSHOT_OS_LINUX; + + header->state = hang ? SNAPSHOT_STATE_HUNG : SNAPSHOT_STATE_RUNNING; + + /* Get the kernel build information */ + strlcpy(header->release, utsname()->release, sizeof(header->release)); + strlcpy(header->version, utsname()->version, sizeof(header->version)); + + /* Get the Unix time for the timestamp */ + header->seconds = get_seconds(); + + /* Remember the power information */ + header->power_flags = pwr->power_flags; + header->power_level = pwr->active_pwrlevel; + header->power_interval_timeout = pwr->interval_timeout; + header->grpclk = kgsl_get_clkrate(pwr->grp_clks[0]); + header->busclk = kgsl_get_clkrate(pwr->ebi1_clk); + + /* Future proof for per-context timestamps */ + header->current_context = -1; + + /* Get the current PT base */ + header->ptbase = kgsl_mmu_get_current_ptbase(&device->mmu); + /* And the PID for the task leader */ + pid = header->pid = kgsl_mmu_get_ptname_from_ptbase(header->ptbase); + + task = find_task_by_vpid(pid); + + if (task) + get_task_comm(header->comm, task); + + header->ctxtcount = ctxtcount; + + /* append information for each context */ + _ctxtptr = snapshot + sizeof(*header); + idr_for_each(&device->context_idr, snapshot_context_info, NULL); + + /* Return the size of the data segment */ + return size; +} +/* + * kgsl_snapshot_dump_indexed_regs - helper function to dump indexed registers + * @device - the device to dump registers from + * @snapshot - pointer to the start of the region of memory for the snapshot + * @remain - a pointer to the number of bytes remaining in the snapshot + * @priv - A pointer to the kgsl_snapshot_indexed_registers data + * + * Given a indexed register cmd/data pair and a count, dump each indexed + * register + */ + +static int kgsl_snapshot_dump_indexed_regs(struct kgsl_device *device, + void *snapshot, int remain, void *priv) +{ + struct kgsl_snapshot_indexed_registers *iregs = priv; + struct kgsl_snapshot_indexed_regs *header = snapshot; + unsigned int *data = snapshot + sizeof(*header); + int i; + + if (remain < (iregs->count * 4) + sizeof(*header)) { + SNAPSHOT_ERR_NOMEM(device, "INDEXED REGS"); + return 0; + } + + header->index_reg = iregs->index; + header->data_reg = iregs->data; + header->count = iregs->count; + header->start = iregs->start; + + for (i = 0; i < iregs->count; i++) { + kgsl_regwrite(device, iregs->index, iregs->start + i); + kgsl_regread(device, iregs->data, &data[i]); + } + + return (iregs->count * 4) + sizeof(*header); +} + +#define GPU_OBJ_HEADER_SZ \ + (sizeof(struct kgsl_snapshot_section_header) + \ + sizeof(struct kgsl_snapshot_gpu_object)) + +#define GPU_OBJ_SECTION_SIZE(_o) \ + (GPU_OBJ_HEADER_SZ + ((_o)->size)) + +static int kgsl_snapshot_dump_object(struct kgsl_device *device, + struct kgsl_snapshot_object *obj, void *buf, + unsigned int off, unsigned int count) +{ + unsigned char headers[GPU_OBJ_HEADER_SZ]; + struct kgsl_snapshot_section_header *sect = + (struct kgsl_snapshot_section_header *) headers; + struct kgsl_snapshot_gpu_object *header = + (struct kgsl_snapshot_gpu_object *) (headers + sizeof(*sect)); + int ret = 0; + + /* Construct a local copy of the headers */ + + sect->magic = SNAPSHOT_SECTION_MAGIC; + sect->id = KGSL_SNAPSHOT_SECTION_GPU_OBJECT; + sect->size = GPU_OBJ_SECTION_SIZE(obj); + + header->type = obj->type; + + /* Header size is in dwords, object size is in bytes */ + header->size = obj->size >> 2; + header->gpuaddr = obj->gpuaddr; + header->ptbase = obj->ptbase; + + /* Copy out any part of the header block that is needed */ + + if (off < GPU_OBJ_HEADER_SZ) { + int size = count < GPU_OBJ_HEADER_SZ - off ? + count : GPU_OBJ_HEADER_SZ - off; + + memcpy(buf, headers + off, size); + + count -= size; + ret += size; + } + + /* Now copy whatever part of the data is needed */ + + if (off < (GPU_OBJ_HEADER_SZ + obj->size)) { + int offset; + int size = count < obj->size ? count : obj->size; + + /* + * If the desired gpuaddr isn't at the beginning of the region, + * then offset the source pointer + */ + + offset = obj->offset; + + /* + * Then adjust it to account for the offset for the output + * buffer. + */ + + if (off > GPU_OBJ_HEADER_SZ) { + int loff = (off - GPU_OBJ_HEADER_SZ); + + /* Adjust the size so we don't walk off the end */ + + if ((loff + size) > obj->size) + size = obj->size - loff; + + offset += loff; + } + + memcpy(buf + ret, obj->entry->memdesc.hostptr + offset, size); + ret += size; + } + + return ret; +} + +static void kgsl_snapshot_put_object(struct kgsl_device *device, + struct kgsl_snapshot_object *obj) +{ + list_del(&obj->node); + + obj->entry->flags &= ~KGSL_MEM_ENTRY_FROZEN; + kgsl_mem_entry_put(obj->entry); + + kfree(obj); +} + +/* kgsl_snapshot_get_object - Mark a GPU buffer to be frozen + * @device - the device that is being snapshotted + * @ptbase - the pagetable base of the object to freeze + * @gpuaddr - The gpu address of the object to freeze + * @size - the size of the object (may not always be the size of the region) + * @type - the type of object being saved (shader, vbo, etc) + * + * Mark and freeze a GPU buffer object. This will prevent it from being + * freed until it can be copied out as part of the snapshot dump. Returns the + * size of the object being frozen + */ + +int kgsl_snapshot_get_object(struct kgsl_device *device, unsigned int ptbase, + unsigned int gpuaddr, unsigned int size, unsigned int type) +{ + struct kgsl_mem_entry *entry; + struct kgsl_snapshot_object *obj; + int offset; + + entry = kgsl_get_mem_entry(ptbase, gpuaddr, size); + + if (entry == NULL) { + KGSL_DRV_ERR(device, "Unable to find GPU buffer %8.8X\n", + gpuaddr); + return 0; + } + + /* We can't freeze external memory, because we don't own it */ + if (entry->memtype != KGSL_MEM_ENTRY_KERNEL) { + KGSL_DRV_ERR(device, + "Only internal GPU buffers can be frozen\n"); + return 0; + } + + /* + * size indicates the number of bytes in the region to save. This might + * not always be the entire size of the region because some buffers are + * sub-allocated from a larger region. However, if size 0 was passed + * thats a flag that the caller wants to capture the entire buffer + */ + + if (size == 0) { + size = entry->memdesc.size; + offset = 0; + + /* Adjust the gpuaddr to the start of the object */ + gpuaddr = entry->memdesc.gpuaddr; + } else { + offset = gpuaddr - entry->memdesc.gpuaddr; + } + + if (size + offset > entry->memdesc.size) { + KGSL_DRV_ERR(device, "Invalid size for GPU buffer %8.8X\n", + gpuaddr); + return 0; + } + + /* If the buffer is already on the list, skip it */ + list_for_each_entry(obj, &device->snapshot_obj_list, node) { + if (obj->gpuaddr == gpuaddr && obj->ptbase == ptbase) { + /* If the size is different, use the new size */ + if (obj->size != size) + obj->size = size; + + return 0; + } + } + + if (kgsl_memdesc_map(&entry->memdesc) == NULL) { + KGSL_DRV_ERR(device, "Unable to map GPU buffer %X\n", + gpuaddr); + return 0; + } + + obj = kzalloc(sizeof(*obj), GFP_KERNEL); + + if (obj == NULL) { + KGSL_DRV_ERR(device, "Unable to allocate memory\n"); + return 0; + } + + /* Ref count the mem entry */ + kgsl_mem_entry_get(entry); + + obj->type = type; + obj->entry = entry; + obj->gpuaddr = gpuaddr; + obj->ptbase = ptbase; + obj->size = size; + obj->offset = offset; + + list_add(&obj->node, &device->snapshot_obj_list); + + /* + * Return the size of the entire mem entry that was frozen - this gets + * used for tracking how much memory is frozen for a hang. Also, mark + * the memory entry as frozen. If the entry was already marked as + * frozen, then another buffer already got to it. In that case, return + * 0 so it doesn't get counted twice + */ + + if (entry->flags & KGSL_MEM_ENTRY_FROZEN) + return 0; + + entry->flags |= KGSL_MEM_ENTRY_FROZEN; + + return entry->memdesc.size; +} +EXPORT_SYMBOL(kgsl_snapshot_get_object); + +/* + * kgsl_snapshot_dump_regs - helper function to dump device registers + * @device - the device to dump registers from + * @snapshot - pointer to the start of the region of memory for the snapshot + * @remain - a pointer to the number of bytes remaining in the snapshot + * @priv - A pointer to the kgsl_snapshot_registers data + * + * Given an array of register ranges pairs (start,end [inclusive]), dump the + * registers into a snapshot register section. The snapshot region stores a + * part of dwords for each register - the word address of the register, and + * the value. + */ +int kgsl_snapshot_dump_regs(struct kgsl_device *device, void *snapshot, + int remain, void *priv) +{ + struct kgsl_snapshot_regs *header = snapshot; + struct kgsl_snapshot_registers *regs = priv; + unsigned int *data = snapshot + sizeof(*header); + int count = 0, i, j; + + /* Figure out how many registers we are going to dump */ + + for (i = 0; i < regs->count; i++) { + int start = regs->regs[i * 2]; + int end = regs->regs[i * 2 + 1]; + + count += (end - start + 1); + } + + if (remain < (count * 8) + sizeof(*header)) { + SNAPSHOT_ERR_NOMEM(device, "REGISTERS"); + return 0; + } + + for (i = 0; i < regs->count; i++) { + unsigned int start = regs->regs[i * 2]; + unsigned int end = regs->regs[i * 2 + 1]; + + for (j = start; j <= end; j++) { + unsigned int val; + + kgsl_regread(device, j, &val); + *data++ = j; + *data++ = val; + } + } + + header->count = count; + + /* Return the size of the section */ + return (count * 8) + sizeof(*header); +} +EXPORT_SYMBOL(kgsl_snapshot_dump_regs); + +void *kgsl_snapshot_indexed_registers(struct kgsl_device *device, + void *snapshot, int *remain, + unsigned int index, unsigned int data, unsigned int start, + unsigned int count) +{ + struct kgsl_snapshot_indexed_registers iregs; + iregs.index = index; + iregs.data = data; + iregs.start = start; + iregs.count = count; + + return kgsl_snapshot_add_section(device, + KGSL_SNAPSHOT_SECTION_INDEXED_REGS, snapshot, + remain, kgsl_snapshot_dump_indexed_regs, &iregs); +} +EXPORT_SYMBOL(kgsl_snapshot_indexed_registers); + +/* + * kgsl_snapshot - construct a device snapshot + * @device - device to snapshot + * @hang - set to 1 if the snapshot was triggered following a hnag + * Given a device, construct a binary snapshot dump of the current device state + * and store it in the device snapshot memory. + */ +int kgsl_device_snapshot(struct kgsl_device *device, int hang) +{ + struct kgsl_snapshot_header *header = device->snapshot; + int remain = device->snapshot_maxsize - sizeof(*header); + void *snapshot; + + /* + * The first hang is always the one we are interested in. To + * avoid a subsequent hang blowing away the first, the snapshot + * is frozen until it is dumped via sysfs. + * + * Note that triggered snapshots are always taken regardless + * of the state and never frozen. + */ + + if (hang && device->snapshot_frozen == 1) + return 0; + + if (device->snapshot == NULL) { + KGSL_DRV_ERR(device, + "snapshot: No snapshot memory available\n"); + return -ENOMEM; + } + + if (remain < sizeof(*header)) { + KGSL_DRV_ERR(device, + "snapshot: Not enough memory for the header\n"); + return -ENOMEM; + } + + header->magic = SNAPSHOT_MAGIC; + + header->gpuid = kgsl_gpuid(device); + + /* Get a pointer to the first section (right after the header) */ + snapshot = ((void *) device->snapshot) + sizeof(*header); + + /* Build the Linux specific header */ + snapshot = kgsl_snapshot_add_section(device, KGSL_SNAPSHOT_SECTION_OS, + snapshot, &remain, snapshot_os, (void *) hang); + + /* Get the device specific sections */ + if (device->ftbl->snapshot) + snapshot = device->ftbl->snapshot(device, snapshot, &remain, + hang); + + device->snapshot_timestamp = get_seconds(); + device->snapshot_size = (int) (snapshot - device->snapshot); + + /* Freeze the snapshot on a hang until it gets read */ + device->snapshot_frozen = (hang) ? 1 : 0; + + /* log buffer info to aid in ramdump recovery */ + KGSL_DRV_ERR(device, "snapshot created at va %p pa %lx size %d\n", + device->snapshot, __pa(device->snapshot), + device->snapshot_size); + if (hang) + sysfs_notify(&device->snapshot_kobj, NULL, "timestamp"); + return 0; +} +EXPORT_SYMBOL(kgsl_device_snapshot); + +/* An attribute for showing snapshot details */ +struct kgsl_snapshot_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_snapshot_attr(a) \ +container_of(a, struct kgsl_snapshot_attribute, attr) + +#define kobj_to_device(a) \ +container_of(a, struct kgsl_device, snapshot_kobj) + +/* Dump the sysfs binary data to the user */ +static ssize_t snapshot_show(struct file *filep, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct kgsl_device *device = kobj_to_device(kobj); + struct kgsl_snapshot_object *obj, *tmp; + unsigned int size, src, dst = 0; + + if (device == NULL) + return 0; + + /* Return nothing if we haven't taken a snapshot yet */ + if (device->snapshot_timestamp == 0) + return 0; + + /* Get the mutex to keep things from changing while we are dumping */ + mutex_lock(&device->mutex); + + if (off < device->snapshot_size) { + size = count < (device->snapshot_size - off) ? + count : device->snapshot_size - off; + + memcpy(buf, device->snapshot + off, size); + + count -= size; + dst += size; + } + + if (count == 0) + goto done; + + src = device->snapshot_size; + + list_for_each_entry(obj, &device->snapshot_obj_list, node) { + + int objsize = GPU_OBJ_SECTION_SIZE(obj); + int offset; + + /* If the offset is beyond this object, then move on */ + + if (off >= (src + objsize)) { + src += objsize; + continue; + } + + /* Adjust the offset to be relative to the object */ + offset = (off >= src) ? (off - src) : 0; + + size = kgsl_snapshot_dump_object(device, obj, buf + dst, + offset, count); + + count -= size; + dst += size; + + if (count == 0) + goto done; + + /* Move on to the next object - update src accordingly */ + src += objsize; + } + + /* Add the end section */ + + if (off < (src + sizeof(struct kgsl_snapshot_section_header))) { + if (count >= sizeof(struct kgsl_snapshot_section_header)) { + struct kgsl_snapshot_section_header *head = + (void *) (buf + dst); + + head->magic = SNAPSHOT_SECTION_MAGIC; + head->id = KGSL_SNAPSHOT_SECTION_END; + head->size = sizeof(*head); + + dst += sizeof(*head); + } else { + goto done; + } + } + + /* Release the buffers and unfreeze the snapshot */ + + list_for_each_entry_safe(obj, tmp, &device->snapshot_obj_list, node) + kgsl_snapshot_put_object(device, obj); + + if (device->snapshot_frozen) + KGSL_DRV_ERR(device, "Snapshot objects released\n"); + + device->snapshot_frozen = 0; + +done: + mutex_unlock(&device->mutex); + + return dst; +} + +/* Show the timestamp of the last collected snapshot */ +static ssize_t timestamp_show(struct kgsl_device *device, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%x\n", device->snapshot_timestamp); +} + +/* manually trigger a new snapshot to be collected */ +static ssize_t trigger_store(struct kgsl_device *device, const char *buf, + size_t count) +{ + if (device && count > 0) { + mutex_lock(&device->mutex); + kgsl_device_snapshot(device, 0); + mutex_unlock(&device->mutex); + } + + return count; +} + +static struct bin_attribute snapshot_attr = { + .attr.name = "dump", + .attr.mode = 0444, + .size = 0, + .read = snapshot_show +}; + +#define SNAPSHOT_ATTR(_name, _mode, _show, _store) \ +struct kgsl_snapshot_attribute attr_##_name = { \ + .attr = { .name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +SNAPSHOT_ATTR(trigger, 0600, NULL, trigger_store); +SNAPSHOT_ATTR(timestamp, 0444, timestamp_show, NULL); + +static void snapshot_sysfs_release(struct kobject *kobj) +{ +} + +static ssize_t snapshot_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct kgsl_snapshot_attribute *pattr = to_snapshot_attr(attr); + struct kgsl_device *device = kobj_to_device(kobj); + ssize_t ret; + + if (device && pattr->show) + ret = pattr->show(device, buf); + else + ret = -EIO; + + return ret; +} + +static ssize_t snapshot_sysfs_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t count) +{ + struct kgsl_snapshot_attribute *pattr = to_snapshot_attr(attr); + struct kgsl_device *device = kobj_to_device(kobj); + ssize_t ret; + + if (device && pattr->store) + ret = pattr->store(device, buf, count); + else + ret = -EIO; + + return ret; +} + +static const struct sysfs_ops snapshot_sysfs_ops = { + .show = snapshot_sysfs_show, + .store = snapshot_sysfs_store, +}; + +static struct kobj_type ktype_snapshot = { + .sysfs_ops = &snapshot_sysfs_ops, + .default_attrs = NULL, + .release = snapshot_sysfs_release, +}; + +/* kgsl_device_snapshot_init - Add resources for the device GPU snapshot + * @device - The device to initalize + * + * Allocate memory for a GPU snapshot for the specified device, + * and create the sysfs files to manage it + */ + +int kgsl_device_snapshot_init(struct kgsl_device *device) +{ + int ret; + + if (device->snapshot == NULL) + device->snapshot = kzalloc(KGSL_SNAPSHOT_MEMSIZE, GFP_KERNEL); + + if (device->snapshot == NULL) + return -ENOMEM; + + device->snapshot_maxsize = KGSL_SNAPSHOT_MEMSIZE; + device->snapshot_timestamp = 0; + + INIT_LIST_HEAD(&device->snapshot_obj_list); + + ret = kobject_init_and_add(&device->snapshot_kobj, &ktype_snapshot, + &device->dev->kobj, "snapshot"); + if (ret) + goto done; + + ret = sysfs_create_bin_file(&device->snapshot_kobj, &snapshot_attr); + if (ret) + goto done; + + ret = sysfs_create_file(&device->snapshot_kobj, &attr_trigger.attr); + if (ret) + goto done; + + ret = sysfs_create_file(&device->snapshot_kobj, &attr_timestamp.attr); + +done: + return ret; +} +EXPORT_SYMBOL(kgsl_device_snapshot_init); + +/* kgsl_device_snapshot_close - Take down snapshot memory for a device + * @device - Pointer to the kgsl_device + * + * Remove the sysfs files and free the memory allocated for the GPU + * snapshot + */ + +void kgsl_device_snapshot_close(struct kgsl_device *device) +{ + sysfs_remove_bin_file(&device->snapshot_kobj, &snapshot_attr); + sysfs_remove_file(&device->snapshot_kobj, &attr_trigger.attr); + sysfs_remove_file(&device->snapshot_kobj, &attr_timestamp.attr); + + kobject_put(&device->snapshot_kobj); + + kfree(device->snapshot); + + device->snapshot = NULL; + device->snapshot_maxsize = 0; + device->snapshot_timestamp = 0; +} +EXPORT_SYMBOL(kgsl_device_snapshot_close); diff --git a/drivers/gpu/msm/kgsl_snapshot.h b/drivers/gpu/msm/kgsl_snapshot.h new file mode 100644 index 0000000000000000000000000000000000000000..304f4bb1a711fa904b2fba6f6e7fa715ce438876 --- /dev/null +++ b/drivers/gpu/msm/kgsl_snapshot.h @@ -0,0 +1,293 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SNAPSHOT_H_ +#define _KGSL_SNAPSHOT_H_ + +#include + +/* Snapshot header */ + +#define SNAPSHOT_MAGIC 0x504D0001 + +/* GPU ID scheme: + * [16:31] - core identifer (0x0002 for 2D or 0x0003 for 3D) + * [00:16] - GPU specific identifier + */ + +struct kgsl_snapshot_header { + __u32 magic; /* Magic identifier */ + __u32 gpuid; /* GPU ID - see above */ +} __packed; + +/* Section header */ +#define SNAPSHOT_SECTION_MAGIC 0xABCD + +struct kgsl_snapshot_section_header { + __u16 magic; /* Magic identifier */ + __u16 id; /* Type of section */ + __u32 size; /* Size of the section including this header */ +} __packed; + +/* Section identifiers */ +#define KGSL_SNAPSHOT_SECTION_OS 0x0101 +#define KGSL_SNAPSHOT_SECTION_REGS 0x0201 +#define KGSL_SNAPSHOT_SECTION_RB 0x0301 +#define KGSL_SNAPSHOT_SECTION_IB 0x0401 +#define KGSL_SNAPSHOT_SECTION_INDEXED_REGS 0x0501 +#define KGSL_SNAPSHOT_SECTION_ISTORE 0x0801 +#define KGSL_SNAPSHOT_SECTION_DEBUG 0x0901 +#define KGSL_SNAPSHOT_SECTION_DEBUGBUS 0x0A01 +#define KGSL_SNAPSHOT_SECTION_GPU_OBJECT 0x0B01 + +#define KGSL_SNAPSHOT_SECTION_END 0xFFFF + +/* OS sub-section header */ +#define KGSL_SNAPSHOT_OS_LINUX 0x0001 + +/* Linux OS specific information */ + +#define SNAPSHOT_STATE_HUNG 0 +#define SNAPSHOT_STATE_RUNNING 1 + +struct kgsl_snapshot_linux { + int osid; /* subsection OS identifier */ + int state; /* 1 if the thread is running, 0 for hung */ + __u32 seconds; /* Unix timestamp for the snapshot */ + __u32 power_flags; /* Current power flags */ + __u32 power_level; /* Current power level */ + __u32 power_interval_timeout; /* Power interval timeout */ + __u32 grpclk; /* Current GP clock value */ + __u32 busclk; /* Current busclk value */ + __u32 ptbase; /* Current ptbase */ + __u32 pid; /* PID of the process that owns the PT */ + __u32 current_context; /* ID of the current context */ + __u32 ctxtcount; /* Number of contexts appended to section */ + unsigned char release[32]; /* kernel release */ + unsigned char version[32]; /* kernel version */ + unsigned char comm[16]; /* Name of the process that owns the PT */ +} __packed; + +/* + * This structure contains a record of an active context. + * These are appended one after another in the OS section below + * the header above + */ + +struct kgsl_snapshot_linux_context { + __u32 id; /* The context ID */ + __u32 timestamp_queued; /* The last queued timestamp */ + __u32 timestamp_retired; /* The last timestamp retired by HW */ +}; + +/* Ringbuffer sub-section header */ +struct kgsl_snapshot_rb { + int start; /* dword at the start of the dump */ + int end; /* dword at the end of the dump */ + int rbsize; /* Size (in dwords) of the ringbuffer */ + int wptr; /* Current index of the CPU write pointer */ + int rptr; /* Current index of the GPU read pointer */ + int count; /* Number of dwords in the dump */ +} __packed; + +/* Indirect buffer sub-section header */ +struct kgsl_snapshot_ib { + __u32 gpuaddr; /* GPU address of the the IB */ + __u32 ptbase; /* Base for the pagetable the GPU address is valid in */ + int size; /* Size of the IB */ +} __packed; + +/* Register sub-section header */ +struct kgsl_snapshot_regs { + __u32 count; /* Number of register pairs in the section */ +} __packed; + +/* Indexed register sub-section header */ +struct kgsl_snapshot_indexed_regs { + __u32 index_reg; /* Offset of the index register for this section */ + __u32 data_reg; /* Offset of the data register for this section */ + int start; /* Starting index */ + int count; /* Number of dwords in the data */ +} __packed; + +/* Istore sub-section header */ +struct kgsl_snapshot_istore { + int count; /* Number of instructions in the istore */ +} __packed; + +/* Debug data sub-section header */ + +/* A2XX debug sections */ +#define SNAPSHOT_DEBUG_SX 1 +#define SNAPSHOT_DEBUG_CP 2 +#define SNAPSHOT_DEBUG_SQ 3 +#define SNAPSHOT_DEBUG_SQTHREAD 4 +#define SNAPSHOT_DEBUG_MIU 5 + +/* A3XX debug sections */ +#define SNAPSHOT_DEBUG_VPC_MEMORY 6 +#define SNAPSHOT_DEBUG_CP_MEQ 7 +#define SNAPSHOT_DEBUG_CP_PM4_RAM 8 +#define SNAPSHOT_DEBUG_CP_PFP_RAM 9 +#define SNAPSHOT_DEBUG_CP_ROQ 10 + +struct kgsl_snapshot_debug { + int type; /* Type identifier for the attached tata */ + int size; /* Size of the section in dwords */ +} __packed; + +struct kgsl_snapshot_debugbus { + int id; /* Debug bus ID */ + int count; /* Number of dwords in the dump */ +} __packed; + +#define SNAPSHOT_GPU_OBJECT_SHADER 1 +#define SNAPSHOT_GPU_OBJECT_IB 2 +#define SNAPSHOT_GPU_OBJECT_GENERIC 3 + +struct kgsl_snapshot_gpu_object { + int type; /* Type of GPU object */ + __u32 gpuaddr; /* GPU address of the the object */ + __u32 ptbase; /* Base for the pagetable the GPU address is valid in */ + int size; /* Size of the object (in dwords) */ +}; + +#ifdef __KERNEL__ + +/* Allocate 512K for each device snapshot */ +#define KGSL_SNAPSHOT_MEMSIZE (512 * 1024) + +struct kgsl_device; +/* + * A helper macro to print out "not enough memory functions" - this + * makes it easy to standardize the messages as well as cut down on + * the number of strings in the binary + */ + +#define SNAPSHOT_ERR_NOMEM(_d, _s) \ + KGSL_DRV_ERR((_d), \ + "snapshot: not enough snapshot memory for section %s\n", (_s)) + +/* + * kgsl_snapshot_add_section - Add a new section to the GPU snapshot + * @device - the KGSL device being snapshotted + * @id - the section id + * @snapshot - pointer to the memory for the snapshot + * @remain - pointer to the number of bytes left in the snapshot region + * @func - Function pointer to fill the section + * @priv - Priv pointer to pass to the function + * + * Set up a KGSL snapshot header by filling the memory with the callback + * function and adding the standard section header + */ + +static inline void *kgsl_snapshot_add_section(struct kgsl_device *device, + u16 id, void *snapshot, int *remain, + int (*func)(struct kgsl_device *, void *, int, void *), void *priv) +{ + struct kgsl_snapshot_section_header *header = snapshot; + void *data = snapshot + sizeof(*header); + int ret = 0; + + /* + * Sanity check to make sure there is enough for the header. The + * callback will check to make sure there is enough for the rest + * of the data. If there isn't enough room then don't advance the + * pointer. + */ + + if (*remain < sizeof(*header)) + return snapshot; + + /* It is legal to have no function (i.e. - make an empty section) */ + + if (func) { + ret = func(device, data, *remain, priv); + + /* + * If there wasn't enough room for the data then don't bother + * setting up the header. + */ + + if (ret == 0) + return snapshot; + } + + header->magic = SNAPSHOT_SECTION_MAGIC; + header->id = id; + header->size = ret + sizeof(*header); + + /* Decrement the room left in the snapshot region */ + *remain -= header->size; + /* Advance the pointer to the end of the next function */ + return snapshot + header->size; +} + +/* A common helper function to dump a range of registers. This will be used in + * the GPU specific devices like this: + * + * struct kgsl_snapshot_registers priv; + * priv.regs = registers_array;; + * priv.count = num_registers; + * + * kgsl_snapshot_add_section(device, KGSL_SNAPSHOT_SECTION_REGS, snapshot, + * remain, kgsl_snapshot_dump_regs, &priv). + * + * Pass in an array of register range pairs in the form of: + * start reg, stop reg + * All the registers between start and stop inclusive will be dumped + */ + +struct kgsl_snapshot_registers { + unsigned int *regs; /* Pointer to the array of register ranges */ + int count; /* Number of entries in the array */ +}; + +int kgsl_snapshot_dump_regs(struct kgsl_device *device, void *snapshot, + int remain, void *priv); + +/* + * A common helper function to dump a set of indexed registers. Use it + * like this: + * + * struct kgsl_snapshot_indexed_registers priv; + * priv.index = REG_INDEX; + * priv.data = REG_DATA; + * priv.count = num_registers + * + * kgsl_snapshot_add_section(device, KGSL_SNAPSHOT_SECTION_INDEXED_REGS, + * snapshot, remain, kgsl_snapshot_dump_indexed_regs, &priv). + * + * The callback function will write an index from 0 to priv.count to + * the index register and read the data from the data register. + */ + +struct kgsl_snapshot_indexed_registers { + unsigned int index; /* Offset of the index register */ + unsigned int data; /* Offset of the data register */ + unsigned int start; /* Index to start with */ + unsigned int count; /* Number of values to read from the pair */ +}; + +/* Helper function to snapshot a section of indexed registers */ + +void *kgsl_snapshot_indexed_registers(struct kgsl_device *device, + void *snapshot, int *remain, unsigned int index, + unsigned int data, unsigned int start, unsigned int count); + +/* Freeze a GPU buffer so it can be dumped in the snapshot */ +int kgsl_snapshot_get_object(struct kgsl_device *device, unsigned int ptbase, + unsigned int gpuaddr, unsigned int size, unsigned int type); + +#endif +#endif diff --git a/drivers/gpu/msm/kgsl_trace.c b/drivers/gpu/msm/kgsl_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..2bcca15414bb3c0cb0d56bac041719984091aa11 --- /dev/null +++ b/drivers/gpu/msm/kgsl_trace.c @@ -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 "kgsl.h" +#include "kgsl_device.h" + +/* Instantiate tracepoints */ +#define CREATE_TRACE_POINTS +#include "kgsl_trace.h" diff --git a/drivers/gpu/msm/kgsl_trace.h b/drivers/gpu/msm/kgsl_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..60231f6a5c300620b9a62492efe3595db9092c53 --- /dev/null +++ b/drivers/gpu/msm/kgsl_trace.h @@ -0,0 +1,496 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#if !defined(_KGSL_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _KGSL_TRACE_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM kgsl +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE kgsl_trace + +#include +#include "kgsl_device.h" + +struct kgsl_device; +struct kgsl_ringbuffer_issueibcmds; +struct kgsl_device_waittimestamp; + +/* + * Tracepoint for kgsl issue ib commands + */ +TRACE_EVENT(kgsl_issueibcmds, + + TP_PROTO(struct kgsl_device *device, + struct kgsl_ringbuffer_issueibcmds *cmd, + struct kgsl_ibdesc *ibdesc, + int result), + + TP_ARGS(device, cmd, ibdesc, result), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, drawctxt_id) + __field(unsigned int, ibdesc_addr) + __field(unsigned int, numibs) + __field(unsigned int, timestamp) + __field(unsigned int, flags) + __field(int, result) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->drawctxt_id = cmd->drawctxt_id; + __entry->ibdesc_addr = ibdesc[0].gpuaddr; + __entry->numibs = cmd->numibs; + __entry->timestamp = cmd->timestamp; + __entry->flags = cmd->flags; + __entry->result = result; + ), + + TP_printk( + "d_name=%s ctx=%u ib=0x%u numibs=%u timestamp=0x%x " + "flags=0x%x(%s) result=%d", + __get_str(device_name), + __entry->drawctxt_id, + __entry->ibdesc_addr, + __entry->numibs, + __entry->timestamp, + __entry->flags, + __entry->flags ? __print_flags(__entry->flags, "|", + { KGSL_CONTEXT_SAVE_GMEM, "SAVE_GMEM" }, + { KGSL_CONTEXT_SUBMIT_IB_LIST, "IB_LIST" }, + { KGSL_CONTEXT_CTX_SWITCH, "CTX_SWITCH" }) + : "None", + __entry->result + ) +); + +/* + * Tracepoint for kgsl readtimestamp + */ +TRACE_EVENT(kgsl_readtimestamp, + + TP_PROTO(struct kgsl_device *device, + unsigned int context_id, + unsigned int type, + unsigned int timestamp), + + TP_ARGS(device, context_id, type, timestamp), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, context_id) + __field(unsigned int, type) + __field(unsigned int, timestamp) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->context_id = context_id; + __entry->type = type; + __entry->timestamp = timestamp; + ), + + TP_printk( + "d_name=%s context_id=%u type=%u timestamp=0x%x", + __get_str(device_name), + __entry->context_id, + __entry->type, + __entry->timestamp + ) +); + +/* + * Tracepoint for kgsl waittimestamp entry + */ +TRACE_EVENT(kgsl_waittimestamp_entry, + + TP_PROTO(struct kgsl_device *device, + unsigned int context_id, + unsigned int curr_ts, + unsigned int wait_ts, + unsigned int timeout), + + TP_ARGS(device, context_id, curr_ts, wait_ts, timeout), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, context_id) + __field(unsigned int, curr_ts) + __field(unsigned int, wait_ts) + __field(unsigned int, timeout) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->context_id = context_id; + __entry->curr_ts = curr_ts; + __entry->wait_ts = wait_ts; + __entry->timeout = timeout; + ), + + TP_printk( + "d_name=%s context_id=%u curr_ts=%u timestamp=0x%x timeout=%u", + __get_str(device_name), + __entry->context_id, + __entry->curr_ts, + __entry->wait_ts, + __entry->timeout + ) +); + +/* + * Tracepoint for kgsl waittimestamp exit + */ +TRACE_EVENT(kgsl_waittimestamp_exit, + + TP_PROTO(struct kgsl_device *device, unsigned int curr_ts, + int result), + + TP_ARGS(device, curr_ts, result), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, curr_ts) + __field(int, result) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->curr_ts = curr_ts; + __entry->result = result; + ), + + TP_printk( + "d_name=%s curr_ts=%u result=%d", + __get_str(device_name), + __entry->curr_ts, + __entry->result + ) +); + +DECLARE_EVENT_CLASS(kgsl_pwr_template, + TP_PROTO(struct kgsl_device *device, int on), + + TP_ARGS(device, on), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(int, on) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->on = on; + ), + + TP_printk( + "d_name=%s %s", + __get_str(device_name), + __entry->on ? "on" : "off" + ) +); + +DEFINE_EVENT(kgsl_pwr_template, kgsl_clk, + TP_PROTO(struct kgsl_device *device, int on), + TP_ARGS(device, on) +); + +DEFINE_EVENT(kgsl_pwr_template, kgsl_irq, + TP_PROTO(struct kgsl_device *device, int on), + TP_ARGS(device, on) +); + +DEFINE_EVENT(kgsl_pwr_template, kgsl_bus, + TP_PROTO(struct kgsl_device *device, int on), + TP_ARGS(device, on) +); + +DEFINE_EVENT(kgsl_pwr_template, kgsl_rail, + TP_PROTO(struct kgsl_device *device, int on), + TP_ARGS(device, on) +); + +TRACE_EVENT(kgsl_pwrlevel, + + TP_PROTO(struct kgsl_device *device, unsigned int pwrlevel, + unsigned int freq), + + TP_ARGS(device, pwrlevel, freq), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, pwrlevel) + __field(unsigned int, freq) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->pwrlevel = pwrlevel; + __entry->freq = freq; + ), + + TP_printk( + "d_name=%s pwrlevel=%d freq=%d", + __get_str(device_name), + __entry->pwrlevel, + __entry->freq + ) +); + +DECLARE_EVENT_CLASS(kgsl_pwrstate_template, + TP_PROTO(struct kgsl_device *device, unsigned int state), + + TP_ARGS(device, state), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, state) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->state = state; + ), + + TP_printk( + "d_name=%s %s", + __get_str(device_name), + kgsl_pwrstate_to_str(__entry->state) + ) +); + +DEFINE_EVENT(kgsl_pwrstate_template, kgsl_pwr_set_state, + TP_PROTO(struct kgsl_device *device, unsigned int state), + TP_ARGS(device, state) +); + +DEFINE_EVENT(kgsl_pwrstate_template, kgsl_pwr_request_state, + TP_PROTO(struct kgsl_device *device, unsigned int state), + TP_ARGS(device, state) +); + +TRACE_EVENT(kgsl_mem_alloc, + + TP_PROTO(struct kgsl_mem_entry *mem_entry), + + TP_ARGS(mem_entry), + + TP_STRUCT__entry( + __field(unsigned int, gpuaddr) + __field(unsigned int, size) + ), + + TP_fast_assign( + __entry->gpuaddr = mem_entry->memdesc.gpuaddr; + __entry->size = mem_entry->memdesc.size; + ), + + TP_printk( + "gpuaddr=0x%08x size=%d", + __entry->gpuaddr, __entry->size + ) +); + +TRACE_EVENT(kgsl_mem_map, + + TP_PROTO(struct kgsl_mem_entry *mem_entry, int fd), + + TP_ARGS(mem_entry, fd), + + TP_STRUCT__entry( + __field(unsigned int, gpuaddr) + __field(unsigned int, size) + __field(int, fd) + __field(int, type) + ), + + TP_fast_assign( + __entry->gpuaddr = mem_entry->memdesc.gpuaddr; + __entry->size = mem_entry->memdesc.size; + __entry->fd = fd; + __entry->type = mem_entry->memtype; + ), + + TP_printk( + "gpuaddr=0x%08x size=%d type=%d fd=%d", + __entry->gpuaddr, __entry->size, + __entry->type, __entry->fd + ) +); + +TRACE_EVENT(kgsl_mem_free, + + TP_PROTO(struct kgsl_mem_entry *mem_entry), + + TP_ARGS(mem_entry), + + TP_STRUCT__entry( + __field(unsigned int, gpuaddr) + __field(unsigned int, size) + __field(int, type) + __field(int, fd) + ), + + TP_fast_assign( + __entry->gpuaddr = mem_entry->memdesc.gpuaddr; + __entry->size = mem_entry->memdesc.size; + __entry->type = mem_entry->memtype; + ), + + TP_printk( + "gpuaddr=0x%08x size=%d type=%d", + __entry->gpuaddr, __entry->size, __entry->type + ) +); + +DECLARE_EVENT_CLASS(kgsl_mem_timestamp_template, + + TP_PROTO(struct kgsl_device *device, struct kgsl_mem_entry *mem_entry, + unsigned int id, unsigned int curr_ts, unsigned int free_ts), + + TP_ARGS(device, mem_entry, id, curr_ts, free_ts), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, gpuaddr) + __field(unsigned int, size) + __field(int, type) + __field(unsigned int, drawctxt_id) + __field(unsigned int, curr_ts) + __field(unsigned int, free_ts) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->gpuaddr = mem_entry->memdesc.gpuaddr; + __entry->size = mem_entry->memdesc.size; + __entry->drawctxt_id = id; + __entry->type = mem_entry->memtype; + __entry->curr_ts = curr_ts; + __entry->free_ts = free_ts; + ), + + TP_printk( + "d_name=%s gpuaddr=0x%08x size=%d type=%d ctx=%u" + " curr_ts=0x%08x free_ts=0x%08x", + __get_str(device_name), + __entry->gpuaddr, + __entry->size, + __entry->type, + __entry->drawctxt_id, + __entry->curr_ts, + __entry->free_ts + ) +); + +DEFINE_EVENT(kgsl_mem_timestamp_template, kgsl_mem_timestamp_queue, + TP_PROTO(struct kgsl_device *device, struct kgsl_mem_entry *mem_entry, + unsigned int id, unsigned int curr_ts, unsigned int free_ts), + TP_ARGS(device, mem_entry, id, curr_ts, free_ts) +); + +DEFINE_EVENT(kgsl_mem_timestamp_template, kgsl_mem_timestamp_free, + TP_PROTO(struct kgsl_device *device, struct kgsl_mem_entry *mem_entry, + unsigned int id, unsigned int curr_ts, unsigned int free_ts), + TP_ARGS(device, mem_entry, id, curr_ts, free_ts) +); + +TRACE_EVENT(kgsl_context_create, + + TP_PROTO(struct kgsl_device *device, struct kgsl_context *context, + unsigned int flags), + + TP_ARGS(device, context, flags), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, id) + __field(unsigned int, flags) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->id = context->id; + __entry->flags = flags; + ), + + TP_printk( + "d_name=%s ctx=%u flags=0x%x %s", + __get_str(device_name), __entry->id, __entry->flags, + __entry->flags ? __print_flags(__entry->flags, "|", + { KGSL_CONTEXT_NO_GMEM_ALLOC , "NO_GMEM_ALLOC" }, + { KGSL_CONTEXT_PREAMBLE, "PREAMBLE" }, + { KGSL_CONTEXT_TRASH_STATE, "TRASH_STATE" }, + { KGSL_CONTEXT_PER_CONTEXT_TS, "PER_CONTEXT_TS" }) + : "None" + ) +); + +TRACE_EVENT(kgsl_context_detach, + + TP_PROTO(struct kgsl_device *device, struct kgsl_context *context), + + TP_ARGS(device, context), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, id) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->id = context->id; + ), + + TP_printk( + "d_name=%s ctx=%u", + __get_str(device_name), __entry->id + ) +); + +TRACE_EVENT(kgsl_mmu_pagefault, + + TP_PROTO(struct kgsl_device *device, unsigned int page, + unsigned int pt, const char *op), + + TP_ARGS(device, page, pt, op), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, page) + __field(unsigned int, pt) + __string(op, op) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->page = page; + __entry->pt = pt; + __assign_str(op, op); + ), + + TP_printk( + "d_name=%s page=0x%08x pt=%d op=%s\n", + __get_str(device_name), __entry->page, __entry->pt, + __get_str(op) + ) +); + +#endif /* _KGSL_TRACE_H */ + +/* This part must be outside protection */ +#include diff --git a/drivers/gpu/msm/z180.c b/drivers/gpu/msm/z180.c new file mode 100644 index 0000000000000000000000000000000000000000..f4bbf69cebb24ba831940d0d91288ba9a10d3809 --- /dev/null +++ b/drivers/gpu/msm/z180.c @@ -0,0 +1,970 @@ +/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even 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_cffdump.h" +#include "kgsl_sharedmem.h" + +#include "z180.h" +#include "z180_reg.h" +#include "z180_trace.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, + struct kgsl_context *context, + 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 = { + KGSL_DEVICE_COMMON_INIT(device_2d0.dev), + .name = DEVICE_2D0_NAME, + .id = KGSL_DEVICE_2D0, + .mh = { + .mharb = Z180_CFG_MHARB, + .mh_intf_cfg1 = 0x00032f07, + .mh_intf_cfg2 = 0x004b274f, + /* turn off memory protection unit by setting + acceptable physical address range to include + all pages. */ + .mpu_base = 0x00000000, + .mpu_range = 0xFFFFF000, + }, + .mmu = { + .config = Z180_MMU_CONFIG, + }, + .pwrctrl = { + .irq_name = KGSL_2D0_IRQ, + }, + .iomemname = KGSL_2D0_REG_MEMORY, + .ftbl = &z180_functable, + }, + .cmdwin_lock = __SPIN_LOCK_INITIALIZER(device_2d1.cmdwin_lock), +}; + +static struct z180_device device_2d1 = { + .dev = { + KGSL_DEVICE_COMMON_INIT(device_2d1.dev), + .name = DEVICE_2D1_NAME, + .id = KGSL_DEVICE_2D1, + .mh = { + .mharb = Z180_CFG_MHARB, + .mh_intf_cfg1 = 0x00032f07, + .mh_intf_cfg2 = 0x004b274f, + /* turn off memory protection unit by setting + acceptable physical address range to include + all pages. */ + .mpu_base = 0x00000000, + .mpu_range = 0xFFFFF000, + }, + .mmu = { + .config = Z180_MMU_CONFIG, + }, + .pwrctrl = { + .irq_name = KGSL_2D1_IRQ, + }, + .iomemname = KGSL_2D1_REG_MEMORY, + .ftbl = &z180_functable, + }, + .cmdwin_lock = __SPIN_LOCK_INITIALIZER(device_2d1.cmdwin_lock), +}; + +static irqreturn_t z180_irq_handler(struct kgsl_device *device) +{ + irqreturn_t result = IRQ_NONE; + unsigned int status; + struct z180_device *z180_dev = Z180_DEVICE(device); + + z180_regread(device, ADDR_VGC_IRQSTATUS >> 2, &status); + + trace_kgsl_z180_irq_status(device, 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; + + queue_work(device->work_queue, &device->ts_expired_ws); + 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)) { + kgsl_pwrctrl_request_state(device, KGSL_STATE_NAP); + queue_work(device->work_queue, &device->idle_check_ws); + } + mod_timer_pending(&device->idle_timer, + jiffies + device->pwrctrl.interval_timeout); + + return result; +} + +static void 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.setstate_memory); + + kgsl_mmu_unmap(pagetable, &device->memstore); + + kgsl_mmu_unmap(pagetable, &z180_dev->ringbuffer.cmdbufdesc); +} + +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.setstate_memory, + 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.setstate_memory); + +error_unmap_memstore: + kgsl_mmu_unmap(pagetable, &device->memstore); + +error: + return result; +} + +static inline unsigned int rb_offset(unsigned int timestamp) +{ + return (timestamp % Z180_PACKET_COUNT) + *sizeof(unsigned int)*(Z180_PACKET_SIZE); +} + +static inline unsigned int rb_gpuaddr(struct z180_device *z180_dev, + unsigned int timestamp) +{ + return z180_dev->ringbuffer.cmdbufdesc.gpuaddr + rb_offset(timestamp); +} + +static void addmarker(struct z180_ringbuffer *rb, unsigned int timestamp) +{ + char *ptr = (char *)(rb->cmdbufdesc.hostptr); + unsigned int *p = (unsigned int *)(ptr + rb_offset(timestamp)); + + *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 timestamp, + unsigned int cmd, unsigned int nextcnt) +{ + char * ptr = (char *)(rb->cmdbufdesc.hostptr); + unsigned int *p = (unsigned int *)(ptr + (rb_offset(timestamp) + + (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, int init_ram) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + unsigned int cmd = VGV3_NEXTCMD_JUMP << VGV3_NEXTCMD_NEXTCMD_FSHIFT; + + if (init_ram) { + 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, + rb_gpuaddr(z180_dev, z180_dev->current_timestamp)); + + 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 (timestamp_cmp(z180_dev->current_timestamp, + z180_dev->timestamp) > 0) + status = z180_wait(device, NULL, + z180_dev->current_timestamp, timeout); + + if (status) + KGSL_DRV_ERR(device, "z180_waittimestamp() timed out\n"); + + return status; +} + +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) +{ + long result = 0; + unsigned int ofs = PACKETSIZE_STATESTREAM * sizeof(unsigned int); + unsigned int cnt = 5; + unsigned int old_timestamp = 0; + unsigned int nextcnt = Z180_STREAM_END_CMD | 5; + struct kgsl_mem_entry *entry = NULL; + 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) { + result = -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; + /* + * Get a kernel mapping to the IB for monkey patching. + * See the end of this function. + */ + entry = kgsl_sharedmem_find_region(dev_priv->process_priv, cmd, + sizedwords); + if (entry == NULL) { + KGSL_DRV_ERR(device, "Bad ibdesc: gpuaddr 0x%x size %d\n", + cmd, sizedwords); + result = -EINVAL; + goto error; + } + /* + * This will only map memory if it exists, otherwise it will reuse the + * mapping. And the 2d userspace reuses IBs so we likely won't create + * too many mappings. + */ + if (kgsl_gpuaddr_to_vaddr(&entry->memdesc, cmd) == NULL) { + KGSL_DRV_ERR(device, + "Cannot make kernel mapping for gpuaddr 0x%x\n", + cmd); + result = -EINVAL; + goto error; + } + + 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->mmu, pagetable); + cnt = PACKETSIZE_STATESTREAM; + ofs = 0; + } + kgsl_setstate(&device->mmu, + kgsl_mmu_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: %ld\n", result); + goto error; + } + result = 0; + + old_timestamp = z180_dev->current_timestamp; + z180_dev->current_timestamp++; + *timestamp = z180_dev->current_timestamp; + + z180_dev->ringbuffer.prevctx = context->id; + + addcmd(&z180_dev->ringbuffer, old_timestamp, cmd + ofs, cnt); + kgsl_pwrscale_busy(device); + + /* Make sure the next ringbuffer entry has a marker */ + addmarker(&z180_dev->ringbuffer, z180_dev->current_timestamp); + + /* monkey patch the IB so that it jumps back to the ringbuffer */ + kgsl_sharedmem_writel(&entry->memdesc, + ((sizedwords + 1) * sizeof(unsigned int)), + rb_gpuaddr(z180_dev, z180_dev->current_timestamp)); + kgsl_sharedmem_writel(&entry->memdesc, + ((sizedwords + 2) * sizeof(unsigned int)), + 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 (int)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); + + status = z180_ringbuffer_init(device); + if (status != 0) + goto error; + + status = kgsl_device_platform_probe(device); + if (status) + goto error_close_ringbuffer; + + kgsl_pwrscale_init(device); + kgsl_pwrscale_attach_policy(device, Z180_DEFAULT_PWRSCALE_POLICY); + + 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_pwrscale_close(device); + 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; + + kgsl_pwrctrl_set_state(device, KGSL_STATE_INIT); + + kgsl_pwrctrl_enable(device); + + /* Set interrupts to 0 to ensure a good state */ + z180_regwrite(device, (ADDR_VGC_IRQENABLE >> 2), 0x0); + + kgsl_mh_start(device); + + status = kgsl_mmu_start(device); + if (status) + goto error_clk_off; + + z180_cmdstream_start(device, init_ram); + + mod_timer(&device->idle_timer, jiffies + FIRST_TIMEOUT); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_ON); + device->ftbl->irqctrl(device, 1); + 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) +{ + device->ftbl->irqctrl(device, 0); + z180_idle(device, KGSL_TIMEOUT_DEFAULT); + + del_timer_sync(&device->idle_timer); + + kgsl_mmu_stop(&device->mmu); + + /* 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: + { + int mmu_prop = kgsl_mmu_enabled(); + if (sizebytes != sizeof(int)) { + status = -EINVAL; + break; + } + if (copy_to_user(value, &mmu_prop, sizeof(mmu_prop))) { + 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) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + + return (timestamp_cmp(z180_dev->timestamp, + z180_dev->current_timestamp) == 0) ? true : false; +} + +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->reg_len); + + reg = (unsigned int *)(device->reg_virt + (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->reg_len); + + reg = (unsigned int *)(device->reg_virt + (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 >= MH_ARBITER_CONFIG && + offsetwords <= 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 >= MH_ARBITER_CONFIG && + offsetwords <= 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, + struct kgsl_context *context, enum kgsl_timestamp_type type) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + (void)context; + /* get current EOP timestamp */ + return z180_dev->timestamp; +} + +static int z180_waittimestamp(struct kgsl_device *device, + struct kgsl_context *context, + unsigned int timestamp, + unsigned int msecs) +{ + int status = -EINVAL; + + /* Don't wait forever, set a max (10 sec) value for now */ + if (msecs == -1) + msecs = 10 * MSEC_PER_SEC; + + mutex_unlock(&device->mutex); + status = z180_wait(device, context, timestamp, msecs); + mutex_lock(&device->mutex); + + return status; +} + +static int z180_wait(struct kgsl_device *device, + struct kgsl_context *context, + 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, context, timestamp), + msecs_to_jiffies(msecs)); + + if (timeout > 0) + status = 0; + else if (timeout == 0) { + status = -ETIMEDOUT; + kgsl_pwrctrl_set_state(device, KGSL_STATE_HUNG); + } 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->mmu, KGSL_MMUFLAGS_PTUPDATE); + } +} + +static void z180_power_stats(struct kgsl_device *device, + struct kgsl_power_stats *stats) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + s64 tmp = ktime_to_us(ktime_get()); + + if (pwr->time == 0) { + pwr->time = tmp; + stats->total_time = 0; + stats->busy_time = 0; + } else { + stats->total_time = tmp - pwr->time; + pwr->time = tmp; + stats->busy_time = tmp - device->on_time; + device->on_time = tmp; + } +} + +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 unsigned int z180_gpuid(struct kgsl_device *device) +{ + /* Standard KGSL gpuid format: + * top word is 0x0002 for 2D or 0x0003 for 3D + * Bottom word is core specific identifer + */ + + return (0x0002 << 16) | 180; +} + +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, + .gpuid = z180_gpuid, + .irq_handler = z180_irq_handler, + /* Optional functions */ + .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 0000000000000000000000000000000000000000..e5c5ef303c57d5ba915e17d6852c1ae48a438db8 --- /dev/null +++ b/drivers/gpu/msm/z180.h @@ -0,0 +1,37 @@ +/* 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" + +#define Z180_DEFAULT_PWRSCALE_POLICY NULL + +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 0000000000000000000000000000000000000000..5b6c0017f03a0cbeb45277e0989e326b4069444f --- /dev/null +++ b/drivers/gpu/msm/z180_reg.h @@ -0,0 +1,49 @@ +/* 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_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/gpu/msm/z180_trace.c b/drivers/gpu/msm/z180_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..29b519c4005356cdcdb3e352a87c103289221678 --- /dev/null +++ b/drivers/gpu/msm/z180_trace.c @@ -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. + * + */ + +#include "kgsl.h" +#include "z180.h" +#include "z180_reg.h" + +/* Instantiate tracepoints */ +#define CREATE_TRACE_POINTS +#include "z180_trace.h" diff --git a/drivers/gpu/msm/z180_trace.h b/drivers/gpu/msm/z180_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..fbe1fe54ee219b5abbc082cd3f7edb999dacd9ad --- /dev/null +++ b/drivers/gpu/msm/z180_trace.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. + * + */ + +#if !defined(_Z180_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _Z180_TRACE_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM kgsl +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE z180_trace + +#include + +struct kgsl_device; + +/* + * Tracepoint for z180 irq. Includes status info + */ +TRACE_EVENT(kgsl_z180_irq_status, + + TP_PROTO(struct kgsl_device *device, unsigned int status), + + TP_ARGS(device, status), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(unsigned int, status) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->status = status; + ), + + TP_printk( + "d_name=%s status=%s", + __get_str(device_name), + __entry->status ? __print_flags(__entry->status, "|", + { REG_VGC_IRQSTATUS__MH_MASK, "MH" }, + { REG_VGC_IRQSTATUS__G2D_MASK, "G2D" }, + { REG_VGC_IRQSTATUS__FIFO_MASK, "FIFO" }) : "None" + ) +); + +#endif /* _Z180_TRACE_H */ + +/* This part must be outside protection */ +#include diff --git a/drivers/gud/Kconfig b/drivers/gud/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..3a241b765162041fe7d58ba9bf1efd51f2687c35 --- /dev/null +++ b/drivers/gud/Kconfig @@ -0,0 +1,32 @@ +# +# MobiCore configuration +# +config MOBICORE_SUPPORT + tristate "Linux MobiCore Support" + #depends on ARM_TRUSTZONE + ---help--- + Enable Linux Kernel MobiCore Support + +config MOBICORE_DEBUG + bool "MobiCore Module debug mode" + depends on MOBICORE_SUPPORT + ---help--- + Enable Debug mode in the MobiCore Driver. + It enables printing information about mobicore operations + +config MOBICORE_VERBOSE + bool "MobiCore Module verbose debug mode" + depends on MOBICORE_DEBUG + ---help--- + Enable Verbose Debug mode in the MobiCore Driver. + It enables printing extra information about mobicore operations + Beware: this is only useful for debuging deep in the driver because + it prints too much logs + + +config MOBICORE_API + tristate "Linux MobiCore API" + depends on MOBICORE_SUPPORT + ---help--- + Enable Linux Kernel MobiCore API + diff --git a/drivers/gud/Makefile b/drivers/gud/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ea212c523bd662d77cea756e1415c32798d5a97e --- /dev/null +++ b/drivers/gud/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the kernel mobicore drivers +# +GUD_ROOT_FOLDER := drivers/gud +# add our modules to kernel. +obj-$(CONFIG_MOBICORE_API) += mckernelapi.o +obj-$(CONFIG_MOBICORE_SUPPORT) += mcdrvmodule.o + +mcdrvmodule-objs := mobicore_driver/logging.o mobicore_driver/main.o + +mckernelapi-objs := mobicore_kernelapi/main.o \ + mobicore_kernelapi/clientlib.o \ + mobicore_kernelapi/device.o \ + mobicore_kernelapi/session.o \ + mobicore_kernelapi/connection.o + +# Release mode by default +ccflags-y := -DNDEBUG +ccflags-y += -Wno-declaration-after-statement + +ccflags-$(CONFIG_MOBICORE_DEBUG) += -DDEBUG +ccflags-$(CONFIG_MOBICORE_VERBOSE) += -DDEBUG_VERBOSE + +# Choose one platform from the folder +MOBICORE_PLATFORM := $(shell (ls -1 $(PWD)/$(GUD_ROOT_FOLDER)/mobicore_driver/platforms | tail -1) ) +# Use the available platform folder +ccflags-y += -I$(GUD_ROOT_FOLDER)/mobicore_driver/platforms/$(MOBICORE_PLATFORM) + + +ccflags-y += -I$(GUD_ROOT_FOLDER)/mobicore_driver/public +ccflags-y += -I$(GUD_ROOT_FOLDER)/mobicore_kernelapi/include diff --git a/drivers/gud/README b/drivers/gud/README new file mode 100644 index 0000000000000000000000000000000000000000..c6d62a1e6515566ab96fe280ae89266d9a71af05 --- /dev/null +++ b/drivers/gud/README @@ -0,0 +1,6 @@ +MobiCore is an operating system being shipped with TZBSP +on msm chipsets. MobiCore consists of several components in +the secure world(TrustZone) and non-secure world(linux +kernel, Android user space). The MobiCore driver +communicates with the MobiCore kernel that exists in +TrustZone. diff --git a/drivers/gud/mobicore_driver/build_tag.h b/drivers/gud/mobicore_driver/build_tag.h new file mode 100644 index 0000000000000000000000000000000000000000..43541bbe275309f29c93537274ffb84172ff9b48 --- /dev/null +++ b/drivers/gud/mobicore_driver/build_tag.h @@ -0,0 +1,29 @@ +/** + * + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 MOBICORE_COMPONENT_BUILD_TAG "*** GC_MSM8960_Release_V010 ###" diff --git a/drivers/gud/mobicore_driver/logging.c b/drivers/gud/mobicore_driver/logging.c new file mode 100644 index 0000000000000000000000000000000000000000..eb44c8ad7ee41e945d790fb2ddfdbf9ff71f7a34 --- /dev/null +++ b/drivers/gud/mobicore_driver/logging.c @@ -0,0 +1,336 @@ +/** MobiCore driver module.(interface to the secure world SWD) + * @addtogroup MCD_MCDIMPL_KMOD_LOGGING MobiCore Driver Logging Subsystem. + * @ingroup MCD_MCDIMPL_KMOD + * @{ + * @file + * MobiCore Driver Logging Subsystem. + * The logging subsytem provides the interface between the Mobicore trace + * buffer and the Linux log + * + * + * + * 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 "mc_drv_module.h" +#include "mc_drv_module_linux_api.h" +#include "mc_drv_module_fastcalls.h" + +/* Default len of the log ring buffer 256KB*/ +#define LOG_BUF_SIZE (64 * PAGE_SIZE) + +/* Max Len of a log line for printing */ +#define LOG_LINE_SIZE 256 + +static uint32_t log_size = LOG_BUF_SIZE; +module_param(log_size, uint, 0); +MODULE_PARM_DESC(log_size, " Size of the MobiCore log ringbuffer " + "(or 256KB default)."); + +/*----------------------------------------------------------------------------*/ +/* Definitions for log version 2 */ +#define LOG_TYPE_MASK (0x0007) +#define LOG_TYPE_CHAR 0 +#define LOG_TYPE_INTEGER 1 +/* Field length */ +#define LOG_LENGTH_MASK (0x00F8) +#define LOG_LENGTH_SHIFT 3 +/* Extra attributes */ +#define LOG_EOL (0x0100) +#define LOG_INTEGER_DECIMAL (0x0200) +#define LOG_INTEGER_SIGNED (0x0400) + +struct logmsg_struct { + /* Type and format of data */ + uint16_t ctrl; + /* Unique value for each event source */ + uint16_t source; + /* Value, if any */ + uint32_t log_data; +}; + +/** MobiCore log previous position */ +static uint32_t log_pos; +/** MobiCore log buffer structure */ +static struct mc_trace_buf *log_buf; +/** Log Thread task structure */ +struct task_struct *log_thread; +/** Log Line buffer */ +static char *log_line; + +static void log_msg(struct logmsg_struct *msg); + +/*----------------------------------------------------------------------------*/ +static void log_eol(void) +{ + if (!strnlen(log_line, LOG_LINE_SIZE)) + return; + printk(KERN_INFO "%s\n", log_line); + log_line[0] = 0; +} +/*----------------------------------------------------------------------------*/ +/** + * Put a char to the log line if there is enough space if not then also + * output the line. Assume nobody else is updating the line! */ +static void log_char(char ch) +{ + uint32_t len; + if (ch == '\n' || ch == '\r') { + log_eol(); + return; + } + + if (strnlen(log_line, LOG_LINE_SIZE) >= LOG_LINE_SIZE - 1) { + printk(KERN_INFO "%s\n", log_line); + log_line[0] = 0; + } + + len = strnlen(log_line, LOG_LINE_SIZE); + log_line[len] = ch; + log_line[len + 1] = 0; +} + +/*----------------------------------------------------------------------------*/ +/** + * Put a string to the log line if there is enough space if not then also + * output the line. Assume nobody else is updating the line! */ +static void log_str(const char *s) +{ + int i; + for (i = 0; i < strnlen(s, LOG_LINE_SIZE); i++) + log_char(s[i]); +} + +/*----------------------------------------------------------------------------*/ +static uint32_t process_v1log(void) +{ + char *last_char = log_buf->buff + log_buf->write_pos; + char *buff = log_buf->buff + log_pos; + while (buff != last_char) { + log_char(*(buff++)); + /* Wrap around */ + if (buff - (char *)log_buf >= log_size) + buff = log_buf->buff; + } + return buff - log_buf->buff; +} + +/*----------------------------------------------------------------------------*/ +static uint32_t process_v2log(void) +{ + char *last_msg = log_buf->buff + log_buf->write_pos; + char *buff = log_buf->buff + log_pos; + while (buff != last_msg) { + log_msg((struct logmsg_struct *)buff); + buff += sizeof(struct logmsg_struct); + /* Wrap around */ + if (buff + sizeof(struct logmsg_struct) > + (char *)log_buf + log_size) + buff = log_buf->buff; + } + return buff - log_buf->buff; +} + +static const uint8_t HEX2ASCII[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +/*----------------------------------------------------------------------------*/ +static void dbg_raw_nro(uint32_t format, uint32_t value) +{ + int digits = 1; + uint32_t base = (format & LOG_INTEGER_DECIMAL) ? 10 : 16; + int width = (format & LOG_LENGTH_MASK) >> LOG_LENGTH_SHIFT; + int negative = FALSE; + uint32_t digit_base = 1; + + if ((format & LOG_INTEGER_SIGNED) != 0 && ((signed int)value) < 0) { + negative = TRUE; + value = (uint32_t)(-(signed int)value); + width--; + } + + /* Find length and divider to get largest digit */ + while (value / digit_base >= base) { + digit_base *= base; + digits++; + } + + if (width > digits) { + char ch = (base == 10) ? ' ' : '0'; + while (width > digits) { + log_char(ch); + width--; + } + } + + if (negative) + log_char('-'); + + while (digits-- > 0) { + uint32_t d = value / digit_base; + log_char(HEX2ASCII[d]); + value = value - d * digit_base; + digit_base /= base; + } +} + +/*----------------------------------------------------------------------------*/ +static void log_msg(struct logmsg_struct *msg) +{ + unsigned char msgtxt[5]; + int mpos = 0; + switch (msg->ctrl & LOG_TYPE_MASK) { + case LOG_TYPE_CHAR: { + uint32_t ch; + ch = msg->log_data; + while (ch != 0) { + msgtxt[mpos++] = ch&0xFF; + ch >>= 8; + } + msgtxt[mpos] = 0; + log_str(msgtxt); + break; + } + case LOG_TYPE_INTEGER: { + dbg_raw_nro(msg->ctrl, msg->log_data); + break; + } + default: + break; + } + if (msg->ctrl & LOG_EOL) + log_eol(); +} + +/*----------------------------------------------------------------------------*/ +static int log_worker(void *p) +{ + if (log_buf == NULL) + return -EFAULT; + + /* The thread should have never started */ + if (log_buf == NULL) + return -EFAULT; + + while (!kthread_should_stop()) { + if (log_buf->write_pos == log_pos) + schedule_timeout_interruptible(MAX_SCHEDULE_TIMEOUT); + + switch (log_buf->version) { + case 1: + log_pos = process_v1log(); + break; + case 2: + log_pos = process_v2log(); + break; + default: + MCDRV_DBG_ERROR("Unknown Mobicore log data " + "version %d logging disabled.", + log_buf->version); + log_pos = log_buf->write_pos; + /* Stop the thread as we have no idea what + * happens next */ + return -EFAULT; + } + } + MCDRV_DBG("Logging thread stopped!"); + return 0; +} + + +/*----------------------------------------------------------------------------*/ +/** + * Wakeup the log reader thread + * This should be called from the places where calls into MobiCore have + * generated some logs(eg, yield, SIQ...) + */ +void mobicore_log_read(void) +{ + if (log_thread == NULL || IS_ERR(log_thread)) + return; + + wake_up_process(log_thread); +} + +/*----------------------------------------------------------------------------*/ +/** + * Setup mobicore kernel log. It assumes it's running on CORE 0! + * The fastcall will complain is that is not the case! + */ +long mobicore_log_setup(void *data) +{ + unsigned long phys_log_buf; + union fc_generic fc_log; + + log_pos = 0; + log_buf = NULL; + log_thread = NULL; + log_line = NULL; + + /* Sanity check for the log size */ + if (log_size < PAGE_SIZE) + return -EFAULT; + else + log_size = + get_nr_of_pages_for_buffer(NULL, log_size) * PAGE_SIZE; + + log_line = kzalloc(LOG_LINE_SIZE, GFP_KERNEL); + if (IS_ERR(log_line)) { + MCDRV_DBG_ERROR("failed to allocate log line!"); + return -ENOMEM; + } + + log_thread = kthread_create(log_worker, NULL, "mobicore_log"); + if (IS_ERR(log_thread)) { + MCDRV_DBG_ERROR("mobicore log thread creation failed!"); + return -EFAULT; + } + + log_pos = 0; + log_buf = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, + size_to_order(log_size)); + if (!log_buf) { + MCDRV_DBG_ERROR("Failed to get page for logger!"); + return -ENOMEM; + } + phys_log_buf = virt_to_phys(log_buf); + + memset(&fc_log, 0, sizeof(fc_log)); + fc_log.as_in.cmd = MC_FC_NWD_TRACE; + fc_log.as_in.param[0] = phys_log_buf; + fc_log.as_in.param[1] = log_size; + + MCDRV_DBG("fc_log virt=%p phys=%p ", log_buf, (void *)phys_log_buf); + mc_fastcall(&fc_log); + MCDRV_DBG("fc_log out ret=0x%08x", fc_log.as_out.ret); + /* If the setup failed we must free the memory allocated */ + if (fc_log.as_out.ret) { + MCDRV_DBG_ERROR("MobiCore shared traces setup failed!"); + kthread_stop(log_thread); + free_pages((unsigned long)log_buf, size_to_order(log_size)); + + log_buf = NULL; + log_thread = NULL; + return -EIO; + } + + MCDRV_DBG("fc_log Logger version %u\n", log_buf->version); + return 0; +} + +/*----------------------------------------------------------------------------*/ +/** + * Free kernel log componenets. + * ATTN: We can't free the log buffer because it's also in use by MobiCore and + * even if the module is unloaded MobiCore is still running. + */ +void mobicore_log_free(void) +{ + if (log_thread && !IS_ERR(log_thread)) { + /* We don't really care what the thread returns for exit */ + kthread_stop(log_thread); + } + + kfree(log_line); +} diff --git a/drivers/gud/mobicore_driver/main.c b/drivers/gud/mobicore_driver/main.c new file mode 100644 index 0000000000000000000000000000000000000000..8a8ea1e2e9e6b4fa6943424824c64301f6c8ea8b --- /dev/null +++ b/drivers/gud/mobicore_driver/main.c @@ -0,0 +1,2869 @@ +/** MobiCore driver module.(interface to the secure world SWD) + * @addtogroup MCD_MCDIMPL_KMOD_IMPL + * @{ + * @file + * MobiCore Driver Kernel Module. + * This module is written as a Linux device driver. + * This driver represents the command proxy on the lowest layer, from the + * secure world to the non secure world, and vice versa. + * This driver is located in the non secure world (Linux). + * This driver offers IOCTL commands, for access to the secure world, and has + * the interface from the secure world to the normal world. + * The access to the driver is possible with a file descriptor, + * which has to be created by the fd = open(/dev/mobicore) command. + * + * + * + * 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 "mc_drv_module.h" +#include "mc_drv_module_linux_api.h" +#include "mc_drv_module_android.h" +#include "mc_drv_module_fastcalls.h" +#include "public/mc_kernel_api.h" + +/* Initial value for the daemon sempahore signaling */ +#define DAEMON_SEM_VAL 0 + +/** MobiCore interrupt context data */ +static struct mc_drv_kmod_ctx mc_drv_kmod_ctx; + +/** MobiCore MCI information */ +static uint32_t mci_base; +/* +############################################################################# +## +## Convenience functions for Linux API functions +## +#############################################################################*/ +static int goto_cpu0(void); +static int goto_all_cpu(void) __attribute__ ((unused)); + + +/*----------------------------------------------------------------------------*/ +static void init_and_add_to_list( + struct list_head *item, + struct list_head *list_head +) +{ + INIT_LIST_HEAD(item); + + list_add(item, list_head); +} + +/*----------------------------------------------------------------------------*/ +/** check if CPU supports the ARM TrustZone Security Extensions + * @return int TRUE or FALSE */ +static int has_security_extensions( + void +) +{ + u32 fea = 0; + asm volatile( + "mrc p15, 0, %[fea], cr0, cr1, 0" : + [fea]"=r" (fea)); + + MCDRV_DBG_VERBOSE("CPU Features: 0x%X", fea); + + /* If the CPU features ID has 0 for security features then the CPU + * doesn't support TrustZone at all! + */ + if ((fea & ARM_SECURITY_EXTENSION_MASK) == 0) + return 0; + + return 1; +} + +/*----------------------------------------------------------------------------*/ +/** check if running in secure mode + * @return int TRUE or FALSE */ +static int is_secure_mode( + void +) +{ + u32 cpsr = 0, nsacr = 0; + asm volatile( + "mrc p15, 0, %[nsacr], cr1, cr1, 2\n" + "mrs %[cpsr], cpsr\n" : + [nsacr]"=r" (nsacr), + [cpsr]"=r"(cpsr)); + + MCDRV_DBG_VERBOSE("CPRS.M = set to 0x%X\n", cpsr & ARM_CPSR_MASK); + MCDRV_DBG_VERBOSE("SCR.NS = set to 0x%X\n", nsacr); + + /* If the NSACR contains the reset value(=0) then most likely we are + * running in Secure MODE. + * If the cpsr mode is set to monitor mode then we cannot load! + */ + if (nsacr == 0 || ((cpsr & ARM_CPSR_MASK) == ARM_MONITOR_MODE)) + return 1; + + return 0; +} + +/*----------------------------------------------------------------------------*/ +/** check if userland caller is privileged (aka has "root" access rights). + @return int TRUE or FALSE */ +static int is_userland_caller_privileged( + void +) { + /* For some platforms we cannot run the Daemon as root - for Android + * compliance tests it is not allowed, thus we assume the daemon is ran + * as the system user. + * In Android the system user for daemons has no particular capabilities + * other than a fixed UID: AID_SYSTEM 1000 + * The actual number is guaranteed to be the same in all Android systems + * so we will take it for granted: see android_filesystem_config.h in + * the Android source tree for all UIDs and their meaning: + * http://android-dls.com/wiki/index.php?title=Android_UIDs_and_GIDs + */ +#ifdef MC_ANDROID_UID_CHECK + return current_euid() <= AID_SYSTEM; +#else + /* capable should cover all possibilities, root or sudo, uid checking + * was not very reliable */ + return capable(CAP_SYS_ADMIN); +#endif +} + + + +/*----------------------------------------------------------------------------*/ +static void unlock_page_from_used_l2_table( + struct page *page +){ + /* REV axh: check if we should do this. */ + SetPageDirty(page); + + /* release page, old api was page_cache_release() */ + ClearPageReserved(page); + put_page(page); +} + +/*----------------------------------------------------------------------------*/ +/* convert L2 PTE to page pointer */ +static struct page *l2_pte_to_page( + pte_t pte +) { + void *phys_page_addr = (void *)((unsigned int)pte & PAGE_MASK); + unsigned int pfn = addr_to_pfn(phys_page_addr); + struct page *page = pfn_to_page(pfn); + return page; +} + +/*----------------------------------------------------------------------------*/ +/* convert page pointer to L2 PTE */ +static pte_t page_to_l2_pte( + struct page *page +) +{ + unsigned int pfn = page_to_pfn(page); + void *phys_addr = pfn_to_addr(pfn); + pte_t pte = (pte_t)((unsigned int)phys_addr & PAGE_MASK); + return pte; +} + + +/*----------------------------------------------------------------------------*/ +static inline int lock_user_pages( + struct task_struct *task, + void *virt_start_page_addr, + int nr_of_pages, + struct page **pages +) +{ + int ret = 0; + int locked_pages = 0; + unsigned int i; + + do { + + /* lock user pages, must hold the mmap_sem to do this. */ + down_read(&(task->mm->mmap_sem)); + locked_pages = get_user_pages( + task, + task->mm, + (unsigned long)virt_start_page_addr, + nr_of_pages, + 1, /* write access */ + 0, /* they say drivers should always + pass 0 here..... */ + pages, + NULL); /* we don't need the VMAs */ + up_read(&(task->mm->mmap_sem)); + + /* could as lock all pages? */ + if (locked_pages != nr_of_pages) { + MCDRV_DBG_ERROR( + "get_user_pages() failed, " + "locked_pages=%d\n", + locked_pages); + ret = -ENOMEM; + /* check if an error has been returned. */ + if (locked_pages < 0) { + ret = locked_pages; + locked_pages = 0; + } + break; + } + + /* do cache maintenance on locked pages. */ + for (i = 0; i < nr_of_pages; i++) + flush_dcache_page(pages[i]); + + } while (FALSE); + + + if (ret != 0) { + /* release all locked pages. */ + MCDRV_ASSERT(locked_pages >= 0); + for (i = 0; i < locked_pages; i++) + put_page(pages[i]); + } + + return ret; + +} + +/* +############################################################################# +## +## Driver implementation functions +## +#############################################################################*/ +/*----------------------------------------------------------------------------*/ +/* check if caller is MobiCore Daemon */ +static unsigned int is_caller_mc_daemon( + struct mc_instance *instance +) +{ + return ((instance != NULL) + && (mc_drv_kmod_ctx.daemon_inst == instance)); +} + + +/*----------------------------------------------------------------------------*/ +/* Get process context from file pointer */ +static struct mc_instance *get_instance( + struct file *file +) { + MCDRV_ASSERT(file != NULL); + + return (struct mc_instance *)(file->private_data); +} + + +/*----------------------------------------------------------------------------*/ +/* Get a unique ID */ +static unsigned int get_mc_kmod_unique_id( + void +) +{ + return (unsigned int)atomic_inc_return( + &(mc_drv_kmod_ctx.unique_counter)); +} + + +/*----------------------------------------------------------------------------*/ +/* Get kernel pointer to shared L2 table given a per-process reference */ +static struct l2table *get_l2_table_kernel_virt( + struct mc_used_l2_table *used_l2table +) +{ + MCDRV_ASSERT(used_l2table != NULL); + MCDRV_ASSERT(used_l2table->set != NULL); + MCDRV_ASSERT(used_l2table->set->kernel_virt != NULL); + return &(used_l2table->set->kernel_virt->table[used_l2table->idx]); +} + +/*----------------------------------------------------------------------------*/ +/* Get physical address of a shared L2 table given a per-process reference */ +static struct l2table *get_l2_table_phys( + struct mc_used_l2_table *used_l2table +) +{ + MCDRV_ASSERT(used_l2table != NULL); + MCDRV_ASSERT(used_l2table->set != NULL); + MCDRV_ASSERT(used_l2table->set->phys != NULL); + return &(used_l2table->set->phys->table[used_l2table->idx]); +} + +/*----------------------------------------------------------------------------*/ +static unsigned int is_in_use_used_l2_table( + struct mc_used_l2_table *used_l2table +) +{ + return ((used_l2table->flags & + (MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_APP + | MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC)) != 0); +} + + + +/*----------------------------------------------------------------------------*/ +static struct mc_used_l2_table *find_used_l2_table_by_handle( + unsigned int handle +) { + struct mc_used_l2_table *used_l2table; + struct mc_used_l2_table *used_l2table_with_handle = NULL; + + list_for_each_entry( + used_l2table, + &(mc_drv_kmod_ctx.mc_used_l2_tables), + list + ) { + if (handle == used_l2table->handle) { + used_l2table_with_handle = used_l2table; + break; + } + } + + return used_l2table_with_handle; +} + +/* +############################################################################# +## +## L2 Table Pool +## +#############################################################################*/ + +/*----------------------------------------------------------------------------*/ +static struct mc_used_l2_table *allocate_used_l2_table( + struct mc_instance *instance +) { + int ret = 0; + struct mc_l2_table_store *l2table_store = NULL; + struct mc_l2_tables_set *l2table_set = NULL; + struct mc_used_l2_table *used_l2table = NULL; + struct page *page; + unsigned int i = 0; + + do { + /* allocate a WSM L2 descriptor */ + used_l2table = kmalloc(sizeof(*used_l2table), GFP_KERNEL); + if (used_l2table == NULL) { + ret = -ENOMEM; + MCDRV_DBG_ERROR("out of memory\n"); + break; + } + /* clean */ + memset(used_l2table, 0, sizeof(*used_l2table)); + used_l2table->handle = get_mc_kmod_unique_id(); + used_l2table->owner = instance; + + /* add to global list. */ + init_and_add_to_list( + &(used_l2table->list), + &(mc_drv_kmod_ctx.mc_used_l2_tables)); + + /* walk though list to find free set. */ + list_for_each_entry( + l2table_set, + &(mc_drv_kmod_ctx.mc_l2_tables_sets), + list + ) { + for (i = 0; i < MC_DRV_KMOD_L2_TABLE_PER_PAGES; i++) { + if ((l2table_set->usage_bitmap & (1U << i)) + == 0) { + /* found a set, + l2table_set and i are set. */ + l2table_store = + l2table_set->kernel_virt; + break; + } + } + if (l2table_store != NULL) + break; + } /* end while */ + + if (l2table_store == NULL) { + l2table_store = (struct mc_l2_table_store *) + get_zeroed_page(GFP_KERNEL); + if (l2table_store == NULL) { + ret = -ENOMEM; + break; + } + + /* Actually, locking is not necessary, because kernel + memory is not supposed to get swapped out. But + we play safe.... */ + page = virt_to_page(l2table_store); + SetPageReserved(page); + + /* allocate a descriptor */ + l2table_set = kmalloc(sizeof(*l2table_set), GFP_KERNEL); + if (l2table_set == NULL) { + kfree(l2table_store); + ret = -ENOMEM; + break; + } + /* initialize */ + memset(l2table_set, 0, sizeof(*l2table_set)); + + l2table_set->kernel_virt = l2table_store; + l2table_set->page = page; + l2table_set->phys = (void *)virt_to_phys(l2table_store); + + /* init add to list. */ + init_and_add_to_list( + &(l2table_set->list), + &(mc_drv_kmod_ctx.mc_l2_tables_sets)); + + /* use first table */ + i = 0; + } + + /* set set usage */ + l2table_set->usage_bitmap |= (1U << i); + + /* set set reference */ + used_l2table->set = l2table_set; + used_l2table->idx = i; + + MCDRV_DBG_VERBOSE( + "chunkPhys=%p,idx=%d\n", + l2table_set->phys, i); + + } while (FALSE); + + if (ret != 0) { + if (used_l2table != NULL) { + /* remove from list */ + list_del(&(l2table_set->list)); + /* free memory */ + kfree(used_l2table); + used_l2table = NULL; + } + } + + return used_l2table; +} + +/*----------------------------------------------------------------------------*/ +static void free_used_l2_table( + struct mc_used_l2_table *used_l2table +) +{ + struct mc_l2_tables_set *l2table_set; + unsigned int idx; + + MCDRV_ASSERT(used_l2table != NULL); + + l2table_set = used_l2table->set; + MCDRV_ASSERT(l2table_set != NULL); + + /* clean usage flag */ + idx = used_l2table->idx; + MCDRV_ASSERT(idx < MC_DRV_KMOD_L2_TABLE_PER_PAGES); + l2table_set->usage_bitmap &= ~(1U << idx); + + /* if nobody uses this set, we can release it. */ + if (l2table_set->usage_bitmap == 0) { + MCDRV_ASSERT(l2table_set->page != NULL); + ClearPageReserved(l2table_set->page); + + MCDRV_ASSERT(l2table_set->kernel_virt != NULL); + free_page((unsigned long)l2table_set->kernel_virt); + + /* remove from list */ + list_del(&(l2table_set->list)); + + /* free memory */ + kfree(l2table_set); + } + + return; +} + + + +/*----------------------------------------------------------------------------*/ +/** + * Create a L2 table in a WSM container that has been allocates previously. + * + * @param task pointer to task owning WSM + * @param wsm_buffer user space WSM start + * @param wsm_len WSM length + * @param used_l2table Pointer to L2 table details + */ +static int map_buffer_into_used_l2_table( + struct task_struct *task, + void *wsm_buffer, + unsigned int wsm_len, + struct mc_used_l2_table *used_l2table +) +{ + int ret = 0; + unsigned int i, nr_of_pages; + void *virt_addr_page; + struct page *page; + struct l2table *l2table; + struct page **l2table_as_array_of_pointers_to_page; + + /* task can be null when called from kernel space */ + MCDRV_ASSERT(wsm_buffer != NULL); + MCDRV_ASSERT(wsm_len != 0); + MCDRV_ASSERT(used_l2table != NULL); + + MCDRV_DBG_VERBOSE("WSM addr=0x%p, len=0x%08x\n", wsm_buffer, wsm_len); + + /* Check if called from kernel space wsm_buffer is actually + * vmalloced or not */ + if (task == NULL && !is_vmalloc_addr(wsm_buffer)) { + MCDRV_DBG_ERROR("WSM addr is not a vmalloc address"); + return -EINVAL; + } + + l2table = get_l2_table_kernel_virt(used_l2table); + /* We use the memory for the L2 table to hold the pointer + and convert them later. This works, as everything comes + down to a 32 bit value. */ + l2table_as_array_of_pointers_to_page = (struct page **)l2table; + + do { + + /* no size > 1Mib supported */ + if (wsm_len > SZ_1M) { + MCDRV_DBG_ERROR("size > 1 MiB\n"); + ret = -EINVAL; + break; + } + + /* calculate page usage */ + virt_addr_page = get_page_start(wsm_buffer); + nr_of_pages = get_nr_of_pages_for_buffer(wsm_buffer, wsm_len); + + + MCDRV_DBG_VERBOSE("virt addr pageStart=0x%p,pages=%d\n", + virt_addr_page, + nr_of_pages); + + /* L2 table can hold max 1MiB in 256 pages. */ + if ((nr_of_pages*PAGE_SIZE) > SZ_1M) { + MCDRV_DBG_ERROR("WSM paged exceed 1 MiB\n"); + ret = -EINVAL; + break; + } + + /* Request comes from user space */ + if (task != NULL) { + /* lock user page in memory, so they do not get swapped + * out. + * REV axh: + * Kernel 2.6.27 added a new get_user_pages_fast() + * function, maybe it is called fast_gup() in some + * versions. + * handle user process doing a fork(). + * Child should not get things. + * http://osdir.com/ml/linux-media/2009-07/msg00813.html + * http://lwn.net/Articles/275808/ */ + + ret = lock_user_pages( + task, + virt_addr_page, + nr_of_pages, + l2table_as_array_of_pointers_to_page); + if (ret != 0) { + MCDRV_DBG_ERROR("lock_user_pages() failed\n"); + break; + } + } + /* Request comes from kernel space(vmalloc buffer) */ + else { + void *uaddr = wsm_buffer; + for (i = 0; i < nr_of_pages; i++) { + page = vmalloc_to_page(uaddr); + if (!page) { + MCDRV_DBG_ERROR( + "vmalloc_to_Page()" + " failed to map address\n"); + ret = -EINVAL; + break; + } + get_page(page); + /* Lock the page in memory, it can't be swapped + * out */ + SetPageReserved(page); + l2table_as_array_of_pointers_to_page[i] = page; + uaddr += PAGE_SIZE; + } + } + + used_l2table->nr_of_pages = nr_of_pages; + used_l2table->flags |= MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_APP; + + /* create L2 Table entries. used_l2table->table contains a list + of page pointers here. For a proper cleanup we have to ensure + that the following code either works and used_l2table contains + a valid L2 table - or fails and used_l2table->table contains the + list of page pointers. Any mixed contents will make cleanup + difficult.*/ + + for (i = 0; i < nr_of_pages; i++) { + pte_t pte; + page = l2table_as_array_of_pointers_to_page[i]; + + /* create L2 table entry, see ARM MMU docu for details + about flags stored in the lowest 12 bits. As a side + reference, the Article "ARM's multiply-mapped memory + mess" found in the collection at at + http://lwn.net/Articles/409032/ is also worth reading.*/ + pte = page_to_l2_pte(page) + | L2_FLAG_AP1 | L2_FLAG_AP0 + | L2_FLAG_C | L2_FLAG_B + | L2_FLAG_SMALL | L2_FLAG_SMALL_XN + /* Linux uses different mappings for SMP systems(the + * sharing flag is set for the pte. In order not to + * confuse things too much in Mobicore make sure the + * shared buffers have the same flags. + * This should also be done in SWD side + */ +#ifdef CONFIG_SMP + | L2_FLAG_S | L2_FLAG_SMALL_TEX0 +#endif + ; + + l2table->table_entries[i] = pte; + MCDRV_DBG_VERBOSE("L2 entry %d: 0x%08x\n", i, + (unsigned int)(pte)); + } + + /* ensure rest of table is empty */ + while (i < 255) + l2table->table_entries[i++] = (pte_t)0; + + } while (FALSE); + + return ret; +} + + +/*----------------------------------------------------------------------------*/ +/** + * Remove a L2 table in a WSM container. Afterwards the container may be + * released. + * + * @param used_l2table Pointer to L2 table details + */ + +static void unmap_buffers_from_used_l2_table( + struct mc_used_l2_table *used_l2table +) +{ + unsigned int i; + struct l2table *l2table; + + MCDRV_ASSERT(used_l2table != NULL); + /* this should not happen, as we have no empty tables. */ + MCDRV_ASSERT(!is_in_use_used_l2_table(used_l2table)); + + /* found the table, now release the resources. */ + MCDRV_DBG_VERBOSE("clear L2 table, phys_base=%p, nr_of_pages=%d\n", + get_l2_table_phys(used_l2table), + used_l2table->nr_of_pages); + + l2table = get_l2_table_kernel_virt(used_l2table); + + /* release all locked user space pages */ + for (i = 0; i < used_l2table->nr_of_pages; i++) { + /* convert physical entries from L2 table to page pointers */ + pte_t pte = get_l2_table_kernel_virt(used_l2table)-> + table_entries[i]; + struct page *page = l2_pte_to_page(pte); + unlock_page_from_used_l2_table(page); + } + + /* remember that all pages have been freed */ + used_l2table->nr_of_pages = 0; + + return; +} + + +/* +############################################################################# +## +## Helper functions +## +#############################################################################*/ +/*----------------------------------------------------------------------------*/ +#define FREE_FROM_SWD TRUE +#define FREE_FROM_NWD FALSE +/** Delete a used l2 table. */ +static void delete_used_l2_table( + struct mc_used_l2_table *used_l2table, + unsigned int is_swd +) +{ + if (is_swd) { + used_l2table->flags &= + ~MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC; + } else { + used_l2table->flags &= + ~MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_APP; + used_l2table->owner = NULL; + } + + /* release if Nwd and Swd/MC do no longer use it. */ + if (is_in_use_used_l2_table(used_l2table)) { + MCDRV_DBG_WARN( + "WSM L2 table still in use: physBase=%p, " + "nr_of_pages=%d\n", + get_l2_table_phys(used_l2table), + used_l2table->nr_of_pages); + } else { + unmap_buffers_from_used_l2_table(used_l2table); + free_used_l2_table(used_l2table); + + list_del(&(used_l2table->list)); + + kfree(used_l2table); + } + return; +} + +/*----------------------------------------------------------------------------*/ +/** Allocate L2 table and map buffer into it. That is, create respective table + entries. Must hold Semaphore mc_drv_kmod_ctx.wsm_l2_sem */ +static struct mc_used_l2_table *new_used_l2_table( + struct mc_instance *instance, + struct task_struct *task, + void *wsm_buffer, + unsigned int wsm_len +) { + int ret = 0; + struct mc_used_l2_table *used_l2table; + + do { + used_l2table = allocate_used_l2_table(instance); + if (used_l2table == NULL) { + MCDRV_DBG_ERROR( + "allocate_used_l2_table() failed\n"); + break; + } + + /* create the L2 page for the WSM */ + ret = map_buffer_into_used_l2_table( + task, + wsm_buffer, + wsm_len, + used_l2table); + if (ret != 0) { + MCDRV_DBG_ERROR( + "map_buffer_into_used_l2_table() failed\n"); + delete_used_l2_table(used_l2table, FREE_FROM_NWD); + used_l2table = NULL; + break; + } + + } while (FALSE); + + + return used_l2table; +} + +/* +############################################################################# +## +## IoCtl handler +## +#############################################################################*/ + +/** + * Map a virtual memory buffer structure to Mobicore + * @param instance + * @param addr address of the buffer(NB it must be kernel virtual!) + * @param len buffer length + * @param handle pointer to handle + * @param phys_wsm_l2_table pointer to physical L2 table(?) + * + * @return 0 if no error + * + */ +/*----------------------------------------------------------------------------*/ +int mobicore_map_vmem( + struct mc_instance *instance, + void *addr, + uint32_t len, + uint32_t *handle, + void **phys_wsm_l2_table +) +{ + int ret = 0; + struct mc_used_l2_table *used_l2table = NULL; + MCDRV_ASSERT(instance != NULL); + + MCDRV_DBG_VERBOSE("enter\n"); + + do { + if (len == 0) { + MCDRV_DBG_ERROR("len=0 is not supported!\n"); + ret = -EINVAL; + break; + } + + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR("down_interruptible() failed with %d\n", + ret); + ret = -ERESTARTSYS; + break; + } + + do { + used_l2table = new_used_l2_table( + instance, + NULL, + addr, + len); + + if (used_l2table == NULL) { + MCDRV_DBG_ERROR("new_used_l2_table() failed\n"); + ret = -EINVAL; + break; + } + + /* set response */ + *handle = used_l2table->handle; + *phys_wsm_l2_table = + (void *)get_l2_table_phys(used_l2table); + MCDRV_DBG_VERBOSE("handle: %d, phys=%p\n", + *handle, + (void *)(*phys_wsm_l2_table)); + + } while (FALSE); + + /* release semaphore */ + up(&(mc_drv_kmod_ctx.wsm_l2_sem)); + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} +EXPORT_SYMBOL(mobicore_map_vmem); +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_app_register_wsm_l2( + struct mc_instance *instance, + union mc_ioctl_app_reg_wsm_l2_params *user_params +) +{ + int ret = 0; + union mc_ioctl_app_reg_wsm_l2_params params; + struct mc_used_l2_table *used_l2table = NULL; + struct pid *pid_struct = NULL; + struct task_struct *task = current; + + MCDRV_ASSERT(instance != NULL); + + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* get use parameters */ + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user() failed\n"); + break; + } + + /* daemon can do this for another task. */ + if (params.in.pid != 0) { + MCDRV_DBG_ERROR("pid != 0 unsupported\n"); + ret = -EINVAL; + break; + } + if (params.in.len == 0) { + MCDRV_DBG_ERROR("len=0 is not supported!\n"); + ret = -EINVAL; + break; + } + + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR("down_interruptible() failed with %d\n", + ret); + ret = -ERESTARTSYS; + break; + } + + do { + used_l2table = new_used_l2_table( + instance, + task, + (void *)(params.in.buffer), + params.in.len); + + if (used_l2table == NULL) { + MCDRV_DBG_ERROR("new_used_l2_table() failed\n"); + ret = -EINVAL; + break; + } + + /* if the daemon does this, we set the MC lock */ + if (is_caller_mc_daemon(instance)) + used_l2table->flags |= + MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC; + + /* set response */ + memset(¶ms.out, 0, sizeof(params.out)); + params.out.handle = used_l2table->handle; + /* TODO: return the physical address for daemon only, + otherwise set NULL */ + params.out.phys_wsm_l2_table = + (uint32_t)get_l2_table_phys(used_l2table); + + MCDRV_DBG_VERBOSE("handle: %d, phys=%p\n", + params.out.handle, + (void *)(params.out.phys_wsm_l2_table)); + + + /* copy L2Table to user space */ + ret = copy_to_user( + &(user_params->out), + &(params.out), + sizeof(params.out)); + if (ret != 0) { + MCDRV_DBG_ERROR("copy_to_user() failed\n"); + + /* free the table again, as app does not know + about anything. */ + if (is_caller_mc_daemon(instance)) { + used_l2table->flags &= + ~MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC; + } + delete_used_l2_table(used_l2table, + FREE_FROM_NWD); + used_l2table = NULL; + break; + } + + } while (FALSE); + + /* release semaphore */ + up(&(mc_drv_kmod_ctx.wsm_l2_sem)); + + } while (FALSE); + + + + /* release PID struct reference */ + if (pid_struct != NULL) + put_pid(pid_struct); + + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + + +/*----------------------------------------------------------------------------*/ +/** + * Unmap a virtual memory buffer from mobicore + * @param instance + * @param handle + * + * @return 0 if no error + * + */ +int mobicore_unmap_vmem( + struct mc_instance *instance, + uint32_t handle +) +{ + int ret = 0; + struct mc_used_l2_table *used_l2table = NULL; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR("processOpenSession() failed with %d\n", + ret); + ret = -ERESTARTSYS; + break; + } + + do { + used_l2table = find_used_l2_table_by_handle(handle); + if (used_l2table == NULL) { + ret = -EINVAL; + MCDRV_DBG_ERROR("entry not found\n"); + break; + } + + if (instance != used_l2table->owner) { + ret = -EINVAL; + MCDRV_DBG_ERROR("instance does no own it\n"); + break; + } + + /* free table (if no further locks exist) */ + delete_used_l2_table(used_l2table, FREE_FROM_NWD); + used_l2table = NULL; + /* there are no out parameters */ + } while (FALSE); + /* release semaphore */ + up(&(mc_drv_kmod_ctx.wsm_l2_sem)); + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} +EXPORT_SYMBOL(mobicore_unmap_vmem); +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_app_unregister_wsm_l2( + struct mc_instance *instance, + struct mc_ioctl_app_unreg_wsm_l2_params *user_params +) +{ + int ret = 0; + struct mc_ioctl_app_unreg_wsm_l2_params params; + struct mc_used_l2_table *used_l2table = NULL; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user\n"); + break; + } + + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR("down_interruptible() failed with %d\n", + ret); + ret = -ERESTARTSYS; + break; + } + + do { + /* daemon can do this for another task. */ + if (params.in.pid != 0) { + MCDRV_DBG_ERROR("pid != 0 unsupported\n"); + ret = -EINVAL; + break; + } + + used_l2table = + find_used_l2_table_by_handle(params.in.handle); + if (used_l2table == NULL) { + ret = -EINVAL; + MCDRV_DBG_ERROR("entry not found\n"); + break; + } + + if (is_caller_mc_daemon(instance)) { + /* if daemon does this, we have to release the + MobiCore lock. */ + used_l2table->flags &= + ~MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC; + } else if (instance != used_l2table->owner) { + ret = -EINVAL; + MCDRV_DBG_ERROR("instance does no own it\n"); + break; + } + + /* free table (if no further locks exist) */ + delete_used_l2_table(used_l2table, FREE_FROM_NWD); + used_l2table = NULL; + + /* there are no out parameters */ + + } while (FALSE); + + /* release semaphore */ + up(&(mc_drv_kmod_ctx.wsm_l2_sem)); + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + + +/*----------------------------------------------------------------------------*/ +static int handle_ioctl_daemon_lock_wsm_l2( + struct mc_instance *instance, + struct mc_ioctl_daemon_lock_wsm_l2_params *user_params +) +{ + int ret = 0; + struct mc_ioctl_daemon_lock_wsm_l2_params params; + struct mc_used_l2_table *used_l2table = NULL; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user\n"); + break; + } + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR("down_interruptible() failed with %d\n", + ret); + ret = -ERESTARTSYS; + break; + } + + do { + used_l2table = + find_used_l2_table_by_handle(params.in.handle); + if (used_l2table == NULL) { + ret = -EINVAL; + MCDRV_DBG_ERROR("entry not found\n"); + break; + } + if (instance != used_l2table->owner) { + ret = -EINVAL; + MCDRV_DBG_ERROR("instance does no own it\n"); + break; + } + + /* lock entry */ + if ((used_l2table->flags & + MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC) != 0) { + MCDRV_DBG_WARN("entry already locked\n"); + } + used_l2table->flags |= + MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC; + + /* prepare response */ + memset(&(params.out), 0, sizeof(params.out)); + params.out.phys_wsm_l2_table = + (uint32_t)get_l2_table_phys(used_l2table); + + /* copy to user space */ + ret = copy_to_user( + &(user_params->out), + &(params.out), + sizeof(params.out)); + if (ret != 0) { + MCDRV_DBG_ERROR("copy_to_user() failed\n"); + + /* undo, as userspace did not get it. */ + used_l2table->flags |= + MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC; + break; + } + + } while (FALSE); + + /* release semaphore */ + up(&(mc_drv_kmod_ctx.wsm_l2_sem)); + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + + +/*----------------------------------------------------------------------------*/ +static int handle_ioctl_daemon_unlock_wsm_l2( + struct mc_instance *instance, + struct mc_ioctl_daemon_unlock_wsm_l2_params *user_params +) +{ + int ret = 0; + struct mc_ioctl_daemon_unlock_wsm_l2_params params; + struct mc_used_l2_table *used_l2table = NULL; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user\n"); + break; + } + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR("down_interruptible() failed with %d\n", + ret); + ret = -ERESTARTSYS; + break; + } + + do { + used_l2table = + find_used_l2_table_by_handle(params.in.handle); + if (used_l2table == NULL) { + ret = -EINVAL; + MCDRV_DBG_ERROR("entry not found\n"); + break; + } + if (instance != used_l2table->owner) { + ret = -EINVAL; + MCDRV_DBG_ERROR("instance does no own it\n"); + break; + } + + /* lock entry */ + if ((used_l2table->flags & + MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC) == 0) { + MCDRV_DBG_WARN("entry is not locked locked\n"); + } + + /* free table (if no further locks exist) */ + delete_used_l2_table(used_l2table, FREE_FROM_SWD); + used_l2table = NULL; + + /* there are no out parameters */ + + } while (FALSE); + + } while (FALSE); + + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** Clears the reserved bit of each page and frees the pages */ +static inline void free_continguous_pages( + void *addr, + unsigned int size +) +{ + struct page *page = virt_to_page(addr); + int i; + for (i = 0; i < size; i++) { + MCDRV_DBG_VERBOSE("free page at 0x%p\n", page); + ClearPageReserved(page); + page++; + } + /* REV luh: see man kmalloc */ + free_pages((unsigned long)addr, size_to_order(size)); +} + +/*----------------------------------------------------------------------------*/ +/** + * Free a WSM buffer allocated with mobicore_allocate_wsm + * @param instance + * @param handle handle of the buffer + * + * @return 0 if no error + * + */ +int mobicore_free( + struct mc_instance *instance, + uint32_t handle +) +{ + int ret = 0; + unsigned int i; + struct mc_contg_buffer *contg_buffer; + + do { + /* search for the given address in the contg_buffers list */ + for (i = 0; i < MC_DRV_KMOD_CONTG_BUFFER_MAX; i++) { + contg_buffer = &(instance->contg_buffers[i]); + if (contg_buffer->handle == handle) + break; + } + if (i == MC_DRV_KMOD_CONTG_BUFFER_MAX) { + MCDRV_DBG_ERROR("contigous buffer not found\n"); + ret = -EFAULT; + break; + } + + MCDRV_DBG_VERBOSE("phys_addr=0x%p, virt_addr=0x%p\n", + contg_buffer->phys_addr, + contg_buffer->virt_kernel_addr); + + free_continguous_pages(contg_buffer->virt_kernel_addr, + contg_buffer->num_pages); + + memset(contg_buffer, 0, sizeof(*contg_buffer)); + + /* there are no out parameters */ + + } while (FALSE); + + + return ret; +} +EXPORT_SYMBOL(mobicore_free); +/*----------------------------------------------------------------------------*/ + +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_free( + struct mc_instance *instance, + union mc_ioctl_free_params *user_params +) +{ + int ret = 0; + union mc_ioctl_free_params params; + + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user\n"); + break; + } + + /* daemon can do this for another task. */ + if (params.in.pid != 0) { + MCDRV_DBG_ERROR("pid != 0 unsupported\n"); + ret = -EINVAL; + break; + } + + ret = mobicore_free(instance, params.in.handle); + + /* there are no out parameters */ + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; + +} + + +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_info( + struct mc_instance *instance, + union mc_ioctl_info_params *user_params +) +{ + int ret = 0; + union mc_ioctl_info_params params; + union mc_fc_info fc_info; + + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* only the MobiCore Daemon is allowed to call this function */ + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user\n"); + break; + } + + + memset(&fc_info, 0, sizeof(fc_info)); + fc_info.as_in.cmd = MC_FC_INFO; + fc_info.as_in.ext_info_id = params.in.ext_info_id; + + MCDRV_DBG( + "fc_info in cmd=0x%08x, ext_info_id=0x%08x " + "rfu=(0x%08x, 0x%08x)\n", + fc_info.as_in.cmd, + fc_info.as_in.ext_info_id, + fc_info.as_in.rfu[0], + fc_info.as_in.rfu[1]); + + mc_fastcall(&(fc_info.as_generic)); + + MCDRV_DBG( + "fc_info out resp=0x%08x, ret=0x%08x " + "state=0x%08x, ext_info=0x%08x\n", + fc_info.as_out.resp, + fc_info.as_out.ret, + fc_info.as_out.state, + fc_info.as_out.ext_info); + + ret = convert_fc_ret(fc_info.as_out.ret); + if (ret != 0) + break; + + memset(&(params.out), 0, sizeof(params.out)); + params.out.state = fc_info.as_out.state; + params.out.ext_info = fc_info.as_out.ext_info; + + ret = copy_to_user( + &(user_params->out), + &(params.out), + sizeof(params.out)); + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_to_user\n"); + break; + } + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_yield( + struct mc_instance *instance +) +{ + int ret = 0; + union mc_fc_s_yield fc_s_yield; + + MCDRV_ASSERT(instance != NULL); + + /* avoid putting debug output here, as we do this very often */ + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* only the MobiCore Daemon is allowed to call this function */ + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + memset(&fc_s_yield, 0, sizeof(fc_s_yield)); + fc_s_yield.as_in.cmd = MC_SMC_N_YIELD; + mc_fastcall(&(fc_s_yield.as_generic)); + ret = convert_fc_ret(fc_s_yield.as_out.ret); + if (ret != 0) + break; + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * handle ioctl and call common notify + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_nsiq( + struct mc_instance *instance, + unsigned long arg +) +{ + int ret = 0; + + MCDRV_ASSERT(instance != NULL); + + /* avoid putting debug output here, as we do this very often */ + MCDRV_DBG_VERBOSE("enter\n"); + /* only the MobiCore Daemon is allowed to call this function */ + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + return -EFAULT; + } + + do { + union mc_fc_nsiq fc_nsiq; + memset(&fc_nsiq, 0, sizeof(fc_nsiq)); + fc_nsiq.as_in.cmd = MC_SMC_N_SIQ; + mc_fastcall(&(fc_nsiq.as_generic)); + ret = convert_fc_ret(fc_nsiq.as_out.ret); + if (ret != 0) + break; + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_dump_status( + struct mc_instance *instance, + unsigned long arg +) +{ + int ret = 0; + int i = 0; + union mc_fc_info fc_info; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* anybody with root access can do this. */ + if (!is_userland_caller_privileged()) { + MCDRV_DBG_ERROR("caller must have root privileges\n"); + ret = -EFAULT; + break; + } + + /* loop ext_info */ + while (TRUE) { + memset(&fc_info, 0, sizeof(fc_info)); + fc_info.as_in.cmd = MC_FC_INFO; + fc_info.as_in.ext_info_id = i; + + MCDRV_DBG( + "fc_info in cmd=0x%08x, ext_info_id=0x%08x " + "rfu=(0x%08x, 0x%08x)\n", + fc_info.as_in.cmd, + fc_info.as_in.ext_info_id, + fc_info.as_in.rfu[0], + fc_info.as_in.rfu[1]); + + mc_fastcall(&(fc_info.as_generic)); + + MCDRV_DBG( + "fc_info out resp=0x%08x, ret=0x%08x " + "state=0x%08x, ext_info=0x%08x\n", + fc_info.as_out.resp, + fc_info.as_out.ret, + fc_info.as_out.state, + fc_info.as_out.ext_info); + + ret = convert_fc_ret(fc_info.as_out.ret); + if (ret != 0) + break; + + MCDRV_DBG("state=%08X, idx=%02d: ext_info=%08X\n", + fc_info.as_out.state, + i, + fc_info.as_out.ext_info); + i++; + }; + + if (ret != 0) + break; + + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_init( + struct mc_instance *instance, + union mc_ioctl_init_params *user_params +) +{ + int ret = 0; + union mc_ioctl_init_params params; + union mc_fc_init fc_init; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* only the MobiCore Daemon is allowed to call this function */ + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user failed\n"); + break; + } + + memset(&fc_init, 0, sizeof(fc_init)); + + fc_init.as_in.cmd = MC_FC_INIT; + /* base address of mci buffer 4KB aligned */ + fc_init.as_in.base = (uint32_t)params.in.base; + /* notification buffer start/length [16:16] [start, length] */ + fc_init.as_in.nq_info = (params.in.nq_offset << 16) + | (params.in.nq_length & 0xFFFF); + /* mcp buffer start/length [16:16] [start, length] */ + fc_init.as_in.mcp_info = (params.in.mcp_offset << 16) + | (params.in.mcp_length & 0xFFFF); + + /* Set KMOD notification queue to start of MCI + mciInfo was already set up in mmap */ + if (!mci_base) { + MCDRV_DBG_ERROR("No MCI set yet.\n"); + return -EFAULT; + } + MCDRV_DBG("in cmd=0x%08x, base=0x%08x, " + "nq_info=0x%08x, mcp_info=0x%08x\n", + fc_init.as_in.cmd, + fc_init.as_in.base, + fc_init.as_in.nq_info, + fc_init.as_in.mcp_info); + + mc_fastcall(&(fc_init.as_generic)); + + MCDRV_DBG("out cmd=0x%08x, ret=0x%08x rfu=(0x%08x, 0x%08x)\n", + fc_init.as_out.resp, + fc_init.as_out.ret, + fc_init.as_out.rfu[0], + fc_init.as_out.rfu[1]); + + ret = convert_fc_ret(fc_init.as_out.ret); + if (ret != 0) + break; + + /* no ioctl response parameters */ + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_fc_execute( + struct mc_instance *instance, + union mc_ioctl_fc_execute_params *user_params +) +{ + int ret = 0; + union mc_ioctl_fc_execute_params params; + union fc_generic fc_params; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* only the MobiCore Daemon is allowed to call this function */ + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + ret = copy_from_user( + &(params.in), + &(user_params->in), + sizeof(params.in)); + if (ret != 0) { + MCDRV_DBG_ERROR("copy_from_user failed\n"); + break; + } + + fc_params.as_in.cmd = -4;/*FC_EXECUTE */ + fc_params.as_in.param[0] = params.in.phys_start_addr; + fc_params.as_in.param[1] = params.in.length; + fc_params.as_in.param[2] = 0; + + MCDRV_DBG("in cmd=0x%08x, startAddr=0x%08x, length=0x%08x\n", + fc_params.as_in.cmd, + fc_params.as_in.param[0], + fc_params.as_in.param[1]); + + mc_fastcall(&fc_params); + + MCDRV_DBG("out cmd=0x%08x, ret=0x%08x rfu=(0x%08x, 0x%08x)\n", + fc_params.as_out.resp, + fc_params.as_out.ret, + fc_params.as_out.param[0], + fc_params.as_out.param[1]); + + ret = convert_fc_ret(fc_params.as_out.ret); + if (ret != 0) + break; + + /* no ioctl response parameters */ + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +#define MC_MAKE_VERSION(major, minor) \ + (((major & 0x0000ffff) << 16) | (minor & 0x0000ffff)) +/** + * + * @param instance + * @param arg + * + * @return 0 if no error + * + */ +static int handle_ioctl_get_version( + struct mc_instance *instance, + struct mc_ioctl_get_version_params *user_params +) +{ + int ret = 0; + struct mc_ioctl_get_version_params params = { + { + MC_MAKE_VERSION(MCDRVMODULEAPI_VERSION_MAJOR, + MCDRVMODULEAPI_VERSION_MINOR) + } + }; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG_VERBOSE("enter\n"); + + do { + MCDRV_DBG("mcDrvModuleApi version is %i.%i\n", + MCDRVMODULEAPI_VERSION_MAJOR, + MCDRVMODULEAPI_VERSION_MINOR); + + /* no ioctl response parameters */ + ret = copy_to_user( + &(user_params->out), + &(params.out), + sizeof(params.out)); + if (ret != 0) + MCDRV_DBG_ERROR("copy_to_user() failed\n"); + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * This function will be called from user space as ioctl(...). + * @param file pointer to file + * @param cmd command + * @param arg arguments + * + * @return int 0 for OK and an errno in case of error + */ +static long mc_kernel_module_ioctl( + struct file *file, + unsigned int cmd, + unsigned long arg +) +{ + int ret; + struct mc_instance *instance = get_instance(file); + + MCDRV_ASSERT(instance != NULL); + + switch (cmd) { + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_DUMP_STATUS: + ret = handle_ioctl_dump_status( + instance, + arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_FC_INIT: + ret = handle_ioctl_init( + instance, + (union mc_ioctl_init_params *)arg); + break; + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_FC_INFO: + ret = handle_ioctl_info( + instance, + (union mc_ioctl_info_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_FC_YIELD: + ret = handle_ioctl_yield( + instance); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_FC_NSIQ: + ret = handle_ioctl_nsiq( + instance, + arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_DAEMON_LOCK_WSM_L2: + ret = handle_ioctl_daemon_lock_wsm_l2( + instance, + (struct mc_ioctl_daemon_lock_wsm_l2_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_DAEMON_UNLOCK_WSM_L2: + ret = handle_ioctl_daemon_unlock_wsm_l2( + instance, + (struct mc_ioctl_daemon_unlock_wsm_l2_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_FREE: + /* called by ClientLib */ + ret = handle_ioctl_free( + instance, + (union mc_ioctl_free_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_APP_REGISTER_WSM_L2: + /* called by ClientLib */ + ret = handle_ioctl_app_register_wsm_l2( + instance, + (union mc_ioctl_app_reg_wsm_l2_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_APP_UNREGISTER_WSM_L2: + /* called by ClientLib */ + ret = handle_ioctl_app_unregister_wsm_l2( + instance, + (struct mc_ioctl_app_unreg_wsm_l2_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_FC_EXECUTE: + ret = handle_ioctl_fc_execute( + instance, + (union mc_ioctl_fc_execute_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + case MC_DRV_KMOD_IOCTL_GET_VERSION: + ret = handle_ioctl_get_version( + instance, + (struct mc_ioctl_get_version_params *)arg); + break; + + /*--------------------------------------------------------------------*/ + default: + MCDRV_DBG_ERROR("unsupported cmd=%d\n", cmd); + ret = -EFAULT; + break; + + } /* end switch(cmd) */ + +#ifdef MC_MEM_TRACES + mobicore_log_read(); +#endif + + return (int)ret; +} + + +/*----------------------------------------------------------------------------*/ +/** + * This function will be called from user space as read(...). + * The read function is blocking until a interrupt occurs. In that case the + * event counter is copied into user space and the function is finished. + * @param *file + * @param *buffer buffer where to copy to(userspace) + * @param buffer_len number of requested data + * @param *pos not used + * @return ssize_t ok case: number of copied data + * error case: return errno + */ +static ssize_t mc_kernel_module_read( + struct file *file, + char *buffer, + size_t buffer_len, + loff_t *pos +) +{ + int ret = 0, ssiq_counter; + size_t retLen = 0; + struct mc_instance *instance = get_instance(file); + + MCDRV_ASSERT(instance != NULL); + + /* avoid debug output on non-error, because this is call quite often */ + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* only the MobiCore Daemon is allowed to call this function */ + if (!is_caller_mc_daemon(instance)) { + MCDRV_DBG_ERROR("caller not MobiCore Daemon\n"); + ret = -EFAULT; + break; + } + + if (buffer_len < sizeof(unsigned int)) { + MCDRV_DBG_ERROR("invalid length\n"); + ret = (ssize_t)(-EINVAL); + break; + } + + for (;;) { + if (down_interruptible( + &mc_drv_kmod_ctx.daemon_ctx.sem)) { + MCDRV_DBG_VERBOSE("read interrupted\n"); + ret = (ssize_t)-ERESTARTSYS; + break; + } + + ssiq_counter = atomic_read( + &(mc_drv_kmod_ctx.ssiq_ctx.counter)); + MCDRV_DBG_VERBOSE("ssiq_counter=%i, ctx.counter=%i\n", + ssiq_counter, + mc_drv_kmod_ctx.daemon_ctx.ssiq_counter); + + if (ssiq_counter != + mc_drv_kmod_ctx.daemon_ctx.ssiq_counter) { + /* read data and exit loop without + error */ + mc_drv_kmod_ctx.daemon_ctx.ssiq_counter = + ssiq_counter; + ret = 0; + break; + } + + /* end loop if non-blocking */ + if ((file->f_flags & O_NONBLOCK) != 0) { + MCDRV_DBG_ERROR("non-blocking read\n"); + ret = (ssize_t)(-EAGAIN); + break; + } + + if (signal_pending(current) != 0) { + MCDRV_DBG_VERBOSE("received signal.\n"); + ret = (ssize_t)(-ERESTARTSYS); + break; + } + + } + + /* we are here if an event occurred or we had an + error.*/ + if (ret != 0) + break; + + /* read data and exit loop */ + ret = copy_to_user( + buffer, + &(mc_drv_kmod_ctx.daemon_ctx.ssiq_counter), + sizeof(unsigned int)); + + + if (ret != 0) { + MCDRV_DBG_ERROR("copy_to_user failed\n"); + ret = (ssize_t)(-EFAULT); + break; + } + + retLen = sizeof(s32); + + } while (FALSE); + + /* avoid debug on non-error. */ + if (ret == 0) + ret = (size_t)retLen; + else + MCDRV_DBG("exit with %d/0x%08X\n", ret, ret); + + return (ssize_t)ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * Allocate WSM for given instance + * + * @param instance instance + * @param requested_size size of the WSM + * @param handle pointer where the handle will be saved + * @param virt_kernel_addr pointer for the kernel virtual address + * @param phys_addr pointer for the physical address + * + * @return error code or 0 for success + */ +int mobicore_allocate_wsm( + struct mc_instance *instance, + unsigned long requested_size, + uint32_t *handle, + void **virt_kernel_addr, + void **phys_addr +) +{ + unsigned int i; + unsigned int order; + unsigned long allocated_size; + int ret = 0; + struct mc_contg_buffer *contg_buffer = 0; + void *virt_kernel_addr_stack; + void *phys_addr_stack; + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG("%s (size=%ld)\n", __func__, requested_size); + + order = size_to_order(requested_size); + if (order == INVALID_ORDER) { + MCDRV_DBG_ERROR( + "size to order converting failed for size %ld\n", + requested_size); + return INVALID_ORDER; + } + + allocated_size = (1< order %d --> %ld (2^n pages)\n", + requested_size, order, allocated_size); + + do { + /* Usual Wsm request, allocate contigous buffer. */ + /* search for a free entry in the wsm buffer list + * REV axh: serialize this over multiple instances. */ + for (i = 0; i < MC_DRV_KMOD_CONTG_BUFFER_MAX; i++) { + contg_buffer = &(instance->contg_buffers[i]); + if (contg_buffer->handle == 0) { + contg_buffer->handle = get_mc_kmod_unique_id(); + break; + } + } + if (i == MC_DRV_KMOD_CONTG_BUFFER_MAX) { + MCDRV_DBG_ERROR("no free contigous buffer\n"); + ret = -EFAULT; + break; + } + + /* Common code for all allocation paths */ + virt_kernel_addr_stack = (void *)__get_free_pages( + GFP_USER | __GFP_COMP, + order); + if (virt_kernel_addr_stack == NULL) { + MCDRV_DBG_ERROR("get_free_pages failed\n"); + ret = -ENOMEM; + break; + } + + /* Get physical address to instance data */ + phys_addr_stack = (void *)virt_to_phys(virt_kernel_addr_stack); + /* TODO: check for INVALID_ADDRESS? */ + + MCDRV_DBG( + "allocated phys=0x%p - 0x%p, " + "size=%ld, kernel_virt=0x%p, handle=%d\n", + phys_addr_stack, + (void *)((unsigned int)phys_addr_stack+allocated_size), + allocated_size, + virt_kernel_addr_stack, + contg_buffer->handle); + + /* Usual Wsm request, allocate contg_buffer. + * Also, we never free a persistent Tci */ + contg_buffer->phys_addr = phys_addr_stack; + contg_buffer->virt_kernel_addr = virt_kernel_addr_stack; + contg_buffer->virt_user_addr = virt_kernel_addr_stack; + contg_buffer->num_pages = (1U << order); + *handle = contg_buffer->handle; + *virt_kernel_addr = virt_kernel_addr_stack; + *phys_addr = phys_addr_stack; + + } while (FALSE); + + MCDRV_DBG_VERBOSE("%s: exit with 0x%08X\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(mobicore_allocate_wsm); + + +/*----------------------------------------------------------------------------*/ +/** + * This function will be called from user space as address = mmap(...). + * + * @param file + * @param vmarea + * vmarea.pg_offset != 0 is mapping of MCI is requested + * + * @return 0 if OK or -ENOMEM in case of error. + */ +static int mc_kernel_module_mmap( + struct file *file, + struct vm_area_struct *vmarea +) +{ + unsigned int i; + unsigned int order; + void *virt_kernel_addr_stack = 0; + void *phys_addr = 0; + unsigned long requested_size = + vmarea->vm_end - vmarea->vm_start; + unsigned long allocated_size; + int ret = 0; + struct mc_contg_buffer *contg_buffer = 0; + unsigned int handle = 0; + struct mc_instance *instance = get_instance(file); + unsigned int request = vmarea->vm_pgoff * 4096; +#if defined(DEBUG) + bool release = false; +#else + bool release = true; +#endif + + MCDRV_ASSERT(instance != NULL); + MCDRV_DBG("enter (vmaStart=0x%p, size=%ld, request=0x%x, mci=0x%x)\n", + (void *)vmarea->vm_start, + requested_size, + request, + mci_base); + + order = size_to_order(requested_size); + if (order == INVALID_ORDER) { + MCDRV_DBG_ERROR( + "size to order converting failed for size %ld\n", + requested_size); + return -ENOMEM; + } + + allocated_size = (1< order %d --> %ld (2^n pages)\n", + requested_size, order, allocated_size); + + do { + /* Daemon tries to get an existing MCI */ + if ((request == MC_DRV_KMOD_MMAP_MCI) && (mci_base != 0)) { + MCDRV_DBG("Request MCI, it is at (%x)\n", mci_base); + + if (!is_caller_mc_daemon(instance)) { + ret = -EPERM; + break; + } + virt_kernel_addr_stack = (void *)mci_base; + phys_addr = + (void *)virt_to_phys(virt_kernel_addr_stack); + } else { + /* Usual Wsm request, allocate buffer. */ + if (request == MC_DRV_KMOD_MMAP_WSM) { + /* search for a free entry in the buffer list + REV axh: serialize this over multiple instances. + */ + for (i = 0; i < MC_DRV_KMOD_CONTG_BUFFER_MAX; + i++) { + contg_buffer = + &(instance->contg_buffers[i]); + if (contg_buffer->handle == 0) { + contg_buffer->handle = + get_mc_kmod_unique_id(); + break; + } + } + if (i == MC_DRV_KMOD_CONTG_BUFFER_MAX) { + MCDRV_DBG_ERROR( + "no free contigous buffer\n"); + ret = -EFAULT; + break; + } + } else { + if (request <= MC_DRV_KMOD_MMAP_PERSISTENTWSM + || release) { + /* Special Wsm request + --> only Daemon is allowed */ + if (!is_caller_mc_daemon(instance)) { + ret = -EPERM; + break; + } + } + } + if (request <= MC_DRV_KMOD_MMAP_PERSISTENTWSM) { + /* Common code for all allocation paths + * get physical address, */ + virt_kernel_addr_stack = + (void *)__get_free_pages( + GFP_USER | __GFP_COMP, + order); + if (virt_kernel_addr_stack == NULL) { + MCDRV_DBG_ERROR( + "get_free_pages failed\n"); + ret = -ENOMEM; + break; + } + if (request == MC_DRV_KMOD_MMAP_WSM) + handle = contg_buffer->handle; + /* Get physical address to instance data */ + /* TODO: check for INVALID_ADDRESS? */ + phys_addr = (void *)virt_to_phys( + virt_kernel_addr_stack); + } else { +#if defined(DEBUG) + phys_addr = (void *)request; + virt_kernel_addr_stack = phys_to_virt(request); +#endif + } + } + /* Common code for all mmap calls: + * map page to user + * store data in page */ + + MCDRV_DBG("allocated phys=0x%p - 0x%p, " + "size=%ld, kernel_virt=0x%p, handle=%d\n", + phys_addr, + (void *)((unsigned int)phys_addr+allocated_size), + allocated_size, virt_kernel_addr_stack, handle); + + vmarea->vm_flags |= VM_RESERVED; + /* convert Kernel address to User Address. Kernel address begins + at PAGE_OFFSET, user Address range is below PAGE_OFFSET. + Remapping the area is always done, so multiple mappings + of one region are possible. Now remap kernel address + space into user space */ + ret = (int)remap_pfn_range( + vmarea, + (vmarea->vm_start), + addr_to_pfn(phys_addr), + requested_size, + vmarea->vm_page_prot); + if (ret != 0) { + MCDRV_DBG_ERROR("remapPfnRange failed\n"); + + /* free allocated pages when mmap fails, however, do not + do it, when daemon tried to get an MCI that + existed */ + if (!((request == MC_DRV_KMOD_MMAP_MCI) && + (mci_base != 0))) + free_continguous_pages(virt_kernel_addr_stack, + (1U << order)); + break; + } + + /* Usual Wsm request, allocate contg_buffer. + When requesting Mci, we do not associate the page with + the process. + Note: we also never free the Mci + Also, we never free a persistent Tci */ + if (request == MC_DRV_KMOD_MMAP_WSM) { + contg_buffer->phys_addr = phys_addr; + contg_buffer->virt_kernel_addr = virt_kernel_addr_stack; + contg_buffer->virt_user_addr = + (void *)(vmarea->vm_start); + contg_buffer->num_pages = (1U << order); + } + + /* set response in allocated buffer */ + { + struct mc_mmap_resp *mmap_resp = + (struct mc_mmap_resp *)virt_kernel_addr_stack; + /* TODO: do this for daemon only, otherwise set NULL */ + mmap_resp->phys_addr = (uint32_t)phys_addr; + mmap_resp->handle = handle; + if ((request == MC_DRV_KMOD_MMAP_MCI) && + (mci_base != 0)) { + mmap_resp->is_reused = 1; + } else + mmap_resp->is_reused = 0; + } + + /* store MCI pointer */ + if ((request == MC_DRV_KMOD_MMAP_MCI) && (mci_base == 0)) { + mci_base = (uint32_t)virt_kernel_addr_stack; + MCDRV_DBG("MCI base set to 0x%x\n", mci_base); + } + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return (int)ret; +} + +#ifdef CONFIG_SMP +/*----------------------------------------------------------------------------*/ +/** + * Force migration of current task to CPU0(where the monitor resides) + * + * @return Error code or 0 for success + */ +static int goto_cpu0( + void +) +{ + int ret = 0; + struct cpumask mask = CPU_MASK_CPU0; + + MCDRV_DBG_VERBOSE("System has %d CPU's, we are on CPU #%d\n" + "\tBinding this process to CPU #0.\n" + "\tactive mask is %lx, setting it to mask=%lx\n", + nr_cpu_ids, + raw_smp_processor_id(), + cpu_active_mask->bits[0], + mask.bits[0]); + ret = set_cpus_allowed_ptr(current, &mask); + if (ret != 0) + MCDRV_DBG_ERROR("set_cpus_allowed_ptr=%d.\n", ret); + MCDRV_DBG_VERBOSE("And now we are on CPU #%d\n", + raw_smp_processor_id()); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** + * Restore CPU mask for current to ALL Cpus(reverse of goto_cpu0) + * + * @return Error code or 0 for success + */ +static int goto_all_cpu( + void +) +{ + int ret = 0; + + struct cpumask mask = CPU_MASK_ALL; + + MCDRV_DBG_VERBOSE("System has %d CPU's, we are on CPU #%d\n" + "\tBinding this process to CPU #0.\n" + "\tactive mask is %lx, setting it to mask=%lx\n", + nr_cpu_ids, + raw_smp_processor_id(), + cpu_active_mask->bits[0], + mask.bits[0]); + ret = set_cpus_allowed_ptr(current, &mask); + if (ret != 0) + MCDRV_DBG_ERROR("set_cpus_allowed_ptr=%d.\n", ret); + MCDRV_DBG_VERBOSE("And now we are on CPU #%d\n", + raw_smp_processor_id()); + + return ret; +} + +#else +static int goto_cpu0(void) +{ + return 0; +} + +static int goto_all_cpu(void) +{ + return 0; +} +#endif + +/*----------------------------------------------------------------------------*/ +/** + * Initialize a new mobicore API instance object + * + * @return Instance or NULL if no allocation was possible. + */ +struct mc_instance *mobicore_open( + void +) { + struct mc_instance *instance; + pid_t pid_vnr; + + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + if (instance == NULL) + return NULL; + + /* get a unique ID for this instance (PIDs are not unique) */ + instance->handle = get_mc_kmod_unique_id(); + + /* get the PID of the calling process. We avoid using + * current->pid directly, as 2.6.24 introduced PID + * namespaces. See also http://lwn.net/Articles/259217 */ + pid_vnr = task_pid_vnr(current); + instance->pid_vnr = pid_vnr; + + return instance; +} +EXPORT_SYMBOL(mobicore_open); + +/*----------------------------------------------------------------------------*/ +/** + * This function will be called from user space as fd = open(...). + * A set of internal instance data are created and initialized. + * + * @param inode + * @param file + * @return 0 if OK or -ENOMEM if no allocation was possible. + */ +static int mc_kernel_module_open( + struct inode *inode, + struct file *file +) +{ + struct mc_instance *instance; + int ret = 0; + + MCDRV_DBG_VERBOSE("enter\n"); + + do { + instance = mobicore_open(); + if (instance == NULL) + return -ENOMEM; + + /* check if Daemon. We simply assume that the first to open us + with root privileges must be the daemon. */ + if ((is_userland_caller_privileged()) + && (mc_drv_kmod_ctx.daemon_inst == NULL)) { + MCDRV_DBG("accept this as MobiCore Daemon\n"); + + /* Set the caller's CPU mask to CPU0*/ + ret = goto_cpu0(); + if (ret != 0) { + mobicore_release(instance); + file->private_data = NULL; + MCDRV_DBG("changing core failed!\n"); + break; + } + + mc_drv_kmod_ctx.daemon_inst = instance; + sema_init(&mc_drv_kmod_ctx.daemon_ctx.sem, + DAEMON_SEM_VAL); + /* init ssiq event counter */ + mc_drv_kmod_ctx.daemon_ctx.ssiq_counter = + atomic_read( + &(mc_drv_kmod_ctx.ssiq_ctx.counter)); + +#ifdef MC_MEM_TRACES + /* The traces have to be setup on CPU-0 since we must + * do a fastcall to MobiCore. */ + if (!mci_base) + /* Do the work only if MCI base is not + * initialized properly */ + work_on_cpu(0, mobicore_log_setup, NULL); +#endif + } + + /* store instance data reference */ + file->private_data = instance; + + /* TODO axh: link all instances to allow clean up? */ + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return (int)ret; + +} + +/*----------------------------------------------------------------------------*/ +/** + * Release a mobicore instance object and all objects related to it + * @param instance instance + * @return 0 if Ok or -E ERROR + */ +int mobicore_release( + struct mc_instance *instance +) +{ + int ret = 0; + int i; + struct mc_used_l2_table *used_l2table, *used_l2table_temp; + + do { + /* try to get the semaphore */ + ret = down_interruptible(&(mc_drv_kmod_ctx.wsm_l2_sem)); + if (ret != 0) { + MCDRV_DBG_ERROR( + "down_interruptible() failed with %d\n", ret); + /* TODO: can be block here? */ + ret = -ERESTARTSYS; + } else { + /* Check if some WSM is still in use. */ + list_for_each_entry_safe( + used_l2table, + used_l2table_temp, + &(mc_drv_kmod_ctx.mc_used_l2_tables), + list + ) { + if (used_l2table->owner == instance) { + MCDRV_DBG_WARN( + "trying to release WSM L2: " + "physBase=%p ,nr_of_pages=%d\n", + get_l2_table_phys(used_l2table), + used_l2table->nr_of_pages); + + /* unlock app usage and free if MobiCore + does not use it */ + delete_used_l2_table(used_l2table, + FREE_FROM_NWD); + } + } /* end while */ + + /* release semaphore */ + up(&(mc_drv_kmod_ctx.wsm_l2_sem)); + } + + + /* release all mapped data */ + for (i = 0; i < MC_DRV_KMOD_CONTG_BUFFER_MAX; i++) { + struct mc_contg_buffer *contg_buffer = + &(instance->contg_buffers[i]); + + if (contg_buffer->virt_user_addr != 0) { + free_continguous_pages( + contg_buffer->virt_kernel_addr, + contg_buffer->num_pages); + } + } + + /* release instance context */ + kfree(instance); + } while (FALSE); + + return ret; +} +EXPORT_SYMBOL(mobicore_release); + +/*----------------------------------------------------------------------------*/ +/** + * This function will be called from user space as close(...). + * The instance data are freed and the associated memory pages are unreserved. + * + * @param inode + * @param file + * + * @return 0 + */ +static int mc_kernel_module_release( + struct inode *inode, + struct file *file +) +{ + int ret = 0; + struct mc_instance *instance = get_instance(file); + + MCDRV_DBG_VERBOSE("enter\n"); + + do { + /* check if daemon closes us. */ + if (is_caller_mc_daemon(instance)) { + /* TODO: cleanup? + * mc_drv_kmod_ctx.mc_used_l2_tables remains */ + MCDRV_DBG_WARN("WARNING: MobiCore Daemon died\n"); + mc_drv_kmod_ctx.daemon_inst = NULL; + } + + ret = mobicore_release(instance); + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return (int)ret; +} + + +/*----------------------------------------------------------------------------*/ +/** + * This function represents the interrupt function of the mcDrvModule. + * It signals by incrementing of an event counter and the start of the read + * waiting queue, the read function a interrupt has occurred. + * + * @param intr + * @param *context pointer to registered device data + * + * @return IRQ_HANDLED + */ +static irqreturn_t mc_kernel_module_intr_ssiq( + int intr, + void *context +) +{ + irqreturn_t ret = IRQ_NONE; + + /* we know the context. */ + MCDRV_ASSERT(&mc_drv_kmod_ctx == context); + + do { + if (intr != MC_INTR_SSIQ) { + /* this should not happen, as we did no register for any + other interrupt. For debugging, we print a + message, but continue */ + MCDRV_DBG_WARN( + "unknown interrupt %d, expecting only %d\n", + intr, MC_INTR_SSIQ); + } + MCDRV_DBG_VERBOSE("received interrupt %d\n", + intr); + + /* increment interrupt event counter */ + atomic_inc(&(mc_drv_kmod_ctx.ssiq_ctx.counter)); + + /* signal the daemon */ + up(&mc_drv_kmod_ctx.daemon_ctx.sem); + + + ret = IRQ_HANDLED; + + } while (FALSE); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/** function table structure of this device driver. */ +static const struct file_operations mc_kernel_module_file_operations = { + .owner = THIS_MODULE, /**< driver owner */ + .open = mc_kernel_module_open, /**< driver open function */ + .release = mc_kernel_module_release, /**< driver release function*/ + .unlocked_ioctl = mc_kernel_module_ioctl, /**< driver ioctl function */ + .mmap = mc_kernel_module_mmap, /**< driver mmap function */ + .read = mc_kernel_module_read, /**< driver read function */ +}; + +/*----------------------------------------------------------------------------*/ +/** registration structure as miscdevice. */ +static struct miscdevice mc_kernel_module_device = { + .name = MC_DRV_MOD_DEVNODE, /**< device name */ + .minor = MISC_DYNAMIC_MINOR, /**< device minor number */ + /** device interface function structure */ + .fops = &mc_kernel_module_file_operations, +}; + + +/*----------------------------------------------------------------------------*/ +/** + * This function is called the kernel during startup or by a insmod command. + * This device is installed and registered as miscdevice, then interrupt and + * queue handling is set up + * + * @return 0 for no error or -EIO if registration fails + */ +static int __init mc_kernel_module_init( + void +) +{ + int ret = 0; + + MCDRV_DBG("enter (Build " __TIMESTAMP__ ")\n"); + MCDRV_DBG("mcDrvModuleApi version is %i.%i\n", + MCDRVMODULEAPI_VERSION_MAJOR, + MCDRVMODULEAPI_VERSION_MINOR); +#ifdef MOBICORE_COMPONENT_BUILD_TAG + MCDRV_DBG("%s\n", MOBICORE_COMPONENT_BUILD_TAG); +#endif + do { + /* Hardware does not support ARM TrustZone + -> Cannot continue! */ + if (!has_security_extensions()) { + MCDRV_DBG_ERROR( + "Hardware does't support ARM TrustZone!\n"); + ret = -ENODEV; + break; + } + + /* Running in secure mode -> Cannot load the driver! */ + if (is_secure_mode()) { + MCDRV_DBG_ERROR("Running in secure MODE!\n"); + ret = -ENODEV; + break; + } + + sema_init(&mc_drv_kmod_ctx.daemon_ctx.sem, DAEMON_SEM_VAL); + /* set up S-SIQ interrupt handler */ + ret = request_irq( + MC_INTR_SSIQ, + mc_kernel_module_intr_ssiq, + IRQF_TRIGGER_RISING, + MC_DRV_MOD_DEVNODE, + &mc_drv_kmod_ctx); + if (ret != 0) { + MCDRV_DBG_ERROR("interrupt request failed\n"); + break; + } + + ret = misc_register(&mc_kernel_module_device); + if (ret != 0) { + MCDRV_DBG_ERROR("device register failed\n"); + break; + } + + /* initialize event counter for signaling of an IRQ to zero */ + atomic_set(&(mc_drv_kmod_ctx.ssiq_ctx.counter), 0); + + /* init list for WSM L2 chunks. */ + INIT_LIST_HEAD(&(mc_drv_kmod_ctx.mc_l2_tables_sets)); + + /* L2 table descriptor list. */ + INIT_LIST_HEAD(&(mc_drv_kmod_ctx.mc_used_l2_tables)); + + sema_init(&(mc_drv_kmod_ctx.wsm_l2_sem), 1); + + /* initialize unique number counter which we can use for + handles. It is limited to 2^32, but this should be + enough to be roll-over safe for us. We start with 1 + instead of 0. */ + atomic_set(&(mc_drv_kmod_ctx.unique_counter), 1); + + mci_base = 0; + MCDRV_DBG("initialized\n"); + + ret = 0; + + } while (FALSE); + + MCDRV_DBG_VERBOSE("exit with %d/0x%08X\n", ret, ret); + + return (int)ret; +} + + + +/*----------------------------------------------------------------------------*/ +/** + * This function removes this device driver from the Linux device manager . + */ +static void __exit mc_kernel_module_exit( + void +) +{ + struct mc_used_l2_table *used_l2table; + + MCDRV_DBG_VERBOSE("enter\n"); + + mobicore_log_free(); + + /* Check if some WSM is still in use. */ + list_for_each_entry( + used_l2table, + &(mc_drv_kmod_ctx.mc_used_l2_tables), + list + ) { + MCDRV_DBG_WARN( + "WSM L2 still in use: physBase=%p ,nr_of_pages=%d\n", + get_l2_table_phys(used_l2table), + used_l2table->nr_of_pages); + } /* end while */ + + free_irq(MC_INTR_SSIQ, &mc_drv_kmod_ctx); + + misc_deregister(&mc_kernel_module_device); + MCDRV_DBG_VERBOSE("exit"); +} + + +/*----------------------------------------------------------------------------*/ +/* Linux Driver Module Macros */ +module_init(mc_kernel_module_init); +module_exit(mc_kernel_module_exit); +MODULE_AUTHOR("Giesecke & Devrient GmbH"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MobiCore driver"); + +/** @} */ + diff --git a/drivers/gud/mobicore_driver/mc_drv_module.h b/drivers/gud/mobicore_driver/mc_drv_module.h new file mode 100644 index 0000000000000000000000000000000000000000..8b402d6ff37184c6b6e7b2e719ea5af8222a407c --- /dev/null +++ b/drivers/gud/mobicore_driver/mc_drv_module.h @@ -0,0 +1,238 @@ +/** + * Header file of MobiCore Driver Kernel Module. + * + * @addtogroup MCD_MCDIMPL_KMOD_IMPL + * @{ + * Internal structures of the McDrvModule + * @file + * + * Header file the MobiCore Driver Kernel Module, + * its internal structures and defines. + * + * + * + * 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 _MC_DRV_KMOD_H_ +#define _MC_DRV_KMOD_H_ + +#include "mc_drv_module_linux_api.h" +#include "public/mc_drv_module_api.h" +/** Platform specific settings */ +#include "platform.h" + +/** ARM Specific masks and modes */ +#define ARM_CPSR_MASK 0x1F +#define ARM_MONITOR_MODE 0b10110 +#define ARM_SECURITY_EXTENSION_MASK 0x30 + +/** + * Number of page table entries in one L2 table. This is ARM specific, an + * L2 table covers 1 MiB by using 256 entry referring to 4KiB pages each. + */ +#define MC_ARM_L2_TABLE_ENTRIES 256 + +/** Maximum number of contiguous buffer allocations for one driver instance. */ +#define MC_DRV_KMOD_CONTG_BUFFER_MAX 16 + +/** Number of pages for L2 tables. There are 4 table in each page. */ +#define MC_DRV_KMOD_L2_TABLE_PER_PAGES 4 + +/** ARM level 2 (L2) table with 256 entries. Size: 1k */ +struct l2table { + pte_t table_entries[MC_ARM_L2_TABLE_ENTRIES]; +}; + +#define INVALID_ADDRESS ((void *)(-1)) + +/** ARM L2 PTE bits */ +#define L2_FLAG_SMALL_XN (1U << 0) +#define L2_FLAG_SMALL (1U << 1) +#define L2_FLAG_B (1U << 2) +#define L2_FLAG_C (1U << 3) +#define L2_FLAG_AP0 (1U << 4) +#define L2_FLAG_AP1 (1U << 5) +#define L2_FLAG_SMALL_TEX0 (1U << 6) +#define L2_FLAG_SMALL_TEX1 (1U << 7) +#define L2_FLAG_SMALL_TEX2 (1U << 8) +#define L2_FLAG_APX (1U << 9) +#define L2_FLAG_S (1U << 10) +#define L2_FLAG_NG (1U << 11) + +/** + * Contiguous buffer allocated to TLCs. + * These buffers are uses as world shared memory (wsm) and shared with + * secure world. + * The virtual kernel address is added for a simpler search algorithm. + */ +struct mc_contg_buffer { + unsigned int handle; /* unique handle */ + void *virt_user_addr; /**< virtual User start address */ + void *virt_kernel_addr; /**< virtual Kernel start address */ + void *phys_addr; /**< physical start address */ + unsigned int num_pages; /**< number of pages */ +}; + +/** Instance data for MobiCore Daemon and TLCs. */ +struct mc_instance { + /** unique handle */ + unsigned int handle; + /** process that opened this instance */ + pid_t pid_vnr; + /** buffer list for mmap generated address space and + its virtual client address */ + struct mc_contg_buffer contg_buffers[MC_DRV_KMOD_CONTG_BUFFER_MAX]; +}; + +/** Store for four L2 tables in one 4kb page*/ +struct mc_l2_table_store { + struct l2table table[MC_DRV_KMOD_L2_TABLE_PER_PAGES]; +}; + +/** Usage and maintenance information about mc_l2_table_store */ +struct mc_l2_tables_set { + struct list_head list; + unsigned int usage_bitmap; /**< usage bitmap */ + struct mc_l2_table_store *kernel_virt; /**< kernel virtual address */ + struct mc_l2_table_store *phys; /**< physical address */ + struct page *page; /**< pointer to page struct */ +}; + +/** + * L2 table allocated to the Daemon or a TLC describing a world shared buffer. + * When users map a malloc()ed area into SWd, a L2 table is allocated. + * In addition, the area of maximum 1MB virtual address space is mapped into + * the L2 table and a handle for this table is returned to the user. + */ +struct mc_used_l2_table { + struct list_head list; + + /** handle as communicated to user mode */ + unsigned int handle; + unsigned int flags; + + /** owner of this L2 table */ + struct mc_instance *owner; + + /** set describing where our L2 table is stored */ + struct mc_l2_tables_set *set; + + /** index into L2 table set */ + unsigned int idx; + + /** size of buffer */ + unsigned int nr_of_pages; +}; + +#define MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_APP (1U << 0) +#define MC_WSM_L2_CONTAINER_WSM_LOCKED_BY_MC (1U << 1) + + +/** MobiCore S-SIQ interrupt context data. */ +struct mc_ssiq_ctx { + /** S-SIQ interrupt counter */ + atomic_t counter; +}; + +/** MobiCore Daemon context data. */ +struct mc_daemon_ctx { + /** event semaphore */ + struct semaphore sem; + struct fasync_struct *async_queue; + /** event counter */ + unsigned int ssiq_counter; +}; + +/** MobiCore Driver Kernel Module context data. */ +struct mc_drv_kmod_ctx { + + /** ever incrementing counter */ + atomic_t unique_counter; + + /** S-SIQ interrupt context */ + struct mc_ssiq_ctx ssiq_ctx; + + /** MobiCore Daemon context */ + struct mc_daemon_ctx daemon_ctx; + + /** pointer to instance of daemon */ + struct mc_instance *daemon_inst; + + /** Backing store for L2 tables */ + struct list_head mc_l2_tables_sets; + + /** Bookkeeping for used L2 tables */ + struct list_head mc_used_l2_tables; + + /** semaphore to synchronize access to above lists */ + struct semaphore wsm_l2_sem; +}; + +/** MobiCore internal trace buffer structure. */ +struct mc_trace_buf { + uint32_t version; /**< version of trace buffer */ + uint32_t length; /**< length of allocated buffer(includes header) */ + uint32_t write_pos; /**< last write position */ + char buff[1]; /**< start of the log buffer */ +}; + +/*** MobiCore internal trace log setup. */ +void mobicore_log_read(void); +long mobicore_log_setup(void *); +void mobicore_log_free(void); + +#define MCDRV_DBG_ERROR(txt, ...) \ + printk(KERN_ERR "mcDrvKMod [%d] %s() ### ERROR: " txt, \ + task_pid_vnr(current), \ + __func__, \ + ##__VA_ARGS__) + +/* dummy function helper macro. */ +#define DUMMY_FUNCTION() do {} while (0) + +#if defined(DEBUG) + +/* #define DEBUG_VERBOSE */ +#if defined(DEBUG_VERBOSE) +#define MCDRV_DBG_VERBOSE MCDRV_DBG +#else +#define MCDRV_DBG_VERBOSE(...) DUMMY_FUNCTION() +#endif + +#define MCDRV_DBG(txt, ...) \ + printk(KERN_INFO "mcDrvKMod [%d on CPU%d] %s(): " txt, \ + task_pid_vnr(current), \ + raw_smp_processor_id(), \ + __func__, \ + ##__VA_ARGS__) + +#define MCDRV_DBG_WARN(txt, ...) \ + printk(KERN_WARNING "mcDrvKMod [%d] %s() WARNING: " txt, \ + task_pid_vnr(current), \ + __func__, \ + ##__VA_ARGS__) + +#define MCDRV_ASSERT(cond) \ + do { \ + if (unlikely(!(cond))) { \ + panic("mcDrvKMod Assertion failed: %s:%d\n", \ + __FILE__, __LINE__); \ + } \ + } while (0) + +#else + +#define MCDRV_DBG_VERBOSE(...) DUMMY_FUNCTION() +#define MCDRV_DBG(...) DUMMY_FUNCTION() +#define MCDRV_DBG_WARN(...) DUMMY_FUNCTION() + +#define MCDRV_ASSERT(...) DUMMY_FUNCTION() + +#endif /* [not] defined(DEBUG) */ + + +#endif /* _MC_DRV_KMOD_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/mc_drv_module_android.h b/drivers/gud/mobicore_driver/mc_drv_module_android.h new file mode 100644 index 0000000000000000000000000000000000000000..319509f34456c0cad7fd3ef10701af868eb5b2f1 --- /dev/null +++ b/drivers/gud/mobicore_driver/mc_drv_module_android.h @@ -0,0 +1,37 @@ +/** + * Header file of MobiCore Driver Kernel Module. + * + * @addtogroup MobiCore_Driver_Kernel_Module + * @{ + * Android specific defines + * @file + * + * Android specific defines + * + * + * + * 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 _MC_DRV_MODULE_ANDROID_H_ +#define _MC_DRV_MODULE_ANDROID_H_ + +/* Defines needed to identify the Daemon in Android systems + * For the full list see: + * platform_system_core/include/private/android_filesystem_config.h in the + * Android source tree + */ +/* traditional unix root user */ +#define AID_ROOT 0 +/* system server */ +#define AID_SYSTEM 1000 +/* access to misc storage */ +#define AID_MISC 9998 +#define AID_NOBODY 9999 +/* first app user */ +#define AID_APP 10000 + +#endif /* _MC_DRV_MODULE_ANDROID_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/mc_drv_module_fastcalls.h b/drivers/gud/mobicore_driver/mc_drv_module_fastcalls.h new file mode 100644 index 0000000000000000000000000000000000000000..d058043249123a090cac1454ed6b395d9e657c8f --- /dev/null +++ b/drivers/gud/mobicore_driver/mc_drv_module_fastcalls.h @@ -0,0 +1,227 @@ +/** + * Header file of MobiCore Driver Kernel Module. + * + * @addtogroup MobiCore_Driver_Kernel_Module + * @{ + * Internal structures of the McDrvModule + * @file + * + * MobiCore Fast Call interface + * + * + * + * 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 _MC_DRV_MODULE_FC_H_ +#define _MC_DRV_MODULE_FC_H_ + +#include "mc_drv_module.h" + +/** + * MobiCore SMCs + */ +enum mc_smc_codes { + MC_SMC_N_YIELD = 0x3, /**< Yield to switch from NWd to SWd. */ + MC_SMC_N_SIQ = 0x4 /**< SIQ to switch from NWd to SWd. */ +}; + +/** + * MobiCore fast calls. See MCI documentation + */ +enum mc_fast_call_codes { + MC_FC_INIT = -1, + MC_FC_INFO = -2, + MC_FC_POWER = -3, + MC_FC_DUMP = -4, + MC_FC_NWD_TRACE = -31 /**< Mem trace setup fastcall */ +}; + +/** + * return code for fast calls + */ +enum mc_fast_calls_result { + MC_FC_RET_OK = 0, + MC_FC_RET_ERR_INVALID = 1, + MC_FC_RET_ERR_ALREADY_INITIALIZED = 5 +}; + + + +/*------------------------------------------------------------------------------ + structure wrappers for specific fastcalls +------------------------------------------------------------------------------*/ + +/** generic fast call parameters */ +union fc_generic { + struct { + uint32_t cmd; + uint32_t param[3]; + } as_in; + struct { + uint32_t resp; + uint32_t ret; + uint32_t param[2]; + } as_out; +}; + + +/** fast call init */ +union mc_fc_init { + union fc_generic as_generic; + struct { + uint32_t cmd; + uint32_t base; + uint32_t nq_info; + uint32_t mcp_info; + } as_in; + struct { + uint32_t resp; + uint32_t ret; + uint32_t rfu[2]; + } as_out; +}; + + +/** fast call info parameters */ +union mc_fc_info { + union fc_generic as_generic; + struct { + uint32_t cmd; + uint32_t ext_info_id; + uint32_t rfu[2]; + } as_in; + struct { + uint32_t resp; + uint32_t ret; + uint32_t state; + uint32_t ext_info; + } as_out; +}; + + +/** fast call S-Yield parameters */ +union mc_fc_s_yield { + union fc_generic as_generic; + struct { + uint32_t cmd; + uint32_t rfu[3]; + } as_in; + struct { + uint32_t resp; + uint32_t ret; + uint32_t rfu[2]; + } as_out; +}; + + +/** fast call N-SIQ parameters */ +union mc_fc_nsiq { + union fc_generic as_generic; + struct { + uint32_t cmd; + uint32_t rfu[3]; + } as_in; + struct { + uint32_t resp; + uint32_t ret; + uint32_t rfu[2]; + } as_out; +}; + + +/*----------------------------------------------------------------------------*/ +/** + * fast call to MobiCore + * + * @param fc_generic pointer to fast call data + */ +static inline void mc_fastcall( + union fc_generic *fc_generic +) +{ + MCDRV_ASSERT(fc_generic != NULL); + /* We only expect to make smc calls on CPU0 otherwise something wrong + * will happen */ + MCDRV_ASSERT(raw_smp_processor_id() == 0); + mb(); +#ifdef MC_SMC_FASTCALL + { + int ret = 0; + MCDRV_DBG("Going into SCM()"); + ret = smc_fastcall((void *)fc_generic, sizeof(*fc_generic)); + MCDRV_DBG("Coming from SCM, scm_call=%i, resp=%d/0x%x\n", + ret, + fc_generic->as_out.resp, fc_generic->as_out.resp); + } +#else + { + /* SVC expect values in r0-r3 */ + register u32 reg0 __asm__("r0") = fc_generic->as_in.cmd; + register u32 reg1 __asm__("r1") = fc_generic->as_in.param[0]; + register u32 reg2 __asm__("r2") = fc_generic->as_in.param[1]; + register u32 reg3 __asm__("r3") = fc_generic->as_in.param[2]; + + /* one of the famous preprocessor hacks to stingitize things.*/ +#define __STR2(x) #x +#define __STR(x) __STR2(x) + + /* compiler does not support certain instructions + "SMC": secure monitor call.*/ +#define ASM_ARM_SMC 0xE1600070 + /* "BPKT": debugging breakpoint. We keep this, as is comes + quite handy for debugging. */ +#define ASM_ARM_BPKT 0xE1200070 +#define ASM_THUMB_BPKT 0xBE00 + + + __asm__ volatile ( + ".word " __STR(ASM_ARM_SMC) "\n" + : "+r"(reg0), "+r"(reg1), "+r"(reg2), "+r"(reg3) + ); + + /* set response */ + fc_generic->as_out.resp = reg0; + fc_generic->as_out.ret = reg1; + fc_generic->as_out.param[0] = reg2; + fc_generic->as_out.param[1] = reg3; + } +#endif +} + + +/*----------------------------------------------------------------------------*/ +/** + * convert fast call return code to linux driver module error code + * + */ +static inline int convert_fc_ret( + uint32_t sret +) +{ + int ret = -EFAULT; + + switch (sret) { + + case MC_FC_RET_OK: + ret = 0; + break; + + case MC_FC_RET_ERR_INVALID: + ret = -EINVAL; + break; + + case MC_FC_RET_ERR_ALREADY_INITIALIZED: + ret = -EBUSY; + break; + + default: + break; + } /* end switch( sret ) */ + return ret; +} + +#endif /* _MC_DRV_MODULE_FC_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/mc_drv_module_linux_api.h b/drivers/gud/mobicore_driver/mc_drv_module_linux_api.h new file mode 100644 index 0000000000000000000000000000000000000000..b2a99f17355d1d4c44c2b8e737d79ae15cac4e8d --- /dev/null +++ b/drivers/gud/mobicore_driver/mc_drv_module_linux_api.h @@ -0,0 +1,187 @@ +/** + * Header file of MobiCore Driver Kernel Module. + * + * @addtogroup MobiCore_Driver_Kernel_Module + * @{ + * Wrapper for Linux API + * @file + * + * Some convenient wrappers for memory functions + * + * + * + * 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 _MC_DRV_MODULE_LINUX_API_H_ +#define _MC_DRV_MODULE_LINUX_API_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* make some nice types */ +#if !defined(TRUE) +#define TRUE (1 == 1) +#endif + +#if !defined(FALSE) +#define FALSE (1 != 1) +#endif + + +/* Linux GCC modifiers */ +#if !defined(__init) +#warning "missing definition: __init" +/* define a dummy */ +#define __init +#endif + + +#if !defined(__exit) +#warning "missing definition: __exit" +/* define a dummy */ +#define __exit +#endif + + +#if !defined(__must_check) +#warning "missing definition: __must_check" +/* define a dummy */ +#define __must_check +#endif + + +#if !defined(__user) +#warning "missing definition: __user" +/* define a dummy */ +#define __user +#endif + +#define INVALID_ORDER ((unsigned int)(-1)) + +/*----------------------------------------------------------------------------*/ +/* get start address of the 4 KiB page where the given addres is located in. */ +static inline void *get_page_start( + void *addr +) +{ + return (void *)(((unsigned long)(addr)) & PAGE_MASK); +} + +/*----------------------------------------------------------------------------*/ +/* get offset into the 4 KiB page where the given addres is located in. */ +static inline unsigned int get_offset_in_page( + void *addr +) +{ + return (unsigned int)(((unsigned long)(addr)) & (~PAGE_MASK)); +} + +/*----------------------------------------------------------------------------*/ +/* get number of pages for a given buffer. */ +static inline unsigned int get_nr_of_pages_for_buffer( + void *addr_start, /* may be null */ + unsigned int len +) +{ + /* calculate used number of pages. Example: + offset+size newSize+PAGE_SIZE-1 nr_of_pages + 0 4095 0 + 1 4096 1 + 4095 8190 1 + 4096 8191 1 + 4097 8192 2 */ + + return (get_offset_in_page(addr_start) + len + PAGE_SIZE-1) / PAGE_SIZE; +} + + +/*----------------------------------------------------------------------------*/ +/** + * convert a given size to page order, which is equivalent to finding log_2(x). + * The maximum for order was 5 in Linux 2.0 corresponding to 32 pages. + * Later versions allow 9 corresponding to 512 pages, which is 2 MB on + * most platforms). Anyway, the bigger order is, the more likely it is + * that the allocation will fail. + * Size 0 1 4097 8193 12289 24577 28673 40961 61441 + * Pages - 1 2 3 4 7 8 15 16 + * Order INVALID_ORDER 0 1 1 2 2 3 3 4 + * + * @param size + * @return order + */ +static inline unsigned int size_to_order( + unsigned int size +) +{ + unsigned int order = INVALID_ORDER; + + if (size != 0) { + /* ARMv5 as a CLZ instruction which count the leading zeros of + the binary representation of a value. It return a value + between 0 and 32. + Value 0 1 2 3 4 5 6 7 8 9 10 ... + CLZ 32 31 30 30 29 29 29 29 28 28 28 ... + + We have excluded Size==0 before, so this is safe. */ + order = __builtin_clz( + get_nr_of_pages_for_buffer(NULL, size)); + + /* there is a size overflow in get_nr_of_pages_for_buffer when + * the size is too large */ + if (unlikely(order > 31)) + return INVALID_ORDER; + order = 31 - order; + + /* above algorithm rounds down: clz(5)=2 instead of 3 */ + /* quick correction to fix it: */ + if (((1<> PAGE_SHIFT; +} + + +/*----------------------------------------------------------------------------*/ +/* return the address of a page frame number */ +static inline void *pfn_to_addr( + unsigned int pfn +) +{ + /* there is no real API for this */ + return (void *)(pfn << PAGE_SHIFT); +} + +#endif /* _MC_DRV_MODULE_LINUX_API_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/platforms/msm8960_surf_std/platform.h b/drivers/gud/mobicore_driver/platforms/msm8960_surf_std/platform.h new file mode 100644 index 0000000000000000000000000000000000000000..7034cb058d426f9f14f26194132ba49c9ba6972a --- /dev/null +++ b/drivers/gud/mobicore_driver/platforms/msm8960_surf_std/platform.h @@ -0,0 +1,50 @@ +/** + * Header file of MobiCore Driver Kernel Module Platform + * specific structures + * + * @addtogroup MobiCore_Driver_Kernel_Module + * @{ + * Internal structures of the McDrvModule + * @file + * + * Header file the MobiCore Driver Kernel Module, + * its internal structures and defines. + * + * + * + * 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 _MC_DRV_PLATFORM_H_ +#define _MC_DRV_PLATFORM_H_ + +/** MobiCore Interrupt for Qualcomm */ +#define MC_INTR_SSIQ 218 + +/** Use SMC for fastcalls */ +#define MC_SMC_FASTCALL + + +/*--------------- Implementation -------------- */ +#include +/* from following file */ +#define SCM_SVC_MOBICORE 250 +#define SCM_CMD_MOBICORE 1 + +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); + +static inline int smc_fastcall(void *fc_generic, size_t size) +{ + return scm_call(SCM_SVC_MOBICORE, SCM_CMD_MOBICORE, + fc_generic, size, + fc_generic, size); +} + +/** Enable mobicore mem traces */ +#define MC_MEM_TRACES + +#endif /* _MC_DRV_PLATFORM_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/public/mc_drv_module_api.h b/drivers/gud/mobicore_driver/public/mc_drv_module_api.h new file mode 100644 index 0000000000000000000000000000000000000000..59366f35f2a6b85eec1af22edbe232d74257fda3 --- /dev/null +++ b/drivers/gud/mobicore_driver/public/mc_drv_module_api.h @@ -0,0 +1,311 @@ +/** @addtogroup MCD_MCDIMPL_KMOD_API Mobicore Driver Module API + * @ingroup MCD_MCDIMPL_KMOD + * @{ + * Interface to Mobicore Driver Kernel Module. + * @file + * + *

Introduction

+ * The MobiCore Driver Kernel Module is a Linux device driver, which represents + * the command proxy on the lowest layer to the secure world (Swd). Additional + * services like memory allocation via mmap and generation of a L2 tables for + * given virtual memory are also supported. IRQ functionallity receives + * information from the SWd in the non secure world (NWd). + * As customary the driver is handled as linux device driver with "open", + * "close" and "ioctl" commands. Access to the driver is possible after the + * device "/dev/mobicore" has been opened. + * The MobiCore Driver Kernel Module must be installed via + * "insmod mcDrvModule.ko". + * + * + *

Version history

+ * + * + * + * + *
DateVersionChanges
2010-05-250.1Initial Release
+ * + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _MC_DRV_MODULEAPI_H_ +#define _MC_DRV_MODULEAPI_H_ + +#include "version.h" + +#define MC_DRV_MOD_DEVNODE "mobicore" +#define MC_DRV_MOD_DEVNODE_FULLPATH "/dev/" MC_DRV_MOD_DEVNODE + +/** + * Data exchange structure of the MC_DRV_MODULE_INIT ioctl command. + * INIT request data to SWD + */ +union mc_ioctl_init_params { + struct { + /** base address of mci buffer 4KB align */ + uint32_t base; + /** notification buffer start/length [16:16] [start, length] */ + uint32_t nq_offset; + /** length of notification queue */ + uint32_t nq_length; + /** mcp buffer start/length [16:16] [start, length] */ + uint32_t mcp_offset; + /** length of mcp buffer */ + uint32_t mcp_length; + } in; + struct { + /* nothing */ + } out; +}; + + +/** + * Data exchange structure of the MC_DRV_MODULE_INFO ioctl command. + * INFO request data to the SWD + */ +union mc_ioctl_info_params { + struct { + uint32_t ext_info_id; /**< extended info ID */ + } in; + struct { + uint32_t state; /**< state */ + uint32_t ext_info; /**< extended info */ + } out; +}; + +/** + * Mmap allocates and maps contiguous memory into a process. + * We use the third parameter, void *offset, to distinguish between some cases + * offset = MC_DRV_KMOD_MMAP_WSM usual operation, pages are registered in + device structure and freed later. + * offset = MC_DRV_KMOD_MMAP_MCI get Instance of MCI, allocates or mmaps + the MCI to daemon + * offset = MC_DRV_KMOD_MMAP_PERSISTENTWSM special operation, without + registration of pages + * + * In mmap(), the offset specifies which of several device I/O pages is + * requested. Linux only transfers the page number, i.e. the upper 20 bits to + * kernel module. Therefore we define our special offsets as multiples of page + * size. + */ +enum mc_mmap_memtype { + MC_DRV_KMOD_MMAP_WSM = 0, + MC_DRV_KMOD_MMAP_MCI = 4096, + MC_DRV_KMOD_MMAP_PERSISTENTWSM = 8192 +}; + +struct mc_mmap_resp { + uint32_t handle; /**< WSN handle */ + uint32_t phys_addr; /**< physical address of WSM (or NULL) */ + bool is_reused; /**< if WSM memory was reused, or new allocated */ +}; + +/** + * Data exchange structure of the MC_DRV_KMOD_IOCTL_FREE ioctl command. + */ +union mc_ioctl_free_params { + struct { + uint32_t handle; /**< driver handle */ + uint32_t pid; /**< process id */ + } in; + struct { + /* nothing */ + } out; +}; + + +/** + * Data exchange structure of the MC_DRV_KMOD_IOCTL_APP_REGISTER_WSM_L2 command. + * + * Allocates a physical L2 table and maps the buffer into this page. + * Returns the physical address of the L2 table. + * The page alignment will be created and the appropriated pSize and pOffsetL2 + * will be modified to the used values. + */ +union mc_ioctl_app_reg_wsm_l2_params { + struct { + uint32_t buffer; /**< base address of the virtual address */ + uint32_t len; /**< size of the virtual address space */ + uint32_t pid; /**< process id */ + } in; + struct { + uint32_t handle; /**< driver handle for locked memory */ + uint32_t phys_wsm_l2_table; /* physical address of the L2 table */ + } out; +}; + + +/** + * Data exchange structure of the MC_DRV_KMOD_IOCTL_APP_UNREGISTER_WSM_L2 + * command. + */ +struct mc_ioctl_app_unreg_wsm_l2_params { + struct { + uint32_t handle; /**< driver handle for locked memory */ + uint32_t pid; /**< process id */ + } in; + struct { + /* nothing */ + } out; +}; + + +/** + * Data exchange structure of the MC_DRV_KMOD_IOCTL_DAEMON_LOCK_WSM_L2 command. + */ +struct mc_ioctl_daemon_lock_wsm_l2_params { + struct { + uint32_t handle; /**< driver handle for locked memory */ + } in; + struct { + uint32_t phys_wsm_l2_table; + } out; +}; + + +/** + * Data exchange structure of the MC_DRV_KMOD_IOCTL_DAEMON_UNLOCK_WSM_L2 + * command. + */ +struct mc_ioctl_daemon_unlock_wsm_l2_params { + struct { + uint32_t handle; /**< driver handle for locked memory */ + } in; + struct { + /* nothing */ + } out; +}; + +/** + * Data exchange structure of the MC_DRV_MODULE_FC_EXECUTE ioctl command. + */ +union mc_ioctl_fc_execute_params { + struct { + /**< base address of mobicore binary */ + uint32_t phys_start_addr; + /**< length of DDR area */ + uint32_t length; + } in; + struct { + /* nothing */ + } out; +}; + +/** + * Data exchange structure of the MC_DRV_MODULE_GET_VERSION ioctl command. + */ +struct mc_ioctl_get_version_params { + struct { + uint32_t kernel_module_version; + } out; +}; + +/* @defgroup Mobicore_Driver_Kernel_Module_Interface IOCTL */ + + + + +/* TODO: use IOCTL macros like _IOWR. See Documentation/ioctl/ioctl-number.txt, + Documentation/ioctl/ioctl-decoding.txt */ +/** + * defines for the ioctl mobicore driver module function call from user space. + */ +enum mc_kmod_ioctl { + + /* + * get detailed MobiCore Status + */ + MC_DRV_KMOD_IOCTL_DUMP_STATUS = 200, + + /* + * initialize MobiCore + */ + MC_DRV_KMOD_IOCTL_FC_INIT = 201, + + /* + * get MobiCore status + */ + MC_DRV_KMOD_IOCTL_FC_INFO = 202, + + /** + * ioctl parameter to send the YIELD command to the SWD. + * Only possible in Privileged Mode. + * ioctl(fd, MC_DRV_MODULE_YIELD) + */ + MC_DRV_KMOD_IOCTL_FC_YIELD = 203, + /** + * ioctl parameter to send the NSIQ signal to the SWD. + * Only possible in Privileged Mode + * ioctl(fd, MC_DRV_MODULE_NSIQ) + */ + MC_DRV_KMOD_IOCTL_FC_NSIQ = 204, + /** + * ioctl parameter to tzbsp to start Mobicore binary from DDR. + * Only possible in Privileged Mode + * ioctl(fd, MC_DRV_KMOD_IOCTL_FC_EXECUTE) + */ + MC_DRV_KMOD_IOCTL_FC_EXECUTE = 205, + + /** + * Free's memory which is formerly allocated by the driver's mmap + * command. The parameter must be this mmaped address. + * The internal instance data regarding to this address are deleted as + * well as each according memory page and its appropriated reserved bit + * is cleared (ClearPageReserved). + * Usage: ioctl(fd, MC_DRV_MODULE_FREE, &address) with address beeing of + * type long address + */ + MC_DRV_KMOD_IOCTL_FREE = 218, + + /** + * Creates a L2 Table of the given base address and the size of the + * data. + * Parameter: mc_ioctl_app_reg_wsm_l2_params + */ + MC_DRV_KMOD_IOCTL_APP_REGISTER_WSM_L2 = 220, + + /** + * Frees the L2 table created by a MC_DRV_KMOD_IOCTL_APP_REGISTER_WSM_L2 + * ioctl. + * Parameter: mc_ioctl_app_unreg_wsm_l2_params + */ + MC_DRV_KMOD_IOCTL_APP_UNREGISTER_WSM_L2 = 221, + + + /* TODO: comment this. */ + MC_DRV_KMOD_IOCTL_DAEMON_LOCK_WSM_L2 = 222, + MC_DRV_KMOD_IOCTL_DAEMON_UNLOCK_WSM_L2 = 223, + + /** + * Return kernel driver version. + * Parameter: mc_ioctl_get_version_params + */ + MC_DRV_KMOD_IOCTL_GET_VERSION = 224, +}; + + +#endif /* _MC_DRV_MODULEAPI_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/public/mc_kernel_api.h b/drivers/gud/mobicore_driver/public/mc_kernel_api.h new file mode 100644 index 0000000000000000000000000000000000000000..fdfc61876aad7354c4eb06bef0e6ce9943e62be9 --- /dev/null +++ b/drivers/gud/mobicore_driver/public/mc_kernel_api.h @@ -0,0 +1,100 @@ +/** @addtogroup MCD_MCDIMPL_KMOD_KAPI Mobicore Driver Module API inside Kernel. + * @ingroup MCD_MCDIMPL_KMOD + * @{ + * Interface to Mobicore Driver Kernel Module inside Kernel. + * @file + * + * Interface to be used by module MobiCoreKernelAPI. + * + * + * + * 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 _MOBICORE_KERNELMODULE_API_H_ +#define _MOBICORE_KERNELMODULE_API_H_ + +struct mc_instance; + +/** + * Initialize a new mobicore API instance object + * + * @return Instance or NULL if no allocation was possible. + */ +struct mc_instance *mobicore_open( + void +); + +/** + * Release a mobicore instance object and all objects related to it + * @param instance instance + * @return 0 if Ok or -E ERROR + */ +int mobicore_release( + struct mc_instance *instance +); + +/** + * Free a WSM buffer allocated with mobicore_allocate_wsm + * @param instance + * @param handle handle of the buffer + * + * @return 0 if no error + * + */ +int mobicore_allocate_wsm( + struct mc_instance *instance, + unsigned long requested_size, + uint32_t *handle, + void **kernel_virt_addr, + void **phys_addr +); + +/** + * Free a WSM buffer allocated with mobicore_allocate_wsm + * @param instance + * @param handle handle of the buffer + * + * @return 0 if no error + * + */ +int mobicore_free( + struct mc_instance *instance, + uint32_t handle +); + +/** + * Map a virtual memory buffer structure to Mobicore + * @param instance + * @param addr address of the buffer(NB it must be kernel virtual!) + * @param len buffer length + * @param handle pointer to handle + * @param phys_wsm_l2_table pointer to physical L2 table(?) + * + * @return 0 if no error + * + */ +int mobicore_map_vmem( + struct mc_instance *instance, + void *addr, + uint32_t len, + uint32_t *handle, + void **phys_wsm_l2_table +); + +/** + * Unmap a virtual memory buffer from mobicore + * @param instance + * @param handle + * + * @return 0 if no error + * + */ +int mobicore_unmap_vmem( + struct mc_instance *instance, + uint32_t handle +); +#endif /* _MOBICORE_KERNELMODULE_API_H_ */ +/** @} */ diff --git a/drivers/gud/mobicore_driver/public/version.h b/drivers/gud/mobicore_driver/public/version.h new file mode 100644 index 0000000000000000000000000000000000000000..9b2dbcadb9d64647db4c982d2e40ada5cac52863 --- /dev/null +++ b/drivers/gud/mobicore_driver/public/version.h @@ -0,0 +1,36 @@ +/** @addtogroup MCD_MCDIMPL_KMOD + * @{ + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _MC_DRV_VERSION_H_ +#define _MC_DRV_VERSION_H_ + +#define MCDRVMODULEAPI_VERSION_MAJOR 0 +#define MCDRVMODULEAPI_VERSION_MINOR 1 + +#endif /* _MC_DRV_VERSION_H_ */ diff --git a/drivers/gud/mobicore_kernelapi/clientlib.c b/drivers/gud/mobicore_kernelapi/clientlib.c new file mode 100644 index 0000000000000000000000000000000000000000..13826f281b225c1a369c6c8db6ee5a0692a03466 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/clientlib.c @@ -0,0 +1,1093 @@ +/** + * MobiCore KernelApi module + * + * + * + * 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 "public/mobicore_driver_api.h" +#include "public/mobicore_driver_cmd.h" +#include "device.h" +#include "session.h" + +/* device list */ +LIST_HEAD(devices); + +/*----------------------------------------------------------------------------*/ +static struct mcore_device_t *resolve_device_id( + uint32_t device_id +) { + struct mcore_device_t *tmp; + struct list_head *pos; + + /* Get mcore_device_t for device_id */ + list_for_each(pos, &devices) { + tmp = list_entry(pos, struct mcore_device_t, list); + if (tmp->device_id == device_id) + return tmp; + } + return NULL; +} + + +/*----------------------------------------------------------------------------*/ +static void add_device( + struct mcore_device_t *device +) { + list_add_tail(&(device->list), &devices); +} + + +/*----------------------------------------------------------------------------*/ +static bool remove_device( + uint32_t device_id +) { + struct mcore_device_t *tmp; + struct list_head *pos, *q; + + list_for_each_safe(pos, q, &devices) { + tmp = list_entry(pos, struct mcore_device_t, list); + if (tmp->device_id == device_id) { + list_del(pos); + mcore_device_cleanup(tmp); + return true; + } + } + return false; +} + + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_open_device( + uint32_t device_id +) { + enum mc_result mc_result = MC_DRV_OK; + struct connection *dev_con = NULL; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + struct mcore_device_t *device = resolve_device_id(device_id); + if (device != NULL) { + MCDRV_DBG_ERROR("Device %d already opened", device_id); + mc_result = MC_DRV_ERR_INVALID_OPERATION; + break; + } + + /* Open new connection to device */ + dev_con = connection_new(); + if (!connection_connect(dev_con, MC_DAEMON_PID)) { + MCDRV_DBG_ERROR( + "Could not setup netlink connection to PID %u", + MC_DAEMON_PID); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + /* Forward device open to the daemon and read result */ + struct mc_drv_cmd_open_device_t mc_drv_cmd_open_device = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_OPEN_DEVICE + }, + /* .payload = */ { + /* .device_id = */ device_id + } + }; + + int len = connection_write_data( + dev_con, + &mc_drv_cmd_open_device, + sizeof(struct mc_drv_cmd_open_device_t)); + if (len < 0) { + MCDRV_DBG_ERROR("CMD_OPEN_DEVICE writeCmd failed " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + struct mc_drv_response_header_t rsp_header; + len = connection_read_datablock( + dev_con, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_OPEN_DEVICE readRsp failed " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_OPEN_DEVICE failed, respId=%d", + rsp_header.response_id); + switch (rsp_header.response_id) { + case MC_DRV_RSP_PAYLOAD_LENGTH_ERROR: + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + case MC_DRV_INVALID_DEVICE_NAME: + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + case MC_DRV_RSP_DEVICE_ALREADY_OPENED: + default: + mc_result = MC_DRV_ERR_INVALID_OPERATION; + break; + } + break; + } + + /* there is no payload to read */ + + device = mcore_device_create(device_id, dev_con); + if (!mcore_device_open(device, MC_DRV_MOD_DEVNODE_FULLPATH)) { + mcore_device_cleanup(device); + MCDRV_DBG_ERROR("could not open device file: %s", + MC_DRV_MOD_DEVNODE_FULLPATH); + mc_result = MC_DRV_ERR_INVALID_DEVICE_FILE; + break; + } + + add_device(device); + + } while (false); + + if (mc_result != MC_DRV_OK) + connection_cleanup(dev_con); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_open_device); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_close_device( + uint32_t device_id +) { + enum mc_result mc_result = MC_DRV_OK; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + do { + struct mcore_device_t *device = resolve_device_id(device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + struct connection *dev_con = device->connection; + + /* Return if not all sessions have been closed */ + if (mcore_device_has_sessions(device)) { + MCDRV_DBG_ERROR("cannot close with sessions pending"); + mc_result = MC_DRV_ERR_SESSION_PENDING; + break; + } + + struct mc_drv_cmd_close_device_t mc_drv_cmd_close_device = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_CLOSE_DEVICE + } + }; + int len = connection_write_data( + dev_con, + &mc_drv_cmd_close_device, + sizeof(struct mc_drv_cmd_close_device_t)); + /* ignore error, but log details */ + if (len < 0) { + MCDRV_DBG_ERROR("CMD_CLOSE_DEVICE writeCmd failed " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + } + + struct mc_drv_response_header_t rsp_header; + len = connection_read_datablock( + dev_con, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_CLOSE_DEVICE readResp failed " + " ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_CLOSE_DEVICE failed, respId=%d", + rsp_header.response_id); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + remove_device(device_id); + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_close_device); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_open_session( + struct mc_session_handle *session, + const struct mc_uuid_t *uuid, + uint8_t *tci, + uint32_t len +) { + enum mc_result mc_result = MC_DRV_OK; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + if (session == NULL) { + MCDRV_DBG_ERROR("Session is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (uuid == NULL) { + MCDRV_DBG_ERROR("UUID is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (tci == NULL) { + MCDRV_DBG_ERROR("TCI is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (len > MC_MAX_TCI_LEN) { + MCDRV_DBG_ERROR("TCI length is longer than %d", + MC_MAX_TCI_LEN); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + /* Get the device associated with the given session */ + struct mcore_device_t *device = + resolve_device_id(session->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + struct connection *dev_con = device->connection; + + /* Get the physical address of the given TCI */ + struct wsm *wsm = + mcore_device_find_contiguous_wsm(device, tci); + if (wsm == NULL) { + MCDRV_DBG_ERROR("Could not resolve TCI phy address "); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + if (wsm->len < len) { + MCDRV_DBG_ERROR("length is more than allocated TCI"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + /* Prepare open session command */ + struct mc_drv_cmd_open_session_t cmdOpenSession = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_OPEN_SESSION + }, + /* .payload = */ { + /* .device_id = */ session->device_id, + /* .uuid = */ *uuid, + /* .tci = */ (uint32_t)wsm->phys_addr, + /* .len = */ len + } + }; + + /* Transmit command data */ + + int len = connection_write_data( + dev_con, + &cmdOpenSession, + sizeof(cmdOpenSession)); + if (len != sizeof(cmdOpenSession)) { + MCDRV_DBG_ERROR("CMD_OPEN_SESSION writeData failed " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + /* Read command response */ + + /* read header first */ + struct mc_drv_response_header_t rsp_header; + len = connection_read_datablock( + dev_con, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_OPEN_SESSION readResp failed " + " ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_OPEN_SESSION failed, respId=%d", + rsp_header.response_id); + switch (rsp_header.response_id) { + case MC_DRV_RSP_TRUSTLET_NOT_FOUND: + mc_result = MC_DRV_ERR_INVALID_DEVICE_FILE; + break; + case MC_DRV_RSP_PAYLOAD_LENGTH_ERROR: + case MC_DRV_RSP_DEVICE_NOT_OPENED: + case MC_DRV_RSP_FAILED: + default: + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + break; + } + + /* read payload */ + struct mc_drv_rsp_open_session_payload_t + rsp_open_session_payload; + len = connection_read_datablock( + dev_con, + &rsp_open_session_payload, + sizeof(rsp_open_session_payload)); + if (len != sizeof(rsp_open_session_payload)) { + MCDRV_DBG_ERROR("CMD_OPEN_SESSION readPayload failed " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + /* Register session with handle */ + session->session_id = rsp_open_session_payload.session_id; + + /* Set up second channel for notifications */ + struct connection *session_connection = connection_new(); + /*TODO: no real need to connect here? */ + if (!connection_connect(session_connection, MC_DAEMON_PID)) { + MCDRV_DBG_ERROR( + "Could not setup netlink connection to PID %u", + MC_DAEMON_PID); + connection_cleanup(session_connection); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + /*TODO CONTINOUE HERE !!!! FIX RW RETURN HANDLING!!!! */ + + /* Write command to use channel for notifications */ + struct mc_drv_cmd_nqconnect_t cmd_nqconnect = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_NQ_CONNECT + }, + /* .payload = */ { + /* .device_id = */ session->device_id, + /* .session_id = */ session->session_id, + /* .device_session_id = */ + rsp_open_session_payload.device_session_id, + /* .session_magic = */ + rsp_open_session_payload.session_magic + } + }; + connection_write_data(session_connection, + &cmd_nqconnect, + sizeof(cmd_nqconnect)); + + /* Read command response, header first */ + len = connection_read_datablock( + session_connection, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_NQ_CONNECT readRsp failed " + "ret=%d", len); + connection_cleanup(session_connection); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_NQ_CONNECT failed, respId=%d", + rsp_header.response_id); + connection_cleanup(session_connection); + mc_result = MC_DRV_ERR_NQ_FAILED; + break; + } + + /* there is no payload. */ + + /* Session established, new session object must be created */ + mcore_device_create_new_session( + device, + session->session_id, + session_connection); + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_open_session); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_close_session( + struct mc_session_handle *session +) { + enum mc_result mc_result = MC_DRV_OK; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + if (session == NULL) { + MCDRV_DBG_ERROR("Session is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + struct mcore_device_t *device = + resolve_device_id(session->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + struct connection *dev_con = device->connection; + + struct session *nq_session = + mcore_device_resolve_session_id(device, session->session_id); + if (nq_session == NULL) { + MCDRV_DBG_ERROR("Session not found"); + mc_result = MC_DRV_ERR_UNKNOWN_SESSION; + break; + } + + /* Write close session command */ + struct mc_drv_cmd_close_session_t cmd_close_session = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_CLOSE_SESSION + }, + /* .payload = */ { + /* .session_id = */ session->session_id, + } + }; + connection_write_data( + dev_con, + &cmd_close_session, + sizeof(cmd_close_session)); + + /* Read command response */ + struct mc_drv_response_header_t rsp_header; + int len = connection_read_datablock( + dev_con, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_CLOSE_SESSION readRsp failed " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_CLOSE_SESSION failed, respId=%d", + rsp_header.response_id); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + + mcore_device_remove_session(device, session->session_id); + mc_result = MC_DRV_OK; + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_close_session); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_notify( + struct mc_session_handle *session +) { + enum mc_result mc_result = MC_DRV_OK; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + do { + if (session == NULL) { + MCDRV_DBG_ERROR("Session is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + struct mcore_device_t *device = + resolve_device_id(session->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + struct connection *dev_con = device->connection; + + struct session *nqsession = + mcore_device_resolve_session_id(device, session->session_id); + if (nqsession == NULL) { + MCDRV_DBG_ERROR("Session not found"); + mc_result = MC_DRV_ERR_UNKNOWN_SESSION; + break; + } + + struct mc_drv_cmd_notify_t cmd_notify = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_NOTIFY + }, + /* .payload = */ { + /* .session_id = */ session->session_id, + } + }; + + connection_write_data( + dev_con, + &cmd_notify, + sizeof(cmd_notify)); + + /* Daemon will not return a response */ + + } while (false); + + return mc_result; +} +EXPORT_SYMBOL(mc_notify); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_wait_notification( + struct mc_session_handle *session, + int32_t timeout +) { + enum mc_result mc_result = MC_DRV_OK; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + do { + if (session == NULL) { + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + struct mcore_device_t *device = + resolve_device_id(session->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + + struct session *nq_session = + mcore_device_resolve_session_id(device, session->session_id); + if (nq_session == NULL) { + MCDRV_DBG_ERROR("Session not found"); + mc_result = MC_DRV_ERR_UNKNOWN_SESSION; + break; + } + + struct connection *nqconnection = + nq_session->notification_connection; + uint32_t count = 0; + + /* Read notification queue till it's empty */ + for (;;) { + struct notification notification; + ssize_t num_read = connection_read_data( + nqconnection, + ¬ification, + sizeof(notification), + timeout); + /* Exit on timeout in first run. Later runs have + * timeout set to 0. + * -2 means, there is no more data. */ + if (count == 0 && num_read == -2) { + MCDRV_DBG_ERROR("read timeout"); + mc_result = MC_DRV_ERR_TIMEOUT; + break; + } + /* After first notification the queue will be + * drained, Thus we set no timeout for the + * following reads */ + timeout = 0; + + if (num_read != sizeof(struct notification)) { + if (count == 0) { + /* failure in first read, notify it */ + mc_result = MC_DRV_ERR_NOTIFICATION; + MCDRV_DBG_ERROR( + "read notification failed, " + "%i bytes received", (int)num_read); + break; + } else { + /* Read of the n-th notification + failed/timeout. We don't tell the + caller, as we got valid notifications + before. */ + mc_result = MC_DRV_OK; + break; + } + } + + count++; + MCDRV_DBG_VERBOSE("readNq count=%d, SessionID=%d, " + "Payload=%d", count, + notification.session_id, notification.payload); + + if (notification.payload != 0) { + /* Session end point died -> store exit code */ + session_set_error_info(nq_session, + notification.payload); + + mc_result = MC_DRV_INFO_NOTIFICATION; + break; + } + } /* for(;;) */ + + } while (false); + + return mc_result; +} +EXPORT_SYMBOL(mc_wait_notification); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_malloc_wsm( + uint32_t device_id, + uint32_t align, + uint32_t len, + uint8_t **wsm, + uint32_t wsm_flags +) { + enum mc_result mc_result = MC_DRV_ERR_UNKNOWN; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + struct mcore_device_t *device = resolve_device_id(device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + if (wsm == NULL) { + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + struct wsm *wsm_stack = + mcore_device_allocate_contiguous_wsm(device, len); + if (wsm_stack == NULL) { + MCDRV_DBG_ERROR("Allocation of WSM failed"); + mc_result = MC_DRV_ERR_NO_FREE_MEMORY; + break; + } + + *wsm = (uint8_t *)wsm_stack->virt_addr; + mc_result = MC_DRV_OK; + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_malloc_wsm); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_free_wsm( + uint32_t device_id, + uint8_t *wsm +) { + enum mc_result mc_result = MC_DRV_ERR_UNKNOWN; + struct mcore_device_t *device; + + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + + /* Get the device associated wit the given session */ + device = resolve_device_id(device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + + /* find WSM object */ + struct wsm *wsm_stack = + mcore_device_find_contiguous_wsm(device, wsm); + if (wsm_stack == NULL) { + MCDRV_DBG_ERROR("unknown address"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + /* Free the given virtual address */ + if (!mcore_device_free_contiguous_wsm(device, wsm_stack)) { + MCDRV_DBG_ERROR("Free of virtual address failed"); + mc_result = MC_DRV_ERR_FREE_MEMORY_FAILED; + break; + } + mc_result = MC_DRV_OK; + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_free_wsm); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_map( + struct mc_session_handle *session_handle, + void *buf, + uint32_t buf_len, + struct mc_bulk_map *map_info +) { + enum mc_result mc_result = MC_DRV_ERR_UNKNOWN; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + if (session_handle == NULL) { + MCDRV_DBG_ERROR("session_handle is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (map_info == NULL) { + MCDRV_DBG_ERROR("map_info is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (buf == NULL) { + MCDRV_DBG_ERROR("buf is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + /* Determine device the session belongs to */ + struct mcore_device_t *device = resolve_device_id( + session_handle->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + struct connection *dev_con = device->connection; + + /* Get session */ + struct session *session = + mcore_device_resolve_session_id(device, + session_handle->session_id); + if (session == NULL) { + MCDRV_DBG_ERROR("Session not found"); + mc_result = MC_DRV_ERR_UNKNOWN_SESSION; + break; + } + + /* Register mapped bulk buffer to Kernel Module and keep mapped + bulk buffer in mind */ + struct bulk_buffer_descriptor *bulk_buf = session_add_bulk_buf( + session, buf, buf_len); + if (bulk_buf == NULL) { + MCDRV_DBG_ERROR("Error mapping bulk buffer"); + mc_result = MC_DRV_ERR_BULK_MAPPING; + break; + } + + /* Prepare map command */ + struct mc_drv_cmd_map_bulk_mem_t mc_drv_cmd_map_bulk_mem = { + /* C++ does not support C99 designated initializers */ + /* .header = */ { + /* .command_id = */ MC_DRV_CMD_MAP_BULK_BUF + }, + /* .payload = */ { + /* .session_id = */ session->session_id, + /* .phys_addr_l2; = */ + (uint32_t)bulk_buf->phys_addr_wsm_l2, + /* .offset_payload = */ + (uint32_t)(bulk_buf->virt_addr) & 0xFFF, + /* .len_bulk_mem = */ bulk_buf->len + } + }; + + /* Transmit map command to MobiCore device */ + connection_write_data( + dev_con, + &mc_drv_cmd_map_bulk_mem, + sizeof(mc_drv_cmd_map_bulk_mem)); + + /* Read command response */ + struct mc_drv_response_header_t rsp_header; + int len = connection_read_datablock( + dev_con, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_MAP_BULK_BUF readRsp failed, " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_MAP_BULK_BUF failed, respId=%d", + rsp_header.response_id); + /* REV We ignore Daemon Error code because client cannot + handle it anyhow. */ + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + + /* Unregister mapped bulk buffer from Kernel Module and + remove mapped bulk buffer from session maintenance */ + if (!session_remove_bulk_buf(session, buf)) { + /* Removing of bulk buffer not possible */ + MCDRV_DBG_ERROR("Unregistering of bulk memory" + "from Kernel Module failed"); + } + break; + } + + struct mc_drv_rsp_map_bulk_mem_payload_t + rsp_map_bulk_mem_payload; + connection_read_datablock( + dev_con, + &rsp_map_bulk_mem_payload, + sizeof(rsp_map_bulk_mem_payload)); + + /* Set mapping info for Trustlet */ + map_info->secure_virt_addr = + (void *)(rsp_map_bulk_mem_payload.secure_virtual_adr); + map_info->secure_virt_len = buf_len; + mc_result = MC_DRV_OK; + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_map); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_unmap( + struct mc_session_handle *session_handle, + void *buf, + struct mc_bulk_map *map_info +) { + enum mc_result mc_result = MC_DRV_ERR_UNKNOWN; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + /* Enter critical section */ + + do { + if (session_handle == NULL) { + MCDRV_DBG_ERROR("session_handle is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (map_info == NULL) { + MCDRV_DBG_ERROR("map_info is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + if (buf == NULL) { + MCDRV_DBG_ERROR("buf is null"); + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + /* Determine device the session belongs to */ + struct mcore_device_t *device = + resolve_device_id(session_handle->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + struct connection *dev_con = device->connection; + + /* Get session */ + struct session *session = + mcore_device_resolve_session_id(device, + session_handle->session_id); + if (session == NULL) { + MCDRV_DBG_ERROR("Session not found"); + mc_result = MC_DRV_ERR_UNKNOWN_SESSION; + break; + } + + /* Prepare unmap command */ + struct mc_drv_cmd_unmap_bulk_mem_t cmd_unmap_bulk_mem = { + /* .header = */ { + /* .command_id = */ + MC_DRV_CMD_UNMAP_BULK_BUF + }, + /* .payload = */ { + /* .session_id = */ session->session_id, + /* .secure_virtual_adr = */ + (uint32_t)(map_info->secure_virt_addr), + /* .len_bulk_mem = + map_info->secure_virt_len*/ + } + }; + + connection_write_data( + dev_con, + &cmd_unmap_bulk_mem, + sizeof(cmd_unmap_bulk_mem)); + + /* Read command response */ + struct mc_drv_response_header_t rsp_header; + int len = connection_read_datablock( + dev_con, + &rsp_header, + sizeof(rsp_header)); + if (len != sizeof(rsp_header)) { + MCDRV_DBG_ERROR("CMD_UNMAP_BULK_BUF readRsp failed, " + "ret=%d", len); + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + if (rsp_header.response_id != MC_DRV_RSP_OK) { + MCDRV_DBG_ERROR("CMD_UNMAP_BULK_BUF failed, respId=%d", + rsp_header.response_id); + /* REV We ignore Daemon Error code because client + cannot handle it anyhow. */ + mc_result = MC_DRV_ERR_DAEMON_UNREACHABLE; + break; + } + + struct mc_drv_rsp_unmap_bulk_mem_payload_t + rsp_unmap_bulk_mem_payload; + connection_read_datablock( + dev_con, + &rsp_unmap_bulk_mem_payload, + sizeof(rsp_unmap_bulk_mem_payload)); + + /* REV axh: what about check the payload? */ + + /* Unregister mapped bulk buffer from Kernel Module and + * remove mapped bulk buffer from session maintenance */ + if (!session_remove_bulk_buf(session, buf)) { + /* Removing of bulk buffer not possible */ + MCDRV_DBG_ERROR("Unregistering of bulk memory from " + "Kernel Module failed"); + mc_result = MC_DRV_ERR_BULK_UNMAPPING; + break; + } + + mc_result = MC_DRV_OK; + + } while (false); + + /* Exit critical section */ + + return mc_result; +} +EXPORT_SYMBOL(mc_unmap); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_get_session_error_code( + struct mc_session_handle *session, + int32_t *last_error +) { + enum mc_result mc_result = MC_DRV_OK; + + MCDRV_DBG_VERBOSE("===%s()===", __func__); + + do { + if (session == NULL || last_error == NULL) { + mc_result = MC_DRV_ERR_INVALID_PARAMETER; + break; + } + + /* Get device */ + struct mcore_device_t *device = + resolve_device_id(session->device_id); + if (device == NULL) { + MCDRV_DBG_ERROR("Device not found"); + mc_result = MC_DRV_ERR_UNKNOWN_DEVICE; + break; + } + + /* Get session */ + struct session *nqsession = + mcore_device_resolve_session_id(device, session->session_id); + if (nqsession == NULL) { + MCDRV_DBG_ERROR("Session not found"); + mc_result = MC_DRV_ERR_UNKNOWN_SESSION; + break; + } + + /* get session error code from session */ + *last_error = session_get_last_err(nqsession); + + } while (false); + + return mc_result; +} +EXPORT_SYMBOL(mc_get_session_error_code); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_driver_ctrl( + enum mc_driver_ctrl param, + uint8_t *data, + uint32_t len +) { + MCDRV_DBG_WARN("not implemented"); + return MC_DRV_ERR_NOT_IMPLEMENTED; +} +EXPORT_SYMBOL(mc_driver_ctrl); + +/*----------------------------------------------------------------------------*/ +enum mc_result mc_manage( + uint32_t device_id, + uint8_t *data, + uint32_t len +) { + MCDRV_DBG_WARN("not implemented"); + return MC_DRV_ERR_NOT_IMPLEMENTED; +} +EXPORT_SYMBOL(mc_manage); + diff --git a/drivers/gud/mobicore_kernelapi/common.h b/drivers/gud/mobicore_kernelapi/common.h new file mode 100644 index 0000000000000000000000000000000000000000..2a7347487040e2b837fb62312e847b080fb25546 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/common.h @@ -0,0 +1,97 @@ +/** + * + * Common data types + * + * + * + * 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 COMMON_H +#define COMMON_H + +#include "connection.h" +#include "mcinq.h" + +void mcapi_insert_connection( + struct connection *connection +); + +void mcapi_remove_connection( + uint32_t seq +); + +unsigned int mcapi_unique_id( + void +); + + +#define MC_DAEMON_PID 0xFFFFFFFF +#define MC_DRV_MOD_DEVNODE_FULLPATH "/dev/mobicore" + +/* dummy function helper macro. */ +#define DUMMY_FUNCTION() do {} while (0) + +#define MCDRV_ERROR(txt, ...) \ + printk(KERN_ERR "mcKernelApi %s() ### ERROR: " txt, \ + __func__, \ + ##__VA_ARGS__) + +#if defined(DEBUG) + +/* #define DEBUG_VERBOSE */ +#if defined(DEBUG_VERBOSE) +#define MCDRV_DBG_VERBOSE MCDRV_DBG +#else +#define MCDRV_DBG_VERBOSE(...) DUMMY_FUNCTION() +#endif + +#define MCDRV_DBG(txt, ...) \ + printk(KERN_INFO "mcKernelApi %s(): " txt, \ + __func__, \ + ##__VA_ARGS__) + +#define MCDRV_DBG_WARN(txt, ...) \ + printk(KERN_WARNING "mcKernelApi %s() WARNING: " txt, \ + __func__, \ + ##__VA_ARGS__) + +#define MCDRV_DBG_ERROR(txt, ...) \ + printk(KERN_ERR "mcKernelApi %s() ### ERROR: " txt, \ + __func__, \ + ##__VA_ARGS__) + + +#define MCDRV_ASSERT(cond) \ + do { \ + if (unlikely(!(cond))) { \ + panic("mcKernelApi Assertion failed: %s:%d\n", \ + __FILE__, __LINE__); \ + } \ + } while (0) + +#elif defined(NDEBUG) + +#define MCDRV_DBG_VERBOSE(...) DUMMY_FUNCTION() +#define MCDRV_DBG(...) DUMMY_FUNCTION() +#define MCDRV_DBG_WARN(...) DUMMY_FUNCTION() +#define MCDRV_DBG_ERROR(...) DUMMY_FUNCTION() + +#define MCDRV_ASSERT(...) DUMMY_FUNCTION() + +#else +#error "Define DEBUG or NDEBUG" +#endif /* [not] defined(DEBUG_MCMODULE) */ + + +#define LOG_I MCDRV_DBG_VERBOSE +#define LOG_W MCDRV_DBG_WARN +#define LOG_E MCDRV_DBG_ERROR + + +#define assert(expr) MCDRV_ASSERT(expr) + +#endif /* COMMON_H */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/connection.c b/drivers/gud/mobicore_kernelapi/connection.c new file mode 100644 index 0000000000000000000000000000000000000000..9048ae87a5a1194ac06a06f187ee210e538ce360 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/connection.c @@ -0,0 +1,229 @@ +/** @addtogroup MCD_MCDIMPL_DAEMON_SRV + * @{ + * @file + * + * Connection data. + * + * + * + * 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 "connection.h" +#include "common.h" + +/* Define the initial state of the Data Available Semaphore */ +#define SEM_NO_DATA_AVAILABLE 0 + +/*----------------------------------------------------------------------------*/ +struct connection *connection_new( + void +) { + struct connection *conn = kzalloc(sizeof(struct connection), + GFP_KERNEL); + conn->sequence_magic = mcapi_unique_id(); + mutex_init(&conn->data_lock); + /* No data available */ + sema_init(&conn->data_available_sem, SEM_NO_DATA_AVAILABLE); + + mcapi_insert_connection(conn); + return conn; +} + +/*----------------------------------------------------------------------------*/ +struct connection *connection_create( + int socket_descriptor, + pid_t dest +) { + struct connection *conn = connection_new(); + + conn->peer_pid = dest; + return conn; +} + + +/*----------------------------------------------------------------------------*/ +void connection_cleanup( + struct connection *conn +) { + if (!conn) + return; + + kfree_skb(conn->skb); + + mcapi_remove_connection(conn->sequence_magic); + kfree(conn); +} + + +/*----------------------------------------------------------------------------*/ +bool connection_connect( + struct connection *conn, + pid_t dest +) { + /* Nothing to connect */ + conn->peer_pid = dest; + return true; +} + +/*----------------------------------------------------------------------------*/ +size_t connection_readDataMsg( + struct connection *conn, + void *buffer, + uint32_t len +) { + size_t ret = -1; + MCDRV_DBG_VERBOSE("reading connection data %u, connection data left %u", + len, conn->data_len); + /* trying to read more than the left data */ + if (len > conn->data_len) { + ret = conn->data_len; + memcpy(buffer, conn->data_start, conn->data_len); + conn->data_len = 0; + } else { + ret = len; + memcpy(buffer, conn->data_start, len); + conn->data_len -= len; + conn->data_start += len; + } + + if (conn->data_len == 0) { + conn->data_start = NULL; + kfree_skb(conn->skb); + conn->skb = NULL; + } + MCDRV_DBG_VERBOSE("read %u", ret); + return ret; +} + +/*----------------------------------------------------------------------------*/ +size_t connection_read_datablock( + struct connection *conn, + void *buffer, + uint32_t len +) { + return connection_read_data(conn, buffer, len, -1); +} + + +/*----------------------------------------------------------------------------*/ +size_t connection_read_data( + struct connection *conn, + void *buffer, + uint32_t len, + int32_t timeout +) { + size_t ret = 0; + + MCDRV_ASSERT(buffer != NULL); + MCDRV_ASSERT(conn->socket_descriptor != NULL); + + MCDRV_DBG_VERBOSE("read data len = %u for PID = %u", + len, conn->sequence_magic); + do { + /* Wait until data is available or timeout + msecs_to_jiffies(-1) -> wait forever for the sem */ + if (down_timeout(&(conn->data_available_sem), + msecs_to_jiffies(timeout))) { + MCDRV_DBG_VERBOSE("Timeout reading the data sem"); + ret = -2; + break; + } + + if (mutex_lock_interruptible(&(conn->data_lock))) { + MCDRV_DBG_ERROR("interrupted reading the data sem"); + ret = -1; + break; + } + /* Have data, use it */ + if (conn->data_len > 0) + ret = connection_readDataMsg(conn, buffer, len); + + mutex_unlock(&(conn->data_lock)); + + /* There is still some data left */ + if (conn->data_len > 0) + up(&conn->data_available_sem); + } while (0); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +size_t connection_write_data( + struct connection *conn, + void *buffer, + uint32_t len +) { + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh; + int ret = 0; + + MCDRV_DBG_VERBOSE("buffer length %u from pid %u\n", + len, conn->sequence_magic); + do { + skb = nlmsg_new(NLMSG_SPACE(len), GFP_KERNEL); + if (!skb) { + ret = -1; + break; + } + + nlh = nlmsg_put(skb, 0, conn->sequence_magic, 2, + NLMSG_LENGTH(len), NLM_F_REQUEST); + if (!nlh) { + ret = -1; + break; + } + memcpy(NLMSG_DATA(nlh), buffer, len); + + netlink_unicast(conn->socket_descriptor, skb, + conn->peer_pid, MSG_DONTWAIT); + ret = len; + } while (0); + + if (!ret && skb != NULL) + kfree_skb(skb); + + return ret; +} + +int connection_process( + struct connection *conn, + struct sk_buff *skb +) +{ + int ret = 0; + do { + if (mutex_lock_interruptible(&(conn->data_lock))) { + MCDRV_DBG_ERROR("Interrupted getting data semaphore!"); + ret = -1; + break; + } + + kfree_skb(conn->skb); + + /* Get a reference to the incomming skb */ + conn->skb = skb_get(skb); + if (conn->skb) { + conn->data_msg = nlmsg_hdr(conn->skb); + conn->data_len = NLMSG_PAYLOAD(conn->data_msg, 0); + conn->data_start = NLMSG_DATA(conn->data_msg); + up(&(conn->data_available_sem)); + } + mutex_unlock(&(conn->data_lock)); + ret = 0; + } while (0); + return ret; +} +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/connection.h b/drivers/gud/mobicore_kernelapi/connection.h new file mode 100644 index 0000000000000000000000000000000000000000..0b468e6c7b4355feca6529ef7508f324fdaa5d0a --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/connection.h @@ -0,0 +1,122 @@ +/** @addtogroup MCD_MCDIMPL_DAEMON_SRV + * @{ + * @file + * + * Connection data. + * + * + * + * 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 CONNECTION_H_ +#define CONNECTION_H_ + +#include + +#include +#include + +#define MAX_PAYLOAD_SIZE 128 + +struct connection { + struct sock *socket_descriptor; /**< Netlink socket */ + uint32_t sequence_magic; /**< Random? magic to match requests/answers */ + + struct nlmsghdr *data_msg; + uint32_t data_len; /**< How much connection data is left */ + void *data_start; /**< Start pointer of remaining data */ + struct sk_buff *skb; + + struct mutex data_lock; /**< Data protection lock */ + struct semaphore data_available_sem; /**< Data protection semaphore */ + + pid_t self_pid; /**< PID address used for local connection */ + pid_t peer_pid; /**< Remote PID for connection */ + + struct list_head list; /**< The list param for using the kernel lists*/ +}; + +struct connection *connection_new( + void +); + +struct connection *connection_create( + int socket_descriptor, + pid_t dest +); + +void connection_cleanup( + struct connection *conn +); + +/** + * Connect to destination. + * + * @param Destination pointer. + * @return true on success. + */ +bool connection_connect( + struct connection *conn, + pid_t dest +); + + +/** + * Read bytes from the connection. + * + * @param buffer Pointer to destination buffer. + * @param len Number of bytes to read. + * @return Number of bytes read. + */ +size_t connection_read_datablock( + struct connection *conn, + void *buffer, + uint32_t len +); +/** + * Read bytes from the connection. + * + * @param buffer Pointer to destination buffer. + * @param len Number of bytes to read. + * @param timeout Timeout in milliseconds + * @return Number of bytes read. + * @return -1 if select() failed (returned -1) + * @return -2 if no data available, i.e. timeout + */ +size_t connection_read_data( + struct connection *conn, + void *buffer, + uint32_t len, + int32_t timeout +); + +/** + * Write bytes to the connection. + * + * @param buffer Pointer to source buffer. + * @param len Number of bytes to read. + * @return Number of bytes written. + */ +size_t connection_write_data( + struct connection *conn, + void *buffer, + uint32_t len +); + +/** + * Write bytes to the connection. + * + * @param buffer Pointer to source buffer. + * @param len Number of bytes to read. + * @return Number of bytes written. + */ +int connection_process( + struct connection *conn, + struct sk_buff *skb +); + +#endif /* CONNECTION_H_ */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/device.c b/drivers/gud/mobicore_kernelapi/device.c new file mode 100644 index 0000000000000000000000000000000000000000..dbeee6a81190546514cbc6d5e05449a17016e3ee --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/device.c @@ -0,0 +1,257 @@ +/** @addtogroup MCD_IMPL_LIB + * @{ + * @file + * + * Client library device management. + * + * Device and Trustlet Session management Funtions. + * + * + * + * 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 "mc_kernel_api.h" +#include "public/mobicore_driver_api.h" + +#include "device.h" +#include "common.h" + +/*----------------------------------------------------------------------------*/ +struct wsm *wsm_create( + void *virt_addr, + uint32_t len, + uint32_t handle, + void *phys_addr /*= NULL this may be unknown, so is can be omitted.*/ + ) +{ + struct wsm *wsm = kzalloc(sizeof(struct wsm), GFP_KERNEL); + wsm->virt_addr = virt_addr; + wsm->len = len; + wsm->handle = handle; + wsm->phys_addr = phys_addr; + return wsm; +} + + +/*----------------------------------------------------------------------------*/ +struct mcore_device_t *mcore_device_create( + uint32_t device_id, + struct connection *connection +) { + struct mcore_device_t *dev = + kzalloc(sizeof(struct mcore_device_t), GFP_KERNEL); + dev->device_id = device_id; + dev->connection = connection; + + INIT_LIST_HEAD(&dev->session_vector); + INIT_LIST_HEAD(&dev->wsm_l2_vector); + + return dev; +} + + +/*----------------------------------------------------------------------------*/ +void mcore_device_cleanup( + struct mcore_device_t *dev +) { + struct session *tmp; + struct wsm *wsm; + struct list_head *pos, *q; + + /* Delete all session objects. Usually this should not be needed + * as closeDevice()requires that all sessions have been closed before.*/ + list_for_each_safe(pos, q, &dev->session_vector) { + tmp = list_entry(pos, struct session, list); + list_del(pos); + session_cleanup(tmp); + } + + /* Free all allocated WSM descriptors */ + list_for_each_safe(pos, q, &dev->wsm_l2_vector) { + wsm = list_entry(pos, struct wsm, list); + /* mcKMod_free(dev->instance, wsm->handle); */ + list_del(pos); + kfree(wsm); + } + connection_cleanup(dev->connection); + + mcore_device_close(dev); + kfree(dev); +} + + +/*----------------------------------------------------------------------------*/ +bool mcore_device_open( + struct mcore_device_t *dev, + const char *deviceName +) { + dev->instance = mobicore_open(); + return (dev->instance != NULL); +} + + +/*----------------------------------------------------------------------------*/ +void mcore_device_close( + struct mcore_device_t *dev +) { + mobicore_release(dev->instance); +} + + +/*----------------------------------------------------------------------------*/ +bool mcore_device_has_sessions( + struct mcore_device_t *dev +) { + return !list_empty(&dev->session_vector); +} + + +/*----------------------------------------------------------------------------*/ +bool mcore_device_create_new_session( + struct mcore_device_t *dev, + uint32_t session_id, + struct connection *connection +) { + /* Check if session_id already exists */ + if (mcore_device_resolve_session_id(dev, session_id)) { + MCDRV_DBG_ERROR(" session %u already exists", session_id); + return false; + } + struct session *session = session_create(session_id, dev->instance, + connection); + list_add_tail(&(session->list), &(dev->session_vector)); + return true; +} + + +/*----------------------------------------------------------------------------*/ +bool mcore_device_remove_session( + struct mcore_device_t *dev, + uint32_t session_id +) { + bool ret = false; + struct session *tmp; + struct list_head *pos, *q; + + list_for_each_safe(pos, q, &dev->session_vector) { + tmp = list_entry(pos, struct session, list); + if (tmp->session_id == session_id) { + list_del(pos); + session_cleanup(tmp); + ret = true; + break; + } + } + return ret; +} + + +/*----------------------------------------------------------------------------*/ +struct session *mcore_device_resolve_session_id( + struct mcore_device_t *dev, + uint32_t session_id +) { + struct session *ret = NULL; + struct session *tmp; + struct list_head *pos; + + + /* Get session for session_id */ + list_for_each(pos, &dev->session_vector) { + tmp = list_entry(pos, struct session, list); + if (tmp->session_id == session_id) { + ret = tmp; + break; + } + } + return ret; +} + + +/*----------------------------------------------------------------------------*/ +struct wsm *mcore_device_allocate_contiguous_wsm( + struct mcore_device_t *dev, + uint32_t len +) { + struct wsm *wsm = NULL; + do { + if (len == 0) + break; + + /* Allocate shared memory */ + void *virt_addr; + uint32_t handle; + void *phys_addr; + int ret = mobicore_allocate_wsm(dev->instance, + len, + &handle, + &virt_addr, + &phys_addr); + if (ret != 0) + break; + + /* Register (vaddr,paddr) with device */ + wsm = wsm_create(virt_addr, len, handle, phys_addr); + + list_add_tail(&(wsm->list), &(dev->wsm_l2_vector)); + + } while (0); + + /* Return pointer to the allocated memory */ + return wsm; +} + + +/*----------------------------------------------------------------------------*/ +bool mcore_device_free_contiguous_wsm( + struct mcore_device_t *dev, + struct wsm *wsm +) { + bool ret = false; + struct wsm *tmp; + struct list_head *pos; + + list_for_each(pos, &dev->wsm_l2_vector) { + tmp = list_entry(pos, struct wsm, list); + if (tmp == wsm) { + ret = true; + break; + } + } + + if (ret) { + MCDRV_DBG_VERBOSE("freeWsm virt_addr=0x%p, handle=%d", + wsm->virt_addr, wsm->handle); + + /* ignore return code */ + mobicore_free(dev->instance, wsm->handle); + + list_del(pos); + kfree(wsm); + } + return ret; +} + + +/*----------------------------------------------------------------------------*/ +struct wsm *mcore_device_find_contiguous_wsm( + struct mcore_device_t *dev, + void *virt_addr +) { + struct wsm *wsm; + struct list_head *pos; + + list_for_each(pos, &dev->wsm_l2_vector) { + wsm = list_entry(pos, struct wsm, list); + if (virt_addr == wsm->virt_addr) + return wsm; + } + + return NULL; +} + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/device.h b/drivers/gud/mobicore_kernelapi/device.h new file mode 100644 index 0000000000000000000000000000000000000000..f40d9936714ae277dd8e4a19adb28330506ac341 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/device.h @@ -0,0 +1,139 @@ +/** @addtogroup MCD_IMPL_LIB + * @{ + * @file + * + * Client library device management. + * + * Device and Trustlet Session management Functions. + * + * + * + * 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 DEVICE_H_ +#define DEVICE_H_ + +#include + +#include "connection.h" +#include "session.h" +#include "wsm.h" + + +struct mcore_device_t { + struct list_head session_vector; /**< MobiCore Trustlet session + associated with the device */ + struct list_head wsm_l2_vector; /**< WSM L2 Table */ + + uint32_t device_id; /**< Device identifier */ + struct connection *connection; /**< The device connection */ + struct mc_instance *instance; /**< MobiCore Driver instance */ + + struct list_head list; /**< The list param for using the kernel lists*/ +}; + +struct mcore_device_t *mcore_device_create( + uint32_t device_id, + struct connection *connection +); + +void mcore_device_cleanup( + struct mcore_device_t *dev +); + +/** + * Open the device. + * @param deviceName Name of the kernel modules device file. + * @return true if the device has been opened successfully + */ +bool mcore_device_open( + struct mcore_device_t *dev, + const char *deviceName +); + +/** + * Closes the device. + */ +void mcore_device_close( + struct mcore_device_t *dev +); + +/** + * Check if the device has open sessions. + * @return true if the device has one or more open sessions. + */ +bool mcore_device_has_sessions( + struct mcore_device_t *dev +); + +/** + * Add a session to the device. + * @param session_id session ID + * @param connection session connection + */ +bool mcore_device_create_new_session( + struct mcore_device_t *dev, + uint32_t session_id, + struct connection *connection +); + +/** + * Remove the specified session from the device. + * The session object will be destroyed and all resources associated with it + * will be freed. + * + * @param session_id Session of the session to remove. + * @return true if a session has been found and removed. + */ +bool mcore_device_remove_session( + struct mcore_device_t *dev, + uint32_t session_id +); + +/** + * Get as session object for a given session ID. + * @param session_id Identified of a previously opened session. + * @return Session object if available or NULL if no session has been found. + */ +struct session *mcore_device_resolve_session_id( + struct mcore_device_t *dev, + uint32_t session_id +); + +/** + * Allocate a block of contiguous WSM. + * @param len The virtual address to be registered. + * @return The virtual address of the allocated memory or NULL if no memory + * is available. + */ +struct wsm *mcore_device_allocate_contiguous_wsm( + struct mcore_device_t *dev, + uint32_t len +); + +/** + * Unregister a vaddr from a device. + * @param vaddr The virtual address to be registered. + * @param paddr The physical address to be registered. + */ +bool mcore_device_free_contiguous_wsm( + struct mcore_device_t *dev, + struct wsm *wsm +); + +/** + * Get a WSM object for a given virtual address. + * @param vaddr The virtual address which has been allocate with mc_malloc_wsm() + * in advance. + * @return the WSM object or NULL if no address has been found. + */ +struct wsm *mcore_device_find_contiguous_wsm( + struct mcore_device_t *dev, + void *virt_addr +); + +#endif /* DEVICE_H_ */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/include/mcinq.h b/drivers/gud/mobicore_kernelapi/include/mcinq.h new file mode 100644 index 0000000000000000000000000000000000000000..3cb82be8a32d9d506f72420f6c875c358dc90625 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/include/mcinq.h @@ -0,0 +1,125 @@ +/** @addtogroup NQ + * @{ + * Notifications inform the MobiCore runtime environment that information is + * pending in a WSM buffer. + * The Trustlet Connector (TLC) and the corresponding trustlet also utilize + * this buffer to notify each other about new data within the + * Trustlet Connector Interface (TCI). + * + * The buffer is set up as a queue, which means that more than one + * notification can be written to the buffer before the switch to the other + * world is performed. Each side therefore facilitates an incoming and an + * outgoing queue for communication with the other side. + * + * Notifications hold the session ID, which is used to reference the + * communication partner in the other world. + * So if, e.g., the TLC in the normal world wants to notify his trustlet + * about new data in the TLC buffer + * + * @file + * Notification queue declarations. + * + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 NQ_H_ +#define NQ_H_ + +/** \name NQ Size Defines + * Minimum and maximum count of elements in the notification queue. + * @{ */ +#define MIN_NQ_ELEM 1 /**< Minimum notification queue elements. */ +#define MAX_NQ_ELEM 64 /**< Maximum notification queue elements. */ +/** @} */ + +/** \name NQ Length Defines + * Minimum and maximum notification queue length. + * @{ */ +/**< Minimum notification length (in bytes). */ +#define MIN_NQ_LEN (MIN_NQ_ELEM * sizeof(notification)) +/**< Maximum notification length (in bytes). */ +#define MAX_NQ_LEN (MAX_NQ_ELEM * sizeof(notification)) +/** @} */ + +/** \name Session ID Defines + * Standard Session IDs. + * @{ */ +/**< MCP session ID is used when directly communicating with the MobiCore + * (e.g. for starting and stopping of trustlets). */ +#define SID_MCP 0 +/**< Invalid session id is returned in case of an error. */ +#define SID_INVALID 0xffffffff +/** @} */ + +/** Notification data structure. */ +struct notification { + uint32_t session_id; /**< Session ID. */ + int32_t payload; /**< Additional notification information. */ +}; + +/** Notification payload codes. + * 0 indicated a plain simple notification, + * a positive value is a termination reason from the task, + * a negative value is a termination reason from MobiCore. + * Possible negative values are given below. + */ +enum notification_payload { + /**< task terminated, but exit code is invalid */ + ERR_INVALID_EXIT_CODE = -1, + /**< task terminated due to session end, no exit code available */ + ERR_SESSION_CLOSE = -2, + /**< task terminated due to invalid operation */ + ERR_INVALID_OPERATION = -3, + /**< session ID is unknown */ + ERR_INVALID_SID = -4, + /**< session is not active */ + ERR_SID_NOT_ACTIVE = -5 +}; + +/** Declaration of the notification queue header. + * layout as specified in the data structure specification. + */ +struct notification_queue_header { + uint32_t write_cnt; /**< Write counter. */ + uint32_t read_cnt; /**< Read counter. */ + uint32_t queue_size; /**< Queue size. */ +}; + +/** Queue struct which defines a queue object. + * The queue struct is accessed by the queue type of + * function. elementCnt must be a power of two and the power needs + * to be smaller than power of uint32_t (obviously 32). + */ +struct notification_queue { + /**< Queue header. */ + struct notification_queue_header hdr; + /**< Notification elements. */ + struct notification notification[MIN_NQ_ELEM]; +} ; + +#endif /** NQ_H_ */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/include/mcuuid.h b/drivers/gud/mobicore_kernelapi/include/mcuuid.h new file mode 100644 index 0000000000000000000000000000000000000000..b72acb8a002e58cd40b24d62dd3fa09edeaa4b22 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/include/mcuuid.h @@ -0,0 +1,74 @@ +/** + * @addtogroup MC_UUID mcUuid - Universally Unique Identifier. + * + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + * @ingroup MC_DATA_TYPES + * @{ + */ + +#ifndef MC_UUID_H_ +#define MC_UUID_H_ + +#define UUID_TYPE + +/** Universally Unique Identifier (UUID) according to ISO/IEC 11578. */ +struct mc_uuid_t { + uint8_t value[16]; /**< Value of the UUID. */ +}; + +/** UUID value used as free marker in service provider containers. */ +#define MC_UUID_FREE_DEFINE \ + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + +static const struct mc_uuid_t MC_UUID_FREE = { + MC_UUID_FREE_DEFINE +}; + +/** Reserved UUID. */ +#define MC_UUID_RESERVED_DEFINE \ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + +static const struct mc_uuid_t MC_UUID_RESERVED = { + MC_UUID_RESERVED_DEFINE +}; + +/** UUID for system applications. */ +#define MC_UUID_SYSTEM_DEFINE \ + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE } + +static const struct mc_uuid_t MC_UUID_SYSTEM = { + MC_UUID_SYSTEM_DEFINE +}; + +#endif /* MC_UUID_H_ */ + +/** @} */ + diff --git a/drivers/gud/mobicore_kernelapi/main.c b/drivers/gud/mobicore_kernelapi/main.c new file mode 100644 index 0000000000000000000000000000000000000000..62997f725e6295f2a4aa7378b7d6c8ab37b507d4 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/main.c @@ -0,0 +1,181 @@ +/** + * MobiCore KernelApi module + * + * + * + * 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 "connection.h" +#include "common.h" + +#define MC_DAEMON_NETLINK 17 + +struct mc_kernelapi_ctx { + struct sock *sk; + struct list_head peers; + atomic_t counter; +}; + +struct mc_kernelapi_ctx *mod_ctx; /* = NULL; */ + +/*----------------------------------------------------------------------------*/ +/* get a unique ID */ +unsigned int mcapi_unique_id( + void +) +{ + return (unsigned int)atomic_inc_return( + &(mod_ctx->counter)); +} + + +/*----------------------------------------------------------------------------*/ +static struct connection *mcapi_find_connection( + uint32_t seq +) +{ + struct connection *tmp; + struct list_head *pos; + + /* Get session for session_id */ + list_for_each(pos, &mod_ctx->peers) { + tmp = list_entry(pos, struct connection, list); + if (tmp->sequence_magic == seq) + return tmp; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ +void mcapi_insert_connection( + struct connection *connection +) +{ + list_add_tail(&(connection->list), &(mod_ctx->peers)); + connection->socket_descriptor = mod_ctx->sk; +} + +void mcapi_remove_connection( + uint32_t seq +) +{ + struct connection *tmp; + struct list_head *pos, *q; + + /* Delete all session objects. Usually this should not be needed as + closeDevice() requires that all sessions have been closed before.*/ + list_for_each_safe(pos, q, &mod_ctx->peers) { + tmp = list_entry(pos, struct connection, list); + if (tmp->sequence_magic == seq) { + list_del(pos); + break; + } + } +} + +/*----------------------------------------------------------------------------*/ +static int mcapi_process( + struct sk_buff *skb, + struct nlmsghdr *nlh +) +{ + struct connection *c; + int length; + int seq; + pid_t pid; + int ret; + + pid = nlh->nlmsg_pid; + length = nlh->nlmsg_len; + seq = nlh->nlmsg_seq; + MCDRV_DBG_VERBOSE("nlmsg len %d type %d pid 0x%X seq %d\n", + length, nlh->nlmsg_type, pid, seq); + do { + c = mcapi_find_connection(seq); + if (!c) { + MCDRV_ERROR("Invalid incomming connection - seq=%u!", + seq); + ret = -1; + break; + } + + /* Pass the buffer to the appropriate connection */ + connection_process(c, skb); + + ret = 0; + } while (false); + return ret; +} + +/*----------------------------------------------------------------------------*/ +static void mcapi_callback( + struct sk_buff *skb +) +{ + struct nlmsghdr *nlh = nlmsg_hdr(skb); + int len = skb->len; + int err = 0; + + while (NLMSG_OK(nlh, len)) { + err = mcapi_process(skb, nlh); + + /* if err or if this message says it wants a response */ + if (err || (nlh->nlmsg_flags & NLM_F_ACK)) + netlink_ack(skb, nlh, err); + + nlh = NLMSG_NEXT(nlh, len); + } +} + +/*----------------------------------------------------------------------------*/ +static int __init mcapi_init(void) +{ + printk(KERN_INFO "Mobicore API module initialized!\n"); + + mod_ctx = kzalloc(sizeof(struct mc_kernelapi_ctx), GFP_KERNEL); + + /* start kernel thread */ + mod_ctx->sk = netlink_kernel_create(&init_net, MC_DAEMON_NETLINK, 0, + mcapi_callback, NULL, THIS_MODULE); + + if (!mod_ctx->sk) { + MCDRV_ERROR("register of recieve handler failed"); + return -EFAULT; + } + + INIT_LIST_HEAD(&mod_ctx->peers); + return 0; +} + +static void __exit mcapi_exit(void) +{ + printk(KERN_INFO "Unloading Mobicore API module.\n"); + + if (mod_ctx->sk != NULL) { + netlink_kernel_release(mod_ctx->sk); + mod_ctx->sk = NULL; + } + kfree(mod_ctx); + mod_ctx = NULL; +} + +module_init(mcapi_init); +module_exit(mcapi_exit); + +MODULE_AUTHOR("Giesecke & Devrient GmbH"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MobiCore API driver"); diff --git a/drivers/gud/mobicore_kernelapi/public/mobicore_driver_api.h b/drivers/gud/mobicore_kernelapi/public/mobicore_driver_api.h new file mode 100644 index 0000000000000000000000000000000000000000..ccfb2e5e9de9ba64d78cd99a5ac7ba4d4fbf6870 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/public/mobicore_driver_api.h @@ -0,0 +1,483 @@ +/** + * @defgroup MCD_API MobiCore Driver API + * @addtogroup MCD_API + * @{ + * + * @if DOXYGEN_MCDRV_API + * @mainpage MobiCore Driver API. + * @endif + * + * MobiCore Driver API. + * + * The MobiCore (MC) Driver API provides access functions to the MobiCore + * runtime environment and the contained Trustlets. + * + * @image html DoxyOverviewDrvApi500x.png + * @image latex DoxyOverviewDrvApi500x.png "MobiCore Overview" width=12cm + * + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 MCDRIVER_H_ +#define MCDRIVER_H_ + +#define __MC_CLIENT_LIB_API + +#include "mcuuid.h" + +/** + * Return values of MobiCore driver functions. + */ +enum mc_result { + /**< Function call succeeded. */ + MC_DRV_OK = 0, + /**< No notification available. */ + MC_DRV_NO_NOTIFICATION = 1, + /**< Error during notification on communication level. */ + MC_DRV_ERR_NOTIFICATION = 2, + /**< Function not implemented. */ + MC_DRV_ERR_NOT_IMPLEMENTED = 3, + /**< No more resources available. */ + MC_DRV_ERR_OUT_OF_RESOURCES = 4, + /**< Driver initialization failed. */ + MC_DRV_ERR_INIT = 5, + /**< Unknown error. */ + MC_DRV_ERR_UNKNOWN = 6, + /**< The specified device is unknown. */ + MC_DRV_ERR_UNKNOWN_DEVICE = 7, + /**< The specified session is unknown.*/ + MC_DRV_ERR_UNKNOWN_SESSION = 8, + /**< The specified operation is not allowed. */ + MC_DRV_ERR_INVALID_OPERATION = 9, + /**< The response header from the MC is invalid. */ + MC_DRV_ERR_INVALID_RESPONSE = 10, + /**< Function call timed out. */ + MC_DRV_ERR_TIMEOUT = 11, + /**< Can not allocate additional memory. */ + MC_DRV_ERR_NO_FREE_MEMORY = 12, + /**< Free memory failed. */ + MC_DRV_ERR_FREE_MEMORY_FAILED = 13, + /**< Still some open sessions pending. */ + MC_DRV_ERR_SESSION_PENDING = 14, + /**< MC daemon not reachable */ + MC_DRV_ERR_DAEMON_UNREACHABLE = 15, + /**< The device file of the kernel module could not be opened. */ + MC_DRV_ERR_INVALID_DEVICE_FILE = 16, + /**< Invalid parameter. */ + MC_DRV_ERR_INVALID_PARAMETER = 17, + /**< Unspecified error from Kernel Module*/ + MC_DRV_ERR_KERNEL_MODULE = 18, + /**< Error during mapping of additional bulk memory to session. */ + MC_DRV_ERR_BULK_MAPPING = 19, + /**< Error during unmapping of additional bulk memory to session. */ + MC_DRV_ERR_BULK_UNMAPPING = 20, + /**< Notification received, exit code available. */ + MC_DRV_INFO_NOTIFICATION = 21, + /**< Set up of NWd connection failed. */ + MC_DRV_ERR_NQ_FAILED = 22 +}; + + +/** + * Driver control command. + */ +enum mc_driver_ctrl { + MC_CTRL_GET_VERSION = 1 /**< Return the driver version */ +}; + + +/** Structure of Session Handle, includes the Session ID and the Device ID the + * Session belongs to. + * The session handle will be used for session-based MobiCore communication. + * It will be passed to calls which address a communication end point in the + * MobiCore environment. + */ +struct mc_session_handle { + uint32_t session_id; /**< MobiCore session ID */ + uint32_t device_id; /**< Device ID the session belongs to */ +}; + +/** Information structure about additional mapped Bulk buffer between the + * Trustlet Connector (Nwd) and the Trustlet (Swd). This structure is + * initialized from a Trustlet Connector by calling mc_map(). + * In order to use the memory within a Trustlet the Trustlet Connector has to + * inform the Trustlet with the content of this structure via the TCI. + */ +struct mc_bulk_map { + /**< The virtual address of the Bulk buffer regarding the address space + * of the Trustlet, already includes a possible offset! */ + void *secure_virt_addr; + uint32_t secure_virt_len; /**< Length of the mapped Bulk buffer */ +}; + + +/**< The default device ID */ +#define MC_DEVICE_ID_DEFAULT 0 +/**< Wait infinite for a response of the MC. */ +#define MC_INFINITE_TIMEOUT ((int32_t)(-1)) +/**< Do not wait for a response of the MC. */ +#define MC_NO_TIMEOUT 0 +/**< TCI/DCI must not exceed 1MiB */ +#define MC_MAX_TCI_LEN 0x100000 + + + +/** Open a new connection to a MobiCore device. + * + * mc_open_device() initializes all device specific resources required to + * communicate with an MobiCore instance located on the specified device in the + * system. If the device does not exist the function will return + * MC_DRV_ERR_UNKNOWN_DEVICE. + * + * @param [in] device_id Identifier for the MobiCore device to be used. + * MC_DEVICE_ID_DEFAULT refers to the default device. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_ERR_INVALID_OPERATION if device already opened. + * @return MC_DRV_ERR_DAEMON_UNREACHABLE when problems with daemon occur. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device_id is unknown. + * @return MC_DRV_ERR_INVALID_DEVICE_FILE if kernel module under + * /dev/mobicore cannot be opened + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_open_device( + uint32_t device_id +); + +/** Close the connection to a MobiCore device. + * When closing a device, active sessions have to be closed beforehand. + * Resources associated with the device will be released. + * The device may be opened again after it has been closed. + * + * @param [in] device_id Identifier for the MobiCore device. + * MC_DEVICE_ID_DEFAULT refers to the default device. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id is invalid. + * @return MC_DRV_ERR_SESSION_PENDING when a session is still open. + * @return MC_DRV_ERR_DAEMON_UNREACHABLE when problems with daemon occur. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_close_device( + uint32_t device_id +); + +/** Open a new session to a Trustlet. The trustlet with the given UUID has + * to be available in the flash filesystem. + * + * Write MCP open message to buffer and notify MobiCore about the availability + * of a new command. + * Waits till the MobiCore responses with the new session ID (stored in the MCP + * buffer). + * + * @param [in,out] session On success, the session data will be returned. + * Note that session.device_id has to be the device id of an opened device. + * @param [in] uuid UUID of the Trustlet to be opened. + * @param [in] tci TCI buffer for communicating with the trustlet. + * @param [in] tci_len Length of the TCI buffer. Maximum allowed value + * is MC_MAX_TCI_LEN. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if session parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id is invalid. + * @return MC_DRV_ERR_DAEMON_UNREACHABLE when problems with daemon socket occur. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when daemon returns an error. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_open_session( + struct mc_session_handle *session, + const struct mc_uuid_t *uuid, + uint8_t *tci, + uint32_t tci_len +); + +/** Close a Trustlet session. + * + * Closes the specified MobiCore session. The call will block until the + * session has been closed. + * + * @pre Device device_id has to be opened in advance. + * + * @param [in] session Session to be closed. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if session parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_SESSION when session id is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id of session is invalid. + * @return MC_DRV_ERR_DAEMON_UNREACHABLE when problems with daemon occur. + * @return MC_DRV_ERR_INVALID_DEVICE_FILE when daemon cannot open trustlet file. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_close_session( + struct mc_session_handle *session +); + +/** Notify a session. + * Notifies the session end point about available message data. + * If the session parameter is correct, notify will always succeed. + * Corresponding errors can only be received by mc_wait_notification(). + * @pre A session has to be opened in advance. + * + * @param session The session to be notified. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if session parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_SESSION when session id is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id of session is invalid. + */ +__MC_CLIENT_LIB_API enum mc_result mc_notify( + struct mc_session_handle *session +); + +/** Wait for a notification. + * + * Wait for a notification issued by the MobiCore for a specific session. + * The timeout parameter specifies the number of milliseconds the call will wait + * for a notification. + * If the caller passes 0 as timeout value the call will immediately return. + * If timeout value is below 0 the call will block until a notification for the + session has been received. + * + * @attention if timeout is below 0, call will block: + * Caller has to trust the other side to send a notification to wake him up + * again. + * + * @param [in] session The session the notification should correspond to. + * @param [in] timeout Time in milliseconds to wait + * (MC_NO_TIMEOUT : direct return, > 0 : milliseconds, + * MC_INFINITE_TIMEOUT : wait infinitely) + * + * @return MC_DRV_OK if notification is available. + * @return MC_DRV_ERR_TIMEOUT if no notification arrived in time. + * @return MC_DRV_INFO_NOTIFICATION if a problem with the session was + * encountered. Get more details with mc_get_session_error_code(). + * @return MC_DRV_ERR_NOTIFICATION if a problem with the socket occurred. + * @return MC_DRV_INVALID_PARAMETER if a parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_SESSION when session id is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id of session is invalid. + */ +__MC_CLIENT_LIB_API enum mc_result mc_wait_notification( + struct mc_session_handle *session, + int32_t timeout +); + +/** + * Allocate a block of world shared memory (WSM). + * The MC driver allocates a contiguous block of memory which can be used as + * WSM. + * This implicates that the allocated memory is aligned according to the + * alignment parameter. + * Always returns a buffer of size WSM_SIZE aligned to 4K. + * + * @param [in] device_id The ID of an opened device to retrieve the WSM from. + * @param [in] align The alignment (number of pages) of the memory block + * (e.g. 0x00000001 for 4kb). + * @param [in] len Length of the block in bytes. + * @param [out] wsm Virtual address of the world shared memory block. + * @param [in] wsm_flags Platform specific flags describing the memory to + * be allocated. + * + * @attention: align and wsm_flags are currently ignored + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if a parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id is invalid. + * @return MC_DRV_ERR_NO_FREE_MEMORY if no more contiguous memory is available + * in this size or for this process. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_malloc_wsm( + uint32_t device_id, + uint32_t align, + uint32_t len, + uint8_t **wsm, + uint32_t wsm_flags +); + +/** + * Free a block of world shared memory (WSM). + * The MC driver will free a block of world shared memory (WSM) previously + * allocated with mc_malloc_wsm(). The caller has to assure that the address + * handed over to the driver is a valid WSM address. + * + * @param [in] device_id The ID to which the given address belongs. + * @param [in] wsm Address of WSM block to be freed. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if a parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id is invalid. + * @return MC_DRV_ERR_FREE_MEMORY_FAILED on failures. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_free_wsm( + uint32_t device_id, + uint8_t *wsm +); + +/** + * Map additional bulk buffer between a Trustlet Connector (TLC) and + * the Trustlet (TL) for a session. + * Memory allocated in user space of the TLC can be mapped as additional + * communication channel (besides TCI) to the Trustlet. Limitation of the + * Trustlet memory structure apply: only 6 chunks can be mapped with a maximum + * chunk size of 1 MiB each. + * + * @attention It is up to the application layer (TLC) to inform the Trustlet + * about the additional mapped bulk memory. + * + * @param [in] session Session handle with information of the device_id and + * the session_id. The + * given buffer is mapped to the session specified in the sessionHandle. + * @param [in] buf Virtual address of a memory portion (relative to TLC) + * to be shared with the Trustlet, already includes a possible offset! + * @param [in] len length of buffer block in bytes. + * @param [out] map_info Information structure about the mapped Bulk buffer + * between the TLC (Nwd) and + * the TL (Swd). + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if a parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_SESSION when session id is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id of session is invalid. + * @return MC_DRV_ERR_DAEMON_UNREACHABLE when problems with daemon occur. + * @return MC_DRV_ERR_BULK_MAPPING when buf is already uses as bulk buffer or + * when registering the buffer failed. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_map( + struct mc_session_handle *session, + void *buf, + uint32_t len, + struct mc_bulk_map *map_info +); + +/** + * Remove additional mapped bulk buffer between Trustlet Connector (TLC) + * and the Trustlet (TL) for a session. + * + * @attention The bulk buffer will immediately be unmapped from the session + * context. + * @attention The application layer (TLC) must inform the TL about unmapping + * of the additional bulk memory before calling mc_unmap! + * + * @param [in] session Session handle with information of the device_id and + * the session_id. The given buffer is unmapped from the session specified + * in the sessionHandle. + * @param [in] buf Virtual address of a memory portion (relative to TLC) + * shared with the TL, already includes a possible offset! + * @param [in] map_info Information structure about the mapped Bulk buffer + * between the TLC (Nwd) and + * the TL (Swd). + * @attention The clientlib currently ignores the len field in map_info. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if a parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_SESSION when session id is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id of session is invalid. + * @return MC_DRV_ERR_DAEMON_UNREACHABLE when problems with daemon occur. + * @return MC_DRV_ERR_BULK_UNMAPPING when buf was not registered earlier + * or when unregistering failed. + * + * Uses a Mutex. + */ +__MC_CLIENT_LIB_API enum mc_result mc_unmap( + struct mc_session_handle *session, + void *buf, + struct mc_bulk_map *map_info +); + + +/** + * @attention: Not implemented. + * Execute driver specific command. + * mc_driver_ctrl() can be used to execute driver specific commands. + * Besides the control command MC_CTRL_GET_VERSION commands are implementation + * specific. + * Please refer to the corresponding specification of the driver manufacturer. + * + * @param [in] param Command ID of the command to be executed. + * @param [in, out] data Command data and response depending on command. + * @param [in] len Length of the data block. + * + * @return MC_DRV_ERR_NOT_IMPLEMENTED. + */ +__MC_CLIENT_LIB_API enum mc_result mc_driver_ctrl( + enum mc_driver_ctrl param, + uint8_t *data, + uint32_t len +); + +/** + * @attention: Not implemented. + * Execute application management command. + * mc_manage() shall be used to exchange application management commands with + * the MobiCore. + * The MobiCore Application Management Protocol is described in [MCAMP]. + * + * @param [in] device_id Identifier for the MobiCore device to be used. + * NULL refers to the default device. + * @param [in, out] data Command data/response data depending on command. + * @param [in] len Length of the data block. + * + * @return MC_DRV_ERR_NOT_IMPLEMENTED. + */ +__MC_CLIENT_LIB_API enum mc_result mc_manage( + uint32_t device_id, + uint8_t *data, + uint32_t len +); + +/** + * Get additional error information of the last error that occured on a session. + * After the request the stored error code will be deleted. + * + * @param [in] session Session handle with information of the device_id and + * the session_id. + * @param [out] last_error >0 Trustlet has terminated itself with this value, + * <0 Trustlet is dead because of an error within the MobiCore + * (e.g. Kernel exception). + * See also MCI definition. + * + * @return MC_DRV_OK if operation has been successfully completed. + * @return MC_DRV_INVALID_PARAMETER if a parameter is invalid. + * @return MC_DRV_ERR_UNKNOWN_SESSION when session id is invalid. + * @return MC_DRV_ERR_UNKNOWN_DEVICE when device id of session is invalid. + */ +__MC_CLIENT_LIB_API enum mc_result mc_get_session_error_code( + struct mc_session_handle *session, + int32_t *last_error +); + +#endif /** MCDRIVER_H_ */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/public/mobicore_driver_cmd.h b/drivers/gud/mobicore_kernelapi/public/mobicore_driver_cmd.h new file mode 100644 index 0000000000000000000000000000000000000000..9ff798927dc42c2621d2be36f9834b0b4cf0c075 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/public/mobicore_driver_cmd.h @@ -0,0 +1,289 @@ +/** @addtogroup MCD_MCDIMPL_DAEMON + * @{ + * @file + * + * + * + * 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. + * 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 name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 MCDAEMON_H_ +#define MCDAEMON_H_ + + + + +#include "mcuuid.h" + +enum mc_drv_cmd_t { + MC_DRV_CMD_PING = 0, + MC_DRV_CMD_GET_INFO = 1, + MC_DRV_CMD_OPEN_DEVICE = 2, + MC_DRV_CMD_CLOSE_DEVICE = 3, + MC_DRV_CMD_NQ_CONNECT = 4, + MC_DRV_CMD_OPEN_SESSION = 5, + MC_DRV_CMD_CLOSE_SESSION = 6, + MC_DRV_CMD_NOTIFY = 7, + MC_DRV_CMD_MAP_BULK_BUF = 8, + MC_DRV_CMD_UNMAP_BULK_BUF = 9 +}; + + +enum mc_drv_rsp_t { + MC_DRV_RSP_OK = 0, + MC_DRV_RSP_FAILED = 1, + MC_DRV_RSP_DEVICE_NOT_OPENED = 2, + MC_DRV_RSP_DEVICE_ALREADY_OPENED = 3, + MC_DRV_RSP_COMMAND_NOT_ALLOWED = 4, + MC_DRV_INVALID_DEVICE_NAME = 5, + MC_DRV_RSP_MAP_BULK_ERRO = 6, + MC_DRV_RSP_TRUSTLET_NOT_FOUND = 7, + MC_DRV_RSP_PAYLOAD_LENGTH_ERROR = 8, +}; + + +struct mc_drv_command_header_t { + uint32_t command_id; +}; + +struct mc_drv_response_header_t { + uint32_t response_id; +}; + +#define MC_DEVICE_ID_DEFAULT 0 /**< The default device ID */ + + +/*****************************************************************************/ +struct mc_drv_cmd_open_device_payload_t { + uint32_t device_id; +}; + +struct mc_drv_cmd_open_device_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_open_device_payload_t payload; +}; + + +struct mc_drv_rsp_open_device_payload_t { + /* empty */ +}; + +struct mc_drv_rsp_open_device_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_open_device_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_close_device_t { + struct mc_drv_command_header_t header; + /* no payload here because close has none. + If we use an empty struct, C++ will count it as 4 bytes. + This will write too much into the socket at write(cmd,sizeof(cmd)) */ +}; + + +struct mc_drv_rsp_close_device_payload_t { + /* empty */ +}; + +struct mc_drv_rsp_close_device_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_close_device_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_open_session_payload_t { + uint32_t device_id; + struct mc_uuid_t uuid; + uint32_t tci; + uint32_t len; +}; + +struct mc_drv_cmd_open_session_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_open_session_payload_t payload; +}; + + +struct mc_drv_rsp_open_session_payload_t { + uint32_t device_id; + uint32_t session_id; + uint32_t device_session_id; + uint32_t mc_result; + uint32_t session_magic; +}; + +struct mc_drv_rsp_open_session_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_open_session_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_close_session_payload_t { + uint32_t session_id; +}; + +struct mc_drv_cmd_close_session_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_close_session_payload_t payload; +}; + + +struct mc_drv_rsp_close_session_payload_t { + /* empty */ +}; + +struct mc_drv_rsp_close_session_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_close_session_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_notify_payload_t { + uint32_t session_id; +}; + +struct mc_drv_cmd_notify_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_notify_payload_t payload; +}; + + +struct mc_drv_rsp_notify_payload_t { + /* empty */ +}; + +struct mc_drv_rsp_notify_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_notify_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_map_bulk_mem_payload_t { + uint32_t session_id; + uint32_t phys_addr_l2; + uint32_t offset_payload; + uint32_t len_bulk_mem; +}; + +struct mc_drv_cmd_map_bulk_mem_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_map_bulk_mem_payload_t payload; +}; + + +struct mc_drv_rsp_map_bulk_mem_payload_t { + uint32_t session_id; + uint32_t secure_virtual_adr; + uint32_t mc_result; +}; + +struct mc_drv_rsp_map_bulk_mem_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_map_bulk_mem_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_unmap_bulk_mem_payload_t { + uint32_t session_id; + uint32_t secure_virtual_adr; + uint32_t len_bulk_mem; +}; + +struct mc_drv_cmd_unmap_bulk_mem_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_unmap_bulk_mem_payload_t payload; +}; + + +struct mc_drv_rsp_unmap_bulk_mem_payload_t { + uint32_t response_id; + uint32_t session_id; + uint32_t mc_result; +}; + +struct mc_drv_rsp_unmap_bulk_mem_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_unmap_bulk_mem_payload_t payload; +}; + + +/*****************************************************************************/ +struct mc_drv_cmd_nqconnect_payload_t { + uint32_t device_id; + uint32_t session_id; + uint32_t device_session_id; + uint32_t session_magic; /* Random data */ +}; + +struct mc_drv_cmd_nqconnect_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_nqconnect_payload_t payload; +}; + + +struct mc_drv_rsp_nqconnect_payload_t { + /* empty; */ +}; + +struct mc_drv_rsp_nqconnect_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_nqconnect_payload_t payload; +}; + + +/*****************************************************************************/ +union mc_drv_command_t { + struct mc_drv_command_header_t header; + struct mc_drv_cmd_open_device_t mc_drv_cmd_open_device; + struct mc_drv_cmd_close_device_t mc_drv_cmd_close_device; + struct mc_drv_cmd_open_session_t mc_drv_cmd_open_session; + struct mc_drv_cmd_close_session_t mc_drv_cmd_close_session; + struct mc_drv_cmd_nqconnect_t mc_drv_cmd_nqconnect; + struct mc_drv_cmd_notify_t mc_drv_cmd_notify; + struct mc_drv_cmd_map_bulk_mem_t mc_drv_cmd_map_bulk_mem; + struct mc_drv_cmd_unmap_bulk_mem_t mc_drv_cmd_unmap_bulk_mem; +}; + +union mc_drv_response_t { + struct mc_drv_response_header_t header; + struct mc_drv_rsp_open_device_t mc_drv_rsp_open_device; + struct mc_drv_rsp_close_device_t mc_drv_rsp_close_device; + struct mc_drv_rsp_open_session_t mc_drv_rsp_open_session; + struct mc_drv_rsp_close_session_t mc_drv_rsp_close_session; + struct mc_drv_rsp_nqconnect_t mc_drv_rsp_nqconnect; + struct mc_drv_rsp_notify_t mc_drv_rsp_notify; + struct mc_drv_rsp_map_bulk_mem_t mc_drv_rsp_map_bulk_mem; + struct mc_drv_rsp_unmap_bulk_mem_t mc_drv_rsp_unmap_bulk_mem; +}; + +#endif /* MCDAEMON_H_ */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/session.c b/drivers/gud/mobicore_kernelapi/session.c new file mode 100644 index 0000000000000000000000000000000000000000..e62b4b3efc7311fd134499f08c1df1d7b92b2876 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/session.c @@ -0,0 +1,202 @@ +/** @addtogroup MCD_IMPL_LIB + * @{ + * @file + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include "mc_kernel_api.h" +#include "public/mobicore_driver_api.h" + +#include "session.h" + +/*****************************************************************************/ +struct bulk_buffer_descriptor *bulk_buffer_descriptor_create( + void *virt_addr, + uint32_t len, + uint32_t handle, + void *phys_addr_wsm_l2 +) { + struct bulk_buffer_descriptor *desc = + kzalloc(sizeof(struct bulk_buffer_descriptor), GFP_KERNEL); + desc->virt_addr = virt_addr; + desc->len = len; + desc->handle = handle; + desc->phys_addr_wsm_l2 = phys_addr_wsm_l2; + return desc; +} + +/*****************************************************************************/ +struct session *session_create( + uint32_t session_id, + void *instance, + struct connection *connection +) { + struct session *session = + kzalloc(sizeof(struct session), GFP_KERNEL); + session->session_id = session_id; + session->instance = instance; + session->notification_connection = connection; + + session->session_info.last_error = SESSION_ERR_NO; + session->session_info.state = SESSION_STATE_INITIAL; + + INIT_LIST_HEAD(&(session->bulk_buffer_descriptors)); + return session; +} + + +/*****************************************************************************/ +void session_cleanup( + struct session *session +) { + struct bulk_buffer_descriptor *bulk_buf_descr; + struct list_head *pos, *q; + + /* Unmap still mapped buffers */ + list_for_each_safe(pos, q, &session->bulk_buffer_descriptors) { + bulk_buf_descr = + list_entry(pos, struct bulk_buffer_descriptor, list); + + MCDRV_DBG_VERBOSE("Physical Address of L2 Table = 0x%X, " + "handle= %d", + (unsigned int)bulk_buf_descr->phys_addr_wsm_l2, + bulk_buf_descr->handle); + + /* ignore any error, as we cannot do anything in this case. */ + int ret = mobicore_unmap_vmem(session->instance, + bulk_buf_descr->handle); + if (ret != 0) + MCDRV_DBG_ERROR("mobicore_unmap_vmem failed: %d", ret); + + list_del(pos); + kfree(bulk_buf_descr); + } + + /* Finally delete notification connection */ + connection_cleanup(session->notification_connection); + kfree(session); +} + + +/*****************************************************************************/ +void session_set_error_info( + struct session *session, + int32_t err +) { + session->session_info.last_error = err; +} + + +/*****************************************************************************/ +int32_t session_get_last_err( + struct session *session +) { + return session->session_info.last_error; +} + + +/*****************************************************************************/ +struct bulk_buffer_descriptor *session_add_bulk_buf( + struct session *session, + void *buf, + uint32_t len +) { + struct bulk_buffer_descriptor *bulk_buf_descr = NULL; + struct bulk_buffer_descriptor *tmp; + struct list_head *pos; + + /* Search bulk buffer descriptors for existing vAddr + At the moment a virtual address can only be added one time */ + list_for_each(pos, &session->bulk_buffer_descriptors) { + tmp = list_entry(pos, struct bulk_buffer_descriptor, list); + if (tmp->virt_addr == buf) + return NULL; + } + + do { + /* Prepare the interface structure for memory registration in + Kernel Module */ + void *l2_table_phys; + uint32_t handle; + + int ret = mobicore_map_vmem(session->instance, + buf, + len, + &handle, + &l2_table_phys); + + if (ret != 0) { + MCDRV_DBG_ERROR("mobicore_map_vmem failed, ret=%d", + ret); + break; + } + + MCDRV_DBG_VERBOSE("Physical Address of L2 Table = 0x%X, " + "handle=%d", + (unsigned int)l2_table_phys, + handle); + + /* Create new descriptor */ + bulk_buf_descr = bulk_buffer_descriptor_create( + buf, + len, + handle, + l2_table_phys); + + /* Add to vector of descriptors */ + list_add_tail(&(bulk_buf_descr->list), + &(session->bulk_buffer_descriptors)); + } while (0); + + return bulk_buf_descr; +} + + +/*****************************************************************************/ +bool session_remove_bulk_buf( + struct session *session, + void *virt_addr +) { + bool ret = true; + struct bulk_buffer_descriptor *bulk_buf_descr = NULL; + struct bulk_buffer_descriptor *tmp; + struct list_head *pos, *q; + + MCDRV_DBG_VERBOSE("Virtual Address = 0x%X", (unsigned int) virt_addr); + + /* Search and remove bulk buffer descriptor */ + list_for_each_safe(pos, q, &session->bulk_buffer_descriptors) { + tmp = list_entry(pos, struct bulk_buffer_descriptor, list); + if (tmp->virt_addr == virt_addr) { + bulk_buf_descr = tmp; + list_del(pos); + break; + } + } + + if (bulk_buf_descr == NULL) { + MCDRV_DBG_ERROR("Virtual Address not found"); + ret = false; + } else { + MCDRV_DBG_VERBOSE("WsmL2 phys=0x%X, handle=%d", + (unsigned int)bulk_buf_descr->phys_addr_wsm_l2, + bulk_buf_descr->handle); + + /* ignore any error, as we cannot do anything */ + int ret = mobicore_unmap_vmem(session->instance, + bulk_buf_descr->handle); + if (ret != 0) + MCDRV_DBG_ERROR("mobicore_unmap_vmem failed: %d", ret); + + kfree(bulk_buf_descr); + } + + return ret; +} + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/session.h b/drivers/gud/mobicore_kernelapi/session.h new file mode 100644 index 0000000000000000000000000000000000000000..9a5374027335b98cd9642cd6a410cc388836b45e --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/session.h @@ -0,0 +1,136 @@ +/** @addtogroup MCD_IMPL_LIB + * @{ + * @file + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef SESSION_H_ +#define SESSION_H_ + +#include "common.h" + +#include +#include "connection.h" + + +struct bulk_buffer_descriptor { + void *virt_addr;/**< The virtual address of the Bulk buffer*/ + uint32_t len; /**< Length of the Bulk buffer*/ + uint32_t handle; + void *phys_addr_wsm_l2; /**< The physical address of the + L2 table of the Bulk buffer*/ + struct list_head list; /**< The list param for using the kernel lists*/ +}; + +struct bulk_buffer_descriptor *bulk_buffer_descriptor_create( + void *virt_addr, + uint32_t len, + uint32_t handle, + void *phys_addr_wsm_l2 +); + +/** Session states. + * At the moment not used !!. + */ +enum session_state { + SESSION_STATE_INITIAL, + SESSION_STATE_OPEN, + SESSION_STATE_TRUSTLET_DEAD +}; + +#define SESSION_ERR_NO 0 /**< No session error */ + +/** Session information structure. + * The information structure is used to hold the state of the session, which + * will limit further actions for the session. + * Also the last error code will be stored till it's read. + */ +struct session_information { + enum session_state state; /**< Session state */ + int32_t last_error; /**< Last error of session */ +}; + + +struct session { + struct mc_instance *instance; + /**< Descriptors of additional bulk buffer of a session */ + struct list_head bulk_buffer_descriptors; + /**< Informations about session */ + struct session_information session_info; + + uint32_t session_id; + struct connection *notification_connection; + + /**< The list param for using the kernel lists*/ + struct list_head list; +}; + +struct session *session_create( + uint32_t session_id, + void *instance, + struct connection *connection +); + +void session_cleanup( + struct session *session +); + +/** + * Add address information of additional bulk buffer memory to session and + * register virtual memory in kernel module. + * + * @attention The virtual address can only be added one time. If the virtual + * address already exist, NULL is returned. + * + * @param buf The virtual address of bulk buffer. + * @param len Length of bulk buffer. + * + * @return On success the actual Bulk buffer descriptor with all address + * information is retured, NULL if an error occurs. + */ +struct bulk_buffer_descriptor *session_add_bulk_buf( + struct session *session, + void *buf, + uint32_t len +); + +/** + * Remove address information of additional bulk buffer memory from session and + * unregister virtual memory in kernel module + * + * @param buf The virtual address of the bulk buffer. + * + * @return true on success. + */ +bool session_remove_bulk_buf( + struct session *session, + void *buf +); + +/** + * Set additional error information of the last error that occured. + * + * @param errorCode The actual error. + */ +void session_set_error_info( + struct session *session, + int32_t err +); + +/** + * Get additional error information of the last error that occured. + * + * @attention After request the information is set to SESSION_ERR_NO. + * + * @return Last stored error code or SESSION_ERR_NO. + */ +int32_t session_get_last_err( + struct session *session +); + +#endif /* SESSION_H_ */ + +/** @} */ diff --git a/drivers/gud/mobicore_kernelapi/wsm.h b/drivers/gud/mobicore_kernelapi/wsm.h new file mode 100644 index 0000000000000000000000000000000000000000..6877c53768b20684ade977bada00f5591f8f5ef8 --- /dev/null +++ b/drivers/gud/mobicore_kernelapi/wsm.h @@ -0,0 +1,35 @@ +/** @addtogroup MCD_MCDIMPL_DAEMON_SRV + * @{ + * @file + * + * World shared memory definitions. + * + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef WSM_H_ +#define WSM_H_ + +#include "common.h" +#include + +struct wsm { + void *virt_addr; + uint32_t len; + uint32_t handle; + void *phys_addr; + struct list_head list; +}; + +struct wsm *wsm_create( + void *virt_addr, + uint32_t len, + uint32_t handle, + void *phys_addr /*= NULL this may be unknown, so is can be omitted.*/ +); +#endif /* WSM_H_ */ + +/** @} */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8deedc1b98405f8622cca6bff7d826b7114f00fc..b050db29136d46eaa00d3921ed76ea14a737069b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -838,6 +838,36 @@ config SENSORS_NTC_THERMISTOR This driver can also be built as a module. If so, the module will be called ntc-thermistor. +config SENSORS_MSM_ADC + tristate "MSM ADC Driver for current measurement" + depends on ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_FSM9XXX || ARCH_MSM7X27A + 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_PM8XXX_ADC + tristate "Support for Qualcomm PM8XXX ADC" + depends on MFD_PM8XXX + help + This is the ADC arbiter driver for Qualcomm PM8XXX Chip. + + The driver supports reading the HKADC, XOADC and support to set and receive + temperature threshold notifications using the Battery temperature module. + +config SENSORS_EPM_ADC + tristate "EPM ADC Driver for power measurement" + depends on I2C && SPI_MASTER + default n + help + Provides interface for measuring the current on specific power rails + through the channels on ADC1158 ADC + config SENSORS_PC87360 tristate "National Semiconductor PC87360 family" depends on !PPC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 6d3f11f71815356ba3e7f777a4fb4d48d4d6aeca..228c4e98ef8d11bd22605a35bf69348f2c8f480b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -126,6 +126,10 @@ 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 +obj-$(CONFIG_SENSORS_PM8XXX_ADC) += pm8xxx-adc.o pm8xxx-adc-scale.o +obj-$(CONFIG_SENSORS_EPM_ADC) += epm_adc.o obj-$(CONFIG_PMBUS) += pmbus/ diff --git a/drivers/hwmon/epm_adc.c b/drivers/hwmon/epm_adc.c new file mode 100644 index 0000000000000000000000000000000000000000..a8b99b9eb3cac28e6f3134cacf4bd9f3a6015d61 --- /dev/null +++ b/drivers/hwmon/epm_adc.c @@ -0,0 +1,917 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 EPM_ADC_DRIVER_NAME "epm_adc" +#define EPM_ADC_MAX_FNAME 20 +#define EPM_ADC_CONVERSION_DELAY 100 /* milliseconds */ +/* Command Bits */ +#define EPM_ADC_ADS_SPI_BITS_PER_WORD 8 +#define EPM_ADC_ADS_DATA_READ_CMD (0x1 << 5) +#define EPM_ADC_ADS_REG_READ_CMD (0x2 << 5) +#define EPM_ADC_ADS_REG_WRITE_CMD (0x3 << 5) +#define EPM_ADC_ADS_PULSE_CONVERT_CMD (0x4 << 5) +#define EPM_ADC_ADS_MULTIPLE_REG_ACCESS (0x1 << 4) +/* Register map */ +#define EPM_ADC_ADS_CONFIG0_REG_ADDR 0x0 +#define EPM_ADC_ADS_CONFIG1_REG_ADDR 0x1 +#define EPM_ADC_ADS_MUXSG0_REG_ADDR 0x4 +#define EPM_ADC_ADS_MUXSG1_REG_ADDR 0x5 +/* Register map default data */ +#define EPM_ADC_ADS_REG0_DEFAULT 0x2 +#define EPM_ADC_ADS_REG1_DEFAULT 0x52 +#define EPM_ADC_ADS_CHANNEL_DATA_CHID 0x1f +/* Channel ID */ +#define EPM_ADC_ADS_CHANNEL_OFFSET 0x18 +#define EPM_ADC_ADS_CHANNEL_VCC 0x1a +#define EPM_ADC_ADS_CHANNEL_TEMP 0x1b +#define EPM_ADC_ADS_CHANNEL_GAIN 0x1c +#define EPM_ADC_ADS_CHANNEL_REF 0x1d +/* Scaling data co-efficients */ +#define EPM_ADC_SCALE_MILLI 1000 +#define EPM_ADC_SCALE_CODE_VOLTS 3072 +#define EPM_ADC_SCALE_CODE_GAIN 30720 +#define EPM_ADC_TEMP_SENSOR_COEFF 394 +#define EPM_ADC_TEMP_TO_DEGC_COEFF 168000 +#define EPM_ADC_CHANNEL_AIN_OFFSET 8 +#define EPM_ADC_MAX_NEGATIVE_SCALE_CODE 0x8000 +#define EPM_ADC_NEG_LSB_CODE 0xffff +#define EPM_ADC_VREF_CODE 0x7800 +#define EPM_ADC_MILLI_VOLTS_SOURCE 4750 +#define EPM_ADC_SCALE_FACTOR 64 +#define GPIO_EPM_GLOBAL_ENABLE 86 +#define EPM_ADC_CONVERSION_TIME_MIN 50000 +#define EPM_ADC_CONVERSION_TIME_MAX 51000 + +struct epm_adc_drv { + struct platform_device *pdev; + struct device *hwmon; + struct sensor_device_attribute *sens_attr; + char **fnames; + struct spi_device *epm_spi_client; + struct mutex conv_lock; + uint32_t bus_id; + struct miscdevice misc; +}; + +static struct epm_adc_drv *epm_adc_drv; +static struct i2c_board_info *epm_i2c_info; +static bool epm_adc_first_request; +static int epm_gpio_expander_base_addr; +static bool epm_adc_expander_register; + +#define GPIO_EPM_EXPANDER_IO0 epm_gpio_expander_base_addr +#define GPIO_PWR_MON_ENABLE (GPIO_EPM_EXPANDER_IO0 + 1) +#define GPIO_ADC1_PWDN_N (GPIO_PWR_MON_ENABLE + 1) +#define GPIO_PWR_MON_RESET_N (GPIO_ADC1_PWDN_N + 1) +#define GPIO_EPM_SPI_ADC1_CS_N (GPIO_PWR_MON_RESET_N + 1) +#define GPIO_PWR_MON_START (GPIO_EPM_SPI_ADC1_CS_N + 1) +#define GPIO_ADC1_DRDY_N (GPIO_PWR_MON_START + 1) +#define GPIO_ADC2_PWDN_N (GPIO_ADC1_DRDY_N + 1) +#define GPIO_EPM_SPI_ADC2_CS_N (GPIO_ADC2_PWDN_N + 1) +#define GPIO_ADC2_DRDY_N (GPIO_EPM_SPI_ADC2_CS_N + 1) + +static int epm_adc_i2c_expander_register(void) +{ + int rc = 0; + static struct i2c_adapter *i2c_adap; + static struct i2c_client *epm_i2c_client; + + rc = gpio_request(GPIO_EPM_GLOBAL_ENABLE, "EPM_GLOBAL_EN"); + if (!rc) { + gpio_direction_output(GPIO_EPM_GLOBAL_ENABLE, 1); + } else { + pr_err("%s: Configure EPM_GLOBAL_EN Failed\n", __func__); + return rc; + } + + usleep_range(EPM_ADC_CONVERSION_TIME_MIN, + EPM_ADC_CONVERSION_TIME_MAX); + + i2c_adap = i2c_get_adapter(epm_adc_drv->bus_id); + if (i2c_adap == NULL) { + pr_err("%s: i2c_get_adapter() failed\n", __func__); + return -EINVAL; + } + + usleep_range(EPM_ADC_CONVERSION_TIME_MIN, + EPM_ADC_CONVERSION_TIME_MAX); + + epm_i2c_client = i2c_new_device(i2c_adap, epm_i2c_info); + if (IS_ERR(epm_i2c_client)) { + pr_err("Error with i2c epm device register\n"); + return -ENODEV; + } + + epm_adc_first_request = false; + + return 0; +} + +static int epm_adc_gpio_configure_expander_enable(void) +{ + int rc = 0; + + if (epm_adc_first_request) { + rc = gpio_request(GPIO_EPM_GLOBAL_ENABLE, "EPM_GLOBAL_EN"); + if (!rc) { + gpio_direction_output(GPIO_EPM_GLOBAL_ENABLE, 1); + } else { + pr_err("%s: Configure EPM_GLOBAL_EN Failed\n", + __func__); + return rc; + } + } else { + epm_adc_first_request = true; + } + + usleep_range(EPM_ADC_CONVERSION_TIME_MIN, + EPM_ADC_CONVERSION_TIME_MAX); + + rc = gpio_request(GPIO_PWR_MON_ENABLE, "GPIO_PWR_MON_ENABLE"); + if (!rc) { + rc = gpio_direction_output(GPIO_PWR_MON_ENABLE, 1); + if (rc) { + pr_err("%s: Set GPIO_PWR_MON_ENABLE failed\n", + __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_PWR_MON_ENABLE failed\n", + __func__); + return rc; + } + + rc = gpio_request(GPIO_ADC1_PWDN_N, "GPIO_ADC1_PWDN_N"); + if (!rc) { + rc = gpio_direction_output(GPIO_ADC1_PWDN_N, 1); + if (rc) { + pr_err("%s: Set GPIO_ADC1_PWDN_N failed\n", __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_ADC1_PWDN_N failed\n", __func__); + return rc; + } + + rc = gpio_request(GPIO_ADC2_PWDN_N, "GPIO_ADC2_PWDN_N"); + if (!rc) { + rc = gpio_direction_output(GPIO_ADC2_PWDN_N, 1); + if (rc) { + pr_err("%s: Set GPIO_ADC2_PWDN_N failed\n", + __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_ADC2_PWDN_N failed\n", + __func__); + return rc; + } + + rc = gpio_request(GPIO_EPM_SPI_ADC1_CS_N, "GPIO_EPM_SPI_ADC1_CS_N"); + if (!rc) { + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 1); + if (rc) { + pr_err("%s:Set GPIO_EPM_SPI_ADC1_CS_N failed\n", + __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_EPM_SPI_ADC1_CS_N failed\n", + __func__); + return rc; + } + + rc = gpio_request(GPIO_EPM_SPI_ADC2_CS_N, + "GPIO_EPM_SPI_ADC2_CS_N"); + if (!rc) { + rc = gpio_direction_output(GPIO_EPM_SPI_ADC2_CS_N, 1); + if (rc) { + pr_err("%s: Set GPIO_EPM_SPI_ADC2_CS_N " + "failed\n", __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_EPM_SPI_ADC2_CS_N " + "failed\n", __func__); + return rc; + } + + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 0); + if (rc) { + pr_err("%s:Reset GPIO_EPM_SPI_ADC1_CS_N failed\n", __func__); + return rc; + } + + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 1); + if (rc) { + pr_err("%s: Set GPIO_EPM_SPI_ADC1_CS_N failed\n", __func__); + return rc; + } + + rc = gpio_request(GPIO_PWR_MON_START, "GPIO_PWR_MON_START"); + if (!rc) { + rc = gpio_direction_output(GPIO_PWR_MON_START, 0); + if (rc) { + pr_err("%s: Reset GPIO_PWR_MON_START failed\n", + __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_PWR_MON_START failed\n", + __func__); + return rc; + } + + rc = gpio_request(GPIO_PWR_MON_RESET_N, "GPIO_PWR_MON_RESET_N"); + if (!rc) { + rc = gpio_direction_output(GPIO_PWR_MON_RESET_N, 0); + if (rc) { + pr_err("%s: Reset GPIO_PWR_MON_RESET_N failed\n", + __func__); + return rc; + } + } else { + pr_err("%s: gpio_request GPIO_PWR_MON_RESET_N failed\n", + __func__); + return rc; + } + + rc = gpio_direction_output(GPIO_PWR_MON_RESET_N, 1); + if (rc) { + pr_err("%s: Set GPIO_PWR_MON_RESET_N failed\n", __func__); + return rc; + } + + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 0); + if (rc) { + pr_err("%s:Reset GPIO_EPM_SPI_ADC1_CS_N failed\n", __func__); + return rc; + } + return rc; +} + +static int epm_adc_gpio_configure_expander_disable(void) +{ + int rc = 0; + gpio_free(GPIO_PWR_MON_ENABLE); + gpio_free(GPIO_ADC1_PWDN_N); + gpio_free(GPIO_ADC2_PWDN_N); + gpio_free(GPIO_EPM_SPI_ADC1_CS_N); + gpio_free(GPIO_EPM_SPI_ADC2_CS_N); + gpio_free(GPIO_PWR_MON_START); + gpio_free(GPIO_PWR_MON_RESET_N); + rc = gpio_direction_output(GPIO_EPM_GLOBAL_ENABLE, 0); + if (rc) + pr_debug("%s: Disable EPM_GLOBAL_EN Failed\n", __func__); + gpio_free(GPIO_EPM_GLOBAL_ENABLE); + return rc; +} + +static int epm_adc_spi_chip_select(int32_t id) +{ + int rc = 0; + if (id == 0) { + rc = gpio_direction_output(GPIO_EPM_SPI_ADC2_CS_N, 1); + if (rc) { + pr_err("%s:Disable SPI_ADC2_CS failed", + __func__); + return rc; + } + + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 0); + if (rc) { + pr_err("%s:Enable SPI_ADC1_CS failed", __func__); + return rc; + } + } else if (id == 1) { + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 1); + if (rc) { + pr_err("%s:Disable SPI_ADC1_CS failed", __func__); + return rc; + } + rc = gpio_direction_output(GPIO_EPM_SPI_ADC2_CS_N, 0); + if (rc) { + pr_err("%s:Enable SPI_ADC2_CS failed", __func__); + return rc; + } + } else { + rc = -EFAULT; + } + return rc; +} + +static int epm_adc_ads_spi_write(struct epm_adc_drv *epm_adc, + uint8_t addr, uint8_t val) +{ + struct spi_message m; + struct spi_transfer t; + char tx_buf[2]; + int rc = 0; + + spi_setup(epm_adc->epm_spi_client); + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + t.tx_buf = tx_buf; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = EPM_ADC_ADS_REG_WRITE_CMD | addr; + tx_buf[1] = val; + + t.len = sizeof(tx_buf); + t.bits_per_word = EPM_ADC_ADS_SPI_BITS_PER_WORD; + + rc = spi_sync(epm_adc->epm_spi_client, &m); + + return rc; +} + +static int epm_adc_init_ads(struct epm_adc_drv *epm_adc) +{ + int rc = 0; + + rc = epm_adc_ads_spi_write(epm_adc, EPM_ADC_ADS_CONFIG0_REG_ADDR, + EPM_ADC_ADS_REG0_DEFAULT); + if (rc) + return rc; + + rc = epm_adc_ads_spi_write(epm_adc, EPM_ADC_ADS_CONFIG1_REG_ADDR, + EPM_ADC_ADS_REG1_DEFAULT); + if (rc) + return rc; + return rc; +} + +static int epm_adc_ads_pulse_convert(struct epm_adc_drv *epm_adc) +{ + struct spi_message m; + struct spi_transfer t; + char tx_buf[1]; + int rc = 0; + + spi_setup(epm_adc->epm_spi_client); + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + t.tx_buf = tx_buf; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = EPM_ADC_ADS_PULSE_CONVERT_CMD; + t.len = sizeof(tx_buf); + t.bits_per_word = EPM_ADC_ADS_SPI_BITS_PER_WORD; + + rc = spi_sync(epm_adc->epm_spi_client, &m); + + return rc; +} + +static int epm_adc_ads_read_data(struct epm_adc_drv *epm_adc, char *adc_data) +{ + struct spi_message m; + struct spi_transfer t; + char tx_buf[4], rx_buf[4]; + int rc = 0; + + spi_setup(epm_adc->epm_spi_client); + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + memset(rx_buf, 0, sizeof tx_buf); + t.tx_buf = tx_buf; + t.rx_buf = rx_buf; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = EPM_ADC_ADS_DATA_READ_CMD | + EPM_ADC_ADS_MULTIPLE_REG_ACCESS; + + t.len = sizeof(tx_buf); + t.bits_per_word = EPM_ADC_ADS_SPI_BITS_PER_WORD; + + rc = spi_sync(epm_adc->epm_spi_client, &m); + if (rc) + return rc; + + rc = spi_sync(epm_adc->epm_spi_client, &m); + if (rc) + return rc; + + rc = spi_sync(epm_adc->epm_spi_client, &m); + if (rc) + return rc; + + adc_data[0] = rx_buf[1]; + adc_data[1] = rx_buf[2]; + adc_data[2] = rx_buf[3]; + + return rc; +} + +static int epm_adc_hw_init(struct epm_adc_drv *epm_adc) +{ + int rc = 0; + + mutex_lock(&epm_adc->conv_lock); + rc = epm_adc_gpio_configure_expander_enable(); + if (rc != 0) { + pr_err("epm gpio configure expander failed, rc = %d\n", rc); + goto epm_adc_hw_init_err; + } + rc = epm_adc_init_ads(epm_adc); + if (rc) { + pr_err("epm_adc_init_ads failed, rc=%d\n", rc); + goto epm_adc_hw_init_err; + } + +epm_adc_hw_init_err: + mutex_unlock(&epm_adc->conv_lock); + return rc; +} + +static int epm_adc_hw_deinit(struct epm_adc_drv *epm_adc) +{ + int rc = 0; + + mutex_lock(&epm_adc->conv_lock); + rc = epm_adc_gpio_configure_expander_disable(); + if (rc != 0) { + pr_err("epm gpio configure expander disable failed," + " rc = %d\n", rc); + goto epm_adc_hw_deinit_err; + } + +epm_adc_hw_deinit_err: + mutex_unlock(&epm_adc->conv_lock); + return rc; +} + +static int epm_adc_ads_scale_result(struct epm_adc_drv *epm_adc, + uint8_t *adc_raw_data, struct epm_chan_request *conv) +{ + uint32_t channel_num; + int16_t sign_bit; + struct epm_adc_platform_data *pdata = epm_adc->pdev->dev.platform_data; + uint32_t chan_idx = (conv->device_idx * pdata->chan_per_adc) + + conv->channel_idx; + int32_t *adc_scaled_data = &conv->physical; + + /* Get the channel number */ + channel_num = (adc_raw_data[0] & EPM_ADC_ADS_CHANNEL_DATA_CHID); + sign_bit = 1; + /* This is the 16-bit raw data */ + *adc_scaled_data = ((adc_raw_data[1] << 8) | adc_raw_data[2]); + /* Obtain the internal system reading */ + if (channel_num == EPM_ADC_ADS_CHANNEL_VCC) { + *adc_scaled_data *= EPM_ADC_SCALE_MILLI; + *adc_scaled_data /= EPM_ADC_SCALE_CODE_VOLTS; + } else if (channel_num == EPM_ADC_ADS_CHANNEL_GAIN) { + *adc_scaled_data /= EPM_ADC_SCALE_CODE_GAIN; + } else if (channel_num == EPM_ADC_ADS_CHANNEL_REF) { + *adc_scaled_data *= EPM_ADC_SCALE_MILLI; + *adc_scaled_data /= EPM_ADC_SCALE_CODE_VOLTS; + } else if (channel_num == EPM_ADC_ADS_CHANNEL_TEMP) { + /* Convert Code to micro-volts */ + /* Use this formula to get the temperature reading */ + *adc_scaled_data -= EPM_ADC_TEMP_TO_DEGC_COEFF; + *adc_scaled_data /= EPM_ADC_TEMP_SENSOR_COEFF; + } else if (channel_num == EPM_ADC_ADS_CHANNEL_OFFSET) { + /* The offset should be zero */ + pr_debug("%s: ADC Channel Offset\n", __func__); + return -EFAULT; + } else { + channel_num -= EPM_ADC_CHANNEL_AIN_OFFSET; + /* + * Conversion for the adc channels. + * mvVRef is in milli-volts and resistorValue is in micro-ohms. + * Hence, I = V/R gives us current in kilo-amps. + */ + if (*adc_scaled_data & EPM_ADC_MAX_NEGATIVE_SCALE_CODE) { + sign_bit = -1; + *adc_scaled_data = (~*adc_scaled_data + & EPM_ADC_NEG_LSB_CODE); + } + if (*adc_scaled_data != 0) { + *adc_scaled_data *= EPM_ADC_SCALE_FACTOR; + /* Device is calibrated for 1LSB = VREF/7800h.*/ + *adc_scaled_data *= EPM_ADC_MILLI_VOLTS_SOURCE; + *adc_scaled_data /= EPM_ADC_VREF_CODE; + /* Data will now be in micro-volts.*/ + *adc_scaled_data *= EPM_ADC_SCALE_MILLI; + /* Divide by amplifier gain value.*/ + *adc_scaled_data /= pdata->channel[chan_idx].gain; + /* Data will now be in nano-volts.*/ + *adc_scaled_data /= EPM_ADC_SCALE_FACTOR; + *adc_scaled_data *= EPM_ADC_SCALE_MILLI; + /* Data is now in micro-amps.*/ + *adc_scaled_data /= + pdata->channel[chan_idx].resistorValue; + /* Set the sign bit for lekage current. */ + *adc_scaled_data *= sign_bit; + } + } + return 0; +} + +static int epm_adc_blocking_conversion(struct epm_adc_drv *epm_adc, + struct epm_chan_request *conv) +{ + struct epm_adc_platform_data *pdata = epm_adc->pdev->dev.platform_data; + int32_t channel_num = 0, mux_chan_idx = 0; + char adc_data[3]; + int rc = 0; + + mutex_lock(&epm_adc->conv_lock); + + rc = epm_adc_spi_chip_select(conv->device_idx); + if (rc) { + pr_err("epm_adc_chip_select failed, rc=%d\n", rc); + goto conv_err; + } + + if (conv->channel_idx < pdata->chan_per_mux) { + /* Reset MUXSG1_REGISTER */ + rc = epm_adc_ads_spi_write(epm_adc, EPM_ADC_ADS_MUXSG1_REG_ADDR, + 0x0); + if (rc) + goto conv_err; + + mux_chan_idx = 1 << conv->channel_idx; + /* Select Channel index in MUXSG0_REGISTER */ + rc = epm_adc_ads_spi_write(epm_adc, EPM_ADC_ADS_MUXSG0_REG_ADDR, + mux_chan_idx); + if (rc) + goto conv_err; + } else { + /* Reset MUXSG0_REGISTER */ + rc = epm_adc_ads_spi_write(epm_adc, EPM_ADC_ADS_MUXSG0_REG_ADDR, + 0x0); + if (rc) + goto conv_err; + + mux_chan_idx = 1 << (conv->channel_idx - pdata->chan_per_mux); + /* Select Channel index in MUXSG1_REGISTER */ + rc = epm_adc_ads_spi_write(epm_adc, EPM_ADC_ADS_MUXSG1_REG_ADDR, + mux_chan_idx); + if (rc) + goto conv_err; + } + + rc = epm_adc_ads_pulse_convert(epm_adc); + if (rc) { + pr_err("epm_adc_ads_pulse_convert failed, rc=%d\n", rc); + goto conv_err; + } + + rc = epm_adc_ads_read_data(epm_adc, adc_data); + if (rc) { + pr_err("epm_adc_ads_read_data failed, rc=%d\n", rc); + goto conv_err; + } + + channel_num = (adc_data[0] & EPM_ADC_ADS_CHANNEL_DATA_CHID); + pr_debug("ADC data Read: adc_data =%d, %d, %d\n", + adc_data[0], adc_data[1], adc_data[2]); + + epm_adc_ads_scale_result(epm_adc, (uint8_t *)adc_data, conv); + + pr_debug("channel_num(0x) = %x, scaled_data = %d\n", + (channel_num - EPM_ADC_ADS_SPI_BITS_PER_WORD), + conv->physical); +conv_err: + mutex_unlock(&epm_adc->conv_lock); + return rc; +} + +static long epm_adc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct epm_adc_drv *epm_adc = epm_adc_drv; + + switch (cmd) { + case EPM_ADC_REQUEST: + { + struct epm_chan_request conv; + int rc; + + if (copy_from_user(&conv, (void __user *)arg, + sizeof(struct epm_chan_request))) + return -EFAULT; + + rc = epm_adc_blocking_conversion(epm_adc, &conv); + if (rc) { + pr_err("Failed EPM conversion:%d\n", rc); + return rc; + } + + if (copy_to_user((void __user *)arg, &conv, + sizeof(struct epm_chan_request))) + return -EFAULT; + break; + } + case EPM_ADC_INIT: + { + uint32_t result; + if (!epm_adc_expander_register) { + result = epm_adc_i2c_expander_register(); + if (result) { + pr_err("Failed i2c register:%d\n", + result); + return result; + } + epm_adc_expander_register = true; + } + + result = epm_adc_hw_init(epm_adc_drv); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_ADC_DEINIT: + { + uint32_t result; + result = epm_adc_hw_deinit(epm_adc_drv); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +const struct file_operations epm_adc_fops = { + .unlocked_ioctl = epm_adc_ioctl, +}; + +static ssize_t epm_adc_show_in(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct epm_adc_drv *epm_adc = dev_get_drvdata(dev); + struct epm_adc_platform_data *pdata = epm_adc->pdev->dev.platform_data; + struct epm_chan_request conv; + int rc = 0; + + conv.device_idx = attr->index / pdata->chan_per_adc; + conv.channel_idx = attr->index % pdata->chan_per_adc; + conv.physical = 0; + pr_debug("%s: device_idx=%d channel_idx=%d", __func__, conv.device_idx, + conv.channel_idx); + if (!epm_adc_expander_register) { + rc = epm_adc_i2c_expander_register(); + if (rc) { + pr_err("I2C expander register failed:%d\n", rc); + return rc; + } + epm_adc_expander_register = true; + } + + rc = epm_adc_hw_init(epm_adc); + if (rc) { + pr_err("%s: epm_adc_hw_init() failed, rc = %d", + __func__, rc); + return 0; + } + + rc = epm_adc_blocking_conversion(epm_adc, &conv); + if (rc) { + pr_err("%s: epm_adc_blocking_conversion() failed, rc = %d\n", + __func__, rc); + return 0; + } + rc = epm_adc_hw_deinit(epm_adc); + if (rc) { + pr_err("%s: epm_adc_hw_deinit() failed, rc = %d", + __func__, rc); + return 0; + } + + return snprintf(buf, 16, "Result: %d\n", conv.physical); +} + +static struct sensor_device_attribute epm_adc_in_attr = + SENSOR_ATTR(NULL, S_IRUGO, epm_adc_show_in, NULL, 0); + +static int __devinit epm_adc_init_hwmon(struct platform_device *pdev, + struct epm_adc_drv *epm_adc) +{ + struct epm_adc_platform_data *pdata = pdev->dev.platform_data; + int num_chans = pdata->num_channels, dev_idx = 0, chan_idx = 0; + int i = 0, rc = 0; + const char prefix[] = "ads", postfix[] = "_chan"; + char tmpbuf[3]; + + epm_adc->fnames = devm_kzalloc(&pdev->dev, + num_chans * EPM_ADC_MAX_FNAME + + num_chans * sizeof(char *), GFP_KERNEL); + if (!epm_adc->fnames) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + epm_adc->sens_attr = devm_kzalloc(&pdev->dev, num_chans * + sizeof(struct sensor_device_attribute), GFP_KERNEL); + if (!epm_adc->sens_attr) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + rc = -ENOMEM; + } + + for (i = 0; i < num_chans; i++, chan_idx++) { + epm_adc->fnames[i] = (char *)epm_adc->fnames + + (i * EPM_ADC_MAX_FNAME) + (num_chans * + sizeof(char *)); + if (chan_idx == pdata->chan_per_adc) { + chan_idx = 0; + dev_idx++; + } + strlcpy(epm_adc->fnames[i], prefix, EPM_ADC_MAX_FNAME); + snprintf(tmpbuf, sizeof(tmpbuf), "%d", dev_idx); + strlcat(epm_adc->fnames[i], tmpbuf, EPM_ADC_MAX_FNAME); + strlcat(epm_adc->fnames[i], postfix, EPM_ADC_MAX_FNAME); + snprintf(tmpbuf, sizeof(tmpbuf), "%d", chan_idx); + strlcat(epm_adc->fnames[i], tmpbuf, EPM_ADC_MAX_FNAME); + epm_adc_in_attr.index = i; + epm_adc_in_attr.dev_attr.attr.name = epm_adc->fnames[i]; + memcpy(&epm_adc->sens_attr[i], &epm_adc_in_attr, + sizeof(epm_adc_in_attr)); + rc = device_create_file(&pdev->dev, + &epm_adc->sens_attr[i].dev_attr); + if (rc) { + dev_err(&pdev->dev, "device_create_file failed\n"); + return rc; + } + } + + return rc; +} + +static int __devinit epm_adc_spi_probe(struct spi_device *spi) + +{ + if (!epm_adc_drv) + return -ENODEV; + epm_adc_drv->epm_spi_client = spi; + epm_adc_drv->epm_spi_client->bits_per_word = + EPM_ADC_ADS_SPI_BITS_PER_WORD; + + return 0; +} + +static int __devexit epm_adc_spi_remove(struct spi_device *spi) +{ + epm_adc_drv->epm_spi_client = NULL; + return 0; +} + +static struct spi_driver epm_spi_driver = { + .probe = epm_adc_spi_probe, + .remove = __devexit_p(epm_adc_spi_remove), + .driver = { + .name = EPM_ADC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __devinit epm_adc_probe(struct platform_device *pdev) +{ + struct epm_adc_drv *epm_adc; + struct epm_adc_platform_data *pdata = pdev->dev.platform_data; + int rc = 0; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + epm_adc = kzalloc(sizeof(struct epm_adc_drv), GFP_KERNEL); + if (!epm_adc) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, epm_adc); + epm_adc_drv = epm_adc; + epm_adc->pdev = pdev; + + epm_adc->misc.name = EPM_ADC_DRIVER_NAME; + epm_adc->misc.minor = MISC_DYNAMIC_MINOR; + epm_adc->misc.fops = &epm_adc_fops; + + if (misc_register(&epm_adc->misc)) { + dev_err(&pdev->dev, "Unable to register misc device!\n"); + return -EFAULT; + } + + rc = epm_adc_init_hwmon(pdev, epm_adc); + if (rc) { + dev_err(&pdev->dev, "msm_adc_dev_init failed\n"); + misc_deregister(&epm_adc->misc); + return rc; + } + + epm_adc->hwmon = hwmon_device_register(&pdev->dev); + if (IS_ERR(epm_adc->hwmon)) { + dev_err(&pdev->dev, "hwmon_device_register failed\n"); + misc_deregister(&epm_adc->misc); + rc = PTR_ERR(epm_adc->hwmon); + return rc; + } + + mutex_init(&epm_adc->conv_lock); + epm_i2c_info = &pdata->epm_i2c_board_info; + epm_adc->bus_id = pdata->bus_id; + epm_gpio_expander_base_addr = pdata->gpio_expander_base_addr; + epm_adc_expander_register = false; + return rc; +} + +static int __devexit epm_adc_remove(struct platform_device *pdev) +{ + struct epm_adc_drv *epm_adc = platform_get_drvdata(pdev); + struct epm_adc_platform_data *pdata = pdev->dev.platform_data; + int num_chans = pdata->num_channels; + int i = 0; + + if (epm_adc->sens_attr) + for (i = 0; i < num_chans; i++) + device_remove_file(&pdev->dev, + &epm_adc->sens_attr[i].dev_attr); + hwmon_device_unregister(epm_adc->hwmon); + misc_deregister(&epm_adc->misc); + epm_adc = NULL; + + return 0; +} + +static struct platform_driver epm_adc_driver = { + .probe = epm_adc_probe, + .remove = __devexit_p(epm_adc_remove), + .driver = { + .name = EPM_ADC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init epm_adc_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&epm_adc_driver); + if (ret) { + pr_err("%s: driver register failed, rc=%d\n", __func__, ret); + return ret; + } + + ret = spi_register_driver(&epm_spi_driver); + if (ret) + pr_err("%s: spi register failed: rc=%d\n", __func__, ret); + + return ret; +} + +static void __exit epm_adc_exit(void) +{ + spi_unregister_driver(&epm_spi_driver); + platform_driver_unregister(&epm_adc_driver); +} + +module_init(epm_adc_init); +module_exit(epm_adc_exit); + +MODULE_DESCRIPTION("EPM ADC Driver"); +MODULE_ALIAS("platform:epm_adc"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/m_adcproc.c b/drivers/hwmon/m_adcproc.c new file mode 100644 index 0000000000000000000000000000000000000000..70e505e734e1be4e61b7778fe2511107ae644dae --- /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 0000000000000000000000000000000000000000..018ce79c58b046abac3517e31b71b3118ee7d30e --- /dev/null +++ b/drivers/hwmon/msm_adc.c @@ -0,0 +1,1533 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 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 */ + +#define MSM_8x25_ADC_DEV_ID 0 +#define MSM_8x25_CHAN_ID 16 + +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 bool conv_first_request; + +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]; + int ret = 0; + + if (conv_first_request) { + ret = pm8058_xoadc_calib_device(channel->adc_dev_instance); + if (ret) { + pr_err("pmic8058 xoadc calibration failed, retry\n"); + return ret; + } + conv_first_request = false; + } + + 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; + int ret; + + if (conv_first_request) { + ret = pm8058_xoadc_calib_device(channel->adc_dev_instance); + if (ret) { + pr_err("pmic8058 xoadc calibration failed, retry\n"); + return ret; + } + conv_first_request = false; + } + + 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; +} + +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; + } + + if (!pdata->target_hw == MSM_8x25) { + /* 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->nchans = target.dal.chan_idx; + } else { + /* On targets prior to MSM7x30 the remote driver has + only the channel list and no device id. */ + adc_dev->transl.dal_dev_idx = MSM_8x25_ADC_DEV_ID; + adc_dev->nchans = MSM_8x25_CHAN_ID; + } + + adc_dev->transl.hwmon_dev_idx = i; + 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 __devinit 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); + } + conv_first_request = true; + + 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/pm8xxx-adc-scale.c b/drivers/hwmon/pm8xxx-adc-scale.c new file mode 100644 index 0000000000000000000000000000000000000000..fb2f1d5cd912e133fb6f3cb09ca48d812de48b61 --- /dev/null +++ b/drivers/hwmon/pm8xxx-adc-scale.c @@ -0,0 +1,738 @@ +/* + * 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 + +/* Units for temperature below (on x axis) is in 0.1DegC as + required by the battery driver. Note the resolution used + here to compute the table was done for DegC to milli-volts. + In consideration to limit the size of the table for the given + temperature range below, the result is linearly interpolated + and provided to the battery driver in the units desired for + their framework which is 0.1DegC. True resolution of 0.1DegC + will result in the below table size to increase by 10 times */ +static const struct pm8xxx_adc_map_pt adcmap_btm_threshold[] = { + {-300, 1642}, + {-200, 1544}, + {-100, 1414}, + {0, 1260}, + {10, 1244}, + {20, 1228}, + {30, 1212}, + {40, 1195}, + {50, 1179}, + {60, 1162}, + {70, 1146}, + {80, 1129}, + {90, 1113}, + {100, 1097}, + {110, 1080}, + {120, 1064}, + {130, 1048}, + {140, 1032}, + {150, 1016}, + {160, 1000}, + {170, 985}, + {180, 969}, + {190, 954}, + {200, 939}, + {210, 924}, + {220, 909}, + {230, 894}, + {240, 880}, + {250, 866}, + {260, 852}, + {270, 838}, + {280, 824}, + {290, 811}, + {300, 798}, + {310, 785}, + {320, 773}, + {330, 760}, + {340, 748}, + {350, 736}, + {360, 725}, + {370, 713}, + {380, 702}, + {390, 691}, + {400, 681}, + {410, 670}, + {420, 660}, + {430, 650}, + {440, 640}, + {450, 631}, + {460, 622}, + {470, 613}, + {480, 604}, + {490, 595}, + {500, 587}, + {510, 579}, + {520, 571}, + {530, 563}, + {540, 556}, + {550, 548}, + {560, 541}, + {570, 534}, + {580, 527}, + {590, 521}, + {600, 514}, + {610, 508}, + {620, 502}, + {630, 496}, + {640, 490}, + {650, 485}, + {660, 281}, + {670, 274}, + {680, 267}, + {690, 260}, + {700, 254}, + {710, 247}, + {720, 241}, + {730, 235}, + {740, 229}, + {750, 224}, + {760, 218}, + {770, 213}, + {780, 208}, + {790, 203} +}; + +static const struct pm8xxx_adc_map_pt adcmap_pa_therm[] = { + {1677, -30}, + {1671, -29}, + {1663, -28}, + {1656, -27}, + {1648, -26}, + {1640, -25}, + {1632, -24}, + {1623, -23}, + {1615, -22}, + {1605, -21}, + {1596, -20}, + {1586, -19}, + {1576, -18}, + {1565, -17}, + {1554, -16}, + {1543, -15}, + {1531, -14}, + {1519, -13}, + {1507, -12}, + {1494, -11}, + {1482, -10}, + {1468, -9}, + {1455, -8}, + {1441, -7}, + {1427, -6}, + {1412, -5}, + {1398, -4}, + {1383, -3}, + {1367, -2}, + {1352, -1}, + {1336, 0}, + {1320, 1}, + {1304, 2}, + {1287, 3}, + {1271, 4}, + {1254, 5}, + {1237, 6}, + {1219, 7}, + {1202, 8}, + {1185, 9}, + {1167, 10}, + {1149, 11}, + {1131, 12}, + {1114, 13}, + {1096, 14}, + {1078, 15}, + {1060, 16}, + {1042, 17}, + {1024, 18}, + {1006, 19}, + {988, 20}, + {970, 21}, + {952, 22}, + {934, 23}, + {917, 24}, + {899, 25}, + {882, 26}, + {865, 27}, + {848, 28}, + {831, 29}, + {814, 30}, + {797, 31}, + {781, 32}, + {764, 33}, + {748, 34}, + {732, 35}, + {717, 36}, + {701, 37}, + {686, 38}, + {671, 39}, + {656, 40}, + {642, 41}, + {627, 42}, + {613, 43}, + {599, 44}, + {586, 45}, + {572, 46}, + {559, 47}, + {546, 48}, + {534, 49}, + {522, 50}, + {509, 51}, + {498, 52}, + {486, 53}, + {475, 54}, + {463, 55}, + {452, 56}, + {442, 57}, + {431, 58}, + {421, 59}, + {411, 60}, + {401, 61}, + {392, 62}, + {383, 63}, + {374, 64}, + {365, 65}, + {356, 66}, + {348, 67}, + {339, 68}, + {331, 69}, + {323, 70}, + {316, 71}, + {308, 72}, + {301, 73}, + {294, 74}, + {287, 75}, + {280, 76}, + {273, 77}, + {267, 78}, + {261, 79}, + {255, 80}, + {249, 81}, + {243, 82}, + {237, 83}, + {232, 84}, + {226, 85}, + {221, 86}, + {216, 87}, + {211, 88}, + {206, 89}, + {201, 90} +}; + +static const struct pm8xxx_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 pm8xxx_adc_map_linear(const struct pm8xxx_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; +} + +static int32_t pm8xxx_adc_map_batt_therm(const struct pm8xxx_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].y < pts[1].y) + descending = 0; + } + + while (i < tablesize) { + if ((descending == 1) && (pts[i].y < input)) { + /* table entry is less than measured + value and table is descending, stop */ + break; + } else if ((descending == 0) && (pts[i].y > input)) { + /* table entry is greater than measured + value and table is ascending, stop */ + break; + } else { + i++; + } + } + + if (i == 0) { + *output = pts[0].x; + } else if (i == tablesize) { + *output = pts[tablesize-1].x; + } else { + /* result is between search_index and search_index-1 */ + /* interpolate linearly */ + *output = (((int32_t) ((pts[i].x - pts[i-1].x)* + (input - pts[i-1].y))/ + (pts[i].y - pts[i-1].y))+ + pts[i-1].x); + } + + return 0; +} + +int32_t pm8xxx_adc_scale_default(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties, + struct pm8xxx_adc_chan_result *adc_chan_result) +{ + bool negative_rawfromoffset = 0, negative_offset = 0; + int64_t scale_voltage = 0; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + scale_voltage = (adc_code - + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].adc_gnd) + * chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx; + if (scale_voltage < 0) { + negative_offset = 1; + scale_voltage = -scale_voltage; + } + do_div(scale_voltage, + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dy); + if (negative_offset) + scale_voltage = -scale_voltage; + scale_voltage += chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx; + + if (scale_voltage < 0) { + if (adc_properties->bipolar) { + scale_voltage = -scale_voltage; + negative_rawfromoffset = 1; + } else { + scale_voltage = 0; + } + } + + adc_chan_result->measurement = scale_voltage * + chan_properties->offset_gain_denominator; + + /* do_div only perform positive integer division! */ + do_div(adc_chan_result->measurement, + 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 = adc_chan_result->measurement; + + return 0; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_default); + +static int64_t pm8xxx_adc_scale_ratiometric_calib(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties) +{ + int64_t adc_voltage = 0; + bool negative_offset = 0; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties) + return -EINVAL; + + adc_voltage = (adc_code - + chan_properties->adc_graph[ADC_CALIB_RATIOMETRIC].adc_gnd) + * adc_properties->adc_vdd_reference; + if (adc_voltage < 0) { + negative_offset = 1; + adc_voltage = -adc_voltage; + } + do_div(adc_voltage, + chan_properties->adc_graph[ADC_CALIB_RATIOMETRIC].dy); + if (negative_offset) + adc_voltage = -adc_voltage; + + return adc_voltage; +} + +int32_t pm8xxx_adc_scale_batt_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties, + struct pm8xxx_adc_chan_result *adc_chan_result) +{ + int64_t bat_voltage = 0; + + bat_voltage = pm8xxx_adc_scale_ratiometric_calib(adc_code, + adc_properties, chan_properties); + + return pm8xxx_adc_map_batt_therm( + adcmap_btm_threshold, + ARRAY_SIZE(adcmap_btm_threshold), + bat_voltage, + &adc_chan_result->physical); +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_batt_therm); + +int32_t pm8xxx_adc_scale_pa_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties, + struct pm8xxx_adc_chan_result *adc_chan_result) +{ + int64_t pa_voltage = 0; + + pa_voltage = pm8xxx_adc_scale_ratiometric_calib(adc_code, + adc_properties, chan_properties); + + return pm8xxx_adc_map_linear( + adcmap_pa_therm, + ARRAY_SIZE(adcmap_pa_therm), + pa_voltage, + &adc_chan_result->physical); +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_pa_therm); + +int32_t pm8xxx_adc_scale_batt_id(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties, + struct pm8xxx_adc_chan_result *adc_chan_result) +{ + int64_t batt_id_voltage = 0; + + batt_id_voltage = pm8xxx_adc_scale_ratiometric_calib(adc_code, + adc_properties, chan_properties); + adc_chan_result->physical = batt_id_voltage; + adc_chan_result->physical = adc_chan_result->measurement; + + return 0; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_batt_id); + +int32_t pm8xxx_adc_scale_pmic_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties, + struct pm8xxx_adc_chan_result *adc_chan_result) +{ + int64_t pmic_voltage = 0; + bool negative_offset = 0; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + pmic_voltage = (adc_code - + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].adc_gnd) + * chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx; + if (pmic_voltage < 0) { + negative_offset = 1; + pmic_voltage = -pmic_voltage; + } + do_div(pmic_voltage, + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dy); + if (negative_offset) + pmic_voltage = -pmic_voltage; + pmic_voltage += chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx; + + if (pmic_voltage > 0) { + /* 2mV/K */ + adc_chan_result->measurement = pmic_voltage* + chan_properties->offset_gain_denominator; + + do_div(adc_chan_result->measurement, + chan_properties->offset_gain_numerator * 2); + } else { + adc_chan_result->measurement = 0; + } + /* Change to .001 deg C */ + adc_chan_result->measurement -= KELVINMIL_DEGMIL; + adc_chan_result->physical = (int32_t)adc_chan_result->measurement; + + return 0; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_pmic_therm); + +/* Scales the ADC code to 0.001 degrees C using the map + * table for the XO thermistor. + */ +int32_t pm8xxx_adc_tdkntcg_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties, + struct pm8xxx_adc_chan_result *adc_chan_result) +{ + int64_t xo_thm = 0; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + xo_thm = pm8xxx_adc_scale_ratiometric_calib(adc_code, + adc_properties, chan_properties); + xo_thm <<= 4; + pm8xxx_adc_map_linear(adcmap_ntcg_104ef_104fb, + ARRAY_SIZE(adcmap_ntcg_104ef_104fb), + xo_thm, &adc_chan_result->physical); + + return 0; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_tdkntcg_therm); + +int32_t pm8xxx_adc_batt_scaler(struct pm8xxx_adc_arb_btm_param *btm_param, + const struct pm8xxx_adc_properties *adc_properties, + const struct pm8xxx_adc_chan_properties *chan_properties) +{ + int rc; + + rc = pm8xxx_adc_map_linear( + adcmap_btm_threshold, + ARRAY_SIZE(adcmap_btm_threshold), + (btm_param->low_thr_temp), + &btm_param->low_thr_voltage); + if (rc) + return rc; + + btm_param->low_thr_voltage *= + chan_properties->adc_graph[ADC_CALIB_RATIOMETRIC].dy; + do_div(btm_param->low_thr_voltage, adc_properties->adc_vdd_reference); + btm_param->low_thr_voltage += + chan_properties->adc_graph[ADC_CALIB_RATIOMETRIC].adc_gnd; + + rc = pm8xxx_adc_map_linear( + adcmap_btm_threshold, + ARRAY_SIZE(adcmap_btm_threshold), + (btm_param->high_thr_temp), + &btm_param->high_thr_voltage); + if (rc) + return rc; + + btm_param->high_thr_voltage *= + chan_properties->adc_graph[ADC_CALIB_RATIOMETRIC].dy; + do_div(btm_param->high_thr_voltage, adc_properties->adc_vdd_reference); + btm_param->high_thr_voltage += + chan_properties->adc_graph[ADC_CALIB_RATIOMETRIC].adc_gnd; + + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_batt_scaler); diff --git a/drivers/hwmon/pm8xxx-adc.c b/drivers/hwmon/pm8xxx-adc.c new file mode 100644 index 0000000000000000000000000000000000000000..6bef3d3ce5b7b42105667e0160c5dcb20b5903b5 --- /dev/null +++ b/drivers/hwmon/pm8xxx-adc.c @@ -0,0 +1,1307 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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/PM8018 ADC Arbiter driver + */ +#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 + +/* User Bank register set */ +#define PM8XXX_ADC_ARB_USRP_CNTRL1 0x197 +#define PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB BIT(0) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV1 BIT(1) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV2 BIT(2) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV3 BIT(3) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV4 BIT(4) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV5 BIT(5) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_EOC BIT(6) +#define PM8XXX_ADC_ARB_USRP_CNTRL1_REQ BIT(7) + +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL 0x198 +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_PREMUX0 BIT(2) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_PREMUX1 BIT(3) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6) +#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7) + +#define PM8XXX_ADC_ARB_USRP_ANA_PARAM 0x199 +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM 0x19A +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_EOC BIT(4) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6) +#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_EN BIT(7) + +#define PM8XXX_ADC_ARB_USRP_RSV 0x19B +#define PM8XXX_ADC_ARB_USRP_RSV_RST BIT(0) +#define PM8XXX_ADC_ARB_USRP_RSV_DTEST0 BIT(1) +#define PM8XXX_ADC_ARB_USRP_RSV_DTEST1 BIT(2) +#define PM8XXX_ADC_ARB_USRP_RSV_OP BIT(3) +#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL0 BIT(4) +#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL1 BIT(5) +#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL2 BIT(6) +#define PM8XXX_ADC_ARB_USRP_RSV_TRM BIT(7) + +#define PM8XXX_ADC_ARB_USRP_DATA0 0x19D +#define PM8XXX_ADC_ARB_USRP_DATA1 0x19C + +#define PM8XXX_ADC_ARB_BTM_CNTRL1 0x17e +#define PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_EOC BIT(6) +#define PM8XXX_ADC_ARB_BTM_CNTRL1_REQ BIT(7) + +#define PM8XXX_ADC_ARB_BTM_CNTRL2 0x18c +#define PM8XXX_ADC_ARB_BTM_AMUX_CNTRL 0x17f +#define PM8XXX_ADC_ARB_BTM_ANA_PARAM 0x180 +#define PM8XXX_ADC_ARB_BTM_DIG_PARAM 0x181 +#define PM8XXX_ADC_ARB_BTM_RSV 0x182 +#define PM8XXX_ADC_ARB_BTM_DATA1 0x183 +#define PM8XXX_ADC_ARB_BTM_DATA0 0x184 +#define PM8XXX_ADC_ARB_BTM_BAT_COOL_THR1 0x185 +#define PM8XXX_ADC_ARB_BTM_BAT_COOL_THR0 0x186 +#define PM8XXX_ADC_ARB_BTM_BAT_WARM_THR1 0x187 +#define PM8XXX_ADC_ARB_BTM_BAT_WARM_THR0 0x188 + +#define PM8XXX_ADC_ARB_ANA_DIG 0xa0 +#define PM8XXX_ADC_BTM_RSV 0x10 +#define PM8XXX_ADC_AMUX_MPP_SEL 2 +#define PM8XXX_ADC_AMUX_SEL 4 +#define PM8XXX_ADC_RSV_IP_SEL 4 +#define PM8XXX_ADC_BTM_CHANNEL_SEL 4 +#define PM8XXX_MAX_CHANNEL_PROPERTIES 2 +#define PM8XXX_ADC_IRQ_0 0 +#define PM8XXX_ADC_IRQ_1 1 +#define PM8XXX_ADC_IRQ_2 2 +#define PM8XXX_ADC_BTM_INTERVAL_SEL_MASK 0xF +#define PM8XXX_ADC_BTM_INTERVAL_SEL_SHIFT 2 +#define PM8XXX_ADC_BTM_DECIMATION_SEL 5 +#define PM8XXX_ADC_MUL 10 +#define PM8XXX_ADC_CONV_TIME_MIN 2000 +#define PM8XXX_ADC_CONV_TIME_MAX 2100 +#define PM8XXX_ADC_MPP_SETTLE_TIME_MIN 200 +#define PM8XXX_ADC_MPP_SETTLE_TIME_MAX 200 +#define PM8XXX_ADC_PA_THERM_VREG_UV_MIN 1800000 +#define PM8XXX_ADC_PA_THERM_VREG_UV_MAX 1800000 +#define PM8XXX_ADC_PA_THERM_VREG_UA_LOAD 100000 +#define PM8XXX_ADC_HWMON_NAME_LENGTH 32 +#define PM8XXX_ADC_BTM_INTERVAL_MAX 0x14 + +struct pm8xxx_adc { + struct device *dev; + struct pm8xxx_adc_properties *adc_prop; + int adc_irq; + struct mutex adc_lock; + struct mutex mpp_adc_lock; + spinlock_t btm_lock; + uint32_t adc_num_board_channel; + struct completion adc_rslt_completion; + struct pm8xxx_adc_amux *adc_channel; + int btm_warm_irq; + int btm_cool_irq; + struct dentry *dent; + struct work_struct warm_work; + struct work_struct cool_work; + uint32_t mpp_base; + struct device *hwmon; + struct wake_lock adc_wakelock; + int msm_suspend_check; + struct pm8xxx_adc_amux_properties *conv; + struct pm8xxx_adc_arb_btm_param batt; + struct sensor_device_attribute sens_attr[0]; +}; + +struct pm8xxx_adc_amux_properties { + uint32_t amux_channel; + uint32_t decimation; + uint32_t amux_ip_rsv; + uint32_t amux_mpp_channel; + struct pm8xxx_adc_chan_properties chan_prop[0]; +}; + +static const struct pm8xxx_adc_scaling_ratio pm8xxx_amux_scaling_ratio[] = { + {1, 1}, + {1, 3}, + {1, 4}, + {1, 6} +}; + +static struct pm8xxx_adc *pmic_adc; +static struct regulator *pa_therm; + +static struct pm8xxx_adc_scale_fn adc_scale_fn[] = { + [ADC_SCALE_DEFAULT] = {pm8xxx_adc_scale_default}, + [ADC_SCALE_BATT_THERM] = {pm8xxx_adc_scale_batt_therm}, + [ADC_SCALE_PA_THERM] = {pm8xxx_adc_scale_pa_therm}, + [ADC_SCALE_PMIC_THERM] = {pm8xxx_adc_scale_pmic_therm}, + [ADC_SCALE_XOTHERM] = {pm8xxx_adc_tdkntcg_therm}, +}; + +/* On PM8921 ADC the MPP needs to first be configured +as an analog input to the AMUX pre-mux channel before +issuing a read request. PM8921 MPP 8 is mapped to AMUX8 +and is common between remote processor's. +On PM8018 ADC the MPP is directly connected to the AMUX +pre-mux. Therefore clients of the PM8018 MPP do not need +to configure the MPP as an analog input to the pre-mux. +Clients can directly issue request on the pre-mux AMUX +channel to read the ADC on the MPP */ +static struct pm8xxx_mpp_config_data pm8xxx_adc_mpp_config = { + .type = PM8XXX_MPP_TYPE_A_INPUT, + /* AMUX6 is dedicated to be used for apps processor */ + .level = PM8XXX_MPP_AIN_AMUX_CH6, + .control = PM8XXX_MPP_AOUT_CTRL_DISABLE, +}; + +/* MPP Configuration for default settings */ +static struct pm8xxx_mpp_config_data pm8xxx_adc_mpp_unconfig = { + .type = PM8XXX_MPP_TYPE_SINK, + .level = PM8XXX_MPP_AIN_AMUX_CH5, + .control = PM8XXX_MPP_AOUT_CTRL_DISABLE, +}; + +static bool pm8xxx_adc_calib_first_adc; +static bool pm8xxx_adc_initialized, pm8xxx_adc_calib_device_init; + +static int32_t pm8xxx_adc_check_channel_valid(uint32_t channel) +{ + if (channel < CHANNEL_VCOIN || + (channel > CHANNEL_MUXOFF && channel < ADC_MPP_1_ATEST_8) || + (channel > ADC_MPP_1_ATEST_7 && channel < ADC_MPP_2_ATEST_8) + || (channel >= ADC_CHANNEL_MAX_NUM)) + return -EBADF; + else + return 0; +} + +static int32_t pm8xxx_adc_arb_cntrl(uint32_t arb_cntrl, + uint32_t channel) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int i, rc; + u8 data_arb_cntrl = 0; + + if (arb_cntrl) { + if (adc_pmic->msm_suspend_check) + pr_err("PM8xxx ADC request made after suspend_noirq " + "with channel: %d\n", channel); + data_arb_cntrl |= PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB; + wake_lock(&adc_pmic->adc_wakelock); + } + + /* 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, + PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl); + if (rc < 0) { + pr_err("PM8xxx arb cntrl write failed with %d\n", rc); + return rc; + } + } + + if (arb_cntrl) { + data_arb_cntrl |= PM8XXX_ADC_ARB_USRP_CNTRL1_REQ; + INIT_COMPLETION(adc_pmic->adc_rslt_completion); + rc = pm8xxx_writeb(adc_pmic->dev->parent, + PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl); + } else + wake_unlock(&adc_pmic->adc_wakelock); + + return 0; +} + +static int32_t pm8xxx_adc_patherm_power(bool on) +{ + int rc = 0; + + if (!pa_therm) { + pr_err("pm8xxx adc pa_therm not valid\n"); + return -EINVAL; + } + + if (on) { + rc = regulator_set_voltage(pa_therm, + PM8XXX_ADC_PA_THERM_VREG_UV_MIN, + PM8XXX_ADC_PA_THERM_VREG_UV_MAX); + if (rc < 0) { + pr_err("failed to set the voltage for " + "pa_therm with error %d\n", rc); + return rc; + } + + rc = regulator_set_optimum_mode(pa_therm, + PM8XXX_ADC_PA_THERM_VREG_UA_LOAD); + if (rc < 0) { + pr_err("failed to set optimum mode for " + "pa_therm with error %d\n", rc); + return rc; + } + + rc = regulator_enable(pa_therm); + if (rc < 0) { + pr_err("failed to enable pa_therm vreg " + "with error %d\n", rc); + return rc; + } + } else { + rc = regulator_disable(pa_therm); + if (rc < 0) { + pr_err("failed to disable pa_therm vreg " + "with error %d\n", rc); + return rc; + } + } + + return rc; +} + +static int32_t pm8xxx_adc_channel_power_enable(uint32_t channel, + bool power_cntrl) +{ + int rc = 0; + + switch (channel) + case ADC_MPP_1_AMUX8: + rc = pm8xxx_adc_patherm_power(power_cntrl); + + return rc; +} + + +static uint32_t pm8xxx_adc_read_reg(uint32_t reg, u8 *data) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int rc; + + rc = pm8xxx_readb(adc_pmic->dev->parent, reg, data); + if (rc < 0) { + pr_err("PM8xxx adc read reg %d failed with %d\n", reg, rc); + return rc; + } + + return 0; +} + +static uint32_t pm8xxx_adc_write_reg(uint32_t reg, u8 data) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int rc; + + rc = pm8xxx_writeb(adc_pmic->dev->parent, reg, data); + if (rc < 0) { + pr_err("PM8xxx adc write reg %d failed with %d\n", reg, rc); + return rc; + } + + return 0; +} + +static int32_t pm8xxx_adc_configure( + struct pm8xxx_adc_amux_properties *chan_prop) +{ + u8 data_amux_chan = 0, data_arb_rsv = 0, data_dig_param = 0; + int rc; + + data_amux_chan |= chan_prop->amux_channel << PM8XXX_ADC_AMUX_SEL; + + if (chan_prop->amux_mpp_channel) + data_amux_chan |= chan_prop->amux_mpp_channel << + PM8XXX_ADC_AMUX_MPP_SEL; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_AMUX_CNTRL, + data_amux_chan); + if (rc < 0) + return rc; + + rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_RSV, &data_arb_rsv); + if (rc < 0) + return rc; + + data_arb_rsv &= (PM8XXX_ADC_ARB_USRP_RSV_RST | + PM8XXX_ADC_ARB_USRP_RSV_DTEST0 | + PM8XXX_ADC_ARB_USRP_RSV_DTEST1 | + PM8XXX_ADC_ARB_USRP_RSV_OP); + data_arb_rsv |= (chan_prop->amux_ip_rsv << PM8XXX_ADC_RSV_IP_SEL | + PM8XXX_ADC_ARB_USRP_RSV_TRM); + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_RSV, data_arb_rsv); + if (rc < 0) + return rc; + + rc = pm8xxx_adc_read_reg(PM8XXX_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 |= PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0; + break; + case ADC_DECIMATION_TYPE2: + data_dig_param |= (PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 + | PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1); + break; + default: + data_dig_param |= PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0; + break; + } + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_DIG_PARAM, + PM8XXX_ADC_ARB_ANA_DIG); + if (rc < 0) + return rc; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_ANA_PARAM, + PM8XXX_ADC_ARB_ANA_DIG); + if (rc < 0) + return rc; + + rc = pm8xxx_adc_arb_cntrl(1, data_amux_chan); + if (rc < 0) { + pr_err("Configuring ADC Arbiter" + "enable failed with %d\n", rc); + return rc; + } + + return 0; +} + +static uint32_t pm8xxx_adc_read_adc_code(int32_t *data) +{ + struct pm8xxx_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, + PM8XXX_ADC_ARB_USRP_DATA0, &rslt_lsb); + if (rc < 0) { + pr_err("PM8xxx adc result read failed with %d\n", rc); + return rc; + } + + rc = pm8xxx_readb(adc_pmic->dev->parent, + PM8XXX_ADC_ARB_USRP_DATA1, &rslt_msb); + if (rc < 0) { + pr_err("PM8xxx 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 = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE); + if (rc < 0) { + pr_err("%s: Configuring ADC Arbiter disable" + "failed\n", __func__); + return rc; + } + + return 0; +} + +static void pm8xxx_adc_btm_warm_scheduler_fn(struct work_struct *work) +{ + struct pm8xxx_adc *adc_pmic = container_of(work, struct pm8xxx_adc, + warm_work); + unsigned long flags = 0; + bool warm_status; + + spin_lock_irqsave(&adc_pmic->btm_lock, flags); + warm_status = irq_read_line(adc_pmic->btm_warm_irq); + if (adc_pmic->batt.btm_warm_fn != NULL) + adc_pmic->batt.btm_warm_fn(warm_status); + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); +} + +static void pm8xxx_adc_btm_cool_scheduler_fn(struct work_struct *work) +{ + struct pm8xxx_adc *adc_pmic = container_of(work, struct pm8xxx_adc, + cool_work); + unsigned long flags = 0; + bool cool_status; + + spin_lock_irqsave(&adc_pmic->btm_lock, flags); + cool_status = irq_read_line(adc_pmic->btm_cool_irq); + if (adc_pmic->batt.btm_cool_fn != NULL) + adc_pmic->batt.btm_cool_fn(cool_status); + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); +} + +void trigger_completion(struct work_struct *work) +{ + struct pm8xxx_adc *adc_8xxx = pmic_adc; + + complete(&adc_8xxx->adc_rslt_completion); +} +DECLARE_WORK(trigger_completion_work, trigger_completion); + +static irqreturn_t pm8xxx_adc_isr(int irq, void *dev_id) +{ + + if (pm8xxx_adc_calib_first_adc) + return IRQ_HANDLED; + + schedule_work(&trigger_completion_work); + + return IRQ_HANDLED; +} + +static irqreturn_t pm8xxx_btm_warm_isr(int irq, void *dev_id) +{ + struct pm8xxx_adc *btm_8xxx = dev_id; + + schedule_work(&btm_8xxx->warm_work); + + return IRQ_HANDLED; +} + +static irqreturn_t pm8xxx_btm_cool_isr(int irq, void *dev_id) +{ + struct pm8xxx_adc *btm_8xxx = dev_id; + + schedule_work(&btm_8xxx->cool_work); + + return IRQ_HANDLED; +} + +static uint32_t pm8xxx_adc_calib_device(void) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + struct pm8xxx_adc_amux_properties conv; + int rc, 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; + pm8xxx_adc_calib_first_adc = true; + rc = pm8xxx_adc_configure(&conv); + if (rc) { + pr_err("pm8xxx_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC | + PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8XXX_ADC_CONV_TIME_MIN, + PM8XXX_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8xxx_adc_read_adc_code(&calib_read_1); + if (rc) { + pr_err("pm8xxx_adc read adc failed with %d\n", rc); + pm8xxx_adc_calib_first_adc = false; + goto calib_fail; + } + pm8xxx_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; + pm8xxx_adc_calib_first_adc = true; + rc = pm8xxx_adc_configure(&conv); + if (rc) { + pr_err("pm8xxx_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC | + PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8XXX_ADC_CONV_TIME_MIN, + PM8XXX_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8xxx_adc_read_adc_code(&calib_read_2); + if (rc) { + pr_err("pm8xxx_adc read adc failed with %d\n", rc); + pm8xxx_adc_calib_first_adc = false; + goto calib_fail; + } + pm8xxx_adc_calib_first_adc = false; + + 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 + = PM8XXX_CHANNEL_ADC_625_UV; + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].adc_vref = + calib_read_1; + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].adc_gnd = + calib_read_2; + rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE); + 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; + pm8xxx_adc_calib_first_adc = true; + rc = pm8xxx_adc_configure(&conv); + if (rc) { + pr_err("pm8xxx_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC | + PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8XXX_ADC_CONV_TIME_MIN, + PM8XXX_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8xxx_adc_read_adc_code(&calib_read_1); + if (rc) { + pr_err("pm8xxx_adc read adc failed with %d\n", rc); + pm8xxx_adc_calib_first_adc = false; + goto calib_fail; + } + pm8xxx_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; + pm8xxx_adc_calib_first_adc = true; + rc = pm8xxx_adc_configure(&conv); + if (rc) { + pr_err("pm8xxx_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC | + PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8XXX_ADC_CONV_TIME_MIN, + PM8XXX_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8xxx_adc_read_adc_code(&calib_read_2); + if (rc) { + pr_err("pm8xxx_adc read adc failed with %d\n", rc); + pm8xxx_adc_calib_first_adc = false; + goto calib_fail; + } + pm8xxx_adc_calib_first_adc = false; + + 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; + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].adc_vref = + calib_read_1; + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].adc_gnd = + calib_read_2; +calib_fail: + rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE); + if (rc < 0) { + pr_err("%s: Configuring ADC Arbiter disable" + "failed\n", __func__); + } + + return rc; +} + +uint32_t pm8xxx_adc_read(enum pm8xxx_adc_channels channel, + struct pm8xxx_adc_chan_result *result) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int i = 0, rc = 0, rc_fail, amux_prescaling, scale_type; + enum pm8xxx_adc_premux_mpp_scale_type mpp_scale; + + if (!pm8xxx_adc_initialized) + return -ENODEV; + + if (!pm8xxx_adc_calib_device_init) { + if (pm8xxx_adc_calib_device() == 0) + pm8xxx_adc_calib_device_init = true; + } + + mutex_lock(&adc_pmic->adc_lock); + + for (i = 0; i < adc_pmic->adc_num_board_channel; i++) { + if (channel == adc_pmic->adc_channel[i].channel_name) + break; + } + + if (i == adc_pmic->adc_num_board_channel || + (pm8xxx_adc_check_channel_valid(channel) != 0)) { + rc = -EBADF; + goto fail_unlock; + } + + if (channel < PM8XXX_CHANNEL_MPP_SCALE1_IDX) { + mpp_scale = PREMUX_MPP_SCALE_0; + adc_pmic->conv->amux_channel = channel; + } else if (channel >= PM8XXX_CHANNEL_MPP_SCALE1_IDX && + channel < PM8XXX_CHANNEL_MPP_SCALE3_IDX) { + mpp_scale = PREMUX_MPP_SCALE_1; + adc_pmic->conv->amux_channel = channel % + PM8XXX_CHANNEL_MPP_SCALE1_IDX; + } else { + mpp_scale = PREMUX_MPP_SCALE_1_DIV3; + adc_pmic->conv->amux_channel = channel % + PM8XXX_CHANNEL_MPP_SCALE3_IDX; + } + + 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 = + pm8xxx_amux_scaling_ratio[amux_prescaling].num; + adc_pmic->conv->chan_prop->offset_gain_denominator = + pm8xxx_amux_scaling_ratio[amux_prescaling].den; + + rc = pm8xxx_adc_channel_power_enable(channel, true); + if (rc) { + rc = -EINVAL; + goto fail_unlock; + } + + rc = pm8xxx_adc_configure(adc_pmic->conv); + if (rc) { + rc = -EINVAL; + goto fail; + } + + wait_for_completion(&adc_pmic->adc_rslt_completion); + + rc = pm8xxx_adc_read_adc_code(&result->adc_code); + if (rc) { + rc = -EINVAL; + goto fail; + } + + scale_type = adc_pmic->adc_channel[i].adc_scale_fn; + if (scale_type >= ADC_SCALE_NONE) { + rc = -EBADF; + goto fail; + } + + adc_scale_fn[scale_type].chan(result->adc_code, + adc_pmic->adc_prop, adc_pmic->conv->chan_prop, result); + + rc = pm8xxx_adc_channel_power_enable(channel, false); + if (rc) { + rc = -EINVAL; + goto fail_unlock; + } + + mutex_unlock(&adc_pmic->adc_lock); + + return 0; +fail: + rc_fail = pm8xxx_adc_channel_power_enable(channel, false); + if (rc_fail) + pr_err("pm8xxx adc power disable failed\n"); +fail_unlock: + mutex_unlock(&adc_pmic->adc_lock); + pr_err("pm8xxx adc error with %d\n", rc); + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_read); + +uint32_t pm8xxx_adc_mpp_config_read(uint32_t mpp_num, + enum pm8xxx_adc_channels channel, + struct pm8xxx_adc_chan_result *result) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int rc = 0; + + if (!pm8xxx_adc_initialized) + return -ENODEV; + + if (!adc_pmic->mpp_base) { + rc = -EINVAL; + pr_info("PM8xxx MPP base invalid with error %d\n", rc); + return rc; + } + + if (mpp_num == PM8XXX_AMUX_MPP_8) { + rc = -EINVAL; + pr_info("PM8xxx MPP8 is already configured " + "to AMUX8. Use pm8xxx_adc_read() instead.\n"); + return rc; + } + + mutex_lock(&adc_pmic->mpp_adc_lock); + + rc = pm8xxx_mpp_config(((mpp_num - 1) + adc_pmic->mpp_base), + &pm8xxx_adc_mpp_config); + if (rc < 0) { + pr_err("pm8xxx adc mpp config error with %d\n", rc); + goto fail; + } + + usleep_range(PM8XXX_ADC_MPP_SETTLE_TIME_MIN, + PM8XXX_ADC_MPP_SETTLE_TIME_MAX); + + rc = pm8xxx_adc_read(channel, result); + if (rc < 0) + pr_err("pm8xxx adc read error with %d\n", rc); + + rc = pm8xxx_mpp_config(((mpp_num - 1) + adc_pmic->mpp_base), + &pm8xxx_adc_mpp_unconfig); + if (rc < 0) + pr_err("pm8xxx adc mpp config error with %d\n", rc); +fail: + mutex_unlock(&adc_pmic->mpp_adc_lock); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_mpp_config_read); + +uint32_t pm8xxx_adc_btm_configure(struct pm8xxx_adc_arb_btm_param *btm_param) +{ + struct pm8xxx_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; + unsigned long flags = 0; + int rc; + + if (adc_pmic == NULL) { + pr_err("PMIC ADC not valid\n"); + return -EINVAL; + } + + if ((btm_param->btm_cool_fn == NULL) && + (btm_param->btm_warm_fn == NULL)) { + pr_err("No BTM warm/cool notification??\n"); + return -EINVAL; + } + + rc = pm8xxx_adc_batt_scaler(btm_param, adc_pmic->adc_prop, + adc_pmic->conv->chan_prop); + if (rc < 0) { + pr_err("Failed to lookup the BTM thresholds\n"); + return rc; + } + + if (btm_param->interval > PM8XXX_ADC_BTM_INTERVAL_MAX) { + pr_info("Bug in PMIC BTM interval time and cannot set" + " a value greater than 0x14 %x\n", btm_param->interval); + btm_param->interval = PM8XXX_ADC_BTM_INTERVAL_MAX; + } + + spin_lock_irqsave(&adc_pmic->btm_lock, flags); + + 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); + + if (btm_param->btm_cool_fn != NULL) { + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_COOL_THR0, + data_btm_cool_thr0); + if (rc < 0) + goto write_err; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_COOL_THR1, + data_btm_cool_thr1); + if (rc < 0) + goto write_err; + + adc_pmic->batt.btm_cool_fn = btm_param->btm_cool_fn; + } + + if (btm_param->btm_warm_fn != NULL) { + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_WARM_THR0, + data_btm_warm_thr0); + if (rc < 0) + goto write_err; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_WARM_THR1, + data_btm_warm_thr1); + if (rc < 0) + goto write_err; + + adc_pmic->batt.btm_warm_fn = btm_param->btm_warm_fn; + } + + rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, &arb_btm_cntrl1); + if (rc < 0) + goto bail_out; + + btm_param->interval &= PM8XXX_ADC_BTM_INTERVAL_SEL_MASK; + arb_btm_cntrl1 |= + btm_param->interval << PM8XXX_ADC_BTM_INTERVAL_SEL_SHIFT; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, arb_btm_cntrl1); + if (rc < 0) + goto write_err; + + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); + + return rc; +bail_out: +write_err: + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); + pr_debug("%s: with error code %d\n", __func__, rc); + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_configure); + +static uint32_t pm8xxx_adc_btm_read(uint32_t channel) +{ + struct pm8xxx_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, data_arb_btm_cntrl = 0; + unsigned long flags; + + arb_btm_amux_cntrl = channel << PM8XXX_ADC_BTM_CHANNEL_SEL; + arb_btm_rsv = adc_pmic->adc_channel[channel].adc_rsv; + arb_btm_dig_param = arb_btm_ana_param = PM8XXX_ADC_ARB_ANA_DIG; + + spin_lock_irqsave(&adc_pmic->btm_lock, flags); + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_AMUX_CNTRL, + arb_btm_amux_cntrl); + if (rc < 0) + goto write_err; + + arb_btm_rsv = PM8XXX_ADC_BTM_RSV; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_RSV, arb_btm_rsv); + if (rc < 0) + goto write_err; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_DIG_PARAM, + arb_btm_dig_param); + if (rc < 0) + goto write_err; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_ANA_PARAM, + arb_btm_ana_param); + if (rc < 0) + goto write_err; + + data_arb_btm_cntrl |= PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM; + + for (i = 0; i < 2; i++) { + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, + data_arb_btm_cntrl); + if (rc < 0) + goto write_err; + } + + data_arb_btm_cntrl |= PM8XXX_ADC_ARB_BTM_CNTRL1_REQ + | PM8XXX_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE; + + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, + data_arb_btm_cntrl); + if (rc < 0) + goto write_err; + + if (pmic_adc->batt.btm_warm_fn != NULL) + enable_irq(adc_pmic->btm_warm_irq); + + if (pmic_adc->batt.btm_cool_fn != NULL) + enable_irq(adc_pmic->btm_cool_irq); + +write_err: + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); + return rc; +} + +uint32_t pm8xxx_adc_btm_start(void) +{ + return pm8xxx_adc_btm_read(CHANNEL_BATT_THERM); +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_start); + +uint32_t pm8xxx_adc_btm_end(void) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int i, rc; + u8 data_arb_btm_cntrl = 0; + unsigned long flags; + + disable_irq_nosync(adc_pmic->btm_warm_irq); + disable_irq_nosync(adc_pmic->btm_cool_irq); + + spin_lock_irqsave(&adc_pmic->btm_lock, flags); + + /* Write twice to the CNTRL register for the arbiter settings + to take into effect */ + for (i = 0; i < 2; i++) { + rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, + data_arb_btm_cntrl); + if (rc < 0) { + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); + return rc; + } + } + + spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_end); + +static ssize_t pm8xxx_adc_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pm8xxx_adc_chan_result result; + int rc = -1; + + rc = pm8xxx_adc_read(attr->index, &result); + + if (rc) + return 0; + + return snprintf(buf, PM8XXX_ADC_HWMON_NAME_LENGTH, + "Result:%lld Raw:%d\n", result.physical, result.adc_code); +} + +static int get_adc(void *data, u64 *val) +{ + struct pm8xxx_adc_chan_result result; + int i = (int)data; + int rc; + + rc = pm8xxx_adc_read(i, &result); + if (!rc) + 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"); + +static int get_mpp_adc(void *data, u64 *val) +{ + struct pm8xxx_adc_chan_result result; + int i = (int)data; + int rc; + + rc = pm8xxx_adc_mpp_config_read(i, + ADC_MPP_1_AMUX6, &result); + if (!rc) + pr_info("ADC MPP value raw:%x physical:%lld\n", + result.adc_code, result.physical); + *val = result.physical; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(reg_mpp_fops, get_mpp_adc, NULL, "%llu\n"); + +#ifdef CONFIG_DEBUG_FS +static void create_debugfs_entries(void) +{ + int i = 0; + pmic_adc->dent = debugfs_create_dir("pm8xxx_adc", NULL); + + if (IS_ERR(pmic_adc->dent)) { + pr_err("pmic adc debugfs dir not created\n"); + return; + } + + for (i = 0; i < pmic_adc->adc_num_board_channel; i++) + debugfs_create_file(pmic_adc->adc_channel[i].name, + 0644, pmic_adc->dent, + (void *)pmic_adc->adc_channel[i].channel_name, + ®_fops); +} +#else +static inline void create_debugfs_entries(void) +{ +} +#endif +static struct sensor_device_attribute pm8xxx_adc_attr = + SENSOR_ATTR(NULL, S_IRUGO, pm8xxx_adc_show, NULL, 0); + +static int32_t pm8xxx_adc_init_hwmon(struct platform_device *pdev) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int rc = 0, i, channel; + + for (i = 0; i < pmic_adc->adc_num_board_channel; i++) { + channel = adc_pmic->adc_channel[i].channel_name; + if (pm8xxx_adc_check_channel_valid(channel)) { + pr_err("Invalid ADC init HWMON channel: %d\n", channel); + continue; + } + pm8xxx_adc_attr.index = adc_pmic->adc_channel[i].channel_name; + pm8xxx_adc_attr.dev_attr.attr.name = + adc_pmic->adc_channel[i].name; + memcpy(&adc_pmic->sens_attr[i], &pm8xxx_adc_attr, + sizeof(pm8xxx_adc_attr)); + rc = device_create_file(&pdev->dev, + &adc_pmic->sens_attr[i].dev_attr); + if (rc) { + dev_err(&pdev->dev, "device_create_file failed for " + "dev %s\n", + adc_pmic->adc_channel[i].name); + goto hwmon_err_sens; + } + } + + return 0; +hwmon_err_sens: + pr_info("Init HWMON failed for pm8xxx_adc with %d\n", rc); + return rc; +} + +#ifdef CONFIG_PM +static int pm8xxx_adc_suspend_noirq(struct device *dev) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + + adc_pmic->msm_suspend_check = 1; + + return 0; +} + +static int pm8xxx_adc_resume_noirq(struct device *dev) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + + adc_pmic->msm_suspend_check = 0; + + return 0; +} + +static const struct dev_pm_ops pm8xxx_adc_dev_pm_ops = { + .suspend_noirq = pm8xxx_adc_suspend_noirq, + .resume_noirq = pm8xxx_adc_resume_noirq, +}; + +#define PM8XXX_ADC_DEV_PM_OPS (&pm8xxx_adc_dev_pm_ops) +#else +#define PM8XXX_ADC_DEV_PM_OPS NULL +#endif + +static int __devexit pm8xxx_adc_teardown(struct platform_device *pdev) +{ + struct pm8xxx_adc *adc_pmic = pmic_adc; + int i; + + wake_lock_destroy(&adc_pmic->adc_wakelock); + platform_set_drvdata(pdev, NULL); + pmic_adc = NULL; + if (!pa_therm) { + regulator_put(pa_therm); + pa_therm = NULL; + } + for (i = 0; i < adc_pmic->adc_num_board_channel; i++) + device_remove_file(adc_pmic->dev, + &adc_pmic->sens_attr[i].dev_attr); + pm8xxx_adc_initialized = false; + + return 0; +} + +static int __devinit pm8xxx_adc_probe(struct platform_device *pdev) +{ + const struct pm8xxx_adc_platform_data *pdata = pdev->dev.platform_data; + struct pm8xxx_adc *adc_pmic; + struct pm8xxx_adc_amux_properties *adc_amux_prop; + int rc = 0; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + adc_pmic = devm_kzalloc(&pdev->dev, sizeof(struct pm8xxx_adc) + + (sizeof(struct sensor_device_attribute) * + pdata->adc_num_board_channel), GFP_KERNEL); + if (!adc_pmic) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_amux_prop = devm_kzalloc(&pdev->dev, + sizeof(struct pm8xxx_adc_amux_properties) + + sizeof(struct pm8xxx_adc_chan_properties) + , GFP_KERNEL); + if (!adc_amux_prop) { + 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; + init_completion(&adc_pmic->adc_rslt_completion); + adc_pmic->adc_channel = pdata->adc_channel; + adc_pmic->adc_num_board_channel = pdata->adc_num_board_channel; + adc_pmic->mpp_base = pdata->adc_mpp_base; + + mutex_init(&adc_pmic->adc_lock); + mutex_init(&adc_pmic->mpp_adc_lock); + spin_lock_init(&adc_pmic->btm_lock); + + adc_pmic->adc_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_0); + if (adc_pmic->adc_irq < 0) + return adc_pmic->adc_irq; + + rc = devm_request_irq(&pdev->dev, adc_pmic->adc_irq, + pm8xxx_adc_isr, + IRQF_TRIGGER_RISING, "pm8xxx_adc_interrupt", adc_pmic); + if (rc) { + dev_err(&pdev->dev, "failed to request adc irq " + "with error %d\n", rc); + } else { + enable_irq_wake(adc_pmic->adc_irq); + } + + adc_pmic->btm_warm_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_1); + if (adc_pmic->btm_warm_irq < 0) + return adc_pmic->btm_warm_irq; + + rc = devm_request_irq(&pdev->dev, adc_pmic->btm_warm_irq, + pm8xxx_btm_warm_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "pm8xxx_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"); + } + + disable_irq_nosync(adc_pmic->btm_warm_irq); + + adc_pmic->btm_cool_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_2); + if (adc_pmic->btm_cool_irq < 0) + return adc_pmic->btm_cool_irq; + + rc = devm_request_irq(&pdev->dev, adc_pmic->btm_cool_irq, + pm8xxx_btm_cool_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "pm8xxx_btm_cool_interrupt", adc_pmic); + if (rc) { + pr_err("btm cool irq failed with return %d and number %d\n", + rc, adc_pmic->btm_cool_irq); + dev_err(&pdev->dev, "failed to request btm irq\n"); + } + + disable_irq_nosync(adc_pmic->btm_cool_irq); + platform_set_drvdata(pdev, adc_pmic); + wake_lock_init(&adc_pmic->adc_wakelock, WAKE_LOCK_SUSPEND, + "pm8xxx_adc_wakelock"); + adc_pmic->msm_suspend_check = 0; + pmic_adc = adc_pmic; + + INIT_WORK(&adc_pmic->warm_work, pm8xxx_adc_btm_warm_scheduler_fn); + INIT_WORK(&adc_pmic->cool_work, pm8xxx_adc_btm_cool_scheduler_fn); + create_debugfs_entries(); + pm8xxx_adc_calib_first_adc = false; + pm8xxx_adc_calib_device_init = false; + pm8xxx_adc_initialized = true; + + rc = pm8xxx_adc_init_hwmon(pdev); + if (rc) { + pr_err("pm8xxx adc init hwmon failed with %d\n", rc); + dev_err(&pdev->dev, "failed to initialize pm8xxx hwmon adc\n"); + } + adc_pmic->hwmon = hwmon_device_register(adc_pmic->dev); + + pa_therm = regulator_get(adc_pmic->dev, "pa_therm"); + if (IS_ERR(pa_therm)) { + rc = PTR_ERR(pa_therm); + pr_err("failed to request pa_therm vreg with error %d\n", rc); + pa_therm = NULL; + } + return 0; +} + +static struct platform_driver pm8xxx_adc_driver = { + .probe = pm8xxx_adc_probe, + .remove = __devexit_p(pm8xxx_adc_teardown), + .driver = { + .name = PM8XXX_ADC_DEV_NAME, + .owner = THIS_MODULE, + .pm = PM8XXX_ADC_DEV_PM_OPS, + }, +}; + +static int __init pm8xxx_adc_init(void) +{ + return platform_driver_register(&pm8xxx_adc_driver); +} +module_init(pm8xxx_adc_init); + +static void __exit pm8xxx_adc_exit(void) +{ + platform_driver_unregister(&pm8xxx_adc_driver); +} +module_exit(pm8xxx_adc_exit); + +MODULE_ALIAS("platform:" PM8XXX_ADC_DEV_NAME); +MODULE_DESCRIPTION("PMIC8921/8018 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/wpce775x.c b/drivers/hwmon/wpce775x.c new file mode 100644 index 0000000000000000000000000000000000000000..2d007003cc29262dee912a4efcf2a870c6de2faf --- /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 d2c5095deeaca5dc0324b3786ff056ec09447275..574ec8a64b52944d2264dca9be866ab11df69a37 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -470,6 +470,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 569567b0d02704653a8884757fa47aaba0fb453e..4f494fcd74b23dd6c205b1a81e0459a0ecda335a 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -46,6 +46,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 0000000000000000000000000000000000000000..b753fd0cf938d4d0dca164816fc847ea6f27b368 --- /dev/null +++ b/drivers/i2c/busses/i2c-msm.c @@ -0,0 +1,798 @@ +/* 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 +#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 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, "core_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 0000000000000000000000000000000000000000..297afa77f6aaae6cf21cdbf5b552b62272f49105 --- /dev/null +++ b/drivers/i2c/busses/i2c-qup.c @@ -0,0 +1,1515 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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_BUS_MASTER = 1U << 9, + 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"}; + +static struct gpiomux_setting recovery_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +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 int +qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t req_state, bool only_valid) +{ + uint32_t retries = 0; + + dev_dbg(dev->dev, "Polling for state:0x%x, or valid-only:%d\n", + req_state, only_valid); + + while (retries != 2000) { + uint32_t status = readl_relaxed(dev->base + QUP_STATE); + + /* + * If only valid bit needs to be checked, requested state is + * 'don't care' + */ + if (status & QUP_STATE_VALID) { + if (only_valid) + return 0; + else if ((req_state & QUP_I2C_MAST_GEN) && + (status & QUP_I2C_MAST_GEN)) + return 0; + else if ((status & QUP_STATE_MASK) == req_state) + return 0; + } + if (retries++ == 1000) + udelay(100); + } + return -ETIMEDOUT; +} + +static int +qup_update_state(struct qup_i2c_dev *dev, uint32_t state) +{ + if (qup_i2c_poll_state(dev, 0, true) != 0) + return -EIO; + writel_relaxed(state, dev->base + QUP_STATE); + if (qup_i2c_poll_state(dev, state, false) != 0) + return -EIO; + return 0; +} + +/* + * Before calling qup_config_core_on_en(), please make + * sure that QuPE core is in RESET state. + */ +static void +qup_config_core_on_en(struct qup_i2c_dev *dev) +{ + uint32_t status; + + status = readl_relaxed(dev->base + QUP_CONFIG); + status |= BIT(13); + writel_relaxed(status, dev->base + QUP_CONFIG); + /* making sure that write has really gone through */ + mb(); +} + +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); + clk_enable(dev->pclk); + } else { + qup_update_state(dev, QUP_RESET_STATE); + clk_disable(dev->clk); + qup_config_core_on_en(dev); + 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) { + /* + * Wait for FIFO number of bytes to be absolutely sure + * that I2C write state machine is not idle. Each byte + * takes 9 clock cycles. (8 bits + 1 ack) + */ + usleep_range((dev->one_bit_t * (dev->out_fifo_sz * 9)), + (dev->one_bit_t * (dev->out_fifo_sz * 9))); + } + } + 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 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_START << 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) && + (next->addr != msg->addr)) { + /* Last byte of an intermittent write */ + writel_relaxed((QUP_OUT_STOP | + msg->buf[dev->pos]), + dev->base + QUP_OUT_FIFO_BASE); + + qup_verify_fifo(dev, + QUP_OUT_STOP | 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 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; + int len = dev->msg->len; + struct i2c_msg *next = NULL; + if (rem > 1) + next = dev->msg + 1; + while (rem > 1 && next->flags == 0 && (next->addr == dev->msg->addr)) { + len += next->len + 1; + next = next + 1; + rem--; + } + if (len >= (dev->out_fifo_sz - 1)) { + total_len = len + 1 + (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) { + 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 void qup_i2c_recover_bus_busy(struct qup_i2c_dev *dev) +{ + int i; + int gpio_clk; + int gpio_dat; + bool gpio_clk_status = false; + uint32_t status = readl_relaxed(dev->base + QUP_I2C_STATUS); + struct gpiomux_setting old_gpio_setting; + + if (dev->pdata->msm_i2c_config_gpio) + return; + + if (!(status & (I2C_STATUS_BUS_ACTIVE)) || + (status & (I2C_STATUS_BUS_MASTER))) + return; + + gpio_clk = dev->i2c_gpios[0]; + gpio_dat = dev->i2c_gpios[1]; + + if ((gpio_clk == -1) && (gpio_dat == -1)) { + dev_err(dev->dev, "Recovery failed due to undefined GPIO's\n"); + return; + } + + disable_irq(dev->err_irq); + for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) { + if (msm_gpiomux_write(dev->i2c_gpios[i], GPIOMUX_ACTIVE, + &recovery_config, &old_gpio_setting)) { + dev_err(dev->dev, "GPIO pins have no active setting\n"); + goto recovery_end; + } + } + + dev_warn(dev->dev, "i2c_scl: %d, i2c_sda: %d\n", + gpio_get_value(gpio_clk), gpio_get_value(gpio_dat)); + + 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)) + udelay(20); + if (!gpio_get_value(gpio_clk)) + usleep_range(10000, 10000); + gpio_clk_status = gpio_get_value(gpio_clk); + gpio_direction_input(gpio_dat); + udelay(5); + } + + /* Configure ALT funciton to QUP I2C*/ + for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) { + msm_gpiomux_write(dev->i2c_gpios[i], GPIOMUX_ACTIVE, + &old_gpio_setting, NULL); + } + + udelay(10); + + status = readl_relaxed(dev->base + QUP_I2C_STATUS); + if (!(status & I2C_STATUS_BUS_ACTIVE)) { + dev_info(dev->dev, "Bus busy cleared after %d clock cycles, " + "status %x\n", + i, status); + goto recovery_end; + } + + dev_warn(dev->dev, "Bus still busy, status %x\n", status); + +recovery_end: + enable_irq(dev->err_irq); +} + +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) + 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, false); + 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, false) != 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, + msecs_to_jiffies(dev->out_fifo_sz)); + 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); + + /* + * Dont wait for 1 sec if i2c sees the bus + * active and controller is not master. + * A slave has pulled line low. Try to recover + */ + if (!(istatus & I2C_STATUS_BUS_ACTIVE) || + (istatus & I2C_STATUS_BUS_MASTER)) { + timeout = + wait_for_completion_timeout(&complete, + HZ); + if (timeout) + goto timeout_err; + } + qup_i2c_recover_bus_busy(dev); + dev_err(dev->dev, + "Transaction timed out, SL-AD = 0x%x\n", + dev->msg->addr); + + 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; + } +timeout_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); + dev->err = ENOTCONN; + } else if (dev->err < 0) { + dev_err(dev->dev, + "QUP data xfer error %d\n", dev->err); + ret = dev->err; + goto out_err; + } else if (dev->err > 0) { + /* + * ISR returns +ve error if error code + * is I2C related, e.g. unexpected start + * So you may call recover-bus-busy when + * this error happens + */ + qup_i2c_recover_bus_busy(dev); + } + 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; + + gsbi_mem = NULL; + dev_dbg(&pdev->dev, "qup_i2c_probe\n"); + + if (pdev->dev.of_node) { + struct device_node *node = pdev->dev.of_node; + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + ret = of_property_read_u32(node, "qcom,i2c-bus-freq", + &pdata->clk_freq); + if (ret) + goto get_res_failed; + ret = of_property_read_u32(node, "cell-index", &pdev->id); + if (ret) + goto get_res_failed; + /* Optional property */ + of_property_read_u32(node, "qcom,i2c-src-freq", + &pdata->src_clk_rate); + } else + 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"); + ret = -ENODEV; + goto get_res_failed; + } + + /* + * 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"); + ret = -ENODEV; + goto get_res_failed; + } + + 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"); + ret = -EBUSY; + goto get_res_failed; + } + if (!pdata->use_gsbi_shared_mode) { + gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "gsbi_qup_i2c_addr"); + if (!gsbi_mem) { + dev_dbg(&pdev->dev, "Assume BLSP\n"); + /* + * BLSP core does not need protocol programming so this + * resource is not expected + */ + goto blsp_core_init; + } + 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"); + ret = -EBUSY; + goto err_res_failed; + } + } + +blsp_core_init: + clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Could not get core_clk\n"); + ret = PTR_ERR(clk); + goto err_clk_get_failed; + } + + pclk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(pclk)) { + dev_err(&pdev->dev, "Could not get iface_clk\n"); + ret = PTR_ERR(pclk); + clk_put(clk); + 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_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) + 1; + dev->pdata = pdata; + dev->clk_ctl = 0; + dev->pos = 0; + + /* + * If bootloaders leave a pending interrupt on certain GSBI's, + * then we reset the core before registering for interrupts. + */ + + if (dev->pdata->src_clk_rate > 0) + clk_set_rate(dev->clk, dev->pdata->src_clk_rate); + else + dev->pdata->src_clk_rate = 19200000; + + clk_prepare_enable(dev->clk); + clk_prepare_enable(dev->pclk); + writel_relaxed(1, dev->base + QUP_SW_RESET); + if (qup_i2c_poll_state(dev, 0, true) != 0) + goto err_reset_failed; + clk_disable_unprepare(dev->clk); + clk_disable_unprepare(dev->pclk); + + /* + * 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; + if (pdata->msm_i2c_config_gpio) + pdata->msm_i2c_config_gpio(dev->adapter.nr, 1); + + dev->suspended = 0; + mutex_init(&dev->mlock); + dev->clk_state = 0; + clk_prepare(dev->clk); + clk_prepare(dev->pclk); + 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 { + if (dev->dev->of_node) + of_i2c_register_devices(&dev->adapter); + return 0; + } + + +err_request_irq_failed: + qup_i2c_free_gpios(dev); + if (dev->gsbi) + iounmap(dev->gsbi); +err_reset_failed: + clk_disable_unprepare(dev->clk); + clk_disable_unprepare(dev->pclk); +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); + clk_put(pclk); +err_clk_get_failed: + if (gsbi_mem) + release_mem_region(gsbi_mem->start, resource_size(gsbi_mem)); +err_res_failed: + release_mem_region(qup_mem->start, resource_size(qup_mem)); +get_res_failed: + if (pdev->dev.of_node) + kfree(pdata); + 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_unprepare(dev->clk); + clk_unprepare(dev->pclk); + clk_put(dev->clk); + clk_put(dev->pclk); + qup_i2c_free_gpios(dev); + if (dev->gsbi) + iounmap(dev->gsbi); + iounmap(dev->base); + + pm_runtime_disable(&pdev->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)); + if (dev->dev->of_node) + kfree(dev->pdata); + kfree(dev); + 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); + clk_unprepare(dev->clk); + clk_unprepare(dev->pclk); + 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); + clk_prepare(dev->clk); + clk_prepare(dev->pclk); + 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 of_device_id i2c_qup_dt_match[] = { + { + .compatible = "qcom,i2c-qup", + }, + {} +}; + +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, + .of_match_table = i2c_qup_dt_match, + }, +}; + +/* 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 0000000000000000000000000000000000000000..c8034023087a4315722ef3fe55cbc59c525c1d18 --- /dev/null +++ b/drivers/i2c/busses/i2c-ssbi.c @@ -0,0 +1,517 @@ +/* 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 +#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/infiniband/hw/mlx4/qp.c b/drivers/infiniband/hw/mlx4/qp.c index 3a78489666277eeb97a77ce65c5fa9ff804b5339..fda9e0a5c416ec62c093cedc813aa2ad253a9c0f 100644 --- a/drivers/infiniband/hw/mlx4/qp.c +++ b/drivers/infiniband/hw/mlx4/qp.c @@ -1363,7 +1363,7 @@ static int build_mlx_header(struct mlx4_ib_sqp *sqp, struct ib_send_wr *wr, int is_eth; int is_vlan = 0; int is_grh; - u16 vlan; + u16 vlan = 0; send_size = 0; for (i = 0; i < wr->num_sge; ++i) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index a93743879c01fb8c81cb17225a54cfd2032a6d79..9ed3d534fab2ceb4d218996d4a2c791714391824 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -323,6 +323,7 @@ static int evdev_open(struct inode *inode, struct file *file) goto err_put_evdev; } + client->clkid = CLOCK_MONOTONIC; client->bufsize = bufsize; spin_lock_init(&client->buffer_lock); snprintf(client->name, sizeof(client->name), "%s-%d", diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig index 56eb471b5576954f6119664b23a1d7b83316d3fb..aaee4481197f668d0b9faae07816472196986b1c 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 92dc0de9dfeda43171a901f96c40bebe0e7a5ae2..7009c38e79fd485a8dd25208d01108b288482706 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 0000000000000000000000000000000000000000..efbe97474b8e9e95b3ab27f2c37636d93a8e4e31 --- /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 f354813a13e8608868040355e3a1f4f19cf2b139..badbc2be2b8a4f6d965ab2378fdf1319b3108c97 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -411,6 +411,13 @@ 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_PXA27x tristate "PXA27x/PXA3xx keypad support" depends on PXA27x || PXA3xx || ARCH_MMP @@ -570,6 +577,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 @@ -581,3 +612,5 @@ config KEYBOARD_W90P910 module will be called w90p910_keypad. endif + + diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index df7061f129184f4c5dfb6265229529fe1387dcf7..61b57eff5e0783f011c5249ab32c4ed358a81b50 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -51,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 9b223d73de326a12f9a7279e15d7a85ad4cd8760..dd05ccabdbf0adf4c9fcd50eb09e4ef560d147e8 100644 --- a/drivers/input/keyboard/matrix_keypad.c +++ b/drivers/input/keyboard/matrix_keypad.c @@ -2,6 +2,7 @@ * GPIO driven matrix keyboard driver * * Copyright (c) 2008 Marek Vasut + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. * * Based on corgikbd.c * @@ -34,7 +35,7 @@ struct matrix_keypad { uint32_t last_key_state[MATRIX_MAX_COLS]; struct delayed_work work; - spinlock_t lock; + struct mutex lock; bool scan_pending; bool stopped; bool gpio_all_disabled; @@ -162,19 +163,17 @@ static void matrix_keypad_scan(struct work_struct *work) activate_all_cols(pdata, true); - /* Enable IRQs again */ - spin_lock_irq(&keypad->lock); + mutex_lock(&keypad->lock); keypad->scan_pending = false; enable_row_irqs(keypad); - spin_unlock_irq(&keypad->lock); + mutex_unlock(&keypad->lock); } static irqreturn_t matrix_keypad_interrupt(int irq, void *id) { struct matrix_keypad *keypad = id; - unsigned long flags; - spin_lock_irqsave(&keypad->lock, flags); + mutex_lock(&keypad->lock); /* * See if another IRQ beaten us to it and scheduled the @@ -190,7 +189,7 @@ static irqreturn_t matrix_keypad_interrupt(int irq, void *id) msecs_to_jiffies(keypad->pdata->debounce_ms)); out: - spin_unlock_irqrestore(&keypad->lock, flags); + mutex_unlock(&keypad->lock); return IRQ_HANDLED; } @@ -334,19 +333,22 @@ 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_threaded_irq( + gpio_to_irq(pdata->row_gpios[i]), + NULL, matrix_keypad_interrupt, + IRQF_DISABLED | IRQF_ONESHOT | 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", @@ -415,7 +417,7 @@ static int __devinit matrix_keypad_probe(struct platform_device *pdev) keypad->row_shift = row_shift; keypad->stopped = true; INIT_DELAYED_WORK(&keypad->work, matrix_keypad_scan); - spin_lock_init(&keypad->lock); + mutex_init(&keypad->lock); input_dev->name = pdev->name; input_dev->id.bustype = BUS_HOST; @@ -477,6 +479,7 @@ static int __devexit matrix_keypad_remove(struct platform_device *pdev) for (i = 0; i < pdata->num_col_gpios; i++) gpio_free(pdata->col_gpios[i]); + mutex_destroy(&keypad->lock); input_unregister_device(keypad->input_dev); platform_set_drvdata(pdev, NULL); kfree(keypad->keycodes); diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c index 01a1c9f8a383b586d1ea4e18fbb826387a161093..d529ea42ce16d6dc315a5e898a5e28dd6acb0e22 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 0000000000000000000000000000000000000000..d7350121fdc81f5652a424b309e64011da362e0a --- /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 66550c2bc35f266299d3e1e38b395484b274811c..8fb19dc014f8a4433ae6dc94f0cd6ac174c797f2 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -423,6 +423,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 @@ -606,4 +618,33 @@ config INPUT_XEN_KBDDEV_FRONTEND To compile this driver as a module, choose M here: the module will be called xen-kbdfront. +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 f8cb5221fe34e128d0495a545367d48c5e9eb7b3..eecee4eabf5cabe21a1002e5df23983aa26fb4e1 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -57,3 +57,6 @@ 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_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 index e2f1e9f952b16818d41350e222a4cae8c4bd21a6..8911c0b2b8955cd65f5660e87f97ce034cb6383b 100644 --- a/drivers/input/misc/bma150.c +++ b/drivers/input/misc/bma150.c @@ -1,50 +1,174 @@ +/* Date: 2011/3/7 11:00:00 + * Revision: 2.11 + */ + /* - * Copyright (c) 2011 Bosch Sensortec GmbH - * Copyright (c) 2011 Unixphere - * - * This driver adds support for Bosch Sensortec's digital acceleration - * sensors BMA150 and SMB380. - * The SMB380 is fully compatible with BMA150 and only differs in packaging. - * - * The datasheet for the BMA150 chip can be found here: - * http://www.bosch-sensortec.com/content/language1/downloads/BST-BMA150-DS000-07.pdf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * 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 */ -#include + + +/* file BMA150.c + brief This file contains all function implementations for the BMA150 in linux + +*/ + #include +#include #include -#include -#include #include -#include +#include +#include #include -#include -#include +#include #include -#define ABSMAX_ACC_VAL 0x01FF -#define ABSMIN_ACC_VAL -(ABSMAX_ACC_VAL) +#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) + -/* Each axis is represented by a 2-byte data word */ -#define BMA150_XYZ_DATA_SIZE 6 +#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; + } + } -/* High-G interrupt register fields */ -#define BMA150_HIGH_G_EN_POS 1 -#define BMA150_HIGH_G_EN_MSK 0x02 -#define BMA150_HIGH_G_EN_REG BMA150_CTRL_1_REG + return comres; +} -#define BMA150_HIGH_G_HYST_POS 3 -#define BMA150_HIGH_G_HYST_MSK 0x38 -#define BMA150_HIGH_G_HYST_REG BMA150_CFG_5_REG -#define BMA150_HIGH_G_DUR_REG BMA150_CFG_3_REG -#define BMA150_HIGH_G_THRES_REG BMA150_CFG_2_REG +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; + } + } -/* Low-G interrupt register fields */ -#define BMA150_LOW_G_EN_POS 0 -#define BMA150_LOW_G_EN_MSK 0x01 -#define BMA150_LOW_G_EN_REG BMA150_CTRL_1_REG + return comres; +} -#define BMA150_LOW_G_HYST_POS 0 -#define BMA150_LOW_G_HYST_MSK 0x07 -#define BMA150_LOW_G_HYST_REG BMA150_CFG_5_REG +static int bma150_get_range(struct i2c_client *client, unsigned char *Range) +{ + int comres = 0; + unsigned char data; -#define BMA150_LOW_G_DUR_REG BMA150_CFG_1_REG -#define BMA150_LOW_G_THRES_REG BMA150_CFG_0_REG + if (client == NULL) { + comres = -1; + } else{ + comres = bma150_smbus_read_byte(client, + BMA150_RANGE__REG, &data); -struct bma150_data { - struct i2c_client *client; - struct input_polled_dev *input_polled; - struct input_dev *input; - u8 mode; -}; + *Range = BMA150_GET_BITSLICE(data, BMA150_RANGE); -/* - * The settings for the given range, bandwidth and interrupt features - * are stated and verified by Bosch Sensortec where they are configured - * to provide a generic sensitivity performance. - */ -static struct bma150_cfg default_cfg __devinitdata = { - .any_motion_int = 1, - .hg_int = 1, - .lg_int = 1, - .any_motion_dur = 0, - .any_motion_thres = 0, - .hg_hyst = 0, - .hg_dur = 150, - .hg_thres = 160, - .lg_hyst = 0, - .lg_dur = 150, - .lg_thres = 20, - .range = BMA150_RANGE_2G, - .bandwidth = BMA150_BW_50HZ -}; + } -static int bma150_write_byte(struct i2c_client *client, u8 reg, u8 val) -{ - s32 ret; + return comres; +} - /* As per specification, disable irq in between register writes */ - if (client->irq) - disable_irq_nosync(client->irq); - ret = i2c_smbus_write_byte_data(client, reg, val); - if (client->irq) - enable_irq(client->irq); +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 ret; + return comres; } -static int bma150_set_reg_bits(struct i2c_client *client, - int val, int shift, u8 mask, u8 reg) +static int bma150_get_bandwidth(struct i2c_client *client, unsigned char *BW) { - int data; + int comres = 0; + unsigned char data; - data = i2c_smbus_read_byte_data(client, reg); - if (data < 0) - return data; + if (client == NULL) { + comres = -1; + } else{ - data = (data & ~mask) | ((val << shift) & mask); - return bma150_write_byte(client, reg, data); -} -static int bma150_set_mode(struct bma150_data *bma150, u8 mode) -{ - int error; + comres = bma150_smbus_read_byte(client, + BMA150_BANDWIDTH__REG, &data); - error = bma150_set_reg_bits(bma150->client, mode, BMA150_WAKE_UP_POS, - BMA150_WAKE_UP_MSK, BMA150_WAKE_UP_REG); - if (error) - return error; + *BW = BMA150_GET_BITSLICE(data, BMA150_BANDWIDTH); - error = bma150_set_reg_bits(bma150->client, mode, BMA150_SLEEP_POS, - BMA150_SLEEP_MSK, BMA150_SLEEP_REG); - if (error) - return error; - if (mode == BMA150_MODE_NORMAL) - msleep(2); + } - bma150->mode = mode; - return 0; + return comres; } -static int __devinit bma150_soft_reset(struct bma150_data *bma150) +static int bma150_read_accel_xyz(struct i2c_client *client, + struct bma150acc *acc) { - int error; + 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)); - error = bma150_set_reg_bits(bma150->client, 1, BMA150_SW_RES_POS, - BMA150_SW_RES_MSK, BMA150_SW_RES_REG); - if (error) - return error; + } - msleep(2); - return 0; + return comres; } -static int __devinit bma150_set_range(struct bma150_data *bma150, u8 range) +static void bma150_work_func(struct work_struct *work) { - return bma150_set_reg_bits(bma150->client, range, BMA150_RANGE_POS, - BMA150_RANGE_MSK, BMA150_RANGE_REG); -} + 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)); -static int __devinit bma150_set_bandwidth(struct bma150_data *bma150, u8 bw) -{ - return bma150_set_reg_bits(bma150->client, bw, BMA150_BANDWIDTH_POS, - BMA150_BANDWIDTH_MSK, BMA150_BANDWIDTH_REG); -} -static int __devinit bma150_set_low_g_interrupt(struct bma150_data *bma150, - u8 enable, u8 hyst, u8 dur, u8 thres) -{ - int error; - error = bma150_set_reg_bits(bma150->client, hyst, - BMA150_LOW_G_HYST_POS, BMA150_LOW_G_HYST_MSK, - BMA150_LOW_G_HYST_REG); - if (error) - return error; + 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); +} - error = bma150_write_byte(bma150->client, BMA150_LOW_G_DUR_REG, dur); - if (error) - return error; +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); - error = bma150_write_byte(bma150->client, BMA150_LOW_G_THRES_REG, thres); - if (error) - return error; + mutex_lock(&bma150->mode_mutex); + data = bma150->mode; + mutex_unlock(&bma150->mode_mutex); - return bma150_set_reg_bits(bma150->client, !!enable, - BMA150_LOW_G_EN_POS, BMA150_LOW_G_EN_MSK, - BMA150_LOW_G_EN_REG); + return sprintf(buf, "%d\n", data); } -static int __devinit bma150_set_high_g_interrupt(struct bma150_data *bma150, - u8 enable, u8 hyst, u8 dur, u8 thres) +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 = bma150_set_reg_bits(bma150->client, hyst, - BMA150_HIGH_G_HYST_POS, BMA150_HIGH_G_HYST_MSK, - BMA150_HIGH_G_HYST_REG); - if (error) - return error; - - error = bma150_write_byte(bma150->client, - BMA150_HIGH_G_DUR_REG, dur); + error = strict_strtoul(buf, 10, &data); if (error) return error; + if (bma150_set_mode(bma150->bma150_client, (unsigned char) data) < 0) + return -EINVAL; - error = bma150_write_byte(bma150->client, - BMA150_HIGH_G_THRES_REG, thres); - if (error) - return error; - return bma150_set_reg_bits(bma150->client, !!enable, - BMA150_HIGH_G_EN_POS, BMA150_HIGH_G_EN_MSK, - BMA150_HIGH_G_EN_REG); + 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 int __devinit bma150_set_any_motion_interrupt(struct bma150_data *bma150, - u8 enable, u8 dur, u8 thres) +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 = bma150_set_reg_bits(bma150->client, dur, - BMA150_ANY_MOTION_DUR_POS, - BMA150_ANY_MOTION_DUR_MSK, - BMA150_ANY_MOTION_DUR_REG); - if (error) - return error; - - error = bma150_write_byte(bma150->client, - BMA150_ANY_MOTION_THRES_REG, thres); - if (error) - return error; - - error = bma150_set_reg_bits(bma150->client, !!enable, - BMA150_ADV_INT_EN_POS, BMA150_ADV_INT_EN_MSK, - BMA150_ADV_INT_EN_REG); + error = strict_strtoul(buf, 10, &data); if (error) return error; + if (bma150_set_range(bma150->bma150_client, (unsigned char) data) < 0) + return -EINVAL; - return bma150_set_reg_bits(bma150->client, !!enable, - BMA150_ANY_MOTION_EN_POS, - BMA150_ANY_MOTION_EN_MSK, - BMA150_ANY_MOTION_EN_REG); + return count; } -static void bma150_report_xyz(struct bma150_data *bma150) +static ssize_t bma150_bandwidth_show(struct device *dev, + struct device_attribute *attr, char *buf) { - u8 data[BMA150_XYZ_DATA_SIZE]; - s16 x, y, z; - s32 ret; - - ret = i2c_smbus_read_i2c_block_data(bma150->client, - BMA150_ACC_X_LSB_REG, BMA150_XYZ_DATA_SIZE, data); - if (ret != BMA150_XYZ_DATA_SIZE) - return; - - x = ((0xc0 & data[0]) >> 6) | (data[1] << 2); - y = ((0xc0 & data[2]) >> 6) | (data[3] << 2); - z = ((0xc0 & data[4]) >> 6) | (data[5] << 2); - - /* sign extension */ - x = (s16) (x << 6) >> 6; - y = (s16) (y << 6) >> 6; - z = (s16) (z << 6) >> 6; - - input_report_abs(bma150->input, ABS_X, x); - input_report_abs(bma150->input, ABS_Y, y); - input_report_abs(bma150->input, ABS_Z, z); - input_sync(bma150->input); -} + unsigned char data; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); -static irqreturn_t bma150_irq_thread(int irq, void *dev) -{ - bma150_report_xyz(dev); + if (bma150_get_bandwidth(bma150->bma150_client, &data) < 0) + return sprintf(buf, "Read error\n"); - return IRQ_HANDLED; -} + return sprintf(buf, "%d\n", data); -static void bma150_poll(struct input_polled_dev *dev) -{ - bma150_report_xyz(dev->private); } -static int bma150_open(struct bma150_data *bma150) +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 = pm_runtime_get_sync(&bma150->client->dev); - if (error && error != -ENOSYS) + error = strict_strtoul(buf, 10, &data); + if (error) return error; + if (bma150_set_bandwidth(bma150->bma150_client, + (unsigned char) data) < 0) + return -EINVAL; - /* - * See if runtime PM woke up the device. If runtime PM - * is disabled we need to do it ourselves. - */ - if (bma150->mode != BMA150_MODE_NORMAL) { - error = bma150_set_mode(bma150, BMA150_MODE_NORMAL); - if (error) - return error; - } - - return 0; -} - -static void bma150_close(struct bma150_data *bma150) -{ - pm_runtime_put_sync(&bma150->client->dev); - - if (bma150->mode != BMA150_MODE_SLEEP) - bma150_set_mode(bma150, BMA150_MODE_SLEEP); + return count; } -static int bma150_irq_open(struct input_dev *input) +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; - return bma150_open(bma150); -} - -static void bma150_irq_close(struct input_dev *input) -{ - struct bma150_data *bma150 = input_get_drvdata(input); + mutex_lock(&bma150->value_mutex); + acc_value = bma150->value; + mutex_unlock(&bma150->value_mutex); - bma150_close(bma150); + return sprintf(buf, "%d %d %d\n", acc_value.x, acc_value.y, + acc_value.z); } -static void bma150_poll_open(struct input_polled_dev *ipoll_dev) -{ - struct bma150_data *bma150 = ipoll_dev->private; - bma150_open(bma150); -} -static void bma150_poll_close(struct input_polled_dev *ipoll_dev) +static ssize_t bma150_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) { - struct bma150_data *bma150 = ipoll_dev->private; + 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)); - bma150_close(bma150); } -static int __devinit bma150_initialize(struct bma150_data *bma150, - const struct bma150_cfg *cfg) +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 = bma150_soft_reset(bma150); - if (error) - return error; - - error = bma150_set_bandwidth(bma150, cfg->bandwidth); - if (error) - return error; - - error = bma150_set_range(bma150, cfg->range); + 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); - if (bma150->client->irq) { - error = bma150_set_any_motion_interrupt(bma150, - cfg->any_motion_int, - cfg->any_motion_dur, - cfg->any_motion_thres); - if (error) - return error; - - error = bma150_set_high_g_interrupt(bma150, - cfg->hg_int, cfg->hg_hyst, - cfg->hg_dur, cfg->hg_thres); - if (error) - return error; - - error = bma150_set_low_g_interrupt(bma150, - cfg->lg_int, cfg->lg_hyst, - cfg->lg_dur, cfg->lg_thres); - if (error) - return error; - } - - return bma150_set_mode(bma150, BMA150_MODE_SLEEP); -} - -static void __devinit bma150_init_input_device(struct bma150_data *bma150, - struct input_dev *idev) -{ - idev->name = BMA150_DRIVER; - idev->phys = BMA150_DRIVER "/input0"; - idev->id.bustype = BUS_I2C; - idev->dev.parent = &bma150->client->dev; - - idev->evbit[0] = BIT_MASK(EV_ABS); - input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0); - input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0); - input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0); + return count; } -static int __devinit bma150_register_input_device(struct bma150_data *bma150) -{ - struct input_dev *idev; - int error; +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 +}; - idev = input_allocate_device(); - if (!idev) - return -ENOMEM; +static struct attribute_group bma150_attribute_group = { + .attrs = bma150_attributes +}; - bma150_init_input_device(bma150, idev); +static int bma150_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; - idev->open = bma150_irq_open; - idev->close = bma150_irq_close; - input_set_drvdata(idev, bma150); + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -ENODEV; - error = input_register_device(idev); - if (error) { - input_free_device(idev); - return error; - } + strlcpy(info->type, SENSOR_NAME, I2C_NAME_SIZE); - bma150->input = idev; return 0; } -static int __devinit bma150_register_polled_device(struct bma150_data *bma150) +static int bma150_input_init(struct bma150_data *bma150) { - struct input_polled_dev *ipoll_dev; - int error; + struct input_dev *dev; + int err; - ipoll_dev = input_allocate_polled_device(); - if (!ipoll_dev) + dev = input_allocate_device(); + if (!dev) return -ENOMEM; - - ipoll_dev->private = bma150; - ipoll_dev->open = bma150_poll_open; - ipoll_dev->close = bma150_poll_close; - ipoll_dev->poll = bma150_poll; - ipoll_dev->poll_interval = BMA150_POLL_INTERVAL; - ipoll_dev->poll_interval_min = BMA150_POLL_MIN; - ipoll_dev->poll_interval_max = BMA150_POLL_MAX; - - bma150_init_input_device(bma150, ipoll_dev->input); - - error = input_register_polled_device(ipoll_dev); - if (error) { - input_free_polled_device(ipoll_dev); - return error; + 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_polled = ipoll_dev; - bma150->input = ipoll_dev->input; + bma150->input = dev; return 0; } -static int __devinit bma150_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static void bma150_input_delete(struct bma150_data *bma150) { - const struct bma150_platform_data *pdata = client->dev.platform_data; - const struct bma150_cfg *cfg; - struct bma150_data *bma150; - int chip_id; - int error; + 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)) { - dev_err(&client->dev, "i2c_check_functionality error\n"); - return -EIO; + printk(KERN_INFO "i2c_check_functionality error\n"); + goto exit; } - - chip_id = i2c_smbus_read_byte_data(client, BMA150_CHIP_ID_REG); - if (chip_id != BMA150_CHIP_ID) { - dev_err(&client->dev, "BMA150 chip id error: %d\n", chip_id); - return -EINVAL; + data = kzalloc(sizeof(struct bma150_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; } - bma150 = kzalloc(sizeof(struct bma150_data), GFP_KERNEL); - if (!bma150) - return -ENOMEM; - - bma150->client = client; - - if (pdata) { - if (pdata->irq_gpio_cfg) { - error = pdata->irq_gpio_cfg(); - if (error) { - dev_err(&client->dev, - "IRQ GPIO conf. error %d, error %d\n", - client->irq, error); - goto err_free_mem; - } - } - cfg = &pdata->cfg; - } else { - cfg = &default_cfg; + 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); - error = bma150_initialize(bma150, cfg); - if (error) - goto err_free_mem; - - if (client->irq > 0) { - error = bma150_register_input_device(bma150); - if (error) - goto err_free_mem; - - error = request_threaded_irq(client->irq, - NULL, bma150_irq_thread, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - BMA150_DRIVER, bma150); - if (error) { - dev_err(&client->dev, - "irq request failed %d, error %d\n", - client->irq, error); - input_unregister_device(bma150->input); - goto err_free_mem; - } - } else { - error = bma150_register_polled_device(bma150); - if (error) - goto err_free_mem; - } - i2c_set_clientdata(client, bma150); + 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; - pm_runtime_enable(&client->dev); + schedule_delayed_work(&data->work, + msecs_to_jiffies(atomic_read(&data->delay))); return 0; -err_free_mem: - kfree(bma150); - return error; +error_sysfs: + bma150_input_delete(data); + +kfree_exit: + kfree(data); +exit: + return err; } -static int __devexit bma150_remove(struct i2c_client *client) +static int bma150_suspend(struct i2c_client *client, pm_message_t mesg) { - struct bma150_data *bma150 = i2c_get_clientdata(client); + struct bma150_data *data = i2c_get_clientdata(client); - pm_runtime_disable(&client->dev); + cancel_delayed_work_sync(&data->work); - if (client->irq > 0) { - free_irq(client->irq, bma150); - input_unregister_device(bma150->input); - } else { - input_unregister_polled_device(bma150->input_polled); - input_free_polled_device(bma150->input_polled); - } + bma150_set_mode(client, BMA150_MODE_SLEEP); - kfree(bma150); + if ((data->platform_data) && (data->platform_data->power_off)) + data->platform_data->power_off(); return 0; } -#ifdef CONFIG_PM -static int bma150_suspend(struct device *dev) +static int bma150_resume(struct i2c_client *client) { - struct i2c_client *client = to_i2c_client(dev); - struct bma150_data *bma150 = i2c_get_clientdata(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); - return bma150_set_mode(bma150, BMA150_MODE_SLEEP); + schedule_delayed_work(&data->work, + msecs_to_jiffies(atomic_read(&data->delay))); + + return 0; } -static int bma150_resume(struct device *dev) +static int bma150_remove(struct i2c_client *client) { - struct i2c_client *client = to_i2c_client(dev); - struct bma150_data *bma150 = i2c_get_clientdata(client); + struct bma150_data *data = i2c_get_clientdata(client); - return bma150_set_mode(bma150, BMA150_MODE_NORMAL); -} -#endif + 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); -static UNIVERSAL_DEV_PM_OPS(bma150_pm, bma150_suspend, bma150_resume, NULL); + return 0; +} static const struct i2c_device_id bma150_id[] = { - { "bma150", 0 }, - { "smb380", 0 }, - { "bma023", 0 }, + { SENSOR_NAME, 0 }, { } }; @@ -664,17 +763,29 @@ MODULE_DEVICE_TABLE(i2c, bma150_id); static struct i2c_driver bma150_driver = { .driver = { .owner = THIS_MODULE, - .name = BMA150_DRIVER, - .pm = &bma150_pm, + .name = SENSOR_NAME, }, - .class = I2C_CLASS_HWMON, + .class = I2C_CLASS_HWMON, .id_table = bma150_id, .probe = bma150_probe, - .remove = __devexit_p(bma150_remove), + .remove = bma150_remove, + .detect = bma150_detect, + .suspend = bma150_suspend, + .resume = bma150_resume, }; -module_i2c_driver(bma150_driver); +static int __init BMA150_init(void) +{ + return i2c_add_driver(&bma150_driver); +} + +static void __exit BMA150_exit(void) +{ + i2c_del_driver(&bma150_driver); +} -MODULE_AUTHOR("Albert Zhang "); MODULE_DESCRIPTION("BMA150 driver"); -MODULE_LICENSE("GPL"); + +module_init(BMA150_init); +module_exit(BMA150_exit); + diff --git a/drivers/input/misc/isa1200-ff-memless.c b/drivers/input/misc/isa1200-ff-memless.c new file mode 100644 index 0000000000000000000000000000000000000000..f4e2c354f5aabba6245f78a4efa856b55282a9eb --- /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 0000000000000000000000000000000000000000..cac748a9ac3a61abe55d3da366c10e1d6292ccf2 --- /dev/null +++ b/drivers/input/misc/pmic8058-othc.c @@ -0,0 +1,1188 @@ +/* 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 device *dev; + 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 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 = pm8xxx_readb(dd->dev->parent, dd->othc_base + 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 = pm8xxx_writeb(dd->dev->parent, dd->othc_base + 1, reg); + 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 = pm8xxx_read_irq_stat(dd->dev->parent, 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 = pm8xxx_read_irq_stat(dd->dev->parent, 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); + + /* Accessory has been inserted */ + rc = pm8058_accessory_report(dd, 1); + if (rc) + pr_err("Accessory insertion could not be detected\n"); + + 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 = pm8xxx_read_irq_stat(dd->dev->parent, 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); + + + /* Check the MIC_BIAS status, to check if inserted or removed */ + rc = pm8xxx_read_irq_stat(dd->dev->parent, dd->othc_irq_ir); + if (rc < 0) { + pr_err("Unable to read IR status\n"); + goto fail_ir; + } + + dd->othc_ir_state = rc; + if (dd->othc_ir_state) { + /* disable irq, this gets enabled in the workqueue */ + disable_irq_nosync(dd->othc_irq_ir); + /* Accessory has been inserted, report with detection delay */ + schedule_delayed_work(&dd->detect_work, + msecs_to_jiffies(dd->detection_delay_ms)); + } else { + /* Accessory has been removed, report removal immediately */ + rc = pm8058_accessory_report(dd, 0); + if (rc) + pr_err("Accessory removal could not be detected\n"); + /* Clear existing switch state */ + dd->othc_sw_state = false; + } + +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 = pm8xxx_readb(dd->dev->parent, base_addr, ®); + 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 = pm8xxx_writeb(dd->dev->parent, base_addr, reg); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + /* Control register 2*/ + rc = pm8xxx_readb(dd->dev->parent, base_addr + 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 = pm8xxx_writeb(dd->dev->parent, base_addr + 1, reg); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + /* Control register 3 */ + rc = pm8xxx_readb(dd->dev->parent, base_addr + 2 , ®); + 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 = pm8xxx_writeb(dd->dev->parent, base_addr + 2, reg); + 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 = pm8xxx_read_irq_stat(dd->dev->parent, 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 resource *res; + struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data; + + 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->dev = &pd->dev; + dd->othc_pdata = pdata; + 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-vib-memless.c b/drivers/input/misc/pmic8058-vib-memless.c new file mode 100644 index 0000000000000000000000000000000000000000..ba054000410db77083e5b24de102eca76f5b017e --- /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 0f83d0f1d015c3f6ed9f4b6aaeaaab85cb63d29a..58f9661ae54cd0f3677af9c4db96f0b619df2b9b 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) @@ -98,7 +100,9 @@ static int __devinit pmic8xxx_pwrkey_probe(struct platform_device *pdev) return -EINVAL; } - if (pdata->kpd_trigger_delay_us > 62500) { + /* Valid range of pwr key trigger delay is 1/64 sec to 2 seconds. */ + if (pdata->kpd_trigger_delay_us > USEC_PER_SEC * 2 || + pdata->kpd_trigger_delay_us < USEC_PER_SEC / 64) { dev_err(&pdev->dev, "invalid power key trigger delay\n"); return -EINVAL; } @@ -107,6 +111,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"); @@ -120,8 +126,8 @@ static int __devinit pmic8xxx_pwrkey_probe(struct platform_device *pdev) pwr->phys = "pmic8xxx_pwrkey/input0"; pwr->dev.parent = &pdev->dev; - delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC; - delay = 1 + ilog2(delay); + delay = (pdata->kpd_trigger_delay_us << 6) / USEC_PER_SEC; + delay = ilog2(delay); err = pm8xxx_readb(pdev->dev.parent, PON_CNTL_1, &pon_cntl); if (err < 0) { @@ -153,7 +159,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_any_context_irq(key_press_irq, 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 +167,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_any_context_irq(key_release_irq, 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 9b8db821d5f0b00f498161b4879a87a19dbe065c..33260914c5a3b456d217938006eae945ec56c4dd 100644 --- a/drivers/input/mouse/Kconfig +++ b/drivers/input/mouse/Kconfig @@ -322,6 +322,14 @@ 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. + config MOUSE_SYNAPTICS_USB tristate "Synaptics USB device support" depends on USB_ARCH_HAS_HCD diff --git a/drivers/input/mouse/qci_touchpad.c b/drivers/input/mouse/qci_touchpad.c new file mode 100644 index 0000000000000000000000000000000000000000..ef93a7ea8727560a32949f9de9daa298d274efdc --- /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 1e7c563c090a9a9e21d5c92a42011fcefe5f36c8..77a0affd0c288725e00b937969166024e16ee9be 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -55,6 +55,18 @@ config TOUCHSCREEN_AD7877 To compile this driver as a module, choose M here: the module will be called ad7877. +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 @@ -431,6 +443,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 @@ -457,6 +478,18 @@ config TOUCHSCREEN_SYNAPTICS_I2C_RMI help This enables support for Synaptics RMI over I2C based touchscreens. +config TOUCHSCREEN_SYNAPTICS_RMI4_I2C + tristate "Synaptics i2c touchscreen(ClearPad 3000)" + depends on I2C + select SYNA_MULTI_TOUCH + help + This enables support for Synaptics RMI over I2C based touchscreens(ClearPad 3000). + +config SYNA_MULTI_TOUCH + tristate "Synaptics i2c touchscreen(ClearPad 3000) MutilTouch support" + depends on TOUCHSCREEN_SYNAPTICS_RMI4_I2C + default y + config TOUCHSCREEN_TOUCHRIGHT tristate "Touchright serial touchscreen" select SERIO @@ -798,6 +831,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 @@ -851,4 +905,45 @@ 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_QC + 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. + +config TOUCHSCREEN_FT5X06 + tristate "FocalTech touchscreens" + depends on I2C + help + Say Y here if you have a ft5X06 touchscreen. + Ft5x06 controllers are multi touch controllers which can + report 5 touches at a time. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ft5x06_ts. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 175d641404cf1ad03202b014b01675908eaa355c..5a1eec731c537ef43a52576ecec212efd8032f0a 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -12,10 +12,13 @@ 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_AUO_PIXCIR) += auo-pixcir-ts.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_BU21013) += bu21013_ts.o obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o @@ -26,6 +29,7 @@ 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_EGALAX) += egalax_ts.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o @@ -39,6 +43,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 @@ -52,6 +57,7 @@ obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o obj-$(CONFIG_TOUCHSCREEN_TI_TSCADC) += ti_tscadc.o obj-$(CONFIG_TOUCHSCREEN_TNETV107X) += tnetv107x-ts.o obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI) += synaptics_i2c_rmi.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_RMI4_I2C) +=synaptics/ obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o @@ -70,3 +76,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_QC) += cyttsp-i2c-qc.o diff --git a/drivers/input/touchscreen/atmel_maxtouch.c b/drivers/input/touchscreen/atmel_maxtouch.c new file mode 100644 index 0000000000000000000000000000000000000000..52f1f4a8333ad3257092d87b84958c52ee80178b --- /dev/null +++ b/drivers/input/touchscreen/atmel_maxtouch.c @@ -0,0 +1,2340 @@ +/* + * 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 + +#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); + } + } + input_report_key(mxt->input, BTN_TOUCH, !!active_touches); + 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); + 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"); + goto fail_worker; + } + } else { + dev_err(&client->dev, + "Message length larger than 256 bytes not supported\n"); + goto fail_worker; + } + + 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); + goto fail_worker; + } + + 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); + goto fail_worker; + } + 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); + +fail_worker: + /* Make sure we just didn't miss a interrupt. */ + if (mxt->read_chg() == 0){ + schedule_delayed_work(&mxt->dwork, 0); + } else + enable_irq(mxt->irq); +} + + +/* + * 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. */ + disable_irq_nosync(mxt->irq); + 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; + + /* Make sure we just didn't miss a interrupt. */ + if (mxt->read_chg() == 0) + schedule_delayed_work(&mxt->dwork, 0); + else + enable_irq(mxt->irq); + + mxt->is_suspended = false; + + 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 maxtouch_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); + disable_irq(mxt->irq); + 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/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index 19d4ea65ea017d5da31b3de5e1746086a0a77e84..7fe54983f6e9be0db85c444e2b7aeb2187e209a6 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -3,6 +3,7 @@ * * Copyright (C) 2010 Samsung Electronics Co.Ltd * Author: Joonyoung Shim + * Copyright (c) 2011-2012, Code Aurora Forum. All 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 @@ -20,21 +21,54 @@ #include #include #include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +/* Early-suspend level */ +#define MXT_SUSPEND_LEVEL 1 +#endif + +/* Family ID */ +#define MXT224_ID 0x80 +#define MXT224E_ID 0x81 +#define MXT1386_ID 0xA0 /* Version */ #define MXT_VER_20 20 #define MXT_VER_21 21 #define MXT_VER_22 22 -/* Slave addresses */ -#define MXT_APP_LOW 0x4a -#define MXT_APP_HIGH 0x4b -#define MXT_BOOT_LOW 0x24 -#define MXT_BOOT_HIGH 0x25 +/* I2C slave address pairs */ +struct mxt_address_pair { + int bootloader; + int application; +}; + +static const struct mxt_address_pair mxt_slave_addresses[] = { + { 0x24, 0x4a }, + { 0x25, 0x4b }, + { 0x25, 0x4b }, + { 0x26, 0x4c }, + { 0x27, 0x4d }, + { 0x34, 0x5a }, + { 0x35, 0x5b }, + { 0 }, +}; + +enum mxt_device_state { INIT, APPMODE, BOOTLOADER }; /* Firmware */ #define MXT_FW_NAME "maxtouch.fw" +/* Firmware frame size including frame data and CRC */ +#define MXT_SINGLE_FW_MAX_FRAME_SIZE 278 +#define MXT_CHIPSET_FW_MAX_FRAME_SIZE 534 + /* Registers */ #define MXT_FAMILY_ID 0x00 #define MXT_VARIANT_ID 0x01 @@ -66,6 +100,7 @@ #define MXT_PROCI_PALM_T41 41 #define MXT_PROCI_TOUCHSUPPRESSION_T42 42 #define MXT_PROCI_STYLUS_T47 47 +#define MXT_PROCI_SHIELDLESS_T56 56 #define MXT_PROCG_NOISESUPPRESSION_T48 48 #define MXT_SPT_COMMSCONFIG_T18 18 #define MXT_SPT_GPIOPWM_T19 19 @@ -97,7 +132,7 @@ #define MXT_ACQUIRE_ATCHCALST 6 #define MXT_ACQUIRE_ATCHCALSTHR 7 -/* MXT_TOUCH_MULTI_T9 field */ +/* MXT_TOUCH_MULT_T9 field */ #define MXT_TOUCH_CTRL 0 #define MXT_TOUCH_XORIGIN 1 #define MXT_TOUCH_YORIGIN 2 @@ -172,13 +207,35 @@ #define MXT_VOLTAGE_DEFAULT 2700000 #define MXT_VOLTAGE_STEP 10000 +/* Analog voltage @2.7 V */ +#define MXT_VTG_MIN_UV 2700000 +#define MXT_VTG_MAX_UV 3300000 +#define MXT_ACTIVE_LOAD_UA 15000 +#define MXT_LPM_LOAD_UA 10 +/* Digital voltage @1.8 V */ +#define MXT_VTG_DIG_MIN_UV 1800000 +#define MXT_VTG_DIG_MAX_UV 1800000 +#define MXT_ACTIVE_LOAD_DIG_UA 10000 +#define MXT_LPM_LOAD_DIG_UA 10 + +#define MXT_I2C_VTG_MIN_UV 1800000 +#define MXT_I2C_VTG_MAX_UV 1800000 +#define MXT_I2C_LOAD_UA 10000 +#define MXT_I2C_LPM_LOAD_UA 10 + /* Define for MXT_GEN_COMMAND_T6 */ #define MXT_BOOT_VALUE 0xa5 #define MXT_BACKUP_VALUE 0x55 #define MXT_BACKUP_TIME 25 /* msec */ -#define MXT_RESET_TIME 65 /* msec */ +#define MXT224_RESET_TIME 65 /* msec */ +#define MXT224E_RESET_TIME 22 /* msec */ +#define MXT1386_RESET_TIME 250 /* msec */ +#define MXT_RESET_TIME 250 /* msec */ +#define MXT_RESET_NOCHGREAD 400 /* msec */ -#define MXT_FWRESET_TIME 175 /* msec */ +#define MXT_FWRESET_TIME 1000 /* msec */ + +#define MXT_WAKE_TIME 25 /* Command to unlock bootloader */ #define MXT_UNLOCK_CMD_MSB 0xaa @@ -192,6 +249,8 @@ #define MXT_FRAME_CRC_PASS 0x04 #define MXT_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */ #define MXT_BOOT_STATUS_MASK 0x3f +#define MXT_BOOT_EXTENDED_ID (1 << 5) +#define MXT_BOOT_ID_MASK 0x1f /* Touch status */ #define MXT_SUPPRESS (1 << 1) @@ -212,6 +271,17 @@ #define MXT_MAX_FINGER 10 +#define T7_DATA_SIZE 3 +#define MXT_MAX_RW_TRIES 3 +#define MXT_BLOCK_SIZE 256 +#define MXT_CFG_VERSION_LEN 3 +#define MXT_CFG_VERSION_EQUAL 0 +#define MXT_CFG_VERSION_LESS 1 +#define MXT_CFG_VERSION_GREATER 2 + +#define MXT_DEBUGFS_DIR "atmel_mxt_ts" +#define MXT_DEBUGFS_FILE "object" + struct mxt_info { u8 family_id; u8 variant_id; @@ -252,14 +322,36 @@ struct mxt_data { struct i2c_client *client; struct input_dev *input_dev; const struct mxt_platform_data *pdata; + const struct mxt_config_info *config_info; + enum mxt_device_state state; struct mxt_object *object_table; struct mxt_info info; struct mxt_finger finger[MXT_MAX_FINGER]; unsigned int irq; - unsigned int max_x; - unsigned int max_y; + struct regulator *vcc_ana; + struct regulator *vcc_dig; + struct regulator *vcc_i2c; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + + u8 t7_data[T7_DATA_SIZE]; + u16 t7_start_addr; + u32 keyarray_old; + u32 keyarray_new; + u8 t9_max_reportid; + u8 t9_min_reportid; + u8 t15_max_reportid; + u8 t15_min_reportid; + u8 cfg_version[MXT_CFG_VERSION_LEN]; + int cfg_version_idx; + int t38_start_addr; + bool update_cfg; + const char *fw_name; }; +static struct dentry *debug_base; + static bool mxt_object_readable(unsigned int type) { switch (type) { @@ -280,6 +372,7 @@ static bool mxt_object_readable(unsigned int type) case MXT_PROCI_PALM_T41: case MXT_PROCI_TOUCHSUPPRESSION_T42: case MXT_PROCI_STYLUS_T47: + case MXT_PROCI_SHIELDLESS_T56: case MXT_PROCG_NOISESUPPRESSION_T48: case MXT_SPT_COMMSCONFIG_T18: case MXT_SPT_GPIOPWM_T19: @@ -312,11 +405,13 @@ static bool mxt_object_writable(unsigned int type) case MXT_PROCI_PALM_T41: case MXT_PROCI_TOUCHSUPPRESSION_T42: case MXT_PROCI_STYLUS_T47: + case MXT_PROCI_SHIELDLESS_T56: case MXT_PROCG_NOISESUPPRESSION_T48: case MXT_SPT_COMMSCONFIG_T18: case MXT_SPT_GPIOPWM_T19: case MXT_SPT_SELFTEST_T25: case MXT_SPT_CTECONFIG_T28: + case MXT_SPT_USERDATA_T38: case MXT_SPT_DIGITIZER_T43: case MXT_SPT_CTECONFIG_T46: return true; @@ -339,8 +434,116 @@ static void mxt_dump_message(struct device *dev, dev_dbg(dev, "checksum:\t0x%x\n", message->checksum); } +static int mxt_switch_to_bootloader_address(struct mxt_data *data) +{ + int i; + struct i2c_client *client = data->client; + + if (data->state == BOOTLOADER) { + dev_err(&client->dev, "Already in BOOTLOADER state\n"); + return -EINVAL; + } + + for (i = 0; mxt_slave_addresses[i].application != 0; i++) { + if (mxt_slave_addresses[i].application == client->addr) { + dev_info(&client->dev, "Changing to bootloader address: " + "%02x -> %02x", + client->addr, + mxt_slave_addresses[i].bootloader); + + client->addr = mxt_slave_addresses[i].bootloader; + data->state = BOOTLOADER; + return 0; + } + } + + dev_err(&client->dev, "Address 0x%02x not found in address table", + client->addr); + return -EINVAL; +} + +static int mxt_switch_to_appmode_address(struct mxt_data *data) +{ + int i; + struct i2c_client *client = data->client; + + if (data->state == APPMODE) { + dev_err(&client->dev, "Already in APPMODE state\n"); + return -EINVAL; + } + + for (i = 0; mxt_slave_addresses[i].application != 0; i++) { + if (mxt_slave_addresses[i].bootloader == client->addr) { + dev_info(&client->dev, + "Changing to application mode address: " + "0x%02x -> 0x%02x", + client->addr, + mxt_slave_addresses[i].application); + + client->addr = mxt_slave_addresses[i].application; + data->state = APPMODE; + return 0; + } + } + + dev_err(&client->dev, "Address 0x%02x not found in address table", + client->addr); + return -EINVAL; +} + +static int mxt_get_bootloader_version(struct i2c_client *client, u8 val) +{ + u8 buf[3]; + + if (val | MXT_BOOT_EXTENDED_ID) { + dev_dbg(&client->dev, + "Retrieving extended mode ID information"); + + if (i2c_master_recv(client, &buf[0], 3) != 3) { + dev_err(&client->dev, "%s: i2c recv failed\n", + __func__); + return -EIO; + } + + dev_info(&client->dev, "Bootloader ID:%d Version:%d", + buf[1], buf[2]); + + return buf[0]; + } else { + dev_info(&client->dev, "Bootloader ID:%d", + val & MXT_BOOT_ID_MASK); + + return val; + } +} + +static int mxt_get_bootloader_id(struct i2c_client *client) +{ + u8 val; + u8 buf[3]; + + if (i2c_master_recv(client, &val, 1) != 1) { + dev_err(&client->dev, "%s: i2c recv failed\n", __func__); + return -EIO; + } + + if (val | MXT_BOOT_EXTENDED_ID) { + if (i2c_master_recv(client, &buf[0], 3) != 3) { + dev_err(&client->dev, "%s: i2c recv failed\n", + __func__); + return -EIO; + } + return buf[1]; + } else { + dev_info(&client->dev, "Bootloader ID:%d", + val & MXT_BOOT_ID_MASK); + + return val & MXT_BOOT_ID_MASK; + } +} + static int mxt_check_bootloader(struct i2c_client *client, - unsigned int state) + unsigned int state) { u8 val; @@ -352,19 +555,28 @@ static int mxt_check_bootloader(struct i2c_client *client, switch (state) { case MXT_WAITING_BOOTLOAD_CMD: + val = mxt_get_bootloader_version(client, val); + val &= ~MXT_BOOT_STATUS_MASK; + break; case MXT_WAITING_FRAME_DATA: + case MXT_APP_CRC_FAIL: val &= ~MXT_BOOT_STATUS_MASK; break; case MXT_FRAME_CRC_PASS: if (val == MXT_FRAME_CRC_CHECK) goto recheck; + if (val == MXT_FRAME_CRC_FAIL) { + dev_err(&client->dev, "Bootloader CRC fail\n"); + return -EINVAL; + } break; default: return -EINVAL; } if (val != state) { - dev_err(&client->dev, "Unvalid bootloader mode state\n"); + dev_err(&client->dev, "Invalid bootloader mode state %X\n", + val); return -EINVAL; } @@ -387,7 +599,7 @@ static int mxt_unlock_bootloader(struct i2c_client *client) } static int mxt_fw_write(struct i2c_client *client, - const u8 *data, unsigned int frame_size) + const u8 *data, unsigned int frame_size) { if (i2c_master_send(client, data, frame_size) != frame_size) { dev_err(&client->dev, "%s: i2c send failed\n", __func__); @@ -402,6 +614,7 @@ static int __mxt_read_reg(struct i2c_client *client, { struct i2c_msg xfer[2]; u8 buf[2]; + int i = 0; buf[0] = reg & 0xff; buf[1] = (reg >> 8) & 0xff; @@ -418,12 +631,14 @@ static int __mxt_read_reg(struct i2c_client *client, xfer[1].len = len; xfer[1].buf = val; - if (i2c_transfer(client->adapter, xfer, 2) != 2) { - dev_err(&client->dev, "%s: i2c transfer failed\n", __func__); - return -EIO; - } + do { + if (i2c_transfer(client->adapter, xfer, 2) == 2) + return 0; + msleep(MXT_WAKE_TIME); + } while (++i < MXT_MAX_RW_TRIES); - return 0; + dev_err(&client->dev, "%s: i2c transfer failed\n", __func__); + return -EIO; } static int mxt_read_reg(struct i2c_client *client, u16 reg, u8 *val) @@ -431,20 +646,33 @@ static int mxt_read_reg(struct i2c_client *client, u16 reg, u8 *val) return __mxt_read_reg(client, reg, 1, val); } -static int mxt_write_reg(struct i2c_client *client, u16 reg, u8 val) +static int __mxt_write_reg(struct i2c_client *client, + u16 addr, u16 length, u8 *value) { - u8 buf[3]; + u8 buf[MXT_BLOCK_SIZE + 2]; + int i, tries = 0; - buf[0] = reg & 0xff; - buf[1] = (reg >> 8) & 0xff; - buf[2] = val; + if (length > MXT_BLOCK_SIZE) + return -EINVAL; - if (i2c_master_send(client, buf, 3) != 3) { - dev_err(&client->dev, "%s: i2c send failed\n", __func__); - return -EIO; - } + buf[0] = addr & 0xff; + buf[1] = (addr >> 8) & 0xff; + for (i = 0; i < length; i++) + buf[i + 2] = *value++; - return 0; + do { + if (i2c_master_send(client, buf, length + 2) == (length + 2)) + return 0; + msleep(MXT_WAKE_TIME); + } while (++tries < MXT_MAX_RW_TRIES); + + dev_err(&client->dev, "%s: i2c send failed\n", __func__); + return -EIO; +} + +static int mxt_write_reg(struct i2c_client *client, u16 reg, u8 val) +{ + return __mxt_write_reg(client, reg, 1, &val); } static int mxt_read_object_table(struct i2c_client *client, @@ -526,6 +754,17 @@ static void mxt_input_report(struct mxt_data *data, int single_id) continue; input_mt_slot(input_dev, id); + /* Firmware reports min/max values when the touch is + * outside screen area. Send a release event in + * such cases to avoid unwanted touches. + */ + if (finger[id].x <= data->pdata->panel_minx || + finger[id].x >= data->pdata->panel_maxx || + finger[id].y <= data->pdata->panel_miny || + finger[id].y >= data->pdata->panel_maxy) { + finger[id].status = MXT_RELEASE; + } + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, finger[id].status != MXT_RELEASE); @@ -538,7 +777,7 @@ static void mxt_input_report(struct mxt_data *data, int single_id) input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[id].y); input_report_abs(input_dev, ABS_MT_PRESSURE, - finger[id].pressure); + finger[id].pressure); } else { finger[id].status = 0; } @@ -546,11 +785,18 @@ static void mxt_input_report(struct mxt_data *data, int single_id) input_report_key(input_dev, BTN_TOUCH, finger_num > 0); + if (finger[single_id].x <= data->pdata->panel_minx || + finger[single_id].x >= data->pdata->panel_maxx || + finger[single_id].y <= data->pdata->panel_miny || + finger[single_id].y >= data->pdata->panel_maxy) { + status = MXT_RELEASE; + } + if (status != MXT_RELEASE) { input_report_abs(input_dev, ABS_X, finger[single_id].x); input_report_abs(input_dev, ABS_Y, finger[single_id].y); input_report_abs(input_dev, - ABS_PRESSURE, finger[single_id].pressure); + ABS_PRESSURE, finger[single_id].pressure); } input_sync(input_dev); @@ -584,9 +830,9 @@ static void mxt_input_touchevent(struct mxt_data *data, x = (message->message[1] << 4) | ((message->message[3] >> 4) & 0xf); y = (message->message[2] << 4) | ((message->message[3] & 0xf)); - if (data->max_x < 1024) + if (data->pdata->panel_maxx < 1024) x = x >> 2; - if (data->max_y < 1024) + if (data->pdata->panel_maxy < 1024) y = y >> 2; area = message->message[4]; @@ -606,36 +852,75 @@ static void mxt_input_touchevent(struct mxt_data *data, mxt_input_report(data, id); } +static void mxt_handle_key_array(struct mxt_data *data, + struct mxt_message *message) +{ + u32 keys_changed; + int i; + + if (!data->pdata->key_codes) { + dev_err(&data->client->dev, "keyarray is not supported\n"); + return; + } + + data->keyarray_new = message->message[1] | + (message->message[2] << 8) | + (message->message[3] << 16) | + (message->message[4] << 24); + + keys_changed = data->keyarray_old ^ data->keyarray_new; + + if (!keys_changed) { + dev_dbg(&data->client->dev, "no keys changed\n"); + return; + } + + for (i = 0; i < MXT_KEYARRAY_MAX_KEYS; i++) { + if (!(keys_changed & (1 << i))) + continue; + + input_report_key(data->input_dev, data->pdata->key_codes[i], + (data->keyarray_new & (1 << i))); + input_sync(data->input_dev); + } + + data->keyarray_old = data->keyarray_new; +} + static irqreturn_t mxt_interrupt(int irq, void *dev_id) { struct mxt_data *data = dev_id; struct mxt_message message; - struct mxt_object *object; struct device *dev = &data->client->dev; int id; u8 reportid; - u8 max_reportid; - u8 min_reportid; + + if (data->state != APPMODE) { + dev_err(dev, "Ignoring IRQ - not in APPMODE state\n"); + return IRQ_HANDLED; + } do { if (mxt_read_message(data, &message)) { dev_err(dev, "Failed to read message\n"); goto end; } - reportid = message.reportid; - /* whether reportid is thing of MXT_TOUCH_MULTI_T9 */ - object = mxt_get_object(data, MXT_TOUCH_MULTI_T9); - if (!object) - goto end; + if (!reportid) { + dev_dbg(dev, "Report id 0 is reserved\n"); + continue; + } - max_reportid = object->max_reportid; - min_reportid = max_reportid - object->num_report_ids + 1; - id = reportid - min_reportid; + /* check whether report id is part of T9 or T15 */ + id = reportid - data->t9_min_reportid; - if (reportid >= min_reportid && reportid <= max_reportid) + if (reportid >= data->t9_min_reportid && + reportid <= data->t9_max_reportid) mxt_input_touchevent(data, &message, id); + else if (reportid >= data->t15_min_reportid && + reportid <= data->t15_max_reportid) + mxt_handle_key_array(data, &message); else mxt_dump_message(dev, &message); } while (reportid != 0xff); @@ -646,13 +931,13 @@ static irqreturn_t mxt_interrupt(int irq, void *dev_id) static int mxt_check_reg_init(struct mxt_data *data) { - const struct mxt_platform_data *pdata = data->pdata; + const struct mxt_config_info *config_info = data->config_info; struct mxt_object *object; struct device *dev = &data->client->dev; int index = 0; int i, j, config_offset; - if (!pdata->config) { + if (!config_info) { dev_dbg(dev, "No cfg data defined, skipping reg init\n"); return 0; } @@ -663,18 +948,16 @@ static int mxt_check_reg_init(struct mxt_data *data) if (!mxt_object_writable(object->type)) continue; - for (j = 0; - j < (object->size + 1) * (object->instances + 1); - j++) { + for (j = 0; j < object->size + 1; j++) { config_offset = index + j; - if (config_offset > pdata->config_length) { + if (config_offset > config_info->config_length) { dev_err(dev, "Not enough config data!\n"); return -EINVAL; } mxt_write_object(data, object->type, j, - pdata->config[config_offset]); + config_info->config[config_offset]); } - index += (object->size + 1) * (object->instances + 1); + index += object->size + 1; } return 0; @@ -702,54 +985,6 @@ static int mxt_make_highchg(struct mxt_data *data) return 0; } -static void mxt_handle_pdata(struct mxt_data *data) -{ - const struct mxt_platform_data *pdata = data->pdata; - u8 voltage; - - /* Set touchscreen lines */ - mxt_write_object(data, MXT_TOUCH_MULTI_T9, MXT_TOUCH_XSIZE, - pdata->x_line); - mxt_write_object(data, MXT_TOUCH_MULTI_T9, MXT_TOUCH_YSIZE, - pdata->y_line); - - /* Set touchscreen orient */ - mxt_write_object(data, MXT_TOUCH_MULTI_T9, MXT_TOUCH_ORIENT, - pdata->orient); - - /* Set touchscreen burst length */ - mxt_write_object(data, MXT_TOUCH_MULTI_T9, - MXT_TOUCH_BLEN, pdata->blen); - - /* Set touchscreen threshold */ - mxt_write_object(data, MXT_TOUCH_MULTI_T9, - MXT_TOUCH_TCHTHR, pdata->threshold); - - /* Set touchscreen resolution */ - mxt_write_object(data, MXT_TOUCH_MULTI_T9, - MXT_TOUCH_XRANGE_LSB, (pdata->x_size - 1) & 0xff); - mxt_write_object(data, MXT_TOUCH_MULTI_T9, - MXT_TOUCH_XRANGE_MSB, (pdata->x_size - 1) >> 8); - mxt_write_object(data, MXT_TOUCH_MULTI_T9, - MXT_TOUCH_YRANGE_LSB, (pdata->y_size - 1) & 0xff); - mxt_write_object(data, MXT_TOUCH_MULTI_T9, - MXT_TOUCH_YRANGE_MSB, (pdata->y_size - 1) >> 8); - - /* Set touchscreen voltage */ - if (pdata->voltage) { - if (pdata->voltage < MXT_VOLTAGE_DEFAULT) { - voltage = (MXT_VOLTAGE_DEFAULT - pdata->voltage) / - MXT_VOLTAGE_STEP; - voltage = 0xff - voltage + 1; - } else - voltage = (pdata->voltage - MXT_VOLTAGE_DEFAULT) / - MXT_VOLTAGE_STEP; - - mxt_write_object(data, MXT_SPT_CTECONFIG_T28, - MXT_CTE_VOLTAGE, voltage); - } -} - static int mxt_get_info(struct mxt_data *data) { struct i2c_client *client = data->client; @@ -792,6 +1027,7 @@ static int mxt_get_object_table(struct mxt_data *data) u16 reg; u8 reportid = 0; u8 buf[MXT_OBJECT_SIZE]; + bool found_t38 = false; for (i = 0; i < data->info.object_num; i++) { struct mxt_object *object = data->object_table + i; @@ -812,239 +1048,671 @@ static int mxt_get_object_table(struct mxt_data *data) (object->instances + 1); object->max_reportid = reportid; } + + /* Calculate index for config major version in config array. + * Major version is the first byte in object T38. + */ + if (object->type == MXT_SPT_USERDATA_T38) { + data->t38_start_addr = object->start_address; + found_t38 = true; + } + if (!found_t38 && mxt_object_writable(object->type)) + data->cfg_version_idx += object->size + 1; } return 0; } -static int mxt_initialize(struct mxt_data *data) +static int compare_versions(const u8 *v1, const u8 *v2) { - struct i2c_client *client = data->client; - struct mxt_info *info = &data->info; - int error; - u8 val; - - error = mxt_get_info(data); - if (error) - return error; + int i; - data->object_table = kcalloc(info->object_num, - sizeof(struct mxt_object), - GFP_KERNEL); - if (!data->object_table) { - dev_err(&client->dev, "Failed to allocate memory\n"); - return -ENOMEM; - } + if (!v1 || !v2) + return -EINVAL; - /* Get object table information */ - error = mxt_get_object_table(data); - if (error) - return error; + /* The major version number stays the same across different versions for + * a particular controller on a target. The minor and sub-minor version + * numbers indicate which version is newer. + */ + if (v1[0] != v2[0]) + return -EINVAL; - /* Check register init values */ - error = mxt_check_reg_init(data); - if (error) - return error; + for (i = 1; i < MXT_CFG_VERSION_LEN; i++) { + if (v1[i] > v2[i]) + return MXT_CFG_VERSION_LESS; /* v2 is older */ - mxt_handle_pdata(data); + if (v1[i] < v2[i]) + return MXT_CFG_VERSION_GREATER; /* v2 is newer */ + } - /* Backup to memory */ - mxt_write_object(data, MXT_GEN_COMMAND_T6, - MXT_COMMAND_BACKUPNV, - MXT_BACKUP_VALUE); - msleep(MXT_BACKUP_TIME); + return MXT_CFG_VERSION_EQUAL; /* v1 and v2 are equal */ +} - /* Soft reset */ - mxt_write_object(data, MXT_GEN_COMMAND_T6, - MXT_COMMAND_RESET, 1); - msleep(MXT_RESET_TIME); +static void mxt_check_config_version(struct mxt_data *data, + const struct mxt_config_info *cfg_info, + bool match_major, + const u8 **cfg_version_found, + bool *found_cfg_major_match) +{ + const u8 *cfg_version; + int result = -EINVAL; - /* Update matrix size at info struct */ - error = mxt_read_reg(client, MXT_MATRIX_X_SIZE, &val); - if (error) - return error; - info->matrix_xsize = val; + cfg_version = cfg_info->config + data->cfg_version_idx; - error = mxt_read_reg(client, MXT_MATRIX_Y_SIZE, &val); - if (error) - return error; - info->matrix_ysize = val; + if (*cfg_version_found) + result = compare_versions(*cfg_version_found, cfg_version); - dev_info(&client->dev, - "Family ID: %d Variant ID: %d Version: %d Build: %d\n", - info->family_id, info->variant_id, info->version, - info->build); + if (match_major) { + if (result >= MXT_CFG_VERSION_EQUAL) + *found_cfg_major_match = true; - dev_info(&client->dev, - "Matrix X Size: %d Matrix Y Size: %d Object Num: %d\n", - info->matrix_xsize, info->matrix_ysize, - info->object_num); + if (result == MXT_CFG_VERSION_EQUAL || + result == MXT_CFG_VERSION_GREATER) { + data->config_info = cfg_info; + data->fw_name = cfg_info->fw_name; + *cfg_version_found = cfg_version; + } - return 0; + if (result == MXT_CFG_VERSION_GREATER) + data->update_cfg = true; + } else if (!*cfg_version_found || result == MXT_CFG_VERSION_GREATER) { + data->config_info = cfg_info; + data->fw_name = cfg_info->fw_name; + data->update_cfg = true; + *cfg_version_found = cfg_version; + } } -static void mxt_calc_resolution(struct mxt_data *data) +/* If the controller's config version has a non-zero major number, call this + * function with match_major = true to look for the latest config present in + * the pdata based on matching family id, variant id, f/w version, build, and + * config major number. If the controller is programmed with wrong config data + * previously, call this function with match_major = false to look for latest + * config based on based on matching family id, variant id, f/w version and + * build only. + */ +static int mxt_search_config_array(struct mxt_data *data, bool match_major) { - unsigned int max_x = data->pdata->x_size - 1; - unsigned int max_y = data->pdata->y_size - 1; - if (data->pdata->orient & MXT_XY_SWITCH) { - data->max_x = max_y; - data->max_y = max_x; - } else { - data->max_x = max_x; - data->max_y = max_y; - } -} + const struct mxt_platform_data *pdata = data->pdata; + const struct mxt_config_info *cfg_info; + const struct mxt_info *info = &data->info; + const u8 *cfg_version_found; + bool found_cfg_major_match = false; + int i; -static ssize_t mxt_object_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mxt_data *data = dev_get_drvdata(dev); - struct mxt_object *object; - int count = 0; - int i, j; - int error; - u8 val; + cfg_version_found = match_major ? data->cfg_version : NULL; - for (i = 0; i < data->info.object_num; i++) { - object = data->object_table + i; + for (i = 0; i < pdata->config_array_size; i++) { - count += snprintf(buf + count, PAGE_SIZE - count, - "Object[%d] (Type %d)\n", - i + 1, object->type); - if (count >= PAGE_SIZE) - return PAGE_SIZE - 1; + cfg_info = &pdata->config_array[i]; - if (!mxt_object_readable(object->type)) { - count += snprintf(buf + count, PAGE_SIZE - count, - "\n"); - if (count >= PAGE_SIZE) - return PAGE_SIZE - 1; + if (!cfg_info->config || !cfg_info->config_length) continue; - } - for (j = 0; j < object->size + 1; j++) { - error = mxt_read_object(data, - object->type, j, &val); - if (error) - return error; + if (info->family_id == cfg_info->family_id && + info->variant_id == cfg_info->variant_id && + info->version == cfg_info->version && + info->build == cfg_info->build) { - count += snprintf(buf + count, PAGE_SIZE - count, - "\t[%2d]: %02x (%d)\n", j, val, val); - if (count >= PAGE_SIZE) - return PAGE_SIZE - 1; + mxt_check_config_version(data, cfg_info, match_major, + &cfg_version_found, &found_cfg_major_match); } - - count += snprintf(buf + count, PAGE_SIZE - count, "\n"); - if (count >= PAGE_SIZE) - return PAGE_SIZE - 1; } - return count; + if (data->config_info || found_cfg_major_match) + return 0; + + data->config_info = NULL; + data->fw_name = NULL; + + return -EINVAL; } -static int mxt_load_fw(struct device *dev, const char *fn) +static int mxt_get_config(struct mxt_data *data) { - struct mxt_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - const struct firmware *fw = NULL; - unsigned int frame_size; - unsigned int pos = 0; - int ret; + const struct mxt_platform_data *pdata = data->pdata; + struct device *dev = &data->client->dev; + struct mxt_object *object; + int error; - ret = request_firmware(&fw, fn, dev); - if (ret) { - dev_err(dev, "Unable to open firmware %s\n", fn); - return ret; + if (!pdata->config_array || !pdata->config_array_size) { + dev_dbg(dev, "No cfg data provided by platform data\n"); + return 0; } - /* Change to the bootloader mode */ - mxt_write_object(data, MXT_GEN_COMMAND_T6, - MXT_COMMAND_RESET, MXT_BOOT_VALUE); - msleep(MXT_RESET_TIME); + /* Get current config version */ + object = mxt_get_object(data, MXT_SPT_USERDATA_T38); + if (!object) { + dev_err(dev, "Unable to obtain USERDATA object\n"); + return -EINVAL; + } - /* Change to slave address of bootloader */ - if (client->addr == MXT_APP_LOW) - client->addr = MXT_BOOT_LOW; - else - client->addr = MXT_BOOT_HIGH; + error = __mxt_read_reg(data->client, object->start_address, + sizeof(data->cfg_version), data->cfg_version); + if (error) { + dev_err(dev, "Unable to read config version\n"); + return error; + } + dev_info(dev, "Current config version on the controller is %d.%d.%d\n", + data->cfg_version[0], data->cfg_version[1], + data->cfg_version[2]); + + /* It is possible that the config data on the controller is not + * versioned and the version number returns 0. In this case, + * find a match without the config version checking. + */ + error = mxt_search_config_array(data, + data->cfg_version[0] != 0 ? true : false); + if (error) { + /* If a match wasn't found for a non-zero config version, + * it means the controller has the wrong config data. Search + * for a best match based on controller and firmware version, + * but not config version. + */ + if (data->cfg_version[0]) + error = mxt_search_config_array(data, false); + if (error) { + dev_err(dev, + "Unable to find matching config in pdata\n"); + return error; + } + } - ret = mxt_check_bootloader(client, MXT_WAITING_BOOTLOAD_CMD); - if (ret) - goto out; + return 0; +} - /* Unlock bootloader */ - mxt_unlock_bootloader(client); +static void mxt_reset_delay(struct mxt_data *data) +{ + struct mxt_info *info = &data->info; + + switch (info->family_id) { + case MXT224_ID: + msleep(MXT224_RESET_TIME); + break; + case MXT224E_ID: + msleep(MXT224E_RESET_TIME); + break; + case MXT1386_ID: + msleep(MXT1386_RESET_TIME); + break; + default: + msleep(MXT_RESET_TIME); + } +} + +static int mxt_backup_nv(struct mxt_data *data) +{ + int error; + u8 command_register; + int timeout_counter = 0; + + /* Backup to memory */ + mxt_write_object(data, MXT_GEN_COMMAND_T6, + MXT_COMMAND_BACKUPNV, + MXT_BACKUP_VALUE); + msleep(MXT_BACKUP_TIME); + + do { + error = mxt_read_object(data, MXT_GEN_COMMAND_T6, + MXT_COMMAND_BACKUPNV, + &command_register); + if (error) + return error; + + usleep_range(1000, 2000); + + } while ((command_register != 0) && (++timeout_counter <= 100)); + + if (timeout_counter > 100) { + dev_err(&data->client->dev, "No response after backup!\n"); + return -EIO; + } + + /* Soft reset */ + mxt_write_object(data, MXT_GEN_COMMAND_T6, MXT_COMMAND_RESET, 1); + + mxt_reset_delay(data); + + return 0; +} + +static int mxt_save_objects(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct mxt_object *t7_object; + struct mxt_object *t9_object; + struct mxt_object *t15_object; + int error; + + /* Store T7 and T9 locally, used in suspend/resume operations */ + t7_object = mxt_get_object(data, MXT_GEN_POWER_T7); + if (!t7_object) { + dev_err(&client->dev, "Failed to get T7 object\n"); + return -EINVAL; + } + + data->t7_start_addr = t7_object->start_address; + error = __mxt_read_reg(client, data->t7_start_addr, + T7_DATA_SIZE, data->t7_data); + if (error < 0) { + dev_err(&client->dev, + "Failed to save current power state\n"); + return error; + } + + /* Store T9, T15's min and max report ids */ + t9_object = mxt_get_object(data, MXT_TOUCH_MULTI_T9); + if (!t9_object) { + dev_err(&client->dev, "Failed to get T9 object\n"); + return -EINVAL; + } + data->t9_max_reportid = t9_object->max_reportid; + data->t9_min_reportid = t9_object->max_reportid - + t9_object->num_report_ids + 1; + + if (data->pdata->key_codes) { + t15_object = mxt_get_object(data, MXT_TOUCH_KEYARRAY_T15); + if (!t15_object) + dev_dbg(&client->dev, "T15 object is not available\n"); + else { + data->t15_max_reportid = t15_object->max_reportid; + data->t15_min_reportid = t15_object->max_reportid - + t15_object->num_report_ids + 1; + } + } + + return 0; +} + +static int mxt_initialize(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct mxt_info *info = &data->info; + int error; + u8 val; + const u8 *cfg_ver; + + error = mxt_get_info(data); + if (error) { + /* Try bootloader mode */ + error = mxt_switch_to_bootloader_address(data); + if (error) + return error; + + error = mxt_check_bootloader(client, MXT_APP_CRC_FAIL); + if (error) + return error; + + dev_err(&client->dev, "Application CRC failure\n"); + data->state = BOOTLOADER; + + return 0; + } + + dev_info(&client->dev, + "Family ID: %d Variant ID: %d Version: %d.%d " + "Build: 0x%02X Object Num: %d\n", + info->family_id, info->variant_id, + info->version >> 4, info->version & 0xf, + info->build, info->object_num); + + data->state = APPMODE; + + data->object_table = kcalloc(info->object_num, + sizeof(struct mxt_object), + GFP_KERNEL); + if (!data->object_table) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + /* Get object table information */ + error = mxt_get_object_table(data); + if (error) + goto free_object_table; + + /* Get config data from platform data */ + error = mxt_get_config(data); + if (error) + dev_dbg(&client->dev, "Config info not found.\n"); + + /* Check register init values */ + if (data->config_info && data->config_info->config) { + if (data->update_cfg) { + error = mxt_check_reg_init(data); + if (error) { + dev_err(&client->dev, + "Failed to check reg init value\n"); + goto free_object_table; + } + + error = mxt_backup_nv(data); + if (error) { + dev_err(&client->dev, "Failed to back up NV\n"); + goto free_object_table; + } + + cfg_ver = data->config_info->config + + data->cfg_version_idx; + dev_info(&client->dev, + "Config updated from %d.%d.%d to %d.%d.%d\n", + data->cfg_version[0], data->cfg_version[1], + data->cfg_version[2], + cfg_ver[0], cfg_ver[1], cfg_ver[2]); + + memcpy(data->cfg_version, cfg_ver, MXT_CFG_VERSION_LEN); + } + } else { + dev_info(&client->dev, + "No cfg data defined, skipping check reg init\n"); + } + + error = mxt_save_objects(data); + if (error) + goto free_object_table; + + /* Update matrix size at info struct */ + error = mxt_read_reg(client, MXT_MATRIX_X_SIZE, &val); + if (error) + goto free_object_table; + info->matrix_xsize = val; + + error = mxt_read_reg(client, MXT_MATRIX_Y_SIZE, &val); + if (error) + goto free_object_table; + info->matrix_ysize = val; + + dev_info(&client->dev, + "Matrix X Size: %d Matrix Y Size: %d\n", + info->matrix_xsize, info->matrix_ysize); + + return 0; + +free_object_table: + kfree(data->object_table); + return error; +} + +static ssize_t mxt_object_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_object *object; + int count = 0; + int i, j; + int error; + u8 val; + + for (i = 0; i < data->info.object_num; i++) { + object = data->object_table + i; + + count += snprintf(buf + count, PAGE_SIZE - count, + "Object[%d] (Type %d)\n", + i + 1, object->type); + if (count >= PAGE_SIZE) + return PAGE_SIZE - 1; + + if (!mxt_object_readable(object->type)) { + count += snprintf(buf + count, PAGE_SIZE - count, + "\n"); + if (count >= PAGE_SIZE) + return PAGE_SIZE - 1; + continue; + } + + for (j = 0; j < object->size + 1; j++) { + error = mxt_read_object(data, + object->type, j, &val); + if (error) + return error; + + count += snprintf(buf + count, PAGE_SIZE - count, + "\t[%2d]: %02x (%d)\n", j, val, val); + if (count >= PAGE_SIZE) + return PAGE_SIZE - 1; + } + + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + if (count >= PAGE_SIZE) + return PAGE_SIZE - 1; + } + + return count; +} + +static int strtobyte(const char *data, u8 *value) +{ + char str[3]; + + str[0] = data[0]; + str[1] = data[1]; + str[2] = '\0'; + + return kstrtou8(str, 16, value); +} + +static int mxt_load_fw(struct device *dev, const char *fn) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + const struct firmware *fw = NULL; + unsigned int frame_size; + unsigned int retry = 0; + unsigned int pos = 0; + int ret, i, max_frame_size; + u8 *frame; + + switch (data->info.family_id) { + case MXT224_ID: + max_frame_size = MXT_SINGLE_FW_MAX_FRAME_SIZE; + break; + case MXT1386_ID: + max_frame_size = MXT_CHIPSET_FW_MAX_FRAME_SIZE; + break; + default: + return -EINVAL; + } + + frame = kmalloc(max_frame_size, GFP_KERNEL); + if (!frame) { + dev_err(dev, "Unable to allocate memory for frame data\n"); + return -ENOMEM; + } + + ret = request_firmware(&fw, fn, dev); + if (ret < 0) { + dev_err(dev, "Unable to open firmware %s\n", fn); + goto free_frame; + } + + if (data->state != BOOTLOADER) { + /* Change to the bootloader mode */ + mxt_write_object(data, MXT_GEN_COMMAND_T6, + MXT_COMMAND_RESET, MXT_BOOT_VALUE); + mxt_reset_delay(data); + + ret = mxt_switch_to_bootloader_address(data); + if (ret) + goto release_firmware; + } + + ret = mxt_check_bootloader(client, MXT_WAITING_BOOTLOAD_CMD); + if (ret) { + /* Bootloader may still be unlocked from previous update + * attempt */ + ret = mxt_check_bootloader(client, + MXT_WAITING_FRAME_DATA); + + if (ret) + goto return_to_app_mode; + } else { + dev_info(dev, "Unlocking bootloader\n"); + /* Unlock bootloader */ + mxt_unlock_bootloader(client); + } while (pos < fw->size) { ret = mxt_check_bootloader(client, MXT_WAITING_FRAME_DATA); if (ret) - goto out; + goto release_firmware; + + /* Get frame length MSB */ + ret = strtobyte(fw->data + pos, frame); + if (ret) + goto release_firmware; - frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1)); + /* Get frame length LSB */ + ret = strtobyte(fw->data + pos + 2, frame + 1); + if (ret) + goto release_firmware; + + frame_size = ((*frame << 8) | *(frame + 1)); /* We should add 2 at frame size as the the firmware data is not * included the CRC bytes. */ frame_size += 2; + if (frame_size > max_frame_size) { + dev_err(dev, "Invalid frame size - %d\n", frame_size); + ret = -EINVAL; + goto release_firmware; + } + + /* Convert frame data and CRC from hex to binary */ + for (i = 2; i < frame_size; i++) { + ret = strtobyte(fw->data + pos + i * 2, frame + i); + if (ret) + goto release_firmware; + } + /* Write one frame to device */ - mxt_fw_write(client, fw->data + pos, frame_size); + mxt_fw_write(client, frame, frame_size); ret = mxt_check_bootloader(client, MXT_FRAME_CRC_PASS); - if (ret) - goto out; + if (ret) { + retry++; - pos += frame_size; + /* Back off by 20ms per retry */ + msleep(retry * 20); - dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw->size); + if (retry > 20) + goto release_firmware; + } else { + retry = 0; + pos += frame_size * 2; + dev_dbg(dev, "Updated %d/%zd bytes\n", pos, fw->size); + } } -out: +return_to_app_mode: + mxt_switch_to_appmode_address(data); +release_firmware: release_firmware(fw); - - /* Change to slave address of application */ - if (client->addr == MXT_BOOT_LOW) - client->addr = MXT_APP_LOW; - else - client->addr = MXT_APP_HIGH; +free_frame: + kfree(frame); return ret; } +static const char * +mxt_search_fw_name(struct mxt_data *data, u8 bootldr_id) +{ + const struct mxt_platform_data *pdata = data->pdata; + const struct mxt_config_info *cfg_info; + const char *fw_name = NULL; + int i; + + for (i = 0; i < pdata->config_array_size; i++) { + cfg_info = &pdata->config_array[i]; + if (bootldr_id == cfg_info->bootldr_id && cfg_info->fw_name) { + data->config_info = cfg_info; + data->info.family_id = cfg_info->family_id; + fw_name = cfg_info->fw_name; + } + } + + return fw_name; +} + static ssize_t mxt_update_fw_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mxt_data *data = dev_get_drvdata(dev); int error; + const char *fw_name; + u8 bootldr_id; + u8 cfg_version[MXT_CFG_VERSION_LEN] = {0}; + + /* If fw_name is set, then the existing firmware has an upgrade */ + if (!data->fw_name) { + /* + * If the device boots up in the bootloader mode, check if + * there is a firmware to upgrade. + */ + if (data->state == BOOTLOADER) { + bootldr_id = mxt_get_bootloader_id(data->client); + if (bootldr_id <= 0) { + dev_err(dev, + "Unable to retrieve bootloader id\n"); + return -EINVAL; + } + fw_name = mxt_search_fw_name(data, bootldr_id); + if (fw_name == NULL) { + dev_err(dev, + "Unable to find fw from bootloader id\n"); + return -EINVAL; + } + } else { + /* In APPMODE, if the f/w name does not exist, quit */ + dev_err(dev, + "Firmware name not specified in platform data\n"); + return -EINVAL; + } + } else { + fw_name = data->fw_name; + } + + dev_info(dev, "Upgrading the firmware file to %s\n", fw_name); disable_irq(data->irq); - error = mxt_load_fw(dev, MXT_FW_NAME); + error = mxt_load_fw(dev, fw_name); if (error) { dev_err(dev, "The firmware update failed(%d)\n", error); count = error; } else { - dev_dbg(dev, "The firmware update succeeded\n"); + dev_info(dev, "The firmware update succeeded\n"); /* Wait for reset */ msleep(MXT_FWRESET_TIME); + data->state = INIT; kfree(data->object_table); data->object_table = NULL; + data->cfg_version_idx = 0; + data->update_cfg = false; + + error = __mxt_write_reg(data->client, data->t38_start_addr, + sizeof(cfg_version), cfg_version); + if (error) + dev_err(dev, + "Unable to zero out config version after fw upgrade\n"); mxt_initialize(data); } - enable_irq(data->irq); + if (data->state == APPMODE) { + enable_irq(data->irq); - error = mxt_make_highchg(data); - if (error) - return error; + error = mxt_make_highchg(data); + if (error) + return error; + } return count; } @@ -1062,25 +1730,50 @@ static const struct attribute_group mxt_attr_group = { .attrs = mxt_attrs, }; -static void mxt_start(struct mxt_data *data) +static int mxt_start(struct mxt_data *data) { - /* Touch enable */ - mxt_write_object(data, - MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0x83); + int error; + + /* restore the old power state values and reenable touch */ + error = __mxt_write_reg(data->client, data->t7_start_addr, + T7_DATA_SIZE, data->t7_data); + if (error < 0) { + dev_err(&data->client->dev, + "failed to restore old power state\n"); + return error; + } + + return 0; } -static void mxt_stop(struct mxt_data *data) +static int mxt_stop(struct mxt_data *data) { - /* Touch disable */ - mxt_write_object(data, - MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0); + int error; + u8 t7_data[T7_DATA_SIZE] = {0}; + + error = __mxt_write_reg(data->client, data->t7_start_addr, + T7_DATA_SIZE, t7_data); + if (error < 0) { + dev_err(&data->client->dev, + "failed to configure deep sleep mode\n"); + return error; + } + + return 0; } static int mxt_input_open(struct input_dev *dev) { struct mxt_data *data = input_get_drvdata(dev); + int error; - mxt_start(data); + if (data->state == APPMODE) { + error = mxt_start(data); + if (error < 0) { + dev_err(&data->client->dev, "mxt_start failed in input_open\n"); + return error; + } + } return 0; } @@ -1088,8 +1781,447 @@ static int mxt_input_open(struct input_dev *dev) static void mxt_input_close(struct input_dev *dev) { struct mxt_data *data = input_get_drvdata(dev); + int error; + + if (data->state == APPMODE) { + error = mxt_stop(data); + if (error < 0) + dev_err(&data->client->dev, "mxt_stop failed in input_close\n"); + } +} + +static int reg_set_optimum_mode_check(struct regulator *reg, int load_uA) +{ + return (regulator_count_voltages(reg) > 0) ? + regulator_set_optimum_mode(reg, load_uA) : 0; +} + +static int mxt_power_on(struct mxt_data *data, bool on) +{ + int rc; + + if (on == false) + goto power_off; + + rc = reg_set_optimum_mode_check(data->vcc_ana, MXT_ACTIVE_LOAD_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_ana set_opt failed rc=%d\n", rc); + return rc; + } + + rc = regulator_enable(data->vcc_ana); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_ana enable failed rc=%d\n", rc); + goto error_reg_en_vcc_ana; + } + + if (data->pdata->digital_pwr_regulator) { + rc = reg_set_optimum_mode_check(data->vcc_dig, + MXT_ACTIVE_LOAD_DIG_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_dig set_opt failed rc=%d\n", + rc); + goto error_reg_opt_vcc_dig; + } + + rc = regulator_enable(data->vcc_dig); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_dig enable failed rc=%d\n", rc); + goto error_reg_en_vcc_dig; + } + } + + if (data->pdata->i2c_pull_up) { + rc = reg_set_optimum_mode_check(data->vcc_i2c, MXT_I2C_LOAD_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_i2c set_opt failed rc=%d\n", rc); + goto error_reg_opt_i2c; + } + + rc = regulator_enable(data->vcc_i2c); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_i2c enable failed rc=%d\n", rc); + goto error_reg_en_vcc_i2c; + } + } + + msleep(130); + + return 0; + +error_reg_en_vcc_i2c: + if (data->pdata->i2c_pull_up) + reg_set_optimum_mode_check(data->vcc_i2c, 0); +error_reg_opt_i2c: + if (data->pdata->digital_pwr_regulator) + regulator_disable(data->vcc_dig); +error_reg_en_vcc_dig: + if (data->pdata->digital_pwr_regulator) + reg_set_optimum_mode_check(data->vcc_dig, 0); +error_reg_opt_vcc_dig: + regulator_disable(data->vcc_ana); +error_reg_en_vcc_ana: + reg_set_optimum_mode_check(data->vcc_ana, 0); + return rc; + +power_off: + reg_set_optimum_mode_check(data->vcc_ana, 0); + regulator_disable(data->vcc_ana); + if (data->pdata->digital_pwr_regulator) { + reg_set_optimum_mode_check(data->vcc_dig, 0); + regulator_disable(data->vcc_dig); + } + if (data->pdata->i2c_pull_up) { + reg_set_optimum_mode_check(data->vcc_i2c, 0); + regulator_disable(data->vcc_i2c); + } + msleep(50); + return 0; +} + +static int mxt_regulator_configure(struct mxt_data *data, bool on) +{ + int rc; + + if (on == false) + goto hw_shutdown; + + data->vcc_ana = regulator_get(&data->client->dev, "vdd_ana"); + if (IS_ERR(data->vcc_ana)) { + rc = PTR_ERR(data->vcc_ana); + dev_err(&data->client->dev, + "Regulator get failed vcc_ana rc=%d\n", rc); + return rc; + } + + if (regulator_count_voltages(data->vcc_ana) > 0) { + rc = regulator_set_voltage(data->vcc_ana, MXT_VTG_MIN_UV, + MXT_VTG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "regulator set_vtg failed rc=%d\n", rc); + goto error_set_vtg_vcc_ana; + } + } + if (data->pdata->digital_pwr_regulator) { + data->vcc_dig = regulator_get(&data->client->dev, "vdd_dig"); + if (IS_ERR(data->vcc_dig)) { + rc = PTR_ERR(data->vcc_dig); + dev_err(&data->client->dev, + "Regulator get dig failed rc=%d\n", rc); + goto error_get_vtg_vcc_dig; + } + + if (regulator_count_voltages(data->vcc_dig) > 0) { + rc = regulator_set_voltage(data->vcc_dig, + MXT_VTG_DIG_MIN_UV, MXT_VTG_DIG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "regulator set_vtg failed rc=%d\n", rc); + goto error_set_vtg_vcc_dig; + } + } + } + if (data->pdata->i2c_pull_up) { + data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c"); + if (IS_ERR(data->vcc_i2c)) { + rc = PTR_ERR(data->vcc_i2c); + dev_err(&data->client->dev, + "Regulator get failed rc=%d\n", rc); + goto error_get_vtg_i2c; + } + if (regulator_count_voltages(data->vcc_i2c) > 0) { + rc = regulator_set_voltage(data->vcc_i2c, + MXT_I2C_VTG_MIN_UV, MXT_I2C_VTG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "regulator set_vtg failed rc=%d\n", rc); + goto error_set_vtg_i2c; + } + } + } + + return 0; + +error_set_vtg_i2c: + regulator_put(data->vcc_i2c); +error_get_vtg_i2c: + if (data->pdata->digital_pwr_regulator) + if (regulator_count_voltages(data->vcc_dig) > 0) + regulator_set_voltage(data->vcc_dig, 0, + MXT_VTG_DIG_MAX_UV); +error_set_vtg_vcc_dig: + if (data->pdata->digital_pwr_regulator) + regulator_put(data->vcc_dig); +error_get_vtg_vcc_dig: + if (regulator_count_voltages(data->vcc_ana) > 0) + regulator_set_voltage(data->vcc_ana, 0, MXT_VTG_MAX_UV); +error_set_vtg_vcc_ana: + regulator_put(data->vcc_ana); + return rc; + +hw_shutdown: + if (regulator_count_voltages(data->vcc_ana) > 0) + regulator_set_voltage(data->vcc_ana, 0, MXT_VTG_MAX_UV); + regulator_put(data->vcc_ana); + if (data->pdata->digital_pwr_regulator) { + if (regulator_count_voltages(data->vcc_dig) > 0) + regulator_set_voltage(data->vcc_dig, 0, + MXT_VTG_DIG_MAX_UV); + regulator_put(data->vcc_dig); + } + if (data->pdata->i2c_pull_up) { + if (regulator_count_voltages(data->vcc_i2c) > 0) + regulator_set_voltage(data->vcc_i2c, 0, + MXT_I2C_VTG_MAX_UV); + regulator_put(data->vcc_i2c); + } + return 0; +} + +#ifdef CONFIG_PM +static int mxt_regulator_lpm(struct mxt_data *data, bool on) +{ + + int rc; + + if (on == false) + goto regulator_hpm; + + rc = reg_set_optimum_mode_check(data->vcc_ana, MXT_LPM_LOAD_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_ana set_opt failed rc=%d\n", rc); + goto fail_regulator_lpm; + } + + if (data->pdata->digital_pwr_regulator) { + rc = reg_set_optimum_mode_check(data->vcc_dig, + MXT_LPM_LOAD_DIG_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_dig set_opt failed rc=%d\n", rc); + goto fail_regulator_lpm; + } + } + + if (data->pdata->i2c_pull_up) { + rc = reg_set_optimum_mode_check(data->vcc_i2c, + MXT_I2C_LPM_LOAD_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_i2c set_opt failed rc=%d\n", rc); + goto fail_regulator_lpm; + } + } + + return 0; + +regulator_hpm: + + rc = reg_set_optimum_mode_check(data->vcc_ana, MXT_ACTIVE_LOAD_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_ana set_opt failed rc=%d\n", rc); + goto fail_regulator_hpm; + } + + if (data->pdata->digital_pwr_regulator) { + rc = reg_set_optimum_mode_check(data->vcc_dig, + MXT_ACTIVE_LOAD_DIG_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_dig set_opt failed rc=%d\n", rc); + goto fail_regulator_hpm; + } + } + + if (data->pdata->i2c_pull_up) { + rc = reg_set_optimum_mode_check(data->vcc_i2c, MXT_I2C_LOAD_UA); + if (rc < 0) { + dev_err(&data->client->dev, + "Regulator vcc_i2c set_opt failed rc=%d\n", rc); + goto fail_regulator_hpm; + } + } + + return 0; + +fail_regulator_lpm: + reg_set_optimum_mode_check(data->vcc_ana, MXT_ACTIVE_LOAD_UA); + if (data->pdata->digital_pwr_regulator) + reg_set_optimum_mode_check(data->vcc_dig, + MXT_ACTIVE_LOAD_DIG_UA); + if (data->pdata->i2c_pull_up) + reg_set_optimum_mode_check(data->vcc_i2c, MXT_I2C_LOAD_UA); + + return rc; + +fail_regulator_hpm: + reg_set_optimum_mode_check(data->vcc_ana, MXT_LPM_LOAD_UA); + if (data->pdata->digital_pwr_regulator) + reg_set_optimum_mode_check(data->vcc_dig, MXT_LPM_LOAD_DIG_UA); + if (data->pdata->i2c_pull_up) + reg_set_optimum_mode_check(data->vcc_i2c, MXT_I2C_LPM_LOAD_UA); + + return rc; +} + +static int mxt_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mxt_data *data = i2c_get_clientdata(client); + struct input_dev *input_dev = data->input_dev; + int error; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) { + error = mxt_stop(data); + if (error < 0) { + dev_err(dev, "mxt_stop failed in suspend\n"); + mutex_unlock(&input_dev->mutex); + return error; + } + + } + + mutex_unlock(&input_dev->mutex); + + /* put regulators in low power mode */ + error = mxt_regulator_lpm(data, true); + if (error < 0) { + dev_err(dev, "failed to enter low power mode\n"); + return error; + } + + return 0; +} + +static int mxt_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mxt_data *data = i2c_get_clientdata(client); + struct input_dev *input_dev = data->input_dev; + int error; + + /* put regulators in high power mode */ + error = mxt_regulator_lpm(data, false); + if (error < 0) { + dev_err(dev, "failed to enter high power mode\n"); + return error; + } + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) { + error = mxt_start(data); + if (error < 0) { + dev_err(dev, "mxt_start failed in resume\n"); + mutex_unlock(&input_dev->mutex); + return error; + } + } + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +#if defined(CONFIG_HAS_EARLYSUSPEND) +static void mxt_early_suspend(struct early_suspend *h) +{ + struct mxt_data *data = container_of(h, struct mxt_data, early_suspend); + + mxt_suspend(&data->client->dev); +} + +static void mxt_late_resume(struct early_suspend *h) +{ + struct mxt_data *data = container_of(h, struct mxt_data, early_suspend); + + mxt_resume(&data->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 mxt_debugfs_object_show(struct seq_file *m, void *v) +{ + struct mxt_data *data = m->private; + struct mxt_object *object; + struct device *dev = &data->client->dev; + int i, j, k; + int error; + int obj_size; + u8 val; + + for (i = 0; i < data->info.object_num; i++) { + object = data->object_table + i; + obj_size = object->size + 1; + + seq_printf(m, "Object[%d] (Type %d)\n", i + 1, object->type); + + for (j = 0; j < object->instances + 1; j++) { + seq_printf(m, "[Instance %d]\n", j); + + for (k = 0; k < obj_size; k++) { + error = mxt_read_object(data, object->type, + j * obj_size + k, &val); + if (error) { + dev_err(dev, + "Failed to read object %d " + "instance %d at offset %d\n", + object->type, j, k); + return error; + } + + seq_printf(m, "Byte %d: 0x%02x (%d)\n", + k, val, val); + } + } + } + + return 0; +} - mxt_stop(data); +static int mxt_debugfs_object_open(struct inode *inode, struct file *file) +{ + return single_open(file, mxt_debugfs_object_show, inode->i_private); +} + +static const struct file_operations mxt_object_fops = { + .owner = THIS_MODULE, + .open = mxt_debugfs_object_open, + .read = seq_read, + .release = single_release, +}; + +static void __devinit mxt_debugfs_init(struct mxt_data *data) +{ + debug_base = debugfs_create_dir(MXT_DEBUGFS_DIR, NULL); + if (IS_ERR_OR_NULL(debug_base)) + pr_err("atmel_mxt_ts: Failed to create debugfs dir\n"); + if (IS_ERR_OR_NULL(debugfs_create_file(MXT_DEBUGFS_FILE, + 0444, + debug_base, + data, + &mxt_object_fops))) { + pr_err("atmel_mxt_ts: Failed to create object file\n"); + debugfs_remove_recursive(debug_base); + } } static int __devinit mxt_probe(struct i2c_client *client, @@ -1098,7 +2230,7 @@ static int __devinit mxt_probe(struct i2c_client *client, const struct mxt_platform_data *pdata = client->dev.platform_data; struct mxt_data *data; struct input_dev *input_dev; - int error; + int error, i; if (!pdata) return -EINVAL; @@ -1111,7 +2243,8 @@ static int __devinit mxt_probe(struct i2c_client *client, goto err_free_mem; } - input_dev->name = "Atmel maXTouch Touchscreen"; + data->state = INIT; + input_dev->name = "atmel_mxt_ts"; input_dev->id.bustype = BUS_I2C; input_dev->dev.parent = &client->dev; input_dev->open = mxt_input_open; @@ -1122,17 +2255,15 @@ static int __devinit mxt_probe(struct i2c_client *client, data->pdata = pdata; data->irq = client->irq; - mxt_calc_resolution(data); - __set_bit(EV_ABS, input_dev->evbit); __set_bit(EV_KEY, input_dev->evbit); __set_bit(BTN_TOUCH, input_dev->keybit); /* For single touch */ input_set_abs_params(input_dev, ABS_X, - 0, data->max_x, 0, 0); + pdata->disp_minx, pdata->disp_maxx, 0, 0); input_set_abs_params(input_dev, ABS_Y, - 0, data->max_y, 0, 0); + pdata->disp_miny, pdata->disp_maxy, 0, 0); input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0); @@ -1141,18 +2272,83 @@ static int __devinit mxt_probe(struct i2c_client *client, input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MXT_MAX_AREA, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_X, - 0, data->max_x, 0, 0); + pdata->disp_minx, pdata->disp_maxx, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, - 0, data->max_y, 0, 0); + pdata->disp_miny, pdata->disp_maxy, 0, 0); input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + /* set key array supported keys */ + if (pdata->key_codes) { + for (i = 0; i < MXT_KEYARRAY_MAX_KEYS; i++) { + if (pdata->key_codes[i]) + input_set_capability(input_dev, EV_KEY, + pdata->key_codes[i]); + } + } + input_set_drvdata(input_dev, data); i2c_set_clientdata(client, data); + if (pdata->init_hw) + error = pdata->init_hw(true); + else + error = mxt_regulator_configure(data, true); + if (error) { + dev_err(&client->dev, "Failed to intialize hardware\n"); + goto err_free_mem; + } + + if (pdata->power_on) + error = pdata->power_on(true); + else + error = mxt_power_on(data, true); + if (error) { + dev_err(&client->dev, "Failed to power on hardware\n"); + goto err_regulator_on; + } + + if (gpio_is_valid(pdata->irq_gpio)) { + /* configure touchscreen irq gpio */ + error = gpio_request(pdata->irq_gpio, + "mxt_irq_gpio"); + if (error) { + pr_err("%s: unable to request gpio [%d]\n", __func__, + pdata->irq_gpio); + goto err_power_on; + } + error = gpio_direction_input(pdata->irq_gpio); + if (error) { + pr_err("%s: unable to set_direction for gpio [%d]\n", + __func__, pdata->irq_gpio); + goto err_irq_gpio_req; + } + } + + if (gpio_is_valid(pdata->reset_gpio)) { + /* configure touchscreen reset out gpio */ + error = gpio_request(pdata->reset_gpio, + "mxt_reset_gpio"); + if (error) { + pr_err("%s: unable to request reset gpio %d\n", + __func__, pdata->reset_gpio); + goto err_irq_gpio_req; + } + + error = gpio_direction_output( + pdata->reset_gpio, 1); + if (error) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, pdata->reset_gpio); + goto err_reset_gpio_req; + } + } + + mxt_reset_delay(data); + error = mxt_initialize(data); if (error) - goto err_free_object; + goto err_reset_gpio_req; error = request_threaded_irq(client->irq, NULL, mxt_interrupt, pdata->irqflags, client->dev.driver->name, data); @@ -1161,9 +2357,13 @@ static int __devinit mxt_probe(struct i2c_client *client, goto err_free_object; } - error = mxt_make_highchg(data); - if (error) - goto err_free_irq; + if (data->state == APPMODE) { + error = mxt_make_highchg(data); + if (error) { + dev_err(&client->dev, "Failed to make high CHG\n"); + goto err_free_irq; + } + } error = input_register_device(input_dev); if (error) @@ -1173,6 +2373,16 @@ static int __devinit mxt_probe(struct i2c_client *client, if (error) goto err_unregister_device; +#if defined(CONFIG_HAS_EARLYSUSPEND) + data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + MXT_SUSPEND_LEVEL; + data->early_suspend.suspend = mxt_early_suspend; + data->early_suspend.resume = mxt_late_resume; + register_early_suspend(&data->early_suspend); +#endif + + mxt_debugfs_init(data); + return 0; err_unregister_device: @@ -1182,6 +2392,22 @@ static int __devinit mxt_probe(struct i2c_client *client, free_irq(client->irq, data); err_free_object: kfree(data->object_table); +err_reset_gpio_req: + if (gpio_is_valid(pdata->reset_gpio)) + gpio_free(pdata->reset_gpio); +err_irq_gpio_req: + if (gpio_is_valid(pdata->irq_gpio)) + gpio_free(pdata->irq_gpio); +err_power_on: + if (pdata->power_on) + pdata->power_on(false); + else + mxt_power_on(data, false); +err_regulator_on: + if (pdata->init_hw) + pdata->init_hw(false); + else + mxt_regulator_configure(data, false); err_free_mem: input_free_device(input_dev); kfree(data); @@ -1195,57 +2421,34 @@ static int __devexit mxt_remove(struct i2c_client *client) sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); free_irq(data->irq, data); input_unregister_device(data->input_dev); - kfree(data->object_table); - kfree(data); - - return 0; -} - -#ifdef CONFIG_PM -static int mxt_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct mxt_data *data = i2c_get_clientdata(client); - struct input_dev *input_dev = data->input_dev; - - mutex_lock(&input_dev->mutex); - - if (input_dev->users) - mxt_stop(data); - - mutex_unlock(&input_dev->mutex); - - return 0; -} +#if defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&data->early_suspend); +#endif -static int mxt_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct mxt_data *data = i2c_get_clientdata(client); - struct input_dev *input_dev = data->input_dev; + if (data->pdata->power_on) + data->pdata->power_on(false); + else + mxt_power_on(data, false); - /* Soft reset */ - mxt_write_object(data, MXT_GEN_COMMAND_T6, - MXT_COMMAND_RESET, 1); + if (data->pdata->init_hw) + data->pdata->init_hw(false); + else + mxt_regulator_configure(data, false); - msleep(MXT_RESET_TIME); + if (gpio_is_valid(data->pdata->reset_gpio)) + gpio_free(data->pdata->reset_gpio); - mutex_lock(&input_dev->mutex); + if (gpio_is_valid(data->pdata->irq_gpio)) + gpio_free(data->pdata->irq_gpio); - if (input_dev->users) - mxt_start(data); + kfree(data->object_table); + kfree(data); - mutex_unlock(&input_dev->mutex); + debugfs_remove_recursive(debug_base); return 0; } -static const struct dev_pm_ops mxt_pm_ops = { - .suspend = mxt_suspend, - .resume = mxt_resume, -}; -#endif - static const struct i2c_device_id mxt_id[] = { { "qt602240_ts", 0 }, { "atmel_mxt_ts", 0 }, diff --git a/drivers/input/touchscreen/cy8c_tmg_ts.c b/drivers/input/touchscreen/cy8c_tmg_ts.c new file mode 100644 index 0000000000000000000000000000000000000000..f48374eb30e17b28462f89b6de676d3f95c6a046 --- /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 0000000000000000000000000000000000000000..f70858251c81aa5a7a46d6ea2425477da1b0d4a2 --- /dev/null +++ b/drivers/input/touchscreen/cy8c_ts.c @@ -0,0 +1,824 @@ +/* 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 CY8CTMA340 0x2 + +#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, + }, + [2] = { + .x_index = 1, + .y_index = 3, + .z_index = 5, + .id_index = 6, + .data_reg = 0x2, + .status_reg = 0, + .update_data = 0x4, + .touch_bytes = 6, + .touch_meta_data = 3, + .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_PRESSURE, pressure); + 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_PRESSURE, 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_PRESSURE, 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) || (ts->device_id == CY8CTMA340)) + 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; + } else if (ts->device_id == CY8CTMA340) { + 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 = 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); + + if (ts->device_id == CY8CTMA340) { + /* set up virtual key */ + __set_bit(EV_KEY, input_device->evbit); + /* set dummy key to make driver work with virtual keys */ + input_set_capability(input_device, EV_KEY, KEY_PROG1); + } + + 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_PRESSURE, + ts->pdata->min_touch, ts->pdata->max_touch, 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); + + /* 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) { + 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"); + } + } + 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 if (id->driver_data == CY8CTMA340) + temp_reg = 0x00; + 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}, + {"cy8ctma340", CY8CTMA340}, + {} +}; +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("CY8CTMA340-CY8CTMG200 touchscreen controller driver"); +MODULE_AUTHOR("Cypress"); +MODULE_ALIAS("platform:cy8c_ts"); diff --git a/drivers/input/touchscreen/cyttsp-i2c-qc.c b/drivers/input/touchscreen/cyttsp-i2c-qc.c new file mode 100644 index 0000000000000000000000000000000000000000..e82dd1301d095023e7c72e85fd3184ac037d376b --- /dev/null +++ b/drivers/input/touchscreen/cyttsp-i2c-qc.c @@ -0,0 +1,3132 @@ +/* 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 +#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 +#define TTSP_BUFF_SIZE 50 + +/* CY TTSP I2C Driver private data */ +struct cyttsp { + struct i2c_client *client; + struct input_dev *input; + struct timer_list timer; + struct mutex mutex; + char phys[32]; + struct cyttsp_platform_data *platform_data; + u8 num_prv_st_tch; + u16 fw_start_addr; + 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 */ + + +/* **************************************************************************** + * Prototypes for static functions + * ************************************************************************** */ +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); + +#ifdef CONFIG_PM +static const struct dev_pm_ops cyttsp_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = cyttsp_suspend, + .resume = cyttsp_resume, +#endif +}; +#endif + +static struct i2c_driver cyttsp_driver = { + .driver = { + .name = CY_I2C_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &cyttsp_pm_ops, +#endif + }, + .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 snprintf(buf, TTSP_BUFF_SIZE, "%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, 0664, cyttsp_irq_status, cyttsp_irq_enable); + +static ssize_t cyttsp_fw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, TTSP_BUFF_SIZE, "%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, 0664, cyttsp_fw_show, NULL); + +/* firmware flashing block */ +#define BLK_SIZE 16 +#define DATA_REC_LEN 64 +#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 BL_CHECKSUM_MASK 0x01 + +#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 + +#define NUM_CHAR_IN_HEX 2 +#define ID_INFO_REC_LEN 9 + +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) + return -EINVAL; + + if (strnlen(str, NUM_CHAR_IN_HEX) < 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) + snprintf(p, TTSP_BUFF_SIZE, "%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(struct cyttsp *ts, 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 >= ts->fw_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(ts, 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); + + /* 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 (!(g_bl_data.bl_status & BL_CHECKSUM_MASK) && + (appid_lo == ts->platform_data->correct_fw_ver)) + 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 if (appid_lo == ts->platform_data->correct_fw_ver) + fw_upgrade = 1; + else { + fw_upgrade = 0; + pr_info("%s: Firmware 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, + strnlen(ID_INFO_REC, ID_INFO_REC_LEN))) { + 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, + strnlen(ID_INFO_REC, ID_INFO_REC_LEN))) { + 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, 0664, 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, 0664, 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; + + strlcpy(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, 0664, cyttsp_fw_name_show, + cyttsp_fw_name_store); + +static void cyttsp_xy_handler(struct cyttsp *ts) +{ + 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; + int val; + + cyttsp_xdebug("TTSP handler 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_handler; + } + + cyttsp_xdebug("TTSP handler 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 { + usleep_range(1000, 1000); + 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 { + msleep(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_handler; + } 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 (ts->platform_data->disable_ghost_det && + (cur_tch == CY_GEN2_GHOST)) + cur_tch = CY_GEN2_2TOUCH; + + 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_handler; + } + + 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) { + val = INVERT_X(g_xy_data.x4, + ts->platform_data->panel_maxx); + if (val >= 0) + g_xy_data.x4 = val; + else + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + } + if (rev_y) { + val = INVERT_X(g_xy_data.y4, + ts->platform_data->panel_maxy); + if (val >= 0) + g_xy_data.y4 = val; + else + 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) { + val = INVERT_X(g_xy_data.x3, + ts->platform_data->panel_maxx); + if (val >= 0) + g_xy_data.x3 = val; + else + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + + } + if (rev_y) { + val = INVERT_X(g_xy_data.y3, + ts->platform_data->panel_maxy); + if (val >= 0) + g_xy_data.y3 = val; + else + 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) { + val = INVERT_X(g_xy_data.x2, + ts->platform_data->panel_maxx); + if (val >= 0) + g_xy_data.x2 = val; + else + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + } + if (rev_y) { + val = INVERT_X(g_xy_data.y2, + ts->platform_data->panel_maxy); + if (val >= 0) + g_xy_data.y2 = val; + else + 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) { + val = INVERT_X(g_xy_data.x1, + ts->platform_data->panel_maxx); + if (val >= 0) + g_xy_data.x1 = val; + else + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + } + if (rev_y) { + val = INVERT_X(g_xy_data.y1, + ts->platform_data->panel_maxy); + if (val >= 0) + g_xy_data.y1 = val; + else + 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_handler: + /* restart event timer */ + if (ts->client->irq == 0) + mod_timer(&ts->timer, jiffies + TOUCHSCREEN_TIMEOUT); + 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 */ + cyttsp_xy_handler(ts); + + 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); + + cyttsp_xy_handler(ts); + + 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 { + usleep_range(1000, 1000); + 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 { + msleep(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)) { + usleep_range(1000, 1000); + 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 { + msleep(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 { + usleep_range(1000, 1000); + 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 { + usleep_range(1000, 1000); + + /* 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 */ + msleep(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"); + tries = 0; + do { + msleep(100); + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(bl_cmd), bl_cmd); + if (retval == CY_OK) + break; + } while (tries++ < 5); + + if (retval == CY_OK) { + tries = 0; + do { + msleep(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++ < 5); + } + } + + 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 { + msleep(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 */ + msleep(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); + msleep(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 */ + msleep(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); + msleep(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].hpm_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); + if (regulator_count_voltages(ts->vdd[i]) > 0) { + 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; + } + + 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"); + error = request_threaded_irq(client->irq, NULL, cyttsp_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + 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) + pr_warn("%s: Invalid firmware version detected;" + " Please update.\n", __func__); + } + + 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"); + return -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) + strlcpy(ts->fw_fname, ts->platform_data->fw_fname, + FW_FNAME_LEN - 1); + else + strlcpy(ts->fw_fname, "cyttsp.hex", FW_FNAME_LEN - 1); + + if (ts->platform_data->gen == CY_GEN3) { + ts->fw_start_addr = 0x0b00; + } else if (ts->platform_data->gen == CY_GEN2) { + ts->fw_start_addr = 0x0880; + } else { + pr_err("%s: unsupported cypress chip\n", __func__); + kfree(ts); + return -EINVAL; + } + + i2c_set_clientdata(client, ts); + + error = cyttsp_initialize(client, ts); + if (error) { + cyttsp_xdebug1("err cyttsp_initialize\n"); + /* deallocate memory */ + kfree(ts); +/* + i2c_del_driver(&cyttsp_driver); +*/ + return -ENODEV; + } + } + +#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; +} + +#ifdef CONFIG_PM +static int cyttsp_regulator_lpm(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 (on == false) + goto regulator_hpm; + + for (i = 0; i < num_reg; i++) { + if (regulator_count_voltages(ts->vdd[i]) < 0) + continue; + rc = regulator_set_optimum_mode(ts->vdd[i], + reg_info[i].lpm_load_uA); + if (rc < 0) { + pr_err("%s: regulator_set_optimum failed rc = %d\n", + __func__, rc); + goto fail_regulator_lpm; + } + + } + + return 0; + +regulator_hpm: + for (i = 0; i < num_reg; i++) { + if (regulator_count_voltages(ts->vdd[i]) < 0) + continue; + rc = regulator_set_optimum_mode(ts->vdd[i], + reg_info[i].hpm_load_uA); + if (rc < 0) { + pr_err("%s: regulator_set_optimum failed" + "rc = %d\n", __func__, rc); + goto fail_regulator_hpm; + } + } + + return 0; + +fail_regulator_lpm: + while (i--) { + if (regulator_count_voltages(ts->vdd[i]) < 0) + continue; + regulator_set_optimum_mode(ts->vdd[i], + reg_info[i].hpm_load_uA); + } + + return rc; + +fail_regulator_hpm: + while (i--) { + if (regulator_count_voltages(ts->vdd[i]) < 0) + continue; + regulator_set_optimum_mode(ts->vdd[i], + reg_info[i].lpm_load_uA); + } + + return rc; +} + +/* 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); + else + retval = cyttsp_regulator_lpm(ts, false); + /* take TTSP device out of bootloader mode; + * switch back to TrueTouch operational mode */ + if (!(retval < CY_OK)) { + int tries = 0; + do { + msleep(100); + retval = i2c_smbus_write_i2c_block_data( + ts->client, CY_REG_BASE, + sizeof(bl_cmd), bl_cmd); + if (retval == CY_OK) + break; + } while (tries++ < 2); + /* wait for TTSP Device to complete + * switch to Operational mode */ + tries = 0; + do { + msleep(100); + cyttsp_putbl(ts, 16, false, false, false); + } while (GET_BOOTLOADERMODE(g_bl_data.bl_status) && + tries++ < 2); + 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; + } + + if (ts->client->irq == 0) + del_timer(&ts->timer); + else + disable_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->suspend) { + retval = + ts->platform_data->suspend(ts->client); + } else { + retval = cyttsp_regulator_lpm(ts, true); + } + if (ts->platform_data->use_sleep & CY_USE_DEEP_SLEEP_SEL) + sleep_mode = CY_DEEP_SLEEP_MODE; + else + sleep_mode = CY_LOW_PWR_MODE; + + if (!(retval < CY_OK)) { + 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; +} +#endif + +/* 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); + + /* 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 */ + 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__); + + ret = i2c_add_driver(&cyttsp_driver); + + return ret; +} + +static void cyttsp_exit(void) +{ + 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 100644 index 0000000000000000000000000000000000000000..f14153e0dec80a3e6d58473768f94615ddbe246a --- /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/ft5x06_ts.c b/drivers/input/touchscreen/ft5x06_ts.c new file mode 100644 index 0000000000000000000000000000000000000000..c9905a4a87dfa3d2085487f9aeb0b7df0a67698b --- /dev/null +++ b/drivers/input/touchscreen/ft5x06_ts.c @@ -0,0 +1,654 @@ +/* + * + * FocalTech ft5x06 TouchScreen driver. + * + * Copyright (c) 2010 Focal tech Ltd. + * Copyright (c) 2012, 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 + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +/* Early-suspend level */ +#define FT5X06_SUSPEND_LEVEL 1 +#endif + +#define CFG_MAX_TOUCH_POINTS 5 + +#define FT_STARTUP_DLY 150 +#define FT_RESET_DLY 20 + +#define FT_PRESS 0x7F +#define FT_MAX_ID 0x0F +#define FT_TOUCH_STEP 6 +#define FT_TOUCH_X_H_POS 3 +#define FT_TOUCH_X_L_POS 4 +#define FT_TOUCH_Y_H_POS 5 +#define FT_TOUCH_Y_L_POS 6 +#define FT_TOUCH_EVENT_POS 3 +#define FT_TOUCH_ID_POS 5 + +#define POINT_READ_BUF (3 + FT_TOUCH_STEP * CFG_MAX_TOUCH_POINTS) + +/*register address*/ +#define FT5X06_REG_PMODE 0xA5 +#define FT5X06_REG_FW_VER 0xA6 +#define FT5X06_REG_POINT_RATE 0x88 +#define FT5X06_REG_THGROUP 0x80 + +/* power register bits*/ +#define FT5X06_PMODE_ACTIVE 0x00 +#define FT5X06_PMODE_MONITOR 0x01 +#define FT5X06_PMODE_STANDBY 0x02 +#define FT5X06_PMODE_HIBERNATE 0x03 + +#define FT5X06_VTG_MIN_UV 2600000 +#define FT5X06_VTG_MAX_UV 3300000 +#define FT5X06_I2C_VTG_MIN_UV 1800000 +#define FT5X06_I2C_VTG_MAX_UV 1800000 + +struct ts_event { + u16 x[CFG_MAX_TOUCH_POINTS]; /*x coordinate */ + u16 y[CFG_MAX_TOUCH_POINTS]; /*y coordinate */ + /* touch event: 0 -- down; 1-- contact; 2 -- contact */ + u8 touch_event[CFG_MAX_TOUCH_POINTS]; + u8 finger_id[CFG_MAX_TOUCH_POINTS]; /*touch ID */ + u16 pressure; + u8 touch_point; +}; + +struct ft5x06_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct ts_event event; + const struct ft5x06_ts_platform_data *pdata; + struct regulator *vdd; + struct regulator *vcc_i2c; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif +}; + +static int ft5x06_i2c_read(struct i2c_client *client, char *writebuf, + int writelen, char *readbuf, int readlen) +{ + int ret; + + if (writelen > 0) { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret < 0) + dev_err(&client->dev, "%s: i2c read error.\n", + __func__); + } else { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s:i2c read error.\n", __func__); + } + return ret; +} + +static int ft5x06_i2c_write(struct i2c_client *client, char *writebuf, + int writelen) +{ + int ret; + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s: i2c write error.\n", __func__); + + return ret; +} + +static void ft5x06_report_value(struct ft5x06_ts_data *data) +{ + struct ts_event *event = &data->event; + int i; + int fingerdown = 0; + + for (i = 0; i < event->touch_point; i++) { + if (event->touch_event[i] == 0 || event->touch_event[i] == 2) { + event->pressure = FT_PRESS; + fingerdown++; + } else { + event->pressure = 0; + } + + input_report_abs(data->input_dev, ABS_MT_POSITION_X, + event->x[i]); + input_report_abs(data->input_dev, ABS_MT_POSITION_Y, + event->y[i]); + input_report_abs(data->input_dev, ABS_MT_PRESSURE, + event->pressure); + input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, + event->finger_id[i]); + input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, + event->pressure); + input_mt_sync(data->input_dev); + } + + input_report_key(data->input_dev, BTN_TOUCH, !!fingerdown); + input_sync(data->input_dev); +} + +static int ft5x06_handle_touchdata(struct ft5x06_ts_data *data) +{ + struct ts_event *event = &data->event; + int ret, i; + u8 buf[POINT_READ_BUF] = { 0 }; + u8 pointid = FT_MAX_ID; + + ret = ft5x06_i2c_read(data->client, buf, 1, buf, POINT_READ_BUF); + if (ret < 0) { + dev_err(&data->client->dev, "%s read touchdata failed.\n", + __func__); + return ret; + } + memset(event, 0, sizeof(struct ts_event)); + + event->touch_point = 0; + for (i = 0; i < CFG_MAX_TOUCH_POINTS; i++) { + pointid = (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4; + if (pointid >= FT_MAX_ID) + break; + else + event->touch_point++; + event->x[i] = + (s16) (buf[FT_TOUCH_X_H_POS + FT_TOUCH_STEP * i] & 0x0F) << + 8 | (s16) buf[FT_TOUCH_X_L_POS + FT_TOUCH_STEP * i]; + event->y[i] = + (s16) (buf[FT_TOUCH_Y_H_POS + FT_TOUCH_STEP * i] & 0x0F) << + 8 | (s16) buf[FT_TOUCH_Y_L_POS + FT_TOUCH_STEP * i]; + event->touch_event[i] = + buf[FT_TOUCH_EVENT_POS + FT_TOUCH_STEP * i] >> 6; + event->finger_id[i] = + (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4; + } + + ft5x06_report_value(data); + + return 0; +} + +static irqreturn_t ft5x06_ts_interrupt(int irq, void *dev_id) +{ + struct ft5x06_ts_data *data = dev_id; + int rc; + + rc = ft5x06_handle_touchdata(data); + if (rc) + pr_err("%s: handling touchdata failed\n", __func__); + + return IRQ_HANDLED; +} + +static int ft5x06_power_on(struct ft5x06_ts_data *data, bool on) +{ + int rc; + + if (!on) + goto power_off; + + rc = regulator_enable(data->vdd); + if (rc) { + dev_err(&data->client->dev, + "Regulator vdd enable failed rc=%d\n", rc); + return rc; + } + + rc = regulator_enable(data->vcc_i2c); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_i2c enable failed rc=%d\n", rc); + regulator_disable(data->vdd); + } + + return rc; + +power_off: + rc = regulator_disable(data->vdd); + if (rc) { + dev_err(&data->client->dev, + "Regulator vdd disable failed rc=%d\n", rc); + return rc; + } + + rc = regulator_disable(data->vcc_i2c); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_i2c disable failed rc=%d\n", rc); + regulator_enable(data->vdd); + } + + return rc; +} + +static int ft5x06_power_init(struct ft5x06_ts_data *data, bool on) +{ + int rc; + + if (!on) + goto pwr_deinit; + + data->vdd = regulator_get(&data->client->dev, "vdd"); + if (IS_ERR(data->vdd)) { + rc = PTR_ERR(data->vdd); + dev_err(&data->client->dev, + "Regulator get failed vdd rc=%d\n", rc); + return rc; + } + + if (regulator_count_voltages(data->vdd) > 0) { + rc = regulator_set_voltage(data->vdd, FT5X06_VTG_MIN_UV, + FT5X06_VTG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "Regulator set_vtg failed vdd rc=%d\n", rc); + goto reg_vdd_put; + } + } + + data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c"); + if (IS_ERR(data->vcc_i2c)) { + rc = PTR_ERR(data->vcc_i2c); + dev_err(&data->client->dev, + "Regulator get failed vcc_i2c rc=%d\n", rc); + goto reg_vdd_set_vtg; + } + + if (regulator_count_voltages(data->vcc_i2c) > 0) { + rc = regulator_set_voltage(data->vcc_i2c, FT5X06_I2C_VTG_MIN_UV, + FT5X06_I2C_VTG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "Regulator set_vtg failed vcc_i2c rc=%d\n", rc); + goto reg_vcc_i2c_put; + } + } + + return 0; + +reg_vcc_i2c_put: + regulator_put(data->vcc_i2c); +reg_vdd_set_vtg: + if (regulator_count_voltages(data->vdd) > 0) + regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV); +reg_vdd_put: + regulator_put(data->vdd); + return rc; + +pwr_deinit: + if (regulator_count_voltages(data->vdd) > 0) + regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV); + + regulator_put(data->vdd); + + if (regulator_count_voltages(data->vcc_i2c) > 0) + regulator_set_voltage(data->vcc_i2c, 0, FT5X06_I2C_VTG_MAX_UV); + + regulator_put(data->vcc_i2c); + return 0; +} + +#ifdef CONFIG_PM +static int ft5x06_ts_suspend(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + char txbuf[2]; + + disable_irq(data->client->irq); + + if (gpio_is_valid(data->pdata->reset_gpio)) { + txbuf[0] = FT5X06_REG_PMODE; + txbuf[1] = FT5X06_PMODE_HIBERNATE; + ft5x06_i2c_write(data->client, txbuf, sizeof(txbuf)); + } + + return 0; +} + +static int ft5x06_ts_resume(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + if (gpio_is_valid(data->pdata->reset_gpio)) { + gpio_set_value_cansleep(data->pdata->reset_gpio, 0); + msleep(FT_RESET_DLY); + gpio_set_value_cansleep(data->pdata->reset_gpio, 1); + } + enable_irq(data->client->irq); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void ft5x06_ts_early_suspend(struct early_suspend *handler) +{ + struct ft5x06_ts_data *data = container_of(handler, + struct ft5x06_ts_data, + early_suspend); + + ft5x06_ts_suspend(&data->client->dev); +} + +static void ft5x06_ts_late_resume(struct early_suspend *handler) +{ + struct ft5x06_ts_data *data = container_of(handler, + struct ft5x06_ts_data, + early_suspend); + + ft5x06_ts_resume(&data->client->dev); +} +#endif + +static const struct dev_pm_ops ft5x06_ts_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = ft5x06_ts_suspend, + .resume = ft5x06_ts_resume, +#endif +}; +#endif + +static int ft5x06_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct ft5x06_ts_platform_data *pdata = client->dev.platform_data; + struct ft5x06_ts_data *data; + struct input_dev *input_dev; + u8 reg_value; + u8 reg_addr; + int err; + + if (!pdata) { + dev_err(&client->dev, "Invalid pdata\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "I2C not supported\n"); + return -ENODEV; + } + + data = kzalloc(sizeof(struct ft5x06_ts_data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "Not enough memory\n"); + return -ENOMEM; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + dev_err(&client->dev, "failed to allocate input device\n"); + goto free_mem; + } + + data->input_dev = input_dev; + data->client = client; + data->pdata = pdata; + + input_dev->name = "ft5x06_ts"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + + input_set_drvdata(input_dev, data); + i2c_set_clientdata(client, data); + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, + pdata->x_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, + pdata->y_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, + CFG_MAX_TOUCH_POINTS, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, FT_PRESS, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, FT_PRESS, 0, 0); + + err = input_register_device(input_dev); + if (err) { + dev_err(&client->dev, "Input device registration failed\n"); + goto free_inputdev; + } + + if (pdata->power_init) { + err = pdata->power_init(true); + if (err) { + dev_err(&client->dev, "power init failed"); + goto unreg_inputdev; + } + } else { + err = ft5x06_power_init(data, true); + if (err) { + dev_err(&client->dev, "power init failed"); + goto unreg_inputdev; + } + } + + if (pdata->power_on) { + err = pdata->power_on(true); + if (err) { + dev_err(&client->dev, "power on failed"); + goto pwr_deinit; + } + } else { + err = ft5x06_power_on(data, true); + if (err) { + dev_err(&client->dev, "power on failed"); + goto pwr_deinit; + } + } + + if (gpio_is_valid(pdata->irq_gpio)) { + err = gpio_request(pdata->irq_gpio, "ft5x06_irq_gpio"); + if (err) { + dev_err(&client->dev, "irq gpio request failed"); + goto pwr_off; + } + err = gpio_direction_input(pdata->irq_gpio); + if (err) { + dev_err(&client->dev, + "set_direction for irq gpio failed\n"); + goto free_irq_gpio; + } + } + + if (gpio_is_valid(pdata->reset_gpio)) { + err = gpio_request(pdata->reset_gpio, "ft5x06_reset_gpio"); + if (err) { + dev_err(&client->dev, "reset gpio request failed"); + goto free_irq_gpio; + } + + err = gpio_direction_output(pdata->reset_gpio, 0); + if (err) { + dev_err(&client->dev, + "set_direction for reset gpio failed\n"); + goto free_reset_gpio; + } + msleep(FT_RESET_DLY); + gpio_set_value_cansleep(data->pdata->reset_gpio, 1); + } + + /* make sure CTP already finish startup process */ + msleep(FT_STARTUP_DLY); + + /*get some register information */ + reg_addr = FT5X06_REG_FW_VER; + err = ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); + if (err) + dev_err(&client->dev, "version read failed"); + + dev_info(&client->dev, "[FTS] Firmware version = 0x%x\n", reg_value); + + reg_addr = FT5X06_REG_POINT_RATE; + ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); + if (err) + dev_err(&client->dev, "report rate read failed"); + dev_info(&client->dev, "[FTS] report rate is %dHz.\n", reg_value * 10); + + reg_addr = FT5X06_REG_THGROUP; + err = ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); + if (err) + dev_err(&client->dev, "threshold read failed"); + dev_dbg(&client->dev, "[FTS] touch threshold is %d.\n", reg_value * 4); + + err = request_threaded_irq(client->irq, NULL, + ft5x06_ts_interrupt, pdata->irqflags, + client->dev.driver->name, data); + if (err) { + dev_err(&client->dev, "request irq failed\n"); + goto free_reset_gpio; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + FT5X06_SUSPEND_LEVEL; + data->early_suspend.suspend = ft5x06_ts_early_suspend; + data->early_suspend.resume = ft5x06_ts_late_resume; + register_early_suspend(&data->early_suspend); +#endif + + return 0; + +free_reset_gpio: + if (gpio_is_valid(pdata->reset_gpio)) + gpio_free(pdata->reset_gpio); +free_irq_gpio: + if (gpio_is_valid(pdata->irq_gpio)) + gpio_free(pdata->reset_gpio); +pwr_off: + if (pdata->power_on) + pdata->power_on(false); + else + ft5x06_power_on(data, false); +pwr_deinit: + if (pdata->power_init) + pdata->power_init(false); + else + ft5x06_power_init(data, false); +unreg_inputdev: + input_unregister_device(input_dev); + input_dev = NULL; +free_inputdev: + input_free_device(input_dev); +free_mem: + kfree(data); + return err; +} + +static int __devexit ft5x06_ts_remove(struct i2c_client *client) +{ + struct ft5x06_ts_data *data = i2c_get_clientdata(client); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&data->early_suspend); +#endif + free_irq(client->irq, data); + + if (gpio_is_valid(data->pdata->reset_gpio)) + gpio_free(data->pdata->reset_gpio); + + if (gpio_is_valid(data->pdata->irq_gpio)) + gpio_free(data->pdata->reset_gpio); + + if (data->pdata->power_on) + data->pdata->power_on(false); + else + ft5x06_power_on(data, false); + + if (data->pdata->power_init) + data->pdata->power_init(false); + else + ft5x06_power_init(data, false); + + input_unregister_device(data->input_dev); + kfree(data); + + return 0; +} + +static const struct i2c_device_id ft5x06_ts_id[] = { + {"ft5x06_ts", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id); + +static struct i2c_driver ft5x06_ts_driver = { + .probe = ft5x06_ts_probe, + .remove = __devexit_p(ft5x06_ts_remove), + .driver = { + .name = "ft5x06_ts", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &ft5x06_ts_pm_ops, +#endif + }, + .id_table = ft5x06_ts_id, +}; + +static int __init ft5x06_ts_init(void) +{ + return i2c_add_driver(&ft5x06_ts_driver); +} +module_init(ft5x06_ts_init); + +static void __exit ft5x06_ts_exit(void) +{ + i2c_del_driver(&ft5x06_ts_driver); +} +module_exit(ft5x06_ts_exit); + +MODULE_DESCRIPTION("FocalTech ft5x06 TouchScreen driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/msm_touch.c b/drivers/input/touchscreen/msm_touch.c new file mode 100644 index 0000000000000000000000000000000000000000..7ba896a3888c6da4b17dc69bdbe3926f3b4aa63c --- /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 0000000000000000000000000000000000000000..eb2e73bb85c0e4ff08b86f1b9b7824348671477d --- /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-2012, 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 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/synaptics/Makefile b/drivers/input/touchscreen/synaptics/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..32cbd76f3f5a376fc1df25c95583ce94d5e0f0fb --- /dev/null +++ b/drivers/input/touchscreen/synaptics/Makefile @@ -0,0 +1,11 @@ +CFLAGS_rmi_bus.o := -DDEBUG +CFLAGS_rmi_sensor.o := -DDEBUG +CFLAGS_rmi_function.o := -DDEBUG +CFLAGS_rmi_f01.o := -DDEBUG +CFLAGS_rmi_f05.o := -DDEBUG +CFLAGS_rmi_f11.o := -DDEBUG +CFLAGS_rmi_f19.o := -DDEBUG +CFLAGS_rmi_f34.o := -DDEBUG +CFLAGS_rmi_i2c.o := -DDEBUG +CFLAGS_rmi_spi.o := -DDEBUG +obj-y += rmi_bus.o rmi_sensor.o rmi_function.o rmi_f01.o rmi_f05.o rmi_f11.o rmi_f19.o rmi_f34.o rmi_i2c.o diff --git a/drivers/input/touchscreen/synaptics/rmi.h b/drivers/input/touchscreen/synaptics/rmi.h new file mode 100644 index 0000000000000000000000000000000000000000..7484258f7d0c05471c9e7b24f0d92de4bde970a4 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi.h @@ -0,0 +1,164 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Header File. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ + +#ifndef _RMI_H +#define _RMI_H + +/* RMI4 Protocol Support + */ + +/* For each function present on the RMI device, we need to get the RMI4 Function + * Descriptor info from the Page Descriptor Table. This will give us the + * addresses for Query, Command, Control, Data and the Source Count (number + * of sources for this function) and the function id. + */ +struct rmi_function_descriptor { + unsigned char queryBaseAddr; + unsigned char commandBaseAddr; + unsigned char controlBaseAddr; + unsigned char dataBaseAddr; + unsigned char interruptSrcCnt; + unsigned char functionNum; +}; + +/* This encapsulates the information found using the RMI4 Function $01 + * query registers. There is only one Function $01 per device. + * + * Assuming appropriate endian-ness, you can populate most of this + * structure by reading query registers starting at the query base address + * that was obtained from RMI4 function 0x01 function descriptor info read + * from the Page Descriptor Table. + * + * Specific register information is provided in the comments for each field. + * For further reference, please see the "Synaptics RMI 4 Interfacing + * Guide" document : go to http://www.synaptics.com/developers/manuals - and + * select "Synaptics RMI 4 Interfacting Guide". + */ +struct rmi_F01_query { + /* The manufacturer identification byte.*/ + unsigned char mfgid; + + /* The Product Properties information.*/ + unsigned char properties; + + /* The product info bytes.*/ + unsigned char prod_info[2]; + + /* Date Code - Year, Month, Day.*/ + unsigned char date_code[3]; + + /* Tester ID (14 bits).*/ + unsigned short tester_id; + + /* Serial Number (14 bits).*/ + unsigned short serial_num; + + /* A null-terminated string that identifies this particular product.*/ + char prod_id[11]; +}; + +/* This encapsulates the F01 Device Control control registers. + * TODO: This isn't right. The number of interrupt enables needs to be determined + * dynamically as the sensor is initialized. Fix this. + */ +struct rmi_F01_control { + unsigned char deviceControl; + unsigned char interruptEnable[1]; +}; + +/** This encapsulates the F01 Device Control data registers. + * TODO: This isn't right. The number of irqs needs to be determined + * dynamically as the sensor is initialized. Fix this. + */ +struct rmi_F01_data { + unsigned char deviceStatus; + unsigned char irqs[1]; +}; + + +/**********************************************************/ + +/** This is the data read from the F11 query registers. + */ +struct rmi_F11_device_query { + bool hasQuery9; + unsigned char numberOfSensors; +}; + +struct rmi_F11_sensor_query { + bool configurable; + bool hasSensitivityAdjust; + bool hasGestures; + bool hasAbs; + bool hasRel; + unsigned char numberOfFingers; + unsigned char numberOfXElectrodes; + unsigned char numberOfYElectrodes; + unsigned char maximumElectrodes; + bool hasAnchoredFinger; + unsigned char absDataSize; +}; + +struct rmi_F11_control { + bool relativeBallistics; + bool relativePositionFilter; + bool absolutePositionFilter; + unsigned char reportingMode; + bool manuallyTrackedFinger; + bool manuallyTrackedFingerEnable; + unsigned char motionSensitivity; + unsigned char palmDetectThreshold; + unsigned char deltaXPosThreshold; + unsigned char deltaYPosThreshold; + unsigned char velocity; + unsigned char acceleration; + unsigned short sensorMaxXPos; + unsigned short sensorMaxYPos; +}; + + +/**********************************************************/ + +/** This is the data read from the F19 query registers. + */ +struct rmi_F19_query { + bool hasHysteresisThreshold; + bool hasSensitivityAdjust; + bool configurable; + unsigned char buttonCount; +}; + +struct rmi_F19_control { + unsigned char buttonUsage; + unsigned char filterMode; + unsigned char *intEnableRegisters; + unsigned char *singleButtonControl; + unsigned char *sensorMap; + unsigned char *singleButtonSensitivity; + unsigned char globalSensitivityAdjustment; + unsigned char globalHysteresisThreshold; +}; + +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_bus.c b/drivers/input/touchscreen/synaptics/rmi_bus.c new file mode 100644 index 0000000000000000000000000000000000000000..b65eabb8b65df5c45804b95e7d890d26fdfe93a3 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_bus.c @@ -0,0 +1,402 @@ +/** + * Synaptics Register Mapped Interface (RMI4) - RMI Bus Module. + * Copyright (C) 2007 - 2011, Synaptics Incorporated + * + * Impliments "rmi" bus per Documentation/driver-model/bus.txt + * + * This protocol is layered as follows. + * + * + * + * +-------+ +-------+ +-------+ +--------+ + * | Fn32 | | Fn11| | Fn19 | | Fn11 | Devices/Functions + * *---|---+ +--|----+ +----|--+ +----|---* (2D, cap. btns, etc.) + * | | | | + * +----------------+ +----------------+ + * | Sensor0 | | Sensor1 | Sensors Dev/Drivers + * +----------------+ +----------------+ (a sensor has one or + * | | more functions) + * | | + * +----------------------------------------+ + * | | + * | RMI4 Bus | RMI Bus Layer + * | (this file) | + * *--|-----|------|--------------|---------* + * | | | | + * | | | | + * +-----+-----+-------+--------------------+ + * | I2C | SPI | SMBus | etc. | Physical Layer + * +-----+-----+-------+--------------------+ + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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. + * + *############################################################################# + */ + +static const char busname[] = "rmi"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rmi_drvr.h" +#include "rmi.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_function.h" + +/* list of physical drivers - i2c, spi, etc. */ +static LIST_HEAD(phys_drivers); +static DEFINE_MUTEX(phys_drivers_mutex); + +/* list of sensors found on a physical bus (i2c, smi, etc.)*/ +static LIST_HEAD(sensor_drivers); +static DEFINE_MUTEX(sensor_drivers_mutex); +static LIST_HEAD(sensor_devices); +static DEFINE_MUTEX(sensor_devices_mutex); + +#define PDT_START_SCAN_LOCATION 0x00E9 +#define PDT_END_SCAN_LOCATION 0x0005 +#define PDT_ENTRY_SIZE 0x0006 + +/* definitions for rmi bus */ +struct device rmi_bus_device; + +struct bus_type rmi_bus_type; +EXPORT_SYMBOL(rmi_bus_type); + + +/* + * This method is called, perhaps multiple times, whenever a new device or driver + * is added for this bus. It should return a nonzero value if the given device can be + * handled by the given driver. This function must be handled at the bus level, + * because that is where the proper logic exists; the core kernel cannot know how + * to match devices and drivers for every possible bus type + * The match function does a comparison between the hardware ID provided by + * the device itself and the IDs supported by the driver. + * + */ +static int rmi_bus_match(struct device *dev, struct device_driver *driver) +{ + printk(KERN_DEBUG "%s: Matching %s for rmi bus.\n", __func__, dev->bus->name); + return !strncmp(dev->bus->name, driver->name, strlen(driver->name)); +} + +/** Stub for now. + */ +static int rmi_bus_suspend(struct device *dev, pm_message_t state) +{ + printk(KERN_INFO "%s: RMI bus suspending.", __func__); + return 0; +} + +/** Stub for now. + */ +static int rmi_bus_resume(struct device *dev) +{ + printk(KERN_INFO "%s: RMI bus resuming.", __func__); + return 0; +} + +/* + * This method is called, whenever a new device is added for this bus. + * It will scan the devices PDT to get the function $01 query, control, + * command and data regsiters so that it can create a function $01 (sensor) + * device for the new physical device. It also caches the PDT for later use by + * other functions that are created for the device. For example, if a function + * $11 is found it will need the query, control, command and data register + * addresses for that function. The new function could re-scan the PDT but + * since it is being done here we can cache it and keep it around. + * + * TODO: If the device is reset or some action takes place that would invalidate + * the PDT - such as a reflash of the firmware - then the device should be re-added + * to the bus and the PDT re-scanned and cached. + * + */ +int rmi_register_sensor(struct rmi_phys_driver *rpd, struct rmi_sensordata *sensordata) +{ + int i; + int pdt_entry_count = 0; + struct rmi_sensor_device *rmi_sensor_dev; + struct rmi_function_info *rfi; + struct rmi_function_descriptor rmi_fd; + int retval; + static int index; + + /* Make sure we have a read, write, read_multiple, write_multiple + function pointers from whatever physical layer the sensor is on. + */ + if (!rpd->name) { + printk(KERN_ERR "%s: Physical driver must specify a name", + __func__); + return -EINVAL; + } + if (!rpd->write) { + printk(KERN_ERR + "%s: Physical driver %s must specify a writer.", + __func__, rpd->name); + return -EINVAL; + } + if (!rpd->read) { + printk(KERN_ERR + "%s: Physical driver %s must specify a reader.", + __func__, rpd->name); + return -EINVAL; + } + if (!rpd->write_multiple) { + printk(KERN_ERR "%s: Physical driver %s must specify a " + "multiple writer.", + __func__, rpd->name); + return -EINVAL; + } + if (!rpd->read_multiple) { + printk(KERN_ERR "%s: Physical driver %s must specify a " + "multiple reader.", + __func__, rpd->name); + return -EINVAL; + } + + /* Get some information from the device */ + printk(KERN_DEBUG "%s: Identifying sensors by presence of F01...", __func__); + + rmi_sensor_dev = NULL; + + /* Scan the page descriptor table until we find F01. If we find that, + * we assume that we can reliably talk to this sensor. + */ + for (i = PDT_START_SCAN_LOCATION; /* Register the rmi sensor driver */ + i >= PDT_END_SCAN_LOCATION; + i -= PDT_ENTRY_SIZE) { + retval = rpd->read_multiple(rpd, i, (char *)&rmi_fd, + sizeof(rmi_fd)); + if (!retval) { + rfi = NULL; + + if (rmi_fd.functionNum != 0x00 && rmi_fd.functionNum != 0xff) { + pdt_entry_count++; + if ((rmi_fd.functionNum & 0xff) == 0x01) { + printk(KERN_DEBUG "%s: F01 Found - RMI Device Control", __func__); + + /* This appears to be a valid device, so create a sensor + * device and sensor driver for it. */ + rmi_sensor_dev = kzalloc(sizeof(*rmi_sensor_dev), GFP_KERNEL); + if (!rmi_sensor_dev) { + printk(KERN_ERR "%s: Error allocating memory for rmi_sensor_device\n", __func__); + retval = -ENOMEM; + goto exit_fail; + } + rmi_sensor_dev->dev.bus = &rmi_bus_type; + + retval = rmi_sensor_register_device(rmi_sensor_dev, index++); + if (retval < 0) { + printk(KERN_ERR "%s: Error %d registering sensor device.", __func__, retval); + goto exit_fail; + } + + rmi_sensor_dev->driver = kzalloc(sizeof(struct rmi_sensor_driver), GFP_KERNEL); + if (!rmi_sensor_dev->driver) { + printk(KERN_ERR "%s: Error allocating memory for rmi_sensor_driver\n", __func__); + retval = -ENOMEM; + goto exit_fail; + } + rmi_sensor_dev->driver->sensor_device = rmi_sensor_dev; + rmi_sensor_dev->driver->polling_required = rpd->polling_required; + rmi_sensor_dev->driver->rpd = rpd; + if (sensordata) + rmi_sensor_dev->driver->perfunctiondata = sensordata->perfunctiondata; + INIT_LIST_HEAD(&rmi_sensor_dev->driver->functions); + + retval = rmi_sensor_register_driver(rmi_sensor_dev->driver); + if (retval < 0) { + printk(KERN_ERR "%s: Error %d registering sensor driver.", __func__, retval); + goto exit_fail; + } + + /* link the attention fn in the rpd to the sensor attn fn */ + + rpd->sensor = rmi_sensor_dev->driver; + rpd->attention = rmi_sensor_dev->driver->attention; + + /* Add it into the list of sensors on the rmi bus */ + mutex_lock(&sensor_devices_mutex); + list_add_tail(&rmi_sensor_dev->sensors, &sensor_devices); + mutex_unlock(&sensor_devices_mutex); + + /* All done with this sensor, fall out of PDT scan loop. */ + break; + } else { + /* Just print out the function found for now */ + printk(KERN_DEBUG "%s: Found Function %02x - Ignored.\n", __func__, rmi_fd.functionNum & 0xff); + } + } else { + /* A zero or 0xff in the function number + signals the end of the PDT */ + pr_debug("%s: Found End of PDT.", + __func__); + break; + } + } else { + /* failed to read next PDT entry - end PDT + scan - this may result in an incomplete set + of recognized functions - should probably + return an error but the driver may still be + viable for diagnostics and debugging so let's + let it continue. */ + printk(KERN_ERR "%s: Read Error %d when reading next PDT entry - " + "ending PDT scan.", + __func__, retval); + break; + } + } + + /* If we actually found a sensor, keep it around. */ + if (rmi_sensor_dev) { + /* Add physical driver struct to list */ + mutex_lock(&phys_drivers_mutex); + list_add_tail(&rpd->drivers, &phys_drivers); + mutex_unlock(&phys_drivers_mutex); + printk(KERN_DEBUG "%s: Registered sensor drivers.", __func__); + retval = 0; + } else { + printk(KERN_ERR "%s: Failed to find sensor. PDT contained %d entries.", __func__, pdt_entry_count); + retval = -ENODEV; + } + +exit_fail: + return retval; +} +EXPORT_SYMBOL(rmi_register_sensor); + +int rmi_unregister_sensors(struct rmi_phys_driver *rpd) +{ + if (rpd->sensor) { + printk(KERN_WARNING "%s: WARNING: unregister of %s while %s still attached.", + __func__, rpd->name, rpd->sensor->drv.name); + } + + pr_debug("%s: Unregistering sensor drivers %s\n", __func__, rpd->name); + + /* TODO: We should call sensor_teardown() for each sensor before we get + * rid of this list. + */ + + mutex_lock(&sensor_drivers_mutex); + list_del(&rpd->sensor->sensor_drivers); + mutex_unlock(&sensor_drivers_mutex); + + return 0; +} +EXPORT_SYMBOL(rmi_unregister_sensors); + + +static void rmi_bus_dev_release(struct device *dev) +{ + printk(KERN_DEBUG "rmi bus device release\n"); +} + + +int rmi_register_bus_device(struct device *rmibusdev) +{ + printk(KERN_DEBUG "%s: Registering RMI4 bus device.\n", __func__); + + /* Here, we simply fill in some of the embedded device structure fields + (which individual drivers should not need to know about), and register + the device with the driver core. */ + + rmibusdev->bus = &rmi_bus_type; + rmibusdev->parent = &rmi_bus_device; + rmibusdev->release = rmi_bus_dev_release; + dev_set_name(rmibusdev, "rmi"); + + /* If we wanted to add bus-specific attributes to the device, we could do so here.*/ + + return device_register(rmibusdev); +} +EXPORT_SYMBOL(rmi_register_bus_device); + +void rmi_unregister_bus_device(struct device *rmibusdev) +{ + printk(KERN_DEBUG "%s: Unregistering bus device.", __func__); + + device_unregister(rmibusdev); +} +EXPORT_SYMBOL(rmi_unregister_bus_device); + +static int __init rmi_bus_init(void) +{ + int status; + + status = 0; + + printk(KERN_INFO "%s: RMI Bus Driver Init", __func__); + + /* Register the rmi bus */ + rmi_bus_type.name = busname; + rmi_bus_type.match = rmi_bus_match; + rmi_bus_type.suspend = rmi_bus_suspend; + rmi_bus_type.resume = rmi_bus_resume; + status = bus_register(&rmi_bus_type); + if (status < 0) { + printk(KERN_ERR "%s: Error %d registering the rmi bus.", __func__, status); + goto err_exit; + } + printk(KERN_DEBUG "%s: registered bus.", __func__); + +#if 0 + /** This doesn't seem to be required any more. It worked OK in Froyo, + * but breaks in Gingerbread */ + /* Register the rmi bus device - "rmi". There is only one rmi bus device. */ + status = rmi_register_bus_device(&rmi_bus_device); + if (status < 0) { + printk(KERN_ERR "%s: Error %d registering rmi bus device.", __func__, status); + bus_unregister(&rmi_bus_type); + goto err_exit; + } + printk(KERN_DEBUG "%s: Registered bus device.", __func__); +#endif + + return 0; +err_exit: + return status; +} + +static void __exit rmi_bus_exit(void) +{ + printk(KERN_DEBUG "%s: RMI Bus Driver Exit.", __func__); + + /* Unregister the rmi bus device - "rmi". There is only one rmi bus device. */ + rmi_unregister_bus_device(&rmi_bus_device); + + /* Unregister the rmi bus */ + bus_unregister(&rmi_bus_type); +} + + +module_init(rmi_bus_init); +module_exit(rmi_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("RMI4 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/synaptics/rmi_bus.h b/drivers/input/touchscreen/synaptics/rmi_bus.h new file mode 100644 index 0000000000000000000000000000000000000000..1e9bd24edc92e1cb844da3c447173700d0ffba27 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_bus.h @@ -0,0 +1,32 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) - RMI Bus Module Header. + * Copyright (C) 2007 - 2010, Synaptics Incorporated + * + */ +/* + * + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ + +#ifndef _RMI_BUS_H +#define _RMI_BUS_H + + +extern struct bus_type rmi_bus_type; + +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_drvr.h b/drivers/input/touchscreen/synaptics/rmi_drvr.h new file mode 100644 index 0000000000000000000000000000000000000000..d8c848d339fe4007ed90c3e32ad32df630d3af25 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_drvr.h @@ -0,0 +1,104 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) RMI Driver Header File. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 "rmi.h" + +#ifndef _RMI_DRVR_H +#define _RMI_DRVR_H + +#include + +/* RMI4 Protocol Support + */ + +struct rmi_phys_driver { + char *name; + int (*write)(struct rmi_phys_driver *physdrvr, unsigned short address, + char data); + int (*read)(struct rmi_phys_driver *physdrvr, unsigned short address, + char *buffer); + int (*write_multiple)(struct rmi_phys_driver *physdrvr, + unsigned short address, char *buffer, int length); + int (*read_multiple)(struct rmi_phys_driver *physdrvr, unsigned short address, + char *buffer, int length); + void (*attention)(struct rmi_phys_driver *physdrvr, int instance); + bool polling_required; + int irq; + + /* Standard kernel linked list implementation. + * Documentation on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head drivers; + struct rmi_sensor_driver *sensor; + struct module *module; +}; + +int rmi_read(struct rmi_sensor_driver *sensor, unsigned short address, char *dest); +int rmi_write(struct rmi_sensor_driver *sensor, unsigned short address, + unsigned char data); +int rmi_read_multiple(struct rmi_sensor_driver *sensor, unsigned short address, + char *dest, int length); +int rmi_write_multiple(struct rmi_sensor_driver *sensor, unsigned short address, + unsigned char *data, int length); +int rmi_register_sensor(struct rmi_phys_driver *physdrvr, + struct rmi_sensordata *sensordata); +int rmi_unregister_sensors(struct rmi_phys_driver *physdrvr); + +/* Utility routine to set bits in a register. */ +int rmi_set_bits(struct rmi_sensor_driver *sensor, unsigned short address, unsigned char bits); +/* Utility routine to clear bits in a register. */ +int rmi_clear_bits(struct rmi_sensor_driver *sensor, unsigned short address, unsigned char bits); +/* Utility routine to set the value of a bit field in a register. */ +int rmi_set_bit_field(struct rmi_sensor_driver *sensor, unsigned short address, + unsigned char field_mask, unsigned char bits); + +/* Set this to 1 to turn on code used in detecting buffer leaks. */ +#define RMI_ALLOC_STATS 1 + +#if RMI_ALLOC_STATS +extern int appallocsrmi; +extern int rfiallocsrmi; +extern int fnallocsrmi; + +#define INC_ALLOC_STAT(X) (X##allocsrmi++) +#define DEC_ALLOC_STAT(X) \ + do { \ + if (X##allocsrmi) X##allocsrmi--; \ + else printk(KERN_DEBUG "Too many " #X " frees\n"); \ + } while (0) +#define CHECK_ALLOC_STAT(X) \ + do { \ + if (X##allocsrmi) \ + printk(KERN_DEBUG "Left over " #X " buffers: %d\n", \ + X##allocsrmi); \ + } while (0) +#else +#define INC_ALLOC_STAT(X) do { } while (0) +#define DEC_ALLOC_STAT(X) do { } while (0) +#define CHECK_ALLOC_STAT(X) do { } while (0) +#endif + +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_f01.c b/drivers/input/touchscreen/synaptics/rmi_f01.c new file mode 100644 index 0000000000000000000000000000000000000000..25a337d612bbb4627b175c3afb60e86aaf1af4b5 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f01.c @@ -0,0 +1,603 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $01 support for sensor + * control and configuration. + * + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 +#include + +#include "rmi.h" +#include "rmi_drvr.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_function.h" +#include "rmi_f01.h" + +/* Control register bits. */ +#define F01_CONFIGURED (1 << 7) +#define NONSTANDARD_REPORT_RATE (1 << 6) + +/* Command register bits. */ +#define F01_RESET 1 +#define F01_SHUTDOWN (1 << 1) + +/* Data register 0 bits. */ +#define F01_UNCONFIGURED (1 << 7) +#define F01_FLASH_PROGRAMMING_MODE (1 << 6) +#define F01_STATUS_MASK 0x0F + +/** Context data for each F01 we find. + */ +struct f01_instance_data { + struct rmi_F01_control *controlRegisters; + struct rmi_F01_data *dataRegisters; + struct rmi_F01_query *query_registers; + + bool nonstandard_report_rate; +}; + +static ssize_t rmi_fn_01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_productinfo_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(productinfo, 0444, rmi_fn_01_productinfo_show, rmi_fn_01_productinfo_store); /* RO attr */ + +static ssize_t rmi_fn_01_productid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_productid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(productid, 0444, rmi_fn_01_productid_show, rmi_fn_01_productid_store); /* RO attr */ + +static ssize_t rmi_fn_01_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_manufacturer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(manufacturer, 0444, rmi_fn_01_manufacturer_show, rmi_fn_01_manufacturer_store); /* RO attr */ + +static ssize_t rmi_fn_01_datecode_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_datecode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(datecode, 0444, rmi_fn_01_datecode_show, rmi_fn_01_datecode_store); /* RO attr */ + +static ssize_t rmi_fn_01_reportrate_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_reportrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(reportrate, 0644, rmi_fn_01_reportrate_show, rmi_fn_01_reportrate_store); /* RW attr */ + +static ssize_t rmi_fn_01_reset_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(reset, 0200, rmi_fn_01_reset_show, rmi_fn_01_reset_store); /* WO attr */ + +static ssize_t rmi_fn_01_testerid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_testerid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(testerid, 0444, rmi_fn_01_testerid_show, rmi_fn_01_testerid_store); /* RO attr */ + +static ssize_t rmi_fn_01_serialnumber_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_01_serialnumber_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(serialnumber, 0444, rmi_fn_01_serialnumber_show, rmi_fn_01_serialnumber_store); /* RO attr */ + +static int set_report_rate(struct rmi_function_info *function_info, bool nonstandard) +{ + if (nonstandard) { + return rmi_set_bits(function_info->sensor, function_info->funcDescriptor.controlBaseAddr, NONSTANDARD_REPORT_RATE); + } else { + return rmi_set_bits(function_info->sensor, function_info->funcDescriptor.controlBaseAddr, NONSTANDARD_REPORT_RATE); + } +} + +/*. + * The interrupt handler for Fn $01 doesn't do anything (for now). + */ +void FN_01_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs) +{ + struct f01_instance_data *instanceData = (struct f01_instance_data *) rmifninfo->fndata; + + printk(KERN_DEBUG "%s: Read device status.", __func__); + + if (rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.dataBaseAddr, + &instanceData->dataRegisters->deviceStatus, 1)) { + printk(KERN_ERR "%s : Could not read F01 device status.\n", + __func__); + } + printk(KERN_INFO "%s: read device status register. Value 0x%02X.", __func__, instanceData->dataRegisters->deviceStatus); + + if (instanceData->dataRegisters->deviceStatus & F01_UNCONFIGURED) { + printk(KERN_INFO "%s: ++++ Device reset detected.", __func__); + /* TODO: Handle device reset appropriately. + */ + } +} +EXPORT_SYMBOL(FN_01_inthandler); + +/* + * This reads in the function $01 source data. + * + */ +void FN_01_attention(struct rmi_function_info *rmifninfo) +{ + struct f01_instance_data *instanceData = (struct f01_instance_data *) rmifninfo->fndata; + + /* TODO: Compute size to read and number of IRQ registers to processors + * dynamically. See comments in rmi.h. */ + if (rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.dataBaseAddr+1, + instanceData->dataRegisters->irqs, 1)) { + printk(KERN_ERR "%s : Could not read interrupt status registers at 0x%02x\n", + __func__, rmifninfo->funcDescriptor.dataBaseAddr); + return; + } + + if (instanceData->dataRegisters->irqs[0] & instanceData->controlRegisters->interruptEnable[0]) { +// printk(KERN_INFO "%s: ++++ IRQs == 0x%02X", __func__, instanceData->dataRegisters->irqs[0]); + /* call down to the sensors irq dispatcher to dispatch all enabled IRQs */ + rmifninfo->sensor->dispatchIRQs(rmifninfo->sensor, + instanceData->dataRegisters->irqs[0]); + } + +} +EXPORT_SYMBOL(FN_01_attention); + +int FN_01_config(struct rmi_function_info *rmifninfo) +{ + int retval = 0; + struct f01_instance_data *instance_data = rmifninfo->fndata; + + printk(KERN_DEBUG "%s: RMI4 function $01 config\n", __func__); + + /* First thing to do is set the configuration bit. We'll check this at + * the end to determine if the device has reset during the config process. + */ + retval = rmi_set_bits(rmifninfo->sensor, rmifninfo->funcDescriptor.controlBaseAddr, F01_CONFIGURED); + if (retval) + printk(KERN_WARNING "%s: failed to set configured bit, errno = %d.", + __func__, retval); + + /* At config time, the device is presumably in its default state, so we + * only need to write non-default configuration settings. + */ + if (instance_data->nonstandard_report_rate) { + retval = set_report_rate(rmifninfo, true); + if (!retval) + printk(KERN_WARNING "%s: failed to configure report rate, errno = %d.", + __func__, retval); + } + + /* TODO: Check for reset! */ + + return retval; +} +EXPORT_SYMBOL(FN_01_config); + +/* Initialize any function $01 specific params and settings - input + * settings, device settings, etc. + */ +int FN_01_init(struct rmi_function_device *function_device) +{ + int retval; + struct rmi_f01_functiondata *functiondata = rmi_sensor_get_functiondata(function_device->sensor, RMI_F01_INDEX); + struct f01_instance_data *instance_data = function_device->rfi->fndata; + + pr_debug("%s: RMI4 function $01 init\n", __func__); + + if (functiondata) { + instance_data->nonstandard_report_rate = functiondata->nonstandard_report_rate; + } + + retval = device_create_file(&function_device->dev, &dev_attr_productinfo); + if (retval) { + printk(KERN_ERR "%s: Failed to create productinfo.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_productid); + if (retval) { + printk(KERN_ERR "%s: Failed to create productid.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_manufacturer); + if (retval) { + printk(KERN_ERR "%s: Failed to create manufacturer.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_datecode); + if (retval) { + printk(KERN_ERR "%s: Failed to create datecode.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_reportrate); + if (retval) { + printk(KERN_ERR "%s: Failed to create reportrate.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_reset); + if (retval) { + printk(KERN_ERR "%s: Failed to create reset.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_serialnumber); + if (retval) { + printk(KERN_ERR "%s: Failed to create serialnumber.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_testerid); + if (retval) { + printk(KERN_ERR "%s: Failed to create testerid.", __func__); + return retval; + } + + return 0; +} +EXPORT_SYMBOL(FN_01_init); + +int FN_01_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, unsigned int interruptCount) +{ + int i; + int InterruptOffset; + int retval = 0; + struct f01_instance_data *instanceData = NULL; + struct rmi_F01_control *controlRegisters = NULL; + struct rmi_F01_data *dataRegisters = NULL; + struct rmi_F01_query *query_registers = NULL; + unsigned char query_buffer[21]; + + pr_debug("%s: RMI4 function $01 detect\n", __func__); + + /* Store addresses - used elsewhere to read data, + * control, query, etc. */ + rmifninfo->funcDescriptor.queryBaseAddr = fndescr->queryBaseAddr; + rmifninfo->funcDescriptor.commandBaseAddr = fndescr->commandBaseAddr; + rmifninfo->funcDescriptor.controlBaseAddr = fndescr->controlBaseAddr; + rmifninfo->funcDescriptor.dataBaseAddr = fndescr->dataBaseAddr; + rmifninfo->funcDescriptor.interruptSrcCnt = fndescr->interruptSrcCnt; + rmifninfo->funcDescriptor.functionNum = fndescr->functionNum; + + rmifninfo->numSources = fndescr->interruptSrcCnt; + + /* Set up context data. */ + instanceData = kzalloc(sizeof(*instanceData), GFP_KERNEL); + if (!instanceData) { + printk(KERN_ERR "%s: Error allocating memory for F01 context data.\n", __func__); + retval = -ENOMEM; + goto error_exit; + } + query_registers = kzalloc(sizeof(*query_registers), GFP_KERNEL); + if (!query_registers) { + printk(KERN_ERR "%s: Error allocating memory for F01 query registers.\n", __func__); + retval = -ENOMEM; + goto error_exit; + } + instanceData->query_registers = query_registers; + + /* Read the query info and unpack it. */ + retval = rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.queryBaseAddr, + query_buffer, 21); + if (retval) { + printk(KERN_ERR "%s : Could not read F01 query registers at 0x%02x. Error %d.\n", + __func__, rmifninfo->funcDescriptor.queryBaseAddr, retval); + /* Presumably if the read fails, the buffer should be all zeros, so we're OK to continue. */ + } + query_registers->mfgid = query_buffer[0]; + query_registers->properties = query_buffer[1]; + query_registers->prod_info[0] = query_buffer[2] & 0x7F; + query_registers->prod_info[1] = query_buffer[3] & 0x7F; + query_registers->date_code[0] = query_buffer[4] & 0x1F; + query_registers->date_code[1] = query_buffer[5] & 0x0F; + query_registers->date_code[2] = query_buffer[6] & 0x1F; + query_registers->tester_id = (((unsigned short) query_buffer[7] & 0x7F) << 7) | (query_buffer[8] & 0x7F); + query_registers->serial_num = (((unsigned short) query_buffer[9] & 0x7F) << 7) | (query_buffer[10] & 0x7F); + memcpy(query_registers->prod_id, &query_buffer[11], 10); + + printk(KERN_DEBUG "%s: RMI4 Protocol Function $01 Query information, rmifninfo->funcDescriptor.queryBaseAddr = %d\n", __func__, rmifninfo->funcDescriptor.queryBaseAddr); + printk(KERN_DEBUG "%s: Manufacturer ID: %d %s\n", __func__, + query_registers->mfgid, query_registers->mfgid == 1 ? "(Synaptics)" : ""); + printk(KERN_DEBUG "%s: Product Properties: 0x%x\n", + __func__, query_registers->properties); + printk(KERN_DEBUG "%s: Product Info: 0x%x 0x%x\n", + __func__, query_registers->prod_info[0], query_registers->prod_info[1]); + printk(KERN_DEBUG "%s: Date Code: Year : %d Month: %d Day: %d\n", + __func__, query_registers->date_code[0], query_registers->date_code[1], + query_registers->date_code[2]); + printk(KERN_DEBUG "%s: Tester ID: %d\n", __func__, query_registers->tester_id); + printk(KERN_DEBUG "%s: Serial Number: 0x%x\n", + __func__, query_registers->serial_num); + printk(KERN_DEBUG "%s: Product ID: %s\n", __func__, query_registers->prod_id); + + /* TODO: size of control registers needs to be computed dynamically. See comment + * in rmi.h. */ + controlRegisters = kzalloc(sizeof(*controlRegisters), GFP_KERNEL); + if (!controlRegisters) { + printk(KERN_ERR "%s: Error allocating memory for F01 control registers.\n", __func__); + retval = -ENOMEM; + goto error_exit; + } + instanceData->controlRegisters = controlRegisters; + retval = rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.controlBaseAddr, + (char *)instanceData->controlRegisters, sizeof(struct rmi_F01_control)); + if (retval) { + printk(KERN_ERR "%s : Could not read F01 control registers at 0x%02x. Error %d.\n", + __func__, rmifninfo->funcDescriptor.controlBaseAddr, retval); + } + + /* TODO: size of data registers needs to be computed dynamically. See comment + * in rmi.h. */ + dataRegisters = kzalloc(sizeof(*dataRegisters), GFP_KERNEL); + if (!dataRegisters) { + printk(KERN_ERR "%s: Error allocating memory for F01 data registers.\n", __func__); + retval = -ENOMEM; + goto error_exit; + } + instanceData->dataRegisters = dataRegisters; + rmifninfo->fndata = instanceData; + + /* Need to get interrupt info to be used later when handling + * interrupts. */ + rmifninfo->interruptRegister = interruptCount/8; + + /* loop through interrupts for each source and or in a bit + * to the interrupt mask for each. */ + InterruptOffset = interruptCount % 8; + + for (i = InterruptOffset; + i < ((fndescr->interruptSrcCnt & 0x7) + InterruptOffset); + i++) { + rmifninfo->interruptMask |= 1 << i; + } + + return retval; + +error_exit: + kfree(instanceData); + kfree(query_registers); + kfree(controlRegisters); + kfree(dataRegisters); + return retval; +} +EXPORT_SYMBOL(FN_01_detect); + +static ssize_t rmi_fn_01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers && instance_data->query_registers->prod_info) + return sprintf(buf, "0x%02X 0x%02X\n", instance_data->query_registers->prod_info[0], instance_data->query_registers->prod_info[1]); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_productinfo_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} + + +static ssize_t rmi_fn_01_productid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers && instance_data->query_registers->prod_id) + return sprintf(buf, "%s\n", instance_data->query_registers->prod_id); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_productid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} + +static ssize_t rmi_fn_01_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers) + return sprintf(buf, "0x%02X\n", instance_data->query_registers->mfgid); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_manufacturer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} + +static ssize_t rmi_fn_01_datecode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers && instance_data->query_registers->date_code) + return sprintf(buf, "20%02u-%02u-%02u\n", instance_data->query_registers->date_code[0], instance_data->query_registers->date_code[1], instance_data->query_registers->date_code[2]); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_datecode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} + +static ssize_t rmi_fn_01_reportrate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers && instance_data->query_registers->date_code) + return sprintf(buf, "%d\n", instance_data->nonstandard_report_rate); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_reportrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + unsigned int new_rate; + int retval; + + printk(KERN_DEBUG "%s: Report rate set to %s", __func__, buf); + + if (sscanf(buf, "%u", &new_rate) != 1) + return -EINVAL; + if (new_rate < 0 || new_rate > 1) + return -EINVAL; + instance_data->nonstandard_report_rate = new_rate; + + retval = set_report_rate(fn->rfi, new_rate); + if (retval < 0) { + printk(KERN_ERR "%s: failed to set report rate bit, error = %d.", __func__, retval); + return retval; + } + + return count; +} + +static ssize_t rmi_fn_01_reset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return -EPERM; +} + +static ssize_t rmi_fn_01_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + unsigned int reset; + int retval; + + printk(KERN_INFO "%s: Reset written with %s", __func__, buf); + + if (sscanf(buf, "%u", &reset) != 1) + return -EINVAL; + if (reset < 0 || reset > 1) + return -EINVAL; + + /* Per spec, 0 has no effect, so we skip it entirely. */ + if (reset) { + retval = rmi_set_bits(fn->sensor, fn->rfi->funcDescriptor.commandBaseAddr, F01_RESET); + if (retval < 0) { + printk(KERN_ERR "%s: failed to issue reset command, error = %d.", __func__, retval); + return retval; + } + } + + return count; +} + +static ssize_t rmi_fn_01_serialnumber_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers) + return sprintf(buf, "%u\n", instance_data->query_registers->serial_num); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_serialnumber_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} + +static ssize_t rmi_fn_01_testerid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f01_instance_data *instance_data = (struct f01_instance_data *)fn->rfi->fndata; + + if (instance_data && instance_data->query_registers) + return sprintf(buf, "%u\n", instance_data->query_registers->tester_id); + + return sprintf(buf, "unknown"); +} + +static ssize_t rmi_fn_01_testerid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} diff --git a/drivers/input/touchscreen/synaptics/rmi_f01.h b/drivers/input/touchscreen/synaptics/rmi_f01.h new file mode 100644 index 0000000000000000000000000000000000000000..976e0620ee0e08c919f8050702939070e330c8d6 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f01.h @@ -0,0 +1,40 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $01 header. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + * There is only one function $01 for each RMI4 sensor. This will be + * the function that is used to set sensor control and configurations + * and check the interrupts to find the source function that is interrupting. + * + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ +#ifndef _RMI_FUNCTION_01_H +#define _RMI_FUNCTION_01_H + +void FN_01_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs); +int FN_01_config(struct rmi_function_info *rmifninfo); +int FN_01_init(struct rmi_function_device *function_device); +int FN_01_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); +void FN_01_attention(struct rmi_function_info *rmifninfo); +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_f05.c b/drivers/input/touchscreen/synaptics/rmi_f05.c new file mode 100644 index 0000000000000000000000000000000000000000..4f209e7fba060cb867f327d05d18f20111057133 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f05.c @@ -0,0 +1,137 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $11 support for 2D. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 "rmi.h" +#include "rmi_drvr.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_function.h" +#include "rmi_f05.h" + +struct f05_instance_data { + int dummy; /* TODO: Write this */ +}; + +/* + * There is no attention function for F05 - it is left NULL + * in the function table so it is not called. + * + */ + + +/* + * This reads in a sample and reports the F05 source data to the + * input subsystem. It is used for both polling and interrupt driven + * operation. This is called a lot so don't put in any informational + * printks since they will slow things way down! + */ +void FN_05_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs) +{ +// struct f05_instance_data *instance_data = rmifninfo->fndata; +} +EXPORT_SYMBOL(FN_05_inthandler); + +int FN_05_config(struct rmi_function_info *rmifninfo) +{ + int retval = 0; + + pr_debug("%s: RMI4 F05 config\n", __func__); + + /* TODO: Perform configuration. In particular, write any cached control + * register values to the device. */ + + return retval; +} +EXPORT_SYMBOL(FN_05_config); + +/* Initialize any F05 specific params and settings - input + * settings, device settings, etc. + */ +int FN_05_init(struct rmi_function_device *function_device) +{ + int retval = 0; +// struct f05_instance_data *instance_data = function_device->rfi->fndata; +// struct rmi_f05_functiondata *functiondata = rmi_sensor_get_functiondata(function_device->sensor, RMI_F05_INDEX); + + printk(KERN_DEBUG "%s: RMI4 F05 init\n", __func__); + + return retval; +} +EXPORT_SYMBOL(FN_05_init); + + +int FN_05_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, unsigned int interruptCount) +{ + int retval = 0; + int i; + struct f05_instance_data *instanceData; + int fn05InterruptOffset; + + printk(KERN_DEBUG "%s: RMI4 F05 detect\n", __func__); + + instanceData = kzalloc(sizeof(struct f05_instance_data), GFP_KERNEL); + if (!instanceData) { + printk(KERN_ERR "%s: Error allocating F05 instance data.\n", __func__); + return -ENOMEM; + } + rmifninfo->fndata = instanceData; + + /* Store addresses - used elsewhere to read data, + * control, query, etc. */ + rmifninfo->funcDescriptor.queryBaseAddr = fndescr->queryBaseAddr; + rmifninfo->funcDescriptor.commandBaseAddr = fndescr->commandBaseAddr; + rmifninfo->funcDescriptor.controlBaseAddr = fndescr->controlBaseAddr; + rmifninfo->funcDescriptor.dataBaseAddr = fndescr->dataBaseAddr; + rmifninfo->funcDescriptor.interruptSrcCnt = fndescr->interruptSrcCnt; + rmifninfo->funcDescriptor.functionNum = fndescr->functionNum; + + rmifninfo->numSources = fndescr->interruptSrcCnt; + /* Need to get interrupt info to be used later when handling + interrupts. */ + rmifninfo->interruptRegister = interruptCount/8; + + /* loop through interrupts for each source in fn $11 and or in a bit + to the interrupt mask for each. */ + fn05InterruptOffset = interruptCount % 8; + + for (i = fn05InterruptOffset; + i < ((fndescr->interruptSrcCnt & 0x7) + fn05InterruptOffset); + i++) + rmifninfo->interruptMask |= 1 << i; + + return retval; +} +EXPORT_SYMBOL(FN_05_detect); diff --git a/drivers/input/touchscreen/synaptics/rmi_f05.h b/drivers/input/touchscreen/synaptics/rmi_f05.h new file mode 100644 index 0000000000000000000000000000000000000000..b820e715936483c9ec107510b9ca548a256e950b --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f05.h @@ -0,0 +1,43 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $11 header. + * Copyright (c) 2007 - 2010, Synaptics Incorporated + * + * For every RMI4 function that has a data source - like 2D sensors, + * buttons, LEDs, GPIOs, etc. - the user will create a new rmi_function_xx.c + * file and add these functions to perform the config(), init(), report() + * and detect() functionality. The function pointers are then srored under + * the RMI function info and these functions will automatically be called by + * the global config(), init(), report() and detect() functions that will + * loop through all data sources and call the data sources functions using + * these functions pointed to by the function ptrs. + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ +#ifndef _RMI_FUNCTION_05_H +#define _RMI_FUNCTION_05_H + +void FN_05_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs); +int FN_05_config(struct rmi_function_info *rmifninfo); +int FN_05_init(struct rmi_function_device *function_device); +int FN_05_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); +/* No attention function for F05 */ +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_f11.c b/drivers/input/touchscreen/synaptics/rmi_f11.c new file mode 100644 index 0000000000000000000000000000000000000000..b75b3e0cee858b84604ac03ddd4deac2246c0339 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f11.c @@ -0,0 +1,913 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $11 support for 2D. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 + +#include "rmi.h" +#include "rmi_drvr.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_function.h" +#include "rmi_f11.h" + +static int sensorMaxX; +static int sensorMaxY; + +struct f11_instance_data { + struct rmi_F11_device_query *deviceInfo; + struct rmi_F11_sensor_query *sensorInfo; + struct rmi_F11_control *controlRegisters; + int button_height; + unsigned char fingerDataBufferSize; + unsigned char absDataOffset; + unsigned char absDataSize; + unsigned char relDataOffset; + unsigned char gestureDataOffset; + unsigned char *fingerDataBuffer; + /* Last X & Y seen, needed at finger lift. Was down indicates at least one finger was here. */ + /* TODO: Eventually we'll need to track this info on a per finger basis. */ + bool wasdown; + unsigned int oldX; + unsigned int oldY; + /* Transformations to be applied to coordinates before reporting. */ + bool flipX; + bool flipY; + int offsetX; + int offsetY; + int clipXLow; + int clipXHigh; + int clipYLow; + int clipYHigh; + bool swap_axes; + bool relReport; +}; + +enum f11_finger_state { + F11_NO_FINGER = 0, + F11_PRESENT = 1, + F11_INACCURATE = 2, + F11_RESERVED = 3 +}; + +static ssize_t rmi_fn_11_flip_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_11_flip_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(flip, 0664, rmi_fn_11_flip_show, rmi_fn_11_flip_store); /* RW attr */ + +static ssize_t rmi_fn_11_clip_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_11_clip_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(clip, 0664, rmi_fn_11_clip_show, rmi_fn_11_clip_store); /* RW attr */ + +static ssize_t rmi_fn_11_offset_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_11_offset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(offset, 0664, rmi_fn_11_offset_show, rmi_fn_11_offset_store); /* RW attr */ + +static ssize_t rmi_fn_11_swap_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_11_swap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(swap, 0664, rmi_fn_11_swap_show, rmi_fn_11_swap_store); /* RW attr */ + +static ssize_t rmi_fn_11_relreport_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_11_relreport_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(relreport, 0664, rmi_fn_11_relreport_show, rmi_fn_11_relreport_store); /* RW attr */ + +static ssize_t rmi_fn_11_maxPos_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_11_maxPos_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(maxPos, 0664, rmi_fn_11_maxPos_show, rmi_fn_11_maxPos_store); /* RW attr */ + + +static void FN_11_relreport(struct rmi_function_info *rmifninfo); + +/* + * There is no attention function for Fn $11 - it is left NULL + * in the function table so it is not called. + * + */ + + +/* + * This reads in a sample and reports the function $11 source data to the + * input subsystem. It is used for both polling and interrupt driven + * operation. This is called a lot so don't put in any informational + * printks since they will slow things way down! + */ +void FN_11_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs) +{ + /* number of touch points - fingers down in this case */ + int fingerDownCount; + int finger; + struct rmi_function_device *function_device; + struct f11_instance_data *instanceData; + + instanceData = (struct f11_instance_data *) rmifninfo->fndata; + + fingerDownCount = 0; + function_device = rmifninfo->function_device; + + /* get 2D sensor finger data */ + + if (rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.dataBaseAddr, + instanceData->fingerDataBuffer, instanceData->fingerDataBufferSize)) { + printk(KERN_ERR "%s: Failed to read finger data registers.\n", __func__); + return; + } + + /* First we need to count the fingers and generate some events related to that. */ + for (finger = 0; finger < instanceData->sensorInfo->numberOfFingers; finger++) { + int reg; + int fingerShift; + int fingerStatus; + + /* determine which data byte the finger status is in */ + reg = finger/4; + /* bit shift to get finger's status */ + fingerShift = (finger % 4) * 2; + fingerStatus = (instanceData->fingerDataBuffer[reg] >> fingerShift) & 3; + + if (fingerStatus == F11_PRESENT || fingerStatus == F11_INACCURATE) { + fingerDownCount++; + instanceData->wasdown = true; + } + } + input_report_key(function_device->input, + BTN_TOUCH, !!fingerDownCount); + + for (finger = 0; finger < instanceData->sensorInfo->numberOfFingers; finger++) { + int reg; + int fingerShift; + int fingerStatus; + int X = 0, Y = 0, Z = 0, Wy = 0, Wx = 0; + + /* determine which data byte the finger status is in */ + reg = finger/4; + /* bit shift to get finger's status */ + fingerShift = (finger % 4) * 2; + fingerStatus = (instanceData->fingerDataBuffer[reg] >> fingerShift) & 3; + + /* if finger status indicates a finger is present then + read the finger data and report it */ + if (fingerStatus == F11_PRESENT || fingerStatus == F11_INACCURATE) { + + if (instanceData->sensorInfo->hasAbs) { + int maxX = instanceData->controlRegisters->sensorMaxXPos; + int maxY = instanceData->controlRegisters->sensorMaxYPos; + reg = instanceData->absDataOffset + (finger * instanceData->absDataSize); + X = (instanceData->fingerDataBuffer[reg] << 4) & 0x0ff0; + X |= (instanceData->fingerDataBuffer[reg+2] & 0x0f); + Y = (instanceData->fingerDataBuffer[reg+1] << 4) & 0x0ff0; + Y |= ((instanceData->fingerDataBuffer[reg+2] & 0xf0) >> 4) & 0x0f; + /* First thing to do is swap axes if needed. + */ + if (instanceData->swap_axes) { + int temp = X; + X = Y; + Y = temp; + maxX = instanceData->controlRegisters->sensorMaxYPos; + maxY = instanceData->controlRegisters->sensorMaxXPos; + } + if (instanceData->flipX) + X = max(maxX-X, 0); + X = X - instanceData->offsetX; + X = min(max(X, instanceData->clipXLow), instanceData->clipXHigh); + if (instanceData->flipY) + Y = max(maxY-Y, 0); + Y = Y - instanceData->offsetY; + Y = min(max(Y, instanceData->clipYLow), instanceData->clipYHigh); + + /* upper 4 bits of W are Wy, + lower 4 of W are Wx */ + Wy = (instanceData->fingerDataBuffer[reg+3] >> 4) & 0x0f; + Wx = instanceData->fingerDataBuffer[reg+3] & 0x0f; + if (instanceData->swap_axes) { + int temp = Wx; + Wx = Wy; + Wy = temp; + } + + Z = instanceData->fingerDataBuffer[reg+4]; + + /* if this is the first finger report normal + ABS_X, ABS_Y, PRESSURE, TOOL_WIDTH events for + non-MT apps. Apps that support Multi-touch + will ignore these events and use the MT events. + Apps that don't support Multi-touch will still + function. + */ + if (fingerDownCount == 1) { + instanceData->oldX = X; + instanceData->oldY = Y; + input_report_abs(function_device->input, ABS_X, X); + input_report_abs(function_device->input, ABS_Y, Y); + input_report_abs(function_device->input, ABS_PRESSURE, Z); + input_report_abs(function_device->input, ABS_TOOL_WIDTH, + max(Wx, Wy)); + + } else { + /* TODO generate non MT events for multifinger situation. */ + } +#ifdef CONFIG_SYNA_MULTI_TOUCH + /* Report Multi-Touch events for each finger */ + input_report_abs(function_device->input, + ABS_MT_PRESSURE, Z); + input_report_abs(function_device->input, ABS_MT_POSITION_X, X); + input_report_abs(function_device->input, ABS_MT_POSITION_Y, Y); + + /* TODO: Tracking ID needs to be reported but not used yet. */ + /* Could be formed by keeping an id per position and assiging */ + /* a new id when fingerStatus changes for that position.*/ + input_report_abs(function_device->input, ABS_MT_TRACKING_ID, + finger); + /* MT sync between fingers */ + input_mt_sync(function_device->input); +#endif + } + } + } + + /* if we had a finger down before and now we don't have any send a button up. */ + if ((fingerDownCount == 0) && instanceData->wasdown) { + instanceData->wasdown = false; + +#ifdef CONFIG_SYNA_MULTI_TOUCH + input_report_abs(function_device->input, ABS_MT_PRESSURE, 0); + input_report_key(function_device->input, BTN_TOUCH, 0); + input_mt_sync(function_device->input); +#endif + } + + FN_11_relreport(rmifninfo); + input_sync(function_device->input); /* sync after groups of events */ + +} +EXPORT_SYMBOL(FN_11_inthandler); + +/* This function reads in relative data for first finger and send to input system */ +static void FN_11_relreport(struct rmi_function_info *rmifninfo) +{ + struct f11_instance_data *instanceData; + struct rmi_function_device *function_device; + signed char X, Y; + unsigned short fn11DataBaseAddr; + + instanceData = (struct f11_instance_data *) rmifninfo->fndata; + + if (instanceData->sensorInfo->hasRel && instanceData->relReport) { + int reg = instanceData->relDataOffset; + + function_device = rmifninfo->function_device; + + fn11DataBaseAddr = rmifninfo->funcDescriptor.dataBaseAddr; + /* Read and report Rel data for primary finger one register for X and one for Y*/ + X = instanceData->fingerDataBuffer[reg]; + Y = instanceData->fingerDataBuffer[reg+1]; + if (instanceData->swap_axes) { + signed char temp = X; + X = Y; + Y = temp; + } + if (instanceData->flipX) { + X = -X; + } + if (instanceData->flipY) { + Y = -Y; + } + X = (signed char) min(127, max(-128, (int) X)); + Y = (signed char) min(127, max(-128, (int) Y)); + + input_report_rel(function_device->input, REL_X, X); + input_report_rel(function_device->input, REL_Y, Y); + } +} + +int FN_11_config(struct rmi_function_info *rmifninfo) +{ + /* For the data source - print info and do any + source specific configuration. */ + unsigned char data[14]; + int retval = 0; + + pr_debug("%s: RMI4 function $11 config\n", __func__); + + /* Get and print some info about the data source... */ + + /* To Query 2D devices we need to read from the address obtained + * from the function descriptor stored in the RMI function info. + */ + retval = rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.queryBaseAddr, + data, 9); + if (retval) { + printk(KERN_ERR "%s: RMI4 function $11 config:" + "Could not read function query registers 0x%x\n", + __func__, rmifninfo->funcDescriptor.queryBaseAddr); + } else { + pr_debug("%s: Number of Fingers: %d\n", + __func__, data[1] & 7); + pr_debug("%s: Is Configurable: %d\n", + __func__, data[1] & (1 << 7) ? 1 : 0); + pr_debug("%s: Has Gestures: %d\n", + __func__, data[1] & (1 << 5) ? 1 : 0); + pr_debug("%s: Has Absolute: %d\n", + __func__, data[1] & (1 << 4) ? 1 : 0); + pr_debug("%s: Has Relative: %d\n", + __func__, data[1] & (1 << 3) ? 1 : 0); + + pr_debug("%s: Number X Electrodes: %d\n", + __func__, data[2] & 0x1f); + pr_debug("%s: Number Y Electrodes: %d\n", + __func__, data[3] & 0x1f); + pr_debug("%s: Maximum Electrodes: %d\n", + __func__, data[4] & 0x1f); + + pr_debug("%s: Absolute Data Size: %d\n", + __func__, data[5] & 3); + + pr_debug("%s: Has XY Dist: %d\n", + __func__, data[7] & (1 << 7) ? 1 : 0); + pr_debug("%s: Has Pinch: %d\n", + __func__, data[7] & (1 << 6) ? 1 : 0); + pr_debug("%s: Has Press: %d\n", + __func__, data[7] & (1 << 5) ? 1 : 0); + pr_debug("%s: Has Flick: %d\n", + __func__, data[7] & (1 << 4) ? 1 : 0); + pr_debug("%s: Has Early Tap: %d\n", + __func__, data[7] & (1 << 3) ? 1 : 0); + pr_debug("%s: Has Double Tap: %d\n", + __func__, data[7] & (1 << 2) ? 1 : 0); + pr_debug("%s: Has Tap and Hold: %d\n", + __func__, data[7] & (1 << 1) ? 1 : 0); + pr_debug("%s: Has Tap: %d\n", + __func__, data[7] & 1 ? 1 : 0); + pr_debug("%s: Has Palm Detect: %d\n", + __func__, data[8] & 1 ? 1 : 0); + pr_debug("%s: Has Rotate: %d\n", + __func__, data[8] & (1 << 1) ? 1 : 0); + + retval = rmi_read_multiple(rmifninfo->sensor, + rmifninfo->funcDescriptor.controlBaseAddr, data, 14); + if (retval) { + printk(KERN_ERR "%s: RMI4 function $11 config:" + "Could not read control registers 0x%x\n", + __func__, rmifninfo->funcDescriptor.controlBaseAddr); + return retval; + } + + /* Store these for use later...*/ + sensorMaxX = ((data[6] & 0x1f) << 8) | ((data[7] & 0xff) << 0); + sensorMaxY = ((data[8] & 0x1f) << 8) | ((data[9] & 0xff) << 0); + + pr_debug("%s: Sensor Max X: %d\n", __func__, sensorMaxX); + pr_debug("%s: Sensor Max Y: %d\n", __func__, sensorMaxY); + } + + return retval; +} +EXPORT_SYMBOL(FN_11_config); + +/* This operation is done in a number of places, so we have a handy routine + * for it. + */ +static void f11_set_abs_params(struct rmi_function_device *function_device) +{ + struct f11_instance_data *instance_data = function_device->rfi->fndata; + /* Use the max X and max Y read from the device, or the clip values, + * whichever is stricter. + */ + int xMin = instance_data->clipXLow; + int xMax = min((int) instance_data->controlRegisters->sensorMaxXPos, instance_data->clipXHigh); + int yMin = instance_data->clipYLow; + int yMax = min((int) instance_data->controlRegisters->sensorMaxYPos, instance_data->clipYHigh) - instance_data->button_height; + if (instance_data->swap_axes) { + int temp = xMin; + xMin = yMin; + yMin = temp; + temp = xMax; + xMax = yMax; + yMax = temp; + } + printk(KERN_DEBUG "%s: Set ranges X=[%d..%d] Y=[%d..%d].", __func__, xMin, xMax, yMin, yMax); + input_set_abs_params(function_device->input, ABS_X, xMin, xMax, + 0, 0); + input_set_abs_params(function_device->input, ABS_Y, yMin, yMax, + 0, 0); + input_set_abs_params(function_device->input, ABS_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(function_device->input, ABS_TOOL_WIDTH, 0, 15, 0, 0); + +#ifdef CONFIG_SYNA_MULTI_TOUCH + input_set_abs_params(function_device->input, ABS_MT_PRESSURE, + 0, 15, 0, 0); + input_set_abs_params(function_device->input, ABS_MT_ORIENTATION, 0, 1, 0, 0); + input_set_abs_params(function_device->input, ABS_MT_TRACKING_ID, + 0, 10, 0, 0); + input_set_abs_params(function_device->input, ABS_MT_POSITION_X, xMin, xMax, + 0, 0); + input_set_abs_params(function_device->input, ABS_MT_POSITION_Y, yMin, yMax, + 0, 0); +#endif +} + +/* Initialize any function $11 specific params and settings - input + * settings, device settings, etc. + */ +int FN_11_init(struct rmi_function_device *function_device) +{ + struct f11_instance_data *instanceData = function_device->rfi->fndata; + int retval = 0; + struct rmi_f11_functiondata *functiondata = rmi_sensor_get_functiondata(function_device->sensor, RMI_F11_INDEX); + printk(KERN_DEBUG "%s: RMI4 F11 init", __func__); + + /* TODO: Initialize these through some normal kernel mechanism. + */ + instanceData->flipX = false; + instanceData->flipY = false; + instanceData->swap_axes = false; + instanceData->relReport = true; + instanceData->offsetX = instanceData->offsetY = 0; + instanceData->clipXLow = instanceData->clipYLow = 0; + /* TODO: 65536 should actually be the largest valid RMI4 position coordinate */ + instanceData->clipXHigh = instanceData->clipYHigh = 65536; + + /* Load any overrides that were specified via platform data. + */ + if (functiondata) { + printk(KERN_DEBUG "%s: found F11 per function platformdata.", __func__); + instanceData->flipX = functiondata->flipX; + instanceData->flipY = functiondata->flipY; + instanceData->button_height = functiondata->button_height; + instanceData->swap_axes = functiondata->swap_axes; + if (functiondata->offset) { + instanceData->offsetX = functiondata->offset->x; + instanceData->offsetY = functiondata->offset->y; + } + if (functiondata->clipX) { + if (functiondata->clipX->min >= functiondata->clipX->max) { + printk(KERN_WARNING "%s: Clip X min (%d) >= X clip max (%d) - ignored.", + __func__, functiondata->clipX->min, functiondata->clipX->max); + } else { + instanceData->clipXLow = functiondata->clipX->min; + instanceData->clipXHigh = functiondata->clipX->max; + } + } + if (functiondata->clipY) { + if (functiondata->clipY->min >= functiondata->clipY->max) { + printk(KERN_WARNING "%s: Clip Y min (%d) >= Y clip max (%d) - ignored.", + __func__, functiondata->clipY->min, functiondata->clipY->max); + } else { + instanceData->clipYLow = functiondata->clipY->min; + instanceData->clipYHigh = functiondata->clipY->max; + } + } + } + + /* need to init the input abs params for the 2D */ + set_bit(EV_ABS, function_device->input->evbit); + set_bit(EV_SYN, function_device->input->evbit); + set_bit(EV_KEY, function_device->input->evbit); + set_bit(BTN_TOUCH, function_device->input->keybit); + set_bit(KEY_OK, function_device->input->keybit); + + f11_set_abs_params(function_device); + + printk(KERN_DEBUG "%s: Creating sysfs files.", __func__); + retval = device_create_file(&function_device->dev, &dev_attr_flip); + if (retval) { + printk(KERN_ERR "%s: Failed to create flip.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_clip); + if (retval) { + printk(KERN_ERR "%s: Failed to create clip.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_offset); + if (retval) { + printk(KERN_ERR "%s: Failed to create offset.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_swap); + if (retval) { + printk(KERN_ERR "%s: Failed to create swap.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_relreport); + if (retval) { + printk(KERN_ERR "%s: Failed to create relreport.", __func__); + return retval; + } + retval = device_create_file(&function_device->dev, &dev_attr_maxPos); + if (retval) { + printk(KERN_ERR "%s: Failed to create maxPos.", __func__); + return retval; + } + + return 0; +} +EXPORT_SYMBOL(FN_11_init); + +int FN_11_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, unsigned int interruptCount) +{ + unsigned char fn11Queries[12]; /* TODO: Compute size correctly. */ + unsigned char fn11Control[12]; /* TODO: Compute size correctly. */ + int i; + unsigned short fn11InterruptOffset; + unsigned char fn11AbsDataBlockSize; + int fn11HasPinch, fn11HasFlick, fn11HasTap; + int fn11HasTapAndHold, fn11HasDoubleTap; + int fn11HasEarlyTap, fn11HasPress; + int fn11HasPalmDetect, fn11HasRotate; + int fn11HasRel; + unsigned char f11_egr_0, f11_egr_1; + unsigned int fn11AllDataBlockSize; + int retval = 0; + struct f11_instance_data *instanceData; + + printk(KERN_DEBUG "%s: RMI4 F11 detect\n", __func__); + + instanceData = kzalloc(sizeof(struct f11_instance_data), GFP_KERNEL); + if (!instanceData) { + printk(KERN_ERR "%s: Error allocating F11 instance data.\n", __func__); + return -ENOMEM; + } + instanceData->deviceInfo = kzalloc(sizeof(struct rmi_F11_device_query), GFP_KERNEL); + if (!instanceData->deviceInfo) { + printk(KERN_ERR "%s: Error allocating F11 device query.\n", __func__); + return -ENOMEM; + } + instanceData->sensorInfo = kzalloc(sizeof(struct rmi_F11_sensor_query), GFP_KERNEL); + if (!instanceData->sensorInfo) { + printk(KERN_ERR "%s: Error allocating F11 sensor query.\n", __func__); + return -ENOMEM; + } + rmifninfo->fndata = instanceData; + + /* Store addresses - used elsewhere to read data, + * control, query, etc. */ + rmifninfo->funcDescriptor.queryBaseAddr = fndescr->queryBaseAddr; + rmifninfo->funcDescriptor.commandBaseAddr = fndescr->commandBaseAddr; + rmifninfo->funcDescriptor.controlBaseAddr = fndescr->controlBaseAddr; + rmifninfo->funcDescriptor.dataBaseAddr = fndescr->dataBaseAddr; + rmifninfo->funcDescriptor.interruptSrcCnt = fndescr->interruptSrcCnt; + rmifninfo->funcDescriptor.functionNum = fndescr->functionNum; + + rmifninfo->numSources = fndescr->interruptSrcCnt; + + /* need to get number of fingers supported, data size, etc. - + to be used when getting data since the number of registers to + read depends on the number of fingers supported and data size. */ + retval = rmi_read_multiple(rmifninfo->sensor, fndescr->queryBaseAddr, fn11Queries, + sizeof(fn11Queries)); + if (retval) { + printk(KERN_ERR "%s: RMI4 function $11 detect: " + "Could not read function query registers 0x%x\n", + __func__, rmifninfo->funcDescriptor.queryBaseAddr); + return retval; + } + + /* Extract device data. */ + instanceData->deviceInfo->hasQuery9 = (fn11Queries[0] & 0x04) != 0; + instanceData->deviceInfo->numberOfSensors = (fn11Queries[0] & 0x07) + 1; + printk(KERN_DEBUG "%s: F11 device - %d sensors. Query 9? %d.", __func__, instanceData->deviceInfo->numberOfSensors, instanceData->deviceInfo->hasQuery9); + + /* Extract sensor data. */ + /* 2D data sources have only 3 bits for the number of fingers + supported - so the encoding is a bit wierd. */ + instanceData->sensorInfo->numberOfFingers = 2; /* default number of fingers supported */ + if ((fn11Queries[1] & 0x7) <= 4) + /* add 1 since zero based */ + instanceData->sensorInfo->numberOfFingers = (fn11Queries[1] & 0x7) + 1; + else { + /* a value of 5 is up to 10 fingers - 6 and 7 are reserved + (shouldn't get these i int retval;n a normal 2D source). */ + if ((fn11Queries[1] & 0x7) == 5) + instanceData->sensorInfo->numberOfFingers = 10; + } + instanceData->sensorInfo->configurable = (fn11Queries[1] & 0x80) != 0; + instanceData->sensorInfo->hasSensitivityAdjust = (fn11Queries[1] & 0x40) != 0; + instanceData->sensorInfo->hasGestures = (fn11Queries[1] & 0x20) != 0; + instanceData->sensorInfo->hasAbs = (fn11Queries[1] & 0x10) != 0; + instanceData->sensorInfo->hasRel = (fn11Queries[1] & 0x08) != 0; + instanceData->sensorInfo->absDataSize = fn11Queries[5] & 0x03; + printk(KERN_DEBUG "%s: Number of fingers: %d.", __func__, instanceData->sensorInfo->numberOfFingers); + + /* Need to get interrupt info to be used later when handling + interrupts. */ + rmifninfo->interruptRegister = interruptCount/8; + + /* loop through interrupts for each source in fn $11 and or in a bit + to the interrupt mask for each. */ + fn11InterruptOffset = interruptCount % 8; + + for (i = fn11InterruptOffset; + i < ((fndescr->interruptSrcCnt & 0x7) + fn11InterruptOffset); + i++) + rmifninfo->interruptMask |= 1 << i; + + /* Figure out just how much data we'll need to read. */ + instanceData->fingerDataBufferSize = (instanceData->sensorInfo->numberOfFingers + 3) / 4; + /* One each for X and Y, one for LSB for X & Y, one for W, one for Z */ + fn11AbsDataBlockSize = 5; + if (instanceData->sensorInfo->absDataSize != 0) + printk(KERN_WARNING "%s: Unrecognized abs data size %d ignored.", __func__, instanceData->sensorInfo->absDataSize); + if (instanceData->sensorInfo->hasAbs) { + instanceData->absDataSize = fn11AbsDataBlockSize; + instanceData->absDataOffset = instanceData->fingerDataBufferSize; + instanceData->fingerDataBufferSize += instanceData->sensorInfo->numberOfFingers * fn11AbsDataBlockSize; + } + if (instanceData->sensorInfo->hasRel) { + instanceData->relDataOffset = ((instanceData->sensorInfo->numberOfFingers + 3) / 4) + + /* absolute data, per finger times number of fingers */ + (fn11AbsDataBlockSize * instanceData->sensorInfo->numberOfFingers); + instanceData->fingerDataBufferSize += instanceData->sensorInfo->numberOfFingers * 2; + } + if (instanceData->sensorInfo->hasGestures) { + instanceData->gestureDataOffset = instanceData->fingerDataBufferSize; + printk(KERN_WARNING "%s: WARNING Need to correctly compute gesture data location.", __func__); + } + + /* need to determine the size of data to read - this depends on + conditions such as whether Relative data is reported and if Gesture + data is reported. */ + f11_egr_0 = fn11Queries[7]; + f11_egr_1 = fn11Queries[8]; + + /* Get info about what EGR data is supported, whether it has + Relative data supported, etc. */ + fn11HasPinch = f11_egr_0 & 0x40; + fn11HasFlick = f11_egr_0 & 0x10; + fn11HasTap = f11_egr_0 & 0x01; + fn11HasTapAndHold = f11_egr_0 & 0x02; + fn11HasDoubleTap = f11_egr_0 & 0x04; + fn11HasEarlyTap = f11_egr_0 & 0x08; + fn11HasPress = f11_egr_0 & 0x20; + fn11HasPalmDetect = f11_egr_1 & 0x01; + fn11HasRotate = f11_egr_1 & 0x02; + fn11HasRel = fn11Queries[1] & 0x08; + + /* Size of all data including finger status, absolute data for each + finger, relative data and EGR data */ + fn11AllDataBlockSize = + /* finger status, four fingers per register */ + ((instanceData->sensorInfo->numberOfFingers + 3) / 4) + + /* absolute data, per finger times number of fingers */ + (fn11AbsDataBlockSize * instanceData->sensorInfo->numberOfFingers) + + /* two relative registers (if relative is being reported) */ + 2 * fn11HasRel + + /* F11_2D_Data8 is only present if the egr_0 + register is non-zero. */ + !!(f11_egr_0) + + /* F11_2D_Data9 is only present if either egr_0 or + egr_1 registers are non-zero. */ + (f11_egr_0 || f11_egr_1) + + /* F11_2D_Data10 is only present if EGR_PINCH or EGR_FLICK of + egr_0 reports as 1. */ + !!(fn11HasPinch | fn11HasFlick) + + /* F11_2D_Data11 and F11_2D_Data12 are only present if + EGR_FLICK of egr_0 reports as 1. */ + 2 * !!(fn11HasFlick); + instanceData->fingerDataBuffer = kcalloc(instanceData->fingerDataBufferSize, sizeof(unsigned char), GFP_KERNEL); + if (!instanceData->fingerDataBuffer) { + printk(KERN_ERR "%s: Failed to allocate finger data buffer.", __func__); + return -ENOMEM; + } + + /* Grab a copy of the control registers. */ + instanceData->controlRegisters = kzalloc(sizeof(struct rmi_F11_control), GFP_KERNEL); + if (!instanceData->controlRegisters) { + printk(KERN_ERR "%s: Error allocating F11 control registers.\n", __func__); + return -ENOMEM; + } + retval = rmi_read_multiple(rmifninfo->sensor, fndescr->controlBaseAddr, + fn11Control, sizeof(fn11Control)); + if (retval) { + printk(KERN_ERR "%s: Failed to read F11 control registers.", __func__); + return retval; + } + instanceData->controlRegisters->sensorMaxXPos = (((int) fn11Control[7] & 0x0F) << 8) + fn11Control[6]; + instanceData->controlRegisters->sensorMaxYPos = (((int) fn11Control[9] & 0x0F) << 8) + fn11Control[8]; + printk(KERN_DEBUG "%s: Max X %d Max Y %d", __func__, instanceData->controlRegisters->sensorMaxXPos, instanceData->controlRegisters->sensorMaxYPos); + return 0; +} +EXPORT_SYMBOL(FN_11_detect); + +static ssize_t rmi_fn_11_maxPos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%u %u\n", instance_data->controlRegisters->sensorMaxXPos, instance_data->controlRegisters->sensorMaxYPos); +} + +static ssize_t rmi_fn_11_maxPos_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return -EPERM; +} + +static ssize_t rmi_fn_11_flip_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%u %u\n", instance_data->flipX, instance_data->flipY); +} + +static ssize_t rmi_fn_11_flip_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + unsigned int newX, newY; + + printk(KERN_DEBUG "%s: Flip set to %s", __func__, buf); + + if (sscanf(buf, "%u %u", &newX, &newY) != 2) + return -EINVAL; + if (newX < 0 || newX > 1 || newY < 0 || newY > 1) + return -EINVAL; + instance_data->flipX = newX; + instance_data->flipY = newY; + + return count; +} + +static ssize_t rmi_fn_11_swap_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%u\n", instance_data->swap_axes); +} + +static ssize_t rmi_fn_11_swap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + unsigned int newSwap; + + printk(KERN_DEBUG "%s: Swap set to %s", __func__, buf); + + if (sscanf(buf, "%u", &newSwap) != 1) + return -EINVAL; + if (newSwap < 0 || newSwap > 1) + return -EINVAL; + instance_data->swap_axes = newSwap; + + f11_set_abs_params(fn); + + return count; +} + +static ssize_t rmi_fn_11_relreport_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%u \n", instance_data->relReport); +} + +static ssize_t rmi_fn_11_relreport_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + unsigned int relRep; + + printk(KERN_DEBUG "%s: relReport set to %s", __func__, buf); + if (sscanf(buf, "%u", &relRep) != 1) + return -EINVAL; + if (relRep < 0 || relRep > 1) + return -EINVAL; + instance_data->relReport = relRep; + + return count; +} + +static ssize_t rmi_fn_11_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%d %d\n", instance_data->offsetX, instance_data->offsetY); +} + +static ssize_t rmi_fn_11_offset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + int newX, newY; + + printk(KERN_DEBUG "%s: Offset set to %s", __func__, buf); + + if (sscanf(buf, "%d %d", &newX, &newY) != 2) + return -EINVAL; + instance_data->offsetX = newX; + instance_data->offsetY = newY; + + return count; +} + +static ssize_t rmi_fn_11_clip_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%u %u %u %u\n", + instance_data->clipXLow, instance_data->clipXHigh, + instance_data->clipYLow, instance_data->clipYHigh); +} + +static ssize_t rmi_fn_11_clip_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f11_instance_data *instance_data = (struct f11_instance_data *)fn->rfi->fndata; + unsigned int newXLow, newXHigh, newYLow, newYHigh; + + printk(KERN_DEBUG "%s: Clip set to %s", __func__, buf); + + if (sscanf(buf, "%u %u %u %u", &newXLow, &newXHigh, &newYLow, &newYHigh) != 4) + return -EINVAL; + if (newXLow < 0 || newXLow >= newXHigh || newYLow < 0 || newYLow >= newYHigh) + return -EINVAL; + instance_data->clipXLow = newXLow; + instance_data->clipXHigh = newXHigh; + instance_data->clipYLow = newYLow; + instance_data->clipYHigh = newYHigh; + + f11_set_abs_params(fn); + + return count; +} diff --git a/drivers/input/touchscreen/synaptics/rmi_f11.h b/drivers/input/touchscreen/synaptics/rmi_f11.h new file mode 100644 index 0000000000000000000000000000000000000000..0bf386aea166021fe98c319402773245f6bdf724 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f11.h @@ -0,0 +1,43 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $11 header. + * Copyright (c) 2007 - 2010, Synaptics Incorporated + * + * For every RMI4 function that has a data source - like 2D sensors, + * buttons, LEDs, GPIOs, etc. - the user will create a new rmi_function_xx.c + * file and add these functions to perform the config(), init(), report() + * and detect() functionality. The function pointers are then srored under + * the RMI function info and these functions will automatically be called by + * the global config(), init(), report() and detect() functions that will + * loop through all data sources and call the data sources functions using + * these functions pointed to by the function ptrs. + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ +#ifndef _RMI_FUNCTION_11_H +#define _RMI_FUNCTION_11_H + +void FN_11_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs); +int FN_11_config(struct rmi_function_info *rmifninfo); +int FN_11_init(struct rmi_function_device *function_device); +int FN_11_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); +/* No attention function for Fn $11 */ +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_f19.c b/drivers/input/touchscreen/synaptics/rmi_f19.c new file mode 100644 index 0000000000000000000000000000000000000000..7bb8712aa8680596b63f9992dd2f36e422d0c738 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f19.c @@ -0,0 +1,514 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $11 support for 2D. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 "rmi.h" +#include "rmi_drvr.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_function.h" +#include "rmi_f19.h" + +struct f19_instance_data { + struct rmi_F19_query *deviceInfo; + struct rmi_F19_control *controlRegisters; + bool *buttonDown; + unsigned char buttonDataBufferSize; + unsigned char *buttonDataBuffer; + unsigned char *buttonMap; + int fn19ControlRegisterSize; + int fn19regCountForBitPerButton; + int fn19btnUsageandfilterModeOffset; + int fn19intEnableOffset; + int fn19intEnableLen; + int fn19singleBtnCtrlLen; + int fn19singleBtnCtrlOffset; + int fn19sensorMapCtrlOffset; + int fn19sensorMapCtrlLen; + int fn19singleBtnSensOffset; + int fn19singleBtnSensLen; + int fn19globalSensOffset; + int fn19globalHystThreshOffset; +}; + +static ssize_t rmi_f19_buttonCount_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_f19_buttonCount_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(buttonCount, 0444, rmi_f19_buttonCount_show, rmi_f19_buttonCount_store); /* RO attr */ + +static ssize_t rmi_f19_buttonMap_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_f19_buttonMap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +DEVICE_ATTR(buttonMap, 0664, rmi_f19_buttonMap_show, rmi_f19_buttonMap_store); /* RW attr */ + + +/* + * There is no attention function for F19 - it is left NULL + * in the function table so it is not called. + * + */ + + +/* + * This reads in a sample and reports the F19 source data to the + * input subsystem. It is used for both polling and interrupt driven + * operation. This is called a lot so don't put in any informational + * printks since they will slow things way down! + */ +void FN_19_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs) +{ + struct rmi_function_device *function_device; + struct f19_instance_data *instanceData; + int button; + + instanceData = (struct f19_instance_data *) rmifninfo->fndata; + + function_device = rmifninfo->function_device; + + /* Read the button data. */ + + if (rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.dataBaseAddr, + instanceData->buttonDataBuffer, instanceData->buttonDataBufferSize)) { + printk(KERN_ERR "%s: Failed to read button data registers.\n", __func__); + return; + } + + /* Generate events for buttons that change state. */ + for (button = 0; button < instanceData->deviceInfo->buttonCount; button++) { + int buttonReg; + int buttonShift; + bool buttonStatus; + + /* determine which data byte the button status is in */ + buttonReg = button/4; + /* bit shift to get button's status */ + buttonShift = button % 8; + buttonStatus = ((instanceData->buttonDataBuffer[buttonReg] >> buttonShift) & 0x01) != 0; + + /* if the button state changed from the last time report it and store the new state */ + if (buttonStatus != instanceData->buttonDown[button]) { + printk(KERN_DEBUG "%s: Button %d (code %d) -> %d.", __func__, button, instanceData->buttonMap[button], buttonStatus); + /* Generate an event here. */ + input_report_key(function_device->input, + instanceData->buttonMap[button], buttonStatus); + instanceData->buttonDown[button] = buttonStatus; + } + } + + input_sync(function_device->input); /* sync after groups of events */ +} +EXPORT_SYMBOL(FN_19_inthandler); + +int FN_19_config(struct rmi_function_info *rmifninfo) +{ + int retval = 0; + + pr_debug("%s: RMI4 F19 config\n", __func__); + + /* TODO: Perform configuration. In particular, write any cached control + * register values to the device. */ + + return retval; +} +EXPORT_SYMBOL(FN_19_config); + +/* Initialize any F19 specific params and settings - input + * settings, device settings, etc. + */ +int FN_19_init(struct rmi_function_device *function_device) +{ + int i, retval = 0; + struct f19_instance_data *instance_data = function_device->rfi->fndata; + struct rmi_f19_functiondata *functiondata = rmi_sensor_get_functiondata(function_device->sensor, RMI_F19_INDEX); + + printk(KERN_DEBUG "%s: RMI4 F19 init\n", __func__); + + if (functiondata) { + if (functiondata->button_map) { + if (functiondata->button_map->nbuttons != instance_data->deviceInfo->buttonCount) { + printk(KERN_WARNING "%s: Platformdata button map size (%d) != number of buttons on device (%d) - ignored.", __func__, functiondata->button_map->nbuttons, instance_data->deviceInfo->buttonCount); + } else if (!functiondata->button_map->map) { + printk(KERN_WARNING "%s: Platformdata button map is missing!", __func__); + } else { + for (i = 0; i < functiondata->button_map->nbuttons; i++) + instance_data->buttonMap[i] = functiondata->button_map->map[i]; + } + } + } + + /* Set up any input events. */ + set_bit(EV_SYN, function_device->input->evbit); + set_bit(EV_KEY, function_device->input->evbit); + /* set bits for each button...*/ + for (i = 0; i < instance_data->deviceInfo->buttonCount; i++) { + set_bit(instance_data->buttonMap[i], function_device->input->keybit); + } + + printk(KERN_DEBUG "%s: Creating sysfs files.", __func__); + retval = device_create_file(&function_device->dev, &dev_attr_buttonCount); + if (retval) { + printk(KERN_ERR "%s: Failed to create button count.", __func__); + return retval; + } + + retval = device_create_file(&function_device->dev, &dev_attr_buttonMap); + if (retval) { + printk(KERN_ERR "%s: Failed to create button map.", __func__); + return retval; + } + + return 0; +} +EXPORT_SYMBOL(FN_19_init); + +static int getControlRegisters(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr) +{ + struct f19_instance_data *instanceData; + unsigned char *fn19Control = NULL; + int retval = 0; + + /* Get the instance data - it should have been allocated and stored in detect.*/ + instanceData = rmifninfo->fndata; + + /* Check to make sure instanceData is really there before using.*/ + if (!instanceData) { + printk(KERN_ERR "%s: Error - instance data not initialized yet when getting fn19 control registers.\n", __func__); + return -EINVAL; + } + + /* Allocate memory for the control registers. */ + instanceData->controlRegisters = kzalloc(sizeof(struct rmi_F19_control), GFP_KERNEL); + if (!instanceData->controlRegisters) { + printk(KERN_ERR "%s: Error allocating F19 control registers.\n", __func__); + return -ENOMEM; + } + + instanceData->fn19regCountForBitPerButton = (instanceData->deviceInfo->buttonCount + 7)/8; + + /* Need to compute the amount of data to read since it varies with the + * number of buttons */ + instanceData->fn19ControlRegisterSize = 1 /* 1 for filter mode and button usage bits */ + + 2*instanceData->fn19regCountForBitPerButton /* interrupt enable bits and single button participation bits */ + + 2*instanceData->deviceInfo->buttonCount /* sensormap registers + single button sensitivity registers */ + + 2; /* 1 for global sensitivity adjust + 1 for global hysteresis threshold */ + + /* Allocate a temp memory buffer to read the control registers into */ + fn19Control = kzalloc(instanceData->fn19ControlRegisterSize, GFP_KERNEL); + if (!fn19Control) { + printk(KERN_ERR "%s: Error allocating temp storage to read fn19 control info.\n", __func__); + return -ENOMEM; + } + + /* Grab a copy of the control registers. */ + retval = rmi_read_multiple(rmifninfo->sensor, fndescr->controlBaseAddr, + fn19Control, instanceData->fn19ControlRegisterSize); + if (retval) { + printk(KERN_ERR "%s: Failed to read F19 control registers.", __func__); + return retval; + } + + /* Copy over control registers data to the instance data */ + instanceData->fn19btnUsageandfilterModeOffset = 0; + instanceData->controlRegisters->buttonUsage = fn19Control[instanceData->fn19btnUsageandfilterModeOffset] & 0x3; + instanceData->controlRegisters->filterMode = fn19Control[instanceData->fn19btnUsageandfilterModeOffset] & 0xc; + + /* Fill in interrupt enable registers */ + instanceData->fn19intEnableOffset = 1; + instanceData->fn19intEnableLen = instanceData->fn19regCountForBitPerButton; + instanceData->controlRegisters->intEnableRegisters = kzalloc(instanceData->fn19intEnableLen, GFP_KERNEL); + if (!instanceData->controlRegisters->intEnableRegisters) { + printk(KERN_ERR "%s: Error allocating storage for interrupt enable control info.\n", __func__); + return -ENOMEM; + } + memcpy(instanceData->controlRegisters->intEnableRegisters, &fn19Control[instanceData->fn19intEnableOffset], + instanceData->fn19intEnableLen); + + /* Fill in single button control registers */ + instanceData->fn19singleBtnCtrlOffset = instanceData->fn19intEnableOffset + instanceData->fn19intEnableLen; + instanceData->fn19singleBtnCtrlLen = instanceData->fn19regCountForBitPerButton; + instanceData->controlRegisters->singleButtonControl = kzalloc(instanceData->fn19singleBtnCtrlLen, GFP_KERNEL); + if (!instanceData->controlRegisters->singleButtonControl) { + printk(KERN_ERR "%s: Error allocating storage for single button participation control info.\n", __func__); + return -ENOMEM; + } + memcpy(instanceData->controlRegisters->singleButtonControl, &fn19Control[instanceData->fn19singleBtnCtrlOffset], + instanceData->fn19singleBtnCtrlLen); + + /* Fill in sensor map registers */ + instanceData->fn19sensorMapCtrlOffset = instanceData->fn19singleBtnCtrlOffset + instanceData->fn19singleBtnCtrlLen; + instanceData->fn19sensorMapCtrlLen = instanceData->deviceInfo->buttonCount; + instanceData->controlRegisters->sensorMap = kzalloc(instanceData->fn19sensorMapCtrlLen, GFP_KERNEL); + if (!instanceData->controlRegisters->sensorMap) { + printk(KERN_ERR "%s: Error allocating storage for sensor map control info.\n", __func__); + return -ENOMEM; + } + memcpy(instanceData->controlRegisters->sensorMap, &fn19Control[instanceData->fn19sensorMapCtrlOffset], + instanceData->fn19sensorMapCtrlLen); + + /* Fill in single button sensitivity registers */ + instanceData->fn19singleBtnSensOffset = instanceData->fn19sensorMapCtrlOffset + instanceData->fn19sensorMapCtrlLen; + instanceData->fn19singleBtnSensLen = instanceData->deviceInfo->buttonCount; + instanceData->controlRegisters->singleButtonSensitivity = kzalloc(instanceData->fn19singleBtnSensLen, GFP_KERNEL); + if (!instanceData->controlRegisters->intEnableRegisters) { + printk(KERN_ERR "%s: Error allocating storage for single button sensitivity control info.\n", __func__); + return -ENOMEM; + } + memcpy(instanceData->controlRegisters->singleButtonSensitivity, &fn19Control[instanceData->fn19singleBtnSensOffset], + instanceData->fn19singleBtnSensLen); + + /* Fill in global sensitivity adjustment and global hysteresis threshold values */ + instanceData->fn19globalSensOffset = instanceData->fn19singleBtnSensOffset + instanceData->fn19singleBtnSensLen; + instanceData->fn19globalHystThreshOffset = instanceData->fn19globalSensOffset + 1; + instanceData->controlRegisters->globalSensitivityAdjustment = fn19Control[instanceData->fn19globalSensOffset] & 0x1f; + instanceData->controlRegisters->globalHysteresisThreshold = fn19Control[instanceData->fn19globalHystThreshOffset] & 0x0f; + + /* Free up temp storage that held copy of control registers */ + kfree(fn19Control); + + return 0; +} + +int FN_19_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, unsigned int interruptCount) +{ + unsigned char fn19queries[2]; + int retval = 0; + int i; + struct f19_instance_data *instanceData; + int fn19InterruptOffset; + + printk(KERN_DEBUG "%s: RMI4 F19 detect\n", __func__); + + instanceData = kzalloc(sizeof(struct f19_instance_data), GFP_KERNEL); + if (!instanceData) { + printk(KERN_ERR "%s: Error allocating F19 instance data.\n", __func__); + return -ENOMEM; + } + instanceData->deviceInfo = kzalloc(sizeof(struct rmi_F19_query), GFP_KERNEL); + if (!instanceData->deviceInfo) { + printk(KERN_ERR "%s: Error allocating F19 device query.\n", __func__); + return -ENOMEM; + } + rmifninfo->fndata = instanceData; + + /* Store addresses - used elsewhere to read data, + * control, query, etc. */ + rmifninfo->funcDescriptor.queryBaseAddr = fndescr->queryBaseAddr; + rmifninfo->funcDescriptor.commandBaseAddr = fndescr->commandBaseAddr; + rmifninfo->funcDescriptor.controlBaseAddr = fndescr->controlBaseAddr; + rmifninfo->funcDescriptor.dataBaseAddr = fndescr->dataBaseAddr; + rmifninfo->funcDescriptor.interruptSrcCnt = fndescr->interruptSrcCnt; + rmifninfo->funcDescriptor.functionNum = fndescr->functionNum; + + rmifninfo->numSources = fndescr->interruptSrcCnt; + + /* need to get number of fingers supported, data size, etc. - + to be used when getting data since the number of registers to + read depends on the number of fingers supported and data size. */ + retval = rmi_read_multiple(rmifninfo->sensor, fndescr->queryBaseAddr, fn19queries, + sizeof(fn19queries)); + if (retval) { + printk(KERN_ERR "%s: RMI4 F19 detect: " + "Could not read function query registers 0x%x\n", + __func__, rmifninfo->funcDescriptor.queryBaseAddr); + return retval; + } + + /* Extract device data. */ + instanceData->deviceInfo->configurable = fn19queries[0] & 0x01; + instanceData->deviceInfo->hasSensitivityAdjust = fn19queries[0] & 0x02; + instanceData->deviceInfo->hasHysteresisThreshold = fn19queries[0] & 0x04; + instanceData->deviceInfo->buttonCount = fn19queries[1] & 0x01F; + printk(KERN_DEBUG "%s: F19 device - %d buttons...", __func__, instanceData->deviceInfo->buttonCount); + + /* Need to get interrupt info to be used later when handling + interrupts. */ + rmifninfo->interruptRegister = interruptCount/8; + + /* loop through interrupts for each source in fn $11 and or in a bit + to the interrupt mask for each. */ + fn19InterruptOffset = interruptCount % 8; + + for (i = fn19InterruptOffset; + i < ((fndescr->interruptSrcCnt & 0x7) + fn19InterruptOffset); + i++) + rmifninfo->interruptMask |= 1 << i; + + /* Figure out just how much data we'll need to read. */ + instanceData->buttonDown = kcalloc(instanceData->deviceInfo->buttonCount, sizeof(bool), GFP_KERNEL); + if (!instanceData->buttonDown) { + printk(KERN_ERR "%s: Error allocating F19 button state buffer.\n", __func__); + return -ENOMEM; + } + + instanceData->buttonDataBufferSize = (instanceData->deviceInfo->buttonCount + 7) / 8; + instanceData->buttonDataBuffer = kcalloc(instanceData->buttonDataBufferSize, sizeof(unsigned char), GFP_KERNEL); + if (!instanceData->buttonDataBuffer) { + printk(KERN_ERR "%s: Failed to allocate button data buffer.", __func__); + return -ENOMEM; + } + + instanceData->buttonMap = kcalloc(instanceData->deviceInfo->buttonCount, sizeof(unsigned char), GFP_KERNEL); + if (!instanceData->buttonMap) { + printk(KERN_ERR "%s: Error allocating F19 button map.\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < instanceData->deviceInfo->buttonCount; i++) + instanceData->buttonMap[i] = BTN_0 + i; /* default values */ + + /* Grab the control register info. */ + retval = getControlRegisters(rmifninfo, fndescr); + if (retval) { + printk(KERN_ERR "%s: Error %d getting fn19 control register info.\n", __func__, retval); + return retval; + } + + return 0; +} +EXPORT_SYMBOL(FN_19_detect); + +static ssize_t rmi_f19_buttonCount_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f19_instance_data *instance_data = (struct f19_instance_data *)fn->rfi->fndata; + + return sprintf(buf, "%u\n", instance_data->deviceInfo->buttonCount); +} + +static ssize_t rmi_f19_buttonCount_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + /* Not allowed. */ + return -EPERM; +} + +static ssize_t rmi_f19_buttonMap_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f19_instance_data *instance_data = (struct f19_instance_data *)fn->rfi->fndata; + int i, len, totalLen = 0; + + /* loop through each button map value and copy it's string representation into buf */ + for (i = 0; i < instance_data->deviceInfo->buttonCount; i++) { + /* get next button mapping value and write it to buf */ + len = sprintf(buf, "%u ", instance_data->buttonMap[i]); + /* bump up ptr to next location in buf if the sprintf was valid */ + if (len > 0) { + buf += len; + totalLen += len; + } + } + + return totalLen; +} + +static ssize_t rmi_f19_buttonMap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct f19_instance_data *instance_data = (struct f19_instance_data *)fn->rfi->fndata; + unsigned int button; + int i; + int retval = count; + int buttonCount = 0; + unsigned char *tmpButtonMap; + + /* Do validation on the button map data passed in. */ + /* Store button mappings into a temp buffer and then verify button count + and data prior to clearing out old button mappings and storing the new ones. */ + tmpButtonMap = kzalloc(instance_data->deviceInfo->buttonCount, GFP_KERNEL); + if (!tmpButtonMap) { + printk(KERN_ERR "%s: Error allocating temp button map.\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < instance_data->deviceInfo->buttonCount && *buf != 0; i++) { + /* get next button mapping value and store and bump up to point to next item in buf */ + sscanf(buf, "%u", &button); + + /* Make sure the key is a valid key */ + if (button > KEY_MAX) { + printk(KERN_ERR "%s: Error - button map for button %d is not a valid value 0x%x.\n", + __func__, i, button); + retval = -EINVAL; + goto err_ret; + } + + tmpButtonMap[i] = button; + buttonCount++; + + /* bump up buf to point to next item to read */ + while (*buf != 0) { + buf++; + if (*(buf-1) == ' ') + break; + } + } + + /* Make sure the button count matches */ + if (buttonCount != instance_data->deviceInfo->buttonCount) { + printk(KERN_ERR "%s: Error - button map count of %d doesn't match device button count of %d.\n" + , __func__, buttonCount, instance_data->deviceInfo->buttonCount); + retval = -EINVAL; + goto err_ret; + } + + /* Clear out old buttonMap data */ + memset(instance_data->buttonMap, 0, buttonCount); + + /* Loop through the temp buffer and copy the button event and set the key bit for the new mapping. */ + for (i = 0; i < buttonCount; i++) { + instance_data->buttonMap[i] = tmpButtonMap[1]; + set_bit(instance_data->buttonMap[i], fn->input->keybit); + } + +err_ret: + kfree(tmpButtonMap); + + return retval; +} diff --git a/drivers/input/touchscreen/synaptics/rmi_f19.h b/drivers/input/touchscreen/synaptics/rmi_f19.h new file mode 100644 index 0000000000000000000000000000000000000000..41f3e4d8a295da0ed7a455c1faf7256f1e07be90 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f19.h @@ -0,0 +1,43 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $11 header. + * Copyright (c) 2007 - 2010, Synaptics Incorporated + * + * For every RMI4 function that has a data source - like 2D sensors, + * buttons, LEDs, GPIOs, etc. - the user will create a new rmi_function_xx.c + * file and add these functions to perform the config(), init(), report() + * and detect() functionality. The function pointers are then srored under + * the RMI function info and these functions will automatically be called by + * the global config(), init(), report() and detect() functions that will + * loop through all data sources and call the data sources functions using + * these functions pointed to by the function ptrs. + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ +#ifndef _RMI_FUNCTION_19_H +#define _RMI_FUNCTION_19_H + +void FN_19_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs); +int FN_19_config(struct rmi_function_info *rmifninfo); +int FN_19_init(struct rmi_function_device *function_device); +int FN_19_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); +/* No attention function for Fn $19 */ +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_f34.c b/drivers/input/touchscreen/synaptics/rmi_f34.c new file mode 100644 index 0000000000000000000000000000000000000000..26b63898ce5647f56b872a260b705c5742ca7bd3 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f34.c @@ -0,0 +1,557 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $34 support for sensor + * firmware reflashing. + * + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 +#include "rmi_drvr.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_function.h" +#include "rmi_f34.h" + +/* data specific to fn $34 that needs to be kept around */ +struct rmi_fn_34_data { + unsigned char status; + unsigned char cmd; + unsigned short bootloaderid; + unsigned short blocksize; +}; + + +static ssize_t rmi_fn_34_status_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_34_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + + +static ssize_t rmi_fn_34_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_34_cmd_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +static ssize_t rmi_fn_34_data_read(struct file *, + struct kobject *kobj, + struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t rmi_fn_34_data_write(struct file *, + struct kobject *kobj, + struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t rmi_fn_34_bootloaderid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_34_bootloaderid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +static ssize_t rmi_fn_34_blocksize_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmi_fn_34_blocksize_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +/* define the device attributes using DEVICE_ATTR macros */ +DEVICE_ATTR(status, 0444, rmi_fn_34_status_show, rmi_fn_34_status_store); /* RO attr */ +DEVICE_ATTR(cmd, 0664, rmi_fn_34_cmd_show, rmi_fn_34_cmd_store); /* RW attr */ +DEVICE_ATTR(bootloaderid, 0644, rmi_fn_34_bootloaderid_show, rmi_fn_34_bootloaderid_store); /* RW attr */ +DEVICE_ATTR(blocksize, 0444, rmi_fn_34_blocksize_show, rmi_fn_34_blocksize_store); /* RO attr */ + + +struct bin_attribute dev_attr_data = { + .attr = { + .name = "data", + .mode = 0644 + }, + .size = 0, + .read = rmi_fn_34_data_read, + .write = rmi_fn_34_data_write, +}; + +/* Helper fn to convert from processor specific data to our firmware specific endianness. + * TODO: Should we use ntohs or something like that? + */ +void copyEndianAgnostic(unsigned char *dest, unsigned short src) +{ + dest[0] = src%0x100; + dest[1] = src/0x100; +} + +/*. + * The interrupt handler for Fn $34. + */ +void FN_34_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs) +{ + unsigned int status; + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)rmifninfo->fndata; + + /* Read the Fn $34 status register to see whether the previous command executed OK */ + /* inform user space - through a sysfs param. */ + if (rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.dataBaseAddr+3, + (unsigned char *)&status, 1)) { + printk(KERN_ERR "%s : Could not read status from 0x%x\n", + __func__, rmifninfo->funcDescriptor.dataBaseAddr+3); + status = 0xff; /* failure */ + } + + /* set a sysfs value that the user mode can read - only upper 4 bits are the status */ + fn34data->status = status & 0xf0; /* successful is $80, anything else is failure */ +} +EXPORT_SYMBOL(FN_34_inthandler); + +void FN_34_attention(struct rmi_function_info *rmifninfo) +{ + +} +EXPORT_SYMBOL(FN_34_attention); + +int FN_34_config(struct rmi_function_info *rmifninfo) +{ + pr_debug("%s: RMI4 function $34 config\n", __func__); + return 0; +} +EXPORT_SYMBOL(FN_34_config); + + +int FN_34_init(struct rmi_function_device *function_device) +{ + int retval = 0; + unsigned char uData[2]; + struct rmi_function_info *rmifninfo = function_device->rfi; + struct rmi_fn_34_data *fn34data; + + pr_debug("%s: RMI4 function $34 init\n", __func__); + + /* Here we will need to set up sysfs files for Bootloader ID and Block size */ + fn34data = kzalloc(sizeof(struct rmi_fn_34_data), GFP_KERNEL); + if (!fn34data) { + printk(KERN_ERR "%s: Error allocating memeory for rmi_fn_34_data.\n", __func__); + return -ENOMEM; + } + rmifninfo->fndata = (void *)fn34data; + + /* set up sysfs file for Bootloader ID. */ + if (sysfs_create_file(&function_device->dev.kobj, &dev_attr_bootloaderid.attr) < 0) { + printk(KERN_ERR "%s: Failed to create sysfs file for fn 34 bootloaderid.\n", __func__); + return -ENODEV; + } + + /* set up sysfs file for Block Size. */ + if (sysfs_create_file(&function_device->dev.kobj, &dev_attr_blocksize.attr) < 0) { + printk(KERN_ERR "%s: Failed to create sysfs file for fn 34 blocksize.\n", __func__); + return -ENODEV; + } + + /* get the Bootloader ID and Block Size and store in the sysfs attributes. */ + retval = rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.queryBaseAddr, + uData, 2); + if (retval) { + printk(KERN_ERR "%s : Could not read bootloaderid from 0x%x\n", + __func__, function_device->function->functionQueryBaseAddr); + return retval; + } + /* need to convert from our firmware storage to processore specific data */ + fn34data->bootloaderid = (unsigned int)uData[0] + (unsigned int)uData[1]*0x100; + + retval = rmi_read_multiple(rmifninfo->sensor, rmifninfo->funcDescriptor.queryBaseAddr+3, + uData, 2); + if (retval) { + printk(KERN_ERR "%s : Could not read block size from 0x%x\n", + __func__, rmifninfo->funcDescriptor.queryBaseAddr+3); + return retval; + } + /* need to convert from our firmware storage to processor specific data */ + fn34data->blocksize = (unsigned int)uData[0] + (unsigned int)uData[1]*0x100; + + /* set up sysfs file for status. */ + if (sysfs_create_file(&function_device->dev.kobj, &dev_attr_status.attr) < 0) { + printk(KERN_ERR "%s: Failed to create sysfs file for fn 34 status.\n", __func__); + return -ENODEV; + } + + /* Also, sysfs will need to have a file set up to distinguish between commands - like + Config write/read, Image write/verify.*/ + /* set up sysfs file for command code. */ + if (sysfs_create_file(&function_device->dev.kobj, &dev_attr_cmd.attr) < 0) { + printk(KERN_ERR "%s: Failed to create sysfs file for fn 34 cmd.\n", __func__); + return -ENODEV; + } + + /* We will also need a sysfs file for the image/config block to write or read.*/ + /* set up sysfs bin file for binary data block. Since the image is already in our format + there is no need to convert the data for endianess. */ + if (sysfs_create_bin_file(&function_device->dev.kobj, &dev_attr_data) < 0) { + printk(KERN_ERR "%s: Failed to create sysfs file for fn 34 data.\n", __func__); + return -ENODEV; + } + + return retval; +} +EXPORT_SYMBOL(FN_34_init); + +int FN_34_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, unsigned int interruptCount) +{ + int i; + int InterruptOffset; + int retval = 0; + + pr_debug("%s: RMI4 function $34 detect\n", __func__); + if (rmifninfo->sensor == NULL) { + printk(KERN_ERR "%s: NULL sensor passed in!", __func__); + return -EINVAL; + } + + /* Store addresses - used elsewhere to read data, + * control, query, etc. */ + rmifninfo->funcDescriptor.queryBaseAddr = fndescr->queryBaseAddr; + rmifninfo->funcDescriptor.commandBaseAddr = fndescr->commandBaseAddr; + rmifninfo->funcDescriptor.controlBaseAddr = fndescr->controlBaseAddr; + rmifninfo->funcDescriptor.dataBaseAddr = fndescr->dataBaseAddr; + rmifninfo->funcDescriptor.interruptSrcCnt = fndescr->interruptSrcCnt; + rmifninfo->funcDescriptor.functionNum = fndescr->functionNum; + + rmifninfo->numSources = fndescr->interruptSrcCnt; + + /* Need to get interrupt info to be used later when handling + interrupts. */ + rmifninfo->interruptRegister = interruptCount/8; + + /* loop through interrupts for each source and or in a bit + to the interrupt mask for each. */ + InterruptOffset = interruptCount % 8; + + for (i = InterruptOffset; + i < ((fndescr->interruptSrcCnt & 0x7) + InterruptOffset); + i++) { + rmifninfo->interruptMask |= 1 << i; + } + + return retval; +} +EXPORT_SYMBOL(FN_34_detect); + +static ssize_t rmi_fn_34_bootloaderid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + + return sprintf(buf, "%u\n", fn34data->bootloaderid); +} + +static ssize_t rmi_fn_34_bootloaderid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int error; + unsigned long val; + unsigned char uData[2]; + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + + /* need to convert the string data to an actual value */ + error = strict_strtoul(buf, 10, &val); + + if (error) + return error; + + fn34data->bootloaderid = val; + + /* Write the Bootloader ID key data back to the first two Block Data registers + (F34_Flash_Data2.0 and F34_Flash_Data2.1).*/ + copyEndianAgnostic(uData, (unsigned short)val); + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr, + uData, 2); + if (error) { + printk(KERN_ERR "%s : Could not write bootloader id to 0x%x\n", + __func__, fn->function->functionDataBaseAddr); + return error; + } + + return count; +} + +static ssize_t rmi_fn_34_blocksize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + + return sprintf(buf, "%u\n", fn34data->blocksize); +} + +static ssize_t rmi_fn_34_blocksize_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + /* Block Size is RO so we shouldn't do anything if the + user space writes to the sysfs file. */ + + return -EPERM; +} + +static ssize_t rmi_fn_34_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + + return sprintf(buf, "%u\n", fn34data->status); +} + +static ssize_t rmi_fn_34_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + /* Status is RO so we shouldn't do anything if the user + app writes to the sysfs file. */ + return -EPERM; +} + +static ssize_t rmi_fn_34_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + + return sprintf(buf, "%u\n", fn34data->cmd); +} + +static ssize_t rmi_fn_34_cmd_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + unsigned long val; + unsigned char cmd; + int error; + + /* need to convert the string data to an actual value */ + error = strict_strtoul(buf, 10, &val); + + if (error) + return error; + + fn34data->cmd = val; + + /* determine the proper command to issue. + */ + switch (val) { + case ENABLE_FLASH_PROG: + /* Issue a Flash Program Enable ($0F) command to the Flash Command + (F34_Flash_Data3, bits 3:0) field.*/ + cmd = 0x0F; + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+3, + (unsigned char *)&cmd, 1); + if (error) { + printk(KERN_ERR "%s : Could not write Flash Program Enable cmd to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+3); + return error; + } + break; + + case ERASE_ALL: + /* Issue a Erase All ($03) command to the Flash Command + (F34_Flash_Data3, bits 3:0) field.*/ + cmd = 0x03; + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+3, + (unsigned char *)&cmd, 1); + if (error) { + printk(KERN_ERR "%s : Could not write Erase All cmd to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+3); + return error; + } + break; + + case ERASE_CONFIG: + /* Issue a Erase Configuration ($07) command to the Flash Command + (F34_Flash_Data3, bits 3:0) field.*/ + cmd = 0x07; + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+3, + (unsigned char *)&cmd, 1); + if (error) { + printk(KERN_ERR "%s : Could not write Erase Configuration cmd to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+3); + return error; + } + break; + + case WRITE_FW_BLOCK: + /* Issue a Write Firmware Block ($02) command to the Flash Command + (F34_Flash_Data3, bits 3:0) field.*/ + cmd = 0x02; + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+3, + (unsigned char *)&cmd, 1); + if (error) { + printk(KERN_ERR "%s : Could not write Write Firmware Block cmd to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+3); + return error; + } + break; + + case WRITE_CONFIG_BLOCK: + /* Issue a Write Config Block ($06) command to the Flash Command + (F34_Flash_Data3, bits 3:0) field.*/ + cmd = 0x06; + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+3, + (unsigned char *)&cmd, 1); + if (error) { + printk(KERN_ERR "%s : Could not write Write Config Block cmd to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+3); + return error; + } + break; + + case READ_CONFIG_BLOCK: + /* Issue a Read Config Block ($05) command to the Flash Command + (F34_Flash_Data3, bits 3:0) field.*/ + cmd = 0x05; + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+3, + (unsigned char *)&cmd, 1); + if (error) { + printk(KERN_ERR "%s : Could not write Read Config Block cmd to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+3); + return error; + } + break; + + case DISABLE_FLASH_PROG: + /* Issue a reset command ($01) - this will reboot the sensor and ATTN will now go to + the Fn $01 instead of the Fn $34 since the sensor will no longer be in Flash mode. */ + cmd = 0x01; + /*if ((error = rmi_write_multiple(fn->sensor, fn->sensor->sensorCommandBaseAddr, + (unsigned char *)&cmd, 1))) { + printk(KERN_ERR "%s : Could not write Reset cmd to 0x%x\n", + __func__, fn->sensor->sensorCommandBaseAddr); + return error; + }*/ + break; + + default: + pr_debug("%s: RMI4 function $34 - unknown command.\n", __func__); + break; + } + + return count; +} + +static ssize_t rmi_fn_34_data_read(struct file * filp, + struct kobject *kobj, + struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct rmi_function_device *fn = dev_get_drvdata(dev); + int error; + + /* TODO: add check for count to verify it's the correct blocksize */ + + /* read the data from flash into buf. */ + /* the app layer will be blocked at reading from the sysfs file. */ + /* when we return the count (or error if we fail) the app will resume. */ + error = rmi_read_multiple(fn->sensor, fn->function->functionDataBaseAddr+pos, + (unsigned char *)buf, count); + if (error) { + printk(KERN_ERR "%s : Could not read data from 0x%llx\n", + __func__, fn->function->functionDataBaseAddr+pos); + return error; + } + + return count; +} + +static ssize_t rmi_fn_34_data_write(struct file *filp, + struct kobject *kobj, + struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct rmi_function_device *fn = dev_get_drvdata(dev); + struct rmi_fn_34_data *fn34data = (struct rmi_fn_34_data *)fn->rfi->fndata; + unsigned int blocknum; + int error; + + /* write the data from buf to flash. */ + /* the app layer will be blocked at writing to the sysfs file. */ + /* when we return the count (or error if we fail) the app will resume. */ + + /* TODO: Add check on count - if non-zero veriy it's the correct blocksize */ + + /* Verify that the byte offset is always aligned on a block boundary and if not + return an error. We can't just use the mod operator % and do a (pos % fn34data->blocksize) because of a gcc + bug that results in undefined symbols. So we have to compute it the hard + way. Grumble. */ + unsigned int remainder; + div_u64_rem(pos, fn34data->blocksize, &remainder); + if (remainder) { + printk(KERN_ERR "%s : Invalid byte offset of %llx leads to invalid block number.\n", + __func__, pos); + return -EINVAL; + } + + /* Compute the block number using the byte offset (pos) and the block size. + once again, we can't just do a divide due to a gcc bug. */ + blocknum = div_u64(pos, fn34data->blocksize); + + /* Write the block number first */ + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr, + (unsigned char *)&blocknum, 2); + if (error) { + printk(KERN_ERR "%s : Could not write block number to 0x%x\n", + __func__, fn->function->functionDataBaseAddr); + return error; + } + + /* Write the data block - only if the count is non-zero */ + if (count) { + error = rmi_write_multiple(fn->sensor, fn->function->functionDataBaseAddr+2, + (unsigned char *)buf, count); + if (error) { + printk(KERN_ERR "%s : Could not write block data to 0x%x\n", + __func__, fn->function->functionDataBaseAddr+2); + return error; + } + } + + return count; +} diff --git a/drivers/input/touchscreen/synaptics/rmi_f34.h b/drivers/input/touchscreen/synaptics/rmi_f34.h new file mode 100644 index 0000000000000000000000000000000000000000..48293e3d131a9ae02586c0f1bee68058602294c9 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_f34.h @@ -0,0 +1,50 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function $34 header. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + * There is only one function $34 for each RMI4 sensor. This will be + * the function that is used to reflash the firmware and get the + * boot loader address and the boot image block size. + * + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ +#ifndef _RMI_FUNCTION_34_H +#define _RMI_FUNCTION_34_H + +/* define fn $34 commands */ +#define WRITE_FW_BLOCK 2 +#define ERASE_ALL 3 +#define READ_CONFIG_BLOCK 5 +#define WRITE_CONFIG_BLOCK 6 +#define ERASE_CONFIG 7 +#define ENABLE_FLASH_PROG 15 +#define DISABLE_FLASH_PROG 16 + +void FN_34_inthandler(struct rmi_function_info *rmifninfo, + unsigned int assertedIRQs); +int FN_34_config(struct rmi_function_info *rmifninfo); +int FN_34_init(struct rmi_function_device *function_device); +int FN_34_detect(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); +void FN_34_attention(struct rmi_function_info *rmifninfo); + +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_function.c b/drivers/input/touchscreen/synaptics/rmi_function.c new file mode 100644 index 0000000000000000000000000000000000000000..4a029f792f4427f7b87c64ac3e7f9b78bea244e2 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_function.c @@ -0,0 +1,326 @@ +/** + * Synaptics Register Mapped Interface (RMI4) - RMI Function Module. + * Copyright (C) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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. + * + *############################################################################# + */ + +static const char functionname[10] = "fn"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rmi_drvr.h" +#include "rmi_function.h" +#include "rmi_bus.h" +#include "rmi_sensor.h" +#include "rmi_f01.h" +#include "rmi_f05.h" +#include "rmi_f11.h" +#include "rmi_f19.h" +#include "rmi_f34.h" + +/* Each time a new RMI4 function support is added the developer needs to +bump the number of supported functions and add the info for +that RMI4 function to the array along with pointers to the report, +config, init and detect functions that they coded in rmi_fxx.c +and rmi_fxx.h - where xx is the RMI4 function number in hex for the new +RMI4 data source function. The information for the RMI4 functions is +obtained from the RMI4 specification document. + */ +#define rmi4_num_supported_data_src_fns 5 + +/* supported RMI4 functions list - controls what we + * will provide support for - if it's not in the list then + * the developer needs to add support functions for it.*/ +static LIST_HEAD(fns_list); +static DEFINE_MUTEX(fns_mutex); + +/* NOTE: Developer - add in any new RMI4 fn data info - function number + * and ptrs to report, config, init and detect functions. This data is + * used to point to the functions that need to be called to config, init, + * detect and report data for the new RMI4 function. Refer to the RMI4 + * specification for information on RMI4 functions. + */ +/* TODO: This will eventually go away, and each function will be an independent + * module. */ +static struct rmi_functions_data + rmi4_supported_data_src_functions[rmi4_num_supported_data_src_fns] = { + /* Fn $11 - 2D sensing */ + {.functionNumber = 0x11, .inthandlerFn = FN_11_inthandler, .configFn = FN_11_config, .initFn = FN_11_init, .detectFn = FN_11_detect, .attnFn = NULL}, + /* Fn $01 - device control */ + {.functionNumber = 0x01, .inthandlerFn = FN_01_inthandler, .configFn = FN_01_config, .initFn = FN_01_init, .detectFn = FN_01_detect, .attnFn = FN_01_attention}, + /* Fn $05 - analog report */ + {.functionNumber = 0x05, .inthandlerFn = FN_05_inthandler, .configFn = FN_05_config, .initFn = FN_05_init, .detectFn = FN_05_detect, .attnFn = NULL}, + /* Fn $19 - buttons */ + {.functionNumber = 0x19, .inthandlerFn = FN_19_inthandler, .configFn = FN_19_config, .initFn = FN_19_init, .detectFn = FN_19_detect, .attnFn = NULL}, + /* Fn $34 - firmware reflash */ + {.functionNumber = 0x34, .inthandlerFn = FN_34_inthandler, .configFn = FN_34_config, .initFn = FN_34_init, .detectFn = FN_34_detect, .attnFn = FN_34_attention}, +}; + + +/* This function is here to provide a way for external modules to access the + * functions list. It will try to find a matching function base on the passed + * in RMI4 function number and return the pointer to the struct rmi_functions + * if a match is found or NULL if not found. + */ +struct rmi_functions *rmi_find_function(int functionNum) +{ + struct rmi_functions *fn; + bool found = false; + + list_for_each_entry(fn, &fns_list, link) { + if (functionNum == fn->functionNum) { + found = true; + break; + } + } + + if (!found) + return NULL; + else + return fn; +} +EXPORT_SYMBOL(rmi_find_function); + + +static void rmi_function_config(struct rmi_function_device *function) +{ + printk(KERN_DEBUG "%s: rmi_function_config", __func__); + +} + +#if 0 /* This may not be needed anymore. */ +/** + * This is the probe function passed to the RMI4 subsystem that gives us a + * chance to recognize an RMI4 function. + */ +static int rmi_function_probe(struct rmi_function_driver *function) +{ + struct rmi_phys_driver *rpd; + + rpd = function->rpd; + + if (!rpd) { + printk(KERN_ERR "%s: Invalid rmi physical driver - null ptr.", __func__); + return 0; + } + + return 1; +} +#endif + +/** Just a stub for now. + */ +static int rmi_function_suspend(struct device *dev, pm_message_t state) +{ + printk(KERN_INFO "%s: function suspend called.", __func__); + return 0; +} + +/** Just a stub for now. + */ +static int rmi_function_resume(struct device *dev) +{ + printk(KERN_INFO "%s: function resume called.", __func__); + return 0; +} + +int rmi_function_register_driver(struct rmi_function_driver *drv, int fnNumber) +{ + int retval; + char *drvrname; + + printk(KERN_INFO "%s: Registering function driver for F%02x.\n", __func__, fnNumber); + + retval = 0; + + /* Create a function device and function driver for this Fn */ + drvrname = kzalloc(sizeof(functionname) + 4, GFP_KERNEL); + if (!drvrname) { + printk(KERN_ERR "%s: Error allocating memeory for rmi_function_driver name.\n", __func__); + return -ENOMEM; + } + sprintf(drvrname, "fn%02x", fnNumber); + + drv->drv.name = drvrname; + drv->module = drv->drv.owner; + + drv->drv.suspend = rmi_function_suspend; + drv->drv.resume = rmi_function_resume; + + /* register the sensor driver */ + retval = driver_register(&drv->drv); + if (retval) { + printk(KERN_ERR "%s: Failed driver_register %d\n", + __func__, retval); + } + + return retval; +} +EXPORT_SYMBOL(rmi_function_register_driver); + +void rmi_function_unregister_driver(struct rmi_function_driver *drv) +{ + printk(KERN_INFO "%s: Unregistering function driver.\n", __func__); + + driver_unregister(&drv->drv); +} +EXPORT_SYMBOL(rmi_function_unregister_driver); + +int rmi_function_register_device(struct rmi_function_device *function_device, int fnNumber) +{ + struct input_dev *input; + int retval; + + printk(KERN_INFO "%s: Registering function device for F%02x.\n", __func__, fnNumber); + + retval = 0; + + /* make name - fn11, fn19, etc. */ + dev_set_name(&function_device->dev, "%sfn%02x", function_device->sensor->drv.name, fnNumber); + dev_set_drvdata(&function_device->dev, function_device); + retval = device_register(&function_device->dev); + if (retval) { + printk(KERN_ERR "%s: Failed device_register for function device.\n", + __func__); + return retval; + } + + input = input_allocate_device(); + if (input == NULL) { + printk(KERN_ERR "%s: Failed to allocate memory for a " + "new input device.\n", + __func__); + return -ENOMEM; + } + + input->name = dev_name(&function_device->dev); + input->phys = "rmi_function"; + function_device->input = input; + + + /* init any input specific params for this function */ + function_device->rmi_funcs->init(function_device); + + retval = input_register_device(input); + + if (retval) { + printk(KERN_ERR "%s: Failed input_register_device.\n", + __func__); + return retval; + } + + + rmi_function_config(function_device); + + return retval; +} +EXPORT_SYMBOL(rmi_function_register_device); + +void rmi_function_unregister_device(struct rmi_function_device *dev) +{ + printk(KERN_INFO "%s: Unregistering function device.n", __func__); + + input_unregister_device(dev->input); + device_unregister(&dev->dev); +} +EXPORT_SYMBOL(rmi_function_unregister_device); + +static int __init rmi_function_init(void) +{ + struct rmi_functions_data *rmi4_fn; + int i; + + printk(KERN_DEBUG "%s: RMI Function Init\n", __func__); + + /* Initialize global list of RMI4 Functions. + We need to add the supported RMI4 funcions so that we will have + pointers to the associated functions for init, config, report and + detect. See rmi.h for more details. The developer will add a new + RMI4 function number in the array in rmi_drvr.h, then add a new file to + the build (called rmi_fXX.c where XX is the hex number for + the added RMI4 function). The rest should be automatic. + */ + + /* for each function number defined in rmi.h creat a new rmi_function + struct and initialize the pointers to the servicing functions and then + add it into the global list for function support. + */ + for (i = 0; i < rmi4_num_supported_data_src_fns; i++) { + /* Add new rmi4 function struct to list */ + struct rmi_functions *fn = kzalloc(sizeof(*fn), GFP_KERNEL); + if (!fn) { + printk(KERN_ERR "%s: could not allocate memory " + "for rmi_function struct for function 0x%x\n", + __func__, + rmi4_supported_data_src_functions[i].functionNumber); + return -ENOMEM; + } else { + + rmi4_fn = &rmi4_supported_data_src_functions[i]; + fn->functionNum = rmi4_fn->functionNumber; + /* Fill in ptrs to functions. The functions are + linked in from a file called rmi_fxx.c + where xx is the hex number of the RMI4 function + from the RMI4 spec. Also, the function prototypes + need to be added to rmi_fxx.h - also where + xx is the hex number of the RMI4 function. So + that you don't get compile errors and that new + header needs to be included in the rmi_function.h + */ + fn->inthandler = rmi4_fn->inthandlerFn; + fn->config = rmi4_fn->configFn; + fn->init = rmi4_fn->initFn; + fn->detect = rmi4_fn->detectFn; + fn->attention = rmi4_fn->attnFn; + + /* Add the new fn to the global list */ + mutex_lock(&fns_mutex); + list_add_tail(&fn->link, &fns_list); + mutex_unlock(&fns_mutex); + } + } + + return 0; +} + +static void __exit rmi_function_exit(void) +{ + printk(KERN_DEBUG "%s: RMI Function Exit\n", __func__); +} + + +module_init(rmi_function_init); +module_exit(rmi_function_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("RMI4 Function Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/synaptics/rmi_function.h b/drivers/input/touchscreen/synaptics/rmi_function.h new file mode 100644 index 0000000000000000000000000000000000000000..801609bd77a02857d274d9c39808cfcb228ff3d0 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_function.h @@ -0,0 +1,213 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) Function Device Header File. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ + +#ifndef _RMI_FUNCTION_H +#define _RMI_FUNCTION_H + +#include +#include + + +/* For each function present on the RMI device, there will be a corresponding + * entry in the functions list of the rmi_sensor_driver structure. This entry + * gives information about the number of data sources and the number of data + * registers associated with the function. + */ +struct rmi_function_info { + /* The sensor this function belongs to. + */ + struct rmi_sensor_driver *sensor; + + /* A device associated with this function. + */ + struct rmi_function_device *function_device; + + unsigned char functionNum; + + /* This is the number of data sources associated with the function.*/ + unsigned char numSources; + + /* This is the number of data registers to read.*/ + unsigned char dataRegBlockSize; + + /* This is the interrupt register and mask - needed for enabling the + * interrupts and for checking what source had caused the attention line + * interrupt. + */ + unsigned char interruptRegister; + unsigned char interruptMask; + + /* This is the RMI function descriptor associated with this function. + * It contains the Base addresses for the functions query, command, + * control, and data registers. + */ + struct rmi_function_descriptor funcDescriptor; + + /* pointer to data specific to a functions implementation. */ + void *fndata; + + /* A list of the function information. + * This list uses the standard kernel linked list implementation. + * Documentation on on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head link; +}; + + +/* This struct is for creating a list of RMI4 functions that have data sources +associated with them. This is to facilitate adding new support for other +data sources besides 2D sensors. +To add a new data source support, the developer will create a new file +and add these 4 functions below with FN$## in front of the names - where +## is the hex number for the function taken from the RMI4 specification. + +The function number will be associated with this and later will be used to +match the RMI4 function to the 4 functions for that RMI4 function number. +The user will also have to add code that adds the new rmi_functions item +to the global list of RMI4 functions and stores the pointers to the 4 +functions in the function pointers. + */ +struct rmi_functions { + unsigned char functionNum; + + /* Pointers to function specific functions for interruptHandler, config, init + , detect and attention. */ + /* These ptrs. need to be filled in for every RMI4 function that has + data source(s) associated with it - like fn $11 (2D sensors), + fn $19 (buttons), etc. Each RMI4 function that has data sources + will be added into a list that is used to match the function + number against the number stored here. + */ + /* The sensor implementation will call this whenever and IRQ is + * dispatched that this function is interested in. + */ + void (*inthandler)(struct rmi_function_info *rfi, unsigned int assertedIRQs); + + int (*config)(struct rmi_function_info *rmifninfo); + int (*init)(struct rmi_function_device *function_device); + int (*detect)(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); + /** If this is non-null, the sensor implemenation will call this + * whenever the ATTN line is asserted. + */ + void (*attention)(struct rmi_function_info *rmifninfo); + + + /* Standard kernel linked list implementation. + * Documentation on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head link; +}; + + +typedef void(*inthandlerFuncPtr)(struct rmi_function_info *rfi, unsigned int assertedIRQs); +typedef int(*configFuncPtr)(struct rmi_function_info *rmifninfo); +typedef int(*initFuncPtr)(struct rmi_function_device *function_device); +typedef int(*detectFuncPtr)(struct rmi_function_info *rmifninfo, + struct rmi_function_descriptor *fndescr, + unsigned int interruptCount); +typedef void (*attnFuncPtr)(struct rmi_function_info *rmifninfo); + +struct rmi_functions_data { + int functionNumber; + inthandlerFuncPtr inthandlerFn; + configFuncPtr configFn; + initFuncPtr initFn; + detectFuncPtr detectFn; + attnFuncPtr attnFn; +}; + + +struct rmi_functions *rmi_find_function(int functionNum); +int rmi_functions_init(struct input_dev *inputdev); + +struct rmi_function_driver { + struct module *module; + struct device_driver drv; + + /* Probe Function + * This function is called to give the function driver layer an + * opportunity to claim an RMI function. + */ + int (*probe)(struct rmi_function_driver *function); + /* Config Function + * This function is called after a successful probe. It gives the + * function driver an opportunity to query and/or configure an RMI + * function before data starts flowing. + */ + void (*config)(struct rmi_function_driver *function); + + unsigned short functionQueryBaseAddr; /* RMI4 function control */ + unsigned short functionControlBaseAddr; + unsigned short functionCommandBaseAddr; + unsigned short functionDataBaseAddr; + unsigned int interruptRegisterOffset; /* offset from start of interrupt registers */ + unsigned int interruptMask; + + /* pointer to the corresponding phys driver info for this sensor */ + /* The phys driver has the pointers to read, write, etc. */ + /* Probably don't need it here - used down in bus driver and sensor driver */ + struct rmi_phys_driver *rpd; + + /* Standard kernel linked list implementation. + * Documentation on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head function_drivers; /* link function drivers into list */ +}; + +struct rmi_function_device { + struct rmi_function_driver *function; + struct device dev; + struct input_dev *input; + struct rmi_sensor_driver *sensor; /* need this to be bound to phys driver layer */ + + /* the function ptrs to the config, init, detect and + report fns for this rmi function device. */ + struct rmi_functions *rmi_funcs; + struct rmi_function_info *rfi; + + /** An RMI sensor might actually have several IRQ registers - + * this tells us which IRQ register this function is interested in. + */ + unsigned int irqRegisterSet; + + /** This is a mask of the IRQs the function is interested in. + */ + unsigned int irqMask; + + /* Standard kernel linked list implementation. + * Documentation on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head functions; /* link functions into list */ +}; + +int rmi_function_register_device(struct rmi_function_device *dev, int fnNumber); + +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_i2c.c b/drivers/input/touchscreen/synaptics/rmi_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..1932b9be8a783bf1d670b70a1bbf671198c88d81 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_i2c.c @@ -0,0 +1,633 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) I2C Physical Layer Driver. + * Copyright (c) 2007-2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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 + +#include "rmi_drvr.h" + +#define DRIVER_NAME "rmi4_ts" + +#define DEVICE_NAME "rmi4_ts" + +/* Used to lock access to the page address.*/ +/* TODO: for multiple device support will need a per-device mutex */ +static DEFINE_MUTEX(page_mutex); + + +static const struct i2c_device_id rmi_i2c_id_table[] = { + { DEVICE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, rmi_i2c_id_table); + + +/* Used to count the number of I2C modules we get. + */ +static int device_count; + + +/* + * This is the data kept on a per instance (client) basis. This data is + * always accessible by using the container_of() macro of the various elements + * inside. + */ +struct instance_data { + int instance_no; + int irq; + struct rmi_phys_driver rmiphysdrvr; + struct i2c_client *i2cclient; /* pointer to i2c_client for later use in + read, write, read_multiple, etc. */ + int page; +}; + +/* + * RMI devices have 16-bit addressing, but some of the physical + * implementations (like SMBus) only have 8-bit addressing. So RMI implements + * a page address at 0xff of every page so we can reliable page addresses + * every 256 registers. This function sets the page. + * + * The page_mutex lock must be held when this function is entered. + * + * param[in] id - The pointer to the instance_data struct + * param[in] page - The new page address. + * returns zero on success, non-zero on failure. + */ +/** Writing to page select is giving errors in some configurations. It's + * not needed for basic operation, so we've turned it off for the moment. + */ +#if defined(USE_PAGESELECT) +int +rmi_set_page(struct instance_data *instancedata, unsigned int page) +{ + char txbuf[2]; + int retval; + txbuf[0] = 0xff; + txbuf[1] = page; + retval = i2c_master_send(instancedata->i2cclient, txbuf, 2); + if (retval != 2) { + dev_err(&instancedata->i2cclient->dev, + "%s: Set page failed: %d.", __func__, retval); + } else { + retval = 0; + instancedata->page = page; + } + return retval; +} +#else +int +rmi_set_page(struct instance_data *instancedata, unsigned int page) +{ + return 0; +} +#endif + +/* + * Read a single register through i2c. + * + * param[in] pd - The pointer to the rmi_phys_driver struct + * param[in] address - The address at which to start the data read. + * param[out] valp - Pointer to the buffer where the data will be stored. + * returns zero upon success (with the byte read in valp), non-zero upon error. + */ +static int +rmi_i2c_read(struct rmi_phys_driver *physdrvr, unsigned short address, char *valp) +{ + struct instance_data *instancedata = + container_of(physdrvr, struct instance_data, rmiphysdrvr); + + char txbuf[2]; + int retval = 0; + int retry_count = 0; + + /* Can't have anyone else changing the page behind our backs */ + mutex_lock(&page_mutex); + + if (((address >> 8) & 0xff) != instancedata->page) { + /* Switch pages */ + retval = rmi_set_page(instancedata, ((address >> 8) & 0xff)); + if (retval) + goto exit; + } + +retry: + txbuf[0] = address & 0xff; + retval = i2c_master_send(instancedata->i2cclient, txbuf, 1); + + if (retval != 1) { + dev_err(&instancedata->i2cclient->dev, "%s: Write fail: %d\n", + __func__, retval); + goto exit; + } + retval = i2c_master_recv(instancedata->i2cclient, txbuf, 1); + + if (retval != 1) { + if (++retry_count == 5) { + dev_err(&instancedata->i2cclient->dev, + "%s: Read of 0x%04x fail: %d\n", + __func__, address, retval); + } else { + mdelay(10); + rmi_set_page(instancedata, ((address >> 8) & 0xff)); + goto retry; + } + } else { + retval = 0; + *valp = txbuf[0]; + } +exit: + + mutex_unlock(&page_mutex); + return retval; +} + +/* + * Same as rmi_i2c_read, except that multiple bytes are allowed to be read. + * + * param[in] pd - The pointer to the rmi_phys_driver struct + * param[in] address - The address at which to start the data read. + * param[out] valp - Pointer to the buffer where the data will be stored. This + * buffer must be at least size bytes long. + * param[in] size - The number of bytes to be read. + * returns zero upon success (with the byte read in valp), non-zero upon error. + * + */ +static int +rmi_i2c_read_multiple(struct rmi_phys_driver *physdrvr, unsigned short address, + char *valp, int size) +{ + struct instance_data *instancedata = + container_of(physdrvr, struct instance_data, rmiphysdrvr); + + char txbuf[2]; + int retval = 0; + int retry_count = 0; + + /* Can't have anyone else changing the page behind our backs */ + mutex_lock(&page_mutex); + + if (((address >> 8) & 0xff) != instancedata->page) { + /* Switch pages */ + retval = rmi_set_page(instancedata, ((address >> 8) & 0xff)); + if (retval) + goto exit; + } + +retry: + txbuf[0] = address & 0xff; + retval = i2c_master_send(instancedata->i2cclient, txbuf, 1); + + if (retval != 1) { + dev_err(&instancedata->i2cclient->dev, "%s: Write fail: %d\n", + __func__, retval); + goto exit; + } + retval = i2c_master_recv(instancedata->i2cclient, valp, size); + + if (retval != size) { + if (++retry_count == 5) { + dev_err(&instancedata->i2cclient->dev, + "%s: Read of 0x%04x size %d fail: %d\n", + __func__, address, size, retval); + } else { + mdelay(10); + rmi_set_page(instancedata, ((address >> 8) & 0xff)); + goto retry; + } + } else { + retval = 0; + } +exit: + + mutex_unlock(&page_mutex); + return retval; +} + + +/* + * Write a single register through i2c. + * You can write multiple registers at once, but I made the functions for that + * seperate for performance reasons. Writing multiple requires allocation and + * freeing. + * + * param[in] pd - The pointer to the rmi_phys_driver struct + * param[in] address - The address at which to start the write. + * param[in] data - The data to be written. + * returns one upon success, something else upon error. + */ +static int +rmi_i2c_write(struct rmi_phys_driver *physdrvr, unsigned short address, char data) +{ + struct instance_data *instancedata = + container_of(physdrvr, struct instance_data, rmiphysdrvr); + + unsigned char txbuf[2]; + int retval = 0; + + /* Can't have anyone else changing the page behind our backs */ + mutex_lock(&page_mutex); + + if (((address >> 8) & 0xff) != instancedata->page) { + /* Switch pages */ + retval = rmi_set_page(instancedata, ((address >> 8) & 0xff)); + if (retval) + goto exit; + } + + txbuf[0] = address & 0xff; + txbuf[1] = data; + retval = i2c_master_send(instancedata->i2cclient, txbuf, 2); + + /* TODO: Add in retry on writes only in certian error return values */ + if (retval != 2) { + dev_err(&instancedata->i2cclient->dev, "%s: Write fail: %d\n", + __func__, retval); + goto exit; /* Leave this in case we add code below */ + } else { + retval = 1; + } +exit: + + mutex_unlock(&page_mutex); + return retval; +} + +/* + * Write multiple registers. + * + * For fast writes of 16 bytes of less we will re-use a buffer on the stack. + * For larger writes (like for RMI reflashing) we will need to allocate a + * temp buffer. + * + * param[in] pd - The pointer to the rmi_phys_driver struct + * param[in] address - The address at which to start the write. + * param[in] valp - A pointer to a buffer containing the data to be written. + * param[in] size - The number of bytes to write. + * returns one upon success, something else upon error. + */ +static int +rmi_i2c_write_multiple(struct rmi_phys_driver *physdrvr, unsigned short address, + char *valp, int size) +{ + struct instance_data *instancedata = + container_of(physdrvr, struct instance_data, rmiphysdrvr); + + unsigned char *txbuf; + unsigned char txbuf_most[17]; /* Use this buffer for fast writes of 16 + bytes or less. The first byte will + contain the address at which to start + the write. */ + int retval = 0; + int i; + + if (size < sizeof(txbuf_most)) { + /* Avoid an allocation if we can help it. */ + txbuf = txbuf_most; + } else { + /* over 16 bytes write we'll need to allocate a temp buffer */ + txbuf = kzalloc(size + 1, GFP_KERNEL); + if (!txbuf) + return -ENOMEM; + } + + /* Yes, it stinks here that we have to copy the buffer */ + /* We copy from valp to txbuf leaving + the first location open for the address */ + for (i = 0; i < size; i++) + txbuf[i + 1] = valp[i]; + + /* Can't have anyone else changing the page behind our backs */ + mutex_lock(&page_mutex); + + if (((address >> 8) & 0xff) != instancedata->page) { + /* Switch pages */ + retval = rmi_set_page(instancedata, ((address >> 8) & 0xff)); + if (retval) + goto exit; + } + + txbuf[0] = address & 0xff; /* put the address in the first byte */ + retval = i2c_master_send(instancedata->i2cclient, txbuf, size + 1); + + /* TODO: Add in retyr on writes only in certian error return values */ + if (retval != 1) { + dev_err(&instancedata->i2cclient->dev, "%s: Write fail: %d\n", + __func__, retval); + goto exit; + } +exit: + + mutex_unlock(&page_mutex); + if (txbuf != txbuf_most) + kfree(txbuf); + return retval; +} + +/* + * This is the Interrupt Service Routine. It just notifies the application + * layer that attention is required. + */ +static irqreturn_t +i2c_attn_isr(int irq, void *info) +{ + struct instance_data *instancedata = info; + + disable_irq_nosync(instancedata->irq); + + if (instancedata->rmiphysdrvr.attention) { + instancedata->rmiphysdrvr.attention(&instancedata->rmiphysdrvr, + instancedata->instance_no); + } + + return IRQ_HANDLED; +} + +/* The Driver probe function - will allocate and initialize the instance + * data and request the irq and set the instance data as the clients + * platform data then register the physical driver which will do a scan of + * the RMI4 Physical Device Table and enumerate any RMI4 functions that + * have data sources associated with them. + */ +static int +rmi_i2c_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) +{ + + struct instance_data *instancedata; + int retval = 0; + int irqtype = 0; + + struct rmi_i2c_platformdata *platformdata; + struct rmi_sensordata *sensordata; + + if (client == NULL) { + printk(KERN_ERR "%s: Invalid NULL client received.", __func__); + return -EINVAL; + } + + printk(KERN_DEBUG "%s: Probing i2c RMI device, addr: 0x%02x", __func__, client->addr); + + + /* Allocate and initialize the instance data for this client */ + instancedata = kzalloc(sizeof(*instancedata), GFP_KERNEL); + if (!instancedata) { + dev_err(&client->dev, + "%s: Out of memory trying to allocate instance_data.\n", + __func__); + return -ENOMEM; + } + + instancedata->rmiphysdrvr.name = DRIVER_NAME; + instancedata->rmiphysdrvr.write = rmi_i2c_write; + instancedata->rmiphysdrvr.read = rmi_i2c_read; + instancedata->rmiphysdrvr.write_multiple = rmi_i2c_write_multiple; + instancedata->rmiphysdrvr.read_multiple = rmi_i2c_read_multiple; + instancedata->rmiphysdrvr.module = THIS_MODULE; + + /* Set default to polling in case no matching platform data is located + for this device. We'll still work but in polling mode since we didn't + find any irq info */ + instancedata->rmiphysdrvr.polling_required = true; + + instancedata->page = 0xffff; /* Force a set page the first time */ + + /* cast to our struct rmi_i2c_platformdata so we know + the fields (see rmi_ic2.h) */ + platformdata = client->dev.platform_data; + if (platformdata == NULL) { + printk(KERN_ERR "%s: CONFIGURATION ERROR - platform data is NULL.", __func__); + return -EINVAL; + } + sensordata = platformdata->sensordata; + + /* Egregiously horrible delay here that seems to prevent I2C disasters on + * certain broken dev systems. In most cases, you can safely leave this + * as zero. + */ + if (platformdata->delay_ms > 0) + mdelay(platformdata->delay_ms); + + /* Call the platform setup routine, to do any setup that is required before + * interacting with the device. + */ + if (sensordata && sensordata->rmi_sensor_setup) { + retval = sensordata->rmi_sensor_setup(); + if (retval) { + printk(KERN_ERR "%s: sensor setup failed with code %d.", __func__, retval); + return retval; + } + } + + printk(KERN_DEBUG "%s: sensor addr: 0x%02x irq: 0x%x type: %d", + __func__, platformdata->i2c_address, platformdata->irq, platformdata->irq_type); + if (client->addr != platformdata->i2c_address) { + printk(KERN_ERR "%s: CONFIGURATION ERROR - client I2C address 0x%02x doesn't match platform data address 0x%02x.", __func__, client->addr, platformdata->i2c_address); + return -EINVAL; + } + + instancedata->instance_no = device_count++; + + /* set the device name using the instance_no appended + to DEVICE_NAME to make a unique name */ + dev_set_name(&client->dev, + "rmi4-i2c%d", instancedata->instance_no); + + /* Determine if we need to poll (inefficient) or use interrupts. + */ + if (platformdata->irq) { + instancedata->irq = platformdata->irq; + switch (platformdata->irq_type) { + case IORESOURCE_IRQ_HIGHEDGE: + irqtype = IRQF_TRIGGER_RISING; + break; + case IORESOURCE_IRQ_LOWEDGE: + irqtype = IRQF_TRIGGER_FALLING; + break; + case IORESOURCE_IRQ_HIGHLEVEL: + irqtype = IRQF_TRIGGER_HIGH; + break; + case IORESOURCE_IRQ_LOWLEVEL: + irqtype = IRQF_TRIGGER_LOW; + break; + default: + dev_warn(&client->dev, + "%s: Invalid IRQ flags in platform data.\n", + __func__); + kfree(instancedata); + return -ENXIO; + } + + instancedata->rmiphysdrvr.polling_required = false; + instancedata->rmiphysdrvr.irq = instancedata->irq; + + } else { + instancedata->rmiphysdrvr.polling_required = true; + dev_info(&client->dev, + "%s: No IRQ info given. Polling required.\n", + __func__); + } + + /* Store the instance data in the i2c_client - we need to do this prior + * to calling register_physical_driver since it may use the read, write + * functions. If nothing was found then the id fields will be set to 0 + * for the irq and the default will be set to polling required so we + * will still work but in polling mode. */ + i2c_set_clientdata(client, instancedata); + + /* Copy i2c_client pointer into instance_data's i2c_client pointer for + later use in rmi4_read, rmi4_write, etc. */ + instancedata->i2cclient = client; + + /* Register sensor drivers - this will call the detect function that + * will then scan the device and determine the supported RMI4 sensors + * and functions. + */ + retval = rmi_register_sensor(&instancedata->rmiphysdrvr, platformdata->sensordata); + if (retval) { + dev_err(&client->dev, "%s: Failed to Register %s sensor drivers\n", + __func__, instancedata->rmiphysdrvr.name); + i2c_set_clientdata(client, NULL); + kfree(instancedata); + return retval; + } + + if (instancedata->rmiphysdrvr.polling_required == false) { + retval = request_irq(instancedata->irq, i2c_attn_isr, + irqtype, "rmi_i2c", instancedata); + if (retval) { + dev_err(&client->dev, "%s: failed to obtain IRQ %d. Result: %d.", + __func__, instancedata->irq, retval); + dev_info(&client->dev, "%s: Reverting to polling.\n", __func__); + instancedata->rmiphysdrvr.polling_required = true; + /* TODO: Need to revert back to polling - create and start timer. */ + } else { + dev_dbg(&client->dev, "%s: got irq.\n", __func__); + } + } + + dev_dbg(&client->dev, "%s: Successfully registered %s sensor driver.\n", + __func__, instancedata->rmiphysdrvr.name); + + printk(KERN_INFO "%s: Successfully registered %s sensor driver.\n", __func__, instancedata->rmiphysdrvr.name); + + return retval; +} + +/* The Driver remove function. We tear down the instance data and unregister + * the phys driver in this call. + */ +static int +rmi_i2c_remove(struct i2c_client *client) +{ + struct instance_data *instancedata = + i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s: Unregistering phys driver %s\n", __func__, + instancedata->rmiphysdrvr.name); + + rmi_unregister_sensors(&instancedata->rmiphysdrvr); + + dev_dbg(&client->dev, "%s: Unregistered phys driver %s\n", + __func__, instancedata->rmiphysdrvr.name); + + /* only free irq if we have an irq - otherwise the instance_data + will be 0 for that field */ + if (instancedata->irq) + free_irq(instancedata->irq, instancedata); + + kfree(instancedata); + dev_dbg(&client->dev, "%s: Remove successful\n", __func__); + + return 0; +} + +#ifdef CONFIG_PM +static int +rmi_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + /* Touch sleep mode */ + return 0; +} + +static int +rmi_i2c_resume(struct i2c_client *client) +{ + /* Re-initialize upon resume */ + return 0; +} +#else +#define rmi_i2c_suspend NULL +#define rmi_i2c_resume NULL +#endif + +/* + * This structure tells the i2c subsystem about us. + * + * TODO: we should add .suspend and .resume fns. + * + */ +static struct i2c_driver rmi_i2c_driver = { + .probe = rmi_i2c_probe, + .remove = rmi_i2c_remove, + .suspend = rmi_i2c_suspend, + .resume = rmi_i2c_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .id_table = rmi_i2c_id_table, +}; + +/* + * Register ourselves with i2c Chip Driver. + * + */ +static int __init rmi_phys_i2c_init(void) +{ + return i2c_add_driver(&rmi_i2c_driver); +} + +/* + * Un-register ourselves from the i2c Chip Driver. + * + */ +static void __exit rmi_phys_i2c_exit(void) +{ + i2c_del_driver(&rmi_i2c_driver); +} + + +module_init(rmi_phys_i2c_init); +module_exit(rmi_phys_i2c_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("RMI4 Driver I2C Physical Layer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/synaptics/rmi_sensor.c b/drivers/input/touchscreen/synaptics/rmi_sensor.c new file mode 100644 index 0000000000000000000000000000000000000000..2c64609c1aeaa0171746b9b8fd0f670d57f96f12 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_sensor.c @@ -0,0 +1,662 @@ +/** + * Synaptics Register Mapped Interface (RMI4) - RMI Sensor Module. + * Copyright (C) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################ + * GPL + * + * 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. + * + *############################################################################ + */ + +static const char sensorname[] = "sensor"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "rmi_drvr.h" +#include "rmi_bus.h" +#include "rmi_function.h" +#include "rmi_sensor.h" + +long polltime = 25000000; /* Shared with rmi_function.c. */ +EXPORT_SYMBOL(polltime); +module_param(polltime, long, 0644); +MODULE_PARM_DESC(polltime, "How long to wait between polls (in nano seconds)."); + + +#define PDT_START_SCAN_LOCATION 0x00E9 +#define PDT_END_SCAN_LOCATION 0x0005 +#define PDT_ENTRY_SIZE 0x0006 + +static DEFINE_MUTEX(rfi_mutex); + +struct rmi_functions *rmi_find_function(int functionNum); + +int rmi_read(struct rmi_sensor_driver *sensor, unsigned short address, + char *dest) +{ + struct rmi_phys_driver *rpd = sensor->rpd; + if (!rpd) + return -ENODEV; + return rpd->read(rpd, address, dest); +} +EXPORT_SYMBOL(rmi_read); + +int rmi_write(struct rmi_sensor_driver *sensor, unsigned short address, + unsigned char data) +{ + struct rmi_phys_driver *rpd = sensor->rpd; + if (!rpd) + return -ENODEV; + return rpd->write(rpd, address, data); +} +EXPORT_SYMBOL(rmi_write); + +int rmi_read_multiple(struct rmi_sensor_driver *sensor, + unsigned short address, char *dest, int length) +{ + struct rmi_phys_driver *rpd = sensor->rpd; + if (!rpd) + return -ENODEV; + return rpd->read_multiple(rpd, address, dest, length); +} +EXPORT_SYMBOL(rmi_read_multiple); + +int rmi_write_multiple(struct rmi_sensor_driver *sensor, + unsigned short address, unsigned char *data, int length) +{ + struct rmi_phys_driver *rpd = sensor->rpd; + if (!rpd) + return -ENODEV; + return rpd->write_multiple(rpd, address, data, length); +} +EXPORT_SYMBOL(rmi_write_multiple); + +/* Utility routine to set bits in a register. */ +int rmi_set_bits(struct rmi_sensor_driver *sensor, unsigned short address, + unsigned char bits) +{ + unsigned char reg_contents; + int retval; + + retval = rmi_read(sensor, address, ®_contents); + if (retval) + return retval; + reg_contents = reg_contents | bits; + retval = rmi_write(sensor, address, reg_contents); + if (retval == 1) + return 0; + else if (retval == 0) + return -EINVAL; /* TODO: What should this be? */ + else + return retval; +} +EXPORT_SYMBOL(rmi_set_bits); + +/* Utility routine to clear bits in a register. */ +int rmi_clear_bits(struct rmi_sensor_driver *sensor, + unsigned short address, unsigned char bits) +{ + unsigned char reg_contents; + int retval; + + retval = rmi_read(sensor, address, ®_contents); + if (retval) + return retval; + reg_contents = reg_contents & ~bits; + retval = rmi_write(sensor, address, reg_contents); + if (retval == 1) + return 0; + else if (retval == 0) + return -EINVAL; /* TODO: What should this be? */ + else + return retval; +} +EXPORT_SYMBOL(rmi_clear_bits); + +/* Utility routine to set the value of a bit field in a register. */ +int rmi_set_bit_field(struct rmi_sensor_driver *sensor, + unsigned short address, unsigned char field_mask, unsigned char bits) +{ + unsigned char reg_contents; + int retval; + + retval = rmi_read(sensor, address, ®_contents); + if (retval) + return retval; + reg_contents = (reg_contents & ~field_mask) | bits; + retval = rmi_write(sensor, address, reg_contents); + if (retval == 1) + return 0; + else if (retval == 0) + return -EINVAL; /* TODO: What should this be? */ + else + return retval; +} +EXPORT_SYMBOL(rmi_set_bit_field); + +bool rmi_polling_required(struct rmi_sensor_driver *sensor) +{ + return sensor->polling_required; +} +EXPORT_SYMBOL(rmi_polling_required); + +/** Functions can call this in order to dispatch IRQs. */ +void dispatchIRQs(struct rmi_sensor_driver *sensor, unsigned int irqStatus) +{ + struct rmi_function_info *functionInfo; + + list_for_each_entry(functionInfo, &sensor->functions, link) { + if ((functionInfo->interruptMask & irqStatus)) { + if (functionInfo->function_device-> + rmi_funcs->inthandler) { + /* Call the functions interrupt handler function. */ + functionInfo->function_device->rmi_funcs-> + inthandler(functionInfo, + (functionInfo->interruptMask & irqStatus)); + } + } + } +} + +/** + * This is the function we pass to the RMI4 subsystem so we can be notified + * when attention is required. It may be called in interrupt context. + */ +static void attention(struct rmi_phys_driver *physdrvr, int instance) +{ + /* All we have to do is schedule work. */ + + /* TODO: It's possible that workIsReady is not really needed anymore. + * Investigate this to see if the race condition between setting up + * the work and enabling the interrupt still exists. + */ + if (physdrvr->sensor->workIsReady) { + schedule_work(&(physdrvr->sensor->work)); + } else { + /* Got an interrupt but we're not ready so enable the irq + * so it doesn't get hung up + */ + printk(KERN_DEBUG "%s: Work not initialized yet -" + "enabling irqs.\n", __func__); + enable_irq(physdrvr->irq); + } +} + +/** + * This notifies any interested functions that there + * is an Attention interrupt. The interested functions should take + * appropriate + * actions (such as reading the interrupt status register and dispatching any + * appropriate RMI4 interrupts). + */ +void attn_notify(struct rmi_sensor_driver *sensor) +{ + struct rmi_function_info *functionInfo; + + /* check each function that has data sources and if the interrupt for + * that triggered then call that RMI4 functions report() function to + * gather data and report it to the input subsystem + */ + list_for_each_entry(functionInfo, &sensor->functions, link) { + if (functionInfo->function_device && + functionInfo->function_device->rmi_funcs->attention) + functionInfo->function_device-> + rmi_funcs->attention(functionInfo); + } +} + +/* This is the worker function - for now it simply has to call attn_notify. + * This work should be scheduled whenever an ATTN interrupt is asserted by + * the touch sensor. + * We then call attn_notify to dispatch notification of the ATTN interrupt + * to all + * interested functions. After all the attention handling functions + * have returned, it is presumed safe to re-enable the Attention interrupt. + */ +static void sensor_work_func(struct work_struct *work) +{ + struct rmi_sensor_driver *sensor = container_of(work, + struct rmi_sensor_driver, work); + + attn_notify(sensor); + + /* we only need to enable the irq if doing interrupts */ + if (!rmi_polling_required(sensor)) + enable_irq(sensor->rpd->irq); +} + +/* This is the timer function for polling - it simply has to schedule work + * and restart the timer. */ +static enum hrtimer_restart sensor_poll_timer_func(struct hrtimer *timer) +{ + struct rmi_sensor_driver *sensor = container_of(timer, + struct rmi_sensor_driver, timer); + + schedule_work(&sensor->work); + hrtimer_start(&sensor->timer, ktime_set(0, polltime), + HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} + +/* This is the probe function passed to the RMI4 subsystem that gives us a + * chance to recognize an RMI4 device. In this case, we're looking for + * Synaptics devices that have data sources - such as touch screens, buttons, + * etc. + * + * TODO: Well, it used to do this. I'm not sure it's required any more. + */ +static int probe(struct rmi_sensor_driver *sensor) +{ + struct rmi_phys_driver *rpd; + + rpd = sensor->rpd; + + if (!rpd) { + printk(KERN_ERR "%s: Invalid rmi physical driver - null ptr:" + "%p\n", __func__, rpd); + return 0; + } + + return 1; +} + +static void config(struct rmi_sensor_driver *sensor) +{ + /* For each data source we had detected print info and set up interrupts + or polling. */ + struct rmi_function_info *functionInfo; + struct rmi_phys_driver *rpd; + + rpd = sensor->rpd; /* get ptr to rmi_physical_driver from app */ + + list_for_each_entry(functionInfo, &sensor->functions, link) { + /* Get and print some info about the data sources... */ + struct rmi_functions *fn; + bool found = false; + /* check if function number matches - if so call that + config function */ + fn = rmi_find_function(functionInfo->functionNum); + if (fn) { + found = true; + + if (fn->config) { + fn->config(functionInfo); + } else { + /* the developer did not add in the + pointer to the config function into + rmi4_supported_data_src_functions */ + printk(KERN_ERR + "%s: no config function for " + "function 0x%x\n", + __func__, functionInfo->functionNum); + break; + } + } + + if (!found) { + /* if no support found for this RMI4 function + it means the developer did not add the + appropriate function pointer list into the + rmi4_supported_data_src_functions array and/or + did not bump up the number of supported RMI4 + functions in rmi.h as required */ + printk(KERN_ERR "%s: could not find support " + "for function 0x%x\n", + __func__, functionInfo->functionNum); + } + } + + /* This will handle interrupts on the ATTN line (interrupt driven) + * or will be called every poll interval (when we're not interrupt + * driven). + */ + INIT_WORK(&sensor->work, sensor_work_func); + sensor->workIsReady = true; + + if (rmi_polling_required(sensor)) { + /* We're polling driven, so set up the polling timer + and timer function. */ + hrtimer_init(&sensor->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + sensor->timer.function = sensor_poll_timer_func; + hrtimer_start(&sensor->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + } +} + +/** Just a stub for now. + */ +static int rmi_sensor_suspend(struct device *dev, pm_message_t state) +{ + printk(KERN_INFO "%s: sensor suspend called.", __func__); + return 0; +} + +/** Just a stub for now. + */ +static int rmi_sensor_resume(struct device *dev) +{ + printk(KERN_INFO "%s: sensor resume called.", __func__); + return 0; +} + +/* + * This method is called, whenever a new sensor device is added for the rmi + * bus. + * + * It will scan the devices PDT to determine the supported functions + * and create a new function device for each of these. It will read + * the query, control, command and data regsiters for the function + * to be used for each newly created function device. + * + * The sensor device is then bound to every function it supports. + * + */ +int rmi_sensor_register_functions(struct rmi_sensor_driver *sensor) +{ + struct rmi_function_device *function; + unsigned int interruptRegisterCount; + struct rmi_phys_driver *rpd; + int i; + unsigned char interruptCount; + struct rmi_function_info *functionInfo; + struct rmi_function_descriptor rmi_fd; + struct rmi_functions *fn; + int retval; + + pr_debug("%s: Registering sensor functions\n", __func__); + + retval = 0; + + /* Scan device for functions that may be supported */ + { + pr_debug("%s: Scanning sensor for Functions:\n", __func__); + + interruptCount = 0; + rpd = sensor->rpd; + + /* Read the Page Descriptor Table to determine what functions + * are present */ + + printk(KERN_DEBUG "%s: Scanning page descriptors.", __func__); + for (i = PDT_START_SCAN_LOCATION; + i >= PDT_END_SCAN_LOCATION; + i -= PDT_ENTRY_SIZE) { + printk(KERN_DEBUG "%s: Reading page descriptor 0x%02x", __func__, i); + retval = rpd->read_multiple(rpd, i, (char *)&rmi_fd, + sizeof(rmi_fd)); + if (!retval) { + functionInfo = NULL; + + if (rmi_fd.functionNum != 0x00 && rmi_fd.functionNum != 0xff) { + printk(KERN_DEBUG "%s: F%02x - queries %02x commands %02x control %02x data %02x ints %02x", __func__, rmi_fd.functionNum, rmi_fd.queryBaseAddr, rmi_fd.commandBaseAddr, rmi_fd.controlBaseAddr, rmi_fd.dataBaseAddr, rmi_fd.interruptSrcCnt); + + if ((rmi_fd.functionNum & 0xff) == 0x01) + printk(KERN_DEBUG "%s: Fn $01 Found - RMI Device Control", __func__); + + /* determine if the function is supported and if so + * then bind this function device to the sensor */ + if (rmi_fd.interruptSrcCnt) { + functionInfo = kzalloc(sizeof(*functionInfo), GFP_KERNEL); + if (!functionInfo) { + printk(KERN_ERR "%s: could not allocate memory for function 0x%x.", + __func__, rmi_fd.functionNum); + retval = -ENOMEM; + goto exit_fail; + } + functionInfo->sensor = sensor; + functionInfo->functionNum = (rmi_fd.functionNum & 0xff); + INIT_LIST_HEAD(&functionInfo->link); + /* Get the ptr to the detect function based on + * the function number */ + printk(KERN_DEBUG "%s: Checking for RMI function F%02x.", __func__, rmi_fd.functionNum); + fn = rmi_find_function(rmi_fd.functionNum); + if (fn) { + retval = fn->detect(functionInfo, &rmi_fd, + interruptCount); + if (retval) + printk(KERN_ERR "%s: Function detect for F%02x failed with %d.", + __func__, rmi_fd.functionNum, retval); + + /* Create a function device and function driver for this Fn */ + function = kzalloc(sizeof(*function), GFP_KERNEL); + if (!function) { + printk(KERN_ERR "%s: Error allocating memory for rmi_function_device.", __func__); + return -ENOMEM; + } + + function->dev.parent = &sensor->sensor_device->dev; + function->dev.bus = sensor->sensor_device->dev.bus; + function->rmi_funcs = fn; + function->sensor = sensor; + function->rfi = functionInfo; + functionInfo->function_device = function; + + /* Check if we have an interrupt mask of 0 and a non-NULL interrupt + handler function and print a debug message since we should never + have this. + */ + if (functionInfo->interruptMask == 0 && fn->inthandler != NULL) { + printk(KERN_DEBUG "%s: Can't have a zero interrupt mask for function F%02x (which requires an interrupt handler).\n", + __func__, rmi_fd.functionNum); + } + + + /* Check if we have a non-zero interrupt mask and a NULL interrupt + handler function and print a debug message since we should never + have this. + */ + if (functionInfo->interruptMask != 0 && fn->inthandler == NULL) { + printk(KERN_DEBUG "%s: Can't have a non-zero interrupt mask %d for function F%02x with a NULL inthandler fn.\n", + __func__, functionInfo->interruptMask, rmi_fd.functionNum); + } + + /* Register the rmi function device */ + retval = rmi_function_register_device(function, rmi_fd.functionNum); + if (retval) { + printk(KERN_ERR "%s: Failed rmi_function_register_device.\n", + __func__); + return retval; + } + } else { + printk(KERN_ERR "%s: could not find support for function 0x%02X.\n", + __func__, rmi_fd.functionNum); + } + } else { + printk(KERN_DEBUG "%s: Found function F%02x - Ignored.\n", __func__, rmi_fd.functionNum & 0xff); + } + + /* bump interrupt count for next iteration */ + /* NOTE: The value 7 is reserved - for now, only bump up one for an interrupt count of 7 */ + if ((rmi_fd.interruptSrcCnt & 0x7) == 0x7) { + interruptCount += 1; + } else { + interruptCount += + (rmi_fd.interruptSrcCnt & 0x7); + } + + /* link this function info to the RMI module infos list + of functions */ + if (functionInfo == NULL) { + printk(KERN_DEBUG "%s: WTF? functionInfo is null here.", __func__); + } else { + printk(KERN_DEBUG "%s: Adding function F%02x with %d sources.\n", + __func__, functionInfo->functionNum, functionInfo->numSources); + + mutex_lock(&rfi_mutex); + list_add_tail(&functionInfo->link, + &sensor->functions); + mutex_unlock(&rfi_mutex); + } + + } else { + /* A zero or 0xff in the function number + signals the end of the PDT */ + printk(KERN_DEBUG "%s: Found End of PDT\n", + __func__); + break; + } + } else { + /* failed to read next PDT entry - end PDT + scan - this may result in an incomplete set + of recognized functions - should probably + return an error but the driver may still be + viable for diagnostics and debugging so let's + let it continue. */ + printk(KERN_ERR "%s: Read Error %d when reading next PDT entry - " + "ending PDT scan.\n", + __func__, retval); + break; + } + } + printk(KERN_DEBUG "%s: Done scanning.", __func__); + + /* calculate the interrupt register count - used in the + ISR to read the correct number of interrupt registers */ + interruptRegisterCount = (interruptCount + 7) / 8; + sensor->interruptRegisterCount = interruptRegisterCount; /* TODO: Is this needed by the sensor anymore? */ + } + + return 0; + +exit_fail: + return retval; +} +EXPORT_SYMBOL(rmi_sensor_register_functions); + +int rmi_sensor_register_device(struct rmi_sensor_device *dev, int index) +{ + int status; + + printk(KERN_INFO "%s: Registering sensor device.\n", __func__); + + /* make name - sensor00, sensor01, etc. */ + dev_set_name(&dev->dev, "sensor%02d", index); + status = device_register(&dev->dev); + + return status; +} +EXPORT_SYMBOL(rmi_sensor_register_device); + +static void rmi_sensor_unregister_device(struct rmi_sensor_device *rmisensordev) +{ + printk(KERN_INFO "%s: Unregistering sensor device.\n", __func__); + + device_unregister(&rmisensordev->dev); +} +EXPORT_SYMBOL(rmi_sensor_unregister_device); + +int rmi_sensor_register_driver(struct rmi_sensor_driver *driver) +{ + static int index; + int ret; + char *drvrname; + + driver->workIsReady = false; + + printk(KERN_INFO "%s: Registering sensor driver.\n", __func__); + driver->dispatchIRQs = dispatchIRQs; + driver->attention = attention; + driver->config = config; + driver->probe = probe; + + /* assign the bus type for this driver to be rmi bus */ + driver->drv.bus = &rmi_bus_type; + driver->drv.suspend = rmi_sensor_suspend; + driver->drv.resume = rmi_sensor_resume; + /* Create a function device and function driver for this Fn */ + drvrname = kzalloc(sizeof(sensorname) + 4, GFP_KERNEL); + if (!drvrname) { + printk(KERN_ERR "%s: Error allocating memeory for rmi_sensor_driver name.\n", __func__); + return -ENOMEM; + } + sprintf(drvrname, "sensor%02d", index++); + + driver->drv.name = drvrname; + driver->module = driver->drv.owner; + + /* register the sensor driver */ + ret = driver_register(&driver->drv); + if (ret) { + printk(KERN_ERR "%s: Failed driver_register %d\n", + __func__, ret); + goto exit_fail; + } + + /* register the functions on the sensor */ + ret = rmi_sensor_register_functions(driver); + if (ret) { + printk(KERN_ERR "%s: Failed rmi_sensor_register_functions %d\n", + __func__, ret); + } + + /* configure the sensor - enable interrupts for each function, init work, set polling timer or adjust report rate, etc. */ + config(driver); + + printk(KERN_DEBUG "%s: sensor driver registration completed.", __func__); + +exit_fail: + return ret; +} +EXPORT_SYMBOL(rmi_sensor_register_driver); + +static void rmi_sensor_unregister_driver(struct rmi_sensor_driver *driver) +{ + printk(KERN_DEBUG "%s: Unregistering sensor driver.\n", __func__); + + /* Stop the polling timer if doing polling */ + if (rmi_polling_required(driver)) + hrtimer_cancel(&driver->timer); + + flush_scheduled_work(); /* Make sure all scheduled work is stopped */ + + driver_unregister(&driver->drv); +} +EXPORT_SYMBOL(rmi_sensor_unregister_driver); + + +static int __init rmi_sensor_init(void) +{ + printk(KERN_DEBUG "%s: RMI Sensor Init\n", __func__); + return 0; +} + +static void __exit rmi_sensor_exit(void) +{ + printk(KERN_DEBUG "%s: RMI Sensor Driver Exit\n", __func__); + flush_scheduled_work(); /* Make sure all scheduled work is stopped */ +} + + +module_init(rmi_sensor_init); +module_exit(rmi_sensor_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("RMI4 Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/synaptics/rmi_sensor.h b/drivers/input/touchscreen/synaptics/rmi_sensor.h new file mode 100644 index 0000000000000000000000000000000000000000..63d255538775da89744a9f3e5fd4d55ca279e716 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_sensor.h @@ -0,0 +1,143 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) - RMI Sensor Module Header. + * Copyright (C) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * + * This file is licensed under the GPL2 license. + * + *############################################################################ + * GPL + * + * 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 + +#ifndef _RMI_SENSOR_H +#define _RMI_SENSOR_H + +#include + +struct rmi_sensor_driver { + struct module *module; + struct device_driver drv; + struct rmi_sensor_device *sensor_device; + + /* Attention Function + * This function is called by the low level isr in the physical + * driver. It merely schedules work to be done. + */ + void (*attention)(struct rmi_phys_driver *physdrvr, int instance); + /* Probe Function + * This function is called to give the sensor driver layer an + * opportunity to claim an RMI device. The sensor layer cannot + * read RMI registers at this point since the rmi physical driver + * has not been bound to it yet. Defer that to the config + * function call which occurs immediately after a successful probe. + */ + int (*probe)(struct rmi_sensor_driver *sensor); + /* Config Function + * This function is called after a successful probe. It gives the + * sensor driver an opportunity to query and/or configure an RMI + * device before data starts flowing. + */ + void (*config)(struct rmi_sensor_driver *sensor); + + /* Functions can call this in order to dispatch IRQs. */ + void (*dispatchIRQs)(struct rmi_sensor_driver *sensor, + unsigned int irqStatus); + + /* Register Functions + * This function is called in the rmi bus + * driver to have the sensor driver scan for any supported + * functions on the sensor and add devices for each one. + */ + void (*rmi_sensor_register_functions)(struct rmi_sensor_driver + *sensor); + + unsigned int interruptRegisterCount; + + bool polling_required; + + /* pointer to the corresponding phys driver info for this sensor */ + /* The phys driver has the pointers to read, write, etc. */ + struct rmi_phys_driver *rpd; + + struct hrtimer timer; + struct work_struct work; + bool workIsReady; + + /* This list is for keeping around the list of sensors. + * Every time that a physical device is detected by the + * physical layer - be it i2c, spi, or some other - then + * we need to bind the physical layer to the device. When + * the Page Descriptor Table is scanned and when Function $01 + * is found then a new sensor device is created. The corresponding + * rmi_phys_driver struct pointer needs to be bound to the new + * sensor since Function $01 will be used to control and get + * interrupt information about the particular data source that is + * doing the interrupt. The rmi_phys_driver contains the pointers + * to the particular read, write, read_multiple, write_multiple + * functions for this device. This rmi_phys_driver struct will + * have to be up-bound to any drivers upstream that need it. + */ + + /* Standard kernel linked list implementation. + * Documentation on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head sensor_drivers; /* link sensor drivers into list */ + + struct list_head functions; /* List of rmi_function_infos */ + /* Per function initialization data. */ + struct rmi_functiondata_list *perfunctiondata; +}; + +/* macro to get the pointer to the device_driver struct from the sensor */ +#define to_rmi_sensor_driver(drv) container_of(drv, \ + struct rmi_sensor_driver, drv); + +struct rmi_sensor_device { + struct rmi_sensor_driver *driver; + struct device dev; + + /* Standard kernel linked list implementation. + * Documentation on how to use it can be found at + * http://isis.poly.edu/kulesh/stuff/src/klist/. + */ + struct list_head sensors; /* link sensors into list */ +}; + +int rmi_sensor_register_device(struct rmi_sensor_device *dev, int index); +int rmi_sensor_register_driver(struct rmi_sensor_driver *driver); +int rmi_sensor_register_functions(struct rmi_sensor_driver *sensor); +bool rmi_polling_required(struct rmi_sensor_driver *sensor); + +static inline void *rmi_sensor_get_functiondata(struct rmi_sensor_driver + *driver, unsigned char function_index) +{ + int i; + if (driver->perfunctiondata) { + for (i = 0; i < driver->perfunctiondata->count; i++) { + if (driver->perfunctiondata->functiondata[i]. + function_index == function_index) + return driver->perfunctiondata-> + functiondata[i].data; + } + } + return NULL; +} + +#endif diff --git a/drivers/input/touchscreen/synaptics/rmi_spi.c b/drivers/input/touchscreen/synaptics/rmi_spi.c new file mode 100644 index 0000000000000000000000000000000000000000..d6b247d9ce0d75e978c8aeb674c8cf45f03111a3 --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_spi.c @@ -0,0 +1,616 @@ +/** + * + * Synaptics Register Mapped Interface (RMI4) SPI Physical Layer Driver. + * Copyright (C) 2008-2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################ + * GPL + * + * 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 "rmi_spi.h" +#include "rmi_drvr.h" + +#define COMM_DEBUG 1 /* Set to 1 to dump transfers. */ + +/* 65 microseconds inter-byte delay between bytes for RMI chip*/ +#define RMI_DEFAULT_BYTE_DELAY_US 0 /* 65 */ +#define SPI_BUFFER_SIZE 32 + +static u8 *buf; + +/* This is the data kept on a per instance (client) basis. This data is + * always accessible by using the container_of() macro of the various elements + * inside. + */ +struct spi_device_instance_data { + int instance_no; + int irq; + unsigned int byte_delay_us; + struct rmi_phys_driver rpd; + struct spi_device *spidev; + struct rmi_spi_platformdata *platformdata; +}; + +static int spi_xfer(struct spi_device_instance_data *instance_data, + const u8 *txbuf, unsigned n_tx, u8 *rxbuf, unsigned n_rx) +{ + struct spi_device *spi = instance_data->spidev; +#if COMM_DEBUG + int i; +#endif + int status; + struct spi_message message; + struct spi_transfer *xfer_list; + u8 *local_buf; + int nXfers = 0; + int xfer_index = 0; + + if ((n_tx + n_rx) > SPI_BUFFER_SIZE) + return -EINVAL; + + if (n_tx) + nXfers += 1; + if (n_rx) { + if (instance_data->byte_delay_us) + nXfers += n_rx; + else + nXfers += 1; + } + + xfer_list = kcalloc(nXfers, sizeof(struct spi_transfer), GFP_KERNEL); + if (!xfer_list) + return -ENOMEM; + + /* ... unless someone else is using the pre-allocated buffer */ + local_buf = kzalloc(SPI_BUFFER_SIZE, GFP_KERNEL); + if (!local_buf) { + kfree(xfer_list); + return -ENOMEM; + } + + spi_message_init(&message); + + if (n_tx) { + memset(&xfer_list[0], 0, sizeof(struct spi_transfer)); + xfer_list[0].len = n_tx; + xfer_list[0].delay_usecs = instance_data->byte_delay_us; + spi_message_add_tail(&xfer_list[0], &message); + memcpy(local_buf, txbuf, n_tx); + xfer_list[0].tx_buf = local_buf; + xfer_index++; + } + if (n_rx) { + if (instance_data->byte_delay_us) { + int buffer_offset = n_tx; + for (; xfer_index < nXfers; xfer_index++) { + memset(&xfer_list[xfer_index], 0, + sizeof(struct spi_transfer)); + xfer_list[xfer_index].len = 1; + xfer_list[xfer_index].delay_usecs = + instance_data->byte_delay_us; + xfer_list[xfer_index].rx_buf = + local_buf + buffer_offset; + buffer_offset++; + spi_message_add_tail(&xfer_list[xfer_index], + &message); +#ifdef CONFIG_ARCH_OMAP + printk(KERN_INFO "%s: Did you compensate for + ARCH_OMAP?", __func__); +/* x[1].len = n_rx-1; */ /* since OMAP has one dummy byte. */ +#else +/* x[1].len = n_rx; */ +#endif + } + } else { + memset(&xfer_list[xfer_index], 0, sizeof(struct + spi_transfer)); +#ifdef CONFIG_ARCH_OMAP + /* since OMAP has one dummy byte. */ + xfer_list[xfer_index].len = n_rx-1; +#else + xfer_list[xfer_index].len = n_rx; +#endif + xfer_list[xfer_index].rx_buf = local_buf + n_tx; + spi_message_add_tail(&xfer_list[xfer_index], + &message); + xfer_index++; + } + } + printk(KERN_INFO "%s: Ready to go, xfer_index = %d, nXfers = %d.", + __func__, xfer_index, nXfers); +#if COMM_DEBUG + printk(KERN_INFO "%s: SPI transmits %d bytes...", __func__, n_tx); + for (i = 0; i < n_tx; i++) + printk(KERN_INFO " 0x%02X", local_buf[i]); +#endif + + /* do the i/o */ + status = spi_sync(spi, &message); + if (status == 0) { + memcpy(rxbuf, local_buf + n_tx, n_rx); + status = message.status; +#if COMM_DEBUG + if (n_rx) { + printk(KERN_INFO "%s: SPI received %d bytes...", + __func__, n_rx); + for (i = 0; i < n_rx; i++) + printk(KERN_INFO " 0x%02X", rxbuf[i]); + } +#endif + } else { + printk(KERN_ERR "%s: spi_sync failed with error code %d.", + __func__, status); + } + + kfree(local_buf); + kfree(xfer_list); + + return status; +} + +/** + * Read a single register through spi. + * \param[in] pd + * \param[in] address The address at which to start the data read. + * \param[out] valp Pointer to the buffer where the data will be stored. + * \return zero upon success (with the byte read in valp),non-zero upon error. + */ +static int +rmi_spi_read(struct rmi_phys_driver *pd, unsigned short address, char *valp) +{ + struct spi_device_instance_data *id = + container_of(pd, struct spi_device_instance_data, rpd); + + char rxbuf[2]; + int retval; + unsigned short addr = address; + + addr = ((addr & 0xff00) >> 8); + address = ((address & 0x00ff) << 8); + addr |= address; + addr |= 0x80; /* High bit set indicates read. */ + + retval = spi_xfer(id, (u8 *)&addr, 2, rxbuf, 1); + + *valp = rxbuf[0]; + + return retval; +} + +/** + * Same as rmi_spi_read, except that multiple bytes are allowed to be read. + * \param[in] pd + * \param[in] address The address at which to start the data read. + * \param[out] valp Pointer to the buffer where the data will be stored. This + * buffer must be at least size bytes long. + * \param[in] size The number of bytes to be read. + * \return zero upon success(with the byte read in valp), non-zero upon error. + */ +static int +rmi_spi_read_multiple(struct rmi_phys_driver *pd, unsigned short address, + char *valp, int size) +{ + struct spi_device_instance_data *id = + container_of(pd, struct spi_device_instance_data, rpd); + int retval; + + unsigned short addr = address; + + addr = ((addr & 0xff00) >> 8); + address = ((address & 0x00ff) << 8); + addr |= address; + addr |= 0x80; /* High bit set indicates read. */ + + retval = spi_xfer(id, (u8 *)&addr, 2, valp, size); + + return retval; +} + +/** + * Write a single register through spi. + * You can write multiple registers at once, but I made the functions for that + * seperate for performance reasons. Writing multiple requires allocation and + * freeing. + * \param[in] pd + * \param[in] address The address at which to start the write. + * \param[in] data The data to be written. + * \return one upon success, something else upon error. + */ +static int +rmi_spi_write(struct rmi_phys_driver *pd, unsigned short address, char data) +{ + struct spi_device_instance_data *id = + container_of(pd, struct spi_device_instance_data, rpd); + unsigned char txbuf[4]; + int retval; + + txbuf[2] = data; + txbuf[1] = address; + txbuf[0] = address>>8; + + retval = spi_xfer(id, txbuf, 3, NULL, 0); + return retval ? 0 : 1; +} + +/** + * Write multiple registers. + * \param[in] pd + * \param[in] address The address at which to start the write. + * \param[in] valp A pointer to a buffer containing the data to be written. + * \param[in] size The number of bytes to write. + * \return one upon success, something else upon error. + */ +static int +rmi_spi_write_multiple(struct rmi_phys_driver *pd, unsigned short address, + char *valp, int size) +{ + struct spi_device_instance_data *id = + container_of(pd, struct spi_device_instance_data, rpd); + unsigned char txbuf[32]; + int retval; + int i; + + txbuf[1] = address; + txbuf[0] = address>>8; + + for (i = 0; i < size; i++) + txbuf[i + 2] = valp[i]; + + retval = spi_xfer(id, txbuf, size+2, NULL, 0); + + return retval ? 0 : 1; +} + +/** + * This is the Interrupt Service Routine. + * It just notifies the physical device + * that attention is required. + */ +static irqreturn_t spi_attn_isr(int irq, void *info) +{ + struct spi_device_instance_data *instance_data = info; + disable_irq_nosync(instance_data->irq); + if (instance_data->rpd.attention) + instance_data->rpd.attention(&instance_data->rpd, + instance_data->instance_no); + return IRQ_HANDLED; +} + +/* TODO: Move this to rmi_bus, and call a function to get the next sensorID + */ +static int sensor_count; + +static int __devinit rmi_spi_probe(struct spi_device *spi) +{ + struct spi_device_instance_data *instance_data; + int retval; + struct rmi_spi_platformdata *platformdata; + struct rmi_sensordata *sensordata; + int irqtype = 0; + + printk(KERN_INFO "Probing RMI4 SPI device\n"); + + /* This should have already been set up in the board file, + shouldn't it? */ + spi->bits_per_word = 8; + + spi->mode = SPI_MODE_3; + + retval = spi_setup(spi); + if (retval < 0) { + printk(KERN_ERR "%s: spi_setup failed with %d.", __func__, + retval); + return retval; + } + + buf = kzalloc(SPI_BUFFER_SIZE, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "%s: Failed to allocate memory for spi + buffer.", __func__); + return -ENOMEM; + } + + instance_data = kzalloc(sizeof(*instance_data), GFP_KERNEL); + if (!instance_data) { + printk(KERN_ERR "%s: Failer to allocate memory for instance + data.", __func__); + return -ENOMEM; + } + + instance_data->byte_delay_us = RMI_DEFAULT_BYTE_DELAY_US; + instance_data->spidev = spi; + instance_data->rpd.name = RMI4_SPI_DRIVER_NAME; + instance_data->rpd.write = rmi_spi_write; + instance_data->rpd.read = rmi_spi_read; + instance_data->rpd.write_multiple = rmi_spi_write_multiple; + instance_data->rpd.read_multiple = rmi_spi_read_multiple; + instance_data->rpd.module = THIS_MODULE; + /* default to polling if irq not used */ + instance_data->rpd.polling_required = true; + + platformdata = spi->dev.platform_data; + if (platformdata == NULL) { + printk(KERN_ERR "%s: CONFIGURATION ERROR - platform data + is NULL.", __func__); + return -EINVAL; + } + + instance_data->platformdata = platformdata; + sensordata = platformdata->sensordata; + + /* Call the platform setup routine, to do any setup that is required + * before + * interacting with the device. + */ + if (sensordata && sensordata->rmi_sensor_setup) { + retval = sensordata->rmi_sensor_setup(); + if (retval) { + printk(KERN_ERR "%s: sensor setup failed with + code %d.", __func__, retval); + kfree(instance_data); + return retval; + } + } + + /* TODO: I think this if is no longer required. */ + if (platformdata->chip == RMI_SUPPORT) { + instance_data->instance_no = sensor_count; + sensor_count++; + + /* set the device name using the instance_no + * appended to DEVICE_NAME to make a unique name + */ + dev_set_name(&spi->dev, "%s%d", RMI4_SPI_DEVICE_NAME, + instance_data->instance_no); + /* + * Determine if we need to poll (inefficient) or + * use interrupts. + */ + if (platformdata->irq) { + switch (platformdata->irq_type) { + case IORESOURCE_IRQ_HIGHEDGE: + irqtype = IRQF_TRIGGER_RISING; + break; + case IORESOURCE_IRQ_LOWEDGE: + irqtype = IRQF_TRIGGER_FALLING; + break; + case IORESOURCE_IRQ_HIGHLEVEL: + irqtype = IRQF_TRIGGER_HIGH; + break; + case IORESOURCE_IRQ_LOWLEVEL: + irqtype = IRQF_TRIGGER_LOW; + break; + default: + dev_warn(&spi->dev, "%s: Invalid IRQ flags + in platform data.", __func__); + retval = -ENXIO; + goto error_exit; + } +/* + retval = request_irq(instance_data->irq, spi_attn_isr, + irqtype, "rmi_spi", instance_data); + if (retval) { + dev_info(&spi->dev, "%s: Unable to get attn + irq %d. Reverting to polling. ", __func__, + instance_data->irq); + instance_data->rpd.polling_required = true; + } else { + dev_dbg(&spi->dev, "%s: got irq", __func__); + instance_data->rpd.polling_required = false; + instance_data->rpd.irq = instance_data->irq; + } +*/ + instance_data->rpd.polling_required = false; + } else { + instance_data->rpd.polling_required = true; + dev_info(&spi->dev, "%s: No IRQ info given. + Polling required.", __func__); + } + } + + /* Store instance data for later access. */ + if (instance_data) + spi_set_drvdata(spi, instance_data); + + /* Register the sensor driver - + * which will trigger a scan of the PDT. + */ + retval = rmi_register_sensor(&instance_data->rpd, + platformdata->sensordata); + if (retval) { + printk(KERN_ERR "%s: sensor registration failed with code + %d.", __func__, retval); + goto error_exit; + } + + if (instance_data->rpd.polling_required == false) { + instance_data->irq = platformdata->irq; + retval = request_irq(platformdata->irq, spi_attn_isr, + irqtype, dev_name(&spi->dev), instance_data); + if (retval) { + dev_err(&spi->dev, "%s: failed to obtain IRQ %d. + Result: %d.", __func__, + platformdata->irq, retval); + dev_info(&spi->dev, "%s: Reverting to polling.\n", + __func__); + instance_data->rpd.polling_required = true; + instance_data->irq = 0; + /* TODO: Need to revert back to polling + * - create and start timer. + */ + } else { + dev_dbg(&spi->dev, "%s: got irq.\n", __func__); + instance_data->rpd.irq = instance_data->irq; + } + } + + printk(KERN_INFO "%s: Successfully Registered %s.", + __func__, instance_data->rpd.name); + + return 0; + +error_exit: + if (sensordata && sensordata->rmi_sensor_teardown) + sensordata->rmi_sensor_teardown(); + if (instance_data->irq) + free_irq(instance_data->irq, instance_data); + kfree(instance_data); + return retval; +} + +static int rmi_spi_suspend(struct spi_device *spi, pm_message_t message) +{ + printk(KERN_INFO "%s: Suspending...", __func__); + return 0; +} + +static int rmi_spi_resume(struct spi_device *spi) +{ + printk(KERN_INFO "%s: Resuming...", __func__); + return 0; +} + +static int __devexit rmi_spi_remove(struct spi_device *spi) +{ + struct spi_device_instance_data *id = spi_get_drvdata(spi); + + printk(KERN_INFO "%s: RMI SPI device removed.", __func__); + + rmi_spi_suspend(spi, PMSG_SUSPEND); + + rmi_unregister_sensors(&id->rpd); + + if (id) { + if (id->irq) + free_irq(id->irq, id); + kfree(id); + } + + return 0; +} + +static struct spi_driver rmi_spi_driver = { + .driver = { + .name = RMI4_SPI_DRIVER_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = rmi_spi_probe, + .remove = __devexit_p(rmi_spi_remove), + .suspend = rmi_spi_suspend, + .resume = rmi_spi_resume, +}; + +/** + * The Platform Driver probe function. We just tell the spi subsystem about + * ourselves in this call. + */ +static int +rmi_spi_plat_probe(struct platform_device *dev) +{ + struct rmi_spi_platformdata *platform_data = dev->dev.platform_data; + + printk(KERN_INFO "%s: Platform driver probe.", __func__); + + if (!platform_data) { + printk(KERN_ERR "A platform device must contain + rmi_spi_platformdata\n"); + return -ENXIO; + } + + return spi_register_driver(&rmi_spi_driver); +} + +/** + * Tell the spi subsystem that we're done. + * \param[in] dev + * \return Always returns 0. + */ +static int +rmi_spi_plat_remove(struct platform_device *dev) +{ + printk(KERN_INFO "%s: Platform driver removed.", __func__); + spi_unregister_driver(&rmi_spi_driver); + return 0; +} + +/** + * Structure used to tell the Platform Driver subsystem about us. + */ +static struct platform_driver rmi_spi_platform_driver = { + .driver = { + .name = RMI4_SPI_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rmi_spi_plat_probe, + .remove = __devexit_p(rmi_spi_plat_remove), +}; + +static int __init rmi_spi_init(void) +{ + int retval; + + printk(KERN_INFO "%s: RMI SPI physical layer initialization.", + __func__); + retval = spi_register_driver(&rmi_spi_driver); + if (retval < 0) { + printk(KERN_ERR "%s: Failed to register spi driver, code + = %d.", __func__, retval); + return retval; + } +/* +#else + retval = platform_driver_register(&rmi_spi_platform_driver); + if (retval < 0) { + printk(KERN_ERR "%s: Failed to register platform driver, + code = %d.", __func__, retval); + return retval; + } +#endif +*/ + printk(KERN_INFO "%s: result = %d", __func__, retval); + return retval; +} +module_init(rmi_spi_init); + +static void __exit rmi_spi_exit(void) +{ + printk(KERN_INFO "%s: RMI SPI physical layer exits.", __func__); + kfree(buf); + buf = NULL; + platform_driver_unregister(&rmi_spi_platform_driver); +} +module_exit(rmi_spi_exit); + +/** Standard driver module information - the author of the module. + */ +MODULE_AUTHOR("Synaptics, Inc."); +/** Standard driver module information - a summary description of this module. + */ +MODULE_DESCRIPTION("RMI4 Driver SPI Physical Layer"); +/** Standard driver module information - the license under which this module + * is included in the kernel. + */ +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/synaptics/rmi_spi.h b/drivers/input/touchscreen/synaptics/rmi_spi.h new file mode 100644 index 0000000000000000000000000000000000000000..daeebede2cce18d75acc1ebb26127dd5cda2ad6d --- /dev/null +++ b/drivers/input/touchscreen/synaptics/rmi_spi.h @@ -0,0 +1,57 @@ +/** + * + * Register Mapped Interface SPI Physical Layer Driver Header File. + * Copyright (C) 2008-2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * 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. + * + *############################################################################# + */ + +#if !defined(_RMI_SPI_H) +#define _RMI_SPI_H + +#include + +#define RMI_CHIP_VER_3 0 +#define RMI_CHIP_VER_4 1 + +#define RMI_SUPPORT (RMI_CHIP_VER_3|RMI_CHIP_VER_4) + +#define RMI4_SPI_DRIVER_NAME "rmi4_ts" +#define RMI4_SPI_DEVICE_NAME "rmi4_ts" + +/** Platform-specific configuration data. + * This structure is used by the platform-specific driver to designate + * specific information about the hardware. A platform client may supply + * an array of these to the rmi_phys_spi driver. + */ +struct rmi_spi_platformdata { + int chip; + + /* The number of the irq. Set to zero if polling is required. */ + int irq; + + /* The type of the irq (e.g., IRQF_TRIGGER_FALLING). Only valid if + * irq != 0 */ + int irq_type; + + /* Use this to specify platformdata that is not I2C specific. */ + struct rmi_sensordata *sensordata; +}; + +#endif diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index 1473d2382afd001f36907d5b96d655312dae4638..e59ca17a7cb819de62fa14611cf50cd87f81a181 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) @@ -66,6 +72,7 @@ struct ts_event { struct tsc2007 { struct input_dev *input; char phys[32]; + struct delayed_work work; struct i2c_client *client; @@ -74,14 +81,25 @@ struct tsc2007 { u16 max_rt; unsigned long poll_delay; unsigned long poll_period; + u16 min_x; + u16 max_x; + u16 min_y; + u16 max_y; + bool pendown; int irq; - wait_queue_head_t wait; - bool stopped; + 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) @@ -118,6 +136,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); } @@ -142,8 +172,25 @@ static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc) return rt; } -static bool tsc2007_is_pen_down(struct tsc2007 *ts) +static void tsc2007_send_up_event(struct tsc2007 *tsc) { + struct input_dev *input = tsc->input; + + dev_dbg(&tsc->client->dev, "UP\n"); + + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_sync(input); +} + +static void tsc2007_work(struct work_struct *work) +{ + struct tsc2007 *ts = + container_of(to_delayed_work(work), struct tsc2007, work); + bool debounced = false; + struct ts_event tc; + u32 rt; + /* * NOTE: We can't rely on the pressure to determine the pen down * state, even though this controller has a pressure sensor. @@ -154,82 +201,79 @@ static bool tsc2007_is_pen_down(struct tsc2007 *ts) * The only safe way to check for the pen up condition is in the * work function by reading the pen signal state (it's a GPIO * and IRQ). Unfortunately such callback is not always available, - * in that case we assume that the pen is down and expect caller - * to fall back on the pressure reading. + * in that case we have rely on the pressure anyway. */ + if (ts->get_pendown_state) { + if (unlikely(!ts->get_pendown_state())) { + tsc2007_send_up_event(ts); + ts->pendown = false; + goto out; + } - if (!ts->get_pendown_state) - return true; - - return ts->get_pendown_state(); -} - -static irqreturn_t tsc2007_soft_irq(int irq, void *handle) -{ - struct tsc2007 *ts = handle; - struct input_dev *input = ts->input; - struct ts_event tc; - u32 rt; + dev_dbg(&ts->client->dev, "pen is still down\n"); + } - while (!ts->stopped && tsc2007_is_pen_down(ts)) { + tsc2007_read_values(ts, &tc); - /* pen is down, continue with the measurement */ - tsc2007_read_values(ts, &tc); + rt = tsc2007_calculate_pressure(ts, &tc); + if (rt > ts->max_rt) { + /* + * Sample found inconsistent by debouncing or pressure is + * beyond the maximum. Don't report it to user space, + * repeat at least once more the measurement. + */ + dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt); + debounced = true; + goto out; - rt = tsc2007_calculate_pressure(ts, &tc); + } - if (rt == 0 && !ts->get_pendown_state) { - /* - * If pressure reported is 0 and we don't have - * callback to check pendown state, we have to - * assume that pen was lifted up. - */ - break; - } + if (rt) { + struct input_dev *input = ts->input; - if (rt <= ts->max_rt) { - dev_dbg(&ts->client->dev, - "DOWN point(%4d,%4d), pressure (%4u)\n", - tc.x, tc.y, rt); + if (!ts->pendown) { + dev_dbg(&ts->client->dev, "DOWN\n"); input_report_key(input, BTN_TOUCH, 1); - input_report_abs(input, ABS_X, tc.x); - input_report_abs(input, ABS_Y, tc.y); - input_report_abs(input, ABS_PRESSURE, rt); - - input_sync(input); - - } else { - /* - * Sample found inconsistent by debouncing or pressure is - * beyond the maximum. Don't report it to user space, - * repeat at least once more the measurement. - */ - dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt); + ts->pendown = true; } - wait_event_timeout(ts->wait, ts->stopped, - msecs_to_jiffies(ts->poll_period)); - } + input_report_abs(input, ABS_X, tc.x); + input_report_abs(input, ABS_Y, tc.y); + input_report_abs(input, ABS_PRESSURE, rt); - dev_dbg(&ts->client->dev, "UP\n"); + input_sync(input); - input_report_key(input, BTN_TOUCH, 0); - input_report_abs(input, ABS_PRESSURE, 0); - input_sync(input); + dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n", + tc.x, tc.y, rt); - if (ts->clear_penirq) - ts->clear_penirq(); + } else if (!ts->get_pendown_state && ts->pendown) { + /* + * We don't have callback to check pendown state, so we + * have to assume that since pressure reported is 0 the + * pen was lifted up. + */ + tsc2007_send_up_event(ts); + ts->pendown = false; + } - return IRQ_HANDLED; + out: + if (ts->pendown || debounced) + schedule_delayed_work(&ts->work, + msecs_to_jiffies(ts->poll_period)); + else + enable_irq(ts->irq); } -static irqreturn_t tsc2007_hard_irq(int irq, void *handle) +static irqreturn_t tsc2007_irq(int irq, void *handle) { struct tsc2007 *ts = handle; - if (!ts->get_pendown_state || likely(ts->get_pendown_state())) - return IRQ_WAKE_THREAD; + if (!ts->get_pendown_state || likely(ts->get_pendown_state())) { + disable_irq_nosync(ts->irq); + schedule_delayed_work(&ts->work, + msecs_to_jiffies(ts->poll_delay)); + } if (ts->clear_penirq) ts->clear_penirq(); @@ -237,41 +281,84 @@ static irqreturn_t tsc2007_hard_irq(int irq, void *handle) return IRQ_HANDLED; } -static void tsc2007_stop(struct tsc2007 *ts) +static void tsc2007_free_irq(struct tsc2007 *ts) { - ts->stopped = true; - mb(); - wake_up(&ts->wait); - - disable_irq(ts->irq); + free_irq(ts->irq, ts); + if (cancel_delayed_work_sync(&ts->work)) { + /* + * Work was pending, therefore we need to enable + * IRQ here to balance the disable_irq() done in the + * interrupt handler. + */ + enable_irq(ts->irq); + } } -static int tsc2007_open(struct input_dev *input_dev) +#ifdef CONFIG_PM +static int tsc2007_suspend(struct device *dev) { - struct tsc2007 *ts = input_get_drvdata(input_dev); - int err; + int rc; + struct tsc2007 *ts = dev_get_drvdata(dev); - ts->stopped = false; - mb(); + disable_irq(ts->irq); - enable_irq(ts->irq); + if (cancel_delayed_work_sync(&ts->work)) + enable_irq(ts->irq); - /* Prepare for touch readings - power down ADC and enable PENIRQ */ - err = tsc2007_xfer(ts, PWRDOWN); - if (err < 0) { - tsc2007_stop(ts); - return err; + 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 void tsc2007_close(struct input_dev *input_dev) +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 = input_get_drvdata(input_dev); + struct tsc2007 *ts = container_of(h, struct tsc2007, early_suspend); - tsc2007_stop(ts); + 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) @@ -300,7 +387,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client, ts->client = client; ts->irq = client->irq; ts->input = input_dev; - init_waitqueue_head(&ts->wait); + INIT_DELAYED_WORK(&ts->work, tsc2007_work); ts->model = pdata->model; ts->x_plate_ohms = pdata->x_plate_ohms; @@ -309,12 +396,15 @@ 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; - - if (pdata->x_plate_ohms == 0) { - dev_err(&client->dev, "x_plate_ohms is not set up in platform data"); - err = -EINVAL; - goto err_free_mem; - } + 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->min_x = pdata->min_x ? pdata->min_x : 0; + ts->max_x = pdata->max_x ? pdata->max_x : MAX_12BIT; + ts->min_y = pdata->min_y ? pdata->min_y : 0; + ts->max_y = pdata->max_y ? pdata->max_y : MAX_12BIT; + ts->power_shutdown = pdata->power_shutdown; snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&client->dev)); @@ -323,41 +413,49 @@ static int __devinit tsc2007_probe(struct i2c_client *client, input_dev->phys = ts->phys; input_dev->id.bustype = BUS_I2C; - input_dev->open = tsc2007_open; - input_dev->close = tsc2007_close; - - input_set_drvdata(input_dev, ts); - input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); - input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, pdata->fuzzx, 0); - input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, pdata->fuzzy, 0); + input_set_abs_params(input_dev, ABS_X, ts->min_x, + ts->max_x, pdata->fuzzx, 0); + input_set_abs_params(input_dev, ABS_Y, ts->min_y, + ts->max_y, pdata->fuzzy, 0); input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, pdata->fuzzz, 0); if (pdata->init_platform_hw) pdata->init_platform_hw(); - err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq, - IRQF_ONESHOT, client->dev.driver->name, ts); + 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); goto err_free_mem; } - tsc2007_stop(ts); + /* Prepare for touch readings - power down ADC and enable PENIRQ */ + err = tsc2007_xfer(ts, PWRDOWN); + if (err < 0) + goto err_free_irq; err = input_register_device(input_dev); 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; err_free_irq: - free_irq(ts->irq, ts); + tsc2007_free_irq(ts); if (pdata->exit_platform_hw) pdata->exit_platform_hw(); err_free_mem: @@ -371,11 +469,14 @@ static int __devexit tsc2007_remove(struct i2c_client *client) struct tsc2007 *ts = i2c_get_clientdata(client); struct tsc2007_platform_data *pdata = client->dev.platform_data; - free_irq(ts->irq, ts); + tsc2007_free_irq(ts); 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); @@ -392,7 +493,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/iommu/Kconfig b/drivers/iommu/Kconfig index 3bd9fff5c589301113e5e0501662b211b42e2088..84a69bfdd31edc8315eeaf660f9bd3be7428d35e 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -16,7 +16,7 @@ if IOMMU_SUPPORT # MSM IOMMU support config MSM_IOMMU bool "MSM IOMMU Support" - depends on ARCH_MSM8X60 || ARCH_MSM8960 + depends on ARCH_MSM8X60 || ARCH_MSM8960 || ARCH_APQ8064 || ARCH_MSMCOPPER select IOMMU_API help Support for the IOMMUs found on certain Qualcomm SOCs. @@ -26,8 +26,14 @@ config MSM_IOMMU If unsure, say N here. config IOMMU_PGTABLES_L2 - def_bool y + bool "Allow SMMU page tables in the L2 cache (Experimental)" depends on MSM_IOMMU && MMU && SMP && CPU_DCACHE_DISABLE=n + 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. # AMD IOMMU support config AMD_IOMMU diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 7ad7a3bc1242f9533072fe6d3a52209e6442e15a..066bc3e59b667c8b8f3e2d114db9643a3939e89f 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -1,5 +1,8 @@ obj-$(CONFIG_IOMMU_API) += iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o +ifdef CONFIG_OF +obj-$(CONFIG_MSM_IOMMU) += msm_iommu-v2.o msm_iommu_dev-v2.o msm_iommu_pagetable.o +endif obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_DMAR_TABLE) += dmar.o diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 2198b2dbbcd3ad964b03a13dd6fd8dd336f27bed..ef69d91516da2a14050145b946a6dcb0b04376ac 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -26,6 +26,7 @@ #include #include #include +#include static ssize_t show_iommu_group(struct device *dev, struct device_attribute *attr, char *buf) @@ -135,7 +136,7 @@ void iommu_set_fault_handler(struct iommu_domain *domain, } EXPORT_SYMBOL_GPL(iommu_set_fault_handler); -struct iommu_domain *iommu_domain_alloc(struct bus_type *bus) +struct iommu_domain *iommu_domain_alloc(struct bus_type *bus, int flags) { struct iommu_domain *domain; int ret; @@ -149,7 +150,7 @@ struct iommu_domain *iommu_domain_alloc(struct bus_type *bus) domain->ops = bus->iommu_ops; - ret = domain->ops->domain_init(domain); + ret = domain->ops->domain_init(domain, flags); if (ret) goto out_free; @@ -333,6 +334,39 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size) } EXPORT_SYMBOL_GPL(iommu_unmap); +int iommu_map_range(struct iommu_domain *domain, unsigned int iova, + struct scatterlist *sg, unsigned int len, int prot) +{ + if (unlikely(domain->ops->map_range == NULL)) + return -ENODEV; + + BUG_ON(iova & (~PAGE_MASK)); + + return domain->ops->map_range(domain, iova, sg, len, prot); +} +EXPORT_SYMBOL_GPL(iommu_map_range); + +int iommu_unmap_range(struct iommu_domain *domain, unsigned int iova, + unsigned int len) +{ + if (unlikely(domain->ops->unmap_range == NULL)) + return -ENODEV; + + BUG_ON(iova & (~PAGE_MASK)); + + return domain->ops->unmap_range(domain, iova, len); +} +EXPORT_SYMBOL_GPL(iommu_unmap_range); + +phys_addr_t iommu_get_pt_base_addr(struct iommu_domain *domain) +{ + if (unlikely(domain->ops->get_pt_base_addr == NULL)) + return 0; + + return domain->ops->get_pt_base_addr(domain); +} +EXPORT_SYMBOL_GPL(iommu_get_pt_base_addr); + int iommu_device_group(struct device *dev, unsigned int *groupid) { if (iommu_present(dev->bus) && dev->bus->iommu_ops->device_group) diff --git a/drivers/iommu/msm_iommu-v2.c b/drivers/iommu/msm_iommu-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..10d0b6650ad50216c2e495b552f0c67c0e67925c --- /dev/null +++ b/drivers/iommu/msm_iommu-v2.c @@ -0,0 +1,575 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "msm_iommu_pagetable.h" + +/* bitmap of the page sizes currently supported */ +#define MSM_IOMMU_PGSIZES (SZ_4K | SZ_64K | SZ_1M | SZ_16M) + +static DEFINE_MUTEX(msm_iommu_lock); + +struct msm_priv { + struct iommu_pt pt; + struct list_head list_attached; +}; + +static int __enable_clocks(struct msm_iommu_drvdata *drvdata) +{ + int ret; + + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + goto fail; + + if (drvdata->clk) { + ret = clk_prepare_enable(drvdata->clk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + } +fail: + return ret; +} + +static void __disable_clocks(struct msm_iommu_drvdata *drvdata) +{ + if (drvdata->clk) + clk_disable_unprepare(drvdata->clk); + clk_disable_unprepare(drvdata->pclk); +} + +static int __flush_iotlb_va(struct iommu_domain *domain, unsigned int va) +{ + struct msm_priv *priv = domain->priv; + struct msm_iommu_drvdata *iommu_drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + int asid; + + list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { + BUG_ON(!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent); + + iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); + BUG_ON(!iommu_drvdata); + + asid = GET_CB_CONTEXTIDR_ASID(iommu_drvdata->base, + ctx_drvdata->num); + + SET_TLBIVA(iommu_drvdata->base, ctx_drvdata->num, + asid | (va & CB_TLBIVA_VA)); + mb(); + } + + return 0; +} + +static int __flush_iotlb(struct iommu_domain *domain) +{ + struct msm_priv *priv = domain->priv; + struct msm_iommu_drvdata *iommu_drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + int asid; + + list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { + BUG_ON(!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent); + + iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); + BUG_ON(!iommu_drvdata); + + asid = GET_CB_CONTEXTIDR_ASID(iommu_drvdata->base, + ctx_drvdata->num); + + SET_TLBIASID(iommu_drvdata->base, ctx_drvdata->num, asid); + mb(); + } + + return 0; +} + +static void __reset_context(void __iomem *base, int ctx) +{ + SET_ACTLR(base, ctx, 0); + SET_FAR(base, ctx, 0); + SET_FSRRESTORE(base, ctx, 0); + SET_NMRR(base, ctx, 0); + SET_PAR(base, ctx, 0); + SET_PRRR(base, ctx, 0); + SET_SCTLR(base, ctx, 0); + SET_TLBIALL(base, ctx, 0); + SET_TTBCR(base, ctx, 0); + SET_TTBR0(base, ctx, 0); + SET_TTBR1(base, ctx, 0); + mb(); +} + +static void __program_context(void __iomem *base, int ctx, int ncb, + phys_addr_t pgtable, int redirect) +{ + unsigned int prrr, nmrr; + unsigned int pn; + int i, j, found; + + __reset_context(base, ctx); + + pn = pgtable >> CB_TTBR0_ADDR_SHIFT; + SET_TTBCR(base, ctx, 0); + SET_CB_TTBR0_ADDR(base, ctx, pn); + + /* Enable context fault interrupt */ + SET_CB_SCTLR_CFIE(base, ctx, 1); + + /* Redirect all cacheable requests to L2 slave port. */ + SET_CB_ACTLR_BPRCISH(base, ctx, 1); + SET_CB_ACTLR_BPRCOSH(base, ctx, 1); + SET_CB_ACTLR_BPRCNSH(base, ctx, 1); + + /* Turn on TEX Remap */ + SET_CB_SCTLR_TRE(base, ctx, 1); + + /* Enable private ASID namespace */ + SET_CB_SCTLR_ASIDPNE(base, ctx, 1); + + /* Set TEX remap attributes */ + RCP15_PRRR(prrr); + RCP15_NMRR(nmrr); + SET_PRRR(base, ctx, prrr); + SET_NMRR(base, ctx, nmrr); + + /* Configure page tables as inner-cacheable and shareable to reduce + * the TLB miss penalty. + */ + if (redirect) { + SET_CB_TTBR0_S(base, ctx, 1); + SET_CB_TTBR0_NOS(base, ctx, 1); + SET_CB_TTBR0_IRGN1(base, ctx, 0); /* WB, WA */ + SET_CB_TTBR0_IRGN0(base, ctx, 1); + SET_CB_TTBR0_RGN(base, ctx, 1); /* WB, WA */ + } + + /* Find if this page table is used elsewhere, and re-use ASID */ + found = 0; + for (i = 0; i < ncb; i++) + if ((GET_CB_TTBR0_ADDR(base, i) == pn) && (i != ctx)) { + SET_CB_CONTEXTIDR_ASID(base, ctx, \ + GET_CB_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_CB_CONTEXTIDR_ASID(base, j) == i && + j != ctx) + found = 1; + } + + if (!found) { + SET_CB_CONTEXTIDR_ASID(base, ctx, i); + break; + } + } + BUG_ON(found); + } + + /* Enable the MMU */ + SET_CB_SCTLR_M(base, ctx, 1); + mb(); +} + +static int msm_iommu_domain_init(struct iommu_domain *domain, int flags) +{ + struct msm_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + goto fail_nomem; + +#ifdef CONFIG_IOMMU_PGTABLES_L2 + priv->pt.redirect = flags & MSM_IOMMU_DOMAIN_PT_CACHEABLE; +#endif + + INIT_LIST_HEAD(&priv->list_attached); + if (msm_iommu_pagetable_alloc(&priv->pt)) + goto fail_nomem; + + domain->priv = priv; + return 0; + +fail_nomem: + kfree(priv); + return -ENOMEM; +} + +static void msm_iommu_domain_destroy(struct iommu_domain *domain) +{ + struct msm_priv *priv; + + mutex_lock(&msm_iommu_lock); + priv = domain->priv; + domain->priv = NULL; + + if (priv) + msm_iommu_pagetable_free(&priv->pt); + + kfree(priv); + mutex_unlock(&msm_iommu_lock); +} + +static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) +{ + struct msm_priv *priv; + struct msm_iommu_drvdata *iommu_drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + struct msm_iommu_ctx_drvdata *tmp_drvdata; + int ret = 0; + + mutex_lock(&msm_iommu_lock); + + priv = domain->priv; + if (!priv || !dev) { + ret = -EINVAL; + goto fail; + } + + iommu_drvdata = dev_get_drvdata(dev->parent); + ctx_drvdata = dev_get_drvdata(dev); + if (!iommu_drvdata || !ctx_drvdata) { + ret = -EINVAL; + goto fail; + } + + if (!list_empty(&ctx_drvdata->attached_elm)) { + ret = -EBUSY; + goto fail; + } + + list_for_each_entry(tmp_drvdata, &priv->list_attached, attached_elm) + if (tmp_drvdata == ctx_drvdata) { + ret = -EBUSY; + goto fail; + } + + ret = __enable_clocks(iommu_drvdata); + if (ret) + goto fail; + + __program_context(iommu_drvdata->base, ctx_drvdata->num, + iommu_drvdata->ncb, __pa(priv->pt.fl_table), + priv->pt.redirect); + + __disable_clocks(iommu_drvdata); + list_add(&(ctx_drvdata->attached_elm), &priv->list_attached); + ctx_drvdata->attached_domain = domain; + +fail: + mutex_unlock(&msm_iommu_lock); + return ret; +} + +static void msm_iommu_detach_dev(struct iommu_domain *domain, + struct device *dev) +{ + struct msm_priv *priv; + struct msm_iommu_drvdata *iommu_drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + int ret; + + mutex_lock(&msm_iommu_lock); + priv = domain->priv; + if (!priv || !dev) + goto fail; + + iommu_drvdata = dev_get_drvdata(dev->parent); + ctx_drvdata = dev_get_drvdata(dev); + if (!iommu_drvdata || !ctx_drvdata || !ctx_drvdata->attached_domain) + goto fail; + + ret = __enable_clocks(iommu_drvdata); + if (ret) + goto fail; + + SET_TLBIASID(iommu_drvdata->base, ctx_drvdata->num, + GET_CB_CONTEXTIDR_ASID(iommu_drvdata->base, ctx_drvdata->num)); + + __reset_context(iommu_drvdata->base, ctx_drvdata->num); + __disable_clocks(iommu_drvdata); + list_del_init(&ctx_drvdata->attached_elm); + ctx_drvdata->attached_domain = NULL; + +fail: + mutex_unlock(&msm_iommu_lock); +} + +static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, + phys_addr_t pa, size_t len, int prot) +{ + struct msm_priv *priv; + int ret = 0; + + mutex_lock(&msm_iommu_lock); + + priv = domain->priv; + if (!priv) { + ret = -EINVAL; + goto fail; + } + + ret = msm_iommu_pagetable_map(&priv->pt, va, pa, len, prot); + if (ret) + goto fail; + + ret = __flush_iotlb_va(domain, va); +fail: + mutex_unlock(&msm_iommu_lock); + return ret; +} + +static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, + size_t len) +{ + struct msm_priv *priv; + int ret = -ENODEV; + + mutex_lock(&msm_iommu_lock); + + priv = domain->priv; + if (!priv) + goto fail; + + ret = msm_iommu_pagetable_unmap(&priv->pt, va, len); + if (ret < 0) + goto fail; + + ret = __flush_iotlb_va(domain, va); +fail: + mutex_unlock(&msm_iommu_lock); + + /* the IOMMU API requires us to return how many bytes were unmapped */ + len = ret ? 0 : len; + return len; +} + +static int msm_iommu_map_range(struct iommu_domain *domain, unsigned int va, + struct scatterlist *sg, unsigned int len, + int prot) +{ + int ret; + struct msm_priv *priv; + + mutex_lock(&msm_iommu_lock); + + priv = domain->priv; + if (!priv) { + ret = -EINVAL; + goto fail; + } + + ret = msm_iommu_pagetable_map_range(&priv->pt, va, sg, len, prot); + if (ret) + goto fail; + + __flush_iotlb(domain); +fail: + mutex_unlock(&msm_iommu_lock); + return ret; +} + + +static int msm_iommu_unmap_range(struct iommu_domain *domain, unsigned int va, + unsigned int len) +{ + struct msm_priv *priv; + + mutex_lock(&msm_iommu_lock); + + priv = domain->priv; + msm_iommu_pagetable_unmap_range(&priv->pt, va, len); + + __flush_iotlb(domain); + mutex_unlock(&msm_iommu_lock); + return 0; +} + +static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, + unsigned long va) +{ + struct msm_priv *priv; + struct msm_iommu_drvdata *iommu_drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + unsigned int par; + void __iomem *base; + phys_addr_t pa = 0; + int ctx; + + mutex_lock(&msm_iommu_lock); + + priv = domain->priv; + if (list_empty(&priv->list_attached)) + goto fail; + + ctx_drvdata = list_entry(priv->list_attached.next, + struct msm_iommu_ctx_drvdata, attached_elm); + iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); + + base = iommu_drvdata->base; + ctx = ctx_drvdata->num; + + SET_ATS1PR(base, ctx, va & CB_ATS1PR_ADDR); + mb(); + while (GET_CB_ATSR_ACTIVE(base, ctx)) + cpu_relax(); + + par = GET_PAR(base, ctx); + if (par & CB_PAR_F) { + pa = 0; + } else { + /* We are dealing with a supersection */ + if (par & CB_PAR_SS) + pa = (par & 0xFF000000) | (va & 0x00FFFFFF); + else /* Upper 20 bits from PAR, lower 12 from VA */ + pa = (par & 0xFFFFF000) | (va & 0x00000FFF); + } + +fail: + mutex_unlock(&msm_iommu_lock); + return pa; +} + +static int msm_iommu_domain_has_cap(struct iommu_domain *domain, + unsigned long cap) +{ + return 0; +} + +static void print_ctx_regs(void __iomem *base, int ctx, unsigned int fsr) +{ + pr_err("FAR = %08x PAR = %08x\n", + GET_FAR(base, ctx), GET_PAR(base, ctx)); + pr_err("FSR = %08x [%s%s%s%s%s%s%s%s%s]\n", fsr, + (fsr & 0x02) ? "TF " : "", + (fsr & 0x04) ? "AFF " : "", + (fsr & 0x08) ? "PF " : "", + (fsr & 0x10) ? "EF " : "", + (fsr & 0x20) ? "TLBMCF " : "", + (fsr & 0x40) ? "TLBLKF " : "", + (fsr & 0x80) ? "MHF " : "", + (fsr & 0x40000000) ? "SS " : "", + (fsr & 0x80000000) ? "MULTI " : ""); + + pr_err("FSYNR0 = %08x FSYNR1 = %08x\n", + GET_FSYNR0(base, ctx), GET_FSYNR1(base, ctx)); + pr_err("TTBR0 = %08x TTBR1 = %08x\n", + GET_TTBR0(base, ctx), GET_TTBR1(base, ctx)); + pr_err("SCTLR = %08x ACTLR = %08x\n", + GET_SCTLR(base, ctx), GET_ACTLR(base, ctx)); + pr_err("PRRR = %08x NMRR = %08x\n", + GET_PRRR(base, ctx), GET_NMRR(base, ctx)); +} + +irqreturn_t msm_iommu_fault_handler_v2(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct msm_iommu_drvdata *drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + unsigned int fsr; + int ret = IRQ_NONE; + + mutex_lock(&msm_iommu_lock); + + BUG_ON(!pdev); + + drvdata = dev_get_drvdata(pdev->dev.parent); + BUG_ON(!drvdata); + + ctx_drvdata = dev_get_drvdata(&pdev->dev); + BUG_ON(!ctx_drvdata); + + fsr = GET_FSR(drvdata->base, ctx_drvdata->num); + if (fsr) { + if (!ctx_drvdata->attached_domain) { + pr_err("Bad domain in interrupt handler\n"); + ret = -ENOSYS; + } else + ret = report_iommu_fault(ctx_drvdata->attached_domain, + &ctx_drvdata->pdev->dev, + GET_FAR(drvdata->base, ctx_drvdata->num), 0); + + if (ret == -ENOSYS) { + pr_err("Unexpected IOMMU page fault!\n"); + pr_err("name = %s\n", drvdata->name); + pr_err("context = %s (%d)\n", ctx_drvdata->name, + ctx_drvdata->num); + pr_err("Interesting registers:\n"); + print_ctx_regs(drvdata->base, ctx_drvdata->num, fsr); + } + + SET_FSR(drvdata->base, ctx_drvdata->num, fsr); + ret = IRQ_HANDLED; + } else + ret = IRQ_NONE; + + mutex_unlock(&msm_iommu_lock); + return ret; +} + +static phys_addr_t msm_iommu_get_pt_base_addr(struct iommu_domain *domain) +{ + struct msm_priv *priv = domain->priv; + return __pa(priv->pt.fl_table); +} + +static struct iommu_ops msm_iommu_ops = { + .domain_init = msm_iommu_domain_init, + .domain_destroy = msm_iommu_domain_destroy, + .attach_dev = msm_iommu_attach_dev, + .detach_dev = msm_iommu_detach_dev, + .map = msm_iommu_map, + .unmap = msm_iommu_unmap, + .map_range = msm_iommu_map_range, + .unmap_range = msm_iommu_unmap_range, + .iova_to_phys = msm_iommu_iova_to_phys, + .domain_has_cap = msm_iommu_domain_has_cap, + .get_pt_base_addr = msm_iommu_get_pt_base_addr, + .pgsize_bitmap = MSM_IOMMU_PGSIZES, +}; + +static int __init msm_iommu_init(void) +{ + msm_iommu_pagetable_init(); + bus_set_iommu(&platform_bus_type, &msm_iommu_ops); + return 0; +} + +subsys_initcall(msm_iommu_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM SMMU v2 Driver"); diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c index cee307e866060ca30aa2627eaaf3f0cb7789802e..e17e1f8f94a24fcfd225b409605d5f14d446c9c4 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -27,6 +22,7 @@ #include #include #include +#include #include #include @@ -42,15 +38,34 @@ __asm__ __volatile__ ( \ #define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0) #define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1) +/* Sharability attributes of MSM IOMMU mappings */ +#define MSM_IOMMU_ATTR_NON_SH 0x0 +#define MSM_IOMMU_ATTR_SH 0x4 + +/* Cacheability attributes of MSM IOMMU mappings */ +#define MSM_IOMMU_ATTR_NONCACHED 0x0 +#define MSM_IOMMU_ATTR_CACHED_WB_WA 0x1 +#define MSM_IOMMU_ATTR_CACHED_WB_NWA 0x2 +#define MSM_IOMMU_ATTR_CACHED_WT 0x3 + + +static inline void clean_pte(unsigned long *start, unsigned long *end, + int redirect) +{ + if (!redirect) + dmac_flush_range(start, end); +} + /* bitmap of the page sizes currently supported */ #define MSM_IOMMU_PGSIZES (SZ_4K | SZ_64K | SZ_1M | SZ_16M) static int msm_iommu_tex_class[4]; -DEFINE_SPINLOCK(msm_iommu_lock); +DEFINE_MUTEX(msm_iommu_lock); struct msm_priv { unsigned long *pgtable; + int redirect; struct list_head list_attached; }; @@ -58,14 +73,14 @@ static int __enable_clocks(struct msm_iommu_drvdata *drvdata) { int ret; - ret = clk_enable(drvdata->pclk); + ret = clk_prepare_enable(drvdata->pclk); if (ret) goto fail; if (drvdata->clk) { - ret = clk_enable(drvdata->clk); + ret = clk_prepare_enable(drvdata->clk); if (ret) - clk_disable(drvdata->pclk); + clk_disable_unprepare(drvdata->pclk); } fail: return ret; @@ -74,44 +89,67 @@ static int __enable_clocks(struct msm_iommu_drvdata *drvdata) static void __disable_clocks(struct msm_iommu_drvdata *drvdata) { if (drvdata->clk) - clk_disable(drvdata->clk); - clk_disable(drvdata->pclk); + clk_disable_unprepare(drvdata->clk); + clk_disable_unprepare(drvdata->pclk); } -static int __flush_iotlb(struct iommu_domain *domain) +static int __flush_iotlb_va(struct iommu_domain *domain, unsigned int va) { struct msm_priv *priv = domain->priv; struct msm_iommu_drvdata *iommu_drvdata; struct msm_iommu_ctx_drvdata *ctx_drvdata; int ret = 0; -#ifndef CONFIG_IOMMU_PGTABLES_L2 - unsigned long *fl_table = priv->pgtable; - int i; + int asid; - if (!list_empty(&priv->list_attached)) { - dmac_flush_range(fl_table, fl_table + SZ_16K); + list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { + if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent) + BUG(); - for (i = 0; i < NUM_FL_PTE; i++) - if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) { - void *sl_table = __va(fl_table[i] & - FL_BASE_MASK); - dmac_flush_range(sl_table, sl_table + SZ_4K); - } + iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); + if (!iommu_drvdata) + BUG(); + + ret = __enable_clocks(iommu_drvdata); + if (ret) + goto fail; + + asid = GET_CONTEXTIDR_ASID(iommu_drvdata->base, + ctx_drvdata->num); + + SET_TLBIVA(iommu_drvdata->base, ctx_drvdata->num, + asid | (va & TLBIVA_VA)); + mb(); + __disable_clocks(iommu_drvdata); } -#endif +fail: + return ret; +} + +static int __flush_iotlb(struct iommu_domain *domain) +{ + struct msm_priv *priv = domain->priv; + struct msm_iommu_drvdata *iommu_drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata; + int ret = 0; + int asid; list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent) BUG(); iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); - BUG_ON(!iommu_drvdata); + if (!iommu_drvdata) + BUG(); ret = __enable_clocks(iommu_drvdata); if (ret) goto fail; - SET_CTX_TLBIALL(iommu_drvdata->base, ctx_drvdata->num, 0); + asid = GET_CONTEXTIDR_ASID(iommu_drvdata->base, + ctx_drvdata->num); + + SET_TLBIASID(iommu_drvdata->base, ctx_drvdata->num, asid); + mb(); __disable_clocks(iommu_drvdata); } fail: @@ -134,17 +172,20 @@ static void __reset_context(void __iomem *base, int ctx) SET_BFBCR(base, ctx, 0); SET_PAR(base, ctx, 0); SET_FAR(base, ctx, 0); - SET_CTX_TLBIALL(base, ctx, 0); SET_TLBFLPTER(base, ctx, 0); SET_TLBSLPTER(base, ctx, 0); 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, int redirect, + int ttbr_split) { unsigned int prrr, nmrr; + int i, j, found; __reset_context(base, ctx); /* Set up HTW mode */ @@ -154,14 +195,10 @@ static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) /* V2P configuration: HTW for access */ SET_V2PCFG(base, ctx, 0x3); - SET_TTBCR(base, ctx, 0); - SET_TTBR0_PA(base, ctx, (pgtable >> 14)); - - /* Invalidate the TLB for this context */ - SET_CTX_TLBIALL(base, ctx, 0); - - /* Set interrupt number to "secure" interrupt */ - SET_IRPTNDX(base, ctx, 0); + SET_TTBCR(base, ctx, ttbr_split); + SET_TTBR0_PA(base, ctx, (pgtable >> TTBR0_PA_SHIFT)); + if (ttbr_split) + SET_TTBR1_PA(base, ctx, (pgtable >> TTBR1_PA_SHIFT)); /* Enable context fault interrupt */ SET_CFEIE(base, ctx, 1); @@ -186,31 +223,61 @@ static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) /* Turn on BFB prefetch */ SET_BFBDFE(base, ctx, 1); -#ifdef CONFIG_IOMMU_PGTABLES_L2 /* Configure page tables as inner-cacheable and shareable to reduce * the TLB miss penalty. */ - SET_TTBR0_SH(base, ctx, 1); - SET_TTBR1_SH(base, ctx, 1); + if (redirect) { + SET_TTBR0_SH(base, ctx, 1); + SET_TTBR1_SH(base, ctx, 1); - SET_TTBR0_NOS(base, ctx, 1); - SET_TTBR1_NOS(base, ctx, 1); + SET_TTBR0_NOS(base, ctx, 1); + SET_TTBR1_NOS(base, ctx, 1); - SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */ - SET_TTBR0_IRGNL(base, ctx, 1); + SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */ + SET_TTBR0_IRGNL(base, ctx, 1); - SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */ - SET_TTBR1_IRGNL(base, ctx, 1); + SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */ + SET_TTBR1_IRGNL(base, ctx, 1); - SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */ - SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */ -#endif + SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */ + SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */ + } + + /* 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) +static int msm_iommu_domain_init(struct iommu_domain *domain, int flags) { struct msm_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL); @@ -224,8 +291,15 @@ static int msm_iommu_domain_init(struct iommu_domain *domain) if (!priv->pgtable) goto fail_nomem; +#ifdef CONFIG_IOMMU_PGTABLES_L2 + priv->redirect = flags & MSM_IOMMU_DOMAIN_PT_CACHEABLE; +#endif + memset(priv->pgtable, 0, SZ_16K); domain->priv = priv; + + clean_pte(priv->pgtable, priv->pgtable + NUM_FL_PTE, priv->redirect); + return 0; fail_nomem: @@ -236,11 +310,10 @@ static int msm_iommu_domain_init(struct iommu_domain *domain) static void msm_iommu_domain_destroy(struct iommu_domain *domain) { struct msm_priv *priv; - unsigned long flags; unsigned long *fl_table; int i; - spin_lock_irqsave(&msm_iommu_lock, flags); + mutex_lock(&msm_iommu_lock); priv = domain->priv; domain->priv = NULL; @@ -257,7 +330,7 @@ static void msm_iommu_domain_destroy(struct iommu_domain *domain) } kfree(priv); - spin_unlock_irqrestore(&msm_iommu_lock, flags); + mutex_unlock(&msm_iommu_lock); } static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) @@ -268,9 +341,8 @@ static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) struct msm_iommu_ctx_drvdata *ctx_drvdata; struct msm_iommu_ctx_drvdata *tmp_drvdata; int ret = 0; - unsigned long flags; - spin_lock_irqsave(&msm_iommu_lock, flags); + mutex_lock(&msm_iommu_lock); priv = domain->priv; @@ -303,15 +375,16 @@ 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, - __pa(priv->pgtable)); + __program_context(iommu_drvdata->base, ctx_dev->num, iommu_drvdata->ncb, + __pa(priv->pgtable), priv->redirect, + iommu_drvdata->ttbr_split); __disable_clocks(iommu_drvdata); list_add(&(ctx_drvdata->attached_elm), &priv->list_attached); - ret = __flush_iotlb(domain); + ctx_drvdata->attached_domain = domain; fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); + mutex_unlock(&msm_iommu_lock); return ret; } @@ -322,10 +395,9 @@ static void msm_iommu_detach_dev(struct iommu_domain *domain, struct msm_iommu_ctx_dev *ctx_dev; struct msm_iommu_drvdata *iommu_drvdata; struct msm_iommu_ctx_drvdata *ctx_drvdata; - unsigned long flags; int ret; - spin_lock_irqsave(&msm_iommu_lock, flags); + mutex_lock(&msm_iommu_lock); priv = domain->priv; if (!priv || !dev) @@ -338,27 +410,67 @@ static void msm_iommu_detach_dev(struct iommu_domain *domain, if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) goto fail; - ret = __flush_iotlb(domain); - if (ret) - goto fail; - ret = __enable_clocks(iommu_drvdata); if (ret) goto fail; + SET_TLBIASID(iommu_drvdata->base, ctx_dev->num, + GET_CONTEXTIDR_ASID(iommu_drvdata->base, ctx_dev->num)); + __reset_context(iommu_drvdata->base, ctx_dev->num); __disable_clocks(iommu_drvdata); list_del_init(&ctx_drvdata->attached_elm); - + ctx_drvdata->attached_domain = NULL; fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); + mutex_unlock(&msm_iommu_lock); +} + +static int __get_pgprot(int prot, int len) +{ + unsigned int pgprot; + int tex; + + if (!(prot & (IOMMU_READ | IOMMU_WRITE))) { + prot |= IOMMU_READ | IOMMU_WRITE; + WARN_ONCE(1, "No attributes in iommu mapping; assuming RW\n"); + } + + if ((prot & IOMMU_WRITE) && !(prot & IOMMU_READ)) { + prot |= IOMMU_READ; + WARN_ONCE(1, "Write-only iommu mappings unsupported; falling back to RW\n"); + } + + if (prot & IOMMU_CACHE) + tex = (pgprot_kernel >> 2) & 0x07; + else + tex = msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED]; + + if (tex < 0 || tex > NUM_TEX_CLASS - 1) + return 0; + + if (len == SZ_16M || len == SZ_1M) { + pgprot = FL_SHARED; + pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0; + pgprot |= tex & 0x02 ? FL_CACHEABLE : 0; + pgprot |= tex & 0x04 ? FL_TEX0 : 0; + pgprot |= FL_AP0 | FL_AP1; + pgprot |= prot & IOMMU_WRITE ? 0 : FL_AP2; + } else { + pgprot = SL_SHARED; + pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0; + pgprot |= tex & 0x02 ? SL_CACHEABLE : 0; + pgprot |= tex & 0x04 ? SL_TEX0 : 0; + pgprot |= SL_AP0 | SL_AP1; + pgprot |= prot & IOMMU_WRITE ? 0 : SL_AP2; + } + + return pgprot; } static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, phys_addr_t pa, size_t len, int prot) { struct msm_priv *priv; - unsigned long flags; unsigned long *fl_table; unsigned long *fl_pte; unsigned long fl_offset; @@ -366,17 +478,9 @@ static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, unsigned long *sl_pte; unsigned long sl_offset; unsigned int pgprot; - int ret = 0, tex, sh; - - spin_lock_irqsave(&msm_iommu_lock, flags); - - sh = (prot & MSM_IOMMU_ATTR_SH) ? 1 : 0; - tex = msm_iommu_tex_class[prot & MSM_IOMMU_CP_MASK]; + int ret = 0; - if (tex < 0 || tex > NUM_TEX_CLASS - 1) { - ret = -EINVAL; - goto fail; - } + mutex_lock(&msm_iommu_lock); priv = domain->priv; if (!priv) { @@ -399,16 +503,11 @@ static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, goto fail; } - if (len == SZ_16M || len == SZ_1M) { - pgprot = sh ? FL_SHARED : 0; - pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0; - pgprot |= tex & 0x02 ? FL_CACHEABLE : 0; - pgprot |= tex & 0x04 ? FL_TEX0 : 0; - } else { - pgprot = sh ? SL_SHARED : 0; - pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0; - pgprot |= tex & 0x02 ? SL_CACHEABLE : 0; - pgprot |= tex & 0x04 ? SL_TEX0 : 0; + pgprot = __get_pgprot(prot, len); + + if (!pgprot) { + ret = -EINVAL; + goto fail; } fl_offset = FL_OFFSET(va); /* Upper 12 bits */ @@ -416,52 +515,92 @@ 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++) - *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION | - FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT | - FL_SHARED | FL_NG | pgprot; + if (*(fl_pte+i)) { + ret = -EBUSY; + goto fail; + } + + for (i = 0; i < 16; i++) + *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION + | FL_TYPE_SECT | FL_SHARED | FL_NG | pgprot; + clean_pte(fl_pte, fl_pte + 16, priv->redirect); } - if (len == SZ_1M) - *fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG | - FL_TYPE_SECT | FL_SHARED | pgprot; + if (len == SZ_1M) { + if (*fl_pte) { + ret = -EBUSY; + goto fail; + } + + *fl_pte = (pa & 0xFFF00000) | FL_NG | FL_TYPE_SECT | FL_SHARED + | pgprot; + clean_pte(fl_pte, fl_pte + 1, priv->redirect); + } /* 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_KERNEL, 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); + clean_pte(sl, sl + NUM_SL_PTE, priv->redirect); + + *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | \ + FL_TYPE_TABLE); + + clean_pte(fl_pte, fl_pte + 1, priv->redirect); } - 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; + *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_NG | SL_SHARED + | SL_TYPE_SMALL | pgprot; + clean_pte(sl_pte, sl_pte + 1, priv->redirect); + } if (len == SZ_64K) { int i; 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; + if (*(sl_pte+i)) { + ret = -EBUSY; + goto fail; + } + + for (i = 0; i < 16; i++) + *(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_NG + | SL_SHARED | SL_TYPE_LARGE | pgprot; + + clean_pte(sl_pte, sl_pte + 16, priv->redirect); } - ret = __flush_iotlb(domain); + ret = __flush_iotlb_va(domain, va); fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); + mutex_unlock(&msm_iommu_lock); return ret; } @@ -469,7 +608,6 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, size_t len) { struct msm_priv *priv; - unsigned long flags; unsigned long *fl_table; unsigned long *fl_pte; unsigned long fl_offset; @@ -478,7 +616,7 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, unsigned long sl_offset; int i, ret = 0; - spin_lock_irqsave(&msm_iommu_lock, flags); + mutex_lock(&msm_iommu_lock); priv = domain->priv; @@ -507,13 +645,19 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, } /* Unmap supersection */ - if (len == SZ_16M) + if (len == SZ_16M) { for (i = 0; i < 16; i++) *(fl_pte+i) = 0; - if (len == SZ_1M) + clean_pte(fl_pte, fl_pte + 16, priv->redirect); + } + + if (len == SZ_1M) { *fl_pte = 0; + clean_pte(fl_pte, fl_pte + 1, priv->redirect); + } + sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); sl_offset = SL_OFFSET(va); sl_pte = sl_table + sl_offset; @@ -521,11 +665,16 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, if (len == SZ_64K) { for (i = 0; i < 16; i++) *(sl_pte+i) = 0; + + clean_pte(sl_pte, sl_pte + 16, priv->redirect); } - if (len == SZ_4K) + if (len == SZ_4K) { *sl_pte = 0; + clean_pte(sl_pte, sl_pte + 1, priv->redirect); + } + if (len == SZ_4K || len == SZ_64K) { int used = 0; @@ -535,19 +684,211 @@ static size_t msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, if (!used) { free_page((unsigned long)sl_table); *fl_pte = 0; + + clean_pte(fl_pte, fl_pte + 1, priv->redirect); } } - ret = __flush_iotlb(domain); + ret = __flush_iotlb_va(domain, va); fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); + mutex_unlock(&msm_iommu_lock); /* the IOMMU API requires us to return how many bytes were unmapped */ len = ret ? 0 : len; return len; } +static unsigned int get_phys_addr(struct scatterlist *sg) +{ + /* + * Try sg_dma_address first so that we can + * map carveout regions that do not have a + * struct page associated with them. + */ + unsigned int pa = sg_dma_address(sg); + if (pa == 0) + pa = sg_phys(sg); + return pa; +} + +static int msm_iommu_map_range(struct iommu_domain *domain, unsigned int va, + struct scatterlist *sg, unsigned int len, + int prot) +{ + unsigned int pa; + unsigned int offset = 0; + unsigned int pgprot; + unsigned long *fl_table; + unsigned long *fl_pte; + unsigned long fl_offset; + unsigned long *sl_table; + unsigned long sl_offset, sl_start; + unsigned int chunk_offset = 0; + unsigned int chunk_pa; + int ret = 0; + struct msm_priv *priv; + + mutex_lock(&msm_iommu_lock); + + BUG_ON(len & (SZ_4K - 1)); + + priv = domain->priv; + fl_table = priv->pgtable; + + pgprot = __get_pgprot(prot, SZ_4K); + + if (!pgprot) { + ret = -EINVAL; + goto fail; + } + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */ + + sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); + sl_offset = SL_OFFSET(va); + + chunk_pa = get_phys_addr(sg); + if (chunk_pa == 0) { + pr_debug("No dma address for sg %p\n", sg); + ret = -EINVAL; + goto fail; + } + + while (offset < len) { + /* Set up a 2nd level page table if one doesn't exist */ + if (*fl_pte == 0) { + sl_table = (unsigned long *) + __get_free_pages(GFP_KERNEL, get_order(SZ_4K)); + + if (!sl_table) { + pr_debug("Could not allocate second level table\n"); + ret = -ENOMEM; + goto fail; + } + + memset(sl_table, 0, SZ_4K); + clean_pte(sl_table, sl_table + NUM_SL_PTE, + priv->redirect); + + *fl_pte = ((((int)__pa(sl_table)) & FL_BASE_MASK) | + FL_TYPE_TABLE); + clean_pte(fl_pte, fl_pte + 1, priv->redirect); + } else + sl_table = (unsigned long *) + __va(((*fl_pte) & FL_BASE_MASK)); + + /* Keep track of initial position so we + * don't clean more than we have to + */ + sl_start = sl_offset; + + /* Build the 2nd level page table */ + while (offset < len && sl_offset < NUM_SL_PTE) { + pa = chunk_pa + chunk_offset; + sl_table[sl_offset] = (pa & SL_BASE_MASK_SMALL) | + pgprot | SL_NG | SL_SHARED | SL_TYPE_SMALL; + sl_offset++; + offset += SZ_4K; + + chunk_offset += SZ_4K; + + if (chunk_offset >= sg->length && offset < len) { + chunk_offset = 0; + sg = sg_next(sg); + chunk_pa = get_phys_addr(sg); + if (chunk_pa == 0) { + pr_debug("No dma address for sg %p\n", + sg); + ret = -EINVAL; + goto fail; + } + } + } + + clean_pte(sl_table + sl_start, sl_table + sl_offset, + priv->redirect); + + fl_pte++; + sl_offset = 0; + } + __flush_iotlb(domain); +fail: + mutex_unlock(&msm_iommu_lock); + return ret; +} + + +static int msm_iommu_unmap_range(struct iommu_domain *domain, unsigned int va, + unsigned int len) +{ + unsigned int offset = 0; + unsigned long *fl_table; + unsigned long *fl_pte; + unsigned long fl_offset; + unsigned long *sl_table; + unsigned long sl_start, sl_end; + int used, i; + struct msm_priv *priv; + + mutex_lock(&msm_iommu_lock); + + BUG_ON(len & (SZ_4K - 1)); + + priv = domain->priv; + fl_table = priv->pgtable; + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */ + + sl_start = SL_OFFSET(va); + + while (offset < len) { + sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); + sl_end = ((len - offset) / SZ_4K) + sl_start; + + if (sl_end > NUM_SL_PTE) + sl_end = NUM_SL_PTE; + + memset(sl_table + sl_start, 0, (sl_end - sl_start) * 4); + clean_pte(sl_table + sl_start, sl_table + sl_end, + priv->redirect); + + offset += (sl_end - sl_start) * SZ_4K; + + /* Unmap and free the 2nd level table if all mappings in it + * were removed. This saves memory, but the table will need + * to be re-allocated the next time someone tries to map these + * VAs. + */ + used = 0; + + /* If we just unmapped the whole table, don't bother + * seeing if there are still used entries left. + */ + if (sl_end - sl_start != NUM_SL_PTE) + for (i = 0; i < NUM_SL_PTE; i++) + if (sl_table[i]) { + used = 1; + break; + } + if (!used) { + free_page((unsigned long)sl_table); + *fl_pte = 0; + + clean_pte(fl_pte, fl_pte + 1, priv->redirect); + } + + sl_start = 0; + fl_pte++; + } + + __flush_iotlb(domain); + mutex_unlock(&msm_iommu_lock); + return 0; +} + static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, unsigned long va) { @@ -555,12 +896,11 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, struct msm_iommu_drvdata *iommu_drvdata; struct msm_iommu_ctx_drvdata *ctx_drvdata; unsigned int par; - unsigned long flags; void __iomem *base; phys_addr_t ret = 0; int ctx; - spin_lock_irqsave(&msm_iommu_lock, flags); + mutex_lock(&msm_iommu_lock); priv = domain->priv; if (list_empty(&priv->list_attached)) @@ -577,10 +917,9 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, if (ret) goto fail; - /* Invalidate context TLB */ - SET_CTX_TLBIALL(base, ctx, 0); SET_V2PPR(base, ctx, va & V2Pxx_VA); + mb(); par = GET_PAR(base, ctx); /* We are dealing with a supersection */ @@ -594,7 +933,7 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, __disable_clocks(iommu_drvdata); fail: - spin_unlock_irqrestore(&msm_iommu_lock, flags); + mutex_unlock(&msm_iommu_lock); return ret; } @@ -633,40 +972,61 @@ static void print_ctx_regs(void __iomem *base, int ctx) irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id) { - struct msm_iommu_drvdata *drvdata = dev_id; + struct msm_iommu_ctx_drvdata *ctx_drvdata = dev_id; + struct msm_iommu_drvdata *drvdata; void __iomem *base; - unsigned int fsr; - int i, ret; + unsigned int fsr, num; + int ret; - spin_lock(&msm_iommu_lock); + mutex_lock(&msm_iommu_lock); + BUG_ON(!ctx_drvdata); - if (!drvdata) { - pr_err("Invalid device ID in context interrupt handler\n"); - goto fail; - } + drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); + BUG_ON(!drvdata); base = drvdata->base; - - pr_err("Unexpected IOMMU page fault!\n"); - pr_err("base = %08x\n", (unsigned int) base); + num = ctx_drvdata->num; ret = __enable_clocks(drvdata); if (ret) goto fail; - for (i = 0; i < drvdata->ncb; i++) { - fsr = GET_FSR(base, i); - if (fsr) { - pr_err("Fault occurred in context %d.\n", i); + fsr = GET_FSR(base, num); + + if (fsr) { + if (!ctx_drvdata->attached_domain) { + pr_err("Bad domain in interrupt handler\n"); + ret = -ENOSYS; + } else + ret = report_iommu_fault(ctx_drvdata->attached_domain, + &ctx_drvdata->pdev->dev, + GET_FAR(base, num), 0); + + if (ret == -ENOSYS) { + pr_err("Unexpected IOMMU page fault!\n"); + pr_err("name = %s\n", drvdata->name); + pr_err("context = %s (%d)\n", ctx_drvdata->name, num); pr_err("Interesting registers:\n"); - print_ctx_regs(base, i); - SET_FSR(base, i, 0x4000000F); + print_ctx_regs(base, num); } - } + + SET_FSR(base, num, fsr); + SET_RESUME(base, num, 1); + + ret = IRQ_HANDLED; + } else + ret = IRQ_NONE; + __disable_clocks(drvdata); fail: - spin_unlock(&msm_iommu_lock); - return 0; + mutex_unlock(&msm_iommu_lock); + return ret; +} + +static phys_addr_t msm_iommu_get_pt_base_addr(struct iommu_domain *domain) +{ + struct msm_priv *priv = domain->priv; + return __pa(priv->pgtable); } static struct iommu_ops msm_iommu_ops = { @@ -676,8 +1036,11 @@ static struct iommu_ops msm_iommu_ops = { .detach_dev = msm_iommu_detach_dev, .map = msm_iommu_map, .unmap = msm_iommu_unmap, + .map_range = msm_iommu_map_range, + .unmap_range = msm_iommu_unmap_range, .iova_to_phys = msm_iommu_iova_to_phys, .domain_has_cap = msm_iommu_domain_has_cap, + .get_pt_base_addr = msm_iommu_get_pt_base_addr, .pgsize_bitmap = MSM_IOMMU_PGSIZES, }; @@ -721,6 +1084,9 @@ static void __init setup_iommu_tex_classes(void) static int __init msm_iommu_init(void) { + if (!msm_soc_version_supports_iommu_v1()) + return -ENODEV; + setup_iommu_tex_classes(); bus_set_iommu(&platform_bus_type, &msm_iommu_ops); return 0; diff --git a/drivers/iommu/msm_iommu_dev-v2.c b/drivers/iommu/msm_iommu_dev-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..e690ada07bb9e66e782c6c388ffe6445ba486c79 --- /dev/null +++ b/drivers/iommu/msm_iommu_dev-v2.c @@ -0,0 +1,376 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static void msm_iommu_reset(void __iomem *base) +{ + int i; + + SET_ACR(base, 0); + SET_NSACR(base, 0); + SET_CR2(base, 0); + SET_NSCR2(base, 0); + SET_GFAR(base, 0); + SET_GFSRRESTORE(base, 0); + SET_TLBIALLNSNH(base, 0); + SET_PMCR(base, 0); + SET_SCR1(base, 0); + SET_SSDR_N(base, 0, 0); + + for (i = 0; i < MAX_NUM_SMR; i++) + SET_SMR_VALID(base, i, 0); + + mb(); +} + +static int msm_iommu_parse_dt(struct platform_device *pdev, + struct msm_iommu_drvdata *drvdata) +{ + struct device_node *child; + int ret; + + ret = device_move(&pdev->dev, &msm_iommu_root_dev->dev, DPM_ORDER_NONE); + if (ret) + return ret; + + for_each_child_of_node(pdev->dev.of_node, child) { + drvdata->ncb++; + if (!of_platform_device_create(child, NULL, &pdev->dev)) + pr_err("Failed to create %s device\n", child->name); + } + + drvdata->name = dev_name(&pdev->dev); + return 0; +} + +static atomic_t msm_iommu_next_id = ATOMIC_INIT(-1); + +static int __devinit msm_iommu_probe(struct platform_device *pdev) +{ + struct msm_iommu_drvdata *drvdata; + struct resource *r; + int ret; + + if (msm_iommu_root_dev == pdev) + return 0; + + if (pdev->id == -1) + pdev->id = atomic_inc_return(&msm_iommu_next_id) - 1; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EINVAL; + + drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!drvdata->base) + return -ENOMEM; + + drvdata->pclk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(drvdata->pclk)) + return PTR_ERR(drvdata->pclk); + + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + goto fail_enable; + + drvdata->clk = clk_get(&pdev->dev, "core_clk"); + if (!IS_ERR(drvdata->clk)) { + if (clk_get_rate(drvdata->clk) == 0) { + ret = clk_round_rate(drvdata->clk, 1); + clk_set_rate(drvdata->clk, ret); + } + + ret = clk_prepare_enable(drvdata->clk); + if (ret) { + clk_put(drvdata->clk); + goto fail_pclk; + } + } else + drvdata->clk = NULL; + + msm_iommu_reset(drvdata->base); + + SET_CR0_SMCFCFG(drvdata->base, 1); + SET_CR0_USFCFG(drvdata->base, 1); + SET_CR0_STALLD(drvdata->base, 1); + SET_CR0_GCFGFIE(drvdata->base, 1); + SET_CR0_GCFGFRE(drvdata->base, 1); + SET_CR0_GFIE(drvdata->base, 1); + SET_CR0_GFRE(drvdata->base, 1); + SET_CR0_CLIENTPD(drvdata->base, 0); + + ret = msm_iommu_parse_dt(pdev, drvdata); + if (ret) + goto fail_clk; + + pr_info("device %s mapped at %p, with %d ctx banks\n", + drvdata->name, drvdata->base, drvdata->ncb); + + platform_set_drvdata(pdev, drvdata); + + if (drvdata->clk) + clk_disable_unprepare(drvdata->clk); + + clk_disable_unprepare(drvdata->pclk); + + return 0; + +fail_clk: + if (drvdata->clk) { + clk_disable_unprepare(drvdata->clk); + clk_put(drvdata->clk); + } +fail_pclk: + clk_disable_unprepare(drvdata->pclk); +fail_enable: + clk_put(drvdata->pclk); + return ret; +} + +static int __devexit msm_iommu_remove(struct platform_device *pdev) +{ + struct msm_iommu_drvdata *drv = NULL; + + drv = platform_get_drvdata(pdev); + if (drv) { + if (drv->clk) + clk_put(drv->clk); + clk_put(drv->pclk); + platform_set_drvdata(pdev, NULL); + } + return 0; +} + +static int msm_iommu_ctx_parse_dt(struct platform_device *pdev, + struct msm_iommu_drvdata *drvdata, + struct msm_iommu_ctx_drvdata *ctx_drvdata) +{ + struct resource *r, rp; + u32 sids[MAX_NUM_SMR]; + int num = 0; + int irq, i, ret, len = 0; + + irq = platform_get_irq(pdev, 0); + if (irq > 0) { + ret = request_threaded_irq(irq, NULL, + msm_iommu_fault_handler_v2, + IRQF_ONESHOT | IRQF_SHARED, + "msm_iommu_nonsecure_irq", pdev); + if (ret) { + pr_err("Request IRQ %d failed with ret=%d\n", irq, ret); + return ret; + } + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EINVAL; + + ret = of_address_to_resource(pdev->dev.parent->of_node, 0, &rp); + if (ret) + return -EINVAL; + + /* Calculate the context bank number using the base addresses. The + * first 8 pages belong to the global address space which is followed + * by the context banks, hence subtract by 8 to get the context bank + * number. + */ + ctx_drvdata->num = ((r->start - rp.start) >> CTX_SHIFT) - 8; + + if (of_property_read_string(pdev->dev.of_node, "qcom,iommu-ctx-name", + &ctx_drvdata->name)) + ctx_drvdata->name = dev_name(&pdev->dev); + + of_get_property(pdev->dev.of_node, "qcom,iommu-ctx-sids", &len); + BUG_ON(len >= sizeof(sids)); + if (of_property_read_u32_array(pdev->dev.of_node, "qcom,iommu-ctx-sids", + sids, len / sizeof(*sids))) + return -EINVAL; + + /* Program the M2V tables for this context */ + for (i = 0; i < len / sizeof(*sids); i++) { + for (; num < MAX_NUM_SMR; num++) + if (GET_SMR_VALID(drvdata->base, num) == 0) + break; + BUG_ON(num >= MAX_NUM_SMR); + + SET_SMR_VALID(drvdata->base, num, 1); + SET_SMR_MASK(drvdata->base, num, 0); + SET_SMR_ID(drvdata->base, num, sids[i]); + + /* Set VMID = 0 */ + SET_S2CR_N(drvdata->base, num, 0); + SET_S2CR_CBNDX(drvdata->base, num, ctx_drvdata->num); + /* Set security bit override to be Non-secure */ + SET_S2CR_NSCFG(drvdata->base, sids[i], 3); + + SET_CBAR_N(drvdata->base, ctx_drvdata->num, 0); + /* Stage 1 Context with Stage 2 bypass */ + SET_CBAR_TYPE(drvdata->base, ctx_drvdata->num, 1); + /* Route page faults to the non-secure interrupt */ + SET_CBAR_IRPTNDX(drvdata->base, ctx_drvdata->num, 1); + } + mb(); + + return 0; +} + +static int __devinit msm_iommu_ctx_probe(struct platform_device *pdev) +{ + struct msm_iommu_drvdata *drvdata; + struct msm_iommu_ctx_drvdata *ctx_drvdata = NULL; + int ret; + + if (!pdev->dev.parent) + return -EINVAL; + + drvdata = dev_get_drvdata(pdev->dev.parent); + if (!drvdata) + return -ENODEV; + + ctx_drvdata = devm_kzalloc(&pdev->dev, sizeof(*ctx_drvdata), + GFP_KERNEL); + if (!ctx_drvdata) + return -ENOMEM; + + ctx_drvdata->pdev = pdev; + INIT_LIST_HEAD(&ctx_drvdata->attached_elm); + platform_set_drvdata(pdev, ctx_drvdata); + + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; + + if (drvdata->clk) { + ret = clk_prepare_enable(drvdata->clk); + if (ret) { + clk_disable_unprepare(drvdata->pclk); + return ret; + } + } + + ret = msm_iommu_ctx_parse_dt(pdev, drvdata, ctx_drvdata); + if (!ret) + dev_info(&pdev->dev, "context %s using bank %d\n", + dev_name(&pdev->dev), ctx_drvdata->num); + + if (drvdata->clk) + clk_disable_unprepare(drvdata->clk); + clk_disable_unprepare(drvdata->pclk); + + return ret; +} + +static int __devexit msm_iommu_ctx_remove(struct platform_device *pdev) +{ + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct of_device_id msm_iommu_match_table[] = { + { .compatible = "qcom,msm-smmu-v2", }, + {} +}; + +static struct platform_driver msm_iommu_driver = { + .driver = { + .name = "msm_iommu_v2", + .of_match_table = msm_iommu_match_table, + }, + .probe = msm_iommu_probe, + .remove = __devexit_p(msm_iommu_remove), +}; + +static struct of_device_id msm_iommu_ctx_match_table[] = { + { .name = "qcom,iommu-ctx", }, + {} +}; + +static struct platform_driver msm_iommu_ctx_driver = { + .driver = { + .name = "msm_iommu_ctx_v2", + .of_match_table = msm_iommu_ctx_match_table, + }, + .probe = msm_iommu_ctx_probe, + .remove = __devexit_p(msm_iommu_ctx_remove), +}; + +static int __init msm_iommu_driver_init(void) +{ + struct device_node *node; + int ret; + + node = of_find_compatible_node(NULL, NULL, "qcom,msm-smmu-v2"); + if (!node) + return -ENODEV; + + of_node_put(node); + + msm_iommu_root_dev = platform_device_register_simple( + "msm_iommu", -1, 0, 0); + if (!msm_iommu_root_dev) { + pr_err("Failed to create root IOMMU device\n"); + ret = -ENODEV; + goto error; + } + + atomic_inc(&msm_iommu_next_id); + + ret = platform_driver_register(&msm_iommu_driver); + if (ret != 0) { + pr_err("Failed to register IOMMU driver\n"); + goto error; + } + + ret = platform_driver_register(&msm_iommu_ctx_driver); + if (ret != 0) { + pr_err("Failed to register IOMMU context driver\n"); + goto error; + } + +error: + return ret; +} + +static void __exit msm_iommu_driver_exit(void) +{ + platform_driver_unregister(&msm_iommu_ctx_driver); + platform_driver_unregister(&msm_iommu_driver); + platform_device_unregister(msm_iommu_root_dev); +} + +subsys_initcall(msm_iommu_driver_init); +module_exit(msm_iommu_driver_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iommu/msm_iommu_dev.c b/drivers/iommu/msm_iommu_dev.c index 8e8fb079852d22ca124a5c0ff7bbce9d3f5ec365..c16482548132a9f3890b34f87c664fc50d6e7b3a 100644 --- a/drivers/iommu/msm_iommu_dev.c +++ b/drivers/iommu/msm_iommu_dev.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -29,7 +24,6 @@ #include #include -#include struct iommu_ctx_iter_data { /* input */ @@ -39,13 +33,14 @@ struct iommu_ctx_iter_data { struct device *dev; }; -static struct platform_device *msm_iommu_root_dev; +struct platform_device *msm_iommu_root_dev; static int each_iommu_ctx(struct device *dev, void *data) { struct iommu_ctx_iter_data *res = data; - struct msm_iommu_ctx_dev *c = dev->platform_data; + struct msm_iommu_ctx_drvdata *c; + c = dev_get_drvdata(dev); if (!res || !c || !c->name || !res->name) return -EINVAL; @@ -74,7 +69,7 @@ struct device *msm_iommu_get_ctx(const char *ctx_name) r.name = ctx_name; found = device_for_each_child(&msm_iommu_root_dev->dev, &r, each_iommu); - if (!found) { + if (!found || !dev_get_drvdata(r.dev)) { pr_err("Could not find context <%s>\n", ctx_name); goto fail; } @@ -116,26 +111,28 @@ static void msm_iommu_reset(void __iomem *base, int ncb) SET_BFBCR(base, ctx, 0); SET_PAR(base, ctx, 0); SET_FAR(base, ctx, 0); - SET_CTX_TLBIALL(base, ctx, 0); SET_TLBFLPTER(base, ctx, 0); SET_TLBSLPTER(base, ctx, 0); SET_TLBLKCR(base, ctx, 0); + SET_CTX_TLBIALL(base, ctx, 0); + SET_TLBIVA(base, ctx, 0); SET_PRRR(base, ctx, 0); 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; resource_size_t len; - int ret, irq, par; + int ret, par; if (pdev->id == -1) { msm_iommu_root_dev = pdev; @@ -154,23 +151,25 @@ static int msm_iommu_probe(struct platform_device *pdev) goto fail; } - iommu_pclk = clk_get(NULL, "smmu_pclk"); + iommu_pclk = clk_get_sys("msm_iommu", "iface_clk"); if (IS_ERR(iommu_pclk)) { ret = -ENODEV; goto fail; } - ret = clk_enable(iommu_pclk); + ret = clk_prepare_enable(iommu_pclk); if (ret) goto fail_enable; - iommu_clk = clk_get(&pdev->dev, "iommu_clk"); + iommu_clk = clk_get(&pdev->dev, "core_clk"); if (!IS_ERR(iommu_clk)) { - if (clk_get_rate(iommu_clk) == 0) - clk_set_min_rate(iommu_clk, 1); + if (clk_get_rate(iommu_clk) == 0) { + ret = clk_round_rate(iommu_clk, 1); + clk_set_rate(iommu_clk, ret); + } - ret = clk_enable(iommu_clk); + ret = clk_prepare_enable(iommu_clk); if (ret) { clk_put(iommu_clk); goto fail_pclk; @@ -204,21 +203,17 @@ static int msm_iommu_probe(struct platform_device *pdev) goto fail_mem; } - irq = platform_get_irq_byname(pdev, "secure_irq"); - if (irq < 0) { - ret = -ENODEV; - goto fail_io; - } - msm_iommu_reset(regs_base, iommu_dev->ncb); SET_M(regs_base, 0, 1); 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); @@ -226,29 +221,22 @@ static int msm_iommu_probe(struct platform_device *pdev) goto fail_io; } - ret = request_irq(irq, msm_iommu_fault_handler, 0, - "msm_iommu_secure_irpt_handler", drvdata); - if (ret) { - pr_err("Request IRQ %d failed with ret=%d\n", irq, ret); - goto fail_io; - } - - drvdata->pclk = iommu_pclk; drvdata->clk = iommu_clk; drvdata->base = regs_base; - drvdata->irq = irq; drvdata->ncb = iommu_dev->ncb; + drvdata->ttbr_split = iommu_dev->ttbr_split; + 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); + pr_info("device %s mapped at %p, with %d ctx banks\n", + iommu_dev->name, regs_base, iommu_dev->ncb); platform_set_drvdata(pdev, drvdata); if (iommu_clk) - clk_disable(iommu_clk); + clk_disable_unprepare(iommu_clk); - clk_disable(iommu_pclk); + clk_disable_unprepare(iommu_pclk); return 0; fail_io: @@ -257,11 +245,11 @@ static int msm_iommu_probe(struct platform_device *pdev) release_mem_region(r->start, len); fail_clk: if (iommu_clk) { - clk_disable(iommu_clk); + clk_disable_unprepare(iommu_clk); clk_put(iommu_clk); } fail_pclk: - clk_disable(iommu_pclk); + clk_disable_unprepare(iommu_pclk); fail_enable: clk_put(iommu_pclk); fail: @@ -290,7 +278,7 @@ static int msm_iommu_ctx_probe(struct platform_device *pdev) struct msm_iommu_ctx_dev *c = pdev->dev.platform_data; struct msm_iommu_drvdata *drvdata; struct msm_iommu_ctx_drvdata *ctx_drvdata = NULL; - int i, ret; + int i, ret, irq; if (!c || !pdev->dev.parent) { ret = -EINVAL; goto fail; @@ -310,18 +298,35 @@ static int msm_iommu_ctx_probe(struct platform_device *pdev) } ctx_drvdata->num = c->num; ctx_drvdata->pdev = pdev; + ctx_drvdata->name = c->name; + + irq = platform_get_irq_byname(to_platform_device(pdev->dev.parent), + "nonsecure_irq"); + if (irq < 0) { + ret = -ENODEV; + goto fail; + } + + ret = request_threaded_irq(irq, NULL, msm_iommu_fault_handler, + IRQF_ONESHOT | IRQF_SHARED, + "msm_iommu_nonsecure_irq", ctx_drvdata); + + if (ret) { + pr_err("request_threaded_irq %d failed: %d\n", irq, ret); + goto fail; + } INIT_LIST_HEAD(&ctx_drvdata->attached_elm); platform_set_drvdata(pdev, ctx_drvdata); - ret = clk_enable(drvdata->pclk); + ret = clk_prepare_enable(drvdata->pclk); if (ret) goto fail; if (drvdata->clk) { - ret = clk_enable(drvdata->clk); + ret = clk_prepare_enable(drvdata->clk); if (ret) { - clk_disable(drvdata->pclk); + clk_disable_unprepare(drvdata->pclk); goto fail; } } @@ -335,25 +340,29 @@ static int msm_iommu_ctx_probe(struct platform_device *pdev) SET_M2VCBR_N(drvdata->base, mid, 0); SET_CBACR_N(drvdata->base, c->num, 0); + /* Route page faults to the non-secure interrupt */ + SET_IRPTNDX(drvdata->base, c->num, 1); + /* Set VMID = 0 */ SET_VMID(drvdata->base, mid, 0); /* 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); - clk_disable(drvdata->pclk); + clk_disable_unprepare(drvdata->clk); + clk_disable_unprepare(drvdata->pclk); dev_info(&pdev->dev, "context %s using bank %d\n", c->name, c->num); return 0; diff --git a/drivers/iommu/msm_iommu_pagetable.c b/drivers/iommu/msm_iommu_pagetable.c new file mode 100644 index 0000000000000000000000000000000000000000..b93860eb50a249d46079b2bf704ced7a200e05e5 --- /dev/null +++ b/drivers/iommu/msm_iommu_pagetable.c @@ -0,0 +1,519 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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_iommu_pagetable.h" + +/* Sharability attributes of MSM IOMMU mappings */ +#define MSM_IOMMU_ATTR_NON_SH 0x0 +#define MSM_IOMMU_ATTR_SH 0x4 + +/* Cacheability attributes of MSM IOMMU mappings */ +#define MSM_IOMMU_ATTR_NONCACHED 0x0 +#define MSM_IOMMU_ATTR_CACHED_WB_WA 0x1 +#define MSM_IOMMU_ATTR_CACHED_WB_NWA 0x2 +#define MSM_IOMMU_ATTR_CACHED_WT 0x3 + +static int msm_iommu_tex_class[4]; + +static inline void clean_pte(unsigned long *start, unsigned long *end, + int redirect) +{ + if (!redirect) + dmac_flush_range(start, end); +} + +int msm_iommu_pagetable_alloc(struct iommu_pt *pt) +{ + pt->fl_table = (unsigned long *)__get_free_pages(GFP_KERNEL, + get_order(SZ_16K)); + if (!pt->fl_table) + return -ENOMEM; + + memset(pt->fl_table, 0, SZ_16K); + clean_pte(pt->fl_table, pt->fl_table + NUM_FL_PTE, pt->redirect); + + return 0; +} + +void msm_iommu_pagetable_free(struct iommu_pt *pt) +{ + unsigned long *fl_table; + int i; + + fl_table = pt->fl_table; + for (i = 0; i < NUM_FL_PTE; i++) + if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) + free_page((unsigned long) __va(((fl_table[i]) & + FL_BASE_MASK))); + free_pages((unsigned long)fl_table, get_order(SZ_16K)); + pt->fl_table = 0; +} + +static int __get_pgprot(int prot, int len) +{ + unsigned int pgprot; + int tex; + + if (!(prot & (IOMMU_READ | IOMMU_WRITE))) { + prot |= IOMMU_READ | IOMMU_WRITE; + WARN_ONCE(1, "No attributes in iommu mapping; assuming RW\n"); + } + + if ((prot & IOMMU_WRITE) && !(prot & IOMMU_READ)) { + prot |= IOMMU_READ; + WARN_ONCE(1, "Write-only unsupported; falling back to RW\n"); + } + + if (prot & IOMMU_CACHE) + tex = (pgprot_kernel >> 2) & 0x07; + else + tex = msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED]; + + if (tex < 0 || tex > NUM_TEX_CLASS - 1) + return 0; + + if (len == SZ_16M || len == SZ_1M) { + pgprot = FL_SHARED; + pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0; + pgprot |= tex & 0x02 ? FL_CACHEABLE : 0; + pgprot |= tex & 0x04 ? FL_TEX0 : 0; + pgprot |= FL_AP0 | FL_AP1; + pgprot |= prot & IOMMU_WRITE ? 0 : FL_AP2; + } else { + pgprot = SL_SHARED; + pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0; + pgprot |= tex & 0x02 ? SL_CACHEABLE : 0; + pgprot |= tex & 0x04 ? SL_TEX0 : 0; + pgprot |= SL_AP0 | SL_AP1; + pgprot |= prot & IOMMU_WRITE ? 0 : SL_AP2; + } + + return pgprot; +} + +int msm_iommu_pagetable_map(struct iommu_pt *pt, unsigned long va, + phys_addr_t pa, size_t len, int prot) +{ + unsigned long *fl_pte; + unsigned long fl_offset; + unsigned long *sl_table; + unsigned long *sl_pte; + unsigned long sl_offset; + unsigned int pgprot; + int ret = 0; + + if (len != SZ_16M && len != SZ_1M && + len != SZ_64K && len != SZ_4K) { + pr_debug("Bad size: %d\n", len); + ret = -EINVAL; + goto fail; + } + + if (!pt->fl_table) { + pr_debug("Null page table\n"); + ret = -EINVAL; + goto fail; + } + + pgprot = __get_pgprot(prot, len); + if (!pgprot) { + ret = -EINVAL; + goto fail; + } + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = pt->fl_table + fl_offset; /* int pointers, 4 bytes */ + + 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_TYPE_SECT | FL_SHARED | FL_NG | pgprot; + clean_pte(fl_pte, fl_pte + 16, pt->redirect); + } + + if (len == SZ_1M) { + if (*fl_pte) { + ret = -EBUSY; + goto fail; + } + + *fl_pte = (pa & 0xFFF00000) | FL_NG | FL_TYPE_SECT + | FL_SHARED | pgprot; + clean_pte(fl_pte, fl_pte + 1, pt->redirect); + } + + /* Need a 2nd level table */ + if (len == SZ_4K || len == SZ_64K) { + + if (*fl_pte == 0) { + unsigned long *sl; + sl = (unsigned long *) __get_free_pages(GFP_KERNEL, + get_order(SZ_4K)); + + if (!sl) { + pr_debug("Could not allocate second level table\n"); + ret = -ENOMEM; + goto fail; + } + memset(sl, 0, SZ_4K); + clean_pte(sl, sl + NUM_SL_PTE, pt->redirect); + + *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | \ + FL_TYPE_TABLE); + clean_pte(fl_pte, fl_pte + 1, pt->redirect); + } + + 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; + } + + *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_NG | SL_SHARED + | SL_TYPE_SMALL | pgprot; + clean_pte(sl_pte, sl_pte + 1, pt->redirect); + } + + 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_NG + | SL_SHARED | SL_TYPE_LARGE | pgprot; + + clean_pte(sl_pte, sl_pte + 16, pt->redirect); + } + +fail: + return ret; +} + +size_t msm_iommu_pagetable_unmap(struct iommu_pt *pt, unsigned long va, + size_t len) +{ + unsigned long *fl_pte; + unsigned long fl_offset; + unsigned long *sl_table; + unsigned long *sl_pte; + unsigned long sl_offset; + int i, ret = 0; + + if (len != SZ_16M && len != SZ_1M && + len != SZ_64K && len != SZ_4K) { + pr_debug("Bad length: %d\n", len); + ret = -EINVAL; + goto fail; + } + + if (!pt->fl_table) { + pr_debug("Null page table\n"); + ret = -EINVAL; + goto fail; + } + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = pt->fl_table + fl_offset; /* int pointers, 4 bytes */ + + if (*fl_pte == 0) { + pr_debug("First level PTE is 0\n"); + ret = -ENODEV; + goto fail; + } + + /* Unmap supersection */ + if (len == SZ_16M) { + for (i = 0; i < 16; i++) + *(fl_pte+i) = 0; + + clean_pte(fl_pte, fl_pte + 16, pt->redirect); + } + + if (len == SZ_1M) { + *fl_pte = 0; + clean_pte(fl_pte, fl_pte + 1, pt->redirect); + } + + sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); + sl_offset = SL_OFFSET(va); + sl_pte = sl_table + sl_offset; + + if (len == SZ_64K) { + for (i = 0; i < 16; i++) + *(sl_pte+i) = 0; + + clean_pte(sl_pte, sl_pte + 16, pt->redirect); + } + + if (len == SZ_4K) { + *sl_pte = 0; + clean_pte(sl_pte, sl_pte + 1, pt->redirect); + } + + if (len == SZ_4K || len == SZ_64K) { + int used = 0; + + for (i = 0; i < NUM_SL_PTE; i++) + if (sl_table[i]) + used = 1; + if (!used) { + free_page((unsigned long)sl_table); + *fl_pte = 0; + clean_pte(fl_pte, fl_pte + 1, pt->redirect); + } + } + +fail: + return ret; +} + +static unsigned int get_phys_addr(struct scatterlist *sg) +{ + /* + * Try sg_dma_address first so that we can + * map carveout regions that do not have a + * struct page associated with them. + */ + unsigned int pa = sg_dma_address(sg); + if (pa == 0) + pa = sg_phys(sg); + return pa; +} + +int msm_iommu_pagetable_map_range(struct iommu_pt *pt, unsigned int va, + struct scatterlist *sg, unsigned int len, int prot) +{ + unsigned int pa; + unsigned int offset = 0; + unsigned int pgprot; + unsigned long *fl_pte; + unsigned long fl_offset; + unsigned long *sl_table; + unsigned long sl_offset, sl_start; + unsigned int chunk_offset = 0; + unsigned int chunk_pa; + int ret = 0; + + BUG_ON(len & (SZ_4K - 1)); + + pgprot = __get_pgprot(prot, SZ_4K); + if (!pgprot) { + ret = -EINVAL; + goto fail; + } + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = pt->fl_table + fl_offset; /* int pointers, 4 bytes */ + + sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); + sl_offset = SL_OFFSET(va); + + chunk_pa = get_phys_addr(sg); + if (chunk_pa == 0) { + pr_debug("No dma address for sg %p\n", sg); + ret = -EINVAL; + goto fail; + } + + while (offset < len) { + /* Set up a 2nd level page table if one doesn't exist */ + if (*fl_pte == 0) { + sl_table = (unsigned long *) + __get_free_pages(GFP_KERNEL, get_order(SZ_4K)); + + if (!sl_table) { + pr_debug("Could not allocate second level table\n"); + ret = -ENOMEM; + goto fail; + } + + memset(sl_table, 0, SZ_4K); + clean_pte(sl_table, sl_table + NUM_SL_PTE, + pt->redirect); + + *fl_pte = ((((int)__pa(sl_table)) & FL_BASE_MASK) | + FL_TYPE_TABLE); + clean_pte(fl_pte, fl_pte + 1, pt->redirect); + } else + sl_table = (unsigned long *) + __va(((*fl_pte) & FL_BASE_MASK)); + + /* Keep track of initial position so we + * don't clean more than we have to + */ + sl_start = sl_offset; + + /* Build the 2nd level page table */ + while (offset < len && sl_offset < NUM_SL_PTE) { + pa = chunk_pa + chunk_offset; + sl_table[sl_offset] = (pa & SL_BASE_MASK_SMALL) | + pgprot | SL_NG | SL_SHARED | SL_TYPE_SMALL; + sl_offset++; + offset += SZ_4K; + + chunk_offset += SZ_4K; + + if (chunk_offset >= sg->length && offset < len) { + chunk_offset = 0; + sg = sg_next(sg); + chunk_pa = get_phys_addr(sg); + if (chunk_pa == 0) { + pr_debug("No dma address for sg %p\n", + sg); + ret = -EINVAL; + goto fail; + } + } + } + + clean_pte(sl_table + sl_start, sl_table + sl_offset, + pt->redirect); + fl_pte++; + sl_offset = 0; + } + +fail: + return ret; +} + +void msm_iommu_pagetable_unmap_range(struct iommu_pt *pt, unsigned int va, + unsigned int len) +{ + unsigned int offset = 0; + unsigned long *fl_pte; + unsigned long fl_offset; + unsigned long *sl_table; + unsigned long sl_start, sl_end; + int used, i; + + BUG_ON(len & (SZ_4K - 1)); + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = pt->fl_table + fl_offset; /* int pointers, 4 bytes */ + + sl_start = SL_OFFSET(va); + + while (offset < len) { + sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); + sl_end = ((len - offset) / SZ_4K) + sl_start; + + if (sl_end > NUM_SL_PTE) + sl_end = NUM_SL_PTE; + + memset(sl_table + sl_start, 0, (sl_end - sl_start) * 4); + clean_pte(sl_table + sl_start, sl_table + sl_end, + pt->redirect); + + offset += (sl_end - sl_start) * SZ_4K; + + /* Unmap and free the 2nd level table if all mappings in it + * were removed. This saves memory, but the table will need + * to be re-allocated the next time someone tries to map these + * VAs. + */ + used = 0; + + /* If we just unmapped the whole table, don't bother + * seeing if there are still used entries left. + */ + if (sl_end - sl_start != NUM_SL_PTE) + for (i = 0; i < NUM_SL_PTE; i++) + if (sl_table[i]) { + used = 1; + break; + } + if (!used) { + free_page((unsigned long)sl_table); + *fl_pte = 0; + clean_pte(fl_pte, fl_pte + 1, pt->redirect); + } + + sl_start = 0; + fl_pte++; + } +} + +static int __init get_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); + + for (i = 0; i < NUM_TEX_CLASS; i++) { + c_nos = PRRR_NOS(prrr, i); + c_mt = PRRR_MT(prrr, i); + c_icp = NMRR_ICP(nmrr, i); + c_ocp = NMRR_OCP(nmrr, i); + + if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos) + return i; + } + + return -ENODEV; +} + +static void __init setup_iommu_tex_classes(void) +{ + msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED] = + get_tex_class(CP_NONCACHED, CP_NONCACHED, MT_NORMAL, 1); + + msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_WA] = + get_tex_class(CP_WB_WA, CP_WB_WA, MT_NORMAL, 1); + + msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_NWA] = + get_tex_class(CP_WB_NWA, CP_WB_NWA, MT_NORMAL, 1); + + msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WT] = + get_tex_class(CP_WT, CP_WT, MT_NORMAL, 1); +} + +void __init msm_iommu_pagetable_init(void) +{ + setup_iommu_tex_classes(); +} diff --git a/drivers/iommu/msm_iommu_pagetable.h b/drivers/iommu/msm_iommu_pagetable.h new file mode 100644 index 0000000000000000000000000000000000000000..b943084ad047a9585896f3ed553c5b48b86ae9e9 --- /dev/null +++ b/drivers/iommu/msm_iommu_pagetable.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IOMMU_PAGETABLE_H +#define __ARCH_ARM_MACH_MSM_IOMMU_PAGETABLE_H + +#define NUM_FL_PTE 4096 +#define NUM_SL_PTE 256 +#define NUM_TEX_CLASS 8 + +/* First-level page table bits */ +#define FL_BASE_MASK 0xFFFFFC00 +#define FL_TYPE_TABLE (1 << 0) +#define FL_TYPE_SECT (2 << 0) +#define FL_SUPERSECTION (1 << 18) +#define FL_AP0 (1 << 10) +#define FL_AP1 (1 << 11) +#define FL_AP2 (1 << 15) +#define FL_SHARED (1 << 16) +#define FL_BUFFERABLE (1 << 2) +#define FL_CACHEABLE (1 << 3) +#define FL_TEX0 (1 << 12) +#define FL_OFFSET(va) (((va) & 0xFFF00000) >> 20) +#define FL_NG (1 << 17) + +/* Second-level page table bits */ +#define SL_BASE_MASK_LARGE 0xFFFF0000 +#define SL_BASE_MASK_SMALL 0xFFFFF000 +#define SL_TYPE_LARGE (1 << 0) +#define SL_TYPE_SMALL (2 << 0) +#define SL_AP0 (1 << 4) +#define SL_AP1 (2 << 4) +#define SL_AP2 (1 << 9) +#define SL_SHARED (1 << 10) +#define SL_BUFFERABLE (1 << 2) +#define SL_CACHEABLE (1 << 3) +#define SL_TEX0 (1 << 6) +#define SL_OFFSET(va) (((va) & 0xFF000) >> 12) +#define SL_NG (1 << 11) + +/* Memory type and cache policy attributes */ +#define MT_SO 0 +#define MT_DEV 1 +#define MT_NORMAL 2 +#define CP_NONCACHED 0 +#define CP_WB_WA 1 +#define CP_WT 2 +#define CP_WB_NWA 3 + +/* TEX Remap Registers */ +#define NMRR_ICP(nmrr, n) (((nmrr) & (3 << ((n) * 2))) >> ((n) * 2)) +#define NMRR_OCP(nmrr, n) (((nmrr) & (3 << ((n) * 2 + 16))) >> ((n) * 2 + 16)) + +#define PRRR_NOS(prrr, n) ((prrr) & (1 << ((n) + 24)) ? 1 : 0) +#define PRRR_MT(prrr, n) ((((prrr) & (3 << ((n) * 2))) >> ((n) * 2))) + +#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) + +struct iommu_pt { + unsigned long *fl_table; + int redirect; +}; + +void msm_iommu_pagetable_init(void); +int msm_iommu_pagetable_alloc(struct iommu_pt *pt); +void msm_iommu_pagetable_free(struct iommu_pt *pt); +int msm_iommu_pagetable_map(struct iommu_pt *pt, unsigned long va, + phys_addr_t pa, size_t len, int prot); +size_t msm_iommu_pagetable_unmap(struct iommu_pt *pt, unsigned long va, + size_t len); +int msm_iommu_pagetable_map_range(struct iommu_pt *pt, unsigned int va, + struct scatterlist *sg, unsigned int len, int prot); +void msm_iommu_pagetable_unmap_range(struct iommu_pt *pt, unsigned int va, + unsigned int len); +#endif diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ef2ddd41a6d805d3d257309890cbf2cb8870f74a..d49bfa62b12078d7a07fd774ead4525f2802d2ac 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -157,6 +157,47 @@ 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_PMIC_MPP + tristate "LED Support for Qualcomm PMIC MPP connected LEDs" + depends on LEDS_CLASS && MSM_SMD + help + This option enables support for LEDs connected to PMIC MPPs + on Qualcomm reference boards. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called leds-pmic-mpp. + +config LEDS_GPIO_PLATFORM + bool "Platform device bindings for GPIO LEDs" + depends on LEDS_GPIO + default y + help + Let the leds-gpio driver drive LEDs which have been defined as + platform devices. If you don't know what this means, say yes. + +config LEDS_GPIO_OF + bool "OpenFirmware platform device bindings for GPIO LEDs" + depends on LEDS_GPIO && OF_DEVICE + default y + help + Let the leds-gpio driver drive LEDs which have been defined as + of_platform devices. For instance, LEDs which are listed in a "dts" + file. + config LEDS_LP3944 tristate "LED Support for N.S. LP3944 (Fun Light) I2C chip" depends on LEDS_CLASS @@ -169,6 +210,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 @@ -227,6 +274,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_PCA9633 tristate "LED support for PCA9633 I2C chip" depends on LEDS_CLASS @@ -289,6 +347,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 n + 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 @@ -335,6 +411,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 9d3b1094697a4de32abbe8421e1765ba2352de1d..aa518d4d7b94d084858b03b56674743b11944573 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -18,6 +18,7 @@ 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 @@ -25,10 +26,12 @@ obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.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_OT200) += leds-ot200.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_PCA9633) += leds-pca9633.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o @@ -45,6 +48,10 @@ obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o +obj-$(CONFIG_LEDS_PMIC8058) += leds-pmic8058.o +obj-$(CONFIG_LEDS_PMIC_MPP) += leds-pmic-mpp.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 5bff8439dc68a7e8c6424f0cbed3746cd9820efb..a2b2f3dbb789d70b81b787f6e8caa53e8459a504 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -21,6 +21,8 @@ #include #include "leds.h" +#define LED_BUFF_SIZE 50 + static struct class *leds_class; static void led_update_brightness(struct led_classdev *led_cdev) @@ -37,7 +39,7 @@ static ssize_t led_brightness_show(struct device *dev, /* no lock needed for this */ led_update_brightness(led_cdev); - return sprintf(buf, "%u\n", led_cdev->brightness); + return snprintf(buf, LED_BUFF_SIZE, "%u\n", led_cdev->brightness); } static ssize_t led_brightness_store(struct device *dev, @@ -63,17 +65,37 @@ 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) { struct led_classdev *led_cdev = dev_get_drvdata(dev); - return sprintf(buf, "%u\n", led_cdev->max_brightness); + return snprintf(buf, LED_BUFF_SIZE, "%u\n", led_cdev->max_brightness); } 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 0000000000000000000000000000000000000000..eab004c3d75c5fb88580e193070bdc81f3fcaabc --- /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 0000000000000000000000000000000000000000..467026b4c5a103f77be9ce694c99229ae5820fb9 --- /dev/null +++ b/drivers/leds/leds-msm-pdm.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. + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#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 0000000000000000000000000000000000000000..b9c6a531fd761b6f5f13090a54e70fdf0a53cfba --- /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 0000000000000000000000000000000000000000..32610376a541a82a59173835a9ccc87bb194755e --- /dev/null +++ b/drivers/leds/leds-pm8xxx.c @@ -0,0 +1,969 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 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 SSBI_REG_ADDR_WLED_CTRL_BASE 0x25A +#define SSBI_REG_ADDR_WLED_CTRL(n) (SSBI_REG_ADDR_WLED_CTRL_BASE + (n) - 1) + +/* wled control registers */ +#define WLED_MOD_CTRL_REG SSBI_REG_ADDR_WLED_CTRL(1) +#define WLED_MAX_CURR_CFG_REG(n) SSBI_REG_ADDR_WLED_CTRL(n + 2) +#define WLED_BRIGHTNESS_CNTL_REG1(n) SSBI_REG_ADDR_WLED_CTRL(n + 5) +#define WLED_BRIGHTNESS_CNTL_REG2(n) SSBI_REG_ADDR_WLED_CTRL(n + 6) +#define WLED_SYNC_REG SSBI_REG_ADDR_WLED_CTRL(11) +#define WLED_OVP_CFG_REG SSBI_REG_ADDR_WLED_CTRL(13) +#define WLED_BOOST_CFG_REG SSBI_REG_ADDR_WLED_CTRL(14) +#define WLED_HIGH_POLE_CAP_REG SSBI_REG_ADDR_WLED_CTRL(16) + +#define WLED_STRINGS 0x03 +#define WLED_OVP_VAL_MASK 0x30 +#define WLED_OVP_VAL_BIT_SHFT 0x04 +#define WLED_BOOST_LIMIT_MASK 0xE0 +#define WLED_BOOST_LIMIT_BIT_SHFT 0x05 +#define WLED_BOOST_OFF 0x00 +#define WLED_EN_MASK 0x01 +#define WLED_CP_SELECT_MAX 0x03 +#define WLED_CP_SELECT_MASK 0x03 +#define WLED_DIG_MOD_GEN_MASK 0x70 +#define WLED_CS_OUT_MASK 0x0E +#define WLED_CTL_DLY_STEP 200 +#define WLED_CTL_DLY_MAX 1400 +#define WLED_CTL_DLY_MASK 0xE0 +#define WLED_CTL_DLY_BIT_SHFT 0x05 +#define WLED_MAX_CURR 25 +#define WLED_MAX_CURR_MASK 0x1F +#define WLED_OP_FDBCK_MASK 0x1C +#define WLED_OP_FDBCK_BIT_SHFT 0x02 + +#define WLED_MAX_LEVEL 255 +#define WLED_8_BIT_MASK 0xFF +#define WLED_8_BIT_SHFT 0x08 +#define WLED_MAX_DUTY_CYCLE 0xFFF + +#define WLED_SYNC_VAL 0x07 +#define WLED_SYNC_RESET_VAL 0x00 + +#define SSBI_REG_ADDR_RGB_CNTL1 0x12D +#define SSBI_REG_ADDR_RGB_CNTL2 0x12E + +#define PM8XXX_DRV_RGB_RED_LED BIT(2) +#define PM8XXX_DRV_RGB_GREEN_LED BIT(1) +#define PM8XXX_DRV_RGB_BLUE_LED BIT(0) + +#define MAX_FLASH_LED_CURRENT 300 +#define MAX_LC_LED_CURRENT 40 +#define MAX_KP_BL_LED_CURRENT 300 + +#define PM8XXX_ID_LED_CURRENT_FACTOR 2 /* Iout = x * 2mA */ +#define PM8XXX_ID_FLASH_CURRENT_FACTOR 20 /* Iout = x * 20mA */ + +#define PM8XXX_FLASH_MODE_DBUS1 1 +#define PM8XXX_FLASH_MODE_DBUS2 2 +#define PM8XXX_FLASH_MODE_PWM 3 + +#define MAX_LC_LED_BRIGHTNESS 20 +#define MAX_FLASH_BRIGHTNESS 15 +#define MAX_KB_LED_BRIGHTNESS 15 + +#define PM8XXX_LED_OFFSET(id) ((id) - PM8XXX_ID_LED_0) + +#define PM8XXX_LED_PWM_FLAGS (PM_PWM_LUT_LOOP | PM_PWM_LUT_RAMP_UP) + +#define LED_MAP(_version, _kb, _led0, _led1, _led2, _flash_led0, _flash_led1, \ + _wled, _rgb_led_red, _rgb_led_green, _rgb_led_blue)\ + {\ + .version = _version,\ + .supported = _kb << PM8XXX_ID_LED_KB_LIGHT | \ + _led0 << PM8XXX_ID_LED_0 | _led1 << PM8XXX_ID_LED_1 | \ + _led2 << PM8XXX_ID_LED_2 | \ + _flash_led0 << PM8XXX_ID_FLASH_LED_0 | \ + _flash_led1 << PM8XXX_ID_FLASH_LED_1 | \ + _wled << PM8XXX_ID_WLED | \ + _rgb_led_red << PM8XXX_ID_RGB_LED_RED | \ + _rgb_led_green << PM8XXX_ID_RGB_LED_GREEN | \ + _rgb_led_blue << PM8XXX_ID_RGB_LED_BLUE, \ + } + +/** + * supported_leds - leds supported for each PMIC version + * @version - version of PMIC + * @supported - which leds are supported on version + */ + +struct supported_leds { + enum pm8xxx_version version; + u32 supported; +}; + +static const struct supported_leds led_map[] = { + LED_MAP(PM8XXX_VERSION_8058, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0), + LED_MAP(PM8XXX_VERSION_8921, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0), + LED_MAP(PM8XXX_VERSION_8018, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0), + LED_MAP(PM8XXX_VERSION_8922, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1), + LED_MAP(PM8XXX_VERSION_8038, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1), +}; + +/** + * struct pm8xxx_led_data - internal led data structure + * @led_classdev - led class device + * @id - led index + * @work - workqueue for led + * @lock - to protect the transactions + * @reg - cached value of led register + * @pwm_dev - pointer to PWM device if LED is driven using PWM + * @pwm_channel - PWM channel ID + * @pwm_period_us - PWM period in micro seconds + * @pwm_duty_cycles - struct that describes PWM duty cycles info + */ +struct pm8xxx_led_data { + struct led_classdev cdev; + int id; + u8 reg; + u8 wled_mod_ctrl_val; + struct device *dev; + struct work_struct work; + struct mutex lock; + struct pwm_device *pwm_dev; + int pwm_channel; + u32 pwm_period_us; + struct pm8xxx_pwm_duty_cycles *pwm_duty_cycles; + struct wled_config_data *wled_cfg; + int max_current; +}; + +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 int +led_wled_set(struct pm8xxx_led_data *led, enum led_brightness value) +{ + int rc, duty; + u8 val, i, num_wled_strings; + + if (value > WLED_MAX_LEVEL) + value = WLED_MAX_LEVEL; + + if (value == 0) { + rc = pm8xxx_writeb(led->dev->parent, WLED_MOD_CTRL_REG, + WLED_BOOST_OFF); + if (rc) { + dev_err(led->dev->parent, "can't write wled ctrl config" + " register rc=%d\n", rc); + return rc; + } + } else { + rc = pm8xxx_writeb(led->dev->parent, WLED_MOD_CTRL_REG, + led->wled_mod_ctrl_val); + if (rc) { + dev_err(led->dev->parent, "can't write wled ctrl config" + " register rc=%d\n", rc); + return rc; + } + } + + duty = (WLED_MAX_DUTY_CYCLE * value) / WLED_MAX_LEVEL; + + num_wled_strings = led->wled_cfg->num_strings; + + /* program brightness control registers */ + for (i = 0; i < num_wled_strings; i++) { + rc = pm8xxx_readb(led->dev->parent, + WLED_BRIGHTNESS_CNTL_REG1(i), &val); + if (rc) { + dev_err(led->dev->parent, "can't read wled brightnes ctrl" + " register1 rc=%d\n", rc); + return rc; + } + + val = (val & ~WLED_MAX_CURR_MASK) | (duty >> WLED_8_BIT_SHFT); + rc = pm8xxx_writeb(led->dev->parent, + WLED_BRIGHTNESS_CNTL_REG1(i), val); + if (rc) { + dev_err(led->dev->parent, "can't write wled brightness ctrl" + " register1 rc=%d\n", rc); + return rc; + } + + val = duty & WLED_8_BIT_MASK; + rc = pm8xxx_writeb(led->dev->parent, + WLED_BRIGHTNESS_CNTL_REG2(i), val); + if (rc) { + dev_err(led->dev->parent, "can't write wled brightness ctrl" + " register2 rc=%d\n", rc); + return rc; + } + } + + /* sync */ + val = WLED_SYNC_VAL; + rc = pm8xxx_writeb(led->dev->parent, WLED_SYNC_REG, val); + if (rc) { + dev_err(led->dev->parent, + "can't read wled sync register rc=%d\n", rc); + return rc; + } + + val = WLED_SYNC_RESET_VAL; + rc = pm8xxx_writeb(led->dev->parent, WLED_SYNC_REG, val); + if (rc) { + dev_err(led->dev->parent, + "can't read wled sync register rc=%d\n", rc); + return rc; + } + return 0; +} + +static void wled_dump_regs(struct pm8xxx_led_data *led) +{ + int i; + u8 val; + + for (i = 1; i < 17; i++) { + pm8xxx_readb(led->dev->parent, + SSBI_REG_ADDR_WLED_CTRL(i), &val); + pr_debug("WLED_CTRL_%d = 0x%x\n", i, val); + } +} + +static void +led_rgb_set(struct pm8xxx_led_data *led, enum led_brightness value) +{ + int rc; + u8 val, mask; + + rc = pm8xxx_readb(led->dev->parent, SSBI_REG_ADDR_RGB_CNTL2, &val); + if (rc) { + dev_err(led->cdev.dev, "can't read rgb ctrl register rc=%d\n", + rc); + return; + } + + switch (led->id) { + case PM8XXX_ID_RGB_LED_RED: + mask = PM8XXX_DRV_RGB_RED_LED; + break; + case PM8XXX_ID_RGB_LED_GREEN: + mask = PM8XXX_DRV_RGB_GREEN_LED; + break; + case PM8XXX_ID_RGB_LED_BLUE: + mask = PM8XXX_DRV_RGB_BLUE_LED; + break; + default: + return; + } + + if (value) + val |= mask; + else + val &= ~mask; + + rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_RGB_CNTL2, val); + if (rc < 0) + dev_err(led->cdev.dev, "can't set rgb led %d level rc=%d\n", + led->id, rc); +} + +static int pm8xxx_led_pwm_work(struct pm8xxx_led_data *led) +{ + int duty_us; + int rc = 0; + + if (led->pwm_duty_cycles == NULL) { + duty_us = (led->pwm_period_us * led->cdev.brightness) / + LED_FULL; + rc = pwm_config(led->pwm_dev, duty_us, led->pwm_period_us); + if (led->cdev.brightness) + rc = pwm_enable(led->pwm_dev); + else + pwm_disable(led->pwm_dev); + } else { + rc = pm8xxx_pwm_lut_enable(led->pwm_dev, led->cdev.brightness); + } + + return rc; +} + +static void __pm8xxx_led_work(struct pm8xxx_led_data *led, + enum led_brightness level) +{ + int rc; + + mutex_lock(&led->lock); + + switch (led->id) { + case PM8XXX_ID_LED_KB_LIGHT: + led_kp_set(led, level); + break; + case PM8XXX_ID_LED_0: + case PM8XXX_ID_LED_1: + case PM8XXX_ID_LED_2: + led_lc_set(led, level); + break; + case PM8XXX_ID_FLASH_LED_0: + case PM8XXX_ID_FLASH_LED_1: + led_flash_set(led, level); + break; + case PM8XXX_ID_WLED: + rc = led_wled_set(led, level); + if (rc < 0) + pr_err("wled brightness set failed %d\n", rc); + break; + case PM8XXX_ID_RGB_LED_RED: + case PM8XXX_ID_RGB_LED_GREEN: + case PM8XXX_ID_RGB_LED_BLUE: + led_rgb_set(led, level); + break; + default: + dev_err(led->cdev.dev, "unknown led id %d", led->id); + break; + } + + mutex_unlock(&led->lock); +} + +static void pm8xxx_led_work(struct work_struct *work) +{ + int rc; + + struct pm8xxx_led_data *led = container_of(work, + struct pm8xxx_led_data, work); + + if (led->pwm_dev == NULL) { + __pm8xxx_led_work(led, led->cdev.brightness); + } else { + rc = pm8xxx_led_pwm_work(led); + if (rc) + pr_err("could not configure PWM mode for LED:%d\n", + led->id); + } +} + +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); + + if (value < LED_OFF || value > led->cdev.max_brightness) { + dev_err(led->cdev.dev, "Invalid brightness value exceeds"); + return; + } + + led->cdev.brightness = value; + schedule_work(&led->work); +} + +static int pm8xxx_set_led_mode_and_max_brightness(struct pm8xxx_led_data *led, + enum pm8xxx_led_modes led_mode, int max_current) +{ + switch (led->id) { + case PM8XXX_ID_LED_0: + case PM8XXX_ID_LED_1: + case PM8XXX_ID_LED_2: + led->cdev.max_brightness = max_current / + PM8XXX_ID_LED_CURRENT_FACTOR; + if (led->cdev.max_brightness > MAX_LC_LED_BRIGHTNESS) + led->cdev.max_brightness = MAX_LC_LED_BRIGHTNESS; + led->reg = led_mode; + break; + case PM8XXX_ID_LED_KB_LIGHT: + case PM8XXX_ID_FLASH_LED_0: + case PM8XXX_ID_FLASH_LED_1: + led->cdev.max_brightness = max_current / + PM8XXX_ID_FLASH_CURRENT_FACTOR; + if (led->cdev.max_brightness > MAX_FLASH_BRIGHTNESS) + led->cdev.max_brightness = MAX_FLASH_BRIGHTNESS; + + switch (led_mode) { + case PM8XXX_LED_MODE_PWM1: + case PM8XXX_LED_MODE_PWM2: + case PM8XXX_LED_MODE_PWM3: + led->reg = PM8XXX_FLASH_MODE_PWM; + break; + case PM8XXX_LED_MODE_DTEST1: + led->reg = PM8XXX_FLASH_MODE_DBUS1; + break; + case PM8XXX_LED_MODE_DTEST2: + led->reg = PM8XXX_FLASH_MODE_DBUS2; + break; + default: + led->reg = PM8XXX_LED_MODE_MANUAL; + break; + } + break; + case PM8XXX_ID_WLED: + led->cdev.max_brightness = WLED_MAX_LEVEL; + break; + case PM8XXX_ID_RGB_LED_RED: + case PM8XXX_ID_RGB_LED_GREEN: + case PM8XXX_ID_RGB_LED_BLUE: + led->cdev.max_brightness = LED_FULL; + break; + default: + dev_err(led->cdev.dev, "LED Id is invalid"); + return -EINVAL; + } + + return 0; +} + +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->cdev.brightness; +} + +static int __devinit init_wled(struct pm8xxx_led_data *led) +{ + int rc, i; + u8 val, num_wled_strings; + + num_wled_strings = led->wled_cfg->num_strings; + + /* program over voltage protection threshold */ + if (led->wled_cfg->ovp_val > WLED_OVP_37V) { + dev_err(led->dev->parent, "Invalid ovp value"); + return -EINVAL; + } + + rc = pm8xxx_readb(led->dev->parent, WLED_OVP_CFG_REG, &val); + if (rc) { + dev_err(led->dev->parent, "can't read wled ovp config" + " register rc=%d\n", rc); + return rc; + } + + val = (val & ~WLED_OVP_VAL_MASK) | + (led->wled_cfg->ovp_val << WLED_OVP_VAL_BIT_SHFT); + + rc = pm8xxx_writeb(led->dev->parent, WLED_OVP_CFG_REG, val); + if (rc) { + dev_err(led->dev->parent, "can't write wled ovp config" + " register rc=%d\n", rc); + return rc; + } + + /* program current boost limit and output feedback*/ + if (led->wled_cfg->boost_curr_lim > WLED_CURR_LIMIT_1680mA) { + dev_err(led->dev->parent, "Invalid boost current limit"); + return -EINVAL; + } + + rc = pm8xxx_readb(led->dev->parent, WLED_BOOST_CFG_REG, &val); + if (rc) { + dev_err(led->dev->parent, "can't read wled boost config" + " register rc=%d\n", rc); + return rc; + } + + val = (val & ~WLED_BOOST_LIMIT_MASK) | + (led->wled_cfg->boost_curr_lim << WLED_BOOST_LIMIT_BIT_SHFT); + + val = (val & ~WLED_OP_FDBCK_MASK) | + (led->wled_cfg->op_fdbck << WLED_OP_FDBCK_BIT_SHFT); + + rc = pm8xxx_writeb(led->dev->parent, WLED_BOOST_CFG_REG, val); + if (rc) { + dev_err(led->dev->parent, "can't write wled boost config" + " register rc=%d\n", rc); + return rc; + } + + /* program high pole capacitance */ + if (led->wled_cfg->cp_select > WLED_CP_SELECT_MAX) { + dev_err(led->dev->parent, "Invalid pole capacitance"); + return -EINVAL; + } + + rc = pm8xxx_readb(led->dev->parent, WLED_HIGH_POLE_CAP_REG, &val); + if (rc) { + dev_err(led->dev->parent, "can't read wled high pole" + " capacitance register rc=%d\n", rc); + return rc; + } + + val = (val & ~WLED_CP_SELECT_MASK) | led->wled_cfg->cp_select; + + rc = pm8xxx_writeb(led->dev->parent, WLED_HIGH_POLE_CAP_REG, val); + if (rc) { + dev_err(led->dev->parent, "can't write wled high pole" + " capacitance register rc=%d\n", rc); + return rc; + } + + /* program activation delay and maximum current */ + for (i = 0; i < num_wled_strings; i++) { + rc = pm8xxx_readb(led->dev->parent, + WLED_MAX_CURR_CFG_REG(i + 2), &val); + if (rc) { + dev_err(led->dev->parent, "can't read wled max current" + " config register rc=%d\n", rc); + return rc; + } + + if ((led->wled_cfg->ctrl_delay_us % WLED_CTL_DLY_STEP) || + (led->wled_cfg->ctrl_delay_us > WLED_CTL_DLY_MAX)) { + dev_err(led->dev->parent, "Invalid control delay\n"); + return rc; + } + + val = val / WLED_CTL_DLY_STEP; + val = (val & ~WLED_CTL_DLY_MASK) | + (led->wled_cfg->ctrl_delay_us << WLED_CTL_DLY_BIT_SHFT); + + if ((led->max_current > WLED_MAX_CURR)) { + dev_err(led->dev->parent, "Invalid max current\n"); + return -EINVAL; + } + + val = (val & ~WLED_MAX_CURR_MASK) | led->max_current; + + rc = pm8xxx_writeb(led->dev->parent, + WLED_MAX_CURR_CFG_REG(i + 2), val); + if (rc) { + dev_err(led->dev->parent, "can't write wled max current" + " config register rc=%d\n", rc); + return rc; + } + } + + /* program digital module generator, cs out and enable the module */ + rc = pm8xxx_readb(led->dev->parent, WLED_MOD_CTRL_REG, &val); + if (rc) { + dev_err(led->dev->parent, "can't read wled module ctrl" + " register rc=%d\n", rc); + return rc; + } + + if (led->wled_cfg->dig_mod_gen_en) + val |= WLED_DIG_MOD_GEN_MASK; + + if (led->wled_cfg->cs_out_en) + val |= WLED_CS_OUT_MASK; + + val |= WLED_EN_MASK; + + rc = pm8xxx_writeb(led->dev->parent, WLED_MOD_CTRL_REG, val); + if (rc) { + dev_err(led->dev->parent, "can't write wled module ctrl" + " register rc=%d\n", rc); + return rc; + } + led->wled_mod_ctrl_val = val; + + /* dump wled registers */ + wled_dump_regs(led); + + return 0; +} + +static int __devinit init_rgb_led(struct pm8xxx_led_data *led) +{ + int rc; + u8 val; + + rc = pm8xxx_readb(led->dev->parent, SSBI_REG_ADDR_RGB_CNTL1, &val); + if (rc) { + dev_err(led->cdev.dev, "can't read rgb ctrl1 register rc=%d\n", + rc); + return rc; + } + + switch (led->id) { + case PM8XXX_ID_RGB_LED_RED: + val |= PM8XXX_DRV_RGB_RED_LED; + break; + case PM8XXX_ID_RGB_LED_GREEN: + val |= PM8XXX_DRV_RGB_GREEN_LED; + break; + case PM8XXX_ID_RGB_LED_BLUE: + val |= PM8XXX_DRV_RGB_BLUE_LED; + break; + default: + return -EINVAL; + } + + return pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_RGB_CNTL1, val); +} + +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; + case PM8XXX_ID_WLED: + rc = init_wled(led); + if (rc) + dev_err(led->cdev.dev, "can't initialize wled rc=%d\n", + rc); + return rc; + case PM8XXX_ID_RGB_LED_RED: + case PM8XXX_ID_RGB_LED_GREEN: + case PM8XXX_ID_RGB_LED_BLUE: + rc = init_rgb_led(led); + if (rc) { + dev_err(led->cdev.dev, "can't initialize rgb rc=%d\n", + rc); + return rc; + } + addr = SSBI_REG_ADDR_RGB_CNTL1; + break; + default: + dev_err(led->cdev.dev, "unknown led id %d", led->id); + return -EINVAL; + } + + 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 pm8xxx_led_pwm_configure(struct pm8xxx_led_data *led) +{ + int start_idx, idx_len, duty_us, rc; + + led->pwm_dev = pwm_request(led->pwm_channel, + led->cdev.name); + + if (IS_ERR_OR_NULL(led->pwm_dev)) { + pr_err("could not acquire PWM Channel %d, " + "error %ld\n", led->pwm_channel, + PTR_ERR(led->pwm_dev)); + led->pwm_dev = NULL; + return -ENODEV; + } + + if (led->pwm_duty_cycles != NULL) { + start_idx = led->pwm_duty_cycles->start_idx; + idx_len = led->pwm_duty_cycles->num_duty_pcts; + + 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; + } + + rc = pm8xxx_pwm_lut_config(led->pwm_dev, led->pwm_period_us, + led->pwm_duty_cycles->duty_pcts, + led->pwm_duty_cycles->duty_ms, + start_idx, idx_len, 0, 0, + PM8XXX_LED_PWM_FLAGS); + } else { + duty_us = led->pwm_period_us; + rc = pwm_config(led->pwm_dev, duty_us, led->pwm_period_us); + } + + return rc; +} + + +static int __devinit pm8xxx_led_probe(struct platform_device *pdev) +{ + const struct pm8xxx_led_platform_data *pdata = pdev->dev.platform_data; + const struct led_platform_data *pcore_data; + struct led_info *curr_led; + struct pm8xxx_led_data *led, *led_dat; + struct pm8xxx_led_config *led_cfg; + enum pm8xxx_version version; + bool found = false; + int rc, i, j; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data not supplied\n"); + return -EINVAL; + } + + pcore_data = pdata->led_core; + + if (pcore_data->num_leds != pdata->num_configs) { + dev_err(&pdev->dev, "#no. of led configs and #no. of led" + "entries are not equal\n"); + return -EINVAL; + } + + led = kcalloc(pcore_data->num_leds, sizeof(*led), GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pcore_data->num_leds; i++) { + curr_led = &pcore_data->leds[i]; + led_dat = &led[i]; + led_cfg = &pdata->configs[i]; + + led_dat->id = led_cfg->id; + led_dat->pwm_channel = led_cfg->pwm_channel; + led_dat->pwm_period_us = led_cfg->pwm_period_us; + led_dat->pwm_duty_cycles = led_cfg->pwm_duty_cycles; + led_dat->wled_cfg = led_cfg->wled_cfg; + led_dat->max_current = led_cfg->max_current; + + if (!((led_dat->id >= PM8XXX_ID_LED_KB_LIGHT) && + (led_dat->id < PM8XXX_ID_MAX))) { + dev_err(&pdev->dev, "invalid LED ID(%d) specified\n", + led_dat->id); + rc = -EINVAL; + goto fail_id_check; + + } + + found = false; + version = pm8xxx_get_version(pdev->dev.parent); + for (j = 0; j < ARRAY_SIZE(led_map); j++) { + if (version == led_map[j].version + && (led_map[j].supported & (1 << led_dat->id))) { + found = true; + break; + } + } + + if (!found) { + 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 = curr_led->flags; + led_dat->dev = &pdev->dev; + + rc = get_init_value(led_dat, &led_dat->reg); + if (rc < 0) + goto fail_id_check; + + rc = pm8xxx_set_led_mode_and_max_brightness(led_dat, + led_cfg->mode, led_cfg->max_current); + 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; + } + + /* configure default state */ + if (led_cfg->default_state) + led->cdev.brightness = led_dat->cdev.max_brightness; + else + led->cdev.brightness = LED_OFF; + + if (led_cfg->mode != PM8XXX_LED_MODE_MANUAL) { + if (led_dat->id == PM8XXX_ID_RGB_LED_RED || + led_dat->id == PM8XXX_ID_RGB_LED_GREEN || + led_dat->id == PM8XXX_ID_RGB_LED_BLUE) + __pm8xxx_led_work(led_dat, 0); + else + __pm8xxx_led_work(led_dat, + led_dat->cdev.max_brightness); + + if (led_dat->pwm_channel != -1) { + led_dat->cdev.max_brightness = LED_FULL; + rc = pm8xxx_led_pwm_configure(led_dat); + if (rc) { + dev_err(&pdev->dev, "failed to " + "configure LED, error: %d\n", rc); + goto fail_id_check; + } + schedule_work(&led->work); + } + } else { + __pm8xxx_led_work(led_dat, led->cdev.brightness); + } + } + + 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); + if (led[i].pwm_dev != NULL) + pwm_free(led[i].pwm_dev); + } + } + 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); + if (led[i].pwm_dev != NULL) + pwm_free(led[i].pwm_dev); + } + + 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); +} +subsys_initcall(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-pmic-mpp.c b/drivers/leds/leds-pmic-mpp.c new file mode 100644 index 0000000000000000000000000000000000000000..a3762be847b9b523c4d7094738f56fe4fadec7d5 --- /dev/null +++ b/drivers/leds/leds-pmic-mpp.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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 LED_MPP(x) ((x) & 0xFF) +#define LED_CURR(x) ((x) >> 16) + +struct pmic_mpp_led_data { + struct led_classdev cdev; + int curr; + int mpp; +}; + +static void pm_mpp_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pmic_mpp_led_data *led; + int ret; + + led = container_of(led_cdev, struct pmic_mpp_led_data, cdev); + + if (value < LED_OFF || value > led->cdev.max_brightness) { + dev_err(led->cdev.dev, "Invalid brightness value"); + return; + } + + ret = pmic_secure_mpp_config_i_sink(led->mpp, led->curr, + value ? PM_MPP__I_SINK__SWITCH_ENA : + PM_MPP__I_SINK__SWITCH_DIS); + if (ret) + dev_err(led_cdev->dev, "can't set mpp led\n"); +} + +static int pmic_mpp_led_probe(struct platform_device *pdev) +{ + const struct led_platform_data *pdata = pdev->dev.platform_data; + struct pmic_mpp_led_data *led, *tmp_led; + int i, rc; + + if (!pdata) { + dev_err(&pdev->dev, "platform data not supplied\n"); + return -EINVAL; + } + + led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL); + if (!led) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, led); + + for (i = 0; i < pdata->num_leds; i++) { + tmp_led = &led[i]; + tmp_led->cdev.name = pdata->leds[i].name; + tmp_led->cdev.brightness_set = pm_mpp_led_set; + tmp_led->cdev.brightness = LED_OFF; + tmp_led->cdev.max_brightness = LED_FULL; + tmp_led->mpp = LED_MPP(pdata->leds[i].flags); + tmp_led->curr = LED_CURR(pdata->leds[i].flags); + + if (tmp_led->curr < PM_MPP__I_SINK__LEVEL_5mA || + tmp_led->curr > PM_MPP__I_SINK__LEVEL_40mA) { + dev_err(&pdev->dev, "invalid current\n"); + goto unreg_led_cdev; + } + + rc = led_classdev_register(&pdev->dev, &tmp_led->cdev); + if (rc) { + dev_err(&pdev->dev, "failed to register led\n"); + goto unreg_led_cdev; + } + } + + return 0; + +unreg_led_cdev: + while (i) + led_classdev_unregister(&led[--i].cdev); + + kfree(led); + return rc; + +} + +static int __devexit pmic_mpp_led_remove(struct platform_device *pdev) +{ + const struct led_platform_data *pdata = pdev->dev.platform_data; + struct pmic_mpp_led_data *led = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < pdata->num_leds; i++) + led_classdev_unregister(&led[i].cdev); + + kfree(led); + + return 0; +} + +static struct platform_driver pmic_mpp_led_driver = { + .probe = pmic_mpp_led_probe, + .remove = __devexit_p(pmic_mpp_led_remove), + .driver = { + .name = "pmic-mpp-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init pmic_mpp_led_init(void) +{ + return platform_driver_register(&pmic_mpp_led_driver); +} +module_init(pmic_mpp_led_init); + +static void __exit pmic_mpp_led_exit(void) +{ + platform_driver_unregister(&pmic_mpp_led_driver); +} +module_exit(pmic_mpp_led_exit); + +MODULE_DESCRIPTION("PMIC MPP LEDs driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pmic-mpp-leds"); diff --git a/drivers/leds/leds-pmic8058.c b/drivers/leds/leds-pmic8058.c new file mode 100644 index 0000000000000000000000000000000000000000..0ba3f5817b86ecf4717c26ba9fc5fd3b5a73f1fa --- /dev/null +++ b/drivers/leds/leds-pmic8058.c @@ -0,0 +1,428 @@ +/* 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 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 device *dev; + struct led_classdev cdev; + int id; + enum led_brightness brightness; + u8 flags; + 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 = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_DRV_KEYPAD, + led->reg_kp); + 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 = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), + tmp); + 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 = pm8xxx_writeb(led->dev->parent, reg_addr, reg_flash_led); + 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; + u8 reg_kp; + u8 reg_led_ctrl[3]; + u8 reg_flash_led0; + u8 reg_flash_led1; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data not supplied\n"); + return -EINVAL; + } + + rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_DRV_KEYPAD, ®_kp); + if (rc) { + dev_err(&pdev->dev, "can't get keypad backlight level\n"); + goto err_reg_read; + } + + rc = pm8xxx_read_buf(pdev->dev.parent, 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 = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_FLASH_DRV0, + ®_flash_led0); + if (rc) { + dev_err(&pdev->dev, "can't read flash led0\n"); + goto err_reg_read; + } + + rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_FLASH_DRV1, + ®_flash_led1); + 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->dev = &pdev->dev; + + 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 0000000000000000000000000000000000000000..67502e89f635bb5aafde07587ea16d0af0fcb876 --- /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/md/dm-crypt.c b/drivers/md/dm-crypt.c index 3f06df59fd824022fd80a9c77551178660909eb6..da5db8b5237c4b1ea8c674ba2a823386abdc54f9 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -43,7 +42,8 @@ struct convert_context { unsigned int idx_in; unsigned int idx_out; sector_t sector; - atomic_t pending; + atomic_t cc_pending; + struct ablkcipher_request *req; }; /* @@ -56,7 +56,7 @@ struct dm_crypt_io { struct convert_context ctx; - atomic_t pending; + atomic_t io_pending; int error; sector_t sector; struct dm_crypt_io *base_io; @@ -104,19 +104,8 @@ struct iv_lmk_private { */ enum flags { DM_CRYPT_SUSPENDED, DM_CRYPT_KEY_VALID }; -/* - * Duplicated per-CPU state for cipher. - */ -struct crypt_cpu { - struct ablkcipher_request *req; - /* ESSIV: struct crypto_cipher *essiv_tfm */ - void *iv_private; - struct crypto_ablkcipher *tfms[0]; -}; - /* * The fields in here must be read only after initialization, - * changing state should be in crypt_cpu. */ struct crypt_config { struct dm_dev *dev; @@ -146,11 +135,9 @@ struct crypt_config { sector_t iv_offset; unsigned int iv_size; - /* - * Duplicated per cpu state. Access through - * per_cpu_ptr() only. - */ - struct crypt_cpu __percpu *cpu; + /* ESSIV: struct crypto_cipher *essiv_tfm */ + void *iv_private; + struct crypto_ablkcipher **tfms; unsigned tfms_count; /* @@ -183,17 +170,12 @@ static void clone_init(struct dm_crypt_io *, struct bio *); static void kcryptd_queue_crypt(struct dm_crypt_io *io); static u8 *iv_of_dmreq(struct crypt_config *cc, struct dm_crypt_request *dmreq); -static struct crypt_cpu *this_crypt_config(struct crypt_config *cc) -{ - return this_cpu_ptr(cc->cpu); -} - /* * Use this to access cipher attributes that are the same for each CPU. */ static struct crypto_ablkcipher *any_tfm(struct crypt_config *cc) { - return __this_cpu_ptr(cc->cpu)->tfms[0]; + return cc->tfms[0]; } /* @@ -258,7 +240,7 @@ static int crypt_iv_essiv_init(struct crypt_config *cc) struct hash_desc desc; struct scatterlist sg; struct crypto_cipher *essiv_tfm; - int err, cpu; + int err; sg_init_one(&sg, cc->key, cc->key_size); desc.tfm = essiv->hash_tfm; @@ -268,14 +250,12 @@ static int crypt_iv_essiv_init(struct crypt_config *cc) if (err) return err; - for_each_possible_cpu(cpu) { - essiv_tfm = per_cpu_ptr(cc->cpu, cpu)->iv_private, + essiv_tfm = cc->iv_private; - err = crypto_cipher_setkey(essiv_tfm, essiv->salt, - crypto_hash_digestsize(essiv->hash_tfm)); - if (err) - return err; - } + err = crypto_cipher_setkey(essiv_tfm, essiv->salt, + crypto_hash_digestsize(essiv->hash_tfm)); + if (err) + return err; return 0; } @@ -286,16 +266,14 @@ static int crypt_iv_essiv_wipe(struct crypt_config *cc) struct iv_essiv_private *essiv = &cc->iv_gen_private.essiv; unsigned salt_size = crypto_hash_digestsize(essiv->hash_tfm); struct crypto_cipher *essiv_tfm; - int cpu, r, err = 0; + int r, err = 0; memset(essiv->salt, 0, salt_size); - for_each_possible_cpu(cpu) { - essiv_tfm = per_cpu_ptr(cc->cpu, cpu)->iv_private; - r = crypto_cipher_setkey(essiv_tfm, essiv->salt, salt_size); - if (r) - err = r; - } + essiv_tfm = cc->iv_private; + r = crypto_cipher_setkey(essiv_tfm, essiv->salt, salt_size); + if (r) + err = r; return err; } @@ -335,8 +313,6 @@ static struct crypto_cipher *setup_essiv_cpu(struct crypt_config *cc, static void crypt_iv_essiv_dtr(struct crypt_config *cc) { - int cpu; - struct crypt_cpu *cpu_cc; struct crypto_cipher *essiv_tfm; struct iv_essiv_private *essiv = &cc->iv_gen_private.essiv; @@ -346,15 +322,13 @@ static void crypt_iv_essiv_dtr(struct crypt_config *cc) kzfree(essiv->salt); essiv->salt = NULL; - for_each_possible_cpu(cpu) { - cpu_cc = per_cpu_ptr(cc->cpu, cpu); - essiv_tfm = cpu_cc->iv_private; + essiv_tfm = cc->iv_private; - if (essiv_tfm) - crypto_free_cipher(essiv_tfm); + if (essiv_tfm) + crypto_free_cipher(essiv_tfm); + + cc->iv_private = NULL; - cpu_cc->iv_private = NULL; - } } static int crypt_iv_essiv_ctr(struct crypt_config *cc, struct dm_target *ti, @@ -363,7 +337,7 @@ static int crypt_iv_essiv_ctr(struct crypt_config *cc, struct dm_target *ti, struct crypto_cipher *essiv_tfm = NULL; struct crypto_hash *hash_tfm = NULL; u8 *salt = NULL; - int err, cpu; + int err; if (!opts) { ti->error = "Digest algorithm missing for ESSIV mode"; @@ -388,15 +362,13 @@ static int crypt_iv_essiv_ctr(struct crypt_config *cc, struct dm_target *ti, cc->iv_gen_private.essiv.salt = salt; cc->iv_gen_private.essiv.hash_tfm = hash_tfm; - for_each_possible_cpu(cpu) { - essiv_tfm = setup_essiv_cpu(cc, ti, salt, - crypto_hash_digestsize(hash_tfm)); - if (IS_ERR(essiv_tfm)) { - crypt_iv_essiv_dtr(cc); - return PTR_ERR(essiv_tfm); - } - per_cpu_ptr(cc->cpu, cpu)->iv_private = essiv_tfm; + essiv_tfm = setup_essiv_cpu(cc, ti, salt, + crypto_hash_digestsize(hash_tfm)); + if (IS_ERR(essiv_tfm)) { + crypt_iv_essiv_dtr(cc); + return PTR_ERR(essiv_tfm); } + cc->iv_private = essiv_tfm; return 0; @@ -410,7 +382,8 @@ static int crypt_iv_essiv_ctr(struct crypt_config *cc, struct dm_target *ti, static int crypt_iv_essiv_gen(struct crypt_config *cc, u8 *iv, struct dm_crypt_request *dmreq) { - struct crypto_cipher *essiv_tfm = this_crypt_config(cc)->iv_private; + + struct crypto_cipher *essiv_tfm = cc->iv_private; memset(iv, 0, cc->iv_size); *(__le64 *)iv = cpu_to_le64(dmreq->iv_sector); @@ -748,16 +721,15 @@ static void kcryptd_async_done(struct crypto_async_request *async_req, static void crypt_alloc_req(struct crypt_config *cc, struct convert_context *ctx) { - struct crypt_cpu *this_cc = this_crypt_config(cc); unsigned key_index = ctx->sector & (cc->tfms_count - 1); - if (!this_cc->req) - this_cc->req = mempool_alloc(cc->req_pool, GFP_NOIO); + if (!ctx->req) + ctx->req = mempool_alloc(cc->req_pool, GFP_NOIO); - ablkcipher_request_set_tfm(this_cc->req, this_cc->tfms[key_index]); - ablkcipher_request_set_callback(this_cc->req, + ablkcipher_request_set_tfm(ctx->req, cc->tfms[key_index]); + ablkcipher_request_set_callback(ctx->req, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, - kcryptd_async_done, dmreq_of_req(cc, this_cc->req)); + kcryptd_async_done, dmreq_of_req(cc, ctx->req)); } /* @@ -766,19 +738,18 @@ static void crypt_alloc_req(struct crypt_config *cc, static int crypt_convert(struct crypt_config *cc, struct convert_context *ctx) { - struct crypt_cpu *this_cc = this_crypt_config(cc); int r; - atomic_set(&ctx->pending, 1); + atomic_set(&ctx->cc_pending, 1); while(ctx->idx_in < ctx->bio_in->bi_vcnt && ctx->idx_out < ctx->bio_out->bi_vcnt) { crypt_alloc_req(cc, ctx); - atomic_inc(&ctx->pending); + atomic_inc(&ctx->cc_pending); - r = crypt_convert_block(cc, ctx, this_cc->req); + r = crypt_convert_block(cc, ctx, ctx->req); switch (r) { /* async */ @@ -787,20 +758,20 @@ static int crypt_convert(struct crypt_config *cc, INIT_COMPLETION(ctx->restart); /* fall through*/ case -EINPROGRESS: - this_cc->req = NULL; + ctx->req = NULL; ctx->sector++; continue; /* sync */ case 0: - atomic_dec(&ctx->pending); + atomic_dec(&ctx->cc_pending); ctx->sector++; cond_resched(); continue; /* error */ default: - atomic_dec(&ctx->pending); + atomic_dec(&ctx->cc_pending); return r; } } @@ -896,14 +867,15 @@ static struct dm_crypt_io *crypt_io_alloc(struct dm_target *ti, io->sector = sector; io->error = 0; io->base_io = NULL; - atomic_set(&io->pending, 0); + io->ctx.req = NULL; + atomic_set(&io->io_pending, 0); return io; } static void crypt_inc_pending(struct dm_crypt_io *io) { - atomic_inc(&io->pending); + atomic_inc(&io->io_pending); } /* @@ -918,9 +890,11 @@ static void crypt_dec_pending(struct dm_crypt_io *io) struct dm_crypt_io *base_io = io->base_io; int error = io->error; - if (!atomic_dec_and_test(&io->pending)) + if (!atomic_dec_and_test(&io->io_pending)) return; + if (io->ctx.req) + mempool_free(io->ctx.req, cc->req_pool); mempool_free(io, cc->io_pool); if (likely(!base_io)) @@ -1106,8 +1080,7 @@ static void kcryptd_crypt_write_convert(struct dm_crypt_io *io) r = crypt_convert(cc, &io->ctx); if (r < 0) io->error = -EIO; - - crypt_finished = atomic_dec_and_test(&io->ctx.pending); + crypt_finished = atomic_dec_and_test(&io->ctx.cc_pending); /* Encryption was already finished, submit io now */ if (crypt_finished) { @@ -1178,10 +1151,11 @@ static void kcryptd_crypt_read_convert(struct dm_crypt_io *io) io->sector); r = crypt_convert(cc, &io->ctx); + if (r < 0) io->error = -EIO; - if (atomic_dec_and_test(&io->ctx.pending)) + if (atomic_dec_and_test(&io->ctx.cc_pending)) kcryptd_crypt_read_done(io); crypt_dec_pending(io); @@ -1208,7 +1182,7 @@ static void kcryptd_async_done(struct crypto_async_request *async_req, mempool_free(req_of_dmreq(cc, dmreq), cc->req_pool); - if (!atomic_dec_and_test(&ctx->pending)) + if (!atomic_dec_and_test(&ctx->cc_pending)) return; if (bio_data_dir(io->base_bio) == READ) @@ -1276,29 +1250,35 @@ static void crypt_encode_key(char *hex, u8 *key, unsigned int size) } } -static void crypt_free_tfms(struct crypt_config *cc, int cpu) +static void crypt_free_tfms(struct crypt_config *cc) { - struct crypt_cpu *cpu_cc = per_cpu_ptr(cc->cpu, cpu); unsigned i; + if (!cc->tfms) + return; + for (i = 0; i < cc->tfms_count; i++) - if (cpu_cc->tfms[i] && !IS_ERR(cpu_cc->tfms[i])) { - crypto_free_ablkcipher(cpu_cc->tfms[i]); - cpu_cc->tfms[i] = NULL; + if (cc->tfms[i] && !IS_ERR(cc->tfms[i])) { + crypto_free_ablkcipher(cc->tfms[i]); + cc->tfms[i] = NULL; } } -static int crypt_alloc_tfms(struct crypt_config *cc, int cpu, char *ciphermode) +static int crypt_alloc_tfms(struct crypt_config *cc, char *ciphermode) { - struct crypt_cpu *cpu_cc = per_cpu_ptr(cc->cpu, cpu); unsigned i; int err; + cc->tfms = kmalloc(cc->tfms_count * sizeof(struct crypto_ablkcipher *), + GFP_KERNEL); + if (!cc->tfms) + return -ENOMEM; + for (i = 0; i < cc->tfms_count; i++) { - cpu_cc->tfms[i] = crypto_alloc_ablkcipher(ciphermode, 0, 0); - if (IS_ERR(cpu_cc->tfms[i])) { - err = PTR_ERR(cpu_cc->tfms[i]); - crypt_free_tfms(cc, cpu); + cc->tfms[i] = crypto_alloc_ablkcipher(ciphermode, 0, 0); + if (IS_ERR(cc->tfms[i])) { + err = PTR_ERR(cc->tfms[i]); + crypt_free_tfms(cc); return err; } } @@ -1309,15 +1289,14 @@ static int crypt_alloc_tfms(struct crypt_config *cc, int cpu, char *ciphermode) static int crypt_setkey_allcpus(struct crypt_config *cc) { unsigned subkey_size = cc->key_size >> ilog2(cc->tfms_count); - int cpu, err = 0, i, r; - - for_each_possible_cpu(cpu) { - for (i = 0; i < cc->tfms_count; i++) { - r = crypto_ablkcipher_setkey(per_cpu_ptr(cc->cpu, cpu)->tfms[i], - cc->key + (i * subkey_size), subkey_size); - if (r) - err = r; - } + int err = 0, i, r; + + for (i = 0; i < cc->tfms_count; i++) { + r = crypto_ablkcipher_setkey(cc->tfms[i], + cc->key + (i * subkey_size), + subkey_size); + if (r) + err = r; } return err; @@ -1361,8 +1340,6 @@ static int crypt_wipe_key(struct crypt_config *cc) static void crypt_dtr(struct dm_target *ti) { struct crypt_config *cc = ti->private; - struct crypt_cpu *cpu_cc; - int cpu; ti->private = NULL; @@ -1374,13 +1351,7 @@ static void crypt_dtr(struct dm_target *ti) if (cc->crypt_queue) destroy_workqueue(cc->crypt_queue); - if (cc->cpu) - for_each_possible_cpu(cpu) { - cpu_cc = per_cpu_ptr(cc->cpu, cpu); - if (cpu_cc->req) - mempool_free(cpu_cc->req, cc->req_pool); - crypt_free_tfms(cc, cpu); - } + crypt_free_tfms(cc); if (cc->bs) bioset_free(cc->bs); @@ -1398,9 +1369,6 @@ static void crypt_dtr(struct dm_target *ti) if (cc->dev) dm_put_device(ti, cc->dev); - if (cc->cpu) - free_percpu(cc->cpu); - kzfree(cc->cipher); kzfree(cc->cipher_string); @@ -1414,7 +1382,7 @@ static int crypt_ctr_cipher(struct dm_target *ti, struct crypt_config *cc = ti->private; char *tmp, *cipher, *chainmode, *ivmode, *ivopts, *keycount; char *cipher_api = NULL; - int cpu, ret = -EINVAL; + int ret = -EINVAL; char dummy; /* Convert to crypto api definition? */ @@ -1455,14 +1423,6 @@ static int crypt_ctr_cipher(struct dm_target *ti, if (tmp) DMWARN("Ignoring unexpected additional cipher options"); - cc->cpu = __alloc_percpu(sizeof(*(cc->cpu)) + - cc->tfms_count * sizeof(*(cc->cpu->tfms)), - __alignof__(struct crypt_cpu)); - if (!cc->cpu) { - ti->error = "Cannot allocate per cpu state"; - goto bad_mem; - } - /* * For compatibility with the original dm-crypt mapping format, if * only the cipher name is supplied, use cbc-plain. @@ -1489,12 +1449,10 @@ static int crypt_ctr_cipher(struct dm_target *ti, } /* Allocate cipher */ - for_each_possible_cpu(cpu) { - ret = crypt_alloc_tfms(cc, cpu, cipher_api); - if (ret < 0) { - ti->error = "Error allocating crypto tfm"; - goto bad; - } + ret = crypt_alloc_tfms(cc, cipher_api); + if (ret < 0) { + ti->error = "Error allocating crypto tfm"; + goto bad; } /* Initialize and set key */ diff --git a/drivers/md/dm-mpath.c b/drivers/md/dm-mpath.c index 754f38f8a6922d94d425fcaef5d834a573851fd4..5a61c5fe5140b7d45c854bc5ac86b14c93a319f7 100644 --- a/drivers/md/dm-mpath.c +++ b/drivers/md/dm-mpath.c @@ -770,6 +770,11 @@ static int parse_features(struct dm_arg_set *as, struct multipath *m) if (!argc) return 0; + if (argc > as->argc) { + ti->error = "not enough arguments for features"; + return -EINVAL; + } + do { arg_name = dm_shift_arg(as); argc--; diff --git a/drivers/media/dvb/Kconfig b/drivers/media/dvb/Kconfig index f6e40b3a44cc2204f842969deec3ea72c3170b21..915dcaad9b5d430864b3a460700da987ccea709c 100644 --- a/drivers/media/dvb/Kconfig +++ b/drivers/media/dvb/Kconfig @@ -88,4 +88,8 @@ comment "Supported DVB Frontends" depends on DVB_CORE source "drivers/media/dvb/frontends/Kconfig" +comment "Qualcomm MPQ adapter" + depends on ARCH_MSM && DVB_CORE +source "drivers/media/dvb/mpq/Kconfig" + endif # DVB_CAPTURE_DRIVERS diff --git a/drivers/media/dvb/Makefile b/drivers/media/dvb/Makefile index b2cefe637a6410e42bb3ccc762beaa3c3b28901f..d3438d68e29c0ae6dd7784572b30964b8f350d2a 100644 --- a/drivers/media/dvb/Makefile +++ b/drivers/media/dvb/Makefile @@ -19,3 +19,5 @@ obj-y := dvb-core/ \ ddbridge/ obj-$(CONFIG_DVB_FIREDTV) += firewire/ +obj-$(CONFIG_DVB_MPQ) += mpq/ + diff --git a/drivers/media/dvb/dvb-core/demux.h b/drivers/media/dvb/dvb-core/demux.h index eb91fd808c16307a9d7259f960ff89be70efbd96..a57ad44b813780a346651b1712e9cf1572ba037f 100644 --- a/drivers/media/dvb/dvb-core/demux.h +++ b/drivers/media/dvb/dvb-core/demux.h @@ -7,6 +7,8 @@ * Copyright (c) 2000 Nokia Research Center * Tampere, FINLAND * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 @@ -134,6 +136,8 @@ struct dmx_ts_feed { struct timespec timeout); int (*start_filtering) (struct dmx_ts_feed* feed); int (*stop_filtering) (struct dmx_ts_feed* feed); + int (*set_indexing_params) (struct dmx_ts_feed *feed, + struct dmx_indexing_video_params *params); }; /*--------------------------------------------------------------------------*/ @@ -190,6 +194,14 @@ typedef int (*dmx_section_cb) ( const u8 * buffer1, struct dmx_section_filter * source, enum dmx_success success); +typedef int (*dmx_ts_fullness) ( + struct dmx_ts_feed *source, + int required_space); + +typedef int (*dmx_section_fullness) ( + struct dmx_section_filter *source, + int required_space); + /*--------------------------------------------------------------------------*/ /* DVB Front-End */ /*--------------------------------------------------------------------------*/ @@ -247,7 +259,7 @@ struct dmx_demux { void* priv; /* Pointer to private data of the API client */ int (*open) (struct dmx_demux* demux); int (*close) (struct dmx_demux* demux); - int (*write) (struct dmx_demux* demux, const char __user *buf, size_t count); + int (*write) (struct dmx_demux *demux, const char *buf, size_t count); int (*allocate_ts_feed) (struct dmx_demux* demux, struct dmx_ts_feed** feed, dmx_ts_cb callback); @@ -271,7 +283,20 @@ struct dmx_demux { int (*get_caps) (struct dmx_demux* demux, struct dmx_caps *caps); - int (*set_source) (struct dmx_demux* demux, const dmx_source_t *src); + int (*set_source) (struct dmx_demux *demux, const dmx_source_t *src); + + int (*set_tsp_format) (struct dmx_demux *demux, + enum dmx_tsp_format_t tsp_format); + + int (*set_tsp_out_format) (struct dmx_demux *demux, + enum dmx_tsp_format_t tsp_format); + + int (*set_playback_mode) (struct dmx_demux *demux, + enum dmx_playback_mode_t mode, + dmx_ts_fullness ts_fullness_callback, + dmx_section_fullness sec_fullness_callback); + + int (*write_cancel) (struct dmx_demux *demux); int (*get_stc) (struct dmx_demux* demux, unsigned int num, u64 *stc, unsigned int *base); diff --git a/drivers/media/dvb/dvb-core/dmxdev.c b/drivers/media/dvb/dvb-core/dmxdev.c index 73970cd97af1356e1fdeaea9dd733aab94a5f5c7..6dfa193ef26dff74dd2f02c5be8070c70f3c68c4 100644 --- a/drivers/media/dvb/dvb-core/dmxdev.c +++ b/drivers/media/dvb/dvb-core/dmxdev.c @@ -4,6 +4,8 @@ * Copyright (C) 2000 Ralph Metzler & Marcus Metzler * for convergence integrated media GmbH * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 @@ -28,6 +30,7 @@ #include #include #include +#include #include #include "dmxdev.h" @@ -80,12 +83,15 @@ static ssize_t dvb_dmxdev_buffer_read(struct dvb_ringbuffer *src, break; } - ret = wait_event_interruptible(src->queue, - !dvb_ringbuffer_empty(src) || - (src->error != 0)); + ret = wait_event_interruptible(src->queue, (!src->data) || + !dvb_ringbuffer_empty(src) || + (src->error != 0)); if (ret < 0) break; + if (!src->data) + return 0; + if (src->error) { ret = src->error; dvb_ringbuffer_flush(src); @@ -103,6 +109,9 @@ static ssize_t dvb_dmxdev_buffer_read(struct dvb_ringbuffer *src, buf += ret; } + if (count - todo) /* some data was read? */ + wake_up_all(&src->queue); + return (count - todo) ? (count - todo) : ret; } @@ -125,8 +134,11 @@ static int dvb_dvr_open(struct inode *inode, struct file *file) struct dvb_device *dvbdev = file->private_data; struct dmxdev *dmxdev = dvbdev->priv; struct dmx_frontend *front; + void *mem; - dprintk("function : %s\n", __func__); + dprintk("function : %s(%X)\n", + __func__, + (file->f_flags & O_ACCMODE)); if (mutex_lock_interruptible(&dmxdev->mutex)) return -ERESTARTSYS; @@ -144,21 +156,20 @@ static int dvb_dvr_open(struct inode *inode, struct file *file) } if ((file->f_flags & O_ACCMODE) == O_RDONLY) { - void *mem; if (!dvbdev->readers) { mutex_unlock(&dmxdev->mutex); return -EBUSY; } - mem = vmalloc(DVR_BUFFER_SIZE); + mem = vmalloc_user(DVR_BUFFER_SIZE); if (!mem) { mutex_unlock(&dmxdev->mutex); return -ENOMEM; } dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); dvbdev->readers--; - } - - if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + } else if (!dvbdev->writers) { + dmxdev->dvr_in_exit = 0; + dmxdev->dvr_processing_input = 0; dmxdev->dvr_orig_fe = dmxdev->demux->frontend; if (!dmxdev->demux->write) { @@ -172,9 +183,22 @@ static int dvb_dvr_open(struct inode *inode, struct file *file) mutex_unlock(&dmxdev->mutex); return -EINVAL; } + + mem = vmalloc_user(DVR_BUFFER_SIZE); + if (!mem) { + mutex_unlock(&dmxdev->mutex); + return -ENOMEM; + } + dmxdev->demux->disconnect_frontend(dmxdev->demux); dmxdev->demux->connect_frontend(dmxdev->demux, front); + + dvb_ringbuffer_init(&dmxdev->dvr_input_buffer, + mem, + DVR_BUFFER_SIZE); + dvbdev->writers--; } + dvbdev->users++; mutex_unlock(&dmxdev->mutex); return 0; @@ -187,11 +211,6 @@ static int dvb_dvr_release(struct inode *inode, struct file *file) mutex_lock(&dmxdev->mutex); - if ((file->f_flags & O_ACCMODE) == O_WRONLY) { - dmxdev->demux->disconnect_frontend(dmxdev->demux); - dmxdev->demux->connect_frontend(dmxdev->demux, - dmxdev->dvr_orig_fe); - } if ((file->f_flags & O_ACCMODE) == O_RDONLY) { dvbdev->readers++; if (dmxdev->dvr_buffer.data) { @@ -200,6 +219,51 @@ static int dvb_dvr_release(struct inode *inode, struct file *file) spin_lock_irq(&dmxdev->lock); dmxdev->dvr_buffer.data = NULL; spin_unlock_irq(&dmxdev->lock); + wake_up_all(&dmxdev->dvr_buffer.queue); + vfree(mem); + } + } else { + int i; + + dmxdev->dvr_in_exit = 1; + wake_up_all(&dmxdev->dvr_input_buffer.queue); + + /* + * There might be dmx filters reading now from DVR + * device, in PULL mode, they might be also stalled + * on output, signal to them that DVR is exiting. + */ + if (dmxdev->playback_mode == DMX_PB_MODE_PULL) { + wake_up_all(&dmxdev->dvr_buffer.queue); + + for (i = 0; i < dmxdev->filternum; i++) + if (dmxdev->filter[i].state == DMXDEV_STATE_GO) + wake_up_all( + &dmxdev->filter[i].buffer.queue); + } + + /* notify kernel demux that we are canceling */ + if (dmxdev->demux->write_cancel) + dmxdev->demux->write_cancel(dmxdev->demux); + + /* + * Now flush dvr-in workqueue so that no one + * would process data from dvr input buffer any more + * before it gets freed. + */ + flush_workqueue(dmxdev->dvr_input_workqueue); + + dvbdev->writers++; + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, + dmxdev->dvr_orig_fe); + + if (dmxdev->dvr_input_buffer.data) { + void *mem = dmxdev->dvr_input_buffer.data; + mb(); + spin_lock_irq(&dmxdev->dvr_in_lock); + dmxdev->dvr_input_buffer.data = NULL; + spin_unlock_irq(&dmxdev->dvr_in_lock); vfree(mem); } } @@ -216,17 +280,20 @@ static int dvb_dvr_release(struct inode *inode, struct file *file) return 0; } -static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) + +static int dvb_dvr_mmap(struct file *filp, struct vm_area_struct *vma) { - struct dvb_device *dvbdev = file->private_data; + struct dvb_device *dvbdev = filp->private_data; struct dmxdev *dmxdev = dvbdev->priv; + struct dvb_ringbuffer *buffer; + int vma_size; + int buffer_size; int ret; - if (!dmxdev->demux->write) - return -EOPNOTSUPP; - if ((file->f_flags & O_ACCMODE) != O_WRONLY) + if (((filp->f_flags & O_ACCMODE) == O_RDONLY) && + (vma->vm_flags & VM_WRITE)) return -EINVAL; + if (mutex_lock_interruptible(&dmxdev->mutex)) return -ERESTARTSYS; @@ -234,11 +301,114 @@ static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, mutex_unlock(&dmxdev->mutex); return -ENODEV; } - ret = dmxdev->demux->write(dmxdev->demux, buf, count); + + if ((filp->f_flags & O_ACCMODE) == O_RDONLY) + buffer = &dmxdev->dvr_buffer; + else + buffer = &dmxdev->dvr_input_buffer; + + vma_size = vma->vm_end - vma->vm_start; + + /* Make sure requested mapping is not larger than buffer size */ + buffer_size = buffer->size + (PAGE_SIZE-1); + buffer_size = buffer_size & ~(PAGE_SIZE-1); + + if (vma_size != buffer_size) { + mutex_unlock(&dmxdev->mutex); + return -EINVAL; + } + + ret = remap_vmalloc_range(vma, buffer->data, 0); + if (ret) { + mutex_unlock(&dmxdev->mutex); + return ret; + } + + vma->vm_flags |= VM_RESERVED; + vma->vm_flags |= VM_DONTEXPAND; + mutex_unlock(&dmxdev->mutex); return ret; } +static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + struct dvb_ringbuffer *src = &dmxdev->dvr_input_buffer; + int ret; + size_t todo; + ssize_t free_space; + + if (!dmxdev->demux->write) + return -EOPNOTSUPP; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EINVAL; + + if ((file->f_flags & O_NONBLOCK) && + (dvb_ringbuffer_free(src) == 0)) + return -EWOULDBLOCK; + + ret = 0; + for (todo = count; todo > 0; todo -= ret) { + ret = wait_event_interruptible(src->queue, + (!src->data) || + (dvb_ringbuffer_free(src)) || + (src->error != 0) || + (dmxdev->dvr_in_exit)); + + if (ret < 0) + return ret; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (!src->data) { + mutex_unlock(&dmxdev->mutex); + return 0; + } + + if (dmxdev->exit || dmxdev->dvr_in_exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + + if (src->error) { + ret = src->error; + dvb_ringbuffer_flush(src); + mutex_unlock(&dmxdev->mutex); + wake_up_all(&src->queue); + return ret; + } + + free_space = dvb_ringbuffer_free(src); + + if (free_space > todo) + free_space = todo; + + ret = dvb_ringbuffer_write_user(src, buf, free_space); + + if (ret < 0) { + mutex_unlock(&dmxdev->mutex); + return ret; + } + + buf += ret; + + mutex_unlock(&dmxdev->mutex); + + wake_up_all(&src->queue); + + if (!work_pending(&dmxdev->dvr_input_work)) + queue_work(dmxdev->dvr_input_workqueue, + &dmxdev->dvr_input_work); + } + + return (count - todo) ? (count - todo) : ret; +} + static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { @@ -253,39 +423,223 @@ static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, buf, count, ppos); } +static void dvr_input_work_func(struct work_struct *worker) +{ + struct dmxdev *dmxdev = + container_of(worker, struct dmxdev, dvr_input_work); + struct dvb_ringbuffer *src = &dmxdev->dvr_input_buffer; + int ret; + size_t todo; + size_t split; + + while (1) { + /* wait for input */ + ret = wait_event_interruptible(src->queue, + (!src->data) || + (dvb_ringbuffer_avail(src)) || + (src->error != 0) || + (dmxdev->dvr_in_exit)); + + if (ret < 0) + break; + + spin_lock(&dmxdev->dvr_in_lock); + + if (!src->data || dmxdev->exit || dmxdev->dvr_in_exit) { + spin_unlock(&dmxdev->dvr_in_lock); + break; + } + + if (src->error) { + spin_unlock(&dmxdev->dvr_in_lock); + wake_up_all(&src->queue); + break; + } + + dmxdev->dvr_processing_input = 1; + + ret = dvb_ringbuffer_avail(src); + todo = ret; + + split = (src->pread + ret > src->size) ? + src->size - src->pread : + 0; + + /* + * In DVR PULL mode, write might block. + * Lock on DVR buffer is released before calling to + * write, if DVR was released meanwhile, dvr_in_exit is + * prompted. Lock is aquired when updating the read pointer + * again to preserve read/write pointers consistancy + */ + if (split > 0) { + spin_unlock(&dmxdev->dvr_in_lock); + dmxdev->demux->write(dmxdev->demux, + src->data + src->pread, + split); + + if (dmxdev->dvr_in_exit) + break; + + spin_lock(&dmxdev->dvr_in_lock); + + todo -= split; + DVB_RINGBUFFER_SKIP(src, split); + } + + spin_unlock(&dmxdev->dvr_in_lock); + dmxdev->demux->write(dmxdev->demux, + src->data + src->pread, todo); + + if (dmxdev->dvr_in_exit) + break; + + spin_lock(&dmxdev->dvr_in_lock); + + DVB_RINGBUFFER_SKIP(src, todo); + dmxdev->dvr_processing_input = 0; + spin_unlock(&dmxdev->dvr_in_lock); + + wake_up_all(&src->queue); + } +} + static int dvb_dvr_set_buffer_size(struct dmxdev *dmxdev, - unsigned long size) + unsigned int f_flags, + unsigned long size) { - struct dvb_ringbuffer *buf = &dmxdev->dvr_buffer; + struct dvb_ringbuffer *buf; void *newmem; void *oldmem; + spinlock_t *lock; dprintk("function : %s\n", __func__); + if ((f_flags & O_ACCMODE) == O_RDONLY) { + buf = &dmxdev->dvr_buffer; + lock = &dmxdev->lock; + } else { + buf = &dmxdev->dvr_input_buffer; + lock = &dmxdev->dvr_in_lock; + } + if (buf->size == size) return 0; if (!size) return -EINVAL; - newmem = vmalloc(size); + newmem = vmalloc_user(size); if (!newmem) return -ENOMEM; oldmem = buf->data; - spin_lock_irq(&dmxdev->lock); + spin_lock_irq(lock); + + if (((f_flags & O_ACCMODE) != O_RDONLY) && + (dmxdev->dvr_processing_input)) { + spin_unlock_irq(lock); + vfree(oldmem); + return -EBUSY; + } + buf->data = newmem; buf->size = size; /* reset and not flush in case the buffer shrinks */ dvb_ringbuffer_reset(buf); - spin_unlock_irq(&dmxdev->lock); + + spin_unlock_irq(lock); vfree(oldmem); return 0; } +static int dvb_dvr_get_buffer_status(struct dmxdev *dmxdev, + unsigned int f_flags, + struct dmx_buffer_status *dmx_buffer_status) +{ + struct dvb_ringbuffer *buf; + spinlock_t *lock; + + if ((f_flags & O_ACCMODE) == O_RDONLY) { + buf = &dmxdev->dvr_buffer; + lock = &dmxdev->lock; + } else { + buf = &dmxdev->dvr_input_buffer; + lock = &dmxdev->dvr_in_lock; + } + + spin_lock_irq(lock); + + dmx_buffer_status->error = buf->error; + if (buf->error) + dvb_ringbuffer_flush(buf); + + dmx_buffer_status->fullness = dvb_ringbuffer_avail(buf); + dmx_buffer_status->free_bytes = dvb_ringbuffer_free(buf); + dmx_buffer_status->read_offset = buf->pread; + dmx_buffer_status->write_offset = buf->pwrite; + dmx_buffer_status->size = buf->size; + + spin_unlock_irq(lock); + + return 0; +} + +static int dvb_dvr_release_data(struct dmxdev *dmxdev, + unsigned int f_flags, + u32 bytes_count) +{ + ssize_t buff_fullness; + + if (!(f_flags & O_ACCMODE) == O_RDONLY) + return -EINVAL; + + if (!bytes_count) + return 0; + + buff_fullness = dvb_ringbuffer_avail(&dmxdev->dvr_buffer); + + if (bytes_count > buff_fullness) + return -EINVAL; + + DVB_RINGBUFFER_SKIP(&dmxdev->dvr_buffer, bytes_count); + wake_up_all(&dmxdev->dvr_buffer.queue); + return 0; +} + +static int dvb_dvr_feed_data(struct dmxdev *dmxdev, + unsigned int f_flags, + u32 bytes_count) +{ + ssize_t free_space; + struct dvb_ringbuffer *buffer = &dmxdev->dvr_input_buffer; + + if ((f_flags & O_ACCMODE) == O_RDONLY) + return -EINVAL; + + if (!bytes_count) + return 0; + + free_space = dvb_ringbuffer_free(buffer); + + if (bytes_count > free_space) + return -EINVAL; + + buffer->pwrite = + (buffer->pwrite + bytes_count) % buffer->size; + + wake_up_all(&buffer->queue); + + if (!work_pending(&dmxdev->dvr_input_work)) + queue_work(dmxdev->dvr_input_workqueue, + &dmxdev->dvr_input_work); + + return 0; +} + static inline void dvb_dmxdev_filter_state_set(struct dmxdev_filter *dmxdevfilter, int state) { @@ -308,7 +662,7 @@ static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, if (dmxdevfilter->state >= DMXDEV_STATE_GO) return -EBUSY; - newmem = vmalloc(size); + newmem = vmalloc_user(size); if (!newmem) return -ENOMEM; @@ -323,6 +677,212 @@ static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, spin_unlock_irq(&dmxdevfilter->dev->lock); vfree(oldmem); + return 0; +} + +static int dvb_dmxdev_set_pes_buffer_size(struct dmxdev_filter *dmxdevfilter, + unsigned long size) +{ + if (dmxdevfilter->pes_buffer_size == size) + return 0; + if (!size) + return -EINVAL; + if (dmxdevfilter->state >= DMXDEV_STATE_GO) + return -EBUSY; + + dmxdevfilter->pes_buffer_size = size; + + return 0; +} + +static int dvb_dmxdev_set_source(struct dmxdev_filter *dmxdevfilter, + dmx_source_t *source) +{ + struct dmxdev *dev; + + if (dmxdevfilter->state == DMXDEV_STATE_GO) + return -EBUSY; + + dev = dmxdevfilter->dev; + + dev->source = *source; + + if (dev->demux->set_source) + return dev->demux->set_source(dev->demux, source); + + return 0; +} + +static int dvb_dmxdev_ts_fullness_callback( + struct dmx_ts_feed *filter, + int required_space) +{ + struct dmxdev_filter *dmxdevfilter = filter->priv; + struct dvb_ringbuffer *src; + int ret; + + if (dmxdevfilter->params.pes.output == DMX_OUT_TAP + || dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) + src = &dmxdevfilter->buffer; + else + src = &dmxdevfilter->dev->dvr_buffer; + + do { + ret = 0; + + if (dmxdevfilter->dev->dvr_in_exit) + return -ENODEV; + + spin_lock(&dmxdevfilter->dev->lock); + + if ((!src->data) || + (dmxdevfilter->state != DMXDEV_STATE_GO)) + ret = -EINVAL; + else if (src->error) + ret = src->error; + + if (ret) { + spin_unlock(&dmxdevfilter->dev->lock); + return ret; + } + + if (required_space <= dvb_ringbuffer_free(src)) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + + spin_unlock(&dmxdevfilter->dev->lock); + + ret = wait_event_interruptible(src->queue, + (!src->data) || + (dvb_ringbuffer_free(src) >= required_space) || + (src->error != 0) || + (dmxdevfilter->state != DMXDEV_STATE_GO) || + dmxdevfilter->dev->dvr_in_exit); + + if (ret < 0) + return ret; + } while (1); +} + +static int dvb_dmxdev_sec_fullness_callback( + struct dmx_section_filter *filter, + int required_space) +{ + struct dmxdev_filter *dmxdevfilter = filter->priv; + struct dvb_ringbuffer *src = &dmxdevfilter->buffer; + int ret; + + do { + ret = 0; + + if (dmxdevfilter->dev->dvr_in_exit) + return -ENODEV; + + spin_lock(&dmxdevfilter->dev->lock); + + if ((!src->data) || + (dmxdevfilter->state != DMXDEV_STATE_GO)) + ret = -EINVAL; + else if (src->error) + ret = src->error; + + if (ret) { + spin_unlock(&dmxdevfilter->dev->lock); + return ret; + } + + if (required_space <= dvb_ringbuffer_free(src)) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + + spin_unlock(&dmxdevfilter->dev->lock); + + ret = wait_event_interruptible(src->queue, + (!src->data) || + (dvb_ringbuffer_free(src) >= required_space) || + (src->error != 0) || + (dmxdevfilter->state != DMXDEV_STATE_GO) || + dmxdevfilter->dev->dvr_in_exit); + + if (ret < 0) + return ret; + } while (1); +} + +static int dvb_dmxdev_set_playback_mode(struct dmxdev_filter *dmxdevfilter, + enum dmx_playback_mode_t playback_mode) +{ + struct dmxdev *dmxdev = dmxdevfilter->dev; + + if ((playback_mode != DMX_PB_MODE_PUSH) && + (playback_mode != DMX_PB_MODE_PULL)) + return -EINVAL; + + if (((dmxdev->source < DMX_SOURCE_DVR0) || + !dmxdev->demux->set_playback_mode || + !(dmxdev->capabilities & DMXDEV_CAP_PULL_MODE)) && + (playback_mode == DMX_PB_MODE_PULL)) + return -EPERM; + + if (dmxdevfilter->state == DMXDEV_STATE_GO) + return -EBUSY; + + dmxdev->playback_mode = playback_mode; + + return dmxdev->demux->set_playback_mode( + dmxdev->demux, + dmxdev->playback_mode, + dvb_dmxdev_ts_fullness_callback, + dvb_dmxdev_sec_fullness_callback); +} + +static int dvb_dmxdev_get_buffer_status( + struct dmxdev_filter *dmxdevfilter, + struct dmx_buffer_status *dmx_buffer_status) +{ + struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; + + if (!buf->data) + return -EINVAL; + + spin_lock_irq(&dmxdevfilter->dev->lock); + + dmx_buffer_status->error = buf->error; + if (buf->error) + dvb_ringbuffer_flush(buf); + + dmx_buffer_status->fullness = dvb_ringbuffer_avail(buf); + dmx_buffer_status->free_bytes = dvb_ringbuffer_free(buf); + dmx_buffer_status->read_offset = buf->pread; + dmx_buffer_status->write_offset = buf->pwrite; + dmx_buffer_status->size = buf->size; + + spin_unlock_irq(&dmxdevfilter->dev->lock); + + return 0; +} + +static int dvb_dmxdev_release_data(struct dmxdev_filter *dmxdevfilter, + u32 bytes_count) +{ + ssize_t buff_fullness; + + if (!dmxdevfilter->buffer.data) + return -EINVAL; + + if (!bytes_count) + return 0; + + buff_fullness = dvb_ringbuffer_avail(&dmxdevfilter->buffer); + + if (bytes_count > buff_fullness) + return -EINVAL; + + DVB_RINGBUFFER_SKIP(&dmxdevfilter->buffer, bytes_count); + + wake_up_all(&dmxdevfilter->buffer.queue); return 0; } @@ -335,7 +895,7 @@ static void dvb_dmxdev_filter_timeout(unsigned long data) spin_lock_irq(&dmxdevfilter->dev->lock); dmxdevfilter->state = DMXDEV_STATE_TIMEDOUT; spin_unlock_irq(&dmxdevfilter->dev->lock); - wake_up(&dmxdevfilter->buffer.queue); + wake_up_all(&dmxdevfilter->buffer.queue); } static void dvb_dmxdev_filter_timer(struct dmxdev_filter *dmxdevfilter) @@ -361,7 +921,7 @@ static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, int ret; if (dmxdevfilter->buffer.error) { - wake_up(&dmxdevfilter->buffer.queue); + wake_up_all(&dmxdevfilter->buffer.queue); return 0; } spin_lock(&dmxdevfilter->dev->lock); @@ -386,7 +946,7 @@ static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, if (dmxdevfilter->params.sec.flags & DMX_ONESHOT) dmxdevfilter->state = DMXDEV_STATE_DONE; spin_unlock(&dmxdevfilter->dev->lock); - wake_up(&dmxdevfilter->buffer.queue); + wake_up_all(&dmxdevfilter->buffer.queue); return 0; } @@ -401,18 +961,34 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, spin_lock(&dmxdevfilter->dev->lock); if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) { - spin_unlock(&dmxdevfilter->dev->lock); - return 0; - } - - if (dmxdevfilter->params.pes.output == DMX_OUT_TAP + if ((dmxdevfilter->dev->capabilities & + DMXDEV_CAP_PCR_EXTRACTION) && + ((dmxdevfilter->params.pes.pes_type == DMX_PES_PCR0) || + (dmxdevfilter->params.pes.pes_type == DMX_PES_PCR1) || + (dmxdevfilter->params.pes.pes_type == DMX_PES_PCR2) || + (dmxdevfilter->params.pes.pes_type == DMX_PES_PCR3))) { + /* + * Support for reporting PCR and STC pairs to user. + * Reported data should have the following format: + * <8 bit flags><64 bits of STC> <64bits of PCR> + * STC and PCR values are in 27MHz. + * The current flags that are defined: + * 0x00000001: discontinuity_indicator + */ + buffer = &dmxdevfilter->buffer; + } else { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + } else if (dmxdevfilter->params.pes.output == DMX_OUT_TAP || dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) buffer = &dmxdevfilter->buffer; else buffer = &dmxdevfilter->dev->dvr_buffer; + if (buffer->error) { spin_unlock(&dmxdevfilter->dev->lock); - wake_up(&buffer->queue); + wake_up_all(&buffer->queue); return 0; } ret = dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); @@ -423,7 +999,7 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, buffer->error = ret; } spin_unlock(&dmxdevfilter->dev->lock); - wake_up(&buffer->queue); + wake_up_all(&buffer->queue); return 0; } @@ -533,6 +1109,8 @@ static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) } dvb_ringbuffer_flush(&dmxdevfilter->buffer); + wake_up_all(&dmxdevfilter->buffer.queue); + return 0; } @@ -599,12 +1177,32 @@ static int dvb_dmxdev_start_feed(struct dmxdev *dmxdev, tsfeed = feed->ts; tsfeed->priv = filter; - ret = tsfeed->set(tsfeed, feed->pid, ts_type, ts_pes, 32768, timeout); + ret = tsfeed->set(tsfeed, feed->pid, + ts_type, ts_pes, + filter->pes_buffer_size, timeout); if (ret < 0) { dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); return ret; } + /* Support indexing for video PES */ + if ((para->pes_type == DMX_PES_VIDEO0) || + (para->pes_type == DMX_PES_VIDEO1) || + (para->pes_type == DMX_PES_VIDEO2) || + (para->pes_type == DMX_PES_VIDEO3)) { + + if (tsfeed->set_indexing_params) { + ret = tsfeed->set_indexing_params(tsfeed, + ¶->video_params); + + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, + tsfeed); + return ret; + } + } + } + ret = tsfeed->start_filtering(tsfeed); if (ret < 0) { dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); @@ -628,7 +1226,7 @@ static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) dvb_dmxdev_filter_stop(filter); if (!filter->buffer.data) { - mem = vmalloc(filter->buffer.size); + mem = vmalloc_user(filter->buffer.size); if (!mem) return -ENOMEM; spin_lock_irq(&filter->dev->lock); @@ -648,7 +1246,6 @@ static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) *secfilter = NULL; *secfeed = NULL; - /* find active filter/feed with same PID */ for (i = 0; i < dmxdev->filternum; i++) { if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && @@ -762,6 +1359,8 @@ static int dvb_demux_open(struct inode *inode, struct file *file) dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); init_timer(&dmxdevfilter->timer); + dmxdevfilter->pes_buffer_size = 32768; + dvbdev->users++; mutex_unlock(&dmxdev->mutex); @@ -787,7 +1386,7 @@ static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, } dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); - wake_up(&dmxdevfilter->buffer.queue); + wake_up_all(&dmxdevfilter->buffer.queue); mutex_unlock(&dmxdevfilter->mutex); mutex_unlock(&dmxdev->mutex); return 0; @@ -882,6 +1481,23 @@ static int dvb_dmxdev_pes_filter_set(struct dmxdev *dmxdev, if (params->pes_type > DMX_PES_OTHER || params->pes_type < 0) return -EINVAL; + if (params->flags & DMX_ENABLE_INDEXING) { + if (!(dmxdev->capabilities & DMXDEV_CAP_INDEXING)) + return -EINVAL; + + /* can do indexing only on video PES */ + if ((params->pes_type != DMX_PES_VIDEO0) && + (params->pes_type != DMX_PES_VIDEO1) && + (params->pes_type != DMX_PES_VIDEO2) && + (params->pes_type != DMX_PES_VIDEO3)) + return -EINVAL; + + /* can do indexing only when recording */ + if ((params->output != DMX_OUT_TS_TAP) && + (params->output != DMX_OUT_TSDEMUX_TAP)) + return -EINVAL; + } + dmxdevfilter->type = DMXDEV_TYPE_PES; memcpy(&dmxdevfilter->params, params, sizeof(struct dmx_pes_filter_params)); @@ -1022,6 +1638,24 @@ static int dvb_demux_do_ioctl(struct file *file, mutex_unlock(&dmxdevfilter->mutex); break; + case DMX_GET_BUFFER_STATUS: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_get_buffer_status(dmxdevfilter, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_RELEASE_DATA: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_release_data(dmxdevfilter, arg); + mutex_unlock(&dmxdevfilter->mutex); + break; + case DMX_GET_PES_PIDS: if (!dmxdev->demux->get_pes_pids) { ret = -EINVAL; @@ -1039,11 +1673,59 @@ static int dvb_demux_do_ioctl(struct file *file, break; case DMX_SET_SOURCE: - if (!dmxdev->demux->set_source) { + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_set_source(dmxdevfilter, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_TS_PACKET_FORMAT: + if (!dmxdev->demux->set_tsp_format) { ret = -EINVAL; break; } - ret = dmxdev->demux->set_source(dmxdev->demux, parg); + + if (dmxdevfilter->state >= DMXDEV_STATE_GO) { + ret = -EBUSY; + break; + } + ret = dmxdev->demux->set_tsp_format( + dmxdev->demux, + *(enum dmx_tsp_format_t *)parg); + break; + + case DMX_SET_TS_OUT_FORMAT: + if (!dmxdev->demux->set_tsp_out_format) { + ret = -EINVAL; + break; + } + + if (dmxdevfilter->state >= DMXDEV_STATE_GO) { + ret = -EBUSY; + break; + } + + ret = dmxdev->demux->set_tsp_out_format( + dmxdev->demux, + *(enum dmx_tsp_format_t *)parg); + break; + + case DMX_SET_DECODER_BUFFER_SIZE: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + + ret = dvb_dmxdev_set_pes_buffer_size(dmxdevfilter, arg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_PLAYBACK_MODE: + ret = dvb_dmxdev_set_playback_mode( + dmxdevfilter, + *(enum dmx_playback_mode_t *)parg); break; case DMX_GET_STC: @@ -1113,6 +1795,59 @@ static unsigned int dvb_demux_poll(struct file *file, poll_table *wait) return mask; } +static int dvb_demux_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct dmxdev_filter *dmxdevfilter = filp->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + int ret; + int vma_size; + int buffer_size; + + vma_size = vma->vm_end - vma->vm_start; + + if (vma->vm_flags & VM_WRITE) + return -EINVAL; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + + if (!dmxdevfilter->buffer.data) { + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + return -EINVAL; + } + + /* Make sure requested mapping is not larger than buffer size */ + buffer_size = dmxdevfilter->buffer.size + (PAGE_SIZE-1); + buffer_size = buffer_size & ~(PAGE_SIZE-1); + + if (vma_size != buffer_size) { + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + return -EINVAL; + } + + ret = remap_vmalloc_range(vma, dmxdevfilter->buffer.data, 0); + if (ret) { + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + return ret; + } + + vma->vm_flags |= VM_RESERVED; + vma->vm_flags |= VM_DONTEXPAND; + + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + + return 0; +} + static int dvb_demux_release(struct inode *inode, struct file *file) { struct dmxdev_filter *dmxdevfilter = file->private_data; @@ -1143,6 +1878,7 @@ static const struct file_operations dvb_demux_fops = { .release = dvb_demux_release, .poll = dvb_demux_poll, .llseek = default_llseek, + .mmap = dvb_demux_mmap, }; static struct dvb_device dvbdev_demux = { @@ -1165,7 +1901,19 @@ static int dvb_dvr_do_ioctl(struct file *file, switch (cmd) { case DMX_SET_BUFFER_SIZE: - ret = dvb_dvr_set_buffer_size(dmxdev, arg); + ret = dvb_dvr_set_buffer_size(dmxdev, file->f_flags, arg); + break; + + case DMX_GET_BUFFER_STATUS: + ret = dvb_dvr_get_buffer_status(dmxdev, file->f_flags, parg); + break; + + case DMX_RELEASE_DATA: + ret = dvb_dvr_release_data(dmxdev, file->f_flags, arg); + break; + + case DMX_FEED_DATA: + ret = dvb_dvr_feed_data(dmxdev, file->f_flags, arg); break; default: @@ -1190,16 +1938,22 @@ static unsigned int dvb_dvr_poll(struct file *file, poll_table *wait) dprintk("function : %s\n", __func__); - poll_wait(file, &dmxdev->dvr_buffer.queue, wait); - if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + poll_wait(file, &dmxdev->dvr_buffer.queue, wait); + if (dmxdev->dvr_buffer.error) mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) mask |= (POLLIN | POLLRDNORM | POLLPRI); - } else - mask |= (POLLOUT | POLLWRNORM | POLLPRI); + } else { + poll_wait(file, &dmxdev->dvr_input_buffer.queue, wait); + if (dmxdev->dvr_input_buffer.error) + mask |= (POLLOUT | POLLRDNORM | POLLPRI | POLLERR); + + if (dvb_ringbuffer_free(&dmxdev->dvr_input_buffer)) + mask |= (POLLOUT | POLLRDNORM | POLLPRI); + } return mask; } @@ -1208,6 +1962,7 @@ static const struct file_operations dvb_dvr_fops = { .owner = THIS_MODULE, .read = dvb_dvr_read, .write = dvb_dvr_write, + .mmap = dvb_dvr_mmap, .unlocked_ioctl = dvb_dvr_ioctl, .open = dvb_dvr_open, .release = dvb_dvr_release, @@ -1233,8 +1988,19 @@ int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) if (!dmxdev->filter) return -ENOMEM; + dmxdev->dvr_input_workqueue = + create_singlethread_workqueue("dvr_workqueue"); + + if (dmxdev->dvr_input_workqueue == NULL) { + vfree(dmxdev->filter); + return -ENOMEM; + } + + dmxdev->playback_mode = DMX_PB_MODE_PUSH; + mutex_init(&dmxdev->mutex); spin_lock_init(&dmxdev->lock); + spin_lock_init(&dmxdev->dvr_in_lock); for (i = 0; i < dmxdev->filternum; i++) { dmxdev->filter[i].dev = dmxdev; dmxdev->filter[i].buffer.data = NULL; @@ -1248,6 +2014,10 @@ int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) dmxdev, DVB_DEVICE_DVR); dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); + dvb_ringbuffer_init(&dmxdev->dvr_input_buffer, NULL, 8192); + + INIT_WORK(&dmxdev->dvr_input_work, + dvr_input_work_func); return 0; } @@ -1266,6 +2036,9 @@ void dvb_dmxdev_release(struct dmxdev *dmxdev) dmxdev->dvr_dvbdev->users==1); } + flush_workqueue(dmxdev->dvr_input_workqueue); + destroy_workqueue(dmxdev->dvr_input_workqueue); + dvb_unregister_device(dmxdev->dvbdev); dvb_unregister_device(dmxdev->dvr_dvbdev); diff --git a/drivers/media/dvb/dvb-core/dmxdev.h b/drivers/media/dvb/dvb-core/dmxdev.h index 02ebe28f830d811b9ea7079ad450cc626a6b3863..4c52e84c876e57413504d42ac62f4c47410085a5 100644 --- a/drivers/media/dvb/dvb-core/dmxdev.h +++ b/drivers/media/dvb/dvb-core/dmxdev.h @@ -4,6 +4,8 @@ * Copyright (C) 2000 Ralph Metzler & Marcus Metzler * for convergence integrated media GmbH * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 @@ -32,7 +34,7 @@ #include #include #include - +#include #include #include "dvbdev.h" @@ -83,14 +85,18 @@ struct dmxdev_filter { struct mutex mutex; + /* relevent for decoder PES */ + unsigned long pes_buffer_size; + /* only for sections */ struct timer_list timer; int todo; u8 secheader[3]; }; - struct dmxdev { + struct work_struct dvr_input_work; + struct dvb_device *dvbdev; struct dvb_device *dvr_dvbdev; @@ -99,16 +105,29 @@ struct dmxdev { int filternum; int capabilities; +#define DMXDEV_CAP_DUPLEX 0x1 +#define DMXDEV_CAP_PULL_MODE 0x2 +#define DMXDEV_CAP_PCR_EXTRACTION 0x4 +#define DMXDEV_CAP_INDEXING 0x8 + + enum dmx_playback_mode_t playback_mode; + dmx_source_t source; unsigned int exit:1; -#define DMXDEV_CAP_DUPLEX 1 + unsigned int dvr_in_exit:1; + unsigned int dvr_processing_input:1; + struct dmx_frontend *dvr_orig_fe; struct dvb_ringbuffer dvr_buffer; + struct dvb_ringbuffer dvr_input_buffer; + struct workqueue_struct *dvr_input_workqueue; + #define DVR_BUFFER_SIZE (10*188*1024) struct mutex mutex; spinlock_t lock; + spinlock_t dvr_in_lock; }; diff --git a/drivers/media/dvb/dvb-core/dvb_demux.c b/drivers/media/dvb/dvb-core/dvb_demux.c index faa3671b649e1dd54214abf5f7bce1c4044c2bac..0ff2a5549b2f4a6ef512e6ca7a188d9aeb38eda8 100644 --- a/drivers/media/dvb/dvb-core/dvb_demux.c +++ b/drivers/media/dvb/dvb-core/dvb_demux.c @@ -5,6 +5,8 @@ * & Marcus Metzler * for convergence integrated media GmbH * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 @@ -50,6 +52,14 @@ module_param(dvb_demux_speedcheck, int, 0644); MODULE_PARM_DESC(dvb_demux_speedcheck, "enable transport stream speed check"); +/* counter advancing for each new dvb-demux device */ +static int dvb_demux_index; + +static int dvb_demux_performancecheck; +module_param(dvb_demux_performancecheck, int, 0644); +MODULE_PARM_DESC(dvb_demux_performancecheck, + "enable transport stream performance check, reported through debugfs"); + #define dprintk_tscheck(x...) do { \ if (dvb_demux_tscheck && printk_ratelimit()) \ printk(x); \ @@ -95,6 +105,19 @@ static void dvb_dmx_memcopy(struct dvb_demux_feed *f, u8 *d, const u8 *s, memcpy(d, s, len); } +static u32 dvb_dmx_calc_time_delta(struct timespec past_time) +{ + struct timespec curr_time, delta_time; + u64 delta_time_us; + + curr_time = current_kernel_time(); + delta_time = timespec_sub(curr_time, past_time); + delta_time_us = ((s64)delta_time.tv_sec * USEC_PER_SEC) + + delta_time.tv_nsec / 1000; + + return (u32)delta_time_us; +} + /****************************************************************************** * Software filter functions ******************************************************************************/ @@ -120,8 +143,14 @@ static inline int dvb_dmx_swfilter_payload(struct dvb_demux_feed *feed, printk("missed packet!\n"); */ - if (buf[1] & 0x40) // PUSI ? + /* PUSI ? */ + if (buf[1] & 0x40) { feed->peslen = 0xfffa; + feed->pusi_seen = 1; + } + + if (feed->pusi_seen == 0) + return 0; feed->peslen += count; @@ -164,10 +193,23 @@ static inline int dvb_dmx_swfilter_section_feed(struct dvb_demux_feed *feed) return 0; if (sec->check_crc) { + struct timespec pre_crc_time; + + if (dvb_demux_performancecheck) + pre_crc_time = current_kernel_time(); + section_syntax_indicator = ((sec->secbuf[1] & 0x80) != 0); if (section_syntax_indicator && - demux->check_crc32(feed, sec->secbuf, sec->seclen)) + demux->check_crc32(feed, sec->secbuf, sec->seclen)) { + if (dvb_demux_performancecheck) + demux->total_crc_time += + dvb_dmx_calc_time_delta(pre_crc_time); return -1; + } + + if (dvb_demux_performancecheck) + demux->total_crc_time += + dvb_dmx_calc_time_delta(pre_crc_time); } do { @@ -351,6 +393,161 @@ static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, return 0; } +static inline void dvb_dmx_swfilter_output_packet( + struct dvb_demux_feed *feed, + const u8 *buf) +{ + u8 time_stamp[4] = {0}; + struct dvb_demux *demux = feed->demux; + + /* + * if we output 192 packet with timestamp at head of packet, + * output the timestamp now before the 188 TS packet + */ + if (demux->tsp_out_format == DMX_TSP_FORMAT_192_HEAD) + feed->cb.ts(time_stamp, 4, NULL, 0, &feed->feed.ts, DMX_OK); + + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, DMX_OK); + + /* + * if we output 192 packet with timestamp at tail of packet, + * output the timestamp now after the 188 TS packet + */ + if (demux->tsp_out_format == DMX_TSP_FORMAT_192_TAIL) + feed->cb.ts(time_stamp, 4, NULL, 0, &feed->feed.ts, DMX_OK); +} + +static inline void dvb_dmx_configure_decoder_fullness( + struct dvb_demux *demux, + int initialize) +{ + struct dvb_demux_feed *feed; + int j; + + for (j = 0; j < demux->feednum; j++) { + feed = &demux->feed[j]; + + if ((feed->state != DMX_STATE_GO) || + (feed->type != DMX_TYPE_TS) || + !(feed->ts_type & TS_DECODER)) + continue; + + if (initialize) { + if (demux->decoder_fullness_init) + demux->decoder_fullness_init(feed); + } else { + if (demux->decoder_fullness_abort) + demux->decoder_fullness_abort(feed); + } + } +} + +static inline int dvb_dmx_swfilter_buffer_check( + struct dvb_demux *demux, + u16 pid) +{ + int desired_space; + int ret; + struct dmx_ts_feed *ts; + struct dvb_demux_filter *f; + struct dvb_demux_feed *feed; + int was_locked; + int i, j; + + if (likely(spin_is_locked(&demux->lock))) + was_locked = 1; + else + was_locked = 0; + + /* + * Check that there's enough free space for data output. + * If there no space, wait for it (block). + * Since this function is called while spinlock + * is aquired, the lock should be released first. + * Once we get control back, lock is aquired back + * and checks that the filter is still valid. + */ + for (j = 0; j < demux->feednum; j++) { + feed = &demux->feed[j]; + + if (demux->sw_filter_abort) + return -ENODEV; + + if ((feed->state != DMX_STATE_GO) || + ((feed->pid != pid) && (feed->pid != 0x2000))) + continue; + + if (feed->type == DMX_TYPE_TS) { + desired_space = 192; /* upper bound */ + ts = &feed->feed.ts; + + if (feed->ts_type & TS_PACKET) { + if (likely(was_locked)) + spin_unlock(&demux->lock); + + ret = demux->buffer_ctrl.ts(ts, desired_space); + + if (likely(was_locked)) + spin_lock(&demux->lock); + + if (ret < 0) + continue; + } + + if (demux->sw_filter_abort) + return -ENODEV; + + if (!ts->is_filtering) + continue; + + if ((feed->ts_type & TS_DECODER) && + (demux->decoder_fullness_wait)) { + if (likely(was_locked)) + spin_unlock(&demux->lock); + + ret = demux->decoder_fullness_wait( + feed, + desired_space); + + if (likely(was_locked)) + spin_lock(&demux->lock); + + if (ret < 0) + continue; + } + + continue; + } + + /* else - section case */ + desired_space = feed->feed.sec.tsfeedp + 188; /* upper bound */ + for (i = 0; i < demux->filternum; i++) { + if (demux->sw_filter_abort) + return -EPERM; + + if (!feed->feed.sec.is_filtering) + continue; + + f = &demux->filter[i]; + if (f->feed != feed) + continue; + + if (likely(was_locked)) + spin_unlock(&demux->lock); + + ret = demux->buffer_ctrl.sec(&f->filter, desired_space); + + if (likely(was_locked)) + spin_lock(&demux->lock); + + if (ret < 0) + break; + } + } + + return 0; +} + static inline void dvb_dmx_swfilter_packet_type(struct dvb_demux_feed *feed, const u8 *buf) { @@ -362,8 +559,7 @@ static inline void dvb_dmx_swfilter_packet_type(struct dvb_demux_feed *feed, if (feed->ts_type & TS_PAYLOAD_ONLY) dvb_dmx_swfilter_payload(feed, buf); else - feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, - DMX_OK); + dvb_dmx_swfilter_output_packet(feed, buf); } if (feed->ts_type & TS_DECODER) if (feed->demux->write_to_decoder) @@ -446,6 +642,10 @@ static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) /* end check */ }; + if (demux->playback_mode == DMX_PB_MODE_PULL) + if (dvb_dmx_swfilter_buffer_check(demux, pid) < 0) + return; + list_for_each_entry(feed, &demux->feed_list, list_head) { if ((feed->pid != pid) && (feed->pid != 0x2000)) continue; @@ -457,16 +657,25 @@ static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) if (feed->pid == pid) dvb_dmx_swfilter_packet_type(feed, buf); - else if (feed->pid == 0x2000) - feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, DMX_OK); + else if ((feed->pid == 0x2000) && + (feed->feed.ts.is_filtering)) + dvb_dmx_swfilter_output_packet(feed, buf); } } void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, size_t count) { + struct timespec pre_time; + + if (dvb_demux_performancecheck) + pre_time = current_kernel_time(); + spin_lock(&demux->lock); + demux->sw_filter_abort = 0; + dvb_dmx_configure_decoder_fullness(demux, 1); + while (count--) { if (buf[0] == 0x47) dvb_dmx_swfilter_packet(demux, buf); @@ -474,18 +683,24 @@ void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, } spin_unlock(&demux->lock); + + if (dvb_demux_performancecheck) + demux->total_process_time += dvb_dmx_calc_time_delta(pre_time); } EXPORT_SYMBOL(dvb_dmx_swfilter_packets); static inline int find_next_packet(const u8 *buf, int pos, size_t count, - const int pktsize) + const int pktsize, const int leadingbytes) { int start = pos, lost; while (pos < count) { - if (buf[pos] == 0x47 || - (pktsize == 204 && buf[pos] == 0xB8)) + if ((buf[pos] == 0x47 && !leadingbytes) || + (pktsize == 204 && buf[pos] == 0xB8) || + (pktsize == 192 && leadingbytes && + (pos+leadingbytes < count) && + buf[pos+leadingbytes] == 0x47)) break; pos++; } @@ -495,7 +710,9 @@ static inline int find_next_packet(const u8 *buf, int pos, size_t count, /* This garbage is part of a valid packet? */ int backtrack = pos - pktsize; if (backtrack >= 0 && (buf[backtrack] == 0x47 || - (pktsize == 204 && buf[backtrack] == 0xB8))) + (pktsize == 204 && buf[backtrack] == 0xB8) || + (pktsize == 192 && + buf[backtrack+leadingbytes] == 0x47))) return backtrack; } @@ -504,13 +721,20 @@ static inline int find_next_packet(const u8 *buf, int pos, size_t count, /* Filter all pktsize= 188 or 204 sized packets and skip garbage. */ static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, - size_t count, const int pktsize) + size_t count, const int pktsize, const int leadingbytes) { int p = 0, i, j; const u8 *q; + struct timespec pre_time; + + if (dvb_demux_performancecheck) + pre_time = current_kernel_time(); spin_lock(&demux->lock); + demux->sw_filter_abort = 0; + dvb_dmx_configure_decoder_fullness(demux, 1); + if (demux->tsbufp) { /* tsbuf[0] is now 0x47. */ i = demux->tsbufp; j = pktsize - i; @@ -520,14 +744,22 @@ static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, goto bailout; } memcpy(&demux->tsbuf[i], buf, j); - if (demux->tsbuf[0] == 0x47) /* double check */ + if (pktsize == 192 && + leadingbytes && + demux->tsbuf[leadingbytes] == 0x47) /* double check */ + dvb_dmx_swfilter_packet(demux, demux->tsbuf+4); + else if (demux->tsbuf[0] == 0x47) /* double check */ dvb_dmx_swfilter_packet(demux, demux->tsbuf); demux->tsbufp = 0; p += j; } while (1) { - p = find_next_packet(buf, p, count, pktsize); + p = find_next_packet(buf, p, count, pktsize, leadingbytes); + + if (demux->sw_filter_abort) + goto bailout; + if (p >= count) break; if (count - p < pktsize) @@ -540,6 +772,10 @@ static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, demux->tsbuf[0] = 0x47; q = demux->tsbuf; } + + if (pktsize == 192 && leadingbytes) + q = &buf[p+leadingbytes]; + dvb_dmx_swfilter_packet(demux, q); p += pktsize; } @@ -554,20 +790,55 @@ static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, bailout: spin_unlock(&demux->lock); + + if (dvb_demux_performancecheck) + demux->total_process_time += dvb_dmx_calc_time_delta(pre_time); } void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) { - _dvb_dmx_swfilter(demux, buf, count, 188); + _dvb_dmx_swfilter(demux, buf, count, 188, 0); } EXPORT_SYMBOL(dvb_dmx_swfilter); void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count) { - _dvb_dmx_swfilter(demux, buf, count, 204); + _dvb_dmx_swfilter(demux, buf, count, 204, 0); } EXPORT_SYMBOL(dvb_dmx_swfilter_204); +void dvb_dmx_swfilter_format( + struct dvb_demux *demux, + const u8 *buf, + size_t count, + enum dmx_tsp_format_t tsp_format) +{ + switch (tsp_format) { + case DMX_TSP_FORMAT_188: + _dvb_dmx_swfilter(demux, buf, count, 188, 0); + break; + + case DMX_TSP_FORMAT_192_TAIL: + _dvb_dmx_swfilter(demux, buf, count, 192, 0); + break; + + case DMX_TSP_FORMAT_192_HEAD: + _dvb_dmx_swfilter(demux, buf, count, 192, 4); + break; + + case DMX_TSP_FORMAT_204: + _dvb_dmx_swfilter(demux, buf, count, 204, 0); + break; + + default: + printk(KERN_ERR "%s: invalid TS packet format (format=%d)\n", + __func__, + tsp_format); + break; + } +} +EXPORT_SYMBOL(dvb_dmx_swfilter_format); + static struct dvb_demux_filter *dvb_dmx_filter_alloc(struct dvb_demux *demux) { int i; @@ -756,6 +1027,18 @@ static int dmx_ts_feed_stop_filtering(struct dmx_ts_feed *ts_feed) return ret; } +static int dmx_ts_set_indexing_params( + struct dmx_ts_feed *ts_feed, + struct dmx_indexing_video_params *params) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + + memcpy(&feed->indexing_params, params, + sizeof(struct dmx_indexing_video_params)); + + return 0; +} + static int dvbdmx_allocate_ts_feed(struct dmx_demux *dmx, struct dmx_ts_feed **ts_feed, dmx_ts_cb callback) @@ -777,6 +1060,15 @@ static int dvbdmx_allocate_ts_feed(struct dmx_demux *dmx, feed->pid = 0xffff; feed->peslen = 0xfffa; feed->buffer = NULL; + memset(&feed->indexing_params, 0, + sizeof(struct dmx_indexing_video_params)); + + /* default behaviour - pass first PES data even if it is + * partial PES data from previous PES that we didn't receive its header. + * Override this to 0 in your start_feed function in order to handle + * first PES differently. + */ + feed->pusi_seen = 1; (*ts_feed) = &feed->feed.ts; (*ts_feed)->parent = dmx; @@ -785,6 +1077,7 @@ static int dvbdmx_allocate_ts_feed(struct dmx_demux *dmx, (*ts_feed)->start_filtering = dmx_ts_feed_start_filtering; (*ts_feed)->stop_filtering = dmx_ts_feed_stop_filtering; (*ts_feed)->set = dmx_ts_feed_set; + (*ts_feed)->set_indexing_params = dmx_ts_set_indexing_params; if (!(feed->filter = dvb_dmx_filter_alloc(demux))) { feed->state = DMX_STATE_FREE; @@ -1119,30 +1412,54 @@ static int dvbdmx_close(struct dmx_demux *demux) return 0; } -static int dvbdmx_write(struct dmx_demux *demux, const char __user *buf, size_t count) +static int dvbdmx_write(struct dmx_demux *demux, const char *buf, size_t count) { struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - void *p; if ((!demux->frontend) || (demux->frontend->source != DMX_MEMORY_FE)) return -EINVAL; - p = memdup_user(buf, count); - if (IS_ERR(p)) - return PTR_ERR(p); - if (mutex_lock_interruptible(&dvbdemux->mutex)) { - kfree(p); - return -ERESTARTSYS; - } - dvb_dmx_swfilter(dvbdemux, p, count); - kfree(p); - mutex_unlock(&dvbdemux->mutex); + dvb_dmx_swfilter_format(dvbdemux, buf, count, dvbdemux->tsp_format); if (signal_pending(current)) return -EINTR; return count; } +static int dvbdmx_write_cancel(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; + + spin_lock_irq(&dvbdmx->lock); + + /* cancel any pending wait for decoder's buffers */ + dvbdmx->sw_filter_abort = 1; + dvbdmx->tsbufp = 0; + dvb_dmx_configure_decoder_fullness(dvbdmx, 0); + + spin_unlock_irq(&dvbdmx->lock); + + return 0; +} + +static int dvbdmx_set_playback_mode(struct dmx_demux *demux, + enum dmx_playback_mode_t mode, + dmx_ts_fullness ts_fullness_callback, + dmx_section_fullness sec_fullness_callback) +{ + struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; + + mutex_lock(&dvbdmx->mutex); + + dvbdmx->playback_mode = mode; + dvbdmx->buffer_ctrl.ts = ts_fullness_callback; + dvbdmx->buffer_ctrl.sec = sec_fullness_callback; + + mutex_unlock(&dvbdmx->mutex); + + return 0; +} + static int dvbdmx_add_frontend(struct dmx_demux *demux, struct dmx_frontend *frontend) { @@ -1214,6 +1531,40 @@ static int dvbdmx_get_pes_pids(struct dmx_demux *demux, u16 * pids) return 0; } +static int dvbdmx_set_tsp_format( + struct dmx_demux *demux, + enum dmx_tsp_format_t tsp_format) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if ((tsp_format > DMX_TSP_FORMAT_204) || + (tsp_format < DMX_TSP_FORMAT_188)) + return -EINVAL; + + mutex_lock(&dvbdemux->mutex); + + dvbdemux->tsp_format = tsp_format; + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dvbdmx_set_tsp_out_format( + struct dmx_demux *demux, + enum dmx_tsp_format_t tsp_format) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if ((tsp_format > DMX_TSP_FORMAT_192_HEAD) || + (tsp_format < DMX_TSP_FORMAT_188)) + return -EINVAL; + + mutex_lock(&dvbdemux->mutex); + + dvbdemux->tsp_out_format = tsp_format; + mutex_unlock(&dvbdemux->mutex); + return 0; +} + int dvb_dmx_init(struct dvb_demux *dvbdemux) { int i; @@ -1232,6 +1583,30 @@ int dvb_dmx_init(struct dvb_demux *dvbdemux) dvbdemux->filter = NULL; return -ENOMEM; } + + dvbdemux->total_process_time = 0; + dvbdemux->total_crc_time = 0; + snprintf(dvbdemux->alias, + MAX_DVB_DEMUX_NAME_LEN, + "demux%d", + dvb_demux_index++); + + dvbdemux->debugfs_demux_dir = debugfs_create_dir(dvbdemux->alias, NULL); + + if (dvbdemux->debugfs_demux_dir != NULL) { + debugfs_create_u32( + "total_processing_time", + S_IRUGO|S_IWUGO, + dvbdemux->debugfs_demux_dir, + &dvbdemux->total_process_time); + + debugfs_create_u32( + "total_crc_time", + S_IRUGO|S_IWUGO, + dvbdemux->debugfs_demux_dir, + &dvbdemux->total_crc_time); + } + for (i = 0; i < dvbdemux->filternum; i++) { dvbdemux->filter[i].state = DMX_STATE_FREE; dvbdemux->filter[i].index = i; @@ -1258,6 +1633,9 @@ int dvb_dmx_init(struct dvb_demux *dvbdemux) dvbdemux->recording = 0; dvbdemux->tsbufp = 0; + dvbdemux->tsp_format = DMX_TSP_FORMAT_188; + dvbdemux->tsp_out_format = DMX_TSP_FORMAT_188; + if (!dvbdemux->check_crc32) dvbdemux->check_crc32 = dvb_dmx_crc32; @@ -1269,6 +1647,8 @@ int dvb_dmx_init(struct dvb_demux *dvbdemux) dmx->open = dvbdmx_open; dmx->close = dvbdmx_close; dmx->write = dvbdmx_write; + dmx->write_cancel = dvbdmx_write_cancel; + dmx->set_playback_mode = dvbdmx_set_playback_mode; dmx->allocate_ts_feed = dvbdmx_allocate_ts_feed; dmx->release_ts_feed = dvbdmx_release_ts_feed; dmx->allocate_section_feed = dvbdmx_allocate_section_feed; @@ -1281,6 +1661,9 @@ int dvb_dmx_init(struct dvb_demux *dvbdemux) dmx->disconnect_frontend = dvbdmx_disconnect_frontend; dmx->get_pes_pids = dvbdmx_get_pes_pids; + dmx->set_tsp_format = dvbdmx_set_tsp_format; + dmx->set_tsp_out_format = dvbdmx_set_tsp_out_format; + mutex_init(&dvbdemux->mutex); spin_lock_init(&dvbdemux->lock); @@ -1291,6 +1674,9 @@ EXPORT_SYMBOL(dvb_dmx_init); void dvb_dmx_release(struct dvb_demux *dvbdemux) { + if (dvbdemux->debugfs_demux_dir != NULL) + debugfs_remove_recursive(dvbdemux->debugfs_demux_dir); + vfree(dvbdemux->cnt_storage); vfree(dvbdemux->filter); vfree(dvbdemux->feed); diff --git a/drivers/media/dvb/dvb-core/dvb_demux.h b/drivers/media/dvb/dvb-core/dvb_demux.h index a7d876fd02dd8067cf722488c3902699e196fb56..17f4960ae44e72c9e8f301ac727fb6b89565399b 100644 --- a/drivers/media/dvb/dvb-core/dvb_demux.h +++ b/drivers/media/dvb/dvb-core/dvb_demux.h @@ -4,6 +4,8 @@ * Copyright (C) 2000-2001 Marcus Metzler & Ralph Metzler * for convergence integrated media GmbH * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 @@ -27,6 +29,7 @@ #include #include #include +#include #include "demux.h" @@ -92,10 +95,12 @@ struct dvb_demux_feed { int cc; int pusi_seen; /* prevents feeding of garbage from previous section */ - u16 peslen; + u32 peslen; struct list_head list_head; unsigned int index; /* a unique index for each feed (can be used as hardware pid filter index) */ + + struct dmx_indexing_video_params indexing_params; }; struct dvb_demux { @@ -107,6 +112,10 @@ struct dvb_demux { int (*stop_feed)(struct dvb_demux_feed *feed); int (*write_to_decoder)(struct dvb_demux_feed *feed, const u8 *buf, size_t len); + int (*decoder_fullness_init)(struct dvb_demux_feed *feed); + int (*decoder_fullness_wait)(struct dvb_demux_feed *feed, + size_t required_space); + int (*decoder_fullness_abort)(struct dvb_demux_feed *feed); u32 (*check_crc32)(struct dvb_demux_feed *feed, const u8 *buf, size_t len); void (*memcopy)(struct dvb_demux_feed *feed, u8 *dst, @@ -136,6 +145,28 @@ struct dvb_demux { struct timespec speed_last_time; /* for TS speed check */ uint32_t speed_pkts_cnt; /* for TS speed check */ + + enum dmx_tsp_format_t tsp_format; + enum dmx_tsp_format_t tsp_out_format; + + enum dmx_playback_mode_t playback_mode; + int sw_filter_abort; + + struct { + dmx_ts_fullness ts; + dmx_section_fullness sec; + } buffer_ctrl; + + /* + * the following is used for debugfs exposing info + * about dvb demux performance. + */ +#define MAX_DVB_DEMUX_NAME_LEN 10 + char alias[MAX_DVB_DEMUX_NAME_LEN]; + + u32 total_process_time; + u32 total_crc_time; + struct dentry *debugfs_demux_dir; }; int dvb_dmx_init(struct dvb_demux *dvbdemux); @@ -145,5 +176,10 @@ void dvb_dmx_swfilter_packets(struct dvb_demux *dvbdmx, const u8 *buf, void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count); void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count); +void dvb_dmx_swfilter_format( + struct dvb_demux *demux, const u8 *buf, + size_t count, + enum dmx_tsp_format_t tsp_format); + #endif /* _DVB_DEMUX_H_ */ diff --git a/drivers/media/dvb/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb/dvb-core/dvb_ringbuffer.c index a5712cd7c65ff084845cc86599d63f802ac81722..36cc475a2c936aa09ef051235c402cd3855321b3 100644 --- a/drivers/media/dvb/dvb-core/dvb_ringbuffer.c +++ b/drivers/media/dvb/dvb-core/dvb_ringbuffer.c @@ -5,6 +5,8 @@ * Copyright (C) 2003 Oliver Endriss * Copyright (C) 2004 Andrew de Quincey * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * based on code originally found in av7110.c & dvb_ci.c: * Copyright (C) 1999-2003 Ralph Metzler * & Marcus Metzler for convergence integrated media GmbH @@ -37,6 +39,8 @@ #define PKT_READY 0 #define PKT_DISPOSED 1 +#define PKT_PENDING 2 + void dvb_ringbuffer_init(struct dvb_ringbuffer *rbuf, void *data, size_t len) @@ -166,6 +170,35 @@ ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, size_t return len; } +ssize_t dvb_ringbuffer_write_user(struct dvb_ringbuffer *rbuf, + const u8 *buf, size_t len) +{ + size_t todo = len; + size_t split; + ssize_t oldpwrite = rbuf->pwrite; + + split = (rbuf->pwrite + len > rbuf->size) ? + rbuf->size - rbuf->pwrite : + 0; + + if (split > 0) { + if (copy_from_user(rbuf->data + rbuf->pwrite, buf, split)) + return -EFAULT; + buf += split; + todo -= split; + rbuf->pwrite = 0; + } + + if (copy_from_user(rbuf->data + rbuf->pwrite, buf, todo)) { + rbuf->pwrite = oldpwrite; + return -EFAULT; + } + + rbuf->pwrite = (rbuf->pwrite + todo) % rbuf->size; + + return len; +} + ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, size_t len) { int status; @@ -180,6 +213,31 @@ ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, size_t le return status; } +ssize_t dvb_ringbuffer_pkt_start(struct dvb_ringbuffer *rbuf, size_t len) +{ + ssize_t oldpwrite = rbuf->pwrite; + + DVB_RINGBUFFER_WRITE_BYTE(rbuf, len >> 8); + DVB_RINGBUFFER_WRITE_BYTE(rbuf, len & 0xff); + DVB_RINGBUFFER_WRITE_BYTE(rbuf, PKT_PENDING); + + return oldpwrite; +} +EXPORT_SYMBOL(dvb_ringbuffer_pkt_start); + +int dvb_ringbuffer_pkt_close(struct dvb_ringbuffer *rbuf, ssize_t idx) +{ + idx = (idx + 2) % rbuf->size; + + if (rbuf->data[idx] != PKT_PENDING) + return -EINVAL; + + rbuf->data[idx] = PKT_READY; + + return 0; +} +EXPORT_SYMBOL(dvb_ringbuffer_pkt_close); + ssize_t dvb_ringbuffer_pkt_read_user(struct dvb_ringbuffer *rbuf, size_t idx, int offset, u8 __user *buf, size_t len) { @@ -187,6 +245,9 @@ ssize_t dvb_ringbuffer_pkt_read_user(struct dvb_ringbuffer *rbuf, size_t idx, size_t split; size_t pktlen; + if (DVB_RINGBUFFER_PEEK(rbuf, (idx+2)) != PKT_READY) + return -EINVAL; + pktlen = rbuf->data[idx] << 8; pktlen |= rbuf->data[(idx + 1) % rbuf->size]; if (offset > pktlen) return -EINVAL; @@ -207,6 +268,7 @@ ssize_t dvb_ringbuffer_pkt_read_user(struct dvb_ringbuffer *rbuf, size_t idx, return len; } +EXPORT_SYMBOL(dvb_ringbuffer_pkt_read_user); ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, int offset, u8* buf, size_t len) @@ -215,9 +277,13 @@ ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, size_t split; size_t pktlen; + if (rbuf->data[(idx + 2) % rbuf->size] != PKT_READY) + return -EINVAL; + pktlen = rbuf->data[idx] << 8; pktlen |= rbuf->data[(idx + 1) % rbuf->size]; if (offset > pktlen) return -EINVAL; + if ((offset + len) > pktlen) len = pktlen - offset; idx = (idx + DVB_RINGBUFFER_PKTHDRSIZE + offset) % rbuf->size; @@ -232,6 +298,7 @@ ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, memcpy(buf, rbuf->data+idx, todo); return len; } +EXPORT_SYMBOL(dvb_ringbuffer_pkt_read); void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx) { @@ -251,6 +318,7 @@ void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx) } } } +EXPORT_SYMBOL(dvb_ringbuffer_pkt_dispose); ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* pktlen) { @@ -279,6 +347,9 @@ ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* return idx; } + if (curpktstatus == PKT_PENDING) + return -EFAULT; + consumed += curpktlen + DVB_RINGBUFFER_PKTHDRSIZE; idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; } @@ -286,8 +357,7 @@ ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* // no packets available return -1; } - - +EXPORT_SYMBOL(dvb_ringbuffer_pkt_next); EXPORT_SYMBOL(dvb_ringbuffer_init); EXPORT_SYMBOL(dvb_ringbuffer_empty); @@ -297,3 +367,5 @@ EXPORT_SYMBOL(dvb_ringbuffer_flush_spinlock_wakeup); EXPORT_SYMBOL(dvb_ringbuffer_read_user); EXPORT_SYMBOL(dvb_ringbuffer_read); EXPORT_SYMBOL(dvb_ringbuffer_write); +EXPORT_SYMBOL(dvb_ringbuffer_write_user); + diff --git a/drivers/media/dvb/dvb-core/dvb_ringbuffer.h b/drivers/media/dvb/dvb-core/dvb_ringbuffer.h index 41f04dae69b618badd91e783a1ad463b6bd0a393..8b591a6c31aa21d19ec3cbc832dfac6e914b49ed 100644 --- a/drivers/media/dvb/dvb-core/dvb_ringbuffer.h +++ b/drivers/media/dvb/dvb-core/dvb_ringbuffer.h @@ -5,6 +5,8 @@ * Copyright (C) 2003 Oliver Endriss * Copyright (C) 2004 Andrew de Quincey * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * based on code originally found in av7110.c & dvb_ci.c: * Copyright (C) 1999-2003 Ralph Metzler & Marcus Metzler * for convergence integrated media GmbH @@ -134,6 +136,8 @@ extern void dvb_ringbuffer_read(struct dvb_ringbuffer *rbuf, extern ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, size_t len); +extern ssize_t dvb_ringbuffer_write_user(struct dvb_ringbuffer *rbuf, + const u8 *buf, size_t len); /** * Write a packet into the ringbuffer. @@ -183,4 +187,30 @@ extern void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx); extern ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* pktlen); +/** + * Start a new packet that will be written directly by the user to the packet buffer. + * The function only writes the header of the packet into the packet buffer, + * and the packet is in pending state (can't be read by the reader) until it is + * closed using dvb_ringbuffer_pkt_close. You must write the data into the + * packet buffer using dvb_ringbuffer_write followed by + * dvb_ringbuffer_pkt_close. + * + * Ringbuffer concerned. + * Size of the packet's data + * returns Index of the packet's header that was started. + */ +extern ssize_t dvb_ringbuffer_pkt_start(struct dvb_ringbuffer *rbuf, + size_t len); + +/** + * Close a packet that was started using dvb_ringbuffer_pkt_start. + * The packet will be marked as ready to be ready. + * + * Ringbuffer concerned. + * Packet index that was returned by dvb_ringbuffer_pkt_start + * returns error status, -EINVAL if the provided index is invalid + */ +extern int dvb_ringbuffer_pkt_close(struct dvb_ringbuffer *rbuf, ssize_t idx); + + #endif /* _DVB_RINGBUFFER_H_ */ diff --git a/drivers/media/dvb/mpq/Kconfig b/drivers/media/dvb/mpq/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..868ad8cc26337cebeef6236abe4afba6c4045d2e --- /dev/null +++ b/drivers/media/dvb/mpq/Kconfig @@ -0,0 +1,12 @@ +config DVB_MPQ + tristate "Qualcomm Multimedia Processor DVB Adapter" + depends on ARCH_MSM && DVB_CORE + default n + + help + Support for Qualcomm MPQ based DVB adapter. + Say Y or M if you own such a device and want to use it. + +source "drivers/media/dvb/mpq/demux/Kconfig" + + diff --git a/drivers/media/dvb/mpq/Makefile b/drivers/media/dvb/mpq/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7ccf13e34268b5e02068c7213853a3378f901283 --- /dev/null +++ b/drivers/media/dvb/mpq/Makefile @@ -0,0 +1,5 @@ + +obj-$(CONFIG_DVB_MPQ) += adapter/ +obj-$(CONFIG_DVB_MPQ_DEMUX) += demux/ + + diff --git a/drivers/media/dvb/mpq/adapter/Makefile b/drivers/media/dvb/mpq/adapter/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ed664dae0b59d51be6840914b6d4ba5352d86059 --- /dev/null +++ b/drivers/media/dvb/mpq/adapter/Makefile @@ -0,0 +1,8 @@ + +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/ +EXTRA_CFLAGS += -Idrivers/media/dvb/mpq/include/ + +obj-$(CONFIG_DVB_MPQ) += mpq-adapter.o + +mpq-adapter-y := mpq_adapter.o mpq_stream_buffer.o + diff --git a/drivers/media/dvb/mpq/adapter/mpq_adapter.c b/drivers/media/dvb/mpq/adapter/mpq_adapter.c new file mode 100644 index 0000000000000000000000000000000000000000..9664f048ae305012c7eca4bff890a2cdece651e4 --- /dev/null +++ b/drivers/media/dvb/mpq/adapter/mpq_adapter.c @@ -0,0 +1,212 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "mpq_adapter.h" +#include "mpq_dvb_debug.h" + + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* data-structure holding MPQ adapter information */ +static struct +{ + /* MPQ adapter registered to dvb-core */ + struct dvb_adapter adapter; + + /* mutex protect against the data-structure */ + struct mutex mutex; + + /* List of stream interfaces registered to the MPQ adapter */ + struct { + /* pointer to the stream buffer using for data tunneling */ + struct mpq_streambuffer *stream_buffer; + + /* callback triggered when the stream interface is registered */ + mpq_adapter_stream_if_callback callback; + + /* parameter passed to the callback function */ + void *user_param; + } interfaces[MPQ_ADAPTER_MAX_NUM_OF_INTERFACES]; +} mpq_info; + + +/** + * Initialize MPQ DVB adapter module. + * + * Return error status + */ +static int __init mpq_adapter_init(void) +{ + int i; + int result; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + mutex_init(&mpq_info.mutex); + + /* reset stream interfaces list */ + for (i = 0; i < MPQ_ADAPTER_MAX_NUM_OF_INTERFACES; i++) { + mpq_info.interfaces[i].stream_buffer = NULL; + mpq_info.interfaces[i].callback = NULL; + } + + /* regsiter a new dvb-adapter to dvb-core */ + result = dvb_register_adapter(&mpq_info.adapter, + "Qualcomm DVB adapter", + THIS_MODULE, + NULL, + adapter_nr); + + if (result < 0) { + MPQ_DVB_ERR_PRINT( + "%s: dvb_register_adapter failed, errno %d\n", + __func__, + result); + } + + return result; +} + + +/** + * Cleanup MPQ DVB adapter module. + */ +static void __exit mpq_adapter_exit(void) +{ + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* un-regsiter adapter from dvb-core */ + dvb_unregister_adapter(&mpq_info.adapter); + mutex_destroy(&mpq_info.mutex); +} + +struct dvb_adapter *mpq_adapter_get(void) +{ + return &mpq_info.adapter; +} +EXPORT_SYMBOL(mpq_adapter_get); + + +int mpq_adapter_register_stream_if( + enum mpq_adapter_stream_if interface_id, + struct mpq_streambuffer *stream_buffer) +{ + int ret; + + if (interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES) { + ret = -EINVAL; + goto register_failed; + } + + if (mutex_lock_interruptible(&mpq_info.mutex)) { + ret = -ERESTARTSYS; + goto register_failed; + } + + if (mpq_info.interfaces[interface_id].stream_buffer != NULL) { + /* already registered interface */ + ret = -EINVAL; + goto register_failed_unlock_mutex; + } + + mpq_info.interfaces[interface_id].stream_buffer = stream_buffer; + mutex_unlock(&mpq_info.mutex); + + /* + * If callback is installed, trigger it to notify that + * stream interface was registered. + */ + if (mpq_info.interfaces[interface_id].callback != NULL) { + mpq_info.interfaces[interface_id].callback( + interface_id, + mpq_info.interfaces[interface_id].user_param); + } + + return 0; + +register_failed_unlock_mutex: + mutex_unlock(&mpq_info.mutex); +register_failed: + return ret; +} +EXPORT_SYMBOL(mpq_adapter_register_stream_if); + + +int mpq_adapter_unregister_stream_if( + enum mpq_adapter_stream_if interface_id) +{ + if (interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES) + return -EINVAL; + + if (mutex_lock_interruptible(&mpq_info.mutex)) + return -ERESTARTSYS; + + /* clear the registered interface */ + mpq_info.interfaces[interface_id].stream_buffer = NULL; + + mutex_unlock(&mpq_info.mutex); + + return 0; +} +EXPORT_SYMBOL(mpq_adapter_unregister_stream_if); + + +int mpq_adapter_get_stream_if( + enum mpq_adapter_stream_if interface_id, + struct mpq_streambuffer **stream_buffer) +{ + if ((interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES) || + (stream_buffer == NULL)) + return -EINVAL; + + if (mutex_lock_interruptible(&mpq_info.mutex)) + return -ERESTARTSYS; + + *stream_buffer = mpq_info.interfaces[interface_id].stream_buffer; + + mutex_unlock(&mpq_info.mutex); + + return 0; +} +EXPORT_SYMBOL(mpq_adapter_get_stream_if); + + +int mpq_adapter_notify_stream_if( + enum mpq_adapter_stream_if interface_id, + mpq_adapter_stream_if_callback callback, + void *user_param) +{ + if (interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES) + return -EINVAL; + + if (mutex_lock_interruptible(&mpq_info.mutex)) + return -ERESTARTSYS; + + mpq_info.interfaces[interface_id].callback = callback; + mpq_info.interfaces[interface_id].user_param = user_param; + + mutex_unlock(&mpq_info.mutex); + + return 0; +} +EXPORT_SYMBOL(mpq_adapter_notify_stream_if); + + +module_init(mpq_adapter_init); +module_exit(mpq_adapter_exit); + +MODULE_DESCRIPTION("Qualcomm MPQ adapter"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/dvb/mpq/adapter/mpq_stream_buffer.c b/drivers/media/dvb/mpq/adapter/mpq_stream_buffer.c new file mode 100644 index 0000000000000000000000000000000000000000..738d73059885fb8640dfe3033a79dd08e92a3b79 --- /dev/null +++ b/drivers/media/dvb/mpq/adapter/mpq_stream_buffer.c @@ -0,0 +1,224 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include "mpq_dvb_debug.h" +#include "mpq_stream_buffer.h" + + +void mpq_streambuffer_init( + struct mpq_streambuffer *sbuff, + void *data_buff, size_t data_buff_len, + void *packet_buff, size_t packet_buff_size) +{ + dvb_ringbuffer_init(&sbuff->raw_data, data_buff, data_buff_len); + dvb_ringbuffer_init(&sbuff->packet_data, packet_buff, packet_buff_size); +} +EXPORT_SYMBOL(mpq_streambuffer_init); + + +ssize_t mpq_streambuffer_pkt_next( + struct mpq_streambuffer *sbuff, + ssize_t idx, size_t *pktlen) +{ + return dvb_ringbuffer_pkt_next(&sbuff->packet_data, idx, pktlen); +} +EXPORT_SYMBOL(mpq_streambuffer_pkt_next); + + +ssize_t mpq_streambuffer_pkt_read( + struct mpq_streambuffer *sbuff, + size_t idx, + struct mpq_streambuffer_packet_header *packet, + u8 *user_data) +{ + size_t ret; + size_t read_len; + + /* read-out the packet header first */ + ret = dvb_ringbuffer_pkt_read( + &sbuff->packet_data, idx, 0, + (u8 *)packet, + sizeof(struct mpq_streambuffer_packet_header)); + + /* verify length, at least packet header should exist */ + if (ret != sizeof(struct mpq_streambuffer_packet_header)) + return -EINVAL; + + read_len = ret; + + /* read-out private user-data if there are such */ + if ((packet->user_data_len) && (user_data != NULL)) { + ret = dvb_ringbuffer_pkt_read( + &sbuff->packet_data, + idx, + sizeof(struct mpq_streambuffer_packet_header), + user_data, + packet->user_data_len); + + if (ret < 0) + return ret; + + read_len += ret; + } + + return read_len; +} +EXPORT_SYMBOL(mpq_streambuffer_pkt_read); + + +int mpq_streambuffer_pkt_dispose( + struct mpq_streambuffer *sbuff, + size_t idx, + int dispose_data) +{ + int ret; + struct mpq_streambuffer_packet_header packet; + + if (dispose_data) { + /* read-out the packet header first */ + ret = dvb_ringbuffer_pkt_read( + &sbuff->packet_data, + idx, + 0, + (u8 *)&packet, + sizeof(struct mpq_streambuffer_packet_header)); + + if (ret != sizeof(struct mpq_streambuffer_packet_header)) + return -EINVAL; + + /* Advance the read pointer in the raw-data buffer first */ + ret = mpq_streambuffer_data_read_dispose( + sbuff, + packet.raw_data_len); + if (ret != 0) + return ret; + } + + /* Now clear the packet from the packet header */ + dvb_ringbuffer_pkt_dispose(&sbuff->packet_data, idx); + + return 0; +} +EXPORT_SYMBOL(mpq_streambuffer_pkt_dispose); + + +int mpq_streambuffer_pkt_write( + struct mpq_streambuffer *sbuff, + struct mpq_streambuffer_packet_header *packet, + u8 *user_data) +{ + ssize_t idx; + size_t len; + + len = + sizeof(struct mpq_streambuffer_packet_header) + + packet->user_data_len; + + /* Make sure enough space available for packet header */ + if (dvb_ringbuffer_free(&sbuff->packet_data) < len) + return -ENOSPC; + + /* Starting writting packet header */ + idx = dvb_ringbuffer_pkt_start(&sbuff->packet_data, len); + + /* Write non-user private data header */ + dvb_ringbuffer_write( + &sbuff->packet_data, + (u8 *)packet, + sizeof(struct mpq_streambuffer_packet_header)); + + /* Write user's own private data header */ + dvb_ringbuffer_write(&sbuff->packet_data, + user_data, + packet->user_data_len); + + dvb_ringbuffer_pkt_close(&sbuff->packet_data, idx); + + wake_up_all(&sbuff->packet_data.queue); + + return 0; +} +EXPORT_SYMBOL(mpq_streambuffer_pkt_write); + + +ssize_t mpq_streambuffer_data_write( + struct mpq_streambuffer *sbuff, + const u8 *buf, size_t len) +{ + int res; + + if (unlikely(dvb_ringbuffer_free(&sbuff->raw_data) < len)) + return -ENOSPC; + + res = dvb_ringbuffer_write(&sbuff->raw_data, buf, len); + wake_up_all(&sbuff->raw_data.queue); + + return res; +} +EXPORT_SYMBOL(mpq_streambuffer_data_write); + + +int mpq_streambuffer_data_write_deposit( + struct mpq_streambuffer *sbuff, + size_t len) +{ + if (unlikely(dvb_ringbuffer_free(&sbuff->raw_data) < len)) + return -ENOSPC; + + sbuff->raw_data.pwrite = + (sbuff->raw_data.pwrite+len) % sbuff->raw_data.size; + + wake_up_all(&sbuff->raw_data.queue); + + return 0; +} +EXPORT_SYMBOL(mpq_streambuffer_data_write_deposit); + + +size_t mpq_streambuffer_data_read( + struct mpq_streambuffer *sbuff, + u8 *buf, size_t len) +{ + int actual_len; + + actual_len = dvb_ringbuffer_avail(&sbuff->raw_data); + if (actual_len < len) + len = actual_len; + + if (actual_len) + dvb_ringbuffer_read(&sbuff->raw_data, buf, actual_len); + + wake_up_all(&sbuff->raw_data.queue); + + return actual_len; +} +EXPORT_SYMBOL(mpq_streambuffer_data_read); + + +int mpq_streambuffer_data_read_dispose( + struct mpq_streambuffer *sbuff, + size_t len) +{ + if (unlikely(dvb_ringbuffer_avail(&sbuff->raw_data) < len)) + return -EINVAL; + + DVB_RINGBUFFER_SKIP(&sbuff->raw_data, len); + + wake_up_all(&sbuff->raw_data.queue); + return 0; +} +EXPORT_SYMBOL(mpq_streambuffer_data_read_dispose); + diff --git a/drivers/media/dvb/mpq/demux/Kconfig b/drivers/media/dvb/mpq/demux/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..34961c2a7327fc0b0c370ad27ae8fd61de8067f5 --- /dev/null +++ b/drivers/media/dvb/mpq/demux/Kconfig @@ -0,0 +1,45 @@ +config DVB_MPQ_DEMUX + tristate "DVB Demux Device" + depends on DVB_MPQ && ION && ION_MSM + default n + + help + Support for Qualcomm based dvb demux device. + Say Y or M if you own such a device and want to use it. + +config DVB_MPQ_NUM_DMX_DEVICES + int "Number of demux devices" + depends on DVB_MPQ_DEMUX + default 4 + range 1 255 + + help + Configure number of demux devices. Depends on your use-cases for maximum concurrent stream playback. + +choice + prompt "Demux Hardware Plugin" + depends on DVB_MPQ_DEMUX + default DVB_MPQ_TSIF + help + Enable support of specific demux HW plugin depending on the existing HW support. + Depending on the enabled HW, demux may take advantage of HW capbailities to perform some tasks in HW instead of SW. + + config DVB_MPQ_TSPP1 + bool "TSPPv1 plugin" + depends on TSPP + help + Use this option of your HW has Transport Stream Packet Processor (TSPP) version1 support + + config DVB_MPQ_TSPP2 + bool "TSPPv2 plugin" + depends on TSPP + help + Use this option of your HW has Transport Stream Packet Processor (TSPP) version2 support + + config DVB_MPQ_TSIF + bool "TSIF plugin" + depends on TSIF + help + Use this option of your HW has only TSIF input without any Transport Stream Packet Processor (TSPP) support +endchoice + diff --git a/drivers/media/dvb/mpq/demux/Makefile b/drivers/media/dvb/mpq/demux/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b9310c3c95d67c6d0d1e9a242401b7d1aec8b445 --- /dev/null +++ b/drivers/media/dvb/mpq/demux/Makefile @@ -0,0 +1,14 @@ + +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/ +EXTRA_CFLAGS += -Idrivers/media/dvb/mpq/include/ + +obj-$(CONFIG_DVB_MPQ_DEMUX) += mpq-dmx-hw-plugin.o + +mpq-dmx-hw-plugin-y := mpq_dmx_plugin_common.o + +mpq-dmx-hw-plugin-$(CONFIG_DVB_MPQ_TSPP1) += mpq_dmx_plugin_tspp_v1.o + +mpq-dmx-hw-plugin-$(CONFIG_DVB_MPQ_TSPP2) += mpq_dmx_plugin_tspp_v2.o + +mpq-dmx-hw-plugin-$(CONFIG_DVB_MPQ_TSIF) += mpq_dmx_plugin_tsif.o + diff --git a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_common.c b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_common.c new file mode 100644 index 0000000000000000000000000000000000000000..03b3929cc1c4d0c96f0157642be223c43c302848 --- /dev/null +++ b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_common.c @@ -0,0 +1,1956 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include "mpq_dvb_debug.h" +#include "mpq_dmx_plugin_common.h" + + +/* Length of mandatory fields that must exist in header of video PES */ +#define PES_MANDATORY_FIELDS_LEN 9 + + +/* + * 500 PES header packets in the meta-data buffer, + * should be more than enough + */ +#define VIDEO_NUM_OF_PES_PACKETS 500 + +#define VIDEO_META_DATA_BUFFER_SIZE \ + (VIDEO_NUM_OF_PES_PACKETS * \ + (DVB_RINGBUFFER_PKTHDRSIZE + \ + sizeof(struct mpq_streambuffer_packet_header) + \ + sizeof(struct mpq_adapter_video_meta_data))) + +/* + * PCR/STC information length saved in ring-buffer. + * PCR / STC are saved in ring-buffer in the following form: + * <8 bit flags><64 bits of STC> <64bits of PCR> + * STC and PCR values are in 27MHz. + * The current flags that are defined: + * 0x00000001: discontinuity_indicator + */ +#define PCR_STC_LEN 17 + + +/* Number of demux devices, has default of linux configuration */ +static int mpq_demux_device_num = CONFIG_DVB_MPQ_NUM_DMX_DEVICES; +module_param(mpq_demux_device_num, int, S_IRUGO); + +/** + * Maximum allowed framing pattern size + */ +#define MPQ_MAX_PATTERN_SIZE 6 + +/** + * Number of patterns to look for when doing framing, per video standard + */ +#define MPQ_MPEG2_PATTERN_NUM 5 +#define MPQ_H264_PATTERN_NUM 5 +#define MPQ_VC1_PATTERN_NUM 3 + +/* + * mpq_framing_pattern_lookup_params - framing pattern lookup parameters. + * + * @pattern: the byte pattern to look for. + * @mask: the byte mask to use (same length as pattern). + * @size: the length of the pattern, in bytes. + * @type: the type of the pattern. + */ +struct mpq_framing_pattern_lookup_params { + u8 pattern[MPQ_MAX_PATTERN_SIZE]; + u8 mask[MPQ_MAX_PATTERN_SIZE]; + size_t size; + enum dmx_framing_pattern_type type; +}; + +/* + * Pre-defined video framing lookup pattern information. + * Note: the first pattern in each patterns database must + * be the Sequence Header (or equivalent SPS in H.264). + * The code assumes this is the case when prepending + * Sequence Header data in case it is required. + */ +static const struct mpq_framing_pattern_lookup_params + mpeg2_patterns[MPQ_MPEG2_PATTERN_NUM] = { + {{0x00, 0x00, 0x01, 0xB3}, {0xFF, 0xFF, 0xFF, 0xFF}, 4, + DMX_FRM_MPEG2_SEQUENCE_HEADER}, + {{0x00, 0x00, 0x01, 0xB8}, {0xFF, 0xFF, 0xFF, 0xFF}, 4, + DMX_FRM_MPEG2_GOP_HEADER}, + {{0x00, 0x00, 0x01, 0x00, 0x00, 0x08}, + {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38}, 6, + DMX_FRM_MPEG2_I_PIC}, + {{0x00, 0x00, 0x01, 0x00, 0x00, 0x10}, + {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38}, 6, + DMX_FRM_MPEG2_P_PIC}, + {{0x00, 0x00, 0x01, 0x00, 0x00, 0x18}, + {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38}, 6, + DMX_FRM_MPEG2_B_PIC} +}; + +static const struct mpq_framing_pattern_lookup_params + h264_patterns[MPQ_H264_PATTERN_NUM] = { + {{0x00, 0x00, 0x01, 0x07}, {0xFF, 0xFF, 0xFF, 0x1F}, 4, + DMX_FRM_H264_SPS}, + {{0x00, 0x00, 0x01, 0x08}, {0xFF, 0xFF, 0xFF, 0x1F}, 4, + DMX_FRM_H264_PPS}, + {{0x00, 0x00, 0x01, 0x05, 0x80}, {0xFF, 0xFF, 0xFF, 0x1F, 0x80}, 5, + DMX_FRM_H264_IDR_PIC}, + {{0x00, 0x00, 0x01, 0x01, 0x80}, {0xFF, 0xFF, 0xFF, 0x1F, 0x80}, 5, + DMX_FRM_H264_NON_IDR_PIC} +}; + +static const struct mpq_framing_pattern_lookup_params + vc1_patterns[MPQ_VC1_PATTERN_NUM] = { + {{0x00, 0x00, 0x01, 0x0F}, {0xFF, 0xFF, 0xFF, 0xFF}, 4, + DMX_FRM_VC1_SEQUENCE_HEADER}, + {{0x00, 0x00, 0x01, 0x0E}, {0xFF, 0xFF, 0xFF, 0xFF}, 4, + DMX_FRM_VC1_ENTRY_POINT_HEADER}, + {{0x00, 0x00, 0x01, 0x0D}, {0xFF, 0xFF, 0xFF, 0xFF}, 4, + DMX_FRM_VC1_FRAME_START_CODE} +}; + +/* Global data-structure for managing demux devices */ +static struct +{ + /* ION demux client used for memory allocation */ + struct ion_client *ion_client; + + /* demux devices array */ + struct mpq_demux *devices; + + /* Stream buffers objects used for tunneling to decoders */ + struct mpq_streambuffer + decoder_buffers[MPQ_ADAPTER_MAX_NUM_OF_INTERFACES]; + + /* + * Indicates whether the video decoder handles framing + * or we are required to provide framing information + * in the meta-data passed to the decoder. + */ + int decoder_framing; +} mpq_dmx_info; + +/* Check that PES header is valid and that it is a video PES */ +static int mpq_dmx_is_valid_video_pes(struct pes_packet_header *pes_header) +{ + /* start-code valid? */ + if ((pes_header->packet_start_code_prefix_1 != 0) || + (pes_header->packet_start_code_prefix_2 != 0) || + (pes_header->packet_start_code_prefix_3 != 1)) + return -EINVAL; + + /* stream_id is video? */ + if ((pes_header->stream_id & 0xF0) != 0xE0) + return -EINVAL; + + return 0; +} + +/* Check if a framing pattern is a video frame pattern or a header pattern */ +static inline int mpq_dmx_is_video_frame( + enum dmx_indexing_video_standard standard, + enum dmx_framing_pattern_type pattern_type) +{ + switch (standard) { + case DMX_INDEXING_MPEG2: + if ((pattern_type == DMX_FRM_MPEG2_I_PIC) || + (pattern_type == DMX_FRM_MPEG2_P_PIC) || + (pattern_type == DMX_FRM_MPEG2_B_PIC)) + return 1; + return 0; + case DMX_INDEXING_H264: + if ((pattern_type == DMX_FRM_H264_IDR_PIC) || + (pattern_type == DMX_FRM_H264_NON_IDR_PIC)) + return 1; + return 0; + case DMX_INDEXING_VC1: + if (pattern_type == DMX_FRM_VC1_FRAME_START_CODE) + return 1; + return 0; + default: + return -EINVAL; + } +} + +/* + * mpq_framing_pattern_lookup_results - framing lookup results + * + * @offset: The offset in the buffer where the pattern was found. + * If a pattern is found using a prefix (i.e. started on the + * previous buffer), offset is zero. + * @type: the type of the pattern found. + * @used_prefix_size: the prefix size that was used to find this pattern + */ +struct mpq_framing_pattern_lookup_results { + struct { + u32 offset; + enum dmx_framing_pattern_type type; + u32 used_prefix_size; + } info[MPQ_MAX_FOUND_PATTERNS]; +}; + +/* + * Check if two patterns are identical, taking mask into consideration. + * @pattern1: the first byte pattern to compare. + * @pattern2: the second byte pattern to compare. + * @mask: the bit mask to use. + * @pattern_size: the length of both patterns and the mask, in bytes. + * + * Return: 1 if patterns match, 0 otherwise. + */ +static inline int mpq_dmx_patterns_match(const u8 *pattern1, const u8 *pattern2, + const u8 *mask, size_t pattern_size) +{ + int i; + + /* + * Assumption: it is OK to access pattern1, pattern2 and mask. + * This function performs no sanity checks to keep things fast. + */ + + for (i = 0; i < pattern_size; i++) + if ((pattern1[i] & mask[i]) != (pattern2[i] & mask[i])) + return 0; + + return 1; +} + +/* + * mpq_dmx_framing_pattern_search - + * search for framing patterns in a given buffer. + * + * Optimized version: first search for a common substring, e.g. 0x00 0x00 0x01. + * If this string is found, go over all the given patterns (all must start + * with this string) and search for their ending in the buffer. + * + * Assumption: the patterns we look for do not spread over more than two + * buffers. + * + * @paterns: the full patterns information to look for. + * @patterns_num: the number of patterns to look for. + * @buf: the buffer to search. + * @buf_size: the size of the buffer to search. we search the entire buffer. + * @prefix_size_masks: a bit mask (per pattern) of possible prefix sizes to use + * when searching for a pattern that started at the last buffer. + * Updated in this function for use in the next lookup. + * @results: lookup results (offset, type, used_prefix_size) per found pattern, + * up to MPQ_MAX_FOUND_PATTERNS. + * + * Return: + * Number of patterns found (up to MPQ_MAX_FOUND_PATTERNS). + * 0 if pattern was not found. + * Negative error value on failure. + */ +static int mpq_dmx_framing_pattern_search( + const struct mpq_framing_pattern_lookup_params *patterns, + int patterns_num, + const u8 *buf, + size_t buf_size, + struct mpq_framing_prefix_size_masks *prefix_size_masks, + struct mpq_framing_pattern_lookup_results *results) +{ + int i, j; + unsigned int current_size; + u32 prefix; + int found = 0; + int start_offset = 0; + /* the starting common substring to look for */ + u8 string[] = {0x00, 0x00, 0x01}; + /* the mask for the starting string */ + u8 string_mask[] = {0xFF, 0xFF, 0xFF}; + /* the size of the starting string (in bytes) */ + size_t string_size = 3; + + /* sanity checks - can be commented out for optimization purposes */ + if ((patterns == NULL) || (patterns_num <= 0) || (buf == NULL)) { + MPQ_DVB_ERR_PRINT("%s: invalid parameters\n", __func__); + return -EINVAL; + } + + memset(results, 0, sizeof(struct mpq_framing_pattern_lookup_results)); + + /* + * handle prefix - disregard string, simply check all patterns, + * looking for a matching suffix at the very beginning of the buffer. + */ + for (j = 0; (j < patterns_num) && !found; j++) { + prefix = prefix_size_masks->size_mask[j]; + current_size = 32; + while (prefix) { + if (prefix & (0x1 << (current_size - 1))) { + /* + * check that we don't look further + * than buf_size boundary + */ + if ((int)(patterns[j].size - current_size) > + buf_size) + break; + + if (mpq_dmx_patterns_match( + (patterns[j].pattern + current_size), + buf, (patterns[j].mask + current_size), + (patterns[j].size - current_size))) { + + MPQ_DVB_DBG_PRINT( + "%s: Found matching pattern" + "using prefix of size %d\n", + __func__, current_size); + /* + * pattern found using prefix at the + * very beginning of the buffer, so + * offset is 0, but we already zeroed + * everything in the beginning of the + * function. that's why the next line + * is commented. + */ + /* results->info[found].offset = 0; */ + results->info[found].type = + patterns[j].type; + results->info[found].used_prefix_size = + current_size; + found++; + /* + * save offset to start looking from + * in the buffer, to avoid reusing the + * data of a pattern we already found. + */ + start_offset = (patterns[j].size - + current_size); + + if (found >= MPQ_MAX_FOUND_PATTERNS) + goto next_prefix_lookup; + /* + * we don't want to search for the same + * pattern with several possible prefix + * sizes if we have already found it, + * so we break from the inner loop. + * since we incremented 'found', we + * will not search for additional + * patterns using a prefix - that would + * imply ambiguous patterns where one + * pattern can be included in another. + * the for loop will exit. + */ + break; + } + } + current_size--; + prefix &= ~(0x1 << (current_size - 1)); + } + } + + /* + * Search buffer for entire pattern, starting with the string. + * Note the external for loop does not execute if buf_size is + * smaller than string_size (the cast to int is required, since + * size_t is unsigned). + */ + for (i = start_offset; i < (int)(buf_size - string_size + 1); i++) { + if (mpq_dmx_patterns_match(string, (buf + i), string_mask, + string_size)) { + /* now search for patterns: */ + for (j = 0; j < patterns_num; j++) { + /* avoid overflow to next buffer */ + if ((i + patterns[j].size) > buf_size) + continue; + + if (mpq_dmx_patterns_match( + (patterns[j].pattern + string_size), + (buf + i + string_size), + (patterns[j].mask + string_size), + (patterns[j].size - string_size))) { + + results->info[found].offset = i; + results->info[found].type = + patterns[j].type; + /* + * save offset to start next prefix + * lookup, to avoid reusing the data + * of any pattern we already found. + */ + if ((i + patterns[j].size) > + start_offset) + start_offset = (i + + patterns[j].size); + /* + * did not use a prefix to find this + * pattern, but we zeroed everything + * in the beginning of the function. + * So no need to zero used_prefix_size + * for results->info[found] + */ + + found++; + if (found >= MPQ_MAX_FOUND_PATTERNS) + goto next_prefix_lookup; + /* + * theoretically we don't have to break + * here, but we don't want to search + * for the other matching patterns on + * the very same same place in the + * buffer. That would mean the + * (pattern & mask) combinations are + * not unique. So we break from inner + * loop and move on to the next place + * in the buffer. + */ + break; + } + } + } + } + +next_prefix_lookup: + /* check for possible prefix sizes for the next buffer */ + for (j = 0; j < patterns_num; j++) { + prefix_size_masks->size_mask[j] = 0; + for (i = 1; i < patterns[j].size; i++) { + /* + * avoid looking outside of the buffer + * or reusing previously used data. + */ + if (i > (buf_size - start_offset)) + break; + + if (mpq_dmx_patterns_match(patterns[j].pattern, + (buf + buf_size - i), + patterns[j].mask, i)) { + prefix_size_masks->size_mask[j] |= + (1 << (i - 1)); + } + } + } + + return found; +} + +/* + * mpq_dmx_get_pattern_params - + * get a pointer to the relevant pattern parameters structure, + * based on the video parameters. + * + * @video_params: the video parameters (e.g. video standard). + * @patterns: a pointer to a pointer to the pattern parameters, + * updated by this function. + * @patterns_num: number of patterns, updated by this function. + */ +static inline int mpq_dmx_get_pattern_params( + struct dmx_indexing_video_params *video_params, + const struct mpq_framing_pattern_lookup_params **patterns, + int *patterns_num) +{ + switch (video_params->standard) { + case DMX_INDEXING_MPEG2: + *patterns = mpeg2_patterns; + *patterns_num = MPQ_MPEG2_PATTERN_NUM; + break; + case DMX_INDEXING_H264: + *patterns = h264_patterns; + *patterns_num = MPQ_H264_PATTERN_NUM; + break; + case DMX_INDEXING_VC1: + *patterns = vc1_patterns; + *patterns_num = MPQ_VC1_PATTERN_NUM; + break; + default: + MPQ_DVB_ERR_PRINT("%s: invalid parameters\n", __func__); + *patterns = NULL; + *patterns_num = 0; + return -EINVAL; + } + + return 0; +} + +/* Extend dvb-demux debugfs with HW statistics */ +void mpq_dmx_init_hw_statistics(struct mpq_demux *mpq_demux) +{ + /* + * Extend dvb-demux debugfs with HW statistics. + * Note that destruction of debugfs directory is done + * when dvb-demux is terminated. + */ + mpq_demux->hw_notification_count = 0; + mpq_demux->hw_notification_rate = 0; + mpq_demux->hw_notification_size = 0; + mpq_demux->decoder_tsp_drop_count = 0; + + if (mpq_demux->demux.debugfs_demux_dir != NULL) { + debugfs_create_u32( + "hw_notification_rate", + S_IRUGO|S_IWUGO, + mpq_demux->demux.debugfs_demux_dir, + &mpq_demux->hw_notification_rate); + + debugfs_create_u32( + "hw_notification_count", + S_IRUGO|S_IWUGO, + mpq_demux->demux.debugfs_demux_dir, + &mpq_demux->hw_notification_count); + + debugfs_create_u32( + "hw_notification_size", + S_IRUGO|S_IWUGO, + mpq_demux->demux.debugfs_demux_dir, + &mpq_demux->hw_notification_size); + + debugfs_create_u32( + "decoder_tsp_drop_count", + S_IRUGO|S_IWUGO, + mpq_demux->demux.debugfs_demux_dir, + &mpq_demux->decoder_tsp_drop_count); + } +} +EXPORT_SYMBOL(mpq_dmx_init_hw_statistics); + + +/* Update dvb-demux debugfs with HW notification statistics */ +void mpq_dmx_update_hw_statistics(struct mpq_demux *mpq_demux) +{ + struct timespec curr_time, delta_time; + u64 delta_time_ms; + + curr_time = current_kernel_time(); + if (likely(mpq_demux->hw_notification_count)) { + /* calculate time-delta between notifications */ + delta_time = + timespec_sub( + curr_time, + mpq_demux->last_notification_time); + + delta_time_ms = (u64)timespec_to_ns(&delta_time); + delta_time_ms = div64_u64(delta_time_ms, 1000000); /* ns->ms */ + + mpq_demux->hw_notification_rate = delta_time_ms; + } + + mpq_demux->hw_notification_count++; + mpq_demux->last_notification_time = curr_time; +} +EXPORT_SYMBOL(mpq_dmx_update_hw_statistics); + + +int mpq_dmx_plugin_init(mpq_dmx_init dmx_init_func) +{ + int i; + int result; + struct mpq_demux *mpq_demux; + struct dvb_adapter *mpq_adapter; + + MPQ_DVB_DBG_PRINT("%s executed, device num %d\n", + __func__, + mpq_demux_device_num); + + mpq_adapter = mpq_adapter_get(); + + if (mpq_adapter == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: mpq_adapter is not valid\n", + __func__); + result = -EPERM; + goto init_failed; + } + + if (mpq_demux_device_num == 0) { + MPQ_DVB_ERR_PRINT( + "%s: mpq_demux_device_num set to 0\n", + __func__); + + result = -EPERM; + goto init_failed; + } + + mpq_dmx_info.devices = NULL; + mpq_dmx_info.ion_client = NULL; + + /* + * TODO: the following should be set based on the decoder: + * 0 means the decoder doesn't handle framing, so framing + * is done by demux. 1 means the decoder handles framing. + */ + mpq_dmx_info.decoder_framing = 0; + + /* Allocate memory for all MPQ devices */ + mpq_dmx_info.devices = + vmalloc(mpq_demux_device_num*sizeof(struct mpq_demux)); + + if (!mpq_dmx_info.devices) { + MPQ_DVB_ERR_PRINT( + "%s: failed to allocate devices memory\n", + __func__); + + result = -ENOMEM; + goto init_failed; + } + + /* Zero allocated memory */ + memset(mpq_dmx_info.devices, + 0, + mpq_demux_device_num*sizeof(struct mpq_demux)); + + /* + * Create a new ION client used by demux to allocate memory + * for decoder's buffers. + */ + mpq_dmx_info.ion_client = + msm_ion_client_create(UINT_MAX, "demux client"); + + if (IS_ERR_OR_NULL(mpq_dmx_info.ion_client)) { + MPQ_DVB_ERR_PRINT( + "%s: msm_ion_client_create\n", + __func__); + + result = PTR_ERR(mpq_dmx_info.ion_client); + mpq_dmx_info.ion_client = NULL; + goto init_failed_free_demux_devices; + } + + /* Initialize and register all demux devices to the system */ + for (i = 0; i < mpq_demux_device_num; i++) { + mpq_demux = mpq_dmx_info.devices+i; + + /* initialize demux source to memory by default */ + mpq_demux->source = DMX_SOURCE_DVR0 + i; + + /* + * Give the plugin pointer to the ion client so + * that it can allocate memory from ION if it requires so + */ + mpq_demux->ion_client = mpq_dmx_info.ion_client; + + spin_lock_init(&mpq_demux->feed_lock); + + /* + * mpq_demux_plugin_hw_init should be implemented + * by the specific plugin + */ + result = dmx_init_func(mpq_adapter, mpq_demux); + if (result < 0) { + MPQ_DVB_ERR_PRINT( + "%s: dmx_init_func (errno=%d)\n", + __func__, + result); + + goto init_failed_free_demux_devices; + } + + mpq_demux->is_initialized = 1; + + /* + * Add capability of receiving input from memory. + * Every demux in our system may be connected to memory input, + * or any live input. + */ + mpq_demux->fe_memory.source = DMX_MEMORY_FE; + result = + mpq_demux->demux.dmx.add_frontend( + &mpq_demux->demux.dmx, + &mpq_demux->fe_memory); + + if (result < 0) { + MPQ_DVB_ERR_PRINT( + "%s: add_frontend (mem) failed (errno=%d)\n", + __func__, + result); + + goto init_failed_free_demux_devices; + } + } + + return 0; + +init_failed_free_demux_devices: + mpq_dmx_plugin_exit(); +init_failed: + return result; +} +EXPORT_SYMBOL(mpq_dmx_plugin_init); + + +void mpq_dmx_plugin_exit(void) +{ + int i; + struct mpq_demux *mpq_demux; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + if (mpq_dmx_info.ion_client != NULL) { + ion_client_destroy(mpq_dmx_info.ion_client); + mpq_dmx_info.ion_client = NULL; + } + + if (mpq_dmx_info.devices != NULL) { + for (i = 0; i < mpq_demux_device_num; i++) { + mpq_demux = mpq_dmx_info.devices+i; + + if (mpq_demux->is_initialized) { + mpq_demux->demux.dmx.remove_frontend( + &mpq_demux->demux.dmx, + &mpq_demux->fe_memory); + + dvb_dmxdev_release(&mpq_demux->dmxdev); + dvb_dmx_release(&mpq_demux->demux); + } + } + + vfree(mpq_dmx_info.devices); + mpq_dmx_info.devices = NULL; + } +} +EXPORT_SYMBOL(mpq_dmx_plugin_exit); + + +int mpq_dmx_set_source( + struct dmx_demux *demux, + const dmx_source_t *src) +{ + int i; + int dvr_index; + int dmx_index; + struct dvb_demux *dvb_demux = (struct dvb_demux *)demux->priv; + struct mpq_demux *mpq_demux; + + if ((mpq_dmx_info.devices == NULL) || (dvb_demux == NULL)) { + MPQ_DVB_ERR_PRINT("%s: invalid parameters\n", __func__); + return -EINVAL; + } + + mpq_demux = (struct mpq_demux *)dvb_demux->priv; + if (mpq_demux == NULL) { + MPQ_DVB_ERR_PRINT("%s: invalid parameters\n", __func__); + return -EINVAL; + } + + /* + * For dvr sources, + * verify that this source is connected to the respective demux + */ + dmx_index = mpq_demux - mpq_dmx_info.devices; + + if (*src >= DMX_SOURCE_DVR0) { + dvr_index = *src - DMX_SOURCE_DVR0; + + if (dvr_index != dmx_index) { + MPQ_DVB_ERR_PRINT( + "%s: can't connect demux%d to dvr%d\n", + __func__, + dmx_index, + dvr_index); + return -EINVAL; + } + } + + /* + * For front-end sources, + * verify that this source is not already set to different demux + */ + for (i = 0; i < mpq_demux_device_num; i++) { + if ((&mpq_dmx_info.devices[i] != mpq_demux) && + (mpq_dmx_info.devices[i].source == *src)) { + MPQ_DVB_ERR_PRINT( + "%s: demux%d source can't be set,\n" + "demux%d occupies this source already\n", + __func__, + dmx_index, + i); + return -EBUSY; + } + } + + mpq_demux->source = *src; + return 0; +} +EXPORT_SYMBOL(mpq_dmx_set_source); + + +int mpq_dmx_init_video_feed(struct dvb_demux_feed *feed) +{ + int ret; + void *packet_buffer; + void *payload_buffer; + struct mpq_video_feed_info *feed_data; + struct mpq_demux *mpq_demux = + (struct mpq_demux *)feed->demux->priv; + struct mpq_streambuffer *stream_buffer; + int actual_buffer_size; + + /* Allocate memory for private feed data */ + feed_data = vmalloc(sizeof(struct mpq_video_feed_info)); + + if (feed_data == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: FAILED to allocate private video feed data\n", + __func__); + + ret = -ENOMEM; + goto init_failed; + } + + /* get and store framing information if required */ + if (!mpq_dmx_info.decoder_framing) { + mpq_dmx_get_pattern_params(&feed->indexing_params, + &feed_data->patterns, &feed_data->patterns_num); + if (feed_data->patterns == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: FAILED to get framing pattern parameters\n", + __func__); + + ret = -EINVAL; + goto init_failed_free_priv_data; + } + } + + /* Allocate packet buffer holding the meta-data */ + packet_buffer = vmalloc(VIDEO_META_DATA_BUFFER_SIZE); + + if (packet_buffer == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: FAILED to allocate packets buffer\n", + __func__); + + ret = -ENOMEM; + goto init_failed_free_priv_data; + } + + /* + * Allocate payload buffer through ION. + * TODO: for scrambling support, need to check if the + * stream is scrambled and allocate the buffer with secure + * flag set. + */ + + actual_buffer_size = feed->buffer_size; + + actual_buffer_size += (SZ_4K - 1); + actual_buffer_size &= ~(SZ_4K - 1); + + feed_data->payload_buff_handle = + ion_alloc(mpq_demux->ion_client, + actual_buffer_size, + SZ_4K, + ION_HEAP(ION_CP_MM_HEAP_ID)); + + if (IS_ERR_OR_NULL(feed_data->payload_buff_handle)) { + ret = PTR_ERR(feed_data->payload_buff_handle); + + MPQ_DVB_ERR_PRINT( + "%s: FAILED to allocate payload buffer %d\n", + __func__, + ret); + + goto init_failed_free_packet_buffer; + } + + payload_buffer = + ion_map_kernel(mpq_demux->ion_client, + feed_data->payload_buff_handle, + 0); + + if (IS_ERR_OR_NULL(payload_buffer)) { + ret = PTR_ERR(payload_buffer); + + MPQ_DVB_ERR_PRINT( + "%s: FAILED to map payload buffer %d\n", + __func__, + ret); + + goto init_failed_free_payload_buffer; + } + + /* Register the new stream-buffer interface to MPQ adapter */ + switch (feed->pes_type) { + case DMX_TS_PES_VIDEO0: + feed_data->stream_interface = + MPQ_ADAPTER_VIDEO0_STREAM_IF; + break; + + case DMX_TS_PES_VIDEO1: + feed_data->stream_interface = + MPQ_ADAPTER_VIDEO1_STREAM_IF; + break; + + case DMX_TS_PES_VIDEO2: + feed_data->stream_interface = + MPQ_ADAPTER_VIDEO2_STREAM_IF; + break; + + case DMX_TS_PES_VIDEO3: + feed_data->stream_interface = + MPQ_ADAPTER_VIDEO3_STREAM_IF; + break; + + default: + MPQ_DVB_ERR_PRINT( + "%s: Invalid pes type %d\n", + __func__, + feed->pes_type); + ret = -EINVAL; + goto init_failed_unmap_payload_buffer; + } + + /* make sure not occupied already */ + stream_buffer = NULL; + mpq_adapter_get_stream_if( + feed_data->stream_interface, + &stream_buffer); + if (stream_buffer != NULL) { + MPQ_DVB_ERR_PRINT( + "%s: Video interface %d already occupied!\n", + __func__, + feed_data->stream_interface); + ret = -EBUSY; + goto init_failed_unmap_payload_buffer; + } + + feed_data->video_buffer = + &mpq_dmx_info.decoder_buffers[feed_data->stream_interface]; + + mpq_streambuffer_init( + feed_data->video_buffer, + payload_buffer, + actual_buffer_size, + packet_buffer, + VIDEO_META_DATA_BUFFER_SIZE); + + ret = + mpq_adapter_register_stream_if( + feed_data->stream_interface, + feed_data->video_buffer); + + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: mpq_adapter_register_stream_if failed, " + "err = %d\n", + __func__, + ret); + goto init_failed_unmap_payload_buffer; + } + + feed->buffer_size = actual_buffer_size; + feed_data->pes_payload_address = + (u32)feed_data->video_buffer->raw_data.data; + + feed_data->pes_header_left_bytes = PES_MANDATORY_FIELDS_LEN; + feed_data->pes_header_offset = 0; + feed->pusi_seen = 0; + feed->peslen = 0; + feed_data->fullness_wait_cancel = 0; + feed_data->last_framing_match_address = 0; + feed_data->last_framing_match_type = DMX_FRM_UNKNOWN; + feed_data->found_sequence_header_pattern = 0; + memset(&feed_data->prefix_size, 0, + sizeof(struct mpq_framing_prefix_size_masks)); + feed_data->first_pattern_offset = 0; + feed_data->first_prefix_size = 0; + feed_data->write_pts_dts = 0; + + spin_lock(&mpq_demux->feed_lock); + feed->priv = (void *)feed_data; + spin_unlock(&mpq_demux->feed_lock); + + return 0; + +init_failed_unmap_payload_buffer: + ion_unmap_kernel(mpq_demux->ion_client, + feed_data->payload_buff_handle); +init_failed_free_payload_buffer: + ion_free(mpq_demux->ion_client, + feed_data->payload_buff_handle); +init_failed_free_packet_buffer: + vfree(packet_buffer); +init_failed_free_priv_data: + vfree(feed_data); + feed->priv = NULL; +init_failed: + + return ret; +} +EXPORT_SYMBOL(mpq_dmx_init_video_feed); + + +int mpq_dmx_terminate_video_feed(struct dvb_demux_feed *feed) +{ + struct mpq_video_feed_info *feed_data; + struct mpq_demux *mpq_demux; + + if (feed->priv == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: invalid feed, feed->priv is NULL\n", + __func__); + + return -EINVAL; + } + + mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + feed_data = + (struct mpq_video_feed_info *)feed->priv; + + spin_lock(&mpq_demux->feed_lock); + feed->priv = NULL; + spin_unlock(&mpq_demux->feed_lock); + + wake_up_all(&feed_data->video_buffer->raw_data.queue); + + mpq_adapter_unregister_stream_if( + feed_data->stream_interface); + + vfree(feed_data->video_buffer->packet_data.data); + + ion_unmap_kernel(mpq_demux->ion_client, + feed_data->payload_buff_handle); + + ion_free(mpq_demux->ion_client, + feed_data->payload_buff_handle); + + vfree(feed_data); + + return 0; +} +EXPORT_SYMBOL(mpq_dmx_terminate_video_feed); + +int mpq_dmx_decoder_fullness_init(struct dvb_demux_feed *feed) +{ + struct mpq_demux *mpq_demux; + + mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + if (mpq_dmx_is_video_feed(feed)) { + struct mpq_video_feed_info *feed_data; + + spin_lock(&mpq_demux->feed_lock); + + if (feed->priv == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: invalid feed, feed->priv is NULL\n", + __func__); + spin_unlock(&mpq_demux->feed_lock); + return -EINVAL; + } + + feed_data = + (struct mpq_video_feed_info *)feed->priv; + + feed_data->fullness_wait_cancel = 0; + + spin_unlock(&mpq_demux->feed_lock); + + return 0; + } + + /* else */ + MPQ_DVB_DBG_PRINT( + "%s: Invalid feed type %d\n", + __func__, + feed->pes_type); + + return -EINVAL; +} +EXPORT_SYMBOL(mpq_dmx_decoder_fullness_init); + +int mpq_dmx_decoder_fullness_wait( + struct dvb_demux_feed *feed, + size_t required_space) +{ + struct mpq_demux *mpq_demux; + + mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + if (mpq_dmx_is_video_feed(feed)) { + int ret; + struct mpq_video_feed_info *feed_data; + struct dvb_ringbuffer *video_buff; + + spin_lock(&mpq_demux->feed_lock); + + if (feed->priv == NULL) { + spin_unlock(&mpq_demux->feed_lock); + return -EINVAL; + } + + feed_data = + (struct mpq_video_feed_info *)feed->priv; + + video_buff = + &feed_data->video_buffer->raw_data; + + ret = 0; + if ((feed_data != NULL) && + (!feed_data->fullness_wait_cancel) && + (dvb_ringbuffer_free(video_buff) < required_space)) { + DEFINE_WAIT(__wait); + for (;;) { + prepare_to_wait( + &video_buff->queue, + &__wait, + TASK_INTERRUPTIBLE); + + if ((feed->priv == NULL) || + (feed_data->fullness_wait_cancel) || + (dvb_ringbuffer_free(video_buff) >= + required_space)) + break; + + if (!signal_pending(current)) { + spin_unlock(&mpq_demux->feed_lock); + schedule(); + spin_lock(&mpq_demux->feed_lock); + continue; + } + ret = -ERESTARTSYS; + break; + } + finish_wait(&video_buff->queue, &__wait); + } + + if (ret < 0) { + spin_unlock(&mpq_demux->feed_lock); + return ret; + } + + if ((feed->priv == NULL) || + (feed_data->fullness_wait_cancel)) { + spin_unlock(&mpq_demux->feed_lock); + return -EINVAL; + } + + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + /* else */ + MPQ_DVB_DBG_PRINT( + "%s: Invalid feed type %d\n", + __func__, + feed->pes_type); + + return -EINVAL; +} +EXPORT_SYMBOL(mpq_dmx_decoder_fullness_wait); + +int mpq_dmx_decoder_fullness_abort(struct dvb_demux_feed *feed) +{ + struct mpq_demux *mpq_demux; + + mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + if (mpq_dmx_is_video_feed(feed)) { + struct mpq_video_feed_info *feed_data; + struct dvb_ringbuffer *video_buff; + + spin_lock(&mpq_demux->feed_lock); + + if (feed->priv == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: invalid feed, feed->priv is NULL\n", + __func__); + spin_unlock(&mpq_demux->feed_lock); + return -EINVAL; + } + + feed_data = + (struct mpq_video_feed_info *)feed->priv; + + video_buff = + &feed_data->video_buffer->raw_data; + + feed_data->fullness_wait_cancel = 1; + spin_unlock(&mpq_demux->feed_lock); + + wake_up_all(&video_buff->queue); + + return 0; + } + + /* else */ + MPQ_DVB_ERR_PRINT( + "%s: Invalid feed type %d\n", + __func__, + feed->pes_type); + + return -EINVAL; +} +EXPORT_SYMBOL(mpq_dmx_decoder_fullness_abort); + + +static inline int mpq_dmx_parse_mandatory_pes_header( + struct dvb_demux_feed *feed, + struct mpq_video_feed_info *feed_data, + struct pes_packet_header *pes_header, + const u8 *buf, + u32 *ts_payload_offset, + int *bytes_avail) +{ + int left_size, copy_len; + + if (feed_data->pes_header_offset < PES_MANDATORY_FIELDS_LEN) { + left_size = + PES_MANDATORY_FIELDS_LEN - + feed_data->pes_header_offset; + + copy_len = (left_size > *bytes_avail) ? + *bytes_avail : + left_size; + + memcpy((u8 *)((u8 *)pes_header + feed_data->pes_header_offset), + (buf + *ts_payload_offset), + copy_len); + + feed_data->pes_header_offset += copy_len; + + if (left_size > *bytes_avail) + return -EINVAL; + + /* else - we have beginning of PES header */ + *bytes_avail -= left_size; + *ts_payload_offset += left_size; + + /* Make sure the PES packet is valid */ + if (mpq_dmx_is_valid_video_pes(pes_header) < 0) { + /* + * Since the new PES header parsing + * failed, reset pusi_seen to drop all + * data until next PUSI + */ + feed->pusi_seen = 0; + feed_data->pes_header_offset = 0; + + MPQ_DVB_ERR_PRINT( + "%s: invalid packet\n", + __func__); + + return -EINVAL; + } + + feed_data->pes_header_left_bytes = + pes_header->pes_header_data_length; + } + + return 0; +} + +static inline int mpq_dmx_parse_remaining_pes_header( + struct dvb_demux_feed *feed, + struct mpq_video_feed_info *feed_data, + struct pes_packet_header *pes_header, + const u8 *buf, + u32 *ts_payload_offset, + int *bytes_avail) +{ + int left_size, copy_len; + + /* Remainning header bytes that need to be processed? */ + if (!feed_data->pes_header_left_bytes) + return 0; + + /* Did we capture the PTS value (if exists)? */ + if ((*bytes_avail != 0) && + (feed_data->pes_header_offset < + (PES_MANDATORY_FIELDS_LEN+5)) && + ((pes_header->pts_dts_flag == 2) || + (pes_header->pts_dts_flag == 3))) { + + /* 5 more bytes should be there */ + left_size = + PES_MANDATORY_FIELDS_LEN + 5 - + feed_data->pes_header_offset; + + copy_len = (left_size > *bytes_avail) ? + *bytes_avail : + left_size; + + memcpy((u8 *)((u8 *)pes_header + feed_data->pes_header_offset), + (buf + *ts_payload_offset), + copy_len); + + feed_data->pes_header_offset += copy_len; + feed_data->pes_header_left_bytes -= copy_len; + + if (left_size > *bytes_avail) + return -EINVAL; + + /* else - we have the PTS */ + *bytes_avail -= copy_len; + *ts_payload_offset += copy_len; + feed_data->write_pts_dts = 1; + } + + /* Did we capture the DTS value (if exist)? */ + if ((*bytes_avail != 0) && + (feed_data->pes_header_offset < + (PES_MANDATORY_FIELDS_LEN+10)) && + (pes_header->pts_dts_flag == 3)) { + + /* 5 more bytes should be there */ + left_size = + PES_MANDATORY_FIELDS_LEN + 10 - + feed_data->pes_header_offset; + + copy_len = (left_size > *bytes_avail) ? + *bytes_avail : + left_size; + + memcpy((u8 *)((u8 *)pes_header + feed_data->pes_header_offset), + (buf + *ts_payload_offset), + copy_len); + + feed_data->pes_header_offset += copy_len; + feed_data->pes_header_left_bytes -= copy_len; + + if (left_size > *bytes_avail) + return -EINVAL; + + /* else - we have the DTS */ + *bytes_avail -= copy_len; + *ts_payload_offset += copy_len; + feed_data->write_pts_dts = 1; + } + + /* Any more header bytes?! */ + if (feed_data->pes_header_left_bytes >= *bytes_avail) { + feed_data->pes_header_left_bytes -= *bytes_avail; + return -EINVAL; + } + + /* Got PES header, process payload */ + *bytes_avail -= feed_data->pes_header_left_bytes; + *ts_payload_offset += feed_data->pes_header_left_bytes; + feed_data->pes_header_left_bytes = 0; + + return 0; +} + +static inline void mpq_dmx_get_pts_dts(struct mpq_video_feed_info *feed_data, + struct pes_packet_header *pes_header, + struct mpq_adapter_video_meta_data *meta_data, + enum dmx_packet_type packet_type) +{ + struct dmx_pts_dts_info *info; + + if (packet_type == DMX_PES_PACKET) + info = &(meta_data->info.pes.pts_dts_info); + else + info = &(meta_data->info.framing.pts_dts_info); + + if (feed_data->write_pts_dts) { + if ((pes_header->pts_dts_flag == 2) || + (pes_header->pts_dts_flag == 3)) { + info->pts_exist = 1; + + info->pts = + ((u64)pes_header->pts_1 << 30) | + ((u64)pes_header->pts_2 << 22) | + ((u64)pes_header->pts_3 << 15) | + ((u64)pes_header->pts_4 << 7) | + (u64)pes_header->pts_5; + } else { + info->pts_exist = 0; + info->pts = 0; + } + + if (pes_header->pts_dts_flag == 3) { + info->dts_exist = 1; + + info->dts = + ((u64)pes_header->dts_1 << 30) | + ((u64)pes_header->dts_2 << 22) | + ((u64)pes_header->dts_3 << 15) | + ((u64)pes_header->dts_4 << 7) | + (u64)pes_header->dts_5; + } else { + info->dts_exist = 0; + info->dts = 0; + } + } else { + info->pts_exist = 0; + info->dts_exist = 0; + } +} + +static int mpq_dmx_process_video_packet_framing( + struct dvb_demux_feed *feed, + const u8 *buf) +{ + int bytes_avail; + u32 ts_payload_offset; + struct mpq_video_feed_info *feed_data; + const struct ts_packet_header *ts_header; + struct mpq_streambuffer *stream_buffer; + struct pes_packet_header *pes_header; + struct mpq_demux *mpq_demux; + + struct mpq_framing_pattern_lookup_results framing_res; + int found_patterns = 0; + int first_pattern = 0; + int i; + u32 pattern_addr = 0; + int is_video_frame = 0; + + mpq_demux = (struct mpq_demux *)feed->demux->priv; + + spin_lock(&mpq_demux->feed_lock); + + feed_data = (struct mpq_video_feed_info *)feed->priv; + + if (unlikely(feed_data == NULL)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + ts_header = (const struct ts_packet_header *)buf; + + stream_buffer = feed_data->video_buffer; + + pes_header = &feed_data->pes_header; + + /* MPQ_DVB_DBG_PRINT("TS packet: %X %X %X %X %X%X %X %X %X\n", + ts_header->sync_byte, + ts_header->transport_error_indicator, + ts_header->payload_unit_start_indicator, + ts_header->transport_priority, + ts_header->pid_msb, + ts_header->pid_lsb, + ts_header->transport_scrambling_control, + ts_header->adaptation_field_control, + ts_header->continuity_counter); */ + + /* Make sure this TS packet has a payload and not scrambled */ + if ((ts_header->sync_byte != 0x47) || + (ts_header->adaptation_field_control == 0) || + (ts_header->adaptation_field_control == 2) || + (ts_header->transport_scrambling_control)) { + /* continue to next packet */ + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + if (ts_header->payload_unit_start_indicator) { /* PUSI? */ + if (feed->pusi_seen) { /* Did we see PUSI before? */ + /* + * Double check that we are not in middle of + * previous PES header parsing. + */ + if (feed_data->pes_header_left_bytes != 0) { + MPQ_DVB_ERR_PRINT( + "%s: received PUSI" + "while handling PES header" + "of previous PES\n", + __func__); + } + + feed->peslen = 0; + feed_data->pes_header_offset = 0; + feed_data->pes_header_left_bytes = + PES_MANDATORY_FIELDS_LEN; + feed_data->write_pts_dts = 0; + } else { + feed->pusi_seen = 1; + } + } + + /* + * Parse PES data only if PUSI was encountered, + * otherwise the data is dropped + */ + if (!feed->pusi_seen) { + spin_unlock(&mpq_demux->feed_lock); + return 0; /* drop and wait for next packets */ + } + + ts_payload_offset = sizeof(struct ts_packet_header); + + /* Skip adaptation field if exists */ + if (ts_header->adaptation_field_control == 3) + ts_payload_offset += buf[ts_payload_offset] + 1; + + /* 188 bytes: the size of a TS packet including the TS packet header */ + bytes_avail = 188 - ts_payload_offset; + + /* Get the mandatory fields of the video PES header */ + if (mpq_dmx_parse_mandatory_pes_header(feed, feed_data, + pes_header, buf, + &ts_payload_offset, + &bytes_avail)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + if (mpq_dmx_parse_remaining_pes_header(feed, feed_data, + pes_header, buf, + &ts_payload_offset, + &bytes_avail)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + /* + * If we reached here, + * then we are now at the PES payload data + */ + if (bytes_avail == 0) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + /* + * the decoder requires demux to do framing, + * so search for the patterns now. + */ + found_patterns = mpq_dmx_framing_pattern_search( + feed_data->patterns, + feed_data->patterns_num, + (buf + ts_payload_offset), + bytes_avail, + &feed_data->prefix_size, + &framing_res); + + if (!(feed_data->found_sequence_header_pattern)) { + for (i = 0; i < found_patterns; i++) { + if ((framing_res.info[i].type == + DMX_FRM_MPEG2_SEQUENCE_HEADER) || + (framing_res.info[i].type == + DMX_FRM_H264_SPS) || + (framing_res.info[i].type == + DMX_FRM_VC1_SEQUENCE_HEADER)) { + + MPQ_DVB_DBG_PRINT( + "%s: Found Sequence Pattern, buf %p, " + "i = %d, offset = %d, type = %d\n", + __func__, buf, i, + framing_res.info[i].offset, + framing_res.info[i].type); + + first_pattern = i; + feed_data->found_sequence_header_pattern = 1; + ts_payload_offset += + framing_res.info[i].offset; + bytes_avail -= framing_res.info[i].offset; + + if (framing_res.info[i].used_prefix_size) { + feed_data->first_prefix_size = + framing_res.info[i]. + used_prefix_size; + } + /* + * if this is the first pattern we write, + * no need to take offset into account since we + * dropped all data before it (so effectively + * offset is 0). + * we save the first pattern offset and take + * it into consideration for the rest of the + * patterns found in this buffer. + */ + feed_data->first_pattern_offset = + framing_res.info[i].offset; + break; + } + } + } + + /* + * If decoder requires demux to do framing, + * pass data to decoder only after sequence header + * or equivalent is found. Otherwise the data is dropped. + */ + if (!(feed_data->found_sequence_header_pattern)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + /* + * write prefix used to find first Sequence pattern, if needed. + * feed_data->patterns[0].pattern always contains the Sequence + * pattern. + */ + if (feed_data->first_prefix_size) { + if (mpq_streambuffer_data_write(stream_buffer, + (feed_data->patterns[0].pattern), + feed_data->first_prefix_size) < 0) { + mpq_demux->decoder_tsp_drop_count++; + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + feed_data->first_prefix_size = 0; + } + /* write data to payload buffer */ + if (mpq_streambuffer_data_write(stream_buffer, + (buf + ts_payload_offset), + bytes_avail) < 0) { + mpq_demux->decoder_tsp_drop_count++; + } else { + struct mpq_streambuffer_packet_header packet; + struct mpq_adapter_video_meta_data meta_data; + + feed->peslen += bytes_avail; + + meta_data.packet_type = DMX_FRAMING_INFO_PACKET; + packet.user_data_len = + sizeof(struct mpq_adapter_video_meta_data); + + for (i = first_pattern; i < found_patterns; i++) { + if (feed_data->last_framing_match_address) { + is_video_frame = mpq_dmx_is_video_frame( + feed->indexing_params.standard, + feed_data->last_framing_match_type); + if (is_video_frame == 1) { + mpq_dmx_get_pts_dts(feed_data, + pes_header, + &meta_data, + DMX_FRAMING_INFO_PACKET); + } else { + meta_data.info.framing. + pts_dts_info.pts_exist = 0; + meta_data.info.framing. + pts_dts_info.dts_exist = 0; + } + /* + * writing meta-data that includes + * framing information + */ + meta_data.info.framing.pattern_type = + feed_data->last_framing_match_type; + packet.raw_data_addr = + feed_data->last_framing_match_address; + + pattern_addr = feed_data->pes_payload_address + + framing_res.info[i].offset - + framing_res.info[i].used_prefix_size; + + if ((pattern_addr - + feed_data->first_pattern_offset) < + feed_data->last_framing_match_address) { + /* wraparound case */ + packet.raw_data_len = + (pattern_addr - + feed_data-> + last_framing_match_address + + stream_buffer->raw_data.size) - + feed_data->first_pattern_offset; + } else { + packet.raw_data_len = + pattern_addr - + feed_data-> + last_framing_match_address - + feed_data->first_pattern_offset; + } + + MPQ_DVB_DBG_PRINT("Writing Packet: " + "addr = 0x%X, len = %d, type = %d, " + "isPts = %d, isDts = %d\n", + packet.raw_data_addr, + packet.raw_data_len, + meta_data.info.framing.pattern_type, + meta_data.info.framing. + pts_dts_info.pts_exist, + meta_data.info.framing. + pts_dts_info.dts_exist); + + if (mpq_streambuffer_pkt_write(stream_buffer, + &packet, + (u8 *)&meta_data) < 0) { + MPQ_DVB_ERR_PRINT( + "%s: " + "Couldn't write packet. " + "Should never happen\n", + __func__); + } else { + if (is_video_frame == 1) + feed_data->write_pts_dts = 0; + } + } + + /* save the last match for next time */ + feed_data->last_framing_match_type = + framing_res.info[i].type; + + feed_data->last_framing_match_address = + (feed_data->pes_payload_address + + framing_res.info[i].offset - + framing_res.info[i].used_prefix_size - + feed_data->first_pattern_offset); + } + /* + * the first pattern offset is needed only for the group of + * patterns that are found and written with the first pattern. + */ + feed_data->first_pattern_offset = 0; + + feed_data->pes_payload_address = + (u32)stream_buffer->raw_data.data + + stream_buffer->raw_data.pwrite; + } + + spin_unlock(&mpq_demux->feed_lock); + + return 0; +} + +static int mpq_dmx_process_video_packet_no_framing( + struct dvb_demux_feed *feed, + const u8 *buf) +{ + int bytes_avail; + u32 ts_payload_offset; + struct mpq_video_feed_info *feed_data; + const struct ts_packet_header *ts_header; + struct mpq_streambuffer *stream_buffer; + struct pes_packet_header *pes_header; + struct mpq_demux *mpq_demux; + + mpq_demux = (struct mpq_demux *)feed->demux->priv; + + spin_lock(&mpq_demux->feed_lock); + + feed_data = (struct mpq_video_feed_info *)feed->priv; + + if (unlikely(feed_data == NULL)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + ts_header = (const struct ts_packet_header *)buf; + + stream_buffer = feed_data->video_buffer; + + pes_header = &feed_data->pes_header; + + /* MPQ_DVB_DBG_PRINT("TS packet: %X %X %X %X %X%X %X %X %X\n", + ts_header->sync_byte, + ts_header->transport_error_indicator, + ts_header->payload_unit_start_indicator, + ts_header->transport_priority, + ts_header->pid_msb, + ts_header->pid_lsb, + ts_header->transport_scrambling_control, + ts_header->adaptation_field_control, + ts_header->continuity_counter); */ + + /* Make sure this TS packet has a payload and not scrambled */ + if ((ts_header->sync_byte != 0x47) || + (ts_header->adaptation_field_control == 0) || + (ts_header->adaptation_field_control == 2) || + (ts_header->transport_scrambling_control)) { + /* continue to next packet */ + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + if (ts_header->payload_unit_start_indicator) { /* PUSI? */ + if (feed->pusi_seen) { /* Did we see PUSI before? */ + struct mpq_streambuffer_packet_header packet; + struct mpq_adapter_video_meta_data meta_data; + + /* + * Close previous PES. + * Push new packet to the meta-data buffer. + * Double check that we are not in middle of + * previous PES header parsing. + */ + + if (0 == feed_data->pes_header_left_bytes) { + packet.raw_data_addr = + feed_data->pes_payload_address; + + packet.raw_data_len = feed->peslen; + + packet.user_data_len = + sizeof(struct + mpq_adapter_video_meta_data); + + mpq_dmx_get_pts_dts(feed_data, pes_header, + &meta_data, + DMX_PES_PACKET); + + meta_data.packet_type = DMX_PES_PACKET; + + if (mpq_streambuffer_pkt_write( + stream_buffer, + &packet, + (u8 *)&meta_data) < 0) + MPQ_DVB_ERR_PRINT( + "%s: " + "Couldn't write packet. " + "Should never happen\n", + __func__); + else + feed_data->write_pts_dts = 0; + } else { + MPQ_DVB_ERR_PRINT( + "%s: received PUSI" + "while handling PES header" + "of previous PES\n", + __func__); + } + + /* Reset PES info */ + feed_data->pes_payload_address = + (u32)stream_buffer->raw_data.data + + stream_buffer->raw_data.pwrite; + + feed->peslen = 0; + feed_data->pes_header_offset = 0; + feed_data->pes_header_left_bytes = + PES_MANDATORY_FIELDS_LEN; + } else { + feed->pusi_seen = 1; + } + } + + /* + * Parse PES data only if PUSI was encountered, + * otherwise the data is dropped + */ + if (!feed->pusi_seen) { + spin_unlock(&mpq_demux->feed_lock); + return 0; /* drop and wait for next packets */ + } + + ts_payload_offset = sizeof(struct ts_packet_header); + + /* Skip adaptation field if exists */ + if (ts_header->adaptation_field_control == 3) + ts_payload_offset += buf[ts_payload_offset] + 1; + + /* 188 bytes: size of a TS packet including the TS packet header */ + bytes_avail = 188 - ts_payload_offset; + + /* Get the mandatory fields of the video PES header */ + if (mpq_dmx_parse_mandatory_pes_header(feed, feed_data, + pes_header, buf, + &ts_payload_offset, + &bytes_avail)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + if (mpq_dmx_parse_remaining_pes_header(feed, feed_data, + pes_header, buf, + &ts_payload_offset, + &bytes_avail)) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + /* + * If we reached here, + * then we are now at the PES payload data + */ + if (bytes_avail == 0) { + spin_unlock(&mpq_demux->feed_lock); + return 0; + } + + if (mpq_streambuffer_data_write( + stream_buffer, + buf+ts_payload_offset, + bytes_avail) < 0) + mpq_demux->decoder_tsp_drop_count++; + else + feed->peslen += bytes_avail; + + spin_unlock(&mpq_demux->feed_lock); + + return 0; +} + +int mpq_dmx_process_video_packet( + struct dvb_demux_feed *feed, + const u8 *buf) +{ + if (mpq_dmx_info.decoder_framing) + return mpq_dmx_process_video_packet_no_framing(feed, buf); + else + return mpq_dmx_process_video_packet_framing(feed, buf); +} +EXPORT_SYMBOL(mpq_dmx_process_video_packet); + +int mpq_dmx_process_pcr_packet( + struct dvb_demux_feed *feed, + const u8 *buf) +{ + int i; + u64 pcr; + u64 stc; + u8 output[PCR_STC_LEN]; + struct mpq_demux *mpq_demux = + (struct mpq_demux *)feed->demux->priv; + const struct ts_packet_header *ts_header; + const struct ts_adaptation_field *adaptation_field; + + /* + * When we play from front-end, we configure HW + * to output the extra timestamp, if we are playing + * from DVR, make sure the format is 192 packet. + */ + if ((mpq_demux->source >= DMX_SOURCE_DVR0) && + (mpq_demux->demux.tsp_format != DMX_TSP_FORMAT_192_TAIL)) { + MPQ_DVB_ERR_PRINT( + "%s: invalid packet format %d for PCR extraction\n", + __func__, + mpq_demux->demux.tsp_format); + + return -EINVAL; + } + + ts_header = (const struct ts_packet_header *)buf; + + /* Make sure this TS packet has a adaptation field */ + if ((ts_header->sync_byte != 0x47) || + (ts_header->adaptation_field_control == 0) || + (ts_header->adaptation_field_control == 1)) { + return 0; + } + + adaptation_field = (const struct ts_adaptation_field *) + (buf + sizeof(struct ts_packet_header)); + + if ((!adaptation_field->adaptation_field_length) || + (!adaptation_field->PCR_flag)) + return 0; /* 0 adaptation field or no PCR */ + + pcr = ((u64)adaptation_field->program_clock_reference_base_1) << 25; + pcr += ((u64)adaptation_field->program_clock_reference_base_2) << 17; + pcr += ((u64)adaptation_field->program_clock_reference_base_3) << 9; + pcr += ((u64)adaptation_field->program_clock_reference_base_4) << 1; + pcr += adaptation_field->program_clock_reference_base_5; + pcr *= 300; + pcr += + (((u64)adaptation_field->program_clock_reference_ext_1) << 8) + + adaptation_field->program_clock_reference_ext_2; + + stc = buf[190] << 16; + stc += buf[189] << 8; + stc += buf[188]; + stc *= 256; /* convert from 105.47 KHZ to 27MHz */ + + output[0] = adaptation_field->discontinuity_indicator; + + for (i = 1; i <= 8; i++) + output[i] = (stc >> ((8-i) << 3)) & 0xFF; + + for (i = 9; i <= 16; i++) + output[i] = (pcr >> ((16-i) << 3)) & 0xFF; + + feed->cb.ts(output, PCR_STC_LEN, + NULL, 0, + &feed->feed.ts, DMX_OK); + return 0; +} +EXPORT_SYMBOL(mpq_dmx_process_pcr_packet); + diff --git a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_common.h b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_common.h new file mode 100644 index 0000000000000000000000000000000000000000..0275b145c0fb84f77a5a4213d50a8954c699b95a --- /dev/null +++ b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_common.h @@ -0,0 +1,538 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MPQ_DMX_PLUGIN_COMMON_H +#define _MPQ_DMX_PLUGIN_COMMON_H + +#include + +#include "dvbdev.h" +#include "dmxdev.h" +#include "demux.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "mpq_adapter.h" + + +/* Max number open() request can be done on demux device */ +#define MPQ_MAX_DMX_FILES 128 + + +/** + * TSIF alias name length + */ +#define TSIF_NAME_LENGTH 10 + +#define MPQ_MAX_FOUND_PATTERNS 5 + +/** + * struct mpq_demux - mpq demux information + * @demux: The dvb_demux instance used by mpq_demux + * @dmxdev: The dmxdev instance used by mpq_demux + * @fe_memory: Handle of front-end memory source to mpq_demux + * @source: The current source connected to the demux + * @is_initialized: Indicates whether this demux device was + * initialized or not. + * @ion_client: ION demux client used to allocate memory from ION. + * @feed_lock: Lock used to protect against private feed data + * @hw_notification_rate: Notification rate in msec, exposed in debugfs. + * @hw_notification_count: Notification count, exposed in debugfs. + * @hw_notification_size: Notification size in bytes, exposed in debugfs. + * @decoder_tsp_drop_count: Counter of number of dropped TS packets + * due to decoder buffer fullness, exposed in debugfs. + * @last_notification_time: Time of last HW notification. + */ +struct mpq_demux { + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_memory; + dmx_source_t source; + int is_initialized; + struct ion_client *ion_client; + spinlock_t feed_lock; + + /* debug-fs */ + u32 hw_notification_rate; + u32 hw_notification_count; + u32 hw_notification_size; + u32 decoder_tsp_drop_count; + struct timespec last_notification_time; +}; + +/** + * mpq_dmx_init - initialization and registration function of + * single MPQ demux device + * + * @adapter: The adapter to register mpq_demux to + * @mpq_demux: The mpq demux to initialize + * + * Every HW pluging need to provide implementation of such + * function that will be called for each demux device on the + * module initialization. The function mpq_demux_plugin_init + * should be called during the HW plugin module initialization. + */ +typedef int (*mpq_dmx_init)( + struct dvb_adapter *mpq_adapter, + struct mpq_demux *demux); + +/** + * struct ts_packet_header - Transport packet header + * as defined in MPEG2 transport stream standard. + */ +struct ts_packet_header { +#if defined(__BIG_ENDIAN_BITFIELD) + unsigned sync_byte:8; + unsigned transport_error_indicator:1; + unsigned payload_unit_start_indicator:1; + unsigned transport_priority:1; + unsigned pid_msb:5; + unsigned pid_lsb:8; + unsigned transport_scrambling_control:2; + unsigned adaptation_field_control:2; + unsigned continuity_counter:4; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + unsigned sync_byte:8; + unsigned pid_msb:5; + unsigned transport_priority:1; + unsigned payload_unit_start_indicator:1; + unsigned transport_error_indicator:1; + unsigned pid_lsb:8; + unsigned continuity_counter:4; + unsigned adaptation_field_control:2; + unsigned transport_scrambling_control:2; +#else +#error "Please fix " +#endif +} __packed; + +/** + * struct ts_adaptation_field - Adaptation field prefix + * as defined in MPEG2 transport stream standard. + */ +struct ts_adaptation_field { +#if defined(__BIG_ENDIAN_BITFIELD) + unsigned adaptation_field_length:8; + unsigned discontinuity_indicator:1; + unsigned random_access_indicator:1; + unsigned elementary_stream_priority_indicator:1; + unsigned PCR_flag:1; + unsigned OPCR_flag:1; + unsigned splicing_point_flag:1; + unsigned transport_private_data_flag:1; + unsigned adaptation_field_extension_flag:1; + unsigned program_clock_reference_base_1:8; + unsigned program_clock_reference_base_2:8; + unsigned program_clock_reference_base_3:8; + unsigned program_clock_reference_base_4:8; + unsigned program_clock_reference_base_5:1; + unsigned reserved:6; + unsigned program_clock_reference_ext_1:1; + unsigned program_clock_reference_ext_2:8; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + unsigned adaptation_field_length:8; + unsigned adaptation_field_extension_flag:1; + unsigned transport_private_data_flag:1; + unsigned splicing_point_flag:1; + unsigned OPCR_flag:1; + unsigned PCR_flag:1; + unsigned elementary_stream_priority_indicator:1; + unsigned random_access_indicator:1; + unsigned discontinuity_indicator:1; + unsigned program_clock_reference_base_1:8; + unsigned program_clock_reference_base_2:8; + unsigned program_clock_reference_base_3:8; + unsigned program_clock_reference_base_4:8; + unsigned program_clock_reference_ext_1:1; + unsigned reserved:6; + unsigned program_clock_reference_base_5:1; + unsigned program_clock_reference_ext_2:8; +#else +#error "Please fix " +#endif +} __packed; + + +/* + * PES packet header containing dts and/or pts values + * as defined in MPEG2 transport stream standard. + */ +struct pes_packet_header { +#if defined(__BIG_ENDIAN_BITFIELD) + unsigned packet_start_code_prefix_1:8; + unsigned packet_start_code_prefix_2:8; + unsigned packet_start_code_prefix_3:8; + unsigned stream_id:8; + unsigned pes_packet_length_msb:8; + unsigned pes_packet_length_lsb:8; + unsigned reserved_bits0:2; + unsigned pes_scrambling_control:2; + unsigned pes_priority:1; + unsigned data_alignment_indicator:1; + unsigned copyright:1; + unsigned original_or_copy:1; + unsigned pts_dts_flag:2; + unsigned escr_flag:1; + unsigned es_rate_flag:1; + unsigned dsm_trick_mode_flag:1; + unsigned additional_copy_info_flag:1; + unsigned pes_crc_flag:1; + unsigned pes_extension_flag:1; + unsigned pes_header_data_length:8; + unsigned reserved_bits1:4; + unsigned pts_1:3; + unsigned marker_bit0:1; + unsigned pts_2:8; + unsigned pts_3:7; + unsigned marker_bit1:1; + unsigned pts_4:8; + unsigned pts_5:7; + unsigned marker_bit2:1; + unsigned reserved_bits2:4; + unsigned dts_1:3; + unsigned marker_bit3:1; + unsigned dts_2:8; + unsigned dts_3:7; + unsigned marker_bit4:1; + unsigned dts_4:8; + unsigned dts_5:7; + unsigned marker_bit5:1; + unsigned reserved_bits3:4; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + unsigned packet_start_code_prefix_1:8; + unsigned packet_start_code_prefix_2:8; + unsigned packet_start_code_prefix_3:8; + unsigned stream_id:8; + unsigned pes_packet_length_lsb:8; + unsigned pes_packet_length_msb:8; + unsigned original_or_copy:1; + unsigned copyright:1; + unsigned data_alignment_indicator:1; + unsigned pes_priority:1; + unsigned pes_scrambling_control:2; + unsigned reserved_bits0:2; + unsigned pes_extension_flag:1; + unsigned pes_crc_flag:1; + unsigned additional_copy_info_flag:1; + unsigned dsm_trick_mode_flag:1; + unsigned es_rate_flag:1; + unsigned escr_flag:1; + unsigned pts_dts_flag:2; + unsigned pes_header_data_length:8; + unsigned marker_bit0:1; + unsigned pts_1:3; + unsigned reserved_bits1:4; + unsigned pts_2:8; + unsigned marker_bit1:1; + unsigned pts_3:7; + unsigned pts_4:8; + unsigned marker_bit2:1; + unsigned pts_5:7; + unsigned marker_bit3:1; + unsigned dts_1:3; + unsigned reserved_bits2:4; + unsigned dts_2:8; + unsigned marker_bit4:1; + unsigned dts_3:7; + unsigned dts_4:8; + unsigned marker_bit5:1; + unsigned dts_5:7; + unsigned reserved_bits3:4; +#else +#error "Please fix " +#endif +} __packed; + +/* + * mpq_framing_prefix_size_masks - possible prefix sizes. + * + * @size_mask: a bit mask (per pattern) of possible prefix sizes to use + * when searching for a pattern that started in the last buffer. + * Updated in mpq_dmx_framing_pattern_search for use in the next lookup + */ +struct mpq_framing_prefix_size_masks { + u32 size_mask[MPQ_MAX_FOUND_PATTERNS]; +}; + +/* + * mpq_video_feed_info - private data used for video feed. + * + * @plugin_data: Underlying plugin's own private data. + * @video_buffer: Holds the streamer buffer shared with + * the decoder for feeds having the data going to the decoder. + * @pes_header: Used for feeds that output data to decoder, + * holds PES header of current processed PES. + * @pes_header_left_bytes: Used for feeds that output data to decoder, + * holds remainning PES header bytes of current processed PES. + * @pes_header_offset: Holds the offset within the current processed + * pes header. + * @fullness_wait_cancel: Flag used to signal to abort waiting for + * decoder's fullness. + * @pes_payload_address: Used for feeds that output data to decoder, + * holds current PES payload start address. + * @payload_buff_handle: ION handle for the allocated payload buffer + * @stream_interface: The ID of the video stream interface registered + * with this stream buffer. + * @patterns: pointer to the framing patterns to look for. + * @patterns_num: number of framing patterns. + * @last_framing_match_address: Used for saving the raw data address of + * the previous pattern match found in this video feed. + * @last_framing_match_type: Used for saving the type of + * the previous pattern match found in this video feed. + * @found_sequence_header_pattern: Flag used to note that an MPEG-2 + * Sequence Header, H.264 SPS or VC-1 Sequence Header pattern + * (whichever is relevant according to the video standard) had already + * been found. + * @prefix_size: a bit mask representing the size(s) of possible prefixes + * to the pattern, already found in the previous buffer. If bit 0 is set, + * a prefix of size 1 was found. If bit 1 is set, a prefix of size 2 was + * found, etc. This supports a prefix size of up to 32, which is more + * than we need. The search function updates prefix_size as needed + * for the next buffer search. + * @first_pattern_offset: used to save the offset of the first pattern written + * to the stream buffer. + * @first_prefix_size: used to save the prefix size used to find the first + * pattern written to the stream buffer. + * @write_pts_dts: Flag used to decide if to write PTS/DTS information + * (if it is available in the PES header) in the meta-data passed + * to the video decoder. PTS/DTS information is written in the first + * packet after it is available. + */ +struct mpq_video_feed_info { + void *plugin_data; + struct mpq_streambuffer *video_buffer; + struct pes_packet_header pes_header; + u32 pes_header_left_bytes; + u32 pes_header_offset; + u32 pes_payload_address; + int fullness_wait_cancel; + struct ion_handle *payload_buff_handle; + enum mpq_adapter_stream_if stream_interface; + const struct mpq_framing_pattern_lookup_params *patterns; + int patterns_num; + u32 last_framing_match_address; + enum dmx_framing_pattern_type last_framing_match_type; + int found_sequence_header_pattern; + struct mpq_framing_prefix_size_masks prefix_size; + u32 first_pattern_offset; + u32 first_prefix_size; + int write_pts_dts; +}; + +/** + * mpq_demux_plugin_init - Initialize demux devices and register + * them to the dvb adapter. + * + * @dmx_init_func: Pointer to the function to be used + * to initialize demux of the udnerlying HW plugin. + * + * Return error code + * + * Should be called at the HW plugin module initialization. + */ +int mpq_dmx_plugin_init(mpq_dmx_init dmx_init_func); + +/** + * mpq_demux_plugin_exit - terminate demux devices. + * + * Should be called at the HW plugin module termination. + */ +void mpq_dmx_plugin_exit(void); + +/** + * mpq_dmx_set_source - implmenetation of set_source routine. + * + * @demux: The demux device to set its source. + * @src: The source to be set. + * + * Return error code + * + * Can be used by the underlying plugins to implement kernel + * demux API set_source routine. + */ +int mpq_dmx_set_source(struct dmx_demux *demux, const dmx_source_t *src); + +/** + * mpq_dmx_init_video_feed - Initializes video feed + * used to pass data to decoder directly. + * + * @feed: The feed used for the video TS packets + * + * Return error code. + * + * If the underlying plugin wishes to perform SW PES assmebly + * for the video data and stream it to the decoder, it should + * call this function when video feed is initialized before + * using mpq_dmx_process_video_packet. + * + * The function allocates mpq_video_feed_info and saves in + * feed->priv. + */ +int mpq_dmx_init_video_feed(struct dvb_demux_feed *feed); + +/** + * mpq_dmx_terminate_video_feed - Free private data of + * video feed allocated in mpq_dmx_init_video_feed + * + * @feed: The feed used for the video TS packets + * + * Return error code. + */ +int mpq_dmx_terminate_video_feed(struct dvb_demux_feed *feed); + +/** + * mpq_dmx_decoder_fullness_init - Initialize waiting + * mechanism on decoder's buffer fullness. + * + * @feed: The decoder's feed + * + * Return error code. + */ +int mpq_dmx_decoder_fullness_init( + struct dvb_demux_feed *feed); + +/** + * mpq_dmx_decoder_fullness_wait - Checks whether decoder buffer + * have free space as required, if not, wait for it. + * + * @feed: The decoder's feed + * @required_space: the required free space to wait for + * + * Return error code. + */ +int mpq_dmx_decoder_fullness_wait( + struct dvb_demux_feed *feed, + size_t required_space); + +/** + * mpq_dmx_decoder_fullness_abort - Aborts waiting + * on decoder's buffer fullness if any waiting is done + * now. After calling this, to wait again the user must + * call mpq_dmx_decoder_fullness_init. + * + * @feed: The decoder's feed + * + * Return error code. + */ +int mpq_dmx_decoder_fullness_abort( + struct dvb_demux_feed *feed); + +/** + * mpq_dmx_process_video_packet - Assemble PES data and output it + * to the stream-buffer connected to the decoder. + * + * @feed: The feed used for the video TS packets + * @buf: The buffer holding video TS packet. + * + * Return error code. + * + * The function assumes it receives buffer with single TS packet + * of the relevant PID. + * If the output buffer is full while assembly, the function drops + * the packet and does not write them to the output buffer. + * Scrambled packets are bypassed. + */ +int mpq_dmx_process_video_packet( + struct dvb_demux_feed *feed, + const u8 *buf); + +/** + * mpq_dmx_process_pcr_packet - Extract PCR/STC pairs from + * a 192 bytes packet. + * + * @feed: The feed used for the PCR TS packets + * @buf: The buffer holding pcr/stc packet. + * + * Return error code. + * + * The function assumes it receives buffer with single TS packet + * of the relevant PID, and that it has 4 bytes + * suffix as extra timestamp in the following format: + * + * Byte3: TSIF flags + * Byte0-2: TTS, 0..2^24-1 at 105.47 Khz (27*10^6/256). + * + * The function callbacks dmxdev after extraction of the pcr/stc + * pair. + */ +int mpq_dmx_process_pcr_packet( + struct dvb_demux_feed *feed, + const u8 *buf); + +/** + * mpq_dmx_is_video_feed - Returns whether the PES feed + * is video one. + * + * @feed: The feed to be checked. + * + * Return 1 if feed is video feed, 0 otherwise. + */ +static inline int mpq_dmx_is_video_feed(struct dvb_demux_feed *feed) +{ + if (feed->type != DMX_TYPE_TS) + return 0; + + if (feed->ts_type & (~TS_DECODER)) + return 0; + + if ((feed->pes_type == DMX_TS_PES_VIDEO0) || + (feed->pes_type == DMX_TS_PES_VIDEO1) || + (feed->pes_type == DMX_TS_PES_VIDEO2) || + (feed->pes_type == DMX_TS_PES_VIDEO3)) + return 1; + + return 0; +} + +/** + * mpq_dmx_is_pcr_feed - Returns whether the PES feed + * is PCR one. + * + * @feed: The feed to be checked. + * + * Return 1 if feed is PCR feed, 0 otherwise. + */ +static inline int mpq_dmx_is_pcr_feed(struct dvb_demux_feed *feed) +{ + if (feed->type != DMX_TYPE_TS) + return 0; + + if (feed->ts_type & (~TS_DECODER)) + return 0; + + if ((feed->pes_type == DMX_TS_PES_PCR0) || + (feed->pes_type == DMX_TS_PES_PCR1) || + (feed->pes_type == DMX_TS_PES_PCR2) || + (feed->pes_type == DMX_TS_PES_PCR3)) + return 1; + + return 0; +} + +/** + * mpq_dmx_init_hw_statistics - + * Extend dvb-demux debugfs with HW statistics. + * + * @mpq_demux: The mpq_demux device to initialize. + */ +void mpq_dmx_init_hw_statistics(struct mpq_demux *mpq_demux); + + +/** + * mpq_dmx_update_hw_statistics - + * Update dvb-demux debugfs with HW notification statistics. + * + * @mpq_demux: The mpq_demux device to update. + */ +void mpq_dmx_update_hw_statistics(struct mpq_demux *mpq_demux); + +#endif /* _MPQ_DMX_PLUGIN_COMMON_H */ + diff --git a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tsif.c b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tsif.c new file mode 100644 index 0000000000000000000000000000000000000000..bfd6b962eba2e1dcf0f263cab67722b0c983b8bd --- /dev/null +++ b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tsif.c @@ -0,0 +1,791 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "mpq_dvb_debug.h" +#include "mpq_dmx_plugin_common.h" + + +/* TSIF HW configuration: */ +#define TSIF_COUNT 2 + +/* Max number of section filters */ +#define DMX_TSIF_MAX_SECTION_FILTER_NUM 64 + +/* When TSIF driver notifies demux that new packets are received */ +#define DMX_TSIF_PACKETS_IN_CHUNK_DEF 16 +#define DMX_TSIF_CHUNKS_IN_BUF 8 +#define DMX_TSIF_TIME_LIMIT 10000 + +/* TSIF_DRIVER_MODE: 3 means manual control from debugfs. use 1 normally. */ +#define DMX_TSIF_DRIVER_MODE_DEF 1 + + +/* module parameters for load time configuration: */ +static int threshold = DMX_TSIF_PACKETS_IN_CHUNK_DEF; +static int mode = DMX_TSIF_DRIVER_MODE_DEF; +module_param(threshold, int, S_IRUGO); +module_param(mode, int, S_IRUGO); + +/* + * Work scheduled each time TSIF notifies dmx + * of new TS packet + */ +struct tsif_work { + struct work_struct work; + int tsif_id; +}; + + +/* + * TSIF driver information + */ +struct tsif_driver_info { + /* handler to TSIF driver */ + void *tsif_handler; + /* TSIF driver data buffer pointer */ + void *data_buffer; + /* TSIF driver data buffer size, in packets */ + int buffer_size; + /* TSIF driver read pointer */ + int ri; + /* TSIF driver write pointer */ + int wi; + /* TSIF driver state */ + enum tsif_state state; +}; + + +/* + * The following structure hold singelton information + * required for dmx implementation on top of TSIF. + */ +static struct +{ + /* Information for each TSIF input processing */ + struct { + /* work used to submit to workqueue for processing */ + struct tsif_work work; + + /* workqueue that processes TS packets from specific TSIF */ + struct workqueue_struct *workqueue; + + /* TSIF alias */ + char name[TSIF_NAME_LENGTH]; + + /* TSIF driver information */ + struct tsif_driver_info tsif_driver; + + /* TSIF reference count (counts start/stop operations */ + int ref_count; + + /* Pointer to the demux connected to this TSIF */ + struct mpq_demux *mpq_demux; + + /* mutex protecting the data-structure */ + struct mutex mutex; + } tsif[TSIF_COUNT]; +} mpq_dmx_tsif_info; + + +/** + * Worker function that processes the TS packets notified by the TSIF driver. + * + * @worker: the executed work + */ +static void mpq_dmx_tsif_work(struct work_struct *worker) +{ + struct tsif_work *tsif_work = + container_of(worker, struct tsif_work, work); + struct mpq_demux *mpq_demux; + struct tsif_driver_info *tsif_driver; + size_t packets = 0; + int tsif = tsif_work->tsif_id; + + mpq_demux = mpq_dmx_tsif_info.tsif[tsif].mpq_demux; + tsif_driver = &(mpq_dmx_tsif_info.tsif[tsif].tsif_driver); + + MPQ_DVB_DBG_PRINT( + "%s executed, tsif = %d\n", + __func__, + tsif); + + if (mutex_lock_interruptible(&mpq_dmx_tsif_info.tsif[tsif].mutex)) + return; + + /* Check if driver handler is still valid */ + if (tsif_driver->tsif_handler == NULL) { + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + MPQ_DVB_ERR_PRINT("%s: tsif_driver->tsif_handler is NULL!\n", + __func__); + return; + } + + tsif_get_state(tsif_driver->tsif_handler, &(tsif_driver->ri), + &(tsif_driver->wi), &(tsif_driver->state)); + + if ((tsif_driver->wi == tsif_driver->ri) || + (tsif_driver->state == tsif_state_stopped) || + (tsif_driver->state == tsif_state_error)) { + + mpq_demux->hw_notification_size = 0; + + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + + MPQ_DVB_ERR_PRINT( + "%s: invalid TSIF state (%d), wi = (%d), ri = (%d)\n", + __func__, + tsif_driver->state, tsif_driver->wi, tsif_driver->ri); + return; + } + + if (tsif_driver->wi > tsif_driver->ri) { + packets = (tsif_driver->wi - tsif_driver->ri); + mpq_demux->hw_notification_size = packets; + + dvb_dmx_swfilter_format( + &mpq_demux->demux, + (tsif_driver->data_buffer + + (tsif_driver->ri * TSIF_PKT_SIZE)), + (packets * TSIF_PKT_SIZE), + DMX_TSP_FORMAT_192_TAIL); + + tsif_driver->ri = + (tsif_driver->ri + packets) % tsif_driver->buffer_size; + + tsif_reclaim_packets(tsif_driver->tsif_handler, + tsif_driver->ri); + } else { + /* + * wi < ri, means wraparound on cyclic buffer. + * Handle in two stages. + */ + packets = (tsif_driver->buffer_size - tsif_driver->ri); + mpq_demux->hw_notification_size = packets; + + dvb_dmx_swfilter_format( + &mpq_demux->demux, + (tsif_driver->data_buffer + + (tsif_driver->ri * TSIF_PKT_SIZE)), + (packets * TSIF_PKT_SIZE), + DMX_TSP_FORMAT_192_TAIL); + + /* tsif_driver->ri should be 0 after this */ + tsif_driver->ri = + (tsif_driver->ri + packets) % tsif_driver->buffer_size; + + packets = tsif_driver->wi; + if (packets > 0) { + mpq_demux->hw_notification_size += packets; + + dvb_dmx_swfilter_format( + &mpq_demux->demux, + (tsif_driver->data_buffer + + (tsif_driver->ri * TSIF_PKT_SIZE)), + (packets * TSIF_PKT_SIZE), + DMX_TSP_FORMAT_192_TAIL); + + tsif_driver->ri = + (tsif_driver->ri + packets) % + tsif_driver->buffer_size; + } + + tsif_reclaim_packets(tsif_driver->tsif_handler, + tsif_driver->ri); + } + + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); +} + + +/** + * Callback function from TSIF driver when new data is ready. + * + * @user: user-data holding TSIF number + */ +static void mpq_tsif_callback(void *user) +{ + int tsif = (int)user; + struct work_struct *work; + struct mpq_demux *mpq_demux; + + MPQ_DVB_DBG_PRINT("%s executed, tsif = %d\n", __func__, tsif); + + /* Save statistics on TSIF notifications */ + mpq_demux = mpq_dmx_tsif_info.tsif[tsif].mpq_demux; + mpq_dmx_update_hw_statistics(mpq_demux); + + work = &mpq_dmx_tsif_info.tsif[tsif].work.work; + + /* Scheudle a new work to demux workqueue */ + if (!work_pending(work)) + queue_work(mpq_dmx_tsif_info.tsif[tsif].workqueue, work); +} + + +/** + * Attach to TSIF driver and start TSIF operation. + * + * @mpq_demux: the mpq_demux we are working on. + * + * Return error code. + */ +static int mpq_tsif_dmx_start(struct mpq_demux *mpq_demux) +{ + int ret = 0; + int tsif; + struct tsif_driver_info *tsif_driver; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* determine the TSIF we are reading from */ + if (mpq_demux->source == DMX_SOURCE_FRONT0) { + tsif = 0; + } else if (mpq_demux->source == DMX_SOURCE_FRONT1) { + tsif = 1; + } else { + /* invalid source */ + MPQ_DVB_ERR_PRINT( + "%s: invalid input source (%d)\n", + __func__, + mpq_demux->source); + + return -EINVAL; + } + + if (mutex_lock_interruptible(&mpq_dmx_tsif_info.tsif[tsif].mutex)) + return -ERESTARTSYS; + + if (mpq_dmx_tsif_info.tsif[tsif].ref_count == 0) { + tsif_driver = &(mpq_dmx_tsif_info.tsif[tsif].tsif_driver); + + /* Attach to TSIF driver */ + + tsif_driver->tsif_handler = + tsif_attach(tsif, mpq_tsif_callback, (void *)tsif); + if (IS_ERR_OR_NULL(tsif_driver->tsif_handler)) { + tsif_driver->tsif_handler = NULL; + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + MPQ_DVB_DBG_PRINT("%s: tsif_attach(%d) failed\n", + __func__, tsif); + return -ENODEV; + } + + /* Set TSIF driver mode */ + ret = tsif_set_mode(tsif_driver->tsif_handler, + mode); + if (ret < 0) { + MPQ_DVB_ERR_PRINT("%s: tsif_set_mode (%d) failed\n", + __func__, mode); + } + + /* Set TSIF buffer configuration */ + ret = tsif_set_buf_config(tsif_driver->tsif_handler, + threshold, + DMX_TSIF_CHUNKS_IN_BUF); + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: tsif_set_buf_config (%d, %d) failed\n", + __func__, threshold, + DMX_TSIF_CHUNKS_IN_BUF); + MPQ_DVB_ERR_PRINT("Using default TSIF driver values\n"); + } + + + /* Set TSIF driver time limit */ + /* TODO: needed?? */ +/* ret = tsif_set_time_limit(tsif_driver->tsif_handler, + DMX_TSIF_TIME_LIMIT); + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: tsif_set_time_limit (%d) failed\n", + __func__, DMX_TSIF_TIME_LIMIT); + } +*/ + + /* Start TSIF driver */ + ret = tsif_start(tsif_driver->tsif_handler); + if (ret < 0) { + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + MPQ_DVB_ERR_PRINT("%s: tsif_start failed\n", __func__); + return ret; + } + + /* + * Get data buffer information from TSIF driver + * (must be called after tsif_start) + */ + tsif_get_info(tsif_driver->tsif_handler, + &(tsif_driver->data_buffer), + &(tsif_driver->buffer_size)); + + /* save pointer to the mpq_demux we are working on */ + mpq_dmx_tsif_info.tsif[tsif].mpq_demux = mpq_demux; + } + mpq_dmx_tsif_info.tsif[tsif].ref_count++; + + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + + return ret; +} + + +/** + * Stop TSIF operation and detach from TSIF driver. + * + * @mpq_demux: the mpq_demux we are working on. + * + * Return error code. + */ +static int mpq_tsif_dmx_stop(struct mpq_demux *mpq_demux) +{ + int tsif; + struct tsif_driver_info *tsif_driver; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* determine the TSIF we are reading from */ + if (mpq_demux->source == DMX_SOURCE_FRONT0) { + tsif = 0; + } else if (mpq_demux->source == DMX_SOURCE_FRONT1) { + tsif = 1; + } else { + /* invalid source */ + MPQ_DVB_ERR_PRINT( + "%s: invalid input source (%d)\n", + __func__, + mpq_demux->source); + + return -EINVAL; + } + + if (mutex_lock_interruptible(&mpq_dmx_tsif_info.tsif[tsif].mutex)) + return -ERESTARTSYS; + + mpq_dmx_tsif_info.tsif[tsif].ref_count--; + + if (mpq_dmx_tsif_info.tsif[tsif].ref_count == 0) { + tsif_driver = &(mpq_dmx_tsif_info.tsif[tsif].tsif_driver); + tsif_stop(tsif_driver->tsif_handler); + tsif_detach(tsif_driver->tsif_handler); + /* + * temporarily release mutex and flush the work queue + * before setting tsif_handler to NULL + */ + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + flush_workqueue(mpq_dmx_tsif_info.tsif[tsif].workqueue); + /* re-acquire mutex */ + if (mutex_lock_interruptible( + &mpq_dmx_tsif_info.tsif[tsif].mutex)) + return -ERESTARTSYS; + + tsif_driver->tsif_handler = NULL; + tsif_driver->data_buffer = NULL; + tsif_driver->buffer_size = 0; + mpq_dmx_tsif_info.tsif[tsif].mpq_demux = NULL; + } + + mutex_unlock(&mpq_dmx_tsif_info.tsif[tsif].mutex); + + return 0; +} + + +/** + * Start filtering according to feed parameter. + * + * @feed: the feed we are working on. + * + * Return error code. + */ +static int mpq_tsif_dmx_start_filtering(struct dvb_demux_feed *feed) +{ + int ret = 0; + struct mpq_demux *mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + MPQ_DVB_DBG_PRINT( + "%s(%d) executed\n", + __func__, + feed->pid); + + if (mpq_demux == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: invalid mpq_demux handle\n", + __func__); + + return -EINVAL; + } + + if (mpq_demux->source < DMX_SOURCE_DVR0) { + /* Source from TSIF, need to configure TSIF hardware */ + ret = mpq_tsif_dmx_start(mpq_demux); + + if (ret < 0) { + MPQ_DVB_DBG_PRINT( + "%s: mpq_tsif_dmx_start failed(%d)\n", + __func__, + ret); + return ret; + } + } + + /* Always feed sections/PES starting from a new one and + * do not partial transfer data from older one + */ + feed->pusi_seen = 0; + + /* + * For video PES, data is tunneled to the decoder, + * initialize tunneling and pes parsing. + */ + if (mpq_dmx_is_video_feed(feed)) { + ret = mpq_dmx_init_video_feed(feed); + + if (ret < 0) { + MPQ_DVB_DBG_PRINT( + "%s: mpq_dmx_init_video_feed failed(%d)\n", + __func__, + ret); + + if (mpq_demux->source < DMX_SOURCE_DVR0) + mpq_tsif_dmx_stop(mpq_demux); + + return ret; + } + } + + return ret; +} + + +/** + * Stop filtering according to feed parameter. + * + * @feed: the feed we are working on. + * + * Return error code. + */ +static int mpq_tsif_dmx_stop_filtering(struct dvb_demux_feed *feed) +{ + int ret = 0; + struct mpq_demux *mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + MPQ_DVB_DBG_PRINT( + "%s(%d) executed\n", + __func__, + feed->pid); + + if (mpq_demux == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: invalid mpq_demux handle\n", + __func__); + + return -EINVAL; + } + + /* + * For video PES, data is tunneled to the decoder, + * terminate tunnel and pes parsing. + */ + if (mpq_dmx_is_video_feed(feed)) + mpq_dmx_terminate_video_feed(feed); + + if (mpq_demux->source < DMX_SOURCE_DVR0) { + /* Source from TSIF, need to configure TSIF hardware */ + ret = mpq_tsif_dmx_stop(mpq_demux); + } + + return ret; +} + + +/** + * TSIF demux plugin write-to-decoder function. + * + * @feed: The feed we are working on. + * @buf: The data buffer to process. + * @len: The data buffer length. + * + * Return error code + */ +static int mpq_tsif_dmx_write_to_decoder( + struct dvb_demux_feed *feed, + const u8 *buf, + size_t len) +{ + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* + * It is assumed that this function is called once for each + * TS packet of the relevant feed. + */ + if (len > TSIF_PKT_SIZE) + MPQ_DVB_DBG_PRINT( + "%s: warnning - len larger than one packet\n", + __func__); + + if (mpq_dmx_is_video_feed(feed)) + return mpq_dmx_process_video_packet(feed, buf); + + if (mpq_dmx_is_pcr_feed(feed)) + return mpq_dmx_process_pcr_packet(feed, buf); + + return 0; +} + +/** + * Returns demux capabilities of TSIF plugin + * + * @demux: demux device + * @caps: Returned capbabilities + * + * Return error code + */ +static int mpq_tsif_dmx_get_caps(struct dmx_demux *demux, + struct dmx_caps *caps) +{ + struct dvb_demux *dvb_demux = (struct dvb_demux *)demux->priv; + + if ((dvb_demux == NULL) || (caps == NULL)) { + MPQ_DVB_ERR_PRINT( + "%s: invalid parameters\n", + __func__); + + return -EINVAL; + } + + caps->caps = DMX_CAP_PULL_MODE | DMX_CAP_VIDEO_DECODER_DATA; + caps->num_decoders = MPQ_ADAPTER_MAX_NUM_OF_INTERFACES; + caps->num_demux_devices = CONFIG_DVB_MPQ_NUM_DMX_DEVICES; + caps->num_pid_filters = dvb_demux->feednum; + caps->num_section_filters = dvb_demux->filternum; + caps->num_section_filters_per_pid = dvb_demux->filternum; + caps->section_filter_length = DMX_FILTER_SIZE; + caps->num_demod_inputs = TSIF_COUNT; + caps->num_memory_inputs = CONFIG_DVB_MPQ_NUM_DMX_DEVICES; + caps->max_bitrate = 144; + caps->demod_input_max_bitrate = 72; + caps->memory_input_max_bitrate = 72; + + return 0; +} + +/** + * Initialize a single demux device. + * + * @mpq_adapter: MPQ DVB adapter + * @mpq_demux: The demux device to initialize + * + * Return error code + */ +static int mpq_tsif_dmx_init( + struct dvb_adapter *mpq_adapter, + struct mpq_demux *mpq_demux) +{ + int result; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* Set the kernel-demux object capabilities */ + mpq_demux->demux.dmx.capabilities = + DMX_TS_FILTERING | + DMX_PES_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING | + DMX_CRC_CHECKING | + DMX_TS_DESCRAMBLING; + + /* Set dvb-demux "virtual" function pointers */ + mpq_demux->demux.priv = (void *)mpq_demux; + mpq_demux->demux.filternum = DMX_TSIF_MAX_SECTION_FILTER_NUM; + mpq_demux->demux.feednum = MPQ_MAX_DMX_FILES; + mpq_demux->demux.start_feed = mpq_tsif_dmx_start_filtering; + mpq_demux->demux.stop_feed = mpq_tsif_dmx_stop_filtering; + mpq_demux->demux.write_to_decoder = mpq_tsif_dmx_write_to_decoder; + + mpq_demux->demux.decoder_fullness_init = + mpq_dmx_decoder_fullness_init; + + mpq_demux->demux.decoder_fullness_wait = + mpq_dmx_decoder_fullness_wait; + + mpq_demux->demux.decoder_fullness_abort = + mpq_dmx_decoder_fullness_abort; + + /* Initialize dvb_demux object */ + result = dvb_dmx_init(&mpq_demux->demux); + if (result < 0) { + MPQ_DVB_ERR_PRINT("%s: dvb_dmx_init failed\n", __func__); + goto init_failed; + } + + /* Now initailize the dmx-dev object */ + mpq_demux->dmxdev.filternum = MPQ_MAX_DMX_FILES; + mpq_demux->dmxdev.demux = &mpq_demux->demux.dmx; + mpq_demux->dmxdev.capabilities = + DMXDEV_CAP_DUPLEX | + DMXDEV_CAP_PULL_MODE | + DMXDEV_CAP_PCR_EXTRACTION | + DMXDEV_CAP_INDEXING; + + mpq_demux->dmxdev.demux->set_source = mpq_dmx_set_source; + mpq_demux->dmxdev.demux->get_caps = mpq_tsif_dmx_get_caps; + + result = dvb_dmxdev_init(&mpq_demux->dmxdev, mpq_adapter); + if (result < 0) { + MPQ_DVB_ERR_PRINT("%s: dvb_dmxdev_init failed (errno=%d)\n", + __func__, + result); + goto init_failed_dmx_release; + } + + /* Extend dvb-demux debugfs with TSIF statistics. */ + mpq_dmx_init_hw_statistics(mpq_demux); + + return 0; + +init_failed_dmx_release: + dvb_dmx_release(&mpq_demux->demux); +init_failed: + return result; +} + + +/** + * Module initialization function. + * + * Return error code + */ +static int __init mpq_dmx_tsif_plugin_init(void) +{ + int i; + int ret; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* check module parameters validity */ + if (threshold < 1) { + MPQ_DVB_ERR_PRINT( + "%s: invalid threshold parameter, using %d instead\n", + __func__, DMX_TSIF_PACKETS_IN_CHUNK_DEF); + threshold = DMX_TSIF_PACKETS_IN_CHUNK_DEF; + } + if ((mode < 1) || (mode > 3)) { + MPQ_DVB_ERR_PRINT( + "%s: invalid mode parameter, using %d instead\n", + __func__, DMX_TSIF_DRIVER_MODE_DEF); + mode = DMX_TSIF_DRIVER_MODE_DEF; + } + + for (i = 0; i < TSIF_COUNT; i++) { + mpq_dmx_tsif_info.tsif[i].work.tsif_id = i; + + INIT_WORK(&mpq_dmx_tsif_info.tsif[i].work.work, + mpq_dmx_tsif_work); + + snprintf(mpq_dmx_tsif_info.tsif[i].name, + TSIF_NAME_LENGTH, + "tsif_%d", + i); + + mpq_dmx_tsif_info.tsif[i].workqueue = + create_singlethread_workqueue( + mpq_dmx_tsif_info.tsif[i].name); + + if (mpq_dmx_tsif_info.tsif[i].workqueue == NULL) { + int j; + + for (j = 0; j < i; j++) { + destroy_workqueue( + mpq_dmx_tsif_info.tsif[j].workqueue); + mutex_destroy(&mpq_dmx_tsif_info.tsif[j].mutex); + } + + MPQ_DVB_ERR_PRINT( + "%s: create_singlethread_workqueue failed\n", + __func__); + + return -ENOMEM; + } + + mutex_init(&mpq_dmx_tsif_info.tsif[i].mutex); + + mpq_dmx_tsif_info.tsif[i].tsif_driver.tsif_handler = NULL; + mpq_dmx_tsif_info.tsif[i].ref_count = 0; + } + + ret = mpq_dmx_plugin_init(mpq_tsif_dmx_init); + + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: mpq_dmx_plugin_init failed (errno=%d)\n", + __func__, + ret); + + for (i = 0; i < TSIF_COUNT; i++) { + destroy_workqueue(mpq_dmx_tsif_info.tsif[i].workqueue); + mutex_destroy(&mpq_dmx_tsif_info.tsif[i].mutex); + } + } + + return ret; +} + + +/** + * Module exit function. + */ +static void __exit mpq_dmx_tsif_plugin_exit(void) +{ + int i; + struct tsif_driver_info *tsif_driver; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + for (i = 0; i < TSIF_COUNT; i++) { + mutex_lock(&mpq_dmx_tsif_info.tsif[i].mutex); + + tsif_driver = &(mpq_dmx_tsif_info.tsif[i].tsif_driver); + if (mpq_dmx_tsif_info.tsif[i].ref_count > 0) { + mpq_dmx_tsif_info.tsif[i].ref_count = 0; + if (tsif_driver->tsif_handler) + tsif_stop(tsif_driver->tsif_handler); + } + /* Detach from TSIF driver to avoid further notifications. */ + if (tsif_driver->tsif_handler) + tsif_detach(tsif_driver->tsif_handler); + + /* release mutex to allow work queue to finish scheduled work */ + mutex_unlock(&mpq_dmx_tsif_info.tsif[i].mutex); + /* flush the work queue and destroy it */ + flush_workqueue(mpq_dmx_tsif_info.tsif[i].workqueue); + destroy_workqueue(mpq_dmx_tsif_info.tsif[i].workqueue); + + mutex_destroy(&mpq_dmx_tsif_info.tsif[i].mutex); + } + + mpq_dmx_plugin_exit(); +} + + +module_init(mpq_dmx_tsif_plugin_init); +module_exit(mpq_dmx_tsif_plugin_exit); + +MODULE_DESCRIPTION("Qualcomm demux TSIF HW Plugin"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c new file mode 100644 index 0000000000000000000000000000000000000000..a552fdfcf941460f43495489782d2cf0618badaa --- /dev/null +++ b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c @@ -0,0 +1,894 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include "mpq_dvb_debug.h" +#include "mpq_dmx_plugin_common.h" + + +#define TSIF_COUNT 2 + +#define TSPP_MAX_PID_FILTER_NUM 16 + +/* Max number of section filters */ +#define TSPP_MAX_SECTION_FILTER_NUM 64 + +/* For each TSIF we allocate two pipes, one for PES and one for sections */ +#define TSPP_PES_CHANNEL 0 +#define TSPP_SECTION_CHANNEL 1 + +/* the channel_id set to TSPP driver based on TSIF number and channel type */ +#define TSPP_CHANNEL_ID(tsif, ch) ((tsif << 1) + ch) +#define TSPP_IS_PES_CHANNEL(ch_id) ((ch_id & 0x1) == 0) +#define TSPP_GET_TSIF_NUM(ch_id) (ch_id >> 1) + +/* mask that set to care for all bits in pid filter */ +#define TSPP_PID_MASK 0x1FFF + +/* dvb-demux defines pid 0x2000 as full capture pid */ +#define TSPP_PASS_THROUGH_PID 0x2000 + +/* TODO - NEED TO SET THESE PROPERLY + * once TSPP driver is ready, reduce TSPP_BUFFER_SIZE + * to single packet and set TSPP_BUFFER_COUNT accordingly + */ + +#define TSPP_RAW_TTS_SIZE 192 + +/* Size of single descriptor. + * Assuming 20MBit/sec stream, with 200 packets + * per descriptor there would be about 68 descriptors. + * Meanning about 68 interrupts per second. + */ +#define TSPP_BUFFER_SIZE (TSPP_RAW_TTS_SIZE * 200) + +/* Number of descriptors, total size: TSPP_BUFFER_SIZE*TSPP_BUFFER_COUNT */ +#define TSPP_BUFFER_COUNT (16) + +/* When TSPP notifies demux that new packets are received */ +#define TSPP_NOTIFICATION_SIZE (TSPP_RAW_TTS_SIZE * 100) + +/* Channel timeout in msec */ +#define TSPP_CHANNEL_TIMEOUT 16 + + +/* + * Work scheduled each time TSPP notifies dmx + * of new TS packet in some channel + */ +struct tspp_work { + struct work_struct work; + int channel_id; +}; + +/* The following structure hold singelton information + * required for dmx implementation on top of TSPP. + */ +static struct +{ + /* Information for each TSIF input processing */ + struct { + /* + * TSPP pipe holding all TS packets with PES data. + * The following is reference count for number of feeds + * allocated on that pipe. + */ + int pes_channel_ref; + + /* work used to submit to workqueue to process pes channel */ + struct tspp_work pes_work; + + /* + * TSPP pipe holding all TS packets with section data. + * The following is reference count for number of feeds + * allocated on that pipe. + */ + int section_channel_ref; + + /* work used to submit to workqueue to process pes channel */ + struct tspp_work section_work; + + /* + * Holds PIDs of allocated TSPP filters along with + * how many feeds are opened on same PID. + */ + struct { + int pid; + int ref_count; + } filters[TSPP_MAX_PID_FILTER_NUM]; + + /* workqueue that processes TS packets from specific TSIF */ + struct workqueue_struct *workqueue; + + /* TSIF alias */ + char name[TSIF_NAME_LENGTH]; + + /* Pointer to the demux connected to this TSIF */ + struct mpq_demux *mpq_demux; + + /* mutex protecting the data-structure */ + struct mutex mutex; + } tsif[TSIF_COUNT]; +} mpq_dmx_tspp_info; + + +/** + * Returns a free filter slot that can be used. + * + * @tsif: The TSIF to allocate filter from + * @channel_id: The channel allocating filter to + * + * Return filter index or -1 if no filters available + * + * To give priority to PES data, for pes filters + * the table is scanned from high to low priority, + * and sections from low to high priority. This way TSPP + * would get a match on PES data filters faster as they + * are scanned first. + */ +static int mpq_tspp_get_free_filter_slot(int tsif, int channel_id) +{ + int i; + + if (TSPP_IS_PES_CHANNEL(channel_id)) { + for (i = 0; i < TSPP_MAX_PID_FILTER_NUM; i++) + if (mpq_dmx_tspp_info.tsif[tsif].filters[i].pid == -1) + return i; + } else { + for (i = TSPP_MAX_PID_FILTER_NUM-1; i >= 0; i--) + if (mpq_dmx_tspp_info.tsif[tsif].filters[i].pid == -1) + return i; + } + + return -ENOMEM; +} + +/** + * Returns filter index of specific pid. + * + * @tsif: The TSIF to which the pid is allocated + * @pid: The pid to search for + * + * Return filter index or -1 if no filter available + */ +static int mpq_tspp_get_filter_slot(int tsif, int pid) +{ + int i; + + for (i = 0; i < TSPP_MAX_PID_FILTER_NUM; i++) + if (mpq_dmx_tspp_info.tsif[tsif].filters[i].pid == pid) + return i; + + return -EINVAL; +} + +/** + * Worker function that processes the TS packets notified by TSPP. + * + * @worker: the executed work + */ +static void mpq_dmx_tspp_work(struct work_struct *worker) +{ + struct tspp_work *tspp_work = + container_of(worker, struct tspp_work, work); + struct mpq_demux *mpq_demux; + int channel_id = tspp_work->channel_id; + int tsif = TSPP_GET_TSIF_NUM(channel_id); + const struct tspp_data_descriptor *tspp_data_desc; + int ref_count; + + mpq_demux = mpq_dmx_tspp_info.tsif[tsif].mpq_demux; + + /* Lock against the TSPP filters data-structure */ + if (mutex_lock_interruptible(&mpq_dmx_tspp_info.tsif[tsif].mutex)) + return; + + /* Make sure channel is still active */ + if (TSPP_IS_PES_CHANNEL(channel_id)) + ref_count = mpq_dmx_tspp_info.tsif[tsif].pes_channel_ref; + else + ref_count = mpq_dmx_tspp_info.tsif[tsif].section_channel_ref; + + if (ref_count == 0) { + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return; + } + + mpq_demux->hw_notification_size = 0; + + /* Go through all filled descriptors and perform demuxing on them */ + while ((tspp_data_desc = tspp_get_buffer(0, channel_id)) != NULL) { + mpq_demux->hw_notification_size += + (tspp_data_desc->size / TSPP_RAW_TTS_SIZE); + + dvb_dmx_swfilter_format( + &mpq_demux->demux, + tspp_data_desc->virt_base, + tspp_data_desc->size, + DMX_TSP_FORMAT_192_TAIL); + + /* Notify TSPP that the buffer is no longer needed */ + tspp_release_buffer(0, channel_id, tspp_data_desc->id); + } + + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); +} + +/** + * Callback function from TSPP when new data is ready. + * + * @channel_id: Channel with new TS packets + * @user: user-data holding TSIF number + */ +static void mpq_tspp_callback(u32 channel_id, void *user) +{ + int tsif = (int)user; + struct work_struct *work; + struct mpq_demux *mpq_demux; + + /* Save statistics on TSPP notifications */ + mpq_demux = mpq_dmx_tspp_info.tsif[tsif].mpq_demux; + mpq_dmx_update_hw_statistics(mpq_demux); + + if (TSPP_IS_PES_CHANNEL(channel_id)) + work = &mpq_dmx_tspp_info.tsif[tsif].pes_work.work; + else + work = &mpq_dmx_tspp_info.tsif[tsif].section_work.work; + + /* Scheudle a new work to demux workqueue */ + if (!work_pending(work)) + queue_work(mpq_dmx_tspp_info.tsif[tsif].workqueue, work); +} + +/** + * Configure TSPP channel to filter the PID of new feed. + * + * @feed: The feed to configure the channel with + * + * Return error status + * + * The function checks if the new PID can be added to an already + * allocated channel, if not, a new channel is allocated and configured. + */ +static int mpq_tspp_dmx_add_channel(struct dvb_demux_feed *feed) +{ + struct mpq_demux *mpq_demux = (struct mpq_demux *)feed->demux->priv; + enum tspp_source tspp_source; + struct tspp_filter tspp_filter; + int tsif; + int ret; + int channel_id; + int *channel_ref_count; + + /* determine the TSIF we are reading from */ + if (mpq_demux->source == DMX_SOURCE_FRONT0) { + tsif = 0; + tspp_source = TSPP_SOURCE_TSIF0; + } else if (mpq_demux->source == DMX_SOURCE_FRONT1) { + tsif = 1; + tspp_source = TSPP_SOURCE_TSIF1; + } else { + /* invalid source */ + MPQ_DVB_ERR_PRINT( + "%s: invalid input source (%d)\n", + __func__, + mpq_demux->source); + + return -EINVAL; + } + + if (mutex_lock_interruptible(&mpq_dmx_tspp_info.tsif[tsif].mutex)) + return -ERESTARTSYS; + + /* + * It is possible that this PID was already requested before. + * Can happen if we play and record same PES or PCR + * piggypacked on video packet. + */ + ret = mpq_tspp_get_filter_slot(tsif, feed->pid); + if (ret >= 0) { + /* PID already configured */ + mpq_dmx_tspp_info.tsif[tsif].filters[ret].ref_count++; + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return 0; + } + + /* determine to which pipe the feed should be routed: section or pes */ + if ((feed->type == DMX_TYPE_PES) || (feed->type == DMX_TYPE_TS)) { + channel_id = TSPP_CHANNEL_ID(tsif, TSPP_PES_CHANNEL); + channel_ref_count = + &mpq_dmx_tspp_info.tsif[tsif].pes_channel_ref; + } else { + channel_id = TSPP_CHANNEL_ID(tsif, TSPP_SECTION_CHANNEL); + channel_ref_count = + &mpq_dmx_tspp_info.tsif[tsif].section_channel_ref; + } + + /* check if required TSPP pipe is already allocated or not */ + if (*channel_ref_count == 0) { + ret = tspp_open_channel(0, channel_id); + + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: tspp_open_channel(%d) failed (%d)\n", + __func__, + channel_id, + ret); + + goto add_channel_failed; + } + + /* set TSPP source */ + ret = tspp_open_stream(0, channel_id, tspp_source); + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: tspp_select_source(%d,%d) failed (%d)\n", + __func__, + channel_id, + tspp_source, + ret); + + goto add_channel_close_ch; + } + + /* register notification on TS packets */ + tspp_register_notification(0, + channel_id, + mpq_tspp_callback, + (void *)tsif, + TSPP_CHANNEL_TIMEOUT); + + /* TODO: register allocater and provide allocation function + * that allocate from continous memory so that we can have + * big notification size, smallest descriptor, and still provide + * TZ with single big buffer based on notification size. + */ + + /* set buffer/descriptor size and count */ + ret = tspp_allocate_buffers(0, + channel_id, + TSPP_BUFFER_COUNT, + TSPP_BUFFER_SIZE, + TSPP_NOTIFICATION_SIZE, + NULL, + NULL); + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: tspp_allocate_buffers(%d) failed (%d)\n", + __func__, + channel_id, + ret); + + goto add_channel_unregister_notif; + } + + mpq_dmx_tspp_info.tsif[tsif].mpq_demux = mpq_demux; + } + + /* add new PID to the existing pipe */ + ret = mpq_tspp_get_free_filter_slot(tsif, channel_id); + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: mpq_allocate_filter_slot(%d, %d) failed\n", + __func__, + tsif, + channel_id); + + goto add_channel_unregister_notif; + } + + mpq_dmx_tspp_info.tsif[tsif].filters[ret].pid = feed->pid; + mpq_dmx_tspp_info.tsif[tsif].filters[ret].ref_count++; + + tspp_filter.priority = ret; + if (feed->pid == TSPP_PASS_THROUGH_PID) { + /* pass all pids */ + tspp_filter.pid = 0; + tspp_filter.mask = 0; + } else { + tspp_filter.pid = feed->pid; + tspp_filter.mask = TSPP_PID_MASK; + } + + /* + * Include TTS in RAW packets, if you change this to + * TSPP_MODE_RAW_NO_SUFFIX you must also change TSPP_RAW_TTS_SIZE + * accordingly. + */ + tspp_filter.mode = TSPP_MODE_RAW; + tspp_filter.source = tspp_source; + tspp_filter.decrypt = 0; + ret = tspp_add_filter(0, channel_id, &tspp_filter); + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: tspp_add_filter(%d) failed (%d)\n", + __func__, + channel_id, + ret); + + goto add_channel_free_filter_slot; + } + + (*channel_ref_count)++; + + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return 0; + +add_channel_free_filter_slot: + mpq_dmx_tspp_info.tsif[tsif].filters[tspp_filter.priority].pid = -1; + mpq_dmx_tspp_info.tsif[tsif].filters[tspp_filter.priority].ref_count--; +add_channel_unregister_notif: + tspp_unregister_notification(0, channel_id); +add_channel_close_ch: + tspp_close_channel(0, channel_id); +add_channel_failed: + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return ret; +} + +/** + * Removes filter from TSPP. + * + * @feed: The feed to remove + * + * Return error status + * + * The function checks if this is the only PID allocated within + * the channel, if so, the channel is closed as well. + */ +static int mpq_tspp_dmx_remove_channel(struct dvb_demux_feed *feed) +{ + int tsif; + int ret; + int channel_id; + int *channel_ref_count; + struct tspp_filter tspp_filter; + struct mpq_demux *mpq_demux = (struct mpq_demux *)feed->demux->priv; + + /* determine the TSIF we are reading from */ + if (mpq_demux->source == DMX_SOURCE_FRONT0) { + tsif = 0; + } else if (mpq_demux->source == DMX_SOURCE_FRONT1) { + tsif = 1; + } else { + /* invalid source */ + MPQ_DVB_ERR_PRINT( + "%s: invalid input source (%d)\n", + __func__, + mpq_demux->source); + + return -EINVAL; + } + + if (mutex_lock_interruptible(&mpq_dmx_tspp_info.tsif[tsif].mutex)) + return -ERESTARTSYS; + + /* determine to which pipe the feed should be routed: section or pes */ + if ((feed->type == DMX_TYPE_PES) || (feed->type == DMX_TYPE_TS)) { + channel_id = TSPP_CHANNEL_ID(tsif, TSPP_PES_CHANNEL); + channel_ref_count = + &mpq_dmx_tspp_info.tsif[tsif].pes_channel_ref; + } else { + channel_id = TSPP_CHANNEL_ID(tsif, TSPP_SECTION_CHANNEL); + channel_ref_count = + &mpq_dmx_tspp_info.tsif[tsif].section_channel_ref; + } + + /* check if required TSPP pipe is already allocated or not */ + if (*channel_ref_count == 0) { + /* invalid feed provided as the channel is not allocated */ + MPQ_DVB_ERR_PRINT( + "%s: invalid feed (%d)\n", + __func__, + channel_id); + + ret = -EINVAL; + goto remove_channel_failed; + } + + tspp_filter.priority = mpq_tspp_get_filter_slot(tsif, feed->pid); + + if (tspp_filter.priority < 0) { + /* invalid feed provided as it has no filter allocated */ + MPQ_DVB_ERR_PRINT( + "%s: mpq_tspp_get_filter_slot failed (%d,%d)\n", + __func__, + feed->pid, + tsif); + + ret = -EINVAL; + goto remove_channel_failed; + } + + mpq_dmx_tspp_info.tsif[tsif].filters[tspp_filter.priority].ref_count--; + + if (mpq_dmx_tspp_info.tsif[tsif]. + filters[tspp_filter.priority].ref_count) { + /* + * there are still references to this pid, do not + * remove the filter yet + */ + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return 0; + } + + ret = tspp_remove_filter(0, channel_id, &tspp_filter); + if (ret < 0) { + /* invalid feed provided as it has no filter allocated */ + MPQ_DVB_ERR_PRINT( + "%s: tspp_remove_filter failed (%d,%d)\n", + __func__, + channel_id, + tspp_filter.priority); + + goto remove_channel_failed_restore_count; + } + + mpq_dmx_tspp_info.tsif[tsif].filters[tspp_filter.priority].pid = -1; + (*channel_ref_count)--; + + if (*channel_ref_count == 0) { + /* channel is not used any more, release it */ + tspp_unregister_notification(0, channel_id); + tspp_close_channel(0, channel_id); + } + + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return 0; + +remove_channel_failed_restore_count: + mpq_dmx_tspp_info.tsif[tsif].filters[tspp_filter.priority].ref_count++; + +remove_channel_failed: + mutex_unlock(&mpq_dmx_tspp_info.tsif[tsif].mutex); + return ret; +} + +static int mpq_tspp_dmx_start_filtering(struct dvb_demux_feed *feed) +{ + int ret; + struct mpq_demux *mpq_demux = + (struct mpq_demux *)feed->demux->priv; + + MPQ_DVB_DBG_PRINT( + "%s(%d) executed\n", + __func__, + feed->pid); + + if (mpq_demux == NULL) { + MPQ_DVB_ERR_PRINT( + "%s: invalid mpq_demux handle\n", + __func__); + + return -EINVAL; + } + + if (mpq_demux->source < DMX_SOURCE_DVR0) { + /* source from TSPP, need to configure tspp pipe */ + ret = mpq_tspp_dmx_add_channel(feed); + + if (ret < 0) { + MPQ_DVB_DBG_PRINT( + "%s: mpq_tspp_dmx_add_channel failed(%d)\n", + __func__, + ret); + return ret; + } + } + + /* + * Always feed sections/PES starting from a new one and + * do not partial transfer data from older one + */ + feed->pusi_seen = 0; + + /* + * For video PES, data is tunneled to the decoder, + * initialize tunneling and pes parsing. + */ + if (mpq_dmx_is_video_feed(feed)) { + ret = mpq_dmx_init_video_feed(feed); + + if (ret < 0) { + MPQ_DVB_DBG_PRINT( + "%s: mpq_dmx_init_video_feed failed(%d)\n", + __func__, + ret); + + if (mpq_demux->source < DMX_SOURCE_DVR0) + mpq_tspp_dmx_remove_channel(feed); + + return ret; + } + } + + return 0; +} + +static int mpq_tspp_dmx_stop_filtering(struct dvb_demux_feed *feed) +{ + int ret = 0; + struct mpq_demux *mpq_demux = (struct mpq_demux *)feed->demux->priv; + + MPQ_DVB_DBG_PRINT( + "%s(%d) executed\n", + __func__, + feed->pid); + + /* + * For video PES, data is tunneled to the decoder, + * terminate tunnel and pes parsing. + */ + if (mpq_dmx_is_video_feed(feed)) + mpq_dmx_terminate_video_feed(feed); + + if (mpq_demux->source < DMX_SOURCE_DVR0) { + /* source from TSPP, need to configure tspp pipe */ + ret = mpq_tspp_dmx_remove_channel(feed); + } + + return ret; +} + +static int mpq_tspp_dmx_write_to_decoder( + struct dvb_demux_feed *feed, + const u8 *buf, + size_t len) +{ + + /* + * It is assumed that this function is called once for each + * TS packet of the relevant feed. + */ + if (len > TSPP_RAW_TTS_SIZE) + MPQ_DVB_DBG_PRINT( + "%s: warnning - len larger than one packet\n", + __func__); + + if (mpq_dmx_is_video_feed(feed)) + return mpq_dmx_process_video_packet(feed, buf); + + if (mpq_dmx_is_pcr_feed(feed)) + return mpq_dmx_process_pcr_packet(feed, buf); + + return 0; +} + +/** + * Returns demux capabilities of TSPPv1 plugin + * + * @demux: demux device + * @caps: Returned capbabilities + * + * Return error code + */ +static int mpq_tspp_dmx_get_caps(struct dmx_demux *demux, + struct dmx_caps *caps) +{ + struct dvb_demux *dvb_demux = (struct dvb_demux *)demux->priv; + + if ((dvb_demux == NULL) || (caps == NULL)) { + MPQ_DVB_ERR_PRINT( + "%s: invalid parameters\n", + __func__); + + return -EINVAL; + } + + caps->caps = DMX_CAP_PULL_MODE | DMX_CAP_VIDEO_DECODER_DATA; + caps->num_decoders = MPQ_ADAPTER_MAX_NUM_OF_INTERFACES; + caps->num_demux_devices = CONFIG_DVB_MPQ_NUM_DMX_DEVICES; + caps->num_pid_filters = TSPP_MAX_PID_FILTER_NUM; + caps->num_section_filters = dvb_demux->filternum; + caps->num_section_filters_per_pid = dvb_demux->filternum; + caps->section_filter_length = DMX_FILTER_SIZE; + caps->num_demod_inputs = TSIF_COUNT; + caps->num_memory_inputs = CONFIG_DVB_MPQ_NUM_DMX_DEVICES; + caps->max_bitrate = 144; + caps->demod_input_max_bitrate = 72; + caps->memory_input_max_bitrate = 72; + + return 0; +} + +static int mpq_tspp_dmx_init( + struct dvb_adapter *mpq_adapter, + struct mpq_demux *mpq_demux) +{ + int result; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* Set the kernel-demux object capabilities */ + mpq_demux->demux.dmx.capabilities = + DMX_TS_FILTERING | + DMX_PES_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING | + DMX_CRC_CHECKING | + DMX_TS_DESCRAMBLING; + + /* Set dvb-demux "virtual" function pointers */ + mpq_demux->demux.priv = (void *)mpq_demux; + mpq_demux->demux.filternum = TSPP_MAX_SECTION_FILTER_NUM; + mpq_demux->demux.feednum = MPQ_MAX_DMX_FILES; + mpq_demux->demux.start_feed = mpq_tspp_dmx_start_filtering; + mpq_demux->demux.stop_feed = mpq_tspp_dmx_stop_filtering; + mpq_demux->demux.write_to_decoder = mpq_tspp_dmx_write_to_decoder; + + mpq_demux->demux.decoder_fullness_init = + mpq_dmx_decoder_fullness_init; + + mpq_demux->demux.decoder_fullness_wait = + mpq_dmx_decoder_fullness_wait; + + mpq_demux->demux.decoder_fullness_abort = + mpq_dmx_decoder_fullness_abort; + + /* Initialize dvb_demux object */ + result = dvb_dmx_init(&mpq_demux->demux); + if (result < 0) { + MPQ_DVB_ERR_PRINT("%s: dvb_dmx_init failed\n", __func__); + goto init_failed; + } + + /* Now initailize the dmx-dev object */ + mpq_demux->dmxdev.filternum = MPQ_MAX_DMX_FILES; + mpq_demux->dmxdev.demux = &mpq_demux->demux.dmx; + mpq_demux->dmxdev.capabilities = + DMXDEV_CAP_DUPLEX | + DMXDEV_CAP_PULL_MODE | + DMXDEV_CAP_PCR_EXTRACTION | + DMXDEV_CAP_INDEXING; + + mpq_demux->dmxdev.demux->set_source = mpq_dmx_set_source; + mpq_demux->dmxdev.demux->get_caps = mpq_tspp_dmx_get_caps; + + result = dvb_dmxdev_init(&mpq_demux->dmxdev, mpq_adapter); + if (result < 0) { + MPQ_DVB_ERR_PRINT("%s: dvb_dmxdev_init failed (errno=%d)\n", + __func__, + result); + goto init_failed_dmx_release; + } + + /* Extend dvb-demux debugfs with TSPP statistics. */ + mpq_dmx_init_hw_statistics(mpq_demux); + + return 0; + +init_failed_dmx_release: + dvb_dmx_release(&mpq_demux->demux); +init_failed: + return result; +} + +static int __init mpq_dmx_tspp_plugin_init(void) +{ + int i; + int j; + int ret; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + for (i = 0; i < TSIF_COUNT; i++) { + mpq_dmx_tspp_info.tsif[i].pes_channel_ref = 0; + + mpq_dmx_tspp_info.tsif[i].pes_work.channel_id = + TSPP_CHANNEL_ID(i, TSPP_PES_CHANNEL); + + INIT_WORK(&mpq_dmx_tspp_info.tsif[i].pes_work.work, + mpq_dmx_tspp_work); + + mpq_dmx_tspp_info.tsif[i].section_channel_ref = 0; + + mpq_dmx_tspp_info.tsif[i].section_work.channel_id = + TSPP_CHANNEL_ID(i, TSPP_SECTION_CHANNEL); + + INIT_WORK(&mpq_dmx_tspp_info.tsif[i].section_work.work, + mpq_dmx_tspp_work); + + for (j = 0; j < TSPP_MAX_PID_FILTER_NUM; j++) { + mpq_dmx_tspp_info.tsif[i].filters[j].pid = -1; + mpq_dmx_tspp_info.tsif[i].filters[j].ref_count = 0; + } + + snprintf(mpq_dmx_tspp_info.tsif[i].name, + TSIF_NAME_LENGTH, + "tsif_%d", + i); + + mpq_dmx_tspp_info.tsif[i].workqueue = + create_singlethread_workqueue( + mpq_dmx_tspp_info.tsif[i].name); + + if (mpq_dmx_tspp_info.tsif[i].workqueue == NULL) { + + for (j = 0; j < i; j++) { + destroy_workqueue( + mpq_dmx_tspp_info.tsif[j].workqueue); + + mutex_destroy(&mpq_dmx_tspp_info.tsif[j].mutex); + } + + MPQ_DVB_ERR_PRINT( + "%s: create_singlethread_workqueue failed\n", + __func__); + + return -ENOMEM; + } + + mutex_init(&mpq_dmx_tspp_info.tsif[i].mutex); + } + + ret = mpq_dmx_plugin_init(mpq_tspp_dmx_init); + + if (ret < 0) { + MPQ_DVB_ERR_PRINT( + "%s: mpq_dmx_plugin_init failed (errno=%d)\n", + __func__, + ret); + + for (i = 0; i < TSIF_COUNT; i++) { + destroy_workqueue(mpq_dmx_tspp_info.tsif[i].workqueue); + mutex_destroy(&mpq_dmx_tspp_info.tsif[i].mutex); + } + } + + return ret; +} + +static void __exit mpq_dmx_tspp_plugin_exit(void) +{ + int i; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + for (i = 0; i < TSIF_COUNT; i++) { + mutex_lock(&mpq_dmx_tspp_info.tsif[i].mutex); + + if (mpq_dmx_tspp_info.tsif[i].pes_channel_ref) { + tspp_unregister_notification(0, TSPP_PES_CHANNEL); + tspp_close_channel(0, + TSPP_CHANNEL_ID(i, TSPP_PES_CHANNEL)); + } + + if (mpq_dmx_tspp_info.tsif[i].section_channel_ref) { + tspp_unregister_notification(0, TSPP_SECTION_CHANNEL); + tspp_close_channel(0, + TSPP_CHANNEL_ID(i, TSPP_SECTION_CHANNEL)); + } + + /* TODO: if we allocate buffer + * to TSPP ourself, need to free those as well + */ + + mutex_unlock(&mpq_dmx_tspp_info.tsif[i].mutex); + flush_workqueue(mpq_dmx_tspp_info.tsif[i].workqueue); + destroy_workqueue(mpq_dmx_tspp_info.tsif[i].workqueue); + mutex_destroy(&mpq_dmx_tspp_info.tsif[i].mutex); + } + + mpq_dmx_plugin_exit(); +} + + +module_init(mpq_dmx_tspp_plugin_init); +module_exit(mpq_dmx_tspp_plugin_exit); + +MODULE_DESCRIPTION("Qualcomm demux TSPP version 1 HW Plugin"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v2.c b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v2.c new file mode 100644 index 0000000000000000000000000000000000000000..6c484a04b017e769e11a29418d91e72db8f6133e --- /dev/null +++ b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v2.c @@ -0,0 +1,183 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "mpq_dvb_debug.h" +#include "mpq_dmx_plugin_common.h" + + +#define TSIF_COUNT 2 + +#define BAM_INPUT_COUNT 4 + +/* Max number of PID filters */ +#define TSPP_MAX_PID_FILTER_NUM 128 + +/* Max number of section filters */ +#define TSPP_MAX_SECTION_FILTER_NUM 64 + + +static int mpq_tspp_dmx_start_filtering(struct dvb_demux_feed *feed) +{ + MPQ_DVB_DBG_PRINT( + "%s(%d) executed\n", + __func__, + feed->pid); + + /* Always feed sections/PES starting from a new one and + * do not partial transfer data from older one + */ + feed->pusi_seen = 0; + return 0; +} + +static int mpq_tspp_dmx_stop_filtering(struct dvb_demux_feed *feed) +{ + MPQ_DVB_DBG_PRINT( + "%s(%d) executed\n", + __func__, + feed->pid); + + return 0; +} + +/** + * Returns demux capabilities of TSPPv2 plugin + * + * @demux: demux device + * @caps: Returned capbabilities + * + * Return error code + */ +static int mpq_tspp_dmx_get_caps(struct dmx_demux *demux, + struct dmx_caps *caps) +{ + struct dvb_demux *dvb_demux = (struct dvb_demux *)demux->priv; + + if ((dvb_demux == NULL) || (caps == NULL)) { + MPQ_DVB_ERR_PRINT( + "%s: invalid parameters\n", + __func__); + + return -EINVAL; + } + + caps->caps = DMX_CAP_PULL_MODE | DMX_CAP_VIDEO_INDEXING | + DMX_CAP_VIDEO_DECODER_DATA; + caps->num_decoders = MPQ_ADAPTER_MAX_NUM_OF_INTERFACES; + caps->num_demux_devices = CONFIG_DVB_MPQ_NUM_DMX_DEVICES; + caps->num_pid_filters = TSPP_MAX_PID_FILTER_NUM; + caps->num_section_filters = dvb_demux->filternum; + caps->num_section_filters_per_pid = dvb_demux->filternum; + caps->section_filter_length = DMX_FILTER_SIZE; + caps->num_demod_inputs = TSIF_COUNT; + caps->num_memory_inputs = BAM_INPUT_COUNT; + caps->max_bitrate = 320; + caps->demod_input_max_bitrate = 96; + caps->memory_input_max_bitrate = 80; + + return 0; +} + +/** + * Initialize a single demux device. + * + * @mpq_adapter: MPQ DVB adapter + * @mpq_demux: The demux device to initialize + * + * Return error code + */ +static int mpq_tspp_dmx_init( + struct dvb_adapter *mpq_adapter, + struct mpq_demux *mpq_demux) +{ + int result; + + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + /* Set the kernel-demux object capabilities */ + mpq_demux->demux.dmx.capabilities = + DMX_TS_FILTERING | + DMX_PES_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING | + DMX_CRC_CHECKING | + DMX_TS_DESCRAMBLING; + + /* Set dvb-demux "virtual" function pointers */ + mpq_demux->demux.priv = (void *)mpq_demux; + mpq_demux->demux.filternum = TSPP_MAX_SECTION_FILTER_NUM; + mpq_demux->demux.feednum = MPQ_MAX_DMX_FILES; + mpq_demux->demux.start_feed = mpq_tspp_dmx_start_filtering; + mpq_demux->demux.stop_feed = mpq_tspp_dmx_stop_filtering; + mpq_demux->demux.write_to_decoder = NULL; + mpq_demux->demux.decoder_fullness_init = NULL; + mpq_demux->demux.decoder_fullness_wait = NULL; + mpq_demux->demux.decoder_fullness_abort = NULL; + + /* Initialize dvb_demux object */ + result = dvb_dmx_init(&mpq_demux->demux); + if (result < 0) { + MPQ_ERR_PRINT("%s: dvb_dmx_init failed\n", __func__); + goto init_failed; + } + + /* Now initailize the dmx-dev object */ + mpq_demux->dmxdev.filternum = MPQ_MAX_DMX_FILES; + mpq_demux->dmxdev.demux = &mpq_demux->demux.dmx; + mpq_demux->dmxdev.capabilities = + DMXDEV_CAP_DUPLEX | + DMXDEV_CAP_PULL_MODE | + DMXDEV_CAP_PCR_EXTRACTION | + DMXDEV_CAP_INDEXING; + + mpq_demux->dmxdev.demux->set_source = mpq_dmx_set_source; + mpq_demux->dmxdev.demux->get_caps = mpq_tspp_dmx_get_caps; + + result = dvb_dmxdev_init(&mpq_demux->dmxdev, mpq_adapter); + if (result < 0) { + MPQ_DVB_ERR_PRINT("%s: dvb_dmxdev_init failed (errno=%d)\n", + __func__, + result); + + goto init_failed_dmx_release; + } + + return 0; + +init_failed_dmx_release: + dvb_dmx_release(&mpq_demux->demux); +init_failed: + return result; +} + +static int __init mpq_dmx_tspp_plugin_init(void) +{ + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + + return mpq_dmx_plugin_init(mpq_tspp_dmx_init); +} + +static void __exit mpq_dmx_tspp_plugin_exit(void) +{ + MPQ_DVB_DBG_PRINT("%s executed\n", __func__); + mpq_dmx_plugin_exit(); +} + + +module_init(mpq_dmx_tspp_plugin_init); +module_exit(mpq_dmx_tspp_plugin_exit); + +MODULE_DESCRIPTION("Qualcomm demux TSPP version2 HW Plugin"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/dvb/mpq/include/mpq_adapter.h b/drivers/media/dvb/mpq/include/mpq_adapter.h new file mode 100644 index 0000000000000000000000000000000000000000..c9b24416dfbc00057cae6255f04736eb997403d5 --- /dev/null +++ b/drivers/media/dvb/mpq/include/mpq_adapter.h @@ -0,0 +1,193 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MPQ_ADAPTER_H +#define _MPQ_ADAPTER_H + +#include "dvbdev.h" +#include "mpq_stream_buffer.h" + + + +/** IDs of interfaces holding stream-buffers */ +enum mpq_adapter_stream_if { + /** Interface holding stream-buffer for video0 stream */ + MPQ_ADAPTER_VIDEO0_STREAM_IF = 0, + + /** Interface holding stream-buffer for video1 stream */ + MPQ_ADAPTER_VIDEO1_STREAM_IF = 1, + + /** Interface holding stream-buffer for video1 stream */ + MPQ_ADAPTER_VIDEO2_STREAM_IF = 2, + + /** Interface holding stream-buffer for video1 stream */ + MPQ_ADAPTER_VIDEO3_STREAM_IF = 3, + + /** Maximum number of interfaces holding stream-buffers */ + MPQ_ADAPTER_MAX_NUM_OF_INTERFACES, +}; + + +enum dmx_framing_pattern_type { + /* MPEG-2 */ + DMX_FRM_MPEG2_SEQUENCE_HEADER, + DMX_FRM_MPEG2_GOP_HEADER, + DMX_FRM_MPEG2_I_PIC, + DMX_FRM_MPEG2_P_PIC, + DMX_FRM_MPEG2_B_PIC, + /* H.264 */ + DMX_FRM_H264_SPS, + DMX_FRM_H264_PPS, + /* H.264 First Coded slice of an IDR Picture */ + DMX_FRM_H264_IDR_PIC, + /* H.264 First Coded slice of a non-IDR Picture */ + DMX_FRM_H264_NON_IDR_PIC, + /* VC-1 Sequence Header*/ + DMX_FRM_VC1_SEQUENCE_HEADER, + /* VC-1 Entry Point Header (Advanced Profile only) */ + DMX_FRM_VC1_ENTRY_POINT_HEADER, + /* VC-1 Frame Start Code */ + DMX_FRM_VC1_FRAME_START_CODE, + /* Unknown or invalid framing information */ + DMX_FRM_UNKNOWN +}; + +enum dmx_packet_type { + DMX_PADDING_PACKET, + DMX_PES_PACKET, + DMX_FRAMING_INFO_PACKET, + DMX_EOS_PACKET +}; + +struct dmx_pts_dts_info { + /** Indication whether PTS exist */ + int pts_exist; + + /** Indication whether DTS exist */ + int dts_exist; + + /** PTS value associated with the PES data if any */ + u64 pts; + + /** DTS value associated with the PES data if any */ + u64 dts; +}; + +struct dmx_framing_packet_info { + /** framing pattern type */ + enum dmx_framing_pattern_type pattern_type; + /** PTS/DTS information */ + struct dmx_pts_dts_info pts_dts_info; +}; + +struct dmx_pes_packet_info { + /** PTS/DTS information */ + struct dmx_pts_dts_info pts_dts_info; +}; + +/** The meta-data used for video interface */ +struct mpq_adapter_video_meta_data { + /** meta-data packet type */ + enum dmx_packet_type packet_type; + + /** packet-type specific information */ + union { + struct dmx_framing_packet_info framing; + struct dmx_pes_packet_info pes; + } info; +} __packed; + + +/** Callback function to notify on registrations of specific interfaces */ +typedef void (*mpq_adapter_stream_if_callback)( + enum mpq_adapter_stream_if interface_id, + void *user_param); + + +/** + * mpq_adapter_get - Returns pointer to Qualcomm DVB adapter + * + * Return dvb adapter or NULL if not exist. + */ +struct dvb_adapter *mpq_adapter_get(void); + + +/** + * mpq_adapter_register_stream_if - Register a stream interface. + * + * @interface_id: The interface id + * @stream_buffer: The buffer used for the interface + * + * Return error status + * + * Stream interface used to connect between two units in tunneling + * mode using mpq_streambuffer implementation. + * The producer of the interface should register the new interface, + * consumer may get the interface using mpq_adapter_get_stream_if. + * + * Note that the function holds a pointer to this interface, + * stream_buffer pointer assumed to be valid as long as interface + * is active. + */ +int mpq_adapter_register_stream_if( + enum mpq_adapter_stream_if interface_id, + struct mpq_streambuffer *stream_buffer); + + +/** + * mpq_adapter_unregister_stream_if - Un-register a stream interface. + * + * @interface_id: The interface id + * + * Return error status + */ +int mpq_adapter_unregister_stream_if( + enum mpq_adapter_stream_if interface_id); + + +/** + * mpq_adapter_get_stream_if - Get buffer used for a stream interface. + * + * @interface_id: The interface id + * @stream_buffer: The returned stream buffer + * + * Return error status + */ +int mpq_adapter_get_stream_if( + enum mpq_adapter_stream_if interface_id, + struct mpq_streambuffer **stream_buffer); + + +/** + * mpq_adapter_notify_stream_if - Register notification + * to be triggered when a stream interface is registered. + * + * @interface_id: The interface id + * @callback: The callback to be triggered when the interface is registered + * @user_param: A parameter that is passed back to the callback function + * when triggered. + * + * Return error status + * + * Producer may use this to register notification when desired + * interface registered in the system and query its information + * afterwards using mpq_adapter_get_stream_if. + * To remove the callback, this function should be called with NULL + * value in callback parameter. + */ +int mpq_adapter_notify_stream_if( + enum mpq_adapter_stream_if interface_id, + mpq_adapter_stream_if_callback callback, + void *user_param); + +#endif /* _MPQ_ADAPTER_H */ + diff --git a/drivers/media/dvb/mpq/include/mpq_dvb_debug.h b/drivers/media/dvb/mpq/include/mpq_dvb_debug.h new file mode 100644 index 0000000000000000000000000000000000000000..4890b8554652620c3b3a0116e32e896769a85897 --- /dev/null +++ b/drivers/media/dvb/mpq/include/mpq_dvb_debug.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MPQ_DVB_DEBUG_H +#define _MPQ_DVB_DEBUG_H + +/* Enable this line if you want to output debug printouts */ +#define MPG_DVB_DEBUG_ENABLE + +#undef MPQ_DVB_DBG_PRINT /* undef it, just in case */ + +#ifdef MPG_DVB_DEBUG_ENABLE +#define MPQ_DVB_DBG_PRINT(fmt, args...) pr_debug(fmt, ## args) +#define MPQ_DVB_ERR_PRINT(fmt, args...) pr_err(fmt, ## args) +#else /* MPG_DVB_DEBUG_ENABLE */ +#define MPQ_DVB_DBG_PRINT(fmt, args...) +#define MPQ_DVB_ERR_PRINT(fmt, args...) +#endif /* MPG_DVB_DEBUG_ENABLE */ + + +/* + * The following can be used to disable specific printout + * by adding a letter to the end of MPQ_DVB_DBG_PRINT + */ +#undef MPQ_DVB_DBG_PRINTT +#define MPQ_DVB_DBG_PRINTT(fmt, args...) + +#endif /* _MPQ_DVB_DEBUG_H */ + diff --git a/drivers/media/dvb/mpq/include/mpq_stream_buffer.h b/drivers/media/dvb/mpq/include/mpq_stream_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..4ea42227fe605ec6cab102bcdececcf509604677 --- /dev/null +++ b/drivers/media/dvb/mpq/include/mpq_stream_buffer.h @@ -0,0 +1,269 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MPQ_STREAM_BUFFER_H +#define _MPQ_STREAM_BUFFER_H + +#include "dvb_ringbuffer.h" + + +/** + * DOC: MPQ Stream Buffer + * + * A stream buffer implmenetation used to transfer data between two units + * such as demux and decoders. The implementation relies on dvb_ringbuffer + * implementation. Refer to dvb_ringbuffer.h for details. + * + * The implementation uses two dvb_ringbuffers, one to pass the + * raw-data (PES payload for example) and the other to pass + * meta-data (information from PES header for example). + * + * The meta-data uses dvb_ringbuffer packet interface. Each meta-data + * packet hold the address and size of raw-data described by the + * meta-data packet, in addition to user's own parameters if any required. + * + * Contrary to dvb_ringbuffer implementation, this API makes sure there's + * enough data to read/write when making read/write operations. + * Users interested to flush/reset specific buffer, check for bytes + * ready or space available for write should use the respective services + * in dvb_ringbuffer (dvb_ringbuffer_avail, dvb_ringbuffer_free, + * dvb_ringbuffer_reset, dvb_ringbuffer_flush, + * dvb_ringbuffer_flush_spinlock_wakeup). + * + * Concurrency protection is handled in the same manner as in + * dvb_ringbuffer implementation. + * + * Typical call flow from producer: + * + * - Start writting the raw-data of new packet, the following call is + * repeated until end of data of the specific packet + * + * mpq_streambuffer_data_write(...) + * + * - Now write a new packet describing the new available raw-data + * mpq_streambuffer_pkt_write(...) + * + * Typical call flow from consumer: + * + * - Poll for next available packet: + * mpq_streambuffer_pkt_next(&streambuff,-1) + * + * In different approach, consumer can wait on event for new data and then + * call mpq_streambuffer_pkt_next, waiting for data can be done as follows: + * + * wait_event_interruptible( + * streambuff->packet_data->queue, + * !dvb_ringbuffer_empty(&streambuff->packet_data) || + * (streambuff->packet_data.error != 0); + * + * - Get the new packet information: + * mpq_streambuffer_pkt_read(..) + * + * - Read the raw-data of the new packet. Here you can use two methods: + * + * 1. Read the data to a user supplied buffer: + * mpq_streambuffer_data_read() + * + * In this case memory copy is done, read pointer is updated in the raw + * data buffer, the amount of raw-data is provided part of the + * packet's information. User should then call mpq_streambuffer_pkt_dispose + * with dispose_data set to 0 as the raw-data was already disposed. + * + * 2. Access the data directly using the raw-data address. The address + * of the raw data is provided part of the packet's information. User + * then should call mpq_streambuffer_pkt_dispose with dispose_data set + * to 1 to dispose the packet along with it's raw-data. + */ + +/** + * struct mpq_streambuffer - mpq stream buffer representation + * + * @raw_data: The buffer used to hold the raw-data + * @packet_data: The buffer user to hold the meta-data + */ +struct mpq_streambuffer { + struct dvb_ringbuffer raw_data; + struct dvb_ringbuffer packet_data; +}; + +/** + * struct mpq_streambuffer_packet_header - packet header saved in packet buffer + * @user_data_len: length of private user (meta) data + * @raw_data_addr: raw-data address in the raw-buffer described by the packet + * @raw_data_len: size of raw-data in the raw-data buffer (can be 0) + * + * The packet structure that is saved in each packet-buffer: + * user_data_len + * raw_data_addr + * raw_data_len + * private user-data bytes + */ +struct mpq_streambuffer_packet_header { + u32 user_data_len; + u32 raw_data_addr; + u32 raw_data_len; +} __packed; + +/** + * mpq_streambuffer_init - Initialize a new stream buffer + * + * @sbuff: The buffer to initialize + * @data_buff: The buffer holding raw-data + * @data_buff_len: Size of raw-data buffer + * @packet_buff: The buffer holding meta-data + * @packet_buff_size: Size of meta-data buffer + */ +void mpq_streambuffer_init( + struct mpq_streambuffer *sbuff, + void *data_buff, size_t data_buff_len, + void *packet_buff, size_t packet_buff_size); + +/** + * mpq_streambuffer_packet_next - Returns index of next avaialble packet. + * + * @sbuff: The stream buffer + * @idx: Previous packet index or -1 to return index of the the first + * available packet. + * @pktlen: The length of the ready packet + * + * Return index to the packet-buffer, -1 if buffer is empty + * + * After getting the index, the user of this function can either + * access the packet buffer directly using the returned index + * or ask to read the data back from the buffer using mpq_ringbuffer_pkt_read + */ +ssize_t mpq_streambuffer_pkt_next( + struct mpq_streambuffer *sbuff, + ssize_t idx, size_t *pktlen); + +/** + * mpq_streambuffer_pkt_read - Reads out the packet from the provided index. + * + * @sbuff: The stream buffer + * @idx: The index of the packet to be read + * @packet: The read packet's header + * @user_data: The read private user data + * + * Return The actual number of bytes read, -EINVAL if the packet is + * already disposed or the packet-data is invalid. + * + * The packet is not disposed after this function is called, to dispose it + * along with the raw-data it points to use mpq_streambuffer_pkt_dispose. + * If there are no private user-data, the user-data pointer can be NULL. + * The caller of this function must make sure that the private user-data + * buffer has enough space for the private user-data length + */ +ssize_t mpq_streambuffer_pkt_read( + struct mpq_streambuffer *sbuff, + size_t idx, + struct mpq_streambuffer_packet_header *packet, + u8 *user_data); + +/** + * mpq_streambuffer_pkt_dispose - Disposes a packet from the packet buffer + * + * @sbuff: The stream buffer + * @idx: The index of the packet to be disposed + * @dispose_data: Indicates whether to update the read pointer inside the + * raw-data buffer for the respective data pointed by the packet. + * + * Return error status, -EINVAL if the packet-data is invalid + * + * The function updates the read pointer inside the raw-data buffer + * for the respective data pointed by the packet if dispose_data is set. + */ +int mpq_streambuffer_pkt_dispose( + struct mpq_streambuffer *sbuff, + size_t idx, + int dispose_data); + +/** + * mpq_streambuffer_pkt_write - Write a new packet to the packet buffer. + * + * @sbuff: The stream buffer + * @packet: The packet header to write + * @user_data: The private user-data to be written + * + * Return error status, -ENOSPC if there's no space to write the packet + */ +int mpq_streambuffer_pkt_write( + struct mpq_streambuffer *sbuff, + struct mpq_streambuffer_packet_header *packet, + u8 *user_data); + +/** + * mpq_streambuffer_data_write - Write data to raw-data buffer + * + * @sbuff: The stream buffer + * @buf: The buffer holding the data to be written + * @len: The length of the data buffer + * + * Return The actual number of bytes written or -ENOSPC if + * no space to write the data + */ +ssize_t mpq_streambuffer_data_write( + struct mpq_streambuffer *sbuff, + const u8 *buf, size_t len); + +/** + * mpq_streambuffer_data_write_deposit - Advances the raw-buffer write pointer. + * Assumes the raw-data was written by the user directly + * + * @sbuff: The stream buffer + * @len: The length of the raw-data that was already written + * + * Return error status + */ +int mpq_streambuffer_data_write_deposit( + struct mpq_streambuffer *sbuff, + size_t len); + +/** + * mpq_streambuffer_data_read - Reads out raw-data to the provided buffer. + * + * @sbuff: The stream buffer + * @buf: The buffer to read the raw-data data to + * @len: The length of the buffer that will hold the raw-data + * + * Return The actual number of bytes read + * + * This fucntion copies the data from the ring-buffer to the + * provided buf parameter. The user can save the extra copy by accessing + * the data pointer directly and reading from it, then update the + * read pointer by the amount of data that was read using + * mpq_streambuffer_data_read_dispose + */ +size_t mpq_streambuffer_data_read( + struct mpq_streambuffer *sbuff, + u8 *buf, size_t len); + +/** + * mpq_streambuffer_data_read_dispose - Advances the raw-buffer read pointer. + * Assumes the raw-data was read by the user directly. + * + * @sbuff: The stream buffer + * @len: The length of the raw-data to be disposed + * + * Return error status, -EINVAL if buffer there's no enough data to + * be disposed + * + * The user can instead dipose a packet along with the data in the + * raw-data buffer using mpq_streambuffer_pkt_dispose. + */ +int mpq_streambuffer_data_read_dispose( + struct mpq_streambuffer *sbuff, + size_t len); + + + +#endif /* _MPQ_STREAM_BUFFER_H */ + diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 8db2d7f4b52af94b0f65d911c0b0d482d5b7d99b..8786fe6b6b4246a853ed676e6d5e95838cfd25a1 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -432,4 +432,38 @@ config RADIO_ZOLTRIX_PORT endif # V4L_RADIO_ISA_DRIVERS +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 ca8c7d134b95137f6bacf8e0870d95dff828aeaf..b80eba7bb52682d5efe39893e52f3c8c253dfd5b 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -28,5 +28,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 ccflags-y += -Isound diff --git a/drivers/media/radio/radio-iris-transport.c b/drivers/media/radio/radio-iris-transport.c new file mode 100644 index 0000000000000000000000000000000000000000..ed6ab4d861b10f0dcc9320cb540b7fd7ff655a85 --- /dev/null +++ b/drivers/media/radio/radio-iris-transport.c @@ -0,0 +1,202 @@ +/* + * 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-2012 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 +#include + +struct radio_data { + struct radio_hci_dev *hdev; + struct tasklet_struct rx_task; + struct smd_channel *fm_channel; +}; +struct radio_data hs; + +static struct work_struct *reset_worker; + +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_ATOMIC); + if (!skb) { + FMDERR("Memory not allocated for the socket"); + return; + } + + buf = kmalloc(len, GFP_ATOMIC); + if (!buf) { + kfree_skb(skb); + FMDERR("Error in allocating buffer memory"); + return; + } + + rc = smd_read(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); + kfree_skb(skb); + return -ENODEV; + } + kfree_skb(skb); + return 0; +} + + +static void send_disable_event(struct work_struct *worker) +{ + struct sk_buff *skb; + unsigned char buf[6] = { 0x0f, 0x04, 0x01, 0x02, 0x4c, 0x00 }; + int len = sizeof(buf); + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + FMDERR("Memory not allocated for the socket"); + return; + } + + FMDERR("FM INSERT DISABLE Rx Event"); + + memcpy(skb_put(skb, len), buf, len); + + skb_orphan(skb); + skb->dev = (struct net_device *)hs.hdev; + + radio_hci_recv_frame(skb); + kfree(worker); +} + +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: + break; + case SMD_EVENT_CLOSE: + reset_worker = kzalloc(sizeof(*reset_worker), GFP_ATOMIC); + if (!reset_worker) { + FMDERR("Out of memory"); + break; + } + INIT_WORK(reset_worker, send_disable_event); + schedule_work(reset_worker); + 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 0000000000000000000000000000000000000000..53eb85ccf628a86d2e1c2e9f9e0f9b6273dfe680 --- /dev/null +++ b/drivers/media/radio/radio-iris.c @@ -0,0 +1,3605 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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; + unsigned int mode; + + __u16 pi; + __u8 pty; + __u8 ps_repeatcount; + __u8 prev_trans_rds; + __u8 af_jump_bit; + 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_trans_conf_req_struct trans_conf; + struct hci_fm_rds_grp_req rds_grp; + unsigned char g_search_mode; + unsigned char power_mode; + int search_on; + unsigned int tone_freq; + unsigned char g_scan_time; + unsigned int g_antenna; + unsigned int g_rds_grp_proc_ps; + unsigned char event_mask; + enum iris_region_t region; + struct hci_fm_dbg_param_rsp st_dbg_param; + struct hci_ev_srch_list_compl srch_st_result; + struct hci_fm_riva_poke riva_data_req; + struct hci_fm_ssbi_req ssbi_data_accs; + struct hci_fm_ssbi_peek ssbi_peek_reg; + struct hci_fm_sig_threshold_rsp sig_th; + struct hci_fm_ch_det_threshold ch_det_threshold; + struct hci_fm_data_rd_rsp default_data; +}; + +static struct video_device *priv_videodev; +static int iris_do_calibration(struct iris_device *radio); + +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, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SOFT_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Soft Mute", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_ADDR, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Riva addr", + .minimum = 0x3180000, + .maximum = 0x31E0004, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_LEN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Data len", + .minimum = 0, + .maximum = 0xFF, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_PEEK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Riva peek", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_POKE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Riva poke", + .minimum = 0x3180000, + .maximum = 0x31E0004, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SSBI_ACCS_ADDR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Ssbi addr", + .minimum = 0x280, + .maximum = 0x37F, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SSBI_PEEK, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Ssbi peek", + .minimum = 0, + .maximum = 0x37F, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SSBI_POKE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "ssbi poke", + .minimum = 0x01, + .maximum = 0xFF, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_HLSI, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "set hlsi", + .minimum = 0, + .maximum = 2, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDS_GRP_COUNTERS, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS grp", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SET_NOTCH_FILTER, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Notch filter", + .minimum = 0, + .maximum = 2, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_READ_DEFAULT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Read default", + }, + { + .id = V4L2_CID_PRIVATE_IRIS_WRITE_DEFAULT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Write default", + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SET_CALIBRATION, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "SET Calibration", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_DO_CALIBRATION, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "SET Calibration", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_GET_SINR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "GET SINR", + .minimum = -128, + .maximum = 127, + }, + { + .id = V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Intf High Threshold", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Intf low Threshold", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SINR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "SINR Threshold", + .minimum = -128, + .maximum = 127, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SINR_SAMPLES, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "SINR samples", + .minimum = 1, + .maximum = 0xFF, + .default_value = 0, + }, +}; + +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); + kfree_skb(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_tone_generator(struct radio_hci_dev *hdev, + unsigned long param) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_FM_SET_INTERNAL_TONE_GENRATOR); + return radio_hci_send_cmd(hdev, opcode, + sizeof(radio->tone_freq), &radio->tone_freq); +} + +static int hci_fm_enable_trans_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_ENABLE_TRANS_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_fm_disable_trans_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_DISABLE_TRANS_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_get_fm_trans_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_GET_TRANS_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_set_fm_trans_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + struct hci_fm_trans_conf_req_struct *trans_conf_req = + (struct hci_fm_trans_conf_req_struct *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_SET_TRANS_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*trans_conf_req)), + trans_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_trans_ps_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_tx_ps *tx_ps_req = + (struct hci_fm_tx_ps *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_PS_REQ); + + return radio_hci_send_cmd(hdev, opcode, sizeof((*tx_ps_req)), + tx_ps_req); +} + +static int hci_trans_rt_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_tx_rt *tx_rt_req = + (struct hci_fm_tx_rt *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_RT_REQ); + + return radio_hci_send_cmd(hdev, opcode, sizeof((*tx_rt_req)), + tx_rt_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_RECV_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_set_event_mask(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + u8 event_mask = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_EVENT_MASK); + return radio_hci_send_cmd(hdev, opcode, sizeof(event_mask), + &event_mask); +} +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_RECV_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_RECV_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_RECV_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_RECV_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_COMMON_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_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_DEFAULT_DATA_WRITE); + + return radio_hci_send_cmd(hdev, opcode, (def_data_wr->length+2), + def_data_wr); +} + +static int hci_set_notch_filter_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + __u8 notch_filter_val = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_EN_NOTCH_CTRL); + return radio_hci_send_cmd(hdev, opcode, sizeof(notch_filter_val), + ¬ch_filter_val); +} + + + +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_STATUS_PARAMETERS_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_riva_data *peek_data = (struct hci_fm_riva_data *)param; + + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_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_riva_poke *poke_data = (struct hci_fm_riva_poke *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_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_peek *ssbi_peek = (struct hci_fm_ssbi_peek *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_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_DIAGNOSTIC_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_DIAGNOSTIC_CMD_REQ, + HCI_OCF_FM_STATION_DBG_PARAM); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_set_ch_det_th(struct radio_hci_dev *hdev, + unsigned long param) +{ + struct hci_fm_ch_det_threshold *ch_det_th = + (struct hci_fm_ch_det_threshold *) param; + u16 opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_CH_DET_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, sizeof((*ch_det_th)), + ch_det_th); +} + +static int hci_fm_get_ch_det_th(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_CH_DET_THRESHOLD); + 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 inline int hci_conf_event_mask(__u8 *arg, + struct radio_hci_dev *hdev) +{ + u8 event_mask = *arg; + return radio_hci_request(hdev, hci_fm_set_event_mask, + event_mask, RADIO_HCI_TIMEOUT); +} +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_set_fm_trans_conf(struct hci_fm_trans_conf_req_struct *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_trans_conf_req_struct *set_trans_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_trans_conf_req, (unsigned + long)set_trans_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; +} + +static 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; +} + +static int hci_set_notch_filter(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 notch_filter = *arg; + ret = radio_hci_request(hdev, hci_set_notch_filter_req, + notch_filter, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_peek_data(struct hci_fm_riva_data *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_riva_data *peek_data = arg; + + ret = radio_hci_request(hdev, hci_peek_data_req, (unsigned + long)peek_data, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_poke_data(struct hci_fm_riva_poke *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_riva_poke *poke_data = arg; + + ret = radio_hci_request(hdev, hci_poke_data_req, (unsigned + long)poke_data, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_ssbi_peek_reg(struct hci_fm_ssbi_peek *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ssbi_peek *ssbi_peek_reg = arg; + + ret = radio_hci_request(hdev, hci_ssbi_peek_reg_req, (unsigned + long)ssbi_peek_reg, RADIO_HCI_TIMEOUT); + + return ret; +} + +static 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_set_ch_det_thresholds_req(struct hci_fm_ch_det_threshold *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ch_det_threshold *ch_det_threshold = arg; + ret = radio_hci_request(hdev, hci_fm_set_ch_det_th, + (unsigned long)ch_det_threshold, RADIO_HCI_TIMEOUT); + return ret; +} + +static int hci_fm_set_cal_req_proc(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + struct hci_fm_set_cal_req_proc *cal_req = + (struct hci_fm_set_cal_req_proc *)param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_SET_CALIBRATION); + return radio_hci_send_cmd(hdev, opcode, sizeof(*cal_req), + cal_req); +} + +static int hci_fm_do_cal_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + u8 cal_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(cal_mode), + &cal_mode); + +} +static int hci_cmd(unsigned int cmd, struct radio_hci_dev *hdev) +{ + int ret = 0; + unsigned long arg = 0; + + if (!hdev) + return -ENODEV; + + 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; + + case HCI_FM_ENABLE_TRANS_CMD: + ret = radio_hci_request(hdev, hci_fm_enable_trans_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_DISABLE_TRANS_CMD: + ret = radio_hci_request(hdev, hci_fm_disable_trans_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_TX_CONFIG: + ret = radio_hci_request(hdev, hci_get_fm_trans_conf_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + case HCI_FM_GET_DET_CH_TH_CMD: + ret = radio_hci_request(hdev, hci_fm_get_ch_det_th, 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; + if (radio->mode != FM_CALIB) + iris_q_event(radio, IRIS_EVT_RADIO_DISABLED); + + 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_trans_get_conf_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_get_trans_conf_rsp *rsp = (void *)skb->data; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (rsp->status) + return; + memcpy((void *)&radio->trans_conf, (void*)&rsp->trans_conf_rsp, + sizeof(rsp->trans_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; + if (radio->mode != FM_CALIB) + iris_q_event(radio, IRIS_EVT_RADIO_READY); + + radio_hci_req_complete(hdev, rsp->status); +} + + +static void hci_cc_fm_trans_set_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; + + iris_q_event(radio, HCI_EV_CMD_COMPLETE); + + 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()); + + if (rsp->status) + return; + + memcpy(&radio->sig_th, rsp, sizeof(struct hci_fm_sig_threshold_rsp)); + 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_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 void iris_q_evt_data(struct iris_device *radio, + char *data, int len, int event) +{ + struct kfifo *data_b = &radio->data_buf[event]; + if (kfifo_in_locked(data_b, data, len, &radio->buf_lock[event])) + wake_up_interruptible(&radio->event_queue); +} + +static void hci_cc_riva_peek_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status = *((__u8 *) skb->data); + int len; + char *data; + + if (status) + return; + len = skb->data[RIVA_PEEK_LEN_OFSET] + RIVA_PEEK_PARAM; + data = kmalloc(len, GFP_ATOMIC); + + if (!data) { + FMDERR("Memory allocation failed"); + return; + } + + memcpy(data, &skb->data[PEEK_DATA_OFSET], len); + iris_q_evt_data(radio, data, len, IRIS_BUF_PEEK); + radio_hci_req_complete(hdev, status); + kfree(data); + +} + +static void hci_cc_riva_read_default_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status = *((__u8 *) skb->data); + __u8 len; + + if (status) + return; + len = skb->data[1]; + + memset(&radio->default_data, 0 , sizeof(struct hci_fm_data_rd_rsp)); + memcpy(&radio->default_data, &skb->data[0], len+2); + iris_q_evt_data(radio, &skb->data[0], len+2, IRIS_BUF_RD_DEFAULT); + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_ssbi_peek_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status = *((__u8 *) skb->data); + char *data; + + if (status) + return; + data = kmalloc(SSBI_PEEK_LEN, GFP_ATOMIC); + if (!data) { + FMDERR("Memory allocation failed"); + return; + } + + data[0] = skb->data[PEEK_DATA_OFSET]; + iris_q_evt_data(radio, data, SSBI_PEEK_LEN, IRIS_BUF_SSBI_PEEK); + radio_hci_req_complete(hdev, status); + kfree(data); +} + +static void hci_cc_rds_grp_cntrs_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status = *((__u8 *) skb->data); + char *data; + if (status) + return; + data = kmalloc(RDS_GRP_CNTR_LEN, GFP_ATOMIC); + if (!data) { + FMDERR("memory allocation failed"); + return; + } + memcpy(data, &skb->data[1], RDS_GRP_CNTR_LEN); + iris_q_evt_data(radio, data, RDS_GRP_CNTR_LEN, IRIS_BUF_RDS_CNTRS); + radio_hci_req_complete(hdev, status); + kfree(data); + +} + +static void hci_cc_do_calibration_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + static struct hci_cc_do_calibration_rsp rsp ; + rsp.status = skb->data[0]; + rsp.mode = skb->data[CALIB_MODE_OFSET]; + + if (rsp.status) { + FMDERR("status = %d", rsp.status); + return; + } + if (rsp.mode == PROCS_CALIB_MODE) { + memcpy(&rsp.data[0], &skb->data[CALIB_DATA_OFSET], + PROCS_CALIB_SIZE); + iris_q_evt_data(radio, rsp.data, PROCS_CALIB_SIZE, + IRIS_BUF_CAL_DATA); + } else { + return; + } + + radio_hci_req_complete(hdev, rsp.status); +} + +static void hci_cc_get_ch_det_threshold_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + u8 status = skb->data[0]; + if (status) { + FMDERR("status = %d", status); + return; + } + memcpy(&radio->ch_det_threshold, &skb->data[1], + sizeof(struct hci_fm_ch_det_threshold)); + radio_hci_req_complete(hdev, 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): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_ENABLE_TRANS_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): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_DISABLE_TRANS_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_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_CH_DET_THRESHOLD): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_RT_REQ): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_PS_REQ): + case hci_common_cmd_op_pack(HCI_OCF_FM_DEFAULT_DATA_WRITE): + hci_cc_rsp(hdev, skb); + break; + case hci_common_cmd_op_pack(HCI_OCF_FM_RESET): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_SSBI_POKE_REG): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_POKE_DATA): + case hci_diagnostic_cmd_op_pack(HCI_FM_SET_INTERNAL_TONE_GENRATOR): + case hci_common_cmd_op_pack(HCI_OCF_FM_SET_CALIBRATION): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_EVENT_MASK): + hci_cc_rsp(hdev, skb); + break; + + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_SSBI_PEEK_REG): + hci_cc_ssbi_peek_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): + hci_cc_riva_read_default_rsp(hdev, skb); + break; + + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_PEEK_DATA): + hci_cc_riva_peek_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; + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_SET_TRANS_CONF_REQ): + hci_cc_fm_trans_set_conf_rsp(hdev, skb); + break; + + case hci_status_param_op_pack(HCI_OCF_FM_READ_GRP_COUNTERS): + hci_cc_rds_grp_cntrs_rsp(hdev, skb); + break; + case hci_common_cmd_op_pack(HCI_OCF_FM_DO_CALIBRATION): + hci_cc_do_calibration_rsp(hdev, skb); + break; + + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_GET_TRANS_CONF_REQ): + hci_cc_fm_trans_get_conf_rsp(hdev, skb); + break; + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_CH_DET_THRESHOLD): + hci_cc_get_ch_det_threshold_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; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + memcpy(&radio->fm_st_rsp.station_rsp, &skb->data[0], + sizeof(struct hci_ev_tune_status)); + 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.serv_avble) + 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 ; + int cnt; + int stn_num; + int rel_freq; + int abs_freq; + int len; + + ev = kmalloc(sizeof(*ev), GFP_ATOMIC); + if (!ev) { + FMDERR("Memory allocation failed"); + return ; + } + + ev->num_stations_found = skb->data[STN_NUM_OFFSET]; + len = ev->num_stations_found * PARAMS_PER_STATION + STN_FREQ_OFFSET; + + for (cnt = STN_FREQ_OFFSET, stn_num = 0; + (cnt < len) && (stn_num < ev->num_stations_found) + && (stn_num < ARRAY_SIZE(ev->rel_freq)); + cnt += PARAMS_PER_STATION, stn_num++) { + abs_freq = *((int *)&skb->data[cnt]); + rel_freq = abs_freq - radio->recv_conf.band_low_limit; + rel_freq = (rel_freq * 20) / KHZ_TO_MHZ; + + ev->rel_freq[stn_num].rel_freq_lsb = GET_LSB(rel_freq); + ev->rel_freq[stn_num].rel_freq_msb = GET_MSB(rel_freq); + } + + len = ev->num_stations_found * 2 + sizeof(ev->num_stations_found); + iris_q_event(radio, IRIS_EVT_NEW_SRCH_LIST); + iris_q_evt_data(radio, (char *)ev, len, IRIS_BUF_SRCH_LIST); + kfree(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); +} + + +static inline void hci_ev_program_service(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + int len; + char *data; + + len = (skb->data[RDS_PS_LENGTH_OFFSET] * RDS_STRING) + RDS_OFFSET; + iris_q_event(radio, IRIS_EVT_NEW_PS_RDS); + data = kmalloc(len, GFP_ATOMIC); + if (!data) { + FMDERR("Failed to allocate memory"); + return; + } + + data[0] = skb->data[RDS_PS_LENGTH_OFFSET]; + data[1] = skb->data[RDS_PTYPE]; + data[2] = skb->data[RDS_PID_LOWER]; + data[3] = skb->data[RDS_PID_HIGHER]; + data[4] = 0; + + memcpy(data+RDS_OFFSET, &skb->data[RDS_PS_DATA_OFFSET], len-RDS_OFFSET); + + iris_q_evt_data(radio, data, len, IRIS_BUF_PS_RDS); + + kfree(data); +} + + +static inline void hci_ev_radio_text(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + int len = 0; + char *data; + + iris_q_event(radio, IRIS_EVT_NEW_RT_RDS); + + while ((skb->data[len+RDS_OFFSET] != 0x0d) && (len < RX_RT_DATA_LENGTH)) + len++; + data = kmalloc(len+RDS_OFFSET, GFP_ATOMIC); + if (!data) { + FMDERR("Failed to allocate memory"); + return; + } + + data[0] = len; + data[1] = skb->data[RDS_PTYPE]; + data[2] = skb->data[RDS_PID_LOWER]; + data[3] = skb->data[RDS_PID_HIGHER]; + data[4] = 0; + + memcpy(data+RDS_OFFSET, &skb->data[RDS_OFFSET], len); + data[len+RDS_OFFSET] = 0x00; + + iris_q_evt_data(radio, data, len+RDS_OFFSET, IRIS_BUF_RT_RDS); + + kfree(data); +} + +static void hci_ev_af_list(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_ev_af_list ev; + + ev.tune_freq = *((int *) &skb->data[0]); + ev.pi_code = *((__le16 *) &skb->data[PI_CODE_OFFSET]); + ev.af_size = skb->data[AF_SIZE_OFFSET]; + memcpy(&ev.af_list[0], &skb->data[AF_LIST_OFFSET], ev.af_size); + iris_q_event(radio, IRIS_EVT_NEW_AF_LIST); + iris_q_evt_data(radio, (char *)&ev, sizeof(ev), IRIS_BUF_AF_LIST); +} + +static void hci_ev_rds_lock_status(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 rds_status = skb->data[0]; + + if (rds_status) + iris_q_event(radio, IRIS_EVT_RDS_AVAIL); + else + iris_q_event(radio, IRIS_EVT_RDS_NOT_AVAIL); +} + +static void hci_ev_service_available(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + u8 serv_avble = skb->data[0]; + if (serv_avble) + iris_q_event(radio, IRIS_EVT_ABOVE_TH); + else + iris_q_event(radio, IRIS_EVT_BELOW_TH); +} + +static void hci_ev_rds_grp_complete(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_TXRDSDONE); +} + +void radio_hci_event_packet(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct radio_hci_event_hdr *hdr; + u8 event; + + if (skb == NULL) { + FMDERR("Socket buffer is NULL"); + return; + } + + hdr = (void *) skb->data; + 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: + hci_ev_rds_lock_status(hdev, skb); + break; + case HCI_EV_SERVICE_AVAILABLE: + hci_ev_service_available(hdev, skb); + break; + case HCI_EV_RDS_RX_DATA: + break; + case HCI_EV_PROGRAM_SERVICE: + hci_ev_program_service(hdev, skb); + break; + case HCI_EV_RADIO_TEXT: + hci_ev_radio_text(hdev, skb); + break; + case HCI_EV_FM_AF_LIST: + hci_ev_af_list(hdev, skb); + break; + case HCI_EV_TX_RDS_GRP_COMPL: + hci_ev_rds_grp_complete(hdev, skb); + break; + 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: + hci_ev_srch_st_list_compl(hdev, skb); + 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; + radio->search_on = on; + + 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: + srch = srch - SEARCH_RDS_STNS_MODE_OFFSET; + 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 set_low_power_mode(struct iris_device *radio, int power_mode) +{ + + int rds_grps_proc = 0x00; + int retval = 0; + if (radio->power_mode != power_mode) { + + if (power_mode) { + radio->event_mask = 0x00; + if (radio->af_jump_bit) + rds_grps_proc = 0x00 | AF_JUMP_ENABLE; + else + rds_grps_proc = 0x00; + retval = hci_fm_rds_grps_process( + &rds_grps_proc, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Disable RDS failed"); + return retval; + } + retval = hci_conf_event_mask(&radio->event_mask, + radio->fm_hdev); + } else { + + radio->event_mask = SIG_LEVEL_INTR | + RDS_SYNC_INTR | AUDIO_CTRL_INTR; + retval = hci_conf_event_mask(&radio->event_mask, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enable Async events failed"); + return retval; + } + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + } + radio->power_mode = power_mode; + } + return retval; +} +static int iris_recv_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 = + REGION_US_EU_BAND_LOW; + radio->recv_conf.band_high_limit = + REGION_US_EU_BAND_HIGH; + break; + case IRIS_REGION_EU: + radio->recv_conf.band_low_limit = + REGION_US_EU_BAND_LOW; + radio->recv_conf.band_high_limit = + REGION_US_EU_BAND_HIGH; + break; + case IRIS_REGION_JAPAN: + radio->recv_conf.band_low_limit = + REGION_JAPAN_STANDARD_BAND_LOW; + radio->recv_conf.band_high_limit = + REGION_JAPAN_STANDARD_BAND_HIGH; + break; + case IRIS_REGION_JAPAN_WIDE: + radio->recv_conf.band_low_limit = + REGION_JAPAN_WIDE_BAND_LOW; + radio->recv_conf.band_high_limit = + REGION_JAPAN_WIDE_BAND_HIGH; + break; + default: + /* The user specifies the value. + So nothing needs to be done */ + break; + } + + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + + return retval; +} + + +static int iris_trans_set_region(struct iris_device *radio, int req_region) +{ + int retval; + radio->region = req_region; + + switch (radio->region) { + case IRIS_REGION_US: + radio->trans_conf.band_low_limit = + REGION_US_EU_BAND_LOW; + radio->trans_conf.band_high_limit = + REGION_US_EU_BAND_HIGH; + break; + case IRIS_REGION_EU: + radio->trans_conf.band_low_limit = + REGION_US_EU_BAND_LOW; + radio->trans_conf.band_high_limit = + REGION_US_EU_BAND_HIGH; + break; + case IRIS_REGION_JAPAN: + radio->trans_conf.band_low_limit = + REGION_JAPAN_STANDARD_BAND_LOW; + radio->trans_conf.band_high_limit = + REGION_JAPAN_STANDARD_BAND_HIGH; + break; + case IRIS_REGION_JAPAN_WIDE: + radio->recv_conf.band_low_limit = + REGION_JAPAN_WIDE_BAND_LOW; + radio->recv_conf.band_high_limit = + REGION_JAPAN_WIDE_BAND_HIGH; + default: + break; + } + + retval = hci_set_fm_trans_conf( + &radio->trans_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_do_calibration(struct iris_device *radio) +{ + char cal_mode = 0x00; + int retval = 0x00; + + cal_mode = PROCS_CALIB_MODE; + radio->mode = FM_CALIB; + retval = hci_cmd(HCI_FM_ENABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enable failed before calibration %x", retval); + radio->mode = FM_OFF; + return retval; + } + retval = radio_hci_request(radio->fm_hdev, hci_fm_do_cal_req, + (unsigned long)cal_mode, RADIO_HCI_TIMEOUT); + if (retval < 0) { + FMDERR("Do Process calibration failed %x", retval); + radio->mode = FM_RECV; + return retval; + } + retval = hci_cmd(HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Disable Failed after calibration %d", retval); + radio->mode = FM_OFF; + 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: + ctrl->value = radio->search_on; + break; + case V4L2_CID_PRIVATE_IRIS_STATE: + ctrl->value = radio->mode; + 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); + if (retval < 0) { + FMDERR("Error in get signal threshold %d\n", retval); + return retval; + } + ctrl->value = radio->sig_th.sig_threshold; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PTY: + ctrl->value = radio->srch_rds.srch_pty; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PI: + ctrl->value = radio->srch_rds.srch_pi; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_CNT: + ctrl->value = radio->srch_st_result.num_stations_found; + break; + case V4L2_CID_PRIVATE_IRIS_EMPHASIS: + if (radio->mode == FM_RECV) { + ctrl->value = radio->recv_conf.emphasis; + } else if (radio->mode == FM_TRANS) { + ctrl->value = radio->trans_conf.emphasis; + } else { + FMDERR("Error in radio mode" + " %d\n", retval); + return -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDS_STD: + if (radio->mode == FM_RECV) { + ctrl->value = radio->recv_conf.rds_std; + } else if (radio->mode == FM_TRANS) { + ctrl->value = radio->trans_conf.rds_std; + } else { + FMDERR("Error in radio mode" + " %d\n", retval); + return -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_SPACING: + if (radio->mode == FM_RECV) { + ctrl->value = radio->recv_conf.ch_spacing; + } else { + FMDERR("Error in radio mode" + " %d\n", retval); + return -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSON: + if (radio->mode == FM_RECV) { + ctrl->value = radio->recv_conf.rds_std; + } else { + FMDERR("Error in radio mode" + " %d\n", retval); + return -EINVAL; + } + 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: + case V4L2_CID_PRIVATE_IRIS_PSALL: + ctrl->value = (radio->g_rds_grp_proc_ps << RDS_CONFIG_OFFSET); + break; + case V4L2_CID_PRIVATE_IRIS_RDSD_BUF: + ctrl->value = radio->rds_grp.rds_buf_size; + break; + case V4L2_CID_PRIVATE_IRIS_LP_MODE: + ctrl->value = radio->power_mode; + break; + case V4L2_CID_PRIVATE_IRIS_ANTENNA: + ctrl->value = radio->g_antenna; + break; + case V4L2_CID_PRIVATE_IRIS_SOFT_MUTE: + ctrl->value = radio->mute_mode.soft_mute; + break; + case V4L2_CID_PRIVATE_IRIS_DO_CALIBRATION: + retval = iris_do_calibration(radio); + break; + case V4L2_CID_PRIVATE_IRIS_GET_SINR: + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Get SINR Failed"); + return retval; + } + ctrl->value = radio->fm_st_rsp.station_rsp.sinr; + + } else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Get High det threshold failed %x", retval); + return retval; + } + ctrl->value = radio->ch_det_threshold.high_th; + break; + case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Get Low det threshold failed %x", retval); + return retval; + } + ctrl->value = radio->ch_det_threshold.low_th; + break; + case V4L2_CID_PRIVATE_SINR_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Get SINR threshold failed %x", retval); + return retval; + } + ctrl->value = radio->ch_det_threshold.sinr; + break; + case V4L2_CID_PRIVATE_SINR_SAMPLES: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Get SINR samples failed %x", retval); + return retval; + } + + ctrl->value = radio->ch_det_threshold.sinr_samples; + 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_g_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrl) +{ + int retval = 0; + char *data = NULL; + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + struct hci_fm_def_data_rd_req default_data_rd; + + switch ((ctrl->controls[0]).id) { + case V4L2_CID_PRIVATE_IRIS_READ_DEFAULT: + data = (ctrl->controls[0]).string; + memset(&default_data_rd, 0, sizeof(default_data_rd)); + if (copy_from_user(&default_data_rd.mode, data, + sizeof(default_data_rd))) + return -EFAULT; + retval = hci_def_data_read(&default_data_rd, radio->fm_hdev); + break; + default: + retval = -EINVAL; + } + + return retval; +} + +static int iris_vidioc_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrl) +{ + int retval = 0; + int bytes_to_copy; + struct hci_fm_tx_ps tx_ps; + struct hci_fm_tx_rt tx_rt; + struct hci_fm_def_data_wr_req default_data; + struct hci_fm_set_cal_req_proc proc_cal_req; + + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + char *data = NULL; + + 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 */ + + memset(tx_ps.ps_data, 0, MAX_PS_LENGTH); + bytes_to_copy = min((int)(ctrl->controls[0]).size, + MAX_PS_LENGTH); + data = (ctrl->controls[0]).string; + + if (copy_from_user(tx_ps.ps_data, + data, bytes_to_copy)) + return -EFAULT; + tx_ps.ps_control = 0x01; + tx_ps.pi = radio->pi; + tx_ps.pty = radio->pty; + tx_ps.ps_repeatcount = radio->ps_repeatcount; + tx_ps.ps_len = bytes_to_copy; + + retval = radio_hci_request(radio->fm_hdev, hci_trans_ps_req, + (unsigned long)&tx_ps, RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_RDS_TX_RADIO_TEXT: + bytes_to_copy = + min((int)(ctrl->controls[0]).size, MAX_RT_LENGTH); + data = (ctrl->controls[0]).string; + + memset(tx_rt.rt_data, 0, MAX_RT_LENGTH); + + if (copy_from_user(tx_rt.rt_data, + data, bytes_to_copy)) + return -EFAULT; + + tx_rt.rt_control = 0x01; + tx_rt.pi = radio->pi; + tx_rt.pty = radio->pty; + tx_rt.ps_len = bytes_to_copy; + + retval = radio_hci_request(radio->fm_hdev, hci_trans_rt_req, + (unsigned long)&tx_rt, RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_PRIVATE_IRIS_WRITE_DEFAULT: + data = (ctrl->controls[0]).string; + memset(&default_data, 0, sizeof(default_data)); + /* + * Check if length of the 'FM Default Data' to be sent + * is within the maximum 'FM Default Data' packet limit. + * Max. 'FM Default Data' packet length is 251 bytes: + * 1 byte - XFR Mode + * 1 byte - length of the default data + * 249 bytes - actual data to be configured + */ + if (ctrl->controls[0].size > (DEFAULT_DATA_SIZE + 2)) { + pr_err("%s: Default data buffer overflow!\n", __func__); + return -EINVAL; + } + + /* copy only 'size' bytes of data as requested by user */ + retval = copy_from_user(&default_data, data, + ctrl->controls[0].size); + if (retval > 0) { + pr_err("%s: Failed to copy %d bytes of default data" + " passed by user\n", __func__, retval); + return -EFAULT; + } + FMDBG("%s: XFR Mode\t: 0x%x\n", __func__, default_data.mode); + FMDBG("%s: XFR Data Length\t: %d\n", __func__, + default_data.length); + /* + * Check if the 'length' of the actual XFR data to be configured + * is valid or not. Length of actual XFR data should be always + * 2 bytes less than the total length of the 'FM Default Data'. + * Length of 'FM Default Data' DEF_DATA_LEN: (1+1+XFR Data Size) + * Length of 'Actual XFR Data' XFR_DATA_LEN: (DEF_DATA_LEN - 2) + */ + if (default_data.length != (ctrl->controls[0].size - 2)) { + pr_err("%s: Invalid 'length' parameter passed for " + "actual xfr data\n", __func__); + return -EINVAL; + } + retval = hci_def_data_write(&default_data, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SET_CALIBRATION: + data = (ctrl->controls[0]).string; + bytes_to_copy = (ctrl->controls[0]).size; + if (bytes_to_copy < PROCS_CALIB_SIZE) { + FMDERR("data is less than required size"); + return -EFAULT; + } + memset(proc_cal_req.data, 0, PROCS_CALIB_SIZE); + proc_cal_req.mode = PROCS_CALIB_MODE; + if (copy_from_user(&proc_cal_req.data[0], + data, sizeof(proc_cal_req.data))) + return -EFAULT; + retval = radio_hci_request(radio->fm_hdev, + hci_fm_set_cal_req_proc, + (unsigned long)&proc_cal_req, + RADIO_HCI_TIMEOUT); + if (retval < 0) + FMDERR("Set Process calibration failed %d", retval); + break; + default: + FMDBG("Shouldn't reach here\n"); + retval = -1; + } + return retval; +} + +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; + unsigned long arg = 0; + struct hci_fm_tx_ps tx_ps = {0}; + struct hci_fm_tx_rt tx_rt = {0}; + struct hci_fm_def_data_rd_req rd_txgain; + struct hci_fm_def_data_wr_req wr_txgain; + + switch (ctrl->id) { + case V4L2_CID_PRIVATE_IRIS_TX_TONE: + radio->tone_freq = ctrl->value; + retval = radio_hci_request(radio->fm_hdev, + hci_fm_tone_generator, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + if (retval < 0) + FMDERR("Error while setting the tone %d", retval); + break; + 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: + switch (ctrl->value) { + case FM_RECV: + retval = hci_cmd(HCI_FM_ENABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error while enabling RECV FM" + " %d\n", retval); + return retval; + } + radio->mode = FM_RECV; + radio->mute_mode.soft_mute = CTRL_ON; + retval = hci_set_fm_mute_mode( + &radio->mute_mode, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to enable Smute\n"); + return retval; + } + radio->stereo_mode.stereo_mode = CTRL_OFF; + radio->stereo_mode.sig_blend = CTRL_ON; + radio->stereo_mode.intf_blend = CTRL_ON; + radio->stereo_mode.most_switch = CTRL_ON; + retval = hci_set_fm_stereo_mode( + &radio->stereo_mode, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set stereo mode\n"); + return retval; + } + radio->event_mask = SIG_LEVEL_INTR | + RDS_SYNC_INTR | AUDIO_CTRL_INTR; + retval = hci_conf_event_mask(&radio->event_mask, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enable Async events failed"); + return retval; + } + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Failed to get the Recv Config\n"); + break; + case FM_TRANS: + retval = hci_cmd(HCI_FM_ENABLE_TRANS_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error while enabling TRANS FM" + " %d\n", retval); + return retval; + } + radio->mode = FM_TRANS; + retval = hci_cmd(HCI_FM_GET_TX_CONFIG, radio->fm_hdev); + if (retval < 0) + FMDERR("get frequency failed %d\n", retval); + break; + case FM_OFF: + switch (radio->mode) { + case FM_RECV: + retval = hci_cmd(HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Err on disable recv FM" + " %d\n", retval); + return retval; + } + radio->mode = FM_OFF; + break; + case FM_TRANS: + retval = hci_cmd(HCI_FM_DISABLE_TRANS_CMD, + radio->fm_hdev); + + if (retval < 0) { + FMDERR("Err disabling trans FM" + " %d\n", retval); + return retval; + } + radio->mode = FM_OFF; + break; + default: + retval = -EINVAL; + } + break; + default: + retval = -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_REGION: + if (radio->mode == FM_RECV) { + retval = iris_recv_set_region(radio, ctrl->value); + } else { + if (radio->mode == FM_TRANS) + retval = iris_trans_set_region(radio, + ctrl->value); + else + retval = -EINVAL; + } + 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: + if (radio->mode == FM_RECV) { + radio->recv_conf.ch_spacing = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in setting channel spacing"); + } + break; + case V4L2_CID_PRIVATE_IRIS_EMPHASIS: + switch (radio->mode) { + case FM_RECV: + radio->recv_conf.emphasis = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in setting emphasis"); + break; + case FM_TRANS: + radio->trans_conf.emphasis = ctrl->value; + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in setting emphasis"); + break; + default: + retval = -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDS_STD: + switch (radio->mode) { + case FM_RECV: + radio->recv_conf.rds_std = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in rds_std"); + break; + case FM_TRANS: + radio->trans_conf.rds_std = ctrl->value; + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in rds_Std"); + break; + default: + retval = -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSON: + switch (radio->mode) { + case FM_RECV: + radio->recv_conf.rds_std = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in rds_std"); + break; + case FM_TRANS: + radio->trans_conf.rds_std = ctrl->value; + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error in rds_Std"); + break; + default: + retval = -EINVAL; + } + 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; + radio->g_rds_grp_proc_ps = (rds_grps_proc >> RDS_CONFIG_OFFSET); + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + 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: + rds_grps_proc = (ctrl->value << RDS_CONFIG_OFFSET); + radio->g_rds_grp_proc_ps |= rds_grps_proc; + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_AF_JUMP: + /*Clear the current AF jump settings*/ + radio->g_rds_grp_proc_ps &= ~(1 << RDS_AF_JUMP_OFFSET); + radio->af_jump_bit = ctrl->value; + rds_grps_proc = 0x00; + rds_grps_proc = (ctrl->value << RDS_AF_JUMP_OFFSET); + radio->g_rds_grp_proc_ps |= rds_grps_proc; + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_LP_MODE: + set_low_power_mode(radio, ctrl->value); + break; + case V4L2_CID_PRIVATE_IRIS_ANTENNA: + temp_val = ctrl->value; + retval = hci_fm_set_antenna(&temp_val, radio->fm_hdev); + if (retval < 0) { + FMDERR("Set Antenna failed retval = %x", retval); + return retval; + } + radio->g_antenna = ctrl->value; + break; + 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_IRIS_STOP_RDS_TX_PS_NAME: + tx_ps.ps_control = 0x00; + retval = radio_hci_request(radio->fm_hdev, hci_trans_ps_req, + (unsigned long)&tx_ps, RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_RT: + tx_rt.rt_control = 0x00; + retval = radio_hci_request(radio->fm_hdev, hci_trans_rt_req, + (unsigned long)&tx_rt, RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_PRIVATE_IRIS_TX_SETPSREPEATCOUNT: + radio->ps_repeatcount = ctrl->value; + break; + case V4L2_CID_TUNE_POWER_LEVEL: + 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; + rd_txgain.mode = FM_TX_PHY_CFG_MODE; + rd_txgain.length = FM_TX_PHY_CFG_LEN; + rd_txgain.param_len = 0x00; + rd_txgain.param = 0x00; + + retval = hci_def_data_read(&rd_txgain, radio->fm_hdev); + if (retval < 0) { + FMDERR("Default data read failed for PHY_CFG %d\n", + retval); + break; + } + memset(&wr_txgain, 0, sizeof(wr_txgain)); + wr_txgain.mode = FM_TX_PHY_CFG_MODE; + wr_txgain.length = FM_TX_PHY_CFG_LEN; + memcpy(&wr_txgain.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wr_txgain.data[FM_TX_PWR_GAIN_OFFSET] = + (ctrl->value) * FM_TX_PWR_LVL_STEP_SIZE; + retval = hci_def_data_write(&wr_txgain, radio->fm_hdev); + if (retval < 0) + FMDERR("Default write failed for PHY_TXGAIN %d\n", + retval); + break; + case V4L2_CID_PRIVATE_IRIS_SOFT_MUTE: + radio->mute_mode.soft_mute = ctrl->value; + retval = hci_set_fm_mute_mode( + &radio->mute_mode, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error while setting FM soft mute"" %d\n", + retval); + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_ADDR: + radio->riva_data_req.cmd_params.start_addr = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_LEN: + radio->riva_data_req.cmd_params.length = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_POKE: + memcpy(radio->riva_data_req.data, (void *)ctrl->value, + radio->riva_data_req.cmd_params.length); + radio->riva_data_req.cmd_params.subopcode = RIVA_POKE_OPCODE; + retval = hci_poke_data(&radio->riva_data_req , radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SSBI_ACCS_ADDR: + radio->ssbi_data_accs.start_addr = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_SSBI_POKE: + radio->ssbi_data_accs.data = ctrl->value; + retval = hci_ssbi_poke_reg(&radio->ssbi_data_accs , + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_PEEK: + radio->riva_data_req.cmd_params.subopcode = RIVA_PEEK_OPCODE; + ctrl->value = hci_peek_data(&radio->riva_data_req.cmd_params , + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SSBI_PEEK: + radio->ssbi_peek_reg.start_address = ctrl->value; + hci_ssbi_peek_reg(&radio->ssbi_peek_reg, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDS_GRP_COUNTERS: + temp_val = ctrl->value; + hci_read_grp_counters(&temp_val, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_HLSI: + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval) + break; + radio->recv_conf.hlsi = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SET_NOTCH_FILTER: + temp_val = ctrl->value; + retval = hci_set_notch_filter(&temp_val, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get chnl det thresholds %d", retval); + return retval; + } + radio->ch_det_threshold.high_th = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set High det threshold %d ", retval); + return retval; + } + break; + + case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get chnl det thresholds %d", retval); + return retval; + } + radio->ch_det_threshold.low_th = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to Set Low det threshold %d", retval); + return retval; + } + break; + + case V4L2_CID_PRIVATE_SINR_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get chnl det thresholds %d", retval); + return retval; + } + radio->ch_det_threshold.sinr = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set SINR threshold %d", retval); + return retval; + } + break; + + case V4L2_CID_PRIVATE_SINR_SAMPLES: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get chnl det thresholds %d", retval); + return retval; + } + radio->ch_det_threshold.sinr_samples = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set SINR samples %d", retval); + return retval; + } + break; + + case V4L2_CID_PRIVATE_IRIS_SRCH_ALGORITHM: + case V4L2_CID_PRIVATE_IRIS_SET_AUDIO_PATH: + /* + These private controls are place holders to keep the + driver compatible with changes done in the frameworks + which are specific to TAVARUA. + */ + retval = 0; + break; + default: + retval = -EINVAL; + } + return retval; +} + +static int iris_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int retval; + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + + if (tuner->index > 0) { + FMDERR("Invalid Tuner Index"); + return -EINVAL; + } + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to Get station params"); + 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; + } else if (radio->mode == FM_TRANS) { + retval = hci_cmd(HCI_FM_GET_TX_CONFIG, radio->fm_hdev); + if (retval < 0) { + FMDERR("get Tx config failed %d\n", retval); + return retval; + } else { + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + radio->trans_conf.band_low_limit * TUNE_PARAM; + tuner->rangehigh = + radio->trans_conf.band_high_limit * TUNE_PARAM; + } + + } else + return -EINVAL; + 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 = 0; + if (tuner->index > 0) + return -EINVAL; + + if (radio->mode == FM_RECV) { + 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; + } else if (radio->mode == FM_TRANS) { + radio->trans_conf.band_low_limit = + tuner->rangelow / TUNE_PARAM; + radio->trans_conf.band_high_limit = + tuner->rangehigh / TUNE_PARAM; + } else + return -EINVAL; + + 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)); + if ((freq != NULL) && (radio != NULL)) { + freq->frequency = + radio->fm_st_rsp.station_rsp.station_freq * TUNE_PARAM; + } else + return -EINVAL; + return 0; +} + +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; + + /* We turn off RDS prior to tuning to a new station. + because of a bug in SoC which prevents tuning + during RDS transmission. + */ + if (radio->mode == FM_TRANS + && (radio->trans_conf.rds_std == 0 || + radio->trans_conf.rds_std == 1)) { + radio->prev_trans_rds = radio->trans_conf.rds_std; + radio->trans_conf.rds_std = 2; + hci_set_fm_trans_conf(&radio->trans_conf, + radio->fm_hdev); + } + + retval = iris_set_freq(radio, freq->frequency); + + if (radio->mode == FM_TRANS + && radio->trans_conf.rds_std == 2 + && (radio->prev_trans_rds == 1 + || radio->prev_trans_rds == 0)) { + radio->trans_conf.rds_std = radio->prev_trans_rds; + hci_set_fm_trans_conf(&radio->trans_conf, + radio->fm_hdev); + } + + 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, + .vidioc_g_ext_ctrls = iris_vidioc_g_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) || (i == IRIS_BUF_PEEK)) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + rds_buf*3, GFP_KERNEL); + else if ((i == IRIS_BUF_CAL_DATA) || (i == IRIS_BUF_RT_RDS)) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE*2, 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; + radio->prev_trans_rds = 2; + 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 0000000000000000000000000000000000000000..c15609f58f93cad5351c16645447b7e0bbea6376 --- /dev/null +++ b/drivers/media/radio/radio-tavarua.c @@ -0,0 +1,4204 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 */ + atomic_t 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 shutdown_done; + 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; + int enable_optimized_srch_alg; +}; + +/************************************************************************** + * 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); +} + +static void fm_shutdown(struct work_struct *work) +{ + struct tavarua_device *radio = container_of(work, + struct tavarua_device, work.work); + FMDERR("%s: Releasing the FM I2S GPIO\n", __func__); + if (radio->pdata->config_i2s_gpio != NULL) + radio->pdata->config_i2s_gpio(FM_I2S_OFF); + FMDERR("%s: Shutting down FM SOC\n", __func__); + radio->pdata->fm_shutdown(radio->pdata); + complete(&radio->shutdown_done); +} + + +/************************************************************************* + * 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). + */ + /* 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); + free_irq(irq, radio); + cancel_delayed_work_sync(&radio->work); + flush_workqueue(radio->wqueue); + return 0; +} + +static int optimized_search_algorithm(struct tavarua_device *radio, + int region) +{ + unsigned char adie_type_bahma; + int retval = 0; + unsigned int rdsMask = 0; + unsigned char value; + + adie_type_bahma = is_bahama(); + + switch (region) { + case TAVARUA_REGION_US: + /* + Radio band for all the 200KHz channel-spaced regions + coming under EUROPE too, have been set as TAVARUA_REGION_US. + */ + FMDBG("%s: The region selected from APP is" + " : TAVARUA_REGION_US", __func__); + break; + case TAVARUA_REGION_EU: + /* + Radio band for all the 50KHz channel-spaced regions + coming under EUROPE, have been set as TAVARUA_REGION_EU. + */ + FMDBG("%s: The region selected from APP is : " + "TAVARUA_REGION_EU", __func__); + break; + case TAVARUA_REGION_JAPAN: + /* + Radio band for the 100KHz channel-spaced JAPAN region + has been set as TAVARUA_REGION_JAPAN. + */ + FMDBG("%s: The region selected from APP is" + " : TAVARUA_REGION_JAPAN", __func__); + break; + case TAVARUA_REGION_JAPAN_WIDE: + /* + Radio band for the 50KHz channel-spaced JAPAN WIDE region + has been set as TAVARUA_REGION_JAPAN_WIDE. + */ + FMDBG("%s: The region selected from APP is" + " : TAVARUA_REGION_JAPAN_WIDE", __func__); + break; + case TAVARUA_REGION_OTHER: + /* + Radio band for all the 100KHz channel-spaced regions + including those coming under EUROPE have been set as + TAVARUA_REGION_OTHER. + */ + FMDBG("%s: The region selected from APP is" + " : TAVARUA_REGION_OTHER", __func__); + break; + default: + pr_err("%s: Should not reach here.", __func__); + break; + + } + + /* Enable or Disable the 200KHz enforcer */ + switch (region) { + case TAVARUA_REGION_US: + case TAVARUA_REGION_JAPAN: + case TAVARUA_REGION_OTHER: + /* + These are the 3 bands for which we need to enable the + 200KHz enforcer in ADVCTL reg. + */ + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + FMDBG("%s: Enabling the 200KHz enforcer for" + " Region : %d", __func__, region); + /*Enable the 200KHz enforcer*/ + retval = tavarua_read_registers(radio, + ADVCTRL, 1); + if (retval >= 0) { + rdsMask = radio->registers[ADVCTRL]; + SET_REG_FIELD(rdsMask, ENF_SRCH200khz, + SRCH200KHZ_OFFSET, SRCH_MASK); + retval = tavarua_write_register(radio, + ADVCTRL, rdsMask); + } else + return retval; + } /* if Marimba do nothing */ + break; + case TAVARUA_REGION_EU: + case TAVARUA_REGION_JAPAN_WIDE: + /* + These are the 2 bands for which we need to disable the + 200KHz enforcer in ADVCTL reg. + Radio band for all the 50KHz channel-spaced regions + coming under EUROPE have been set as TAVARUA_REGION_EU. + */ + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + FMDBG("%s: Disabling the 200KHz enforcer for" + " Region : %d", __func__, region); + /* + Disable 200KHz enforcer for all 50 KHz + spaced regions. + */ + retval = tavarua_read_registers(radio, + ADVCTRL, 1); + if (retval >= 0) { + rdsMask = radio->registers[ADVCTRL]; + SET_REG_FIELD(rdsMask, NO_SRCH200khz, + SRCH200KHZ_OFFSET, SRCH_MASK); + retval = tavarua_write_register(radio, + ADVCTRL, rdsMask); + } else + return retval; + } /* if Marimba do nothing */ + break; + default: + FMDBG("%s: Defaulting in case of Enabling/Disabling" + "the 200KHz Enforcer", __func__); + break; + } + + /* Set channel spacing */ + switch (region) { + case TAVARUA_REGION_US: + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + /* + Configuring all 200KHZ spaced regions as 100KHz due to + change in the new Bahma FM SoC search algorithm. + */ + value = FM_CH_SPACE_100KHZ; + } else { + FMDBG("Adie type : Marimba\n"); + value = FM_CH_SPACE_200KHZ; + } + break; + case TAVARUA_REGION_JAPAN: + case TAVARUA_REGION_OTHER: + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + FMDBG("%s: Configuring the channel-spacing as 50KHz" + "for the Region : %d", __func__, region); + /* + Configuring all 100KHZ spaced regions as 50KHz due to + change in the new Bahma FM SoC search algorithm. + */ + value = FM_CH_SPACE_50KHZ; + } else { + FMDBG("Adie type : Marimba\n"); + value = FM_CH_SPACE_100KHZ; + } + break; + case TAVARUA_REGION_EU: + case TAVARUA_REGION_JAPAN_WIDE: + value = FM_CH_SPACE_50KHZ; + break; + default: + FMDBG("%s: Defualting in case of Channel-Spacing", __func__); + break; + } + + SET_REG_FIELD(radio->registers[RDCTRL], value, + RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK); + + return retval; +} +/************************************************************************* + * 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 int rdsMask = 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; + unsigned char adie_type_bahma; + + adie_type_bahma = is_bahama(); + + /* Set freq band */ + if (region == TAVARUA_REGION_JAPAN) + SET_REG_FIELD(radio->registers[RDCTRL], 1, + RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK); + else + SET_REG_FIELD(radio->registers[RDCTRL], 0, + RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK); + + /* Set De-emphasis and soft band range*/ + SET_REG_FIELD(radio->registers[RDCTRL], radio->region_params.emphasis, + RDCTRL_DEEMPHASIS_OFFSET, RDCTRL_DEEMPHASIS_MASK); + + /* set RDS standard */ + SET_REG_FIELD(radio->registers[RDSCTRL], radio->region_params.rds_std, + 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) { + FMDERR("Failed to set RDS/RBDS standard\n"); + return retval; + } + + /* Set the lower and upper band limits*/ + 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; + + xfr_buf[0] = RSH_DATA(band_low, 8); + xfr_buf[1] = GET_ABS_VAL(band_low); + xfr_buf[2] = RSH_DATA(band_high, 8); + xfr_buf[3] = GET_ABS_VAL(band_high); + xfr_buf[4] = 0; /* Active LOW */ + retval = sync_write_xfr(radio, RADIO_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("Could not set regional settings\n"); + return retval; + } + radio->region_params.region = region; + + /* Check for the FM Algorithm used */ + if (radio->enable_optimized_srch_alg) { + FMDBG("Optimized Srch Algorithm!!!"); + optimized_search_algorithm(radio, region); + } else { + FMDBG("Native Srch Algorithm!!!"); + /* Enable/Disable the 200KHz enforcer */ + switch (region) { + case TAVARUA_REGION_US: + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + /*Enable the 200KHz enforcer*/ + retval = tavarua_read_registers(radio, + ADVCTRL, 1); + if (retval >= 0) { + rdsMask = radio->registers[ADVCTRL]; + SET_REG_FIELD(rdsMask, ENF_SRCH200khz, + SRCH200KHZ_OFFSET, SRCH_MASK); + retval = tavarua_write_register(radio, + ADVCTRL, rdsMask); + } else + return retval; + } /* if Marimba do nothing */ + break; + case TAVARUA_REGION_EU: + case TAVARUA_REGION_JAPAN: + case TAVARUA_REGION_JAPAN_WIDE: + default: + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + /* + Disable 200KHz enforcer for all 100/50 KHz + spaced regions. + */ + retval = tavarua_read_registers(radio, + ADVCTRL, 1); + if (retval >= 0) { + rdsMask = radio->registers[ADVCTRL]; + SET_REG_FIELD(rdsMask, NO_SRCH200khz, + SRCH200KHZ_OFFSET, SRCH_MASK); + retval = tavarua_write_register(radio, + ADVCTRL, rdsMask); + } else + return retval; + } /* if Marimba do nothing */ + break; + } + + /* Set channel spacing */ + if (region == TAVARUA_REGION_US) { + if (adie_type_bahma) { + FMDBG("Adie type : Bahama\n"); + /* + Configuring all 200KHZ spaced regions as + 100KHz due to change in the new Bahma + FM SoC search algorithm. + */ + value = FM_CH_SPACE_100KHZ; + } else { + FMDBG("Adie type : Marimba\n"); + value = FM_CH_SPACE_200KHZ; + } + } else { + /* + Set the channel spacing as configured from + the upper layers. + */ + value = radio->region_params.spacing; + } + SET_REG_FIELD(radio->registers[RDCTRL], value, + RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK); + + } + + /* Write the config values into RDCTL register */ + 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; + } + + 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; + + INIT_DELAYED_WORK(&radio->work, read_int_stat); + if (!atomic_dec_and_test(&radio->users)) { + pr_err("%s: Device already in use." + "Try again later", __func__); + atomic_inc(&radio->users); + return -EBUSY; + } + + /* 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; + atomic_inc(&radio->users); + 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) { + pr_err("%s: Radio device not available...", __func__); + return -ENODEV; + } + + FMDBG("In %s", __func__); + + FMDBG("%s, Disabling the 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; + } + + /* disable radio ctrl */ + retval = tavarua_write_register(radio, RDCTRL, 0x00); + if (retval < 0) { + printk(KERN_ERR "%s: failed to disable FM\n", __func__); + return retval; + } + + init_completion(&radio->shutdown_done); + + bahama_present = is_bahama(); + + if (bahama_present == -ENODEV) + return -ENODEV; + + INIT_DELAYED_WORK(&radio->work, fm_shutdown); + + 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__); + goto exit; + } + /* 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__); + goto exit; + } + } 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__); + goto exit; + } +exit: + FMDBG("%s, Calling fm_shutdown\n", __func__); + queue_delayed_work(radio->wqueue, &radio->work, + msecs_to_jiffies(TAVARUA_DELAY/2)); + /* teardown gpio and pmic */ + marimba_set_fm_status(radio->marimba, false); + wait_for_completion(&radio->shutdown_done); + radio->handle_irq = 1; + radio->lp_mode = 1; + atomic_inc(&radio->users); + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + flush_workqueue(radio->wqueue); + return retval; +} + +/* + * 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, + }, + { .id = V4L2_CID_PRIVATE_SET_NOTCH_FILTER, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Notch filter", + .minimum = 0, + .maximum = 2, + }, + +}; + +/*============================================================================= +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; + int cnt = 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]; + msleep(TAVARUA_DELAY*5); + break; + case V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA: + retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n"); + FMDERR("sync_read_xfr [retval=%d]\n", retval); + break; + } + ctrl->value = (unsigned char)xfr_buf[4]; + 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; + case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD: + size = 0x04; + xfr_buf[0] = (XFR_PEEK_MODE | (size << 1)); + xfr_buf[1] = ON_CHANNEL_TH_MSB; + xfr_buf[2] = ON_CHANNEL_TH_LSB; + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3); + if (retval < 0) { + pr_err("%s: Failed to write\n", __func__); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + retval = tavarua_read_registers(radio, XFRDAT0, 4); + if (retval < 0) { + pr_err("%s: On Ch. DET: Read failure\n", __func__); + return retval; + } + for (cnt = 0; cnt < 4; cnt++) + FMDBG("On-Channel data set is : 0x%x\t", + (int)radio->registers[XFRDAT0+cnt]); + + ctrl->value = LSH_DATA(radio->registers[XFRDAT0], 24) | + LSH_DATA(radio->registers[XFRDAT0+1], 16) | + LSH_DATA(radio->registers[XFRDAT0+2], 8) | + (radio->registers[XFRDAT0+3]); + FMDBG("The On Channel Threshold value is : 0x%x", ctrl->value); + break; + case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD: + size = 0x04; + xfr_buf[0] = (XFR_PEEK_MODE | (size << 1)); + xfr_buf[1] = OFF_CHANNEL_TH_MSB; + xfr_buf[2] = OFF_CHANNEL_TH_LSB; + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3); + if (retval < 0) { + pr_err("%s: Failed to write\n", __func__); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + retval = tavarua_read_registers(radio, XFRDAT0, 4); + if (retval < 0) { + pr_err("%s: Off Ch. DET: Read failure\n", __func__); + return retval; + } + for (cnt = 0; cnt < 4; cnt++) + FMDBG("Off-channel data set is : 0x%x\t", + (int)radio->registers[XFRDAT0+cnt]); + + ctrl->value = LSH_DATA(radio->registers[XFRDAT0], 24) | + LSH_DATA(radio->registers[XFRDAT0+1], 16) | + LSH_DATA(radio->registers[XFRDAT0+2], 8) | + (radio->registers[XFRDAT0+3]); + FMDBG("The Off Channel Threshold value is : 0x%x", ctrl->value); + break; + /* + * These IOCTL's are place holders to keep the + * driver compatible with change in frame works for IRIS + */ + case V4L2_CID_PRIVATE_SINR_THRESHOLD: + case V4L2_CID_PRIVATE_SINR_SAMPLES: + case V4L2_CID_PRIVATE_IRIS_GET_SINR: + retval = 0; + 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; + int size = 0, cnt = 0; + unsigned char value; + unsigned char xfr_buf[XFR_REG_NUM]; + unsigned char tx_data[XFR_REG_NUM]; + unsigned char dis_buf[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) { + /* Enabling 'SoftMute' & 'SignalBlending' */ + 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("%s: turning off...\n", __func__); + tavarua_write_register(radio, RDCTRL, ctrl->value); + /* flush the event and work queues */ + kfifo_reset(&radio->data_buf[TAVARUA_BUF_EVENTS]); + flush_workqueue(radio->wqueue); + /* + * queue the READY event from the host side + * in case of FM off + */ + tavarua_q_event(radio, TAVARUA_EVT_RADIO_DISABLED); + + FMDBG("%s, Disable All Interrupts\n", __func__); + /* disable irq */ + dis_buf[STATUS_REG1] = 0x00; + dis_buf[STATUS_REG2] = 0x00; + dis_buf[STATUS_REG3] = TRANSFER; + retval = sync_write_xfr(radio, INT_CTRL, dis_buf); + if (retval < 0) { + pr_err("%s: failed to disable" + "Interrupts\n", __func__); + return retval; + } + } + break; + case V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH: + FMDBG("Setting audio path ...\n"); + if (ctrl->value == FM_DIGITAL_PATH) { + FMDBG("Digital audio path enabled ...\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); + } + } else if (ctrl->value == FM_ANALOG_PATH) { + FMDBG("Analog audio path enabled ...\n"); + retval = tavarua_set_audio_path( + TAVARUA_AUDIO_OUT_DIGITAL_OFF, + TAVARUA_AUDIO_OUT_ANALOG_ON); + if (retval < 0) { + FMDERR("Error in tavarua_set_audio_path" + " %d\n", retval); + } + } + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM: + radio->enable_optimized_srch_alg = ctrl->value; + FMDBG("V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM : %d", + radio->enable_optimized_srch_alg); + 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; + 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_AF_JUMP: + retval = tavarua_read_registers(radio, ADVCTRL, 1); + SET_REG_FIELD(radio->registers[ADVCTRL], ctrl->value, + RDSAF_OFFSET, RDSAF_MASK); + msleep(TAVARUA_DELAY*5); + retval = tavarua_write_register(radio, + ADVCTRL, radio->registers[ADVCTRL]); + msleep(TAVARUA_DELAY*5); + break; + case V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA: + retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n"); + FMDERR("sync_read_xfr [retval=%d]\n", retval); + break; + } + xfr_buf[4] = (unsigned char)ctrl->value; + retval = sync_write_xfr(radio, RX_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n"); + FMDERR("sync_write_xfr [retval=%d]\n", retval); + break; + } + break; + case V4L2_CID_PRIVATE_TAVARUA_HLSI: + retval = tavarua_read_registers(radio, RDCTRL, 1); + SET_REG_FIELD(radio->registers[RDCTRL], ctrl->value, + RDCTRL_HLSI_OFFSET, RDCTRL_HLSI_MASK); + retval = tavarua_write_register(radio, RDCTRL, + radio->registers[RDCTRL]); + 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; + case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD: + size = 0x04; + /* Poking the value of ON Channel Threshold value */ + xfr_buf[0] = (XFR_POKE_MODE | (size << 1)); + xfr_buf[1] = ON_CHANNEL_TH_MSB; + xfr_buf[2] = ON_CHANNEL_TH_LSB; + /* Data to be poked into the register */ + xfr_buf[3] = (ctrl->value & 0xFF000000) >> 24; + xfr_buf[4] = (ctrl->value & 0x00FF0000) >> 16; + xfr_buf[5] = (ctrl->value & 0x0000FF00) >> 8; + xfr_buf[6] = (ctrl->value & 0x000000FF); + + for (cnt = 3; cnt < 7; cnt++) { + FMDBG("On-channel data to be poked is : %d", + (int)xfr_buf[cnt]); + } + + retval = tavarua_write_registers(radio, XFRCTRL, + xfr_buf, size+3); + if (retval < 0) { + pr_err("%s: Failed to write\n", __func__); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + break; + case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD: + size = 0x04; + /* Poking the value of OFF Channel Threshold value */ + xfr_buf[0] = (XFR_POKE_MODE | (size << 1)); + xfr_buf[1] = OFF_CHANNEL_TH_MSB; + xfr_buf[2] = OFF_CHANNEL_TH_LSB; + /* Data to be poked into the register */ + xfr_buf[3] = (ctrl->value & 0xFF000000) >> 24; + xfr_buf[4] = (ctrl->value & 0x00FF0000) >> 16; + xfr_buf[5] = (ctrl->value & 0x0000FF00) >> 8; + xfr_buf[6] = (ctrl->value & 0x000000FF); + + for (cnt = 3; cnt < 7; cnt++) { + FMDBG("Off-channel data to be poked is : %d", + (int)xfr_buf[cnt]); + } + + retval = tavarua_write_registers(radio, XFRCTRL, + xfr_buf, size+3); + if (retval < 0) { + pr_err("%s: Failed to write\n", __func__); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + 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; + /*These IOCTL's are place holders to keep the + driver compatible with change in frame works for IRIS */ + case V4L2_CID_PRIVATE_SOFT_MUTE: + case V4L2_CID_PRIVATE_RIVA_ACCS_ADDR: + case V4L2_CID_PRIVATE_RIVA_ACCS_LEN: + case V4L2_CID_PRIVATE_RIVA_PEEK: + case V4L2_CID_PRIVATE_RIVA_POKE: + case V4L2_CID_PRIVATE_SSBI_ACCS_ADDR: + case V4L2_CID_PRIVATE_SSBI_PEEK: + case V4L2_CID_PRIVATE_SSBI_POKE: + case V4L2_CID_PRIVATE_TX_TONE: + case V4L2_CID_PRIVATE_RDS_GRP_COUNTERS: + case V4L2_CID_PRIVATE_SET_NOTCH_FILTER: + case V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION: + case V4L2_CID_PRIVATE_SINR_THRESHOLD: + case V4L2_CID_PRIVATE_SINR_SAMPLES: + retval = 0; + 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 = -1; + unsigned char buf_fifo[STD_BUF_SIZE] = {0}; + struct kfifo *data_fifo = NULL; + unsigned char *buf = NULL; + unsigned int len = 0, retval = -1; + + if ((radio == NULL) || (buffer == NULL)) { + FMDERR("radio/buffer is NULL\n"); + return -ENXIO; + } + buf_type = buffer->index; + buf = (unsigned char *)buffer->m.userptr; + len = buffer->length; + FMDBG("%s: requesting buffer %d\n", __func__, buf_type); + + 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; + } + if (len <= STD_BUF_SIZE) { + buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0], + len, &radio->buf_lock[buf_type]); + } else { + FMDERR("kfifo_out_locked can not use len more than 128\n"); + return -EINVAL; + } + retval = copy_to_user(buf, &buf_fifo[0], buffer->bytesused); + if (retval > 0) { + FMDERR("Failed to copy %d bytes of data\n", retval); + return -EAGAIN; + } + + return retval; +} + +/*============================================================================= +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) { + users = atomic_read(&radio->users); + 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) { + users = atomic_read(&radio->users); + + 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; + int retval = 0; + if (!radio) + return -ENOMEM; + /* RX */ + FMDBG("%s: digital: %d analog: %d\n", __func__, digital_on, analog_on); + if ((radio->pdata != NULL) && (radio->pdata->config_i2s_gpio != NULL)) { + if (digital_on) { + retval = radio->pdata->config_i2s_gpio(FM_I2S_ON); + if (retval) { + pr_err("%s: config_i2s_gpio failed\n", + __func__); + } + } else { + retval = radio->pdata->config_i2s_gpio(FM_I2S_OFF); + if (retval) { + pr_err("%s: config_i2s_gpio failed\n", + __func__); + } + } + } + + 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 if (i == TAVARUA_BUF_RT_RDS) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE * 2, 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; + } + } + /* initializing the device count */ + atomic_set(&radio->users, 1); + 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*/ + radio->wqueue = create_singlethread_workqueue("kfmradio"); + if (!radio->wqueue) + return -ENOMEM; + + /* 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); + + destroy_workqueue(radio->wqueue); + + 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/rc/Kconfig b/drivers/media/rc/Kconfig index a3fbb21350e93fbd206da3aacfa38907623fb97c..d2ed9966718a5e65eb1a3cad2dc7f1894bc0abc8 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -24,6 +24,17 @@ config LIRC LIRC daemon handles protocol decoding for IR reception and encoding for IR transmitting (aka "blasting"). +config USER_RC_INPUT + tristate "User Space Input device wrapper for Remote Control" + depends on RC_CORE + + ---help--- + Say Y if you want to report remote control input events + from userspace. + + To compile this driver as a module, choose M here: the module will + be called user-rc-input. + source "drivers/media/rc/keymaps/Kconfig" config IR_NEC_DECODER diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 29f364f88a94d0e8c492829c2a1fd280d1765704..be02eec8477e946ed6478b78993d5ad3a5e54ebf 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_IR_RC5_SZ_DECODER) += ir-rc5-sz-decoder.o obj-$(CONFIG_IR_SANYO_DECODER) += ir-sanyo-decoder.o obj-$(CONFIG_IR_MCE_KBD_DECODER) += ir-mce_kbd-decoder.o obj-$(CONFIG_IR_LIRC_CODEC) += ir-lirc-codec.o +obj-$(CONFIG_USER_RC_INPUT) += user-rc-input.o # stand-alone IR receivers/transmitters obj-$(CONFIG_RC_ATI_REMOTE) += ati_remote.o diff --git a/drivers/media/rc/gpio-ir-recv.c b/drivers/media/rc/gpio-ir-recv.c index 0d875450c5ce3d92f1295445b852f77617ed5364..3416c9183b537e89830f8fb87d4aad00cd5d0e50 100644 --- a/drivers/media/rc/gpio-ir-recv.c +++ b/drivers/media/rc/gpio-ir-recv.c @@ -26,14 +26,14 @@ struct gpio_rc_dev { struct rc_dev *rcdev; - int gpio_nr; + unsigned int gpio_nr; bool active_low; }; static irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id) { struct gpio_rc_dev *gpio_dev = dev_id; - int gval; + unsigned int gval; int rc = 0; enum raw_event_type type = IR_SPACE; @@ -87,7 +87,7 @@ static int __devinit gpio_ir_recv_probe(struct platform_device *pdev) rcdev->input_name = GPIO_IR_DEVICE_NAME; rcdev->input_id.bustype = BUS_HOST; rcdev->driver_name = GPIO_IR_DRIVER_NAME; - rcdev->map_name = RC_MAP_EMPTY; + rcdev->map_name = RC_MAP_SAMSUNG_NECX; gpio_dev->rcdev = rcdev; gpio_dev->gpio_nr = pdata->gpio_nr; @@ -115,6 +115,8 @@ static int __devinit gpio_ir_recv_probe(struct platform_device *pdev) if (rc < 0) goto err_request_irq; + device_init_wakeup(&pdev->dev, pdata->can_wakeup); + return 0; err_request_irq: diff --git a/drivers/media/rc/keymaps/Makefile b/drivers/media/rc/keymaps/Makefile index 49ce2662f56bb840f3a74997be830f3abbb33d9e..613e62cda26ae3a7a874a7df18c4bfbe86db91f7 100644 --- a/drivers/media/rc/keymaps/Makefile +++ b/drivers/media/rc/keymaps/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-norwood.o \ rc-npgtech.o \ rc-pctv-sedna.o \ + rc-philips.o \ rc-pinnacle-color.o \ rc-pinnacle-grey.o \ rc-pinnacle-pctv-hd.o \ @@ -76,6 +77,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-rc6-mce.o \ rc-real-audio-220-32-keys.o \ rc-snapstream-firefly.o \ + rc-samsung-necx.o\ rc-streamzap.o \ rc-tbs-nec.o \ rc-technisat-usb2.o \ @@ -88,6 +90,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-trekstor.o \ rc-tt-1500.o \ rc-twinhan1027.o \ + rc-ue-rf4ce.o \ rc-videomate-m1f.o \ rc-videomate-s350.o \ rc-videomate-tv-pvr.o \ diff --git a/drivers/media/rc/keymaps/rc-philips.c b/drivers/media/rc/keymaps/rc-philips.c new file mode 100644 index 0000000000000000000000000000000000000000..9f63520f3b7e17cc67cf47dc868a4e42dfdf5848 --- /dev/null +++ b/drivers/media/rc/keymaps/rc-philips.c @@ -0,0 +1,82 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +static struct rc_map_table philips[] = { + + { 0x00, KEY_NUMERIC_0 }, + { 0x01, KEY_NUMERIC_1 }, + { 0x02, KEY_NUMERIC_2 }, + { 0x03, KEY_NUMERIC_3 }, + { 0x04, KEY_NUMERIC_4 }, + { 0x05, KEY_NUMERIC_5 }, + { 0x06, KEY_NUMERIC_6 }, + { 0x07, KEY_NUMERIC_7 }, + { 0x08, KEY_NUMERIC_8 }, + { 0x09, KEY_NUMERIC_9 }, + { 0xF4, KEY_SOUND }, + { 0xF3, KEY_SCREEN }, /* Picture */ + + { 0x10, KEY_VOLUMEUP }, + { 0x11, KEY_VOLUMEDOWN }, + { 0x0d, KEY_MUTE }, + { 0x20, KEY_CHANNELUP }, + { 0x21, KEY_CHANNELDOWN }, + { 0x0A, KEY_BACK }, + { 0x0f, KEY_INFO }, + { 0x5c, KEY_OK }, + { 0x58, KEY_UP }, + { 0x59, KEY_DOWN }, + { 0x5a, KEY_LEFT }, + { 0x5b, KEY_RIGHT }, + { 0xcc, KEY_PAUSE }, + { 0x6d, KEY_PVR }, /* Demo */ + { 0x40, KEY_EXIT }, + { 0x6e, KEY_PROG1 }, /* Scenea */ + { 0x6f, KEY_MODE }, /* Dual */ + { 0x70, KEY_SLEEP }, + { 0xf5, KEY_TUNER }, /* Format */ + + { 0x4f, KEY_TV }, + { 0x3c, KEY_NEW }, /* USB */ + { 0x38, KEY_COMPOSE }, /* Source */ + { 0x54, KEY_MENU }, + + { 0x0C, KEY_POWER }, +}; + +static struct rc_map_list rc6_philips_map = { + .map = { + .scan = philips, + .size = ARRAY_SIZE(philips), + .rc_type = RC_TYPE_RC6, + .name = RC_MAP_RC6_PHILIPS, + } +}; + +static int __init init_rc_map_rc6_philips(void) +{ + return rc_map_register(&rc6_philips_map); +} + +static void __exit exit_rc_map_rc6_philips(void) +{ + rc_map_unregister(&rc6_philips_map); +} + +module_init(init_rc_map_rc6_philips) +module_exit(exit_rc_map_rc6_philips) + +MODULE_DESCRIPTION("Philips Remote Keymap "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/rc/keymaps/rc-samsung-necx.c b/drivers/media/rc/keymaps/rc-samsung-necx.c new file mode 100644 index 0000000000000000000000000000000000000000..6cf837fd3010b3c6111960ed22664f3934a2d011 --- /dev/null +++ b/drivers/media/rc/keymaps/rc-samsung-necx.c @@ -0,0 +1,82 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include + +static struct rc_map_table samsung_necx[] = { + { 0x70702, KEY_POWER}, /* power */ + { 0x7070f, KEY_MUTE}, /* mute */ + { 0x70704, KEY_1}, + { 0x70705, KEY_2}, + { 0x70706, KEY_3}, + { 0x70708, KEY_4}, + { 0x70709, KEY_5}, + { 0x7070a, KEY_6}, + { 0x7070c, KEY_7}, + { 0x7070d, KEY_8}, + { 0x7070e, KEY_9}, + { 0x70711, KEY_0}, + { 0x70712, KEY_CHANNELUP}, + { 0x70710, KEY_CHANNELDOWN}, + { 0x70707, KEY_VOLUMEUP}, + { 0x7070b, KEY_VOLUMEDOWN}, + { 0x70760, KEY_UP}, + { 0x70768, KEY_ENTER}, /* ok */ + { 0x70761, KEY_DOWN}, + { 0x70765, KEY_LEFT}, + { 0x70762, KEY_RIGHT}, + { 0x7072d, KEY_EXIT}, + { 0x70749, KEY_RECORD}, + { 0x70747, KEY_PLAY}, + { 0x70746, KEY_STOP}, + { 0x70745, KEY_REWIND}, + { 0x70748, KEY_FORWARD}, + { 0x7074a, KEY_PAUSE}, + { 0x70703, KEY_SLEEP}, + { 0x7076c, KEY_A}, /* search */ + { 0x70714, KEY_B}, /* camera */ + { 0x70715, KEY_C}, + { 0x70716, KEY_D}, + { 0x70758, KEY_BACK}, + { 0x7071a, KEY_MENU}, + { 0x7076b, KEY_LIST}, + { 0x70701, KEY_SCREENLOCK}, + { 0x7071f, KEY_HOME}, + +}; + +static struct rc_map_list samsung_necx_map = { + .map = { + .scan = samsung_necx, + .size = ARRAY_SIZE(samsung_necx), + .rc_type = RC_TYPE_NEC, + .name = RC_MAP_SAMSUNG_NECX, + } +}; + +static int __init init_rc_map_samsung_necx(void) +{ + return rc_map_register(&samsung_necx_map); +} + +static void __exit exit_rc_map_samsung_necx(void) +{ + rc_map_unregister(&samsung_necx_map); +} + +module_init(init_rc_map_samsung_necx) +module_exit(exit_rc_map_samsung_necx) + +MODULE_DESCRIPTION("SAMSUNG IR Remote Keymap"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/rc/keymaps/rc-ue-rf4ce.c b/drivers/media/rc/keymaps/rc-ue-rf4ce.c new file mode 100644 index 0000000000000000000000000000000000000000..71c5505eb2cc0f78d6da2e5767ec68a8c979f374 --- /dev/null +++ b/drivers/media/rc/keymaps/rc-ue-rf4ce.c @@ -0,0 +1,83 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +static struct rc_map_table ue_rf4ce[] = { + { 0x0a, KEY_SETUP }, + { 0x6b, KEY_POWER }, + { 0x00, KEY_OK }, + { 0x03, KEY_LEFT }, + { 0x04, KEY_RIGHT }, + { 0x01, KEY_UP }, + { 0x02, KEY_DOWN }, + { 0x53, KEY_HOMEPAGE }, + { 0x0d, KEY_EXIT }, + { 0x72, KEY_TV }, + { 0x73, KEY_VIDEO }, + { 0x74, KEY_COMPOSE }, + { 0x71, KEY_AUX }, + { 0x45, KEY_STOP }, + { 0x0b, KEY_LIST }, + { 0x47, KEY_RECORD }, + { 0x48, KEY_REWIND }, + { 0x44, KEY_PLAY }, + { 0x49, KEY_FASTFORWARD }, + { 0x4c, KEY_BACK }, + { 0x46, KEY_PAUSE }, + { 0x4b, KEY_NEXT }, + { 0x41, KEY_VOLUMEUP }, + { 0x42, KEY_VOLUMEDOWN }, + { 0x32, KEY_LAST }, + { 0x43, KEY_MUTE }, + { 0x30, KEY_CHANNELUP }, + { 0x31, KEY_CHANNELDOWN }, + + { 0x20, KEY_NUMERIC_0 }, + { 0x21, KEY_NUMERIC_1 }, + { 0x22, KEY_NUMERIC_2 }, + { 0x23, KEY_NUMERIC_3 }, + { 0x24, KEY_NUMERIC_4 }, + { 0x25, KEY_NUMERIC_5 }, + { 0x26, KEY_NUMERIC_6 }, + { 0x27, KEY_NUMERIC_7 }, + { 0x28, KEY_NUMERIC_8 }, + { 0x29, KEY_NUMERIC_9 }, + { 0x34, KEY_INSERT }, + { 0x2b, KEY_ENTER }, +}; + +static struct rc_map_list ue_rf4ce_map = { + .map = { + .scan = ue_rf4ce, + .size = ARRAY_SIZE(ue_rf4ce), + .rc_type = RC_TYPE_OTHER, + .name = RC_MAP_UE_RF4CE, + } +}; + +static int __init init_rc_map_ue_rf4ce(void) +{ + return rc_map_register(&ue_rf4ce_map); +} + +static void __exit exit_rc_map_ue_rf4ce(void) +{ + rc_map_unregister(&ue_rf4ce_map); +} + +module_init(init_rc_map_ue_rf4ce) +module_exit(exit_rc_map_ue_rf4ce) + +MODULE_DESCRIPTION("UE RF4CE Remote Keymap "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/rc/user-rc-input.c b/drivers/media/rc/user-rc-input.c new file mode 100644 index 0000000000000000000000000000000000000000..f1a93347563b788c89541fd2c834b1c6d75d98fc --- /dev/null +++ b/drivers/media/rc/user-rc-input.c @@ -0,0 +1,251 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 MAX_RC_DEVICES 1 +#define USER_RC_INPUT_DEV_NAME "user-rc-input" +#define USER_RC_INPUT_DRV_NAME "rc-user-input" + +struct user_rc_input_dev { + struct cdev rc_input_cdev; + struct class *rc_input_class; + struct device *rc_input_dev; + struct rc_dev *rcdev; + dev_t rc_input_base_dev; + struct device *dev; + int in_use; +}; + +static int user_rc_input_open(struct inode *inode, struct file *file) +{ + struct cdev *input_cdev = inode->i_cdev; + struct user_rc_input_dev *input_dev = + container_of(input_cdev, struct user_rc_input_dev, rc_input_cdev); + + if (input_dev->in_use) { + dev_err(input_dev->dev, + "Device is already open..only one instance is allowed\n"); + return -EBUSY; + } + input_dev->in_use++; + file->private_data = input_dev; + + return 0; +} + +static int user_rc_input_release(struct inode *inode, struct file *file) +{ + struct user_rc_input_dev *input_dev = file->private_data; + + input_dev->in_use--; + + return 0; +} + +static ssize_t user_rc_input_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + int ret; + struct user_rc_input_dev *input_dev = file->private_data; + __u8 *buf; + + buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); + if (!buf) { + dev_err(input_dev->dev, + "kmalloc failed...Insufficient memory\n"); + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(buf, buffer, count)) { + dev_err(input_dev->dev, "Copy from user failed\n"); + ret = -EFAULT; + goto out_free; + } + + switch (buf[0]) { + case USER_CONTROL_PRESSED: + dev_dbg(input_dev->dev, "user controlled" + " pressed 0x%x\n", buf[1]); + rc_keydown(input_dev->rcdev, buf[1], 0); + break; + case USER_CONTROL_REPEATED: + dev_dbg(input_dev->dev, "user controlled" + " repeated 0x%x\n", buf[1]); + rc_repeat(input_dev->rcdev); + break; + case USER_CONTROL_RELEASED: + dev_dbg(input_dev->dev, "user controlled" + " released 0x%x\n", buf[1]); + rc_keyup(input_dev->rcdev); + break; + } + +out_free: + kfree(buf); +out: + return ret; +} + +const struct file_operations fops = { + .owner = THIS_MODULE, + .open = user_rc_input_open, + .write = user_rc_input_write, + .release = user_rc_input_release, +}; + +static int __devinit user_rc_input_probe(struct platform_device *pdev) +{ + struct user_rc_input_dev *user_rc_dev; + struct rc_dev *rcdev; + int retval; + + user_rc_dev = kzalloc(sizeof(struct user_rc_input_dev), GFP_KERNEL); + if (!user_rc_dev) + return -ENOMEM; + + user_rc_dev->rc_input_class = class_create(THIS_MODULE, + "user-rc-input-loopback"); + + if (IS_ERR(user_rc_dev->rc_input_class)) { + retval = PTR_ERR(user_rc_dev->rc_input_class); + goto err; + } + + retval = alloc_chrdev_region(&user_rc_dev->rc_input_base_dev, 0, + MAX_RC_DEVICES, USER_RC_INPUT_DEV_NAME); + + if (retval) { + dev_err(&pdev->dev, + "alloc_chrdev_region failed\n"); + goto alloc_chrdev_err; + } + + dev_info(&pdev->dev, "User space report key event input " + "loopback driver registered, " + "major %d\n", MAJOR(user_rc_dev->rc_input_base_dev)); + + cdev_init(&user_rc_dev->rc_input_cdev, &fops); + retval = cdev_add(&user_rc_dev->rc_input_cdev, + user_rc_dev->rc_input_base_dev, + MAX_RC_DEVICES); + if (retval) { + dev_err(&pdev->dev, "cdev_add failed\n"); + goto cdev_add_err; + } + user_rc_dev->rc_input_dev = + device_create(user_rc_dev->rc_input_class, + NULL, + MKDEV(MAJOR(user_rc_dev->rc_input_base_dev), + 0), NULL, "user-rc-input-dev%d", 0); + + if (IS_ERR(user_rc_dev->rc_input_dev)) { + retval = PTR_ERR(user_rc_dev->rc_input_dev); + dev_err(&pdev->dev, "device_create failed\n"); + goto device_create_err; + } + + rcdev = rc_allocate_device(); + if (!rcdev) { + dev_err(&pdev->dev, "failed to allocate rc device"); + retval = -ENOMEM; + goto err_allocate_device; + } + + rcdev->driver_type = RC_DRIVER_SCANCODE; + rcdev->allowed_protos = RC_TYPE_OTHER; + rcdev->input_name = USER_RC_INPUT_DEV_NAME; + rcdev->input_id.bustype = BUS_HOST; + rcdev->driver_name = USER_RC_INPUT_DRV_NAME; + rcdev->map_name = RC_MAP_UE_RF4CE; + + retval = rc_register_device(rcdev); + if (retval < 0) { + dev_err(&pdev->dev, "failed to register rc device\n"); + goto rc_register_err; + } + user_rc_dev->rcdev = rcdev; + user_rc_dev->dev = &pdev->dev; + platform_set_drvdata(pdev, user_rc_dev); + user_rc_dev->in_use = 0; + + return 0; + +rc_register_err: + rc_free_device(rcdev); +err_allocate_device: + device_destroy(user_rc_dev->rc_input_class, + MKDEV(MAJOR(user_rc_dev->rc_input_base_dev), 0)); +cdev_add_err: + unregister_chrdev_region(user_rc_dev->rc_input_base_dev, + MAX_RC_DEVICES); +device_create_err: + cdev_del(&user_rc_dev->rc_input_cdev); +alloc_chrdev_err: + class_destroy(user_rc_dev->rc_input_class); +err: + kfree(user_rc_dev); + return retval; +} + +static int __devexit user_rc_input_remove(struct platform_device *pdev) +{ + struct user_rc_input_dev *user_rc_dev = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + rc_free_device(user_rc_dev->rcdev); + device_destroy(user_rc_dev->rc_input_class, + MKDEV(MAJOR(user_rc_dev->rc_input_base_dev), 0)); + unregister_chrdev_region(user_rc_dev->rc_input_base_dev, + MAX_RC_DEVICES); + cdev_del(&user_rc_dev->rc_input_cdev); + class_destroy(user_rc_dev->rc_input_class); + kfree(user_rc_dev); + + return 0; +} + +static struct platform_driver user_rc_input_driver = { + .probe = user_rc_input_probe, + .remove = __devexit_p(user_rc_input_remove), + .driver = { + .name = USER_RC_INPUT_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init user_rc_input_init(void) +{ + return platform_driver_register(&user_rc_input_driver); +} +module_init(user_rc_input_init); + +static void __exit user_rc_input_exit(void) +{ + platform_driver_unregister(&user_rc_input_driver); +} +module_exit(user_rc_input_exit); + +MODULE_DESCRIPTION("User RC Input driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index ce1e7ba940f6c75ac2818b799010deee37553c48..ae020e844cbbd693c9a8c03193112e9071b56558 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -66,6 +66,12 @@ config VIDEOBUF2_DMA_SG select VIDEOBUF2_CORE select VIDEOBUF2_MEMOPS tristate + +config VIDEOBUF2_MSM_MEM + tristate "MSM videobuf2 extensions" + select VIDEOBUF2_DMA_SG + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC # # Multimedia Video device configuration # @@ -633,6 +639,14 @@ config VIDEO_VIVI Say Y here if you want to test video apps or debug V4L devices. In doubt, say N. +config MSM_VCAP + tristate "Qualcomm MSM VCAP" + depends on VIDEO_DEV && VIDEO_V4L2 + default y + ---help--- + Enables VCAP driver. This device allows for video capture and + video processing using the v4l2 api + # # USB Multimedia device configuration # @@ -1160,6 +1174,30 @@ config VIDEO_S5P_MIPI_CSIS source "drivers/media/video/s5p-tv/Kconfig" +# +# 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 # V4L_PLATFORM_DRIVERS endif # VIDEO_CAPTURE_DRIVERS @@ -1222,3 +1260,5 @@ config VIDEO_MX2_EMMAPRP conversion. endif # V4L_MEM2MEM_DRIVERS + +source "drivers/media/video/msm_vidc/Kconfig" diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index a6282a3a6a822037104ea0653df38e58897d0faf..956a0431e61c8d940173d74bc07db0755e790d7d 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -12,6 +12,7 @@ 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 + ifeq ($(CONFIG_COMPAT),y) videodev-objs += v4l2-compat-ioctl32.o endif @@ -134,6 +135,7 @@ obj-$(CONFIG_VIDEOBUF2_MEMOPS) += videobuf2-memops.o obj-$(CONFIG_VIDEOBUF2_VMALLOC) += videobuf2-vmalloc.o obj-$(CONFIG_VIDEOBUF2_DMA_CONTIG) += videobuf2-dma-contig.o obj-$(CONFIG_VIDEOBUF2_DMA_SG) += videobuf2-dma-sg.o +obj-$(CONFIG_VIDEOBUF2_MSM_MEM) += videobuf2-msm-mem.o obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o @@ -168,6 +170,9 @@ obj-$(CONFIG_VIDEO_VIVI) += vivi.o obj-$(CONFIG_VIDEO_MEM2MEM_TESTDEV) += mem2mem_testdev.o obj-$(CONFIG_VIDEO_CX23885) += cx23885/ +obj-$(CONFIG_MSM_VCAP) += vcap_v4l2.o +obj-$(CONFIG_MSM_VCAP) += vcap_vc.o +obj-$(CONFIG_MSM_VCAP) += vcap_vp.o obj-$(CONFIG_VIDEO_AK881X) += ak881x.o obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o @@ -207,7 +212,9 @@ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-y += davinci/ +obj-$(CONFIG_MSM_CAMERA) += msm/ obj-$(CONFIG_ARCH_OMAP) += omap/ +obj-$(CONFIG_MSM_VIDC) += msm_vidc/ ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core ccflags-y += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/video/msm/Kconfig b/drivers/media/video/msm/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..ab4a6f2fd1021ca47cfa2cf6d94e27374ab26ab5 --- /dev/null +++ b/drivers/media/video/msm/Kconfig @@ -0,0 +1,288 @@ +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 + ---help--- + SONY 13.5 MP Bayer Sensor +config OV5640 + bool "Sensor OV5640 (YUV 5M)" + depends on MSM_CAMERA && !MSM_CAMERA_V4L2 + default n + ---help--- + Omni 5M YUV Sensor + +config OV5647 + bool "Sensor ov5647 (BAYER 5M)" + depends on MSM_CAMERA + ---help--- + OV 5M Bayer Sensor with AutoFocus + +config AD5046_ACT + bool "Lens actuator ad5046" + depends on MSM_CAMERA && OV5647 + ---help--- + ad5046 lens actuator driver for ov5647. + Say Y here if this is msm7627A variant platform. +config WEBCAM_OV7692_QRD + bool "Sensor OV7692 QRD(VGA YUV)" + depends on MSM_CAMERA && (ARCH_MSM7X27A || ARCH_MSM8X60) + default n + ---help--- + Omni Vision VGA YUV Sensor for QRD Devices +config MT9M114 + bool "Sensor MT9M114 (YUV 1.26M)" + depends on MSM_CAMERA + ---help--- + APTINA 1.26 MP yuv 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) + 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 || ARCH_MSM8X60) && !MSM_CAMERA_V4L2 + 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) + default n + ---help--- + Aptina 8M Bayer Sensor modules with Autofocus + +config IMX074_ACT + bool "Actuator IMX074 (BAYER 13.5M)" + depends on MSM_CAMERA + ---help--- + Actuator for SONY 13.5 MP Bayer Sensor + +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 DW9712_ACT + bool "Lens actuator dw9721" + depends on MSM_CAMERA && S5K4E1 + ---help--- + dw9721 lens actuator driver for S5K4E1. + Say Y here if this is msm7627A 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 MSM_CAMERA_FLASH_TPS61310 + bool "Qualcomm MSM camera tps61310 flash support" + depends on MSM_CAMERA + default n + ---help--- + Enable support for LED flash for msm camera. + It is a Texas Instruments multiple LED Flash + for camera flash and video light applications. + +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 + +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 + +config MSM_ACTUATOR + bool "Qualcomm MSM actuator support" + depends on MSM_CAMERA + +config MSM_EEPROM + bool "Qualcomm MSM EEPROM support" + depends on MSM_CAMERA + +config IMX074_EEPROM + bool "IMX074 EEPROM support" + depends on MSM_CAMERA + +config IMX091_EEPROM + bool "IMX091 EEPROM support" + depends on MSM_CAMERA + +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. + +config S5K3L1YX + bool "Sensor S5K3L1YX (BAYER 12M)" + depends on MSM_CAMERA + ---help--- + Samsung 12 MP Bayer Sensor with auto focus, uses + 4 mipi lanes, preview config = 1984 * 1508 at 30 fps, + snapshot config = 4000 * 3000 at 20 fps, + hfr video at 60, 90 and 120 fps. + +config IMX091 + bool "Sensor imx091 (Sony 13MP)" + depends on MSM_CAMERA + ---help--- + Sony 13MP sensor back camera that uses 4 mipi lanes, + runs at 30 fps preview and 14 fps snapshot + +config MSM_V4L2_VIDEO_OVERLAY_DEVICE + tristate "Qualcomm MSM V4l2 video overlay device" + ---help--- + Enables support for the MSM V4L2 video + overlay driver. This allows video rendering + apps to render overlaid video using Video4Linux2 + APIs, by using /dev/videoX device + +config OV7692 + bool "Sensor OV7692 (VGA YUV)" + depends on MSM_CAMERA + ---help--- + Omni Vision VGA YUV Sensor diff --git a/drivers/media/video/msm/Makefile b/drivers/media/video/msm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b60f99f4ec633b63770922a4449288fdf678aa7e --- /dev/null +++ b/drivers/media/video/msm/Makefile @@ -0,0 +1,63 @@ +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 + +EXTRA_CFLAGS += -Idrivers/media/video/msm/io +obj-$(CONFIG_MSM_CAMERA) += io/ +ifeq ($(CONFIG_MSM_CAMERA_V4L2),y) + EXTRA_CFLAGS += -Idrivers/media/video/msm/csi + EXTRA_CFLAGS += -Idrivers/media/video/msm/io + EXTRA_CFLAGS += -Idrivers/media/video/msm/eeprom + EXTRA_CFLAGS += -Idrivers/media/video/msm/sensors + EXTRA_CFLAGS += -Idrivers/media/video/msm/actuators + obj-$(CONFIG_MSM_CAMERA) += msm_isp.o msm.o msm_mem.o msm_mctl.o msm_mctl_buf.o msm_mctl_pp.o + obj-$(CONFIG_MSM_CAMERA) += io/ eeprom/ sensors/ actuators/ csi/ + obj-$(CONFIG_MSM_CAMERA) += msm_gesture.o +else + obj-$(CONFIG_MSM_CAMERA) += msm_camera.o +endif +obj-$(CONFIG_MSM_CAMERA) += msm_axi_qos.o gemini/ +obj-$(CONFIG_MSM_CAMERA_FLASH) += flash.o +obj-$(CONFIG_ARCH_MSM_ARM11) += msm_vfe7x.o +ifeq ($(CONFIG_MSM_CAMERA_V4L2),y) + obj-$(CONFIG_ARCH_MSM7X27A) += msm_vfe7x27a_v4l2.o +else + obj-$(CONFIG_ARCH_MSM7X27A) += msm_vfe7x27a.o +endif +obj-$(CONFIG_ARCH_QSD8X50) += msm_vfe8x.o msm_vfe8x_proc.o +ifeq ($(CONFIG_MSM_CAMERA_V4L2),y) + obj-$(CONFIG_ARCH_MSM8X60) += msm_vfe31_v4l2.o msm_vpe.o + obj-$(CONFIG_ARCH_MSM7X30) += msm_vfe31_v4l2.o msm_vpe.o msm_axi_qos.o +else + obj-$(CONFIG_ARCH_MSM8X60) += msm_vfe31.o msm_vpe1.o + obj-$(CONFIG_ARCH_MSM7X30) += msm_vfe31.o msm_vpe1.o +endif +obj-$(CONFIG_ARCH_MSM8960) += msm_vfe32.o msm_vpe.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_S5K3E2FX) += s5k3e2fx.o +#FIXME: Merge the two ifeq causes VX6953 preview not coming up. +ifneq ($(CONFIG_MSM_CAMERA_V4L2),y) + obj-$(CONFIG_VX6953) += vx6953.o vx6953_reg.o + obj-$(CONFIG_IMX074) += imx074.o imx074_reg.o + obj-$(CONFIG_MT9E013) += mt9e013.o mt9e013_reg.o + obj-$(CONFIG_WEBCAM_OV9726) += ov9726.o ov9726_reg.o + obj-$(CONFIG_OV5647) += ov5647.o ov5647_reg.o + obj-$(CONFIG_S5K4E1) += s5k4e1.o s5k4e1_reg.o + obj-$(CONFIG_WEBCAM_OV7692) += ov7692.o + obj-$(CONFIG_WEBCAM_OV7692_QRD) += ov7692_qrd.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_OV5640) += ov5640.o +obj-$(CONFIG_MT9D112) += mt9d112.o mt9d112_reg.o + +obj-$(CONFIG_MT9D113) += mt9d113.o mt9d113_reg.o +obj-$(CONFIG_FB_MSM_WRITEBACK_MSM_PANEL) += wfd/ +obj-$(CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE) += msm_v4l2_video.o diff --git a/drivers/media/video/msm/actuators/Makefile b/drivers/media/video/msm/actuators/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..70a3a1911392b15145f89057452b82f31c10a6e2 --- /dev/null +++ b/drivers/media/video/msm/actuators/Makefile @@ -0,0 +1,4 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) +EXTRA_CFLAGS += -Idrivers/media/video/msm +EXTRA_CFLAGS += -Idrivers/media/video/msm/io +obj-$(CONFIG_MSM_ACTUATOR) += msm_actuator.o diff --git a/drivers/media/video/msm/actuators/msm_actuator.c b/drivers/media/video/msm/actuators/msm_actuator.c new file mode 100644 index 0000000000000000000000000000000000000000..50399de6228875c0ae51321f6647b1754fbb2eb9 --- /dev/null +++ b/drivers/media/video/msm/actuators/msm_actuator.c @@ -0,0 +1,649 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_actuator.h" + +static struct msm_actuator_ctrl_t msm_actuator_t; + +static struct msm_actuator msm_vcm_actuator_table = { + .act_type = ACTUATOR_VCM, + .func_tbl = { + .actuator_init_step_table = msm_actuator_init_step_table, + .actuator_move_focus = msm_actuator_move_focus, + .actuator_write_focus = msm_actuator_write_focus, + .actuator_set_default_focus = msm_actuator_set_default_focus, + .actuator_init_focus = msm_actuator_init_focus, + .actuator_i2c_write = msm_actuator_i2c_write, + }, +}; + +static struct msm_actuator msm_piezo_actuator_table = { + .act_type = ACTUATOR_PIEZO, + .func_tbl = { + .actuator_init_step_table = NULL, + .actuator_move_focus = msm_actuator_piezo_move_focus, + .actuator_write_focus = NULL, + .actuator_set_default_focus = + msm_actuator_piezo_set_default_focus, + .actuator_init_focus = msm_actuator_init_focus, + .actuator_i2c_write = msm_actuator_i2c_write, + }, +}; + +static struct msm_actuator *actuators[] = { + &msm_vcm_actuator_table, + &msm_piezo_actuator_table, +}; + +int32_t msm_actuator_piezo_set_default_focus( + struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params) +{ + int32_t rc = 0; + + if (a_ctrl->curr_step_pos != 0) { + rc = a_ctrl->func_tbl->actuator_i2c_write(a_ctrl, + a_ctrl->initial_code, 0); + rc = a_ctrl->func_tbl->actuator_i2c_write(a_ctrl, + a_ctrl->initial_code, 0); + a_ctrl->curr_step_pos = 0; + } + return rc; +} + +int32_t msm_actuator_i2c_write(struct msm_actuator_ctrl_t *a_ctrl, + int16_t next_lens_position, uint32_t hw_params) +{ + struct msm_actuator_reg_params_t *write_arr = a_ctrl->reg_tbl; + uint32_t hw_dword = hw_params; + uint16_t i2c_byte1 = 0, i2c_byte2 = 0; + uint16_t value = 0; + uint32_t size = a_ctrl->reg_tbl_size, i = 0; + int32_t rc = 0; + CDBG("%s: IN\n", __func__); + for (i = 0; i < size; i++) { + if (write_arr[i].reg_write_type == MSM_ACTUATOR_WRITE_DAC) { + value = (next_lens_position << + write_arr[i].data_shift) | + ((hw_dword & write_arr[i].hw_mask) >> + write_arr[i].hw_shift); + + if (write_arr[i].reg_addr != 0xFFFF) { + i2c_byte1 = write_arr[i].reg_addr; + i2c_byte2 = value; + if (size != (i+1)) { + i2c_byte2 = (i2c_byte2 & 0xFF00) >> 8; + CDBG("%s: byte1:0x%x, byte2:0x%x\n", + __func__, i2c_byte1, i2c_byte2); + rc = msm_camera_i2c_write( + &a_ctrl->i2c_client, + i2c_byte1, i2c_byte2, + a_ctrl->i2c_data_type); + if (rc < 0) { + pr_err("%s: i2c write error:%d\n", + __func__, rc); + return rc; + } + + i++; + i2c_byte1 = write_arr[i].reg_addr; + i2c_byte2 = value & 0xFF; + } + } else { + i2c_byte1 = (value & 0xFF00) >> 8; + i2c_byte2 = value & 0xFF; + } + } else { + i2c_byte1 = write_arr[i].reg_addr; + i2c_byte2 = (hw_dword & write_arr[i].hw_mask) >> + write_arr[i].hw_shift; + } + CDBG("%s: i2c_byte1:0x%x, i2c_byte2:0x%x\n", __func__, + i2c_byte1, i2c_byte2); + rc = msm_camera_i2c_write(&a_ctrl->i2c_client, + i2c_byte1, i2c_byte2, a_ctrl->i2c_data_type); + } + CDBG("%s: OUT\n", __func__); + return rc; +} + +int32_t msm_actuator_init_focus(struct msm_actuator_ctrl_t *a_ctrl, + uint16_t size, enum msm_actuator_data_type type, + struct reg_settings_t *settings) +{ + int32_t rc = -EFAULT; + int32_t i = 0; + CDBG("%s called\n", __func__); + + for (i = 0; i < size; i++) { + switch (type) { + case MSM_ACTUATOR_BYTE_DATA: + rc = msm_camera_i2c_write( + &a_ctrl->i2c_client, + settings[i].reg_addr, + settings[i].reg_data, MSM_CAMERA_I2C_BYTE_DATA); + break; + case MSM_ACTUATOR_WORD_DATA: + rc = msm_camera_i2c_write( + &a_ctrl->i2c_client, + settings[i].reg_addr, + settings[i].reg_data, MSM_CAMERA_I2C_WORD_DATA); + break; + default: + pr_err("%s: Unsupport data type: %d\n", + __func__, type); + break; + } + if (rc < 0) + break; + } + + a_ctrl->curr_step_pos = 0; + CDBG("%s Exit:%d\n", __func__, rc); + return rc; +} + +int32_t msm_actuator_write_focus( + struct msm_actuator_ctrl_t *a_ctrl, + uint16_t curr_lens_pos, + struct damping_params_t *damping_params, + int8_t sign_direction, + int16_t code_boundary) +{ + int32_t rc = 0; + int16_t next_lens_pos = 0; + uint16_t damping_code_step = 0; + uint16_t wait_time = 0; + + damping_code_step = damping_params->damping_step; + wait_time = damping_params->damping_delay; + + /* Write code based on damping_code_step in a loop */ + for (next_lens_pos = + curr_lens_pos + (sign_direction * damping_code_step); + (sign_direction * next_lens_pos) <= + (sign_direction * code_boundary); + next_lens_pos = + (next_lens_pos + + (sign_direction * damping_code_step))) { + rc = a_ctrl->func_tbl-> + actuator_i2c_write(a_ctrl, next_lens_pos, + damping_params->hw_params); + if (rc < 0) { + pr_err("%s: error:%d\n", + __func__, rc); + return rc; + } + curr_lens_pos = next_lens_pos; + usleep(wait_time); + } + + if (curr_lens_pos != code_boundary) { + rc = a_ctrl->func_tbl-> + actuator_i2c_write(a_ctrl, code_boundary, + damping_params->hw_params); + usleep(wait_time); + } + return rc; +} + +int32_t msm_actuator_piezo_move_focus( + struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params) +{ + int32_t dest_step_position = move_params->dest_step_pos; + int32_t rc = 0; + int32_t num_steps = move_params->num_steps; + + if (num_steps == 0) + return rc; + + rc = a_ctrl->func_tbl-> + actuator_i2c_write(a_ctrl, + (num_steps * + a_ctrl->region_params[0].code_per_step), + move_params->ringing_params[0].hw_params); + + a_ctrl->curr_step_pos = dest_step_position; + return rc; +} + +int32_t msm_actuator_move_focus( + struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params) +{ + int32_t rc = 0; + int8_t sign_dir = move_params->sign_dir; + uint16_t step_boundary = 0; + uint16_t target_step_pos = 0; + uint16_t target_lens_pos = 0; + int16_t dest_step_pos = move_params->dest_step_pos; + uint16_t curr_lens_pos = 0; + int dir = move_params->dir; + int32_t num_steps = move_params->num_steps; + + CDBG("%s called, dir %d, num_steps %d\n", + __func__, + dir, + num_steps); + + if (dest_step_pos == a_ctrl->curr_step_pos) + return rc; + + curr_lens_pos = a_ctrl->step_position_table[a_ctrl->curr_step_pos]; + CDBG("curr_step_pos =%d dest_step_pos =%d curr_lens_pos=%d\n", + a_ctrl->curr_step_pos, dest_step_pos, curr_lens_pos); + + while (a_ctrl->curr_step_pos != dest_step_pos) { + step_boundary = + a_ctrl->region_params[a_ctrl->curr_region_index]. + step_bound[dir]; + if ((dest_step_pos * sign_dir) <= + (step_boundary * sign_dir)) { + + target_step_pos = dest_step_pos; + target_lens_pos = + a_ctrl->step_position_table[target_step_pos]; + if (curr_lens_pos == target_lens_pos) + return rc; + rc = a_ctrl->func_tbl-> + actuator_write_focus( + a_ctrl, + curr_lens_pos, + &(move_params-> + ringing_params[a_ctrl-> + curr_region_index]), + sign_dir, + target_lens_pos); + if (rc < 0) { + pr_err("%s: error:%d\n", + __func__, rc); + return rc; + } + curr_lens_pos = target_lens_pos; + + } else { + target_step_pos = step_boundary; + target_lens_pos = + a_ctrl->step_position_table[target_step_pos]; + if (curr_lens_pos == target_lens_pos) + return rc; + rc = a_ctrl->func_tbl-> + actuator_write_focus( + a_ctrl, + curr_lens_pos, + &(move_params-> + ringing_params[a_ctrl-> + curr_region_index]), + sign_dir, + target_lens_pos); + if (rc < 0) { + pr_err("%s: error:%d\n", + __func__, rc); + return rc; + } + curr_lens_pos = target_lens_pos; + + a_ctrl->curr_region_index += sign_dir; + } + a_ctrl->curr_step_pos = target_step_pos; + } + + return rc; +} + +int32_t msm_actuator_init_step_table(struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_set_info_t *set_info) +{ + int16_t code_per_step = 0; + int32_t rc = 0; + int16_t cur_code = 0; + int16_t step_index = 0, region_index = 0; + uint16_t step_boundary = 0; + uint32_t max_code_size = 1; + uint16_t data_size = set_info->actuator_params.data_size; + CDBG("%s called\n", __func__); + + for (; data_size > 0; data_size--) + max_code_size *= 2; + + kfree(a_ctrl->step_position_table); + a_ctrl->step_position_table = NULL; + + /* Fill step position table */ + a_ctrl->step_position_table = + kmalloc(sizeof(uint16_t) * + (set_info->af_tuning_params.total_steps + 1), GFP_KERNEL); + + if (a_ctrl->step_position_table == NULL) + return -EFAULT; + + cur_code = set_info->af_tuning_params.initial_code; + a_ctrl->step_position_table[step_index++] = cur_code; + for (region_index = 0; + region_index < a_ctrl->region_size; + region_index++) { + code_per_step = + a_ctrl->region_params[region_index].code_per_step; + step_boundary = + a_ctrl->region_params[region_index]. + step_bound[MOVE_NEAR]; + for (; step_index <= step_boundary; + step_index++) { + cur_code += code_per_step; + if (cur_code < max_code_size) + a_ctrl->step_position_table[step_index] = + cur_code; + else { + for (; step_index < + set_info->af_tuning_params.total_steps; + step_index++) + a_ctrl-> + step_position_table[ + step_index] = + max_code_size; + + return rc; + } + } + } + + return rc; +} + +int32_t msm_actuator_set_default_focus( + struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params) +{ + int32_t rc = 0; + CDBG("%s called\n", __func__); + + if (a_ctrl->curr_step_pos != 0) + rc = a_ctrl->func_tbl->actuator_move_focus(a_ctrl, move_params); + return rc; +} + +int32_t msm_actuator_power_down(struct msm_actuator_ctrl_t *a_ctrl) +{ + int32_t rc = 0; + if (a_ctrl->vcm_enable) { + rc = gpio_direction_output(a_ctrl->vcm_pwd, 0); + if (!rc) + gpio_free(a_ctrl->vcm_pwd); + } + + kfree(a_ctrl->step_position_table); + a_ctrl->step_position_table = NULL; + return rc; +} + +int32_t msm_actuator_init(struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_set_info_t *set_info) { + struct reg_settings_t *init_settings = NULL; + int32_t rc = -EFAULT; + uint16_t i = 0; + CDBG("%s: IN\n", __func__); + + for (i = 0; i < ARRAY_SIZE(actuators); i++) { + if (set_info->actuator_params.act_type == + actuators[i]->act_type) { + a_ctrl->func_tbl = &actuators[i]->func_tbl; + rc = 0; + } + } + + if (rc < 0) { + pr_err("%s: Actuator function table not found\n", __func__); + return rc; + } + + a_ctrl->region_size = set_info->af_tuning_params.region_size; + if (a_ctrl->region_size > MAX_ACTUATOR_REGION) { + pr_err("%s: MAX_ACTUATOR_REGION is exceeded.\n", __func__); + return -EFAULT; + } + a_ctrl->total_steps = set_info->af_tuning_params.total_steps; + a_ctrl->pwd_step = set_info->af_tuning_params.pwd_step; + a_ctrl->total_steps = set_info->af_tuning_params.total_steps; + + if (copy_from_user(&a_ctrl->region_params, + (void *)set_info->af_tuning_params.region_params, + a_ctrl->region_size * sizeof(struct region_params_t))) + return -EFAULT; + + a_ctrl->i2c_data_type = set_info->actuator_params.i2c_data_type; + a_ctrl->i2c_client.client->addr = set_info->actuator_params.i2c_addr; + a_ctrl->i2c_client.addr_type = set_info->actuator_params.i2c_addr_type; + a_ctrl->reg_tbl_size = set_info->actuator_params.reg_tbl_size; + if (a_ctrl->reg_tbl_size > MAX_ACTUATOR_REG_TBL_SIZE) { + pr_err("%s: MAX_ACTUATOR_REG_TBL_SIZE is exceeded.\n", + __func__); + return -EFAULT; + } + if (copy_from_user(&a_ctrl->reg_tbl, + (void *)set_info->actuator_params.reg_tbl_params, + a_ctrl->reg_tbl_size * + sizeof(struct msm_actuator_reg_params_t))) + return -EFAULT; + + if (set_info->actuator_params.init_setting_size) { + if (a_ctrl->func_tbl->actuator_init_focus) { + init_settings = kmalloc(sizeof(struct reg_settings_t) * + (set_info->actuator_params.init_setting_size), + GFP_KERNEL); + if (init_settings == NULL) { + pr_err("%s Error allocating memory for init_settings\n", + __func__); + return -EFAULT; + } + if (copy_from_user(init_settings, + (void *)set_info->actuator_params.init_settings, + set_info->actuator_params.init_setting_size * + sizeof(struct reg_settings_t))) { + kfree(init_settings); + pr_err("%s Error copying init_settings\n", + __func__); + return -EFAULT; + } + rc = a_ctrl->func_tbl->actuator_init_focus(a_ctrl, + set_info->actuator_params.init_setting_size, + a_ctrl->i2c_data_type, + init_settings); + kfree(init_settings); + if (rc < 0) { + pr_err("%s Error actuator_init_focus\n", + __func__); + return -EFAULT; + } + } + } + + a_ctrl->initial_code = set_info->af_tuning_params.initial_code; + if (a_ctrl->func_tbl->actuator_init_step_table) + rc = a_ctrl->func_tbl-> + actuator_init_step_table(a_ctrl, set_info); + + a_ctrl->curr_step_pos = 0; + a_ctrl->curr_region_index = 0; + + return rc; +} + + +int32_t msm_actuator_config(struct msm_actuator_ctrl_t *a_ctrl, + void __user *argp) +{ + struct msm_actuator_cfg_data cdata; + int32_t rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct msm_actuator_cfg_data))) + return -EFAULT; + mutex_lock(a_ctrl->actuator_mutex); + CDBG("%s called, type %d\n", __func__, cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_SET_ACTUATOR_INFO: + rc = msm_actuator_init(a_ctrl, &cdata.cfg.set_info); + if (rc < 0) + pr_err("%s init table failed %d\n", __func__, rc); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = a_ctrl->func_tbl->actuator_set_default_focus(a_ctrl, + &cdata.cfg.move); + if (rc < 0) + pr_err("%s move focus failed %d\n", __func__, rc); + break; + + case CFG_MOVE_FOCUS: + rc = a_ctrl->func_tbl->actuator_move_focus(a_ctrl, + &cdata.cfg.move); + if (rc < 0) + pr_err("%s move focus failed %d\n", __func__, rc); + break; + + default: + break; + } + mutex_unlock(a_ctrl->actuator_mutex); + return rc; +} + +int32_t msm_actuator_i2c_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + struct msm_actuator_ctrl_t *act_ctrl_t = NULL; + CDBG("%s called\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("i2c_check_functionality failed\n"); + goto probe_failure; + } + + act_ctrl_t = (struct msm_actuator_ctrl_t *)(id->driver_data); + CDBG("%s client = %x\n", + __func__, (unsigned int) client); + act_ctrl_t->i2c_client.client = client; + + /* Assign name for sub device */ + snprintf(act_ctrl_t->sdev.name, sizeof(act_ctrl_t->sdev.name), + "%s", act_ctrl_t->i2c_driver->driver.name); + + /* Initialize sub device */ + v4l2_i2c_subdev_init(&act_ctrl_t->sdev, + act_ctrl_t->i2c_client.client, + act_ctrl_t->act_v4l2_subdev_ops); + + CDBG("%s succeeded\n", __func__); + return rc; + +probe_failure: + pr_err("%s failed! rc = %d\n", __func__, rc); + return rc; +} + +int32_t msm_actuator_power_up(struct msm_actuator_ctrl_t *a_ctrl) +{ + int rc = 0; + CDBG("%s called\n", __func__); + + CDBG("vcm info: %x %x\n", a_ctrl->vcm_pwd, + a_ctrl->vcm_enable); + if (a_ctrl->vcm_enable) { + rc = gpio_request(a_ctrl->vcm_pwd, "msm_actuator"); + if (!rc) { + CDBG("Enable VCM PWD\n"); + gpio_direction_output(a_ctrl->vcm_pwd, 1); + } + } + return rc; +} + +DEFINE_MUTEX(msm_actuator_mutex); + +static const struct i2c_device_id msm_actuator_i2c_id[] = { + {"msm_actuator", (kernel_ulong_t)&msm_actuator_t}, + { } +}; + +static struct i2c_driver msm_actuator_i2c_driver = { + .id_table = msm_actuator_i2c_id, + .probe = msm_actuator_i2c_probe, + .remove = __exit_p(msm_actuator_i2c_remove), + .driver = { + .name = "msm_actuator", + }, +}; + +static int __init msm_actuator_i2c_add_driver( + void) +{ + CDBG("%s called\n", __func__); + return i2c_add_driver(msm_actuator_t.i2c_driver); +} + +long msm_actuator_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct msm_actuator_ctrl_t *a_ctrl = get_actrl(sd); + void __user *argp = (void __user *)arg; + switch (cmd) { + case VIDIOC_MSM_ACTUATOR_CFG: + return msm_actuator_config(a_ctrl, argp); + default: + return -ENOIOCTLCMD; + } +} + +int32_t msm_actuator_power(struct v4l2_subdev *sd, int on) +{ + int rc = 0; + struct msm_actuator_ctrl_t *a_ctrl = get_actrl(sd); + mutex_lock(a_ctrl->actuator_mutex); + if (on) + rc = msm_actuator_power_up(a_ctrl); + else + rc = msm_actuator_power_down(a_ctrl); + mutex_unlock(a_ctrl->actuator_mutex); + return rc; +} + +struct msm_actuator_ctrl_t *get_actrl(struct v4l2_subdev *sd) +{ + return container_of(sd, struct msm_actuator_ctrl_t, sdev); +} + +static struct v4l2_subdev_core_ops msm_actuator_subdev_core_ops = { + .ioctl = msm_actuator_subdev_ioctl, + .s_power = msm_actuator_power, +}; + +static struct v4l2_subdev_ops msm_actuator_subdev_ops = { + .core = &msm_actuator_subdev_core_ops, +}; + +static struct msm_actuator_ctrl_t msm_actuator_t = { + .i2c_driver = &msm_actuator_i2c_driver, + .act_v4l2_subdev_ops = &msm_actuator_subdev_ops, + + .curr_step_pos = 0, + .curr_region_index = 0, + .actuator_mutex = &msm_actuator_mutex, + +}; + +subsys_initcall(msm_actuator_i2c_add_driver); +MODULE_DESCRIPTION("MSM ACTUATOR"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/actuators/msm_actuator.h b/drivers/media/video/msm/actuators/msm_actuator.h new file mode 100644 index 0000000000000000000000000000000000000000..4f936e7538c03265aab901a1e67db6c5b3953787 --- /dev/null +++ b/drivers/media/video/msm/actuators/msm_actuator.h @@ -0,0 +1,128 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ACTUATOR_H +#define MSM_ACTUATOR_H + +#include +#include +#include +#include +#include +#include "msm_camera_i2c.h" + +#ifdef LERROR +#undef LERROR +#endif + +#ifdef LINFO +#undef LINFO +#endif + +#define LERROR(fmt, args...) pr_err(fmt, ##args) + +#define CONFIG_MSM_CAMERA_ACT_DBG 0 + +#if CONFIG_MSM_CAMERA_ACT_DBG +#define LINFO(fmt, args...) printk(fmt, ##args) +#else +#define LINFO(fmt, args...) CDBG(fmt, ##args) +#endif + +struct msm_actuator_ctrl_t; + +struct msm_actuator_func_tbl { + int32_t (*actuator_i2c_write_b_af)(struct msm_actuator_ctrl_t *, + uint8_t, + uint8_t); + int32_t (*actuator_init_step_table)(struct msm_actuator_ctrl_t *, + struct msm_actuator_set_info_t *); + int32_t (*actuator_init_focus)(struct msm_actuator_ctrl_t *, + uint16_t, enum msm_actuator_data_type, struct reg_settings_t *); + int32_t (*actuator_set_default_focus) (struct msm_actuator_ctrl_t *, + struct msm_actuator_move_params_t *); + int32_t (*actuator_move_focus) (struct msm_actuator_ctrl_t *, + struct msm_actuator_move_params_t *); + int32_t (*actuator_i2c_write)(struct msm_actuator_ctrl_t *, + int16_t, uint32_t); + int32_t (*actuator_write_focus)(struct msm_actuator_ctrl_t *, + uint16_t, + struct damping_params_t *, + int8_t, + int16_t); +}; + +struct msm_actuator { + enum actuator_type act_type; + struct msm_actuator_func_tbl func_tbl; +}; + +struct msm_actuator_ctrl_t { + struct i2c_driver *i2c_driver; + struct msm_camera_i2c_client i2c_client; + struct mutex *actuator_mutex; + struct msm_actuator_func_tbl *func_tbl; + enum msm_actuator_data_type i2c_data_type; + struct v4l2_subdev sdev; + struct v4l2_subdev_ops *act_v4l2_subdev_ops; + + int16_t curr_step_pos; + uint16_t curr_region_index; + uint16_t *step_position_table; + struct region_params_t region_params[MAX_ACTUATOR_REGION]; + uint16_t reg_tbl_size; + struct msm_actuator_reg_params_t reg_tbl[MAX_ACTUATOR_REG_TBL_SIZE]; + uint16_t region_size; + void *user_data; + uint32_t vcm_pwd; + uint32_t vcm_enable; + uint32_t total_steps; + uint16_t pwd_step; + uint16_t initial_code; +}; + +struct msm_actuator_ctrl_t *get_actrl(struct v4l2_subdev *sd); +int32_t msm_actuator_i2c_write(struct msm_actuator_ctrl_t *a_ctrl, + int16_t next_lens_position, uint32_t hw_params); +int32_t msm_actuator_init_focus(struct msm_actuator_ctrl_t *a_ctrl, + uint16_t size, enum msm_actuator_data_type type, + struct reg_settings_t *settings); +int32_t msm_actuator_i2c_write_b_af(struct msm_actuator_ctrl_t *a_ctrl, + uint8_t msb, + uint8_t lsb); +int32_t msm_actuator_move_focus(struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params); +int32_t msm_actuator_piezo_move_focus( + struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params); +int32_t msm_actuator_init_step_table(struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_set_info_t *set_info); +int32_t msm_actuator_set_default_focus(struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params); +int32_t msm_actuator_piezo_set_default_focus( + struct msm_actuator_ctrl_t *a_ctrl, + struct msm_actuator_move_params_t *move_params); +int32_t msm_actuator_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id); +int32_t msm_actuator_write_focus(struct msm_actuator_ctrl_t *a_ctrl, + uint16_t curr_lens_pos, struct damping_params_t *damping_params, + int8_t sign_direction, int16_t code_boundary); +int32_t msm_actuator_write_focus2(struct msm_actuator_ctrl_t *a_ctrl, + uint16_t curr_lens_pos, struct damping_params_t *damping_params, + int8_t sign_direction, int16_t code_boundary); +long msm_actuator_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg); +int32_t msm_actuator_power(struct v4l2_subdev *sd, int on); + +#define VIDIOC_MSM_ACTUATOR_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 11, void __user *) + +#endif diff --git a/drivers/media/video/msm/csi/Makefile b/drivers/media/video/msm/csi/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e5d59668bda135345599c416db34e6cd58cbfc8d --- /dev/null +++ b/drivers/media/video/msm/csi/Makefile @@ -0,0 +1,6 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) +EXTRA_CFLAGS += -Idrivers/media/video/msm +obj-$(CONFIG_ARCH_MSM8960) += msm_csiphy.o msm_csid.o msm_ispif.o +obj-$(CONFIG_ARCH_MSM7X27A) += msm_csic.o +obj-$(CONFIG_ARCH_MSM8X60) += msm_csic.o +obj-$(CONFIG_ARCH_MSM7X30) += msm_csic.o diff --git a/drivers/media/video/msm/csi/msm_csic.c b/drivers/media/video/msm/csi/msm_csic.c new file mode 100644 index 0000000000000000000000000000000000000000..a217f9df4ad11688e3a7b0cc0a5c4bd174e95e4b --- /dev/null +++ b/drivers/media/video/msm/csi/msm_csic.c @@ -0,0 +1,490 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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_csic.h" +#include "msm.h" + +#define DBG_CSIC 0 + +#define V4L2_IDENT_CSIC 50004 +/* 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 0x40000 +/* 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_EN 0x07 +#define MIPI_PWR_CNTL_DIS 0x0 + +static int msm_csic_config(struct csic_cfg_params *cfg_params) +{ + int rc = 0; + uint32_t val = 0; + struct csic_device *csic_dev; + struct msm_camera_csi_params *csic_params; + void __iomem *csicbase; + int i; + + csic_dev = v4l2_get_subdevdata(cfg_params->subdev); + csicbase = csic_dev->base; + csic_params = cfg_params->parms; + + /* Enable error correction for DATA lane. Applies to all data lanes */ + msm_camera_io_w(0x4, csicbase + MIPI_PHY_CONTROL); + + msm_camera_io_w(MIPI_PROTOCOL_CONTROL_SW_RST_BMSK, + csicbase + 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)(csic_params->data_format) << + MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT; + val |= csic_params->dpcm_scheme << + MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT; + CDBG("%s MIPI_PROTOCOL_CONTROL val=0x%x\n", __func__, val); + msm_camera_io_w(val, csicbase + MIPI_PROTOCOL_CONTROL); + + val = (csic_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); + for (i = 0; i < csic_params->lane_cnt; i++) + msm_camera_io_w(val, csicbase + MIPI_PHY_D0_CONTROL2 + i * 4); + + + 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_camera_io_w(val, csicbase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_camera_io_w(val, csicbase + 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_camera_io_w(val, csicbase + MIPI_PHY_D1_CONTROL); + + msm_camera_io_w(0x00000000, csicbase + MIPI_PHY_D2_CONTROL); + msm_camera_io_w(0x00000000, csicbase + MIPI_PHY_D3_CONTROL); + + /* program number of lanes and lane mapping */ + switch (csic_params->lane_cnt) { + case 1: + msm_camera_io_w(csic_params->lane_assign << 8 | 0x4, + csicbase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_camera_io_w(csic_params->lane_assign << 8 | 0x5, + csicbase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_camera_io_w(csic_params->lane_assign << 8 | 0x6, + csicbase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_camera_io_w(csic_params->lane_assign << 8 | 0x7, + csicbase + MIPI_CAMERA_CNTL); + break; + } + + msm_camera_io_w(0xF077F3C0, csicbase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits*/ + msm_camera_io_w(0xF077F3C0, csicbase + MIPI_INTERRUPT_STATUS); + + return rc; +} + +static irqreturn_t msm_csic_irq(int irq_num, void *data) +{ + uint32_t irq; + struct csic_device *csic_dev = data; + + pr_info("msm_csic_irq: %x\n", (unsigned int)csic_dev->base); + irq = msm_camera_io_r(csic_dev->base + MIPI_INTERRUPT_STATUS); + pr_info("%s MIPI_INTERRUPT_STATUS = 0x%x 0x%x\n", + __func__, irq, + msm_camera_io_r(csic_dev->base + MIPI_PROTOCOL_CONTROL)); + msm_camera_io_w(irq, csic_dev->base + 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; +} + +static int msm_csic_subdev_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + BUG_ON(!chip); + chip->ident = V4L2_IDENT_CSIC; + chip->revision = 0; + return 0; +} + +static struct msm_cam_clk_info csic_8x_clk_info[] = { + {"csi_src_clk", 384000000}, + {"csi_clk", -1}, + {"csi_vfe_clk", -1}, + {"csi_pclk", -1}, +}; + +static struct msm_cam_clk_info csic_7x_clk_info[] = { + {"csi_clk", 400000000}, + {"csi_vfe_clk", -1}, + {"csi_pclk", -1}, +}; + +static int msm_csic_init(struct v4l2_subdev *sd, uint32_t *csic_version) +{ + int rc = 0; + struct csic_device *csic_dev; + csic_dev = v4l2_get_subdevdata(sd); + if (csic_dev == NULL) { + rc = -ENOMEM; + return rc; + } + + csic_dev->base = ioremap(csic_dev->mem->start, + resource_size(csic_dev->mem)); + if (!csic_dev->base) { + rc = -ENOMEM; + return rc; + } + + csic_dev->hw_version = CSIC_8X; + rc = msm_cam_clk_enable(&csic_dev->pdev->dev, csic_8x_clk_info, + csic_dev->csic_clk, ARRAY_SIZE(csic_8x_clk_info), 1); + if (rc < 0) { + csic_dev->hw_version = CSIC_7X; + rc = msm_cam_clk_enable(&csic_dev->pdev->dev, csic_7x_clk_info, + csic_dev->csic_clk, ARRAY_SIZE(csic_7x_clk_info), 1); + if (rc < 0) { + csic_dev->hw_version = 0; + iounmap(csic_dev->base); + csic_dev->base = NULL; + return rc; + } + } + if (csic_dev->hw_version == CSIC_7X) + msm_camio_vfe_blk_reset_3(); + +#if DBG_CSIC + enable_irq(csic_dev->irq->start); +#endif + + return 0; +} + +static void msm_csic_disable(struct v4l2_subdev *sd) +{ + uint32_t val; + struct csic_device *csic_dev; + csic_dev = v4l2_get_subdevdata(sd); + + val = 0x0; + if (csic_dev->base != NULL) { + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_camera_io_w(val, csic_dev->base + MIPI_PHY_D0_CONTROL2); + msm_camera_io_w(val, csic_dev->base + MIPI_PHY_D1_CONTROL2); + msm_camera_io_w(val, csic_dev->base + MIPI_PHY_D2_CONTROL2); + msm_camera_io_w(val, csic_dev->base + MIPI_PHY_D3_CONTROL2); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_camera_io_w(val, csic_dev->base + MIPI_PHY_CL_CONTROL); + msleep(20); + val = msm_camera_io_r(csic_dev->base + 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_camera_io_w(val, csic_dev->base + MIPI_PHY_D1_CONTROL); + usleep_range(5000, 6000); + msm_camera_io_w(0x0, csic_dev->base + MIPI_INTERRUPT_MASK); + msm_camera_io_w(0x0, csic_dev->base + MIPI_INTERRUPT_STATUS); + msm_camera_io_w(MIPI_PROTOCOL_CONTROL_SW_RST_BMSK, + csic_dev->base + MIPI_PROTOCOL_CONTROL); + + msm_camera_io_w(0xE400, csic_dev->base + MIPI_CAMERA_CNTL); + } +} + +static int msm_csic_release(struct v4l2_subdev *sd) +{ + struct csic_device *csic_dev; + csic_dev = v4l2_get_subdevdata(sd); + + msm_csic_disable(sd); +#if DBG_CSIC + disable_irq(csic_dev->irq->start); +#endif + + if (csic_dev->hw_version == CSIC_8X) { + msm_cam_clk_enable(&csic_dev->pdev->dev, csic_8x_clk_info, + csic_dev->csic_clk, ARRAY_SIZE(csic_8x_clk_info), 0); + } else if (csic_dev->hw_version == CSIC_7X) { + msm_cam_clk_enable(&csic_dev->pdev->dev, csic_7x_clk_info, + csic_dev->csic_clk, ARRAY_SIZE(csic_7x_clk_info), 0); + } + + iounmap(csic_dev->base); + csic_dev->base = NULL; + return 0; +} + +static long msm_csic_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct csic_cfg_params cfg_params; + switch (cmd) { + case VIDIOC_MSM_CSIC_CFG: + cfg_params.subdev = sd; + cfg_params.parms = arg; + return msm_csic_config((struct csic_cfg_params *)&cfg_params); + case VIDIOC_MSM_CSIC_INIT: + return msm_csic_init(sd, (uint32_t *)arg); + case VIDIOC_MSM_CSIC_RELEASE: + return msm_csic_release(sd); + default: + return -ENOIOCTLCMD; + } +} + +static struct v4l2_subdev_core_ops msm_csic_subdev_core_ops = { + .g_chip_ident = &msm_csic_subdev_g_chip_ident, + .ioctl = &msm_csic_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_csic_subdev_ops = { + .core = &msm_csic_subdev_core_ops, +}; + +static const struct v4l2_subdev_internal_ops msm_csic_internal_ops; + +static int __devinit csic_probe(struct platform_device *pdev) +{ + struct csic_device *new_csic_dev; + int rc = 0; + CDBG("%s: device id = %d\n", __func__, pdev->id); + new_csic_dev = kzalloc(sizeof(struct csic_device), GFP_KERNEL); + if (!new_csic_dev) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&new_csic_dev->subdev, &msm_csic_subdev_ops); + new_csic_dev->subdev.internal_ops = &msm_csic_internal_ops; + new_csic_dev->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(new_csic_dev->subdev.name, + ARRAY_SIZE(new_csic_dev->subdev.name), "msm_csic"); + v4l2_set_subdevdata(&new_csic_dev->subdev, new_csic_dev); + platform_set_drvdata(pdev, &new_csic_dev->subdev); + mutex_init(&new_csic_dev->mutex); + + new_csic_dev->mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "csic"); + if (!new_csic_dev->mem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto csic_no_resource; + } + new_csic_dev->irq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "csic"); + if (!new_csic_dev->irq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto csic_no_resource; + } + new_csic_dev->io = request_mem_region(new_csic_dev->mem->start, + resource_size(new_csic_dev->mem), pdev->name); + if (!new_csic_dev->io) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto csic_no_resource; + } + + rc = request_irq(new_csic_dev->irq->start, msm_csic_irq, + IRQF_TRIGGER_HIGH, "csic", new_csic_dev); + if (rc < 0) { + release_mem_region(new_csic_dev->mem->start, + resource_size(new_csic_dev->mem)); + pr_err("%s: irq request fail\n", __func__); + rc = -EBUSY; + goto csic_no_resource; + } + disable_irq(new_csic_dev->irq->start); + + new_csic_dev->pdev = pdev; + + rc = msm_cam_clk_enable(&new_csic_dev->pdev->dev, &csic_7x_clk_info[2], + new_csic_dev->csic_clk, 1, 1); + new_csic_dev->base = ioremap(new_csic_dev->mem->start, + resource_size(new_csic_dev->mem)); + if (!new_csic_dev->base) { + rc = -ENOMEM; + return rc; + } + + msm_camera_io_w(MIPI_PWR_CNTL_DIS, new_csic_dev->base + MIPI_PWR_CNTL); + + rc = msm_cam_clk_enable(&new_csic_dev->pdev->dev, &csic_7x_clk_info[2], + new_csic_dev->csic_clk, 1, 0); + + iounmap(new_csic_dev->base); + new_csic_dev->base = NULL; + msm_cam_register_subdev_node( + &new_csic_dev->subdev, CSIC_DEV, pdev->id); + + return 0; + +csic_no_resource: + mutex_destroy(&new_csic_dev->mutex); + kfree(new_csic_dev); + return 0; +} + +static struct platform_driver csic_driver = { + .probe = csic_probe, + .driver = { + .name = MSM_CSIC_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_csic_init_module(void) +{ + return platform_driver_register(&csic_driver); +} + +static void __exit msm_csic_exit_module(void) +{ + platform_driver_unregister(&csic_driver); +} + +module_init(msm_csic_init_module); +module_exit(msm_csic_exit_module); +MODULE_DESCRIPTION("MSM csic driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/csi/msm_csic.h b/drivers/media/video/msm/csi/msm_csic.h new file mode 100644 index 0000000000000000000000000000000000000000..177e9d19b92c0d418bb1b27dd703e5f6e2755a7c --- /dev/null +++ b/drivers/media/video/msm/csi/msm_csic.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_CSIC_H +#define MSM_CSIC_H + +#include +#include +#include + +#define CSIC_7X 0x1 +#define CSIC_8X (0x1 << 1) + +struct csic_device { + struct platform_device *pdev; + struct v4l2_subdev subdev; + struct resource *mem; + struct resource *irq; + struct resource *io; + void __iomem *base; + struct mutex mutex; + uint32_t hw_version; + + struct clk *csic_clk[5]; +}; + +struct csic_cfg_params { + struct v4l2_subdev *subdev; + void *parms; +}; + +#define VIDIOC_MSM_CSIC_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 4, struct csic_cfg_params) + +#define VIDIOC_MSM_CSIC_INIT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 5, struct v4l2_subdev*) + +#define VIDIOC_MSM_CSIC_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 6, struct v4l2_subdev*) + +#endif + diff --git a/drivers/media/video/msm/csi/msm_csid.c b/drivers/media/video/msm/csi/msm_csid.c new file mode 100644 index 0000000000000000000000000000000000000000..0495b7ba21d416f68e5d18ccd75112aace8b27d6 --- /dev/null +++ b/drivers/media/video/msm/csi/msm_csid.c @@ -0,0 +1,369 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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_csid.h" +#include "msm.h" + +#define V4L2_IDENT_CSID 50002 + +/* MIPI CSID registers */ +#define CSID_HW_VERSION_ADDR 0x0 +#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 +#define CSID_RST_DONE_IRQ_BITSHIFT 11 +#define CSID_RST_STB_ALL 0x7FFF + +#define DBG_CSID 0 + +static int msm_csid_cid_lut( + struct msm_camera_csid_lut_params *csid_lut_params, + void __iomem *csidbase) +{ + 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_camera_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_camera_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_camera_io_w(val, csidbase + CSID_CID_n_CFG_ADDR + + (csid_lut_params->vc_cfg[i].cid * 4)); + } + return rc; +} + +#if DBG_CSID +static void msm_csid_set_debug_reg(void __iomem *csidbase, + struct msm_camera_csid_params *csid_params) +{ + uint32_t val = 0; + val = ((1 << csid_params->lane_cnt) - 1) << 20; + msm_camera_io_w(0x7f010800 | val, csidbase + CSID_IRQ_MASK_ADDR); + msm_camera_io_w(0x7f010800 | val, csidbase + CSID_IRQ_CLEAR_CMD_ADDR); +} +#else +static void msm_csid_set_debug_reg(void __iomem *csidbase, + struct msm_camera_csid_params *csid_params) {} +#endif + +static int msm_csid_config(struct csid_cfg_params *cfg_params) +{ + int rc = 0; + uint32_t val = 0; + struct csid_device *csid_dev; + struct msm_camera_csid_params *csid_params; + void __iomem *csidbase; + csid_dev = v4l2_get_subdevdata(cfg_params->subdev); + csidbase = csid_dev->base; + csid_params = cfg_params->parms; + + val = csid_params->lane_cnt - 1; + val |= csid_params->lane_assign << 2; + val |= 0x1 << 10; + val |= 0x1 << 11; + val |= 0x1 << 12; + val |= 0x1 << 13; + val |= 0x1 << 28; + msm_camera_io_w(val, csidbase + CSID_CORE_CTRL_ADDR); + + rc = msm_csid_cid_lut(&csid_params->lut_params, csidbase); + if (rc < 0) + return rc; + + msm_csid_set_debug_reg(csidbase, csid_params); + + return rc; +} + +static irqreturn_t msm_csid_irq(int irq_num, void *data) +{ + uint32_t irq; + struct csid_device *csid_dev = data; + irq = msm_camera_io_r(csid_dev->base + CSID_IRQ_STATUS_ADDR); + CDBG("%s CSID%d_IRQ_STATUS_ADDR = 0x%x\n", + __func__, csid_dev->pdev->id, irq); + if (irq & (0x1 << CSID_RST_DONE_IRQ_BITSHIFT)) + complete(&csid_dev->reset_complete); + msm_camera_io_w(irq, csid_dev->base + CSID_IRQ_CLEAR_CMD_ADDR); + return IRQ_HANDLED; +} + +static void msm_csid_reset(struct csid_device *csid_dev) +{ + msm_camera_io_w(CSID_RST_STB_ALL, csid_dev->base + CSID_RST_CMD_ADDR); + wait_for_completion_interruptible(&csid_dev->reset_complete); + return; +} + +static int msm_csid_subdev_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + BUG_ON(!chip); + chip->ident = V4L2_IDENT_CSID; + chip->revision = 0; + return 0; +} + +static struct msm_cam_clk_info csid_clk_info[] = { + {"csi_src_clk", 177780000}, + {"csi_clk", -1}, + {"csi_phy_clk", -1}, + {"csi_pclk", -1}, +}; + +static struct camera_vreg_t csid_vreg_info[] = { + {"mipi_csi_vdd", REG_LDO, 1200000, 1200000, 20000}, +}; + +static int msm_csid_init(struct v4l2_subdev *sd, uint32_t *csid_version) +{ + int rc = 0; + struct csid_device *csid_dev; + csid_dev = v4l2_get_subdevdata(sd); + if (csid_dev == NULL) { + rc = -ENOMEM; + return rc; + } + + csid_dev->base = ioremap(csid_dev->mem->start, + resource_size(csid_dev->mem)); + if (!csid_dev->base) { + rc = -ENOMEM; + return rc; + } + + rc = msm_camera_config_vreg(&csid_dev->pdev->dev, csid_vreg_info, + ARRAY_SIZE(csid_vreg_info), &csid_dev->csi_vdd, 1); + if (rc < 0) { + pr_err("%s: regulator on failed\n", __func__); + goto vreg_config_failed; + } + + rc = msm_camera_enable_vreg(&csid_dev->pdev->dev, csid_vreg_info, + ARRAY_SIZE(csid_vreg_info), &csid_dev->csi_vdd, 1); + if (rc < 0) { + pr_err("%s: regulator enable failed\n", __func__); + goto vreg_enable_failed; + } + + rc = msm_cam_clk_enable(&csid_dev->pdev->dev, csid_clk_info, + csid_dev->csid_clk, ARRAY_SIZE(csid_clk_info), 1); + if (rc < 0) { + pr_err("%s: regulator enable failed\n", __func__); + goto clk_enable_failed; + } + + csid_dev->hw_version = + msm_camera_io_r(csid_dev->base + CSID_HW_VERSION_ADDR); + *csid_version = csid_dev->hw_version; + + init_completion(&csid_dev->reset_complete); + + rc = request_irq(csid_dev->irq->start, msm_csid_irq, + IRQF_TRIGGER_RISING, "csid", csid_dev); + + msm_csid_reset(csid_dev); + return rc; + +clk_enable_failed: + msm_camera_enable_vreg(&csid_dev->pdev->dev, csid_vreg_info, + ARRAY_SIZE(csid_vreg_info), &csid_dev->csi_vdd, 0); +vreg_enable_failed: + msm_camera_config_vreg(&csid_dev->pdev->dev, csid_vreg_info, + ARRAY_SIZE(csid_vreg_info), &csid_dev->csi_vdd, 0); +vreg_config_failed: + iounmap(csid_dev->base); + csid_dev->base = NULL; + return rc; +} + +static int msm_csid_release(struct v4l2_subdev *sd) +{ + uint32_t irq; + struct csid_device *csid_dev; + csid_dev = v4l2_get_subdevdata(sd); + + irq = msm_camera_io_r(csid_dev->base + CSID_IRQ_STATUS_ADDR); + msm_camera_io_w(irq, csid_dev->base + CSID_IRQ_CLEAR_CMD_ADDR); + msm_camera_io_w(0, csid_dev->base + CSID_IRQ_MASK_ADDR); + + free_irq(csid_dev->irq->start, csid_dev); + + msm_cam_clk_enable(&csid_dev->pdev->dev, csid_clk_info, + csid_dev->csid_clk, ARRAY_SIZE(csid_clk_info), 0); + + msm_camera_enable_vreg(&csid_dev->pdev->dev, csid_vreg_info, + ARRAY_SIZE(csid_vreg_info), &csid_dev->csi_vdd, 0); + + msm_camera_config_vreg(&csid_dev->pdev->dev, csid_vreg_info, + ARRAY_SIZE(csid_vreg_info), &csid_dev->csi_vdd, 0); + + iounmap(csid_dev->base); + csid_dev->base = NULL; + return 0; +} + +static long msm_csid_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + int rc = -ENOIOCTLCMD; + struct csid_cfg_params cfg_params; + struct csid_device *csid_dev = v4l2_get_subdevdata(sd); + mutex_lock(&csid_dev->mutex); + switch (cmd) { + case VIDIOC_MSM_CSID_CFG: + cfg_params.subdev = sd; + cfg_params.parms = arg; + rc = msm_csid_config((struct csid_cfg_params *)&cfg_params); + break; + case VIDIOC_MSM_CSID_INIT: + rc = msm_csid_init(sd, (uint32_t *)arg); + break; + case VIDIOC_MSM_CSID_RELEASE: + rc = msm_csid_release(sd); + break; + default: + pr_err("%s: command not found\n", __func__); + } + mutex_unlock(&csid_dev->mutex); + return rc; +} + +static const struct v4l2_subdev_internal_ops msm_csid_internal_ops; + +static struct v4l2_subdev_core_ops msm_csid_subdev_core_ops = { + .g_chip_ident = &msm_csid_subdev_g_chip_ident, + .ioctl = &msm_csid_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_csid_subdev_ops = { + .core = &msm_csid_subdev_core_ops, +}; + +static int __devinit csid_probe(struct platform_device *pdev) +{ + struct csid_device *new_csid_dev; + int rc = 0; + CDBG("%s: device id = %d\n", __func__, pdev->id); + new_csid_dev = kzalloc(sizeof(struct csid_device), GFP_KERNEL); + if (!new_csid_dev) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&new_csid_dev->subdev, &msm_csid_subdev_ops); + new_csid_dev->subdev.internal_ops = &msm_csid_internal_ops; + new_csid_dev->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(new_csid_dev->subdev.name, + ARRAY_SIZE(new_csid_dev->subdev.name), "msm_csid"); + v4l2_set_subdevdata(&new_csid_dev->subdev, new_csid_dev); + platform_set_drvdata(pdev, &new_csid_dev->subdev); + mutex_init(&new_csid_dev->mutex); + + new_csid_dev->mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "csid"); + if (!new_csid_dev->mem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto csid_no_resource; + } + new_csid_dev->irq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "csid"); + if (!new_csid_dev->irq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto csid_no_resource; + } + new_csid_dev->io = request_mem_region(new_csid_dev->mem->start, + resource_size(new_csid_dev->mem), pdev->name); + if (!new_csid_dev->io) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto csid_no_resource; + } + + new_csid_dev->pdev = pdev; + msm_cam_register_subdev_node(&new_csid_dev->subdev, CSID_DEV, pdev->id); + return 0; + +csid_no_resource: + mutex_destroy(&new_csid_dev->mutex); + kfree(new_csid_dev); + return 0; +} + +static struct platform_driver csid_driver = { + .probe = csid_probe, + .driver = { + .name = MSM_CSID_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_csid_init_module(void) +{ + return platform_driver_register(&csid_driver); +} + +static void __exit msm_csid_exit_module(void) +{ + platform_driver_unregister(&csid_driver); +} + +module_init(msm_csid_init_module); +module_exit(msm_csid_exit_module); +MODULE_DESCRIPTION("MSM CSID driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/csi/msm_csid.h b/drivers/media/video/msm/csi/msm_csid.h new file mode 100644 index 0000000000000000000000000000000000000000..2eae49cd51c4509dab7df5e4c83e00c1c920188a --- /dev/null +++ b/drivers/media/video/msm/csi/msm_csid.h @@ -0,0 +1,50 @@ +/* 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_CSID_H +#define MSM_CSID_H + +#include +#include +#include + +struct csid_device { + struct platform_device *pdev; + struct v4l2_subdev subdev; + struct resource *mem; + struct resource *irq; + struct resource *io; + struct regulator *csi_vdd; + void __iomem *base; + struct mutex mutex; + struct completion reset_complete; + uint32_t hw_version; + + struct clk *csid_clk[5]; +}; + +struct csid_cfg_params { + struct v4l2_subdev *subdev; + void *parms; +}; + +#define VIDIOC_MSM_CSID_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 4, struct csid_cfg_params) + +#define VIDIOC_MSM_CSID_INIT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 5, struct v4l2_subdev*) + +#define VIDIOC_MSM_CSID_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 6, struct v4l2_subdev*) + +#endif + diff --git a/drivers/media/video/msm/csi/msm_csiphy.c b/drivers/media/video/msm/csi/msm_csiphy.c new file mode 100644 index 0000000000000000000000000000000000000000..c384b5a0acbf3a078fab3131c0f2adb1fafcfe9e --- /dev/null +++ b/drivers/media/video/msm/csi/msm_csiphy.c @@ -0,0 +1,364 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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_csiphy.h" +#include "msm.h" + +#define DBG_CSIPHY 0 + +#define V4L2_IDENT_CSIPHY 50003 + +/*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_T_WAKEUP_CFG1_ADDR 0x1EC +#define MIPI_CSIPHY_GLBL_RESET_ADDR 0x0140 +#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 + +int msm_csiphy_config(struct csiphy_cfg_params *cfg_params) +{ + int rc = 0; + int j = 0; + uint32_t val = 0; + uint8_t lane_cnt = 0, lane_mask = 0; + struct csiphy_device *csiphy_dev; + struct msm_camera_csiphy_params *csiphy_params; + void __iomem *csiphybase; + csiphy_dev = v4l2_get_subdevdata(cfg_params->subdev); + csiphybase = csiphy_dev->base; + csiphy_params = cfg_params->parms; + lane_mask = csiphy_params->lane_mask; + lane_cnt = csiphy_params->lane_cnt; + 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_camera_io_w((csiphy_params->lane_mask << 2) | val, + csiphybase + MIPI_CSIPHY_GLBL_PWR_CFG_ADDR); + msm_camera_io_w(0x1, csiphybase + MIPI_CSIPHY_GLBL_T_INIT_CFG0_ADDR); + msm_camera_io_w(0x1, csiphybase + MIPI_CSIPHY_T_WAKEUP_CFG0_ADDR); + + while (lane_mask & 0xf) { + if (!(lane_mask & 0x1)) { + j++; + lane_mask >>= 1; + continue; + } + msm_camera_io_w(0x10, + csiphybase + MIPI_CSIPHY_LNn_CFG2_ADDR + 0x40*j); + msm_camera_io_w(csiphy_params->settle_cnt, + csiphybase + MIPI_CSIPHY_LNn_CFG3_ADDR + 0x40*j); + msm_camera_io_w(0x6F, + csiphybase + MIPI_CSIPHY_INTERRUPT_MASK0_ADDR + + 0x4*(j+1)); + msm_camera_io_w(0x6F, + csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR + + 0x4*(j+1)); + j++; + lane_mask >>= 1; + } + + msm_camera_io_w(0x10, csiphybase + MIPI_CSIPHY_LNCK_CFG2_ADDR); + msm_camera_io_w(csiphy_params->settle_cnt, + csiphybase + MIPI_CSIPHY_LNCK_CFG3_ADDR); + + msm_camera_io_w(0x24, + csiphybase + MIPI_CSIPHY_INTERRUPT_MASK0_ADDR); + msm_camera_io_w(0x24, + csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR); + return rc; +} + +static irqreturn_t msm_csiphy_irq(int irq_num, void *data) +{ + uint32_t irq; + struct csiphy_device *csiphy_dev = data; + + irq = msm_camera_io_r( + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_STATUS0_ADDR); + msm_camera_io_w(irq, + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR); + CDBG("%s MIPI_CSIPHY%d_INTERRUPT_STATUS0 = 0x%x\n", + __func__, csiphy_dev->pdev->id, irq); + + irq = msm_camera_io_r( + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_STATUS1_ADDR); + msm_camera_io_w(irq, + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_CLEAR1_ADDR); + CDBG("%s MIPI_CSIPHY%d_INTERRUPT_STATUS1 = 0x%x\n", + __func__, csiphy_dev->pdev->id, irq); + + irq = msm_camera_io_r( + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_STATUS2_ADDR); + msm_camera_io_w(irq, + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_CLEAR2_ADDR); + CDBG("%s MIPI_CSIPHY%d_INTERRUPT_STATUS2 = 0x%x\n", + __func__, csiphy_dev->pdev->id, irq); + + irq = msm_camera_io_r( + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_STATUS3_ADDR); + msm_camera_io_w(irq, + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_CLEAR3_ADDR); + CDBG("%s MIPI_CSIPHY%d_INTERRUPT_STATUS3 = 0x%x\n", + __func__, csiphy_dev->pdev->id, irq); + + irq = msm_camera_io_r( + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_STATUS4_ADDR); + msm_camera_io_w(irq, + csiphy_dev->base + MIPI_CSIPHY_INTERRUPT_CLEAR4_ADDR); + CDBG("%s MIPI_CSIPHY%d_INTERRUPT_STATUS4 = 0x%x\n", + __func__, csiphy_dev->pdev->id, irq); + msm_camera_io_w(0x1, csiphy_dev->base + 0x164); + msm_camera_io_w(0x0, csiphy_dev->base + 0x164); + return IRQ_HANDLED; +} + +static void msm_csiphy_reset(struct csiphy_device *csiphy_dev) +{ + msm_camera_io_w(0x1, csiphy_dev->base + MIPI_CSIPHY_GLBL_RESET_ADDR); + usleep_range(5000, 8000); + msm_camera_io_w(0x0, csiphy_dev->base + MIPI_CSIPHY_GLBL_RESET_ADDR); +} + +static int msm_csiphy_subdev_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + BUG_ON(!chip); + chip->ident = V4L2_IDENT_CSIPHY; + chip->revision = 0; + return 0; +} + +static struct msm_cam_clk_info csiphy_clk_info[] = { + {"csiphy_timer_src_clk", 177780000}, + {"csiphy_timer_clk", -1}, +}; + +static int msm_csiphy_init(struct v4l2_subdev *sd) +{ + int rc = 0; + struct csiphy_device *csiphy_dev; + csiphy_dev = v4l2_get_subdevdata(sd); + if (csiphy_dev == NULL) { + rc = -ENOMEM; + return rc; + } + + csiphy_dev->base = ioremap(csiphy_dev->mem->start, + resource_size(csiphy_dev->mem)); + if (!csiphy_dev->base) { + rc = -ENOMEM; + return rc; + } + + rc = msm_cam_clk_enable(&csiphy_dev->pdev->dev, csiphy_clk_info, + csiphy_dev->csiphy_clk, ARRAY_SIZE(csiphy_clk_info), 1); + + if (rc < 0) { + iounmap(csiphy_dev->base); + csiphy_dev->base = NULL; + return rc; + } + +#if DBG_CSIPHY + enable_irq(csiphy_dev->irq->start); +#endif + msm_csiphy_reset(csiphy_dev); + + return 0; +} + +static int msm_csiphy_release(struct v4l2_subdev *sd) +{ + struct csiphy_device *csiphy_dev; + int i; + csiphy_dev = v4l2_get_subdevdata(sd); + for (i = 0; i < 4; i++) + msm_camera_io_w(0x0, csiphy_dev->base + + MIPI_CSIPHY_LNn_CFG2_ADDR + 0x40*i); + + msm_camera_io_w(0x0, csiphy_dev->base + MIPI_CSIPHY_LNCK_CFG2_ADDR); + msm_camera_io_w(0x0, csiphy_dev->base + MIPI_CSIPHY_GLBL_PWR_CFG_ADDR); + +#if DBG_CSIPHY + disable_irq(csiphy_dev->irq->start); +#endif + msm_cam_clk_enable(&csiphy_dev->pdev->dev, csiphy_clk_info, + csiphy_dev->csiphy_clk, ARRAY_SIZE(csiphy_clk_info), 0); + + iounmap(csiphy_dev->base); + csiphy_dev->base = NULL; + return 0; +} + +static long msm_csiphy_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + int rc = -ENOIOCTLCMD; + struct csiphy_cfg_params cfg_params; + struct csiphy_device *csiphy_dev = v4l2_get_subdevdata(sd); + mutex_lock(&csiphy_dev->mutex); + switch (cmd) { + case VIDIOC_MSM_CSIPHY_CFG: + cfg_params.subdev = sd; + cfg_params.parms = arg; + rc = msm_csiphy_config( + (struct csiphy_cfg_params *)&cfg_params); + break; + case VIDIOC_MSM_CSIPHY_INIT: + rc = msm_csiphy_init(sd); + break; + case VIDIOC_MSM_CSIPHY_RELEASE: + rc = msm_csiphy_release(sd); + break; + default: + pr_err("%s: command not found\n", __func__); + } + mutex_unlock(&csiphy_dev->mutex); + return rc; +} + +static const struct v4l2_subdev_internal_ops msm_csiphy_internal_ops; + +static struct v4l2_subdev_core_ops msm_csiphy_subdev_core_ops = { + .g_chip_ident = &msm_csiphy_subdev_g_chip_ident, + .ioctl = &msm_csiphy_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_csiphy_subdev_ops = { + .core = &msm_csiphy_subdev_core_ops, +}; + +static int __devinit csiphy_probe(struct platform_device *pdev) +{ + struct csiphy_device *new_csiphy_dev; + int rc = 0; + CDBG("%s: device id = %d\n", __func__, pdev->id); + new_csiphy_dev = kzalloc(sizeof(struct csiphy_device), GFP_KERNEL); + if (!new_csiphy_dev) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&new_csiphy_dev->subdev, &msm_csiphy_subdev_ops); + new_csiphy_dev->subdev.internal_ops = &msm_csiphy_internal_ops; + new_csiphy_dev->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(new_csiphy_dev->subdev.name, + ARRAY_SIZE(new_csiphy_dev->subdev.name), "msm_csiphy"); + v4l2_set_subdevdata(&new_csiphy_dev->subdev, new_csiphy_dev); + platform_set_drvdata(pdev, &new_csiphy_dev->subdev); + + mutex_init(&new_csiphy_dev->mutex); + + new_csiphy_dev->mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "csiphy"); + if (!new_csiphy_dev->mem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto csiphy_no_resource; + } + new_csiphy_dev->irq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "csiphy"); + if (!new_csiphy_dev->irq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto csiphy_no_resource; + } + new_csiphy_dev->io = request_mem_region(new_csiphy_dev->mem->start, + resource_size(new_csiphy_dev->mem), pdev->name); + if (!new_csiphy_dev->io) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto csiphy_no_resource; + } + + rc = request_irq(new_csiphy_dev->irq->start, msm_csiphy_irq, + IRQF_TRIGGER_RISING, "csiphy", new_csiphy_dev); + if (rc < 0) { + release_mem_region(new_csiphy_dev->mem->start, + resource_size(new_csiphy_dev->mem)); + pr_err("%s: irq request fail\n", __func__); + rc = -EBUSY; + goto csiphy_no_resource; + } + disable_irq(new_csiphy_dev->irq->start); + + new_csiphy_dev->pdev = pdev; + msm_cam_register_subdev_node( + &new_csiphy_dev->subdev, CSIPHY_DEV, pdev->id); + return 0; + +csiphy_no_resource: + mutex_destroy(&new_csiphy_dev->mutex); + kfree(new_csiphy_dev); + return 0; +} + +static struct platform_driver csiphy_driver = { + .probe = csiphy_probe, + .driver = { + .name = MSM_CSIPHY_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_csiphy_init_module(void) +{ + return platform_driver_register(&csiphy_driver); +} + +static void __exit msm_csiphy_exit_module(void) +{ + platform_driver_unregister(&csiphy_driver); +} + +module_init(msm_csiphy_init_module); +module_exit(msm_csiphy_exit_module); +MODULE_DESCRIPTION("MSM CSIPHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/csi/msm_csiphy.h b/drivers/media/video/msm/csi/msm_csiphy.h new file mode 100644 index 0000000000000000000000000000000000000000..522a1c15d3847829a8e7420cef1489f6a663ce6d --- /dev/null +++ b/drivers/media/video/msm/csi/msm_csiphy.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 MSM_CSIPHY_H +#define MSM_CSIPHY_H + +#include +#include +#include + +struct csiphy_device { + struct platform_device *pdev; + struct v4l2_subdev subdev; + struct resource *mem; + struct resource *irq; + struct resource *io; + void __iomem *base; + struct mutex mutex; + + struct clk *csiphy_clk[2]; +}; + +struct csiphy_cfg_params { + struct v4l2_subdev *subdev; + void *parms; +}; + +#define VIDIOC_MSM_CSIPHY_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 7, struct csiphy_cfg_params) + +#define VIDIOC_MSM_CSIPHY_INIT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 8, struct v4l2_subdev*) + +#define VIDIOC_MSM_CSIPHY_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 9, struct v4l2_subdev*) + +#endif diff --git a/drivers/media/video/msm/csi/msm_ispif.c b/drivers/media/video/msm/csi/msm_ispif.c new file mode 100644 index 0000000000000000000000000000000000000000..23982dde90befe8a6ff4d9b6c425faef5622aca2 --- /dev/null +++ b/drivers/media/video/msm/csi/msm_ispif.c @@ -0,0 +1,688 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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_ispif.h" +#include "msm.h" + +#define V4L2_IDENT_ISPIF 50001 +#define CSID_VERSION_V2 0x2000011 + +/* 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 +#define ISPIF_IRQ_GLOBAL_CLEAR_CMD_ADDR 0x0124 + +/*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 ISPIF_IRQ_STATUS_MASK 0xA493000 +#define ISPIF_IRQ_1_STATUS_MASK 0xA493000 +#define ISPIF_IRQ_STATUS_RDI_SOF_MASK 0x492000 +#define ISPIF_IRQ_GLOBAL_CLEAR_CMD 0x1 + +#define MAX_CID 15 + + +static struct ispif_device *ispif; +atomic_t ispif_irq_cnt; +spinlock_t ispif_tasklet_lock; +struct list_head ispif_tasklet_q; + +static uint32_t global_intf_cmd_mask = 0xFFFFFFFF; + + +static int msm_ispif_intf_reset(uint8_t intfmask) +{ + int rc = 0; + uint32_t data = 0x1; + uint8_t intfnum = 0, mask = intfmask; + while (mask != 0) { + if (!(intfmask & (0x1 << intfnum))) { + mask >>= 1; + intfnum++; + continue; + } + switch (intfnum) { + case PIX0: + data = (0x1 << STROBED_RST_EN) + + (0x1 << PIX_VFE_RST_STB) + + (0x1 << PIX_CSID_RST_STB); + break; + + case RDI0: + data = (0x1 << STROBED_RST_EN) + + (0x1 << RDI_VFE_RST_STB) + + (0x1 << RDI_CSID_RST_STB); + break; + + case RDI1: + data = (0x1 << STROBED_RST_EN) + + (0x1 << RDI_1_VFE_RST_STB) + + (0x1 << RDI_1_CSID_RST_STB); + break; + + default: + rc = -EINVAL; + break; + } + mask >>= 1; + intfnum++; + } /*end while */ + if (rc >= 0) { + msm_camera_io_w(data, ispif->base + ISPIF_RST_CMD_ADDR); + rc = wait_for_completion_interruptible(&ispif->reset_complete); + } + + return rc; +} + +static int msm_ispif_reset(void) +{ + uint32_t data = (0x1 << STROBED_RST_EN) + + (0x1 << SW_REG_RST_STB) + + (0x1 << MISC_LOGIC_RST_STB) + + (0x1 << PIX_VFE_RST_STB) + + (0x1 << PIX_CSID_RST_STB) + + (0x1 << RDI_VFE_RST_STB) + + (0x1 << RDI_CSID_RST_STB) + + (0x1 << RDI_1_VFE_RST_STB) + + (0x1 << RDI_1_CSID_RST_STB); + msm_camera_io_w(data, ispif->base + ISPIF_RST_CMD_ADDR); + return wait_for_completion_interruptible(&ispif->reset_complete); +} + +static int msm_ispif_subdev_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + BUG_ON(!chip); + chip->ident = V4L2_IDENT_ISPIF; + chip->revision = 0; + return 0; +} + +static void msm_ispif_sel_csid_core(uint8_t intftype, uint8_t csid) +{ + int rc = 0; + uint32_t data; + if (ispif->ispif_clk[intftype] == NULL) { + pr_err("%s: ispif NULL clk\n", __func__); + return; + } + + rc = clk_set_rate(ispif->ispif_clk[intftype], csid); + if (rc < 0) + pr_err("%s: clk_set_rate failed %d\n", __func__, rc); + + data = msm_camera_io_r(ispif->base + ISPIF_INPUT_SEL_ADDR); + data |= csid<<(intftype*4); + msm_camera_io_w(data, ispif->base + ISPIF_INPUT_SEL_ADDR); +} + +static void msm_ispif_enable_intf_cids(uint8_t intftype, uint16_t cid_mask) +{ + uint32_t data; + mutex_lock(&ispif->mutex); + switch (intftype) { + case PIX0: + data = msm_camera_io_r(ispif->base + + ISPIF_PIX_INTF_CID_MASK_ADDR); + data |= cid_mask; + msm_camera_io_w(data, ispif->base + + ISPIF_PIX_INTF_CID_MASK_ADDR); + break; + + case RDI0: + data = msm_camera_io_r(ispif->base + + ISPIF_RDI_INTF_CID_MASK_ADDR); + data |= cid_mask; + msm_camera_io_w(data, ispif->base + + ISPIF_RDI_INTF_CID_MASK_ADDR); + break; + + case RDI1: + data = msm_camera_io_r(ispif->base + + ISPIF_RDI_1_INTF_CID_MASK_ADDR); + data |= cid_mask; + msm_camera_io_w(data, ispif->base + + ISPIF_RDI_1_INTF_CID_MASK_ADDR); + break; + } + mutex_unlock(&ispif->mutex); +} + +static int msm_ispif_config(struct msm_ispif_params_list *params_list) +{ + uint32_t params_len; + struct msm_ispif_params *ispif_params; + uint32_t data, data1; + int rc = 0, i = 0; + params_len = params_list->len; + ispif_params = params_list->params; + CDBG("Enable interface\n"); + data = msm_camera_io_r(ispif->base + ISPIF_PIX_STATUS_ADDR); + data1 = msm_camera_io_r(ispif->base + ISPIF_RDI_STATUS_ADDR); + if (((data & 0xf) != 0xf) || ((data1 & 0xf) != 0xf)) + return -EBUSY; + msm_camera_io_w(0x00000000, ispif->base + ISPIF_IRQ_MASK_ADDR); + for (i = 0; i < params_len; 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_camera_io_w(ISPIF_IRQ_STATUS_MASK, ispif->base + + ISPIF_IRQ_MASK_ADDR); + msm_camera_io_w(ISPIF_IRQ_STATUS_MASK, ispif->base + + ISPIF_IRQ_CLEAR_ADDR); + msm_camera_io_w(ISPIF_IRQ_GLOBAL_CLEAR_CMD, ispif->base + + ISPIF_IRQ_GLOBAL_CLEAR_CMD_ADDR); + return rc; +} + +static uint32_t msm_ispif_get_cid_mask(uint8_t intftype) +{ + uint32_t mask = 0; + switch (intftype) { + case PIX0: + mask = msm_camera_io_r(ispif->base + + ISPIF_PIX_INTF_CID_MASK_ADDR); + break; + + case RDI0: + mask = msm_camera_io_r(ispif->base + + ISPIF_RDI_INTF_CID_MASK_ADDR); + break; + + case RDI1: + mask = msm_camera_io_r(ispif->base + + ISPIF_RDI_1_INTF_CID_MASK_ADDR); + break; + + default: + break; + } + return mask; +} + +static void +msm_ispif_intf_cmd(uint8_t intfmask, uint8_t intf_cmd_mask) +{ + uint8_t vc = 0, val = 0; + uint8_t mask = intfmask, intfnum = 0; + uint32_t cid_mask = 0; + while (mask != 0) { + if (!(intfmask & (0x1 << intfnum))) { + mask >>= 1; + intfnum++; + continue; + } + + cid_mask = msm_ispif_get_cid_mask(intfnum); + vc = 0; + + while (cid_mask != 0) { + if ((cid_mask & 0xf) != 0x0) { + val = (intf_cmd_mask>>(vc*2)) & 0x3; + global_intf_cmd_mask |= + (0x3 << ((vc * 2) + (intfnum * 8))); + global_intf_cmd_mask &= ~((0x3 & ~val) + << ((vc * 2) + + (intfnum * 8))); + } + vc++; + cid_mask >>= 4; + } + mask >>= 1; + intfnum++; + } + msm_camera_io_w(global_intf_cmd_mask, + ispif->base + ISPIF_INTF_CMD_ADDR); +} + +static int msm_ispif_abort_intf_transfer(uint8_t intfmask) +{ + int rc = 0; + uint8_t intf_cmd_mask = 0xAA; + uint8_t intfnum = 0, mask = intfmask; + mutex_lock(&ispif->mutex); + msm_ispif_intf_cmd(intfmask, intf_cmd_mask); + while (mask != 0) { + if (intfmask & (0x1 << intfnum)) + global_intf_cmd_mask |= (0xFF << (intfnum * 8)); + mask >>= 1; + intfnum++; + } + mutex_unlock(&ispif->mutex); + return rc; +} + +static int msm_ispif_start_intf_transfer(uint8_t intfmask) +{ + uint8_t intf_cmd_mask = 0x55; + int rc = 0; + mutex_lock(&ispif->mutex); + rc = msm_ispif_intf_reset(intfmask); + msm_ispif_intf_cmd(intfmask, intf_cmd_mask); + mutex_unlock(&ispif->mutex); + return rc; +} + +static int msm_ispif_stop_intf_transfer(uint8_t intfmask) +{ + int rc = 0; + uint8_t intf_cmd_mask = 0x00; + uint8_t intfnum = 0, mask = intfmask; + mutex_lock(&ispif->mutex); + msm_ispif_intf_cmd(intfmask, intf_cmd_mask); + while (mask != 0) { + if (intfmask & (0x1 << intfnum)) { + switch (intfnum) { + case PIX0: + while ((msm_camera_io_r(ispif->base + + ISPIF_PIX_STATUS_ADDR) + & 0xf) != 0xf) { + CDBG("Wait for pix0 Idle\n"); + } + break; + + case RDI0: + while ((msm_camera_io_r(ispif->base + + ISPIF_RDI_STATUS_ADDR) + & 0xf) != 0xf) { + CDBG("Wait for rdi0 Idle\n"); + } + break; + + case RDI1: + while ((msm_camera_io_r(ispif->base + + ISPIF_RDI_1_STATUS_ADDR) + & 0xf) != 0xf) { + CDBG("Wait for rdi1 Idle\n"); + } + break; + + default: + break; + } + global_intf_cmd_mask |= (0xFF << (intfnum * 8)); + } + mask >>= 1; + intfnum++; + } + mutex_unlock(&ispif->mutex); + return rc; +} + +static int msm_ispif_subdev_video_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ispif_device *ispif = + (struct ispif_device *)v4l2_get_subdevdata(sd); + int32_t cmd = enable & ((1<> ISPIF_S_STREAM_SHIFT; + int rc = -EINVAL; + + BUG_ON(!ispif); + switch (cmd) { + case ISPIF_ON_FRAME_BOUNDARY: + rc = msm_ispif_start_intf_transfer(intf); + break; + case ISPIF_OFF_FRAME_BOUNDARY: + rc = msm_ispif_stop_intf_transfer(intf); + break; + case ISPIF_OFF_IMMEDIATELY: + rc = msm_ispif_abort_intf_transfer(intf); + break; + default: + break; + } + return rc; +} + +static void ispif_do_tasklet(unsigned long data) +{ + unsigned long flags; + + struct ispif_isr_queue_cmd *qcmd = NULL; + CDBG("=== ispif_do_tasklet start ===\n"); + + while (atomic_read(&ispif_irq_cnt)) { + spin_lock_irqsave(&ispif_tasklet_lock, flags); + qcmd = list_first_entry(&ispif_tasklet_q, + struct ispif_isr_queue_cmd, list); + atomic_sub(1, &ispif_irq_cnt); + + if (!qcmd) { + spin_unlock_irqrestore(&ispif_tasklet_lock, + flags); + return; + } + list_del(&qcmd->list); + spin_unlock_irqrestore(&ispif_tasklet_lock, + flags); + if (qcmd->ispifInterruptStatus0 & + ISPIF_IRQ_STATUS_RDI_SOF_MASK) { + CDBG("ispif rdi irq status\n"); + } + if (qcmd->ispifInterruptStatus1 & + ISPIF_IRQ_STATUS_RDI_SOF_MASK) { + CDBG("ispif rdi1 irq status\n"); + } + kfree(qcmd); + } + CDBG("=== ispif_do_tasklet end ===\n"); +} + +DECLARE_TASKLET(ispif_tasklet, ispif_do_tasklet, 0); + +static void ispif_process_irq(struct ispif_irq_status *out) +{ + unsigned long flags; + struct ispif_isr_queue_cmd *qcmd; + + CDBG("ispif_process_irq\n"); + qcmd = kzalloc(sizeof(struct ispif_isr_queue_cmd), + GFP_ATOMIC); + if (!qcmd) { + pr_err("ispif_process_irq: qcmd malloc failed!\n"); + return; + } + qcmd->ispifInterruptStatus0 = out->ispifIrqStatus0; + qcmd->ispifInterruptStatus1 = out->ispifIrqStatus1; + + spin_lock_irqsave(&ispif_tasklet_lock, flags); + list_add_tail(&qcmd->list, &ispif_tasklet_q); + + atomic_add(1, &ispif_irq_cnt); + spin_unlock_irqrestore(&ispif_tasklet_lock, flags); + tasklet_schedule(&ispif_tasklet); + return; +} + +static inline void msm_ispif_read_irq_status(struct ispif_irq_status *out) +{ + out->ispifIrqStatus0 = msm_camera_io_r(ispif->base + + ISPIF_IRQ_STATUS_ADDR); + out->ispifIrqStatus1 = msm_camera_io_r(ispif->base + + ISPIF_IRQ_STATUS_1_ADDR); + msm_camera_io_w(out->ispifIrqStatus0, + ispif->base + ISPIF_IRQ_CLEAR_ADDR); + msm_camera_io_w(out->ispifIrqStatus1, + ispif->base + ISPIF_IRQ_CLEAR_1_ADDR); + + CDBG("ispif->irq: Irq_status0 = 0x%x\n", + out->ispifIrqStatus0); + if (out->ispifIrqStatus0 & ISPIF_IRQ_STATUS_MASK) { + if (out->ispifIrqStatus0 & (0x1 << RESET_DONE_IRQ)) + complete(&ispif->reset_complete); + if (out->ispifIrqStatus0 & (0x1 << PIX_INTF_0_OVERFLOW_IRQ)) + pr_err("%s: pix intf 0 overflow.\n", __func__); + if (out->ispifIrqStatus0 & (0x1 << RAW_INTF_0_OVERFLOW_IRQ)) + pr_err("%s: rdi intf 0 overflow.\n", __func__); + if ((out->ispifIrqStatus0 & ISPIF_IRQ_STATUS_RDI_SOF_MASK) || + (out->ispifIrqStatus1 & + ISPIF_IRQ_STATUS_RDI_SOF_MASK)) { + ispif_process_irq(out); + } + } + msm_camera_io_w(ISPIF_IRQ_GLOBAL_CLEAR_CMD, ispif->base + + ISPIF_IRQ_GLOBAL_CLEAR_CMD_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; +} + +static struct msm_cam_clk_info ispif_clk_info[] = { + {"csi_pix_clk", 0}, + {"csi_rdi_clk", 0}, + {"csi_pix1_clk", 0}, + {"csi_rdi1_clk", 0}, + {"csi_rdi2_clk", 0}, +}; + +static int msm_ispif_init(const uint32_t *csid_version) +{ + int rc = 0; + spin_lock_init(&ispif_tasklet_lock); + INIT_LIST_HEAD(&ispif_tasklet_q); + rc = request_irq(ispif->irq->start, msm_io_ispif_irq, + IRQF_TRIGGER_RISING, "ispif", 0); + + global_intf_cmd_mask = 0xFFFFFFFF; + init_completion(&ispif->reset_complete); + + ispif->csid_version = *csid_version; + if (ispif->csid_version == CSID_VERSION_V2) { + rc = msm_cam_clk_enable(&ispif->pdev->dev, ispif_clk_info, + ispif->ispif_clk, ARRAY_SIZE(ispif_clk_info), 1); + if (rc < 0) + return rc; + } else { + rc = msm_cam_clk_enable(&ispif->pdev->dev, ispif_clk_info, + ispif->ispif_clk, 2, 1); + if (rc < 0) + return rc; + } + + rc = msm_ispif_reset(); + return rc; +} + +static void msm_ispif_release(struct v4l2_subdev *sd) +{ + struct ispif_device *ispif = + (struct ispif_device *)v4l2_get_subdevdata(sd); + + CDBG("%s, free_irq\n", __func__); + free_irq(ispif->irq->start, 0); + tasklet_kill(&ispif_tasklet); + + if (ispif->csid_version == CSID_VERSION_V2) + msm_cam_clk_enable(&ispif->pdev->dev, ispif_clk_info, + ispif->ispif_clk, ARRAY_SIZE(ispif_clk_info), 0); + else + msm_cam_clk_enable(&ispif->pdev->dev, ispif_clk_info, + ispif->ispif_clk, 2, 0); +} + +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_camera_io_r(ispif->base + + ISPIF_PIX_INTF_CID_MASK_ADDR); + break; + + case RDI0: + data = msm_camera_io_r(ispif->base + + ISPIF_RDI_INTF_CID_MASK_ADDR); + break; + + case RDI1: + data = msm_camera_io_r(ispif->base + + 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; + } +} + +static long msm_ispif_subdev_ioctl(struct v4l2_subdev *sd, unsigned int cmd, + void *arg) +{ + switch (cmd) { + case VIDIOC_MSM_ISPIF_CFG: + return msm_ispif_config((struct msm_ispif_params_list *)arg); + case VIDIOC_MSM_ISPIF_INIT: + return msm_ispif_init((uint32_t *)arg); + case VIDIOC_MSM_ISPIF_RELEASE: + msm_ispif_release(sd); + default: + return -ENOIOCTLCMD; + } +} + +static struct v4l2_subdev_core_ops msm_ispif_subdev_core_ops = { + .g_chip_ident = &msm_ispif_subdev_g_chip_ident, + .ioctl = &msm_ispif_subdev_ioctl, +}; + +static struct v4l2_subdev_video_ops msm_ispif_subdev_video_ops = { + .s_stream = &msm_ispif_subdev_video_s_stream, +}; + +static const struct v4l2_subdev_ops msm_ispif_subdev_ops = { + .core = &msm_ispif_subdev_core_ops, + .video = &msm_ispif_subdev_video_ops, +}; + +static const struct v4l2_subdev_internal_ops msm_ispif_internal_ops; + +static int __devinit ispif_probe(struct platform_device *pdev) +{ + int rc = 0; + CDBG("%s\n", __func__); + ispif = kzalloc(sizeof(struct ispif_device), GFP_KERNEL); + if (!ispif) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&ispif->subdev, &msm_ispif_subdev_ops); + ispif->subdev.internal_ops = &msm_ispif_internal_ops; + ispif->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(ispif->subdev.name, + ARRAY_SIZE(ispif->subdev.name), "msm_ispif"); + v4l2_set_subdevdata(&ispif->subdev, ispif); + platform_set_drvdata(pdev, &ispif->subdev); + snprintf(ispif->subdev.name, sizeof(ispif->subdev.name), + "ispif"); + mutex_init(&ispif->mutex); + + ispif->mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ispif"); + if (!ispif->mem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto ispif_no_resource; + } + ispif->irq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "ispif"); + if (!ispif->irq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto ispif_no_resource; + } + ispif->io = request_mem_region(ispif->mem->start, + resource_size(ispif->mem), pdev->name); + if (!ispif->io) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto ispif_no_resource; + } + ispif->base = ioremap(ispif->mem->start, + resource_size(ispif->mem)); + if (!ispif->base) { + rc = -ENOMEM; + goto ispif_no_mem; + } + + ispif->pdev = pdev; + msm_cam_register_subdev_node(&ispif->subdev, ISPIF_DEV, pdev->id); + return 0; + +ispif_no_mem: + release_mem_region(ispif->mem->start, + resource_size(ispif->mem)); +ispif_no_resource: + mutex_destroy(&ispif->mutex); + kfree(ispif); + return rc; +} + +static struct platform_driver ispif_driver = { + .probe = ispif_probe, + .driver = { + .name = MSM_ISPIF_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_ispif_init_module(void) +{ + return platform_driver_register(&ispif_driver); +} + +static void __exit msm_ispif_exit_module(void) +{ + platform_driver_unregister(&ispif_driver); +} + +module_init(msm_ispif_init_module); +module_exit(msm_ispif_exit_module); +MODULE_DESCRIPTION("MSM ISP Interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/csi/msm_ispif.h b/drivers/media/video/msm/csi/msm_ispif.h new file mode 100644 index 0000000000000000000000000000000000000000..8f1dd121b09f25a11e6bd7bc5637c9aa4f8e0b2e --- /dev/null +++ b/drivers/media/video/msm/csi/msm_ispif.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +struct ispif_irq_status { + uint32_t ispifIrqStatus0; + uint32_t ispifIrqStatus1; +}; + +struct ispif_device { + struct platform_device *pdev; + struct v4l2_subdev subdev; + struct resource *mem; + struct resource *irq; + struct resource *io; + void __iomem *base; + struct mutex mutex; + uint8_t start_ack_pending; + struct completion reset_complete; + uint32_t csid_version; + struct clk *ispif_clk[5]; +}; + +struct ispif_isr_queue_cmd { + struct list_head list; + uint32_t ispifInterruptStatus0; + uint32_t ispifInterruptStatus1; +}; + +#define VIDIOC_MSM_ISPIF_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 1, struct msm_ispif_params) + +#define VIDIOC_MSM_ISPIF_INIT \ + _IO('V', BASE_VIDIOC_PRIVATE + 2) + +#define VIDIOC_MSM_ISPIF_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 3, struct v4l2_subdev*) + +#define ISPIF_STREAM(intf, action) (((intf)< +#include "msm_camera_eeprom.h" +#include "msm_camera_i2c.h" + +DEFINE_MUTEX(imx074_eeprom_mutex); +static struct msm_eeprom_ctrl_t imx074_eeprom_t; + +static const struct i2c_device_id imx074_eeprom_i2c_id[] = { + {"imx074_eeprom", (kernel_ulong_t)&imx074_eeprom_t}, + { } +}; + +static struct i2c_driver imx074_eeprom_i2c_driver = { + .id_table = imx074_eeprom_i2c_id, + .probe = msm_eeprom_i2c_probe, + .remove = __exit_p(imx074_eeprom_i2c_remove), + .driver = { + .name = "imx074_eeprom", + }, +}; + +static int __init imx074_eeprom_i2c_add_driver(void) +{ + int rc = 0; + rc = i2c_add_driver(imx074_eeprom_t.i2c_driver); + return rc; +} + +static struct v4l2_subdev_core_ops imx074_eeprom_subdev_core_ops = { + .ioctl = msm_eeprom_subdev_ioctl, +}; + +static struct v4l2_subdev_ops imx074_eeprom_subdev_ops = { + .core = &imx074_eeprom_subdev_core_ops, +}; + +uint8_t imx074_wbcalib_data[6]; +struct msm_calib_wb imx074_wb_data; + +static struct msm_camera_eeprom_info_t imx074_calib_supp_info = { + {FALSE, 0, 0, 1}, + {TRUE, 6, 0, 1024}, + {FALSE, 0, 0, 1}, + {FALSE, 0, 0, 1}, +}; + +static struct msm_camera_eeprom_read_t imx074_eeprom_read_tbl[] = { + {0x10, &imx074_wbcalib_data[0], 6, 0}, +}; + + +static struct msm_camera_eeprom_data_t imx074_eeprom_data_tbl[] = { + {&imx074_wb_data, sizeof(struct msm_calib_wb)}, +}; + +static void imx074_format_wbdata(void) +{ + imx074_wb_data.r_over_g = (uint16_t)(imx074_wbcalib_data[0] << 8) | + imx074_wbcalib_data[1]; + imx074_wb_data.b_over_g = (uint16_t)(imx074_wbcalib_data[2] << 8) | + imx074_wbcalib_data[3]; + imx074_wb_data.gr_over_gb = (uint16_t)(imx074_wbcalib_data[4] << 8) | + imx074_wbcalib_data[5]; +} + +void imx074_format_calibrationdata(void) +{ + imx074_format_wbdata(); +} +static struct msm_eeprom_ctrl_t imx074_eeprom_t = { + .i2c_driver = &imx074_eeprom_i2c_driver, + .i2c_addr = 0xA4, + .eeprom_v4l2_subdev_ops = &imx074_eeprom_subdev_ops, + + .i2c_client = { + .addr_type = MSM_CAMERA_I2C_BYTE_ADDR, + }, + + .eeprom_mutex = &imx074_eeprom_mutex, + + .func_tbl = { + .eeprom_init = NULL, + .eeprom_release = NULL, + .eeprom_get_info = msm_camera_eeprom_get_info, + .eeprom_get_data = msm_camera_eeprom_get_data, + .eeprom_set_dev_addr = NULL, + .eeprom_format_data = imx074_format_calibrationdata, + }, + .info = &imx074_calib_supp_info, + .info_size = sizeof(struct msm_camera_eeprom_info_t), + .read_tbl = imx074_eeprom_read_tbl, + .read_tbl_size = ARRAY_SIZE(imx074_eeprom_read_tbl), + .data_tbl = imx074_eeprom_data_tbl, + .data_tbl_size = ARRAY_SIZE(imx074_eeprom_data_tbl), +}; + +subsys_initcall(imx074_eeprom_i2c_add_driver); +MODULE_DESCRIPTION("IMX074 EEPROM"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/eeprom/imx091_eeprom.c b/drivers/media/video/msm/eeprom/imx091_eeprom.c new file mode 100644 index 0000000000000000000000000000000000000000..68a23613d82e51929e17c678a1181a1f85f08219 --- /dev/null +++ b/drivers/media/video/msm/eeprom/imx091_eeprom.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_camera_eeprom.h" +#include "msm_camera_i2c.h" + +DEFINE_MUTEX(imx091_eeprom_mutex); +static struct msm_eeprom_ctrl_t imx091_eeprom_t; + +static const struct i2c_device_id imx091_eeprom_i2c_id[] = { + {"imx091_eeprom", (kernel_ulong_t)&imx091_eeprom_t}, + { } +}; + +static struct i2c_driver imx091_eeprom_i2c_driver = { + .id_table = imx091_eeprom_i2c_id, + .probe = msm_eeprom_i2c_probe, + .remove = __exit_p(imx091_eeprom_i2c_remove), + .driver = { + .name = "imx091_eeprom", + }, +}; + +static int __init imx091_eeprom_i2c_add_driver(void) +{ + int rc = 0; + rc = i2c_add_driver(imx091_eeprom_t.i2c_driver); + return rc; +} + +static struct v4l2_subdev_core_ops imx091_eeprom_subdev_core_ops = { + .ioctl = msm_eeprom_subdev_ioctl, +}; + +static struct v4l2_subdev_ops imx091_eeprom_subdev_ops = { + .core = &imx091_eeprom_subdev_core_ops, +}; + +uint8_t imx091_wbcalib_data[6]; +uint8_t imx091_afcalib_data[6]; +struct msm_calib_wb imx091_wb_data; +struct msm_calib_af imx091_af_data; + +static struct msm_camera_eeprom_info_t imx091_calib_supp_info = { + {TRUE, 6, 1, 1}, + {TRUE, 6, 0, 32768}, + {FALSE, 0, 0, 1}, + {FALSE, 0, 0, 1}, +}; + +static struct msm_camera_eeprom_read_t imx091_eeprom_read_tbl[] = { + {0x05, &imx091_wbcalib_data[0], 6, 0}, + {0x0B, &imx091_afcalib_data[0], 6, 0}, +}; + + +static struct msm_camera_eeprom_data_t imx091_eeprom_data_tbl[] = { + {&imx091_wb_data, sizeof(struct msm_calib_wb)}, + {&imx091_af_data, sizeof(struct msm_calib_af)}, +}; + +static void imx091_format_wbdata(void) +{ + imx091_wb_data.r_over_g = (uint16_t)(imx091_wbcalib_data[1] << 8) | + (imx091_wbcalib_data[0] - 0x32); + imx091_wb_data.b_over_g = (uint16_t)(imx091_wbcalib_data[3] << 8) | + (imx091_wbcalib_data[2] - 0x32); + imx091_wb_data.gr_over_gb = (uint16_t)(imx091_wbcalib_data[5] << 8) | + (imx091_wbcalib_data[4] - 0x32); +} + +static void imx091_format_afdata(void) +{ + imx091_af_data.inf_dac = (uint16_t)(imx091_afcalib_data[1] << 8) | + imx091_afcalib_data[0]; + imx091_af_data.macro_dac = (uint16_t)(imx091_afcalib_data[3] << 8) | + imx091_afcalib_data[2]; + imx091_af_data.start_dac = (uint16_t)(imx091_afcalib_data[5] << 8) | + imx091_afcalib_data[4]; +} + +void imx091_format_calibrationdata(void) +{ + imx091_format_wbdata(); + imx091_format_afdata(); +} +static struct msm_eeprom_ctrl_t imx091_eeprom_t = { + .i2c_driver = &imx091_eeprom_i2c_driver, + .i2c_addr = 0xA1, + .eeprom_v4l2_subdev_ops = &imx091_eeprom_subdev_ops, + + .i2c_client = { + .addr_type = MSM_CAMERA_I2C_BYTE_ADDR, + }, + + .eeprom_mutex = &imx091_eeprom_mutex, + + .func_tbl = { + .eeprom_init = NULL, + .eeprom_release = NULL, + .eeprom_get_info = msm_camera_eeprom_get_info, + .eeprom_get_data = msm_camera_eeprom_get_data, + .eeprom_set_dev_addr = NULL, + .eeprom_format_data = imx091_format_calibrationdata, + }, + .info = &imx091_calib_supp_info, + .info_size = sizeof(struct msm_camera_eeprom_info_t), + .read_tbl = imx091_eeprom_read_tbl, + .read_tbl_size = ARRAY_SIZE(imx091_eeprom_read_tbl), + .data_tbl = imx091_eeprom_data_tbl, + .data_tbl_size = ARRAY_SIZE(imx091_eeprom_data_tbl), +}; + +subsys_initcall(imx091_eeprom_i2c_add_driver); +MODULE_DESCRIPTION("imx091 EEPROM"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/eeprom/msm_camera_eeprom.c b/drivers/media/video/msm/eeprom/msm_camera_eeprom.c new file mode 100644 index 0000000000000000000000000000000000000000..96a6e04d35179207ccbab19dd764307503c95f77 --- /dev/null +++ b/drivers/media/video/msm/eeprom/msm_camera_eeprom.c @@ -0,0 +1,196 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_camera_eeprom.h" + +int32_t msm_camera_eeprom_read(struct msm_eeprom_ctrl_t *ectrl, + uint32_t reg_addr, void *data, uint32_t num_byte, + uint16_t convert_endian) +{ + int rc = 0; + if (ectrl->func_tbl.eeprom_set_dev_addr != NULL) + ectrl->func_tbl.eeprom_set_dev_addr(ectrl, ®_addr); + + if (!convert_endian) { + rc = msm_camera_i2c_read_seq( + &ectrl->i2c_client, reg_addr, data, num_byte); + } else { + unsigned char buf[num_byte]; + uint8_t *data_ptr = (uint8_t *) data; + int i; + rc = msm_camera_i2c_read_seq( + &ectrl->i2c_client, reg_addr, buf, num_byte); + for (i = 0; i < num_byte; i += 2) { + data_ptr[i] = buf[i+1]; + data_ptr[i+1] = buf[i]; + } + } + return rc; +} + +int32_t msm_camera_eeprom_read_tbl(struct msm_eeprom_ctrl_t *ectrl, + struct msm_camera_eeprom_read_t *read_tbl, uint16_t tbl_size) +{ + int i, rc = 0; + CDBG("%s: open\n", __func__); + if (read_tbl == NULL) + return rc; + + for (i = 0; i < tbl_size; i++) { + rc = msm_camera_eeprom_read + (ectrl, read_tbl[i].reg_addr, + read_tbl[i].dest_ptr, read_tbl[i].num_byte, + read_tbl[i].convert_endian); + if (rc < 0) { + pr_err("%s: read failed\n", __func__); + return rc; + } + } + CDBG("%s: done\n", __func__); + return rc; +} + +int32_t msm_camera_eeprom_get_info(struct msm_eeprom_ctrl_t *ectrl, + struct msm_camera_eeprom_info_t *einfo) +{ + int rc = 0; + CDBG("%s: open\n", __func__); + memcpy(einfo, ectrl->info, ectrl->info_size); + CDBG("%s: done =%d\n", __func__, rc); + return rc; +} + +int32_t msm_camera_eeprom_get_data(struct msm_eeprom_ctrl_t *ectrl, + struct msm_eeprom_data_t *edata) +{ + int rc = 0; + if (edata->index >= ectrl->data_tbl_size) + return -EFAULT; + if (copy_to_user(edata->eeprom_data, + ectrl->data_tbl[edata->index].data, + ectrl->data_tbl[edata->index].size)) + rc = -EFAULT; + return rc; +} + +int32_t msm_eeprom_config(struct msm_eeprom_ctrl_t *e_ctrl, + void __user *argp) +{ + struct msm_eeprom_cfg_data cdata; + int32_t rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct msm_eeprom_cfg_data))) + return -EFAULT; + mutex_lock(e_ctrl->eeprom_mutex); + + switch (cdata.cfgtype) { + case CFG_GET_EEPROM_INFO: + if (e_ctrl->func_tbl.eeprom_get_info == NULL) { + rc = -EFAULT; + break; + } + rc = e_ctrl->func_tbl.eeprom_get_info(e_ctrl, + &cdata.cfg.get_info); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct msm_eeprom_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_EEPROM_DATA: + if (e_ctrl->func_tbl.eeprom_get_data == NULL) { + rc = -EFAULT; + break; + } + rc = e_ctrl->func_tbl.eeprom_get_data(e_ctrl, + &cdata.cfg.get_data); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct msm_eeprom_cfg_data))) + rc = -EFAULT; + break; + default: + break; + } + mutex_unlock(e_ctrl->eeprom_mutex); + return rc; +} + +struct msm_eeprom_ctrl_t *get_ectrl(struct v4l2_subdev *sd) +{ + return container_of(sd, struct msm_eeprom_ctrl_t, sdev); +} + +long msm_eeprom_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct msm_eeprom_ctrl_t *e_ctrl = get_ectrl(sd); + void __user *argp = (void __user *)arg; + switch (cmd) { + case VIDIOC_MSM_EEPROM_CFG: + return msm_eeprom_config(e_ctrl, argp); + default: + return -ENOIOCTLCMD; + } +} + +int32_t msm_eeprom_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + struct msm_eeprom_ctrl_t *e_ctrl_t = NULL; + CDBG("%s called\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("i2c_check_functionality failed\n"); + goto probe_failure; + } + + e_ctrl_t = (struct msm_eeprom_ctrl_t *)(id->driver_data); + e_ctrl_t->i2c_client.client = client; + + if (e_ctrl_t->i2c_addr != 0) + e_ctrl_t->i2c_client.client->addr = e_ctrl_t->i2c_addr; + + CDBG("%s client = %x\n", __func__, (unsigned int) client); + + /* Assign name for sub device */ + snprintf(e_ctrl_t->sdev.name, sizeof(e_ctrl_t->sdev.name), + "%s", e_ctrl_t->i2c_driver->driver.name); + + if (e_ctrl_t->func_tbl.eeprom_init != NULL) { + rc = e_ctrl_t->func_tbl.eeprom_init(e_ctrl_t, + e_ctrl_t->i2c_client.client->adapter); + } + msm_camera_eeprom_read_tbl(e_ctrl_t, + e_ctrl_t->read_tbl, + e_ctrl_t->read_tbl_size); + + if (e_ctrl_t->func_tbl.eeprom_format_data != NULL) + e_ctrl_t->func_tbl.eeprom_format_data(); + + if (e_ctrl_t->func_tbl.eeprom_release != NULL) + rc = e_ctrl_t->func_tbl.eeprom_release(e_ctrl_t); + + + /* Initialize sub device */ + v4l2_i2c_subdev_init(&e_ctrl_t->sdev, + e_ctrl_t->i2c_client.client, + e_ctrl_t->eeprom_v4l2_subdev_ops); + CDBG("%s success resut=%d\n", __func__, rc); + return rc; + +probe_failure: + pr_err("%s failed! rc = %d\n", __func__, rc); + return rc; +} diff --git a/drivers/media/video/msm/eeprom/msm_camera_eeprom.h b/drivers/media/video/msm/eeprom/msm_camera_eeprom.h new file mode 100644 index 0000000000000000000000000000000000000000..830e5d817b76ef8867db56409b67f716b1b61a0a --- /dev/null +++ b/drivers/media/video/msm/eeprom/msm_camera_eeprom.h @@ -0,0 +1,82 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_CAMERA_EEPROM_H +#define MSM_CAMERA_EEPROM_H + +#include +#include +#include +#include "msm_camera_i2c.h" + +#define TRUE 1 +#define FALSE 0 + +struct msm_eeprom_ctrl_t; + +struct msm_camera_eeprom_fn_t { + int32_t (*eeprom_init) + (struct msm_eeprom_ctrl_t *ectrl, + struct i2c_adapter *adapter); + int32_t (*eeprom_release) + (struct msm_eeprom_ctrl_t *ectrl); + int32_t (*eeprom_get_info) + (struct msm_eeprom_ctrl_t *ectrl, + struct msm_camera_eeprom_info_t *einfo); + int32_t (*eeprom_get_data) + (struct msm_eeprom_ctrl_t *ectrl, + struct msm_eeprom_data_t *edata); + void (*eeprom_set_dev_addr) + (struct msm_eeprom_ctrl_t*, uint32_t*); + void (*eeprom_format_data) + (void); +}; + +struct msm_camera_eeprom_read_t { + uint32_t reg_addr; + void *dest_ptr; + uint32_t num_byte; + uint16_t convert_endian; +}; + +struct msm_camera_eeprom_data_t { + void *data; + uint16_t size; +}; + +struct msm_eeprom_ctrl_t { + struct msm_camera_i2c_client i2c_client; + uint16_t i2c_addr; + struct i2c_driver *i2c_driver; + struct mutex *eeprom_mutex; + struct v4l2_subdev sdev; + struct v4l2_subdev_ops *eeprom_v4l2_subdev_ops; + struct msm_camera_eeprom_fn_t func_tbl; + struct msm_camera_eeprom_info_t *info; + uint16_t info_size; + struct msm_camera_eeprom_read_t *read_tbl; + uint16_t read_tbl_size; + struct msm_camera_eeprom_data_t *data_tbl; + uint16_t data_tbl_size; +}; + +int32_t msm_camera_eeprom_get_data(struct msm_eeprom_ctrl_t *ectrl, + struct msm_eeprom_data_t *edata); +int32_t msm_camera_eeprom_get_info(struct msm_eeprom_ctrl_t *ectrl, + struct msm_camera_eeprom_info_t *einfo); +int32_t msm_eeprom_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id); +long msm_eeprom_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg); + +#define VIDIOC_MSM_EEPROM_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 12, void __user *) +#endif diff --git a/drivers/media/video/msm/flash.c b/drivers/media/video/msm/flash.c new file mode 100644 index 0000000000000000000000000000000000000000..7c4021dc0fb8ad0e759a5659a4d0be8b90f2e5e8 --- /dev/null +++ b/drivers/media/video/msm/flash.c @@ -0,0 +1,777 @@ + +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 i2c_client *sx150x_client; +struct timer_list timer_flash; +static struct msm_camera_sensor_info *sensor_data; +enum msm_cam_flash_stat{ + MSM_CAM_FLASH_OFF, + MSM_CAM_FLASH_ON, +}; + +static struct i2c_client *sc628a_client; + +static int32_t flash_i2c_txdata(struct i2c_client *client, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = client->addr >> 1, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(client->adapter, msg, 1) < 0) { + CDBG("flash_i2c_txdata faild 0x%x\n", client->addr >> 1); + return -EIO; + } + + return 0; +} + +static int32_t flash_i2c_write_b(struct i2c_client *client, + uint8_t baddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[2]; + if (!client) + return -ENOTSUPP; + + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + + rc = flash_i2c_txdata(client, buf, 2); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + baddr, bdata); + } + usleep_range(4000, 5000); + + return rc; +} + +static const struct i2c_device_id sc628a_i2c_id[] = { + {"sc628a", 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_client = client; + + CDBG("sc628a_probe success 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", + }, +}; + +static struct i2c_client *tps61310_client; + +static const struct i2c_device_id tps61310_i2c_id[] = { + {"tps61310", 0}, + { } +}; + +static int tps61310_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("%s enter\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("i2c_check_functionality failed\n"); + goto probe_failure; + } + + tps61310_client = client; + + rc = flash_i2c_write_b(tps61310_client, 0x01, 0x00); + if (rc < 0) { + tps61310_client = NULL; + goto probe_failure; + } + + CDBG("%s success! rc = %d\n", __func__, rc); + return 0; + +probe_failure: + pr_err("%s failed! rc = %d\n", __func__, rc); + return rc; +} + +static struct i2c_driver tps61310_i2c_driver = { + .id_table = tps61310_i2c_id, + .probe = tps61310_i2c_probe, + .remove = __exit_p(tps61310_i2c_remove), + .driver = { + .name = "tps61310", + }, +}; + +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; + case MSM_CAMERA_LED_INIT: + case MSM_CAMERA_LED_RELEASE: + break; + + default: + rc = -EFAULT; + break; + } + CDBG("msm_camera_flash_led_pmic8058: return %d\n", rc); +#endif /* CONFIG_LEDS_PMIC8058 */ + return rc; +} + +int msm_camera_flash_led( + struct msm_camera_sensor_flash_external *external, + unsigned led_state) +{ + int rc = 0; + + CDBG("msm_camera_flash_led: %d\n", led_state); + switch (led_state) { + case MSM_CAMERA_LED_INIT: + rc = gpio_request(external->led_en, "sgm3141"); + CDBG("MSM_CAMERA_LED_INIT: gpio_req: %d %d\n", + external->led_en, rc); + if (!rc) + gpio_direction_output(external->led_en, 0); + else + return 0; + + rc = gpio_request(external->led_flash_en, "sgm3141"); + CDBG("MSM_CAMERA_LED_INIT: gpio_req: %d %d\n", + external->led_flash_en, rc); + if (!rc) + gpio_direction_output(external->led_flash_en, 0); + + break; + + case MSM_CAMERA_LED_RELEASE: + CDBG("MSM_CAMERA_LED_RELEASE\n"); + gpio_set_value_cansleep(external->led_en, 0); + gpio_free(external->led_en); + gpio_set_value_cansleep(external->led_flash_en, 0); + gpio_free(external->led_flash_en); + break; + + case MSM_CAMERA_LED_OFF: + CDBG("MSM_CAMERA_LED_OFF\n"); + gpio_set_value_cansleep(external->led_en, 0); + gpio_set_value_cansleep(external->led_flash_en, 0); + break; + + case MSM_CAMERA_LED_LOW: + CDBG("MSM_CAMERA_LED_LOW\n"); + gpio_set_value_cansleep(external->led_en, 1); + gpio_set_value_cansleep(external->led_flash_en, 1); + break; + + case MSM_CAMERA_LED_HIGH: + CDBG("MSM_CAMERA_LED_HIGH\n"); + gpio_set_value_cansleep(external->led_en, 1); + gpio_set_value_cansleep(external->led_flash_en, 1); + break; + + default: + rc = -EFAULT; + break; + } + + return rc; +} + +int msm_camera_flash_external( + struct msm_camera_sensor_flash_external *external, + unsigned led_state) +{ + int rc = 0; + + switch (led_state) { + + case MSM_CAMERA_LED_INIT: + if (external->flash_id == MAM_CAMERA_EXT_LED_FLASH_SC628A) { + if (!sc628a_client) { + rc = i2c_add_driver(&sc628a_i2c_driver); + if (rc < 0 || sc628a_client == NULL) { + pr_err("sc628a_i2c_driver add failed\n"); + rc = -ENOTSUPP; + return rc; + } + } + } else if (external->flash_id == + MAM_CAMERA_EXT_LED_FLASH_TPS61310) { + if (!tps61310_client) { + rc = i2c_add_driver(&tps61310_i2c_driver); + if (rc < 0 || tps61310_client == NULL) { + pr_err("tps61310_i2c_driver add failed\n"); + rc = -ENOTSUPP; + return rc; + } + } + } else { + pr_err("Flash id not supported\n"); + rc = -ENOTSUPP; + return rc; + } + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + if (external->expander_info && !sx150x_client) { + struct i2c_adapter *adapter = + i2c_get_adapter(external->expander_info->bus_id); + if (adapter) + sx150x_client = i2c_new_device(adapter, + external->expander_info->board_info); + if (!sx150x_client || !adapter) { + pr_err("sx150x_client is not available\n"); + rc = -ENOTSUPP; + if (sc628a_client) { + i2c_del_driver(&sc628a_i2c_driver); + sc628a_client = NULL; + } + if (tps61310_client) { + i2c_del_driver(&tps61310_i2c_driver); + tps61310_client = NULL; + } + return rc; + } + i2c_put_adapter(adapter); + } +#endif + if (sc628a_client) + rc = gpio_request(external->led_en, "sc628a"); + if (tps61310_client) + rc = gpio_request(external->led_en, "tps61310"); + + if (!rc) { + gpio_direction_output(external->led_en, 0); + } else { + goto error; + } + + if (sc628a_client) + rc = gpio_request(external->led_flash_en, "sc628a"); + if (tps61310_client) + rc = gpio_request(external->led_flash_en, "tps61310"); + + if (!rc) { + gpio_direction_output(external->led_flash_en, 0); + break; + } + + gpio_set_value_cansleep(external->led_en, 0); + gpio_free(external->led_en); +error: + pr_err("%s gpio request failed\n", __func__); + if (sc628a_client) { + i2c_del_driver(&sc628a_i2c_driver); + sc628a_client = NULL; + } + if (tps61310_client) { + i2c_del_driver(&tps61310_i2c_driver); + tps61310_client = NULL; + } + break; + + case MSM_CAMERA_LED_RELEASE: + if (sc628a_client || tps61310_client) { + gpio_set_value_cansleep(external->led_en, 0); + gpio_free(external->led_en); + gpio_set_value_cansleep(external->led_flash_en, 0); + gpio_free(external->led_flash_en); + if (sc628a_client) { + i2c_del_driver(&sc628a_i2c_driver); + sc628a_client = NULL; + } + if (tps61310_client) { + i2c_del_driver(&tps61310_i2c_driver); + tps61310_client = NULL; + } + } +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + if (external->expander_info && sx150x_client) { + i2c_unregister_device(sx150x_client); + sx150x_client = NULL; + } +#endif + break; + + case MSM_CAMERA_LED_OFF: + if (sc628a_client) + rc = flash_i2c_write_b(sc628a_client, 0x02, 0x00); + if (tps61310_client) + rc = flash_i2c_write_b(tps61310_client, 0x01, 0x00); + gpio_set_value_cansleep(external->led_en, 0); + gpio_set_value_cansleep(external->led_flash_en, 0); + break; + + case MSM_CAMERA_LED_LOW: + gpio_set_value_cansleep(external->led_en, 1); + gpio_set_value_cansleep(external->led_flash_en, 1); + usleep_range(2000, 3000); + if (sc628a_client) + rc = flash_i2c_write_b(sc628a_client, 0x02, 0x06); + if (tps61310_client) + rc = flash_i2c_write_b(tps61310_client, 0x01, 0x86); + break; + + case MSM_CAMERA_LED_HIGH: + gpio_set_value_cansleep(external->led_en, 1); + gpio_set_value_cansleep(external->led_flash_en, 1); + usleep_range(2000, 3000); + if (sc628a_client) + rc = flash_i2c_write_b(sc628a_client, 0x02, 0x49); + if (tps61310_client) + rc = flash_i2c_write_b(tps61310_client, 0x01, 0x8B); + break; + + default: + rc = -EFAULT; + break; + } + 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; + case MSM_CAMERA_LED_INIT: + case MSM_CAMERA_LED_RELEASE: + 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; + + case MSM_CAMERA_LED_INIT: + case MSM_CAMERA_LED_RELEASE: + 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; + + if (fdata->flash_type != MSM_CAMERA_FLASH_LED || + fdata->flash_src == NULL) + 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; + + case MSM_CAMERA_FLASH_SRC_EXT: + rc = msm_camera_flash_external( + &fdata->flash_src->_fsrc.ext_driver_src, + led_state); + break; + + case MSM_CAMERA_FLASH_SRC_LED1: + rc = msm_camera_flash_led( + &fdata->flash_src->_fsrc.ext_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; + sensor_data = sdata; + 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/gemini/Makefile b/drivers/media/video/msm/gemini/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..8a7cd93c378a5e6fd72dde9fdbbc42dc68c93776 --- /dev/null +++ b/drivers/media/video/msm/gemini/Makefile @@ -0,0 +1,3 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) +EXTRA_CFLAGS += -Idrivers/media/video/msm +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/gemini/msm_gemini_common.h b/drivers/media/video/msm/gemini/msm_gemini_common.h new file mode 100644 index 0000000000000000000000000000000000000000..0ddedc5013f518652be67c721ebb08923a7d72bf --- /dev/null +++ b/drivers/media/video/msm/gemini/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/gemini/msm_gemini_core.c b/drivers/media/video/msm/gemini/msm_gemini_core.c new file mode 100644 index 0000000000000000000000000000000000000000..45d3937b4f1993cc7ba8cfb5de82ef3f84dddf7d --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_core.c @@ -0,0 +1,250 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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[i].handle); + 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/gemini/msm_gemini_core.h b/drivers/media/video/msm/gemini/msm_gemini_core.h new file mode 100644 index 0000000000000000000000000000000000000000..f240505fc2d778e1daeaebfd4c62705d86b5a60e --- /dev/null +++ b/drivers/media/video/msm/gemini/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/gemini/msm_gemini_dev.c b/drivers/media/video/msm/gemini/msm_gemini_dev.c new file mode 100644 index 0000000000000000000000000000000000000000..01d45edc403a0a74afa013dd41bd8cf612360bb3 --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_dev.c @@ -0,0 +1,266 @@ +/* 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 "msm.h" +#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("%s:%d] cmd=%d pgmn_dev=0x%x arg=0x%x\n", __func__, + __LINE__, _IOC_NR(cmd), (uint32_t)pgmn_dev, (uint32_t)arg); + + 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; +struct msm_gemini_device *msm_gemini_device_p; + +int msm_gemini_subdev_init(struct v4l2_subdev *gemini_sd) +{ + int rc; + struct msm_gemini_device *pgmn_dev = + (struct msm_gemini_device *)gemini_sd->host_priv; + + GMN_DBG("%s:%d: gemini_sd=0x%x pgmn_dev=0x%x\n", + __func__, __LINE__, (uint32_t)gemini_sd, (uint32_t)pgmn_dev); + rc = __msm_gemini_open(pgmn_dev); + GMN_DBG("%s:%d: rc=%d\n", + __func__, __LINE__, rc); + return rc; +} + +static long msm_gemini_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + long rc; + struct msm_gemini_device *pgmn_dev = + (struct msm_gemini_device *)sd->host_priv; + + GMN_DBG("%s: cmd=%d\n", __func__, cmd); + + GMN_DBG("%s: pgmn_dev 0x%x", __func__, (uint32_t)pgmn_dev); + + GMN_DBG("%s: Calling __msm_gemini_ioctl\n", __func__); + + rc = __msm_gemini_ioctl(pgmn_dev, cmd, (unsigned long)arg); + pr_debug("%s: X\n", __func__); + return rc; +} + +void msm_gemini_subdev_release(struct v4l2_subdev *gemini_sd) +{ + int rc; + struct msm_gemini_device *pgmn_dev = + (struct msm_gemini_device *)gemini_sd->host_priv; + GMN_DBG("%s:pgmn_dev=0x%x", __func__, (uint32_t)pgmn_dev); + rc = __msm_gemini_release(pgmn_dev); + GMN_DBG("%s:rc=%d", __func__, rc); +} + +static const struct v4l2_subdev_core_ops msm_gemini_subdev_core_ops = { + .ioctl = msm_gemini_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_gemini_subdev_ops = { + .core = &msm_gemini_subdev_core_ops, +}; + +static int msm_gemini_init(struct platform_device *pdev) +{ + int rc = -1; + struct device *dev; + + GMN_DBG("%s:\n", __func__); + msm_gemini_device_p = __msm_gemini_init(pdev); + if (msm_gemini_device_p == NULL) { + GMN_PR_ERR("%s: initialization failed\n", __func__); + goto fail; + } + + v4l2_subdev_init(&msm_gemini_device_p->subdev, &msm_gemini_subdev_ops); + v4l2_set_subdev_hostdata(&msm_gemini_device_p->subdev, + msm_gemini_device_p); + pr_debug("%s: msm_gemini_device_p 0x%x", __func__, + (uint32_t)msm_gemini_device_p); + GMN_DBG("%s:gemini: platform_set_drvdata\n", __func__); + platform_set_drvdata(pdev, &msm_gemini_device_p->subdev); + + 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) +{ + return msm_gemini_init(pdev); +} + +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_DRV_NAME, + .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/gemini/msm_gemini_hw.c b/drivers/media/video/msm/gemini/msm_gemini_hw.c new file mode 100644 index 0000000000000000000000000000000000000000..ba8f353e58fbe3765d160ddcf00df80b6cfe5c27 --- /dev/null +++ b/drivers/media/video/msm/gemini/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/gemini/msm_gemini_hw.h b/drivers/media/video/msm/gemini/msm_gemini_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..233f082a31961ebbf416a53e637fd673717a8de8 --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_hw.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" +#include +#include + +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 ion_handle *handle; +}; + +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/gemini/msm_gemini_hw_reg.h b/drivers/media/video/msm/gemini/msm_gemini_hw_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..4bddfbba4a989c4159574ab55ffe7c3cd9bd5bcb --- /dev/null +++ b/drivers/media/video/msm/gemini/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/gemini/msm_gemini_platform.c b/drivers/media/video/msm/gemini/msm_gemini_platform.c new file mode 100644 index 0000000000000000000000000000000000000000..d7f6bd851e7fb2e6e77ad6eea36136178b0a1239 --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_platform.c @@ -0,0 +1,255 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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_sync.h" +#include "msm_gemini_common.h" +#include "msm_gemini_hw.h" + +/* AXI rate in KHz */ +#define MSM_SYSTEM_BUS_RATE 160000 +struct ion_client *gemini_client; + +void msm_gemini_platform_p2v(struct file *file, + struct ion_handle **ionhandle) +{ +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_iommu(gemini_client, *ionhandle, CAMERA_DOMAIN, GEN_POOL); + ion_free(gemini_client, *ionhandle); + *ionhandle = NULL; +#elif CONFIG_ANDROID_PMEM + put_pmem_file(file); +#endif +} + +uint32_t msm_gemini_platform_v2p(int fd, uint32_t len, struct file **file_p, + struct ion_handle **ionhandle) +{ + unsigned long paddr; + unsigned long size; + int rc; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + *ionhandle = ion_import_fd(gemini_client, fd); + if (IS_ERR_OR_NULL(*ionhandle)) + return 0; + + rc = ion_map_iommu(gemini_client, *ionhandle, CAMERA_DOMAIN, GEN_POOL, + SZ_4K, 0, &paddr, (unsigned long *)&size, UNCACHED, 0); +#elif 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); + goto error1; + } + + /* validate user input */ + if (len > size) { + GMN_PR_ERR("%s: invalid offset + len\n", __func__); + goto error1; + } + + return paddr; +error1: +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(gemini_client, *ionhandle); +#endif + return 0; +} + +static struct msm_cam_clk_info gemini_8x_clk_info[] = { + {"core_clk", 228571000}, + {"iface_clk", -1}, +}; + +static struct msm_cam_clk_info gemini_7x_clk_info[] = { + {"core_clk", 153600000}, + {"iface_clk", -1}, +}; + +static struct msm_cam_clk_info gemini_imem_clk_info[] = { + {"mem_clk", -1}, +}; + +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; + struct msm_gemini_device *pgmn_dev = + (struct msm_gemini_device *) context; + + 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; + } + + pgmn_dev->hw_version = GEMINI_8X60; + rc = msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_8x_clk_info, + pgmn_dev->gemini_clk, ARRAY_SIZE(gemini_8x_clk_info), 1); + if (rc < 0) { + pgmn_dev->hw_version = GEMINI_7X; + rc = msm_cam_clk_enable(&pgmn_dev->pdev->dev, + gemini_7x_clk_info, pgmn_dev->gemini_clk, + ARRAY_SIZE(gemini_7x_clk_info), 1); + if (rc < 0) { + GMN_PR_ERR("%s: clk failed rc = %d\n", __func__, rc); + goto fail2; + } + } else { + rc = msm_cam_clk_enable(&pgmn_dev->pdev->dev, + gemini_imem_clk_info, &pgmn_dev->gemini_clk[2], + ARRAY_SIZE(gemini_imem_clk_info), 1); + if (!rc) + pgmn_dev->hw_version = GEMINI_8960; + } + + if (pgmn_dev->hw_version != GEMINI_7X) { + if (pgmn_dev->gemini_fs == NULL) { + pgmn_dev->gemini_fs = + regulator_get(&pgmn_dev->pdev->dev, "vdd"); + if (IS_ERR(pgmn_dev->gemini_fs)) { + pr_err("%s: Regulator FS_ijpeg get failed %ld\n", + __func__, PTR_ERR(pgmn_dev->gemini_fs)); + pgmn_dev->gemini_fs = NULL; + goto gemini_fs_failed; + } else if (regulator_enable(pgmn_dev->gemini_fs)) { + pr_err("%s: Regulator FS_ijpeg enable failed\n", + __func__); + regulator_put(pgmn_dev->gemini_fs); + pgmn_dev->gemini_fs = NULL; + goto gemini_fs_failed; + } + } + } + + 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; + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + gemini_client = msm_ion_client_create(-1, "camera/gemini"); +#endif + GMN_DBG("%s:%d] success\n", __func__, __LINE__); + + return rc; + +fail3: + if (pgmn_dev->hw_version != GEMINI_7X) { + regulator_disable(pgmn_dev->gemini_fs); + regulator_put(pgmn_dev->gemini_fs); + pgmn_dev->gemini_fs = NULL; + } +gemini_fs_failed: + if (pgmn_dev->hw_version == GEMINI_8960) + msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_imem_clk_info, + &pgmn_dev->gemini_clk[2], ARRAY_SIZE(gemini_imem_clk_info), 0); + if (pgmn_dev->hw_version != GEMINI_7X) + msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_8x_clk_info, + pgmn_dev->gemini_clk, ARRAY_SIZE(gemini_8x_clk_info), 0); + else + msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_7x_clk_info, + pgmn_dev->gemini_clk, ARRAY_SIZE(gemini_7x_clk_info), 0); +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 = 0; + struct msm_gemini_device *pgmn_dev = + (struct msm_gemini_device *) context; + + free_irq(irq, context); + + if (pgmn_dev->hw_version != GEMINI_7X) { + regulator_disable(pgmn_dev->gemini_fs); + regulator_put(pgmn_dev->gemini_fs); + pgmn_dev->gemini_fs = NULL; + } + + if (pgmn_dev->hw_version == GEMINI_8960) + msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_imem_clk_info, + &pgmn_dev->gemini_clk[2], ARRAY_SIZE(gemini_imem_clk_info), 0); + if (pgmn_dev->hw_version != GEMINI_7X) + msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_8x_clk_info, + pgmn_dev->gemini_clk, ARRAY_SIZE(gemini_8x_clk_info), 0); + else + msm_cam_clk_enable(&pgmn_dev->pdev->dev, gemini_7x_clk_info, + pgmn_dev->gemini_clk, ARRAY_SIZE(gemini_7x_clk_info), 0); + + iounmap(base); + release_mem_region(mem->start, resource_size(mem)); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_client_destroy(gemini_client); +#endif + GMN_DBG("%s:%d] success\n", __func__, __LINE__); + return result; +} + diff --git a/drivers/media/video/msm/gemini/msm_gemini_platform.h b/drivers/media/video/msm/gemini/msm_gemini_platform.h new file mode 100644 index 0000000000000000000000000000000000000000..45421293d7155f0b3d88fa949230dbb3cf49f2cc --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_platform.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +void msm_gemini_platform_p2v(struct file *file, + struct ion_handle **ionhandle); +uint32_t msm_gemini_platform_v2p(int fd, uint32_t len, struct file **file, + struct ion_handle **ionhandle); + +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/gemini/msm_gemini_sync.c b/drivers/media/video/msm/gemini/msm_gemini_sync.c new file mode 100644 index 0000000000000000000000000000000000000000..fe7c99f05b8d29814e9979f853949d42391a1f99 --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_sync.c @@ -0,0 +1,850 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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, + &buf_p->handle); + 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, &buf_p->handle); + 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, &buf_p->handle); + 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; + if (pgmn_dev->op_mode == MSM_GEMINI_MODE_OFFLINE_ENCODE || + pgmn_dev->op_mode == MSM_GEMINI_MODE_OFFLINE_ROTATION) { + msm_gemini_platform_p2v(buf_p->file, &buf_p->handle); + } + 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); + + if (pgmn_dev->op_mode == MSM_GEMINI_MODE_REALTIME_ENCODE) { + buf_p->y_buffer_addr = buf_cmd.y_off; + } else { + buf_p->y_buffer_addr = msm_gemini_platform_v2p(buf_cmd.fd, + buf_cmd.y_len + buf_cmd.cbcr_len, &buf_p->file, + &buf_p->handle) + 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; + GMN_DBG("%s: y_addr=%x,y_len=%x,cbcr_addr=%x,cbcr_len=%x\n", __func__, + buf_p->y_buffer_addr, buf_p->y_len, buf_p->cbcr_buffer_addr, + buf_p->cbcr_len); + + 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/gemini/msm_gemini_sync.h b/drivers/media/video/msm/gemini/msm_gemini_sync.h new file mode 100644 index 0000000000000000000000000000000000000000..1c6726d1a1fec417db997fe45f1e4a5e784b74b5 --- /dev/null +++ b/drivers/media/video/msm/gemini/msm_gemini_sync.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 MSM_GEMINI_SYNC_H +#define MSM_GEMINI_SYNC_H + +#include +#include +#include +#include +#include +#include +#include "msm_gemini_core.h" + +#define GEMINI_7X 0x1 +#define GEMINI_8X60 (0x1 << 1) +#define GEMINI_8960 (0x1 << 2) + +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 clk *gemini_clk[3]; + struct regulator *gemini_fs; + uint32_t hw_version; + + 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; + + struct v4l2_subdev subdev; +}; + +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/imx072.c b/drivers/media/video/msm/imx072.c new file mode 100644 index 0000000000000000000000000000000000000000..7e3417824f87fbeb2eef1d93bfad4945a2fe6dd5 --- /dev/null +++ b/drivers/media/video/msm/imx072.c @@ -0,0 +1,1184 @@ +/* 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 gpio_req_fail; + + 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; + } + + return rc; +init_probe_fail: + pr_err(" imx072_probe_init_sensor fails\n"); + gpio_set_value_cansleep(data->sensor_reset, 0); + imx072_probe_init_done(data); + if (data->vcm_enable) { + int ret = gpio_request(data->vcm_pwd, "imx072_af"); + if (!ret) { + gpio_direction_output(data->vcm_pwd, 0); + msleep(20); + gpio_free(data->vcm_pwd); + } + } +gpio_req_fail: + 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); + if (imx072_ctrl->sensordata->vcm_enable) { + gpio_set_value_cansleep(imx072_ctrl->sensordata->vcm_pwd, 0); + gpio_free(imx072_ctrl->sensordata->vcm_pwd); + } + 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); + if (info->vcm_enable) { + rc = gpio_request(info->vcm_pwd, "imx072_af"); + if (!rc) { + gpio_direction_output(info->vcm_pwd, 0); + msleep(20); + gpio_free(info->vcm_pwd); + } else + return rc; + } + 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 0000000000000000000000000000000000000000..e3d279fe353e041a9b21bc72efbb32049e3ab29c --- /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 0000000000000000000000000000000000000000..ea7554815b77855c4fe75b88a6e7de4a410972ef --- /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 0000000000000000000000000000000000000000..636b40283ba689437bb53cc6b0b32f88f7e0348a --- /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 0000000000000000000000000000000000000000..8be0fb70d8685d449478fdc0e0da054ca246dda3 --- /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 0000000000000000000000000000000000000000..ccc9b2f10925a89efcd1bbec7db9ed8f3478bcab --- /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/io/Makefile b/drivers/media/video/msm/io/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..64df7fb14dcb78fb77fcdb9ebff13b34c59288b6 --- /dev/null +++ b/drivers/media/video/msm/io/Makefile @@ -0,0 +1,17 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) + +obj-$(CONFIG_MSM_CAMERA) += msm_camera_io_util.o +EXTRA_CFLAGS += -Idrivers/media/video/msm +ifeq ($(CONFIG_MSM_CAMERA_V4L2),y) + obj-$(CONFIG_MSM_CAMERA) += msm_camera_i2c.o msm_camera_i2c_mux.o + obj-$(CONFIG_ARCH_MSM7X27A) += msm_io_7x27a_v4l2.o + obj-$(CONFIG_ARCH_MSM8X60) += msm_io_vfe31_v4l2.o + obj-$(CONFIG_ARCH_MSM7X30) += msm_io_vfe31_v4l2.o +else + obj-$(CONFIG_ARCH_MSM7X27A) += msm_io_7x27a.o + obj-$(CONFIG_ARCH_MSM8X60) += msm_io_8x60.o + obj-$(CONFIG_ARCH_MSM7X30) += msm_io_vfe31.o +endif +obj-$(CONFIG_ARCH_MSM_ARM11) += msm_io7x.o +obj-$(CONFIG_ARCH_QSD8X50) += msm_io8x.o +obj-$(CONFIG_ARCH_MSM8960) += msm_io_8960.o diff --git a/drivers/media/video/msm/io/msm_camera_i2c.c b/drivers/media/video/msm/io/msm_camera_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..6f456378ebfc1ee5b467c08edd153c92adfe7cdc --- /dev/null +++ b/drivers/media/video/msm/io/msm_camera_i2c.c @@ -0,0 +1,429 @@ +/* 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_camera_i2c.h" + +int32_t msm_camera_i2c_rxdata(struct msm_camera_i2c_client *dev_client, + unsigned char *rxdata, int data_length) +{ + int32_t rc = 0; + uint16_t saddr = dev_client->client->addr >> 1; + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = dev_client->addr_type, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = data_length, + .buf = rxdata, + }, + }; + rc = i2c_transfer(dev_client->client->adapter, msgs, 2); + if (rc < 0) + S_I2C_DBG("msm_camera_i2c_rxdata failed 0x%x\n", saddr); + return rc; +} + +int32_t msm_camera_i2c_txdata(struct msm_camera_i2c_client *dev_client, + unsigned char *txdata, int length) +{ + int32_t rc = 0; + uint16_t saddr = dev_client->client->addr >> 1; + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + rc = i2c_transfer(dev_client->client->adapter, msg, 1); + if (rc < 0) + S_I2C_DBG("msm_camera_i2c_txdata faild 0x%x\n", saddr); + return 0; +} + +int32_t msm_camera_i2c_write(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t data, + enum msm_camera_i2c_data_type data_type) +{ + int32_t rc = -EFAULT; + unsigned char buf[client->addr_type+data_type]; + uint8_t len = 0; + if ((client->addr_type != MSM_CAMERA_I2C_BYTE_ADDR + && client->addr_type != MSM_CAMERA_I2C_WORD_ADDR) + || (data_type != MSM_CAMERA_I2C_BYTE_DATA + && data_type != MSM_CAMERA_I2C_WORD_DATA)) + return rc; + + S_I2C_DBG("%s reg addr = 0x%x data type: %d\n", + __func__, addr, data_type); + if (client->addr_type == MSM_CAMERA_I2C_BYTE_ADDR) { + buf[0] = addr; + S_I2C_DBG("%s byte %d: 0x%x\n", __func__, len, buf[len]); + len = 1; + } else if (client->addr_type == MSM_CAMERA_I2C_WORD_ADDR) { + buf[0] = addr >> BITS_PER_BYTE; + buf[1] = addr; + S_I2C_DBG("%s byte %d: 0x%x\n", __func__, len, buf[len]); + S_I2C_DBG("%s byte %d: 0x%x\n", __func__, len+1, buf[len+1]); + len = 2; + } + S_I2C_DBG("Data: 0x%x\n", data); + if (data_type == MSM_CAMERA_I2C_BYTE_DATA) { + buf[len] = data; + S_I2C_DBG("Byte %d: 0x%x\n", len, buf[len]); + len += 1; + } else if (data_type == MSM_CAMERA_I2C_WORD_DATA) { + buf[len] = data >> BITS_PER_BYTE; + buf[len+1] = data; + S_I2C_DBG("Byte %d: 0x%x\n", len, buf[len]); + S_I2C_DBG("Byte %d: 0x%x\n", len+1, buf[len+1]); + len += 2; + } + + rc = msm_camera_i2c_txdata(client, buf, len); + if (rc < 0) + S_I2C_DBG("%s fail\n", __func__); + return rc; +} + +int32_t msm_camera_i2c_write_seq(struct msm_camera_i2c_client *client, + uint16_t addr, uint8_t *data, uint16_t num_byte) +{ + int32_t rc = -EFAULT; + unsigned char buf[client->addr_type+num_byte]; + uint8_t len = 0, i = 0; + + if ((client->addr_type != MSM_CAMERA_I2C_BYTE_ADDR + && client->addr_type != MSM_CAMERA_I2C_WORD_ADDR) + || num_byte == 0) + return rc; + + S_I2C_DBG("%s reg addr = 0x%x num bytes: %d\n", + __func__, addr, num_byte); + if (client->addr_type == MSM_CAMERA_I2C_BYTE_ADDR) { + buf[0] = addr; + S_I2C_DBG("%s byte %d: 0x%x\n", __func__, len, buf[len]); + len = 1; + } else if (client->addr_type == MSM_CAMERA_I2C_WORD_ADDR) { + buf[0] = addr >> BITS_PER_BYTE; + buf[1] = addr; + S_I2C_DBG("%s byte %d: 0x%x\n", __func__, len, buf[len]); + S_I2C_DBG("%s byte %d: 0x%x\n", __func__, len+1, buf[len+1]); + len = 2; + } + for (i = 0; i < num_byte; i++) { + buf[i+len] = data[i]; + S_I2C_DBG("Byte %d: 0x%x\n", i+len, buf[i+len]); + S_I2C_DBG("Data: 0x%x\n", data[i]); + } + + rc = msm_camera_i2c_txdata(client, buf, len+num_byte); + if (rc < 0) + S_I2C_DBG("%s fail\n", __func__); + return rc; +} + +int32_t msm_camera_i2c_set_mask(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t mask, + enum msm_camera_i2c_data_type data_type, uint16_t set_mask) +{ + int32_t rc; + uint16_t reg_data; + + rc = msm_camera_i2c_read(client, addr, ®_data, data_type); + if (rc < 0) { + S_I2C_DBG("%s read fail\n", __func__); + return rc; + } + S_I2C_DBG("%s addr: 0x%x data: 0x%x setmask: 0x%x\n", + __func__, addr, reg_data, mask); + + if (set_mask) + reg_data |= mask; + else + reg_data &= ~mask; + S_I2C_DBG("%s write: 0x%x\n", __func__, reg_data); + + rc = msm_camera_i2c_write(client, addr, reg_data, data_type); + if (rc < 0) + S_I2C_DBG("%s write fail\n", __func__); + + return rc; +} + +int32_t msm_camera_i2c_compare(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t data, + enum msm_camera_i2c_data_type data_type) +{ + int32_t rc = -EIO; + uint16_t reg_data = 0; + int data_len = 0; + switch (data_type) { + case MSM_CAMERA_I2C_BYTE_DATA: + case MSM_CAMERA_I2C_WORD_DATA: + data_len = data_type; + break; + case MSM_CAMERA_I2C_SET_BYTE_MASK: + case MSM_CAMERA_I2C_UNSET_BYTE_MASK: + data_len = MSM_CAMERA_I2C_BYTE_DATA; + break; + case MSM_CAMERA_I2C_SET_WORD_MASK: + case MSM_CAMERA_I2C_UNSET_WORD_MASK: + data_len = MSM_CAMERA_I2C_WORD_DATA; + break; + default: + pr_err("%s: Unsupport data type: %d\n", __func__, data_type); + break; + } + + rc = msm_camera_i2c_read(client, + addr, ®_data, data_len); + if (rc < 0) + return rc; + + rc = 0; + switch (data_type) { + case MSM_CAMERA_I2C_BYTE_DATA: + case MSM_CAMERA_I2C_WORD_DATA: + if (data == reg_data) + return rc; + break; + case MSM_CAMERA_I2C_SET_BYTE_MASK: + case MSM_CAMERA_I2C_SET_WORD_MASK: + if ((reg_data & data) == data) + return rc; + break; + case MSM_CAMERA_I2C_UNSET_BYTE_MASK: + case MSM_CAMERA_I2C_UNSET_WORD_MASK: + if (!(reg_data & data)) + return rc; + break; + default: + pr_err("%s: Unsupport data type: %d\n", __func__, data_type); + break; + } + + S_I2C_DBG("%s: Register and data does not match\n", __func__); + rc = 1; + return rc; +} + +int32_t msm_camera_i2c_poll(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t data, + enum msm_camera_i2c_data_type data_type) +{ + int32_t rc = -EIO; + int i; + S_I2C_DBG("%s: addr: 0x%x data: 0x%x dt: %d\n", + __func__, addr, data, data_type); + + for (i = 0; i < 20; i++) { + rc = msm_camera_i2c_compare(client, + addr, data, data_type); + if (rc == 0 || rc < 0) + break; + usleep_range(10000, 11000); + } + return rc; +} + +int32_t msm_camera_i2c_write_tbl(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_reg_conf *reg_conf_tbl, uint16_t size, + enum msm_camera_i2c_data_type data_type) +{ + int i; + int32_t rc = -EFAULT; + for (i = 0; i < size; i++) { + enum msm_camera_i2c_data_type dt; + if (reg_conf_tbl->cmd_type == MSM_CAMERA_I2C_CMD_POLL) { + rc = msm_camera_i2c_poll(client, reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data, reg_conf_tbl->dt); + } else { + if (reg_conf_tbl->dt == 0) + dt = data_type; + else + dt = reg_conf_tbl->dt; + + switch (dt) { + case MSM_CAMERA_I2C_BYTE_DATA: + case MSM_CAMERA_I2C_WORD_DATA: + rc = msm_camera_i2c_write( + client, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data, dt); + break; + case MSM_CAMERA_I2C_SET_BYTE_MASK: + rc = msm_camera_i2c_set_mask(client, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data, + MSM_CAMERA_I2C_BYTE_DATA, 1); + break; + case MSM_CAMERA_I2C_UNSET_BYTE_MASK: + rc = msm_camera_i2c_set_mask(client, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data, + MSM_CAMERA_I2C_BYTE_DATA, 0); + break; + case MSM_CAMERA_I2C_SET_WORD_MASK: + rc = msm_camera_i2c_set_mask(client, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data, + MSM_CAMERA_I2C_WORD_DATA, 1); + break; + case MSM_CAMERA_I2C_UNSET_WORD_MASK: + rc = msm_camera_i2c_set_mask(client, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data, + MSM_CAMERA_I2C_WORD_DATA, 0); + break; + default: + pr_err("%s: Unsupport data type: %d\n", + __func__, dt); + break; + } + } + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +int32_t msm_camera_i2c_read(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t *data, + enum msm_camera_i2c_data_type data_type) +{ + int32_t rc = -EFAULT; + unsigned char buf[client->addr_type+data_type]; + + if ((client->addr_type != MSM_CAMERA_I2C_BYTE_ADDR + && client->addr_type != MSM_CAMERA_I2C_WORD_ADDR) + || (data_type != MSM_CAMERA_I2C_BYTE_DATA + && data_type != MSM_CAMERA_I2C_WORD_DATA)) + return rc; + + if (client->addr_type == MSM_CAMERA_I2C_BYTE_ADDR) { + buf[0] = addr; + } else if (client->addr_type == MSM_CAMERA_I2C_WORD_ADDR) { + buf[0] = addr >> BITS_PER_BYTE; + buf[1] = addr; + } + rc = msm_camera_i2c_rxdata(client, buf, data_type); + if (rc < 0) { + S_I2C_DBG("%s fail\n", __func__); + return rc; + } + if (data_type == MSM_CAMERA_I2C_BYTE_DATA) + *data = buf[0]; + else + *data = buf[0] << 8 | buf[1]; + + S_I2C_DBG("%s addr = 0x%x data: 0x%x\n", __func__, addr, *data); + return rc; +} + +int32_t msm_camera_i2c_read_seq(struct msm_camera_i2c_client *client, + uint16_t addr, uint8_t *data, uint16_t num_byte) +{ + int32_t rc = -EFAULT; + unsigned char buf[client->addr_type+num_byte]; + int i; + + if ((client->addr_type != MSM_CAMERA_I2C_BYTE_ADDR + && client->addr_type != MSM_CAMERA_I2C_WORD_ADDR) + || num_byte == 0) + return rc; + + if (client->addr_type == MSM_CAMERA_I2C_BYTE_ADDR) { + buf[0] = addr; + } else if (client->addr_type == MSM_CAMERA_I2C_WORD_ADDR) { + buf[0] = addr >> BITS_PER_BYTE; + buf[1] = addr; + } + rc = msm_camera_i2c_rxdata(client, buf, num_byte); + if (rc < 0) { + S_I2C_DBG("%s fail\n", __func__); + return rc; + } + + S_I2C_DBG("%s addr = 0x%x", __func__, addr); + for (i = 0; i < num_byte; i++) { + data[i] = buf[i]; + S_I2C_DBG("Byte %d: 0x%x\n", i, buf[i]); + S_I2C_DBG("Data: 0x%x\n", data[i]); + } + return rc; +} + +int32_t msm_sensor_write_conf_array(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_conf_array *array, uint16_t index) +{ + int32_t rc; + + rc = msm_camera_i2c_write_tbl(client, + (struct msm_camera_i2c_reg_conf *) array[index].conf, + array[index].size, array[index].data_type); + if (array[index].delay > 20) + msleep(array[index].delay); + else + usleep_range(array[index].delay*1000, + (array[index].delay+1)*1000); + return rc; +} + +int32_t msm_sensor_write_enum_conf_array(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_enum_conf_array *conf, + uint16_t enum_val) +{ + int32_t rc = -1, i; + for (i = 0; i < conf->num_enum; i++) { + if (conf->conf_enum[i] == enum_val) + break; + if (conf->conf_enum[i] > enum_val) + break; + } + if (i == conf->num_enum) + i = conf->num_enum - 1; + + if (i >= conf->num_index) + return rc; + + rc = msm_sensor_write_all_conf_array(client, + &conf->conf[i*conf->num_conf], conf->num_conf); + + if (conf->delay > 20) + msleep(conf->delay); + else + usleep_range(conf->delay*1000, + (conf->delay+1)*1000); + return rc; +} + +int32_t msm_sensor_write_all_conf_array(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_conf_array *array, uint16_t size) +{ + int32_t rc = 0, i; + for (i = 0; i < size; i++) { + rc = msm_sensor_write_conf_array(client, array, i); + if (rc < 0) + break; + } + return rc; +} + + diff --git a/drivers/media/video/msm/io/msm_camera_i2c.h b/drivers/media/video/msm/io/msm_camera_i2c.h new file mode 100644 index 0000000000000000000000000000000000000000..2fbf5cad469efe902bf0d769e9011f8318584c01 --- /dev/null +++ b/drivers/media/video/msm/io/msm_camera_i2c.h @@ -0,0 +1,120 @@ +/* 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_CAMERA_I2C_H +#define MSM_CAMERA_I2C_H + +#include +#include +#include + +#define CONFIG_MSM_CAMERA_I2C_DBG 0 + +#if CONFIG_MSM_CAMERA_I2C_DBG +#define S_I2C_DBG(fmt, args...) printk(fmt, ##args) +#else +#define S_I2C_DBG(fmt, args...) CDBG(fmt, ##args) +#endif + +enum msm_camera_i2c_reg_addr_type { + MSM_CAMERA_I2C_BYTE_ADDR = 1, + MSM_CAMERA_I2C_WORD_ADDR, +}; + +struct msm_camera_i2c_client { + struct i2c_client *client; + enum msm_camera_i2c_reg_addr_type addr_type; +}; + +enum msm_camera_i2c_data_type { + MSM_CAMERA_I2C_BYTE_DATA = 1, + MSM_CAMERA_I2C_WORD_DATA, + MSM_CAMERA_I2C_SET_BYTE_MASK, + MSM_CAMERA_I2C_UNSET_BYTE_MASK, + MSM_CAMERA_I2C_SET_WORD_MASK, + MSM_CAMERA_I2C_UNSET_WORD_MASK, +}; + +enum msm_camera_i2c_cmd_type { + MSM_CAMERA_I2C_CMD_WRITE, + MSM_CAMERA_I2C_CMD_POLL, +}; + +struct msm_camera_i2c_reg_conf { + uint16_t reg_addr; + uint16_t reg_data; + enum msm_camera_i2c_data_type dt; + enum msm_camera_i2c_cmd_type cmd_type; +}; + +struct msm_camera_i2c_conf_array { + struct msm_camera_i2c_reg_conf *conf; + uint16_t size; + uint16_t delay; + enum msm_camera_i2c_data_type data_type; +}; + +struct msm_camera_i2c_enum_conf_array { + struct msm_camera_i2c_conf_array *conf; + int *conf_enum; + uint16_t num_enum; + uint16_t num_index; + uint16_t num_conf; + uint16_t delay; + enum msm_camera_i2c_data_type data_type; +}; + +int32_t msm_camera_i2c_rxdata(struct msm_camera_i2c_client *client, + unsigned char *rxdata, int data_length); + +int32_t msm_camera_i2c_txdata(struct msm_camera_i2c_client *client, + unsigned char *txdata, int length); + +int32_t msm_camera_i2c_read(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t *data, + enum msm_camera_i2c_data_type data_type); + +int32_t msm_camera_i2c_read_seq(struct msm_camera_i2c_client *client, + uint16_t addr, uint8_t *data, uint16_t num_byte); + +int32_t msm_camera_i2c_write(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t data, + enum msm_camera_i2c_data_type data_type); + +int32_t msm_camera_i2c_write_seq(struct msm_camera_i2c_client *client, + uint16_t addr, uint8_t *data, uint16_t num_byte); + +int32_t msm_camera_i2c_set_mask(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t mask, + enum msm_camera_i2c_data_type data_type, uint16_t flag); + +int32_t msm_camera_i2c_compare(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t data, + enum msm_camera_i2c_data_type data_type); + +int32_t msm_camera_i2c_poll(struct msm_camera_i2c_client *client, + uint16_t addr, uint16_t data, + enum msm_camera_i2c_data_type data_type); + +int32_t msm_camera_i2c_write_tbl(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_reg_conf *reg_conf_tbl, uint16_t size, + enum msm_camera_i2c_data_type data_type); + +int32_t msm_sensor_write_conf_array(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_conf_array *array, uint16_t index); + +int32_t msm_sensor_write_enum_conf_array(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_enum_conf_array *conf, uint16_t enum_val); + +int32_t msm_sensor_write_all_conf_array(struct msm_camera_i2c_client *client, + struct msm_camera_i2c_conf_array *array, uint16_t size); +#endif diff --git a/drivers/media/video/msm/io/msm_camera_i2c_mux.c b/drivers/media/video/msm/io/msm_camera_i2c_mux.c new file mode 100644 index 0000000000000000000000000000000000000000..60f3e101e40415c7dd5cdc30a5df2ebce721ab38 --- /dev/null +++ b/drivers/media/video/msm/io/msm_camera_i2c_mux.c @@ -0,0 +1,187 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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.h" +#include "msm_camera_i2c_mux.h" + +static int msm_i2c_mux_config(struct i2c_mux_device *mux_device, uint8_t *mode) +{ + uint32_t val; + val = msm_camera_io_r(mux_device->ctl_base); + if (*mode == MODE_DUAL) { + msm_camera_io_w(val | 0x3, mux_device->ctl_base); + } else if (*mode == MODE_L) { + msm_camera_io_w(((val | 0x2) & ~(0x1)), mux_device->ctl_base); + val = msm_camera_io_r(mux_device->ctl_base); + CDBG("the camio mode config left value is %d\n", val); + } else { + msm_camera_io_w(((val | 0x1) & ~(0x2)), mux_device->ctl_base); + val = msm_camera_io_r(mux_device->ctl_base); + CDBG("the camio mode config right value is %d\n", val); + } + return 0; +} + +static int msm_i2c_mux_init(struct i2c_mux_device *mux_device) +{ + int rc = 0, val = 0; + if (mux_device->use_count == 0) { + mux_device->ctl_base = ioremap(mux_device->ctl_mem->start, + resource_size(mux_device->ctl_mem)); + if (!mux_device->ctl_base) { + rc = -ENOMEM; + return rc; + } + mux_device->rw_base = ioremap(mux_device->rw_mem->start, + resource_size(mux_device->rw_mem)); + if (!mux_device->rw_base) { + rc = -ENOMEM; + iounmap(mux_device->ctl_base); + return rc; + } + val = msm_camera_io_r(mux_device->rw_base); + msm_camera_io_w((val | 0x200), mux_device->rw_base); + } + mux_device->use_count++; + return 0; +}; + +static int msm_i2c_mux_release(struct i2c_mux_device *mux_device) +{ + int val = 0; + mux_device->use_count--; + if (mux_device->use_count == 0) { + val = msm_camera_io_r(mux_device->rw_base); + msm_camera_io_w((val & ~0x200), mux_device->rw_base); + iounmap(mux_device->rw_base); + iounmap(mux_device->ctl_base); + } + return 0; +} + +static long msm_i2c_mux_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct i2c_mux_device *mux_device; + int rc = 0; + mux_device = v4l2_get_subdevdata(sd); + if (mux_device == NULL) { + rc = -ENOMEM; + return rc; + } + mutex_lock(&mux_device->mutex); + switch (cmd) { + case VIDIOC_MSM_I2C_MUX_CFG: + rc = msm_i2c_mux_config(mux_device, (uint8_t *) arg); + break; + case VIDIOC_MSM_I2C_MUX_INIT: + rc = msm_i2c_mux_init(mux_device); + break; + case VIDIOC_MSM_I2C_MUX_RELEASE: + rc = msm_i2c_mux_release(mux_device); + break; + default: + rc = -ENOIOCTLCMD; + } + mutex_unlock(&mux_device->mutex); + return rc; +} + +static struct v4l2_subdev_core_ops msm_i2c_mux_subdev_core_ops = { + .ioctl = &msm_i2c_mux_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_i2c_mux_subdev_ops = { + .core = &msm_i2c_mux_subdev_core_ops, +}; + +static int __devinit i2c_mux_probe(struct platform_device *pdev) +{ + struct i2c_mux_device *mux_device; + int rc = 0; + CDBG("%s: device id = %d\n", __func__, pdev->id); + mux_device = kzalloc(sizeof(struct i2c_mux_device), GFP_KERNEL); + if (!mux_device) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&mux_device->subdev, &msm_i2c_mux_subdev_ops); + v4l2_set_subdevdata(&mux_device->subdev, mux_device); + platform_set_drvdata(pdev, &mux_device->subdev); + mutex_init(&mux_device->mutex); + + mux_device->ctl_mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "i2c_mux_ctl"); + if (!mux_device->ctl_mem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto i2c_mux_no_resource; + } + mux_device->ctl_io = request_mem_region(mux_device->ctl_mem->start, + resource_size(mux_device->ctl_mem), pdev->name); + if (!mux_device->ctl_io) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto i2c_mux_no_resource; + } + mux_device->rw_mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "i2c_mux_rw"); + if (!mux_device->rw_mem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto i2c_mux_no_resource; + } + mux_device->rw_io = request_mem_region(mux_device->rw_mem->start, + resource_size(mux_device->rw_mem), pdev->name); + if (!mux_device->rw_io) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto i2c_mux_no_resource; + } + mux_device->pdev = pdev; + return 0; + +i2c_mux_no_resource: + mutex_destroy(&mux_device->mutex); + kfree(mux_device); + return 0; +} + +static struct platform_driver i2c_mux_driver = { + .probe = i2c_mux_probe, + .driver = { + .name = MSM_I2C_MUX_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_camera_i2c_mux_init_module(void) +{ + return platform_driver_register(&i2c_mux_driver); +} + +static void __exit msm_camera_i2c_mux_exit_module(void) +{ + platform_driver_unregister(&i2c_mux_driver); +} + +module_init(msm_camera_i2c_mux_init_module); +module_exit(msm_camera_i2c_mux_exit_module); +MODULE_DESCRIPTION("MSM Camera I2C mux driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/io/msm_camera_i2c_mux.h b/drivers/media/video/msm/io/msm_camera_i2c_mux.h new file mode 100644 index 0000000000000000000000000000000000000000..94394fc8ffa0aa01a3f3db939a257c2d6c119267 --- /dev/null +++ b/drivers/media/video/msm/io/msm_camera_i2c_mux.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_I2C_MUX_H +#define MSM_I2C_MUX_H + +#include +#include + +struct i2c_mux_device { + struct platform_device *pdev; + struct v4l2_subdev subdev; + struct resource *ctl_mem; + struct resource *ctl_io; + void __iomem *ctl_base; + struct resource *rw_mem; + struct resource *rw_io; + void __iomem *rw_base; + struct mutex mutex; + unsigned use_count; +}; + +struct i2c_mux_cfg_params { + struct v4l2_subdev *subdev; + void *parms; +}; + +#define VIDIOC_MSM_I2C_MUX_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 13, struct i2c_mux_cfg_params) + +#define VIDIOC_MSM_I2C_MUX_INIT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 14, struct v4l2_subdev*) + +#define VIDIOC_MSM_I2C_MUX_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 15, struct v4l2_subdev*) + +#endif diff --git a/drivers/media/video/msm/io/msm_camera_io_util.c b/drivers/media/video/msm/io/msm_camera_io_util.c new file mode 100644 index 0000000000000000000000000000000000000000..4049266c9d89219e0920634b031b487f0374e8da --- /dev/null +++ b/drivers/media/video/msm/io/msm_camera_io_util.c @@ -0,0 +1,382 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 BUFF_SIZE_128 128 + +void msm_camera_io_w(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + writel_relaxed((data), (addr)); +} + +void msm_camera_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_camera_io_r(void __iomem *addr) +{ + uint32_t data = readl_relaxed(addr); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +u32 msm_camera_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_camera_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; + + for (i = 0; i < len; i++) + writel_relaxed(*s++, d++); +} + +void msm_camera_io_dump(void __iomem *addr, int size) +{ + char line_str[BUFF_SIZE_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) { + snprintf(p_str, 12, "%08x: ", (u32) p); + p_str += 10; + } + data = readl_relaxed(p++); + snprintf(p_str, 12, "%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_camera_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_camera_io_memcpy_toio(dest_addr, src_addr, len / 4); + msm_camera_io_dump(dest_addr, len); +} + +int msm_cam_clk_enable(struct device *dev, struct msm_cam_clk_info *clk_info, + struct clk **clk_ptr, int num_clk, int enable) +{ + int i; + int rc = 0; + if (enable) { + for (i = 0; i < num_clk; i++) { + clk_ptr[i] = clk_get(dev, clk_info[i].clk_name); + if (IS_ERR(clk_ptr[i])) { + pr_err("%s get failed\n", clk_info[i].clk_name); + rc = PTR_ERR(clk_ptr[i]); + goto cam_clk_get_err; + } + if (clk_info[i].clk_rate >= 0) { + rc = clk_set_rate(clk_ptr[i], + clk_info[i].clk_rate); + if (rc < 0) { + pr_err("%s set failed\n", + clk_info[i].clk_name); + goto cam_clk_set_err; + } + } + rc = clk_prepare(clk_ptr[i]); + if (rc < 0) { + pr_err("%s prepare failed\n", + clk_info[i].clk_name); + goto cam_clk_prepare_err; + } + + rc = clk_enable(clk_ptr[i]); + if (rc < 0) { + pr_err("%s enable failed\n", + clk_info[i].clk_name); + goto cam_clk_enable_err; + } + } + } else { + for (i = num_clk - 1; i >= 0; i--) { + if (clk_ptr[i] != NULL) { + clk_disable(clk_ptr[i]); + clk_unprepare(clk_ptr[i]); + clk_put(clk_ptr[i]); + } + } + } + return rc; + + +cam_clk_enable_err: + clk_unprepare(clk_ptr[i]); +cam_clk_prepare_err: +cam_clk_set_err: + clk_put(clk_ptr[i]); +cam_clk_get_err: + for (i--; i >= 0; i--) { + if (clk_ptr[i] != NULL) { + clk_disable(clk_ptr[i]); + clk_unprepare(clk_ptr[i]); + clk_put(clk_ptr[i]); + } + } + return rc; +} + +int msm_camera_config_vreg(struct device *dev, struct camera_vreg_t *cam_vreg, + int num_vreg, struct regulator **reg_ptr, int config) +{ + int i = 0; + int rc = 0; + struct camera_vreg_t *curr_vreg; + if (config) { + for (i = 0; i < num_vreg; i++) { + curr_vreg = &cam_vreg[i]; + reg_ptr[i] = regulator_get(dev, + curr_vreg->reg_name); + if (IS_ERR(reg_ptr[i])) { + pr_err("%s: %s get failed\n", + __func__, + curr_vreg->reg_name); + reg_ptr[i] = NULL; + goto vreg_get_fail; + } + if (curr_vreg->type == REG_LDO) { + rc = regulator_set_voltage( + reg_ptr[i], + curr_vreg->min_voltage, + curr_vreg->max_voltage); + if (rc < 0) { + pr_err("%s: %s set voltage failed\n", + __func__, + curr_vreg->reg_name); + goto vreg_set_voltage_fail; + } + if (curr_vreg->op_mode >= 0) { + rc = regulator_set_optimum_mode( + reg_ptr[i], + curr_vreg->op_mode); + if (rc < 0) { + pr_err( + "%s: %s set optimum mode failed\n", + __func__, + curr_vreg->reg_name); + goto vreg_set_opt_mode_fail; + } + } + } + } + } else { + for (i = num_vreg-1; i >= 0; i--) { + curr_vreg = &cam_vreg[i]; + if (reg_ptr[i]) { + if (curr_vreg->type == REG_LDO) { + if (curr_vreg->op_mode >= 0) { + regulator_set_optimum_mode( + reg_ptr[i], 0); + } + regulator_set_voltage( + reg_ptr[i], 0, curr_vreg-> + max_voltage); + } + regulator_put(reg_ptr[i]); + reg_ptr[i] = NULL; + } + } + } + return 0; + +vreg_unconfig: +if (curr_vreg->type == REG_LDO) + regulator_set_optimum_mode(reg_ptr[i], 0); + +vreg_set_opt_mode_fail: +if (curr_vreg->type == REG_LDO) + regulator_set_voltage(reg_ptr[i], 0, + curr_vreg->max_voltage); + +vreg_set_voltage_fail: + regulator_put(reg_ptr[i]); + reg_ptr[i] = NULL; + +vreg_get_fail: + for (i--; i >= 0; i--) { + curr_vreg = &cam_vreg[i]; + goto vreg_unconfig; + } + return -ENODEV; +} + +int msm_camera_enable_vreg(struct device *dev, struct camera_vreg_t *cam_vreg, + int num_vreg, struct regulator **reg_ptr, int enable) +{ + int i = 0, rc = 0; + if (enable) { + for (i = 0; i < num_vreg; i++) { + if (IS_ERR(reg_ptr[i])) { + pr_err("%s: %s null regulator\n", + __func__, cam_vreg[i].reg_name); + goto disable_vreg; + } + rc = regulator_enable(reg_ptr[i]); + if (rc < 0) { + pr_err("%s: %s enable failed\n", + __func__, cam_vreg[i].reg_name); + goto disable_vreg; + } + } + } else { + for (i = num_vreg-1; i >= 0; i--) + regulator_disable(reg_ptr[i]); + } + return rc; +disable_vreg: + for (i--; i >= 0; i--) { + regulator_disable(reg_ptr[i]); + goto disable_vreg; + } + return rc; +} + +static int config_gpio_table(struct msm_camera_gpio_conf *gpio) +{ + int rc = 0, i = 0; + uint32_t *table_on; + uint32_t *table_off; + uint32_t len; + + table_on = gpio->camera_on_table; + table_off = gpio->camera_off_table; + len = gpio->camera_on_table_size; + + for (i = 0; i < len; i++) { + rc = gpio_tlmm_config(table_on[i], GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s not able to get gpio\n", __func__); + for (i--; i >= 0; i--) + gpio_tlmm_config(table_off[i], + GPIO_CFG_ENABLE); + break; + } + } + return rc; +} + +int msm_camera_request_gpio_table(struct msm_camera_sensor_info *sinfo, + int gpio_en) +{ + int rc = 0; + struct msm_camera_gpio_conf *gpio_conf = + sinfo->sensor_platform_info->gpio_conf; + + if (!gpio_conf->gpio_no_mux) { + if (gpio_conf->cam_gpio_req_tbl == NULL || + gpio_conf->cam_gpio_common_tbl == NULL) { + pr_err("%s: NULL camera gpio table\n", __func__); + return -EFAULT; + } + } + if (gpio_conf->gpio_no_mux) + config_gpio_table(gpio_conf); + + if (gpio_en) { + if (!gpio_conf->gpio_no_mux) { + if (gpio_conf->cam_gpiomux_conf_tbl != NULL) { + msm_gpiomux_install( + (struct msm_gpiomux_config *) + gpio_conf->cam_gpiomux_conf_tbl, + gpio_conf->cam_gpiomux_conf_tbl_size); + } + rc = gpio_request_array(gpio_conf->cam_gpio_common_tbl, + gpio_conf->cam_gpio_common_tbl_size); + if (rc < 0) { + pr_err("%s common gpio request failed\n" + , __func__); + return rc; + } + } + if (gpio_conf->cam_gpio_req_tbl_size) { + rc = gpio_request_array(gpio_conf->cam_gpio_req_tbl, + gpio_conf->cam_gpio_req_tbl_size); + if (rc < 0) { + pr_err("%s camera gpio" + "request failed\n", __func__); + gpio_free_array(gpio_conf->cam_gpio_common_tbl, + gpio_conf->cam_gpio_common_tbl_size); + return rc; + } + } + } else { + gpio_free_array(gpio_conf->cam_gpio_req_tbl, + gpio_conf->cam_gpio_req_tbl_size); + if (!gpio_conf->gpio_no_mux) + gpio_free_array(gpio_conf->cam_gpio_common_tbl, + gpio_conf->cam_gpio_common_tbl_size); + } + return rc; +} + +int msm_camera_config_gpio_table(struct msm_camera_sensor_info *sinfo, + int gpio_en) +{ + struct msm_camera_gpio_conf *gpio_conf = + sinfo->sensor_platform_info->gpio_conf; + int rc = 0, i; + + if (gpio_en) { + for (i = 0; i < gpio_conf->cam_gpio_set_tbl_size; i++) { + gpio_set_value_cansleep( + gpio_conf->cam_gpio_set_tbl[i].gpio, + gpio_conf->cam_gpio_set_tbl[i].flags); + usleep_range(gpio_conf->cam_gpio_set_tbl[i].delay, + gpio_conf->cam_gpio_set_tbl[i].delay + 1000); + } + } else { + for (i = gpio_conf->cam_gpio_set_tbl_size - 1; i >= 0; i--) { + if (gpio_conf->cam_gpio_set_tbl[i].flags) + gpio_set_value_cansleep( + gpio_conf->cam_gpio_set_tbl[i].gpio, + GPIOF_OUT_INIT_LOW); + } + } + return rc; +} diff --git a/drivers/media/video/msm/io/msm_io7x.c b/drivers/media/video/msm/io/msm_io7x.c new file mode 100644 index 0000000000000000000000000000000000000000..8804106d8fb57092d90b754fcbf99f6916e0fc5d --- /dev/null +++ b/drivers/media/video/msm/io/msm_io7x.c @@ -0,0 +1,318 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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 = (msm_camera_io_r_mb(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; + + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_EXTERNAL); + usleep_range(10000, 11000); +} + +void msm_camio_vfe_blk_reset(void) +{ + uint32_t val; + + /* do apps reset */ + val = msm_camera_io_r_mb(appbase + 0x00000210); + val |= 0x1; + msm_camera_io_w_mb(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + val = msm_camera_io_r_mb(appbase + 0x00000210); + val &= ~0x1; + msm_camera_io_w_mb(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + /* do axi reset */ + val = msm_camera_io_r_mb(appbase + 0x00000208); + val |= 0x1; + msm_camera_io_w_mb(val, appbase + 0x00000208); + usleep_range(10000, 11000); + + val = msm_camera_io_r_mb(appbase + 0x00000208); + val &= ~0x1; + msm_camera_io_w_mb(val, appbase + 0x00000208); + usleep_range(10000, 11000); +} + +void msm_camio_camif_pad_reg_reset_2(void) +{ + uint32_t reg; + uint32_t mask, value; + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); +} + +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/io/msm_io8x.c b/drivers/media/video/msm/io/msm_io8x.c new file mode 100644 index 0000000000000000000000000000000000000000..5f1f34f548b9a5b0a0e8eac1921d3fb536292361 --- /dev/null +++ b/drivers/media/video/msm/io/msm_io8x.c @@ -0,0 +1,331 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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 = (msm_camera_io_r_mb(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; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_EXTERNAL); + + usleep_range(10000, 11000); + + /* 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 = msm_camera_io_r_mb(appbase + APPS_RESET_OFFSET); + val |= 0x1; + msm_camera_io_w_mb(val, appbase + APPS_RESET_OFFSET); + usleep_range(10000, 11000); + + val = msm_camera_io_r_mb(appbase + APPS_RESET_OFFSET); + val &= ~0x1; + msm_camera_io_w_mb(val, appbase + APPS_RESET_OFFSET); + usleep_range(10000, 11000); +} + +void msm_camio_camif_pad_reg_reset_2(void) +{ + uint32_t reg; + uint32_t mask, value; + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r_mb(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w_mb((reg & (~mask)) | (value & mask), mdcbase); + usleep_range(10000, 11000); +} + +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/io/msm_io_7x27a.c b/drivers/media/video/msm/io/msm_io_7x27a.c new file mode 100644 index 0000000000000000000000000000000000000000..4d89c061470696a9e7bd28ac87050439138c99f8 --- /dev/null +++ b/drivers/media/video/msm/io/msm_io_7x27a.c @@ -0,0 +1,595 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; + + +int msm_camio_vfe_clk_rate_set(int rate) +{ + int rc = 0; + struct clk *clk = camio_vfe_clk; + if (rate > clk_get_rate(clk)) + rc = clk_set_rate(clk, rate); + return rc; +} + +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); +} + +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq; + + irq = msm_camera_io_r(csibase + MIPI_INTERRUPT_STATUS); + CDBG("%s MIPI_INTERRUPT_STATUS = 0x%x\n", __func__, irq); + msm_camera_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_camera_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_camera_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_camera_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_camera_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_camera_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_camera_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 = msm_camera_io_r(appbase + 0x00000210); + val |= 0x1; + msm_camera_io_w(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + val = msm_camera_io_r(appbase + 0x00000210); + val &= ~0x1; + msm_camera_io_w(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + /* do axi reset */ + val = msm_camera_io_r(appbase + 0x00000208); + val |= 0x1; + msm_camera_io_w(val, appbase + 0x00000208); + usleep_range(10000, 11000); + + val = msm_camera_io_r(appbase + 0x00000208); + val &= ~0x1; + msm_camera_io_w(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_camera_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_camera_io_w(0x4, csibase + MIPI_PHY_CONTROL); + + msm_camera_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_camera_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_camera_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_camera_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_camera_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_camera_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_camera_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_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + + msm_camera_io_w(0x00000000, csibase + MIPI_PHY_D2_CONTROL); + msm_camera_io_w(0x00000000, csibase + MIPI_PHY_D3_CONTROL); + + /* program number of lanes and lane mapping */ + switch (csi_params->lane_cnt) { + case 1: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x4, + csibase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x5, + csibase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x6, + csibase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x7, + csibase + MIPI_CAMERA_CNTL); + break; + } + + msm_camera_io_w(0xFFFFF3FF, csibase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits - write 1 clears the status*/ + msm_camera_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/io/msm_io_7x27a_v4l2.c b/drivers/media/video/msm/io/msm_io_7x27a_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..7e801459eddf1d0d88ee29a55211c39e0d22915c --- /dev/null +++ b/drivers/media/video/msm/io/msm_io_7x27a_v4l2.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 MSM_AXI_QOS_PREVIEW 200000 +#define MSM_AXI_QOS_SNAPSHOT 200000 +#define MSM_AXI_QOS_RECORDING 200000 + +static struct clk *camio_cam_clk; +static struct resource *clk_ctrl_mem; +static struct msm_camera_io_clk camio_clk; +static int apps_reset; +void __iomem *appbase; + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + 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; + 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; + 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_vfe_blk_reset_2(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); +} + +void msm_camio_vfe_blk_reset_3(void) +{ + uint32_t val; + + if (!apps_reset) + return; + + /* do apps reset */ + val = readl_relaxed(appbase + 0x00000210); + val |= 0x10A0000; + writel_relaxed(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + val = readl_relaxed(appbase + 0x00000210); + val &= ~(0x10A0000); + writel_relaxed(val, appbase + 0x00000210); + usleep_range(10000, 11000); + mb(); +} + +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__); + } +} + +static int __devinit clkctl_probe(struct platform_device *pdev) +{ + int rc = 0; + + apps_reset = *(int *)pdev->dev.platform_data; + clk_ctrl_mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "clk_ctl"); + if (!clk_ctrl_mem) { + pr_err("%s: no mem resource:3?\n", __func__); + return -ENODEV; + } + + appbase = ioremap(clk_ctrl_mem->start, + resource_size(clk_ctrl_mem)); + if (!appbase) { + pr_err("clkctl_probe: appbase:err\n"); + rc = -ENOMEM; + goto ioremap_fail; + } + return 0; + +ioremap_fail: + msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + return rc; +} + +static int clkctl_remove(struct platform_device *pdev) +{ + if (clk_ctrl_mem) + iounmap(clk_ctrl_mem); + + return 0; +} + +static struct platform_driver clkctl_driver = { + .probe = clkctl_probe, + .remove = clkctl_remove, + .driver = { + .name = "msm_clk_ctl", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_clkctl_init_module(void) +{ + return platform_driver_register(&clkctl_driver); +} + +static void __exit msm_clkctl_exit_module(void) +{ + platform_driver_unregister(&clkctl_driver); +} + +module_init(msm_clkctl_init_module); +module_exit(msm_clkctl_exit_module); +MODULE_DESCRIPTION("CAM IO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/io/msm_io_8960.c b/drivers/media/video/msm/io/msm_io_8960.c new file mode 100644 index 0000000000000000000000000000000000000000..f9c454af8f051fbf4cafcc09de1868b90ce8f9d5 --- /dev/null +++ b/drivers/media/video/msm/io/msm_io_8960.c @@ -0,0 +1,95 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +#define BUFF_SIZE_128 128 + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +void msm_camio_bus_scale_cfg(struct msm_bus_scale_pdata *cam_bus_scale_table, + 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) { + 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_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 + 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/io/msm_io_8x60.c b/drivers/media/video/msm/io/msm_io_8x60.c new file mode 100644 index 0000000000000000000000000000000000000000..baa1245f7889b54c69575f7e91ccd3491d429263 --- /dev/null +++ b/drivers/media/video/msm/io/msm_io_8x60.c @@ -0,0 +1,820 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 +#define DBG_CSI 0 + +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_vpe_clk; +static struct clk *camio_vpe_pclk; +static struct regulator *fs_vfe; +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; + +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_VPE_CLK: + camio_vpe_clk = + clk = clk_get(NULL, "vpe_clk"); + vpe_clk_rate = clk_round_rate(camio_vpe_clk, vpe_clk_rate); + clk_set_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_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; +} + +int msm_camio_vfe_clk_rate_set(int rate) +{ + int rc = 0; + struct clk *clk = camio_vfe_clk; + if (rate > clk_get_rate(clk)) + rc = clk_set_rate(clk, rate); + 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); +} + +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq = 0; + if (csibase != NULL) + irq = msm_camera_io_r(csibase + MIPI_INTERRUPT_STATUS); + CDBG("%s MIPI_INTERRUPT_STATUS = 0x%x\n", __func__, irq); + if (csibase != NULL) + msm_camera_io_w(irq, csibase + MIPI_INTERRUPT_STATUS); + return IRQ_HANDLED; +} + +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; +} + +#ifdef DBG_CSI +static int csi_request_irq(void) +{ + return request_irq(camio_ext.csiirq, msm_io_csi_irq, + IRQF_TRIGGER_HIGH, "csi", 0); +} +#else +static int csi_request_irq(void) { return 0; } +#endif + +#ifdef DBG_CSI +static void csi_free_irq(void) +{ + free_irq(camio_ext.csiirq, 0); +} +#else +static void csi_free_irq(void) { return 0; } +#endif + +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_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 = csi_request_irq(); + if (rc < 0) + goto csi_irq_fail; + + return 0; + +csi_irq_fail: + iounmap(csibase); + csibase = NULL; +csi_busy: + release_mem_region(camio_ext.csiphy, camio_ext.csisz); + csibase = NULL; +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; +} + +static void msm_camio_csi_disable(void) +{ + uint32_t val; + + val = 0x0; + if (csibase != NULL) { + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_camera_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_camera_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + msleep(20); + val = msm_camera_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_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + usleep_range(5000, 6000); + msm_camera_io_w(0x0, csibase + MIPI_INTERRUPT_MASK); + msm_camera_io_w(0x0, csibase + MIPI_INTERRUPT_STATUS); + csi_free_irq(); + iounmap(csibase); + csibase = NULL; + release_mem_region(camio_ext.csiphy, camio_ext.csisz); + } +} +void msm_camio_disable(struct platform_device *pdev) +{ + CDBG("disable mipi\n"); + msm_camio_csi_disable(); + 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(); + usleep_range(10000, 11000); + 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; + int i; + + CDBG("msm_camio_csi_config\n"); + if (csibase != NULL) { + /* SOT_ECC_EN enable error correction for SYNC (data-lane) */ + msm_camera_io_w(0x4, csibase + MIPI_PHY_CONTROL); + + /* SW_RST to the CSI core */ + msm_camera_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_camera_io_w(val, csibase + MIPI_PROTOCOL_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); + for (i = 0; i < csi_params->lane_cnt; i++) + msm_camera_io_w(val, + csibase + MIPI_PHY_D0_CONTROL2 + i * 4); + + 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_camera_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_camera_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_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + + msm_camera_io_w(0x00000000, csibase + MIPI_PHY_D2_CONTROL); + msm_camera_io_w(0x00000000, csibase + MIPI_PHY_D3_CONTROL); + + /* halcyon only supports 1 or 2 lane */ + switch (csi_params->lane_cnt) { + case 1: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x4, + csibase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x5, + csibase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x6, + csibase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_camera_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_camera_io_w(0xF017F3C0, csibase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits*/ + msm_camera_io_w(0xF017F3C0, csibase + MIPI_INTERRUPT_STATUS); + } else { + pr_info("CSIBASE is NULL"); + } + + 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__); + } +} + +int msm_cam_core_reset(void) +{ + struct clk *clk1; + int rc = 0; + clk1 = clk_get(&camio_dev->dev, "csi_vfe_clk"); + if (IS_ERR(clk1)) { + pr_err("%s: did not get csi_vfe_clk\n", __func__); + return PTR_ERR(clk1); + } + rc = clk_reset(clk1, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s:csi_vfe_clk assert failed\n", __func__); + clk_put(clk1); + return rc; + } + usleep_range(1000, 1200); + rc = clk_reset(clk1, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s:csi_vfe_clk deassert failed\n", __func__); + clk_put(clk1); + return rc; + } + clk_put(clk1); + + clk1 = clk_get(&camio_dev->dev, "csi_clk"); + if (IS_ERR(clk1)) { + pr_err("%s: did not get csi_clk\n", __func__); + return PTR_ERR(clk1); + } + rc = clk_reset(clk1, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s:csi_clk assert failed\n", __func__); + clk_put(clk1); + return rc; + } + usleep_range(1000, 1200); + rc = clk_reset(clk1, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s:csi_clk deassert failed\n", __func__); + clk_put(clk1); + return rc; + } + clk_put(clk1); + + clk1 = clk_get(&camio_dev->dev, "csi_pclk"); + if (IS_ERR(clk1)) { + pr_err("%s: did not get csi_pclk\n", __func__); + return PTR_ERR(clk1); + } + rc = clk_reset(clk1, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s:csi_pclk assert failed\n", __func__); + clk_put(clk1); + return rc; + } + usleep_range(1000, 1200); + rc = clk_reset(clk1, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s:csi_pclk deassert failed\n", __func__); + clk_put(clk1); + return rc; + } + clk_put(clk1); + + return rc; +} diff --git a/drivers/media/video/msm/io/msm_io_vfe31.c b/drivers/media/video/msm/io/msm_io_vfe31.c new file mode 100644 index 0000000000000000000000000000000000000000..0c0c4502ed003b89ea00482b4fffd9263af77984 --- /dev/null +++ b/drivers/media/video/msm/io/msm_io_vfe31.c @@ -0,0 +1,775 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/* AXI rates in KHz */ +#define MSM_AXI_QOS_PREVIEW 192000 +#define MSM_AXI_QOS_SNAPSHOT 192000 +#define MSM_AXI_QOS_RECORDING 192000 + +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_vpe_clk; +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; + +static struct regulator_bulk_data regs[] = { + { .supply = "gp2", .min_uV = 2600000, .max_uV = 2600000 }, + { .supply = "lvsw1" }, + { .supply = "fs_vfe" }, + /* sn12m0pz regulators */ + { .supply = "gp6", .min_uV = 3050000, .max_uV = 3100000 }, + { .supply = "gp16", .min_uV = 1200000, .max_uV = 1200000 }, +}; + +static int reg_count; + +static void msm_camera_vreg_enable(struct platform_device *pdev) +{ + int count, rc; + + struct device *dev = &pdev->dev; + + /* Use gp6 and gp16 if and only if dev name matches. */ + if (!strncmp(pdev->name, "msm_camera_sn12m0pz", 20)) + count = ARRAY_SIZE(regs); + else + count = ARRAY_SIZE(regs) - 2; + + rc = regulator_bulk_get(dev, count, regs); + + if (rc) { + dev_err(dev, "%s: could not get regulators: %d\n", + __func__, rc); + return; + } + + rc = regulator_bulk_set_voltage(count, regs); + + if (rc) { + dev_err(dev, "%s: could not set voltages: %d\n", + __func__, rc); + goto reg_free; + } + + rc = regulator_bulk_enable(count, regs); + + if (rc) { + dev_err(dev, "%s: could not enable regulators: %d\n", + __func__, rc); + goto reg_free; + } + + reg_count = count; + return; + +reg_free: + regulator_bulk_free(count, regs); + return; +} + + +static void msm_camera_vreg_disable(void) +{ + regulator_bulk_disable(reg_count, regs); + regulator_bulk_free(reg_count, regs); + reg_count = 0; +} + +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_VPE_CLK: + camio_vpe_clk = + clk = clk_get(NULL, "vpe_clk"); + vpe_clk_rate = clk_round_rate(clk, vpe_clk_rate); + clk_set_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_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); +} + +int msm_camio_vfe_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_clk; + return clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq; + irq = msm_camera_io_r(csibase + MIPI_INTERRUPT_STATUS); + CDBG("%s MIPI_INTERRUPT_STATUS = 0x%x\n", __func__, irq); + msm_camera_io_w(irq, csibase + MIPI_INTERRUPT_STATUS); + return IRQ_HANDLED; +} + +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; + 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); + } + 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; +} + +static void msm_camio_csi_disable(void) +{ + uint32_t val; + val = 0x0; + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_camera_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_camera_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_camera_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + usleep_range(9000, 10000); + free_irq(camio_ext.csiirq, 0); + iounmap(csibase); + release_mem_region(camio_ext.csiphy, camio_ext.csisz); +} + +void msm_camio_disable(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + if (!sinfo->csi_if) { + msm_camio_clk_disable(CAMIO_VFE_CAMIF_CLK); + } else { + CDBG("disable mipi\n"); + msm_camio_csi_disable(); + CDBG("disable clocks\n"); + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_CLK); + msm_camio_clk_disable(CAMIO_VFE_CLK); + } + msm_camio_clk_disable(CAMIO_VFE_PBDG_CLK); +} + +void msm_camio_camif_pad_reg_reset(void) +{ + uint32_t reg; + + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_INTERNAL); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r(camifpadbase)) & CAMIF_CFG_RMSK; + reg |= 0x3; + msm_camera_io_w(reg, camifpadbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r(camifpadbase)) & CAMIF_CFG_RMSK; + reg |= 0x10; + msm_camera_io_w(reg, camifpadbase); + usleep_range(10000, 11000); + + reg = (msm_camera_io_r(camifpadbase)) & CAMIF_CFG_RMSK; + /* Need to be uninverted*/ + reg &= 0x03; + msm_camera_io_w(reg, camifpadbase); + usleep_range(10000, 11000); +} + +void msm_camio_vfe_blk_reset(void) +{ + return; + + +} + +void msm_camio_camif_pad_reg_reset_2(void) +{ + uint32_t reg; + uint32_t mask, value; + reg = (msm_camera_io_r(camifpadbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w((reg & (~mask)) | (value & mask), camifpadbase); + usleep_range(10000, 11000); + reg = (msm_camera_io_r(camifpadbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + msm_camera_io_w((reg & (~mask)) | (value & mask), camifpadbase); + usleep_range(10000, 11000); +} + +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; + } + } +} +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; + 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; + int i; + + CDBG("msm_camio_csi_config\n"); + + /* SOT_ECC_EN enable error correction for SYNC (data-lane) */ + msm_camera_io_w(0x4, csibase + MIPI_PHY_CONTROL); + + /* SW_RST to the CSI core */ + msm_camera_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_camera_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_camera_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); + for (i = 0; i < csi_params->lane_cnt; i++) + msm_camera_io_w(val, csibase + MIPI_PHY_D0_CONTROL2 + i * 4); + + 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_camera_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_camera_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_camera_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + + msm_camera_io_w(0x00000000, csibase + MIPI_PHY_D2_CONTROL); + msm_camera_io_w(0x00000000, csibase + MIPI_PHY_D3_CONTROL); + + /* halcyon only supports 1 or 2 lane */ + switch (csi_params->lane_cnt) { + case 1: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x4, + csibase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x5, + csibase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_camera_io_w(csi_params->lane_assign << 8 | 0x6, + csibase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_camera_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_camera_io_w(0xFFF7F3FF, csibase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits*/ + msm_camera_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__); + } +} + +int msm_cam_core_reset(void) +{ + struct clk *clk1; + int rc = 0; + + clk1 = clk_get(NULL, "csi_vfe_clk"); + if (IS_ERR(clk1)) { + pr_err("%s: did not get csi_vfe_clk\n", __func__); + return PTR_ERR(clk1); + } + + rc = clk_reset(clk1, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s:csi_vfe_clk assert failed\n", __func__); + clk_put(clk1); + return rc; + } + usleep_range(1000, 1200); + rc = clk_reset(clk1, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s:csi_vfe_clk deassert failed\n", __func__); + clk_put(clk1); + return rc; + } + clk_put(clk1); + + clk1 = clk_get(NULL, "csi_clk"); + if (IS_ERR(clk1)) { + pr_err("%s: did not get csi_clk\n", __func__); + return PTR_ERR(clk1); + } + + rc = clk_reset(clk1, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s:csi_clk assert failed\n", __func__); + clk_put(clk1); + return rc; + } + usleep_range(1000, 1200); + rc = clk_reset(clk1, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s:csi_clk deassert failed\n", __func__); + clk_put(clk1); + return rc; + } + clk_put(clk1); + + clk1 = clk_get(NULL, "csi_pclk"); + if (IS_ERR(clk1)) { + pr_err("%s: did not get csi_pclk\n", __func__); + return PTR_ERR(clk1); + } + + rc = clk_reset(clk1, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s:csi_pclk assert failed\n", __func__); + clk_put(clk1); + return rc; + } + usleep_range(1000, 1200); + rc = clk_reset(clk1, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s:csi_pclk deassert failed\n", __func__); + clk_put(clk1); + return rc; + } + clk_put(clk1); + + return rc; +} diff --git a/drivers/media/video/msm/io/msm_io_vfe31_v4l2.c b/drivers/media/video/msm/io/msm_io_vfe31_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..88a9316bdb7be39e58a6c121af987272b7459ddc --- /dev/null +++ b/drivers/media/video/msm/io/msm_io_vfe31_v4l2.c @@ -0,0 +1,274 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/* AXI rates in KHz */ +#define MSM_AXI_QOS_PREVIEW 192000 +#define MSM_AXI_QOS_SNAPSHOT 192000 +#define MSM_AXI_QOS_RECORDING 192000 + +#define BUFF_SIZE_128 128 + +static struct clk *camio_vfe_clk; +static struct clk *camio_vpe_clk; +static struct clk *camio_vpe_pclk; +static struct regulator *fs_vpe; + +static int vpe_clk_rate; + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VPE_CLK: + camio_vpe_clk = + clk = clk_get(NULL, "vpe_clk"); + vpe_clk_rate = clk_round_rate(camio_vpe_clk, vpe_clk_rate); + clk_set_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_prepare(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_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_unprepare(clk); + clk_put(clk); + } else { + rc = -1; + } + + return rc; +} + +int msm_camio_vfe_clk_rate_set(int rate) +{ + int rc = 0; + struct clk *clk = camio_vfe_clk; + if (rate > clk_get_rate(clk)) + rc = clk_set_rate(clk, rate); + return rc; +} + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +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; +} + +void msm_camio_vfe_blk_reset(void) +{ + return; +} + +void msm_camio_vfe_blk_reset_3(void) +{ + return; +} + +static void msm_camio_axi_cfg(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__); + } +} + +void msm_camio_bus_scale_cfg(struct msm_bus_scale_pdata *cam_bus_scale_table, + enum msm_bus_perf_setting perf_setting) +{ + static uint32_t bus_perf_client; + int rc = 0; + if (cam_bus_scale_table == NULL) { + msm_camio_axi_cfg(perf_setting); + return; + } + + 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.c b/drivers/media/video/msm/msm.c new file mode 100644 index 0000000000000000000000000000000000000000..07db7425f757ab545cfce0843832f4667a1b6163 --- /dev/null +++ b/drivers/media/video/msm/msm.c @@ -0,0 +1,3452 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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.h" +#include "msm_csid.h" +#include "msm_csic.h" +#include "msm_csiphy.h" +#include "msm_ispif.h" +#include "msm_sensor.h" +#include "msm_actuator.h" +#include "msm_vfe32.h" +#include "msm_camera_eeprom.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 long msm_server_send_v4l2_evt(void *evt); +static void msm_cam_server_subdev_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg); + +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); +} + +static void msm_drain_eventq(struct msm_device_queue *queue) +{ + unsigned long flags; + struct msm_queue_cmd *qcmd; + spin_lock_irqsave(&queue->lock, flags); + while (!list_empty(&queue->list)) { + qcmd = list_first_entry(&queue->list, + struct msm_queue_cmd, list_eventdata); + list_del_init(&qcmd->list_eventdata); + kfree(qcmd->command); + free_qcmd(qcmd); + } + spin_unlock_irqrestore(&queue->lock, flags); +} + +static int32_t msm_find_free_queue(void) +{ + int i; + for (i = 0; i < MAX_NUM_ACTIVE_CAMERA; i++) { + struct msm_cam_server_queue *queue; + queue = &g_server_dev.server_queue[i]; + if (!queue->queue_active) + return i; + } + return -EINVAL; +} + +uint32_t msm_camera_get_mctl_handle(void) +{ + uint32_t i; + if ((g_server_dev.mctl_handle_cnt << 8) == 0) + g_server_dev.mctl_handle_cnt++; + for (i = 0; i < MAX_NUM_ACTIVE_CAMERA; i++) { + if (g_server_dev.mctl[i].handle == 0) { + g_server_dev.mctl[i].handle = + (++g_server_dev.mctl_handle_cnt) << 8 | i; + memset(&g_server_dev.mctl[i].mctl, + 0, sizeof(g_server_dev.mctl[i].mctl)); + return g_server_dev.mctl[i].handle; + } + } + return 0; +} + +struct msm_cam_media_controller *msm_camera_get_mctl(uint32_t handle) +{ + uint32_t mctl_index; + mctl_index = handle & 0xff; + if ((mctl_index < MAX_NUM_ACTIVE_CAMERA) && + (g_server_dev.mctl[mctl_index].handle == handle)) + return &g_server_dev.mctl[mctl_index].mctl; + return NULL; +} + +void msm_camera_free_mctl(uint32_t handle) +{ + uint32_t mctl_index; + mctl_index = handle & 0xff; + if ((mctl_index < MAX_NUM_ACTIVE_CAMERA) && + (g_server_dev.mctl[mctl_index].handle == handle)) + g_server_dev.mctl[mctl_index].handle = 0; + else + pr_err("%s: invalid free handle\n", __func__); +} + +/* 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; + struct msm_cam_media_controller *pmctl; + + if (sd == NULL) + return; + + pcam = to_pcam(sd->v4l2_dev); + + if (pcam == NULL) + return; + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (pmctl == NULL) + return; +} + +static int msm_ctrl_cmd_done(void *arg) +{ + void __user *uptr; + struct msm_queue_cmd *qcmd; + struct msm_camera_v4l2_ioctl_t *ioctl_ptr = arg; + struct msm_ctrl_cmd *command = + kzalloc(sizeof(struct msm_ctrl_cmd), GFP_KERNEL); + if (!command) { + pr_err("%s Insufficient memory. return", __func__); + return -ENOMEM; + } + + D("%s\n", __func__); + if (copy_from_user(command, (void __user *)ioctl_ptr->ioctl_ptr, + sizeof(struct msm_ctrl_cmd))) { + pr_err("%s: copy_from_user failed, size=%d\n", + __func__, sizeof(struct msm_ctrl_cmd)); + return -EINVAL; + } + + D("%s qid %d evtid %d %d\n", __func__, command->queue_idx, + command->evt_id, + g_server_dev.server_queue[command->queue_idx].evt_id); + g_server_dev.server_queue[command->queue_idx].ctrl = command; + if (command->evt_id != + g_server_dev.server_queue[command->queue_idx].evt_id) { + pr_err("%s Invalid event id from userspace cmd id %d %d qid %d\n", + __func__, command->evt_id, + g_server_dev.server_queue[command->queue_idx].evt_id, + command->queue_idx); + return -EINVAL; + } + + mutex_lock(&g_server_dev.server_queue_lock); + qcmd = kzalloc(sizeof(struct msm_queue_cmd), GFP_KERNEL); + atomic_set(&qcmd->on_heap, 1); + uptr = command->value; + qcmd->command = command; + + if (command->length > 0) { + command->value = + g_server_dev.server_queue[command->queue_idx].ctrl_data; + if (command->length > max_control_command_size) { + pr_err("%s: user data %d is too big (max %d)\n", + __func__, command->length, + max_control_command_size); + free_qcmd(qcmd); + return -EINVAL; + } + if (copy_from_user(command->value, uptr, command->length)) { + free_qcmd(qcmd); + return -EINVAL; + } + } + msm_enqueue(&g_server_dev.server_queue + [command->queue_idx].ctrl_q, &qcmd->list_control); + mutex_unlock(&g_server_dev.server_queue_lock); + 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_queue_cmd *event_qcmd; + struct msm_ctrl_cmd *ctrlcmd; + struct msm_device_queue *queue = + &server_dev->server_queue[out->queue_idx].ctrl_q; + + struct v4l2_event v4l2_evt; + struct msm_isp_event_ctrl *isp_event; + isp_event = kzalloc(sizeof(struct msm_isp_event_ctrl), GFP_KERNEL); + if (!isp_event) { + pr_err("%s Insufficient memory. return", __func__); + return -ENOMEM; + } + event_qcmd = kzalloc(sizeof(struct msm_queue_cmd), GFP_KERNEL); + if (!event_qcmd) { + pr_err("%s Insufficient memory. return", __func__); + return -ENOMEM; + } + + D("%s\n", __func__); + mutex_lock(&server_dev->server_queue_lock); + if (++server_dev->server_evt_id == 0) + server_dev->server_evt_id++; + D("%s qid %d evtid %d\n", __func__, out->queue_idx, + server_dev->server_evt_id); + + server_dev->server_queue[out->queue_idx].evt_id = + server_dev->server_evt_id; + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_V4L2; + v4l2_evt.id = 0; + v4l2_evt.u.data[0] = out->queue_idx; + /* setup event object to transfer the command; */ + isp_event->resptype = MSM_CAM_RESP_V4L2; + isp_event->isp_data.ctrl = *out; + isp_event->isp_data.ctrl.evt_id = server_dev->server_evt_id; + + atomic_set(&event_qcmd->on_heap, 1); + event_qcmd->command = isp_event; + + msm_enqueue(&server_dev->server_queue[out->queue_idx].eventData_q, + &event_qcmd->list_eventdata); + + /* now send command to config thread in userspace, + * 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); + mutex_unlock(&server_dev->server_queue_lock); + + /* wait for config return status */ + D("Waiting for config status\n"); + rc = wait_event_interruptible_timeout(queue->wait, + !list_empty_careful(&queue->list), + msecs_to_jiffies(out->timeout_ms)); + D("Waiting is over for config status\n"); + if (list_empty_careful(&queue->list)) { + if (!rc) + rc = -ETIMEDOUT; + if (rc < 0) { + kfree(isp_event); + 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 && value != NULL && + ctrlcmd->length <= out->length) + memcpy(value, ctrlcmd->value, ctrlcmd->length); + + memcpy(out, ctrlcmd, sizeof(struct msm_ctrl_cmd)); + out->value = value; + + kfree(ctrlcmd); + server_dev->server_queue[out->queue_idx].ctrl = NULL; + + free_qcmd(rcmd); + kfree(isp_event); + 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 || out->status == 4) + rc = 0; + else + rc = -EINVAL; + } + return rc; +} + +/*send open command to server*/ +static int msm_send_open_server(struct msm_cam_v4l2_device *pcam) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + D("%s qid %d\n", __func__, pcam->server_queue_idx); + ctrlcmd.type = MSM_V4L2_OPEN; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.length = strnlen(g_server_dev.config_info.config_dev_name[0], + MAX_DEV_NAME_LEN)+1; + ctrlcmd.value = (char *)g_server_dev.config_info.config_dev_name[0]; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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(struct msm_cam_v4l2_device *pcam) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + D("%s qid %d\n", __func__, pcam->server_queue_idx); + ctrlcmd.type = MSM_V4L2_CLOSE; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.length = strnlen(g_server_dev.config_info.config_dev_name[0], + MAX_DEV_NAME_LEN)+1; + ctrlcmd.value = (char *)g_server_dev.config_info.config_dev_name[0]; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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; + struct img_plane_info plane_info; + + plane_info.width = pix->width; + plane_info.height = pix->height; + plane_info.pixelformat = pix->pixelformat; + plane_info.buffer_type = pfmt->type; + plane_info.ext_mode = pcam->dev_inst[idx]->image_mode; + plane_info.num_planes = 1; + 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 = sizeof(struct img_plane_info); + ctrlcmd.value = (void *)&plane_info; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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 = *pfmt; + 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); + pcam->dev_inst[idx]->plane_info = plane_info; + } + + return rc; +} + +static int msm_server_set_fmt_mplane(struct msm_cam_v4l2_device *pcam, int idx, + struct v4l2_format *pfmt) +{ + int rc = 0; + int i = 0; + struct v4l2_pix_format_mplane *pix_mp = &pfmt->fmt.pix_mp; + struct msm_ctrl_cmd ctrlcmd; + struct img_plane_info plane_info; + + plane_info.width = pix_mp->width; + plane_info.height = pix_mp->height; + plane_info.pixelformat = pix_mp->pixelformat; + plane_info.buffer_type = pfmt->type; + plane_info.ext_mode = pcam->dev_inst[idx]->image_mode; + plane_info.num_planes = pix_mp->num_planes; + if (plane_info.num_planes <= 0 || + plane_info.num_planes > VIDEO_MAX_PLANES) { + pr_err("%s Invalid number of planes set %d", __func__, + plane_info.num_planes); + return -EINVAL; + } + D("%s: %d, %d, 0x%x\n", __func__, + pfmt->fmt.pix_mp.width, pfmt->fmt.pix_mp.height, + pfmt->fmt.pix_mp.pixelformat); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pr_err("%s, Attention! Wrong buf-type %d\n", + __func__, pfmt->type); + return -EINVAL; + } + + for (i = 0; i < pcam->num_fmts; i++) + if (pcam->usr_fmts[i].fourcc == pix_mp->pixelformat) + break; + if (i == pcam->num_fmts) { + pr_err("%s: User requested pixelformat %x not supported\n", + __func__, pix_mp->pixelformat); + return -EINVAL; + } + + ctrlcmd.type = MSM_V4L2_VID_CAP_TYPE; + ctrlcmd.length = sizeof(struct img_plane_info); + ctrlcmd.value = (void *)&plane_info; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + + /* 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 = *pfmt; + 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_mp.width, + pcam->dev_inst[idx]->vid_fmt.fmt.pix_mp.height); + pcam->dev_inst[idx]->plane_info = plane_info; + } + + 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; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + + /* 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; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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; + if (tmp_cmd->timeout_ms > 0) + ctrlcmd.timeout_ms = tmp_cmd->timeout_ms; + else + ctrlcmd.timeout_ms = 1000; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + D("%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: + D("%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 == NULL) { + pr_err("%s Invalid control\n", __func__); + return -EINVAL; + } + if (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; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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 == NULL) { + pr_err("%s Invalid control\n", __func__); + return -EINVAL; + } + if (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; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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_get_fmt_mplane(struct msm_cam_v4l2_device *pcam, + int idx, struct v4l2_format *pfmt) +{ + *pfmt = pcam->dev_inst[idx]->vid_fmt; + 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; + + 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; + } + return rc; +} + +static int msm_server_try_fmt_mplane(struct msm_cam_v4l2_device *pcam, + struct v4l2_format *pfmt) +{ + int rc = 0; + int i = 0; + struct v4l2_pix_format_mplane *pix_mp = &pfmt->fmt.pix_mp; + + D("%s: 0x%x\n", __func__, pix_mp->pixelformat); + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pr_err("%s: Incorrect format type %d ", + __func__, pfmt->type); + 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_mp->pixelformat) + break; + } + + if (i == pcam->num_fmts) { + pr_err("%s: Format %x not found\n", + __func__, pix_mp->pixelformat); + return -EINVAL; + } + return rc; +} + +static int msm_camera_get_crop(struct msm_cam_v4l2_device *pcam, + int idx, struct v4l2_crop *crop) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + + BUG_ON(crop == NULL); + + ctrlcmd.type = MSM_V4L2_GET_CROP; + ctrlcmd.length = sizeof(struct v4l2_crop); + ctrlcmd.value = (void *)crop; + ctrlcmd.timeout_ms = 1000; + ctrlcmd.vnode_id = pcam->vnode_id; + ctrlcmd.queue_idx = pcam->server_queue_idx; + ctrlcmd.stream_type = pcam->dev_inst[idx]->image_mode; + ctrlcmd.config_ident = g_server_dev.config_info.config_dev_id[0]; + + /* 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); + + return rc; +} + +/* + * + * implementation of v4l2_ioctl_ops + * + */ +static int msm_camera_v4l2_querycap(struct file *f, void *pctx, + struct v4l2_capability *pcaps) +{ + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + /* some other day, some other time */ + /*cap->version = LINUX_VERSION_CODE; */ + 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); + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + + WARN_ON(pctx != f->private_data); + mutex_lock(&pcam->vid_lock); + switch (ctrl->id) { + case MSM_V4L2_PID_MMAP_INST: + D("%s: mmap_inst=(0x%p, %d)\n", + __func__, pcam_inst, pcam_inst->my_index); + pcam_inst->is_mem_map_inst = 1; + break; + case MSM_V4L2_PID_MMAP_ENTRY: + if (copy_from_user(&pcam_inst->mem_map, + (void *)ctrl->value, + sizeof(struct msm_mem_map_info))) { + rc = -EFAULT; + } else + D("%s:mmap entry:cookie=0x%x,mem_type=%d,len=%d\n", + __func__, pcam_inst->mem_map.cookie, + pcam_inst->mem_map.mem_type, + pcam_inst->mem_map.length); + break; + default: + if (ctrl->id == MSM_V4L2_PID_CAM_MODE) + pcam->op_mode = ctrl->value; + rc = msm_server_s_ctrl(pcam, ctrl); + break; + } + 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, i, j; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + rc = vb2_reqbufs(&pcam_inst->vid_bufq, pb); + if (rc < 0) { + pr_err("%s reqbufs failed %d ", __func__, rc); + return rc; + } + if (!pb->count) { + /* Deallocation. free buf_offset array */ + D("%s Inst %p freeing buffer offsets array", + __func__, pcam_inst); + for (j = 0 ; j < pcam_inst->buf_count ; j++) + kfree(pcam_inst->buf_offset[j]); + kfree(pcam_inst->buf_offset); + pcam_inst->buf_offset = NULL; + /* If the userspace has deallocated all the + * buffers, then release the vb2 queue */ + if (pcam_inst->vbqueue_initialized) { + vb2_queue_release(&pcam_inst->vid_bufq); + pcam_inst->vbqueue_initialized = 0; + } + } else { + D("%s Inst %p Allocating buf_offset array", + __func__, pcam_inst); + /* Allocation. allocate buf_offset array */ + pcam_inst->buf_offset = (struct msm_cam_buf_offset **) + kzalloc(pb->count * sizeof(struct msm_cam_buf_offset *), + GFP_KERNEL); + if (!pcam_inst->buf_offset) { + pr_err("%s out of memory ", __func__); + return -ENOMEM; + } + for (i = 0; i < pb->count; i++) { + pcam_inst->buf_offset[i] = + kzalloc(sizeof(struct msm_cam_buf_offset) * + pcam_inst->plane_info.num_planes, GFP_KERNEL); + if (!pcam_inst->buf_offset[i]) { + pr_err("%s out of memory ", __func__); + for (j = i-1 ; j >= 0; j--) + kfree(pcam_inst->buf_offset[j]); + kfree(pcam_inst->buf_offset); + pcam_inst->buf_offset = NULL; + return -ENOMEM; + } + } + } + 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; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + return vb2_querybuf(&pcam_inst->vid_bufq, pb); +} + +static int msm_camera_v4l2_qbuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + int rc = 0, i = 0; + /* get the camera device */ + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst=%p, mode=%d, idx=%d\n", __func__, pcam_inst, + pcam_inst->image_mode, pb->index); + WARN_ON(pctx != f->private_data); + + if (!pcam_inst->buf_offset) { + pr_err("%s Buffer is already released. Returning.\n", __func__); + return -EINVAL; + } + + if (pb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* Reject the buffer if planes array was not allocated */ + if (pb->m.planes == NULL) { + pr_err("%s Planes array is null\n", __func__); + return -EINVAL; + } + for (i = 0; i < pcam_inst->plane_info.num_planes; i++) { + D("%s stored offsets for plane %d as" + "addr offset %d, data offset %d\n", + __func__, i, pb->m.planes[i].reserved[0], + pb->m.planes[i].data_offset); + pcam_inst->buf_offset[pb->index][i].data_offset = + pb->m.planes[i].data_offset; + pcam_inst->buf_offset[pb->index][i].addr_offset = + pb->m.planes[i].reserved[0]; + } + } else { + D("%s stored reserved info %d\n", __func__, pb->reserved); + pcam_inst->buf_offset[pb->index][0].addr_offset = pb->reserved; + } + + rc = vb2_qbuf(&pcam_inst->vid_bufq, pb); + D("%s, videobuf_qbuf mode %d and idx %d returns %d\n", __func__, + pcam_inst->image_mode, pb->index, rc); + + return rc; +} + +static int msm_camera_v4l2_dqbuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + int rc = 0, i = 0; + /* get the camera device */ + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + rc = vb2_dqbuf(&pcam_inst->vid_bufq, pb, f->f_flags & O_NONBLOCK); + D("%s, videobuf_dqbuf returns %d\n", __func__, rc); + + if (pb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* Reject the buffer if planes array was not allocated */ + if (pb->m.planes == NULL) { + pr_err("%s Planes array is null\n", __func__); + return -EINVAL; + } + for (i = 0; i < pcam_inst->plane_info.num_planes; i++) { + pb->m.planes[i].data_offset = + pcam_inst->buf_offset[pb->index][i].data_offset; + pb->m.planes[i].reserved[0] = + pcam_inst->buf_offset[pb->index][i].addr_offset; + D("%s stored offsets for plane %d as " + "addr offset %d, data offset %d\n", + __func__, i, pb->m.planes[i].reserved[0], + pb->m.planes[i].data_offset); + } + } else { + D("%s stored reserved info %d\n", __func__, pb->reserved); + pb->reserved = pcam_inst->buf_offset[pb->index][0].addr_offset; + } + + return rc; +} + +static int msm_camera_v4l2_streamon(struct file *f, void *pctx, + enum v4l2_buf_type buf_type) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst %p\n", __func__, pcam_inst); + WARN_ON(pctx != f->private_data); + + if ((buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) { + pr_err("%s Invalid buffer type ", __func__); + return -EINVAL; + } + + D("%s Calling videobuf_streamon", __func__); + /* if HW streaming on is successful, start buffer streaming */ + rc = vb2_streamon(&pcam_inst->vid_bufq, buf_type); + D("%s, videobuf_streamon returns %d\n", __func__, rc); + + mutex_lock(&pcam->vid_lock); + /* turn HW (VFE/sensor) streaming */ + pcam_inst->streamon = 1; + rc = msm_server_streamon(pcam, pcam_inst->my_index); + mutex_unlock(&pcam->vid_lock); + D("%s rc = %d\n", __func__, rc); + return rc; +} + +static int msm_camera_v4l2_streamoff(struct file *f, void *pctx, + enum v4l2_buf_type buf_type) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst %p\n", __func__, pcam_inst); + WARN_ON(pctx != f->private_data); + + if ((buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) { + pr_err("%s Invalid buffer type ", __func__); + return -EINVAL; + } + + /* first turn of HW (VFE/sensor) streaming so that buffers are + not in use when we free the buffers */ + mutex_lock(&pcam->vid_lock); + pcam_inst->streamon = 0; + if (g_server_dev.use_count > 0) + 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 = vb2_streamoff(&pcam_inst->vid_bufq, buf_type); + 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_MPLANE) && + (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; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + 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; +} + +static int msm_camera_v4l2_g_fmt_cap_mplane(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; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + rc = msm_server_get_fmt_mplane(pcam, pcam_inst->my_index, pfmt); + D("%s: current_fmt->fourcc: 0x%08x, rc = %d\n", __func__, + pfmt->fmt.pix_mp.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; +} + +static int msm_camera_v4l2_try_fmt_cap_mplane(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_mplane(pcam, pfmt); + if (rc) + pr_err("Format %x not found, rc = %d\n", + pfmt->fmt.pix_mp.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; + /* get the video device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_media_controller *pmctl; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + 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); + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (pmctl == NULL) + return -EINVAL; + + if (!pcam_inst->vbqueue_initialized) { + pmctl->mctl_vbqueue_init(pcam_inst, &pcam_inst->vid_bufq, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + pcam_inst->vbqueue_initialized = 1; + } + + 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); + } + mutex_unlock(&pcam->vid_lock); + + return rc; +} + +static int msm_camera_v4l2_s_fmt_cap_mplane(struct file *f, void *pctx, + struct v4l2_format *pfmt) +{ + int rc; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_media_controller *pmctl; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst %p\n", __func__, pcam_inst); + WARN_ON(pctx != f->private_data); + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (pmctl == NULL) + return -EINVAL; + + if (!pcam_inst->vbqueue_initialized) { + pmctl->mctl_vbqueue_init(pcam_inst, &pcam_inst->vid_bufq, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + pcam_inst->vbqueue_initialized = 1; + } + + mutex_lock(&pcam->vid_lock); + rc = msm_server_set_fmt_mplane(pcam, pcam_inst->my_index, pfmt); + 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 *crop) +{ + int rc = -EINVAL; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + mutex_lock(&pcam->vid_lock); + rc = msm_camera_get_crop(pcam, pcam_inst->my_index, crop); + mutex_unlock(&pcam->vid_lock); + 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; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + 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; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = + (struct msm_cam_v4l2_dev_inst *)container_of(fh, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s:fh = 0x%x, type = 0x%x\n", __func__, (u32)fh, sub->type); + if (pcam_inst->my_index != 0) + return -EINVAL; + if (sub->type == V4L2_EVENT_ALL) + sub->type = V4L2_EVENT_PRIVATE_START+MSM_CAM_APP_NOTIFY_EVENT; + rc = v4l2_event_subscribe(fh, sub, 30); + if (rc < 0) + D("%s: failed for evtType = 0x%x, rc = %d\n", + __func__, sub->type, rc); + return rc; +} + +static int msm_camera_v4l2_unsubscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = + (struct msm_cam_v4l2_dev_inst *)container_of(fh, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s: fh = 0x%x\n", __func__, (u32)fh); + if (pcam_inst->my_index != 0) + return -EINVAL; + + rc = v4l2_event_unsubscribe(fh, sub); + D("%s: rc = %d\n", __func__, rc); + return rc; +} + +static int msm_server_v4l2_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + + D("%s: fh = 0x%x, type = 0x%x", __func__, (u32)fh, 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, 30); + 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_SVR_RESP_MAX); + } else { + D("sub->type not V4L2_EVENT_ALL = 0x%x\n", sub->type); + rc = v4l2_event_subscribe(fh, sub, 30); + 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_server_v4l2_unsubscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + + D("%s: fh = 0x%x\n", __func__, (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_enum_fmt_vid_cap_mplane = msm_camera_v4l2_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = msm_camera_v4l2_try_fmt_cap, + .vidioc_try_fmt_vid_cap_mplane = msm_camera_v4l2_try_fmt_cap_mplane, + .vidioc_g_fmt_vid_cap = msm_camera_v4l2_g_fmt_cap, + .vidioc_g_fmt_vid_cap_mplane = msm_camera_v4l2_g_fmt_cap_mplane, + .vidioc_s_fmt_vid_cap = msm_camera_v4l2_s_fmt_cap, + .vidioc_s_fmt_vid_cap_mplane = msm_camera_v4l2_s_fmt_cap_mplane, + + .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; + struct msm_cam_media_controller *pmctl; + + D("%s\n", __func__); + + if (!ps || !pcam) { + pr_err("%s NULL pointer passed in!\n", __func__); + return rc; + } + + /* The number of camera instance should be controlled by the + resource manager. Currently supporting one active instance + until multiple instances are supported */ + if (atomic_read(&ps->number_pcam_active) > 0) { + pr_err("%s Cannot have more than one active camera %d\n", + __func__, atomic_read(&ps->number_pcam_active)); + return -EINVAL; + } + /* 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(pcam); + + /*for single VFE msms (8660, 8960v1), just populate the session + with our VFE devices that registered*/ + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + pmctl->axi_sdev = ps->axi_device[0]; + pmctl->isp_sdev = ps->isp_subdev[0]; + 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; + + msm_mctl_free(pcam); + return rc; +} + +int msm_server_open_client(int *p_qidx) +{ + int rc = 0; + int server_q_idx = 0; + struct msm_cam_server_queue *queue = NULL; + + mutex_lock(&g_server_dev.server_lock); + server_q_idx = msm_find_free_queue(); + if (server_q_idx < 0) { + mutex_unlock(&g_server_dev.server_lock); + return server_q_idx; + } + + *p_qidx = server_q_idx; + queue = &g_server_dev.server_queue[server_q_idx]; + queue->ctrl = NULL; + queue->ctrl_data = kzalloc(sizeof(uint8_t) * + max_control_command_size, GFP_KERNEL); + msm_queue_init(&queue->ctrl_q, "control"); + msm_queue_init(&queue->eventData_q, "eventdata"); + queue->queue_active = 1; + mutex_unlock(&g_server_dev.server_lock); + return rc; +} + +int msm_server_send_ctrl(struct msm_ctrl_cmd *out, + int ctrl_id) +{ + int rc = 0; + void *value; + struct msm_queue_cmd *rcmd; + struct msm_queue_cmd *event_qcmd; + struct msm_ctrl_cmd *ctrlcmd; + struct msm_cam_server_dev *server_dev = &g_server_dev; + struct msm_device_queue *queue = + &server_dev->server_queue[out->queue_idx].ctrl_q; + + struct v4l2_event v4l2_evt; + struct msm_isp_event_ctrl *isp_event; + isp_event = kzalloc(sizeof(struct msm_isp_event_ctrl), GFP_KERNEL); + if (!isp_event) { + pr_err("%s Insufficient memory. return", __func__); + return -ENOMEM; + } + event_qcmd = kzalloc(sizeof(struct msm_queue_cmd), GFP_KERNEL); + if (!event_qcmd) { + pr_err("%s Insufficient memory. return", __func__); + kfree(isp_event); + return -ENOMEM; + } + + D("%s\n", __func__); + mutex_lock(&server_dev->server_queue_lock); + if (++server_dev->server_evt_id == 0) + server_dev->server_evt_id++; + + D("%s qid %d evtid %d\n", __func__, out->queue_idx, + server_dev->server_evt_id); + server_dev->server_queue[out->queue_idx].evt_id = + server_dev->server_evt_id; + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + ctrl_id; + v4l2_evt.id = 0; + v4l2_evt.u.data[0] = out->queue_idx; + /* setup event object to transfer the command; */ + isp_event->resptype = MSM_CAM_RESP_V4L2; + isp_event->isp_data.ctrl = *out; + isp_event->isp_data.ctrl.evt_id = server_dev->server_evt_id; + + atomic_set(&event_qcmd->on_heap, 1); + event_qcmd->command = isp_event; + + msm_enqueue(&server_dev->server_queue[out->queue_idx].eventData_q, + &event_qcmd->list_eventdata); + + /* now send command to config thread in userspace, + * 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); + mutex_unlock(&server_dev->server_queue_lock); + + /* wait for config return status */ + D("Waiting for config status\n"); + rc = wait_event_interruptible_timeout(queue->wait, + !list_empty_careful(&queue->list), + msecs_to_jiffies(out->timeout_ms)); + D("Waiting is over for config status\n"); + if (list_empty_careful(&queue->list)) { + if (!rc) + rc = -ETIMEDOUT; + if (rc < 0) { + kfree(isp_event); + 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; + + kfree(ctrlcmd); + server_dev->server_queue[out->queue_idx].ctrl = NULL; + + free_qcmd(rcmd); + kfree(isp_event); + 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 || out->status == 4) + rc = 0; + else + rc = -EINVAL; + } + return rc; +} + +int msm_server_close_client(int idx) +{ + int rc = 0; + struct msm_cam_server_queue *queue = NULL; + mutex_lock(&g_server_dev.server_lock); + queue = &g_server_dev.server_queue[idx]; + queue->queue_active = 0; + kfree(queue->ctrl); + queue->ctrl = NULL; + kfree(queue->ctrl_data); + queue->ctrl_data = NULL; + msm_queue_drain(&queue->ctrl_q, list_control); + msm_drain_eventq(&queue->eventData_q); + mutex_unlock(&g_server_dev.server_lock); + return rc; +} +/* v4l2_file_operations */ +static int msm_open(struct file *f) +{ + int i; + int rc = -EINVAL; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + int ion_client_created = 0; +#endif + int server_q_idx = 0; + /*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; + struct msm_cam_media_controller *pmctl = NULL; + struct msm_cam_server_queue *queue = NULL; + + D("%s\n", __func__); + + if (!pcam) { + pr_err("%s NULL pointer passed in!\n", __func__); + return rc; + } + if (!g_server_dev.use_count) { + pr_err("%s: error, daemon not yet started.", __func__); + return -EINVAL; + } + mutex_lock(&pcam->vid_lock); + for (i = 0; i < MSM_DEV_INST_MAX; i++) { + if (pcam->dev_inst[i] == NULL) + break; + } + + server_q_idx = msm_find_free_queue(); + if (server_q_idx < 0) + return server_q_idx; + + /* 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 index %d nodeid %d count %d\n", __func__, + pcam_inst->my_index, + pcam->vnode_id, pcam->use_count); + pcam->use_count++; + D("%s use_count %d\n", __func__, pcam->use_count); + if (pcam->use_count == 1) { + int ges_evt = MSM_V4L2_GES_CAM_OPEN; + pcam->server_queue_idx = server_q_idx; + queue = &g_server_dev.server_queue[server_q_idx]; + queue->ctrl = NULL; + queue->ctrl_data = kzalloc(sizeof(uint8_t) * + max_control_command_size, GFP_KERNEL); + msm_queue_init(&queue->ctrl_q, "control"); + msm_queue_init(&queue->eventData_q, "eventdata"); + queue->queue_active = 1; + + pr_err("%s send gesture evt\n", __func__); + msm_cam_server_subdev_notify(g_server_dev.gesture_device, + NOTIFY_GESTURE_CAM_EVT, &ges_evt); + + 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); + goto msm_cam_server_open_session_failed; + } + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + pmctl->client = msm_ion_client_create(-1, "camera"); + kref_init(&pmctl->refcount); + ion_client_created = 1; +#endif + + /* Should be set to sensor ops if any but right now its OK!! */ + if (!pmctl->mctl_open) { + D("%s: media contoller is not inited\n", __func__); + rc = -ENODEV; + goto mctl_open_failed; + } + + /* Now we really have to activate the camera */ + D("%s: call mctl_open\n", __func__); + rc = pmctl->mctl_open(pmctl, MSM_APPS_ID_V4L2); + if (rc < 0) { + pr_err("%s: HW open failed rc = 0x%x\n", __func__, rc); + goto mctl_open_failed; + } + pmctl->pcam_ptr = pcam; + + rc = msm_setup_v4l2_event_queue(&pcam_inst->eventHandle, + pcam->pvdev); + if (rc < 0) { + pr_err("%s: msm_setup_v4l2_event_queue failed %d", + __func__, rc); + goto mctl_event_q_setup_failed; + } + } + pcam_inst->vbqueue_initialized = 0; + rc = 0; + + f->private_data = &pcam_inst->eventHandle; + + 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(pcam); + if (rc < 0) { + pr_err("%s: msm_send_open_server failed %d\n", + __func__, rc); + goto msm_send_open_server_failed; + } + } + mutex_unlock(&pcam->vid_lock); + D("%s: end", __func__); + return rc; + +msm_send_open_server_failed: + v4l2_fh_del(&pcam_inst->eventHandle); + v4l2_fh_exit(&pcam_inst->eventHandle); +mctl_event_q_setup_failed: + if (pmctl->mctl_release) + if (pmctl->mctl_release(pmctl) < 0) + pr_err("%s: mctl_release failed\n", __func__); +mctl_open_failed: + if (pcam->use_count == 1) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + if (ion_client_created) { + pr_err("%s: destroy ion client", __func__); + kref_put(&pmctl->refcount, msm_release_ion_client); + } +#endif + if (msm_cam_server_close_session(&g_server_dev, pcam) < 0) + pr_err("%s: msm_cam_server_close_session failed\n", + __func__); + } +msm_cam_server_open_session_failed: + if (pcam->use_count == 1) { + if (queue != NULL) { + queue->queue_active = 0; + msm_drain_eventq(&queue->eventData_q); + kfree(queue->ctrl_data); + queue->ctrl_data = NULL; + msm_queue_drain(&queue->ctrl_q, list_control); + msm_drain_eventq(&queue->eventData_q); + queue = NULL; + } + pcam->dev_inst[i] = NULL; + pcam->use_count = 0; + } + mutex_unlock(&pcam->vid_lock); + kfree(pcam_inst); + pr_err("%s: error end", __func__); + return rc; +} + +int msm_cam_server_close_mctl_session(struct msm_cam_v4l2_device *pcam) +{ + int rc = 0; + struct msm_cam_media_controller *pmctl = NULL; + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pmctl) { + D("%s: invalid handle\n", __func__); + return -ENODEV; + } + + if (pmctl->mctl_release) { + rc = pmctl->mctl_release(pmctl); + if (rc < 0) + pr_err("mctl_release fails %d\n", rc); + } + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + kref_put(&pmctl->refcount, msm_release_ion_client); +#endif + + rc = msm_cam_server_close_session(&g_server_dev, pcam); + if (rc < 0) + pr_err("msm_cam_server_close_session fails %d\n", rc); + + return rc; +} + +int msm_cam_server_open_mctl_session(struct msm_cam_v4l2_device *pcam, + int *p_active) +{ + int rc = 0; + struct msm_cam_media_controller *pmctl = NULL; + D("%s: %p", __func__, g_server_dev.pcam_active); + *p_active = 0; + if (g_server_dev.pcam_active) { + D("%s: Active camera present return", __func__); + return 0; + } + 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); + return rc; + } + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + /* Should be set to sensor ops if any but right now its OK!! */ + if (!pmctl->mctl_open) { + D("%s: media contoller is not inited\n", + __func__); + rc = -ENODEV; + return rc; + } + + D("%s: call mctl_open\n", __func__); + rc = pmctl->mctl_open(pmctl, MSM_APPS_ID_V4L2); + + if (rc < 0) { + pr_err("%s: HW open failed rc = 0x%x\n", __func__, rc); + return rc; + } + pmctl->pcam_ptr = pcam; + *p_active = 1; + return rc; +} + +static int msm_addr_remap(struct msm_cam_v4l2_dev_inst *pcam_inst, + struct vm_area_struct *vma) +{ + int phyaddr; + int retval; + unsigned long size; + int rc = 0; + struct msm_cam_media_controller *mctl; + + mctl = msm_camera_get_mctl(pcam_inst->pcam->mctl_handle); + if (!mctl) { + pr_err("%s: invalid mctl pointer", __func__); + return -EFAULT; + } + + rc = msm_pmem_region_get_phy_addr(&mctl->stats_info.pmem_stats_list, + &pcam_inst->mem_map, + &phyaddr); + if (rc) { + pr_err("%s: cannot map vaddr", __func__); + return -EFAULT; + } + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + retval = remap_pfn_range(vma, vma->vm_start, + phyaddr >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (retval) { + pr_err("%s:mmap: remap failed with error %d. ", + __func__, retval); + memset(&pcam_inst->mem_map, 0, sizeof(pcam_inst->mem_map)); + return -ENOMEM; + } + D("%s:mmap: phy_addr=0x%x: %08lx-%08lx, pgoff %08lx\n", + __func__, (uint32_t)phyaddr, + vma->vm_start, vma->vm_end, vma->vm_pgoff); + memset(&pcam_inst->mem_map, 0, sizeof(pcam_inst->mem_map)); + return 0; +} + +static int msm_mmap(struct file *f, struct vm_area_struct *vma) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("mmap called, vma=0x%08lx\n", (unsigned long)vma); + + if (pcam_inst->is_mem_map_inst && + pcam_inst->mem_map.cookie) { + rc = msm_addr_remap(pcam_inst, vma); + D("%s: msm_addr_remap ret=%d\n", __func__, rc); + return rc; + } else + rc = vb2_mmap(&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; +} + +void msm_release_ion_client(struct kref *ref) +{ + struct msm_cam_media_controller *mctl = container_of(ref, + struct msm_cam_media_controller, refcount); + pr_err("%s Calling ion_client_destroy ", __func__); + ion_client_destroy(mctl->client); +} + +static int msm_close(struct file *f) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam; + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_server_queue *queue; + struct msm_cam_media_controller *pmctl; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + pcam = pcam_inst->pcam; + if (!pcam) { + pr_err("%s NULL pointer of camera device!\n", __func__); + return -EINVAL; + } + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pmctl) { + pr_err("%s NULL mctl pointer\n", __func__); + return -EINVAL; + } + + mutex_lock(&pcam->vid_lock); + + if (pcam_inst->streamon) { + /*something went wrong since instance + is closing without streamoff*/ + if (pmctl->mctl_release) { + rc = pmctl->mctl_release(pmctl); + if (rc < 0) + pr_err("mctl_release fails %d\n", rc); + } + pmctl->mctl_release = NULL;/*so that it isn't closed again*/ + } + + pcam_inst->streamon = 0; + pcam->use_count--; + pcam->dev_inst_map[pcam_inst->image_mode] = NULL; + if (pcam_inst->vbqueue_initialized) + vb2_queue_release(&pcam_inst->vid_bufq); + D("%s Closing down instance %p ", __func__, pcam_inst); + D("%s index %d nodeid %d count %d\n", __func__, pcam_inst->my_index, + pcam->vnode_id, pcam->use_count); + pcam->dev_inst[pcam_inst->my_index] = NULL; + if (pcam_inst->my_index == 0) { + v4l2_fh_del(&pcam_inst->eventHandle); + v4l2_fh_exit(&pcam_inst->eventHandle); + } + kfree(pcam_inst); + f->private_data = NULL; + + if (pcam->use_count == 0) { + int ges_evt = MSM_V4L2_GES_CAM_CLOSE; + if (g_server_dev.use_count > 0) { + rc = msm_send_close_server(pcam); + if (rc < 0) + pr_err("msm_send_close_server failed %d\n", rc); + } + if (pmctl->mctl_release) { + rc = pmctl->mctl_release(pmctl); + if (rc < 0) + pr_err("mctl_release fails %d\n", rc); + } +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + kref_put(&pmctl->refcount, msm_release_ion_client); +#endif + queue = &g_server_dev.server_queue[pcam->server_queue_idx]; + queue->queue_active = 0; + kfree(queue->ctrl); + queue->ctrl = NULL; + kfree(queue->ctrl_data); + queue->ctrl_data = NULL; + msm_queue_drain(&queue->ctrl_q, list_control); + msm_drain_eventq(&queue->eventData_q); + rc = msm_cam_server_close_session(&g_server_dev, pcam); + if (rc < 0) + pr_err("msm_cam_server_close_session fails %d\n", rc); + + if (g_server_dev.use_count == 0) + mutex_unlock(&g_server_dev.server_lock); + + msm_cam_server_subdev_notify(g_server_dev.gesture_device, + NOTIFY_GESTURE_CAM_EVT, &ges_evt); + } + 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; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + pcam = pcam_inst->pcam; + D("%s\n", __func__); + if (!pcam) { + pr_err("%s NULL pointer of camera device!\n", __func__); + return -EINVAL; + } + if (pcam_inst->my_index == 0) { + poll_wait(f, &(pcam_inst->eventHandle.wait), wait); + if (v4l2_event_pending(&pcam_inst->eventHandle)) + rc |= POLLPRI; + } else { + if (!pcam_inst->vid_bufq.streaming) { + D("%s vid_bufq.streaming is off, inst=0x%x\n", + __func__, (u32)pcam_inst); + return -EINVAL; + } + rc |= vb2_poll(&pcam_inst->vid_bufq, f, 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.wait, + wait); + if (v4l2_event_pending(&g_server_dev.server_command_queue.eventHandle)) + rc |= POLLPRI; + + return rc; +} +static long msm_ioctl_server(struct file *file, void *fh, + bool valid_prio, int cmd, void *arg) +{ + int rc = -EINVAL; + struct msm_camera_v4l2_ioctl_t *ioctl_ptr = arg; + struct msm_camera_info temp_cam_info; + struct msm_cam_config_dev_info temp_config_info; + struct msm_mctl_node_info temp_mctl_info; + int i; + + D("%s: cmd %d\n", __func__, _IOC_NR(cmd)); + + switch (cmd) { + case MSM_CAM_V4L2_IOCTL_GET_CAMERA_INFO: + if (copy_from_user(&temp_cam_info, + (void __user *)ioctl_ptr->ioctl_ptr, + 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], + strnlen( + g_server_dev.camera_info.video_dev_name[i], + MAX_DEV_NAME_LEN))) { + 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 *)ioctl_ptr->ioctl_ptr, + &temp_cam_info, + sizeof(struct msm_camera_info))) { + rc = -EINVAL; + return rc; + } + rc = 0; + break; + + case MSM_CAM_V4L2_IOCTL_GET_CONFIG_INFO: + if (copy_from_user(&temp_config_info, + (void __user *)ioctl_ptr->ioctl_ptr, + 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 *)ioctl_ptr->ioctl_ptr, + &temp_config_info, + sizeof(struct msm_cam_config_dev_info))) { + rc = -EINVAL; + return rc; + } + rc = 0; + break; + case MSM_CAM_V4L2_IOCTL_GET_MCTL_INFO: + if (copy_from_user(&temp_mctl_info, + (void __user *)ioctl_ptr->ioctl_ptr, + sizeof(struct msm_mctl_node_info))) { + rc = -EINVAL; + return rc; + } + for (i = 0; i < g_server_dev.mctl_node_info.num_mctl_nodes; + i++) { + if (copy_to_user((void __user *) + temp_mctl_info.mctl_node_name[i], + g_server_dev.mctl_node_info.mctl_node_name[i], strnlen( + g_server_dev.mctl_node_info.mctl_node_name[i], + MAX_DEV_NAME_LEN))) { + rc = -EINVAL; + return rc; + } + } + temp_mctl_info.num_mctl_nodes = + g_server_dev.mctl_node_info.num_mctl_nodes; + if (copy_to_user((void __user *)ioctl_ptr->ioctl_ptr, + &temp_mctl_info, + sizeof(struct msm_mctl_node_info))) { + rc = -EINVAL; + return rc; + } + rc = 0; + break; + + case MSM_CAM_V4L2_IOCTL_CTRL_CMD_DONE: + D("%s: MSM_CAM_IOCTL_CTRL_CMD_DONE\n", __func__); + rc = msm_ctrl_cmd_done(arg); + break; + + case MSM_CAM_V4L2_IOCTL_GET_EVENT_PAYLOAD: { + struct msm_queue_cmd *event_cmd; + struct msm_isp_event_ctrl u_isp_event; + struct msm_isp_event_ctrl *k_isp_event; + struct msm_device_queue *queue; + void __user *u_ctrl_value = NULL; + if (copy_from_user(&u_isp_event, + (void __user *)ioctl_ptr->ioctl_ptr, + sizeof(struct msm_isp_event_ctrl))) { + rc = -EINVAL; + return rc; + } + queue = &g_server_dev.server_queue + [u_isp_event.isp_data.ctrl.queue_idx].eventData_q; + event_cmd = msm_dequeue(queue, list_eventdata); + if (!event_cmd) { + pr_err("%s: No event payload\n", __func__); + rc = -EINVAL; + return rc; + } + k_isp_event = (struct msm_isp_event_ctrl *) + event_cmd->command; + free_qcmd(event_cmd); + + /* Save the pointer of the user allocated command buffer*/ + u_ctrl_value = u_isp_event.isp_data.ctrl.value; + + /* Copy the event structure into user struct*/ + u_isp_event = *k_isp_event; + + /* Restore the saved pointer of the user + * allocated command buffer. */ + u_isp_event.isp_data.ctrl.value = u_ctrl_value; + + /* Copy the ctrl cmd, if present*/ + if (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, + k_isp_event->isp_data.ctrl.length)) { + rc = -EINVAL; + break; + } + } + if (copy_to_user((void __user *)ioctl_ptr->ioctl_ptr, + &u_isp_event, + sizeof(struct msm_isp_event_ctrl))) { + rc = -EINVAL; + return rc; + } + rc = 0; + break; + } + + case MSM_CAM_IOCTL_SEND_EVENT: + rc = msm_server_send_v4l2_evt(arg); + break; + + default: + pr_err("%s: Invalid IOCTL = %d", __func__, cmd); + break; + } + return rc; +} + +static int msm_open_server(struct file *fp) +{ + int rc = 0; + D("%s: open %s\n", __func__, fp->f_path.dentry->d_name.name); + mutex_lock(&g_server_dev.server_lock); + g_server_dev.use_count++; + if (g_server_dev.use_count == 1) + fp->private_data = + &g_server_dev.server_command_queue.eventHandle; + mutex_unlock(&g_server_dev.server_lock); + 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.wait, wait); + if (v4l2_event_pending(&config->config_stat_event_queue.eventHandle)) + rc |= POLLPRI; + return rc; +} + +static int msm_close_server(struct file *fp) +{ + struct v4l2_event_subscription sub; + D("%s\n", __func__); + mutex_lock(&g_server_dev.server_lock); + if (g_server_dev.use_count > 0) + g_server_dev.use_count--; + mutex_unlock(&g_server_dev.server_lock); + if (g_server_dev.use_count == 0) { + if (g_server_dev.pcam_active) { + struct v4l2_event v4l2_ev; + mutex_lock(&g_server_dev.server_lock); + + v4l2_ev.type = V4L2_EVENT_PRIVATE_START + + MSM_CAM_APP_NOTIFY_ERROR_EVENT; + v4l2_ev.id = 0; + ktime_get_ts(&v4l2_ev.timestamp); + v4l2_event_queue( + g_server_dev.pcam_active->pvdev, &v4l2_ev); + } + sub.type = V4L2_EVENT_ALL; + msm_server_v4l2_unsubscribe_event( + &g_server_dev.server_command_queue.eventHandle, &sub); + } + return 0; +} + +static long msm_server_send_v4l2_evt(void *evt) +{ + struct v4l2_event *v4l2_ev = (struct v4l2_event *)evt; + int rc = 0; + v4l2_ev->id = 0; + if (NULL == evt) { + pr_err("%s: evt is NULL\n", __func__); + return -EINVAL; + } + + D("%s: evt type 0x%x\n", __func__, v4l2_ev->type); + if ((v4l2_ev->type >= MSM_GES_APP_EVT_MIN) && + (v4l2_ev->type < MSM_GES_APP_EVT_MAX)) { + msm_cam_server_subdev_notify(g_server_dev.gesture_device, + NOTIFY_GESTURE_EVT, v4l2_ev); + } else { + pr_err("%s: Invalid evt %d\n", __func__, v4l2_ev->type); + rc = -EINVAL; + } + D("%s: end\n", __func__); + + return rc; +} + +static long msm_v4l2_evt_notify(struct msm_cam_media_controller *mctl, + unsigned int cmd, unsigned long evt) +{ + struct v4l2_event v4l2_ev; + struct msm_cam_v4l2_device *pcam = NULL; + + if (!mctl) { + pr_err("%s: mctl is NULL\n", __func__); + return -EINVAL; + } + + if (copy_from_user(&v4l2_ev, (void __user *)evt, + sizeof(struct v4l2_event))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + v4l2_ev.id = 0; + pcam = mctl->pcam_ptr; + ktime_get_ts(&v4l2_ev.timestamp); + v4l2_event_queue(pcam->pvdev, &v4l2_ev); + return 0; +} + +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; + ev.id = 0; + 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->stats_info.pmem_stats_list, + (void __user *)arg, config_cam->p_mctl->client); + break; + + case MSM_CAM_IOCTL_UNREGISTER_PMEM: + return msm_pmem_table_del( + &config_cam->p_mctl->stats_info.pmem_stats_list, + (void __user *)arg, config_cam->p_mctl->client); + 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_server_v4l2_subscribe_event + (&config_cam->config_stat_event_queue.eventHandle, + &temp_sub); + if (rc < 0) { + pr_err("%s: cam_v4l2_subscribe_event failed rc=%d\n", + __func__, rc); + 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_server_v4l2_unsubscribe_event + (&config_cam->config_stat_event_queue.eventHandle, + &temp_sub); + if (rc < 0) { + pr_err("%s: server_unsubscribe_event failed rc=%d\n", + __func__, rc); + return rc; + } + break; + + case VIDIOC_DQEVENT: { + void __user *u_msg_value = NULL, *user_ptr = NULL; + struct msm_isp_event_ctrl u_isp_event; + struct msm_isp_event_ctrl *k_isp_event; + + /* First, copy the v4l2 event structure from userspace */ + D("%s: VIDIOC_DQEVENT\n", __func__); + if (copy_from_user(&ev, (void __user *)arg, + sizeof(struct v4l2_event))) + break; + /* Next, get the pointer to event_ctrl structure + * embedded inside the v4l2_event.u.data array. */ + user_ptr = (void __user *)(*((uint32_t *)ev.u.data)); + + /* Next, copy the userspace event ctrl structure */ + if (copy_from_user((void *)&u_isp_event, user_ptr, + sizeof(struct msm_isp_event_ctrl))) { + break; + } + /* Save the pointer of the user allocated command buffer*/ + u_msg_value = u_isp_event.isp_data.isp_msg.data; + + /* Dequeue the event queued into the v4l2 queue*/ + 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; + } + /* Use k_isp_event to point to the event_ctrl structure + * embedded inside v4l2_event.u.data */ + k_isp_event = (struct msm_isp_event_ctrl *) + (*((uint32_t *)ev.u.data)); + /* Copy the event structure into user struct. */ + u_isp_event = *k_isp_event; + if (ev.type != (V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_DIV_FRAME_EVT_MSG) && + ev.type != (V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_MCTL_PP_EVENT)) { + + /* Restore the saved pointer of the + * user allocated command buffer. */ + u_isp_event.isp_data.isp_msg.data = u_msg_value; + + if (ev.type == (V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_STAT_EVT_MSG)) { + if (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); + } + } + } + /* Copy the event ctrl structure back + * into user's structure. */ + if (copy_to_user(user_ptr, + (void *)&u_isp_event, sizeof( + struct msm_isp_event_ctrl))) { + rc = -EINVAL; + break; + } + kfree(k_isp_event); + + /* Copy the v4l2_event structure back to the user*/ + if (copy_to_user((void __user *)arg, &ev, + sizeof(struct v4l2_event))) { + rc = -EINVAL; + break; + } + } + + break; + + case MSM_CAM_IOCTL_V4L2_EVT_NOTIFY: + rc = msm_v4l2_evt_notify(config_cam->p_mctl, cmd, arg); + break; + + case MSM_CAM_IOCTL_SET_MEM_MAP_INFO: + if (copy_from_user(&config_cam->mem_map, (void __user *)arg, + sizeof(struct msm_mem_map_info))) + rc = -EINVAL; + 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_mmap_config(struct file *fp, struct vm_area_struct *vma) +{ + struct msm_cam_config_dev *config_cam = fp->private_data; + int rc = 0; + int phyaddr; + int retval; + unsigned long size; + + D("%s: phy_addr=0x%x", __func__, config_cam->mem_map.cookie); + phyaddr = (int)config_cam->mem_map.cookie; + if (!phyaddr) { + pr_err("%s: no physical memory to map", __func__); + return -EFAULT; + } + memset(&config_cam->mem_map, 0, + sizeof(struct msm_mem_map_info)); + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + retval = remap_pfn_range(vma, vma->vm_start, + phyaddr >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (retval) { + pr_err("%s: remap failed, rc = %d", + __func__, retval); + rc = -ENOMEM; + goto end; + } + D("%s: phy_addr=0x%x: %08lx-%08lx, pgoff %08lx\n", + __func__, (uint32_t)phyaddr, + vma->vm_start, vma->vm_end, vma->vm_pgoff); +end: + 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 = + msm_camera_get_mctl(g_server_dev.pcam_active->mctl_handle); + + INIT_HLIST_HEAD(&config_cam->p_mctl->stats_info.pmem_stats_list); + spin_lock_init(&config_cam->p_mctl->stats_info.pmem_stats_spinlock); + + config_cam->p_mctl->config_device = config_cam; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + kref_get(&config_cam->p_mctl->refcount); +#endif + fp->private_data = config_cam; + return rc; +} + +static int msm_close_config(struct inode *node, struct file *f) +{ +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + struct msm_cam_config_dev *config_cam = f->private_data; + D("%s Decrementing ref count of config node ", __func__); + kref_put(&config_cam->p_mctl->refcount, msm_release_ion_client); +#endif + return 0; +} + +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 v4l2_file_operations msm_fops_server = { + .owner = THIS_MODULE, + .open = msm_open_server, + .poll = msm_poll_server, + .unlocked_ioctl = video_ioctl2, + .release = msm_close_server, +}; + +static const struct v4l2_ioctl_ops msm_ioctl_ops_server = { + .vidioc_subscribe_event = msm_server_v4l2_subscribe_event, + .vidioc_default = 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, + .mmap = msm_mmap_config, + .release = msm_close_config, +}; + +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); + + v4l2_fh_init(eventHandle, pvdev); + 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); + goto config_setup_fail; + } + + 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); + goto config_setup_fail; + } + + 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]); + g_server_dev.config_info.config_dev_id[dev_num] = 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__); + goto config_setup_fail; + } + + 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__); + video_device_release(config_cam->config_stat_event_queue.pvdev); + goto config_setup_fail; + } + + return rc; + +config_setup_fail: + kfree(config_cam); + return rc; +} + +static void msm_cam_server_subdev_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + int rc = -EINVAL; + struct msm_sensor_ctrl_t *s_ctrl; + struct msm_camera_sensor_info *sinfo; + struct msm_camera_device_platform_data *camdev; + uint8_t csid_core = 0; + + if (notification == NOTIFY_CID_CHANGE || + notification == NOTIFY_ISPIF_STREAM || + notification == NOTIFY_PCLK_CHANGE || + notification == NOTIFY_CSIPHY_CFG || + notification == NOTIFY_CSID_CFG || + notification == NOTIFY_CSIC_CFG) { + s_ctrl = get_sctrl(sd); + sinfo = (struct msm_camera_sensor_info *) s_ctrl->sensordata; + camdev = sinfo->pdata; + csid_core = camdev->csid_core; + } + + switch (notification) { + case NOTIFY_CID_CHANGE: + /* reconfig the ISPIF*/ + if (g_server_dev.ispif_device) { + struct msm_ispif_params_list ispif_params; + ispif_params.len = 1; + ispif_params.params[0].intftype = PIX0; + ispif_params.params[0].cid_mask = 0x0001; + ispif_params.params[0].csid = csid_core; + + rc = v4l2_subdev_call( + g_server_dev.ispif_device, core, ioctl, + VIDIOC_MSM_ISPIF_CFG, &ispif_params); + if (rc < 0) + return; + } + break; + case NOTIFY_ISPIF_STREAM: + /* call ISPIF stream on/off */ + rc = v4l2_subdev_call(g_server_dev.ispif_device, video, + s_stream, (int)arg); + if (rc < 0) + return; + + break; + case NOTIFY_ISP_MSG_EVT: + case NOTIFY_VFE_MSG_OUT: + case NOTIFY_VFE_MSG_STATS: + case NOTIFY_VFE_MSG_COMP_STATS: + case NOTIFY_VFE_BUF_EVT: + case NOTIFY_VFE_BUF_FREE_EVT: + if (g_server_dev.isp_subdev[0] && + g_server_dev.isp_subdev[0]->isp_notify) { + rc = g_server_dev.isp_subdev[0]->isp_notify( + g_server_dev.vfe_device[0], notification, arg); + } + break; + case NOTIFY_VPE_MSG_EVT: { + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *) + v4l2_get_subdev_hostdata(sd); + struct msm_vpe_resp *vdata = (struct msm_vpe_resp *)arg; + msm_mctl_pp_notify(pmctl, + (struct msm_mctl_pp_frame_info *) + vdata->extdata); + break; + } + case NOTIFY_VFE_IRQ:{ + struct msm_vfe_cfg_cmd cfg_cmd; + struct msm_camvfe_params vfe_params; + cfg_cmd.cmd_type = CMD_VFE_PROCESS_IRQ; + vfe_params.vfe_cfg = &cfg_cmd; + vfe_params.data = arg; + rc = v4l2_subdev_call(g_server_dev.vfe_device[0], + core, ioctl, 0, &vfe_params); + } + break; + case NOTIFY_AXI_IRQ: + rc = v4l2_subdev_call(g_server_dev.axi_device[0], + core, ioctl, VIDIOC_MSM_AXI_IRQ, arg); + break; + case NOTIFY_PCLK_CHANGE: + if (g_server_dev.axi_device[0]) + rc = v4l2_subdev_call(g_server_dev.axi_device[0], video, + s_crystal_freq, *(uint32_t *)arg, 0); + else + rc = v4l2_subdev_call(g_server_dev.vfe_device[0], video, + s_crystal_freq, *(uint32_t *)arg, 0); + break; + case NOTIFY_CSIPHY_CFG: + rc = v4l2_subdev_call(g_server_dev.csiphy_device[csid_core], + core, ioctl, VIDIOC_MSM_CSIPHY_CFG, arg); + break; + case NOTIFY_CSID_CFG: + rc = v4l2_subdev_call(g_server_dev.csid_device[csid_core], + core, ioctl, VIDIOC_MSM_CSID_CFG, arg); + break; + case NOTIFY_CSIC_CFG: + rc = v4l2_subdev_call(g_server_dev.csic_device[csid_core], + core, ioctl, VIDIOC_MSM_CSIC_CFG, arg); + break; + case NOTIFY_GESTURE_EVT: + rc = v4l2_subdev_call(g_server_dev.gesture_device, + core, ioctl, VIDIOC_MSM_GESTURE_EVT, arg); + break; + case NOTIFY_GESTURE_CAM_EVT: + rc = v4l2_subdev_call(g_server_dev.gesture_device, + core, ioctl, VIDIOC_MSM_GESTURE_CAM_EVT, arg); + break; + default: + break; + } + + return; +} + +void msm_cam_release_subdev_node(struct video_device *vdev) +{ + struct v4l2_subdev *sd = video_get_drvdata(vdev); + sd->devnode = NULL; + kfree(vdev); +} + +int msm_cam_register_subdev_node(struct v4l2_subdev *sd, + enum msm_cam_subdev_type sdev_type, uint8_t index) +{ + struct video_device *vdev; + int err = 0; + + if (sdev_type == CSIPHY_DEV) { + if (index >= MAX_NUM_CSIPHY_DEV) + return -EINVAL; + g_server_dev.csiphy_device[index] = sd; + } else if (sdev_type == CSID_DEV) { + if (index >= MAX_NUM_CSID_DEV) + return -EINVAL; + g_server_dev.csid_device[index] = sd; + } else if (sdev_type == CSIC_DEV) { + if (index >= MAX_NUM_CSIC_DEV) + return -EINVAL; + g_server_dev.csic_device[index] = sd; + } else if (sdev_type == ISPIF_DEV) { + g_server_dev.ispif_device = sd; + } else if (sdev_type == VFE_DEV) { + if (index >= MAX_NUM_VFE_DEV) + return -EINVAL; + g_server_dev.vfe_device[index] = sd; + } else if (sdev_type == VPE_DEV) { + if (index >= MAX_NUM_VPE_DEV) + return -EINVAL; + g_server_dev.vpe_device[index] = sd; + } else if (sdev_type == AXI_DEV) { + if (index >= MAX_NUM_AXI_DEV) + return -EINVAL; + g_server_dev.axi_device[index] = sd; + } else if (sdev_type == GESTURE_DEV) { + g_server_dev.gesture_device = sd; + } + + err = v4l2_device_register_subdev(&g_server_dev.v4l2_dev, sd); + if (err < 0) + return err; + + /* Register a device node for every subdev marked with the + * V4L2_SUBDEV_FL_HAS_DEVNODE flag. + */ + if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE)) + return err; + + vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); + if (!vdev) { + err = -ENOMEM; + goto clean_up; + } + + video_set_drvdata(vdev, sd); + strlcpy(vdev->name, sd->name, sizeof(vdev->name)); + vdev->v4l2_dev = &g_server_dev.v4l2_dev; + vdev->fops = &v4l2_subdev_fops; + vdev->release = msm_cam_release_subdev_node; + err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1, + sd->owner); + if (err < 0) { + kfree(vdev); + goto clean_up; + } +#if defined(CONFIG_MEDIA_CONTROLLER) + sd->entity.info.v4l.major = VIDEO_MAJOR; + sd->entity.info.v4l.minor = vdev->minor; +#endif + sd->devnode = vdev; + return 0; + +clean_up: + if (sd->devnode) + video_unregister_device(sd->devnode); + return err; +} + +static int msm_setup_server_dev(struct platform_device *pdev) +{ + int rc = -ENODEV, i; + + D("%s\n", __func__); + g_server_dev.server_pdev = pdev; + g_server_dev.v4l2_dev.dev = &pdev->dev; + g_server_dev.v4l2_dev.notify = msm_cam_server_subdev_notify; + rc = v4l2_device_register(g_server_dev.v4l2_dev.dev, + &g_server_dev.v4l2_dev); + if (rc < 0) + return -EINVAL; + + g_server_dev.video_dev = video_device_alloc(); + if (g_server_dev.video_dev == NULL) { + pr_err("%s: video_device_alloc failed\n", __func__); + return rc; + } + + strlcpy(g_server_dev.video_dev->name, pdev->name, + sizeof(g_server_dev.video_dev->name)); + + g_server_dev.video_dev->v4l2_dev = &g_server_dev.v4l2_dev; + g_server_dev.video_dev->fops = &msm_fops_server; + g_server_dev.video_dev->ioctl_ops = &msm_ioctl_ops_server; + g_server_dev.video_dev->release = video_device_release; + g_server_dev.video_dev->minor = 100; + g_server_dev.video_dev->vfl_type = 1; + + video_set_drvdata(g_server_dev.video_dev, &g_server_dev); + + strlcpy(g_server_dev.media_dev.model, "qcamera", + sizeof(g_server_dev.media_dev.model)); + g_server_dev.media_dev.dev = &pdev->dev; + rc = media_device_register(&g_server_dev.media_dev); + g_server_dev.v4l2_dev.mdev = &g_server_dev.media_dev; + + rc = video_register_device(g_server_dev.video_dev, + VFL_TYPE_GRABBER, 100); + + mutex_init(&g_server_dev.server_lock); + mutex_init(&g_server_dev.server_queue_lock); + 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.server_evt_id = 0; + + /*initialize fake video device and event queue*/ + + g_server_dev.server_command_queue.pvdev = g_server_dev.video_dev; + 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__); + video_device_release(g_server_dev.server_command_queue.pvdev); + return rc; + } + + for (i = 0; i < MAX_NUM_ACTIVE_CAMERA; i++) { + struct msm_cam_server_queue *queue; + queue = &g_server_dev.server_queue[i]; + queue->queue_active = 0; + msm_queue_init(&queue->ctrl_q, "control"); + msm_queue_init(&queue->eventData_q, "eventdata"); + } + return rc; +} + +static int msm_cam_dev_init(struct msm_cam_v4l2_device *pcam) +{ + int rc = -ENOMEM; + struct video_device *pvdev = NULL; + struct i2c_client *client = v4l2_get_subdevdata(pcam->sensor_sdev); + D("%s\n", __func__); + + /* first register the v4l2 device */ + pcam->v4l2_dev.dev = &client->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; + } + + strlcpy(pcam->media_dev.model, QCAMERA_NAME, + sizeof(pcam->media_dev.model)); + pcam->media_dev.dev = &client->dev; + rc = media_device_register(&pcam->media_dev); + pvdev->v4l2_dev = &pcam->v4l2_dev; + pcam->v4l2_dev.mdev = &pcam->media_dev; + + /* init video device's driver interface */ + D("sensor name = %s, sizeof(pvdev->name)=%d\n", + pcam->sensor_sdev->name, sizeof(pvdev->name)); + + /* device info - strlcpy is safer than strncpy but + only if architecture supports*/ + strlcpy(pvdev->name, pcam->sensor_sdev->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; + + media_entity_init(&pvdev->entity, 0, NULL, 0); + pvdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L; + pvdev->entity.group_id = QCAMERA_VNODE_GROUP_ID; + + /* 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; + } + pvdev->entity.name = video_device_node_name(pvdev); + 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 struct v4l2_subdev *msm_actuator_probe( + struct msm_actuator_info *actuator_info) +{ + struct v4l2_subdev *act_sdev; + struct i2c_adapter *adapter = NULL; + struct msm_actuator_ctrl_t *actrl; + void *act_client = NULL; + + D("%s called\n", __func__); + + if (!actuator_info) + goto probe_fail; + + adapter = i2c_get_adapter(actuator_info->bus_id); + if (!adapter) + goto probe_fail; + + act_client = i2c_new_device(adapter, actuator_info->board_info); + if (!act_client) + goto device_fail; + + act_sdev = (struct v4l2_subdev *)i2c_get_clientdata(act_client); + if (act_sdev == NULL) + goto client_fail; + + if (actuator_info->vcm_enable) { + actrl = get_actrl(act_sdev); + if (actrl) { + actrl->vcm_enable = actuator_info->vcm_enable; + actrl->vcm_pwd = actuator_info->vcm_pwd; + } + } + + return act_sdev; + +client_fail: + i2c_unregister_device(act_client); +device_fail: + i2c_put_adapter(adapter); + adapter = NULL; +probe_fail: + return NULL; +} + +static struct v4l2_subdev *msm_eeprom_probe( + struct msm_eeprom_info *eeprom_info) +{ + struct v4l2_subdev *eeprom_sdev; + struct i2c_adapter *adapter = NULL; + void *eeprom_client = NULL; + + D("%s called\n", __func__); + + if (!eeprom_info) + goto probe_fail; + + adapter = i2c_get_adapter(eeprom_info->bus_id); + if (!adapter) + goto probe_fail; + + eeprom_client = i2c_new_device(adapter, eeprom_info->board_info); + if (!eeprom_client) + goto device_fail; + + eeprom_sdev = (struct v4l2_subdev *)i2c_get_clientdata(eeprom_client); + if (eeprom_sdev == NULL) + goto client_fail; + + return eeprom_sdev; +client_fail: + pr_err("%s client_fail\n", __func__); + i2c_unregister_device(eeprom_client); +device_fail: + pr_err("%s device_fail\n", __func__); + i2c_put_adapter(adapter); + adapter = NULL; +probe_fail: + pr_err("%s probe_fail\n", __func__); + return NULL; +} + +/* 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 v4l2_subdev *sensor_sd) +{ + int rc = -EINVAL; + struct msm_camera_sensor_info *sdata; + struct msm_cam_v4l2_device *pcam; + struct msm_sensor_ctrl_t *s_ctrl; + + D("%s for %s\n", __func__, sensor_sd->name); + + /* allocate the memory for the camera device first */ + pcam = kzalloc(sizeof(*pcam), GFP_KERNEL); + if (!pcam) { + pr_err("%s: could not allocate mem for msm_cam_v4l2_device\n", + __func__); + return -ENOMEM; + } + + pcam->sensor_sdev = sensor_sd; + s_ctrl = get_sctrl(sensor_sd); + sdata = (struct msm_camera_sensor_info *) s_ctrl->sensordata; + + pcam->act_sdev = msm_actuator_probe(sdata->actuator_info); + pcam->eeprom_sdev = msm_eeprom_probe(sdata->eeprom_info); + + D("%s: pcam =0x%p\n", __func__, pcam); + + pcam->sdata = sdata; + + /* init the user count and lock*/ + pcam->use_count = 0; + mutex_init(&pcam->vid_lock); + mutex_init(&pcam->mctl_node.dev_lock); + + /* Initialize the formats supported */ + rc = msm_mctl_init_user_formats(pcam); + if (rc < 0) + goto failure; + + rc = msm_cam_dev_init(pcam); + if (rc < 0) + goto failure; + + rc = msm_setup_mctl_node(pcam); + if (rc < 0) { + pr_err("%s:failed to create mctl device: %d\n", + __func__, rc); + 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.s_mount_angle + [g_server_dev.camera_info.num_cameras] + = sdata->sensor_platform_info->mount_angle; + + g_server_dev.camera_info.is_internal_cam + [g_server_dev.camera_info.num_cameras] + = sdata->camera_type; + + g_server_dev.mctl_node_info.mctl_node_name + [g_server_dev.mctl_node_info.num_mctl_nodes] + = video_device_node_name(pcam->mctl_node.pvdev); + + pr_info("%s mctl_node_name[%d] = %s\n", __func__, + g_server_dev.mctl_node_info.num_mctl_nodes, + g_server_dev.mctl_node_info.mctl_node_name + [g_server_dev.mctl_node_info.num_mctl_nodes]); + + /*Temporary solution to store info in media device structure + until we can expand media device structure to support more + device info*/ + snprintf(pcam->media_dev.serial, + sizeof(pcam->media_dev.serial), + "%s-%d-%d", QCAMERA_NAME, + sdata->sensor_platform_info->mount_angle, + sdata->camera_type); + + g_server_dev.camera_info.num_cameras++; + g_server_dev.mctl_node_info.num_mctl_nodes++; + + 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); + + /* register the subdevice, must be done for callbacks */ + rc = msm_cam_register_subdev_node(sensor_sd, SENSOR_DEV, vnode_count); + if (rc < 0) { + D("%s sensor sub device register failed\n", + __func__); + goto failure; + } + + if (pcam->act_sdev) { + rc = v4l2_device_register_subdev(&pcam->v4l2_dev, + pcam->act_sdev); + if (rc < 0) { + D("%s actuator sub device register failed\n", + __func__); + goto failure; + } + } + + if (pcam->eeprom_sdev) { + rc = v4l2_device_register_subdev(&pcam->v4l2_dev, + pcam->eeprom_sdev); + if (rc < 0) { + D("%s eeprom sub device register failed\n", __func__); + goto failure; + } + } + + pcam->vnode_id = vnode_count++; + return rc; + +failure: + kzfree(pcam); + return rc; +} +EXPORT_SYMBOL(msm_sensor_register); + +static int __devinit msm_camera_probe(struct platform_device *pdev) +{ + 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(pdev); + 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 int __exit msm_camera_exit(struct platform_device *pdev) +{ + msm_isp_unregister(&g_server_dev); + return 0; +} + + +static struct platform_driver msm_cam_server_driver = { + .probe = msm_camera_probe, + .remove = msm_camera_exit, + .driver = { + .name = "msm_cam_server", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_camera_init(void) +{ + return platform_driver_register(&msm_cam_server_driver); +} + +static void __exit msm_cam_server_exit(void) +{ + platform_driver_unregister(&msm_cam_server_driver); +} + +module_init(msm_camera_init); +module_exit(msm_cam_server_exit); diff --git a/drivers/media/video/msm/msm.h b/drivers/media/video/msm/msm.h new file mode 100644 index 0000000000000000000000000000000000000000..688339b5e158ca25ffd1dd3d8422086c79627bef --- /dev/null +++ b/drivers/media/video/msm/msm.h @@ -0,0 +1,585 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#include +#include +#include + +#define MSM_V4L2_DIMENSION_SIZE 96 +#define MAX_DEV_NAME_LEN 50 + +#define ERR_USER_COPY(to) pr_debug("%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_CSIPHY_DRV_NAME "msm_csiphy" +#define MSM_CSID_DRV_NAME "msm_csid" +#define MSM_CSIC_DRV_NAME "msm_csic" +#define MSM_ISPIF_DRV_NAME "msm_ispif" +#define MSM_VFE_DRV_NAME "msm_vfe" +#define MSM_VPE_DRV_NAME "msm_vpe" +#define MSM_GEMINI_DRV_NAME "msm_gemini" +#define MSM_I2C_MUX_DRV_NAME "msm_cam_i2c_mux" + +#define MAX_NUM_CSIPHY_DEV 3 +#define MAX_NUM_CSID_DEV 3 +#define MAX_NUM_CSIC_DEV 3 +#define MAX_NUM_ISPIF_DEV 1 +#define MAX_NUM_VFE_DEV 2 +#define MAX_NUM_AXI_DEV 2 +#define MAX_NUM_VPE_DEV 1 + +enum msm_cam_subdev_type { + CSIPHY_DEV, + CSID_DEV, + CSIC_DEV, + ISPIF_DEV, + VFE_DEV, + AXI_DEV, + VPE_DEV, + SENSOR_DEV, + ACTUATOR_DEV, + EEPROM_DEV, + GESTURE_DEV, +}; + +/* 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); +} + +struct isp_msg_stats { + uint32_t id; + uint32_t buffer; + uint32_t frameCounter; +}; + +struct msm_free_buf { + uint8_t num_planes; + uint32_t ch_paddr[VIDEO_MAX_PLANES]; + uint32_t vb; +}; + +struct isp_msg_event { + uint32_t msg_id; + uint32_t sof_count; +}; + +struct isp_msg_output { + uint8_t output_id; + struct msm_free_buf buf; + uint32_t frameCounter; +}; + +/* message id for v4l2_subdev_notify*/ +enum msm_camera_v4l2_subdev_notify { + NOTIFY_CID_CHANGE, /* arg = msm_camera_csid_params */ + NOTIFY_ISP_MSG_EVT, /* arg = enum ISP_MESSAGE_ID */ + NOTIFY_VFE_MSG_OUT, /* arg = struct isp_msg_output */ + NOTIFY_VFE_MSG_STATS, /* arg = struct isp_msg_stats */ + NOTIFY_VFE_MSG_COMP_STATS, /* arg = struct msm_stats_buf */ + NOTIFY_VFE_BUF_EVT, /* arg = struct msm_vfe_resp */ + NOTIFY_ISPIF_STREAM, /* arg = enable parameter for s_stream */ + NOTIFY_VPE_MSG_EVT, + NOTIFY_PCLK_CHANGE, /* arg = pclk */ + NOTIFY_CSIPHY_CFG, /* arg = msm_camera_csiphy_params */ + NOTIFY_CSID_CFG, /* arg = msm_camera_csid_params */ + NOTIFY_CSIC_CFG, /* arg = msm_camera_csic_params */ + NOTIFY_VFE_BUF_FREE_EVT, /* arg = msm_camera_csic_params */ + NOTIFY_VFE_IRQ, + NOTIFY_AXI_IRQ, + NOTIFY_GESTURE_EVT, /* arg = v4l2_event */ + NOTIFY_GESTURE_CAM_EVT, /* arg = int */ + 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; +#define MSM_MAX_IMG_MODE 8 + +enum msm_buffer_state { + MSM_BUFFER_STATE_UNUSED, + MSM_BUFFER_STATE_INITIALIZED, + MSM_BUFFER_STATE_PREPARED, + MSM_BUFFER_STATE_QUEUED, + MSM_BUFFER_STATE_RESERVED, + MSM_BUFFER_STATE_DEQUEUED +}; + +/* buffer for one video frame */ +struct msm_frame_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_buffer vidbuf; + struct list_head list; + enum v4l2_mbus_pixelcode pxlcode; + enum msm_buffer_state state; + int active; +}; + +struct msm_isp_color_fmt { + char *name; + int depth; + int bitsperpxl; + u32 fourcc; + enum v4l2_mbus_pixelcode pxlcode; + enum v4l2_colorspace colorspace; +}; + +struct msm_mctl_pp_frame_info { + int user_cmd; + struct msm_pp_frame src_frame; + struct msm_pp_frame dest_frame; + struct msm_mctl_pp_frame_cmd pp_frame_cmd; +}; + +struct msm_mctl_pp_ctrl { + int pp_msg_type; + struct msm_mctl_pp_frame_info *pp_frame_info; + +}; +struct msm_mctl_pp_info { + spinlock_t lock; + uint32_t cnt; + uint32_t pp_key; + uint32_t cur_frame_id[MSM_MAX_IMG_MODE]; + struct msm_free_buf div_frame[MSM_MAX_IMG_MODE]; + struct msm_mctl_pp_ctrl pp_ctrl; + +}; +/* "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_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_buf_init)(struct msm_cam_v4l2_dev_inst *pcam); + int (*mctl_vbqueue_init)(struct msm_cam_v4l2_dev_inst *pcam, + struct vb2_queue *q, enum v4l2_buf_type type); + int (*mctl_ufmt_init)(struct msm_cam_media_controller *p_mctl); + + /* the following reflect the HW topology information*/ + struct v4l2_subdev *sensor_sdev; /* sensor sub device */ + struct v4l2_subdev *act_sdev; /* actuator sub device */ + struct v4l2_subdev *csiphy_sdev; /*csiphy sub device*/ + struct v4l2_subdev *csid_sdev; /*csid sub device*/ + struct v4l2_subdev *csic_sdev; /*csid sub device*/ + struct v4l2_subdev *ispif_sdev; /* ispif sub device */ + struct v4l2_subdev *gemini_sdev; /* gemini sub device */ + struct v4l2_subdev *vpe_sdev; /* vpe sub device */ + struct v4l2_subdev *axi_sdev; /* axi sub device */ + struct v4l2_subdev *eeprom_sdev; /* eeprom sub device */ + + struct msm_isp_ops *isp_sdev; /* isp sub device : camif/VFE */ + struct msm_cam_config_dev *config_device; + + /*mctl session control information*/ + uint8_t opencnt; /*mctl ref count*/ + const char *apps_id; /*ID for app that open this session*/ + struct mutex lock; + struct wake_lock wake_lock; /*avoid low power mode when active*/ + struct pm_qos_request pm_qos_req_list; + struct msm_mctl_pp_info pp_info; + struct msm_mctl_stats_t stats_info; /*stats pmem info*/ + uint32_t vfe_output_mode; /* VFE output mode */ + struct ion_client *client; + struct kref refcount; + + /*pcam ptr*/ + struct msm_cam_v4l2_device *pcam_ptr; + + /*sensor info*/ + struct msm_camera_sensor_info *sdata; +}; + +/* 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_cam_media_controller *mctl); + int (*isp_config)(struct msm_cam_media_controller *pmctl, + unsigned int cmd, unsigned long arg); + int (*isp_notify)(struct v4l2_subdev *sd, + unsigned int notification, void *arg); + void (*isp_release)(struct v4l2_subdev *sd); + int (*isp_pp_cmd)(struct msm_cam_media_controller *pmctl, + struct msm_mctl_pp_cmd, void *data); + + /* vfe subdevice */ + struct v4l2_subdev *sd; + struct v4l2_subdev *sd_vpe; +}; + +struct msm_isp_buf_info { + int type; + unsigned long buffer; + int fd; +}; +struct msm_cam_buf_offset { + uint32_t addr_offset; + uint32_t data_offset; +}; + +#define MSM_DEV_INST_MAX 16 +struct msm_cam_v4l2_dev_inst { + struct v4l2_fh eventHandle; + struct vb2_queue vid_bufq; + spinlock_t vq_irqlock; + struct list_head free_vq; + struct v4l2_format vid_fmt; + /* sensor 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; + /* buffer offsets, if any */ + struct msm_cam_buf_offset **buf_offset; + struct v4l2_crop crop; + int streamon; + struct msm_mem_map_info mem_map; + int is_mem_map_inst; + struct img_plane_info plane_info; + int vbqueue_initialized; +}; + +struct msm_cam_mctl_node { + /* MCTL V4l2 device */ + struct v4l2_device v4l2_dev; + struct video_device *pvdev; + 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]; + struct mutex dev_lock; + int active; + int use_count; +}; + +/* abstract camera device for each sensor successfully probed*/ +struct msm_cam_v4l2_device { + + /* device node information */ + int vnode_id; + struct v4l2_device v4l2_dev; /* V4l2 device */ + struct video_device *pvdev; /* registered as /dev/video*/ + struct msm_cam_mctl_node mctl_node; /* node for buffer management */ + struct media_device media_dev; /* node to get video node info*/ + + /* device session information */ + int use_count; + struct mutex vid_lock; + uint32_t server_queue_idx; + uint32_t mctl_handle; + 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]; + int op_mode; + + /* v4l2 format support */ + struct msm_isp_color_fmt *usr_fmts; + int num_fmts; + + struct v4l2_subdev *sensor_sdev; /* sensor sub device */ + struct v4l2_subdev *act_sdev; /* actuator sub device */ + struct v4l2_subdev *eeprom_sdev; /* actuator sub device */ + struct msm_camera_sensor_info *sdata; +}; + +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; + struct msm_mem_map_info mem_map; +}; + +/* 2 for camera, 1 for gesture */ +#define MAX_NUM_ACTIVE_CAMERA 3 + +struct msm_cam_server_queue { + uint32_t queue_active; + struct msm_device_queue ctrl_q; + struct msm_device_queue eventData_q; + struct msm_ctrl_cmd *ctrl; + uint8_t *ctrl_data; + uint32_t evt_id; +}; + +struct msm_cam_server_mctl_inst { + struct msm_cam_media_controller mctl; + uint32_t handle; +}; + +/* abstract camera server device for all sensor successfully probed*/ +struct msm_cam_server_dev { + + /* config node device*/ + struct platform_device *server_pdev; + /* server node v4l2 device */ + struct v4l2_device v4l2_dev; + struct video_device *video_dev; + struct media_device media_dev; + + /* 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_cam_server_queue server_queue[MAX_NUM_ACTIVE_CAMERA]; + uint32_t server_evt_id; + + struct msm_cam_server_mctl_inst mctl[MAX_NUM_ACTIVE_CAMERA]; + uint32_t mctl_handle_cnt; + + int use_count; + /* all the registered ISP subdevice*/ + struct msm_isp_ops *isp_subdev[MSM_MAX_CAMERA_CONFIGS]; + /* info of MCTL nodes successfully probed*/ + struct msm_mctl_node_info mctl_node_info; + struct mutex server_lock; + struct mutex server_queue_lock; + /*v4l2 subdevs*/ + struct v4l2_subdev *csiphy_device[MAX_NUM_CSIPHY_DEV]; + struct v4l2_subdev *csid_device[MAX_NUM_CSID_DEV]; + struct v4l2_subdev *csic_device[MAX_NUM_CSIC_DEV]; + struct v4l2_subdev *ispif_device; + struct v4l2_subdev *vfe_device[MAX_NUM_VFE_DEV]; + struct v4l2_subdev *axi_device[MAX_NUM_AXI_DEV]; + struct v4l2_subdev *vpe_device[MAX_NUM_VPE_DEV]; + struct v4l2_subdev *gesture_device; +}; + +/* 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_sensor_register(struct v4l2_subdev *); +int msm_isp_init_module(int g_num_config_nodes); + +int msm_mctl_init(struct msm_cam_v4l2_device *pcam); +int msm_mctl_free(struct msm_cam_v4l2_device *pcam); +int msm_mctl_buf_init(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, struct msm_free_buf *buf, + uint32_t frame_id); +int msm_mctl_buf_done_pp(struct msm_cam_media_controller *pmctl, + int msg_type, struct msm_free_buf *frame, int dirty, int node_type); +int msm_mctl_reserve_free_buf(struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, + int path, struct msm_free_buf *free_buf); +int msm_mctl_release_free_buf(struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, + int path, struct msm_free_buf *free_buf); +/*Memory(PMEM) functions*/ +int msm_register_pmem(struct hlist_head *ptype, void __user *arg, + struct ion_client *client); +int msm_pmem_table_del(struct hlist_head *ptype, void __user *arg, + struct ion_client *client); +int msm_pmem_region_get_phy_addr(struct hlist_head *ptype, + struct msm_mem_map_info *mem_map, int32_t *phyaddr); +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); +unsigned long msm_pmem_stats_vtop_lookup( + struct msm_cam_media_controller *mctl, + unsigned long buffer, + int fd); +unsigned long msm_pmem_stats_ptov_lookup( + struct msm_cam_media_controller *mctl, + unsigned long addr, int *fd); + +int msm_vfe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl); +void msm_vfe_subdev_release(struct v4l2_subdev *sd); + +int msm_isp_subdev_ioctl(struct v4l2_subdev *sd, + struct msm_vfe_cfg_cmd *cfgcmd, void *data); +int msm_vpe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl); +int msm_gemini_subdev_init(struct v4l2_subdev *gemini_sd); +void msm_vpe_subdev_release(void); +void msm_gemini_subdev_release(struct v4l2_subdev *gemini_sd); +int msm_mctl_is_pp_msg_type(struct msm_cam_media_controller *p_mctl, + int msg_type); +int msm_mctl_do_pp(struct msm_cam_media_controller *p_mctl, + int msg_type, uint32_t y_phy, uint32_t frame_id); +int msm_mctl_pp_ioctl(struct msm_cam_media_controller *p_mctl, + unsigned int cmd, unsigned long arg); +int msm_mctl_pp_notify(struct msm_cam_media_controller *pmctl, + struct msm_mctl_pp_frame_info *pp_frame_info); +int msm_mctl_img_mode_to_inst_index(struct msm_cam_media_controller *pmctl, + int out_type, int node_type); +struct msm_frame_buffer *msm_mctl_buf_find( + struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, int del_buf, + int msg_type, struct msm_free_buf *fbuf); +void msm_mctl_gettimeofday(struct timeval *tv); +struct msm_frame_buffer *msm_mctl_get_free_buf( + struct msm_cam_media_controller *pmctl, + int msg_type); +int msm_mctl_put_free_buf( + struct msm_cam_media_controller *pmctl, + int msg_type, struct msm_frame_buffer *buf); +int msm_mctl_check_pp(struct msm_cam_media_controller *p_mctl, + int msg_type, int *pp_divert_type, int *pp_type); +int msm_mctl_do_pp_divert( + struct msm_cam_media_controller *p_mctl, + int msg_type, struct msm_free_buf *fbuf, + uint32_t frame_id, int pp_type); +int msm_mctl_buf_del(struct msm_cam_media_controller *pmctl, + int msg_type, + struct msm_frame_buffer *my_buf); +int msm_mctl_pp_release_free_frame( + struct msm_cam_media_controller *p_mctl, + void __user *arg); +int msm_mctl_pp_reserve_free_frame( + struct msm_cam_media_controller *p_mctl, + void __user *arg); +int msm_mctl_set_pp_key(struct msm_cam_media_controller *p_mctl, + void __user *arg); +int msm_mctl_pp_done( + struct msm_cam_media_controller *p_mctl, + void __user *arg); +int msm_mctl_pp_divert_done( + struct msm_cam_media_controller *p_mctl, + void __user *arg); +int msm_setup_v4l2_event_queue(struct v4l2_fh *eventHandle, + struct video_device *pvdev); +int msm_setup_mctl_node(struct msm_cam_v4l2_device *pcam); +struct msm_cam_v4l2_dev_inst *msm_mctl_get_pcam_inst( + struct msm_cam_media_controller *pmctl, + int image_mode); +int msm_mctl_buf_return_buf(struct msm_cam_media_controller *pmctl, + int image_mode, struct msm_frame_buffer *buf); +int msm_mctl_pp_mctl_divert_done(struct msm_cam_media_controller *p_mctl, + void __user *arg); +void msm_release_ion_client(struct kref *ref); +int msm_cam_register_subdev_node(struct v4l2_subdev *sd, + enum msm_cam_subdev_type sdev_type, uint8_t index); +uint32_t msm_camera_get_mctl_handle(void); +struct msm_cam_media_controller *msm_camera_get_mctl(uint32_t handle); +void msm_camera_free_mctl(uint32_t handle); +int msm_server_open_client(int *p_qidx); +int msm_server_send_ctrl(struct msm_ctrl_cmd *out, int ctrl_id); +int msm_server_close_client(int idx); +int msm_cam_server_open_mctl_session(struct msm_cam_v4l2_device *pcam, + int *p_active); +int msm_cam_server_close_mctl_session(struct msm_cam_v4l2_device *pcam); +#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 0000000000000000000000000000000000000000..eaceceb1234a2f5d84420767ef36436ef705600a --- /dev/null +++ b/drivers/media/video/msm/msm_axi_qos.c @@ -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. + * + */ + +#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_prepare(ebi1_clk); + 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_unprepare(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 0000000000000000000000000000000000000000..e609d6e30526309196b429045f57314cca223953 --- /dev/null +++ b/drivers/media/video/msm/msm_camera.c @@ -0,0 +1,4124 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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]; + +struct ion_client *client_for_ion; + +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) \ + 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->planar0_off < len && + info->planar1_off < len && + info->planar2_off < len) + return 0; + + pr_err("%s: check failed: off %d len %d y 0x%x cbcr_p1 0x%x p2_add 0x%x(total len %d)\n", + __func__, + info->offset, + info->len, + info->planar0_off, + info->planar1_off, + info->planar2_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) +{ + unsigned long paddr; +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + struct file *file; + unsigned long kvstart; +#endif + unsigned long len; + int rc = -ENOMEM; + struct msm_pmem_region *region; + unsigned long flags; + + region = kmalloc(sizeof(struct msm_pmem_region), GFP_KERNEL); + if (!region) + goto out; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + region->handle = ion_import_fd(client_for_ion, info->fd); + if (IS_ERR_OR_NULL(region->handle)) + goto out1; + ion_phys(client_for_ion, region->handle, + &paddr, (size_t *)&len); +#else + 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); + goto out1; + } + region->file = file; +#endif + if (!info->len) + info->len = len; + + rc = check_pmem_info(info, len); + if (rc < 0) + goto out2; + + 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); + rc = -EINVAL; + goto out2; + } + spin_unlock_irqrestore(pmem_spinlock, flags); + + spin_lock_irqsave(pmem_spinlock, flags); + INIT_HLIST_NODE(®ion->list); + + region->paddr = paddr; + region->len = len; + 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 p0_add = 0x%x" + "p1_addr = 0x%x p2_addr = 0x%x\n", + __func__, info->type, paddr, (unsigned long)info->vaddr, + info->planar0_off, info->planar1_off, info->planar2_off); + return 0; +out2: +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(client_for_ion, region->handle); +#else + put_pmem_file(region->file); +#endif +out1: + kfree(region); +out: + return rc; +} + +/* 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 p0addr, + unsigned long p1addr, + unsigned long p2addr, + 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 (p0addr == (region->paddr + region->info.planar0_off) && + p1addr == (region->paddr + region->info.planar1_off) && + p2addr == (region->paddr + region->info.planar2_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 plane0 addr = 0x%lx, plane1 addr = 0x%lx plane2 addr = 0x%lx\n", + __func__, p0addr, p1addr, p2addr); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) { + pr_err("listed p0addr 0x%lx, p1addr 0x%lx, p2addr 0x%lx, active = %d", + (region->paddr + region->info.planar0_off), + (region->paddr + region->info.planar1_off), + (region->paddr + region->info.planar2_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 p0_phy, + 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 (p0_phy == (region->paddr + region->info.planar0_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 p0_off, uint32_t p1_off, + uint32_t p2_off, 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.planar0_off == p0_off) && + (region->info.planar1_off == p1_off) && + (region->info.planar2_off == p2_off) && + (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, p0_off %d p1_off %d\n", + __func__, buffer, p0_off, p1_off); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) { + pr_err("%s, listed vaddr 0x%lx, r_p0 = 0x%x p0_off 0x%x" + "r_p1 = 0x%x, p1_off 0x%x, r_p2 = 0x%x, p2_off = 0x%x" + " active = %d\n", __func__, buffer, + region->info.planar0_off, + p0_off, region->info.planar1_off, + p1_off, region->info.planar2_off, p2_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); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(client_for_ion, region->handle); +#else + put_pmem_file(region->file); +#endif + 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); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(client_for_ion, region->handle); +#else + put_pmem_file(region->file); +#endif + 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); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(client_for_ion, region->handle); +#else + put_pmem_file(region->file); +#endif + 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; + CDBG("%s, pphy->p2_phy = 0x%x\n", __func__, pphy->p2_phy); + + rc = msm_pmem_frame_ptov_lookup(sync, + pphy->p0_phy, + pphy->p1_phy, + pphy->p2_phy, + &pmem_info, + 1); /* Clear the active flag */ + + if (rc < 0) { + pr_err("%s: cannot get frame, invalid lookup address" + "plane0 add %x plane1 add %x plane2 add%x\n", + __func__, + pphy->p0_phy, + pphy->p1_phy, + pphy->p2_phy); + goto err; + } + + frame->ts = qcmd->ts; + frame->buffer = (unsigned long)pmem_info.vaddr; + frame->planar0_off = pmem_info.planar0_off; + frame->planar1_off = pmem_info.planar1_off; + frame->planar2_off = pmem_info.planar2_off; + frame->fd = pmem_info.fd; + frame->path = vdata->phy.output_id; + frame->frame_id = vdata->phy.frame_id; + CDBG("%s: plane0 %x, plane1 %x, plane2 %x,qcmd %x, virt_addr %x\n", + __func__, pphy->p0_phy, pphy->p1_phy, pphy->p2_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; + } + msm_queue_drain(&ctrl_pmsm->ctrl_q, list_control); + 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.p0_phy, + data->phy.p1_phy, data->phy.p2_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.planar0_off = pinfo.planar0_off; + buf.fmain.planar1_off = pinfo.planar1_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.p0_phy, + data->phy.p1_phy, data->phy.p2_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.p0_phy, + data->phy.p1_phy, data->phy.p2_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.planar0_off = pinfo.planar0_off; + buf.buf_info.planar1_off = pinfo.planar1_off; + buf.buf_info.planar2_off = pinfo.planar2_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; + stats.awb_ymin = data->stats_msg.awb_ymin; + + 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.awb_ymin = data->stats_msg.awb_ymin; + 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_ALL_CHNLS: + 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_ALL_CHNLS: + 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->planar0_off, pb->planar1_off, pb->planar2_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->planar0_off, pb->planar1_off, pb->planar2_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: + case CMD_AXI_CFG_VIDEO_ALL_CHNLS: + case CMD_AXI_CFG_ZSL_ALL_CHNLS: + 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->p0_phy, + &pmem_info, + 1); /* mark pic frame in use */ + + if (rc < 0) { + pr_err("%s: cannot get pic frame, invalid lookup" + " address p0_phy add %x p1_phy add%x\n", + __func__, pphy->p0_phy, pphy->p1_phy); + goto err; + } + + frame->ts = qcmd->ts; + frame->buffer = (unsigned long)pmem_info.vaddr; + frame->planar0_off = pmem_info.planar0_off; + frame->planar1_off = pmem_info.planar1_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:p0_phy add %x, p0_phy add %x, qcmd %x, virt_addr %x\n", + __func__, pphy->p0_phy, + pphy->p1_phy, (int) qcmd, (int) frame->buffer); + } else { /* PP */ + pframe = (struct msm_frame *)(qcmd->command); + frame->ts = qcmd->ts; + frame->buffer = pframe->buffer; + frame->planar0_off = pframe->planar0_off; + frame->planar1_off = pframe->planar1_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->planar0_off, frame->planar1_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)) { + CDBG("%s: more than one PP request!\n", + __func__); + } + if (sync->pp_mask) { + if (enable) { + CDBG("%s: postproc %x is already enabled\n", + __func__, sync->pp_mask & enable); + } else { + sync->pp_mask &= enable; + CDBG("%s: sync->pp_mask %d enable %d\n", + __func__, sync->pp_mask, enable); + } + } + + 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.p0_phy, + stereo_frame_half.L.buf_p0_off, + stereo_frame_half.L.buf_p1_off); + + sync->vpefn.vpe_cfg_offset(stereo_frame_half.packing, + vfe_rp->phy.p0_phy + stereo_frame_half.L.buf_p0_off, + vfe_rp->phy.p1_phy + stereo_frame_half.L.buf_p1_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.planar0_off, + stereo_frame_half.buf_info.planar1_off, + stereo_frame_half.buf_info.planar2_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_p0_off, + st_pphy + stereo_frame_half.R.buf_p1_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); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(client_for_ion, region->handle); +#else + put_pmem_file(region->file); +#endif + 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); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_free(client_for_ion, region->handle); +#else + put_pmem_file(region->file); +#endif + 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); + ion_client_destroy(client_for_ion); + + 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: p0_add %x p1_add %x\n", + __func__, + vdata->phy.p0_phy, + vdata->phy.p1_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.p0_phy = vdata->phy.p0_phy; + vdata->vpe_bf.p1_phy = vdata->phy.p1_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.p0_phy, + vdata->phy.p1_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 if (sync->event_q.len > 100) { + pr_err("%s, Error Event Queue limit exceeded f_q = %d, e_q = %d\n", + __func__, sync->frame_q.len, sync->event_q.len); + qcmd->error_code = 0xffffffff; + qcmd->command = NULL; + 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); + } + +} + +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; + } + vdata->phy.p2_phy = vdata->phy.p0_phy; + 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++; + client_for_ion = msm_ion_client_create(-1, "camera"); + +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_frame(struct inode *inode, struct file *filep) +{ + struct msm_cam_device *pmsm = + container_of(inode->i_cdev, struct msm_cam_device, cdev); + msm_queue_drain(&pmsm->sync->frame_q, list_frame); + return msm_open_common(inode, filep, 1, 0); +} + +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_frame, + .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_gesture.c b/drivers/media/video/msm/msm_gesture.c new file mode 100644 index 0000000000000000000000000000000000000000..258c73dd9f3147b540eb59ef3c94306b1ee517c5 --- /dev/null +++ b/drivers/media/video/msm/msm_gesture.c @@ -0,0 +1,497 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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.h" + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) pr_debug("msm_gesture: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +struct msm_gesture_ctrl { + int queue_id; + atomic_t active; + struct v4l2_ctrl_handler ctrl_handler; + int num_ctrls; + struct v4l2_fh *p_eventHandle; + struct v4l2_subdev *sd; + struct msm_ges_evt event; + int camera_opened; +}; + +static struct msm_gesture_ctrl g_gesture_ctrl; + +int msm_gesture_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + D("%s\n", __func__); + if (sub->type == V4L2_EVENT_ALL) + sub->type = MSM_GES_APP_NOTIFY_EVENT; + return v4l2_event_subscribe(fh, sub, 30); +} + +static int msm_gesture_send_ctrl(struct msm_gesture_ctrl *p_gesture_ctrl, + int type, void *value, int length, uint32_t timeout) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + D("%s qid %d\n", __func__, p_gesture_ctrl->queue_id); + ctrlcmd.type = type; + ctrlcmd.timeout_ms = timeout; + ctrlcmd.length = length; + ctrlcmd.value = value; + ctrlcmd.vnode_id = 0; + ctrlcmd.queue_idx = p_gesture_ctrl->queue_id; + ctrlcmd.config_ident = 0; + + rc = msm_server_send_ctrl(&ctrlcmd, MSM_GES_RESP_V4L2); + return rc; +} + +static int msm_gesture_proc_ctrl_cmd(struct msm_gesture_ctrl *p_gesture_ctrl, + struct v4l2_control *ctrl) +{ + int rc = 0; + struct msm_ctrl_cmd *tmp_cmd = NULL; + 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; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_send_ctrl((struct msm_ctrl_cmd *)ctrl_data, + MSM_GES_RESP_V4L2); + D("%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: + D("%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_gesture_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + int rc = 0; + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + D("%s ctrl->id %d\n", __func__, ctrl->id); + rc = msm_gesture_proc_ctrl_cmd(p_gesture_ctrl, ctrl); + if (rc != 0) { + pr_err("%s set ctrl failed %d\n", __func__, rc); + return -EINVAL; + } + return rc; +} + +static int msm_gesture_s_ctrl_ops(struct v4l2_ctrl *ctrl) +{ + int rc = 0; + struct v4l2_control control; + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + control.id = ctrl->id; + control.value = ctrl->val; + D("%s ctrl->id 0x%x\n", __func__, ctrl->id); + rc = msm_gesture_proc_ctrl_cmd(p_gesture_ctrl, &control); + if (rc != 0) { + pr_err("%s proc ctrl failed %d\n", __func__, rc); + return -EINVAL; + } + return rc; +} + +static int msm_gesture_s_ctrl_ext(struct v4l2_subdev *sd, + struct v4l2_ext_controls *ctrls) +{ + int rc = 0; + struct v4l2_control control; + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + if ((ctrls->count < 1) || (NULL == ctrls->controls)) { + pr_err("%s invalid ctrl failed\n", __func__); + return -EINVAL; + } + control.id = ctrls->controls->id; + control.value = ctrls->controls->value; + D("%s ctrl->id %d\n", __func__, control.id); + rc = msm_gesture_proc_ctrl_cmd(p_gesture_ctrl, &control); + if (rc != 0) { + pr_err("%s proc ctrl failed %d\n", __func__, rc); + return -EINVAL; + } + return rc; +} + +static int msm_gesture_handle_event(struct v4l2_subdev *sd, + struct msm_gesture_ctrl *p_gesture_ctrl, void* arg) +{ + int rc = 0; + struct v4l2_event *evt = (struct v4l2_event *)arg; + struct msm_ges_evt *p_ges_evt = NULL; + D("%s: Received gesture evt 0x%x ", __func__, evt->type); + p_gesture_ctrl->event.evt_len = 0; + p_gesture_ctrl->event.evt_data = NULL; + if (0 != evt->u.data[0]) { + p_ges_evt = (struct msm_ges_evt *)evt->u.data; + D("%s: event data %p len %d", __func__, + p_ges_evt->evt_data, + p_ges_evt->evt_len); + + if (p_ges_evt->evt_len > 0) { + p_gesture_ctrl->event.evt_data = + kzalloc(p_ges_evt->evt_len, GFP_KERNEL); + + if (NULL == p_gesture_ctrl->event.evt_data) { + pr_err("%s: cannot allocate event", __func__); + rc = -ENOMEM; + } else { + if (copy_from_user( + (void *)p_gesture_ctrl->event.evt_data, + (void __user *)p_ges_evt->evt_data, + p_ges_evt->evt_len)) { + pr_err("%s: copy_from_user failed", + __func__); + rc = -EFAULT; + } else { + D("%s: copied the event", __func__); + p_gesture_ctrl->event.evt_len = + p_ges_evt->evt_len; + } + } + } + } + + if (rc == 0) { + ktime_get_ts(&evt->timestamp); + v4l2_event_queue(sd->devnode, evt); + } + D("%s: exit rc %d ", __func__, rc); + return rc; +} + +static int msm_gesture_get_evt_payload(struct v4l2_subdev *sd, + struct msm_gesture_ctrl *p_gesture_ctrl, void* arg) +{ + int rc = 0; + struct msm_ges_evt *p_ges_evt = (struct msm_ges_evt *)arg; + D("%s: enter ", __func__); + if (NULL != p_gesture_ctrl->event.evt_data) { + D("%s: event data %p len %d", __func__, + p_gesture_ctrl->event.evt_data, + p_gesture_ctrl->event.evt_len); + + if (copy_to_user((void __user *)p_ges_evt->evt_data, + p_gesture_ctrl->event.evt_data, + p_gesture_ctrl->event.evt_len)) { + pr_err("%s: copy_to_user failed.\n", __func__); + rc = -EFAULT; + } else { + D("%s: copied the event", __func__); + p_ges_evt->evt_len = p_gesture_ctrl->event.evt_len; + } + } + D("%s: exit rc %d ", __func__, rc); + return rc; +} + +static int msm_gesture_handle_cam_event(struct v4l2_subdev *sd, + struct msm_gesture_ctrl *p_gesture_ctrl, int cam_evt) +{ + int rc = 0; + D("%s: cam_evt %d ", __func__, cam_evt); + + if ((cam_evt != MSM_V4L2_GES_CAM_OPEN) + && (cam_evt != MSM_V4L2_GES_CAM_CLOSE)) { + pr_err("%s: error invalid event %d ", __func__, cam_evt); + return -EINVAL; + } + + p_gesture_ctrl->camera_opened = + (cam_evt == MSM_V4L2_GES_CAM_OPEN); + + if (atomic_read(&p_gesture_ctrl->active) == 0) { + D("%s gesture not active\n", __func__); + return 0; + } + + rc = msm_gesture_send_ctrl(p_gesture_ctrl, cam_evt, NULL, + 0, 2000); + if (rc != 0) { + pr_err("%s gesture ctrl failed %d\n", __func__, rc); + rc = -EINVAL; + } + D("%s exit rc %d\n", __func__, rc); + return rc; +} + +long msm_gesture_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + D("%s\n", __func__); + switch (cmd) { + case MSM_GES_IOCTL_CTRL_COMMAND: { + struct v4l2_control *ctrl = (struct v4l2_control *)arg; + D("%s MSM_GES_IOCTL_CTRL_COMMAND arg %p size %d\n", __func__, + arg, sizeof(ctrl)); + rc = msm_gesture_s_ctrl(sd, ctrl); + break; + } + case VIDIOC_MSM_GESTURE_EVT: { + rc = msm_gesture_handle_event(sd, p_gesture_ctrl, arg); + break; + } + case VIDIOC_MSM_GESTURE_CAM_EVT: { + int cam_evt = *((int *)arg); + rc = msm_gesture_handle_cam_event(sd, p_gesture_ctrl, cam_evt); + break; + } + case MSM_GES_GET_EVT_PAYLOAD: { + rc = msm_gesture_get_evt_payload(sd, p_gesture_ctrl, arg); + break; + } + default: + pr_err("%s: Invalid ioctl %d", __func__, cmd); + break; + } + D("%s exit rc %d\n", __func__, rc); + return rc; +} + +static const struct v4l2_ctrl_ops msm_gesture_ctrl_ops = { + .s_ctrl = msm_gesture_s_ctrl_ops, +}; + +static const struct v4l2_ctrl_config msm_gesture_ctrl_filter = { + .ops = &msm_gesture_ctrl_ops, + .id = MSM_GESTURE_CID_CTRL_CMD, + .name = "Gesture ctrl", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_SLIDER, + .max = 0x7fffffff, + .step = 1, + .min = 0x80000000, +}; + +static int msm_gesture_init_ctrl(struct v4l2_subdev *sd, + struct msm_gesture_ctrl *p_gesture_ctrl) +{ + int rc = 0; + p_gesture_ctrl->num_ctrls = 1; + p_gesture_ctrl->ctrl_handler.error = 0; + v4l2_ctrl_handler_init(&p_gesture_ctrl->ctrl_handler, + p_gesture_ctrl->num_ctrls); + v4l2_ctrl_new_custom(&p_gesture_ctrl->ctrl_handler, + &msm_gesture_ctrl_filter, p_gesture_ctrl); + if (p_gesture_ctrl->ctrl_handler.error) { + int err = p_gesture_ctrl->ctrl_handler.error; + D("%s: error adding control %d", __func__, err); + p_gesture_ctrl->ctrl_handler.error = 0; + } + sd->ctrl_handler = &p_gesture_ctrl->ctrl_handler; + return rc; +} + +static int msm_gesture_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int rc = 0, rc_err = 0; + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + D("%s\n", __func__); + if (atomic_read(&p_gesture_ctrl->active) != 0) { + pr_err("%s already opened\n", __func__); + return -EINVAL; + } + memset(&p_gesture_ctrl->event, 0x0, sizeof(struct msm_ges_evt)); + rc = msm_server_open_client(&p_gesture_ctrl->queue_id); + if (rc != 0) { + pr_err("%s open failed %d\n", __func__, rc); + rc = -EINVAL; + goto err; + } + + rc = msm_gesture_init_ctrl(sd, p_gesture_ctrl); + if (rc != 0) { + pr_err("%s init ctrl failed %d\n", __func__, rc); + rc = -EINVAL; + goto err; + } + + rc = msm_gesture_send_ctrl(p_gesture_ctrl, MSM_V4L2_GES_OPEN, NULL, + 0, 10000); + if (rc != 0) { + pr_err("%s gesture ctrl failed %d\n", __func__, rc); + rc = -EINVAL; + goto err; + } + + atomic_inc(&p_gesture_ctrl->active); + + return rc; + +err: + rc_err = msm_server_close_client(p_gesture_ctrl->queue_id); + if (rc_err != 0) + pr_err("%s failed %d\n", __func__, rc); + return rc; +} + +static int msm_gesture_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int rc = 0; + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + D("%s\n", __func__); + if (atomic_read(&p_gesture_ctrl->active) == 0) { + pr_err("%s already closed\n", __func__); + return -EINVAL; + } + + rc = msm_gesture_send_ctrl(p_gesture_ctrl, MSM_V4L2_GES_CLOSE, NULL, + 0, 10000); + if (rc != 0) + pr_err("%s gesture ctrl failed %d\n", __func__, rc); + + rc = msm_server_close_client(p_gesture_ctrl->queue_id); + if (rc != 0) + pr_err("%s failed %d\n", __func__, rc); + + v4l2_ctrl_handler_free(&p_gesture_ctrl->ctrl_handler); + kfree(p_gesture_ctrl->event.evt_data); + + atomic_dec(&p_gesture_ctrl->active); + g_gesture_ctrl.queue_id = -1; + return 0; +} + +static struct v4l2_subdev_core_ops msm_gesture_core_ops = { + .s_ctrl = msm_gesture_s_ctrl, + .s_ext_ctrls = msm_gesture_s_ctrl_ext, + .ioctl = msm_gesture_ioctl, + .subscribe_event = msm_gesture_subscribe_event, +}; + +static struct v4l2_subdev_video_ops msm_gesture_video_ops; + +static struct v4l2_subdev_ops msm_gesture_subdev_ops = { + .core = &msm_gesture_core_ops, + .video = &msm_gesture_video_ops, +}; + +static const struct v4l2_subdev_internal_ops msm_gesture_internal_ops = { + .open = msm_gesture_open, + .close = msm_gesture_close, +}; + +static int msm_gesture_node_register(void) +{ + struct msm_gesture_ctrl *p_gesture_ctrl = &g_gesture_ctrl; + struct v4l2_subdev *gesture_subdev = + kzalloc(sizeof(struct v4l2_subdev), GFP_KERNEL); + D("%s\n", __func__); + if (!gesture_subdev) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + }; + + v4l2_subdev_init(gesture_subdev, &msm_gesture_subdev_ops); + gesture_subdev->internal_ops = &msm_gesture_internal_ops; + gesture_subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(gesture_subdev->name, + sizeof(gesture_subdev->name), "gesture"); + + media_entity_init(&gesture_subdev->entity, 0, NULL, 0); + gesture_subdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L; + gesture_subdev->entity.group_id = GESTURE_DEV; + gesture_subdev->entity.name = gesture_subdev->name; + + /* events */ + gesture_subdev->flags |= V4L2_SUBDEV_FL_HAS_EVENTS; + + msm_cam_register_subdev_node(gesture_subdev, GESTURE_DEV, 0); + + gesture_subdev->entity.revision = gesture_subdev->devnode->num; + + atomic_set(&p_gesture_ctrl->active, 0); + p_gesture_ctrl->queue_id = -1; + p_gesture_ctrl->event.evt_data = NULL; + p_gesture_ctrl->event.evt_len = 0; + return 0; +} + +static int __init msm_gesture_init_module(void) +{ + return msm_gesture_node_register(); +} + +module_init(msm_gesture_init_module); +MODULE_DESCRIPTION("MSM Gesture driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_isp.c b/drivers/media/video/msm/msm_isp.c new file mode 100644 index 0000000000000000000000000000000000000000..148f8b5b21296f1793fc8aca3b606211aadfceec --- /dev/null +++ b/drivers/media/video/msm/msm_isp.c @@ -0,0 +1,790 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 MSM_FRAME_AXI_MAX_BUF 32 + +/* + * This function executes in interrupt context. + */ + +void *msm_isp_sync_alloc(int size, + 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); + } +} + +static int msm_isp_notify_VFE_BUF_FREE_EVT(struct v4l2_subdev *sd, void *arg) +{ + struct msm_vfe_cfg_cmd cfgcmd; + struct msm_camvfe_params vfe_params; + int rc; + + cfgcmd.cmd_type = CMD_VFE_BUFFER_RELEASE; + cfgcmd.value = NULL; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = NULL; + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + return 0; +} + +int msm_isp_vfe_msg_to_img_mode(struct msm_cam_media_controller *pmctl, + int vfe_msg) +{ + int image_mode; + if (vfe_msg == VFE_MSG_OUTPUT_PRIMARY) { + switch (pmctl->vfe_output_mode) { + case VFE_OUTPUTS_MAIN_AND_PREVIEW: + case VFE_OUTPUTS_MAIN_AND_VIDEO: + case VFE_OUTPUTS_MAIN_AND_THUMB: + case VFE_OUTPUTS_RAW: + case VFE_OUTPUTS_JPEG_AND_THUMB: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_MAIN; + break; + case VFE_OUTPUTS_THUMB_AND_MAIN: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL; + break; + case VFE_OUTPUTS_VIDEO: + case VFE_OUTPUTS_VIDEO_AND_PREVIEW: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_VIDEO; + break; + case VFE_OUTPUTS_PREVIEW: + case VFE_OUTPUTS_PREVIEW_AND_VIDEO: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW; + break; + default: + image_mode = -1; + break; + } + } else if (vfe_msg == VFE_MSG_OUTPUT_SECONDARY) { + switch (pmctl->vfe_output_mode) { + case VFE_OUTPUTS_MAIN_AND_PREVIEW: + case VFE_OUTPUTS_VIDEO_AND_PREVIEW: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW; + break; + case VFE_OUTPUTS_MAIN_AND_VIDEO: + case VFE_OUTPUTS_PREVIEW_AND_VIDEO: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_VIDEO; + break; + case VFE_OUTPUTS_MAIN_AND_THUMB: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL; + break; + case VFE_OUTPUTS_THUMB_AND_MAIN: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_MAIN; + break; + case VFE_OUTPUTS_JPEG_AND_THUMB: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL; + break; + case VFE_OUTPUTS_PREVIEW: + case VFE_OUTPUTS_VIDEO: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW; + break; + default: + image_mode = -1; + break; + } + } else + image_mode = -1; + + D("%s Selected image mode %d vfe output mode %d, vfe msg %d\n", + __func__, image_mode, pmctl->vfe_output_mode, vfe_msg); + return image_mode; +} + +static int msm_isp_notify_VFE_BUF_EVT(struct v4l2_subdev *sd, void *arg) +{ + int rc = -EINVAL, image_mode; + struct msm_vfe_resp *vdata = (struct msm_vfe_resp *)arg; + struct msm_free_buf free_buf, temp_free_buf; + struct msm_camvfe_params vfe_params; + struct msm_vfe_cfg_cmd cfgcmd; + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *)v4l2_get_subdev_hostdata(sd); + struct msm_cam_v4l2_device *pcam = pmctl->pcam_ptr; + + int vfe_id = vdata->evt_msg.msg_id; + if (!pcam) { + pr_debug("%s pcam is null. return\n", __func__); + msm_isp_sync_free(vdata); + return rc; + } + /* Convert the vfe msg to the image mode */ + image_mode = msm_isp_vfe_msg_to_img_mode(pmctl, vfe_id); + BUG_ON(image_mode < 0); + switch (vdata->type) { + case VFE_MSG_V32_START: + case VFE_MSG_V32_START_RECORDING: + case VFE_MSG_V2X_PREVIEW: + D("%s Got V32_START_*: Getting ping addr id = %d", + __func__, vfe_id); + msm_mctl_reserve_free_buf(pmctl, NULL, + image_mode, &free_buf); + cfgcmd.cmd_type = CMD_CONFIG_PING_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + msm_mctl_reserve_free_buf(pmctl, NULL, + image_mode, &free_buf); + cfgcmd.cmd_type = CMD_CONFIG_PONG_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + break; + case VFE_MSG_V32_CAPTURE: + case VFE_MSG_V2X_CAPTURE: + pr_debug("%s Got V32_CAPTURE: getting buffer for id = %d", + __func__, vfe_id); + msm_mctl_reserve_free_buf(pmctl, NULL, + image_mode, &free_buf); + cfgcmd.cmd_type = CMD_CONFIG_PING_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + temp_free_buf = free_buf; + if (msm_mctl_reserve_free_buf(pmctl, NULL, + image_mode, &free_buf)) { + /* Write the same buffer into PONG */ + free_buf = temp_free_buf; + } + cfgcmd.cmd_type = CMD_CONFIG_PONG_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + break; + case VFE_MSG_V32_JPEG_CAPTURE: + D("%s:VFE_MSG_V32_JPEG_CAPTURE vdata->type %d\n", __func__, + vdata->type); + free_buf.num_planes = 1; + free_buf.ch_paddr[0] = IMEM_Y_PING_OFFSET; + free_buf.ch_paddr[1] = IMEM_CBCR_PING_OFFSET; + cfgcmd.cmd_type = CMD_CONFIG_PING_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + D("%s:VFE_MSG_V32_JPEG_CAPTURE y_ping=%x cbcr_ping=%x\n", + __func__, free_buf.ch_paddr[0], free_buf.ch_paddr[1]); + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + /* Write the same buffer into PONG */ + free_buf.ch_paddr[0] = IMEM_Y_PONG_OFFSET; + free_buf.ch_paddr[1] = IMEM_CBCR_PONG_OFFSET; + cfgcmd.cmd_type = CMD_CONFIG_PONG_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + D("%s:VFE_MSG_V32_JPEG_CAPTURE y_pong=%x cbcr_pong=%x\n", + __func__, free_buf.ch_paddr[0], free_buf.ch_paddr[1]); + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + break; + case VFE_MSG_OUTPUT_IRQ: + D("%s Got OUTPUT_IRQ: Getting free buf id = %d", + __func__, vfe_id); + msm_mctl_reserve_free_buf(pmctl, NULL, + image_mode, &free_buf); + cfgcmd.cmd_type = CMD_CONFIG_FREE_BUF_ADDR; + cfgcmd.value = &vfe_id; + vfe_params.vfe_cfg = &cfgcmd; + vfe_params.data = (void *)&free_buf; + rc = v4l2_subdev_call(sd, core, ioctl, 0, &vfe_params); + break; + default: + pr_err("%s: Invalid vdata type: %d\n", __func__, vdata->type); + break; + } + return rc; +} + +/* + * This function executes in interrupt context. + */ +static int msm_isp_notify_vfe(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + int rc = 0; + struct v4l2_event v4l2_evt; + struct msm_isp_event_ctrl *isp_event; + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *)v4l2_get_subdev_hostdata(sd); + struct msm_free_buf buf; + + if (!pmctl) { + pr_err("%s: no context in dsp callback.\n", __func__); + rc = -EINVAL; + return rc; + } + + if (notification == NOTIFY_VFE_BUF_EVT) + return msm_isp_notify_VFE_BUF_EVT(sd, arg); + + if (notification == NOTIFY_VFE_BUF_FREE_EVT) + return msm_isp_notify_VFE_BUF_FREE_EVT(sd, arg); + + isp_event = kzalloc(sizeof(struct msm_isp_event_ctrl), GFP_ATOMIC); + if (!isp_event) { + pr_err("%s Insufficient memory. return", __func__); + return -ENOMEM; + } + + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_STAT_EVT_MSG; + v4l2_evt.id = 0; + + *((uint32_t *)v4l2_evt.u.data) = (uint32_t)isp_event; + + isp_event->resptype = MSM_CAM_RESP_STAT_EVT_MSG; + isp_event->isp_data.isp_msg.type = MSM_CAMERA_MSG; + isp_event->isp_data.isp_msg.len = 0; + + switch (notification) { + case NOTIFY_ISP_MSG_EVT: { + struct isp_msg_event *isp_msg = (struct isp_msg_event *)arg; + + isp_event->isp_data.isp_msg.msg_id = isp_msg->msg_id; + isp_event->isp_data.isp_msg.frame_id = isp_msg->sof_count; + getnstimeofday(&(isp_event->isp_data.isp_msg.timestamp)); + break; + } + case NOTIFY_VFE_MSG_OUT: { + uint8_t msgid; + struct isp_msg_output *isp_output = + (struct isp_msg_output *)arg; + switch (isp_output->output_id) { + case MSG_ID_OUTPUT_P: + msgid = VFE_MSG_OUTPUT_P; + break; + case MSG_ID_OUTPUT_V: + msgid = VFE_MSG_OUTPUT_V; + break; + case MSG_ID_OUTPUT_T: + msgid = VFE_MSG_OUTPUT_T; + break; + case MSG_ID_OUTPUT_S: + msgid = VFE_MSG_OUTPUT_S; + break; + case MSG_ID_OUTPUT_PRIMARY: + msgid = VFE_MSG_OUTPUT_PRIMARY; + break; + case MSG_ID_OUTPUT_SECONDARY: + msgid = VFE_MSG_OUTPUT_SECONDARY; + break; + default: + pr_err("%s: Invalid VFE output id: %d\n", + __func__, isp_output->output_id); + rc = -EINVAL; + break; + } + + if (!rc) { + isp_event->isp_data.isp_msg.msg_id = + isp_output->output_id; + isp_event->isp_data.isp_msg.frame_id = + isp_output->frameCounter; + buf = isp_output->buf; + msgid = msm_isp_vfe_msg_to_img_mode(pmctl, msgid); + BUG_ON(msgid < 0); + msm_mctl_buf_done(pmctl, msgid, + &buf, isp_output->frameCounter); + } + } + break; + case NOTIFY_VFE_MSG_COMP_STATS: { + struct msm_stats_buf *stats = (struct msm_stats_buf *)arg; + struct msm_stats_buf *stats_buf = NULL; + + isp_event->isp_data.isp_msg.msg_id = MSG_ID_STATS_COMPOSITE; + stats->aec.buff = msm_pmem_stats_ptov_lookup(pmctl, + stats->aec.buff, &(stats->aec.fd)); + stats->awb.buff = msm_pmem_stats_ptov_lookup(pmctl, + stats->awb.buff, &(stats->awb.fd)); + stats->af.buff = msm_pmem_stats_ptov_lookup(pmctl, + stats->af.buff, &(stats->af.fd)); + stats->ihist.buff = msm_pmem_stats_ptov_lookup(pmctl, + stats->ihist.buff, &(stats->ihist.fd)); + stats->rs.buff = msm_pmem_stats_ptov_lookup(pmctl, + stats->rs.buff, &(stats->rs.fd)); + stats->cs.buff = msm_pmem_stats_ptov_lookup(pmctl, + stats->cs.buff, &(stats->cs.fd)); + + stats_buf = kmalloc(sizeof(struct msm_stats_buf), GFP_ATOMIC); + if (!stats_buf) { + pr_err("%s: out of memory.\n", __func__); + rc = -ENOMEM; + } else { + *stats_buf = *stats; + isp_event->isp_data.isp_msg.len = + sizeof(struct msm_stats_buf); + isp_event->isp_data.isp_msg.data = stats_buf; + } + } + break; + case NOTIFY_VFE_MSG_STATS: { + struct msm_stats_buf stats; + struct isp_msg_stats *isp_stats = (struct isp_msg_stats *)arg; + + isp_event->isp_data.isp_msg.msg_id = isp_stats->id; + isp_event->isp_data.isp_msg.frame_id = + isp_stats->frameCounter; + stats.buffer = msm_pmem_stats_ptov_lookup(pmctl, + isp_stats->buffer, + &(stats.fd)); + switch (isp_stats->id) { + case MSG_ID_STATS_AEC: + stats.aec.buff = stats.buffer; + stats.aec.fd = stats.fd; + break; + case MSG_ID_STATS_AF: + stats.af.buff = stats.buffer; + stats.af.fd = stats.fd; + break; + case MSG_ID_STATS_AWB: + stats.awb.buff = stats.buffer; + stats.awb.fd = stats.fd; + break; + case MSG_ID_STATS_IHIST: + stats.ihist.buff = stats.buffer; + stats.ihist.fd = stats.fd; + break; + case MSG_ID_STATS_RS: + stats.rs.buff = stats.buffer; + stats.rs.fd = stats.fd; + break; + case MSG_ID_STATS_CS: + stats.cs.buff = stats.buffer; + stats.cs.fd = stats.fd; + break; + case MSG_ID_STATS_AWB_AEC: + break; + default: + pr_err("%s: Invalid msg type", __func__); + break; + } + if (!stats.buffer) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + isp_event->isp_data.isp_msg.len = 0; + rc = -EFAULT; + } 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__); + rc = -ENOMEM; + } else { + *stats_buf = stats; + isp_event->isp_data.isp_msg.len = + sizeof(struct msm_stats_buf); + isp_event->isp_data.isp_msg.data = stats_buf; + } + } + } + break; + default: + pr_err("%s: Unsupport isp notification %d\n", + __func__, notification); + rc = -EINVAL; + break; + } + + v4l2_event_queue(pmctl->config_device->config_stat_event_queue.pvdev, + &v4l2_evt); + + return rc; +} + +static int msm_isp_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + return msm_isp_notify_vfe(sd, notification, arg); +} + +/* This function is called by open() function, so we need to init HW*/ +static int msm_isp_open(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + /* init vfe and senor, register sync callbacks for init*/ + int rc = 0; + D("%s\n", __func__); + if (!mctl) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } + + rc = msm_vfe_subdev_init(sd, mctl); + if (rc < 0) { + pr_err("%s: vfe_init failed at %d\n", + __func__, rc); + } + return rc; +} + +static void msm_isp_release( + struct v4l2_subdev *sd) +{ + D("%s\n", __func__); + msm_vfe_subdev_release(sd); +} + +static int msm_config_vfe(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl, 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( + &mctl->stats_info.pmem_stats_list, + 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( + &mctl->stats_info.pmem_stats_list, + 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( + &mctl->stats_info.pmem_stats_list, + 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_AEC_AWB_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup( + &mctl->stats_info.pmem_stats_list, + MSM_PMEM_AEC_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( + &mctl->stats_info.pmem_stats_list, + 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( + &mctl->stats_info.pmem_stats_list, + 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( + &mctl->stats_info.pmem_stats_list, + 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_axi_config(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl, 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_PRIM: + case CMD_AXI_CFG_SEC: + case CMD_AXI_CFG_ZSL: + case CMD_RAW_PICT_AXI_CFG: + case CMD_AXI_CFG_PRIM_ALL_CHNLS: + case CMD_AXI_CFG_PRIM|CMD_AXI_CFG_SEC: + case CMD_AXI_CFG_PRIM|CMD_AXI_CFG_SEC_ALL_CHNLS: + case CMD_AXI_CFG_PRIM_ALL_CHNLS|CMD_AXI_CFG_SEC: + /* Dont need to pass buffer information. + * subdev will get the buffer from media + * controller free queue. + */ + return msm_isp_subdev_ioctl(sd, &cfgcmd, NULL); + + default: + pr_err("%s: unknown command type %d\n", + __func__, + cfgcmd.cmd_type); + return -EINVAL; + } + + return 0; +} + +static int msm_put_stats_buffer(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl, 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(mctl, 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 if (buf.type == STAT_AEAW) + cfgcmd.cmd_type = CMD_STATS_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, argp); + break; + + case MSM_CAM_IOCTL_AXI_CONFIG: + D("Received MSM_CAM_IOCTL_AXI_CONFIG\n"); + rc = msm_axi_config(sd, pmctl, argp); + break; + + case MSM_CAM_IOCTL_RELEASE_STATS_BUFFER: + rc = msm_put_stats_buffer(sd, pmctl, 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_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_mctl.c b/drivers/media/video/msm/msm_mctl.c new file mode 100644 index 0000000000000000000000000000000000000000..cc3c59b5135b916c5e2a52daf2ab622958e6029d --- /dev/null +++ b/drivers/media/video/msm/msm_mctl.c @@ -0,0 +1,1743 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_csid.h" +#include "msm_csic.h" +#include "msm_csiphy.h" +#include "msm_ispif.h" +#include "msm_sensor.h" +#include "msm_actuator.h" +#include "msm_vpe.h" +#include "msm_vfe32.h" +#include "msm_camera_eeprom.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 = "NV16BAYER", + .depth = 8, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_NV16, + .pxlcode = V4L2_MBUS_FMT_SBGGR10_1X10, /* Bayer sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .name = "NV61BAYER", + .depth = 8, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_NV61, + .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_SGRBG10_1X10, /* Bayer sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .name = "YU12BAYER", + .depth = 8, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_YUV420M, + .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, + }, + { + .name = "RAWBAYER", + .depth = 10, + .bitsperpxl = 10, + .fourcc = V4L2_PIX_FMT_SBGGR10, + .pxlcode = V4L2_MBUS_FMT_SGRBG10_1X10, /* Bayer sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + +}; + +static int msm_get_sensor_info( + struct msm_cam_media_controller *mctl, + void __user *arg) +{ + int rc = 0; + struct msm_camsensor_info info; + struct msm_camera_sensor_info *sdata; + struct msm_cam_v4l2_device *pcam = mctl->pcam_ptr; + if (copy_from_user(&info, + arg, + sizeof(struct msm_camsensor_info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + sdata = mctl->sdata; + 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; + info.pxlcode = pcam->usr_fmts[0].pxlcode; + info.flashtype = sdata->flash_type; /* two flash_types here? */ + info.camera_type = sdata->camera_type; + /* sensor_type needed to add YUV/SOC in probing */ + info.sensor_type = sdata->sensor_type; + info.mount_angle = sdata->sensor_platform_info->mount_angle; + info.actuator_enabled = sdata->actuator_info ? 1 : 0; + info.strobe_flash_enabled = sdata->strobe_flash_data ? 1 : 0; + /* 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_mctl_set_vfe_output_mode(struct msm_cam_media_controller + *p_mctl, void __user *arg) +{ + int rc = 0; + if (copy_from_user(&p_mctl->vfe_output_mode, + (void __user *)arg, sizeof(p_mctl->vfe_output_mode))) { + pr_err("%s Copy from user failed ", __func__); + rc = -EFAULT; + } else { + pr_info("%s: mctl=0x%p, vfe output mode =0x%x", + __func__, p_mctl, p_mctl->vfe_output_mode); + } + 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:%d: cmd %d\n", __func__, __LINE__, 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, argp); + break; + + case MSM_CAM_IOCTL_SENSOR_IO_CFG: + rc = v4l2_subdev_call(p_mctl->sensor_sdev, + core, ioctl, VIDIOC_MSM_SENSOR_CFG, argp); + break; + + case MSM_CAM_IOCTL_SENSOR_V4l2_S_CTRL: { + struct v4l2_control v4l2_ctrl; + CDBG("subdev call\n"); + if (copy_from_user(&v4l2_ctrl, + (void *)argp, + sizeof(struct v4l2_control))) { + CDBG("copy fail\n"); + return -EFAULT; + } + CDBG("subdev call ok\n"); + rc = v4l2_subdev_call(p_mctl->sensor_sdev, + core, s_ctrl, &v4l2_ctrl); + break; + } + + case MSM_CAM_IOCTL_SENSOR_V4l2_QUERY_CTRL: { + struct v4l2_queryctrl v4l2_qctrl; + CDBG("query called\n"); + if (copy_from_user(&v4l2_qctrl, + (void *)argp, + sizeof(struct v4l2_queryctrl))) { + CDBG("copy fail\n"); + rc = -EFAULT; + break; + } + rc = v4l2_subdev_call(p_mctl->sensor_sdev, + core, queryctrl, &v4l2_qctrl); + if (rc < 0) { + rc = -EFAULT; + break; + } + if (copy_to_user((void *)argp, + &v4l2_qctrl, + sizeof(struct v4l2_queryctrl))) { + rc = -EFAULT; + } + break; + } + + case MSM_CAM_IOCTL_GET_ACTUATOR_INFO: { + struct msm_actuator_cfg_data cdata; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct msm_actuator_cfg_data))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + cdata.is_af_supported = 0; + rc = 0; + + if (p_mctl->act_sdev) { + struct msm_camera_sensor_info *sdata; + + sdata = p_mctl->sdata; + CDBG("%s: Act_cam_Name %d\n", __func__, + sdata->actuator_info->cam_name); + + cdata.is_af_supported = 1; + cdata.cfg.cam_name = + (enum af_camera_name)sdata-> + actuator_info->cam_name; + + CDBG("%s: Af Support:%d\n", __func__, + cdata.is_af_supported); + CDBG("%s: Act_name:%d\n", __func__, cdata.cfg.cam_name); + + } + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct msm_actuator_cfg_data))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + break; + } + + case MSM_CAM_IOCTL_ACTUATOR_IO_CFG: { + struct msm_actuator_cfg_data act_data; + if (p_mctl->act_sdev) { + rc = v4l2_subdev_call(p_mctl->act_sdev, + core, ioctl, VIDIOC_MSM_ACTUATOR_CFG, argp); + } else { + rc = copy_from_user( + &act_data, + (void *)argp, + sizeof(struct msm_actuator_cfg_data)); + if (rc != 0) { + rc = -EFAULT; + break; + } + act_data.is_af_supported = 0; + rc = copy_to_user((void *)argp, + &act_data, + sizeof(struct msm_actuator_cfg_data)); + if (rc != 0) { + rc = -EFAULT; + break; + } + } + break; + } + + case MSM_CAM_IOCTL_EEPROM_IO_CFG: { + struct msm_eeprom_cfg_data eeprom_data; + if (p_mctl->eeprom_sdev) { + eeprom_data.is_eeprom_supported = 1; + rc = v4l2_subdev_call(p_mctl->eeprom_sdev, + core, ioctl, VIDIOC_MSM_EEPROM_CFG, argp); + } else { + rc = copy_from_user( + &eeprom_data, + (void *)argp, + sizeof(struct msm_eeprom_cfg_data)); + if (rc != 0) { + rc = -EFAULT; + break; + } + eeprom_data.is_eeprom_supported = 0; + rc = copy_to_user((void *)argp, + &eeprom_data, + sizeof(struct msm_eeprom_cfg_data)); + if (rc != 0) { + rc = -EFAULT; + break; + } + } + break; + } + + case MSM_CAM_IOCTL_GET_KERNEL_SYSTEM_TIME: { + struct timeval timestamp; + if (copy_from_user(×tamp, argp, sizeof(timestamp))) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + } else { + msm_mctl_gettimeofday(×tamp); + rc = copy_to_user((void *)argp, + ×tamp, sizeof(timestamp)); + } + 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->sdata, &flash_info); + } + break; + } + case MSM_CAM_IOCTL_PICT_PP: + rc = msm_mctl_set_pp_key(p_mctl, (void __user *)arg); + break; + case MSM_CAM_IOCTL_PICT_PP_DIVERT_DONE: + rc = msm_mctl_pp_divert_done(p_mctl, (void __user *)arg); + break; + case MSM_CAM_IOCTL_PICT_PP_DONE: + rc = msm_mctl_pp_done(p_mctl, (void __user *)arg); + break; + case MSM_CAM_IOCTL_MCTL_POST_PROC: + rc = msm_mctl_pp_ioctl(p_mctl, cmd, arg); + break; + case MSM_CAM_IOCTL_RESERVE_FREE_FRAME: + rc = msm_mctl_pp_reserve_free_frame(p_mctl, + (void __user *)arg); + break; + case MSM_CAM_IOCTL_RELEASE_FREE_FRAME: + rc = msm_mctl_pp_release_free_frame(p_mctl, + (void __user *)arg); + break; + case MSM_CAM_IOCTL_SET_VFE_OUTPUT_TYPE: + rc = msm_mctl_set_vfe_output_mode(p_mctl, + (void __user *)arg); + break; + case MSM_CAM_IOCTL_MCTL_DIVERT_DONE: + rc = msm_mctl_pp_mctl_divert_done(p_mctl, + (void __user *)arg); + break; + /* ISFIF config*/ + case MSM_CAM_IOCTL_AXI_CONFIG: + if (p_mctl->axi_sdev) + rc = v4l2_subdev_call(p_mctl->axi_sdev, core, ioctl, + VIDIOC_MSM_AXI_CFG, (void __user *)arg); + else + rc = p_mctl->isp_sdev->isp_config(p_mctl, cmd, arg); + break; + default: + /* ISP config*/ + D("%s:%d: go to default. Calling msm_isp_config\n", + __func__, __LINE__); + 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_mctl_subdev_match_core(struct device *dev, void *data) +{ + int core_index = (int)data; + struct platform_device *pdev = to_platform_device(dev); + + if (pdev->id == core_index) + return 1; + else + return 0; +} + +static int msm_mctl_register_subdevs(struct msm_cam_media_controller *p_mctl, + int core_index) +{ + struct device_driver *driver; + struct device *dev; + int rc = -ENODEV; + + struct msm_sensor_ctrl_t *s_ctrl = get_sctrl(p_mctl->sensor_sdev); + struct msm_camera_sensor_info *sinfo = + (struct msm_camera_sensor_info *) s_ctrl->sensordata; + struct msm_camera_device_platform_data *pdata = sinfo->pdata; + + if (pdata->is_csiphy) { + /* register csiphy subdev */ + driver = driver_find(MSM_CSIPHY_DRV_NAME, &platform_bus_type); + if (!driver) + goto out; + + dev = driver_find_device(driver, NULL, (void *)core_index, + msm_mctl_subdev_match_core); + if (!dev) + goto out; + + p_mctl->csiphy_sdev = dev_get_drvdata(dev); + } + + if (pdata->is_csic) { + /* register csic subdev */ + driver = driver_find(MSM_CSIC_DRV_NAME, &platform_bus_type); + if (!driver) + goto out; + + dev = driver_find_device(driver, NULL, (void *)core_index, + msm_mctl_subdev_match_core); + if (!dev) + goto out; + + p_mctl->csic_sdev = dev_get_drvdata(dev); + } + + if (pdata->is_csid) { + /* register csid subdev */ + driver = driver_find(MSM_CSID_DRV_NAME, &platform_bus_type); + if (!driver) + goto out; + + dev = driver_find_device(driver, NULL, (void *)core_index, + msm_mctl_subdev_match_core); + if (!dev) + goto out; + + p_mctl->csid_sdev = dev_get_drvdata(dev); + } + + if (pdata->is_ispif) { + /* register ispif subdev */ + driver = driver_find(MSM_ISPIF_DRV_NAME, &platform_bus_type); + if (!driver) + goto out; + + dev = driver_find_device(driver, NULL, 0, + msm_mctl_subdev_match_core); + if (!dev) + goto out; + + p_mctl->ispif_sdev = dev_get_drvdata(dev); + } + + /* register vfe subdev */ + driver = driver_find(MSM_VFE_DRV_NAME, &platform_bus_type); + if (!driver) + goto out; + + dev = driver_find_device(driver, NULL, 0, + msm_mctl_subdev_match_core); + if (!dev) + goto out; + + p_mctl->isp_sdev->sd = dev_get_drvdata(dev); + + if (pdata->is_vpe) { + /* register vfe subdev */ + driver = driver_find(MSM_VPE_DRV_NAME, &platform_bus_type); + if (!driver) + goto out; + + dev = driver_find_device(driver, NULL, 0, + msm_mctl_subdev_match_core); + if (!dev) + goto out; + + p_mctl->vpe_sdev = dev_get_drvdata(dev); + } + + rc = 0; + + + /* register gemini subdev */ + driver = driver_find(MSM_GEMINI_DRV_NAME, &platform_bus_type); + if (!driver) { + pr_err("%s:%d:Gemini: Failure: goto out\n", + __func__, __LINE__); + goto out; + } + pr_debug("%s:%d:Gemini: driver_find_device Gemini driver 0x%x\n", + __func__, __LINE__, (uint32_t)driver); + dev = driver_find_device(driver, NULL, NULL, + msm_mctl_subdev_match_core); + if (!dev) { + pr_err("%s:%d:Gemini: Failure goto out\n", + __func__, __LINE__); + goto out; + } + p_mctl->gemini_sdev = dev_get_drvdata(dev); + pr_debug("%s:%d:Gemini: After dev_get_drvdata gemini_sdev=0x%x\n", + __func__, __LINE__, (uint32_t)p_mctl->gemini_sdev); + + if (p_mctl->gemini_sdev == NULL) { + pr_err("%s:%d:Gemini: Failure gemini_sdev is null\n", + __func__, __LINE__); + goto out; + } + rc = 0; +out: + return rc; +} + +static int msm_mctl_open(struct msm_cam_media_controller *p_mctl, + const char *const apps_id) +{ + int rc = 0; + struct msm_sensor_ctrl_t *s_ctrl = get_sctrl(p_mctl->sensor_sdev); + struct msm_camera_sensor_info *sinfo = + (struct msm_camera_sensor_info *) s_ctrl->sensordata; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint8_t csid_core; + D("%s\n", __func__); + if (!p_mctl) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } + + mutex_lock(&p_mctl->lock); + /* open sub devices - once only*/ + if (!p_mctl->opencnt) { + uint32_t csid_version; + wake_lock(&p_mctl->wake_lock); + + csid_core = camdev->csid_core; + rc = msm_mctl_register_subdevs(p_mctl, csid_core); + if (rc < 0) { + pr_err("%s: msm_mctl_register_subdevs failed:%d\n", + __func__, rc); + goto register_sdev_failed; + } + + /* then sensor - move sub dev later */ + rc = v4l2_subdev_call(p_mctl->sensor_sdev, core, s_power, 1); + if (rc < 0) { + pr_err("%s: sensor powerup failed: %d\n", __func__, rc); + goto sensor_sdev_failed; + } + + if (p_mctl->act_sdev) + rc = v4l2_subdev_call(p_mctl->act_sdev, + core, s_power, 1); + if (rc < 0) { + pr_err("%s: act power failed:%d\n", __func__, rc); + goto act_power_up_failed; + } + + if (camdev->is_csiphy) { + rc = v4l2_subdev_call(p_mctl->csiphy_sdev, core, ioctl, + VIDIOC_MSM_CSIPHY_INIT, NULL); + if (rc < 0) { + pr_err("%s: csiphy initialization failed %d\n", + __func__, rc); + goto csiphy_init_failed; + } + } + + if (camdev->is_csid) { + rc = v4l2_subdev_call(p_mctl->csid_sdev, core, ioctl, + VIDIOC_MSM_CSID_INIT, &csid_version); + if (rc < 0) { + pr_err("%s: csid initialization failed %d\n", + __func__, rc); + goto csid_init_failed; + } + } + + if (camdev->is_csic) { + rc = v4l2_subdev_call(p_mctl->csic_sdev, core, ioctl, + VIDIOC_MSM_CSIC_INIT, &csid_version); + if (rc < 0) { + pr_err("%s: csic initialization failed %d\n", + __func__, rc); + goto csic_init_failed; + } + } + + /* 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, p_mctl); + if (rc < 0) { + pr_err("%s: isp init failed: %d\n", + __func__, rc); + goto isp_open_failed; + } + } + + if (p_mctl->axi_sdev) { + rc = v4l2_subdev_call(p_mctl->axi_sdev, core, ioctl, + VIDIOC_MSM_AXI_INIT, p_mctl); + if (rc < 0) { + pr_err("%s: axi initialization failed %d\n", + __func__, rc); + goto axi_init_failed; + } + } + + if (camdev->is_vpe) { + rc = v4l2_subdev_call(p_mctl->vpe_sdev, core, ioctl, + VIDIOC_MSM_VPE_INIT, p_mctl); + if (rc < 0) { + pr_err("%s: vpe initialization failed %d\n", + __func__, rc); + goto vpe_init_failed; + } + } + + if (camdev->is_ispif) { + rc = v4l2_subdev_call(p_mctl->ispif_sdev, core, ioctl, + VIDIOC_MSM_ISPIF_INIT, &csid_version); + if (rc < 0) { + pr_err("%s: ispif initialization failed %d\n", + __func__, rc); + goto ispif_init_failed; + } + } + + if (camdev->is_ispif) { + 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); + } + p_mctl->apps_id = apps_id; + p_mctl->opencnt++; + } else { + D("%s: camera is already open", __func__); + } + mutex_unlock(&p_mctl->lock); + + return rc; + +ispif_init_failed: + if (camdev->is_vpe) + if (v4l2_subdev_call(p_mctl->vpe_sdev, core, ioctl, + VIDIOC_MSM_VPE_RELEASE, NULL) < 0) + pr_err("%s: vpe release failed %d\n", __func__, rc); +vpe_init_failed: + if (p_mctl->axi_sdev) + if (v4l2_subdev_call(p_mctl->axi_sdev, core, ioctl, + VIDIOC_MSM_AXI_RELEASE, NULL) < 0) + pr_err("%s: axi release failed %d\n", __func__, rc); +axi_init_failed: + if (p_mctl->isp_sdev && p_mctl->isp_sdev->isp_release) + p_mctl->isp_sdev->isp_release(p_mctl->isp_sdev->sd); +isp_open_failed: + if (camdev->is_csic) + if (v4l2_subdev_call(p_mctl->csic_sdev, core, ioctl, + VIDIOC_MSM_CSIC_RELEASE, NULL) < 0) + pr_err("%s: csic release failed %d\n", __func__, rc); +csic_init_failed: + if (camdev->is_csid) + if (v4l2_subdev_call(p_mctl->csid_sdev, core, ioctl, + VIDIOC_MSM_CSID_RELEASE, NULL) < 0) + pr_err("%s: csid release failed %d\n", __func__, rc); +csid_init_failed: + if (camdev->is_csiphy) + if (v4l2_subdev_call(p_mctl->csiphy_sdev, core, ioctl, + VIDIOC_MSM_CSIPHY_RELEASE, NULL) < 0) + pr_err("%s: csiphy release failed %d\n", __func__, rc); +csiphy_init_failed: + if (p_mctl->act_sdev) + if (v4l2_subdev_call(p_mctl->act_sdev, core, + s_power, 0) < 0) + pr_err("%s: act power down failed:%d\n", __func__, rc); +act_power_up_failed: + if (v4l2_subdev_call(p_mctl->sensor_sdev, core, s_power, 0) < 0) + pr_err("%s: sensor powerdown failed: %d\n", __func__, rc); +sensor_sdev_failed: +register_sdev_failed: + wake_unlock(&p_mctl->wake_lock); + mutex_unlock(&p_mctl->lock); + return rc; +} + +static int msm_mctl_release(struct msm_cam_media_controller *p_mctl) +{ + int rc = 0; + struct msm_sensor_ctrl_t *s_ctrl = get_sctrl(p_mctl->sensor_sdev); + struct msm_camera_sensor_info *sinfo = + (struct msm_camera_sensor_info *) s_ctrl->sensordata; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + + v4l2_subdev_call(p_mctl->sensor_sdev, core, ioctl, + VIDIOC_MSM_SENSOR_RELEASE, NULL); + + if (camdev->is_ispif) { + v4l2_subdev_call(p_mctl->ispif_sdev, core, ioctl, + VIDIOC_MSM_ISPIF_RELEASE, NULL); + } + + if (camdev->is_csic) { + v4l2_subdev_call(p_mctl->csic_sdev, core, ioctl, + VIDIOC_MSM_CSIC_RELEASE, NULL); + } + + if (camdev->is_vpe) { + v4l2_subdev_call(p_mctl->vpe_sdev, core, ioctl, + VIDIOC_MSM_VPE_RELEASE, NULL); + } + + if (p_mctl->axi_sdev) { + v4l2_subdev_call(p_mctl->axi_sdev, core, ioctl, + VIDIOC_MSM_AXI_RELEASE, NULL); + } + + if (p_mctl->isp_sdev && p_mctl->isp_sdev->isp_release) + p_mctl->isp_sdev->isp_release( + p_mctl->isp_sdev->sd); + + if (camdev->is_csid) { + v4l2_subdev_call(p_mctl->csid_sdev, core, ioctl, + VIDIOC_MSM_CSID_RELEASE, NULL); + } + + if (camdev->is_csiphy) { + v4l2_subdev_call(p_mctl->csiphy_sdev, core, ioctl, + VIDIOC_MSM_CSIPHY_RELEASE, NULL); + } + + if (camdev->is_ispif) { + pm_qos_update_request(&p_mctl->pm_qos_req_list, + PM_QOS_DEFAULT_VALUE); + pm_qos_remove_request(&p_mctl->pm_qos_req_list); + } + + if (p_mctl->act_sdev) + v4l2_subdev_call(p_mctl->act_sdev, core, s_power, 0); + + v4l2_subdev_call(p_mctl->sensor_sdev, core, s_power, 0); + + wake_unlock(&p_mctl->wake_lock); + 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(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; + } + pcam->mctl_handle = msm_camera_get_mctl_handle(); + if (pcam->mctl_handle == 0) { + pr_err("%s: cannot get mctl handle", __func__); + return -EINVAL; + } + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pmctl) { + pr_err("%s: invalid mctl controller", __func__); + return -EINVAL; + } + + wake_lock_init(&pmctl->wake_lock, WAKE_LOCK_IDLE, "msm_camera"); + mutex_init(&pmctl->lock); + pmctl->opencnt = 0; + + /* init module operations*/ + pmctl->mctl_open = msm_mctl_open; + pmctl->mctl_cmd = msm_mctl_cmd; + pmctl->mctl_release = msm_mctl_release; + /* init mctl buf */ + msm_mctl_buf_init(pcam); + memset(&pmctl->pp_info, 0, sizeof(pmctl->pp_info)); + pmctl->vfe_output_mode = 0; + spin_lock_init(&pmctl->pp_info.lock); + + pmctl->act_sdev = pcam->act_sdev; + pmctl->eeprom_sdev = pcam->eeprom_sdev; + pmctl->sensor_sdev = pcam->sensor_sdev; + pmctl->sdata = pcam->sdata; + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + pmctl->client = msm_ion_client_create(-1, "camera"); + kref_init(&pmctl->refcount); +#endif + + return 0; +} + +int msm_mctl_free(struct msm_cam_v4l2_device *pcam) +{ + int rc = 0; + struct msm_cam_media_controller *pmctl = NULL; + D("%s\n", __func__); + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pmctl) { + pr_err("%s: invalid mctl controller", __func__); + return -EINVAL; + } + + mutex_destroy(&pmctl->lock); + wake_lock_destroy(&pmctl->wake_lock); + msm_camera_free_mctl(pcam->mctl_handle); + return rc; +} + +/* mctl node v4l2_file_operations */ +static int msm_mctl_dev_open(struct file *f) +{ + int rc = -EINVAL, i; + /* get the video device */ + struct msm_cam_v4l2_device *pcam = NULL; + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_media_controller *pmctl; + + if (f == NULL) { + pr_err("%s :: cannot open video driver data", __func__); + return rc; + } + pcam = video_drvdata(f); + + if (!pcam) { + pr_err("%s NULL pointer passed in!\n", __func__); + return rc; + } + + D("%s : E use_count %d", __func__, pcam->mctl_node.use_count); + mutex_lock(&pcam->mctl_node.dev_lock); + for (i = 0; i < MSM_DEV_INST_MAX; i++) { + if (pcam->mctl_node.dev_inst[i] == NULL) + break; + } + /* if no instance is available, return error */ + if (i == MSM_DEV_INST_MAX) { + mutex_unlock(&pcam->mctl_node.dev_lock); + return rc; + } + pcam_inst = kzalloc(sizeof(struct msm_cam_v4l2_dev_inst), GFP_KERNEL); + if (!pcam_inst) { + mutex_unlock(&pcam->mctl_node.dev_lock); + return rc; + } + + pcam_inst->sensor_pxlcode = pcam->usr_fmts[0].pxlcode; + pcam_inst->my_index = i; + pcam_inst->pcam = pcam; + pcam->mctl_node.dev_inst[i] = pcam_inst; + + D("%s pcam_inst %p my_index = %d\n", __func__, + pcam_inst, pcam_inst->my_index); + rc = msm_cam_server_open_mctl_session(pcam, + &pcam->mctl_node.active); + if (rc < 0) { + pr_err("%s: mctl session open failed %d", __func__, rc); + mutex_unlock(&pcam->mctl_node.dev_lock); + return rc; + } + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pmctl) { + pr_err("%s mctl NULL!\n", __func__); + return rc; + } + + D("%s active %d\n", __func__, pcam->mctl_node.active); + rc = msm_setup_v4l2_event_queue(&pcam_inst->eventHandle, + pcam->mctl_node.pvdev); + if (rc < 0) { + mutex_unlock(&pcam->mctl_node.dev_lock); + return rc; + } + pcam_inst->vbqueue_initialized = 0; + kref_get(&pmctl->refcount); + f->private_data = &pcam_inst->eventHandle; + + D("f->private_data = 0x%x, pcam = 0x%x\n", + (u32)f->private_data, (u32)pcam_inst); + + pcam->mctl_node.use_count++; + mutex_unlock(&pcam->mctl_node.dev_lock); + D("%s : X ", __func__); + return rc; +} + +static unsigned int msm_mctl_dev_poll(struct file *f, + struct poll_table_struct *wait) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + pcam = pcam_inst->pcam; + + D("%s : E pcam_inst = %p", __func__, pcam_inst); + if (!pcam) { + pr_err("%s NULL pointer of camera device!\n", __func__); + return -EINVAL; + } + + poll_wait(f, &(pcam_inst->eventHandle.wait), wait); + if (v4l2_event_pending(&pcam_inst->eventHandle)) { + rc |= POLLPRI; + D("%s Event available on mctl node ", __func__); + } + + D("%s poll on vb2\n", __func__); + if (!pcam_inst->vid_bufq.streaming) { + D("%s vid_bufq.streaming is off, inst=0x%x\n", + __func__, (u32)pcam_inst); + return rc; + } + rc |= vb2_poll(&pcam_inst->vid_bufq, f, wait); + + D("%s : X ", __func__); + return rc; +} + +static int msm_mctl_dev_close(struct file *f) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam; + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_media_controller *pmctl; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + pcam = pcam_inst->pcam; + + D("%s : E ", __func__); + if (!pcam) { + pr_err("%s NULL pointer of camera device!\n", __func__); + return -EINVAL; + } + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + mutex_lock(&pcam->mctl_node.dev_lock); + D("%s : active %d ", __func__, pcam->mctl_node.active); + if (pcam->mctl_node.active == 1) { + rc = msm_cam_server_close_mctl_session(pcam); + if (rc < 0) { + pr_err("%s: mctl session close failed %d", + __func__, rc); + mutex_unlock(&pcam->mctl_node.dev_lock); + return rc; + } + pmctl = NULL; + } + pcam_inst->streamon = 0; + pcam->mctl_node.dev_inst_map[pcam_inst->image_mode] = NULL; + if (pcam_inst->vbqueue_initialized) + vb2_queue_release(&pcam_inst->vid_bufq); + D("%s Closing down instance %p ", __func__, pcam_inst); + pcam->mctl_node.dev_inst[pcam_inst->my_index] = NULL; + v4l2_fh_del(&pcam_inst->eventHandle); + v4l2_fh_exit(&pcam_inst->eventHandle); + + kfree(pcam_inst); + if (NULL != pmctl) { + D("%s : release ion client", __func__); + kref_put(&pmctl->refcount, msm_release_ion_client); + } + f->private_data = NULL; + mutex_unlock(&pcam->mctl_node.dev_lock); + pcam->mctl_node.use_count--; + D("%s : use_count %d X ", __func__, pcam->mctl_node.use_count); + return rc; +} + +static struct v4l2_file_operations g_msm_mctl_fops = { + .owner = THIS_MODULE, + .open = msm_mctl_dev_open, + .poll = msm_mctl_dev_poll, + .release = msm_mctl_dev_close, + .unlocked_ioctl = video_ioctl2, +}; + +/* + * + * implementation of mctl node v4l2_ioctl_ops + * + */ +static int msm_mctl_v4l2_querycap(struct file *f, void *pctx, + struct v4l2_capability *pcaps) +{ + struct msm_cam_v4l2_device *pcam; + + if (f == NULL) { + pr_err("%s :: NULL file pointer", __func__); + return -EINVAL; + } + + pcam = video_drvdata(f); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (!pcam) { + pr_err("%s NULL pointer passed in!\n", __func__); + return -EINVAL; + } + + strlcpy(pcaps->driver, pcam->media_dev.dev->driver->name, + sizeof(pcaps->driver)); + pcaps->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + return 0; +} + +static int msm_mctl_v4l2_queryctrl(struct file *f, void *pctx, + struct v4l2_queryctrl *pqctrl) +{ + int rc = 0; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +static int msm_mctl_v4l2_g_ctrl(struct file *f, void *pctx, + struct v4l2_control *c) +{ + int rc = 0; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +static int msm_mctl_v4l2_s_ctrl(struct file *f, void *pctx, + struct v4l2_control *ctrl) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + + WARN_ON(pctx != f->private_data); + mutex_lock(&pcam->mctl_node.dev_lock); + if (ctrl->id == MSM_V4L2_PID_PP_PLANE_INFO) { + if (copy_from_user(&pcam_inst->plane_info, + (void *)ctrl->value, + sizeof(struct img_plane_info))) { + pr_err("%s inst %p Copying plane_info failed ", + __func__, pcam_inst); + rc = -EFAULT; + } + D("%s inst %p got plane info: num_planes = %d," + "plane size = %ld %ld ", __func__, pcam_inst, + pcam_inst->plane_info.num_planes, + pcam_inst->plane_info.plane[0].size, + pcam_inst->plane_info.plane[1].size); + } else + pr_err("%s Unsupported S_CTRL Value ", __func__); + + mutex_unlock(&pcam->mctl_node.dev_lock); + + return rc; +} + +static int msm_mctl_v4l2_reqbufs(struct file *f, void *pctx, + struct v4l2_requestbuffers *pb) +{ + int rc = 0, i, j; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + rc = vb2_reqbufs(&pcam_inst->vid_bufq, pb); + if (rc < 0) { + pr_err("%s reqbufs failed %d ", __func__, rc); + return rc; + } + if (!pb->count) { + /* Deallocation. free buf_offset array */ + D("%s Inst %p freeing buffer offsets array", + __func__, pcam_inst); + for (j = 0 ; j < pcam_inst->buf_count ; j++) + kfree(pcam_inst->buf_offset[j]); + kfree(pcam_inst->buf_offset); + pcam_inst->buf_offset = NULL; + /* If the userspace has deallocated all the + * buffers, then release the vb2 queue */ + if (pcam_inst->vbqueue_initialized) { + vb2_queue_release(&pcam_inst->vid_bufq); + pcam_inst->vbqueue_initialized = 0; + } + } else { + D("%s Inst %p Allocating buf_offset array", + __func__, pcam_inst); + /* Allocation. allocate buf_offset array */ + pcam_inst->buf_offset = (struct msm_cam_buf_offset **) + kzalloc(pb->count * sizeof(struct msm_cam_buf_offset *), + GFP_KERNEL); + if (!pcam_inst->buf_offset) { + pr_err("%s out of memory ", __func__); + return -ENOMEM; + } + for (i = 0; i < pb->count; i++) { + pcam_inst->buf_offset[i] = + kzalloc(sizeof(struct msm_cam_buf_offset) * + pcam_inst->plane_info.num_planes, GFP_KERNEL); + if (!pcam_inst->buf_offset[i]) { + pr_err("%s out of memory ", __func__); + for (j = i-1 ; j >= 0; j--) + kfree(pcam_inst->buf_offset[j]); + kfree(pcam_inst->buf_offset); + pcam_inst->buf_offset = NULL; + return -ENOMEM; + } + } + } + pcam_inst->buf_count = pb->count; + D("%s inst %p, buf count %d ", __func__, + pcam_inst, pcam_inst->buf_count); + return rc; +} + +static int msm_mctl_v4l2_querybuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + /* get the video device */ + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + return vb2_querybuf(&pcam_inst->vid_bufq, pb); +} + +static int msm_mctl_v4l2_qbuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + int rc = 0, i = 0; + /* get the camera device */ + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst = %p\n", __func__, pcam_inst); + WARN_ON(pctx != f->private_data); + + if (!pcam_inst->buf_offset) { + pr_err("%s Buffer is already released. Returning. ", __func__); + return -EINVAL; + } + + if (pb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* Reject the buffer if planes array was not allocated */ + if (pb->m.planes == NULL) { + pr_err("%s Planes array is null ", __func__); + return -EINVAL; + } + for (i = 0; i < pcam_inst->plane_info.num_planes; i++) { + D("%s stored offsets for plane %d as" + "addr offset %d, data offset %d", + __func__, i, pb->m.planes[i].reserved[0], + pb->m.planes[i].data_offset); + pcam_inst->buf_offset[pb->index][i].data_offset = + pb->m.planes[i].data_offset; + pcam_inst->buf_offset[pb->index][i].addr_offset = + pb->m.planes[i].reserved[0]; + pcam_inst->plane_info.plane[i].offset = 0; + D("%s, len %d user[%d] %p buf_len %d\n", + __func__, pb->length, i, + (void *)pb->m.planes[i].m.userptr, + pb->m.planes[i].length); + } + } else { + D("%s stored reserved info %d", __func__, pb->reserved); + pcam_inst->buf_offset[pb->index][0].addr_offset = pb->reserved; + } + + rc = vb2_qbuf(&pcam_inst->vid_bufq, pb); + D("%s, videobuf_qbuf returns %d\n", __func__, rc); + + return rc; +} + +static int msm_mctl_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; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + rc = vb2_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_mctl_v4l2_streamon(struct file *f, void *pctx, + enum v4l2_buf_type buf_type) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst %p\n", __func__, pcam_inst); + WARN_ON(pctx != f->private_data); + + if ((buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) { + pr_err("%s Invalid buffer type ", __func__); + return -EINVAL; + } + + D("%s Calling videobuf_streamon", __func__); + /* if HW streaming on is successful, start buffer streaming */ + rc = vb2_streamon(&pcam_inst->vid_bufq, buf_type); + D("%s, videobuf_streamon returns %d\n", __func__, rc); + + mutex_lock(&pcam->mctl_node.dev_lock); + /* turn HW (VFE/sensor) streaming */ + pcam_inst->streamon = 1; + mutex_unlock(&pcam->mctl_node.dev_lock); + D("%s rc = %d\n", __func__, rc); + return rc; +} + +static int msm_mctl_v4l2_streamoff(struct file *f, void *pctx, + enum v4l2_buf_type buf_type) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst %p\n", __func__, pcam_inst); + WARN_ON(pctx != f->private_data); + + if ((buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) { + pr_err("%s Invalid buffer type ", __func__); + return -EINVAL; + } + + /* first turn of HW (VFE/sensor) streaming so that buffers are + not in use when we free the buffers */ + mutex_lock(&pcam->mctl_node.dev_lock); + pcam_inst->streamon = 0; + mutex_unlock(&pcam->mctl_node.dev_lock); + if (rc < 0) + pr_err("%s: hw failed to stop streaming\n", __func__); + + /* stop buffer streaming */ + rc = vb2_streamoff(&pcam_inst->vid_bufq, buf_type); + D("%s, videobuf_streamoff returns %d\n", __func__, rc); + return rc; +} + +static int msm_mctl_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_MPLANE) && + (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_mctl_v4l2_g_fmt_cap(struct file *f, + void *pctx, struct v4l2_format *pfmt) +{ + int rc = 0; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return rc; +} + +static int msm_mctl_v4l2_g_fmt_cap_mplane(struct file *f, + void *pctx, struct v4l2_format *pfmt) +{ + int rc = 0; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + return rc; +} + +/* This function will readjust the format parameters based in HW + capabilities. Called by s_fmt_cap +*/ +static int msm_mctl_v4l2_try_fmt_cap(struct file *f, void *pctx, + struct v4l2_format *pfmt) +{ + int rc = 0; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +static int msm_mctl_v4l2_try_fmt_cap_mplane(struct file *f, void *pctx, + struct v4l2_format *pfmt) +{ + int rc = 0; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +/* This function will reconfig the v4l2 driver and HW device, it should be + called after the streaming is stopped. +*/ +static int msm_mctl_v4l2_s_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_media_controller *pmctl; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + 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); + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pcam_inst->vbqueue_initialized) { + pmctl->mctl_vbqueue_init(pcam_inst, &pcam_inst->vid_bufq, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + pcam_inst->vbqueue_initialized = 1; + } + + return rc; +} + +static int msm_mctl_v4l2_s_fmt_cap_mplane(struct file *f, void *pctx, + struct v4l2_format *pfmt) +{ + int rc = 0, i; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_media_controller *pmctl; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s Inst %p vbqueue %d\n", __func__, + pcam_inst, pcam_inst->vbqueue_initialized); + WARN_ON(pctx != f->private_data); + + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + if (!pcam_inst->vbqueue_initialized) { + pmctl->mctl_vbqueue_init(pcam_inst, &pcam_inst->vid_bufq, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + pcam_inst->vbqueue_initialized = 1; + } + for (i = 0; i < pcam->num_fmts; i++) + if (pcam->usr_fmts[i].fourcc == pfmt->fmt.pix_mp.pixelformat) + break; + if (i == pcam->num_fmts) { + pr_err("%s: User requested pixelformat %x not supported\n", + __func__, pfmt->fmt.pix_mp.pixelformat); + return -EINVAL; + } + pcam_inst->vid_fmt = *pfmt; + pcam_inst->sensor_pxlcode = + pcam->usr_fmts[i].pxlcode; + D("%s: inst=%p, width=%d, heigth=%d\n", + __func__, pcam_inst, + pcam_inst->vid_fmt.fmt.pix_mp.width, + pcam_inst->vid_fmt.fmt.pix_mp.height); + return rc; +} +static int msm_mctl_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_mctl_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_mctl_v4l2_g_crop(struct file *f, void *pctx, + struct v4l2_crop *crop) +{ + int rc = -EINVAL; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +static int msm_mctl_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_mctl_v4l2_g_parm(struct file *f, void *pctx, + struct v4l2_streamparm *a) +{ + int rc = -EINVAL; + return rc; +} + +static int msm_mctl_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_mctl_v4l2_s_parm(struct file *f, void *pctx, + struct v4l2_streamparm *a) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = container_of(f->private_data, + struct msm_cam_v4l2_dev_inst, eventHandle); + pcam_inst->image_mode = a->parm.capture.extendedmode; + pcam_inst->pcam->mctl_node.dev_inst_map[pcam_inst->image_mode] = + pcam_inst; + pcam_inst->path = msm_mctl_vidbuf_get_path(pcam_inst->image_mode); + D("%s path=%d, image mode = %d rc=%d\n", __func__, + pcam_inst->path, pcam_inst->image_mode, rc); + return rc; +} + +static int msm_mctl_v4l2_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = + (struct msm_cam_v4l2_dev_inst *)container_of(fh, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s:fh = 0x%x, type = 0x%x\n", __func__, (u32)fh, sub->type); + + if (sub->type == V4L2_EVENT_ALL) + sub->type = V4L2_EVENT_PRIVATE_START+MSM_CAM_APP_NOTIFY_EVENT; + rc = v4l2_event_subscribe(fh, sub, 30); + if (rc < 0) + pr_err("%s: failed for evtType = 0x%x, rc = %d\n", + __func__, sub->type, rc); + return rc; +} + +static int msm_mctl_v4l2_unsubscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst; + pcam_inst = + (struct msm_cam_v4l2_dev_inst *)container_of(fh, + struct msm_cam_v4l2_dev_inst, eventHandle); + + D("%s: fh = 0x%x\n", __func__, (u32)fh); + + rc = v4l2_event_unsubscribe(fh, sub); + D("%s: rc = %d\n", __func__, rc); + return rc; +} + +/* mctl node v4l2_ioctl_ops */ +static const struct v4l2_ioctl_ops g_msm_mctl_ioctl_ops = { + .vidioc_querycap = msm_mctl_v4l2_querycap, + + .vidioc_s_crop = msm_mctl_v4l2_s_crop, + .vidioc_g_crop = msm_mctl_v4l2_g_crop, + + .vidioc_queryctrl = msm_mctl_v4l2_queryctrl, + .vidioc_g_ctrl = msm_mctl_v4l2_g_ctrl, + .vidioc_s_ctrl = msm_mctl_v4l2_s_ctrl, + + .vidioc_reqbufs = msm_mctl_v4l2_reqbufs, + .vidioc_querybuf = msm_mctl_v4l2_querybuf, + .vidioc_qbuf = msm_mctl_v4l2_qbuf, + .vidioc_dqbuf = msm_mctl_v4l2_dqbuf, + + .vidioc_streamon = msm_mctl_v4l2_streamon, + .vidioc_streamoff = msm_mctl_v4l2_streamoff, + + /* format ioctls */ + .vidioc_enum_fmt_vid_cap = msm_mctl_v4l2_enum_fmt_cap, + .vidioc_enum_fmt_vid_cap_mplane = msm_mctl_v4l2_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = msm_mctl_v4l2_try_fmt_cap, + .vidioc_try_fmt_vid_cap_mplane = msm_mctl_v4l2_try_fmt_cap_mplane, + .vidioc_g_fmt_vid_cap = msm_mctl_v4l2_g_fmt_cap, + .vidioc_g_fmt_vid_cap_mplane = msm_mctl_v4l2_g_fmt_cap_mplane, + .vidioc_s_fmt_vid_cap = msm_mctl_v4l2_s_fmt_cap, + .vidioc_s_fmt_vid_cap_mplane = msm_mctl_v4l2_s_fmt_cap_mplane, + + .vidioc_g_jpegcomp = msm_mctl_v4l2_g_jpegcomp, + .vidioc_s_jpegcomp = msm_mctl_v4l2_s_jpegcomp, + + /* Stream type-dependent parameter ioctls */ + .vidioc_g_parm = msm_mctl_v4l2_g_parm, + .vidioc_s_parm = msm_mctl_v4l2_s_parm, + + /* event subscribe/unsubscribe */ + .vidioc_subscribe_event = msm_mctl_v4l2_subscribe_event, + .vidioc_unsubscribe_event = msm_mctl_v4l2_unsubscribe_event, +}; + +int msm_setup_mctl_node(struct msm_cam_v4l2_device *pcam) +{ + int rc = -EINVAL; + struct video_device *pvdev = NULL; + struct i2c_client *client = v4l2_get_subdevdata(pcam->sensor_sdev); + + D("%s\n", __func__); + + /* first register the v4l2 device */ + pcam->mctl_node.v4l2_dev.dev = &client->dev; + rc = v4l2_device_register(pcam->mctl_node.v4l2_dev.dev, + &pcam->mctl_node.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->sensor_sdev->name, sizeof(pvdev->name)); + + /* device info - strlcpy is safer than strncpy but + only if architecture supports*/ + strlcpy(pvdev->name, pcam->sensor_sdev->name, + sizeof(pvdev->name)); + + pvdev->release = video_device_release; + pvdev->fops = &g_msm_mctl_fops; + pvdev->ioctl_ops = &g_msm_mctl_ioctl_ops; + pvdev->minor = -1; + pvdev->vfl_type = 1; + + /* register v4l2 video device to kernel as /dev/videoXX */ + D("%s video_register_device\n", __func__); + rc = video_register_device(pvdev, + VFL_TYPE_GRABBER, + -1); + 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 mctl video dev to each other */ + pcam->mctl_node.pvdev = pvdev; + video_set_drvdata(pcam->mctl_node.pvdev, pcam); + + return rc ; + +reg_fail: + video_device_release(pvdev); + v4l2_device_unregister(&pcam->mctl_node.v4l2_dev); + pcam->mctl_node.v4l2_dev.dev = NULL; + return rc; +} diff --git a/drivers/media/video/msm/msm_mctl_buf.c b/drivers/media/video/msm/msm_mctl_buf.c new file mode 100644 index 0000000000000000000000000000000000000000..88e89a334eeb7f87851ecd6acb3069964b467fbb --- /dev/null +++ b/drivers/media/video/msm/msm_mctl_buf.c @@ -0,0 +1,842 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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.h" +#include "msm_ispif.h" + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) pr_debug("msm_mctl_buf: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +static int msm_vb2_ops_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + void *alloc_ctxs[]) +{ + /* get the video device */ + struct msm_cam_v4l2_dev_inst *pcam_inst = vb2_get_drv_priv(vq); + struct msm_cam_v4l2_device *pcam = pcam_inst->pcam; + int i; + + D("%s\n", __func__); + if (!pcam || !(*num_buffers)) { + pr_err("%s error : invalid input\n", __func__); + return -EINVAL; + } + + *num_planes = pcam_inst->plane_info.num_planes; + for (i = 0; i < pcam_inst->vid_fmt.fmt.pix_mp.num_planes; i++) { + sizes[i] = pcam_inst->plane_info.plane[i].size; + D("%s Inst %p : Plane %d Offset = %d Size = %ld " + "Aligned Size = %d\n", __func__, pcam_inst, i, + pcam_inst->plane_info.plane[i].offset, + pcam_inst->plane_info.plane[i].size, sizes[i]); + } + return 0; +} + +static void msm_vb2_ops_wait_prepare(struct vb2_queue *q) +{ + /* we use polling so do not use this fn now */ +} +static void msm_vb2_ops_wait_finish(struct vb2_queue *q) +{ + /* we use polling so do not use this fn now */ +} + +static int msm_vb2_ops_buf_init(struct vb2_buffer *vb) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_v4l2_device *pcam; + struct msm_cam_media_controller *pmctl; + struct videobuf2_contig_pmem *mem; + struct vb2_queue *vq; + uint32_t buf_idx; + struct msm_frame_buffer *buf; + int rc = 0, i; + enum videobuf2_buffer_type buf_type; + struct videobuf2_msm_offset offset; + vq = vb->vb2_queue; + pcam_inst = vb2_get_drv_priv(vq); + pcam = pcam_inst->pcam; + D("%s\n", __func__); + 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); + + buf = container_of(vb, struct msm_frame_buffer, vidbuf); + if (buf->state == MSM_BUFFER_STATE_INITIALIZED) + return rc; + + if (pcam_inst->plane_info.buffer_type == + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + buf_type = VIDEOBUF2_MULTIPLE_PLANES; + else if (pcam_inst->plane_info.buffer_type == + V4L2_BUF_TYPE_VIDEO_CAPTURE) + buf_type = VIDEOBUF2_SINGLE_PLANE; + else + return -EINVAL; + + if (buf_type == VIDEOBUF2_SINGLE_PLANE) { + offset.sp_off.y_off = pcam_inst->plane_info.sp_y_offset; + offset.sp_off.cbcr_off = + pcam_inst->plane_info.plane[0].offset; + } + buf_idx = vb->v4l2_buf.index; + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + for (i = 0; i < vb->num_planes; i++) { + mem = vb2_plane_cookie(vb, i); + if (buf_type == VIDEOBUF2_MULTIPLE_PLANES) + offset.data_offset = + pcam_inst->plane_info.plane[i].offset; + + if (vb->v4l2_buf.memory == V4L2_MEMORY_USERPTR) + rc = videobuf2_pmem_contig_user_get(mem, &offset, + buf_type, + pcam_inst->buf_offset[buf_idx][i].addr_offset, + pcam_inst->path, pmctl->client); + else + rc = videobuf2_pmem_contig_mmap_get(mem, &offset, + buf_type, pcam_inst->path); + if (rc < 0) { + pr_err("%s error initializing buffer ", + __func__); + return rc; + } + } + buf->state = MSM_BUFFER_STATE_INITIALIZED; + return rc; +} + +static int msm_vb2_ops_buf_prepare(struct vb2_buffer *vb) +{ + int i, rc = 0; + uint32_t len; + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_v4l2_device *pcam; + struct msm_frame_buffer *buf; + struct vb2_queue *vq = vb->vb2_queue; + + D("%s\n", __func__); + if (!vb || !vq) { + pr_err("%s error : input is NULL\n", __func__); + return -EINVAL; + } + pcam_inst = vb2_get_drv_priv(vq); + 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; + } + /* 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; + } + /* prefill in the byteused field */ + for (i = 0; i < vb->num_planes; i++) { + len = vb2_plane_size(vb, i); + vb2_set_plane_payload(vb, i, len); + } + buf->state = MSM_BUFFER_STATE_PREPARED; + return rc; +} + +static int msm_vb2_ops_buf_finish(struct vb2_buffer *vb) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_v4l2_device *pcam; + struct msm_frame_buffer *buf; + + pcam_inst = vb2_get_drv_priv(vb->vb2_queue); + pcam = pcam_inst->pcam; + buf = container_of(vb, struct msm_frame_buffer, vidbuf); + buf->state = MSM_BUFFER_STATE_DEQUEUED; + D("%s: inst=0x%x, buf=0x%x, idx=%d\n", __func__, + (uint32_t)pcam_inst, (uint32_t)buf, vb->v4l2_buf.index); + return 0; +} + +static void msm_vb2_ops_buf_cleanup(struct vb2_buffer *vb) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_media_controller *pmctl; + struct msm_cam_v4l2_device *pcam; + struct videobuf2_contig_pmem *mem; + struct msm_frame_buffer *buf, *tmp; + uint32_t i, vb_phyaddr = 0, buf_phyaddr = 0; + unsigned long flags = 0; + + pcam_inst = vb2_get_drv_priv(vb->vb2_queue); + pcam = pcam_inst->pcam; + buf = container_of(vb, struct msm_frame_buffer, vidbuf); + + if (pcam_inst->vid_fmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + for (i = 0; i < vb->num_planes; i++) { + mem = vb2_plane_cookie(vb, i); + if (!mem) { + D("%s Inst %p memory already freed up. return", + __func__, pcam_inst); + return; + } + D("%s: inst=%p, buf=0x%x, idx=%d plane id = %d\n", + __func__, pcam_inst, + (uint32_t)buf, vb->v4l2_buf.index, i); + + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + list_for_each_entry_safe(buf, tmp, + &pcam_inst->free_vq, list) { + if (&buf->vidbuf == vb) { + list_del_init(&buf->list); + break; + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + } + } else { + mem = vb2_plane_cookie(vb, 0); + if (!mem) + return; + D("%s: inst=0x%x, buf=0x%x, idx=%d\n", __func__, + (uint32_t)pcam_inst, (uint32_t)buf, vb->v4l2_buf.index); + vb_phyaddr = (unsigned long) videobuf2_to_pmem_contig(vb, 0); + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + list_for_each_entry_safe(buf, tmp, + &pcam_inst->free_vq, list) { + buf_phyaddr = (unsigned long) + videobuf2_to_pmem_contig(&buf->vidbuf, 0); + D("%s vb_idx=%d,vb_paddr=0x%x,phyaddr=0x%x\n", + __func__, buf->vidbuf.v4l2_buf.index, + buf_phyaddr, vb_phyaddr); + if (vb_phyaddr == buf_phyaddr) { + list_del_init(&buf->list); + break; + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + } + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + for (i = 0; i < vb->num_planes; i++) { + mem = vb2_plane_cookie(vb, i); + videobuf2_pmem_contig_user_put(mem, pmctl->client); + } + buf->state = MSM_BUFFER_STATE_UNUSED; +} + +static int msm_vb2_ops_start_streaming(struct vb2_queue *q, unsigned int count) +{ + return 0; +} + +static int msm_vb2_ops_stop_streaming(struct vb2_queue *q) +{ + return 0; +} + +static void msm_vb2_ops_buf_queue(struct vb2_buffer *vb) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + struct msm_cam_v4l2_device *pcam = NULL; + unsigned long flags = 0; + struct vb2_queue *vq = vb->vb2_queue; + struct msm_frame_buffer *buf; + D("%s\n", __func__); + if (!vb || !vq) { + pr_err("%s error : input is NULL\n", __func__); + return ; + } + pcam_inst = vb2_get_drv_priv(vq); + pcam = pcam_inst->pcam; + D("%s pcam_inst=%p,(vb=0x%p),idx=%d,len=%d\n", + __func__, pcam_inst, + vb, vb->v4l2_buf.index, vb->v4l2_buf.length); + D("%s pcam_inst=%p, idx=%d\n", __func__, pcam_inst, + vb->v4l2_buf.index); + buf = container_of(vb, struct msm_frame_buffer, vidbuf); + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + /* we are returning a buffer to the queue */ + list_add_tail(&buf->list, &pcam_inst->free_vq); + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + buf->state = MSM_BUFFER_STATE_QUEUED; +} + +static struct vb2_ops msm_vb2_ops = { + .queue_setup = msm_vb2_ops_queue_setup, + .wait_prepare = msm_vb2_ops_wait_prepare, + .wait_finish = msm_vb2_ops_wait_finish, + .buf_init = msm_vb2_ops_buf_init, + .buf_prepare = msm_vb2_ops_buf_prepare, + .buf_finish = msm_vb2_ops_buf_finish, + .buf_cleanup = msm_vb2_ops_buf_cleanup, + .start_streaming = msm_vb2_ops_start_streaming, + .stop_streaming = msm_vb2_ops_stop_streaming, + .buf_queue = msm_vb2_ops_buf_queue, +}; + + +/* prepare a video buffer queue for a vl42 device*/ +static int msm_vbqueue_init(struct msm_cam_v4l2_dev_inst *pcam_inst, + struct vb2_queue *q, enum v4l2_buf_type type) +{ + if (!q) { + pr_err("%s error : input is NULL\n", __func__); + return -EINVAL; + } + + spin_lock_init(&pcam_inst->vq_irqlock); + INIT_LIST_HEAD(&pcam_inst->free_vq); + videobuf2_queue_pmem_contig_init(q, type, + &msm_vb2_ops, + sizeof(struct msm_frame_buffer), + (void *)pcam_inst); + return 0; +} + +int msm_mctl_img_mode_to_inst_index(struct msm_cam_media_controller *pmctl, + int image_mode, int node_type) +{ + if ((image_mode >= 0) && node_type && + pmctl->pcam_ptr->mctl_node.dev_inst_map[image_mode]) + return pmctl->pcam_ptr-> + mctl_node.dev_inst_map[image_mode]->my_index; + else if ((image_mode >= 0) && + pmctl->pcam_ptr->dev_inst_map[image_mode]) + return pmctl->pcam_ptr-> + dev_inst_map[image_mode]->my_index; + else + return -EINVAL; +} + +void msm_mctl_gettimeofday(struct timeval *tv) +{ + struct timespec ts; + + BUG_ON(!tv); + + ktime_get_ts(&ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec/1000; +} + +struct msm_frame_buffer *msm_mctl_buf_find( + struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, int del_buf, + int image_mode, struct msm_free_buf *fbuf) +{ + struct msm_frame_buffer *buf = NULL, *tmp; + uint32_t buf_phyaddr = 0; + unsigned long flags = 0; + uint32_t buf_idx, offset = 0; + struct videobuf2_contig_pmem *mem; + + /* we actually need a list, not a queue */ + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + list_for_each_entry_safe(buf, tmp, + &pcam_inst->free_vq, list) { + buf_idx = buf->vidbuf.v4l2_buf.index; + mem = vb2_plane_cookie(&buf->vidbuf, 0); + if (mem->buffer_type == VIDEOBUF2_MULTIPLE_PLANES) + offset = mem->offset.data_offset + + pcam_inst->buf_offset[buf_idx][0].data_offset; + else + offset = mem->offset.sp_off.y_off; + buf_phyaddr = (unsigned long) + videobuf2_to_pmem_contig(&buf->vidbuf, 0) + + offset; + D("%s vb_idx=%d,vb_paddr=0x%x ch0=0x%x\n", + __func__, buf->vidbuf.v4l2_buf.index, + buf_phyaddr, fbuf->ch_paddr[0]); + if (fbuf->ch_paddr[0] == buf_phyaddr) { + if (del_buf) + list_del_init(&buf->list); + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, + flags); + buf->state = MSM_BUFFER_STATE_RESERVED; + return buf; + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return NULL; +} + +int msm_mctl_buf_done_proc( + struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, + int image_mode, struct msm_free_buf *fbuf, + uint32_t *frame_id, int gen_timestamp) +{ + struct msm_frame_buffer *buf = NULL; + int del_buf = 1; + + buf = msm_mctl_buf_find(pmctl, pcam_inst, del_buf, + image_mode, fbuf); + if (!buf) { + pr_err("%s: buf=0x%x not found\n", + __func__, fbuf->ch_paddr[0]); + return -EINVAL; + } + if (gen_timestamp) { + if (frame_id) + buf->vidbuf.v4l2_buf.sequence = *frame_id; + msm_mctl_gettimeofday( + &buf->vidbuf.v4l2_buf.timestamp); + } + vb2_buffer_done(&buf->vidbuf, VB2_BUF_STATE_DONE); + return 0; +} + + +int msm_mctl_buf_done(struct msm_cam_media_controller *p_mctl, + int image_mode, struct msm_free_buf *fbuf, + uint32_t frame_id) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + int idx, rc; + int pp_divert_type = 0, pp_type = 0; + + msm_mctl_check_pp(p_mctl, image_mode, &pp_divert_type, &pp_type); + D("%s: pp_type=%d, pp_divert_type = %d, frame_id = 0x%x image_mode %d", + __func__, pp_type, pp_divert_type, frame_id, image_mode); + if (pp_type || pp_divert_type) + rc = msm_mctl_do_pp_divert(p_mctl, + image_mode, fbuf, frame_id, pp_type); + else { + idx = msm_mctl_img_mode_to_inst_index( + p_mctl, image_mode, 0); + if (idx < 0) { + /* check mctl node */ + if ((image_mode >= 0) && + p_mctl->pcam_ptr->mctl_node. + dev_inst_map[image_mode]) { + int index = p_mctl->pcam_ptr->mctl_node. + dev_inst_map[image_mode]->my_index; + pcam_inst = p_mctl->pcam_ptr->mctl_node. + dev_inst[index]; + D("%s: Mctl node index %d inst %p", + __func__, index, pcam_inst); + rc = msm_mctl_buf_done_proc(p_mctl, pcam_inst, + image_mode, fbuf, + &frame_id, 1); + D("%s mctl node buf done %d\n", __func__, 0); + return -EINVAL; + } else { + pr_err("%s Invalid instance, dropping buffer\n", + __func__); + return idx; + } + } + pcam_inst = p_mctl->pcam_ptr->dev_inst[idx]; + rc = msm_mctl_buf_done_proc(p_mctl, pcam_inst, + image_mode, fbuf, + &frame_id, 1); + } + return rc; +} + +int msm_mctl_buf_init(struct msm_cam_v4l2_device *pcam) +{ + struct msm_cam_media_controller *pmctl; + pmctl = msm_camera_get_mctl(pcam->mctl_handle); + pmctl->mctl_vbqueue_init = msm_vbqueue_init; + return 0; +} + +static int is_buffer_queued(struct msm_cam_v4l2_device *pcam, int image_mode) +{ + int idx; + int ret = 0; + struct msm_frame_buffer *buf = NULL; + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + idx = pcam->mctl_node.dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->mctl_node.dev_inst[idx]; + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + if (buf->state != MSM_BUFFER_STATE_QUEUED) + continue; + ret = 1; + } + return ret; +} + +struct msm_cam_v4l2_dev_inst *msm_mctl_get_pcam_inst( + struct msm_cam_media_controller *pmctl, + int image_mode) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + struct msm_cam_v4l2_device *pcam = pmctl->pcam_ptr; + int idx; + + if (image_mode >= 0) { + /* Valid image mode. Search the mctl node first. + * If mctl node doesnt have the instance, then + * search in the user's video node */ + if (pmctl->vfe_output_mode == VFE_OUTPUTS_MAIN_AND_THUMB + || pmctl->vfe_output_mode == VFE_OUTPUTS_THUMB_AND_MAIN) { + if (pcam->mctl_node.dev_inst_map[image_mode] + && is_buffer_queued(pcam, image_mode)) { + idx = + pcam->mctl_node.dev_inst_map[image_mode] + ->my_index; + pcam_inst = pcam->mctl_node.dev_inst[idx]; + D("%s Found instance %p in mctl node device\n", + __func__, pcam_inst); + } else if (pcam->dev_inst_map[image_mode]) { + idx = pcam->dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->dev_inst[idx]; + D("%s Found instance %p in video device\n", + __func__, pcam_inst); + } + } else { + if (pcam->mctl_node.dev_inst_map[image_mode]) { + idx = pcam->mctl_node.dev_inst_map[image_mode] + ->my_index; + pcam_inst = pcam->mctl_node.dev_inst[idx]; + D("%s Found instance %p in mctl node device\n", + __func__, pcam_inst); + } else if (pcam->dev_inst_map[image_mode]) { + idx = pcam->dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->dev_inst[idx]; + D("%s Found instance %p in video device\n", + __func__, pcam_inst); + } + } + } else + pr_err("%s Invalid image mode %d. Return NULL\n", + __func__, image_mode); + return pcam_inst; +} + +int msm_mctl_reserve_free_buf( + struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pref_pcam_inst, + int image_mode, struct msm_free_buf *free_buf) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst = pref_pcam_inst; + unsigned long flags = 0; + struct videobuf2_contig_pmem *mem; + struct msm_frame_buffer *buf = NULL; + int rc = -EINVAL, i; + uint32_t buf_idx, plane_offset = 0; + + if (!free_buf || !pmctl) { + pr_err("%s: free_buf/pmctl is null\n", __func__); + return rc; + } + memset(free_buf, 0, sizeof(struct msm_free_buf)); + + /* If the caller wants to reserve a buffer from a particular + * camera instance, he would send the preferred camera instance. + * If the preferred camera instance is NULL, get the + * camera instance using the image mode passed */ + if (!pcam_inst) + pcam_inst = msm_mctl_get_pcam_inst(pmctl, image_mode); + + if (!pcam_inst || !pcam_inst->streamon) { + pr_err("%s: stream is turned off\n", __func__); + return rc; + } + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + if (buf->state != MSM_BUFFER_STATE_QUEUED) + continue; + + buf_idx = buf->vidbuf.v4l2_buf.index; + if (pcam_inst->vid_fmt.type == + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + free_buf->num_planes = + pcam_inst->plane_info.num_planes; + for (i = 0; i < free_buf->num_planes; i++) { + mem = vb2_plane_cookie(&buf->vidbuf, i); + if (mem->buffer_type == + VIDEOBUF2_MULTIPLE_PLANES) + plane_offset = + mem->offset.data_offset; + else + plane_offset = + mem->offset.sp_off.cbcr_off; + + D("%s: data off %d plane off %d", + __func__, + pcam_inst->buf_offset[buf_idx][i]. + data_offset, plane_offset); + free_buf->ch_paddr[i] = (uint32_t) + videobuf2_to_pmem_contig(&buf->vidbuf, i) + + pcam_inst->buf_offset[buf_idx][i].data_offset + + plane_offset; + + } + } else { + mem = vb2_plane_cookie(&buf->vidbuf, 0); + free_buf->ch_paddr[0] = (uint32_t) + videobuf2_to_pmem_contig(&buf->vidbuf, 0) + + mem->offset.sp_off.y_off; + free_buf->ch_paddr[1] = free_buf->ch_paddr[0] + + mem->offset.sp_off.cbcr_off; + } + free_buf->vb = (uint32_t)buf; + buf->state = MSM_BUFFER_STATE_RESERVED; + D("%s inst=0x%p, idx=%d, paddr=0x%x, " + "ch1 addr=0x%x\n", __func__, + pcam_inst, buf->vidbuf.v4l2_buf.index, + free_buf->ch_paddr[0], free_buf->ch_paddr[1]); + rc = 0; + break; + } + if (rc != 0) + D("%s:No free buffer available: inst = 0x%p ", + __func__, pcam_inst); + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return rc; +} + +int msm_mctl_release_free_buf(struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, + int image_mode, struct msm_free_buf *free_buf) +{ + unsigned long flags = 0; + struct msm_frame_buffer *buf = NULL; + uint32_t buf_phyaddr = 0; + int rc = -EINVAL; + + if (!free_buf) + return rc; + + if (!pcam_inst) { + pr_err("%s Invalid instance, buffer not released\n", + __func__); + return rc; + } + + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + buf_phyaddr = + (uint32_t) videobuf2_to_pmem_contig(&buf->vidbuf, 0); + if (free_buf->ch_paddr[0] == buf_phyaddr) { + D("%s buf = 0x%x \n", __func__, free_buf->ch_paddr[0]); + buf->state = MSM_BUFFER_STATE_UNUSED; + rc = 0; + break; + } + } + + if (rc != 0) + pr_err("%s invalid buffer address ", __func__); + + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return rc; +} + +int msm_mctl_buf_done_pp(struct msm_cam_media_controller *pmctl, + int image_mode, struct msm_free_buf *frame, int dirty, int node_type) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + int rc = 0, idx; + + idx = msm_mctl_img_mode_to_inst_index(pmctl, image_mode, node_type); + if (idx < 0) { + pr_err("%s Invalid instance, buffer not released\n", __func__); + return idx; + } + if (node_type) + pcam_inst = pmctl->pcam_ptr->mctl_node.dev_inst[idx]; + else + pcam_inst = pmctl->pcam_ptr->dev_inst[idx]; + if (!pcam_inst) { + pr_err("%s Invalid instance, cannot send buf to user", + __func__); + return -EINVAL; + } + + D("%s:inst=0x%p, paddr=0x%x, dirty=%d", + __func__, pcam_inst, frame->ch_paddr[0], dirty); + if (dirty) + /* the frame is dirty, not going to disptach to app */ + rc = msm_mctl_release_free_buf(pmctl, pcam_inst, + image_mode, frame); + else + rc = msm_mctl_buf_done_proc(pmctl, pcam_inst, + image_mode, frame, NULL, 0); + return rc; +} + +struct msm_frame_buffer *msm_mctl_get_free_buf( + struct msm_cam_media_controller *pmctl, + int image_mode) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + unsigned long flags = 0; + struct msm_frame_buffer *buf = NULL; + int rc = -EINVAL, idx; + + idx = msm_mctl_img_mode_to_inst_index(pmctl, + image_mode, 0); + if (idx < 0) { + pr_err("%s Invalid instance, cant get buffer\n", __func__); + return NULL; + } + pcam_inst = pmctl->pcam_ptr->dev_inst[idx]; + if (!pcam_inst->streamon) { + D("%s: stream 0x%p is off\n", __func__, pcam_inst); + return NULL; + } + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + if (!list_empty(&pcam_inst->free_vq)) { + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + if (buf->state == MSM_BUFFER_STATE_QUEUED) { + buf->state = MSM_BUFFER_STATE_RESERVED; + rc = 0; + break; + } + } + } + if (rc != 0) { + D("%s:No free buffer available: inst = 0x%p ", + __func__, pcam_inst); + buf = NULL; + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return buf; +} + +int msm_mctl_put_free_buf( + struct msm_cam_media_controller *pmctl, + int image_mode, struct msm_frame_buffer *my_buf) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + unsigned long flags = 0; + int rc = 0, idx; + struct msm_frame_buffer *buf = NULL; + + idx = msm_mctl_img_mode_to_inst_index(pmctl, + image_mode, 0); + if (idx < 0) { + pr_err("%s Invalid instance, cant put buffer\n", __func__); + return idx; + } + pcam_inst = pmctl->pcam_ptr->dev_inst[idx]; + if (!pcam_inst->streamon) { + D("%s: stream 0x%p is off\n", __func__, pcam_inst); + return rc; + } + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + if (!list_empty(&pcam_inst->free_vq)) { + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + if (my_buf == buf) { + buf->state = MSM_BUFFER_STATE_QUEUED; + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, + flags); + return 0; + } + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return rc; +} + +int msm_mctl_buf_del(struct msm_cam_media_controller *pmctl, + int image_mode, + struct msm_frame_buffer *my_buf) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_frame_buffer *buf = NULL; + unsigned long flags = 0; + int idx; + + idx = msm_mctl_img_mode_to_inst_index(pmctl, + image_mode, 0); + if (idx < 0) { + pr_err("%s Invalid instance, cant delete buffer\n", __func__); + return idx; + } + pcam_inst = pmctl->pcam_ptr->dev_inst[idx]; + D("%s: idx = %d, pinst=0x%p", __func__, idx, pcam_inst); + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + if (!list_empty(&pcam_inst->free_vq)) { + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + if (my_buf == buf) { + list_del_init(&buf->list); + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, + flags); + return 0; + } + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + pr_err("%s: buf 0x%p not found", __func__, my_buf); + return -EINVAL; +} + +int msm_mctl_buf_return_buf(struct msm_cam_media_controller *pmctl, + int image_mode, struct msm_frame_buffer *rbuf) +{ + int idx = 0; + struct msm_frame_buffer *buf = NULL; + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_cam_v4l2_device *pcam = pmctl->pcam_ptr; + unsigned long flags = 0; + + if (pcam->mctl_node.dev_inst_map[image_mode]) { + idx = pcam->mctl_node.dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->mctl_node.dev_inst[idx]; + D("%s Found instance %p in mctl node device\n", + __func__, pcam_inst); + } else { + pr_err("%s Invalid image mode %d ", __func__, image_mode); + return -EINVAL; + } + + if (!pcam_inst) { + pr_err("%s Invalid instance\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + if (!list_empty(&pcam_inst->free_vq)) { + list_for_each_entry(buf, &pcam_inst->free_vq, list) { + if (rbuf == buf) { + D("%s Return buffer %x in pcam_inst %p ", + __func__, (int)rbuf, pcam_inst); + buf->state = MSM_BUFFER_STATE_QUEUED; + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, + flags); + return 0; + } + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return -EINVAL; +} diff --git a/drivers/media/video/msm/msm_mctl_pp.c b/drivers/media/video/msm/msm_mctl_pp.c new file mode 100644 index 0000000000000000000000000000000000000000..f4c04bb9f89ecec1a214fa8ce3c6f137cb32fc1b --- /dev/null +++ b/drivers/media/video/msm/msm_mctl_pp.c @@ -0,0 +1,1029 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_vpe.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 + + +static int msm_mctl_pp_vpe_ioctl(struct v4l2_subdev *vpe_sd, + struct msm_mctl_pp_cmd *cmd, void *data) +{ + int rc = 0; + struct msm_mctl_pp_params parm; + parm.cmd = cmd; + parm.data = data; + rc = v4l2_subdev_call(vpe_sd, core, ioctl, VIDIOC_MSM_VPE_CFG, &parm); + return rc; +} + +static int msm_mctl_pp_buf_divert( + struct msm_cam_media_controller *pmctl, + struct msm_cam_v4l2_dev_inst *pcam_inst, + struct msm_cam_evt_divert_frame *div) +{ + struct v4l2_event v4l2_evt; + struct msm_isp_event_ctrl *isp_event; + isp_event = kzalloc(sizeof(struct msm_isp_event_ctrl), + GFP_ATOMIC); + if (!isp_event) { + pr_err("%s Insufficient memory. return", __func__); + return -ENOMEM; + } + D("%s: msm_cam_evt_divert_frame=%d", + __func__, sizeof(struct msm_cam_evt_divert_frame)); + memset(&v4l2_evt, 0, sizeof(v4l2_evt)); + v4l2_evt.id = 0; + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_DIV_FRAME_EVT_MSG; + *((uint32_t *)v4l2_evt.u.data) = (uint32_t)isp_event; + /* Copy the divert frame struct into event ctrl struct. */ + isp_event->isp_data.div_frame = *div; + + D("%s inst=%p, img_mode=%d, frame_id=%d\n", __func__, + pcam_inst, pcam_inst->image_mode, div->frame.frame_id); + v4l2_event_queue( + pmctl->config_device->config_stat_event_queue.pvdev, + &v4l2_evt); + return 0; +} + +int msm_mctl_check_pp(struct msm_cam_media_controller *p_mctl, + int image_mode, int *pp_divert_type, int *pp_type) +{ + int rc = 0; + unsigned long flags; + uint32_t pp_key = 0; + + *pp_type = 0; + *pp_divert_type = 0; + spin_lock_irqsave(&p_mctl->pp_info.lock, flags); + switch (image_mode) { + case MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW: + pp_key = PP_PREV; + if (p_mctl->pp_info.pp_key & pp_key) + *pp_divert_type = OUTPUT_TYPE_P; + if (p_mctl->pp_info.pp_ctrl.pp_msg_type & OUTPUT_TYPE_P) + *pp_type = OUTPUT_TYPE_P; + break; + case MSM_V4L2_EXT_CAPTURE_MODE_MAIN: + pp_key = PP_SNAP; + if (p_mctl->pp_info.pp_key & pp_key) + *pp_divert_type = OUTPUT_TYPE_S; + if (p_mctl->pp_info.pp_ctrl.pp_msg_type & OUTPUT_TYPE_S) + *pp_type = OUTPUT_TYPE_P; + break; + case MSM_V4L2_EXT_CAPTURE_MODE_VIDEO: + if (p_mctl->pp_info.pp_ctrl.pp_msg_type == OUTPUT_TYPE_V) + *pp_type = OUTPUT_TYPE_V; + break; + case MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL: + pp_key = PP_THUMB; + if (p_mctl->pp_info.pp_key & pp_key) + *pp_divert_type = OUTPUT_TYPE_T; + if (p_mctl->pp_info.pp_ctrl.pp_msg_type == OUTPUT_TYPE_T) + *pp_type = OUTPUT_TYPE_T; + break; + default: + break; + } + if (p_mctl->vfe_output_mode != VFE_OUTPUTS_MAIN_AND_THUMB && + p_mctl->vfe_output_mode != VFE_OUTPUTS_THUMB_AND_MAIN) { + if (p_mctl->pp_info.div_frame[image_mode].ch_paddr[0]) + *pp_divert_type = 0; + } + spin_unlock_irqrestore(&p_mctl->pp_info.lock, flags); + D("%s: pp_type=%d, pp_divert_type = %d", + __func__, *pp_type, *pp_divert_type); + return rc; +} + +static int is_buf_in_queue(struct msm_cam_v4l2_device *pcam, + struct msm_free_buf *fbuf, int image_mode) +{ + struct msm_frame_buffer *buf = NULL, *tmp; + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + unsigned long flags = 0; + struct videobuf2_contig_pmem *mem; + uint32_t buf_idx, offset = 0; + uint32_t buf_phyaddr = 0; + int idx; + idx = pcam->mctl_node.dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->mctl_node.dev_inst[idx]; + spin_lock_irqsave(&pcam_inst->vq_irqlock, flags); + list_for_each_entry_safe(buf, tmp, + &pcam_inst->free_vq, list) { + buf_idx = buf->vidbuf.v4l2_buf.index; + mem = vb2_plane_cookie(&buf->vidbuf, 0); + if (mem->buffer_type == VIDEOBUF2_MULTIPLE_PLANES) + offset = mem->offset.data_offset + + pcam_inst->buf_offset[buf_idx][0].data_offset; + else + offset = mem->offset.sp_off.y_off; + buf_phyaddr = (unsigned long) + videobuf2_to_pmem_contig(&buf->vidbuf, 0) + + offset; + D("%s vb_idx=%d,vb_paddr=0x%x ch0=0x%x\n", + __func__, buf->vidbuf.v4l2_buf.index, + buf_phyaddr, fbuf->ch_paddr[0]); + if (fbuf->ch_paddr[0] == buf_phyaddr) { + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return 1; + } + } + spin_unlock_irqrestore(&pcam_inst->vq_irqlock, flags); + return 0; +} +static struct msm_cam_v4l2_dev_inst *msm_mctl_get_pcam_inst_for_divert( + struct msm_cam_media_controller *pmctl, + int image_mode, struct msm_free_buf *fbuf, int *node_type) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + struct msm_cam_v4l2_device *pcam = pmctl->pcam_ptr; + int idx; + + if (image_mode >= 0) { + /* Valid image mode. Search the mctl node first. + * If mctl node doesnt have the instance, then + * search in the user's video node */ + if (pcam->mctl_node.dev_inst_map[image_mode] + && is_buf_in_queue(pcam, fbuf, image_mode)) { + idx = + pcam->mctl_node.dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->mctl_node.dev_inst[idx]; + *node_type = MCTL_NODE; + D("%s Found instance %p in mctl node device\n", + __func__, pcam_inst); + } else if (pcam->dev_inst_map[image_mode]) { + idx = pcam->dev_inst_map[image_mode]->my_index; + pcam_inst = pcam->dev_inst[idx]; + *node_type = VIDEO_NODE; + D("%s Found instance %p in video device", + __func__, pcam_inst); + } else + pr_err("%s Invalid image mode %d. Return NULL\n", + __func__, image_mode); + } + return pcam_inst; +} + +int msm_mctl_do_pp_divert( + struct msm_cam_media_controller *p_mctl, + int image_mode, struct msm_free_buf *fbuf, + uint32_t frame_id, int pp_type) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst; + int rc = 0, i, buf_idx; + int del_buf = 0; /* delete from free queue */ + struct msm_cam_evt_divert_frame div; + struct msm_frame_buffer *vb = NULL; + struct videobuf2_contig_pmem *mem; + int node; + + pcam_inst = msm_mctl_get_pcam_inst_for_divert + (p_mctl, image_mode, fbuf, &node); + if (!pcam_inst) { + pr_err("%s Invalid instance. Cannot divert frame.\n", + __func__); + return -EINVAL; + } + vb = msm_mctl_buf_find(p_mctl, pcam_inst, + del_buf, image_mode, fbuf); + if (!vb) + return -EINVAL; + + vb->vidbuf.v4l2_buf.sequence = frame_id; + buf_idx = vb->vidbuf.v4l2_buf.index; + D("%s Diverting frame %d %x Image mode %d\n", __func__, buf_idx, + (uint32_t)vb, pcam_inst->image_mode); + div.image_mode = pcam_inst->image_mode; + div.op_mode = pcam_inst->pcam->op_mode; + div.inst_idx = pcam_inst->my_index; + div.node_idx = pcam_inst->pcam->vnode_id; + p_mctl->pp_info.cur_frame_id[pcam_inst->image_mode] = frame_id; + div.frame.frame_id = + p_mctl->pp_info.cur_frame_id[pcam_inst->image_mode]; + div.frame.buf_idx = buf_idx; + div.frame.handle = (uint32_t)vb; + msm_mctl_gettimeofday(&div.frame.timestamp); + vb->vidbuf.v4l2_buf.timestamp = div.frame.timestamp; + div.do_pp = pp_type; + D("%s Diverting frame %x id %d to userspace ", __func__, + (int)div.frame.handle, div.frame.frame_id); + /* Get the cookie for 1st plane and store the path. + * Also use this to check the number of planes in + * this buffer.*/ + mem = vb2_plane_cookie(&vb->vidbuf, 0); + div.frame.path = mem->path; + div.frame.node_type = node; + if (mem->buffer_type == VIDEOBUF2_SINGLE_PLANE) { + /* This buffer contains only 1 plane. Use the + * single planar structure to store the info.*/ + div.frame.num_planes = 1; + div.frame.sp.phy_addr = + videobuf2_to_pmem_contig(&vb->vidbuf, 0); + div.frame.sp.addr_offset = mem->addr_offset; + div.frame.sp.y_off = 0; + div.frame.sp.cbcr_off = mem->offset.sp_off.cbcr_off; + div.frame.sp.fd = (int)mem->vaddr; + div.frame.sp.length = mem->size; + if (!pp_type) + p_mctl->pp_info.div_frame[pcam_inst->image_mode]. + ch_paddr[0] = div.frame.sp.phy_addr; + } else { + /* This buffer contains multiple planes. Use the mutliplanar + * structure to store the info. */ + div.frame.num_planes = pcam_inst->plane_info.num_planes; + /* Now traverse through all the planes of the buffer to + * fill out the plane info. */ + for (i = 0; i < div.frame.num_planes; i++) { + mem = vb2_plane_cookie(&vb->vidbuf, i); + div.frame.mp[i].phy_addr = + videobuf2_to_pmem_contig(&vb->vidbuf, i); + if (!pcam_inst->buf_offset) + div.frame.mp[i].data_offset = 0; + else + div.frame.mp[i].data_offset = + pcam_inst->buf_offset[buf_idx][i].data_offset; + div.frame.mp[i].addr_offset = + mem->addr_offset; + div.frame.mp[i].fd = (int)mem->vaddr; + div.frame.mp[i].length = mem->size; + } + if (!pp_type) + p_mctl->pp_info.div_frame[pcam_inst->image_mode]. + ch_paddr[0] = div.frame.mp[0].phy_addr + + div.frame.mp[0].data_offset; + } + rc = msm_mctl_pp_buf_divert(p_mctl, pcam_inst, &div); + return rc; +} + +static int msm_mctl_pp_get_phy_addr( + struct msm_cam_v4l2_dev_inst *pcam_inst, + uint32_t frame_handle, + struct msm_pp_frame *pp_frame) +{ + struct msm_frame_buffer *vb = NULL; + struct videobuf2_contig_pmem *mem; + int i, buf_idx = 0; + + vb = (struct msm_frame_buffer *)frame_handle; + buf_idx = vb->vidbuf.v4l2_buf.index; + memset(pp_frame, 0, sizeof(struct msm_pp_frame)); + pp_frame->handle = (uint32_t)vb; + pp_frame->frame_id = vb->vidbuf.v4l2_buf.sequence; + pp_frame->timestamp = vb->vidbuf.v4l2_buf.timestamp; + pp_frame->buf_idx = buf_idx; + /* Get the cookie for 1st plane and store the path. + * Also use this to check the number of planes in + * this buffer.*/ + mem = vb2_plane_cookie(&vb->vidbuf, 0); + pp_frame->image_type = (unsigned short)mem->path; + if (mem->buffer_type == VIDEOBUF2_SINGLE_PLANE) { + pp_frame->num_planes = 1; + pp_frame->sp.addr_offset = mem->addr_offset; + pp_frame->sp.phy_addr = + videobuf2_to_pmem_contig(&vb->vidbuf, 0); + pp_frame->sp.y_off = 0; + pp_frame->sp.cbcr_off = mem->offset.sp_off.cbcr_off; + pp_frame->sp.length = mem->size; + pp_frame->sp.fd = (int)mem->vaddr; + } else { + pp_frame->num_planes = pcam_inst->plane_info.num_planes; + for (i = 0; i < pp_frame->num_planes; i++) { + mem = vb2_plane_cookie(&vb->vidbuf, i); + pp_frame->mp[i].addr_offset = mem->addr_offset; + pp_frame->mp[i].phy_addr = + videobuf2_to_pmem_contig(&vb->vidbuf, i); + pp_frame->mp[i].data_offset = + pcam_inst->buf_offset[buf_idx][i].data_offset; + pp_frame->mp[i].fd = (int)mem->vaddr; + pp_frame->mp[i].length = mem->size; + D("%s frame id %d buffer %d plane %d phy addr 0x%x" + " fd %d length %d\n", __func__, + pp_frame->frame_id, buf_idx, i, + (uint32_t)pp_frame->mp[i].phy_addr, + pp_frame->mp[i].fd, pp_frame->mp[i].length); + } + } + return 0; +} + +static int msm_mctl_pp_copy_timestamp_and_frame_id( + uint32_t src_handle, uint32_t dest_handle) +{ + struct msm_frame_buffer *src_vb; + struct msm_frame_buffer *dest_vb; + + src_vb = (struct msm_frame_buffer *)src_handle; + dest_vb = (struct msm_frame_buffer *)dest_handle; + dest_vb->vidbuf.v4l2_buf.timestamp = + src_vb->vidbuf.v4l2_buf.timestamp; + dest_vb->vidbuf.v4l2_buf.sequence = + src_vb->vidbuf.v4l2_buf.sequence; + D("%s: timestamp=%ld:%ld,frame_id=0x%x", __func__, + dest_vb->vidbuf.v4l2_buf.timestamp.tv_sec, + dest_vb->vidbuf.v4l2_buf.timestamp.tv_usec, + dest_vb->vidbuf.v4l2_buf.sequence); + return 0; +} + +static int msm_mctl_pp_path_to_inst_index(struct msm_cam_v4l2_device *pcam, + int out_type) +{ + int image_mode; + switch (out_type) { + case OUTPUT_TYPE_P: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW; + break; + case OUTPUT_TYPE_V: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_VIDEO; + break; + case OUTPUT_TYPE_S: + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_MAIN; + break; + default: + image_mode = -1; + break; + } + if ((image_mode >= 0) && pcam->dev_inst_map[image_mode]) + return pcam->dev_inst_map[image_mode]->my_index; + else + return -EINVAL; +} + +int msm_mctl_pp_proc_vpe_cmd( + struct msm_cam_media_controller *p_mctl, + struct msm_mctl_pp_cmd *pp_cmd) +{ + int rc = 0, idx; + void __user *argp = (void __user *)pp_cmd->value; + struct msm_cam_v4l2_dev_inst *pcam_inst; + + switch (pp_cmd->id) { + case VPE_CMD_INIT: + case VPE_CMD_DEINIT: + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + case VPE_CMD_DISABLE: + case VPE_CMD_RESET: + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + case VPE_CMD_ENABLE: { + struct msm_vpe_clock_rate clk_rate; + if (sizeof(struct msm_vpe_clock_rate) != + pp_cmd->length) { + pr_err("%s: vpe cmd size mismatch " + "(id=%d, length = %d, expect size = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_clock_rate)); + rc = -EINVAL; + break; + } + if (copy_from_user(&clk_rate, pp_cmd->value, + sizeof(struct msm_vpe_clock_rate))) { + pr_err("%s:clk_rate copy failed", __func__); + return -EFAULT; + } + pp_cmd->value = (void *)&clk_rate; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + pp_cmd->value = argp; + break; + } + case VPE_CMD_FLUSH: { + struct msm_vpe_flush_frame_buffer flush_buf; + if (sizeof(struct msm_vpe_flush_frame_buffer) != + pp_cmd->length) { + D("%s: size mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_flush_frame_buffer)); + rc = -EINVAL; + break; + } + if (copy_from_user( + &flush_buf, pp_cmd->value, sizeof(flush_buf))) + return -EFAULT; + pp_cmd->value = (void *)&flush_buf; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + if (rc == 0) { + if (copy_to_user((void *)argp, + &flush_buf, + sizeof(flush_buf))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + pp_cmd->value = argp; + } + } + break; + case VPE_CMD_OPERATION_MODE_CFG: { + struct msm_vpe_op_mode_cfg op_mode_cfg; + if (sizeof(struct msm_vpe_op_mode_cfg) != + pp_cmd->length) { + D("%s: size mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_op_mode_cfg)); + rc = -EINVAL; + break; + } + if (copy_from_user(&op_mode_cfg, + pp_cmd->value, + sizeof(op_mode_cfg))) + return -EFAULT; + pp_cmd->value = (void *)&op_mode_cfg; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + } + case VPE_CMD_INPUT_PLANE_CFG: { + struct msm_vpe_input_plane_cfg input_cfg; + if (sizeof(struct msm_vpe_input_plane_cfg) != + pp_cmd->length) { + D("%s: mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_input_plane_cfg)); + rc = -EINVAL; + break; + } + if (copy_from_user( + &input_cfg, pp_cmd->value, sizeof(input_cfg))) + return -EFAULT; + pp_cmd->value = (void *)&input_cfg; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + } + case VPE_CMD_OUTPUT_PLANE_CFG: { + struct msm_vpe_output_plane_cfg output_cfg; + if (sizeof(struct msm_vpe_output_plane_cfg) != + pp_cmd->length) { + D("%s: size mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_output_plane_cfg)); + rc = -EINVAL; + break; + } + if (copy_from_user(&output_cfg, pp_cmd->value, + sizeof(output_cfg))) { + D("%s: cannot copy pp_cmd->value, size=%d", + __func__, pp_cmd->length); + return -EFAULT; + } + pp_cmd->value = (void *)&output_cfg; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + } + case VPE_CMD_INPUT_PLANE_UPDATE: { + struct msm_vpe_input_plane_update_cfg input_update_cfg; + if (sizeof(struct msm_vpe_input_plane_update_cfg) != + pp_cmd->length) { + D("%s: size mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_input_plane_update_cfg)); + rc = -EINVAL; + break; + } + if (copy_from_user(&input_update_cfg, pp_cmd->value, + sizeof(input_update_cfg))) + return -EFAULT; + pp_cmd->value = (void *)&input_update_cfg; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + } + case VPE_CMD_SCALE_CFG_TYPE: { + struct msm_vpe_scaler_cfg scaler_cfg; + if (sizeof(struct msm_vpe_scaler_cfg) != + pp_cmd->length) { + D("%s: size mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(struct msm_vpe_scaler_cfg)); + rc = -EINVAL; + break; + } + if (copy_from_user(&scaler_cfg, pp_cmd->value, + sizeof(scaler_cfg))) + return -EFAULT; + pp_cmd->value = (void *)&scaler_cfg; + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, NULL); + break; + } + case VPE_CMD_ZOOM: { + struct msm_mctl_pp_frame_info *zoom; + zoom = kmalloc(sizeof(struct msm_mctl_pp_frame_info), + GFP_ATOMIC); + if (!zoom) { + rc = -ENOMEM; + break; + } + if (sizeof(zoom->pp_frame_cmd) != pp_cmd->length) { + D("%s: size mismatch(id=%d, len = %d, expected = %d", + __func__, pp_cmd->id, pp_cmd->length, + sizeof(zoom->pp_frame_cmd)); + rc = -EINVAL; + kfree(zoom); + break; + } + if (copy_from_user(&zoom->pp_frame_cmd, pp_cmd->value, + sizeof(zoom->pp_frame_cmd))) { + kfree(zoom); + return -EFAULT; + } + D("%s: src=0x%x, dest=0x%x,cookie=0x%x,action=0x%x,path=0x%x", + __func__, zoom->pp_frame_cmd.src_buf_handle, + zoom->pp_frame_cmd.dest_buf_handle, + zoom->pp_frame_cmd.cookie, + zoom->pp_frame_cmd.vpe_output_action, + zoom->pp_frame_cmd.path); + idx = msm_mctl_pp_path_to_inst_index(p_mctl->pcam_ptr, + zoom->pp_frame_cmd.path); + if (idx < 0) { + pr_err("%s Invalid path, returning\n", __func__); + kfree(zoom); + return idx; + } + pcam_inst = p_mctl->pcam_ptr->dev_inst[idx]; + if (!pcam_inst) { + pr_err("%s Invalid instance, returning\n", __func__); + kfree(zoom); + return -EINVAL; + } + zoom->user_cmd = pp_cmd->id; + rc = msm_mctl_pp_get_phy_addr(pcam_inst, + zoom->pp_frame_cmd.src_buf_handle, &zoom->src_frame); + if (rc) { + kfree(zoom); + break; + } + rc = msm_mctl_pp_get_phy_addr(pcam_inst, + zoom->pp_frame_cmd.dest_buf_handle, &zoom->dest_frame); + if (rc) { + kfree(zoom); + break; + } + rc = msm_mctl_pp_copy_timestamp_and_frame_id( + zoom->pp_frame_cmd.src_buf_handle, + + zoom->pp_frame_cmd.dest_buf_handle); + if (rc) { + kfree(zoom); + break; + } + rc = msm_mctl_pp_vpe_ioctl( + p_mctl->vpe_sdev, pp_cmd, (void *)zoom); + if (rc) { + kfree(zoom); + break; + } + break; + } + default: + rc = -1; + break; + } + return rc; +} + +static int msm_mctl_pp_path_to_img_mode(int path) +{ + switch (path) { + case OUTPUT_TYPE_P: + return MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW; + case OUTPUT_TYPE_V: + return MSM_V4L2_EXT_CAPTURE_MODE_VIDEO; + case OUTPUT_TYPE_S: + return MSM_V4L2_EXT_CAPTURE_MODE_MAIN; + case OUTPUT_TYPE_T: + return MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL; + default: + return -EINVAL; + } +} + +int msm_mctl_pp_proc_cmd(struct msm_cam_media_controller *p_mctl, + struct msm_mctl_pp_cmd *pp_cmd) +{ + int rc = 0; + struct msm_mctl_pp_frame_buffer pp_buffer; + struct msm_frame_buffer *buf = NULL; + void __user *argp = (void __user *)pp_cmd->value; + int img_mode; + unsigned long flags; + + switch (pp_cmd->id) { + case MCTL_CMD_GET_FRAME_BUFFER: { + if (copy_from_user(&pp_buffer, pp_cmd->value, + sizeof(pp_buffer))) + return -EFAULT; + img_mode = msm_mctl_pp_path_to_img_mode(pp_buffer.path); + if (img_mode < 0) { + pr_err("%s Invalid image mode\n", __func__); + return img_mode; + } + buf = msm_mctl_get_free_buf(p_mctl, img_mode); + pp_buffer.buf_handle = (uint32_t)buf; + if (copy_to_user((void *)argp, + &pp_buffer, + sizeof(struct msm_mctl_pp_frame_buffer))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + break; + } + case MCTL_CMD_PUT_FRAME_BUFFER: { + if (copy_from_user(&pp_buffer, pp_cmd->value, + sizeof(pp_buffer))) + return -EFAULT; + img_mode = msm_mctl_pp_path_to_img_mode(pp_buffer.path); + if (img_mode < 0) { + pr_err("%s Invalid image mode\n", __func__); + return img_mode; + } + buf = (struct msm_frame_buffer *)pp_buffer.buf_handle; + msm_mctl_put_free_buf(p_mctl, img_mode, buf); + break; + } + case MCTL_CMD_DIVERT_FRAME_PP_PATH: { + struct msm_mctl_pp_divert_pp divert_pp; + if (copy_from_user(&divert_pp, pp_cmd->value, + sizeof(divert_pp))) + return -EFAULT; + D("%s: PP_PATH, path=%d", + __func__, divert_pp.path); + spin_lock_irqsave(&p_mctl->pp_info.lock, flags); + if (divert_pp.enable) + p_mctl->pp_info.pp_ctrl.pp_msg_type |= divert_pp.path; + else + p_mctl->pp_info.pp_ctrl.pp_msg_type &= ~divert_pp.path; + spin_unlock_irqrestore(&p_mctl->pp_info.lock, flags); + D("%s: pp path = 0x%x", __func__, + p_mctl->pp_info.pp_ctrl.pp_msg_type); + break; + } + default: + rc = -EPERM; + break; + } + return rc; +} + + +int msm_mctl_pp_ioctl(struct msm_cam_media_controller *p_mctl, + unsigned int cmd, unsigned long arg) +{ + int rc = -EINVAL; + struct msm_mctl_post_proc_cmd pp_cmd; + void __user *argp = (void __user *)arg; + + if (copy_from_user(&pp_cmd, argp, sizeof(pp_cmd))) + return -EFAULT; + + switch (pp_cmd.type) { + case MSM_PP_CMD_TYPE_VPE: + rc = msm_mctl_pp_proc_vpe_cmd(p_mctl, &pp_cmd.cmd); + break; + case MSM_PP_CMD_TYPE_MCTL: + rc = msm_mctl_pp_proc_cmd(p_mctl, &pp_cmd.cmd); + break; + default: + rc = -EPERM; + break; + } + if (!rc) { + /* deep copy back the return value */ + if (copy_to_user((void *)arg, + &pp_cmd, + sizeof(struct msm_mctl_post_proc_cmd))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + } + return rc; +} + +int msm_mctl_pp_notify(struct msm_cam_media_controller *p_mctl, + struct msm_mctl_pp_frame_info *pp_frame_info) +{ + struct msm_mctl_pp_frame_cmd *pp_frame_cmd; + pp_frame_cmd = &pp_frame_info->pp_frame_cmd; + + D("%s: msm_cam_evt_divert_frame=%d", + __func__, sizeof(struct msm_mctl_pp_event_info)); + if ((MSM_MCTL_PP_VPE_FRAME_TO_APP & + pp_frame_cmd->vpe_output_action)) { + struct msm_free_buf done_frame; + int img_mode = + msm_mctl_pp_path_to_img_mode( + pp_frame_cmd->path); + if (img_mode < 0) { + pr_err("%s Invalid image mode\n", __func__); + return img_mode; + } + done_frame.ch_paddr[0] = + pp_frame_info->dest_frame.sp.phy_addr; + done_frame.vb = + pp_frame_info->dest_frame.handle; + msm_mctl_buf_done_pp( + p_mctl, img_mode, &done_frame, 0, 0); + D("%s: vpe done to app, vb=0x%x, path=%d, phy=0x%x", + __func__, done_frame.vb, + pp_frame_cmd->path, done_frame.ch_paddr[0]); + } + if ((MSM_MCTL_PP_VPE_FRAME_ACK & + pp_frame_cmd->vpe_output_action)) { + struct v4l2_event v4l2_evt; + struct msm_mctl_pp_event_info *pp_event_info; + struct msm_isp_event_ctrl *isp_event; + isp_event = kzalloc(sizeof(struct msm_isp_event_ctrl), + GFP_ATOMIC); + if (!isp_event) { + pr_err("%s Insufficient memory.", __func__); + return -ENOMEM; + } + memset(&v4l2_evt, 0, sizeof(v4l2_evt)); + *((uint32_t *)v4l2_evt.u.data) = (uint32_t)isp_event; + + /* Get hold of pp event info struct inside event ctrl.*/ + pp_event_info = &(isp_event->isp_data.pp_event_info); + + pp_event_info->event = MCTL_PP_EVENT_CMD_ACK; + pp_event_info->ack.cmd = pp_frame_info->user_cmd; + pp_event_info->ack.status = 0; + pp_event_info->ack.cookie = pp_frame_cmd->cookie; + v4l2_evt.id = 0; + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_MCTL_PP_EVENT; + + v4l2_event_queue( + p_mctl->config_device-> + config_stat_event_queue.pvdev, + &v4l2_evt); + D("%s: ack to daemon, cookie=0x%x, event = 0x%x", + __func__, pp_frame_info->pp_frame_cmd.cookie, + v4l2_evt.type); + } + kfree(pp_frame_info); /* free mem */ + return 0; +} + +int msm_mctl_pp_reserve_free_frame( + struct msm_cam_media_controller *p_mctl, + void __user *arg) +{ + struct msm_cam_evt_divert_frame frame; + int image_mode, rc = 0; + struct msm_free_buf free_buf; + struct msm_cam_v4l2_dev_inst *pcam_inst; + + memset(&free_buf, 0, sizeof(struct msm_free_buf)); + if (copy_from_user(&frame, arg, + sizeof(struct msm_cam_evt_divert_frame))) + return -EFAULT; + + image_mode = frame.image_mode; + if (image_mode <= 0) { + pr_err("%s Invalid image mode %d", __func__, image_mode); + return -EINVAL; + } + /* Always reserve the buffer from user's video node */ + pcam_inst = p_mctl->pcam_ptr->dev_inst[image_mode]; + if (!pcam_inst) { + pr_err("%s Instance already closed ", __func__); + return -EINVAL; + } + rc = msm_mctl_reserve_free_buf(p_mctl, pcam_inst, + image_mode, &free_buf); + if (rc == 0) { + msm_mctl_pp_get_phy_addr(pcam_inst, free_buf.vb, &frame.frame); + if (copy_to_user((void *)arg, &frame, sizeof(frame))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + } + D("%s: reserve free buf got buffer %d from %p rc = %d, phy = 0x%x", + __func__, frame.frame.buf_idx, + pcam_inst, rc, free_buf.ch_paddr[0]); + return rc; +} + +int msm_mctl_pp_release_free_frame( + struct msm_cam_media_controller *p_mctl, + void __user *arg) +{ + struct msm_cam_evt_divert_frame div_frame; + struct msm_cam_v4l2_dev_inst *pcam_inst; + struct msm_pp_frame *frame; + int image_mode, rc = 0; + struct msm_free_buf free_buf; + + if (copy_from_user(&div_frame, arg, + sizeof(struct msm_cam_evt_divert_frame))) + return -EFAULT; + + image_mode = div_frame.image_mode; + if (image_mode < 0) { + pr_err("%s Invalid image mode %d\n", __func__, image_mode); + return -EINVAL; + } + frame = &div_frame.frame; + if (frame->num_planes > 1) + free_buf.ch_paddr[0] = frame->mp[0].phy_addr; + else + free_buf.ch_paddr[0] = frame->sp.phy_addr; + + pcam_inst = msm_mctl_get_pcam_inst(p_mctl, image_mode); + if (!pcam_inst) { + pr_err("%s Invalid instance. Cannot release frame.\n", + __func__); + return -EINVAL; + } + + rc = msm_mctl_release_free_buf(p_mctl, pcam_inst, + image_mode, &free_buf); + D("%s: release free buf, rc = %d, phy = 0x%x", + __func__, rc, free_buf.ch_paddr[0]); + + return rc; +} + +int msm_mctl_set_pp_key(struct msm_cam_media_controller *p_mctl, + void __user *arg) +{ + int rc = 0; + unsigned long flags; + spin_lock_irqsave(&p_mctl->pp_info.lock, flags); + if (copy_from_user(&p_mctl->pp_info.pp_key, + arg, sizeof(p_mctl->pp_info.pp_key))) + rc = -EFAULT; + else + D("%s: mctl=0x%p, pp_key_setting=0x%x", + __func__, p_mctl, p_mctl->pp_info.pp_key); + spin_unlock_irqrestore(&p_mctl->pp_info.lock, flags); + return rc; +} + +int msm_mctl_pp_done( + struct msm_cam_media_controller *p_mctl, + void __user *arg) +{ + struct msm_pp_frame frame; + int image_mode, rc = 0; + int dirty = 0; + struct msm_free_buf buf; + unsigned long flags; + + if (copy_from_user(&frame, arg, sizeof(frame))) + return -EFAULT; + + spin_lock_irqsave(&p_mctl->pp_info.lock, flags); + image_mode = msm_mctl_pp_path_to_img_mode(frame.path); + if (image_mode < 0) { + pr_err("%s Invalid image mode\n", __func__); + return image_mode; + } + D("%s Returning frame %x id %d to kernel ", __func__, + (int)frame.handle, frame.frame_id); + if (p_mctl->pp_info.div_frame[image_mode].ch_paddr[0]) { + memcpy(&buf, + &p_mctl->pp_info.div_frame[image_mode], + sizeof(buf)); + memset(&p_mctl->pp_info.div_frame[image_mode], + 0, sizeof(buf)); + if (p_mctl->pp_info.cur_frame_id[image_mode] != + frame.frame_id) { + /* dirty frame. should not pass to app */ + dirty = 1; + } + } else { + if (frame.num_planes > 1) + buf.ch_paddr[0] = frame.mp[0].phy_addr + + frame.mp[0].data_offset; + else + buf.ch_paddr[0] = frame.sp.phy_addr + frame.sp.y_off; + } + spin_unlock_irqrestore(&p_mctl->pp_info.lock, flags); + /* here buf.addr is phy_addr */ + rc = msm_mctl_buf_done_pp(p_mctl, image_mode, &buf, dirty, 0); + return rc; +} + +int msm_mctl_pp_divert_done( + struct msm_cam_media_controller *p_mctl, + void __user *arg) +{ + struct msm_pp_frame frame; + int msg_type, image_mode, rc = 0; + int dirty = 0; + struct msm_free_buf buf; + unsigned long flags; + D("%s enter\n", __func__); + + if (copy_from_user(&frame, arg, sizeof(frame))) + return -EFAULT; + + spin_lock_irqsave(&p_mctl->pp_info.lock, flags); + D("%s Frame path: %d\n", __func__, frame.path); + switch (frame.path) { + case OUTPUT_TYPE_P: + msg_type = VFE_MSG_OUTPUT_P; + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW; + break; + case OUTPUT_TYPE_S: + msg_type = VFE_MSG_OUTPUT_S; + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_MAIN; + break; + case OUTPUT_TYPE_V: + msg_type = VFE_MSG_OUTPUT_V; + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_VIDEO; + break; + case OUTPUT_TYPE_T: + msg_type = VFE_MSG_OUTPUT_T; + image_mode = MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL; + break; + default: + rc = -EFAULT; + goto err; + } + + if (frame.num_planes > 1) + buf.ch_paddr[0] = frame.mp[0].phy_addr + + frame.mp[0].data_offset; + else + buf.ch_paddr[0] = frame.sp.phy_addr + frame.sp.y_off; + + spin_unlock_irqrestore(&p_mctl->pp_info.lock, flags); + D("%s Frame done id: %d\n", __func__, frame.frame_id); + rc = msm_mctl_buf_done_pp(p_mctl, image_mode, + &buf, dirty, frame.node_type); + return rc; +err: + spin_unlock_irqrestore(&p_mctl->pp_info.lock, flags); + return rc; +} + +int msm_mctl_pp_mctl_divert_done( + struct msm_cam_media_controller *p_mctl, + void __user *arg) +{ + struct msm_cam_evt_divert_frame div_frame; + struct msm_frame_buffer *buf; + int image_mode, rc = 0; + + if (copy_from_user(&div_frame, arg, + sizeof(struct msm_cam_evt_divert_frame))) { + pr_err("%s copy from user failed ", __func__); + return -EFAULT; + } + + if (!div_frame.frame.handle) { + pr_err("%s Invalid buffer handle ", __func__); + return -EINVAL; + } + image_mode = div_frame.image_mode; + buf = (struct msm_frame_buffer *)div_frame.frame.handle; + D("%s Returning buffer %x Image mode %d ", __func__, + (int)buf, image_mode); + rc = msm_mctl_buf_return_buf(p_mctl, image_mode, buf); + if (rc < 0) + pr_err("%s Error returning mctl buffer ", __func__); + + return rc; +} diff --git a/drivers/media/video/msm/msm_mem.c b/drivers/media/video/msm/msm_mem.c new file mode 100644 index 0000000000000000000000000000000000000000..0043f72b2b2817c6bcb0a472369a0ce283d70aaf --- /dev/null +++ b/drivers/media/video/msm/msm_mem.c @@ -0,0 +1,416 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 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->planar0_off < len && + info->planar1_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->planar0_off, + info->planar1_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 ion_client *client) +{ + unsigned long paddr; +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + unsigned long kvstart; + struct file *file; +#endif + int rc = -ENOMEM; + + unsigned long len; + struct msm_pmem_region *region; + + region = kmalloc(sizeof(struct msm_pmem_region), GFP_KERNEL); + if (!region) + goto out; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + region->handle = ion_import_fd(client, info->fd); + if (IS_ERR_OR_NULL(region->handle)) + goto out1; + if (ion_map_iommu(client, region->handle, CAMERA_DOMAIN, GEN_POOL, + SZ_4K, 0, &paddr, &len, UNCACHED, 0) < 0) + goto out2; +#elif 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); + goto out1; + } + region->file = file; +#else + paddr = 0; + file = NULL; + kvstart = 0; +#endif + if (!info->len) + info->len = len; + rc = check_pmem_info(info, len); + if (rc < 0) + goto out3; + paddr += info->offset; + len = info->len; + + if (check_overlap(ptype, paddr, len) < 0) { + rc = -EINVAL; + goto out3; + } + + CDBG("%s: type %d, active flag %d, paddr 0x%lx, vaddr 0x%lx\n", + __func__, info->type, info->active, paddr, + (unsigned long)info->vaddr); + + INIT_HLIST_NODE(®ion->list); + region->paddr = paddr; + region->len = len; + 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; +out3: +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_iommu(client, region->handle, CAMERA_DOMAIN, GEN_POOL); +#endif +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +out2: + ion_free(client, region->handle); +#elif CONFIG_ANDROID_PMEM + put_pmem_file(region->file); +#endif +out1: + kfree(region); +out: + return rc; +} + +static int __msm_register_pmem(struct hlist_head *ptype, + struct msm_pmem_info *pinfo, struct ion_client *client) +{ + int rc = 0; + + switch (pinfo->type) { + 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: + case MSM_PMEM_AEC_AWB: + rc = msm_pmem_table_add(ptype, pinfo, client); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int __msm_pmem_table_del(struct hlist_head *ptype, + struct msm_pmem_info *pinfo, struct ion_client *client) +{ + int rc = 0; + struct msm_pmem_region *region; + struct hlist_node *node, *n; + + switch (pinfo->type) { + 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: + case MSM_PMEM_AEC_AWB: + 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_MSM_MULTIMEDIA_USE_ION + ion_unmap_iommu(client, region->handle, + CAMERA_DOMAIN, GEN_POOL); + ion_free(client, region->handle); +#else + put_pmem_file(region->file); +#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; +} + +int msm_pmem_region_get_phy_addr(struct hlist_head *ptype, + struct msm_mem_map_info *mem_map, int32_t *phyaddr) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + int pmem_type = mem_map->mem_type; + int rc = -EFAULT; + + D("%s\n", __func__); + *phyaddr = 0; + mutex_lock(&hlist_mut); + hlist_for_each_entry_safe(region, node, n, ptype, list) { + if (region->info.type == pmem_type && + (uint32_t)region->info.vaddr == mem_map->cookie) { + *phyaddr = (int32_t)region->paddr; + rc = 0; + break; + } + } + D("%s finished, phy_addr = 0x%x, rc=%d\n", __func__, *phyaddr, 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; +} + +unsigned long msm_pmem_stats_vtop_lookup( + struct msm_cam_media_controller *mctl, + unsigned long buffer, + int fd) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + + hlist_for_each_entry_safe(region, node, n, + &mctl->stats_info.pmem_stats_list, 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_cam_media_controller *mctl, + unsigned long addr, int *fd) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + + hlist_for_each_entry_safe(region, node, n, + &mctl->stats_info.pmem_stats_list, 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 ion_client *client) +{ + 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, client); +} +//EXPORT_SYMBOL(msm_register_pmem); + +int msm_pmem_table_del(struct hlist_head *ptype, void __user *arg, + struct ion_client *client) +{ + 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, client); +} +//EXPORT_SYMBOL(msm_pmem_table_del); diff --git a/drivers/media/video/msm/msm_v4l2_video.c b/drivers/media/video/msm/msm_v4l2_video.c new file mode 100644 index 0000000000000000000000000000000000000000..c4e6c621e7b65afcea93d6d46c668204dbec0bce --- /dev/null +++ b/drivers/media/video/msm/msm_v4l2_video.c @@ -0,0 +1,947 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_v4l2_video.h" + +#define MSM_VIDEO -1 + +static struct msm_v4l2_overlay_device *saved_vout0; + +static struct mutex msmfb_lock; +static char *v4l2_ram_phys; +static unsigned int v4l2_ram_size; + +static int msm_v4l2_overlay_mapformat(uint32_t pixelformat); + +static int msm_v4l2_overlay_startstreaming(struct msm_v4l2_overlay_device *vout) +{ + vout->req.src.width = vout->pix.width; + vout->req.src.height = vout->pix.height; + + vout->req.src_rect.x = vout->crop_rect.left; + vout->req.src_rect.y = vout->crop_rect.top; + vout->req.src_rect.w = vout->crop_rect.width; + vout->req.src_rect.h = vout->crop_rect.height; + + vout->req.src.format = + msm_v4l2_overlay_mapformat(vout->pix.pixelformat); + + vout->req.dst_rect.x = vout->win.w.left; + vout->req.dst_rect.y = vout->win.w.top; + vout->req.dst_rect.w = vout->win.w.width; + vout->req.dst_rect.h = vout->win.w.height; + + vout->req.alpha = MDP_ALPHA_NOP; + vout->req.transp_mask = MDP_TRANSP_NOP; + + pr_debug("msm_v4l2_overlay:startstreaming:enabling fb\n"); + mutex_lock(&msmfb_lock); + msm_fb_v4l2_enable(&vout->req, true, &vout->par); + mutex_unlock(&msmfb_lock); + + vout->streaming = 1; + + return 0; +} + +static int msm_v4l2_overlay_stopstreaming(struct msm_v4l2_overlay_device *vout) +{ + if (!vout->streaming) + return 0; + + pr_debug("msm_v4l2_overlay:startstreaming:disabling fb\n"); + mutex_lock(&msmfb_lock); + msm_fb_v4l2_enable(&vout->req, false, &vout->par); + mutex_unlock(&msmfb_lock); + + vout->streaming = 0; + + return 0; +} + +static int msm_v4l2_overlay_mapformat(uint32_t pixelformat) +{ + int mdp_format; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + mdp_format = MDP_RGB_565; + break; + case V4L2_PIX_FMT_RGB32: + mdp_format = MDP_ARGB_8888; + break; + case V4L2_PIX_FMT_RGB24: + mdp_format = MDP_RGB_888; + break; + case V4L2_PIX_FMT_NV12: + mdp_format = MDP_Y_CRCB_H2V2; + break; + case V4L2_PIX_FMT_NV21: + mdp_format = MDP_Y_CBCR_H2V2; + break; + case V4L2_PIX_FMT_YUV420: + mdp_format = MDP_Y_CR_CB_H2V2; + break; + default: + pr_err("%s:Unrecognized format %u\n", __func__, pixelformat); + mdp_format = MDP_Y_CBCR_H2V2; + break; + } + + return mdp_format; +} + +static int +msm_v4l2_overlay_fb_update(struct msm_v4l2_overlay_device *vout, + struct v4l2_buffer *buffer) +{ + int ret; + unsigned long src_addr, src_size; + struct msm_v4l2_overlay_userptr_buffer up_buffer; + + if (!buffer || + (buffer->memory == V4L2_MEMORY_MMAP && + buffer->index >= vout->numbufs)) + return -EINVAL; + + mutex_lock(&msmfb_lock); + switch (buffer->memory) { + case V4L2_MEMORY_MMAP: + src_addr = (unsigned long)v4l2_ram_phys + + vout->bufs[buffer->index].offset; + src_size = buffer->bytesused; + ret = msm_fb_v4l2_update(vout->par, src_addr, src_size, + 0, 0, 0, 0); + break; + case V4L2_MEMORY_USERPTR: + if (copy_from_user(&up_buffer, + (void __user *)buffer->m.userptr, + sizeof(struct msm_v4l2_overlay_userptr_buffer))) { + mutex_unlock(&msmfb_lock); + return -EINVAL; + } + ret = msm_fb_v4l2_update(vout->par, + (unsigned long)up_buffer.base[0], up_buffer.length[0], + (unsigned long)up_buffer.base[1], up_buffer.length[1], + (unsigned long)up_buffer.base[2], up_buffer.length[2]); + break; + default: + mutex_unlock(&msmfb_lock); + return -EINVAL; + } + mutex_unlock(&msmfb_lock); + + if (buffer->memory == V4L2_MEMORY_MMAP) { + vout->bufs[buffer->index].queued = 1; + buffer->flags |= V4L2_BUF_FLAG_MAPPED; + } + buffer->flags |= V4L2_BUF_FLAG_QUEUED; + + return ret; +} + +static int +msm_v4l2_overlay_vidioc_dqbuf(struct file *file, + struct msm_v4l2_overlay_fh *fh, void *arg) +{ + struct msm_v4l2_overlay_device *vout = fh->vout; + struct v4l2_buffer *buffer = arg; + int i; + + if (!vout->streaming) { + pr_err("%s: Video Stream not enabled\n", __func__); + return -EINVAL; + } + + if (!buffer || buffer->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + if (buffer->memory == V4L2_MEMORY_MMAP) { + for (i = 0; i < vout->numbufs; i++) { + if (vout->bufs[i].queued == 1) { + vout->bufs[i].queued = 0; + /* Call into fb to remove this buffer? */ + break; + } + } + + /* + * This should actually block, unless O_NONBLOCK was + * specified in open, but fine for now, especially + * since this is not a capturing device + */ + if (i == vout->numbufs) + return -EAGAIN; + } + + buffer->flags &= ~V4L2_BUF_FLAG_QUEUED; + + return 0; +} + + +static int +msm_v4l2_overlay_vidioc_qbuf(struct file *file, struct msm_v4l2_overlay_fh* fh, + void *arg, bool bUserPtr) +{ + struct msm_v4l2_overlay_device *vout = fh->vout; + struct v4l2_buffer *buffer = arg; + int ret; + + if (!bUserPtr && buffer->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + if (!vout->streaming) { + pr_err("%s: Video Stream not enabled\n", __func__); + return -EINVAL; + } + + if (!buffer || buffer->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + /* maybe allow only one qbuf at a time? */ + ret = msm_v4l2_overlay_fb_update(vout, buffer); + + return 0; +} + +static int +msm_v4l2_overlay_vidioc_querycap(struct file *file, void *arg) +{ + struct v4l2_capability *buffer = arg; + + memset(buffer, 0, sizeof(struct v4l2_capability)); + strlcpy(buffer->driver, "msm_v4l2_video_overlay", + ARRAY_SIZE(buffer->driver)); + strlcpy(buffer->card, "MSM MDP", + ARRAY_SIZE(buffer->card)); + buffer->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT + | V4L2_CAP_VIDEO_OVERLAY; + return 0; +} + +static int +msm_v4l2_overlay_vidioc_fbuf(struct file *file, + struct msm_v4l2_overlay_device *vout, void *arg, bool get) +{ + struct v4l2_framebuffer *fb = arg; + + if (fb == NULL) + return -EINVAL; + + if (get) { + mutex_lock(&vout->update_lock); + memcpy(&fb->fmt, &vout->pix, sizeof(struct v4l2_pix_format)); + mutex_unlock(&vout->update_lock); + } + /* The S_FBUF request does not store anything right now */ + return 0; +} + +static long msm_v4l2_overlay_calculate_bufsize(struct v4l2_pix_format *pix) +{ + int bpp; + long bufsize; + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + bpp = 12; + break; + + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_YUV444: + bpp = 24; + break; + + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + bpp = 32; + break; + default: + pr_err("%s: Unrecognized format %u\n", __func__, + pix->pixelformat); + bpp = 0; + } + + bufsize = (pix->width * pix->height * bpp)/8; + + return bufsize; +} + +static long +msm_v4l2_overlay_vidioc_reqbufs(struct file *file, + struct msm_v4l2_overlay_device *vout, void *arg) + +{ + struct v4l2_requestbuffers *rqb = arg; + long bufsize; + int i; + + if (rqb == NULL || rqb->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + if (rqb->memory == V4L2_MEMORY_MMAP) { + if (rqb->count == 0) { + /* Deallocate allocated buffers */ + mutex_lock(&vout->update_lock); + vout->numbufs = 0; + kfree(vout->bufs); + /* + * There should be a way to look at bufs[i]->mapped, + * and prevent userspace from mmaping and directly + * calling this ioctl without unmapping. Maybe kernel + * handles for us, but needs to be checked out + */ + mutex_unlock(&vout->update_lock); + } else { + /* + * Keep it simple for now - need to deallocate + * before reallocate + */ + if (vout->bufs) + return -EINVAL; + + mutex_lock(&vout->update_lock); + bufsize = + msm_v4l2_overlay_calculate_bufsize(&vout->pix); + mutex_unlock(&vout->update_lock); + + if (bufsize == 0 + || (bufsize * rqb->count) > v4l2_ram_size) { + pr_err("%s: Unsupported format or buffer size too large\n", + __func__); + pr_err("%s: bufsize %lu ram_size %u count %u\n", + __func__, bufsize, v4l2_ram_size, rqb->count); + return -EINVAL; + } + + /* + * We don't support multiple open of one vout, + * but there are probably still some MT problems here, + * (what if same fh is shared between two userspace + * threads and they both call REQBUFS etc) + */ + + mutex_lock(&vout->update_lock); + vout->numbufs = rqb->count; + vout->bufs = + kmalloc(rqb->count * + sizeof(struct msm_v4l2_overlay_buffer), + GFP_KERNEL); + + for (i = 0; i < rqb->count; i++) { + struct msm_v4l2_overlay_buffer *b = + (struct msm_v4l2_overlay_buffer *)vout->bufs + + i; + b->mapped = 0; + b->queued = 0; + b->offset = PAGE_ALIGN(bufsize*i); + b->bufsize = bufsize; + } + + mutex_unlock(&vout->update_lock); + + } + } + + return 0; +} + +static long +msm_v4l2_overlay_vidioc_querybuf(struct file *file, + struct msm_v4l2_overlay_device *vout, + void *arg) +{ + struct v4l2_buffer *buf = arg; + struct msm_v4l2_overlay_buffer *mbuf; + + if (buf == NULL || buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT + || buf->memory == V4L2_MEMORY_USERPTR + || buf->index >= vout->numbufs) + return -EINVAL; + + mutex_lock(&vout->update_lock); + + mbuf = (struct msm_v4l2_overlay_buffer *)vout->bufs + buf->index; + buf->flags = 0; + if (mbuf->mapped) + buf->flags |= V4L2_BUF_FLAG_MAPPED; + if (mbuf->queued) + buf->flags |= V4L2_BUF_FLAG_QUEUED; + + buf->memory = V4L2_MEMORY_MMAP; + buf->length = mbuf->bufsize; + buf->m.offset = mbuf->offset; + + mutex_unlock(&vout->update_lock); + + return 0; +} + +static long +msm_v4l2_overlay_do_ioctl(struct file *file, + unsigned int cmd, void *arg) +{ + struct msm_v4l2_overlay_fh *fh = file->private_data; + struct msm_v4l2_overlay_device *vout = fh->vout; + int ret; + + switch (cmd) { + case VIDIOC_QUERYCAP: + return msm_v4l2_overlay_vidioc_querycap(file, arg); + + case VIDIOC_G_FBUF: + return msm_v4l2_overlay_vidioc_fbuf(file, vout, arg, true); + + case VIDIOC_S_FBUF: + return msm_v4l2_overlay_vidioc_fbuf(file, vout, arg, false); + + case VIDIOC_REQBUFS: + return msm_v4l2_overlay_vidioc_reqbufs(file, vout, arg); + + case VIDIOC_QUERYBUF: + return msm_v4l2_overlay_vidioc_querybuf(file, vout, arg); + + case VIDIOC_QBUF: + mutex_lock(&vout->update_lock); + ret = msm_v4l2_overlay_vidioc_qbuf(file, fh, arg, false); + mutex_unlock(&vout->update_lock); + + return ret; + + case VIDIOC_MSM_USERPTR_QBUF: + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&vout->update_lock); + ret = msm_v4l2_overlay_vidioc_qbuf(file, fh, arg, true); + mutex_unlock(&vout->update_lock); + + return ret; + + case VIDIOC_DQBUF: + mutex_lock(&vout->update_lock); + ret = msm_v4l2_overlay_vidioc_dqbuf(file, fh, arg); + mutex_unlock(&vout->update_lock); + break; + + case VIDIOC_S_FMT: { + struct v4l2_format *f = arg; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + mutex_lock(&vout->update_lock); + memcpy(&vout->win, &f->fmt.win, + sizeof(struct v4l2_window)); + mutex_unlock(&vout->update_lock); + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + mutex_lock(&vout->update_lock); + memcpy(&vout->pix, &f->fmt.pix, + sizeof(struct v4l2_pix_format)); + mutex_unlock(&vout->update_lock); + break; + + default: + return -EINVAL; + } + break; + } + case VIDIOC_G_FMT: { + struct v4l2_format *f = arg; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: { + struct v4l2_pix_format *pix = &f->fmt.pix; + memset(pix, 0, sizeof(*pix)); + *pix = vout->pix; + break; + } + + case V4L2_BUF_TYPE_VIDEO_OVERLAY: { + struct v4l2_window *win = &f->fmt.win; + memset(win, 0, sizeof(*win)); + win->w = vout->win.w; + break; + } + default: + return -EINVAL; + } + break; + } + + case VIDIOC_CROPCAP: { + struct v4l2_cropcap *cr = arg; + if (cr->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + cr->bounds.left = 0; + cr->bounds.top = 0; + cr->bounds.width = vout->crop_rect.width; + cr->bounds.height = vout->crop_rect.height; + + cr->defrect.left = 0; + cr->defrect.top = 0; + cr->defrect.width = vout->crop_rect.width; + cr->defrect.height = vout->crop_rect.height; + + cr->pixelaspect.numerator = 1; + cr->pixelaspect.denominator = 1; + break; + } + + case VIDIOC_S_CROP: { + struct v4l2_crop *crop = arg; + + switch (crop->type) { + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + + mutex_lock(&vout->update_lock); + memcpy(&vout->crop_rect, &crop->c, + sizeof(struct v4l2_rect)); + mutex_unlock(&vout->update_lock); + + break; + + default: + + return -EINVAL; + } + break; + } + case VIDIOC_G_CROP: { + struct v4l2_crop *crop = arg; + + switch (crop->type) { + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + memcpy(&crop->c, &vout->crop_rect, + sizeof(struct v4l2_rect)); + break; + + default: + return -EINVAL; + } + break; + } + + case VIDIOC_S_CTRL: { + struct v4l2_control *ctrl = arg; + int32_t rotflag; + + switch (ctrl->id) { + + case V4L2_CID_ROTATE: + switch (ctrl->value) { + case 0: + rotflag = MDP_ROT_NOP; + break; + case 90: + rotflag = MDP_ROT_90; + break; + case 180: + rotflag = MDP_ROT_180; + break; + case 270: + rotflag = MDP_ROT_270; + break; + default: + pr_err("%s: V4L2_CID_ROTATE invalid rotation value %d.\n", + __func__, ctrl->value); + return -ERANGE; + } + + mutex_lock(&vout->update_lock); + /* Clear the rotation flags */ + vout->req.flags &= ~MDP_ROT_NOP; + vout->req.flags &= ~MDP_ROT_90; + vout->req.flags &= ~MDP_ROT_180; + vout->req.flags &= ~MDP_ROT_270; + /* Set the new rotation flag */ + vout->req.flags |= rotflag; + mutex_unlock(&vout->update_lock); + + break; + + case V4L2_CID_HFLIP: + mutex_lock(&vout->update_lock); + /* Clear the flip flag */ + vout->req.flags &= ~MDP_FLIP_LR; + if (true == ctrl->value) + vout->req.flags |= MDP_FLIP_LR; + mutex_unlock(&vout->update_lock); + + break; + + case V4L2_CID_VFLIP: + mutex_lock(&vout->update_lock); + /* Clear the flip flag */ + vout->req.flags &= ~MDP_FLIP_UD; + if (true == ctrl->value) + vout->req.flags |= MDP_FLIP_UD; + mutex_unlock(&vout->update_lock); + + break; + + default: + pr_err("%s: VIDIOC_S_CTRL invalid control ID %d.\n", + __func__, ctrl->id); + return -EINVAL; + } + break; + } + case VIDIOC_G_CTRL: { + struct v4l2_control *ctrl = arg; + __s32 rotation; + + switch (ctrl->id) { + + case V4L2_CID_ROTATE: + if (MDP_ROT_NOP == (vout->req.flags & MDP_ROT_NOP)) + rotation = 0; + if (MDP_ROT_90 == (vout->req.flags & MDP_ROT_90)) + rotation = 90; + if (MDP_ROT_180 == (vout->req.flags & MDP_ROT_180)) + rotation = 180; + if (MDP_ROT_270 == (vout->req.flags & MDP_ROT_270)) + rotation = 270; + + ctrl->value = rotation; + break; + + case V4L2_CID_HFLIP: + if (MDP_FLIP_LR == (vout->req.flags & MDP_FLIP_LR)) + ctrl->value = true; + break; + + case V4L2_CID_VFLIP: + if (MDP_FLIP_UD == (vout->req.flags & MDP_FLIP_UD)) + ctrl->value = true; + break; + + default: + pr_err("%s: VIDIOC_G_CTRL invalid control ID %d.\n", + __func__, ctrl->id); + return -EINVAL; + } + break; + } + + case VIDIOC_STREAMON: { + + if (vout->streaming) { + pr_err("%s: VIDIOC_STREAMON: already streaming.\n", + __func__); + return -EBUSY; + } + + mutex_lock(&vout->update_lock); + msm_v4l2_overlay_startstreaming(vout); + mutex_unlock(&vout->update_lock); + + break; + } + + case VIDIOC_STREAMOFF: { + + if (!vout->streaming) { + pr_err("%s: VIDIOC_STREAMOFF: not currently streaming.\n", + __func__); + return -EINVAL; + } + + mutex_lock(&vout->update_lock); + msm_v4l2_overlay_stopstreaming(vout); + mutex_unlock(&vout->update_lock); + + break; + } + + default: + return -ENOIOCTLCMD; + + } /* switch */ + + return 0; +} + +static long +msm_v4l2_overlay_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(file, cmd, arg, msm_v4l2_overlay_do_ioctl); +} + +static int +msm_v4l2_overlay_mmap(struct file *filp, struct vm_area_struct * vma) +{ + unsigned long start = (unsigned long)v4l2_ram_phys; + + /* + * vm_pgoff is the offset (>>PAGE_SHIFT) that we provided + * during REQBUFS. off therefore should equal the offset we + * provided in REQBUFS, since last (PAGE_SHIFT) bits of off + * should be 0 + */ + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + u32 len = PAGE_ALIGN((start & ~PAGE_MASK) + v4l2_ram_size); + + /* + * This is probably unnecessary now - the last PAGE_SHIFT + * bits of start should be 0 now, since we are page aligning + * v4l2_ram_phys + */ + start &= PAGE_MASK; + + pr_debug("v4l2 map req for phys(%p,%p) offset %u to virt (%p,%p)\n", + (void *)(start+off), (void *)(start+off+(vma->vm_end - vma->vm_start)), + (unsigned int)off, (void *)vma->vm_start, (void *)vma->vm_end); + + if ((vma->vm_end - vma->vm_start + off) > len) { + pr_err("v4l2 map request, memory requested too big\n"); + return -EINVAL; + } + + start += off; + vma->vm_pgoff = start >> PAGE_SHIFT; + /* This is an IO map - tell maydump to skip this VMA */ + vma->vm_flags |= VM_IO | VM_RESERVED; + + 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, start >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static int +msm_v4l2_overlay_release(struct file *file) +{ + struct msm_v4l2_overlay_fh *fh = file->private_data; + struct msm_v4l2_overlay_device *vout = fh->vout; + + if (vout->streaming) + msm_v4l2_overlay_stopstreaming(vout); + + vout->ref_count--; + + kfree(vout->bufs); + vout->numbufs = 0; + kfree(fh); + + return 0; +} + +static int +msm_v4l2_overlay_open(struct file *file) +{ + struct msm_v4l2_overlay_device *vout = NULL; + struct v4l2_pix_format *pix = NULL; + struct msm_v4l2_overlay_fh *fh; + + vout = saved_vout0; + vout->id = 0; + + if (vout->ref_count) { + pr_err("%s: multiple open currently is not" + "supported!\n", __func__); + return -EBUSY; + } + + vout->ref_count++; + + /* allocate per-filehandle data */ + fh = kmalloc(sizeof(struct msm_v4l2_overlay_fh), GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + + fh->vout = vout; + fh->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + file->private_data = fh; + + vout->streaming = 0; + vout->crop_rect.left = vout->crop_rect.top = 0; + vout->crop_rect.width = vout->screen_width; + vout->crop_rect.height = vout->screen_height; + + pix = &vout->pix; + pix->width = vout->screen_width; + pix->height = vout->screen_height; + pix->pixelformat = V4L2_PIX_FMT_RGB32; + pix->field = V4L2_FIELD_NONE; + pix->bytesperline = pix->width * 4; + pix->sizeimage = pix->bytesperline * pix->height; + pix->priv = 0; + pix->colorspace = V4L2_COLORSPACE_SRGB; + + vout->win.w.left = 0; + vout->win.w.top = 0; + vout->win.w.width = vout->screen_width; + vout->win.w.height = vout->screen_height; + + vout->fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY + | V4L2_FBUF_CAP_LOCAL_ALPHA; + vout->fb.flags = V4L2_FBUF_FLAG_LOCAL_ALPHA; + vout->fb.base = 0; + memcpy(&vout->fb.fmt, pix, sizeof(struct v4l2_format)); + + vout->bufs = 0; + vout->numbufs = 0; + + mutex_init(&vout->update_lock); + + return 0; +} + + +static int __devinit +msm_v4l2_overlay_probe(struct platform_device *pdev) +{ + char *v4l2_ram_phys_unaligned; + if ((pdev->id == 0) && (pdev->num_resources > 0)) { + v4l2_ram_size = + pdev->resource[0].end - pdev->resource[0].start + 1; + v4l2_ram_phys_unaligned = (char *)pdev->resource[0].start; + v4l2_ram_phys = + (char *)PAGE_ALIGN((unsigned int)v4l2_ram_phys_unaligned); + /* + * We are (fwd) page aligning the start of v4l2 memory. + * Therefore we have that much less physical memory available + */ + v4l2_ram_size -= (unsigned int)v4l2_ram_phys + - (unsigned int)v4l2_ram_phys_unaligned; + + + } + return 0; +} + +static int __devexit +msm_v4l2_overlay_remove(struct platform_device *pdev) +{ + return 0; +} + +static void msm_v4l2_overlay_videodev_release(struct video_device *vfd) +{ + return; +} + +static const struct v4l2_file_operations msm_v4l2_overlay_fops = { + .owner = THIS_MODULE, + .open = msm_v4l2_overlay_open, + .release = msm_v4l2_overlay_release, + .mmap = msm_v4l2_overlay_mmap, + .ioctl = msm_v4l2_overlay_ioctl, +}; + +static struct video_device msm_v4l2_overlay_vid_device0 = { + .name = "msm_v4l2_overlay", + .fops = &msm_v4l2_overlay_fops, + .minor = -1, + .release = msm_v4l2_overlay_videodev_release, +}; + +static struct platform_driver msm_v4l2_overlay_platform_driver = { + .probe = msm_v4l2_overlay_probe, + .remove = msm_v4l2_overlay_remove, + .driver = { + .name = "msm_v4l2_overlay_pd", + }, +}; + +static int __init msm_v4l2_overlay_init(void) +{ + int ret; + + + saved_vout0 = kzalloc(sizeof(struct msm_v4l2_overlay_device), + GFP_KERNEL); + + if (!saved_vout0) + return -ENOMEM; + + ret = platform_driver_register(&msm_v4l2_overlay_platform_driver); + if (ret < 0) + goto end; + + /* + * Register the device with videodev. + * Videodev will make IOCTL calls on application requests + */ + ret = video_register_device(&msm_v4l2_overlay_vid_device0, + VFL_TYPE_GRABBER, MSM_VIDEO); + + if (ret < 0) { + pr_err("%s: V4L2 video overlay device registration failure(%d)\n", + __func__, ret); + goto end_unregister; + } + + mutex_init(&msmfb_lock); + + return 0; + +end_unregister: + platform_driver_unregister(&msm_v4l2_overlay_platform_driver); + +end: + kfree(saved_vout0); + return ret; +} + +static void __exit msm_v4l2_overlay_exit(void) +{ + video_unregister_device(&msm_v4l2_overlay_vid_device0); + platform_driver_unregister(&msm_v4l2_overlay_platform_driver); + mutex_destroy(&msmfb_lock); + kfree(saved_vout0); +} + +module_init(msm_v4l2_overlay_init); +module_exit(msm_v4l2_overlay_exit); + +MODULE_DESCRIPTION("MSM V4L2 Video Overlay Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_v4l2_video.h b/drivers/media/video/msm/msm_v4l2_video.h new file mode 100644 index 0000000000000000000000000000000000000000..a7baa756685112e34573ed31b71ead42427775b6 --- /dev/null +++ b/drivers/media/video/msm/msm_v4l2_video.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_V4L2_VIDEO_H +#define MSM_V4L2_VIDEO_H + +#include +#include +#include + + +struct msm_v4l2_overlay_buffer { + int mapped; + int queued; + int offset; + int bufsize; +}; + +struct msm_v4l2_overlay_device { + struct device dev; + + int ref_count; + int id; + + int screen_width; + int screen_height; + int streaming; + + struct v4l2_pix_format pix; + struct v4l2_window win; + struct v4l2_rect crop_rect; + struct v4l2_framebuffer fb; + struct msm_v4l2_overlay_buffer *bufs; + int numbufs; + struct mdp_overlay req; + void *par; + + struct mutex update_lock; +}; + +struct msm_v4l2_overlay_fh { + struct msm_v4l2_overlay_device *vout; + enum v4l2_buf_type type; +}; + +struct msm_v4l2_overlay_userptr_buffer { + uint base[3]; + size_t length[3]; +}; + +#endif diff --git a/drivers/media/video/msm/msm_vfe31.c b/drivers/media/video/msm/msm_vfe31.c new file mode 100644 index 0000000000000000000000000000000000000000..2929538923e91e9ba0e329669a129c16587b76fb --- /dev/null +++ b/drivers/media/video/msm/msm_vfe31.c @@ -0,0 +1,4012 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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_vfe31.h" +#include "msm_vpe1.h" +atomic_t irq_cnt; + +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->p0_phy = + ((struct vfe_message *)data)->_u.msgOut.p0_addr; + pinfo->p1_phy = + ((struct vfe_message *)data)->_u.msgOut.p1_addr; + pinfo->p2_phy = + ((struct vfe_message *)data)->_u.msgOut.p2_addr; + + 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; + rp->stats_msg.awb_ymin = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.awb_ymin; + 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 p0_addr, + uint32_t p1_addr, uint32_t p2_addr) +{ + 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.p0_addr = p0_addr; + msg._u.msgOut.p1_addr = p1_addr; + msg._u.msgOut.p2_addr = p2_addr; + CDBG("%s p2_addr = 0x%x\n", __func__, p2_addr); + 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_camera_io_w_mb(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + + /* disable all interrupts. */ + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, + vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* now enable only halt_irq & reset_irq */ + msm_camera_io_w(0xf0000000, /* this is for async timer. */ + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_IMASK_AXI_HALT, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* then apply axi halt command. */ + msm_camera_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 p0_off, uint32_t p1_off, uint32_t p2_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->planar0_off = p0_off; + free_buf->planar1_off = p1_off; + free_buf->planar2_off = p2_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->planar0_off, + free_buf->planar1_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.planar0_off, \ + regptr->info.planar1_off, \ + regptr->info.planar2_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.planar0_off); + + p1 = ao + 12 + i; /* wm1 for cbcr */ + *p1 = (regp1->paddr + regp1->info.planar1_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.planar0_off); + + /* this is to duplicate ping address to pong.*/ + *p1 = (regp1->paddr + regp1->info.planar0_off); + + p1 = ao + 30; /* wm4 ping */ + *p1++ = (regp1->paddr + regp1->info.planar1_off); + CDBG("%s: regp1->info.cbcr_off = 0x%x\n", __func__, + regp1->info.planar1_off); + + /* this is to duplicate ping address to pong.*/ + *p1 = (regp1->paddr + regp1->info.planar1_off); + + p1 = ao + 12; /* wm1 ping */ + *p1++ = (regp2->paddr + regp2->info.planar0_off); + + /* pong = ping,*/ + *p1 = (regp2->paddr + regp2->info.planar0_off); + + p1 = ao + 36; /* wm5 */ + *p1++ = (regp2->paddr + regp2->info.planar1_off); + CDBG("%s: regp2->info.cbcr_off = 0x%x\n", __func__, + regp2->info.planar1_off); + + /* pong = ping,*/ + *p1 = (regp2->paddr + regp2->info.planar1_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.planar0_off); + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.planar1_off); + regp1--; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.planar0_off); + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.planar1_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.planar0_off); + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.planar1_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.planar0_off); + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.planar1_off); + regp2++; + } + + for (i = 0; i < 2; i++) { + p3 = ao + 18 + i; /* wm2 for y */ + *p3 = (regp3->paddr + regp3->info.planar0_off); + p3 = ao + 42 + i; /* wm6 for cbcr */ + *p3 = (regp3->paddr + regp3->info.planar1_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_ZSL_ALL_CHNLS: + CDBG("%s: OUTPUT_ZSL_ALL_CHNLS", __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_ALL_CHNLS; /* 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 = (regp2->paddr + regp2->info.planar0_off); + p1 = ao + 12 + i; /* wm1 for cbcr */ + *p1 = (regp2->paddr + regp2->info.planar1_off); + regp2++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 30 + i; /* wm4 for y */ + *p2 = (regp1->paddr + regp1->info.planar0_off); + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp1->paddr + regp1->info.planar1_off); + p2 = ao + 42 + i; /* wm5 for cbcr */ + *p2 = (regp1->paddr + regp1->info.planar2_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p3 = ao + 18 + i; /* wm2 for y */ + *p3 = (regp3->paddr + regp3->info.planar0_off); + p3 = ao + 24 + i; /* wm3 for cbcr */ + *p3 = (regp3->paddr + regp3->info.planar1_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.planar0_off); + + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.planar1_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.planar0_off); + + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.planar1_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_VIDEO_ALL_CHNLS: { + /* use wm0&4 for preview, wm1&5 for video.*/ + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_V; /* video*/ + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_P_ALL_CHNLS; + regp1 = &(ad->region[0]); + regp2 = &(ad->region[ad->bufnum1]); + outp1 = &(vfe31_ctrl->outpath.out0); + outp2 = &(vfe31_ctrl->outpath.out2); + + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.planar0_off); + + p1 = ao + 12 + i; /* wm1 for cbcr */ + *p1 = (regp1->paddr + regp1->info.planar1_off); + + p1 = ao + 18 + i; /* wm2 for cbcr */ + *p1 = (regp1->paddr + regp1->info.planar2_off); + regp1++; + } + for (i = 0; i < 2; i++) { + p2 = ao + 30 + i; /* wm4 for y */ + *p2 = (regp2->paddr + regp2->info.planar0_off); + + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.planar1_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.planar0_off); + if (p_sync->stereocam_enabled) + p_sync->stereo_state = STEREO_RAW_SNAP_IDLE; + } + break; + default: + break; + } + msm_camera_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_camera_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_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* enable reset_ack interrupt. */ + msm_camera_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_camera_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_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_CFG_OFF); + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_REALIGN_BUF); + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_CHROMA_UP); + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(irq_mask, vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_camera_io_w(VFE_IMASK_RESET, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + /* enable out of order option */ + msm_camera_io_w(0x80000000, vfe31_ctrl->vfebase + VFE_AXI_CFG); + /* enable performance monitor */ + msm_camera_io_w(1, vfe31_ctrl->vfebase + VFE_BUS_PM_CFG); + msm_camera_io_w(1, vfe31_ctrl->vfebase + VFE_BUS_PM_CMD); + + + msm_camera_io_dump(vfe31_ctrl->vfebase, 0x600); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_camera_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_camera_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_camera_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_camera_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))); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) { + irq_comp_mask |= (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1 | + 0x1 << vfe31_ctrl->outpath.out0.ch2); + } + 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_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch2]); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_T) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_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_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch1]); + } + } + msm_camera_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_camera_io_w(1, vfe31_ctrl->vfebase + 0x18C); + msm_camera_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_camera_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_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_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_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_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_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + } + } + msm_camera_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_camera_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.out0.ch2 >= 0) + irq_comp_mask |= + (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1 | + 0x1 << vfe31_ctrl->outpath.out0.ch2); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) { + irq_comp_mask |= (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1 | + 0x1 << vfe31_ctrl->outpath.out0.ch2); + } + + 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_camera_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PT) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + if (vfe31_ctrl->outpath.out0.ch2 >= 0) + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch2]); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch2]); + } + + 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_camera_io_r(vfe31_ctrl->vfebase + V31_GAMMA_CFG_OFF)) + msm_camera_io_w(7, + vfe31_ctrl->vfebase+V31_GAMMA_CFG_OFF); + else + msm_camera_io_w(0, + vfe31_ctrl->vfebase+V31_GAMMA_CFG_OFF); + vfe31_ctrl->update_gamma = false; + } + if (vfe31_ctrl->update_luma) { + if (!msm_camera_io_r(vfe31_ctrl->vfebase + V31_LUMA_CFG_OFF)) + msm_camera_io_w(1, + vfe31_ctrl->vfebase + V31_LUMA_CFG_OFF); + else + msm_camera_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_camera_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_camera_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_camera_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF); + /* Sync Timer Line Start */ + value = *tbl++; + msm_camera_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 4 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Start */ + value = *tbl++; + msm_camera_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_camera_io_w(val, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 12 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Timer0 Active High/LOW */ + value = *tbl++; + msm_camera_io_w(value, + vfe31_ctrl->vfebase + V31_SYNC_TIMER_POLARITY_OFF); + /* Selects sync timer 0 output to drive onto timer1 port */ + value = 0; + msm_camera_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_camera_io_w_mb(value, vfe31_ctrl->vfebase + VFE_DMI_CFG); + /* by default, always starts with offset 0.*/ + msm_camera_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_camera_io_w((value1), + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_camera_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_camera_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_camera_io_w((value1), + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_camera_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AE_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AF_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AWB_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= IHIST_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_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; + } + msm_camera_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; + } + msm_camera_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_camera_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_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_camera_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_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 8, + &new_val, 4); + cmdp_local += 1; + msm_camera_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_camera_io_r( + vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF); + old_val &= BPC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_camera_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF, + cmdp_local, 4); + cmdp_local += 1; + msm_camera_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_camera_io_r( + vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF); + old_val &= ABF_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF, + cmdp_local, 4); + + cmdp_local += 1; + msm_camera_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_camera_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_camera_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_camera_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_camera_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_camera_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_camera_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_camera_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_camera_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_camera_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AWB_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V31_STATS_AE_STOP: { + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AE_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V31_STATS_AF_STOP: { + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AF_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V31_STATS_IHIST_STOP: { + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~IHIST_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V31_STATS_RS_STOP: { + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~RS_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V31_STATS_CS_STOP: { + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~CS_ENABLE_MASK; + msm_camera_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= STATS_ENABLE_MASK; + *cmdp |= old_val; + + msm_camera_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; + } + + if (copy_from_user(cmdp, (void __user *)cmd->value, + cmd->length)) { + rc = -EFAULT; + pr_err("%s copy from user failed for cmd %d", + __func__, cmd->id); + goto proc_general_done; + } + msm_camera_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->planar0_off, + b->planar1_off, b->planar2_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->planar0_off, + b->planar1_off, b->planar2_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_ZSL_ALL_CHNLS: { + struct axidata *axid; + uint32_t *axio; + 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_ZSL_ALL_CHNLS, 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; + } + + case CMD_AXI_CFG_VIDEO_ALL_CHNLS: { + 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_VIDEO_ALL_CHNLS, 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_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch0]); + msm_camera_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_camera_io_r( + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= RS_CS_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + msm_camera_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_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch0]); + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch1]); + } + + /*disable rs& cs when stop recording. */ + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= (~RS_CS_ENABLE_MASK); + msm_camera_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_camera_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_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[ + vfe31_ctrl->outpath.out0.ch0]); + msm_camera_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_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out1.ch0]); + msm_camera_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_camera_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_camera_io_r_mb(vfe31_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } + /* then do reg_update. */ + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + + VFE_REG_UPDATE_CMD); + } /* if snapshot mode. */ +} + +static void vfe31_set_default_reg_values(void) +{ + msm_camera_io_w(0x800080, vfe31_ctrl->vfebase + VFE_DEMUX_GAIN_0); + msm_camera_io_w(0x800080, vfe31_ctrl->vfebase + VFE_DEMUX_GAIN_1); + msm_camera_io_w(0xFFFFF, vfe31_ctrl->vfebase + VFE_CGC_OVERRIDE); + + /* default frame drop period and pattern */ + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG); + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_CFG); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_PATTERN); + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y); + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_PATTERN); + msm_camera_io_w(0, vfe31_ctrl->vfebase + VFE_CLAMP_MIN); + msm_camera_io_w(0xFFFFFF, vfe31_ctrl->vfebase + VFE_CLAMP_MAX); + + /* stats UB config */ + msm_camera_io_w(0x3980007, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_UB_CFG); + msm_camera_io_w(0x3A00007, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_UB_CFG); + msm_camera_io_w(0x3A8000F, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_UB_CFG); + msm_camera_io_w(0x3B80007, + vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_UB_CFG); + msm_camera_io_w(0x3C0001F, + vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_UB_CFG); + msm_camera_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_camera_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_camera_io_w_mb(AXI_HALT_CLEAR, + vfe31_ctrl->vfebase + VFE_AXI_CMD); + vfe31_ctrl->while_stopping_mask = VFE_IMASK_RESET; + + /* disable all interrupts. */ + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, + vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* now enable only halt_irq & reset_irq */ + msm_camera_io_w(0xf0000000, /* this is for async timer. */ + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_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_camera_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_camera_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + temp = msm_camera_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_camera_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_camera_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_camera_io_r(vfe31_ctrl->vfebase + + VFE_BUS_OPERATION_STATUS); + pr_debug("VFE_BUS_OPERATION_STATUS = 0x%x\n", read_val); + read_val = msm_camera_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_camera_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_camera_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_camera_io_r(vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe31_get_ch_pong_addr(chn) \ + (msm_camera_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_camera_io_w((addr), vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe31_put_ch_pong_addr(chn, addr) \ + (msm_camera_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 p0_addr, p1_addr, p2_addr; +#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. + */ + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) { + if (!(((ping_pong & PINGPONG_LOWER) == PINGPONG_LOWER) || + ((ping_pong & PINGPONG_LOWER) == 0x0))) { + pr_err(" Irq_2 - skip the frame pp_status is not proper" + "PP_status = 0x%x\n", ping_pong); + return; + } + } + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out0); + + if (free_buf) { + /* Y channel */ + p0_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0); + /* Chroma channel */ + p1_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1); + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) { + p2_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch2); + } else { + p2_addr = p0_addr; + } + CDBG("Output path 0, p0_addr = 0x%x, p1_addr = 0x%x," + "p2_addr = 0x%x\n", p0_addr, p1_addr, p2_addr); + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0, + free_buf->paddr + free_buf->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1, + free_buf->paddr + free_buf->planar1_off); + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_P_ALL_CHNLS) + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch2, + free_buf->paddr + free_buf->planar2_off); + kfree(free_buf); + /* if continuous mode, for display. (preview) */ + vfe_send_outmsg(MSG_ID_OUTPUT_P, p0_addr, p1_addr, + p2_addr); + } 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 p0_addr, p1_addr; + struct vfe31_free_buf *free_buf = NULL; + /* Y channel- Main Image */ + p0_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel - TN Image */ + p1_addr = 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, p0_addr = 0x%x, p1_addr = 0x%x\n", + __func__, p0_addr, p1_addr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0, + free_buf->paddr + free_buf->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->planar1_off); + kfree(free_buf); + } + vfe_send_outmsg(MSG_ID_OUTPUT_S, p0_addr, p1_addr, p0_addr); + + /* Y channel- TN Image */ + p0_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0); + /* Chroma channel - TN Image */ + p1_addr = 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, p0_addr = 0x%x, p1_addr = 0x%x\n", + __func__, p0_addr, p1_addr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0, + free_buf->paddr + free_buf->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1, + free_buf->paddr + free_buf->planar1_off); + kfree(free_buf); + } + + vfe_send_outmsg(MSG_ID_OUTPUT_T, p0_addr, p1_addr, p0_addr); + + /* 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_camera_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->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->planar1_off); + kfree(free_buf); + } + vfe_send_outmsg(MSG_ID_OUTPUT_S, pyaddr, pcbcraddr, 0); + + /* 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_camera_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 p0_addr, p1_addr; + struct vfe31_free_buf *free_buf = NULL; + /* Y channel- Main Image */ + p0_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0); + /* Chroma channel - Main Image */ + p1_addr = 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__, p0_addr, p1_addr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0, + free_buf->paddr + free_buf->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1, + free_buf->paddr + free_buf->planar1_off); + kfree(free_buf); + } + vfe_send_outmsg(MSG_ID_OUTPUT_S, p0_addr, p1_addr, p0_addr); + + /* Y channel- TN Image */ + p0_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel - TN Image */ + p1_addr = 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__, p0_addr, p1_addr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0, + free_buf->paddr + free_buf->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->planar1_off); + kfree(free_buf); + } + + vfe_send_outmsg(MSG_ID_OUTPUT_T, p0_addr, p1_addr, p0_addr); +} + +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 p0_addr, p1_addr, p2_addr; + 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_STOP_REQUESTED) + || (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 */ + p0_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0); + /* Chroma channel */ + p1_addr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1); + p2_addr = p0_addr; + CDBG("video output, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + p0_addr, p1_addr); + + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0, + free_buf->paddr + free_buf->planar0_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1, + free_buf->paddr + free_buf->planar1_off); + kfree(free_buf); + vfe_send_outmsg(MSG_ID_OUTPUT_V, p0_addr, p1_addr, p2_addr); + } 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_camera_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_camera_io_r((uint32_t *)pingpongAddr); + msm_camera_io_w(newAddr, (uint32_t *)pingpongAddr); + return returnAddr; +} + +static void vfe_send_stats_msg(void) +{ + struct vfe_message msg; + uint32_t temp; + + /* 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; + + temp = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_STATS_AWB_SGW_CFG); + msg._u.msgStats.buff.awb_ymin = (0xFF00 & temp) >> 8; + + 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_camera_io_r(val); + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_IRQ_STATUS_1); + irq.vfeIrqStatus1 = msm_camera_io_r(val); + + if (irq.vfeIrqStatus1 & VFE_IMASK_AXI_HALT) { + msm_camera_io_w(VFE_IMASK_RESET, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + msm_camera_io_w_mb(AXI_HALT_CLEAR, + vfe31_ctrl->vfebase + VFE_AXI_CMD); + } + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_CAMIF_STATUS); + irq.camifStatus = msm_camera_io_r(val); + CDBG("camifStatus = 0x%x\n", irq.camifStatus); + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_BUS_PING_PONG_STATUS); + irq.vfePingPongStatus = msm_camera_io_r(val); + + /* clear the pending interrupt of the same kind.*/ + msm_camera_io_w(irq.vfeIrqStatus0, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_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_camera_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); + spin_lock_init(&vfe31_ctrl->xbar_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 0000000000000000000000000000000000000000..1d66621e9a9e12f80ec411e35aaa6bb337ef842f --- /dev/null +++ b/drivers/media/video/msm/msm_vfe31.h @@ -0,0 +1,1119 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 PINGPONG_LOWER 0x7 + +#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 { + uint8_t awb_ymin; + 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 p0_addr; + uint32_t p1_addr; + uint32_t p2_addr; + 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 planar0_off; + uint32_t planar1_off; + uint32_t planar2_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_STATS_AWB_SGW_CFG 0x00000554 +#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_vfe31_v4l2.c b/drivers/media/video/msm/msm_vfe31_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..be8d84bef47e4478f3d930d4a52d5457ef7b38b6 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe31_v4l2.c @@ -0,0 +1,3917 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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.h" +#include "msm_vfe31_v4l2.h" + +atomic_t irq_cnt; + +#define BUFF_SIZE_128 128 + +#define VFE31_AXI_OFFSET 0x0050 +#define vfe31_get_ch_ping_addr(chn) \ + (msm_camera_io_r(vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe31_get_ch_pong_addr(chn) \ + (msm_camera_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_camera_io_w((addr), vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe31_put_ch_pong_addr(chn, addr) \ + (msm_camera_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))) + +#define VFE_CLK_RATE 153600000 +#define CAMIF_CFG_RMSK 0x1fffff + +static struct vfe31_ctrl_type *vfe31_ctrl; +static uint32_t vfe_clk_rate; + +struct vfe31_isr_queue_cmd { + struct list_head list; + uint32_t vfeInterruptStatus0; + uint32_t vfeInterruptStatus1; +}; + +/*TODO: Why is V32 reference in arch/arm/mach-msm/include/mach/camera.h?*/ +#define VFE_MSG_V31_START VFE_MSG_V32_START +#define VFE_MSG_V31_CAPTURE VFE_MSG_V32_CAPTURE +#define VFE_MSG_V31_JPEG_CAPTURE VFE_MSG_V32_JPEG_CAPTURE +#define VFE_MSG_V31_START_RECORDING VFE_MSG_V32_START_RECORDING + +static struct vfe31_cmd_type vfe31_cmd[] = { +/* 0*/ {VFE_CMD_DUMMY_0}, + {VFE_CMD_SET_CLK}, + {VFE_CMD_RESET}, + {VFE_CMD_START}, + {VFE_CMD_TEST_GEN_START}, +/* 5*/ {VFE_CMD_OPERATION_CFG, V31_OPERATION_CFG_LEN}, + {VFE_CMD_AXI_OUT_CFG, V31_AXI_OUT_LEN, V31_AXI_OUT_OFF, 0xFF}, + {VFE_CMD_CAMIF_CFG, V31_CAMIF_LEN, V31_CAMIF_OFF, 0xFF}, + {VFE_CMD_AXI_INPUT_CFG}, + {VFE_CMD_BLACK_LEVEL_CFG, V31_BLACK_LEVEL_LEN, + V31_BLACK_LEVEL_OFF, + 0xFF}, +/*10*/ {VFE_CMD_MESH_ROLL_OFF_CFG, V31_MESH_ROLL_OFF_CFG_LEN, + V31_MESH_ROLL_OFF_CFG_OFF, 0xFF}, + {VFE_CMD_DEMUX_CFG, V31_DEMUX_LEN, V31_DEMUX_OFF, 0xFF}, + {VFE_CMD_FOV_CFG, V31_FOV_LEN, V31_FOV_OFF, 0xFF}, + {VFE_CMD_MAIN_SCALER_CFG, V31_MAIN_SCALER_LEN, + V31_MAIN_SCALER_OFF, 0xFF}, + {VFE_CMD_WB_CFG, V31_WB_LEN, V31_WB_OFF, 0xFF}, +/*15*/ {VFE_CMD_COLOR_COR_CFG, V31_COLOR_COR_LEN, V31_COLOR_COR_OFF, 0xFF}, + {VFE_CMD_RGB_G_CFG, V31_RGB_G_LEN, V31_RGB_G_OFF, 0xFF}, + {VFE_CMD_LA_CFG, V31_LA_LEN, V31_LA_OFF, 0xFF }, + {VFE_CMD_CHROMA_EN_CFG, V31_CHROMA_EN_LEN, V31_CHROMA_EN_OFF, + 0xFF}, + {VFE_CMD_CHROMA_SUP_CFG, V31_CHROMA_SUP_LEN, V31_CHROMA_SUP_OFF, + 0xFF}, +/*20*/ {VFE_CMD_MCE_CFG, V31_MCE_LEN, V31_MCE_OFF, 0xFF}, + {VFE_CMD_SK_ENHAN_CFG, V31_SCE_LEN, V31_SCE_OFF, 0xFF}, + {VFE_CMD_ASF_CFG, V31_ASF_LEN, V31_ASF_OFF, 0xFF}, + {VFE_CMD_S2Y_CFG, V31_S2Y_LEN, V31_S2Y_OFF, 0xFF}, + {VFE_CMD_S2CbCr_CFG, V31_S2CbCr_LEN, V31_S2CbCr_OFF, 0xFF}, +/*25*/ {VFE_CMD_CHROMA_SUBS_CFG, V31_CHROMA_SUBS_LEN, V31_CHROMA_SUBS_OFF, + 0xFF}, + {VFE_CMD_OUT_CLAMP_CFG, V31_OUT_CLAMP_LEN, V31_OUT_CLAMP_OFF, + 0xFF}, + {VFE_CMD_FRAME_SKIP_CFG, V31_FRAME_SKIP_LEN, V31_FRAME_SKIP_OFF, + 0xFF}, + {VFE_CMD_DUMMY_1}, + {VFE_CMD_DUMMY_2}, +/*30*/ {VFE_CMD_DUMMY_3}, + {VFE_CMD_UPDATE}, + {VFE_CMD_BL_LVL_UPDATE, V31_BLACK_LEVEL_LEN, + V31_BLACK_LEVEL_OFF, 0xFF}, + {VFE_CMD_DEMUX_UPDATE, V31_DEMUX_LEN, V31_DEMUX_OFF, 0xFF}, + {VFE_CMD_FOV_UPDATE, V31_FOV_LEN, V31_FOV_OFF, 0xFF}, +/*35*/ {VFE_CMD_MAIN_SCALER_UPDATE, V31_MAIN_SCALER_LEN, V31_MAIN_SCALER_OFF, + 0xFF}, + {VFE_CMD_WB_UPDATE, V31_WB_LEN, V31_WB_OFF, 0xFF}, + {VFE_CMD_COLOR_COR_UPDATE, V31_COLOR_COR_LEN, V31_COLOR_COR_OFF, + 0xFF}, + {VFE_CMD_RGB_G_UPDATE, V31_RGB_G_LEN, V31_CHROMA_EN_OFF, 0xFF}, + {VFE_CMD_LA_UPDATE, V31_LA_LEN, V31_LA_OFF, 0xFF }, +/*40*/ {VFE_CMD_CHROMA_EN_UPDATE, V31_CHROMA_EN_LEN, V31_CHROMA_EN_OFF, + 0xFF}, + {VFE_CMD_CHROMA_SUP_UPDATE, V31_CHROMA_SUP_LEN, + V31_CHROMA_SUP_OFF, 0xFF}, + {VFE_CMD_MCE_UPDATE, V31_MCE_LEN, V31_MCE_OFF, 0xFF}, + {VFE_CMD_SK_ENHAN_UPDATE, V31_SCE_LEN, V31_SCE_OFF, 0xFF}, + {VFE_CMD_S2CbCr_UPDATE, V31_S2CbCr_LEN, V31_S2CbCr_OFF, 0xFF}, +/*45*/ {VFE_CMD_S2Y_UPDATE, V31_S2Y_LEN, V31_S2Y_OFF, 0xFF}, + {VFE_CMD_ASF_UPDATE, V31_ASF_UPDATE_LEN, V31_ASF_OFF, 0xFF}, + {VFE_CMD_FRAME_SKIP_UPDATE}, + {VFE_CMD_CAMIF_FRAME_UPDATE}, + {VFE_CMD_STATS_AF_UPDATE, V31_STATS_AF_LEN, V31_STATS_AF_OFF}, +/*50*/ {VFE_CMD_STATS_AE_UPDATE, V31_STATS_AE_LEN, V31_STATS_AE_OFF}, + {VFE_CMD_STATS_AWB_UPDATE, V31_STATS_AWB_LEN, + V31_STATS_AWB_OFF}, + {VFE_CMD_STATS_RS_UPDATE, V31_STATS_RS_LEN, V31_STATS_RS_OFF}, + {VFE_CMD_STATS_CS_UPDATE, V31_STATS_CS_LEN, V31_STATS_CS_OFF}, + {VFE_CMD_STATS_SKIN_UPDATE}, +/*55*/ {VFE_CMD_STATS_IHIST_UPDATE, V31_STATS_IHIST_LEN, V31_STATS_IHIST_OFF}, + {VFE_CMD_DUMMY_4}, + {VFE_CMD_EPOCH1_ACK}, + {VFE_CMD_EPOCH2_ACK}, + {VFE_CMD_START_RECORDING}, +/*60*/ {VFE_CMD_STOP_RECORDING}, + {VFE_CMD_DUMMY_5}, + {VFE_CMD_DUMMY_6}, + {VFE_CMD_CAPTURE, V31_CAPTURE_LEN, 0xFF}, + {VFE_CMD_DUMMY_7}, +/*65*/ {VFE_CMD_STOP}, + {VFE_CMD_GET_HW_VERSION, V31_GET_HW_VERSION_LEN, + V31_GET_HW_VERSION_OFF}, + {VFE_CMD_GET_FRAME_SKIP_COUNTS}, + {VFE_CMD_OUTPUT1_BUFFER_ENQ}, + {VFE_CMD_OUTPUT2_BUFFER_ENQ}, +/*70*/ {VFE_CMD_OUTPUT3_BUFFER_ENQ}, + {VFE_CMD_JPEG_OUT_BUF_ENQ}, + {VFE_CMD_RAW_OUT_BUF_ENQ}, + {VFE_CMD_RAW_IN_BUF_ENQ}, + {VFE_CMD_STATS_AF_ENQ}, +/*75*/ {VFE_CMD_STATS_AE_ENQ}, + {VFE_CMD_STATS_AWB_ENQ}, + {VFE_CMD_STATS_RS_ENQ}, + {VFE_CMD_STATS_CS_ENQ}, + {VFE_CMD_STATS_SKIN_ENQ}, +/*80*/ {VFE_CMD_STATS_IHIST_ENQ}, + {VFE_CMD_DUMMY_8}, + {VFE_CMD_JPEG_ENC_CFG}, + {VFE_CMD_DUMMY_9}, + {VFE_CMD_STATS_AF_START, V31_STATS_AF_LEN, V31_STATS_AF_OFF}, +/*85*/ {VFE_CMD_STATS_AF_STOP}, + {VFE_CMD_STATS_AE_START, V31_STATS_AE_LEN, V31_STATS_AE_OFF}, + {VFE_CMD_STATS_AE_STOP}, + {VFE_CMD_STATS_AWB_START, V31_STATS_AWB_LEN, V31_STATS_AWB_OFF}, + {VFE_CMD_STATS_AWB_STOP}, +/*90*/ {VFE_CMD_STATS_RS_START, V31_STATS_RS_LEN, V31_STATS_RS_OFF}, + {VFE_CMD_STATS_RS_STOP}, + {VFE_CMD_STATS_CS_START, V31_STATS_CS_LEN, V31_STATS_CS_OFF}, + {VFE_CMD_STATS_CS_STOP}, + {VFE_CMD_STATS_SKIN_START}, +/*95*/ {VFE_CMD_STATS_SKIN_STOP}, + {VFE_CMD_STATS_IHIST_START, + V31_STATS_IHIST_LEN, V31_STATS_IHIST_OFF}, + {VFE_CMD_STATS_IHIST_STOP}, + {VFE_CMD_DUMMY_10}, + {VFE_CMD_SYNC_TIMER_SETTING, V31_SYNC_TIMER_LEN, + V31_SYNC_TIMER_OFF}, +/*100*/ {VFE_CMD_ASYNC_TIMER_SETTING, V31_ASYNC_TIMER_LEN, V31_ASYNC_TIMER_OFF}, + {VFE_CMD_LIVESHOT}, + {VFE_CMD_LA_SETUP}, + {VFE_CMD_LINEARIZATION_CFG}, + {VFE_CMD_DEMOSAICV3}, +/*105*/ {VFE_CMD_DEMOSAICV3_ABCC_CFG}, + {VFE_CMD_DEMOSAICV3_DBCC_CFG}, + {VFE_CMD_DEMOSAICV3_DBPC_CFG, V31_DEMOSAICV3_DBPC_LEN, + V31_DEMOSAICV3_DBPC_CFG_OFF}, + {VFE_CMD_DEMOSAICV3_ABF_CFG, V31_DEMOSAICV3_ABF_LEN, + V31_DEMOSAICV3_ABF_OFF}, + {VFE_CMD_DEMOSAICV3_ABCC_UPDATE}, +/*110*/ {VFE_CMD_DEMOSAICV3_DBCC_UPDATE}, + {VFE_CMD_DEMOSAICV3_DBPC_UPDATE, V31_DEMOSAICV3_DBPC_LEN, + V31_DEMOSAICV3_DBPC_CFG_OFF}, + {VFE_CMD_XBAR_CFG}, + {VFE_CMD_MODULE_CFG, V31_MODULE_CFG_LEN, V31_MODULE_CFG_OFF}, + {VFE_CMD_ZSL}, +/*115*/ {VFE_CMD_LINEARIZATION_UPDATE}, + {VFE_CMD_DEMOSAICV3_ABF_UPDATE, V31_DEMOSAICV3_ABF_LEN, + V31_DEMOSAICV3_ABF_OFF}, + {VFE_CMD_CLF_CFG}, + {VFE_CMD_CLF_LUMA_UPDATE}, + {VFE_CMD_CLF_CHROMA_UPDATE}, +/*120*/ {VFE_CMD_PCA_ROLL_OFF_CFG}, + {VFE_CMD_PCA_ROLL_OFF_UPDATE}, + {VFE_CMD_GET_REG_DUMP}, + {VFE_CMD_GET_LINEARIZATON_TABLE}, + {VFE_CMD_GET_MESH_ROLLOFF_TABLE}, +/*125*/ {VFE_CMD_GET_PCA_ROLLOFF_TABLE}, + {VFE_CMD_GET_RGB_G_TABLE}, + {VFE_CMD_GET_LA_TABLE}, + {VFE_CMD_DEMOSAICV3_UPDATE}, +}; + +uint32_t vfe31_AXI_WM_CFG[] = { + 0x0000004C, + 0x00000064, + 0x0000007C, + 0x00000094, + 0x000000AC, + 0x000000C4, + 0x000000DC, +}; + +static const char * const 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", + "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_CFG", + "DEMOSAICV3", + "DEMOSAICV3_ABCC_CFG", /* 105 */ + "DEMOSAICV3_DBCC_CFG", + "DEMOSAICV3_DBPC_CFG", + "DEMOSAICV3_ABF_CFG", + "DEMOSAICV3_ABCC_UPDATE", + "DEMOSAICV3_DBCC_UPDATE", /* 110 */ + "DEMOSAICV3_DBPC_UPDATE", + "XBAR_CFG", + "EZTUNE_CFG", + "V31_ZSL", + "LINEARIZATION_UPDATE", /*115*/ + "DEMOSAICV3_ABF_UPDATE", + "CLF_CFG", + "CLF_LUMA_UPDATE", + "CLF_CHROMA_UPDATE", + "PCA_ROLL_OFF_CFG", /*120*/ + "PCA_ROLL_OFF_UPDATE", + "GET_REG_DUMP", + "GET_LINEARIZATON_TABLE", + "GET_MESH_ROLLOFF_TABLE", + "GET_PCA_ROLLOFF_TABLE", /*125*/ + "GET_RGB_G_TABLE", + "GET_LA_TABLE", + "DEMOSAICV3_UPDATE", +}; + +static void vfe31_stop(void) +{ + + uint8_t axiBusyFlag = true; + unsigned long flags; + + atomic_set(&vfe31_ctrl->vstate, 0); + + /* for reset hw modules, and send msg when reset_irq comes.*/ + spin_lock_irqsave(&vfe31_ctrl->stop_flag_lock, flags); + vfe31_ctrl->stop_ack_pending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->stop_flag_lock, flags); + + /* disable all interrupts. */ + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, + vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* in either continuous or snapshot mode, stop command can be issued + * at any time. stop camif immediately. */ + msm_camera_io_w_mb(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + + /* axi halt command. */ + msm_camera_io_w(AXI_HALT, + vfe31_ctrl->vfebase + VFE_AXI_CMD); + wmb(); + while (axiBusyFlag) { + if (msm_camera_io_r(vfe31_ctrl->vfebase + VFE_AXI_STATUS) & 0x1) + axiBusyFlag = false; + } + /* Ensure the write order while writing + to the command register using the barrier */ + msm_camera_io_w_mb(AXI_HALT_CLEAR, + vfe31_ctrl->vfebase + VFE_AXI_CMD); + + /* now enable only halt_irq & reset_irq */ + msm_camera_io_w(0xf0000000, /* this is for async timer. */ + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_IMASK_WHILE_STOPPING_1, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + msm_camera_io_w_mb(VFE_RESET_UPON_STOP_CMD, + vfe31_ctrl->vfebase + VFE_GLOBAL_RESET); +} + +static void vfe31_subdev_notify(int id, int path) +{ + struct msm_vfe_resp rp; + unsigned long flags; + spin_lock_irqsave(&vfe31_ctrl->sd_notify_lock, flags); + memset(&rp, 0, sizeof(struct msm_vfe_resp)); + CDBG("vfe31_subdev_notify : msgId = %d\n", id); + rp.evt_msg.type = MSM_CAMERA_MSG; + rp.evt_msg.msg_id = path; + rp.type = id; + v4l2_subdev_notify(&vfe31_ctrl->subdev, NOTIFY_VFE_BUF_EVT, &rp); + spin_unlock_irqrestore(&vfe31_ctrl->sd_notify_lock, flags); +} + +static int vfe31_config_axi(int mode, uint32_t *ao) +{ + uint32_t *ch_info; + uint32_t *axi_cfg = ao+V31_AXI_RESERVED; + /* Update the corresponding write masters for each output*/ + ch_info = axi_cfg + 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++; + + switch (mode) { + case OUTPUT_PRIM: + vfe31_ctrl->outpath.output_mode = + VFE31_OUTPUT_MODE_PRIMARY; + break; + case OUTPUT_PRIM_ALL_CHNLS: + vfe31_ctrl->outpath.output_mode = + VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS; + break; + case OUTPUT_PRIM|OUTPUT_SEC: + vfe31_ctrl->outpath.output_mode = + VFE31_OUTPUT_MODE_PRIMARY; + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_SECONDARY; + break; + case OUTPUT_PRIM|OUTPUT_SEC_ALL_CHNLS: + vfe31_ctrl->outpath.output_mode = + VFE31_OUTPUT_MODE_PRIMARY; + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_SECONDARY_ALL_CHNLS; + break; + case OUTPUT_PRIM_ALL_CHNLS|OUTPUT_SEC: + vfe31_ctrl->outpath.output_mode = + VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS; + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_SECONDARY; + break; + default: + pr_err("%s Invalid AXI mode %d ", __func__, mode); + return -EINVAL; + } + + msm_camera_io_memcpy(vfe31_ctrl->vfebase + + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].offset, axi_cfg, + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length - V31_AXI_CH_INF_LEN - + V31_AXI_RESERVED); + 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->stop_flag_lock, flags); + vfe31_ctrl->stop_ack_pending = FALSE; + spin_unlock_irqrestore(&vfe31_ctrl->stop_flag_lock, flags); + + 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_STATE_IDLE; + vfe31_ctrl->liveshot_state = VFE_STATE_IDLE; + + atomic_set(&vfe31_ctrl->vstate, 0); + + /* 0 for continuous mode, 1 for snapshot mode */ + vfe31_ctrl->operation_mode = 0; + vfe31_ctrl->outpath.output_mode = 0; + vfe31_ctrl->vfe_capture_count = 0; + + /* this is unsigned 32 bit integer. */ + vfe31_ctrl->vfeFrameId = 0; + /* 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)); + + vfe31_ctrl->frame_skip_cnt = 31; + vfe31_ctrl->frame_skip_pattern = 0xffffffff; + vfe31_ctrl->snapshot_frame_cnt = 0; +} + +static void vfe31_reset(void) +{ + vfe31_reset_internal_variables(); + /* disable all interrupts. vfeImaskLocal is also reset to 0 + * to begin with. */ + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* enable reset_ack interrupt. */ + msm_camera_io_w(VFE_IMASK_WHILE_STOPPING_1, + 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_camera_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; + vfe31_ctrl->stats_comp = *(++p); + vfe31_ctrl->hfr_mode = *(++p); + + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_CFG); + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_REALIGN_BUF); + msm_camera_io_w(*(++p), vfe31_ctrl->vfebase + VFE_CHROMA_UP); + msm_camera_io_w(*(++p), vfe31_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_io_w(addr, + vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PONG_ADDR); + + vfe31_ctrl->csStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static void msm_camera_io_dump2(void __iomem *addr, int size) +{ + char line_str[BUFF_SIZE_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) { + snprintf(p_str, 12, "%08x: ", (u32) p); + p_str += 10; + } + data = readl_relaxed(p++); + snprintf(p_str, 12, "%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); +} + +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); + if (vfe31_ctrl->stats_comp) + irq_mask |= VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK; + else + irq_mask |= 0x000FE000; + + msm_camera_io_w(irq_mask, vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_IMASK_WHILE_STOPPING_1, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + + msm_camera_io_dump2(vfe31_ctrl->vfebase, vfe31_ctrl->register_total*4); + atomic_set(&vfe31_ctrl->vstate, 1); +} + +static int vfe31_start_recording(struct msm_cam_media_controller *pmctl) +{ + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_VIDEO); + vfe31_ctrl->recording_state = VFE_STATE_START_REQUESTED; + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + return 0; +} + +static int vfe31_stop_recording(struct msm_cam_media_controller *pmctl) +{ + vfe31_ctrl->recording_state = VFE_STATE_STOP_REQUESTED; + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_PREVIEW); + return 0; +} + +static void vfe31_start_liveshot(struct msm_cam_media_controller *pmctl) +{ + /* Hardcode 1 live snapshot for now. */ + vfe31_ctrl->outpath.out0.capture_cnt = 1; + vfe31_ctrl->vfe_capture_count = vfe31_ctrl->outpath.out0.capture_cnt; + + vfe31_ctrl->liveshot_state = VFE_STATE_START_REQUESTED; + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); +} + +static int vfe31_zsl(struct msm_cam_media_controller *pmctl) +{ + uint32_t irq_comp_mask = 0; + /* capture command is valid for both idle and active state. */ + irq_comp_mask = + msm_camera_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->outpath.output_mode & VFE31_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= ((0x1 << (vfe31_ctrl->outpath.out0.ch0)) | + (0x1 << (vfe31_ctrl->outpath.out0.ch1))); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + irq_comp_mask |= ((0x1 << (vfe31_ctrl->outpath.out0.ch0)) | + (0x1 << (vfe31_ctrl->outpath.out0.ch1)) | + (0x1 << (vfe31_ctrl->outpath.out0.ch2))); + } + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_SECONDARY) { + irq_comp_mask |= ((0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out1.ch1 + 8))); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + irq_comp_mask |= ((0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out1.ch1 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out1.ch2 + 8))); + } + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch2]); + } + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch2]); + } + + msm_camera_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + vfe31_start_common(); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_ZSL); + + msm_camera_io_w(1, vfe31_ctrl->vfebase + 0x18C); + msm_camera_io_w(1, vfe31_ctrl->vfebase + 0x188); + return 0; +} +static int vfe31_capture_raw( + struct msm_cam_media_controller *pmctl, + uint32_t num_frames_capture) +{ + uint32_t irq_comp_mask = 0; + + vfe31_ctrl->outpath.out0.capture_cnt = num_frames_capture; + vfe31_ctrl->vfe_capture_count = num_frames_capture; + + irq_comp_mask = + msm_camera_io_r(vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= (0x1 << (vfe31_ctrl->outpath.out0.ch0)); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + } + + msm_camera_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_CAPTURE); + vfe31_start_common(); + return 0; +} + +static int vfe31_capture( + struct msm_cam_media_controller *pmctl, + uint32_t num_frames_capture) +{ + uint32_t irq_comp_mask = 0; + /* capture command is valid for both idle and active state. */ + vfe31_ctrl->outpath.out1.capture_cnt = num_frames_capture; + if (vfe31_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) { + vfe31_ctrl->outpath.out0.capture_cnt = + num_frames_capture; + } + + vfe31_ctrl->vfe_capture_count = num_frames_capture; + irq_comp_mask = msm_camera_io_r( + vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe31_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN) { + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1); + } + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY) { + 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_PRIMARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } + } + + vfe31_ctrl->vfe_capture_count = num_frames_capture; + + msm_camera_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_camera_io_r(vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_CAPTURE); + + vfe31_start_common(); + /* for debug */ + msm_camera_io_w(1, vfe31_ctrl->vfebase + 0x18C); + msm_camera_io_w(1, vfe31_ctrl->vfebase + 0x188); + return 0; +} + +static int vfe31_start(struct msm_cam_media_controller *pmctl) +{ + uint32_t irq_comp_mask = 0; + + irq_comp_mask = + msm_camera_io_r(vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + irq_comp_mask |= (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1 | + 0x1 << vfe31_ctrl->outpath.out0.ch2); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_SECONDARY) { + irq_comp_mask |= (0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8) | + 0x1 << (vfe31_ctrl->outpath.out1.ch1 + 8)); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + irq_comp_mask |= (0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8) | + 0x1 << (vfe31_ctrl->outpath.out1.ch1 + 8) | + 0x1 << (vfe31_ctrl->outpath.out1.ch2 + 8)); + } + msm_camera_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + switch (vfe31_ctrl->operation_mode) { + case VFE_OUTPUTS_PREVIEW: + case VFE_OUTPUTS_PREVIEW_AND_VIDEO: + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch2]); + } + break; + default: + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } else if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch2]); + } + break; + } + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_PREVIEW); + vfe31_start_common(); + return 0; +} + +static void vfe31_update(void) +{ + unsigned long flags; + + if (vfe31_ctrl->update_la) { + if (!msm_camera_io_r(vfe31_ctrl->vfebase + V31_LA_OFF)) + msm_camera_io_w(1, vfe31_ctrl->vfebase + V31_LA_OFF); + else + msm_camera_io_w(0, vfe31_ctrl->vfebase + V31_LA_OFF); + vfe31_ctrl->update_la = false; + } + + if (vfe31_ctrl->update_gamma) { + if (!msm_camera_io_r(vfe31_ctrl->vfebase + V31_RGB_G_OFF)) + msm_camera_io_w(7, vfe31_ctrl->vfebase+V31_RGB_G_OFF); + else + msm_camera_io_w(0, vfe31_ctrl->vfebase+V31_RGB_G_OFF); + vfe31_ctrl->update_gamma = 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_camera_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_camera_io_w(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_camera_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF); + /* Sync Timer Line Start */ + value = *tbl++; + msm_camera_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 4 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Start */ + value = *tbl++; + msm_camera_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 8 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Duration */ + value = *tbl++; + val = vfe_clk_rate / 10000; + val = 10000000 / val; + val = value * 10000 / val; + CDBG("%s: Pixel Clk Cycles!!! %d\n", __func__, val); + msm_camera_io_w(val, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 12 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Timer0 Active High/LOW */ + value = *tbl++; + msm_camera_io_w(value, + vfe31_ctrl->vfebase + V31_SYNC_TIMER_POLARITY_OFF); + /* Selects sync timer 0 output to drive onto timer1 port */ + value = 0; + msm_camera_io_w(value, vfe31_ctrl->vfebase + V31_TIMER_SELECT_OFF); +} + +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; + CDBG("%s: banksel = %d\n", __func__, bankSel); + + msm_camera_io_w(value, vfe31_ctrl->vfebase + VFE_DMI_CFG); + /* by default, always starts with offset 0.*/ + msm_camera_io_w(0, vfe31_ctrl->vfebase + VFE_DMI_ADDR); +} +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 (i = 0 ; i < (VFE31_GAMMA_NUM_ENTRIES/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_camera_io_w((value1), + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_camera_io_w((value2), + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); +} + +static void vfe31_read_gamma_cfg(enum VFE31_DMI_RAM_SEL channel_sel, + uint32_t *tbl) +{ + int i; + vfe31_program_dmi_cfg(channel_sel); + CDBG("%s: Gamma table channel: %d\n", __func__, channel_sel); + for (i = 0 ; i < VFE31_GAMMA_NUM_ENTRIES ; i++) { + *tbl = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *tbl); + tbl++; + } + 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 (i = 0 ; i < (VFE31_LA_TABLE_LENGTH/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_camera_io_w((value1), + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_camera_io_w((value2), + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); +} + +static struct vfe31_output_ch *vfe31_get_ch(int path) +{ + struct vfe31_output_ch *ch = NULL; + + if (path == VFE_MSG_OUTPUT_PRIMARY) + ch = &vfe31_ctrl->outpath.out0; + else if (path == VFE_MSG_OUTPUT_SECONDARY) + ch = &vfe31_ctrl->outpath.out1; + else + pr_err("%s: Invalid path %d\n", __func__, path); + + BUG_ON(ch == NULL); + return ch; +} +static struct msm_free_buf *vfe31_check_free_buffer(int id, int path) +{ + struct vfe31_output_ch *outch = NULL; + struct msm_free_buf *b = NULL; + vfe31_subdev_notify(id, path); + outch = vfe31_get_ch(path); + if (outch->free_buf.ch_paddr[0]) + b = &outch->free_buf; + return b; +} +static int vfe31_configure_pingpong_buffers(int id, int path) +{ + struct vfe31_output_ch *outch = NULL; + int rc = 0; + vfe31_subdev_notify(id, path); + outch = vfe31_get_ch(path); + if (outch->ping.ch_paddr[0] && outch->pong.ch_paddr[0]) { + /* Configure Preview Ping Pong */ + CDBG("%s Configure ping/pong address for %d", + __func__, path); + vfe31_put_ch_ping_addr(outch->ch0, + outch->ping.ch_paddr[0]); + vfe31_put_ch_pong_addr(outch->ch0, + outch->pong.ch_paddr[0]); + + if (vfe31_ctrl->operation_mode != + VFE_OUTPUTS_RAW) { + vfe31_put_ch_ping_addr(outch->ch1, + outch->ping.ch_paddr[1]); + vfe31_put_ch_pong_addr(outch->ch1, + outch->pong.ch_paddr[1]); + } + + if (outch->ping.num_planes > 2) + vfe31_put_ch_ping_addr(outch->ch2, + outch->ping.ch_paddr[2]); + if (outch->pong.num_planes > 2) + vfe31_put_ch_pong_addr(outch->ch2, + outch->pong.ch_paddr[2]); + + /* avoid stale info */ + memset(&outch->ping, 0, sizeof(struct msm_free_buf)); + memset(&outch->pong, 0, sizeof(struct msm_free_buf)); + } else { + pr_err("%s ping/pong addr is null!!", __func__); + rc = -EINVAL; + } + return rc; +} + +static void vfe31_send_isp_msg(struct vfe31_ctrl_type *vctrl, + uint32_t isp_msg_id) +{ + struct isp_msg_event isp_msg_evt; + + isp_msg_evt.msg_id = isp_msg_id; + isp_msg_evt.sof_count = vfe31_ctrl->vfeFrameId; + v4l2_subdev_notify(&vctrl->subdev, + NOTIFY_ISP_MSG_EVT, (void *)&isp_msg_evt); +} + +static int vfe31_proc_general( + struct msm_cam_media_controller *pmctl, + struct msm_isp_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 temp1 = 0, temp2 = 0; + + CDBG("vfe31_proc_general: cmdID = %s, length = %d\n", + vfe31_general_cmd[cmd->id], cmd->length); + switch (cmd->id) { + case VFE_CMD_RESET: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + vfe31_reset(); + break; + case VFE_CMD_START: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + if ((vfe31_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) || + (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW)) + /* Configure primary channel */ + rc = vfe31_configure_pingpong_buffers( + VFE_MSG_V31_START, VFE_MSG_OUTPUT_PRIMARY); + else + /* Configure secondary channel */ + rc = vfe31_configure_pingpong_buffers( + VFE_MSG_V31_START, VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe31_start(pmctl); + break; + case VFE_CMD_UPDATE: + vfe31_update(); + break; + case VFE_CMD_CAPTURE_RAW: + pr_info("%s: cmdID = VFE_CMD_CAPTURE_RAW\n", __func__); + if (copy_from_user(&snapshot_cnt, (void __user *)(cmd->value), + sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + rc = vfe31_configure_pingpong_buffers(VFE_MSG_V31_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for snapshot", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe31_capture_raw(pmctl, snapshot_cnt); + break; + case VFE_CMD_CAPTURE: + if (copy_from_user(&snapshot_cnt, (void __user *)(cmd->value), + sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + + if (vfe31_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) { + if (snapshot_cnt != 1) { + pr_err("only support 1 inline snapshot\n"); + rc = -EINVAL; + goto proc_general_done; + } + /* Configure primary channel for JPEG */ + rc = vfe31_configure_pingpong_buffers( + VFE_MSG_V31_JPEG_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + } else { + /* Configure primary channel */ + rc = vfe31_configure_pingpong_buffers( + VFE_MSG_V31_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + } + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for primary output", __func__); + rc = -EINVAL; + goto proc_general_done; + } + /* Configure secondary channel */ + rc = vfe31_configure_pingpong_buffers(VFE_MSG_V31_CAPTURE, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for secondary output", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe31_capture(pmctl, snapshot_cnt); + break; + case VFE_CMD_START_RECORDING: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) + rc = vfe31_configure_pingpong_buffers( + VFE_MSG_V31_START_RECORDING, + VFE_MSG_OUTPUT_SECONDARY); + else if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_VIDEO_AND_PREVIEW) + rc = vfe31_configure_pingpong_buffers( + VFE_MSG_V31_START_RECORDING, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for video", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe31_start_recording(pmctl); + break; + case VFE_CMD_STOP_RECORDING: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + rc = vfe31_stop_recording(pmctl); + break; + case VFE_CMD_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 VFE_CMD_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AE_BG_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + case VFE_CMD_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AF_BF_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + case VFE_CMD_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AWB_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + + case VFE_CMD_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= IHIST_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + + case VFE_CMD_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; + } + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + + case VFE_CMD_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; + } + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + + case VFE_CMD_MCE_UPDATE: + case VFE_CMD_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_camera_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_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_camera_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_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 8, + &new_val, 4); + cmdp_local += 1; + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, (vfe31_cmd[cmd->id].length)); + break; + case VFE_CMD_CHROMA_SUP_UPDATE: + case VFE_CMD_CHROMA_SUP_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_camera_io_memcpy(vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF, + cmdp_local, 4); + + cmdp_local += 1; + new_val = *cmdp_local; + /* Incrementing with 4 so as to point to the 2nd Register as + * the 2nd register has the mce_enable bit + */ + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + + V31_CHROMA_SUP_OFF + 4); + old_val &= ~MCE_EN_MASK; + new_val = new_val | old_val; + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_camera_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_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 8, + &new_val, 4); + break; + + case VFE_CMD_MESH_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_camera_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 < (V31_MESH_ROLL_OFF_INIT_TABLE_SIZE * 2); i++) { + msm_camera_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_camera_io_w(V31_MESH_ROLL_OFF_DELTA_TABLE_OFFSET, + vfe31_ctrl->vfebase + VFE_DMI_ADDR); + /* for loop for extracting delta table. */ + for (i = 0; i < (V31_MESH_ROLL_OFF_DELTA_TABLE_SIZE * 2); i++) { + msm_camera_io_w(*cmdp_local, + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + cmdp_local++; + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); + break; + + case VFE_CMD_GET_MESH_ROLLOFF_TABLE: + temp1 = sizeof(uint32_t) * ((V31_MESH_ROLL_OFF_INIT_TABLE_SIZE * + 2) + (V31_MESH_ROLL_OFF_DELTA_TABLE_SIZE * 2)); + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + vfe31_program_dmi_cfg(ROLLOFF_RAM); + CDBG("%s: Mesh Rolloff init Table\n", __func__); + for (i = 0; i < (V31_MESH_ROLL_OFF_INIT_TABLE_SIZE * 2); i++) { + *cmdp_local = msm_camera_io_r( + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *cmdp_local); + cmdp_local++; + } + msm_camera_io_w(V31_MESH_ROLL_OFF_DELTA_TABLE_OFFSET, + vfe31_ctrl->vfebase + VFE_DMI_ADDR); + CDBG("%s: Mesh Rolloff Delta Table\n", __func__); + for (i = 0; i < (V31_MESH_ROLL_OFF_DELTA_TABLE_SIZE * 2); i++) { + *cmdp_local = msm_camera_io_r( + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *cmdp_local); + cmdp_local++; + } + CDBG("done reading delta table\n"); + vfe31_program_dmi_cfg(NO_MEM_SELECTED); + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_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; + } + cmdp_local = cmdp; + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, (vfe31_cmd[cmd->id].length)); + + cmdp_local += 1; + vfe31_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0, cmdp_local); + break; + + case VFE_CMD_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; + } + + cmdp_local = cmdp + 1; + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + V31_LA_OFF); + if (old_val != 0x0) + vfe31_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0, + cmdp_local); + else + vfe31_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK1, + cmdp_local); + vfe31_ctrl->update_la = true; + break; + + case VFE_CMD_GET_LA_TABLE: + temp1 = sizeof(uint32_t) * VFE31_LA_TABLE_LENGTH / 2; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + if (msm_camera_io_r(vfe31_ctrl->vfebase + V31_LA_OFF)) + vfe31_program_dmi_cfg(LUMA_ADAPT_LUT_RAM_BANK1); + else + vfe31_program_dmi_cfg(LUMA_ADAPT_LUT_RAM_BANK0); + for (i = 0 ; i < (VFE31_LA_TABLE_LENGTH / 2) ; i++) { + *cmdp_local = msm_camera_io_r( + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + *cmdp_local |= (msm_camera_io_r(vfe31_ctrl->vfebase + + VFE_DMI_DATA_LO)) << 16; + cmdp_local++; + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_SK_ENHAN_CFG: + case VFE_CMD_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_camera_io_memcpy(vfe31_ctrl->vfebase + V31_SCE_OFF, + cmdp, V31_SCE_LEN); + break; + + case VFE_CMD_LIVESHOT: + /* Configure primary channel */ + rc = vfe31_configure_pingpong_buffers(VFE_MSG_V31_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for primary output", __func__); + rc = -EINVAL; + goto proc_general_done; + } + vfe31_start_liveshot(pmctl); + break; + + case VFE_CMD_DEMOSAICV3: + if (cmd->length != V31_DEMOSAICV3_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; + new_val = *cmdp_local; + + old_val = msm_camera_io_r( + vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF); + old_val &= DEMOSAIC_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF, + cmdp_local, V31_DEMOSAICV3_LEN); + break; + + case VFE_CMD_DEMOSAICV3_UPDATE: + if (cmd->length != + V31_DEMOSAICV3_LEN * V31_DEMOSAICV3_UP_REG_CNT) { + 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; + new_val = *cmdp_local; + + old_val = msm_camera_io_r( + vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF); + old_val &= DEMOSAIC_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF, + cmdp_local, V31_DEMOSAICV3_LEN); + + break; + + case VFE_CMD_DEMOSAICV3_ABCC_CFG: + rc = -EFAULT; + break; + + case VFE_CMD_DEMOSAICV3_ABF_UPDATE:/* 116 ABF update */ + case VFE_CMD_DEMOSAICV3_ABF_CFG: /* 108 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_camera_io_r( + vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF); + old_val &= ABF_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF, + cmdp_local, 4); + + cmdp_local += 1; + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, (vfe31_cmd[cmd->id].length)); + break; + + case VFE_CMD_DEMOSAICV3_DBCC_CFG: + case VFE_CMD_DEMOSAICV3_DBCC_UPDATE: + return -EINVAL; + + case VFE_CMD_DEMOSAICV3_DBPC_CFG: + case VFE_CMD_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_camera_io_r( + vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF); + old_val &= BPC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_camera_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAICV3_OFF, + cmdp_local, V31_DEMOSAICV3_LEN); + cmdp_local += 1; + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + V31_DEMOSAICV3_DBPC_CFG_OFF, + cmdp_local, V31_DEMOSAICV3_DBPC_LEN); + break; + + case VFE_CMD_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_camera_io_memcpy(vfe31_ctrl->vfebase + V31_RGB_G_OFF, + cmdp, 4); + cmdp += 1; + + vfe31_write_gamma_cfg(RGBLUT_RAM_CH0_BANK0, cmdp); + vfe31_write_gamma_cfg(RGBLUT_RAM_CH1_BANK0, cmdp); + vfe31_write_gamma_cfg(RGBLUT_RAM_CH2_BANK0, cmdp); + cmdp -= 1; + break; + + case VFE_CMD_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_camera_io_r(vfe31_ctrl->vfebase + V31_RGB_G_OFF); + cmdp += 1; + if (old_val != 0x0) { + vfe31_write_gamma_cfg(RGBLUT_RAM_CH0_BANK0, cmdp); + vfe31_write_gamma_cfg(RGBLUT_RAM_CH1_BANK0, cmdp); + vfe31_write_gamma_cfg(RGBLUT_RAM_CH2_BANK0, cmdp); + } else { + vfe31_write_gamma_cfg(RGBLUT_RAM_CH0_BANK1, cmdp); + vfe31_write_gamma_cfg(RGBLUT_RAM_CH1_BANK1, cmdp); + vfe31_write_gamma_cfg(RGBLUT_RAM_CH2_BANK1, cmdp); + } + vfe31_ctrl->update_gamma = TRUE; + cmdp -= 1; + break; + + case VFE_CMD_GET_RGB_G_TABLE: + temp1 = sizeof(uint32_t) * VFE31_GAMMA_NUM_ENTRIES * 3; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + V31_RGB_G_OFF); + temp2 = old_val ? RGBLUT_RAM_CH0_BANK1 : + RGBLUT_RAM_CH0_BANK0; + for (i = 0; i < 3; i++) { + vfe31_read_gamma_cfg(temp2, + cmdp_local + (VFE31_GAMMA_NUM_ENTRIES * i)); + temp2 += 2; + } + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + + case VFE_CMD_STATS_AWB_STOP: + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AWB_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + break; + case VFE_CMD_STATS_AE_STOP: + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AE_BG_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + break; + case VFE_CMD_STATS_AF_STOP: + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AF_BF_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + break; + + case VFE_CMD_STATS_IHIST_STOP: + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~IHIST_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + break; + + case VFE_CMD_STATS_RS_STOP: + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~RS_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + break; + + case VFE_CMD_STATS_CS_STOP: + old_val = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~CS_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + break; + + case VFE_CMD_STOP: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + vfe31_stop(); + break; + + case VFE_CMD_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 VFE_CMD_MODULE_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_camera_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= STATS_ENABLE_MASK; + *cmdp |= old_val; + + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + break; + + case VFE_CMD_ZSL: + rc = vfe31_configure_pingpong_buffers(VFE_MSG_V31_START, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) + goto proc_general_done; + rc = vfe31_configure_pingpong_buffers(VFE_MSG_V31_START, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) + goto proc_general_done; + + rc = vfe31_zsl(pmctl); + break; + + case VFE_CMD_ASF_CFG: + case VFE_CMD_ASF_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_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + cmdp_local = cmdp + V31_ASF_LEN/4; + break; + + case VFE_CMD_GET_HW_VERSION: + if (cmd->length != V31_GET_HW_VERSION_LEN) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kmalloc(V31_GET_HW_VERSION_LEN, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + *cmdp = msm_camera_io_r( + vfe31_ctrl->vfebase+V31_GET_HW_VERSION_OFF); + if (copy_to_user((void __user *)(cmd->value), cmdp, + V31_GET_HW_VERSION_LEN)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_GET_REG_DUMP: + temp1 = sizeof(uint32_t) * vfe31_ctrl->register_total; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kmalloc(temp1, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + msm_camera_io_dump( + vfe31_ctrl->vfebase, vfe31_ctrl->register_total*4); + CDBG("%s: %p %p %d\n", __func__, (void *)cmdp, + vfe31_ctrl->vfebase, temp1); + memcpy_fromio((void *)cmdp, vfe31_ctrl->vfebase, temp1); + if (copy_to_user((void __user *)(cmd->value), cmdp, temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_FRAME_SKIP_CFG: + 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; + } + + if (copy_from_user(cmdp, (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + msm_camera_io_memcpy( + vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + vfe31_ctrl->frame_skip_cnt = ((uint32_t) + *cmdp & VFE_FRAME_SKIP_PERIOD_MASK) + 1; + vfe31_ctrl->frame_skip_pattern = (uint32_t)(*(cmdp + 2)); + 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; + } + + if (copy_from_user(cmdp, (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + msm_camera_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) +{ + unsigned long flags; + spinlock_t *lock = (vfe31_ctrl->stats_comp ? + &vfe31_ctrl->comp_stats_ack_lock : + &vfe31_ctrl->af_ack_lock); + spin_lock_irqsave(lock, flags); + vfe31_ctrl->afStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->afStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static void vfe31_stats_awb_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe31_ctrl->stats_comp ? + &vfe31_ctrl->comp_stats_ack_lock : + &vfe31_ctrl->awb_ack_lock); + spin_lock_irqsave(lock, flags); + vfe31_ctrl->awbStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->awbStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static void vfe31_stats_aec_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe31_ctrl->stats_comp ? + &vfe31_ctrl->comp_stats_ack_lock : + &vfe31_ctrl->aec_ack_lock); + spin_lock_irqsave(lock, flags); + vfe31_ctrl->aecStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->aecStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static void vfe31_stats_ihist_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe31_ctrl->stats_comp ? + &vfe31_ctrl->comp_stats_ack_lock : + &vfe31_ctrl->ihist_ack_lock); + spin_lock_irqsave(lock, flags); + vfe31_ctrl->ihistStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->ihistStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} +static void vfe31_stats_rs_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe31_ctrl->stats_comp ? + &vfe31_ctrl->comp_stats_ack_lock : + &vfe31_ctrl->rs_ack_lock); + spin_lock_irqsave(lock, flags); + vfe31_ctrl->rsStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->rsStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} +static void vfe31_stats_cs_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe31_ctrl->stats_comp ? + &vfe31_ctrl->comp_stats_ack_lock : + &vfe31_ctrl->cs_ack_lock); + spin_lock_irqsave(lock, flags); + vfe31_ctrl->csStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->csStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static inline void vfe31_read_irq_status(struct vfe31_irq_status *out) +{ + uint32_t *temp; + memset(out, 0, sizeof(struct vfe31_irq_status)); + temp = (uint32_t *)(vfe31_ctrl->vfebase + VFE_IRQ_STATUS_0); + out->vfeIrqStatus0 = msm_camera_io_r(temp); + + temp = (uint32_t *)(vfe31_ctrl->vfebase + VFE_IRQ_STATUS_1); + out->vfeIrqStatus1 = msm_camera_io_r(temp); + + temp = (uint32_t *)(vfe31_ctrl->vfebase + VFE_CAMIF_STATUS); + out->camifStatus = msm_camera_io_r(temp); + CDBG("camifStatus = 0x%x\n", out->camifStatus); + + /* clear the pending interrupt of the same kind.*/ + msm_camera_io_w(out->vfeIrqStatus0, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_io_w(out->vfeIrqStatus1, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_IRQ_CMD); + +} + +static void vfe31_process_reg_update_irq(void) +{ + unsigned long flags; + + if (vfe31_ctrl->recording_state == VFE_STATE_START_REQUESTED) { + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_VIDEO_AND_PREVIEW) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } else if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } + vfe31_ctrl->recording_state = VFE_STATE_STARTED; + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + CDBG("start video triggered .\n"); + } else if (vfe31_ctrl->recording_state == + VFE_STATE_STOP_REQUESTED) { + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_VIDEO_AND_PREVIEW) { + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } else if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) { + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } + CDBG("stop video triggered .\n"); + } + + if (vfe31_ctrl->start_ack_pending == TRUE) { + vfe31_send_isp_msg(vfe31_ctrl, MSG_ID_START_ACK); + vfe31_ctrl->start_ack_pending = FALSE; + } else { + if (vfe31_ctrl->recording_state == + VFE_STATE_STOP_REQUESTED) { + vfe31_ctrl->recording_state = VFE_STATE_STOPPED; + /* request a reg update and send STOP_REC_ACK + * when we process the next reg update irq. + */ + msm_camera_io_w_mb(1, + vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } else if (vfe31_ctrl->recording_state == + VFE_STATE_STOPPED) { + vfe31_send_isp_msg(vfe31_ctrl, MSG_ID_STOP_REC_ACK); + vfe31_ctrl->recording_state = VFE_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_isp_msg(vfe31_ctrl, MSG_ID_UPDATE_ACK); + } else { + spin_unlock_irqrestore( + &vfe31_ctrl->update_ack_lock, flags); + } + } + + if (vfe31_ctrl->liveshot_state == VFE_STATE_START_REQUESTED) { + pr_info("%s enabling liveshot output\n", __func__); + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + vfe31_ctrl->liveshot_state = VFE_STATE_STARTED; + } + } + + if (vfe31_ctrl->liveshot_state == VFE_STATE_STARTED) { + vfe31_ctrl->vfe_capture_count--; + if (!vfe31_ctrl->vfe_capture_count) + vfe31_ctrl->liveshot_state = VFE_STATE_STOP_REQUESTED; + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } else if (vfe31_ctrl->liveshot_state == VFE_STATE_STOP_REQUESTED) { + CDBG("%s: disabling liveshot output\n", __func__); + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + vfe31_ctrl->liveshot_state = VFE_STATE_STOPPED; + msm_camera_io_w_mb(1, vfe31_ctrl->vfebase + + VFE_REG_UPDATE_CMD); + } + } else if (vfe31_ctrl->liveshot_state == VFE_STATE_STOPPED) { + vfe31_ctrl->liveshot_state = VFE_STATE_IDLE; + } + + if ((vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN) || + (vfe31_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB) || + (vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) || + (vfe31_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB)) { + /* in snapshot mode */ + /* later we need to add check for live snapshot mode. */ + if (vfe31_ctrl->frame_skip_pattern & (0x1 << + (vfe31_ctrl->snapshot_frame_cnt % + vfe31_ctrl->frame_skip_cnt))) { + /* 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_PRIMARY) { + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out0.ch0]); + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out0.ch1]); + } + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out1.ch0]); + msm_camera_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out1.ch1]); + } + msm_camera_io_w_mb + (CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + vfe31_ctrl->snapshot_frame_cnt = -1; + vfe31_ctrl->frame_skip_cnt = 31; + vfe31_ctrl->frame_skip_pattern = 0xffffffff; + } /*if snapshot count is 0*/ + } /*if frame is not being dropped*/ + /* then do reg_update. */ + msm_camera_io_w(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } /* if snapshot mode. */ +} + +static void vfe31_set_default_reg_values(void) +{ + msm_camera_io_w(0x800080, vfe31_ctrl->vfebase + VFE_DEMUX_GAIN_0); + msm_camera_io_w(0x800080, vfe31_ctrl->vfebase + VFE_DEMUX_GAIN_1); + /* What value should we program CGC_OVERRIDE to? */ + msm_camera_io_w(0xFFFFF, vfe31_ctrl->vfebase + VFE_CGC_OVERRIDE); + + /* default frame drop period and pattern */ + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG); + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_CFG); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_PATTERN); + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y); + msm_camera_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN); + msm_camera_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_PATTERN); + msm_camera_io_w(0, vfe31_ctrl->vfebase + VFE_CLAMP_MIN); + msm_camera_io_w(0xFFFFFF, vfe31_ctrl->vfebase + VFE_CLAMP_MAX); + + /* stats UB config */ + msm_camera_io_w(0x3980007, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_UB_CFG); + msm_camera_io_w(0x3A00007, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_UB_CFG); + msm_camera_io_w(0x3A8000F, + vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_UB_CFG); + msm_camera_io_w(0x3B80007, + vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_UB_CFG); + msm_camera_io_w(0x3C0001F, + vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_UB_CFG); + msm_camera_io_w(0x3E0001F, + vfe31_ctrl->vfebase + VFE_BUS_STATS_HIST_UB_CFG); +} + +static void vfe31_process_reset_irq(void) +{ + unsigned long flags; + + atomic_set(&vfe31_ctrl->vstate, 0); + + spin_lock_irqsave(&vfe31_ctrl->stop_flag_lock, flags); + if (vfe31_ctrl->stop_ack_pending) { + vfe31_ctrl->stop_ack_pending = FALSE; + spin_unlock_irqrestore(&vfe31_ctrl->stop_flag_lock, flags); + vfe31_send_isp_msg(vfe31_ctrl, MSG_ID_STOP_ACK); + } else { + spin_unlock_irqrestore(&vfe31_ctrl->stop_flag_lock, flags); + /* this is from reset command. */ + vfe31_set_default_reg_values(); + + /* reload all write masters. (frame & line)*/ + msm_camera_io_w(0x7FFF, vfe31_ctrl->vfebase + VFE_BUS_CMD); + vfe31_send_isp_msg(vfe31_ctrl, MSG_ID_RESET_ACK); + } +} + +static void vfe31_process_camif_sof_irq(void) +{ + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_RAW) { + if (vfe31_ctrl->start_ack_pending) { + vfe31_send_isp_msg(vfe31_ctrl, MSG_ID_START_ACK); + vfe31_ctrl->start_ack_pending = FALSE; + } + 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_camera_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + 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_ctrl->vfeFrameId++; + vfe31_send_isp_msg(vfe31_ctrl, MSG_ID_SOF_ACK); + 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--; + } + if ((vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN) || + (vfe31_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB) || + (vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) || + (vfe31_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB)) { + if (vfe31_ctrl->frame_skip_pattern & (0x1 << + (vfe31_ctrl->snapshot_frame_cnt % + vfe31_ctrl->frame_skip_cnt))) { + vfe31_ctrl->vfe_capture_count--; + } + vfe31_ctrl->snapshot_frame_cnt++; + } +} + +static void vfe31_process_error_irq(uint32_t errStatus) +{ + uint32_t reg_value, read_val; + + if (errStatus & VFE31_IMASK_CAMIF_ERROR) { + pr_err("vfe31_irq: camif errors\n"); + reg_value = msm_camera_io_r( + vfe31_ctrl->vfebase + VFE_CAMIF_STATUS); + pr_err("camifStatus = 0x%x\n", reg_value); + vfe31_send_isp_msg(vfe31_ctrl, 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_BG_BUS_OVFL) + pr_err("vfe31_irq: ae/bg stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_AF_BF_BUS_OVFL) + pr_err("vfe31_irq: af/bf 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_BHIST_BUS_OVFL) + pr_err("vfe31_irq: skin/bhist 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_camera_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_camera_io_r(vfe31_ctrl->vfebase + + VFE_BUS_OPERATION_STATUS); + pr_debug("VFE_BUS_OPERATION_STATUS = 0x%x\n", read_val); + read_val = msm_camera_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_camera_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_camera_io_r(vfe31_ctrl->vfebase + + VFE_AXI_STATUS); + pr_debug("VFE_AXI_STATUS = 0x%x\n", read_val); + } +} +static void vfe_send_outmsg(struct v4l2_subdev *sd, uint8_t msgid, + uint32_t ch0_paddr, uint32_t ch1_paddr, uint32_t ch2_paddr) +{ + struct isp_msg_output msg; + + msg.output_id = msgid; + msg.buf.ch_paddr[0] = ch0_paddr; + msg.buf.ch_paddr[1] = ch1_paddr; + msg.buf.ch_paddr[2] = ch2_paddr; + msg.frameCounter = vfe31_ctrl->vfeFrameId; + + v4l2_subdev_notify(&vfe31_ctrl->subdev, + NOTIFY_VFE_MSG_OUT, &msg); + return; +} + +static void vfe31_process_output_path_irq_0(void) +{ + uint32_t ping_pong; + uint32_t ch0_paddr, ch1_paddr, ch2_paddr; + uint8_t out_bool = 0; + struct msm_free_buf *free_buf = NULL; + + free_buf = vfe31_check_free_buffer(VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + + /* 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 = ((vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe31_ctrl->operation_mode == VFE_OUTPUTS_RAW || + vfe31_ctrl->liveshot_state == VFE_STATE_STARTED || + vfe31_ctrl->liveshot_state == VFE_STATE_STOP_REQUESTED || + vfe31_ctrl->liveshot_state == VFE_STATE_STOPPED) && + (vfe31_ctrl->vfe_capture_count <= 1)) || free_buf; + + if (out_bool) { + ping_pong = msm_camera_io_r(vfe31_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Channel 0*/ + ch0_paddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0); + /* Channel 1*/ + ch1_paddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1); + /* Channel 2*/ + ch2_paddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch2); + + CDBG("output path 0, ch0 = 0x%x, ch1 = 0x%x, ch2 = 0x%x\n", + ch0_paddr, ch1_paddr, ch2_paddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0, + free_buf->ch_paddr[0]); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1, + free_buf->ch_paddr[1]); + if (free_buf->num_planes > 2) + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch2, + free_buf->ch_paddr[2]); + } + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_JPEG || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_RAW || + vfe31_ctrl->liveshot_state == VFE_STATE_STOPPED) + vfe31_ctrl->outpath.out0.capture_cnt--; + + vfe_send_outmsg(&vfe31_ctrl->subdev, + MSG_ID_OUTPUT_PRIMARY, ch0_paddr, + ch1_paddr, ch2_paddr); + + if (vfe31_ctrl->liveshot_state == VFE_STATE_STOPPED) + vfe31_ctrl->liveshot_state = VFE_STATE_IDLE; + + } else { + vfe31_ctrl->outpath.out0.frame_drop_cnt++; + CDBG("path_irq_0 - no free buffer!\n"); + } +} + +static void vfe31_process_output_path_irq_1(void) +{ + uint32_t ping_pong; + uint32_t ch0_paddr, ch1_paddr, ch2_paddr; + /* this must be snapshot main image output. */ + uint8_t out_bool = 0; + struct msm_free_buf *free_buf = NULL; + + free_buf = vfe31_check_free_buffer(VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY); + out_bool = ((vfe31_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_RAW || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB) && + (vfe31_ctrl->vfe_capture_count <= 1)) || free_buf; + + if (out_bool) { + ping_pong = msm_camera_io_r(vfe31_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Y channel */ + ch0_paddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel */ + ch1_paddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1); + ch2_paddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch2); + + pr_debug("%s ch0 = 0x%x, ch1 = 0x%x, ch2 = 0x%x\n", + __func__, ch0_paddr, ch1_paddr, ch2_paddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0, + free_buf->ch_paddr[0]); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->ch_paddr[1]); + if (free_buf->num_planes > 2) + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch2, + free_buf->ch_paddr[2]); + } + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_RAW || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB) + vfe31_ctrl->outpath.out1.capture_cnt--; + + vfe_send_outmsg(&vfe31_ctrl->subdev, + MSG_ID_OUTPUT_SECONDARY, ch0_paddr, + ch1_paddr, ch2_paddr); + } else { + vfe31_ctrl->outpath.out1.frame_drop_cnt++; + CDBG("path_irq_1 - no free buffer!\n"); + } +} + +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_camera_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_camera_io_r((uint32_t *)pingpongAddr); + msm_camera_io_w(newAddr, (uint32_t *)pingpongAddr); + return returnAddr; +} + +static void +vfe_send_stats_msg(uint32_t bufAddress, uint32_t statsNum) +{ + unsigned long flags; + /* fill message with right content. */ + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + struct isp_msg_stats msgStats; + msgStats.frameCounter = vfe31_ctrl->vfeFrameId; + msgStats.buffer = bufAddress; + + switch (statsNum) { + case STATS_AE_NUM:{ + msgStats.id = MSG_ID_STATS_AEC; + spin_lock_irqsave(&vfe31_ctrl->aec_ack_lock, flags); + vfe31_ctrl->aecStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->aec_ack_lock, flags); + } + break; + case STATS_AF_NUM:{ + msgStats.id = MSG_ID_STATS_AF; + spin_lock_irqsave(&vfe31_ctrl->af_ack_lock, flags); + vfe31_ctrl->afStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->af_ack_lock, flags); + } + break; + case STATS_AWB_NUM: { + msgStats.id = MSG_ID_STATS_AWB; + spin_lock_irqsave(&vfe31_ctrl->awb_ack_lock, flags); + vfe31_ctrl->awbStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->awb_ack_lock, flags); + } + break; + + case STATS_IHIST_NUM: { + msgStats.id = MSG_ID_STATS_IHIST; + spin_lock_irqsave(&vfe31_ctrl->ihist_ack_lock, flags); + vfe31_ctrl->ihistStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->ihist_ack_lock, flags); + } + break; + case STATS_RS_NUM: { + msgStats.id = MSG_ID_STATS_RS; + spin_lock_irqsave(&vfe31_ctrl->rs_ack_lock, flags); + vfe31_ctrl->rsStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->rs_ack_lock, flags); + } + break; + case STATS_CS_NUM: { + msgStats.id = MSG_ID_STATS_CS; + spin_lock_irqsave(&vfe31_ctrl->cs_ack_lock, flags); + vfe31_ctrl->csStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->cs_ack_lock, flags); + } + break; + + default: + goto stats_done; + } + + v4l2_subdev_notify(&vfe31_ctrl->subdev, + NOTIFY_VFE_MSG_STATS, + &msgStats); +stats_done: + /* spin_unlock_irqrestore(&ctrl->state_lock, flags); */ + return; +} + +static void vfe_send_comp_stats_msg(uint32_t status_bits) +{ + struct msm_stats_buf msgStats; + uint32_t temp; + + msgStats.frame_id = vfe31_ctrl->vfeFrameId; + msgStats.status_bits = status_bits; + + msgStats.aec.buff = vfe31_ctrl->aecStatsControl.bufToRender; + msgStats.awb.buff = vfe31_ctrl->awbStatsControl.bufToRender; + msgStats.af.buff = vfe31_ctrl->afStatsControl.bufToRender; + + msgStats.ihist.buff = vfe31_ctrl->ihistStatsControl.bufToRender; + msgStats.rs.buff = vfe31_ctrl->rsStatsControl.bufToRender; + msgStats.cs.buff = vfe31_ctrl->csStatsControl.bufToRender; + + temp = msm_camera_io_r(vfe31_ctrl->vfebase + VFE_STATS_AWB_SGW_CFG); + msgStats.awb_ymin = (0xFF00 & temp) >> 8; + + v4l2_subdev_notify(&vfe31_ctrl->subdev, + NOTIFY_VFE_MSG_COMP_STATS, &msgStats); +} + +static void vfe31_process_stats_ae_irq(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe31_ctrl->aec_ack_lock, flags); + if (!(vfe31_ctrl->aecStatsControl.ackPending)) { + spin_unlock_irqrestore(&vfe31_ctrl->aec_ack_lock, flags); + vfe31_ctrl->aecStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_AE_NUM, + vfe31_ctrl->aecStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe31_ctrl->aecStatsControl.bufToRender, + STATS_AE_NUM); + } else{ + spin_unlock_irqrestore(&vfe31_ctrl->aec_ack_lock, flags); + vfe31_ctrl->aecStatsControl.droppedStatsFrameCount++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe31_ctrl->aecStatsControl.droppedStatsFrameCount); + } +} + +static void vfe31_process_stats_awb_irq(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe31_ctrl->awb_ack_lock, flags); + if (!(vfe31_ctrl->awbStatsControl.ackPending)) { + spin_unlock_irqrestore(&vfe31_ctrl->awb_ack_lock, flags); + vfe31_ctrl->awbStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_AWB_NUM, + vfe31_ctrl->awbStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe31_ctrl->awbStatsControl.bufToRender, + STATS_AWB_NUM); + } else{ + spin_unlock_irqrestore(&vfe31_ctrl->awb_ack_lock, flags); + vfe31_ctrl->awbStatsControl.droppedStatsFrameCount++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe31_ctrl->awbStatsControl.droppedStatsFrameCount); + } +} + +static void vfe31_process_stats_af_irq(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe31_ctrl->af_ack_lock, flags); + if (!(vfe31_ctrl->afStatsControl.ackPending)) { + spin_unlock_irqrestore(&vfe31_ctrl->af_ack_lock, flags); + vfe31_ctrl->afStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_AF_NUM, + vfe31_ctrl->afStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe31_ctrl->afStatsControl.bufToRender, + STATS_AF_NUM); + } else{ + spin_unlock_irqrestore(&vfe31_ctrl->af_ack_lock, flags); + vfe31_ctrl->afStatsControl.droppedStatsFrameCount++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe31_ctrl->afStatsControl.droppedStatsFrameCount); + } +} + +static void vfe31_process_stats_ihist_irq(void) +{ + if (!(vfe31_ctrl->ihistStatsControl.ackPending)) { + vfe31_ctrl->ihistStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_IHIST_NUM, + vfe31_ctrl->ihistStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe31_ctrl->ihistStatsControl.bufToRender, + STATS_IHIST_NUM); + } else { + vfe31_ctrl->ihistStatsControl.droppedStatsFrameCount++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe31_ctrl->ihistStatsControl.droppedStatsFrameCount); + } +} + +static void vfe31_process_stats_rs_irq(void) +{ + if (!(vfe31_ctrl->rsStatsControl.ackPending)) { + vfe31_ctrl->rsStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_RS_NUM, + vfe31_ctrl->rsStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe31_ctrl->rsStatsControl.bufToRender, + STATS_RS_NUM); + } else { + vfe31_ctrl->rsStatsControl.droppedStatsFrameCount++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe31_ctrl->rsStatsControl.droppedStatsFrameCount); + } +} + +static void vfe31_process_stats_cs_irq(void) +{ + if (!(vfe31_ctrl->csStatsControl.ackPending)) { + vfe31_ctrl->csStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_CS_NUM, + vfe31_ctrl->csStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe31_ctrl->csStatsControl.bufToRender, + STATS_CS_NUM); + } else { + vfe31_ctrl->csStatsControl.droppedStatsFrameCount++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe31_ctrl->csStatsControl.droppedStatsFrameCount); + } +} + +static void vfe31_process_stats(uint32_t status_bits) +{ + unsigned long flags; + int32_t process_stats = false; + CDBG("%s, stats = 0x%x\n", __func__, status_bits); + + spin_lock_irqsave(&vfe31_ctrl->comp_stats_ack_lock, flags); + if (status_bits & VFE_IRQ_STATUS0_STATS_AEC) { + if (!vfe31_ctrl->aecStatsControl.ackPending) { + vfe31_ctrl->aecStatsControl.ackPending = TRUE; + vfe31_ctrl->aecStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_AE_NUM, + vfe31_ctrl->aecStatsControl.nextFrameAddrBuf); + process_stats = true; + } else{ + vfe31_ctrl->aecStatsControl.bufToRender = 0; + vfe31_ctrl->aecStatsControl.droppedStatsFrameCount++; + } + } else { + vfe31_ctrl->aecStatsControl.bufToRender = 0; + } + + if (status_bits & VFE_IRQ_STATUS0_STATS_AWB) { + if (!vfe31_ctrl->awbStatsControl.ackPending) { + vfe31_ctrl->awbStatsControl.ackPending = TRUE; + vfe31_ctrl->awbStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_AWB_NUM, + vfe31_ctrl->awbStatsControl.nextFrameAddrBuf); + process_stats = true; + } else{ + vfe31_ctrl->awbStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->awbStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->awbStatsControl.bufToRender = 0; + } + + + if (status_bits & VFE_IRQ_STATUS0_STATS_AF) { + if (!vfe31_ctrl->afStatsControl.ackPending) { + vfe31_ctrl->afStatsControl.ackPending = TRUE; + vfe31_ctrl->afStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_AF_NUM, + vfe31_ctrl->afStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->afStatsControl.bufToRender = 0; + vfe31_ctrl->afStatsControl.droppedStatsFrameCount++; + } + } else { + vfe31_ctrl->afStatsControl.bufToRender = 0; + } + + if (status_bits & VFE_IRQ_STATUS0_STATS_IHIST) { + if (!vfe31_ctrl->ihistStatsControl.ackPending) { + vfe31_ctrl->ihistStatsControl.ackPending = TRUE; + vfe31_ctrl->ihistStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_IHIST_NUM, + vfe31_ctrl->ihistStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->ihistStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->ihistStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->ihistStatsControl.bufToRender = 0; + } + + if (status_bits & VFE_IRQ_STATUS0_STATS_RS) { + if (!vfe31_ctrl->rsStatsControl.ackPending) { + vfe31_ctrl->rsStatsControl.ackPending = TRUE; + vfe31_ctrl->rsStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_RS_NUM, + vfe31_ctrl->rsStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->rsStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->rsStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->rsStatsControl.bufToRender = 0; + } + + + if (status_bits & VFE_IRQ_STATUS0_STATS_CS) { + if (!vfe31_ctrl->csStatsControl.ackPending) { + vfe31_ctrl->csStatsControl.ackPending = TRUE; + vfe31_ctrl->csStatsControl.bufToRender = + vfe31_process_stats_irq_common(STATS_CS_NUM, + vfe31_ctrl->csStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->csStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->csStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->csStatsControl.bufToRender = 0; + } + + spin_unlock_irqrestore(&vfe31_ctrl->comp_stats_ack_lock, flags); + if (process_stats) + vfe_send_comp_stats_msg(status_bits); + + return; +} + +static void vfe31_process_stats_irq(uint32_t *irqstatus) +{ + uint32_t status_bits = VFE_COM_STATUS & *irqstatus; + + 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_process_stats(status_bits); + 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); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_CAMIF_SOF_MASK) { + CDBG("irq camifSofIrq\n"); + vfe31_process_camif_sof_irq(); + } + /* 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_WHILE_STOPPING_1) { + CDBG("irq resetAckIrq\n"); + vfe31_process_reset_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); + } + /* 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(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE1_MASK) { + CDBG("Image composite done 1 irq occured.\n"); + vfe31_process_output_path_irq_1(); + } + /* in snapshot mode if done then send + snapshot done message */ + if (vfe31_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_JPEG || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB || + vfe31_ctrl->operation_mode == + VFE_OUTPUTS_RAW) { + if ((vfe31_ctrl->outpath.out0.capture_cnt == 0) + && (vfe31_ctrl->outpath.out1. + capture_cnt == 0)) { + msm_camera_io_w_mb( + CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe31_ctrl->vfebase + + VFE_CAMIF_COMMAND); + vfe31_send_isp_msg(vfe31_ctrl, + MSG_ID_SNAPSHOT_DONE); + } + } + /* 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_IRQ_STATUS0_STATS_AEC) { + CDBG("Stats AEC irq occured.\n"); + vfe31_process_stats_ae_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AWB) { + CDBG("Stats AWB irq occured.\n"); + vfe31_process_stats_awb_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AF) { + CDBG("Stats AF irq occured.\n"); + vfe31_process_stats_af_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_IHIST) { + CDBG("Stats IHIST irq occured.\n"); + vfe31_process_stats_ihist_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_RS) { + CDBG("Stats RS irq occured.\n"); + vfe31_process_stats_rs_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_CS) { + CDBG("Stats CS irq occured.\n"); + vfe31_process_stats_cs_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER0) { + CDBG("SYNC_TIMER 0 irq occured.\n"); + vfe31_send_isp_msg(vfe31_ctrl, + MSG_ID_SYNC_TIMER0_DONE); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER1) { + CDBG("SYNC_TIMER 1 irq occured.\n"); + vfe31_send_isp_msg(vfe31_ctrl, + MSG_ID_SYNC_TIMER1_DONE); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER2) { + CDBG("SYNC_TIMER 2 irq occured.\n"); + vfe31_send_isp_msg(vfe31_ctrl, + MSG_ID_SYNC_TIMER2_DONE); + } + } + } + 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; + + CDBG("vfe_parse_irq\n"); + + vfe31_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 vfe31_isr_queue_cmd), + GFP_ATOMIC); + if (!qcmd) { + pr_err("vfe_parse_irq: qcmd malloc failed!\n"); + return IRQ_HANDLED; + } + + spin_lock_irqsave(&vfe31_ctrl->stop_flag_lock, flags); + if (vfe31_ctrl->stop_ack_pending) { + irq.vfeIrqStatus0 &= VFE_IMASK_WHILE_STOPPING_0; + irq.vfeIrqStatus1 &= VFE_IMASK_WHILE_STOPPING_1; + } + spin_unlock_irqrestore(&vfe31_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(&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 long msm_vfe_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int subdev_cmd, void *arg) +{ + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *)v4l2_get_subdev_hostdata(sd); + struct msm_isp_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_CONFIG_PING_ADDR && + cmd->cmd_type != CMD_CONFIG_PONG_ADDR && + cmd->cmd_type != CMD_CONFIG_FREE_BUF_ADDR && + 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_CONFIG_PING_ADDR && + cmd->cmd_type != CMD_CONFIG_PONG_ADDR && + cmd->cmd_type != CMD_CONFIG_FREE_BUF_ADDR) { + /* 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; + default: + pr_err("%s Unsupported cmd type %d", + __func__, cmd->cmd_type); + break; + } + goto vfe31_config_done; + } + switch (cmd->cmd_type) { + case CMD_GENERAL: { + rc = vfe31_proc_general(pmctl, &vfecmd); + } + break; + case CMD_CONFIG_PING_ADDR: { + int path = *((int *)cmd->value); + struct vfe31_output_ch *outch = vfe31_get_ch(path); + outch->ping = *((struct msm_free_buf *)data); + } + break; + + case CMD_CONFIG_PONG_ADDR: { + int path = *((int *)cmd->value); + struct vfe31_output_ch *outch = vfe31_get_ch(path); + outch->pong = *((struct msm_free_buf *)data); + } + break; + + case CMD_CONFIG_FREE_BUF_ADDR: { + int path = *((int *)cmd->value); + struct vfe31_output_ch *outch = vfe31_get_ch(path); + outch->free_buf = *((struct msm_free_buf *)data); + } + break; + + case CMD_SNAP_BUF_RELEASE: + 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_PRIM: { + uint32_t *axio = NULL; + axio = kmalloc(vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_PRIM, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_PRIM_ALL_CHNLS: { + uint32_t *axio = NULL; + axio = kmalloc(vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_PRIM_ALL_CHNLS, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_PRIM|CMD_AXI_CFG_SEC: { + uint32_t *axio = NULL; + axio = kmalloc(vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_PRIM|OUTPUT_SEC, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_PRIM|CMD_AXI_CFG_SEC_ALL_CHNLS: { + uint32_t *axio = NULL; + axio = kmalloc(vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_PRIM|OUTPUT_SEC_ALL_CHNLS, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_PRIM_ALL_CHNLS|CMD_AXI_CFG_SEC: { + uint32_t *axio = NULL; + axio = kmalloc(vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_PRIM_ALL_CHNLS|OUTPUT_SEC, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_PRIM_ALL_CHNLS|CMD_AXI_CFG_SEC_ALL_CHNLS: { + pr_err("%s Invalid/Unsupported AXI configuration %x", + __func__, cmd->cmd_type); + } + break; + + default: + pr_err("%s Unsupported AXI configuration %x ", __func__, + cmd->cmd_type); + break; + } +vfe31_config_done: + kfree(scfg); + kfree(sack); + CDBG("%s done: rc = %d\n", __func__, (int) rc); + return rc; +} + +static int msm_vfe_subdev_s_crystal_freq(struct v4l2_subdev *sd, + u32 freq, u32 flags) +{ + int rc = 0; + int round_rate; + + round_rate = clk_round_rate(vfe31_ctrl->vfe_clk[0], freq); + if (rc < 0) { + pr_err("%s: clk_round_rate failed %d\n", + __func__, rc); + return rc; + } + + vfe_clk_rate = round_rate; + rc = clk_set_rate(vfe31_ctrl->vfe_clk[0], round_rate); + if (rc < 0) + pr_err("%s: clk_set_rate failed %d\n", + __func__, rc); + + return rc; +} + +static const struct v4l2_subdev_video_ops msm_vfe_subdev_video_ops = { + .s_crystal_freq = msm_vfe_subdev_s_crystal_freq, +}; + +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, + .video = &msm_vfe_subdev_video_ops, +}; + +static struct msm_cam_clk_info vfe_clk_info[] = { + {"vfe_clk", VFE_CLK_RATE}, + {"vfe_pclk", -1}, +}; + +static struct msm_cam_clk_info vfe_camif_clk_info[] = { + {"camif_pad_pclk", -1}, + {"vfe_camif_clk", -1}, +}; + +static void msm_vfe_camio_clk_sel(enum msm_camio_clk_src_type srctype) +{ + struct clk *clk = NULL; + + clk = vfe31_ctrl->vfe_clk[0]; + + 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; + } + } +} + +static void msm_vfe_camif_pad_reg_reset(void) +{ + uint32_t reg; + + msm_vfe_camio_clk_sel(MSM_CAMIO_CLK_SRC_INTERNAL); + usleep_range(10000, 15000); + + reg = (msm_camera_io_r(vfe31_ctrl->camifbase)) & CAMIF_CFG_RMSK; + reg |= 0x3; + msm_camera_io_w(reg, vfe31_ctrl->camifbase); + usleep_range(10000, 15000); + + reg = (msm_camera_io_r(vfe31_ctrl->camifbase)) & CAMIF_CFG_RMSK; + reg |= 0x10; + msm_camera_io_w(reg, vfe31_ctrl->camifbase); + usleep_range(10000, 15000); + + reg = (msm_camera_io_r(vfe31_ctrl->camifbase)) & CAMIF_CFG_RMSK; + /* Need to be uninverted*/ + reg &= 0x03; + msm_camera_io_w(reg, vfe31_ctrl->camifbase); + usleep_range(10000, 15000); +} + +int msm_vfe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + int rc = 0; + v4l2_set_subdev_hostdata(sd, mctl); + + spin_lock_init(&vfe31_ctrl->stop_flag_lock); + spin_lock_init(&vfe31_ctrl->state_lock); + spin_lock_init(&vfe31_ctrl->io_lock); + spin_lock_init(&vfe31_ctrl->update_ack_lock); + spin_lock_init(&vfe31_ctrl->tasklet_lock); + + spin_lock_init(&vfe31_ctrl->aec_ack_lock); + spin_lock_init(&vfe31_ctrl->awb_ack_lock); + spin_lock_init(&vfe31_ctrl->af_ack_lock); + spin_lock_init(&vfe31_ctrl->ihist_ack_lock); + spin_lock_init(&vfe31_ctrl->rs_ack_lock); + spin_lock_init(&vfe31_ctrl->cs_ack_lock); + spin_lock_init(&vfe31_ctrl->comp_stats_ack_lock); + spin_lock_init(&vfe31_ctrl->sd_notify_lock); + INIT_LIST_HEAD(&vfe31_ctrl->tasklet_q); + + vfe31_ctrl->update_linear = false; + vfe31_ctrl->update_rolloff = false; + vfe31_ctrl->update_la = false; + vfe31_ctrl->update_gamma = false; + vfe31_ctrl->hfr_mode = HFR_MODE_OFF; + + vfe31_ctrl->vfebase = ioremap(vfe31_ctrl->vfemem->start, + resource_size(vfe31_ctrl->vfemem)); + if (!vfe31_ctrl->vfebase) { + rc = -ENOMEM; + pr_err("%s: vfe ioremap failed\n", __func__); + goto vfe_remap_failed; + } + if (!mctl->sdata->csi_if) { + vfe31_ctrl->camifbase = ioremap(vfe31_ctrl->camifmem->start, + resource_size(vfe31_ctrl->camifmem)); + if (!vfe31_ctrl->camifbase) { + rc = -ENOMEM; + pr_err("%s: camif ioremap failed\n", __func__); + goto camif_remap_failed; + } + } + + if (vfe31_ctrl->fs_vfe == NULL) { + vfe31_ctrl->fs_vfe = + regulator_get(&vfe31_ctrl->pdev->dev, "fs_vfe"); + if (IS_ERR(vfe31_ctrl->fs_vfe)) { + pr_err("%s: Regulator FS_VFE get failed %ld\n", + __func__, PTR_ERR(vfe31_ctrl->fs_vfe)); + vfe31_ctrl->fs_vfe = NULL; + goto vfe_fs_failed; + } else if (regulator_enable(vfe31_ctrl->fs_vfe)) { + pr_err("%s: Regulator FS_VFE enable failed\n", + __func__); + regulator_put(vfe31_ctrl->fs_vfe); + vfe31_ctrl->fs_vfe = NULL; + goto vfe_fs_failed; + } + } + + rc = msm_cam_clk_enable(&vfe31_ctrl->pdev->dev, vfe_clk_info, + vfe31_ctrl->vfe_clk, ARRAY_SIZE(vfe_clk_info), 1); + if (rc < 0) + goto vfe_clk_enable_failed; + + if (!mctl->sdata->csi_if) { + rc = msm_cam_clk_enable(&vfe31_ctrl->pdev->dev, + vfe_camif_clk_info, + vfe31_ctrl->vfe_camif_clk, + ARRAY_SIZE(vfe_camif_clk_info), 1); + if (rc < 0) + goto vfe_clk_enable_failed; + msm_vfe_camif_pad_reg_reset(); + } + + msm_camio_bus_scale_cfg( + mctl->sdata->pdata->cam_bus_scale_table, S_INIT); + msm_camio_bus_scale_cfg( + mctl->sdata->pdata->cam_bus_scale_table, S_PREVIEW); + vfe31_ctrl->register_total = VFE31_REGISTER_TOTAL; + + enable_irq(vfe31_ctrl->vfeirq->start); + + return rc; + +vfe_clk_enable_failed: + regulator_disable(vfe31_ctrl->fs_vfe); + regulator_put(vfe31_ctrl->fs_vfe); + vfe31_ctrl->fs_vfe = NULL; +vfe_fs_failed: + if (!mctl->sdata->csi_if) + iounmap(vfe31_ctrl->camifbase); +camif_remap_failed: + iounmap(vfe31_ctrl->vfebase); +vfe_remap_failed: + disable_irq(vfe31_ctrl->vfeirq->start); + return rc; +} + +void msm_vfe_subdev_release(struct v4l2_subdev *sd) +{ + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *)v4l2_get_subdev_hostdata(sd); + disable_irq(vfe31_ctrl->vfeirq->start); + tasklet_kill(&vfe31_tasklet); + + if (!pmctl->sdata->csi_if) + msm_cam_clk_enable(&vfe31_ctrl->pdev->dev, + vfe_camif_clk_info, + vfe31_ctrl->vfe_camif_clk, + ARRAY_SIZE(vfe_camif_clk_info), 0); + + msm_cam_clk_enable(&vfe31_ctrl->pdev->dev, vfe_clk_info, + vfe31_ctrl->vfe_clk, ARRAY_SIZE(vfe_clk_info), 0); + if (vfe31_ctrl->fs_vfe) { + regulator_disable(vfe31_ctrl->fs_vfe); + regulator_put(vfe31_ctrl->fs_vfe); + vfe31_ctrl->fs_vfe = NULL; + } + CDBG("%s, 31ee_irq\n", __func__); + if (!pmctl->sdata->csi_if) + iounmap(vfe31_ctrl->camifbase); + iounmap(vfe31_ctrl->vfebase); + + if (atomic_read(&irq_cnt)) + pr_warning("%s, Warning IRQ Count not ZERO\n", __func__); + + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_EXIT); +} + +static const struct v4l2_subdev_internal_ops msm_vfe_internal_ops; + +static int __devinit vfe31_probe(struct platform_device *pdev) +{ + int rc = 0; + CDBG("%s: device id = %d\n", __func__, pdev->id); + vfe31_ctrl = kzalloc(sizeof(struct vfe31_ctrl_type), GFP_KERNEL); + if (!vfe31_ctrl) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&vfe31_ctrl->subdev, &msm_vfe_subdev_ops); + vfe31_ctrl->subdev.internal_ops = &msm_vfe_internal_ops; + vfe31_ctrl->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(vfe31_ctrl->subdev.name, + sizeof(vfe31_ctrl->subdev.name), "vfe3.1"); + v4l2_set_subdevdata(&vfe31_ctrl->subdev, vfe31_ctrl); + platform_set_drvdata(pdev, &vfe31_ctrl->subdev); + + vfe31_ctrl->vfemem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "msm_vfe"); + if (!vfe31_ctrl->vfemem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto vfe31_no_resource; + } + vfe31_ctrl->vfeirq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "msm_vfe"); + if (!vfe31_ctrl->vfeirq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto vfe31_no_resource; + } + vfe31_ctrl->camifmem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "msm_camif"); + if (!vfe31_ctrl->camifmem) + pr_err("%s: camif not supported\n", __func__); + + vfe31_ctrl->vfeio = request_mem_region(vfe31_ctrl->vfemem->start, + resource_size(vfe31_ctrl->vfemem), pdev->name); + if (!vfe31_ctrl->vfeio) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto vfe31_no_resource; + } + + if (vfe31_ctrl->camifmem) { + vfe31_ctrl->camifio = request_mem_region( + vfe31_ctrl->camifmem->start, + resource_size(vfe31_ctrl->camifmem), pdev->name); + if (!vfe31_ctrl->camifio) { + release_mem_region(vfe31_ctrl->vfemem->start, + resource_size(vfe31_ctrl->vfemem)); + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto vfe31_no_resource; + } + } + + rc = request_irq(vfe31_ctrl->vfeirq->start, vfe31_parse_irq, + IRQF_TRIGGER_RISING, "vfe", 0); + if (rc < 0) { + if (vfe31_ctrl->camifmem) { + release_mem_region(vfe31_ctrl->camifmem->start, + resource_size(vfe31_ctrl->camifmem)); + } + release_mem_region(vfe31_ctrl->vfemem->start, + resource_size(vfe31_ctrl->vfemem)); + pr_err("%s: irq request fail\n", __func__); + rc = -EBUSY; + goto vfe31_no_resource; + } + + disable_irq(vfe31_ctrl->vfeirq->start); + + vfe31_ctrl->pdev = pdev; + msm_cam_register_subdev_node(&vfe31_ctrl->subdev, VFE_DEV, 0); + return 0; + +vfe31_no_resource: + kfree(vfe31_ctrl); + return 0; +} + +static struct platform_driver vfe31_driver = { + .probe = vfe31_probe, + .driver = { + .name = MSM_VFE_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_vfe31_init_module(void) +{ + return platform_driver_register(&vfe31_driver); +} + +static void __exit msm_vfe31_exit_module(void) +{ + platform_driver_unregister(&vfe31_driver); +} + +module_init(msm_vfe31_init_module); +module_exit(msm_vfe31_exit_module); +MODULE_DESCRIPTION("VFE 3.1 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_vfe31_v4l2.h b/drivers/media/video/msm/msm_vfe31_v4l2.h new file mode 100644 index 0000000000000000000000000000000000000000..e94f2868ccbe32030289aa750904f0c8a8ad4aca --- /dev/null +++ b/drivers/media/video/msm/msm_vfe31_v4l2.h @@ -0,0 +1,955 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_V4L2_H__ +#define __MSM_VFE31_V4L2_H__ + +#include + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +/* This defines total number registers in VFE. + * Each register is 4 bytes so to get the range, + * multiply this number with 4. */ +#define VFE31_REGISTER_TOTAL 0x0000017F + +/* 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 DBPC bit 0 is set to zero and other's 1 */ +#define DBPC_MASK 0xFFFFFFFE + +/* For DBCC bit 1 is set to zero and other's 1 */ +#define DBCC_MASK 0xFFFFFFFD + +/* For DBPC/ABF/DBCC/ABCC bits are set to 1 all others 0 */ +#define DEMOSAIC_MASK 0x8FFFFFFF +/* 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 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 IHIST_ENABLE_MASK 0x00008000 /* bit 15 */ +#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 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 +#define VFE_FRAME_SKIP_PERIOD_MASK 0x0000001F /*bits 0 -4*/ + +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_output_state { + VFE_STATE_IDLE, + VFE_STATE_START_REQUESTED, + VFE_STATE_STARTED, + VFE_STATE_STOP_REQUESTED, + VFE_STATE_STOPPED, +}; + +#define V31_CAMIF_OFF 0x000001E4 +#define V31_CAMIF_LEN 32 + +#define V31_DEMUX_OFF 0x00000284 +#define V31_DEMUX_LEN 20 + +#define V31_DEMOSAICV3_UP_REG_CNT 5 + +#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_AXI_RESERVED 1 + +#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_MESH_ROLL_OFF_CFG_OFF 0x00000274 +#define V31_MESH_ROLL_OFF_CFG_LEN 16 +#define V31_MESH_ROLL_OFF_INIT_TABLE_SIZE 13 +#define V31_MESH_ROLL_OFF_DELTA_TABLE_SIZE 208 +#define V31_MESH_ROLL_OFF_DELTA_TABLE_OFFSET 32 + +#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_ASF_OFF 0x000004A0 +#define V31_ASF_LEN 48 +#define V31_ASF_UPDATE_LEN 36 +#define V31_CAPTURE_LEN 4 +#define V31_GET_HW_VERSION_OFF 0 +#define V31_GET_HW_VERSION_LEN 4 +#define V31_DEMOSAICV3_OFF 0x00000298 +#define V31_DEMOSAICV3_LEN 4 +/* BPC */ +#define V31_DEMOSAICV3_DBPC_CFG_OFF 0x0000029C +#define V31_DEMOSAICV3_DBPC_LEN 8 +#define V31_XBAR_CFG_OFF 0x00000040 +/* ABF */ +#define V31_DEMOSAICV3_ABF_OFF 0x000002A4 +#define V31_DEMOSAICV3_ABF_LEN 180 +#define V31_XBAR_CFG_LEN 8 + +#define V31_MODULE_CFG_OFF 0x00000010 +#define V31_MODULE_CFG_LEN 4 +#define V31_EZTUNE_CFG_OFF 0x00000010 +#define V31_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 +}; + +#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 +}; + +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; +}; + +#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_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; + struct msm_free_buf ping; + struct msm_free_buf pong; + struct msm_free_buf free_buf; +}; + +/* 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_BG_BUS_OVFL (0x00000001<<14) +#define VFE31_IMASK_STATS_AF_BF_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_BHIST_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_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_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_STATS_AWB_SGW_CFG 0x00000554 +#define VFE_DMI_CFG 0x00000598 +#define VFE_DMI_ADDR 0x0000059C +#define VFE_DMI_DATA_LO 0x000005A4 +#define VFE_AXI_CFG 0x00000600 + +#define VFE31_OUTPUT_MODE_PT BIT(0) +#define VFE31_OUTPUT_MODE_S BIT(1) +#define VFE31_OUTPUT_MODE_V BIT(2) +#define VFE31_OUTPUT_MODE_P BIT(3) +#define VFE31_OUTPUT_MODE_T BIT(4) +#define VFE31_OUTPUT_MODE_P_ALL_CHNLS BIT(5) +#define VFE31_OUTPUT_MODE_PRIMARY BIT(6) +#define VFE31_OUTPUT_MODE_PRIMARY_ALL_CHNLS BIT(7) +#define VFE31_OUTPUT_MODE_SECONDARY BIT(8) +#define VFE31_OUTPUT_MODE_SECONDARY_ALL_CHNLS BIT(9) +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 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; + spinlock_t ihist_ack_lock; + spinlock_t rs_ack_lock; + spinlock_t cs_ack_lock; + spinlock_t comp_stats_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; + enum vfe_output_state recording_state; + int8_t update_linear; + int8_t update_rolloff; + int8_t update_la; + int8_t update_gamma; + enum vfe_output_state liveshot_state; + + spinlock_t tasklet_lock; + struct list_head tasklet_q; + void __iomem *vfebase; + void __iomem *camifbase; + void *syncdata; + uint32_t register_total; + + struct resource *vfemem; + struct resource *camifmem; + struct resource *vfeio; + struct resource *camifio; + struct resource *vfeirq; + struct regulator *fs_vfe; + + 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; + struct platform_device *pdev; + struct clk *vfe_clk[5]; + struct clk *vfe_camif_clk[2]; + spinlock_t sd_notify_lock; + uint32_t hfr_mode; + uint32_t frame_skip_cnt; + uint32_t frame_skip_pattern; + uint32_t snapshot_frame_cnt; +}; + +enum VFE31_STATS_NUM { + STATS_AE_NUM, + STATS_AF_NUM, + STATS_AWB_NUM, + STATS_RS_NUM, + STATS_CS_NUM, + STATS_IHIST_NUM, + STATS_SKIN_NUM, + STATS_MAX_NUM, +}; + +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 0000000000000000000000000000000000000000..53fd6f119b0d76b44656e2fa054c95f34fcfb04d --- /dev/null +++ b/drivers/media/video/msm/msm_vfe32.c @@ -0,0 +1,4309 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_vfe32.h" + +atomic_t irq_cnt; + +#define VFE32_AXI_OFFSET 0x0050 +#define vfe32_get_ch_ping_addr(chn) \ + (msm_camera_io_r(vfe32_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe32_get_ch_pong_addr(chn) \ + (msm_camera_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_camera_io_w((addr), vfe32_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe32_put_ch_pong_addr(chn, addr) \ + (msm_camera_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 struct vfe32_ctrl_type *vfe32_ctrl; +static uint32_t vfe_clk_rate; + +struct vfe32_isr_queue_cmd { + struct list_head list; + uint32_t vfeInterruptStatus0; + uint32_t vfeInterruptStatus1; +}; + +static struct vfe32_cmd_type vfe32_cmd[] = { +/* 0*/ {VFE_CMD_DUMMY_0}, + {VFE_CMD_SET_CLK}, + {VFE_CMD_RESET}, + {VFE_CMD_START}, + {VFE_CMD_TEST_GEN_START}, +/* 5*/ {VFE_CMD_OPERATION_CFG, V32_OPERATION_CFG_LEN}, + {VFE_CMD_AXI_OUT_CFG, V32_AXI_OUT_LEN, V32_AXI_OUT_OFF, 0xFF}, + {VFE_CMD_CAMIF_CFG, V32_CAMIF_LEN, V32_CAMIF_OFF, 0xFF}, + {VFE_CMD_AXI_INPUT_CFG}, + {VFE_CMD_BLACK_LEVEL_CFG, V32_BLACK_LEVEL_LEN, + V32_BLACK_LEVEL_OFF, + 0xFF}, +/*10*/ {VFE_CMD_MESH_ROLL_OFF_CFG, V32_MESH_ROLL_OFF_CFG_LEN, + V32_MESH_ROLL_OFF_CFG_OFF, 0xFF}, + {VFE_CMD_DEMUX_CFG, V32_DEMUX_LEN, V32_DEMUX_OFF, 0xFF}, + {VFE_CMD_FOV_CFG, V32_FOV_LEN, V32_FOV_OFF, 0xFF}, + {VFE_CMD_MAIN_SCALER_CFG, V32_MAIN_SCALER_LEN, + V32_MAIN_SCALER_OFF, 0xFF}, + {VFE_CMD_WB_CFG, V32_WB_LEN, V32_WB_OFF, 0xFF}, +/*15*/ {VFE_CMD_COLOR_COR_CFG, V32_COLOR_COR_LEN, V32_COLOR_COR_OFF, 0xFF}, + {VFE_CMD_RGB_G_CFG, V32_RGB_G_LEN, V32_RGB_G_OFF, 0xFF}, + {VFE_CMD_LA_CFG, V32_LA_LEN, V32_LA_OFF, 0xFF }, + {VFE_CMD_CHROMA_EN_CFG, V32_CHROMA_EN_LEN, V32_CHROMA_EN_OFF, + 0xFF}, + {VFE_CMD_CHROMA_SUP_CFG, V32_CHROMA_SUP_LEN, V32_CHROMA_SUP_OFF, + 0xFF}, +/*20*/ {VFE_CMD_MCE_CFG, V32_MCE_LEN, V32_MCE_OFF, 0xFF}, + {VFE_CMD_SK_ENHAN_CFG, V32_SCE_LEN, V32_SCE_OFF, 0xFF}, + {VFE_CMD_ASF_CFG, V32_ASF_LEN, V32_ASF_OFF, 0xFF}, + {VFE_CMD_S2Y_CFG, V32_S2Y_LEN, V32_S2Y_OFF, 0xFF}, + {VFE_CMD_S2CbCr_CFG, V32_S2CbCr_LEN, V32_S2CbCr_OFF, 0xFF}, +/*25*/ {VFE_CMD_CHROMA_SUBS_CFG, V32_CHROMA_SUBS_LEN, V32_CHROMA_SUBS_OFF, + 0xFF}, + {VFE_CMD_OUT_CLAMP_CFG, V32_OUT_CLAMP_LEN, V32_OUT_CLAMP_OFF, + 0xFF}, + {VFE_CMD_FRAME_SKIP_CFG, V32_FRAME_SKIP_LEN, V32_FRAME_SKIP_OFF, + 0xFF}, + {VFE_CMD_DUMMY_1}, + {VFE_CMD_DUMMY_2}, +/*30*/ {VFE_CMD_DUMMY_3}, + {VFE_CMD_UPDATE}, + {VFE_CMD_BL_LVL_UPDATE, V32_BLACK_LEVEL_LEN, + V32_BLACK_LEVEL_OFF, 0xFF}, + {VFE_CMD_DEMUX_UPDATE, V32_DEMUX_LEN, V32_DEMUX_OFF, 0xFF}, + {VFE_CMD_FOV_UPDATE, V32_FOV_LEN, V32_FOV_OFF, 0xFF}, +/*35*/ {VFE_CMD_MAIN_SCALER_UPDATE, V32_MAIN_SCALER_LEN, V32_MAIN_SCALER_OFF, + 0xFF}, + {VFE_CMD_WB_UPDATE, V32_WB_LEN, V32_WB_OFF, 0xFF}, + {VFE_CMD_COLOR_COR_UPDATE, V32_COLOR_COR_LEN, V32_COLOR_COR_OFF, + 0xFF}, + {VFE_CMD_RGB_G_UPDATE, V32_RGB_G_LEN, V32_CHROMA_EN_OFF, 0xFF}, + {VFE_CMD_LA_UPDATE, V32_LA_LEN, V32_LA_OFF, 0xFF }, +/*40*/ {VFE_CMD_CHROMA_EN_UPDATE, V32_CHROMA_EN_LEN, V32_CHROMA_EN_OFF, + 0xFF}, + {VFE_CMD_CHROMA_SUP_UPDATE, V32_CHROMA_SUP_LEN, + V32_CHROMA_SUP_OFF, 0xFF}, + {VFE_CMD_MCE_UPDATE, V32_MCE_LEN, V32_MCE_OFF, 0xFF}, + {VFE_CMD_SK_ENHAN_UPDATE, V32_SCE_LEN, V32_SCE_OFF, 0xFF}, + {VFE_CMD_S2CbCr_UPDATE, V32_S2CbCr_LEN, V32_S2CbCr_OFF, 0xFF}, +/*45*/ {VFE_CMD_S2Y_UPDATE, V32_S2Y_LEN, V32_S2Y_OFF, 0xFF}, + {VFE_CMD_ASF_UPDATE, V32_ASF_UPDATE_LEN, V32_ASF_OFF, 0xFF}, + {VFE_CMD_FRAME_SKIP_UPDATE}, + {VFE_CMD_CAMIF_FRAME_UPDATE}, + {VFE_CMD_STATS_AF_UPDATE, V32_STATS_AF_LEN, V32_STATS_AF_OFF}, +/*50*/ {VFE_CMD_STATS_AE_UPDATE, V32_STATS_AE_LEN, V32_STATS_AE_OFF}, + {VFE_CMD_STATS_AWB_UPDATE, V32_STATS_AWB_LEN, + V32_STATS_AWB_OFF}, + {VFE_CMD_STATS_RS_UPDATE, V32_STATS_RS_LEN, V32_STATS_RS_OFF}, + {VFE_CMD_STATS_CS_UPDATE, V32_STATS_CS_LEN, V32_STATS_CS_OFF}, + {VFE_CMD_STATS_SKIN_UPDATE}, +/*55*/ {VFE_CMD_STATS_IHIST_UPDATE, V32_STATS_IHIST_LEN, V32_STATS_IHIST_OFF}, + {VFE_CMD_DUMMY_4}, + {VFE_CMD_EPOCH1_ACK}, + {VFE_CMD_EPOCH2_ACK}, + {VFE_CMD_START_RECORDING}, +/*60*/ {VFE_CMD_STOP_RECORDING}, + {VFE_CMD_DUMMY_5}, + {VFE_CMD_DUMMY_6}, + {VFE_CMD_CAPTURE, V32_CAPTURE_LEN, 0xFF}, + {VFE_CMD_DUMMY_7}, +/*65*/ {VFE_CMD_STOP}, + {VFE_CMD_GET_HW_VERSION, V32_GET_HW_VERSION_LEN, + V32_GET_HW_VERSION_OFF}, + {VFE_CMD_GET_FRAME_SKIP_COUNTS}, + {VFE_CMD_OUTPUT1_BUFFER_ENQ}, + {VFE_CMD_OUTPUT2_BUFFER_ENQ}, +/*70*/ {VFE_CMD_OUTPUT3_BUFFER_ENQ}, + {VFE_CMD_JPEG_OUT_BUF_ENQ}, + {VFE_CMD_RAW_OUT_BUF_ENQ}, + {VFE_CMD_RAW_IN_BUF_ENQ}, + {VFE_CMD_STATS_AF_ENQ}, +/*75*/ {VFE_CMD_STATS_AE_ENQ}, + {VFE_CMD_STATS_AWB_ENQ}, + {VFE_CMD_STATS_RS_ENQ}, + {VFE_CMD_STATS_CS_ENQ}, + {VFE_CMD_STATS_SKIN_ENQ}, +/*80*/ {VFE_CMD_STATS_IHIST_ENQ}, + {VFE_CMD_DUMMY_8}, + {VFE_CMD_JPEG_ENC_CFG}, + {VFE_CMD_DUMMY_9}, + {VFE_CMD_STATS_AF_START, V32_STATS_AF_LEN, V32_STATS_AF_OFF}, +/*85*/ {VFE_CMD_STATS_AF_STOP}, + {VFE_CMD_STATS_AE_START, V32_STATS_AE_LEN, V32_STATS_AE_OFF}, + {VFE_CMD_STATS_AE_STOP}, + {VFE_CMD_STATS_AWB_START, V32_STATS_AWB_LEN, V32_STATS_AWB_OFF}, + {VFE_CMD_STATS_AWB_STOP}, +/*90*/ {VFE_CMD_STATS_RS_START, V32_STATS_RS_LEN, V32_STATS_RS_OFF}, + {VFE_CMD_STATS_RS_STOP}, + {VFE_CMD_STATS_CS_START, V32_STATS_CS_LEN, V32_STATS_CS_OFF}, + {VFE_CMD_STATS_CS_STOP}, + {VFE_CMD_STATS_SKIN_START}, +/*95*/ {VFE_CMD_STATS_SKIN_STOP}, + {VFE_CMD_STATS_IHIST_START, + V32_STATS_IHIST_LEN, V32_STATS_IHIST_OFF}, + {VFE_CMD_STATS_IHIST_STOP}, + {VFE_CMD_DUMMY_10}, + {VFE_CMD_SYNC_TIMER_SETTING, V32_SYNC_TIMER_LEN, + V32_SYNC_TIMER_OFF}, +/*100*/ {VFE_CMD_ASYNC_TIMER_SETTING, V32_ASYNC_TIMER_LEN, V32_ASYNC_TIMER_OFF}, + {VFE_CMD_LIVESHOT}, + {VFE_CMD_LA_SETUP}, + {VFE_CMD_LINEARIZATION_CFG, V32_LINEARIZATION_LEN1, + V32_LINEARIZATION_OFF1}, + {VFE_CMD_DEMOSAICV3}, +/*105*/ {VFE_CMD_DEMOSAICV3_ABCC_CFG}, + {VFE_CMD_DEMOSAICV3_DBCC_CFG, V32_DEMOSAICV3_DBCC_LEN, + V32_DEMOSAICV3_DBCC_OFF}, + {VFE_CMD_DEMOSAICV3_DBPC_CFG}, + {VFE_CMD_DEMOSAICV3_ABF_CFG, V32_DEMOSAICV3_ABF_LEN, + V32_DEMOSAICV3_ABF_OFF}, + {VFE_CMD_DEMOSAICV3_ABCC_UPDATE}, +/*110*/ {VFE_CMD_DEMOSAICV3_DBCC_UPDATE, V32_DEMOSAICV3_DBCC_LEN, + V32_DEMOSAICV3_DBCC_OFF}, + {VFE_CMD_DEMOSAICV3_DBPC_UPDATE}, + {VFE_CMD_XBAR_CFG}, + {VFE_CMD_MODULE_CFG, V32_MODULE_CFG_LEN, V32_MODULE_CFG_OFF}, + {VFE_CMD_ZSL}, +/*115*/ {VFE_CMD_LINEARIZATION_UPDATE, V32_LINEARIZATION_LEN1, + V32_LINEARIZATION_OFF1}, + {VFE_CMD_DEMOSAICV3_ABF_UPDATE, V32_DEMOSAICV3_ABF_LEN, + V32_DEMOSAICV3_ABF_OFF}, + {VFE_CMD_CLF_CFG, V32_CLF_CFG_LEN, V32_CLF_CFG_OFF}, + {VFE_CMD_CLF_LUMA_UPDATE, V32_CLF_LUMA_UPDATE_LEN, + V32_CLF_LUMA_UPDATE_OFF}, + {VFE_CMD_CLF_CHROMA_UPDATE, V32_CLF_CHROMA_UPDATE_LEN, + V32_CLF_CHROMA_UPDATE_OFF}, +/*120*/ {VFE_CMD_PCA_ROLL_OFF_CFG}, + {VFE_CMD_PCA_ROLL_OFF_UPDATE}, + {VFE_CMD_GET_REG_DUMP}, + {VFE_CMD_GET_LINEARIZATON_TABLE}, + {VFE_CMD_GET_MESH_ROLLOFF_TABLE}, +/*125*/ {VFE_CMD_GET_PCA_ROLLOFF_TABLE}, + {VFE_CMD_GET_RGB_G_TABLE}, + {VFE_CMD_GET_LA_TABLE}, + {VFE_CMD_DEMOSAICV3_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_CFG", + "DEMOSAICV3", + "DEMOSAICV3_ABCC_CFG", /* 105 */ + "DEMOSAICV3_DBCC_CFG", + "DEMOSAICV3_DBPC_CFG", + "DEMOSAICV3_ABF_CFG", + "DEMOSAICV3_ABCC_UPDATE", + "DEMOSAICV3_DBCC_UPDATE", /* 110 */ + "DEMOSAICV3_DBPC_UPDATE", + "XBAR_CFG", + "EZTUNE_CFG", + "V32_ZSL", + "LINEARIZATION_UPDATE", /*115*/ + "DEMOSAICV3_ABF_UPDATE", + "CLF_CFG", + "CLF_LUMA_UPDATE", + "CLF_CHROMA_UPDATE", + "PCA_ROLL_OFF_CFG", /*120*/ + "PCA_ROLL_OFF_UPDATE", + "GET_REG_DUMP", + "GET_LINEARIZATON_TABLE", + "GET_MESH_ROLLOFF_TABLE", + "GET_PCA_ROLLOFF_TABLE", /*125*/ + "GET_RGB_G_TABLE", + "GET_LA_TABLE", + "DEMOSAICV3_UPDATE", +}; + +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_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_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_camera_io_w(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + + /* axi halt command. */ + msm_camera_io_w(AXI_HALT, + vfe32_ctrl->vfebase + VFE_AXI_CMD); + wmb(); + while (axiBusyFlag) { + if (msm_camera_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_camera_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_camera_io_w(0xf0000000, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_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_camera_io_w_mb(VFE_RESET_UPON_STOP_CMD, + vfe32_ctrl->vfebase + VFE_GLOBAL_RESET); +} + +static void vfe32_subdev_notify(int id, int path) +{ + struct msm_vfe_resp rp; + unsigned long flags = 0; + spin_lock_irqsave(&vfe32_ctrl->sd_notify_lock, flags); + CDBG("vfe32_subdev_notify : msgId = %d\n", id); + memset(&rp, 0, sizeof(struct msm_vfe_resp)); + rp.evt_msg.type = MSM_CAMERA_MSG; + rp.evt_msg.msg_id = path; + rp.type = id; + v4l2_subdev_notify(&vfe32_ctrl->subdev, NOTIFY_VFE_BUF_EVT, &rp); + spin_unlock_irqrestore(&vfe32_ctrl->sd_notify_lock, flags); +} + +static int vfe32_config_axi(int mode, uint32_t *ao) +{ + uint32_t *ch_info; + uint32_t *axi_cfg = ao+V32_AXI_BUS_FMT_OFF; + + /* Update the corresponding write masters for each output*/ + ch_info = axi_cfg + 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++; + + switch (mode) { + case OUTPUT_PRIM: + vfe32_ctrl->outpath.output_mode = + VFE32_OUTPUT_MODE_PRIMARY; + break; + case OUTPUT_PRIM_ALL_CHNLS: + vfe32_ctrl->outpath.output_mode = + VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS; + break; + case OUTPUT_PRIM|OUTPUT_SEC: + vfe32_ctrl->outpath.output_mode = + VFE32_OUTPUT_MODE_PRIMARY; + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_SECONDARY; + break; + case OUTPUT_PRIM|OUTPUT_SEC_ALL_CHNLS: + vfe32_ctrl->outpath.output_mode = + VFE32_OUTPUT_MODE_PRIMARY; + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_SECONDARY_ALL_CHNLS; + break; + case OUTPUT_PRIM_ALL_CHNLS|OUTPUT_SEC: + vfe32_ctrl->outpath.output_mode = + VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS; + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_SECONDARY; + break; + default: + pr_err("%s Invalid AXI mode %d ", __func__, mode); + return -EINVAL; + } + msm_camera_io_w(*ao, vfe32_ctrl->vfebase + + VFE_BUS_IO_FORMAT_CFG); + msm_camera_io_memcpy(vfe32_ctrl->vfebase + + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].offset, axi_cfg, + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length - V32_AXI_CH_INF_LEN + - V32_AXI_BUS_FMT_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->recording_state = VFE_STATE_IDLE; + vfe32_ctrl->liveshot_state = VFE_STATE_IDLE; + + 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)); + + vfe32_ctrl->frame_skip_cnt = 31; + vfe32_ctrl->frame_skip_pattern = 0xffffffff; + vfe32_ctrl->snapshot_frame_cnt = 0; +} + +static void vfe32_reset(void) +{ + vfe32_reset_internal_variables(); + /* disable all interrupts. vfeImaskLocal is also reset to 0 + * to begin with. */ + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_camera_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_camera_io_w(VFE_CLEAR_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_IRQ_CMD); + + /* enable reset_ack interrupt. */ + msm_camera_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_camera_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); + vfe32_ctrl->hfr_mode = *(++p); + + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_CFG); + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_PIXEL_IF_CFG); + if (msm_camera_io_r(vfe32_ctrl->vfebase + V32_GET_HW_VERSION_OFF) == + VFE33_HW_NUMBER) { + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_RDI0_CFG); + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_RDI1_CFG); + } else { + ++p; + ++p; + } + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_REALIGN_BUF); + msm_camera_io_w(*(++p), vfe32_ctrl->vfebase + VFE_CHROMA_UP); + msm_camera_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_camera_io_w(addr, + vfe32_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe32_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe32_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe32_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe32_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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_camera_io_w(addr, + vfe32_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PING_ADDR); + addr = ptr[1]; + msm_camera_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) +{ + uint32_t irq_mask = 0x00E00021; + 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); + if (vfe32_ctrl->stats_comp) + irq_mask |= VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK; + else + irq_mask |= 0x000FE000; + + msm_camera_io_w(irq_mask, vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_camera_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_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + + + atomic_set(&vfe32_ctrl->vstate, 1); +} + +static int vfe32_start_recording(struct msm_cam_media_controller *pmctl) +{ + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_VIDEO); + vfe32_ctrl->recording_state = VFE_STATE_START_REQUESTED; + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + return 0; +} + +static int vfe32_stop_recording(struct msm_cam_media_controller *pmctl) +{ + vfe32_ctrl->recording_state = VFE_STATE_STOP_REQUESTED; + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_PREVIEW); + return 0; +} + +static void vfe32_start_liveshot(struct msm_cam_media_controller *pmctl) +{ + /* Hardcode 1 live snapshot for now. */ + vfe32_ctrl->outpath.out0.capture_cnt = 1; + vfe32_ctrl->vfe_capture_count = vfe32_ctrl->outpath.out0.capture_cnt; + + vfe32_ctrl->liveshot_state = VFE_STATE_START_REQUESTED; + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); +} + +static int vfe32_zsl(struct msm_cam_media_controller *pmctl) +{ + uint32_t irq_comp_mask = 0; + /* capture command is valid for both idle and active state. */ + irq_comp_mask = + msm_camera_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + CDBG("%s:op mode %d O/P Mode %d\n", __func__, + vfe32_ctrl->operation_mode, vfe32_ctrl->outpath.output_mode); + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= ((0x1 << (vfe32_ctrl->outpath.out0.ch0)) | + (0x1 << (vfe32_ctrl->outpath.out0.ch1))); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + irq_comp_mask |= ((0x1 << (vfe32_ctrl->outpath.out0.ch0)) | + (0x1 << (vfe32_ctrl->outpath.out0.ch1)) | + (0x1 << (vfe32_ctrl->outpath.out0.ch2))); + } + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_SECONDARY) { + irq_comp_mask |= ((0x1 << (vfe32_ctrl->outpath.out1.ch0 + 8)) | + (0x1 << (vfe32_ctrl->outpath.out1.ch1 + 8))); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + irq_comp_mask |= ((0x1 << (vfe32_ctrl->outpath.out1.ch0 + 8)) | + (0x1 << (vfe32_ctrl->outpath.out1.ch1 + 8)) | + (0x1 << (vfe32_ctrl->outpath.out1.ch2 + 8))); + } + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch2]); + } + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch2]); + } + + msm_camera_io_w(irq_comp_mask, vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + vfe32_start_common(); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_ZSL); + + msm_camera_io_w(1, vfe32_ctrl->vfebase + 0x18C); + msm_camera_io_w(1, vfe32_ctrl->vfebase + 0x188); + return 0; +} +static int vfe32_capture_raw( + struct msm_cam_media_controller *pmctl, + uint32_t num_frames_capture) +{ + uint32_t irq_comp_mask = 0; + + vfe32_ctrl->outpath.out0.capture_cnt = num_frames_capture; + vfe32_ctrl->vfe_capture_count = num_frames_capture; + + irq_comp_mask = + msm_camera_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= (0x1 << (vfe32_ctrl->outpath.out0.ch0)); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + } + + msm_camera_io_w(irq_comp_mask, vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_CAPTURE); + vfe32_start_common(); + return 0; +} + +static int vfe32_capture( + struct msm_cam_media_controller *pmctl, + uint32_t num_frames_capture) +{ + uint32_t irq_comp_mask = 0; + + /* 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_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) { + vfe32_ctrl->outpath.out0.capture_cnt = + num_frames_capture; + } + + vfe32_ctrl->vfe_capture_count = num_frames_capture; + irq_comp_mask = msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe32_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN) { + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= (0x1 << vfe32_ctrl->outpath.out0.ch0 | + 0x1 << vfe32_ctrl->outpath.out0.ch1); + } + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY) { + 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_PRIMARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + } + } + + vfe32_ctrl->vfe_capture_count = num_frames_capture; + + msm_camera_io_w(irq_comp_mask, vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_camera_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_CAPTURE); + + vfe32_start_common(); + /* for debug */ + msm_camera_io_w(1, vfe32_ctrl->vfebase + 0x18C); + msm_camera_io_w(1, vfe32_ctrl->vfebase + 0x188); + return 0; +} + +static int vfe32_start(struct msm_cam_media_controller *pmctl) +{ + uint32_t irq_comp_mask = 0; + + irq_comp_mask = + msm_camera_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PRIMARY) { + irq_comp_mask |= (0x1 << vfe32_ctrl->outpath.out0.ch0 | + 0x1 << vfe32_ctrl->outpath.out0.ch1); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + irq_comp_mask |= (0x1 << vfe32_ctrl->outpath.out0.ch0 | + 0x1 << vfe32_ctrl->outpath.out0.ch1 | + 0x1 << vfe32_ctrl->outpath.out0.ch2); + } + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_SECONDARY) { + irq_comp_mask |= (0x1 << (vfe32_ctrl->outpath.out1.ch0 + 8) | + 0x1 << (vfe32_ctrl->outpath.out1.ch1 + 8)); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + irq_comp_mask |= (0x1 << (vfe32_ctrl->outpath.out1.ch0 + 8) | + 0x1 << (vfe32_ctrl->outpath.out1.ch1 + 8) | + 0x1 << (vfe32_ctrl->outpath.out1.ch2 + 8)); + } + msm_camera_io_w(irq_comp_mask, vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + switch (vfe32_ctrl->operation_mode) { + case VFE_OUTPUTS_PREVIEW: + case VFE_OUTPUTS_PREVIEW_AND_VIDEO: + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch2]); + } + break; + default: + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + } else if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY_ALL_CHNLS) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch2]); + } + break; + } + + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_PREVIEW); + vfe32_start_common(); + return 0; +} + +static void vfe32_update(void) +{ + unsigned long flags; + uint32_t value = 0; + if (vfe32_ctrl->update_linear) { + if (!msm_camera_io_r( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1)) + msm_camera_io_w(1, + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1); + else + msm_camera_io_w(0, + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1); + vfe32_ctrl->update_linear = false; + } + + if (vfe32_ctrl->update_rolloff) { + value = msm_camera_io_r(vfe32_ctrl->vfebase + + V33_PCA_ROLL_OFF_CFG_OFF1); + value ^= V33_PCA_ROLL_OFF_LUT_BANK_SEL_MASK; + msm_camera_io_w(value, vfe32_ctrl->vfebase + + V33_PCA_ROLL_OFF_CFG_OFF1); + vfe32_ctrl->update_rolloff = false; + } + + if (vfe32_ctrl->update_la) { + if (!msm_camera_io_r(vfe32_ctrl->vfebase + V32_LA_OFF)) + msm_camera_io_w(1, + vfe32_ctrl->vfebase + V32_LA_OFF); + else + msm_camera_io_w(0, + vfe32_ctrl->vfebase + V32_LA_OFF); + vfe32_ctrl->update_la = false; + } + + if (vfe32_ctrl->update_gamma) { + value = msm_camera_io_r(vfe32_ctrl->vfebase + V32_RGB_G_OFF); + value ^= V32_GAMMA_LUT_BANK_SEL_MASK; + msm_camera_io_w(value, vfe32_ctrl->vfebase + V32_RGB_G_OFF); + vfe32_ctrl->update_gamma = false; + } + + 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_camera_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_camera_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_camera_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF); + /* Sync Timer Line Start */ + value = *tbl++; + msm_camera_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF + + 4 + ((vfe32_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Start */ + value = *tbl++; + msm_camera_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF + + 8 + ((vfe32_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Duration */ + value = *tbl++; + val = vfe_clk_rate / 10000; + val = 10000000 / val; + val = value * 10000 / val; + CDBG("%s: Pixel Clk Cycles!!! %d\n", __func__, val); + msm_camera_io_w(val, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF + + 12 + ((vfe32_ctrl->sync_timer_number) * 12)); + /* Timer0 Active High/LOW */ + value = *tbl++; + msm_camera_io_w(value, + vfe32_ctrl->vfebase + V32_SYNC_TIMER_POLARITY_OFF); + /* Selects sync timer 0 output to drive onto timer1 port */ + value = 0; + msm_camera_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; + CDBG("%s: banksel = %d\n", __func__, bankSel); + + msm_camera_io_w(value, vfe32_ctrl->vfebase + VFE_DMI_CFG); + /* by default, always starts with offset 0.*/ + msm_camera_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 (i = 0 ; i < (VFE32_GAMMA_NUM_ENTRIES/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_camera_io_w((value1), + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_camera_io_w((value2), + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); +} + +static void vfe32_read_gamma_cfg(enum VFE32_DMI_RAM_SEL channel_sel, + uint32_t *tbl) +{ + int i; + vfe32_program_dmi_cfg(channel_sel); + CDBG("%s: Gamma table channel: %d\n", __func__, channel_sel); + for (i = 0 ; i < VFE32_GAMMA_NUM_ENTRIES ; i++) { + *tbl = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *tbl); + tbl++; + } + 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 (i = 0 ; i < (VFE32_LA_TABLE_LENGTH/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_camera_io_w((value1), + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_camera_io_w((value2), + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); +} + +static struct vfe32_output_ch *vfe32_get_ch(int path) +{ + struct vfe32_output_ch *ch = NULL; + + if (path == VFE_MSG_OUTPUT_PRIMARY) + ch = &vfe32_ctrl->outpath.out0; + else if (path == VFE_MSG_OUTPUT_SECONDARY) + ch = &vfe32_ctrl->outpath.out1; + else + pr_err("%s: Invalid path %d\n", __func__, + path); + + BUG_ON(ch == NULL); + return ch; +} +static struct msm_free_buf *vfe32_check_free_buffer(int id, int path) +{ + struct vfe32_output_ch *outch = NULL; + struct msm_free_buf *b = NULL; + vfe32_subdev_notify(id, path); + outch = vfe32_get_ch(path); + if (outch->free_buf.ch_paddr[0]) + b = &outch->free_buf; + return b; +} +static int vfe32_configure_pingpong_buffers(int id, int path) +{ + struct vfe32_output_ch *outch = NULL; + int rc = 0; + vfe32_subdev_notify(id, path); + outch = vfe32_get_ch(path); + if (outch->ping.ch_paddr[0] && outch->pong.ch_paddr[0]) { + /* Configure Preview Ping Pong */ + pr_info("%s Configure ping/pong address for %d", + __func__, path); + vfe32_put_ch_ping_addr(outch->ch0, + outch->ping.ch_paddr[0]); + vfe32_put_ch_pong_addr(outch->ch0, + outch->pong.ch_paddr[0]); + + if (vfe32_ctrl->operation_mode != + VFE_OUTPUTS_RAW) { + vfe32_put_ch_ping_addr(outch->ch1, + outch->ping.ch_paddr[1]); + vfe32_put_ch_pong_addr(outch->ch1, + outch->pong.ch_paddr[1]); + } + + if (outch->ping.num_planes > 2) + vfe32_put_ch_ping_addr(outch->ch2, + outch->ping.ch_paddr[2]); + if (outch->pong.num_planes > 2) + vfe32_put_ch_pong_addr(outch->ch2, + outch->pong.ch_paddr[2]); + + /* avoid stale info */ + memset(&outch->ping, 0, sizeof(struct msm_free_buf)); + memset(&outch->pong, 0, sizeof(struct msm_free_buf)); + } else { + pr_err("%s ping/pong addr is null!!", __func__); + rc = -EINVAL; + } + return rc; +} + +static void vfe32_write_linear_cfg(enum VFE32_DMI_RAM_SEL channel_sel, + const uint32_t *tbl) +{ + uint32_t i; + + vfe32_program_dmi_cfg(channel_sel); + /* for loop for configuring LUT. */ + for (i = 0 ; i < VFE32_LINEARIZATON_TABLE_LENGTH ; i++) { + msm_camera_io_w(*tbl, vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + tbl++; + } + CDBG("done writing to linearization table\n"); + vfe32_program_dmi_cfg(NO_MEM_SELECTED); +} + +static void vfe32_send_isp_msg( + struct vfe32_ctrl_type *vctrl, + uint32_t isp_msg_id) +{ + struct isp_msg_event isp_msg_evt; + + isp_msg_evt.msg_id = isp_msg_id; + isp_msg_evt.sof_count = vfe32_ctrl->vfeFrameId; + v4l2_subdev_notify(&vctrl->subdev, + NOTIFY_ISP_MSG_EVT, + (void *)&isp_msg_evt); +} + +static int vfe32_proc_general( + struct msm_cam_media_controller *pmctl, + struct msm_isp_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 temp1 = 0, temp2 = 0; + + CDBG("vfe32_proc_general: cmdID = %s, length = %d\n", + vfe32_general_cmd[cmd->id], cmd->length); + switch (cmd->id) { + case VFE_CMD_RESET: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + vfe32_reset(); + break; + case VFE_CMD_START: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + if ((vfe32_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) || + (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW)) + /* Configure primary channel */ + rc = vfe32_configure_pingpong_buffers( + VFE_MSG_V32_START, VFE_MSG_OUTPUT_PRIMARY); + else + /* Configure secondary channel */ + rc = vfe32_configure_pingpong_buffers( + VFE_MSG_V32_START, VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe32_start(pmctl); + break; + case VFE_CMD_UPDATE: + vfe32_update(); + break; + case VFE_CMD_CAPTURE_RAW: + pr_info("%s: cmdID = VFE_CMD_CAPTURE_RAW\n", __func__); + if (copy_from_user(&snapshot_cnt, (void __user *)(cmd->value), + sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + rc = vfe32_configure_pingpong_buffers(VFE_MSG_V32_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for snapshot", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe32_capture_raw(pmctl, snapshot_cnt); + break; + case VFE_CMD_CAPTURE: + if (copy_from_user(&snapshot_cnt, (void __user *)(cmd->value), + sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + + if (vfe32_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) { + if (snapshot_cnt != 1) { + pr_err("only support 1 inline snapshot\n"); + rc = -EINVAL; + goto proc_general_done; + } + /* Configure primary channel for JPEG */ + rc = vfe32_configure_pingpong_buffers( + VFE_MSG_V32_JPEG_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + } else { + /* Configure primary channel */ + rc = vfe32_configure_pingpong_buffers( + VFE_MSG_V32_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + } + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for primary output", __func__); + rc = -EINVAL; + goto proc_general_done; + } + /* Configure secondary channel */ + rc = vfe32_configure_pingpong_buffers(VFE_MSG_V32_CAPTURE, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for secondary output", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe32_capture(pmctl, snapshot_cnt); + break; + case VFE_CMD_START_RECORDING: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) + rc = vfe32_configure_pingpong_buffers( + VFE_MSG_V32_START_RECORDING, + VFE_MSG_OUTPUT_SECONDARY); + else if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_VIDEO_AND_PREVIEW) + rc = vfe32_configure_pingpong_buffers( + VFE_MSG_V32_START_RECORDING, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for video", __func__); + rc = -EINVAL; + goto proc_general_done; + } + rc = vfe32_start_recording(pmctl); + break; + case VFE_CMD_STOP_RECORDING: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + rc = vfe32_stop_recording(pmctl); + break; + case VFE_CMD_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 VFE_CMD_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_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AE_BG_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + case VFE_CMD_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_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AF_BF_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + case VFE_CMD_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_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AWB_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case VFE_CMD_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_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= IHIST_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + + case VFE_CMD_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; + } + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case VFE_CMD_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; + } + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case VFE_CMD_MCE_UPDATE: + case VFE_CMD_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_camera_io_r(vfe32_ctrl->vfebase + + V32_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_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_CHROMA_SUP_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + + V32_CHROMA_SUP_OFF + 8); + new_val = *cmdp_local; + old_val &= MCE_Q_K_MASK; + new_val = new_val | old_val; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_CHROMA_SUP_OFF + 8, + &new_val, 4); + cmdp_local += 1; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, (vfe32_cmd[cmd->id].length)); + } + break; + case VFE_CMD_CHROMA_SUP_UPDATE: + case VFE_CMD_CHROMA_SUP_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_camera_io_memcpy(vfe32_ctrl->vfebase + V32_CHROMA_SUP_OFF, + cmdp_local, 4); + + cmdp_local += 1; + new_val = *cmdp_local; + /* Incrementing with 4 so as to point to the 2nd Register as + * the 2nd register has the mce_enable bit + */ + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + + V32_CHROMA_SUP_OFF + 4); + old_val &= ~MCE_EN_MASK; + new_val = new_val | old_val; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_CHROMA_SUP_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + + V32_CHROMA_SUP_OFF + 8); + new_val = *cmdp_local; + old_val &= ~MCE_Q_K_MASK; + new_val = new_val | old_val; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_CHROMA_SUP_OFF + 8, + &new_val, 4); + } + break; + case VFE_CMD_BLACK_LEVEL_CFG: + rc = -EFAULT; + goto proc_general_done; + + case VFE_CMD_MESH_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_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, 16); + cmdp_local += 4; + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK0); + /* for loop for extrcting init table. */ + for (i = 0; i < (V32_MESH_ROLL_OFF_INIT_TABLE_SIZE * 2); i++) { + msm_camera_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_camera_io_w(V32_MESH_ROLL_OFF_DELTA_TABLE_OFFSET, + vfe32_ctrl->vfebase + VFE_DMI_ADDR); + /* for loop for extracting delta table. */ + for (i = 0; i < (V32_MESH_ROLL_OFF_DELTA_TABLE_SIZE * 2); i++) { + msm_camera_io_w(*cmdp_local, + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + cmdp_local++; + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + } + break; + + case VFE_CMD_GET_MESH_ROLLOFF_TABLE: + temp1 = sizeof(uint32_t) * ((V32_MESH_ROLL_OFF_INIT_TABLE_SIZE * + 2) + (V32_MESH_ROLL_OFF_DELTA_TABLE_SIZE * 2)); + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK0); + CDBG("%s: Mesh Rolloff init Table\n", __func__); + for (i = 0; i < (V32_MESH_ROLL_OFF_INIT_TABLE_SIZE * 2); i++) { + *cmdp_local = + msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *cmdp_local); + cmdp_local++; + } + msm_camera_io_w(V32_MESH_ROLL_OFF_DELTA_TABLE_OFFSET, + vfe32_ctrl->vfebase + VFE_DMI_ADDR); + CDBG("%s: Mesh Rolloff Delta Table\n", __func__); + for (i = 0; i < (V32_MESH_ROLL_OFF_DELTA_TABLE_SIZE * 2); i++) { + *cmdp_local = + msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *cmdp_local); + cmdp_local++; + } + CDBG("done reading delta table\n"); + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_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; + } + cmdp_local = cmdp; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, (vfe32_cmd[cmd->id].length)); + + cmdp_local += 1; + vfe32_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0, cmdp_local); + break; + + case VFE_CMD_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; + } + + cmdp_local = cmdp + 1; + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + V32_LA_OFF); + if (old_val != 0x0) + vfe32_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0, + cmdp_local); + else + vfe32_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK1, + cmdp_local); + } + vfe32_ctrl->update_la = true; + break; + + case VFE_CMD_GET_LA_TABLE: + temp1 = sizeof(uint32_t) * VFE32_LA_TABLE_LENGTH / 2; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + if (msm_camera_io_r(vfe32_ctrl->vfebase + V32_LA_OFF)) + vfe32_program_dmi_cfg(LUMA_ADAPT_LUT_RAM_BANK1); + else + vfe32_program_dmi_cfg(LUMA_ADAPT_LUT_RAM_BANK0); + for (i = 0 ; i < (VFE32_LA_TABLE_LENGTH / 2) ; i++) { + *cmdp_local = + msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + *cmdp_local |= (msm_camera_io_r(vfe32_ctrl->vfebase + + VFE_DMI_DATA_LO)) << 16; + cmdp_local++; + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_SK_ENHAN_CFG: + case VFE_CMD_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_camera_io_memcpy(vfe32_ctrl->vfebase + V32_SCE_OFF, + cmdp, V32_SCE_LEN); + } + break; + + case VFE_CMD_LIVESHOT: + /* Configure primary channel */ + rc = vfe32_configure_pingpong_buffers(VFE_MSG_V32_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for primary output", __func__); + rc = -EINVAL; + goto proc_general_done; + } + vfe32_start_liveshot(pmctl); + break; + + case VFE_CMD_LINEARIZATION_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_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1, + cmdp_local, V32_LINEARIZATION_LEN1); + cmdp_local += 4; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF2, + cmdp_local, V32_LINEARIZATION_LEN2); + + cmdp_local = cmdp + 17; + vfe32_write_linear_cfg(BLACK_LUT_RAM_BANK0, cmdp_local); + break; + + case VFE_CMD_LINEARIZATION_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; + cmdp_local++; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1 + 4, + cmdp_local, (V32_LINEARIZATION_LEN1 - 4)); + cmdp_local += 3; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF2, + cmdp_local, V32_LINEARIZATION_LEN2); + cmdp_local = cmdp + 17; + /*extracting the bank select*/ + old_val = msm_camera_io_r( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1); + + if (old_val != 0x0) + vfe32_write_linear_cfg(BLACK_LUT_RAM_BANK0, cmdp_local); + else + vfe32_write_linear_cfg(BLACK_LUT_RAM_BANK1, cmdp_local); + vfe32_ctrl->update_linear = true; + break; + + case VFE_CMD_GET_LINEARIZATON_TABLE: + temp1 = sizeof(uint32_t) * VFE32_LINEARIZATON_TABLE_LENGTH; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + if (msm_camera_io_r( + vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1)) + vfe32_program_dmi_cfg(BLACK_LUT_RAM_BANK1); + else + vfe32_program_dmi_cfg(BLACK_LUT_RAM_BANK0); + CDBG("%s: Linearization Table\n", __func__); + for (i = 0 ; i < VFE32_LINEARIZATON_TABLE_LENGTH ; i++) { + *cmdp_local = msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + CDBG("%s: %08x\n", __func__, *cmdp_local); + cmdp_local++; + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_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; + new_val = *cmdp_local; + + old_val = msm_camera_io_r( + vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= DEMOSAIC_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF, + cmdp_local, V32_DEMOSAICV3_0_LEN); + cmdp_local += 1; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_1_OFF, + cmdp_local, V32_DEMOSAICV3_1_LEN); + break; + + case VFE_CMD_DEMOSAICV3_UPDATE: + if (cmd->length != + V32_DEMOSAICV3_0_LEN * V32_DEMOSAICV3_UP_REG_CNT) { + 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; + new_val = *cmdp_local; + + old_val = msm_camera_io_r( + vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= DEMOSAIC_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF, + cmdp_local, V32_DEMOSAICV3_0_LEN); + /* As the address space is not contiguous increment by 2 + * before copying to next address space */ + cmdp_local += 1; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_1_OFF, + cmdp_local, 2 * V32_DEMOSAICV3_0_LEN); + /* As the address space is not contiguous increment by 2 + * before copying to next address space */ + cmdp_local += 2; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_2_OFF, + cmdp_local, 2 * V32_DEMOSAICV3_0_LEN); + break; + + case VFE_CMD_DEMOSAICV3_ABCC_CFG: + rc = -EFAULT; + break; + + case VFE_CMD_DEMOSAICV3_ABF_UPDATE:/* 116 ABF update */ + case VFE_CMD_DEMOSAICV3_ABF_CFG: { /* 108 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_camera_io_r( + vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= ABF_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF, + cmdp_local, 4); + + cmdp_local += 1; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, (vfe32_cmd[cmd->id].length)); + } + break; + + case VFE_CMD_DEMOSAICV3_DBCC_CFG: + case VFE_CMD_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_camera_io_r( + vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= DBCC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF, + cmdp_local, 4); + cmdp_local += 1; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, (vfe32_cmd[cmd->id].length)); + break; + + case VFE_CMD_DEMOSAICV3_DBPC_CFG: + case VFE_CMD_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_camera_io_r( + vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= DBPC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_0_OFF, + cmdp_local, V32_DEMOSAICV3_LEN); + cmdp_local += 1; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + cmdp_local += 1; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF0, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + cmdp_local += 1; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF1, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + cmdp_local += 1; + msm_camera_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF2, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + break; + + case VFE_CMD_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_camera_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 VFE_CMD_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_camera_io_r(vfe32_ctrl->vfebase + V32_RGB_G_OFF); + cmdp += 1; + if (old_val != 0x0) { + 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); + } else { + 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); + } + } + vfe32_ctrl->update_gamma = TRUE; + cmdp -= 1; + break; + + case VFE_CMD_GET_RGB_G_TABLE: + temp1 = sizeof(uint32_t) * VFE32_GAMMA_NUM_ENTRIES * 3; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + V32_RGB_G_OFF); + temp2 = old_val ? RGBLUT_RAM_CH0_BANK1 : + RGBLUT_RAM_CH0_BANK0; + for (i = 0; i < 3; i++) { + vfe32_read_gamma_cfg(temp2, + cmdp_local + (VFE32_GAMMA_NUM_ENTRIES * i)); + temp2 += 2; + } + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + + case VFE_CMD_STATS_AWB_STOP: { + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AWB_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case VFE_CMD_STATS_AE_STOP: { + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AE_BG_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case VFE_CMD_STATS_AF_STOP: { + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AF_BF_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case VFE_CMD_STATS_IHIST_STOP: { + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~IHIST_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case VFE_CMD_STATS_RS_STOP: { + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~RS_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case VFE_CMD_STATS_CS_STOP: { + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~CS_ENABLE_MASK; + msm_camera_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case VFE_CMD_STOP: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + vfe32_stop(); + break; + + case VFE_CMD_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 VFE_CMD_MODULE_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_camera_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= STATS_ENABLE_MASK; + *cmdp |= old_val; + + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case VFE_CMD_ZSL: + rc = vfe32_configure_pingpong_buffers(VFE_MSG_V32_START, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) + goto proc_general_done; + rc = vfe32_configure_pingpong_buffers(VFE_MSG_V32_START, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) + goto proc_general_done; + + rc = vfe32_zsl(pmctl); + break; + + case VFE_CMD_ASF_CFG: + case VFE_CMD_ASF_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_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + cmdp_local = cmdp + V32_ASF_LEN/4; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V32_ASF_SPECIAL_EFX_CFG_OFF, + cmdp_local, V32_ASF_SPECIAL_EFX_CFG_LEN); + break; + + case VFE_CMD_PCA_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; + + temp1 = *cmdp_local; + cmdp_local++; + + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V33_PCA_ROLL_OFF_CFG_OFF1, + cmdp_local, V33_PCA_ROLL_OFF_CFG_LEN1); + cmdp_local += 4; + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + V33_PCA_ROLL_OFF_CFG_OFF2, + cmdp_local, V33_PCA_ROLL_OFF_CFG_LEN2); + + cmdp_local += 3; + CDBG("%s: start writing RollOff Ram0 table\n", __func__); + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK0); + msm_camera_io_w(temp1, vfe32_ctrl->vfebase + VFE_DMI_ADDR); + for (i = 0 ; i < V33_PCA_ROLL_OFF_TABLE_SIZE ; i++) { + msm_camera_io_w(*(cmdp_local + 1), + vfe32_ctrl->vfebase + VFE33_DMI_DATA_HI); + msm_camera_io_w(*cmdp_local, + vfe32_ctrl->vfebase + VFE33_DMI_DATA_LO); + cmdp_local += 2; + } + CDBG("%s: end writing RollOff Ram0 table\n", __func__); + + CDBG("%s: start writing RollOff Ram1 table\n", __func__); + vfe32_program_dmi_cfg(ROLLOFF_RAM1_BANK0); + msm_camera_io_w(temp1, vfe32_ctrl->vfebase + VFE_DMI_ADDR); + for (i = 0 ; i < V33_PCA_ROLL_OFF_TABLE_SIZE ; i++) { + msm_camera_io_w(*cmdp_local, + vfe32_ctrl->vfebase + VFE33_DMI_DATA_LO); + cmdp_local += 2; + } + CDBG("%s: end writing RollOff Ram1 table\n", __func__); + + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + break; + + case VFE_CMD_PCA_ROLL_OFF_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; + + temp1 = *cmdp_local; + cmdp_local += 8; + + temp2 = msm_camera_io_r(vfe32_ctrl->vfebase + + V33_PCA_ROLL_OFF_CFG_OFF1) + & V33_PCA_ROLL_OFF_LUT_BANK_SEL_MASK; + + CDBG("%s: start writing RollOff Ram0 table\n", __func__); + if (temp2) + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK0); + else + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK1); + + msm_camera_io_w(temp1, vfe32_ctrl->vfebase + VFE_DMI_ADDR); + for (i = 0 ; i < V33_PCA_ROLL_OFF_TABLE_SIZE ; i++) { + msm_camera_io_w(*(cmdp_local + 1), + vfe32_ctrl->vfebase + VFE33_DMI_DATA_HI); + msm_camera_io_w(*cmdp_local, + vfe32_ctrl->vfebase + VFE33_DMI_DATA_LO); + cmdp_local += 2; + } + CDBG("%s: end writing RollOff Ram0 table\n", __func__); + + CDBG("%s: start writing RollOff Ram1 table\n", __func__); + if (temp2) + vfe32_program_dmi_cfg(ROLLOFF_RAM1_BANK0); + else + vfe32_program_dmi_cfg(ROLLOFF_RAM1_BANK1); + + msm_camera_io_w(temp1, vfe32_ctrl->vfebase + VFE_DMI_ADDR); + for (i = 0 ; i < V33_PCA_ROLL_OFF_TABLE_SIZE ; i++) { + msm_camera_io_w(*cmdp_local, + vfe32_ctrl->vfebase + VFE33_DMI_DATA_LO); + cmdp_local += 2; + } + CDBG("%s: end writing RollOff Ram1 table\n", __func__); + + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + vfe32_ctrl->update_rolloff = true; + break; + case VFE_CMD_GET_PCA_ROLLOFF_TABLE: + temp1 = sizeof(uint64_t) * V33_PCA_ROLL_OFF_TABLE_SIZE * 2; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kzalloc(temp1, GFP_KERNEL); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + cmdp_local = cmdp; + old_val = msm_camera_io_r(vfe32_ctrl->vfebase + + V33_PCA_ROLL_OFF_CFG_OFF1) & + V33_PCA_ROLL_OFF_LUT_BANK_SEL_MASK; + + if (old_val) + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK1); + else + vfe32_program_dmi_cfg(ROLLOFF_RAM0_BANK0); + + CDBG("%s: PCA Rolloff Ram0\n", __func__); + for (i = 0 ; i < V33_PCA_ROLL_OFF_TABLE_SIZE * 2; i++) { + temp2 = (i == (V33_PCA_ROLL_OFF_TABLE_SIZE)); + if (old_val && temp2) + vfe32_program_dmi_cfg(ROLLOFF_RAM1_BANK1); + else if (!old_val && temp2) + vfe32_program_dmi_cfg(ROLLOFF_RAM1_BANK0); + + *cmdp_local = msm_camera_io_r(vfe32_ctrl->vfebase + + VFE33_DMI_DATA_LO); + *(cmdp_local + 1) = + msm_camera_io_r(vfe32_ctrl->vfebase + + VFE33_DMI_DATA_HI); + CDBG("%s: %08x%08x\n", __func__, + *(cmdp_local + 1), *cmdp_local); + cmdp_local += 2; + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + if (copy_to_user((void __user *)(cmd->value), cmdp, + temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_GET_HW_VERSION: + if (cmd->length != V32_GET_HW_VERSION_LEN) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kmalloc(V32_GET_HW_VERSION_LEN, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + *cmdp = msm_camera_io_r( + vfe32_ctrl->vfebase+V32_GET_HW_VERSION_OFF); + if (copy_to_user((void __user *)(cmd->value), cmdp, + V32_GET_HW_VERSION_LEN)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_GET_REG_DUMP: + temp1 = sizeof(uint32_t) * vfe32_ctrl->register_total; + if (cmd->length != temp1) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kmalloc(temp1, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + msm_camera_io_dump( + vfe32_ctrl->vfebase, vfe32_ctrl->register_total*4); + CDBG("%s: %p %p %d\n", __func__, (void *)cmdp, + vfe32_ctrl->vfebase, temp1); + memcpy_fromio((void *)cmdp, vfe32_ctrl->vfebase, temp1); + if (copy_to_user((void __user *)(cmd->value), cmdp, temp1)) { + rc = -EFAULT; + goto proc_general_done; + } + break; + case VFE_CMD_FRAME_SKIP_CFG: + 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; + } + + if (copy_from_user((cmdp), (void __user *)cmd->value, + cmd->length)) { + rc = -EFAULT; + pr_err("%s copy from user failed for cmd %d", + __func__, cmd->id); + break; + } + + msm_camera_io_memcpy( + vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + vfe32_ctrl->frame_skip_cnt = ((uint32_t) + *cmdp & VFE_FRAME_SKIP_PERIOD_MASK) + 1; + vfe32_ctrl->frame_skip_pattern = (uint32_t)(*(cmdp + 2)); + 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; + } + + if (copy_from_user((cmdp), (void __user *)cmd->value, + cmd->length)) { + rc = -EFAULT; + pr_err("%s copy from user failed for cmd %d", + __func__, cmd->id); + goto proc_general_done; + } + msm_camera_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; + spinlock_t *lock = (vfe32_ctrl->stats_comp ? + &vfe32_ctrl->comp_stats_ack_lock : + &vfe32_ctrl->af_ack_lock); + spin_lock_irqsave(lock, flags); + vfe32_ctrl->afStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->afStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static void vfe32_stats_awb_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe32_ctrl->stats_comp ? + &vfe32_ctrl->comp_stats_ack_lock : + &vfe32_ctrl->awb_ack_lock); + spin_lock_irqsave(lock, flags); + vfe32_ctrl->awbStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->awbStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static void vfe32_stats_aec_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe32_ctrl->stats_comp ? + &vfe32_ctrl->comp_stats_ack_lock : + &vfe32_ctrl->aec_ack_lock); + spin_lock_irqsave(lock, flags); + vfe32_ctrl->aecStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->aecStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +static void vfe32_stats_ihist_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe32_ctrl->stats_comp ? + &vfe32_ctrl->comp_stats_ack_lock : + &vfe32_ctrl->ihist_ack_lock); + spin_lock_irqsave(lock, flags); + vfe32_ctrl->ihistStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->ihistStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} +static void vfe32_stats_rs_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe32_ctrl->stats_comp ? + &vfe32_ctrl->comp_stats_ack_lock : + &vfe32_ctrl->rs_ack_lock); + spin_lock_irqsave(lock, flags); + vfe32_ctrl->rsStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->rsStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} +static void vfe32_stats_cs_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spinlock_t *lock = (vfe32_ctrl->stats_comp ? + &vfe32_ctrl->comp_stats_ack_lock : + &vfe32_ctrl->cs_ack_lock); + spin_lock_irqsave(lock, flags); + vfe32_ctrl->csStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->csStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(lock, flags); +} + +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_camera_io_r(temp); + + temp = (uint32_t *)(vfe32_ctrl->vfebase + VFE_IRQ_STATUS_1); + out->vfeIrqStatus1 = msm_camera_io_r(temp); + + temp = (uint32_t *)(vfe32_ctrl->vfebase + VFE_CAMIF_STATUS); + out->camifStatus = msm_camera_io_r(temp); + CDBG("camifStatus = 0x%x\n", out->camifStatus); + + /* clear the pending interrupt of the same kind.*/ + msm_camera_io_w(out->vfeIrqStatus0, + vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_camera_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_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_IRQ_CMD); + +} + +static void vfe32_process_reg_update_irq(void) +{ + unsigned long flags; + + if (vfe32_ctrl->recording_state == VFE_STATE_START_REQUESTED) { + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_VIDEO_AND_PREVIEW) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } else if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + } + vfe32_ctrl->recording_state = VFE_STATE_STARTED; + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + CDBG("start video triggered .\n"); + } else if (vfe32_ctrl->recording_state == + VFE_STATE_STOP_REQUESTED) { + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_VIDEO_AND_PREVIEW) { + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } else if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_PREVIEW_AND_VIDEO) { + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + } + CDBG("stop video triggered .\n"); + } + + if (vfe32_ctrl->start_ack_pending == TRUE) { + vfe32_send_isp_msg(vfe32_ctrl, MSG_ID_START_ACK); + vfe32_ctrl->start_ack_pending = FALSE; + } else { + if (vfe32_ctrl->recording_state == + VFE_STATE_STOP_REQUESTED) { + vfe32_ctrl->recording_state = VFE_STATE_STOPPED; + /* request a reg update and send STOP_REC_ACK + * when we process the next reg update irq. + */ + msm_camera_io_w_mb(1, + vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } else if (vfe32_ctrl->recording_state == + VFE_STATE_STOPPED) { + vfe32_send_isp_msg(vfe32_ctrl, MSG_ID_STOP_REC_ACK); + vfe32_ctrl->recording_state = VFE_STATE_IDLE; + } + 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_isp_msg(vfe32_ctrl, MSG_ID_UPDATE_ACK); + } else { + spin_unlock_irqrestore( + &vfe32_ctrl->update_ack_lock, flags); + } + } + + if (vfe32_ctrl->liveshot_state == VFE_STATE_START_REQUESTED) { + pr_info("%s enabling liveshot output\n", __func__); + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + vfe32_ctrl->liveshot_state = VFE_STATE_STARTED; + } + } + + if (vfe32_ctrl->liveshot_state == VFE_STATE_STARTED) { + vfe32_ctrl->vfe_capture_count--; + if (!vfe32_ctrl->vfe_capture_count) + vfe32_ctrl->liveshot_state = VFE_STATE_STOP_REQUESTED; + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } else if (vfe32_ctrl->liveshot_state == VFE_STATE_STOP_REQUESTED) { + CDBG("%s: disabling liveshot output\n", __func__); + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PRIMARY) { + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + vfe32_ctrl->liveshot_state = VFE_STATE_STOPPED; + msm_camera_io_w_mb(1, vfe32_ctrl->vfebase + + VFE_REG_UPDATE_CMD); + } + } else if (vfe32_ctrl->liveshot_state == VFE_STATE_STOPPED) { + vfe32_ctrl->liveshot_state = VFE_STATE_IDLE; + } + + if ((vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_MAIN) || + (vfe32_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB) || + (vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG) || + (vfe32_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB)) { + /* in snapshot mode */ + /* later we need to add check for live snapshot mode. */ + if (vfe32_ctrl->frame_skip_pattern & (0x1 << + (vfe32_ctrl->snapshot_frame_cnt % + vfe32_ctrl->frame_skip_cnt))) { + 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_PRIMARY) { + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out0.ch0]); + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out0.ch1]); + } + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_SECONDARY) { + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out1.ch0]); + msm_camera_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out1.ch1]); + } + msm_camera_io_w_mb + (CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + vfe32_ctrl->snapshot_frame_cnt = -1; + vfe32_ctrl->frame_skip_cnt = 31; + vfe32_ctrl->frame_skip_pattern = 0xffffffff; + } /*if snapshot count is 0*/ + } /*if frame is not being dropped*/ + vfe32_ctrl->snapshot_frame_cnt++; + /* then do reg_update. */ + msm_camera_io_w(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } /* if snapshot mode. */ +} + +static void vfe32_set_default_reg_values(void) +{ + msm_camera_io_w(0x800080, vfe32_ctrl->vfebase + VFE_DEMUX_GAIN_0); + msm_camera_io_w(0x800080, vfe32_ctrl->vfebase + VFE_DEMUX_GAIN_1); + /* What value should we program CGC_OVERRIDE to? */ + msm_camera_io_w(0xFFFFF, vfe32_ctrl->vfebase + VFE_CGC_OVERRIDE); + + /* default frame drop period and pattern */ + msm_camera_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG); + msm_camera_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_CFG); + msm_camera_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN); + msm_camera_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_PATTERN); + msm_camera_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y); + msm_camera_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR); + msm_camera_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN); + msm_camera_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_PATTERN); + msm_camera_io_w(0, vfe32_ctrl->vfebase + VFE_CLAMP_MIN); + msm_camera_io_w(0xFFFFFF, vfe32_ctrl->vfebase + VFE_CLAMP_MAX); + + /* stats UB config */ + msm_camera_io_w(0x3980007, + vfe32_ctrl->vfebase + VFE_BUS_STATS_AEC_UB_CFG); + msm_camera_io_w(0x3A00007, + vfe32_ctrl->vfebase + VFE_BUS_STATS_AF_UB_CFG); + msm_camera_io_w(0x3A8000F, + vfe32_ctrl->vfebase + VFE_BUS_STATS_AWB_UB_CFG); + msm_camera_io_w(0x3B80007, + vfe32_ctrl->vfebase + VFE_BUS_STATS_RS_UB_CFG); + msm_camera_io_w(0x3C0001F, + vfe32_ctrl->vfebase + VFE_BUS_STATS_CS_UB_CFG); + msm_camera_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_isp_msg(vfe32_ctrl, 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_camera_io_w(0x7FFF, vfe32_ctrl->vfebase + VFE_BUS_CMD); + vfe32_send_isp_msg(vfe32_ctrl, MSG_ID_RESET_ACK); + } +} + +static void vfe32_process_camif_sof_irq(void) +{ + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_RAW) { + if (vfe32_ctrl->start_ack_pending) { + vfe32_send_isp_msg(vfe32_ctrl, 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_camera_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + } + } /* if raw snapshot mode. */ + if ((vfe32_ctrl->hfr_mode != HFR_MODE_OFF) && + (vfe32_ctrl->operation_mode == VFE_MODE_OF_OPERATION_VIDEO) && + (vfe32_ctrl->vfeFrameId % vfe32_ctrl->hfr_mode != 0)) { + vfe32_ctrl->vfeFrameId++; + CDBG("Skip the SOF notification when HFR enabled\n"); + return; + } + vfe32_ctrl->vfeFrameId++; + vfe32_send_isp_msg(vfe32_ctrl, MSG_ID_SOF_ACK); + 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 reg_value; + + if (errStatus & VFE32_IMASK_CAMIF_ERROR) { + pr_err("vfe32_irq: camif errors\n"); + reg_value = msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_CAMIF_STATUS); + pr_err("camifStatus = 0x%x\n", reg_value); + vfe32_send_isp_msg(vfe32_ctrl, 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"); + reg_value = msm_camera_io_r( + vfe32_ctrl->vfebase + VFE_VIOLATION_STATUS); + pr_err("%s: violationStatus = 0x%x\n", __func__, reg_value); + } + + 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"); +} + +static void vfe_send_outmsg(struct v4l2_subdev *sd, uint8_t msgid, + uint32_t ch0_paddr, uint32_t ch1_paddr, uint32_t ch2_paddr) +{ + struct isp_msg_output msg; + + msg.output_id = msgid; + msg.buf.ch_paddr[0] = ch0_paddr; + msg.buf.ch_paddr[1] = ch1_paddr; + msg.buf.ch_paddr[2] = ch2_paddr; + msg.frameCounter = vfe32_ctrl->vfeFrameId; + + v4l2_subdev_notify(&vfe32_ctrl->subdev, + NOTIFY_VFE_MSG_OUT, + &msg); + return; +} + +static void vfe32_process_output_path_irq_0(void) +{ + uint32_t ping_pong; + uint32_t ch0_paddr, ch1_paddr, ch2_paddr; + uint8_t out_bool = 0; + struct msm_free_buf *free_buf = NULL; + + free_buf = vfe32_check_free_buffer(VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + + /* 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_OUTPUTS_THUMB_AND_MAIN || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_THUMB_AND_JPEG || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_JPEG_AND_THUMB || + vfe32_ctrl->operation_mode == VFE_OUTPUTS_RAW || + vfe32_ctrl->liveshot_state == VFE_STATE_STARTED || + vfe32_ctrl->liveshot_state == VFE_STATE_STOP_REQUESTED || + vfe32_ctrl->liveshot_state == VFE_STATE_STOPPED) && + (vfe32_ctrl->vfe_capture_count <= 1)) || free_buf; + + if (out_bool) { + ping_pong = msm_camera_io_r(vfe32_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Channel 0*/ + ch0_paddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch0); + /* Channel 1*/ + ch1_paddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch1); + /* Channel 2*/ + ch2_paddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch2); + + CDBG("output path 0, ch0 = 0x%x, ch1 = 0x%x, ch2 = 0x%x\n", + ch0_paddr, ch1_paddr, ch2_paddr); + if (free_buf) { + /* Y channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch0, + free_buf->ch_paddr[0]); + /* Chroma channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch1, + free_buf->ch_paddr[1]); + if (free_buf->num_planes > 2) + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch2, + free_buf->ch_paddr[2]); + } + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_JPEG || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_RAW || + vfe32_ctrl->liveshot_state == VFE_STATE_STOPPED) + vfe32_ctrl->outpath.out0.capture_cnt--; + + vfe_send_outmsg(&vfe32_ctrl->subdev, + MSG_ID_OUTPUT_PRIMARY, ch0_paddr, + ch1_paddr, ch2_paddr); + + if (vfe32_ctrl->liveshot_state == VFE_STATE_STOPPED) + vfe32_ctrl->liveshot_state = VFE_STATE_IDLE; + + } else { + vfe32_ctrl->outpath.out0.frame_drop_cnt++; + CDBG("path_irq_0 - no free buffer!\n"); + } +} + +static void vfe32_process_output_path_irq_1(void) +{ + uint32_t ping_pong; + uint32_t ch0_paddr, ch1_paddr, ch2_paddr; + /* this must be snapshot main image output. */ + uint8_t out_bool = 0; + struct msm_free_buf *free_buf = NULL; + + free_buf = vfe32_check_free_buffer(VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY); + out_bool = ((vfe32_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_RAW || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB) && + (vfe32_ctrl->vfe_capture_count <= 1)) || free_buf; + + if (out_bool) { + ping_pong = msm_camera_io_r(vfe32_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Y channel */ + ch0_paddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch0); + /* Chroma channel */ + ch1_paddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch1); + ch2_paddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch2); + + CDBG("%s ch0 = 0x%x, ch1 = 0x%x, ch2 = 0x%x\n", + __func__, ch0_paddr, ch1_paddr, ch2_paddr); + if (free_buf) { + /* Y channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch0, + free_buf->ch_paddr[0]); + /* Chroma channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch1, + free_buf->ch_paddr[1]); + if (free_buf->num_planes > 2) + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch2, + free_buf->ch_paddr[2]); + } + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_RAW || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB) + vfe32_ctrl->outpath.out1.capture_cnt--; + + vfe_send_outmsg(&vfe32_ctrl->subdev, + MSG_ID_OUTPUT_SECONDARY, ch0_paddr, + ch1_paddr, ch2_paddr); + } else { + vfe32_ctrl->outpath.out1.frame_drop_cnt++; + CDBG("path_irq_1 - no free buffer!\n"); + } +} + +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_camera_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_camera_io_r((uint32_t *)pingpongAddr); + msm_camera_io_w(newAddr, (uint32_t *)pingpongAddr); + return returnAddr; +} + +static void +vfe_send_stats_msg(uint32_t bufAddress, uint32_t statsNum) +{ + unsigned long flags; + /* fill message with right content. */ + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + struct isp_msg_stats msgStats; + msgStats.frameCounter = vfe32_ctrl->vfeFrameId; + msgStats.buffer = bufAddress; + + switch (statsNum) { + case statsAeNum:{ + msgStats.id = 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:{ + msgStats.id = 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: { + msgStats.id = 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: { + msgStats.id = MSG_ID_STATS_IHIST; + spin_lock_irqsave(&vfe32_ctrl->ihist_ack_lock, flags); + vfe32_ctrl->ihistStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->ihist_ack_lock, flags); + } + break; + case statsRsNum: { + msgStats.id = MSG_ID_STATS_RS; + spin_lock_irqsave(&vfe32_ctrl->rs_ack_lock, flags); + vfe32_ctrl->rsStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->rs_ack_lock, flags); + } + break; + case statsCsNum: { + msgStats.id = MSG_ID_STATS_CS; + spin_lock_irqsave(&vfe32_ctrl->cs_ack_lock, flags); + vfe32_ctrl->csStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->cs_ack_lock, flags); + } + break; + + default: + goto stats_done; + } + + v4l2_subdev_notify(&vfe32_ctrl->subdev, + NOTIFY_VFE_MSG_STATS, + &msgStats); +stats_done: + /* spin_unlock_irqrestore(&ctrl->state_lock, flags); */ + return; +} + +static void vfe_send_comp_stats_msg(uint32_t status_bits) +{ + struct msm_stats_buf msgStats; + uint32_t temp; + + msgStats.frame_id = vfe32_ctrl->vfeFrameId; + msgStats.status_bits = status_bits; + + msgStats.aec.buff = vfe32_ctrl->aecStatsControl.bufToRender; + msgStats.awb.buff = vfe32_ctrl->awbStatsControl.bufToRender; + msgStats.af.buff = vfe32_ctrl->afStatsControl.bufToRender; + + msgStats.ihist.buff = vfe32_ctrl->ihistStatsControl.bufToRender; + msgStats.rs.buff = vfe32_ctrl->rsStatsControl.bufToRender; + msgStats.cs.buff = vfe32_ctrl->csStatsControl.bufToRender; + + temp = msm_camera_io_r(vfe32_ctrl->vfebase + VFE_STATS_AWB_SGW_CFG); + msgStats.awb_ymin = (0xFF00 & temp) >> 8; + + v4l2_subdev_notify(&vfe32_ctrl->subdev, + NOTIFY_VFE_MSG_COMP_STATS, + &msgStats); +} + +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++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + 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++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + 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++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + 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++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + 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++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + 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++; + CDBG("%s: droppedStatsFrameCount = %d", __func__, + vfe32_ctrl->csStatsControl.droppedStatsFrameCount); + } +} + +static void vfe32_process_stats(uint32_t status_bits) +{ + unsigned long flags; + int32_t process_stats = false; + CDBG("%s, stats = 0x%x\n", __func__, status_bits); + + spin_lock_irqsave(&vfe32_ctrl->comp_stats_ack_lock, flags); + if (status_bits & VFE_IRQ_STATUS0_STATS_AEC) { + if (!vfe32_ctrl->aecStatsControl.ackPending) { + vfe32_ctrl->aecStatsControl.ackPending = TRUE; + vfe32_ctrl->aecStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsAeNum, + vfe32_ctrl->aecStatsControl.nextFrameAddrBuf); + process_stats = true; + } else{ + vfe32_ctrl->aecStatsControl.bufToRender = 0; + vfe32_ctrl->aecStatsControl.droppedStatsFrameCount++; + } + } else { + vfe32_ctrl->aecStatsControl.bufToRender = 0; + } + + if (status_bits & VFE_IRQ_STATUS0_STATS_AWB) { + if (!vfe32_ctrl->awbStatsControl.ackPending) { + vfe32_ctrl->awbStatsControl.ackPending = TRUE; + vfe32_ctrl->awbStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsAwbNum, + vfe32_ctrl->awbStatsControl.nextFrameAddrBuf); + process_stats = true; + } else{ + vfe32_ctrl->awbStatsControl.droppedStatsFrameCount++; + vfe32_ctrl->awbStatsControl.bufToRender = 0; + } + } else { + vfe32_ctrl->awbStatsControl.bufToRender = 0; + } + + + if (status_bits & VFE_IRQ_STATUS0_STATS_AF) { + if (!vfe32_ctrl->afStatsControl.ackPending) { + vfe32_ctrl->afStatsControl.ackPending = TRUE; + vfe32_ctrl->afStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsAfNum, + vfe32_ctrl->afStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe32_ctrl->afStatsControl.bufToRender = 0; + vfe32_ctrl->afStatsControl.droppedStatsFrameCount++; + } + } else { + vfe32_ctrl->afStatsControl.bufToRender = 0; + } + + if (status_bits & VFE_IRQ_STATUS0_STATS_IHIST) { + if (!vfe32_ctrl->ihistStatsControl.ackPending) { + vfe32_ctrl->ihistStatsControl.ackPending = TRUE; + vfe32_ctrl->ihistStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsIhistNum, + vfe32_ctrl->ihistStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe32_ctrl->ihistStatsControl.droppedStatsFrameCount++; + vfe32_ctrl->ihistStatsControl.bufToRender = 0; + } + } else { + vfe32_ctrl->ihistStatsControl.bufToRender = 0; + } + + if (status_bits & VFE_IRQ_STATUS0_STATS_RS) { + if (!vfe32_ctrl->rsStatsControl.ackPending) { + vfe32_ctrl->rsStatsControl.ackPending = TRUE; + vfe32_ctrl->rsStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsRsNum, + vfe32_ctrl->rsStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe32_ctrl->rsStatsControl.droppedStatsFrameCount++; + vfe32_ctrl->rsStatsControl.bufToRender = 0; + } + } else { + vfe32_ctrl->rsStatsControl.bufToRender = 0; + } + + + if (status_bits & VFE_IRQ_STATUS0_STATS_CS) { + if (!vfe32_ctrl->csStatsControl.ackPending) { + vfe32_ctrl->csStatsControl.ackPending = TRUE; + vfe32_ctrl->csStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsCsNum, + vfe32_ctrl->csStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe32_ctrl->csStatsControl.droppedStatsFrameCount++; + vfe32_ctrl->csStatsControl.bufToRender = 0; + } + } else { + vfe32_ctrl->csStatsControl.bufToRender = 0; + } + + spin_unlock_irqrestore(&vfe32_ctrl->comp_stats_ack_lock, flags); + if (process_stats) + vfe_send_comp_stats_msg(status_bits); + + return; +} + +static void vfe32_process_stats_irq(uint32_t irqstatus) +{ + uint32_t status_bits = VFE_COM_STATUS & irqstatus; + + if ((vfe32_ctrl->hfr_mode != HFR_MODE_OFF) && + (vfe32_ctrl->vfeFrameId % vfe32_ctrl->hfr_mode != 0)) { + CDBG("Skip the stats when HFR enabled\n"); + return; + } + + vfe32_process_stats(status_bits); + return; +} + +static void vfe32_process_irq(uint32_t irqstatus) +{ + if (irqstatus & + VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK) { + vfe32_process_stats_irq(irqstatus); + return; + } + + switch (irqstatus) { + case VFE_IRQ_STATUS0_CAMIF_SOF_MASK: + CDBG("irq camifSofIrq\n"); + vfe32_process_camif_sof_irq(); + break; + case VFE_IRQ_STATUS0_REG_UPDATE_MASK: + CDBG("irq regUpdateIrq\n"); + vfe32_process_reg_update_irq(); + break; + case VFE_IMASK_WHILE_STOPPING_1: + CDBG("irq resetAckIrq\n"); + vfe32_process_reset_irq(); + break; + case VFE_IRQ_STATUS0_STATS_AEC: + CDBG("Stats AEC irq occured.\n"); + vfe32_process_stats_ae_irq(); + break; + case VFE_IRQ_STATUS0_STATS_AWB: + CDBG("Stats AWB irq occured.\n"); + vfe32_process_stats_awb_irq(); + break; + case VFE_IRQ_STATUS0_STATS_AF: + CDBG("Stats AF irq occured.\n"); + vfe32_process_stats_af_irq(); + break; + case VFE_IRQ_STATUS0_STATS_IHIST: + CDBG("Stats IHIST irq occured.\n"); + vfe32_process_stats_ihist_irq(); + break; + case VFE_IRQ_STATUS0_STATS_RS: + CDBG("Stats RS irq occured.\n"); + vfe32_process_stats_rs_irq(); + break; + case VFE_IRQ_STATUS0_STATS_CS: + CDBG("Stats CS irq occured.\n"); + vfe32_process_stats_cs_irq(); + break; + case VFE_IRQ_STATUS0_SYNC_TIMER0: + CDBG("SYNC_TIMER 0 irq occured.\n"); + vfe32_send_isp_msg(vfe32_ctrl, + MSG_ID_SYNC_TIMER0_DONE); + break; + case VFE_IRQ_STATUS0_SYNC_TIMER1: + CDBG("SYNC_TIMER 1 irq occured.\n"); + vfe32_send_isp_msg(vfe32_ctrl, + MSG_ID_SYNC_TIMER1_DONE); + break; + case VFE_IRQ_STATUS0_SYNC_TIMER2: + CDBG("SYNC_TIMER 2 irq occured.\n"); + vfe32_send_isp_msg(vfe32_ctrl, + MSG_ID_SYNC_TIMER2_DONE); + break; + default: + pr_err("Invalid IRQ status\n"); + } +} + +static void axi32_do_tasklet(unsigned long data) +{ + unsigned long flags; + struct axi_ctrl_t *axi_ctrl = (struct axi_ctrl_t *)data; + struct vfe32_isr_queue_cmd *qcmd = NULL; + + CDBG("=== axi32_do_tasklet start ===\n"); + + while (atomic_read(&irq_cnt)) { + spin_lock_irqsave(&axi_ctrl->tasklet_lock, flags); + qcmd = list_first_entry(&axi_ctrl->tasklet_q, + struct vfe32_isr_queue_cmd, list); + atomic_sub(1, &irq_cnt); + + if (!qcmd) { + spin_unlock_irqrestore(&axi_ctrl->tasklet_lock, + flags); + return; + } + + list_del(&qcmd->list); + spin_unlock_irqrestore(&axi_ctrl->tasklet_lock, + flags); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_CAMIF_SOF_MASK) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_CAMIF_SOF_MASK); + + /* interrupt to be processed, *qcmd has the payload. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_REG_UPDATE_MASK) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_REG_UPDATE_MASK); + + if (qcmd->vfeInterruptStatus1 & + VFE_IMASK_WHILE_STOPPING_1) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IMASK_WHILE_STOPPING_1); + + 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); + } + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_AXI_IRQ, + (void *)qcmd->vfeInterruptStatus0); + + /* 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"); + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)qcmd->vfeInterruptStatus0); + } + } else { + /* process individual stats interrupt. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AEC) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_STATS_AEC); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AWB) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_STATS_AWB); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AF) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_STATS_AF); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_IHIST) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_STATS_IHIST); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_RS) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_STATS_RS); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_CS) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_STATS_CS); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER0) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_SYNC_TIMER0); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER1) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_SYNC_TIMER1); + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER2) + v4l2_subdev_notify(&axi_ctrl->subdev, + NOTIFY_VFE_IRQ, + (void *)VFE_IRQ_STATUS0_SYNC_TIMER2); + } + } + kfree(qcmd); + } + CDBG("=== axi32_do_tasklet end ===\n"); +} + +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; + struct axi_ctrl_t *axi_ctrl = data; + + 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(&axi_ctrl->tasklet_lock, flags); + list_add_tail(&qcmd->list, &axi_ctrl->tasklet_q); + + atomic_add(1, &irq_cnt); + spin_unlock_irqrestore(&axi_ctrl->tasklet_lock, flags); + tasklet_schedule(&axi_ctrl->vfe32_tasklet); + return IRQ_HANDLED; +} + +static long msm_vfe_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int subdev_cmd, void *arg) +{ + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *)v4l2_get_subdev_hostdata(sd); + struct msm_isp_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_VFE_PROCESS_IRQ) { + vfe32_process_irq((uint32_t) data); + return rc; + } else if (cmd->cmd_type != CMD_CONFIG_PING_ADDR && + cmd->cmd_type != CMD_CONFIG_PONG_ADDR && + cmd->cmd_type != CMD_CONFIG_FREE_BUF_ADDR && + 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_CONFIG_PING_ADDR && + cmd->cmd_type != CMD_CONFIG_PONG_ADDR && + cmd->cmd_type != CMD_CONFIG_FREE_BUF_ADDR) { + /* 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; + default: + pr_err("%s Unsupported cmd type %d", + __func__, cmd->cmd_type); + break; + } + goto vfe32_config_done; + } + switch (cmd->cmd_type) { + case CMD_GENERAL: + rc = vfe32_proc_general(pmctl, &vfecmd); + break; + + case CMD_CONFIG_PING_ADDR: { + int path = *((int *)cmd->value); + struct vfe32_output_ch *outch = vfe32_get_ch(path); + outch->ping = *((struct msm_free_buf *)data); + } + break; + + case CMD_CONFIG_PONG_ADDR: { + int path = *((int *)cmd->value); + struct vfe32_output_ch *outch = vfe32_get_ch(path); + outch->pong = *((struct msm_free_buf *)data); + } + break; + + case CMD_CONFIG_FREE_BUF_ADDR: { + int path = *((int *)cmd->value); + struct vfe32_output_ch *outch = vfe32_get_ch(path); + outch->free_buf = *((struct msm_free_buf *)data); + } + 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; + default: + pr_err("%s Unsupported AXI configuration %x ", __func__, + cmd->cmd_type); + break; + } +vfe32_config_done: + kfree(scfg); + kfree(sack); + CDBG("%s done: rc = %d\n", __func__, (int) rc); + return rc; +} + +static struct msm_cam_clk_info vfe32_clk_info[] = { + {"vfe_clk", 228570000}, + {"vfe_pclk", -1}, + {"csi_vfe_clk", -1}, +}; + +static int msm_axi_subdev_s_crystal_freq(struct v4l2_subdev *sd, + u32 freq, u32 flags) +{ + int rc = 0; + int round_rate; + struct axi_ctrl_t *axi_ctrl = v4l2_get_subdevdata(sd); + + round_rate = clk_round_rate(axi_ctrl->vfe_clk[0], freq); + if (rc < 0) { + pr_err("%s: clk_round_rate failed %d\n", + __func__, rc); + return rc; + } + + vfe_clk_rate = round_rate; + rc = clk_set_rate(axi_ctrl->vfe_clk[0], round_rate); + if (rc < 0) + pr_err("%s: clk_set_rate failed %d\n", + __func__, 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_axi_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + int rc = 0; + struct axi_ctrl_t *axi_ctrl = v4l2_get_subdevdata(sd); + v4l2_set_subdev_hostdata(sd, mctl); + spin_lock_init(&axi_ctrl->tasklet_lock); + INIT_LIST_HEAD(&axi_ctrl->tasklet_q); + + axi_ctrl->vfebase = ioremap(axi_ctrl->vfemem->start, + resource_size(axi_ctrl->vfemem)); + if (!axi_ctrl->vfebase) { + rc = -ENOMEM; + pr_err("%s: vfe ioremap failed\n", __func__); + goto remap_failed; + } + + vfe32_ctrl->vfebase = axi_ctrl->vfebase; + + if (axi_ctrl->fs_vfe == NULL) { + axi_ctrl->fs_vfe = + regulator_get(&axi_ctrl->pdev->dev, "fs_vfe"); + if (IS_ERR(axi_ctrl->fs_vfe)) { + pr_err("%s: Regulator FS_VFE get failed %ld\n", + __func__, PTR_ERR(axi_ctrl->fs_vfe)); + axi_ctrl->fs_vfe = NULL; + goto fs_failed; + } else if (regulator_enable(axi_ctrl->fs_vfe)) { + pr_err("%s: Regulator FS_VFE enable failed\n", + __func__); + regulator_put(axi_ctrl->fs_vfe); + axi_ctrl->fs_vfe = NULL; + goto fs_failed; + } + } + + rc = msm_cam_clk_enable(&axi_ctrl->pdev->dev, vfe32_clk_info, + axi_ctrl->vfe_clk, ARRAY_SIZE(vfe32_clk_info), 1); + if (rc < 0) + goto clk_enable_failed; + + msm_camio_bus_scale_cfg( + mctl->sdata->pdata->cam_bus_scale_table, S_INIT); + msm_camio_bus_scale_cfg( + mctl->sdata->pdata->cam_bus_scale_table, S_PREVIEW); + + if (msm_camera_io_r(vfe32_ctrl->vfebase + V32_GET_HW_VERSION_OFF) == + VFE32_HW_NUMBER) + vfe32_ctrl->register_total = VFE32_REGISTER_TOTAL; + else + vfe32_ctrl->register_total = VFE33_REGISTER_TOTAL; + + enable_irq(axi_ctrl->vfeirq->start); + + return rc; +clk_enable_failed: + regulator_disable(axi_ctrl->fs_vfe); + regulator_put(axi_ctrl->fs_vfe); + axi_ctrl->fs_vfe = NULL; +fs_failed: + iounmap(axi_ctrl->vfebase); + axi_ctrl->vfebase = NULL; +remap_failed: + disable_irq(axi_ctrl->vfeirq->start); + return rc; +} + +int msm_vfe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + int rc = 0; + v4l2_set_subdev_hostdata(sd, mctl); + + 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->aec_ack_lock); + spin_lock_init(&vfe32_ctrl->awb_ack_lock); + spin_lock_init(&vfe32_ctrl->af_ack_lock); + spin_lock_init(&vfe32_ctrl->ihist_ack_lock); + spin_lock_init(&vfe32_ctrl->rs_ack_lock); + spin_lock_init(&vfe32_ctrl->cs_ack_lock); + spin_lock_init(&vfe32_ctrl->comp_stats_ack_lock); + spin_lock_init(&vfe32_ctrl->sd_notify_lock); + + vfe32_ctrl->update_linear = false; + vfe32_ctrl->update_rolloff = false; + vfe32_ctrl->update_la = false; + vfe32_ctrl->update_gamma = false; + vfe32_ctrl->hfr_mode = HFR_MODE_OFF; + + return rc; +} + +void msm_axi_subdev_release(struct v4l2_subdev *sd) +{ + struct msm_cam_media_controller *pmctl = + (struct msm_cam_media_controller *)v4l2_get_subdev_hostdata(sd); + struct axi_ctrl_t *axi_ctrl = v4l2_get_subdevdata(sd); + CDBG("%s, free_irq\n", __func__); + disable_irq(axi_ctrl->vfeirq->start); + tasklet_kill(&axi_ctrl->vfe32_tasklet); + msm_cam_clk_enable(&axi_ctrl->pdev->dev, vfe32_clk_info, + axi_ctrl->vfe_clk, ARRAY_SIZE(vfe32_clk_info), 0); + if (axi_ctrl->fs_vfe) { + regulator_disable(axi_ctrl->fs_vfe); + regulator_put(axi_ctrl->fs_vfe); + axi_ctrl->fs_vfe = NULL; + } + iounmap(axi_ctrl->vfebase); + axi_ctrl->vfebase = NULL; + + if (atomic_read(&irq_cnt)) + pr_warning("%s, Warning IRQ Count not ZERO\n", __func__); + + msm_camio_bus_scale_cfg( + pmctl->sdata->pdata->cam_bus_scale_table, S_EXIT); +} + +void msm_vfe_subdev_release(struct v4l2_subdev *sd) +{ + vfe32_ctrl->vfebase = 0; +} + +static int msm_axi_config(struct v4l2_subdev *sd, void __user *arg) +{ + struct msm_vfe_cfg_cmd cfgcmd; + struct msm_isp_cmd vfecmd; + int rc = 0; + + if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + if (copy_from_user(&vfecmd, + (void __user *)(cfgcmd.value), + sizeof(vfecmd))) { + pr_err("%s %d: copy_from_user failed\n", __func__, + __LINE__); + return -EFAULT; + } + + switch (cfgcmd.cmd_type) { + case CMD_AXI_CFG_PRIM: { + uint32_t *axio = NULL; + axio = kmalloc(vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_PRIM, axio); + kfree(axio); + } + break; + case CMD_AXI_CFG_PRIM_ALL_CHNLS: { + uint32_t *axio = NULL; + axio = kmalloc(vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_PRIM_ALL_CHNLS, axio); + kfree(axio); + } + break; + case CMD_AXI_CFG_PRIM|CMD_AXI_CFG_SEC: { + uint32_t *axio = NULL; + axio = kmalloc(vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_PRIM|OUTPUT_SEC, axio); + kfree(axio); + } + break; + case CMD_AXI_CFG_PRIM|CMD_AXI_CFG_SEC_ALL_CHNLS: { + uint32_t *axio = NULL; + axio = kmalloc(vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_PRIM|OUTPUT_SEC_ALL_CHNLS, axio); + kfree(axio); + } + break; + case CMD_AXI_CFG_PRIM_ALL_CHNLS|CMD_AXI_CFG_SEC: { + uint32_t *axio = NULL; + axio = kmalloc(vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[VFE_CMD_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_PRIM_ALL_CHNLS|OUTPUT_SEC, axio); + kfree(axio); + } + break; + case CMD_AXI_CFG_PRIM_ALL_CHNLS|CMD_AXI_CFG_SEC_ALL_CHNLS: + pr_err("%s Invalid/Unsupported AXI configuration %x", + __func__, cfgcmd.cmd_type); + break; + default: + pr_err("%s Unsupported AXI configuration %x ", __func__, + cfgcmd.cmd_type); + break; + } + return rc; +} + +static void msm_axi_process_irq(struct v4l2_subdev *sd, void *arg) +{ + uint32_t irqstatus = (uint32_t) arg; + /* next, check output path related interrupts. */ + if (irqstatus & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE0_MASK) { + CDBG("Image composite done 0 irq occured.\n"); + vfe32_process_output_path_irq_0(); + } + if (irqstatus & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE1_MASK) { + CDBG("Image composite done 1 irq occured.\n"); + vfe32_process_output_path_irq_1(); + } + /* in snapshot mode if done then send + snapshot done message */ + if (vfe32_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_MAIN || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_MAIN_AND_THUMB || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_THUMB_AND_JPEG || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_JPEG_AND_THUMB || + vfe32_ctrl->operation_mode == + VFE_OUTPUTS_RAW) { + if ((vfe32_ctrl->outpath.out0.capture_cnt == 0) + && (vfe32_ctrl->outpath.out1. + capture_cnt == 0)) { + msm_camera_io_w_mb( + CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe32_ctrl->vfebase + + VFE_CAMIF_COMMAND); + vfe32_send_isp_msg(vfe32_ctrl, + MSG_ID_SNAPSHOT_DONE); + } + } +} + +static const struct v4l2_subdev_internal_ops msm_vfe_internal_ops; + +static long msm_axi_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + int rc = -ENOIOCTLCMD; + switch (cmd) { + case VIDIOC_MSM_AXI_INIT: + rc = msm_axi_subdev_init(sd, + (struct msm_cam_media_controller *)arg); + break; + case VIDIOC_MSM_AXI_CFG: + rc = msm_axi_config(sd, arg); + break; + case VIDIOC_MSM_AXI_IRQ: + msm_axi_process_irq(sd, arg); + rc = 0; + break; + case VIDIOC_MSM_AXI_RELEASE: + msm_axi_subdev_release(sd); + rc = 0; + break; + default: + pr_err("%s: command not found\n", __func__); + } + return rc; +} + +static const struct v4l2_subdev_core_ops msm_axi_subdev_core_ops = { + .ioctl = msm_axi_subdev_ioctl, +}; + +static const struct v4l2_subdev_video_ops msm_axi_subdev_video_ops = { + .s_crystal_freq = msm_axi_subdev_s_crystal_freq, +}; + +static const struct v4l2_subdev_ops msm_axi_subdev_ops = { + .core = &msm_axi_subdev_core_ops, + .video = &msm_axi_subdev_video_ops, +}; + +static const struct v4l2_subdev_internal_ops msm_axi_internal_ops; + +static int __devinit vfe32_probe(struct platform_device *pdev) +{ + int rc = 0; + struct axi_ctrl_t *axi_ctrl; + CDBG("%s: device id = %d\n", __func__, pdev->id); + vfe32_ctrl = kzalloc(sizeof(struct vfe32_ctrl_type), GFP_KERNEL); + if (!vfe32_ctrl) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + axi_ctrl = kzalloc(sizeof(struct axi_ctrl_t), GFP_KERNEL); + v4l2_subdev_init(&axi_ctrl->subdev, &msm_axi_subdev_ops); + axi_ctrl->subdev.internal_ops = &msm_axi_internal_ops; + axi_ctrl->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(axi_ctrl->subdev.name, + sizeof(axi_ctrl->subdev.name), "axi"); + v4l2_set_subdevdata(&axi_ctrl->subdev, axi_ctrl); + axi_ctrl->pdev = pdev; + msm_cam_register_subdev_node(&axi_ctrl->subdev, AXI_DEV, 0); + + v4l2_subdev_init(&vfe32_ctrl->subdev, &msm_vfe_subdev_ops); + vfe32_ctrl->subdev.internal_ops = &msm_vfe_internal_ops; + vfe32_ctrl->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(vfe32_ctrl->subdev.name, + sizeof(vfe32_ctrl->subdev.name), "vfe3.2"); + v4l2_set_subdevdata(&vfe32_ctrl->subdev, vfe32_ctrl); + platform_set_drvdata(pdev, &vfe32_ctrl->subdev); + + axi_ctrl->vfemem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "vfe32"); + if (!axi_ctrl->vfemem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto vfe32_no_resource; + } + axi_ctrl->vfeirq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "vfe32"); + if (!axi_ctrl->vfeirq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto vfe32_no_resource; + } + + axi_ctrl->vfeio = request_mem_region(axi_ctrl->vfemem->start, + resource_size(axi_ctrl->vfemem), pdev->name); + if (!axi_ctrl->vfeio) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto vfe32_no_resource; + } + + rc = request_irq(axi_ctrl->vfeirq->start, vfe32_parse_irq, + IRQF_TRIGGER_RISING, "vfe", axi_ctrl); + if (rc < 0) { + release_mem_region(axi_ctrl->vfemem->start, + resource_size(axi_ctrl->vfemem)); + pr_err("%s: irq request fail\n", __func__); + rc = -EBUSY; + goto vfe32_no_resource; + } + + disable_irq(axi_ctrl->vfeirq->start); + + tasklet_init(&axi_ctrl->vfe32_tasklet, + axi32_do_tasklet, (unsigned long)axi_ctrl); + + vfe32_ctrl->pdev = pdev; + msm_cam_register_subdev_node(&vfe32_ctrl->subdev, VFE_DEV, 0); + return 0; + +vfe32_no_resource: + kfree(vfe32_ctrl); + kfree(axi_ctrl); + return 0; +} + +static struct platform_driver vfe32_driver = { + .probe = vfe32_probe, + .driver = { + .name = MSM_VFE_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_vfe32_init_module(void) +{ + return platform_driver_register(&vfe32_driver); +} + +static void __exit msm_vfe32_exit_module(void) +{ + platform_driver_unregister(&vfe32_driver); +} + +module_init(msm_vfe32_init_module); +module_exit(msm_vfe32_exit_module); +MODULE_DESCRIPTION("VFE 3.2 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_vfe32.h b/drivers/media/video/msm/msm_vfe32.h new file mode 100644 index 0000000000000000000000000000000000000000..d1fadedd17f99d29ad13c031d489d1de8da35d03 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe32.h @@ -0,0 +1,1018 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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__ + +#include + +#define TRUE 1 +#define FALSE 0 + +#define VFE32_HW_NUMBER 0x3030B +#define VFE33_HW_NUMBER 0x30408 + +/* This defines total number registers in VFE. + * Each register is 4 bytes so to get the range, + * multiply this number with 4. */ +#define VFE32_REGISTER_TOTAL 0x000001CD +#define VFE33_REGISTER_TOTAL 0x000001EE + +/* 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 ABF bit 4 is set to zero and other's 1 */ +#define ABF_MASK 0xFFFFFFF7 + + +/* 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 DBPC/ABF/DBCC/ABCC bits are set to 1 all others 0 */ +#define DEMOSAIC_MASK 0xF + +/* 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 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 +#define VFE_FRAME_SKIP_PERIOD_MASK 0x0000001F /*bits 0 -4*/ + +enum VFE32_DMI_RAM_SEL { + NO_MEM_SELECTED = 0, + BLACK_LUT_RAM_BANK0 = 0x1, + BLACK_LUT_RAM_BANK1 = 0x2, + ROLLOFF_RAM0_BANK0 = 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, + ROLLOFF_RAM1_BANK0 = 0x13, + ROLLOFF_RAM0_BANK1 = 0x14, + ROLLOFF_RAM1_BANK1 = 0x15, +}; + +enum vfe_output_state { + VFE_STATE_IDLE, + VFE_STATE_START_REQUESTED, + VFE_STATE_STARTED, + VFE_STATE_STOP_REQUESTED, + VFE_STATE_STOPPED, +}; + +#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 +#define V32_DEMOSAICV3_2_OFF 0x0000066C +#define V32_DEMOSAICV3_UP_REG_CNT 5 +/* 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 44 + +#define V32_AXI_OUT_OFF 0x00000038 +#define V32_AXI_OUT_LEN 216 +#define V32_AXI_CH_INF_LEN 24 +#define V32_AXI_CFG_LEN 47 +#define V32_AXI_BUS_FMT_OFF 1 +#define V32_AXI_BUS_FMT_LEN 4 + +#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_MESH_ROLL_OFF_CFG_OFF 0x00000274 +#define V32_MESH_ROLL_OFF_CFG_LEN 16 +#define V32_MESH_ROLL_OFF_INIT_TABLE_SIZE 13 +#define V32_MESH_ROLL_OFF_DELTA_TABLE_SIZE 208 +#define V32_MESH_ROLL_OFF_DELTA_TABLE_OFFSET 32 +#define V32_GAMMA_LUT_BANK_SEL_MASK 0x00000007 + +#define V33_PCA_ROLL_OFF_CFG_LEN1 16 +#define V33_PCA_ROLL_OFF_CFG_OFF1 0x00000274 +#define V33_PCA_ROLL_OFF_CFG_LEN2 12 +#define V33_PCA_ROLL_OFF_CFG_OFF2 0x000007A8 +#define V33_PCA_ROLL_OFF_TABLE_SIZE (17 + (13*4)) +#define V33_PCA_ROLL_OFF_LUT_BANK_SEL_MASK 0x00010000 + +#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 0x000003F4 +#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_GET_HW_VERSION_OFF 0 +#define V32_GET_HW_VERSION_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 0x000002A4 +#define V32_DEMOSAICV3_ABF_LEN 180 + +#define V32_MODULE_CFG_OFF 0x00000010 +#define V32_MODULE_CFG_LEN 4 + +#define V32_ASF_SPECIAL_EFX_CFG_OFF 0x000005FC +#define V32_ASF_SPECIAL_EFX_CFG_LEN 4 + +#define V32_CLF_CFG_OFF 0x000006B0 +#define V32_CLF_CFG_LEN 72 + +#define V32_CLF_LUMA_UPDATE_OFF 0x000006B4 +#define V32_CLF_LUMA_UPDATE_LEN 60 + +#define V32_CLF_CHROMA_UPDATE_OFF 0x000006F0 +#define V32_CLF_CHROMA_UPDATE_LEN 8 + +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 +}; + +#define VFE32_GAMMA_NUM_ENTRIES 64 + +#define VFE32_LA_TABLE_LENGTH 64 + +#define VFE32_LINEARIZATON_TABLE_LENGTH 36 + +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 +}; + +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; +}; + +#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; + struct msm_free_buf ping; + struct msm_free_buf pong; + struct msm_free_buf free_buf; +}; + +/* 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) + +#define VFE_COM_STATUS 0x000FE000 + +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_STATS_AWB_SGW_CFG 0x00000554 +#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 +#define VFE_RDI0_CFG 0x00000734 +#define VFE_RDI1_CFG 0x000007A4 + +#define VFE_VIOLATION_STATUS 0x000007B4 + +#define VFE33_DMI_DATA_HI 0x000005A0 +#define VFE33_DMI_DATA_LO 0x000005A4 + +#define VFE32_OUTPUT_MODE_PT BIT(0) +#define VFE32_OUTPUT_MODE_S BIT(1) +#define VFE32_OUTPUT_MODE_V BIT(2) +#define VFE32_OUTPUT_MODE_P BIT(3) +#define VFE32_OUTPUT_MODE_T BIT(4) +#define VFE32_OUTPUT_MODE_P_ALL_CHNLS BIT(5) +#define VFE32_OUTPUT_MODE_PRIMARY BIT(6) +#define VFE32_OUTPUT_MODE_PRIMARY_ALL_CHNLS BIT(7) +#define VFE32_OUTPUT_MODE_SECONDARY BIT(8) +#define VFE32_OUTPUT_MODE_SECONDARY_ALL_CHNLS BIT(9) + +struct vfe_stats_control { + uint8_t ackPending; + uint32_t nextFrameAddrBuf; + uint32_t droppedStatsFrameCount; + uint32_t bufToRender; +}; + +struct axi_ctrl_t { + struct v4l2_subdev subdev; + struct platform_device *pdev; + struct resource *vfeirq; + spinlock_t tasklet_lock; + struct list_head tasklet_q; + + void __iomem *vfebase; + void *syncdata; + + struct resource *vfemem; + struct resource *vfeio; + struct regulator *fs_vfe; + struct clk *vfe_clk[3]; + struct tasklet_struct vfe32_tasklet; +}; + +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; + spinlock_t ihist_ack_lock; + spinlock_t rs_ack_lock; + spinlock_t cs_ack_lock; + spinlock_t comp_stats_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; + enum vfe_output_state recording_state; + int8_t update_linear; + int8_t update_rolloff; + int8_t update_la; + int8_t update_gamma; + enum vfe_output_state liveshot_state; + + void __iomem *vfebase; + uint32_t register_total; + + 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; + struct platform_device *pdev; + spinlock_t sd_notify_lock; + uint32_t hfr_mode; + uint32_t frame_skip_cnt; + uint32_t frame_skip_pattern; + uint32_t snapshot_frame_cnt; +}; + +#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]; +}; + +#define VIDIOC_MSM_AXI_INIT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 18, struct msm_cam_media_controller *) + +#define VIDIOC_MSM_AXI_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 19, struct msm_cam_media_controller *) + +#define VIDIOC_MSM_AXI_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 20, void *) + +#define VIDIOC_MSM_AXI_IRQ \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 21, void *) + +#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 0000000000000000000000000000000000000000..6a6eeb73c5c8258aeb1cbe35794b97b88ccfc80a --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x.c @@ -0,0 +1,786 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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->p0_phy = ((struct vfe_endframe *)data)->y_address; + pinfo->p1_phy = + ((struct vfe_endframe *)data)->cbcr_address; + pinfo->p2_phy = pinfo->p0_phy; + pinfo->output_id = OUTPUT_TYPE_P; + + CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n", + pinfo->p0_phy, pinfo->p1_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->p0_phy = paddr_s_y; + pinfo->p1_phy = paddr_s_cbcr; + pinfo->p2_phy = pinfo->p0_phy; + pinfo->output_id = OUTPUT_TYPE_S; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->p0_phy, pinfo->p1_phy); + } + break; + + case VFE_MSG_OUTPUT_T: { + pinfo->p0_phy = paddr_t_y; + pinfo->p1_phy = paddr_t_cbcr; + pinfo->p2_phy = pinfo->p0_phy; + pinfo->output_id = OUTPUT_TYPE_T; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->p0_phy, pinfo->p1_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.planar0_off; + paddr_t_cbcr = regptr->paddr + regptr->info.planar1_off; + } + + CDBG("config_axi1: O1, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.planar0_off, + regptr->info.planar1_off); + + bptr = &ao->output1buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum1; cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum1); cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_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.planar0_off; + paddr_s_cbcr = regptr->paddr + regptr->info.planar1_off; + CDBG("config_axi2: O2, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.planar0_off, + regptr->info.planar1_off); + + bptr = &ao->output2buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum2; cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum2); cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_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->planar0_off); + + fack.output2newcbcrbufferaddress = + (void *)(p + b->planar1_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 0000000000000000000000000000000000000000..dd3571fd331f0d559b2aa54c996408e2d027a7ef --- /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 0000000000000000000000000000000000000000..825b7cbae3b33f930ac7910807e88979a657fa6f --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x27a.c @@ -0,0 +1,745 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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->p0_phy = ((struct vfe_endframe *)data)->y_address; + pinfo->p1_phy = + ((struct vfe_endframe *)data)->cbcr_address; + pinfo->p2_phy = pinfo->p0_phy; + pinfo->output_id = OUTPUT_TYPE_P; + + CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n", + pinfo->p0_phy, pinfo->p1_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->p0_phy = paddr_s_y; + pinfo->p1_phy = paddr_s_cbcr; + pinfo->p2_phy = pinfo->p0_phy; + pinfo->output_id = OUTPUT_TYPE_S; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->p0_phy, pinfo->p1_phy); + } + break; + case VFE_MSG_OUTPUT_T: { + pinfo->p0_phy = paddr_t_y; + pinfo->p1_phy = paddr_t_cbcr; + pinfo->p2_phy = pinfo->p0_phy; + pinfo->output_id = OUTPUT_TYPE_T; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->p0_phy, pinfo->p1_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; + + msm_camio_set_perf_lvl(S_EXIT); +} + +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.planar0_off; + paddr_t_cbcr = regptr->paddr + + regptr->info.planar1_off; + } + + CDBG("config_axi1: O1, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.planar0_off, + regptr->info.planar1_off); + + bptr = &ao->output1buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum1; cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum1); cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_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.planar0_off; + paddr_s_cbcr = regptr->paddr + regptr->info.planar1_off; + + CDBG("config_axi2: O2, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.planar0_off, + regptr->info.planar1_off); + + bptr = &ao->output2buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum2; cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum2); cnt++) { + *bptr = regptr->paddr + regptr->info.planar0_off; + bptr++; + *bptr = regptr->paddr + regptr->info.planar1_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->planar0_off); + + fack.output2newcbcrbufferaddress = + (void *)(p + b->planar1_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 0000000000000000000000000000000000000000..a4882060efc664c1d6d47e85ce085b3cb5cf3d95 --- /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_vfe7x27a_v4l2.c b/drivers/media/video/msm/msm_vfe7x27a_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..135ad20100dfa8e5bafc47274b43aa188f66ede1 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x27a_v4l2.c @@ -0,0 +1,1827 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm_vfe7x27a_v4l2.h" +#include "msm.h" + +/* ADSP Messages */ +#define MSG_RESET_ACK 0 +#define MSG_STOP_ACK 1 +#define MSG_SNAPSHOT 2 +#define MSG_ILLEGAL_COMMAND 3 +#define MSG_START_ACK 4 +#define MSG_UPDATE_ACK 5 +#define MSG_OUTPUT1 6 +#define MSG_OUTPUT2 7 +#define MSG_STATS_AF 8 +#define MSG_STATS_WE 9 +#define MSG_STATS_HISTOGRAM 10 +#define MSG_EPOCH1 11 +#define MSG_EPOCH2 12 +#define MSG_VFE_ERROR 13 +#define MSG_SYNC_TIMER1_DONE 14 +#define MSG_SYNC_TIMER2_DONE 15 +#define MSG_ASYNC_TIMER1_DONE 16 +#define MSG_ASYNC_TIMER2_DONE 17 +#define MSG_CAPTURE_COMPLETE 18 +#define MSG_TABLE_CMD_ACK 19 +#define MSG_EXP_TIMEOUT_ACK 20 +#define MSG_SOF 21 +#define MSG_OUTPUT_T 22 +#define MSG_OUTPUT_S 23 + +#define VFE_ADSP_EVENT 0xFFFF +#define SNAPSHOT_MASK_MODE 0x00000001 +#define MSM_AXI_QOS_PREVIEW 122000 +#define MSM_AXI_QOS_SNAPSHOT 192000 + + +#define QDSP_CMDQUEUE 25 +#define QDSP_SCALEQUEUE 26 +#define QDSP_TABLEQUEUE 27 + +/* ADSP Scler queue Cmd IDs */ +#define VFE_SCALE_OUTPUT1_CONFIG 0 +#define VFE_SCALE_OUTPUT2_CONFIG 1 +#define VFE_SCALE_MAX 0xFFFFFFFF + +/* ADSP table queue Cmd IDs */ +#define VFE_AXI_INPUT_CONFIG 0 +#define VFE_AXI_OUTPUT_CONFIG 1 +#define VFE_RGB_GAMMA_CONFIG 2 +#define VFE_Y_GAMMA_CONFIG 3 +#define VFE_ROLL_OFF_CONFIG 4 +#define VFE_DEMOSAICv3_BPC_CFG 6 +#define VFE_DEMOSAICv3_ABF_CFG 7 +#define VFE_DEMOSAICv3_CFG 8 +#define VFE_MAX 0xFFFFFFFF + +/* ADSP cfg queue cmd IDs */ +#define VFE_RESET 0 +#define VFE_START 1 +#define VFE_STOP 2 +#define VFE_UPDATE 3 +#define VFE_CAMIF_CONFIG 4 +#define VFE_ACTIVE_REGION_CONFIG 5 +#define VFE_DEMOSAIC_CONFIG 6 +#define VFE_INPUT_FORMAT_CONFIG 7 +#define VFE_OUTPUT_CLAMP_CONFIG 8 +#define VFE_CHROMA_SUBSAMPLE_CONFIG 9 +#define VFE_BLACK_LEVEL_CONFIG 10 +#define VFE_WHITE_BALANCE_CONFIG 11 +#define VFE_COLOR_PROCESSING_CONFIG 12 +#define VFE_ADAPTIVE_FILTER_CONFIG 13 +#define VFE_FRAME_SKIP_CONFIG 14 +#define VFE_FOV_CROP 15 +#define VFE_STATS_AUTOFOCUS_CONFIG 16 +#define VFE_STATS_WB_EXP_CONFIG 17 +#define VFE_STATS_HISTOGRAM_CONFIG 18 +#define VFE_OUTPUT1_ACK 19 +#define VFE_OUTPUT2_ACK 20 +#define VFE_STATS_AUTOFOCUS_ACK 21 +#define VFE_STATS_WB_EXP_ACK 22 +#define VFE_EPOCH1_ACK 23 +#define VFE_EPOCH2_ACK 24 +#define VFE_UPDATE_CAMIF_FRAME_CONFIG 25 +#define VFE_SYNC_TIMER1_CONFIG 26 +#define VFE_SYNC_TIMER2_CONFIG 27 +#define VFE_ASYNC_TIMER1_START 28 +#define VFE_ASYNC_TIMER2_START 29 +#define VFE_STATS_AUTOFOCUS_UPDATE 30 +#define VFE_STATS_WB_EXP_UPDATE 31 +#define VFE_ROLL_OFF_UPDATE 33 +#define VFE_DEMOSAICv3_BPC_UPDATE 34 +#define VFE_TESTGEN_START 35 +#define VFE_STATS_MA 0xFFFFFFFF + +struct msg_id_map msgs_map[] = { + {MSG_RESET_ACK, MSG_ID_RESET_ACK}, + {MSG_STOP_ACK, MSG_ID_STOP_ACK}, + {MSG_SNAPSHOT, MSG_ID_SNAPSHOT_DONE}, + {MSG_ILLEGAL_COMMAND, VFE_MAX}, + {MSG_START_ACK, MSG_ID_START_ACK}, + {MSG_UPDATE_ACK, MSG_ID_UPDATE_ACK}, + {MSG_OUTPUT1, VFE_MAX}, + {MSG_OUTPUT2, VFE_MAX}, + {MSG_STATS_AF, MSG_ID_STATS_AF}, + {MSG_STATS_WE, MSG_ID_STATS_AWB_AEC}, + {MSG_STATS_HISTOGRAM, MSG_ID_STATS_IHIST}, + {MSG_EPOCH1, MSG_ID_EPOCH1}, + {MSG_EPOCH2, MSG_ID_EPOCH2}, + {MSG_VFE_ERROR, MSG_ID_CAMIF_ERROR}, + {MSG_SYNC_TIMER1_DONE, MSG_ID_SYNC_TIMER1_DONE}, + {MSG_SYNC_TIMER2_DONE, MSG_ID_SYNC_TIMER2_DONE}, + {MSG_ASYNC_TIMER1_DONE, MSG_ID_ASYNC_TIMER1_DONE}, + {MSG_ASYNC_TIMER2_DONE, MSG_ID_ASYNC_TIMER2_DONE}, + {MSG_CAPTURE_COMPLETE, MSG_CAPTURE_COMPLETE}, + {MSG_TABLE_CMD_ACK, MSG_TABLE_CMD_ACK}, + {MSG_EXP_TIMEOUT_ACK, MSG_EXP_TIMEOUT_ACK}, + {MSG_SOF, MSG_ID_SOF_ACK}, + {MSG_OUTPUT_T, MSG_ID_OUTPUT_T}, + {MSG_OUTPUT_S, MSG_ID_OUTPUT_S}, +}; + +struct cmd_id_map cmds_map[] = { + {VFE_CMD_DUMMY_0, VFE_MAX, VFE_MAX}, + {VFE_CMD_SET_CLK, VFE_MAX, VFE_MAX}, + {VFE_CMD_RESET, VFE_RESET, QDSP_CMDQUEUE, + "VFE_CMD_RESET", "VFE_RESET"}, + {VFE_CMD_START, VFE_START, QDSP_CMDQUEUE, + "VFE_CMD_START", "VFE_START"}, + {VFE_CMD_TEST_GEN_START, VFE_TESTGEN_START, QDSP_CMDQUEUE, + "VFE_CMD_TEST_GEN_START", "VFE_TESTGEN_START"}, + {VFE_CMD_OPERATION_CFG, VFE_MAX , VFE_MAX}, + {VFE_CMD_AXI_OUT_CFG, VFE_AXI_OUTPUT_CONFIG, QDSP_TABLEQUEUE, + "VFE_CMD_AXI_OUT_CFG", "VFE_AXI_OUTPUT_CONFIG"}, + {VFE_CMD_CAMIF_CFG, VFE_CAMIF_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_CAMIF_CFG", "VFE_CAMIF_CONFIG"}, + {VFE_CMD_AXI_INPUT_CFG, VFE_AXI_INPUT_CONFIG, QDSP_TABLEQUEUE, + "VFE_CMD_AXI_INPUT_CFG", "VFE_AXI_INPUT_CONFIG"}, + {VFE_CMD_BLACK_LEVEL_CFG, VFE_BLACK_LEVEL_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_BLACK_LEVEL_CFG", "VFE_BLACK_LEVEL_CONFIG"}, + {VFE_CMD_MESH_ROLL_OFF_CFG, VFE_ROLL_OFF_CONFIG, QDSP_TABLEQUEUE, + "VFE_CMD_MESH_ROLL_OFF_CFG", "VFE_ROLL_OFF_CONFIG"}, + {VFE_CMD_DEMUX_CFG, VFE_INPUT_FORMAT_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_DEMUX_CFG", "VFE_INPUT_FORMAT_CONFIG"}, + {VFE_CMD_FOV_CFG, VFE_FOV_CROP, QDSP_CMDQUEUE, + "VFE_CMD_FOV_CFG", "VFE_FOV_CROP"}, + {VFE_CMD_MAIN_SCALER_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_WB_CFG, VFE_WHITE_BALANCE_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_WB_CFG", "VFE_WHITE_BALANCE_CONFIG"}, + {VFE_CMD_COLOR_COR_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_RGB_G_CFG, VFE_RGB_GAMMA_CONFIG, QDSP_TABLEQUEUE, + "VFE_CMD_RGB_G_CFG", "VFE_RGB_GAMMA_CONFIG"}, + {VFE_CMD_LA_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_CHROMA_EN_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_CHROMA_SUP_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_MCE_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_SK_ENHAN_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_ASF_CFG, VFE_ADAPTIVE_FILTER_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_ASF_CFG", "VFE_ADAPTIVE_FILTER_CONFIG"}, + {VFE_CMD_S2Y_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_S2CbCr_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_CHROMA_SUBS_CFG, VFE_CHROMA_SUBSAMPLE_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_CHROMA_SUBS_CFG", "VFE_CHROMA_SUBSAMPLE_CONFIG"}, + {VFE_CMD_OUT_CLAMP_CFG, VFE_OUTPUT_CLAMP_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_OUT_CLAMP_CFG", "VFE_OUTPUT_CLAMP_CONFIG"}, + {VFE_CMD_FRAME_SKIP_CFG, VFE_FRAME_SKIP_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_FRAME_SKIP_CFG", "VFE_FRAME_SKIP_CONFIG"}, + {VFE_CMD_DUMMY_1, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_2, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_3, VFE_MAX, VFE_MAX}, + {VFE_CMD_UPDATE, VFE_UPDATE, QDSP_CMDQUEUE, + "VFE_CMD_UPDATE", "VFE_UPDATE"}, + {VFE_CMD_BL_LVL_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMUX_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_FOV_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_MAIN_SCALER_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_WB_UPDATE, VFE_WHITE_BALANCE_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_WB_UPDATE", "VFE_WHITE_BALANCE_CONFIG"}, + {VFE_CMD_COLOR_COR_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_RGB_G_UPDATE, VFE_RGB_GAMMA_CONFIG, QDSP_TABLEQUEUE, + "VFE_CMD_RGB_G_UPDATE", "VFE_RGB_GAMMA_CONFIG"}, + {VFE_CMD_LA_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_CHROMA_EN_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_CHROMA_SUP_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_MCE_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_SK_ENHAN_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_S2CbCr_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_S2Y_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_ASF_UPDATE, VFE_ADAPTIVE_FILTER_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_ASF_UPDATE", "VFE_ADAPTIVE_FILTER_CONFIG"}, + {VFE_CMD_FRAME_SKIP_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_CAMIF_FRAME_UPDATE, VFE_UPDATE_CAMIF_FRAME_CONFIG, + QDSP_CMDQUEUE, "VFE_CMD_CAMIF_FRAME_UPDATE", + "VFE_UPDATE_CAMIF_FRAME_CONFIG"}, + {VFE_CMD_STATS_AF_UPDATE, VFE_STATS_AUTOFOCUS_UPDATE, QDSP_CMDQUEUE, + "VFE_CMD_STATS_AF_UPDATE", "VFE_STATS_AUTOFOCUS_UPDATE"}, + {VFE_CMD_STATS_AE_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AWB_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_RS_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_CS_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_SKIN_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_IHIST_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_4, VFE_MAX, VFE_MAX}, + {VFE_CMD_EPOCH1_ACK, VFE_EPOCH1_ACK, QDSP_CMDQUEUE, + "VFE_CMD_EPOCH1_ACK", "VFE_EPOCH1_ACK"}, + {VFE_CMD_EPOCH2_ACK, VFE_EPOCH2_ACK, QDSP_CMDQUEUE, + "VFE_CMD_EPOCH2_ACK", "VFE_EPOCH2_ACK"}, + {VFE_CMD_START_RECORDING, VFE_MAX, VFE_MAX}, + {VFE_CMD_STOP_RECORDING, VFE_MAX , VFE_MAX}, + {VFE_CMD_DUMMY_5, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_6, VFE_MAX, VFE_MAX}, + {VFE_CMD_CAPTURE, VFE_START, QDSP_CMDQUEUE, + "VFE_CMD_CAPTURE", "VFE_START"}, + {VFE_CMD_DUMMY_7, VFE_MAX, VFE_MAX}, + {VFE_CMD_STOP, VFE_STOP, QDSP_CMDQUEUE, "VFE_CMD_STOP", "VFE_STOP"}, + {VFE_CMD_GET_HW_VERSION, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_FRAME_SKIP_COUNTS, VFE_MAX, VFE_MAX}, + {VFE_CMD_OUTPUT1_BUFFER_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_OUTPUT2_BUFFER_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_OUTPUT3_BUFFER_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_JPEG_OUT_BUF_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_RAW_OUT_BUF_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_RAW_IN_BUF_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AF_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AE_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AWB_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_RS_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_CS_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_SKIN_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_IHIST_ENQ, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_8, VFE_MAX, VFE_MAX}, + {VFE_CMD_JPEG_ENC_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_9, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AF_START, VFE_STATS_AUTOFOCUS_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_STATS_AF_START", "VFE_STATS_AUTOFOCUS_CONFIG"}, + {VFE_CMD_STATS_AF_STOP, VFE_STATS_AUTOFOCUS_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_STATS_AF_STOP", "VFE_STATS_AUTOFOCUS_CONFIG"}, + {VFE_CMD_STATS_AE_START, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AE_STOP, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AWB_START, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_AWB_STOP, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_RS_START, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_RS_STOP, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_CS_START, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_CS_STOP, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_SKIN_START, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_SKIN_STOP, VFE_MAX, VFE_MAX}, + {VFE_CMD_STATS_IHIST_START, VFE_STATS_HISTOGRAM_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_STATS_IHIST_START", "VFE_STATS_HISTOGRAM_CONFIG"}, + {VFE_CMD_STATS_IHIST_STOP, VFE_MAX, VFE_MAX}, + {VFE_CMD_DUMMY_10, VFE_MAX, VFE_MAX}, + {VFE_CMD_SYNC_TIMER_SETTING, VFE_MAX, VFE_MAX}, + {VFE_CMD_ASYNC_TIMER_SETTING, VFE_MAX, VFE_MAX}, + {VFE_CMD_LIVESHOT, VFE_MAX, VFE_MAX}, + {VFE_CMD_LA_SETUP, VFE_MAX, VFE_MAX}, + {VFE_CMD_LINEARIZATION_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3, VFE_DEMOSAICv3_CFG, QDSP_TABLEQUEUE, + "VFE_CMD_DEMOSAICV3", "VFE_DEMOSAICv3_CFG"}, + {VFE_CMD_DEMOSAICV3_ABCC_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3_DBCC_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3_DBPC_CFG, VFE_DEMOSAICv3_BPC_CFG, QDSP_TABLEQUEUE, + "VFE_CMD_DEMOSAICV3_DBPC_CFG", "VFE_DEMOSAICv3_BPC_CFG"}, + {VFE_CMD_DEMOSAICV3_ABF_CFG, VFE_DEMOSAICv3_ABF_CFG, QDSP_TABLEQUEUE, + "VFE_CMD_DEMOSAICV3_ABF_CFG", "VFE_DEMOSAICv3_ABF_CFG"}, + {VFE_CMD_DEMOSAICV3_ABCC_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3_DBCC_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3_DBPC_UPDATE, VFE_DEMOSAICv3_BPC_UPDATE, + QDSP_CMDQUEUE, "VFE_CMD_DEMOSAICV3_DBPC_UPDATE", + "VFE_DEMOSAICv3_BPC_UPDATE"}, + {VFE_CMD_XBAR_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_MODULE_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_ZSL, VFE_START, QDSP_CMDQUEUE, + "VFE_CMD_ZSL", "VFE_START"}, + {VFE_CMD_LINEARIZATION_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3_ABF_UPDATE, VFE_DEMOSAICv3_ABF_CFG, + QDSP_TABLEQUEUE, "VFE_CMD_DEMOSAICV3_ABF_UPDATE", + "VFE_DEMOSAICv3_ABF_CFG"}, + {VFE_CMD_CLF_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_CLF_LUMA_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_CLF_CHROMA_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_PCA_ROLL_OFF_CFG, VFE_MAX, VFE_MAX}, + {VFE_CMD_PCA_ROLL_OFF_UPDATE, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_REG_DUMP, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_LINEARIZATON_TABLE, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_MESH_ROLLOFF_TABLE, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_PCA_ROLLOFF_TABLE, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_RGB_G_TABLE, VFE_MAX, VFE_MAX}, + {VFE_CMD_GET_LA_TABLE, VFE_MAX, VFE_MAX}, + {VFE_CMD_DEMOSAICV3_UPDATE, VFE_DEMOSAICv3_CFG, QDSP_TABLEQUEUE, + "VFE_CMD_DEMOSAICV3_UPDATE", "VFE_DEMOSAICv3_CFG"}, + {VFE_CMD_ACTIVE_REGION_CFG, VFE_ACTIVE_REGION_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_ACTIVE_REGION_CFG", "VFE_ACTIVE_REGION_CONFIG"}, + {VFE_CMD_COLOR_PROCESSING_CONFIG, VFE_COLOR_PROCESSING_CONFIG, + QDSP_CMDQUEUE, "VFE_CMD_COLOR_PROCESSING_CONFIG", + "VFE_COLOR_PROCESSING_CONFIG"}, + {VFE_CMD_STATS_WB_AEC_CONFIG, VFE_STATS_WB_EXP_CONFIG, QDSP_CMDQUEUE, + "VFE_CMD_STATS_WB_AEC_CONFIG", "VFE_STATS_WB_EXP_CONFIG"}, + {VFE_CMD_STATS_WB_AEC_UPDATE, VFE_STATS_WB_EXP_UPDATE, QDSP_CMDQUEUE, + "VFE_CMD_STATS_WB_AEC_UPDATE", "VFE_STATS_WB_EXP_UPDATE"}, + {VFE_CMD_Y_GAMMA_CONFIG, VFE_Y_GAMMA_CONFIG, QDSP_TABLEQUEUE, + "VFE_CMD_Y_GAMMA_CONFIG", "VFE_Y_GAMMA_CONFIG"}, + {VFE_CMD_SCALE_OUTPUT1_CONFIG, VFE_SCALE_OUTPUT1_CONFIG, + QDSP_SCALEQUEUE, "VFE_CMD_SCALE_OUTPUT1_CONFIG", + "VFE_SCALE_OUTPUT1_CONFIG"}, + {VFE_CMD_SCALE_OUTPUT2_CONFIG, VFE_SCALE_OUTPUT2_CONFIG, + QDSP_SCALEQUEUE, "VFE_CMD_SCALE_OUTPUT2_CONFIG", + "VFE_SCALE_OUTPUT2_CONFIG"}, + {VFE_CMD_CAPTURE_RAW, VFE_START, QDSP_CMDQUEUE, + "VFE_CMD_CAPTURE_RAW", "VFE_START"}, + {VFE_CMD_RECONFIG_VFE, VFE_MAX, VFE_MAX}, +}; + + +static struct msm_adsp_module *qcam_mod; +static struct msm_adsp_module *vfe_mod; +static void *extdata; +static uint32_t extlen; + +struct mutex vfe_lock; +static uint8_t vfestopped; + +static struct stop_event stopevent; + +static uint32_t op_mode; +static uint32_t raw_mode; +static struct vfe2x_ctrl_type *vfe2x_ctrl; + +static void vfe2x_send_isp_msg( + struct vfe2x_ctrl_type *vctrl, + uint32_t isp_msg_id) +{ + struct isp_msg_event isp_msg_evt; + + isp_msg_evt.msg_id = isp_msg_id; + isp_msg_evt.sof_count = vfe2x_ctrl->vfeFrameId; + v4l2_subdev_notify(&vctrl->subdev, + NOTIFY_ISP_MSG_EVT, + (void *)&isp_msg_evt); +} + +static void vfe_send_outmsg(struct v4l2_subdev *sd, uint8_t msgid, + uint32_t ch0_paddr, uint32_t ch1_paddr) +{ + struct isp_msg_output msg; + + msg.output_id = msgid; + msg.buf.ch_paddr[0] = ch0_paddr; + msg.buf.ch_paddr[1] = ch1_paddr; + msg.frameCounter = vfe2x_ctrl->vfeFrameId; + + v4l2_subdev_notify(&vfe2x_ctrl->subdev, + NOTIFY_VFE_MSG_OUT, + &msg); + return; +} + +static void vfe_send_stats_msg(uint32_t buf_addr, uint32_t msg_id) +{ + struct isp_msg_stats msg_stats; + + msg_stats.frameCounter = vfe2x_ctrl->vfeFrameId; + msg_stats.buffer = buf_addr; + msg_stats.id = msg_id; + + v4l2_subdev_notify(&vfe2x_ctrl->subdev, + NOTIFY_VFE_MSG_STATS, + &msg_stats); +} + +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]; + void *data = NULL; + struct buf_info *outch = NULL; + uint32_t y_phy, cbcr_phy; + struct table_cmd *table_pending = NULL; + unsigned long flags; + void *cmd_data = NULL; + unsigned char buf[256]; + struct msm_free_buf *free_buf = NULL; + struct vfe_outputack fack; + int i; + + CDBG("%s:id=%d\n", __func__, id); + if (id != VFE_ADSP_EVENT) { + data = kzalloc(len, GFP_ATOMIC); + if (!data) { + pr_err("%s: rp: cannot allocate buffer\n", __func__); + return; + } + } + if (id == VFE_ADSP_EVENT) { + /* event */ + getevent(evt_buf, sizeof(evt_buf)); + CDBG("%s:event:msg_id=%d\n", __func__, id); + } else { + /* messages */ + getevent(data, len); + CDBG("%s:messages:msg_id=%d\n", __func__, id); + + switch (id) { + case MSG_SNAPSHOT: + msm_camio_set_perf_lvl(S_PREVIEW); + vfe_7x_ops(driver_data, MSG_OUTPUT_S, len, getevent); + if (!raw_mode) + vfe_7x_ops(driver_data, MSG_OUTPUT_T, + len, getevent); + vfe2x_send_isp_msg(vfe2x_ctrl, MSG_ID_SNAPSHOT_DONE); + kfree(data); + return; + case MSG_OUTPUT_S: + outch = &vfe2x_ctrl->snap; + y_phy = outch->ping.ch_paddr[0]; + cbcr_phy = outch->ping.ch_paddr[1]; + CDBG("MSG_OUTPUT_S: %x %x\n", + (unsigned int)y_phy, (unsigned int)cbcr_phy); + vfe_send_outmsg(&vfe2x_ctrl->subdev, + MSG_ID_OUTPUT_PRIMARY, + y_phy, cbcr_phy); + break; + case MSG_OUTPUT_T: + outch = &vfe2x_ctrl->thumb; + y_phy = outch->ping.ch_paddr[0]; + cbcr_phy = outch->ping.ch_paddr[1]; + CDBG("MSG_OUTPUT_T: %x %x\n", + (unsigned int)y_phy, (unsigned int)cbcr_phy); + vfe_send_outmsg(&vfe2x_ctrl->subdev, + MSG_ID_OUTPUT_SECONDARY, + y_phy, cbcr_phy); + break; + case MSG_OUTPUT1: + if (op_mode & SNAPSHOT_MASK_MODE) { + kfree(data); + return; + } else { + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY + ); + CDBG("free_buf = %x\n", + (unsigned int) free_buf); + if (free_buf) { + fack.header = VFE_OUTPUT1_ACK; + + fack.output2newybufferaddress = + (void *)(free_buf->ch_paddr[0]); + + fack.output2newcbcrbufferaddress = + (void *)(free_buf->ch_paddr[1]); + + cmd_data = &fack; + len = sizeof(fack); + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + } else { + fack.header = VFE_OUTPUT1_ACK; + fack.output2newybufferaddress = + (void *) + ((struct vfe_endframe *)data)->y_address; + fack.output2newcbcrbufferaddress = + (void *) + ((struct vfe_endframe *)data)->cbcr_address; + cmd_data = &fack; + len = sizeof(fack); + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + if (!vfe2x_ctrl->zsl_mode) { + kfree(data); + return; + } + } + } + y_phy = ((struct vfe_endframe *)data)->y_address; + cbcr_phy = ((struct vfe_endframe *)data)->cbcr_address; + + + CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n", + y_phy, cbcr_phy); + if (free_buf) { + for (i = 0; i < 3; i++) { + if (vfe2x_ctrl->free_buf.buf[i]. + ch_paddr[0] == y_phy) { + vfe2x_ctrl->free_buf. + buf[i].ch_paddr[0] = + free_buf->ch_paddr[0]; + vfe2x_ctrl->free_buf. + buf[i].ch_paddr[1] = + free_buf->ch_paddr[1]; + break; + } + } + if (i == 3) + CDBG("Address doesnt match\n"); + } + memcpy(((struct vfe_frame_extra *)extdata), + &((struct vfe_endframe *)data)->extra, + sizeof(struct vfe_frame_extra)); + + vfe2x_ctrl->vfeFrameId = + ((struct vfe_frame_extra *)extdata)->frame_id; + vfe_send_outmsg(&vfe2x_ctrl->subdev, + MSG_ID_OUTPUT_SECONDARY, + y_phy, cbcr_phy); + break; + case MSG_OUTPUT2: + if (op_mode & SNAPSHOT_MASK_MODE) { + kfree(data); + return; + } else { + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + CDBG("free_buf = %x\n", (unsigned int) free_buf); + if (free_buf) { + fack.header = VFE_OUTPUT2_ACK; + + fack.output2newybufferaddress = + (void *)(free_buf->ch_paddr[0]); + + fack.output2newcbcrbufferaddress = + (void *)(free_buf->ch_paddr[1]); + + cmd_data = &fack; + len = sizeof(fack); + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + } else { + fack.header = VFE_OUTPUT2_ACK; + fack.output2newybufferaddress = + (void *) + ((struct vfe_endframe *)data)->y_address; + fack.output2newcbcrbufferaddress = + (void *) + ((struct vfe_endframe *)data)->cbcr_address; + cmd_data = &fack; + len = sizeof(fack); + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + if (!vfe2x_ctrl->zsl_mode) { + kfree(data); + return; + } + } + } + y_phy = ((struct vfe_endframe *)data)->y_address; + cbcr_phy = ((struct vfe_endframe *)data)->cbcr_address; + + + CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n", + y_phy, cbcr_phy); + if (free_buf) { + for (i = 0; i < 3; i++) { + if (vfe2x_ctrl->free_buf.buf[i]. + ch_paddr[0] == y_phy) { + vfe2x_ctrl->free_buf. + buf[i].ch_paddr[0] = + free_buf->ch_paddr[0]; + vfe2x_ctrl->free_buf. + buf[i].ch_paddr[1] = + free_buf->ch_paddr[1]; + break; + } + } + if (i == 3) + CDBG("Address doesnt match\n"); + } + memcpy(((struct vfe_frame_extra *)extdata), + &((struct vfe_endframe *)data)->extra, + sizeof(struct vfe_frame_extra)); + + vfe2x_ctrl->vfeFrameId = + ((struct vfe_frame_extra *)extdata)->frame_id; + vfe_send_outmsg(&vfe2x_ctrl->subdev, + MSG_ID_OUTPUT_PRIMARY, + y_phy, cbcr_phy); + break; + case MSG_RESET_ACK: + case MSG_START_ACK: + case MSG_UPDATE_ACK: + case MSG_VFE_ERROR: + case MSG_SYNC_TIMER1_DONE: + case MSG_SYNC_TIMER2_DONE: + vfe2x_send_isp_msg(vfe2x_ctrl, msgs_map[id].isp_id); + if (id == MSG_START_ACK) + vfe2x_ctrl->vfe_started = 1; + if (id == MSG_VFE_ERROR) { + uint16_t *ptr; + struct vfe_error_msg *VFE_ErrorMessageBuffer + = data; + ptr = data; + CDBG("Error: %x %x\n", ptr[0], ptr[1]); + CDBG("CAMIF_Error = %d\n", + VFE_ErrorMessageBuffer->camif_error); + CDBG("output1YBusOverflow = %d\n", + VFE_ErrorMessageBuffer-> + output1ybusoverflow); + CDBG("output1CbCrBusOverflow = %d\n", + VFE_ErrorMessageBuffer-> + output1cbcrbusoverflow); + CDBG("output2YBusOverflow = %d\n", + VFE_ErrorMessageBuffer-> + output2ybusoverflow); + CDBG("output2CbCrBusOverflow = %d\n", + VFE_ErrorMessageBuffer-> + output2cbcrbusoverflow); + CDBG("autofocusStatBusOverflow = %d\n", + VFE_ErrorMessageBuffer-> + autofocusstatbusoverflow); + CDBG("WB_EXPStatBusOverflow = %d\n", + VFE_ErrorMessageBuffer-> + wb_expstatbusoverflow); + CDBG("AXIError = %d\n", + VFE_ErrorMessageBuffer-> + axierror); + CDBG("CAMIF_Staus = %d\n", + VFE_ErrorMessageBuffer-> + camif_staus); + CDBG("pixel_count = %d\n", + VFE_ErrorMessageBuffer-> + pixel_count); + CDBG("line_count = %d\n", + VFE_ErrorMessageBuffer-> + line_count); + } + break; + case MSG_SOF: + vfe2x_ctrl->vfeFrameId++; + if (vfe2x_ctrl->vfeFrameId == 0) + vfe2x_ctrl->vfeFrameId = 1; /* wrapped back */ + if ((op_mode & SNAPSHOT_MASK_MODE) && !raw_mode) { + pr_err("Ignore SOF for snapshot\n"); + kfree(data); + return; + } + vfe2x_send_isp_msg(vfe2x_ctrl, MSG_ID_SOF_ACK); + if (raw_mode) + vfe2x_send_isp_msg(vfe2x_ctrl, + MSG_ID_START_ACK); + break; + case MSG_STOP_ACK: + stopevent.state = 1; + vfe2x_ctrl->vfe_started = 0; + wake_up(&stopevent.wait); + vfe2x_send_isp_msg(vfe2x_ctrl, MSG_ID_STOP_ACK); + break; + case MSG_STATS_AF: + case MSG_STATS_WE: + vfe_send_stats_msg(*(uint32_t *)data, + msgs_map[id].isp_id); + break; + default: + if (MSG_TABLE_CMD_ACK != id) + vfe2x_send_isp_msg(vfe2x_ctrl, + msgs_map[id].isp_id); + break; + } + } + if (MSG_TABLE_CMD_ACK == id) { + spin_lock_irqsave(&vfe2x_ctrl->table_lock, flags); + vfe2x_ctrl->tableack_pending = 0; + if (list_empty(&vfe2x_ctrl->table_q)) { + if (vfe2x_ctrl->start_pending) { + CDBG("Send START\n"); + cmd_data = buf; + *(uint32_t *)cmd_data = VFE_START; + memcpy(((char *)cmd_data) + 4, + &vfe2x_ctrl->start_cmd, + sizeof(vfe2x_ctrl->start_cmd)); + /* Send Start cmd here */ + len = sizeof(vfe2x_ctrl->start_cmd) + 4; + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + vfe2x_ctrl->start_pending = 0; + } else if (vfe2x_ctrl->stop_pending) { + CDBG("Send STOP\n"); + cmd_data = buf; + *(uint32_t *)cmd_data = VFE_STOP; + /* Send Stop cmd here */ + len = 4; + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + vfe2x_ctrl->stop_pending = 0; + } else if (vfe2x_ctrl->update_pending) { + CDBG("Send Update\n"); + cmd_data = buf; + *(uint32_t *)cmd_data = VFE_UPDATE; + /* Send Update cmd here */ + len = 4; + msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + cmd_data, len); + vfe2x_ctrl->update_pending = 0; + } + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + kfree(data); + return; + } + table_pending = list_first_entry(&vfe2x_ctrl->table_q, + struct table_cmd, list); + if (!table_pending) { + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + kfree(data); + return; + } + msm_adsp_write(vfe_mod, table_pending->queue, + table_pending->cmd, table_pending->size); + list_del(&table_pending->list); + kfree(table_pending->cmd); + kfree(table_pending); + vfe2x_ctrl->tableack_pending = 1; + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + } else if (!vfe2x_ctrl->tableack_pending) { + if (!list_empty(&vfe2x_ctrl->table_q)) { + kfree(data); + return; + } + } + kfree(data); +} + +static struct msm_adsp_ops vfe_7x_sync = { + .event = vfe_7x_ops, +}; + +static int vfe_7x_config_axi(int mode, + struct buf_info *ad, struct axiout *ao) +{ + unsigned long *bptr; + int cnt; + int rc = 0; + int o_mode = 0; + + if (op_mode & SNAPSHOT_MASK_MODE) + o_mode = SNAPSHOT_MASK_MODE; + + if (mode == OUTPUT_SEC) { + /* Thumbnail */ + if (vfe2x_ctrl->zsl_mode) { + ao->output1buffer1_y_phy = ad->ping.ch_paddr[0]; + ao->output1buffer1_cbcr_phy = ad->ping.ch_paddr[1]; + ao->output1buffer2_y_phy = ad->pong.ch_paddr[0]; + ao->output1buffer2_cbcr_phy = ad->pong.ch_paddr[1]; + ao->output1buffer3_y_phy = ad->free_buf.ch_paddr[0]; + ao->output1buffer3_cbcr_phy = ad->free_buf.ch_paddr[1]; + bptr = &ao->output1buffer4_y_phy; + for (cnt = 0; cnt < 5; cnt++) { + *bptr = ad->pong.ch_paddr[0]; + bptr++; + *bptr = ad->pong.ch_paddr[1]; + bptr++; + } + } else { + ao->output1buffer1_y_phy = ad->ping.ch_paddr[0]; + ao->output1buffer1_cbcr_phy = ad->ping.ch_paddr[1]; + ao->output1buffer2_y_phy = ad->pong.ch_paddr[0]; + ao->output1buffer2_cbcr_phy = ad->pong.ch_paddr[1]; + bptr = &ao->output1buffer3_y_phy; + for (cnt = 0; cnt < 6; cnt++) { + *bptr = ad->pong.ch_paddr[0]; + bptr++; + *bptr = ad->pong.ch_paddr[1]; + bptr++; + } + } + } else if (mode == OUTPUT_PRIM && o_mode != SNAPSHOT_MASK_MODE) { + /* Preview */ + ao->output2buffer1_y_phy = ad->ping.ch_paddr[0]; + ao->output2buffer1_cbcr_phy = ad->ping.ch_paddr[1]; + ao->output2buffer2_y_phy = ad->pong.ch_paddr[0]; + ao->output2buffer2_cbcr_phy = ad->pong.ch_paddr[1]; + ao->output2buffer3_y_phy = ad->free_buf.ch_paddr[0]; + ao->output2buffer3_cbcr_phy = ad->free_buf.ch_paddr[1]; + bptr = &ao->output2buffer4_y_phy; + for (cnt = 0; cnt < 5; cnt++) { + *bptr = ad->pong.ch_paddr[0]; + bptr++; + *bptr = ad->pong.ch_paddr[1]; + bptr++; + } + CDBG("%x %x\n", (unsigned int)ao->output2buffer1_y_phy, + (unsigned int)ao->output2buffer1_cbcr_phy); + CDBG("%x %x\n", (unsigned int)ao->output2buffer2_y_phy, + (unsigned int)ao->output2buffer2_cbcr_phy); + CDBG("%x %x\n", (unsigned int)ao->output2buffer3_y_phy, + (unsigned int)ao->output2buffer3_cbcr_phy); + CDBG("%x %x\n", (unsigned int)ao->output2buffer4_y_phy, + (unsigned int)ao->output2buffer4_cbcr_phy); + CDBG("%x %x\n", (unsigned int)ao->output2buffer5_y_phy, + (unsigned int)ao->output2buffer5_cbcr_phy); + CDBG("%x %x\n", (unsigned int)ao->output2buffer6_y_phy, + (unsigned int)ao->output2buffer6_cbcr_phy); + CDBG("%x %x\n", (unsigned int)ao->output2buffer7_y_phy, + (unsigned int)ao->output2buffer7_cbcr_phy); + vfe2x_ctrl->free_buf.buf[0].ch_paddr[0] = ad->ping.ch_paddr[0]; + vfe2x_ctrl->free_buf.buf[0].ch_paddr[1] = ad->ping.ch_paddr[1]; + vfe2x_ctrl->free_buf.buf[1].ch_paddr[0] = ad->pong.ch_paddr[0]; + vfe2x_ctrl->free_buf.buf[1].ch_paddr[1] = ad->pong.ch_paddr[1]; + vfe2x_ctrl->free_buf.buf[2].ch_paddr[0] = + ad->free_buf.ch_paddr[0]; + vfe2x_ctrl->free_buf.buf[2].ch_paddr[1] = + ad->free_buf.ch_paddr[1]; + } else if (mode == OUTPUT_PRIM && o_mode == SNAPSHOT_MASK_MODE) { + vfe2x_ctrl->reconfig_vfe = 0; + if (raw_mode) { + ao->output2buffer1_y_phy = ad->ping.ch_paddr[0]; + ao->output2buffer1_cbcr_phy = ad->ping.ch_paddr[0]; + ao->output2buffer2_y_phy = ad->pong.ch_paddr[0]; + ao->output2buffer2_cbcr_phy = ad->pong.ch_paddr[0]; + } else { + ao->output2buffer1_y_phy = ad->ping.ch_paddr[0]; + ao->output2buffer1_cbcr_phy = ad->ping.ch_paddr[1]; + ao->output2buffer2_y_phy = ad->pong.ch_paddr[0]; + ao->output2buffer2_cbcr_phy = ad->pong.ch_paddr[1]; + } + bptr = &ao->output2buffer3_y_phy; + for (cnt = 0; cnt < 6; cnt++) { + *bptr = ad->pong.ch_paddr[0]; + bptr++; + *bptr = ad->pong.ch_paddr[1]; + bptr++; + } + } + + return rc; +} + +static void vfe2x_subdev_notify(int id, int path) +{ + struct msm_vfe_resp rp; + unsigned long flags = 0; + spin_lock_irqsave(&vfe2x_ctrl->sd_notify_lock, flags); + memset(&rp, 0, sizeof(struct msm_vfe_resp)); + CDBG("vfe2x_subdev_notify : msgId = %d\n", id); + rp.evt_msg.type = MSM_CAMERA_MSG; + rp.evt_msg.msg_id = path; + rp.type = id; + v4l2_subdev_notify(&vfe2x_ctrl->subdev, NOTIFY_VFE_BUF_EVT, &rp); + spin_unlock_irqrestore(&vfe2x_ctrl->sd_notify_lock, flags); +} + +static struct msm_free_buf *vfe2x_check_free_buffer(int id, int path) +{ + struct buf_info *outch = NULL; + + vfe2x_subdev_notify(id, path); + if (op_mode & SNAPSHOT_MASK_MODE) { + if (path == VFE_MSG_OUTPUT_PRIMARY) + outch = &vfe2x_ctrl->snap; + else if (path == VFE_MSG_OUTPUT_SECONDARY) + outch = &vfe2x_ctrl->thumb; + } else { + if (path == VFE_MSG_OUTPUT_PRIMARY) { + if (vfe2x_ctrl->zsl_mode) + outch = &vfe2x_ctrl->zsl_prim; + else + outch = &vfe2x_ctrl->prev; + } else if (path == VFE_MSG_OUTPUT_SECONDARY) + outch = &vfe2x_ctrl->zsl_sec; + } + if (outch->free_buf.ch_paddr[0]) + return &outch->free_buf; + + return NULL; +} + +static int vfe2x_configure_pingpong_buffers(int id, int path) +{ + struct buf_info *outch = NULL; + int rc = 0; + + vfe2x_subdev_notify(id, path); + CDBG("Opmode = %d\n", op_mode); + if (op_mode & SNAPSHOT_MASK_MODE) { + if (path == VFE_MSG_OUTPUT_PRIMARY) + outch = &vfe2x_ctrl->snap; + else if (path == VFE_MSG_OUTPUT_SECONDARY) + outch = &vfe2x_ctrl->thumb; + } else { + if (path == VFE_MSG_OUTPUT_PRIMARY) { + if (vfe2x_ctrl->zsl_mode) + outch = &vfe2x_ctrl->zsl_prim; + else + outch = &vfe2x_ctrl->prev; + } else if (path == VFE_MSG_OUTPUT_SECONDARY) + outch = &vfe2x_ctrl->zsl_sec; + } + if (outch->ping.ch_paddr[0] && outch->pong.ch_paddr[0]) { + /* Configure Preview Ping Pong */ + CDBG("%s Configure ping/pong address for %d", + __func__, path); + } else { + pr_err("%s ping/pong addr is null!!", __func__); + rc = -EINVAL; + } + return rc; +} + +static struct buf_info *vfe2x_get_ch(int path) +{ + struct buf_info *ch = NULL; + + CDBG("path = %d op_mode = %d\n", path, op_mode); + /* TODO: Remove Mode specific stuff */ + if (op_mode & SNAPSHOT_MASK_MODE) { + if (path == VFE_MSG_OUTPUT_SECONDARY) + ch = &vfe2x_ctrl->thumb; + else if (path == VFE_MSG_OUTPUT_PRIMARY) + ch = &vfe2x_ctrl->snap; + } else { + if (path == VFE_MSG_OUTPUT_PRIMARY) { + if (vfe2x_ctrl->zsl_mode) + ch = &vfe2x_ctrl->zsl_prim; + else + ch = &vfe2x_ctrl->prev; + } else if (path == VFE_MSG_OUTPUT_SECONDARY) + ch = &vfe2x_ctrl->zsl_sec; + } + + BUG_ON(ch == NULL); + return ch; +} + +static long msm_vfe_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int subdev_cmd, void *arg) +{ + struct msm_isp_cmd vfecmd; + struct msm_camvfe_params *vfe_params = + (struct msm_camvfe_params *)arg; + struct msm_vfe_cfg_cmd *cmd = vfe_params->vfe_cfg; + struct table_cmd *table_pending; + long rc = 0; + void *data = vfe_params->data; + + struct msm_pmem_region *regptr; + unsigned char buf[256]; + + struct vfe_stats_ack sack; + struct axidata *axid; + uint32_t i; + uint32_t header = 0; + uint32_t queue = 0; + 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; + unsigned long flags; + struct msm_free_buf *free_buf = NULL; + struct vfe_outputack fack; + + CDBG("msm_vfe_subdev_ioctl is called\n"); + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE && + cmd->cmd_type != CMD_CONFIG_PING_ADDR && + cmd->cmd_type != CMD_CONFIG_PONG_ADDR && + cmd->cmd_type != CMD_CONFIG_FREE_BUF_ADDR && + cmd->cmd_type != CMD_VFE_BUFFER_RELEASE) { + if (copy_from_user(&vfecmd, + (void __user *)(cmd->value), + sizeof(vfecmd))) { + pr_err("copy_from_user in msm_vfe_subdev_ioctl fail\n"); + return -EFAULT; + } + } + + switch (cmd->cmd_type) { + case CMD_VFE_BUFFER_RELEASE: { + if (!(vfe2x_ctrl->vfe_started) || op_mode == 1) + return 0; + if (op_mode & SNAPSHOT_MASK_MODE) { + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY); + } else { + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + if (free_buf) { + fack.header = VFE_OUTPUT2_ACK; + + fack.output2newybufferaddress = + (void *)(free_buf->ch_paddr[0]); + + fack.output2newcbcrbufferaddress = + (void *)(free_buf->ch_paddr[1]); + + cmd_data = &fack; + vfecmd.length = sizeof(fack) - 4; + queue = QDSP_CMDQUEUE; + } + } + } + break; + case CMD_CONFIG_PING_ADDR: { + int path = *((int *)cmd->value); + struct buf_info *outch = vfe2x_get_ch(path); + outch->ping = *((struct msm_free_buf *)data); + } + return 0; + case CMD_CONFIG_PONG_ADDR: { + int path = *((int *)cmd->value); + struct buf_info *outch = vfe2x_get_ch(path); + outch->pong = *((struct msm_free_buf *)data); + } + return 0; + + case CMD_CONFIG_FREE_BUF_ADDR: { + int path = *((int *)cmd->value); + struct buf_info *outch = vfe2x_get_ch(path); + outch->free_buf = *((struct msm_free_buf *)data); + } + return 0; + + 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((char *)scfg + 4, + (void __user *)(vfecmd.value), + vfecmd.length)) { + + rc = -EFAULT; + goto config_done; + } + + CDBG("STATS_ENABLE: bufnum = %d, enabling = %d\n", + axid->bufnum1, scfg->wb_expstatsenable); + + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_failure; + } + *(uint32_t *)scfg = header; + 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: { + CDBG("CMD_STATS_AF_ENABLE CMD_STATS_AF_AXI_CFG\n"); + 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((char *)sfcfg + 4, + (void __user *)(vfecmd.value), + vfecmd.length)) { + + rc = -EFAULT; + goto config_done; + } + + CDBG("AF_ENABLE: bufnum = %d, enabling = %d\n", + axid->bufnum1, sfcfg->af_enable); + + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_failure; + } + *(uint32_t *)sfcfg = header; + CDBG("Number of buffers = %d\n", axid->bufnum1); + 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_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 = VFE_STATS_WB_EXP_ACK; + sack.bufaddr = (void *)*(uint32_t *)data; + + queue = QDSP_CMDQUEUE; + vfecmd.length = sizeof(struct vfe_stats_ack) - 4; + 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 = VFE_STATS_AUTOFOCUS_ACK; + sack.bufaddr = (void *)*(uint32_t *)data; + + queue = QDSP_CMDQUEUE; + vfecmd.length = sizeof(struct vfe_stats_ack) - 4; + cmd_data = &sack; + } + break; + case CMD_GENERAL: + case CMD_STATS_DISABLE: { + CDBG("CMD_GENERAL:%d %d\n", vfecmd.id, vfecmd.length); + if (vfecmd.id == VFE_CMD_OPERATION_CFG) { + if (copy_from_user(&vfe2x_ctrl->start_cmd, + (void __user *)(vfecmd.value), + vfecmd.length)) + rc = -EFAULT; + op_mode = vfe2x_ctrl->start_cmd.mode_of_operation; + return rc; + } + if (vfecmd.id == VFE_CMD_RECONFIG_VFE) { + CDBG("VFE is RECONFIGURED\n"); + vfe2x_ctrl->reconfig_vfe = 1; + return 0; + } + if (vfecmd.length > 256 - 4) { + cmd_data_alloc = + cmd_data = kmalloc(vfecmd.length + 4, GFP_ATOMIC); + if (!cmd_data) { + rc = -ENOMEM; + goto config_failure; + } + } else + cmd_data = buf; + + if (copy_from_user(((char *)cmd_data) + 4, + (void __user *)(vfecmd.value), + vfecmd.length)) { + + rc = -EFAULT; + goto config_done; + } + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_done; + } + CDBG("%s %s\n", cmds_map[vfecmd.id].isp_id_name, + cmds_map[vfecmd.id].vfe_id_name); + *(uint32_t *)cmd_data = header; + if (queue == QDSP_CMDQUEUE) { + switch (vfecmd.id) { + case VFE_CMD_RESET: + msm_camio_vfe_blk_reset_2(); + vfestopped = 0; + break; + case VFE_CMD_START: + case VFE_CMD_CAPTURE: + case VFE_CMD_CAPTURE_RAW: + case VFE_CMD_ZSL: + spin_lock_irqsave(&vfe2x_ctrl->table_lock, + flags); + if ((!list_empty(&vfe2x_ctrl->table_q)) || + vfe2x_ctrl->tableack_pending) { + CDBG("start pending\n"); + vfe2x_ctrl->start_pending = 1; + spin_unlock_irqrestore( + &vfe2x_ctrl->table_lock, + flags); + return 0; + } + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, + flags); + vfecmd.length = sizeof(vfe2x_ctrl->start_cmd); + memcpy(((char *)cmd_data) + 4, + &vfe2x_ctrl->start_cmd, + sizeof(vfe2x_ctrl->start_cmd)); + 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_CMD_STOP: + vfestopped = 1; + spin_lock_irqsave(&vfe2x_ctrl->table_lock, + flags); + if ((!list_empty(&vfe2x_ctrl->table_q)) || + vfe2x_ctrl->tableack_pending) { + CDBG("stop pending\n"); + vfe2x_ctrl->stop_pending = 1; + spin_unlock_irqrestore( + &vfe2x_ctrl->table_lock, + flags); + return 0; + } + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, + flags); + vfe2x_ctrl->vfe_started = 0; + goto config_send; + case VFE_CMD_UPDATE: + spin_lock_irqsave(&vfe2x_ctrl->table_lock, + flags); + if ((!list_empty(&vfe2x_ctrl->table_q)) || + vfe2x_ctrl->tableack_pending) { + CDBG("update pending\n"); + vfe2x_ctrl->update_pending = 0; + vfe2x_send_isp_msg(vfe2x_ctrl, + msgs_map[MSG_UPDATE_ACK]. + isp_id); + spin_unlock_irqrestore( + &vfe2x_ctrl->table_lock, + flags); + return 0; + } + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, + flags); + goto config_send; + default: + break; + } + } /* QDSP_CMDQUEUE */ + } + break; + case CMD_AXI_CFG_SEC: { + CDBG("CMD_AXI_CFG_SEC\n"); + raw_mode = 0; + vfe2x_ctrl->zsl_mode = 0; + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + pr_err("NULL axio\n"); + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user((char *)axio + 4, + (void __user *)(vfecmd.value), + sizeof(struct axiout))) { + CDBG("copy_from_user failed\n"); + rc = -EFAULT; + goto config_done; + } + if (op_mode & SNAPSHOT_MASK_MODE) + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_CAPTURE, + VFE_MSG_OUTPUT_SECONDARY); + else + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_PREVIEW, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto config_done; + } + + if (!(op_mode & SNAPSHOT_MASK_MODE)) + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY); + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_done; + } + *(uint32_t *)axio = header; + if (op_mode & SNAPSHOT_MASK_MODE) + vfe_7x_config_axi(OUTPUT_SEC, + &vfe2x_ctrl->thumb, axio); + else + vfe_7x_config_axi(OUTPUT_SEC, + &vfe2x_ctrl->video, axio); + cmd_data = axio; + } + break; + case CMD_AXI_CFG_PRIM: { + CDBG("CMD_AXI_CFG_PRIM : %d\n", op_mode); + raw_mode = 0; + vfe2x_ctrl->zsl_mode = 0; + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + pr_err("NULL axio\n"); + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user((char *)axio + 4, + (void __user *)(vfecmd.value), + sizeof(struct axiout))) { + pr_err("copy_from_user failed\n"); + rc = -EFAULT; + goto config_done; + } + if (!vfe2x_ctrl->reconfig_vfe) { + if (op_mode & SNAPSHOT_MASK_MODE) + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + else + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_PREVIEW, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto config_done; + } + if (!(op_mode & SNAPSHOT_MASK_MODE)) + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + } else { + vfe2x_ctrl->prev.ping.ch_paddr[0] = + vfe2x_ctrl->free_buf.buf[0].ch_paddr[0]; + vfe2x_ctrl->prev.ping.ch_paddr[1] = + vfe2x_ctrl->free_buf.buf[0].ch_paddr[1]; + vfe2x_ctrl->prev.pong.ch_paddr[0] = + vfe2x_ctrl->free_buf.buf[1].ch_paddr[0]; + vfe2x_ctrl->prev.pong.ch_paddr[1] = + vfe2x_ctrl->free_buf.buf[1].ch_paddr[1]; + vfe2x_ctrl->prev.free_buf.ch_paddr[0] = + vfe2x_ctrl->free_buf.buf[2].ch_paddr[0]; + vfe2x_ctrl->prev.free_buf.ch_paddr[1] = + vfe2x_ctrl->free_buf.buf[2].ch_paddr[1]; + vfe2x_ctrl->reconfig_vfe = 0; + } + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_done; + } + *(uint32_t *)axio = header; + if (op_mode & SNAPSHOT_MASK_MODE) + vfe_7x_config_axi(OUTPUT_PRIM, &vfe2x_ctrl->snap, axio); + else + vfe_7x_config_axi(OUTPUT_PRIM, &vfe2x_ctrl->prev, axio); + cmd_data = axio; + } + break; + case CMD_AXI_CFG_ZSL: { + CDBG("CMD_AXI_CFG_ZSL: %d\n", op_mode); + raw_mode = 0; + vfe2x_ctrl->zsl_mode = 1; + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + pr_err("NULL axio\n"); + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user((char *)axio + 4, + (void __user *)(vfecmd.value), + sizeof(struct axiout))) { + pr_err("copy_from_user failed\n"); + rc = -EFAULT; + goto config_done; + } + if (!vfe2x_ctrl->reconfig_vfe) { + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_PREVIEW, + VFE_MSG_OUTPUT_PRIMARY); + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_PREVIEW, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto config_done; + } + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY); + } else { + vfe2x_ctrl->prev.ping.ch_paddr[0] = + vfe2x_ctrl->free_buf.buf[0].ch_paddr[0]; + vfe2x_ctrl->prev.ping.ch_paddr[1] = + vfe2x_ctrl->free_buf.buf[0].ch_paddr[1]; + vfe2x_ctrl->prev.pong.ch_paddr[0] = + vfe2x_ctrl->free_buf.buf[1].ch_paddr[0]; + vfe2x_ctrl->prev.pong.ch_paddr[1] = + vfe2x_ctrl->free_buf.buf[1].ch_paddr[1]; + vfe2x_ctrl->prev.free_buf.ch_paddr[0] = + vfe2x_ctrl->free_buf.buf[2].ch_paddr[0]; + vfe2x_ctrl->prev.free_buf.ch_paddr[1] = + vfe2x_ctrl->free_buf.buf[2].ch_paddr[1]; + vfe2x_ctrl->reconfig_vfe = 0; + } + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_done; + } + *(uint32_t *)axio = header; + vfe_7x_config_axi(OUTPUT_PRIM, &vfe2x_ctrl->zsl_prim, axio); + vfe_7x_config_axi(OUTPUT_SEC, &vfe2x_ctrl->zsl_sec, axio); + cmd_data = axio; + } + break; + case CMD_AXI_CFG_SEC|CMD_AXI_CFG_PRIM: { + CDBG("CMD_AXI_CFG_SEC|PRIM\n"); + raw_mode = 0; + vfe2x_ctrl->zsl_mode = 0; + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + pr_err("NULL axio\n"); + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user((char *)axio + 4, + (void __user *)(vfecmd.value), + sizeof(struct axiout))) { + pr_err("copy_from_user failed\n"); + rc = -EFAULT; + goto config_done; + } + if (op_mode & SNAPSHOT_MASK_MODE) + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_CAPTURE, + VFE_MSG_OUTPUT_SECONDARY); + else + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_PREVIEW, + VFE_MSG_OUTPUT_SECONDARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto config_done; + } + + if (!(op_mode & SNAPSHOT_MASK_MODE)) + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_SECONDARY); + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_done; + } + *(uint32_t *)axio = header; + if (op_mode & SNAPSHOT_MASK_MODE) + vfe_7x_config_axi(OUTPUT_SEC, &vfe2x_ctrl->thumb, axio); + else + vfe_7x_config_axi(OUTPUT_SEC, &vfe2x_ctrl->prev, axio); + + if (op_mode & SNAPSHOT_MASK_MODE) + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + else + rc = vfe2x_configure_pingpong_buffers( + VFE_MSG_V2X_PREVIEW, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto config_done; + } + + if (!(op_mode & SNAPSHOT_MASK_MODE)) + free_buf = vfe2x_check_free_buffer( + VFE_MSG_OUTPUT_IRQ, + VFE_MSG_OUTPUT_PRIMARY); + if (op_mode & SNAPSHOT_MASK_MODE) + vfe_7x_config_axi(OUTPUT_PRIM, + &vfe2x_ctrl->snap, axio); + else + vfe_7x_config_axi(OUTPUT_PRIM, + &vfe2x_ctrl->prev, axio); + cmd_data = axio; + } + break; + case CMD_RAW_PICT_AXI_CFG: { + CDBG("CMD_RAW_PICT_AXI_CFG:%d\n", op_mode); + raw_mode = 1; + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user((char *)axio + 4, + (void __user *)(vfecmd.value), + sizeof(struct axiout))) { + rc = -EFAULT; + goto config_done; + } + header = cmds_map[vfecmd.id].vfe_id; + queue = cmds_map[vfecmd.id].queue; + rc = vfe2x_configure_pingpong_buffers(VFE_MSG_V2X_CAPTURE, + VFE_MSG_OUTPUT_PRIMARY); + if (rc < 0) { + pr_err("%s error configuring pingpong buffers" + " for preview", __func__); + rc = -EINVAL; + goto config_done; + } + if (header == -1 && queue == -1) { + rc = -EFAULT; + goto config_done; + } + *(uint32_t *)axio = header; + vfe_7x_config_axi(OUTPUT_PRIM, &vfe2x_ctrl->snap, axio); + cmd_data = axio; + } + break; + default: + break; + } + + if (vfestopped) + goto config_done; + +config_send: + CDBG("send adsp command = %d\n", *(uint32_t *)cmd_data); + spin_lock_irqsave(&vfe2x_ctrl->table_lock, flags); + if (queue == QDSP_TABLEQUEUE && + vfe2x_ctrl->tableack_pending) { + CDBG("store table cmd\n"); + table_pending = kzalloc(sizeof(struct table_cmd), GFP_ATOMIC); + if (!table_pending) { + rc = -ENOMEM; + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + goto config_done; + } + table_pending->cmd = kzalloc(vfecmd.length + 4, GFP_ATOMIC); + if (!table_pending->cmd) { + kfree(table_pending); + rc = -ENOMEM; + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + goto config_done; + } + memcpy(table_pending->cmd, cmd_data, vfecmd.length + 4); + table_pending->queue = queue; + table_pending->size = vfecmd.length + 4; + list_add_tail(&table_pending->list, &vfe2x_ctrl->table_q); + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + } else { + if (queue == QDSP_TABLEQUEUE) { + CDBG("sending table cmd\n"); + vfe2x_ctrl->tableack_pending = 1; + rc = msm_adsp_write(vfe_mod, queue, + cmd_data, vfecmd.length + 4); + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + } else { + if (*(uint32_t *)cmd_data == VFE_OUTPUT2_ACK) { + uint32_t *ptr = cmd_data; + CDBG("%x %x %x\n", ptr[0], ptr[1], ptr[2]); + } + CDBG("send n-table cmd\n"); + rc = msm_adsp_write(vfe_mod, queue, + cmd_data, vfecmd.length + 4); + spin_unlock_irqrestore(&vfe2x_ctrl->table_lock, flags); + CDBG("%x\n", vfecmd.length + 4); + } + } + +config_done: + kfree(cmd_data_alloc); + +config_failure: + kfree(scfg); + kfree(axio); + kfree(sfcfg); + return rc; +} + +static struct msm_cam_clk_info vfe2x_clk_info[] = { + {"vfe_clk", 192000000}, +}; + +int msm_vfe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + int rc = 0; + v4l2_set_subdev_hostdata(sd, mctl); + + spin_lock_init(&vfe2x_ctrl->sd_notify_lock); + spin_lock_init(&vfe2x_ctrl->table_lock); + spin_lock_init(&vfe2x_ctrl->vfe_msg_lock); + init_waitqueue_head(&stopevent.wait); + INIT_LIST_HEAD(&vfe2x_ctrl->table_q); + INIT_LIST_HEAD(&vfe2x_ctrl->vfe_msg_q); + stopevent.timeout = 200; + stopevent.state = 0; + vfe2x_ctrl->vfe_started = 0; + + + CDBG("msm_cam_clk_enable: enable vfe_clk\n"); + rc = msm_cam_clk_enable(&vfe2x_ctrl->pdev->dev, vfe2x_clk_info, + vfe2x_ctrl->vfe_clk, ARRAY_SIZE(vfe2x_clk_info), 1); + if (rc < 0) + return rc; + + msm_camio_set_perf_lvl(S_INIT); + + /* TODO : check is it required */ + 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; + } + msm_adsp_enable(qcam_mod); + msm_adsp_enable(vfe_mod); + return 0; + +get_vfe_fail: + msm_adsp_put(qcam_mod); +get_qcam_fail: + kfree(extdata); +init_fail: + extlen = 0; + return rc; +} + +int msm_vpe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + return 0; +} + +void msm_vpe_subdev_release(void) +{ + return; +} + +void msm_vfe_subdev_release(struct v4l2_subdev *sd) +{ + CDBG("msm_cam_clk_enable: disable vfe_clk\n"); + msm_cam_clk_enable(&vfe2x_ctrl->pdev->dev, vfe2x_clk_info, + vfe2x_ctrl->vfe_clk, ARRAY_SIZE(vfe2x_clk_info), 0); + msm_adsp_disable(qcam_mod); + msm_adsp_disable(vfe_mod); + + msm_adsp_put(qcam_mod); + msm_adsp_put(vfe_mod); + + kfree(extdata); + msm_camio_set_perf_lvl(S_EXIT); + return; +} + +static int msm_vfe_subdev_s_crystal_freq(struct v4l2_subdev *sd, + u32 freq, u32 flags) +{ + int rc = 0; + int round_rate; + + round_rate = clk_round_rate(vfe2x_ctrl->vfe_clk[0], freq); + if (rc < 0) { + pr_err("%s: clk_round_rate failed %d\n", + __func__, rc); + return rc; + } + + rc = clk_set_rate(vfe2x_ctrl->vfe_clk[0], round_rate); + if (rc < 0) + pr_err("%s: clk_set_rate failed %d\n", + __func__, rc); + + return rc; +} + +static const struct v4l2_subdev_video_ops msm_vfe_subdev_video_ops = { + .s_crystal_freq = msm_vfe_subdev_s_crystal_freq, +}; + +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, + .video = &msm_vfe_subdev_video_ops, +}; + +static const struct v4l2_subdev_internal_ops msm_vfe_internal_ops; + +static int __devinit vfe2x_probe(struct platform_device *pdev) +{ + CDBG("%s: device id = %d\n", __func__, pdev->id); + vfe2x_ctrl = kzalloc(sizeof(struct vfe2x_ctrl_type), GFP_KERNEL); + if (!vfe2x_ctrl) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&vfe2x_ctrl->subdev, &msm_vfe_subdev_ops); + vfe2x_ctrl->subdev.internal_ops = &msm_vfe_internal_ops; + vfe2x_ctrl->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(vfe2x_ctrl->subdev.name, + sizeof(vfe2x_ctrl->subdev.name), "vfe2.x"); + v4l2_set_subdevdata(&vfe2x_ctrl->subdev, vfe2x_ctrl); + platform_set_drvdata(pdev, &vfe2x_ctrl->subdev); + + vfe2x_ctrl->pdev = pdev; + msm_cam_register_subdev_node(&vfe2x_ctrl->subdev, VFE_DEV, 0); + return 0; +} + +static struct platform_driver vfe2x_driver = { + .probe = vfe2x_probe, + .driver = { + .name = MSM_VFE_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_vfe2x_init_module(void) +{ + return platform_driver_register(&vfe2x_driver); +} + +static void __exit msm_vfe2x_exit_module(void) +{ + platform_driver_unregister(&vfe2x_driver); +} + +module_init(msm_vfe2x_init_module); +module_exit(msm_vfe2x_exit_module); +MODULE_DESCRIPTION("VFE 2.x driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_vfe7x27a_v4l2.h b/drivers/media/video/msm/msm_vfe7x27a_v4l2.h new file mode 100644 index 0000000000000000000000000000000000000000..2f2d3c65f894c59824b870bf4e00be6f087df295 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x27a_v4l2.h @@ -0,0 +1,414 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#include "msm.h" + +struct cmd_id_map { + uint32_t isp_id; + uint32_t vfe_id; + uint32_t queue; + char isp_id_name[64]; + char vfe_id_name[64]; +} __packed; + +struct msg_id_map { + uint32_t vfe_id; + uint32_t isp_id; +} __packed; + +struct table_cmd { + struct list_head list; + void *cmd; + int size; + int queue; +} __packed; + +struct vfe_msg { + struct list_head list; + void *cmd; + int len; + int id; +} __packed; + +struct buf_info { + /* Buffer */ + struct msm_free_buf ping; + struct msm_free_buf pong; + struct msm_free_buf free_buf; +} __packed; + +struct prev_free_buf_info { + struct msm_free_buf buf[3]; +}; + +struct vfe_cmd_start { + uint32_t input_source:1; + uint32_t mode_of_operation:1; + uint32_t snap_number:4; + uint32_t /* reserved */ : 26; + + /* Image Pipeline Modules */ + uint32_t blacklevel_correction_enable:1; + uint32_t lens_rolloff_correction_enable:1; + uint32_t white_balance_enable:1; + uint32_t rgb_gamma_enable:1; + uint32_t luma_noise_reductionpath_enable:1; + uint32_t adaptive_spatialfilter_enable:1; + uint32_t chroma_subsample_enable:1; + uint32_t /* reserved */ : 25; + + /* The dimension fed to the statistics module */ + uint32_t last_pixel:12; + uint32_t /* reserved */ : 4; + uint32_t last_line:12; + uint32_t /* reserved */ : 4; +} __packed; + +struct vfe2x_ctrl_type { + struct buf_info prev; + struct buf_info video; + struct buf_info snap; + struct buf_info raw; + struct buf_info thumb; + struct prev_free_buf_info free_buf; + struct buf_info zsl_prim; + struct buf_info zsl_sec; + struct prev_free_buf_info zsl_free_buf[2]; + + + spinlock_t table_lock; + struct list_head table_q; + uint32_t tableack_pending; + uint32_t vfeFrameId; + + spinlock_t vfe_msg_lock; + struct list_head vfe_msg_q; + + struct vfe_cmd_start start_cmd; + uint32_t start_pending; + uint32_t vfe_started; + uint32_t stop_pending; + uint32_t update_pending; + + /* v4l2 subdev */ + struct v4l2_subdev subdev; + struct platform_device *pdev; + struct clk *vfe_clk[3]; + spinlock_t sd_notify_lock; + uint32_t reconfig_vfe; + uint32_t zsl_mode; +} __packed; + +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; +} __packed; + +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; +}; +struct vfe_error_msg { + unsigned int camif_error:1; + unsigned int output1ybusoverflow:1; + unsigned int output1cbcrbusoverflow:1; + unsigned int output2ybusoverflow:1; + unsigned int output2cbcrbusoverflow:1; + unsigned int autofocusstatbusoverflow:1; + unsigned int wb_expstatbusoverflow:1; + unsigned int axierror:1; + unsigned int /* reserved */ : 24; + unsigned int camif_staus:1; + unsigned int pixel_count:14; + unsigned int line_count:14; + unsigned int /*reserved */ : 3; +} __packed; + +static struct msm_free_buf *vfe2x_check_free_buffer(int id, int path); + +#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 0000000000000000000000000000000000000000..38011bab45684152e71c247c00c7339e9b2ac193 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe8x.c @@ -0,0 +1,843 @@ +/* Copyright (c) 2009, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the 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.planar0_off; + p1++; + + *p2 = regptr->paddr + regptr->info.planar1_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.planar0_off, + regptr->info.planar1_off); + + for (j = 0; j < ao->output2.fragmentCount; j++) { + + *p1 = regptr->paddr + regptr->info.planar0_off; + CDBG("vfe_config_axi: p1 = 0x%x\n", *p1); + p1++; + + *p2 = regptr->paddr + regptr->info.planar1_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.planar0_off, regptr->info.planar1_off); + + for (j = 0; j < ao->output1.fragmentCount; j++) { + + *p1 = regptr->paddr + regptr->info.planar0_off; + CDBG("vfe_config_axi: p1 = 0x%x\n", *p1); + p1++; + + *p2 = regptr->paddr + regptr->info.planar1_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.planar0_off, regptr1->info.planar1_off); + + for (j = 0; j < ao->output2.fragmentCount; j++) { + *p1 = regptr1->paddr + + regptr1->info.planar0_off; + CDBG("vfe_config_axi: p1 = 0x%x\n", *p1); + p1++; + *p2 = regptr1->paddr + + r1->info.planar1_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->planar0_off); + + fack.chromabufaddr[0] = (uint32_t) (p + b->planar1_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 0000000000000000000000000000000000000000..1b3148f0e8484b876cc94199615098ac9621d284 --- /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 0000000000000000000000000000000000000000..055b2445a46537bae2636893bc97384443af7c29 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe8x_proc.c @@ -0,0 +1,3889 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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->planar0_off = + ((struct vfe_message *)data)->_u.msgOutput2.yBuffer; + pinfo->planar1_off = + ((struct vfe_message *)data)->_u.msgOutput2. + cbcrBuffer; + pinfo->planar2_off = pinfo->planar0_off; + 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 0000000000000000000000000000000000000000..da00e8fb17f605bd8f63cc99452e789899f485a5 --- /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_vpe.c b/drivers/media/video/msm/msm_vpe.c new file mode 100644 index 0000000000000000000000000000000000000000..f9ce74b3db5d321e6823d2834a3edf93778f2b2e --- /dev/null +++ b/drivers/media/video/msm/msm_vpe.c @@ -0,0 +1,796 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm.h" +#include "msm_vpe.h" + +static int vpe_enable(uint32_t); +static int vpe_disable(void); +static int vpe_update_scaler(struct msm_pp_crop *pcrop); +struct vpe_ctrl_type *vpe_ctrl; +static atomic_t vpe_init_done = ATOMIC_INIT(0); + +static int msm_vpe_do_pp(struct msm_mctl_pp_cmd *cmd, + struct msm_mctl_pp_frame_info *pp_frame_info); + +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_camera_io_w_mb(1, vpe_ctrl->vpebase + VPE_INTR_ENABLE_OFFSET); + msm_camera_io_dump(vpe_ctrl->vpebase, 0x120); + msm_camera_io_dump(vpe_ctrl->vpebase + 0x00400, 0x18); + msm_camera_io_dump(vpe_ctrl->vpebase + 0x10000, 0x250); + msm_camera_io_dump(vpe_ctrl->vpebase + 0x30000, 0x20); + msm_camera_io_dump(vpe_ctrl->vpebase + 0x50000, 0x30); + msm_camera_io_dump(vpe_ctrl->vpebase + 0x50400, 0x10); + + /* this triggers the operation. */ + msm_camera_io_w(1, vpe_ctrl->vpebase + VPE_DL0_START_OFFSET); + wmb(); + 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; +} + +static void vpe_config_axi_default(void) +{ + msm_camera_io_w(0x25, vpe_ctrl->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_camera_io_w(vpe_ctrl->out_y_addr, + vpe_ctrl->vpebase + VPE_OUTP0_ADDR_OFFSET); + /* for video CbCr address */ + msm_camera_io_w(vpe_ctrl->out_cbcr_addr, + vpe_ctrl->vpebase + VPE_OUTP1_ADDR_OFFSET); + +} + +static int vpe_reset(void) +{ + uint32_t vpe_version; + uint32_t rc = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&vpe_ctrl->lock, flags); + if (vpe_ctrl->state == VPE_STATE_IDLE) { + CDBG("%s: VPE already disabled.", __func__); + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + return rc; + } + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + + vpe_reset_state_variables(); + vpe_version = msm_camera_io_r( + vpe_ctrl->vpebase + VPE_HW_VERSION_OFFSET); + CDBG("vpe_version = 0x%x\n", vpe_version); + /* disable all interrupts.*/ + msm_camera_io_w(0, vpe_ctrl->vpebase + VPE_INTR_ENABLE_OFFSET); + /* clear all pending interrupts*/ + msm_camera_io_w(0x1fffff, vpe_ctrl->vpebase + VPE_INTR_CLEAR_OFFSET); + /* write sw_reset to reset the core. */ + msm_camera_io_w(0x10, vpe_ctrl->vpebase + VPE_SW_RESET_OFFSET); + /* then poll the reset bit, it should be self-cleared. */ + while (1) { + rc = + msm_camera_io_r(vpe_ctrl->vpebase + VPE_SW_RESET_OFFSET) & 0x10; + if (rc == 0) + break; + } + /* at this point, hardware is reset. Then pogram to default + values. */ + msm_camera_io_w(VPE_AXI_RD_ARB_CONFIG_VALUE, + vpe_ctrl->vpebase + VPE_AXI_RD_ARB_CONFIG_OFFSET); + + msm_camera_io_w(VPE_CGC_ENABLE_VALUE, + vpe_ctrl->vpebase + VPE_CGC_EN_OFFSET); + msm_camera_io_w(1, vpe_ctrl->vpebase + VPE_CMD_MODE_OFFSET); + msm_camera_io_w(VPE_DEFAULT_OP_MODE_VALUE, + vpe_ctrl->vpebase + VPE_OP_MODE_OFFSET); + msm_camera_io_w(VPE_DEFAULT_SCALE_CONFIG, + vpe_ctrl->vpebase + VPE_SCALE_CONFIG_OFFSET); + vpe_config_axi_default(); + return rc; +} + +static int msm_vpe_cfg_update(void *pinfo) +{ + uint32_t rot_flag, rc = 0; + struct msm_pp_crop *pcrop = (struct msm_pp_crop *)pinfo; + + rot_flag = msm_camera_io_r(vpe_ctrl->vpebase + + VPE_OP_MODE_OFFSET) & 0xE00; + if (pinfo != NULL) { + CDBG("%s: Crop info in2_w = %d, in2_h = %d " + "out2_w = %d out2_h = %d\n", + __func__, pcrop->src_w, pcrop->src_h, + pcrop->dst_w, pcrop->dst_h); + 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_camera_io_w(*(++p), + vpe_ctrl->vpebase + VPE_SCALE_COEFF_LSBn(i)); + msm_camera_io_w(*(++p), + vpe_ctrl->vpebase + VPE_SCALE_COEFF_MSBn(i)); + } +} + +void vpe_input_plane_config(uint32_t *p) +{ + msm_camera_io_w(*p, vpe_ctrl->vpebase + VPE_SRC_FORMAT_OFFSET); + msm_camera_io_w(*(++p), + vpe_ctrl->vpebase + VPE_SRC_UNPACK_PATTERN1_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_SRC_IMAGE_SIZE_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_SRC_YSTRIDE1_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_SRC_SIZE_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_SRC_XY_OFFSET); +} + +void vpe_output_plane_config(uint32_t *p) +{ + msm_camera_io_w(*p, vpe_ctrl->vpebase + VPE_OUT_FORMAT_OFFSET); + msm_camera_io_w(*(++p), + vpe_ctrl->vpebase + VPE_OUT_PACK_PATTERN1_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_OUT_YSTRIDE1_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_OUT_SIZE_OFFSET); + msm_camera_io_w(*(++p), vpe_ctrl->vpebase + VPE_OUT_XY_OFFSET); +} + +static int vpe_operation_config(uint32_t *p) +{ + uint32_t w, h, temp; + msm_camera_io_w(*p, vpe_ctrl->vpebase + VPE_OP_MODE_OFFSET); + + temp = msm_camera_io_r(vpe_ctrl->vpebase + VPE_OUT_SIZE_OFFSET); + w = temp & 0xFFF; + h = (temp & 0xFFF0000) >> 16; + if (*p++ & 0xE00) { + /* rotation enabled. */ + vpe_ctrl->out_w = h; + vpe_ctrl->out_h = w; + } else { + vpe_ctrl->out_w = w; + vpe_ctrl->out_h = h; + } + CDBG("%s: out_w=%d, out_h=%d", __func__, vpe_ctrl->out_w, + vpe_ctrl->out_h); + 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 msm_pp_crop *pcrop) +{ + uint32_t out_ROI_width, out_ROI_height; + uint32_t src_ROI_width, 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_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; + + /* assumption is both direction need zoom. this can be + improved. */ + temp = + msm_camera_io_r(vpe_ctrl->vpebase + VPE_OP_MODE_OFFSET) | 0x3; + msm_camera_io_w(temp, vpe_ctrl->vpebase + VPE_OP_MODE_OFFSET); + + src_ROI_width = pcrop->src_w; + src_ROI_height = pcrop->src_h; + out_ROI_width = pcrop->dst_w; + out_ROI_height = pcrop->dst_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_camera_io_w(src_roi, vpe_ctrl->vpebase + VPE_SRC_SIZE_OFFSET); + + src_x = pcrop->src_x; + src_y = pcrop->src_y; + + CDBG("src_x = %d, src_y=%d.\n", src_x, src_y); + + src_xy = src_y*(1<<16) + src_x; + msm_camera_io_w(src_xy, vpe_ctrl->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_camera_io_w(phase_step_x, vpe_ctrl->vpebase + + VPE_SCALE_PHASEX_STEP_OFFSET); + msm_camera_io_w(phase_step_y, vpe_ctrl->vpebase + + VPE_SCALE_PHASEY_STEP_OFFSET); + + msm_camera_io_w(phase_init_x, vpe_ctrl->vpebase + + VPE_SCALE_PHASEX_INIT_OFFSET); + + msm_camera_io_w(phase_init_y, vpe_ctrl->vpebase + + VPE_SCALE_PHASEY_INIT_OFFSET); + + return 1; +} + +int msm_vpe_is_busy(void) +{ + int busy = 0; + unsigned long flags; + spin_lock_irqsave(&vpe_ctrl->lock, flags); + if (vpe_ctrl->state == VPE_STATE_ACTIVE) + busy = 1; + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + return busy; +} +static int msm_send_frame_to_vpe(void) +{ + int rc = 0; + unsigned long flags; + + spin_lock_irqsave(&vpe_ctrl->lock, flags); + msm_camera_io_w((vpe_ctrl->pp_frame_info->src_frame.sp.phy_addr + + vpe_ctrl->pp_frame_info->src_frame.sp.y_off), + vpe_ctrl->vpebase + VPE_SRCP0_ADDR_OFFSET); + msm_camera_io_w((vpe_ctrl->pp_frame_info->src_frame.sp.phy_addr + + vpe_ctrl->pp_frame_info->src_frame.sp.cbcr_off), + vpe_ctrl->vpebase + VPE_SRCP1_ADDR_OFFSET); + msm_camera_io_w((vpe_ctrl->pp_frame_info->dest_frame.sp.phy_addr + + vpe_ctrl->pp_frame_info->dest_frame.sp.y_off), + vpe_ctrl->vpebase + VPE_OUTP0_ADDR_OFFSET); + msm_camera_io_w((vpe_ctrl->pp_frame_info->dest_frame.sp.phy_addr + + vpe_ctrl->pp_frame_info->dest_frame.sp.cbcr_off), + vpe_ctrl->vpebase + VPE_OUTP1_ADDR_OFFSET); + vpe_ctrl->state = VPE_STATE_ACTIVE; + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + vpe_start(); + return rc; +} + +static void vpe_send_outmsg(void) +{ + unsigned long flags; + struct msm_vpe_resp rp; + memset(&rp, 0, sizeof(rp)); + spin_lock_irqsave(&vpe_ctrl->lock, flags); + if (vpe_ctrl->state == VPE_STATE_IDLE) { + pr_err("%s VPE is in IDLE state. Ignore the ack msg", __func__); + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + return; + } + rp.type = vpe_ctrl->pp_frame_info->pp_frame_cmd.path; + rp.extdata = (void *)vpe_ctrl->pp_frame_info; + rp.extlen = sizeof(*vpe_ctrl->pp_frame_info); + vpe_ctrl->state = VPE_STATE_INIT; /* put it back to idle. */ + vpe_ctrl->pp_frame_info = NULL; + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + v4l2_subdev_notify(&vpe_ctrl->subdev, + NOTIFY_VPE_MSG_EVT, (void *)&rp); +} + +static void vpe_do_tasklet(unsigned long data) +{ + CDBG("%s: irq_status = 0x%x", + __func__, vpe_ctrl->irq_status); + if (vpe_ctrl->irq_status & 0x1) + vpe_send_outmsg(); + +} +DECLARE_TASKLET(vpe_tasklet, vpe_do_tasklet, 0); + +static irqreturn_t vpe_parse_irq(int irq_num, void *data) +{ + vpe_ctrl->irq_status = msm_camera_io_r_mb(vpe_ctrl->vpebase + + VPE_INTR_STATUS_OFFSET); + msm_camera_io_w_mb(vpe_ctrl->irq_status, vpe_ctrl->vpebase + + VPE_INTR_CLEAR_OFFSET); + msm_camera_io_w(0, vpe_ctrl->vpebase + VPE_INTR_ENABLE_OFFSET); + CDBG("%s: vpe_parse_irq =0x%x.\n", __func__, vpe_ctrl->irq_status); + tasklet_schedule(&vpe_tasklet); + return IRQ_HANDLED; +} + +static struct msm_cam_clk_info vpe_clk_info[] = { + {"vpe_clk", 160000000}, + {"vpe_pclk", -1}, +}; + +int vpe_enable(uint32_t clk_rate) +{ + int rc = 0; + unsigned long flags = 0; + CDBG("%s", __func__); + /* don't change the order of clock and irq.*/ + spin_lock_irqsave(&vpe_ctrl->lock, flags); + if (vpe_ctrl->state != VPE_STATE_IDLE) { + pr_err("%s: VPE already enabled", __func__); + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + return 0; + } + vpe_ctrl->state = VPE_STATE_INIT; + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + enable_irq(vpe_ctrl->vpeirq->start); + vpe_ctrl->fs_vpe = regulator_get(NULL, "fs_vpe"); + if (IS_ERR(vpe_ctrl->fs_vpe)) { + pr_err("%s: Regulator FS_VPE get failed %ld\n", __func__, + PTR_ERR(vpe_ctrl->fs_vpe)); + vpe_ctrl->fs_vpe = NULL; + goto vpe_fs_failed; + } else if (regulator_enable(vpe_ctrl->fs_vpe)) { + pr_err("%s: Regulator FS_VPE enable failed\n", __func__); + regulator_put(vpe_ctrl->fs_vpe); + goto vpe_fs_failed; + } + + rc = msm_cam_clk_enable(&vpe_ctrl->pdev->dev, vpe_clk_info, + vpe_ctrl->vpe_clk, ARRAY_SIZE(vpe_clk_info), 1); + if (rc < 0) + goto vpe_clk_failed; + + return rc; + +vpe_clk_failed: + regulator_disable(vpe_ctrl->fs_vpe); + regulator_put(vpe_ctrl->fs_vpe); + vpe_ctrl->fs_vpe = NULL; +vpe_fs_failed: + disable_irq(vpe_ctrl->vpeirq->start); + vpe_ctrl->state = VPE_STATE_IDLE; + return rc; +} + +int vpe_disable(void) +{ + int rc = 0; + unsigned long flags = 0; + CDBG("%s", __func__); + spin_lock_irqsave(&vpe_ctrl->lock, flags); + if (vpe_ctrl->state == VPE_STATE_IDLE) { + CDBG("%s: VPE already disabled", __func__); + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + return rc; + } + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + + disable_irq(vpe_ctrl->vpeirq->start); + tasklet_kill(&vpe_tasklet); + msm_cam_clk_enable(&vpe_ctrl->pdev->dev, vpe_clk_info, + vpe_ctrl->vpe_clk, ARRAY_SIZE(vpe_clk_info), 0); + + regulator_disable(vpe_ctrl->fs_vpe); + regulator_put(vpe_ctrl->fs_vpe); + vpe_ctrl->fs_vpe = NULL; + spin_lock_irqsave(&vpe_ctrl->lock, flags); + vpe_ctrl->state = VPE_STATE_IDLE; + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + return rc; +} + +static int msm_vpe_do_pp(struct msm_mctl_pp_cmd *cmd, + struct msm_mctl_pp_frame_info *pp_frame_info) +{ + int rc = 0; + unsigned long flags; + + spin_lock_irqsave(&vpe_ctrl->lock, flags); + if (vpe_ctrl->state == VPE_STATE_ACTIVE || + vpe_ctrl->state == VPE_STATE_IDLE) { + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + pr_err(" =====VPE in wrong state:%d!!! Wrong!========\n", + vpe_ctrl->state); + return -EBUSY; + } + spin_unlock_irqrestore(&vpe_ctrl->lock, flags); + vpe_ctrl->pp_frame_info = pp_frame_info; + msm_vpe_cfg_update( + &vpe_ctrl->pp_frame_info->pp_frame_cmd.crop); + CDBG("%s Sending frame idx %d id %d to VPE ", __func__, + pp_frame_info->src_frame.buf_idx, + pp_frame_info->src_frame.frame_id); + rc = msm_send_frame_to_vpe(); + return rc; +} + +static int msm_vpe_resource_init(void); + +int msm_vpe_subdev_init(struct v4l2_subdev *sd, + struct msm_cam_media_controller *mctl) +{ + int rc = 0; + CDBG("%s:begin", __func__); + if (atomic_read(&vpe_init_done)) { + pr_err("%s: VPE has been initialized", __func__); + return -EBUSY; + } + atomic_set(&vpe_init_done, 1); + + rc = msm_vpe_resource_init(); + if (rc < 0) { + atomic_set(&vpe_init_done, 0); + return rc; + } + v4l2_set_subdev_hostdata(sd, mctl); + spin_lock_init(&vpe_ctrl->lock); + CDBG("%s:end", __func__); + return rc; +} +EXPORT_SYMBOL(msm_vpe_subdev_init); + +static int msm_vpe_resource_init(void) +{ + int rc = 0; + + vpe_ctrl->vpebase = ioremap(vpe_ctrl->vpemem->start, + resource_size(vpe_ctrl->vpemem)); + + if (!vpe_ctrl->vpebase) { + rc = -ENOMEM; + pr_err("%s: vpe ioremap failed\n", __func__); + goto vpe_unmap_mem_region; + } + + return rc; +/* from this part it is error handling. */ +vpe_unmap_mem_region: + iounmap(vpe_ctrl->vpebase); + vpe_ctrl->vpebase = NULL; + return rc; /* this rc should have error code. */ +} + +void msm_vpe_subdev_release(void) +{ + if (!atomic_read(&vpe_init_done)) { + /* no VPE object created */ + pr_err("%s: no VPE object to release", __func__); + return; + } + + vpe_reset(); + vpe_disable(); + iounmap(vpe_ctrl->vpebase); + vpe_ctrl->vpebase = NULL; + atomic_set(&vpe_init_done, 0); +} +EXPORT_SYMBOL(msm_vpe_subdev_release); + +static long msm_vpe_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int subdev_cmd, void *arg) +{ + struct msm_mctl_pp_params *vpe_params; + struct msm_mctl_pp_cmd *cmd; + int rc = 0; + + if (subdev_cmd == VIDIOC_MSM_VPE_INIT) { + struct msm_cam_media_controller *mctl = + (struct msm_cam_media_controller *)arg; + msm_vpe_subdev_init(sd, mctl); + } else if (subdev_cmd == VIDIOC_MSM_VPE_RELEASE) { + msm_vpe_subdev_release(); + } else if (subdev_cmd == VIDIOC_MSM_VPE_CFG) { + vpe_params = (struct msm_mctl_pp_params *)arg; + cmd = vpe_params->cmd; + switch (cmd->id) { + case VPE_CMD_INIT: + case VPE_CMD_DEINIT: + break; + case VPE_CMD_RESET: + rc = vpe_reset(); + break; + case VPE_CMD_OPERATION_MODE_CFG: + rc = vpe_operation_config(cmd->value); + break; + case VPE_CMD_INPUT_PLANE_CFG: + vpe_input_plane_config(cmd->value); + break; + case VPE_CMD_OUTPUT_PLANE_CFG: + vpe_output_plane_config(cmd->value); + break; + case VPE_CMD_SCALE_CFG_TYPE: + vpe_update_scale_coef(cmd->value); + break; + case VPE_CMD_ZOOM: { + rc = msm_vpe_do_pp(cmd, + (struct msm_mctl_pp_frame_info *)vpe_params->data); + break; + } + case VPE_CMD_ENABLE: { + struct msm_vpe_clock_rate *clk_rate = cmd->value; + int turbo_mode = (int)clk_rate->rate; + rc = turbo_mode ? + vpe_enable(VPE_TURBO_MODE_CLOCK_RATE) : + vpe_enable(VPE_NORMAL_MODE_CLOCK_RATE); + break; + } + case VPE_CMD_DISABLE: + rc = vpe_disable(); + break; + case VPE_CMD_INPUT_PLANE_UPDATE: + case VPE_CMD_FLUSH: + default: + break; + } + CDBG("%s: end, id = %d, rc = %d", __func__, cmd->id, rc); + } + return rc; +} + +static const struct v4l2_subdev_core_ops msm_vpe_subdev_core_ops = { + .ioctl = msm_vpe_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_vpe_subdev_ops = { + .core = &msm_vpe_subdev_core_ops, +}; + +static const struct v4l2_subdev_internal_ops msm_vpe_internal_ops; + +static int __devinit vpe_probe(struct platform_device *pdev) +{ + int rc = 0; + CDBG("%s: device id = %d\n", __func__, pdev->id); + vpe_ctrl = kzalloc(sizeof(struct vpe_ctrl_type), GFP_KERNEL); + if (!vpe_ctrl) { + pr_err("%s: no enough memory\n", __func__); + return -ENOMEM; + } + + v4l2_subdev_init(&vpe_ctrl->subdev, &msm_vpe_subdev_ops); + v4l2_set_subdevdata(&vpe_ctrl->subdev, vpe_ctrl); + vpe_ctrl->subdev.internal_ops = &msm_vpe_internal_ops; + vpe_ctrl->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(vpe_ctrl->subdev.name, sizeof(vpe_ctrl->subdev.name), "vpe"); + platform_set_drvdata(pdev, &vpe_ctrl->subdev); + + vpe_ctrl->vpemem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "vpe"); + if (!vpe_ctrl->vpemem) { + pr_err("%s: no mem resource?\n", __func__); + rc = -ENODEV; + goto vpe_no_resource; + } + vpe_ctrl->vpeirq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "vpe"); + if (!vpe_ctrl->vpeirq) { + pr_err("%s: no irq resource?\n", __func__); + rc = -ENODEV; + goto vpe_no_resource; + } + + vpe_ctrl->vpeio = request_mem_region(vpe_ctrl->vpemem->start, + resource_size(vpe_ctrl->vpemem), pdev->name); + if (!vpe_ctrl->vpeio) { + pr_err("%s: no valid mem region\n", __func__); + rc = -EBUSY; + goto vpe_no_resource; + } + + rc = request_irq(vpe_ctrl->vpeirq->start, vpe_parse_irq, + IRQF_TRIGGER_RISING, "vpe", 0); + if (rc < 0) { + release_mem_region(vpe_ctrl->vpemem->start, + resource_size(vpe_ctrl->vpemem)); + pr_err("%s: irq request fail\n", __func__); + rc = -EBUSY; + goto vpe_no_resource; + } + + disable_irq(vpe_ctrl->vpeirq->start); + + vpe_ctrl->pdev = pdev; + msm_cam_register_subdev_node(&vpe_ctrl->subdev, VPE_DEV, pdev->id); + return 0; + +vpe_no_resource: + kfree(vpe_ctrl); + return 0; +} + +struct platform_driver vpe_driver = { + .probe = vpe_probe, + .driver = { + .name = MSM_VPE_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_vpe_init_module(void) +{ + return platform_driver_register(&vpe_driver); +} + +module_init(msm_vpe_init_module); +MODULE_DESCRIPTION("VPE driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_vpe.h b/drivers/media/video/msm/msm_vpe.h new file mode 100644 index 0000000000000000000000000000000000000000..0d14626e55232285fe901ffb3549d7c13e3eddc3 --- /dev/null +++ b/drivers/media/video/msm/msm_vpe.h @@ -0,0 +1,191 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VPE_H_ +#define _MSM_VPE_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_1_OFFSET 0x00408 +#define VPE_AXI_ARB_2_OFFSET 0x0040C + +#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 + + +/**************************************************/ +/*********** End of command id ********************/ +/**************************************************/ + +enum vpe_state { + VPE_STATE_IDLE, + VPE_STATE_INIT, + VPE_STATE_ACTIVE, +}; + +struct vpe_ctrl_type { + spinlock_t lock; + uint32_t irq_status; + void *syncdata; + uint16_t op_mode; + void *extdata; + uint32_t extlen; + struct msm_vpe_callback *resp; + uint32_t out_h; /* this is BEFORE rotation. */ + uint32_t out_w; /* this is BEFORE rotation. */ + struct timespec ts; + 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; + struct v4l2_subdev subdev; + struct platform_device *pdev; + struct resource *vpeirq; + void __iomem *vpebase; + struct resource *vpemem; + struct resource *vpeio; + void *device_extdata; + struct regulator *fs_vpe; + struct clk *vpe_clk[2]; + struct msm_mctl_pp_frame_info *pp_frame_info; +}; + +/* +* 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; + /* crop 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; +}; + +#define VIDIOC_MSM_VPE_INIT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 15, struct msm_cam_media_controller *) + +#define VIDIOC_MSM_VPE_RELEASE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 16, struct msm_cam_media_controller *) + +#define VIDIOC_MSM_VPE_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 17, struct msm_mctl_pp_params *) + +#endif /*_MSM_VPE_H_*/ + diff --git a/drivers/media/video/msm/msm_vpe1.c b/drivers/media/video/msm/msm_vpe1.c new file mode 100644 index 0000000000000000000000000000000000000000..4f97c4314f9fa9ab8eb121e15b12bd3ad509b508 --- /dev/null +++ b/drivers/media/video/msm/msm_vpe1.c @@ -0,0 +1,1468 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied 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 + +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_camera_io_w(1, vpe_device->vpebase + VPE_INTR_ENABLE_OFFSET); + msm_camera_io_dump(vpe_device->vpebase + 0x10000, 0x250); + /* this triggers the operation. */ + msm_camera_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_camera_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_camera_io_w(vpe_ctrl->out_y_addr, + vpe_device->vpebase + VPE_OUTP0_ADDR_OFFSET); + /* for video CbCr address */ + msm_camera_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_camera_io_r( + vpe_device->vpebase + VPE_HW_VERSION_OFFSET); + CDBG("vpe_version = 0x%x\n", vpe_version); + + /* disable all interrupts.*/ + msm_camera_io_w(0, vpe_device->vpebase + VPE_INTR_ENABLE_OFFSET); + /* clear all pending interrupts*/ + msm_camera_io_w(0x1fffff, vpe_device->vpebase + VPE_INTR_CLEAR_OFFSET); + + /* write sw_reset to reset the core. */ + msm_camera_io_w(0x10, vpe_device->vpebase + VPE_SW_RESET_OFFSET); + + /* then poll the reset bit, it should be self-cleared. */ + while (1) { + rc = msm_camera_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_camera_io_w(VPE_AXI_RD_ARB_CONFIG_VALUE, + vpe_device->vpebase + VPE_AXI_RD_ARB_CONFIG_OFFSET); + + msm_camera_io_w(VPE_CGC_ENABLE_VALUE, + vpe_device->vpebase + VPE_CGC_EN_OFFSET); + + msm_camera_io_w(1, vpe_device->vpebase + VPE_CMD_MODE_OFFSET); + + msm_camera_io_w(VPE_DEFAULT_OP_MODE_VALUE, + vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + msm_camera_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_camera_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_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SCALE_COEFF_LSBn(i)); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SCALE_COEFF_MSBn(i)); + } +} + +void vpe_input_plane_config(uint32_t *p) +{ + msm_camera_io_w(*p, + vpe_device->vpebase + VPE_SRC_FORMAT_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SRC_UNPACK_PATTERN1_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SRC_IMAGE_SIZE_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SRC_YSTRIDE1_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SRC_SIZE_OFFSET); + vpe_ctrl->in_h_w = *p; + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_SRC_XY_OFFSET); +} + +void vpe_output_plane_config(uint32_t *p) +{ + msm_camera_io_w(*p, + vpe_device->vpebase + VPE_OUT_FORMAT_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_OUT_PACK_PATTERN1_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_OUT_YSTRIDE1_OFFSET); + msm_camera_io_w(*(++p), + vpe_device->vpebase + VPE_OUT_SIZE_OFFSET); + msm_camera_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_camera_io_w(*p, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + temp = msm_camera_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_camera_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) + & 0xfffffffc; + msm_camera_io_w(temp, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + + msm_camera_io_w(0, vpe_device->vpebase + VPE_SRC_XY_OFFSET); + + CDBG("vpe_ctrl->in_h_w = %d\n", vpe_ctrl->in_h_w); + msm_camera_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_camera_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) | 0x3; + msm_camera_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_camera_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_camera_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_camera_io_w(phase_step_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_STEP_OFFSET); + msm_camera_io_w(phase_step_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_STEP_OFFSET); + + msm_camera_io_w(phase_init_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_INIT_OFFSET); + + msm_camera_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_camera_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) + & 0xfffffffc; + msm_camera_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_camera_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_camera_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_camera_io_r(vpe_device->vpebase + + VPE_OP_MODE_OFFSET) | 0x3; + msm_camera_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_camera_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_camera_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_camera_io_w(phase_step_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_STEP_OFFSET); + + msm_camera_io_w(phase_step_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_STEP_OFFSET); + + msm_camera_io_w(phase_init_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_INIT_OFFSET); + + msm_camera_io_w(phase_init_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_INIT_OFFSET); + + return 1; +} + +void msm_send_frame_to_vpe(uint32_t p0_phy_add, uint32_t p1_phy_add, + struct timespec *ts, int output_type) +{ + uint32_t temp_pyaddr = 0, temp_pcbcraddr = 0; + + CDBG("vpe input, p0_phy_add = 0x%x, p1_phy_add = 0x%x\n", + p0_phy_add, p1_phy_add); + msm_camera_io_w(p0_phy_add, + vpe_device->vpebase + VPE_SRCP0_ADDR_OFFSET); + msm_camera_io_w(p1_phy_add, + 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_camera_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + temp_pyaddr = msm_camera_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_camera_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_camera_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + temp_pyaddr = msm_camera_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + temp_pcbcraddr = temp_pyaddr + vpe_ctrl->pcbcr_dis_offset; + msm_camera_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->p0_phy, vpe_buf->p1_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->p0_phy = + ((struct vpe_message *)data)->_u.msgOut.p0_Buffer; + pinfo->p1_phy = + ((struct vpe_message *)data)->_u.msgOut.p1_Buffer; + *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.planar0_off); + msm_camera_io_w(p1, vpe_device->vpebase + VPE_OUTP0_ADDR_OFFSET); + /* for video CbCr address */ + p1 = (regp1->paddr + regp1->info.planar1_off); + msm_camera_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_p1_stride * (1<<16)) + + st_half.buf_p0_stride; + + msm_camera_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 p0_addr, + uint32_t p1_addr, uint32_t p2_addr) +{ + struct vpe_message msg; + uint8_t outid; + msg._d = outid = msgid; + msg._u.msgOut.output_id = msgid; + msg._u.msgOut.p0_Buffer = p0_addr; + msg._u.msgOut.p1_Buffer = p1_addr; + msg._u.msgOut.p2_Buffer = p2_addr; + 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_camera_io_r(vpe_device->vpebase + + VPE_SRCP0_ADDR_OFFSET); + orig_src_cbcr = msm_camera_io_r(vpe_device->vpebase + + VPE_SRCP1_ADDR_OFFSET); + + pyaddr = msm_camera_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + pcbcraddr = msm_camera_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_camera_io_w(pyaddr + (vpe_ctrl->out_w * + vpe_ctrl->out_h), vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + msm_camera_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_camera_io_w(pyaddr + vpe_ctrl->out_w, + vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + msm_camera_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_camera_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_camera_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_camera_io_r(vpe_device->vpebase + + VPE_SRCP0_ADDR_OFFSET); + src_cbcr = msm_camera_io_r(vpe_device->vpebase + + VPE_SRCP1_ADDR_OFFSET); + pyaddr = msm_camera_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + pcbcraddr = msm_camera_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + } + + if (vpe_ctrl->dis_en) + pcbcraddr = vpe_ctrl->pcbcr_before_dis; + + msm_camera_io_w(src_y, + vpe_device->vpebase + VPE_OUTP0_ADDR_OFFSET); + msm_camera_io_w(src_cbcr, + vpe_device->vpebase + VPE_OUTP1_ADDR_OFFSET); + + temp = msm_camera_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) + & 0xFFFFFFFC; + msm_camera_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, pyaddr); + } 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, pyaddr); + } + + 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_camera_io_r_mb(vpe_device->vpebase + + VPE_INTR_STATUS_OFFSET); + msm_camera_io_w_mb(irq_status, vpe_device->vpebase + + VPE_INTR_CLEAR_OFFSET); + + msm_camera_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_camera_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + vpe_ctrl->out_cbcr_addr = msm_camera_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 */ + vpe_device->vpebase = NULL; +/* 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); + vpe_device->vpebase = NULL; + 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 0000000000000000000000000000000000000000..f4d328d6e157f6496b03c995b2f1397d33d9d634 --- /dev/null +++ b/drivers/media/video/msm/msm_vpe1.h @@ -0,0 +1,254 @@ +/* Copyright (c) 2010, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 p0_Buffer; + uint32_t p1_Buffer; + uint32_t p2_Buffer; + 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 0000000000000000000000000000000000000000..a7b5156c1ecf02c9b751b8113a113ab7f98fbbc2 --- /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 0000000000000000000000000000000000000000..309fcec05b406d2c33df4d1c4e5ed6c9748b774c --- /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 0000000000000000000000000000000000000000..24edaf2b72d1dbde9290f3731b640a8f810d8c2e --- /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 0000000000000000000000000000000000000000..8e81bda5949a3b50e8ec780c44470394963255cf --- /dev/null +++ b/drivers/media/video/msm/mt9d113.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. + * + */ + +#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, + 0x0990, 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, + 0x0990, 0x0002, WORD_LEN); + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA103, WORD_LEN); + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x0990, 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_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, WORD_LEN); + 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 id is%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_cam_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 0000000000000000000000000000000000000000..f22f16c542cf315b88301d06a208229cf028e552 --- /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 0000000000000000000000000000000000000000..cd5be0f441b9d3cbd2f8025c43d7887ebbc7e06a --- /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 0000000000000000000000000000000000000000..94546f476a1800991c3503715dec8d0d9bc657b5 --- /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 0000000000000000000000000000000000000000..9052a354453e2e4bbc039d95f43094ceb851b5a4 --- /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 0000000000000000000000000000000000000000..9a4bd7eefa92dfe104488cb36e6d1e16e5c129f8 --- /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 0000000000000000000000000000000000000000..05798130bbf7fcda15c66a61a0b8e7fc9a058cb2 --- /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 0000000000000000000000000000000000000000..919738099f379f582a11952b1e8af8a4110db71e --- /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 0000000000000000000000000000000000000000..a652c9f8ef53ead770e7737e9314bcee04c501c1 --- /dev/null +++ b/drivers/media/video/msm/mt9p012_fox.c @@ -0,0 +1,1346 @@ +/* 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 "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 0000000000000000000000000000000000000000..5ba0e62d3dcf10872dfd9f729eb1caef0e1e2489 --- /dev/null +++ b/drivers/media/video/msm/mt9p012_km.c @@ -0,0 +1,1296 @@ +/* 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 "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 0000000000000000000000000000000000000000..aefabd4122ce53541332e4acd5e2fbdec7085b1a --- /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 0000000000000000000000000000000000000000..109930b6c55d1244c3c74530565992784ea4b1e2 --- /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 0000000000000000000000000000000000000000..06fe4196fec9956879b2812eb29744edd8abb3a2 --- /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 0000000000000000000000000000000000000000..e1f6167416c4fabb6971f5d876f1386c1e4b7603 --- /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 0000000000000000000000000000000000000000..f6b7c280f3e34314ea0302287413f2c0be854e2b --- /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 0000000000000000000000000000000000000000..9a9867e2cc28fb33df282455e7ac7749e0ac0b53 --- /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/ov5640.c b/drivers/media/video/msm/ov5640.c new file mode 100644 index 0000000000000000000000000000000000000000..1380bcf642a4bdcc326904fe2a0e94f40912315d --- /dev/null +++ b/drivers/media/video/msm/ov5640.c @@ -0,0 +1,1477 @@ +/* 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 "ov5640.h" + +#define FALSE 0 +#define TRUE 1 + +struct ov5640_work { + struct work_struct work; +}; + +struct __ov5640_ctrl { + const struct msm_camera_sensor_info *sensordata; + int sensormode; + uint fps_divider; /* init to 1 * 0x00000400 */ + uint pict_fps_divider; /* init to 1 * 0x00000400 */ + u16 curr_step_pos; + u16 curr_lens_pos; + u16 init_curr_lens_pos; + u16 my_reg_gain; + u16 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; +}; + +static DECLARE_WAIT_QUEUE_HEAD(ov5640_wait_queue); +DEFINE_MUTEX(ov5640_mutex); + +static int ov5640_pwdn_gpio; +static int ov5640_reset_gpio; +static int ov5640_driver_pwdn_gpio; +static int OV5640_CSI_CONFIG; +static struct ov5640_work *ov5640_sensorw; +static struct i2c_client *ov5640_client; +static u8 ov5640_i2c_buf[4]; +static u8 ov5640_counter; +static int16_t ov5640_effect; +static int is_autoflash; +static int effect_value; +unsigned int ov5640_SAT_U = 0x40; +unsigned int ov5640_SAT_V = 0x40; + +static struct __ov5640_ctrl *ov5640_ctrl; +static int ov5640_afinit = 1; + +struct rw_semaphore ov_leds_list_lock; +struct list_head ov_leds_list; + +static int ov5640_i2c_remove(struct i2c_client *client); +static int ov5640_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id); + +static int ov5640_i2c_txdata(u16 saddr, u8 *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(ov5640_client->adapter, msg, 1) < 0) + return -EIO; + else + return 0; +} + +static int ov5640_i2c_write(unsigned short saddr, unsigned int waddr, + unsigned short bdata, u8 trytimes) +{ + int rc = -EIO; + + ov5640_counter = 0; + ov5640_i2c_buf[0] = (waddr & 0xFF00) >> 8; + ov5640_i2c_buf[1] = (waddr & 0x00FF); + ov5640_i2c_buf[2] = (bdata & 0x00FF); + + while ((ov5640_counter < trytimes) && (rc != 0)) { + rc = ov5640_i2c_txdata(saddr, ov5640_i2c_buf, 3); + + if (rc < 0) { + ov5640_counter++; + CDBG("***--CAMERA i2c_write_w failed,i2c addr=0x%x," + "command addr = 0x%x, val = 0x%x,s=%d," + "rc=%d!\n", saddr, waddr, bdata, + ov5640_counter, rc); + msleep(20); + } + } + return rc; +} + +static int ov5640_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(ov5640_client->adapter, msgs, 2) < 0) { + CDBG("ov5640_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t ov5640_i2c_read_byte(unsigned short saddr, + unsigned int raddr, unsigned int *rdata) +{ + int rc = 0; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00)>>8; + buf[1] = (raddr & 0x00FF); + + rc = ov5640_i2c_rxdata(saddr, buf, 1); + if (rc < 0) { + CDBG("ov5640_i2c_read_byte failed!\n"); + return rc; + } + + *rdata = buf[0]; + + return rc; +} + +static int32_t ov5640_writepregs(struct ov5640_sensor *ptb, int32_t len) +{ + int32_t i, ret = 0; + uint32_t regv; + + for (i = 0; i < len; i++) { + if (0 == ptb[i].mask) { + ov5640_i2c_write(ov5640_client->addr, ptb[i].addr, + ptb[i].data, 10); + } else { + ov5640_i2c_read_byte(ov5640_client->addr, ptb[i].addr, + ®v); + regv &= ptb[i].mask; + regv |= (ptb[i].data & (~ptb[i].mask)); + ov5640_i2c_write(ov5640_client->addr, ptb[i].addr, + regv, 10); + } + } + return ret; +} + +static void camera_sw_power_onoff(int v) +{ + if (v == 0) { + CDBG("camera_sw_power_onoff: down\n"); + ov5640_i2c_write(ov5640_client->addr, 0x3008, 0x42, 10); + } else { + CDBG("camera_sw_power_onoff: on\n"); + ov5640_i2c_write(ov5640_client->addr, 0x3008, 0x02, 10); + } +} + +static void ov5640_power_off(void) +{ + CDBG("--CAMERA-- %s ... (Start...)\n", __func__); + gpio_set_value(ov5640_pwdn_gpio, 1); + CDBG("--CAMERA-- %s ... (End...)\n", __func__); +} + +static void ov5640_power_on(void) +{ + CDBG("--CAMERA-- %s ... (Start...)\n", __func__); + gpio_set_value(ov5640_pwdn_gpio, 0); + CDBG("--CAMERA-- %s ... (End...)\n", __func__); +} + +static void ov5640_power_reset(void) +{ + CDBG("--CAMERA-- %s ... (Start...)\n", __func__); + gpio_set_value(ov5640_reset_gpio, 1); /* reset camera reset pin */ + msleep(20); + gpio_set_value(ov5640_reset_gpio, 0); + msleep(20); + gpio_set_value(ov5640_reset_gpio, 1); + msleep(20); + + CDBG("--CAMERA-- %s ... (End...)\n", __func__); +} + +static int ov5640_probe_readID(const struct msm_camera_sensor_info *data) +{ + int rc = 0; + u32 device_id_high = 0; + u32 device_id_low = 0; + + CDBG("--CAMERA-- %s (Start...)\n", __func__); + CDBG("--CAMERA-- %s sensor poweron,begin to read ID!\n", __func__); + + /* 0x300A ,sensor ID register */ + rc = ov5640_i2c_read_byte(ov5640_client->addr, 0x300A, + &device_id_high); + + if (rc < 0) { + CDBG("--CAMERA-- %s ok , readI2C failed, rc = 0x%x\r\n", + __func__, rc); + return rc; + } + CDBG("--CAMERA-- %s readID high byte, data = 0x%x\r\n", + __func__, device_id_high); + + /* 0x300B ,sensor ID register */ + rc = ov5640_i2c_read_byte(ov5640_client->addr, 0x300B, + &device_id_low); + if (rc < 0) { + CDBG("--CAMERA-- %s ok , readI2C failed,rc = 0x%x\r\n", + __func__, rc); + return rc; + } + + CDBG("--CAMERA-- %s readID low byte, data = 0x%x\r\n", + __func__, device_id_low); + CDBG("--CAMERA-- %s return ID :0x%x\n", __func__, + (device_id_high << 8) + device_id_low); + + /* 0x5640, ov5640 chip id */ + if ((device_id_high << 8) + device_id_low != OV5640_SENSOR_ID) { + CDBG("--CAMERA-- %s ok , device id error, should be 0x%x\r\n", + __func__, OV5640_SENSOR_ID); + return -EINVAL; + } else { + CDBG("--CAMERA-- %s ok , device id=0x%x\n", __func__, + OV5640_SENSOR_ID); + return 0; + } +} + +static int ov5640_af_setting(void) +{ + int rc = 0; + int lens = sizeof(ov5640_afinit_tbl) / sizeof(ov5640_afinit_tbl[0]); + + CDBG("--CAMERA-- ov5640_af_setting\n"); + + ov5640_i2c_write(ov5640_client->addr, 0x3000, 0x20, 10); + + rc = ov5640_i2c_txdata(ov5640_client->addr, ov5640_afinit_tbl, lens); + if (rc < 0) { + CDBG("--CAMERA-- AF_init failed\n"); + return rc; + } + + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_MAIN, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_ACK, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_PARA0, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_PARA1, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_PARA2, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_PARA3, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_PARA4, 0x00, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_FW_STATUS, 0x7f, 10); + ov5640_i2c_write(ov5640_client->addr, 0x3000, 0x00, 10); + + return rc; +} + +static int ov5640_set_flash_light(enum led_brightness brightness) +{ + struct led_classdev *led_cdev; + + CDBG("ov5640_set_flash_light brightness = %d\n", brightness); + + down_read(&ov_leds_list_lock); + list_for_each_entry(led_cdev, &ov_leds_list, node) { + if (!strncmp(led_cdev->name, "flashlight", 10)) + break; + } + up_read(&ov_leds_list_lock); + + if (led_cdev) { + led_brightness_set(led_cdev, brightness); + } else { + CDBG("get flashlight device failed\n"); + return -EINVAL; + } + + return 0; +} + +static int ov5640_video_config(void) +{ + int rc = 0; + + CDBG("--CAMERA-- ov5640_video_config\n"); + CDBG("--CAMERA-- preview in, is_autoflash - 0x%x\n", is_autoflash); + + /* autoflash setting */ + if (is_autoflash == 1) + ov5640_set_flash_light(LED_OFF); + + /* preview setting */ + rc = OV5640CORE_WRITEPREG(ov5640_preview_tbl); + return rc; +} + +static int ov5640_snapshot_config(void) +{ + int rc = 0; + unsigned int tmp; + + CDBG("--CAMERA-- SENSOR_SNAPSHOT_MODE\n"); + CDBG("--CAMERA-- %s, snapshot in, is_autoflash - 0x%x\n", __func__, + is_autoflash); + + if (is_autoflash == 1) { + ov5640_i2c_read_byte(ov5640_client->addr, 0x350b, &tmp); + CDBG("--CAMERA-- GAIN VALUE : %x\n", tmp); + if ((tmp & 0x80) == 0) + ov5640_set_flash_light(LED_OFF); + else + ov5640_set_flash_light(LED_FULL); + } + + rc = OV5640CORE_WRITEPREG(ov5640_capture_tbl); + + return rc; +} + +static int ov5640_setting(enum msm_s_reg_update rupdate, + enum msm_s_setting rt) +{ + int rc = -EINVAL, tmp; + struct msm_camera_csi_params ov5640_csi_params; + + CDBG("--CAMERA-- %s (Start...), rupdate=%d\n", __func__, rupdate); + + switch (rupdate) { + case S_UPDATE_PERIODIC: + if (!OV5640_CSI_CONFIG) { + camera_sw_power_onoff(0); /* standby */ + msleep(20); + + ov5640_csi_params.lane_cnt = 2; + ov5640_csi_params.data_format = CSI_8BIT; + ov5640_csi_params.lane_assign = 0xe4; + ov5640_csi_params.dpcm_scheme = 0; + ov5640_csi_params.settle_cnt = 0x6; + + CDBG("%s: msm_camio_csi_config\n", __func__); + + rc = msm_camio_csi_config(&ov5640_csi_params); + msleep(20); + camera_sw_power_onoff(1); /* on */ + msleep(20); + + OV5640_CSI_CONFIG = 1; + + } else { + rc = 0; + } + + if (S_RES_PREVIEW == rt) + rc = ov5640_video_config(); + else if (S_RES_CAPTURE == rt) + rc = ov5640_snapshot_config(); + + break; /* UPDATE_PERIODIC */ + + case S_REG_INIT: + CDBG("--CAMERA-- S_REG_INIT (Start)\n"); + + rc = ov5640_i2c_write(ov5640_client->addr, 0x3103, 0x11, 10); + rc = ov5640_i2c_write(ov5640_client->addr, 0x3008, 0x82, 10); + msleep(20); + + /* set sensor init setting */ + CDBG("set sensor init setting\n"); + rc = OV5640CORE_WRITEPREG(ov5640_init_tbl); + if (rc < 0) { + CDBG("sensor init setting failed\n"); + break; + } + + /* set image quality setting */ + rc = OV5640CORE_WRITEPREG(ov5640_init_iq_tbl); + rc = ov5640_i2c_read_byte(ov5640_client->addr, 0x4740, &tmp); + CDBG("--CAMERA-- init 0x4740 value=0x%x\n", tmp); + + if (tmp != 0x21) { + rc = ov5640_i2c_write(ov5640_client->addr, 0x4740, + 0x21, 10); + msleep(20); + rc = ov5640_i2c_read_byte(ov5640_client->addr, + 0x4740, &tmp); + CDBG("--CAMERA-- WG 0x4740 value=0x%x\n", tmp); + } + + CDBG("--CAMERA-- AF_init: ov5640_afinit = %d\n", + ov5640_afinit); + if (ov5640_afinit == 1) { + rc = ov5640_af_setting(); + if (rc < 0) { + CDBG("--CAMERA-- ov5640_af_setting failed\n"); + break; + } + ov5640_afinit = 0; + } + + /* reset fps_divider */ + ov5640_ctrl->fps_divider = 1 * 0x0400; + CDBG("--CAMERA-- S_REG_INIT (End)\n"); + break; /* case REG_INIT: */ + + default: + break; + } /* switch (rupdate) */ + + CDBG("--CAMERA-- %s (End), rupdate=%d\n", __func__, rupdate); + + return rc; +} + +static int ov5640_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int rc = -ENOMEM; + + CDBG("--CAMERA-- %s\n", __func__); + ov5640_ctrl = kzalloc(sizeof(struct __ov5640_ctrl), GFP_KERNEL); + if (!ov5640_ctrl) { + CDBG("--CAMERA-- kzalloc ov5640_ctrl error !!\n"); + kfree(ov5640_ctrl); + return rc; + } + + ov5640_ctrl->fps_divider = 1 * 0x00000400; + ov5640_ctrl->pict_fps_divider = 1 * 0x00000400; + ov5640_ctrl->set_test = S_TEST_OFF; + ov5640_ctrl->prev_res = S_QTR_SIZE; + ov5640_ctrl->pict_res = S_FULL_SIZE; + + if (data) + ov5640_ctrl->sensordata = data; + + ov5640_power_off(); + + CDBG("%s: msm_camio_clk_rate_set\n", __func__); + + msm_camio_clk_rate_set(24000000); + msleep(20); + + ov5640_power_on(); + ov5640_power_reset(); + + CDBG("%s: init sequence\n", __func__); + + if (ov5640_ctrl->prev_res == S_QTR_SIZE) + rc = ov5640_setting(S_REG_INIT, S_RES_PREVIEW); + else + rc = ov5640_setting(S_REG_INIT, S_RES_CAPTURE); + + if (rc < 0) { + CDBG("--CAMERA-- %s : ov5640_setting failed. rc = %d\n", + __func__, rc); + kfree(ov5640_ctrl); + return rc; + } + + OV5640_CSI_CONFIG = 0; + + CDBG("--CAMERA--re_init_sensor ok!!\n"); + return rc; +} + +static int ov5640_sensor_release(void) +{ + CDBG("--CAMERA--ov5640_sensor_release!!\n"); + + mutex_lock(&ov5640_mutex); + + ov5640_power_off(); + + kfree(ov5640_ctrl); + ov5640_ctrl = NULL; + + OV5640_CSI_CONFIG = 0; + + mutex_unlock(&ov5640_mutex); + return 0; +} + +static const struct i2c_device_id ov5640_i2c_id[] = { + {"ov5640", 0}, {} +}; + +static int ov5640_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static int ov5640_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&ov5640_wait_queue); + return 0; +} + +static long ov5640_set_effect(int mode, int effect) +{ + int rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + + switch (mode) { + case SENSOR_PREVIEW_MODE: + /* Context A Special Effects */ + CDBG("--CAMERA-- %s ...SENSOR_PREVIEW_MODE\n", __func__); + break; + + case SENSOR_SNAPSHOT_MODE: + /* Context B Special Effects */ + CDBG("--CAMERA-- %s ...SENSOR_SNAPSHOT_MODE\n", __func__); + break; + + default: + break; + } + + effect_value = effect; + + switch (effect) { + case CAMERA_EFFECT_OFF: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_OFF\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_normal_tbl); + /* for recover saturation level when change special effect */ + ov5640_i2c_write(ov5640_client->addr, 0x5583, ov5640_SAT_U, + 10); + /* for recover saturation level when change special effect */ + ov5640_i2c_write(ov5640_client->addr, 0x5584, ov5640_SAT_V, + 10); + break; + + case CAMERA_EFFECT_MONO: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_MONO\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_mono_tbl); + break; + + case CAMERA_EFFECT_BW: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_BW\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_bw_tbl); + break; + + case CAMERA_EFFECT_BLUISH: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_BLUISH\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_bluish_tbl); + break; + + case CAMERA_EFFECT_SOLARIZE: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_NEGATIVE\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_solarize_tbl); + break; + + case CAMERA_EFFECT_SEPIA: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_SEPIA\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_sepia_tbl); + break; + + case CAMERA_EFFECT_REDDISH: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_REDDISH\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_reddish_tbl); + break; + + case CAMERA_EFFECT_GREENISH: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_GREENISH\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_greenish_tbl); + break; + + case CAMERA_EFFECT_NEGATIVE: + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_NEGATIVE\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_effect_negative_tbl); + break; + + default: + CDBG("--CAMERA-- %s ...Default(Not Support)\n", __func__); + } + + ov5640_effect = effect; + /* Refresh Sequencer */ + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov5640_set_brightness(int8_t brightness) +{ + int rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...brightness = %d\n", __func__ , brightness); + + switch (brightness) { + case CAMERA_BRIGHTNESS_LV0: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV0\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv0_tbl); + break; + + case CAMERA_BRIGHTNESS_LV1: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV1\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv1_tbl); + break; + + case CAMERA_BRIGHTNESS_LV2: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV2\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv2_tbl); + break; + + case CAMERA_BRIGHTNESS_LV3: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV3\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv3_tbl); + break; + + case CAMERA_BRIGHTNESS_LV4: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV4\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_default_lv4_tbl); + break; + + case CAMERA_BRIGHTNESS_LV5: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV5\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv5_tbl); + break; + + case CAMERA_BRIGHTNESS_LV6: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV6\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv6_tbl); + break; + + case CAMERA_BRIGHTNESS_LV7: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV7\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv7_tbl); + break; + + case CAMERA_BRIGHTNESS_LV8: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_LV8\n"); + rc = OV5640CORE_WRITEPREG(ov5640_brightness_lv8_tbl); + break; + + default: + CDBG("--CAMERA--CAMERA_BRIGHTNESS_ERROR COMMAND\n"); + break; + } + + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov5640_set_contrast(int contrast) +{ + int rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...contrast = %d\n", __func__ , contrast); + + if (effect_value == CAMERA_EFFECT_OFF) { + switch (contrast) { + case CAMERA_CONTRAST_LV0: + CDBG("--CAMERA--CAMERA_CONTRAST_LV0\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv0_tbl); + break; + + case CAMERA_CONTRAST_LV1: + CDBG("--CAMERA--CAMERA_CONTRAST_LV1\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv1_tbl); + break; + + case CAMERA_CONTRAST_LV2: + CDBG("--CAMERA--CAMERA_CONTRAST_LV2\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv2_tbl); + break; + + case CAMERA_CONTRAST_LV3: + CDBG("--CAMERA--CAMERA_CONTRAST_LV3\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv3_tbl); + break; + + case CAMERA_CONTRAST_LV4: + CDBG("--CAMERA--CAMERA_CONTRAST_LV4\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_contrast_default_lv4_tbl); + break; + + case CAMERA_CONTRAST_LV5: + CDBG("--CAMERA--CAMERA_CONTRAST_LV5\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv5_tbl); + break; + + case CAMERA_CONTRAST_LV6: + CDBG("--CAMERA--CAMERA_CONTRAST_LV6\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv6_tbl); + break; + + case CAMERA_CONTRAST_LV7: + CDBG("--CAMERA--CAMERA_CONTRAST_LV7\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv7_tbl); + break; + + case CAMERA_CONTRAST_LV8: + CDBG("--CAMERA--CAMERA_CONTRAST_LV8\n"); + rc = OV5640CORE_WRITEPREG(ov5640_contrast_lv8_tbl); + break; + + default: + CDBG("--CAMERA--CAMERA_CONTRAST_ERROR COMMAND\n"); + break; + } + } + + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov5640_set_sharpness(int sharpness) +{ + int rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...sharpness = %d\n", __func__ , sharpness); + + if (effect_value == CAMERA_EFFECT_OFF) { + switch (sharpness) { + case CAMERA_SHARPNESS_LV0: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV0\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv0_tbl); + break; + + case CAMERA_SHARPNESS_LV1: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV1\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv1_tbl); + break; + + case CAMERA_SHARPNESS_LV2: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV2\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_sharpness_default_lv2_tbl); + break; + + case CAMERA_SHARPNESS_LV3: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV3\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv3_tbl); + break; + + case CAMERA_SHARPNESS_LV4: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV4\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv4_tbl); + break; + + case CAMERA_SHARPNESS_LV5: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV5\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv5_tbl); + break; + + case CAMERA_SHARPNESS_LV6: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV6\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv6_tbl); + break; + + case CAMERA_SHARPNESS_LV7: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV7\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv7_tbl); + break; + + case CAMERA_SHARPNESS_LV8: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV8\n"); + rc = OV5640CORE_WRITEPREG(ov5640_sharpness_lv8_tbl); + break; + + default: + CDBG("--CAMERA--CAMERA_SHARPNESS_ERROR COMMAND\n"); + break; + } + } + + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov5640_set_saturation(int saturation) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...saturation = %d\n", __func__ , saturation); + + if (effect_value == CAMERA_EFFECT_OFF) { + switch (saturation) { + case CAMERA_SATURATION_LV0: + CDBG("--CAMERA--CAMERA_SATURATION_LV0\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv0_tbl); + break; + + case CAMERA_SATURATION_LV1: + CDBG("--CAMERA--CAMERA_SATURATION_LV1\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv1_tbl); + break; + + case CAMERA_SATURATION_LV2: + CDBG("--CAMERA--CAMERA_SATURATION_LV2\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv2_tbl); + break; + + case CAMERA_SATURATION_LV3: + CDBG("--CAMERA--CAMERA_SATURATION_LV3\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv3_tbl); + break; + + case CAMERA_SATURATION_LV4: + CDBG("--CAMERA--CAMERA_SATURATION_LV4\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_saturation_default_lv4_tbl); + break; + + case CAMERA_SATURATION_LV5: + CDBG("--CAMERA--CAMERA_SATURATION_LV5\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv5_tbl); + break; + + case CAMERA_SATURATION_LV6: + CDBG("--CAMERA--CAMERA_SATURATION_LV6\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv6_tbl); + break; + + case CAMERA_SATURATION_LV7: + CDBG("--CAMERA--CAMERA_SATURATION_LV7\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv7_tbl); + break; + + case CAMERA_SATURATION_LV8: + CDBG("--CAMERA--CAMERA_SATURATION_LV8\n"); + rc = OV5640CORE_WRITEPREG(ov5640_saturation_lv8_tbl); + break; + + default: + CDBG("--CAMERA--CAMERA_SATURATION_ERROR COMMAND\n"); + break; + } + } + + /* for recover saturation level when change special effect */ + switch (saturation) { + case CAMERA_SATURATION_LV0: + CDBG("--CAMERA--CAMERA_SATURATION_LV0\n"); + ov5640_SAT_U = 0x00; + ov5640_SAT_V = 0x00; + break; + case CAMERA_SATURATION_LV1: + CDBG("--CAMERA--CAMERA_SATURATION_LV1\n"); + ov5640_SAT_U = 0x10; + ov5640_SAT_V = 0x10; + break; + case CAMERA_SATURATION_LV2: + CDBG("--CAMERA--CAMERA_SATURATION_LV2\n"); + ov5640_SAT_U = 0x20; + ov5640_SAT_V = 0x20; + break; + case CAMERA_SATURATION_LV3: + CDBG("--CAMERA--CAMERA_SATURATION_LV3\n"); + ov5640_SAT_U = 0x30; + ov5640_SAT_V = 0x30; + break; + case CAMERA_SATURATION_LV4: + CDBG("--CAMERA--CAMERA_SATURATION_LV4\n"); + ov5640_SAT_U = 0x40; + ov5640_SAT_V = 0x40; break; + case CAMERA_SATURATION_LV5: + CDBG("--CAMERA--CAMERA_SATURATION_LV5\n"); + ov5640_SAT_U = 0x50; + ov5640_SAT_V = 0x50; break; + case CAMERA_SATURATION_LV6: + CDBG("--CAMERA--CAMERA_SATURATION_LV6\n"); + ov5640_SAT_U = 0x60; + ov5640_SAT_V = 0x60; + break; + case CAMERA_SATURATION_LV7: + CDBG("--CAMERA--CAMERA_SATURATION_LV7\n"); + ov5640_SAT_U = 0x70; + ov5640_SAT_V = 0x70; break; + case CAMERA_SATURATION_LV8: + CDBG("--CAMERA--CAMERA_SATURATION_LV8\n"); + ov5640_SAT_U = 0x80; + ov5640_SAT_V = 0x80; + break; + default: + CDBG("--CAMERA--CAMERA_SATURATION_ERROR COMMAND\n"); + break; + } + + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static long ov5640_set_antibanding(int antibanding) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...antibanding = %d\n", __func__, antibanding); + + switch (antibanding) { + case CAMERA_ANTIBANDING_OFF: + CDBG("--CAMERA--CAMERA_ANTIBANDING_OFF\n"); + break; + + case CAMERA_ANTIBANDING_60HZ: + CDBG("--CAMERA--CAMERA_ANTIBANDING_60HZ\n"); + rc = OV5640CORE_WRITEPREG(ov5640_antibanding_60z_tbl); + break; + + case CAMERA_ANTIBANDING_50HZ: + CDBG("--CAMERA--CAMERA_ANTIBANDING_50HZ\n"); + rc = OV5640CORE_WRITEPREG(ov5640_antibanding_50z_tbl); + break; + + case CAMERA_ANTIBANDING_AUTO: + CDBG("--CAMERA--CAMERA_ANTIBANDING_AUTO\n"); + rc = OV5640CORE_WRITEPREG(ov5640_antibanding_auto_tbl); + break; + + default: + CDBG("--CAMERA--CAMERA_ANTIBANDING_ERROR COMMAND\n"); + break; + } + + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static long ov5640_set_exposure_mode(int mode) +{ + long rc = 0; + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...mode = %d\n", __func__ , mode); + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int32_t ov5640_lens_shading_enable(uint8_t is_enable) +{ + int32_t rc = 0; + CDBG("--CAMERA--%s: ...(Start). enable = %d\n", __func__, is_enable); + + if (is_enable) { + CDBG("%s: enable~!!\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_lens_shading_on_tbl); + } else { + CDBG("%s: disable~!!\n", __func__); + rc = OV5640CORE_WRITEPREG(ov5640_lens_shading_off_tbl); + } + CDBG("--CAMERA--%s: ...(End). rc = %d\n", __func__, rc); + return rc; +} + +static int ov5640_set_sensor_mode(int mode, int res) +{ + int rc = 0; + + CDBG("--CAMERA-- ov5640_set_sensor_mode mode = %d, res = %d\n", + mode, res); + + switch (mode) { + case SENSOR_PREVIEW_MODE: + CDBG("--CAMERA-- SENSOR_PREVIEW_MODE\n"); + rc = ov5640_setting(S_UPDATE_PERIODIC, S_RES_PREVIEW); + break; + + case SENSOR_SNAPSHOT_MODE: + CDBG("--CAMERA-- SENSOR_SNAPSHOT_MODE\n"); + rc = ov5640_setting(S_UPDATE_PERIODIC, S_RES_CAPTURE); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + CDBG("--CAMERA-- SENSOR_RAW_SNAPSHOT_MODE\n"); + rc = ov5640_setting(S_UPDATE_PERIODIC, S_RES_CAPTURE); + break; + + default: + CDBG("--CAMERA--ov5640_set_sensor_mode no support\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static int ov5640_set_wb_oem(uint8_t param) +{ + int rc = 0; + unsigned int tmp2; + + CDBG("[kylin] %s \r\n", __func__); + + ov5640_i2c_read_byte(ov5640_client->addr, 0x350b, &tmp2); + CDBG("--CAMERA-- GAIN VALUE : %x\n", tmp2); + + switch (param) { + case CAMERA_WB_AUTO: + + CDBG("--CAMERA--CAMERA_WB_AUTO\n"); + rc = OV5640CORE_WRITEPREG(ov5640_wb_def); + break; + + case CAMERA_WB_CUSTOM: + CDBG("--CAMERA--CAMERA_WB_CUSTOM\n"); + rc = OV5640CORE_WRITEPREG(ov5640_wb_custom); + break; + case CAMERA_WB_INCANDESCENT: + CDBG("--CAMERA--CAMERA_WB_INCANDESCENT\n"); + rc = OV5640CORE_WRITEPREG(ov5640_wb_inc); + break; + case CAMERA_WB_DAYLIGHT: + CDBG("--CAMERA--CAMERA_WB_DAYLIGHT\n"); + rc = OV5640CORE_WRITEPREG(ov5640_wb_daylight); + break; + case CAMERA_WB_CLOUDY_DAYLIGHT: + CDBG("--CAMERA--CAMERA_WB_CLOUDY_DAYLIGHT\n"); + rc = OV5640CORE_WRITEPREG(ov5640_wb_cloudy); + break; + default: + break; + } + return rc; +} + +static int ov5640_set_touchaec(uint32_t x, uint32_t y) +{ + uint8_t aec_arr[8] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; + int idx = 0; + int i; + + CDBG("[kylin] %s x: %d ,y: %d\r\n", __func__ , x, y); + idx = x / 2 + y * 2; + CDBG("[kylin] idx: %d\r\n", idx); + + if (x % 2 == 0) + aec_arr[idx] = 0x10 | 0x0a; + else + aec_arr[idx] = 0x01 | 0xa0; + + for (i = 0; i < 8; i++) { + CDBG("write : %x val : %x ", 0x5688 + i, aec_arr[i]); + ov5640_i2c_write(ov5640_client->addr, 0x5688 + i, + aec_arr[i], 10); + } + + return 1; +} + +static int ov5640_set_exposure_compensation(int compensation) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + + CDBG("--CAMERA-- %s ...exposure_compensation = %d\n", __func__ , + compensation); + + switch (compensation) { + case CAMERA_EXPOSURE_COMPENSATION_LV0: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV0\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_exposure_compensation_lv0_tbl); + break; + + case CAMERA_EXPOSURE_COMPENSATION_LV1: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV1\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_exposure_compensation_lv1_tbl); + break; + + case CAMERA_EXPOSURE_COMPENSATION_LV2: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV2\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_exposure_compensation_lv2_default_tbl); + break; + + case CAMERA_EXPOSURE_COMPENSATION_LV3: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV3\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_exposure_compensation_lv3_tbl); + break; + + case CAMERA_EXPOSURE_COMPENSATION_LV4: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV3\n"); + rc = OV5640CORE_WRITEPREG( + ov5640_exposure_compensation_lv4_tbl); + break; + + default: + CDBG("--CAMERA--ERROR CAMERA_EXPOSURE_COMPENSATION\n"); + break; + } + + CDBG("--CAMERA-- %s ...(End)\n", __func__); + + return rc; +} + +static int ov5640_sensor_start_af(void) +{ + int i; + unsigned int af_st = 0; + unsigned int af_ack = 0; + unsigned int tmp = 0; + int rc = 0; + + CDBG("--CAMERA-- %s (Start...)\n", __func__); + + ov5640_i2c_read_byte(ov5640_client->addr, + OV5640_CMD_FW_STATUS, &af_st); + CDBG("--CAMERA-- %s af_st = %d\n", __func__, af_st); + + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_ACK, 0x01, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_MAIN, 0x03, 10); + + for (i = 0; i < 50; i++) { + ov5640_i2c_read_byte(ov5640_client->addr, + OV5640_CMD_ACK, &af_ack); + if (af_ack == 0) + break; + msleep(50); + } + CDBG("--CAMERA-- %s af_ack = 0x%x\n", __func__, af_ack); + + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_FW_STATUS, + &af_st); + CDBG("--CAMERA-- %s af_st = %d\n", __func__, af_st); + + if (af_st == 0x10) { + CDBG("--CAMERA-- %s AF ok and release AF setting~!!\n", + __func__); + } else { + CDBG("--CAMERA-- %s AF not ready!!\n", __func__); + } + + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_ACK, 0x01, 10); + ov5640_i2c_write(ov5640_client->addr, OV5640_CMD_MAIN, 0x07, 10); + + for (i = 0; i < 70; i++) { + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_ACK, + &af_ack); + if (af_ack == 0) + break; + msleep(25); + } + + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_PARA0, &tmp); + CDBG("0x3024 = %x\n", tmp); + rc = ((tmp == 0) ? 1 : 0); + + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_PARA1, &tmp); + CDBG("0x3025 = %x\n", tmp); + rc = ((tmp == 0) ? 1 : 0); + + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_PARA2, &tmp); + CDBG("0x3026 = %x\n", tmp); + rc = ((tmp == 0) ? 1 : 0); + + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_PARA3, &tmp); + CDBG("0x3027 = %x\n", tmp); + rc = ((tmp == 0) ? 1 : 0) ; + + ov5640_i2c_read_byte(ov5640_client->addr, OV5640_CMD_PARA4, &tmp); + CDBG("0x3028 = %x\n", tmp); + rc = ((tmp == 0) ? 1 : 0) ; + + CDBG("--CAMERA-- %s rc = %d(End...)\n", __func__, rc); + return rc; +} + +static int ov5640_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; + + CDBG("--CAMERA-- %s %d\n", __func__, cdata.cfgtype); + + mutex_lock(&ov5640_mutex); + + switch (cdata.cfgtype) { + case CFG_SET_MODE: + rc = ov5640_set_sensor_mode(cdata.mode, cdata.rs); + break; + + case CFG_SET_EFFECT: + CDBG("--CAMERA-- CFG_SET_EFFECT mode=%d," + "effect = %d !!\n", cdata.mode, + cdata.cfg.effect); + rc = ov5640_set_effect(cdata.mode, cdata.cfg.effect); + break; + + case CFG_START: + CDBG("--CAMERA-- CFG_START (Not Support) !!\n"); + /* Not Support */ + break; + + case CFG_PWR_UP: + CDBG("--CAMERA-- CFG_PWR_UP (Not Support) !!\n"); + /* Not Support */ + break; + + case CFG_PWR_DOWN: + CDBG("--CAMERA-- CFG_PWR_DOWN (Not Support)\n"); + ov5640_power_off(); + break; + + case CFG_SET_DEFAULT_FOCUS: + CDBG("--CAMERA-- CFG_SET_DEFAULT_FOCUS (Not Implement) !!\n"); + break; + + case CFG_MOVE_FOCUS: + CDBG("--CAMERA-- CFG_MOVE_FOCUS (Not Implement) !!\n"); + break; + + case CFG_SET_BRIGHTNESS: + CDBG("--CAMERA-- CFG_SET_BRIGHTNESS !!\n"); + rc = ov5640_set_brightness(cdata.cfg.brightness); + break; + + case CFG_SET_CONTRAST: + CDBG("--CAMERA-- CFG_SET_CONTRAST !!\n"); + rc = ov5640_set_contrast(cdata.cfg.contrast); + break; + + case CFG_SET_EXPOSURE_MODE: + CDBG("--CAMERA-- CFG_SET_EXPOSURE_MODE !!\n"); + rc = ov5640_set_exposure_mode(cdata.cfg.ae_mode); + break; + + case CFG_SET_ANTIBANDING: + CDBG("--CAMERA-- CFG_SET_ANTIBANDING antibanding = %d!!\n", + cdata.cfg.antibanding); + rc = ov5640_set_antibanding(cdata.cfg.antibanding); + break; + + case CFG_SET_LENS_SHADING: + CDBG("--CAMERA-- CFG_SET_LENS_SHADING !!\n"); + rc = ov5640_lens_shading_enable( + cdata.cfg.lens_shading); + break; + + case CFG_SET_SATURATION: + CDBG("--CAMERA-- CFG_SET_SATURATION !!\n"); + rc = ov5640_set_saturation(cdata.cfg.saturation); + break; + + case CFG_SET_SHARPNESS: + CDBG("--CAMERA-- CFG_SET_SHARPNESS !!\n"); + rc = ov5640_set_sharpness(cdata.cfg.sharpness); + break; + + case CFG_SET_WB: + CDBG("--CAMERA-- CFG_SET_WB!!\n"); + ov5640_set_wb_oem(cdata.cfg.wb_val); + rc = 0 ; + break; + + case CFG_SET_TOUCHAEC: + CDBG("--CAMERA-- CFG_SET_TOUCHAEC!!\n"); + ov5640_set_touchaec(cdata.cfg.aec_cord.x, + cdata.cfg.aec_cord.y); + rc = 0 ; + break; + + case CFG_SET_AUTO_FOCUS: + CDBG("--CAMERA-- CFG_SET_AUTO_FOCUS !\n"); + rc = ov5640_sensor_start_af(); + break; + + case CFG_SET_AUTOFLASH: + CDBG("--CAMERA-- CFG_SET_AUTOFLASH !\n"); + is_autoflash = cdata.cfg.is_autoflash; + CDBG("[kylin] is autoflash %d\r\n", is_autoflash); + rc = 0; + break; + + case CFG_SET_EXPOSURE_COMPENSATION: + CDBG("--CAMERA-- CFG_SET_EXPOSURE_COMPENSATION !\n"); + rc = ov5640_set_exposure_compensation( + cdata.cfg.exp_compensation); + break; + + default: + CDBG("%s: Command=%d (Not Implement)!!\n", __func__, + cdata.cfgtype); + rc = -EINVAL; + break; + } + + mutex_unlock(&ov5640_mutex); + return rc; +} + +static struct i2c_driver ov5640_i2c_driver = { + .id_table = ov5640_i2c_id, + .probe = ov5640_i2c_probe, + .remove = ov5640_i2c_remove, + .driver = { + .name = "ov5640", + }, +}; + +static int ov5640_probe_init_gpio(const struct msm_camera_sensor_info *data) +{ + int rc = 0; + + CDBG("--CAMERA-- %s\n", __func__); + + ov5640_pwdn_gpio = data->sensor_pwd; + ov5640_reset_gpio = data->sensor_reset; + ov5640_driver_pwdn_gpio = data->vcm_pwd ; + + if (data->vcm_enable) + gpio_direction_output(data->vcm_pwd, 1); + + gpio_direction_output(data->sensor_reset, 1); + gpio_direction_output(data->sensor_pwd, 1); + + return rc; + +} + +static void ov5640_probe_free_gpio(const struct msm_camera_sensor_info *data) +{ + gpio_free(ov5640_pwdn_gpio); + gpio_free(ov5640_reset_gpio); + + if (data->vcm_enable) { + gpio_free(ov5640_driver_pwdn_gpio); + ov5640_driver_pwdn_gpio = 0xFF ; + } + + ov5640_pwdn_gpio = 0xFF; + ov5640_reset_gpio = 0xFF; +} + +static int ov5640_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = -ENOTSUPP; + + CDBG("--CAMERA-- %s (Start...)\n", __func__); + rc = i2c_add_driver(&ov5640_i2c_driver); + CDBG("--CAMERA-- i2c_add_driver ret:0x%x,ov5640_client=0x%x\n", + rc, (unsigned int)ov5640_client); + if ((rc < 0) || (ov5640_client == NULL)) { + CDBG("--CAMERA-- i2c_add_driver FAILS!!\n"); + return rc; + } + + rc = ov5640_probe_init_gpio(info); + if (rc < 0) + return rc; + + ov5640_power_off(); + + /* SENSOR NEED MCLK TO DO I2C COMMUNICTION, OPEN CLK FIRST*/ + msm_camio_clk_rate_set(24000000); + + msleep(20); + + ov5640_power_on(); + ov5640_power_reset(); + + rc = ov5640_probe_readID(info); + + if (rc < 0) { + CDBG("--CAMERA--ov5640_probe_readID Fail !!~~~~!!\n"); + CDBG("--CAMERA-- %s, unregister\n", __func__); + i2c_del_driver(&ov5640_i2c_driver); + ov5640_power_off(); + ov5640_probe_free_gpio(info); + return rc; + } + + s->s_init = ov5640_sensor_open_init; + s->s_release = ov5640_sensor_release; + s->s_config = ov5640_sensor_config; + s->s_camera_type = BACK_CAMERA_2D; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + + ov5640_power_off(); + + CDBG("--CAMERA-- %s (End...)\n", __func__); + return rc; +} + +static int ov5640_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + CDBG("--CAMERA-- %s ... (Start...)\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("--CAMERA--i2c_check_functionality failed\n"); + return -ENOMEM; + } + + ov5640_sensorw = kzalloc(sizeof(struct ov5640_work), GFP_KERNEL); + if (!ov5640_sensorw) { + CDBG("--CAMERA--kzalloc failed\n"); + return -ENOMEM; + } + + i2c_set_clientdata(client, ov5640_sensorw); + ov5640_init_client(client); + ov5640_client = client; + + CDBG("--CAMERA-- %s ... (End...)\n", __func__); + return 0; +} + +static int __ov5640_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, ov5640_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __ov5640_probe, + .driver = { + .name = "msm_camera_ov5640", + .owner = THIS_MODULE, + }, +}; + +static int __init ov5640_init(void) +{ + ov5640_i2c_buf[0] = 0x5A; + return platform_driver_register(&msm_camera_driver); +} + +module_init(ov5640_init); + +MODULE_DESCRIPTION("OV5640 YUV MIPI sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/ov5640.h b/drivers/media/video/msm/ov5640.h new file mode 100644 index 0000000000000000000000000000000000000000..0e65329ce634183adf3c91a41746d495a3e9bb2d --- /dev/null +++ b/drivers/media/video/msm/ov5640.h @@ -0,0 +1,2993 @@ +/* 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. + */ + + +/* +[SENSOR] +Sensor Model: OV5640 +Camera Module: +Lens Model: +Driver IC: +PV Size = 640 x 480 +Cap Size = 2592 x 1944 +Output Format = YUYV +MCLK Speed = 24M +PV DVP_PCLK = 28M +Cap DVP_PCLK = 56M +PV Frame Rate = 30fps +Cap Frame Rate = 7.5fps +I2C Slave ID = 0x78 +I2C Mode = 16Addr, 8Data +*/ + +#ifndef CAMSENSOR_OV5640 +#define CAMSENSOR_OV5640 + +#define INVMASK(v) (0xff-v) +#define OV5640CORE_WRITEPREG(PTBL) ov5640_writepregs(PTBL,\ + sizeof(PTBL)/sizeof(PTBL[0])) + +/* OV SENSOR SCCB */ +struct ov5640_sensor { + uint16_t addr; + uint8_t data; + uint8_t mask; +}; + +/* Auto Focus Command */ +#define OV5640_CMD_MAIN 0x3022 +#define OV5640_CMD_ACK 0x3023 +#define OV5640_CMD_PARA0 0x3024 +#define OV5640_CMD_PARA1 0x3025 +#define OV5640_CMD_PARA2 0x3026 +#define OV5640_CMD_PARA3 0x3027 +#define OV5640_CMD_PARA4 0x3028 +#define OV5640_CMD_FW_STATUS 0x3029 + +/* Sensor ID */ +#define OV5640_SENSOR_ID 0x5640 + +#define capture_framerate 750 /* 7.5fps capture frame rate */ +#define g_preview_frameRate 3000 /* 30fps preview frame rate */ + +struct ov5640_sensor ov5640_init_tbl[] = { + {0x3008, 0x42}, + {0x3103, 0x03}, + {0x3017, 0x00}, + {0x3018, 0x00}, + {0x3034, 0x18}, + {0x3035, 0x14}, + {0x3036, 0x38}, + {0x3037, 0x13}, + {0x3108, 0x01}, + {0x3630, 0x36}, + {0x3631, 0x0e}, + {0x3632, 0xe2}, + {0x3633, 0x12}, + {0x3621, 0xe0}, + {0x3704, 0xa0}, + {0x3703, 0x5a}, + {0x3715, 0x78}, + {0x3717, 0x01}, + {0x370b, 0x60}, + {0x3705, 0x1a}, + {0x3905, 0x02}, + {0x3906, 0x10}, + {0x3901, 0x0a}, + {0x3731, 0x12}, + {0x3600, 0x08}, + {0x3601, 0x33}, + {0x302d, 0x60}, + {0x3620, 0x52}, + {0x371b, 0x20}, + {0x471c, 0x50}, + {0x3a13, 0x43}, + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + {0x3635, 0x13}, + {0x3636, 0x03}, + {0x3634, 0x40}, + {0x3622, 0x01}, + {0x3c01, 0x34}, + {0x3c04, 0x28}, + {0x3c05, 0x98}, + {0x3c06, 0x00}, + {0x3c07, 0x08}, + {0x3c08, 0x00}, + {0x3c09, 0x1c}, + {0x3c0a, 0x9c}, + {0x3c0b, 0x40}, + {0x3820, 0x41}, + {0x3821, 0x07}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0x9b}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x380c, 0x07}, + {0x380d, 0x68}, + {0x380e, 0x03}, + {0x380f, 0xd8}, + {0x3810, 0x00}, + {0x3811, 0x10}, + {0x3812, 0x00}, + {0x3813, 0x06}, + {0x3618, 0x00}, + {0x3612, 0x29}, + {0x3708, 0x64}, + {0x3709, 0x52}, + {0x370c, 0x03}, + {0x3a02, 0x03}, + {0x3a03, 0xd8}, + {0x3a08, 0x01}, + {0x3a09, 0x27}, + {0x3a0a, 0x00}, + {0x3a0b, 0xf6}, + {0x3a0e, 0x03}, + {0x3a0d, 0x04}, + {0x3a14, 0x03}, + {0x3a15, 0xd8}, + {0x4001, 0x02}, + {0x4004, 0x02}, + {0x3000, 0x00}, + {0x3002, 0x1c}, + {0x3004, 0xff}, + {0x3006, 0xc3}, + {0x300e, 0x45}, + {0x302e, 0x08}, + {0x4300, 0x30}, + {0x501f, 0x00}, + {0x4713, 0x03}, + {0x4407, 0x04}, + {0x440e, 0x00}, + {0x460b, 0x35}, + {0x460c, 0x22}, + {0x4837, 0x44}, + {0x3824, 0x02}, + {0x5000, 0xa7}, + {0x5001, 0xa3}, + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + {0x5381, 0x1e}, + {0x5382, 0x5b}, + {0x5383, 0x08}, + {0x5384, 0x0a}, + {0x5385, 0x7e}, + {0x5386, 0x88}, + {0x5387, 0x7c}, + {0x5388, 0x6c}, + {0x5389, 0x10}, + {0x538a, 0x01}, + {0x538b, 0x98}, + {0x5300, 0x08}, + {0x5301, 0x30}, + {0x5302, 0x10}, + {0x5303, 0x00}, + {0x5304, 0x08}, + {0x5305, 0x30}, + {0x5306, 0x08}, + {0x5307, 0x16}, + {0x5309, 0x08}, + {0x530a, 0x30}, + {0x530b, 0x04}, + {0x530c, 0x06}, + {0x5480, 0x01}, + {0x5481, 0x08}, + {0x5482, 0x14}, + {0x5483, 0x28}, + {0x5484, 0x51}, + {0x5485, 0x65}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x87}, + {0x5489, 0x91}, + {0x548a, 0x9a}, + {0x548b, 0xaa}, + {0x548c, 0xb8}, + {0x548d, 0xcd}, + {0x548e, 0xdd}, + {0x548f, 0xea}, + {0x5490, 0x1d}, + {0x5580, 0x02}, + {0x5583, 0x40}, + {0x5584, 0x10}, + {0x5589, 0x10}, + {0x558a, 0x00}, + {0x558b, 0xf8}, + {0x5800, 0x23}, + {0x5801, 0x14}, + {0x5802, 0x0f}, + {0x5803, 0x0f}, + {0x5804, 0x12}, + {0x5805, 0x26}, + {0x5806, 0x0c}, + {0x5807, 0x08}, + {0x5808, 0x05}, + {0x5809, 0x05}, + {0x580a, 0x08}, + {0x580b, 0x0d}, + {0x580c, 0x08}, + {0x580d, 0x03}, + {0x580e, 0x00}, + {0x580f, 0x00}, + {0x5810, 0x03}, + {0x5811, 0x09}, + {0x5812, 0x07}, + {0x5813, 0x03}, + {0x5814, 0x00}, + {0x5815, 0x01}, + {0x5816, 0x03}, + {0x5817, 0x08}, + {0x5818, 0x0d}, + {0x5819, 0x08}, + {0x581a, 0x05}, + {0x581b, 0x06}, + {0x581c, 0x08}, + {0x581d, 0x0e}, + {0x581e, 0x29}, + {0x581f, 0x17}, + {0x5820, 0x11}, + {0x5821, 0x11}, + {0x5822, 0x15}, + {0x5823, 0x28}, + {0x5824, 0x46}, + {0x5825, 0x26}, + {0x5826, 0x08}, + {0x5827, 0x26}, + {0x5828, 0x64}, + {0x5829, 0x26}, + {0x582a, 0x24}, + {0x582b, 0x22}, + {0x582c, 0x24}, + {0x582d, 0x24}, + {0x582e, 0x06}, + {0x582f, 0x22}, + {0x5830, 0x40}, + {0x5831, 0x42}, + {0x5832, 0x24}, + {0x5833, 0x26}, + {0x5834, 0x24}, + {0x5835, 0x22}, + {0x5836, 0x22}, + {0x5837, 0x26}, + {0x5838, 0x44}, + {0x5839, 0x24}, + {0x583a, 0x26}, + {0x583b, 0x28}, + {0x583c, 0x42}, + {0x583d, 0xce}, + {0x5025, 0x00}, + {0x3a0f, 0x30}, + {0x3a10, 0x28}, + {0x3a1b, 0x30}, + {0x3a1e, 0x26}, + {0x3a11, 0x60}, + {0x3a1f, 0x14}, + {0x3008, 0x02}, +}; + +struct ov5640_sensor ov5640_init_iq_tbl[] = { +/* Lens correction */ +/* OV5640 LENC setting */ + {0x5800, 0x3f}, + {0x5801, 0x20}, + {0x5802, 0x1a}, + {0x5803, 0x1a}, + {0x5804, 0x23}, + {0x5805, 0x3f}, + {0x5806, 0x11}, + {0x5807, 0x0c}, + {0x5808, 0x09}, + {0x5809, 0x08}, + {0x580a, 0x0d}, + {0x580b, 0x12}, + {0x580c, 0x0d}, + {0x580d, 0x04}, + {0x580e, 0x00}, + {0x580f, 0x00}, + {0x5810, 0x05}, + {0x5811, 0x0d}, + {0x5812, 0x0d}, + {0x5813, 0x04}, + {0x5814, 0x00}, + {0x5815, 0x00}, + {0x5816, 0x04}, + {0x5817, 0x0d}, + {0x5818, 0x13}, + {0x5819, 0x0d}, + {0x581a, 0x08}, + {0x581b, 0x08}, + {0x581c, 0x0c}, + {0x581d, 0x13}, + {0x581e, 0x3f}, + {0x581f, 0x1f}, + {0x5820, 0x1b}, + {0x5821, 0x1c}, + {0x5822, 0x23}, + {0x5823, 0x3f}, + {0x5824, 0x6a}, + {0x5825, 0x06}, + {0x5826, 0x08}, + {0x5827, 0x06}, + {0x5828, 0x2a}, + {0x5829, 0x08}, + {0x582a, 0x24}, + {0x582b, 0x24}, + {0x582c, 0x24}, + {0x582d, 0x08}, + {0x582e, 0x08}, + {0x582f, 0x22}, + {0x5830, 0x40}, + {0x5831, 0x22}, + {0x5832, 0x06}, + {0x5833, 0x08}, + {0x5834, 0x24}, + {0x5835, 0x24}, + {0x5836, 0x04}, + {0x5837, 0x0a}, + {0x5838, 0x86}, + {0x5839, 0x08}, + {0x583a, 0x28}, + {0x583b, 0x28}, + {0x583c, 0x66}, + {0x583d, 0xce}, +/* AEC */ + {0x3a0f, 0x38}, + {0x3a10, 0x30}, + {0x3a11, 0x61}, + {0x3a1b, 0x38}, + {0x3a1e, 0x30}, + {0x3a1f, 0x10}, + /* AWB */ + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x88}, + {0x518a, 0x54}, + {0x518b, 0xee}, + {0x518c, 0xb2}, + {0x518d, 0x50}, + {0x518e, 0x34}, + {0x518f, 0x6b}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x6c}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x09}, + {0x519d, 0x2b}, + {0x519e, 0x38}, + +/* UV Adjust Auto Mode */ + {0x5580, 0x02}, /* 02 ;Sat enable */ + {0x5588, 0x01}, /*40 ;enable UV adj */ + {0x5583, 0x40}, /* ;offset high */ + {0x5584, 0x18}, /* ;offset low */ + {0x5589, 0x18}, /* ;gth1 */ + {0x558a, 0x00}, + {0x358b, 0xf8}, /* ;gth2 */ +}; + +struct ov5640_sensor ov5640_preview_tbl[] = { +/* @@ MIPI_2lane_5M to vga(YUV) 30fps 99 640 480 98 0 0 */ + {0x3503, 0x00}, /* enable AE back from capture to preview */ + {0x3035, 0x14}, + {0x3036, 0x38}, + {0x3820, 0x41}, + {0x3821, 0x07}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3803, 0x04}, + {0x3807, 0x9b}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x380c, 0x07}, + {0x380d, 0x68}, + {0x380e, 0x03}, + {0x380f, 0xd8}, + {0x3813, 0x06}, + {0x3618, 0x00}, + {0x3612, 0x29}, + {0x3708, 0x64}, + {0x3709, 0x52}, + {0x370c, 0x03}, + {0x5001, 0xa3}, + {0x4004, 0x02}, + {0x4005, 0x18}, + {0x4837, 0x44}, + {0x4713, 0x03}, + {0x4407, 0x04}, + {0x460b, 0x35}, + {0x460c, 0x22}, + {0x3824, 0x02}, +}; + +struct ov5640_sensor ov5640_capture_tbl[] = { +/* @@ MIPI_2lane_5M(YUV) 7.5/15fps 99 2592 1944 98 0 0 */ + {0x3035, 0x21}, /* 11 */ + {0x3036, 0x54}, + {0x3820, 0x40}, + {0x3821, 0x06}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3803, 0x00}, + {0x3807, 0x9f}, + {0x3808, 0x0a}, + {0x3809, 0x20}, + {0x380a, 0x07}, + {0x380b, 0x98}, + {0x380c, 0x0b}, + {0x380d, 0x1c}, + {0x380e, 0x07}, + {0x380f, 0xb0}, + {0x3813, 0x04}, + {0x3618, 0x04}, + {0x3612, 0x2b}, + {0x3708, 0x21}, + {0x3709, 0x12}, + {0x370c, 0x00}, + {0x5001, 0x83}, + {0x4004, 0x06}, + {0x4005, 0x1a}, + {0x4837, 0x15}, /* 0a */ + {0x4713, 0x02}, + {0x4407, 0x0c}, + {0x460b, 0x37}, + {0x460c, 0x20}, + {0x3824, 0x01}, +}; + +/* Contrast */ + +struct ov5640_sensor ov5640_contrast_lv0_tbl[] = { +/* Contrast -4 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, /* Enable BIT2 for contrast/brightness + control*/ + {0x5586, 0x10}, /* Gain */ + {0x5585, 0x10}, /* Offset */ + {0x5588, 0x00, INVMASK(0x04)}, /* Offset sign */ +}; + +struct ov5640_sensor ov5640_contrast_lv1_tbl[] = { +/* Contrast -3 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, /* Enable BIT2 for contrast/brightness + control */ + {0x5586, 0x14}, /* Gain */ + {0x5585, 0x14}, /* Offset */ + {0x5588, 0x00, INVMASK(0x04)}, /* Offset sign */ +}; + +struct ov5640_sensor ov5640_contrast_lv2_tbl[] = { +/* Contrast -2 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, /* Enable BIT2 for contrast/brightness + control */ + {0x5586, 0x18}, /* Gain */ + {0x5585, 0x18}, /* Offset */ + {0x5588, 0x00, INVMASK(0x04)}, /* Offset sign */ +}; + +struct ov5640_sensor ov5640_contrast_lv3_tbl[] = { +/* Contrast -1 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5586, 0x1c}, + {0x5585, 0x1c}, + {0x5588, 0x00, INVMASK(0x04)}, +}; + +struct ov5640_sensor ov5640_contrast_default_lv4_tbl[] = { +/* Contrast (Default) */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5586, 0x20}, + {0x5585, 0x00}, + {0x5588, 0x00, INVMASK(0x04)}, +}; + +struct ov5640_sensor ov5640_contrast_lv5_tbl[] = { +/* Contrast +1 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5586, 0x24}, + {0x5585, 0x10}, + {0x5588, 0x00, INVMASK(0x04)}, +}; + +struct ov5640_sensor ov5640_contrast_lv6_tbl[] = { +/* Contrast +2 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5586, 0x28}, + {0x5585, 0x18}, + {0x5588, 0x00, INVMASK(0x04)}, +}; + +struct ov5640_sensor ov5640_contrast_lv7_tbl[] = { +/* Contrast +3 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5586, 0x2c}, + {0x5585, 0x1c}, + {0x5588, 0x00, INVMASK(0x04)}, +}; + +struct ov5640_sensor ov5640_contrast_lv8_tbl[] = { +/* Contrast +4 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5586, 0x30}, + {0x5585, 0x20}, + {0x5588, 0x00, INVMASK(0x04)}, +}; + +/* Sharpness */ + +struct ov5640_sensor ov5640_sharpness_lv0_tbl[] = { +/* Sharpness 0 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x00}, +}; + +struct ov5640_sensor ov5640_sharpness_lv1_tbl[] = { +/* Sharpness 1 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x02}, +}; + +struct ov5640_sensor ov5640_sharpness_default_lv2_tbl[] = { +/* Sharpness_Auto (Default) */ + {0x5308, 0x00, INVMASK(0x40)}, + {0x5300, 0x08}, + {0x5301, 0x30}, + {0x5302, 0x10}, + {0x5303, 0x00}, + {0x5309, 0x08}, + {0x530a, 0x30}, + {0x530b, 0x04}, + {0x530c, 0x06}, +}; + +struct ov5640_sensor ov5640_sharpness_lv3_tbl[] = { +/* Sharpness 3 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x08}, +}; + +struct ov5640_sensor ov5640_sharpness_lv4_tbl[] = { +/* Sharpness 4 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x0c}, +}; + +struct ov5640_sensor ov5640_sharpness_lv5_tbl[] = { +/* Sharpness 5 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x10}, +}; + +struct ov5640_sensor ov5640_sharpness_lv6_tbl[] = { +/* Sharpness 6 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x14}, +}; + +struct ov5640_sensor ov5640_sharpness_lv7_tbl[] = { +/* Sharpness 7 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x18}, +}; + +struct ov5640_sensor ov5640_sharpness_lv8_tbl[] = { +/* Sharpness 8 */ + {0x5308, 0x40, INVMASK(0x40)}, + {0x5302, 0x20}, +}; + +/* Saturation */ + +struct ov5640_sensor ov5640_saturation_lv0_tbl[] = { +/* Saturation x0.25 */ + {0x5001, 0x83, INVMASK(0x80)}, /* SDE_En */ + {0x5583, 0x00}, /* Saturaion gain in U */ + {0x5584, 0x00}, /* Saturation gain in V */ + {0x5580, 0x02, INVMASK(0x02)}, /* Saturation enable */ + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv1_tbl[] = { +/* Saturation x0.5 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x10}, + {0x5584, 0x10}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv2_tbl[] = { +/* Saturation x0.75 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x20}, + {0x5584, 0x20}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv3_tbl[] = { +/* Saturation x0.75 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x30}, + {0x5584, 0x30}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_default_lv4_tbl[] = { +/* Saturation x1 (Default) */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x40}, + {0x5584, 0x40}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv5_tbl[] = { +/* Saturation x1.25 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x50}, + {0x5584, 0x50}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv6_tbl[] = { +/* Saturation x1.5 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x60}, + {0x5584, 0x60}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv7_tbl[] = { +/* Saturation x1.25 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x70}, + {0x5584, 0x70}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +struct ov5640_sensor ov5640_saturation_lv8_tbl[] = { +/* Saturation x1.5 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5583, 0x80}, + {0x5584, 0x80}, + {0x5580, 0x02, INVMASK(0x02)}, + {0x5588, 0x40, INVMASK(0x40)}, +}; + +/* Brightness */ + +struct ov5640_sensor ov5640_brightness_lv0_tbl[] = { +/* Brightness -4 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x40}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x08, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv1_tbl[] = { +/* Brightness -3 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x30}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x08, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv2_tbl[] = { +/* Brightness -2 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x20}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x08, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv3_tbl[] = { +/* Brightness -1 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x10}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x08, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_default_lv4_tbl[] = { +/* Brightness 0 (Default) */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x00}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x00, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv5_tbl[] = { +/* Brightness +1 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x10}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x00, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv6_tbl[] = { +/* Brightness +2 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x20}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x00, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv7_tbl[] = { +/* Brightness +3 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x30}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x00, INVMASK(0x08)}, +}; + +struct ov5640_sensor ov5640_brightness_lv8_tbl[] = { +/* Brightness +4 */ + {0x5001, 0x83, INVMASK(0x80)}, + {0x5587, 0x40}, + {0x5580, 0x04, INVMASK(0x04)}, + {0x5588, 0x00, INVMASK(0x08)}, +}; + +/* Exposure Compensation */ +struct ov5640_sensor ov5640_exposure_compensation_lv0_tbl[] = { + /* @@ +1.7EV */ + {0x3a0f, 0x60}, + {0x3a10, 0x58}, + {0x3a11, 0xa0}, + {0x3a1b, 0x60}, + {0x3a1e, 0x58}, + {0x3a1f, 0x20}, +}; + +struct ov5640_sensor ov5640_exposure_compensation_lv1_tbl[] = { + /* @@ +1.0EV */ + {0x3a0f, 0x50}, + {0x3a10, 0x48}, + {0x3a11, 0x90}, + {0x3a1b, 0x50}, + {0x3a1e, 0x48}, + {0x3a1f, 0x20}, +}; + +struct ov5640_sensor ov5640_exposure_compensation_lv2_default_tbl[] = { + /* @@ default */ + {0x3a0f, 0x38}, + {0x3a10, 0x30}, + {0x3a11, 0x61}, + {0x3a1b, 0x38}, + {0x3a1e, 0x30}, + {0x3a1f, 0x10}, +}; + +struct ov5640_sensor ov5640_exposure_compensation_lv3_tbl[] = { + /* @@ -1.0EV */ + {0x3a0f, 0x20}, + {0x3a10, 0x18}, + {0x3a11, 0x41}, + {0x3a1b, 0x20}, + {0x3a1e, 0x18}, + {0x3a1f, 0x10}, +}; + +struct ov5640_sensor ov5640_exposure_compensation_lv4_tbl[] = { + /* @@ -1.7EV */ + {0x3a0f, 0x10}, + {0x3a10, 0x08}, + {0x3a11, 0x10}, + {0x3a1b, 0x08}, + {0x3a1e, 0x20}, + {0x3a1f, 0x10}, +}; + +/* Auto Expourse Weight */ + +struct ov5640_sensor ov5640_ae_average_tbl[] = { + /* Whole Image Average */ + {0x5688, 0x11}, /* Zone 1/Zone 0 weight */ + {0x5689, 0x11}, /* Zone 3/Zone 2 weight */ + {0x569a, 0x11}, /* Zone 5/Zone 4 weight */ + {0x569b, 0x11}, /* Zone 7/Zone 6 weight */ + {0x569c, 0x11}, /* Zone 9/Zone 8 weight */ + {0x569d, 0x11}, /* Zone b/Zone a weight */ + {0x569e, 0x11}, /* Zone d/Zone c weight */ + {0x569f, 0x11}, /* Zone f/Zone e weight */ +}; + +struct ov5640_sensor ov5640_ae_centerweight_tbl[] = { + /* Whole Image Center More weight */ + {0x5688, 0x62}, + {0x5689, 0x26}, + {0x568a, 0xe6}, + {0x568b, 0x6e}, + {0x568c, 0xea}, + {0x568d, 0xae}, + {0x568e, 0xa6}, + {0x568f, 0x6a}, +}; + +/* Light Mode */ +struct ov5640_sensor ov5640_wb_def[] = { + {0x3406, 0x00, INVMASK(0x01)}, +}; + +struct ov5640_sensor ov5640_wb_custom[] = { + {0x3406, 0x01, INVMASK(0x01)}, + {0x3400, 0x04}, + {0x3401, 0x58}, + {0x3402, 0x04}, + {0x3403, 0x00}, + {0x3404, 0x08}, + {0x3405, 0x40}, +}; + +struct ov5640_sensor ov5640_wb_inc[] = { + {0x3406, 0x01, INVMASK(0x01)}, + {0x3400, 0x04}, + {0x3401, 0x88}, + {0x3402, 0x04}, + {0x3403, 0x00}, + {0x3404, 0x08}, + {0x3405, 0xb6}, +}; + +struct ov5640_sensor ov5640_wb_daylight[] = { + {0x3406, 0x01, INVMASK(0x01)}, + {0x3400, 0x07}, + {0x3401, 0x02}, + {0x3402, 0x04}, + {0x3403, 0x00}, + {0x3404, 0x05}, + {0x3405, 0x15}, +}; + +struct ov5640_sensor ov5640_wb_cloudy[] = { + {0x3406, 0x01, INVMASK(0x01)}, + {0x3400, 0x07}, + {0x3401, 0x88}, + {0x3402, 0x04}, + {0x3403, 0x00}, + {0x3404, 0x05}, + {0x3405, 0x00}, +}; + +/* EFFECT */ +struct ov5640_sensor ov5640_effect_normal_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x00, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0x40}, + {0x5584, 0x40}, +}; + +struct ov5640_sensor ov5640_effect_mono_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x20, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0x40}, + {0x5584, 0x40}, +}; + +struct ov5640_sensor ov5640_effect_bw_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x18, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0x80}, + {0x5584, 0x80}, +}; + +struct ov5640_sensor ov5640_effect_bluish_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x18, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0xa0}, + {0x5584, 0x40}, +}; + +struct ov5640_sensor ov5640_effect_solarize_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x00, INVMASK(0x78)}, + {0x5003, 0x09}, +}; + + +struct ov5640_sensor ov5640_effect_sepia_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x18, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0x40}, + {0x5584, 0xa0}, +}; + +struct ov5640_sensor ov5640_effect_reddish_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x18, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0x80}, + {0x5584, 0xc0}, +}; + +struct ov5640_sensor ov5640_effect_greenish_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x18, INVMASK(0x78)}, + {0x5003, 0x08}, + {0x5583, 0x60}, + {0x5584, 0x60}, +}; + +struct ov5640_sensor ov5640_effect_negative_tbl[] = { + {0x5001, 0x83, INVMASK(0x80)}, + {0x5580, 0x40, INVMASK(0x78)}, + {0x5003, 0x08}, +}; + +/* AntiBanding */ +struct ov5640_sensor ov5640_antibanding_auto_tbl[] = { + /* Auto-XCLK24MHz */ + {0x3622, 0x01}, /* PD-sel */ + {0x3635, 0x1c}, /* VMREF 3635[2:0] */ + {0x3634, 0x40}, /* I_5060 3643[2:0] */ + {0x3c01, 0x34}, + {0x3c00, 0x00}, + {0x3c04, 0x28}, + {0x3c05, 0x98}, + {0x3c06, 0x00}, + {0x3c07, 0x08}, + {0x3c08, 0x00}, + {0x3c09, 0x1c}, + {0x300c, 0x22}, /* 50/60div 300c[2:0] */ + {0x3c0a, 0x9c}, + {0x3c0b, 0x40}, +}; + +struct ov5640_sensor ov5640_antibanding_50z_tbl[] = { + /* Band 50Hz */ + {0x3c01, 0x80, INVMASK(0x80)}, + {0x3c00, 0x04}, +}; + +struct ov5640_sensor ov5640_antibanding_60z_tbl[] = { + /* Band 60Hz */ + {0x3c01, 0x80, INVMASK(0x80)}, + {0x3c00, 0x00}, +}; + + +/* Lens_shading */ + +struct ov5640_sensor ov5640_lens_shading_on_tbl[] = { + /* @@ Lenc On(C) */ + {0x5000, 0x80, INVMASK(0x80)}, +}; + +struct ov5640_sensor ov5640_lens_shading_off_tbl[] = { + /* Lenc Off */ + {0x5000, 0x00, INVMASK(0x80)}, +}; + +/* Auto Focus Firmware-use 2011-08-24 firmware settings */ +u8 ov5640_afinit_tbl[] = { + 0x80, 0x00, 0x02, 0x0b, 0x7b, 0x02, 0x07, 0xbd, 0xc2, + 0x01, 0x22, 0x22, 0x00, 0x02, 0x0b, 0x57, 0xe5, 0x1f, + 0x70, 0x72, 0xf5, 0x1e, 0xd2, 0x35, 0xff, 0xef, 0x25, + 0xe0, 0x24, 0x4b, 0xf8, 0xe4, 0xf6, 0x08, 0xf6, 0x0f, + 0xbf, 0x34, 0xf2, 0x90, 0x0e, 0x88, 0xe4, 0x93, 0xff, + 0xe5, 0x49, 0xc3, 0x9f, 0x50, 0x04, 0x7f, 0x05, 0x80, + 0x02, 0x7f, 0xfb, 0x78, 0xba, 0xa6, 0x07, 0x12, 0x0a, + 0xb4, 0x40, 0x04, 0x7f, 0x03, 0x80, 0x02, 0x7f, 0x30, + 0x78, 0xb9, 0xa6, 0x07, 0xe6, 0x18, 0xf6, 0x08, 0xe6, + 0x78, 0xb6, 0xf6, 0x78, 0xb9, 0xe6, 0x78, 0xb7, 0xf6, + 0x78, 0xbc, 0x76, 0x33, 0xe4, 0x08, 0xf6, 0x78, 0xb5, + 0x76, 0x01, 0x75, 0x48, 0x02, 0x78, 0xb3, 0xf6, 0x08, + 0xf6, 0x74, 0xff, 0x78, 0xbe, 0xf6, 0x08, 0xf6, 0x75, + 0x1f, 0x01, 0x78, 0xb9, 0xe6, 0x75, 0xf0, 0x05, 0xa4, + 0xf5, 0x49, 0x12, 0x08, 0x5b, 0xc2, 0x37, 0x22, 0x78, + 0xb5, 0xe6, 0xd3, 0x94, 0x00, 0x40, 0x02, 0x16, 0x22, + 0xe5, 0x1f, 0x64, 0x05, 0x70, 0x28, 0xf5, 0x1f, 0xc2, + 0x01, 0x78, 0xb6, 0xe6, 0x25, 0xe0, 0x24, 0x4b, 0xf8, + 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x78, 0x4b, 0xa6, 0x06, + 0x08, 0xa6, 0x07, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, + 0x90, 0x30, 0x28, 0xf0, 0x75, 0x1e, 0x10, 0xd2, 0x35, + 0x22, 0xe5, 0x49, 0x75, 0xf0, 0x05, 0x84, 0x78, 0xb9, + 0xf6, 0x90, 0x0e, 0x85, 0xe4, 0x93, 0xff, 0x25, 0xe0, + 0x24, 0x0a, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x78, + 0xb9, 0xe6, 0x25, 0xe0, 0x24, 0x4b, 0xf8, 0xa6, 0x04, + 0x08, 0xa6, 0x05, 0xef, 0x12, 0x0a, 0xbb, 0xd3, 0x78, + 0xb4, 0x96, 0xee, 0x18, 0x96, 0x40, 0x0d, 0x78, 0xb9, + 0xe6, 0x78, 0xb6, 0xf6, 0x78, 0xb3, 0xa6, 0x06, 0x08, + 0xa6, 0x07, 0x90, 0x0e, 0x85, 0xe4, 0x93, 0x12, 0x0a, + 0xbb, 0xc3, 0x78, 0xbf, 0x96, 0xee, 0x18, 0x96, 0x50, + 0x0d, 0x78, 0xb9, 0xe6, 0x78, 0xb7, 0xf6, 0x78, 0xbe, + 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0xb3, 0xe6, 0xfe, + 0x08, 0xe6, 0xc3, 0x78, 0xbf, 0x96, 0xff, 0xee, 0x18, + 0x96, 0x78, 0xc0, 0xf6, 0x08, 0xa6, 0x07, 0x90, 0x0e, + 0x8a, 0xe4, 0x18, 0x12, 0x0a, 0x99, 0xc3, 0x33, 0xce, + 0x33, 0xce, 0xd8, 0xf9, 0xff, 0xd3, 0xed, 0x9f, 0xec, + 0x9e, 0x40, 0x02, 0xd2, 0x37, 0x78, 0xb9, 0xe6, 0x08, + 0x26, 0x08, 0xf6, 0xe5, 0x1f, 0x64, 0x01, 0x70, 0x55, + 0xe6, 0xc3, 0x78, 0xbd, 0x12, 0x0a, 0x8f, 0x40, 0x10, + 0x12, 0x0a, 0x8a, 0x50, 0x0b, 0x30, 0x37, 0x41, 0x78, + 0xb9, 0xe6, 0x78, 0xb6, 0x66, 0x60, 0x39, 0x12, 0x0a, + 0xb2, 0x40, 0x04, 0x7f, 0xfe, 0x80, 0x02, 0x7f, 0x02, + 0x78, 0xba, 0xa6, 0x07, 0x78, 0xb6, 0xe6, 0x24, 0x03, + 0x78, 0xbc, 0xf6, 0x78, 0xb6, 0xe6, 0x24, 0xfd, 0x78, + 0xbd, 0xf6, 0x12, 0x0a, 0xb2, 0x40, 0x06, 0x78, 0xbd, + 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbc, 0xe6, 0xff, 0x78, + 0xbb, 0xa6, 0x07, 0x75, 0x1f, 0x02, 0x78, 0xb5, 0x76, + 0x01, 0x02, 0x02, 0x68, 0xe5, 0x1f, 0x64, 0x02, 0x60, + 0x03, 0x02, 0x02, 0x48, 0x78, 0xbb, 0xe6, 0xff, 0xc3, + 0x78, 0xbd, 0x12, 0x0a, 0x90, 0x40, 0x08, 0x12, 0x0a, + 0x8a, 0x50, 0x03, 0x02, 0x02, 0x46, 0x12, 0x0a, 0xb2, + 0x40, 0x04, 0x7f, 0xff, 0x80, 0x02, 0x7f, 0x01, 0x78, + 0xba, 0xa6, 0x07, 0x78, 0xb6, 0xe6, 0x04, 0x78, 0xbc, + 0xf6, 0x78, 0xb6, 0xe6, 0x14, 0x78, 0xbd, 0xf6, 0x18, + 0x12, 0x0a, 0xb4, 0x40, 0x04, 0xe6, 0xff, 0x80, 0x02, + 0x7f, 0x00, 0x78, 0xbc, 0xa6, 0x07, 0xd3, 0x08, 0xe6, + 0x64, 0x80, 0x94, 0x80, 0x40, 0x04, 0xe6, 0xff, 0x80, + 0x02, 0x7f, 0x00, 0x78, 0xbd, 0xa6, 0x07, 0xc3, 0x18, + 0xe6, 0x64, 0x80, 0x94, 0xb3, 0x50, 0x04, 0xe6, 0xff, + 0x80, 0x02, 0x7f, 0x33, 0x78, 0xbc, 0xa6, 0x07, 0xc3, + 0x08, 0xe6, 0x64, 0x80, 0x94, 0xb3, 0x50, 0x04, 0xe6, + 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xbd, 0xa6, 0x07, + 0x12, 0x0a, 0xb2, 0x40, 0x06, 0x78, 0xbd, 0xe6, 0xff, + 0x80, 0x04, 0x78, 0xbc, 0xe6, 0xff, 0x78, 0xbb, 0xa6, + 0x07, 0x75, 0x1f, 0x03, 0x78, 0xb5, 0x76, 0x01, 0x80, + 0x20, 0xe5, 0x1f, 0x64, 0x03, 0x70, 0x26, 0x78, 0xbb, + 0xe6, 0xff, 0xc3, 0x78, 0xbd, 0x12, 0x0a, 0x90, 0x40, + 0x05, 0x12, 0x0a, 0x8a, 0x40, 0x09, 0x78, 0xb6, 0xe6, + 0x78, 0xbb, 0xf6, 0x75, 0x1f, 0x04, 0x78, 0xbb, 0xe6, + 0x75, 0xf0, 0x05, 0xa4, 0xf5, 0x49, 0x02, 0x08, 0x5b, + 0xe5, 0x1f, 0xb4, 0x04, 0x1d, 0x90, 0x0e, 0x89, 0xe4, + 0x78, 0xc0, 0x12, 0x0a, 0x99, 0xc3, 0x33, 0xce, 0x33, + 0xce, 0xd8, 0xf9, 0xff, 0xd3, 0xed, 0x9f, 0xec, 0x9e, + 0x40, 0x02, 0xd2, 0x37, 0x75, 0x1f, 0x05, 0x22, 0xef, + 0x8d, 0xf0, 0xa4, 0xa8, 0xf0, 0xcf, 0x8c, 0xf0, 0xa4, + 0x28, 0xce, 0x8d, 0xf0, 0xa4, 0x2e, 0xfe, 0x22, 0xbc, + 0x00, 0x0b, 0xbe, 0x00, 0x29, 0xef, 0x8d, 0xf0, 0x84, + 0xff, 0xad, 0xf0, 0x22, 0xe4, 0xcc, 0xf8, 0x75, 0xf0, + 0x08, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xec, 0x33, + 0xfc, 0xee, 0x9d, 0xec, 0x98, 0x40, 0x05, 0xfc, 0xee, + 0x9d, 0xfe, 0x0f, 0xd5, 0xf0, 0xe9, 0xe4, 0xce, 0xfd, + 0x22, 0xed, 0xf8, 0xf5, 0xf0, 0xee, 0x84, 0x20, 0xd2, + 0x1c, 0xfe, 0xad, 0xf0, 0x75, 0xf0, 0x08, 0xef, 0x2f, + 0xff, 0xed, 0x33, 0xfd, 0x40, 0x07, 0x98, 0x50, 0x06, + 0xd5, 0xf0, 0xf2, 0x22, 0xc3, 0x98, 0xfd, 0x0f, 0xd5, + 0xf0, 0xea, 0x22, 0xe8, 0x8f, 0xf0, 0xa4, 0xcc, 0x8b, + 0xf0, 0xa4, 0x2c, 0xfc, 0xe9, 0x8e, 0xf0, 0xa4, 0x2c, + 0xfc, 0x8a, 0xf0, 0xed, 0xa4, 0x2c, 0xfc, 0xea, 0x8e, + 0xf0, 0xa4, 0xcd, 0xa8, 0xf0, 0x8b, 0xf0, 0xa4, 0x2d, + 0xcc, 0x38, 0x25, 0xf0, 0xfd, 0xe9, 0x8f, 0xf0, 0xa4, + 0x2c, 0xcd, 0x35, 0xf0, 0xfc, 0xeb, 0x8e, 0xf0, 0xa4, + 0xfe, 0xa9, 0xf0, 0xeb, 0x8f, 0xf0, 0xa4, 0xcf, 0xc5, + 0xf0, 0x2e, 0xcd, 0x39, 0xfe, 0xe4, 0x3c, 0xfc, 0xea, + 0xa4, 0x2d, 0xce, 0x35, 0xf0, 0xfd, 0xe4, 0x3c, 0xfc, + 0x22, 0x75, 0xf0, 0x08, 0x75, 0x82, 0x00, 0xef, 0x2f, + 0xff, 0xee, 0x33, 0xfe, 0xcd, 0x33, 0xcd, 0xcc, 0x33, + 0xcc, 0xc5, 0x82, 0x33, 0xc5, 0x82, 0x9b, 0xed, 0x9a, + 0xec, 0x99, 0xe5, 0x82, 0x98, 0x40, 0x0c, 0xf5, 0x82, + 0xee, 0x9b, 0xfe, 0xed, 0x9a, 0xfd, 0xec, 0x99, 0xfc, + 0x0f, 0xd5, 0xf0, 0xd6, 0xe4, 0xce, 0xfb, 0xe4, 0xcd, + 0xfa, 0xe4, 0xcc, 0xf9, 0xa8, 0x82, 0x22, 0xb8, 0x00, + 0xc1, 0xb9, 0x00, 0x59, 0xba, 0x00, 0x2d, 0xec, 0x8b, + 0xf0, 0x84, 0xcf, 0xce, 0xcd, 0xfc, 0xe5, 0xf0, 0xcb, + 0xf9, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, + 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xeb, 0x33, 0xfb, + 0x10, 0xd7, 0x03, 0x99, 0x40, 0x04, 0xeb, 0x99, 0xfb, + 0x0f, 0xd8, 0xe5, 0xe4, 0xf9, 0xfa, 0x22, 0x78, 0x18, + 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, + 0xec, 0x33, 0xfc, 0xc9, 0x33, 0xc9, 0x10, 0xd7, 0x05, + 0x9b, 0xe9, 0x9a, 0x40, 0x07, 0xec, 0x9b, 0xfc, 0xe9, + 0x9a, 0xf9, 0x0f, 0xd8, 0xe0, 0xe4, 0xc9, 0xfa, 0xe4, + 0xcc, 0xfb, 0x22, 0x75, 0xf0, 0x10, 0xef, 0x2f, 0xff, + 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xcc, 0x33, 0xcc, + 0xc8, 0x33, 0xc8, 0x10, 0xd7, 0x07, 0x9b, 0xec, 0x9a, + 0xe8, 0x99, 0x40, 0x0a, 0xed, 0x9b, 0xfd, 0xec, 0x9a, + 0xfc, 0xe8, 0x99, 0xf8, 0x0f, 0xd5, 0xf0, 0xda, 0xe4, + 0xcd, 0xfb, 0xe4, 0xcc, 0xfa, 0xe4, 0xc8, 0xf9, 0x22, + 0xeb, 0x9f, 0xf5, 0xf0, 0xea, 0x9e, 0x42, 0xf0, 0xe9, + 0x9d, 0x42, 0xf0, 0xe8, 0x9c, 0x45, 0xf0, 0x22, 0xe8, + 0x60, 0x0f, 0xef, 0xc3, 0x33, 0xff, 0xee, 0x33, 0xfe, + 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xd8, 0xf1, 0x22, + 0xe4, 0x93, 0xfc, 0x74, 0x01, 0x93, 0xfd, 0x74, 0x02, + 0x93, 0xfe, 0x74, 0x03, 0x93, 0xff, 0x22, 0xe6, 0xfb, + 0x08, 0xe6, 0xf9, 0x08, 0xe6, 0xfa, 0x08, 0xe6, 0xcb, + 0xf8, 0x22, 0xec, 0xf6, 0x08, 0xed, 0xf6, 0x08, 0xee, + 0xf6, 0x08, 0xef, 0xf6, 0x22, 0xa4, 0x25, 0x82, 0xf5, + 0x82, 0xe5, 0xf0, 0x35, 0x83, 0xf5, 0x83, 0x22, 0xd0, + 0x83, 0xd0, 0x82, 0xf8, 0xe4, 0x93, 0x70, 0x12, 0x74, + 0x01, 0x93, 0x70, 0x0d, 0xa3, 0xa3, 0x93, 0xf8, 0x74, + 0x01, 0x93, 0xf5, 0x82, 0x88, 0x83, 0xe4, 0x73, 0x74, + 0x02, 0x93, 0x68, 0x60, 0xef, 0xa3, 0xa3, 0xa3, 0x80, + 0xdf, 0x90, 0x38, 0x04, 0x78, 0x4f, 0x12, 0x09, 0x50, + 0x90, 0x38, 0x00, 0xe0, 0xfe, 0xa3, 0xe0, 0xfd, 0xed, + 0xff, 0xc3, 0x12, 0x09, 0x09, 0x90, 0x38, 0x10, 0x12, + 0x08, 0xfd, 0x90, 0x38, 0x06, 0x78, 0x51, 0x12, 0x09, + 0x50, 0x90, 0x38, 0x02, 0xe0, 0xfe, 0xa3, 0xe0, 0xfd, + 0xed, 0xff, 0xc3, 0x12, 0x09, 0x09, 0x90, 0x38, 0x12, + 0x12, 0x08, 0xfd, 0xa3, 0xe0, 0xb4, 0x31, 0x07, 0x78, + 0x4f, 0x79, 0x4f, 0x12, 0x09, 0x66, 0x90, 0x38, 0x14, + 0xe0, 0xb4, 0x71, 0x15, 0x78, 0x4f, 0xe6, 0xfe, 0x08, + 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, + 0xf9, 0x79, 0x50, 0xf7, 0xee, 0x19, 0xf7, 0x90, 0x38, + 0x15, 0xe0, 0xb4, 0x31, 0x07, 0x78, 0x51, 0x79, 0x51, + 0x12, 0x09, 0x66, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x71, + 0x15, 0x78, 0x51, 0xe6, 0xfe, 0x08, 0xe6, 0x78, 0x02, + 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, 0xf9, 0x79, 0x52, + 0xf7, 0xee, 0x19, 0xf7, 0x79, 0x4f, 0x12, 0x09, 0x38, + 0x09, 0x12, 0x09, 0x38, 0xaf, 0x45, 0x12, 0x08, 0xee, + 0x7d, 0x50, 0x12, 0x02, 0xa9, 0x78, 0x57, 0xa6, 0x06, + 0x08, 0xa6, 0x07, 0xaf, 0x43, 0x12, 0x08, 0xee, 0x7d, + 0x50, 0x12, 0x02, 0xa9, 0x78, 0x53, 0xa6, 0x06, 0x08, + 0xa6, 0x07, 0xaf, 0x46, 0x78, 0x51, 0x12, 0x08, 0xf0, + 0x7d, 0x3c, 0x12, 0x02, 0xa9, 0x78, 0x59, 0xa6, 0x06, + 0x08, 0xa6, 0x07, 0xaf, 0x44, 0x7e, 0x00, 0x78, 0x51, + 0x12, 0x08, 0xf2, 0x7d, 0x3c, 0x12, 0x02, 0xa9, 0x78, + 0x55, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xc3, 0x78, 0x58, + 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, + 0x76, 0x00, 0x08, 0x76, 0x08, 0xc3, 0x78, 0x5a, 0xe6, + 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, + 0x00, 0x08, 0x76, 0x08, 0x78, 0x57, 0x12, 0x09, 0x25, + 0xff, 0xd3, 0x78, 0x54, 0xe6, 0x9f, 0x18, 0xe6, 0x9e, + 0x40, 0x0e, 0x78, 0x57, 0xe6, 0x13, 0xfe, 0x08, 0xe6, + 0x78, 0x54, 0x12, 0x09, 0x5b, 0x80, 0x04, 0x7e, 0x00, + 0x7f, 0x00, 0x78, 0x5b, 0x12, 0x09, 0x1d, 0xff, 0xd3, + 0x78, 0x56, 0xe6, 0x9f, 0x18, 0xe6, 0x9e, 0x40, 0x0e, + 0x78, 0x59, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x56, + 0x12, 0x09, 0x5b, 0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, + 0xe4, 0xfc, 0xfd, 0x78, 0x5f, 0x12, 0x04, 0x5c, 0x78, + 0x57, 0x12, 0x09, 0x25, 0x78, 0x54, 0x26, 0xff, 0xee, + 0x18, 0x36, 0xfe, 0x78, 0x63, 0x12, 0x09, 0x1d, 0x78, + 0x56, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0xe4, 0xfc, + 0xfd, 0x78, 0x67, 0x12, 0x04, 0x5c, 0x12, 0x09, 0x2d, + 0x78, 0x63, 0x12, 0x04, 0x4f, 0xd3, 0x12, 0x04, 0x1b, + 0x40, 0x08, 0x12, 0x09, 0x2d, 0x78, 0x63, 0x12, 0x04, + 0x5c, 0x78, 0x51, 0x12, 0x09, 0x2f, 0x78, 0x67, 0x12, + 0x04, 0x4f, 0xd3, 0x12, 0x04, 0x1b, 0x40, 0x0a, 0x78, + 0x51, 0x12, 0x09, 0x2f, 0x78, 0x67, 0x12, 0x04, 0x5c, + 0xe4, 0xfd, 0x78, 0x5e, 0x12, 0x09, 0x48, 0x24, 0x01, + 0x12, 0x09, 0x11, 0x78, 0x62, 0x12, 0x09, 0x48, 0x24, + 0x02, 0x12, 0x09, 0x11, 0x78, 0x66, 0x12, 0x09, 0x48, + 0x24, 0x03, 0x12, 0x09, 0x11, 0x78, 0x6a, 0x12, 0x09, + 0x48, 0x24, 0x04, 0x12, 0x09, 0x11, 0x0d, 0xbd, 0x05, + 0xd4, 0xc2, 0x0e, 0xc2, 0x06, 0x22, 0x85, 0x08, 0x41, + 0x90, 0x30, 0x24, 0xe0, 0xf5, 0x3d, 0xa3, 0xe0, 0xf5, + 0x3e, 0xa3, 0xe0, 0xf5, 0x3f, 0xa3, 0xe0, 0xf5, 0x40, + 0xa3, 0xe0, 0xf5, 0x3c, 0xd2, 0x34, 0xe5, 0x41, 0x12, + 0x04, 0x74, 0x06, 0xc7, 0x03, 0x06, 0xcb, 0x04, 0x06, + 0xd1, 0x07, 0x06, 0xda, 0x08, 0x06, 0xeb, 0x12, 0x07, + 0x03, 0x18, 0x07, 0x19, 0x19, 0x06, 0xee, 0x1a, 0x06, + 0xfa, 0x1b, 0x07, 0x3e, 0x80, 0x07, 0x43, 0x81, 0x07, + 0xa1, 0x8f, 0x07, 0x90, 0x90, 0x07, 0xa1, 0x91, 0x07, + 0xa1, 0x92, 0x07, 0xa1, 0x93, 0x07, 0xa1, 0x94, 0x07, + 0xa1, 0x98, 0x07, 0x9e, 0x9f, 0x00, 0x00, 0x07, 0xbc, + 0x12, 0x0a, 0xf4, 0x22, 0x12, 0x0a, 0xf4, 0xd2, 0x03, + 0x22, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x02, 0x07, + 0xa1, 0xc2, 0x01, 0xc2, 0x02, 0xc2, 0x03, 0x12, 0x09, + 0x70, 0x75, 0x1e, 0x70, 0xd2, 0x35, 0x02, 0x07, 0xa1, + 0x02, 0x07, 0x8b, 0x85, 0x40, 0x48, 0x85, 0x3c, 0x49, + 0x12, 0x08, 0x5b, 0x02, 0x07, 0xa1, 0x85, 0x48, 0x40, + 0x85, 0x49, 0x3c, 0x02, 0x07, 0xa1, 0xe4, 0xf5, 0x22, + 0xf5, 0x23, 0x85, 0x40, 0x31, 0x85, 0x3f, 0x30, 0x85, + 0x3e, 0x2f, 0x85, 0x3d, 0x2e, 0x12, 0x0a, 0xc6, 0x80, + 0x1f, 0x75, 0x22, 0x00, 0x75, 0x23, 0x01, 0x74, 0xff, + 0xf5, 0x2d, 0xf5, 0x2c, 0xf5, 0x2b, 0xf5, 0x2a, 0x12, + 0x0a, 0xc6, 0x85, 0x2d, 0x40, 0x85, 0x2c, 0x3f, 0x85, + 0x2b, 0x3e, 0x85, 0x2a, 0x3d, 0xe4, 0xf5, 0x3c, 0x02, + 0x07, 0xa1, 0x12, 0x0b, 0x3d, 0x80, 0x5e, 0x85, 0x3d, + 0x43, 0x85, 0x3e, 0x44, 0xe5, 0x45, 0xc3, 0x13, 0xff, + 0xe5, 0x43, 0xc3, 0x9f, 0x50, 0x02, 0x8f, 0x43, 0xe5, + 0x46, 0xc3, 0x13, 0xff, 0xe5, 0x44, 0xc3, 0x9f, 0x50, + 0x02, 0x8f, 0x44, 0xe5, 0x45, 0xc3, 0x13, 0xff, 0xfd, + 0xe5, 0x43, 0x90, 0x0e, 0x7f, 0x12, 0x0b, 0x10, 0x40, + 0x04, 0xee, 0x9f, 0xf5, 0x43, 0xe5, 0x46, 0xc3, 0x13, + 0xff, 0xfd, 0xe5, 0x44, 0x90, 0x0e, 0x80, 0x12, 0x0b, + 0x10, 0x40, 0x04, 0xee, 0x9f, 0xf5, 0x44, 0x12, 0x04, + 0x9a, 0x80, 0x11, 0x85, 0x40, 0x46, 0x85, 0x3f, 0x45, + 0x85, 0x3e, 0x44, 0x85, 0x3d, 0x43, 0x80, 0x03, 0x02, + 0x04, 0x9a, 0x90, 0x30, 0x24, 0xe5, 0x3d, 0xf0, 0xa3, + 0xe5, 0x3e, 0xf0, 0xa3, 0xe5, 0x3f, 0xf0, 0xa3, 0xe5, + 0x40, 0xf0, 0xa3, 0xe5, 0x3c, 0xf0, 0x90, 0x30, 0x23, + 0xe4, 0xf0, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, + 0xc0, 0xd0, 0x90, 0x3f, 0x0c, 0xe0, 0xf5, 0x32, 0xe5, + 0x32, 0x30, 0xe3, 0x4c, 0x30, 0x36, 0x3e, 0x90, 0x60, + 0x19, + 0xe0, + 0xf5, + 0x0a, + 0xa3, + 0xe0, + 0xf5, + 0x0b, + 0x90, + 0x60, + 0x1d, + 0xe0, + 0xf5, + 0x14, + 0xa3, + 0xe0, + 0xf5, + 0x15, + 0x30, + 0x01, + 0x06, + 0x30, + 0x33, + 0x03, + 0xd3, + 0x80, + 0x01, + 0xc3, + 0x92, + 0x09, + 0x30, + 0x02, + 0x06, + 0x30, + 0x33, + 0x03, + 0xd3, + 0x80, + 0x01, + 0xc3, + 0x92, + 0x0a, + 0x30, + 0x33, + 0x0c, + 0x30, + 0x03, + 0x09, + 0x20, + 0x02, + 0x06, + 0x20, + 0x01, + 0x03, + 0xd3, + 0x80, + 0x01, + 0xc3, + 0x92, + 0x0b, + 0x90, + 0x30, + 0x01, + 0xe0, + 0x44, + 0x40, + 0xf0, + 0xe0, + 0x54, + 0xbf, + 0xf0, + 0xe5, + 0x32, + 0x30, + 0xe1, + 0x14, + 0x30, + 0x34, + 0x11, + 0x90, + 0x30, + 0x22, + 0xe0, + 0xf5, + 0x08, + 0xe4, + 0xf0, + 0x30, + 0x00, + 0x03, + 0xd3, + 0x80, + 0x01, + 0xc3, + 0x92, + 0x08, + 0xe5, + 0x32, + 0x30, + 0xe5, + 0x12, + 0x90, + 0x56, + 0xa1, + 0xe0, + 0xf5, + 0x09, + 0x30, + 0x31, + 0x09, + 0x30, + 0x05, + 0x03, + 0xd3, + 0x80, + 0x01, + 0xc3, + 0x92, + 0x0d, + 0x90, + 0x3f, + 0x0c, + 0xe5, + 0x32, + 0xf0, + 0xd0, + 0xd0, + 0xd0, + 0x82, + 0xd0, + 0x83, + 0xd0, + 0xe0, + 0x32, + 0x90, + 0x0e, + 0x7d, + 0xe4, + 0x93, + 0xfe, + 0x74, + 0x01, + 0x93, + 0xff, + 0xc3, + 0x90, + 0x0e, + 0x7b, + 0x74, + 0x01, + 0x93, + 0x9f, + 0xff, + 0xe4, + 0x93, + 0x9e, + 0xfe, + 0xe4, + 0x8f, + 0x3b, + 0x8e, + 0x3a, + 0xf5, + 0x39, + 0xf5, + 0x38, + 0xab, + 0x3b, + 0xaa, + 0x3a, + 0xa9, + 0x39, + 0xa8, + 0x38, + 0xaf, + 0x49, + 0xfc, + 0xfd, + 0xfe, + 0x12, + 0x02, + 0xfe, + 0x12, + 0x0b, + 0x22, + 0xe4, + 0x7b, + 0xff, + 0xfa, + 0xf9, + 0xf8, + 0x12, + 0x03, + 0x89, + 0x12, + 0x0b, + 0x22, + 0x90, + 0x0e, + 0x69, + 0xe4, + 0x12, + 0x0b, + 0x37, + 0x12, + 0x0b, + 0x22, + 0xe4, + 0x85, + 0x48, + 0x37, + 0xf5, + 0x36, + 0xf5, + 0x35, + 0xf5, + 0x34, + 0xaf, + 0x37, + 0xae, + 0x36, + 0xad, + 0x35, + 0xac, + 0x34, + 0xa3, + 0x12, + 0x0b, + 0x37, + 0x8f, + 0x37, + 0x8e, + 0x36, + 0x8d, + 0x35, + 0x8c, + 0x34, + 0xe5, + 0x3b, + 0x45, + 0x37, + 0xf5, + 0x3b, + 0xe5, + 0x3a, + 0x45, + 0x36, + 0xf5, + 0x3a, + 0xe5, + 0x39, + 0x45, + 0x35, + 0xf5, + 0x39, + 0xe5, + 0x38, + 0x45, + 0x34, + 0xf5, + 0x38, + 0xe4, + 0xf5, + 0x22, + 0xf5, + 0x23, + 0x85, + 0x3b, + 0x31, + 0x85, + 0x3a, + 0x30, + 0x85, + 0x39, + 0x2f, + 0x85, + 0x38, + 0x2e, + 0x02, + 0x0a, + 0xc6, + 0x78, + 0x4f, + 0x7e, + 0x00, + 0xe6, + 0xfc, + 0x08, + 0xe6, + 0xfd, + 0x12, + 0x02, + 0x97, + 0x7c, + 0x00, + 0x22, + 0xe0, + 0xa3, + 0xe0, + 0x75, + 0xf0, + 0x02, + 0xa4, + 0xff, + 0xae, + 0xf0, + 0xc3, + 0x08, + 0xe6, + 0x9f, + 0xf6, + 0x18, + 0xe6, + 0x9e, + 0xf6, + 0x22, + 0xff, + 0xe5, + 0xf0, + 0x34, + 0x60, + 0x8f, + 0x82, + 0xf5, + 0x83, + 0xec, + 0xf0, + 0x22, + 0xe4, + 0xfc, + 0xfd, + 0x12, + 0x04, + 0x5c, + 0x78, + 0x59, + 0xe6, + 0xc3, + 0x13, + 0xfe, + 0x08, + 0xe6, + 0x13, + 0x22, + 0x78, + 0x4f, + 0xe6, + 0xfe, + 0x08, + 0xe6, + 0xff, + 0xe4, + 0xfc, + 0xfd, + 0x22, + 0xe7, + 0xc4, + 0xf8, + 0x54, + 0xf0, + 0xc8, + 0x68, + 0xf7, + 0x09, + 0xe7, + 0xc4, + 0x54, + 0x0f, + 0x48, + 0xf7, + 0x22, + 0xe6, + 0xfc, + 0xed, + 0x75, + 0xf0, + 0x04, + 0xa4, + 0x22, + 0xe0, + 0xfe, + 0xa3, + 0xe0, + 0xfd, + 0xee, + 0xf6, + 0xed, + 0x08, + 0xf6, + 0x22, + 0x13, + 0xff, + 0xc3, + 0xe6, + 0x9f, + 0xff, + 0x18, + 0xe6, + 0x9e, + 0xfe, + 0x22, + 0xe6, + 0xc3, + 0x13, + 0xf7, + 0x08, + 0xe6, + 0x13, + 0x09, + 0xf7, + 0x22, + 0xe4, + 0xf5, + 0x49, + 0x90, + 0x0e, + 0x77, + 0x93, + 0xff, + 0xe4, + 0x8f, + 0x37, + 0xf5, + 0x36, + 0xf5, + 0x35, + 0xf5, + 0x34, + 0xaf, + 0x37, + 0xae, + 0x36, + 0xad, + 0x35, + 0xac, + 0x34, + 0x90, + 0x0e, + 0x6a, + 0x12, + 0x0b, + 0x37, + 0x8f, + 0x37, + 0x8e, + 0x36, + 0x8d, + 0x35, + 0x8c, + 0x34, + 0x90, + 0x0e, + 0x72, + 0x12, + 0x04, + 0x3f, + 0xef, + 0x45, + 0x37, + 0xf5, + 0x37, + 0xee, + 0x45, + 0x36, + 0xf5, + 0x36, + 0xed, + 0x45, + 0x35, + 0xf5, + 0x35, + 0xec, + 0x45, + 0x34, + 0xf5, + 0x34, + 0xe4, + 0xf5, + 0x22, + 0xf5, + 0x23, + 0x85, + 0x37, + 0x31, + 0x85, + 0x36, + 0x30, + 0x85, + 0x35, + 0x2f, + 0x85, + 0x34, + 0x2e, + 0x12, + 0x0a, + 0xc6, + 0xe4, + 0xf5, + 0x22, + 0xf5, + 0x23, + 0x90, + 0x0e, + 0x72, + 0x12, + 0x0b, + 0x2b, + 0x12, + 0x0a, + 0xc6, + 0xe4, + 0xf5, + 0x22, + 0xf5, + 0x23, + 0x90, + 0x0e, + 0x6e, + 0x12, + 0x0b, + 0x2b, + 0x02, + 0x0a, + 0xc6, + 0x75, + 0x89, + 0x03, + 0x75, + 0xa8, + 0x01, + 0x75, + 0xb8, + 0x04, + 0x75, + 0x34, + 0xff, + 0x75, + 0x35, + 0x0e, + 0x75, + 0x36, + 0x15, + 0x75, + 0x37, + 0x0d, + 0x12, + 0x0a, + 0x4a, + 0x12, + 0x00, + 0x09, + 0x12, + 0x0b, + 0x3d, + 0x12, + 0x00, + 0x06, + 0xd2, + 0x00, + 0xd2, + 0x34, + 0xd2, + 0xaf, + 0x75, + 0x34, + 0xff, + 0x75, + 0x35, + 0x0e, + 0x75, + 0x36, + 0x49, + 0x75, + 0x37, + 0x03, + 0x12, + 0x0a, + 0x4a, + 0x30, + 0x08, + 0x09, + 0xc2, + 0x34, + 0x12, + 0x06, + 0x6a, + 0xc2, + 0x08, + 0xd2, + 0x34, + 0x30, + 0x09, + 0x09, + 0xc2, + 0x36, + 0x12, + 0x00, + 0x0e, + 0xc2, + 0x09, + 0xd2, + 0x36, + 0x30, + 0x0e, + 0x03, + 0x12, + 0x04, + 0x9a, + 0x30, + 0x35, + 0xdf, + 0x90, + 0x30, + 0x29, + 0xe5, + 0x1e, + 0xf0, + 0xb4, + 0x10, + 0x05, + 0x90, + 0x30, + 0x23, + 0xe4, + 0xf0, + 0xc2, + 0x35, + 0x80, + 0xcd, + 0xae, + 0x35, + 0xaf, + 0x36, + 0xe4, + 0xfd, + 0xed, + 0xc3, + 0x95, + 0x37, + 0x50, + 0x33, + 0x12, + 0x0b, + 0x87, + 0xe4, + 0x93, + 0xf5, + 0x38, + 0x74, + 0x01, + 0x93, + 0xf5, + 0x39, + 0x45, + 0x38, + 0x60, + 0x23, + 0x85, + 0x39, + 0x82, + 0x85, + 0x38, + 0x83, + 0xe0, + 0xfc, + 0x12, + 0x0b, + 0x87, + 0x74, + 0x03, + 0x93, + 0x52, + 0x04, + 0x12, + 0x0b, + 0x87, + 0x74, + 0x02, + 0x93, + 0x42, + 0x04, + 0x85, + 0x39, + 0x82, + 0x85, + 0x38, + 0x83, + 0xec, + 0xf0, + 0x0d, + 0x80, + 0xc7, + 0x22, + 0x78, + 0xbb, + 0xe6, + 0xd3, + 0x08, + 0xff, + 0xe6, + 0x64, + 0x80, + 0xf8, + 0xef, + 0x64, + 0x80, + 0x98, + 0x22, + 0x93, + 0xff, + 0x7e, + 0x00, + 0xe6, + 0xfc, + 0x08, + 0xe6, + 0xfd, + 0x12, + 0x02, + 0x97, + 0xac, + 0x06, + 0xad, + 0x07, + 0x78, + 0xb3, + 0xe6, + 0xfe, + 0x08, + 0xe6, + 0x78, + 0x03, + 0x22, + 0x78, + 0xba, + 0xd3, + 0xe6, + 0x64, + 0x80, + 0x94, + 0x80, + 0x22, + 0x25, + 0xe0, + 0x24, + 0x0a, + 0xf8, + 0xe6, + 0xfe, + 0x08, + 0xe6, + 0xff, + 0x22, + 0xa2, + 0xaf, + 0x92, + 0x32, + 0xc2, + 0xaf, + 0xe5, + 0x23, + 0x45, + 0x22, + 0x90, + 0x0e, + 0x5d, + 0x60, + 0x0e, + 0x12, + 0x0b, + 0x70, + 0xe0, + 0xf5, + 0x2c, + 0x12, + 0x0b, + 0x6d, + 0xe0, + 0xf5, + 0x2d, + 0x80, + 0x0c, + 0x12, + 0x0b, + 0x70, + 0xe5, + 0x30, + 0xf0, + 0x12, + 0x0b, + 0x6d, + 0xe5, + 0x31, + 0xf0, + 0xa2, + 0x32, + 0x92, + 0xaf, + 0x22, + 0xd2, + 0x01, + 0xc2, + 0x02, + 0xe4, + 0xf5, + 0x1f, + 0xf5, + 0x1e, + 0xd2, + 0x35, + 0xd2, + 0x33, + 0xd2, + 0x36, + 0xd2, + 0x01, + 0xc2, + 0x02, + 0xf5, + 0x1f, + 0xf5, + 0x1e, + 0xd2, + 0x35, + 0xd2, + 0x33, + 0x22, + 0x2d, + 0xfd, + 0xe4, + 0x33, + 0xfc, + 0xe4, + 0x93, + 0xfe, + 0xfb, + 0xd3, + 0xed, + 0x9b, + 0x74, + 0x80, + 0xf8, + 0x6c, + 0x98, + 0x22, + 0x8f, + 0x3b, + 0x8e, + 0x3a, + 0x8d, + 0x39, + 0x8c, + 0x38, + 0x22, + 0x12, + 0x04, + 0x3f, + 0x8f, + 0x31, + 0x8e, + 0x30, + 0x8d, + 0x2f, + 0x8c, + 0x2e, + 0x22, + 0x93, + 0xf9, + 0xf8, + 0x02, + 0x04, + 0x2c, + 0x90, + 0x0e, + 0x81, + 0x12, + 0x04, + 0x3f, + 0x8f, + 0x46, + 0x8e, + 0x45, + 0x8d, + 0x44, + 0x8c, + 0x43, + 0xd2, + 0x06, + 0x30, + 0x06, + 0x03, + 0xd3, + 0x80, + 0x01, + 0xc3, + 0x92, + 0x0e, + 0x22, + 0xc0, + 0xe0, + 0xc0, + 0x83, + 0xc0, + 0x82, + 0x90, + 0x3f, + 0x0d, + 0xe0, + 0xf5, + 0x33, + 0xe5, + 0x33, + 0xf0, + 0xd0, + 0x82, + 0xd0, + 0x83, + 0xd0, + 0xe0, + 0x32, + 0x90, + 0x0e, + 0x5f, + 0xe4, + 0x93, + 0xfe, + 0x74, + 0x01, + 0x93, + 0xf5, + 0x82, + 0x8e, + 0x83, + 0x22, + 0x78, + 0x7f, + 0xe4, + 0xf6, + 0xd8, + 0xfd, + 0x75, + 0x81, + 0xca, + 0x02, + 0x09, + 0xe1, + 0x8f, + 0x82, + 0x8e, + 0x83, + 0x75, + 0xf0, + 0x04, + 0xed, + 0x02, + 0x04, + 0x68, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x07, + 0x21, + 0x15, + 0x29, + 0x13, + 0x4f, + 0x56, + 0x54, + 0x20, + 0x20, + 0x20, + 0x20, + 0x20, + 0x20, + 0x01, + 0x10, + 0x00, + 0x56, + 0x40, + 0x1a, + 0x30, + 0x29, + 0x7e, + 0x00, + 0x30, + 0x04, + 0x20, + 0xdf, + 0x30, + 0x05, + 0x40, + 0xbf, + 0x50, + 0x03, + 0x00, + 0xfd, + 0x50, + 0x27, + 0x01, + 0xfe, + 0x60, + 0x00, + 0x11, + 0x00, + 0x3f, + 0x05, + 0x30, + 0x00, + 0x3f, + 0x06, + 0x22, + 0x00, + 0x3f, + 0x01, + 0x2a, + 0x00, + 0x3f, + 0x02, + 0x00, + 0x00, + 0x36, + 0x06, + 0x07, + 0x00, + 0x3f, + 0x0b, + 0x0f, + 0xf0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x30, + 0x01, + 0x40, + 0xbf, + 0x30, + 0x01, + 0x00, + 0xbf, + 0x30, + 0x29, + 0x70, + 0x00, + 0x3a, + 0x00, + 0x00, + 0xff, + 0x3a, + 0x00, + 0x00, + 0xff, + 0x36, + 0x03, + 0x36, + 0x02, + 0x41, + 0x44, + 0x58, + 0x20, + 0x18, + 0x10, + 0x0a, + 0x04, + 0x04, + 0x00, + 0x03, + 0xff, + 0x64, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x04, + 0x06, + 0x00, + 0x03, + 0x98, + 0x00, + 0xcc, + 0x50, + 0x3c, + 0x28, + 0x1e, + 0x10, + 0x10, + 0x00, + 0x00, + 0x00, + 0x6e, + 0x30, + 0x28, + 0x00, + 0xa5, + 0x5a, + 0x00, +}; + +#endif /* CAMSENSOR_OV5640 */ diff --git a/drivers/media/video/msm/ov5647.c b/drivers/media/video/msm/ov5647.c new file mode 100644 index 0000000000000000000000000000000000000000..2a6e7be0f58dd4d24ebe7f189e963ac477786aad --- /dev/null +++ b/drivers/media/video/msm/ov5647.c @@ -0,0 +1,1201 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "ov5647.h" + +/* 16bit address - 8 bit context register structure */ +#define Q8 0x00000100 +#define Q10 0x00000400 + +#define REG_OV5647_GAIN_MSB 0x350A +#define REG_OV5647_GAIN_LSB 0x350B +#define REG_OV5647_LINE_HSB 0x3500 +#define REG_OV5647_LINE_MSB 0x3501 +#define REG_OV5647_LINE_LSB 0x3502 + +/* MCLK */ +#define OV5647_MASTER_CLK_RATE 24000000 + +/* AF Total steps parameters */ +#define OV5647_TOTAL_STEPS_NEAR_TO_FAR 32 + +#define OV5647_REG_PREV_FRAME_LEN_1 31 +#define OV5647_REG_PREV_FRAME_LEN_2 32 +#define OV5647_REG_PREV_LINE_LEN_1 33 +#define OV5647_REG_PREV_LINE_LEN_2 34 + +#define OV5647_REG_SNAP_FRAME_LEN_1 15 +#define OV5647_REG_SNAP_FRAME_LEN_2 16 +#define OV5647_REG_SNAP_LINE_LEN_1 17 +#define OV5647_REG_SNAP_LINE_LEN_2 18 +#define MSB 1 +#define LSB 0 + +/* Debug switch */ +#ifdef CDBG +#undef CDBG +#endif +#ifdef CDBG_HIGH +#undef CDBG_HIGH +#endif + +/*#define OV5647_VERBOSE_DGB*/ + +#ifdef OV5647_VERBOSE_DGB +#define CDBG(fmt, args...) pr_debug(fmt, ##args) +#define CDBG_HIGH(fmt, args...) pr_debug(fmt, ##args) +#else +#define CDBG(fmt, args...) do { } while (0) +#define CDBG_HIGH(fmt, args...) pr_debug(fmt, ##args) +#endif + +/*for debug*/ +#ifdef CDBG +#undef CDBG +#endif +#define CDBG(fmt, args...) printk(fmt, ##args) + +static uint8_t mode_mask = 0x09; +struct ov5647_work_t { + struct work_struct work; +}; + +static struct ov5647_work_t *ov5647_sensorw; +static struct ov5647_work_t *ov5647_af_sensorw; +static struct i2c_client *ov5647_af_client; +static struct i2c_client *ov5647_client; + +struct ov5647_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 ov5647_resolution_t prev_res; + enum ov5647_resolution_t pict_res; + enum ov5647_resolution_t curr_res; + enum ov5647_test_mode_t set_test; +}; + +static bool CSI_CONFIG; +static struct ov5647_ctrl_t *ov5647_ctrl; + +static DECLARE_WAIT_QUEUE_HEAD(ov5647_wait_queue); +static DECLARE_WAIT_QUEUE_HEAD(ov5647_af_wait_queue); +DEFINE_MUTEX(ov5647_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 ov5647_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 = 1, + .buf = rxdata, + }, + }; + if (i2c_transfer(ov5647_client->adapter, msgs, 2) < 0) { + CDBG("ov5647_i2c_rxdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +static int32_t ov5647_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(ov5647_client->adapter, msg, 1) < 0) { + CDBG("ov5647_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t ov5647_i2c_read(unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc = 0; + unsigned char buf[2]; + + if (!rdata) + return -EIO; + CDBG("%s:saddr:0x%x raddr:0x%x data:0x%x", + __func__, ov5647_client->addr, raddr, *rdata); + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = ov5647_i2c_rxdata(ov5647_client->addr >> 1, buf, 1); + if (rc < 0) { + CDBG("ov5647_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = buf[0]; + CDBG("ov5647_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + + return rc; +} + +static int32_t ov5647_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 = ov5647_i2c_txdata(ov5647_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 ov5647_i2c_write_b_table(struct ov5647_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num; i++) { + rc = ov5647_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static int32_t ov5647_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(ov5647_af_client->adapter, msg, 1) < 0) { + pr_err("ov5647_af_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t ov5647_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 = ov5647_af_i2c_txdata(ov5647_af_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 void ov5647_start_stream(void) +{ + CDBG("CAMERA_DBG: 0x4202 0x0, stream on...\r\n"); + ov5647_i2c_write_b_sensor(0x4202, 0x00);/* streaming on */ +} + +static void ov5647_stop_stream(void) +{ + CDBG("CAMERA_DBG: 0x4202 0xf, stream off...\r\n"); + ov5647_i2c_write_b_sensor(0x4202, 0x0f);/* streaming off */ +} + +static void ov5647_group_hold_on(void) +{ + ov5647_i2c_write_b_sensor(0x0104, 0x01); +} + +static void ov5647_group_hold_off(void) +{ + ov5647_i2c_write_b_sensor(0x0104, 0x0); +} + +static void ov5647_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider, d1, d2; + uint32_t preview_pclk = 0x37, snapshot_pclk = 0x4f; + + d1 = (prev_frame_length_lines * 0x00000400) / snap_frame_length_lines; + d2 = (prev_line_length_pck * 0x00000400) / snap_line_length_pck; + divider = (d1 * d2*preview_pclk/snapshot_pclk) / 0x400; + CDBG(KERN_ERR "ov5647_get_pict_fps divider = %d", divider); + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); +} + +static uint16_t ov5647_get_prev_lines_pf(void) +{ + if (ov5647_ctrl->prev_res == QTR_SIZE) + return prev_frame_length_lines; + else + return snap_frame_length_lines; +} + +static uint16_t ov5647_get_prev_pixels_pl(void) +{ + if (ov5647_ctrl->prev_res == QTR_SIZE) + return prev_line_length_pck; + else + return snap_line_length_pck; +} + +static uint16_t ov5647_get_pict_lines_pf(void) +{ + if (ov5647_ctrl->pict_res == QTR_SIZE) + return prev_frame_length_lines; + else + return snap_frame_length_lines; +} + +static uint16_t ov5647_get_pict_pixels_pl(void) +{ + if (ov5647_ctrl->pict_res == QTR_SIZE) + return prev_line_length_pck; + else + return snap_line_length_pck; +} + +static uint32_t ov5647_get_pict_max_exp_lc(void) +{ + return snap_frame_length_lines * 24; +} + +static int32_t ov5647_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + + ov5647_ctrl->fps_divider = fps->fps_div; + ov5647_ctrl->pict_fps_divider = fps->pict_fps_div; + + if (ov5647_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + total_lines_per_frame = (uint16_t) + ((prev_frame_length_lines * ov5647_ctrl->fps_divider) / 0x400); + } else { + total_lines_per_frame = (uint16_t) + ((snap_frame_length_lines * ov5647_ctrl->fps_divider) / 0x400); + } + + ov5647_group_hold_on(); + rc = ov5647_i2c_write_b_sensor(0x0340, + ((total_lines_per_frame & 0xFF00) >> 8)); + rc = ov5647_i2c_write_b_sensor(0x0341, + (total_lines_per_frame & 0x00FF)); + ov5647_group_hold_off(); + + return rc; +} + +static inline uint8_t ov5647_byte(uint16_t word, uint8_t offset) +{ + return word >> (offset * BITS_PER_BYTE); +} + +static int32_t ov5647_write_exp_gain(uint16_t gain, uint32_t line) +{ + int rc = 0; + uint16_t max_line; + u8 intg_time_hsb, intg_time_msb, intg_time_lsb; + uint8_t gain_lsb, gain_hsb; + ov5647_ctrl->my_reg_gain = gain; + ov5647_ctrl->my_reg_line_count = (uint16_t)line; + + CDBG(KERN_ERR "preview exposure setting 0x%x, 0x%x, %d", + gain, line, line); + + gain_lsb = (uint8_t) (ov5647_ctrl->my_reg_gain); + gain_hsb = (uint8_t)((ov5647_ctrl->my_reg_gain & 0x300)>>8); + /* adjust frame rate */ + if (line > 980) { + rc = ov5647_i2c_write_b_sensor(0x380E, + (uint8_t)((line+4) >> 8)) ; + rc = ov5647_i2c_write_b_sensor(0x380F, + (uint8_t)((line+4) & 0x00FF)) ; + max_line = line + 4; + } else if (max_line > 984) { + rc = ov5647_i2c_write_b_sensor(0x380E, + (uint8_t)(984 >> 8)) ; + rc = ov5647_i2c_write_b_sensor(0x380F, + (uint8_t)(984 & 0x00FF)) ; + max_line = 984; + } + + line = line<<4; + /* ov5647 need this operation */ + intg_time_hsb = (u8)(line>>16); + intg_time_msb = (u8) ((line & 0xFF00) >> 8); + intg_time_lsb = (u8) (line & 0x00FF); + + ov5647_group_hold_on(); + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_HSB, intg_time_hsb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_MSB, intg_time_msb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_LSB, intg_time_lsb) ; + + rc = ov5647_i2c_write_b_sensor(REG_OV5647_GAIN_MSB, gain_hsb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_GAIN_LSB, gain_lsb) ; + ov5647_group_hold_off(); + + return rc; +} + + +static int32_t ov5647_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_line; + int rc = 0; + uint8_t gain_lsb, gain_hsb; + u8 intg_time_hsb, intg_time_msb, intg_time_lsb; + + ov5647_ctrl->my_reg_gain = gain; + ov5647_ctrl->my_reg_line_count = (uint16_t)line; + + gain_lsb = (uint8_t) (ov5647_ctrl->my_reg_gain); + gain_hsb = (uint8_t)((ov5647_ctrl->my_reg_gain & 0x300)>>8); + + CDBG(KERN_ERR "snapshot exposure seting 0x%x, 0x%x, %d" + , gain, line, line); + + if (line > 1964) { + rc = ov5647_i2c_write_b_sensor(0x380E, + (uint8_t)((line+4) >> 8)) ; + rc = ov5647_i2c_write_b_sensor(0x380F, + (uint8_t)((line+4) & 0x00FF)) ; + max_line = line + 4; + } else if (max_line > 1968) { + rc = ov5647_i2c_write_b_sensor(0x380E, + (uint8_t)(1968 >> 8)) ; + rc = ov5647_i2c_write_b_sensor(0x380F, + (uint8_t)(1968 & 0x00FF)) ; + max_line = 1968; + } + line = line<<4; + /* ov5647 need this operation */ + intg_time_hsb = (u8)(line>>16); + intg_time_msb = (u8) ((line & 0xFF00) >> 8); + intg_time_lsb = (u8) (line & 0x00FF); + + /* FIXME for BLC trigger */ + ov5647_group_hold_on(); + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_HSB, intg_time_hsb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_MSB, intg_time_msb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_LSB, intg_time_lsb) ; + + rc = ov5647_i2c_write_b_sensor(REG_OV5647_GAIN_MSB, gain_hsb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_GAIN_LSB, gain_lsb - 1) ; + + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_HSB, intg_time_hsb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_MSB, intg_time_msb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_LINE_LSB, intg_time_lsb) ; + + rc = ov5647_i2c_write_b_sensor(REG_OV5647_GAIN_MSB, gain_hsb) ; + rc = ov5647_i2c_write_b_sensor(REG_OV5647_GAIN_LSB, gain_lsb) ; + ov5647_group_hold_off(); + + msleep(500); + return rc; + +} + +static int32_t ov5647_move_focus(int direction, int32_t num_steps) +{ + uint8_t code_val_msb = 0; + uint8_t code_val_lsb = 0; + int16_t step_direction, actual_step, next_position; + int rc; + + if (num_steps == 0) + return 0; + + if (direction == MOVE_NEAR) + step_direction = 20; + else if (direction == MOVE_FAR) + step_direction = -20; + else + return -EINVAL; + + actual_step = (int16_t)(step_direction * num_steps); + next_position = (int16_t)ov5647_ctrl->curr_lens_pos + actual_step; + if (next_position < 0) { + CDBG(KERN_ERR "%s: OV5647 position(=%d) out of range", + __func__, next_position); + next_position = 0; + } + if (next_position > 0x3FF) { + CDBG(KERN_ERR "%s: OV5647 position(=%d) out of range", + __func__, next_position); + next_position = 0x3FF; + } + ov5647_ctrl->curr_lens_pos = next_position; + + code_val_msb = (uint8_t)((ov5647_ctrl->curr_lens_pos & 0x03FF) >> 4); + code_val_lsb = (uint8_t)((ov5647_ctrl->curr_lens_pos & 0x000F) << 4); + code_val_lsb |= mode_mask; + + rc = ov5647_af_i2c_write_b_sensor(code_val_msb, code_val_lsb); + /* DAC Setting */ + if (rc != 0) { + CDBG(KERN_ERR "%s: WRITE ERROR lsb = 0x%x, msb = 0x%x", + __func__, code_val_lsb, code_val_msb); + } else { + CDBG(KERN_ERR "%s: Successful lsb = 0x%x, msb = 0x%x", + __func__, code_val_lsb, code_val_msb); + /* delay may set based on the steps moved + when I2C write successful */ + msleep(100); + } + return 0; +} + +static int32_t ov5647_set_default_focus(uint8_t af_step) +{ + uint8_t code_val_msb = 0; + uint8_t code_val_lsb = 0; + int rc = 0; + + ov5647_ctrl->curr_lens_pos = 200; + + + code_val_msb = (ov5647_ctrl->curr_lens_pos & 0x03FF) >> 4; + code_val_lsb = (ov5647_ctrl->curr_lens_pos & 0x000F) << 4; + code_val_lsb |= mode_mask; + + CDBG(KERN_ERR "ov5647_set_default_focus:lens pos = %d", + ov5647_ctrl->curr_lens_pos); + rc = ov5647_af_i2c_write_b_sensor(code_val_msb, code_val_lsb); + /* DAC Setting */ + if (rc != 0) + CDBG(KERN_ERR "%s: WRITE ERROR lsb = 0x%x, msb = 0x%x", + __func__, code_val_lsb, code_val_msb); + else + CDBG(KERN_ERR "%s: WRITE successful lsb = 0x%x, msb = 0x%x", + __func__, code_val_lsb, code_val_msb); + + usleep_range(10000, 11000); + return 0; +} + +static int32_t ov5647_test(enum ov5647_test_mode_t mo) +{ + int32_t rc = 0; + + if (mo != TEST_OFF) + rc = ov5647_i2c_write_b_sensor(0x0601, (uint8_t) mo); + + return rc; +} + +static void ov5647_reset_sensor(void) +{ + ov5647_i2c_write_b_sensor(0x103, 0x1); +} + + +static int32_t ov5647_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + struct msm_camera_csi_params ov5647_csi_params; + + ov5647_stop_stream(); + + /* wait for clk/data really stop */ + if ((rt == RES_CAPTURE) || (CSI_CONFIG == 0)) + msleep(66); + else + msleep(266); + + CDBG("CAMERA_DBG1: 0x4800 regVal:0x25\r\n"); + ov5647_i2c_write_b_sensor(0x4800, 0x25);/* streaming off */ + + usleep_range(10000, 11000); + + if (update_type == REG_INIT) { + ov5647_reset_sensor(); + ov5647_i2c_write_b_table(ov5647_regs.rec_settings, + ov5647_regs.rec_size); + CSI_CONFIG = 0; + } else if (update_type == UPDATE_PERIODIC) { + /* turn off flash when preview */ + + if (rt == RES_PREVIEW) { + ov5647_i2c_write_b_table(ov5647_regs.reg_prev, + ov5647_regs.reg_prev_size); + CDBG("CAMERA_DBG:preview settings...\r\n"); + } else { + ov5647_i2c_write_b_table(ov5647_regs.reg_snap, + ov5647_regs.reg_snap_size); + CDBG("CAMERA_DBG:snapshot settings...\r\n"); + } + + msleep(20); + if (!CSI_CONFIG) { + msm_camio_vfe_clk_rate_set(192000000); + ov5647_csi_params.data_format = CSI_8BIT; + ov5647_csi_params.lane_cnt = 2; + ov5647_csi_params.lane_assign = 0xe4; + ov5647_csi_params.dpcm_scheme = 0; + ov5647_csi_params.settle_cnt = 10; + rc = msm_camio_csi_config(&ov5647_csi_params); + msleep(20); + CSI_CONFIG = 1; + /* exit powerdown state */ + ov5647_i2c_write_b_sensor(0x0100, 0x01); + } + CDBG("CAMERA_DBG: 0x4800 regVal:0x04\r\n"); + /* streaming on */ + ov5647_i2c_write_b_sensor(0x4800, 0x04); + msleep(266); + ov5647_start_stream(); + msleep(30); + } + return rc; +} + +static int32_t ov5647_video_config(int mode) +{ + int32_t rc = 0; + int rt; + CDBG("video config\n"); + /* change sensor resolution if needed */ + if (ov5647_ctrl->prev_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + if (ov5647_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + if (ov5647_ctrl->set_test) { + if (ov5647_test(ov5647_ctrl->set_test) < 0) + return rc; + } + + ov5647_ctrl->curr_res = ov5647_ctrl->prev_res; + ov5647_ctrl->sensormode = mode; + return rc; +} + +static int32_t ov5647_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + + /*change sensor resolution if needed */ + if (ov5647_ctrl->curr_res != ov5647_ctrl->pict_res) { + if (ov5647_ctrl->pict_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + if (ov5647_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + ov5647_ctrl->curr_res = ov5647_ctrl->pict_res; + ov5647_ctrl->sensormode = mode; + return rc; +} + +static int32_t ov5647_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + + /* change sensor resolution if needed */ + if (ov5647_ctrl->curr_res != ov5647_ctrl->pict_res) { + if (ov5647_ctrl->pict_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + if (ov5647_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + ov5647_ctrl->curr_res = ov5647_ctrl->pict_res; + ov5647_ctrl->sensormode = mode; + return rc; +} + +static int32_t ov5647_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = ov5647_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = ov5647_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = ov5647_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int32_t ov5647_power_down(void) +{ + ov5647_stop_stream(); + return 0; +} + +static int ov5647_probe_init_done(const struct msm_camera_sensor_info *data) +{ + CDBG("probe done\n"); + gpio_direction_output(data->sensor_pwd, 1); + return 0; +} + +static int ov5647_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + uint16_t regaddress1 = 0x300a; + uint16_t regaddress2 = 0x300b; + uint16_t chipid1 = 0; + uint16_t chipid2 = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + + gpio_direction_output(data->sensor_pwd, 0); + usleep_range(4000, 4100); + gpio_direction_output(data->sensor_reset, 1); + usleep_range(2000, 2100); + + ov5647_i2c_read(regaddress1, &chipid1); + if (chipid1 != 0x56) { + rc = -ENODEV; + pr_err("ov5647_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + + ov5647_i2c_read(regaddress2, &chipid2); + if (chipid2 != 0x47) { + rc = -ENODEV; + pr_err("ov5647_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + + pr_err("ID1: 0x%x\n", chipid1); + pr_err("ID2: 0x%x\n", chipid2); + goto init_probe_done; + +init_probe_fail: + pr_err(" ov5647_probe_init_sensor fails\n"); + ov5647_probe_init_done(data); + return rc; +init_probe_done: + pr_debug(" ov5647_probe_init_sensor finishes\n"); + gpio_direction_output(data->sensor_pwd, 1); + return rc; +} + + +static int ov5647_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling ov5647_sensor_open_init\n"); + + ov5647_ctrl = kzalloc(sizeof(struct ov5647_ctrl_t), GFP_KERNEL); + if (!ov5647_ctrl) { + CDBG("ov5647_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + ov5647_ctrl->fps_divider = 1 * 0x00000400; + ov5647_ctrl->pict_fps_divider = 1 * 0x00000400; + ov5647_ctrl->set_test = TEST_OFF; + ov5647_ctrl->prev_res = QTR_SIZE; + ov5647_ctrl->pict_res = FULL_SIZE; + + if (data) + ov5647_ctrl->sensordata = data; + + prev_frame_length_lines = 0x3d8; + + prev_line_length_pck = 0x768*2; + + snap_frame_length_lines = 0x7b0; + + snap_line_length_pck = 0xa8c; + + /* enable mclk first */ + msm_camio_clk_rate_set(OV5647_MASTER_CLK_RATE); + + gpio_direction_output(data->sensor_pwd, 1); + gpio_direction_output(data->sensor_reset, 0); + usleep_range(10000, 11000); + /* power on camera ldo and vreg */ + if (ov5647_ctrl->sensordata->pmic_gpio_enable) + lcd_camera_power_onoff(1); + usleep_range(10000, 11000); /*waiting for ldo stable*/ + gpio_direction_output(data->sensor_pwd, 0); + msleep(20); + gpio_direction_output(data->sensor_reset, 1); + msleep(25); + + CDBG("init settings\n"); + if (ov5647_ctrl->prev_res == QTR_SIZE) + rc = ov5647_sensor_setting(REG_INIT, RES_PREVIEW); + else + rc = ov5647_sensor_setting(REG_INIT, RES_CAPTURE); + ov5647_ctrl->fps = 30 * Q8; + + /* enable AF actuator */ + if (ov5647_ctrl->sensordata->vcm_enable) { + CDBG("enable AF actuator, gpio = %d\n", + ov5647_ctrl->sensordata->vcm_pwd); + rc = gpio_request(ov5647_ctrl->sensordata->vcm_pwd, + "ov5647_af"); + if (!rc) + gpio_direction_output( + ov5647_ctrl->sensordata->vcm_pwd, + 1); + else { + pr_err("ov5647_ctrl gpio request failed!\n"); + goto init_fail; + } + msleep(20); + rc = ov5647_set_default_focus(0); + if (rc < 0) { + gpio_direction_output(ov5647_ctrl->sensordata->vcm_pwd, + 0); + gpio_free(ov5647_ctrl->sensordata->vcm_pwd); + } + } + if (rc < 0) + goto init_fail; + else + goto init_done; +init_fail: + CDBG("init_fail\n"); + ov5647_probe_init_done(data); + /* No need to power OFF camera ldo and vreg + affects Display while resume */ +init_done: + CDBG("init_done\n"); + return rc; +} + +static int ov5647_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static int ov5647_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&ov5647_wait_queue); + return 0; +} + +static int ov5647_af_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&ov5647_af_wait_queue); + return 0; +} + +static const struct i2c_device_id ov5647_af_i2c_id[] = { + {"ov5647_af", 0}, + { } +}; + +static int ov5647_af_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("ov5647_af_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + ov5647_af_sensorw = kzalloc(sizeof(struct ov5647_work_t), GFP_KERNEL); + if (!ov5647_af_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, ov5647_af_sensorw); + ov5647_af_init_client(client); + ov5647_af_client = client; + + msleep(50); + + CDBG("ov5647_af_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("ov5647_af_probe failed! rc = %d\n", rc); + return rc; +} + +static const struct i2c_device_id ov5647_i2c_id[] = { + {"ov5647", 0}, {} +}; + +static int ov5647_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("ov5647_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + ov5647_sensorw = kzalloc(sizeof(struct ov5647_work_t), GFP_KERNEL); + if (!ov5647_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, ov5647_sensorw); + ov5647_init_client(client); + ov5647_client = client; + + msleep(50); + + CDBG("ov5647_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("ov5647_probe failed! rc = %d\n", rc); + return rc; +} + +static int __devexit ov5647_remove(struct i2c_client *client) +{ + struct ov5647_work_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + ov5647_client = NULL; + kfree(sensorw); + return 0; +} + +static int __devexit ov5647_af_remove(struct i2c_client *client) +{ + struct ov5647_work_t *ov5647_af = i2c_get_clientdata(client); + free_irq(client->irq, ov5647_af); + ov5647_af_client = NULL; + kfree(ov5647_af); + return 0; +} + +static struct i2c_driver ov5647_i2c_driver = { + .id_table = ov5647_i2c_id, + .probe = ov5647_i2c_probe, + .remove = ov5647_i2c_remove, + .driver = { + .name = "ov5647", + }, +}; + +static struct i2c_driver ov5647_af_i2c_driver = { + .id_table = ov5647_af_i2c_id, + .probe = ov5647_af_i2c_probe, + .remove = __exit_p(ov5647_af_i2c_remove), + .driver = { + .name = "ov5647_af", + }, +}; + +int ov5647_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(&ov5647_mut); + CDBG("ov5647_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + ov5647_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 = + ov5647_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 = + ov5647_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 = + ov5647_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 = + ov5647_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 = + ov5647_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 = ov5647_set_fps(&(cdata.cfg.fps)); + break; + case CFG_SET_EXP_GAIN: + rc = ov5647_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = ov5647_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_MODE: + rc = ov5647_set_sensor_mode(cdata.mode, cdata.rs); + break; + case CFG_PWR_DOWN: + rc = ov5647_power_down(); + break; + case CFG_MOVE_FOCUS: + rc = ov5647_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + case CFG_SET_DEFAULT_FOCUS: + rc = ov5647_set_default_focus(cdata.cfg.focus.steps); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = OV5647_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 = ov5647_set_default_focus(cdata.cfg.effect); + break; + default: + rc = -EFAULT; + break; + } + mutex_unlock(&ov5647_mut); + + return rc; +} + +static int ov5647_sensor_release(void) +{ + int rc = -EBADF; + unsigned short rdata; + + mutex_lock(&ov5647_mut); + ov5647_power_down(); + msleep(20); + ov5647_i2c_read(0x3018, &rdata); + rdata |= 0x18; /*set bit 3 bit 4 to 1*/ + ov5647_i2c_write_b_sensor(0x3018, rdata);/*write back*/ + msleep(20); + + gpio_set_value(ov5647_ctrl->sensordata->sensor_pwd, 1); + usleep_range(5000, 5100); + if (ov5647_ctrl->sensordata->vcm_enable) { + gpio_direction_output(ov5647_ctrl->sensordata->vcm_pwd, 0); + gpio_free(ov5647_ctrl->sensordata->vcm_pwd); + } + + /* No need to power OFF camera ldo and vreg + affects Display while resume */ + + kfree(ov5647_ctrl); + ov5647_ctrl = NULL; + CDBG("ov5647_release completed\n"); + mutex_unlock(&ov5647_mut); + + return rc; +} + +static int ov5647_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + + CDBG("%s E\n", __func__); + + gpio_direction_output(info->sensor_pwd, 1); + gpio_direction_output(info->sensor_reset, 0); + usleep_range(1000, 1100); + /* turn on ldo and vreg */ + if (info->pmic_gpio_enable) + lcd_camera_power_onoff(1); + + rc = i2c_add_driver(&ov5647_i2c_driver); + if (rc < 0 || ov5647_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver ov5647 failed"); + goto probe_fail_2; + } + if (info->vcm_enable) { + rc = i2c_add_driver(&ov5647_af_i2c_driver); + if (rc < 0 || ov5647_af_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver ov5647 af failed"); + goto probe_fail_3; + } + } + msm_camio_clk_rate_set(OV5647_MASTER_CLK_RATE); + + rc = ov5647_probe_init_sensor(info); + if (rc < 0) + goto probe_fail_1; + + s->s_init = ov5647_sensor_open_init; + s->s_release = ov5647_sensor_release; + s->s_config = ov5647_sensor_config; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + gpio_set_value(info->sensor_pwd, 1); + ov5647_probe_init_done(info); + /* turn off ldo and vreg */ + if (info->pmic_gpio_enable) + lcd_camera_power_onoff(0); + + CDBG("%s X", __func__); + return rc; + +probe_fail_3: + i2c_del_driver(&ov5647_af_i2c_driver); +probe_fail_2: + i2c_del_driver(&ov5647_i2c_driver); +probe_fail_1: + /* turn off ldo and vreg */ + if (info->pmic_gpio_enable) + lcd_camera_power_onoff(0); + CDBG("ov5647_sensor_probe: SENSOR PROBE FAILS!\n"); + CDBG("%s X", __func__); + return rc; +} + +static int __devinit ov5647_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, ov5647_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = ov5647_probe, + .driver = { + .name = "msm_camera_ov5647", + .owner = THIS_MODULE, + }, +}; + +static int __init ov5647_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(ov5647_init); +MODULE_DESCRIPTION("Omnivision 5 MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/ov5647.h b/drivers/media/video/msm/ov5647.h new file mode 100644 index 0000000000000000000000000000000000000000..b43f15c9d23e792a1fa614e24f6d863bf7754382 --- /dev/null +++ b/drivers/media/video/msm/ov5647.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef OV5647_H +#define OV5647_H +#include +#include + +extern struct ov5647_reg ov5647_regs; +extern int lcd_camera_power_onoff(int on); +extern struct rw_semaphore leds_list_lock; +extern struct list_head leds_list; + +struct ov5647_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +enum ov5647_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum ov5647_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; +enum ov5647_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum ov5647_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 ov5647_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 ov5647_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 ov5647_reg { + const struct ov5647_i2c_reg_conf *rec_settings; + const unsigned short rec_size; + const struct ov5647_i2c_reg_conf *reg_prev; + const unsigned short reg_prev_size; + const struct ov5647_i2c_reg_conf *reg_snap; + const unsigned short reg_snap_size; +}; +#endif /* OV5647_H */ diff --git a/drivers/media/video/msm/ov5647_reg.c b/drivers/media/video/msm/ov5647_reg.c new file mode 100644 index 0000000000000000000000000000000000000000..4a0fed404ade3d6dab218bb38d704938535b25a3 --- /dev/null +++ b/drivers/media/video/msm/ov5647_reg.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include "ov5647.h" +struct ov5647_i2c_reg_conf ov5647_prev_settings[] = { + /*1280*960 Reference Setting 24M MCLK 2lane 280Mbps/lane 30fps + for back to preview*/ + {0x3035, 0x21}, + {0x3036, 0x37}, + {0x3821, 0x07}, + {0x3820, 0x41}, + {0x3612, 0x09}, + {0x3618, 0x00}, + {0x380c, 0x07}, + {0x380d, 0x68}, + {0x380e, 0x03}, + {0x380f, 0xd8}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3709, 0x52}, + {0x3808, 0x05}, + {0x3809, 0x00}, + {0x380a, 0x03}, + {0x380b, 0xc0}, + {0x3800, 0x00}, + {0x3801, 0x18}, + {0x3802, 0x00}, + {0x3803, 0x0e}, + {0x3804, 0x0a}, + {0x3805, 0x27}, + {0x3806, 0x07}, + {0x3807, 0x95}, + {0x4004, 0x02}, +}; + +struct ov5647_i2c_reg_conf ov5647_snap_settings[] = { + /*2608*1952 Reference Setting 24M MCLK 2lane 280Mbps/lane 30fps*/ + {0x3035, 0x21}, + {0x3036, 0x4f}, + {0x3821, 0x06}, + {0x3820, 0x00}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x380c, 0x0a}, + {0x380d, 0x8c}, + {0x380e, 0x07}, + {0x380f, 0xb0}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3709, 0x12}, + {0x3808, 0x0a}, + {0x3809, 0x30}, + {0x380a, 0x07}, + {0x380b, 0xa0}, + {0x3800, 0x00}, + {0x3801, 0x04}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3b}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x4004, 0x04}, +}; + +struct ov5647_i2c_reg_conf ov5647_recommend_settings[] = { + {0x3035, 0x11}, + {0x303c, 0x11}, + {0x370c, 0x03}, + {0x5000, 0x06}, + {0x5003, 0x08}, + {0x5a00, 0x08}, + {0x3000, 0xff}, + {0x3001, 0xff}, + {0x3002, 0xff}, + {0x301d, 0xf0}, + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + {0x3c01, 0x80}, + {0x3b07, 0x0c}, + {0x3708, 0x64}, + {0x3630, 0x2e}, + {0x3632, 0xe2}, + {0x3633, 0x23}, + {0x3634, 0x44}, + {0x3620, 0x64}, + {0x3621, 0xe0}, + {0x3600, 0x37}, + {0x3704, 0xa0}, + {0x3703, 0x5a}, + {0x3715, 0x78}, + {0x3717, 0x01}, + {0x3731, 0x02}, + {0x370b, 0x60}, + {0x3705, 0x1a}, + {0x3f05, 0x02}, + {0x3f06, 0x10}, + {0x3f01, 0x0a}, + {0x3a08, 0x01}, + {0x3a0f, 0x58}, + {0x3a10, 0x50}, + {0x3a1b, 0x58}, + {0x3a1e, 0x50}, + {0x3a11, 0x60}, + {0x3a1f, 0x28}, + {0x4001, 0x02}, + {0x4000, 0x09}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3017, 0xe0}, + {0x301c, 0xfc}, + {0x3636, 0x06}, + {0x3016, 0x08}, + {0x3827, 0xec}, + {0x3018, 0x44}, + {0x3035, 0x21}, + {0x3106, 0xf5}, + {0x3034, 0x18}, + {0x301c, 0xf8}, + /*lens setting*/ + {0x5000, 0x86}, + {0x5800, 0x11}, + {0x5801, 0x0c}, + {0x5802, 0x0a}, + {0x5803, 0x0b}, + {0x5804, 0x0d}, + {0x5805, 0x13}, + {0x5806, 0x09}, + {0x5807, 0x05}, + {0x5808, 0x03}, + {0x5809, 0x03}, + {0x580a, 0x06}, + {0x580b, 0x08}, + {0x580c, 0x05}, + {0x580d, 0x01}, + {0x580e, 0x00}, + {0x580f, 0x00}, + {0x5810, 0x02}, + {0x5811, 0x06}, + {0x5812, 0x05}, + {0x5813, 0x01}, + {0x5814, 0x00}, + {0x5815, 0x00}, + {0x5816, 0x02}, + {0x5817, 0x06}, + {0x5818, 0x09}, + {0x5819, 0x05}, + {0x581a, 0x04}, + {0x581b, 0x04}, + {0x581c, 0x06}, + {0x581d, 0x09}, + {0x581e, 0x11}, + {0x581f, 0x0c}, + {0x5820, 0x0b}, + {0x5821, 0x0b}, + {0x5822, 0x0d}, + {0x5823, 0x13}, + {0x5824, 0x22}, + {0x5825, 0x26}, + {0x5826, 0x26}, + {0x5827, 0x24}, + {0x5828, 0x24}, + {0x5829, 0x24}, + {0x582a, 0x22}, + {0x582b, 0x20}, + {0x582c, 0x22}, + {0x582d, 0x26}, + {0x582e, 0x22}, + {0x582f, 0x22}, + {0x5830, 0x42}, + {0x5831, 0x22}, + {0x5832, 0x02}, + {0x5833, 0x24}, + {0x5834, 0x22}, + {0x5835, 0x22}, + {0x5836, 0x22}, + {0x5837, 0x26}, + {0x5838, 0x42}, + {0x5839, 0x26}, + {0x583a, 0x06}, + {0x583b, 0x26}, + {0x583c, 0x24}, + {0x583d, 0xce}, + /* manual AWB,manual AE,close Lenc,open WBC*/ + {0x3503, 0x03}, /*manual AE*/ + {0x3501, 0x10}, + {0x3502, 0x80}, + {0x350a, 0x00}, + {0x350b, 0x7f}, + {0x5001, 0x01}, /*manual AWB*/ + {0x5180, 0x08}, + {0x5186, 0x04}, + {0x5187, 0x00}, + {0x5188, 0x04}, + {0x5189, 0x00}, + {0x518a, 0x04}, + {0x518b, 0x00}, + {0x5000, 0x06}, /*No lenc,WBC on*/ +}; + +struct ov5647_reg ov5647_regs = { + .rec_settings = &ov5647_recommend_settings[0], + .rec_size = ARRAY_SIZE(ov5647_recommend_settings), + .reg_prev = &ov5647_prev_settings[0], + .reg_prev_size = ARRAY_SIZE(ov5647_prev_settings), + .reg_snap = &ov5647_snap_settings[0], + .reg_snap_size = ARRAY_SIZE(ov5647_snap_settings), +}; diff --git a/drivers/media/video/msm/ov7692.c b/drivers/media/video/msm/ov7692.c new file mode 100644 index 0000000000000000000000000000000000000000..7696b44ba2237ef77c08bb52b5cfe0d4642b9882 --- /dev/null +++ b/drivers/media/video/msm/ov7692.c @@ -0,0 +1,597 @@ +/* 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; + case CFG_SET_EFFECT: + 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 0000000000000000000000000000000000000000..fc9cf1c2df58b8ca5f637bebf757d48ab081ca97 --- /dev/null +++ b/drivers/media/video/msm/ov7692.h @@ -0,0 +1,666 @@ +/* Copyright (c) 2010, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#define INVMASK(v) (0xff-v) +#define OV7692Core_WritePREG(pTbl) OV7692_WritePRegs \ + (pTbl, sizeof(pTbl)/sizeof(pTbl[0])) + +extern int lcd_camera_power_onoff(int on); +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 +}; + +/*OV SENSOR SCCB*/ +struct OV7692_WREG { + uint8_t addr; + uint8_t data; + uint8_t mask; +} OV7692_WREG; + +#ifdef CONFIG_WEBCAM_OV7692_QRD +/* 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},*/ + {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}, +}; +#endif +/* Exposure Compensation */ +struct OV7692_WREG ov7692_exposure_compensation_lv0_tbl[] = { + /*@@ +1.7EV*/ + {0x24, 0xc0}, + {0x25, 0xb8}, + {0x26, 0xe6}, +}; + +struct OV7692_WREG ov7692_exposure_compensation_lv1_tbl[] = { + /*@@ +1.0EV*/ + {0x24, 0xa8}, + {0x25, 0xa0}, + {0x26, 0xc4}, +}; + +struct OV7692_WREG ov7692_exposure_compensation_lv2_default_tbl[] = { + /*@@ default*/ + {0x24, 0x86}, + {0x25, 0x76}, + {0x26, 0xb3}, +}; + +struct OV7692_WREG ov7692_exposure_compensation_lv3_tbl[] = { + /*@@ -1.0EV*/ + {0x24, 0x70}, + {0x25, 0x60}, + {0x26, 0xa2}, +}; + +struct OV7692_WREG ov7692_exposure_compensation_lv4_tbl[] = { + /*@@ -1.7EV*/ + {0x24, 0x50}, + {0x25, 0x40}, + {0x26, 0xa2}, +}; + +struct OV7692_WREG ov7692_antibanding_off_tbl[] = { + {0x13, 0xE5, INVMASK(0x20)}, +}; + +struct OV7692_WREG ov7692_antibanding_auto_tbl[] = { + {0x13, 0x20, INVMASK(0x20)}, + {0x14, 0x14, INVMASK(0x17)}, +}; + +struct OV7692_WREG ov7692_antibanding_50z_tbl[] = { + /*Band 50Hz*/ + {0x13, 0x20, INVMASK(0x20)}, + {0x14, 0x17, INVMASK(0x17)}, +}; + +struct OV7692_WREG ov7692_antibanding_60z_tbl[] = { + /*Band 60Hz*/ + {0x13, 0x20, INVMASK(0x20)}, + {0x14, 0x16, INVMASK(0x17)}, +}; + +/*Saturation*/ +struct OV7692_WREG ov7692_saturation_lv0_tbl[] = { + /*Saturation level 0*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x00, INVMASK(0xff)}, + {0xd9, 0x00, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +struct OV7692_WREG ov7692_saturation_lv1_tbl[] = { + /*Saturation level 1*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x10, INVMASK(0xff)}, + {0xd9, 0x10, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +struct OV7692_WREG ov7692_saturation_lv2_tbl[] = { + /*Saturation level 2*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x20, INVMASK(0xff)}, + {0xd9, 0x20, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, + +}; + +struct OV7692_WREG ov7692_saturation_lv3_tbl[] = { + /*Saturation level 3*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x30, INVMASK(0xff)}, + {0xd9, 0x30, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, + +}; + +struct OV7692_WREG ov7692_saturation_default_lv4_tbl[] = { + /*Saturation level 4 (default)*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x40, INVMASK(0xff)}, + {0xd9, 0x40, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +struct OV7692_WREG ov7692_saturation_lv5_tbl[] = { + /*Saturation level 5*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x50, INVMASK(0xff)}, + {0xd9, 0x50, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +struct OV7692_WREG ov7692_saturation_lv6_tbl[] = { + /*Saturation level 6*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x60, INVMASK(0xff)}, + {0xd9, 0x60, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +struct OV7692_WREG ov7692_saturation_lv7_tbl[] = { + /*Saturation level 7*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x70, INVMASK(0xff)}, + {0xd9, 0x70, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +struct OV7692_WREG ov7692_saturation_lv8_tbl[] = { + /*Saturation level 8*/ + {0x81, 0x33, INVMASK(0x33)}, + {0xd8, 0x80, INVMASK(0xff)}, + {0xd9, 0x80, INVMASK(0xff)}, + {0xd2, 0x02, INVMASK(0xff)}, +}; + +/*EFFECT*/ +struct OV7692_WREG ov7692_effect_normal_tbl[] = { + {0x81, 0x00, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x00, }, + {0xda, 0x80, }, + {0xdb, 0x80, }, +}; + +struct OV7692_WREG ov7692_effect_mono_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x18, }, + {0xda, 0x80, }, + {0xdb, 0x80, }, +}; + +struct OV7692_WREG ov7692_effect_bw_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x18, }, + {0xda, 0x80, }, + {0xdb, 0x80, }, +}; + +struct OV7692_WREG ov7692_effect_sepia_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x18, }, + {0xda, 0x40, }, + {0xdb, 0xa0, }, +}; + +struct OV7692_WREG ov7692_effect_bluish_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x18, }, + {0xda, 0xc0, }, + {0xdb, 0x80, }, +}; + +struct OV7692_WREG ov7692_effect_reddish_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x18, }, + {0xda, 0x80, }, + {0xdb, 0xc0, }, +}; + +struct OV7692_WREG ov7692_effect_greenish_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x00, }, + {0xd2, 0x18, }, + {0xda, 0x60, }, + {0xdb, 0x60, }, +}; + +struct OV7692_WREG ov7692_effect_negative_tbl[] = { + {0x81, 0x20, INVMASK(0x20)}, + {0x28, 0x80, }, + {0xd2, 0x40, }, + {0xda, 0x80, }, + {0xdb, 0x80, }, +}; + +/*Contrast*/ +struct OV7692_WREG ov7692_contrast_lv0_tbl[] = { + /*Contrast -4*/ + {0xb2, 0x29}, + {0xa3, 0x55}, + {0xa4, 0x5b}, + {0xa5, 0x67}, + {0xa6, 0x7e}, + {0xa7, 0x89}, + {0xa8, 0x93}, + {0xa9, 0x9c}, + {0xaa, 0xa4}, + {0xab, 0xac}, + {0xac, 0xb3}, + {0xad, 0xbe}, + {0xae, 0xc7}, + {0xaf, 0xd5}, + {0xb0, 0xdd}, + {0xb1, 0xe1}, +}; + +struct OV7692_WREG ov7692_contrast_lv1_tbl[] = { + /*Contrast -3*/ + {0xb2, 0x20}, + {0xa3, 0x43}, + {0xa4, 0x4a}, + {0xa5, 0x58}, + {0xa6, 0x73}, + {0xa7, 0x80}, + {0xa8, 0x8b}, + {0xa9, 0x96}, + {0xaa, 0x9f}, + {0xab, 0xa8}, + {0xac, 0xb1}, + {0xad, 0xbe}, + {0xae, 0xc9}, + {0xaf, 0xd8}, + {0xb0, 0xe2}, + {0xb1, 0xe8}, +}; + +struct OV7692_WREG ov7692_contrast_lv2_tbl[] = { + /*Contrast -2*/ + {0xb2, 0x18}, + {0xa3, 0x31}, + {0xa4, 0x39}, + {0xa5, 0x4a}, + {0xa6, 0x68}, + {0xa7, 0x77}, + {0xa8, 0x84}, + {0xa9, 0x90}, + {0xaa, 0x9b}, + {0xab, 0xa5}, + {0xac, 0xaf}, + {0xad, 0xbe}, + {0xae, 0xca}, + {0xaf, 0xdc}, + {0xb0, 0xe7}, + {0xb1, 0xee}, +}; + +struct OV7692_WREG ov7692_contrast_lv3_tbl[] = { + /*Contrast -1*/ + {0xb2, 0x10}, + {0xa3, 0x1f}, + {0xa4, 0x28}, + {0xa5, 0x3b}, + {0xa6, 0x5d}, + {0xa7, 0x6e}, + {0xa8, 0x7d}, + {0xa9, 0x8a}, + {0xaa, 0x96}, + {0xab, 0xa2}, + {0xac, 0xad}, + {0xad, 0xbe}, + {0xae, 0xcc}, + {0xaf, 0xe0}, + {0xb0, 0xed}, + {0xb1, 0xf4}, +}; + +struct OV7692_WREG ov7692_contrast_default_lv4_tbl[] = { + /*Contrast 0*/ + {0xb2, 0x6}, + {0xa3, 0xb}, + {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}, +}; + +struct OV7692_WREG ov7692_contrast_lv5_tbl[] = { + /*Contrast 1*/ + {0xb2, 0xc}, + {0xa3, 0x4}, + {0xa4, 0xc}, + {0xa5, 0x1f}, + {0xa6, 0x45}, + {0xa7, 0x58}, + {0xa8, 0x6b}, + {0xa9, 0x7c}, + {0xaa, 0x8d}, + {0xab, 0x9d}, + {0xac, 0xac}, + {0xad, 0xc3}, + {0xae, 0xd2}, + {0xaf, 0xe8}, + {0xb0, 0xf2}, + {0xb1, 0xf7}, +}; + +struct OV7692_WREG ov7692_contrast_lv6_tbl[] = { + /*Contrast 2*/ + {0xb2, 0x1}, + {0xa3, 0x2}, + {0xa4, 0x9}, + {0xa5, 0x1a}, + {0xa6, 0x3e}, + {0xa7, 0x4a}, + {0xa8, 0x59}, + {0xa9, 0x6a}, + {0xaa, 0x79}, + {0xab, 0x8e}, + {0xac, 0xa4}, + {0xad, 0xc1}, + {0xae, 0xdb}, + {0xaf, 0xf4}, + {0xb0, 0xff}, + {0xb1, 0xff}, +}; + +struct OV7692_WREG ov7692_contrast_lv7_tbl[] = { + /*Contrast 3*/ + {0xb2, 0xc}, + {0xa3, 0x4}, + {0xa4, 0x8}, + {0xa5, 0x17}, + {0xa6, 0x27}, + {0xa7, 0x3d}, + {0xa8, 0x54}, + {0xa9, 0x60}, + {0xaa, 0x77}, + {0xab, 0x85}, + {0xac, 0xa4}, + {0xad, 0xc6}, + {0xae, 0xd2}, + {0xaf, 0xe9}, + {0xb0, 0xf0}, + {0xb1, 0xf7}, +}; + +struct OV7692_WREG ov7692_contrast_lv8_tbl[] = { + /*Contrast 4*/ + {0xb2, 0x1}, + {0xa3, 0x4}, + {0xa4, 0x4}, + {0xa5, 0x7}, + {0xa6, 0xb}, + {0xa7, 0x17}, + {0xa8, 0x2a}, + {0xa9, 0x41}, + {0xaa, 0x59}, + {0xab, 0x6b}, + {0xac, 0x8b}, + {0xad, 0xb1}, + {0xae, 0xd2}, + {0xaf, 0xea}, + {0xb0, 0xf4}, + {0xb1, 0xff}, +}; + + /*Sharpness*/ +struct OV7692_WREG ov7692_sharpness_lv0_tbl[] = { + /*Sharpness 0*/ + {0xb4, 0x20, INVMASK(0x20)}, + {0xb6, 0x00, INVMASK(0x1f)}, +}; +struct OV7692_WREG ov7692_sharpness_lv1_tbl[] = { + /*Sharpness 1*/ + {0xb4, 0x20, INVMASK(0x20)}, + {0xb6, 0x01, INVMASK(0x1f)}, +}; +struct OV7692_WREG ov7692_sharpness_default_lv2_tbl[] = { + /*Sharpness Auto (Default)*/ + {0xb4, 0x00, INVMASK(0x20)}, + {0xb6, 0x00, INVMASK(0x1f)}, +}; +struct OV7692_WREG ov7692_sharpness_lv3_tbl[] = { + /*Sharpness 3*/ + {0xb4, 0x20, INVMASK(0x20)}, + {0xb6, 0x66, INVMASK(0x04)}, +}; +struct OV7692_WREG ov7692_sharpness_lv4_tbl[] = { + /*Sharpness 4*/ + {0xb4, 0x20, INVMASK(0x20)}, + {0xb6, 0x99, INVMASK(0x1f)}, +}; +struct OV7692_WREG ov7692_sharpness_lv5_tbl[] = { + /*Sharpness 5*/ + {0xb4, 0x20, INVMASK(0x20)}, + {0xb6, 0xcc, INVMASK(0x1f)}, +}; +struct OV7692_WREG ov7692_sharpness_lv6_tbl[] = { + /*Sharpness 6*/ + {0xb4, 0x20, INVMASK(0x20)}, + {0xb6, 0xff, INVMASK(0x1f)}, +}; + + /* ISO TYPE*/ +struct OV7692_WREG ov7692_iso_type_auto[] = { + /*@@ISO Auto*/ + {0x14, 0x20, INVMASK(0x70)}, +}; + +struct OV7692_WREG ov7692_iso_type_100[] = { + /*@@ISO 100*/ + {0x14, 0x00, INVMASK(0x70)}, +}; + +struct OV7692_WREG ov7692_iso_type_200[] = { + /*@@ISO 200*/ + {0x14, 0x10, INVMASK(0x70)}, +}; + +struct OV7692_WREG ov7692_iso_type_400[] = { + /*@@ISO 400*/ + {0x14, 0x20, INVMASK(0x70)}, +}; + +struct OV7692_WREG ov7692_iso_type_800[] = { + /*@@ISO 800*/ + {0x14, 0x30, INVMASK(0x70)}, +}; + +struct OV7692_WREG ov7692_iso_type_1600[] = { + /*@@ISO 1600*/ + {0x14, 0x40, INVMASK(0x70)}, +}; + + /*Light Mode*/ +struct OV7692_WREG ov7692_wb_def[] = { + {0x13, 0xf7}, + {0x15, 0x00}, +}; + +struct OV7692_WREG ov7692_wb_custom[] = { + {0x13, 0xf5}, + {0x01, 0x56}, + {0x02, 0x50}, + {0x15, 0x00}, +}; + +struct OV7692_WREG ov7692_wb_inc[] = { + {0x13, 0xf5}, + {0x01, 0x66}, + {0x02, 0x40}, + {0x15, 0x00}, +}; + +struct OV7692_WREG ov7692_wb_daylight[] = { + {0x13, 0xf5}, + {0x01, 0x43}, + {0x02, 0x5d}, + {0x15, 0x00}, +}; + +struct OV7692_WREG ov7692_wb_cloudy[] = { + {0x13, 0xf5}, + {0x01, 0x48}, + {0x02, 0x63}, + {0x15, 0x00}, +}; + +#endif + diff --git a/drivers/media/video/msm/ov7692_qrd.c b/drivers/media/video/msm/ov7692_qrd.c new file mode 100644 index 0000000000000000000000000000000000000000..d83f28e49e691985b44d96516d36cad3e0cb94a4 --- /dev/null +++ b/drivers/media/video/msm/ov7692_qrd.c @@ -0,0 +1,1178 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#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 + +static int ov7692_pwdn_gpio; +static int ov7692_reset_gpio; + + +/*============================================================================ + DATA DECLARATIONS +============================================================================*/ + + +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 effect_value; +static int16_t ov7692_effect = CAMERA_EFFECT_OFF; +static unsigned int SAT_U = 0x80; +static unsigned int SAT_V = 0x80; + +/*=============================================================*/ + +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_WritePRegs(struct OV7692_WREG *pTb, int32_t len) +{ + int32_t i, ret = 0; + uint8_t regv; + + for (i = 0; i < len; i++) { + if (pTb[i].mask == 0) { + ov7692_i2c_write_b_sensor(pTb[i].addr, pTb[i].data); + } else { + ov7692_i2c_read(pTb[i].addr, ®v, 1); + regv &= pTb[i].mask; + regv |= (pTb[i].data & (~pTb[i].mask)); + ov7692_i2c_write_b_sensor(pTb[i].addr, regv); + } + } + return ret; +} + +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; + + CDBG("%s: rt = %d\n", __func__, rt); + + 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; + + 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; + } + usleep_range(10000, 11000); + rc = msm_camio_csi_config(&ov7692_csi_params); + usleep_range(10000, 11000); + ov7692_i2c_write_b_sensor(0x0e, 0x00); + 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; + + CDBG("%s\n", __func__); + + 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 int ov7692_set_exposure_compensation(int compensation) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...exposure_compensation = %d\n", + __func__ , compensation); + switch (compensation) { + case CAMERA_EXPOSURE_COMPENSATION_LV0: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV0\n"); + rc = OV7692Core_WritePREG( + ov7692_exposure_compensation_lv0_tbl); + break; + case CAMERA_EXPOSURE_COMPENSATION_LV1: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV1\n"); + rc = OV7692Core_WritePREG( + ov7692_exposure_compensation_lv1_tbl); + break; + case CAMERA_EXPOSURE_COMPENSATION_LV2: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV2\n"); + rc = OV7692Core_WritePREG( + ov7692_exposure_compensation_lv2_default_tbl); + break; + case CAMERA_EXPOSURE_COMPENSATION_LV3: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV3\n"); + rc = OV7692Core_WritePREG( + ov7692_exposure_compensation_lv3_tbl); + break; + case CAMERA_EXPOSURE_COMPENSATION_LV4: + CDBG("--CAMERA--CAMERA_EXPOSURE_COMPENSATION_LV3\n"); + rc = OV7692Core_WritePREG( + ov7692_exposure_compensation_lv4_tbl); + break; + default: + CDBG("--CAMERA--ERROR CAMERA_EXPOSURE_COMPENSATION\n"); + break; + } + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static long ov7692_set_antibanding(int antibanding) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...antibanding = %d\n", __func__, antibanding); + switch (antibanding) { + case CAMERA_ANTIBANDING_OFF: + CDBG("--CAMERA--CAMERA_ANTIBANDING_OFF\n"); + break; + case CAMERA_ANTIBANDING_60HZ: + CDBG("--CAMERA--CAMERA_ANTIBANDING_60HZ\n"); + rc = OV7692Core_WritePREG(ov7692_antibanding_60z_tbl); + break; + case CAMERA_ANTIBANDING_50HZ: + CDBG("--CAMERA--CAMERA_ANTIBANDING_50HZ\n"); + rc = OV7692Core_WritePREG(ov7692_antibanding_50z_tbl); + break; + case CAMERA_ANTIBANDING_AUTO: + CDBG("--CAMERA--CAMERA_ANTIBANDING_AUTO\n"); + rc = OV7692Core_WritePREG(ov7692_antibanding_auto_tbl); + break; + default: + CDBG("--CAMERA--CAMERA_ANTIBANDING_ERROR COMMAND\n"); + break; + } + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov7692_set_saturation(int saturation) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...saturation = %d\n", __func__ , saturation); + + if (effect_value == CAMERA_EFFECT_OFF) { + switch (saturation) { + case CAMERA_SATURATION_LV0: + CDBG("--CAMERA--CAMERA_SATURATION_LV0\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv0_tbl); + break; + case CAMERA_SATURATION_LV1: + CDBG("--CAMERA--CAMERA_SATURATION_LV1\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv1_tbl); + break; + case CAMERA_SATURATION_LV2: + CDBG("--CAMERA--CAMERA_SATURATION_LV2\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv2_tbl); + break; + case CAMERA_SATURATION_LV3: + CDBG("--CAMERA--CAMERA_SATURATION_LV3\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv3_tbl); + break; + case CAMERA_SATURATION_LV4: + CDBG("--CAMERA--CAMERA_SATURATION_LV4\n"); + rc = OV7692Core_WritePREG( + ov7692_saturation_default_lv4_tbl); + break; + case CAMERA_SATURATION_LV5: + CDBG("--CAMERA--CAMERA_SATURATION_LV5\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv5_tbl); + break; + case CAMERA_SATURATION_LV6: + CDBG("--CAMERA--CAMERA_SATURATION_LV6\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv6_tbl); + break; + case CAMERA_SATURATION_LV7: + CDBG("--CAMERA--CAMERA_SATURATION_LV7\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv7_tbl); + break; + case CAMERA_SATURATION_LV8: + CDBG("--CAMERA--CAMERA_SATURATION_LV8\n"); + rc = OV7692Core_WritePREG(ov7692_saturation_lv8_tbl); + break; + default: + CDBG("--CAMERA--CAMERA_SATURATION_ERROR COMMAND\n"); + break; + } + } + + /*for recover saturation level when change special effect*/ + switch (saturation) { + case CAMERA_SATURATION_LV0: + CDBG("--CAMERA--CAMERA_SATURATION_LV0\n"); + SAT_U = 0x00; + SAT_V = 0x00; + break; + case CAMERA_SATURATION_LV1: + CDBG("--CAMERA--CAMERA_SATURATION_LV1\n"); + SAT_U = 0x10; + SAT_V = 0x10; + break; + case CAMERA_SATURATION_LV2: + CDBG("--CAMERA--CAMERA_SATURATION_LV2\n"); + SAT_U = 0x20; + SAT_V = 0x20; + break; + case CAMERA_SATURATION_LV3: + CDBG("--CAMERA--CAMERA_SATURATION_LV3\n"); + SAT_U = 0x30; + SAT_V = 0x30; + break; + case CAMERA_SATURATION_LV4: + CDBG("--CAMERA--CAMERA_SATURATION_LV4\n"); + SAT_U = 0x40; + SAT_V = 0x40; + break; + case CAMERA_SATURATION_LV5: + CDBG("--CAMERA--CAMERA_SATURATION_LV5\n"); + SAT_U = 0x50; + SAT_V = 0x50; + break; + case CAMERA_SATURATION_LV6: + CDBG("--CAMERA--CAMERA_SATURATION_LV6\n"); + SAT_U = 0x60; + SAT_V = 0x60; + break; + case CAMERA_SATURATION_LV7: + CDBG("--CAMERA--CAMERA_SATURATION_LV7\n"); + SAT_U = 0x70; + SAT_V = 0x70; + break; + case CAMERA_SATURATION_LV8: + CDBG("--CAMERA--CAMERA_SATURATION_LV8\n"); + SAT_U = 0x80; + SAT_V = 0x80; + break; + default: + CDBG("--CAMERA--CAMERA_SATURATION_ERROR COMMAND\n"); + break; + } + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static long ov7692_set_effect(int mode, int effect) +{ + int rc = 0; + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + + switch (mode) { + case SENSOR_PREVIEW_MODE: + break; + case SENSOR_HFR_60FPS_MODE: + break; + case SENSOR_HFR_90FPS_MODE: + /* Context A Special Effects */ + CDBG("-CAMERA- %s ...SENSOR_PREVIEW_MODE\n", __func__); + break; + case SENSOR_SNAPSHOT_MODE: + /* Context B Special Effects */ + CDBG("-CAMERA- %s ...SENSOR_SNAPSHOT_MODE\n", __func__); + break; + default: + break; + } + effect_value = effect; + switch (effect) { + case CAMERA_EFFECT_OFF: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_OFF\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_normal_tbl); + /* for recover saturation level + when change special effect*/ + ov7692_i2c_write_b_sensor(0xda, SAT_U); + /* for recover saturation level + when change special effect*/ + ov7692_i2c_write_b_sensor(0xdb, SAT_V); + break; + } + case CAMERA_EFFECT_MONO: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_MONO\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_mono_tbl); + break; + } + case CAMERA_EFFECT_BW: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_BW\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_bw_tbl); + break; + } + case CAMERA_EFFECT_BLUISH: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_BLUISH\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_bluish_tbl); + break; + } + case CAMERA_EFFECT_SOLARIZE: { + CDBG("%s ...CAMERA_EFFECT_NEGATIVE(No Support)!\n", __func__); + break; + } + case CAMERA_EFFECT_SEPIA: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_SEPIA\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_sepia_tbl); + break; + } + case CAMERA_EFFECT_REDDISH: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_REDDISH\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_reddish_tbl); + break; + } + case CAMERA_EFFECT_GREENISH: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_GREENISH\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_greenish_tbl); + break; + } + case CAMERA_EFFECT_NEGATIVE: { + CDBG("--CAMERA-- %s ...CAMERA_EFFECT_NEGATIVE\n", __func__); + rc = OV7692Core_WritePREG(ov7692_effect_negative_tbl); + break; + } + default: { + CDBG("--CAMERA-- %s ...Default(Not Support)\n", __func__); + } + } + ov7692_effect = effect; + /*Refresh Sequencer */ + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov7692_set_contrast(int contrast) +{ + int rc = 0; + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...contrast = %d\n", __func__ , contrast); + + if (effect_value == CAMERA_EFFECT_OFF) { + switch (contrast) { + case CAMERA_CONTRAST_LV0: + CDBG("--CAMERA--CAMERA_CONTRAST_LV0\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv0_tbl); + break; + case CAMERA_CONTRAST_LV1: + CDBG("--CAMERA--CAMERA_CONTRAST_LV1\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv1_tbl); + break; + case CAMERA_CONTRAST_LV2: + CDBG("--CAMERA--CAMERA_CONTRAST_LV2\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv2_tbl); + break; + case CAMERA_CONTRAST_LV3: + CDBG("--CAMERA--CAMERA_CONTRAST_LV3\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv3_tbl); + break; + case CAMERA_CONTRAST_LV4: + CDBG("--CAMERA--CAMERA_CONTRAST_LV4\n"); + rc = OV7692Core_WritePREG( + ov7692_contrast_default_lv4_tbl); + break; + case CAMERA_CONTRAST_LV5: + CDBG("--CAMERA--CAMERA_CONTRAST_LV5\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv5_tbl); + break; + case CAMERA_CONTRAST_LV6: + CDBG("--CAMERA--CAMERA_CONTRAST_LV6\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv6_tbl); + break; + case CAMERA_CONTRAST_LV7: + CDBG("--CAMERA--CAMERA_CONTRAST_LV7\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv7_tbl); + break; + case CAMERA_CONTRAST_LV8: + CDBG("--CAMERA--CAMERA_CONTRAST_LV8\n"); + rc = OV7692Core_WritePREG(ov7692_contrast_lv8_tbl); + break; + default: + CDBG("--CAMERA--CAMERA_CONTRAST_ERROR COMMAND\n"); + break; + } + } + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov7692_set_sharpness(int sharpness) +{ + int rc = 0; + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...sharpness = %d\n", __func__ , sharpness); + + if (effect_value == CAMERA_EFFECT_OFF) { + switch (sharpness) { + case CAMERA_SHARPNESS_LV0: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV0\n"); + rc = OV7692Core_WritePREG(ov7692_sharpness_lv0_tbl); + break; + case CAMERA_SHARPNESS_LV1: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV1\n"); + rc = OV7692Core_WritePREG(ov7692_sharpness_lv1_tbl); + break; + case CAMERA_SHARPNESS_LV2: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV2\n"); + rc = OV7692Core_WritePREG( + ov7692_sharpness_default_lv2_tbl); + break; + case CAMERA_SHARPNESS_LV3: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV3\n"); + rc = OV7692Core_WritePREG(ov7692_sharpness_lv3_tbl); + break; + case CAMERA_SHARPNESS_LV4: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV4\n"); + rc = OV7692Core_WritePREG(ov7692_sharpness_lv4_tbl); + break; + case CAMERA_SHARPNESS_LV5: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV5\n"); + rc = OV7692Core_WritePREG(ov7692_sharpness_lv5_tbl); + break; + case CAMERA_SHARPNESS_LV6: + CDBG("--CAMERA--CAMERA_SHARPNESS_LV6\n"); + rc = OV7692Core_WritePREG(ov7692_sharpness_lv6_tbl); + break; + default: + CDBG("--CAMERA--CAMERA_SHARPNESS_ERROR COMMAND\n"); + break; + } + } + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov7692_set_iso(int8_t iso_type) +{ + long rc = 0; + + CDBG("--CAMERA-- %s ...(Start)\n", __func__); + CDBG("--CAMERA-- %s ...iso_type = %d\n", __func__ , iso_type); + switch (iso_type) { + case CAMERA_ISO_TYPE_AUTO: + CDBG("--CAMERA--CAMERA_ISO_TYPE_AUTO\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_auto); + break; + case CAMEAR_ISO_TYPE_HJR: + CDBG("--CAMERA--CAMEAR_ISO_TYPE_HJR\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_auto); + break; + case CAMEAR_ISO_TYPE_100: + CDBG("--CAMERA--CAMEAR_ISO_TYPE_100\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_100); + break; + case CAMERA_ISO_TYPE_200: + CDBG("--CAMERA--CAMERA_ISO_TYPE_200\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_200); + break; + case CAMERA_ISO_TYPE_400: + CDBG("--CAMERA--CAMERA_ISO_TYPE_400\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_400); + break; + case CAMEAR_ISO_TYPE_800: + CDBG("--CAMERA--CAMEAR_ISO_TYPE_800\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_800); + break; + case CAMERA_ISO_TYPE_1600: + CDBG("--CAMERA--CAMERA_ISO_TYPE_1600\n"); + rc = OV7692Core_WritePREG(ov7692_iso_type_1600); + break; + default: + CDBG("--CAMERA--ERROR ISO TYPE\n"); + break; + } + CDBG("--CAMERA-- %s ...(End)\n", __func__); + return rc; +} + +static int ov7692_set_wb_oem(uint8_t param) +{ + int rc = 0; + CDBG("--CAMERA--%s runs\r\n", __func__); + + switch (param) { + case CAMERA_WB_AUTO: + CDBG("--CAMERA--CAMERA_WB_AUTO\n"); + rc = OV7692Core_WritePREG(ov7692_wb_def); + break; + case CAMERA_WB_CUSTOM: + CDBG("--CAMERA--CAMERA_WB_CUSTOM\n"); + rc = OV7692Core_WritePREG(ov7692_wb_custom); + break; + case CAMERA_WB_INCANDESCENT: + CDBG("--CAMERA--CAMERA_WB_INCANDESCENT\n"); + rc = OV7692Core_WritePREG(ov7692_wb_inc); + break; + case CAMERA_WB_DAYLIGHT: + CDBG("--CAMERA--CAMERA_WB_DAYLIGHT\n"); + rc = OV7692Core_WritePREG(ov7692_wb_daylight); + break; + case CAMERA_WB_CLOUDY_DAYLIGHT: + CDBG("--CAMERA--CAMERA_WB_CLOUDY_DAYLIGHT\n"); + rc = OV7692Core_WritePREG(ov7692_wb_cloudy); + break; + default: + break; + } + return rc; +} + +static void ov7692_power_on(void) +{ + CDBG("%s\n", __func__); + gpio_set_value(ov7692_pwdn_gpio, 0); +} + +static void ov7692_power_down(void) +{ + CDBG("%s\n", __func__); + gpio_set_value(ov7692_pwdn_gpio, 1); +} + +static void ov7692_sw_reset(void) +{ + CDBG("%s\n", __func__); + ov7692_i2c_write_b_sensor(0x12, 0x80); +} + +static void ov7692_hw_reset(void) +{ + CDBG("--CAMERA-- %s ... (Start...)\n", __func__); + gpio_set_value(ov7692_reset_gpio, 1); /*reset camera reset pin*/ + usleep_range(5000, 5100); + gpio_set_value(ov7692_reset_gpio, 0); + usleep_range(5000, 5100); + gpio_set_value(ov7692_reset_gpio, 1); + usleep_range(1000, 1100); + CDBG("--CAMERA-- %s ... (End...)\n", __func__); +} + + + +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 = 0; + 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; + /* turn on LDO for PVT */ + if (data->pmic_gpio_enable) + lcd_camera_power_onoff(1); + + /* enable mclk first */ + + msm_camio_clk_rate_set(24000000); + msleep(20); + + ov7692_power_on(); + usleep_range(5000, 5100); + + 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"); + if (data->pmic_gpio_enable) + lcd_camera_power_onoff(0); + 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_SET_EFFECT: + CDBG("--CAMERA-- CFG_SET_EFFECT mode=%d, effect = %d !!\n", + cdata.mode, cdata.cfg.effect); + rc = ov7692_set_effect(cdata.mode, cdata.cfg.effect); + break; + case CFG_START: + CDBG("--CAMERA-- CFG_START (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_PWR_UP: + CDBG("--CAMERA-- CFG_PWR_UP (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_PWR_DOWN: + CDBG("--CAMERA-- CFG_PWR_DOWN !!\n"); + ov7692_power_down(); + break; + case CFG_WRITE_EXPOSURE_GAIN: + CDBG("--CAMERA-- CFG_WRITE_EXPOSURE_GAIN (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_DEFAULT_FOCUS: + CDBG("--CAMERA-- CFG_SET_DEFAULT_FOCUS (Not Implement) !!\n"); + break; + case CFG_MOVE_FOCUS: + CDBG("--CAMERA-- CFG_MOVE_FOCUS (Not Implement) !!\n"); + break; + case CFG_REGISTER_TO_REAL_GAIN: + CDBG("--CAMERA-- CFG_REGISTER_TO_REAL_GAIN (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_REAL_TO_REGISTER_GAIN: + CDBG("--CAMERA-- CFG_REAL_TO_REGISTER_GAIN (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_FPS: + CDBG("--CAMERA-- CFG_SET_FPS (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_PICT_FPS: + CDBG("--CAMERA-- CFG_SET_PICT_FPS (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_BRIGHTNESS: + CDBG("--CAMERA-- CFG_SET_BRIGHTNESS !!\n"); + /* rc = ov7692_set_brightness(cdata.cfg.brightness); */ + break; + case CFG_SET_CONTRAST: + CDBG("--CAMERA-- CFG_SET_CONTRAST !!\n"); + rc = ov7692_set_contrast(cdata.cfg.contrast); + break; + case CFG_SET_ZOOM: + CDBG("--CAMERA-- CFG_SET_ZOOM (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_EXPOSURE_MODE: + CDBG("--CAMERA-- CFG_SET_EXPOSURE_MODE !!\n"); + /* rc = ov7692_set_exposure_mode(cdata.cfg.ae_mode); */ + break; + case CFG_SET_WB: + CDBG("--CAMERA-- CFG_SET_WB!!\n"); + ov7692_set_wb_oem(cdata.cfg.wb_val); + rc = 0 ; + break; + case CFG_SET_ANTIBANDING: + CDBG("--CAMERA-- CFG_SET_ANTIBANDING antibanding = %d !!\n", + cdata.cfg.antibanding); + rc = ov7692_set_antibanding(cdata.cfg.antibanding); + break; + case CFG_SET_EXP_GAIN: + CDBG("--CAMERA-- CFG_SET_EXP_GAIN (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_PICT_EXP_GAIN: + CDBG("--CAMERA-- CFG_SET_PICT_EXP_GAIN (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_LENS_SHADING: + CDBG("--CAMERA-- CFG_SET_LENS_SHADING !!\n"); + /* rc = ov7692_lens_shading_enable(cdata.cfg.lens_shading); */ + break; + case CFG_GET_PICT_FPS: + CDBG("--CAMERA-- CFG_GET_PICT_FPS (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_GET_PREV_L_PF: + CDBG("--CAMERA-- CFG_GET_PREV_L_PF (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_GET_PREV_P_PL: + CDBG("--CAMERA-- CFG_GET_PREV_P_PL (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_GET_PICT_L_PF: + CDBG("--CAMERA-- CFG_GET_PICT_L_PF (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_GET_PICT_P_PL: + CDBG("--CAMERA-- CFG_GET_PICT_P_PL (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_GET_AF_MAX_STEPS: + CDBG("--CAMERA-- CFG_GET_AF_MAX_STEPS (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_GET_PICT_MAX_EXP_LC: + CDBG("--CAMERA-- CFG_GET_PICT_MAX_EXP_LC (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SEND_WB_INFO: + CDBG("--CAMERA-- CFG_SEND_WB_INFO (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SENSOR_INIT: + CDBG("--CAMERA-- CFG_SENSOR_INIT (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_SATURATION: + CDBG("--CAMERA-- CFG_SET_SATURATION !!\n"); + rc = ov7692_set_saturation(cdata.cfg.saturation); + break; + case CFG_SET_SHARPNESS: + CDBG("--CAMERA-- CFG_SET_SHARPNESS !!\n"); + rc = ov7692_set_sharpness(cdata.cfg.sharpness); + break; + case CFG_SET_TOUCHAEC: + CDBG("--CAMERA-- CFG_SET_TOUCHAEC!!\n"); + /* ov7692_set_touchaec(cdata.cfg.aec_cord.x, + cdata.cfg.aec_cord.y); */ + rc = 0 ; + break; + case CFG_SET_AUTO_FOCUS: + CDBG("--CAMERA-- CFG_SET_AUTO_FOCUS (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_AUTOFLASH: + CDBG("--CAMERA-- CFG_SET_AUTOFLASH (Not Support) !!\n"); + /* Not Support */ + break; + case CFG_SET_EXPOSURE_COMPENSATION: + CDBG("--CAMERA-- CFG_SET_EXPOSURE_COMPENSATION !\n"); + rc = ov7692_set_exposure_compensation( + cdata.cfg.exp_compensation); + break; + case CFG_SET_ISO: + CDBG("--CAMERA-- CFG_SET_ISO !\n"); + rc = ov7692_set_iso(cdata.cfg.iso_type); + break; + default: + CDBG("--CAMERA-- %s: Command=%d (Not Implement) !!\n", + __func__, cdata.cfgtype); + rc = -EINVAL; + break; + } + + mutex_unlock(&ov7692_mut); + + return rc; +} +static int ov7692_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&ov7692_mut); + ov7692_sw_reset(); + ov7692_power_down(); + kfree(ov7692_ctrl); + ov7692_ctrl = NULL; + CDBG("ov7692_release completed\n"); + mutex_unlock(&ov7692_mut); + + return rc; +} + +static int ov7692_probe_init_gpio(const struct msm_camera_sensor_info *data) +{ + int rc = 0; + + ov7692_pwdn_gpio = data->sensor_pwd; + ov7692_reset_gpio = data->sensor_reset ; + + if (data->sensor_reset_enable) + gpio_direction_output(data->sensor_reset, 1); + + gpio_direction_output(data->sensor_pwd, 1); + + 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; + } + pr_debug("%s: %d Entered\n", __func__, __LINE__); + rc = ov7692_probe_init_gpio(info); + if (rc < 0) { + CDBG("%s: gpio init failed\n", __func__); + goto probe_fail; + } + /* turn on LDO for PVT */ + if (info->pmic_gpio_enable) + lcd_camera_power_onoff(1); + + ov7692_power_down(); + + msm_camio_clk_rate_set(24000000); + usleep_range(5000, 5100); + + ov7692_power_on(); + usleep_range(5000, 5100); + + if (info->sensor_reset_enable) + ov7692_hw_reset(); + else + ov7692_sw_reset(); + + 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 = info->sensor_platform_info->mount_angle; + + /* ov7692_sw_reset(); */ + ov7692_power_down(); + + if (info->pmic_gpio_enable) + lcd_camera_power_onoff(0); + + return rc; + +probe_fail: + CDBG("ov7692_sensor_probe: SENSOR PROBE FAILS!\n"); + if (info->pmic_gpio_enable) + lcd_camera_power_onoff(0); + 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/ov9726.c b/drivers/media/video/msm/ov9726.c new file mode 100644 index 0000000000000000000000000000000000000000..9619baa1d542aad4ac90fa341ba63fff7137f825 --- /dev/null +++ b/drivers/media/video/msm/ov9726.c @@ -0,0 +1,794 @@ +/* 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); + if (ov9726_ctrl->sensordata->sensor_reset_enable) { + 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 0000000000000000000000000000000000000000..56d3da6514f29aada34fa4cce92bfede1a7d9238 --- /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 0000000000000000000000000000000000000000..54afbe8a00e52dfd1bea7fb78a5380b45886b2fe --- /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 0000000000000000000000000000000000000000..64db015d63bb5c5d25ac2d341e23c90ca1fd139d --- /dev/null +++ b/drivers/media/video/msm/qs_s5k4e1.c @@ -0,0 +1,1822 @@ +/* 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 +#define QS_S5K4E1_TOTAL_STEPS_3D 32 + +uint16_t qs_s5k4e1_step_position_table[QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR+1]; +uint16_t qs_s5k4e1_step_position_table_left[QS_S5K4E1_TOTAL_STEPS_3D+1]; +uint16_t qs_s5k4e1_step_position_table_right[QS_S5K4E1_TOTAL_STEPS_3D+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_lines_per_frame; + 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_lines_per_frame = (uint16_t) + ((prev_frame_length_lines) * qs_s5k4e1_ctrl->fps_divider/0x400); + } else { + total_lines_per_frame = (uint16_t) + ((snap_frame_length_lines) * + qs_s5k4e1_ctrl->pict_fps_divider/0x400); + } + qs_s5k4e1_group_hold_on(); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_FRAME_LENGTH_LINES, + total_lines_per_frame); + 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; + uint32_t ll_pck, fl_lines; + uint16_t gain = exp_cfg.gain; + uint32_t line = exp_cfg.line; + 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; + fl_lines = prev_frame_length_lines * + qs_s5k4e1_ctrl->fps_divider / 0x400; + ll_pck = prev_line_length_pck; + } else { + fl_lines = snap_frame_length_lines * + qs_s5k4e1_ctrl->pict_fps_divider / 0x400; + ll_pck = snap_line_length_pck; + } + if (line > (fl_lines - QS_S5K4E1_OFFSET)) + fl_lines = line + QS_S5K4E1_OFFSET; + qs_s5k4e1_group_hold_on(); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_GLOBAL_GAIN, gain); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_FRAME_LENGTH_LINES, fl_lines); + 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_write_1D_focus_value(uint16_t code_value) +{ + uint8_t code_val_msb, code_val_lsb; + CDBG("%s: 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; + } + + 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; + int16_t max_step_postion = QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; + 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 (qs_s5k4e1_ctrl->cam_mode == MODE_3D) + max_step_postion = QS_S5K4E1_TOTAL_STEPS_3D; + + if (dest_step_position > max_step_postion) + dest_step_position = max_step_postion; + 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; + } + + 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->cam_mode == MODE_3D) { + /* Left */ + bridge_i2c_write_w(0x06, 0x02); + dest_lens_position = + qs_s5k4e1_step_position_table_left[dest_step_position]; + if (qs_s5k4e1_write_1D_focus_value(dest_lens_position) < 0) { + CDBG("move_focus failed at line %d ...\n", __LINE__); + bridge_i2c_write_w(0x06, 0x03); + return -EBUSY; + } + /* Keep left sensor as reference as AF stats is from left */ + qs_s5k4e1_ctrl->curr_step_pos = dest_step_position; + qs_s5k4e1_ctrl->curr_lens_pos = dest_lens_position; + + /* Right */ + bridge_i2c_write_w(0x06, 0x01); + dest_lens_position = + qs_s5k4e1_step_position_table_right[dest_step_position]; + if (qs_s5k4e1_write_1D_focus_value(dest_lens_position) < 0) { + CDBG("move_focus failed at line %d ...\n", __LINE__); + bridge_i2c_write_w(0x06, 0x03); + return -EBUSY; + } + + /* 3D Mode */ + bridge_i2c_write_w(0x06, 0x03); + return 0; + } + + dest_lens_position = qs_s5k4e1_step_position_table[dest_step_position]; + CDBG("%s: Step Position: %d\n", __func__, dest_step_position); + 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 { + if (qs_s5k4e1_ctrl->cam_mode == MODE_3D) { + /* Left */ + bridge_i2c_write_w(0x06, 0x02); + rc = qs_s5k4e1_write_1D_focus_value( + qs_s5k4e1_step_position_table_left[0]); + if (rc < 0) { + bridge_i2c_write_w(0x06, 0x03); + return rc; + } + + /* Right */ + bridge_i2c_write_w(0x06, 0x01); + rc = qs_s5k4e1_write_1D_focus_value( + qs_s5k4e1_step_position_table_right[0]); + if (rc < 0) { + bridge_i2c_write_w(0x06, 0x03); + return rc; + } + + /* Left sensor is the reference sensor for AF stats */ + qs_s5k4e1_ctrl->curr_lens_pos = + qs_s5k4e1_step_position_table_left[0]; + + /* 3D Mode */ + bridge_i2c_write_w(0x06, 0x03); + } 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_3d_table_init(void) +{ + int16_t af_data = 0; + uint16_t step = 8, step_q2 = 8, anchor_point_q2; + int32_t rc = 0, i, j; + uint16_t eeprom_read_addr[2][3] = {{0x110, 0x114, 0x118}, + {0x112, 0x116, 0x11A} }; + uint16_t *step_position_table; + + step_position_table = qs_s5k4e1_step_position_table_left; + for (j = 0; j < 2; j++) { + rc = qs_s5k4e1_eeprom_i2c_read_b(eeprom_read_addr[j][0], + &af_data, 2); + if (rc == 0) { + CDBG("%s: Far data - %d\n", __func__, af_data); + step_position_table[0] = af_data; + } else { + CDBG("%s: EEPROM data read error\n", __func__); + return; + } + + rc = qs_s5k4e1_eeprom_i2c_read_b(eeprom_read_addr[j][1], + &af_data, 2); + if (rc == 0) { + CDBG("%s: Medium data - %d\n", __func__, af_data); + step_position_table[2] = af_data; + } else { + CDBG("%s: EEPROM data read error\n", __func__); + return; + } + + /* + * Using the 150cm and 100cm calibration values + * as per the Lens characteristics derive intermediate step + */ + step_position_table[1] = step_position_table[0] + + (step_position_table[2] - step_position_table[0])/2; + CDBG("%s: Step between 150cm:100cm is %d\n", __func__, + step_position_table[1]); + + rc = qs_s5k4e1_eeprom_i2c_read_b(eeprom_read_addr[j][2], + &af_data, 2); + if (rc == 0) { + CDBG("%s: Short data - %d\n", __func__, af_data); + step_position_table[6] = af_data; + } else { + CDBG("%s: EEPROM data read error\n", __func__); + return; + } + + /* + * Using the 100cm and 50cm calibration values + * as per the Lens characteristics derive + * intermediate steps + */ + step = (step_position_table[6] - step_position_table[2])/4; + + /* + * Interpolate the intermediate steps between 100cm + * to 50cm based on COC1.5 + */ + step_position_table[3] = step_position_table[2] + step; + step_position_table[4] = step_position_table[3] + step; + step_position_table[5] = step_position_table[4] + step; + + /* + * Extrapolate the steps within 50cm based on + * OC2 to converge faster. This range is beyond the 3D + * specification of 50cm + */ + anchor_point_q2 = step_position_table[6] << 1; + step_q2 = (step_position_table[6] - step_position_table[2]); + + for (i = 7; i < QS_S5K4E1_TOTAL_STEPS_3D; i++) { + anchor_point_q2 += step_q2; + step_position_table[i] = anchor_point_q2 >> 1; + } + step_position_table = qs_s5k4e1_step_position_table_right; + } +} + +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_3d_table_init(); + + 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 = 0; + 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 = 1; + 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 = 28; + 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: + if (qs_s5k4e1_ctrl->cam_mode == MODE_3D) + cdata.max_steps = QS_S5K4E1_TOTAL_STEPS_3D; + else + 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 = info->sensor_platform_info->mount_angle; + 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 0000000000000000000000000000000000000000..f9c4c3f492bc50e2a680121724cc860708f8552e --- /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 0000000000000000000000000000000000000000..39c3f29a48e368847d307e355ebe6302220b796c --- /dev/null +++ b/drivers/media/video/msm/qs_s5k4e1_reg.c @@ -0,0 +1,804 @@ +/* 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}, + {0x30B5, 0x01}, + {0x30E2, 0x02},/*num lanes[1:0] = 1*/ + {0x30F1, 0x60}, +/*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}, + {0x30B5, 0x00}, + {0x30E2, 0x01},/*num lanes[1:0] = 1*/ + {0x30F1, 0xd0}, + {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}, + {0x30B5, 0x00}, + {0x30E2, 0x01},/*num lanes[1:0] = 1*/ + {0x30F1, 0xd0}, + {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}, + {0x30B5, 0x01}, + {0x30E2, 0x02},/*num lanes[1:0] = 1*/ + {0x30F1, 0x60}, +/*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}, +}; +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 0000000000000000000000000000000000000000..07a058ece86d08d819ce3b062388aa6643ad5213 --- /dev/null +++ b/drivers/media/video/msm/s5k3e2fx.c @@ -0,0 +1,1387 @@ +/* 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 "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 0000000000000000000000000000000000000000..cf3f88140c4ba9472df6dfc0f06dfdda261e4f75 --- /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 0000000000000000000000000000000000000000..30572d839d90f6f1debb11025f9ce39a270b58a8 --- /dev/null +++ b/drivers/media/video/msm/s5k4e1.c @@ -0,0 +1,1103 @@ +/* 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 gpio_req_fail; + + 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); + + return rc; + +init_probe_fail: + CDBG(" s5k4e1_probe_init_sensor fails\n"); + gpio_set_value_cansleep(data->sensor_reset, 0); + s5k4e1_probe_init_done(data); + if (data->vcm_enable) { + int ret = gpio_request(data->vcm_pwd, "s5k4e1_af"); + if (!ret) { + gpio_direction_output(data->vcm_pwd, 0); + msleep(20); + gpio_free(data->vcm_pwd); + } + } +gpio_req_fail: + 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_set_value_cansleep(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); + /* Keep vcm_pwd to OUT Low */ + if (info->vcm_enable) { + rc = gpio_request(info->vcm_pwd, "s5k4e1_af"); + if (!rc) { + gpio_direction_output(info->vcm_pwd, 0); + msleep(20); + gpio_free(info->vcm_pwd); + } else + return rc; + } + 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 0000000000000000000000000000000000000000..7f603322a3c45f2b0e7808a4d29c5a617232f3b5 --- /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 0000000000000000000000000000000000000000..59bb1c852958b3c531b74b8a5c0a895ef102c943 --- /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/sensors/Makefile b/drivers/media/video/msm/sensors/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ea36bf6cef00983f95ac19370a70b7c49805ddfb --- /dev/null +++ b/drivers/media/video/msm/sensors/Makefile @@ -0,0 +1,17 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) +EXTRA_CFLAGS += -Idrivers/media/video/msm +EXTRA_CFLAGS += -Idrivers/media/video/msm/io +EXTRA_CFLAGS += -Idrivers/media/video/msm/eeprom +EXTRA_CFLAGS += -Idrivers/media/video/msm/csi +obj-$(CONFIG_MSM_CAMERA_SENSOR) += msm_sensor.o +obj-$(CONFIG_OV5647) += ov5647_v4l2.o +obj-$(CONFIG_IMX074) += imx074_v4l2.o +obj-$(CONFIG_S5K3L1YX) += s5k3l1yx.o +obj-$(CONFIG_IMX091) += imx091.o +obj-$(CONFIG_OV2720) += ov2720.o +obj-$(CONFIG_MT9M114) += mt9m114_v4l2.o +obj-$(CONFIG_S5K4E1) += s5k4e1_v4l2.o +obj-$(CONFIG_MT9E013) += mt9e013_v4l2.o +obj-$(CONFIG_WEBCAM_OV9726) += ov9726_v4l2.o +obj-$(CONFIG_OV7692) += ov7692_v4l2.o +obj-$(CONFIG_VX6953) += vx6953.o diff --git a/drivers/media/video/msm/sensors/imx074_v4l2.c b/drivers/media/video/msm/sensors/imx074_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..7e41418a95d8bc799abcdc2130c8b7761cacbc85 --- /dev/null +++ b/drivers/media/video/msm/sensors/imx074_v4l2.c @@ -0,0 +1,321 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "imx074" +#define PLATFORM_DRIVER_NAME "msm_camera_imx074" +#define imx074_obj imx074_##obj + +DEFINE_MUTEX(imx074_mut); +static struct msm_sensor_ctrl_t imx074_s_ctrl; + +static struct msm_camera_i2c_reg_conf imx074_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf imx074_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf imx074_groupon_settings[] = { + {0x104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf imx074_groupoff_settings[] = { + {0x104, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf imx074_prev_settings[] = { + {0x0307, 0x2D}, /*pll_multiplier*/ + {0x0340, 0x06}, /*frame_length_lines_hi*/ + {0x0341, 0x34}, /*frame_length_lines_lo*/ + {0x0342, 0x11}, /*line_length_pclk_hi*/ + {0x0343, 0x78}, /*line_length_pclk_lo*/ + {0x0347, 0x00}, /*y_addr_start*/ + {0x034b, 0x2F}, /*y_add_end*/ + {0x034c, 0x08}, /*x_output_size_msb*/ + {0x034d, 0x38}, /*x_output_size_lsb*/ + {0x034e, 0x06}, /*y_output_size_msb*/ + {0x034f, 0x18}, /*y_output_size_lsb*/ + {0x0381, 0x01}, /*x_even_inc*/ + {0x0383, 0x03}, /*x_odd_inc*/ + {0x0385, 0x01}, /*y_even_inc*/ + {0x0387, 0x03}, /*y_odd_inc*/ + {0x3001, 0x80}, /*hmodeadd*/ + {0x3016, 0x16}, /*vmodeadd*/ + {0x3069, 0x24}, /*vapplinepos_start*/ + {0x306b, 0x53}, /*vapplinepos_end*/ + {0x3086, 0x00}, /*shutter*/ + {0x30e8, 0x80}, /*haddave*/ + {0x3301, 0x83}, /*lanesel*/ +}; + +static struct msm_camera_i2c_reg_conf imx074_snap_settings[] = { + {0x0307, 0x26}, /*pll_multiplier*/ + {0x0340, 0x0C}, /*frame_length_lines_hi*/ + {0x0341, 0x90}, /*frame_length_lines_lo*/ + {0x0342, 0x11}, /*line_length_pclk_hi*/ + {0x0343, 0x78}, /*line_length_pclk_lo*/ + {0x0347, 0x00}, /*y_addr_start*/ + {0x034b, 0x2F}, /*y_add_end*/ + {0x034c, 0x10}, /*x_output_size_msb*/ + {0x034d, 0x70}, /*x_output_size_lsb*/ + {0x034e, 0x0c}, /*y_output_size_msb*/ + {0x034f, 0x30}, /*y_output_size_lsb*/ + {0x0381, 0x01}, /*x_even_inc*/ + {0x0383, 0x01}, /*x_odd_inc*/ + {0x0385, 0x01}, /*y_even_inc*/ + {0x0387, 0x01}, /*y_odd_inc*/ + {0x3001, 0x00}, /*hmodeadd*/ + {0x3016, 0x06}, /*vmodeadd*/ + {0x3069, 0x24}, /*vapplinepos_start*/ + {0x306b, 0x53}, /*vapplinepos_end*/ + {0x3086, 0x00}, /*shutter*/ + {0x30e8, 0x00}, /*haddave*/ + {0x3301, 0x03}, /*lanesel*/ +}; + +static struct msm_camera_i2c_reg_conf imx074_recommend_settings[] = { + {0x0305, 0x02}, + {0x302b, 0x4B}, + {0x3024, 0x03}, + {0x0101, 0x00}, + {0x300a, 0x80}, + {0x3014, 0x08}, + {0x3015, 0x37}, + {0x301c, 0x01}, + {0x302c, 0x05}, + {0x3031, 0x26}, + {0x3041, 0x60}, + {0x3051, 0x24}, + {0x3053, 0x34}, + {0x3057, 0xc0}, + {0x305c, 0x09}, + {0x305d, 0x07}, + {0x3060, 0x30}, + {0x3065, 0x00}, + {0x30aa, 0x08}, + {0x30ab, 0x1c}, + {0x30b0, 0x32}, + {0x30b2, 0x83}, + {0x30d3, 0x04}, + {0x3106, 0x78}, + {0x310c, 0x82}, + {0x3304, 0x05}, + {0x3305, 0x04}, + {0x3306, 0x11}, + {0x3307, 0x02}, + {0x3308, 0x0c}, + {0x3309, 0x06}, + {0x330a, 0x08}, + {0x330b, 0x04}, + {0x330c, 0x08}, + {0x330d, 0x06}, + {0x330f, 0x01}, + {0x3381, 0x00}, +}; + +static struct v4l2_subdev_info 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 struct msm_camera_i2c_conf_array imx074_init_conf[] = { + {&imx074_recommend_settings[0], + ARRAY_SIZE(imx074_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array imx074_confs[] = { + {&imx074_snap_settings[0], + ARRAY_SIZE(imx074_snap_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&imx074_prev_settings[0], + ARRAY_SIZE(imx074_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t imx074_dimensions[] = { + { + .x_output = 0x1070, + .y_output = 0xC30, + .line_length_pclk = 0x1178, + .frame_length_lines = 0xC90, + .vt_pixel_clk = 182400000, + .op_pixel_clk = 182400000, + .binning_factor = 1, + }, + { + .x_output = 0x838, + .y_output = 0x618, + .line_length_pclk = 0x1178, + .frame_length_lines = 0x634, + .vt_pixel_clk = 216000000, + .op_pixel_clk = 108000000, + .binning_factor = 2, + }, +}; + +static struct msm_camera_csi_params imx074_csic_params = { + .data_format = CSI_10BIT, + .lane_cnt = 4, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 0x14, +}; + +static struct msm_camera_csi_params *imx074_csic_params_array[] = { + &imx074_csic_params, + &imx074_csic_params, +}; + +static struct msm_camera_csid_vc_cfg imx074_cid_cfg[] = { + {0, CSI_RAW10, CSI_DECODE_10BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, + {2, CSI_RESERVED_DATA_0, CSI_DECODE_8BIT}, +}; + +static struct msm_camera_csi2_params imx074_csi_params = { + .csid_params = { + .lane_cnt = 4, + .lut_params = { + .num_cid = ARRAY_SIZE(imx074_cid_cfg), + .vc_cfg = imx074_cid_cfg, + }, + }, + .csiphy_params = { + .lane_cnt = 4, + .settle_cnt = 0x1B, + }, +}; + +static struct msm_camera_csi2_params *imx074_csi_params_array[] = { + &imx074_csi_params, + &imx074_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t imx074_reg_addr = { + .x_output = 0x34C, + .y_output = 0x34E, + .line_length_pclk = 0x342, + .frame_length_lines = 0x340, +}; + +static struct msm_sensor_id_info_t imx074_id_info = { + .sensor_id_reg_addr = 0x0, + .sensor_id = 0x0074, +}; + +static struct msm_sensor_exp_gain_info_t imx074_exp_gain_info = { + .coarse_int_time_addr = 0x202, + .global_gain_addr = 0x204, + .vert_offset = 3, +}; + +static const struct i2c_device_id imx074_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&imx074_s_ctrl}, + { } +}; + +static struct i2c_driver imx074_i2c_driver = { + .id_table = imx074_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client imx074_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&imx074_i2c_driver); +} + +static struct v4l2_subdev_core_ops imx074_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops imx074_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops imx074_subdev_ops = { + .core = &imx074_subdev_core_ops, + .video = &imx074_subdev_video_ops, +}; + +static struct msm_sensor_fn_t imx074_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = msm_sensor_write_exp_gain1, + .sensor_write_snapshot_exp_gain = msm_sensor_write_exp_gain1, + .sensor_setting = msm_sensor_setting, + .sensor_csi_setting = msm_sensor_setting1, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, + .sensor_adjust_frame_lines = msm_sensor_adjust_frame_lines, +}; + +static struct msm_sensor_reg_t imx074_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = imx074_start_settings, + .start_stream_conf_size = ARRAY_SIZE(imx074_start_settings), + .stop_stream_conf = imx074_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(imx074_stop_settings), + .group_hold_on_conf = imx074_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(imx074_groupon_settings), + .group_hold_off_conf = imx074_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(imx074_groupoff_settings), + .init_settings = &imx074_init_conf[0], + .init_size = ARRAY_SIZE(imx074_init_conf), + .mode_settings = &imx074_confs[0], + .output_settings = &imx074_dimensions[0], + .num_conf = ARRAY_SIZE(imx074_confs), +}; + +static struct msm_sensor_ctrl_t imx074_s_ctrl = { + .msm_sensor_reg = &imx074_regs, + .sensor_i2c_client = &imx074_sensor_i2c_client, + .sensor_i2c_addr = 0x34, + .sensor_output_reg_addr = &imx074_reg_addr, + .sensor_id_info = &imx074_id_info, + .sensor_exp_gain_info = &imx074_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &imx074_csic_params_array[0], + .csi_params = &imx074_csi_params_array[0], + .msm_sensor_mutex = &imx074_mut, + .sensor_i2c_driver = &imx074_i2c_driver, + .sensor_v4l2_subdev_info = imx074_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(imx074_subdev_info), + .sensor_v4l2_subdev_ops = &imx074_subdev_ops, + .func_tbl = &imx074_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Sony 13MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sensors/imx091.c b/drivers/media/video/msm/sensors/imx091.c new file mode 100644 index 0000000000000000000000000000000000000000..70c3f6e29b27614cef661504772da88396b066a9 --- /dev/null +++ b/drivers/media/video/msm/sensors/imx091.c @@ -0,0 +1,346 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "imx091" +#define PLATFORM_DRIVER_NAME "msm_camera_imx091" +#define imx091_obj imx091_##obj + +DEFINE_MUTEX(imx091_mut); +static struct msm_sensor_ctrl_t imx091_s_ctrl; + +static struct msm_camera_i2c_reg_conf imx091_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf imx091_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf imx091_groupon_settings[] = { + {0x0104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf imx091_groupoff_settings[] = { + {0x0104, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf imx091_prev_settings[] = { + /* 30fps 1/2 * 1/2 */ + /* PLL setting */ + {0x0305, 0x02}, /* pre_pll_clk_div[7:0] */ + {0x0307, 0x2F}, /* pll_multiplier[7:0] */ + {0x30A4, 0x02}, + {0x303C, 0x4B}, + /* mode setting */ + {0x0340, 0x06}, /* frame_length_lines[15:8] */ + {0x0341, 0x5A}, /* frame_length_lines[7:0] */ + {0x0342, 0x12}, /* line_length_pck[15:8] */ + {0x0343, 0x0C}, /* line_length_pck[7:0] */ + {0x0344, 0x00}, /* x_addr_start[15:8] */ + {0x0345, 0x08}, /* x_addr_start[7:0] */ + {0x0346, 0x00}, /* y_addr_start[15:8] */ + {0x0347, 0x30}, /* y_addr_start[7:0] */ + {0x0348, 0x10}, /* x_addr_end[15:8] */ + {0x0349, 0x77}, /* x_addr_end[7:0] */ + {0x034A, 0x0C}, /* y_addr_end[15:8] */ + {0x034B, 0x5F}, /* y_addr_end[7:0] */ + {0x034C, 0x08}, /* x_output_size[15:8] */ + {0x034D, 0x38}, /* x_output_size[7:0] */ + {0x034E, 0x06}, /* y_output_size[15:8] */ + {0x034F, 0x18}, /* y_output_size[7:0] */ + {0x0381, 0x01}, /* x_even_inc[3:0] */ + {0x0383, 0x03}, /* x_odd_inc[3:0] */ + {0x0385, 0x01}, /* y_even_inc[7:0] */ + {0x0387, 0x03}, /* y_odd_inc[7:0] */ + {0x3040, 0x08}, + {0x3041, 0x97}, + {0x3048, 0x01}, + {0x3064, 0x12}, + {0x309B, 0x28}, + {0x309E, 0x00}, + {0x30D5, 0x09}, + {0x30D6, 0x01}, + {0x30D7, 0x01}, + {0x30D8, 0x64}, + {0x30D9, 0x89}, + {0x30DE, 0x02}, + {0x3102, 0x10}, + {0x3103, 0x44}, + {0x3104, 0x40}, + {0x3105, 0x00}, + {0x3106, 0x0D}, + {0x3107, 0x01}, + {0x310A, 0x0A}, + {0x315C, 0x99}, + {0x315D, 0x98}, + {0x316E, 0x9A}, + {0x316F, 0x99}, + {0x3318, 0x73}, +}; + +static struct msm_camera_i2c_reg_conf imx091_snap_settings[] = { + /* full size */ + /* PLL setting */ + {0x0305, 0x02}, /* pre_pll_clk_div[7:0] */ + {0x0307, 0x2B}, /* pll_multiplier[7:0] */ + {0x30A4, 0x02}, + {0x303C, 0x4B}, + /* mode setting */ + {0x0340, 0x0C}, /* frame_length_lines[15:8] */ + {0x0341, 0x8C}, /* frame_length_lines[7:0] */ + {0x0342, 0x12}, /* line_length_pck[15:8] */ + {0x0343, 0x0C}, /* line_length_pck[7:0] */ + {0x0344, 0x00}, /* x_addr_start[15:8] */ + {0x0345, 0x08}, /* x_addr_start[7:0] */ + {0x0346, 0x00}, /* y_addr_start[15:8] */ + {0x0347, 0x30}, /* y_addr_start[7:0] */ + {0x0348, 0x10}, /* x_addr_end[15:8] */ + {0x0349, 0x77}, /* x_addr_end[7:0] */ + {0x034A, 0x0C}, /* y_addr_end[15:8] */ + {0x034B, 0x5F}, /* y_addr_end[7:0] */ + {0x034C, 0x10}, /* x_output_size[15:8] */ + {0x034D, 0x70}, /* x_output_size[7:0] */ + {0x034E, 0x0C}, /* y_output_size[15:8] */ + {0x034F, 0x30}, /* y_output_size[7:0] */ + {0x0381, 0x01}, /* x_even_inc[3:0] */ + {0x0383, 0x01}, /* x_odd_inc[3:0] */ + {0x0385, 0x01}, /* y_even_inc[7:0] */ + {0x0387, 0x01}, /* y_odd_inc[7:0] */ + {0x3040, 0x08}, + {0x3041, 0x97}, + {0x3048, 0x00}, + {0x3064, 0x12}, + {0x309B, 0x20}, + {0x309E, 0x00}, + {0x30D5, 0x00}, + {0x30D6, 0x85}, + {0x30D7, 0x2A}, + {0x30D8, 0x64}, + {0x30D9, 0x89}, + {0x30DE, 0x00}, + {0x3102, 0x10}, + {0x3103, 0x44}, + {0x3104, 0x40}, + {0x3105, 0x00}, + {0x3106, 0x0D}, + {0x3107, 0x01}, + {0x310A, 0x0A}, + {0x315C, 0x99}, + {0x315D, 0x98}, + {0x316E, 0x9A}, + {0x316F, 0x99}, + {0x3318, 0x64}, +}; + +static struct msm_camera_i2c_reg_conf imx091_recommend_settings[] = { + /* global setting */ + {0x3087, 0x53}, + {0x309D, 0x94}, + {0x30A1, 0x08}, + {0x30C7, 0x00}, + {0x3115, 0x0E}, + {0x3118, 0x42}, + {0x311D, 0x34}, + {0x3121, 0x0D}, + {0x3212, 0xF2}, + {0x3213, 0x0F}, + {0x3215, 0x0F}, + {0x3217, 0x0B}, + {0x3219, 0x0B}, + {0x321B, 0x0D}, + {0x321D, 0x0D}, + /* black level setting */ + {0x3032, 0x40}, +}; + +static struct v4l2_subdev_info imx091_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_camera_i2c_conf_array imx091_init_conf[] = { + {&imx091_recommend_settings[0], + ARRAY_SIZE(imx091_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array imx091_confs[] = { + {&imx091_snap_settings[0], + ARRAY_SIZE(imx091_snap_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&imx091_prev_settings[0], + ARRAY_SIZE(imx091_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t imx091_dimensions[] = { + { + /* full size */ + .x_output = 0x1070, /* 4208 */ + .y_output = 0x0C30, /* 3120 */ + .line_length_pclk = 0x120C, /* 4620 */ + .frame_length_lines = 0x0C8C, /* 3212 */ + .vt_pixel_clk = 206400000, + .op_pixel_clk = 206400000, + .binning_factor = 1, + }, + { + /* 30 fps 1/2 * 1/2 */ + .x_output = 0x0838, /* 2104 */ + .y_output = 0x0618, /* 1560 */ + .line_length_pclk = 0x120C, /* 4620 */ + .frame_length_lines = 0x065A, /* 1626 */ + .vt_pixel_clk = 225600000, + .op_pixel_clk = 225600000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csid_vc_cfg imx091_cid_cfg[] = { + {0, CSI_RAW10, CSI_DECODE_10BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, + {2, CSI_RESERVED_DATA_0, CSI_DECODE_8BIT}, +}; + +static struct msm_camera_csi2_params imx091_csi_params = { + .csid_params = { + .lane_cnt = 4, + .lut_params = { + .num_cid = ARRAY_SIZE(imx091_cid_cfg), + .vc_cfg = imx091_cid_cfg, + }, + }, + .csiphy_params = { + .lane_cnt = 4, + .settle_cnt = 0x12, + }, +}; + +static struct msm_camera_csi2_params *imx091_csi_params_array[] = { + &imx091_csi_params, + &imx091_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t imx091_reg_addr = { + .x_output = 0x034C, + .y_output = 0x034E, + .line_length_pclk = 0x0342, + .frame_length_lines = 0x0340, +}; + +static struct msm_sensor_id_info_t imx091_id_info = { + .sensor_id_reg_addr = 0x0000, + .sensor_id = 0x0091, +}; + +static struct msm_sensor_exp_gain_info_t imx091_exp_gain_info = { + .coarse_int_time_addr = 0x0202, + .global_gain_addr = 0x0204, + .vert_offset = 5, +}; + +static const struct i2c_device_id imx091_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&imx091_s_ctrl}, + { } +}; + +static struct i2c_driver imx091_i2c_driver = { + .id_table = imx091_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client imx091_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + + +static int __init imx091_sensor_init_module(void) +{ + return i2c_add_driver(&imx091_i2c_driver); +} + +static struct v4l2_subdev_core_ops imx091_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops imx091_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops imx091_subdev_ops = { + .core = &imx091_subdev_core_ops, + .video = &imx091_subdev_video_ops, +}; + +static struct msm_sensor_fn_t imx091_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = msm_sensor_write_exp_gain1, + .sensor_write_snapshot_exp_gain = msm_sensor_write_exp_gain1, + .sensor_setting = msm_sensor_setting, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, + .sensor_adjust_frame_lines = msm_sensor_adjust_frame_lines, +}; + +static struct msm_sensor_reg_t imx091_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = imx091_start_settings, + .start_stream_conf_size = ARRAY_SIZE(imx091_start_settings), + .stop_stream_conf = imx091_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(imx091_stop_settings), + .group_hold_on_conf = imx091_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(imx091_groupon_settings), + .group_hold_off_conf = imx091_groupoff_settings, + .group_hold_off_conf_size = ARRAY_SIZE(imx091_groupoff_settings), + .init_settings = &imx091_init_conf[0], + .init_size = ARRAY_SIZE(imx091_init_conf), + .mode_settings = &imx091_confs[0], + .output_settings = &imx091_dimensions[0], + .num_conf = ARRAY_SIZE(imx091_confs), +}; + +static struct msm_sensor_ctrl_t imx091_s_ctrl = { + .msm_sensor_reg = &imx091_regs, + .sensor_i2c_client = &imx091_sensor_i2c_client, + .sensor_i2c_addr = 0x34, + .sensor_output_reg_addr = &imx091_reg_addr, + .sensor_id_info = &imx091_id_info, + .sensor_exp_gain_info = &imx091_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csi_params = &imx091_csi_params_array[0], + .msm_sensor_mutex = &imx091_mut, + .sensor_i2c_driver = &imx091_i2c_driver, + .sensor_v4l2_subdev_info = imx091_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(imx091_subdev_info), + .sensor_v4l2_subdev_ops = &imx091_subdev_ops, + .func_tbl = &imx091_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(imx091_sensor_init_module); +MODULE_DESCRIPTION("SONY 12MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sensors/msm_sensor.c b/drivers/media/video/msm/sensors/msm_sensor.c new file mode 100644 index 0000000000000000000000000000000000000000..5b9eb31d4c10aa267c0de683a8f619af365f9f0c --- /dev/null +++ b/drivers/media/video/msm/sensors/msm_sensor.c @@ -0,0 +1,880 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#include "msm.h" +#include "msm_ispif.h" +#include "msm_camera_i2c_mux.h" + +/*=============================================================*/ +int32_t msm_sensor_adjust_frame_lines(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res) +{ + uint16_t cur_line = 0; + uint16_t exp_fl_lines = 0; + if (s_ctrl->sensor_exp_gain_info) { + if (s_ctrl->prev_gain && s_ctrl->prev_line && + s_ctrl->func_tbl->sensor_write_exp_gain) + s_ctrl->func_tbl->sensor_write_exp_gain( + s_ctrl, + s_ctrl->prev_gain, + s_ctrl->prev_line); + + msm_camera_i2c_read(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + &cur_line, + MSM_CAMERA_I2C_WORD_DATA); + exp_fl_lines = cur_line + + s_ctrl->sensor_exp_gain_info->vert_offset; + if (exp_fl_lines > s_ctrl->msm_sensor_reg-> + output_settings[res].frame_length_lines) + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr-> + frame_length_lines, + exp_fl_lines, + MSM_CAMERA_I2C_WORD_DATA); + CDBG("%s cur_fl_lines %d, exp_fl_lines %d\n", __func__, + s_ctrl->msm_sensor_reg-> + output_settings[res].frame_length_lines, + exp_fl_lines); + } + return 0; +} + +int32_t msm_sensor_write_init_settings(struct msm_sensor_ctrl_t *s_ctrl) +{ + int32_t rc; + rc = msm_sensor_write_all_conf_array( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->init_settings, + s_ctrl->msm_sensor_reg->init_size); + return rc; +} + +int32_t msm_sensor_write_res_settings(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res) +{ + int32_t rc; + rc = msm_sensor_write_conf_array( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->mode_settings, res); + if (rc < 0) + return rc; + + rc = msm_sensor_write_output_settings(s_ctrl, res); + if (rc < 0) + return rc; + + if (s_ctrl->func_tbl->sensor_adjust_frame_lines) + rc = s_ctrl->func_tbl->sensor_adjust_frame_lines(s_ctrl, res); + + return rc; +} + +int32_t msm_sensor_write_output_settings(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res) +{ + int32_t rc = -EFAULT; + struct msm_camera_i2c_reg_conf dim_settings[] = { + {s_ctrl->sensor_output_reg_addr->x_output, + s_ctrl->msm_sensor_reg-> + output_settings[res].x_output}, + {s_ctrl->sensor_output_reg_addr->y_output, + s_ctrl->msm_sensor_reg-> + output_settings[res].y_output}, + {s_ctrl->sensor_output_reg_addr->line_length_pclk, + s_ctrl->msm_sensor_reg-> + output_settings[res].line_length_pclk}, + {s_ctrl->sensor_output_reg_addr->frame_length_lines, + s_ctrl->msm_sensor_reg-> + output_settings[res].frame_length_lines}, + }; + + rc = msm_camera_i2c_write_tbl(s_ctrl->sensor_i2c_client, dim_settings, + ARRAY_SIZE(dim_settings), MSM_CAMERA_I2C_WORD_DATA); + return rc; +} + +void msm_sensor_start_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write_tbl( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->start_stream_conf, + s_ctrl->msm_sensor_reg->start_stream_conf_size, + s_ctrl->msm_sensor_reg->default_data_type); +} + +void msm_sensor_stop_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write_tbl( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->stop_stream_conf, + s_ctrl->msm_sensor_reg->stop_stream_conf_size, + s_ctrl->msm_sensor_reg->default_data_type); +} + +void msm_sensor_group_hold_on(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write_tbl( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->group_hold_on_conf, + s_ctrl->msm_sensor_reg->group_hold_on_conf_size, + s_ctrl->msm_sensor_reg->default_data_type); +} + +void msm_sensor_group_hold_off(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write_tbl( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->group_hold_off_conf, + s_ctrl->msm_sensor_reg->group_hold_off_conf_size, + s_ctrl->msm_sensor_reg->default_data_type); +} + +int32_t msm_sensor_set_fps(struct msm_sensor_ctrl_t *s_ctrl, + struct fps_cfg *fps) +{ + s_ctrl->fps_divider = fps->fps_div; + + return 0; +} + +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; + fl_lines = (fl_lines * s_ctrl->fps_divider) / Q10; + offset = s_ctrl->sensor_exp_gain_info->vert_offset; + if (line > (fl_lines - offset)) + fl_lines = line + offset; + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, fl_lines, + MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, line, + MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, gain, + MSM_CAMERA_I2C_WORD_DATA); + 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 * s_ctrl->fps_divider / Q10; + ll_pclk = s_ctrl->curr_line_length_pclk; + offset = s_ctrl->sensor_exp_gain_info->vert_offset; + if (line > (fl_lines - offset)) { + ll_ratio = (line * Q10) / (fl_lines - offset); + ll_pclk = ll_pclk * ll_ratio / Q10; + line = fl_lines - offset; + } + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->line_length_pclk, ll_pclk, + MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, line, + MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, gain, + MSM_CAMERA_I2C_WORD_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + return 0; +} + +int32_t msm_sensor_setting1(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int res) +{ + int32_t rc = 0; + static int csi_config; + + s_ctrl->func_tbl->sensor_stop_stream(s_ctrl); + msleep(30); + if (update_type == MSM_SENSOR_REG_INIT) { + CDBG("Register INIT\n"); + s_ctrl->curr_csi_params = NULL; + msm_sensor_enable_debugfs(s_ctrl); + msm_sensor_write_init_settings(s_ctrl); + csi_config = 0; + } else if (update_type == MSM_SENSOR_UPDATE_PERIODIC) { + CDBG("PERIODIC : %d\n", res); + msm_sensor_write_conf_array( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->mode_settings, res); + msleep(30); + if (!csi_config) { + s_ctrl->curr_csic_params = s_ctrl->csic_params[res]; + CDBG("CSI config in progress\n"); + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_CSIC_CFG, + s_ctrl->curr_csic_params); + CDBG("CSI config is done\n"); + mb(); + msleep(30); + csi_config = 1; + } + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_PCLK_CHANGE, + &s_ctrl->sensordata->pdata->ioclk.vfe_clk_rate); + + s_ctrl->func_tbl->sensor_start_stream(s_ctrl); + msleep(50); + } + return rc; +} +int32_t msm_sensor_setting(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int res) +{ + int32_t rc = 0; + + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_ISPIF_STREAM, (void *)ISPIF_STREAM( + PIX_0, ISPIF_OFF_IMMEDIATELY)); + s_ctrl->func_tbl->sensor_stop_stream(s_ctrl); + msleep(30); + if (update_type == MSM_SENSOR_REG_INIT) { + s_ctrl->curr_csi_params = NULL; + msm_sensor_enable_debugfs(s_ctrl); + msm_sensor_write_init_settings(s_ctrl); + } else if (update_type == MSM_SENSOR_UPDATE_PERIODIC) { + msm_sensor_write_res_settings(s_ctrl, res); + if (s_ctrl->curr_csi_params != s_ctrl->csi_params[res]) { + s_ctrl->curr_csi_params = s_ctrl->csi_params[res]; + s_ctrl->curr_csi_params->csid_params.lane_assign = + s_ctrl->sensordata->sensor_platform_info-> + csi_lane_params->csi_lane_assign; + s_ctrl->curr_csi_params->csiphy_params.lane_mask = + s_ctrl->sensordata->sensor_platform_info-> + csi_lane_params->csi_lane_mask; + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_CSID_CFG, + &s_ctrl->curr_csi_params->csid_params); + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_CID_CHANGE, NULL); + mb(); + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_CSIPHY_CFG, + &s_ctrl->curr_csi_params->csiphy_params); + mb(); + msleep(20); + } + + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_PCLK_CHANGE, &s_ctrl->msm_sensor_reg-> + output_settings[res].op_pixel_clk); + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_ISPIF_STREAM, (void *)ISPIF_STREAM( + PIX_0, ISPIF_ON_FRAME_BOUNDARY)); + s_ctrl->func_tbl->sensor_start_stream(s_ctrl); + msleep(30); + } + return rc; +} + +int32_t msm_sensor_set_sensor_mode(struct msm_sensor_ctrl_t *s_ctrl, + int mode, int res) +{ + int32_t rc = 0; + if (s_ctrl->curr_res != res) { + s_ctrl->curr_frame_length_lines = + s_ctrl->msm_sensor_reg-> + output_settings[res].frame_length_lines; + + s_ctrl->curr_line_length_pclk = + s_ctrl->msm_sensor_reg-> + output_settings[res].line_length_pclk; + + if (s_ctrl->sensordata->pdata->is_csic || + !s_ctrl->sensordata->csi_if) + rc = s_ctrl->func_tbl->sensor_csi_setting(s_ctrl, + MSM_SENSOR_UPDATE_PERIODIC, res); + else + rc = s_ctrl->func_tbl->sensor_setting(s_ctrl, + MSM_SENSOR_UPDATE_PERIODIC, res); + if (rc < 0) + return rc; + s_ctrl->curr_res = res; + } + + return rc; +} + +int32_t msm_sensor_mode_init(struct msm_sensor_ctrl_t *s_ctrl, + int mode, struct sensor_init_cfg *init_info) +{ + int32_t rc = 0; + s_ctrl->fps_divider = Q10; + s_ctrl->cam_mode = MSM_SENSOR_MODE_INVALID; + + CDBG("%s: %d\n", __func__, __LINE__); + if (mode != s_ctrl->cam_mode) { + s_ctrl->curr_res = MSM_SENSOR_INVALID_RES; + s_ctrl->cam_mode = mode; + + if (s_ctrl->sensordata->pdata->is_csic || + !s_ctrl->sensordata->csi_if) + rc = s_ctrl->func_tbl->sensor_csi_setting(s_ctrl, + MSM_SENSOR_REG_INIT, 0); + else + rc = s_ctrl->func_tbl->sensor_setting(s_ctrl, + MSM_SENSOR_REG_INIT, 0); + } + return rc; +} + +int32_t msm_sensor_get_output_info(struct msm_sensor_ctrl_t *s_ctrl, + struct sensor_output_info_t *sensor_output_info) +{ + int rc = 0; + sensor_output_info->num_info = s_ctrl->msm_sensor_reg->num_conf; + if (copy_to_user((void *)sensor_output_info->output_info, + s_ctrl->msm_sensor_reg->output_settings, + sizeof(struct msm_sensor_output_info_t) * + s_ctrl->msm_sensor_reg->num_conf)) + rc = -EFAULT; + + return rc; +} + +int32_t msm_sensor_release(struct msm_sensor_ctrl_t *s_ctrl) +{ + long fps = 0; + uint32_t delay = 0; + CDBG("%s called\n", __func__); + s_ctrl->func_tbl->sensor_stop_stream(s_ctrl); + if (s_ctrl->curr_res != MSM_SENSOR_INVALID_RES) { + fps = s_ctrl->msm_sensor_reg-> + output_settings[s_ctrl->curr_res].vt_pixel_clk / + s_ctrl->curr_frame_length_lines / + s_ctrl->curr_line_length_pclk; + delay = 1000 / fps; + CDBG("%s fps = %ld, delay = %d\n", __func__, fps, delay); + msleep(delay); + } + return 0; +} + +long msm_sensor_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct msm_sensor_ctrl_t *s_ctrl = get_sctrl(sd); + void __user *argp = (void __user *)arg; + switch (cmd) { + case VIDIOC_MSM_SENSOR_CFG: + return s_ctrl->func_tbl->sensor_config(s_ctrl, argp); + case VIDIOC_MSM_SENSOR_RELEASE: + return msm_sensor_release(s_ctrl); + default: + return -ENOIOCTLCMD; + } +} + +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_SET_FPS: + case CFG_SET_PICT_FPS: + if (s_ctrl->func_tbl-> + sensor_set_fps == NULL) { + rc = -EFAULT; + break; + } + rc = s_ctrl->func_tbl-> + sensor_set_fps( + s_ctrl, + &(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + if (s_ctrl->func_tbl-> + sensor_write_exp_gain == NULL) { + rc = -EFAULT; + break; + } + rc = + s_ctrl->func_tbl-> + sensor_write_exp_gain( + s_ctrl, + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + s_ctrl->prev_gain = cdata.cfg.exp_gain.gain; + s_ctrl->prev_line = cdata.cfg.exp_gain.line; + break; + + case CFG_SET_PICT_EXP_GAIN: + if (s_ctrl->func_tbl-> + sensor_write_snapshot_exp_gain == NULL) { + rc = -EFAULT; + break; + } + rc = + s_ctrl->func_tbl-> + sensor_write_snapshot_exp_gain( + s_ctrl, + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + if (s_ctrl->func_tbl-> + sensor_set_sensor_mode == NULL) { + rc = -EFAULT; + break; + } + rc = s_ctrl->func_tbl-> + sensor_set_sensor_mode( + s_ctrl, + cdata.mode, + cdata.rs); + break; + + case CFG_SET_EFFECT: + break; + + case CFG_SENSOR_INIT: + if (s_ctrl->func_tbl-> + sensor_mode_init == NULL) { + rc = -EFAULT; + break; + } + rc = s_ctrl->func_tbl-> + sensor_mode_init( + s_ctrl, + cdata.mode, + &(cdata.cfg.init_info)); + break; + + case CFG_GET_OUTPUT_INFO: + if (s_ctrl->func_tbl-> + sensor_get_output_info == NULL) { + rc = -EFAULT; + break; + } + rc = s_ctrl->func_tbl-> + sensor_get_output_info( + s_ctrl, + &cdata.cfg.output_info); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(s_ctrl->msm_sensor_mutex); + + return rc; +} + +static struct msm_cam_clk_info cam_clk_info[] = { + {"cam_clk", MSM_SENSOR_MCLK_24HZ}, +}; + +int32_t msm_sensor_enable_i2c_mux(struct msm_camera_i2c_conf *i2c_conf) +{ + struct v4l2_subdev *i2c_mux_sd = + dev_get_drvdata(&i2c_conf->mux_dev->dev); + v4l2_subdev_call(i2c_mux_sd, core, ioctl, + VIDIOC_MSM_I2C_MUX_INIT, NULL); + v4l2_subdev_call(i2c_mux_sd, core, ioctl, + VIDIOC_MSM_I2C_MUX_CFG, (void *)&i2c_conf->i2c_mux_mode); + return 0; +} + +int32_t msm_sensor_disable_i2c_mux(struct msm_camera_i2c_conf *i2c_conf) +{ + struct v4l2_subdev *i2c_mux_sd = + dev_get_drvdata(&i2c_conf->mux_dev->dev); + v4l2_subdev_call(i2c_mux_sd, core, ioctl, + VIDIOC_MSM_I2C_MUX_RELEASE, NULL); + return 0; +} + +int32_t msm_sensor_power_up(struct msm_sensor_ctrl_t *s_ctrl) +{ + int32_t rc = 0; + struct msm_camera_sensor_info *data = s_ctrl->sensordata; + CDBG("%s: %d\n", __func__, __LINE__); + s_ctrl->reg_ptr = kzalloc(sizeof(struct regulator *) + * data->sensor_platform_info->num_vreg, GFP_KERNEL); + if (!s_ctrl->reg_ptr) { + pr_err("%s: could not allocate mem for regulators\n", + __func__); + return -ENOMEM; + } + + rc = msm_camera_request_gpio_table(data, 1); + if (rc < 0) { + pr_err("%s: request gpio failed\n", __func__); + goto request_gpio_failed; + } + + rc = msm_camera_config_vreg(&s_ctrl->sensor_i2c_client->client->dev, + s_ctrl->sensordata->sensor_platform_info->cam_vreg, + s_ctrl->sensordata->sensor_platform_info->num_vreg, + s_ctrl->reg_ptr, 1); + if (rc < 0) { + pr_err("%s: regulator on failed\n", __func__); + goto config_vreg_failed; + } + + rc = msm_camera_enable_vreg(&s_ctrl->sensor_i2c_client->client->dev, + s_ctrl->sensordata->sensor_platform_info->cam_vreg, + s_ctrl->sensordata->sensor_platform_info->num_vreg, + s_ctrl->reg_ptr, 1); + if (rc < 0) { + pr_err("%s: enable regulator failed\n", __func__); + goto enable_vreg_failed; + } + + rc = msm_camera_config_gpio_table(data, 1); + if (rc < 0) { + pr_err("%s: config gpio failed\n", __func__); + goto config_gpio_failed; + } + + if (s_ctrl->clk_rate != 0) + cam_clk_info->clk_rate = s_ctrl->clk_rate; + + rc = msm_cam_clk_enable(&s_ctrl->sensor_i2c_client->client->dev, + cam_clk_info, &s_ctrl->cam_clk, ARRAY_SIZE(cam_clk_info), 1); + if (rc < 0) { + pr_err("%s: clk enable failed\n", __func__); + goto enable_clk_failed; + } + + usleep_range(1000, 2000); + if (data->sensor_platform_info->ext_power_ctrl != NULL) + data->sensor_platform_info->ext_power_ctrl(1); + + if (data->sensor_platform_info->i2c_conf && + data->sensor_platform_info->i2c_conf->use_i2c_mux) + msm_sensor_enable_i2c_mux(data->sensor_platform_info->i2c_conf); + + return rc; + +enable_clk_failed: + msm_camera_config_gpio_table(data, 0); +config_gpio_failed: + msm_camera_enable_vreg(&s_ctrl->sensor_i2c_client->client->dev, + s_ctrl->sensordata->sensor_platform_info->cam_vreg, + s_ctrl->sensordata->sensor_platform_info->num_vreg, + s_ctrl->reg_ptr, 0); + +enable_vreg_failed: + msm_camera_config_vreg(&s_ctrl->sensor_i2c_client->client->dev, + s_ctrl->sensordata->sensor_platform_info->cam_vreg, + s_ctrl->sensordata->sensor_platform_info->num_vreg, + s_ctrl->reg_ptr, 0); +config_vreg_failed: + msm_camera_request_gpio_table(data, 0); +request_gpio_failed: + kfree(s_ctrl->reg_ptr); + return rc; +} + +int32_t msm_sensor_power_down(struct msm_sensor_ctrl_t *s_ctrl) +{ + struct msm_camera_sensor_info *data = s_ctrl->sensordata; + CDBG("%s\n", __func__); + if (data->sensor_platform_info->i2c_conf && + data->sensor_platform_info->i2c_conf->use_i2c_mux) + msm_sensor_disable_i2c_mux( + data->sensor_platform_info->i2c_conf); + + if (data->sensor_platform_info->ext_power_ctrl != NULL) + data->sensor_platform_info->ext_power_ctrl(0); + msm_cam_clk_enable(&s_ctrl->sensor_i2c_client->client->dev, + cam_clk_info, &s_ctrl->cam_clk, ARRAY_SIZE(cam_clk_info), 0); + msm_camera_config_gpio_table(data, 0); + msm_camera_enable_vreg(&s_ctrl->sensor_i2c_client->client->dev, + s_ctrl->sensordata->sensor_platform_info->cam_vreg, + s_ctrl->sensordata->sensor_platform_info->num_vreg, + s_ctrl->reg_ptr, 0); + msm_camera_config_vreg(&s_ctrl->sensor_i2c_client->client->dev, + s_ctrl->sensordata->sensor_platform_info->cam_vreg, + s_ctrl->sensordata->sensor_platform_info->num_vreg, + s_ctrl->reg_ptr, 0); + msm_camera_request_gpio_table(data, 0); + kfree(s_ctrl->reg_ptr); + return 0; +} + +int32_t msm_sensor_match_id(struct msm_sensor_ctrl_t *s_ctrl) +{ + int32_t rc = 0; + uint16_t chipid = 0; + rc = msm_camera_i2c_read( + s_ctrl->sensor_i2c_client, + s_ctrl->sensor_id_info->sensor_id_reg_addr, &chipid, + MSM_CAMERA_I2C_WORD_DATA); + if (rc < 0) { + pr_err("%s: %s: read id failed\n", __func__, + s_ctrl->sensordata->sensor_name); + return rc; + } + + CDBG("msm_sensor id: %d\n", chipid); + if (chipid != s_ctrl->sensor_id_info->sensor_id) { + pr_err("msm_sensor_match_id chip id doesnot match\n"); + return -ENODEV; + } + return rc; +} + +struct msm_sensor_ctrl_t *get_sctrl(struct v4l2_subdev *sd) +{ + return container_of(sd, struct msm_sensor_ctrl_t, sensor_v4l2_subdev); +} + +int32_t msm_sensor_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + struct msm_sensor_ctrl_t *s_ctrl; + CDBG("%s %s_i2c_probe called\n", __func__, client->name); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s %s i2c_check_functionality failed\n", + __func__, client->name); + rc = -EFAULT; + return rc; + } + + s_ctrl = (struct msm_sensor_ctrl_t *)(id->driver_data); + if (s_ctrl->sensor_i2c_client != NULL) { + s_ctrl->sensor_i2c_client->client = client; + if (s_ctrl->sensor_i2c_addr != 0) + s_ctrl->sensor_i2c_client->client->addr = + s_ctrl->sensor_i2c_addr; + } else { + pr_err("%s %s sensor_i2c_client NULL\n", + __func__, client->name); + rc = -EFAULT; + return rc; + } + + s_ctrl->sensordata = client->dev.platform_data; + if (s_ctrl->sensordata == NULL) { + pr_err("%s %s NULL sensor data\n", __func__, client->name); + return -EFAULT; + } + + rc = s_ctrl->func_tbl->sensor_power_up(s_ctrl); + if (rc < 0) { + pr_err("%s %s power up failed\n", __func__, client->name); + return rc; + } + + if (s_ctrl->func_tbl->sensor_match_id) + rc = s_ctrl->func_tbl->sensor_match_id(s_ctrl); + else + rc = msm_sensor_match_id(s_ctrl); + if (rc < 0) + goto probe_fail; + + snprintf(s_ctrl->sensor_v4l2_subdev.name, + sizeof(s_ctrl->sensor_v4l2_subdev.name), "%s", id->name); + v4l2_i2c_subdev_init(&s_ctrl->sensor_v4l2_subdev, client, + s_ctrl->sensor_v4l2_subdev_ops); + + msm_sensor_register(&s_ctrl->sensor_v4l2_subdev); + goto power_down; +probe_fail: + pr_err("%s %s_i2c_probe failed\n", __func__, client->name); +power_down: + if (rc > 0) + rc = 0; + s_ctrl->func_tbl->sensor_power_down(s_ctrl); + return rc; +} + +int32_t msm_sensor_power(struct v4l2_subdev *sd, int on) +{ + int rc = 0; + struct msm_sensor_ctrl_t *s_ctrl = get_sctrl(sd); + mutex_lock(s_ctrl->msm_sensor_mutex); + if (on) { + rc = s_ctrl->func_tbl->sensor_power_up(s_ctrl); + if (rc < 0) { + pr_err("%s: %s power_up failed rc = %d\n", __func__, + s_ctrl->sensordata->sensor_name, rc); + } else { + if (s_ctrl->func_tbl->sensor_match_id) + rc = s_ctrl->func_tbl->sensor_match_id(s_ctrl); + else + rc = msm_sensor_match_id(s_ctrl); + if (rc < 0) { + pr_err("%s: %s match_id failed rc=%d\n", + __func__, + s_ctrl->sensordata->sensor_name, rc); + if (s_ctrl->func_tbl->sensor_power_down(s_ctrl) + < 0) + pr_err("%s: %s power_down failed\n", + __func__, + s_ctrl->sensordata->sensor_name); + } + } + } else { + rc = s_ctrl->func_tbl->sensor_power_down(s_ctrl); + } + mutex_unlock(s_ctrl->msm_sensor_mutex); + return rc; +} + +int32_t msm_sensor_v4l2_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + struct msm_sensor_ctrl_t *s_ctrl = get_sctrl(sd); + + if ((unsigned int)index >= s_ctrl->sensor_v4l2_subdev_info_size) + return -EINVAL; + + *code = s_ctrl->sensor_v4l2_subdev_info[index].code; + return 0; +} + +int32_t msm_sensor_v4l2_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + int rc = -1, i = 0; + struct msm_sensor_ctrl_t *s_ctrl = + (struct msm_sensor_ctrl_t *) sd->dev_priv; + struct msm_sensor_v4l2_ctrl_info_t *v4l2_ctrl = + s_ctrl->msm_sensor_v4l2_ctrl_info; + + CDBG("%s\n", __func__); + CDBG("%d\n", ctrl->id); + if (v4l2_ctrl == NULL) + return rc; + + for (i = 0; i < s_ctrl->num_v4l2_ctrl; i++) { + if (v4l2_ctrl[i].ctrl_id == ctrl->id) { + if (v4l2_ctrl[i].s_v4l2_ctrl != NULL) { + rc = v4l2_ctrl[i].s_v4l2_ctrl( + s_ctrl, + &s_ctrl->msm_sensor_v4l2_ctrl_info[i], + ctrl->value); + } + break; + } + } + + return rc; +} + +int32_t msm_sensor_v4l2_query_ctrl( + struct v4l2_subdev *sd, struct v4l2_queryctrl *qctrl) +{ + int rc = -1, i = 0; + struct msm_sensor_ctrl_t *s_ctrl = + (struct msm_sensor_ctrl_t *) sd->dev_priv; + + CDBG("%s\n", __func__); + CDBG("%s id: %d\n", __func__, qctrl->id); + + if (s_ctrl->msm_sensor_v4l2_ctrl_info == NULL) + return rc; + + for (i = 0; i < s_ctrl->num_v4l2_ctrl; i++) { + if (s_ctrl->msm_sensor_v4l2_ctrl_info[i].ctrl_id == qctrl->id) { + qctrl->minimum = + s_ctrl->msm_sensor_v4l2_ctrl_info[i].min; + qctrl->maximum = + s_ctrl->msm_sensor_v4l2_ctrl_info[i].max; + qctrl->flags = 1; + rc = 0; + break; + } + } + + return rc; +} + +int msm_sensor_s_ctrl_by_enum(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_v4l2_ctrl_info_t *ctrl_info, int value) +{ + int rc = 0; + CDBG("%s enter\n", __func__); + rc = msm_sensor_write_enum_conf_array( + s_ctrl->sensor_i2c_client, + ctrl_info->enum_cfg_settings, value); + return rc; +} + +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/sensors/msm_sensor.h b/drivers/media/video/msm/sensors/msm_sensor.h new file mode 100644 index 0000000000000000000000000000000000000000..c5fbea26c2bcdf1b511dbc8da934e8e48e075cf8 --- /dev/null +++ b/drivers/media/video/msm/sensors/msm_sensor.h @@ -0,0 +1,255 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SENSOR_H +#define MSM_SENSOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_camera_i2c.h" +#include "msm_camera_eeprom.h" +#define Q8 0x00000100 +#define Q10 0x00000400 + +#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_output_reg_addr_t { + uint16_t x_output; + uint16_t y_output; + uint16_t line_length_pclk; + uint16_t frame_length_lines; +}; + +struct msm_sensor_id_info_t { + uint16_t sensor_id_reg_addr; + uint16_t sensor_id; +}; + +struct msm_sensor_exp_gain_info_t { + uint16_t coarse_int_time_addr; + uint16_t global_gain_addr; + uint16_t vert_offset; +}; + +struct msm_sensor_reg_t { + enum msm_camera_i2c_data_type default_data_type; + struct msm_camera_i2c_reg_conf *start_stream_conf; + uint8_t start_stream_conf_size; + struct msm_camera_i2c_reg_conf *stop_stream_conf; + uint8_t stop_stream_conf_size; + struct msm_camera_i2c_reg_conf *group_hold_on_conf; + uint8_t group_hold_on_conf_size; + struct msm_camera_i2c_reg_conf *group_hold_off_conf; + uint8_t group_hold_off_conf_size; + struct msm_camera_i2c_conf_array *init_settings; + uint8_t init_size; + struct msm_camera_i2c_conf_array *mode_settings; + struct msm_sensor_output_info_t *output_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; + +struct msm_sensor_v4l2_ctrl_info_t { + uint32_t ctrl_id; + int16_t min; + int16_t max; + int16_t step; + struct msm_camera_i2c_enum_conf_array *enum_cfg_settings; + int (*s_v4l2_ctrl) (struct msm_sensor_ctrl_t *, + struct msm_sensor_v4l2_ctrl_info_t *, int); +}; + +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 *); + + 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_write_snapshot_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_csi_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 *); + int32_t (*sensor_get_output_info) (struct msm_sensor_ctrl_t *, + struct sensor_output_info_t *); + int (*sensor_config) (struct msm_sensor_ctrl_t *, void __user *); + int (*sensor_power_down) + (struct msm_sensor_ctrl_t *); + int (*sensor_power_up) (struct msm_sensor_ctrl_t *); + int32_t (*sensor_match_id)(struct msm_sensor_ctrl_t *s_ctrl); + int (*sensor_adjust_frame_lines) + (struct msm_sensor_ctrl_t *s_ctrl, uint16_t res); +}; + +struct msm_sensor_ctrl_t { + struct msm_camera_sensor_info *sensordata; + struct i2c_client *msm_sensor_client; + struct i2c_driver *sensor_i2c_driver; + struct msm_camera_i2c_client *sensor_i2c_client; + uint16_t sensor_i2c_addr; + + struct msm_sensor_output_reg_addr_t *sensor_output_reg_addr; + struct msm_sensor_id_info_t *sensor_id_info; + struct msm_sensor_exp_gain_info_t *sensor_exp_gain_info; + struct msm_sensor_reg_t *msm_sensor_reg; + struct msm_sensor_v4l2_ctrl_info_t *msm_sensor_v4l2_ctrl_info; + uint16_t num_v4l2_ctrl; + + uint16_t curr_line_length_pclk; + uint16_t curr_frame_length_lines; + uint16_t prev_gain; + uint16_t prev_line; + + uint32_t fps_divider; + enum msm_sensor_resolution_t curr_res; + enum msm_sensor_cam_mode_t cam_mode; + + struct mutex *msm_sensor_mutex; + struct msm_camera_csi2_params *curr_csi_params; + struct msm_camera_csi2_params **csi_params; + struct msm_camera_csi_params **csic_params; + struct msm_camera_csi_params *curr_csic_params; + + 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 *func_tbl; + struct regulator **reg_ptr; + struct clk *cam_clk; + long clk_rate; +}; + +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); + +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(struct msm_sensor_ctrl_t *s_ctrl, + int mode, int res); +int32_t msm_sensor_mode_init(struct msm_sensor_ctrl_t *s_ctrl, + int mode, struct sensor_init_cfg *init_info); +int32_t msm_sensor_get_output_info(struct msm_sensor_ctrl_t *, + struct sensor_output_info_t *); +int32_t msm_sensor_config(struct msm_sensor_ctrl_t *s_ctrl, + void __user *argp); +int32_t msm_sensor_power_up(struct msm_sensor_ctrl_t *s_ctrl); +int32_t msm_sensor_power_down(struct msm_sensor_ctrl_t *s_ctrl); + +int32_t msm_sensor_match_id(struct msm_sensor_ctrl_t *s_ctrl); +int msm_sensor_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id); +int32_t msm_sensor_power(struct v4l2_subdev *sd, int on); + +int32_t msm_sensor_v4l2_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl); + +int32_t msm_sensor_v4l2_query_ctrl( + struct v4l2_subdev *sd, struct v4l2_queryctrl *qctrl); + +int msm_sensor_s_ctrl_by_index(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_v4l2_ctrl_info_t *ctrl_info, int value); + +int msm_sensor_s_ctrl_by_enum(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_v4l2_ctrl_info_t *ctrl_info, int value); + +int msm_sensor_v4l2_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code); + +int msm_sensor_write_init_settings(struct msm_sensor_ctrl_t *s_ctrl); +int msm_sensor_write_res_settings + (struct msm_sensor_ctrl_t *s_ctrl, uint16_t res); + +int32_t msm_sensor_write_output_settings(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res); + +int32_t msm_sensor_adjust_frame_lines(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res); + +int32_t msm_sensor_setting(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int res); + +int32_t msm_sensor_setting1(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int res); + +int msm_sensor_enable_debugfs(struct msm_sensor_ctrl_t *s_ctrl); + +long msm_sensor_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg); + +struct msm_sensor_ctrl_t *get_sctrl(struct v4l2_subdev *sd); + +#if defined(CONFIG_OV5647) + extern int lcd_camera_power_onoff(int on); +#endif + +#define VIDIOC_MSM_SENSOR_CFG \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 10, void __user *) + +#define VIDIOC_MSM_SENSOR_RELEASE \ + _IO('V', BASE_VIDIOC_PRIVATE + 11) + +#endif diff --git a/drivers/media/video/msm/sensors/mt9e013_v4l2.c b/drivers/media/video/msm/sensors/mt9e013_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..e6e2d5262920a346da0d5e63c55e583d93b861a3 --- /dev/null +++ b/drivers/media/video/msm/sensors/mt9e013_v4l2.c @@ -0,0 +1,513 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "mt9e013" +#define PLATFORM_DRIVER_NAME "msm_camera_mt9e013" +#define mt9e013_obj mt9e013_##obj + +DEFINE_MUTEX(mt9e013_mut); +static struct msm_sensor_ctrl_t mt9e013_s_ctrl; + +static struct msm_camera_i2c_reg_conf mt9e013_groupon_settings[] = { + {0x0104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf mt9e013_groupoff_settings[] = { + {0x0104, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf mt9e013_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 msm_camera_i2c_reg_conf mt9e013_snap_settings[] = { + /*Output Size (3264x2448)*/ + {0x0344, 0x0000},/*X_ADDR_START */ + {0x0348, 0x0CCF},/*X_ADDR_END*/ + {0x0346, 0x0000},/*Y_ADDR_START */ + {0x034A, 0x099F},/*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 msm_camera_i2c_reg_conf mt9e013_hfr60_settings[] = { + {0x0300, 0x0005},/*VT_PIX_CLK_DIV*/ + {0x0302, 0x0001},/*VT_SYS_CLK_DIV*/ + {0x0304, 0x0002},/*PRE_PLL_CLK_DIV*/ + {0x0306, 0x0029},/*PLL_MULTIPLIER*/ + {0x0308, 0x000A},/*OP_PIX_CLK_DIV*/ + {0x030A, 0x0001},/*OP_SYS_CLK_DIV*/ + {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 msm_camera_i2c_reg_conf mt9e013_hfr90_settings[] = { + {0x0300, 0x0005},/*VT_PIX_CLK_DIV*/ + {0x0302, 0x0001},/*VT_SYS_CLK_DIV*/ + {0x0304, 0x0002},/*PRE_PLL_CLK_DIV*/ + {0x0306, 0x003D},/*PLL_MULTIPLIER*/ + {0x0308, 0x000A},/*OP_PIX_CLK_DIV*/ + {0x030A, 0x0001},/*OP_SYS_CLK_DIV*/ + {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 msm_camera_i2c_reg_conf mt9e013_hfr120_settings[] = { + {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*/ + {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 msm_camera_i2c_reg_conf mt9e013_recommend_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, 0x800D},/*VCM CONTROL*/ + + {0x3044, 0x0590}, + {0x306E, 0xFC80}, + {0x30B2, 0xC000}, + {0x30D6, 0x0800}, + {0x316C, 0xB42F}, + {0x316E, 0x869A}, + {0x3170, 0x210E}, + {0x317A, 0x010E}, + {0x31E0, 0x1FB9}, + {0x31E6, 0x07FC}, + {0x37C0, 0x0000}, + {0x37C2, 0x0000}, + {0x37C4, 0x0000}, + {0x37C6, 0x0000}, + {0x3E00, 0x0011}, + {0x3E02, 0x8801}, + {0x3E04, 0x2801}, + {0x3E06, 0x8449}, + {0x3E08, 0x6841}, + {0x3E0A, 0x400C}, + {0x3E0C, 0x1001}, + {0x3E0E, 0x2603}, + {0x3E10, 0x4B41}, + {0x3E12, 0x4B24}, + {0x3E14, 0xA3CF}, + {0x3E16, 0x8802}, + {0x3E18, 0x8401}, + {0x3E1A, 0x8601}, + {0x3E1C, 0x8401}, + {0x3E1E, 0x840A}, + {0x3E20, 0xFF00}, + {0x3E22, 0x8401}, + {0x3E24, 0x00FF}, + {0x3E26, 0x0088}, + {0x3E28, 0x2E8A}, + {0x3E30, 0x0000}, + {0x3E32, 0x8801}, + {0x3E34, 0x4029}, + {0x3E36, 0x00FF}, + {0x3E38, 0x8469}, + {0x3E3A, 0x00FF}, + {0x3E3C, 0x2801}, + {0x3E3E, 0x3E2A}, + {0x3E40, 0x1C01}, + {0x3E42, 0xFF84}, + {0x3E44, 0x8401}, + {0x3E46, 0x0C01}, + {0x3E48, 0x8401}, + {0x3E4A, 0x00FF}, + {0x3E4C, 0x8402}, + {0x3E4E, 0x8984}, + {0x3E50, 0x6628}, + {0x3E52, 0x8340}, + {0x3E54, 0x00FF}, + {0x3E56, 0x4A42}, + {0x3E58, 0x2703}, + {0x3E5A, 0x6752}, + {0x3E5C, 0x3F2A}, + {0x3E5E, 0x846A}, + {0x3E60, 0x4C01}, + {0x3E62, 0x8401}, + {0x3E66, 0x3901}, + {0x3E90, 0x2C01}, + {0x3E98, 0x2B02}, + {0x3E92, 0x2A04}, + {0x3E94, 0x2509}, + {0x3E96, 0x0000}, + {0x3E9A, 0x2905}, + {0x3E9C, 0x00FF}, + {0x3ECC, 0x00EB}, + {0x3ED0, 0x1E24}, + {0x3ED4, 0xAFC4}, + {0x3ED6, 0x909B}, + {0x3EE0, 0x2424}, + {0x3EE2, 0x9797}, + {0x3EE4, 0xC100}, + {0x3EE6, 0x0540}, + {0x3174, 0x8000}, + /* 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 v4l2_subdev_info mt9e013_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_camera_i2c_conf_array mt9e013_init_conf[] = { + {&mt9e013_recommend_settings[0], + ARRAY_SIZE(mt9e013_recommend_settings), 0, MSM_CAMERA_I2C_WORD_DATA} +}; + +static struct msm_camera_i2c_conf_array mt9e013_confs[] = { + {&mt9e013_snap_settings[0], + ARRAY_SIZE(mt9e013_snap_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, + {&mt9e013_prev_settings[0], + ARRAY_SIZE(mt9e013_prev_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, + {&mt9e013_hfr60_settings[0], + ARRAY_SIZE(mt9e013_hfr60_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, + {&mt9e013_hfr90_settings[0], + ARRAY_SIZE(mt9e013_hfr90_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, + {&mt9e013_hfr120_settings[0], + ARRAY_SIZE(mt9e013_hfr120_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, +}; + +static struct msm_sensor_output_info_t mt9e013_dimensions[] = { + { + .x_output = 0xCD0, + .y_output = 0x9A0, + .line_length_pclk = 0x13F8, + .frame_length_lines = 0xA2F, + .vt_pixel_clk = 174000000, + .op_pixel_clk = 174000000, + .binning_factor = 1, + }, + { + .x_output = 0x660, + .y_output = 0x4C8, + .line_length_pclk = 0x1018, + .frame_length_lines = 0x55B, + .vt_pixel_clk = 174000000, + .op_pixel_clk = 174000000, + .binning_factor = 1, + }, + { + .x_output = 0x340, + .y_output = 0x212, + .line_length_pclk = 0x970, + .frame_length_lines = 0x2A1, + .vt_pixel_clk = 98400000, + .op_pixel_clk = 98400000, + .binning_factor = 1, + }, + { + .x_output = 0x340, + .y_output = 0x212, + .line_length_pclk = 0x970, + .frame_length_lines = 0x2A1, + .vt_pixel_clk = 146400000, + .op_pixel_clk = 146400000, + .binning_factor = 1, + }, + { + .x_output = 0x340, + .y_output = 0x212, + .line_length_pclk = 0x970, + .frame_length_lines = 0x2A1, + .vt_pixel_clk = 196800000, + .op_pixel_clk = 196800000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csi_params mt9e013_csi_params = { + .data_format = CSI_10BIT, + .lane_cnt = 2, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 0x18, +}; + +static struct msm_camera_csi_params *mt9e013_csi_params_array[] = { + &mt9e013_csi_params, + &mt9e013_csi_params, + &mt9e013_csi_params, + &mt9e013_csi_params, + &mt9e013_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t mt9e013_reg_addr = { + .x_output = 0x34C, + .y_output = 0x34E, + .line_length_pclk = 0x342, + .frame_length_lines = 0x340, +}; + +static struct msm_sensor_id_info_t mt9e013_id_info = { + .sensor_id_reg_addr = 0x0, + .sensor_id = 0x4B00, +}; + +static struct msm_sensor_exp_gain_info_t mt9e013_exp_gain_info = { + .coarse_int_time_addr = 0x202, + .global_gain_addr = 0x305E, + .vert_offset = 0, +}; + +static int32_t mt9e013_write_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + uint32_t fl_lines; + fl_lines = + (s_ctrl->curr_frame_length_lines * s_ctrl->fps_divider) / Q10; + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, gain | 0x1000, + MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, line, + MSM_CAMERA_I2C_WORD_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + return 0; +} + +static int32_t mt9e013_write_exp_snapshot_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + uint32_t fl_lines; + fl_lines = + (s_ctrl->curr_frame_length_lines * s_ctrl->fps_divider) / Q10; + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, gain | 0x1000, + MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, line, + MSM_CAMERA_I2C_WORD_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, (0x065C|0x2), MSM_CAMERA_I2C_WORD_DATA); + + return 0; +} +static void mt9e013_start_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, 0x8250, MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, 0x8650, MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, 0x8658, MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x0104, 0x00, MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, 0x065C, MSM_CAMERA_I2C_WORD_DATA); +} + +static void mt9e013_stop_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, 0x0058, MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x301A, 0x0050, MSM_CAMERA_I2C_WORD_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x0104, 0x01, MSM_CAMERA_I2C_BYTE_DATA); +} + +static const struct i2c_device_id mt9e013_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&mt9e013_s_ctrl}, + { } +}; + +static struct i2c_driver mt9e013_i2c_driver = { + .id_table = mt9e013_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client mt9e013_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&mt9e013_i2c_driver); +} + +static struct v4l2_subdev_core_ops mt9e013_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops mt9e013_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops mt9e013_subdev_ops = { + .core = &mt9e013_subdev_core_ops, + .video = &mt9e013_subdev_video_ops, +}; + +static struct msm_sensor_fn_t mt9e013_func_tbl = { + .sensor_start_stream = mt9e013_start_stream, + .sensor_stop_stream = mt9e013_stop_stream, + .sensor_group_hold_on = msm_sensor_group_hold_on, + .sensor_group_hold_off = msm_sensor_group_hold_off, + .sensor_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = mt9e013_write_exp_gain, + .sensor_write_snapshot_exp_gain = mt9e013_write_exp_snapshot_gain, + .sensor_csi_setting = msm_sensor_setting1, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, +}; + +static struct msm_sensor_reg_t mt9e013_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .group_hold_on_conf = mt9e013_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(mt9e013_groupon_settings), + .group_hold_off_conf = mt9e013_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(mt9e013_groupoff_settings), + .init_settings = &mt9e013_init_conf[0], + .init_size = ARRAY_SIZE(mt9e013_init_conf), + .mode_settings = &mt9e013_confs[0], + .output_settings = &mt9e013_dimensions[0], + .num_conf = ARRAY_SIZE(mt9e013_confs), +}; + +static struct msm_sensor_ctrl_t mt9e013_s_ctrl = { + .msm_sensor_reg = &mt9e013_regs, + .sensor_i2c_client = &mt9e013_sensor_i2c_client, + .sensor_i2c_addr = 0x6C, + .sensor_output_reg_addr = &mt9e013_reg_addr, + .sensor_id_info = &mt9e013_id_info, + .sensor_exp_gain_info = &mt9e013_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &mt9e013_csi_params_array[0], + .msm_sensor_mutex = &mt9e013_mut, + .sensor_i2c_driver = &mt9e013_i2c_driver, + .sensor_v4l2_subdev_info = mt9e013_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(mt9e013_subdev_info), + .sensor_v4l2_subdev_ops = &mt9e013_subdev_ops, + .func_tbl = &mt9e013_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Aptina 8MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/media/video/msm/sensors/mt9m114_v4l2.c b/drivers/media/video/msm/sensors/mt9m114_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..218480674fde7163ec816d01db6c7b88b032bedd --- /dev/null +++ b/drivers/media/video/msm/sensors/mt9m114_v4l2.c @@ -0,0 +1,1304 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "mt9m114" +#define PLATFORM_DRIVER_NAME "msm_camera_mt9m114" +#define mt9m114_obj mt9m114_##obj + +/* Sysctl registers */ +#define MT9M114_COMMAND_REGISTER 0x0080 +#define MT9M114_COMMAND_REGISTER_APPLY_PATCH (1 << 0) +#define MT9M114_COMMAND_REGISTER_SET_STATE (1 << 1) +#define MT9M114_COMMAND_REGISTER_REFRESH (1 << 2) +#define MT9M114_COMMAND_REGISTER_WAIT_FOR_EVENT (1 << 3) +#define MT9M114_COMMAND_REGISTER_OK (1 << 15) + +DEFINE_MUTEX(mt9m114_mut); +static struct msm_sensor_ctrl_t mt9m114_s_ctrl; + +static struct msm_camera_i2c_reg_conf mt9m114_720p_settings[] = { + {0xdc00, 0x50, MSM_CAMERA_I2C_BYTE_DATA, MSM_CAMERA_I2C_CMD_WRITE}, + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_SET_STATE, + MSM_CAMERA_I2C_UNSET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, + {MT9M114_COMMAND_REGISTER, (MT9M114_COMMAND_REGISTER_OK | + MT9M114_COMMAND_REGISTER_SET_STATE), MSM_CAMERA_I2C_WORD_DATA, + MSM_CAMERA_I2C_CMD_WRITE}, + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_SET_STATE, + MSM_CAMERA_I2C_UNSET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, + {0xDC01, 0x52, MSM_CAMERA_I2C_BYTE_DATA, MSM_CAMERA_I2C_CMD_POLL}, + + {0x098E, 0, MSM_CAMERA_I2C_BYTE_DATA}, + {0xC800, 0x007C,},/*y_addr_start = 124*/ + {0xC802, 0x0004,},/*x_addr_start = 4*/ + {0xC804, 0x0353,},/*y_addr_end = 851*/ + {0xC806, 0x050B,},/*x_addr_end = 1291*/ + {0xC808, 0x02DC,},/*pixclk = 48000000*/ + {0xC80A, 0x6C00,},/*pixclk = 48000000*/ + {0xC80C, 0x0001,},/*row_speed = 1*/ + {0xC80E, 0x00DB,},/*fine_integ_time_min = 219*/ + {0xC810, 0x05BD,},/*fine_integ_time_max = 1469*/ + {0xC812, 0x03E8,},/*frame_length_lines = 1000*/ + {0xC814, 0x0640,},/*line_length_pck = 1600*/ + {0xC816, 0x0060,},/*fine_correction = 96*/ + {0xC818, 0x02D3,},/*cpipe_last_row = 723*/ + {0xC826, 0x0020,},/*reg_0_data = 32*/ + {0xC834, 0x0000,},/*sensor_control_read_mode = 0*/ + {0xC854, 0x0000,},/*crop_window_xoffset = 0*/ + {0xC856, 0x0000,},/*crop_window_yoffset = 0*/ + {0xC858, 0x0500,},/*crop_window_width = 1280*/ + {0xC85A, 0x02D0,},/*crop_window_height = 720*/ + {0xC85C, 0x03, MSM_CAMERA_I2C_BYTE_DATA}, /*crop_cropmode = 3*/ + {0xC868, 0x0500,},/*output_width = 1280*/ + {0xC86A, 0x02D0,},/*output_height = 720*/ + {0xC878, 0x00, MSM_CAMERA_I2C_BYTE_DATA}, /*aet_aemode = 0*/ + {0xC88C, 0x1E00,},/*aet_max_frame_rate = 7680*/ + {0xC88E, 0x1E00,},/*aet_min_frame_rate = 7680*/ + {0xC914, 0x0000,},/*stat_awb_window_xstart = 0*/ + {0xC916, 0x0000,},/*stat_awb_window_ystart = 0*/ + {0xC918, 0x04FF,},/*stat_awb_window_xend = 1279*/ + {0xC91A, 0x02CF,},/*stat_awb_window_yend = 719*/ + {0xC91C, 0x0000,},/*stat_ae_window_xstart = 0*/ + {0xC91E, 0x0000,},/*stat_ae_window_ystart = 0*/ + {0xC920, 0x00FF,},/*stat_ae_window_xend = 255*/ + {0xC922, 0x008F,},/*stat_ae_window_yend = 143*/ +}; + +static struct msm_camera_i2c_reg_conf mt9m114_recommend_settings[] = { + {0x301A, 0x0200, MSM_CAMERA_I2C_SET_WORD_MASK}, + {0x098E, 0, MSM_CAMERA_I2C_BYTE_DATA}, + /*cam_sysctl_pll_enable = 1*/ + {0xC97E, 0x01, MSM_CAMERA_I2C_BYTE_DATA}, + /*cam_sysctl_pll_divider_m_n = 288*/ + {0xC980, 0x0120,}, + /*cam_sysctl_pll_divider_p = 1792*/ + {0xC982, 0x0700,}, + /*output_control = 32769*/ + {0xC984, 0x8001,}, + /*mipi_timing_t_hs_zero = 3840*/ + {0xC988, 0x0F00,}, + /*mipi_timing_t_hs_exit_hs_trail = 2823*/ + {0xC98A, 0x0B07,}, + /*mipi_timing_t_clk_post_clk_pre = 3329*/ + {0xC98C, 0x0D01,}, + /*mipi_timing_t_clk_trail_clk_zero = 1821*/ + {0xC98E, 0x071D,}, + /*mipi_timing_t_lpx = 6*/ + {0xC990, 0x0006,}, + /*mipi_timing_init_timing = 2572*/ + {0xC992, 0x0A0C,}, + {0xC800, 0x007C,},/*y_addr_start = 124*/ + {0xC802, 0x0004,},/*x_addr_start = 4*/ + {0xC804, 0x0353,},/*y_addr_end = 851*/ + {0xC806, 0x050B,},/*x_addr_end = 1291*/ + {0xC808, 0x02DC,},/*pixclk = 48000000*/ + {0xC80A, 0x6C00,},/*pixclk = 48000000*/ + {0xC80C, 0x0001,},/*row_speed = 1*/ + {0xC80E, 0x00DB,},/*fine_integ_time_min = 219*/ + {0xC810, 0x05BD,},/*fine_integ_time_max = 1469*/ + {0xC812, 0x03E8,},/*frame_length_lines = 1000*/ + {0xC814, 0x0640,},/*line_length_pck = 1600*/ + {0xC816, 0x0060,},/*fine_correction = 96*/ + {0xC818, 0x02D3,},/*cpipe_last_row = 723*/ + {0xC826, 0x0020,},/*reg_0_data = 32*/ + {0xC834, 0x0000,},/*sensor_control_read_mode = 0*/ + {0xC854, 0x0000,},/*crop_window_xoffset = 0*/ + {0xC856, 0x0000,},/*crop_window_yoffset = 0*/ + {0xC858, 0x0500,},/*crop_window_width = 1280*/ + {0xC85A, 0x02D0,},/*crop_window_height = 720*/ + {0xC85C, 0x03, MSM_CAMERA_I2C_BYTE_DATA}, /*crop_cropmode = 3*/ + {0xC868, 0x0500,},/*output_width = 1280*/ + {0xC86A, 0x02D0,},/*output_height = 720*/ + {0xC878, 0x00, MSM_CAMERA_I2C_BYTE_DATA}, /*aet_aemode = 0*/ + {0xC88C, 0x1E00,},/*aet_max_frame_rate = 7680*/ + {0xC88E, 0x1E00,},/*aet_min_frame_rate = 7680*/ + {0xC914, 0x0000,},/*stat_awb_window_xstart = 0*/ + {0xC916, 0x0000,},/*stat_awb_window_ystart = 0*/ + {0xC918, 0x04FF,},/*stat_awb_window_xend = 1279*/ + {0xC91A, 0x02CF,},/*stat_awb_window_yend = 719*/ + {0xC91C, 0x0000,},/*stat_ae_window_xstart = 0*/ + {0xC91E, 0x0000,},/*stat_ae_window_ystart = 0*/ + {0xC920, 0x00FF,},/*stat_ae_window_xend = 255*/ + {0xC922, 0x008F,},/*stat_ae_window_yend = 143*/ + + /*Sensor optimization*/ + {0x316A, 0x8270,}, + {0x316C, 0x8270,}, + {0x3ED0, 0x2305,}, + {0x3ED2, 0x77CF,}, + {0x316E, 0x8202,}, + {0x3180, 0x87FF,}, + {0x30D4, 0x6080,}, + {0xA802, 0x0008,},/*AE_TRACK_MODE*/ + {0x3E14, 0xFF39,}, + {0x0982, 0x0001,},/*ACCESS_CTL_STAT*/ + {0x098A, 0x5000,},/*PHYSICAL_ADDRESS_ACCESS*/ + {0xD000, 0x70CF,}, + {0xD002, 0xFFFF,}, + {0xD004, 0xC5D4,}, + {0xD006, 0x903A,}, + {0xD008, 0x2144,}, + {0xD00A, 0x0C00,}, + {0xD00C, 0x2186,}, + {0xD00E, 0x0FF3,}, + {0xD010, 0xB844,}, + {0xD012, 0xB948,}, + {0xD014, 0xE082,}, + {0xD016, 0x20CC,}, + {0xD018, 0x80E2,}, + {0xD01A, 0x21CC,}, + {0xD01C, 0x80A2,}, + {0xD01E, 0x21CC,}, + {0xD020, 0x80E2,}, + {0xD022, 0xF404,}, + {0xD024, 0xD801,}, + {0xD026, 0xF003,}, + {0xD028, 0xD800,}, + {0xD02A, 0x7EE0,}, + {0xD02C, 0xC0F1,}, + {0xD02E, 0x08BA,}, + {0xD030, 0x0600,}, + {0xD032, 0xC1A1,}, + {0xD034, 0x76CF,}, + {0xD036, 0xFFFF,}, + {0xD038, 0xC130,}, + {0xD03A, 0x6E04,}, + {0xD03C, 0xC040,}, + {0xD03E, 0x71CF,}, + {0xD040, 0xFFFF,}, + {0xD042, 0xC790,}, + {0xD044, 0x8103,}, + {0xD046, 0x77CF,}, + {0xD048, 0xFFFF,}, + {0xD04A, 0xC7C0,}, + {0xD04C, 0xE001,}, + {0xD04E, 0xA103,}, + {0xD050, 0xD800,}, + {0xD052, 0x0C6A,}, + {0xD054, 0x04E0,}, + {0xD056, 0xB89E,}, + {0xD058, 0x7508,}, + {0xD05A, 0x8E1C,}, + {0xD05C, 0x0809,}, + {0xD05E, 0x0191,}, + {0xD060, 0xD801,}, + {0xD062, 0xAE1D,}, + {0xD064, 0xE580,}, + {0xD066, 0x20CA,}, + {0xD068, 0x0022,}, + {0xD06A, 0x20CF,}, + {0xD06C, 0x0522,}, + {0xD06E, 0x0C5C,}, + {0xD070, 0x04E2,}, + {0xD072, 0x21CA,}, + {0xD074, 0x0062,}, + {0xD076, 0xE580,}, + {0xD078, 0xD901,}, + {0xD07A, 0x79C0,}, + {0xD07C, 0xD800,}, + {0xD07E, 0x0BE6,}, + {0xD080, 0x04E0,}, + {0xD082, 0xB89E,}, + {0xD084, 0x70CF,}, + {0xD086, 0xFFFF,}, + {0xD088, 0xC8D4,}, + {0xD08A, 0x9002,}, + {0xD08C, 0x0857,}, + {0xD08E, 0x025E,}, + {0xD090, 0xFFDC,}, + {0xD092, 0xE080,}, + {0xD094, 0x25CC,}, + {0xD096, 0x9022,}, + {0xD098, 0xF225,}, + {0xD09A, 0x1700,}, + {0xD09C, 0x108A,}, + {0xD09E, 0x73CF,}, + {0xD0A0, 0xFF00,}, + {0xD0A2, 0x3174,}, + {0xD0A4, 0x9307,}, + {0xD0A6, 0x2A04,}, + {0xD0A8, 0x103E,}, + {0xD0AA, 0x9328,}, + {0xD0AC, 0x2942,}, + {0xD0AE, 0x7140,}, + {0xD0B0, 0x2A04,}, + {0xD0B2, 0x107E,}, + {0xD0B4, 0x9349,}, + {0xD0B6, 0x2942,}, + {0xD0B8, 0x7141,}, + {0xD0BA, 0x2A04,}, + {0xD0BC, 0x10BE,}, + {0xD0BE, 0x934A,}, + {0xD0C0, 0x2942,}, + {0xD0C2, 0x714B,}, + {0xD0C4, 0x2A04,}, + {0xD0C6, 0x10BE,}, + {0xD0C8, 0x130C,}, + {0xD0CA, 0x010A,}, + {0xD0CC, 0x2942,}, + {0xD0CE, 0x7142,}, + {0xD0D0, 0x2250,}, + {0xD0D2, 0x13CA,}, + {0xD0D4, 0x1B0C,}, + {0xD0D6, 0x0284,}, + {0xD0D8, 0xB307,}, + {0xD0DA, 0xB328,}, + {0xD0DC, 0x1B12,}, + {0xD0DE, 0x02C4,}, + {0xD0E0, 0xB34A,}, + {0xD0E2, 0xED88,}, + {0xD0E4, 0x71CF,}, + {0xD0E6, 0xFF00,}, + {0xD0E8, 0x3174,}, + {0xD0EA, 0x9106,}, + {0xD0EC, 0xB88F,}, + {0xD0EE, 0xB106,}, + {0xD0F0, 0x210A,}, + {0xD0F2, 0x8340,}, + {0xD0F4, 0xC000,}, + {0xD0F6, 0x21CA,}, + {0xD0F8, 0x0062,}, + {0xD0FA, 0x20F0,}, + {0xD0FC, 0x0040,}, + {0xD0FE, 0x0B02,}, + {0xD100, 0x0320,}, + {0xD102, 0xD901,}, + {0xD104, 0x07F1,}, + {0xD106, 0x05E0,}, + {0xD108, 0xC0A1,}, + {0xD10A, 0x78E0,}, + {0xD10C, 0xC0F1,}, + {0xD10E, 0x71CF,}, + {0xD110, 0xFFFF,}, + {0xD112, 0xC7C0,}, + {0xD114, 0xD840,}, + {0xD116, 0xA900,}, + {0xD118, 0x71CF,}, + {0xD11A, 0xFFFF,}, + {0xD11C, 0xD02C,}, + {0xD11E, 0xD81E,}, + {0xD120, 0x0A5A,}, + {0xD122, 0x04E0,}, + {0xD124, 0xDA00,}, + {0xD126, 0xD800,}, + {0xD128, 0xC0D1,}, + {0xD12A, 0x7EE0,}, + {0x098E, 0x0000,}, + + {0x0982, 0x0001,}, + {0x098A, 0x5C10,}, + {0xDC10, 0xC0F1,}, + {0xDC12, 0x0CDA,}, + {0xDC14, 0x0580,}, + {0xDC16, 0x76CF,}, + {0xDC18, 0xFF00,}, + {0xDC1A, 0x2184,}, + {0xDC1C, 0x9624,}, + {0xDC1E, 0x218C,}, + {0xDC20, 0x8FC3,}, + {0xDC22, 0x75CF,}, + {0xDC24, 0xFFFF,}, + {0xDC26, 0xE058,}, + {0xDC28, 0xF686,}, + {0xDC2A, 0x1550,}, + {0xDC2C, 0x1080,}, + {0xDC2E, 0xE001,}, + {0xDC30, 0x1D50,}, + {0xDC32, 0x1002,}, + {0xDC34, 0x1552,}, + {0xDC36, 0x1100,}, + {0xDC38, 0x6038,}, + {0xDC3A, 0x1D52,}, + {0xDC3C, 0x1004,}, + {0xDC3E, 0x1540,}, + {0xDC40, 0x1080,}, + {0xDC42, 0x081B,}, + {0xDC44, 0x00D1,}, + {0xDC46, 0x8512,}, + {0xDC48, 0x1000,}, + {0xDC4A, 0x00C0,}, + {0xDC4C, 0x7822,}, + {0xDC4E, 0x2089,}, + {0xDC50, 0x0FC1,}, + {0xDC52, 0x2008,}, + {0xDC54, 0x0F81,}, + {0xDC56, 0xFFFF,}, + {0xDC58, 0xFF80,}, + {0xDC5A, 0x8512,}, + {0xDC5C, 0x1801,}, + {0xDC5E, 0x0052,}, + {0xDC60, 0xA512,}, + {0xDC62, 0x1544,}, + {0xDC64, 0x1080,}, + {0xDC66, 0xB861,}, + {0xDC68, 0x262F,}, + {0xDC6A, 0xF007,}, + {0xDC6C, 0x1D44,}, + {0xDC6E, 0x1002,}, + {0xDC70, 0x20CA,}, + {0xDC72, 0x0021,}, + {0xDC74, 0x20CF,}, + {0xDC76, 0x04E1,}, + {0xDC78, 0x0850,}, + {0xDC7A, 0x04A1,}, + {0xDC7C, 0x21CA,}, + {0xDC7E, 0x0021,}, + {0xDC80, 0x1542,}, + {0xDC82, 0x1140,}, + {0xDC84, 0x8D2C,}, + {0xDC86, 0x6038,}, + {0xDC88, 0x1D42,}, + {0xDC8A, 0x1004,}, + {0xDC8C, 0x1542,}, + {0xDC8E, 0x1140,}, + {0xDC90, 0xB601,}, + {0xDC92, 0x046D,}, + {0xDC94, 0x0580,}, + {0xDC96, 0x78E0,}, + {0xDC98, 0xD800,}, + {0xDC9A, 0xB893,}, + {0xDC9C, 0x002D,}, + {0xDC9E, 0x04A0,}, + {0xDCA0, 0xD900,}, + {0xDCA2, 0x78E0,}, + {0xDCA4, 0x72CF,}, + {0xDCA6, 0xFFFF,}, + {0xDCA8, 0xE058,}, + {0xDCAA, 0x2240,}, + {0xDCAC, 0x0340,}, + {0xDCAE, 0xA212,}, + {0xDCB0, 0x208A,}, + {0xDCB2, 0x0FFF,}, + {0xDCB4, 0x1A42,}, + {0xDCB6, 0x0004,}, + {0xDCB8, 0xD830,}, + {0xDCBA, 0x1A44,}, + {0xDCBC, 0x0002,}, + {0xDCBE, 0xD800,}, + {0xDCC0, 0x1A50,}, + {0xDCC2, 0x0002,}, + {0xDCC4, 0x1A52,}, + {0xDCC6, 0x0004,}, + {0xDCC8, 0x1242,}, + {0xDCCA, 0x0140,}, + {0xDCCC, 0x8A2C,}, + {0xDCCE, 0x6038,}, + {0xDCD0, 0x1A42,}, + {0xDCD2, 0x0004,}, + {0xDCD4, 0x1242,}, + {0xDCD6, 0x0141,}, + {0xDCD8, 0x70CF,}, + {0xDCDA, 0xFF00,}, + {0xDCDC, 0x2184,}, + {0xDCDE, 0xB021,}, + {0xDCE0, 0xD800,}, + {0xDCE2, 0xB893,}, + {0xDCE4, 0x07E5,}, + {0xDCE6, 0x0460,}, + {0xDCE8, 0xD901,}, + {0xDCEA, 0x78E0,}, + {0xDCEC, 0xC0F1,}, + {0xDCEE, 0x0BFA,}, + {0xDCF0, 0x05A0,}, + {0xDCF2, 0x216F,}, + {0xDCF4, 0x0043,}, + {0xDCF6, 0xC1A4,}, + {0xDCF8, 0x220A,}, + {0xDCFA, 0x1F80,}, + {0xDCFC, 0xFFFF,}, + {0xDCFE, 0xE058,}, + {0xDD00, 0x2240,}, + {0xDD02, 0x134F,}, + {0xDD04, 0x1A48,}, + {0xDD06, 0x13C0,}, + {0xDD08, 0x1248,}, + {0xDD0A, 0x1002,}, + {0xDD0C, 0x70CF,}, + {0xDD0E, 0x7FFF,}, + {0xDD10, 0xFFFF,}, + {0xDD12, 0xE230,}, + {0xDD14, 0xC240,}, + {0xDD16, 0xDA00,}, + {0xDD18, 0xF00C,}, + {0xDD1A, 0x1248,}, + {0xDD1C, 0x1003,}, + {0xDD1E, 0x1301,}, + {0xDD20, 0x04CB,}, + {0xDD22, 0x7261,}, + {0xDD24, 0x2108,}, + {0xDD26, 0x0081,}, + {0xDD28, 0x2009,}, + {0xDD2A, 0x0080,}, + {0xDD2C, 0x1A48,}, + {0xDD2E, 0x10C0,}, + {0xDD30, 0x1248,}, + {0xDD32, 0x100B,}, + {0xDD34, 0xC300,}, + {0xDD36, 0x0BE7,}, + {0xDD38, 0x90C4,}, + {0xDD3A, 0x2102,}, + {0xDD3C, 0x0003,}, + {0xDD3E, 0x238C,}, + {0xDD40, 0x8FC3,}, + {0xDD42, 0xF6C7,}, + {0xDD44, 0xDAFF,}, + {0xDD46, 0x1A05,}, + {0xDD48, 0x1082,}, + {0xDD4A, 0xC241,}, + {0xDD4C, 0xF005,}, + {0xDD4E, 0x7A6F,}, + {0xDD50, 0xC241,}, + {0xDD52, 0x1A05,}, + {0xDD54, 0x10C2,}, + {0xDD56, 0x2000,}, + {0xDD58, 0x8040,}, + {0xDD5A, 0xDA00,}, + {0xDD5C, 0x20C0,}, + {0xDD5E, 0x0064,}, + {0xDD60, 0x781C,}, + {0xDD62, 0xC042,}, + {0xDD64, 0x1C0E,}, + {0xDD66, 0x3082,}, + {0xDD68, 0x1A48,}, + {0xDD6A, 0x13C0,}, + {0xDD6C, 0x7548,}, + {0xDD6E, 0x7348,}, + {0xDD70, 0x7148,}, + {0xDD72, 0x7648,}, + {0xDD74, 0xF002,}, + {0xDD76, 0x7608,}, + {0xDD78, 0x1248,}, + {0xDD7A, 0x1000,}, + {0xDD7C, 0x1400,}, + {0xDD7E, 0x300B,}, + {0xDD80, 0x084D,}, + {0xDD82, 0x02C5,}, + {0xDD84, 0x1248,}, + {0xDD86, 0x1000,}, + {0xDD88, 0xE101,}, + {0xDD8A, 0x1001,}, + {0xDD8C, 0x04CB,}, + {0xDD8E, 0x1A48,}, + {0xDD90, 0x1000,}, + {0xDD92, 0x7361,}, + {0xDD94, 0x1408,}, + {0xDD96, 0x300B,}, + {0xDD98, 0x2302,}, + {0xDD9A, 0x02C0,}, + {0xDD9C, 0x780D,}, + {0xDD9E, 0x2607,}, + {0xDDA0, 0x903E,}, + {0xDDA2, 0x07D6,}, + {0xDDA4, 0xFFE3,}, + {0xDDA6, 0x792F,}, + {0xDDA8, 0x09CF,}, + {0xDDAA, 0x8152,}, + {0xDDAC, 0x1248,}, + {0xDDAE, 0x100E,}, + {0xDDB0, 0x2400,}, + {0xDDB2, 0x334B,}, + {0xDDB4, 0xE501,}, + {0xDDB6, 0x7EE2,}, + {0xDDB8, 0x0DBF,}, + {0xDDBA, 0x90F2,}, + {0xDDBC, 0x1B0C,}, + {0xDDBE, 0x1382,}, + {0xDDC0, 0xC123,}, + {0xDDC2, 0x140E,}, + {0xDDC4, 0x3080,}, + {0xDDC6, 0x7822,}, + {0xDDC8, 0x1A07,}, + {0xDDCA, 0x1002,}, + {0xDDCC, 0x124C,}, + {0xDDCE, 0x1000,}, + {0xDDD0, 0x120B,}, + {0xDDD2, 0x1081,}, + {0xDDD4, 0x1207,}, + {0xDDD6, 0x1083,}, + {0xDDD8, 0x2142,}, + {0xDDDA, 0x004B,}, + {0xDDDC, 0x781B,}, + {0xDDDE, 0x0B21,}, + {0xDDE0, 0x02E2,}, + {0xDDE2, 0x1A4C,}, + {0xDDE4, 0x1000,}, + {0xDDE6, 0xE101,}, + {0xDDE8, 0x0915,}, + {0xDDEA, 0x00C2,}, + {0xDDEC, 0xC101,}, + {0xDDEE, 0x1204,}, + {0xDDF0, 0x1083,}, + {0xDDF2, 0x090D,}, + {0xDDF4, 0x00C2,}, + {0xDDF6, 0xE001,}, + {0xDDF8, 0x1A4C,}, + {0xDDFA, 0x1000,}, + {0xDDFC, 0x1A06,}, + {0xDDFE, 0x1002,}, + {0xDE00, 0x234A,}, + {0xDE02, 0x1000,}, + {0xDE04, 0x7169,}, + {0xDE06, 0xF008,}, + {0xDE08, 0x2053,}, + {0xDE0A, 0x0003,}, + {0xDE0C, 0x6179,}, + {0xDE0E, 0x781C,}, + {0xDE10, 0x2340,}, + {0xDE12, 0x104B,}, + {0xDE14, 0x1203,}, + {0xDE16, 0x1083,}, + {0xDE18, 0x0BF1,}, + {0xDE1A, 0x90C2,}, + {0xDE1C, 0x1202,}, + {0xDE1E, 0x1080,}, + {0xDE20, 0x091D,}, + {0xDE22, 0x0004,}, + {0xDE24, 0x70CF,}, + {0xDE26, 0xFFFF,}, + {0xDE28, 0xC644,}, + {0xDE2A, 0x881B,}, + {0xDE2C, 0xE0B2,}, + {0xDE2E, 0xD83C,}, + {0xDE30, 0x20CA,}, + {0xDE32, 0x0CA2,}, + {0xDE34, 0x1A01,}, + {0xDE36, 0x1002,}, + {0xDE38, 0x1A4C,}, + {0xDE3A, 0x1080,}, + {0xDE3C, 0x02B9,}, + {0xDE3E, 0x05A0,}, + {0xDE40, 0xC0A4,}, + {0xDE42, 0x78E0,}, + {0xDE44, 0xC0F1,}, + {0xDE46, 0xFF95,}, + {0xDE48, 0xD800,}, + {0xDE4A, 0x71CF,}, + {0xDE4C, 0xFF00,}, + {0xDE4E, 0x1FE0,}, + {0xDE50, 0x19D0,}, + {0xDE52, 0x001C,}, + {0xDE54, 0x19D1,}, + {0xDE56, 0x001C,}, + {0xDE58, 0x70CF,}, + {0xDE5A, 0xFFFF,}, + {0xDE5C, 0xE058,}, + {0xDE5E, 0x901F,}, + {0xDE60, 0xB861,}, + {0xDE62, 0x19D2,}, + {0xDE64, 0x001C,}, + {0xDE66, 0xC0D1,}, + {0xDE68, 0x7EE0,}, + {0xDE6A, 0x78E0,}, + {0xDE6C, 0xC0F1,}, + {0xDE6E, 0x0A7A,}, + {0xDE70, 0x0580,}, + {0xDE72, 0x70CF,}, + {0xDE74, 0xFFFF,}, + {0xDE76, 0xC5D4,}, + {0xDE78, 0x9041,}, + {0xDE7A, 0x9023,}, + {0xDE7C, 0x75CF,}, + {0xDE7E, 0xFFFF,}, + {0xDE80, 0xE058,}, + {0xDE82, 0x7942,}, + {0xDE84, 0xB967,}, + {0xDE86, 0x7F30,}, + {0xDE88, 0xB53F,}, + {0xDE8A, 0x71CF,}, + {0xDE8C, 0xFFFF,}, + {0xDE8E, 0xC84C,}, + {0xDE90, 0x91D3,}, + {0xDE92, 0x108B,}, + {0xDE94, 0x0081,}, + {0xDE96, 0x2615,}, + {0xDE98, 0x1380,}, + {0xDE9A, 0x090F,}, + {0xDE9C, 0x0C91,}, + {0xDE9E, 0x0A8E,}, + {0xDEA0, 0x05A0,}, + {0xDEA2, 0xD906,}, + {0xDEA4, 0x7E10,}, + {0xDEA6, 0x2615,}, + {0xDEA8, 0x1380,}, + {0xDEAA, 0x0A82,}, + {0xDEAC, 0x05A0,}, + {0xDEAE, 0xD960,}, + {0xDEB0, 0x790F,}, + {0xDEB2, 0x090D,}, + {0xDEB4, 0x0133,}, + {0xDEB6, 0xAD0C,}, + {0xDEB8, 0xD904,}, + {0xDEBA, 0xAD2C,}, + {0xDEBC, 0x79EC,}, + {0xDEBE, 0x2941,}, + {0xDEC0, 0x7402,}, + {0xDEC2, 0x71CF,}, + {0xDEC4, 0xFF00,}, + {0xDEC6, 0x2184,}, + {0xDEC8, 0xB142,}, + {0xDECA, 0x1906,}, + {0xDECC, 0x0E44,}, + {0xDECE, 0xFFDE,}, + {0xDED0, 0x70C9,}, + {0xDED2, 0x0A5A,}, + {0xDED4, 0x05A0,}, + {0xDED6, 0x8D2C,}, + {0xDED8, 0xAD0B,}, + {0xDEDA, 0xD800,}, + {0xDEDC, 0xAD01,}, + {0xDEDE, 0x0219,}, + {0xDEE0, 0x05A0,}, + {0xDEE2, 0xA513,}, + {0xDEE4, 0xC0F1,}, + {0xDEE6, 0x71CF,}, + {0xDEE8, 0xFFFF,}, + {0xDEEA, 0xC644,}, + {0xDEEC, 0xA91B,}, + {0xDEEE, 0xD902,}, + {0xDEF0, 0x70CF,}, + {0xDEF2, 0xFFFF,}, + {0xDEF4, 0xC84C,}, + {0xDEF6, 0x093E,}, + {0xDEF8, 0x03A0,}, + {0xDEFA, 0xA826,}, + {0xDEFC, 0xFFDC,}, + {0xDEFE, 0xF1B5,}, + {0xDF00, 0xC0F1,}, + {0xDF02, 0x09EA,}, + {0xDF04, 0x0580,}, + {0xDF06, 0x75CF,}, + {0xDF08, 0xFFFF,}, + {0xDF0A, 0xE058,}, + {0xDF0C, 0x1540,}, + {0xDF0E, 0x1080,}, + {0xDF10, 0x08A7,}, + {0xDF12, 0x0010,}, + {0xDF14, 0x8D00,}, + {0xDF16, 0x0813,}, + {0xDF18, 0x009E,}, + {0xDF1A, 0x1540,}, + {0xDF1C, 0x1081,}, + {0xDF1E, 0xE181,}, + {0xDF20, 0x20CA,}, + {0xDF22, 0x00A1,}, + {0xDF24, 0xF24B,}, + {0xDF26, 0x1540,}, + {0xDF28, 0x1081,}, + {0xDF2A, 0x090F,}, + {0xDF2C, 0x0050,}, + {0xDF2E, 0x1540,}, + {0xDF30, 0x1081,}, + {0xDF32, 0x0927,}, + {0xDF34, 0x0091,}, + {0xDF36, 0x1550,}, + {0xDF38, 0x1081,}, + {0xDF3A, 0xDE00,}, + {0xDF3C, 0xAD2A,}, + {0xDF3E, 0x1D50,}, + {0xDF40, 0x1382,}, + {0xDF42, 0x1552,}, + {0xDF44, 0x1101,}, + {0xDF46, 0x1D52,}, + {0xDF48, 0x1384,}, + {0xDF4A, 0xB524,}, + {0xDF4C, 0x082D,}, + {0xDF4E, 0x015F,}, + {0xDF50, 0xFF55,}, + {0xDF52, 0xD803,}, + {0xDF54, 0xF033,}, + {0xDF56, 0x1540,}, + {0xDF58, 0x1081,}, + {0xDF5A, 0x0967,}, + {0xDF5C, 0x00D1,}, + {0xDF5E, 0x1550,}, + {0xDF60, 0x1081,}, + {0xDF62, 0xDE00,}, + {0xDF64, 0xAD2A,}, + {0xDF66, 0x1D50,}, + {0xDF68, 0x1382,}, + {0xDF6A, 0x1552,}, + {0xDF6C, 0x1101,}, + {0xDF6E, 0x1D52,}, + {0xDF70, 0x1384,}, + {0xDF72, 0xB524,}, + {0xDF74, 0x0811,}, + {0xDF76, 0x019E,}, + {0xDF78, 0xB8A0,}, + {0xDF7A, 0xAD00,}, + {0xDF7C, 0xFF47,}, + {0xDF7E, 0x1D40,}, + {0xDF80, 0x1382,}, + {0xDF82, 0xF01F,}, + {0xDF84, 0xFF5A,}, + {0xDF86, 0x8D01,}, + {0xDF88, 0x8D40,}, + {0xDF8A, 0xE812,}, + {0xDF8C, 0x71CF,}, + {0xDF8E, 0xFFFF,}, + {0xDF90, 0xC644,}, + {0xDF92, 0x893B,}, + {0xDF94, 0x7030,}, + {0xDF96, 0x22D1,}, + {0xDF98, 0x8062,}, + {0xDF9A, 0xF20A,}, + {0xDF9C, 0x0A0F,}, + {0xDF9E, 0x009E,}, + {0xDFA0, 0x71CF,}, + {0xDFA2, 0xFFFF,}, + {0xDFA4, 0xC84C,}, + {0xDFA6, 0x893B,}, + {0xDFA8, 0xE902,}, + {0xDFAA, 0xFFCF,}, + {0xDFAC, 0x8D00,}, + {0xDFAE, 0xB8E7,}, + {0xDFB0, 0x26CA,}, + {0xDFB2, 0x1022,}, + {0xDFB4, 0xF5E2,}, + {0xDFB6, 0xFF3C,}, + {0xDFB8, 0xD801,}, + {0xDFBA, 0x1D40,}, + {0xDFBC, 0x1002,}, + {0xDFBE, 0x0141,}, + {0xDFC0, 0x0580,}, + {0xDFC2, 0x78E0,}, + {0xDFC4, 0xC0F1,}, + {0xDFC6, 0xC5E1,}, + {0xDFC8, 0xFF34,}, + {0xDFCA, 0xDD00,}, + {0xDFCC, 0x70CF,}, + {0xDFCE, 0xFFFF,}, + {0xDFD0, 0xE090,}, + {0xDFD2, 0xA8A8,}, + {0xDFD4, 0xD800,}, + {0xDFD6, 0xB893,}, + {0xDFD8, 0x0C8A,}, + {0xDFDA, 0x0460,}, + {0xDFDC, 0xD901,}, + {0xDFDE, 0x71CF,}, + {0xDFE0, 0xFFFF,}, + {0xDFE2, 0xDC10,}, + {0xDFE4, 0xD813,}, + {0xDFE6, 0x0B96,}, + {0xDFE8, 0x0460,}, + {0xDFEA, 0x72A9,}, + {0xDFEC, 0x0119,}, + {0xDFEE, 0x0580,}, + {0xDFF0, 0xC0F1,}, + {0xDFF2, 0x71CF,}, + {0xDFF4, 0x0000,}, + {0xDFF6, 0x5BAE,}, + {0xDFF8, 0x7940,}, + {0xDFFA, 0xFF9D,}, + {0xDFFC, 0xF135,}, + {0xDFFE, 0x78E0,}, + {0xE000, 0xC0F1,}, + {0xE002, 0x70CF,}, + {0xE004, 0x0000,}, + {0xE006, 0x5CBA,}, + {0xE008, 0x7840,}, + {0xE00A, 0x70CF,}, + {0xE00C, 0xFFFF,}, + {0xE00E, 0xE058,}, + {0xE010, 0x8800,}, + {0xE012, 0x0815,}, + {0xE014, 0x001E,}, + {0xE016, 0x70CF,}, + {0xE018, 0xFFFF,}, + {0xE01A, 0xC84C,}, + {0xE01C, 0x881A,}, + {0xE01E, 0xE080,}, + {0xE020, 0x0EE0,}, + {0xE022, 0xFFC1,}, + {0xE024, 0xF121,}, + {0xE026, 0x78E0,}, + {0xE028, 0xC0F1,}, + {0xE02A, 0xD900,}, + {0xE02C, 0xF009,}, + {0xE02E, 0x70CF,}, + {0xE030, 0xFFFF,}, + {0xE032, 0xE0AC,}, + {0xE034, 0x7835,}, + {0xE036, 0x8041,}, + {0xE038, 0x8000,}, + {0xE03A, 0xE102,}, + {0xE03C, 0xA040,}, + {0xE03E, 0x09F3,}, + {0xE040, 0x8114,}, + {0xE042, 0x71CF,}, + {0xE044, 0xFFFF,}, + {0xE046, 0xE058,}, + {0xE048, 0x70CF,}, + {0xE04A, 0xFFFF,}, + {0xE04C, 0xC594,}, + {0xE04E, 0xB030,}, + {0xE050, 0xFFDD,}, + {0xE052, 0xD800,}, + {0xE054, 0xF109,}, + {0xE056, 0x0000,}, + {0xE058, 0x0300,}, + {0xE05A, 0x0204,}, + {0xE05C, 0x0700,}, + {0xE05E, 0x0000,}, + {0xE060, 0x0000,}, + {0xE062, 0x0000,}, + {0xE064, 0x0000,}, + {0xE066, 0x0000,}, + {0xE068, 0x0000,}, + {0xE06A, 0x0000,}, + {0xE06C, 0x0000,}, + {0xE06E, 0x0000,}, + {0xE070, 0x0000,}, + {0xE072, 0x0000,}, + {0xE074, 0x0000,}, + {0xE076, 0x0000,}, + {0xE078, 0x0000,}, + {0xE07A, 0x0000,}, + {0xE07C, 0x0000,}, + {0xE07E, 0x0000,}, + {0xE080, 0x0000,}, + {0xE082, 0x0000,}, + {0xE084, 0x0000,}, + {0xE086, 0x0000,}, + {0xE088, 0x0000,}, + {0xE08A, 0x0000,}, + {0xE08C, 0x0000,}, + {0xE08E, 0x0000,}, + {0xE090, 0x0000,}, + {0xE092, 0x0000,}, + {0xE094, 0x0000,}, + {0xE096, 0x0000,}, + {0xE098, 0x0000,}, + {0xE09A, 0x0000,}, + {0xE09C, 0x0000,}, + {0xE09E, 0x0000,}, + {0xE0A0, 0x0000,}, + {0xE0A2, 0x0000,}, + {0xE0A4, 0x0000,}, + {0xE0A6, 0x0000,}, + {0xE0A8, 0x0000,}, + {0xE0AA, 0x0000,}, + {0xE0AC, 0xFFFF,}, + {0xE0AE, 0xCB68,}, + {0xE0B0, 0xFFFF,}, + {0xE0B2, 0xDFF0,}, + {0xE0B4, 0xFFFF,}, + {0xE0B6, 0xCB6C,}, + {0xE0B8, 0xFFFF,}, + {0xE0BA, 0xE000,}, + {0x098E, 0x0000,}, + + /*MIPI setting for SOC1040*/ + {0x3C5A, 0x0009,}, + {0x3C44, 0x0080,},/*MIPI_CUSTOM_SHORT_PKT*/ + + /*[Tuning_settings]*/ + + /*[CCM]*/ + {0xC892, 0x0267,},/*CAM_AWB_CCM_L_0*/ + {0xC894, 0xFF1A,},/*CAM_AWB_CCM_L_1*/ + {0xC896, 0xFFB3,},/*CAM_AWB_CCM_L_2*/ + {0xC898, 0xFF80,},/*CAM_AWB_CCM_L_3*/ + {0xC89A, 0x0166,},/*CAM_AWB_CCM_L_4*/ + {0xC89C, 0x0003,},/*CAM_AWB_CCM_L_5*/ + {0xC89E, 0xFF9A,},/*CAM_AWB_CCM_L_6*/ + {0xC8A0, 0xFEB4,},/*CAM_AWB_CCM_L_7*/ + {0xC8A2, 0x024D,},/*CAM_AWB_CCM_L_8*/ + {0xC8A4, 0x01BF,},/*CAM_AWB_CCM_M_0*/ + {0xC8A6, 0xFF01,},/*CAM_AWB_CCM_M_1*/ + {0xC8A8, 0xFFF3,},/*CAM_AWB_CCM_M_2*/ + {0xC8AA, 0xFF75,},/*CAM_AWB_CCM_M_3*/ + {0xC8AC, 0x0198,},/*CAM_AWB_CCM_M_4*/ + {0xC8AE, 0xFFFD,},/*CAM_AWB_CCM_M_5*/ + {0xC8B0, 0xFF9A,},/*CAM_AWB_CCM_M_6*/ + {0xC8B2, 0xFEE7,},/*CAM_AWB_CCM_M_7*/ + {0xC8B4, 0x02A8,},/*CAM_AWB_CCM_M_8*/ + {0xC8B6, 0x01D9,},/*CAM_AWB_CCM_R_0*/ + {0xC8B8, 0xFF26,},/*CAM_AWB_CCM_R_1*/ + {0xC8BA, 0xFFF3,},/*CAM_AWB_CCM_R_2*/ + {0xC8BC, 0xFFB3,},/*CAM_AWB_CCM_R_3*/ + {0xC8BE, 0x0132,},/*CAM_AWB_CCM_R_4*/ + {0xC8C0, 0xFFE8,},/*CAM_AWB_CCM_R_5*/ + {0xC8C2, 0xFFDA,},/*CAM_AWB_CCM_R_6*/ + {0xC8C4, 0xFECD,},/*CAM_AWB_CCM_R_7*/ + {0xC8C6, 0x02C2,},/*CAM_AWB_CCM_R_8*/ + {0xC8C8, 0x0075,},/*CAM_AWB_CCM_L_RG_GAIN*/ + {0xC8CA, 0x011C,},/*CAM_AWB_CCM_L_BG_GAIN*/ + {0xC8CC, 0x009A,},/*CAM_AWB_CCM_M_RG_GAIN*/ + {0xC8CE, 0x0105,},/*CAM_AWB_CCM_M_BG_GAIN*/ + {0xC8D0, 0x00A4,},/*CAM_AWB_CCM_R_RG_GAIN*/ + {0xC8D2, 0x00AC,},/*CAM_AWB_CCM_R_BG_GAIN*/ + {0xC8D4, 0x0A8C,},/*CAM_AWB_CCM_L_CTEMP*/ + {0xC8D6, 0x0F0A,},/*CAM_AWB_CCM_M_CTEMP*/ + {0xC8D8, 0x1964,},/*CAM_AWB_CCM_R_CTEMP*/ + + /*[AWB]*/ + {0xC914, 0x0000,},/*CAM_STAT_AWB_CLIP_WINDOW_XSTART*/ + {0xC916, 0x0000,},/*CAM_STAT_AWB_CLIP_WINDOW_YSTART*/ + {0xC918, 0x04FF,},/*CAM_STAT_AWB_CLIP_WINDOW_XEND*/ + {0xC91A, 0x02CF,},/*CAM_STAT_AWB_CLIP_WINDOW_YEND*/ + {0xC904, 0x0033,},/*CAM_AWB_AWB_XSHIFT_PRE_ADJ*/ + {0xC906, 0x0040,},/*CAM_AWB_AWB_YSHIFT_PRE_ADJ*/ + {0xC8F2, 0x03, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_AWB_XSCALE*/ + {0xC8F3, 0x02, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_AWB_YSCALE*/ + {0xC906, 0x003C,},/*CAM_AWB_AWB_YSHIFT_PRE_ADJ*/ + {0xC8F4, 0x0000,},/*CAM_AWB_AWB_WEIGHTS_0*/ + {0xC8F6, 0x0000,},/*CAM_AWB_AWB_WEIGHTS_1*/ + {0xC8F8, 0x0000,},/*CAM_AWB_AWB_WEIGHTS_2*/ + {0xC8FA, 0xE724,},/*CAM_AWB_AWB_WEIGHTS_3*/ + {0xC8FC, 0x1583,},/*CAM_AWB_AWB_WEIGHTS_4*/ + {0xC8FE, 0x2045,},/*CAM_AWB_AWB_WEIGHTS_5*/ + {0xC900, 0x03FF,},/*CAM_AWB_AWB_WEIGHTS_6*/ + {0xC902, 0x007C,},/*CAM_AWB_AWB_WEIGHTS_7*/ + {0xC90C, 0x80, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_K_R_L*/ + {0xC90D, 0x80, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_K_G_L*/ + {0xC90E, 0x80, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_K_B_L*/ + {0xC90F, 0x88, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_K_R_R*/ + {0xC910, 0x80, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_K_G_R*/ + {0xC911, 0x80, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AWB_K_B_R*/ + + /*[Step7-CPIPE_Preference]*/ + {0xC926, 0x0020,},/*CAM_LL_START_BRIGHTNESS*/ + {0xC928, 0x009A,},/*CAM_LL_STOP_BRIGHTNESS*/ + {0xC946, 0x0070,},/*CAM_LL_START_GAIN_METRIC*/ + {0xC948, 0x00F3,},/*CAM_LL_STOP_GAIN_METRIC*/ + {0xC952, 0x0020,},/*CAM_LL_START_TARGET_LUMA_BM*/ + {0xC954, 0x009A,},/*CAM_LL_STOP_TARGET_LUMA_BM*/ + {0xC92A, 0x80, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_SATURATION*/ + {0xC92B, 0x4B, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_END_SATURATION*/ + {0xC92C, 0x00, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_DESATURATION*/ + {0xC92D, 0xFF, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_END_DESATURATION*/ + {0xC92E, 0x3C, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_DEMOSAIC*/ + {0xC92F, 0x02, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_AP_GAIN*/ + {0xC930, 0x06, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_AP_THRESH*/ + {0xC931, 0x64, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_DEMOSAIC*/ + {0xC932, 0x01, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_AP_GAIN*/ + {0xC933, 0x0C, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_AP_THRESH*/ + {0xC934, 0x3C, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_NR_RED*/ + {0xC935, 0x3C, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_NR_GREEN*/ + {0xC936, 0x3C, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_NR_BLUE*/ + {0xC937, 0x0F, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_NR_THRESH*/ + {0xC938, 0x64, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_NR_RED*/ + {0xC939, 0x64, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_NR_GREEN*/ + {0xC93A, 0x64, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_NR_BLUE*/ + {0xC93B, 0x32, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_NR_THRESH*/ + {0xC93C, 0x0020,},/*CAM_LL_START_CONTRAST_BM*/ + {0xC93E, 0x009A,},/*CAM_LL_STOP_CONTRAST_BM*/ + {0xC940, 0x00DC,},/*CAM_LL_GAMMA*/ + /*CAM_LL_START_CONTRAST_GRADIENT*/ + {0xC942, 0x38, MSM_CAMERA_I2C_BYTE_DATA}, + /*CAM_LL_STOP_CONTRAST_GRADIENT*/ + {0xC943, 0x30, MSM_CAMERA_I2C_BYTE_DATA}, + {0xC944, 0x50, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_START_CONTRAST_LUMA*/ + {0xC945, 0x19, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_STOP_CONTRAST_LUMA*/ + {0xC94A, 0x0230,},/*CAM_LL_START_FADE_TO_BLACK_LUMA*/ + {0xC94C, 0x0010,},/*CAM_LL_STOP_FADE_TO_BLACK_LUMA*/ + {0xC94E, 0x01CD,},/*CAM_LL_CLUSTER_DC_TH_BM*/ + {0xC950, 0x05, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_CLUSTER_DC_GATE*/ + {0xC951, 0x40, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_LL_SUMMING_SENSITIVITY*/ + /*CAM_AET_TARGET_AVERAGE_LUMA_DARK*/ + {0xC87B, 0x1B, MSM_CAMERA_I2C_BYTE_DATA}, + {0xC878, 0x0E, MSM_CAMERA_I2C_BYTE_DATA},/*CAM_AET_AEMODE*/ + {0xC890, 0x0080,},/*CAM_AET_TARGET_GAIN*/ + {0xC886, 0x0100,},/*CAM_AET_AE_MAX_VIRT_AGAIN*/ + {0xC87C, 0x005A,},/*CAM_AET_BLACK_CLIPPING_TARGET*/ + {0xB42A, 0x05, MSM_CAMERA_I2C_BYTE_DATA},/*CCM_DELTA_GAIN*/ + /*AE_TRACK_AE_TRACKING_DAMPENING*/ + {0xA80A, 0x20, MSM_CAMERA_I2C_BYTE_DATA}, + {0x3C44, 0x0080,}, + {0x3C40, 0x0004, MSM_CAMERA_I2C_UNSET_WORD_MASK}, + {0xA802, 0x08, MSM_CAMERA_I2C_SET_BYTE_MASK}, + {0xC908, 0x01, MSM_CAMERA_I2C_BYTE_DATA}, + {0xC879, 0x01, MSM_CAMERA_I2C_BYTE_DATA}, + {0xC909, 0x01, MSM_CAMERA_I2C_UNSET_BYTE_MASK}, + {0xA80A, 0x18, MSM_CAMERA_I2C_BYTE_DATA}, + {0xA80B, 0x18, MSM_CAMERA_I2C_BYTE_DATA}, + {0xAC16, 0x18, MSM_CAMERA_I2C_BYTE_DATA}, + {0xC878, 0x08, MSM_CAMERA_I2C_SET_BYTE_MASK}, + {0xBC02, 0x08, MSM_CAMERA_I2C_UNSET_BYTE_MASK}, +}; + +static struct v4l2_subdev_info mt9m114_subdev_info[] = { + { + .code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, + /* more can be supported, to be added later */ +}; + +static struct msm_camera_i2c_reg_conf mt9m114_config_change_settings[] = { + {0xdc00, 0x28, MSM_CAMERA_I2C_BYTE_DATA, MSM_CAMERA_I2C_CMD_WRITE}, + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_SET_STATE, + MSM_CAMERA_I2C_UNSET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, + {MT9M114_COMMAND_REGISTER, (MT9M114_COMMAND_REGISTER_OK | + MT9M114_COMMAND_REGISTER_SET_STATE), MSM_CAMERA_I2C_WORD_DATA, + MSM_CAMERA_I2C_CMD_WRITE}, + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_SET_STATE, + MSM_CAMERA_I2C_UNSET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, + {0xDC01, 0x31, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static void mt9m114_stop_stream(struct msm_sensor_ctrl_t *s_ctrl) {} + +static struct msm_camera_i2c_conf_array mt9m114_init_conf[] = { + {mt9m114_recommend_settings, + ARRAY_SIZE(mt9m114_recommend_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_config_change_settings, + ARRAY_SIZE(mt9m114_config_change_settings), + 0, MSM_CAMERA_I2C_WORD_DATA}, +}; + +static struct msm_camera_i2c_conf_array mt9m114_confs[] = { + {mt9m114_720p_settings, + ARRAY_SIZE(mt9m114_720p_settings), 0, MSM_CAMERA_I2C_WORD_DATA}, +}; + +static struct msm_camera_i2c_reg_conf mt9m114_saturation[][1] = { + {{0xCC12, 0x00},}, + {{0xCC12, 0x1A},}, + {{0xCC12, 0x34},}, + {{0xCC12, 0x4E},}, + {{0xCC12, 0x68},}, + {{0xCC12, 0x80},}, + {{0xCC12, 0x9A},}, + {{0xCC12, 0xB4},}, + {{0xCC12, 0xCE},}, + {{0xCC12, 0xE8},}, + {{0xCC12, 0xFF},}, +}; + +static struct msm_camera_i2c_reg_conf mt9m114_refresh[] = { + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_REFRESH, + MSM_CAMERA_I2C_UNSET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, + {MT9M114_COMMAND_REGISTER, (MT9M114_COMMAND_REGISTER_OK | + MT9M114_COMMAND_REGISTER_REFRESH), MSM_CAMERA_I2C_WORD_DATA, + MSM_CAMERA_I2C_CMD_WRITE}, + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_REFRESH, + MSM_CAMERA_I2C_UNSET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, + {MT9M114_COMMAND_REGISTER, MT9M114_COMMAND_REGISTER_OK, + MSM_CAMERA_I2C_SET_WORD_MASK, MSM_CAMERA_I2C_CMD_POLL}, +}; + +static struct msm_camera_i2c_conf_array mt9m114_saturation_confs[][2] = { + {{mt9m114_saturation[0], + ARRAY_SIZE(mt9m114_saturation[0]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[1], + ARRAY_SIZE(mt9m114_saturation[1]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[2], + ARRAY_SIZE(mt9m114_saturation[2]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[3], + ARRAY_SIZE(mt9m114_saturation[3]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[4], + ARRAY_SIZE(mt9m114_saturation[4]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[5], + ARRAY_SIZE(mt9m114_saturation[5]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[6], + ARRAY_SIZE(mt9m114_saturation[6]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[7], + ARRAY_SIZE(mt9m114_saturation[7]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[8], + ARRAY_SIZE(mt9m114_saturation[8]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[9], + ARRAY_SIZE(mt9m114_saturation[9]), 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, + {{mt9m114_saturation[10], + ARRAY_SIZE(mt9m114_saturation[10]), + 0, MSM_CAMERA_I2C_WORD_DATA}, + {mt9m114_refresh, + ARRAY_SIZE(mt9m114_refresh), 0, MSM_CAMERA_I2C_WORD_DATA},}, +}; + +static int mt9m114_saturation_enum_map[] = { + MSM_V4L2_SATURATION_L0, + MSM_V4L2_SATURATION_L1, + MSM_V4L2_SATURATION_L2, + MSM_V4L2_SATURATION_L3, + MSM_V4L2_SATURATION_L4, + MSM_V4L2_SATURATION_L5, + MSM_V4L2_SATURATION_L6, + MSM_V4L2_SATURATION_L7, + MSM_V4L2_SATURATION_L8, + MSM_V4L2_SATURATION_L9, + MSM_V4L2_SATURATION_L10, +}; + +static struct msm_camera_i2c_enum_conf_array mt9m114_saturation_enum_confs = { + .conf = &mt9m114_saturation_confs[0][0], + .conf_enum = mt9m114_saturation_enum_map, + .num_enum = ARRAY_SIZE(mt9m114_saturation_enum_map), + .num_index = ARRAY_SIZE(mt9m114_saturation_confs), + .num_conf = ARRAY_SIZE(mt9m114_saturation_confs[0]), + .data_type = MSM_CAMERA_I2C_WORD_DATA, +}; + +struct msm_sensor_v4l2_ctrl_info_t mt9m114_v4l2_ctrl_info[] = { + { + .ctrl_id = V4L2_CID_SATURATION, + .min = MSM_V4L2_SATURATION_L0, + .max = MSM_V4L2_SATURATION_L10, + .step = 1, + .enum_cfg_settings = &mt9m114_saturation_enum_confs, + .s_v4l2_ctrl = msm_sensor_s_ctrl_by_enum, + }, +}; + +static struct msm_sensor_output_info_t mt9m114_dimensions[] = { + { + .x_output = 0x500, + .y_output = 0x2D0, + .line_length_pclk = 0x500, + .frame_length_lines = 0x2D0, + .vt_pixel_clk = 48000000, + .op_pixel_clk = 128000000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csid_vc_cfg mt9m114_cid_cfg[] = { + {0, CSI_YUV422_8, CSI_DECODE_8BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, +}; + +static struct msm_camera_csi2_params mt9m114_csi_params = { + .csid_params = { + .lane_cnt = 1, + .lut_params = { + .num_cid = 2, + .vc_cfg = mt9m114_cid_cfg, + }, + }, + .csiphy_params = { + .lane_cnt = 1, + .settle_cnt = 0x14, + }, +}; + +static struct msm_camera_csi2_params *mt9m114_csi_params_array[] = { + &mt9m114_csi_params, + &mt9m114_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t mt9m114_reg_addr = { + .x_output = 0xC868, + .y_output = 0xC86A, + .line_length_pclk = 0xC868, + .frame_length_lines = 0xC86A, +}; + +static struct msm_sensor_id_info_t mt9m114_id_info = { + .sensor_id_reg_addr = 0x0, + .sensor_id = 0x2481, +}; + +static const struct i2c_device_id mt9m114_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&mt9m114_s_ctrl}, + { } +}; + +static struct i2c_driver mt9m114_i2c_driver = { + .id_table = mt9m114_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client mt9m114_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&mt9m114_i2c_driver); +} + +static struct v4l2_subdev_core_ops mt9m114_subdev_core_ops = { + .s_ctrl = msm_sensor_v4l2_s_ctrl, + .queryctrl = msm_sensor_v4l2_query_ctrl, + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops mt9m114_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops mt9m114_subdev_ops = { + .core = &mt9m114_subdev_core_ops, + .video = &mt9m114_subdev_video_ops, +}; + +static struct msm_sensor_fn_t mt9m114_func_tbl = { + .sensor_start_stream = msm_sensor_start_stream, + .sensor_stop_stream = mt9m114_stop_stream, + .sensor_setting = msm_sensor_setting, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, +}; + +static struct msm_sensor_reg_t mt9m114_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = mt9m114_config_change_settings, + .start_stream_conf_size = ARRAY_SIZE(mt9m114_config_change_settings), + .init_settings = &mt9m114_init_conf[0], + .init_size = ARRAY_SIZE(mt9m114_init_conf), + .mode_settings = &mt9m114_confs[0], + .output_settings = &mt9m114_dimensions[0], + .num_conf = ARRAY_SIZE(mt9m114_confs), +}; + +static struct msm_sensor_ctrl_t mt9m114_s_ctrl = { + .msm_sensor_reg = &mt9m114_regs, + .msm_sensor_v4l2_ctrl_info = mt9m114_v4l2_ctrl_info, + .num_v4l2_ctrl = ARRAY_SIZE(mt9m114_v4l2_ctrl_info), + .sensor_i2c_client = &mt9m114_sensor_i2c_client, + .sensor_i2c_addr = 0x90, + .sensor_output_reg_addr = &mt9m114_reg_addr, + .sensor_id_info = &mt9m114_id_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csi_params = &mt9m114_csi_params_array[0], + .msm_sensor_mutex = &mt9m114_mut, + .sensor_i2c_driver = &mt9m114_i2c_driver, + .sensor_v4l2_subdev_info = mt9m114_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(mt9m114_subdev_info), + .sensor_v4l2_subdev_ops = &mt9m114_subdev_ops, + .func_tbl = &mt9m114_func_tbl, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Aptina 1.26MP YUV sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sensors/ov2720.c b/drivers/media/video/msm/sensors/ov2720.c new file mode 100644 index 0000000000000000000000000000000000000000..40867fbde584811c7781e2494a2cbcf983a16dda --- /dev/null +++ b/drivers/media/video/msm/sensors/ov2720.c @@ -0,0 +1,829 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#include "ov2720.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; + +static struct msm_camera_i2c_reg_conf ov2720_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf ov2720_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf ov2720_groupon_settings[] = { + {0x3208, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf ov2720_groupoff_settings[] = { + {0x3208, 0x10}, + {0x3208, 0xA0}, +}; + +static struct msm_camera_i2c_reg_conf ov2720_prev_settings[] = { + {0x3800, 0x00}, + {0x3801, 0x02}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x07}, + {0x3805, 0xA1}, + {0x3806, 0x04}, + {0x3807, 0x47}, + {0x3810, 0x00}, + {0x3811, 0x09}, + {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, 0xcf}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x03}, + {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_camera_i2c_reg_conf ov2720_720_settings[] = { + {0x3800, 0x01}, + {0x3801, 0x4a}, + {0x3802, 0x00}, + {0x3803, 0xba}, + {0x3804, 0x06}, + {0x3805, 0x51+32}, + {0x3806, 0x03}, + {0x3807, 0x8d+24}, + {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_camera_i2c_reg_conf ov2720_vga_settings[] = { + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x02}, + {0x3804, 0x07}, + {0x3805, 0x97+32}, + {0x3806, 0x04}, + {0x3807, 0x45+24}, + {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_camera_i2c_reg_conf ov2720_60fps_settings[] = { + {0x3718, 0x10}, + {0x3702, 0x18}, + {0x373a, 0x3c}, + {0x3715, 0x01}, + {0x3703, 0x1d}, + {0x3705, 0x0b}, + {0x3730, 0x1f}, + {0x3704, 0x3f}, + {0x3f06, 0x1d}, + {0x371c, 0x00}, + {0x371d, 0x83}, + {0x371e, 0x00}, + {0x371f, 0xb6}, + {0x3708, 0x63}, + {0x3709, 0x52}, + {0x3800, 0x01}, + {0x3801, 0x42}, + {0x3802, 0x00}, + {0x3803, 0x40}, + {0x3804, 0x06}, + {0x3805, 0x61}, + {0x3806, 0x04}, + {0x3807, 0x08}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x380c, 0x03}, + {0x380d, 0x0c}, + {0x380e, 0x02}, + {0x380f, 0x00}, + {0x3810, 0x00}, + {0x3811, 0x0f}, + {0x3812, 0x00}, + {0x3813, 0x02}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x02}, + {0x3a09, 0x67}, + {0x3a0a, 0x02}, + {0x3a0b, 0x00}, + {0x3a0d, 0x00}, + {0x3a0e, 0x00}, + {0x4520, 0x0a}, + {0x4837, 0x29}, + {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, 0xcf}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x07}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, + {0x3035, 0x30}, + {0x3036, 0x14}, + {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}, + {0x3011, 0x22}, + {0x3a00, 0x58}, +}; + +static struct msm_camera_i2c_reg_conf ov2720_90fps_settings[] = { + {0x3718, 0x10}, + {0x3702, 0x18}, + {0x373a, 0x3c}, + {0x3715, 0x01}, + {0x3703, 0x1d}, + {0x3705, 0x0b}, + {0x3730, 0x1f}, + {0x3704, 0x3f}, + {0x3f06, 0x1d}, + {0x371c, 0x00}, + {0x371d, 0x83}, + {0x371e, 0x00}, + {0x371f, 0xb6}, + {0x3708, 0x63}, + {0x3709, 0x52}, + {0x3800, 0x01}, + {0x3801, 0x42}, + {0x3802, 0x00}, + {0x3803, 0x40}, + {0x3804, 0x06}, + {0x3805, 0x61}, + {0x3806, 0x04}, + {0x3807, 0x08}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x380c, 0x03}, + {0x380d, 0x0c}, + {0x380e, 0x02}, + {0x380f, 0x00}, + {0x3810, 0x00}, + {0x3811, 0x0f}, + {0x3812, 0x00}, + {0x3813, 0x02}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x02}, + {0x3a09, 0x67}, + {0x3a0a, 0x02}, + {0x3a0b, 0x00}, + {0x3a0d, 0x00}, + {0x3a0e, 0x00}, + {0x4520, 0x0a}, + {0x4837, 0x29}, + {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, 0xcf}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x07}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, + {0x3035, 0x30}, + {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}, + {0x3011, 0x22}, + {0x3a00, 0x58}, +}; + +static struct msm_camera_i2c_reg_conf ov2720_120fps_settings[] = { + {0x3718, 0x10}, + {0x3702, 0x18}, + {0x373a, 0x3c}, + {0x3715, 0x01}, + {0x3703, 0x1d}, + {0x3705, 0x0b}, + {0x3730, 0x1f}, + {0x3704, 0x3f}, + {0x3f06, 0x1d}, + {0x371c, 0x00}, + {0x371d, 0x83}, + {0x371e, 0x00}, + {0x371f, 0xb6}, + {0x3708, 0x63}, + {0x3709, 0x52}, + {0x3800, 0x01}, + {0x3801, 0x42}, + {0x3802, 0x00}, + {0x3803, 0x40}, + {0x3804, 0x06}, + {0x3805, 0x61}, + {0x3806, 0x04}, + {0x3807, 0x08}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x380c, 0x03}, + {0x380d, 0x0c}, + {0x380e, 0x02}, + {0x380f, 0x00}, + {0x3810, 0x00}, + {0x3811, 0x0f}, + {0x3812, 0x00}, + {0x3813, 0x02}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x02}, + {0x3a09, 0x67}, + {0x3a0a, 0x02}, + {0x3a0b, 0x00}, + {0x3a0d, 0x00}, + {0x3a0e, 0x00}, + {0x4520, 0x0a}, + {0x4837, 0x29}, + {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, 0xcf}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x07}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, + {0x3035, 0x10}, + {0x3036, 0x14}, + {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}, + {0x3011, 0x22}, + {0x3a00, 0x58}, +}; + +static struct msm_camera_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 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_camera_i2c_conf_array ov2720_init_conf[] = { + {&ov2720_recommend_settings[0], + ARRAY_SIZE(ov2720_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array ov2720_confs[] = { + {&ov2720_prev_settings[0], + ARRAY_SIZE(ov2720_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov2720_vga_settings[0], + ARRAY_SIZE(ov2720_vga_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov2720_720_settings[0], + ARRAY_SIZE(ov2720_720_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov2720_60fps_settings[0], + ARRAY_SIZE(ov2720_60fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov2720_90fps_settings[0], + ARRAY_SIZE(ov2720_90fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov2720_120fps_settings[0], + ARRAY_SIZE(ov2720_120fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t ov2720_dimensions[] = { + { + .x_output = 0x78C, + .y_output = 0x444, + .line_length_pclk = 0x85c, + .frame_length_lines = 0x460, + .vt_pixel_clk = 72000000, + .op_pixel_clk = 72000000, + .binning_factor = 1, + }, + { + .x_output = 0x510, + .y_output = 0x278, + .line_length_pclk = 0x85c, + .frame_length_lines = 0x460, + .vt_pixel_clk = 72000000, + .op_pixel_clk = 72000000, + .binning_factor = 1, + }, + { + .x_output = 0x298, + .y_output = 0x1F2, + .line_length_pclk = 0x85c, + .frame_length_lines = 0x460, + .vt_pixel_clk = 72000000, + .op_pixel_clk = 72000000, + .binning_factor = 1, + }, + { + .x_output = 0x280, /* 640 */ + .y_output = 0x1E0, /* 480 */ + .line_length_pclk = 0x30C, /* 780 */ + .frame_length_lines = 0x200, /* 512 */ + .vt_pixel_clk = 24000000, + .op_pixel_clk = 24000000, + .binning_factor = 1, + }, + { + .x_output = 0x280, /* 640 */ + .y_output = 0x1E0, /* 480 */ + .line_length_pclk = 0x30C, /* 780 */ + .frame_length_lines = 0x200, /* 512 */ + .vt_pixel_clk = 36000000, + .op_pixel_clk = 36000000, + .binning_factor = 1, + }, + { + .x_output = 0x280, /* 640 */ + .y_output = 0x1E0, /* 480 */ + .line_length_pclk = 0x30C, /* 780 */ + .frame_length_lines = 0x200, /* 512 */ + .vt_pixel_clk = 48000000, + .op_pixel_clk = 48000000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csid_vc_cfg ov2720_cid_cfg[] = { + {0, CSI_RAW10, CSI_DECODE_10BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, +}; + +static struct msm_camera_csi2_params ov2720_csi_params = { + .csid_params = { + .lane_cnt = 2, + .lut_params = { + .num_cid = 2, + .vc_cfg = ov2720_cid_cfg, + }, + }, + .csiphy_params = { + .lane_cnt = 2, + .settle_cnt = 0x1B, + }, +}; + +static struct msm_camera_csi2_params *ov2720_csi_params_array[] = { + &ov2720_csi_params, + &ov2720_csi_params, + &ov2720_csi_params, + &ov2720_csi_params, + &ov2720_csi_params, + &ov2720_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t ov2720_reg_addr = { + .x_output = 0x3808, + .y_output = 0x380a, + .line_length_pclk = 0x380c, + .frame_length_lines = 0x380e, +}; + +static struct msm_sensor_id_info_t ov2720_id_info = { + .sensor_id_reg_addr = 0x300A, + .sensor_id = 0x2720, +}; + +static struct msm_sensor_exp_gain_info_t ov2720_exp_gain_info = { + .coarse_int_time_addr = 0x3501, + .global_gain_addr = 0x3508, + .vert_offset = 6, +}; + +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; + uint8_t int_time[3]; + fl_lines = + (s_ctrl->curr_frame_length_lines * s_ctrl->fps_divider) / Q10; + offset = s_ctrl->sensor_exp_gain_info->vert_offset; + if (line > (fl_lines - offset)) + fl_lines = line + offset; + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, fl_lines, + MSM_CAMERA_I2C_WORD_DATA); + int_time[0] = line >> 12; + int_time[1] = line >> 4; + int_time[2] = line << 4; + msm_camera_i2c_write_seq(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr-1, + &int_time[0], 3); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, gain, + MSM_CAMERA_I2C_WORD_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + 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 struct msm_camera_i2c_client ov2720_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&ov2720_i2c_driver); +} + +static struct v4l2_subdev_core_ops ov2720_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +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_fn_t ov2720_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = ov2720_write_exp_gain, + .sensor_write_snapshot_exp_gain = ov2720_write_exp_gain, + .sensor_setting = msm_sensor_setting, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, + .sensor_adjust_frame_lines = msm_sensor_adjust_frame_lines, +}; + +static struct msm_sensor_reg_t ov2720_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .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), + .mode_settings = &ov2720_confs[0], + .output_settings = &ov2720_dimensions[0], + .num_conf = ARRAY_SIZE(ov2720_confs), +}; + +static struct msm_sensor_ctrl_t ov2720_s_ctrl = { + .msm_sensor_reg = &ov2720_regs, + .sensor_i2c_client = &ov2720_sensor_i2c_client, + .sensor_i2c_addr = 0x6C, + .sensor_output_reg_addr = &ov2720_reg_addr, + .sensor_id_info = &ov2720_id_info, + .sensor_exp_gain_info = &ov2720_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csi_params = &ov2720_csi_params_array[0], + .msm_sensor_mutex = &ov2720_mut, + .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 = &ov2720_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Omnivision 2MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/media/video/msm/sensors/ov2720.h b/drivers/media/video/msm/sensors/ov2720.h new file mode 100644 index 0000000000000000000000000000000000000000..7077a7db9299af93456972a4d3cacb3970f47b18 --- /dev/null +++ b/drivers/media/video/msm/sensors/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/sensors/ov5647_v4l2.c b/drivers/media/video/msm/sensors/ov5647_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..aac2f2b98cd1fba626a7d820ceeb4148e3ead9d2 --- /dev/null +++ b/drivers/media/video/msm/sensors/ov5647_v4l2.c @@ -0,0 +1,868 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#include "msm.h" +#define SENSOR_NAME "ov5647" +#define PLATFORM_DRIVER_NAME "msm_camera_ov5647" +#define ov5647_obj ov5647_##obj + +static struct msm_sensor_ctrl_t ov5647_s_ctrl; + +DEFINE_MUTEX(ov5647_mut); + +static struct msm_camera_i2c_reg_conf ov5647_start_settings[] = { + {0x4202, 0x00}, /* streaming on */ +}; + +static struct msm_camera_i2c_reg_conf ov5647_stop_settings[] = { + {0x4202, 0x0f}, /* streaming off*/ +}; + +static struct msm_camera_i2c_reg_conf ov5647_groupon_settings[] = { + {0x3208, 0x0}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_groupoff_settings[] = { + {0x3208, 0x10}, + {0x3208, 0xa0}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_prev_settings[] = { + /*1280*960 Reference Setting 24M MCLK 2lane 280Mbps/lane 30fps + for back to preview*/ + {0x3035, 0x21}, + {0x3036, 0x37}, + {0x3821, 0x07}, + {0x3820, 0x41}, + {0x3612, 0x09}, + {0x3618, 0x00}, + {0x380c, 0x07}, + {0x380d, 0x68}, + {0x380e, 0x03}, + {0x380f, 0xd8}, + {0x3814, 0x31}, + {0x3815, 0x31}, + {0x3709, 0x52}, + {0x3808, 0x05}, + {0x3809, 0x00}, + {0x380a, 0x03}, + {0x380b, 0xc0}, + {0x3800, 0x00}, + {0x3801, 0x18}, + {0x3802, 0x00}, + {0x3803, 0x0e}, + {0x3804, 0x0a}, + {0x3805, 0x27}, + {0x3806, 0x07}, + {0x3807, 0x95}, + {0x4004, 0x02}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_snap_settings[] = { + /*2608*1952 Reference Setting 24M MCLK 2lane 280Mbps/lane 30fps*/ + {0x3035, 0x21}, + {0x3036, 0x4f}, + {0x3821, 0x06}, + {0x3820, 0x00}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x380c, 0x0a}, + {0x380d, 0x8c}, + {0x380e, 0x07}, + {0x380f, 0xb0}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3709, 0x12}, + {0x3808, 0x0a}, + {0x3809, 0x30}, + {0x380a, 0x07}, + {0x380b, 0xa0}, + {0x3800, 0x00}, + {0x3801, 0x04}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3b}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x4004, 0x04}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_video_60fps_settings[] = { + {0x3035, 0x21}, + {0x3036, 0x38}, + {0x3821, 0x07}, + {0x3820, 0x41}, + {0x3612, 0x49}, + {0x3618, 0x00}, + {0x380c, 0x07}, + {0x380d, 0x30}, + {0x380e, 0x01}, + {0x380f, 0xf8}, + {0x3814, 0x71}, + {0x3815, 0x71}, + {0x3709, 0x52}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x3800, 0x00}, + {0x3801, 0x10}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x2f}, + {0x3806, 0x07}, + {0x3807, 0x9f}, + {0x4004, 0x02}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_video_90fps_settings[] = { + {0x3035, 0x11}, + {0x3036, 0x2a}, + {0x3821, 0x07}, + {0x3820, 0x41}, + {0x3612, 0x49}, + {0x3618, 0x00}, + {0x380c, 0x07}, + {0x380d, 0x30}, + {0x380e, 0x01}, + {0x380f, 0xf8}, + {0x3814, 0x71}, + {0x3815, 0x71}, + {0x3709, 0x52}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, + {0x3800, 0x00}, + {0x3801, 0x10}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x2f}, + {0x3806, 0x07}, + {0x3807, 0x9f}, + {0x4004, 0x02}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_zsl_settings[] = { + {0x3035, 0x21}, + {0x3036, 0x2f}, + {0x3821, 0x06}, + {0x3820, 0x00}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x380c, 0x0a}, + {0x380d, 0x8c}, + {0x380e, 0x07}, + {0x380f, 0xb0}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3709, 0x12}, + {0x3808, 0x0a}, + {0x3809, 0x30}, + {0x380a, 0x07}, + {0x380b, 0xa0}, + {0x3800, 0x00}, + {0x3801, 0x04}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3b}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x4004, 0x04}, +}; + +static struct msm_camera_i2c_reg_conf ov5647_recommend_settings[] = { + {0x3035, 0x11}, + {0x303c, 0x11}, + {0x370c, 0x03}, + {0x5000, 0x06}, + {0x5003, 0x08}, + {0x5a00, 0x08}, + {0x3000, 0xff}, + {0x3001, 0xff}, + {0x3002, 0xff}, + {0x301d, 0xf0}, + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + {0x3c01, 0x80}, + {0x3b07, 0x0c}, + {0x3708, 0x64}, + {0x3630, 0x2e}, + {0x3632, 0xe2}, + {0x3633, 0x23}, + {0x3634, 0x44}, + {0x3620, 0x64}, + {0x3621, 0xe0}, + {0x3600, 0x37}, + {0x3704, 0xa0}, + {0x3703, 0x5a}, + {0x3715, 0x78}, + {0x3717, 0x01}, + {0x3731, 0x02}, + {0x370b, 0x60}, + {0x3705, 0x1a}, + {0x3f05, 0x02}, + {0x3f06, 0x10}, + {0x3f01, 0x0a}, + {0x3a08, 0x01}, + {0x3a0f, 0x58}, + {0x3a10, 0x50}, + {0x3a1b, 0x58}, + {0x3a1e, 0x50}, + {0x3a11, 0x60}, + {0x3a1f, 0x28}, + {0x4001, 0x02}, + {0x4000, 0x09}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3017, 0xe0}, + {0x301c, 0xfc}, + {0x3636, 0x06}, + {0x3016, 0x08}, + {0x3827, 0xec}, + {0x3018, 0x44}, + {0x3035, 0x21}, + {0x3106, 0xf5}, + {0x3034, 0x18}, + {0x301c, 0xf8}, + /*lens setting*/ + {0x5000, 0x86}, + {0x5800, 0x11}, + {0x5801, 0x0c}, + {0x5802, 0x0a}, + {0x5803, 0x0b}, + {0x5804, 0x0d}, + {0x5805, 0x13}, + {0x5806, 0x09}, + {0x5807, 0x05}, + {0x5808, 0x03}, + {0x5809, 0x03}, + {0x580a, 0x06}, + {0x580b, 0x08}, + {0x580c, 0x05}, + {0x580d, 0x01}, + {0x580e, 0x00}, + {0x580f, 0x00}, + {0x5810, 0x02}, + {0x5811, 0x06}, + {0x5812, 0x05}, + {0x5813, 0x01}, + {0x5814, 0x00}, + {0x5815, 0x00}, + {0x5816, 0x02}, + {0x5817, 0x06}, + {0x5818, 0x09}, + {0x5819, 0x05}, + {0x581a, 0x04}, + {0x581b, 0x04}, + {0x581c, 0x06}, + {0x581d, 0x09}, + {0x581e, 0x11}, + {0x581f, 0x0c}, + {0x5820, 0x0b}, + {0x5821, 0x0b}, + {0x5822, 0x0d}, + {0x5823, 0x13}, + {0x5824, 0x22}, + {0x5825, 0x26}, + {0x5826, 0x26}, + {0x5827, 0x24}, + {0x5828, 0x24}, + {0x5829, 0x24}, + {0x582a, 0x22}, + {0x582b, 0x20}, + {0x582c, 0x22}, + {0x582d, 0x26}, + {0x582e, 0x22}, + {0x582f, 0x22}, + {0x5830, 0x42}, + {0x5831, 0x22}, + {0x5832, 0x02}, + {0x5833, 0x24}, + {0x5834, 0x22}, + {0x5835, 0x22}, + {0x5836, 0x22}, + {0x5837, 0x26}, + {0x5838, 0x42}, + {0x5839, 0x26}, + {0x583a, 0x06}, + {0x583b, 0x26}, + {0x583c, 0x24}, + {0x583d, 0xce}, + /* manual AWB,manual AE,close Lenc,open WBC*/ + {0x3503, 0x03}, /*manual AE*/ + {0x3501, 0x10}, + {0x3502, 0x80}, + {0x350a, 0x00}, + {0x350b, 0x7f}, + {0x5001, 0x01}, /*manual AWB*/ + {0x5180, 0x08}, + {0x5186, 0x04}, + {0x5187, 0x00}, + {0x5188, 0x04}, + {0x5189, 0x00}, + {0x518a, 0x04}, + {0x518b, 0x00}, + {0x5000, 0x06}, /*No lenc,WBC on*/ + {0x4005, 0x18}, + {0x4051, 0x8f}, +}; + + +static struct msm_camera_i2c_conf_array ov5647_init_conf[] = { + {&ov5647_recommend_settings[0], + ARRAY_SIZE(ov5647_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array ov5647_confs[] = { + {&ov5647_snap_settings[0], + ARRAY_SIZE(ov5647_snap_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov5647_prev_settings[0], + ARRAY_SIZE(ov5647_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov5647_video_60fps_settings[0], + ARRAY_SIZE(ov5647_video_60fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov5647_video_90fps_settings[0], + ARRAY_SIZE(ov5647_video_90fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&ov5647_zsl_settings[0], + ARRAY_SIZE(ov5647_zsl_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_camera_csi_params ov5647_csi_params = { + .data_format = CSI_8BIT, + .lane_cnt = 2, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 10, +}; + +static struct v4l2_subdev_info ov5647_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_output_info_t ov5647_dimensions[] = { + { /* For SNAPSHOT */ + .x_output = 0xA30, /*2608*/ /*for 5Mp*/ + .y_output = 0x7A0, /*1952*/ + .line_length_pclk = 0xA8C, + .frame_length_lines = 0x7B0, + .vt_pixel_clk = 79704000, + .op_pixel_clk = 159408000, + .binning_factor = 0x0, + }, + { /* For PREVIEW */ + .x_output = 0x500, /*1280*/ + .y_output = 0x3C0, /*960*/ + .line_length_pclk = 0x768, + .frame_length_lines = 0x3D8, + .vt_pixel_clk = 55969920, + .op_pixel_clk = 159408000, + .binning_factor = 0x0, + }, + { /* For 60fps */ + .x_output = 0x280, /*640*/ + .y_output = 0x1E0, /*480*/ + .line_length_pclk = 0x73C, + .frame_length_lines = 0x1F8, + .vt_pixel_clk = 56004480, + .op_pixel_clk = 159408000, + .binning_factor = 0x0, + }, + { /* For 90fps */ + .x_output = 0x280, /*640*/ + .y_output = 0x1E0, /*480*/ + .line_length_pclk = 0x73C, + .frame_length_lines = 0x1F8, + .vt_pixel_clk = 56004480, + .op_pixel_clk = 159408000, + .binning_factor = 0x0, + }, + { /* For ZSL */ + .x_output = 0xA30, /*2608*/ /*for 5Mp*/ + .y_output = 0x7A0, /*1952*/ + .line_length_pclk = 0xA8C, + .frame_length_lines = 0x7B0, + .vt_pixel_clk = 79704000, + .op_pixel_clk = 159408000, + .binning_factor = 0x0, + }, + +}; + +static struct msm_sensor_output_reg_addr_t ov5647_reg_addr = { + .x_output = 0x3808, + .y_output = 0x380A, + .line_length_pclk = 0x380C, + .frame_length_lines = 0x380E, +}; + +static struct msm_camera_csi_params *ov5647_csi_params_array[] = { + &ov5647_csi_params, /* Snapshot */ + &ov5647_csi_params, /* Preview */ + &ov5647_csi_params, /* 60fps */ + &ov5647_csi_params, /* 90fps */ + &ov5647_csi_params, /* ZSL */ +}; + +static struct msm_sensor_id_info_t ov5647_id_info = { + .sensor_id_reg_addr = 0x300a, + .sensor_id = 0x5647, +}; + +static struct msm_sensor_exp_gain_info_t ov5647_exp_gain_info = { + .coarse_int_time_addr = 0x3500, + .global_gain_addr = 0x350A, + .vert_offset = 4, +}; + +void ov5647_sensor_reset_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_camera_i2c_write( + s_ctrl->sensor_i2c_client, + 0x103, 0x1, + MSM_CAMERA_I2C_BYTE_DATA); +} + +static int32_t ov5647_write_pict_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + + static uint16_t max_line = 1964; + uint8_t gain_lsb, gain_hsb; + u8 intg_time_hsb, intg_time_msb, intg_time_lsb; + + gain_lsb = (uint8_t) (gain); + gain_hsb = (uint8_t)((gain & 0x300)>>8); + + CDBG(KERN_ERR "snapshot exposure seting 0x%x, 0x%x, %d" + , gain, line, line); + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + if (line > 1964 && line <= 1968) { + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, + (uint8_t)((line+4) >> 8), + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines + 1, + (uint8_t)((line+4) & 0x00FF), + MSM_CAMERA_I2C_BYTE_DATA); + max_line = line + 4; + } else if (max_line > 1968) { + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, + (uint8_t)(1968 >> 8), + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines + 1, + (uint8_t)(1968 & 0x00FF), + MSM_CAMERA_I2C_BYTE_DATA); + max_line = 1968; + } + + + line = line<<4; + /* ov5647 need this operation */ + intg_time_hsb = (u8)(line>>16); + intg_time_msb = (u8) ((line & 0xFF00) >> 8); + intg_time_lsb = (u8) (line & 0x00FF); + + /* FIXME for BLC trigger */ + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + intg_time_hsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + intg_time_msb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 2, + intg_time_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + /* gain */ + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, + gain_hsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr + 1, + gain_lsb^0x1, + MSM_CAMERA_I2C_BYTE_DATA); + + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + intg_time_hsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + intg_time_msb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 2, + intg_time_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + /* gain */ + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, + gain_hsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr + 1, + gain_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + return 0; + +} + + +static int32_t ov5647_write_prev_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + + static uint16_t max_line = 984; + u8 intg_time_hsb, intg_time_msb, intg_time_lsb; + uint8_t gain_lsb, gain_hsb; + + CDBG(KERN_ERR "preview exposure setting 0x%x, 0x%x, %d", + gain, line, line); + + gain_lsb = (uint8_t) (gain); + gain_hsb = (uint8_t)((gain & 0x300)>>8); + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + + /* adjust frame rate */ + if (line > 980 && line <= 984) { + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, + (uint8_t)((line+4) >> 8), + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines + 1, + (uint8_t)((line+4) & 0x00FF), + MSM_CAMERA_I2C_BYTE_DATA); + max_line = line + 4; + } else if (max_line > 984) { + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, + (uint8_t)(984 >> 8), + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines + 1 , + (uint8_t)(984 & 0x00FF), + MSM_CAMERA_I2C_BYTE_DATA); + max_line = 984; + } + + line = line<<4; + /* ov5647 need this operation */ + intg_time_hsb = (u8)(line>>16); + intg_time_msb = (u8) ((line & 0xFF00) >> 8); + intg_time_lsb = (u8) (line & 0x00FF); + + + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + intg_time_hsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + intg_time_msb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 2, + intg_time_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + /* gain */ + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, + gain_hsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr + 1, + gain_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + + return 0; +} + +static const struct i2c_device_id ov5647_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&ov5647_s_ctrl}, + { } +}; +int32_t ov5647_sensor_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int32_t rc = 0; + struct msm_sensor_ctrl_t *s_ctrl; + + rc = msm_sensor_i2c_probe(client, id); + + if (client->dev.platform_data == NULL) { + pr_err("%s: NULL sensor data\n", __func__); + return -EFAULT; + } + + s_ctrl = client->dev.platform_data; + if (s_ctrl->sensordata->pmic_gpio_enable) + lcd_camera_power_onoff(0); + + return rc; +} + +static struct i2c_driver ov5647_i2c_driver = { + .id_table = ov5647_i2c_id, + .probe = ov5647_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + + + +static struct msm_camera_i2c_client ov5647_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&ov5647_i2c_driver); +} + +static struct v4l2_subdev_core_ops ov5647_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops ov5647_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops ov5647_subdev_ops = { + .core = &ov5647_subdev_core_ops, + .video = &ov5647_subdev_video_ops, +}; + +int32_t ov5647_sensor_power_down(struct msm_sensor_ctrl_t *s_ctrl) +{ + struct msm_camera_sensor_info *info = NULL; + unsigned short rdata; + int rc; + + info = s_ctrl->sensordata; + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x4202, 0xf, + MSM_CAMERA_I2C_BYTE_DATA); + msleep(20); + rc = msm_camera_i2c_read(s_ctrl->sensor_i2c_client, 0x3018, + &rdata, MSM_CAMERA_I2C_WORD_DATA); + CDBG("ov5647_sensor_power_down: %d\n", rc); + rdata |= 0x18; + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + 0x3018, rdata, + MSM_CAMERA_I2C_WORD_DATA); + msleep(20); + gpio_direction_output(info->sensor_pwd, 1); + usleep_range(5000, 5100); + msm_sensor_power_down(s_ctrl); + return 0; +} + +int32_t ov5647_sensor_power_up(struct msm_sensor_ctrl_t *s_ctrl) +{ + int32_t rc = 0; + struct msm_camera_sensor_info *info = NULL; + + info = s_ctrl->sensordata; + gpio_direction_output(info->sensor_pwd, 1); + gpio_direction_output(info->sensor_reset, 0); + usleep_range(10000, 11000); + if (info->pmic_gpio_enable) { + info->pmic_gpio_enable = 0; + lcd_camera_power_onoff(1); + } + usleep_range(10000, 11000); + rc = msm_sensor_power_up(s_ctrl); + if (rc < 0) { + CDBG("%s: msm_sensor_power_up failed\n", __func__); + return rc; + } + + /* turn on ldo and vreg */ + + gpio_direction_output(info->sensor_pwd, 0); + msleep(20); + gpio_direction_output(info->sensor_reset, 1); + msleep(25); + + return rc; + +} + +static int32_t vfe_clk = 266667000; + +int32_t ov5647_sensor_setting(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int res) +{ + int32_t rc = 0; + static int csi_config; + s_ctrl->func_tbl->sensor_stop_stream(s_ctrl); + if (csi_config == 0 || res == 0) + msleep(66); + else + msleep(266); + + msm_camera_i2c_write( + s_ctrl->sensor_i2c_client, + 0x4800, 0x25, + MSM_CAMERA_I2C_BYTE_DATA); + if (update_type == MSM_SENSOR_REG_INIT) { + CDBG("Register INIT\n"); + s_ctrl->curr_csi_params = NULL; + msm_camera_i2c_write( + s_ctrl->sensor_i2c_client, + 0x103, 0x1, + MSM_CAMERA_I2C_BYTE_DATA); + msm_sensor_enable_debugfs(s_ctrl); + msm_sensor_write_init_settings(s_ctrl); + csi_config = 0; + } else if (update_type == MSM_SENSOR_UPDATE_PERIODIC) { + CDBG("PERIODIC : %d\n", res); + msm_sensor_write_conf_array( + s_ctrl->sensor_i2c_client, + s_ctrl->msm_sensor_reg->mode_settings, res); + msleep(30); + if (!csi_config) { + s_ctrl->curr_csic_params = s_ctrl->csic_params[res]; + CDBG("CSI config in progress\n"); + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_CSIC_CFG, + s_ctrl->curr_csic_params); + CDBG("CSI config is done\n"); + mb(); + msleep(30); + csi_config = 1; + msm_camera_i2c_write( + s_ctrl->sensor_i2c_client, + 0x100, 0x1, + MSM_CAMERA_I2C_BYTE_DATA); + } + msm_camera_i2c_write( + s_ctrl->sensor_i2c_client, + 0x4800, 0x4, + MSM_CAMERA_I2C_BYTE_DATA); + msleep(266); + if (res == MSM_SENSOR_RES_4) + v4l2_subdev_notify(&s_ctrl->sensor_v4l2_subdev, + NOTIFY_PCLK_CHANGE, + &vfe_clk); + s_ctrl->func_tbl->sensor_start_stream(s_ctrl); + msleep(50); + } + return rc; +} +static struct msm_sensor_fn_t ov5647_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = ov5647_write_prev_exp_gain, + .sensor_write_snapshot_exp_gain = ov5647_write_pict_exp_gain, + .sensor_csi_setting = ov5647_sensor_setting, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = ov5647_sensor_power_up, + .sensor_power_down = ov5647_sensor_power_down, +}; + +static struct msm_sensor_reg_t ov5647_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = ov5647_start_settings, + .start_stream_conf_size = ARRAY_SIZE(ov5647_start_settings), + .stop_stream_conf = ov5647_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(ov5647_stop_settings), + .group_hold_on_conf = ov5647_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(ov5647_groupon_settings), + .group_hold_off_conf = ov5647_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(ov5647_groupoff_settings), + .init_settings = &ov5647_init_conf[0], + .init_size = ARRAY_SIZE(ov5647_init_conf), + .mode_settings = &ov5647_confs[0], + .output_settings = &ov5647_dimensions[0], + .num_conf = ARRAY_SIZE(ov5647_confs), +}; + +static struct msm_sensor_ctrl_t ov5647_s_ctrl = { + .msm_sensor_reg = &ov5647_regs, + .sensor_i2c_client = &ov5647_sensor_i2c_client, + .sensor_i2c_addr = 0x36 << 1 , + .sensor_output_reg_addr = &ov5647_reg_addr, + .sensor_id_info = &ov5647_id_info, + .sensor_exp_gain_info = &ov5647_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &ov5647_csi_params_array[0], + .msm_sensor_mutex = &ov5647_mut, + .sensor_i2c_driver = &ov5647_i2c_driver, + .sensor_v4l2_subdev_info = ov5647_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(ov5647_subdev_info), + .sensor_v4l2_subdev_ops = &ov5647_subdev_ops, + .func_tbl = &ov5647_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Omnivision WXGA Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sensors/ov7692_v4l2.c b/drivers/media/video/msm/sensors/ov7692_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..e7970d5f458a7848a39097bde1d7c5a8cfb001af --- /dev/null +++ b/drivers/media/video/msm/sensors/ov7692_v4l2.c @@ -0,0 +1,332 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "ov7692" + +DEFINE_MUTEX(ov7692_mut); +static struct msm_sensor_ctrl_t ov7692_s_ctrl; + +static struct msm_camera_i2c_reg_conf ov7692_start_settings[] = { + {0x0e, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf ov7692_stop_settings[] = { + {0x0e, 0x08}, +}; + +static struct msm_camera_i2c_reg_conf ov7692_recommend_settings[] = { + {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}, + {0xd0, 0x48}, + {0x82, 0x03}, + {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}, + {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 struct msm_camera_i2c_reg_conf ov7692_full_settings[] = { + {0xcc, 0x02}, + {0xcd, 0x80}, + {0xce, 0x01}, + {0xcf, 0xe0}, + {0xc8, 0x02}, + {0xc9, 0x80}, + {0xca, 0x01}, + {0xcb, 0xe0}, +}; + +static struct v4l2_subdev_info ov7692_subdev_info[] = { + { + .code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, + /* more can be supported, to be added later */ +}; + + +static struct msm_camera_i2c_conf_array ov7692_init_conf[] = { + {&ov7692_recommend_settings[0], + ARRAY_SIZE(ov7692_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array ov7692_confs[] = { + {&ov7692_full_settings[0], + ARRAY_SIZE(ov7692_full_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t ov7692_dimensions[] = { + { + .x_output = 0x280, + .y_output = 0x1E0, + .line_length_pclk = 0x290, + .frame_length_lines = 0x1EC, + .vt_pixel_clk = 9216000, + .op_pixel_clk = 9216000, + .binning_factor = 1, + }, +}; + + +static struct msm_camera_csi_params ov7692_csi_params = { + .data_format = CSI_8BIT, + .lane_cnt = 1, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 0x14, +}; + +static struct msm_camera_csi_params *ov7692_csi_params_array[] = { + &ov7692_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t ov7692_reg_addr = { + .x_output = 0xCC, + .y_output = 0xCE, + .line_length_pclk = 0xC8, + .frame_length_lines = 0xCA, +}; + +static struct msm_sensor_id_info_t ov7692_id_info = { + .sensor_id_reg_addr = 0x0A, + .sensor_id = 0x7692, +}; + +static const struct i2c_device_id ov7692_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&ov7692_s_ctrl}, + { } +}; + + +static struct i2c_driver ov7692_i2c_driver = { + .id_table = ov7692_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client ov7692_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_BYTE_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + int rc = 0; + CDBG("OV7692\n"); + + rc = i2c_add_driver(&ov7692_i2c_driver); + + return rc; +} + +static struct v4l2_subdev_core_ops ov7692_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops ov7692_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops ov7692_subdev_ops = { + .core = &ov7692_subdev_core_ops, + .video = &ov7692_subdev_video_ops, +}; + +int32_t ov7692_sensor_power_up(struct msm_sensor_ctrl_t *s_ctrl) +{ + int32_t rc = 0; + struct msm_camera_sensor_info *info = NULL; + + info = s_ctrl->sensordata; + if (info->pmic_gpio_enable) { + info->sensor_lcd_gpio_onoff(1); + usleep_range(5000, 5100); + } + + rc = msm_sensor_power_up(s_ctrl); + if (rc < 0) { + CDBG("%s: msm_sensor_power_up failed\n", __func__); + return rc; + } + + return rc; +} + +int32_t ov7692_sensor_power_down(struct msm_sensor_ctrl_t *s_ctrl) +{ + int32_t rc = 0; + struct msm_camera_sensor_info *info = NULL; + + rc = msm_sensor_power_down(s_ctrl); + if (rc < 0) + CDBG("%s: msm_sensor_power_down failed\n", __func__); + + info = s_ctrl->sensordata; + if (info->pmic_gpio_enable) { + info->pmic_gpio_enable = 0; + info->sensor_lcd_gpio_onoff(0); + usleep_range(5000, 5100); + } + return rc; +} + +static struct msm_sensor_fn_t ov7692_func_tbl = { + .sensor_start_stream = msm_sensor_start_stream, + .sensor_stop_stream = msm_sensor_stop_stream, + .sensor_csi_setting = msm_sensor_setting1, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = ov7692_sensor_power_up, + .sensor_power_down = ov7692_sensor_power_down, +}; + +static struct msm_sensor_reg_t ov7692_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = ov7692_start_settings, + .start_stream_conf_size = ARRAY_SIZE(ov7692_start_settings), + .stop_stream_conf = ov7692_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(ov7692_stop_settings), + .init_settings = &ov7692_init_conf[0], + .init_size = ARRAY_SIZE(ov7692_init_conf), + .mode_settings = &ov7692_confs[0], + .output_settings = &ov7692_dimensions[0], + .num_conf = ARRAY_SIZE(ov7692_confs), +}; + +static struct msm_sensor_ctrl_t ov7692_s_ctrl = { + .msm_sensor_reg = &ov7692_regs, + .sensor_i2c_client = &ov7692_sensor_i2c_client, + .sensor_i2c_addr = 0x78, + .sensor_output_reg_addr = &ov7692_reg_addr, + .sensor_id_info = &ov7692_id_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &ov7692_csi_params_array[0], + .msm_sensor_mutex = &ov7692_mut, + .sensor_i2c_driver = &ov7692_i2c_driver, + .sensor_v4l2_subdev_info = ov7692_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(ov7692_subdev_info), + .sensor_v4l2_subdev_ops = &ov7692_subdev_ops, + .func_tbl = &ov7692_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Omnivision VGA YUV sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sensors/ov9726_v4l2.c b/drivers/media/video/msm/sensors/ov9726_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..61c693e6d4a45bba06dc1e5d756d301472d12230 --- /dev/null +++ b/drivers/media/video/msm/sensors/ov9726_v4l2.c @@ -0,0 +1,281 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "ov9726" +#define PLATFORM_DRIVER_NAME "msm_camera_ov9726" +#define ov9726_obj ov9726_##obj + +DEFINE_MUTEX(ov9726_mut); +static struct msm_sensor_ctrl_t ov9726_s_ctrl; + +static struct msm_camera_i2c_reg_conf ov9726_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf ov9726_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf ov9726_groupon_settings[] = { + {0x0104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf ov9726_groupoff_settings[] = { + {0x0104, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf ov9726_prev_settings[] = { +}; + +static struct msm_camera_i2c_reg_conf ov9726_recommend_settings[] = { + {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 */ +}; + +static struct v4l2_subdev_info ov9726_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_camera_i2c_conf_array ov9726_init_conf[] = { + {&ov9726_recommend_settings[0], + ARRAY_SIZE(ov9726_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array ov9726_confs[] = { + {&ov9726_prev_settings[0], + ARRAY_SIZE(ov9726_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t ov9726_dimensions[] = { + { + .x_output = 0x510, /* 1296 */ + .y_output = 0x328, /* 808 */ + .line_length_pclk = 0x680, /* 1664 */ + .frame_length_lines = 0x3C1, /* 961 */ + .vt_pixel_clk = 320000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csi_params ov9726_csi_params = { + .data_format = CSI_10BIT, + .lane_cnt = 1, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 7, +}; + +static struct msm_camera_csi_params *ov9726_csi_params_array[] = { + &ov9726_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t ov9726_reg_addr = { + .x_output = 0x034c, + .y_output = 0x034e, + .line_length_pclk = 0x0342, + .frame_length_lines = 0x0340, +}; + +static struct msm_sensor_id_info_t ov9726_id_info = { + .sensor_id_reg_addr = 0x0000, + .sensor_id = 0x9726, +}; + +static struct msm_sensor_exp_gain_info_t ov9726_exp_gain_info = { + .coarse_int_time_addr = 0x0202, + .global_gain_addr = 0x0204, + .vert_offset = 6, +}; + +static const struct i2c_device_id ov9726_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&ov9726_s_ctrl}, + { } +}; + +static struct i2c_driver ov9726_i2c_driver = { + .id_table = ov9726_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client ov9726_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&ov9726_i2c_driver); +} + +static struct v4l2_subdev_core_ops ov9726_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops ov9726_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops ov9726_subdev_ops = { + .core = &ov9726_subdev_core_ops, + .video = &ov9726_subdev_video_ops, +}; + +static struct msm_sensor_fn_t ov9726_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = msm_sensor_write_exp_gain1, + .sensor_write_snapshot_exp_gain = msm_sensor_write_exp_gain1, + .sensor_csi_setting = msm_sensor_setting1, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, +}; + +static struct msm_sensor_reg_t ov9726_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = ov9726_start_settings, + .start_stream_conf_size = ARRAY_SIZE(ov9726_start_settings), + .stop_stream_conf = ov9726_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(ov9726_stop_settings), + .group_hold_on_conf = ov9726_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(ov9726_groupon_settings), + .group_hold_off_conf = ov9726_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(ov9726_groupoff_settings), + .init_settings = &ov9726_init_conf[0], + .init_size = ARRAY_SIZE(ov9726_init_conf), + .mode_settings = &ov9726_confs[0], + .output_settings = &ov9726_dimensions[0], + .num_conf = ARRAY_SIZE(ov9726_confs), +}; + +static struct msm_sensor_ctrl_t ov9726_s_ctrl = { + .msm_sensor_reg = &ov9726_regs, + .sensor_i2c_client = &ov9726_sensor_i2c_client, + .sensor_i2c_addr = 0x20, + .sensor_output_reg_addr = &ov9726_reg_addr, + .sensor_id_info = &ov9726_id_info, + .sensor_exp_gain_info = &ov9726_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &ov9726_csi_params_array[0], + .msm_sensor_mutex = &ov9726_mut, + .sensor_i2c_driver = &ov9726_i2c_driver, + .sensor_v4l2_subdev_info = ov9726_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(ov9726_subdev_info), + .sensor_v4l2_subdev_ops = &ov9726_subdev_ops, + .func_tbl = &ov9726_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Omnivision WXGA Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/media/video/msm/sensors/s5k3l1yx.c b/drivers/media/video/msm/sensors/s5k3l1yx.c new file mode 100644 index 0000000000000000000000000000000000000000..debda88f97657ae5e06b7fe36c267bb490c8e600 --- /dev/null +++ b/drivers/media/video/msm/sensors/s5k3l1yx.c @@ -0,0 +1,696 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "s5k3l1yx" +#define PLATFORM_DRIVER_NAME "msm_camera_s5k3l1yx" + +DEFINE_MUTEX(s5k3l1yx_mut); +static struct msm_sensor_ctrl_t s5k3l1yx_s_ctrl; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_groupon_settings[] = { + {0x104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_groupoff_settings[] = { + {0x104, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_snap_settings[] = { + {0x0501, 0x00}, /* compression_algorithim_L(1d) */ + {0x0112, 0x0A}, /* CCP_data_format_H */ + {0x0113, 0x0A}, /* CCP_data_format_L raw8=0808 ,DCPM10 -->8= 0A08 */ + {0x0306, 0x00}, /* pll_multiplier */ + {0x0307, 0xA5}, /* pll_multiplier */ + {0x0202, 0x09}, /* coarse_integration_time */ + {0x0203, 0x32}, /* coarse_integration_time */ + {0x0340, 0x0B}, /* frame_length_lines */ + {0x0341, 0xEC}, /* frame_length_lines */ + {0x0342, 0x14}, /* line_length_pck */ + {0x0343, 0xD8}, /* line_length_pck */ + {0x0344, 0x00}, /* x_addr_start */ + {0x0345, 0x08}, /* x_addr_start */ + {0x0346, 0x00}, /* y_addr_start */ + {0x0347, 0x00}, /* y_addr_start */ + {0x0348, 0x0F}, /* x_addr_end */ + {0x0349, 0xA7}, /* x_addr_end */ + {0x034A, 0x0B}, /* y_addr_end */ + {0x034B, 0xC7}, /* y_addr_end */ + {0x034C, 0x0F}, /* x_output_size */ + {0x034D, 0xA0}, /* x_output_size */ + {0x034E, 0x0B}, /* y_output_size */ + {0x034F, 0xC8}, /* y_output_size */ + {0x0380, 0x00}, /* x_even_inc */ + {0x0381, 0x01}, /* x_even_inc */ + {0x0382, 0x00}, /* x_odd_inc */ + {0x0383, 0x01}, /* x_odd_inc */ + {0x0384, 0x00}, /* y_even_inc */ + {0x0385, 0x01}, /* y_even_inc */ + {0x0386, 0x00}, /* y_odd_inc */ + {0x0387, 0x01}, /* y_odd_inc */ + {0x0900, 0x00}, /* binning_mode */ + {0x0901, 0x22}, /* binning_type */ + {0x0902, 0x01}, /* binning_weighting */ +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_prev_settings[] = { + {0x0501, 0x00}, /* compression_algorithim_L(1d) */ + {0x0112, 0x0A}, /* CCP_data_format_H */ + {0x0113, 0x0A}, /* CCP_data_format_L raw8=0808 ,DCPM10 -->8= 0A08 */ + {0x0306, 0x00}, /* pll_multiplier */ + {0x0307, 0xA5}, /* pll_multiplier */ + {0x0202, 0x06}, /* coarse_integration_time */ + {0x0203, 0x00}, /* coarse_integration_time */ + {0x0340, 0x09}, /* frame_length_lines */ + {0x0341, 0x98}, /* frame_length_lines */ + {0x0342, 0x11}, /* line_length_pck */ + {0x0343, 0x80}, /* line_length_pck */ + {0x0344, 0x00}, /* x_addr_start */ + {0x0345, 0x18}, /* x_addr_start */ + {0x0346, 0x00}, /* y_addr_start */ + {0x0347, 0x00}, /* y_addr_start */ + {0x0348, 0x0F}, /* x_addr_end */ + {0x0349, 0x97}, /* x_addr_end */ + {0x034A, 0x0B}, /* y_addr_end */ + {0x034B, 0xC7}, /* y_addr_end */ + {0x034C, 0x07}, /* x_output_size */ + {0x034D, 0xC0}, /* x_output_size */ + {0x034E, 0x05}, /* y_output_size */ + {0x034F, 0xE4}, /* y_output_size */ + {0x0380, 0x00}, /* x_even_inc */ + {0x0381, 0x01}, /* x_even_inc */ + {0x0382, 0x00}, /* x_odd_inc */ + {0x0383, 0x03}, /* x_odd_inc */ + {0x0384, 0x00}, /* y_even_inc */ + {0x0385, 0x01}, /* y_even_inc */ + {0x0386, 0x00}, /* y_odd_inc */ + {0x0387, 0x03}, /* y_odd_inc */ + {0x0900, 0x01}, /* binning_mode */ + {0x0901, 0x22}, /* binning_type */ + {0x0902, 0x01}, /* binning_weighting */ +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_video_60fps_settings[] = { + {0x0501, 0x00}, /* compression_algorithim_L(1d) */ + {0x0112, 0x0A}, /* CCP_data_format_H */ + {0x0113, 0x0A}, /* CCP_data_format_L raw8=0808 ,DCPM10 -->8= 0A08 */ + {0x0306, 0x00}, /* pll_multiplier */ + {0x0307, 0xA5}, /* pll_multiplier */ + {0x0202, 0x03}, /* coarse_integration_time */ + {0x0203, 0xD8}, /* coarse_integration_time */ + {0x0340, 0x03}, /* frame_length_lines */ + {0x0341, 0xE0}, /* frame_length_lines */ + {0x0342, 0x14}, /* line_length_pck */ + {0x0343, 0xD8}, /* line_length_pck */ + {0x0344, 0x01}, /* x_addr_start */ + {0x0345, 0x20}, /* x_addr_start */ + {0x0346, 0x02}, /* y_addr_start */ + {0x0347, 0x24}, /* y_addr_start */ + {0x0348, 0x0E}, /* x_addr_end */ + {0x0349, 0xA0}, /* x_addr_end */ + {0x034A, 0x09}, /* y_addr_end */ + {0x034B, 0xA4}, /* y_addr_end */ + {0x034C, 0x03}, /* x_output_size */ + {0x034D, 0x60}, /* x_output_size */ + {0x034E, 0x01}, /* y_output_size */ + {0x034F, 0xE0}, /* y_output_size */ + {0x0380, 0x00}, /* x_even_inc */ + {0x0381, 0x01}, /* x_even_inc */ + {0x0382, 0x00}, /* x_odd_inc */ + {0x0383, 0x07}, /* x_odd_inc */ + {0x0384, 0x00}, /* y_even_inc */ + {0x0385, 0x01}, /* y_even_inc */ + {0x0386, 0x00}, /* y_odd_inc */ + {0x0387, 0x07}, /* y_odd_inc */ + {0x0900, 0x01}, /* binning_mode */ + {0x0901, 0x44}, /* binning_type */ + {0x0902, 0x01}, /* binning_weighting */ +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_video_90fps_settings[] = { + {0x0501, 0x00}, /* compression_algorithim_L(1d) */ + {0x0112, 0x0A}, /* CCP_data_format_H */ + {0x0113, 0x0A}, /* CCP_data_format_L raw8=0808 ,DCPM10 -->8= 0A08 */ + {0x0306, 0x00}, /* pll_multiplier */ + {0x0307, 0xA5}, /* pll_multiplier */ + {0x0202, 0x02}, /* coarse_integration_time */ + {0x0203, 0x90}, /* coarse_integration_time */ + {0x0340, 0x02}, /* frame_length_lines */ + {0x0341, 0x98}, /* frame_length_lines */ + {0x0342, 0x14}, /* line_length_pck */ + {0x0343, 0xD8}, /* line_length_pck */ + {0x0344, 0x01}, /* x_addr_start */ + {0x0345, 0x20}, /* x_addr_start */ + {0x0346, 0x02}, /* y_addr_start */ + {0x0347, 0x24}, /* y_addr_start */ + {0x0348, 0x0E}, /* x_addr_end */ + {0x0349, 0xA0}, /* x_addr_end */ + {0x034A, 0x09}, /* y_addr_end */ + {0x034B, 0xA4}, /* y_addr_end */ + {0x034C, 0x03}, /* x_output_size */ + {0x034D, 0x60}, /* x_output_size */ + {0x034E, 0x01}, /* y_output_size */ + {0x034F, 0xE0}, /* y_output_size */ + {0x0380, 0x00}, /* x_even_inc */ + {0x0381, 0x01}, /* x_even_inc */ + {0x0382, 0x00}, /* x_odd_inc */ + {0x0383, 0x07}, /* x_odd_inc */ + {0x0384, 0x00}, /* y_even_inc */ + {0x0385, 0x01}, /* y_even_inc */ + {0x0386, 0x00}, /* y_odd_inc */ + {0x0387, 0x07}, /* y_odd_inc */ + {0x0900, 0x01}, /* binning_mode */ + {0x0901, 0x44}, /* binning_type */ + {0x0902, 0x01}, /* binning_weighting */ +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_video_120fps_settings[] = { + {0x0501, 0x00}, /* compression_algorithim_L(1d) */ + {0x0112, 0x0A}, /* CCP_data_format_H */ + {0x0113, 0x0A}, /* CCP_data_format_L raw8=0808 ,DCPM10 -->8= 0A08 */ + {0x0306, 0x00}, /* pll_multiplier */ + {0x0307, 0xA5}, /* pll_multiplier */ + {0x0202, 0x01}, /* coarse_integration_time */ + {0x0203, 0xFA}, /* coarse_integration_time */ + {0x0340, 0x02}, /* frame_length_lines */ + {0x0341, 0x02}, /* frame_length_lines */ + {0x0342, 0x14}, /* line_length_pck */ + {0x0343, 0xD8}, /* line_length_pck */ + {0x0344, 0x01}, /* x_addr_start */ + {0x0345, 0x20}, /* x_addr_start */ + {0x0346, 0x02}, /* y_addr_start */ + {0x0347, 0x24}, /* y_addr_start */ + {0x0348, 0x0E}, /* x_addr_end */ + {0x0349, 0xA0}, /* x_addr_end */ + {0x034A, 0x09}, /* y_addr_end */ + {0x034B, 0xA4}, /* y_addr_end */ + {0x034C, 0x03}, /* x_output_size */ + {0x034D, 0x60}, /* x_output_size */ + {0x034E, 0x01}, /* y_output_size */ + {0x034F, 0xE0}, /* y_output_size */ + {0x0380, 0x00}, /* x_even_inc */ + {0x0381, 0x01}, /* x_even_inc */ + {0x0382, 0x00}, /* x_odd_inc */ + {0x0383, 0x07}, /* x_odd_inc */ + {0x0384, 0x00}, /* y_even_inc */ + {0x0385, 0x01}, /* y_even_inc */ + {0x0386, 0x00}, /* y_odd_inc */ + {0x0387, 0x07}, /* y_odd_inc */ + {0x0900, 0x01}, /* binning_mode */ + {0x0901, 0x44}, /* binning_type */ + {0x0902, 0x01}, /* binning_weighting */ +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_dpcm_settings[] = { + {0x0501, 0x01}, /* compression_algorithim_L(1d) */ + {0x0112, 0x0A}, /* CCP_data_format_H */ + {0x0113, 0x08}, /* CCP_data_format_L raw8=0808 ,DCPM10 -->8= 0A08 */ + {0x0306, 0x00}, /* pll_multiplier */ + {0x0307, 0xA0}, /* pll_multiplier */ + {0x0202, 0x09}, /* coarse_integration_time */ + {0x0203, 0x32}, /* coarse_integration_time */ + {0x0340, 0x0B}, /* frame_length_lines */ + {0x0341, 0xEC}, /* frame_length_lines */ + {0x0342, 0x11}, /* line_length_pck */ + {0x0343, 0x80}, /* line_length_pck */ + {0x0344, 0x00}, /* x_addr_start */ + {0x0345, 0x08}, /* x_addr_start */ + {0x0346, 0x00}, /* y_addr_start */ + {0x0347, 0x00}, /* y_addr_start */ + {0x0348, 0x0F}, /* x_addr_end */ + {0x0349, 0xA7}, /* x_addr_end */ + {0x034A, 0x0B}, /* y_addr_end */ + {0x034B, 0xC7}, /* y_addr_end */ + {0x034C, 0x0F}, /* x_output_size */ + {0x034D, 0xA0}, /* x_output_size */ + {0x034E, 0x0B}, /* y_output_size */ + {0x034F, 0xC8}, /* y_output_size */ + {0x0380, 0x00}, /* x_even_inc */ + {0x0381, 0x01}, /* x_even_inc */ + {0x0382, 0x00}, /* x_odd_inc */ + {0x0383, 0x01}, /* x_odd_inc */ + {0x0384, 0x00}, /* y_even_inc */ + {0x0385, 0x01}, /* y_even_inc */ + {0x0386, 0x00}, /* y_odd_inc */ + {0x0387, 0x01}, /* y_odd_inc */ + {0x0900, 0x00}, /* binning_mode */ + {0x0901, 0x22}, /* binning_type */ + {0x0902, 0x01}, /* binning_weighting */ +}; + +static struct msm_camera_i2c_reg_conf s5k3l1yx_recommend_settings[] = { + {0x0100, 0x00}, + {0x0103, 0x01}, /* software_reset */ + {0x0104, 0x00}, /* grouped_parameter_hold */ + {0x0114, 0x03}, /* CSI_lane_mode, 4 lane setting */ + {0x0120, 0x00}, /* gain_mode, global analogue gain*/ + {0x0121, 0x00}, /* exposure_mode, global exposure */ + {0x0136, 0x18}, /* Extclk_frequency_mhz */ + {0x0137, 0x00}, /* Extclk_frequency_mhz */ + {0x0200, 0x08}, /* fine_integration_time */ + {0x0201, 0x88}, /* fine_integration_time */ + {0x0204, 0x00}, /* analogue_gain_code_global */ + {0x0205, 0x20}, /* analogue_gain_code_global */ + {0x020E, 0x01}, /* digital_gain_greenR */ + {0x020F, 0x00}, /* digital_gain_greenR */ + {0x0210, 0x01}, /* digital_gain_red */ + {0x0211, 0x00}, /* digital_gain_red */ + {0x0212, 0x01}, /* digital_gain_blue */ + {0x0213, 0x00}, /* digital_gain_blue */ + {0x0214, 0x01}, /* digital_gain_greenB */ + {0x0215, 0x00}, /* digital_gain_greenB */ + {0x0300, 0x00}, /* vt_pix_clk_div */ + {0x0301, 0x02}, /* vt_pix_clk_div */ + {0x0302, 0x00}, /* vt_sys_clk_div */ + {0x0303, 0x01}, /* vt_sys_clk_div */ + {0x0304, 0x00}, /* pre_pll_clk_div */ + {0x0305, 0x06}, /* pre_pll_clk_div */ + {0x0308, 0x00}, /* op_pix_clk_div */ + {0x0309, 0x02}, /* op_pix_clk_div */ + {0x030A, 0x00}, /* op_sys_clk_div */ + {0x030B, 0x01}, /* op_sys_clk_div */ + {0x0800, 0x00}, /* tclk_post for D-PHY control */ + {0x0801, 0x00}, /* ths_prepare for D-PHY control */ + {0x0802, 0x00}, /* ths_zero_min for D-PHY control */ + {0x0803, 0x00}, /* ths_trail for D-PHY control */ + {0x0804, 0x00}, /* tclk_trail_min for D-PHY control */ + {0x0805, 0x00}, /* tclk_prepare for D-PHY control */ + {0x0806, 0x00}, /* tclk_zero_zero for D-PHY control */ + {0x0807, 0x00}, /* tlpx for D-PHY control */ + {0x0820, 0x02}, /* requested_link_bit_rate_mbps */ + {0x0821, 0x94}, /* requested_link_bit_rate_mbps */ + {0x0822, 0x00}, /* requested_link_bit_rate_mbps */ + {0x0823, 0x00}, /* requested_link_bit_rate_mbps */ + {0x3000, 0x0A}, + {0x3001, 0xF7}, + {0x3002, 0x0A}, + {0x3003, 0xF7}, + {0x3004, 0x08}, + {0x3005, 0xF8}, + {0x3006, 0x5B}, + {0x3007, 0x73}, + {0x3008, 0x49}, + {0x3009, 0x0C}, + {0x300A, 0xF8}, + {0x300B, 0x4E}, + {0x300C, 0x64}, + {0x300D, 0x5C}, + {0x300E, 0x71}, + {0x300F, 0x0C}, + {0x3010, 0x6A}, + {0x3011, 0x14}, + {0x3012, 0x14}, + {0x3013, 0x0C}, + {0x3014, 0x24}, + {0x3015, 0x4F}, + {0x3016, 0x86}, + {0x3017, 0x0E}, + {0x3018, 0x2C}, + {0x3019, 0x30}, + {0x301A, 0x31}, + {0x301B, 0x32}, + {0x301C, 0xFF}, + {0x301D, 0x33}, + {0x301E, 0x5C}, + {0x301F, 0xFA}, + {0x3020, 0x36}, + {0x3021, 0x46}, + {0x3022, 0x92}, + {0x3023, 0xF5}, + {0x3024, 0x6E}, + {0x3025, 0x19}, + {0x3026, 0x32}, + {0x3027, 0x4B}, + {0x3028, 0x04}, + {0x3029, 0x50}, + {0x302A, 0x0C}, + {0x302B, 0x04}, + {0x302C, 0xEF}, + {0x302D, 0xC1}, + {0x302E, 0x74}, + {0x302F, 0x40}, + {0x3030, 0x00}, + {0x3031, 0x00}, + {0x3032, 0x00}, + {0x3033, 0x00}, + {0x3034, 0x0F}, + {0x3035, 0x01}, + {0x3036, 0x00}, + {0x3037, 0x00}, + {0x3038, 0x88}, + {0x3039, 0x98}, + {0x303A, 0x1F}, + {0x303B, 0x01}, + {0x303C, 0x00}, + {0x303D, 0x03}, + {0x303E, 0x2F}, + {0x303F, 0x09}, + {0x3040, 0xFF}, + {0x3041, 0x22}, + {0x3042, 0x03}, + {0x3043, 0x03}, + {0x3044, 0x20}, + {0x3045, 0x10}, + {0x3046, 0x10}, + {0x3047, 0x08}, + {0x3048, 0x10}, + {0x3049, 0x01}, + {0x304A, 0x00}, + {0x304B, 0x80}, + {0x304C, 0x80}, + {0x304D, 0x00}, + {0x304E, 0x00}, + {0x304F, 0x00}, + {0x3051, 0x09}, + {0x3052, 0xC4}, + {0x305A, 0xE0}, + {0x323D, 0x04}, + {0x323E, 0x38}, + {0x3305, 0xDD}, + {0x3050, 0x01}, + {0x3202, 0x01}, + {0x3203, 0x01}, + {0x3204, 0x01}, + {0x3205, 0x01}, + {0x3206, 0x01}, + {0x3207, 0x01}, + {0x320A, 0x05}, + {0x320B, 0x20}, + {0x3235, 0xB7}, + {0x324C, 0x04}, + {0x324A, 0x07}, + {0x3902, 0x01}, + {0x3915, 0x70}, + {0x3916, 0x80}, + {0x3A00, 0x01}, + {0x3A06, 0x03}, + {0x3B29, 0x01}, + {0x3C11, 0x08}, + {0x3C12, 0x7B}, + {0x3C13, 0xC0}, + {0x3C14, 0x70}, + {0x3C15, 0x80}, + {0x3C20, 0x00}, + {0x3C23, 0x03}, + {0x3C24, 0x00}, + {0x3C50, 0x72}, + {0x3C51, 0x85}, + {0x3C53, 0x40}, + {0x3C55, 0xA0}, + {0x3D00, 0x00}, + {0x3D01, 0x00}, + {0x3D11, 0x01}, + {0x3486, 0x05}, + {0x3B35, 0x06}, + {0x3A05, 0x01}, + {0x3A07, 0x2B}, + {0x3A09, 0x01}, + {0x3940, 0xFF}, + {0x3300, 0x00}, + {0x3900, 0xFF}, + {0x3914, 0x08}, + {0x3A01, 0x0F}, + {0x3A02, 0xA0}, + {0x3A03, 0x0B}, + {0x3A04, 0xC8}, + {0x3701, 0x00}, + {0x3702, 0x00}, + {0x3703, 0x00}, + {0x3704, 0x00}, + {0x0101, 0x00}, /* image_orientation, mirror & flip off*/ + {0x0105, 0x01}, /* mask_corrupted_frames */ + {0x0110, 0x00}, /* CSI-2_channel_identifier */ + {0x3942, 0x01}, /* [0] 1:mipi, 0:pvi */ + {0x0B00, 0x00}, +}; + +static struct v4l2_subdev_info s5k3l1yx_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_camera_i2c_conf_array s5k3l1yx_init_conf[] = { + {&s5k3l1yx_recommend_settings[0], + ARRAY_SIZE(s5k3l1yx_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array s5k3l1yx_confs[] = { + {&s5k3l1yx_snap_settings[0], + ARRAY_SIZE(s5k3l1yx_snap_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&s5k3l1yx_prev_settings[0], + ARRAY_SIZE(s5k3l1yx_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&s5k3l1yx_video_60fps_settings[0], + ARRAY_SIZE(s5k3l1yx_video_60fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&s5k3l1yx_video_90fps_settings[0], + ARRAY_SIZE(s5k3l1yx_video_90fps_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&s5k3l1yx_video_120fps_settings[0], + ARRAY_SIZE(s5k3l1yx_video_120fps_settings), 0, + MSM_CAMERA_I2C_BYTE_DATA}, + {&s5k3l1yx_dpcm_settings[0], + ARRAY_SIZE(s5k3l1yx_dpcm_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t s5k3l1yx_dimensions[] = { + /* 20 fps snapshot */ + { + .x_output = 4000, + .y_output = 3016, + .line_length_pclk = 5336, + .frame_length_lines = 3052, + .vt_pixel_clk = 330000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, + /* 30 fps preview */ + { + .x_output = 1984, + .y_output = 1508, + .line_length_pclk = 4480, + .frame_length_lines = 2456, + .vt_pixel_clk = 330000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, + /* 60 fps video */ + { + .x_output = 864, + .y_output = 480, + .line_length_pclk = 5336, + .frame_length_lines = 992, + .vt_pixel_clk = 330000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, + /* 90 fps video */ + { + .x_output = 864, + .y_output = 480, + .line_length_pclk = 5336, + .frame_length_lines = 664, + .vt_pixel_clk = 330000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, + /* 120 fps video */ + { + .x_output = 864, + .y_output = 480, + .line_length_pclk = 5336, + .frame_length_lines = 514, + .vt_pixel_clk = 330000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, + /* 24 fps snapshot */ + { + .x_output = 4000, + .y_output = 3016, + .line_length_pclk = 4480, + .frame_length_lines = 3052, + .vt_pixel_clk = 330000000, + .op_pixel_clk = 320000000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csid_vc_cfg s5k3l1yx_cid_cfg[] = { + {0, CSI_RAW10, CSI_DECODE_10BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, +}; + +static struct msm_camera_csi2_params s5k3l1yx_csi_params = { + .csid_params = { + .lane_cnt = 4, + .lut_params = { + .num_cid = ARRAY_SIZE(s5k3l1yx_cid_cfg), + .vc_cfg = s5k3l1yx_cid_cfg, + }, + }, + .csiphy_params = { + .lane_cnt = 4, + .settle_cnt = 0x1B, + }, +}; + +static struct msm_camera_csid_vc_cfg s5k3l1yx_cid_dpcm_cfg[] = { + {0, CSI_RAW8, CSI_DECODE_DPCM_10_8_10}, +}; + +static struct msm_camera_csi2_params s5k3l1yx_csi_dpcm_params = { + .csid_params = { + .lane_assign = 0xe4, + .lane_cnt = 4, + .lut_params = { + .num_cid = ARRAY_SIZE(s5k3l1yx_cid_dpcm_cfg), + .vc_cfg = s5k3l1yx_cid_dpcm_cfg, + }, + }, + .csiphy_params = { + .lane_cnt = 4, + .settle_cnt = 0x1B, + }, +}; + +static struct msm_camera_csi2_params *s5k3l1yx_csi_params_array[] = { + &s5k3l1yx_csi_params, + &s5k3l1yx_csi_params, + &s5k3l1yx_csi_params, + &s5k3l1yx_csi_params, + &s5k3l1yx_csi_params, + &s5k3l1yx_csi_dpcm_params, +}; + +static struct msm_sensor_output_reg_addr_t s5k3l1yx_reg_addr = { + .x_output = 0x34C, + .y_output = 0x34E, + .line_length_pclk = 0x342, + .frame_length_lines = 0x340, +}; + +static struct msm_sensor_id_info_t s5k3l1yx_id_info = { + .sensor_id_reg_addr = 0x0, + .sensor_id = 0x3121, +}; + +static struct msm_sensor_exp_gain_info_t s5k3l1yx_exp_gain_info = { + .coarse_int_time_addr = 0x202, + .global_gain_addr = 0x204, + .vert_offset = 8, +}; + +static const struct i2c_device_id s5k3l1yx_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&s5k3l1yx_s_ctrl}, + { } +}; + +static struct i2c_driver s5k3l1yx_i2c_driver = { + .id_table = s5k3l1yx_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client s5k3l1yx_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&s5k3l1yx_i2c_driver); +} + +static struct v4l2_subdev_core_ops s5k3l1yx_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops s5k3l1yx_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops s5k3l1yx_subdev_ops = { + .core = &s5k3l1yx_subdev_core_ops, + .video = &s5k3l1yx_subdev_video_ops, +}; + +static struct msm_sensor_fn_t s5k3l1yx_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = msm_sensor_write_exp_gain1, + .sensor_write_snapshot_exp_gain = msm_sensor_write_exp_gain1, + .sensor_setting = msm_sensor_setting, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, + .sensor_adjust_frame_lines = msm_sensor_adjust_frame_lines, +}; + +static struct msm_sensor_reg_t s5k3l1yx_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = s5k3l1yx_start_settings, + .start_stream_conf_size = ARRAY_SIZE(s5k3l1yx_start_settings), + .stop_stream_conf = s5k3l1yx_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(s5k3l1yx_stop_settings), + .group_hold_on_conf = s5k3l1yx_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(s5k3l1yx_groupon_settings), + .group_hold_off_conf = s5k3l1yx_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(s5k3l1yx_groupoff_settings), + .init_settings = &s5k3l1yx_init_conf[0], + .init_size = ARRAY_SIZE(s5k3l1yx_init_conf), + .mode_settings = &s5k3l1yx_confs[0], + .output_settings = &s5k3l1yx_dimensions[0], + .num_conf = ARRAY_SIZE(s5k3l1yx_confs), +}; + +static struct msm_sensor_ctrl_t s5k3l1yx_s_ctrl = { + .msm_sensor_reg = &s5k3l1yx_regs, + .sensor_i2c_client = &s5k3l1yx_sensor_i2c_client, + .sensor_i2c_addr = 0x6E, + .sensor_output_reg_addr = &s5k3l1yx_reg_addr, + .sensor_id_info = &s5k3l1yx_id_info, + .sensor_exp_gain_info = &s5k3l1yx_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csi_params = &s5k3l1yx_csi_params_array[0], + .msm_sensor_mutex = &s5k3l1yx_mut, + .sensor_i2c_driver = &s5k3l1yx_i2c_driver, + .sensor_v4l2_subdev_info = s5k3l1yx_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(s5k3l1yx_subdev_info), + .sensor_v4l2_subdev_ops = &s5k3l1yx_subdev_ops, + .func_tbl = &s5k3l1yx_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Samsung 12MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sensors/s5k4e1_v4l2.c b/drivers/media/video/msm/sensors/s5k4e1_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..2d25824add86ccd6a4f80fe4d97041c107478930 --- /dev/null +++ b/drivers/media/video/msm/sensors/s5k4e1_v4l2.c @@ -0,0 +1,532 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#define SENSOR_NAME "s5k4e1" +#define PLATFORM_DRIVER_NAME "msm_camera_s5k4e1" +#define s5k4e1_obj s5k4e1_##obj +#define MSB 1 +#define LSB 0 + +DEFINE_MUTEX(s5k4e1_mut); +static struct msm_sensor_ctrl_t s5k4e1_s_ctrl; + +static struct msm_camera_i2c_reg_conf s5k4e1_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf s5k4e1_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf s5k4e1_groupon_settings[] = { + {0x0104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf s5k4e1_groupoff_settings[] = { + {0x0104, 0x00}, +}; + +static struct msm_camera_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}, +}; + +static struct msm_camera_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}, +}; + +static struct msm_camera_i2c_reg_conf s5k4e1_recommend_settings[] = { + /* Reset setting */ + {0x0103, 0x01}, + /* 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] */ + + /* REC 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}, + + /* PLL settings */ + {0x0305, 0x04}, + {0x0306, 0x00}, + {0x0307, 0x44}, + {0x30B5, 0x00}, + {0x30E2, 0x01},/* num lanes[1:0] = 2 */ + {0x30F1, 0xB0}, +}; + +static struct v4l2_subdev_info s5k4e1_subdev_info[] = { + { + .code = V4L2_MBUS_FMT_SGRBG10_1X10, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, + /* more can be supported, to be added later */ +}; + +static struct msm_camera_i2c_conf_array s5k4e1_init_conf[] = { + {&s5k4e1_recommend_settings[0], + ARRAY_SIZE(s5k4e1_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array s5k4e1_confs[] = { + {&s5k4e1_snap_settings[0], + ARRAY_SIZE(s5k4e1_snap_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&s5k4e1_prev_settings[0], + ARRAY_SIZE(s5k4e1_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t s5k4e1_dimensions[] = { + { + .x_output = 0xA30, + .y_output = 0x7A8, + .line_length_pclk = 0xAB2, + .frame_length_lines = 0x7B4, + .vt_pixel_clk = 81600000, + .op_pixel_clk = 81600000, + .binning_factor = 0, + }, + { + .x_output = 0x518, + .y_output = 0x3D4, + .line_length_pclk = 0xAB2, + .frame_length_lines = 0x3E0, + .vt_pixel_clk = 81600000, + .op_pixel_clk = 81600000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csi_params s5k4e1_csi_params = { + .data_format = CSI_10BIT, + .lane_cnt = 1, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 24, +}; + +static struct msm_camera_csi_params *s5k4e1_csi_params_array[] = { + &s5k4e1_csi_params, + &s5k4e1_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t s5k4e1_reg_addr = { + .x_output = 0x034C, + .y_output = 0x034E, + .line_length_pclk = 0x0342, + .frame_length_lines = 0x0340, +}; + +static struct msm_sensor_id_info_t s5k4e1_id_info = { + .sensor_id_reg_addr = 0x0000, + .sensor_id = 0x4E10, +}; + +static struct msm_sensor_exp_gain_info_t s5k4e1_exp_gain_info = { + .coarse_int_time_addr = 0x0202, + .global_gain_addr = 0x0204, + .vert_offset = 4, +}; + +static inline uint8_t s5k4e1_byte(uint16_t word, uint8_t offset) +{ + return word >> (offset * BITS_PER_BYTE); +} + +static int32_t s5k4e1_write_prev_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x0200; + int32_t rc = 0; + static uint32_t fl_lines, offset; + + pr_info("s5k4e1_write_prev_exp_gain :%d %d\n", gain, line); + offset = s_ctrl->sensor_exp_gain_info->vert_offset; + if (gain > max_legal_gain) { + CDBG("Max legal gain Line:%d\n", __LINE__); + gain = max_legal_gain; + } + + /* Analogue Gain */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, + s5k4e1_byte(gain, MSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr + 1, + s5k4e1_byte(gain, LSB), + MSM_CAMERA_I2C_BYTE_DATA); + + if (line > (s_ctrl->curr_frame_length_lines - offset)) { + fl_lines = line + offset; + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, + s5k4e1_byte(fl_lines, MSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines + 1, + s5k4e1_byte(fl_lines, LSB), + MSM_CAMERA_I2C_BYTE_DATA); + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + s5k4e1_byte(line, MSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + s5k4e1_byte(line, LSB), + MSM_CAMERA_I2C_BYTE_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + } else if (line < (fl_lines - offset)) { + fl_lines = line + offset; + if (fl_lines < s_ctrl->curr_frame_length_lines) + fl_lines = s_ctrl->curr_frame_length_lines; + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + s5k4e1_byte(line, MSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + s5k4e1_byte(line, LSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines, + s5k4e1_byte(fl_lines, MSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->frame_length_lines + 1, + s5k4e1_byte(fl_lines, LSB), + MSM_CAMERA_I2C_BYTE_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + } else { + fl_lines = line+4; + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + s5k4e1_byte(line, MSB), + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + s5k4e1_byte(line, LSB), + MSM_CAMERA_I2C_BYTE_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + } + return rc; +} + +static int32_t s5k4e1_write_pict_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + 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; + 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) { + CDBG("Max legal gain Line:%d\n", __LINE__); + gain = max_legal_gain; + } + + pr_info("s5k4e1_write_exp_gain : gain = %d line = %d\n", gain, line); + line = (uint32_t) (line * s_ctrl->fps_divider); + fl_lines = s_ctrl->curr_frame_length_lines * s_ctrl->fps_divider / Q10; + ll_pck = s_ctrl->curr_line_length_pclk; + + if (fl_lines < (line / Q10)) + ll_ratio = (line / (fl_lines - 4)); + else + ll_ratio = Q10; + + ll_pck = ll_pck * ll_ratio / Q10; + 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); + + s_ctrl->func_tbl->sensor_group_hold_on(s_ctrl); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr, + gain_msb, + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->global_gain_addr + 1, + gain_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->line_length_pclk, + ll_pck_msb, + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_output_reg_addr->line_length_pclk + 1, + ll_pck_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + + /* Coarse Integration Time */ + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr, + intg_time_msb, + MSM_CAMERA_I2C_BYTE_DATA); + msm_camera_i2c_write(s_ctrl->sensor_i2c_client, + s_ctrl->sensor_exp_gain_info->coarse_int_time_addr + 1, + intg_time_lsb, + MSM_CAMERA_I2C_BYTE_DATA); + s_ctrl->func_tbl->sensor_group_hold_off(s_ctrl); + + return 0; +} + +int32_t s5k4e1_sensor_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + struct msm_camera_sensor_info *s_info; + + rc = msm_sensor_i2c_probe(client, id); + + s_info = client->dev.platform_data; + if (s_info == NULL) { + pr_err("%s %s NULL sensor data\n", __func__, client->name); + return -EFAULT; + } + + if (s_info->actuator_info->vcm_enable) { + rc = gpio_request(s_info->actuator_info->vcm_pwd, + "msm_actuator"); + if (rc < 0) + pr_err("%s: gpio_request:msm_actuator %d failed\n", + __func__, s_info->actuator_info->vcm_pwd); + rc = gpio_direction_output(s_info->actuator_info->vcm_pwd, 0); + if (rc < 0) + pr_err("%s: gpio:msm_actuator %d direction can't be set\n", + __func__, s_info->actuator_info->vcm_pwd); + gpio_free(s_info->actuator_info->vcm_pwd); + } + + return rc; +} + +static const struct i2c_device_id s5k4e1_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&s5k4e1_s_ctrl}, + { } +}; + +static struct i2c_driver s5k4e1_i2c_driver = { + .id_table = s5k4e1_i2c_id, + .probe = s5k4e1_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client s5k4e1_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&s5k4e1_i2c_driver); +} + +static struct v4l2_subdev_core_ops s5k4e1_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops s5k4e1_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops s5k4e1_subdev_ops = { + .core = &s5k4e1_subdev_core_ops, + .video = &s5k4e1_subdev_video_ops, +}; + +static struct msm_sensor_fn_t s5k4e1_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_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = s5k4e1_write_prev_exp_gain, + .sensor_write_snapshot_exp_gain = s5k4e1_write_pict_exp_gain, + .sensor_csi_setting = msm_sensor_setting1, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, +}; + +static struct msm_sensor_reg_t s5k4e1_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = s5k4e1_start_settings, + .start_stream_conf_size = ARRAY_SIZE(s5k4e1_start_settings), + .stop_stream_conf = s5k4e1_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(s5k4e1_stop_settings), + .group_hold_on_conf = s5k4e1_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(s5k4e1_groupon_settings), + .group_hold_off_conf = s5k4e1_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(s5k4e1_groupoff_settings), + .init_settings = &s5k4e1_init_conf[0], + .init_size = ARRAY_SIZE(s5k4e1_init_conf), + .mode_settings = &s5k4e1_confs[0], + .output_settings = &s5k4e1_dimensions[0], + .num_conf = ARRAY_SIZE(s5k4e1_confs), +}; + +static struct msm_sensor_ctrl_t s5k4e1_s_ctrl = { + .msm_sensor_reg = &s5k4e1_regs, + .sensor_i2c_client = &s5k4e1_sensor_i2c_client, + .sensor_i2c_addr = 0x6C, + .sensor_output_reg_addr = &s5k4e1_reg_addr, + .sensor_id_info = &s5k4e1_id_info, + .sensor_exp_gain_info = &s5k4e1_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &s5k4e1_csi_params_array[0], + .msm_sensor_mutex = &s5k4e1_mut, + .sensor_i2c_driver = &s5k4e1_i2c_driver, + .sensor_v4l2_subdev_info = s5k4e1_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(s5k4e1_subdev_info), + .sensor_v4l2_subdev_ops = &s5k4e1_subdev_ops, + .func_tbl = &s5k4e1_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Samsung 5MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/media/video/msm/sensors/vx6953.c b/drivers/media/video/msm/sensors/vx6953.c new file mode 100644 index 0000000000000000000000000000000000000000..b43782cf2c2302112a74b4d31a32a9a6274c907f --- /dev/null +++ b/drivers/media/video/msm/sensors/vx6953.c @@ -0,0 +1,2058 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_sensor.h" +#include "msm.h" +#include "vx6953.h" +#include "vx6953_reg.h" +#define SENSOR_NAME "vx6953" +#define PLATFORM_DRIVER_NAME "msm_camera_vx6953" +#define vx6953_obj vx6953_##obj + +DEFINE_MUTEX(vx6953_mut); + +#undef CDBG +#define CDBG printk +#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 +/* 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*/ +static struct msm_sensor_ctrl_t vx6953_s_ctrl; +static uint32_t fps_divider;/* init to 1 * 0x00000400 */ +static uint16_t fps; +static uint8_t vx6953_stm5m0edof_delay_msecs_stdby; +static struct msm_camera_i2c_reg_conf vx6953_start_settings[] = { + {0x0100, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf vx6953_stop_settings[] = { + {0x0100, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf vx6953_groupon_settings[] = { + {0x0104, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf vx6953_groupoff_settings[] = { + {0x0104, 0x00}, +}; + +static struct msm_camera_i2c_reg_conf vx6953_prev_settings[] = { + {0x0202, 0x03},/*REG = 0x0202 coarse integration_time_hi*/ + {0x0203, 0xD0},/*REG = 0x0203 coarse_integration_time_lo*/ + {0x0205, 0xC0},/*REG = 0x0205 analogue_gain_code_global*/ + {0x0340, 0x03},/*REG = 0x0340 frame_length_lines_hi*/ + {0x0341, 0xf0},/*REG = 0x0341 frame_length_lines_lo*/ + {0x0342, 0x0b},/*REG = 0x0342 line_length_pck_hi*/ + {0x0343, 0x74},/*REG = 0x0343 line_length_pck_lo*/ + {0x3005, 0x03},/*REG = 0x3005*/ + {0x3010, 0x00},/*REG = 0x3010*/ + {0x3011, 0x01},/*REG = 0x3011*/ + {0x301a, 0x6a},/*REG = 0x301a*/ + {0x3035, 0x03},/*REG = 0x3035*/ + {0x3036, 0x2c},/*REG = 0x3036*/ + {0x3041, 0x00},/*REG = 0x3041*/ + {0x3042, 0x24},/*REG = 0x3042*/ + {0x3045, 0x81},/*REG = 0x3045*/ + {0x0b80, 0x02},/*REG = 0x0b80 edof estimate*/ + {0x0900, 0x01},/*REG = 0x0900*/ + {0x0901, 0x22},/*REG = 0x0901*/ + {0x0902, 0x04},/*REG = 0x0902*/ + {0x0383, 0x03},/*REG = 0x0383*/ + {0x0387, 0x03},/*REG = 0x0387*/ + {0x034c, 0x05},/*REG = 0x034c*/ + {0x034d, 0x18},/*REG = 0x034d*/ + {0x034e, 0x03},/*REG = 0x034e*/ + {0x034f, 0xd4},/*REG = 0x034f*/ + {0x1716, 0x02},/*0x1716*/ + {0x1717, 0x04},/*0x1717*/ + {0x1718, 0x08},/*0x1718*/ + {0x1719, 0x2c},/*0x1719*/ +}; + +static struct msm_camera_i2c_reg_conf vx6953_snap_settings[] = { + {0x0202, 0x07},/*REG = 0x0202 coarse_integration_time_hi*/ + {0x0203, 0x00},/*REG = 0x0203 coarse_integration_time_lo*/ + {0x0205, 0xc0},/*REG = 0x0205 analogue_gain_code_global*/ + {0x0340, 0x07},/*REG = 0x0340 frame_length_lines_hi*/ + {0x0341, 0xd0},/*REG = 0x0341 frame_length_lines_lo*/ + {0x0342, 0x0b},/*REG = 0x0342 line_length_pck_hi*/ + {0x0343, 0x8c},/*REG = 0x0343 line_length_pck_lo*/ + {0x3005, 0x01},/*REG = 0x3005*/ + {0x3010, 0x00},/*REG = 0x3010*/ + {0x3011, 0x00},/*REG = 0x3011*/ + {0x301a, 0x55},/*REG = 0x301a*/ + {0x3035, 0x01},/*REG = 0x3035*/ + {0x3036, 0x23},/*REG = 0x3036*/ + {0x3041, 0x00},/*REG = 0x3041*/ + {0x3042, 0x24},/*REG = 0x3042*/ + {0x3045, 0xb7},/*REG = 0x3045*/ + {0x0b80, 0x01},/*REG = 0x0b80 edof application*/ + {0x0900, 0x00},/*REG = 0x0900*/ + {0x0901, 0x00},/*REG = 0x0901*/ + {0x0902, 0x00},/*REG = 0x0902*/ + {0x0383, 0x01},/*REG = 0x0383*/ + {0x0387, 0x01},/*REG = 0x0387*/ + {0x034c, 0x0A},/*REG = 0x034c*/ + {0x034d, 0x30},/*REG = 0x034d*/ + {0x034e, 0x07},/*REG = 0x034e*/ + {0x034f, 0xA8},/*REG = 0x034f*/ + {0x1716, 0x02},/*0x1716*/ + {0x1717, 0x0d},/*0x1717*/ + {0x1718, 0x07},/*0x1718*/ + {0x1719, 0x7d},/*0x1719*/ +}; + +static struct msm_camera_i2c_reg_conf vx6953_recommend_settings[] = { + {0x0103, 0x01}, /* standby */ + {0x0100, 0x00}, /* stop streaming */ + /* patch cut 2*/ + {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*/ + /* end of patch cut 2 */ + /* common settings */ + {0x0112, 10},/*REG = 0x0112 , 10 bit */ + {0x0113, 10},/*REG = 0x0113*/ + {0x0301, 9},/*REG = 0x0301 vt_pix_clk_div*/ + {0x0305, 4},/*REG = 0x0305 pre_pll_clk_div*/ + {0x0307, 133},/*REG = 0x0307 pll_multiplier*/ + {0x0309, 10},/*REG = 0x0309 op_pix_clk_div*/ + {0x3030, 0x08},/*REG = 0x3030*/ + {0x0111, 0x02},/*REG = 0x0111*/ + {0x0b00, 0x01},/*REG = 0x0b00 ,lens shading off */ + {0x3001, 0x30},/*REG = 0x3001*/ + {0x3004, 0x33},/*REG = 0x3004*/ + {0x3007, 0x09},/*REG = 0x3007*/ + {0x3016, 0x1F},/*REG = 0x3016*/ + {0x301d, 0x03},/*REG = 0x301d*/ + {0x317E, 0x11},/*REG = 0x317E*/ + {0x317F, 0x09},/*REG = 0x317F*/ + {0x3400, 0x38},/*REG = 0x3400*/ + {0x0b06, 0x00},/*REG_0x0b06*/ + {0x0b07, 0x80},/*REG_0x0b07*/ + {0x0b08, 0x01},/*REG_0x0b08*/ + {0x0b09, 0x4F},/*REG_0x0b09*/ + {0x0136, 0x18},/*REG_0x0136*/ + {0x0137, 0x00},/*/REG_0x0137*/ + {0x0b83, 0x20},/*REG = 0x0b83*/ + {0x0b84, 0x90},/*REG = 0x0b84*/ + {0x0b85, 0x20},/*REG = 0x0b85*/ + {0x0b88, 0x80},/*REG = 0x0b88*/ + {0x0b89, 0x00},/*REG = 0x0b89*/ + {0x0b8a, 0x00},/*REG = 0x0b8a*/ + /* end of common settings */ +}; + +static struct v4l2_subdev_info vx6953_subdev_info[] = { + { + .code = V4L2_MBUS_FMT_SGRBG10_1X10, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, + /* more can be supported, to be added later */ +}; + +static struct msm_camera_i2c_conf_array vx6953_init_conf[] = { + {&vx6953_recommend_settings[0], + ARRAY_SIZE(vx6953_recommend_settings), 0, MSM_CAMERA_I2C_BYTE_DATA} +}; + +static struct msm_camera_i2c_conf_array vx6953_confs[] = { + {&vx6953_snap_settings[0], + ARRAY_SIZE(vx6953_snap_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, + {&vx6953_prev_settings[0], + ARRAY_SIZE(vx6953_prev_settings), 0, MSM_CAMERA_I2C_BYTE_DATA}, +}; + +static struct msm_sensor_output_info_t vx6953_dimensions[] = { + { + .x_output = 0xA30, + .y_output = 0x7A8, + .line_length_pclk = 0xB8C, + .frame_length_lines = 0x7D0, + .vt_pixel_clk = 88666666, + .op_pixel_clk = 192000000, + .binning_factor = 1, + }, + { + .x_output = 0x518, + .y_output = 0x3D4, + .line_length_pclk = 0xB74, + .frame_length_lines = 0x3F0, + .vt_pixel_clk = 88666666, + .op_pixel_clk = 192000000, + .binning_factor = 1, + }, +}; + +static struct msm_camera_csi_params vx6953_csi_params = { + .data_format = CSI_8BIT, + .lane_cnt = 1, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 7, +}; + +static struct msm_camera_csi_params *vx6953_csi_params_array[] = { + &vx6953_csi_params, + &vx6953_csi_params, +}; + +static struct msm_sensor_output_reg_addr_t vx6953_reg_addr = { + .x_output = 0x034C, + .y_output = 0x034E, + .line_length_pclk = 0x0342, + .frame_length_lines = 0x0340, +}; + +static struct msm_sensor_id_info_t vx6953_id_info = { + .sensor_id_reg_addr = 0x0000, + .sensor_id = 0x03B9, +}; + +static struct msm_sensor_exp_gain_info_t vx6953_exp_gain_info = { + .coarse_int_time_addr = 0x0202, + .global_gain_addr = 0x0204, + .vert_offset = 9, +}; + +static const struct i2c_device_id vx6953_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&vx6953_s_ctrl}, + { } +}; + +static struct i2c_driver vx6953_i2c_driver = { + .id_table = vx6953_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static struct msm_camera_i2c_client vx6953_sensor_i2c_client = { + .addr_type = MSM_CAMERA_I2C_WORD_ADDR, +}; + +static int __init msm_sensor_init_module(void) +{ + return i2c_add_driver(&vx6953_i2c_driver); +} + +static int32_t vx6953_set_fps(struct msm_sensor_ctrl_t *s_ctrl, + struct fps_cfg *fps) { + return 0; +} + +int32_t vx6953_write_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) { + return 0; +} + +static struct v4l2_subdev_core_ops vx6953_subdev_core_ops = { + .ioctl = msm_sensor_subdev_ioctl, + .s_power = msm_sensor_power, +}; + +static struct v4l2_subdev_video_ops vx6953_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops vx6953_subdev_ops = { + .core = &vx6953_subdev_core_ops, + .video = &vx6953_subdev_video_ops, +}; + +static struct msm_camera_i2c_reg_conf vx6953_edof_estimation[] = { + {REG_0x0b80, 0x02}, +}; + +static struct msm_camera_i2c_reg_conf vx6953_edof_application[] = { + {REG_0x0b80, 0x01}, +}; + +static struct msm_camera_i2c_reg_conf vx6953_edof_default[] = { + {REG_0x0b80, 0x00}, +}; + +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 */ + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_edof_estimation, + ARRAY_SIZE(vx6953_edof_estimation), + vx6953_s_ctrl.msm_sensor_reg->default_data_type); + CDBG("VX6953_EDOF_ESTIMATION"); + } else if (edof_mode == VX6953_EDOF_APPLICATION) { + /* EDof Application mode for Capture */ + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_edof_application, + ARRAY_SIZE(vx6953_edof_application), + vx6953_s_ctrl.msm_sensor_reg->default_data_type); + CDBG("VX6953_EDOF_APPLICATION"); + } else { + /* EDOF disabled */ + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_edof_default, + ARRAY_SIZE(vx6953_edof_default), + vx6953_s_ctrl.msm_sensor_reg->default_data_type); + CDBG("VX6953_EDOF_DISABLE"); + } + return rc; +} + +static struct msm_camera_i2c_reg_conf vx6953_standby[] = { + {0x103, 0x01}, +}; + +static struct msm_camera_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*/ +}; +struct msm_camera_i2c_reg_conf init_tbl[] = { + {0x0112, 10}, + {0x0113, 10}, + {0x0301, 9}, + {0x0305, 4}, + {0x0307, 133}, + {0x0309, 10}, + {0x0202, 0x03}, + {0x0203, 0xd0}, + {0x0205, 0xc0}, + {0x3030, 0x08}, + {0x0111, 0x02}, + {0x0b00, 0x01}, + {0x3001, 0x30}, + {0x3004, 0x33}, + {0x3007, 0x09}, + {0x3016, 0x1F}, + {0x301d, 0x03}, + {0x317e, 0x11}, + {0x317f, 0x09}, + {0x3400, 0x38}, + {0x0b06, 0x00}, + {0x0b07, 0x80}, + {0x0b08, 0x01}, + {0x0b09, 0x4F}, + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x0b83, 0x20}, + {0x0b84, 0x90}, + {0x0b85, 0x20}, + {0x0b88, 0x80}, + {0x0b89, 0x00}, + {0x0b8a, 0x00}, + {0x0340, 0x03}, /*REG = 0x0340 frame_length_lines_hi*/ + {0x0341, 0xf0}, /*REG = 0x0341 frame_length_lines_lo*/ + {0x0342, 0x0b}, /*REG = 0x0342 line_length_pck_hi*/ + {0x0343, 0x74}, /*REG = 0x0343 line_length_pck_lo*/ + {0x3005, 0x03}, /*REG = 0x3005*/ + {0x3010, 0x00}, /*REG = 0x3010*/ + {0x3011, 0x01}, /*REG = 0x3011*/ + {0x301a, 0x6a}, /*REG = 0x301a*/ + {0x3035, 0x03}, /*REG = 0x3035*/ + {0x3036, 0x2c}, /*REG = 0x3036*/ + {0x3041, 0x00}, /*REG = 0x3041*/ + {0x3042, 0x24}, /*REG = 0x3042*/ + {0x3045, 0x81}, /*REG = 0x3045*/ + {0x0b80, 0x02}, /*REG = 0x0b80 edof estimate*/ + {0x0900, 0x01}, /*REG = 0x0900*/ + {0x0901, 0x22}, /*REG = 0x0901*/ + {0x0902, 0x04}, /*REG = 0x0902*/ + {0x0383, 0x03}, /*REG = 0x0383*/ + {0x0387, 0x03}, /*REG = 0x0387*/ + {0x034c, 0x05}, /*REG = 0x034c*/ + {0x034d, 0x18}, /*REG = 0x034d*/ + {0x034e, 0x03}, /*REG = 0x034e*/ + {0x034f, 0xd4}, /*REG = 0x034f*/ + {0x1716, 0x02}, /*0x1716*/ + {0x1717, 0x04}, /*0x1717*/ + {0x1718, 0x08}, /*0x1718*/ + {0x1719, 0x2c}, /*0x1719*/ +}; + +struct msm_camera_i2c_reg_conf mode_tbl1[] = { + {REG_0x0112, 10},/*REG = 0x0112 , 10 bit */ + {REG_0x0113, 10},/*REG = 0x0113*/ + {REG_VT_PIX_CLK_DIV, 9},/*REG = 0x0301 vt_pix_clk_div*/ + {REG_PRE_PLL_CLK_DIV, 4},/*REG = 0x0305 pre_pll_clk_div*/ + {REG_PLL_MULTIPLIER, 133},/*REG = 0x0307 pll_multiplier*/ + {REG_OP_PIX_CLK_DIV, 10},/*REG = 0x0309 op_pix_clk_div*/ + {REG_FRAME_LENGTH_LINES_HI, 0x03},/*REG = 0x0340 frame_length_lines_hi*/ + {REG_FRAME_LENGTH_LINES_LO, 0xf0},/*REG = 0x0341 frame_length_lines_lo*/ + {REG_LINE_LENGTH_PCK_HI, 0x0b}, /*REG = 0x0342 line_length_pck_hi*/ + {REG_LINE_LENGTH_PCK_LO, 0x74}, /*REG = 0x0343 line_length_pck_lo*/ + {REG_0x3005, 0x03}, /*REG = 0x3005*/ + {0x3010, 0x00}, /*REG = 0x3010*/ + {REG_0x3011, 0x01}, /*REG = 0x3011*/ + {REG_0x301a, 0x6a}, /*REG = 0x301a*/ + {REG_0x3035, 0x03}, /*REG = 0x3035*/ + {REG_0x3036, 0x2c}, /*REG = 0x3036*/ + {REG_0x3041, 0x00}, /*REG = 0x3041*/ + {0x3042, 0x24}, /*REG = 0x3042*/ + {REG_0x3045, 0x81}, /*REG = 0x3045*/ + {REG_0x0b80, 0x02}, /*REG = 0x0b80 edof estimate*/ + {REG_0x0900, 0x01}, /*REG = 0x0900*/ + {REG_0x0901, 0x22}, /*REG = 0x0901*/ + {REG_0x0902, 0x04}, /*REG = 0x0902*/ + {REG_0x0383, 0x03}, /*REG = 0x0383*/ + {REG_0x0387, 0x03}, /*REG = 0x0387*/ + {REG_0x034c, 0x05}, /*REG = 0x034c*/ + {REG_0x034d, 0x18}, /*REG = 0x034d*/ + {REG_0x034e, 0x03}, /*REG = 0x034e*/ + {REG_0x034f, 0xd4}, /*REG = 0x034f*/ + {REG_0x1716, 0x02}, /*0x1716*/ + {REG_0x1717, 0x04}, /*0x1717*/ + {REG_0x1718, 0x08}, /*0x1718*/ + {REG_0x1719, 0x2c}, /*0x1719*/ +}; + +struct msm_camera_i2c_reg_conf mode_tbl2[] = { + {REG_0x0112, 10},/*REG = 0x0112 , 10 bit */ + {REG_0x0113, 10},/*REG = 0x0113*/ + {REG_VT_PIX_CLK_DIV, 9},/*REG = 0x0301 vt_pix_clk_div*/ + {REG_PRE_PLL_CLK_DIV, 4},/*REG = 0x0305 pre_pll_clk_div*/ + {REG_PLL_MULTIPLIER, 133},/*REG = 0x0307 pll_multiplier*/ + {REG_OP_PIX_CLK_DIV, 10},/*REG = 0x0309 op_pix_clk_div*/ + {REG_FRAME_LENGTH_LINES_HI, 0x07},/*REG = 0x0340 frame_length_lines_hi*/ + {REG_FRAME_LENGTH_LINES_LO, 0xd0},/*REG = 0x0341 frame_length_lines_lo*/ + {REG_LINE_LENGTH_PCK_HI, 0x0b},/*REG = 0x0342 line_length_pck_hi*/ + {REG_LINE_LENGTH_PCK_LO, 0x8c},/*REG = 0x0343 line_length_pck_lo*/ + {REG_0x3005, 0x01},/*REG = 0x3005*/ + {0x3010, 0x00},/*REG = 0x3010*/ + {REG_0x3011, 0x00},/*REG = 0x3011*/ + {REG_0x301a, 0x55},/*REG = 0x301a*/ + {REG_0x3035, 0x01},/*REG = 0x3035*/ + {REG_0x3036, 0x23},/*REG = 0x3036*/ + {REG_0x3041, 0x00},/*REG = 0x3041*/ + {0x3042, 0x24},/*REG = 0x3042*/ + {REG_0x3045, 0xb7},/*REG = 0x3045*/ + {REG_0x0b80, 0x01},/*REG = 0x0b80 edof application*/ + {REG_0x0900, 0x00},/*REG = 0x0900*/ + {REG_0x0901, 0x00},/*REG = 0x0901*/ + {REG_0x0902, 0x00},/*REG = 0x0902*/ + {REG_0x0383, 0x01},/*REG = 0x0383*/ + {REG_0x0387, 0x01},/*REG = 0x0387*/ + {REG_0x034c, 0x0A},/*REG = 0x034c*/ + {REG_0x034d, 0x30},/*REG = 0x034d*/ + {REG_0x034e, 0x07},/*REG = 0x034e*/ + {REG_0x034f, 0xA8},/*REG = 0x034f*/ + {REG_0x1716, 0x02},/*0x1716*/ + {REG_0x1717, 0x0d},/*0x1717*/ + {REG_0x1718, 0x07},/*0x1718*/ + {REG_0x1719, 0x7d},/*0x1719*/ +}; + +static int32_t vx6953_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + uint16_t frame_cnt = 0; + CDBG("%s update type = %d, rt = %d\n", + __func__, update_type, rt); + + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + /* reset fps_divider */ + fps = 30 * Q8; + /* stop streaming */ + + /* Reset everything first */ + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_standby, + ARRAY_SIZE(vx6953_standby), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + + msleep(20); + + CDBG("Init vx6953_sensor_setting standby\n"); + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_stop_settings, + ARRAY_SIZE(vx6953_stop_settings), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + patch_tbl_cut2, + ARRAY_SIZE(patch_tbl_cut2), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + init_tbl, + ARRAY_SIZE(init_tbl), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + } + return rc; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct msm_camera_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}, + }; + /* stop streaming */ + msleep(20); + + /* Reset everything first */ + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_standby, + ARRAY_SIZE(vx6953_standby), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + + + msleep(20); + + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_stop_settings, + ARRAY_SIZE(vx6953_stop_settings), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + patch_tbl_cut2, + ARRAY_SIZE(patch_tbl_cut2), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + init_mode_tbl, + ARRAY_SIZE(init_mode_tbl), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + + + vx6953_s_ctrl.curr_csic_params = + vx6953_s_ctrl.csic_params[0]; + v4l2_subdev_notify(&vx6953_s_ctrl.sensor_v4l2_subdev, + NOTIFY_CSIC_CFG, + vx6953_s_ctrl.curr_csic_params); + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + if (rt == RES_PREVIEW) { + CDBG("%s write mode_tbl for preview\n", + __func__); + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + mode_tbl1, + ARRAY_SIZE(mode_tbl1), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + } else if (rt == RES_CAPTURE) { + CDBG("%s write mode_tbl for capture\n", + __func__); + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + mode_tbl2, + ARRAY_SIZE(mode_tbl2), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + } + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + /* Start sensor streaming */ + msm_camera_i2c_write_tbl( + vx6953_s_ctrl.sensor_i2c_client, + vx6953_start_settings, + ARRAY_SIZE(vx6953_start_settings), + vx6953_s_ctrl.msm_sensor_reg-> + default_data_type); + msleep(20); + + msm_camera_i2c_read( + vx6953_s_ctrl.sensor_i2c_client, + 0x0005, + &frame_cnt, + MSM_CAMERA_I2C_BYTE_ADDR); + while (frame_cnt == 0xFF) { + msm_camera_i2c_read( + vx6953_s_ctrl.sensor_i2c_client, + 0x0005, + &frame_cnt, + MSM_CAMERA_I2C_BYTE_ADDR); + CDBG("%s frame_cnt = %d\n", + __func__, frame_cnt); + usleep_range(5000, 10000); + } + } + return rc; + default: + return rc; + } + return rc; +} + +static int32_t vx6953_init_config(void) +{ + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + CDBG("%s called\n", __func__); + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * fps_divider) / + fps) * Q8) / Q10) + 1; + + vx6953_sensor_setting(REG_INIT, rt); + + vx6953_enable_edof(VX6953_EDOF_ESTIMATION); + return rc; +} + +static int32_t vx6953_update_config(int rt) +{ + int32_t rc = 0; + CDBG("%s rt = %d\n", __func__, rt); + if (rt == MSM_SENSOR_RES_FULL) + rt = RES_CAPTURE; + else if (rt == MSM_SENSOR_RES_QTR) + rt = RES_PREVIEW; + + vx6953_stm5m0edof_delay_msecs_stdby = 67; + vx6953_sensor_setting(UPDATE_PERIODIC, rt); + + if (rt == RES_PREVIEW) + vx6953_enable_edof(VX6953_EDOF_ESTIMATION); + else if (rt == RES_CAPTURE) + vx6953_enable_edof(VX6953_EDOF_APPLICATION); + + return rc; +} /*end of vx6953_snapshot_config*/ + +static int32_t vx6953_set_sensor_mode(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int rt) +{ + int32_t rc = 0; + + fps_divider = 1 * 0x00000400; + fps = 30*Q8; + + switch (update_type) { + case MSM_SENSOR_REG_INIT: + rc = vx6953_init_config(); + break; + case MSM_SENSOR_UPDATE_PERIODIC: + rc = vx6953_update_config(rt); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} +static struct msm_sensor_fn_t vx6953_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_set_fps = vx6953_set_fps, + .sensor_write_exp_gain = vx6953_write_exp_gain, + .sensor_write_snapshot_exp_gain = vx6953_write_exp_gain, + .sensor_csi_setting = vx6953_set_sensor_mode, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode, + .sensor_mode_init = msm_sensor_mode_init, + .sensor_get_output_info = msm_sensor_get_output_info, + .sensor_config = msm_sensor_config, + .sensor_power_up = msm_sensor_power_up, + .sensor_power_down = msm_sensor_power_down, +}; + +static struct msm_sensor_reg_t vx6953_data_regs = { + .default_data_type = MSM_CAMERA_I2C_BYTE_DATA, + .start_stream_conf = vx6953_start_settings, + .start_stream_conf_size = ARRAY_SIZE(vx6953_start_settings), + .stop_stream_conf = vx6953_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(vx6953_stop_settings), + .group_hold_on_conf = vx6953_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(vx6953_groupon_settings), + .group_hold_off_conf = vx6953_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(vx6953_groupoff_settings), + .init_settings = &vx6953_init_conf[0], + .init_size = ARRAY_SIZE(vx6953_init_conf), + .mode_settings = &vx6953_confs[0], + .output_settings = &vx6953_dimensions[0], + .num_conf = ARRAY_SIZE(vx6953_confs), +}; + +static struct msm_sensor_ctrl_t vx6953_s_ctrl = { + .msm_sensor_reg = &vx6953_data_regs, + .sensor_i2c_client = &vx6953_sensor_i2c_client, + .sensor_i2c_addr = 0x20, + .sensor_output_reg_addr = &vx6953_reg_addr, + .sensor_id_info = &vx6953_id_info, + .sensor_exp_gain_info = &vx6953_exp_gain_info, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .csic_params = &vx6953_csi_params_array[0], + .msm_sensor_mutex = &vx6953_mut, + .sensor_i2c_driver = &vx6953_i2c_driver, + .sensor_v4l2_subdev_info = vx6953_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(vx6953_subdev_info), + .sensor_v4l2_subdev_ops = &vx6953_subdev_ops, + .func_tbl = &vx6953_func_tbl, + .clk_rate = MSM_SENSOR_MCLK_24HZ, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Sensor VX6953 (BAYER 5M)"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/video/msm/sensors/vx6953.h b/drivers/media/video/msm/sensors/vx6953.h new file mode 100644 index 0000000000000000000000000000000000000000..0fcdb53a78e1ec004ea6f61a0019f5051da31008 --- /dev/null +++ b/drivers/media/video/msm/sensors/vx6953.h @@ -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. + * + */ + +#ifndef VX6953_H +#define VX6953_H +#include +#include +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/sensors/vx6953_reg.h b/drivers/media/video/msm/sensors/vx6953_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..b76aa938523633b8d6d67ebda76fb658e84c340f --- /dev/null +++ b/drivers/media/video/msm/sensors/vx6953_reg.h @@ -0,0 +1,134 @@ +/* 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. + * + */ + + +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*/ + } +}; +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/sn12m0pz.c b/drivers/media/video/msm/sn12m0pz.c new file mode 100644 index 0000000000000000000000000000000000000000..2eabb3c49a4c506a4fa081f7b10fc0d76257886b --- /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 __devinit 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 __devinit 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 0000000000000000000000000000000000000000..f2abc4734212f3a89e73cd654e8e95a9e3ee4945 --- /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 0000000000000000000000000000000000000000..d21eac1373c0edf24d69e9d4edf6e5f233a9f990 --- /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 0000000000000000000000000000000000000000..fa825700c323b0a05c4f1aeba3d87c7e95d80cbf --- /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 0000000000000000000000000000000000000000..8248f8d97e0397f59115ed25fc5953275f049f31 --- /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 0000000000000000000000000000000000000000..3b8f14c7b0e94b147cbf09f89fad9b71a810bee2 --- /dev/null +++ b/drivers/media/video/msm/vx6953.c @@ -0,0 +1,3666 @@ +/* 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 "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) + return rc; + + 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 = info->sensor_platform_info->mount_angle; + 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 0000000000000000000000000000000000000000..0e120635a5d2a39497dbc0dd947081357a05f3ab --- /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 0000000000000000000000000000000000000000..48fc71f5bf4b183f2f3154acfca11270c2236567 --- /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 0000000000000000000000000000000000000000..f16054b68658080ccbf192b96f211f2758608ea6 --- /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 0000000000000000000000000000000000000000..2e5e39be672cdc6846138dbf4a7423b90accc4f3 --- /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 0000000000000000000000000000000000000000..e5428e99192bdb1701f70648847240951da4a0bf --- /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/msm/wfd/Makefile b/drivers/media/video/msm/wfd/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5decaca12b43483e7326cc5c667cced38d577353 --- /dev/null +++ b/drivers/media/video/msm/wfd/Makefile @@ -0,0 +1,5 @@ +obj-y += mdp-subdev.o +obj-y += enc-subdev.o +obj-y += vsg-subdev.o +obj-y += wfd-ioctl.o +obj-y += wfd-util.o diff --git a/drivers/media/video/msm/wfd/enc-subdev.c b/drivers/media/video/msm/wfd/enc-subdev.c new file mode 100644 index 0000000000000000000000000000000000000000..c94fa13a9a8ae8e3828f35097d4da4f21d5d33ff --- /dev/null +++ b/drivers/media/video/msm/wfd/enc-subdev.c @@ -0,0 +1,2286 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +*/ + +#include +#include +#include "enc-subdev.h" +#include "wfd-util.h" +#include +#include +#include +#include +#include + +#define VID_ENC_MAX_ENCODER_CLIENTS 1 +#define MAX_NUM_CTRLS 20 + +struct venc_inst { + struct video_client_ctx venc_client; + void *cbdata; + void (*op_buffer_done)(void *cookie, u32 status, + struct vb2_buffer *buf); + void (*ip_buffer_done)(void *cookie, u32 status, + struct mem_region *mregion); + u32 width; + u32 height; + int secure; +}; + +struct venc { + s32 device_handle; + void *virt_base; + struct venc_inst venc_clients[VID_ENC_MAX_ENCODER_CLIENTS]; + struct mutex lock; + struct ion_client *iclient; +}; + +static struct venc venc_p; + +static void *venc_map_dev_base_addr(void *device_name) +{ + return venc_p.virt_base; +} + +static void venc_interrupt_deregister(void) +{ +} + +static void venc_interrupt_register(void *device_name) +{ +} + +static void venc_interrupt_clear(void) +{ +} + +int venc_load_fw(struct v4l2_subdev *sd) +{ + return !vidc_load_firmware(); +} + +static u32 venc_get_empty_client_index(void) +{ + u32 i; + u32 found = false; + + for (i = 0; i < VID_ENC_MAX_ENCODER_CLIENTS; i++) { + if (!venc_p.venc_clients[i].venc_client.vcd_handle) { + found = true; + break; + } + } + if (!found) { + WFD_MSG_ERR("%s():ERROR No space for new client\n", + __func__); + return -ENOMEM; + } + WFD_MSG_INFO("%s(): available client index = %u\n", + __func__, i); + return i; +} + +int venc_init(struct v4l2_subdev *sd, u32 val) +{ + struct vcd_init_config vcd_init_config; + mutex_init(&venc_p.lock); + venc_p.virt_base = vidc_get_ioaddr(); + vcd_init_config.device_name = "VIDC"; + vcd_init_config.map_dev_base_addr = venc_map_dev_base_addr; + vcd_init_config.interrupt_clr = venc_interrupt_clear; + vcd_init_config.register_isr = venc_interrupt_register; + vcd_init_config.deregister_isr = venc_interrupt_deregister; + vcd_init(&vcd_init_config, &venc_p.device_handle); + return 0; +} + +static void venc_notify_client(struct video_client_ctx *client_ctx) +{ + if (client_ctx) + complete(&client_ctx->event); +} + +static void venc_open_done(struct video_client_ctx *client_ctx, + struct vcd_handle_container *handle_container) +{ + if (client_ctx) { + if (handle_container) + client_ctx->vcd_handle = handle_container->handle; + else + WFD_MSG_ERR("handle_container is NULL\n"); + venc_notify_client(client_ctx); + } else + WFD_MSG_ERR("ERROR. client_ctx is NULL"); +} + +static void venc_start_done(struct video_client_ctx *client_ctx, u32 status) +{ + if (client_ctx) + venc_notify_client(client_ctx); + else + WFD_MSG_ERR("ERROR. client_ctx is NULL"); +} + +static void venc_stop_done(struct video_client_ctx *client_ctx, u32 status) +{ + WFD_MSG_DBG("Inside venc_stop_done: E\n"); + if (client_ctx) + venc_notify_client(client_ctx); + else + WFD_MSG_ERR("ERROR. client_ctx is NULL"); + WFD_MSG_DBG("Inside venc_stop_done: X\n"); +} + +static void venc_cb(u32 event, u32 status, void *info, u32 size, void *handle, + void *const client_data) +{ + struct venc_inst *inst = client_data; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct vb2_buffer *vbuf; + struct mem_region *mregion; + struct vcd_frame_data *frame_data = (struct vcd_frame_data *)info; + + if (!client_ctx) { + WFD_MSG_ERR("Client context is NULL\n"); + return; + } + client_ctx->event_status = status; + switch (event) { + case VCD_EVT_RESP_OPEN: + WFD_MSG_DBG("EVENT: open done = %d\n", event); + venc_open_done(client_ctx, + (struct vcd_handle_container *)info); + break; + case VCD_EVT_RESP_INPUT_DONE: + case VCD_EVT_RESP_INPUT_FLUSHED: + WFD_MSG_DBG("EVENT: input done = %d\n", event); + mregion = (struct mem_region *) + frame_data->frm_clnt_data; + inst->ip_buffer_done(inst->cbdata, status, mregion); + break; + case VCD_EVT_RESP_OUTPUT_DONE: + case VCD_EVT_RESP_OUTPUT_FLUSHED: + WFD_MSG_DBG("EVENT: output done = %d\n", event); + vbuf = (struct vb2_buffer *) + frame_data->frm_clnt_data; + vbuf->v4l2_planes[0].bytesused = + frame_data->data_len; + + switch (frame_data->frame) { + case VCD_FRAME_I: + case VCD_FRAME_IDR: + vbuf->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + break; + case VCD_FRAME_P: + vbuf->v4l2_buf.flags |= V4L2_BUF_FLAG_PFRAME; + break; + case VCD_FRAME_B: + vbuf->v4l2_buf.flags |= V4L2_BUF_FLAG_BFRAME; + break; + default: + break; + } + + vbuf->v4l2_buf.timestamp = + ns_to_timeval(frame_data->time_stamp * NSEC_PER_USEC); + + WFD_MSG_DBG("bytes used %d, ts: %d.%d, frame type is %d\n", + frame_data->data_len, + (int)vbuf->v4l2_buf.timestamp.tv_sec, + (int)vbuf->v4l2_buf.timestamp.tv_usec, + frame_data->frame); + + /* + * Output buffers are enc-subdev and vcd's problem, so + * if buffer is cached, need to flush before giving to + * client. So doing the dirty stuff in this little context + */ + { + unsigned long kvaddr, phys_addr; + s32 buffer_index = -1, ion_flags = 0; + struct ion_handle *ion_handle; + int pmem_fd; + struct file *filp; + bool rc; + + rc = vidc_lookup_addr_table(client_ctx, + BUFFER_TYPE_OUTPUT, true, + (unsigned long *)&frame_data-> + frm_clnt_data, &kvaddr, &phys_addr, + &pmem_fd, &filp, &buffer_index); + + if (rc) + ion_flags = vidc_get_fd_info(client_ctx, + BUFFER_TYPE_OUTPUT, pmem_fd, + kvaddr, buffer_index, &ion_handle); + else + WFD_MSG_ERR("Got an output buffer that we " + "couldn't recognize!\n"); + + if (msm_ion_do_cache_op(client_ctx->user_ion_client, + ion_handle, &kvaddr, frame_data->data_len, + ION_IOC_CLEAN_INV_CACHES)) + WFD_MSG_ERR("OP buffer flush failed\n"); + + } + + inst->op_buffer_done(inst->cbdata, status, vbuf); + break; + case VCD_EVT_RESP_START: + WFD_MSG_DBG("EVENT: start done = %d\n", event); + venc_start_done(client_ctx, status); + /*TODO: should wait for this event*/ + break; + case VCD_EVT_RESP_STOP: + WFD_MSG_DBG("EVENT: not expected = %d\n", event); + venc_stop_done(client_ctx, status); + break; + case VCD_EVT_RESP_FLUSH_INPUT_DONE: + case VCD_EVT_RESP_FLUSH_OUTPUT_DONE: + venc_notify_client(client_ctx); + break; + case VCD_EVT_RESP_PAUSE: + case VCD_EVT_IND_OUTPUT_RECONFIG: + WFD_MSG_DBG("EVENT: not expected = %d\n", event); + break; + case VCD_EVT_IND_HWERRFATAL: + case VCD_EVT_IND_RESOURCES_LOST: + WFD_MSG_DBG("EVENT: error = %d\n", event); + break; + default: + WFD_MSG_ERR("Invalid event type = %u\n", event); + break; + } +} + +static long venc_open(struct v4l2_subdev *sd, void *arg) +{ + u32 client_index; + int rc = 0; + struct venc_inst *inst; + struct video_client_ctx *client_ctx; + struct venc_msg_ops *vmops = arg; + int flags = 0; + mutex_lock(&venc_p.lock); + client_index = venc_get_empty_client_index(); + if (client_index < 0) { + WFD_MSG_ERR("No free clients, client_index = %d\n", + client_index); + rc = -ENODEV; + goto no_free_client; + } + inst = &venc_p.venc_clients[client_index]; + client_ctx = &inst->venc_client; + init_completion(&client_ctx->event); + mutex_init(&client_ctx->msg_queue_lock); + mutex_init(&client_ctx->enrty_queue_lock); + INIT_LIST_HEAD(&client_ctx->msg_queue); + init_waitqueue_head(&client_ctx->msg_wait); + inst->op_buffer_done = vmops->op_buffer_done; + inst->ip_buffer_done = vmops->ip_buffer_done; + inst->cbdata = vmops->cbdata; + inst->secure = vmops->secure; + if (vmops->secure) { + WFD_MSG_ERR("OPENING SECURE SESSION\n"); + flags |= VCD_CP_SESSION; + } + if (vcd_get_ion_status()) { + client_ctx->user_ion_client = vcd_get_ion_client(); + if (!client_ctx->user_ion_client) { + WFD_MSG_ERR("vcd_open ion get client failed"); + return -EFAULT; + } + } + + rc = vcd_open(venc_p.device_handle, false, venc_cb, + inst, flags); + if (rc) { + WFD_MSG_ERR("vcd_open failed, rc = %d\n", rc); + rc = -ENODEV; + goto no_free_client; + } + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) { + WFD_MSG_ERR("callback for vcd_open returned error: %u", + client_ctx->event_status); + goto no_free_client; + } + WFD_MSG_ERR("NOTE: client_ctx = %p\n", client_ctx); + vmops->cookie = inst; + sd->dev_priv = inst; +no_free_client: + mutex_unlock(&venc_p.lock); + return rc; +} + +static long venc_close(struct v4l2_subdev *sd, void *arg) +{ + long rc = 0; + struct venc_inst *inst; + struct video_client_ctx *client_ctx = NULL; + mutex_lock(&venc_p.lock); + inst = sd->dev_priv; + client_ctx = &inst->venc_client; + if (!client_ctx || !client_ctx->vcd_handle) { + WFD_MSG_ERR("Invalid client context in close\n"); + rc = -ENODEV; + goto end; + } + rc = vcd_close(client_ctx->vcd_handle); + if (rc) { + WFD_MSG_ERR("Failed to close encoder subdevice\n"); + goto end; + } + memset((void *)client_ctx, 0, + sizeof(struct video_client_ctx)); +end: + mutex_unlock(&venc_p.lock); + return rc; +} + +static long venc_get_buffer_req(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct v4l2_requestbuffers *b = arg; + struct vcd_buffer_requirement buf_req; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + if (!client_ctx) { + WFD_MSG_ERR("Invalid client context"); + rc = -EINVAL; + goto err; + } + rc = vcd_get_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, &buf_req); + if (rc) { + WFD_MSG_ERR("Failed to get out buf reqs rc = %d", rc); + goto err; + } + + buf_req.actual_count = b->count = max(buf_req.min_count, b->count); + rc = vcd_set_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, &buf_req); + if (rc) { + WFD_MSG_ERR("Failed to set out buf reqs rc = %d", rc); + goto err; + } + +err: + return rc; +} + +static long venc_set_buffer_req(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct bufreq *b = arg; + struct vcd_buffer_requirement buf_req; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + int aligned_width, aligned_height; + if (!client_ctx) { + WFD_MSG_ERR("Invalid client context"); + rc = -EINVAL; + goto err; + } + aligned_width = ALIGN(b->width, 16); + aligned_height = ALIGN(b->height, 16); + + if (aligned_width != b->width) { + WFD_MSG_ERR("Width not 16 byte aligned\n"); + rc = -EINVAL; + goto err; + } + + buf_req.actual_count = b->count; + buf_req.min_count = b->count; + buf_req.max_count = b->count; + buf_req.sz = ALIGN(aligned_height * aligned_width, SZ_2K) + + ALIGN(aligned_height * aligned_width * 1/2, SZ_2K); + buf_req.align = SZ_4K; + inst->width = b->width; + inst->height = b->height; + rc = vcd_set_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_INPUT, &buf_req); + if (rc) { + WFD_MSG_ERR("Failed to get out buf reqs rc = %d", rc); + goto err; + } + b->size = buf_req.sz; +err: + return rc; +} + +static long venc_start(struct v4l2_subdev *sd) +{ + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + int rc; + if (!client_ctx) { + WFD_MSG_ERR("Client context is NULL"); + return -EINVAL; + } + rc = vcd_encode_start(client_ctx->vcd_handle); + if (rc) { + WFD_MSG_ERR("vcd_encode_start failed, rc = %d\n", rc); + goto err; + } + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) + WFD_MSG_ERR("callback for vcd_encode_start returned error: %u", + client_ctx->event_status); +err: + return rc; +} + +static long venc_stop(struct v4l2_subdev *sd) +{ + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + int rc; + if (!client_ctx) { + WFD_MSG_ERR("Client context is NULL"); + return -EINVAL; + } + rc = vcd_stop(client_ctx->vcd_handle); + wait_for_completion(&client_ctx->event); + return rc; +} + +static long venc_set_codec(struct video_client_ctx *client_ctx, __s32 codec) +{ + struct vcd_property_codec vcd_property_codec; + struct vcd_property_hdr vcd_property_hdr; + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + vcd_property_codec.codec = VCD_CODEC_H264; + + switch (codec) { + case V4L2_PIX_FMT_H264: + vcd_property_codec.codec = VCD_CODEC_H264; + break; + case V4L2_PIX_FMT_MPEG4: + vcd_property_codec.codec = VCD_CODEC_MPEG4; + break; + default: + WFD_MSG_ERR("Codec not supported, defaulting to h264\n"); + break; + } + return vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); +} + +static long venc_set_codec_level(struct video_client_ctx *client_ctx, + __s32 codec, __s32 level) +{ + struct vcd_property_level vcd_property_level; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_codec vcd_property_codec; + + int rc = 0; + int mpeg4_base = VCD_LEVEL_MPEG4_0; + int h264_base = VCD_LEVEL_H264_1; + + /* Validate params */ + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (rc < 0) { + WFD_MSG_ERR("Error getting codec property"); + rc = -EINVAL; + goto err; + } + + if (!((vcd_property_codec.codec == VCD_CODEC_H264 + && codec == V4L2_CID_MPEG_VIDEO_H264_LEVEL) || + (vcd_property_codec.codec == VCD_CODEC_MPEG4 + && codec == V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL))) { + WFD_MSG_ERR("Attempting to set %d for codec type %d", + codec, vcd_property_codec.codec); + rc = -EINVAL; + goto err; + } + + /* Set property */ + vcd_property_hdr.prop_id = VCD_I_LEVEL; + vcd_property_hdr.sz = sizeof(struct vcd_property_level); + + if (codec == V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL) { + vcd_property_level.level = mpeg4_base + level; + + if (vcd_property_level.level < VCD_LEVEL_MPEG4_0 + || vcd_property_level.level > VCD_LEVEL_MPEG4_X) { + WFD_MSG_ERR("Level (%d) out of range" + "for codec (%d)\n", level, codec); + + rc = -EINVAL; + goto err; + } + } else if (codec == V4L2_CID_MPEG_VIDEO_H264_LEVEL) { + vcd_property_level.level = h264_base + level; + + if (vcd_property_level.level < VCD_LEVEL_H264_1 + || vcd_property_level.level > VCD_LEVEL_H264_5p1) { + WFD_MSG_ERR("Level (%d) out of range" + "for codec (%d)\n", level, codec); + + rc = -EINVAL; + goto err; + } + } else { + WFD_MSG_ERR("Codec (%d) not supported, not setting level (%d)", + codec, level); + rc = -ENOTSUPP; + goto err; + } + + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_level); +err: + return rc; +} + +static long venc_get_codec_level(struct video_client_ctx *client_ctx, + __s32 codec, __s32 *level) +{ + struct vcd_property_level vcd_property_level; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_codec vcd_property_codec; + + int rc = 0; + int mpeg4_base = VCD_LEVEL_MPEG4_0; + int h264_base = VCD_LEVEL_H264_1; + + /* Validate params */ + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (rc < 0) { + WFD_MSG_ERR("Error getting codec property"); + rc = -EINVAL; + goto err; + } + + if (!((vcd_property_codec.codec == VCD_CODEC_H264 + && codec == V4L2_CID_MPEG_VIDEO_H264_LEVEL) || + (vcd_property_codec.codec == VCD_CODEC_MPEG4 + && codec == V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL))) { + WFD_MSG_ERR("Attempting to get %d for codec type %d", + codec, vcd_property_codec.codec); + rc = -EINVAL; + goto err; + } + + vcd_property_hdr.prop_id = VCD_I_LEVEL; + vcd_property_hdr.sz = sizeof(struct vcd_property_level); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_level); + if (rc < 0) { + rc = -EINVAL; + goto err; + } + + if (codec == V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL) { + *level = vcd_property_level.level - mpeg4_base; + } else if (codec == V4L2_CID_MPEG_VIDEO_H264_LEVEL) { + *level = vcd_property_level.level - h264_base; + } else { + WFD_MSG_ERR("Codec (%d) not supported", codec); + rc = -ENOTSUPP; + goto err; + } + +err: + return rc; +} + +static long venc_set_codec_profile(struct video_client_ctx *client_ctx, + __s32 codec, __s32 profile) +{ + struct vcd_property_profile vcd_property_profile; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_codec vcd_property_codec; + struct vcd_property_i_period vcd_property_i_period; + int rc = 0; + + /* Validate params */ + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (rc < 0) { + WFD_MSG_ERR("Error getting codec property"); + rc = -EINVAL; + goto err_set_profile; + } + + if (!((vcd_property_codec.codec == VCD_CODEC_H264 + && codec == V4L2_CID_MPEG_VIDEO_H264_PROFILE) || + (vcd_property_codec.codec == VCD_CODEC_MPEG4 + && codec == V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE))) { + WFD_MSG_ERR("Attempting to set %d for codec type %d", + codec, vcd_property_codec.codec); + rc = -EINVAL; + goto err_set_profile; + } + + /* Set property */ + vcd_property_hdr.prop_id = VCD_I_PROFILE; + vcd_property_hdr.sz = sizeof(struct vcd_property_profile); + + if (codec == V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE) { + switch (profile) { + case V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE: + vcd_property_profile.profile = VCD_PROFILE_MPEG4_SP; + break; + case V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE: + vcd_property_profile.profile = VCD_PROFILE_MPEG4_ASP; + break; + default: + WFD_MSG_ERR("Profile %d not supported," + "defaulting to simple (%d)", + profile, VCD_PROFILE_MPEG4_SP); + vcd_property_profile.profile = VCD_PROFILE_MPEG4_SP; + break; + } + } else if (codec == V4L2_CID_MPEG_VIDEO_H264_PROFILE) { + switch (profile) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + vcd_property_profile.profile = + VCD_PROFILE_H264_BASELINE; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + vcd_property_profile.profile = VCD_PROFILE_H264_MAIN; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + vcd_property_profile.profile = VCD_PROFILE_H264_HIGH; + break; + default: + WFD_MSG_ERR("Profile %d not supported," + "defaulting to baseline (%d)", + profile, VCD_PROFILE_H264_BASELINE); + vcd_property_profile.profile = + VCD_PROFILE_H264_BASELINE; + break; + } + } else { + WFD_MSG_ERR("Codec (%d) not supported," + "not setting profile (%d)", + codec, profile); + rc = -ENOTSUPP; + goto err_set_profile; + } + + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_profile); + + /* Disable B-frames, since VSG doesn't support out of order i/p bufs */ + vcd_property_hdr.prop_id = VCD_I_INTRA_PERIOD; + vcd_property_hdr.sz = sizeof(struct vcd_property_i_period); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_i_period); + if (rc) { + WFD_MSG_ERR("Error getting I-period property"); + goto err_set_profile; + } + vcd_property_i_period.b_frames = 0; + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_i_period); + if (rc) { + WFD_MSG_ERR("Error setting I-period property"); + goto err_set_profile; + } + +err_set_profile: + return rc; +} + +static long venc_get_codec_profile(struct video_client_ctx *client_ctx, + __s32 codec, __s32 *profile) +{ + struct vcd_property_profile vcd_property_profile; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_codec vcd_property_codec; + int rc = 0; + + /* Validate params */ + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (rc < 0) { + WFD_MSG_ERR("Error getting codec property"); + rc = -EINVAL; + goto err; + } + + if (!((vcd_property_codec.codec == VCD_CODEC_H264 + && codec == V4L2_CID_MPEG_VIDEO_H264_PROFILE) || + (vcd_property_codec.codec == VCD_CODEC_MPEG4 + && codec == V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE))) { + WFD_MSG_ERR("Attempting to set %d for codec type %d", + codec, vcd_property_codec.codec); + rc = -EINVAL; + goto err; + } + + /* Set property */ + vcd_property_hdr.prop_id = VCD_I_PROFILE; + vcd_property_hdr.sz = sizeof(struct vcd_property_profile); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_profile); + + if (rc < 0) { + WFD_MSG_ERR("Unable to get property"); + rc = -EINVAL; + goto err; + } + + switch (vcd_property_profile.profile) { + case VCD_PROFILE_MPEG4_SP: + *profile = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE; + break; + case VCD_PROFILE_MPEG4_ASP: + *profile = V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE; + break; + case VCD_PROFILE_H264_BASELINE: + *profile = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; + break; + case VCD_PROFILE_H264_MAIN: + *profile = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; + break; + case VCD_PROFILE_H264_HIGH: + *profile = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; + break; + default: + WFD_MSG_ERR("Unexpected profile"); + rc = -EINVAL; + goto err; + break; + } +err: + return rc; +} + +static long venc_set_h264_intra_period(struct video_client_ctx *client_ctx, + __s32 period) +{ + struct vcd_property_i_period vcd_property_i_period; + struct vcd_property_codec vcd_property_codec; + struct vcd_property_hdr vcd_property_hdr; + int rc = 0; + + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (rc < 0) { + WFD_MSG_ERR("Error getting codec property\n"); + goto err; + } + + if (vcd_property_codec.codec != VCD_CODEC_H264) { + rc = -ENOTSUPP; + WFD_MSG_ERR("Control not supported for non H264 codec\n"); + goto err; + } + + vcd_property_hdr.prop_id = VCD_I_INTRA_PERIOD; + vcd_property_hdr.sz = sizeof(struct vcd_property_i_period); + + vcd_property_i_period.p_frames = period - 1; + vcd_property_i_period.b_frames = 0; + + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_i_period); + + if (rc < 0) { + WFD_MSG_ERR("Error setting intra period\n"); + goto err; + } + +err: + return rc; +} + +static long venc_get_h264_intra_period(struct video_client_ctx *client_ctx, + __s32 *period) +{ + struct vcd_property_i_period vcd_property_i_period; + struct vcd_property_codec vcd_property_codec; + struct vcd_property_hdr vcd_property_hdr; + int rc = 0; + + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (rc < 0) { + WFD_MSG_ERR("Error getting codec property\n"); + goto err; + } + + if (vcd_property_codec.codec != VCD_CODEC_H264) { + rc = -ENOTSUPP; + WFD_MSG_ERR("Control not supported for non H264 codec\n"); + goto err; + } + + vcd_property_hdr.prop_id = VCD_I_INTRA_PERIOD; + vcd_property_hdr.sz = sizeof(struct vcd_property_i_period); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_i_period); + + if (rc < 0) { + WFD_MSG_ERR("Error getting intra period\n"); + goto err; + } + + *period = vcd_property_i_period.p_frames + 1; +err: + return rc; +} + +static long venc_request_frame(struct video_client_ctx *client_ctx, __s32 type) +{ + struct vcd_property_req_i_frame vcd_property_req_i_frame; + struct vcd_property_hdr vcd_property_hdr; + + int rc = 0; + switch (type) { + case V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED: + /*So...nothing to do?*/ + break; + case V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_I_FRAME: + vcd_property_hdr.prop_id = VCD_I_REQ_IFRAME; + vcd_property_hdr.sz = sizeof(struct vcd_property_req_i_frame); + vcd_property_req_i_frame.req_i_frame = 1; + + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_req_i_frame); + break; + case V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_NOT_CODED: + default: + rc = -ENOTSUPP; + } + + return rc; +} + +static long venc_set_bitrate(struct video_client_ctx *client_ctx, + __s32 bitrate) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_target_bitrate bit_rate; + if (!client_ctx || !bitrate) + return -EINVAL; + + vcd_property_hdr.prop_id = VCD_I_TARGET_BITRATE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_target_bitrate); + bit_rate.target_bitrate = bitrate; + return vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &bit_rate); +} + +static long venc_get_bitrate(struct video_client_ctx *client_ctx, + __s32 *bitrate) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_target_bitrate bit_rate; + int rc = 0; + + if (!client_ctx || !bitrate) + return -EINVAL; + + vcd_property_hdr.prop_id = VCD_I_TARGET_BITRATE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_target_bitrate); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &bit_rate); + + if (rc < 0) { + WFD_MSG_ERR("Failed getting property for bitrate"); + return rc; + } + + *bitrate = bit_rate.target_bitrate; + return rc; +} + +static long venc_set_bitrate_mode(struct video_client_ctx *client_ctx, + __s32 mode) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_rate_control rate_control; + int rc = 0; + + if (!client_ctx) { + rc = -EINVAL; + goto err; + } + + vcd_property_hdr.prop_id = VCD_I_RATE_CONTROL; + vcd_property_hdr.sz = sizeof(struct vcd_property_rate_control); + /* + * XXX: V4L doesn't seem have a control to toggle between CFR + * and VFR, so assuming worse case VFR. + */ + switch (mode) { + case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR: + rate_control.rate_control = VCD_RATE_CONTROL_VBR_VFR; + break; + case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR: + rate_control.rate_control = VCD_RATE_CONTROL_CBR_VFR; + break; + default: + WFD_MSG_ERR("unknown bitrate mode %d", mode); + rc = -EINVAL; + goto err; + } + + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &rate_control); +err: + return rc; +} + +static long venc_get_bitrate_mode(struct video_client_ctx *client_ctx, + __s32 *mode) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_rate_control rate_control; + int rc = 0; + + if (!client_ctx) + return -EINVAL; + + vcd_property_hdr.prop_id = VCD_I_RATE_CONTROL; + vcd_property_hdr.sz = sizeof(struct vcd_property_rate_control); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &rate_control); + + switch (rate_control.rate_control) { + case VCD_RATE_CONTROL_CBR_VFR: + case VCD_RATE_CONTROL_CBR_CFR: + *mode = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR; + break; + case VCD_RATE_CONTROL_VBR_VFR: + case VCD_RATE_CONTROL_VBR_CFR: + *mode = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR; + break; + default: + WFD_MSG_ERR("unknown bitrate mode %d", + rate_control.rate_control); + return -EINVAL; + } + + return 0; +} + +static long venc_set_frame_size(struct video_client_ctx *client_ctx, + u32 height, u32 width) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_size frame_size; + vcd_property_hdr.prop_id = VCD_I_FRAME_SIZE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_frame_size); + frame_size.height = height; + frame_size.width = width; + return vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &frame_size); +} + +static long venc_set_format(struct v4l2_subdev *sd, void *arg) +{ + struct venc_inst *inst; + struct video_client_ctx *client_ctx; + struct v4l2_format *fmt = arg; + struct vcd_buffer_requirement buf_req; + int rc = 0; + + inst = sd->dev_priv; + client_ctx = &inst->venc_client; + if (!inst || !client_ctx || !fmt) { + WFD_MSG_ERR("Invalid parameters\n"); + return -EINVAL; + } + rc = venc_set_codec(client_ctx, fmt->fmt.pix.pixelformat); + if (rc) { + WFD_MSG_ERR("Failed to set codec, rc = %d\n", rc); + goto err; + } + + rc = venc_set_frame_size(client_ctx, fmt->fmt.pix.height, + fmt->fmt.pix.width); + if (rc) { + WFD_MSG_ERR("Failed to set frame size, rc = %d\n", rc); + goto err; + } + rc = vcd_get_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, &buf_req); + if (rc) { + WFD_MSG_ERR("Failed to get buf requrements, rc = %d\n", rc); + goto err; + } + fmt->fmt.pix.sizeimage = buf_req.sz; +err: + return rc; +} + +static long venc_set_framerate(struct v4l2_subdev *sd, + void *arg) +{ + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct v4l2_fract *frate = arg; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_rate vcd_frame_rate; + struct vcd_property_vop_timing_constant_delta vcd_delta; + int rc; + vcd_property_hdr.prop_id = VCD_I_FRAME_RATE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_frame_rate); + /* v4l2 passes in "fps" as "spf", so take reciprocal*/ + vcd_frame_rate.fps_denominator = frate->numerator; + vcd_frame_rate.fps_numerator = frate->denominator; + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_frame_rate); + if (rc) { + WFD_MSG_ERR("Failed to set frame rate, rc = %d\n", rc); + goto set_framerate_fail; + } + + vcd_property_hdr.prop_id = VCD_I_VOP_TIMING_CONSTANT_DELTA; + vcd_property_hdr.sz = sizeof(vcd_delta); + + vcd_delta.constant_delta = (frate->numerator * USEC_PER_SEC) / + frate->denominator; + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_delta); + + if (rc) { + WFD_MSG_ERR("Failed to set frame delta, rc = %d", rc); + goto set_framerate_fail; + } + +set_framerate_fail: + return rc; +} + +static long venc_set_qp_value(struct video_client_ctx *client_ctx, + __s32 frametype, __s32 qp) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_session_qp vcd_property_session_qp; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + return -EINVAL; + } + + vcd_property_hdr.prop_id = VCD_I_SESSION_QP; + vcd_property_hdr.sz = sizeof(vcd_property_session_qp); + + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_session_qp); + + if (rc) { + WFD_MSG_ERR("Failed to get session qp\n"); + goto err; + } + + switch (frametype) { + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + vcd_property_session_qp.i_frame_qp = qp; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + vcd_property_session_qp.p_frame_qp = qp; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + vcd_property_session_qp.b_frame_qp = qp; + break; + case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP: + rc = -ENOTSUPP; + goto err; + default: + rc = -EINVAL; + goto err; + } + + + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_session_qp); + + if (rc) { + WFD_MSG_ERR("Failed to set session qp\n"); + goto err; + } +err: + return rc; +} + +static long venc_get_qp_value(struct video_client_ctx *client_ctx, + __s32 frametype, __s32 *qp) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_session_qp vcd_property_session_qp; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + return -EINVAL; + } + + vcd_property_hdr.prop_id = VCD_I_SESSION_QP; + vcd_property_hdr.sz = sizeof(vcd_property_session_qp); + + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_session_qp); + + if (rc) { + WFD_MSG_ERR("Failed to get session qp\n"); + goto err; + } + + switch (frametype) { + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + *qp = vcd_property_session_qp.i_frame_qp; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + *qp = vcd_property_session_qp.p_frame_qp; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + *qp = vcd_property_session_qp.b_frame_qp; + break; + default: + rc = -EINVAL; + goto err; + } + +err: + return rc; +} + +static long venc_set_qp_range(struct video_client_ctx *client_ctx, + __s32 type, __s32 qp) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_qp_range vcd_property_qp_range; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + return -EINVAL; + } + + vcd_property_hdr.prop_id = VCD_I_QP_RANGE; + vcd_property_hdr.sz = sizeof(vcd_property_qp_range); + + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_qp_range); + + if (rc) { + WFD_MSG_ERR("Failed to get qp range\n"); + goto err; + } + + switch (type) { + case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H263_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + vcd_property_qp_range.min_qp = qp; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H263_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + vcd_property_qp_range.max_qp = qp; + break; + default: + rc = -EINVAL; + goto err; + } + + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_qp_range); + + if (rc) { + WFD_MSG_ERR("Failed to set qp range\n"); + goto err; + } +err: + return rc; +} + +static long venc_get_qp_range(struct video_client_ctx *client_ctx, + __s32 type, __s32 *qp) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_qp_range vcd_property_qp_range; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + return -EINVAL; + } + + vcd_property_hdr.prop_id = VCD_I_QP_RANGE; + vcd_property_hdr.sz = sizeof(vcd_property_qp_range); + + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_qp_range); + + if (rc) { + WFD_MSG_ERR("Failed to get qp range\n"); + goto err; + } + + switch (type) { + case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H263_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + *qp = vcd_property_qp_range.min_qp; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H263_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + *qp = vcd_property_qp_range.max_qp; + break; + default: + rc = -EINVAL; + goto err; + } + + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_property_qp_range); + + if (rc) { + WFD_MSG_ERR("Failed to set qp range\n"); + goto err; + } +err: + return rc; +} +static long venc_set_max_perf_level(struct video_client_ctx *client_ctx, + int val) +{ + int rc = 0; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_perf_level perf; + vcd_property_hdr.prop_id = VCD_REQ_PERF_LEVEL; + vcd_property_hdr.sz = + sizeof(struct vcd_property_perf_level); + perf.level = VCD_PERF_LEVEL2; + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &perf); + return rc; +} +static long venc_set_header_mode(struct video_client_ctx *client_ctx, + __s32 mode) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_sps_pps_for_idr_enable sps_pps_for_idr_enable; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto err; + } + + vcd_property_hdr.prop_id = VCD_I_ENABLE_SPS_PPS_FOR_IDR; + vcd_property_hdr.sz = sizeof(sps_pps_for_idr_enable); + switch (mode) { + case V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE: + sps_pps_for_idr_enable.sps_pps_for_idr_enable_flag = 0; + break; + case V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_I_FRAME: + sps_pps_for_idr_enable.sps_pps_for_idr_enable_flag = 1; + break; + case V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME: + default: + WFD_MSG_ERR("Video header mode %d not supported\n", + mode); + rc = -ENOTSUPP; + goto err; + } + + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &sps_pps_for_idr_enable); + if (rc) { + WFD_MSG_ERR("Failed to set enable_sps_pps_for_idr\n"); + goto err; + } +err: + return rc; +} + +static long venc_get_header_mode(struct video_client_ctx *client_ctx, + __s32 *mode) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_sps_pps_for_idr_enable sps_pps_for_idr_enable; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto err; + } + + vcd_property_hdr.prop_id = VCD_I_ENABLE_SPS_PPS_FOR_IDR; + vcd_property_hdr.sz = sizeof(sps_pps_for_idr_enable); + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &sps_pps_for_idr_enable); + if (rc) { + WFD_MSG_ERR("Failed to get sps/pps for idr enable\n"); + goto err; + } + + *mode = sps_pps_for_idr_enable.sps_pps_for_idr_enable_flag ? + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_I_FRAME : + V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE; +err: + return rc; +} + +static long venc_set_multislicing_mode(struct video_client_ctx *client_ctx, + __u32 control, __s32 value) +{ + int rc = 0; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_size vcd_frame_size; + struct vcd_buffer_requirement vcd_buf_req; + struct vcd_property_multi_slice vcd_multi_slice; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto set_multislicing_mode_fail; + } + + vcd_property_hdr.prop_id = VCD_I_FRAME_SIZE; + vcd_property_hdr.sz = + sizeof(vcd_frame_size); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_frame_size); + + if (rc) { + WFD_MSG_ERR("Failed to get frame size\n"); + goto set_multislicing_mode_fail; + } + + rc = vcd_get_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, &vcd_buf_req); + + if (rc) { + WFD_MSG_ERR("Failed to get buf reqs\n"); + goto set_multislicing_mode_fail; + } + + vcd_property_hdr.prop_id = VCD_I_MULTI_SLICE; + vcd_property_hdr.sz = sizeof(vcd_multi_slice); + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_multi_slice); + if (rc) { + WFD_MSG_ERR("Failed to get multi slice\n"); + goto set_multislicing_mode_fail; + } + + switch (control) { + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + if (vcd_multi_slice.m_slice_sel != + VCD_MSLICE_BY_BYTE_COUNT) { + WFD_MSG_ERR("Not in proper mode\n"); + goto set_multislicing_mode_fail; + } + vcd_multi_slice.m_slice_size = value; + break; + + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + if (vcd_multi_slice.m_slice_sel != + VCD_MSLICE_BY_MB_COUNT) { + WFD_MSG_ERR("Not in proper mode\n"); + goto set_multislicing_mode_fail; + } + vcd_multi_slice.m_slice_size = value; + break; + + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + switch (value) { + case V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE: + vcd_multi_slice.m_slice_sel = VCD_MSLICE_OFF; + break; + case V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_MB: + vcd_multi_slice.m_slice_sel = VCD_MSLICE_BY_MB_COUNT; + /* Just a temporary size until client calls + * V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB */ + vcd_multi_slice.m_slice_size = + (vcd_frame_size.stride / 16) * + (vcd_frame_size.scan_lines / 16); + break; + case V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES: + vcd_multi_slice.m_slice_sel = VCD_MSLICE_BY_BYTE_COUNT; + /* Just a temporary size until client calls + * V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES */ + vcd_multi_slice.m_slice_size = vcd_buf_req.sz; + break; + default: + WFD_MSG_ERR("Unrecognized mode %d\n", value); + rc = -ENOTSUPP; + goto set_multislicing_mode_fail; + } + + break; + default: + rc = -EINVAL; + goto set_multislicing_mode_fail; + } + + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_multi_slice); + if (rc) { + WFD_MSG_ERR("Failed to set multi slice\n"); + goto set_multislicing_mode_fail; + } + +set_multislicing_mode_fail: + return rc; +} + +static long venc_get_multislicing_mode(struct video_client_ctx *client_ctx, + __u32 control, __s32 *value) +{ + int rc = 0; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_size vcd_frame_size; + struct vcd_buffer_requirement vcd_buf_req; + struct vcd_property_multi_slice vcd_multi_slice; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto get_multislicing_mode_fail; + } + + vcd_property_hdr.prop_id = VCD_I_FRAME_SIZE; + vcd_property_hdr.sz = + sizeof(vcd_frame_size); + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_frame_size); + + if (rc) { + WFD_MSG_ERR("Failed to get frame size\n"); + goto get_multislicing_mode_fail; + } + + vcd_property_hdr.prop_id = VCD_I_MULTI_SLICE; + vcd_property_hdr.sz = sizeof(vcd_multi_slice); + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_multi_slice); + if (rc) { + WFD_MSG_ERR("Failed to get multi slice\n"); + goto get_multislicing_mode_fail; + } + + rc = vcd_get_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, &vcd_buf_req); + + if (rc) { + WFD_MSG_ERR("Failed to get buf reqs\n"); + goto get_multislicing_mode_fail; + } + + switch (control) { + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + if (vcd_multi_slice.m_slice_sel == VCD_MSLICE_BY_BYTE_COUNT) + *value = vcd_multi_slice.m_slice_size; + else { + WFD_MSG_ERR("Invalid query when in slice mode %d\n", + vcd_multi_slice.m_slice_sel); + rc = -EINVAL; + goto get_multislicing_mode_fail; + } + break; + + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + if (vcd_multi_slice.m_slice_sel == VCD_MSLICE_BY_MB_COUNT) + *value = vcd_multi_slice.m_slice_size; + else { + WFD_MSG_ERR("Invalid query when in slice mode %d\n", + vcd_multi_slice.m_slice_sel); + rc = -EINVAL; + goto get_multislicing_mode_fail; + } + break; + + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + switch (vcd_multi_slice.m_slice_sel) { + case VCD_MSLICE_OFF: + *value = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE; + break; + case VCD_MSLICE_BY_MB_COUNT: + *value = V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_MB; + break; + case VCD_MSLICE_BY_BYTE_COUNT: + *value = V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES; + break; + default: + WFD_MSG_ERR("Encoder in an unknown mode %d\n", + vcd_multi_slice.m_slice_sel); + rc = -ENOENT; + goto get_multislicing_mode_fail; + + } + break; + default: + rc = -EINVAL; + goto get_multislicing_mode_fail; + } + +get_multislicing_mode_fail: + return rc; +} + +static long venc_set_entropy_mode(struct video_client_ctx *client_ctx, + __s32 value) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_entropy_control entropy_control; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto set_entropy_mode_fail; + } + + vcd_property_hdr.prop_id = VCD_I_ENTROPY_CTRL; + vcd_property_hdr.sz = sizeof(entropy_control); + + switch (value) { + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC: + entropy_control.entropy_sel = VCD_ENTROPY_SEL_CAVLC; + break; + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC: + entropy_control.entropy_sel = VCD_ENTROPY_SEL_CABAC; + entropy_control.cabac_model = VCD_CABAC_MODEL_NUMBER_0; + break; + default: + WFD_MSG_ERR("Entropy type %d not supported\n", value); + rc = -ENOTSUPP; + goto set_entropy_mode_fail; + } + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &entropy_control); + if (rc) { + WFD_MSG_ERR("Failed to set entropy mode\n"); + goto set_entropy_mode_fail; + } + +set_entropy_mode_fail: + return rc; +} + +static long venc_get_entropy_mode(struct video_client_ctx *client_ctx, + __s32 *value) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_entropy_control entropy_control; + int rc = 0; + + if (!client_ctx || !value) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto get_entropy_mode_fail; + } + + vcd_property_hdr.prop_id = VCD_I_ENTROPY_CTRL; + vcd_property_hdr.sz = sizeof(entropy_control); + + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &entropy_control); + + if (rc) { + WFD_MSG_ERR("Failed to get entropy mode\n"); + goto get_entropy_mode_fail; + } + + switch (entropy_control.entropy_sel) { + case VCD_ENTROPY_SEL_CAVLC: + *value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC; + break; + case VCD_ENTROPY_SEL_CABAC: + *value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC; + break; + default: + WFD_MSG_ERR("Entropy type %d not known\n", + entropy_control.entropy_sel); + rc = -EINVAL; + goto get_entropy_mode_fail; + } +get_entropy_mode_fail: + return rc; +} + +static long venc_set_cyclic_intra_refresh_mb( + struct video_client_ctx *client_ctx, + __s32 value) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_intra_refresh_mb_number cir_mb_num; + int rc = 0; + + if (!client_ctx) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto set_cir_mbs_fail; + } + + vcd_property_hdr.prop_id = VCD_I_INTRA_REFRESH; + vcd_property_hdr.sz = sizeof(cir_mb_num); + + cir_mb_num.cir_mb_number = value; + + rc = vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &cir_mb_num); + if (rc) { + WFD_MSG_ERR("Failed to set CIR MBs\n"); + goto set_cir_mbs_fail; + } + +set_cir_mbs_fail: + return rc; +} + +static long venc_get_cyclic_intra_refresh_mb( + struct video_client_ctx *client_ctx, + __s32 *value) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_intra_refresh_mb_number cir_mb_num; + int rc = 0; + + if (!client_ctx || !value) { + WFD_MSG_ERR("Invalid parameters\n"); + rc = -EINVAL; + goto get_cir_mbs_fail; + } + + vcd_property_hdr.prop_id = VCD_I_INTRA_REFRESH; + vcd_property_hdr.sz = sizeof(cir_mb_num); + + rc = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &cir_mb_num); + if (rc) { + WFD_MSG_ERR("Failed to set CIR MBs\n"); + goto get_cir_mbs_fail; + } + + *value = cir_mb_num.cir_mb_number; + +get_cir_mbs_fail: + return rc; +} +static long venc_set_input_buffer(struct v4l2_subdev *sd, void *arg) +{ + struct mem_region *mregion = arg; + struct venc_inst *inst = sd->dev_priv; + unsigned long paddr, kvaddr, temp; + struct video_client_ctx *client_ctx = &inst->venc_client; + int rc = 0; + + if (!client_ctx || !mregion) { + WFD_MSG_ERR("Invalid input\n"); + rc = -EINVAL; + goto ins_table_fail; + } + + kvaddr = (unsigned long)mregion->kvaddr; + paddr = (unsigned long)mregion->paddr; + + if (!kvaddr || !paddr) { + WFD_MSG_ERR("Invalid addresses\n"); + rc = -EINVAL; + goto ins_table_fail; + } + + /* + * Just a note: the third arg of vidc_insert_\ + * addr_table_kernel is supposed to be a userspace + * address that is used as a key in the table. As + * these bufs never leave the kernel, we need to have + * an unique value to use as a key. So re-using kernel + * virtual addr for this purpose + */ + rc = vidc_insert_addr_table_kernel(client_ctx, + BUFFER_TYPE_INPUT, kvaddr, kvaddr, + paddr, 32, mregion->size); + + if (rc == (u32)false) { + WFD_MSG_ERR("Failed to insert input buffer into table\n"); + rc = -EFAULT; + goto ins_table_fail; + } + + rc = vcd_set_buffer(client_ctx->vcd_handle, + VCD_BUFFER_INPUT, (u8 *)kvaddr, + mregion->size); + + if (rc) { + WFD_MSG_ERR("Failed to set input buffer\n"); + rc = -EFAULT; + goto set_input_buf_fail; + } + + + return rc; + +set_input_buf_fail: + vidc_delete_addr_table(client_ctx, BUFFER_TYPE_INPUT, + kvaddr, &temp); +ins_table_fail: + return rc; +} + +static long venc_set_output_buffer(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct mem_region *mregion = arg; + if (!client_ctx || !mregion) { + WFD_MSG_ERR("Invalid input\n"); + return -EINVAL; + } + WFD_MSG_DBG("size = %u, offset = %u fd = %d\n", mregion->size, + mregion->offset, mregion->fd); + rc = vidc_insert_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + mregion->cookie, + (unsigned long *)&mregion->kvaddr, + mregion->fd, + mregion->offset, + 32, + mregion->size); + if (rc == (u32)false) { + WFD_MSG_ERR("Failed to insert outbuf in table\n"); + rc = -EINVAL; + goto err; + } + WFD_MSG_DBG("size = %u, %p\n", mregion->size, mregion->kvaddr); + + rc = vcd_set_buffer(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, (u8 *) mregion->kvaddr, + mregion->size); + if (rc) + WFD_MSG_ERR("Failed to set outbuf on encoder\n"); +err: + return rc; +} + +static long venc_fill_outbuf(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct mem_region *mregion = arg; + struct vcd_frame_data vcd_frame = {0}; + unsigned long kernel_vaddr, phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + + user_vaddr = mregion->cookie; + rc = vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + true, &user_vaddr, + &kernel_vaddr, &phy_addr, &pmem_fd, &file, + &buffer_index); + if (!rc) { + WFD_MSG_ERR("Address lookup failed\n"); + goto err; + } + vcd_frame.virtual = (u8 *) kernel_vaddr; + vcd_frame.frm_clnt_data = mregion->cookie; + vcd_frame.alloc_len = mregion->size; + + rc = vcd_fill_output_buffer(client_ctx->vcd_handle, &vcd_frame); + if (rc) + WFD_MSG_ERR("Failed to fill output buffer on encoder"); +err: + return rc; +} + +static long venc_encode_frame(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct venc_buf_info *venc_buf = arg; + struct mem_region *mregion = venc_buf->mregion; + struct vcd_frame_data vcd_input_buffer = {0}; + int64_t ts = 0; + + ts = venc_buf->timestamp; + do_div(ts, NSEC_PER_USEC); + + vcd_input_buffer.virtual = mregion->kvaddr; + vcd_input_buffer.frm_clnt_data = (u32)mregion; + vcd_input_buffer.ip_frm_tag = (u32)mregion; + vcd_input_buffer.data_len = mregion->size; + vcd_input_buffer.time_stamp = ts; + vcd_input_buffer.offset = 0; + + rc = vcd_encode_frame(client_ctx->vcd_handle, + &vcd_input_buffer); + + if (rc) + WFD_MSG_ERR("encode frame failed\n"); + return rc; +} + +static long venc_alloc_recon_buffers(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_buffer_size control; + struct vcd_property_enc_recon_buffer *ctrl = NULL; + unsigned long phy_addr; + int i = 0; + int flags = 0; + u32 len; + control.width = inst->width; + control.height = inst->height; + vcd_property_hdr.prop_id = VCD_I_GET_RECON_BUFFER_SIZE; + vcd_property_hdr.sz = sizeof(struct vcd_property_buffer_size); + + rc = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (rc) { + WFD_MSG_ERR("Failed to get recon buf size\n"); + goto err; + } + flags = ION_HEAP(ION_CP_MM_HEAP_ID); + flags |= inst->secure ? ION_SECURE : ION_HEAP(ION_IOMMU_HEAP_ID); + + if (vcd_get_ion_status()) { + for (i = 0; i < 4; ++i) { + ctrl = &client_ctx->recon_buffer[i]; + ctrl->buffer_size = control.size; + ctrl->pmem_fd = 0; + ctrl->offset = 0; + ctrl->user_virtual_addr = (void *)i; + client_ctx->recon_buffer_ion_handle[i] + = ion_alloc(client_ctx->user_ion_client, + control.size, SZ_8K, flags); + + ctrl->kernel_virtual_addr = ion_map_kernel( + client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], 0); + + rc = ion_map_iommu(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + VIDEO_DOMAIN, VIDEO_MAIN_POOL, SZ_4K, + 0, &phy_addr, (unsigned long *)&len, 0, 0); + if (rc) { + WFD_MSG_ERR("Failed to allo recon buffers\n"); + break; + } + ctrl->physical_addr = (u8 *) phy_addr; + ctrl->dev_addr = ctrl->physical_addr; + vcd_property_hdr.prop_id = VCD_I_RECON_BUFFERS; + vcd_property_hdr.sz = + sizeof(struct vcd_property_enc_recon_buffer); + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, ctrl); + if (rc) { + WFD_MSG_ERR("Failed to set recon buffers\n"); + break; + } + } + } else { + WFD_MSG_ERR("PMEM not suported\n"); + return -ENOMEM; + } +err: + return rc; +} + +static long venc_free_output_buffer(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct mem_region *mregion = arg; + unsigned long kernel_vaddr, user_vaddr; + + if (!client_ctx || !mregion) { + WFD_MSG_ERR("Invalid input\n"); + return -EINVAL; + } + + user_vaddr = mregion->cookie; + rc = vidc_delete_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + user_vaddr, + &kernel_vaddr); + if (!rc) { + WFD_MSG_ERR("Failed to delete buf from address table\n"); + return -EINVAL; + } + return vcd_free_buffer(client_ctx->vcd_handle, VCD_BUFFER_OUTPUT, + (u8 *)kernel_vaddr); +} + +static long venc_flush_buffers(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + if (!client_ctx) { + WFD_MSG_ERR("Invalid input\n"); + return -EINVAL; + } + rc = vcd_flush(client_ctx->vcd_handle, VCD_FLUSH_INPUT); + if (rc) { + WFD_MSG_ERR("Failed to flush input buffers\n"); + rc = -EIO; + goto flush_failed; + } + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) { + WFD_MSG_ERR("callback for vcd_flush input returned error: %u", + client_ctx->event_status); + rc = -EIO; + goto flush_failed; + } + rc = vcd_flush(client_ctx->vcd_handle, VCD_FLUSH_OUTPUT); + if (rc) { + WFD_MSG_ERR("Failed to flush output buffers\n"); + rc = -EIO; + goto flush_failed; + } + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) { + WFD_MSG_ERR("callback for vcd_flush output returned error: %u", + client_ctx->event_status); + rc = -EIO; + goto flush_failed; + } + +flush_failed: + return rc; +} + +static long venc_free_input_buffer(struct v4l2_subdev *sd, void *arg) +{ + int del_rc = 0, free_rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct mem_region *mregion = arg; + unsigned long vidc_kvaddr; + + if (!client_ctx || !mregion) { + WFD_MSG_ERR("Invalid input\n"); + return -EINVAL; + } + + del_rc = vidc_delete_addr_table(client_ctx, BUFFER_TYPE_INPUT, + (unsigned long)mregion->kvaddr, + &vidc_kvaddr); + /* + * Even if something went wrong in when + * deleting from table, call vcd_free_buf + */ + if (del_rc == (u32)false) { + WFD_MSG_ERR("Failed to delete buf from address table\n"); + del_rc = -ENOKEY; + } else if ((u8 *)vidc_kvaddr != mregion->kvaddr) { + WFD_MSG_ERR("Failed to find expected buffer\n"); + del_rc = -EINVAL; + } else + del_rc = 0; + + free_rc = vcd_free_buffer(client_ctx->vcd_handle, VCD_BUFFER_INPUT, + (u8 *)vidc_kvaddr); + + if (free_rc) { + WFD_MSG_ERR("Failed to free buffer from encoder\n"); + free_rc = -EINVAL; + } + + return del_rc ? del_rc : free_rc; +} + +static long venc_free_recon_buffers(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct video_client_ctx *client_ctx = &inst->venc_client; + struct vcd_property_hdr vcd_property_hdr; + int i; + + if (vcd_get_ion_status()) { + for (i = 0; i < 4; i++) { + vcd_property_hdr.prop_id = VCD_I_FREE_RECON_BUFFERS; + vcd_property_hdr.sz = + sizeof(struct vcd_property_buffer_size); + rc = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &client_ctx->recon_buffer[i]); + if (rc) + WFD_MSG_ERR("Failed to free recon buffer\n"); + + if (client_ctx->recon_buffer_ion_handle[i]) { + ion_unmap_iommu(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + VIDEO_DOMAIN, VIDEO_MAIN_POOL); + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i]); + ion_free(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i]); + client_ctx->recon_buffer_ion_handle[i] = NULL; + } + } + } + return rc; +} + +static long venc_set_property(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct v4l2_control *ctrl = arg; + struct video_client_ctx *client_ctx = &inst->venc_client; + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE: + rc = venc_set_bitrate(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + rc = venc_set_bitrate_mode(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + rc = venc_set_h264_intra_period(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + rc = venc_set_codec_level(client_ctx, ctrl->id, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + rc = venc_set_codec_profile(client_ctx, ctrl->id, ctrl->value); + break; + case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE: + rc = venc_request_frame(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP: + rc = venc_set_qp_value(client_ctx, ctrl->id, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H263_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H263_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + rc = venc_set_qp_range(client_ctx, ctrl->id, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + rc = venc_set_header_mode(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + rc = venc_set_multislicing_mode(client_ctx, ctrl->id, + ctrl->value); + break; + case V4L2_CID_MPEG_QCOM_SET_PERF_LEVEL: + rc = venc_set_max_perf_level(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + rc = venc_set_entropy_mode(client_ctx, ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: + rc = venc_set_cyclic_intra_refresh_mb(client_ctx, ctrl->value); + break; + default: + WFD_MSG_ERR("Set property not suported: %d\n", ctrl->id); + rc = -ENOTSUPP; + break; + } + return rc; +} + +static long venc_get_property(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct venc_inst *inst = sd->dev_priv; + struct v4l2_control *ctrl = arg; + struct video_client_ctx *client_ctx = &inst->venc_client; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE: + rc = venc_get_bitrate(client_ctx, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + rc = venc_get_bitrate_mode(client_ctx, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + rc = venc_get_codec_level(client_ctx, ctrl->id, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + rc = venc_get_codec_profile(client_ctx, ctrl->id, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + rc = venc_get_h264_intra_period(client_ctx, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP: + rc = venc_get_qp_value(client_ctx, ctrl->id, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP: + case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H263_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H263_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + rc = venc_get_qp_range(client_ctx, ctrl->id, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + rc = venc_get_header_mode(client_ctx, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + rc = venc_get_multislicing_mode(client_ctx, ctrl->id, + &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + rc = venc_get_entropy_mode(client_ctx, &ctrl->value); + break; + case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: + rc = venc_get_cyclic_intra_refresh_mb(client_ctx, &ctrl->value); + break; + default: + WFD_MSG_ERR("Get property not suported: %d\n", ctrl->id); + rc = -ENOTSUPP; + break; + } + return rc; +} + +long venc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + long rc = 0; + switch (cmd) { + case OPEN: + rc = venc_open(sd, arg); + break; + case CLOSE: + rc = venc_close(sd, arg); + break; + case ENCODE_START: + rc = venc_start(sd); + break; + case ENCODE_FRAME: + venc_encode_frame(sd, arg); + break; + case ENCODE_STOP: + rc = venc_stop(sd); + break; + case SET_PROP: + rc = venc_set_property(sd, arg); + break; + case GET_PROP: + rc = venc_get_property(sd, arg); + break; + case GET_BUFFER_REQ: + rc = venc_get_buffer_req(sd, arg); + break; + case SET_BUFFER_REQ: + rc = venc_set_buffer_req(sd, arg); + break; + case FREE_BUFFER: + break; + case FILL_OUTPUT_BUFFER: + rc = venc_fill_outbuf(sd, arg); + break; + case SET_FORMAT: + rc = venc_set_format(sd, arg); + break; + case SET_FRAMERATE: + rc = venc_set_framerate(sd, arg); + break; + case SET_INPUT_BUFFER: + rc = venc_set_input_buffer(sd, arg); + break; + case SET_OUTPUT_BUFFER: + rc = venc_set_output_buffer(sd, arg); + break; + case ALLOC_RECON_BUFFERS: + rc = venc_alloc_recon_buffers(sd, arg); + break; + case FREE_OUTPUT_BUFFER: + rc = venc_free_output_buffer(sd, arg); + break; + case FREE_INPUT_BUFFER: + rc = venc_free_input_buffer(sd, arg); + break; + case FREE_RECON_BUFFERS: + rc = venc_free_recon_buffers(sd, arg); + break; + case ENCODE_FLUSH: + rc = venc_flush_buffers(sd, arg); + break; + default: + rc = -1; + break; + } + return rc; +} diff --git a/drivers/media/video/msm/wfd/enc-subdev.h b/drivers/media/video/msm/wfd/enc-subdev.h new file mode 100644 index 0000000000000000000000000000000000000000..5873e6253d8b1611af059bab1f940deb657a4b3b --- /dev/null +++ b/drivers/media/video/msm/wfd/enc-subdev.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +*/ + +#ifndef _WFD_ENC_SUBDEV_ +#define _WFD_ENC_SUBDEV_ + +#include +#include +#include +#define VENC_MAGIC_IOCTL 'V' + +struct mem_region { + struct list_head list; + u8 *kvaddr; + u8 *paddr; + u32 size; + u32 offset; + u32 fd; + u32 cookie; + struct ion_handle *ion_handle; +}; +struct bufreq { + u32 count; + u32 height; + u32 width; + u32 size; +}; + +struct venc_buf_info { + u64 timestamp; + struct mem_region *mregion; +}; + +struct venc_msg_ops { + void *cookie; + void *cbdata; + int secure; + void (*op_buffer_done)(void *cookie, u32 status, + struct vb2_buffer *buf); + void (*ip_buffer_done)(void *cookie, u32 status, + struct mem_region *mregion); +}; + +#define OPEN _IOR('V', 1, void *) +#define CLOSE _IO('V', 2) +#define ENCODE_START _IO('V', 3) +#define ENCODE_FRAME _IOW('V', 4, struct venc_buf_info *) +#define PAUSE _IO('V', 5) +#define RESUME _IO('V', 6) +#define FLUSH _IO('V', 7) +#define ENCODE_STOP _IO('V', 8) +#define SET_PROP _IO('V', 9) +#define GET_PROP _IO('V', 10) +#define SET_BUFFER_REQ _IOWR('V', 11, struct v4l2_requestbuffers *) +#define GET_BUFFER_REQ _IOWR('V', 12, struct v4l2_requestbuffers *) +#define ALLOCATE_BUFFER _IO('V', 13) +#define FREE_BUFFER _IO('V', 14) +#define FILL_OUTPUT_BUFFER _IO('V', 15) +#define SET_FORMAT _IOW('V', 16, struct v4l2_format *) +#define SET_FRAMERATE _IOW('V', 17, struct v4l2_fract *) +#define SET_INPUT_BUFFER _IOWR('V', 18, struct mem_region *) +#define SET_OUTPUT_BUFFER _IOWR('V', 19, struct mem_region *) +#define ALLOC_RECON_BUFFERS _IO('V', 20) +#define FREE_OUTPUT_BUFFER _IOWR('V', 21, struct mem_region *) +#define FREE_INPUT_BUFFER _IOWR('V', 22, struct mem_region *) +#define FREE_RECON_BUFFERS _IO('V', 23) +#define ENCODE_FLUSH _IO('V', 24) + +extern int venc_init(struct v4l2_subdev *sd, u32 val); +extern int venc_load_fw(struct v4l2_subdev *sd); +extern long venc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg); + + +#endif /* _WFD_ENC_SUBDEV_ */ diff --git a/drivers/media/video/msm/wfd/mdp-subdev.c b/drivers/media/video/msm/wfd/mdp-subdev.c new file mode 100644 index 0000000000000000000000000000000000000000..a6d244f9423ae0c6b511fdccfb38751b48618933 --- /dev/null +++ b/drivers/media/video/msm/wfd/mdp-subdev.c @@ -0,0 +1,200 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY 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-subdev.h" +#include "wfd-util.h" +#include +#include + +struct mdp_instance { + struct fb_info *mdp; + u32 height; + u32 width; +}; + +int mdp_init(struct v4l2_subdev *sd, u32 val) +{ + return 0; +} +int mdp_open(struct v4l2_subdev *sd, void *arg) +{ + struct mdp_instance *inst = kzalloc(sizeof(struct mdp_instance), + GFP_KERNEL); + void **cookie = (void **)arg; + int rc = 0; + struct fb_info *fbi = NULL; + + if (!inst) { + WFD_MSG_ERR("Out of memory\n"); + return -ENOMEM; + } + + fbi = msm_fb_get_writeback_fb(); + if (!fbi) { + WFD_MSG_ERR("Failed to acquire mdp instance\n"); + rc = -ENODEV; + goto exit; + } + + msm_fb_writeback_init(fbi); + inst->mdp = fbi; + *cookie = inst; + return rc; +exit: + kfree(inst); + return rc; +} + +int mdp_start(struct v4l2_subdev *sd, void *arg) +{ + struct mdp_instance *inst = arg; + int rc = 0; + struct fb_info *fbi = NULL; + if (inst) { + rc = msm_fb_writeback_start(inst->mdp); + if (rc) { + WFD_MSG_ERR("Failed to start MDP mode\n"); + goto exit; + } + fbi = msm_fb_get_writeback_fb(); + if (!fbi) { + WFD_MSG_ERR("Failed to acquire mdp instance\n"); + rc = -ENODEV; + goto exit; + } + } +exit: + return rc; +} +int mdp_stop(struct v4l2_subdev *sd, void *arg) +{ + struct mdp_instance *inst = arg; + int rc = 0; + if (inst) { + rc = msm_fb_writeback_stop(inst->mdp); + if (rc) { + WFD_MSG_ERR("Failed to stop writeback mode\n"); + return rc; + } + } + return 0; +} +int mdp_close(struct v4l2_subdev *sd, void *arg) +{ + struct mdp_instance *inst = arg; + struct fb_info *fbi = NULL; + if (inst) { + fbi = (struct fb_info *)inst->mdp; + msm_fb_writeback_terminate(fbi); + kfree(inst); + } + return 0; +} +int mdp_q_buffer(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct mdp_buf_info *binfo = arg; + struct msmfb_data fbdata; + struct mdp_instance *inst; + if (!binfo || !binfo->inst || !binfo->cookie) { + WFD_MSG_ERR("Invalid argument\n"); + return -EINVAL; + } + inst = binfo->inst; + fbdata.offset = binfo->offset; + fbdata.memory_id = binfo->fd; + fbdata.iova = binfo->paddr; + fbdata.id = 0; + fbdata.flags = 0; + fbdata.priv = (uint32_t)binfo->cookie; + + WFD_MSG_INFO("queue buffer to mdp with offset = %u," + "fd = %u, priv = %p, iova = %p\n", + fbdata.offset, fbdata.memory_id, + (void *)fbdata.priv, (void *)fbdata.iova); + rc = msm_fb_writeback_queue_buffer(inst->mdp, &fbdata); + + if (rc) + WFD_MSG_ERR("Failed to queue buffer\n"); + return rc; +} +int mdp_dq_buffer(struct v4l2_subdev *sd, void *arg) +{ + int rc = 0; + struct mdp_buf_info *obuf = arg; + struct msmfb_data fbdata; + struct mdp_instance *inst; + if (!arg) { + WFD_MSG_ERR("Invalid argument\n"); + return -EINVAL; + } + + inst = obuf->inst; + fbdata.flags = MSMFB_WRITEBACK_DEQUEUE_BLOCKING; + rc = msm_fb_writeback_dequeue_buffer(inst->mdp, &fbdata); + if (rc) { + WFD_MSG_ERR("Failed to dequeue buffer\n"); + return rc; + } + WFD_MSG_DBG("dequeue buf from mdp with priv = %u\n", + fbdata.priv); + obuf->cookie = (void *)fbdata.priv; + return rc; +} +int mdp_set_prop(struct v4l2_subdev *sd, void *arg) +{ + struct mdp_prop *prop = (struct mdp_prop *)arg; + struct mdp_instance *inst = prop->inst; + if (!prop || !inst) { + WFD_MSG_ERR("Invalid arguments\n"); + return -EINVAL; + } + inst->height = prop->height; + inst->width = prop->width; + return 0; +} +long mdp_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + int rc = 0; + if (!sd) { + WFD_MSG_ERR("Invalid arguments\n"); + return -EINVAL; + } + switch (cmd) { + case MDP_Q_BUFFER: + rc = mdp_q_buffer(sd, arg); + break; + case MDP_DQ_BUFFER: + rc = mdp_dq_buffer(sd, arg); + break; + case MDP_OPEN: + rc = mdp_open(sd, arg); + break; + case MDP_START: + rc = mdp_start(sd, arg); + break; + case MDP_STOP: + rc = mdp_stop(sd, arg); + break; + case MDP_SET_PROP: + rc = mdp_set_prop(sd, arg); + break; + case MDP_CLOSE: + rc = mdp_close(sd, arg); + break; + default: + WFD_MSG_ERR("IOCTL: %u not supported\n", cmd); + rc = -EINVAL; + break; + } + return rc; +} diff --git a/drivers/media/video/msm/wfd/mdp-subdev.h b/drivers/media/video/msm/wfd/mdp-subdev.h new file mode 100644 index 0000000000000000000000000000000000000000..081fead129ef5322f5bdbbb4106e98d1db4a1c54 --- /dev/null +++ b/drivers/media/video/msm/wfd/mdp-subdev.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +*/ + +#ifndef _WFD_MDP_SUBDEV_ +#define _WFD_MDP_SUBDEV_ + +#include +#include + +#define MDP_MAGIC_IOCTL 'M' + +struct mdp_buf_info { + void *inst; + void *cookie; + u32 fd; + u32 offset; + u32 kvaddr; + u32 paddr; +}; + +struct mdp_prop { + void *inst; + u32 height; + u32 width; +}; + +static inline bool mdp_buf_info_equals(struct mdp_buf_info *a, + struct mdp_buf_info *b) +{ + return a->inst == b->inst + && a->fd == b->fd + && a->offset == b->offset + && a->kvaddr == b->kvaddr + && a->paddr == b->paddr; +} + +#define MDP_Q_BUFFER _IOW(MDP_MAGIC_IOCTL, 1, struct mdp_buf_info *) +#define MDP_DQ_BUFFER _IOR(MDP_MAGIC_IOCTL, 2, struct mdp_out_buf *) +#define MDP_OPEN _IOR(MDP_MAGIC_IOCTL, 3, void **) +#define MDP_SET_PROP _IOW(MDP_MAGIC_IOCTL, 4, struct mdp_prop *) +#define MDP_CLOSE _IOR(MDP_MAGIC_IOCTL, 5, void *) +#define MDP_START _IOR(MDP_MAGIC_IOCTL, 6, void *) +#define MDP_STOP _IOR(MDP_MAGIC_IOCTL, 7, void *) +extern int mdp_init(struct v4l2_subdev *sd, u32 val); +extern long mdp_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg); + + +#endif /* _WFD_MDP_SUBDEV_ */ diff --git a/drivers/media/video/msm/wfd/vsg-subdev.c b/drivers/media/video/msm/wfd/vsg-subdev.c new file mode 100644 index 0000000000000000000000000000000000000000..73b840b183b7082e4630c806ea58b47238fbfbda --- /dev/null +++ b/drivers/media/video/msm/wfd/vsg-subdev.c @@ -0,0 +1,700 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +*/ +#include +#include +#include +#include +#include "enc-subdev.h" +#include "vsg-subdev.h" +#include "wfd-util.h" + +#define DEFAULT_FRAME_INTERVAL (66*NSEC_PER_MSEC) +#define DEFAULT_MAX_FRAME_INTERVAL (1*NSEC_PER_SEC) +#define DEFAULT_MODE ((enum vsg_modes)VSG_MODE_CFR) +#define MAX_BUFS_BUSY_WITH_ENC 5 + +static int vsg_release_input_buffer(struct vsg_context *context, + struct vsg_buf_info *buf) +{ + WFD_MSG_DBG("Releasing frame with ts %lld ms, paddr %p\n", + timespec_to_ns(&buf->time), + (void *)buf->mdp_buf_info.paddr); + + if (buf->flags & VSG_NEVER_RELEASE) + WFD_MSG_WARN("Warning releasing buffer that's" + "not supposed to be released\n"); + + return context->vmops.release_input_frame(context->vmops.cbdata, + buf); + +} + +static int vsg_encode_frame(struct vsg_context *context, + struct vsg_buf_info *buf) +{ + WFD_MSG_DBG("Encoding frame with ts %lld ms, paddr %p\n", + timespec_to_ns(&buf->time), + (void *)buf->mdp_buf_info.paddr); + + return context->vmops.encode_frame(context->vmops.cbdata, + buf); +} + +static void vsg_set_last_buffer(struct vsg_context *context, + struct vsg_buf_info *buf) +{ + if (buf->flags & VSG_NEVER_SET_LAST_BUFFER) + WFD_MSG_WARN("Shouldn't be setting this to last buffer\n"); + + context->last_buffer = buf; + + WFD_MSG_DBG("Setting last buffer to paddr %p\n", + (void *)buf->mdp_buf_info.paddr); +} + +static void vsg_encode_helper_func(struct work_struct *task) +{ + struct vsg_encode_work *work = + container_of(task, struct vsg_encode_work, work); + + /* + * Note: don't need to lock for context below as we only + * access fields that are "static". + */ + int rc = vsg_encode_frame(work->context, work->buf); + if (rc < 0) { + mutex_lock(&work->context->mutex); + work->context->state = VSG_STATE_ERROR; + mutex_unlock(&work->context->mutex); + } + kfree(work); +} + +static void vsg_work_func(struct work_struct *task) +{ + struct vsg_work *work = + container_of(task, struct vsg_work, work); + struct vsg_encode_work *encode_work; + struct vsg_context *context = work->context; + struct vsg_buf_info *buf_info = NULL, *temp = NULL; + int rc = 0, count = 0; + mutex_lock(&context->mutex); + + if (list_empty(&context->free_queue.node)) { + WFD_MSG_DBG("%s: queue empty doing nothing\n", __func__); + goto err_skip_encode; + } else if (context->state != VSG_STATE_STARTED) { + WFD_MSG_DBG("%s: vsg is stopped or in error state " + "doing nothing\n", __func__); + goto err_skip_encode; + } + + list_for_each_entry(temp, &context->busy_queue.node, node) { + if (++count > MAX_BUFS_BUSY_WITH_ENC) { + WFD_MSG_WARN("Skipping encode, too many " + "buffers with encoder"); + goto err_skip_encode; + } + } + + buf_info = list_first_entry(&context->free_queue.node, + struct vsg_buf_info, node); + list_del(&buf_info->node); + INIT_LIST_HEAD(&buf_info->node); + + ktime_get_ts(&buf_info->time); + hrtimer_forward_now(&context->threshold_timer, ns_to_ktime( + context->max_frame_interval)); + + temp = NULL; + list_for_each_entry(temp, &context->busy_queue.node, node) { + if (mdp_buf_info_equals(&temp->mdp_buf_info, + &buf_info->mdp_buf_info)) { + temp->flags |= VSG_NEVER_RELEASE; + } + } + + if (context->last_buffer && + mdp_buf_info_equals(&context->last_buffer->mdp_buf_info, + &buf_info->mdp_buf_info)) { + context->last_buffer->flags |= VSG_NEVER_RELEASE; + } + + encode_work = kmalloc(sizeof(*encode_work), GFP_KERNEL); + encode_work->buf = buf_info; + encode_work->context = context; + INIT_WORK(&encode_work->work, vsg_encode_helper_func); + rc = queue_work(context->work_queue, &encode_work->work); + if (!rc) { + WFD_MSG_ERR("Queueing buffer for encode failed\n"); + kfree(encode_work); + encode_work = NULL; + goto err_skip_encode; + } + + buf_info->flags |= VSG_BUF_BEING_ENCODED; + if (!(buf_info->flags & VSG_NEVER_SET_LAST_BUFFER)) { + if (context->last_buffer) { + struct vsg_buf_info *old_last_buffer = + context->last_buffer; + bool last_buf_with_us = old_last_buffer && + !(old_last_buffer->flags & + VSG_BUF_BEING_ENCODED); + bool can_release = old_last_buffer && + !(old_last_buffer->flags & + VSG_NEVER_RELEASE); + + if (old_last_buffer && last_buf_with_us + && can_release) { + vsg_release_input_buffer(context, + old_last_buffer); + kfree(old_last_buffer); + } + } + vsg_set_last_buffer(context, buf_info); + } + + list_add_tail(&buf_info->node, &context->busy_queue.node); +err_skip_encode: + mutex_unlock(&context->mutex); + kfree(work); +} + +static void vsg_timer_helper_func(struct work_struct *task) +{ + struct vsg_work *work = + container_of(task, struct vsg_work, work); + struct vsg_work *new_work = NULL; + struct vsg_context *context = work->context; + int num_bufs_to_queue = 1, c = 0; + + mutex_lock(&context->mutex); + + if (context->state != VSG_STATE_STARTED) + goto err_locked; + + if (list_empty(&context->free_queue.node) + && context->last_buffer) { + struct vsg_buf_info *info = NULL, *buf_to_encode = NULL; + + if (context->mode == VSG_MODE_CFR) + num_bufs_to_queue = 1; + else if (context->mode == VSG_MODE_VFR) + num_bufs_to_queue = 2; + + for (c = 0; c < num_bufs_to_queue; ++c) { + info = kzalloc(sizeof(*info), GFP_KERNEL); + + if (!info) { + WFD_MSG_ERR("Couldn't allocate memory in %s\n", + __func__); + goto err_locked; + } + + buf_to_encode = context->last_buffer; + + info->mdp_buf_info = buf_to_encode->mdp_buf_info; + info->flags = 0; + INIT_LIST_HEAD(&info->node); + + list_add_tail(&info->node, &context->free_queue.node); + WFD_MSG_DBG("Regenerated frame with paddr %p\n", + (void *)info->mdp_buf_info.paddr); + } + } + + for (c = 0; c < num_bufs_to_queue; ++c) { + new_work = kzalloc(sizeof(*new_work), GFP_KERNEL); + if (!new_work) { + WFD_MSG_ERR("Unable to allocate memory" + "to queue buffer\n"); + goto err_locked; + } + + INIT_WORK(&new_work->work, vsg_work_func); + new_work->context = context; + queue_work(context->work_queue, &new_work->work); + } + +err_locked: + mutex_unlock(&context->mutex); + kfree(work); +} + +static enum hrtimer_restart vsg_threshold_timeout_func(struct hrtimer *timer) +{ + struct vsg_context *context = NULL; + struct vsg_work *task = NULL; + + task = kzalloc(sizeof(*task), GFP_ATOMIC); + context = container_of(timer, struct vsg_context, + threshold_timer); + if (!task) { + WFD_MSG_ERR("Out of memory in %s", __func__); + goto threshold_err_bad_param; + } else if (!context) { + WFD_MSG_ERR("Context not proper in %s", __func__); + goto threshold_err_no_context; + } + + INIT_WORK(&task->work, vsg_timer_helper_func); + task->context = context; + + queue_work(context->work_queue, &task->work); +threshold_err_bad_param: + hrtimer_forward_now(&context->threshold_timer, ns_to_ktime( + context->max_frame_interval)); + return HRTIMER_RESTART; +threshold_err_no_context: + return HRTIMER_NORESTART; +} + +int vsg_init(struct v4l2_subdev *sd, u32 val) +{ + return 0; +} + +static int vsg_open(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + + if (!arg || !sd) + return -EINVAL; + + context = kzalloc(sizeof(*context), GFP_KERNEL); + INIT_LIST_HEAD(&context->free_queue.node); + INIT_LIST_HEAD(&context->busy_queue.node); + + context->vmops = *(struct vsg_msg_ops *)arg; + context->work_queue = create_singlethread_workqueue("v4l-vsg"); + + context->frame_interval = DEFAULT_FRAME_INTERVAL; + context->max_frame_interval = DEFAULT_MAX_FRAME_INTERVAL; + + hrtimer_init(&context->threshold_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + context->threshold_timer.function = vsg_threshold_timeout_func; + + context->last_buffer = NULL; + context->mode = DEFAULT_MODE; + context->state = VSG_STATE_NONE; + mutex_init(&context->mutex); + + sd->dev_priv = context; + return 0; +} + +static int vsg_close(struct v4l2_subdev *sd) +{ + struct vsg_context *context = NULL; + + if (!sd) + return -EINVAL; + + context = (struct vsg_context *)sd->dev_priv; + destroy_workqueue(context->work_queue); + kfree(context); + return 0; +} + +static int vsg_start(struct v4l2_subdev *sd) +{ + struct vsg_context *context = NULL; + + if (!sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + return -EINVAL; + } + + context = (struct vsg_context *)sd->dev_priv; + + if (context->state == VSG_STATE_STARTED) { + WFD_MSG_ERR("VSG not stopped, start not allowed\n"); + return -EINPROGRESS; + } else if (context->state == VSG_STATE_ERROR) { + WFD_MSG_ERR("VSG in error state, not allowed to restart\n"); + return -ENOTRECOVERABLE; + } + + context->state = VSG_STATE_STARTED; + hrtimer_start(&context->threshold_timer, ns_to_ktime(context-> + max_frame_interval), HRTIMER_MODE_REL); + return 0; +} + +static int vsg_stop(struct v4l2_subdev *sd) +{ + struct vsg_context *context = NULL; + + if (!sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + return -EINVAL; + } + + context = (struct vsg_context *)sd->dev_priv; + + mutex_lock(&context->mutex); + context->state = VSG_STATE_STOPPED; + { /*delete pending buffers as we're not going to encode them*/ + struct list_head *pos, *next; + list_for_each_safe(pos, next, &context->free_queue.node) { + struct vsg_buf_info *temp = + list_entry(pos, struct vsg_buf_info, node); + list_del(&temp->node); + kfree(temp); + } + } + + hrtimer_cancel(&context->threshold_timer); + + mutex_unlock(&context->mutex); + + flush_workqueue(context->work_queue); + return 0; +} + +static long vsg_queue_buffer(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + struct vsg_buf_info *buf_info = kzalloc(sizeof(*buf_info), GFP_KERNEL); + int rc = 0; + bool push = false; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + rc = -EINVAL; + goto queue_err_bad_param; + } else if (!buf_info) { + WFD_MSG_ERR("ERROR, out of memory in %s\n", __func__); + rc = -ENOMEM; + goto queue_err_bad_param; + } + + context = (struct vsg_context *)sd->dev_priv; + mutex_lock(&context->mutex); + + *buf_info = *(struct vsg_buf_info *)arg; + INIT_LIST_HEAD(&buf_info->node); + buf_info->flags = 0; + ktime_get_ts(&buf_info->time); + + WFD_MSG_DBG("Queue frame with paddr %p\n", + (void *)buf_info->mdp_buf_info.paddr); + + { /*return pending buffers as we're not going to encode them*/ + struct list_head *pos, *next; + list_for_each_safe(pos, next, &context->free_queue.node) { + struct vsg_buf_info *temp = + list_entry(pos, struct vsg_buf_info, node); + bool is_last_buffer = context->last_buffer && + mdp_buf_info_equals( + &context->last_buffer->mdp_buf_info, + &temp->mdp_buf_info); + + list_del(&temp->node); + + if (!is_last_buffer && + !(temp->flags & VSG_NEVER_RELEASE)) { + vsg_release_input_buffer(context, temp); + kfree(temp); + } + } + } + + list_add_tail(&buf_info->node, &context->free_queue.node); + + if (context->mode == VSG_MODE_VFR) { + if (!context->last_buffer) + push = true; + else { + struct timespec diff = timespec_sub(buf_info->time, + context->last_buffer->time); + struct timespec temp = ns_to_timespec( + context->frame_interval); + + if (timespec_compare(&diff, &temp) >= 0) + push = true; + } + } else if (context->mode == VSG_MODE_CFR) { + if (!context->last_buffer) { + push = true; + /* + * We need to reset the timer after pushing the buffer + * otherwise, diff between two consecutive frames might + * be less than max_frame_interval (for just one sample) + */ + hrtimer_forward_now(&context->threshold_timer, + ns_to_ktime(context->max_frame_interval)); + } + } + + if (push) { + struct vsg_work *new_work = + kzalloc(sizeof(*new_work), GFP_KERNEL); + + INIT_WORK(&new_work->work, vsg_work_func); + new_work->context = context; + queue_work(context->work_queue, &new_work->work); + } + + mutex_unlock(&context->mutex); +queue_err_bad_param: + if (rc < 0) + kfree(buf_info); + + return rc; +} + +static long vsg_return_ip_buffer(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + struct vsg_buf_info *buf_info, *last_buffer, + *expected_buffer; + int rc = 0; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + rc = -EINVAL; + goto return_ip_buf_err_bad_param; + } + + context = (struct vsg_context *)sd->dev_priv; + mutex_lock(&context->mutex); + buf_info = (struct vsg_buf_info *)arg; + last_buffer = context->last_buffer; + + expected_buffer = list_first_entry(&context->busy_queue.node, + struct vsg_buf_info, node); + + WFD_MSG_DBG("Return frame with paddr %p\n", + (void *)buf_info->mdp_buf_info.paddr); + + if (!expected_buffer) { + WFD_MSG_ERR("Unexpectedly received buffer from enc with " + "paddr %p\n", (void *)buf_info->mdp_buf_info.paddr); + goto return_ip_buf_bad_buf; + } + + expected_buffer->flags &= ~VSG_BUF_BEING_ENCODED; + if (mdp_buf_info_equals(&expected_buffer->mdp_buf_info, + &buf_info->mdp_buf_info)) { + bool is_same_buffer = context->last_buffer && + mdp_buf_info_equals( + &context->last_buffer->mdp_buf_info, + &expected_buffer->mdp_buf_info); + + list_del(&expected_buffer->node); + if (!is_same_buffer && + !(expected_buffer->flags & VSG_NEVER_RELEASE)) { + vsg_release_input_buffer(context, expected_buffer); + kfree(expected_buffer); + } + } else { + WFD_MSG_ERR("Returned buffer %p is not latest buffer, " + "expected %p\n", + (void *)buf_info->mdp_buf_info.paddr, + (void *)expected_buffer->mdp_buf_info.paddr); + rc = -EINVAL; + goto return_ip_buf_bad_buf; + } + +return_ip_buf_bad_buf: + mutex_unlock(&context->mutex); +return_ip_buf_err_bad_param: + return rc; +} + +static long vsg_set_frame_interval(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + int64_t interval; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + return -EINVAL; + } + + context = (struct vsg_context *)sd->dev_priv; + interval = *(int64_t *)arg; + + if (interval <= 0) { + WFD_MSG_ERR("ERROR, invalid interval %lld into %s\n", + interval, __func__); + return -EINVAL; + } + + mutex_lock(&context->mutex); + + context->frame_interval = interval; + if (interval > context->max_frame_interval) { + WFD_MSG_WARN("Changing max frame interval from %lld to %lld\n", + context->max_frame_interval, interval); + context->max_frame_interval = interval; + } + + mutex_unlock(&context->mutex); + return 0; +} + +static long vsg_get_frame_interval(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + return -EINVAL; + } + + context = (struct vsg_context *)sd->dev_priv; + mutex_lock(&context->mutex); + *(int64_t *)arg = context->frame_interval; + mutex_unlock(&context->mutex); + + return 0; +} + +static long vsg_set_max_frame_interval(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + int64_t interval; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + return -EINVAL; + } + + context = (struct vsg_context *)sd->dev_priv; + interval = *(int64_t *)arg; + + if (interval <= 0) { + WFD_MSG_ERR("ERROR, invalid interval %lld into %s\n", + interval, __func__); + return -EINVAL; + } + + mutex_lock(&context->mutex); + + context->max_frame_interval = interval; + if (interval < context->frame_interval) { + WFD_MSG_WARN("Changing frame interval from %lld to %lld\n", + context->frame_interval, interval); + context->frame_interval = interval; + } + + mutex_unlock(&context->mutex); + + return 0; +} + +static long vsg_get_max_frame_interval(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + return -EINVAL; + } + + context = (struct vsg_context *)sd->dev_priv; + mutex_lock(&context->mutex); + *(int64_t *)arg = context->max_frame_interval; + mutex_unlock(&context->mutex); + + return 0; +} + +static long vsg_set_mode(struct v4l2_subdev *sd, void *arg) +{ + struct vsg_context *context = NULL; + enum vsg_modes *mode = NULL; + int rc = 0; + + if (!arg || !sd) { + WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); + rc = -EINVAL; + goto set_mode_err_bad_parm; + } + + context = (struct vsg_context *)sd->dev_priv; + mutex_lock(&context->mutex); + mode = arg; + + switch (*mode) { + case VSG_MODE_CFR: + context->max_frame_interval = context->frame_interval; + /*fall through*/ + case VSG_MODE_VFR: + context->mode = *mode; + break; + default: + context->mode = DEFAULT_MODE; + rc = -EINVAL; + goto set_mode_err_bad_mode; + break; + } + +set_mode_err_bad_mode: + mutex_unlock(&context->mutex); +set_mode_err_bad_parm: + return rc; +} + +long vsg_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + int rc = 0; + + WFD_MSG_DBG("VSG ioctl: %d\n", cmd); + if (sd == NULL) + return -EINVAL; + + switch (cmd) { + case VSG_OPEN: + rc = vsg_open(sd, arg); + break; + case VSG_CLOSE: + rc = vsg_close(sd); + break; + case VSG_START: + rc = vsg_start(sd); + break; + case VSG_STOP: + rc = vsg_stop(sd); + break; + case VSG_Q_BUFFER: + rc = vsg_queue_buffer(sd, arg); + break; + case VSG_RETURN_IP_BUFFER: + rc = vsg_return_ip_buffer(sd, arg); + break; + case VSG_GET_FRAME_INTERVAL: + rc = vsg_get_frame_interval(sd, arg); + break; + case VSG_SET_FRAME_INTERVAL: + rc = vsg_set_frame_interval(sd, arg); + break; + case VSG_GET_MAX_FRAME_INTERVAL: + rc = vsg_get_max_frame_interval(sd, arg); + break; + case VSG_SET_MAX_FRAME_INTERVAL: + rc = vsg_set_max_frame_interval(sd, arg); + break; + case VSG_SET_MODE: + rc = vsg_set_mode(sd, arg); + break; + default: + rc = -ENOTSUPP; + break; + } + + return rc; +} diff --git a/drivers/media/video/msm/wfd/vsg-subdev.h b/drivers/media/video/msm/wfd/vsg-subdev.h new file mode 100644 index 0000000000000000000000000000000000000000..dfc2e2e5382e9810d768310274c98178d884d738 --- /dev/null +++ b/drivers/media/video/msm/wfd/vsg-subdev.h @@ -0,0 +1,100 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +*/ + +#ifndef _WFD_VSG_SUBDEV_ +#define _WFD_VSG_SUBDEV_ + +#include +#include +#include +#include +#include +#include "mdp-subdev.h" + +#define VSG_MAGIC_IOCTL 'V' + +enum vsg_flags { + VSG_NEVER_RELEASE = 1<<0, + VSG_NEVER_SET_LAST_BUFFER = 1<<1, + VSG_BUF_BEING_ENCODED = 1<<2, +}; + +enum vsg_modes { + VSG_MODE_CFR, + VSG_MODE_VFR, +}; + +enum vsg_states { + VSG_STATE_NONE, + VSG_STATE_STARTED, + VSG_STATE_STOPPED, + VSG_STATE_ERROR +}; + +struct vsg_buf_info { + struct mdp_buf_info mdp_buf_info; + struct timespec time; + /* Internal */ + struct list_head node; + uint32_t flags; +}; + +struct vsg_msg_ops { + void *cbdata; + int (*encode_frame)(void *cbdata, struct vsg_buf_info *buffer); + int (*release_input_frame)(void *cbdata, struct vsg_buf_info *buffer); +}; + +struct vsg_context { + struct vsg_buf_info free_queue, busy_queue; + struct vsg_msg_ops vmops; + /* All time related values below in nanosecs */ + int64_t frame_interval, max_frame_interval; + struct workqueue_struct *work_queue; + struct hrtimer threshold_timer; + struct mutex mutex; + struct vsg_buf_info *last_buffer; + int mode; + int state; +}; + +struct vsg_work { + struct vsg_context *context; + struct work_struct work; +}; + +struct vsg_encode_work { + struct vsg_buf_info *buf; + struct vsg_context *context; + struct work_struct work; +}; + +#define VSG_OPEN _IO(VSG_MAGIC_IOCTL, 1) +#define VSG_CLOSE _IO(VSG_MAGIC_IOCTL, 2) +#define VSG_START _IO(VSG_MAGIC_IOCTL, 3) +#define VSG_STOP _IO(VSG_MAGIC_IOCTL, 4) +#define VSG_Q_BUFFER _IOW(VSG_MAGIC_IOCTL, 5, struct vsg_buf_info *) +#define VSG_DQ_BUFFER _IOR(VSG_MAGIC_IOCTL, 6, struct vsg_out_buf *) +#define VSG_RETURN_IP_BUFFER _IOW(VSG_MAGIC_IOCTL, 7, struct vsg_buf_info *) +#define VSG_ENCODE_DONE _IO(VSG_MAGIC_IOCTL, 8) +/* Time related arguments for frame interval ioctls are always in nanosecs*/ +#define VSG_SET_FRAME_INTERVAL _IOW(VSG_MAGIC_IOCTL, 9, int64_t *) +#define VSG_GET_FRAME_INTERVAL _IOR(VSG_MAGIC_IOCTL, 10, int64_t *) +#define VSG_SET_MAX_FRAME_INTERVAL _IOW(VSG_MAGIC_IOCTL, 11, int64_t *) +#define VSG_GET_MAX_FRAME_INTERVAL _IOR(VSG_MAGIC_IOCTL, 12, int64_t *) +#define VSG_SET_MODE _IOW(VSG_MAGIC_IOCTL, 13, enum vsg_modes *) + +extern int vsg_init(struct v4l2_subdev *sd, u32 val); +extern long vsg_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg); + +#endif /* _WFD_VSG_SUBDEV_ */ diff --git a/drivers/media/video/msm/wfd/wfd-ioctl.c b/drivers/media/video/msm/wfd/wfd-ioctl.c new file mode 100644 index 0000000000000000000000000000000000000000..c1988153d15063e87aaab263a633c730e56f3e16 --- /dev/null +++ b/drivers/media/video/msm/wfd/wfd-ioctl.c @@ -0,0 +1,1592 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "wfd-util.h" +#include "mdp-subdev.h" +#include "enc-subdev.h" +#include "vsg-subdev.h" + +#define WFD_VERSION KERNEL_VERSION(0, 0, 1) +#define WFD_NUM_DEVICES 2 +#define WFD_DEVICE_NUMBER_BASE 38 +#define WFD_DEVICE_SECURE (WFD_DEVICE_NUMBER_BASE + 1) +#define DEFAULT_WFD_WIDTH 640 +#define DEFAULT_WFD_HEIGHT 480 +#define VENC_INPUT_BUFFERS 4 + +struct wfd_device { + struct mutex dev_lock; + struct platform_device *pdev; + struct v4l2_device v4l2_dev; + struct video_device *pvdev; + struct v4l2_subdev mdp_sdev; + struct v4l2_subdev enc_sdev; + struct v4l2_subdev vsg_sdev; + struct ion_client *ion_client; + bool secure_device; + bool in_use; +}; + +struct mem_info { + u32 fd; + u32 offset; +}; + +struct mem_info_entry { + struct list_head list; + unsigned long userptr; + struct mem_info minfo; +}; + +struct mem_region_pair { + struct mem_region *enc; + struct mem_region *mdp; + struct list_head list; +}; + +struct wfd_inst { + struct vb2_queue vid_bufq; + spinlock_t inst_lock; + u32 buf_count; + struct task_struct *mdp_task; + void *mdp_inst; + void *venc_inst; + u32 height; + u32 width; + u32 pixelformat; + struct list_head minfo_list; + bool streamoff; + u32 input_bufs_allocated; + u32 input_buf_size; + u32 out_buf_size; + struct list_head input_mem_list; + struct wfd_stats stats; +}; + +struct wfd_vid_buffer { + struct vb2_buffer vidbuf; +}; + +static int wfd_vidbuf_queue_setup(struct vb2_queue *q, + const struct v4l2_format *fmt, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct file *priv_data = (struct file *)(q->drv_priv); + struct wfd_inst *inst = (struct wfd_inst *)priv_data->private_data; + unsigned long flags; + int i; + + WFD_MSG_DBG("In %s\n", __func__); + if (num_buffers == NULL || num_planes == NULL) + return -EINVAL; + + *num_planes = 1; + spin_lock_irqsave(&inst->inst_lock, flags); + for (i = 0; i < *num_planes; ++i) { + sizes[i] = inst->out_buf_size; + alloc_ctxs[i] = inst; + } + spin_unlock_irqrestore(&inst->inst_lock, flags); + + return 0; +} + +void wfd_vidbuf_wait_prepare(struct vb2_queue *q) +{ +} +void wfd_vidbuf_wait_finish(struct vb2_queue *q) +{ +} + +static unsigned long wfd_enc_addr_to_mdp_addr(struct wfd_inst *inst, + unsigned long addr) +{ + struct list_head *ptr, *next; + struct mem_region_pair *mpair; + if (!list_empty(&inst->input_mem_list)) { + list_for_each_safe(ptr, next, + &inst->input_mem_list) { + mpair = list_entry(ptr, struct mem_region_pair, + list); + if (mpair->enc->paddr == (u8 *)addr) + return (unsigned long)mpair->mdp->paddr; + } + } + + return (unsigned long)NULL; +} + +static int wfd_allocate_ion_buffer(struct ion_client *client, + bool secure, struct mem_region *mregion) +{ + struct ion_handle *handle; + void *kvaddr, *phys_addr; + unsigned long size; + unsigned int alloc_regions = 0; + int rc; + + alloc_regions = ION_HEAP(ION_CP_MM_HEAP_ID); + alloc_regions |= secure ? ION_SECURE : + ION_HEAP(ION_IOMMU_HEAP_ID); + handle = ion_alloc(client, + mregion->size, SZ_4K, alloc_regions); + + if (IS_ERR_OR_NULL(handle)) { + WFD_MSG_ERR("Failed to allocate input buffer\n"); + rc = PTR_ERR(handle); + goto alloc_fail; + } + + kvaddr = ion_map_kernel(client, handle, CACHED); + + if (IS_ERR_OR_NULL(kvaddr)) { + WFD_MSG_ERR("Failed to get virtual addr\n"); + rc = PTR_ERR(kvaddr); + goto alloc_fail; + } + + rc = ion_map_iommu(client, handle, + VIDEO_DOMAIN, VIDEO_MAIN_POOL, SZ_4K, + 0, (unsigned long *)&phys_addr, + &size, 0, 0); + + if (rc) { + WFD_MSG_ERR("Failed to get physical addr\n"); + goto alloc_fail; + } else if (size < mregion->size) { + WFD_MSG_ERR("Failed to map enough memory\n"); + rc = -ENOMEM; + goto alloc_fail; + } + + mregion->kvaddr = kvaddr; + mregion->paddr = phys_addr; + mregion->ion_handle = handle; + + return rc; +alloc_fail: + if (!IS_ERR_OR_NULL(handle)) { + ion_unmap_kernel(client, handle); + ion_free(client, handle); + + mregion->kvaddr = NULL; + mregion->paddr = NULL; + mregion->ion_handle = NULL; + } + return rc; +} + +/* Doesn't do iommu unmap */ +static int wfd_free_ion_buffer(struct ion_client *client, + struct mem_region *mregion) +{ + if (!client || !mregion) { + WFD_MSG_ERR("Failed to free ion buffer: " + "Invalid client or region"); + return -EINVAL; + } + ion_unmap_kernel(client, mregion->ion_handle); + ion_free(client, mregion->ion_handle); + return 0; +} + +static int wfd_flush_ion_buffer(struct ion_client *client, + struct mem_region *mregion) +{ + if (!client || !mregion) { + WFD_MSG_ERR("Failed to flush ion buffer: " + "Invalid client or region"); + return -EINVAL; + } else if (!mregion->ion_handle) { + WFD_MSG_ERR("Failed to flush ion buffer: " + "not an ion buffer"); + return -EINVAL; + } + + return msm_ion_do_cache_op(client, + mregion->ion_handle, + mregion->kvaddr, + mregion->size, + ION_IOC_INV_CACHES); + +} +int wfd_allocate_input_buffers(struct wfd_device *wfd_dev, + struct wfd_inst *inst) +{ + int i; + struct mem_region *enc_mregion, *mdp_mregion; + struct mem_region_pair *mpair; + int rc; + unsigned long flags; + struct mdp_buf_info mdp_buf = {0}; + spin_lock_irqsave(&inst->inst_lock, flags); + if (inst->input_bufs_allocated) { + spin_unlock_irqrestore(&inst->inst_lock, flags); + return 0; + } + inst->input_bufs_allocated = true; + spin_unlock_irqrestore(&inst->inst_lock, flags); + + for (i = 0; i < VENC_INPUT_BUFFERS; ++i) { + mpair = kzalloc(sizeof(*mpair), GFP_KERNEL); + enc_mregion = kzalloc(sizeof(*enc_mregion), GFP_KERNEL); + mdp_mregion = kzalloc(sizeof(*enc_mregion), GFP_KERNEL); + enc_mregion->size = ALIGN(inst->input_buf_size, SZ_4K); + + rc = wfd_allocate_ion_buffer(wfd_dev->ion_client, + wfd_dev->secure_device, enc_mregion); + if (rc) { + WFD_MSG_ERR("Failed to allocate input memory." + " This error causes memory leak!!!\n"); + goto alloc_fail; + } + + WFD_MSG_DBG("NOTE: enc paddr = %p, kvaddr = %p\n", + enc_mregion->paddr, + enc_mregion->kvaddr); + + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + SET_INPUT_BUFFER, (void *)enc_mregion); + + /* map the buffer from encoder to mdp */ + mdp_mregion->kvaddr = enc_mregion->kvaddr; + mdp_mregion->size = enc_mregion->size; + mdp_mregion->offset = enc_mregion->offset; + mdp_mregion->fd = enc_mregion->fd; + mdp_mregion->cookie = 0; + mdp_mregion->ion_handle = enc_mregion->ion_handle; + + rc = ion_map_iommu(wfd_dev->ion_client, mdp_mregion->ion_handle, + DISPLAY_DOMAIN, GEN_POOL, SZ_4K, + 0, (unsigned long *)&mdp_mregion->paddr, + (unsigned long *)&mdp_mregion->size, 0, 0); + if (rc) { + WFD_MSG_ERR("Failed to map to mdp\n"); + mdp_mregion->kvaddr = NULL; + mdp_mregion->paddr = NULL; + mdp_mregion->ion_handle = NULL; + goto alloc_fail; + } + + mdp_buf.inst = inst->mdp_inst; + mdp_buf.cookie = enc_mregion; + mdp_buf.kvaddr = (u32) mdp_mregion->kvaddr; + mdp_buf.paddr = (u32) mdp_mregion->paddr; + + WFD_MSG_DBG("NOTE: mdp paddr = %p, kvaddr = %p\n", + mdp_mregion->paddr, + mdp_mregion->kvaddr); + + INIT_LIST_HEAD(&mpair->list); + mpair->enc = enc_mregion; + mpair->mdp = mdp_mregion; + list_add_tail(&mpair->list, &inst->input_mem_list); + + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, + MDP_Q_BUFFER, (void *)&mdp_buf); + if (rc) { + WFD_MSG_ERR("Unable to queue the" + " buffer to mdp\n"); + break; + } else { + wfd_stats_update(&inst->stats, + WFD_STAT_EVENT_MDP_QUEUE); + } + } + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + ALLOC_RECON_BUFFERS, NULL); + if (rc) { + WFD_MSG_ERR("Failed to allocate recon buffers\n"); + goto alloc_fail; + } +alloc_fail: + return rc; +} +void wfd_free_input_buffers(struct wfd_device *wfd_dev, + struct wfd_inst *inst) +{ + struct list_head *ptr, *next; + struct mem_region_pair *mpair; + unsigned long flags; + int rc = 0; + spin_lock_irqsave(&inst->inst_lock, flags); + if (!inst->input_bufs_allocated) { + spin_unlock_irqrestore(&inst->inst_lock, flags); + return; + } + inst->input_bufs_allocated = false; + spin_unlock_irqrestore(&inst->inst_lock, flags); + if (!list_empty(&inst->input_mem_list)) { + list_for_each_safe(ptr, next, + &inst->input_mem_list) { + mpair = list_entry(ptr, struct mem_region_pair, + list); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, + core, ioctl, FREE_INPUT_BUFFER, + (void *)mpair->enc); + + if (rc) + WFD_MSG_ERR("Failed to free buffers " + "from encoder\n"); + + if (mpair->mdp->paddr) + ion_unmap_iommu(wfd_dev->ion_client, + mpair->mdp->ion_handle, + DISPLAY_DOMAIN, GEN_POOL); + + if (mpair->enc->paddr) + ion_unmap_iommu(wfd_dev->ion_client, + mpair->enc->ion_handle, + VIDEO_DOMAIN, VIDEO_MAIN_POOL); + + wfd_free_ion_buffer(wfd_dev->ion_client, mpair->enc); + list_del(&mpair->list); + kfree(mpair->enc); + kfree(mpair->mdp); + kfree(mpair); + } + } + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + FREE_RECON_BUFFERS, NULL); + if (rc) + WFD_MSG_ERR("Failed to free recon buffers\n"); +} + +struct mem_info *wfd_get_mem_info(struct wfd_inst *inst, + unsigned long userptr) +{ + struct mem_info_entry *temp; + struct mem_info *ret = NULL; + unsigned long flags; + spin_lock_irqsave(&inst->inst_lock, flags); + if (!list_empty(&inst->minfo_list)) { + list_for_each_entry(temp, &inst->minfo_list, list) { + if (temp && temp->userptr == userptr) { + ret = &temp->minfo; + break; + } + } + } + spin_unlock_irqrestore(&inst->inst_lock, flags); + return ret; +} +void wfd_put_mem_info(struct wfd_inst *inst, + struct mem_info *minfo) +{ + struct list_head *ptr, *next; + struct mem_info_entry *temp; + unsigned long flags; + spin_lock_irqsave(&inst->inst_lock, flags); + if (!list_empty(&inst->minfo_list)) { + list_for_each_safe(ptr, next, + &inst->minfo_list) { + temp = list_entry(ptr, struct mem_info_entry, + list); + if (temp && (&temp->minfo == minfo)) { + list_del(&temp->list); + kfree(temp); + } + } + } + spin_unlock_irqrestore(&inst->inst_lock, flags); +} +static void wfd_unregister_out_buf(struct wfd_inst *inst, + struct mem_info *minfo) +{ + if (!minfo || !inst) { + WFD_MSG_ERR("Invalid arguments\n"); + return; + } + wfd_put_mem_info(inst, minfo); +} +int wfd_vidbuf_buf_init(struct vb2_buffer *vb) +{ + int rc = 0; + struct vb2_queue *q = vb->vb2_queue; + struct file *priv_data = (struct file *)(q->drv_priv); + struct wfd_inst *inst = (struct wfd_inst *)priv_data->private_data; + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(priv_data); + struct mem_info *minfo = vb2_plane_cookie(vb, 0); + struct mem_region mregion; + mregion.fd = minfo->fd; + mregion.offset = minfo->offset; + mregion.cookie = (u32)vb; + /*TODO: should be fixed in kernel 3.2*/ + mregion.size = inst->out_buf_size; + + if (inst && !inst->vid_bufq.streaming) { + rc = wfd_allocate_input_buffers(wfd_dev, inst); + if (rc) { + WFD_MSG_ERR("Failed to allocate input buffers\n"); + goto free_input_bufs; + } + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + SET_OUTPUT_BUFFER, (void *)&mregion); + if (rc) { + WFD_MSG_ERR("Failed to set output buffer\n"); + goto free_input_bufs; + } + } + return rc; +free_input_bufs: + wfd_free_input_buffers(wfd_dev, inst); + return rc; +} + +int wfd_vidbuf_buf_prepare(struct vb2_buffer *vb) +{ + return 0; +} + +int wfd_vidbuf_buf_finish(struct vb2_buffer *vb) +{ + return 0; +} + +void wfd_vidbuf_buf_cleanup(struct vb2_buffer *vb) +{ + int rc = 0; + struct vb2_queue *q = vb->vb2_queue; + struct file *priv_data = (struct file *)(q->drv_priv); + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(priv_data); + struct wfd_inst *inst = (struct wfd_inst *)priv_data->private_data; + struct mem_info *minfo = vb2_plane_cookie(vb, 0); + struct mem_region mregion; + + if (minfo == NULL) { + WFD_MSG_ERR("not freeing buffers since allocation failed"); + return; + } + + mregion.fd = minfo->fd; + mregion.offset = minfo->offset; + mregion.cookie = (u32)vb; + mregion.size = inst->out_buf_size; + + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + FREE_OUTPUT_BUFFER, (void *)&mregion); + if (rc) + WFD_MSG_ERR("Failed to free output buffer\n"); + wfd_unregister_out_buf(inst, minfo); +} + +static int mdp_output_thread(void *data) +{ + int rc = 0; + struct file *filp = (struct file *)data; + struct wfd_inst *inst = filp->private_data; + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(filp); + struct mdp_buf_info obuf_mdp = {inst->mdp_inst, 0, 0, 0}; + struct mem_region *mregion; + struct vsg_buf_info ibuf_vsg; + while (!kthread_should_stop()) { + WFD_MSG_DBG("waiting for mdp output\n"); + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, + core, ioctl, MDP_DQ_BUFFER, (void *)&obuf_mdp); + + if (rc) { + if (rc != -ENOBUFS) + WFD_MSG_ERR("MDP reported err %d\n", rc); + + WFD_MSG_ERR("Streamoff called\n"); + break; + } else { + wfd_stats_update(&inst->stats, + WFD_STAT_EVENT_MDP_DEQUEUE); + } + + mregion = obuf_mdp.cookie; + if (!mregion) { + WFD_MSG_ERR("mdp cookie is null\n"); + rc = -EINVAL; + break; + } + + ibuf_vsg.mdp_buf_info = obuf_mdp; + ibuf_vsg.mdp_buf_info.inst = inst->mdp_inst; + ibuf_vsg.mdp_buf_info.cookie = mregion; + ibuf_vsg.mdp_buf_info.kvaddr = (u32) mregion->kvaddr; + ibuf_vsg.mdp_buf_info.paddr = + (u32)wfd_enc_addr_to_mdp_addr(inst, + (unsigned long)mregion->paddr); + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, + core, ioctl, VSG_Q_BUFFER, (void *)&ibuf_vsg); + + if (rc) { + WFD_MSG_ERR("Failed to queue frame to vsg\n"); + break; + } else { + wfd_stats_update(&inst->stats, + WFD_STAT_EVENT_VSG_QUEUE); + } + } + WFD_MSG_DBG("Exiting the thread\n"); + return rc; +} + +int wfd_vidbuf_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct file *priv_data = (struct file *)(q->drv_priv); + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(priv_data); + struct wfd_inst *inst = (struct wfd_inst *)priv_data->private_data; + int rc = 0; + + WFD_MSG_ERR("Stream on called\n"); + WFD_MSG_DBG("enc start\n"); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + ENCODE_START, (void *)inst->venc_inst); + if (rc) { + WFD_MSG_ERR("Failed to start encoder\n"); + goto subdev_start_fail; + } + + WFD_MSG_DBG("vsg start\n"); + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, ioctl, + VSG_START, NULL); + if (rc) { + WFD_MSG_ERR("Failed to start vsg\n"); + goto subdev_start_fail; + } + + inst->mdp_task = kthread_run(mdp_output_thread, priv_data, + "mdp_output_thread"); + if (IS_ERR(inst->mdp_task)) { + rc = PTR_ERR(inst->mdp_task); + goto subdev_start_fail; + } + WFD_MSG_DBG("mdp start\n"); + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, + MDP_START, (void *)inst->mdp_inst); + if (rc) + WFD_MSG_ERR("Failed to start MDP\n"); +subdev_start_fail: + return rc; +} + +int wfd_vidbuf_stop_streaming(struct vb2_queue *q) +{ + struct file *priv_data = (struct file *)(q->drv_priv); + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(priv_data); + struct wfd_inst *inst = (struct wfd_inst *)priv_data->private_data; + int rc = 0; + WFD_MSG_DBG("mdp stop\n"); + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, + MDP_STOP, (void *)inst->mdp_inst); + if (rc) + WFD_MSG_ERR("Failed to stop MDP\n"); + + WFD_MSG_DBG("vsg stop\n"); + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, ioctl, + VSG_STOP, NULL); + if (rc) + WFD_MSG_ERR("Failed to stop VSG\n"); + + kthread_stop(inst->mdp_task); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + ENCODE_FLUSH, (void *)inst->venc_inst); + if (rc) + WFD_MSG_ERR("Failed to flush encoder\n"); + WFD_MSG_DBG("enc stop\n"); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + ENCODE_STOP, (void *)inst->venc_inst); + if (rc) + WFD_MSG_ERR("Failed to stop encoder\n"); + + return rc; +} + +void wfd_vidbuf_buf_queue(struct vb2_buffer *vb) +{ + int rc = 0; + struct vb2_queue *q = vb->vb2_queue; + struct file *priv_data = (struct file *)(q->drv_priv); + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(priv_data); + struct wfd_inst *inst = (struct wfd_inst *)priv_data->private_data; + struct mem_region mregion; + struct mem_info *minfo = vb2_plane_cookie(vb, 0); + mregion.fd = minfo->fd; + mregion.offset = minfo->offset; + mregion.cookie = (u32)vb; + mregion.size = inst->out_buf_size; + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + FILL_OUTPUT_BUFFER, (void *)&mregion); + if (rc) { + WFD_MSG_ERR("Failed to fill output buffer\n"); + } +} + +static struct vb2_ops wfd_vidbuf_ops = { + .queue_setup = wfd_vidbuf_queue_setup, + + .wait_prepare = wfd_vidbuf_wait_prepare, + .wait_finish = wfd_vidbuf_wait_finish, + + .buf_init = wfd_vidbuf_buf_init, + .buf_prepare = wfd_vidbuf_buf_prepare, + .buf_finish = wfd_vidbuf_buf_finish, + .buf_cleanup = wfd_vidbuf_buf_cleanup, + + .start_streaming = wfd_vidbuf_start_streaming, + .stop_streaming = wfd_vidbuf_stop_streaming, + + .buf_queue = wfd_vidbuf_buf_queue, +}; + +static const struct v4l2_subdev_core_ops mdp_subdev_core_ops = { + .init = mdp_init, + .ioctl = mdp_ioctl, +}; + +static const struct v4l2_subdev_ops mdp_subdev_ops = { + .core = &mdp_subdev_core_ops, +}; + +static const struct v4l2_subdev_core_ops enc_subdev_core_ops = { + .init = venc_init, + .load_fw = venc_load_fw, + .ioctl = venc_ioctl, +}; + +static const struct v4l2_subdev_ops enc_subdev_ops = { + .core = &enc_subdev_core_ops, +}; + +static const struct v4l2_subdev_core_ops vsg_subdev_core_ops = { + .init = vsg_init, + .ioctl = vsg_ioctl, +}; + +static const struct v4l2_subdev_ops vsg_subdev_ops = { + .core = &vsg_subdev_core_ops, +}; + +static int wfdioc_querycap(struct file *filp, void *fh, + struct v4l2_capability *cap) { + WFD_MSG_DBG("wfdioc_querycap: E\n"); + memset(cap, 0, sizeof(struct v4l2_capability)); + strlcpy(cap->driver, "wifi-display", sizeof(cap->driver)); + strlcpy(cap->card, "msm", sizeof(cap->card)); + cap->version = WFD_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + WFD_MSG_DBG("wfdioc_querycap: X\n"); + return 0; +} +static int wfdioc_g_fmt(struct file *filp, void *fh, + struct v4l2_format *fmt) +{ + struct wfd_inst *inst = filp->private_data; + unsigned long flags; + if (!fmt) { + WFD_MSG_ERR("Invalid argument\n"); + return -EINVAL; + } + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + WFD_MSG_ERR("Only V4L2_BUF_TYPE_VIDEO_CAPTURE is supported\n"); + return -EINVAL; + } + spin_lock_irqsave(&inst->inst_lock, flags); + fmt->fmt.pix.width = inst->width; + fmt->fmt.pix.height = inst->height; + fmt->fmt.pix.pixelformat = inst->pixelformat; + fmt->fmt.pix.sizeimage = inst->out_buf_size; + fmt->fmt.pix.priv = 0; + spin_unlock_irqrestore(&inst->inst_lock, flags); + return 0; +} + +static int wfdioc_s_fmt(struct file *filp, void *fh, + struct v4l2_format *fmt) +{ + int rc = 0; + struct wfd_inst *inst = filp->private_data; + struct wfd_device *wfd_dev = video_drvdata(filp); + struct mdp_prop prop; + unsigned long flags; + struct bufreq breq; + if (!fmt) { + WFD_MSG_ERR("Invalid argument\n"); + return -EINVAL; + } + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_H264) { + WFD_MSG_ERR("Only V4L2_BUF_TYPE_VIDEO_CAPTURE and " + "V4L2_PIX_FMT_H264 are supported\n"); + return -EINVAL; + } + + if (fmt->fmt.pix.width % 16) { + WFD_MSG_ERR("Only 16 byte aligned widths are supported\n"); + return -ENOTSUPP; + } + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, SET_FORMAT, + (void *)fmt); + if (rc) { + WFD_MSG_ERR("Failed to set format on encoder, rc = %d\n", rc); + return rc; + } + breq.count = VENC_INPUT_BUFFERS; + breq.height = fmt->fmt.pix.height; + breq.width = fmt->fmt.pix.width; + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + SET_BUFFER_REQ, (void *)&breq); + if (rc) { + WFD_MSG_ERR("Failed to set buffer reqs on encoder\n"); + return rc; + } + spin_lock_irqsave(&inst->inst_lock, flags); + inst->input_buf_size = breq.size; + inst->out_buf_size = fmt->fmt.pix.sizeimage; + prop.height = inst->height = fmt->fmt.pix.height; + prop.width = inst->width = fmt->fmt.pix.width; + prop.inst = inst->mdp_inst; + spin_unlock_irqrestore(&inst->inst_lock, flags); + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, MDP_SET_PROP, + (void *)&prop); + if (rc) + WFD_MSG_ERR("Failed to set height/width property on mdp\n"); + return rc; +} +static int wfdioc_reqbufs(struct file *filp, void *fh, + struct v4l2_requestbuffers *b) +{ + struct wfd_inst *inst = filp->private_data; + struct wfd_device *wfd_dev = video_drvdata(filp); + unsigned long flags; + int rc = 0; + + if (b->type != V4L2_CAP_VIDEO_CAPTURE || + b->memory != V4L2_MEMORY_USERPTR) { + WFD_MSG_ERR("Only V4L2_CAP_VIDEO_CAPTURE and " + "V4L2_MEMORY_USERPTR are supported\n"); + return -EINVAL; + } + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + GET_BUFFER_REQ, (void *)b); + if (rc) { + WFD_MSG_ERR("Failed to get buf reqs from encoder\n"); + return rc; + } + spin_lock_irqsave(&inst->inst_lock, flags); + inst->buf_count = b->count; + spin_unlock_irqrestore(&inst->inst_lock, flags); + rc = vb2_reqbufs(&inst->vid_bufq, b); + return rc; +} +static int wfd_register_out_buf(struct wfd_inst *inst, + struct v4l2_buffer *b) +{ + struct mem_info_entry *minfo_entry; + struct mem_info *minfo; + unsigned long flags; + if (!b || !inst || !b->reserved) { + WFD_MSG_ERR("Invalid arguments\n"); + return -EINVAL; + } + minfo = wfd_get_mem_info(inst, b->m.userptr); + if (!minfo) { + minfo_entry = kzalloc(sizeof(struct mem_info_entry), + GFP_KERNEL); + if (copy_from_user(&minfo_entry->minfo, (void *)b->reserved, + sizeof(struct mem_info))) { + WFD_MSG_ERR(" copy_from_user failed. Populate" + " v4l2_buffer->reserved with meminfo\n"); + return -EINVAL; + } + minfo_entry->userptr = b->m.userptr; + spin_lock_irqsave(&inst->inst_lock, flags); + list_add_tail(&minfo_entry->list, &inst->minfo_list); + spin_unlock_irqrestore(&inst->inst_lock, flags); + } else + WFD_MSG_INFO("Buffer already registered\n"); + + return 0; +} +static int wfdioc_qbuf(struct file *filp, void *fh, + struct v4l2_buffer *b) +{ + int rc = 0; + struct wfd_inst *inst = filp->private_data; + if (!inst || !b || + (b->index < 0 || b->index >= inst->buf_count)) { + WFD_MSG_ERR("Invalid input parameters to QBUF IOCTL\n"); + return -EINVAL; + } + rc = wfd_register_out_buf(inst, b); + if (rc) { + WFD_MSG_ERR("Failed to register buffer\n"); + return rc; + } + + rc = vb2_qbuf(&inst->vid_bufq, b); + if (rc) + WFD_MSG_ERR("Failed to queue buffer\n"); + else + wfd_stats_update(&inst->stats, WFD_STAT_EVENT_CLIENT_QUEUE); + + return rc; +} + +static int wfdioc_streamon(struct file *filp, void *fh, + enum v4l2_buf_type i) +{ + int rc = 0; + struct wfd_inst *inst = filp->private_data; + unsigned long flags; + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + WFD_MSG_ERR("stream on for buffer type = %d is not " + "supported.\n", i); + return -EINVAL; + } + + spin_lock_irqsave(&inst->inst_lock, flags); + inst->streamoff = false; + spin_unlock_irqrestore(&inst->inst_lock, flags); + + rc = vb2_streamon(&inst->vid_bufq, i); + if (rc) { + WFD_MSG_ERR("videobuf_streamon failed with err = %d\n", rc); + goto vidbuf_streamon_failed; + } + return rc; + +vidbuf_streamon_failed: + vb2_streamoff(&inst->vid_bufq, i); + return rc; +} +static int wfdioc_streamoff(struct file *filp, void *fh, + enum v4l2_buf_type i) +{ + struct wfd_inst *inst = filp->private_data; + unsigned long flags; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + WFD_MSG_ERR("stream off for buffer type = %d is not " + "supported.\n", i); + return -EINVAL; + } + + spin_lock_irqsave(&inst->inst_lock, flags); + if (inst->streamoff) { + WFD_MSG_ERR("Module is already in streamoff state\n"); + spin_unlock_irqrestore(&inst->inst_lock, flags); + return -EINVAL; + } + inst->streamoff = true; + spin_unlock_irqrestore(&inst->inst_lock, flags); + WFD_MSG_DBG("Calling videobuf_streamoff\n"); + vb2_streamoff(&inst->vid_bufq, i); + return 0; +} +static int wfdioc_dqbuf(struct file *filp, void *fh, + struct v4l2_buffer *b) +{ + struct wfd_inst *inst = filp->private_data; + int rc; + + WFD_MSG_INFO("Waiting to dequeue buffer\n"); + rc = vb2_dqbuf(&inst->vid_bufq, b, 0); + + if (rc) + WFD_MSG_ERR("Failed to dequeue buffer\n"); + else + wfd_stats_update(&inst->stats, WFD_STAT_EVENT_CLIENT_DEQUEUE); + + return rc; +} +static int wfdioc_g_ctrl(struct file *filp, void *fh, + struct v4l2_control *a) +{ + int rc = 0; + struct wfd_device *wfd_dev = video_drvdata(filp); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, + ioctl, GET_PROP, a); + if (rc) + WFD_MSG_ERR("Failed to get encoder property\n"); + return rc; +} +static int wfdioc_s_ctrl(struct file *filp, void *fh, + struct v4l2_control *a) +{ + int rc = 0; + struct wfd_device *wfd_dev = video_drvdata(filp); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, + ioctl, SET_PROP, a); + if (rc) + WFD_MSG_ERR("Failed to set encoder property\n"); + return rc; +} + +static int wfdioc_g_parm(struct file *filp, void *fh, + struct v4l2_streamparm *a) +{ + int rc = 0; + struct wfd_device *wfd_dev = video_drvdata(filp); + struct wfd_inst *inst = filp->private_data; + int64_t frame_interval = 0, + max_frame_interval = 0; /* both in nsecs*/ + struct v4l2_qcom_frameskip frameskip, *usr_frameskip; + + usr_frameskip = (struct v4l2_qcom_frameskip *) + a->parm.capture.extendedmode; + + if (!usr_frameskip) { + rc = -EINVAL; + goto get_parm_fail; + } + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_GET_FRAME_INTERVAL, &frame_interval); + + if (rc < 0) + goto get_parm_fail; + + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_GET_MAX_FRAME_INTERVAL, &max_frame_interval); + + if (rc < 0) + goto get_parm_fail; + + frameskip = (struct v4l2_qcom_frameskip) { + .maxframeinterval = max_frame_interval, + }; + + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->parm.capture = (struct v4l2_captureparm) { + .capability = V4L2_CAP_TIMEPERFRAME, + .capturemode = 0, + .timeperframe = (struct v4l2_fract) { + .numerator = frame_interval, + .denominator = NSEC_PER_SEC, + }, + .readbuffers = inst->buf_count, + .extendedmode = (__u32)usr_frameskip, + .reserved = {0} + }; + + rc = copy_to_user((void *)a->parm.capture.extendedmode, + &frameskip, sizeof(frameskip)); + if (rc < 0) + goto get_parm_fail; + +get_parm_fail: + return rc; +} + +static int wfdioc_s_parm(struct file *filp, void *fh, + struct v4l2_streamparm *a) +{ + int rc = 0; + struct wfd_device *wfd_dev = video_drvdata(filp); + struct wfd_inst *inst = filp->private_data; + struct v4l2_qcom_frameskip frameskip; + int64_t frame_interval, max_frame_interval; + void *extendedmode = NULL; + enum vsg_modes mode = VSG_MODE_VFR; + + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + rc = -ENOTSUPP; + goto set_parm_fail; + } + + if (a->parm.capture.readbuffers == 0 || + a->parm.capture.readbuffers == inst->buf_count) { + a->parm.capture.readbuffers = inst->buf_count; + } else { + rc = -EINVAL; + goto set_parm_fail; + } + + extendedmode = (void *)a->parm.capture.extendedmode; + if (a->parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { + if (a->parm.capture.timeperframe.denominator == 0) { + rc = -EINVAL; + goto set_parm_fail; + } + frame_interval = + a->parm.capture.timeperframe.numerator * NSEC_PER_SEC / + a->parm.capture.timeperframe.denominator; + + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_SET_FRAME_INTERVAL, + &frame_interval); + + if (rc) + goto set_parm_fail; + + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, + ioctl, SET_FRAMERATE, + &a->parm.capture.timeperframe); + + if (rc) + goto set_parm_fail; + } + + if (a->parm.capture.capability & V4L2_CAP_QCOM_FRAMESKIP && + extendedmode) { + rc = copy_from_user(&frameskip, + extendedmode, sizeof(frameskip)); + + if (rc) + goto set_parm_fail; + + max_frame_interval = (int64_t)frameskip.maxframeinterval; + mode = VSG_MODE_VFR; + + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_SET_MAX_FRAME_INTERVAL, + &max_frame_interval); + + if (rc) + goto set_parm_fail; + + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_SET_MODE, &mode); + + if (rc) + goto set_parm_fail; + } else { + mode = VSG_MODE_CFR; + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_SET_MODE, &mode); + + if (rc) + goto set_parm_fail; + } + +set_parm_fail: + return rc; +} + +static const struct v4l2_ioctl_ops g_wfd_ioctl_ops = { + .vidioc_querycap = wfdioc_querycap, + .vidioc_s_fmt_vid_cap = wfdioc_s_fmt, + .vidioc_g_fmt_vid_cap = wfdioc_g_fmt, + .vidioc_reqbufs = wfdioc_reqbufs, + .vidioc_qbuf = wfdioc_qbuf, + .vidioc_streamon = wfdioc_streamon, + .vidioc_streamoff = wfdioc_streamoff, + .vidioc_dqbuf = wfdioc_dqbuf, + .vidioc_g_ctrl = wfdioc_g_ctrl, + .vidioc_s_ctrl = wfdioc_s_ctrl, + .vidioc_g_parm = wfdioc_g_parm, + .vidioc_s_parm = wfdioc_s_parm, +}; +static int wfd_set_default_properties(struct file *filp) +{ + unsigned long flags; + struct v4l2_format fmt; + struct v4l2_control ctrl; + struct wfd_inst *inst = filp->private_data; + if (!inst) { + WFD_MSG_ERR("Invalid argument\n"); + return -EINVAL; + } + spin_lock_irqsave(&inst->inst_lock, flags); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.height = inst->height = DEFAULT_WFD_HEIGHT; + fmt.fmt.pix.width = inst->width = DEFAULT_WFD_WIDTH; + fmt.fmt.pix.pixelformat = inst->pixelformat + = V4L2_PIX_FMT_H264; + spin_unlock_irqrestore(&inst->inst_lock, flags); + wfdioc_s_fmt(filp, filp->private_data, &fmt); + + ctrl.id = V4L2_CID_MPEG_VIDEO_HEADER_MODE; + ctrl.value = V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_I_FRAME; + wfdioc_s_ctrl(filp, filp->private_data, &ctrl); + return 0; +} +static void venc_op_buffer_done(void *cookie, u32 status, + struct vb2_buffer *buf) +{ + WFD_MSG_DBG("yay!! got callback\n"); + vb2_buffer_done(buf, VB2_BUF_STATE_DONE); +} + +static void venc_ip_buffer_done(void *cookie, u32 status, + struct mem_region *mregion) +{ + struct file *filp = cookie; + struct wfd_inst *inst = filp->private_data; + struct vsg_buf_info buf; + struct mdp_buf_info mdp_buf = {0}; + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(filp); + int rc = 0; + WFD_MSG_DBG("yay!! got ip callback\n"); + mdp_buf.inst = inst->mdp_inst; + mdp_buf.cookie = mregion; + mdp_buf.kvaddr = (u32) mregion->kvaddr; + mdp_buf.paddr = + (u32)wfd_enc_addr_to_mdp_addr(inst, + (unsigned long)mregion->paddr); + buf.mdp_buf_info = mdp_buf; + + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, + ioctl, VSG_RETURN_IP_BUFFER, (void *)&buf); + if (rc) + WFD_MSG_ERR("Failed to return buffer to vsg\n"); + else + wfd_stats_update(&inst->stats, WFD_STAT_EVENT_ENC_DEQUEUE); + +} + +static int vsg_release_input_frame(void *cookie, struct vsg_buf_info *buf) +{ + struct file *filp = cookie; + struct wfd_inst *inst = filp->private_data; + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(filp); + int rc = 0; + + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, + ioctl, MDP_Q_BUFFER, buf); + if (rc) + WFD_MSG_ERR("Failed to Q buffer to mdp\n"); + else { + wfd_stats_update(&inst->stats, WFD_STAT_EVENT_MDP_QUEUE); + wfd_stats_update(&inst->stats, WFD_STAT_EVENT_VSG_DEQUEUE); + } + + return rc; +} + +static int vsg_encode_frame(void *cookie, struct vsg_buf_info *buf) +{ + struct file *filp = cookie; + struct wfd_inst *inst = filp->private_data; + struct wfd_device *wfd_dev = + (struct wfd_device *)video_drvdata(filp); + struct venc_buf_info venc_buf; + int rc = 0; + + if (!buf) + return -EINVAL; + + venc_buf = (struct venc_buf_info){ + .timestamp = timespec_to_ns(&buf->time), + .mregion = buf->mdp_buf_info.cookie + }; + + wfd_flush_ion_buffer(wfd_dev->ion_client, venc_buf.mregion); + + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + ENCODE_FRAME, &venc_buf); + + if (rc) + WFD_MSG_ERR("Encode failed\n"); + else + wfd_stats_update(&inst->stats, WFD_STAT_EVENT_ENC_QUEUE); + + return rc; +} + +void *wfd_vb2_mem_ops_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + return wfd_get_mem_info(alloc_ctx, vaddr); +} + +void wfd_vb2_mem_ops_put_userptr(void *buf_priv) +{ + /*TODO: Free the list*/ +} + +void *wfd_vb2_mem_ops_cookie(void *buf_priv) +{ + return buf_priv; +} + + +static struct vb2_mem_ops wfd_vb2_mem_ops = { + .get_userptr = wfd_vb2_mem_ops_get_userptr, + .put_userptr = wfd_vb2_mem_ops_put_userptr, + .cookie = wfd_vb2_mem_ops_cookie, +}; + +int wfd_initialize_vb2_queue(struct vb2_queue *q, void *priv) +{ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_USERPTR; + q->ops = &wfd_vidbuf_ops; + q->mem_ops = &wfd_vb2_mem_ops; + q->drv_priv = priv; + return vb2_queue_init(q); +} + +static int wfd_open(struct file *filp) +{ + int rc = 0; + struct wfd_inst *inst = NULL; + struct wfd_device *wfd_dev = NULL; + struct venc_msg_ops enc_mops; + struct vsg_msg_ops vsg_mops; + + WFD_MSG_DBG("wfd_open: E\n"); + wfd_dev = video_drvdata(filp); + + mutex_lock(&wfd_dev->dev_lock); + if (wfd_dev->in_use) { + WFD_MSG_ERR("Device already in use.\n"); + rc = -EBUSY; + mutex_unlock(&wfd_dev->dev_lock); + goto err_dev_busy; + } + + wfd_dev->in_use = true; + mutex_unlock(&wfd_dev->dev_lock); + + inst = kzalloc(sizeof(struct wfd_inst), GFP_KERNEL); + if (!inst || !wfd_dev) { + WFD_MSG_ERR("Could not allocate memory for " + "wfd instance\n"); + rc = -ENOMEM; + goto err_mdp_open; + } + filp->private_data = inst; + spin_lock_init(&inst->inst_lock); + INIT_LIST_HEAD(&inst->input_mem_list); + INIT_LIST_HEAD(&inst->minfo_list); + + wfd_stats_init(&inst->stats, MINOR(filp->f_dentry->d_inode->i_rdev)); + + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, MDP_OPEN, + (void *)&inst->mdp_inst); + if (rc) { + WFD_MSG_ERR("Failed to open mdp subdevice: %d\n", rc); + goto err_mdp_open; + } + + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, load_fw); + if (rc) { + WFD_MSG_ERR("Failed to load video encoder firmware: %d\n", rc); + goto err_venc; + } + enc_mops.op_buffer_done = venc_op_buffer_done; + enc_mops.ip_buffer_done = venc_ip_buffer_done; + enc_mops.cbdata = filp; + enc_mops.secure = wfd_dev->secure_device; + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, OPEN, + (void *)&enc_mops); + if (rc || !enc_mops.cookie) { + WFD_MSG_ERR("Failed to open encoder subdevice: %d\n", rc); + goto err_venc; + } + inst->venc_inst = enc_mops.cookie; + + vsg_mops.encode_frame = vsg_encode_frame; + vsg_mops.release_input_frame = vsg_release_input_frame; + vsg_mops.cbdata = filp; + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, ioctl, VSG_OPEN, + &vsg_mops); + if (rc) { + WFD_MSG_ERR("Failed to open vsg subdevice: %d\n", rc); + goto err_vsg_open; + } + + wfd_initialize_vb2_queue(&inst->vid_bufq, filp); + wfd_set_default_properties(filp); + WFD_MSG_DBG("wfd_open: X\n"); + return rc; + +err_vsg_open: + v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, CLOSE, NULL); +err_venc: + v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, + MDP_CLOSE, (void *)inst->mdp_inst); +err_mdp_open: + kfree(inst); +err_dev_busy: + return rc; +} + +static int wfd_close(struct file *filp) +{ + struct wfd_inst *inst; + struct wfd_device *wfd_dev; + int rc = 0; + wfd_dev = video_drvdata(filp); + WFD_MSG_DBG("wfd_close: E\n"); + inst = filp->private_data; + if (inst) { + wfdioc_streamoff(filp, NULL, V4L2_BUF_TYPE_VIDEO_CAPTURE); + rc = v4l2_subdev_call(&wfd_dev->mdp_sdev, core, ioctl, + MDP_CLOSE, (void *)inst->mdp_inst); + if (rc) + WFD_MSG_ERR("Failed to CLOSE mdp subdevice: %d\n", rc); + + vb2_queue_release(&inst->vid_bufq); + wfd_free_input_buffers(wfd_dev, inst); + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, ioctl, + CLOSE, (void *)inst->venc_inst); + + if (rc) + WFD_MSG_ERR("Failed to CLOSE enc subdev: %d\n", rc); + + rc = v4l2_subdev_call(&wfd_dev->vsg_sdev, core, ioctl, + VSG_CLOSE, NULL); + + if (rc) + WFD_MSG_ERR("Failed to CLOSE vsg subdev: %d\n", rc); + + wfd_stats_deinit(&inst->stats); + kfree(inst); + } + + mutex_lock(&wfd_dev->dev_lock); + wfd_dev->in_use = false; + mutex_unlock(&wfd_dev->dev_lock); + + WFD_MSG_DBG("wfd_close: X\n"); + return 0; +} +static const struct v4l2_file_operations g_wfd_fops = { + .owner = THIS_MODULE, + .open = wfd_open, + .release = wfd_close, + .ioctl = video_ioctl2 +}; +void release_video_device(struct video_device *pvdev) +{ + +} + +static int wfd_dev_setup(struct wfd_device *wfd_dev, int dev_num, + struct platform_device *pdev) +{ + int rc = 0; + rc = v4l2_device_register(&pdev->dev, &wfd_dev->v4l2_dev); + if (rc) { + WFD_MSG_ERR("Failed to register the video device\n"); + goto err_v4l2_registration; + } + wfd_dev->pvdev = video_device_alloc(); + if (!wfd_dev->pvdev) { + WFD_MSG_ERR("Failed to allocate video device\n"); + goto err_video_device_alloc; + } + + wfd_dev->pvdev->release = release_video_device; + wfd_dev->pvdev->fops = &g_wfd_fops; + wfd_dev->pvdev->ioctl_ops = &g_wfd_ioctl_ops; + + rc = video_register_device(wfd_dev->pvdev, VFL_TYPE_GRABBER, + dev_num); + if (rc) { + WFD_MSG_ERR("Failed to register the device\n"); + goto err_video_register_device; + } + video_set_drvdata(wfd_dev->pvdev, wfd_dev); + + v4l2_subdev_init(&wfd_dev->mdp_sdev, &mdp_subdev_ops); + strncpy(wfd_dev->mdp_sdev.name, "wfd-mdp", V4L2_SUBDEV_NAME_SIZE); + rc = v4l2_device_register_subdev(&wfd_dev->v4l2_dev, + &wfd_dev->mdp_sdev); + if (rc) { + WFD_MSG_ERR("Failed to register mdp subdevice: %d\n", rc); + goto err_mdp_register_subdev; + } + + v4l2_subdev_init(&wfd_dev->enc_sdev, &enc_subdev_ops); + strncpy(wfd_dev->enc_sdev.name, "wfd-venc", V4L2_SUBDEV_NAME_SIZE); + rc = v4l2_device_register_subdev(&wfd_dev->v4l2_dev, + &wfd_dev->enc_sdev); + if (rc) { + WFD_MSG_ERR("Failed to register encoder subdevice: %d\n", rc); + goto err_venc_register_subdev; + } + rc = v4l2_subdev_call(&wfd_dev->enc_sdev, core, init, 0); + if (rc) { + WFD_MSG_ERR("Failed to initiate encoder device %d\n", rc); + goto err_venc_init; + } + + v4l2_subdev_init(&wfd_dev->vsg_sdev, &vsg_subdev_ops); + strncpy(wfd_dev->vsg_sdev.name, "wfd-vsg", V4L2_SUBDEV_NAME_SIZE); + rc = v4l2_device_register_subdev(&wfd_dev->v4l2_dev, + &wfd_dev->vsg_sdev); + if (rc) { + WFD_MSG_ERR("Failed to register vsg subdevice: %d\n", rc); + goto err_venc_init; + } + + WFD_MSG_DBG("__wfd_probe: X\n"); + return rc; + +err_venc_init: + v4l2_device_unregister_subdev(&wfd_dev->enc_sdev); +err_venc_register_subdev: + v4l2_device_unregister_subdev(&wfd_dev->mdp_sdev); +err_mdp_register_subdev: + video_unregister_device(wfd_dev->pvdev); +err_video_register_device: + video_device_release(wfd_dev->pvdev); +err_video_device_alloc: + v4l2_device_unregister(&wfd_dev->v4l2_dev); +err_v4l2_registration: + return rc; +} +static int __devinit __wfd_probe(struct platform_device *pdev) +{ + int rc = 0, c = 0; + struct wfd_device *wfd_dev; /* Should be taken as an array*/ + struct ion_client *ion_client = NULL; + + WFD_MSG_DBG("__wfd_probe: E\n"); + wfd_dev = kzalloc(sizeof(*wfd_dev)*WFD_NUM_DEVICES, GFP_KERNEL); + if (!wfd_dev) { + WFD_MSG_ERR("Could not allocate memory for " + "wfd device\n"); + rc = -ENOMEM; + goto err_v4l2_probe; + } + pdev->dev.platform_data = (void *) wfd_dev; + + ion_client = msm_ion_client_create(-1, "wfd"); + + rc = wfd_stats_setup(); + if (rc) { + WFD_MSG_ERR("No debugfs support: %d\n", rc); + /* Don't treat this as a fatal err */ + rc = 0; + } + + if (!ion_client) { + WFD_MSG_ERR("Failed to create ion client\n"); + rc = -ENODEV; + goto err_v4l2_probe; + } + + for (c = 0; c < WFD_NUM_DEVICES; ++c) { + rc = wfd_dev_setup(&wfd_dev[c], + WFD_DEVICE_NUMBER_BASE + c, pdev); + + if (rc) { + /* Clear out old devices */ + for (--c; c >= 0; --c) { + v4l2_device_unregister_subdev( + &wfd_dev[c].vsg_sdev); + v4l2_device_unregister_subdev( + &wfd_dev[c].enc_sdev); + v4l2_device_unregister_subdev( + &wfd_dev[c].mdp_sdev); + video_unregister_device(wfd_dev[c].pvdev); + video_device_release(wfd_dev[c].pvdev); + v4l2_device_unregister(&wfd_dev[c].v4l2_dev); + } + + goto err_v4l2_probe; + } + + /* Other device specific stuff */ + mutex_init(&wfd_dev[c].dev_lock); + wfd_dev[c].ion_client = ion_client; + wfd_dev[c].in_use = false; + switch (WFD_DEVICE_NUMBER_BASE + c) { + case WFD_DEVICE_SECURE: + wfd_dev[c].secure_device = true; + break; + default: + break; + } + + } + WFD_MSG_DBG("__wfd_probe: X\n"); + return rc; +err_v4l2_probe: + kfree(wfd_dev); + return rc; +} + +static int __devexit __wfd_remove(struct platform_device *pdev) +{ + struct wfd_device *wfd_dev; + int c = 0; + + wfd_dev = (struct wfd_device *)pdev->dev.platform_data; + + WFD_MSG_DBG("Inside wfd_remove\n"); + if (!wfd_dev) { + WFD_MSG_ERR("Error removing WFD device"); + return -ENODEV; + } + + wfd_stats_teardown(); + for (c = 0; c < WFD_NUM_DEVICES; ++c) { + v4l2_device_unregister_subdev(&wfd_dev[c].vsg_sdev); + v4l2_device_unregister_subdev(&wfd_dev[c].enc_sdev); + v4l2_device_unregister_subdev(&wfd_dev[c].mdp_sdev); + video_unregister_device(wfd_dev[c].pvdev); + video_device_release(wfd_dev[c].pvdev); + v4l2_device_unregister(&wfd_dev[c].v4l2_dev); + } + + kfree(wfd_dev); + return 0; +} +static struct platform_driver wfd_driver = { + .probe = __wfd_probe, + .remove = __wfd_remove, + .driver = { + .name = "msm_wfd", + .owner = THIS_MODULE, + } +}; + +static int __init wfd_init(void) +{ + int rc = 0; + WFD_MSG_DBG("Calling init function of wfd driver\n"); + rc = platform_driver_register(&wfd_driver); + if (rc) { + WFD_MSG_ERR("failed to load the driver\n"); + goto err_platform_registration; + } +err_platform_registration: + return rc; +} + +static void __exit wfd_exit(void) +{ + WFD_MSG_DBG("wfd_exit: X\n"); + platform_driver_unregister(&wfd_driver); +} + +module_init(wfd_init); +module_exit(wfd_exit); diff --git a/drivers/media/video/msm/wfd/wfd-util.c b/drivers/media/video/msm/wfd/wfd-util.c new file mode 100644 index 0000000000000000000000000000000000000000..233668b0910c3fc65c5d79fc658bd9f91eed5e6b --- /dev/null +++ b/drivers/media/video/msm/wfd/wfd-util.c @@ -0,0 +1,217 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "wfd-util.h" + +static struct dentry *wfd_debugfs_root; + +int wfd_stats_setup() +{ + wfd_debugfs_root = debugfs_create_dir("wfd", NULL); + + if (wfd_debugfs_root == ERR_PTR(-ENODEV)) + return -ENODEV; + else if (!wfd_debugfs_root) + return -ENOMEM; + else + return 0; +} + +void wfd_stats_teardown() +{ + if (wfd_debugfs_root) + debugfs_remove_recursive(wfd_debugfs_root); +} + +int wfd_stats_init(struct wfd_stats *stats, int device) +{ + char device_str[NAME_MAX] = ""; + int rc = 0; + + if (!stats) { + rc = -EINVAL; + goto wfd_stats_init_fail; + } else if (!wfd_debugfs_root) { + WFD_MSG_ERR("wfd debugfs root does not exist\n"); + rc = -ENOENT; + goto wfd_stats_init_fail; + } + + memset(stats, 0, sizeof(*stats)); + INIT_LIST_HEAD(&stats->enc_queue); + + snprintf(device_str, sizeof(device_str), "%d", device); + stats->d_parent = debugfs_create_dir(device_str, wfd_debugfs_root); + if (IS_ERR(stats->d_parent)) { + rc = PTR_ERR(stats->d_parent); + stats->d_parent = NULL; + goto wfd_stats_init_fail; + } + + stats->d_v4l2_buf_count = debugfs_create_u32("v4l2_buf_count", S_IRUGO, + stats->d_parent, &stats->v4l2_buf_count); + if (IS_ERR(stats->d_v4l2_buf_count)) { + rc = PTR_ERR(stats->d_v4l2_buf_count); + stats->d_v4l2_buf_count = NULL; + goto wfd_stats_init_fail; + } + + stats->d_mdp_buf_count = debugfs_create_u32("mdp_buf_count", S_IRUGO, + stats->d_parent, &stats->mdp_buf_count); + if (IS_ERR(stats->d_mdp_buf_count)) { + rc = PTR_ERR(stats->d_mdp_buf_count); + stats->d_mdp_buf_count = NULL; + goto wfd_stats_init_fail; + } + + stats->d_vsg_buf_count = debugfs_create_u32("vsg_buf_count", S_IRUGO, + stats->d_parent, &stats->vsg_buf_count); + if (IS_ERR(stats->d_vsg_buf_count)) { + rc = PTR_ERR(stats->d_vsg_buf_count); + stats->d_vsg_buf_count = NULL; + goto wfd_stats_init_fail; + } + + stats->d_enc_buf_count = debugfs_create_u32("enc_buf_count", S_IRUGO, + stats->d_parent, &stats->enc_buf_count); + if (IS_ERR(stats->d_enc_buf_count)) { + rc = PTR_ERR(stats->d_enc_buf_count); + stats->d_enc_buf_count = NULL; + goto wfd_stats_init_fail; + } + + stats->d_frames_encoded = debugfs_create_u32("frames_encoded", S_IRUGO, + stats->d_parent, &stats->frames_encoded); + if (IS_ERR(stats->d_frames_encoded)) { + rc = PTR_ERR(stats->d_frames_encoded); + stats->d_frames_encoded = NULL; + goto wfd_stats_init_fail; + } + + stats->d_mdp_updates = debugfs_create_u32("mdp_updates", S_IRUGO, + stats->d_parent, &stats->mdp_updates); + if (IS_ERR(stats->d_mdp_updates)) { + rc = PTR_ERR(stats->d_mdp_updates); + stats->d_mdp_updates = NULL; + goto wfd_stats_init_fail; + } + + stats->d_enc_avg_latency = debugfs_create_u32("enc_avg_latency", + S_IRUGO, stats->d_parent, &stats->enc_avg_latency); + if (IS_ERR(stats->d_enc_avg_latency)) { + rc = PTR_ERR(stats->d_enc_avg_latency); + stats->d_enc_avg_latency = NULL; + goto wfd_stats_init_fail; + } + + return rc; +wfd_stats_init_fail: + return rc; +} + +int wfd_stats_update(struct wfd_stats *stats, enum wfd_stats_event event) +{ + int rc = 0; + switch (event) { + case WFD_STAT_EVENT_CLIENT_QUEUE: + stats->v4l2_buf_count++; + break; + case WFD_STAT_EVENT_CLIENT_DEQUEUE: { + struct wfd_stats_encode_sample *sample = NULL; + + stats->v4l2_buf_count--; + + if (!list_empty(&stats->enc_queue)) + sample = list_first_entry(&stats->enc_queue, + struct wfd_stats_encode_sample, + list); + if (sample) { + ktime_t kdiff = ktime_sub(ktime_get(), + sample->encode_start_ts); + uint32_t diff = ktime_to_ms(kdiff); + + stats->enc_cumulative_latency += diff; + stats->enc_latency_samples++; + stats->enc_avg_latency = stats->enc_cumulative_latency / + stats->enc_latency_samples; + + list_del(&sample->list); + kfree(sample); + sample = NULL; + } + break; + } + case WFD_STAT_EVENT_MDP_QUEUE: + stats->mdp_buf_count++; + stats->mdp_updates++; + break; + case WFD_STAT_EVENT_MDP_DEQUEUE: + stats->mdp_buf_count--; + break; + case WFD_STAT_EVENT_ENC_QUEUE: { + struct wfd_stats_encode_sample *sample = NULL; + + stats->enc_buf_count++; + stats->frames_encoded++; + + sample = kzalloc(sizeof(*sample), GFP_KERNEL); + if (sample) { + INIT_LIST_HEAD(&sample->list); + sample->encode_start_ts = ktime_get(); + list_add_tail(&sample->list, &stats->enc_queue); + } else { + WFD_MSG_WARN("Unable to measure latency\n"); + } + break; + } + case WFD_STAT_EVENT_ENC_DEQUEUE: + stats->enc_buf_count--; + break; + case WFD_STAT_EVENT_VSG_QUEUE: + stats->vsg_buf_count++; + break; + case WFD_STAT_EVENT_VSG_DEQUEUE: + stats->vsg_buf_count--; + break; + default: + rc = -ENOTSUPP; + } + + return rc; +} + +int wfd_stats_deinit(struct wfd_stats *stats) +{ + WFD_MSG_ERR("Latencies: avg enc. latency %d", + stats->enc_avg_latency); + /* Delete all debugfs files in one shot :) */ + if (stats->d_parent) + debugfs_remove_recursive(stats->d_parent); + + stats->d_parent = + stats->d_v4l2_buf_count = + stats->d_mdp_buf_count = + stats->d_vsg_buf_count = + stats->d_enc_buf_count = + stats->d_frames_encoded = + stats->d_mdp_updates = + stats->d_enc_avg_latency = NULL; + + return 0; +} diff --git a/drivers/media/video/msm/wfd/wfd-util.h b/drivers/media/video/msm/wfd/wfd-util.h new file mode 100644 index 0000000000000000000000000000000000000000..b6bb245caff459561b48fde87a87e9718df337e3 --- /dev/null +++ b/drivers/media/video/msm/wfd/wfd-util.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#ifndef _WFD_UTIL_H_ +#define _WFD_UTIL_H_ + +/*#define DEBUG_WFD*/ + +#define WFD_TAG "wfd: " +#ifdef DEBUG_WFD + #define WFD_MSG_INFO(fmt...) pr_info(WFD_TAG fmt) + #define WFD_MSG_WARN(fmt...) pr_warning(WFD_TAG fmt) +#else + #define WFD_MSG_INFO(fmt...) + #define WFD_MSG_WARN(fmt...) +#endif + #define WFD_MSG_ERR(fmt...) pr_err(KERN_ERR WFD_TAG fmt) + #define WFD_MSG_CRIT(fmt...) pr_crit(KERN_CRIT WFD_TAG fmt) + #define WFD_MSG_DBG(fmt...) pr_debug(WFD_TAG fmt) + + +struct wfd_stats_encode_sample { + ktime_t encode_start_ts; + struct list_head list; +}; + +struct wfd_stats { + /* Output Buffers */ + uint32_t v4l2_buf_count; + + /* Input Buffers */ + uint32_t mdp_buf_count; + uint32_t vsg_buf_count; + uint32_t enc_buf_count; + + /* Other */ + uint32_t frames_encoded; + uint32_t mdp_updates; + + uint32_t enc_avg_latency; + uint32_t enc_cumulative_latency; + uint32_t enc_latency_samples; + struct list_head enc_queue; + + /* Debugfs entries */ + struct dentry *d_parent; + struct dentry *d_v4l2_buf_count; + struct dentry *d_mdp_buf_count; + struct dentry *d_vsg_buf_count; + struct dentry *d_enc_buf_count; + struct dentry *d_frames_encoded; + struct dentry *d_mdp_updates; + struct dentry *d_enc_avg_latency; +}; + +enum wfd_stats_event { + WFD_STAT_EVENT_CLIENT_QUEUE, + WFD_STAT_EVENT_CLIENT_DEQUEUE, + + WFD_STAT_EVENT_MDP_QUEUE, + WFD_STAT_EVENT_MDP_DEQUEUE, + + WFD_STAT_EVENT_VSG_QUEUE, + WFD_STAT_EVENT_VSG_DEQUEUE, + + WFD_STAT_EVENT_ENC_QUEUE, + WFD_STAT_EVENT_ENC_DEQUEUE, +}; + +int wfd_stats_setup(void); +int wfd_stats_init(struct wfd_stats *, int device); +int wfd_stats_update(struct wfd_stats *, enum wfd_stats_event); +int wfd_stats_deinit(struct wfd_stats *); +void wfd_stats_teardown(void); +#endif diff --git a/drivers/media/video/msm_vidc/Kconfig b/drivers/media/video/msm_vidc/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..0b5a5fe869db919de7cdacde127d87ada49e92d8 --- /dev/null +++ b/drivers/media/video/msm_vidc/Kconfig @@ -0,0 +1,8 @@ +# +# VIDEO CORE +# + +menuconfig MSM_VIDC + bool "Qualcomm MSM Video Core Driver" + depends on ARCH_MSMCOPPER && VIDEO_V4L2 + default y diff --git a/drivers/media/video/msm_vidc/Makefile b/drivers/media/video/msm_vidc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..12c61c92106954b8e35ccd391072a265eeed4f7a --- /dev/null +++ b/drivers/media/video/msm_vidc/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_MSM_VIDC) := msm_v4l2_vidc.o \ + msm_vidc_common.o \ + msm_vidc.o \ + msm_vdec.o \ + msm_venc.o \ + msm_smem.o \ + vidc_hal.o \ + vidc_hal_interrupt_handler.o \ diff --git a/drivers/media/video/msm_vidc/msm_smem.c b/drivers/media/video/msm_vidc/msm_smem.c new file mode 100644 index 0000000000000000000000000000000000000000..25b5c5c7fd75d9f8e85317448b0bb088249d1af8 --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_smem.c @@ -0,0 +1,244 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_smem.h" + +struct smem_client { + int mem_type; + void *clnt; +}; + +static int ion_user_to_kernel(struct smem_client *client, + int fd, u32 offset, struct msm_smem *mem) +{ + struct ion_handle *hndl; + unsigned long ionflag; + size_t len; + int rc = 0; + hndl = ion_import_fd(client->clnt, fd); + if (IS_ERR_OR_NULL(hndl)) { + pr_err("Failed to get handle: %p, %d, %d, %p\n", + client, fd, offset, hndl); + rc = -ENOMEM; + goto fail_import_fd; + } + rc = ion_handle_get_flags(client->clnt, hndl, &ionflag); + if (rc) { + pr_err("Failed to get ion flags: %d", rc); + goto fail_map; + } + rc = ion_phys(client->clnt, hndl, &mem->paddr, &len); + if (rc) { + pr_err("Failed to get physical address\n"); + goto fail_map; + } + mem->kvaddr = ion_map_kernel(client->clnt, hndl, ionflag); + if (!mem->kvaddr) { + pr_err("Failed to map shared mem in kernel\n"); + rc = -EIO; + goto fail_map; + } + + mem->kvaddr += offset; + mem->paddr += offset; + mem->mem_type = client->mem_type; + mem->smem_priv = hndl; + mem->device_addr = mem->paddr; + mem->size = len; + return rc; +fail_map: + ion_free(client->clnt, hndl); +fail_import_fd: + return rc; +} + +static int alloc_ion_mem(struct smem_client *client, size_t size, + u32 align, u32 flags, struct msm_smem *mem) +{ + struct ion_handle *hndl; + size_t len; + int rc = 0; + flags = flags | ION_HEAP(ION_CP_MM_HEAP_ID); + hndl = ion_alloc(client->clnt, size, align, flags); + if (IS_ERR_OR_NULL(hndl)) { + pr_err("Failed to allocate shared memory = %p, %d, %d, 0x%x\n", + client, size, align, flags); + rc = -ENOMEM; + goto fail_shared_mem_alloc; + } + mem->mem_type = client->mem_type; + mem->smem_priv = hndl; + if (ion_phys(client->clnt, hndl, &mem->paddr, &len)) { + pr_err("Failed to get physical address\n"); + rc = -EIO; + goto fail_map; + } + mem->device_addr = mem->paddr; + mem->size = size; + mem->kvaddr = ion_map_kernel(client->clnt, hndl, 0); + if (!mem->kvaddr) { + pr_err("Failed to map shared mem in kernel\n"); + rc = -EIO; + goto fail_map; + } + return rc; +fail_map: + ion_free(client->clnt, hndl); +fail_shared_mem_alloc: + return rc; +} + +static void free_ion_mem(struct smem_client *client, struct msm_smem *mem) +{ + ion_unmap_kernel(client->clnt, mem->smem_priv); + ion_free(client->clnt, mem->smem_priv); +} + +static void *ion_new_client(void) +{ + struct ion_client *client = NULL; + client = msm_ion_client_create(-1, "video_client"); + if (!client) + pr_err("Failed to create smem client\n"); + return client; +}; + +static void ion_delete_client(struct smem_client *client) +{ + ion_client_destroy(client->clnt); +} + +struct msm_smem *msm_smem_user_to_kernel(void *clt, int fd, u32 offset) +{ + struct smem_client *client = clt; + int rc = 0; + struct msm_smem *mem; + if (fd < 0) { + pr_err("Invalid fd: %d\n", fd); + return NULL; + } + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) { + pr_err("Failed to allocte shared mem\n"); + return NULL; + } + switch (client->mem_type) { + case SMEM_ION: + rc = ion_user_to_kernel(clt, fd, offset, mem); + break; + default: + pr_err("Mem type not supported\n"); + rc = -EINVAL; + break; + } + if (rc) { + pr_err("Failed to allocate shared memory\n"); + kfree(mem); + mem = NULL; + } + return mem; +} + +void *msm_smem_new_client(enum smem_type mtype) +{ + struct smem_client *client = NULL; + void *clnt = NULL; + switch (mtype) { + case SMEM_ION: + clnt = ion_new_client(); + break; + default: + pr_err("Mem type not supported\n"); + break; + } + if (clnt) { + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (client) { + client->mem_type = mtype; + client->clnt = clnt; + } + } else { + pr_err("Failed to create new client: mtype = %d\n", mtype); + } + return client; +}; + +struct msm_smem *msm_smem_alloc(void *clt, size_t size, u32 align, u32 flags) +{ + struct smem_client *client; + int rc = 0; + struct msm_smem *mem; + + client = clt; + if (!client) { + pr_err("Invalid client passed\n"); + return NULL; + } + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) { + pr_err("Failed to allocate shared mem\n"); + return NULL; + } + switch (client->mem_type) { + case SMEM_ION: + rc = alloc_ion_mem(client, size, align, flags, mem); + break; + default: + pr_err("Mem type not supported\n"); + rc = -EINVAL; + break; + } + if (rc) { + pr_err("Failed to allocate shared memory\n"); + kfree(mem); + mem = NULL; + } + return mem; +} + +void msm_smem_free(void *clt, struct msm_smem *mem) +{ + struct smem_client *client = clt; + if (!client || !mem) { + pr_err("Invalid client/handle passed\n"); + return; + } + switch (client->mem_type) { + case SMEM_ION: + free_ion_mem(client, mem); + break; + default: + pr_err("Mem type not supported\n"); + break; + } + kfree(mem); +}; + +void msm_smem_delete_client(void *clt) +{ + struct smem_client *client = clt; + if (!client) { + pr_err("Invalid client passed\n"); + return; + } + switch (client->mem_type) { + case SMEM_ION: + ion_delete_client(client); + break; + default: + pr_err("Mem type not supported\n"); + break; + } + kfree(client); +} diff --git a/drivers/media/video/msm_vidc/msm_smem.h b/drivers/media/video/msm_vidc/msm_smem.h new file mode 100644 index 0000000000000000000000000000000000000000..84d12cc9d339442b6da8e608c929e42bc99f8bcc --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_smem.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SMEM_H_ +#define _MSM_SMEM_H_ + +#include +#include + +enum smem_type { + SMEM_ION, +}; + +struct msm_smem { + int mem_type; + size_t size; + void *kvaddr; + unsigned long paddr; + unsigned long device_addr; + /*Device address and others to follow*/ + void *smem_priv; +}; + +void *msm_smem_new_client(enum smem_type mtype); +struct msm_smem *msm_smem_alloc(void *clt, size_t size, u32 align, u32 flags); +void msm_smem_free(void *clt, struct msm_smem *mem); +void msm_smem_delete_client(void *clt); +struct msm_smem *msm_smem_user_to_kernel(void *clt, int fd, u32 offset); +#endif diff --git a/drivers/media/video/msm_vidc/msm_v4l2_vidc.c b/drivers/media/video/msm_vidc/msm_v4l2_vidc.c new file mode 100644 index 0000000000000000000000000000000000000000..1c646dda45019a37acb92b6e4f9dff8afdf00327 --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_v4l2_vidc.c @@ -0,0 +1,604 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_vidc_internal.h" +#include "vidc_hal_api.h" +#include "msm_smem.h" + +#define BASE_DEVICE_NUMBER 32 + +struct msm_vidc_drv *vidc_driver; + +struct buffer_info { + struct list_head list; + int type; + int fd; + int buff_off; + int size; + u32 uvaddr; + struct msm_smem *handle; +}; + +struct msm_v4l2_vid_inst { + struct msm_vidc_inst vidc_inst; + void *mem_client; + struct list_head registered_bufs; +}; + +static inline struct msm_vidc_inst *get_vidc_inst(struct file *filp, void *fh) +{ + return container_of(filp->private_data, + struct msm_vidc_inst, event_handler); +} + +static inline struct msm_v4l2_vid_inst *get_v4l2_inst(struct file *filp, + void *fh) +{ + struct msm_vidc_inst *vidc_inst; + vidc_inst = container_of(filp->private_data, + struct msm_vidc_inst, event_handler); + return container_of((void *)vidc_inst, + struct msm_v4l2_vid_inst, vidc_inst); +} + +static int msm_vidc_v4l2_setup_event_queue(void *inst, + struct video_device *pvdev) +{ + int rc = 0; + struct msm_vidc_inst *vidc_inst = (struct msm_vidc_inst *)inst; + spin_lock_init(&pvdev->fh_lock); + INIT_LIST_HEAD(&pvdev->fh_list); + + v4l2_fh_init(&vidc_inst->event_handler, pvdev); + v4l2_fh_add(&vidc_inst->event_handler); + + return rc; +} + +struct buffer_info *get_registered_buf(struct list_head *list, + int fd, u32 buff_off, u32 size) +{ + struct buffer_info *temp; + struct buffer_info *ret = NULL; + if (!list || fd < 0) { + pr_err("%s Invalid input\n", __func__); + goto err_invalid_input; + } + if (!list_empty(list)) { + list_for_each_entry(temp, list, list) { + if (temp && temp->fd == fd && + (CONTAINS(temp->buff_off, temp->size, buff_off) + || CONTAINS(buff_off, size, temp->buff_off) + || OVERLAPS(buff_off, size, + temp->buff_off, temp->size))) { + pr_err("This memory region is already mapped\n"); + ret = temp; + break; + } + } + } +err_invalid_input: + return ret; +} + +static int msm_v4l2_open(struct file *filp) +{ + int rc = 0; + struct video_device *vdev = video_devdata(filp); + struct msm_video_device *vid_dev = + container_of(vdev, struct msm_video_device, vdev); + struct msm_vidc_core *core = video_drvdata(filp); + struct msm_v4l2_vid_inst *v4l2_inst = kzalloc(sizeof(*v4l2_inst), + GFP_KERNEL); + if (!v4l2_inst) { + pr_err("Failed to allocate memory for this instance\n"); + rc = -ENOMEM; + goto fail_nomem; + } + v4l2_inst->mem_client = msm_smem_new_client(SMEM_ION); + if (!v4l2_inst->mem_client) { + pr_err("Failed to create memory client\n"); + rc = -ENOMEM; + goto fail_mem_client; + } + rc = msm_vidc_open(&v4l2_inst->vidc_inst, core->id, vid_dev->type); + if (rc) { + pr_err("Failed to create video instance, core: %d, type = %d\n", + core->id, vid_dev->type); + rc = -ENOMEM; + goto fail_open; + } + INIT_LIST_HEAD(&v4l2_inst->registered_bufs); + rc = msm_vidc_v4l2_setup_event_queue(&v4l2_inst->vidc_inst, vdev); + clear_bit(V4L2_FL_USES_V4L2_FH, &vdev->flags); + filp->private_data = &(v4l2_inst->vidc_inst.event_handler); + return rc; +fail_open: + msm_smem_delete_client(v4l2_inst->mem_client); +fail_mem_client: + kfree(v4l2_inst); +fail_nomem: + return rc; +} + +static int msm_v4l2_close(struct file *filp) +{ + int rc; + struct list_head *ptr, *next; + struct buffer_info *binfo; + struct msm_vidc_inst *vidc_inst; + struct msm_v4l2_vid_inst *v4l2_inst; + vidc_inst = get_vidc_inst(filp, NULL); + v4l2_inst = get_v4l2_inst(filp, NULL); + rc = msm_vidc_close(vidc_inst); + list_for_each_safe(ptr, next, &v4l2_inst->registered_bufs) { + binfo = list_entry(ptr, struct buffer_info, list); + list_del(&binfo->list); + msm_smem_free(v4l2_inst->mem_client, binfo->handle); + kfree(binfo); + } + msm_smem_delete_client(v4l2_inst->mem_client); + kfree(v4l2_inst); + return rc; +} + +static int msm_v4l2_querycap(struct file *filp, void *fh, + struct v4l2_capability *cap) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(filp, fh); + return msm_vidc_querycap((void *)vidc_inst, cap); +} + +int msm_v4l2_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_enum_fmt((void *)vidc_inst, f); +} + +int msm_v4l2_s_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_s_fmt((void *)vidc_inst, f); +} + +int msm_v4l2_g_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_g_fmt((void *)vidc_inst, f); +} + +int msm_v4l2_s_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_s_ctrl((void *)vidc_inst, a); +} + +int msm_v4l2_g_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_g_ctrl((void *)vidc_inst, a); +} + +int msm_v4l2_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *b) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + struct msm_v4l2_vid_inst *v4l2_inst; + struct list_head *ptr, *next; + int rc; + struct buffer_info *bi; + struct v4l2_buffer buffer_info; + v4l2_inst = get_v4l2_inst(file, NULL); + if (b->count == 0) { + list_for_each_safe(ptr, next, &v4l2_inst->registered_bufs) { + bi = list_entry(ptr, struct buffer_info, list); + if (bi->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + buffer_info.type = bi->type; + buffer_info.m.planes[0].reserved[0] = + bi->fd; + buffer_info.m.planes[0].reserved[1] = + bi->buff_off; + buffer_info.m.planes[0].length = bi->size; + buffer_info.m.planes[0].m.userptr = + bi->uvaddr; + buffer_info.length = 1; + pr_err("Releasing buffer: %d, %d, %d\n", + buffer_info.m.planes[0].reserved[0], + buffer_info.m.planes[0].reserved[1], + buffer_info.m.planes[0].length); + rc = msm_vidc_release_buf(&v4l2_inst->vidc_inst, + &buffer_info); + list_del(&bi->list); + msm_smem_free(v4l2_inst->mem_client, + bi->handle); + kfree(bi); + } + } + } + return msm_vidc_reqbufs((void *)vidc_inst, b); +} + +int msm_v4l2_prepare_buf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct msm_smem *handle; + struct buffer_info *binfo; + struct msm_vidc_inst *vidc_inst; + struct msm_v4l2_vid_inst *v4l2_inst; + int i, rc = 0; + vidc_inst = get_vidc_inst(file, fh); + v4l2_inst = get_v4l2_inst(file, fh); + if (!v4l2_inst->mem_client) { + pr_err("Failed to get memory client\n"); + rc = -ENOMEM; + goto exit; + } + for (i = 0; i < b->length; ++i) { + binfo = get_registered_buf(&v4l2_inst->registered_bufs, + b->m.planes[i].reserved[0], + b->m.planes[i].reserved[1], + b->m.planes[i].length); + if (binfo) { + pr_err("This memory region has already been prepared\n"); + rc = -EINVAL; + goto exit; + } + binfo = kzalloc(sizeof(*binfo), GFP_KERNEL); + if (!binfo) { + pr_err("Out of memory\n"); + rc = -ENOMEM; + goto exit; + } + handle = msm_smem_user_to_kernel(v4l2_inst->mem_client, + b->m.planes[i].reserved[0], + b->m.planes[i].reserved[1]); + if (!handle) { + pr_err("Failed to get device buffer address\n"); + kfree(binfo); + goto exit; + } + binfo->type = b->type; + binfo->fd = b->m.planes[i].reserved[0]; + binfo->buff_off = b->m.planes[i].reserved[1]; + binfo->size = b->m.planes[i].length; + binfo->uvaddr = b->m.planes[i].m.userptr; + binfo->handle = handle; + pr_debug("Registering buffer: %d, %d, %d\n", + b->m.planes[i].reserved[0], + b->m.planes[i].reserved[1], + b->m.planes[i].length); + list_add_tail(&binfo->list, &v4l2_inst->registered_bufs); + b->m.planes[i].m.userptr = handle->device_addr; + } + rc = msm_vidc_prepare_buf(&v4l2_inst->vidc_inst, b); +exit: + return rc; +} + +int msm_v4l2_qbuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct msm_vidc_inst *vidc_inst; + struct msm_v4l2_vid_inst *v4l2_inst; + struct buffer_info *binfo; + int rc = 0; + int i; + vidc_inst = get_vidc_inst(file, fh); + v4l2_inst = get_v4l2_inst(file, fh); + for (i = 0; i < b->length; ++i) { + binfo = get_registered_buf(&v4l2_inst->registered_bufs, + b->m.planes[i].reserved[0], + b->m.planes[i].reserved[1], + b->m.planes[i].length); + if (!binfo) { + pr_err("This buffer is not registered: %d, %d, %d\n", + b->m.planes[i].reserved[0], + b->m.planes[i].reserved[1], + b->m.planes[i].length); + rc = -EINVAL; + goto err_invalid_buff; + } + b->m.planes[i].m.userptr = binfo->handle->device_addr; + pr_debug("Queueing device address = %ld\n", + binfo->handle->device_addr); + } + rc = msm_vidc_qbuf(&v4l2_inst->vidc_inst, b); +err_invalid_buff: + return rc; +} + +int msm_v4l2_dqbuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_dqbuf((void *)vidc_inst, b); +} + +int msm_v4l2_streamon(struct file *file, void *fh, + enum v4l2_buf_type i) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_streamon((void *)vidc_inst, i); +} + +int msm_v4l2_streamoff(struct file *file, void *fh, + enum v4l2_buf_type i) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_streamoff((void *)vidc_inst, i); +} + +static int msm_v4l2_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + if (sub->type == V4L2_EVENT_ALL) + sub->type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + rc = v4l2_event_subscribe(fh, sub, 0); + return rc; +} + +static int msm_v4l2_unsubscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + rc = v4l2_event_unsubscribe(fh, sub); + return rc; +} + +static int msm_v4l2_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *dec) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(file, fh); + return msm_vidc_decoder_cmd((void *)vidc_inst, dec); +} +static const struct v4l2_ioctl_ops msm_v4l2_ioctl_ops = { + .vidioc_querycap = msm_v4l2_querycap, + .vidioc_enum_fmt_vid_cap_mplane = msm_v4l2_enum_fmt, + .vidioc_enum_fmt_vid_out_mplane = msm_v4l2_enum_fmt, + .vidioc_s_fmt_vid_cap_mplane = msm_v4l2_s_fmt, + .vidioc_s_fmt_vid_out_mplane = msm_v4l2_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = msm_v4l2_g_fmt, + .vidioc_g_fmt_vid_out_mplane = msm_v4l2_g_fmt, + .vidioc_reqbufs = msm_v4l2_reqbufs, + .vidioc_prepare_buf = msm_v4l2_prepare_buf, + .vidioc_qbuf = msm_v4l2_qbuf, + .vidioc_dqbuf = msm_v4l2_dqbuf, + .vidioc_streamon = msm_v4l2_streamon, + .vidioc_streamoff = msm_v4l2_streamoff, + .vidioc_s_ctrl = msm_v4l2_s_ctrl, + .vidioc_g_ctrl = msm_v4l2_g_ctrl, + .vidioc_subscribe_event = msm_v4l2_subscribe_event, + .vidioc_unsubscribe_event = msm_v4l2_unsubscribe_event, + .vidioc_decoder_cmd = msm_v4l2_decoder_cmd, +}; + +static const struct v4l2_ioctl_ops msm_v4l2_enc_ioctl_ops = { +}; + +static unsigned int msm_v4l2_poll(struct file *filp, + struct poll_table_struct *pt) +{ + struct msm_vidc_inst *vidc_inst = get_vidc_inst(filp, NULL); + return msm_vidc_poll((void *)vidc_inst, filp, pt); +} + +static const struct v4l2_file_operations msm_v4l2_vidc_fops = { + .owner = THIS_MODULE, + .open = msm_v4l2_open, + .release = msm_v4l2_close, + .ioctl = video_ioctl2, + .poll = msm_v4l2_poll, +}; + +void msm_vidc_release_video_device(struct video_device *pvdev) +{ +} + +static int msm_vidc_initialize_core(struct platform_device *pdev, + struct msm_vidc_core *core) +{ + struct resource *res; + int i = 0; + if (!core) + return -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("Failed to get IORESOURCE_MEM\n"); + return -ENODEV; + } + core->register_base = res->start; + core->register_size = resource_size(res); + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + pr_err("Failed to get IORESOURCE_IRQ\n"); + return -ENODEV; + } + core->irq = res->start; + INIT_LIST_HEAD(&core->instances); + mutex_init(&core->sync_lock); + spin_lock_init(&core->lock); + core->base_addr = 0x34f00000; + core->state = VIDC_CORE_UNINIT; + for (i = SYS_MSG_INDEX(SYS_MSG_START); + i <= SYS_MSG_INDEX(SYS_MSG_END); i++) { + init_completion(&core->completions[i]); + } + return 0; +} + +static int __devinit msm_vidc_probe(struct platform_device *pdev) +{ + int rc = 0; + struct msm_vidc_core *core; + unsigned long flags; + char debugfs_name[MAX_DEBUGFS_NAME]; + + core = kzalloc(sizeof(*core), GFP_KERNEL); + if (!core || !vidc_driver) { + pr_err("Failed to allocate memory for device core\n"); + rc = -ENOMEM; + goto err_no_mem; + } + rc = msm_vidc_initialize_core(pdev, core); + if (rc) { + pr_err("Failed to init core\n"); + goto err_v4l2_register; + } + rc = v4l2_device_register(&pdev->dev, &core->v4l2_dev); + if (rc) { + pr_err("Failed to register v4l2 device\n"); + goto err_v4l2_register; + } + core->vdev[MSM_VIDC_DECODER].vdev.release = + msm_vidc_release_video_device; + core->vdev[MSM_VIDC_DECODER].vdev.fops = &msm_v4l2_vidc_fops; + core->vdev[MSM_VIDC_DECODER].vdev.ioctl_ops = &msm_v4l2_ioctl_ops; + core->vdev[MSM_VIDC_DECODER].type = MSM_VIDC_DECODER; + rc = video_register_device(&core->vdev[MSM_VIDC_DECODER].vdev, + VFL_TYPE_GRABBER, BASE_DEVICE_NUMBER); + if (rc) { + pr_err("Failed to register video decoder device"); + goto err_dec_register; + } + video_set_drvdata(&core->vdev[MSM_VIDC_DECODER].vdev, core); + + core->vdev[MSM_VIDC_ENCODER].vdev.release = + msm_vidc_release_video_device; + core->vdev[MSM_VIDC_ENCODER].vdev.fops = &msm_v4l2_vidc_fops; + core->vdev[MSM_VIDC_ENCODER].vdev.ioctl_ops = &msm_v4l2_ioctl_ops; + core->vdev[MSM_VIDC_ENCODER].type = MSM_VIDC_ENCODER; + rc = video_register_device(&core->vdev[MSM_VIDC_ENCODER].vdev, + VFL_TYPE_GRABBER, BASE_DEVICE_NUMBER + 1); + if (rc) { + pr_err("Failed to register video encoder device"); + goto err_enc_register; + } + video_set_drvdata(&core->vdev[MSM_VIDC_ENCODER].vdev, core); + core->device = vidc_hal_add_device(core->id, core->base_addr, + core->register_base, core->register_size, core->irq, + &handle_cmd_response); + if (!core->device) { + pr_err("Failed to create interrupt handler"); + goto err_cores_exceeded; + } + + spin_lock_irqsave(&vidc_driver->lock, flags); + if (vidc_driver->num_cores + 1 > MSM_VIDC_CORES_MAX) { + spin_unlock_irqrestore(&vidc_driver->lock, flags); + pr_err("Maximum cores already exist, core_no = %d\n", + vidc_driver->num_cores); + goto err_cores_exceeded; + } + + core->id = vidc_driver->num_cores++; + list_add_tail(&core->list, &vidc_driver->cores); + spin_unlock_irqrestore(&vidc_driver->lock, flags); + snprintf(debugfs_name, MAX_DEBUGFS_NAME, "core%d", core->id); + core->debugfs_root = debugfs_create_dir(debugfs_name, + vidc_driver->debugfs_root); + pdev->dev.platform_data = core; + return rc; + +err_cores_exceeded: + video_unregister_device(&core->vdev[MSM_VIDC_ENCODER].vdev); +err_enc_register: + video_unregister_device(&core->vdev[MSM_VIDC_DECODER].vdev); +err_dec_register: + v4l2_device_unregister(&core->v4l2_dev); +err_v4l2_register: + kfree(core); +err_no_mem: + return rc; +} + +static int __devexit msm_vidc_remove(struct platform_device *pdev) +{ + int rc = 0; + struct msm_vidc_core *core = pdev->dev.platform_data; + vidc_hal_delete_device(core->device); + video_unregister_device(&core->vdev[MSM_VIDC_ENCODER].vdev); + video_unregister_device(&core->vdev[MSM_VIDC_DECODER].vdev); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return rc; +} +static const struct of_device_id msm_vidc_dt_match[] = { + {.compatible = "qcom,msm-vidc"}, +}; + +MODULE_DEVICE_TABLE(of, msm_vidc_dt_match); + +static struct platform_driver msm_vidc_driver = { + .probe = msm_vidc_probe, + .remove = msm_vidc_remove, + .driver = { + .name = "msm_vidc", + .owner = THIS_MODULE, + .of_match_table = msm_vidc_dt_match, + }, +}; + +static int __init msm_vidc_init(void) +{ + int rc = 0; + vidc_driver = kzalloc(sizeof(*vidc_driver), + GFP_KERNEL); + if (!vidc_driver) { + pr_err("Failed to allocate memroy for msm_vidc_drv\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&vidc_driver->cores); + spin_lock_init(&vidc_driver->lock); + vidc_driver->debugfs_root = debugfs_create_dir("msm_vidc", NULL); + if (!vidc_driver->debugfs_root) + pr_err("Failed to create debugfs for msm_vidc\n"); + + rc = platform_driver_register(&msm_vidc_driver); + if (rc) { + pr_err("Failed to register platform driver\n"); + kfree(vidc_driver); + vidc_driver = NULL; + } + + return rc; +} + +static void __exit msm_vidc_exit(void) +{ + platform_driver_unregister(&msm_vidc_driver); + debugfs_remove_recursive(vidc_driver->debugfs_root); + kfree(vidc_driver); + vidc_driver = NULL; +} + +module_init(msm_vidc_init); +module_exit(msm_vidc_exit); diff --git a/drivers/media/video/msm_vidc/msm_vdec.c b/drivers/media/video/msm_vidc/msm_vdec.c new file mode 100644 index 0000000000000000000000000000000000000000..20a0cd087956e063f045613c26b4a5951e74e9e6 --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_vdec.c @@ -0,0 +1,885 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_vidc_internal.h" +#include "msm_vidc_common.h" +#include "vidc_hal_api.h" +#include "msm_smem.h" + +#define MSM_VDEC_DVC_NAME "msm_vdec_8974" +#define MAX_PLANES 1 +#define DEFAULT_HEIGHT 720 +#define DEFAULT_WIDTH 1280 +#define MIN_NUM_OUTPUT_BUFFERS 2 +#define MAX_NUM_OUTPUT_BUFFERS 6 + +static const char *const mpeg_video_vidc_divx_format[] = { + "DIVX Format 3", + "DIVX Format 4", + "DIVX Format 5", + "DIVX Format 6", + NULL +}; +static const char *mpeg_video_stream_format[] = { + "NAL Format Start Codes", + "NAL Format One NAL Per Buffer", + "NAL Format One Byte Length", + "NAL Format Two Byte Length", + "NAL Format Four Byte Length", + NULL +}; +static const char *const mpeg_video_output_order[] = { + "Display Order", + "Decode Order", + NULL +}; +static const struct msm_vidc_ctrl msm_vdec_ctrls[] = { + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_STREAM_FORMAT, + .name = "NAL Format", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_STARTCODES, + .maximum = V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_FOUR_BYTE_LENGTH, + .default_value = V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_STARTCODES, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_STARTCODES) | + (1 << V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_ONE_NAL_PER_BUFFER) | + (1 << V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_ONE_BYTE_LENGTH) | + (1 << V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_TWO_BYTE_LENGTH) | + (1 << V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_FOUR_BYTE_LENGTH) + ), + .qmenu = mpeg_video_stream_format, + .step = 0, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_OUTPUT_ORDER, + .name = "Output Order", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DISPLAY, + .maximum = V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DECODE, + .default_value = V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DISPLAY, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DISPLAY) | + (1 << V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DECODE) + ), + .qmenu = mpeg_video_output_order, + .step = 0, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_ENABLE_PICTURE_TYPE, + .name = "Picture Type Decoding", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = 15, + .default_value = 15, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_KEEP_ASPECT_RATIO, + .name = "Keep Aspect Ratio", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_POST_LOOP_DEBLOCKER_MODE, + .name = "Deblocker Mode", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_DIVX_FORMAT, + .name = "Divx Format", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_4, + .maximum = V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_6, + .default_value = V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_4, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_4) | + (1 << V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_5) | + (1 << V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_6) + ), + .qmenu = mpeg_video_vidc_divx_format, + .step = 0, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_MB_ERROR_MAP_REPORTING, + .name = "MB Error Map Reporting", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_CONTINUE_DATA_TRANSFER, + .name = "control", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, +}; + +#define NUM_CTRLS ARRAY_SIZE(msm_vdec_ctrls) + +static u32 get_frame_size_nv12(int plane, + u32 height, u32 width) +{ + return (ALIGN(height, 32) * ALIGN(width, 32) * 3) / 2; +} +static u32 get_frame_size_nv21(int plane, + u32 height, u32 width) +{ + return height * width * 2; +} + +static u32 get_frame_size_compressed(int plane, + u32 height, u32 width) +{ + return 0x500000; +} + +static const struct msm_vidc_format vdec_formats[] = { + { + .name = "YCbCr Semiplanar 4:2:0", + .description = "Y/CbCr 4:2:0", + .fourcc = V4L2_PIX_FMT_NV12, + .num_planes = 1, + .get_frame_size = get_frame_size_nv12, + .type = CAPTURE_PORT, + }, + { + .name = "Mpeg4", + .description = "Mpeg4 compressed format", + .fourcc = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = OUTPUT_PORT, + }, + { + .name = "Mpeg2", + .description = "Mpeg2 compressed format", + .fourcc = V4L2_PIX_FMT_MPEG2, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = OUTPUT_PORT, + }, + { + .name = "H263", + .description = "H263 compressed format", + .fourcc = V4L2_PIX_FMT_H263, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = OUTPUT_PORT, + }, + { + .name = "H264", + .description = "H264 compressed format", + .fourcc = V4L2_PIX_FMT_H264, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = OUTPUT_PORT, + }, + { + .name = "YCrCb Semiplanar 4:2:0", + .description = "Y/CrCb 4:2:0", + .fourcc = V4L2_PIX_FMT_NV21, + .num_planes = 1, + .get_frame_size = get_frame_size_nv21, + .type = CAPTURE_PORT, + }, + { + .name = "DIVX 311", + .description = "DIVX 311 compressed format", + .fourcc = V4L2_PIX_FMT_DIVX_311, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = OUTPUT_PORT, + } +}; + +int msm_vdec_streamon(struct msm_vidc_inst *inst, enum v4l2_buf_type i) +{ + int rc = 0; + struct vb2_queue *q; + q = msm_comm_get_vb2q(inst, i); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", i); + return -EINVAL; + } + pr_debug("Calling streamon\n"); + rc = vb2_streamon(q, i); + if (rc) + pr_err("streamon failed on port: %d\n", i); + return rc; +} + +int msm_vdec_streamoff(struct msm_vidc_inst *inst, enum v4l2_buf_type i) +{ + int rc = 0; + struct vb2_queue *q; + + q = msm_comm_get_vb2q(inst, i); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", i); + return -EINVAL; + } + pr_debug("Calling streamoff\n"); + rc = vb2_streamoff(q, i); + if (rc) + pr_err("streamoff failed on port: %d\n", i); + return rc; +} + +int msm_vdec_prepare_buf(struct msm_vidc_inst *inst, + struct v4l2_buffer *b) +{ + int rc = 0; + int i; + struct vidc_buffer_addr_info buffer_info; + switch (b->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + for (i = 0; i < b->length; i++) { + pr_err("device_addr = %ld, size = %d\n", + b->m.planes[i].m.userptr, + b->m.planes[i].length); + buffer_info.buffer_size = b->m.planes[i].length; + buffer_info.buffer_type = HAL_BUFFER_OUTPUT; + buffer_info.num_buffers = 1; + buffer_info.align_device_addr = + b->m.planes[i].m.userptr; + buffer_info.extradata_size = 0; + buffer_info.extradata_addr = 0; + rc = vidc_hal_session_set_buffers((void *)inst->session, + &buffer_info); + if (rc) { + pr_err("vidc_hal_session_set_buffers failed"); + break; + } + } + break; + default: + pr_err("Buffer type not recognized: %d\n", b->type); + break; + } + return rc; +} + +int msm_vdec_release_buf(struct msm_vidc_inst *inst, + struct v4l2_buffer *b) +{ + int rc = 0; + int i; + struct vidc_buffer_addr_info buffer_info; + + switch (b->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + for (i = 0; i < b->length; i++) { + pr_debug("Release device_addr = %ld, size = %d\n", + b->m.planes[i].m.userptr, + b->m.planes[i].length); + buffer_info.buffer_size = b->m.planes[i].length; + buffer_info.buffer_type = HAL_BUFFER_OUTPUT; + buffer_info.num_buffers = 1; + buffer_info.align_device_addr = + b->m.planes[i].m.userptr; + buffer_info.extradata_addr = 0; + rc = vidc_hal_session_release_buffers( + (void *)inst->session, &buffer_info); + if (rc) + pr_err("vidc_hal_session_release_buffers failed"); + } + break; + default: + pr_err("Buffer type not recognized: %d\n", b->type); + break; + } + return rc; +} + +int msm_vdec_qbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b) +{ + struct vb2_queue *q = NULL; + int rc = 0; + q = msm_comm_get_vb2q(inst, b->type); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", b->type); + return -EINVAL; + } + rc = vb2_qbuf(q, b); + if (rc) + pr_err("Failed to qbuf, %d\n", rc); + return rc; +} +int msm_vdec_dqbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b) +{ + struct vb2_queue *q = NULL; + int rc = 0; + q = msm_comm_get_vb2q(inst, b->type); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", b->type); + return -EINVAL; + } + rc = vb2_dqbuf(q, b, true); + if (rc) + pr_err("Failed to dqbuf, %d\n", rc); + return rc; +} + +int msm_vdec_reqbufs(struct msm_vidc_inst *inst, struct v4l2_requestbuffers *b) +{ + struct vb2_queue *q = NULL; + int rc = 0; + if (!inst || !b) { + pr_err("Invalid input, inst = %p, buffer = %p\n", inst, b); + return -EINVAL; + } + q = msm_comm_get_vb2q(inst, b->type); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", b->type); + return -EINVAL; + } + + rc = vb2_reqbufs(q, b); + if (rc) + pr_err("Failed to get reqbufs, %d\n", rc); + return rc; +} + +int msm_vdec_g_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) +{ + const struct msm_vidc_format *fmt = NULL; + int rc = 0; + int i; + if (!inst || !f) { + pr_err("Invalid input, inst = %p, format = %p\n", inst, f); + return -EINVAL; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + fmt = inst->fmts[CAPTURE_PORT]; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fmt = inst->fmts[OUTPUT_PORT]; + + if (fmt) { + f->fmt.pix_mp.pixelformat = fmt->fourcc; + if (inst->in_reconfig == true) { + inst->height = inst->reconfig_height; + inst->width = inst->reconfig_width; + } + f->fmt.pix_mp.height = inst->height; + f->fmt.pix_mp.width = inst->width; + for (i = 0; i < fmt->num_planes; ++i) { + f->fmt.pix_mp.plane_fmt[i].sizeimage = + fmt->get_frame_size(i, inst->height, inst->width); + } + } else { + pr_err("Buf type not recognized, type = %d\n", + f->type); + rc = -EINVAL; + } + return rc; +} + +int msm_vdec_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) +{ + const struct msm_vidc_format *fmt = NULL; + int rc = 0; + int i; + if (!inst || !f) { + pr_err("Invalid input, inst = %p, format = %p\n", inst, f); + return -EINVAL; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + inst->width = f->fmt.pix_mp.width; + inst->height = f->fmt.pix_mp.height; + fmt = msm_comm_get_pixel_fmt_fourcc(vdec_formats, + ARRAY_SIZE(vdec_formats), f->fmt.pix_mp.pixelformat, + CAPTURE_PORT); + if (fmt && fmt->type != CAPTURE_PORT) { + pr_err("Format: %d not supported on CAPTURE port\n", + f->fmt.pix_mp.pixelformat); + rc = -EINVAL; + goto err_invalid_fmt; + } + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fmt = msm_comm_get_pixel_fmt_fourcc(vdec_formats, + ARRAY_SIZE(vdec_formats), f->fmt.pix_mp.pixelformat, + OUTPUT_PORT); + if (fmt && fmt->type != OUTPUT_PORT) { + pr_err("Format: %d not supported on OUTPUT port\n", + f->fmt.pix_mp.pixelformat); + rc = -EINVAL; + goto err_invalid_fmt; + } + } + + if (fmt) { + for (i = 0; i < fmt->num_planes; ++i) { + f->fmt.pix_mp.plane_fmt[i].sizeimage = + fmt->get_frame_size(i, f->fmt.pix_mp.height, + f->fmt.pix_mp.width); + } + inst->fmts[fmt->type] = fmt; + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + rc = msm_comm_try_state(inst, MSM_VIDC_OPEN); + if (rc) { + pr_err("Failed to open instance\n"); + goto err_invalid_fmt; + } + } + } else { + pr_err("Buf type not recognized, type = %d\n", + f->type); + rc = -EINVAL; + } +err_invalid_fmt: + return rc; +} + +int msm_vdec_querycap(struct msm_vidc_inst *inst, struct v4l2_capability *cap) +{ + if (!inst || !cap) { + pr_err("Invalid input, inst = %p, cap = %p\n", inst, cap); + return -EINVAL; + } + strlcpy(cap->driver, MSM_VIDC_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->card, MSM_VDEC_DVC_NAME, sizeof(cap->card)); + cap->bus_info[0] = 0; + cap->version = MSM_VIDC_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_VIDEO_OUTPUT_MPLANE | + V4L2_CAP_STREAMING; + memset(cap->reserved, 0, sizeof(cap->reserved)); + return 0; +} + +int msm_vdec_enum_fmt(struct msm_vidc_inst *inst, struct v4l2_fmtdesc *f) +{ + const struct msm_vidc_format *fmt = NULL; + int rc = 0; + if (!inst || !f) { + pr_err("Invalid input, inst = %p, f = %p\n", inst, f); + return -EINVAL; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt = msm_comm_get_pixel_fmt_index(vdec_formats, + ARRAY_SIZE(vdec_formats), f->index, CAPTURE_PORT); + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fmt = msm_comm_get_pixel_fmt_index(vdec_formats, + ARRAY_SIZE(vdec_formats), f->index, OUTPUT_PORT); + f->flags = V4L2_FMT_FLAG_COMPRESSED; + } + + memset(f->reserved, 0 , sizeof(f->reserved)); + if (fmt) { + strlcpy(f->description, fmt->description, + sizeof(f->description)); + f->pixelformat = fmt->fourcc; + } else { + pr_err("No more formats found\n"); + rc = -EINVAL; + } + return rc; +} + +static int msm_vdec_queue_setup(struct vb2_queue *q, + const struct v4l2_format *fmt, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + void *alloc_ctxs[]) +{ + int i, rc = 0; + struct msm_vidc_inst *inst; + struct hal_frame_size frame_sz; + unsigned long flags; + if (!q || !q->drv_priv) { + pr_err("Invalid input, q = %p\n", q); + return -EINVAL; + } + inst = q->drv_priv; + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + *num_planes = 1; + if (*num_buffers < MIN_NUM_OUTPUT_BUFFERS || + *num_buffers > MAX_NUM_OUTPUT_BUFFERS) + *num_buffers = MIN_NUM_OUTPUT_BUFFERS; + for (i = 0; i < *num_planes; i++) { + sizes[i] = inst->fmts[OUTPUT_PORT]->get_frame_size( + i, inst->height, inst->width); + } + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + pr_debug("Getting bufreqs on capture plane\n"); + rc = msm_comm_try_state(inst, MSM_VIDC_OPEN_DONE); + if (rc) { + pr_err("Failed to open instance\n"); + break; + } + frame_sz.buffer_type = HAL_BUFFER_OUTPUT; + frame_sz.width = inst->width; + frame_sz.height = inst->height; + pr_debug("width = %d, height = %d\n", + frame_sz.width, frame_sz.height); + rc = vidc_hal_session_set_property((void *)inst->session, + HAL_PARAM_FRAME_SIZE, &frame_sz); + if (rc) { + pr_err("Failed to set hal property for framesize\n"); + break; + } + rc = msm_comm_try_get_bufreqs(inst); + if (rc) { + pr_err("Failed to get buffer requirements: %d\n", rc); + break; + } + *num_planes = 1; + spin_lock_irqsave(&inst->lock, flags); + *num_buffers = inst->buff_req.buffer[1].buffer_count_actual; + spin_unlock_irqrestore(&inst->lock, flags); + pr_debug("size = %d, alignment = %d\n", + inst->buff_req.buffer[1].buffer_size, + inst->buff_req.buffer[1].buffer_alignment); + for (i = 0; i < *num_planes; i++) { + sizes[i] = inst->fmts[CAPTURE_PORT]->get_frame_size( + i, inst->height, inst->width); + } + + break; + default: + pr_err("Invalid q type = %d\n", q->type); + rc = -EINVAL; + break; + } + return rc; +} + +static inline int start_streaming(struct msm_vidc_inst *inst) +{ + int rc = 0; + unsigned long flags; + struct vb2_buf_entry *temp; + struct list_head *ptr, *next; + struct v4l2_control control; + struct hal_nal_stream_format_supported stream_format; + struct hal_enable_picture enable_picture; + struct hal_enable hal_property; + u32 control_idx = 0; + enum hal_property property_id = 0; + u32 property_val = 0; + void *pdata; + rc = msm_comm_set_scratch_buffers(inst); + if (rc) { + pr_err("Failed to set scratch buffers: %d\n", rc); + goto fail_start; + } + for (; control_idx < NUM_CTRLS; control_idx++) { + control.id = msm_vdec_ctrls[control_idx].id; + rc = v4l2_g_ctrl(&inst->ctrl_handler, &control); + if (rc) { + pr_err("Failed to get control value for ID=%d\n", + msm_vdec_ctrls[control_idx].id); + } else { + property_id = 0; + switch (control.id) { + case V4L2_CID_MPEG_VIDC_VIDEO_STREAM_FORMAT: + property_id = + HAL_PARAM_NAL_STREAM_FORMAT_SELECT; + stream_format.nal_stream_format_supported = + (0x00000001 << control.value); + pdata = &stream_format; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_OUTPUT_ORDER: + property_id = HAL_PARAM_VDEC_OUTPUT_ORDER; + property_val = control.value; + pdata = &property_val; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_ENABLE_PICTURE_TYPE: + property_id = + HAL_PARAM_VDEC_PICTURE_TYPE_DECODE; + enable_picture.picture_type = control.value; + pdata = &enable_picture; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_KEEP_ASPECT_RATIO: + property_id = + HAL_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO; + hal_property.enable = control.value; + pdata = &hal_property; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_POST_LOOP_DEBLOCKER_MODE: + property_id = + HAL_CONFIG_VDEC_POST_LOOP_DEBLOCKER; + hal_property.enable = control.value; + pdata = &hal_property; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_DIVX_FORMAT: + property_id = HAL_PARAM_DIVX_FORMAT; + property_val = control.value; + pdata = &property_val; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_MB_ERROR_MAP_REPORTING: + property_id = + HAL_CONFIG_VDEC_MB_ERROR_MAP_REPORTING; + hal_property.enable = control.value; + pdata = &hal_property; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_CONTINUE_DATA_TRANSFER: + property_id = + HAL_PARAM_VDEC_CONTINUE_DATA_TRANSFER; + hal_property.enable = control.value; + pdata = &hal_property; + break; + default: + break; + } + if (property_id) { + pr_err("Control: HAL property=%x,ctrl_id=%x,ctrl_value=%d\n", + property_id, + msm_vdec_ctrls[control_idx].id, + control.value); + rc = vidc_hal_session_set_property((void *) + inst->session, property_id, + pdata); + } + if (rc) + pr_err("Failed to set hal property for framesize\n"); + } + } + + rc = msm_comm_try_state(inst, MSM_VIDC_START_DONE); + if (rc) { + pr_err("Failed to move inst: %p to start done state\n", + inst); + goto fail_start; + } + spin_lock_irqsave(&inst->lock, flags); + if (!list_empty(&inst->pendingq)) { + list_for_each_safe(ptr, next, &inst->pendingq) { + temp = list_entry(ptr, struct vb2_buf_entry, list); + rc = msm_comm_qbuf(temp->vb); + if (rc) { + pr_err("Failed to qbuf to hardware\n"); + break; + } + list_del(&temp->list); + kfree(temp); + } + } + spin_unlock_irqrestore(&inst->lock, flags); + return rc; +fail_start: + return rc; +} + +static int msm_vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct msm_vidc_inst *inst; + int rc = 0; + if (!q || !q->drv_priv) { + pr_err("Invalid input, q = %p\n", q); + return -EINVAL; + } + inst = q->drv_priv; + pr_debug("Streamon called on: %d capability\n", q->type); + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (inst->vb2_bufq[CAPTURE_PORT].streaming) + rc = start_streaming(inst); + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + inst->in_reconfig = false; + if (inst->vb2_bufq[OUTPUT_PORT].streaming) + rc = start_streaming(inst); + break; + default: + pr_err("Q-type is not supported: %d\n", q->type); + rc = -EINVAL; + break; + } + return rc; +} + +static int msm_vdec_stop_streaming(struct vb2_queue *q) +{ + struct msm_vidc_inst *inst; + int rc = 0; + if (!q || !q->drv_priv) { + pr_err("Invalid input, q = %p\n", q); + return -EINVAL; + } + inst = q->drv_priv; + pr_debug("Streamoff called on: %d capability\n", q->type); + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (!inst->vb2_bufq[CAPTURE_PORT].streaming) + rc = msm_comm_try_state(inst, MSM_VIDC_CLOSE_DONE); + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (!inst->vb2_bufq[OUTPUT_PORT].streaming) + rc = msm_comm_try_state(inst, MSM_VIDC_CLOSE_DONE); + break; + default: + pr_err("Q-type is not supported: %d\n", q->type); + rc = -EINVAL; + break; + } + if (rc) + pr_err("Failed to move inst: %p, cap = %d to state: %d\n", + inst, q->type, MSM_VIDC_CLOSE_DONE); + return rc; +} + +static void msm_vdec_buf_queue(struct vb2_buffer *vb) +{ + int rc; + rc = msm_comm_qbuf(vb); + if (rc) + pr_err("Failed to queue buffer: %d\n", rc); +} + +static const struct vb2_ops msm_vdec_vb2q_ops = { + .queue_setup = msm_vdec_queue_setup, + .start_streaming = msm_vdec_start_streaming, + .buf_queue = msm_vdec_buf_queue, + .stop_streaming = msm_vdec_stop_streaming, +}; + +const struct vb2_ops *msm_vdec_get_vb2q_ops(void) +{ + return &msm_vdec_vb2q_ops; +} + +int msm_vdec_inst_init(struct msm_vidc_inst *inst) +{ + int rc = 0; + if (!inst) { + pr_err("Invalid input = %p\n", inst); + return -EINVAL; + } + inst->fmts[OUTPUT_PORT] = &vdec_formats[1]; + inst->fmts[CAPTURE_PORT] = &vdec_formats[0]; + inst->height = DEFAULT_HEIGHT; + inst->width = DEFAULT_WIDTH; + return rc; +} + +static int msm_vdec_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + return 0; +} +static int msm_vdec_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + return 0; +} + +static const struct v4l2_ctrl_ops msm_vdec_ctrl_ops = { + + .s_ctrl = msm_vdec_op_s_ctrl, + .g_volatile_ctrl = msm_vdec_op_g_volatile_ctrl, +}; + +const struct v4l2_ctrl_ops *msm_vdec_get_ctrl_ops(void) +{ + return &msm_vdec_ctrl_ops; +} + +int msm_vdec_s_ctrl(struct msm_vidc_inst *inst, struct v4l2_control *ctrl) +{ + return v4l2_s_ctrl(NULL, &inst->ctrl_handler, ctrl); +} +int msm_vdec_g_ctrl(struct msm_vidc_inst *inst, struct v4l2_control *ctrl) +{ + return v4l2_g_ctrl(&inst->ctrl_handler, ctrl); +} +int msm_vdec_ctrl_init(struct msm_vidc_inst *inst) +{ + int idx = 0; + struct v4l2_ctrl_config ctrl_cfg; + int ret_val = 0; + + ret_val = v4l2_ctrl_handler_init(&inst->ctrl_handler, NUM_CTRLS); + + if (ret_val) { + pr_err("CTRL ERR: Control handler init failed, %d\n", + inst->ctrl_handler.error); + return ret_val; + } + + for (; idx < NUM_CTRLS; idx++) { + if (IS_PRIV_CTRL(msm_vdec_ctrls[idx].id)) { + /*add private control*/ + ctrl_cfg.def = msm_vdec_ctrls[idx].default_value; + ctrl_cfg.flags = 0; + ctrl_cfg.id = msm_vdec_ctrls[idx].id; + /*ctrl_cfg.is_private = + * msm_vdec_ctrls[idx].is_private; + * ctrl_cfg.is_volatile = + * msm_vdec_ctrls[idx].is_volatile;*/ + ctrl_cfg.max = msm_vdec_ctrls[idx].maximum; + ctrl_cfg.min = msm_vdec_ctrls[idx].minimum; + ctrl_cfg.menu_skip_mask = + msm_vdec_ctrls[idx].menu_skip_mask; + ctrl_cfg.name = msm_vdec_ctrls[idx].name; + ctrl_cfg.ops = &msm_vdec_ctrl_ops; + ctrl_cfg.step = msm_vdec_ctrls[idx].step; + ctrl_cfg.type = msm_vdec_ctrls[idx].type; + ctrl_cfg.qmenu = msm_vdec_ctrls[idx].qmenu; + + v4l2_ctrl_new_custom(&inst->ctrl_handler, + &ctrl_cfg, NULL); + } else { + if (msm_vdec_ctrls[idx].type == V4L2_CTRL_TYPE_MENU) { + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, + &msm_vdec_ctrl_ops, + msm_vdec_ctrls[idx].id, + msm_vdec_ctrls[idx].maximum, + msm_vdec_ctrls[idx].menu_skip_mask, + msm_vdec_ctrls[idx].default_value); + } else { + v4l2_ctrl_new_std(&inst->ctrl_handler, + &msm_vdec_ctrl_ops, + msm_vdec_ctrls[idx].id, + msm_vdec_ctrls[idx].minimum, + msm_vdec_ctrls[idx].maximum, + msm_vdec_ctrls[idx].step, + msm_vdec_ctrls[idx].default_value); + } + } + } + ret_val = inst->ctrl_handler.error; + if (ret_val) + pr_err("CTRL ERR: Error adding ctrls to ctrl handle, %d\n", + inst->ctrl_handler.error); + return ret_val; +} diff --git a/drivers/media/video/msm_vidc/msm_vdec.h b/drivers/media/video/msm_vidc/msm_vdec.h new file mode 100644 index 0000000000000000000000000000000000000000..1242fb47726c8b8de59dfe6fbb2d9f4d9cde099b --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_vdec.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VDEC_H_ +#define _MSM_VDEC_H_ + +#include +#include "msm_vidc_internal.h" + +int msm_vdec_inst_init(struct msm_vidc_inst *inst); +int msm_vdec_ctrl_init(struct msm_vidc_inst *inst); +int msm_vdec_querycap(void *instance, struct v4l2_capability *cap); +int msm_vdec_enum_fmt(void *instance, struct v4l2_fmtdesc *f); +int msm_vdec_s_fmt(void *instance, struct v4l2_format *f); +int msm_vdec_g_fmt(void *instance, struct v4l2_format *f); +int msm_vdec_s_ctrl(void *instance, struct v4l2_control *a); +int msm_vdec_g_ctrl(void *instance, struct v4l2_control *a); +int msm_vdec_reqbufs(void *instance, struct v4l2_requestbuffers *b); +int msm_vdec_prepare_buf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_vdec_release_buf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_vdec_qbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_vdec_dqbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_vdec_streamon(struct msm_vidc_inst *inst, enum v4l2_buf_type i); +int msm_vdec_streamoff(struct msm_vidc_inst *inst, enum v4l2_buf_type i); +struct vb2_ops *msm_vdec_get_vb2q_ops(void); + +#endif diff --git a/drivers/media/video/msm_vidc/msm_venc.c b/drivers/media/video/msm_vidc/msm_venc.c new file mode 100644 index 0000000000000000000000000000000000000000..ed99d354e3a6c53159a7e21c8393478a81f8d4ec --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_venc.c @@ -0,0 +1,1254 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_vidc_internal.h" +#include "msm_vidc_common.h" +#include "vidc_hal_api.h" +#include "msm_smem.h" + +#define MSM_VENC_DVC_NAME "msm_venc_8974" +#define DEFAULT_HEIGHT 720 +#define DEFAULT_WIDTH 1280 +#define MIN_NUM_OUTPUT_BUFFERS 2 +#define MAX_NUM_OUTPUT_BUFFERS 8 +#define MIN_BIT_RATE 64 +#define MAX_BIT_RATE 8000 +#define DEFAULT_BIT_RATE 64 +#define BIT_RATE_STEP 1 +#define MIN_FRAME_RATE 1 +#define MAX_FRAME_RATE 120 +#define DEFAULT_FRAME_RATE 30 +#define MAX_SLICE_BYTE_SIZE 1024 +#define MIN_SLICE_BYTE_SIZE 1024 +#define MAX_SLICE_MB_SIZE 300 +#define I_FRAME_QP 26 +#define P_FRAME_QP 28 +#define B_FRAME_QP 30 +#define MAX_INTRA_REFRESH_MBS 300 +#define L_MODE V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY + +static const char *const mpeg_video_rate_control[] = { + "No Rate Control", + "VBR VFR", + "VBR CFR", + "CBR VFR", + "CBR CFR", + NULL +}; + +static const char *const mpeg_video_rotation[] = { + "No Rotation", + "90 Degree Rotation", + "180 Degree Rotation", + "270 Degree Rotation", + NULL +}; + +static const char *const h264_video_entropy_cabac_model[] = { + "Model 0", + "Model 1", + "Model 2", + NULL +}; +static const struct msm_vidc_ctrl msm_venc_ctrls[] = { + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_FRAME_RATE, + .name = "Frame Rate", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = MIN_FRAME_RATE, + .maximum = MAX_FRAME_RATE, + .default_value = DEFAULT_FRAME_RATE, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_IDR_PERIOD, + .name = "IDR Period", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 10*MAX_FRAME_RATE, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_NUM_P_FRAMES, + .name = "Intra Period for P frames", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 10*DEFAULT_FRAME_RATE, + .default_value = 2*DEFAULT_FRAME_RATE-1, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_NUM_B_FRAMES, + .name = "Intra Period for B frames", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 10*DEFAULT_FRAME_RATE, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_REQUEST_IFRAME, + .name = "Request I Frame", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL, + .name = "Rate Control", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_OFF, + .maximum = V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_CBR_CFR, + .default_value = V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_OFF, + .step = 0, + .menu_skip_mask = ~( + (1 << V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_OFF) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_VBR_VFR) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_VBR_CFR) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_CBR_VFR) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_CBR_CFR) + ), + .qmenu = mpeg_video_rate_control, + }, + { + .id = V4L2_CID_MPEG_VIDEO_BITRATE, + .name = "Bit Rate", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = MIN_BIT_RATE, + .maximum = MAX_BIT_RATE, + .default_value = DEFAULT_BIT_RATE, + .step = BIT_RATE_STEP, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + .name = "Entropy Mode", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC, + .maximum = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, + .default_value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC, + .step = 0, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC) | + (1 << V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC) + ), + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL, + .name = "CABAC Model", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0, + .maximum = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_1, + .default_value = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0, + .step = 0, + .menu_skip_mask = ~( + (1 << V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_1) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_2) + ), + .qmenu = h264_video_entropy_cabac_model, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .name = "H264 Profile", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .maximum = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + .default_value = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .step = 1, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + .name = "H264 Level", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + .maximum = V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + .default_value = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + .step = 1, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_ROTATION, + .name = "Rotation", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_NONE, + .maximum = V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_270, + .default_value = V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_NONE, + .step = 0, + .menu_skip_mask = ~( + (1 << V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_NONE) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_90) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_180) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_270) + ), + .qmenu = mpeg_video_rotation, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, + .name = "I Frame Quantization", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = 51, + .default_value = I_FRAME_QP, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, + .name = "P Frame Quantization", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = 51, + .default_value = P_FRAME_QP, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, + .name = "B Frame Quantization", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = 51, + .default_value = B_FRAME_QP, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, + .name = "Slice Mode", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE, + .maximum = V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES, + .default_value = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE, + .step = 1, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES, + .name = "Slice Byte Size", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = MIN_SLICE_BYTE_SIZE, + .maximum = MAX_SLICE_BYTE_SIZE, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB, + .name = "Slice MB Size", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = MAX_SLICE_MB_SIZE, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_MODE, + .name = "Intra Refresh Mode", + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_NONE, + .maximum = V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_RANDOM, + .default_value = V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_NONE, + .step = 0, + .menu_skip_mask = ~( + (1 << V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_NONE) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_CYCLIC) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_ADAPTIVE) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_CYCLIC_ADAPTIVE) | + (1 << V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_RANDOM) + ), + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_AIR_MBS, + .name = "Intra Refresh AIR MBS", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = MAX_INTRA_REFRESH_MBS, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_AIR_REF, + .name = "Intra Refresh AIR REF", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = MAX_INTRA_REFRESH_MBS, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDC_VIDEO_CIR_MBS, + .name = "Intra Refresh CIR MBS", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = MAX_INTRA_REFRESH_MBS, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, + .name = "H.264 Loop Filter Alpha Offset", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = -6, + .maximum = 6, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, + .name = "H.264 Loop Filter Beta Offset", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = -6, + .maximum = 6, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + .qmenu = NULL, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, + .name = "H.264 Loop Filter Mode", + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED, + .maximum = L_MODE, + .default_value = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED, + .step = 1, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED) | + (1 << V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED) | + (1 << L_MODE) + ), + }, +}; + +#define NUM_CTRLS ARRAY_SIZE(msm_venc_ctrls) + +static u32 get_frame_size_nv12(int plane, u32 height, u32 width) +{ + return ((height + 31) & (~31)) * ((width + 31) & (~31)) * 3/2; +} + +static u32 get_frame_size_nv21(int plane, u32 height, u32 width) +{ + return height * width * 2; +} + +static u32 get_frame_size_compressed(int plane, u32 height, u32 width) +{ + return ((height + 31) & (~31)) * ((width + 31) & (~31)) * 3/2; +} + +static struct hal_quantization + venc_quantization = {I_FRAME_QP, P_FRAME_QP, B_FRAME_QP}; +static struct hal_intra_period + venc_intra_period = {2*DEFAULT_FRAME_RATE-1 , 0}; +static struct hal_profile_level + venc_profile_level = {V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + V4L2_MPEG_VIDEO_H264_LEVEL_1_0}; +static struct hal_h264_entropy_control + venc_h264_entropy_control = {V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC, + V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0}; +static struct hal_multi_slice_control + venc_multi_slice_control = {V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE , + 0}; + +static const struct msm_vidc_format venc_formats[] = { + { + .name = "YCbCr Semiplanar 4:2:0", + .description = "Y/CbCr 4:2:0", + .fourcc = V4L2_PIX_FMT_NV12, + .num_planes = 1, + .get_frame_size = get_frame_size_nv12, + .type = OUTPUT_PORT, + }, + { + .name = "Mpeg4", + .description = "Mpeg4 compressed format", + .fourcc = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = CAPTURE_PORT, + }, + { + .name = "H263", + .description = "H263 compressed format", + .fourcc = V4L2_PIX_FMT_H263, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = CAPTURE_PORT, + }, + { + .name = "H264", + .description = "H264 compressed format", + .fourcc = V4L2_PIX_FMT_H264, + .num_planes = 1, + .get_frame_size = get_frame_size_compressed, + .type = CAPTURE_PORT, + }, + { + .name = "YCrCb Semiplanar 4:2:0", + .description = "Y/CrCb 4:2:0", + .fourcc = V4L2_PIX_FMT_NV21, + .num_planes = 1, + .get_frame_size = get_frame_size_nv21, + .type = OUTPUT_PORT, + }, +}; + +static int msm_venc_queue_setup(struct vb2_queue *q, + const struct v4l2_format *fmt, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + void *alloc_ctxs[]) +{ + int i, rc = 0; + struct msm_vidc_inst *inst; + struct hal_frame_size frame_sz; + unsigned long flags; + if (!q || !q->drv_priv) { + pr_err("Invalid input, q = %p\n", q); + return -EINVAL; + } + inst = q->drv_priv; + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + *num_planes = 1; + if (*num_buffers < MIN_NUM_OUTPUT_BUFFERS || + *num_buffers > MAX_NUM_OUTPUT_BUFFERS) + *num_buffers = MIN_NUM_OUTPUT_BUFFERS; + for (i = 0; i < *num_planes; i++) { + sizes[i] = inst->fmts[OUTPUT_PORT]->get_frame_size( + i, inst->height, inst->width); + } + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + rc = msm_comm_try_state(inst, MSM_VIDC_OPEN_DONE); + if (rc) { + pr_err("Failed to open instance\n"); + break; + } + frame_sz.buffer_type = HAL_BUFFER_INPUT; + frame_sz.width = inst->width; + frame_sz.height = inst->height; + pr_debug("width = %d, height = %d\n", + frame_sz.width, frame_sz.height); + rc = vidc_hal_session_set_property((void *)inst->session, + HAL_PARAM_FRAME_SIZE, &frame_sz); + if (rc) { + pr_err("Failed to set hal property for framesize\n"); + break; + } + rc = msm_comm_try_get_bufreqs(inst); + if (rc) { + pr_err("Failed to get buffer requirements: %d\n", rc); + break; + } + *num_planes = 1; + spin_lock_irqsave(&inst->lock, flags); + *num_buffers = inst->buff_req.buffer[0].buffer_count_actual; + spin_unlock_irqrestore(&inst->lock, flags); + pr_debug("size = %d, alignment = %d, count = %d\n", + inst->buff_req.buffer[0].buffer_size, + inst->buff_req.buffer[0].buffer_alignment, + inst->buff_req.buffer[0].buffer_count_actual); + for (i = 0; i < *num_planes; i++) { + sizes[i] = inst->fmts[CAPTURE_PORT]->get_frame_size( + i, inst->height, inst->width); + } + + break; + default: + pr_err("Invalid q type = %d\n", q->type); + rc = -EINVAL; + break; + } + return rc; +} + +static inline int start_streaming(struct msm_vidc_inst *inst) +{ + int rc = 0; + unsigned long flags; + struct vb2_buf_entry *temp; + struct list_head *ptr, *next; + rc = msm_comm_set_scratch_buffers(inst); + if (rc) { + pr_err("Failed to set scratch buffers: %d\n", rc); + goto fail_start; + } + rc = msm_comm_try_state(inst, MSM_VIDC_START_DONE); + if (rc) { + pr_err("Failed to move inst: %p to start done state\n", + inst); + goto fail_start; + } + spin_lock_irqsave(&inst->lock, flags); + if (!list_empty(&inst->pendingq)) { + list_for_each_safe(ptr, next, &inst->pendingq) { + temp = list_entry(ptr, struct vb2_buf_entry, list); + rc = msm_comm_qbuf(temp->vb); + if (rc) { + pr_err("Failed to qbuf to hardware\n"); + break; + } + list_del(&temp->list); + kfree(temp); + } + } + spin_unlock_irqrestore(&inst->lock, flags); + return rc; +fail_start: + return rc; +} + +static int msm_venc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct msm_vidc_inst *inst; + int rc = 0; + if (!q || !q->drv_priv) { + pr_err("Invalid input, q = %p\n", q); + return -EINVAL; + } + inst = q->drv_priv; + pr_debug("Streamon called on: %d capability\n", q->type); + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (inst->vb2_bufq[CAPTURE_PORT].streaming) + rc = start_streaming(inst); + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (inst->vb2_bufq[OUTPUT_PORT].streaming) + rc = start_streaming(inst); + break; + default: + pr_err("Q-type is not supported: %d\n", q->type); + rc = -EINVAL; + break; + } + return rc; +} + +static int msm_venc_stop_streaming(struct vb2_queue *q) +{ + struct msm_vidc_inst *inst; + int rc = 0; + if (!q || !q->drv_priv) { + pr_err("Invalid input, q = %p\n", q); + return -EINVAL; + } + inst = q->drv_priv; + pr_debug("Streamoff called on: %d capability\n", q->type); + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + rc = msm_comm_try_state(inst, MSM_VIDC_CLOSE_DONE); + break; + default: + pr_err("Q-type is not supported: %d\n", q->type); + rc = -EINVAL; + break; + } + if (rc) + pr_err("Failed to move inst: %p, cap = %d to state: %d\n", + inst, q->type, MSM_VIDC_CLOSE_DONE); + return rc; +} + +static void msm_venc_buf_queue(struct vb2_buffer *vb) +{ + int rc; + rc = msm_comm_qbuf(vb); + if (rc) + pr_err("Failed to queue buffer: %d\n", rc); +} + +static const struct vb2_ops msm_venc_vb2q_ops = { + .queue_setup = msm_venc_queue_setup, + .start_streaming = msm_venc_start_streaming, + .buf_queue = msm_venc_buf_queue, + .stop_streaming = msm_venc_stop_streaming, +}; + +const struct vb2_ops *msm_venc_get_vb2q_ops(void) +{ + return &msm_venc_vb2q_ops; +} + +static int msm_venc_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + + int rc = 0; + struct v4l2_control control; + struct hal_frame_rate frame_rate; + struct hal_request_iframe request_iframe; + struct hal_bitrate bitrate; + struct hal_profile_level profile_level; + struct hal_h264_entropy_control h264_entropy_control; + struct hal_quantization quantization; + struct hal_intra_period intra_period; + struct hal_idr_period idr_period; + struct hal_operations operations; + struct hal_intra_refresh intra_refresh; + struct hal_multi_slice_control multi_slice_control; + struct hal_h264_db_control h264_db_control; + u32 control_idx = 0; + u32 property_id = 0; + u32 property_val = 0; + void *pdata; + struct msm_vidc_inst *inst = container_of(ctrl->handler, + struct msm_vidc_inst, ctrl_handler); + + control.id = ctrl->id; + control.value = ctrl->val; + + switch (control.id) { + case V4L2_CID_MPEG_VIDC_VIDEO_FRAME_RATE: + property_id = + HAL_CONFIG_FRAME_RATE; + frame_rate.frame_rate = control.value; + frame_rate.buffer_type = HAL_BUFFER_OUTPUT; + pdata = &frame_rate; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_IDR_PERIOD: + property_id = + HAL_CONFIG_VENC_IDR_PERIOD; + idr_period.idr_period = control.value; + pdata = &idr_period; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_NUM_P_FRAMES: + property_id = + HAL_CONFIG_VENC_INTRA_PERIOD; + intra_period.pframes = control.value; + venc_intra_period.pframes = control.value; + intra_period.bframes = venc_intra_period.bframes; + pdata = &intra_period; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_NUM_B_FRAMES: + property_id = + HAL_CONFIG_VENC_INTRA_PERIOD; + intra_period.bframes = control.value; + venc_intra_period.bframes = control.value; + intra_period.pframes = venc_intra_period.pframes; + pdata = &intra_period; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_REQUEST_IFRAME: + property_id = + HAL_CONFIG_VENC_REQUEST_IFRAME; + request_iframe.enable = control.value; + pdata = &request_iframe; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL: + property_id = + HAL_PARAM_VENC_RATE_CONTROL; + property_val = control.value; + pdata = &property_val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + property_id = + HAL_CONFIG_VENC_TARGET_BITRATE; + bitrate.bit_rate = control.value; + pdata = &bitrate; + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + property_id = + HAL_PARAM_VENC_H264_ENTROPY_CONTROL; + h264_entropy_control.entropy_mode = control.value; + venc_h264_entropy_control.entropy_mode = control.value; + h264_entropy_control.cabac_model = + venc_h264_entropy_control.cabac_model; + pdata = &h264_entropy_control; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL: + property_id = + HAL_PARAM_VENC_H264_ENTROPY_CONTROL; + h264_entropy_control.cabac_model = control.value; + venc_h264_entropy_control.cabac_model = control.value; + h264_entropy_control.entropy_mode = + venc_h264_entropy_control.entropy_mode; + pdata = &h264_entropy_control; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + property_id = + HAL_PARAM_PROFILE_LEVEL_CURRENT; + + switch (control.value) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + control.value = HAL_H264_PROFILE_BASELINE; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + control.value = HAL_H264_PROFILE_MAIN; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED: + control.value = HAL_H264_PROFILE_EXTENDED; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + control.value = HAL_H264_PROFILE_HIGH; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10: + control.value = HAL_H264_PROFILE_HIGH10; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422: + control.value = HAL_H264_PROFILE_HIGH422; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE: + control.value = HAL_H264_PROFILE_HIGH444; + break; + default: + break; + } + profile_level.profile = control.value; + venc_profile_level.profile = control.value; + profile_level.level = venc_profile_level.level; + pdata = &profile_level; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + property_id = + HAL_PARAM_PROFILE_LEVEL_CURRENT; + + switch (control.value) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + control.value = HAL_H264_LEVEL_1; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + control.value = HAL_H264_LEVEL_1b; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + control.value = HAL_H264_LEVEL_11; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + control.value = HAL_H264_LEVEL_12; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + control.value = HAL_H264_LEVEL_13; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + control.value = HAL_H264_LEVEL_2; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + control.value = HAL_H264_LEVEL_21; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + control.value = HAL_H264_LEVEL_22; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + control.value = HAL_H264_LEVEL_3; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + control.value = HAL_H264_LEVEL_31; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + control.value = HAL_H264_LEVEL_32; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + control.value = HAL_H264_LEVEL_4; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + control.value = HAL_H264_LEVEL_41; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + control.value = HAL_H264_LEVEL_42; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + control.value = HAL_H264_LEVEL_3; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + control.value = HAL_H264_LEVEL_51; + break; + default: + break; + } + profile_level.level = control.value; + venc_profile_level.level = control.value; + profile_level.profile = venc_profile_level.profile; + pdata = &profile_level; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_ROTATION: + property_id = + HAL_CONFIG_VPE_OPERATIONS; + operations.rotate = control.value; + pdata = &operations; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + property_id = + HAL_PARAM_VENC_SESSION_QP; + quantization.qpi = control.value; + venc_quantization.qpi = control.value; + quantization.qpp = venc_quantization.qpp; + quantization.qpb = venc_quantization.qpb; + pdata = &quantization; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + property_id = + HAL_PARAM_VENC_SESSION_QP; + quantization.qpp = control.value; + venc_quantization.qpp = control.value; + quantization.qpi = venc_quantization.qpi; + quantization.qpb = venc_quantization.qpb; + pdata = &quantization; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + property_id = + HAL_PARAM_VENC_SESSION_QP; + quantization.qpb = control.value; + venc_quantization.qpb = control.value; + quantization.qpi = venc_quantization.qpi; + quantization.qpp = venc_quantization.qpp; + pdata = &quantization; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + property_id = + HAL_PARAM_VENC_MULTI_SLICE_CONTROL; + multi_slice_control.multi_slice = control.value; + venc_multi_slice_control.multi_slice = control.value; + multi_slice_control.slice_size = + venc_multi_slice_control.slice_size; + pdata = &multi_slice_control; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + property_id = + HAL_PARAM_VENC_MULTI_SLICE_CONTROL; + multi_slice_control.multi_slice = + venc_multi_slice_control.multi_slice; + multi_slice_control.slice_size = control.value; + venc_multi_slice_control.slice_size = control.value; + pdata = &multi_slice_control; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_MODE: + property_id = + HAL_PARAM_VENC_INTRA_REFRESH; + intra_refresh.mode = control.value; + pdata = &intra_refresh; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_AIR_MBS: + property_id = + HAL_PARAM_VENC_INTRA_REFRESH; + intra_refresh.air_mbs = control.value; + pdata = &intra_refresh; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_AIR_REF: + property_id = + HAL_PARAM_VENC_INTRA_REFRESH; + intra_refresh.air_ref = control.value; + pdata = &intra_refresh; + break; + case V4L2_CID_MPEG_VIDC_VIDEO_CIR_MBS: + property_id = + HAL_PARAM_VENC_INTRA_REFRESH; + intra_refresh.cir_mbs = control.value; + pdata = &intra_refresh; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + property_id = + HAL_PARAM_VENC_H264_DEBLOCK_CONTROL; + h264_db_control.mode = control.value; + pdata = &h264_db_control; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA: + property_id = + HAL_PARAM_VENC_H264_DEBLOCK_CONTROL; + h264_db_control.slice_alpha_offset = control.value; + pdata = &h264_db_control; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA: + property_id = + HAL_PARAM_VENC_H264_DEBLOCK_CONTROL; + h264_db_control.slicebeta_offset = control.value; + pdata = &h264_db_control; + default: + break; + } + if (property_id) { + pr_debug("Control: HAL property=%d,ctrl_id=%d,ctrl_value=%d\n", + property_id, + msm_venc_ctrls[control_idx].id, + control.value); + rc = vidc_hal_session_set_property((void *)inst->session, + property_id, pdata); + } + if (rc) + pr_err("Failed to set hal property for framesize\n"); + return rc; +} +static int msm_venc_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + return 0; +} + +static const struct v4l2_ctrl_ops msm_venc_ctrl_ops = { + + .s_ctrl = msm_venc_op_s_ctrl, + .g_volatile_ctrl = msm_venc_op_g_volatile_ctrl, +}; + +const struct v4l2_ctrl_ops *msm_venc_get_ctrl_ops(void) +{ + return &msm_venc_ctrl_ops; +} + +int msm_venc_inst_init(struct msm_vidc_inst *inst) +{ + int rc = 0; + if (!inst) { + pr_err("Invalid input = %p\n", inst); + return -EINVAL; + } + inst->fmts[CAPTURE_PORT] = &venc_formats[1]; + inst->fmts[OUTPUT_PORT] = &venc_formats[0]; + inst->height = DEFAULT_HEIGHT; + inst->width = DEFAULT_WIDTH; + return rc; +} + +int msm_venc_s_ctrl(struct msm_vidc_inst *inst, struct v4l2_control *ctrl) +{ + return v4l2_s_ctrl(NULL, &inst->ctrl_handler, ctrl); +} +int msm_venc_g_ctrl(struct msm_vidc_inst *inst, struct v4l2_control *ctrl) +{ + return v4l2_g_ctrl(&inst->ctrl_handler, ctrl); +} + +int msm_venc_querycap(struct msm_vidc_inst *inst, struct v4l2_capability *cap) +{ + if (!inst || !cap) { + pr_err("Invalid input, inst = %p, cap = %p\n", inst, cap); + return -EINVAL; + } + strlcpy(cap->driver, MSM_VIDC_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->card, MSM_VENC_DVC_NAME, sizeof(cap->card)); + cap->bus_info[0] = 0; + cap->version = MSM_VIDC_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_VIDEO_OUTPUT_MPLANE | + V4L2_CAP_STREAMING; + memset(cap->reserved, 0, sizeof(cap->reserved)); + return 0; +} + +int msm_venc_enum_fmt(struct msm_vidc_inst *inst, struct v4l2_fmtdesc *f) +{ + const struct msm_vidc_format *fmt = NULL; + int rc = 0; + if (!inst || !f) { + pr_err("Invalid input, inst = %p, f = %p\n", inst, f); + return -EINVAL; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt = msm_comm_get_pixel_fmt_index(venc_formats, + ARRAY_SIZE(venc_formats), f->index, CAPTURE_PORT); + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fmt = msm_comm_get_pixel_fmt_index(venc_formats, + ARRAY_SIZE(venc_formats), f->index, OUTPUT_PORT); + f->flags = V4L2_FMT_FLAG_COMPRESSED; + } + + memset(f->reserved, 0 , sizeof(f->reserved)); + if (fmt) { + strlcpy(f->description, fmt->description, + sizeof(f->description)); + f->pixelformat = fmt->fourcc; + } else { + pr_err("No more formats found\n"); + rc = -EINVAL; + } + return rc; +} + +int msm_venc_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) +{ + const struct msm_vidc_format *fmt = NULL; + int rc = 0; + int i; + if (!inst || !f) { + pr_err("Invalid input, inst = %p, format = %p\n", inst, f); + return -EINVAL; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt = msm_comm_get_pixel_fmt_fourcc(venc_formats, + ARRAY_SIZE(venc_formats), f->fmt.pix_mp.pixelformat, + CAPTURE_PORT); + if (fmt && fmt->type != CAPTURE_PORT) { + pr_err("Format: %d not supported on CAPTURE port\n", + f->fmt.pix_mp.pixelformat); + rc = -EINVAL; + goto exit; + } + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + inst->width = f->fmt.pix_mp.width; + inst->height = f->fmt.pix_mp.height; + fmt = msm_comm_get_pixel_fmt_fourcc(venc_formats, + ARRAY_SIZE(venc_formats), f->fmt.pix_mp.pixelformat, + OUTPUT_PORT); + if (fmt && fmt->type != OUTPUT_PORT) { + pr_err("Format: %d not supported on OUTPUT port\n", + f->fmt.pix_mp.pixelformat); + rc = -EINVAL; + goto exit; + } + } + + if (fmt) { + for (i = 0; i < fmt->num_planes; ++i) { + f->fmt.pix_mp.plane_fmt[i].sizeimage = + fmt->get_frame_size(i, f->fmt.pix_mp.height, + f->fmt.pix_mp.width); + } + inst->fmts[fmt->type] = fmt; + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + rc = msm_comm_try_state(inst, MSM_VIDC_OPEN_DONE); + if (rc) { + pr_err("Failed to open instance\n"); + goto exit; + } + } + } else { + pr_err("Buf type not recognized, type = %d\n", + f->type); + rc = -EINVAL; + } +exit: + return rc; +} + +int msm_venc_g_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) +{ + const struct msm_vidc_format *fmt = NULL; + int rc = 0; + int i; + if (!inst || !f) { + pr_err("Invalid input, inst = %p, format = %p\n", inst, f); + return -EINVAL; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + fmt = inst->fmts[CAPTURE_PORT]; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fmt = inst->fmts[OUTPUT_PORT]; + + if (fmt) { + f->fmt.pix_mp.pixelformat = fmt->fourcc; + f->fmt.pix_mp.height = inst->height; + f->fmt.pix_mp.width = inst->width; + for (i = 0; i < fmt->num_planes; ++i) { + f->fmt.pix_mp.plane_fmt[i].sizeimage = + fmt->get_frame_size(i, inst->height, inst->width); + } + } else { + pr_err("Buf type not recognized, type = %d\n", + f->type); + rc = -EINVAL; + } + return rc; +} + +int msm_venc_reqbufs(struct msm_vidc_inst *inst, struct v4l2_requestbuffers *b) +{ + struct vb2_queue *q = NULL; + int rc = 0; + if (!inst || !b) { + pr_err("Invalid input, inst = %p, buffer = %p\n", inst, b); + return -EINVAL; + } + q = msm_comm_get_vb2q(inst, b->type); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", b->type); + return -EINVAL; + } + + rc = vb2_reqbufs(q, b); + if (rc) + pr_err("Failed to get reqbufs, %d\n", rc); + return rc; +} + +int msm_venc_prepare_buf(struct msm_vidc_inst *inst, + struct v4l2_buffer *b) +{ + int rc = 0; + int i; + struct vidc_buffer_addr_info buffer_info; + + switch (b->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + for (i = 0; i < b->length; i++) { + pr_debug("device_addr = %ld, size = %d\n", + b->m.planes[i].m.userptr, + b->m.planes[i].length); + buffer_info.buffer_size = b->m.planes[i].length; + buffer_info.buffer_type = HAL_BUFFER_OUTPUT; + buffer_info.num_buffers = 1; + buffer_info.align_device_addr = + b->m.planes[i].m.userptr; + buffer_info.extradata_size = 0; + buffer_info.extradata_addr = 0; + rc = vidc_hal_session_set_buffers((void *)inst->session, + &buffer_info); + if (rc) + pr_err("vidc_hal_session_set_buffers failed"); + } + break; + default: + pr_err("Buffer type not recognized: %d\n", b->type); + break; + } + return rc; +} + +int msm_venc_qbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b) +{ + struct vb2_queue *q = NULL; + int rc = 0; + q = msm_comm_get_vb2q(inst, b->type); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", b->type); + return -EINVAL; + } + rc = vb2_qbuf(q, b); + if (rc) + pr_err("Failed to qbuf, %d\n", rc); + return rc; +} + +int msm_venc_dqbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b) +{ + struct vb2_queue *q = NULL; + int rc = 0; + q = msm_comm_get_vb2q(inst, b->type); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", b->type); + return -EINVAL; + } + rc = vb2_dqbuf(q, b, true); + if (rc) + pr_err("Failed to qbuf, %d\n", rc); + return rc; +} + +int msm_venc_streamon(struct msm_vidc_inst *inst, enum v4l2_buf_type i) +{ + int rc = 0; + struct vb2_queue *q; + q = msm_comm_get_vb2q(inst, i); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", i); + return -EINVAL; + } + pr_debug("Calling streamon\n"); + rc = vb2_streamon(q, i); + if (rc) + pr_err("streamon failed on port: %d\n", i); + return rc; +} + +int msm_venc_streamoff(struct msm_vidc_inst *inst, enum v4l2_buf_type i) +{ + int rc = 0; + struct vb2_queue *q; + q = msm_comm_get_vb2q(inst, i); + if (!q) { + pr_err("Failed to find buffer queue for type = %d\n", i); + return -EINVAL; + } + pr_debug("Calling streamoff\n"); + rc = vb2_streamoff(q, i); + if (rc) + pr_err("streamoff failed on port: %d\n", i); + return rc; +} + +int msm_venc_ctrl_init(struct msm_vidc_inst *inst) +{ + + int idx = 0; + struct v4l2_ctrl_config ctrl_cfg; + int ret_val = 0; + ret_val = v4l2_ctrl_handler_init(&inst->ctrl_handler, NUM_CTRLS); + if (ret_val) { + pr_err("CTRL ERR: Control handler init failed, %d\n", + inst->ctrl_handler.error); + return ret_val; + } + + for (; idx < NUM_CTRLS; idx++) { + if (IS_PRIV_CTRL(msm_venc_ctrls[idx].id)) { + ctrl_cfg.def = msm_venc_ctrls[idx].default_value; + ctrl_cfg.flags = 0; + ctrl_cfg.id = msm_venc_ctrls[idx].id; + ctrl_cfg.max = msm_venc_ctrls[idx].maximum; + ctrl_cfg.min = msm_venc_ctrls[idx].minimum; + ctrl_cfg.menu_skip_mask = + msm_venc_ctrls[idx].menu_skip_mask; + ctrl_cfg.name = msm_venc_ctrls[idx].name; + ctrl_cfg.ops = &msm_venc_ctrl_ops; + ctrl_cfg.step = msm_venc_ctrls[idx].step; + ctrl_cfg.type = msm_venc_ctrls[idx].type; + ctrl_cfg.qmenu = msm_venc_ctrls[idx].qmenu; + v4l2_ctrl_new_custom(&inst->ctrl_handler, + &ctrl_cfg, NULL); + } else { + if (msm_venc_ctrls[idx].type == V4L2_CTRL_TYPE_MENU) { + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, + &msm_venc_ctrl_ops, + msm_venc_ctrls[idx].id, + msm_venc_ctrls[idx].maximum, + msm_venc_ctrls[idx].menu_skip_mask, + msm_venc_ctrls[idx].default_value); + } else { + v4l2_ctrl_new_std(&inst->ctrl_handler, + &msm_venc_ctrl_ops, + msm_venc_ctrls[idx].id, + msm_venc_ctrls[idx].minimum, + msm_venc_ctrls[idx].maximum, + msm_venc_ctrls[idx].step, + msm_venc_ctrls[idx].default_value); + } + } + } + ret_val = inst->ctrl_handler.error; + if (ret_val) + pr_err("CTRL ERR: Error adding ctrls to ctrl handle, %d\n", + inst->ctrl_handler.error); + return ret_val; +} diff --git a/drivers/media/video/msm_vidc/msm_venc.h b/drivers/media/video/msm_vidc/msm_venc.h new file mode 100644 index 0000000000000000000000000000000000000000..4a156dd3c601d7f1a9ad1752be0b0d6d498e351a --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_venc.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VENC_H_ +#define _MSM_VENC_H_ + +#include +#include "msm_vidc_internal.h" + +int msm_venc_inst_init(struct msm_vidc_inst *inst); +int msm_venc_ctrl_init(struct msm_vidc_inst *inst); +int msm_venc_querycap(void *instance, struct v4l2_capability *cap); +int msm_venc_enum_fmt(void *instance, struct v4l2_fmtdesc *f); +int msm_venc_s_fmt(void *instance, struct v4l2_format *f); +int msm_venc_g_fmt(void *instance, struct v4l2_format *f); +int msm_venc_s_ctrl(void *instance, struct v4l2_control *a); +int msm_venc_g_ctrl(void *instance, struct v4l2_control *a); +int msm_venc_reqbufs(void *instance, struct v4l2_requestbuffers *b); +int msm_venc_prepare_buf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_venc_qbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_venc_dqbuf(struct msm_vidc_inst *inst, struct v4l2_buffer *b); +int msm_venc_streamon(struct msm_vidc_inst *inst, enum v4l2_buf_type i); +int msm_venc_streamoff(struct msm_vidc_inst *inst, enum v4l2_buf_type i); +struct vb2_ops *msm_venc_get_vb2q_ops(void); + +#endif diff --git a/drivers/media/video/msm_vidc/msm_vidc.c b/drivers/media/video/msm_vidc/msm_vidc.c new file mode 100644 index 0000000000000000000000000000000000000000..a11f8170eff7a9471518426d8ebc863425117d59 --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_vidc.c @@ -0,0 +1,348 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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_vidc_internal.h" +#include "msm_vdec.h" +#include "msm_venc.h" +#include "msm_vidc_common.h" +#include "msm_smem.h" + +int msm_vidc_poll(void *instance, struct file *filp, + struct poll_table_struct *wait) +{ + int rc = 0; + struct msm_vidc_inst *inst = instance; + struct vb2_queue *outq = &inst->vb2_bufq[OUTPUT_PORT]; + struct vb2_queue *capq = &inst->vb2_bufq[CAPTURE_PORT]; + struct vb2_buffer *out_vb = NULL; + struct vb2_buffer *cap_vb = NULL; + unsigned long flags; + poll_wait(filp, &inst->event_handler.wait, wait); + if (v4l2_event_pending(&inst->event_handler)) + return POLLPRI; + if (!outq->streaming && !capq->streaming) { + pr_err("Returning POLLERR from here: %d, %d\n", + outq->streaming, capq->streaming); + return POLLERR; + } + poll_wait(filp, &inst->event_handler.wait, wait); + if (v4l2_event_pending(&inst->event_handler)) + return POLLPRI; + poll_wait(filp, &capq->done_wq, wait); + poll_wait(filp, &outq->done_wq, wait); + spin_lock_irqsave(&capq->done_lock, flags); + if (!list_empty(&capq->done_list)) + cap_vb = list_first_entry(&capq->done_list, struct vb2_buffer, + done_entry); + if (cap_vb && (cap_vb->state == VB2_BUF_STATE_DONE + || cap_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&capq->done_lock, flags); + spin_lock_irqsave(&outq->done_lock, flags); + if (!list_empty(&outq->done_list)) + out_vb = list_first_entry(&outq->done_list, struct vb2_buffer, + done_entry); + if (out_vb && (out_vb->state == VB2_BUF_STATE_DONE + || out_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLOUT | POLLWRNORM; + spin_unlock_irqrestore(&outq->done_lock, flags); + return rc; +} + +int msm_vidc_querycap(void *instance, struct v4l2_capability *cap) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_querycap(instance, cap); + else if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_querycap(instance, cap); + return -EINVAL; +} +int msm_vidc_enum_fmt(void *instance, struct v4l2_fmtdesc *f) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_enum_fmt(instance, f); + else if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_enum_fmt(instance, f); + return -EINVAL; +} +int msm_vidc_s_fmt(void *instance, struct v4l2_format *f) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_s_fmt(instance, f); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_s_fmt(instance, f); + return -EINVAL; +} +int msm_vidc_g_fmt(void *instance, struct v4l2_format *f) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_g_fmt(instance, f); + else if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_g_fmt(instance, f); + return -EINVAL; +} +int msm_vidc_s_ctrl(void *instance, struct v4l2_control *control) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_s_ctrl(instance, control); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_s_ctrl(instance, control); + return -EINVAL; +} +int msm_vidc_g_ctrl(void *instance, struct v4l2_control *control) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_g_ctrl(instance, control); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_g_ctrl(instance, control); + return -EINVAL; +} +int msm_vidc_reqbufs(void *instance, struct v4l2_requestbuffers *b) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_reqbufs(instance, b); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_reqbufs(instance, b); + return -EINVAL; +} + +int msm_vidc_prepare_buf(void *instance, struct v4l2_buffer *b) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_prepare_buf(instance, b); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_prepare_buf(instance, b); + return -EINVAL; +} + +int msm_vidc_release_buf(void *instance, struct v4l2_buffer *b) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_release_buf(instance, b); + return -EINVAL; +} + +int msm_vidc_qbuf(void *instance, struct v4l2_buffer *b) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_qbuf(instance, b); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_qbuf(instance, b); + return -EINVAL; +} + +int msm_vidc_dqbuf(void *instance, struct v4l2_buffer *b) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_dqbuf(instance, b); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_dqbuf(instance, b); + return -EINVAL; +} + +int msm_vidc_streamon(void *instance, enum v4l2_buf_type i) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_streamon(instance, i); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_streamon(instance, i); + return -EINVAL; +} + +int msm_vidc_streamoff(void *instance, enum v4l2_buf_type i) +{ + struct msm_vidc_inst *inst = instance; + if (inst->session_type == MSM_VIDC_DECODER) + return msm_vdec_streamoff(instance, i); + if (inst->session_type == MSM_VIDC_ENCODER) + return msm_venc_streamoff(instance, i); + return -EINVAL; +} + +void *vidc_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + return NULL; +} + +void vidc_put_userptr(void *buf_priv) +{ +} + +static const struct vb2_mem_ops msm_vidc_vb2_mem_ops = { + .get_userptr = vidc_get_userptr, + .put_userptr = vidc_put_userptr, +}; + +static inline int vb2_bufq_init(struct msm_vidc_inst *inst, + enum v4l2_buf_type type, enum session_type sess) +{ + struct vb2_queue *q = NULL; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + q = &inst->vb2_bufq[CAPTURE_PORT]; + } else if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + q = &inst->vb2_bufq[OUTPUT_PORT]; + } else { + pr_err("buf_type = %d not recognised\n", type); + return -EINVAL; + } + q->type = type; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->io_flags = 0; + if (sess == MSM_VIDC_DECODER) + q->ops = msm_vdec_get_vb2q_ops(); + else if (sess == MSM_VIDC_ENCODER) + q->ops = msm_venc_get_vb2q_ops(); + q->mem_ops = &msm_vidc_vb2_mem_ops; + q->drv_priv = inst; + return vb2_queue_init(q); +} + +int msm_vidc_open(void *vidc_inst, int core_id, int session_type) +{ + struct msm_vidc_inst *inst = (struct msm_vidc_inst *)vidc_inst; + struct msm_vidc_core *core = NULL; + unsigned long flags; + int rc = 0; + int i = 0; + if (core_id >= MSM_VIDC_CORES_MAX || + session_type >= MSM_VIDC_MAX_DEVICES) { + pr_err("Invalid input, core_id = %d, session = %d\n", + core_id, session_type); + goto err_invalid_core; + } + core = get_vidc_core(core_id); + if (!core) { + pr_err("Failed to find core for core_id = %d\n", core_id); + goto err_invalid_core; + } + + mutex_init(&inst->sync_lock); + spin_lock_init(&inst->lock); + inst->session_type = session_type; + INIT_LIST_HEAD(&inst->pendingq); + INIT_LIST_HEAD(&inst->internalbufs); + inst->state = MSM_VIDC_CORE_UNINIT_DONE; + inst->core = core; + for (i = SESSION_MSG_INDEX(SESSION_MSG_START); + i <= SESSION_MSG_INDEX(SESSION_MSG_END); i++) { + init_completion(&inst->completions[i]); + } + inst->mem_client = msm_smem_new_client(SMEM_ION); + if (!inst->mem_client) { + pr_err("Failed to create memory client\n"); + goto fail_mem_client; + } + if (session_type == MSM_VIDC_DECODER) { + msm_vdec_inst_init(inst); + msm_vdec_ctrl_init(inst); + } else if (session_type == MSM_VIDC_ENCODER) { + msm_venc_inst_init(inst); + msm_venc_ctrl_init(inst); + } + rc = vb2_bufq_init(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + session_type); + if (rc) { + pr_err("Failed to initialize vb2 queue on capture port\n"); + goto fail_init; + } + rc = vb2_bufq_init(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + session_type); + if (rc) { + pr_err("Failed to initialize vb2 queue on capture port\n"); + goto fail_init; + } + rc = msm_comm_try_state(inst, MSM_VIDC_CORE_INIT); + if (rc) { + pr_err("Failed to move video instance to init state\n"); + goto fail_init; + } + spin_lock_irqsave(&core->lock, flags); + list_add_tail(&inst->list, &core->instances); + spin_unlock_irqrestore(&core->lock, flags); + return rc; +fail_init: + msm_smem_delete_client(inst->mem_client); +fail_mem_client: + kfree(inst); + inst = NULL; +err_invalid_core: + return rc; +} + +static void cleanup_instance(struct msm_vidc_inst *inst) +{ + unsigned long flags; + struct list_head *ptr, *next; + struct vb2_buf_entry *entry; + struct internal_buf *buf; + if (inst) { + spin_lock_irqsave(&inst->lock, flags); + if (!list_empty(&inst->pendingq)) { + list_for_each_safe(ptr, next, &inst->pendingq) { + entry = list_entry(ptr, struct vb2_buf_entry, + list); + list_del(&entry->list); + kfree(entry); + } + } + if (!list_empty(&inst->internalbufs)) { + list_for_each_safe(ptr, next, &inst->internalbufs) { + buf = list_entry(ptr, struct internal_buf, + list); + list_del(&buf->list); + msm_smem_free(inst->mem_client, buf->handle); + kfree(buf); + } + } + spin_unlock_irqrestore(&inst->lock, flags); + msm_smem_delete_client(inst->mem_client); + } +} + +int msm_vidc_close(void *instance) +{ + struct msm_vidc_inst *inst = instance; + struct msm_vidc_inst *temp; + struct msm_vidc_core *core; + struct list_head *ptr, *next; + int rc = 0; + core = inst->core; + mutex_lock(&core->sync_lock); + list_for_each_safe(ptr, next, &core->instances) { + temp = list_entry(ptr, struct msm_vidc_inst, list); + if (temp == inst) + list_del(&inst->list); + } + mutex_unlock(&core->sync_lock); + rc = msm_comm_try_state(inst, MSM_VIDC_CORE_UNINIT); + if (rc) + pr_err("Failed to move video instance to uninit state\n"); + cleanup_instance(inst); + pr_debug("Closed the instance\n"); + return 0; +} diff --git a/drivers/media/video/msm_vidc/msm_vidc_common.c b/drivers/media/video/msm_vidc/msm_vidc_common.c new file mode 100644 index 0000000000000000000000000000000000000000..6b06943d20e23bdc575ce99b63c124433d0ff03e --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_vidc_common.c @@ -0,0 +1,1045 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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_vidc_common.h" +#include "vidc_hal_api.h" +#include "msm_smem.h" + +#define HW_RESPONSE_TIMEOUT 5000 + +#define IS_ALREADY_IN_STATE(__p, __d) ({\ + int __rc = (__p >= __d);\ + __rc; \ +}) + +struct msm_vidc_core *get_vidc_core(int core_id) +{ + struct msm_vidc_core *core; + int found = 0; + unsigned long flags; + if (core_id > MSM_VIDC_CORES_MAX) { + pr_err("Core id = %d is greater than max = %d\n", + core_id, MSM_VIDC_CORES_MAX); + return NULL; + } + spin_lock_irqsave(&vidc_driver->lock, flags); + list_for_each_entry(core, &vidc_driver->cores, list) { + if (core && core->id == core_id) + found = 1; + break; + } + spin_unlock_irqrestore(&vidc_driver->lock, flags); + if (found) + return core; + return NULL; +} + +const struct msm_vidc_format *msm_comm_get_pixel_fmt_index( + const struct msm_vidc_format fmt[], int size, int index, int fmt_type) +{ + int i, k = 0; + if (!fmt || index < 0) { + pr_err("Invalid inputs, fmt = %p, index = %d\n", + fmt, index); + return NULL; + } + for (i = 0; i < size; i++) { + if (fmt[i].type != fmt_type) + continue; + if (k == index) + break; + k++; + } + if (i == size) { + pr_err("Format not found\n"); + return NULL; + } + return &fmt[i]; +} +const struct msm_vidc_format *msm_comm_get_pixel_fmt_fourcc( + const struct msm_vidc_format fmt[], int size, int fourcc, int fmt_type) +{ + int i; + if (!fmt) { + pr_err("Invalid inputs, fmt = %p\n", fmt); + return NULL; + } + for (i = 0; i < size; i++) { + if (fmt[i].fourcc == fourcc) + break; + } + if (i == size) { + pr_err("Format not found\n"); + return NULL; + } + return &fmt[i]; +} + +struct vb2_queue *msm_comm_get_vb2q( + struct msm_vidc_inst *inst, enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return &inst->vb2_bufq[CAPTURE_PORT]; + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return &inst->vb2_bufq[OUTPUT_PORT]; + return NULL; +} + +static void handle_sys_init_done(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_core *core; + struct vidc_hal_sys_init_done *sys_init_msg; + int index = SYS_MSG_INDEX(cmd); + if (!response) { + pr_err("Failed to get valid response for sys init\n"); + return; + } + core = get_vidc_core(response->device_id); + if (!core) { + pr_err("Wrong device_id received\n"); + return; + } + pr_debug("index = %d\n", index); + pr_debug("ptr = %p\n", &(core->completions[index])); + complete(&(core->completions[index])); + sys_init_msg = response->data; + if (!sys_init_msg) { + pr_err("sys_init_done message not proper\n"); + return; + } +} + +static inline void change_inst_state(struct msm_vidc_inst *inst, + enum instance_state state) +{ + unsigned long flags; + spin_lock_irqsave(&inst->lock, flags); + pr_debug("Moved inst: %p from state: %d to state: %d\n", + inst, inst->state, state); + inst->state = state; + spin_unlock_irqrestore(&inst->lock, flags); +} + +static int signal_session_msg_receipt(enum command_response cmd, + struct msm_vidc_inst *inst) +{ + if (!inst) { + pr_err("Invalid(%p) instance id\n", inst); + return -EINVAL; + } + complete(&inst->completions[SESSION_MSG_INDEX(cmd)]); + return 0; +} + +static int wait_for_sess_signal_receipt(struct msm_vidc_inst *inst, + enum command_response cmd) +{ + int rc = 0; + rc = wait_for_completion_interruptible_timeout( + &inst->completions[SESSION_MSG_INDEX(cmd)], + msecs_to_jiffies(HW_RESPONSE_TIMEOUT)); + if (!rc) { + pr_err("Wait interrupted or timeout: %d\n", rc); + rc = -EIO; + } else { + rc = 0; + } + return rc; +} + +static int wait_for_state(struct msm_vidc_inst *inst, + enum instance_state flipped_state, + enum instance_state desired_state, + enum command_response hal_cmd) +{ + int rc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, desired_state)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto err_same_state; + } + pr_debug("Waiting for hal_cmd: %d\n", hal_cmd); + rc = wait_for_sess_signal_receipt(inst, hal_cmd); + if (!rc) + change_inst_state(inst, desired_state); +err_same_state: + return rc; +} + +static void handle_session_init_done(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + struct video_device *vdev; + struct v4l2_event dqevent; + struct msm_vidc_core *core; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + signal_session_msg_receipt(cmd, inst); + core = inst->core; + if (inst->session_type == MSM_VIDC_ENCODER) + vdev = &core->vdev[MSM_VIDC_ENCODER].vdev; + else + vdev = &core->vdev[MSM_VIDC_DECODER].vdev; + dqevent.type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + dqevent.u.data[0] = (uint8_t)MSM_VIDC_OPEN_DONE; + v4l2_event_queue(vdev, &dqevent); + return; + } else { + pr_err("Failed to get valid response for session init\n"); + } +} + +static void handle_event_change(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + struct video_device *vdev; + struct v4l2_event dqevent; + struct msm_vidc_cb_event *event_notify; + struct msm_vidc_core *core; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + core = inst->core; + if (inst->session_type == MSM_VIDC_ENCODER) + vdev = &core->vdev[MSM_VIDC_ENCODER].vdev; + else + vdev = &core->vdev[MSM_VIDC_DECODER].vdev; + dqevent.type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + dqevent.u.data[0] = (uint8_t)MSM_VIDC_DECODER_EVENT_CHANGE; + event_notify = (struct msm_vidc_cb_event *) response->data; + inst->reconfig_height = event_notify->height; + inst->reconfig_width = event_notify->width; + inst->in_reconfig = true; + v4l2_event_queue(vdev, &dqevent); + return; + } else { + pr_err("Failed to get valid response for event_change\n"); + } +} + +static void handle_session_prop_info(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + unsigned long flags; + if (!response || !response->data) { + pr_err("Failed to get valid response for prop info\n"); + return; + } + inst = (struct msm_vidc_inst *)response->session_id; + spin_lock_irqsave(&inst->lock, flags); + memcpy(&inst->buff_req, response->data, + sizeof(struct buffer_requirements)); + spin_unlock_irqrestore(&inst->lock, flags); + signal_session_msg_receipt(cmd, inst); +} + +static void handle_load_resource_done(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + if (response) + inst = (struct msm_vidc_inst *)response->session_id; + else + pr_err("Failed to get valid response for load resource\n"); +} + +static void handle_start_done(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + struct video_device *vdev; + struct v4l2_event dqevent; + struct msm_vidc_core *core; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + signal_session_msg_receipt(cmd, inst); + core = inst->core; + if (inst->session_type == MSM_VIDC_ENCODER) + vdev = &core->vdev[MSM_VIDC_ENCODER].vdev; + else + vdev = &core->vdev[MSM_VIDC_DECODER].vdev; + dqevent.type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + dqevent.u.data[0] = (uint8_t)MSM_VIDC_START_DONE; + v4l2_event_queue(vdev, &dqevent); + } else { + pr_err("Failed to get valid response for start\n"); + } +} + +static void handle_stop_done(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + struct video_device *vdev; + struct v4l2_event dqevent; + struct msm_vidc_core *core; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + signal_session_msg_receipt(cmd, inst); + core = inst->core; + if (inst->session_type == MSM_VIDC_ENCODER) + vdev = &core->vdev[MSM_VIDC_ENCODER].vdev; + else + vdev = &core->vdev[MSM_VIDC_DECODER].vdev; + dqevent.type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + dqevent.u.data[0] = (uint8_t)MSM_VIDC_STOP_DONE; + v4l2_event_queue(vdev, &dqevent); + } else { + pr_err("Failed to get valid response for stop\n"); + } +} + +static void handle_release_res_done(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + signal_session_msg_receipt(cmd, inst); + } else { + pr_err("Failed to get valid response for release resource\n"); + } +} + +static void handle_session_flush(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + struct video_device *vdev; + struct v4l2_event dqevent; + struct msm_vidc_core *core; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + core = inst->core; + if (inst->session_type == MSM_VIDC_ENCODER) + vdev = &core->vdev[MSM_VIDC_ENCODER].vdev; + else + vdev = &core->vdev[MSM_VIDC_DECODER].vdev; + dqevent.type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + dqevent.u.data[0] = (uint8_t)MSM_VIDC_DECODER_FLUSH_DONE; + v4l2_event_queue(vdev, &dqevent); + } else { + pr_err("Failed to get valid response for flush\n"); + } +} + + +static void handle_session_close(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_cmd_done *response = data; + struct msm_vidc_inst *inst; + struct video_device *vdev; + struct v4l2_event dqevent; + struct msm_vidc_core *core; + if (response) { + inst = (struct msm_vidc_inst *)response->session_id; + signal_session_msg_receipt(cmd, inst); + core = inst->core; + if (inst->session_type == MSM_VIDC_ENCODER) + vdev = &core->vdev[MSM_VIDC_ENCODER].vdev; + else + vdev = &core->vdev[MSM_VIDC_DECODER].vdev; + dqevent.type = V4L2_EVENT_PRIVATE_START + V4L2_EVENT_VIDC_BASE; + dqevent.u.data[0] = (uint8_t)MSM_VIDC_CLOSE_DONE; + v4l2_event_queue(vdev, &dqevent); + } else { + pr_err("Failed to get valid response for session close\n"); + } +} + +static struct vb2_buffer *get_vb_from_device_addr(struct vb2_queue *q, + u32 dev_addr) +{ + struct vb2_buffer *vb = NULL; + int found = 0; + if (!q) { + pr_err("Invalid parameter\n"); + return NULL; + } + list_for_each_entry(vb, &q->queued_list, queued_entry) { + if (vb->v4l2_planes[0].m.userptr == dev_addr) { + found = 1; + break; + } + } + if (!found) { + pr_err("Failed to find the buffer in queued list: %d, %d\n", + dev_addr, q->type); + vb = NULL; + } + return vb; +} + +static void handle_ebd(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_data_done *response = data; + struct vb2_buffer *vb; + if (!response) { + pr_err("Invalid response from vidc_hal\n"); + return; + } + vb = response->clnt_data; + if (vb) + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); +} + +static void handle_fbd(enum command_response cmd, void *data) +{ + struct msm_vidc_cb_data_done *response = data; + struct msm_vidc_inst *inst; + struct vb2_buffer *vb; + struct vidc_hal_fbd *fill_buf_done; + if (!response) { + pr_err("Invalid response from vidc_hal\n"); + return; + } + inst = (struct msm_vidc_inst *)response->session_id; + fill_buf_done = (struct vidc_hal_fbd *)&response->output_done; + vb = get_vb_from_device_addr(&inst->vb2_bufq[CAPTURE_PORT], + (u32)fill_buf_done->packet_buffer1); + if (vb) { + vb->v4l2_planes[0].bytesused = fill_buf_done->filled_len1; + pr_debug("Filled length = %d\n", vb->v4l2_planes[0].bytesused); + if (fill_buf_done->flags1 & HAL_BUFFERFLAG_EOS) + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_EOS; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } else { + /* + * FIXME: + * Special handling for EOS case: if we sent a 0 length input + * buf with EOS set, Venus doesn't return a valid output buffer. + * So pick up a random buffer that's with us, and send it to + * v4l2 client with EOS flag set. + * + * This would normally be OK unless client decides to send + * frames even after EOS. + * + * This should be fixed in upcoming versions of firmware + */ + if (fill_buf_done->flags1 & HAL_BUFFERFLAG_EOS + && fill_buf_done->filled_len1 == 0) { + struct vb2_queue *q = &inst->vb2_bufq[CAPTURE_PORT]; + + if (!list_empty(&q->queued_list)) { + vb = list_first_entry(&q->queued_list, + struct vb2_buffer, queued_entry); + vb->v4l2_planes[0].bytesused = 0; + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_EOS; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } + + } + + } +} + +void handle_cmd_response(enum command_response cmd, void *data) +{ + pr_debug("Command response = %d\n", cmd); + switch (cmd) { + case SYS_INIT_DONE: + handle_sys_init_done(cmd, data); + break; + case SESSION_INIT_DONE: + handle_session_init_done(cmd, data); + break; + case SESSION_PROPERTY_INFO: + handle_session_prop_info(cmd, data); + break; + case SESSION_LOAD_RESOURCE_DONE: + handle_load_resource_done(cmd, data); + break; + case SESSION_START_DONE: + handle_start_done(cmd, data); + break; + case SESSION_ETB_DONE: + handle_ebd(cmd, data); + break; + case SESSION_FTB_DONE: + handle_fbd(cmd, data); + break; + case SESSION_STOP_DONE: + handle_stop_done(cmd, data); + break; + case SESSION_RELEASE_RESOURCE_DONE: + handle_release_res_done(cmd, data); + break; + case SESSION_END_DONE: + handle_session_close(cmd, data); + break; + case VIDC_EVENT_CHANGE: + handle_event_change(cmd, data); + break; + case SESSION_FLUSH_DONE: + handle_session_flush(cmd, data); + break; + default: + pr_err("response unhandled\n"); + break; + } +} + +static int msm_comm_init_core_done(struct msm_vidc_inst *inst) +{ + struct msm_vidc_core *core = inst->core; + unsigned long flags; + int rc = 0; + mutex_lock(&core->sync_lock); + if (core->state >= VIDC_CORE_INIT_DONE) { + pr_err("Video core: %d is already in state: %d\n", + core->id, core->state); + goto core_already_inited; + } + pr_debug("Waiting for SYS_INIT_DONE\n"); + rc = wait_for_completion_timeout( + &core->completions[SYS_MSG_INDEX(SYS_INIT_DONE)], + msecs_to_jiffies(HW_RESPONSE_TIMEOUT)); + if (!rc) { + pr_err("Wait interrupted or timeout: %d\n", rc); + rc = -EIO; + goto exit; + } else { + spin_lock_irqsave(&core->lock, flags); + core->state = VIDC_CORE_INIT_DONE; + spin_unlock_irqrestore(&core->lock, flags); + } + pr_debug("SYS_INIT_DONE!!!\n"); +core_already_inited: + change_inst_state(inst, MSM_VIDC_CORE_INIT_DONE); + rc = 0; +exit: + mutex_unlock(&core->sync_lock); + return rc; +} + +static int msm_comm_init_core(struct msm_vidc_inst *inst) +{ + int rc = 0; + struct msm_vidc_core *core = inst->core; + unsigned long flags; + mutex_lock(&core->sync_lock); + if (core->state >= VIDC_CORE_INIT) { + pr_err("Video core: %d is already in state: %d\n", + core->id, core->state); + goto core_already_inited; + } + init_completion(&core->completions[SYS_MSG_INDEX(SYS_INIT_DONE)]); + rc = vidc_hal_core_init(core->device); + if (rc) { + pr_err("Failed to init core, id = %d\n", core->id); + goto exit; + } + spin_lock_irqsave(&core->lock, flags); + core->state = VIDC_CORE_INIT; + spin_unlock_irqrestore(&core->lock, flags); +core_already_inited: + change_inst_state(inst, MSM_VIDC_CORE_INIT); +exit: + mutex_unlock(&core->sync_lock); + return rc; +} + +static int msm_vidc_deinit_core(struct msm_vidc_inst *inst) +{ + int rc = 0; + struct msm_vidc_core *core = inst->core; + unsigned long flags; + mutex_lock(&core->sync_lock); + if (core->state == VIDC_CORE_UNINIT) { + pr_err("Video core: %d is already in state: %d\n", + core->id, core->state); + goto core_already_uninited; + } + if (list_empty(&core->instances)) { + pr_debug("Calling vidc_hal_core_release\n"); + rc = vidc_hal_core_release(core->device); + if (rc) { + pr_err("Failed to release core, id = %d\n", core->id); + goto exit; + } + spin_lock_irqsave(&core->lock, flags); + core->state = VIDC_CORE_UNINIT; + spin_unlock_irqrestore(&core->lock, flags); + } +core_already_uninited: + change_inst_state(inst, MSM_VIDC_CORE_UNINIT); +exit: + mutex_unlock(&core->sync_lock); + return rc; +} + +static enum hal_domain get_hal_domain(int session_type) +{ + enum hal_domain domain; + switch (session_type) { + case MSM_VIDC_ENCODER: + domain = HAL_VIDEO_DOMAIN_ENCODER; + break; + case MSM_VIDC_DECODER: + domain = HAL_VIDEO_DOMAIN_DECODER; + break; + default: + pr_err("Wrong domain\n"); + domain = HAL_UNUSED_DOMAIN; + break; + } + return domain; +} + +static enum hal_video_codec get_hal_codec_type(int fourcc) +{ + enum hal_video_codec codec; + pr_debug("codec in %s is 0x%x", __func__, fourcc); + switch (fourcc) { + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_H264_NO_SC: + codec = HAL_VIDEO_CODEC_H264; + break; + case V4L2_PIX_FMT_H263: + codec = HAL_VIDEO_CODEC_H263; + break; + case V4L2_PIX_FMT_MPEG1: + codec = HAL_VIDEO_CODEC_MPEG1; + break; + case V4L2_PIX_FMT_MPEG2: + codec = HAL_VIDEO_CODEC_MPEG2; + break; + case V4L2_PIX_FMT_MPEG4: + codec = HAL_VIDEO_CODEC_MPEG4; + break; + case V4L2_PIX_FMT_VC1_ANNEX_G: + case V4L2_PIX_FMT_VC1_ANNEX_L: + codec = HAL_VIDEO_CODEC_VC1; + break; + case V4L2_PIX_FMT_DIVX_311: + codec = HAL_VIDEO_CODEC_DIVX_311; + break; + /*HAL_VIDEO_CODEC_MVC + HAL_VIDEO_CODEC_DIVX + HAL_VIDEO_CODEC_SPARK + HAL_VIDEO_CODEC_VP6 + HAL_VIDEO_CODEC_VP7 + HAL_VIDEO_CODEC_VP8*/ + default: + pr_err("Wrong codec: %d\n", fourcc); + codec = HAL_UNUSED_CODEC; + } + return codec; +} + +static int msm_comm_session_init(int flipped_state, + struct msm_vidc_inst *inst) +{ + int rc = 0; + int fourcc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, MSM_VIDC_OPEN)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto exit; + } + if (inst->session_type == MSM_VIDC_DECODER) { + fourcc = inst->fmts[OUTPUT_PORT]->fourcc; + } else if (inst->session_type == MSM_VIDC_ENCODER) { + fourcc = inst->fmts[CAPTURE_PORT]->fourcc; + } else { + pr_err("Invalid session\n"); + return -EINVAL; + } + init_completion( + &inst->completions[SESSION_MSG_INDEX(SESSION_INIT_DONE)]); + inst->session = vidc_hal_session_init(inst->core->device, (u32) inst, + get_hal_domain(inst->session_type), + get_hal_codec_type(fourcc)); + if (!inst->session) { + pr_err("Failed to call session init for: %d, %d, %d, %d\n", + (int)inst->core->device, (int)inst, + inst->session_type, fourcc); + goto exit; + } + change_inst_state(inst, MSM_VIDC_OPEN); +exit: + return rc; +} + +static int msm_vidc_load_resources(int flipped_state, + struct msm_vidc_inst *inst) +{ + int rc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, MSM_VIDC_LOAD_RESOURCES)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto exit; + } + rc = vidc_hal_session_load_res((void *) inst->session); + if (rc) { + pr_err("Failed to send load resources\n"); + goto exit; + } + change_inst_state(inst, MSM_VIDC_LOAD_RESOURCES); +exit: + return rc; +} + +static int msm_vidc_start(int flipped_state, struct msm_vidc_inst *inst) +{ + int rc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, MSM_VIDC_START)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto exit; + } + init_completion( + &inst->completions[SESSION_MSG_INDEX(SESSION_START_DONE)]); + rc = vidc_hal_session_start((void *) inst->session); + if (rc) { + pr_err("Failed to send load resources\n"); + goto exit; + } + change_inst_state(inst, MSM_VIDC_START); +exit: + return rc; +} + +static int msm_vidc_stop(int flipped_state, struct msm_vidc_inst *inst) +{ + int rc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, MSM_VIDC_STOP)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto exit; + } + pr_debug("Send Stop to hal\n"); + init_completion( + &inst->completions[SESSION_MSG_INDEX(SESSION_STOP_DONE)]); + rc = vidc_hal_session_stop((void *) inst->session); + if (rc) { + pr_err("Failed to send stop\n"); + goto exit; + } + change_inst_state(inst, MSM_VIDC_STOP); +exit: + return rc; +} + +static int msm_vidc_release_res(int flipped_state, struct msm_vidc_inst *inst) +{ + int rc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, MSM_VIDC_RELEASE_RESOURCES)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto exit; + } + pr_debug("Send release res to hal\n"); + init_completion( + &inst->completions[SESSION_MSG_INDEX(SESSION_RELEASE_RESOURCE_DONE)]); + rc = vidc_hal_session_release_res((void *) inst->session); + if (rc) { + pr_err("Failed to send load resources\n"); + goto exit; + } + change_inst_state(inst, MSM_VIDC_RELEASE_RESOURCES); +exit: + return rc; +} + +static int msm_comm_session_close(int flipped_state, struct msm_vidc_inst *inst) +{ + int rc = 0; + if (IS_ALREADY_IN_STATE(flipped_state, MSM_VIDC_CLOSE)) { + pr_err("inst: %p is already in state: %d\n", inst, inst->state); + goto exit; + } + pr_debug("Send session close to hal\n"); + init_completion( + &inst->completions[SESSION_MSG_INDEX(SESSION_END_DONE)]); + rc = vidc_hal_session_end((void *) inst->session); + if (rc) { + pr_err("Failed to send load resources\n"); + goto exit; + } + change_inst_state(inst, MSM_VIDC_OPEN); +exit: + return rc; +} + +int msm_comm_try_state(struct msm_vidc_inst *inst, int state) +{ + int rc = 0; + int flipped_state; + if (!inst) { + pr_err("Invalid instance pointer = %p\n", inst); + return -EINVAL; + } + pr_debug("Trying to move inst: %p from: 0x%x to 0x%x\n", + inst, inst->state, state); + mutex_lock(&inst->sync_lock); + flipped_state = inst->state; + if (flipped_state < MSM_VIDC_STOP + && state > MSM_VIDC_STOP) { + flipped_state = MSM_VIDC_STOP + (MSM_VIDC_STOP - flipped_state); + flipped_state &= 0xFFFE; + flipped_state = flipped_state - 1; + } else if (flipped_state > MSM_VIDC_STOP + && state < MSM_VIDC_STOP) { + flipped_state = MSM_VIDC_STOP - + (flipped_state - MSM_VIDC_STOP + 1); + flipped_state &= 0xFFFE; + flipped_state = flipped_state - 1; + } + pr_debug("flipped_state = 0x%x\n", flipped_state); + switch (flipped_state) { + case MSM_VIDC_CORE_UNINIT_DONE: + case MSM_VIDC_CORE_INIT: + rc = msm_comm_init_core(inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_CORE_INIT_DONE: + rc = msm_comm_init_core_done(inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_OPEN: + rc = msm_comm_session_init(flipped_state, inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_OPEN_DONE: + rc = wait_for_state(inst, flipped_state, MSM_VIDC_OPEN_DONE, + SESSION_INIT_DONE); + if (rc || state <= inst->state) + break; + case MSM_VIDC_LOAD_RESOURCES: + rc = msm_vidc_load_resources(flipped_state, inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_LOAD_RESOURCES_DONE: + case MSM_VIDC_START: + rc = msm_vidc_start(flipped_state, inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_START_DONE: + rc = wait_for_state(inst, flipped_state, MSM_VIDC_START_DONE, + SESSION_START_DONE); + if (rc || state <= inst->state) + break; + case MSM_VIDC_STOP: + rc = msm_vidc_stop(flipped_state, inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_STOP_DONE: + rc = wait_for_state(inst, flipped_state, MSM_VIDC_STOP_DONE, + SESSION_STOP_DONE); + if (rc || state <= inst->state) + break; + pr_debug("Moving to Stop Done state\n"); + case MSM_VIDC_RELEASE_RESOURCES: + rc = msm_vidc_release_res(flipped_state, inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_RELEASE_RESOURCES_DONE: + rc = wait_for_state(inst, flipped_state, + MSM_VIDC_RELEASE_RESOURCES_DONE, + SESSION_RELEASE_RESOURCE_DONE); + if (rc || state <= inst->state) + break; + pr_debug("Moving to release resources done state\n"); + case MSM_VIDC_CLOSE: + rc = msm_comm_session_close(flipped_state, inst); + if (rc || state <= inst->state) + break; + case MSM_VIDC_CLOSE_DONE: + rc = wait_for_state(inst, flipped_state, MSM_VIDC_CLOSE_DONE, + SESSION_END_DONE); + if (rc || state <= inst->state) + break; + case MSM_VIDC_CORE_UNINIT: + pr_debug("***************Sending core uninit\n"); + rc = msm_vidc_deinit_core(inst); + if (rc || state == inst->state) + break; + default: + pr_err("State not recognized\n"); + rc = -EINVAL; + break; + } + mutex_unlock(&inst->sync_lock); + if (rc) + pr_err("Failed to move from state: %d to %d\n", + inst->state, state); + return rc; +} + +int msm_comm_qbuf(struct vb2_buffer *vb) +{ + int rc = 0; + struct vb2_queue *q; + struct msm_vidc_inst *inst; + unsigned long flags; + struct vb2_buf_entry *entry; + struct vidc_frame_data frame_data; + q = vb->vb2_queue; + inst = q->drv_priv; + + if (!inst || !vb) { + pr_err("Invalid input: %p, %p\n", inst, vb); + return -EINVAL; + } + if (inst->state != MSM_VIDC_START_DONE) { + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + pr_err("Out of memory\n"); + goto err_no_mem; + } + entry->vb = vb; + pr_debug("Queueing buffer in pendingq\n"); + spin_lock_irqsave(&inst->lock, flags); + list_add_tail(&entry->list, &inst->pendingq); + spin_unlock_irqrestore(&inst->lock, flags); + } else { + memset(&frame_data, 0 , sizeof(struct vidc_frame_data)); + frame_data.alloc_len = vb->v4l2_planes[0].length; + frame_data.filled_len = vb->v4l2_planes[0].bytesused; + frame_data.device_addr = vb->v4l2_planes[0].m.userptr; + frame_data.clnt_data = (u32)vb; + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + frame_data.buffer_type = HAL_BUFFER_INPUT; + if (vb->v4l2_buf.flags & V4L2_BUF_FLAG_EOS) { + frame_data.flags = HAL_BUFFERFLAG_EOS; + pr_debug("Received EOS on output capability\n"); + } + pr_debug("Sending etb to hal: Alloc: %d :filled: %d\n", + frame_data.alloc_len, frame_data.filled_len); + rc = vidc_hal_session_etb((void *) inst->session, + &frame_data); + pr_debug("Sent etb to HAL\n"); + } else if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + frame_data.filled_len = 0; + frame_data.buffer_type = HAL_BUFFER_OUTPUT; + frame_data.extradata_addr = 0; + pr_debug("Sending ftb to hal...: Alloc: %d :filled: %d" + " extradata_addr: %d\n", frame_data.alloc_len, + frame_data.filled_len, + frame_data.extradata_addr); + rc = vidc_hal_session_ftb((void *) inst->session, + &frame_data); + } else { + pr_err("This capability is not supported: %d\n", + q->type); + rc = -EINVAL; + } + } + if (rc) + pr_err("Failed to queue buffer\n"); +err_no_mem: + return rc; +} + +int msm_comm_try_get_bufreqs(struct msm_vidc_inst *inst) +{ + int rc = 0; + mutex_lock(&inst->sync_lock); + init_completion( + &inst->completions[SESSION_MSG_INDEX(SESSION_PROPERTY_INFO)]); + rc = vidc_hal_session_get_buf_req((void *) inst->session); + if (rc) { + pr_err("Failed to get property\n"); + goto exit; + } + rc = wait_for_completion_timeout( + &inst->completions[SESSION_MSG_INDEX(SESSION_PROPERTY_INFO)], + msecs_to_jiffies(HW_RESPONSE_TIMEOUT)); + if (!rc) { + pr_err("Wait interrupted or timeout: %d\n", rc); + rc = -EIO; + goto exit; + } + rc = 0; +exit: + mutex_unlock(&inst->sync_lock); + return rc; +} + +int msm_vidc_decoder_cmd(void *instance, struct v4l2_decoder_cmd *dec) +{ + int rc = 0; + struct msm_vidc_inst *inst = (struct msm_vidc_inst *)instance; + mutex_lock(&inst->sync_lock); + if (dec->cmd != V4L2_DEC_CMD_STOP) + return -EINVAL; + rc = vidc_hal_session_flush((void *)inst->session, HAL_FLUSH_OUTPUT); + if (rc) { + pr_err("Failed to get property\n"); + goto exit; + } +exit: + mutex_unlock(&inst->sync_lock); + return rc; +} + +int msm_comm_set_scratch_buffers(struct msm_vidc_inst *inst) +{ + int rc = 0; + struct msm_smem *handle; + struct internal_buf *binfo; + struct list_head *ptr, *next; + struct vidc_buffer_addr_info buffer_info; + unsigned long flags; + int i; + pr_debug("scratch: num = %d, size = %d\n", + inst->buff_req.buffer[6].buffer_count_actual, + inst->buff_req.buffer[6].buffer_size); + spin_lock_irqsave(&inst->lock, flags); + if (!list_empty(&inst->internalbufs)) { + list_for_each_safe(ptr, next, &inst->internalbufs) { + binfo = list_entry(ptr, struct internal_buf, + list); + list_del(&binfo->list); + msm_smem_free(inst->mem_client, binfo->handle); + kfree(binfo); + } + } + spin_unlock_irqrestore(&inst->lock, flags); + + + for (i = 0; i < inst->buff_req.buffer[6].buffer_count_actual; + i++) { + handle = msm_smem_alloc(inst->mem_client, + inst->buff_req.buffer[6].buffer_size, 1, 0); + if (!handle) { + pr_err("Failed to allocate scratch memory\n"); + rc = -ENOMEM; + goto err_no_mem; + } + binfo = kzalloc(sizeof(*binfo), GFP_KERNEL); + if (!binfo) { + pr_err("Out of memory\n"); + rc = -ENOMEM; + goto err_no_mem; + } + binfo->handle = handle; + spin_lock_irqsave(&inst->lock, flags); + list_add_tail(&binfo->list, &inst->internalbufs); + spin_unlock_irqrestore(&inst->lock, flags); + buffer_info.buffer_size = + inst->buff_req.buffer[6].buffer_size; + buffer_info.buffer_type = HAL_BUFFER_INTERNAL_SCRATCH; + buffer_info.num_buffers = 1; + buffer_info.align_device_addr = handle->device_addr; + rc = vidc_hal_session_set_buffers((void *) inst->session, + &buffer_info); + if (rc) { + pr_err("vidc_hal_session_set_buffers failed"); + break; + } + } +err_no_mem: + return rc; +} diff --git a/drivers/media/video/msm_vidc/msm_vidc_common.h b/drivers/media/video/msm_vidc/msm_vidc_common.h new file mode 100644 index 0000000000000000000000000000000000000000..2fafa796a89d1f329010a5dfc19ab04801b107b8 --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_vidc_common.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VIDC_COMMON_H_ +#define _MSM_VIDC_COMMON_H_ +#include "msm_vidc_internal.h" +struct vb2_buf_entry { + struct list_head list; + struct vb2_buffer *vb; +}; +struct msm_vidc_core *get_vidc_core(int core_id); +const struct msm_vidc_format *msm_comm_get_pixel_fmt_index( + const struct msm_vidc_format fmt[], int size, int index, int fmt_type); +const struct msm_vidc_format *msm_comm_get_pixel_fmt_fourcc( + const struct msm_vidc_format fmt[], int size, int fourcc, int fmt_type); +struct vb2_queue *msm_comm_get_vb2q( + struct msm_vidc_inst *inst, enum v4l2_buf_type type); +int msm_comm_try_state(struct msm_vidc_inst *inst, int state); +int msm_comm_try_get_bufreqs(struct msm_vidc_inst *inst); +int msm_comm_set_scratch_buffers(struct msm_vidc_inst *inst); +int msm_comm_qbuf(struct vb2_buffer *vb); +#define IS_PRIV_CTRL(idx) (\ + (V4L2_CTRL_ID2CLASS(idx) == V4L2_CTRL_CLASS_MPEG) && \ + V4L2_CTRL_DRIVER_PRIV(idx)) + +#endif diff --git a/drivers/media/video/msm_vidc/msm_vidc_internal.h b/drivers/media/video/msm_vidc/msm_vidc_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..fb1ab586751868d6c2decfaf3e41607a5c3f3808 --- /dev/null +++ b/drivers/media/video/msm_vidc/msm_vidc_internal.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VIDC_INTERNAL_H_ +#define _MSM_VIDC_INTERNAL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vidc_hal_api.h" + +#define MSM_VIDC_DRV_NAME "msm_vidc_driver" +#define MSM_VIDC_VERSION KERNEL_VERSION(0, 0, 1); +#define MAX_DEBUGFS_NAME 50 +#define DEFAULT_TIMEOUT 3 + +#define V4L2_EVENT_VIDC_BASE 10 + +#define SYS_MSG_START VIDC_EVENT_CHANGE +#define SYS_MSG_END SYS_DEBUG +#define SESSION_MSG_START SESSION_LOAD_RESOURCE_DONE +#define SESSION_MSG_END SESSION_PROPERTY_INFO +#define SYS_MSG_INDEX(__msg) (__msg - SYS_MSG_START) +#define SESSION_MSG_INDEX(__msg) (__msg - SESSION_MSG_START) + +enum vidc_ports { + OUTPUT_PORT, + CAPTURE_PORT, + MAX_PORT_NUM +}; + +enum vidc_core_state { + VIDC_CORE_UNINIT = 0, + VIDC_CORE_INIT, + VIDC_CORE_INIT_DONE, +}; + +/*Donot change the enum values unless + * you know what you are doing*/ +enum instance_state { + MSM_VIDC_CORE_UNINIT_DONE = 0x0001, + MSM_VIDC_CORE_INIT, + MSM_VIDC_CORE_INIT_DONE, + MSM_VIDC_OPEN, + MSM_VIDC_OPEN_DONE, + MSM_VIDC_LOAD_RESOURCES, + MSM_VIDC_LOAD_RESOURCES_DONE, + MSM_VIDC_START, + MSM_VIDC_START_DONE, + MSM_VIDC_STOP, + MSM_VIDC_STOP_DONE, + MSM_VIDC_RELEASE_RESOURCES, + MSM_VIDC_RELEASE_RESOURCES_DONE, + MSM_VIDC_CLOSE, + MSM_VIDC_CLOSE_DONE, + MSM_VIDC_CORE_UNINIT, +}; + +enum vidc_resposes_id { + MSM_VIDC_DECODER_FLUSH_DONE = 0x11, + MSM_VIDC_DECODER_EVENT_CHANGE, +}; + +struct buf_info { + struct list_head list; + struct vb2_buffer *buf; +}; + +struct internal_buf { + struct list_head list; + struct msm_smem *handle; +}; + +struct msm_vidc_format { + char name[64]; + u8 description[32]; + u32 fourcc; + int num_planes; + int type; + u32 (*get_frame_size)(int plane, u32 height, u32 width); +}; + +struct msm_vidc_drv { + spinlock_t lock; + struct list_head cores; + int num_cores; + struct dentry *debugfs_root; +}; + +struct msm_video_device { + int type; + struct video_device vdev; +}; + +struct msm_vidc_core { + struct list_head list; + struct mutex sync_lock; + int id; + void *device; + struct msm_video_device vdev[MSM_VIDC_MAX_DEVICES]; + struct v4l2_device v4l2_dev; + spinlock_t lock; + struct list_head instances; + struct dentry *debugfs_root; + u32 base_addr; + u32 register_base; + u32 register_size; + u32 irq; + enum vidc_core_state state; + struct completion completions[SYS_MSG_END - SYS_MSG_START + 1]; +}; + +struct msm_vidc_inst { + struct list_head list; + struct mutex sync_lock; + struct msm_vidc_core *core; + int session_type; + void *session; + u32 width; + u32 height; + int state; + const struct msm_vidc_format *fmts[MAX_PORT_NUM]; + struct vb2_queue vb2_bufq[MAX_PORT_NUM]; + spinlock_t lock; + struct list_head pendingq; + struct list_head internalbufs; + struct buffer_requirements buff_req; + void *mem_client; + struct v4l2_ctrl_handler ctrl_handler; + struct completion completions[SESSION_MSG_END - SESSION_MSG_START + 1]; + struct v4l2_fh event_handler; + bool in_reconfig; + u32 reconfig_width; + u32 reconfig_height; +}; + +extern struct msm_vidc_drv *vidc_driver; + +struct msm_vidc_ctrl { + u32 id; + char name[64]; + enum v4l2_ctrl_type type; + s32 minimum; + s32 maximum; + s32 default_value; + u32 step; + u32 menu_skip_mask; + const char * const *qmenu; +}; + +void handle_cmd_response(enum command_response cmd, void *data); +#endif diff --git a/drivers/media/video/msm_vidc/vidc_hal.c b/drivers/media/video/msm_vidc/vidc_hal.c new file mode 100644 index 0000000000000000000000000000000000000000..1f33c2c29a6fbb9f2e7ff4464321acb9ff13d84e --- /dev/null +++ b/drivers/media/video/msm_vidc/vidc_hal.c @@ -0,0 +1,1925 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "vidc_hal.h" +#include "vidc_hal_io.h" + +#define FIRMWARE_SIZE 0X00A00000 +#define REG_ADDR_OFFSET_BITMASK 0x000FFFFF + +/*Workaround for virtio */ +#define HFI_VIRTIO_FW_BIAS 0x34f00000 + +struct hal_device_data hal_ctxt; + +static void hal_virtio_modify_cmd_packet(u8 *packet) +{ + struct hfi_cmd_sys_session_init_packet *sys_init; + struct hal_session *sess; + u8 i; + + if (!packet) { + HAL_MSG_ERROR("Invalid Param: %s", __func__); + return; + } + + sys_init = (struct hfi_cmd_sys_session_init_packet *)packet; + sess = (struct hal_session *) sys_init->session_id; + switch (sys_init->packet) { + case HFI_CMD_SESSION_EMPTY_BUFFER: + if (sess->is_decoder) { + struct hfi_cmd_session_empty_buffer_compressed_packet + *pkt = (struct + hfi_cmd_session_empty_buffer_compressed_packet + *) packet; + pkt->packet_buffer -= HFI_VIRTIO_FW_BIAS; + } else { + struct + hfi_cmd_session_empty_buffer_uncompressed_plane0_packet + *pkt = (struct + hfi_cmd_session_empty_buffer_uncompressed_plane0_packet + *) packet; + pkt->packet_buffer -= HFI_VIRTIO_FW_BIAS; + } + break; + case HFI_CMD_SESSION_FILL_BUFFER: + { + struct hfi_cmd_session_fill_buffer_packet *pkt = + (struct hfi_cmd_session_fill_buffer_packet *)packet; + pkt->packet_buffer -= HFI_VIRTIO_FW_BIAS; + break; + } + case HFI_CMD_SESSION_SET_BUFFERS: + { + struct hfi_cmd_session_set_buffers_packet *pkt = + (struct hfi_cmd_session_set_buffers_packet *)packet; + if ((pkt->buffer_type == HFI_BUFFER_OUTPUT) || + (pkt->buffer_type == HFI_BUFFER_OUTPUT2)) { + struct hfi_buffer_info *buff; + buff = (struct hfi_buffer_info *) pkt->rg_buffer_info; + buff->buffer_addr -= HFI_VIRTIO_FW_BIAS; + buff->extradata_addr -= HFI_VIRTIO_FW_BIAS; + } else { + for (i = 0; i < pkt->num_buffers; i++) + pkt->rg_buffer_info[i] -= HFI_VIRTIO_FW_BIAS; + } + break; + } + case HFI_CMD_SESSION_RELEASE_BUFFERS: + { + struct hfi_cmd_session_release_buffer_packet *pkt = + (struct hfi_cmd_session_release_buffer_packet *)packet; + if ((pkt->buffer_type == HAL_BUFFER_OUTPUT) || + (pkt->buffer_type == HAL_BUFFER_OUTPUT2)) { + struct hfi_buffer_info *buff; + buff = (struct hfi_buffer_info *) pkt->rg_buffer_info; + buff->buffer_addr -= HFI_VIRTIO_FW_BIAS; + buff->extradata_addr -= HFI_VIRTIO_FW_BIAS; + } else { + for (i = 0; i < pkt->num_buffers; i++) + pkt->rg_buffer_info[i] -= HFI_VIRTIO_FW_BIAS; + } + break; + } + case HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER: + { + struct hfi_cmd_session_parse_sequence_header_packet *pkt = + (struct hfi_cmd_session_parse_sequence_header_packet *) + packet; + pkt->packet_buffer -= HFI_VIRTIO_FW_BIAS; + break; + } + case HFI_CMD_SESSION_GET_SEQUENCE_HEADER: + { + struct hfi_cmd_session_get_sequence_header_packet *pkt = + (struct hfi_cmd_session_get_sequence_header_packet *) + packet; + pkt->packet_buffer -= HFI_VIRTIO_FW_BIAS; + break; + } + default: + break; + } +} + +static int write_queue(void *info, u8 *packet, u32 *rx_req_is_set) +{ + struct hfi_queue_header *queue; + u32 packet_size_in_words, new_write_idx; + struct vidc_iface_q_info *qinfo; + u32 empty_space, read_idx; + u32 *write_ptr; + + if (!info || !packet || !rx_req_is_set) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } + + qinfo = (struct vidc_iface_q_info *) info; + HAL_MSG_LOW("In %s: ", __func__); + hal_virtio_modify_cmd_packet(packet); + + queue = (struct hfi_queue_header *) qinfo->q_hdr; + + if (!queue) { + HAL_MSG_ERROR("queue not present"); + return -ENOENT; + } + + packet_size_in_words = (*(u32 *)packet) >> 2; + HAL_MSG_LOW("Packet_size in words: %d", packet_size_in_words); + + if (packet_size_in_words == 0) { + HAL_MSG_ERROR("Zero packet size"); + return -ENODATA; + } + + read_idx = queue->qhdr_read_idx; + + empty_space = (queue->qhdr_write_idx >= read_idx) ? + (queue->qhdr_q_size - (queue->qhdr_write_idx - read_idx)) : + (read_idx - queue->qhdr_write_idx); + HAL_MSG_LOW("Empty_space: %d", empty_space); + if (empty_space <= packet_size_in_words) { + queue->qhdr_tx_req = 1; + HAL_MSG_ERROR("Insufficient size (%d) to write (%d)", + empty_space, packet_size_in_words); + return -ENOTEMPTY; + } + + queue->qhdr_tx_req = 0; + + new_write_idx = (queue->qhdr_write_idx + packet_size_in_words); + write_ptr = (u32 *)((qinfo->q_array.align_virtual_addr) + + (queue->qhdr_write_idx << 2)); + HAL_MSG_LOW("Write Ptr: %d", (u32) write_ptr); + if (new_write_idx < queue->qhdr_q_size) { + memcpy(write_ptr, packet, packet_size_in_words << 2); + } else { + new_write_idx -= queue->qhdr_q_size; + memcpy(write_ptr, packet, (packet_size_in_words - + new_write_idx) << 2); + memcpy((void *)queue->qhdr_start_addr, + packet + ((packet_size_in_words - new_write_idx) << 2), + new_write_idx << 2); + } + queue->qhdr_write_idx = new_write_idx; + *rx_req_is_set = (1 == queue->qhdr_rx_req) ? 1 : 0; + HAL_MSG_LOW("Out %s: ", __func__); + return 0; +} + +static void hal_virtio_modify_msg_packet(u8 *packet) +{ + struct hfi_msg_sys_session_init_done_packet *sys_idle; + struct hal_session *sess; + + if (!packet) { + HAL_MSG_ERROR("Invalid Param: %s", __func__); + return; + } + + sys_idle = (struct hfi_msg_sys_session_init_done_packet *)packet; + sess = (struct hal_session *) sys_idle->session_id; + + switch (sys_idle->packet_type) { + case HFI_MSG_SESSION_FILL_BUFFER_DONE: + if (sess->is_decoder) { + struct + hfi_msg_session_fbd_uncompressed_plane0_packet + *pkt_uc = (struct + hfi_msg_session_fbd_uncompressed_plane0_packet + *) packet; + pkt_uc->packet_buffer += HFI_VIRTIO_FW_BIAS; + } else { + struct + hfi_msg_session_fill_buffer_done_compressed_packet + *pkt = (struct + hfi_msg_session_fill_buffer_done_compressed_packet + *) packet; + pkt->packet_buffer += HFI_VIRTIO_FW_BIAS; + } + break; + case HFI_MSG_SESSION_EMPTY_BUFFER_DONE: + { + struct hfi_msg_session_empty_buffer_done_packet *pkt = + (struct hfi_msg_session_empty_buffer_done_packet *)packet; + pkt->packet_buffer += HFI_VIRTIO_FW_BIAS; + break; + } + case HFI_MSG_SESSION_GET_SEQUENCE_HEADER_DONE: + { + struct + hfi_msg_session_get_sequence_header_done_packet + *pkt = + (struct hfi_msg_session_get_sequence_header_done_packet *) + packet; + pkt->sequence_header += HFI_VIRTIO_FW_BIAS; + break; + } + default: + break; + } +} + +static int read_queue(void *info, u8 *packet, u32 *pb_tx_req_is_set) +{ + struct hfi_queue_header *queue; + u32 packet_size_in_words, new_read_idx; + u32 *read_ptr; + struct vidc_iface_q_info *qinfo; + + if (!info || !packet || !pb_tx_req_is_set) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } + + qinfo = (struct vidc_iface_q_info *) info; + HAL_MSG_LOW("In %s: ", __func__); + queue = (struct hfi_queue_header *) qinfo->q_hdr; + + if (!queue) { + HAL_MSG_ERROR("Queue memory is not allocated\n"); + return -ENOMEM; + } + + if (queue->qhdr_read_idx == queue->qhdr_write_idx) { + queue->qhdr_rx_req = 1; + *pb_tx_req_is_set = 0; + return -EPERM; + } + + read_ptr = (u32 *)((qinfo->q_array.align_virtual_addr) + + (queue->qhdr_read_idx << 2)); + packet_size_in_words = (*read_ptr) >> 2; + HAL_MSG_LOW("packet_size_in_words: %d", packet_size_in_words); + if (packet_size_in_words == 0) { + HAL_MSG_ERROR("Zero packet size"); + return -ENODATA; + } + + new_read_idx = queue->qhdr_read_idx + packet_size_in_words; + HAL_MSG_LOW("Read Ptr: %d", (u32) new_read_idx); + if (new_read_idx < queue->qhdr_q_size) { + memcpy(packet, read_ptr, + packet_size_in_words << 2); + } else { + new_read_idx -= queue->qhdr_q_size; + memcpy(packet, read_ptr, + (packet_size_in_words - new_read_idx) << 2); + memcpy(packet + ((packet_size_in_words - + new_read_idx) << 2), + (u8 *)queue->qhdr_start_addr, new_read_idx << 2); + } + + queue->qhdr_read_idx = new_read_idx; + + if (queue->qhdr_read_idx != queue->qhdr_write_idx) + queue->qhdr_rx_req = 0; + else + queue->qhdr_rx_req = 1; + + *pb_tx_req_is_set = (1 == queue->qhdr_tx_req) ? 1 : 0; + hal_virtio_modify_msg_packet(packet); + HAL_MSG_LOW("Out %s: ", __func__); + return 0; +} + +static int vidc_hal_alloc(void *mem, void *clnt, u32 size, u32 align, u32 flags) +{ + struct vidc_mem_addr *vmem; + struct msm_smem *alloc; + + if (!mem || !clnt || !size) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } + vmem = (struct vidc_mem_addr *)mem; + HAL_MSG_HIGH("start to alloc: size:%d, Flags: %d", size, flags); + + alloc = msm_smem_alloc(clnt, size, align, flags); + HAL_MSG_LOW("Alloc done"); + if (!alloc) { + HAL_MSG_HIGH("Alloc fail in %s", __func__); + return -ENOMEM; + } else { + HAL_MSG_MEDIUM("vidc_hal_alloc:ptr=%p,size=%d", + alloc->kvaddr, size); + vmem->mem_size = alloc->size; + vmem->mem_data = alloc; + vmem->align_virtual_addr = (u8 *) alloc->kvaddr; + vmem->align_device_addr = (u8 *)alloc->device_addr; + } + return 0; +} + +static void vidc_hal_free(struct smem_client *clnt, struct msm_smem *mem) +{ + msm_smem_free(clnt, mem); +} + +static void write_register(u8 *base_addr, u32 reg, u32 value, u8 *vaddr) +{ + u32 hwiosymaddr = reg; + + reg &= REG_ADDR_OFFSET_BITMASK; + if (reg == (u32)VIDC_CPU_CS_SCIACMDARG2) { + /* workaround to offset of FW bias */ + struct hfi_queue_header *qhdr; + struct hfi_queue_table_header *qtbl_hdr = + (struct hfi_queue_table_header *)vaddr; + + qhdr = VIDC_IFACEQ_GET_QHDR_START_ADDR(qtbl_hdr, 0); + qhdr->qhdr_start_addr -= HFI_VIRTIO_FW_BIAS; + + qhdr = VIDC_IFACEQ_GET_QHDR_START_ADDR(qtbl_hdr, 1); + qhdr->qhdr_start_addr -= HFI_VIRTIO_FW_BIAS; + + qhdr = VIDC_IFACEQ_GET_QHDR_START_ADDR(qtbl_hdr, 2); + qhdr->qhdr_start_addr -= HFI_VIRTIO_FW_BIAS; + value -= HFI_VIRTIO_FW_BIAS; + } + + hwiosymaddr = ((u32)base_addr + (hwiosymaddr)); + HAL_MSG_LOW("Base addr: 0x%x, written to: 0x%x, Value: 0x%x...", + (u32)base_addr, hwiosymaddr, value); + writel_relaxed(value, hwiosymaddr); + wmb(); +} + +static int read_register(u8 *base_addr, u32 reg) +{ + int rc = readl_relaxed((u32)base_addr + reg); + rmb(); + return rc; +} + +static int vidc_hal_iface_cmdq_write(struct hal_device *device, void *pkt) +{ + u32 rx_req_is_set = 0; + struct vidc_iface_q_info *q_info; + int result = -EPERM; + + if (!device || !pkt) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } + + spin_lock(&device->write_lock); + q_info = &device->iface_queues[VIDC_IFACEQ_CMDQ_IDX]; + if (!q_info) { + HAL_MSG_ERROR("cannot write to shared Q's"); + goto err_q_write; + } + + if (!write_queue(q_info, (u8 *)pkt, &rx_req_is_set)) { + if (rx_req_is_set) + write_register(device->hal_data->register_base_addr, + VIDC_CPU_IC_SOFTINT, + 1 << VIDC_CPU_IC_SOFTINT_H2A_SHFT, 0); + result = 0; + } else { + HAL_MSG_ERROR("vidc_hal_iface_cmdq_write:queue_full"); + } +err_q_write: + spin_unlock(&device->write_lock); + return result; +} + +int vidc_hal_iface_msgq_read(struct hal_device *device, void *pkt) +{ + u32 tx_req_is_set = 0; + int rc = 0; + struct vidc_iface_q_info *q_info; + + if (!pkt) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } + spin_lock(&device->read_lock); + if (device->iface_queues[VIDC_IFACEQ_MSGQ_IDX]. + q_array.align_virtual_addr == 0) { + HAL_MSG_ERROR("cannot read from shared MSG Q's"); + rc = -ENODATA; + goto read_error; + } + q_info = &device->iface_queues[VIDC_IFACEQ_MSGQ_IDX]; + + if (!read_queue(q_info, (u8 *)pkt, &tx_req_is_set)) { + if (tx_req_is_set) + write_register(device->hal_data->register_base_addr, + VIDC_CPU_IC_SOFTINT, + 1 << VIDC_CPU_IC_SOFTINT_H2A_SHFT, 0); + rc = 0; + } else { + HAL_MSG_ERROR("vidc_hal_iface_msgq_read:queue_empty"); + rc = -ENODATA; + } +read_error: + spin_unlock(&device->read_lock); + return rc; +} + +int vidc_hal_iface_dbgq_read(struct hal_device *device, void *pkt) +{ + u32 tx_req_is_set = 0; + int rc = 0; + struct vidc_iface_q_info *q_info; + + if (!pkt) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } + spin_lock(&device->read_lock); + if (device->iface_queues[VIDC_IFACEQ_DBGQ_IDX]. + q_array.align_virtual_addr == 0) { + HAL_MSG_ERROR("cannot read from shared DBG Q's"); + rc = -ENODATA; + goto dbg_error; + } + q_info = &device->iface_queues[VIDC_IFACEQ_DBGQ_IDX]; + if (!read_queue(q_info, (u8 *)pkt, &tx_req_is_set)) { + if (tx_req_is_set) + write_register(device->hal_data->register_base_addr, + VIDC_CPU_IC_SOFTINT, + 1 << VIDC_CPU_IC_SOFTINT_H2A_SHFT, 0); + rc = 0; + } else { + HAL_MSG_MEDIUM("vidc_hal_iface_dbgq_read:queue_empty"); + rc = -ENODATA; + } +dbg_error: + spin_unlock(&device->read_lock); + return rc; +} + +static void vidc_hal_set_queue_hdr_defaults(struct hfi_queue_header *q_hdr) +{ + q_hdr->qhdr_status = 0x1; + q_hdr->qhdr_type = VIDC_IFACEQ_DFLT_QHDR; + q_hdr->qhdr_q_size = VIDC_IFACEQ_QUEUE_SIZE; + q_hdr->qhdr_pkt_size = 0; + q_hdr->qhdr_rx_wm = 0x1; + q_hdr->qhdr_tx_wm = 0x1; + q_hdr->qhdr_rx_req = 0x1; + q_hdr->qhdr_tx_req = 0x0; + q_hdr->qhdr_rx_irq_status = 0x0; + q_hdr->qhdr_tx_irq_status = 0x0; + q_hdr->qhdr_read_idx = 0x0; + q_hdr->qhdr_write_idx = 0x0; +} + +static void vidc_hal_interface_queues_release(struct hal_device *device) +{ + int i; + for (i = 0; i < VIDC_IFACEQ_NUMQ; i++) { + vidc_hal_free(device->hal_client, + device->iface_queues[i].q_array.mem_data); + device->iface_queues[i].q_hdr = NULL; + device->iface_queues[i].q_array.mem_data = NULL; + device->iface_queues[i].q_array.align_virtual_addr = NULL; + device->iface_queues[i].q_array.align_device_addr = NULL; + } + vidc_hal_free(device->hal_client, + device->iface_q_table.mem_data); + device->iface_q_table.align_virtual_addr = NULL; + device->iface_q_table.align_device_addr = NULL; + msm_smem_delete_client(device->hal_client); + device->hal_client = NULL; +} + +static int vidc_hal_interface_queues_init(struct hal_device *dev) +{ + struct hfi_queue_table_header *q_tbl_hdr; + struct hfi_queue_header *q_hdr; + u8 i; + int rc = 0; + struct vidc_iface_q_info *iface_q; + + rc = vidc_hal_alloc((void *) &dev->iface_q_table, + dev->hal_client, + VIDC_IFACEQ_TABLE_SIZE, 1, 0); + if (rc) { + HAL_MSG_ERROR("%s:iface_q_table_alloc_fail", __func__); + return -ENOMEM; + } + q_tbl_hdr = (struct hfi_queue_table_header *) + dev->iface_q_table.align_virtual_addr; + q_tbl_hdr->qtbl_version = 0; + q_tbl_hdr->qtbl_size = VIDC_IFACEQ_TABLE_SIZE; + q_tbl_hdr->qtbl_qhdr0_offset = sizeof( + struct hfi_queue_table_header); + q_tbl_hdr->qtbl_qhdr_size = sizeof( + struct hfi_queue_header); + q_tbl_hdr->qtbl_num_q = VIDC_IFACEQ_NUMQ; + q_tbl_hdr->qtbl_num_active_q = VIDC_IFACEQ_NUMQ; + + for (i = 0; i < VIDC_IFACEQ_NUMQ; i++) { + iface_q = &dev->iface_queues[i]; + rc = vidc_hal_alloc((void *) &iface_q->q_array, + dev->hal_client, VIDC_IFACEQ_QUEUE_SIZE, + 1, 0); + if (rc) { + HAL_MSG_ERROR("%s:iface_q_table_alloc[%d]_fail", + __func__, i); + vidc_hal_interface_queues_release(dev); + return -ENOMEM; + } else { + iface_q->q_hdr = + VIDC_IFACEQ_GET_QHDR_START_ADDR( + dev->iface_q_table.align_virtual_addr, i); + vidc_hal_set_queue_hdr_defaults(iface_q->q_hdr); + } + } + + iface_q = &dev->iface_queues[VIDC_IFACEQ_CMDQ_IDX]; + q_hdr = iface_q->q_hdr; + q_hdr->qhdr_start_addr = (u32) + iface_q->q_array.align_device_addr; + q_hdr->qhdr_type |= HFI_Q_ID_HOST_TO_CTRL_CMD_Q; + + iface_q = &dev->iface_queues[VIDC_IFACEQ_MSGQ_IDX]; + q_hdr = iface_q->q_hdr; + q_hdr->qhdr_start_addr = (u32) + iface_q->q_array.align_device_addr; + q_hdr->qhdr_type |= HFI_Q_ID_CTRL_TO_HOST_MSG_Q; + + iface_q = &dev->iface_queues[VIDC_IFACEQ_DBGQ_IDX]; + q_hdr = iface_q->q_hdr; + q_hdr->qhdr_start_addr = (u32) + iface_q->q_array.align_device_addr; + q_hdr->qhdr_type |= HFI_Q_ID_CTRL_TO_HOST_DEBUG_Q; + write_register(dev->hal_data->register_base_addr, + VIDC_CPU_CS_SCIACMDARG2, + (u32) dev->iface_q_table.align_device_addr, + dev->iface_q_table.align_virtual_addr); + write_register(dev->hal_data->register_base_addr, + VIDC_CPU_CS_SCIACMDARG1, 0x01, + dev->iface_q_table.align_virtual_addr); + return 0; +} + +static int vidc_hal_core_start_cpu(struct hal_device *device) +{ + u32 ctrl_status = 0, count = 0, rc = 0; + write_register(device->hal_data->register_base_addr, + VIDC_WRAPPER_INTR_MASK, 0, 0); + write_register(device->hal_data->register_base_addr, + VIDC_CPU_CS_SCIACMDARG3, 1, 0); + while (!ctrl_status && count < 25) { + ctrl_status = read_register( + device->hal_data->register_base_addr, + VIDC_CPU_CS_SCIACMDARG0); + count++; + } + if (count >= 25) + rc = -ETIME; + return rc; +} + +int vidc_hal_core_init(void *device) +{ + struct hfi_cmd_sys_init_packet pkt; + int rc = 0; + struct hal_device *dev; + + if (device) { + dev = device; + } else { + HAL_MSG_ERROR("%s:invalid device", __func__); + return -ENODEV; + } + enable_irq(dev->hal_data->irq); + INIT_LIST_HEAD(&dev->sess_head); + spin_lock_init(&dev->read_lock); + spin_lock_init(&dev->write_lock); + + if (!dev->hal_client) { + dev->hal_client = msm_smem_new_client(SMEM_ION); + if (dev->hal_client == NULL) { + HAL_MSG_ERROR("Failed to alloc ION_Client"); + rc = -ENODEV; + goto err_no_mem; + } + + HAL_MSG_ERROR("Device_Virt_Address : 0x%x," + "Register_Virt_Addr: 0x%x", + (u32) dev->hal_data->device_base_addr, + (u32) dev->hal_data->register_base_addr); + + rc = vidc_hal_interface_queues_init(dev); + if (rc) { + HAL_MSG_ERROR("failed to init queues"); + rc = -ENOMEM; + goto err_no_mem; + } + } else { + HAL_MSG_ERROR("hal_client exists"); + rc = -EEXIST; + goto err_no_mem; + } + rc = vidc_hal_core_start_cpu(dev); + if (rc) { + HAL_MSG_ERROR("Failed to start core"); + rc = -ENODEV; + goto err_no_dev; + } + pkt.size = sizeof(struct hfi_cmd_sys_init_packet); + pkt.packet = HFI_CMD_SYS_INIT; + if (vidc_hal_iface_cmdq_write(dev, &pkt)) { + rc = -ENOTEMPTY; + goto err_write_fail; + } + return rc; +err_no_dev: +err_write_fail: +err_no_mem: + disable_irq_nosync(dev->hal_data->irq); + return rc; +} + +int vidc_hal_core_release(void *device) +{ + struct hal_device *dev; + if (device) { + dev = device; + } else { + HAL_MSG_ERROR("%s:invalid device", __func__); + return -ENODEV; + } + write_register(dev->hal_data->register_base_addr, + VIDC_CPU_CS_SCIACMDARG3, 0, 0); + disable_irq_nosync(dev->hal_data->irq); + vidc_hal_interface_queues_release(dev); + HAL_MSG_INFO("\nHAL exited\n"); + return 0; +} + +int vidc_hal_core_pc_prep(void *device) +{ + struct hfi_cmd_sys_pc_prep_packet pkt; + int rc = 0; + struct hal_device *dev; + + if (device) { + dev = device; + } else { + HAL_MSG_ERROR("%s:invalid device", __func__); + return -ENODEV; + } + pkt.size = sizeof(struct hfi_cmd_sys_pc_prep_packet); + pkt.packet_type = HFI_CMD_SYS_PC_PREP; + if (vidc_hal_iface_cmdq_write(dev, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +static void vidc_hal_core_clear_interrupt(struct hal_device *device) +{ + u32 intr_status = 0; + + if (!device->callback) + return; + + intr_status = read_register( + device->hal_data->register_base_addr, + VIDC_WRAPPER_INTR_STATUS); + + if ((intr_status & VIDC_WRAPPER_INTR_STATUS_A2H_BMSK) || + (intr_status & VIDC_WRAPPER_INTR_STATUS_A2HWD_BMSK)) { + device->intr_status |= intr_status; + HAL_MSG_ERROR("INTERRUPT for device: 0x%x: " + "times: %d interrupt_status: %d", + (u32) device, ++device->reg_count, intr_status); + } else { + HAL_MSG_ERROR("SPURIOUS_INTR for device: 0x%x: " + "times: %d interrupt_status: %d", + (u32) device, ++device->spur_count, intr_status); + } + write_register(device->hal_data->register_base_addr, + VIDC_CPU_CS_A2HSOFTINTCLR, 1, 0); + write_register(device->hal_data->register_base_addr, + VIDC_WRAPPER_INTR_CLEAR, intr_status, 0); + HAL_MSG_ERROR("Cleared WRAPPER/A2H interrupt"); +} + +int vidc_hal_core_set_resource(void *device, + struct vidc_resource_hdr *resource_hdr, void *resource_value) +{ + struct hfi_cmd_sys_set_resource_packet *pkt; + u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; + int rc = 0; + struct hal_device *dev; + + if (!device || !resource_hdr || !resource_value) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + dev = device; + } + + pkt = (struct hfi_cmd_sys_set_resource_packet *) packet; + + pkt->size = sizeof(struct hfi_cmd_sys_set_resource_packet); + pkt->packet_type = HFI_CMD_SYS_SET_RESOURCE; + pkt->resource_handle = resource_hdr->resource_handle; + + switch (resource_hdr->resource_id) { + case VIDC_RESOURCE_OCMEM: + { + struct hfi_resource_ocmem_type *hfioc_mem = + (struct hfi_resource_ocmem_type *) + &pkt->rg_resource_data[0]; + struct vidc_mem_addr *vidc_oc_mem = + (struct vidc_mem_addr *) resource_value; + + pkt->resource_type = HFI_RESOURCE_OCMEM; + hfioc_mem->size = (u32) vidc_oc_mem->mem_size; + hfioc_mem->mem = (u8 *) vidc_oc_mem->align_device_addr; + pkt->size += sizeof(struct hfi_resource_ocmem_type); + if (vidc_hal_iface_cmdq_write(dev, pkt)) + rc = -ENOTEMPTY; + break; + } + default: + HAL_MSG_INFO("In %s called for resource %d", + __func__, resource_hdr->resource_id); + break; + } + return rc; +} + +int vidc_hal_core_release_resource(void *device, + struct vidc_resource_hdr *resource_hdr) +{ + struct hfi_cmd_sys_release_resource_packet pkt; + int rc = 0; + struct hal_device *dev; + + if (!device || !resource_hdr) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + dev = device; + } + + pkt.size = sizeof(struct hfi_cmd_sys_release_resource_packet); + pkt.packet_type = HFI_CMD_SYS_RELEASE_RESOURCE; + pkt.resource_type = resource_hdr->resource_id; + pkt.resource_handle = resource_hdr->resource_handle; + + if (vidc_hal_iface_cmdq_write(dev, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_core_ping(void *device) +{ + struct hfi_cmd_sys_ping_packet pkt; + int rc = 0; + struct hal_device *dev; + + if (device) { + dev = device; + } else { + HAL_MSG_ERROR("%s:invalid device", __func__); + return -ENODEV; + } + pkt.size = sizeof(struct hfi_cmd_sys_ping_packet); + pkt.packet_type = HFI_CMD_SYS_PING; + + if (vidc_hal_iface_cmdq_write(dev, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_set_property(void *sess, + enum hal_property ptype, void *pdata) +{ + u8 packet[VIDC_IFACEQ_VAR_LARGE_PKT_SIZE]; + struct hfi_cmd_session_set_property_packet *pkt = + (struct hfi_cmd_session_set_property_packet *) &packet; + struct hal_session *session; + + if (!sess || !pdata) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + HAL_MSG_INFO("IN func: %s, with property id: %d", __func__, ptype); + pkt->size = sizeof(struct hfi_cmd_session_set_property_packet) + - sizeof(u32); + pkt->packet_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->session_id = (u32) session; + pkt->num_properties = 1; + + switch (ptype) { + case HAL_CONFIG_FRAME_RATE: + { + struct hfi_frame_rate *hfi_fps; + pkt->rg_property_data[0] = HFI_PROPERTY_CONFIG_FRAME_RATE; + hfi_fps = (struct hfi_frame_rate *) &pkt->rg_property_data[1]; + memcpy(hfi_fps, (struct hfi_frame_rate *) + pdata, sizeof(struct hfi_frame_rate)); + pkt->size += sizeof(u32) + sizeof(struct hfi_frame_rate); + break; + } + case HAL_PARAM_UNCOMPRESSED_FORMAT_SELECT: + { + struct hfi_uncompressed_format_select *hfi_buf_fmt; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT; + hfi_buf_fmt = + (struct hfi_uncompressed_format_select *) + &pkt->rg_property_data[1]; + memcpy(hfi_buf_fmt, (struct hfi_uncompressed_format_select *) + pdata, sizeof(struct hfi_uncompressed_format_select)); + pkt->size += sizeof(u32) + sizeof(struct + hfi_uncompressed_format_select); + break; + } + case HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO: + break; + case HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO: + break; + case HAL_PARAM_EXTRA_DATA_HEADER_CONFIG: + break; + case HAL_PARAM_FRAME_SIZE: + { + struct hfi_frame_size *hfi_rect; + pkt->rg_property_data[0] = HFI_PROPERTY_PARAM_FRAME_SIZE; + hfi_rect = (struct hfi_frame_size *) &pkt->rg_property_data[1]; + memcpy(hfi_rect, (struct hfi_frame_size *) pdata, + sizeof(struct hfi_frame_size)); + pkt->size += sizeof(u32) + sizeof(struct hfi_frame_size); + break; + } + case HAL_CONFIG_REALTIME: + { + struct hfi_enable *hfi; + pkt->rg_property_data[0] = HFI_PROPERTY_CONFIG_REALTIME; + hfi = (struct hfi_enable *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_enable *) pdata, + sizeof(struct hfi_enable)); + pkt->size += sizeof(u32) + sizeof(struct hfi_enable); + break; + } + case HAL_PARAM_BUFFER_COUNT_ACTUAL: + { + struct hfi_buffer_count_actual *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL; + hfi = (struct hfi_buffer_count_actual *) + &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_buffer_count_actual *) pdata, + sizeof(struct hfi_buffer_count_actual)); + pkt->size += sizeof(u32) + sizeof(struct + hfi_buffer_count_actual); + break; + } + case HAL_PARAM_NAL_STREAM_FORMAT_SELECT: + { + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT; + pkt->rg_property_data[1] = (enum HFI_NAL_STREAM_FORMAT)pdata; + pkt->size += sizeof(u32) + sizeof(enum HFI_NAL_STREAM_FORMAT); + break; + } + case HAL_PARAM_VDEC_OUTPUT_ORDER: + { + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER; + pkt->rg_property_data[1] = (enum HFI_OUTPUT_ORDER)pdata; + pkt->size += sizeof(u32) + sizeof(enum HFI_OUTPUT_ORDER); + break; + } + case HAL_PARAM_VDEC_PICTURE_TYPE_DECODE: + { + struct hfi_enable_picture *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE; + hfi = (struct hfi_enable_picture *) &pkt->rg_property_data[1]; + hfi->picture_type = (u32) pdata; + pkt->size += sizeof(u32) + sizeof(struct hfi_enable_picture); + break; + } + case HAL_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO: + { + struct hfi_enable *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO; + hfi = (struct hfi_enable *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_enable *) pdata, + sizeof(struct hfi_enable)); + pkt->size += sizeof(u32) + sizeof(struct hfi_enable); + break; + } + case HAL_CONFIG_VDEC_POST_LOOP_DEBLOCKER: + { + struct hfi_enable *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER; + hfi = (struct hfi_enable *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_enable *) pdata, + sizeof(struct hfi_enable)); + pkt->size += sizeof(u32) + sizeof(struct hfi_enable); + break; + } + case HAL_PARAM_VDEC_MULTI_STREAM: + { + struct hfi_multi_stream *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM; + hfi = (struct hfi_multi_stream *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_multi_stream *)pdata, + sizeof(struct hfi_multi_stream)); + pkt->size += sizeof(u32) + sizeof(struct hfi_multi_stream); + break; + } + case HAL_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT: + { + struct hfi_display_picture_buffer_count *hfi_disp_buf; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT; + hfi_disp_buf = (struct hfi_display_picture_buffer_count *) + &pkt->rg_property_data[1]; + memcpy(hfi_disp_buf, + (struct hfi_display_picture_buffer_count *)pdata, + sizeof(struct hfi_display_picture_buffer_count)); + pkt->size += sizeof(u32) + + sizeof(struct hfi_display_picture_buffer_count); + break; + } + case HAL_PARAM_DIVX_FORMAT: + { + pkt->rg_property_data[0] = HFI_PROPERTY_PARAM_DIVX_FORMAT; + pkt->rg_property_data[1] = (enum HFI_DIVX_FORMAT)pdata; + pkt->size += sizeof(u32) + sizeof(enum HFI_DIVX_FORMAT); + break; + } + case HAL_CONFIG_VDEC_MB_ERROR_MAP_REPORTING: + { + struct hfi_enable *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING; + hfi = (struct hfi_enable *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_enable *) pdata, + sizeof(struct hfi_enable)); + pkt->size += sizeof(u32) + sizeof(struct hfi_enable); + break; + } + case HAL_PARAM_VDEC_CONTINUE_DATA_TRANSFER: + { + struct hfi_enable *enable; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER; + enable = (struct hfi_enable *) &pkt->rg_property_data[1]; + memcpy(enable, (struct hfi_enable *) pdata, + sizeof(struct hfi_enable)); + pkt->size += sizeof(u32) + sizeof(struct hfi_enable); + break; + } + case HAL_CONFIG_VENC_REQUEST_IFRAME: + pkt->rg_property_data[0] = + HFI_PROPERTY_CONFIG_VENC_REQUEST_IFRAME; + break; + case HAL_PARAM_VENC_MPEG4_SHORT_HEADER: + break; + case HAL_PARAM_VENC_MPEG4_AC_PREDICTION: + break; + case HAL_CONFIG_VENC_TARGET_BITRATE: + { + struct hfi_bitrate *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE; + hfi = (struct hfi_bitrate *) &pkt->rg_property_data[1]; + hfi->bit_rate = ((struct hfi_bitrate *)pdata)->bit_rate; + pkt->size += sizeof(u32) + sizeof(struct hfi_bitrate); + break; + } + case HAL_PARAM_PROFILE_LEVEL_CURRENT: + { + struct hfi_profile_level *hfi_profile_level; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT; + hfi_profile_level = (struct hfi_profile_level *) + &pkt->rg_property_data[1]; + memcpy(hfi_profile_level, (struct hfi_profile_level *) pdata, + sizeof(struct hfi_profile_level)); + pkt->size += sizeof(u32) + sizeof(struct hfi_profile_level); + break; + } + case HAL_PARAM_VENC_H264_ENTROPY_CONTROL: + { + struct hfi_h264_entropy_control *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL; + hfi = (struct hfi_h264_entropy_control *) + &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_h264_entropy_control *) pdata, + sizeof(struct hfi_h264_entropy_control)); + pkt->size += sizeof(u32) + sizeof( + struct hfi_h264_entropy_control); + break; + } + case HAL_PARAM_VENC_RATE_CONTROL: + { + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_RATE_CONTROL; + pkt->rg_property_data[1] = (enum HFI_RATE_CONTROL)pdata; + pkt->size += sizeof(u32) + sizeof(enum HFI_RATE_CONTROL); + break; + } + case HAL_PARAM_VENC_MPEG4_TIME_RESOLUTION: + { + struct hfi_mpeg4_time_resolution *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION; + hfi = (struct hfi_mpeg4_time_resolution *) + &pkt->rg_property_data[1]; + hfi->time_increment_resolution = + ((struct hal_mpeg4_time_resolution *)pdata)-> + time_increment_resolution; + pkt->size += sizeof(u32) + sizeof( + struct hfi_mpeg4_time_resolution); + break; + } + case HAL_PARAM_VENC_MPEG4_HEADER_EXTENSION: + { + struct hfi_mpeg4_header_extension *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION; + hfi = (struct hfi_mpeg4_header_extension *) + &pkt->rg_property_data[1]; + hfi->header_extension = (u32) pdata; + pkt->size += sizeof(u32) + + sizeof(struct hfi_mpeg4_header_extension); + break; + } + case HAL_PARAM_VENC_H264_DEBLOCK_CONTROL: + { + struct hfi_h264_db_control *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL; + hfi = (struct hfi_h264_db_control *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_h264_db_control *) pdata, + sizeof(struct hfi_h264_db_control)); + pkt->size += sizeof(u32) + + sizeof(struct hfi_h264_db_control); + break; + } + case HAL_PARAM_VENC_TEMPORAL_SPATIAL_TRADEOFF: + { + struct hfi_temporal_spatial_tradeoff *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_TEMPORAL_SPATIAL_TRADEOFF; + hfi = (struct hfi_temporal_spatial_tradeoff *) + &pkt->rg_property_data[1]; + hfi->ts_factor = ((struct hfi_temporal_spatial_tradeoff *) + pdata)->ts_factor; + pkt->size += sizeof(u32) + + sizeof(struct hfi_temporal_spatial_tradeoff); + break; + } + case HAL_PARAM_VENC_SESSION_QP: + { + struct hfi_quantization *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_SESSION_QP; + hfi = (struct hfi_quantization *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_quantization *) pdata, + sizeof(struct hfi_quantization)); + pkt->size += sizeof(u32) + sizeof(struct hfi_quantization); + break; + } + case HAL_CONFIG_VENC_INTRA_PERIOD: + { + struct hfi_intra_period *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD; + hfi = (struct hfi_intra_period *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_intra_period *) pdata, + sizeof(struct hfi_intra_period)); + pkt->size += sizeof(u32) + sizeof(struct hfi_intra_period); + break; + } + case HAL_CONFIG_VENC_IDR_PERIOD: + { + struct hfi_idr_period *hfi; + pkt->rg_property_data[0] = HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD; + hfi = (struct hfi_idr_period *) &pkt->rg_property_data[1]; + hfi->idr_period = ((struct hfi_idr_period *) pdata)->idr_period; + pkt->size += sizeof(u32) + sizeof(struct hfi_idr_period); + break; + } + case HAL_CONFIG_VPE_OPERATIONS: + break; + case HAL_PARAM_VENC_INTRA_REFRESH: + { + struct hfi_intra_refresh *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH; + hfi = (struct hfi_intra_refresh *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_intra_refresh *) pdata, + sizeof(struct hfi_intra_refresh)); + pkt->size += sizeof(u32) + sizeof(struct hfi_intra_refresh); + break; + } + case HAL_PARAM_VENC_MULTI_SLICE_CONTROL: + { + struct hfi_multi_slice_control *hfi; + pkt->rg_property_data[0] = + HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL; + hfi = (struct hfi_multi_slice_control *) + &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_multi_slice_control *) pdata, + sizeof(struct hfi_multi_slice_control)); + pkt->size += sizeof(u32) + sizeof(struct + hfi_multi_slice_control); + break; + } + case HAL_CONFIG_VPE_DEINTERLACE: + break; + case HAL_SYS_DEBUG_CONFIG: + { + struct hfi_debug_config *hfi; + pkt->rg_property_data[0] = HFI_PROPERTY_SYS_DEBUG_CONFIG; + hfi = (struct hfi_debug_config *) &pkt->rg_property_data[1]; + memcpy(hfi, (struct hfi_debug_config *) pdata, + sizeof(struct hfi_debug_config)); + pkt->size = sizeof(struct hfi_cmd_sys_set_property_packet) + + sizeof(struct hfi_debug_config); + break; + } + /* FOLLOWING PROPERTIES ARE NOT IMPLEMENTED IN CORE YET */ + case HAL_CONFIG_BUFFER_REQUIREMENTS: + case HAL_CONFIG_PRIORITY: + case HAL_CONFIG_BATCH_INFO: + case HAL_PARAM_METADATA_PASS_THROUGH: + case HAL_SYS_IDLE_INDICATOR: + case HAL_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: + case HAL_PARAM_INTERLACE_FORMAT_SUPPORTED: + case HAL_PARAM_CHROMA_SITE: + case HAL_PARAM_PROPERTIES_SUPPORTED: + case HAL_PARAM_PROFILE_LEVEL_SUPPORTED: + case HAL_PARAM_CAPABILITY_SUPPORTED: + case HAL_PARAM_NAL_STREAM_FORMAT_SUPPORTED: + case HAL_PARAM_MULTI_VIEW_FORMAT: + case HAL_PARAM_MAX_SEQUENCE_HEADER_SIZE: + case HAL_PARAM_CODEC_SUPPORTED: + case HAL_PARAM_VDEC_MULTI_VIEW_SELECT: + case HAL_PARAM_VDEC_MB_QUANTIZATION: + case HAL_PARAM_VDEC_NUM_CONCEALED_MB: + case HAL_PARAM_VDEC_H264_ENTROPY_SWITCHING: + case HAL_PARAM_VENC_SLICE_DELIVERY_MODE: + case HAL_PARAM_VENC_MPEG4_DATA_PARTITIONING: + + case HAL_CONFIG_BUFFER_COUNT_ACTUAL: + case HAL_CONFIG_VDEC_MULTI_STREAM: + case HAL_PARAM_VENC_MULTI_SLICE_INFO: + case HAL_CONFIG_VENC_TIMESTAMP_SCALE: + case HAL_PARAM_VENC_LOW_LATENCY: + default: + HAL_MSG_INFO("DEFAULT: Calling 0x%x", ptype); + break; + } + if (vidc_hal_iface_cmdq_write(session->device, pkt)) + return -ENOTEMPTY; + return 0; +} + +int vidc_hal_session_get_property(void *sess, + enum hal_property ptype, void *pdata) +{ + struct hal_session *session; + + if (!sess || !pdata) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + HAL_MSG_INFO("IN func: %s, with property id: %d", __func__, ptype); + + switch (ptype) { + case HAL_CONFIG_FRAME_RATE: + break; + case HAL_PARAM_UNCOMPRESSED_FORMAT_SELECT: + break; + case HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO: + break; + case HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO: + break; + case HAL_PARAM_EXTRA_DATA_HEADER_CONFIG: + break; + case HAL_PARAM_FRAME_SIZE: + break; + case HAL_CONFIG_REALTIME: + break; + case HAL_PARAM_BUFFER_COUNT_ACTUAL: + break; + case HAL_PARAM_NAL_STREAM_FORMAT_SELECT: + break; + case HAL_PARAM_VDEC_OUTPUT_ORDER: + break; + case HAL_PARAM_VDEC_PICTURE_TYPE_DECODE: + break; + case HAL_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO: + break; + case HAL_CONFIG_VDEC_POST_LOOP_DEBLOCKER: + break; + case HAL_PARAM_VDEC_MULTI_STREAM: + break; + case HAL_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT: + break; + case HAL_PARAM_DIVX_FORMAT: + break; + case HAL_CONFIG_VDEC_MB_ERROR_MAP_REPORTING: + break; + case HAL_PARAM_VDEC_CONTINUE_DATA_TRANSFER: + break; + case HAL_CONFIG_VDEC_MB_ERROR_MAP: + break; + case HAL_CONFIG_VENC_REQUEST_IFRAME: + break; + case HAL_PARAM_VENC_MPEG4_SHORT_HEADER: + break; + case HAL_PARAM_VENC_MPEG4_AC_PREDICTION: + break; + case HAL_CONFIG_VENC_TARGET_BITRATE: + break; + case HAL_PARAM_PROFILE_LEVEL_CURRENT: + break; + case HAL_PARAM_VENC_H264_ENTROPY_CONTROL: + break; + case HAL_PARAM_VENC_RATE_CONTROL: + break; + case HAL_PARAM_VENC_MPEG4_TIME_RESOLUTION: + break; + case HAL_PARAM_VENC_MPEG4_HEADER_EXTENSION: + break; + case HAL_PARAM_VENC_H264_DEBLOCK_CONTROL: + break; + case HAL_PARAM_VENC_TEMPORAL_SPATIAL_TRADEOFF: + break; + case HAL_PARAM_VENC_SESSION_QP: + break; + case HAL_CONFIG_VENC_INTRA_PERIOD: + break; + case HAL_CONFIG_VENC_IDR_PERIOD: + break; + case HAL_CONFIG_VPE_OPERATIONS: + break; + case HAL_PARAM_VENC_INTRA_REFRESH: + break; + case HAL_PARAM_VENC_MULTI_SLICE_CONTROL: + break; + case HAL_CONFIG_VPE_DEINTERLACE: + break; + case HAL_SYS_DEBUG_CONFIG: + break; + /*FOLLOWING PROPERTIES ARE NOT IMPLEMENTED IN CORE YET*/ + case HAL_CONFIG_BUFFER_REQUIREMENTS: + case HAL_CONFIG_PRIORITY: + case HAL_CONFIG_BATCH_INFO: + case HAL_PARAM_METADATA_PASS_THROUGH: + case HAL_SYS_IDLE_INDICATOR: + case HAL_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: + case HAL_PARAM_INTERLACE_FORMAT_SUPPORTED: + case HAL_PARAM_CHROMA_SITE: + case HAL_PARAM_PROPERTIES_SUPPORTED: + case HAL_PARAM_PROFILE_LEVEL_SUPPORTED: + case HAL_PARAM_CAPABILITY_SUPPORTED: + case HAL_PARAM_NAL_STREAM_FORMAT_SUPPORTED: + case HAL_PARAM_MULTI_VIEW_FORMAT: + case HAL_PARAM_MAX_SEQUENCE_HEADER_SIZE: + case HAL_PARAM_CODEC_SUPPORTED: + case HAL_PARAM_VDEC_MULTI_VIEW_SELECT: + case HAL_PARAM_VDEC_MB_QUANTIZATION: + case HAL_PARAM_VDEC_NUM_CONCEALED_MB: + case HAL_PARAM_VDEC_H264_ENTROPY_SWITCHING: + case HAL_PARAM_VENC_SLICE_DELIVERY_MODE: + case HAL_PARAM_VENC_MPEG4_DATA_PARTITIONING: + + case HAL_CONFIG_BUFFER_COUNT_ACTUAL: + case HAL_CONFIG_VDEC_MULTI_STREAM: + case HAL_PARAM_VENC_MULTI_SLICE_INFO: + case HAL_CONFIG_VENC_TIMESTAMP_SCALE: + case HAL_PARAM_VENC_LOW_LATENCY: + default: + HAL_MSG_INFO("DEFAULT: Calling 0x%x", ptype); + break; + } + return 0; +} + +void *vidc_hal_session_init(void *device, u32 session_id, + enum hal_domain session_type, enum hal_video_codec codec_type) +{ + struct hfi_cmd_sys_session_init_packet pkt; + struct hal_session *new_session; + struct hal_device *dev; + + if (device) { + dev = device; + } else { + HAL_MSG_ERROR("%s:invalid device", __func__); + return NULL; + } + + new_session = (struct hal_session *) + kzalloc(sizeof(struct hal_session), GFP_KERNEL); + new_session->session_id = (u32) session_id; + if (session_type == 1) + new_session->is_decoder = 0; + else if (session_type == 2) + new_session->is_decoder = 1; + new_session->device = dev; + list_add_tail(&new_session->list, &dev->sess_head); + pkt.size = sizeof(struct hfi_cmd_sys_session_init_packet); + pkt.packet = HFI_CMD_SYS_SESSION_INIT; + pkt.session_id = (u32) new_session; + pkt.session_domain = session_type; + pkt.session_codec = codec_type; + if (vidc_hal_iface_cmdq_write(dev, &pkt)) + return NULL; + return (void *) new_session; +} + +static int vidc_hal_send_session_cmd(void *session_id, + enum HFI_COMMAND pkt_type) +{ + struct vidc_hal_session_cmd_pkt pkt; + int rc = 0; + struct hal_session *session; + + if (session_id) { + session = session_id; + } else { + HAL_MSG_ERROR("%s:invalid session", __func__); + return -ENODEV; + } + + pkt.size = sizeof(struct vidc_hal_session_cmd_pkt); + pkt.packet_type = pkt_type; + pkt.session_id = (u32) session; + + if (vidc_hal_iface_cmdq_write(session->device, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_end(void *session) +{ + return vidc_hal_send_session_cmd(session, + HFI_CMD_SYS_SESSION_END); +} + +int vidc_hal_session_abort(void *session) +{ + return vidc_hal_send_session_cmd(session, + HFI_CMD_SYS_SESSION_ABORT); +} + +int vidc_hal_session_set_buffers(void *sess, + struct vidc_buffer_addr_info *buffer_info) +{ + struct hfi_cmd_session_set_buffers_packet *pkt; + u8 packet[VIDC_IFACEQ_VAR_LARGE_PKT_SIZE]; + int rc = 0; + u16 i; + struct hal_session *session; + + if (!sess || !buffer_info) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + if (buffer_info->buffer_type == HAL_BUFFER_INPUT) + return 0; + + pkt = (struct hfi_cmd_session_set_buffers_packet *)packet; + + pkt->size = sizeof(struct hfi_cmd_session_set_buffers_packet) + + ((buffer_info->num_buffers - 1) * sizeof(u32)); + pkt->packet_type = HFI_CMD_SESSION_SET_BUFFERS; + pkt->session_id = (u32) session; + pkt->buffer_mode = HFI_BUFFER_MODE_STATIC; + pkt->buffer_size = buffer_info->buffer_size; + pkt->min_buffer_size = buffer_info->buffer_size; + pkt->num_buffers = buffer_info->num_buffers; + + if ((buffer_info->buffer_type == HAL_BUFFER_OUTPUT) || + (buffer_info->buffer_type == HAL_BUFFER_OUTPUT2)) { + struct hfi_buffer_info *buff; + pkt->extradata_size = buffer_info->extradata_size; + pkt->size = sizeof(struct hfi_cmd_session_set_buffers_packet) - + sizeof(u32) + ((buffer_info->num_buffers) * + sizeof(struct hfi_buffer_info)); + buff = (struct hfi_buffer_info *) pkt->rg_buffer_info; + for (i = 0; i < pkt->num_buffers; i++) { + buff->buffer_addr = + buffer_info->align_device_addr; + buff->extradata_addr = + buffer_info->extradata_addr; + } + } else { + pkt->extradata_size = 0; + pkt->size = sizeof(struct hfi_cmd_session_set_buffers_packet) + + ((buffer_info->num_buffers - 1) * sizeof(u32)); + for (i = 0; i < pkt->num_buffers; i++) + pkt->rg_buffer_info[i] = + buffer_info->align_device_addr; + } + + if (buffer_info->buffer_type == HAL_BUFFER_INTERNAL_SCRATCH) + pkt->buffer_type = HFI_BUFFER_INTERNAL_SCRATCH; + else if (buffer_info->buffer_type == HAL_BUFFER_INTERNAL_PERSIST) + pkt->buffer_type = HFI_BUFFER_INTERNAL_PERSIST; + else + pkt->buffer_type = (enum HFI_BUFFER) buffer_info->buffer_type; + + if (vidc_hal_iface_cmdq_write(session->device, pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_release_buffers(void *sess, + struct vidc_buffer_addr_info *buffer_info) +{ + struct hfi_cmd_session_release_buffer_packet *pkt; + u8 packet[VIDC_IFACEQ_VAR_LARGE_PKT_SIZE]; + int rc = 0; + u32 i; + struct hal_session *session; + + if (!sess || !buffer_info) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + if (buffer_info->buffer_type == HAL_BUFFER_INPUT) + return 0; + + pkt = (struct hfi_cmd_session_release_buffer_packet *) packet; + pkt->size = sizeof(struct hfi_cmd_session_release_buffer_packet) + + ((buffer_info->num_buffers - 1) * sizeof(u32)); + pkt->packet_type = HFI_CMD_SESSION_RELEASE_BUFFERS; + pkt->session_id = (u32) session; + pkt->buffer_type = (enum HFI_BUFFER) buffer_info->buffer_type; + pkt->buffer_size = buffer_info->buffer_size; + pkt->num_buffers = buffer_info->num_buffers; + + if ((buffer_info->buffer_type == HAL_BUFFER_OUTPUT) || + (buffer_info->buffer_type == HAL_BUFFER_OUTPUT2)) { + struct hfi_buffer_info *buff; + buff = (struct hfi_buffer_info *) pkt->rg_buffer_info; + for (i = 0; i < pkt->num_buffers; i++) { + buff->buffer_addr = + buffer_info->align_device_addr; + buff->extradata_addr = + buffer_info->extradata_addr; + } + pkt->extradata_size = buffer_info->extradata_size; + pkt->size = sizeof(struct hfi_cmd_session_set_buffers_packet) - + sizeof(u32) + ((buffer_info->num_buffers) * + sizeof(struct hfi_buffer_info)); + } else { + for (i = 0; i < pkt->num_buffers; i++) + pkt->rg_buffer_info[i] = + buffer_info->align_device_addr; + pkt->extradata_size = 0; + pkt->size = sizeof(struct hfi_cmd_session_set_buffers_packet) + + ((buffer_info->num_buffers - 1) * sizeof(u32)); + } + + if (vidc_hal_iface_cmdq_write(session->device, pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_load_res(void *sess) +{ + return vidc_hal_send_session_cmd(sess, + HFI_CMD_SESSION_LOAD_RESOURCES); +} + +int vidc_hal_session_release_res(void *sess) +{ + return vidc_hal_send_session_cmd(sess, + HFI_CMD_SESSION_RELEASE_RESOURCES); +} + +int vidc_hal_session_start(void *sess) +{ + return vidc_hal_send_session_cmd(sess, + HFI_CMD_SESSION_START); +} + +int vidc_hal_session_stop(void *sess) +{ + return vidc_hal_send_session_cmd(sess, + HFI_CMD_SESSION_STOP); +} + +int vidc_hal_session_suspend(void *sess) +{ + return vidc_hal_send_session_cmd(sess, + HFI_CMD_SESSION_SUSPEND); +} + +int vidc_hal_session_resume(void *sess) +{ + return vidc_hal_send_session_cmd(sess, + HFI_CMD_SESSION_RESUME); +} + +int vidc_hal_session_etb(void *sess, struct vidc_frame_data *input_frame) +{ + int rc = 0; + struct hal_session *session; + + if (!sess || !input_frame) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + if (session->is_decoder) { + struct hfi_cmd_session_empty_buffer_compressed_packet pkt; + pkt.size = sizeof( + struct hfi_cmd_session_empty_buffer_compressed_packet); + pkt.packet_type = HFI_CMD_SESSION_EMPTY_BUFFER; + pkt.session_id = (u32) session; + pkt.timestamp_hi = (int) (((u64)input_frame->timestamp) >> 32); + pkt.timestamp_lo = (int) input_frame->timestamp; + pkt.flags = input_frame->flags; + pkt.mark_target = input_frame->mark_target; + pkt.mark_data = input_frame->mark_data; + pkt.offset = input_frame->offset; + pkt.alloc_len = input_frame->alloc_len; + pkt.filled_len = input_frame->filled_len; + pkt.input_tag = input_frame->clnt_data; + pkt.packet_buffer = (u8 *) input_frame->device_addr; + HAL_MSG_ERROR("### Q DECODER INPUT BUFFER ###"); + if (vidc_hal_iface_cmdq_write(session->device, &pkt)) + rc = -ENOTEMPTY; + } else { + struct hfi_cmd_session_empty_buffer_uncompressed_plane0_packet + pkt; + pkt.size = sizeof(struct + hfi_cmd_session_empty_buffer_uncompressed_plane0_packet); + pkt.packet = HFI_CMD_SESSION_EMPTY_BUFFER; + pkt.session_id = (u32) session; + pkt.view_id = 0; + pkt.timestamp_hi = (u32) (((u64)input_frame->timestamp) >> 32); + pkt.timestamp_lo = (u32) input_frame->timestamp; + pkt.flags = input_frame->flags; + pkt.mark_target = input_frame->mark_target; + pkt.mark_data = input_frame->mark_data; + pkt.offset = input_frame->offset; + pkt.alloc_len = input_frame->alloc_len; + pkt.filled_len = input_frame->filled_len; + pkt.input_tag = input_frame->clnt_data; + pkt.packet_buffer = (u8 *) input_frame->device_addr; + HAL_MSG_ERROR("### Q ENCODER INPUT BUFFER ###"); + if (vidc_hal_iface_cmdq_write(session->device, &pkt)) + rc = -ENOTEMPTY; + } + return rc; +} + +int vidc_hal_session_ftb(void *sess, + struct vidc_frame_data *output_frame) +{ + struct hfi_cmd_session_fill_buffer_packet pkt; + int rc = 0; + struct hal_session *session; + + if (!sess || !output_frame) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + pkt.size = sizeof(struct hfi_cmd_session_fill_buffer_packet); + pkt.packet_type = HFI_CMD_SESSION_FILL_BUFFER; + pkt.session_id = (u32) session; + if (output_frame->buffer_type == HAL_BUFFER_OUTPUT) + pkt.stream_id = 0; + else if (output_frame->buffer_type == HAL_BUFFER_OUTPUT2) + pkt.stream_id = 1; + pkt.packet_buffer = (u8 *) output_frame->device_addr; + pkt.extra_data_buffer = + (u8 *) output_frame->extradata_addr; + + HAL_MSG_INFO("### Q OUTPUT BUFFER ###"); + if (vidc_hal_iface_cmdq_write(session->device, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_parse_seq_hdr(void *sess, + struct vidc_seq_hdr *seq_hdr) +{ + struct hfi_cmd_session_parse_sequence_header_packet *pkt; + int rc = 0; + u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; + struct hal_session *session; + + if (!sess || !seq_hdr) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + pkt = (struct hfi_cmd_session_parse_sequence_header_packet *) packet; + pkt->size = sizeof(struct hfi_cmd_session_parse_sequence_header_packet); + pkt->packet_type = HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER; + pkt->session_id = (u32) session; + pkt->header_len = seq_hdr->seq_hdr_len; + pkt->packet_buffer = seq_hdr->seq_hdr; + + if (vidc_hal_iface_cmdq_write(session->device, pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_get_seq_hdr(void *sess, + struct vidc_seq_hdr *seq_hdr) +{ + struct hfi_cmd_session_get_sequence_header_packet *pkt; + int rc = 0; + u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; + struct hal_session *session; + + if (!sess || !seq_hdr) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return -EINVAL; + } else { + session = sess; + } + + pkt = (struct hfi_cmd_session_get_sequence_header_packet *) packet; + pkt->size = sizeof(struct hfi_cmd_session_get_sequence_header_packet); + pkt->packet_type = HFI_CMD_SESSION_GET_SEQUENCE_HEADER; + pkt->session_id = (u32) session; + pkt->buffer_len = seq_hdr->seq_hdr_len; + pkt->packet_buffer = seq_hdr->seq_hdr; + + if (vidc_hal_iface_cmdq_write(session->device, pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_get_buf_req(void *sess) +{ + struct hfi_cmd_session_get_property_packet pkt; + int rc = 0; + struct hal_session *session; + + if (sess) { + session = sess; + } else { + HAL_MSG_ERROR("%s:invalid session", __func__); + return -ENODEV; + } + + pkt.size = sizeof(struct hfi_cmd_session_get_property_packet); + pkt.packet_type = HFI_CMD_SESSION_GET_PROPERTY; + pkt.session_id = (u32) session; + pkt.num_properties = 1; + pkt.rg_property_data[0] = HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS; + if (vidc_hal_iface_cmdq_write(session->device, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +int vidc_hal_session_flush(void *sess, enum hal_flush flush_mode) +{ + struct hfi_cmd_session_flush_packet pkt; + int rc = 0; + struct hal_session *session; + + if (sess) { + session = sess; + } else { + HAL_MSG_ERROR("%s:invalid session", __func__); + return -ENODEV; + } + + pkt.size = sizeof(struct hfi_cmd_session_flush_packet); + pkt.packet_type = HFI_CMD_SESSION_FLUSH; + pkt.session_id = (u32) session; + pkt.flush_type = flush_mode; + + if (vidc_hal_iface_cmdq_write(session->device, &pkt)) + rc = -ENOTEMPTY; + return rc; +} + +static int vidc_hal_check_core_registered( + struct hal_device_data core, u32 fw_addr, + u32 reg_addr, u32 reg_size, u32 irq) +{ + struct hal_device *device; + struct list_head *curr, *next; + + if (core.dev_count) { + list_for_each_safe(curr, next, &core.dev_head) { + device = list_entry(curr, struct hal_device, list); + if (device && device->hal_data->irq == irq && + (CONTAINS((u32)device->hal_data-> + device_base_addr, + FIRMWARE_SIZE, fw_addr) || + CONTAINS(fw_addr, FIRMWARE_SIZE, + (u32)device->hal_data-> + device_base_addr) || + CONTAINS((u32)device->hal_data-> + register_base_addr, + reg_size, reg_addr) || + CONTAINS(reg_addr, reg_size, + (u32)device->hal_data-> + register_base_addr) || + OVERLAPS((u32)device->hal_data-> + register_base_addr, + reg_size, reg_addr, reg_size) || + OVERLAPS(reg_addr, reg_size, + (u32)device->hal_data-> + register_base_addr, reg_size) || + OVERLAPS((u32)device->hal_data-> + device_base_addr, + FIRMWARE_SIZE, fw_addr, + FIRMWARE_SIZE) || + OVERLAPS(fw_addr, FIRMWARE_SIZE, + (u32)device->hal_data-> + device_base_addr, + FIRMWARE_SIZE))) { + return 0; + } else { + HAL_MSG_INFO("Device not registered"); + return -EINVAL; + } + } + } else { + HAL_MSG_INFO("no device Registered"); + } + return -EINVAL; +} + +static void vidc_hal_core_work_handler(struct work_struct *work) +{ + struct hal_device *device = list_first_entry( + &hal_ctxt.dev_head, struct hal_device, list); + + HAL_MSG_INFO(" GOT INTERRUPT %s() ", __func__); + if (!device->callback) { + HAL_MSG_ERROR("No callback function " + "to process interrupt: %p\n", device); + return; + } + vidc_hal_core_clear_interrupt(device); + vidc_hal_response_handler(device); + enable_irq(device->hal_data->irq); +} +static DECLARE_WORK(vidc_hal_work, vidc_hal_core_work_handler); + +static irqreturn_t vidc_hal_isr(int irq, void *dev) +{ + struct hal_device *device = dev; + HAL_MSG_MEDIUM("\n vidc_hal_isr() %d ", irq); + disable_irq_nosync(irq); + queue_work(device->vidc_workq, &vidc_hal_work); + HAL_MSG_MEDIUM("\n vidc_hal_isr() %d ", irq); + return IRQ_HANDLED; +} + +void *vidc_hal_add_device(u32 device_id, u32 fw_base_addr, u32 reg_base, + u32 reg_size, u32 irq, + void (*callback) (enum command_response cmd, void *data)) +{ + struct hal_device *hdevice = NULL; + struct hal_data *hal = NULL; + int rc = 0; + + if (device_id || !fw_base_addr || !reg_base || !reg_size || + !irq || !callback) { + HAL_MSG_ERROR("Invalid Paramters"); + return NULL; + } else { + HAL_MSG_INFO("entered %s, device_id: %d", __func__, device_id); + } + + if (vidc_hal_check_core_registered(hal_ctxt, fw_base_addr, + reg_base, reg_size, irq)) { + HAL_MSG_LOW("HAL_DATA will be assigned now"); + hal = (struct hal_data *) + kzalloc(sizeof(struct hal_data), GFP_KERNEL); + if (!hal) { + HAL_MSG_ERROR("Failed to alloc"); + return NULL; + } + hal->irq = irq; + hal->device_base_addr = + ioremap_nocache(fw_base_addr, FIRMWARE_SIZE); + if (!hal->device_base_addr) { + HAL_MSG_ERROR("could not map fw addr %d of size %d", + fw_base_addr, FIRMWARE_SIZE); + goto err_map; + } + hal->register_base_addr = + ioremap_nocache(reg_base, reg_size); + if (!hal->register_base_addr) { + HAL_MSG_ERROR("could not map reg addr %d of size %d", + reg_base, reg_size); + goto err_map; + } + INIT_LIST_HEAD(&hal_ctxt.dev_head); + } else { + HAL_MSG_ERROR("Core present/Already added"); + return NULL; + } + + hdevice = (struct hal_device *) + kzalloc(sizeof(struct hal_device), GFP_KERNEL); + if (!hdevice) { + HAL_MSG_ERROR("failed to allocate new device"); + goto err_map; + } + + INIT_LIST_HEAD(&hdevice->list); + list_add_tail(&hdevice->list, &hal_ctxt.dev_head); + hal_ctxt.dev_count++; + hdevice->device_id = device_id; + hdevice->hal_data = hal; + hdevice->callback = callback; + + hdevice->vidc_workq = create_singlethread_workqueue( + "msm_vidc_workerq"); + if (!hdevice->vidc_workq) { + HAL_MSG_ERROR("%s: create workq failed\n", __func__); + goto error_createq; + } + + rc = request_irq(irq, vidc_hal_isr, IRQF_TRIGGER_HIGH, + "msm_vidc", hdevice); + if (unlikely(rc)) { + HAL_MSG_ERROR("%s() :request_irq failed\n", __func__); + goto error_irq_fail; + } + disable_irq_nosync(irq); + return (void *) hdevice; +error_irq_fail: + destroy_workqueue(hdevice->vidc_workq); +error_createq: + hal_ctxt.dev_count--; + list_del(&hal_ctxt.dev_head); +err_map: + kfree(hal); + return NULL; +} + +void vidc_hal_delete_device(void *device) +{ + struct hal_device *close, *dev; + + if (device) { + dev = (struct hal_device *) device; + list_for_each_entry(close, &hal_ctxt.dev_head, list) { + if (close->hal_data->irq == dev->hal_data->irq) { + hal_ctxt.dev_count--; + free_irq(dev->hal_data->irq, NULL); + list_del(&close->list); + destroy_workqueue(close->vidc_workq); + kfree(close->hal_data); + kfree(close); + break; + } + } + + } +} diff --git a/drivers/media/video/msm_vidc/vidc_hal.h b/drivers/media/video/msm_vidc/vidc_hal.h new file mode 100644 index 0000000000000000000000000000000000000000..166ed0d2225d5e1c5b29436d62fc9c1fe4d70f47 --- /dev/null +++ b/drivers/media/video/msm_vidc/vidc_hal.h @@ -0,0 +1,1618 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_HAL_H__ +#define __VIDC_HAL_H__ + +#include +#include +#include "vidc_hal_api.h" +#include "msm_smem.h" + +#ifdef HAL_MSG_LOG +#define HAL_MSG_LOW(x...) pr_debug(KERN_INFO x) +#define HAL_MSG_MEDIUM(x...) pr_debug(KERN_INFO x) +#define HAL_MSG_HIGH(x...) pr_debug(KERN_INFO x) +#else +#define HAL_MSG_LOW(x...) +#define HAL_MSG_MEDIUM(x...) +#define HAL_MSG_HIGH(x...) +#endif + +#define HAL_MSG_ERROR(x...) pr_err(KERN_INFO x) +#define HAL_MSG_FATAL(x...) pr_err(KERN_INFO x) +#define HAL_MSG_INFO(x...) pr_info(KERN_INFO x) + +#define HFI_MASK_QHDR_TX_TYPE 0xFF000000 +#define HFI_MASK_QHDR_RX_TYPE 0x00FF0000 +#define HFI_MASK_QHDR_PRI_TYPE 0x0000FF00 +#define HFI_MASK_QHDR_Q_ID_TYPE 0x000000FF +#define HFI_Q_ID_HOST_TO_CTRL_CMD_Q 0x00 +#define HFI_Q_ID_CTRL_TO_HOST_MSG_Q 0x01 +#define HFI_Q_ID_CTRL_TO_HOST_DEBUG_Q 0x02 +#define HFI_MASK_QHDR_STATUS 0x000000FF + +#define VIDC_MAX_UNCOMPRESSED_FMT_PLANES 3 + +#define VIDC_IFACEQ_NUMQ 3 +#define VIDC_IFACEQ_CMDQ_IDX 0 +#define VIDC_IFACEQ_MSGQ_IDX 1 +#define VIDC_IFACEQ_DBGQ_IDX 2 + +#define VIDC_IFACEQ_MAX_PKT_SIZE 1024 +#define VIDC_IFACEQ_MED_PKT_SIZE 768 +#define VIDC_IFACEQ_MIN_PKT_SIZE 8 +#define VIDC_IFACEQ_VAR_SMALL_PKT_SIZE 100 +#define VIDC_IFACEQ_VAR_LARGE_PKT_SIZE 512 +#define VIDC_IFACEQ_MAX_BUF_COUNT 50 +#define VIDC_IFACE_MAX_PARALLEL_CLNTS 16 +#define VIDC_IFACEQ_DFLT_QHDR 0x01010000 + +struct hfi_queue_table_header { + u32 qtbl_version; + u32 qtbl_size; + u32 qtbl_qhdr0_offset; + u32 qtbl_qhdr_size; + u32 qtbl_num_q; + u32 qtbl_num_active_q; +}; + +struct hfi_queue_header { + u32 qhdr_status; + u32 qhdr_start_addr; + u32 qhdr_type; + u32 qhdr_q_size; + u32 qhdr_pkt_size; + u32 qhdr_pkt_drop_cnt; + u32 qhdr_rx_wm; + u32 qhdr_tx_wm; + u32 qhdr_rx_req; + u32 qhdr_tx_req; + u32 qhdr_rx_irq_status; + u32 qhdr_tx_irq_status; + u32 qhdr_read_idx; + u32 qhdr_write_idx; +}; + +#define VIDC_IFACEQ_TABLE_SIZE (sizeof(struct hfi_queue_table_header) \ + + sizeof(struct hfi_queue_header) * VIDC_IFACEQ_NUMQ) + +#define VIDC_IFACEQ_QUEUE_SIZE (VIDC_IFACEQ_MAX_PKT_SIZE * \ + VIDC_IFACEQ_MAX_BUF_COUNT * VIDC_IFACE_MAX_PARALLEL_CLNTS) + +#define VIDC_IFACEQ_GET_QHDR_START_ADDR(ptr, i) \ + (void *)((((u32)ptr) + sizeof(struct hfi_queue_table_header)) + \ + (i * sizeof(struct hfi_queue_header))) + +enum vidc_hw_reg { + VIDC_HWREG_CTRL_STATUS = 0x1, + VIDC_HWREG_QTBL_INFO = 0x2, + VIDC_HWREG_QTBL_ADDR = 0x3, + VIDC_HWREG_CTRLR_RESET = 0x4, + VIDC_HWREG_IFACEQ_FWRXREQ = 0x5, + VIDC_HWREG_IFACEQ_FWTXREQ = 0x6, + VIDC_HWREG_VHI_SOFTINTEN = 0x7, + VIDC_HWREG_VHI_SOFTINTSTATUS = 0x8, + VIDC_HWREG_VHI_SOFTINTCLR = 0x9, + VIDC_HWREG_HVI_SOFTINTEN = 0xA, +}; + +enum HFI_EVENT { + HFI_EVENT_SYS_ERROR, + HFI_EVENT_SESSION_ERROR, + HFI_EVENT_SESSION_SEQUENCE_CHANGED, + HFI_EVENT_SESSION_PROPERTY_CHANGED, + HFI_UNUSED_EVENT = 0x10000000, +}; + +enum HFI_EVENT_DATA_SEQUENCE_CHANGED { + HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUFFER_RESOURCES, + HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUFFER_RESOURCES, + HFI_UNUSED_SEQCHG = 0x10000000, +}; + +#define HFI_BUFFERFLAG_EOS 0x00000001 +#define HFI_BUFFERFLAG_STARTTIME 0x00000002 +#define HFI_BUFFERFLAG_DECODEONLY 0x00000004 +#define HFI_BUFFERFLAG_DATACORRUPT 0x00000008 +#define HFI_BUFFERFLAG_ENDOFFRAME 0x00000010 +#define HFI_BUFFERFLAG_SYNCFRAME 0x00000020 +#define HFI_BUFFERFLAG_EXTRADATA 0x00000040 +#define HFI_BUFFERFLAG_CODECCONFIG 0x00000080 +#define HFI_BUFFERFLAG_TIMESTAMPINVALID 0x00000100 +#define HFI_BUFFERFLAG_READONLY 0x00000200 +#define HFI_BUFFERFLAG_ENDOFSUBFRAME 0x00000400 + +enum HFI_ERROR { + HFI_ERR_NONE = 0, + HFI_ERR_SYS_UNKNOWN = 0x80000001, + HFI_ERR_SYS_FATAL = 0x80000002, + HFI_ERR_SYS_INVALID_PARAMETER = 0x80000003, + HFI_ERR_SYS_VERSION_MISMATCH = 0x80000004, + HFI_ERR_SYS_INSUFFICIENT_RESOURCES = 0x80000005, + HFI_ERR_SYS_MAX_SESSIONS_REACHED = 0x80000006, + HFI_ERR_SYS_UNSUPPORTED_CODEC = 0x80000007, + HFI_ERR_SYS_SESSION_IN_USE = 0x80000008, + HFI_ERR_SYS_SESSION_ID_OUT_OF_RANGE = 0x80000009, + HFI_ERR_SYS_UNSUPPORTED_DOMAIN = 0x8000000A, + HFI_ERR_SESSION_START_UNUSED = 0x80001000, + HFI_ERR_SESSION_UNKNOWN = 0x80001001, + HFI_ERR_SESSION_FATAL = 0x80001002, + HFI_ERR_SESSION_INVALID_PARAMETER = 0x80001003, + HFI_ERR_SESSION_BAD_POINTER = 0x80001004, + HFI_ERR_SESSION_INVALID_SESSION_ID = 0x80001005, + HFI_ERR_SESSION_INVALID_STREAM_ID = 0x80001006, + HFI_ERR_SESSION_INCORRECT_STATE_OPERATION = 0x80001007, + HFI_ERR_SESSION_UNSUPPORTED_PROPERTY = 0x80001008, + HFI_ERR_SESSION_UNSUPPORTED_SETTING = 0x80001009, + HFI_ERR_SESSION_INSUFFICIENT_RESOURCES = 0x8000100A, + HFI_ERR_SESSION_STREAM_CORRUPT = 0x8000100B, + HFI_ERR_SESSION_STREAM_CORRUPT_OUTPUT_STALLED = 0x8000100C, + HFI_ERR_SESSION_SYNC_FRAME_NOT_DETECTED = 0x8000100D, + HFI_ERR_SESSION_EMPTY_BUFFER_DONE_OUTPUT_PENDING = 0x8000100E, + HFI_ERR_SESSION_SAME_STATE_OPERATION = 0x8000100F, + HFI_UNUSED_ERR = 0x10000000, +}; + +enum HFI_DOMAIN { + HFI_VIDEO_DOMAIN_VPE, + HFI_VIDEO_DOMAIN_ENCODER, + HFI_VIDEO_DOMAIN_DECODER, + HFI_UNUSED_DOMAIN = 0x10000000, +}; + +enum HFI_VIDEO_CODEC { + HFI_VIDEO_CODEC_UNKNOWN = 0x00000000, + HFI_VIDEO_CODEC_H264 = 0x00000002, + HFI_VIDEO_CODEC_H263 = 0x00000004, + HFI_VIDEO_CODEC_MPEG1 = 0x00000008, + HFI_VIDEO_CODEC_MPEG2 = 0x00000010, + HFI_VIDEO_CODEC_MPEG4 = 0x00000020, + HFI_VIDEO_CODEC_DIVX_311 = 0x00000040, + HFI_VIDEO_CODEC_DIVX = 0x00000080, + HFI_VIDEO_CODEC_VC1 = 0x00000100, + HFI_VIDEO_CODEC_SPARK = 0x00000200, + HFI_VIDEO_CODEC_VP6 = 0x00000400, + HFI_VIDEO_CODEC_VP7 = 0x00000800, + HFI_VIDEO_CODEC_VP8 = 0x00001000, + HFI_UNUSED_CODEC = 0x10000000, +}; + +enum HFI_H263_PROFILE { + HFI_H263_PROFILE_BASELINE = 0x00000001, + HFI_H263_PROFILE_H320CODING = 0x00000002, + HFI_H263_PROFILE_BACKWARDCOMPATIBLE = 0x00000004, + HFI_H263_PROFILE_ISWV2 = 0x00000008, + HFI_H263_PROFILE_ISWV3 = 0x00000010, + HFI_H263_PROFILE_HIGHCOMPRESSION = 0x00000020, + HFI_H263_PROFILE_INTERNET = 0x00000040, + HFI_H263_PROFILE_INTERLACE = 0x00000080, + HFI_H263_PROFILE_HIGHLATENCY = 0x00000100, + HFI_UNUSED_H263_PROFILE = 0x10000000, +}; + +enum HFI_H263_LEVEL { + HFI_H263_LEVEL_10 = 0x00000001, + HFI_H263_LEVEL_20 = 0x00000002, + HFI_H263_LEVEL_30 = 0x00000004, + HFI_H263_LEVEL_40 = 0x00000008, + HFI_H263_LEVEL_45 = 0x00000010, + HFI_H263_LEVEL_50 = 0x00000020, + HFI_H263_LEVEL_60 = 0x00000040, + HFI_H263_LEVEL_70 = 0x00000080, + HFI_UNUSED_H263_LEVEL = 0x10000000, +}; + +enum HFI_MPEG2_PROFILE { + HFI_MPEG2_PROFILE_SIMPLE = 0x00000001, + HFI_MPEG2_PROFILE_MAIN = 0x00000002, + HFI_MPEG2_PROFILE_422 = 0x00000004, + HFI_MPEG2_PROFILE_SNR = 0x00000008, + HFI_MPEG2_PROFILE_SPATIAL = 0x00000010, + HFI_MPEG2_PROFILE_HIGH = 0x00000020, + HFI_UNUSED_MPEG2_PROFILE = 0x10000000, +}; + +enum HFI_MPEG2_LEVEL { + HFI_MPEG2_LEVEL_LL = 0x00000001, + HFI_MPEG2_LEVEL_ML = 0x00000002, + HFI_MPEG2_LEVEL_H14 = 0x00000004, + HFI_MPEG2_LEVEL_HL = 0x00000008, + HFI_UNUSED_MEPG2_LEVEL = 0x10000000, +}; + +enum HFI_MPEG4_PROFILE { + HFI_MPEG4_PROFILE_SIMPLE = 0x00000001, + HFI_MPEG4_PROFILE_SIMPLESCALABLE = 0x00000002, + HFI_MPEG4_PROFILE_CORE = 0x00000004, + HFI_MPEG4_PROFILE_MAIN = 0x00000008, + HFI_MPEG4_PROFILE_NBIT = 0x00000010, + HFI_MPEG4_PROFILE_SCALABLETEXTURE = 0x00000020, + HFI_MPEG4_PROFILE_SIMPLEFACE = 0x00000040, + HFI_MPEG4_PROFILE_SIMPLEFBA = 0x00000080, + HFI_MPEG4_PROFILE_BASICANIMATED = 0x00000100, + HFI_MPEG4_PROFILE_HYBRID = 0x00000200, + HFI_MPEG4_PROFILE_ADVANCEDREALTIME = 0x00000400, + HFI_MPEG4_PROFILE_CORESCALABLE = 0x00000800, + HFI_MPEG4_PROFILE_ADVANCEDCODING = 0x00001000, + HFI_MPEG4_PROFILE_ADVANCEDCORE = 0x00002000, + HFI_MPEG4_PROFILE_ADVANCEDSCALABLE = 0x00004000, + HFI_MPEG4_PROFILE_ADVANCEDSIMPLE = 0x00008000, + HFI_UNUSED_MPEG4_PROFILE = 0x10000000, +}; + +enum HFI_MPEG4_LEVEL { + HFI_MPEG4_LEVEL_0 = 0x00000001, + HFI_MPEG4_LEVEL_0b = 0x00000002, + HFI_MPEG4_LEVEL_1 = 0x00000004, + HFI_MPEG4_LEVEL_2 = 0x00000008, + HFI_MPEG4_LEVEL_3 = 0x00000010, + HFI_MPEG4_LEVEL_4 = 0x00000020, + HFI_MPEG4_LEVEL_4a = 0x00000040, + HFI_MPEG4_LEVEL_5 = 0x00000080, + HFI_MPEG4_LEVEL_VENDOR_START_UNUSED = 0x7F000000, + HFI_MPEG4_LEVEL_6 = 0x7F000001, + HFI_MPEG4_LEVEL_7 = 0x7F000002, + HFI_MPEG4_LEVEL_8 = 0x7F000003, + HFI_MPEG4_LEVEL_9 = 0x7F000004, + HFI_MPEG4_LEVEL_3b = 0x7F000005, + HFI_UNUSED_MPEG4_LEVEL = 0x10000000, +}; + +enum HFI_H264_PROFILE { + HFI_H264_PROFILE_BASELINE = 0x00000001, + HFI_H264_PROFILE_MAIN = 0x00000002, + HFI_H264_PROFILE_EXTENDED = 0x00000004, + HFI_H264_PROFILE_HIGH = 0x00000008, + HFI_H264_PROFILE_HIGH10 = 0x00000010, + HFI_H264_PROFILE_HIGH422 = 0x00000020, + HFI_H264_PROFILE_HIGH444 = 0x00000040, + HFI_H264_PROFILE_STEREO_HIGH = 0x00000080, + HFI_H264_PROFILE_MV_HIGH = 0x00000100, + HFI_UNUSED_H264_PROFILE = 0x10000000, +}; + +enum HFI_H264_LEVEL { + HFI_H264_LEVEL_1 = 0x00000001, + HFI_H264_LEVEL_1b = 0x00000002, + HFI_H264_LEVEL_11 = 0x00000004, + HFI_H264_LEVEL_12 = 0x00000008, + HFI_H264_LEVEL_13 = 0x00000010, + HFI_H264_LEVEL_2 = 0x00000020, + HFI_H264_LEVEL_21 = 0x00000040, + HFI_H264_LEVEL_22 = 0x00000080, + HFI_H264_LEVEL_3 = 0x00000100, + HFI_H264_LEVEL_31 = 0x00000200, + HFI_H264_LEVEL_32 = 0x00000400, + HFI_H264_LEVEL_4 = 0x00000800, + HFI_H264_LEVEL_41 = 0x00001000, + HFI_H264_LEVEL_42 = 0x00002000, + HFI_H264_LEVEL_5 = 0x00004000, + HFI_H264_LEVEL_51 = 0x00008000, + HFI_UNUSED_H264_LEVEL = 0x10000000, +}; + +enum HFI_VPX_PROFILE { + HFI_VPX_PROFILE_SIMPLE = 0x00000001, + HFI_VPX_PROFILE_ADVANCED = 0x00000002, + HFI_VPX_PROFILE_VERSION_0 = 0x00000004, + HFI_VPX_PROFILE_VERSION_1 = 0x00000008, + HFI_VPX_PROFILE_VERSION_2 = 0x00000010, + HFI_VPX_PROFILE_VERSION_3 = 0x00000020, + HFI_VPX_PROFILE_UNUSED = 0x10000000, +}; + +enum HFI_VC1_PROFILE { + HFI_VC1_PROFILE_SIMPLE = 0x00000001, + HFI_VC1_PROFILE_MAIN = 0x00000002, + HFI_VC1_PROFILE_ADVANCED = 0x00000004, + HFI_UNUSED_VC1_PROFILE = 0x10000000, +}; + +enum HFI_VC1_LEVEL { + HFI_VC1_LEVEL_LOW = 0x00000001, + HFI_VC1_LEVEL_MEDIUM = 0x00000002, + HFI_VC1_LEVEL_HIGH = 0x00000004, + HFI_VC1_LEVEL_0 = 0x00000008, + HFI_VC1_LEVEL_1 = 0x00000010, + HFI_VC1_LEVEL_2 = 0x00000020, + HFI_VC1_LEVEL_3 = 0x00000040, + HFI_VC1_LEVEL_4 = 0x00000080, + HFI_UNUSED_VC1_LEVEL = 0x10000000, +}; + +enum HFI_DIVX_FORMAT { + HFI_DIVX_FORMAT_4, + HFI_DIVX_FORMAT_5, + HFI_DIVX_FORMAT_6, + HFI_UNUSED_DIVX_FORMAT = 0x10000000, +}; + +enum HFI_DIVX_PROFILE { + HFI_DIVX_PROFILE_QMOBILE = 0x00000001, + HFI_DIVX_PROFILE_MOBILE = 0x00000002, + HFI_DIVX_PROFILE_MT = 0x00000004, + HFI_DIVX_PROFILE_HT = 0x00000008, + HFI_DIVX_PROFILE_HD = 0x00000010, + HFI_UNUSED_DIVX_PROFILE = 0x10000000, +}; + +enum HFI_BUFFER { + HFI_BUFFER_INPUT, + HFI_BUFFER_OUTPUT, + HFI_BUFFER_OUTPUT2, + HFI_BUFFER_EXTRADATA_INPUT, + HFI_BUFFER_EXTRADATA_OUTPUT, + HFI_BUFFER_EXTRADATA_OUTPUT2, + HFI_BUFFER_INTERNAL_SCRATCH = 0x7F000001, + HFI_BUFFER_INTERNAL_PERSIST = 0x7F000002, + HFI_UNUSED_BUFFER = 0x10000000, +}; + +enum HFI_BUFFER_MODE { + HFI_BUFFER_MODE_STATIC, + HFI_BUFFER_MODE_RING, + HFI_UNUSED_BUFFER_MODE = 0x10000000, +}; + +enum HFI_FLUSH { + HFI_FLUSH_INPUT, + HFI_FLUSH_OUTPUT, + HFI_FLUSH_OUTPUT2, + HFI_FLUSH_ALL, + HFI_UNUSED_FLUSH = 0x10000000, +}; + +enum HFI_EXTRADATA { + HFI_EXTRADATA_NONE = 0x00000000, + HFI_EXTRADATA_MB_QUANTIZATION = 0x00000001, + HFI_EXTRADATA_INTERLACE_VIDEO = 0x00000002, + HFI_EXTRADATA_VC1_FRAMEDISP = 0x00000003, + HFI_EXTRADATA_VC1_SEQDISP = 0x00000004, + HFI_EXTRADATA_TIMESTAMP = 0x00000005, + HFI_EXTRADATA_MULTISLICE_INFO = 0x7F100000, + HFI_EXTRADATA_NUM_CONCEALED_MB = 0x7F100001, + HFI_EXTRADATA_INDEX = 0x7F100002, + HFI_EXTRADATA_METADATA_FILLER = 0x7FE00002, + HFI_UNUSED_EXTRADATA = 0x10000000, +}; + +enum HFI_EXTRADATA_INDEX_TYPE { + HFI_INDEX_EXTRADATA_INPUT_CROP = 0x0700000E, + HFI_INDEX_EXTRADATA_DIGITAL_ZOOM = 0x07000010, + HFI_INDEX_EXTRADATA_ASPECT_RATIO = 0x7F100003, +}; + +struct hfi_extradata_header { + u32 size; + u32 version; + u32 port_tndex; + enum HFI_EXTRADATA type; + u32 data_size; + u8 rg_data[1]; +}; + +enum HFI_INTERLACE_FORMAT { + HFI_INTERLACE_FRAME_PROGRESSIVE = 0x01, + HFI_INTERLACE_INTERLEAVE_FRAME_TOPFIELDFIRST = 0x02, + HFI_INTERLACE_INTERLEAVE_FRAME_BOTTOMFIELDFIRST = 0x04, + HFI_INTERLACE_FRAME_TOPFIELDFIRST = 0x08, + HFI_INTERLACE_FRAME_BOTTOMFIELDFIRST = 0x10, + HFI_UNUSED_INTERLACE = 0x10000000, +}; + +enum HFI_PROPERTY { + HFI_PROPERTY_SYS_UNUSED = 0x08000000, + HFI_PROPERTY_SYS_IDLE_INDICATOR, + HFI_PROPERTY_SYS_DEBUG_CONFIG, + HFI_PROPERTY_SYS_RESOURCE_OCMEM_REQUIREMENT_INFO, + HFI_PROPERTY_PARAM_UNUSED = 0x04000000, + HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL, + HFI_PROPERTY_PARAM_FRAME_SIZE, + HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT, + HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED, + HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO, + HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO, + HFI_PROPERTY_PARAM_INTERLACE_FORMAT_SUPPORTED, + HFI_PROPERTY_PARAM_CHROMA_SITE, + HFI_PROPERTY_PARAM_EXTRA_DATA_HEADER_CONFIG, + HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT, + HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED, + HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED, + HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SUPPORTED, + HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT, + HFI_PROPERTY_PARAM_MULTI_VIEW_FORMAT, + HFI_PROPERTY_PARAM_PROPERTIES_SUPPORTED, + HFI_PROPERTY_PARAM_MAX_SEQUENCE_HEADER_SIZE, + HFI_PROPERTY_PARAM_CODEC_SUPPORTED, + HFI_PROPERTY_PARAM_DIVX_FORMAT, + + HFI_PROPERTY_CONFIG_UNUSED = 0x02000000, + HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS, + HFI_PROPERTY_CONFIG_REALTIME, + HFI_PROPERTY_CONFIG_PRIORITY, + HFI_PROPERTY_CONFIG_BATCH_INFO, + HFI_PROPERTY_CONFIG_FRAME_RATE, + + HFI_PROPERTY_PARAM_VDEC_UNUSED = 0x01000000, + HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER, + HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT, + HFI_PROPERTY_PARAM_VDEC_MULTI_VIEW_SELECT, + HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE, + HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM, + HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER, + HFI_PROPERTY_PARAM_VDEC_MB_QUANTIZATION, + HFI_PROPERTY_PARAM_VDEC_NUM_CONCEALED_MB, + HFI_PROPERTY_PARAM_VDEC_H264_ENTROPY_SWITCHING, + HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO, + + HFI_PROPERTY_CONFIG_VDEC_UNUSED = 0x00800000, + HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER, + HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING, + HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP, + + HFI_PROPERTY_PARAM_VENC_UNUSED = 0x00400000, + HFI_PROPERTY_PARAM_VENC_SLICE_DELIVERY_MODE, + HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL, + HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL, + HFI_PROPERTY_PARAM_VENC_RATE_CONTROL, + HFI_PROPERTY_PARAM_VENC_TEMPORAL_SPATIAL_TRADEOFF, + HFI_PROPERTY_PARAM_VENC_SESSION_QP, + HFI_PROPERTY_PARAM_VENC_MPEG4_AC_PREDICTION, + HFI_PROPERTY_PARAM_VENC_MPEG4_DATA_PARTITIONING, + HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION, + HFI_PROPERTY_PARAM_VENC_MPEG4_SHORT_HEADER, + HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION, + HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_INFO, + HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH, + HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL, + + HFI_PROPERTY_CONFIG_VENC_UNUSED = 0x00200000, + HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE, + HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD, + HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD, + HFI_PROPERTY_CONFIG_VENC_REQUEST_IFRAME, + HFI_PROPERTY_CONFIG_VENC_TIMESTAMP_SCALE, + HFI_PROPERTY_PARAM_VENC_MPEG4_QPEL, + HFI_PROPERTY_PARAM_VENC_ADVANCED, + + HFI_PROPERTY_PARAM_VPE_UNUSED = 0x00100000, + + HFI_PROPERTY_CONFIG_VPE_UNUSED = 0x00080000, + HFI_PROPERTY_CONFIG_VPE_DEINTERLACE, + HFI_PROPERTY_CONFIG_VPE_OPERATIONS, + HFI_PROPERTY_UNUSED = 0x10000000, +}; + +struct hfi_batch_info { + u32 input_batch_count; + u32 output_batch_count; +}; + +struct hfi_bitrate { + u32 bit_rate; +}; + +struct hfi_buffer_count_actual { + enum HFI_BUFFER buffer; + u32 buffer_count_actual; +}; + +struct hfi_buffer_requirements { + enum HFI_BUFFER buffer; + u32 buffer_size; + u32 buffer_region_size; + u32 buffer_hold_count; + u32 buffer_count_min; + u32 buffer_count_actual; + u32 contiguous; + u32 buffer_alignment; +}; + +enum HFI_CAPABILITY { + HFI_CAPABILITY_FRAME_WIDTH, + HFI_CAPABILITY_FRAME_HEIGHT, + HFI_CAPABILITY_MBS_PER_FRAME, + HFI_CAPABILITY_MBS_PER_SECOND, + HFI_CAPABILITY_FRAMERATE, + HFI_CAPABILITY_SCALE_X, + HFI_CAPABILITY_SCALE_Y, + HFI_CAPABILITY_BITRATE, + HFI_UNUSED_CAPABILITY = 0x10000000, +}; + +struct hfi_capability_supported { + enum HFI_CAPABILITY eCapabilityType; + u32 min; + u32 max; + u32 step_size; +}; + +struct hfi_capability_supported_INFO { + u32 num_capabilities; + struct hfi_capability_supported rg_data[1]; +}; + +enum HFI_CHROMA_SITE { + HFI_CHROMA_SITE_0, + HFI_CHROMA_SITE_1, + HFI_UNUSED_CHROMA = 0x10000000, +}; + +struct hfi_data_payload { + u32 size; + u8 rg_data[1]; +}; + +struct hfi_seq_header_info { + u32 max_header_len; +}; + +struct hfi_enable_picture { + u32 picture_type; +}; + +struct hfi_display_picture_buffer_count { + int enable; + u32 count; +}; + +struct hfi_enable { + int enable; +}; + +enum HFI_H264_DB_MODE { + HFI_H264_DB_MODE_DISABLE, + HFI_H264_DB_MODE_SKIP_SLICE_BOUNDARY, + HFI_H264_DB_MODE_ALL_BOUNDARY, + HFI_UNUSED_H264_DB = 0x10000000, +}; + +struct hfi_h264_db_control { + enum HFI_H264_DB_MODE mode; + int slice_alpha_offset; + int slice_beta_offset; +}; + +enum HFI_H264_ENTROPY { + HFI_H264_ENTROPY_CAVLC, + HFI_H264_ENTROPY_CABAC, + HFI_UNUSED_ENTROPY = 0x10000000, +}; + +enum HFI_H264_CABAC_MODEL { + HFI_H264_CABAC_MODEL_0, + HFI_H264_CABAC_MODEL_1, + HFI_H264_CABAC_MODEL_2, + HFI_UNUSED_CABAC = 0x10000000, +}; + +struct hfi_h264_entropy_control { + enum HFI_H264_ENTROPY entropy_mode; + enum HFI_H264_CABAC_MODEL cabac_model; +}; + +struct hfi_extra_data_header_config { + u32 type; + enum HFI_BUFFER buffer_type; + u32 version; + u32 port_index; + u32 client_extradata_id; +}; + +struct hfi_frame_rate { + enum HFI_BUFFER buffer_type; + u32 frame_rate; +}; + +struct hfi_interlace_format_supported { + enum HFI_BUFFER buffer; + enum HFI_INTERLACE_FORMAT format; +}; + +enum hfi_intra_refresh_mode { + HFI_INTRA_REFRESH_NONE, + HFI_INTRA_REFRESH_CYCLIC, + HFI_INTRA_REFRESH_ADAPTIVE, + HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE, + HFI_INTRA_REFRESH_RANDOM, + HFI_UNUSED_INTRA = 0x10000000, +}; + +struct hfi_intra_refresh { + enum hfi_intra_refresh_mode mode; + u32 air_mbs; + u32 air_ref; + u32 cir_mbs; +}; + +struct hfi_idr_period { + u32 idr_period; +}; + +struct hfi_intra_period { + u32 pframes; + u32 bframes; +}; + +struct hfi_timestamp_scale { + u32 time_stamp_scale; +}; + +struct hfi_mb_error_map { + u32 error_map_size; + u8 rg_error_map[1]; +}; + +struct hfi_metadata_pass_through { + int enable; + u32 size; +}; + +struct hfi_mpeg4_header_extension { + u32 header_extension; +}; + +struct hfi_mpeg4_time_resolution { + u32 time_increment_resolution; +}; + +enum HFI_MULTI_SLICE { + HFI_MULTI_SLICE_OFF, + HFI_MULTI_SLICE_BY_MB_COUNT, + HFI_MULTI_SLICE_BY_BYTE_COUNT, + HFI_MULTI_SLICE_GOB, + HFI_UNUSED_SLICE = 0x10000000, +}; + +struct hfi_multi_slice_control { + enum HFI_MULTI_SLICE multi_slice; + u32 slice_size; +}; + +struct hfi_multi_stream { + enum HFI_BUFFER buffer; + u32 enable; + u32 width; + u32 height; +}; + +struct hfi_multi_view_format { + u32 views; + u32 rg_view_order[1]; +}; + +struct hfi_multi_view_select { + u32 view_index; +}; + +enum HFI_NAL_STREAM_FORMAT { + HFI_NAL_FORMAT_STARTCODES = 0x00000001, + HFI_NAL_FORMAT_ONE_NAL_PER_BUFFER = 0x00000002, + HFI_NAL_FORMAT_ONE_BYTE_LENGTH = 0x00000004, + HFI_NAL_FORMAT_TWO_BYTE_LENGTH = 0x00000008, + HFI_NAL_FORMAT_FOUR_BYTE_LENGTH = 0x00000010, + HFI_UNUSED_NAL = 0x10000000, +}; + +struct hfi_nal_stream_format_supported { + u32 nal_stream_format_supported; +}; + +enum HFI_PICTURE { + HFI_PICTURE_I = 0x01, + HFI_PICTURE_P = 0x02, + HFI_PICTURE_B = 0x04, + HFI_PICTURE_IDR = 0x7F001000, + HFI_UNUSED_PICT = 0x10000000, +}; + +enum HFI_PRIORITY { + HFI_PRIORITY_LOW = 10, + HFI_PRIOIRTY_MEDIUM = 20, + HFI_PRIORITY_HIGH = 30, + HFI_UNUSED_PRIORITY = 0x10000000, +}; + +struct hfi_profile_level { + u32 profile; + u32 level; +}; + +struct hfi_profile_level_supported { + u32 profile_count; + struct hfi_profile_level rg_profile_level[1]; +}; + +enum HFI_ROTATE { + HFI_ROTATE_NONE, + HFI_ROTATE_90, + HFI_ROTATE_180, + HFI_ROTATE_270, + HFI_UNUSED_ROTATE = 0x10000000, +}; + +enum HFI_FLIP { + HFI_FLIP_NONE, + HFI_FLIP_HORIZONTAL, + HFI_FLIP_VERTICAL, + HFI_UNUSED_FLIP = 0x10000000, +}; + +struct hfi_operations { + enum HFI_ROTATE rotate; + enum HFI_FLIP flip; +}; + +enum HFI_OUTPUT_ORDER { + HFI_OUTPUT_ORDER_DISPLAY, + HFI_OUTPUT_ORDER_DECODE, + HFI_UNUSED_OUTPUT = 0x10000000, +}; + +struct hfi_quantization { + u32 qp_i; + u32 qp_p; + u32 qp_b; +}; + +enum HFI_RATE_CONTROL { + HFI_RATE_CONTROL_OFF, + HFI_RATE_CONTROL_VBR_VFR, + HFI_RATE_CONTROL_VBR_CFR, + HFI_RATE_CONTROL_CBR_VFR, + HFI_RATE_CONTROL_CBR_CFR, + HFI_UNUSED_RC = 0x10000000, +}; + +struct hfi_slice_delivery_mode { + int enable; +}; + +struct hfi_temporal_spatial_tradeoff { + u32 ts_factor; +}; + +struct hfi_frame_size { + enum HFI_BUFFER buffer; + u32 width; + u32 height; +}; + +enum HFI_UNCOMPRESSED_FORMAT { + HFI_COLOR_FORMAT_MONOCHROME, + HFI_COLOR_FORMAT_NV12, + HFI_COLOR_FORMAT_NV21, + HFI_COLOR_FORMAT_NV12_4x4TILE, + HFI_COLOR_FORMAT_NV21_4x4TILE, + HFI_COLOR_FORMAT_YUYV, + HFI_COLOR_FORMAT_YVYU, + HFI_COLOR_FORMAT_UYVY, + HFI_COLOR_FORMAT_VYUY, + HFI_COLOR_FORMAT_RGB565, + HFI_COLOR_FORMAT_BGR565, + HFI_COLOR_FORMAT_RGB888, + HFI_COLOR_FORMAT_BGR888, + HFI_UNUSED_COLOR = 0x10000000, +}; + +struct hfi_uncompressed_format_select { + enum HFI_BUFFER buffer; + enum HFI_UNCOMPRESSED_FORMAT format; +}; + +struct hfi_uncompressed_format_supported { + enum HFI_BUFFER buffer; + u32 format_entries; + u32 rg_format_info[1]; +}; + +struct hfi_uncompressed_plane_actual { + int actual_stride; + u32 actual_plane_buffer_height; +}; + +struct hfi_uncompressed_plane_actual_info { + enum HFI_BUFFER buffer; + u32 num_planes; + struct hfi_uncompressed_plane_actual rg_plane_format[1]; +}; + +struct hfi_uncompressed_plane_constraints { + u32 stride_multiples; + u32 max_stride; + u32 min_plane_buffer_height_multiple; + u32 buffer_alignment; +}; + +struct hfi_uncompressed_plane_info { + enum HFI_UNCOMPRESSED_FORMAT format; + u32 num_planes; + struct hfi_uncompressed_plane_constraints rg_plane_format[1]; +}; + +struct hfi_uncompressed_plane_actual_constraints_info { + enum HFI_BUFFER buffer; + u32 num_planes; + struct hfi_uncompressed_plane_constraints rg_plane_format[1]; +}; + +struct hfi_codec_supported { + u32 decoder_codec_supported; + u32 encoder_codec_supported; +}; + +enum HFI_DEBUG_MSG { + HFI_DEBUG_MSG_LOW = 0x00000001, + HFI_DEBUG_MSG_MEDIUM = 0x00000002, + HFI_DEBUG_MSG_HIGH = 0x00000004, + HFI_DEBUG_MSG_ERROR = 0x00000008, + HFI_DEBUG_MSG_FATAL = 0x00000010, + HFI_UNUSED_DEBUG_MSG = 0x10000000, +}; + +struct hfi_debug_config { + u32 debug_config; +}; + +struct hfi_properties_supported { + u32 num_properties; + u32 rg_properties[1]; +}; + +enum HFI_RESOURCE { + HFI_RESOURCE_OCMEM = 0x00000001, + HFI_UNUSED_RESOURCE = 0x10000000, +}; + +struct hfi_resource_ocmem_type { + u32 size; + u8 *mem; +}; + +struct hfi_resource_ocmem_requirement { + enum HFI_DOMAIN session_domain; + u32 width; + u32 height; + u32 size; +}; + +struct hfi_resource_ocmem_requirement_info { + u32 num_entries; + struct hfi_resource_ocmem_requirement rg_requirements[1]; +}; + +struct hfi_venc_config_advanced { + u8 pipe2d; + u8 hw_mode; + u8 low_delay_enforce; + int h264_constrain_intra_pred; + int h264_transform_8x8_flag; + int mpeg4_qpel_enable; + int multi_refP_en; + int qmatrix_en; + u8 vpp_info_packet_mode; + u8 ref_tile_mode; + u8 bitstream_flush_mode; + u32 ds_display_frame_width; + u32 ds_display_frame_height; + u32 perf_tune_param_ptr; +}; + +enum HFI_COMMAND { + HFI_CMD_SYS_UNUSED = 0x01000000, + HFI_CMD_SYS_INIT, + HFI_CMD_SYS_SESSION_INIT, + HFI_CMD_SYS_SESSION_END, + HFI_CMD_SYS_SESSION_ABORT, + HFI_CMD_SYS_SET_RESOURCE, + HFI_CMD_SYS_RELEASE_RESOURCE, + HFI_CMD_SYS_PING, + HFI_CMD_SYS_PC_PREP, + HFI_CMD_SYS_SET_PROPERTY, + HFI_CMD_SYS_GET_PROPERTY, + + HFI_CMD_SESSION_UNUSED = 0x02000000, + HFI_CMD_SESSION_LOAD_RESOURCES, + HFI_CMD_SESSION_START, + HFI_CMD_SESSION_STOP, + HFI_CMD_SESSION_EMPTY_BUFFER, + HFI_CMD_SESSION_FILL_BUFFER, + HFI_CMD_SESSION_FLUSH, + HFI_CMD_SESSION_SUSPEND, + HFI_CMD_SESSION_RESUME, + HFI_CMD_SESSION_SET_PROPERTY, + HFI_CMD_SESSION_GET_PROPERTY, + HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER, + HFI_CMD_SESSION_GET_SEQUENCE_HEADER, + HFI_CMD_SESSION_SET_BUFFERS, + HFI_CMD_SESSION_RELEASE_BUFFERS, + HFI_CMD_SESSION_RELEASE_RESOURCES, + + HFI_CMD_UNUSED = 0x10000000, +}; + +enum HFI_MESSAGE { + HFI_MSG_SYS_UNUSED = 0x01000000, + HFI_MSG_SYS_IDLE, + HFI_MSG_SYS_PC_PREP_DONE, + HFI_MSG_SYS_RELEASE_RESOURCE, + HFI_MSG_SYS_PING_ACK, + HFI_MSG_SYS_DEBUG, + HFI_MSG_SYS_INIT_DONE, + HFI_MSG_SYS_PROPERTY_INFO, + HFI_MSG_SESSION_UNUSED = 0x02000000, + HFI_MSG_EVENT_NOTIFY, + HFI_MSG_SYS_SESSION_INIT_DONE, + HFI_MSG_SYS_SESSION_END_DONE, + HFI_MSG_SYS_SESSION_ABORT_DONE, + HFI_MSG_SESSION_LOAD_RESOURCES_DONE, + HFI_MSG_SESSION_START_DONE, + HFI_MSG_SESSION_STOP_DONE, + HFI_MSG_SESSION_SUSPEND_DONE, + HFI_MSG_SESSION_RESUME_DONE, + HFI_MSG_SESSION_EMPTY_BUFFER_DONE, + HFI_MSG_SESSION_FILL_BUFFER_DONE, + HFI_MSG_SESSION_FLUSH_DONE, + HFI_MSG_SESSION_PROPERTY_INFO, + HFI_MSG_SESSION_RELEASE_RESOURCES_DONE, + HFI_MSG_SESSION_PARSE_SEQUENCE_HEADER_DONE, + HFI_MSG_SESSION_GET_SEQUENCE_HEADER_DONE, + HFI_MSG_UNUSED = 0x10000000, +}; + +struct vidc_hal_msg_pkt_hdr { + u32 size; + enum HFI_MESSAGE packet; +}; + +struct vidc_hal_session_cmd_pkt { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +enum HFI_STATUS { + HFI_FAIL = 0, + HFI_SUCCESS, + HFI_UNUSED_STATUS = 0x10000000, +}; + +struct hfi_cmd_sys_init_packet { + u32 size; + enum HFI_COMMAND packet; +}; + +struct hfi_cmd_sys_session_init_packet { + u32 size; + enum HFI_COMMAND packet; + u32 session_id; + enum HFI_DOMAIN session_domain; + enum HFI_VIDEO_CODEC session_codec; +}; + +struct hfi_cmd_sys_session_end_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_sys_session_abort_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_sys_pc_prep_packet { + u32 size; + enum HFI_COMMAND packet_type; +}; + +struct hfi_cmd_sys_set_resource_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 resource_handle; + enum HFI_RESOURCE resource_type; + u32 rg_resource_data[1]; +}; + +struct hfi_cmd_sys_release_resource_packet { + u32 size; + enum HFI_COMMAND packet_type; + enum HFI_RESOURCE resource_type; + u32 resource_handle; +}; + +struct hfi_cmd_sys_ping_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 client_data; +}; + +struct hfi_cmd_sys_set_property_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_cmd_sys_get_property_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 num_properties; + enum HFI_PROPERTY rg_property_data[1]; +}; + +struct hfi_cmd_session_load_resources_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_session_start_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_session_stop_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_session_empty_buffer_compressed_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + u32 timestamp_hi; + u32 timestamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 input_tag; + u8 *packet_buffer; +}; + +struct hfi_cmd_session_empty_buffer_uncompressed_plane0_packet { + u32 size; + enum HFI_COMMAND packet; + u32 session_id; + u32 view_id; + u32 timestamp_hi; + u32 timestamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 input_tag; + u8 *packet_buffer; +}; + +struct hfi_cmd_session_empty_buffer_uncompressed_plane1_packet { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u8 *packet_buffer2; +}; + +struct hfi_cmd_session_empty_buffer_uncompressed_plane2_packet { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u8 *packet_buffer3; +}; + +struct hfi_cmd_session_fill_buffer_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + u32 stream_id; + u8 *packet_buffer; + u8 *extra_data_buffer; +}; + +struct hfi_cmd_session_flush_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + enum HFI_FLUSH flush_type; +}; + +struct hfi_cmd_session_suspend_packet { + u32 size; + enum HFI_COMMAND packet; + u32 session_id; +}; + +struct hfi_cmd_session_resume_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_session_set_property_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_cmd_session_get_property_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + u32 num_properties; + enum HFI_PROPERTY rg_property_data[1]; +}; + +struct hfi_buffer_info { + u32 buffer_addr; + u32 extradata_addr; +}; + +struct hfi_cmd_session_set_buffers_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + enum HFI_BUFFER buffer_type; + enum HFI_BUFFER_MODE buffer_mode; + u32 buffer_size; + u32 extradata_size; + u32 min_buffer_size; + u32 num_buffers; + u32 rg_buffer_info[1]; +}; + +struct hfi_cmd_session_release_buffer_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + enum HFI_BUFFER buffer_type; + u32 buffer_size; + u32 extradata_size; + u32 num_buffers; + u32 rg_buffer_info[1]; +}; + +struct hfi_cmd_session_release_resources_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; +}; + +struct hfi_cmd_session_parse_sequence_header_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + u32 header_len; + u8 *packet_buffer; +}; + +struct hfi_cmd_session_get_sequence_header_packet { + u32 size; + enum HFI_COMMAND packet_type; + u32 session_id; + u32 buffer_len; + u8 *packet_buffer; +}; + +struct hfi_msg_event_notify_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_EVENT event_id; + u32 event_data1; + u32 event_data2; + u32 rg_ext_event_data[1]; +}; + +struct hfi_msg_sys_init_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + enum HFI_ERROR error_type; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_msg_sys_session_init_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_msg_sys_session_end_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_sys_session_abort_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_sys_idle_packet { + u32 size; + enum HFI_MESSAGE packet_type; +}; + +struct hfi_msg_sys_pc_prep_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_sys_release_resource_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 resource_handle; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_sys_ping_ack_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 client_data; +}; + +struct hfi_msg_sys_debug_packet { + u32 size; + enum HFI_MESSAGE packet_type; + enum HFI_DEBUG_MSG msg_type; + u32 msg_size; + u32 timestamp_hi; + u32 timestamp_lo; + u8 rg_msg_data[1]; +}; + +struct hfi_msg_sys_property_info_packet { + u32 nsize; + enum HFI_MESSAGE packet_type; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_msg_session_load_resources_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_session_start_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_session_stop_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_session_suspend_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_session_resume_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_msg_session_empty_buffer_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; + u32 offset; + u32 filled_len; + u32 input_tag; + u8 *packet_buffer; +}; + +struct hfi_msg_session_fill_buffer_done_compressed_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + u32 timestamp_hi; + u32 timestamp_lo; + enum HFI_ERROR error_type; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 input_tag; + enum HFI_PICTURE picture_type; + u8 *packet_buffer; + u8 *extra_data_buffer; +}; + +struct hfi_msg_session_fbd_uncompressed_plane0_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + u32 stream_id; + u32 view_id; + enum HFI_ERROR error_type; + u32 timestamp_hi; + u32 timestamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 alloc_len; + u32 filled_len; + u32 oofset; + u32 frame_width; + u32 frame_height; + u32 start_xCoord; + u32 start_yCoord; + u32 input_tag; + u32 input_tag1; + enum HFI_PICTURE picture_type; + u8 *packet_buffer; + u8 *extra_data_buffer; +}; + +struct hfi_msg_session_fill_buffer_done_uncompressed_plane1_packet { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u8 *packet_buffer; +}; + +struct hfi_msg_session_fill_buffer_done_uncompressed_plane2_packet { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u8 *packet_buffer; +}; + +struct hfi_msg_session_flush_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; + enum HFI_FLUSH flush_type; +}; + +struct hfi_msg_session_parse_sequence_header_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_msg_session_get_sequence_header_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; + u32 header_len; + u8 *sequence_header; +}; + +struct hfi_msg_session_property_info_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + u32 num_properties; + u32 rg_property_data[1]; +}; + +struct hfi_msg_session_release_resources_done_packet { + u32 size; + enum HFI_MESSAGE packet_type; + u32 session_id; + enum HFI_ERROR error_type; +}; + +struct hfi_extradata_mb_quantization_payload { + u8 rg_mb_qp[1]; +}; + +struct hfi_extradata_vc1_pswnd { + u32 ps_wnd_h_offset; + u32 ps_wndv_offset; + u32 ps_wnd_width; + u32 ps_wnd_height; +}; + +struct hfi_extradata_vc1_framedisp_payload { + u32 res_pic; + u32 ref; + u32 range_map_present; + u32 range_map_y; + u32 range_map_uv; + u32 num_pan_scan_wnds; + struct hfi_extradata_vc1_pswnd rg_ps_wnd[1]; +}; + +struct hfi_extradata_vc1_seqdisp_payload { + u32 prog_seg_frm; + u32 uv_sampling_fmt; + u32 color_fmt_flag; + u32 color_primaries; + u32 transfer_char; + u32 mat_coeff; + u32 aspect_ratio; + u32 aspect_horiz; + u32 aspect_vert; +}; + +struct hfi_extradata_timestamp_payload { + u32 timestamp_low; + u32 timestamp_high; +}; + +struct hfi_extradata_interlace_video_payload { + enum HFI_INTERLACE_FORMAT format; +}; + +enum HFI_S3D_FP_LAYOUT { + HFI_S3D_FP_LAYOUT_NONE, + HFI_S3D_FP_LAYOUT_INTRLV_CHECKERBOARD, + HFI_S3D_FP_LAYOUT_INTRLV_COLUMN, + HFI_S3D_FP_LAYOUT_INTRLV_ROW, + HFI_S3D_FP_LAYOUT_SIDEBYSIDE, + HFI_S3D_FP_LAYOUT_TOPBOTTOM, + HFI_S3D_FP_LAYOUT_UNUSED = 0x10000000, +}; + +enum HFI_S3D_FP_VIEW_ORDER { + HFI_S3D_FP_LEFTVIEW_FIRST, + HFI_S3D_FP_RIGHTVIEW_FIRST, + HFI_S3D_FP_UNKNOWN, + HFI_S3D_FP_VIEWORDER_UNUSED = 0x10000000, +}; + +enum HFI_S3D_FP_FLIP { + HFI_S3D_FP_FLIP_NONE, + HFI_S3D_FP_FLIP_LEFT_HORIZ, + HFI_S3D_FP_FLIP_LEFT_VERT, + HFI_S3D_FP_FLIP_RIGHT_HORIZ, + HFI_S3D_FP_FLIP_RIGHT_VERT, + HFI_S3D_FP_FLIP_UNUSED = 0x10000000, +}; + +struct hfi_extradata_s3d_frame_packing_payload { + enum HFI_S3D_FP_LAYOUT eLayout; + enum HFI_S3D_FP_VIEW_ORDER eOrder; + enum HFI_S3D_FP_FLIP eFlip; + int bQuinCunx; + u32 nLeftViewLumaSiteX; + u32 nLeftViewLumaSiteY; + u32 nRightViewLumaSiteX; + u32 nRightViewLumaSiteY; +}; + +struct hfi_extradata_num_concealed_mb_payload { + u32 num_mb_concealed; +}; + +struct hfi_extradata_sliceinfo { + u32 offset_in_stream; + u32 slice_length; +}; + +struct hfi_extradata_multislice_info_payload { + u32 num_slices; + struct hfi_extradata_sliceinfo rg_slice_info[1]; +}; + +struct hfi_index_extradata_input_crop_payload { + u32 size; + u32 version; + u32 port_index; + u32 left; + u32 top; + u32 width; + u32 height; +}; + +struct hfi_index_extradata_digital_zoom_payload { + u32 size; + u32 version; + u32 port_index; + int width; + int height; +}; + +struct vidc_mem_addr { + u8 *align_device_addr; + u8 *align_virtual_addr; + u32 mem_size; + struct msm_smem *mem_data; +}; + +struct vidc_iface_q_info { + void *q_hdr; + struct vidc_mem_addr q_array; +}; + +/* Internal data used in vidc_hal not exposed to msm_vidc*/ + +struct hal_data { + u32 irq; + u8 *device_base_addr; + u8 *register_base_addr; +}; + +struct hal_device { + struct list_head list; + struct list_head sess_head; + u32 intr_status; + u32 device_id; + spinlock_t read_lock; + spinlock_t write_lock; + void (*callback) (u32 response, void *callback); + struct vidc_mem_addr iface_q_table; + struct vidc_iface_q_info iface_queues[VIDC_IFACEQ_NUMQ]; + struct smem_client *hal_client; + struct hal_data *hal_data; + struct workqueue_struct *vidc_workq; + int spur_count; + int reg_count; +}; + +struct hal_session { + struct list_head list; + u32 session_id; + u32 is_decoder; + struct hal_device *device; +}; + +struct hal_device_data { + struct list_head dev_head; + int dev_count; +}; + +extern struct hal_device_data hal_ctxt; + +int vidc_hal_iface_msgq_read(struct hal_device *device, void *pkt); +int vidc_hal_iface_dbgq_read(struct hal_device *device, void *pkt); + +/* Interrupt Processing:*/ +void vidc_hal_response_handler(struct hal_device *device); + +#endif /*__VIDC_HAL_H__ */ diff --git a/drivers/media/video/msm_vidc/vidc_hal_api.h b/drivers/media/video/msm_vidc/vidc_hal_api.h new file mode 100644 index 0000000000000000000000000000000000000000..036091b45bd75ca27255df96d8e2b531f24635a4 --- /dev/null +++ b/drivers/media/video/msm_vidc/vidc_hal_api.h @@ -0,0 +1,975 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_HAL_API_H__ +#define __VIDC_HAL_API_H__ + +#include + +#define CONTAINS(__a, __sz, __t) ({\ + int __rc = __t >= __a && \ + __t < __a + __sz; \ + __rc; \ +}) + +#define OVERLAPS(__t, __tsz, __a, __asz) ({\ + int __rc = __t <= __a && \ + __t + __tsz >= __a + __asz; \ + __rc; \ +}) + +#define HAL_BUFFERFLAG_EOS 0x00000001 +#define HAL_BUFFERFLAG_STARTTIME 0x00000002 +#define HAL_BUFFERFLAG_DECODEONLY 0x00000004 +#define HAL_BUFFERFLAG_DATACORRUPT 0x00000008 +#define HAL_BUFFERFLAG_ENDOFFRAME 0x00000010 +#define HAL_BUFFERFLAG_SYNCFRAME 0x00000020 +#define HAL_BUFFERFLAG_EXTRADATA 0x00000040 +#define HAL_BUFFERFLAG_CODECCONFIG 0x00000080 +#define HAL_BUFFERFLAG_TIMESTAMPINVALID 0x00000100 +#define HAL_BUFFERFLAG_READONLY 0x00000200 +#define HAL_BUFFERFLAG_ENDOFSUBFRAME 0x00000400 + +enum vidc_status { + VIDC_ERR_NONE = 0x0, + VIDC_ERR_FAIL = 0x80000000, + VIDC_ERR_ALLOC_FAIL, + VIDC_ERR_ILLEGAL_OP, + VIDC_ERR_BAD_PARAM, + VIDC_ERR_BAD_HANDLE, + VIDC_ERR_NOT_SUPPORTED, + VIDC_ERR_BAD_STATE, + VIDC_ERR_MAX_CLIENT, + VIDC_ERR_IFRAME_EXPECTED, + VIDC_ERR_HW_FATAL, + VIDC_ERR_BITSTREAM_ERR, + VIDC_ERR_INDEX_NOMORE, + VIDC_ERR_SEQHDR_PARSE_FAIL, + VIDC_ERR_INSUFFICIENT_BUFFER, + VIDC_ERR_BAD_POWER_STATE, + VIDC_ERR_NO_VALID_SESSION, + VIDC_ERR_TIMEOUT, + VIDC_ERR_CMDQFULL, + VIDC_ERR_CLIENT_PRESENT = 0x90000001, + VIDC_ERR_CLIENT_FATAL, + VIDC_ERR_CMD_QUEUE_FULL, + VIDC_ERR_UNUSED = 0x10000000 +}; + +enum hal_property { + HAL_CONFIG_FRAME_RATE = 0x04000001, + HAL_PARAM_UNCOMPRESSED_FORMAT_SELECT, + HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO, + HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO, + HAL_PARAM_EXTRA_DATA_HEADER_CONFIG, + HAL_PARAM_FRAME_SIZE, + HAL_CONFIG_REALTIME, + HAL_PARAM_BUFFER_COUNT_ACTUAL, + HAL_PARAM_NAL_STREAM_FORMAT_SELECT, + HAL_PARAM_VDEC_OUTPUT_ORDER, + HAL_PARAM_VDEC_PICTURE_TYPE_DECODE, + HAL_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO, + HAL_CONFIG_VDEC_POST_LOOP_DEBLOCKER, + HAL_PARAM_VDEC_MULTI_STREAM, + HAL_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT, + HAL_PARAM_DIVX_FORMAT, + HAL_CONFIG_VDEC_MB_ERROR_MAP_REPORTING, + HAL_PARAM_VDEC_CONTINUE_DATA_TRANSFER, + HAL_CONFIG_VDEC_MB_ERROR_MAP, + HAL_CONFIG_VENC_REQUEST_IFRAME, + HAL_PARAM_VENC_MPEG4_SHORT_HEADER, + HAL_PARAM_VENC_MPEG4_AC_PREDICTION, + HAL_CONFIG_VENC_TARGET_BITRATE, + HAL_PARAM_PROFILE_LEVEL_CURRENT, + HAL_PARAM_VENC_H264_ENTROPY_CONTROL, + HAL_PARAM_VENC_RATE_CONTROL, + HAL_PARAM_VENC_MPEG4_TIME_RESOLUTION, + HAL_PARAM_VENC_MPEG4_HEADER_EXTENSION, + HAL_PARAM_VENC_H264_DEBLOCK_CONTROL, + HAL_PARAM_VENC_TEMPORAL_SPATIAL_TRADEOFF, + HAL_PARAM_VENC_SESSION_QP, + HAL_CONFIG_VENC_INTRA_PERIOD, + HAL_CONFIG_VENC_IDR_PERIOD, + HAL_CONFIG_VPE_OPERATIONS, + HAL_PARAM_VENC_INTRA_REFRESH, + HAL_PARAM_VENC_MULTI_SLICE_CONTROL, + HAL_CONFIG_VPE_DEINTERLACE, + HAL_SYS_DEBUG_CONFIG, + HAL_CONFIG_BUFFER_REQUIREMENTS, + HAL_CONFIG_PRIORITY, + HAL_CONFIG_BATCH_INFO, + HAL_PARAM_METADATA_PASS_THROUGH, + HAL_SYS_IDLE_INDICATOR, + HAL_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED, + HAL_PARAM_INTERLACE_FORMAT_SUPPORTED, + HAL_PARAM_CHROMA_SITE, + HAL_PARAM_PROPERTIES_SUPPORTED, + HAL_PARAM_PROFILE_LEVEL_SUPPORTED, + HAL_PARAM_CAPABILITY_SUPPORTED, + HAL_PARAM_NAL_STREAM_FORMAT_SUPPORTED, + HAL_PARAM_MULTI_VIEW_FORMAT, + HAL_PARAM_MAX_SEQUENCE_HEADER_SIZE, + HAL_PARAM_CODEC_SUPPORTED, + HAL_PARAM_VDEC_MULTI_VIEW_SELECT, + HAL_PARAM_VDEC_MB_QUANTIZATION, + HAL_PARAM_VDEC_NUM_CONCEALED_MB, + HAL_PARAM_VDEC_H264_ENTROPY_SWITCHING, + HAL_PARAM_VENC_SLICE_DELIVERY_MODE, + HAL_PARAM_VENC_MPEG4_DATA_PARTITIONING, + HAL_CONFIG_BUFFER_COUNT_ACTUAL, + HAL_CONFIG_VDEC_MULTI_STREAM, + HAL_PARAM_VENC_MULTI_SLICE_INFO, + HAL_CONFIG_VENC_TIMESTAMP_SCALE, + HAL_PARAM_VENC_LOW_LATENCY, +}; + +enum hal_domain { + HAL_VIDEO_DOMAIN_VPE, + HAL_VIDEO_DOMAIN_ENCODER, + HAL_VIDEO_DOMAIN_DECODER, + HAL_UNUSED_DOMAIN = 0x10000000, +}; + +enum hal_video_codec { + HAL_VIDEO_CODEC_UNKNOWN = 0x00000000, + HAL_VIDEO_CODEC_MVC = 0x00000001, + HAL_VIDEO_CODEC_H264 = 0x00000002, + HAL_VIDEO_CODEC_H263 = 0x00000004, + HAL_VIDEO_CODEC_MPEG1 = 0x00000008, + HAL_VIDEO_CODEC_MPEG2 = 0x00000010, + HAL_VIDEO_CODEC_MPEG4 = 0x00000020, + HAL_VIDEO_CODEC_DIVX_311 = 0x00000040, + HAL_VIDEO_CODEC_DIVX = 0x00000080, + HAL_VIDEO_CODEC_VC1 = 0x00000100, + HAL_VIDEO_CODEC_SPARK = 0x00000200, + HAL_VIDEO_CODEC_VP6 = 0x00000400, + HAL_VIDEO_CODEC_VP7 = 0x00000800, + HAL_VIDEO_CODEC_VP8 = 0x00001000, + HAL_UNUSED_CODEC = 0x10000000, +}; + +enum hal_h263_profile { + HAL_H263_PROFILE_BASELINE = 0x00000001, + HAL_H263_PROFILE_H320CODING = 0x00000002, + HAL_H263_PROFILE_BACKWARDCOMPATIBLE = 0x00000004, + HAL_H263_PROFILE_ISWV2 = 0x00000008, + HAL_H263_PROFILE_ISWV3 = 0x00000010, + HAL_H263_PROFILE_HIGHCOMPRESSION = 0x00000020, + HAL_H263_PROFILE_INTERNET = 0x00000040, + HAL_H263_PROFILE_INTERLACE = 0x00000080, + HAL_H263_PROFILE_HIGHLATENCY = 0x00000100, + HAL_UNUSED_H263_PROFILE = 0x10000000, +}; + +enum hal_h263_level { + HAL_H263_LEVEL_10 = 0x00000001, + HAL_H263_LEVEL_20 = 0x00000002, + HAL_H263_LEVEL_30 = 0x00000004, + HAL_H263_LEVEL_40 = 0x00000008, + HAL_H263_LEVEL_45 = 0x00000010, + HAL_H263_LEVEL_50 = 0x00000020, + HAL_H263_LEVEL_60 = 0x00000040, + HAL_H263_LEVEL_70 = 0x00000080, + HAL_UNUSED_H263_LEVEL = 0x10000000, +}; + +enum hal_mpeg2_profile { + HAL_MPEG2_PROFILE_SIMPLE = 0x00000001, + HAL_MPEG2_PROFILE_MAIN = 0x00000002, + HAL_MPEG2_PROFILE_422 = 0x00000004, + HAL_MPEG2_PROFILE_SNR = 0x00000008, + HAL_MPEG2_PROFILE_SPATIAL = 0x00000010, + HAL_MPEG2_PROFILE_HIGH = 0x00000020, + HAL_UNUSED_MPEG2_PROFILE = 0x10000000, +}; + +enum hal_mpeg2_level { + HAL_MPEG2_LEVEL_LL = 0x00000001, + HAL_MPEG2_LEVEL_ML = 0x00000002, + HAL_MPEG2_LEVEL_H14 = 0x00000004, + HAL_MPEG2_LEVEL_HL = 0x00000008, + HAL_UNUSED_MEPG2_LEVEL = 0x10000000, +}; + +enum hal_mpeg4_profile { + HAL_MPEG4_PROFILE_SIMPLE = 0x00000001, + HAL_MPEG4_PROFILE_SIMPLESCALABLE = 0x00000002, + HAL_MPEG4_PROFILE_CORE = 0x00000004, + HAL_MPEG4_PROFILE_MAIN = 0x00000008, + HAL_MPEG4_PROFILE_NBIT = 0x00000010, + HAL_MPEG4_PROFILE_SCALABLETEXTURE = 0x00000020, + HAL_MPEG4_PROFILE_SIMPLEFACE = 0x00000040, + HAL_MPEG4_PROFILE_SIMPLEFBA = 0x00000080, + HAL_MPEG4_PROFILE_BASICANIMATED = 0x00000100, + HAL_MPEG4_PROFILE_HYBRID = 0x00000200, + HAL_MPEG4_PROFILE_ADVANCEDREALTIME = 0x00000400, + HAL_MPEG4_PROFILE_CORESCALABLE = 0x00000800, + HAL_MPEG4_PROFILE_ADVANCEDCODING = 0x00001000, + HAL_MPEG4_PROFILE_ADVANCEDCORE = 0x00002000, + HAL_MPEG4_PROFILE_ADVANCEDSCALABLE = 0x00004000, + HAL_MPEG4_PROFILE_ADVANCEDSIMPLE = 0x00008000, + HAL_UNUSED_MPEG4_PROFILE = 0x10000000, +}; + +enum hal_mpeg4_level { + HAL_MPEG4_LEVEL_0 = 0x00000001, + HAL_MPEG4_LEVEL_0b = 0x00000002, + HAL_MPEG4_LEVEL_1 = 0x00000004, + HAL_MPEG4_LEVEL_2 = 0x00000008, + HAL_MPEG4_LEVEL_3 = 0x00000010, + HAL_MPEG4_LEVEL_4 = 0x00000020, + HAL_MPEG4_LEVEL_4a = 0x00000040, + HAL_MPEG4_LEVEL_5 = 0x00000080, + HAL_MPEG4_LEVEL_VENDOR_START_UNUSED = 0x7F000000, + HAL_MPEG4_LEVEL_6 = 0x7F000001, + HAL_MPEG4_LEVEL_7 = 0x7F000002, + HAL_MPEG4_LEVEL_8 = 0x7F000003, + HAL_MPEG4_LEVEL_9 = 0x7F000004, + HAL_MPEG4_LEVEL_3b = 0x7F000005, + HAL_UNUSED_MPEG4_LEVEL = 0x10000000, +}; + +enum hal_h264_profile { + HAL_H264_PROFILE_BASELINE = 0x00000001, + HAL_H264_PROFILE_MAIN = 0x00000002, + HAL_H264_PROFILE_EXTENDED = 0x00000004, + HAL_H264_PROFILE_HIGH = 0x00000008, + HAL_H264_PROFILE_HIGH10 = 0x00000010, + HAL_H264_PROFILE_HIGH422 = 0x00000020, + HAL_H264_PROFILE_HIGH444 = 0x00000040, + HAL_UNUSED_H264_PROFILE = 0x10000000, +}; + +enum hal_h264_level { + HAL_H264_LEVEL_1 = 0x00000001, + HAL_H264_LEVEL_1b = 0x00000002, + HAL_H264_LEVEL_11 = 0x00000004, + HAL_H264_LEVEL_12 = 0x00000008, + HAL_H264_LEVEL_13 = 0x00000010, + HAL_H264_LEVEL_2 = 0x00000020, + HAL_H264_LEVEL_21 = 0x00000040, + HAL_H264_LEVEL_22 = 0x00000080, + HAL_H264_LEVEL_3 = 0x00000100, + HAL_H264_LEVEL_31 = 0x00000200, + HAL_H264_LEVEL_32 = 0x00000400, + HAL_H264_LEVEL_4 = 0x00000800, + HAL_H264_LEVEL_41 = 0x00001000, + HAL_H264_LEVEL_42 = 0x00002000, + HAL_H264_LEVEL_5 = 0x00004000, + HAL_H264_LEVEL_51 = 0x00008000, + HAL_UNUSED_H264_LEVEL = 0x10000000, +}; + +enum hal_vpx_profile { + HAL_VPX_PROFILE_SIMPLE = 0x00000001, + HAL_VPX_PROFILE_ADVANCED = 0x00000002, + HAL_VPX_PROFILE_VERSION_0 = 0x00000004, + HAL_VPX_PROFILE_VERSION_1 = 0x00000008, + HAL_VPX_PROFILE_VERSION_2 = 0x00000010, + HAL_VPX_PROFILE_VERSION_3 = 0x00000020, + HAL_VPX_PROFILE_UNUSED = 0x10000000, +}; + +enum hal_vc1_profile { + HAL_VC1_PROFILE_SIMPLE = 0x00000001, + HAL_VC1_PROFILE_MAIN = 0x00000002, + HAL_VC1_PROFILE_ADVANCED = 0x00000004, + HAL_UNUSED_VC1_PROFILE = 0x10000000, +}; + +enum hal_vc1_level { + HAL_VC1_LEVEL_LOW = 0x00000001, + HAL_VC1_LEVEL_MEDIUM = 0x00000002, + HAL_VC1_LEVEL_HIGH = 0x00000004, + HAL_VC1_LEVEL_0 = 0x00000008, + HAL_VC1_LEVEL_1 = 0x00000010, + HAL_VC1_LEVEL_2 = 0x00000020, + HAL_VC1_LEVEL_3 = 0x00000040, + HAL_VC1_LEVEL_4 = 0x00000080, + HAL_UNUSED_VC1_LEVEL = 0x10000000, +}; + +enum hal_divx_format { + HAL_DIVX_FORMAT_4, + HAL_DIVX_FORMAT_5, + HAL_DIVX_FORMAT_6, + HAL_UNUSED_DIVX_FORMAT = 0x10000000, +}; + +enum hal_divx_profile { + HAL_DIVX_PROFILE_QMOBILE = 0x00000001, + HAL_DIVX_PROFILE_MOBILE = 0x00000002, + HAL_DIVX_PROFILE_MT = 0x00000004, + HAL_DIVX_PROFILE_HT = 0x00000008, + HAL_DIVX_PROFILE_HD = 0x00000010, + HAL_UNUSED_DIVX_PROFILE = 0x10000000, +}; + +enum hal_mvc_profile { + HAL_MVC_PROFILE_STEREO_HIGH = 0x00000001, + HAL_MVC_PROFILE_MV_HIGH = 0x00000002, + HAL_UNUSED_MVC_PROFILE = 0x10000000, +}; + +enum hal_mvc_level { + HAL_MVC_LEVEL_1 = 0x00000001, + HAL_MVC_LEVEL_1b = 0x00000002, + HAL_MVC_LEVEL_11 = 0x00000004, + HAL_MVC_LEVEL_12 = 0x00000008, + HAL_MVC_LEVEL_13 = 0x00000010, + HAL_MVC_LEVEL_2 = 0x00000020, + HAL_MVC_LEVEL_21 = 0x00000040, + HAL_MVC_LEVEL_22 = 0x00000080, + HAL_MVC_LEVEL_3 = 0x00000100, + HAL_MVC_LEVEL_31 = 0x00000200, + HAL_MVC_LEVEL_32 = 0x00000400, + HAL_MVC_LEVEL_4 = 0x00000800, + HAL_MVC_LEVEL_41 = 0x00001000, + HAL_MVC_LEVEL_42 = 0x00002000, + HAL_MVC_LEVEL_5 = 0x00004000, + HAL_MVC_LEVEL_51 = 0x00008000, + HAL_UNUSED_MVC_LEVEL = 0x10000000, +}; + +enum hal_buffer { + HAL_BUFFER_INPUT, + HAL_BUFFER_OUTPUT, + HAL_BUFFER_OUTPUT2, + HAL_BUFFER_EXTRADATA_INPUT, + HAL_BUFFER_EXTRADATA_OUTPUT, + HAL_BUFFER_EXTRADATA_OUTPUT2, + HAL_BUFFER_INTERNAL_SCRATCH, + HAL_BUFFER_INTERNAL_PERSIST, + HAL_UNUSED_BUFFER = 0x10000000, +}; + +struct hal_frame_rate { + enum hal_buffer buffer_type; + u32 frame_rate; +}; + +enum hal_uncompressed_format { + HAL_COLOR_FORMAT_MONOCHROME, + HAL_COLOR_FORMAT_NV12, + HAL_COLOR_FORMAT_NV21, + HAL_COLOR_FORMAT_NV12_4x4TILE, + HAL_COLOR_FORMAT_NV21_4x4TILE, + HAL_COLOR_FORMAT_YUYV, + HAL_COLOR_FORMAT_YVYU, + HAL_COLOR_FORMAT_UYVY, + HAL_COLOR_FORMAT_VYUY, + HAL_COLOR_FORMAT_RGB565, + HAL_COLOR_FORMAT_BGR565, + HAL_COLOR_FORMAT_RGB888, + HAL_COLOR_FORMAT_BGR888, + HAL_UNUSED_COLOR = 0x10000000, +}; + +struct hal_uncompressed_format_select { + enum hal_buffer buffer_type; + enum hal_uncompressed_format format; +}; + +struct hal_uncompressed_plane_actual { + int actual_stride; + u32 actual_plane_buffer_height; +}; + +struct hal_uncompressed_plane_actual_info { + enum hal_buffer buffer_type; + u32 num_planes; + struct hal_uncompressed_plane_actual rg_plane_format[1]; +}; + +struct hal_uncompressed_plane_constraints { + u32 stride_multiples; + u32 max_stride; + u32 min_plane_buffer_height_multiple; + u32 buffer_alignment; +}; + +struct hal_uncompressed_plane_actual_constraints_info { + enum hal_buffer buffer_type; + u32 num_planes; + struct hal_uncompressed_plane_constraints rg_plane_format[1]; +}; + +struct hal_extra_data_header_config { + u32 type; + enum hal_buffer buffer_type; + u32 version; + u32 port_index; + u32 client_extradata_id; +}; + +struct hal_frame_size { + enum hal_buffer buffer_type; + u32 width; + u32 height; +}; + +struct hal_enable { + u32 enable; +}; + +struct hal_buffer_count_actual { + enum hal_buffer buffer_type; + u32 buffer_count_actual; +}; + +enum hal_nal_stream_format { + HAL_NAL_FORMAT_STARTCODES = 0x00000001, + HAL_NAL_FORMAT_ONE_NAL_PER_BUFFER = 0x00000002, + HAL_NAL_FORMAT_ONE_BYTE_LENGTH = 0x00000004, + HAL_NAL_FORMAT_TWO_BYTE_LENGTH = 0x00000008, + HAL_NAL_FORMAT_FOUR_BYTE_LENGTH = 0x00000010, +}; + +enum hal_output_order { + HAL_OUTPUT_ORDER_DISPLAY, + HAL_OUTPUT_ORDER_DECODE, + HAL_UNUSED_OUTPUT = 0x10000000, +}; + +enum hal_picture { + HAL_PICTURE_I = 0x01, + HAL_PICTURE_P = 0x02, + HAL_PICTURE_B = 0x04, + HAL_PICTURE_IDR = 0x7F001000, + HAL_FRAME_NOTCODED = 0x7F002000, + HAL_FRAME_YUV = 0x7F004000, + HAL_UNUSED_PICT = 0x10000000, +}; + +struct hal_enable_picture { + u32 picture_type; +}; + +struct hal_multi_stream { + enum hal_buffer buffer_type; + u32 enable; + u32 width; + u32 height; +}; + +struct hal_display_picture_buffer_count { + u32 enable; + u32 count; +}; + +struct hal_mb_error_map { + u32 error_map_size; + u8 rg_error_map[1]; +}; + +struct hal_request_iframe { + u32 enable; +}; + +struct hal_bitrate { + u32 bit_rate; +}; + +struct hal_profile_level { + u32 profile; + u32 level; +}; +/* +struct hal_profile_level_range { + u32 profile; + u32 min_level; + u32 max_level; +} + +struct hal_profile_level_supported { + u32 profile_count; + struct hal_profile_level_range profile_level[1]; +}; +*/ +enum hal_h264_entropy { + HAL_H264_ENTROPY_CAVLC, + HAL_H264_ENTROPY_CABAC, + HAL_UNUSED_ENTROPY = 0x10000000, +}; + +enum hal_h264_cabac_model { + HAL_H264_CABAC_MODEL_0, + HAL_H264_CABAC_MODEL_1, + HAL_H264_CABAC_MODEL_2, + HAL_UNUSED_CABAC = 0x10000000, +}; + +struct hal_h264_entropy_control { + enum hal_h264_entropy entropy_mode; + enum hal_h264_cabac_model cabac_model; +}; + +enum hal_rate_control { + HAL_RATE_CONTROL_OFF, + HAL_RATE_CONTROL_VBR_VFR, + HAL_RATE_CONTROL_VBR_CFR, + HAL_RATE_CONTROL_CBR_VFR, + HAL_RATE_CONTROL_CBR_CFR, + HAL_UNUSED_RC = 0x10000000, +}; + +struct hal_mpeg4_time_resolution { + u32 time_increment_resolution; +}; + +struct hal_mpeg4_header_extension { + u32 header_extension; +}; + +enum hal_h264_db_mode { + HAL_H264_DB_MODE_DISABLE, + HAL_H264_DB_MODE_SKIP_SLICE_BOUNDARY, + HAL_H264_DB_MODE_ALL_BOUNDARY, + HAL_UNUSED_H264_DB = 0x10000000, +}; + +struct hal_h264_db_control { + enum hal_h264_db_mode mode; + int slice_alpha_offset; + int slicebeta_offset; +}; + +struct hal_temporal_spatial_tradeoff { + u32 ts_factor; +}; + +struct hal_quantization { + u32 qpi; + u32 qpp; + u32 qpb; +}; + +struct hal_intra_period { + u32 pframes; + u32 bframes; +}; + +struct hal_idr_period { + u32 idr_period; +}; + +enum hal_rotate { + HAL_ROTATE_NONE, + HAL_ROTATE_90, + HAL_ROTATE_180, + HAL_ROTATE_270, + HAL_UNUSED_ROTATE = 0x10000000, +}; + +enum hal_flip { + HAL_FLIP_NONE, + HAL_FLIP_HORIZONTAL, + HAL_FLIP_VERTICAL, + HAL_UNUSED_FLIP = 0x10000000, +}; + +struct hal_operations { + enum hal_rotate rotate; + enum hal_flip flip; +}; + +enum hal_intra_refresh_mode { + HAL_INTRA_REFRESH_NONE, + HAL_INTRA_REFRESH_CYCLIC, + HAL_INTRA_REFRESH_ADAPTIVE, + HAL_INTRA_REFRESH_CYCLIC_ADAPTIVE, + HAL_INTRA_REFRESH_RANDOM, + HAL_UNUSED_INTRA = 0x10000000, +}; + +struct hal_intra_refresh { + enum hal_intra_refresh_mode mode; + u32 air_mbs; + u32 air_ref; + u32 cir_mbs; +}; + +enum hal_multi_slice { + HAL_MULTI_SLICE_OFF, + HAL_MULTI_SLICE_BY_MB_COUNT, + HAL_MULTI_SLICE_BY_BYTE_COUNT, + HAL_MULTI_SLICE_GOB, + HAL_UNUSED_SLICE = 0x10000000, +}; + +struct hal_multi_slice_control { + enum hal_multi_slice multi_slice; + u32 slice_size; +}; + +struct hal_debug_config { + u32 debug_config; +}; + +struct hal_buffer_requirements { + enum hal_buffer buffer_type; + u32 buffer_size; + u32 buffer_region_size; + u32 buffer_hold_count; + u32 buffer_count_min; + u32 buffer_count_actual; + u32 contiguous; + u32 buffer_alignment; +}; + +enum hal_priority {/* Priority increases with number */ + HAL_PRIORITY_LOW = 10, + HAL_PRIOIRTY_MEDIUM = 20, + HAL_PRIORITY_HIGH = 30, + HAL_UNUSED_PRIORITY = 0x10000000, +}; + +struct hal_batch_info { + u32 input_batch_count; + u32 output_batch_count; +}; + +struct hal_metadata_pass_through { + u32 enable; + u32 size; +}; + +struct hal_uncompressed_format_supported { + enum hal_buffer buffer_type; + u32 format_entries; + u32 rg_format_info[1]; +}; + +enum hal_interlace_format { + HAL_INTERLACE_FRAME_PROGRESSIVE = 0x01, + HAL_INTERLACE_INTERLEAVE_FRAME_TOPFIELDFIRST = 0x02, + HAL_INTERLACE_INTERLEAVE_FRAME_BOTTOMFIELDFIRST = 0x04, + HAL_INTERLACE_FRAME_TOPFIELDFIRST = 0x08, + HAL_INTERLACE_FRAME_BOTTOMFIELDFIRST = 0x10, + HAL_UNUSED_INTERLACE = 0x10000000, +}; + +struct hal_interlace_format_supported { + enum hal_buffer buffer_type; + enum hal_interlace_format format; +}; + +enum hal_chroma_site { + HAL_CHROMA_SITE_0, + HAL_CHROMA_SITE_1, + HAL_UNUSED_CHROMA = 0x10000000, +}; + +struct hal_properties_supported { + u32 num_properties; + u32 rg_properties[1]; +}; + +enum hal_capability { + HAL_CAPABILITY_FRAME_WIDTH, + HAL_CAPABILITY_FRAME_HEIGHT, + HAL_CAPABILITY_MBS_PER_FRAME, + HAL_CAPABILITY_MBS_PER_SECOND, + HAL_CAPABILITY_FRAMERATE, + HAL_CAPABILITY_SCALE_X, + HAL_CAPABILITY_SCALE_Y, + HAL_CAPABILITY_BITRATE, + HAL_UNUSED_CAPABILITY = 0x10000000, +}; + +struct hal_capability_supported { + enum hal_capability capability_type; + u32 min; + u32 max; + u32 step_size; +}; + +struct hal_capability_supported_info { + u32 num_capabilities; + struct hal_capability_supported rg_data[1]; +}; + +struct hal_nal_stream_format_supported { + u32 nal_stream_format_supported; +}; + +struct hal_multi_view_format { + u32 views; + u32 rg_view_order[1]; +}; + +struct hal_seq_header_info { + u32 nax_header_len; +}; + +struct hal_codec_supported { + u32 decoder_codec_supported; + u32 encoder_codec_supported; +}; + +struct hal_multi_view_select { + u32 view_index; +}; + +struct hal_timestamp_scale { + u32 time_stamp_scale; +}; + +enum vidc_resource_id { + VIDC_RESOURCE_OCMEM = 0x00000001, + VIDC_UNUSED_RESORUCE = 0x10000000, +}; + +struct vidc_resource_hdr { + enum vidc_resource_id resource_id; + u32 resource_handle; + u32 size; +}; + +struct vidc_buffer_addr_info { + enum hal_buffer buffer_type; + u32 buffer_size; + u32 num_buffers; + u32 align_device_addr; + u32 extradata_size; + u32 extradata_addr; +}; + +struct vidc_frame_plane_config { + u32 left; + u32 top; + u32 width; + u32 height; + u32 stride; + u32 scan_lines; +}; + +struct vidc_uncompressed_frame_config { + struct vidc_frame_plane_config luma_plane; + struct vidc_frame_plane_config chroma_plane; +}; + +struct vidc_frame_data { + enum hal_buffer buffer_type; + u32 device_addr; + u32 extradata_addr; + int64_t timestamp; + u32 flags; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 mark_target; + u32 mark_data; + u32 clnt_data; +}; + +struct vidc_seq_hdr { + u8 *seq_hdr; + u32 seq_hdr_len; +}; + +enum hal_flush { + HAL_FLUSH_INPUT, + HAL_FLUSH_OUTPUT, + HAL_FLUSH_OUTPUT2, + HAL_FLUSH_ALL, + HAL_UNUSED_FLUSH = 0x10000000, +}; + +/* HAL Response */ + +enum command_response { +/* SYSTEM COMMANDS_DONE*/ + VIDC_EVENT_CHANGE, + SYS_INIT_DONE, + SET_RESOURCE_DONE, + RELEASE_RESOURCE_DONE, + PING_ACK_DONE, + PC_PREP_DONE, + SYS_IDLE, + SYS_DEBUG, +/* SESSION COMMANDS_DONE */ + SESSION_LOAD_RESOURCE_DONE, + SESSION_INIT_DONE, + SESSION_END_DONE, + SESSION_ABORT_DONE, + SESSION_START_DONE, + SESSION_STOP_DONE, + SESSION_ETB_DONE, + SESSION_FTB_DONE, + SESSION_FLUSH_DONE, + SESSION_SUSPEND_DONE, + SESSION_RESUME_DONE, + SESSION_SET_PROP_DONE, + SESSION_GET_PROP_DONE, + SESSION_PARSE_SEQ_HDR_DONE, + SESSION_GET_SEQ_HDR_DONE, + SESSION_RELEASE_BUFFER_DONE, + SESSION_RELEASE_RESOURCE_DONE, + SESSION_PROPERTY_INFO, + RESPONSE_UNUSED = 0x10000000, +}; + +/* Command Callback structure */ + +struct msm_vidc_cb_cmd_done { + u32 device_id; + u32 session_id; + u32 status; + u32 size; + void *data; +}; + +struct msm_vidc_cb_event { + u32 device_id; + u32 session_id; + u32 status; + u32 height; + u32 width; +}; + +/* Data callback structure */ + +struct vidc_hal_ebd { + u32 timestamp_hi; + u32 timestamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 offset; + u32 alloc_len; + u32 filled_len; + enum hal_picture picture_type; + u8 *packet_buffer; + u8 *extra_data_buffer; +}; + +struct vidc_hal_fbd { + u32 stream_id; + u32 view_id; + u32 timestamp_hi; + u32 timestamp_lo; + u32 flags1; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 alloc_len1; + u32 filled_len1; + u32 offset1; + u32 frame_width; + u32 frame_height; + u32 start_xCoord; + u32 start_yCoord; + u32 input_tag; + u32 input_tag1; + enum hal_picture picture_type; + u8 *packet_buffer1; + u8 *extra_data_buffer; + u32 flags2; + u32 alloc_len2; + u32 filled_len2; + u32 offset2; + u8 *packet_buffer2; + u32 flags3; + u32 alloc_len3; + u32 filled_len3; + u32 offset3; + u8 *packet_buffer3; + enum hal_buffer buffer_type; +}; + +struct msm_vidc_cb_data_done { + u32 device_id; + u32 session_id; + u32 status; + u32 size; + void *clnt_data; + union { + struct vidc_hal_ebd input_done; + struct vidc_hal_fbd output_done; + }; +}; + +struct vidc_hal_sys_init_done { + u32 enc_codec_supported; + u32 dec_codec_supported; +}; + +struct vidc_hal_session_init_done { + struct hal_capability_supported width; + struct hal_capability_supported height; + struct hal_capability_supported mbs_per_frame; + struct hal_capability_supported mbs_per_sec; + struct hal_capability_supported frame_rate; + struct hal_capability_supported scale_x; + struct hal_capability_supported scale_y; + struct hal_capability_supported bitrate; + struct hal_uncompressed_format_supported uncomp_format; + struct hal_interlace_format_supported HAL_format; + struct hal_nal_stream_format_supported nal_stream_format; +/* struct hal_profile_level_supported profile_level; + // allocate and released memory for above. */ + struct hal_intra_refresh intra_refresh; + struct hal_seq_header_info seq_hdr_info; +}; + +struct buffer_requirements { + struct hal_buffer_requirements buffer[8]; +}; + +/* VIDC_HAL CORE API's */ +int vidc_hal_core_init(void *device); +int vidc_hal_core_release(void *device); +int vidc_hal_core_pc_prep(void *device); +int vidc_hal_core_set_resource(void *device, + struct vidc_resource_hdr *resource_hdr, void *resource_value); +int vidc_hal_core_release_resource(void *device, + struct vidc_resource_hdr *resource_hdr); +int vidc_hal_core_ping(void *device); + +/* VIDC_HAL SESSION API's */ +void *vidc_hal_session_init(void *device, u32 session_id, + enum hal_domain session_type, enum hal_video_codec codec_type); +int vidc_hal_session_end(void *session); +int vidc_hal_session_abort(void *session); +int vidc_hal_session_set_buffers(void *sess, + struct vidc_buffer_addr_info *buffer_info); +int vidc_hal_session_release_buffers(void *sess, + struct vidc_buffer_addr_info *buffer_info); +int vidc_hal_session_load_res(void *sess); +int vidc_hal_session_release_res(void *sess); +int vidc_hal_session_start(void *sess); +int vidc_hal_session_stop(void *sess); +int vidc_hal_session_suspend(void *sess); +int vidc_hal_session_resume(void *sess); +int vidc_hal_session_etb(void *sess, + struct vidc_frame_data *input_frame); +int vidc_hal_session_ftb(void *sess, + struct vidc_frame_data *output_frame); +int vidc_hal_session_parse_seq_hdr(void *sess, + struct vidc_seq_hdr *seq_hdr); +int vidc_hal_session_get_seq_hdr(void *sess, + struct vidc_seq_hdr *seq_hdr); +int vidc_hal_session_get_buf_req(void *sess); +int vidc_hal_session_flush(void *sess, enum hal_flush flush_mode); +int vidc_hal_session_set_property(void *sess, enum hal_property ptype, + void *pdata); +int vidc_hal_session_get_property(void *sess, enum hal_property ptype, + void *pdata); +void *vidc_hal_add_device(u32 device_id, u32 base_addr, + u32 reg_base, u32 reg_size, u32 irq, + void (*callback) (enum command_response cmd, void *data)); +void vidc_hal_delete_device(void *device); + +#endif /*__VIDC_HAL_API_H__ */ diff --git a/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c b/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c new file mode 100644 index 0000000000000000000000000000000000000000..cb44d3a53f6529b70148575432bfa4222a9b0975 --- /dev/null +++ b/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c @@ -0,0 +1,781 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "vidc_hal.h" + +static enum vidc_status vidc_map_hal_err_status(enum HFI_ERROR hfi_err) +{ + enum vidc_status vidc_err; + switch (hfi_err) { + case HFI_ERR_NONE: + case HFI_ERR_SESSION_SAME_STATE_OPERATION: + vidc_err = VIDC_ERR_NONE; + break; + case HFI_ERR_SYS_FATAL: + vidc_err = VIDC_ERR_HW_FATAL; + break; + case HFI_ERR_SYS_VERSION_MISMATCH: + case HFI_ERR_SYS_INVALID_PARAMETER: + case HFI_ERR_SYS_SESSION_ID_OUT_OF_RANGE: + case HFI_ERR_SESSION_INVALID_PARAMETER: + case HFI_ERR_SESSION_INVALID_SESSION_ID: + case HFI_ERR_SESSION_INVALID_STREAM_ID: + vidc_err = VIDC_ERR_BAD_PARAM; + break; + case HFI_ERR_SYS_INSUFFICIENT_RESOURCES: + case HFI_ERR_SYS_UNSUPPORTED_DOMAIN: + case HFI_ERR_SYS_UNSUPPORTED_CODEC: + case HFI_ERR_SESSION_UNSUPPORTED_PROPERTY: + case HFI_ERR_SESSION_UNSUPPORTED_SETTING: + case HFI_ERR_SESSION_INSUFFICIENT_RESOURCES: + vidc_err = VIDC_ERR_NOT_SUPPORTED; + break; + case HFI_ERR_SYS_MAX_SESSIONS_REACHED: + vidc_err = VIDC_ERR_MAX_CLIENT; + break; + case HFI_ERR_SYS_SESSION_IN_USE: + vidc_err = VIDC_ERR_CLIENT_PRESENT; + break; + case HFI_ERR_SESSION_FATAL: + vidc_err = VIDC_ERR_CLIENT_FATAL; + break; + case HFI_ERR_SESSION_BAD_POINTER: + vidc_err = VIDC_ERR_BAD_PARAM; + break; + case HFI_ERR_SESSION_INCORRECT_STATE_OPERATION: + vidc_err = VIDC_ERR_BAD_STATE; + break; + case HFI_ERR_SESSION_STREAM_CORRUPT: + case HFI_ERR_SESSION_STREAM_CORRUPT_OUTPUT_STALLED: + vidc_err = VIDC_ERR_BITSTREAM_ERR; + break; + case HFI_ERR_SESSION_SYNC_FRAME_NOT_DETECTED: + vidc_err = VIDC_ERR_IFRAME_EXPECTED; + break; + case HFI_ERR_SYS_UNKNOWN: + case HFI_ERR_SESSION_UNKNOWN: + case HFI_ERR_SESSION_EMPTY_BUFFER_DONE_OUTPUT_PENDING: + default: + vidc_err = VIDC_ERR_FAIL; + break; + } + return vidc_err; +} + +void hal_process_sess_evt_seq_changed(struct hal_device *device, + struct hfi_msg_event_notify_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + struct msm_vidc_cb_event event_notify; + int num_properties_changed; + struct hfi_frame_size frame_sz; + u8 *data_ptr; + enum HFI_PROPERTY prop_id; + HAL_MSG_LOW("RECEIVED:EVENT_NOTIFY"); + if (sizeof(struct hfi_msg_event_notify_packet) + > pkt->size) { + HAL_MSG_ERROR("hal_process_session_init_done:bad_pkt_size"); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + memset(&event_notify, 0, sizeof(struct + msm_vidc_cb_event)); + + cmd_done.device_id = device->device_id; + cmd_done.session_id = ((struct hal_session *) pkt->session_id)-> + session_id; + cmd_done.status = VIDC_ERR_NONE; + cmd_done.size = sizeof(struct msm_vidc_cb_event); + num_properties_changed = pkt->event_data2; + if (num_properties_changed) { + data_ptr = (u8 *) &pkt->rg_ext_event_data[0]; + do { + prop_id = (enum HFI_PROPERTY) *((u32 *)data_ptr); + switch (prop_id) { + case HFI_PROPERTY_PARAM_FRAME_SIZE: + frame_sz.buffer = + (enum HFI_BUFFER) + *((((u32 *)data_ptr)+1)); + frame_sz.width = + event_notify.width = + *((((u32 *)data_ptr)+2)); + frame_sz.height = + event_notify.height = + *((((u32 *)data_ptr)+3)); + data_ptr += 4; + break; + default: + break; + } + num_properties_changed--; + } while (num_properties_changed > 0); + } + cmd_done.data = &event_notify; + device->callback(VIDC_EVENT_CHANGE, &cmd_done); +} + +static void hal_process_event_notify(struct hal_device *device, + struct hfi_msg_event_notify_packet *pkt) +{ + HAL_MSG_LOW("RECVD:EVENT_NOTIFY"); + + if (!device || !pkt || + pkt->size < sizeof(struct hfi_msg_event_notify_packet)) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return; + } + + switch (pkt->event_id) { + case HFI_EVENT_SYS_ERROR: + HAL_MSG_INFO("HFI_EVENT_SYS_ERROR"); + break; + case HFI_EVENT_SESSION_ERROR: + HAL_MSG_INFO("HFI_EVENT_SESSION_ERROR"); + break; + case HFI_EVENT_SESSION_SEQUENCE_CHANGED: + HAL_MSG_INFO("HFI_EVENT_SESSION_SEQUENCE_CHANGED"); + hal_process_sess_evt_seq_changed(device, pkt); + break; + case HFI_EVENT_SESSION_PROPERTY_CHANGED: + HAL_MSG_INFO("HFI_EVENT_SESSION_PROPERTY_CHANGED"); + break; + default: + HAL_MSG_INFO("hal_process_event_notify:unkown_event_id"); + break; + } +} + +static void hal_process_sys_init_done(struct hal_device *device, + struct hfi_msg_sys_init_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + struct vidc_hal_sys_init_done sys_init_done; + u32 rem_bytes, bytes_read = 0, num_properties; + u8 *data_ptr; + enum HFI_PROPERTY prop_id; + enum vidc_status status = VIDC_ERR_NONE; + + HAL_MSG_LOW("RECEIVED:SYS_INIT_DONE"); + if (sizeof(struct hfi_msg_sys_init_done_packet) > pkt->size) { + HAL_MSG_ERROR("hal_process_sys_init_done:bad_pkt_size: %d", + pkt->size); + return; + } + + status = vidc_map_hal_err_status((u32)pkt->error_type); + + if (!status) { + if (pkt->num_properties == 0) { + HAL_MSG_ERROR("hal_process_sys_init_done:" + "no_properties"); + status = VIDC_ERR_FAIL; + goto err_no_prop; + } + + rem_bytes = pkt->size - sizeof(struct + hfi_msg_sys_init_done_packet) + sizeof(u32); + + if (rem_bytes == 0) { + HAL_MSG_ERROR("hal_process_sys_init_done:" + "missing_prop_info"); + status = VIDC_ERR_FAIL; + goto err_no_prop; + } + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + memset(&sys_init_done, 0, sizeof(struct + vidc_hal_sys_init_done)); + + data_ptr = (u8 *) &pkt->rg_property_data[0]; + num_properties = pkt->num_properties; + + while ((num_properties != 0) && (rem_bytes >= sizeof(u32))) { + prop_id = (enum HFI_PROPERTY) *((u32 *)data_ptr); + data_ptr = data_ptr + 4; + + switch (prop_id) { + case HFI_PROPERTY_PARAM_CODEC_SUPPORTED: + { + struct hfi_codec_supported *prop = + (struct hfi_codec_supported *) data_ptr; + if (rem_bytes < sizeof(struct + hfi_codec_supported)) { + status = VIDC_ERR_BAD_PARAM; + break; + } + sys_init_done.dec_codec_supported = + prop->decoder_codec_supported; + sys_init_done.enc_codec_supported = + prop->encoder_codec_supported; + break; + } + default: + HAL_MSG_ERROR("hal_process_sys_init_done:" + "bad_prop_id"); + status = VIDC_ERR_BAD_PARAM; + break; + } + if (!status) { + rem_bytes -= bytes_read; + data_ptr += bytes_read; + num_properties--; + } + } + } +err_no_prop: + cmd_done.device_id = device->device_id; + cmd_done.session_id = 0; + cmd_done.status = (u32) status; + cmd_done.size = sizeof(struct vidc_hal_sys_init_done); + cmd_done.data = (void *) &sys_init_done; + device->callback(SYS_INIT_DONE, &cmd_done); +} + +enum vidc_status vidc_hal_process_sess_init_done_prop_read( + struct hfi_msg_sys_session_init_done_packet *pkt, + struct msm_vidc_cb_cmd_done *cmddone) +{ + return VIDC_ERR_NONE; +} + +static void hal_process_sess_get_prop_buf_req( + struct hfi_msg_session_property_info_packet *prop, + struct buffer_requirements *buffreq) +{ + struct hfi_buffer_requirements *hfi_buf_req; + u32 req_bytes; + enum vidc_status rc = VIDC_ERR_NONE; + + HAL_MSG_LOW("Entered %s", __func__); + req_bytes = prop->size - sizeof( + struct hfi_msg_session_property_info_packet); + + if (req_bytes == 0 || (req_bytes % sizeof( + struct hfi_buffer_requirements))) { + HAL_MSG_ERROR("hal_process_sess_get_prop_buf_req:bad_pkt_size:" + " %d", req_bytes); + return; + } + + hfi_buf_req = (struct hfi_buffer_requirements *) + &prop->rg_property_data[1]; + + while (req_bytes != 0) { + if ((hfi_buf_req->buffer_count_min > hfi_buf_req-> + buffer_count_actual) + || (hfi_buf_req->buffer_alignment == 0) + || (hfi_buf_req->buffer_size == 0)) { + HAL_MSG_ERROR("hal_process_sess_get_prop_buf_req:" + "bad_buf_req"); + rc = VIDC_ERR_FAIL; + } + HAL_MSG_LOW("got buffer requirements for: %d", + hfi_buf_req->buffer); + switch (hfi_buf_req->buffer) { + case HFI_BUFFER_INPUT: + memcpy(&buffreq->buffer[0], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[0].buffer_type = HAL_BUFFER_INPUT; + break; + case HFI_BUFFER_OUTPUT: + memcpy(&buffreq->buffer[1], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[1].buffer_type = HAL_BUFFER_OUTPUT; + break; + case HFI_BUFFER_OUTPUT2: + memcpy(&buffreq->buffer[2], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[2].buffer_type = HAL_BUFFER_OUTPUT2; + break; + case HFI_BUFFER_EXTRADATA_INPUT: + memcpy(&buffreq->buffer[3], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[3].buffer_type = + HAL_BUFFER_EXTRADATA_INPUT; + break; + case HFI_BUFFER_EXTRADATA_OUTPUT: + memcpy(&buffreq->buffer[4], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[4].buffer_type = + HAL_BUFFER_EXTRADATA_OUTPUT; + break; + case HFI_BUFFER_EXTRADATA_OUTPUT2: + memcpy(&buffreq->buffer[5], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[5].buffer_type = + HAL_BUFFER_EXTRADATA_OUTPUT2; + break; + case HFI_BUFFER_INTERNAL_SCRATCH: + memcpy(&buffreq->buffer[6], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[6].buffer_type = + HAL_BUFFER_INTERNAL_SCRATCH; + break; + case HFI_BUFFER_INTERNAL_PERSIST: + memcpy(&buffreq->buffer[7], hfi_buf_req, + sizeof(struct hfi_buffer_requirements)); + buffreq->buffer[7].buffer_type = + HAL_BUFFER_INTERNAL_PERSIST; + break; + default: + HAL_MSG_ERROR("hal_process_sess_get_prop_buf_req:" + "bad_buffer_type: %d", hfi_buf_req->buffer); + break; + } + req_bytes -= sizeof(struct hfi_buffer_requirements); + hfi_buf_req++; + } +} + +static void hal_process_session_prop_info(struct hal_device *device, + struct hfi_msg_session_property_info_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + struct buffer_requirements buff_req; + + HAL_MSG_INFO("Received SESSION_PROPERTY_INFO"); + + if (pkt->size < sizeof(struct hfi_msg_session_property_info_packet)) { + HAL_MSG_ERROR("hal_process_session_prop_info:bad_pkt_size"); + return; + } + + if (pkt->num_properties == 0) { + HAL_MSG_ERROR("hal_process_session_prop_info:no_properties"); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + memset(&buff_req, 0, sizeof(struct buffer_requirements)); + + switch (pkt->rg_property_data[0]) { + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + hal_process_sess_get_prop_buf_req(pkt, &buff_req); + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = VIDC_ERR_NONE; + cmd_done.data = &buff_req; + cmd_done.size = sizeof(struct buffer_requirements); + device->callback(SESSION_PROPERTY_INFO, &cmd_done); + break; + default: + HAL_MSG_ERROR("hal_process_session_prop_info:" + "unknown_prop_id: %d", + pkt->rg_property_data[0]); + break; + } +} + +static void hal_process_session_init_done(struct hal_device *device, + struct hfi_msg_sys_session_init_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + struct vidc_hal_session_init_done session_init_done; + + HAL_MSG_LOW("RECEIVED:SESSION_INIT_DONE"); + if (sizeof(struct hfi_msg_sys_session_init_done_packet) + > pkt->size) { + HAL_MSG_ERROR("hal_process_session_init_done:bad_pkt_size"); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + memset(&session_init_done, 0, sizeof(struct + vidc_hal_session_init_done)); + + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = &session_init_done; + if (!cmd_done.status) { + cmd_done.status = vidc_hal_process_sess_init_done_prop_read( + pkt, &cmd_done); + } + cmd_done.size = sizeof(struct vidc_hal_session_init_done); + device->callback(SESSION_INIT_DONE, &cmd_done); +} + +static void hal_process_session_load_res_done(struct hal_device *device, + struct hfi_msg_session_load_resources_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + HAL_MSG_LOW("RECEIVED:SESSION_LOAD_RESOURCES_DONE"); + + if (sizeof(struct hfi_msg_session_load_resources_done_packet) != + pkt->size) { + HAL_MSG_ERROR("hal_process_session_load_res_done:" + " bad packet size: %d", pkt->size); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = NULL; + cmd_done.size = 0; + device->callback(SESSION_LOAD_RESOURCE_DONE, &cmd_done); +} + +static void hal_process_session_flush_done(struct hal_device *device, + struct hfi_msg_session_flush_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + + HAL_MSG_LOW("RECEIVED:SESSION_FLUSH_DONE"); + + if (sizeof(struct hfi_msg_session_flush_done_packet) != pkt->size) { + HAL_MSG_ERROR("hal_process_session_flush_done: " + "bad packet size: %d", pkt->size); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = (void *) pkt->flush_type; + cmd_done.size = sizeof(u32); + device->callback(SESSION_FLUSH_DONE, &cmd_done); +} + +static void hal_process_session_etb_done(struct hal_device *device, + struct hfi_msg_session_empty_buffer_done_packet *pkt) +{ + struct msm_vidc_cb_data_done data_done; + + HAL_MSG_LOW("RECEIVED:SESSION_ETB_DONE"); + + if (!pkt || pkt->size != + sizeof(struct hfi_msg_session_empty_buffer_done_packet)) { + HAL_MSG_ERROR("hal_process_session_etb_done:bad_pkt_size"); + return; + } + + memset(&data_done, 0, sizeof(struct msm_vidc_cb_data_done)); + + data_done.device_id = device->device_id; + data_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + data_done.status = vidc_map_hal_err_status((u32) pkt->error_type); + data_done.size = sizeof(struct msm_vidc_cb_data_done); + data_done.clnt_data = (void *)pkt->input_tag; + data_done.input_done.offset = pkt->offset; + data_done.input_done.filled_len = pkt->filled_len; + data_done.input_done.packet_buffer = pkt->packet_buffer; + device->callback(SESSION_ETB_DONE, &data_done); +} + +static void hal_process_session_ftb_done(struct hal_device *device, + void *msg_hdr) +{ + struct msm_vidc_cb_data_done data_done; + struct hfi_msg_session_fill_buffer_done_compressed_packet *pack = + (struct hfi_msg_session_fill_buffer_done_compressed_packet *) msg_hdr; + u32 is_decoder = ((struct hal_session *)pack->session_id)->is_decoder; + struct hal_session *session; + + if (!msg_hdr) { + HAL_MSG_ERROR("Invalid Params in %s", __func__); + return; + } + + session = (struct hal_session *) + ((struct hal_session *) pack->session_id)->session_id; + HAL_MSG_ERROR("RECEIVED:SESSION_FTB_DONE"); + + memset(&data_done, 0, sizeof(struct msm_vidc_cb_data_done)); + + if (is_decoder == 0) { + struct hfi_msg_session_fill_buffer_done_compressed_packet *pkt = + (struct hfi_msg_session_fill_buffer_done_compressed_packet *) + msg_hdr; + if (sizeof(struct + hfi_msg_session_fill_buffer_done_compressed_packet) + != pkt->size) { + HAL_MSG_ERROR("hal_process_session_ftb_done:" + "bad_pkt_size"); + return; + } + + data_done.device_id = device->device_id; + data_done.session_id = (u32) session; + data_done.status = vidc_map_hal_err_status((u32) + pkt->error_type); + data_done.size = sizeof(struct msm_vidc_cb_data_done); + data_done.clnt_data = (void *) pkt->input_tag; + + data_done.output_done.timestamp_hi = pkt->timestamp_hi; + data_done.output_done.timestamp_lo = pkt->timestamp_lo; + data_done.output_done.flags1 = pkt->flags; + data_done.output_done.mark_target = pkt->mark_target; + data_done.output_done.mark_data = pkt->mark_data; + data_done.output_done.stats = pkt->stats; + data_done.output_done.offset1 = pkt->offset; + data_done.output_done.alloc_len1 = pkt->alloc_len; + data_done.output_done.filled_len1 = pkt->filled_len; + data_done.output_done.picture_type = pkt->picture_type; + data_done.output_done.packet_buffer1 = pkt->packet_buffer; + data_done.output_done.extra_data_buffer = + pkt->extra_data_buffer; + } else if (is_decoder == 1) { + struct hfi_msg_session_fbd_uncompressed_plane0_packet *pkt = + (struct hfi_msg_session_fbd_uncompressed_plane0_packet *) + msg_hdr; + if (sizeof(struct + hfi_msg_session_fbd_uncompressed_plane0_packet) + > pkt->size) { + HAL_MSG_ERROR("hal_process_session_ftb_done:" + "bad_pkt_size"); + return; + } + + data_done.device_id = device->device_id; + data_done.session_id = (u32) session; + data_done.status = vidc_map_hal_err_status((u32) + pkt->error_type); + data_done.size = sizeof(struct msm_vidc_cb_data_done); + data_done.clnt_data = (void *)pkt->input_tag; + + data_done.output_done.stream_id = pkt->stream_id; + data_done.output_done.view_id = pkt->view_id; + data_done.output_done.timestamp_hi = pkt->timestamp_hi; + data_done.output_done.timestamp_lo = pkt->timestamp_lo; + data_done.output_done.flags1 = pkt->flags; + data_done.output_done.mark_target = pkt->mark_target; + data_done.output_done.mark_data = pkt->mark_data; + data_done.output_done.stats = pkt->stats; + data_done.output_done.alloc_len1 = pkt->alloc_len; + data_done.output_done.filled_len1 = pkt->filled_len; + data_done.output_done.offset1 = pkt->oofset; + data_done.output_done.frame_width = pkt->frame_width; + data_done.output_done.frame_height = pkt->frame_height; + data_done.output_done.start_xCoord = pkt->start_xCoord; + data_done.output_done.start_yCoord = pkt->start_yCoord; + data_done.output_done.input_tag1 = pkt->input_tag1; + data_done.output_done.picture_type = pkt->picture_type; + data_done.output_done.packet_buffer1 = pkt->packet_buffer; + data_done.output_done.extra_data_buffer = + pkt->extra_data_buffer; + + if (pkt->stream_id == 0) + data_done.output_done.buffer_type = HAL_BUFFER_OUTPUT; + else if (pkt->stream_id == 1) + data_done.output_done.buffer_type = HAL_BUFFER_OUTPUT2; + } + device->callback(SESSION_FTB_DONE, &data_done); +} + +static void hal_process_session_start_done(struct hal_device *device, + struct hfi_msg_session_start_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + + HAL_MSG_LOW("RECEIVED:SESSION_START_DONE"); + + if (!pkt || pkt->size != + sizeof(struct hfi_msg_session_start_done_packet)) { + HAL_MSG_ERROR("hal_process_session_start_done:" + "bad packet/packet size: %d", pkt->size); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = NULL; + cmd_done.size = 0; + device->callback(SESSION_START_DONE, &cmd_done); +} + +static void hal_process_session_stop_done(struct hal_device *device, + struct hfi_msg_session_stop_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + + HAL_MSG_LOW("RECEIVED:SESSION_STOP_DONE"); + + if (!pkt || pkt->size != + sizeof(struct hfi_msg_session_stop_done_packet)) { + HAL_MSG_ERROR("hal_process_session_stop_done:" + "bad packet/packet size: %d", pkt->size); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = NULL; + cmd_done.size = 0; + device->callback(SESSION_STOP_DONE, &cmd_done); +} + +static void hal_process_session_rel_res_done(struct hal_device *device, + struct hfi_msg_session_release_resources_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + + HAL_MSG_LOW("RECEIVED:SESSION_RELEASE_RESOURCES_DONE"); + + if (!pkt || pkt->size != + sizeof(struct hfi_msg_session_release_resources_done_packet)) { + HAL_MSG_ERROR("hal_process_session_rel_res_done:" + "bad packet/packet size: %d", pkt->size); + return; + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = NULL; + cmd_done.size = 0; + device->callback(SESSION_RELEASE_RESOURCE_DONE, &cmd_done); +} + +static void hal_process_session_end_done(struct hal_device *device, + struct hfi_msg_sys_session_end_done_packet *pkt) +{ + struct msm_vidc_cb_cmd_done cmd_done; + struct list_head *curr, *next; + struct hal_session *sess_close; + + HAL_MSG_LOW("RECEIVED:SESSION_END_DONE"); + + if (!pkt || pkt->size != + sizeof(struct hfi_msg_sys_session_end_done_packet)) { + HAL_MSG_ERROR("hal_process_session_end_done: " + "bad packet/packet size: %d", pkt->size); + return; + } + + list_for_each_safe(curr, next, &device->sess_head) { + sess_close = list_entry(curr, struct hal_session, list); + HAL_MSG_MEDIUM("deleted the session: 0x%x", + sess_close->session_id); + list_del(&sess_close->list); + kfree(sess_close); + } + + memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); + cmd_done.device_id = device->device_id; + cmd_done.session_id = + ((struct hal_session *) pkt->session_id)->session_id; + cmd_done.status = vidc_map_hal_err_status((u32)pkt->error_type); + cmd_done.data = NULL; + cmd_done.size = 0; + device->callback(SESSION_END_DONE, &cmd_done); +} + +static void hal_process_msg_packet(struct hal_device *device, + struct vidc_hal_msg_pkt_hdr *msg_hdr) +{ + if (!device || !msg_hdr || msg_hdr->size < + VIDC_IFACEQ_MIN_PKT_SIZE) { + HAL_MSG_ERROR("hal_process_msg_packet:bad" + "packet/packet size: %d", msg_hdr->size); + return; + } + + HAL_MSG_INFO("Received: 0x%x in %s", msg_hdr->packet, __func__); + + switch (msg_hdr->packet) { + case HFI_MSG_EVENT_NOTIFY: + hal_process_event_notify(device, + (struct hfi_msg_event_notify_packet *) msg_hdr); + break; + case HFI_MSG_SYS_INIT_DONE: + hal_process_sys_init_done(device, + (struct hfi_msg_sys_init_done_packet *) + msg_hdr); + break; + case HFI_MSG_SYS_SESSION_INIT_DONE: + hal_process_session_init_done(device, + (struct hfi_msg_sys_session_init_done_packet *) + msg_hdr); + break; + case HFI_MSG_SYS_SESSION_END_DONE: + hal_process_session_end_done(device, + (struct hfi_msg_sys_session_end_done_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_LOAD_RESOURCES_DONE: + hal_process_session_load_res_done(device, + (struct hfi_msg_session_load_resources_done_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_START_DONE: + hal_process_session_start_done(device, + (struct hfi_msg_session_start_done_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_STOP_DONE: + hal_process_session_stop_done(device, + (struct hfi_msg_session_stop_done_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_EMPTY_BUFFER_DONE: + hal_process_session_etb_done(device, + (struct hfi_msg_session_empty_buffer_done_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_FILL_BUFFER_DONE: + hal_process_session_ftb_done(device, msg_hdr); + break; + case HFI_MSG_SESSION_FLUSH_DONE: + hal_process_session_flush_done(device, + (struct hfi_msg_session_flush_done_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_PROPERTY_INFO: + hal_process_session_prop_info(device, + (struct hfi_msg_session_property_info_packet *) + msg_hdr); + break; + case HFI_MSG_SESSION_RELEASE_RESOURCES_DONE: + hal_process_session_rel_res_done(device, + (struct hfi_msg_session_release_resources_done_packet *) + msg_hdr); + break; + default: + HAL_MSG_ERROR("UNKNOWN_MSG_TYPE : %d", msg_hdr->packet); + break; + } +} + +void vidc_hal_response_handler(struct hal_device *device) +{ + u8 packet[VIDC_IFACEQ_MED_PKT_SIZE]; + + HAL_MSG_INFO("############vidc_hal_response_handler\n"); + if (device) { + while (!vidc_hal_iface_msgq_read(device, packet)) { + hal_process_msg_packet(device, + (struct vidc_hal_msg_pkt_hdr *) packet); + } + } else { + HAL_MSG_ERROR("SPURIOUS_INTERRUPT"); + } +} diff --git a/drivers/media/video/msm_vidc/vidc_hal_io.h b/drivers/media/video/msm_vidc/vidc_hal_io.h new file mode 100644 index 0000000000000000000000000000000000000000..05a4c60cb406ba82ca90482f45325c53d9dea352 --- /dev/null +++ b/drivers/media/video/msm_vidc/vidc_hal_io.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __VIDCHALIO_H__ +#define __VIDCHALIO_H__ + +#include + +#define VIDC_VBIF_BASE_OFFS 0x00080000 +#define VIDC_VBIF_VERSION (VIDC_VBIF_BASE_OFFS + 0x00) +#define VIDC_VBIF_ADDR_TRANS_EN (VIDC_VBIF_BASE_OFFS + 0x10) +#define VIDC_VBIF_AT_OLD_BASE (VIDC_VBIF_BASE_OFFS + 0x14) +#define VIDC_VBIF_AT_OLD_HIGH (VIDC_VBIF_BASE_OFFS + 0x18) +#define VIDC_VBIF_AT_NEW_BASE (VIDC_VBIF_BASE_OFFS + 0x20) +#define VIDC_VBIF_AT_NEW_HIGH (VIDC_VBIF_BASE_OFFS + 0x28) + +#define VIDC_CPU_BASE_OFFS 0x000C0000 +#define VIDC_CPU_CS_BASE_OFFS (VIDC_CPU_BASE_OFFS + 0x00012000) +#define VIDC_CPU_IC_BASE_OFFS (VIDC_CPU_BASE_OFFS + 0x0001F000) + +#define VIDC_CPU_CS_REMAP_OFFS (VIDC_CPU_CS_BASE_OFFS + 0x00) +#define VIDC_CPU_CS_TIMER_CONTROL (VIDC_CPU_CS_BASE_OFFS + 0x04) +#define VIDC_CPU_CS_A2HSOFTINTEN (VIDC_CPU_CS_BASE_OFFS + 0x10) +#define VIDC_CPU_CS_A2HSOFTINTENCLR (VIDC_CPU_CS_BASE_OFFS + 0x14) +#define VIDC_CPU_CS_A2HSOFTINT (VIDC_CPU_CS_BASE_OFFS + 0x18) +#define VIDC_CPU_CS_A2HSOFTINTCLR (VIDC_CPU_CS_BASE_OFFS + 0x1C) +#define VIDC_CPU_CS_SCIACMD (VIDC_CPU_CS_BASE_OFFS + 0x48) + +/* HFI_CTRL_STATUS */ +#define VIDC_CPU_CS_SCIACMDARG0 (VIDC_CPU_CS_BASE_OFFS + 0x4C) +#define VIDC_CPU_CS_SCIACMDARG0_BMSK 0xff +#define VIDC_CPU_CS_SCIACMDARG0_SHFT 0x0 +#define VIDC_CPU_CS_SCIACMDARG0_HFI_CTRL_ERROR_STATUS_BMSK 0xfe +#define VIDC_CPU_CS_SCIACMDARG0_HFI_CTRL_ERROR_STATUS_SHFT 0x1 +#define VIDC_CPU_CS_SCIACMDARG0_HFI_CTRL_INIT_STATUS_BMSK 0x1 +#define VIDC_CPU_CS_SCIACMDARG0_HFI_CTRL_INIT_STATUS_SHFT 0x0 + +/* HFI_QTBL_INFO */ +#define VIDC_CPU_CS_SCIACMDARG1 (VIDC_CPU_CS_BASE_OFFS + 0x50) + +/* HFI_QTBL_ADDR */ +#define VIDC_CPU_CS_SCIACMDARG2 (VIDC_CPU_CS_BASE_OFFS + 0x54) + +/* HFI_VERSION_INFO */ +#define VIDC_CPU_CS_SCIACMDARG3 (VIDC_CPU_CS_BASE_OFFS + 0x58) +#define VIDC_CPU_IC_IRQSTATUS (VIDC_CPU_IC_BASE_OFFS + 0x00) +#define VIDC_CPU_IC_FIQSTATUS (VIDC_CPU_IC_BASE_OFFS + 0x04) +#define VIDC_CPU_IC_RAWINTR (VIDC_CPU_IC_BASE_OFFS + 0x08) +#define VIDC_CPU_IC_INTSELECT (VIDC_CPU_IC_BASE_OFFS + 0x0C) +#define VIDC_CPU_IC_INTENABLE (VIDC_CPU_IC_BASE_OFFS + 0x10) +#define VIDC_CPU_IC_INTENACLEAR (VIDC_CPU_IC_BASE_OFFS + 0x14) +#define VIDC_CPU_IC_SOFTINT (VIDC_CPU_IC_BASE_OFFS + 0x18) +#define VIDC_CPU_IC_SOFTINT_H2A_BMSK 0x8000 +#define VIDC_CPU_IC_SOFTINT_H2A_SHFT 0xF +#define VIDC_CPU_IC_SOFTINTCLEAR (VIDC_CPU_IC_BASE_OFFS + 0x1C) + +/*--------------------------------------------------------------------------- + * MODULE: vidc_wrapper + *--------------------------------------------------------------------------*/ +#define VIDC_WRAPPER_BASE_OFFS 0x000E0000 + +#define VIDC_WRAPPER_HW_VERSION (VIDC_WRAPPER_BASE_OFFS + 0x00) +#define VIDC_WRAPPER_CLOCK_CONFIG (VIDC_WRAPPER_BASE_OFFS + 0x04) + +#define VIDC_WRAPPER_INTR_STATUS (VIDC_WRAPPER_BASE_OFFS + 0x0C) +#define VIDC_WRAPPER_INTR_STATUS_A2HWD_BMSK 0x10 +#define VIDC_WRAPPER_INTR_STATUS_A2HWD_SHFT 0x4 +#define VIDC_WRAPPER_INTR_STATUS_A2H_BMSK 0x4 +#define VIDC_WRAPPER_INTR_STATUS_A2H_SHFT 0x2 + +#define VIDC_WRAPPER_INTR_MASK (VIDC_WRAPPER_BASE_OFFS + 0x10) +#define VIDC_WRAPPER_INTR_MASK_A2HWD_BMSK 0x10 +#define VIDC_WRAPPER_INTR_MASK_A2HWD_SHFT 0x4 +#define VIDC_WRAPPER_INTR_MASK_A2H_BMSK 0x4 +#define VIDC_WRAPPER_INTR_MASK_A2H_SHFT 0x2 + +#define VIDC_WRAPPER_INTR_CLEAR (VIDC_WRAPPER_BASE_OFFS + 0x14) +#define VIDC_WRAPPER_INTR_CLEAR_A2HWD_BMSK 0x10 +#define VIDC_WRAPPER_INTR_CLEAR_A2HWD_SHFT 0x4 +#define VIDC_WRAPPER_INTR_CLEAR_A2H_BMSK 0x4 +#define VIDC_WRAPPER_INTR_CLEAR_A2H_SHFT 0x2 + +#define VIDC_WRAPPER_VBIF_XIN_SW_RESET (VIDC_WRAPPER_BASE_OFFS + 0x18) +#define VIDC_WRAPPER_VBIF_XIN_STATUS (VIDC_WRAPPER_BASE_OFFS + 0x1C) +#define VIDC_WRAPPER_CPU_CLOCK_CONFIG (VIDC_WRAPPER_BASE_OFFS + 0x2000) +#define VIDC_WRAPPER_VBIF_XIN_CPU_SW_RESET \ + (VIDC_WRAPPER_BASE_OFFS + 0x2004) +#define VIDC_WRAPPER_AXI_HALT (VIDC_WRAPPER_BASE_OFFS + 0x2008) +#define VIDC_WRAPPER_AXI_HALT_STATUS (VIDC_WRAPPER_BASE_OFFS + 0x200C) +#define VIDC_WRAPPER_CPU_CGC_DIS (VIDC_WRAPPER_BASE_OFFS + 0x2010) + +#endif + diff --git a/drivers/media/video/uvc/uvc_v4l2.c b/drivers/media/video/uvc/uvc_v4l2.c index ff2cdddf9bc629c6d264390e6a68fb9c9c2d581a..b2ef948ff96c522026d93e0fd21e2e8294e10b2a 100644 --- a/drivers/media/video/uvc/uvc_v4l2.c +++ b/drivers/media/video/uvc/uvc_v4l2.c @@ -68,6 +68,15 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain, goto done; } + /* Prevent excessive memory consumption, as well as integer + * overflows. + */ + if (xmap->menu_count == 0 || + xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) { + ret = -EINVAL; + goto done; + } + size = xmap->menu_count * sizeof(*map->menu_info); map->menu_info = kmalloc(size, GFP_KERNEL); if (map->menu_info == NULL) { diff --git a/drivers/media/video/v4l2-ctrls.c b/drivers/media/video/v4l2-ctrls.c index 18015c0a8d312c49554b9c5f211f6978250be5f2..7da4657cd43b96e4eeb4364d6b2be3982410aa75 100644 --- a/drivers/media/video/v4l2-ctrls.c +++ b/drivers/media/video/v4l2-ctrls.c @@ -371,6 +371,13 @@ const char * const *v4l2_ctrl_get_menu(u32 id) "Gray", NULL, }; + static const char *const mpeg_video_intra_refresh_mode[] = { + "No Intra Refresh", + "AIR MBS", + "AIR REF", + "CIR MBS", + NULL + }; switch (id) { case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: @@ -438,7 +445,8 @@ const char * const *v4l2_ctrl_get_menu(u32 id) return mpeg4_profile; case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: return jpeg_chroma_subsampling; - + case V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_MODE: + return mpeg_video_intra_refresh_mode; default: return NULL; } @@ -575,6 +583,14 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_VIDEO_VBV_SIZE: return "VBV Buffer Size"; case V4L2_CID_MPEG_VIDEO_DEC_PTS: return "Video Decoder PTS"; case V4L2_CID_MPEG_VIDEO_DEC_FRAME: return "Video Decoder Frame Count"; + case V4L2_CID_MPEG_VIDC_VIDEO_ROTATION: return "Rotation"; + case V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL: return "Rate Control"; + case V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL: return "CABAC Model"; + case V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_MODE: + return "Intra Refresh Mode"; + case V4L2_CID_MPEG_VIDC_VIDEO_AIR_MBS: return "Intra Refresh AIR MBS"; + case V4L2_CID_MPEG_VIDC_VIDEO_AIR_REF: return "Intra Refresh AIR REF"; + case V4L2_CID_MPEG_VIDC_VIDEO_CIR_MBS: return "Intra Refresh CIR MBS"; /* CAMERA controls */ /* Keep the order of the 'case's the same as in videodev2.h! */ @@ -733,6 +749,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + case V4L2_CID_MPEG_VIDC_VIDEO_ROTATION: + case V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL: + case V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_RDS_TX_PS_NAME: @@ -751,7 +770,12 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *min = *max = *step = *def = 0; break; case V4L2_CID_BG_COLOR: - *type = V4L2_CTRL_TYPE_INTEGER; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + *type = V4L2_CTRL_TYPE_INTEGER; *step = 1; *min = 0; /* Max is calculated as RGB888 that is 2^24 */ diff --git a/drivers/media/video/vcap_v4l2.c b/drivers/media/video/vcap_v4l2.c new file mode 100644 index 0000000000000000000000000000000000000000..9719307f88fe8ded36ba85c229ce9d3e23e1f63c --- /dev/null +++ b/drivers/media/video/vcap_v4l2.c @@ -0,0 +1,1569 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "vcap_vc.h" +#include "vcap_vp.h" + +#define NUM_INPUTS 1 +#define MSM_VCAP_DRV_NAME "msm_vcap" + +static struct vcap_dev *vcap_ctrl; + +static unsigned debug; + +#define dprintk(level, fmt, arg...) \ + do { \ + if (debug >= level) \ + printk(KERN_DEBUG "VCAP: " fmt, ## arg); \ + } while (0) + +enum vcap_op_mode determine_mode(struct vcap_client_data *cd) +{ + if (cd->set_cap == 1 && cd->set_vp_o == 0 && + cd->set_decode == 0) + return VC_VCAP_OP; + else if (cd->set_cap == 1 && cd->set_vp_o == 1 && + cd->set_decode == 0) + return VC_AND_VP_VCAP_OP; + else if (cd->set_cap == 0 && cd->set_vp_o == 1 && + cd->set_decode == 1) + return VP_VCAP_OP; + else + return UNKNOWN_VCAP_OP; +} + +void dealloc_resources(struct vcap_client_data *cd) +{ + cd->set_cap = false; + cd->set_decode = false; + cd->set_vp_o = false; +} + +int get_phys_addr(struct vcap_dev *dev, struct vb2_queue *q, + struct v4l2_buffer *b) +{ + struct vb2_buffer *vb; + struct vcap_buffer *buf; + unsigned long len, offset; + int rc; + + if (q->fileio) { + dprintk(1, "%s: file io in progress\n", __func__); + return -EBUSY; + } + + if (b->type != q->type) { + dprintk(1, "%s: invalid buffer type\n", __func__); + return -EINVAL; + } + + if (b->index >= q->num_buffers) { + dprintk(1, "%s: buffer index out of range\n", __func__); + return -EINVAL; + } + + vb = q->bufs[b->index]; + if (NULL == vb) { + dprintk(1, "%s: buffer is NULL\n", __func__); + return -EINVAL; + } + + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "%s: buffer already in use\n", __func__); + return -EINVAL; + } + + buf = container_of(vb, struct vcap_buffer, vb); + + buf->ion_handle = ion_import_fd(dev->ion_client, b->m.userptr); + if (IS_ERR((void *)buf->ion_handle)) { + pr_err("%s: Could not alloc memory\n", __func__); + buf->ion_handle = NULL; + return -ENOMEM; + } + rc = ion_phys(dev->ion_client, buf->ion_handle, + &buf->paddr, (size_t *)&len); + if (rc < 0) { + pr_err("%s: Could not get phys addr\n", __func__); + ion_free(dev->ion_client, buf->ion_handle); + buf->ion_handle = NULL; + return -EFAULT; + } + + offset = b->reserved; + buf->paddr += offset; + return 0; +} + +void free_ion_handle_work(struct vcap_dev *dev, struct vb2_buffer *vb) +{ + struct vcap_buffer *buf; + + buf = container_of(vb, struct vcap_buffer, vb); + if (buf->ion_handle == NULL) { + dprintk(1, "%s: no ION handle to free\n", __func__); + return; + } + buf->paddr = 0; + ion_free(dev->ion_client, buf->ion_handle); + buf->ion_handle = NULL; + return; +} + +int free_ion_handle(struct vcap_dev *dev, struct vb2_queue *q, + struct v4l2_buffer *b) +{ + struct vb2_buffer *vb; + + if (q->fileio) + return -EBUSY; + + if (b->type != q->type) + return -EINVAL; + + if (b->index >= q->num_buffers) + return -EINVAL; + + vb = q->bufs[b->index]; + if (NULL == vb) + return -EINVAL; + + free_ion_handle_work(dev, vb); + return 0; +} + +/* VC Videobuf operations */ + +static int capture_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + void *alloc_ctxs[]) +{ + *nbuffers += 2; + if (*nbuffers > VIDEO_MAX_FRAME) + return -EINVAL; + *nplanes = 1; + return 0; +} + +static int capture_buffer_init(struct vb2_buffer *vb) +{ + return 0; +} + +static int capture_buffer_prepare(struct vb2_buffer *vb) +{ + return 0; +} + +static void capture_buffer_queue(struct vb2_buffer *vb) +{ + struct vcap_client_data *c_data = vb2_get_drv_priv(vb->vb2_queue); + struct vcap_buffer *buf = container_of(vb, struct vcap_buffer, vb); + struct vcap_action *vid_vc_action = &c_data->vid_vc_action; + struct vb2_queue *q = vb->vb2_queue; + unsigned long flags = 0; + + spin_lock_irqsave(&c_data->cap_slock, flags); + list_add_tail(&buf->list, &vid_vc_action->active); + spin_unlock_irqrestore(&c_data->cap_slock, flags); + + if (atomic_read(&c_data->dev->vc_enabled) == 0) { + + if (atomic_read(&q->queued_count) > 1) + if (vc_hw_kick_off(c_data) == 0) + atomic_set(&c_data->dev->vc_enabled, 1); + } +} + +static int capture_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vcap_client_data *c_data = vb2_get_drv_priv(vq); + dprintk(2, "VC start streaming\n"); + return vc_start_capture(c_data); +} + +static int capture_stop_streaming(struct vb2_queue *vq) +{ + struct vcap_client_data *c_data = vb2_get_drv_priv(vq); + struct vb2_buffer *vb; + + vc_stop_capture(c_data); + + while (!list_empty(&c_data->vid_vc_action.active)) { + struct vcap_buffer *buf; + buf = list_entry(c_data->vid_vc_action.active.next, + struct vcap_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + + /* clean ion handles */ + list_for_each_entry(vb, &vq->queued_list, queued_entry) + free_ion_handle_work(c_data->dev, vb); + return 0; +} + +static int capture_buffer_finish(struct vb2_buffer *vb) +{ + return 0; +} + +static void capture_buffer_cleanup(struct vb2_buffer *vb) +{ +} + +static struct vb2_ops capture_video_qops = { + .queue_setup = capture_queue_setup, + .buf_init = capture_buffer_init, + .buf_prepare = capture_buffer_prepare, + .buf_queue = capture_buffer_queue, + .start_streaming = capture_start_streaming, + .stop_streaming = capture_stop_streaming, + .buf_finish = capture_buffer_finish, + .buf_cleanup = capture_buffer_cleanup, +}; + +/* VP I/P Videobuf operations */ + +static int vp_in_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + void *alloc_ctxs[]) +{ + if (*nbuffers >= VIDEO_MAX_FRAME && *nbuffers < 5) + *nbuffers = 5; + + *nplanes = 1; + return 0; +} + +static int vp_in_buffer_init(struct vb2_buffer *vb) +{ + return 0; +} + +static int vp_in_buffer_prepare(struct vb2_buffer *vb) +{ + return 0; +} + +static void vp_in_buffer_queue(struct vb2_buffer *vb) +{ + struct vcap_client_data *cd = vb2_get_drv_priv(vb->vb2_queue); + struct vcap_buffer *buf = container_of(vb, struct vcap_buffer, vb); + struct vp_action *vp_act = &cd->vid_vp_action; + struct vb2_queue *q = vb->vb2_queue; + unsigned long flags = 0; + + spin_lock_irqsave(&cd->cap_slock, flags); + list_add_tail(&buf->list, &vp_act->in_active); + spin_unlock_irqrestore(&cd->cap_slock, flags); + + if (atomic_read(&cd->dev->vp_enabled) == 0) { + if (cd->vid_vp_action.vp_state == VP_FRAME1) { + if (atomic_read(&q->queued_count) > 1 && + atomic_read(&cd->vp_out_vidq.queued_count) > 0) + /* Valid code flow for VC-VP mode */ + kickoff_vp(cd); + } else { + /* VP has already kicked off just needs cont */ + continue_vp(cd); + } + } +} + +static int vp_in_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + dprintk(2, "VP IN start streaming\n"); + return 0; +} + +static int vp_in_stop_streaming(struct vb2_queue *vq) +{ + struct vcap_client_data *c_data = vb2_get_drv_priv(vq); + struct vb2_buffer *vb; + + dprintk(2, "VP stop streaming\n"); + + while (!list_empty(&c_data->vid_vp_action.in_active)) { + struct vcap_buffer *buf; + buf = list_entry(c_data->vid_vp_action.in_active.next, + struct vcap_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + + /* clean ion handles */ + list_for_each_entry(vb, &vq->queued_list, queued_entry) + free_ion_handle_work(c_data->dev, vb); + return 0; +} + +static int vp_in_buffer_finish(struct vb2_buffer *vb) +{ + return 0; +} + +static void vp_in_buffer_cleanup(struct vb2_buffer *vb) +{ +} + +static struct vb2_ops vp_in_video_qops = { + .queue_setup = vp_in_queue_setup, + .buf_init = vp_in_buffer_init, + .buf_prepare = vp_in_buffer_prepare, + .buf_queue = vp_in_buffer_queue, + .start_streaming = vp_in_start_streaming, + .stop_streaming = vp_in_stop_streaming, + .buf_finish = vp_in_buffer_finish, + .buf_cleanup = vp_in_buffer_cleanup, +}; + + +/* VP O/P Videobuf operations */ + +static int vp_out_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + void *alloc_ctxs[]) +{ + if (*nbuffers >= VIDEO_MAX_FRAME && *nbuffers < 3) + *nbuffers = 3; + + *nplanes = 1; + return 0; +} + +static int vp_out_buffer_init(struct vb2_buffer *vb) +{ + return 0; +} + +static int vp_out_buffer_prepare(struct vb2_buffer *vb) +{ + return 0; +} + +static void vp_out_buffer_queue(struct vb2_buffer *vb) +{ + struct vcap_client_data *cd = vb2_get_drv_priv(vb->vb2_queue); + struct vcap_buffer *buf = container_of(vb, struct vcap_buffer, vb); + struct vp_action *vp_act = &cd->vid_vp_action; + struct vb2_queue *q = vb->vb2_queue; + unsigned long flags = 0; + + spin_lock_irqsave(&cd->cap_slock, flags); + list_add_tail(&buf->list, &vp_act->out_active); + spin_unlock_irqrestore(&cd->cap_slock, flags); + + if (atomic_read(&cd->dev->vp_enabled) == 0) { + if (cd->vid_vp_action.vp_state == VP_FRAME1) { + if (atomic_read(&q->queued_count) > 0 && + atomic_read(& + cd->vp_in_vidq.queued_count) > 1) + kickoff_vp(cd); + } else { + /* VP has already kicked off just needs cont */ + continue_vp(cd); + } + } +} + +static int vp_out_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + return 0; +} + +static int vp_out_stop_streaming(struct vb2_queue *vq) +{ + struct vcap_client_data *c_data = vb2_get_drv_priv(vq); + struct vb2_buffer *vb; + + dprintk(2, "VP out q stop streaming\n"); + vp_stop_capture(c_data); + + while (!list_empty(&c_data->vid_vp_action.out_active)) { + struct vcap_buffer *buf; + buf = list_entry(c_data->vid_vp_action.out_active.next, + struct vcap_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + + /* clean ion handles */ + list_for_each_entry(vb, &vq->queued_list, queued_entry) + free_ion_handle_work(c_data->dev, vb); + return 0; +} + +static int vp_out_buffer_finish(struct vb2_buffer *vb) +{ + return 0; +} + +static void vp_out_buffer_cleanup(struct vb2_buffer *vb) +{ +} + +static struct vb2_ops vp_out_video_qops = { + .queue_setup = vp_out_queue_setup, + .buf_init = vp_out_buffer_init, + .buf_prepare = vp_out_buffer_prepare, + .buf_queue = vp_out_buffer_queue, + .start_streaming = vp_out_start_streaming, + .stop_streaming = vp_out_stop_streaming, + .buf_finish = vp_out_buffer_finish, + .buf_cleanup = vp_out_buffer_cleanup, +}; + +/* IOCTL vidioc handling */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vcap_dev *dev = video_drvdata(file); + + strlcpy(cap->driver, MSM_VCAP_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->card, MSM_VCAP_DRV_NAME, sizeof(cap->card)); + strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info)); + cap->version = 0x10000000; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index >= NUM_INPUTS) + return -EINVAL; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int size; + struct vcap_priv_fmt *priv_fmt; + struct v4l2_format_vc_ext *vc_format; + struct vcap_client_data *c_data = file->private_data; + + priv_fmt = (struct vcap_priv_fmt *) f->fmt.raw_data; + + switch (priv_fmt->type) { + case VC_TYPE: + vc_format = (struct v4l2_format_vc_ext *) &priv_fmt->u.timing; + c_data->vc_format = *vc_format; + + config_vc_format(c_data); + + size = (c_data->vc_format.hactive_end - + c_data->vc_format.hactive_start); + + if (c_data->vc_format.color_space) + size *= 3; + else + size *= 2; + + priv_fmt->u.timing.bytesperline = size; + size *= (c_data->vc_format.vactive_end - + c_data->vc_format.vactive_start); + priv_fmt->u.timing.sizeimage = size; + vcap_ctrl->vc_client = c_data; + c_data->set_cap = true; + break; + case VP_IN_TYPE: + vcap_ctrl->vp_client = c_data; + c_data->vp_in_fmt.width = priv_fmt->u.pix.width; + c_data->vp_in_fmt.height = priv_fmt->u.pix.height; + c_data->vp_in_fmt.pixfmt = priv_fmt->u.pix.pixelformat; + + if (priv_fmt->u.pix.priv) + c_data->vid_vp_action.nr_enabled = 1; + + size = c_data->vp_in_fmt.width * c_data->vp_in_fmt.height; + if (c_data->vp_in_fmt.pixfmt == V4L2_PIX_FMT_NV16) + size = size * 2; + else + size = size / 2 * 3; + priv_fmt->u.pix.sizeimage = size; + c_data->set_decode = true; + break; + case VP_OUT_TYPE: + vcap_ctrl->vp_client = c_data; + c_data->vp_out_fmt.width = priv_fmt->u.pix.width; + c_data->vp_out_fmt.height = priv_fmt->u.pix.height; + c_data->vp_out_fmt.pixfmt = priv_fmt->u.pix.pixelformat; + + if (priv_fmt->u.pix.priv) + c_data->vid_vp_action.nr_enabled = 1; + + size = c_data->vp_out_fmt.width * c_data->vp_out_fmt.height; + if (c_data->vp_out_fmt.pixfmt == V4L2_PIX_FMT_NV16) + size = size * 2; + else + size = size / 2 * 3; + priv_fmt->u.pix.sizeimage = size; + c_data->set_vp_o = true; + break; + default: + break; + } + + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct vcap_client_data *c_data = file->private_data; + int rc; + + dprintk(3, "In Req Buf %08x\n", (unsigned int)rb->type); + c_data->op_mode = determine_mode(c_data); + if (c_data->op_mode == UNKNOWN_VCAP_OP) { + pr_err("VCAP Error: %s: VCAP in unknown mode\n", __func__); + return -ENOTRECOVERABLE; + } + + switch (rb->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (c_data->op_mode == VC_AND_VP_VCAP_OP) { + if (c_data->vc_format.color_space) { + pr_err("VCAP Err: %s: VP No RGB support\n", + __func__); + return -ENOTRECOVERABLE; + } + if (!c_data->vc_format.mode) { + pr_err("VCAP Err: VP No prog support\n"); + return -ENOTRECOVERABLE; + } + if (rb->count < 6) { + pr_err("VCAP Err: Not enough buf for VC_VP\n"); + return -EINVAL; + } + rc = vb2_reqbufs(&c_data->vc_vidq, rb); + if (rc < 0) + return rc; + + c_data->vp_in_fmt.width = + (c_data->vc_format.hactive_end - + c_data->vc_format.hactive_start); + c_data->vp_in_fmt.height = + (c_data->vc_format.vactive_end - + c_data->vc_format.vactive_start); + /* VC outputs YCbCr 4:2:2 */ + c_data->vp_in_fmt.pixfmt = V4L2_PIX_FMT_NV16; + rb->type = V4L2_BUF_TYPE_INTERLACED_IN_DECODER; + rc = vb2_reqbufs(&c_data->vp_in_vidq, rb); + rb->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return rc; + + } else { + return vb2_reqbufs(&c_data->vc_vidq, rb); + } + case V4L2_BUF_TYPE_INTERLACED_IN_DECODER: + return vb2_reqbufs(&c_data->vp_in_vidq, rb); + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return vb2_reqbufs(&c_data->vp_out_vidq, rb); + default: + pr_err("VCAP Error: %s: Unknown buffer type\n", __func__); + return -EINVAL; + } + return 0; +} + +static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct vcap_client_data *c_data = file->private_data; + + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return vb2_querybuf(&c_data->vc_vidq, p); + default: + pr_err("VCAP Error: %s: Unknown buffer type\n", __func__); + return -EINVAL; + } + return 0; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct vcap_client_data *c_data = file->private_data; + struct vb2_buffer *vb; + struct vb2_queue *q; + int rc; + + dprintk(3, "In Q Buf %08x\n", (unsigned int)p->type); + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (c_data->op_mode == VC_AND_VP_VCAP_OP) { + /* If buffer in vp_in_q it will be coming back */ + q = &c_data->vp_in_vidq; + if (p->index >= q->num_buffers) { + dprintk(1, "qbuf: buffer index out of range\n"); + return -EINVAL; + } + + vb = q->bufs[p->index]; + if (NULL == vb) { + dprintk(1, "qbuf: buffer is NULL\n"); + return -EINVAL; + } + + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "qbuf: buffer already in use\n"); + return -EINVAL; + } + } + rc = get_phys_addr(c_data->dev, &c_data->vc_vidq, p); + if (rc < 0) + return rc; + rc = vb2_qbuf(&c_data->vc_vidq, p); + if (rc < 0) + free_ion_handle(c_data->dev, &c_data->vc_vidq, p); + return rc; + case V4L2_BUF_TYPE_INTERLACED_IN_DECODER: + if (c_data->op_mode == VC_AND_VP_VCAP_OP) + return -EINVAL; + rc = get_phys_addr(c_data->dev, &c_data->vp_in_vidq, p); + if (rc < 0) + return rc; + rc = vb2_qbuf(&c_data->vp_in_vidq, p); + if (rc < 0) + free_ion_handle(c_data->dev, &c_data->vp_in_vidq, p); + return rc; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + rc = get_phys_addr(c_data->dev, &c_data->vp_out_vidq, p); + if (rc < 0) + return rc; + rc = vb2_qbuf(&c_data->vp_out_vidq, p); + if (rc < 0) + free_ion_handle(c_data->dev, &c_data->vp_out_vidq, p); + return rc; + default: + pr_err("VCAP Error: %s: Unknown buffer type\n", __func__); + return -EINVAL; + } + return 0; +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct vcap_client_data *c_data = file->private_data; + int rc; + + dprintk(3, "In DQ Buf %08x\n", (unsigned int)p->type); + switch (p->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (c_data->op_mode == VC_AND_VP_VCAP_OP) + return -EINVAL; + rc = vb2_dqbuf(&c_data->vc_vidq, p, file->f_flags & O_NONBLOCK); + if (rc < 0) + return rc; + return free_ion_handle(c_data->dev, &c_data->vc_vidq, p); + case V4L2_BUF_TYPE_INTERLACED_IN_DECODER: + if (c_data->op_mode == VC_AND_VP_VCAP_OP) + return -EINVAL; + rc = vb2_dqbuf(&c_data->vp_in_vidq, p, file->f_flags & + O_NONBLOCK); + if (rc < 0) + return rc; + return free_ion_handle(c_data->dev, &c_data->vp_in_vidq, p); + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + rc = vb2_dqbuf(&c_data->vp_out_vidq, p, file->f_flags & + O_NONBLOCK); + if (rc < 0) + return rc; + return free_ion_handle(c_data->dev, &c_data->vp_out_vidq, p); + default: + pr_err("VCAP Error: %s: Unknown buffer type", __func__); + return -EINVAL; + } + return 0; +} + +/* + * When calling streamon on multiple queues there is a need to first verify + * that the steamon will succeed on all queues, similarly for streamoff + */ +int streamon_validate_q(struct vb2_queue *q) +{ + if (q->fileio) { + dprintk(1, "streamon: file io in progress\n"); + return -EBUSY; + } + + if (q->streaming) { + dprintk(1, "streamon: already streaming\n"); + return -EBUSY; + } + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + if (list_empty(&q->queued_list)) { + dprintk(1, "streamon: no output buffers queued\n"); + return -EINVAL; + } + } + return 0; +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct vcap_client_data *c_data = file->private_data; + int rc; + + dprintk(3, "In Stream ON\n"); + if (determine_mode(c_data) != c_data->op_mode) { + pr_err("VCAP Error: %s: s_fmt called after req_buf", __func__); + return -ENOTRECOVERABLE; + } + + switch (c_data->op_mode) { + case VC_VCAP_OP: + c_data->dev->vc_client = c_data; + config_vc_format(c_data); + return vb2_streamon(&c_data->vc_vidq, i); + case VP_VCAP_OP: + rc = streamon_validate_q(&c_data->vp_in_vidq); + if (rc < 0) + return rc; + rc = streamon_validate_q(&c_data->vp_out_vidq); + if (rc < 0) + return rc; + + c_data->dev->vp_client = c_data; + + rc = config_vp_format(c_data); + if (rc < 0) + return rc; + rc = init_motion_buf(c_data); + if (rc < 0) + return rc; + if (c_data->vid_vp_action.nr_enabled) { + rc = init_nr_buf(c_data); + if (rc < 0) + goto s_on_deinit_m_buf; + } + + c_data->vid_vp_action.vp_state = VP_FRAME1; + + rc = vb2_streamon(&c_data->vp_in_vidq, + V4L2_BUF_TYPE_INTERLACED_IN_DECODER); + if (rc < 0) + goto s_on_deinit_nr_buf; + + rc = vb2_streamon(&c_data->vp_out_vidq, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (rc < 0) + goto s_on_deinit_nr_buf; + return rc; + case VC_AND_VP_VCAP_OP: + rc = streamon_validate_q(&c_data->vc_vidq); + if (rc < 0) + return rc; + rc = streamon_validate_q(&c_data->vp_in_vidq); + if (rc < 0) + return rc; + rc = streamon_validate_q(&c_data->vp_out_vidq); + if (rc < 0) + return rc; + + c_data->dev->vc_client = c_data; + c_data->dev->vp_client = c_data; + c_data->dev->vc_to_vp_work.cd = c_data; + + rc = config_vc_format(c_data); + if (rc < 0) + return rc; + rc = config_vp_format(c_data); + if (rc < 0) + return rc; + rc = init_motion_buf(c_data); + if (rc < 0) + return rc; + if (c_data->vid_vp_action.nr_enabled) { + rc = init_nr_buf(c_data); + if (rc < 0) + goto s_on_deinit_m_buf; + } + c_data->streaming = 1; + + c_data->vid_vp_action.vp_state = VP_FRAME1; + + /* These stream on calls should not fail */ + rc = vb2_streamon(&c_data->vc_vidq, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (rc < 0) + goto s_on_deinit_nr_buf; + + rc = vb2_streamon(&c_data->vp_in_vidq, + V4L2_BUF_TYPE_INTERLACED_IN_DECODER); + if (rc < 0) + goto s_on_deinit_nr_buf; + + rc = vb2_streamon(&c_data->vp_out_vidq, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (rc < 0) + goto s_on_deinit_nr_buf; + return rc; + default: + pr_err("VCAP Error: %s: Operation Mode type", __func__); + return -ENOTRECOVERABLE; + } + return 0; + +s_on_deinit_nr_buf: + if (c_data->vid_vp_action.nr_enabled) + deinit_nr_buf(c_data); +s_on_deinit_m_buf: + deinit_motion_buf(c_data); + return rc; +} + +int streamoff_validate_q(struct vb2_queue *q) +{ + if (q->fileio) { + dprintk(1, "streamoff: file io in progress\n"); + return -EBUSY; + } + + if (!q->streaming) { + dprintk(1, "streamoff: not streaming\n"); + return -EINVAL; + } + return 0; +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct vcap_client_data *c_data = file->private_data; + int rc; + + switch (c_data->op_mode) { + case VC_VCAP_OP: + rc = vb2_streamoff(&c_data->vc_vidq, i); + if (rc >= 0) + atomic_set(&c_data->dev->vc_enabled, 0); + return rc; + case VP_VCAP_OP: + rc = streamoff_validate_q(&c_data->vp_in_vidq); + if (rc < 0) + return rc; + rc = streamoff_validate_q(&c_data->vp_out_vidq); + if (rc < 0) + return rc; + + /* These stream on calls should not fail */ + rc = vb2_streamoff(&c_data->vp_in_vidq, + V4L2_BUF_TYPE_INTERLACED_IN_DECODER); + if (rc < 0) + return rc; + + rc = vb2_streamoff(&c_data->vp_out_vidq, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (rc < 0) + return rc; + + deinit_motion_buf(c_data); + if (c_data->vid_vp_action.nr_enabled) + deinit_nr_buf(c_data); + atomic_set(&c_data->dev->vp_enabled, 0); + return rc; + case VC_AND_VP_VCAP_OP: + rc = streamoff_validate_q(&c_data->vc_vidq); + if (rc < 0) + return rc; + rc = streamoff_validate_q(&c_data->vp_in_vidq); + if (rc < 0) + return rc; + rc = streamoff_validate_q(&c_data->vp_out_vidq); + if (rc < 0) + return rc; + + /* These stream on calls should not fail */ + c_data->streaming = 0; + rc = vb2_streamoff(&c_data->vc_vidq, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (rc < 0) + return rc; + + rc = vb2_streamoff(&c_data->vp_in_vidq, + V4L2_BUF_TYPE_INTERLACED_IN_DECODER); + if (rc < 0) + return rc; + + rc = vb2_streamoff(&c_data->vp_out_vidq, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (rc < 0) + return rc; + + deinit_motion_buf(c_data); + if (c_data->vid_vp_action.nr_enabled) + deinit_nr_buf(c_data); + atomic_set(&c_data->dev->vc_enabled, 0); + atomic_set(&c_data->dev->vp_enabled, 0); + return rc; + default: + pr_err("VCAP Error: %s: Unknown Operation mode", __func__); + return -ENOTRECOVERABLE; + } + return 0; +} + +/* VCAP fops */ +static void *vcap_ops_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct vcap_buf_info *mem; + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + return ERR_PTR(-ENOMEM); + mem->vaddr = vaddr; + mem->size = size; + return mem; +} + +static void vcap_ops_put_userptr(void *buf_priv) +{ + kfree(buf_priv); +} + +const struct vb2_mem_ops vcap_mem_ops = { + .get_userptr = vcap_ops_get_userptr, + .put_userptr = vcap_ops_put_userptr, +}; + +static int vcap_open(struct file *file) +{ + struct vcap_dev *dev = video_drvdata(file); + struct vcap_client_data *c_data; + struct vb2_queue *q; + int ret; + c_data = kzalloc(sizeof(*c_data), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + c_data->dev = dev; + + spin_lock_init(&c_data->cap_slock); + + /* initialize vc queue */ + q = &c_data->vc_vidq; + memset(q, 0, sizeof(c_data->vc_vidq)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_USERPTR; + q->drv_priv = c_data; + q->buf_struct_size = sizeof(struct vcap_buffer); + q->ops = &capture_video_qops; + q->mem_ops = &vcap_mem_ops; + ret = vb2_queue_init(q); + if (ret < 0) + goto vc_q_failed; + + /* initialize vp in queue */ + q = &c_data->vp_in_vidq; + memset(q, 0, sizeof(c_data->vp_in_vidq)); + q->type = V4L2_BUF_TYPE_INTERLACED_IN_DECODER; + q->io_modes = VB2_USERPTR; + q->drv_priv = c_data; + q->buf_struct_size = sizeof(struct vcap_buffer); + q->ops = &vp_in_video_qops; + q->mem_ops = &vcap_mem_ops; + ret = vb2_queue_init(q); + if (ret < 0) + goto vp_in_q_failed; + + /* initialize vp out queue */ + q = &c_data->vp_out_vidq; + memset(q, 0, sizeof(c_data->vp_out_vidq)); + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_USERPTR; + q->drv_priv = c_data; + q->buf_struct_size = sizeof(struct vcap_buffer); + q->ops = &vp_out_video_qops; + q->mem_ops = &vcap_mem_ops; + + ret = vb2_queue_init(q); + if (ret < 0) + goto vp_out_q_failed; + + INIT_LIST_HEAD(&c_data->vid_vc_action.active); + INIT_LIST_HEAD(&c_data->vid_vp_action.in_active); + INIT_LIST_HEAD(&c_data->vid_vp_action.out_active); + file->private_data = c_data; + + return 0; + +vp_out_q_failed: + vb2_queue_release(&c_data->vp_in_vidq); +vp_in_q_failed: + vb2_queue_release(&c_data->vc_vidq); +vc_q_failed: + kfree(c_data); + return ret; +} + +static int vcap_close(struct file *file) +{ + struct vcap_client_data *c_data = file->private_data; + vb2_queue_release(&c_data->vp_out_vidq); + vb2_queue_release(&c_data->vp_in_vidq); + vb2_queue_release(&c_data->vc_vidq); + c_data->dev->vc_client = NULL; + c_data->dev->vp_client = NULL; + kfree(c_data); + return 0; +} + +unsigned int poll_work(struct vb2_queue *q, struct file *file, + poll_table *wait, bool write_q) +{ + unsigned long flags; + struct vb2_buffer *vb = NULL; + + if (q->num_buffers == 0) + return POLLERR; + + if (list_empty(&q->queued_list)) + return POLLERR; + + poll_wait(file, &q->done_wq, wait); + + spin_lock_irqsave(&q->done_lock, flags); + if (!list_empty(&q->done_list)) + vb = list_first_entry(&q->done_list, struct vb2_buffer, + done_entry); + spin_unlock_irqrestore(&q->done_lock, flags); + + if (vb && (vb->state == VB2_BUF_STATE_DONE + || vb->state == VB2_BUF_STATE_ERROR)) { + return (write_q) ? POLLOUT | POLLWRNORM : + POLLIN | POLLRDNORM; + } + return 0; +} + +static unsigned int vcap_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct vcap_client_data *c_data = file->private_data; + struct vb2_queue *q; + unsigned int mask = 0; + + switch (c_data->op_mode) { + case VC_VCAP_OP: + q = &c_data->vc_vidq; + return vb2_poll(q, file, wait); + case VP_VCAP_OP: + q = &c_data->vp_in_vidq; + mask = poll_work(q, file, wait, 0); + q = &c_data->vp_out_vidq; + mask |= poll_work(q, file, wait, 1); + return mask; + case VC_AND_VP_VCAP_OP: + q = &c_data->vp_out_vidq; + mask = poll_work(q, file, wait, 0); + return mask; + default: + pr_err("VCAP Error: %s: Unknown operation mode", __func__); + return POLLERR; + } + return 0; +} +/* V4L2 and video device structures */ + +static const struct v4l2_file_operations vcap_fops = { + .owner = THIS_MODULE, + .open = vcap_open, + .release = vcap_close, + .poll = vcap_poll, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ +}; + +static const struct v4l2_ioctl_ops vcap_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_s_fmt_type_private = vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_type_private = vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + +static struct video_device vcap_template = { + .name = "vcap", + .fops = &vcap_fops, + .ioctl_ops = &vcap_ioctl_ops, + .release = video_device_release, +}; + +int vcap_reg_powerup(struct vcap_dev *dev) +{ + dev->fs_vcap = regulator_get(NULL, "fs_vcap"); + if (IS_ERR(dev->fs_vcap)) { + pr_err("%s: Regulator FS_VCAP get failed %ld\n", __func__, + PTR_ERR(dev->fs_vcap)); + dev->fs_vcap = NULL; + return -EINVAL; + } else if (regulator_enable(dev->fs_vcap)) { + pr_err("%s: Regulator FS_VCAP enable failed\n", __func__); + regulator_put(dev->fs_vcap); + return -EINVAL; + } + return 0; +} + +void vcap_reg_powerdown(struct vcap_dev *dev) +{ + if (dev->fs_vcap == NULL) + return; + regulator_disable(dev->fs_vcap); + regulator_put(dev->fs_vcap); + dev->fs_vcap = NULL; + return; +} + +int config_gpios(int on, struct vcap_platform_data *pdata) +{ + int i, ret; + int num_gpios = pdata->num_gpios; + unsigned *gpios = pdata->gpios; + + if (on) { + for (i = 0; i < num_gpios; i++) { + ret = gpio_request(gpios[i], "vcap:vc"); + if (ret) { + pr_err("VCAP: failed at GPIO %d to request\n", + gpios[i]); + goto gpio_failed; + } + ret = gpio_direction_input(gpios[i]); + if (ret) { + pr_err("VCAP: failed at GPIO %d to set to input\n", + gpios[i]); + i++; + goto gpio_failed; + } + } + } else { + for (i = 0; i < num_gpios; i++) + gpio_free(gpios[i]); + } + dprintk(2, "GPIO config done\n"); + return 0; +gpio_failed: + for (i--; i >= 0; i--) + gpio_free(gpios[i]); + return -EINVAL; +} + +int vcap_clk_powerup(struct vcap_dev *dev, struct device *ddev) +{ + int ret = 0; + + dev->vcap_clk = clk_get(ddev, "core_clk"); + if (IS_ERR(dev->vcap_clk)) { + dev->vcap_clk = NULL; + pr_err("%s: Could not clk_get core_clk\n", __func__); + clk_put(dev->vcap_clk); + dev->vcap_clk = NULL; + return -EINVAL; + } + + clk_prepare(dev->vcap_clk); + ret = clk_enable(dev->vcap_clk); + if (ret) { + pr_err("%s: Failed core clk_enable %d\n", __func__, ret); + goto fail_vcap_clk_unprep; + } + + clk_set_rate(dev->vcap_clk, 160000000); + if (ret) { + pr_err("%s: Failed core set_rate %d\n", __func__, ret); + goto fail_vcap_clk; + } + + dev->vcap_npl_clk = clk_get(ddev, "vcap_npl_clk"); + if (IS_ERR(dev->vcap_npl_clk)) { + dev->vcap_npl_clk = NULL; + pr_err("%s: Could not clk_get npl\n", __func__); + clk_put(dev->vcap_npl_clk); + dev->vcap_npl_clk = NULL; + goto fail_vcap_clk; + } + + clk_prepare(dev->vcap_npl_clk); + ret = clk_enable(dev->vcap_npl_clk); + if (ret) { + pr_err("%s:Failed npl clk_enable %d\n", __func__, ret); + goto fail_vcap_npl_clk_unprep; + } + + dev->vcap_p_clk = clk_get(ddev, "iface_clk"); + if (IS_ERR(dev->vcap_p_clk)) { + dev->vcap_p_clk = NULL; + pr_err("%s: Could not clk_get pix(AHB)\n", __func__); + clk_put(dev->vcap_p_clk); + dev->vcap_p_clk = NULL; + goto fail_vcap_npl_clk; + } + + clk_prepare(dev->vcap_p_clk); + ret = clk_enable(dev->vcap_p_clk); + if (ret) { + pr_err("%s: Failed pix(AHB) clk_enable %d\n", __func__, ret); + goto fail_vcap_p_clk_unprep; + } + return 0; + +fail_vcap_p_clk_unprep: + clk_unprepare(dev->vcap_p_clk); + clk_put(dev->vcap_p_clk); + dev->vcap_p_clk = NULL; + +fail_vcap_npl_clk: + clk_disable(dev->vcap_npl_clk); +fail_vcap_npl_clk_unprep: + clk_unprepare(dev->vcap_npl_clk); + clk_put(dev->vcap_npl_clk); + dev->vcap_npl_clk = NULL; + +fail_vcap_clk: + clk_disable(dev->vcap_clk); +fail_vcap_clk_unprep: + clk_unprepare(dev->vcap_clk); + clk_put(dev->vcap_clk); + dev->vcap_clk = NULL; + return -EINVAL; +} + +void vcap_clk_powerdown(struct vcap_dev *dev) +{ + if (dev->vcap_p_clk != NULL) { + clk_disable(dev->vcap_p_clk); + clk_unprepare(dev->vcap_p_clk); + clk_put(dev->vcap_p_clk); + dev->vcap_p_clk = NULL; + } + + if (dev->vcap_npl_clk != NULL) { + clk_disable(dev->vcap_npl_clk); + clk_unprepare(dev->vcap_npl_clk); + clk_put(dev->vcap_npl_clk); + dev->vcap_npl_clk = NULL; + } + + if (dev->vcap_clk != NULL) { + clk_disable(dev->vcap_clk); + clk_unprepare(dev->vcap_clk); + clk_put(dev->vcap_clk); + dev->vcap_clk = NULL; + } +} + +int vcap_get_bus_client_handle(struct vcap_dev *dev) +{ + struct msm_bus_scale_pdata *vcap_axi_client_pdata = + dev->vcap_pdata->bus_client_pdata; + dev->bus_client_handle = + msm_bus_scale_register_client(vcap_axi_client_pdata); + + return 0; +} + +int vcap_enable(struct vcap_dev *dev, struct device *ddev) +{ + int rc; + + rc = vcap_reg_powerup(dev); + if (rc < 0) + goto reg_failed; + rc = vcap_clk_powerup(dev, ddev); + if (rc < 0) + goto clk_failed; + rc = vcap_get_bus_client_handle(dev); + if (rc < 0) + goto bus_r_failed; + config_gpios(1, dev->vcap_pdata); + if (rc < 0) + goto gpio_failed; + return 0; + +gpio_failed: + msm_bus_scale_unregister_client(dev->bus_client_handle); + dev->bus_client_handle = 0; +bus_r_failed: + vcap_clk_powerdown(dev); +clk_failed: + vcap_reg_powerdown(dev); +reg_failed: + return rc; +} + +int vcap_disable(struct vcap_dev *dev) +{ + config_gpios(0, dev->vcap_pdata); + + msm_bus_scale_unregister_client(dev->bus_client_handle); + dev->bus_client_handle = 0; + vcap_clk_powerdown(dev); + vcap_reg_powerdown(dev); + return 0; +} + +static irqreturn_t vcap_vp_handler(int irq_num, void *data) +{ + return vp_handler(vcap_ctrl); +} + +static irqreturn_t vcap_vc_handler(int irq_num, void *data) +{ + return vc_handler(vcap_ctrl); +} + +static int __devinit vcap_probe(struct platform_device *pdev) +{ + struct vcap_dev *dev; + struct video_device *vfd; + int ret; + + dprintk(1, "Probe started\n"); + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + vcap_ctrl = dev; + dev->vcap_pdata = pdev->dev.platform_data; + + dev->vcapmem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "vcap"); + if (!dev->vcapmem) { + pr_err("VCAP: %s: no mem resource?\n", __func__); + ret = -ENODEV; + goto free_dev; + } + + dev->vcapio = request_mem_region(dev->vcapmem->start, + resource_size(dev->vcapmem), pdev->name); + if (!dev->vcapio) { + pr_err("VCAP: %s: no valid mem region\n", __func__); + ret = -EBUSY; + goto free_dev; + } + + dev->vcapbase = ioremap(dev->vcapmem->start, + resource_size(dev->vcapmem)); + if (!dev->vcapbase) { + ret = -ENOMEM; + pr_err("VCAP: %s: vcap ioremap failed\n", __func__); + goto free_resource; + } + + dev->vcirq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "vc_irq"); + if (!dev->vcirq) { + pr_err("%s: no vc irq resource?\n", __func__); + ret = -ENODEV; + goto free_resource; + } + dev->vpirq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "vp_irq"); + if (!dev->vpirq) { + pr_err("%s: no vp irq resource?\n", __func__); + ret = -ENODEV; + goto free_resource; + } + + + ret = request_irq(dev->vcirq->start, vcap_vc_handler, + IRQF_TRIGGER_RISING, "vc_irq", 0); + if (ret < 0) { + pr_err("%s: vc irq request fail\n", __func__); + ret = -EBUSY; + goto free_resource; + } + disable_irq(dev->vcirq->start); + + ret = request_irq(dev->vpirq->start, vcap_vp_handler, + IRQF_TRIGGER_RISING, "vp_irq", 0); + + if (ret < 0) { + pr_err("%s: vp irq request fail\n", __func__); + ret = -EBUSY; + goto free_resource; + } + disable_irq(dev->vpirq->start); + + snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), + "%s", MSM_VCAP_DRV_NAME); + + ret = v4l2_device_register(NULL, &dev->v4l2_dev); + if (ret) + goto free_resource; + + ret = vcap_enable(dev, &pdev->dev); + if (ret) + goto unreg_dev; + msm_bus_scale_client_update_request(dev->bus_client_handle, 3); + + ret = detect_vc(dev); + + if (ret) + goto power_down; + + /* init video device*/ + vfd = video_device_alloc(); + if (!vfd) + goto deinit_vc; + + *vfd = vcap_template; + vfd->v4l2_dev = &dev->v4l2_dev; + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); + if (ret < 0) + goto rel_vdev; + + dev->vfd = vfd; + video_set_drvdata(vfd, dev); + + dev->vcap_wq = create_workqueue("vcap"); + if (!dev->vcap_wq) { + pr_err("Could not create workqueue"); + goto rel_vdev; + } + + dev->ion_client = msm_ion_client_create(-1, "vcap"); + if (IS_ERR((void *)dev->ion_client)) { + pr_err("could not get ion client"); + goto rel_vcap_wq; + } + + atomic_set(&dev->vc_enabled, 0); + atomic_set(&dev->vp_enabled, 0); + + dprintk(1, "Exit probe succesfully"); + return 0; +rel_vcap_wq: + destroy_workqueue(dev->vcap_wq); +rel_vdev: + video_device_release(vfd); +deinit_vc: + deinit_vc(); +power_down: + vcap_disable(dev); +unreg_dev: + v4l2_device_unregister(&dev->v4l2_dev); +free_resource: + iounmap(dev->vcapbase); + release_mem_region(dev->vcapmem->start, resource_size(dev->vcapmem)); +free_dev: + vcap_ctrl = NULL; + kfree(dev); + return ret; +} + +static int __devexit vcap_remove(struct platform_device *pdev) +{ + struct vcap_dev *dev = vcap_ctrl; + ion_client_destroy(dev->ion_client); + flush_workqueue(dev->vcap_wq); + destroy_workqueue(dev->vcap_wq); + video_device_release(dev->vfd); + deinit_vc(); + vcap_disable(dev); + v4l2_device_unregister(&dev->v4l2_dev); + iounmap(dev->vcapbase); + release_mem_region(dev->vcapmem->start, resource_size(dev->vcapmem)); + vcap_ctrl = NULL; + kfree(dev); + + return 0; +} + +struct platform_driver vcap_platform_driver = { + .driver = { + .name = MSM_VCAP_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = vcap_probe, + .remove = vcap_remove, +}; + +static int __init vcap_init_module(void) +{ + return platform_driver_register(&vcap_platform_driver); +} + +static void __exit vcap_exit_module(void) +{ + platform_driver_unregister(&vcap_platform_driver); +} + +module_init(vcap_init_module); +module_exit(vcap_exit_module); +MODULE_DESCRIPTION("VCAP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/vcap_vc.c b/drivers/media/video/vcap_vc.c new file mode 100644 index 0000000000000000000000000000000000000000..2c4a243fb7cf2dbf1beca5c00e4d1db3a9887af2 --- /dev/null +++ b/drivers/media/video/vcap_vc.c @@ -0,0 +1,387 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "vcap_vc.h" + +static unsigned debug; + +#define dprintk(level, fmt, arg...) \ + do { \ + if (debug >= level) \ + printk(KERN_DEBUG "VC: " fmt, ## arg); \ + } while (0) + +void config_buffer(struct vcap_client_data *c_data, + struct vcap_buffer *buf, + void __iomem *y_addr, + void __iomem *c_addr) +{ + if (c_data->vc_format.color_space == HAL_VCAP_RGB) { + writel_relaxed(buf->paddr, y_addr); + } else { + int size = ((c_data->vc_format.hactive_end - + c_data->vc_format.hactive_start) * + (c_data->vc_format.vactive_end - + c_data->vc_format.vactive_start)); + writel_relaxed(buf->paddr, y_addr); + writel_relaxed(buf->paddr + size, c_addr); + } +} + +static void mov_buf_to_vp(struct work_struct *work) +{ + struct vp_work_t *vp_work = container_of(work, struct vp_work_t, work); + struct v4l2_buffer p; + struct vb2_buffer *vb_vc; + struct vcap_buffer *buf_vc; + struct vb2_buffer *vb_vp; + struct vcap_buffer *buf_vp; + + int rc; + p.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + p.memory = V4L2_MEMORY_USERPTR; + while (1) { + if (!vp_work->cd->streaming) + return; + rc = vb2_dqbuf(&vp_work->cd->vc_vidq, &p, O_NONBLOCK); + if (rc < 0) + return; + + vb_vc = vp_work->cd->vc_vidq.bufs[p.index]; + if (NULL == vb_vc) { + dprintk(1, "%s: buffer is NULL\n", __func__); + vb2_qbuf(&vp_work->cd->vc_vidq, &p); + return; + } + buf_vc = container_of(vb_vc, struct vcap_buffer, vb); + + vb_vp = vp_work->cd->vp_in_vidq.bufs[p.index]; + if (NULL == vb_vp) { + dprintk(1, "%s: buffer is NULL\n", __func__); + vb2_qbuf(&vp_work->cd->vc_vidq, &p); + return; + } + buf_vp = container_of(vb_vp, struct vcap_buffer, vb); + buf_vp->ion_handle = buf_vc->ion_handle; + buf_vp->paddr = buf_vc->paddr; + buf_vc->ion_handle = NULL; + buf_vc->paddr = 0; + + p.type = V4L2_BUF_TYPE_INTERLACED_IN_DECODER; + + /* This call should not fail */ + rc = vb2_qbuf(&vp_work->cd->vp_in_vidq, &p); + if (rc < 0) { + pr_err("%s: qbuf to vp_in failed\n", __func__); + buf_vc->ion_handle = buf_vp->ion_handle; + buf_vc->paddr = buf_vp->paddr; + buf_vp->ion_handle = NULL; + buf_vp->paddr = 0; + p.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vb2_qbuf(&vp_work->cd->vc_vidq, &p); + } + } +} + +irqreturn_t vc_handler(struct vcap_dev *dev) +{ + uint32_t irq, timestamp; + enum rdy_buf vc_buf_status, buf_ind; + struct vcap_buffer *buf; + struct vb2_buffer *vb = NULL; + struct vcap_client_data *c_data; + + + irq = readl_relaxed(VCAP_VC_INT_STATUS); + + dprintk(1, "%s: irq=0x%08x\n", __func__, irq); + + vc_buf_status = irq & VC_BUFFER_WRITTEN; + + dprintk(1, "Done buf status = %d\n", vc_buf_status); + + if (vc_buf_status == VC_NO_BUF) { + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + pr_err("VC IRQ shows some error\n"); + return IRQ_HANDLED; + } + + if (dev->vc_client == NULL) { + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + pr_err("VC: There is no active vc client\n"); + return IRQ_HANDLED; + } + c_data = dev->vc_client; + + spin_lock(&dev->vc_client->cap_slock); + if (list_empty(&dev->vc_client->vid_vc_action.active)) { + /* Just leave we have no new queued buffers */ + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + spin_unlock(&dev->vc_client->cap_slock); + dprintk(1, "We have no more avilable buffers\n"); + return IRQ_HANDLED; + } + spin_unlock(&dev->vc_client->cap_slock); + + timestamp = readl_relaxed(VCAP_VC_TIMESTAMP); + + buf_ind = dev->vc_client->vid_vc_action.buf_ind; + + if (vc_buf_status == VC_BUF1N2) { + /* There are 2 buffer ready */ + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + return IRQ_HANDLED; + } else if (buf_ind != vc_buf_status) { + /* buffer is out of sync */ + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + return IRQ_HANDLED; + } + + if (buf_ind == VC_BUF1) { + dprintk(1, "Got BUF1\n"); + vb = &dev->vc_client->vid_vc_action.buf1->vb; + spin_lock(&dev->vc_client->cap_slock); + if (list_empty(&dev->vc_client->vid_vc_action.active)) { + spin_unlock(&dev->vc_client->cap_slock); + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + return IRQ_HANDLED; + } + buf = list_entry(dev->vc_client->vid_vc_action.active.next, + struct vcap_buffer, list); + list_del(&buf->list); + spin_unlock(&dev->vc_client->cap_slock); + /* Config vc with this new buffer */ + config_buffer(c_data, buf, VCAP_VC_Y_ADDR_1, + VCAP_VC_C_ADDR_1); + + vb->v4l2_buf.timestamp.tv_usec = timestamp; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + dev->vc_client->vid_vc_action.buf1 = buf; + dev->vc_client->vid_vc_action.buf_ind = VC_BUF2; + irq = VC_BUF1; + } else { + dprintk(1, "Got BUF2\n"); + spin_lock(&dev->vc_client->cap_slock); + vb = &dev->vc_client->vid_vc_action.buf2->vb; + if (list_empty(&dev->vc_client->vid_vc_action.active)) { + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + spin_unlock(&dev->vc_client->cap_slock); + return IRQ_HANDLED; + } + buf = list_entry(dev->vc_client->vid_vc_action.active.next, + struct vcap_buffer, list); + list_del(&buf->list); + spin_unlock(&dev->vc_client->cap_slock); + /* Config vc with this new buffer */ + config_buffer(c_data, buf, VCAP_VC_Y_ADDR_2, + VCAP_VC_C_ADDR_2); + + vb->v4l2_buf.timestamp.tv_usec = timestamp; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + + dev->vc_client->vid_vc_action.buf2 = buf; + dev->vc_client->vid_vc_action.buf_ind = VC_BUF1; + irq = VC_BUF2; + } + + if (c_data->op_mode == VC_AND_VP_VCAP_OP) + queue_work(dev->vcap_wq, &dev->vc_to_vp_work.work); + + writel_relaxed(irq, VCAP_VC_INT_CLEAR); + + return IRQ_HANDLED; +} + +int vc_start_capture(struct vcap_client_data *c_data) +{ + return 0; +} + +int vc_hw_kick_off(struct vcap_client_data *c_data) +{ + struct vcap_action *vid_vc_action = &c_data->vid_vc_action; + struct vcap_dev *dev; + unsigned long flags = 0; + int rc, counter = 0; + struct vcap_buffer *buf; + + dev = c_data->dev; + vid_vc_action->buf_ind = VC_BUF1; + dprintk(2, "Start Kickoff\n"); + + if (dev->vc_client == NULL) { + pr_err("No active vc client\n"); + return -ENODEV; + } + spin_lock_irqsave(&dev->vc_client->cap_slock, flags); + if (list_empty(&dev->vc_client->vid_vc_action.active)) { + spin_unlock_irqrestore(&dev->vc_client->cap_slock, flags); + pr_err("%s: VC We have no more avilable buffers\n", + __func__); + return -EINVAL; + } + + list_for_each_entry(buf, &vid_vc_action->active, list) + counter++; + + if (counter < 2) { + /* not enough buffers have been queued */ + spin_unlock_irqrestore(&dev->vc_client->cap_slock, flags); + return -EINVAL; + } + + vid_vc_action->buf1 = list_entry(vid_vc_action->active.next, + struct vcap_buffer, list); + list_del(&vid_vc_action->buf1->list); + + vid_vc_action->buf2 = list_entry(vid_vc_action->active.next, + struct vcap_buffer, list); + list_del(&vid_vc_action->buf2->list); + + spin_unlock_irqrestore(&dev->vc_client->cap_slock, flags); + + config_buffer(c_data, vid_vc_action->buf1, VCAP_VC_Y_ADDR_1, + VCAP_VC_C_ADDR_1); + config_buffer(c_data, vid_vc_action->buf2, VCAP_VC_Y_ADDR_2, + VCAP_VC_C_ADDR_2); + + rc = readl_relaxed(VCAP_VC_CTRL); + writel_iowmb(rc | 0x1, VCAP_VC_CTRL); + + writel_relaxed(0x6, VCAP_VC_INT_MASK); + + enable_irq(dev->vcirq->start); + return 0; +} + +void vc_stop_capture(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + int rc; + + rc = readl_relaxed(VCAP_VC_CTRL); + writel_iowmb(rc & ~(0x1), VCAP_VC_CTRL); + + if (atomic_read(&dev->vc_enabled) == 1) + disable_irq(dev->vcirq->start); + + flush_workqueue(dev->vcap_wq); +} + +int config_vc_format(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev; + unsigned int rc; + int timeout; + struct v4l2_format_vc_ext *vc_format = &c_data->vc_format; + dev = c_data->dev; + + /* restart VC */ + writel_relaxed(0x00000001, VCAP_SW_RESET_REQ); + timeout = 10000; + while (1) { + rc = (readl_relaxed(VCAP_SW_RESET_STATUS) & 0x1); + if (!rc) + break; + timeout--; + if (timeout == 0) { + pr_err("VC is not resetting properly\n"); + return -EINVAL; + } + } + writel_relaxed(0x00000000, VCAP_SW_RESET_REQ); + + writel_iowmb(0x00000102, VCAP_VC_NPL_CTRL); + rc = readl_relaxed(VCAP_VC_NPL_CTRL); + rc = readl_relaxed(VCAP_VC_NPL_CTRL); + writel_iowmb(0x00000002, VCAP_VC_NPL_CTRL); + + dprintk(2, "%s: Starting VC configuration\n", __func__); + writel_iowmb(0x00000002, VCAP_VC_NPL_CTRL); + writel_iowmb(0x00000004 | vc_format->color_space << 1 | + vc_format->mode << 3 | + vc_format->mode << 10, VCAP_VC_CTRL); + + writel_relaxed(vc_format->h_polar << 4 | + vc_format->v_polar << 0, VCAP_VC_POLARITY); + + writel_relaxed(vc_format->h_polar << 4 | + vc_format->v_polar << 0, VCAP_VC_POLARITY); + writel_relaxed(((vc_format->htotal << 16) | vc_format->vtotal), + VCAP_VC_V_H_TOTAL); + writel_relaxed(((vc_format->hactive_end << 16) | + vc_format->hactive_start), VCAP_VC_H_ACTIVE); + + writel_relaxed(((vc_format->vactive_end << 16) | + vc_format->vactive_start), VCAP_VC_V_ACTIVE); + writel_relaxed(((vc_format->f2_vactive_end << 16) | + vc_format->f2_vactive_start), VCAP_VC_V_ACTIVE_F2); + writel_relaxed(((vc_format->vsync_end << 16) | vc_format->vsync_start), + VCAP_VC_VSYNC_VPOS); + writel_relaxed(((vc_format->f2_vsync_v_end << 16) | + vc_format->f2_vsync_v_start), VCAP_VC_VSYNC_F2_VPOS); + writel_relaxed(((vc_format->hsync_end << 16) | + vc_format->hsync_start), VCAP_VC_HSYNC_HPOS); + writel_relaxed(((vc_format->f2_vsync_h_end << 16) | + vc_format->f2_vsync_h_start), VCAP_VC_VSYNC_F2_HPOS); + writel_iowmb(0x000033FF, VCAP_VC_BUF_CTRL); + + rc = vc_format->hactive_end - vc_format->hactive_start; + if (vc_format->color_space) + rc *= 3; + + writel_relaxed(rc, VCAP_VC_Y_STRIDE); + writel_relaxed(rc, VCAP_VC_C_STRIDE); + + writel_relaxed(0x00010033 , VCAP_OFFSET(0x0898)); + writel_relaxed(0x00010fff , VCAP_OFFSET(0x089c)); + writel_relaxed(0x0a418820, VCAP_VC_IN_CTRL1); + writel_relaxed(0x16a4a0e6, VCAP_VC_IN_CTRL2); + writel_relaxed(0x2307b9ac, VCAP_VC_IN_CTRL3); + writel_relaxed(0x2f6ad272, VCAP_VC_IN_CTRL4); + writel_relaxed(0x00006b38, VCAP_VC_IN_CTRL5); + + writel_iowmb(0x00000001 , VCAP_OFFSET(0x0d00)); + dprintk(2, "%s: Done VC configuration\n", __func__); + + return 0; +} + +int detect_vc(struct vcap_dev *dev) +{ + int result; + result = readl_relaxed(VCAP_HARDWARE_VERSION_REG); + dprintk(1, "Hardware version: %08x\n", result); + if (result != VCAP_HARDWARE_VERSION) + return -ENODEV; + INIT_WORK(&dev->vc_to_vp_work.work, mov_buf_to_vp); + return 0; +} + +int deinit_vc(void) +{ + return 0; +} diff --git a/drivers/media/video/vcap_vc.h b/drivers/media/video/vcap_vc.h new file mode 100644 index 0000000000000000000000000000000000000000..57d13cd1dc73b0f2d9d56770e7574b432c7fcdae --- /dev/null +++ b/drivers/media/video/vcap_vc.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef VCAP_VC_H +#define VCAP_VC_H + +#include + +#include + +#define VCAP_HARDWARE_VERSION 0x10000000 + +#define VCAP_BASE (dev->vcapbase) +#define VCAP_OFFSET(off) (VCAP_BASE + off) + +#define VCAP_HARDWARE_VERSION_REG (VCAP_BASE + 0x0000) + +#define VCAP_SW_RESET_REQ (VCAP_BASE + 0x0024) +#define VCAP_SW_RESET_STATUS (VCAP_BASE + 0x0028) + +#define VCAP_VC_CTRL (VCAP_BASE + 0x0800) +#define VCAP_VC_NPL_CTRL (VCAP_BASE + 0x0804) +#define VCAP_VC_POLARITY (VCAP_BASE + 0x081c) +#define VCAP_VC_V_H_TOTAL (VCAP_BASE + 0x0820) +#define VCAP_VC_H_ACTIVE (VCAP_BASE + 0x0824) +#define VCAP_VC_V_ACTIVE (VCAP_BASE + 0x0828) +#define VCAP_VC_V_ACTIVE_F2 (VCAP_BASE + 0x0830) +#define VCAP_VC_VSYNC_VPOS (VCAP_BASE + 0x0834) +#define VCAP_VC_VSYNC_F2_VPOS (VCAP_BASE + 0x0838) +#define VCAP_VC_HSYNC_HPOS (VCAP_BASE + 0x0840) +#define VCAP_VC_VSYNC_F2_HPOS (VCAP_BASE + 0x083c) +#define VCAP_VC_BUF_CTRL (VCAP_BASE + 0x0848) + +#define VCAP_VC_Y_STRIDE (VCAP_BASE + 0x084c) +#define VCAP_VC_C_STRIDE (VCAP_BASE + 0x0850) + +#define VCAP_VC_Y_ADDR_1 (VCAP_BASE + 0x0854) +#define VCAP_VC_C_ADDR_1 (VCAP_BASE + 0x0858) +#define VCAP_VC_Y_ADDR_2 (VCAP_BASE + 0x085c) +#define VCAP_VC_C_ADDR_2 (VCAP_BASE + 0x0860) +#define VCAP_VC_Y_ADDR_3 (VCAP_BASE + 0x0864) +#define VCAP_VC_C_ADDR_3 (VCAP_BASE + 0x0868) +#define VCAP_VC_Y_ADDR_4 (VCAP_BASE + 0x086c) +#define VCAP_VC_C_ADDR_4 (VCAP_BASE + 0x0870) +#define VCAP_VC_Y_ADDR_5 (VCAP_BASE + 0x0874) +#define VCAP_VC_C_ADDR_5 (VCAP_BASE + 0x0878) +#define VCAP_VC_Y_ADDR_6 (VCAP_BASE + 0x087c) +#define VCAP_VC_C_ADDR_6 (VCAP_BASE + 0x0880) + +#define VCAP_VC_IN_CTRL1 (VCAP_BASE + 0x0808) +#define VCAP_VC_IN_CTRL2 (VCAP_BASE + 0x080c) +#define VCAP_VC_IN_CTRL3 (VCAP_BASE + 0x0810) +#define VCAP_VC_IN_CTRL4 (VCAP_BASE + 0x0814) +#define VCAP_VC_IN_CTRL5 (VCAP_BASE + 0x0818) + +#define VCAP_VC_INT_MASK (VCAP_BASE + 0x0884) +#define VCAP_VC_INT_CLEAR (VCAP_BASE + 0x0888) +#define VCAP_VC_INT_STATUS (VCAP_BASE + 0x088c) +#define VCAP_VC_TIMESTAMP (VCAP_BASE + 0x0034) + +#define VC_BUFFER_WRITTEN (0x3 << 1) + +struct vc_reg_data { + unsigned data; + unsigned addr; +}; + +int vc_start_capture(struct vcap_client_data *c_data); +int vc_hw_kick_off(struct vcap_client_data *c_data); +void vc_stop_capture(struct vcap_client_data *c_data); +int config_vc_format(struct vcap_client_data *c_data); +int detect_vc(struct vcap_dev *dev); +int deinit_vc(void); +irqreturn_t vc_handler(struct vcap_dev *dev); +#endif diff --git a/drivers/media/video/vcap_vp.c b/drivers/media/video/vcap_vp.c new file mode 100644 index 0000000000000000000000000000000000000000..f8dfdc1ade1c6109b11c768594679982a6fb3b7e --- /dev/null +++ b/drivers/media/video/vcap_vp.c @@ -0,0 +1,606 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "vcap_vp.h" + +static unsigned debug; + +#define dprintk(level, fmt, arg...) \ + do { \ + if (debug >= level) \ + printk(KERN_DEBUG "VP: " fmt, ## arg); \ + } while (0) + +void config_nr_buffer(struct vcap_client_data *c_data, + struct vcap_buffer *buf) +{ + struct vcap_dev *dev = c_data->dev; + int size = c_data->vp_in_fmt.height * c_data->vp_in_fmt.width; + + writel_relaxed(buf->paddr, VCAP_VP_NR_T2_Y_BASE_ADDR); + writel_relaxed(buf->paddr + size, VCAP_VP_NR_T2_C_BASE_ADDR); +} + +void config_in_buffer(struct vcap_client_data *c_data, + struct vcap_buffer *buf) +{ + struct vcap_dev *dev = c_data->dev; + int size = c_data->vp_in_fmt.height * c_data->vp_in_fmt.width; + + writel_relaxed(buf->paddr, VCAP_VP_T2_Y_BASE_ADDR); + writel_relaxed(buf->paddr + size, VCAP_VP_T2_C_BASE_ADDR); +} + +void config_out_buffer(struct vcap_client_data *c_data, + struct vcap_buffer *buf) +{ + struct vcap_dev *dev = c_data->dev; + int size; + size = c_data->vp_out_fmt.height * c_data->vp_out_fmt.width; + writel_relaxed(buf->paddr, VCAP_VP_OUT_Y_BASE_ADDR); + writel_relaxed(buf->paddr + size, VCAP_VP_OUT_C_BASE_ADDR); +} + +int vp_setup_buffers(struct vcap_client_data *c_data) +{ + struct vp_action *vp_act; + struct vcap_dev *dev; + unsigned long flags = 0; + + if (!c_data->streaming) + return -ENOEXEC; + dev = c_data->dev; + dprintk(2, "Start setup buffers\n"); + + /* No need to verify vp_client is not NULL caller does so */ + vp_act = &dev->vp_client->vid_vp_action; + + spin_lock_irqsave(&dev->vp_client->cap_slock, flags); + if (list_empty(&vp_act->in_active)) { + spin_unlock_irqrestore(&dev->vp_client->cap_slock, flags); + dprintk(1, "%s: VP We have no more input buffers\n", + __func__); + return -EAGAIN; + } + + if (list_empty(&vp_act->out_active)) { + spin_unlock_irqrestore(&dev->vp_client->cap_slock, + flags); + dprintk(1, "%s: VP We have no more output buffers\n", + __func__); + return -EAGAIN; + } + + vp_act->bufT2 = list_entry(vp_act->in_active.next, + struct vcap_buffer, list); + list_del(&vp_act->bufT2->list); + + vp_act->bufOut = list_entry(vp_act->out_active.next, + struct vcap_buffer, list); + list_del(&vp_act->bufOut->list); + spin_unlock_irqrestore(&dev->vp_client->cap_slock, flags); + + config_in_buffer(c_data, vp_act->bufT2); + config_out_buffer(c_data, vp_act->bufOut); + return 0; +} + +static void mov_buf_to_vc(struct work_struct *work) +{ + struct vp_work_t *vp_work = container_of(work, struct vp_work_t, work); + struct v4l2_buffer p; + struct vb2_buffer *vb_vc; + struct vcap_buffer *buf_vc; + struct vb2_buffer *vb_vp; + struct vcap_buffer *buf_vp; + int rc; + + p.type = V4L2_BUF_TYPE_INTERLACED_IN_DECODER; + p.memory = V4L2_MEMORY_USERPTR; + + /* This loop exits when there is no more buffers left */ + while (1) { + if (!vp_work->cd->streaming) + return; + rc = vb2_dqbuf(&vp_work->cd->vp_in_vidq, &p, O_NONBLOCK); + if (rc < 0) + return; + + vb_vc = vp_work->cd->vc_vidq.bufs[p.index]; + if (NULL == vb_vc) { + dprintk(1, "%s: buffer is NULL\n", __func__); + vb2_qbuf(&vp_work->cd->vp_in_vidq, &p); + return; + } + buf_vc = container_of(vb_vc, struct vcap_buffer, vb); + + vb_vp = vp_work->cd->vp_in_vidq.bufs[p.index]; + if (NULL == vb_vp) { + dprintk(1, "%s: buffer is NULL\n", __func__); + vb2_qbuf(&vp_work->cd->vp_in_vidq, &p); + return; + } + buf_vp = container_of(vb_vp, struct vcap_buffer, vb); + buf_vc->ion_handle = buf_vp->ion_handle; + buf_vc->paddr = buf_vp->paddr; + buf_vp->ion_handle = NULL; + buf_vp->paddr = 0; + + p.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + /* This call should not fail */ + rc = vb2_qbuf(&vp_work->cd->vc_vidq, &p); + if (rc < 0) { + dprintk(1, "%s: qbuf to vc failed\n", __func__); + buf_vp->ion_handle = buf_vc->ion_handle; + buf_vp->paddr = buf_vc->paddr; + buf_vc->ion_handle = NULL; + buf_vc->paddr = 0; + p.type = V4L2_BUF_TYPE_INTERLACED_IN_DECODER; + vb2_qbuf(&vp_work->cd->vp_in_vidq, &p); + } + } +} + +static void vp_wq_fnc(struct work_struct *work) +{ + struct vp_work_t *vp_work = container_of(work, struct vp_work_t, work); + struct vcap_dev *dev; + struct vp_action *vp_act; + uint32_t irq; + int rc; +#ifndef TOP_FIELD_FIX + bool top_field; +#endif + + if (vp_work && vp_work->cd && vp_work->cd->dev) + dev = vp_work->cd->dev; + else + return; + + vp_act = &dev->vp_client->vid_vp_action; + irq = vp_work->irq; + + rc = readl_relaxed(VCAP_OFFSET(0x048)); + while (!(rc & 0x00000100)) + rc = readl_relaxed(VCAP_OFFSET(0x048)); + + writel_relaxed(0x00000000, VCAP_VP_BAL_VMOTION_STATE); + writel_relaxed(0x40000000, VCAP_VP_REDUCT_AVG_MOTION2); + + /* Queue the done buffers */ + if (vp_act->vp_state == VP_NORMAL && + vp_act->bufNR.nr_pos != TM1_BUF) { + vb2_buffer_done(&vp_act->bufTm1->vb, VB2_BUF_STATE_DONE); + if (vp_work->cd->op_mode == VC_AND_VP_VCAP_OP) + queue_work(dev->vcap_wq, &dev->vp_to_vc_work.work); + } + + vb2_buffer_done(&vp_act->bufOut->vb, VB2_BUF_STATE_DONE); + + /* Cycle to next state */ + if (vp_act->vp_state != VP_NORMAL) + vp_act->vp_state++; +#ifdef TOP_FIELD_FIX + vp_act->top_field = !vp_act->top_field; +#endif + + /* Cycle Buffers*/ + if (vp_work->cd->vid_vp_action.nr_enabled) { + if (vp_act->bufNR.nr_pos == TM1_BUF) + vp_act->bufNR.nr_pos = BUF_NOT_IN_USE; + + if (vp_act->bufNR.nr_pos != BUF_NOT_IN_USE) + vp_act->bufNR.nr_pos++; + + vp_act->bufTm1 = vp_act->bufT0; + vp_act->bufT0 = vp_act->bufT1; + vp_act->bufT1 = vp_act->bufNRT2; + vp_act->bufNRT2 = vp_act->bufT2; + config_nr_buffer(vp_work->cd, vp_act->bufNRT2); + } else { + vp_act->bufTm1 = vp_act->bufT0; + vp_act->bufT0 = vp_act->bufT1; + vp_act->bufT1 = vp_act->bufT2; + } + + rc = vp_setup_buffers(vp_work->cd); + if (rc < 0) { + /* setup_buf failed because we are waiting for buffers */ + writel_relaxed(0x00000000, VCAP_VP_INTERRUPT_ENABLE); + writel_iowmb(irq, VCAP_VP_INT_CLEAR); + atomic_set(&dev->vp_enabled, 0); + return; + } + + /* Config VP */ +#ifndef TOP_FIELD_FIX + if (vp_act->bufT2->vb.v4l2_buf.field == V4L2_FIELD_TOP) + top_field = 1; +#endif + +#ifdef TOP_FIELD_FIX + writel_iowmb(0x00000000 | vp_act->top_field << 0, VCAP_VP_CTRL); + writel_iowmb(0x00030000 | vp_act->top_field << 0, VCAP_VP_CTRL); +#else + writel_iowmb(0x00000000 | top_field, VCAP_VP_CTRL); + writel_iowmb(0x00030000 | top_field, VCAP_VP_CTRL); +#endif + enable_irq(dev->vpirq->start); + writel_iowmb(irq, VCAP_VP_INT_CLEAR); +} + +irqreturn_t vp_handler(struct vcap_dev *dev) +{ + struct vcap_client_data *c_data; + struct vp_action *vp_act; + uint32_t irq; + int rc; + + irq = readl_relaxed(VCAP_VP_INT_STATUS); + + dprintk(1, "%s: irq=0x%08x\n", __func__, irq); + if (!irq & VP_PIC_DONE) { + writel_relaxed(irq, VCAP_VP_INT_CLEAR); + pr_err("VP IRQ shows some error\n"); + return IRQ_HANDLED; + } + + if (dev->vp_client == NULL) { + writel_relaxed(irq, VCAP_VP_INT_CLEAR); + pr_err("VC: There is no active vp client\n"); + return IRQ_HANDLED; + } + + vp_act = &dev->vp_client->vid_vp_action; + c_data = dev->vp_client; + + if (vp_act->vp_state == VP_UNKNOWN) { + writel_relaxed(irq, VCAP_VP_INT_CLEAR); + pr_err("%s: VP is in an unknown state\n", + __func__); + return -EAGAIN; + } + + INIT_WORK(&dev->vp_work.work, vp_wq_fnc); + dev->vp_work.cd = c_data; + dev->vp_work.irq = irq; + rc = queue_work(dev->vcap_wq, &dev->vp_work.work); + + disable_irq_nosync(dev->vpirq->start); + return IRQ_HANDLED; +} + +void vp_stop_capture(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + + writel_iowmb(0x00000000, VCAP_VP_CTRL); + flush_workqueue(dev->vcap_wq); + + if (atomic_read(&dev->vp_enabled) == 1) + disable_irq(dev->vpirq->start); + + writel_iowmb(0x00000001, VCAP_VP_SW_RESET); + writel_iowmb(0x00000000, VCAP_VP_SW_RESET); +} + +int config_vp_format(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + + INIT_WORK(&dev->vp_to_vc_work.work, mov_buf_to_vc); + dev->vp_to_vc_work.cd = c_data; + + /* SW restart VP */ + writel_iowmb(0x00000001, VCAP_VP_SW_RESET); + writel_iowmb(0x00000000, VCAP_VP_SW_RESET); + + /* Film Mode related settings */ + writel_iowmb(0x00000000, VCAP_VP_FILM_PROJECTION_T0); + writel_relaxed(0x00000000, VCAP_VP_FILM_PROJECTION_T2); + writel_relaxed(0x00000000, VCAP_VP_FILM_PAST_MAX_PROJ); + writel_relaxed(0x00000000, VCAP_VP_FILM_PAST_MIN_PROJ); + writel_relaxed(0x00000000, VCAP_VP_FILM_SEQUENCE_HIST); + writel_relaxed(0x00000000, VCAP_VP_FILM_MODE_STATE); + + writel_relaxed(0x00000000, VCAP_VP_BAL_VMOTION_STATE); + writel_relaxed(0x00000010, VCAP_VP_REDUCT_AVG_MOTION); + writel_relaxed(0x40000000, VCAP_VP_REDUCT_AVG_MOTION2); + writel_relaxed(0x40000000, VCAP_VP_NR_AVG_LUMA); + writel_relaxed(0x40000000, VCAP_VP_NR_AVG_CHROMA); + writel_relaxed(0x40000000, VCAP_VP_NR_CTRL_LUMA); + writel_relaxed(0x40000000, VCAP_VP_NR_CTRL_CHROMA); + writel_relaxed(0x00000000, VCAP_VP_BAL_AVG_BLEND); + writel_relaxed(0x00000000, VCAP_VP_VMOTION_HIST); + writel_relaxed(0x05047D19, VCAP_VP_FILM_ANALYSIS_CONFIG); + writel_relaxed(0x20260200, VCAP_VP_FILM_STATE_CONFIG); + writel_relaxed(0x23A60114, VCAP_VP_FVM_CONFIG); + writel_relaxed(0x03043210, VCAP_VP_FILM_ANALYSIS_CONFIG2); + writel_relaxed(0x04DB7A51, VCAP_VP_MIXED_ANALYSIS_CONFIG); + writel_relaxed(0x14224916, VCAP_VP_SPATIAL_CONFIG); + writel_relaxed(0x83270400, VCAP_VP_SPATIAL_CONFIG2); + writel_relaxed(0x0F000F92, VCAP_VP_SPATIAL_CONFIG3); + writel_relaxed(0x00000000, VCAP_VP_TEMPORAL_CONFIG); + writel_relaxed(0x00000000, VCAP_VP_PIXEL_DIFF_CONFIG); + writel_relaxed(0x0C090511, VCAP_VP_H_FREQ_CONFIG); + writel_relaxed(0x0A000000, VCAP_VP_NR_CONFIG); + writel_relaxed(0x008F4149, VCAP_VP_NR_LUMA_CONFIG); + writel_relaxed(0x008F4149, VCAP_VP_NR_CHROMA_CONFIG); + writel_relaxed(0x43C0FD0C, VCAP_VP_BAL_CONFIG); + writel_relaxed(0x00000255, VCAP_VP_BAL_MOTION_CONFIG); + writel_relaxed(0x24154252, VCAP_VP_BAL_LIGHT_COMB); + writel_relaxed(0x10024414, VCAP_VP_BAL_VMOTION_CONFIG); + writel_relaxed(0x00000002, VCAP_VP_NR_CONFIG2); + writel_relaxed((c_data->vp_out_fmt.height-1)<<16 | + (c_data->vp_out_fmt.width - 1), VCAP_VP_FRAME_SIZE); + writel_relaxed(0x00000000, VCAP_VP_SPLIT_SCRN_CTRL); + + return 0; +} + +int init_motion_buf(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + void *buf; + unsigned long motion_base_addr; + uint32_t size = ((c_data->vp_out_fmt.width + 63) >> 6) * + ((c_data->vp_out_fmt.height + 7) >> 3) * 16; + + if (c_data->vid_vp_action.bufMotion) { + pr_err("Motion buffer has already been created"); + return -ENOEXEC; + } + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + c_data->vid_vp_action.bufMotion = buf; + motion_base_addr = virt_to_phys(buf); + writel_iowmb(motion_base_addr, VCAP_VP_MOTION_EST_ADDR); + return 0; +} + +void deinit_motion_buf(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + void *buf; + + if (!c_data->vid_vp_action.bufMotion) { + dprintk(1, "Motion buffer has not been created"); + return; + } + + buf = c_data->vid_vp_action.bufMotion; + + writel_iowmb(0x00000000, VCAP_VP_MOTION_EST_ADDR); + c_data->vid_vp_action.bufMotion = NULL; + kfree(buf); + return; +} + +int init_nr_buf(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + struct nr_buffer *buf; + uint32_t frame_size, tot_size, rc; + + if (c_data->vid_vp_action.bufNR.vaddr) { + pr_err("NR buffer has already been created"); + return -ENOEXEC; + } + buf = &c_data->vid_vp_action.bufNR; + + frame_size = c_data->vp_in_fmt.width * c_data->vp_in_fmt.height; + if (c_data->vp_in_fmt.pixfmt == V4L2_PIX_FMT_NV16) + tot_size = frame_size * 2; + else + tot_size = frame_size / 2 * 3; + + buf->vaddr = kzalloc(tot_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf->paddr = virt_to_phys(buf->vaddr); + rc = readl_relaxed(VCAP_VP_NR_CONFIG2); + rc |= 0x02D00001; + writel_relaxed(rc, VCAP_VP_NR_CONFIG2); + writel_relaxed(buf->paddr, VCAP_VP_NR_T2_Y_BASE_ADDR); + writel_relaxed(buf->paddr + frame_size, VCAP_VP_NR_T2_C_BASE_ADDR); + buf->nr_pos = NRT2_BUF; + return 0; +} + +void deinit_nr_buf(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev = c_data->dev; + struct nr_buffer *buf; + uint32_t rc; + + if (!c_data->vid_vp_action.bufNR.vaddr) { + pr_err("NR buffer has not been created"); + return; + } + + buf = &c_data->vid_vp_action.bufNR; + + rc = readl_relaxed(VCAP_VP_NR_CONFIG2); + rc &= !(0x02D00001); + writel_relaxed(rc, VCAP_VP_NR_CONFIG2); + + kfree(buf->vaddr); + buf->paddr = 0; + buf->vaddr = NULL; + return; +} + +int kickoff_vp(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev; + struct vp_action *vp_act; + unsigned long flags = 0; + unsigned int chroma_fmt = 0; + int size; +#ifndef TOP_FIELD_FIX + bool top_field; +#endif + + if (!c_data->streaming) + return -ENOEXEC; + + dev = c_data->dev; + dprintk(2, "Start Kickoff\n"); + + if (dev->vp_client == NULL) { + pr_err("No active vp client\n"); + return -ENODEV; + } + vp_act = &dev->vp_client->vid_vp_action; + + spin_lock_irqsave(&dev->vp_client->cap_slock, flags); + if (list_empty(&vp_act->in_active)) { + spin_unlock_irqrestore(&dev->vp_client->cap_slock, flags); + pr_err("%s: VP We have no more input buffers\n", + __func__); + return -EAGAIN; + } + + vp_act->bufT1 = list_entry(vp_act->in_active.next, + struct vcap_buffer, list); + list_del(&vp_act->bufT1->list); + + if (list_empty(&vp_act->in_active)) { + spin_unlock_irqrestore(&dev->vp_client->cap_slock, flags); + list_add(&vp_act->bufT1->list, &vp_act->in_active); + pr_err("%s: VP We have no more input buffers\n", + __func__); + return -EAGAIN; + } + + vp_act->bufT2 = list_entry(vp_act->in_active.next, + struct vcap_buffer, list); + list_del(&vp_act->bufT2->list); + + if (list_empty(&vp_act->out_active)) { + spin_unlock_irqrestore(&dev->vp_client->cap_slock, flags); + list_add(&vp_act->bufT2->list, &vp_act->in_active); + list_add(&vp_act->bufT1->list, &vp_act->in_active); + pr_err("%s: VP We have no more output buffers\n", + __func__); + return -EAGAIN; + } + + vp_act->bufOut = list_entry(vp_act->out_active.next, + struct vcap_buffer, list); + list_del(&vp_act->bufOut->list); + spin_unlock_irqrestore(&dev->vp_client->cap_slock, flags); + + size = c_data->vp_in_fmt.height * c_data->vp_in_fmt.width; + writel_relaxed(vp_act->bufT1->paddr, VCAP_VP_T1_Y_BASE_ADDR); + writel_relaxed(vp_act->bufT1->paddr + size, VCAP_VP_T1_C_BASE_ADDR); + + config_in_buffer(c_data, vp_act->bufT2); + config_out_buffer(c_data, vp_act->bufOut); + + /* Config VP */ + if (c_data->vp_in_fmt.pixfmt == V4L2_PIX_FMT_NV16) + chroma_fmt = 1; + writel_relaxed((c_data->vp_in_fmt.width / 16) << 20 | + chroma_fmt << 11 | 0x2 << 4, VCAP_VP_IN_CONFIG); + + chroma_fmt = 0; + if (c_data->vp_in_fmt.pixfmt == V4L2_PIX_FMT_NV16) + chroma_fmt = 1; + + writel_relaxed((c_data->vp_in_fmt.width / 16) << 20 | + chroma_fmt << 11 | 0x1 << 4, VCAP_VP_OUT_CONFIG); + + /* Enable Interrupt */ +#ifdef TOP_FIELD_FIX + vp_act->top_field = 1; +#else + if (vp_act->bufT2->vb.v4l2_buf.field == V4L2_FIELD_TOP) + top_field = 1; +#endif + vp_act->vp_state = VP_FRAME2; + writel_relaxed(0x01100101, VCAP_VP_INTERRUPT_ENABLE); +#ifdef TOP_FIELD_FIX + writel_iowmb(0x00000000 | vp_act->top_field << 0, VCAP_VP_CTRL); + writel_iowmb(0x00030000 | vp_act->top_field << 0, VCAP_VP_CTRL); +#else + writel_iowmb(0x00000000 | top_field, VCAP_VP_CTRL); + writel_iowmb(0x00030000 | top_field, VCAP_VP_CTRL); +#endif + atomic_set(&c_data->dev->vp_enabled, 1); + enable_irq(dev->vpirq->start); + return 0; +} + +int continue_vp(struct vcap_client_data *c_data) +{ + struct vcap_dev *dev; + struct vp_action *vp_act; + int rc; +#ifndef TOP_FIELD_FIX + bool top_field; +#endif + + dprintk(2, "Start Continue\n"); + dev = c_data->dev; + + if (dev->vp_client == NULL) { + pr_err("No active vp client\n"); + return -ENODEV; + } + vp_act = &dev->vp_client->vid_vp_action; + + if (vp_act->vp_state == VP_UNKNOWN) { + pr_err("%s: VP is in an unknown state\n", + __func__); + return -EAGAIN; + } + + rc = vp_setup_buffers(c_data); + if (rc < 0) + return rc; + +#ifndef TOP_FIELD_FIX + if (vp_act->bufT2->vb.v4l2_buf.field == V4L2_FIELD_TOP) + top_field = 1; +#endif + + /* Config VP & Enable Interrupt */ + writel_relaxed(0x01100101, VCAP_VP_INTERRUPT_ENABLE); +#ifdef TOP_FIELD_FIX + writel_iowmb(0x00000000 | vp_act->top_field << 0, VCAP_VP_CTRL); + writel_iowmb(0x00030000 | vp_act->top_field << 0, VCAP_VP_CTRL); +#else + writel_iowmb(0x00000000 | top_field, VCAP_VP_CTRL); + writel_iowmb(0x00030000 | top_field, VCAP_VP_CTRL); +#endif + + atomic_set(&c_data->dev->vp_enabled, 1); + enable_irq(dev->vpirq->start); + return 0; +} diff --git a/drivers/media/video/vcap_vp.h b/drivers/media/video/vcap_vp.h new file mode 100644 index 0000000000000000000000000000000000000000..47ad8d4d8285b53e88fceefcc871205df012d04b --- /dev/null +++ b/drivers/media/video/vcap_vp.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef VCAP_VP_H +#define VCAP_VP_H + +#include + +#include + +#define VCAP_BASE (dev->vcapbase) +#define VCAP_OFFSET(off) (VCAP_BASE + off) + +#define VCAP_VP_INT_STATUS (VCAP_BASE + 0x404) +#define VCAP_VP_INT_CLEAR (VCAP_BASE + 0x40C) + +#define VCAP_VP_SW_RESET (VCAP_BASE + 0x410) +#define VCAP_VP_INTERRUPT_ENABLE (VCAP_BASE + 0x408) + +#define VCAP_VP_FILM_PROJECTION_T0 (VCAP_BASE + 0x50C) +#define VCAP_VP_FILM_PROJECTION_T2 (VCAP_BASE + 0x508) +#define VCAP_VP_FILM_PAST_MAX_PROJ (VCAP_BASE + 0x510) +#define VCAP_VP_FILM_PAST_MIN_PROJ (VCAP_BASE + 0x514) +#define VCAP_VP_FILM_SEQUENCE_HIST (VCAP_BASE + 0x504) +#define VCAP_VP_FILM_MODE_STATE (VCAP_BASE + 0x500) + +#define VCAP_VP_BAL_VMOTION_STATE (VCAP_BASE + 0x690) +#define VCAP_VP_REDUCT_AVG_MOTION (VCAP_BASE + 0x610) +#define VCAP_VP_REDUCT_AVG_MOTION2 (VCAP_BASE + 0x614) + +#define VCAP_VP_NR_AVG_LUMA (VCAP_BASE + 0x608) +#define VCAP_VP_NR_AVG_CHROMA (VCAP_BASE + 0x60C) +#define VCAP_VP_NR_CTRL_LUMA (VCAP_BASE + 0x600) +#define VCAP_VP_NR_CTRL_CHROMA (VCAP_BASE + 0x604) + +#define VCAP_VP_BAL_AVG_BLEND (VCAP_BASE + 0x694) +#define VCAP_VP_VMOTION_HIST (VCAP_BASE + 0x6F8) + +#define VCAP_VP_MOTION_EST_ADDR (VCAP_BASE + 0x4E0) +#define VCAP_VP_FILM_ANALYSIS_CONFIG (VCAP_BASE + 0x520) +#define VCAP_VP_FILM_STATE_CONFIG (VCAP_BASE + 0x524) + +#define VCAP_VP_FVM_CONFIG (VCAP_BASE + 0x550) +#define VCAP_VP_FILM_ANALYSIS_CONFIG2 (VCAP_BASE + 0x52C) +#define VCAP_VP_MIXED_ANALYSIS_CONFIG (VCAP_BASE + 0x530) + +#define VCAP_VP_SPATIAL_CONFIG (VCAP_BASE + 0x580) +#define VCAP_VP_SPATIAL_CONFIG2 (VCAP_BASE + 0x584) +#define VCAP_VP_SPATIAL_CONFIG3 (VCAP_BASE + 0x588) +#define VCAP_VP_TEMPORAL_CONFIG (VCAP_BASE + 0x5C0) + +#define VCAP_VP_PIXEL_DIFF_CONFIG (VCAP_BASE + 0x6FC) +#define VCAP_VP_H_FREQ_CONFIG (VCAP_BASE + 0x528) +#define VCAP_VP_NR_CONFIG (VCAP_BASE + 0x620) +#define VCAP_VP_NR_LUMA_CONFIG (VCAP_BASE + 0x624) +#define VCAP_VP_NR_CHROMA_CONFIG (VCAP_BASE + 0x628) +#define VCAP_VP_BAL_CONFIG (VCAP_BASE + 0x680) +#define VCAP_VP_BAL_MOTION_CONFIG (VCAP_BASE + 0x684) +#define VCAP_VP_BAL_LIGHT_COMB (VCAP_BASE + 0x688) +#define VCAP_VP_BAL_VMOTION_CONFIG (VCAP_BASE + 0x68C) + +#define VCAP_VP_NR_CONFIG2 (VCAP_BASE + 0x484) +#define VCAP_VP_FRAME_SIZE (VCAP_BASE + 0x48C) +#define VCAP_VP_SPLIT_SCRN_CTRL (VCAP_BASE + 0x750) + +#define VCAP_VP_IN_CONFIG (VCAP_BASE + 0x480) +#define VCAP_VP_OUT_CONFIG (VCAP_BASE + 0x488) + +#define VCAP_VP_T2_Y_BASE_ADDR (VCAP_BASE + 0x4C0) +#define VCAP_VP_T2_C_BASE_ADDR (VCAP_BASE + 0x4C4) +#define VCAP_VP_OUT_Y_BASE_ADDR (VCAP_BASE + 0x4CC) +#define VCAP_VP_OUT_C_BASE_ADDR (VCAP_BASE + 0x4D0) +#define VCAP_VP_OUT_CR_BASE_ADDR (VCAP_BASE + 0x4D4) + +#define VCAP_VP_CTRL (VCAP_BASE + 0x4D8) + +#define VCAP_VP_T1_Y_BASE_ADDR (VCAP_BASE + 0x4A8) +#define VCAP_VP_T1_C_BASE_ADDR (VCAP_BASE + 0x4Ac) +#define VCAP_VP_NR_T2_Y_BASE_ADDR (VCAP_BASE + 0x4B4) +#define VCAP_VP_NR_T2_C_BASE_ADDR (VCAP_BASE + 0x4B8) + +#define VP_PIC_DONE (0x1 << 0) + +irqreturn_t vp_handler(struct vcap_dev *dev); +int config_vp_format(struct vcap_client_data *c_data); +void vp_stop_capture(struct vcap_client_data *c_data); +int init_motion_buf(struct vcap_client_data *c_data); +void deinit_motion_buf(struct vcap_client_data *c_data); +int init_nr_buf(struct vcap_client_data *c_data); +void deinit_nr_buf(struct vcap_client_data *c_data); +int kickoff_vp(struct vcap_client_data *c_data); +int continue_vp(struct vcap_client_data *c_data); + +#endif diff --git a/drivers/media/video/videobuf-core.c b/drivers/media/video/videobuf-core.c index de4fa4eb8844b3b77f82ae8344633e1201e38ad3..ac48afd82c43b158158cbfa5d96a0e89aee2282e 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 0000000000000000000000000000000000000000..5646f9fbf162cefbc675c983afca44a326981829 --- /dev/null +++ b/drivers/media/video/videobuf-msm-mem.c @@ -0,0 +1,396 @@ +/* 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) +{ + if (mem->phyaddr) { + 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 (!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/media/video/videobuf2-core.c b/drivers/media/video/videobuf2-core.c index 2e8f1df775b622ef9f877be16916fd7d9d0e0c80..2ec872bd2a2ae4ab2c8e7cf79f175ac9806367a6 100644 --- a/drivers/media/video/videobuf2-core.c +++ b/drivers/media/video/videobuf2-core.c @@ -513,6 +513,13 @@ int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req) return -EINVAL; } + /* + * If the same number of buffers and memory access method is requested + * then return immediately. + */ + if (q->memory == req->memory && req->count == q->num_buffers) + return 0; + if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) { /* * We already have buffers allocated, so first check if they diff --git a/drivers/media/video/videobuf2-msm-mem.c b/drivers/media/video/videobuf2-msm-mem.c new file mode 100644 index 0000000000000000000000000000000000000000..186195d8e04725ab4c250488c0d5d262e70f9a30 --- /dev/null +++ b/drivers/media/video/videobuf2-msm-mem.c @@ -0,0 +1,351 @@ +/* Copyright (c) 2011-2012, 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 +#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...) pr_debug("videobuf-msm-mem: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +static unsigned long msm_mem_allocate(struct videobuf2_contig_pmem *mem) +{ + unsigned long phyaddr; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + int rc, len; + mem->client = msm_ion_client_create(-1, "camera"); + if (IS_ERR((void *)mem->client)) { + pr_err("%s Could not create client\n", __func__); + goto client_failed; + } + mem->ion_handle = ion_alloc(mem->client, mem->size, SZ_4K, + (0x1 << ION_CP_MM_HEAP_ID | 0x1 << ION_IOMMU_HEAP_ID)); + if (IS_ERR((void *)mem->ion_handle)) { + pr_err("%s Could not allocate\n", __func__); + goto alloc_failed; + } + rc = ion_map_iommu(mem->client, mem->ion_handle, + CAMERA_DOMAIN, GEN_POOL, SZ_4K, 0, + (unsigned long *)&phyaddr, + (unsigned long *)&len, UNCACHED, 0); + if (rc < 0) { + pr_err("%s Could not get physical address\n", __func__); + goto phys_failed; + } +#else + phyaddr = allocate_contiguous_ebi_nomap(mem->size, SZ_4K); +#endif + return phyaddr; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +phys_failed: + ion_free(mem->client, mem->ion_handle); +alloc_failed: + ion_client_destroy(mem->client); +client_failed: + return 0; +#endif +} + +static int32_t msm_mem_free(struct videobuf2_contig_pmem *mem) +{ + int32_t rc = 0; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_iommu(mem->client, mem->ion_handle, CAMERA_DOMAIN, GEN_POOL); + ion_free(mem->client, mem->ion_handle); + ion_client_destroy(mem->client); +#else + free_contiguous_memory_by_paddr(mem->phyaddr); +#endif + return rc; +} + +static void videobuf2_vm_close(struct vm_area_struct *vma) +{ + struct videobuf2_contig_pmem *mem = vma->vm_private_data; + D("vm_close %p [count=%u,vma=%08lx-%08lx]\n", + mem, mem->count, vma->vm_start, vma->vm_end); + mem->count--; +} +static void videobuf2_vm_open(struct vm_area_struct *vma) +{ + struct videobuf2_contig_pmem *mem = vma->vm_private_data; + D("vm_open %p [count=%u,vma=%08lx-%08lx]\n", + mem, mem->count, vma->vm_start, vma->vm_end); + mem->count++; +} + +static const struct vm_operations_struct videobuf2_vm_ops = { + .open = videobuf2_vm_open, + .close = videobuf2_vm_close, +}; + +static void *msm_vb2_mem_ops_alloc(void *alloc_ctx, unsigned long size) +{ + struct videobuf2_contig_pmem *mem; + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + return ERR_PTR(-ENOMEM); + + mem->magic = MAGIC_PMEM; + mem->size = PAGE_ALIGN(size); + mem->alloc_ctx = alloc_ctx; + mem->is_userptr = 0; + mem->phyaddr = msm_mem_allocate(mem); + if (!mem->phyaddr) { + pr_err("%s : pmem memory allocation failed\n", __func__); + kfree(mem); + return ERR_PTR(-ENOMEM); + } + mem->mapped_phyaddr = mem->phyaddr; + return mem; +} +static void msm_vb2_mem_ops_put(void *buf_priv) +{ + struct videobuf2_contig_pmem *mem = buf_priv; + if (!mem->is_userptr) { + D("%s Freeing memory ", __func__); + msm_mem_free(mem); + } + kfree(mem); +} +int videobuf2_pmem_contig_mmap_get(struct videobuf2_contig_pmem *mem, + struct videobuf2_msm_offset *offset, + enum videobuf2_buffer_type buffer_type, + int path) +{ + if (offset) + mem->offset = *offset; + else + memset(&mem->offset, 0, sizeof(struct videobuf2_msm_offset)); + mem->buffer_type = buffer_type; + mem->path = path; + return 0; +} +EXPORT_SYMBOL_GPL(videobuf2_pmem_contig_mmap_get); + +/** + * 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. + */ +int videobuf2_pmem_contig_user_get(struct videobuf2_contig_pmem *mem, + struct videobuf2_msm_offset *offset, + enum videobuf2_buffer_type buffer_type, + uint32_t addr_offset, int path, + struct ion_client *client) +{ + unsigned long len; + int rc = 0; +#ifndef CONFIG_MSM_MULTIMEDIA_USE_ION + unsigned long kvstart; +#endif + unsigned long paddr = 0; + if (mem->phyaddr != 0) + return 0; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + mem->ion_handle = ion_import_fd(client, (int)mem->vaddr); + if (IS_ERR_OR_NULL(mem->ion_handle)) { + pr_err("%s ION import failed\n", __func__); + return PTR_ERR(mem->ion_handle); + } + rc = ion_map_iommu(client, mem->ion_handle, CAMERA_DOMAIN, GEN_POOL, + SZ_4K, 0, (unsigned long *)&mem->phyaddr, &len, UNCACHED, 0); + if (rc < 0) + ion_free(client, mem->ion_handle); +#elif CONFIG_ANDROID_PMEM + rc = get_pmem_file((int)mem->vaddr, (unsigned long *)&mem->phyaddr, + &kvstart, &len, &mem->file); + if (rc < 0) { + pr_err("%s: get_pmem_file fd %d error %d\n", + __func__, (int)mem->vaddr, rc); + return rc; + } +#else + paddr = 0; + kvstart = 0; +#endif + if (offset) + mem->offset = *offset; + else + memset(&mem->offset, 0, sizeof(struct videobuf2_msm_offset)); + mem->path = path; + mem->buffer_type = buffer_type; + paddr = mem->phyaddr; + mem->mapped_phyaddr = paddr + addr_offset; + mem->addr_offset = addr_offset; + return rc; +} +EXPORT_SYMBOL_GPL(videobuf2_pmem_contig_user_get); + +void videobuf2_pmem_contig_user_put(struct videobuf2_contig_pmem *mem, + struct ion_client *client) +{ + if (mem->is_userptr) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_iommu(client, mem->ion_handle, + CAMERA_DOMAIN, GEN_POOL); + ion_free(client, mem->ion_handle); +#elif CONFIG_ANDROID_PMEM + put_pmem_file(mem->file); +#endif + } + mem->is_userptr = 0; + mem->phyaddr = 0; + mem->size = 0; + mem->mapped_phyaddr = 0; +} +EXPORT_SYMBOL_GPL(videobuf2_pmem_contig_user_put); + +static void *msm_vb2_mem_ops_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct videobuf2_contig_pmem *mem; + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + return ERR_PTR(-ENOMEM); + mem->magic = MAGIC_PMEM; + mem->is_userptr = 1; + mem->vaddr = (void *)vaddr; + mem->size = size; + mem->alloc_ctx = alloc_ctx; + return mem; +} +static void msm_vb2_mem_ops_put_userptr(void *buf_priv) +{ + kfree(buf_priv); +} + +static void *msm_vb2_mem_ops_vaddr(void *buf_priv) +{ + struct videobuf2_contig_pmem *mem = buf_priv; + return mem->vaddr; +} +static void *msm_vb2_mem_ops_cookie(void *buf_priv) +{ + return buf_priv; +} +static unsigned int msm_vb2_mem_ops_num_users(void *buf_priv) +{ + struct videobuf2_contig_pmem *mem = buf_priv; + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + return mem->count; +} +static int msm_vb2_mem_ops_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct videobuf2_contig_pmem *mem; + int retval; + unsigned long size; + D("%s\n", __func__); + mem = buf_priv; + D("mem = 0x%x\n", (u32)mem); + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + /* 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); + goto error; + } + mem->vaddr = (void *)vma->vm_start; + vma->vm_ops = &videobuf2_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = mem; + + D("mmap %p: %08lx-%08lx (%lx) pgoff %08lx\n", + map, vma->vm_start, vma->vm_end, + (long int)mem->bsize, vma->vm_pgoff); + videobuf2_vm_open(vma); + return 0; +error: + return -ENOMEM; +} + +static struct vb2_mem_ops msm_vb2_mem_ops = { + .alloc = msm_vb2_mem_ops_alloc, + .put = msm_vb2_mem_ops_put, + .get_userptr = msm_vb2_mem_ops_get_userptr, + .put_userptr = msm_vb2_mem_ops_put_userptr, + .vaddr = msm_vb2_mem_ops_vaddr, + .cookie = msm_vb2_mem_ops_cookie, + .num_users = msm_vb2_mem_ops_num_users, + .mmap = msm_vb2_mem_ops_mmap +}; + +void videobuf2_queue_pmem_contig_init(struct vb2_queue *q, + enum v4l2_buf_type type, + const struct vb2_ops *ops, + unsigned int size, + void *priv) +{ + memset(q, 0, sizeof(struct vb2_queue)); + q->mem_ops = &msm_vb2_mem_ops; + q->ops = ops; + q->drv_priv = priv; + q->type = type; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->io_flags = 0; + q->buf_struct_size = size; + vb2_queue_init(q); +} +EXPORT_SYMBOL_GPL(videobuf2_queue_pmem_contig_init); + +unsigned long videobuf2_to_pmem_contig(struct vb2_buffer *vb, + unsigned int plane_no) +{ + struct videobuf2_contig_pmem *mem; + mem = vb2_plane_cookie(vb, plane_no); + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + return mem->mapped_phyaddr; +} +EXPORT_SYMBOL_GPL(videobuf2_to_pmem_contig); + +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 11e44386fa9bba2287c3fda66b422b3f88c57963..7c61bf32a974699deebe63b7b17203ee71b5917e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -131,6 +131,38 @@ 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 MSM_SSBI + select MFD_CORE + select MFD_PM8XXX + help + Say yes here for Qualcomm PM8058 chip. + +config PMIC8901 + tristate "PMIC8901 Power Management chip" + depends on MSM_SSBI + select MFD_CORE + select MFD_PM8XXX + 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 @@ -214,6 +246,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 @@ -822,6 +881,51 @@ config MFD_PM8921_CORE Say M here if you want to include support for PM8921 chip as a module. This will build a module called "pm8921-core". +config MFD_PM8821_CORE + tristate "Qualcomm PM8821 PMIC chip" + depends on MSM_SSBI + select MFD_CORE + select MFD_PM8XXX + help + If you say yes to this option, support will be included for the + built-in PM8821 PMIC chip. + + This is required if your board has a PM8821 and uses its features, + such as: MPPs, and interrupts. + + Say M here if you want to include support for PM8821 chip as a module. + This will build a module called "pm8821-core". + +config MFD_PM8018_CORE + tristate "Qualcomm PM8018 PMIC chip" + depends on MSM_SSBI + select MFD_CORE + select MFD_PM8XXX + help + If you say yes to this option, support will be included for the + built-in PM8018 PMIC chip. + + This is required if your board has a PM8018 and uses its features, + such as: MPPs, GPIOs, regulators, interrupts, and PWM. + + Say M here if you want to include support for PM8018 chip as a module. + This will build a module called "pm8018-core". + +config MFD_PM8038_CORE + tristate "Qualcomm PM8038 PMIC chip" + depends on MSM_SSBI + select MFD_CORE + select MFD_PM8XXX + help + If you say yes to this option, support will be included for the + built-in PM8038 PMIC chip. + + This is required if your board has a PM8038 and uses its features, + such as: MPPs, GPIOs, regulators, interrupts, and PWM. + + Say M here if you want to include support for PM8038 chip as a module. + This will build a module called "pm8038-core". + config MFD_PM8XXX_IRQ bool "Support for Qualcomm PM8xxx IRQ features" depends on MFD_PM8XXX @@ -866,6 +970,71 @@ config MFD_INTEL_MSIC Passage) chip. This chip embeds audio, battery, GPIO, etc. devices used in Intel Medfield platforms. +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_SPK + tristate "Support for Qualcomm PM8xxx speaker APIs" + depends on MFD_PM8XXX + help + This driver implements several external speaker amplifier 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 WCD9304_CODEC + tristate "WCD9304 Codec" + select SLIMBUS + select MFD_CORE + default n + help + Enables the WCD9304 core driver. The core driver provides + read/write capability to registers which are part of the + WCD9304 core and gives the ability to use the WCD9304 codec. + +config WCD9310_CODEC + tristate "WCD9310 Codec" + select 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. + config MFD_RC5T583 bool "Ricoh RC5T583 Power Management system device" depends on I2C=y && GENERIC_HARDIRQS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 05fa538c5efe942ebd5146e57689fc607a34955c..e81e4b1795ada4d757355ab2e09122f3aefcdd06 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -44,12 +44,26 @@ tps65912-objs := tps65912-core.o tps65912-irq.o obj-$(CONFIG_MFD_TPS65912) += tps65912.o obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.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 @@ -64,6 +78,9 @@ 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) += wcd9xxx-core.o wcd9xxx-irq.o wcd9xxx-slimslave.o +obj-$(CONFIG_WCD9304_CODEC) += wcd9xxx-core.o wcd9xxx-irq.o wcd9xxx-slimslave.o + ifeq ($(CONFIG_SA1100_ASSABET),y) obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o endif @@ -107,12 +124,22 @@ obj-$(CONFIG_MFD_VX855) += vx855.o obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o -obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o -obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_MFD_TPS65090) += tps65090.o obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o obj-$(CONFIG_MFD_S5M_CORE) += s5m-core.o s5m-irq.o +obj-$(CONFIG_PMIC8058) += pmic8058.o +obj-$(CONFIG_PMIC8901) += pmic8901.o +obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o +obj-$(CONFIG_MFD_PM8821_CORE) += pm8821-core.o +obj-$(CONFIG_MFD_PM8018_CORE) += pm8018-core.o +obj-$(CONFIG_MFD_PM8038_CORE) += pm8038-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_SPK) += pm8xxx-spk.o +obj-$(CONFIG_MFD_PM8XXX_BATT_ALARM) += pm8xxx-batt-alarm.o obj-$(CONFIG_MFD_ANATOP) += anatop-mfd.o diff --git a/drivers/mfd/marimba-codec.c b/drivers/mfd/marimba-codec.c new file mode 100644 index 0000000000000000000000000000000000000000..6416e0a1ed6df78a334da01929e48c0d09f36b1a --- /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 0000000000000000000000000000000000000000..70ec2ec285f542abd126e045a8d68e317fcd81d9 --- /dev/null +++ b/drivers/mfd/marimba-core.c @@ -0,0 +1,937 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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]; +static int mutex_initialized; +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 + +#if defined(CONFIG_DEBUG_FS) +struct adie_dbg_device { + struct mutex dbg_mutex; + struct dentry *dent; + int addr; + int mod_id; +}; + +static struct adie_dbg_device *marimba_dbg_device; +static struct adie_dbg_device *timpani_dbg_device; +static struct adie_dbg_device *bahama_dbg_device; +#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]; + if (marimba == NULL) { + pr_err("%s: Unable to access Marimba core\n", __func__); + return -ENODEV; + } + + + 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]; + if (marimba->client == NULL) { + pr_err("%s: Unable to access the Marimba slave device.\n", + __func__); + return -ENODEV; + } + + 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"); + ret = -ENODEV; + } + + 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"); + ret = -ENODEV; + } + + 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); + +#if defined(CONFIG_DEBUG_FS) + +static int check_addr(int addr, const char *func_name) +{ + if (addr < 0 || addr > 0xFF) { + pr_err("%s: Marimba register address is invalid: %d\n", + func_name, addr); + return -EINVAL; + } + return 0; +} + +static int marimba_debugfs_set(void *data, u64 val) +{ + struct adie_dbg_device *dbgdev = data; + u8 reg = val; + int rc; + struct marimba marimba_id; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) + goto done; + + marimba_id.mod_id = dbgdev->mod_id; + rc = marimba_write(&marimba_id, dbgdev->addr, ®, 1); + rc = (rc == 1) ? 0 : rc; + + if (rc) + pr_err("%s: FAIL marimba_write(0x%03X)=0x%02X: rc=%d\n", + __func__, dbgdev->addr, reg, rc); +done: + mutex_unlock(&dbgdev->dbg_mutex); + return rc; +} + +static int marimba_debugfs_get(void *data, u64 *val) +{ + struct adie_dbg_device *dbgdev = data; + int rc; + u8 reg; + struct marimba marimba_id; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) + goto done; + + marimba_id.mod_id = dbgdev->mod_id; + rc = marimba_read(&marimba_id, dbgdev->addr, ®, 1); + rc = (rc == 2) ? 0 : rc; + + if (rc) { + pr_err("%s: FAIL marimba_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_marimba_fops, marimba_debugfs_get, + marimba_debugfs_set, "0x%02llX\n"); + +static int addr_set(void *data, u64 val) +{ + struct adie_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 adie_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 marimba_dbg_init(int adie_type) +{ + struct adie_dbg_device *dbgdev; + struct dentry *dent; + struct dentry *temp; + + dbgdev = kzalloc(sizeof *dbgdev, GFP_KERNEL); + if (dbgdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&dbgdev->dbg_mutex); + dbgdev->addr = -1; + + if (adie_type == MARIMBA_ID) { + marimba_dbg_device = dbgdev; + marimba_dbg_device->mod_id = MARIMBA_SLAVE_ID_MARIMBA; + dent = debugfs_create_dir("marimba-dbg", NULL); + } else if (adie_type == TIMPANI_ID) { + timpani_dbg_device = dbgdev; + timpani_dbg_device->mod_id = MARIMBA_SLAVE_ID_MARIMBA; + dent = debugfs_create_dir("timpani-dbg", NULL); + } else if (adie_type == BAHAMA_ID) { + bahama_dbg_device = dbgdev; + bahama_dbg_device->mod_id = SLAVE_ID_BAHAMA; + dent = debugfs_create_dir("bahama-dbg", NULL); + } + if (dent == NULL || IS_ERR(dent)) { + pr_err("%s: ERR debugfs_create_dir: dent=0x%X\n", + __func__, (unsigned)dent); + kfree(dbgdev); + 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_marimba_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; + + return 0; + +debug_error: + kfree(dbgdev); + debugfs_remove_recursive(dent); + return -ENOMEM; +} + +static int __devexit marimba_dbg_remove(void) +{ + if (marimba_dbg_device) { + debugfs_remove_recursive(marimba_dbg_device->dent); + kfree(marimba_dbg_device); + } + if (timpani_dbg_device) { + debugfs_remove_recursive(timpani_dbg_device->dent); + kfree(timpani_dbg_device); + } + if (bahama_dbg_device) { + debugfs_remove_recursive(bahama_dbg_device->dent); + kfree(bahama_dbg_device); + } + return 0; +} + +#else + +static int __devinit marimba_dbg_init(int adie_type) +{ + return 0; +} + +static int __devexit marimba_dbg_remove(void) +{ + return 0; +} + +#endif + +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 __devinit 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"); + status = -EINVAL; + goto fail; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + dev_dbg(&client->dev, "can't talk I2C?\n"); + status = -EIO; + goto fail; + } + if (!mutex_initialized) { + for (i = 0; i < ADIE_ARRY_SIZE; ++i) { + marimba = &marimba_modules[i]; + mutex_init(&marimba->xfer_lock); + } + mutex_initialized = 1; + } + /* 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) { + status = -EAGAIN; + goto fail; + } + marimba = &marimba_modules[ADIE_ARRY_SIZE - 1]; + marimba->client = client; + + rc = get_adie_type(); + + if (rc < 0) { + if (pdata->bahama_setup != NULL) + pdata->bahama_shutdown(cur_adie_type); + if (pdata->marimba_shutdown != NULL) + pdata->marimba_shutdown(); + status = -ENODEV; + goto fail; + } + + 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; + + 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)); + + } + + if (marimba_dbg_init(rc) != 0) + pr_debug("%s: marimba debugfs init failed\n", __func__); + + 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 (mutex_initialized) + mutex_destroy(&marimba->xfer_lock); + + } + marimba_dbg_remove(); + mutex_initialized = 0; + 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 0000000000000000000000000000000000000000..8a7b781373f31263979279346cd4b74259d15de9 --- /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 0000000000000000000000000000000000000000..d9414edfe2158ddc977e2f1e2edbf03fb851e8eb --- /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/pm8018-core.c b/drivers/mfd/pm8018-core.c new file mode 100644 index 0000000000000000000000000000000000000000..b1b64cb9d1a0edc2924ec1c4af40784ef1c7f8d3 --- /dev/null +++ b/drivers/mfd/pm8018-core.c @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + + +/* PMIC PM8018 SSBI Addresses */ +#define REG_HWREV 0x002 /* PMIC4 revision */ +#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ + +#define REG_MPP_BASE 0x050 +#define REG_IRQ_BASE 0x1BB + +#define REG_RTC_BASE 0x11D + +#define REG_TEMP_ALARM_CTRL 0x01B +#define REG_TEMP_ALARM_PWM 0x09B + + +#define PM8018_VERSION_MASK 0xFFF0 +#define PM8018_VERSION_VALUE 0x08F0 +#define PM8018_REVISION_MASK 0x000F + +#define REG_PM8018_PON_CNTRL_3 0x01D +#define PM8018_RESTART_REASON_MASK 0x07 + +#define SINGLE_IRQ_RESOURCE(_name, _irq) \ +{ \ + .name = _name, \ + .start = _irq, \ + .end = _irq, \ + .flags = IORESOURCE_IRQ, \ +} + +struct pm8018 { + struct device *dev; + struct pm_irq_chip *irq_chip; + struct mfd_cell *mfd_regulators; + struct pm8xxx_regulator_core_platform_data *regulator_cdata; + u32 rev_registers; +}; + +static int pm8018_readb(const struct device *dev, u16 addr, u8 *val) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, val, 1); +} + +static int pm8018_writeb(const struct device *dev, u16 addr, u8 val) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); +} + +static int pm8018_read_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8018_write_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8018_read_irq_stat(const struct device *dev, int irq) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return pm8xxx_get_irq_stat(pmic->irq_chip, irq); +} + +static enum pm8xxx_version pm8018_get_version(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + enum pm8xxx_version version = -ENODEV; + + if ((pmic->rev_registers & PM8018_VERSION_MASK) == PM8018_VERSION_VALUE) + version = PM8XXX_VERSION_8018; + + return version; +} + +static int pm8018_get_revision(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return pmic->rev_registers & PM8018_REVISION_MASK; +} + +static struct pm8xxx_drvdata pm8018_drvdata = { + .pmic_readb = pm8018_readb, + .pmic_writeb = pm8018_writeb, + .pmic_read_buf = pm8018_read_buf, + .pmic_write_buf = pm8018_write_buf, + .pmic_read_irq_stat = pm8018_read_irq_stat, + .pmic_get_version = pm8018_get_version, + .pmic_get_revision = pm8018_get_revision, +}; + +static const struct resource gpio_cell_resources[] __devinitconst = { + [0] = { + .start = PM8018_IRQ_BLOCK_BIT(PM8018_GPIO_BLOCK_START, 0), + .end = PM8018_IRQ_BLOCK_BIT(PM8018_GPIO_BLOCK_START, 0) + + PM8018_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, PM8018_ADC_EOC_USR_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8018_ADC_BATT_TEMP_WARM_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8018_ADC_BATT_TEMP_COLD_IRQ), +}; + +static struct mfd_cell adc_cell __devinitdata = { + .name = PM8XXX_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 = PM8018_IRQ_BLOCK_BIT(PM8018_MPP_BLOCK_START, 0), + .end = PM8018_IRQ_BLOCK_BIT(PM8018_MPP_BLOCK_START, 0) + + PM8018_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, PM8018_RTC_ALARM_IRQ), + [1] = { + .name = "pmic_rtc_base", + .start = REG_RTC_BASE, + .end = REG_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, PM8018_PWRKEY_REL_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8018_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 struct mfd_cell misc_cell __devinitdata = { + .name = PM8XXX_MISC_DEV_NAME, + .id = -1, +}; + +static struct mfd_cell debugfs_cell __devinitdata = { + .name = "pm8xxx-debug", + .id = -1, + .platform_data = "pm8018-dbg", + .pdata_size = sizeof("pm8018-dbg"), +}; + +static struct mfd_cell pwm_cell __devinitdata = { + .name = PM8XXX_PWM_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("pm8018_tempstat_irq", PM8018_TEMPSTAT_IRQ), + SINGLE_IRQ_RESOURCE("pm8018_overtemp_irq", PM8018_OVERTEMP_IRQ), +}; + +static struct pm8xxx_tm_core_data thermal_alarm_cdata = { + .adc_channel = CHANNEL_DIE_TEMP, + .adc_type = PM8XXX_TM_ADC_PM8XXX_ADC, + .reg_addr_temp_alarm_ctrl = REG_TEMP_ALARM_CTRL, + .reg_addr_temp_alarm_pwm = REG_TEMP_ALARM_PWM, + .tm_name = "pm8018_tz", + .irq_name_temp_stat = "pm8018_tempstat_irq", + .irq_name_over_temp = "pm8018_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 struct pm8xxx_vreg regulator_data[] = { + /* name pc_name ctrl test hpm_min */ + PLDO("8018_l2", "8018_l2_pc", 0x0B0, 0x0B1, LDO_50), + PLDO("8018_l3", "8018_l3_pc", 0x0B2, 0x0B3, LDO_50), + PLDO("8018_l4", "8018_l4_pc", 0x0B4, 0x0B5, LDO_300), + PLDO("8018_l5", "8018_l5_pc", 0x0B6, 0x0B7, LDO_150), + PLDO("8018_l6", "8018_l6_pc", 0x0B8, 0x0B9, LDO_150), + PLDO("8018_l7", "8018_l7_pc", 0x0BA, 0x0BB, LDO_300), + NLDO("8018_l8", "8018_l8_pc", 0x0BC, 0x0BD, LDO_150), + NLDO1200("8018_l9", 0x0BE, 0x0BF, LDO_1200), + NLDO1200("8018_l10", 0x0C0, 0x0C1, LDO_1200), + NLDO1200("8018_l11", 0x0C2, 0x0C3, LDO_1200), + NLDO1200("8018_l12", 0x0C4, 0x0C5, LDO_1200), + PLDO("8018_l13", "8018_l13_pc", 0x0C8, 0x0C9, LDO_50), + PLDO("8018_l14", "8018_l14_pc", 0x0CA, 0x0CB, LDO_50), + + /* name pc_name ctrl test2 clk sleep hpm_min */ + SMPS("8018_s1", "8018_s1_pc", 0x1D0, 0x1D5, 0x009, 0x1D2, SMPS_1500), + SMPS("8018_s2", "8018_s2_pc", 0x1D8, 0x1DD, 0x00A, 0x1DA, SMPS_1500), + SMPS("8018_s3", "8018_s3_pc", 0x1E0, 0x1E5, 0x00B, 0x1E2, SMPS_1500), + SMPS("8018_s4", "8018_s4_pc", 0x1E8, 0x1ED, 0x00C, 0x1EA, SMPS_1500), + SMPS("8018_s5", "8018_s5_pc", 0x1F0, 0x1F5, 0x00D, 0x1F2, SMPS_1500), + + /* name pc_name ctrl test */ + VS("8018_lvs1", "8018_lvs1_pc", 0x060, 0x061), +}; + +#define MAX_NAME_COMPARISON_LEN 32 + +static int __devinit match_regulator( + struct pm8xxx_regulator_core_platform_data *core_data, const char *name) +{ + int found = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) { + if (regulator_data[i].rdesc.name + && strncmp(regulator_data[i].rdesc.name, name, + MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = false; + core_data->vreg = ®ulator_data[i]; + found = 1; + break; + } else if (regulator_data[i].rdesc_pc.name + && strncmp(regulator_data[i].rdesc_pc.name, name, + MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = true; + core_data->vreg = ®ulator_data[i]; + found = 1; + break; + } + } + + if (!found) + pr_err("could not find a match for regulator: %s\n", name); + + return found; +} + +static int __devinit +pm8018_add_regulators(const struct pm8018_platform_data *pdata, + struct pm8018 *pmic, int irq_base) +{ + int ret = 0; + struct mfd_cell *mfd_regulators; + struct pm8xxx_regulator_core_platform_data *cdata; + int i; + + /* Add one device for each regulator used by the board. */ + mfd_regulators = kzalloc(sizeof(struct mfd_cell) + * (pdata->num_regulators), GFP_KERNEL); + if (!mfd_regulators) { + pr_err("Cannot allocate %d bytes for pm8018 regulator " + "mfd cells\n", sizeof(struct mfd_cell) + * (pdata->num_regulators)); + return -ENOMEM; + } + cdata = kzalloc(sizeof(struct pm8xxx_regulator_core_platform_data) + * pdata->num_regulators, GFP_KERNEL); + if (!cdata) { + pr_err("Cannot allocate %d bytes for pm8018 regulator " + "core data\n", pdata->num_regulators + * sizeof(struct pm8xxx_regulator_core_platform_data)); + kfree(mfd_regulators); + return -ENOMEM; + } + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_init(®ulator_data[i].pc_lock); + + for (i = 0; i < pdata->num_regulators; i++) { + if (!pdata->regulator_pdatas[i].init_data.constraints.name) { + pr_err("name missing for regulator %d\n", i); + ret = -EINVAL; + goto bail; + } + if (!match_regulator(&cdata[i], + pdata->regulator_pdatas[i].init_data.constraints.name)) { + ret = -ENODEV; + goto bail; + } + cdata[i].pdata = &(pdata->regulator_pdatas[i]); + mfd_regulators[i].name = PM8XXX_REGULATOR_DEV_NAME; + mfd_regulators[i].id = cdata[i].pdata->id; + mfd_regulators[i].platform_data = &cdata[i]; + mfd_regulators[i].pdata_size = + sizeof(struct pm8xxx_regulator_core_platform_data); + } + ret = mfd_add_devices(pmic->dev, 0, mfd_regulators, + pdata->num_regulators, NULL, irq_base); + if (ret) + goto bail; + + pmic->mfd_regulators = mfd_regulators; + pmic->regulator_cdata = cdata; + return ret; + +bail: + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_destroy(®ulator_data[i].pc_lock); + kfree(mfd_regulators); + kfree(cdata); + return ret; +} + +static int __devinit +pm8018_add_subdevices(const struct pm8018_platform_data *pdata, + struct pm8018 *pmic) +{ + int ret = 0, irq_base = 0; + struct pm_irq_chip *irq_chip; + + if (pdata->irq_pdata) { + pdata->irq_pdata->irq_cdata.nirqs = PM8018_NR_IRQS; + pdata->irq_pdata->irq_cdata.base_addr = REG_IRQ_BASE; + irq_base = pdata->irq_pdata->irq_base; + irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); + + if (IS_ERR(irq_chip)) { + pr_err("Failed to init interrupts ret=%ld\n", + PTR_ERR(irq_chip)); + return PTR_ERR(irq_chip); + } + pmic->irq_chip = irq_chip; + } + + if (pdata->gpio_pdata) { + pdata->gpio_pdata->gpio_cdata.ngpios = PM8018_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 = PM8018_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->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->adc_pdata) { + adc_cell.platform_data = pdata->adc_pdata; + adc_cell.pdata_size = sizeof(struct pm8xxx_adc_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &adc_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add adc subdevice ret=%d\n", ret); + } + } + + if (pdata->leds_pdata) { + leds_cell.platform_data = pdata->leds_pdata; + leds_cell.pdata_size = sizeof(struct pm8xxx_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, &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->num_regulators > 0 && pdata->regulator_pdatas) { + ret = pm8018_add_regulators(pdata, pmic, irq_base); + if (ret) { + pr_err("Failed to add regulator subdevices 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; + } + + return 0; +bail: + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + return ret; +} + +static const char * const pm8018_restart_reason[] = { + [0] = "Unknown", + [1] = "Triggered from CBL (external charger)", + [2] = "Triggered from KPD (power key press)", + [3] = "Triggered from CHG (usb charger insertion)", + [4] = "Triggered from SMPL (sudden momentary power loss)", + [5] = "Triggered from RTC (real time clock)", + [6] = "Triggered by Hard Reset", + [7] = "Triggered by General Purpose Trigger", +}; + +static const char * const pm8018_rev_names[] = { + [PM8XXX_REVISION_8018_TEST] = "test", + [PM8XXX_REVISION_8018_1p0] = "1.0", + [PM8XXX_REVISION_8018_2p0] = "2.0", + [PM8XXX_REVISION_8018_2p1] = "2.1", +}; + +static int __devinit pm8018_probe(struct platform_device *pdev) +{ + const struct pm8018_platform_data *pdata = pdev->dev.platform_data; + const char *revision_name = "unknown"; + struct pm8018 *pmic; + enum pm8xxx_version version; + int revision; + int rc; + u8 val; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pmic = kzalloc(sizeof(struct pm8018), GFP_KERNEL); + if (!pmic) { + pr_err("Cannot alloc pm8018 struct\n"); + return -ENOMEM; + } + + /* Read PMIC chip revision */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev 1 reg %d:rc=%d\n", REG_HWREV, rc); + goto err_read_rev; + } + pr_info("PMIC revision 1: %02X\n", val); + pmic->rev_registers = val; + + /* Read PMIC chip revision 2 */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", REG_HWREV_2, + rc); + goto err_read_rev; + } + pr_info("PMIC revision 2: %02X\n", val); + pmic->rev_registers |= val << BITS_PER_BYTE; + + pmic->dev = &pdev->dev; + pm8018_drvdata.pm_chip_data = pmic; + platform_set_drvdata(pdev, &pm8018_drvdata); + + /* Print out human readable version and revision names. */ + version = pm8xxx_get_version(pmic->dev); + if (version == PM8XXX_VERSION_8018) { + revision = pm8xxx_get_revision(pmic->dev); + if (revision >= 0 && revision < ARRAY_SIZE(pm8018_rev_names)) + revision_name = pm8018_rev_names[revision]; + pr_info("PMIC version: PM8018 rev %s\n", revision_name); + } else { + WARN_ON(version != PM8XXX_VERSION_8018); + } + /* Log human readable restart reason */ + rc = msm_ssbi_read(pdev->dev.parent, REG_PM8018_PON_CNTRL_3, &val, 1); + if (rc) { + pr_err("Cannot read restart reason rc=%d\n", rc); + goto err_read_rev; + } + val &= PM8018_RESTART_REASON_MASK; + pr_info("PMIC Restart Reason: %s\n", pm8018_restart_reason[val]); + + rc = pm8018_add_subdevices(pdata, pmic); + if (rc) { + pr_err("Cannot add subdevices rc=%d\n", rc); + goto err; + } + + /* gpio might not work if no irq device is found */ + WARN_ON(pmic->irq_chip == NULL); + + return 0; + +err: + mfd_remove_devices(pmic->dev); + platform_set_drvdata(pdev, NULL); + kfree(pmic->mfd_regulators); + kfree(pmic->regulator_cdata); +err_read_rev: + kfree(pmic); + return rc; +} + +static int __devexit pm8018_remove(struct platform_device *pdev) +{ + struct pm8xxx_drvdata *drvdata; + struct pm8018 *pmic = NULL; + int i; + + drvdata = platform_get_drvdata(pdev); + if (drvdata) + pmic = drvdata->pm_chip_data; + if (pmic) { + if (pmic->dev) + mfd_remove_devices(pmic->dev); + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + if (pmic->mfd_regulators) { + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_destroy(®ulator_data[i].pc_lock); + } + kfree(pmic->mfd_regulators); + kfree(pmic->regulator_cdata); + kfree(pmic); + } + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver pm8018_driver = { + .probe = pm8018_probe, + .remove = __devexit_p(pm8018_remove), + .driver = { + .name = PM8018_CORE_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8018_init(void) +{ + return platform_driver_register(&pm8018_driver); +} +postcore_initcall(pm8018_init); + +static void __exit pm8018_exit(void) +{ + platform_driver_unregister(&pm8018_driver); +} +module_exit(pm8018_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8018 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8018_CORE_DEV_NAME); diff --git a/drivers/mfd/pm8038-core.c b/drivers/mfd/pm8038-core.c new file mode 100644 index 0000000000000000000000000000000000000000..4271a2a9b958932a4b169984af4aa3db9148f9ef --- /dev/null +++ b/drivers/mfd/pm8038-core.c @@ -0,0 +1,775 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#define REG_HWREV 0x002 /* PMIC4 revision */ +#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ + +#define REG_MPP_BASE 0x050 +#define REG_RTC_BASE 0x11D +#define REG_IRQ_BASE 0x1BB + +#define REG_SPK_BASE 0x253 +#define REG_SPK_REGISTERS 3 + +#define PM8038_VERSION_MASK 0xFFF0 +#define PM8038_VERSION_VALUE 0x09F0 +#define PM8038_REVISION_MASK 0x000F + +#define REG_PM8038_PON_CNTRL_3 0x01D +#define PM8038_RESTART_REASON_MASK 0x07 + +#define SINGLE_IRQ_RESOURCE(_name, _irq) \ +{ \ + .name = _name, \ + .start = _irq, \ + .end = _irq, \ + .flags = IORESOURCE_IRQ, \ +} + +struct pm8038 { + struct device *dev; + struct pm_irq_chip *irq_chip; + struct mfd_cell *mfd_regulators; + struct pm8xxx_regulator_core_platform_data *regulator_cdata; + u32 rev_registers; +}; + +static int pm8038_readb(const struct device *dev, u16 addr, u8 *val) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, val, 1); +} + +static int pm8038_writeb(const struct device *dev, u16 addr, u8 val) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); +} + +static int pm8038_read_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8038_write_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8038_read_irq_stat(const struct device *dev, int irq) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return pm8xxx_get_irq_stat(pmic->irq_chip, irq); +} + +static enum pm8xxx_version pm8038_get_version(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + enum pm8xxx_version version = -ENODEV; + + if ((pmic->rev_registers & PM8038_VERSION_MASK) == PM8038_VERSION_VALUE) + version = PM8XXX_VERSION_8038; + + return version; +} + +static int pm8038_get_revision(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return pmic->rev_registers & PM8038_REVISION_MASK; +} + +static struct pm8xxx_drvdata pm8038_drvdata = { + .pmic_readb = pm8038_readb, + .pmic_writeb = pm8038_writeb, + .pmic_read_buf = pm8038_read_buf, + .pmic_write_buf = pm8038_write_buf, + .pmic_read_irq_stat = pm8038_read_irq_stat, + .pmic_get_version = pm8038_get_version, + .pmic_get_revision = pm8038_get_revision, +}; + +static const struct resource gpio_cell_resources[] __devinitconst = { + [0] = { + .start = PM8038_IRQ_BLOCK_BIT(PM8038_GPIO_BLOCK_START, 0), + .end = PM8038_IRQ_BLOCK_BIT(PM8038_GPIO_BLOCK_START, 0) + + PM8038_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, PM8038_ADC_EOC_USR_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8038_ADC_BATT_TEMP_WARM_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8038_ADC_BATT_TEMP_COLD_IRQ), +}; + +static struct mfd_cell adc_cell __devinitdata = { + .name = PM8XXX_ADC_DEV_NAME, + .id = -1, + .resources = adc_cell_resources, + .num_resources = ARRAY_SIZE(adc_cell_resources), +}; + +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 const struct resource mpp_cell_resources[] __devinitconst = { + { + .start = PM8038_IRQ_BLOCK_BIT(PM8038_MPP_BLOCK_START, 0), + .end = PM8038_IRQ_BLOCK_BIT(PM8038_MPP_BLOCK_START, 0) + + PM8038_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, PM8038_RTC_ALARM_IRQ), + [1] = { + .name = "pmic_rtc_base", + .start = REG_RTC_BASE, + .end = REG_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, PM8038_PWRKEY_REL_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8038_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 struct mfd_cell pwm_cell __devinitdata = { + .name = PM8XXX_PWM_DEV_NAME, + .id = -1, +}; + +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 resources_spk[] __devinitconst = { + [0] = { + .name = PM8XXX_SPK_DEV_NAME, + .start = REG_SPK_BASE, + .end = REG_SPK_BASE + REG_SPK_REGISTERS, + .flags = IORESOURCE_IO, + }, +}; + +static struct mfd_cell spk_cell __devinitdata = { + .name = PM8XXX_SPK_DEV_NAME, + .id = -1, + .num_resources = ARRAY_SIZE(resources_spk), + .resources = resources_spk, +}; + +static struct mfd_cell debugfs_cell __devinitdata = { + .name = "pm8xxx-debug", + .id = 0, + .platform_data = "pm8038-dbg", + .pdata_size = sizeof("pm8038-dbg"), +}; + +static struct pm8xxx_vreg regulator_data[] = { + /* name pc_name ctrl test hpm_min */ + NLDO1200("8038_l1", 0x0AE, 0x0AF, LDO_1200), + NLDO("8038_l2", "8038_l2_pc", 0x0B0, 0x0B1, LDO_150), + PLDO("8038_l3", "8038_l3_pc", 0x0B2, 0x0B3, LDO_50), + PLDO("8038_l4", "8038_l4_pc", 0x0B4, 0x0B5, LDO_50), + PLDO("8038_l5", "8038_l5_pc", 0x0B6, 0x0B7, LDO_600), + PLDO("8038_l6", "8038_l6_pc", 0x0B8, 0x0B9, LDO_600), + PLDO("8038_l7", "8038_l7_pc", 0x0BA, 0x0BB, LDO_600), + PLDO("8038_l8", "8038_l8_pc", 0x0BC, 0x0BD, LDO_300), + PLDO("8038_l9", "8038_l9_pc", 0x0BE, 0x0BF, LDO_300), + PLDO("8038_l10", "8038_l10_pc", 0x0C0, 0x0C1, LDO_600), + PLDO("8038_l11", "8038_l11_pc", 0x0C2, 0x0C3, LDO_600), + NLDO("8038_l12", "8038_l12_pc", 0x0C4, 0x0C5, LDO_300), + PLDO("8038_l14", "8038_l14_pc", 0x0C8, 0x0C9, LDO_50), + PLDO("8038_l15", "8038_l15_pc", 0x0CA, 0x0CB, LDO_150), + NLDO1200("8038_l16", 0x0CC, 0x0CD, LDO_1200), + PLDO("8038_l17", "8038_l17_pc", 0x0CE, 0x0CF, LDO_150), + PLDO("8038_l18", "8038_l18_pc", 0x0D0, 0x0D1, LDO_50), + NLDO1200("8038_l19", 0x0D2, 0x0D3, LDO_1200), + NLDO1200("8038_l20", 0x0D4, 0x0D5, LDO_1200), + PLDO("8038_l21", "8038_l21_pc", 0x0D6, 0x0D7, LDO_150), + PLDO("8038_l22", "8038_l22_pc", 0x0D8, 0x0D9, LDO_50), + PLDO("8038_l23", "8038_l23_pc", 0x0DA, 0x0DB, LDO_50), + NLDO1200("8038_l24", 0x0DC, 0x0DD, LDO_1200), + NLDO("8038_l26", "8038_l26_pc", 0x0E0, 0x0E1, LDO_150), + NLDO1200("8038_l27", 0x0E2, 0x0E3, LDO_1200), + + /* name pc_name ctrl test2 clk sleep hpm_min */ + SMPS("8038_s1", "8038_s1_pc", 0x1E0, 0x1E5, 0x009, 0x1E2, SMPS_1500), + SMPS("8038_s2", "8038_s2_pc", 0x1D8, 0x1DD, 0x00A, 0x1DA, SMPS_1500), + SMPS("8038_s3", "8038_s3_pc", 0x1D0, 0x1D5, 0x00B, 0x1D2, SMPS_1500), + SMPS("8038_s4", "8038_s4_pc", 0x1E8, 0x1ED, 0x00C, 0x1EA, SMPS_1500), + + /* name ctrl fts_cnfg1 pfm pwr_cnfg hpm_min */ + FTSMPS("8038_s5", 0x025, 0x02E, 0x026, 0x032, SMPS_2000), + FTSMPS("8038_s6", 0x036, 0x03F, 0x037, 0x043, SMPS_2000), + + /* name pc_name ctrl test */ + VS("8038_lvs1", "8038_lvs1_pc", 0x060, 0x061), + VS("8038_lvs2", "8038_lvs2_pc", 0x062, 0x063), +}; + +#define MAX_NAME_COMPARISON_LEN 32 + +static int __devinit match_regulator( + struct pm8xxx_regulator_core_platform_data *core_data, const char *name) +{ + int found = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) { + if (regulator_data[i].rdesc.name + && strncmp(regulator_data[i].rdesc.name, name, + MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = false; + core_data->vreg = ®ulator_data[i]; + found = 1; + break; + } else if (regulator_data[i].rdesc_pc.name + && strncmp(regulator_data[i].rdesc_pc.name, name, + MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = true; + core_data->vreg = ®ulator_data[i]; + found = 1; + break; + } + } + + if (!found) + pr_err("could not find a match for regulator: %s\n", name); + + return found; +} + +static int __devinit +pm8038_add_regulators(const struct pm8038_platform_data *pdata, + struct pm8038 *pmic, int irq_base) +{ + int ret = 0; + struct mfd_cell *mfd_regulators; + struct pm8xxx_regulator_core_platform_data *cdata; + int i; + + /* Add one device for each regulator used by the board. */ + mfd_regulators = kzalloc(sizeof(struct mfd_cell) + * (pdata->num_regulators), GFP_KERNEL); + if (!mfd_regulators) { + pr_err("Cannot allocate %d bytes for pm8038 regulator " + "mfd cells\n", sizeof(struct mfd_cell) + * (pdata->num_regulators)); + return -ENOMEM; + } + cdata = kzalloc(sizeof(struct pm8xxx_regulator_core_platform_data) + * pdata->num_regulators, GFP_KERNEL); + if (!cdata) { + pr_err("Cannot allocate %d bytes for pm8038 regulator " + "core data\n", pdata->num_regulators + * sizeof(struct pm8xxx_regulator_core_platform_data)); + kfree(mfd_regulators); + return -ENOMEM; + } + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_init(®ulator_data[i].pc_lock); + + for (i = 0; i < pdata->num_regulators; i++) { + if (!pdata->regulator_pdatas[i].init_data.constraints.name) { + pr_err("name missing for regulator %d\n", i); + ret = -EINVAL; + goto bail; + } + if (!match_regulator(&cdata[i], + pdata->regulator_pdatas[i].init_data.constraints.name)) { + ret = -ENODEV; + goto bail; + } + cdata[i].pdata = &(pdata->regulator_pdatas[i]); + mfd_regulators[i].name = PM8XXX_REGULATOR_DEV_NAME; + mfd_regulators[i].id = cdata[i].pdata->id; + mfd_regulators[i].platform_data = &cdata[i]; + mfd_regulators[i].pdata_size = + sizeof(struct pm8xxx_regulator_core_platform_data); + } + ret = mfd_add_devices(pmic->dev, 0, mfd_regulators, + pdata->num_regulators, NULL, irq_base); + if (ret) + goto bail; + + pmic->mfd_regulators = mfd_regulators; + pmic->regulator_cdata = cdata; + return ret; + +bail: + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_destroy(®ulator_data[i].pc_lock); + kfree(mfd_regulators); + kfree(cdata); + return ret; +} + +static int __devinit +pm8038_add_subdevices(const struct pm8038_platform_data *pdata, + struct pm8038 *pmic) +{ + int ret = 0, irq_base = 0; + struct pm_irq_chip *irq_chip; + + if (pdata->irq_pdata) { + pdata->irq_pdata->irq_cdata.nirqs = PM8038_NR_IRQS; + pdata->irq_pdata->irq_cdata.base_addr = REG_IRQ_BASE; + irq_base = pdata->irq_pdata->irq_base; + irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); + + if (IS_ERR(irq_chip)) { + pr_err("Failed to init interrupts ret=%ld\n", + PTR_ERR(irq_chip)); + return PTR_ERR(irq_chip); + } + pmic->irq_chip = irq_chip; + } + + if (pdata->gpio_pdata) { + pdata->gpio_pdata->gpio_cdata.ngpios = PM8038_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 = PM8038_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; + } + } + + 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) { + leds_cell.platform_data = pdata->leds_pdata; + leds_cell.pdata_size = sizeof(struct pm8xxx_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; + } + } + + if (pdata->spk_pdata) { + spk_cell.platform_data = pdata->spk_pdata; + spk_cell.pdata_size = sizeof(struct pm8xxx_spk_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &spk_cell, 1, NULL, 0); + if (ret) { + pr_err("Failed to add spk subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->num_regulators > 0 && pdata->regulator_pdatas) { + ret = pm8038_add_regulators(pdata, pmic, irq_base); + if (ret) { + pr_err("Failed to add regulator subdevices ret=%d\n", + ret); + goto bail; + } + } + + 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; + } + + if (pdata->adc_pdata) { + adc_cell.platform_data = pdata->adc_pdata; + adc_cell.pdata_size = + sizeof(struct pm8xxx_adc_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &adc_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add adc subdevices ret=%d\n", + ret); + } + } + + if (pdata->charger_pdata) { + pdata->charger_pdata->charger_cdata.vbat_channel = CHANNEL_VBAT; + pdata->charger_pdata->charger_cdata.batt_temp_channel + = CHANNEL_BATT_THERM; + pdata->charger_pdata->charger_cdata.batt_id_channel + = CHANNEL_BATT_ID; + 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->bms_pdata) { + pdata->bms_pdata->bms_cdata.batt_temp_channel + = CHANNEL_BATT_THERM; + pdata->bms_pdata->bms_cdata.vbat_channel = CHANNEL_VBAT; + pdata->bms_pdata->bms_cdata.ref625mv_channel = CHANNEL_625MV; + pdata->bms_pdata->bms_cdata.ref1p25v_channel = CHANNEL_125V; + pdata->bms_pdata->bms_cdata.batt_id_channel = CHANNEL_BATT_ID; + 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; + } + } + return 0; +bail: + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + return ret; +} + +static const char * const pm8038_restart_reason[] = { + [0] = "Unknown", + [1] = "Triggered from CBL (external charger)", + [2] = "Triggered from KPD (power key press)", + [3] = "Triggered from CHG (usb charger insertion)", + [4] = "Triggered from SMPL (sudden momentary power loss)", + [5] = "Triggered from RTC (real time clock)", + [6] = "Triggered by Hard Reset", + [7] = "Triggered by General Purpose Trigger", +}; + +static const char * const pm8038_rev_names[] = { + [PM8XXX_REVISION_8038_TEST] = "test", + [PM8XXX_REVISION_8038_1p0] = "1.0", + [PM8XXX_REVISION_8038_2p0] = "2.0", + [PM8XXX_REVISION_8038_2p1] = "2.1", +}; + +static int __devinit pm8038_probe(struct platform_device *pdev) +{ + const struct pm8038_platform_data *pdata = pdev->dev.platform_data; + const char *revision_name = "unknown"; + struct pm8038 *pmic; + enum pm8xxx_version version; + int revision; + int rc; + u8 val; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pmic = kzalloc(sizeof(struct pm8038), GFP_KERNEL); + if (!pmic) { + pr_err("Cannot alloc pm8038 struct\n"); + return -ENOMEM; + } + + /* Read PMIC chip revision */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); + goto err_read_rev; + } + pr_info("PMIC revision 1: PM8038 rev %02X\n", val); + pmic->rev_registers = val; + + /* Read PMIC chip revision 2 */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", + REG_HWREV_2, rc); + goto err_read_rev; + } + pr_info("PMIC revision 2: PM8038 rev %02X\n", val); + pmic->rev_registers |= val << BITS_PER_BYTE; + + pmic->dev = &pdev->dev; + pm8038_drvdata.pm_chip_data = pmic; + platform_set_drvdata(pdev, &pm8038_drvdata); + + /* Print out human readable version and revision names. */ + version = pm8xxx_get_version(pmic->dev); + if (version == PM8XXX_VERSION_8038) { + revision = pm8xxx_get_revision(pmic->dev); + if (revision >= 0 && revision < ARRAY_SIZE(pm8038_rev_names)) + revision_name = pm8038_rev_names[revision]; + pr_info("PMIC version: PM8038 ver %s\n", revision_name); + } else { + WARN_ON(version != PM8XXX_VERSION_8038); + } + + /* Log human readable restart reason */ + rc = msm_ssbi_read(pdev->dev.parent, REG_PM8038_PON_CNTRL_3, &val, 1); + if (rc) { + pr_err("Cannot read restart reason rc=%d\n", rc); + goto err_read_rev; + } + val &= PM8038_RESTART_REASON_MASK; + pr_info("PMIC Restart Reason: %s\n", pm8038_restart_reason[val]); + + rc = pm8038_add_subdevices(pdata, pmic); + if (rc) { + pr_err("Cannot add subdevices rc=%d\n", rc); + goto err; + } + + return 0; + +err: + mfd_remove_devices(pmic->dev); + platform_set_drvdata(pdev, NULL); + kfree(pmic->mfd_regulators); + kfree(pmic->regulator_cdata); +err_read_rev: + kfree(pmic); + return rc; +} + +static int __devexit pm8038_remove(struct platform_device *pdev) +{ + struct pm8xxx_drvdata *drvdata; + struct pm8038 *pmic = NULL; + int i; + + drvdata = platform_get_drvdata(pdev); + + if (drvdata) + pmic = drvdata->pm_chip_data; + + if (pmic) { + if (pmic->dev) + mfd_remove_devices(pmic->dev); + + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + if (pmic->mfd_regulators) { + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_destroy(®ulator_data[i].pc_lock); + } + kfree(pmic->mfd_regulators); + kfree(pmic->regulator_cdata); + kfree(pmic); + } + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver pm8038_driver = { + .probe = pm8038_probe, + .remove = __devexit_p(pm8038_remove), + .driver = { + .name = PM8038_CORE_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8038_init(void) +{ + return platform_driver_register(&pm8038_driver); +} +postcore_initcall(pm8038_init); + +static void __exit pm8038_exit(void) +{ + platform_driver_unregister(&pm8038_driver); +} +module_exit(pm8038_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8038 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8038-core"); diff --git a/drivers/mfd/pm8821-core.c b/drivers/mfd/pm8821-core.c new file mode 100644 index 0000000000000000000000000000000000000000..df9d2e1c799cd30056d7c4ca2e5097aeadaaba73 --- /dev/null +++ b/drivers/mfd/pm8821-core.c @@ -0,0 +1,317 @@ +/* + * 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 + +#define REG_HWREV 0x002 /* PMIC4 revision */ +#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ + +#define REG_MPP_BASE 0x050 +#define REG_IRQ_BASE 0x1BB + +#define PM8821_VERSION_MASK 0xFFF0 +#define PM8821_VERSION_VALUE 0x0BF0 +#define PM8821_REVISION_MASK 0x000F + +#define SINGLE_IRQ_RESOURCE(_name, _irq) \ +{ \ + .name = _name, \ + .start = _irq, \ + .end = _irq, \ + .flags = IORESOURCE_IRQ, \ +} + +struct pm8821 { + struct device *dev; + struct pm_irq_chip *irq_chip; + u32 rev_registers; +}; + +static int pm8821_readb(const struct device *dev, u16 addr, u8 *val) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, val, 1); +} + +static int pm8821_writeb(const struct device *dev, u16 addr, u8 val) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); +} + +static int pm8821_read_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8821_write_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8821_read_irq_stat(const struct device *dev, int irq) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + + return pm8xxx_get_irq_stat(pmic->irq_chip, irq); +} + +static enum pm8xxx_version pm8821_get_version(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + enum pm8xxx_version version = -ENODEV; + + if ((pmic->rev_registers & PM8821_VERSION_MASK) == PM8821_VERSION_VALUE) + version = PM8XXX_VERSION_8821; + + return version; +} + +static int pm8821_get_revision(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8821_drvdata = dev_get_drvdata(dev); + const struct pm8821 *pmic = pm8821_drvdata->pm_chip_data; + + return pmic->rev_registers & PM8821_REVISION_MASK; +} + +static struct pm8xxx_drvdata pm8821_drvdata = { + .pmic_readb = pm8821_readb, + .pmic_writeb = pm8821_writeb, + .pmic_read_buf = pm8821_read_buf, + .pmic_write_buf = pm8821_write_buf, + .pmic_read_irq_stat = pm8821_read_irq_stat, + .pmic_get_version = pm8821_get_version, + .pmic_get_revision = pm8821_get_revision, +}; + +static const struct resource mpp_cell_resources[] __devinitconst = { + { + .start = PM8821_IRQ_BLOCK_BIT(PM8821_MPP_BLOCK_START, 0), + .end = PM8821_IRQ_BLOCK_BIT(PM8821_MPP_BLOCK_START, 0) + + PM8821_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 struct mfd_cell debugfs_cell __devinitdata = { + .name = "pm8xxx-debug", + .id = 1, + .platform_data = "pm8821-dbg", + .pdata_size = sizeof("pm8821-dbg"), +}; + + +static int __devinit +pm8821_add_subdevices(const struct pm8821_platform_data *pdata, + struct pm8821 *pmic) +{ + int ret = 0, irq_base = 0; + struct pm_irq_chip *irq_chip; + + if (pdata->irq_pdata) { + pdata->irq_pdata->irq_cdata.nirqs = PM8821_NR_IRQS; + pdata->irq_pdata->irq_cdata.base_addr = REG_IRQ_BASE; + irq_base = pdata->irq_pdata->irq_base; + irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); + + if (IS_ERR(irq_chip)) { + pr_err("Failed to init interrupts ret=%ld\n", + PTR_ERR(irq_chip)); + return PTR_ERR(irq_chip); + } + pmic->irq_chip = irq_chip; + } + + if (pdata->mpp_pdata) { + pdata->mpp_pdata->core_data.nmpps = PM8821_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; + } + } + + 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; + } + + return 0; +bail: + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + return ret; +} + +static const char * const pm8821_rev_names[] = { + [PM8XXX_REVISION_8821_TEST] = "test", + [PM8XXX_REVISION_8821_1p0] = "1.0", + [PM8XXX_REVISION_8821_2p0] = "2.0", + [PM8XXX_REVISION_8821_2p1] = "2.1", +}; + +static int __devinit pm8821_probe(struct platform_device *pdev) +{ + const struct pm8821_platform_data *pdata = pdev->dev.platform_data; + const char *revision_name = "unknown"; + struct pm8821 *pmic; + enum pm8xxx_version version; + int revision; + int rc; + u8 val; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pmic = kzalloc(sizeof(struct pm8821), GFP_KERNEL); + if (!pmic) { + pr_err("Cannot alloc pm8821 struct\n"); + return -ENOMEM; + } + + /* Read PMIC chip revision */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); + goto err_read_rev; + } + pr_info("PMIC revision 1: PM8821 rev %02X\n", val); + pmic->rev_registers = val; + + /* Read PMIC chip revision 2 */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", + REG_HWREV_2, rc); + goto err_read_rev; + } + pr_info("PMIC revision 2: PM8821 rev %02X\n", val); + pmic->rev_registers |= val << BITS_PER_BYTE; + + pmic->dev = &pdev->dev; + pm8821_drvdata.pm_chip_data = pmic; + platform_set_drvdata(pdev, &pm8821_drvdata); + + /* Print out human readable version and revision names. */ + version = pm8xxx_get_version(pmic->dev); + if (version == PM8XXX_VERSION_8821) { + revision = pm8xxx_get_revision(pmic->dev); + if (revision >= 0 && revision < ARRAY_SIZE(pm8821_rev_names)) + revision_name = pm8821_rev_names[revision]; + pr_info("PMIC version: PM8821 ver %s\n", revision_name); + } else { + WARN_ON(version != PM8XXX_VERSION_8821); + } + + rc = pm8821_add_subdevices(pdata, pmic); + if (rc) { + pr_err("Cannot add subdevices rc=%d\n", rc); + goto err; + } + + return 0; + +err: + mfd_remove_devices(pmic->dev); + platform_set_drvdata(pdev, NULL); +err_read_rev: + kfree(pmic); + return rc; +} + +static int __devexit pm8821_remove(struct platform_device *pdev) +{ + struct pm8xxx_drvdata *drvdata; + struct pm8821 *pmic = NULL; + + drvdata = platform_get_drvdata(pdev); + if (drvdata) + pmic = drvdata->pm_chip_data; + if (pmic) + mfd_remove_devices(pmic->dev); + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + platform_set_drvdata(pdev, NULL); + kfree(pmic); + + return 0; +} + +static struct platform_driver pm8821_driver = { + .probe = pm8821_probe, + .remove = __devexit_p(pm8821_remove), + .driver = { + .name = "pm8821-core", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8821_init(void) +{ + return platform_driver_register(&pm8821_driver); +} +postcore_initcall(pm8821_init); + +static void __exit pm8821_exit(void) +{ + platform_driver_unregister(&pm8821_driver); +} +module_exit(pm8821_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8821 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8821-core"); diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index e873b15753d86aa356e6357035ee4a2b9a5df865..f39a19f94201e685143ad2be122538a3aea80bbf 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -14,20 +14,55 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ #include +#include #include #include +#include #include #include #include #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_IRQ_BASE 0x1BB + +#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 0x021 +#define REG_BATT_ALARM_PWM_CTRL 0x020 + +#define PM8921_VERSION_MASK 0xFFF0 +#define PM8921_VERSION_VALUE 0x06F0 +#define PM8922_VERSION_VALUE 0x0AF0 +#define PM8917_VERSION_VALUE 0x0CF0 +#define PM8921_REVISION_MASK 0x000F + +#define REG_PM8921_PON_CNTRL_3 0x01D +#define PM8921_RESTART_REASON_MASK 0x07 + +#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 device *dev; + struct pm_irq_chip *irq_chip; + struct mfd_cell *mfd_regulators; + struct pm8xxx_regulator_core_platform_data *regulator_cdata; + u32 rev_registers; }; static int pm8921_readb(const struct device *dev, u16 addr, u8 *val) @@ -72,25 +107,481 @@ 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; + else if ((pmic->rev_registers & PM8921_VERSION_MASK) + == PM8922_VERSION_VALUE) + version = PM8XXX_VERSION_8922; + else if ((pmic->rev_registers & PM8921_VERSION_MASK) + == PM8917_VERSION_VALUE) + version = PM8XXX_VERSION_8917; + + 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 struct resource gpio_cell_resources[] = { + [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 = PM8XXX_ADC_DEV_NAME, + .id = -1, + .resources = adc_cell_resources, + .num_resources = ARRAY_SIZE(adc_cell_resources), +}; + +static struct resource mpp_cell_resources[] = { + { + .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 = 0, + .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 = 0, + .platform_data = "pm8921-dbg", + .pdata_size = sizeof("pm8921-dbg"), }; -static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data - *pdata, - struct pm8921 *pmic, - u32 rev) +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 struct pm8xxx_tm_core_data thermal_alarm_cdata = { + .adc_channel = CHANNEL_DIE_TEMP, + .adc_type = PM8XXX_TM_ADC_PM8XXX_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 const struct resource ccadc_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("PM8921_BMS_CCADC_EOC", PM8921_BMS_CCADC_EOC), +}; + +static struct mfd_cell ccadc_cell __devinitdata = { + .name = PM8XXX_CCADC_DEV_NAME, + .id = -1, + .resources = ccadc_cell_resources, + .num_resources = ARRAY_SIZE(ccadc_cell_resources), +}; + +static struct mfd_cell vibrator_cell __devinitdata = { + .name = PM8XXX_VIBRATOR_DEV_NAME, + .id = -1, +}; + +static struct pm8xxx_vreg regulator_data[] = { + /* name pc_name ctrl test hpm_min */ + NLDO("8921_l1", "8921_l1_pc", 0x0AE, 0x0AF, LDO_150), + NLDO("8921_l2", "8921_l2_pc", 0x0B0, 0x0B1, LDO_150), + PLDO("8921_l3", "8921_l3_pc", 0x0B2, 0x0B3, LDO_150), + PLDO("8921_l4", "8921_l4_pc", 0x0B4, 0x0B5, LDO_50), + PLDO("8921_l5", "8921_l5_pc", 0x0B6, 0x0B7, LDO_300), + PLDO("8921_l6", "8921_l6_pc", 0x0B8, 0x0B9, LDO_600), + PLDO("8921_l7", "8921_l7_pc", 0x0BA, 0x0BB, LDO_150), + PLDO("8921_l8", "8921_l8_pc", 0x0BC, 0x0BD, LDO_300), + PLDO("8921_l9", "8921_l9_pc", 0x0BE, 0x0BF, LDO_300), + PLDO("8921_l10", "8921_l10_pc", 0x0C0, 0x0C1, LDO_600), + PLDO("8921_l11", "8921_l11_pc", 0x0C2, 0x0C3, LDO_150), + NLDO("8921_l12", "8921_l12_pc", 0x0C4, 0x0C5, LDO_150), + PLDO("8921_l14", "8921_l14_pc", 0x0C8, 0x0C9, LDO_50), + PLDO("8921_l15", "8921_l15_pc", 0x0CA, 0x0CB, LDO_150), + PLDO("8921_l16", "8921_l16_pc", 0x0CC, 0x0CD, LDO_300), + PLDO("8921_l17", "8921_l17_pc", 0x0CE, 0x0CF, LDO_150), + NLDO("8921_l18", "8921_l18_pc", 0x0D0, 0x0D1, LDO_150), + PLDO("8921_l21", "8921_l21_pc", 0x0D6, 0x0D7, LDO_150), + PLDO("8921_l22", "8921_l22_pc", 0x0D8, 0x0D9, LDO_150), + PLDO("8921_l23", "8921_l23_pc", 0x0DA, 0x0DB, LDO_150), + NLDO1200("8921_l24", 0x0DC, 0x0DD, LDO_1200), + NLDO1200("8921_l25", 0x0DE, 0x0DF, LDO_1200), + NLDO1200("8921_l26", 0x0E0, 0x0E1, LDO_1200), + NLDO1200("8921_l27", 0x0E2, 0x0E3, LDO_1200), + NLDO1200("8921_l28", 0x0E4, 0x0E5, LDO_1200), + PLDO("8921_l29", "8921_l29_pc", 0x0E6, 0x0E7, LDO_150), + + /* name pc_name ctrl test2 clk sleep hpm_min */ + SMPS("8921_s1", "8921_s1_pc", 0x1D0, 0x1D5, 0x009, 0x1D2, SMPS_1500), + SMPS("8921_s2", "8921_s2_pc", 0x1D8, 0x1DD, 0x00A, 0x1DA, SMPS_1500), + SMPS("8921_s3", "8921_s3_pc", 0x1E0, 0x1E5, 0x00B, 0x1E2, SMPS_1500), + SMPS("8921_s4", "8921_s4_pc", 0x1E8, 0x1ED, 0x011, 0x1EA, SMPS_1500), + + /* name ctrl fts_cnfg1 pfm pwr_cnfg hpm_min */ + FTSMPS("8921_s5", 0x025, 0x02E, 0x026, 0x032, SMPS_2000), + FTSMPS("8921_s6", 0x036, 0x03F, 0x037, 0x043, SMPS_2000), + + /* name pc_name ctrl test2 clk sleep hpm_min */ + SMPS("8921_s7", "8921_s7_pc", 0x1F0, 0x1F5, 0x012, 0x1F2, SMPS_1500), + SMPS("8921_s8", "8921_s8_pc", 0x1F8, 0x1FD, 0x013, 0x1FA, SMPS_1500), + + /* name pc_name ctrl test */ + VS("8921_lvs1", "8921_lvs1_pc", 0x060, 0x061), + VS300("8921_lvs2", 0x062, 0x063), + VS("8921_lvs3", "8921_lvs3_pc", 0x064, 0x065), + VS("8921_lvs4", "8921_lvs4_pc", 0x066, 0x067), + VS("8921_lvs5", "8921_lvs5_pc", 0x068, 0x069), + VS("8921_lvs6", "8921_lvs6_pc", 0x06A, 0x06B), + VS("8921_lvs7", "8921_lvs7_pc", 0x06C, 0x06D), + VS300("8921_usb_otg", 0x06E, 0x06F), + VS300("8921_hdmi_mvs", 0x070, 0x071), + + /* name ctrl */ + NCP("8921_ncp", 0x090), +}; + +/* + * PM8917 adds 6 LDOs and a boost regulator beyond those available on PM8921. + * It also replaces SMPS 3 with FTSMPS 3. PM8917 does not have an NCP. + */ +static struct pm8xxx_vreg pm8917_regulator_data[] = { + /* name pc_name ctrl test hpm_min */ + PLDO("8917_l30", "8917_l30_pc", 0x0A3, 0x0A4, LDO_150), + PLDO("8917_l31", "8917_l31_pc", 0x0A5, 0x0A6, LDO_150), + PLDO("8917_l32", "8917_l32_pc", 0x0A7, 0x0A8, LDO_150), + PLDO("8917_l33", "8917_l33_pc", 0x0C6, 0x0C7, LDO_150), + PLDO("8917_l34", "8917_l34_pc", 0x0D2, 0x0D3, LDO_150), + PLDO("8917_l35", "8917_l35_pc", 0x0D4, 0x0D5, LDO_300), + PLDO("8917_l36", "8917_l36_pc", 0x0A9, 0x0AA, LDO_50), + + /* name ctrl */ + BOOST("8917_boost", 0x04B), +}; + +#define MAX_NAME_COMPARISON_LEN 32 + +static int __devinit match_regulator(enum pm8xxx_version version, + struct pm8xxx_regulator_core_platform_data *core_data, const char *name) +{ + int found = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) { + if (regulator_data[i].rdesc.name + && strncmp(regulator_data[i].rdesc.name, name, + MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = false; + core_data->vreg = ®ulator_data[i]; + found = 1; + break; + } else if (regulator_data[i].rdesc_pc.name + && strncmp(regulator_data[i].rdesc_pc.name, name, + MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = true; + core_data->vreg = ®ulator_data[i]; + found = 1; + break; + } + } + if (version == PM8XXX_VERSION_8917) { + for (i = 0; i < ARRAY_SIZE(pm8917_regulator_data); i++) { + if (pm8917_regulator_data[i].rdesc.name + && strncmp(pm8917_regulator_data[i].rdesc.name, + name, MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = false; + core_data->vreg = &pm8917_regulator_data[i]; + found = 1; + break; + } else if (pm8917_regulator_data[i].rdesc_pc.name + && strncmp(pm8917_regulator_data[i].rdesc_pc.name, + name, MAX_NAME_COMPARISON_LEN) == 0) { + core_data->is_pin_controlled = true; + core_data->vreg = &pm8917_regulator_data[i]; + found = 1; + break; + } + } + } + + if (!found) + pr_err("could not find a match for regulator: %s\n", name); + + return found; +} + +static int __devinit +pm8921_add_regulators(const struct pm8921_platform_data *pdata, + struct pm8921 *pmic, int irq_base) +{ + int ret = 0; + struct mfd_cell *mfd_regulators; + struct pm8xxx_regulator_core_platform_data *cdata; + enum pm8xxx_version version; + int i; + + version = pm8xxx_get_version(pmic->dev); + + /* Add one device for each regulator used by the board. */ + 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)); + return -ENOMEM; + } + cdata = kzalloc(sizeof(struct pm8xxx_regulator_core_platform_data) + * pdata->num_regulators, GFP_KERNEL); + if (!cdata) { + pr_err("Cannot allocate %d bytes for pm8921 regulator " + "core data\n", pdata->num_regulators + * sizeof(struct pm8xxx_regulator_core_platform_data)); + kfree(mfd_regulators); + return -ENOMEM; + } + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_init(®ulator_data[i].pc_lock); + for (i = 0; i < ARRAY_SIZE(pm8917_regulator_data); i++) + mutex_init(&pm8917_regulator_data[i].pc_lock); + + for (i = 0; i < pdata->num_regulators; i++) { + if (!pdata->regulator_pdatas[i].init_data.constraints.name) { + pr_err("name missing for regulator %d\n", i); + ret = -EINVAL; + goto bail; + } + if (!match_regulator(version, &cdata[i], + pdata->regulator_pdatas[i].init_data.constraints.name)) { + ret = -ENODEV; + goto bail; + } + cdata[i].pdata = &(pdata->regulator_pdatas[i]); + mfd_regulators[i].name = PM8XXX_REGULATOR_DEV_NAME; + mfd_regulators[i].id = cdata[i].pdata->id; + mfd_regulators[i].platform_data = &cdata[i]; + mfd_regulators[i].pdata_size = + sizeof(struct pm8xxx_regulator_core_platform_data); + } + ret = mfd_add_devices(pmic->dev, 0, mfd_regulators, + pdata->num_regulators, NULL, irq_base); + if (ret) + goto bail; + + pmic->mfd_regulators = mfd_regulators; + pmic->regulator_cdata = cdata; + return ret; + +bail: + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_destroy(®ulator_data[i].pc_lock); + for (i = 0; i < ARRAY_SIZE(pm8917_regulator_data); i++) + mutex_destroy(&pm8917_regulator_data[i].pc_lock); + kfree(mfd_regulators); + kfree(cdata); + return ret; +} + +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; + enum pm8xxx_version version; + + version = pm8xxx_get_version(pmic->dev); if (pdata->irq_pdata) { pdata->irq_pdata->irq_cdata.nirqs = PM8921_NR_IRQS; - pdata->irq_pdata->irq_cdata.rev = rev; + pdata->irq_pdata->irq_cdata.base_addr = REG_IRQ_BASE; irq_base = pdata->irq_pdata->irq_base; irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); @@ -101,16 +592,270 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data } pmic->irq_chip = irq_chip; } + + if (pdata->gpio_pdata) { + if (version == PM8XXX_VERSION_8917) { + gpio_cell_resources[0].end = gpio_cell_resources[0].end + + PM8917_NR_GPIOS + - PM8921_NR_GPIOS; + pdata->gpio_pdata->gpio_cdata.ngpios = PM8917_NR_GPIOS; + } else { + 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) { + if (version == PM8XXX_VERSION_8917) { + mpp_cell_resources[0].end = mpp_cell_resources[0].end + + PM8917_NR_MPPS + - PM8921_NR_MPPS; + pdata->mpp_pdata->core_data.nmpps = PM8917_NR_MPPS; + } else { + 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; + pdata->charger_pdata->charger_cdata.batt_temp_channel + = CHANNEL_BATT_THERM; + pdata->charger_pdata->charger_cdata.batt_id_channel + = CHANNEL_BATT_ID; + 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 pm8xxx_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) { + pdata->bms_pdata->bms_cdata.batt_temp_channel + = CHANNEL_BATT_THERM; + pdata->bms_pdata->bms_cdata.vbat_channel = CHANNEL_VBAT; + pdata->bms_pdata->bms_cdata.ref625mv_channel = CHANNEL_625MV; + pdata->bms_pdata->bms_cdata.ref1p25v_channel = CHANNEL_125V; + pdata->bms_pdata->bms_cdata.batt_id_channel = CHANNEL_BATT_ID; + 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; + } + } + + if (pdata->num_regulators > 0 && pdata->regulator_pdatas) { + ret = pm8921_add_regulators(pdata, pmic, irq_base); + if (ret) { + pr_err("Failed to add regulator subdevices ret=%d\n", + ret); + goto bail; + } + } + + 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; + } + + 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; + } + } + + 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; + } + + if (version != PM8XXX_VERSION_8917) { + if (pdata->pwm_pdata) { + pwm_cell.platform_data = pdata->pwm_pdata; + pwm_cell.pdata_size = + sizeof(struct pm8xxx_pwm_platform_data); + } + 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->leds_pdata) { + leds_cell.platform_data = pdata->leds_pdata; + leds_cell.pdata_size = + sizeof(struct pm8xxx_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; + } + } + + if (pdata->vibrator_pdata) { + vibrator_cell.platform_data = pdata->vibrator_pdata; + vibrator_cell.pdata_size = + sizeof(struct pm8xxx_vibrator_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &vibrator_cell, + 1, NULL, 0); + if (ret) { + pr_err("Failed to add vibrator ret=%d\n", ret); + goto bail; + } + } + } + + if (pdata->ccadc_pdata) { + ccadc_cell.platform_data = pdata->ccadc_pdata; + ccadc_cell.pdata_size = + sizeof(struct pm8xxx_ccadc_platform_data); + + ret = mfd_add_devices(pmic->dev, 0, &ccadc_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add ccadc 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_restart_reason[] = { + [0] = "Unknown", + [1] = "Triggered from CBL (external charger)", + [2] = "Triggered from KPD (power key press)", + [3] = "Triggered from CHG (usb charger insertion)", + [4] = "Triggered from SMPL (sudden momentary power loss)", + [5] = "Triggered from RTC (real time clock)", + [6] = "Triggered by Hard Reset", + [7] = "Triggered by General Purpose Trigger", +}; + +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", + [PM8XXX_REVISION_8921_3p0] = "3.0", + [PM8XXX_REVISION_8921_3p1] = "3.1", +}; + +static const char * const pm8922_rev_names[] = { + [PM8XXX_REVISION_8922_TEST] = "test", + [PM8XXX_REVISION_8922_1p0] = "1.0", + [PM8XXX_REVISION_8922_1p1] = "1.1", + [PM8XXX_REVISION_8922_2p0] = "2.0", +}; + +static const char * const pm8917_rev_names[] = { + [PM8XXX_REVISION_8917_TEST] = "test", + [PM8XXX_REVISION_8917_1p0] = "1.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 +875,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 +885,43 @@ 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); + revision = pm8xxx_get_revision(pmic->dev); + if (version == PM8XXX_VERSION_8921) { + 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 if (version == PM8XXX_VERSION_8922) { + if (revision >= 0 && revision < ARRAY_SIZE(pm8922_rev_names)) + revision_name = pm8922_rev_names[revision]; + pr_info("PMIC version: PM8922 rev %s\n", revision_name); + } else if (version == PM8XXX_VERSION_8917) { + if (revision >= 0 && revision < ARRAY_SIZE(pm8917_rev_names)) + revision_name = pm8917_rev_names[revision]; + pr_info("PMIC version: PM8917 rev %s\n", revision_name); + } else { + WARN_ON(version != PM8XXX_VERSION_8921 + && version != PM8XXX_VERSION_8922 + && version != PM8XXX_VERSION_8917); + } + + /* Log human readable restart reason */ + rc = msm_ssbi_read(pdev->dev.parent, REG_PM8921_PON_CNTRL_3, &val, 1); + if (rc) { + pr_err("Cannot read restart reason rc=%d\n", rc); + goto err_read_rev; + } + val &= PM8921_RESTART_REASON_MASK; + pr_info("PMIC Restart Reason: %s\n", pm8921_restart_reason[val]); + + rc = pm8921_add_subdevices(pdata, pmic); if (rc) { pr_err("Cannot add subdevices rc=%d\n", rc); goto err; @@ -160,6 +935,8 @@ static int __devinit pm8921_probe(struct platform_device *pdev) err: mfd_remove_devices(pmic->dev); platform_set_drvdata(pdev, NULL); + kfree(pmic->mfd_regulators); + kfree(pmic->regulator_cdata); err_read_rev: kfree(pmic); return rc; @@ -169,18 +946,28 @@ static int __devexit pm8921_remove(struct platform_device *pdev) { struct pm8xxx_drvdata *drvdata; struct pm8921 *pmic = NULL; + int i; drvdata = platform_get_drvdata(pdev); if (drvdata) pmic = drvdata->pm_chip_data; - if (pmic) - mfd_remove_devices(pmic->dev); - if (pmic->irq_chip) { - pm8xxx_irq_exit(pmic->irq_chip); - pmic->irq_chip = NULL; + if (pmic) { + if (pmic->dev) + mfd_remove_devices(pmic->dev); + if (pmic->irq_chip) + pm8xxx_irq_exit(pmic->irq_chip); + if (pmic->mfd_regulators) { + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) + mutex_destroy(®ulator_data[i].pc_lock); + for (i = 0; i < ARRAY_SIZE(pm8917_regulator_data); i++) + mutex_destroy( + &pm8917_regulator_data[i].pc_lock); + } + kfree(pmic->mfd_regulators); + kfree(pmic->regulator_cdata); + kfree(pmic); } platform_set_drvdata(pdev, NULL); - kfree(pmic); return 0; } @@ -198,7 +985,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 0000000000000000000000000000000000000000..1d30db9ad75d8a242c561c08e608b70b2cb37e8e --- /dev/null +++ b/drivers/mfd/pm8xxx-batt-alarm.c @@ -0,0 +1,806 @@ +/* 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) + return; + + 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 0000000000000000000000000000000000000000..d450db30cc44a93fe06f44d8e698d51e69eb75ee --- /dev/null +++ b/drivers/mfd/pm8xxx-debug.c @@ -0,0 +1,216 @@ +/* + * 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 + +#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; + } + + 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; + } + + mutex_init(&debugdev->debug_mutex); + + 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); + mutex_destroy(&debugdev->debug_mutex); + 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 d452dd013081a5c742173ee095c9676920ce7952..cc156b37bc079e21698a2bd13c24d7d41f8f5cc8 100644 --- a/drivers/mfd/pm8xxx-irq.c +++ b/drivers/mfd/pm8xxx-irq.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -13,6 +13,7 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ +#include #include #include #include @@ -24,17 +25,15 @@ /* PMIC8xxx 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 SSBI_REG_ADDR_IRQ_ROOT(base) (base + 0) +#define SSBI_REG_ADDR_IRQ_M_STATUS1(base) (base + 1) +#define SSBI_REG_ADDR_IRQ_M_STATUS2(base) (base + 2) +#define SSBI_REG_ADDR_IRQ_M_STATUS3(base) (base + 3) +#define SSBI_REG_ADDR_IRQ_M_STATUS4(base) (base + 4) +#define SSBI_REG_ADDR_IRQ_BLK_SEL(base) (base + 5) +#define SSBI_REG_ADDR_IRQ_IT_STATUS(base) (base + 6) +#define SSBI_REG_ADDR_IRQ_CONFIG(base) (base + 7) +#define SSBI_REG_ADDR_IRQ_RT_STATUS(base) (base + 8) #define PM_IRQF_LVL_SEL 0x01 /* level select */ #define PM_IRQF_MASK_FE 0x02 /* mask falling edge */ @@ -50,6 +49,7 @@ struct pm_irq_chip { struct device *dev; spinlock_t pm_irq_lock; + unsigned int base_addr; unsigned int devirq; unsigned int irq_base; unsigned int num_irqs; @@ -60,13 +60,14 @@ struct pm_irq_chip { static int pm8xxx_read_root_irq(const struct pm_irq_chip *chip, u8 *rp) { - return pm8xxx_readb(chip->dev, SSBI_REG_ADDR_IRQ_ROOT, rp); + return pm8xxx_readb(chip->dev, + SSBI_REG_ADDR_IRQ_ROOT(chip->base_addr), rp); } static int pm8xxx_read_master_irq(const struct pm_irq_chip *chip, u8 m, u8 *bp) { return pm8xxx_readb(chip->dev, - SSBI_REG_ADDR_IRQ_M_STATUS1 + m, bp); + SSBI_REG_ADDR_IRQ_M_STATUS1(chip->base_addr) + m, bp); } static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, u8 bp, u8 *ip) @@ -74,13 +75,15 @@ static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, u8 bp, u8 *ip) int rc; spin_lock(&chip->pm_irq_lock); - rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + rc = pm8xxx_writeb(chip->dev, + SSBI_REG_ADDR_IRQ_BLK_SEL(chip->base_addr), bp); if (rc) { pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); goto bail; } - rc = pm8xxx_readb(chip->dev, SSBI_REG_ADDR_IRQ_IT_STATUS, ip); + rc = pm8xxx_readb(chip->dev, + SSBI_REG_ADDR_IRQ_IT_STATUS(chip->base_addr), ip); if (rc) pr_err("Failed Reading Status rc=%d\n", rc); bail: @@ -88,19 +91,51 @@ static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, u8 bp, u8 *ip) return rc; } -static int pm8xxx_config_irq(struct pm_irq_chip *chip, u8 bp, u8 cp) +static int pm8xxx_read_config_irq(struct pm_irq_chip *chip, u8 bp, u8 cp, u8 *r) { int rc; spin_lock(&chip->pm_irq_lock); - rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + rc = pm8xxx_writeb(chip->dev, + SSBI_REG_ADDR_IRQ_BLK_SEL(chip->base_addr), bp); if (rc) { pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); goto bail; } + cp &= ~PM_IRQF_WRITE; + rc = pm8xxx_writeb(chip->dev, + SSBI_REG_ADDR_IRQ_CONFIG(chip->base_addr), cp); + if (rc) + pr_err("Failed Configuring IRQ rc=%d\n", rc); + + rc = pm8xxx_readb(chip->dev, + SSBI_REG_ADDR_IRQ_CONFIG(chip->base_addr), r); + if (rc) + pr_err("Failed reading IRQ rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int pm8xxx_write_config_irq(struct pm_irq_chip *chip, u8 bp, u8 cp) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = pm8xxx_writeb(chip->dev, + SSBI_REG_ADDR_IRQ_BLK_SEL(chip->base_addr), bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + /* + * Set the write bit here as this could be a unrequested irq + * whose PM_IRQF_WRITE bit is not set + */ cp |= PM_IRQF_WRITE; - rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_CONFIG, cp); + rc = pm8xxx_writeb(chip->dev, + SSBI_REG_ADDR_IRQ_CONFIG(chip->base_addr), cp); if (rc) pr_err("Failed Configuring IRQ rc=%d\n", rc); bail: @@ -157,17 +192,16 @@ static int pm8xxx_irq_master_handler(struct pm_irq_chip *chip, int master) return ret; } -static void pm8xxx_irq_handler(unsigned int irq, struct irq_desc *desc) +static irqreturn_t pm8xxx_irq_handler(int irq, void *data) { - struct pm_irq_chip *chip = irq_desc_get_handler_data(desc); - struct irq_chip *irq_chip = irq_desc_get_chip(desc); + struct pm_irq_chip *chip = data; u8 root; int i, ret, masters = 0; ret = pm8xxx_read_root_irq(chip, &root); if (ret) { pr_err("Can't read root status ret=%d\n", ret); - return; + return IRQ_HANDLED; } /* on pm8xxx series masters start from bit 1 of the root */ @@ -178,7 +212,27 @@ static void pm8xxx_irq_handler(unsigned int irq, struct irq_desc *desc) if (masters & (1 << i)) pm8xxx_irq_master_handler(chip, i); - irq_chip->irq_ack(&desc->irq_data); + return IRQ_HANDLED; +} + +static void pm8xxx_irq_mask(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = d->irq - chip->irq_base; + int master, irq_bit; + u8 block, config; + + block = pmirq / 8; + master = block / 8; + irq_bit = pmirq % 8; + + if (chip->config[pmirq] == 0) { + pr_warn("masking rogue irq=%d pmirq=%d\n", d->irq, pmirq); + chip->config[pmirq] = irq_bit << PM_IRQF_BITS_SHIFT; + } + + config = chip->config[pmirq] | PM_IRQF_MASK_ALL; + pm8xxx_write_config_irq(chip, block, config); } static void pm8xxx_irq_mask_ack(struct irq_data *d) @@ -192,8 +246,13 @@ static void pm8xxx_irq_mask_ack(struct irq_data *d) master = block / 8; irq_bit = pmirq % 8; + if (chip->config[pmirq] == 0) { + pr_warn("mask acking rogue irq=%d pmirq=%d\n", d->irq, pmirq); + chip->config[pmirq] = irq_bit << PM_IRQF_BITS_SHIFT; + } + config = chip->config[pmirq] | PM_IRQF_MASK_ALL | PM_IRQF_CLR; - pm8xxx_config_irq(chip, block, config); + pm8xxx_write_config_irq(chip, block, config); } static void pm8xxx_irq_unmask(struct irq_data *d) @@ -201,14 +260,17 @@ static void pm8xxx_irq_unmask(struct irq_data *d) struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); unsigned int pmirq = d->irq - chip->irq_base; int master, irq_bit; - u8 block, config; + u8 block, config, hw_conf; block = pmirq / 8; master = block / 8; irq_bit = pmirq % 8; config = chip->config[pmirq]; - pm8xxx_config_irq(chip, block, config); + pm8xxx_read_config_irq(chip, block, config, &hw_conf); + /* check if it is masked */ + if ((hw_conf & PM_IRQF_MASK_ALL) == PM_IRQF_MASK_ALL) + pm8xxx_write_config_irq(chip, block, config); } static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) @@ -238,8 +300,14 @@ static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) chip->config[pmirq] &= ~PM_IRQF_MASK_FE; } + /* + * The PM_IRQF_WRITE flag serves as an indication that this interrupt + * been requested + */ + chip->config[pmirq] |= PM_IRQF_WRITE; + config = chip->config[pmirq] | PM_IRQF_CLR; - return pm8xxx_config_irq(chip, block, config); + return pm8xxx_write_config_irq(chip, block, config); } static int pm8xxx_irq_set_wake(struct irq_data *d, unsigned int on) @@ -247,12 +315,21 @@ 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 = pm8xxx_irq_mask, .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, }; @@ -286,14 +363,16 @@ int pm8xxx_get_irq_stat(struct pm_irq_chip *chip, int irq) spin_lock_irqsave(&chip->pm_irq_lock, flags); - rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, block); + rc = pm8xxx_writeb(chip->dev, + SSBI_REG_ADDR_IRQ_BLK_SEL(chip->base_addr), block); if (rc) { pr_err("Failed Selecting block irq=%d pmirq=%d blk=%d rc=%d\n", irq, pmirq, block, rc); goto bail_out; } - rc = pm8xxx_readb(chip->dev, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits); + rc = pm8xxx_readb(chip->dev, + SSBI_REG_ADDR_IRQ_RT_STATUS(chip->base_addr), &bits); if (rc) { pr_err("Failed Configuring irq=%d pmirq=%d blk=%d rc=%d\n", irq, pmirq, block, rc); @@ -339,6 +418,7 @@ struct pm_irq_chip * __devinit pm8xxx_irq_init(struct device *dev, chip->devirq = devirq; chip->irq_base = pdata->irq_base; chip->num_irqs = pdata->irq_cdata.nirqs; + chip->base_addr = pdata->irq_cdata.base_addr; chip->num_blocks = DIV_ROUND_UP(chip->num_irqs, 8); chip->num_masters = DIV_ROUND_UP(chip->num_blocks, 8); spin_lock_init(&chip->pm_irq_lock); @@ -355,15 +435,22 @@ struct pm_irq_chip * __devinit pm8xxx_irq_init(struct device *dev, #endif } - 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); + if (devirq != 0) { + rc = request_irq(devirq, pm8xxx_irq_handler, + pdata->irq_trigger_flag, + "pm8xxx_usr_irq", chip); + if (rc) { + pr_err("failed to request_irq for %d rc=%d\n", + devirq, rc); + } else { + irq_set_irq_wake(devirq, 1); + } + } return chip; } -int __devexit pm8xxx_irq_exit(struct pm_irq_chip *chip) +int pm8xxx_irq_exit(struct pm_irq_chip *chip) { irq_set_chained_handler(chip->devirq, NULL); kfree(chip); diff --git a/drivers/mfd/pm8xxx-misc.c b/drivers/mfd/pm8xxx-misc.c new file mode 100644 index 0000000000000000000000000000000000000000..0af013ef2ce219cb8a06c399e74de21605adf351 --- /dev/null +++ b/drivers/mfd/pm8xxx-misc.c @@ -0,0 +1,1256 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* PON CTRL 1 register */ +#define REG_PM8XXX_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 + +/* PON CNTL registers */ +#define REG_PM8058_PON_CNTL_4 0x098 +#define REG_PM8901_PON_CNTL_4 0x099 +#define REG_PM8018_PON_CNTL_4 0x01E +#define REG_PM8921_PON_CNTL_4 0x01E +#define REG_PM8058_PON_CNTL_5 0x07B +#define REG_PM8901_PON_CNTL_5 0x09A +#define REG_PM8018_PON_CNTL_5 0x01F +#define REG_PM8921_PON_CNTL_5 0x01F + +#define PON_CTRL_4_RESET_EN_MASK 0x01 +#define PON_CTRL_4_SHUTDOWN_ON_RESET 0x0 +#define PON_CTRL_4_RESTART_ON_RESET 0x1 +#define PON_CTRL_5_HARD_RESET_EN_MASK 0x08 +#define PON_CTRL_5_HARD_RESET_EN 0x08 +#define PON_CTRL_5_HARD_RESET_DIS 0x00 + +/* Regulator master enable addresses */ +#define REG_PM8058_VREG_EN_MSM 0x018 +#define REG_PM8058_VREG_EN_GRP_5_4 0x1C8 + +/* Regulator control registers for shutdown/reset */ +#define REG_PM8058_S0_CTRL 0x004 +#define REG_PM8058_S1_CTRL 0x005 +#define REG_PM8058_S3_CTRL 0x111 +#define REG_PM8058_L21_CTRL 0x120 +#define REG_PM8058_L22_CTRL 0x121 + +#define PM8058_REGULATOR_ENABLE_MASK 0x80 +#define PM8058_REGULATOR_ENABLE 0x80 +#define PM8058_REGULATOR_DISABLE 0x00 +#define PM8058_REGULATOR_PULL_DOWN_MASK 0x40 +#define PM8058_REGULATOR_PULL_DOWN_EN 0x40 + +/* Buck CTRL register */ +#define PM8058_SMPS_LEGACY_VREF_SEL 0x20 +#define PM8058_SMPS_LEGACY_VPROG_MASK 0x1F +#define PM8058_SMPS_ADVANCED_BAND_MASK 0xC0 +#define PM8058_SMPS_ADVANCED_BAND_SHIFT 6 +#define PM8058_SMPS_ADVANCED_VPROG_MASK 0x3F + +/* Buck TEST2 registers for shutdown/reset */ +#define REG_PM8058_S0_TEST2 0x084 +#define REG_PM8058_S1_TEST2 0x085 +#define REG_PM8058_S3_TEST2 0x11A + +#define PM8058_REGULATOR_BANK_WRITE 0x80 +#define PM8058_REGULATOR_BANK_MASK 0x70 +#define PM8058_REGULATOR_BANK_SHIFT 4 +#define PM8058_REGULATOR_BANK_SEL(n) ((n) << PM8058_REGULATOR_BANK_SHIFT) + +/* Buck TEST2 register bank 1 */ +#define PM8058_SMPS_LEGACY_VLOW_SEL 0x01 + +/* Buck TEST2 register bank 7 */ +#define PM8058_SMPS_ADVANCED_MODE_MASK 0x02 +#define PM8058_SMPS_ADVANCED_MODE 0x02 +#define PM8058_SMPS_LEGACY_MODE 0x00 + +/* SLEEP CTRL register */ +#define REG_PM8058_SLEEP_CTRL 0x02B +#define REG_PM8921_SLEEP_CTRL 0x10A +#define REG_PM8018_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 + +#define SLEEP_CTRL_SMPL_SEL_MASK 0x03 +#define SLEEP_CTRL_SMPL_SEL_MIN 0 +#define SLEEP_CTRL_SMPL_SEL_MAX 3 + +/* 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 + +/* COINCELL CHG registers */ +#define REG_PM8058_COIN_CHG 0x02F +#define REG_PM8921_COIN_CHG 0x09C +#define REG_PM8018_COIN_CHG 0x09C + +#define COINCELL_RESISTOR_SHIFT 0x2 + +/* GP TEST register */ +#define REG_PM8XXX_GP_TEST_1 0x07A + +/* Stay on configuration */ +#define PM8XXX_STAY_ON_CFG 0x92 + +/* GPIO UART MUX CTRL registers */ +#define REG_PM8XXX_GPIO_MUX_CTRL 0x1CC + +#define UART_PATH_SEL_MASK 0x60 +#define UART_PATH_SEL_SHIFT 0x5 + +#define USB_ID_PU_EN_MASK 0x10 /* PM8921 family only */ +#define USB_ID_PU_EN_SHIFT 4 + +/* Shutdown/restart delays to allow for LDO 7/dVdd regulator load settling. */ +#define PM8901_DELAY_AFTER_REG_DISABLE_MS 4 +#define PM8901_DELAY_BEFORE_SHUTDOWN_MS 8 + +#define REG_PM8XXX_XO_CNTRL_2 0x114 +#define MP3_1_MASK 0xE0 +#define MP3_2_MASK 0x1C +#define MP3_1_SHIFT 5 +#define MP3_2_SHIFT 2 + +#define REG_HSED_BIAS0_CNTL2 0xA1 +#define REG_HSED_BIAS1_CNTL2 0x135 +#define REG_HSED_BIAS2_CNTL2 0x138 +#define HSED_EN_MASK 0xC0 + +struct pm8xxx_misc_chip { + struct list_head link; + struct pm8xxx_misc_platform_data pdata; + struct device *dev; + enum pm8xxx_version version; + u64 osc_halt_count; +}; + +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; +} + +/* + * Set an SMPS regulator to be disabled in its CTRL register, but enabled + * in the master enable register. Also set it's pull down enable bit. + * Take care to make sure that the output voltage doesn't change if switching + * from advanced mode to legacy mode. + */ +static int +__pm8058_disable_smps_locally_set_pull_down(struct pm8xxx_misc_chip *chip, + u16 ctrl_addr, u16 test2_addr, u16 master_enable_addr, + u8 master_enable_bit) +{ + int rc = 0; + u8 vref_sel, vlow_sel, band, vprog, bank, reg; + + bank = PM8058_REGULATOR_BANK_SEL(7); + rc = pm8xxx_writeb(chip->dev->parent, test2_addr, bank); + if (rc) { + pr_err("%s: pm8xxx_writeb(0x%03X) failed: rc=%d\n", __func__, + test2_addr, rc); + goto done; + } + + rc = pm8xxx_readb(chip->dev->parent, test2_addr, ®); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(0x%03X): rc=%d\n", + __func__, test2_addr, rc); + goto done; + } + + /* Check if in advanced mode. */ + if ((reg & PM8058_SMPS_ADVANCED_MODE_MASK) == + PM8058_SMPS_ADVANCED_MODE) { + /* Determine current output voltage. */ + rc = pm8xxx_readb(chip->dev->parent, ctrl_addr, ®); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(0x%03X): rc=%d\n", + __func__, ctrl_addr, rc); + goto done; + } + + band = (reg & PM8058_SMPS_ADVANCED_BAND_MASK) + >> PM8058_SMPS_ADVANCED_BAND_SHIFT; + switch (band) { + case 3: + vref_sel = 0; + vlow_sel = 0; + break; + case 2: + vref_sel = PM8058_SMPS_LEGACY_VREF_SEL; + vlow_sel = 0; + break; + case 1: + vref_sel = PM8058_SMPS_LEGACY_VREF_SEL; + vlow_sel = PM8058_SMPS_LEGACY_VLOW_SEL; + break; + default: + pr_err("%s: regulator already disabled\n", __func__); + return -EPERM; + } + vprog = (reg & PM8058_SMPS_ADVANCED_VPROG_MASK); + /* Round up if fine step is in use. */ + vprog = (vprog + 1) >> 1; + if (vprog > PM8058_SMPS_LEGACY_VPROG_MASK) + vprog = PM8058_SMPS_LEGACY_VPROG_MASK; + + /* Set VLOW_SEL bit. */ + bank = PM8058_REGULATOR_BANK_SEL(1); + rc = pm8xxx_writeb(chip->dev->parent, test2_addr, bank); + if (rc) { + pr_err("%s: FAIL pm8xxx_writeb(0x%03X): rc=%d\n", + __func__, test2_addr, rc); + goto done; + } + + rc = pm8xxx_misc_masked_write(chip, test2_addr, + PM8058_REGULATOR_BANK_WRITE | PM8058_REGULATOR_BANK_MASK + | PM8058_SMPS_LEGACY_VLOW_SEL, + PM8058_REGULATOR_BANK_WRITE | + PM8058_REGULATOR_BANK_SEL(1) | vlow_sel); + if (rc) + goto done; + + /* Switch to legacy mode */ + bank = PM8058_REGULATOR_BANK_SEL(7); + rc = pm8xxx_writeb(chip->dev->parent, test2_addr, bank); + if (rc) { + pr_err("%s: FAIL pm8xxx_writeb(0x%03X): rc=%d\n", + __func__, test2_addr, rc); + goto done; + } + rc = pm8xxx_misc_masked_write(chip, test2_addr, + PM8058_REGULATOR_BANK_WRITE | + PM8058_REGULATOR_BANK_MASK | + PM8058_SMPS_ADVANCED_MODE_MASK, + PM8058_REGULATOR_BANK_WRITE | + PM8058_REGULATOR_BANK_SEL(7) | + PM8058_SMPS_LEGACY_MODE); + if (rc) + goto done; + + /* Enable locally, enable pull down, keep voltage the same. */ + rc = pm8xxx_misc_masked_write(chip, ctrl_addr, + PM8058_REGULATOR_ENABLE_MASK | + PM8058_REGULATOR_PULL_DOWN_MASK | + PM8058_SMPS_LEGACY_VREF_SEL | + PM8058_SMPS_LEGACY_VPROG_MASK, + PM8058_REGULATOR_ENABLE | PM8058_REGULATOR_PULL_DOWN_EN + | vref_sel | vprog); + if (rc) + goto done; + } + + /* Enable in master control register. */ + rc = pm8xxx_misc_masked_write(chip, master_enable_addr, + master_enable_bit, master_enable_bit); + if (rc) + goto done; + + /* Disable locally and enable pull down. */ + rc = pm8xxx_misc_masked_write(chip, ctrl_addr, + PM8058_REGULATOR_ENABLE_MASK | PM8058_REGULATOR_PULL_DOWN_MASK, + PM8058_REGULATOR_DISABLE | PM8058_REGULATOR_PULL_DOWN_EN); + +done: + return rc; +} + +static int +__pm8058_disable_ldo_locally_set_pull_down(struct pm8xxx_misc_chip *chip, + u16 ctrl_addr, u16 master_enable_addr, u8 master_enable_bit) +{ + int rc; + + /* Enable LDO in master control register. */ + rc = pm8xxx_misc_masked_write(chip, master_enable_addr, + master_enable_bit, master_enable_bit); + if (rc) + goto done; + + /* Disable LDO in CTRL register and set pull down */ + rc = pm8xxx_misc_masked_write(chip, ctrl_addr, + PM8058_REGULATOR_ENABLE_MASK | PM8058_REGULATOR_PULL_DOWN_MASK, + PM8058_REGULATOR_DISABLE | PM8058_REGULATOR_PULL_DOWN_EN); + +done: + return rc; +} + +static int __pm8018_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_PM8018_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); + return rc; + } + + /* + * 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_PM8XXX_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); + + return rc; +} + +static int __pm8058_reset_pwr_off(struct pm8xxx_misc_chip *chip, int reset) +{ + int rc; + + /* When shutting down, enable active pulldowns on important rails. */ + if (!reset) { + /* Disable SMPS's 0,1,3 locally and set pulldown enable bits. */ + __pm8058_disable_smps_locally_set_pull_down(chip, + REG_PM8058_S0_CTRL, REG_PM8058_S0_TEST2, + REG_PM8058_VREG_EN_MSM, BIT(7)); + __pm8058_disable_smps_locally_set_pull_down(chip, + REG_PM8058_S1_CTRL, REG_PM8058_S1_TEST2, + REG_PM8058_VREG_EN_MSM, BIT(6)); + __pm8058_disable_smps_locally_set_pull_down(chip, + REG_PM8058_S3_CTRL, REG_PM8058_S3_TEST2, + REG_PM8058_VREG_EN_GRP_5_4, BIT(7) | BIT(4)); + /* Disable LDO 21 locally and set pulldown enable bit. */ + __pm8058_disable_ldo_locally_set_pull_down(chip, + REG_PM8058_L21_CTRL, REG_PM8058_VREG_EN_GRP_5_4, + BIT(1)); + } + + /* + * 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_PM8XXX_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; + } + mdelay(PM8901_DELAY_AFTER_REG_DISABLE_MS); + } + } + +read_write_err: + mdelay(PM8901_DELAY_BEFORE_SHUTDOWN_MS); + 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_PM8XXX_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_8018: + rc = __pm8018_reset_pwr_off(chip, reset); + break; + 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_8038: + case PM8XXX_VERSION_8917: + 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); + +/** + * pm8xxx_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 pm8xxx_smpl_control(int enable) +{ + 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_8018: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8018_SLEEP_CTRL, SLEEP_CTRL_SMPL_EN_MASK, + (enable ? SLEEP_CTRL_SMPL_EN_RESET + : SLEEP_CTRL_SMPL_EN_PWR_OFF)); + break; + case PM8XXX_VERSION_8058: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8058_SLEEP_CTRL, SLEEP_CTRL_SMPL_EN_MASK, + (enable ? SLEEP_CTRL_SMPL_EN_RESET + : SLEEP_CTRL_SMPL_EN_PWR_OFF)); + break; + case PM8XXX_VERSION_8921: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8921_SLEEP_CTRL, SLEEP_CTRL_SMPL_EN_MASK, + (enable ? SLEEP_CTRL_SMPL_EN_RESET + : SLEEP_CTRL_SMPL_EN_PWR_OFF)); + break; + default: + /* PMIC doesn't have reset_pwr_off; do nothing. */ + break; + } + if (rc) { + pr_err("setting smpl control failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_smpl_control); + + +/** + * pm8xxx_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 pm8xxx_smpl_set_delay(enum pm8xxx_smpl_delay delay) +{ + struct pm8xxx_misc_chip *chip; + unsigned long flags; + int rc = 0; + + if (delay < SLEEP_CTRL_SMPL_SEL_MIN + || delay > SLEEP_CTRL_SMPL_SEL_MAX) { + pr_err("%s: invalid delay specified: %d\n", __func__, delay); + return -EINVAL; + } + + 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_8018: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8018_SLEEP_CTRL, SLEEP_CTRL_SMPL_SEL_MASK, + delay); + break; + case PM8XXX_VERSION_8058: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8058_SLEEP_CTRL, SLEEP_CTRL_SMPL_SEL_MASK, + delay); + break; + case PM8XXX_VERSION_8921: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8921_SLEEP_CTRL, SLEEP_CTRL_SMPL_SEL_MASK, + delay); + break; + default: + /* PMIC doesn't have reset_pwr_off; do nothing. */ + break; + } + if (rc) { + pr_err("setting smpl delay failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_smpl_set_delay); + +/** + * pm8xxx_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 pm8xxx_coincell_chg_config(struct pm8xxx_coincell_chg *chg_config) +{ + struct pm8xxx_misc_chip *chip; + unsigned long flags; + u8 reg = 0, voltage, resistor; + int rc = 0; + + if (chg_config == NULL) { + pr_err("chg_config is NULL\n"); + return -EINVAL; + } + + voltage = chg_config->voltage; + resistor = chg_config->resistor; + + if (resistor < PM8XXX_COINCELL_RESISTOR_2100_OHMS || + resistor > PM8XXX_COINCELL_RESISTOR_800_OHMS) { + pr_err("Invalid resistor value provided\n"); + return -EINVAL; + } + + if (voltage < PM8XXX_COINCELL_VOLTAGE_3p2V || + (voltage > PM8XXX_COINCELL_VOLTAGE_3p0V && + voltage != PM8XXX_COINCELL_VOLTAGE_2p5V)) { + pr_err("Invalid voltage value provided\n"); + return -EINVAL; + } + + if (chg_config->state == PM8XXX_COINCELL_CHG_DISABLE) { + reg = 0; + } else { + reg |= voltage; + reg |= (resistor << COINCELL_RESISTOR_SHIFT); + } + + 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_8018: + rc = pm8xxx_writeb(chip->dev->parent, + REG_PM8018_COIN_CHG, reg); + break; + case PM8XXX_VERSION_8058: + rc = pm8xxx_writeb(chip->dev->parent, + REG_PM8058_COIN_CHG, reg); + break; + case PM8XXX_VERSION_8921: + rc = pm8xxx_writeb(chip->dev->parent, + REG_PM8921_COIN_CHG, reg); + break; + default: + /* PMIC doesn't have reset_pwr_off; do nothing. */ + break; + } + if (rc) { + pr_err("coincell chg. config failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_coincell_chg_config); + +/** + * pm8xxx_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 pm8xxx_watchdog_reset_control(int enable) +{ + 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_8018: + case PM8XXX_VERSION_8058: + case PM8XXX_VERSION_8921: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8XXX_PON_CTRL_1, PON_CTRL_1_WD_EN_MASK, + (enable ? PON_CTRL_1_WD_EN_RESET + : PON_CTRL_1_WD_EN_PWR_OFF)); + break; + default: + /* WD reset control not supported */ + break; + } + if (rc) { + pr_err("setting WD reset control failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_watchdog_reset_control); + +/** + * pm8xxx_stay_on - enables stay_on feature + * + * PMIC stay-on feature allows PMIC to ignore MSM PS_HOLD=low + * signal so that some special functions like debugging could be + * performed. + * + * This feature should not be used in any product release. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_stay_on(void) +{ + 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_8018: + case PM8XXX_VERSION_8058: + case PM8XXX_VERSION_8921: + rc = pm8xxx_writeb(chip->dev->parent, + REG_PM8XXX_GP_TEST_1, PM8XXX_STAY_ON_CFG); + break; + default: + /* stay on not supported */ + break; + } + if (rc) { + pr_err("stay_on failed failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_stay_on); + +static int +__pm8xxx_hard_reset_config(struct pm8xxx_misc_chip *chip, + enum pm8xxx_pon_config config, u16 pon4_addr, u16 pon5_addr) +{ + int rc = 0; + + switch (config) { + case PM8XXX_DISABLE_HARD_RESET: + rc = pm8xxx_misc_masked_write(chip, pon5_addr, + PON_CTRL_5_HARD_RESET_EN_MASK, + PON_CTRL_5_HARD_RESET_DIS); + break; + case PM8XXX_SHUTDOWN_ON_HARD_RESET: + rc = pm8xxx_misc_masked_write(chip, pon5_addr, + PON_CTRL_5_HARD_RESET_EN_MASK, + PON_CTRL_5_HARD_RESET_EN); + if (!rc) { + rc = pm8xxx_misc_masked_write(chip, pon4_addr, + PON_CTRL_4_RESET_EN_MASK, + PON_CTRL_4_SHUTDOWN_ON_RESET); + } + break; + case PM8XXX_RESTART_ON_HARD_RESET: + rc = pm8xxx_misc_masked_write(chip, pon5_addr, + PON_CTRL_5_HARD_RESET_EN_MASK, + PON_CTRL_5_HARD_RESET_EN); + if (!rc) { + rc = pm8xxx_misc_masked_write(chip, pon4_addr, + PON_CTRL_4_RESET_EN_MASK, + PON_CTRL_4_RESTART_ON_RESET); + } + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +/** + * pm8xxx_hard_reset_config - Allows different reset configurations + * + * config = PM8XXX_DISABLE_HARD_RESET to disable hard reset + * = PM8XXX_SHUTDOWN_ON_HARD_RESET to turn off the system on hard reset + * = PM8XXX_RESTART_ON_HARD_RESET to restart the system on hard reset + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_hard_reset_config(enum pm8xxx_pon_config config) +{ + 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_8018: + __pm8xxx_hard_reset_config(chip, config, + REG_PM8018_PON_CNTL_4, REG_PM8018_PON_CNTL_5); + break; + case PM8XXX_VERSION_8058: + __pm8xxx_hard_reset_config(chip, config, + REG_PM8058_PON_CNTL_4, REG_PM8058_PON_CNTL_5); + break; + case PM8XXX_VERSION_8901: + __pm8xxx_hard_reset_config(chip, config, + REG_PM8901_PON_CNTL_4, REG_PM8901_PON_CNTL_5); + break; + case PM8XXX_VERSION_8921: + __pm8xxx_hard_reset_config(chip, config, + REG_PM8921_PON_CNTL_4, REG_PM8921_PON_CNTL_5); + break; + default: + /* hard reset config. no supported */ + break; + } + if (rc) { + pr_err("hard reset config. failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_hard_reset_config); + +/* Handle the OSC_HALT interrupt: 32 kHz XTAL oscillator has stopped. */ +static irqreturn_t pm8xxx_osc_halt_isr(int irq, void *data) +{ + struct pm8xxx_misc_chip *chip = data; + u64 count = 0; + + if (chip) { + chip->osc_halt_count++; + count = chip->osc_halt_count; + } + + pr_crit("%s: OSC_HALT interrupt has triggered, 32 kHz XTAL oscillator" + " has halted (%llu)!\n", __func__, count); + + return IRQ_HANDLED; +} + +/** + * pm8xxx_uart_gpio_mux_ctrl - Mux configuration to select the UART + * + * @uart_path_sel: Input argument to select either UART1/2/3 + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_uart_gpio_mux_ctrl(enum pm8xxx_uart_path_sel uart_path_sel) +{ + 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_8018: + case PM8XXX_VERSION_8058: + case PM8XXX_VERSION_8921: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8XXX_GPIO_MUX_CTRL, UART_PATH_SEL_MASK, + uart_path_sel << UART_PATH_SEL_SHIFT); + break; + default: + /* Functionality not supported */ + break; + } + if (rc) { + pr_err("uart_gpio_mux_ctrl failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_uart_gpio_mux_ctrl); + +/** + * pm8xxx_usb_id_pullup - Control a pullup for USB ID + * + * @enable: enable (1) or disable (0) the pullup + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_usb_id_pullup(int enable) +{ + struct pm8xxx_misc_chip *chip; + unsigned long flags; + int rc = -ENXIO; + + 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_8921: + case PM8XXX_VERSION_8922: + case PM8XXX_VERSION_8917: + case PM8XXX_VERSION_8038: + rc = pm8xxx_misc_masked_write(chip, + REG_PM8XXX_GPIO_MUX_CTRL, USB_ID_PU_EN_MASK, + enable << USB_ID_PU_EN_SHIFT); + + if (rc) + pr_err("Fail: reg=%x, rc=%d\n", + REG_PM8XXX_GPIO_MUX_CTRL, rc); + break; + default: + /* Functionality not supported */ + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_usb_id_pullup); + +static int __pm8901_preload_dVdd(struct pm8xxx_misc_chip *chip) +{ + int rc; + + /* dVdd preloading is not needed for PMIC PM8901 rev 2.3 and beyond. */ + if (pm8xxx_get_revision(chip->dev->parent) >= PM8XXX_REVISION_8901_2p3) + return 0; + + rc = pm8xxx_writeb(chip->dev->parent, 0x0BD, 0x0F); + if (rc) + pr_err("pm8xxx_writeb failed for 0x0BD, rc=%d\n", rc); + + rc = pm8xxx_writeb(chip->dev->parent, 0x001, 0xB4); + if (rc) + pr_err("pm8xxx_writeb failed for 0x001, rc=%d\n", rc); + + pr_info("dVdd preloaded\n"); + + return rc; +} + +/** + * pm8xxx_preload_dVdd - preload the dVdd regulator during off state. + * + * This can help to reduce fluctuations in the dVdd voltage during startup + * at the cost of additional off state current draw. + * + * This API should only be called if dVdd startup issues are suspected. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_preload_dVdd(void) +{ + 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_8901: + rc = __pm8901_preload_dVdd(chip); + break; + default: + /* PMIC doesn't have preload_dVdd; do nothing. */ + break; + } + if (rc) { + pr_err("preload_dVdd failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_preload_dVdd); + +int pm8xxx_aux_clk_control(enum pm8xxx_aux_clk_id clk_id, + enum pm8xxx_aux_clk_div divider, bool enable) +{ + struct pm8xxx_misc_chip *chip; + unsigned long flags; + u8 clk_mask = 0, value = 0; + + if (clk_id == CLK_MP3_1) { + clk_mask = MP3_1_MASK; + value = divider << MP3_1_SHIFT; + } else if (clk_id == CLK_MP3_2) { + clk_mask = MP3_2_MASK; + value = divider << MP3_2_SHIFT; + } else { + pr_err("Invalid clock id of %d\n", clk_id); + return -EINVAL; + } + if (!enable) + value = 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_8038: + case PM8XXX_VERSION_8921: + pm8xxx_misc_masked_write(chip, + REG_PM8XXX_XO_CNTRL_2, clk_mask, value); + break; + default: + /* Functionality not supported */ + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pm8xxx_aux_clk_control); + +int pm8xxx_hsed_bias_control(enum pm8xxx_hsed_bias bias, bool enable) +{ + struct pm8xxx_misc_chip *chip; + unsigned long flags; + int rc = 0; + u16 addr; + + switch (bias) { + case PM8XXX_HSED_BIAS0: + addr = REG_HSED_BIAS0_CNTL2; + break; + case PM8XXX_HSED_BIAS1: + addr = REG_HSED_BIAS1_CNTL2; + break; + case PM8XXX_HSED_BIAS2: + addr = REG_HSED_BIAS2_CNTL2; + break; + default: + pr_err("Invalid BIAS line\n"); + return -EINVAL; + } + + 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: + case PM8XXX_VERSION_8921: + rc = pm8xxx_misc_masked_write(chip, addr, + HSED_EN_MASK, enable ? HSED_EN_MASK : 0); + if (rc < 0) + pr_err("Enable HSED BIAS failed rc=%d\n", rc); + break; + default: + /* Functionality not supported */ + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_hsed_bias_control); + +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, irq; + + 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)); + + irq = platform_get_irq_byname(pdev, "pm8xxx_osc_halt_irq"); + if (irq > 0) { + rc = request_any_context_irq(irq, pm8xxx_osc_halt_isr, + IRQF_TRIGGER_RISING | IRQF_DISABLED, + "pm8xxx_osc_halt_irq", chip); + if (rc < 0) { + pr_err("%s: request_any_context_irq(%d) FAIL: %d\n", + __func__, irq, rc); + goto fail_irq; + } + } + + /* 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; + +fail_irq: + platform_set_drvdata(pdev, NULL); + kfree(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; + int irq = platform_get_irq_byname(pdev, "pm8xxx_osc_halt_irq"); + if (irq > 0) + free_irq(irq, chip); + + 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 0000000000000000000000000000000000000000..022cfb6f4b3b0f922d1c0d008e698d9b7542c340 --- /dev/null +++ b/drivers/mfd/pm8xxx-pwm.c @@ -0,0 +1,1471 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_PWM_CHANNELS 3 + +/* + * For the lack of better term to distinguish functional + * differences, hereby, LPG version 0 (V0, v0) denotes + * PM8058/8921, and version 1 (V1, v1) denotes + * PM8922/8038. + */ +#define PM8XXX_LPG_V0_PWM_CHANNELS 8 +#define PM8XXX_LPG_V1_PWM_CHANNELS 6 +#define PM8XXX_LPG_CTL_REGS 7 + +/* PM8XXX PWM */ +#define SSBI_REG_ADDR_PWM1_CTRL1 0x88 +#define SSBI_REG_ADDR_PWM1_CTRL2 0x89 +#define SSBI_REG_ADDR_PWM_CTL(id, base) (id == 0 ? base : (base + (id << 1))) +#define SSBI_REG_ADDR_PWM_CTL1(id) SSBI_REG_ADDR_PWM_CTL(id, \ + SSBI_REG_ADDR_PWM1_CTRL1) +#define SSBI_REG_ADDR_PWM_CTL2(id) SSBI_REG_ADDR_PWM_CTL(id, \ + SSBI_REG_ADDR_PWM1_CTRL2) + +#define PM8XXX_PWM_CLK_SEL_SHIFT 6 +#define PM8XXX_PWM_CLK_SEL_MASK 0xC0 +#define PM8XXX_PWM_PREDIVIDE_SHIFT 5 +#define PM8XXX_PWM_PREDIVIDE_MASK 0x20 +#define PM8XXX_PWM_M_SHIFT 2 +#define PM8XXX_PWM_M_MASK 0x1C +#define PM8XXX_PWM_SIZE_SHIFT 1 +#define PM8XXX_PWM_SIZE_MASK 0x02 +#define PM8XXX_PWM_VALUE_BIT0 0x01 +#define PM8XXX_PWM_DISABLE 0x3F + +/* PM8XXX 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 + +/* LPG 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) + +/* LPG Control 1 */ +#define PM8XXX_PWM_REVERSE_EN 0x80 +#define PM8XXX_PWM_BYPASS_LUT 0x40 +#define PM8XXX_PWM_HIGH_INDEX_MASK 0x3F + +/* LPG Control 2 */ +#define PM8XXX_PWM_LOOP_EN 0x80 +#define PM8XXX_PWM_RAMP_UP 0x40 +#define PM8XXX_PWM_LOW_INDEX_MASK 0x3F + +/* LPG Control 3 */ +#define PM8XXX_PWM_VALUE_BIT7_0 0xFF +#define PM8XXX_PWM_VALUE_BIT5_0 0x3F + +/* LPG Control 4 */ +#define PM8XXX_PWM_VALUE_BIT8 0x80 + +#define PM8XXX_LPG_PWM_CLK_SEL_MASK 0x60 +#define PM8XXX_LPG_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_LPG_PWM_PREDIVIDE_MASK 0x18 +#define PM8XXX_LPG_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_LPG_PWM_M_MASK 0x07 +#define PM8XXX_PWM_M_MIN 0 +#define PM8XXX_PWM_M_MAX 7 + +/* LPG 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 + +/* LPG 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 */ + +/* LPG LUT_CFG1 */ +#define PM8XXX_PWM_LUT_READ 0x40 + +/* TEST */ +#define PM8XXX_PWM_DTEST_MASK 0x38 +#define PM8XXX_PWM_DTEST_SHIFT 3 +#define PM8XXX_PWM_DTEST_BANK_MASK 0x07 + +/* + * 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) = (Pre-divide * Clock Period) * 2^m + */ +#define NUM_CLOCKS 3 + +#define NSEC_1024HZ (NSEC_PER_SEC / 1024) +#define NSEC_32768HZ (NSEC_PER_SEC / 32768) +#define NSEC_19P2MHZ (NSEC_PER_SEC / 19200000) + +#define NUM_LPG_PRE_DIVIDE 4 +#define NUM_PWM_PRE_DIVIDE 2 + +#define PRE_DIVIDE_1 1 /* v1 */ +#define PRE_DIVIDE_2 2 +#define PRE_DIVIDE_3 3 +#define PRE_DIVIDE_5 5 +#define PRE_DIVIDE_6 6 + +static unsigned int pt_t[NUM_LPG_PRE_DIVIDE][NUM_CLOCKS] = { + { PRE_DIVIDE_2 * NSEC_1024HZ, + PRE_DIVIDE_2 * NSEC_32768HZ, + PRE_DIVIDE_2 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_3 * NSEC_1024HZ, + PRE_DIVIDE_3 * NSEC_32768HZ, + PRE_DIVIDE_3 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_5 * NSEC_1024HZ, + PRE_DIVIDE_5 * NSEC_32768HZ, + PRE_DIVIDE_5 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_6 * NSEC_1024HZ, + PRE_DIVIDE_6 * NSEC_32768HZ, + PRE_DIVIDE_6 * NSEC_19P2MHZ, + }, +}; + +/* Private data */ +struct pm8xxx_pwm_chip; + +struct pwm_device { + int pwm_id; /* = bank/channel id */ + int in_use; + const char *label; + struct pm8xxx_pwm_period period; + int pwm_value; + int pwm_period; + int pwm_duty; + u8 pwm_lpg_ctl[PM8XXX_LPG_CTL_REGS]; + u8 pwm_ctl1; + u8 pwm_ctl2; + int irq; + struct pm8xxx_pwm_chip *chip; + int bypass_lut; + int dtest_mode_supported; +}; + +struct pm8xxx_pwm_chip { + struct pwm_device *pwm_dev; + u8 pwm_channels; + u8 pwm_total_pre_divs; + u8 bank_mask; + struct mutex pwm_mutex; + struct device *dev; + bool is_lpg_supported; +}; + +static struct pm8xxx_pwm_chip *pwm_chip; + +struct pm8xxx_pwm_lut { + /* LUT parameters */ + 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 void pm8xxx_pwm_save(u8 *u8p, u8 mask, u8 val) +{ + *u8p &= ~mask; + *u8p |= val & mask; +} + +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_writeb(): 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_writeb(): 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_lpg_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_lpg_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_writeb(): rc=%d (Enable PWM Ctl 0)\n", rc); + else + pwm->pwm_lpg_ctl[0] = reg; + return rc; +} + +static int pm8xxx_pwm_disable(struct pwm_device *pwm) +{ + int rc; + u8 reg; + + reg = pwm->pwm_ctl1 & PM8XXX_PWM_DISABLE; + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_PWM_CTL1(pwm->pwm_id), reg); + + if (rc) + pr_err("pm8xxx_writeb(): rc=%d (Disable PWM Ctl %d)\n", rc, + pwm->pwm_id); + return rc; +} + +static int pm8xxx_pwm_enable(struct pwm_device *pwm) +{ + /** + * A kind of best Effort: Just write the clock information that + * we have in the register. + */ + int rc; + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_PWM_CTL1(pwm->pwm_id), pwm->pwm_ctl1); + + if (rc) + pr_err("pm8xxx_writeb(): rc=%d (Enable PWM Ctl %d)\n", rc, + pwm->pwm_id); + return rc; +} + +static void pm8xxx_pwm_calc_period(unsigned int period_us, + struct pm8xxx_pwm_period *period) +{ + int n, m, clk, div; + int best_m, best_div, best_clk; + unsigned int last_err, cur_err, min_err; + unsigned int tmp_p, period_n; + + /* PWM Period / N */ + if (period_us < ((unsigned)(-1) / NSEC_PER_USEC)) { + period_n = (period_us * NSEC_PER_USEC) >> 6; + n = 6; + } else { + period_n = (period_us >> 9) * NSEC_PER_USEC; + n = 9; + } + + min_err = last_err = (unsigned)(-1); + best_m = 0; + best_clk = 0; + best_div = 0; + for (clk = 0; clk < NUM_CLOCKS; clk++) { + for (div = 0; div < pwm_chip->pwm_total_pre_divs; div++) { + /* period_n = (PWM Period / N) */ + /* tmp_p = (Pre-divide * Clock Period) * 2^m */ + tmp_p = pt_t[div][clk]; + for (m = 0; m <= PM8XXX_PWM_M_MAX; m++) { + if (period_n > tmp_p) + cur_err = period_n - tmp_p; + else + cur_err = tmp_p - period_n; + + if (cur_err < min_err) { + min_err = cur_err; + best_m = m; + best_clk = clk; + best_div = div; + } + + if (m && cur_err > last_err) + /* Break for bigger cur_err */ + break; + + last_err = cur_err; + tmp_p <<= 1; + } + } + } + + /* Use higher resolution */ + if (best_m >= 3 && n == 6) { + n += 3; + best_m -= 3; + } + + period->pwm_size = n; + period->clk = best_clk; + period->pre_div = best_div; + period->pre_div_exp = best_m; +} + +static void pm8xxx_pwm_calc_pwm_value(struct pwm_device *pwm, + unsigned int period_us, + unsigned int duty_us) +{ + unsigned int max_pwm_value, tmp; + + /* Figure out pwm_value with overflow handling */ + tmp = 1 << (sizeof(tmp) * 8 - pwm->period.pwm_size); + if (duty_us < tmp) { + tmp = duty_us << pwm->period.pwm_size; + pwm->pwm_value = tmp / period_us; + } else { + tmp = period_us >> pwm->period.pwm_size; + pwm->pwm_value = duty_us / tmp; + } + max_pwm_value = (1 << pwm->period.pwm_size) - 1; + if (pwm->pwm_value > max_pwm_value) + pwm->pwm_value = max_pwm_value; +} + +static int pm8xxx_pwm_change_table(struct pwm_device *pwm, int duty_pct[], + int start_idx, int len, int raw_value) +{ + unsigned int pwm_value, max_pwm_value; + u8 cfg0, cfg1; + int i, pwm_size; + int rc = 0; + + pwm_size = (pwm->pwm_lpg_ctl[5] & PM8XXX_PWM_SIZE_9_BIT) ? 9 : 6; + max_pwm_value = (1 << pwm_size) - 1; + for (i = 0; i < len; i++) { + if (raw_value) + pwm_value = duty_pct[i]; + else + pwm_value = (duty_pct[i] << pwm_size) / 100; + + if (pwm_value > max_pwm_value) + pwm_value = max_pwm_value; + cfg0 = pwm_value; + cfg1 = (pwm_value >> 1) & 0x80; + cfg1 |= start_idx + i; + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_LUT_CFG0, cfg0); + if (rc) + break; + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_LUT_CFG1, cfg1); + if (rc) + break; + } + return rc; +} + +static void pm8xxx_pwm_save_index(struct pwm_device *pwm, + int low_idx, int high_idx, int flags) +{ + pwm->pwm_lpg_ctl[1] = high_idx & PM8XXX_PWM_HIGH_INDEX_MASK; + pwm->pwm_lpg_ctl[2] = low_idx & PM8XXX_PWM_LOW_INDEX_MASK; + + if (flags & PM_PWM_LUT_REVERSE) + pwm->pwm_lpg_ctl[1] |= PM8XXX_PWM_REVERSE_EN; + if (flags & PM_PWM_LUT_RAMP_UP) + pwm->pwm_lpg_ctl[2] |= PM8XXX_PWM_RAMP_UP; + if (flags & PM_PWM_LUT_LOOP) + pwm->pwm_lpg_ctl[2] |= PM8XXX_PWM_LOOP_EN; +} + +static void pm8xxx_pwm_save_period(struct pwm_device *pwm) +{ + u8 mask, val; + + if (pwm_chip->is_lpg_supported) { + val = ((pwm->period.clk + 1) << PM8XXX_LPG_PWM_CLK_SEL_SHIFT) + & PM8XXX_LPG_PWM_CLK_SEL_MASK; + val |= (pwm->period.pre_div << PM8XXX_LPG_PWM_PREDIVIDE_SHIFT) + & PM8XXX_LPG_PWM_PREDIVIDE_MASK; + val |= pwm->period.pre_div_exp & PM8XXX_LPG_PWM_M_MASK; + mask = PM8XXX_LPG_PWM_CLK_SEL_MASK | + PM8XXX_LPG_PWM_PREDIVIDE_MASK | PM8XXX_LPG_PWM_M_MASK; + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[4], mask, val); + + val = (pwm->period.pwm_size > 6) ? PM8XXX_PWM_SIZE_9_BIT : 0; + mask = PM8XXX_PWM_SIZE_9_BIT; + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[5], mask, val); + } else { + val = ((pwm->period.clk + 1) << PM8XXX_PWM_CLK_SEL_SHIFT) + & PM8XXX_PWM_CLK_SEL_MASK; + val |= (pwm->period.pre_div << PM8XXX_PWM_PREDIVIDE_SHIFT) + & PM8XXX_PWM_PREDIVIDE_MASK; + val |= (pwm->period.pre_div_exp << PM8XXX_PWM_M_SHIFT) + & PM8XXX_PWM_M_MASK; + val |= (((pwm->period.pwm_size > 6) ? PM8XXX_PWM_SIZE_9_BIT : 0) + << PM8XXX_PWM_SIZE_SHIFT) & PM8XXX_PWM_SIZE_MASK; + + mask = PM8XXX_PWM_CLK_SEL_MASK | PM8XXX_PWM_PREDIVIDE_MASK | + PM8XXX_PWM_M_MASK | PM8XXX_PWM_SIZE_MASK; + pm8xxx_pwm_save(&pwm->pwm_ctl1, mask, val); + } +} + +static void pm8xxx_pwm_save_pwm_value(struct pwm_device *pwm) +{ + u8 mask, val; + + if (pwm_chip->is_lpg_supported) { + val = (pwm->period.pwm_size > 6) ? (pwm->pwm_value >> 1) : 0; + pwm->pwm_lpg_ctl[3] = pwm->pwm_value; + mask = PM8XXX_PWM_VALUE_BIT8; + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[4], mask, val); + } else { + val = (pwm->period.pwm_size > 6) ? (pwm->pwm_value >> 8) : 0; + pwm->pwm_ctl2 = pwm->pwm_value; + mask = PM8XXX_PWM_VALUE_BIT0; + pm8xxx_pwm_save(&pwm->pwm_ctl1, mask, val); + } +} + +static void pm8xxx_pwm_save_duty_time(struct pwm_device *pwm, + struct pm8xxx_pwm_lut *lut) +{ + int i; + u8 mask, val; + + /* Linear search for duty time */ + for (i = 0; i < PM8XXX_PWM_1KHZ_COUNT_MAX; i++) { + if (duty_msec[i] >= lut->lut_duty_ms) + break; + } + val = i << PM8XXX_PWM_1KHZ_COUNT_SHIFT; + + mask = PM8XXX_PWM_1KHZ_COUNT_MASK; + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[0], mask, val); +} + +static void pm8xxx_pwm_save_pause(struct pwm_device *pwm, + struct pm8xxx_pwm_lut *lut) +{ + int i, pause_cnt, time_cnt; + u8 mask, val; + + time_cnt = (pwm->pwm_lpg_ctl[0] & PM8XXX_PWM_1KHZ_COUNT_MASK) + >> PM8XXX_PWM_1KHZ_COUNT_SHIFT; + if (lut->flags & PM_PWM_LUT_PAUSE_HI_EN) { + pause_cnt = (lut->lut_pause_hi + duty_msec[time_cnt] / 2) + / duty_msec[time_cnt]; + /* Linear search for pause time */ + for (i = 0; i < PM8XXX_PWM_PAUSE_COUNT_MAX; i++) { + if (pause_count[i] >= pause_cnt) + break; + } + val = (i << PM8XXX_PWM_PAUSE_COUNT_HI_SHIFT) & + PM8XXX_PWM_PAUSE_COUNT_HI_MASK; + val |= PM8XXX_PWM_PAUSE_ENABLE_HIGH; + } else { + val = 0; + } + + mask = PM8XXX_PWM_PAUSE_COUNT_HI_MASK | PM8XXX_PWM_PAUSE_ENABLE_HIGH; + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[5], mask, val); + + if (lut->flags & PM_PWM_LUT_PAUSE_LO_EN) { + /* Linear search for pause time */ + pause_cnt = (lut->lut_pause_lo + duty_msec[time_cnt] / 2) + / duty_msec[time_cnt]; + for (i = 0; i < PM8XXX_PWM_PAUSE_COUNT_MAX; i++) { + if (pause_count[i] >= pause_cnt) + break; + } + val = (i << PM8XXX_PWM_PAUSE_COUNT_LO_SHIFT) & + PM8XXX_PWM_PAUSE_COUNT_LO_MASK; + val |= PM8XXX_PWM_PAUSE_ENABLE_LOW; + } else { + val = 0; + } + + mask = PM8XXX_PWM_PAUSE_COUNT_LO_MASK | PM8XXX_PWM_PAUSE_ENABLE_LOW; + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[6], mask, val); +} + +static int pm8xxx_pwm_write(struct pwm_device *pwm) +{ + int rc = 0; + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_PWM_CTL1(pwm->pwm_id), + pwm->pwm_ctl1); + if (rc) { + pr_err("pm8xxx_writeb() failed: rc=%d (PWM Ctl1[%d])\n", + rc, pwm->pwm_id); + return rc; + } + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_PWM_CTL2(pwm->pwm_id), + pwm->pwm_ctl2); + if (rc) { + pr_err("pm8xxx_writeb() failed: rc=%d (PWM Ctl2[%d])\n", + rc, pwm->pwm_id); + return rc; + } + + return rc; +} + +static int pm8xxx_lpg_pwm_write(struct pwm_device *pwm, int start, int end) +{ + int i, rc; + + /* Write in reverse way so 0 would be the last */ + for (i = end - 1; i >= start; i--) { + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_CTL(i), + pwm->pwm_lpg_ctl[i]); + if (rc) { + pr_err("pm8xxx_writeb(): rc=%d (PWM Ctl[%d])\n", rc, i); + return rc; + } + } + + return 0; +} + +static int pm8xxx_pwm_change_lut(struct pwm_device *pwm, + struct pm8xxx_pwm_lut *lut) +{ + int rc; + + pm8xxx_pwm_save_index(pwm, lut->lut_lo_index, + lut->lut_hi_index, lut->flags); + pm8xxx_pwm_save_duty_time(pwm, lut); + pm8xxx_pwm_save_pause(pwm, lut); + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[1], PM8XXX_PWM_BYPASS_LUT, 0); + + pm8xxx_pwm_bank_sel(pwm); + rc = pm8xxx_lpg_pwm_write(pwm, 0, 7); + + return rc; +} + +static int pm8xxx_pwm_set_dtest(struct pwm_device *pwm, int enable) +{ + int rc; + u8 reg; + + reg = pwm->pwm_id & PM8XXX_PWM_DTEST_BANK_MASK; + + if (enable) { + /* Observe LPG_OUT on DTEST1*/ + reg |= (1 << PM8XXX_PWM_DTEST_SHIFT) & + PM8XXX_PWM_DTEST_MASK; + } + + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_TEST, reg); + if (rc) + pr_err("pm8xxx_write(DTEST=0x%x) failed: rc=%d\n", + reg, rc); + + 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_chip == NULL) { + pr_err("No pwm_chip\n"); + return ERR_PTR(-ENODEV); + } + + if (pwm_id >= pwm_chip->pwm_channels || pwm_id < 0) { + pr_err("Invalid pwm_id: %d with %s\n", + pwm_id, label ? label : "."); + return ERR_PTR(-EINVAL); + } + + 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) { + if (pwm_chip->is_lpg_supported) { + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 0, 0); + } else { + pm8xxx_pwm_disable(pwm); + } + pwm->in_use = 0; + pwm->label = NULL; + } + if (pwm_chip->is_lpg_supported) + 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_period *period; + int rc = 0; + + 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; + } + + period = &pwm->period; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + rc = -EINVAL; + goto out_unlock; + } + + if (pwm->pwm_period != period_us) { + pm8xxx_pwm_calc_period(period_us, period); + pm8xxx_pwm_save_period(pwm); + pwm->pwm_period = period_us; + } + + pm8xxx_pwm_calc_pwm_value(pwm, period_us, duty_us); + pm8xxx_pwm_save_pwm_value(pwm); + + if (pwm_chip->is_lpg_supported) { + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[1], + PM8XXX_PWM_BYPASS_LUT, PM8XXX_PWM_BYPASS_LUT); + + pm8xxx_pwm_bank_sel(pwm); + rc = pm8xxx_lpg_pwm_write(pwm, 1, 6); + } else { + rc = pm8xxx_pwm_write(pwm); + } + + pr_debug("duty/period=%u/%u usec: pwm_value=%d (of %d)\n", + (unsigned)duty_us, (unsigned)period_us, + pwm->pwm_value, 1 << period->pwm_size); + +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 = 0; + + 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 { + if (pwm_chip->is_lpg_supported) { + if (pwm->dtest_mode_supported) + pm8xxx_pwm_set_dtest(pwm, 1); + rc = pm8xxx_pwm_bank_enable(pwm, 1); + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 1, 0); + } else { + pm8xxx_pwm_enable(pwm); + } + } + 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) { + if (pwm_chip->is_lpg_supported) { + if (pwm->dtest_mode_supported) + pm8xxx_pwm_set_dtest(pwm, 0); + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 0, 0); + pm8xxx_pwm_bank_enable(pwm, 0); + } else { + pm8xxx_pwm_disable(pwm); + } + } + mutex_unlock(&pwm->chip->pwm_mutex); +} +EXPORT_SYMBOL_GPL(pwm_disable); + +/** + * pm8xxx_pwm_config_period - change PWM period + * + * @pwm: the PWM device + * @pwm_p: period in struct pm8xxx_pwm_period + */ +int pm8xxx_pwm_config_period(struct pwm_device *pwm, + struct pm8xxx_pwm_period *period) +{ + int rc; + + if (pwm == NULL || IS_ERR(pwm) || period == NULL) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + rc = -EINVAL; + goto out_unlock; + } + + pwm->period.pwm_size = period->pwm_size; + pwm->period.clk = period->clk; + pwm->period.pre_div = period->pre_div; + pwm->period.pre_div_exp = period->pre_div_exp; + + pm8xxx_pwm_save_period(pwm); + + if (pwm_chip->is_lpg_supported) { + pm8xxx_pwm_bank_sel(pwm); + rc = pm8xxx_lpg_pwm_write(pwm, 4, 6); + } else { + rc = pm8xxx_pwm_write(pwm); + } + + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL(pm8xxx_pwm_config_period); + +/** + * pm8xxx_pwm_config_pwm_value - change a PWM device configuration + * @pwm: the PWM device + * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size) + */ +int pm8xxx_pwm_config_pwm_value(struct pwm_device *pwm, int pwm_value) +{ + int rc = 0; + + if (pwm == NULL || IS_ERR(pwm)) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use || !pwm->pwm_period) { + rc = -EINVAL; + goto out_unlock; + } + + if (pwm->pwm_value == pwm_value) + goto out_unlock; + + pwm->pwm_value = pwm_value; + + pm8xxx_pwm_save_pwm_value(pwm); + + if (pwm_chip->is_lpg_supported) { + pm8xxx_pwm_save(&pwm->pwm_lpg_ctl[1], + PM8XXX_PWM_BYPASS_LUT, PM8XXX_PWM_BYPASS_LUT); + pm8xxx_pwm_bank_sel(pwm); + rc = pm8xxx_lpg_pwm_write(pwm, 1, 6); + } else { + rc = pm8xxx_pwm_write(pwm); + } + + if (rc) + pr_err("[%d]: pm8xxx_pwm_write: rc=%d\n", pwm->pwm_id, rc); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_pwm_config_pwm_value); + +/** + * 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_lut lut; + struct pm8xxx_pwm_period *period; + int 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 (pwm->chip->is_lpg_supported == 0) { + pr_err("LPG module isn't supported\n"); + return -EINVAL; + } + + 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; + } + + period = &pwm->period; + 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; + } + + if (pwm->pwm_period != period_us) { + pm8xxx_pwm_calc_period(period_us, period); + pm8xxx_pwm_save_period(pwm); + pwm->pwm_period = period_us; + } + + len = (idx_len > PM_PWM_LUT_SIZE) ? PM_PWM_LUT_SIZE : idx_len; + + if (flags & PM_PWM_LUT_NO_TABLE) + goto after_table_write; + + rc = pm8xxx_pwm_change_table(pwm, duty_pct, start_idx, len, 0); + if (rc) { + pr_err("pm8xxx_pwm_change_table: rc=%d\n", rc); + goto out_unlock; + } + +after_table_write: + lut.lut_duty_ms = duty_time_ms; + lut.lut_lo_index = start_idx; + lut.lut_hi_index = start_idx + len - 1; + lut.lut_pause_lo = pause_lo; + lut.lut_pause_hi = pause_hi; + lut.flags = flags; + pwm->bypass_lut = 0; + + rc = pm8xxx_pwm_change_lut(pwm, &lut); + +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; + } + if (pwm->chip->is_lpg_supported == 0) { + pr_err("LPG module isn't supported\n"); + return -EINVAL; + } + + mutex_lock(&pwm->chip->pwm_mutex); + if (start) { + if (pwm->dtest_mode_supported) + pm8xxx_pwm_set_dtest(pwm, 1); + + pm8xxx_pwm_bank_enable(pwm, 1); + + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 1, 1); + } else { + if (pwm->dtest_mode_supported) + pm8xxx_pwm_set_dtest(pwm, 0); + + 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; +}; + +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; + } else { + pr_debug("[id=%d] pwm_request ok\n", puser->pwm_id); + puser->pwm = tmp; + } + } +} + +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 / 100; + pwm_config(puser->pwm, duty_us, 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; + int rc = 0; + + 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; + } + + dbgdev->user = kcalloc(pwm_chip->pwm_channels, + sizeof(struct pm8xxx_pwm_user), GFP_KERNEL); + if (dbgdev->user == NULL) { + pr_err("kcalloc() failed.\n"); + rc = -ENOMEM; + goto user_error; + } + + 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); + rc = -ENOMEM; + goto dir_error; + } + + dbgdev->dent = dent; + + for (i = 0; i < pwm_chip->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); + rc = -ENOMEM; + 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); + rc = -ENOMEM; + 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); + rc = -ENOMEM; + 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); + rc = -ENOMEM; + goto debug_error; + } + } + + pmic_dbg_device = dbgdev; + + return 0; + +debug_error: + debugfs_remove_recursive(dbgdev->dent); +dir_error: + kfree(dbgdev->user); +user_error: + kfree(dbgdev); + return rc; +} + +static int __devexit pm8xxx_pwm_dbg_remove(void) +{ + if (pmic_dbg_device) { + kfree(pmic_dbg_device->user); + 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) +{ + const struct pm8xxx_pwm_platform_data *pdata = pdev->dev.platform_data; + struct pm8xxx_pwm_chip *chip; + int i, dtest_channel; + enum pm8xxx_version version; + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (chip == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + if (pdata != NULL) + dtest_channel = pdata->dtest_channel; + else + dtest_channel = -1; + + mutex_init(&chip->pwm_mutex); + + chip->dev = &pdev->dev; + pwm_chip = chip; + + version = pm8xxx_get_version(chip->dev->parent); + + if (version == PM8XXX_VERSION_8921 || + version == PM8XXX_VERSION_8058 || + version == PM8XXX_VERSION_8922 || + version == PM8XXX_VERSION_8038) { + chip->is_lpg_supported = 1; + } + if (chip->is_lpg_supported) { + if (version == PM8XXX_VERSION_8922 || + version == PM8XXX_VERSION_8038) { + for (i = 0; i < NUM_CLOCKS; i++) + pt_t[0][i] /= PRE_DIVIDE_2; + chip->pwm_channels = PM8XXX_LPG_V1_PWM_CHANNELS; + } else { + chip->pwm_channels = PM8XXX_LPG_V0_PWM_CHANNELS; + } + chip->pwm_total_pre_divs = NUM_LPG_PRE_DIVIDE; + } else { + chip->pwm_channels = PM8XXX_PWM_CHANNELS; + chip->pwm_total_pre_divs = NUM_PWM_PRE_DIVIDE; + } + + chip->pwm_dev = kcalloc(chip->pwm_channels, sizeof(struct pwm_device), + GFP_KERNEL); + if (chip->pwm_dev == NULL) { + pr_err("kcalloc() failed.\n"); + mutex_destroy(&chip->pwm_mutex); + kfree(chip); + return -ENOMEM; + } + + for (i = 0; i < chip->pwm_channels; i++) { + chip->pwm_dev[i].pwm_id = i; + chip->pwm_dev[i].chip = chip; + if (i == dtest_channel) + chip->pwm_dev[i].dtest_mode_supported = 1; + } + + 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(); + kfree(chip->pwm_dev); + 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/pm8xxx-spk.c b/drivers/mfd/pm8xxx-spk.c new file mode 100644 index 0000000000000000000000000000000000000000..297ddfa474aeba62b4b6788543d7c48cb490c337 --- /dev/null +++ b/drivers/mfd/pm8xxx-spk.c @@ -0,0 +1,279 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#define PM8XXX_SPK_CTL1_REG_OFF 0 +#define PM8XXX_SPK_TEST_REG_1_OFF 1 +#define PM8XXX_SPK_TEST_REG_2_OFF 2 + +#define PM8XXX_SPK_BANK_SEL 4 +#define PM8XXX_SPK_BANK_WRITE 0x80 +#define PM8XXX_SPK_BANK_VAL_MASK 0xF + +#define BOOST_6DB_GAIN_EN_MASK 0x8 +#define VSEL_LD0_1P1 0x0 +#define VSEL_LD0_1P2 0x2 +#define VSEL_LD0_1P0 0x4 + +#define PWM_EN_MASK 0xF +#define PM8XXX_SPK_TEST_REG_1_BANKS 8 +#define PM8XXX_SPK_TEST_REG_2_BANKS 2 + +#define PM8XXX_SPK_GAIN 0x5 +#define PM8XXX_ADD_EN 0x1 + +struct pm8xxx_spk_chip { + struct list_head link; + struct pm8xxx_spk_platform_data pdata; + struct device *dev; + enum pm8xxx_version version; + struct mutex spk_mutex; + u16 base; + u16 end; +}; + +static struct pm8xxx_spk_chip *the_spk_chip; + +static inline bool spk_defined(void) +{ + if (the_spk_chip == NULL || IS_ERR(the_spk_chip)) + return false; + return true; +} + +static int pm8xxx_spk_bank_write(u16 reg, u16 bank, u8 val) +{ + int rc = 0; + u8 bank_val = PM8XXX_SPK_BANK_WRITE | (bank << PM8XXX_SPK_BANK_SEL); + + bank_val |= (val & PM8XXX_SPK_BANK_VAL_MASK); + mutex_lock(&the_spk_chip->spk_mutex); + rc = pm8xxx_writeb(the_spk_chip->dev->parent, reg, bank_val); + if (rc) + pr_err("pm8xxx_writeb(): rc=%d\n", rc); + mutex_unlock(&the_spk_chip->spk_mutex); + return rc; +} + + +static int pm8xxx_spk_read(u16 addr) +{ + int rc = 0; + u8 val = 0; + + mutex_lock(&the_spk_chip->spk_mutex); + rc = pm8xxx_readb(the_spk_chip->dev->parent, + the_spk_chip->base + addr, &val); + if (rc) { + pr_err("pm8xxx_spk_readb() failed: rc=%d\n", rc); + val = rc; + } + mutex_unlock(&the_spk_chip->spk_mutex); + + return val; +} + +static int pm8xxx_spk_write(u16 addr, u8 val) +{ + int rc = 0; + + mutex_lock(&the_spk_chip->spk_mutex); + rc = pm8xxx_writeb(the_spk_chip->dev->parent, + the_spk_chip->base + addr, val); + if (rc) + pr_err("pm8xxx_writeb() failed: rc=%d\n", rc); + mutex_unlock(&the_spk_chip->spk_mutex); + return rc; +} + +int pm8xxx_spk_mute(bool mute) +{ + u8 val = 0; + int ret = 0; + if (spk_defined() == false) { + pr_err("Invalid spk handle or no spk_chip\n"); + return -ENODEV; + } + + val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); + if (val < 0) + return val; + val |= mute << 2; + ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); + return ret; +} +EXPORT_SYMBOL_GPL(pm8xxx_spk_mute); + +int pm8xxx_spk_gain(u8 gain) +{ + u8 val; + int ret = 0; + + if (spk_defined() == false) { + pr_err("Invalid spk handle or no spk_chip\n"); + return -ENODEV; + } + + val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); + if (val < 0) + return val; + val |= (gain << 4); + ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); + if (!ret) { + pm8xxx_spk_bank_write(the_spk_chip->base + + PM8XXX_SPK_TEST_REG_1_OFF, + 0, BOOST_6DB_GAIN_EN_MASK | VSEL_LD0_1P2); + } + return ret; +} +EXPORT_SYMBOL_GPL(pm8xxx_spk_gain); + +int pm8xxx_spk_enable(int enable) +{ + int val = 0; + u16 addr; + int ret = 0; + + if (spk_defined() == false) { + pr_err("Invalid spk handle or no spk_chip\n"); + return -ENODEV; + } + + addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF; + val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); + if (val < 0) + return val; + val |= (enable << 3); + ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); + if (!ret) + ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK); + return ret; +} +EXPORT_SYMBOL_GPL(pm8xxx_spk_enable); + +static int pm8xxx_spk_config(void) +{ + u16 addr; + int ret = 0; + + if (spk_defined() == false) { + pr_err("Invalid spk handle or no spk_chip\n"); + return -ENODEV; + } + + addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF; + ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK & 0); + if (!ret) + ret = pm8xxx_spk_gain(PM8XXX_SPK_GAIN); + return ret; +} + +static int __devinit pm8xxx_spk_probe(struct platform_device *pdev) +{ + const struct pm8xxx_spk_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + the_spk_chip = kzalloc(sizeof(struct pm8xxx_spk_chip), GFP_KERNEL); + if (the_spk_chip == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + mutex_init(&the_spk_chip->spk_mutex); + + the_spk_chip->dev = &pdev->dev; + the_spk_chip->version = pm8xxx_get_version(the_spk_chip->dev->parent); + switch (pm8xxx_get_version(the_spk_chip->dev->parent)) { + case PM8XXX_VERSION_8038: + break; + default: + ret = -ENODEV; + goto err_handle; + } + + memcpy(&(the_spk_chip->pdata), pdata, + sizeof(struct pm8xxx_spk_platform_data)); + + the_spk_chip->base = pdev->resource[0].start; + the_spk_chip->end = pdev->resource[0].end; + + if (the_spk_chip->pdata.spk_add_enable) { + int val; + val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); + if (val < 0) { + ret = val; + goto err_handle; + } + val |= (the_spk_chip->pdata.spk_add_enable & PM8XXX_ADD_EN); + ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); + if (ret < 0) + goto err_handle; + } + return pm8xxx_spk_config(); +err_handle: + pr_err("pm8xxx_spk_probe failed." + "Audio unavailable on speaker.\n"); + mutex_destroy(&the_spk_chip->spk_mutex); + kfree(the_spk_chip); + return ret; +} + +static int __devexit pm8xxx_spk_remove(struct platform_device *pdev) +{ + if (spk_defined() == false) { + pr_err("Invalid spk handle or no spk_chip\n"); + return -ENODEV; + } + mutex_destroy(&the_spk_chip->spk_mutex); + kfree(the_spk_chip); + return 0; +} + +static struct platform_driver pm8xxx_spk_driver = { + .probe = pm8xxx_spk_probe, + .remove = __devexit_p(pm8xxx_spk_remove), + .driver = { + .name = PM8XXX_SPK_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_spk_init(void) +{ + return platform_driver_register(&pm8xxx_spk_driver); +} +subsys_initcall(pm8xxx_spk_init); + +static void __exit pm8xxx_spk_exit(void) +{ + platform_driver_unregister(&pm8xxx_spk_driver); +} +module_exit(pm8xxx_spk_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8XXX SPK driver"); +MODULE_ALIAS("platform:" PM8XXX_SPK_DEV_NAME); diff --git a/drivers/mfd/pmic8058.c b/drivers/mfd/pmic8058.c new file mode 100644 index 0000000000000000000000000000000000000000..8f7ab2d5450eba2c3ad8c0a9779b9ade07851398 --- /dev/null +++ b/drivers/mfd/pmic8058.c @@ -0,0 +1,792 @@ +/* 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 + +#define REG_MPP_BASE 0x50 +#define REG_IRQ_BASE 0x1BB + +/* PMIC8058 Revision */ +#define PM8058_REG_REV 0x002 /* PMIC4 revision */ +#define PM8058_VERSION_MASK 0xF0 +#define PM8058_REVISION_MASK 0x0F +#define PM8058_VERSION_VALUE 0xE0 + +/* PMIC 8058 Battery Alarm SSBI registers */ +#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 REG_TEMP_ALRM_CTRL 0x1B +#define REG_TEMP_ALRM_PWM 0x9B + +/* 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 + +/* GP_TEST1 register */ +#define SSBI_REG_ADDR_GP_TEST_1 0x07A + +#define PM8058_RTC_BASE 0x1E8 +#define PM8058_OTHC_CNTR_BASE0 0xA0 +#define PM8058_OTHC_CNTR_BASE1 0x134 +#define PM8058_OTHC_CNTR_BASE2 0x137 + +#define SINGLE_IRQ_RESOURCE(_name, _irq) \ +{ \ + .name = _name, \ + .start = _irq, \ + .end = _irq, \ + .flags = IORESOURCE_IRQ, \ +} + +struct pm8058_chip { + struct pm8058_platform_data pdata; + struct device *dev; + struct pm_irq_chip *irq_chip; + struct mfd_cell *mfd_regulators, *mfd_xo_buffers; + + u8 revision; +}; + +static int pm8058_readb(const struct device *dev, u16 addr, u8 *val) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, val, 1); +} + +static int pm8058_writeb(const struct device *dev, u16 addr, u8 val) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); +} + +static int pm8058_read_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8058_write_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8058_read_irq_stat(const struct device *dev, int irq) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + + return pm8xxx_get_irq_stat(pmic->irq_chip, irq); + + return 0; +} + +static enum pm8xxx_version pm8058_get_version(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + enum pm8xxx_version version = -ENODEV; + + if ((pmic->revision & PM8058_VERSION_MASK) == PM8058_VERSION_VALUE) + version = PM8XXX_VERSION_8058; + + return version; +} + +static int pm8058_get_revision(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8058_drvdata = dev_get_drvdata(dev); + const struct pm8058_chip *pmic = pm8058_drvdata->pm_chip_data; + + return pmic->revision & PM8058_REVISION_MASK; +} + +static struct pm8xxx_drvdata pm8058_drvdata = { + .pmic_readb = pm8058_readb, + .pmic_writeb = pm8058_writeb, + .pmic_read_buf = pm8058_read_buf, + .pmic_write_buf = pm8058_write_buf, + .pmic_read_irq_stat = pm8058_read_irq_stat, + .pmic_get_version = pm8058_get_version, + .pmic_get_revision = pm8058_get_revision, +}; + +static const struct resource pm8058_charger_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("CHGVAL", PM8058_CHGVAL_IRQ), + SINGLE_IRQ_RESOURCE("CHGINVAL", PM8058_CHGINVAL_IRQ), + SINGLE_IRQ_RESOURCE("CHGILIM", PM8058_CHGILIM_IRQ), + SINGLE_IRQ_RESOURCE("VCP", PM8058_VCP_IRQ), + SINGLE_IRQ_RESOURCE("ATC_DONE", PM8058_ATC_DONE_IRQ), + SINGLE_IRQ_RESOURCE("ATCFAIL", PM8058_ATCFAIL_IRQ), + SINGLE_IRQ_RESOURCE("AUTO_CHGDONE", PM8058_AUTO_CHGDONE_IRQ), + SINGLE_IRQ_RESOURCE("AUTO_CHGFAIL", PM8058_AUTO_CHGFAIL_IRQ), + SINGLE_IRQ_RESOURCE("CHGSTATE", PM8058_CHGSTATE_IRQ), + SINGLE_IRQ_RESOURCE("FASTCHG", PM8058_FASTCHG_IRQ), + SINGLE_IRQ_RESOURCE("CHG_END", PM8058_CHG_END_IRQ), + SINGLE_IRQ_RESOURCE("BATTTEMP", PM8058_BATTTEMP_IRQ), + SINGLE_IRQ_RESOURCE("CHGHOT", PM8058_CHGHOT_IRQ), + SINGLE_IRQ_RESOURCE("CHGTLIMIT", PM8058_CHGTLIMIT_IRQ), + SINGLE_IRQ_RESOURCE("CHG_GONE", PM8058_CHG_GONE_IRQ), + SINGLE_IRQ_RESOURCE("VCPMAJOR", PM8058_VCPMAJOR_IRQ), + SINGLE_IRQ_RESOURCE("VBATDET", PM8058_VBATDET_IRQ), + SINGLE_IRQ_RESOURCE("BATFET", PM8058_BATFET_IRQ), + SINGLE_IRQ_RESOURCE("BATT_REPLACE", PM8058_BATT_REPLACE_IRQ), + SINGLE_IRQ_RESOURCE("BATTCONNECT", PM8058_BATTCONNECT_IRQ), + SINGLE_IRQ_RESOURCE("VBATDET_LOW", PM8058_VBATDET_LOW_IRQ), +}; + +static struct mfd_cell pm8058_charger_cell __devinitdata = { + .name = "pm8058-charger", + .id = -1, + .resources = pm8058_charger_resources, + .num_resources = ARRAY_SIZE(pm8058_charger_resources), +}; + +static const struct resource misc_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("pm8xxx_osc_halt_irq", PM8058_OSCHALT_IRQ), +}; + +static struct mfd_cell misc_cell __devinitdata = { + .name = PM8XXX_MISC_DEV_NAME, + .id = -1, + .resources = misc_cell_resources, + .num_resources = ARRAY_SIZE(misc_cell_resources), +}; + +static struct mfd_cell pm8058_pwm_cell __devinitdata = { + .name = "pm8058-pwm", + .id = -1, +}; + +static struct resource xoadc_resources[] = { + SINGLE_IRQ_RESOURCE(NULL, PM8058_ADC_IRQ), +}; + +static struct mfd_cell xoadc_cell __devinitdata = { + .name = "pm8058-xoadc", + .id = -1, + .resources = xoadc_resources, + .num_resources = ARRAY_SIZE(xoadc_resources), +}; + +static const struct resource thermal_alarm_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("pm8058_tempstat_irq", PM8058_TEMPSTAT_IRQ), + SINGLE_IRQ_RESOURCE("pm8058_overtemp_irq", PM8058_OVERTEMP_IRQ), +}; + +static struct pm8xxx_tm_core_data thermal_alarm_cdata = { + .adc_channel = CHANNEL_ADC_DIE_TEMP, + .adc_type = PM8XXX_TM_ADC_PM8058_ADC, + .reg_addr_temp_alarm_ctrl = REG_TEMP_ALRM_CTRL, + .reg_addr_temp_alarm_pwm = REG_TEMP_ALRM_PWM, + .tm_name = "pm8058_tz", + .irq_name_temp_stat = "pm8058_tempstat_irq", + .irq_name_over_temp = "pm8058_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 struct mfd_cell debugfs_cell __devinitdata = { + .name = "pm8xxx-debug", + .id = -1, + .platform_data = "pm8058-dbg", + .pdata_size = sizeof("pm8058-dbg"), +}; + +static const struct resource othc0_cell_resources[] __devinitconst = { + { + .name = "othc_base", + .start = PM8058_OTHC_CNTR_BASE0, + .end = PM8058_OTHC_CNTR_BASE0, + .flags = IORESOURCE_IO, + }, +}; + +static const struct resource othc1_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE(NULL, PM8058_SW_1_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8058_IR_1_IRQ), + { + .name = "othc_base", + .start = PM8058_OTHC_CNTR_BASE1, + .end = PM8058_OTHC_CNTR_BASE1, + .flags = IORESOURCE_IO, + }, +}; + +static const struct resource othc2_cell_resources[] __devinitconst = { + { + .name = "othc_base", + .start = PM8058_OTHC_CNTR_BASE2, + .end = PM8058_OTHC_CNTR_BASE2, + .flags = IORESOURCE_IO, + }, +}; + +static const struct resource batt_alarm_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("pm8058_batt_alarm_irq", PM8058_BATT_ALARM_IRQ), +}; + +static struct mfd_cell leds_cell __devinitdata = { + .name = "pm8058-led", + .id = -1, +}; + +static struct mfd_cell othc0_cell __devinitdata = { + .name = "pm8058-othc", + .id = 0, + .resources = othc0_cell_resources, + .num_resources = ARRAY_SIZE(othc0_cell_resources), +}; + +static struct mfd_cell othc1_cell __devinitdata = { + .name = "pm8058-othc", + .id = 1, + .resources = othc1_cell_resources, + .num_resources = ARRAY_SIZE(othc1_cell_resources), +}; + +static struct mfd_cell othc2_cell __devinitdata = { + .name = "pm8058-othc", + .id = 2, + .resources = othc2_cell_resources, + .num_resources = ARRAY_SIZE(othc2_cell_resources), +}; + +static struct pm8xxx_batt_alarm_core_data batt_alarm_cdata = { + .irq_name = "pm8058_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 struct mfd_cell upl_cell __devinitdata = { + .name = PM8XXX_UPL_DEV_NAME, + .id = -1, +}; + +static struct mfd_cell nfc_cell __devinitdata = { + .name = PM8XXX_NFC_DEV_NAME, + .id = -1, +}; + +static const struct resource rtc_cell_resources[] __devinitconst = { + [0] = SINGLE_IRQ_RESOURCE(NULL, PM8058_RTC_ALARM_IRQ), + [1] = { + .name = "pmic_rtc_base", + .start = PM8058_RTC_BASE, + .end = PM8058_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, PM8058_PWRKEY_REL_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8058_PWRKEY_PRESS_IRQ), +}; + +static struct mfd_cell vibrator_cell __devinitdata = { + .name = PM8XXX_VIBRATOR_DEV_NAME, + .id = -1, +}; + +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, PM8058_KEYPAD_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8058_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 const struct resource mpp_cell_resources[] __devinitconst = { + { + .start = PM8058_IRQ_BLOCK_BIT(PM8058_MPP_BLOCK_START, 0), + .end = PM8058_IRQ_BLOCK_BIT(PM8058_MPP_BLOCK_START, 0) + + PM8058_MPPS - 1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell mpp_cell __devinitdata = { + .name = PM8XXX_MPP_DEV_NAME, + .id = 0, + .resources = mpp_cell_resources, + .num_resources = ARRAY_SIZE(mpp_cell_resources), +}; + +static const struct resource gpio_cell_resources[] __devinitconst = { + [0] = { + .start = PM8058_IRQ_BLOCK_BIT(PM8058_GPIO_BLOCK_START, 0), + .end = PM8058_IRQ_BLOCK_BIT(PM8058_GPIO_BLOCK_START, 0) + + PM8058_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 int __devinit +pm8058_add_subdevices(const struct pm8058_platform_data *pdata, + struct pm8058_chip *pmic) +{ + int rc = 0, irq_base = 0, i; + struct pm_irq_chip *irq_chip; + static struct mfd_cell *mfd_regulators, *mfd_xo_buffers; + + if (pdata->irq_pdata) { + pdata->irq_pdata->irq_cdata.nirqs = PM8058_NR_IRQS; + pdata->irq_pdata->irq_cdata.base_addr = REG_IRQ_BASE; + irq_base = pdata->irq_pdata->irq_base; + irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); + + if (IS_ERR(irq_chip)) { + pr_err("Failed to init interrupts ret=%ld\n", + PTR_ERR(irq_chip)); + return PTR_ERR(irq_chip); + } + pmic->irq_chip = irq_chip; + } + + if (pdata->gpio_pdata) { + pdata->gpio_pdata->gpio_cdata.ngpios = PM8058_GPIOS; + gpio_cell.platform_data = pdata->gpio_pdata; + gpio_cell.pdata_size = sizeof(struct pm8xxx_gpio_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &gpio_cell, 1, + NULL, irq_base); + if (rc) { + pr_err("Failed to add gpio subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->mpp_pdata) { + pdata->mpp_pdata->core_data.nmpps = PM8058_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); + rc = mfd_add_devices(pmic->dev, 0, &mpp_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add mpp subdevice ret=%d\n", rc); + goto bail; + } + } + + 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 pm8058 regulator " + "mfd cells\n", sizeof(struct mfd_cell) + * (pdata->num_regulators)); + rc = -ENOMEM; + goto bail; + } + for (i = 0; i < pdata->num_regulators; i++) { + mfd_regulators[i].name = "pm8058-regulator"; + 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 pm8058_vreg_pdata); + } + rc = mfd_add_devices(pmic->dev, 0, mfd_regulators, + pdata->num_regulators, NULL, irq_base); + if (rc) { + pr_err("Failed to add regulator subdevices ret=%d\n", + rc); + kfree(mfd_regulators); + goto bail; + } + pmic->mfd_regulators = mfd_regulators; + } + + if (pdata->num_xo_buffers > 0 && pdata->xo_buffer_pdata) { + mfd_xo_buffers = kzalloc(sizeof(struct mfd_cell) + * (pdata->num_xo_buffers), GFP_KERNEL); + if (!mfd_xo_buffers) { + pr_err("Cannot allocate %d bytes for pm8058 XO buffer " + "mfd cells\n", sizeof(struct mfd_cell) + * (pdata->num_xo_buffers)); + rc = -ENOMEM; + goto bail; + } + for (i = 0; i < pdata->num_xo_buffers; i++) { + mfd_xo_buffers[i].name = PM8058_XO_BUFFER_DEV_NAME; + mfd_xo_buffers[i].id = pdata->xo_buffer_pdata[i].id; + mfd_xo_buffers[i].platform_data = + &(pdata->xo_buffer_pdata[i]); + mfd_xo_buffers[i].pdata_size = + sizeof(struct pm8058_xo_pdata); + } + rc = mfd_add_devices(pmic->dev, 0, mfd_xo_buffers, + pdata->num_xo_buffers, NULL, irq_base); + if (rc) { + pr_err("Failed to add XO buffer subdevices ret=%d\n", + rc); + kfree(mfd_xo_buffers); + goto bail; + } + pmic->mfd_xo_buffers = mfd_xo_buffers; + } + + if (pdata->keypad_pdata) { + keypad_cell.platform_data = pdata->keypad_pdata; + keypad_cell.pdata_size = + sizeof(struct pm8xxx_keypad_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &keypad_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add keypad subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->rtc_pdata) { + rtc_cell.platform_data = pdata->rtc_pdata; + rtc_cell.pdata_size = sizeof(struct pm8xxx_rtc_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &rtc_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add rtc subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->pwrkey_pdata) { + pwrkey_cell.platform_data = pdata->pwrkey_pdata; + pwrkey_cell.pdata_size = + sizeof(struct pm8xxx_pwrkey_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &pwrkey_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add pwrkey subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->vibrator_pdata) { + vibrator_cell.platform_data = pdata->vibrator_pdata; + vibrator_cell.pdata_size = + sizeof(struct pm8xxx_vibrator_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &vibrator_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add vibrator subdevice ret=%d\n", + rc); + goto bail; + } + } + + if (pdata->leds_pdata) { + leds_cell.platform_data = pdata->leds_pdata; + leds_cell.pdata_size = + sizeof(struct pmic8058_leds_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &leds_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add leds subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->xoadc_pdata) { + xoadc_cell.platform_data = pdata->xoadc_pdata; + xoadc_cell.pdata_size = + sizeof(struct xoadc_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &xoadc_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add leds subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->othc0_pdata) { + othc0_cell.platform_data = pdata->othc0_pdata; + othc0_cell.pdata_size = + sizeof(struct pmic8058_othc_config_pdata); + rc = mfd_add_devices(pmic->dev, 0, &othc0_cell, 1, NULL, 0); + if (rc) { + pr_err("Failed to add othc0 subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->othc1_pdata) { + othc1_cell.platform_data = pdata->othc1_pdata; + othc1_cell.pdata_size = + sizeof(struct pmic8058_othc_config_pdata); + rc = mfd_add_devices(pmic->dev, 0, &othc1_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add othc1 subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->othc2_pdata) { + othc2_cell.platform_data = pdata->othc2_pdata; + othc2_cell.pdata_size = + sizeof(struct pmic8058_othc_config_pdata); + rc = mfd_add_devices(pmic->dev, 0, &othc2_cell, 1, NULL, 0); + if (rc) { + pr_err("Failed to add othc2 subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->pwm_pdata) { + pm8058_pwm_cell.platform_data = pdata->pwm_pdata; + pm8058_pwm_cell.pdata_size = sizeof(struct pm8058_pwm_pdata); + rc = mfd_add_devices(pmic->dev, 0, &pm8058_pwm_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add pwm subdevice ret=%d\n", rc); + goto bail; + } + } + + if (pdata->misc_pdata) { + misc_cell.platform_data = pdata->misc_pdata; + misc_cell.pdata_size = sizeof(struct pm8xxx_misc_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &misc_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add misc subdevice ret=%d\n", rc); + goto bail; + } + } + + rc = mfd_add_devices(pmic->dev, 0, &thermal_alarm_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add thermal alarm subdevice ret=%d\n", + rc); + goto bail; + } + + rc = mfd_add_devices(pmic->dev, 0, &batt_alarm_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add battery alarm subdevice ret=%d\n", + rc); + goto bail; + } + + rc = mfd_add_devices(pmic->dev, 0, &upl_cell, 1, NULL, 0); + if (rc) { + pr_err("Failed to add upl subdevice ret=%d\n", rc); + goto bail; + } + + rc = mfd_add_devices(pmic->dev, 0, &nfc_cell, 1, NULL, 0); + if (rc) { + pr_err("Failed to add upl subdevice ret=%d\n", rc); + goto bail; + } + + if (pdata->charger_pdata) { + pm8058_charger_cell.platform_data = pdata->charger_pdata; + pm8058_charger_cell.pdata_size = sizeof(struct + pmic8058_charger_data); + rc = mfd_add_devices(pmic->dev, 0, &pm8058_charger_cell, + 1, NULL, irq_base); + if (rc) { + pr_err("Failed to add charger subdevice ret=%d\n", rc); + goto bail; + } + } + + rc = mfd_add_devices(pmic->dev, 0, &debugfs_cell, 1, NULL, irq_base); + if (rc) { + pr_err("Failed to add debugfs subdevice ret=%d\n", rc); + goto bail; + } + + return rc; +bail: + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + return rc; +} + +static int __devinit pm8058_probe(struct platform_device *pdev) +{ + int rc; + struct pm8058_platform_data *pdata = pdev->dev.platform_data; + struct pm8058_chip *pmic; + + if (pdata == NULL) { + pr_err("%s: No platform_data or IRQ.\n", __func__); + return -ENODEV; + } + + pmic = kzalloc(sizeof *pmic, GFP_KERNEL); + if (pmic == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + pmic->dev = &pdev->dev; + + pm8058_drvdata.pm_chip_data = pmic; + platform_set_drvdata(pdev, &pm8058_drvdata); + + /* Read PMIC chip revision */ + rc = pm8058_readb(pmic->dev, PM8058_REG_REV, &pmic->revision); + if (rc) + pr_err("%s: Failed on pm8058_readb for revision: rc=%d.\n", + __func__, rc); + + pr_info("%s: PMIC revision: %X\n", __func__, pmic->revision); + + (void) memcpy((void *)&pmic->pdata, (const void *)pdata, + sizeof(pmic->pdata)); + + rc = pm8058_add_subdevices(pdata, pmic); + if (rc) { + pr_err("Cannot add subdevices rc=%d\n", rc); + goto err; + } + + rc = pm8xxx_hard_reset_config(PM8XXX_SHUTDOWN_ON_HARD_RESET); + if (rc < 0) + pr_err("%s: failed to config shutdown on hard reset: %d\n", + __func__, rc); + + return 0; + +err: + mfd_remove_devices(pmic->dev); + platform_set_drvdata(pdev, NULL); + kfree(pmic); + return rc; +} + +static int __devexit pm8058_remove(struct platform_device *pdev) +{ + struct pm8xxx_drvdata *drvdata; + struct pm8058_chip *pmic = NULL; + + drvdata = platform_get_drvdata(pdev); + if (drvdata) + pmic = drvdata->pm_chip_data; + if (pmic) { + if (pmic->dev) + mfd_remove_devices(pmic->dev); + if (pmic->irq_chip) + pm8xxx_irq_exit(pmic->irq_chip); + kfree(pmic->mfd_regulators); + kfree(pmic); + } + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver pm8058_driver = { + .probe = pm8058_probe, + .remove = __devexit_p(pm8058_remove), + .driver = { + .name = "pm8058-core", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_init(void) +{ + return platform_driver_register(&pm8058_driver); +} +postcore_initcall(pm8058_init); + +static void __exit pm8058_exit(void) +{ + platform_driver_unregister(&pm8058_driver); +} +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 0000000000000000000000000000000000000000..955258cf04dff4b94b5feb0e8626d617ed6f3664 --- /dev/null +++ b/drivers/mfd/pmic8901.c @@ -0,0 +1,382 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 PM8901_REG_REV 0x002 +#define PM8901_VERSION_MASK 0xF0 +#define PM8901_REVISION_MASK 0x0F +#define PM8901_VERSION_VALUE 0xF0 + +#define REG_IRQ_BASE 0xD5 +#define REG_MPP_BASE 0x27 + +#define REG_TEMP_ALRM_CTRL 0x23 +#define REG_TEMP_ALRM_PWM 0x24 + +#define SINGLE_IRQ_RESOURCE(_name, _irq) \ +{ \ + .name = _name, \ + .start = _irq, \ + .end = _irq, \ + .flags = IORESOURCE_IRQ, \ +} + +struct pm8901_chip { + struct pm8901_platform_data pdata; + struct device *dev; + struct pm_irq_chip *irq_chip; + struct mfd_cell *mfd_regulators; + u8 revision; +}; + +static int pm8901_readb(const struct device *dev, u16 addr, u8 *val) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, val, 1); +} + +static int pm8901_writeb(const struct device *dev, u16 addr, u8 val) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); +} + +static int pm8901_read_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8901_write_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8901_read_irq_stat(const struct device *dev, int irq) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + + return pm8xxx_get_irq_stat(pmic->irq_chip, irq); + + return 0; +} + +static enum pm8xxx_version pm8901_get_version(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + enum pm8xxx_version version = -ENODEV; + + if ((pmic->revision & PM8901_VERSION_MASK) == PM8901_VERSION_VALUE) + version = PM8XXX_VERSION_8901; + + return version; +} + +static int pm8901_get_revision(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8901_drvdata = dev_get_drvdata(dev); + const struct pm8901_chip *pmic = pm8901_drvdata->pm_chip_data; + + return pmic->revision & PM8901_REVISION_MASK; +} + +static struct pm8xxx_drvdata pm8901_drvdata = { + .pmic_readb = pm8901_readb, + .pmic_writeb = pm8901_writeb, + .pmic_read_buf = pm8901_read_buf, + .pmic_write_buf = pm8901_write_buf, + .pmic_read_irq_stat = pm8901_read_irq_stat, + .pmic_get_version = pm8901_get_version, + .pmic_get_revision = pm8901_get_revision, +}; + +static struct mfd_cell misc_cell = { + .name = PM8XXX_MISC_DEV_NAME, + .id = 1, +}; + +static struct mfd_cell debugfs_cell = { + .name = "pm8xxx-debug", + .id = 1, + .platform_data = "pm8901-dbg", + .pdata_size = sizeof("pm8901-dbg"), +}; + +static const struct resource thermal_alarm_cell_resources[] = { + SINGLE_IRQ_RESOURCE("pm8901_tempstat_irq", PM8901_TEMPSTAT_IRQ), + SINGLE_IRQ_RESOURCE("pm8901_overtemp_irq", PM8901_OVERTEMP_IRQ), +}; + +static struct pm8xxx_tm_core_data thermal_alarm_cdata = { + .adc_type = PM8XXX_TM_ADC_NONE, + .reg_addr_temp_alarm_ctrl = REG_TEMP_ALRM_CTRL, + .reg_addr_temp_alarm_pwm = REG_TEMP_ALRM_PWM, + .tm_name = "pm8901_tz", + .irq_name_temp_stat = "pm8901_tempstat_irq", + .irq_name_over_temp = "pm8901_overtemp_irq", +}; + +static struct mfd_cell thermal_alarm_cell = { + .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 mpp_cell_resources[] = { + { + .start = PM8901_IRQ_BLOCK_BIT(PM8901_MPP_BLOCK_START, 0), + .end = PM8901_IRQ_BLOCK_BIT(PM8901_MPP_BLOCK_START, 0) + + PM8901_MPPS - 1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell mpp_cell = { + .name = PM8XXX_MPP_DEV_NAME, + .id = 1, + .resources = mpp_cell_resources, + .num_resources = ARRAY_SIZE(mpp_cell_resources), +}; + +static int __devinit +pm8901_add_subdevices(const struct pm8901_platform_data *pdata, + struct pm8901_chip *pmic) +{ + int rc = 0, irq_base = 0, i; + struct pm_irq_chip *irq_chip; + static struct mfd_cell *mfd_regulators; + + if (pdata->irq_pdata) { + pdata->irq_pdata->irq_cdata.nirqs = PM8901_NR_IRQS; + pdata->irq_pdata->irq_cdata.base_addr = REG_IRQ_BASE; + irq_base = pdata->irq_pdata->irq_base; + irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); + + if (IS_ERR(irq_chip)) { + pr_err("Failed to init interrupts ret=%ld\n", + PTR_ERR(irq_chip)); + return PTR_ERR(irq_chip); + } + pmic->irq_chip = irq_chip; + } + + if (pdata->mpp_pdata) { + pdata->mpp_pdata->core_data.nmpps = PM8901_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); + rc = mfd_add_devices(pmic->dev, 0, &mpp_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add mpp subdevice ret=%d\n", rc); + goto bail; + } + } + + 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 pm8901 regulator " + "mfd cells\n", sizeof(struct mfd_cell) + * (pdata->num_regulators)); + rc = -ENOMEM; + goto bail; + } + for (i = 0; i < pdata->num_regulators; i++) { + mfd_regulators[i].name = "pm8901-regulator"; + 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 pm8901_vreg_pdata); + } + rc = mfd_add_devices(pmic->dev, 0, mfd_regulators, + pdata->num_regulators, NULL, irq_base); + if (rc) { + pr_err("Failed to add regulator subdevices ret=%d\n", + rc); + kfree(mfd_regulators); + goto bail; + } + pmic->mfd_regulators = mfd_regulators; + } + + rc = mfd_add_devices(pmic->dev, 0, &thermal_alarm_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add thermal alarm subdevice ret=%d\n", + rc); + goto bail; + } + + rc = mfd_add_devices(pmic->dev, 0, &debugfs_cell, 1, NULL, irq_base); + if (rc) { + pr_err("Failed to add debugfs subdevice ret=%d\n", rc); + goto bail; + } + + if (pdata->misc_pdata) { + misc_cell.platform_data = pdata->misc_pdata; + misc_cell.pdata_size = sizeof(struct pm8xxx_misc_platform_data); + rc = mfd_add_devices(pmic->dev, 0, &misc_cell, 1, NULL, + irq_base); + if (rc) { + pr_err("Failed to add misc subdevice ret=%d\n", rc); + goto bail; + } + } + + return rc; + +bail: + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + return rc; +} + +static const char * const pm8901_rev_names[] = { + [PM8XXX_REVISION_8901_TEST] = "test", + [PM8XXX_REVISION_8901_1p0] = "1.0", + [PM8XXX_REVISION_8901_1p1] = "1.1", + [PM8XXX_REVISION_8901_2p0] = "2.0", + [PM8XXX_REVISION_8901_2p1] = "2.1", + [PM8XXX_REVISION_8901_2p2] = "2.2", + [PM8XXX_REVISION_8901_2p3] = "2.3", +}; + +static int __devinit pm8901_probe(struct platform_device *pdev) +{ + int rc; + struct pm8901_platform_data *pdata = pdev->dev.platform_data; + const char *revision_name = "unknown"; + struct pm8901_chip *pmic; + int revision; + + if (pdata == NULL) { + pr_err("%s: No platform_data or IRQ.\n", __func__); + return -ENODEV; + } + + pmic = kzalloc(sizeof *pmic, GFP_KERNEL); + if (pmic == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + pmic->dev = &pdev->dev; + + pm8901_drvdata.pm_chip_data = pmic; + platform_set_drvdata(pdev, &pm8901_drvdata); + + /* Read PMIC chip revision */ + rc = pm8901_readb(pmic->dev, PM8901_REG_REV, &pmic->revision); + if (rc) + pr_err("%s: Failed reading version register rc=%d.\n", + __func__, rc); + + pr_info("%s: PMIC revision reg: %02X\n", __func__, pmic->revision); + revision = pm8xxx_get_revision(pmic->dev); + if (revision >= 0 && revision < ARRAY_SIZE(pm8901_rev_names)) + revision_name = pm8901_rev_names[revision]; + pr_info("%s: PMIC version: PM8901 rev %s\n", __func__, revision_name); + + (void) memcpy((void *)&pmic->pdata, (const void *)pdata, + sizeof(pmic->pdata)); + + rc = pm8901_add_subdevices(pdata, pmic); + if (rc) { + pr_err("Cannot add subdevices rc=%d\n", rc); + goto err; + } + + return 0; + +err: + platform_set_drvdata(pdev, NULL); + kfree(pmic); + return rc; +} + +static int __devexit pm8901_remove(struct platform_device *pdev) +{ + struct pm8xxx_drvdata *drvdata; + struct pm8901_chip *pmic = NULL; + + drvdata = platform_get_drvdata(pdev); + if (drvdata) + pmic = drvdata->pm_chip_data; + if (pmic) { + if (pmic->dev) + mfd_remove_devices(pmic->dev); + if (pmic->irq_chip) + pm8xxx_irq_exit(pmic->irq_chip); + kfree(pmic->mfd_regulators); + kfree(pmic); + } + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver pm8901_driver = { + .probe = pm8901_probe, + .remove = __devexit_p(pm8901_remove), + .driver = { + .name = "pm8901-core", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8901_init(void) +{ + return platform_driver_register(&pm8901_driver); +} +postcore_initcall(pm8901_init); + +static void __exit pm8901_exit(void) +{ + platform_driver_unregister(&pm8901_driver); +} +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 0000000000000000000000000000000000000000..1e0a839fa9aeed755f60dbce1c54a3fd4d614fa4 --- /dev/null +++ b/drivers/mfd/timpani-codec.c @@ -0,0 +1,3661 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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) +#define MAX_SHADOW_RIGISTERS TIMPANI_A_CDC_COMP_HALT + +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: + case TIMPANI_A_AUXPGA_LR_GAIN: + case TIMPANI_A_CDC_ANC_INPUT_MUX: + return false; + default: + return true; + } +} + +static int adie_codec_write(u8 reg, u8 mask, u8 val) +{ + int rc = 0; + u8 new_val; + + if (reg > MAX_SHADOW_RIGISTERS) { + pr_debug("register number is out of bound for shadow" + " registers reg = %d\n", reg); + new_val = (val & mask); + 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; + } + return rc; + } + 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 < ARRAY_SIZE(timpani_regset); 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 0000000000000000000000000000000000000000..318692fe7beb7b2898beaf2ea9c169dcad742fcf --- /dev/null +++ b/drivers/mfd/tps65023.c @@ -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. + * + */ + +#include +#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/wcd9xxx-core.c b/drivers/mfd/wcd9xxx-core.c new file mode 100644 index 0000000000000000000000000000000000000000..e2dff4b1e955f4a5ceb42dca2fc8b528fd05c662 --- /dev/null +++ b/drivers/mfd/wcd9xxx-core.c @@ -0,0 +1,1168 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +#define WCD9XXX_SLIM_GLA_MAX_RETRIES 5 +#define WCD9XXX_REGISTER_START_OFFSET 0x800 +#define WCD9XXX_SLIM_RW_MAX_TRIES 3 + +#define MAX_WCD9XXX_DEVICE 4 +#define WCD9XXX_I2C_MODE 0x03 + +struct wcd9xxx_i2c { + struct i2c_client *client; + struct i2c_msg xfer_msg[2]; + struct mutex xfer_lock; + int mod_id; +}; + +struct wcd9xxx_i2c wcd9xxx_modules[MAX_WCD9XXX_DEVICE]; +static int wcd9xxx_intf = -1; + +static int wcd9xxx_read(struct wcd9xxx *wcd9xxx, unsigned short reg, + int bytes, void *dest, bool interface_reg) +{ + int ret; + u8 *buf = dest; + + if (bytes <= 0) { + dev_err(wcd9xxx->dev, "Invalid byte read length %d\n", bytes); + return -EINVAL; + } + + ret = wcd9xxx->read_dev(wcd9xxx, reg, bytes, dest, interface_reg); + if (ret < 0) { + dev_err(wcd9xxx->dev, "Codec read failed\n"); + return ret; + } else + dev_dbg(wcd9xxx->dev, "Read 0x%02x from 0x%x\n", + *buf, reg); + + return 0; +} +int wcd9xxx_reg_read(struct wcd9xxx *wcd9xxx, unsigned short reg) +{ + u8 val; + int ret; + + mutex_lock(&wcd9xxx->io_lock); + ret = wcd9xxx_read(wcd9xxx, reg, 1, &val, false); + mutex_unlock(&wcd9xxx->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wcd9xxx_reg_read); + +static int wcd9xxx_write(struct wcd9xxx *wcd9xxx, 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(wcd9xxx->dev, "Write %02x to 0x%x\n", + *buf, reg); + + return wcd9xxx->write_dev(wcd9xxx, reg, bytes, src, interface_reg); +} + +int wcd9xxx_reg_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + u8 val) +{ + int ret; + + mutex_lock(&wcd9xxx->io_lock); + ret = wcd9xxx_write(wcd9xxx, reg, 1, &val, false); + mutex_unlock(&wcd9xxx->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_reg_write); + +static u8 wcd9xxx_pgd_la; +static u8 wcd9xxx_inf_la; + +int wcd9xxx_interface_reg_read(struct wcd9xxx *wcd9xxx, unsigned short reg) +{ + u8 val; + int ret; + + mutex_lock(&wcd9xxx->io_lock); + ret = wcd9xxx_read(wcd9xxx, reg, 1, &val, true); + mutex_unlock(&wcd9xxx->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wcd9xxx_interface_reg_read); + +int wcd9xxx_interface_reg_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + u8 val) +{ + int ret; + + mutex_lock(&wcd9xxx->io_lock); + ret = wcd9xxx_write(wcd9xxx, reg, 1, &val, true); + mutex_unlock(&wcd9xxx->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_interface_reg_write); + +int wcd9xxx_bulk_read(struct wcd9xxx *wcd9xxx, unsigned short reg, + int count, u8 *buf) +{ + int ret; + + mutex_lock(&wcd9xxx->io_lock); + + ret = wcd9xxx_read(wcd9xxx, reg, count, buf, false); + + mutex_unlock(&wcd9xxx->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_bulk_read); + +int wcd9xxx_bulk_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + int count, u8 *buf) +{ + int ret; + + mutex_lock(&wcd9xxx->io_lock); + + ret = wcd9xxx_write(wcd9xxx, reg, count, buf, false); + + mutex_unlock(&wcd9xxx->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_bulk_write); + +static int wcd9xxx_slim_read_device(struct wcd9xxx *wcd9xxx, unsigned short reg, + int bytes, void *dest, bool interface) +{ + int ret; + struct slim_ele_access msg; + int slim_read_tries = WCD9XXX_SLIM_RW_MAX_TRIES; + msg.start_offset = WCD9XXX_REGISTER_START_OFFSET + reg; + msg.num_bytes = bytes; + msg.comp = NULL; + + while (1) { + mutex_lock(&wcd9xxx->xfer_lock); + ret = slim_request_val_element(interface ? + wcd9xxx->slim_slave : wcd9xxx->slim, + &msg, dest, bytes); + mutex_unlock(&wcd9xxx->xfer_lock); + if (likely(ret == 0) || (--slim_read_tries == 0)) + break; + usleep_range(5000, 5000); + } + + if (ret) + pr_err("%s: Error, Codec read failed (%d)\n", __func__, ret); + + return ret; +} +/* Interface specifies whether the write is to the interface or general + * registers. + */ +static int wcd9xxx_slim_write_device(struct wcd9xxx *wcd9xxx, + unsigned short reg, int bytes, void *src, bool interface) +{ + int ret; + struct slim_ele_access msg; + int slim_write_tries = WCD9XXX_SLIM_RW_MAX_TRIES; + msg.start_offset = WCD9XXX_REGISTER_START_OFFSET + reg; + msg.num_bytes = bytes; + msg.comp = NULL; + + while (1) { + mutex_lock(&wcd9xxx->xfer_lock); + ret = slim_change_val_element(interface ? + wcd9xxx->slim_slave : wcd9xxx->slim, + &msg, src, bytes); + mutex_unlock(&wcd9xxx->xfer_lock); + if (likely(ret == 0) || (--slim_write_tries == 0)) + break; + usleep_range(5000, 5000); + } + + if (ret) + pr_err("%s: Error, Codec write failed (%d)\n", __func__, ret); + + return ret; +} + +static struct mfd_cell tabla1x_devs[] = { + { + .name = "tabla1x_codec", + }, +}; + +static struct mfd_cell tabla_devs[] = { + { + .name = "tabla_codec", + }, +}; + +static struct mfd_cell sitar_devs[] = { + { + .name = "sitar_codec", + }, +}; + +static void wcd9xxx_bring_up(struct wcd9xxx *wcd9xxx) +{ + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_LEAKAGE_CTL, 0x4); + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_CDC_CTL, 0); + usleep_range(5000, 5000); + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_CDC_CTL, 3); + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_LEAKAGE_CTL, 3); +} + +static void wcd9xxx_bring_down(struct wcd9xxx *wcd9xxx) +{ + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_LEAKAGE_CTL, 0x7); + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_LEAKAGE_CTL, 0x6); + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_LEAKAGE_CTL, 0xe); + wcd9xxx_reg_write(wcd9xxx, WCD9XXX_A_LEAKAGE_CTL, 0x8); +} + +static int wcd9xxx_reset(struct wcd9xxx *wcd9xxx) +{ + int ret; + + if (wcd9xxx->reset_gpio) { + ret = gpio_request(wcd9xxx->reset_gpio, "CDC_RESET"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + wcd9xxx->reset_gpio); + wcd9xxx->reset_gpio = 0; + return ret; + } + + gpio_direction_output(wcd9xxx->reset_gpio, 1); + msleep(20); + gpio_direction_output(wcd9xxx->reset_gpio, 0); + msleep(20); + gpio_direction_output(wcd9xxx->reset_gpio, 1); + msleep(20); + } + return 0; +} + +static void wcd9xxx_free_reset(struct wcd9xxx *wcd9xxx) +{ + if (wcd9xxx->reset_gpio) { + gpio_free(wcd9xxx->reset_gpio); + wcd9xxx->reset_gpio = 0; + } +} + +static int wcd9xxx_device_init(struct wcd9xxx *wcd9xxx, int irq) +{ + int ret; + u8 idbyte_0, idbyte_1, idbyte_2, idbyte_3; + struct mfd_cell *wcd9xxx_dev = NULL; + int wcd9xxx_dev_size = 0; + + mutex_init(&wcd9xxx->io_lock); + mutex_init(&wcd9xxx->xfer_lock); + + mutex_init(&wcd9xxx->pm_lock); + wcd9xxx->wlock_holders = 0; + wcd9xxx->pm_state = WCD9XXX_PM_SLEEPABLE; + init_waitqueue_head(&wcd9xxx->pm_wq); + wake_lock_init(&wcd9xxx->wlock, WAKE_LOCK_IDLE, "wcd9310-irq"); + + dev_set_drvdata(wcd9xxx->dev, wcd9xxx); + + wcd9xxx_bring_up(wcd9xxx); + + ret = wcd9xxx_irq_init(wcd9xxx); + if (ret) { + pr_err("IRQ initialization failed\n"); + goto err; + } + + idbyte_0 = wcd9xxx_reg_read(wcd9xxx, WCD9XXX_A_CHIP_ID_BYTE_0); + idbyte_1 = wcd9xxx_reg_read(wcd9xxx, WCD9XXX_A_CHIP_ID_BYTE_1); + idbyte_2 = wcd9xxx_reg_read(wcd9xxx, WCD9XXX_A_CHIP_ID_BYTE_2); + idbyte_3 = wcd9xxx_reg_read(wcd9xxx, WCD9XXX_A_CHIP_ID_BYTE_3); + + wcd9xxx->version = wcd9xxx_reg_read(wcd9xxx, + WCD9XXX_A_CHIP_VERSION) & 0x1F; + pr_info("%s : Codec version %u initialized\n", + __func__, wcd9xxx->version); + pr_info("idbyte_0[%08x] idbyte_1[%08x] idbyte_2[%08x] idbyte_3[%08x]\n", + idbyte_0, idbyte_1, idbyte_2, idbyte_3); + + if (wcd9xxx->slim != NULL) { + if (!strncmp(wcd9xxx->slim->name, "tabla", 5)) { + if (TABLA_IS_1_X(wcd9xxx->version)) { + wcd9xxx_dev = tabla1x_devs; + wcd9xxx_dev_size = ARRAY_SIZE(tabla1x_devs); + } else { + wcd9xxx_dev = tabla_devs; + wcd9xxx_dev_size = ARRAY_SIZE(tabla_devs); + } + } else { + wcd9xxx_dev = sitar_devs; + wcd9xxx_dev_size = ARRAY_SIZE(sitar_devs); + } + } else { + /* Need to add here check for Tabla. + * For now the read of version takes + * care of now only tabla. + */ + pr_debug("%s : Read codec version using I2C\n", __func__); + if (TABLA_IS_1_X(wcd9xxx->version)) { + wcd9xxx_dev = tabla1x_devs; + wcd9xxx_dev_size = ARRAY_SIZE(tabla1x_devs); + } else if (TABLA_IS_2_0(wcd9xxx->version)) { + wcd9xxx_dev = tabla_devs; + wcd9xxx_dev_size = ARRAY_SIZE(tabla_devs); + } else { + wcd9xxx_dev = sitar_devs; + wcd9xxx_dev_size = ARRAY_SIZE(sitar_devs); + } + } + + ret = mfd_add_devices(wcd9xxx->dev, -1, + wcd9xxx_dev, wcd9xxx_dev_size, + NULL, 0); + if (ret != 0) { + dev_err(wcd9xxx->dev, "Failed to add children: %d\n", ret); + goto err_irq; + } + return ret; +err_irq: + wcd9xxx_irq_exit(wcd9xxx); +err: + wcd9xxx_bring_down(wcd9xxx); + wake_lock_destroy(&wcd9xxx->wlock); + mutex_destroy(&wcd9xxx->pm_lock); + mutex_destroy(&wcd9xxx->io_lock); + mutex_destroy(&wcd9xxx->xfer_lock); + return ret; +} + +static void wcd9xxx_device_exit(struct wcd9xxx *wcd9xxx) +{ + wcd9xxx_irq_exit(wcd9xxx); + wcd9xxx_bring_down(wcd9xxx); + wcd9xxx_free_reset(wcd9xxx); + mutex_destroy(&wcd9xxx->pm_lock); + wake_lock_destroy(&wcd9xxx->wlock); + mutex_destroy(&wcd9xxx->io_lock); + mutex_destroy(&wcd9xxx->xfer_lock); + if (wcd9xxx_intf == WCD9XXX_INTERFACE_TYPE_SLIMBUS) + slim_remove_device(wcd9xxx->slim_slave); + kfree(wcd9xxx); +} + + +#ifdef CONFIG_DEBUG_FS +struct wcd9xxx *debugCodec; + +static struct dentry *debugfs_wcd9xxx_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)) + wcd9xxx_interface_reg_write(debugCodec, 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 = wcd9xxx_interface_reg_read(debugCodec, + 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 wcd9xxx_enable_supplies(struct wcd9xxx *wcd9xxx, + struct wcd9xxx_pdata *pdata) +{ + int ret; + int i; + wcd9xxx->supplies = kzalloc(sizeof(struct regulator_bulk_data) * + ARRAY_SIZE(pdata->regulator), + GFP_KERNEL); + if (!wcd9xxx->supplies) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) + wcd9xxx->supplies[i].supply = pdata->regulator[i].name; + + ret = regulator_bulk_get(wcd9xxx->dev, ARRAY_SIZE(pdata->regulator), + wcd9xxx->supplies); + if (ret != 0) { + dev_err(wcd9xxx->dev, "Failed to get supplies: err = %d\n", + ret); + goto err_supplies; + } + + for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) { + ret = regulator_set_voltage(wcd9xxx->supplies[i].consumer, + pdata->regulator[i].min_uV, pdata->regulator[i].max_uV); + if (ret) { + pr_err("%s: Setting regulator voltage failed for " + "regulator %s err = %d\n", __func__, + wcd9xxx->supplies[i].supply, ret); + goto err_get; + } + + ret = regulator_set_optimum_mode(wcd9xxx->supplies[i].consumer, + pdata->regulator[i].optimum_uA); + if (ret < 0) { + pr_err("%s: Setting regulator optimum mode failed for " + "regulator %s err = %d\n", __func__, + wcd9xxx->supplies[i].supply, ret); + goto err_get; + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pdata->regulator), + wcd9xxx->supplies); + if (ret != 0) { + dev_err(wcd9xxx->dev, "Failed to enable supplies: err = %d\n", + ret); + goto err_configure; + } + return ret; + +err_configure: + for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) { + regulator_set_voltage(wcd9xxx->supplies[i].consumer, 0, + pdata->regulator[i].max_uV); + regulator_set_optimum_mode(wcd9xxx->supplies[i].consumer, 0); + } +err_get: + regulator_bulk_free(ARRAY_SIZE(pdata->regulator), wcd9xxx->supplies); +err_supplies: + kfree(wcd9xxx->supplies); +err: + return ret; +} + +static void wcd9xxx_disable_supplies(struct wcd9xxx *wcd9xxx, + struct wcd9xxx_pdata *pdata) +{ + int i; + + regulator_bulk_disable(ARRAY_SIZE(pdata->regulator), + wcd9xxx->supplies); + for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) { + regulator_set_voltage(wcd9xxx->supplies[i].consumer, 0, + pdata->regulator[i].max_uV); + regulator_set_optimum_mode(wcd9xxx->supplies[i].consumer, 0); + } + regulator_bulk_free(ARRAY_SIZE(pdata->regulator), wcd9xxx->supplies); + kfree(wcd9xxx->supplies); +} + +int wcd9xxx_get_intf_type(void) +{ + return wcd9xxx_intf; +} +EXPORT_SYMBOL_GPL(wcd9xxx_get_intf_type); + +struct wcd9xxx_i2c *get_i2c_wcd9xxx_device_info(u16 reg) +{ + u16 mask = 0x0f00; + int value = 0; + struct wcd9xxx_i2c *wcd9xxx = NULL; + value = ((reg & mask) >> 8) & 0x000f; + switch (value) { + case 0: + wcd9xxx = &wcd9xxx_modules[0]; + break; + case 1: + wcd9xxx = &wcd9xxx_modules[1]; + break; + case 2: + wcd9xxx = &wcd9xxx_modules[2]; + break; + case 3: + wcd9xxx = &wcd9xxx_modules[3]; + break; + default: + break; + } + return wcd9xxx; +} + +int wcd9xxx_i2c_write_device(u16 reg, u8 *value, + u32 bytes) +{ + + struct i2c_msg *msg; + int ret = 0; + u8 reg_addr = 0; + u8 data[bytes + 1]; + struct wcd9xxx_i2c *wcd9xxx; + + wcd9xxx = get_i2c_wcd9xxx_device_info(reg); + if (wcd9xxx == NULL || wcd9xxx->client == NULL) { + pr_err("failed to get device info\n"); + return -ENODEV; + } + reg_addr = (u8)reg; + msg = &wcd9xxx->xfer_msg[0]; + msg->addr = wcd9xxx->client->addr; + msg->len = bytes + 1; + msg->flags = 0; + data[0] = reg; + data[1] = *value; + msg->buf = data; + ret = i2c_transfer(wcd9xxx->client->adapter, wcd9xxx->xfer_msg, 1); + /* Try again if the write fails */ + if (ret != 1) { + ret = i2c_transfer(wcd9xxx->client->adapter, + wcd9xxx->xfer_msg, 1); + if (ret != 1) { + pr_err("failed to write the device\n"); + return ret; + } + } + pr_debug("write sucess register = %x val = %x\n", reg, data[1]); + return 0; +} + + +int wcd9xxx_i2c_read_device(unsigned short reg, + int bytes, unsigned char *dest) +{ + struct i2c_msg *msg; + int ret = 0; + u8 reg_addr = 0; + struct wcd9xxx_i2c *wcd9xxx; + u8 i = 0; + + wcd9xxx = get_i2c_wcd9xxx_device_info(reg); + if (wcd9xxx == NULL || wcd9xxx->client == NULL) { + pr_err("failed to get device info\n"); + return -ENODEV; + } + for (i = 0; i < bytes; i++) { + reg_addr = (u8)reg++; + msg = &wcd9xxx->xfer_msg[0]; + msg->addr = wcd9xxx->client->addr; + msg->len = 1; + msg->flags = 0; + msg->buf = ®_addr; + + msg = &wcd9xxx->xfer_msg[1]; + msg->addr = wcd9xxx->client->addr; + msg->len = 1; + msg->flags = I2C_M_RD; + msg->buf = dest++; + ret = i2c_transfer(wcd9xxx->client->adapter, + wcd9xxx->xfer_msg, 2); + + /* Try again if read fails first time */ + if (ret != 2) { + ret = i2c_transfer(wcd9xxx->client->adapter, + wcd9xxx->xfer_msg, 2); + if (ret != 2) { + pr_err("failed to read wcd9xxx register\n"); + return ret; + } + } + } + return 0; +} + +int wcd9xxx_i2c_read(struct wcd9xxx *wcd9xxx, unsigned short reg, + int bytes, void *dest, bool interface_reg) +{ + return wcd9xxx_i2c_read_device(reg, bytes, dest); +} + +int wcd9xxx_i2c_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + int bytes, void *src, bool interface_reg) +{ + return wcd9xxx_i2c_write_device(reg, src, bytes); +} + +static int __devinit wcd9xxx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wcd9xxx *wcd9xxx; + struct wcd9xxx_pdata *pdata = client->dev.platform_data; + int val = 0; + int ret = 0; + static int device_id; + + if (wcd9xxx_intf == WCD9XXX_INTERFACE_TYPE_SLIMBUS) { + pr_info("tabla card is already detected in slimbus mode\n"); + return -ENODEV; + } + if (device_id > 0) { + wcd9xxx_modules[device_id++].client = client; + pr_info("probe for other slaves devices of tabla\n"); + return ret; + } + + wcd9xxx = kzalloc(sizeof(struct wcd9xxx), GFP_KERNEL); + if (wcd9xxx == NULL) { + pr_err("%s: error, allocation failed\n", __func__); + ret = -ENOMEM; + goto fail; + } + + if (!pdata) { + dev_dbg(&client->dev, "no platform data?\n"); + ret = -EINVAL; + goto fail; + } + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + dev_dbg(&client->dev, "can't talk I2C?\n"); + ret = -EIO; + goto fail; + } + dev_set_drvdata(&client->dev, wcd9xxx); + wcd9xxx->dev = &client->dev; + wcd9xxx->reset_gpio = pdata->reset_gpio; + ret = wcd9xxx_enable_supplies(wcd9xxx, pdata); + if (ret) { + pr_err("%s: Fail to enable Codec supplies\n", __func__); + goto err_codec; + } + + usleep_range(5, 5); + ret = wcd9xxx_reset(wcd9xxx); + if (ret) { + pr_err("%s: Resetting Codec failed\n", __func__); + goto err_supplies; + } + wcd9xxx_modules[device_id++].client = client; + + wcd9xxx->read_dev = wcd9xxx_i2c_read; + wcd9xxx->write_dev = wcd9xxx_i2c_write; + wcd9xxx->irq = pdata->irq; + wcd9xxx->irq_base = pdata->irq_base; + + /*read the tabla status before initializing the device type*/ + ret = wcd9xxx_read(wcd9xxx, WCD9XXX_A_CHIP_STATUS, 1, &val, 0); + if ((ret < 0) || (val != WCD9XXX_I2C_MODE)) { + pr_err("failed to read the wcd9xxx status\n"); + goto err_device_init; + } + + ret = wcd9xxx_device_init(wcd9xxx, wcd9xxx->irq); + if (ret) { + pr_err("%s: error, initializing device failed\n", __func__); + goto err_device_init; + } + wcd9xxx_intf = WCD9XXX_INTERFACE_TYPE_I2C; + + return ret; +err_device_init: + wcd9xxx_free_reset(wcd9xxx); +err_supplies: + wcd9xxx_disable_supplies(wcd9xxx, pdata); +err_codec: + kfree(wcd9xxx); +fail: + return ret; +} + +static int __devexit wcd9xxx_i2c_remove(struct i2c_client *client) +{ + struct wcd9xxx *wcd9xxx; + struct wcd9xxx_pdata *pdata = client->dev.platform_data; + pr_debug("exit\n"); + wcd9xxx = dev_get_drvdata(&client->dev); + wcd9xxx_disable_supplies(wcd9xxx, pdata); + wcd9xxx_device_exit(wcd9xxx); + return 0; +} + +static int wcd9xxx_slim_probe(struct slim_device *slim) +{ + struct wcd9xxx *wcd9xxx; + struct wcd9xxx_pdata *pdata; + int ret = 0; + int sgla_retry_cnt; + + dev_info(&slim->dev, "Initialized slim device %s\n", slim->name); + pdata = slim->dev.platform_data; + + if (!pdata) { + dev_err(&slim->dev, "Error, no platform data\n"); + ret = -EINVAL; + goto err; + } + + wcd9xxx = kzalloc(sizeof(struct wcd9xxx), GFP_KERNEL); + if (wcd9xxx == 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_codec; + } + wcd9xxx->slim = slim; + slim_set_clientdata(slim, wcd9xxx); + wcd9xxx->reset_gpio = pdata->reset_gpio; + wcd9xxx->dev = &slim->dev; + + ret = wcd9xxx_enable_supplies(wcd9xxx, pdata); + if (ret) + goto err_codec; + usleep_range(5, 5); + + ret = wcd9xxx_reset(wcd9xxx); + if (ret) { + pr_err("%s: Resetting Codec failed\n", __func__); + goto err_supplies; + } + + ret = slim_get_logical_addr(wcd9xxx->slim, wcd9xxx->slim->e_addr, + ARRAY_SIZE(wcd9xxx->slim->e_addr), &wcd9xxx->slim->laddr); + if (ret) { + pr_err("fail to get slimbus logical address %d\n", ret); + goto err_reset; + } + wcd9xxx->read_dev = wcd9xxx_slim_read_device; + wcd9xxx->write_dev = wcd9xxx_slim_write_device; + wcd9xxx->irq = pdata->irq; + wcd9xxx->irq_base = pdata->irq_base; + wcd9xxx_pgd_la = wcd9xxx->slim->laddr; + + if (pdata->num_irqs < TABLA_NUM_IRQS) { + pr_err("%s: Error, not enough interrupt lines allocated\n", + __func__); + goto err_reset; + } + + wcd9xxx->slim_slave = &pdata->slimbus_slave_device; + + ret = slim_add_device(slim->ctrl, wcd9xxx->slim_slave); + if (ret) { + pr_err("%s: error, adding SLIMBUS device failed\n", __func__); + goto err_reset; + } + + sgla_retry_cnt = 0; + + while (1) { + ret = slim_get_logical_addr(wcd9xxx->slim_slave, + wcd9xxx->slim_slave->e_addr, + ARRAY_SIZE(wcd9xxx->slim_slave->e_addr), + &wcd9xxx->slim_slave->laddr); + if (ret) { + if (sgla_retry_cnt++ < WCD9XXX_SLIM_GLA_MAX_RETRIES) { + /* Give SLIMBUS slave time to report present + and be ready. + */ + usleep_range(1000, 1000); + pr_debug("%s: retry slim_get_logical_addr()\n", + __func__); + continue; + } + pr_err("fail to get slimbus slave logical address" + " %d\n", ret); + goto err_slim_add; + } + break; + } + wcd9xxx_inf_la = wcd9xxx->slim_slave->laddr; + wcd9xxx_intf = WCD9XXX_INTERFACE_TYPE_SLIMBUS; + + ret = wcd9xxx_device_init(wcd9xxx, wcd9xxx->irq); + if (ret) { + pr_err("%s: error, initializing device failed\n", __func__); + goto err_slim_add; + } + wcd9xxx_init_slimslave(wcd9xxx, wcd9xxx_pgd_la); +#ifdef CONFIG_DEBUG_FS + debugCodec = wcd9xxx; + + debugfs_wcd9xxx_dent = debugfs_create_dir + ("wcd9310_slimbus_interface_device", 0); + if (!IS_ERR(debugfs_wcd9xxx_dent)) { + debugfs_peek = debugfs_create_file("peek", + S_IFREG | S_IRUGO, debugfs_wcd9xxx_dent, + (void *) "peek", &codec_debug_ops); + + debugfs_poke = debugfs_create_file("poke", + S_IFREG | S_IRUGO, debugfs_wcd9xxx_dent, + (void *) "poke", &codec_debug_ops); + } +#endif + + return ret; + +err_slim_add: + slim_remove_device(wcd9xxx->slim_slave); +err_reset: + wcd9xxx_free_reset(wcd9xxx); +err_supplies: + wcd9xxx_disable_supplies(wcd9xxx, pdata); +err_codec: + kfree(wcd9xxx); +err: + return ret; +} +static int wcd9xxx_slim_remove(struct slim_device *pdev) +{ + struct wcd9xxx *wcd9xxx; + struct wcd9xxx_pdata *pdata = pdev->dev.platform_data; + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_peek); + debugfs_remove(debugfs_poke); + debugfs_remove(debugfs_wcd9xxx_dent); +#endif + wcd9xxx = slim_get_devicedata(pdev); + wcd9xxx_deinit_slimslave(wcd9xxx); + slim_remove_device(wcd9xxx->slim_slave); + wcd9xxx_disable_supplies(wcd9xxx, pdata); + wcd9xxx_device_exit(wcd9xxx); + return 0; +} + +static int wcd9xxx_resume(struct wcd9xxx *wcd9xxx) +{ + int ret = 0; + + pr_debug("%s: enter\n", __func__); + mutex_lock(&wcd9xxx->pm_lock); + if (wcd9xxx->pm_state == WCD9XXX_PM_ASLEEP) { + pr_debug("%s: resuming system, state %d, wlock %d\n", __func__, + wcd9xxx->pm_state, wcd9xxx->wlock_holders); + wcd9xxx->pm_state = WCD9XXX_PM_SLEEPABLE; + } else { + pr_warn("%s: system is already awake, state %d wlock %d\n", + __func__, wcd9xxx->pm_state, wcd9xxx->wlock_holders); + } + mutex_unlock(&wcd9xxx->pm_lock); + wake_up_all(&wcd9xxx->pm_wq); + + return ret; +} + +static int wcd9xxx_slim_resume(struct slim_device *sldev) +{ + struct wcd9xxx *wcd9xxx = slim_get_devicedata(sldev); + return wcd9xxx_resume(wcd9xxx); +} + +static int wcd9xxx_i2c_resume(struct i2c_client *i2cdev) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(&i2cdev->dev); + if (wcd9xxx) + return wcd9xxx_resume(wcd9xxx); + else + return 0; +} + +static int wcd9xxx_suspend(struct wcd9xxx *wcd9xxx, pm_message_t pmesg) +{ + int ret = 0; + + pr_debug("%s: enter\n", __func__); + /* wake_lock() can be called after this suspend chain call started. + * thus suspend can be called while wlock is being held */ + mutex_lock(&wcd9xxx->pm_lock); + if (wcd9xxx->pm_state == WCD9XXX_PM_SLEEPABLE) { + pr_debug("%s: suspending system, state %d, wlock %d\n", + __func__, wcd9xxx->pm_state, wcd9xxx->wlock_holders); + wcd9xxx->pm_state = WCD9XXX_PM_ASLEEP; + } else if (wcd9xxx->pm_state == WCD9XXX_PM_AWAKE) { + /* unlock to wait for pm_state == WCD9XXX_PM_SLEEPABLE + * then set to WCD9XXX_PM_ASLEEP */ + pr_debug("%s: waiting to suspend system, state %d, wlock %d\n", + __func__, wcd9xxx->pm_state, wcd9xxx->wlock_holders); + mutex_unlock(&wcd9xxx->pm_lock); + if (!(wait_event_timeout(wcd9xxx->pm_wq, + wcd9xxx_pm_cmpxchg(wcd9xxx, + WCD9XXX_PM_SLEEPABLE, + WCD9XXX_PM_ASLEEP) == + WCD9XXX_PM_SLEEPABLE, + HZ))) { + pr_debug("%s: suspend failed state %d, wlock %d\n", + __func__, wcd9xxx->pm_state, + wcd9xxx->wlock_holders); + ret = -EBUSY; + } else { + pr_debug("%s: done, state %d, wlock %d\n", __func__, + wcd9xxx->pm_state, wcd9xxx->wlock_holders); + } + mutex_lock(&wcd9xxx->pm_lock); + } else if (wcd9xxx->pm_state == WCD9XXX_PM_ASLEEP) { + pr_warn("%s: system is already suspended, state %d, wlock %dn", + __func__, wcd9xxx->pm_state, wcd9xxx->wlock_holders); + } + mutex_unlock(&wcd9xxx->pm_lock); + + return ret; +} + +static int wcd9xxx_slim_suspend(struct slim_device *sldev, pm_message_t pmesg) +{ + struct wcd9xxx *wcd9xxx = slim_get_devicedata(sldev); + return wcd9xxx_suspend(wcd9xxx, pmesg); +} + +static int wcd9xxx_i2c_suspend(struct i2c_client *i2cdev, pm_message_t pmesg) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(&i2cdev->dev); + if (wcd9xxx) + return wcd9xxx_suspend(wcd9xxx, pmesg); + else + return 0; +} + +static const struct slim_device_id sitar_slimtest_id[] = { + {"sitar-slim", 0}, + {} +}; +static struct slim_driver sitar_slim_driver = { + .driver = { + .name = "sitar-slim", + .owner = THIS_MODULE, + }, + .probe = wcd9xxx_slim_probe, + .remove = wcd9xxx_slim_remove, + .id_table = sitar_slimtest_id, + .resume = wcd9xxx_slim_resume, + .suspend = wcd9xxx_slim_suspend, +}; + +static const struct slim_device_id sitar1p1_slimtest_id[] = { + {"sitar1p1-slim", 0}, + {} +}; +static struct slim_driver sitar1p1_slim_driver = { + .driver = { + .name = "sitar1p1-slim", + .owner = THIS_MODULE, + }, + .probe = wcd9xxx_slim_probe, + .remove = wcd9xxx_slim_remove, + .id_table = sitar1p1_slimtest_id, + .resume = wcd9xxx_slim_resume, + .suspend = wcd9xxx_slim_suspend, +}; + +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 = wcd9xxx_slim_probe, + .remove = wcd9xxx_slim_remove, + .id_table = slimtest_id, + .resume = wcd9xxx_slim_resume, + .suspend = wcd9xxx_slim_suspend, +}; + +static const struct slim_device_id slimtest2x_id[] = { + {"tabla2x-slim", 0}, + {} +}; + +static struct slim_driver tabla2x_slim_driver = { + .driver = { + .name = "tabla2x-slim", + .owner = THIS_MODULE, + }, + .probe = wcd9xxx_slim_probe, + .remove = wcd9xxx_slim_remove, + .id_table = slimtest2x_id, + .resume = wcd9xxx_slim_resume, + .suspend = wcd9xxx_slim_suspend, +}; + +#define TABLA_I2C_TOP_LEVEL 0 +#define TABLA_I2C_ANALOG 1 +#define TABLA_I2C_DIGITAL_1 2 +#define TABLA_I2C_DIGITAL_2 3 + +static struct i2c_device_id tabla_id_table[] = { + {"tabla top level", TABLA_I2C_TOP_LEVEL}, + {"tabla analog", TABLA_I2C_TOP_LEVEL}, + {"tabla digital1", TABLA_I2C_TOP_LEVEL}, + {"tabla digital2", TABLA_I2C_TOP_LEVEL}, + {} +}; +MODULE_DEVICE_TABLE(i2c, tabla_id_table); + +static struct i2c_driver tabla_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "tabla-i2c-core", + }, + .id_table = tabla_id_table, + .probe = wcd9xxx_i2c_probe, + .remove = __devexit_p(wcd9xxx_i2c_remove), + .resume = wcd9xxx_i2c_resume, + .suspend = wcd9xxx_i2c_suspend, +}; + +static int __init wcd9xxx_init(void) +{ + int ret1, ret2, ret3, ret4, ret5; + + ret1 = slim_driver_register(&tabla_slim_driver); + if (ret1 != 0) + pr_err("Failed to register tabla SB driver: %d\n", ret1); + + ret2 = slim_driver_register(&tabla2x_slim_driver); + if (ret2 != 0) + pr_err("Failed to register tabla2x SB driver: %d\n", ret2); + + ret3 = i2c_add_driver(&tabla_i2c_driver); + if (ret3 != 0) + pr_err("failed to add the I2C driver\n"); + + ret4 = slim_driver_register(&sitar_slim_driver); + if (ret4 != 0) + pr_err("Failed to register sitar SB driver: %d\n", ret4); + + ret5 = slim_driver_register(&sitar1p1_slim_driver); + if (ret5 != 0) + pr_err("Failed to register sitar SB driver: %d\n", ret5); + + return (ret1 && ret2 && ret3 && ret4 && ret5) ? -1 : 0; +} +module_init(wcd9xxx_init); + +static void __exit wcd9xxx_exit(void) +{ +} +module_exit(wcd9xxx_exit); + +MODULE_DESCRIPTION("Codec core driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/wcd9xxx-irq.c b/drivers/mfd/wcd9xxx-irq.c new file mode 100644 index 0000000000000000000000000000000000000000..ba3c1e81f4ad6e1cc52d4d0cd23eb357df15b0fe --- /dev/null +++ b/drivers/mfd/wcd9xxx-irq.c @@ -0,0 +1,307 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 BYTE_BIT_MASK(nr) (1UL << ((nr) % BITS_PER_BYTE)) +#define BIT_BYTE(nr) ((nr) / BITS_PER_BYTE) + +struct wcd9xxx_irq { + bool level; +}; + +static struct wcd9xxx_irq wcd9xxx_irqs[TABLA_NUM_IRQS] = { + [0] = { .level = 1}, +/* All other wcd9xxx interrupts are edge triggered */ +}; + +static inline int irq_to_wcd9xxx_irq(struct wcd9xxx *wcd9xxx, int irq) +{ + return irq - wcd9xxx->irq_base; +} + +static void wcd9xxx_irq_lock(struct irq_data *data) +{ + struct wcd9xxx *wcd9xxx = irq_data_get_irq_chip_data(data); + mutex_lock(&wcd9xxx->irq_lock); +} + +static void wcd9xxx_irq_sync_unlock(struct irq_data *data) +{ + struct wcd9xxx *wcd9xxx = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(wcd9xxx->irq_masks_cur); i++) { + /* If there's been a change in the mask write it back + * to the hardware. + */ + if (wcd9xxx->irq_masks_cur[i] != wcd9xxx->irq_masks_cache[i]) { + wcd9xxx->irq_masks_cache[i] = wcd9xxx->irq_masks_cur[i]; + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MASK0+i, + wcd9xxx->irq_masks_cur[i]); + } + } + + mutex_unlock(&wcd9xxx->irq_lock); +} + +static void wcd9xxx_irq_enable(struct irq_data *data) +{ + struct wcd9xxx *wcd9xxx = irq_data_get_irq_chip_data(data); + int wcd9xxx_irq = irq_to_wcd9xxx_irq(wcd9xxx, data->irq); + wcd9xxx->irq_masks_cur[BIT_BYTE(wcd9xxx_irq)] &= + ~(BYTE_BIT_MASK(wcd9xxx_irq)); +} + +static void wcd9xxx_irq_disable(struct irq_data *data) +{ + struct wcd9xxx *wcd9xxx = irq_data_get_irq_chip_data(data); + int wcd9xxx_irq = irq_to_wcd9xxx_irq(wcd9xxx, data->irq); + wcd9xxx->irq_masks_cur[BIT_BYTE(wcd9xxx_irq)] + |= BYTE_BIT_MASK(wcd9xxx_irq); +} + +static struct irq_chip wcd9xxx_irq_chip = { + .name = "wcd9xxx", + .irq_bus_lock = wcd9xxx_irq_lock, + .irq_bus_sync_unlock = wcd9xxx_irq_sync_unlock, + .irq_disable = wcd9xxx_irq_disable, + .irq_enable = wcd9xxx_irq_enable, +}; + +enum wcd9xxx_pm_state wcd9xxx_pm_cmpxchg(struct wcd9xxx *wcd9xxx, + enum wcd9xxx_pm_state o, + enum wcd9xxx_pm_state n) +{ + enum wcd9xxx_pm_state old; + mutex_lock(&wcd9xxx->pm_lock); + old = wcd9xxx->pm_state; + if (old == o) + wcd9xxx->pm_state = n; + mutex_unlock(&wcd9xxx->pm_lock); + return old; +} +EXPORT_SYMBOL_GPL(wcd9xxx_pm_cmpxchg); + +bool wcd9xxx_lock_sleep(struct wcd9xxx *wcd9xxx) +{ + enum wcd9xxx_pm_state os; + + /* wcd9xxx_{lock/unlock}_sleep will be called by wcd9xxx_irq_thread + * and its subroutines only motly. + * but btn0_lpress_fn is not wcd9xxx_irq_thread's subroutine and + * it can race with wcd9xxx_irq_thread. + * so need to embrace wlock_holders with mutex. + */ + mutex_lock(&wcd9xxx->pm_lock); + if (wcd9xxx->wlock_holders++ == 0) { + pr_debug("%s: holding wake lock\n", __func__); + wake_lock(&wcd9xxx->wlock); + } + mutex_unlock(&wcd9xxx->pm_lock); + if (!wait_event_timeout(wcd9xxx->pm_wq, + ((os = wcd9xxx_pm_cmpxchg(wcd9xxx, WCD9XXX_PM_SLEEPABLE, + WCD9XXX_PM_AWAKE)) == + WCD9XXX_PM_SLEEPABLE || + (os == WCD9XXX_PM_AWAKE)), + 5 * HZ)) { + pr_err("%s: system didn't resume within 5000ms, state %d, " + "wlock %d\n", __func__, wcd9xxx->pm_state, + wcd9xxx->wlock_holders); + WARN_ON(1); + wcd9xxx_unlock_sleep(wcd9xxx); + return false; + } + wake_up_all(&wcd9xxx->pm_wq); + return true; +} +EXPORT_SYMBOL_GPL(wcd9xxx_lock_sleep); + +void wcd9xxx_unlock_sleep(struct wcd9xxx *wcd9xxx) +{ + mutex_lock(&wcd9xxx->pm_lock); + if (--wcd9xxx->wlock_holders == 0) { + wcd9xxx->pm_state = WCD9XXX_PM_SLEEPABLE; + pr_debug("%s: releasing wake lock\n", __func__); + wake_unlock(&wcd9xxx->wlock); + } + mutex_unlock(&wcd9xxx->pm_lock); + wake_up_all(&wcd9xxx->pm_wq); +} +EXPORT_SYMBOL_GPL(wcd9xxx_unlock_sleep); + +static void wcd9xxx_irq_dispatch(struct wcd9xxx *wcd9xxx, int irqbit) +{ + if ((irqbit <= TABLA_IRQ_MBHC_INSERTION) && + (irqbit >= TABLA_IRQ_MBHC_REMOVAL)) { + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 + + BIT_BYTE(irqbit), BYTE_BIT_MASK(irqbit)); + if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C) + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MODE, 0x02); + handle_nested_irq(wcd9xxx->irq_base + irqbit); + } else { + handle_nested_irq(wcd9xxx->irq_base + irqbit); + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 + + BIT_BYTE(irqbit), BYTE_BIT_MASK(irqbit)); + if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C) + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MODE, 0x02); + } +} + +static irqreturn_t wcd9xxx_irq_thread(int irq, void *data) +{ + int ret; + struct wcd9xxx *wcd9xxx = data; + u8 status[WCD9XXX_NUM_IRQ_REGS]; + int i; + + if (unlikely(wcd9xxx_lock_sleep(wcd9xxx) == false)) { + dev_err(wcd9xxx->dev, "Failed to hold suspend\n"); + return IRQ_NONE; + } + ret = wcd9xxx_bulk_read(wcd9xxx, TABLA_A_INTR_STATUS0, + WCD9XXX_NUM_IRQ_REGS, status); + if (ret < 0) { + dev_err(wcd9xxx->dev, "Failed to read interrupt status: %d\n", + ret); + wcd9xxx_unlock_sleep(wcd9xxx); + return IRQ_NONE; + } + /* Apply masking */ + for (i = 0; i < WCD9XXX_NUM_IRQ_REGS; i++) + status[i] &= ~wcd9xxx->irq_masks_cur[i]; + + /* Find out which interrupt was triggered and call that interrupt's + * handler function + */ + if (status[BIT_BYTE(TABLA_IRQ_SLIMBUS)] & + BYTE_BIT_MASK(TABLA_IRQ_SLIMBUS)) + wcd9xxx_irq_dispatch(wcd9xxx, TABLA_IRQ_SLIMBUS); + + /* Since codec has only one hardware irq line which is shared by + * codec's different internal interrupts, so it's possible master irq + * handler dispatches multiple nested irq handlers after breaking + * order. Dispatch MBHC interrupts order to follow MBHC state + * machine's order */ + for (i = TABLA_IRQ_MBHC_INSERTION; i >= TABLA_IRQ_MBHC_REMOVAL; i--) { + if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i)) + wcd9xxx_irq_dispatch(wcd9xxx, i); + } + for (i = TABLA_IRQ_BG_PRECHARGE; i < TABLA_NUM_IRQS; i++) { + if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i)) + wcd9xxx_irq_dispatch(wcd9xxx, i); + } + wcd9xxx_unlock_sleep(wcd9xxx); + + return IRQ_HANDLED; +} + +int wcd9xxx_irq_init(struct wcd9xxx *wcd9xxx) +{ + int ret; + unsigned int i, cur_irq; + + mutex_init(&wcd9xxx->irq_lock); + + if (!wcd9xxx->irq) { + dev_warn(wcd9xxx->dev, + "No interrupt specified, no interrupts\n"); + wcd9xxx->irq_base = 0; + return 0; + } + + if (!wcd9xxx->irq_base) { + dev_err(wcd9xxx->dev, + "No interrupt base specified, no interrupts\n"); + return 0; + } + /* Mask the individual interrupt sources */ + for (i = 0, cur_irq = wcd9xxx->irq_base; i < TABLA_NUM_IRQS; i++, + cur_irq++) { + + irq_set_chip_data(cur_irq, wcd9xxx); + + if (wcd9xxx_irqs[i].level) + irq_set_chip_and_handler(cur_irq, &wcd9xxx_irq_chip, + handle_level_irq); + else + irq_set_chip_and_handler(cur_irq, &wcd9xxx_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 + + wcd9xxx->irq_masks_cur[BIT_BYTE(i)] |= BYTE_BIT_MASK(i); + wcd9xxx->irq_masks_cache[BIT_BYTE(i)] |= BYTE_BIT_MASK(i); + wcd9xxx->irq_level[BIT_BYTE(i)] |= wcd9xxx_irqs[i].level << + (i % BITS_PER_BYTE); + } + for (i = 0; i < WCD9XXX_NUM_IRQ_REGS; i++) { + /* Initialize interrupt mask and level registers */ + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_LEVEL0 + i, + wcd9xxx->irq_level[i]); + wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MASK0 + i, + wcd9xxx->irq_masks_cur[i]); + } + + ret = request_threaded_irq(wcd9xxx->irq, NULL, wcd9xxx_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "wcd9xxx", wcd9xxx); + if (ret != 0) + dev_err(wcd9xxx->dev, "Failed to request IRQ %d: %d\n", + wcd9xxx->irq, ret); + else { + ret = enable_irq_wake(wcd9xxx->irq); + if (ret == 0) { + ret = device_init_wakeup(wcd9xxx->dev, 1); + if (ret) { + dev_err(wcd9xxx->dev, "Failed to init device" + "wakeup : %d\n", ret); + disable_irq_wake(wcd9xxx->irq); + } + } else + dev_err(wcd9xxx->dev, "Failed to set wake interrupt on" + " IRQ %d: %d\n", wcd9xxx->irq, ret); + if (ret) + free_irq(wcd9xxx->irq, wcd9xxx); + } + + if (ret) + mutex_destroy(&wcd9xxx->irq_lock); + + return ret; +} + +void wcd9xxx_irq_exit(struct wcd9xxx *wcd9xxx) +{ + if (wcd9xxx->irq) { + disable_irq_wake(wcd9xxx->irq); + free_irq(wcd9xxx->irq, wcd9xxx); + device_init_wakeup(wcd9xxx->dev, 0); + } + mutex_destroy(&wcd9xxx->irq_lock); +} diff --git a/drivers/mfd/wcd9xxx-slimslave.c b/drivers/mfd/wcd9xxx-slimslave.c new file mode 100644 index 0000000000000000000000000000000000000000..538ca7c1d003f81bab39b7c5b4d01ae7d286c540 --- /dev/null +++ b/drivers/mfd/wcd9xxx-slimslave.c @@ -0,0 +1,528 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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 wcd9xxx_slim_sch_rx { + u32 sph; + u32 ch_num; + u16 ch_h; + u16 grph; +}; + +struct wcd9xxx_slim_sch_tx { + u32 sph; + u32 ch_num; + u16 ch_h; + u16 grph; +}; + +struct wcd9xxx_slim_sch { + struct wcd9xxx_slim_sch_rx rx[SLIM_MAX_RX_PORTS]; + struct wcd9xxx_slim_sch_tx tx[SLIM_MAX_TX_PORTS]; +}; + +static struct wcd9xxx_slim_sch sh_ch; + +static int wcd9xxx_alloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx, + u8 wcd9xxx_pgd_la); +static int wcd9xxx_alloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx, + u8 wcd9xxx_pgd_la); +static int wcd9xxx_dealloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx); +static int wcd9xxx_dealloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx); + +int wcd9xxx_init_slimslave(struct wcd9xxx *wcd9xxx, u8 wcd9xxx_pgd_la) +{ + int ret = 0; + + ret = wcd9xxx_alloc_slim_sh_ch_rx(wcd9xxx, wcd9xxx_pgd_la); + if (ret) { + pr_err("%s: Failed to alloc rx slimbus shared channels\n", + __func__); + goto rx_err; + } + ret = wcd9xxx_alloc_slim_sh_ch_tx(wcd9xxx, wcd9xxx_pgd_la); + if (ret) { + pr_err("%s: Failed to alloc tx slimbus shared channels\n", + __func__); + goto tx_err; + } + return 0; +tx_err: + wcd9xxx_dealloc_slim_sh_ch_rx(wcd9xxx); +rx_err: + return ret; +} + + +int wcd9xxx_deinit_slimslave(struct wcd9xxx *wcd9xxx) +{ + int ret = 0; + ret = wcd9xxx_dealloc_slim_sh_ch_rx(wcd9xxx); + if (ret < 0) { + pr_err("%s: fail to dealloc rx slim ports\n", __func__); + goto err; + } + ret = wcd9xxx_dealloc_slim_sh_ch_tx(wcd9xxx); + if (ret < 0) { + pr_err("%s: fail to dealloc tx slim ports\n", __func__); + goto err; + } +err: + return ret; +} + +int wcd9xxx_get_channel(struct wcd9xxx *wcd9xxx, + unsigned int *rx_ch, + unsigned int *tx_ch) +{ + int ch_idx = 0; + struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; + struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; + + for (ch_idx = 0; ch_idx < SLIM_MAX_RX_PORTS; ch_idx++) + rx_ch[ch_idx] = rx[ch_idx].ch_num; + for (ch_idx = 0; ch_idx < SLIM_MAX_TX_PORTS; ch_idx++) + tx_ch[ch_idx] = tx[ch_idx].ch_num; + return 0; +} + +static int wcd9xxx_alloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx, + u8 wcd9xxx_pgd_la) +{ + int ret = 0; + u8 ch_idx ; + u16 slave_port_id = 0; + struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; + + /* + * DSP requires channel number to be between 128 and 255. + */ + pr_debug("%s: pgd_la[%d]\n", __func__, wcd9xxx_pgd_la); + for (ch_idx = 0; ch_idx < SLIM_MAX_RX_PORTS; ch_idx++) { + slave_port_id = (ch_idx + 1 + + SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS); + rx[ch_idx].ch_num = slave_port_id + BASE_CH_NUM; + ret = slim_get_slaveport(wcd9xxx_pgd_la, slave_port_id, + &rx[ch_idx].sph, SLIM_SINK); + if (ret < 0) { + pr_err("%s: slave port failure id[%d] ret[%d]\n", + __func__, slave_port_id, ret); + goto err; + } + + ret = slim_query_ch(wcd9xxx->slim, rx[ch_idx].ch_num, + &rx[ch_idx].ch_h); + if (ret < 0) { + pr_err("%s: slim_query_ch failed ch-num[%d] ret[%d]\n", + __func__, rx[ch_idx].ch_num, ret); + goto err; + } + } +err: + return ret; +} + +static int wcd9xxx_alloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx, + u8 wcd9xxx_pgd_la) +{ + int ret = 0; + u8 ch_idx ; + struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; + u16 slave_port_id = 0; + + pr_debug("%s: pgd_la[%d]\n", __func__, wcd9xxx_pgd_la); + /* DSP requires channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + */ + for (ch_idx = 0; ch_idx < SLIM_MAX_TX_PORTS; ch_idx++) { + slave_port_id = ch_idx; + tx[ch_idx].ch_num = slave_port_id + BASE_CH_NUM; + ret = slim_get_slaveport(wcd9xxx_pgd_la, slave_port_id, + &tx[ch_idx].sph, SLIM_SRC); + if (ret < 0) { + pr_err("%s: slave port failure id[%d] ret[%d]\n", + __func__, slave_port_id, ret); + goto err; + } + ret = slim_query_ch(wcd9xxx->slim, tx[ch_idx].ch_num, + &tx[ch_idx].ch_h); + if (ret < 0) { + pr_err("%s: slim_query_ch failed ch-num[%d] ret[%d]\n", + __func__, tx[ch_idx].ch_num, ret); + goto err; + } + } +err: + return ret; +} + +static int wcd9xxx_dealloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx) +{ + int idx = 0; + int ret = 0; + struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; + /* slim_dealloc_ch */ + for (idx = 0; idx < SLIM_MAX_RX_PORTS; idx++) { + ret = slim_dealloc_ch(wcd9xxx->slim, rx[idx].ch_h); + if (ret < 0) { + pr_err("%s: slim_dealloc_ch fail ret[%d] ch_h[%d]\n", + __func__, ret, rx[idx].ch_h); + } + } + memset(sh_ch.rx, 0, sizeof(sh_ch.rx)); + return ret; +} + +static int wcd9xxx_dealloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx) +{ + int idx = 0; + int ret = 0; + struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; + /* slim_dealloc_ch */ + for (idx = 0; idx < SLIM_MAX_TX_PORTS; idx++) { + ret = slim_dealloc_ch(wcd9xxx->slim, tx[idx].ch_h); + if (ret < 0) { + pr_err("%s: slim_dealloc_ch fail ret[%d] ch_h[%d]\n", + __func__, ret, tx[idx].ch_h); + } + } + memset(sh_ch.tx, 0, sizeof(sh_ch.tx)); + return ret; +} + +/* Enable slimbus slave device for RX path */ +int wcd9xxx_cfg_slim_sch_rx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int ch_cnt, unsigned int rate) +{ + u8 i = 0; + u16 grph; + u32 sph[SLIM_MAX_RX_PORTS] = {0}; + u16 ch_h[SLIM_MAX_RX_PORTS] = {0}; + u16 slave_port_id; + u8 payload_rx = 0, wm_payload = 0; + int ret, idx = 0; + unsigned short multi_chan_cfg_reg_addr; + struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; + struct slim_ch prop; + + /* Configure slave interface device */ + pr_debug("%s: ch_cnt[%d] rate=%d\n", __func__, ch_cnt, rate); + + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM - + SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); + ch_h[i] = rx[idx].ch_h; + sph[i] = rx[idx].sph; + slave_port_id = idx + 1; + if ((slave_port_id > SB_PGD_MAX_NUMBER_OF_RX_SLAVE_DEV_PORTS) || + (slave_port_id == 0)) { + pr_err("Slimbus: invalid slave port id: %d", + slave_port_id); + ret = -EINVAL; + goto err; + } + slave_port_id += SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS; + /* look for the valid port range and chose the + * payload accordingly + */ + if ((slave_port_id > + SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID) && + (slave_port_id <= + SB_PGD_RX_PORT_MULTI_CHANNEL_0_END_PORT_ID)) { + payload_rx = payload_rx | + (1 << + (slave_port_id - + SB_PGD_RX_PORT_MULTI_CHANNEL_0_START_PORT_ID)); + } else { + ret = -EINVAL; + goto err; + } + multi_chan_cfg_reg_addr = + SB_PGD_RX_PORT_MULTI_CHANNEL_0(slave_port_id); + /* write to interface device */ + ret = wcd9xxx_interface_reg_write(wcd9xxx, + multi_chan_cfg_reg_addr, + payload_rx); + if (ret < 0) { + pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n", + __func__, + multi_chan_cfg_reg_addr, + payload_rx, ret); + goto err; + } + /* configure the slave port for water mark and enable*/ + wm_payload = (SLAVE_PORT_WATER_MARK_VALUE << + SLAVE_PORT_WATER_MARK_SHIFT) + + SLAVE_PORT_ENABLE; + ret = wcd9xxx_interface_reg_write(wcd9xxx, + SB_PGD_PORT_CFG_BYTE_ADDR(slave_port_id), + wm_payload); + if (ret < 0) { + pr_err("%s:watermark set failure for port[%d] ret[%d]", + __func__, slave_port_id, ret); + } + } + + /* slim_define_ch api */ + prop.prot = SLIM_AUTO_ISO; + prop.baser = SLIM_RATE_4000HZ; + prop.dataf = SLIM_CH_DATAF_NOT_DEFINED; + prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE; + prop.ratem = (rate/4000); + prop.sampleszbits = 16; + + ret = slim_define_ch(wcd9xxx->slim, &prop, ch_h, ch_cnt, + true, &grph); + if (ret < 0) { + pr_err("%s: slim_define_ch failed ret[%d]\n", + __func__, ret); + goto err; + } + for (i = 0; i < ch_cnt; i++) { + ret = slim_connect_sink(wcd9xxx->slim, &sph[i], + 1, ch_h[i]); + if (ret < 0) { + pr_err("%s: slim_connect_sink failed ret[%d]\n", + __func__, ret); + goto err_close_slim_sch; + } + } + /* slim_control_ch */ + ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_ACTIVATE, + true); + if (ret < 0) { + pr_err("%s: slim_control_ch failed ret[%d]\n", + __func__, ret); + goto err_close_slim_sch; + } + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM - + SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); + rx[idx].grph = grph; + } + return 0; + +err_close_slim_sch: + /* release all acquired handles */ + wcd9xxx_close_slim_sch_rx(wcd9xxx, ch_num, ch_cnt); +err: + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_cfg_slim_sch_rx); + +/* Enable slimbus slave device for RX path */ +int wcd9xxx_cfg_slim_sch_tx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int ch_cnt, unsigned int rate) +{ + u8 i = 0; + u8 payload_tx_0 = 0, payload_tx_1 = 0, wm_payload = 0; + u16 grph; + u32 sph[SLIM_MAX_TX_PORTS] = {0}; + u16 ch_h[SLIM_MAX_TX_PORTS] = {0}; + u16 idx = 0, slave_port_id; + int ret = 0; + unsigned short multi_chan_cfg_reg_addr; + + struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; + struct slim_ch prop; + + pr_debug("%s: ch_cnt[%d] rate[%d]\n", __func__, ch_cnt, rate); + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM); + ch_h[i] = tx[idx].ch_h; + sph[i] = tx[idx].sph; + slave_port_id = idx ; + if (slave_port_id > SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS) { + pr_err("SLIMbus: invalid slave port id: %d", + slave_port_id); + ret = -EINVAL; + goto err; + } + /* look for the valid port range and chose the + * payload accordingly + */ + if (slave_port_id <= + SB_PGD_TX_PORT_MULTI_CHANNEL_0_END_PORT_ID) { + payload_tx_0 = payload_tx_0 | (1 << slave_port_id); + } else if (slave_port_id <= + SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID) { + payload_tx_1 = payload_tx_1 | + (1 << + (slave_port_id - + SB_PGD_TX_PORT_MULTI_CHANNEL_1_START_PORT_ID)); + } else { + ret = -EINVAL; + goto err; + } + multi_chan_cfg_reg_addr = + SB_PGD_TX_PORT_MULTI_CHANNEL_0(slave_port_id); + /* write to interface device */ + ret = wcd9xxx_interface_reg_write(wcd9xxx, + multi_chan_cfg_reg_addr, + payload_tx_0); + if (ret < 0) { + pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n", + __func__, + multi_chan_cfg_reg_addr, + payload_tx_0, ret); + goto err; + } + multi_chan_cfg_reg_addr = + SB_PGD_TX_PORT_MULTI_CHANNEL_1(slave_port_id); + /* ports 8,9 */ + ret = wcd9xxx_interface_reg_write(wcd9xxx, + multi_chan_cfg_reg_addr, + payload_tx_1); + if (ret < 0) { + pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n", + __func__, + multi_chan_cfg_reg_addr, + payload_tx_1, ret); + goto err; + } + /* configure the slave port for water mark and enable*/ + wm_payload = (SLAVE_PORT_WATER_MARK_VALUE << + SLAVE_PORT_WATER_MARK_SHIFT) + + SLAVE_PORT_ENABLE; + ret = wcd9xxx_interface_reg_write(wcd9xxx, + SB_PGD_PORT_CFG_BYTE_ADDR(slave_port_id), + wm_payload); + if (ret < 0) { + pr_err("%s:watermark set failure for port[%d] ret[%d]", + __func__, + slave_port_id, ret); + } + } + + /* slim_define_ch api */ + prop.prot = SLIM_AUTO_ISO; + prop.baser = SLIM_RATE_4000HZ; + prop.dataf = SLIM_CH_DATAF_NOT_DEFINED; + prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE; + prop.ratem = (rate/4000); + prop.sampleszbits = 16; + ret = slim_define_ch(wcd9xxx->slim, &prop, ch_h, ch_cnt, + true, &grph); + if (ret < 0) { + pr_err("%s: slim_define_ch failed ret[%d]\n", + __func__, ret); + goto err; + } + for (i = 0; i < ch_cnt; i++) { + ret = slim_connect_src(wcd9xxx->slim, sph[i], + ch_h[i]); + if (ret < 0) { + pr_err("%s: slim_connect_src failed ret[%d]\n", + __func__, ret); + goto err; + } + } + /* slim_control_ch */ + ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_ACTIVATE, + true); + if (ret < 0) { + pr_err("%s: slim_control_ch failed ret[%d]\n", + __func__, ret); + goto err; + } + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM); + tx[idx].grph = grph; + } + return 0; +err: + /* release all acquired handles */ + wcd9xxx_close_slim_sch_tx(wcd9xxx, ch_num, ch_cnt); + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_cfg_slim_sch_tx); + +int wcd9xxx_close_slim_sch_rx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int ch_cnt) +{ + u16 grph = 0; + u32 sph[SLIM_MAX_RX_PORTS] = {0}; + int i = 0 , idx = 0; + int ret = 0; + struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; + + pr_debug("%s: ch_cnt[%d]\n", __func__, ch_cnt); + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM - + SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); + if (idx < 0) { + pr_err("%s: Error:-Invalid index found = %d\n", + __func__, idx); + ret = -EINVAL; + goto err; + } + sph[i] = rx[idx].sph; + grph = rx[idx].grph; + } + + /* slim_control_ch (REMOVE) */ + ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_REMOVE, true); + if (ret < 0) { + pr_err("%s: slim_control_ch failed ret[%d]\n", + __func__, ret); + goto err; + } + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM - + SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); + rx[idx].grph = 0; + } +err: + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_close_slim_sch_rx); + +int wcd9xxx_close_slim_sch_tx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int ch_cnt) +{ + u16 grph = 0; + u32 sph[SLIM_MAX_TX_PORTS] = {0}; + int ret = 0; + int i = 0 , idx = 0; + struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; + + pr_debug("%s: ch_cnt[%d]\n", __func__, ch_cnt); + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM); + if (idx < 0) { + pr_err("%s: Error:- Invalid index found = %d\n", + __func__, idx); + ret = -EINVAL; + goto err; + } + sph[i] = tx[idx].sph; + grph = tx[idx].grph; + } + + /* slim_control_ch (REMOVE) */ + ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_REMOVE, true); + if (ret < 0) { + pr_err("%s: slim_control_ch failed ret[%d]\n", + __func__, ret); + goto err; + } + for (i = 0; i < ch_cnt; i++) { + idx = (ch_num[i] - BASE_CH_NUM); + tx[idx].grph = 0; + } +err: + return ret; +} +EXPORT_SYMBOL_GPL(wcd9xxx_close_slim_sch_tx); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 1a0254a15c5797b145ff11addc02e1089a706ebb..1c2f018c5e37a4277d072a862e07938a120224ef 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -51,6 +51,10 @@ config AD525X_DPOT_SPI To compile this driver as a module, choose M here: the module will be called ad525x_dpot-spi. +config ANDROID_PMEM + bool "Android pmem allocator" + default y + config ATMEL_PWM tristate "Atmel AT32/AT91 PWM support" depends on HAVE_CLK @@ -517,7 +521,146 @@ config WL127X_RFKILL ---help--- Creates an rfkill entry in sysfs for power control of Bluetooth TI wl127x chips. - + +config APANIC + bool "Android kernel panic diagnostics driver" + default n + ---help--- + Driver which handles kernel panics and attempts to write + critical debugging data to flash. + +config APANIC_PLABEL + string "Android panic dump flash partition label" + depends on APANIC + default "kpanic" + ---help--- + 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 TSPP + depends on ARCH_MSM + tristate "TSPP (Transport Stream Packet Processor) Support" + ---help--- + Transport Stream Packet Processor is used to offload the + processing of MPEG transport streams from the main processor. + This can also be compiled as a loadable module. + +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 PMIC8XXX_VIBRATOR + tristate "Qualcomm Vibrator support for PMIC8XXX" + depends on MFD_PM8XXX && ANDROID_TIMED_OUTPUT + help + This option enables device driver support for the vibrator + on the PM8XXX chips. The vibrator is controlled using the + timed output class. + +config PMIC8XXX_NFC + tristate "Qualcomm PM8XXX support for Near Field Communication" + depends on MFD_PM8XXX + help + Qualcomm PM8XXX chips have a module to support NFC (Near Field + Communication). This option enables the driver to support it. + +config PMIC8XXX_UPL + tristate "Qualcomm PM8XXX support for User Programmable Logic" + depends on MFD_PM8XXX + help + This option enables device driver support for User Programmable Logic + on Qualcomm PM8XXX chips. 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 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 QSEECOM + tristate "Qualcomm Secure Execution Communicator driver" + help + Provides a communication interface between userspace and + Qualcomm Secure Execution Environment (QSEE) 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. + +config USB_HSIC_SMSC_HUB + tristate "Support for HSIC based MSM on-chip SMSC3503 HUB" + depends on USB_EHCI_MSM_HSIC + help + Enables support for the HSIC (High Speed Inter-Chip) based + SMSC3503 hub controller present on the Qualcomm chipsets. + + This adds support for connecting devices like mouse in HSIC + Host mode. + 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 89540d14f841eed198f1db6b1e4046348c32f57b..b6289763ed00ec02cfdbdc7997fede3b1e1cae96 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o +obj-$(CONFIG_ANDROID_PMEM) += pmem.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o @@ -29,6 +30,7 @@ obj-$(CONFIG_HP_ILO) += hpilo.o obj-$(CONFIG_APDS9802ALS) += apds9802als.o obj-$(CONFIG_ISL29003) += isl29003.o obj-$(CONFIG_ISL29020) += isl29020.o +obj-$(CONFIG_USB_HSIC_SMSC_HUB) += smsc_hub.o obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o obj-$(CONFIG_DS1682) += ds1682.o @@ -52,3 +54,21 @@ obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o obj-$(CONFIG_SENSORS_AK8975) += akm8975.o +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_TSPP) += tspp.o +obj-$(CONFIG_HAPTIC_ISA1200) += isa1200.o +obj-$(CONFIG_PMIC8058_PWM) += pmic8058-pwm.o +obj-$(CONFIG_PMIC8XXX_VIBRATOR) += pm8xxx-vibrator.o +obj-$(CONFIG_PMIC8XXX_NFC) += pm8xxx-nfc.o +obj-$(CONFIG_PMIC8XXX_UPL) += pm8xxx-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_TZCOM) += tzcom.o +obj-$(CONFIG_QSEECOM) += qseecom.o +obj-$(CONFIG_QFP_FUSE) += qfp_fuse.o diff --git a/drivers/misc/apanic.c b/drivers/misc/apanic.c new file mode 100644 index 0000000000000000000000000000000000000000..ca875f89da7a7ffe4dfde8eda532f26c800d50f9 --- /dev/null +++ b/drivers/misc/apanic.c @@ -0,0 +1,606 @@ +/* drivers/misc/apanic.c + * + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern void ram_console_enable_console(int); + +struct panic_header { + u32 magic; +#define PANIC_MAGIC 0xdeadf00d + + u32 version; +#define PHDR_VERSION 0x01 + + u32 console_offset; + u32 console_length; + + u32 threads_offset; + u32 threads_length; +}; + +struct apanic_data { + struct mtd_info *mtd; + struct panic_header curr; + void *bounce; + struct proc_dir_entry *apanic_console; + struct proc_dir_entry *apanic_threads; +}; + +static struct apanic_data drv_ctx; +static struct work_struct proc_removal_work; +static DEFINE_MUTEX(drv_mutex); + +static unsigned int *apanic_bbt; +static unsigned int apanic_erase_blocks; +static unsigned int apanic_good_blocks; + +static void set_bb(unsigned int block, unsigned int *bbt) +{ + unsigned int flag = 1; + + BUG_ON(block >= apanic_erase_blocks); + + flag = flag << (block%32); + apanic_bbt[block/32] |= flag; + apanic_good_blocks--; +} + +static unsigned int get_bb(unsigned int block, unsigned int *bbt) +{ + unsigned int flag; + + BUG_ON(block >= apanic_erase_blocks); + + flag = 1 << (block%32); + return apanic_bbt[block/32] & flag; +} + +static void alloc_bbt(struct mtd_info *mtd, unsigned int *bbt) +{ + int bbt_size; + apanic_erase_blocks = (mtd->size)>>(mtd->erasesize_shift); + bbt_size = (apanic_erase_blocks+32)/32; + + apanic_bbt = kmalloc(bbt_size*4, GFP_KERNEL); + memset(apanic_bbt, 0, bbt_size*4); + apanic_good_blocks = apanic_erase_blocks; +} +static void scan_bbt(struct mtd_info *mtd, unsigned int *bbt) +{ + int i; + + for (i = 0; i < apanic_erase_blocks; i++) { + if (mtd->block_isbad(mtd, i*mtd->erasesize)) + set_bb(i, apanic_bbt); + } +} + +#define APANIC_INVALID_OFFSET 0xFFFFFFFF + +static unsigned int phy_offset(struct mtd_info *mtd, unsigned int offset) +{ + unsigned int logic_block = offset>>(mtd->erasesize_shift); + unsigned int phy_block; + unsigned good_block = 0; + + for (phy_block = 0; phy_block < apanic_erase_blocks; phy_block++) { + if (!get_bb(phy_block, apanic_bbt)) + good_block++; + if (good_block == (logic_block + 1)) + break; + } + + if (good_block != (logic_block + 1)) + return APANIC_INVALID_OFFSET; + + return offset + ((phy_block-logic_block)<erasesize_shift); +} + +static void apanic_erase_callback(struct erase_info *done) +{ + wait_queue_head_t *wait_q = (wait_queue_head_t *) done->priv; + wake_up(wait_q); +} + +static int apanic_proc_read(char *buffer, char **start, off_t offset, + int count, int *peof, void *dat) +{ + struct apanic_data *ctx = &drv_ctx; + size_t file_length; + off_t file_offset; + unsigned int page_no; + off_t page_offset; + int rc; + size_t len; + + if (!count) + return 0; + + mutex_lock(&drv_mutex); + + switch ((int) dat) { + case 1: /* apanic_console */ + file_length = ctx->curr.console_length; + file_offset = ctx->curr.console_offset; + break; + case 2: /* apanic_threads */ + file_length = ctx->curr.threads_length; + file_offset = ctx->curr.threads_offset; + break; + default: + pr_err("Bad dat (%d)\n", (int) dat); + mutex_unlock(&drv_mutex); + return -EINVAL; + } + + if ((offset + count) > file_length) { + mutex_unlock(&drv_mutex); + return 0; + } + + /* We only support reading a maximum of a flash page */ + if (count > ctx->mtd->writesize) + count = ctx->mtd->writesize; + + page_no = (file_offset + offset) / ctx->mtd->writesize; + page_offset = (file_offset + offset) % ctx->mtd->writesize; + + + if (phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize)) + == APANIC_INVALID_OFFSET) { + pr_err("apanic: reading an invalid address\n"); + mutex_unlock(&drv_mutex); + return -EINVAL; + } + rc = ctx->mtd->read(ctx->mtd, + phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize)), + ctx->mtd->writesize, + &len, ctx->bounce); + + if (page_offset) + count -= page_offset; + memcpy(buffer, ctx->bounce + page_offset, count); + + *start = count; + + if ((offset + count) == file_length) + *peof = 1; + + mutex_unlock(&drv_mutex); + return count; +} + +static void mtd_panic_erase(void) +{ + struct apanic_data *ctx = &drv_ctx; + struct erase_info erase; + DECLARE_WAITQUEUE(wait, current); + wait_queue_head_t wait_q; + int rc, i; + + init_waitqueue_head(&wait_q); + erase.mtd = ctx->mtd; + erase.callback = apanic_erase_callback; + erase.len = ctx->mtd->erasesize; + erase.priv = (u_long)&wait_q; + for (i = 0; i < ctx->mtd->size; i += ctx->mtd->erasesize) { + erase.addr = i; + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&wait_q, &wait); + + if (get_bb(erase.addr>>ctx->mtd->erasesize_shift, apanic_bbt)) { + printk(KERN_WARNING + "apanic: Skipping erase of bad " + "block @%llx\n", erase.addr); + set_current_state(TASK_RUNNING); + remove_wait_queue(&wait_q, &wait); + continue; + } + + rc = ctx->mtd->erase(ctx->mtd, &erase); + if (rc) { + set_current_state(TASK_RUNNING); + remove_wait_queue(&wait_q, &wait); + printk(KERN_ERR + "apanic: Erase of 0x%llx, 0x%llx failed\n", + (unsigned long long) erase.addr, + (unsigned long long) erase.len); + if (rc == -EIO) { + if (ctx->mtd->block_markbad(ctx->mtd, + erase.addr)) { + printk(KERN_ERR + "apanic: Err marking blk bad\n"); + goto out; + } + printk(KERN_INFO + "apanic: Marked a bad block" + " @%llx\n", erase.addr); + set_bb(erase.addr>>ctx->mtd->erasesize_shift, + apanic_bbt); + continue; + } + goto out; + } + schedule(); + remove_wait_queue(&wait_q, &wait); + } + printk(KERN_DEBUG "apanic: %s partition erased\n", + CONFIG_APANIC_PLABEL); +out: + return; +} + +static void apanic_remove_proc_work(struct work_struct *work) +{ + struct apanic_data *ctx = &drv_ctx; + + mutex_lock(&drv_mutex); + mtd_panic_erase(); + memset(&ctx->curr, 0, sizeof(struct panic_header)); + if (ctx->apanic_console) { + remove_proc_entry("apanic_console", NULL); + ctx->apanic_console = NULL; + } + if (ctx->apanic_threads) { + remove_proc_entry("apanic_threads", NULL); + ctx->apanic_threads = NULL; + } + mutex_unlock(&drv_mutex); +} + +static int apanic_proc_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + schedule_work(&proc_removal_work); + return count; +} + +static void mtd_panic_notify_add(struct mtd_info *mtd) +{ + struct apanic_data *ctx = &drv_ctx; + struct panic_header *hdr = ctx->bounce; + size_t len; + int rc; + int proc_entry_created = 0; + + if (strcmp(mtd->name, CONFIG_APANIC_PLABEL)) + return; + + ctx->mtd = mtd; + + alloc_bbt(mtd, apanic_bbt); + scan_bbt(mtd, apanic_bbt); + + if (apanic_good_blocks == 0) { + printk(KERN_ERR "apanic: no any good blocks?!\n"); + goto out_err; + } + + rc = mtd->read(mtd, phy_offset(mtd, 0), mtd->writesize, + &len, ctx->bounce); + if (rc && rc == -EBADMSG) { + printk(KERN_WARNING + "apanic: Bad ECC on block 0 (ignored)\n"); + } else if (rc && rc != -EUCLEAN) { + printk(KERN_ERR "apanic: Error reading block 0 (%d)\n", rc); + goto out_err; + } + + if (len != mtd->writesize) { + printk(KERN_ERR "apanic: Bad read size (%d)\n", rc); + goto out_err; + } + + printk(KERN_INFO "apanic: Bound to mtd partition '%s'\n", mtd->name); + + if (hdr->magic != PANIC_MAGIC) { + printk(KERN_INFO "apanic: No panic data available\n"); + mtd_panic_erase(); + return; + } + + if (hdr->version != PHDR_VERSION) { + printk(KERN_INFO "apanic: Version mismatch (%d != %d)\n", + hdr->version, PHDR_VERSION); + mtd_panic_erase(); + return; + } + + memcpy(&ctx->curr, hdr, sizeof(struct panic_header)); + + printk(KERN_INFO "apanic: c(%u, %u) t(%u, %u)\n", + hdr->console_offset, hdr->console_length, + hdr->threads_offset, hdr->threads_length); + + if (hdr->console_length) { + ctx->apanic_console = create_proc_entry("apanic_console", + S_IFREG | S_IRUGO, NULL); + if (!ctx->apanic_console) + printk(KERN_ERR "%s: failed creating procfile\n", + __func__); + else { + ctx->apanic_console->read_proc = apanic_proc_read; + ctx->apanic_console->write_proc = apanic_proc_write; + ctx->apanic_console->size = hdr->console_length; + ctx->apanic_console->data = (void *) 1; + proc_entry_created = 1; + } + } + + if (hdr->threads_length) { + ctx->apanic_threads = create_proc_entry("apanic_threads", + S_IFREG | S_IRUGO, NULL); + if (!ctx->apanic_threads) + printk(KERN_ERR "%s: failed creating procfile\n", + __func__); + else { + ctx->apanic_threads->read_proc = apanic_proc_read; + ctx->apanic_threads->write_proc = apanic_proc_write; + ctx->apanic_threads->size = hdr->threads_length; + ctx->apanic_threads->data = (void *) 2; + proc_entry_created = 1; + } + } + + if (!proc_entry_created) + mtd_panic_erase(); + + return; +out_err: + ctx->mtd = NULL; +} + +static void mtd_panic_notify_remove(struct mtd_info *mtd) +{ + struct apanic_data *ctx = &drv_ctx; + if (mtd == ctx->mtd) { + ctx->mtd = NULL; + printk(KERN_INFO "apanic: Unbound from %s\n", mtd->name); + } +} + +static struct mtd_notifier mtd_panic_notifier = { + .add = mtd_panic_notify_add, + .remove = mtd_panic_notify_remove, +}; + +static int in_panic = 0; + +static int apanic_writeflashpage(struct mtd_info *mtd, loff_t to, + const u_char *buf) +{ + int rc; + size_t wlen; + int panic = in_interrupt() | in_atomic(); + + if (panic && !mtd->panic_write) { + printk(KERN_EMERG "%s: No panic_write available\n", __func__); + return 0; + } else if (!panic && !mtd->write) { + printk(KERN_EMERG "%s: No write available\n", __func__); + return 0; + } + + to = phy_offset(mtd, to); + if (to == APANIC_INVALID_OFFSET) { + printk(KERN_EMERG "apanic: write to invalid address\n"); + return 0; + } + + if (panic) + rc = mtd->panic_write(mtd, to, mtd->writesize, &wlen, buf); + else + rc = mtd->write(mtd, to, mtd->writesize, &wlen, buf); + + if (rc) { + printk(KERN_EMERG + "%s: Error writing data to flash (%d)\n", + __func__, rc); + return rc; + } + + return wlen; +} + +extern int log_buf_copy(char *dest, int idx, int len); +extern void log_buf_clear(void); + +/* + * Writes the contents of the console to the specified offset in flash. + * Returns number of bytes written + */ +static int apanic_write_console(struct mtd_info *mtd, unsigned int off) +{ + struct apanic_data *ctx = &drv_ctx; + int saved_oip; + int idx = 0; + int rc, rc2; + unsigned int last_chunk = 0; + + while (!last_chunk) { + saved_oip = oops_in_progress; + oops_in_progress = 1; + rc = log_buf_copy(ctx->bounce, idx, mtd->writesize); + if (rc < 0) + break; + + if (rc != mtd->writesize) + last_chunk = rc; + + oops_in_progress = saved_oip; + if (rc <= 0) + break; + if (rc != mtd->writesize) + memset(ctx->bounce + rc, 0, mtd->writesize - rc); + + rc2 = apanic_writeflashpage(mtd, off, ctx->bounce); + if (rc2 <= 0) { + printk(KERN_EMERG + "apanic: Flash write failed (%d)\n", rc2); + return idx; + } + if (!last_chunk) + idx += rc2; + else + idx += last_chunk; + off += rc2; + } + return idx; +} + +static int apanic(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct apanic_data *ctx = &drv_ctx; + struct panic_header *hdr = (struct panic_header *) ctx->bounce; + int console_offset = 0; + int console_len = 0; + int threads_offset = 0; + int threads_len = 0; + int rc; + + if (in_panic) + return NOTIFY_DONE; + in_panic = 1; +#ifdef CONFIG_PREEMPT + /* Ensure that cond_resched() won't try to preempt anybody */ + add_preempt_count(PREEMPT_ACTIVE); +#endif + touch_softlockup_watchdog(); + + if (!ctx->mtd) + goto out; + + if (ctx->curr.magic) { + printk(KERN_EMERG "Crash partition in use!\n"); + goto out; + } + console_offset = ctx->mtd->writesize; + + /* + * Write out the console + */ + console_len = apanic_write_console(ctx->mtd, console_offset); + if (console_len < 0) { + printk(KERN_EMERG "Error writing console to panic log! (%d)\n", + console_len); + console_len = 0; + } + + /* + * Write out all threads + */ + threads_offset = ALIGN(console_offset + console_len, + ctx->mtd->writesize); + if (!threads_offset) + threads_offset = ctx->mtd->writesize; + + ram_console_enable_console(0); + + log_buf_clear(); + show_state_filter(0); + threads_len = apanic_write_console(ctx->mtd, threads_offset); + if (threads_len < 0) { + printk(KERN_EMERG "Error writing threads to panic log! (%d)\n", + threads_len); + threads_len = 0; + } + + /* + * Finally write the panic header + */ + memset(ctx->bounce, 0, PAGE_SIZE); + hdr->magic = PANIC_MAGIC; + hdr->version = PHDR_VERSION; + + hdr->console_offset = console_offset; + hdr->console_length = console_len; + + hdr->threads_offset = threads_offset; + hdr->threads_length = threads_len; + + rc = apanic_writeflashpage(ctx->mtd, 0, ctx->bounce); + if (rc <= 0) { + printk(KERN_EMERG "apanic: Header write failed (%d)\n", + rc); + goto out; + } + + printk(KERN_EMERG "apanic: Panic dump sucessfully written to flash\n"); + + out: +#ifdef CONFIG_PREEMPT + sub_preempt_count(PREEMPT_ACTIVE); +#endif + in_panic = 0; + return NOTIFY_DONE; +} + +static struct notifier_block panic_blk = { + .notifier_call = apanic, +}; + +static int panic_dbg_get(void *data, u64 *val) +{ + apanic(NULL, 0, NULL); + return 0; +} + +static int panic_dbg_set(void *data, u64 val) +{ + BUG(); + return -1; +} + +DEFINE_SIMPLE_ATTRIBUTE(panic_dbg_fops, panic_dbg_get, panic_dbg_set, "%llu\n"); + +int __init apanic_init(void) +{ + register_mtd_user(&mtd_panic_notifier); + atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); + debugfs_create_file("apanic", 0644, NULL, NULL, &panic_dbg_fops); + memset(&drv_ctx, 0, sizeof(drv_ctx)); + drv_ctx.bounce = (void *) __get_free_page(GFP_KERNEL); + INIT_WORK(&proc_removal_work, apanic_remove_proc_work); + printk(KERN_INFO "Android kernel panic handler initialized (bind=%s)\n", + CONFIG_APANIC_PLABEL); + return 0; +} + +module_init(apanic_init); diff --git a/drivers/misc/isa1200.c b/drivers/misc/isa1200.c new file mode 100644 index 0000000000000000000000000000000000000000..555dfdd2d09237693c57e7a168f404c9c66747b4 --- /dev/null +++ b/drivers/misc/isa1200.c @@ -0,0 +1,716 @@ +/* + * isa1200.c - Haptic Motor + * + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../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 +#define ISA1200_POWER_DOWN_MASK 0x7F + +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; + bool is_len_gpio_valid; + struct regulator **regs; + bool clk_on; + u8 hctrl0_val; +}; + +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 hen and len are seperate then enable hen + * otherwise set normal mode bit */ + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); + else { + rc = isa1200_write_reg(haptic->client, ISA1200_HCTRL0, + haptic->hctrl0_val | ~ISA1200_POWER_DOWN_MASK); + if (rc < 0) { + pr_err("%s: i2c write failure\n", __func__); + return; + } + } + + 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__); + goto chip_dwn; + } + + rc = pwm_enable(haptic->pwm); + if (rc < 0) { + pr_err("%s: pwm_enable fail\n", __func__); + goto chip_dwn; + } + } else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + /* vote for clock */ + if (haptic->pdata->clk_enable && !haptic->clk_on) { + rc = haptic->pdata->clk_enable(true); + if (rc < 0) { + pr_err("%s: clk enable failed\n", + __func__); + goto chip_dwn; + } + haptic->clk_on = true; + } + + rc = isa1200_write_reg(haptic->client, + ISA1200_HCTRL5, + ISA1200_HCTRL5_VIB_STRT); + if (rc < 0) { + pr_err("%s: start vibartion fail\n", __func__); + goto dis_clk; + } + } + } else { + /* if hen and len are seperate then pull down hen + * otherwise set power down bit */ + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + else { + rc = isa1200_write_reg(haptic->client, ISA1200_HCTRL0, + haptic->hctrl0_val & ISA1200_POWER_DOWN_MASK); + if (rc < 0) { + pr_err("%s: i2c write failure\n", __func__); + return; + } + } + + 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__); + + /* de-vote clock */ + if (haptic->pdata->clk_enable && haptic->clk_on) { + rc = haptic->pdata->clk_enable(false); + if (rc < 0) { + pr_err("%s: clk disable failed\n", + __func__); + return; + } + haptic->clk_on = false; + } + } + } + + return; + +dis_clk: + if (haptic->pdata->clk_enable && haptic->clk_on) { + rc = haptic->pdata->clk_enable(false); + if (rc < 0) { + pr_err("%s: clk disable failed\n", __func__); + return; + } + haptic->clk_on = false; + } +chip_dwn: + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + else { + rc = isa1200_write_reg(haptic->client, ISA1200_HCTRL0, + haptic->hctrl0_val & ISA1200_POWER_DOWN_MASK); + if (rc < 0) { + pr_err("%s: i2c write failure\n", __func__); + return; + } + } +} + +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 temp, rc; + u8 value; + + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 0); + + udelay(250); + + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_len_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__); + goto reset_gpios; + } + + 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; + } + + /* if hen and len are seperate then pull down hen + * otherwise set power down bit */ + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + else { + rc = isa1200_write_reg(client, ISA1200_HCTRL0, + value & ISA1200_POWER_DOWN_MASK); + if (rc < 0) { + pr_err("%s: i2c write failure\n", __func__); + goto reset_hctrl1; + } + } + + haptic->hctrl0_val = value; + dump_isa1200_reg("new:", client); + return 0; + +reset_hctrl1: + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, + ISA1200_HCTRL1_RESET); +reset_gpios: + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 0); + return rc; +} + +static int isa1200_reg_power(struct isa1200_chip *haptic, bool on) +{ + const struct isa1200_regulator *reg_info = + haptic->pdata->regulator_info; + u8 i, num_reg = haptic->pdata->num_regulators; + int rc; + + for (i = 0; i < num_reg; i++) { + rc = regulator_set_optimum_mode(haptic->regs[i], + on ? reg_info[i].load_uA : 0); + if (rc < 0) { + pr_err("%s: regulator_set_optimum_mode failed(%d)\n", + __func__, rc); + goto regs_fail; + } + + rc = on ? regulator_enable(haptic->regs[i]) : + regulator_disable(haptic->regs[i]); + if (rc < 0) { + pr_err("%s: regulator %sable fail %d\n", __func__, + on ? "en" : "dis", rc); + regulator_set_optimum_mode(haptic->regs[i], + !on ? reg_info[i].load_uA : 0); + goto regs_fail; + } + } + + return 0; + +regs_fail: + while (i--) { + regulator_set_optimum_mode(haptic->regs[i], + !on ? reg_info[i].load_uA : 0); + !on ? regulator_enable(haptic->regs[i]) : + regulator_disable(haptic->regs[i]); + } + return rc; +} + +static int isa1200_reg_setup(struct isa1200_chip *haptic, bool on) +{ + const struct isa1200_regulator *reg_info = + haptic->pdata->regulator_info; + u8 i, num_reg = haptic->pdata->num_regulators; + int rc = 0; + + /* put regulators */ + if (on == false) { + i = num_reg; + goto put_regs; + } + + haptic->regs = kzalloc(num_reg * sizeof(struct regulator *), + GFP_KERNEL); + if (!haptic->regs) { + pr_err("unable to allocate memory\n"); + return -ENOMEM; + } + + for (i = 0; i < num_reg; i++) { + haptic->regs[i] = regulator_get(&haptic->client->dev, + reg_info[i].name); + if (IS_ERR(haptic->regs[i])) { + rc = PTR_ERR(haptic->regs[i]); + pr_err("%s:regulator get failed(%d)\n", __func__, rc); + goto put_regs; + } + + if (regulator_count_voltages(haptic->regs[i]) > 0) { + rc = regulator_set_voltage(haptic->regs[i], + reg_info[i].min_uV, reg_info[i].max_uV); + if (rc) { + pr_err("%s: regulator_set_voltage failed(%d)\n", + __func__, rc); + regulator_put(haptic->regs[i]); + goto put_regs; + } + } + } + + return rc; + +put_regs: + while (i--) { + if (regulator_count_voltages(haptic->regs[i]) > 0) + regulator_set_voltage(haptic->regs[i], 0, + reg_info[i].max_uV); + regulator_put(haptic->regs[i]); + } + kfree(haptic->regs); + 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->regulator_info) { + ret = isa1200_reg_setup(haptic, true); + if (ret) { + dev_err(&client->dev, "%s: regulator setup failed\n", + __func__); + goto reg_setup_fail; + } + + ret = isa1200_reg_power(haptic, true); + if (ret) { + dev_err(&client->dev, "%s: regulator power failed\n", + __func__); + goto reg_pwr_fail; + } + } + + 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); + haptic->clk_on = false; + + 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_en_gpio"); + if (ret) { + dev_err(&client->dev, "%s: gpio %d request failed\n", + __func__, pdata->hap_en_gpio); + goto hen_gpio_fail; + } + } else { + dev_err(&client->dev, "%s: Invalid gpio %d\n", __func__, + pdata->hap_en_gpio); + goto hen_gpio_fail; + } + + haptic->is_len_gpio_valid = true; + ret = gpio_is_valid(haptic->pdata->hap_len_gpio); + if (ret) { + ret = gpio_request(pdata->hap_len_gpio, + "haptic_ldo_gpio"); + if (ret) { + dev_err(&client->dev, + "%s: gpio %d request failed\n", + __func__, pdata->hap_len_gpio); + goto len_gpio_fail; + } + } else { + dev_err(&client->dev, "%s: gpio is not used/Invalid %d\n", + __func__, pdata->hap_len_gpio); + haptic->is_len_gpio_valid = false; + } + + ret = isa1200_setup(client); + if (ret) { + dev_err(&client->dev, "%s: setup fail %d\n", __func__, ret); + goto setup_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: + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 0); + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, + ISA1200_HCTRL1_RESET); + i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, + ISA1200_HCTRL0_RESET); +setup_fail: + if (haptic->is_len_gpio_valid == true) + gpio_free(pdata->hap_len_gpio); +len_gpio_fail: + gpio_free(pdata->hap_en_gpio); +hen_gpio_fail: + timed_output_dev_unregister(&haptic->dev); +timed_reg_fail: + if (pdata->power_on) + pdata->power_on(0); +pwr_up_fail: + if (pdata->regulator_info) + isa1200_reg_power(haptic, false); +reg_pwr_fail: + if (pdata->regulator_info) + isa1200_reg_setup(haptic, false); +reg_setup_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_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 0); + + gpio_free(haptic->pdata->hap_en_gpio); + if (haptic->is_len_gpio_valid == true) + gpio_free(haptic->pdata->hap_len_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); + + + /* power-off the chip */ + if (haptic->pdata->regulator_info) { + isa1200_reg_power(haptic, false); + isa1200_reg_setup(haptic, false); + } + + if (haptic->pdata->power_on) + haptic->pdata->power_on(0); + + if (haptic->pdata->dev_setup) + haptic->pdata->dev_setup(false); + + 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); + + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + if (haptic->is_len_gpio_valid == true) + gpio_set_value_cansleep(haptic->pdata->hap_len_gpio, 0); + + if (haptic->pdata->regulator_info) + isa1200_reg_power(haptic, false); + + 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->regulator_info) + isa1200_reg_power(haptic, true); + + 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 0000000000000000000000000000000000000000..6dcdd02efe9d9ebf20dc2a2a5b9437fe9559da2a --- /dev/null +++ b/drivers/misc/msm_migrate_pages.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. + */ + +#include +#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/pm8xxx-nfc.c b/drivers/misc/pm8xxx-nfc.c new file mode 100644 index 0000000000000000000000000000000000000000..1aaa3e3bd7f489ac860bd17d5f0af4ddfd9bd43b --- /dev/null +++ b/drivers/misc/pm8xxx-nfc.c @@ -0,0 +1,311 @@ +/* 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 PMIC8XXX NFC driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* PM8XXX NFC */ +#define SSBI_REG_NFC_CTRL 0x14D +#define SSBI_REG_NFC_TEST 0x14E + +/* NFC_CTRL */ +#define PM8XXX_NFC_SUPPORT_EN 0x80 +#define PM8XXX_NFC_LDO_EN 0x40 +#define PM8XXX_NFC_EN 0x20 +#define PM8XXX_NFC_EXT_VDDLDO_EN 0x10 +#define PM8XXX_NFC_VPH_PWR_EN 0x08 +#define PM8XXX_NFC_RESERVED 0x04 +#define PM8XXX_NFC_VDDLDO_LEVEL 0x03 + +/* NFC_TEST */ +#define PM8XXX_NFC_VDDLDO_MON_EN 0x80 +#define PM8XXX_NFC_ATEST_EN 0x40 +#define PM8XXX_NFC_DTEST1_EN 0x20 +#define PM8XXX_NFC_RESERVED2 0x18 +#define PM8XXX_NFC_VDDLDO_OK_S 0x04 +#define PM8XXX_NFC_MBG_EN_S 0x02 +#define PM8XXX_NFC_EXT_EN_S 0x01 + +struct pm8xxx_nfc_device { + struct device *dev; + struct mutex nfc_mutex; +#if defined(CONFIG_DEBUG_FS) + struct dentry *dent; +#endif +}; +static struct pm8xxx_nfc_device *nfc_dev; + +/* APIs */ +/* + * pm8xxx_nfc_request - request a handle to access NFC device + */ +struct pm8xxx_nfc_device *pm8xxx_nfc_request(void) +{ + return nfc_dev; +} +EXPORT_SYMBOL(pm8xxx_nfc_request); + +/* + * pm8xxx_nfc_config - configure NFC signals + * + * @nfcdev: the NFC device + * @mask: signal mask to configure + * @flags: control flags + */ +int pm8xxx_nfc_config(struct pm8xxx_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; + + mutex_lock(&nfcdev->nfc_mutex); + + if (!(mask & PM_NFC_CTRL_REQ)) + goto config_test; + + rc = pm8xxx_readb(nfcdev->dev->parent, SSBI_REG_NFC_CTRL, &nfc_ctrl); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(): 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 = pm8xxx_writeb(nfcdev->dev->parent, SSBI_REG_NFC_CTRL, nfc_ctrl); + if (rc) { + pr_err("%s: FAIL pm8xxx_writeb(): 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 = pm8xxx_readb(nfcdev->dev->parent, SSBI_REG_NFC_TEST, &nfc_test); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(): 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 = pm8xxx_writeb(nfcdev->dev->parent, SSBI_REG_NFC_TEST, nfc_test); + if (rc) { + pr_err("%s: FAIL pm8xxx_writeb(): 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(pm8xxx_nfc_config); + +/* + * pm8xxx_nfc_get_status - get NFC status + * + * @nfcdev: the NFC device + * @mask: of status mask to read + * @status: pointer to the status variable + */ +int pm8xxx_nfc_get_status(struct pm8xxx_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; + + st = 0; + mutex_lock(&nfcdev->nfc_mutex); + + if (!(mask & PM_NFC_CTRL_REQ)) + goto read_test; + + rc = pm8xxx_readb(nfcdev->dev->parent, SSBI_REG_NFC_CTRL, &nfc_ctrl); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(): 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 = pm8xxx_readb(nfcdev->dev->parent, SSBI_REG_NFC_TEST, &nfc_test); + if (rc) + pr_err("%s: FAIL pm8xxx_readb(): 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(pm8xxx_nfc_get_status); + +/* + * pm8xxx_nfc_free - free the NFC device + */ +void pm8xxx_nfc_free(struct pm8xxx_nfc_device *nfcdev) +{ + /* Disable all signals */ + pm8xxx_nfc_config(nfcdev, PM_NFC_CTRL_REQ, 0); +} +EXPORT_SYMBOL(pm8xxx_nfc_free); + +#if defined(CONFIG_DEBUG_FS) +static int pm8xxx_nfc_debug_set(void *data, u64 val) +{ + struct pm8xxx_nfc_device *nfcdev; + u32 mask, control; + int rc; + + nfcdev = (struct pm8xxx_nfc_device *)data; + control = (u32)val & 0xffff; + mask = ((u32)val >> 16) & 0xffff; + rc = pm8xxx_nfc_config(nfcdev, mask, control); + if (rc) + pr_err("%s: ERR pm8xxx_nfc_config: rc=%d, " + "[mask, control]=[0x%x, 0x%x]\n", + __func__, rc, mask, control); + + return 0; +} + +static int pm8xxx_nfc_debug_get(void *data, u64 *val) +{ + struct pm8xxx_nfc_device *nfcdev; + u32 status; + int rc; + + nfcdev = (struct pm8xxx_nfc_device *)data; + rc = pm8xxx_nfc_get_status(nfcdev, (u32)-1, &status); + if (rc) + pr_err("%s: ERR pm8xxx_nfc_get_status: rc=%d, status=0x%x\n", + __func__, rc, status); + + if (val) + *val = (u64)status; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(pm8xxx_nfc_fops, pm8xxx_nfc_debug_get, + pm8xxx_nfc_debug_set, "%llu\n"); + +static int pm8xxx_nfc_debug_init(struct pm8xxx_nfc_device *nfcdev) +{ + struct dentry *dent; + + dent = debugfs_create_file("pm8xxx-nfc", 0644, NULL, + (void *)nfcdev, &pm8xxx_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 pm8xxx_nfc_probe(struct platform_device *pdev) +{ + struct pm8xxx_nfc_device *nfcdev; + + nfcdev = kzalloc(sizeof *nfcdev, GFP_KERNEL); + if (nfcdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&nfcdev->nfc_mutex); + + nfcdev->dev = &pdev->dev; + nfc_dev = nfcdev; + platform_set_drvdata(pdev, nfcdev); + +#if defined(CONFIG_DEBUG_FS) + pm8xxx_nfc_debug_init(nfc_dev); +#endif + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pm8xxx_nfc_remove(struct platform_device *pdev) +{ + struct pm8xxx_nfc_device *nfcdev = platform_get_drvdata(pdev); + +#if defined(CONFIG_DEBUG_FS) + debugfs_remove(nfcdev->dent); +#endif + + platform_set_drvdata(pdev, NULL); + kfree(nfcdev); + return 0; +} + +static struct platform_driver pm8xxx_nfc_driver = { + .probe = pm8xxx_nfc_probe, + .remove = __devexit_p(pm8xxx_nfc_remove), + .driver = { + .name = PM8XXX_NFC_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_nfc_init(void) +{ + return platform_driver_register(&pm8xxx_nfc_driver); +} + +static void __exit pm8xxx_nfc_exit(void) +{ + platform_driver_unregister(&pm8xxx_nfc_driver); +} + +module_init(pm8xxx_nfc_init); +module_exit(pm8xxx_nfc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8XXX NFC driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_NFC_DEV_NAME); diff --git a/drivers/misc/pm8xxx-upl.c b/drivers/misc/pm8xxx-upl.c new file mode 100644 index 0000000000000000000000000000000000000000..d3d9746fceda48d4c6cf97c99197e9865c08cf81 --- /dev/null +++ b/drivers/misc/pm8xxx-upl.c @@ -0,0 +1,350 @@ +/* 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 PM8XXX UPL driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* PMIC8XXX UPL registers */ +#define SSBI_REG_UPL_CTRL 0x17B +#define SSBI_REG_UPL_TRUTHTABLE1 0x17C +#define SSBI_REG_UPL_TRUTHTABLE2 0x17D + +struct pm8xxx_upl_device { + struct device *dev; + struct mutex upl_mutex; +#if defined(CONFIG_DEBUG_FS) + struct dentry *dent; +#endif +}; +static struct pm8xxx_upl_device *upl_dev; + +/* APIs */ + +/* + * pm8xxx_upl_request - request a handle to access UPL device + */ +struct pm8xxx_upl_device *pm8xxx_upl_request(void) +{ + return upl_dev; +} +EXPORT_SYMBOL(pm8xxx_upl_request); + +/* + * pm8xxx_upl_read_truthtable - read value currently stored in UPL truth table + * + * @upldev: the UPL device + * @truthtable: value read from UPL truth table + */ +int pm8xxx_upl_read_truthtable(struct pm8xxx_upl_device *upldev, + u16 *truthtable) +{ + int rc = 0; + u8 table[2]; + + if (upldev == NULL || IS_ERR(upldev)) + return -EINVAL; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE1, + &(table[0])); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(0x%X)=0x%02X: rc=%d\n", + __func__, SSBI_REG_UPL_TRUTHTABLE1, table[0], rc); + goto upl_read_done; + } + + rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE2, + &(table[1])); + if (rc) + pr_err("%s: FAIL pm8xxx_readb(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(pm8xxx_upl_read_truthtable); + +/* + * pm8xxx_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 pm8xxx_upl_write_truthtable(struct pm8xxx_upl_device *upldev, + u16 truthtable) +{ + int rc = 0; + u8 table[2]; + + if (upldev == NULL || IS_ERR(upldev)) + return -EINVAL; + + table[0] = truthtable & 0xFF; + table[1] = (truthtable >> 8) & 0xFF; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8xxx_writeb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE1, + table[0]); + if (rc) { + pr_err("%s: FAIL pm8xxx_writeb(0x%X)=0x%04X: rc=%d\n", + __func__, SSBI_REG_UPL_TRUTHTABLE1, table[0], rc); + goto upl_write_done; + } + + rc = pm8xxx_writeb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE2, + table[1]); + if (rc) + pr_err("%s: FAIL pm8xxx_writeb(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(pm8xxx_upl_write_truthtable); + +/* + * pm8xxx_upl_config - configure UPL I/O settings and UPL enable/disable + * + * @upldev: the UPL device + * @mask: setting mask to configure + * @flags: setting flags + */ +int pm8xxx_upl_config(struct pm8xxx_upl_device *upldev, u32 mask, u32 flags) +{ + int rc; + u8 upl_ctrl, m, f; + + if (upldev == NULL || IS_ERR(upldev)) + return -EINVAL; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_CTRL, &upl_ctrl); + if (rc) { + pr_err("%s: FAIL pm8xxx_readb(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 = pm8xxx_writeb(upldev->dev->parent, SSBI_REG_UPL_CTRL, upl_ctrl); + if (rc) + pr_err("%s: FAIL pm8xxx_writeb(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(pm8xxx_upl_config); + +#if defined(CONFIG_DEBUG_FS) + +static int truthtable_set(void *data, u64 val) +{ + int rc; + + rc = pm8xxx_upl_write_truthtable(data, val); + if (rc) + pr_err("%s: pm8xxx_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 = pm8xxx_upl_read_truthtable(data, &truthtable); + if (rc) + pr_err("%s: pm8xxx_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 = pm8xxx_upl_config(data, mask, flags); + if (rc) + pr_err("%s: pm8xxx_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 pm8xxx_upl_device *upldev; + int rc = 0; + u8 ctrl; + + upldev = data; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_CTRL, &ctrl); + if (rc) + pr_err("%s: FAIL pm8xxx_readb(): 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 pm8xxx_upl_debug_init(struct pm8xxx_upl_device *upldev) +{ + struct dentry *dent; + struct dentry *temp; + + dent = debugfs_create_dir("pm8xxx-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 pm8xxx_upl_debug_remove(struct pm8xxx_upl_device *upldev) +{ + debugfs_remove_recursive(upldev->dent); + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +static int __devinit pm8xxx_upl_probe(struct platform_device *pdev) +{ + struct pm8xxx_upl_device *upldev; + + upldev = kzalloc(sizeof *upldev, GFP_KERNEL); + if (upldev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&upldev->upl_mutex); + + upl_dev = upldev; + upldev->dev = &pdev->dev; + platform_set_drvdata(pdev, upldev); + +#if defined(CONFIG_DEBUG_FS) + pm8xxx_upl_debug_init(upl_dev); +#endif + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pm8xxx_upl_remove(struct platform_device *pdev) +{ + struct pm8xxx_upl_device *upldev = platform_get_drvdata(pdev); + +#if defined(CONFIG_DEBUG_FS) + pm8xxx_upl_debug_remove(upldev); +#endif + + platform_set_drvdata(pdev, NULL); + kfree(upldev); + pr_notice("%s: OK\n", __func__); + + return 0; +} + +static struct platform_driver pm8xxx_upl_driver = { + .probe = pm8xxx_upl_probe, + .remove = __devexit_p(pm8xxx_upl_remove), + .driver = { + .name = PM8XXX_UPL_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_upl_init(void) +{ + return platform_driver_register(&pm8xxx_upl_driver); +} + +static void __exit pm8xxx_upl_exit(void) +{ + platform_driver_unregister(&pm8xxx_upl_driver); +} + +module_init(pm8xxx_upl_init); +module_exit(pm8xxx_upl_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8XXX UPL driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_UPL_DEV_NAME); diff --git a/drivers/misc/pm8xxx-vibrator.c b/drivers/misc/pm8xxx-vibrator.c new file mode 100644 index 0000000000000000000000000000000000000000..62e7b45d92c184546cc9ca7cfc646c2d1507a883 --- /dev/null +++ b/drivers/misc/pm8xxx-vibrator.c @@ -0,0 +1,325 @@ +/* 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 "../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_DRV_LOGIC_SHIFT 0x2 + +#define VIB_MAX_LEVEL_mV 3100 +#define VIB_MIN_LEVEL_mV 1200 + +struct pm8xxx_vib { + struct hrtimer vib_timer; + struct timed_output_dev timed_dev; + spinlock_t lock; + struct work_struct work; + struct device *dev; + const struct pm8xxx_vibrator_platform_data *pdata; + int state; + int level; + u8 reg_vib_drv; +}; + +static struct pm8xxx_vib *vib_dev; + +int pm8xxx_vibrator_config(struct pm8xxx_vib_config *vib_config) +{ + u8 reg = 0; + int rc; + + if (vib_dev == NULL) { + pr_err("%s: vib_dev is NULL\n", __func__); + return -EINVAL; + } + + if (vib_config->drive_mV) { + if ((vib_config->drive_mV < VIB_MIN_LEVEL_mV) || + (vib_config->drive_mV > VIB_MAX_LEVEL_mV)) { + pr_err("Invalid vibrator drive strength\n"); + return -EINVAL; + } + } + + reg = (vib_config->drive_mV / 100) << VIB_DRV_SEL_SHIFT; + + reg |= (!!vib_config->active_low) << VIB_DRV_LOGIC_SHIFT; + + reg |= vib_config->enable_mode; + + rc = pm8xxx_writeb(vib_dev->dev->parent, VIB_DRV, reg); + if (rc) + pr_err("%s: pm8xxx write failed: rc=%d\n", __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_vibrator_config); + +/* REVISIT: just for debugging, will be removed in final working version */ +static void __dump_vib_regs(struct pm8xxx_vib *vib, char *msg) +{ + u8 temp; + + dev_dbg(vib->dev, "%s\n", msg); + + pm8xxx_readb(vib->dev->parent, VIB_DRV, &temp); + dev_dbg(vib->dev, "VIB_DRV - %X\n", temp); +} + +static int pm8xxx_vib_read_u8(struct pm8xxx_vib *vib, + u8 *data, u16 reg) +{ + int rc; + + rc = pm8xxx_readb(vib->dev->parent, reg, data); + if (rc < 0) + dev_warn(vib->dev, "Error reading pm8xxx: %X - ret %X\n", + reg, rc); + + return rc; +} + +static int pm8xxx_vib_write_u8(struct pm8xxx_vib *vib, + u8 data, u16 reg) +{ + int rc; + + rc = pm8xxx_writeb(vib->dev->parent, reg, data); + if (rc < 0) + dev_warn(vib->dev, "Error writing pm8xxx: %X - ret %X\n", + reg, rc); + return rc; +} + +static int pm8xxx_vib_set(struct pm8xxx_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 = pm8xxx_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 = pm8xxx_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + return rc; + vib->reg_vib_drv = val; + } + __dump_vib_regs(vib, "vib_set_end"); + + return rc; +} + +static void pm8xxx_vib_enable(struct timed_output_dev *dev, int value) +{ + struct pm8xxx_vib *vib = container_of(dev, struct pm8xxx_vib, + timed_dev); + unsigned long flags; + +retry: + spin_lock_irqsave(&vib->lock, flags); + if (hrtimer_try_to_cancel(&vib->vib_timer) < 0) { + spin_unlock_irqrestore(&vib->lock, flags); + cpu_relax(); + goto retry; + } + + 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 pm8xxx_vib_update(struct work_struct *work) +{ + struct pm8xxx_vib *vib = container_of(work, struct pm8xxx_vib, + work); + + pm8xxx_vib_set(vib, vib->state); +} + +static int pm8xxx_vib_get_time(struct timed_output_dev *dev) +{ + struct pm8xxx_vib *vib = container_of(dev, struct pm8xxx_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 pm8xxx_vib_timer_func(struct hrtimer *timer) +{ + struct pm8xxx_vib *vib = container_of(timer, struct pm8xxx_vib, + vib_timer); + + vib->state = 0; + schedule_work(&vib->work); + + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_PM +static int pm8xxx_vib_suspend(struct device *dev) +{ + struct pm8xxx_vib *vib = dev_get_drvdata(dev); + + hrtimer_cancel(&vib->vib_timer); + cancel_work_sync(&vib->work); + /* turn-off vibrator */ + pm8xxx_vib_set(vib, 0); + + return 0; +} + +static const struct dev_pm_ops pm8xxx_vib_pm_ops = { + .suspend = pm8xxx_vib_suspend, +}; +#endif + +static int __devinit pm8xxx_vib_probe(struct platform_device *pdev) + +{ + const struct pm8xxx_vibrator_platform_data *pdata = + pdev->dev.platform_data; + struct pm8xxx_vib *vib; + u8 val; + int rc; + + 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->pdata = pdata; + vib->level = pdata->level_mV / 100; + vib->dev = &pdev->dev; + + spin_lock_init(&vib->lock); + INIT_WORK(&vib->work, pm8xxx_vib_update); + + hrtimer_init(&vib->vib_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + vib->vib_timer.function = pm8xxx_vib_timer_func; + + vib->timed_dev.name = "vibrator"; + vib->timed_dev.get_time = pm8xxx_vib_get_time; + vib->timed_dev.enable = pm8xxx_vib_enable; + + __dump_vib_regs(vib, "boot_vib_default"); + + /* + * Configure the vibrator, it operates in manual mode + * for timed_output framework. + */ + rc = pm8xxx_vib_read_u8(vib, &val, VIB_DRV); + if (rc < 0) + goto err_read_vib; + val &= ~VIB_DRV_EN_MANUAL_MASK; + rc = pm8xxx_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; + + pm8xxx_vib_enable(&vib->timed_dev, pdata->initial_vibrate_ms); + + platform_set_drvdata(pdev, vib); + + vib_dev = vib; + + return 0; + +err_read_vib: + kfree(vib); + return rc; +} + +static int __devexit pm8xxx_vib_remove(struct platform_device *pdev) +{ + struct pm8xxx_vib *vib = platform_get_drvdata(pdev); + + cancel_work_sync(&vib->work); + hrtimer_cancel(&vib->vib_timer); + timed_output_dev_unregister(&vib->timed_dev); + platform_set_drvdata(pdev, NULL); + kfree(vib); + + return 0; +} + +static struct platform_driver pm8xxx_vib_driver = { + .probe = pm8xxx_vib_probe, + .remove = __devexit_p(pm8xxx_vib_remove), + .driver = { + .name = PM8XXX_VIBRATOR_DEV_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pm8xxx_vib_pm_ops, +#endif + }, +}; + +static int __init pm8xxx_vib_init(void) +{ + return platform_driver_register(&pm8xxx_vib_driver); +} +module_init(pm8xxx_vib_init); + +static void __exit pm8xxx_vib_exit(void) +{ + platform_driver_unregister(&pm8xxx_vib_driver); +} +module_exit(pm8xxx_vib_exit); + +MODULE_ALIAS("platform:" PM8XXX_VIBRATOR_DEV_NAME); +MODULE_DESCRIPTION("pm8xxx vibrator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/pmem.c b/drivers/misc/pmem.c new file mode 100644 index 0000000000000000000000000000000000000000..0ab5b6978cfcae8841f004c882f065857c91f75e --- /dev/null +++ b/drivers/misc/pmem.c @@ -0,0 +1,2961 @@ +/* drivers/android/pmem.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2012, 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 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 */ +#define PMEM_FLAGS_BUSY 0x1 +/* indicates that this is a suballocation of a larger master range */ +#define PMEM_FLAGS_CONNECTED 0x1 << 1 +/* indicates this is a master and not a sub allocation and that it is mmaped */ +#define PMEM_FLAGS_MASTERMAP 0x1 << 2 +/* submap and unsubmap flags indicate: + * 00: subregion has never been mmaped + * 10: subregion has been mmaped, reference to the mm was taken + * 11: subretion has ben released, refernece to the mm still held + * 01: subretion has been released, reference to the mm has been released + */ +#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 */ + int index; + /* see flags above for descriptions */ + unsigned int flags; + /* protects this data field, if the mm_mmap sem will be held at the + * same time as this sem, the mm sem must be taken first (as this is + * the order for vma_open and vma_close ops */ + struct rw_semaphore sem; + /* info about the mmaping process */ + struct vm_area_struct *vma; + /* task struct of the mapping process */ + struct task_struct *task; + /* process id of teh mapping process */ + pid_t pid; + /* file descriptor of the master */ + int master_fd; + /* file struct of the master */ + struct file *master_file; + /* a list of currently available regions if this is a suballocation */ + struct list_head region_list; + /* a linked list of data so we can access them for debugging */ + struct list_head list; +#if PMEM_DEBUG + int ref; +#endif +}; + +struct pmem_bits { + unsigned allocated:1; /* 1 if allocated, 0 if free */ + unsigned order:7; /* size of the region in pmem space */ +}; + +struct pmem_region_node { + struct pmem_region region; + struct list_head list; +}; + +#define PMEM_DEBUG_MSGS 0 +#if PMEM_DEBUG_MSGS +#define DLOG(fmt,args...) \ + 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 */ + unsigned long base; + /* vitual start address of the remaped pmem space */ + unsigned char __iomem *vbase; + /* total size of the pmem space */ + unsigned long size; + /* number of entries in the pmem space */ + 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; + /* reserved virtual address range */ + struct vm_struct *area; + + 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; + 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_mutex should be taken before pmem_data->sem if both are + * needed */ + struct mutex data_list_mutex; + struct list_head data_list; + /* arena_mutex protects the global allocation arena + * + * IF YOU TAKE BOTH LOCKS TAKE THEM IN THIS ORDER: + * down(pmem_data->sem) => mutex_lock(arena_mutex) + */ + struct mutex arena_mutex; + + long (*ioctl)(struct file *, unsigned int, unsigned long); + int (*release)(struct inode *, struct file *); + /* reference count of allocations */ + atomic_t allocation_cnt; + /* + * request function for a region when the allocation count goes + * from 0 -> 1 + */ + int (*mem_request)(void *); + /* + * release function for a region when the allocation count goes + * from 1 -> 0 + */ + int (*mem_release)(void *); + /* + * private data for the request/release callback + */ + void *region_data; + /* + * map and unmap as needed + */ + int map_on_demand; + /* + * memory will be reused through fmem + */ + int reusable; +}; +#define to_pmem_info_id(a) (container_of(a, struct pmem_info, kobj)->id) + +static void ioremap_pmem(int id); +static void pmem_put_region(int id); +static int pmem_get_region(int id); + +static struct pmem_info pmem[PMEM_MAX_DEVICES]; +static int id_count; + +#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) && \ + (!(data->flags & PMEM_FLAGS_UNSUBMAP))) + +static int pmem_release(struct inode *, struct file *); +static int pmem_mmap(struct file *, struct vm_area_struct *); +static int pmem_open(struct inode *, struct file *); +static long pmem_ioctl(struct file *, unsigned int, unsigned long); + +struct file_operations pmem_fops = { + .release = pmem_release, + .mmap = pmem_mmap, + .open = pmem_open, + .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 pmem_allocate_from_id(const int id, const unsigned long size, + const unsigned int align) +{ + int ret; + ret = pmem_get_region(id); + + if (ret) + return -1; + + ret = pmem[id].allocate(id, size, align); + + if (ret < 0) + pmem_put_region(id); + + return ret; +} + +static int pmem_free_from_id(const int id, const int index) +{ + pmem_put_region(id); + return pmem[id].free(id, index); +} + +static int pmem_get_region(int id) +{ + /* Must be called with arena mutex locked */ + atomic_inc(&pmem[id].allocation_cnt); + if (!pmem[id].vbase) { + DLOG("PMEMDEBUG: mapping for %s", pmem[id].name); + if (pmem[id].mem_request) { + int ret = pmem[id].mem_request(pmem[id].region_data); + if (ret) { + atomic_dec(&pmem[id].allocation_cnt); + return 1; + } + } + ioremap_pmem(id); + } + + if (pmem[id].vbase) { + return 0; + } else { + if (pmem[id].mem_release) + pmem[id].mem_release(pmem[id].region_data); + atomic_dec(&pmem[id].allocation_cnt); + return 1; + } +} + +static void pmem_put_region(int id) +{ + /* Must be called with arena mutex locked */ + if (atomic_dec_and_test(&pmem[id].allocation_cnt)) { + DLOG("PMEMDEBUG: unmapping for %s", pmem[id].name); + BUG_ON(!pmem[id].vbase); + if (pmem[id].map_on_demand) { + /* unmap_kernel_range() flushes the caches + * and removes the page table entries + */ + unmap_kernel_range((unsigned long)pmem[id].vbase, + pmem[id].size); + pmem[id].vbase = NULL; + if (pmem[id].mem_release) { + int ret = pmem[id].mem_release( + pmem[id].region_data); + WARN(ret, "mem_release failed"); + } + + } + } +} + +static int get_id(struct file *file) +{ + return MINOR(file->f_dentry->d_inode->i_rdev); +} + +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); + 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) +{ + /* 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 = file->private_data; + int put_needed, ret = 0; + + if (!has_allocation(file)) + return 0; + 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; + if (master_file) + fput_light(master_file, put_needed); + return ret; +} + +static int pmem_free_all_or_nothing(int id, int index) +{ + /* caller should hold the lock on arena_mutex! */ + DLOG("index %d\n", index); + + 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].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 { + 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; + } + } while (curr < pmem[id].num_entries); + + 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 = file->private_data; + struct pmem_region_node *region_node; + struct list_head *elt, *elt2; + int id = get_id(file), ret = 0; + +#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) { + list_for_each(elt, &pmem[id].data_list) { + struct pmem_data *sub_data = + list_entry(elt, struct pmem_data, list); + int is_master; + + down_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); + } + } + list_del(&data->list); + mutex_unlock(&pmem[id].data_list_mutex); + + down_write(&data->sem); + + /* if it is not a connected file and it has an allocation, free it */ + if (!(PMEM_FLAGS_CONNECTED & data->flags) && has_allocation(file)) { + mutex_lock(&pmem[id].arena_mutex); + ret = pmem_free_from_id(id, data->index); + mutex_unlock(&pmem[id].arena_mutex); + } + + /* if this file is a submap (mapped, connected file), downref the + * task struct */ + if (PMEM_FLAGS_SUBMAP & data->flags) + if (data->task) { + put_task_struct(data->task); + data->task = NULL; + } + + file->private_data = NULL; + + list_for_each_safe(elt, elt2, &data->region_list) { + region_node = list_entry(elt, struct pmem_region_node, list); + list_del(elt); + kfree(region_node); + } + BUG_ON(!list_empty(&data->region_list)); + + up_write(&data->sem); + kfree(data); + if (pmem[id].release) + ret = pmem[id].release(inode, file); + + return ret; +} + +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("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(KERN_ALERT "pmem: %s: unable to allocate memory for " + "pmem metadata.", __func__); + return -1; + } + data->flags = 0; + data->index = -1; + data->task = NULL; + data->vma = NULL; + data->pid = 0; + data->master_file = NULL; +#if PMEM_DEBUG + data->ref = 0; +#endif + INIT_LIST_HEAD(&data->region_list); + init_rwsem(&data->sem); + + file->private_data = data; + INIT_LIST_HEAD(&data->list); + + mutex_lock(&pmem[id].data_list_mutex); + list_add(&data->list, &pmem[id].data_list); + mutex_unlock(&pmem[id].data_list_mutex); + return ret; +} + +static unsigned long pmem_order(unsigned long len, int id) +{ + int i; + + len = (len + pmem[id].quantum - 1)/pmem[id].quantum; + len--; + for (i = 0; i < sizeof(len)*8; i++) + if (len >> i == 0) + break; + return i; +} + +static int pmem_allocator_all_or_nothing(const int id, + const unsigned long len, + const unsigned int align) +{ + /* 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; +} + +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) + goto out; + + DLOG("order %lx\n", order); + + /* 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. + */ + 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_BUDDY_ORDER(id, curr) > + (unsigned char)order && + (best_fit < 0 || + PMEM_BUDDY_ORDER(id, curr) < + PMEM_BUDDY_ORDER(id, best_fit))) + best_fit = curr; + } + + /* if best_fit < 0, there are no suitable slots; return an error */ + if (best_fit < 0) { +#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_BUDDY_ORDER(id, best_fit) > (unsigned char)order) { + int buddy; + PMEM_BUDDY_ORDER(id, best_fit) -= 1; + buddy = PMEM_BUDDY_INDEX(id, best_fit); + PMEM_BUDDY_ORDER(id, buddy) = PMEM_BUDDY_ORDER(id, best_fit); + } + pmem[id].allocator.buddy_bestfit.buddy_bitmap[best_fit].allocated = 1; +out: + return best_fit; +} + + +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 start_bit) +{ + int bit_start, last_bit, word_index; + + if (num_bits_to_alloc <= 0) + return -1; + + for (bit_start = start_bit; ; + bit_start = ((last_bit + + (word_index << PMEM_32BIT_WORD_ORDER) + spacing - 1) + & ~(spacing - 1)) + start_bit) { + 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, + start_bit); + +#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_writecombine + if (pmem[id].cached == 0 || file->f_flags & O_SYNC) + /* on ARMv6 and ARMv7 this expands to Normal Noncached */ + return pgprot_writecombine(vma_prot); +#endif +#ifdef pgprot_ext_buffered + else if (pmem[id].buffered) + return pgprot_ext_buffered(vma_prot); +#endif + return vma_prot; +} + +static unsigned long pmem_start_addr_all_or_nothing(int id, + struct pmem_data *data) +{ + 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 unsigned long pmem_start_addr_bitmap(int id, struct pmem_data *data) +{ + return data->index * pmem[id].quantum + pmem[id].base; +} + +static unsigned long pmem_start_addr_system(int id, struct pmem_data *data) +{ + 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[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, + struct pmem_data *data, unsigned long offset, + unsigned long len) +{ + int i, garbage_pages = len >> PAGE_SHIFT; + + vma->vm_flags |= VM_IO | VM_RESERVED | VM_PFNMAP | VM_SHARED | VM_WRITE; + for (i = 0; i < garbage_pages; i++) { + if (vm_insert_pfn(vma, vma->vm_start + offset + (i * PAGE_SIZE), + pmem[id].garbage_pfn)) + return -EAGAIN; + } + return 0; +} + +static int pmem_unmap_pfn_range(int id, struct vm_area_struct *vma, + struct pmem_data *data, unsigned long offset, + unsigned long len) +{ + int garbage_pages; + DLOG("unmap offset %lx len %lx\n", offset, len); + + BUG_ON(!PMEM_IS_PAGE_ALIGNED(len)); + + garbage_pages = len >> PAGE_SHIFT; + zap_page_range(vma, vma->vm_start + offset, len, NULL); + pmem_map_garbage(id, vma, data, offset, len); + return 0; +} + +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)); + + 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 ret; +} + +static int pmem_remap_pfn_range(int id, struct vm_area_struct *vma, + struct pmem_data *data, unsigned long offset, + unsigned long len) +{ + /* hold the mm semp for the vma you are modifying when you call this */ + BUG_ON(!vma); + zap_page_range(vma, vma->vm_start + offset, len, NULL); + return pmem_map_pfn_range(id, vma, data, offset, len); +} + +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)); + /* 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_read(&data->sem); +} + +static void pmem_vma_close(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct pmem_data *data = file->private_data; + +#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) && + (data->flags & PMEM_FLAGS_SUBMAP)) + data->flags |= PMEM_FLAGS_UNSUBMAP; + } + /* the kernel is going to free this vma now anyway */ + up_write(&data->sem); +} + +static struct vm_operations_struct vm_ops = { + .open = pmem_vma_open, + .close = pmem_vma_close, +}; + +static int pmem_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct pmem_data *data = file->private_data; + int index = -1; + 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 + pr_err("pmem: mmaps must be at offset zero, aligned" + " and a multiple of pages_size.\n"); +#endif + return -EINVAL; + } + + 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 + 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->index == -1) { + mutex_lock(&pmem[id].arena_mutex); + index = pmem_allocate_from_id(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; + } + + if (pmem[id].len(id, data) < vma_size) { +#if PMEM_DEBUG + 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[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)) { + pr_alert("pmem: mmap failed in kernel!\n"); + ret = -EAGAIN; + goto error; + } + list_for_each(elt, &data->region_list) { + region_node = list_entry(elt, struct pmem_region_node, + list); + DLOG("remapping file: %p %lx %lx\n", file, + region_node->region.offset, + region_node->region.len); + if (pmem_remap_pfn_range(id, vma, data, + region_node->region.offset, + region_node->region.len)) { + ret = -EAGAIN; + goto error; + } + } + data->flags |= PMEM_FLAGS_SUBMAP; + get_task_struct(current->group_leader); + data->task = current->group_leader; + data->vma = vma; +#if PMEM_DEBUG + data->pid = current->pid; +#endif + DLOG("submmapped file %p vma %p pid %u\n", file, vma, + current->pid); + } else { + if (pmem_map_pfn_range(id, vma, data, 0, vma_size)) { + pr_err("pmem: mmap failed in kernel!\n"); + ret = -EAGAIN; + goto error; + } + data->flags |= PMEM_FLAGS_MASTERMAP; + data->pid = current->pid; + } + vma->vm_ops = &vm_ops; +error: + up_write(&data->sem); + return ret; +} + +/* the following are the api for accessing pmem regions by other drivers + * from inside the kernel */ +int get_pmem_user_addr(struct file *file, unsigned long *start, + unsigned long *len) +{ + 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 + pr_err("pmem: %s: no vma present.\n", + __func__); +#endif + } + ret = 0; + } + up_read(&data->sem); + } + +#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) +{ + int ret = -1; + + if (is_pmem_file(file)) { + struct pmem_data *data = file->private_data; + + 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 + 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); + } + } + return ret; +} +EXPORT_SYMBOL(get_pmem_file); + +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 + fput(file); + } +} +EXPORT_SYMBOL(put_pmem_file); + +void put_pmem_fd(int fd) +{ + int put_needed; + struct file *file = fget_light(fd, &put_needed); + + if (file) { + put_pmem_file(file); + fput_light(file, put_needed); + } +} + +void flush_pmem_fd(int fd, unsigned long offset, unsigned long len) +{ + int fput_needed; + struct file *file = fget_light(fd, &fput_needed); + + if (file) { + flush_pmem_file(file, offset, len); + fput_light(file, fput_needed); + } +} + +void flush_pmem_file(struct file *file, unsigned long offset, unsigned long len) +{ + struct pmem_data *data; + int id; + void *vaddr; + struct pmem_region_node *region_node; + struct list_head *elt; + void *flush_start, *flush_end; +#ifdef CONFIG_OUTER_CACHE + unsigned long phy_start, phy_end; +#endif + if (!is_pmem_file(file)) + return; + + id = get_id(file); + 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[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 */ + list_for_each(elt, &data->region_list) { + region_node = list_entry(elt, struct pmem_region_node, list); + if ((offset >= region_node->region.offset) && + ((offset + len) <= (region_node->region.offset + + region_node->region.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; + } + } +end: + 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; + + /* + * check that the vaddr passed for flushing is valid + * so that you don't crash the kernel + */ + if (!pmem_addr->vaddr) + return -EINVAL; + + 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) +{ + 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; + } + + src_file = fget_light(connect, &put_needed); + + if (!src_file) { + pr_err("pmem: %s: src file not found!\n", __func__); + ret = -EBADF; + goto leave; + } + + 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 put_src_file; + } + + if (unlikely(!is_pmem_file(src_file))) { + pr_err("pmem: %s: src file is not a pmem file!\n", + __func__); + ret = -EINVAL; + 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); + + 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); +leave: + return ret; +} + +static void pmem_unlock_data_and_mm(struct pmem_data *data, + struct mm_struct *mm) +{ + up_write(&data->sem); + if (mm != NULL) { + up_write(&mm->mmap_sem); + mmput(mm); + } +} + +static int pmem_lock_data_and_mm(struct file *file, struct pmem_data *data, + struct mm_struct **locked_mm) +{ + 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 + pr_alert("pmem: can't remap - task is gone!\n"); +#endif + return -1; + } + } + up_read(&data->sem); + + if (mm) + down_write(&mm->mmap_sem); + + down_write(&data->sem); + /* check that the file didn't get mmaped before we could take the + * data sem, this should be safe b/c you can only submap each file + * once */ + if (PMEM_IS_SUBMAP(data) && !mm) { + pmem_unlock_data_and_mm(data, mm); + DLOG("mapping contention, repeating mmap op\n"); + goto lock_mm; + } + /* now check that vma.mm is still there, it could have been + * deleted by vma_close before we could get the data->sem */ + if ((data->flags & PMEM_FLAGS_UNSUBMAP) && (mm != NULL)) { + /* might as well release this */ + if (data->flags & PMEM_FLAGS_SUBMAP) { + put_task_struct(data->task); + data->task = NULL; + /* lower the submap flag to show the mm is gone */ + 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; + return ret; +} + +int pmem_remap(struct pmem_region *region, struct file *file, + unsigned operation) +{ + int ret; + struct pmem_region_node *region_node; + struct mm_struct *mm = NULL; + struct list_head *elt, *elt2; + int id = get_id(file); + 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 + pr_err("pmem: request for unaligned pmem" + "suballocation %lx %lx\n", + region->offset, region->len); +#endif + return -EINVAL; + } + + /* if userspace requests a region of len 0, there's nothing to do */ + if (region->len == 0) + return 0; + + /* lock the mm and data */ + ret = pmem_lock_data_and_mm(file, data, &mm); + if (ret) + return 0; + + /* only the owner of the master file can remap the client fds + * that back in it */ + if (!is_master_owner(file)) { +#if PMEM_DEBUG + 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[id].len(id, data)) || + (region->len > pmem[id].len(id, data)) || + (region->offset + region->len > pmem[id].len(id, data)))) { +#if PMEM_DEBUG + pr_err("pmem: suballoc doesn't fit in src_file!\n"); +#endif + ret = -EINVAL; + goto err; + } + + if (operation == PMEM_MAP) { + region_node = kmalloc(sizeof(struct pmem_region_node), + GFP_KERNEL); + if (!region_node) { + ret = -ENOMEM; +#if PMEM_DEBUG + pr_alert("pmem: No space to allocate remap metadata!"); +#endif + goto err; + } + region_node->region = *region; + list_add(®ion_node->list, &data->region_list); + } else if (operation == PMEM_UNMAP) { + int found = 0; + list_for_each_safe(elt, elt2, &data->region_list) { + region_node = list_entry(elt, struct pmem_region_node, + list); + if (region->len == 0 || + (region_node->region.offset == region->offset && + region_node->region.len == region->len)) { + list_del(elt); + kfree(region_node); + found = 1; + } + } + if (!found) { +#if PMEM_DEBUG + pr_err("pmem: Unmap region does not map any" + " mapped region!"); +#endif + ret = -EINVAL; + goto err; + } + } + + if (data->vma && PMEM_IS_SUBMAP(data)) { + if (operation == PMEM_MAP) + ret = pmem_remap_pfn_range(id, data->vma, data, + region->offset, region->len); + else if (operation == PMEM_UNMAP) + ret = pmem_unmap_pfn_range(id, data->vma, data, + region->offset, region->len); + } + +err: + pmem_unlock_data_and_mm(data, mm); + return ret; +} + +static void pmem_revoke(struct file *file, struct pmem_data *data) +{ + struct pmem_region_node *region_node; + struct list_head *elt, *elt2; + struct mm_struct *mm = NULL; + int id = get_id(file); + int ret = 0; + + data->master_file = NULL; + ret = pmem_lock_data_and_mm(file, data, &mm); + /* if lock_data_and_mm fails either the task that mapped the fd, or + * the vma that mapped it have already gone away, nothing more + * needs to be done */ + if (ret) + return; + /* unmap everything */ + /* delete the regions and region list nothing is mapped any more */ + if (data->vma) + list_for_each_safe(elt, elt2, &data->region_list) { + region_node = list_entry(elt, struct pmem_region_node, + list); + pmem_unmap_pfn_range(id, data->vma, data, + region_node->region.offset, + region_node->region.len); + list_del(elt); + kfree(region_node); + } + /* delete the master file */ + pmem_unlock_data_and_mm(data, mm); +} + +static void pmem_get_size(struct pmem_region *region, struct file *file) +{ + /* 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; + } else { + region->offset = pmem[id].start_addr(id, data); + region->len = pmem[id].len(id, data); + } + 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) +{ + /* 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 { + region.offset = pmem[id].start_addr(id, data); + region.len = pmem[id].len(id, data); + } + 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; + 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; + return pmem_remap(®ion, file, PMEM_UNMAP); + break; + } + case PMEM_GET_SIZE: + { + struct pmem_region region; + DLOG("get_size\n"); + pmem_get_size(®ion, file); + if (copy_to_user((void __user *)arg, ®ion, + sizeof(struct pmem_region))) + return -EFAULT; + break; + } + case PMEM_GET_TOTAL_SIZE: + { + struct pmem_region region; + DLOG("get total size\n"); + region.offset = 0; + get_id(file); + region.len = pmem[id].size; + if (copy_to_user((void __user *)arg, ®ion, + sizeof(struct pmem_region))) + 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: + { + 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; + } + + mutex_lock(&pmem[id].arena_mutex); + data->index = pmem_allocate_from_id(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_allocate_from_id(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); + case PMEM_CLEAN_INV_CACHES: + case PMEM_CLEAN_CACHES: + case PMEM_INV_CACHES: + { + struct pmem_addr pmem_addr; + + if (copy_from_user(&pmem_addr, (void __user *)arg, + sizeof(struct pmem_addr))) + return -EFAULT; + + 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; +} + +static void ioremap_pmem(int id) +{ + unsigned long addr; + const struct mem_type *type; + + DLOG("PMEMDEBUG: ioremaping for %s\n", pmem[id].name); + if (pmem[id].map_on_demand) { + addr = (unsigned long)pmem[id].area->addr; + if (pmem[id].cached) + type = get_mem_type(MT_DEVICE_CACHED); + else + type = get_mem_type(MT_DEVICE); + DLOG("PMEMDEBUG: Remap phys %lx to virt %lx on %s\n", + pmem[id].base, addr, pmem[id].name); + if (ioremap_pages(addr, pmem[id].base, pmem[id].size, type)) { + pr_err("pmem: Failed to map pages\n"); + BUG(); + } + pmem[id].vbase = pmem[id].area->addr; + /* Flush the cache after installing page table entries to avoid + * aliasing when these pages are remapped to user space. + */ + flush_cache_vmap(addr, addr + pmem[id].size); + } else { + 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); + } +} + +int pmem_setup(struct android_pmem_platform_data *pdata, + long (*ioctl)(struct file *, unsigned int, unsigned long), + int (*release)(struct inode *, struct file *)) +{ + int i, index = 0, id; + struct vm_struct *pmem_vma = NULL; + struct page *page; + + 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; + } + + 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; + } + + id = id_count++; + + pmem[id].id = id; + + 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; + } + + 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].cached = pdata->cached; + pmem[id].buffered = pdata->buffered; + 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; + 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; + pmem[id].reusable = pdata->reusable; + pr_info("pmem: Initializing %s as %s\n", + pdata->name, pdata->cached ? "cached" : "non-cached"); + + if (misc_register(&pmem[id].dev)) { + pr_alert("Unable to register pmem driver!\n"); + goto err_cant_register_device; + } + + if (!pmem[id].reusable) { + pmem[id].base = allocate_contiguous_memory_nomap(pmem[id].size, + pmem[id].memory_type, PAGE_SIZE); + if (!pmem[id].base) { + pr_err("pmem: Cannot allocate from reserved memory for %s\n", + pdata->name); + goto err_misc_deregister; + } + } + + /* reusable pmem requires map on demand */ + pmem[id].map_on_demand = pdata->map_on_demand || pdata->reusable; + if (pmem[id].map_on_demand) { + if (pmem[id].reusable) { + const struct fmem_data *fmem_info = fmem_get_info(); + pmem[id].area = fmem_info->area; + pmem[id].base = fmem_info->phys; + } else { + pmem_vma = get_vm_area(pmem[id].size, VM_IOREMAP); + if (!pmem_vma) { + pr_err("pmem: Failed to allocate virtual space for " + "%s\n", pdata->name); + goto err_free; + } + pr_err("pmem: Reserving virtual address range %lx - %lx for" + " %s\n", (unsigned long) pmem_vma->addr, + (unsigned long) pmem_vma->addr + pmem[id].size, + pdata->name); + pmem[id].area = pmem_vma; + } + } else + pmem[id].area = NULL; + + page = alloc_page(GFP_KERNEL); + if (!page) { + pr_err("pmem: Failed to allocate page for %s\n", pdata->name); + goto cleanup_vm; + } + pmem[id].garbage_pfn = page_to_pfn(page); + atomic_set(&pmem[id].allocation_cnt, 0); + + if (pdata->setup_region) + pmem[id].region_data = pdata->setup_region(); + + if (pdata->request_region) + pmem[id].mem_request = pdata->request_region; + + if (pdata->release_region) + pmem[id].mem_release = pdata->release_region; + + pr_info("allocating %lu bytes at %lx physical for %s\n", + pmem[id].size, pmem[id].base, pmem[id].name); + + return 0; + +cleanup_vm: + if (!pmem[id].reusable) + remove_vm_area(pmem_vma); +err_free: + if (!pmem[id].reusable) + free_contiguous_memory_by_paddr(pmem[id].base); +err_misc_deregister: + 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; +} + +static int pmem_probe(struct platform_device *pdev) +{ + struct android_pmem_platform_data *pdata; + + if (!pdev || !pdev->dev.platform_data) { + 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); + if (pmem[id].vbase) + iounmap(pmem[id].vbase); + if (pmem[id].map_on_demand && !pmem[id].reusable && pmem[id].area) + free_vm_area(pmem[id].area); + if (pmem[id].base) + free_contiguous_memory_by_paddr(pmem[id].base); + 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); + } + 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", + .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); +} + +static void __exit pmem_exit(void) +{ + platform_driver_unregister(&pmem_driver); +} + +module_init(pmem_init); +module_exit(pmem_exit); + diff --git a/drivers/misc/pmic8058-pwm.c b/drivers/misc/pmic8058-pwm.c new file mode 100644 index 0000000000000000000000000000000000000000..33407b7ba28625e11de9e1fd0333dacf0f818bb3 --- /dev/null +++ b/drivers/misc/pmic8058-pwm.c @@ -0,0 +1,1085 @@ +/* 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 PWM driver + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#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) + +#define CHAN_LUT_SIZE (PM_PWM_LUT_SIZE / PM8058_PWM_CHANNELS) + +/* Private data */ +struct pm8058_pwm_chip; + +struct pwm_device { + struct device *dev; + int pwm_id; /* = bank/channel id */ + int in_use; + const char *label; + struct pm8058_pwm_period period; + int pwm_value; + int pwm_period; + int use_lut; /* Use LUT to output PWM */ + 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_pwm_pdata *pdata; +}; + +static struct pm8058_pwm_chip *pwm_chip; + +struct pm8058_pwm_lut { + /* LUT parameters */ + 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 void pm8058_pwm_save(u8 *u8p, u8 mask, u8 val) +{ + *u8p &= ~mask; + *u8p |= val & mask; +} + +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 = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_LPG_BANK_EN, reg); + if (rc) { + pr_err("pm8xxx_write(): rc=%d (Enable LPG Bank)\n", 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 = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_LPG_BANK_SEL, reg); + if (rc) + pr_err("pm8xxx_write(): rc=%d (Select PWM Bank)\n", 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 = pm8xxx_writeb(pwm->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 pm8058_pwm_calc_period(unsigned int period_us, + struct pm8058_pwm_period *period) +{ + 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; + } + } + } + } + + /* Use higher resolution */ + if (best_m >= 3 && n == 6) { + n += 3; + best_m -= 3; + } + + period->pwm_size = n; + period->clk = best_clk; + period->pre_div = best_div; + period->pre_div_exp = best_m; + + pr_debug("period=%u: n=%d, m=%d, clk[%d]=%s, div[%d]=%d\n", + (unsigned)period_us, n, best_m, + best_clk, clks[best_clk], best_div, pre_div[best_div]); +} + +static void pm8058_pwm_calc_pwm_value(struct pwm_device *pwm, + unsigned int period_us, + unsigned int duty_us) +{ + unsigned int max_pwm_value, tmp; + + /* Figure out pwm_value with overflow handling */ + tmp = 1 << (sizeof(tmp) * 8 - pwm->period.pwm_size); + if (duty_us < tmp) { + tmp = duty_us << pwm->period.pwm_size; + pwm->pwm_value = tmp / period_us; + } else { + tmp = period_us >> pwm->period.pwm_size; + pwm->pwm_value = duty_us / tmp; + } + max_pwm_value = (1 << pwm->period.pwm_size) - 1; + if (pwm->pwm_value > max_pwm_value) + pwm->pwm_value = max_pwm_value; +} + +static int pm8058_pwm_change_table(struct pwm_device *pwm, int duty_pct[], + int start_idx, int len, int raw_value) +{ + unsigned int pwm_value, max_pwm_value; + u8 cfg0, cfg1; + int i, pwm_size; + int rc = 0; + + pwm_size = (pwm->pwm_ctl[5] & PM8058_PWM_SIZE_9_BIT) ? 9 : 6; + max_pwm_value = (1 << pwm_size) - 1; + for (i = 0; i < len; i++) { + if (raw_value) + pwm_value = duty_pct[i]; + else + pwm_value = (duty_pct[i] << pwm_size) / 100; + + if (pwm_value > max_pwm_value) + pwm_value = max_pwm_value; + cfg0 = pwm_value; + cfg1 = (pwm_value >> 1) & 0x80; + cfg1 |= start_idx + i; + + rc = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_LPG_LUT_CFG0, cfg0); + if (rc) + break; + + rc = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_LPG_LUT_CFG1, cfg1); + if (rc) + break; + } + return rc; +} + +static void pm8058_pwm_save_index(struct pwm_device *pwm, + int low_idx, int high_idx, int flags) +{ + pwm->pwm_ctl[1] = high_idx & PM8058_PWM_HIGH_INDEX_MASK; + pwm->pwm_ctl[2] = low_idx & PM8058_PWM_LOW_INDEX_MASK; + + if (flags & PM_PWM_LUT_REVERSE) + pwm->pwm_ctl[1] |= PM8058_PWM_REVERSE_EN; + if (flags & PM_PWM_LUT_RAMP_UP) + pwm->pwm_ctl[2] |= PM8058_PWM_RAMP_UP; + if (flags & PM_PWM_LUT_LOOP) + pwm->pwm_ctl[2] |= PM8058_PWM_LOOP_EN; +} + +static void pm8058_pwm_save_period(struct pwm_device *pwm) +{ + u8 mask, val; + + val = ((pwm->period.clk + 1) << PM8058_PWM_CLK_SEL_SHIFT) + & PM8058_PWM_CLK_SEL_MASK; + val |= (pwm->period.pre_div << PM8058_PWM_PREDIVIDE_SHIFT) + & PM8058_PWM_PREDIVIDE_MASK; + val |= pwm->period.pre_div_exp & PM8058_PWM_M_MASK; + mask = PM8058_PWM_CLK_SEL_MASK | PM8058_PWM_PREDIVIDE_MASK | + PM8058_PWM_M_MASK; + pm8058_pwm_save(&pwm->pwm_ctl[4], mask, val); + + val = (pwm->period.pwm_size > 6) ? PM8058_PWM_SIZE_9_BIT : 0; + mask = PM8058_PWM_SIZE_9_BIT; + pm8058_pwm_save(&pwm->pwm_ctl[5], mask, val); +} + +static void pm8058_pwm_save_pwm_value(struct pwm_device *pwm) +{ + u8 mask, val; + + pwm->pwm_ctl[3] = pwm->pwm_value; + + val = (pwm->period.pwm_size > 6) ? (pwm->pwm_value >> 1) : 0; + mask = PM8058_PWM_VALUE_BIT8; + pm8058_pwm_save(&pwm->pwm_ctl[4], mask, val); +} + +static void pm8058_pwm_save_duty_time(struct pwm_device *pwm, + struct pm8058_pwm_lut *lut) +{ + int i; + u8 mask, val; + + /* Linear search for duty time */ + for (i = 0; i < PM8058_PWM_1KHZ_COUNT_MAX; i++) { + if (duty_msec[i] >= lut->lut_duty_ms) + break; + } + val = i << PM8058_PWM_1KHZ_COUNT_SHIFT; + + mask = PM8058_PWM_1KHZ_COUNT_MASK; + pm8058_pwm_save(&pwm->pwm_ctl[0], mask, val); +} + +static void pm8058_pwm_save_pause(struct pwm_device *pwm, + struct pm8058_pwm_lut *lut) +{ + int i, pause_cnt, time_cnt; + u8 mask, val; + + time_cnt = (pwm->pwm_ctl[0] & PM8058_PWM_1KHZ_COUNT_MASK) + >> PM8058_PWM_1KHZ_COUNT_SHIFT; + if (lut->flags & PM_PWM_LUT_PAUSE_HI_EN) { + pause_cnt = (lut->lut_pause_hi + duty_msec[time_cnt] / 2) + / duty_msec[time_cnt]; + /* Linear search for pause time */ + for (i = 0; i < PM8058_PWM_PAUSE_COUNT_MAX; i++) { + if (pause_count[i] >= pause_cnt) + break; + } + val = (i << PM8058_PWM_PAUSE_COUNT_HI_SHIFT) & + PM8058_PWM_PAUSE_COUNT_HI_MASK; + val |= PM8058_PWM_PAUSE_ENABLE_HIGH; + } else + val = 0; + + mask = PM8058_PWM_PAUSE_COUNT_HI_MASK | PM8058_PWM_PAUSE_ENABLE_HIGH; + pm8058_pwm_save(&pwm->pwm_ctl[5], mask, val); + + if (lut->flags & PM_PWM_LUT_PAUSE_LO_EN) { + /* Linear search for pause time */ + pause_cnt = (lut->lut_pause_lo + duty_msec[time_cnt] / 2) + / duty_msec[time_cnt]; + for (i = 0; i < PM8058_PWM_PAUSE_COUNT_MAX; i++) { + if (pause_count[i] >= pause_cnt) + break; + } + val = (i << PM8058_PWM_PAUSE_COUNT_LO_SHIFT) & + PM8058_PWM_PAUSE_COUNT_LO_MASK; + val |= PM8058_PWM_PAUSE_ENABLE_LOW; + } else + val = 0; + + mask = PM8058_PWM_PAUSE_COUNT_LO_MASK | PM8058_PWM_PAUSE_ENABLE_LOW; + pm8058_pwm_save(&pwm->pwm_ctl[6], mask, val); +} + +static int pm8058_pwm_write(struct pwm_device *pwm, int start, int end) +{ + int i, rc; + + /* Write in reverse way so 0 would be the last */ + for (i = end - 1; i >= start; i--) { + rc = pm8xxx_writeb(pwm->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); + return rc; + } + } + + return 0; +} + +static int pm8058_pwm_change_lut(struct pwm_device *pwm, + struct pm8058_pwm_lut *lut) +{ + int rc; + + pm8058_pwm_save_index(pwm, lut->lut_lo_index, + lut->lut_hi_index, lut->flags); + pm8058_pwm_save_duty_time(pwm, lut); + pm8058_pwm_save_pause(pwm, lut); + pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0); + + pm8058_pwm_bank_sel(pwm); + rc = pm8058_pwm_write(pwm, 0, 7); + + 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; + pwm->use_lut = 0; + + 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) +{ + int rc; + + if (pwm == NULL || IS_ERR(pwm) || + duty_us > 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; + } + + if (pwm->pwm_period != period_us) { + pm8058_pwm_calc_period(period_us, &pwm->period); + pm8058_pwm_save_period(pwm); + pwm->pwm_period = period_us; + } + + pm8058_pwm_calc_pwm_value(pwm, period_us, duty_us); + pm8058_pwm_save_pwm_value(pwm); + pm8058_pwm_save(&pwm->pwm_ctl[1], + PM8058_PWM_BYPASS_LUT, PM8058_PWM_BYPASS_LUT); + + pm8058_pwm_bank_sel(pwm); + rc = pm8058_pwm_write(pwm, 1, 6); + + pr_debug("duty/period=%u/%u usec: pwm_value=%d (of %d)\n", + (unsigned)duty_us, (unsigned)period_us, + pwm->pwm_value, 1 << pwm->period.pwm_size); + +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_config_period - change PWM period + * + * @pwm: the PWM device + * @pwm_p: period in struct pm8058_pwm_period + */ +int pm8058_pwm_config_period(struct pwm_device *pwm, + struct pm8058_pwm_period *period) +{ + int rc; + + if (pwm == NULL || IS_ERR(pwm) || period == NULL) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + rc = -EINVAL; + goto out_unlock; + } + + pwm->period.pwm_size = period->pwm_size; + pwm->period.clk = period->clk; + pwm->period.pre_div = period->pre_div; + pwm->period.pre_div_exp = period->pre_div_exp; + + pm8058_pwm_save_period(pwm); + pm8058_pwm_bank_sel(pwm); + rc = pm8058_pwm_write(pwm, 4, 6); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL(pm8058_pwm_config_period); + +/** + * pm8058_pwm_config_duty_cycle - change PWM duty cycle + * + * @pwm: the PWM device + * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size) + */ +int pm8058_pwm_config_duty_cycle(struct pwm_device *pwm, int pwm_value) +{ + struct pm8058_pwm_lut lut; + int flags, start_idx; + int rc = 0; + + if (pwm == NULL || IS_ERR(pwm)) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use || !pwm->pwm_period) { + rc = -EINVAL; + goto out_unlock; + } + + if (pwm->pwm_value == pwm_value) + goto out_unlock; + + pwm->pwm_value = pwm_value; + flags = PM_PWM_LUT_RAMP_UP; + + start_idx = pwm->pwm_id * CHAN_LUT_SIZE; + pm8058_pwm_change_table(pwm, &pwm_value, start_idx, 1, 1); + + if (!pwm->use_lut) { + pwm->use_lut = 1; + + lut.lut_duty_ms = 1; + lut.lut_lo_index = start_idx; + lut.lut_hi_index = start_idx; + lut.lut_pause_lo = 0; + lut.lut_pause_hi = 0; + lut.flags = flags; + + rc = pm8058_pwm_change_lut(pwm, &lut); + } else { + pm8058_pwm_save_index(pwm, start_idx, start_idx, flags); + pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0); + + pm8058_pwm_bank_sel(pwm); + rc = pm8058_pwm_write(pwm, 0, 3); + } + + if (rc) + pr_err("[%d]: pm8058_pwm_write: rc=%d\n", pwm->pwm_id, rc); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL(pm8058_pwm_config_duty_cycle); + +/** + * 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 pm8058_pwm_lut lut; + int 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; + } + + if (pwm->pwm_period != period_us) { + pm8058_pwm_calc_period(period_us, &pwm->period); + pm8058_pwm_save_period(pwm); + pwm->pwm_period = period_us; + } + + len = (idx_len > PM_PWM_LUT_SIZE) ? PM_PWM_LUT_SIZE : idx_len; + + if (flags & PM_PWM_LUT_NO_TABLE) + goto after_table_write; + + rc = pm8058_pwm_change_table(pwm, duty_pct, start_idx, len, 0); + if (rc) { + pr_err("pm8058_pwm_change_table: rc=%d\n", rc); + goto out_unlock; + } + +after_table_write: + lut.lut_duty_ms = duty_time_ms; + lut.lut_lo_index = start_idx; + lut.lut_hi_index = start_idx + len - 1; + lut.lut_pause_lo = pause_lo; + lut.lut_pause_hi = pause_hi; + lut.flags = flags; + + rc = pm8058_pwm_change_lut(pwm, &lut); + +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 = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_LED(id), conf); + 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 = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_FLASH(id), conf); + 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 = pm8xxx_writeb(pwm->dev->parent, + SSBI_REG_ADDR_LPG_TEST, reg); + if (rc) + pr_err("pm8xxx_write(DTEST=0x%x): rc=%d\n", reg, rc); + + } + return rc; +} +EXPORT_SYMBOL(pm8058_pwm_set_dtest); + +static int __devinit pmic8058_pwm_probe(struct platform_device *pdev) +{ + struct pm8058_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 < PM8058_PWM_CHANNELS; i++) { + chip->pwm_dev[i].pwm_id = i; + chip->pwm_dev[i].chip = chip; + chip->pwm_dev[i].dev = &pdev->dev; + } + + mutex_init(&chip->pwm_mutex); + + chip->pdata = pdev->dev.platform_data; + pwm_chip = chip; + platform_set_drvdata(pdev, chip); + + pr_notice("OK\n"); + 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-xoadc.c b/drivers/misc/pmic8058-xoadc.c new file mode 100644 index 0000000000000000000000000000000000000000..345267246d1faf4d002e27c1c524538ed588578a --- /dev/null +++ b/drivers/misc/pmic8058-xoadc.c @@ -0,0 +1,800 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 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 device *dev; + struct xoadc_platform_data *pdata; + 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; + struct wake_lock adc_wakelock; + /* flag to warn/bug if wakelocks are taken after suspend_noirq */ + int msm_suspend_check; +}; + +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, uint32_t channel) +{ + 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) { + if (adc_pmic->msm_suspend_check) + pr_err("XOADC request being made after suspend irq " + "with channel id:%d\n", channel); + 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(); + wake_lock(&adc_pmic->adc_wakelock); + } + + /* 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, ADC_ARB_USRP_CNTRL, + data_arb_cntrl); + 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); + wake_unlock(&adc_pmic->adc_wakelock); + } + + 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 = 0, data_amux_chan = 0, data_arb_rsv = 0; + u8 data_dig_param = 0, data_ana_param2 = 0, data_ana_param = 0; + int rc; + + rc = pm8058_xoadc_arb_cntrl(1, adc_instance, slot->chan_path); + 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[1]; + 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 = pm8xxx_writeb(adc_pmic->dev->parent, + ADC_ARB_USRP_AMUX_CNTRL, data_amux_chan); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + rc = pm8xxx_writeb(adc_pmic->dev->parent, + ADC_ARB_USRP_RSV, data_arb_rsv); + 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 = pm8xxx_writeb(adc_pmic->dev->parent, + ADC_ARB_USRP_ANA_PARAM, data_ana_param); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + rc = pm8xxx_writeb(adc_pmic->dev->parent, + ADC_ARB_USRP_DIG_PARAM, data_dig_param); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + rc = pm8xxx_writeb(adc_pmic->dev->parent, + ADC_ARB_USRP_ANA_PARAM, data_ana_param2); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + enable_irq(adc_pmic->adc_irq); + + rc = pm8xxx_writeb(adc_pmic->dev->parent, + ADC_ARB_USRP_CNTRL, data_arb_cntrl); + 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 = pm8xxx_readb(adc_pmic->dev->parent, ADC_ARB_USRP_DATA0, + &rslt_lsb); + if (rc < 0) { + pr_debug("%s: PM8058 read failed\n", __func__); + return rc; + } + + rc = pm8xxx_readb(adc_pmic->dev->parent, ADC_ARB_USRP_DATA1, + &rslt_msb); + 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, CHANNEL_MUXOFF); + 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); + +#ifdef CONFIG_PM +static int pm8058_xoadc_suspend_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pmic8058_adc *adc_pmic = platform_get_drvdata(pdev); + + adc_pmic->msm_suspend_check = 1; + + return 0; +} + +static int pm8058_xoadc_resume_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pmic8058_adc *adc_pmic = platform_get_drvdata(pdev); + + adc_pmic->msm_suspend_check = 0; + + return 0; +} + +static const struct dev_pm_ops pm8058_xoadc_dev_pm_ops = { + .suspend_noirq = pm8058_xoadc_suspend_noirq, + .resume_noirq = pm8058_xoadc_resume_noirq, +}; + +#define PM8058_XOADC_DEV_PM_OPS (&pm8058_xoadc_dev_pm_ops) +#else +#define PM8058_XOADC_DEV_PM_OPS NULL +#endif + +static int __devinit pm8058_xoadc_probe(struct platform_device *pdev) +{ + struct xoadc_platform_data *pdata = pdev->dev.platform_data; + struct pmic8058_adc *adc_pmic; + int i, rc = 0; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + adc_pmic = devm_kzalloc(&pdev->dev, sizeof(*adc_pmic), GFP_KERNEL); + if (!adc_pmic) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_pmic->dev = &pdev->dev; + 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"); + return -EINVAL; + } + + adc_pmic->pdata = pdata; + adc_pmic->adc_graph = devm_kzalloc(&pdev->dev, + sizeof(struct linear_graph) * MAX_CHANNEL_PROPERTIES_QUEUE, + GFP_KERNEL); + if (!adc_pmic->adc_graph) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + /* 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) + return -ENXIO; + + 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"); + return rc; + } + + disable_irq(adc_pmic->adc_irq); + + 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"); + return PTR_ERR(adc_pmic->adc_voter); + } + } + + device_init_wakeup(&pdev->dev, pdata->xoadc_wakeup); + wake_lock_init(&adc_pmic->adc_wakelock, WAKE_LOCK_SUSPEND, + "pmic8058_xoadc_wakelock"); + + 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; +} + +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(); + + wake_lock_destroy(&adc_pmic->adc_wakelock); + msm_xo_put(adc_pmic->adc_voter); + device_init_wakeup(&pdev->dev, 0); + xoadc_initialized = false; + + return 0; +} + +static struct platform_driver pm8058_xoadc_driver = { + .probe = pm8058_xoadc_probe, + .remove = __devexit_p(pm8058_xoadc_teardown), + .driver = { + .name = "pm8058-xoadc", + .owner = THIS_MODULE, + .pm = PM8058_XOADC_DEV_PM_OPS, + }, +}; + +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 0000000000000000000000000000000000000000..341e5b27d2c50fb4cbf3ca72896d4089a0b0d252 --- /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/qseecom.c b/drivers/misc/qseecom.c new file mode 100644 index 0000000000000000000000000000000000000000..4c92ee5214654c9e65b762b54ab111c5b8285238 --- /dev/null +++ b/drivers/misc/qseecom.c @@ -0,0 +1,1781 @@ + + +/* Qualcomm Secure Execution Environment Communicator (QSEECOM) driver + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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) "QSEECOM: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qseecom_legacy.h" + +#define QSEECOM_DEV "qseecom" +#define QSEOS_VERSION_13 0x13 +#define QSEOS_VERSION_14 0x14 +#define QSEOS_CHECK_VERSION_CMD 0x00001803; + +enum qseecom_command_scm_resp_type { + QSEOS_APP_ID = 0xEE01, + QSEOS_LISTENER_ID +}; + +enum qseecom_qceos_cmd_id { + QSEOS_APP_START_COMMAND = 0x01, + QSEOS_APP_SHUTDOWN_COMMAND, + QSEOS_APP_LOOKUP_COMMAND, + QSEOS_REGISTER_LISTENER, + QSEOS_DEREGISTER_LISTENER, + QSEOS_CLIENT_SEND_DATA_COMMAND, + QSEOS_LISTENER_DATA_RSP_COMMAND, + QSEOS_LOAD_EXTERNAL_ELF_COMMAND, + QSEOS_UNLOAD_EXTERNAL_ELF_COMMAND, + QSEOS_CMD_MAX = 0xEFFFFFFF +}; + +enum qseecom_qceos_cmd_status { + QSEOS_RESULT_SUCCESS = 0, + QSEOS_RESULT_INCOMPLETE, + QSEOS_RESULT_FAILURE = 0xFFFFFFFF +}; + +enum qseecom_clk_definitions { + CLK_DFAB = 0, + CLK_SFPB, +}; + +__packed struct qseecom_check_app_ireq { + uint32_t qsee_cmd_id; + char app_name[MAX_APP_NAME_SIZE]; +}; + +__packed struct qseecom_load_app_ireq { + uint32_t qsee_cmd_id; + uint32_t mdt_len; /* Length of the mdt file */ + uint32_t img_len; /* Length of .bxx and .mdt files */ + uint32_t phy_addr; /* phy addr of the start of image */ + char app_name[MAX_APP_NAME_SIZE]; /* application name*/ +}; + +__packed struct qseecom_unload_app_ireq { + uint32_t qsee_cmd_id; + uint32_t app_id; +}; + +__packed struct qseecom_register_listener_ireq { + uint32_t qsee_cmd_id; + uint32_t listener_id; + void *sb_ptr; + uint32_t sb_len; +}; + +__packed struct qseecom_unregister_listener_ireq { + uint32_t qsee_cmd_id; + uint32_t listener_id; +}; + +__packed struct qseecom_client_send_data_ireq { + uint32_t qsee_cmd_id; + uint32_t app_id; + void *req_ptr; + uint32_t req_len; + void *rsp_ptr; /* First 4 bytes should always be the return status */ + uint32_t rsp_len; +}; + +/* send_data resp */ +__packed struct qseecom_client_listener_data_irsp { + uint32_t qsee_cmd_id; + uint32_t listener_id; +}; + +/* + * struct qseecom_command_scm_resp - qseecom 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 qseecom_command_scm_resp { + uint32_t result; + enum qseecom_command_scm_resp_type resp_type; + unsigned int data; +}; + +static struct class *driver_class; +static dev_t qseecom_device_no; +static struct cdev qseecom_cdev; + +/* Data structures used in legacy support */ +static void *pil; +static uint32_t pil_ref_cnt; +static DEFINE_MUTEX(pil_access_lock); + +static DEFINE_MUTEX(send_msg_lock); +static DEFINE_MUTEX(qsee_bw_mutex); +static DEFINE_MUTEX(qsee_sfpb_bw_mutex); +static DEFINE_MUTEX(app_access_lock); + +static int qsee_bw_count; +static int qsee_sfpb_bw_count; +static struct clk *qseecom_bus_clk; +static uint32_t qsee_perf_client; + +struct qseecom_registered_listener_list { + struct list_head list; + struct qseecom_register_listener_req svc; + u8 *sb_reg_req; + u8 *sb_virt; + s32 sb_phys; + size_t sb_length; + struct ion_handle *ihandle; /* Retrieve phy addr */ + + wait_queue_head_t rcv_req_wq; + int rcv_req_flag; +}; + +struct qseecom_registered_app_list { + struct list_head list; + u32 app_id; + u32 ref_cnt; +}; + +struct qseecom_control { + struct ion_client *ion_clnt; /* Ion client */ + struct list_head registered_listener_list_head; + spinlock_t registered_listener_list_lock; + + struct list_head registered_app_list_head; + spinlock_t registered_app_list_lock; + + wait_queue_head_t send_resp_wq; + int send_resp_flag; + + uint32_t qseos_version; +}; + +struct qseecom_client_handle { + u32 app_id; + u8 *sb_virt; + s32 sb_phys; + uint32_t user_virt_sb_base; + size_t sb_length; + struct ion_handle *ihandle; /* Retrieve phy addr */ +}; + +struct qseecom_listener_handle { + u32 id; +}; + +static struct qseecom_control qseecom; + +struct qseecom_dev_handle { + bool service; + union { + struct qseecom_client_handle client; + struct qseecom_listener_handle listener; + }; + bool released; + int abort; + wait_queue_head_t abort_wq; + atomic_t ioctl_count; +}; + +/* Function proto types */ +static int qsee_vote_for_clock(int32_t); +static void qsee_disable_clock_vote(int32_t); + +static int __qseecom_is_svc_unique(struct qseecom_dev_handle *data, + struct qseecom_register_listener_req *svc) +{ + struct qseecom_registered_listener_list *ptr; + int unique = 1; + unsigned long flags; + + spin_lock_irqsave(&qseecom.registered_listener_list_lock, flags); + list_for_each_entry(ptr, &qseecom.registered_listener_list_head, list) { + if (ptr->svc.listener_id == svc->listener_id) { + pr_err("Service id: %u is already registered\n", + ptr->svc.listener_id); + unique = 0; + break; + } + } + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, flags); + return unique; +} + +static struct qseecom_registered_listener_list *__qseecom_find_svc( + int32_t listener_id) +{ + struct qseecom_registered_listener_list *entry = NULL; + unsigned long flags; + + spin_lock_irqsave(&qseecom.registered_listener_list_lock, flags); + list_for_each_entry(entry, &qseecom.registered_listener_list_head, list) + { + if (entry->svc.listener_id == listener_id) + break; + } + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, flags); + return entry; +} + +static int __qseecom_set_sb_memory(struct qseecom_registered_listener_list *svc, + struct qseecom_dev_handle *handle, + struct qseecom_register_listener_req *listener) +{ + int ret = 0; + unsigned int flags = 0; + struct qseecom_register_listener_ireq req; + struct qseecom_command_scm_resp resp; + ion_phys_addr_t pa; + + /* Get the handle of the shared fd */ + svc->ihandle = ion_import_fd(qseecom.ion_clnt, listener->ifd_data_fd); + if (svc->ihandle == NULL) { + pr_err("Ion client could not retrieve the handle\n"); + return -ENOMEM; + } + + /* Get the physical address of the ION BUF */ + ret = ion_phys(qseecom.ion_clnt, svc->ihandle, &pa, &svc->sb_length); + + /* Populate the structure for sending scm call to load image */ + svc->sb_virt = (char *) ion_map_kernel(qseecom.ion_clnt, + svc->ihandle, flags); + svc->sb_phys = pa; + + if (qseecom.qseos_version == QSEOS_VERSION_14) { + req.qsee_cmd_id = QSEOS_REGISTER_LISTENER; + req.listener_id = svc->svc.listener_id; + req.sb_len = svc->sb_length; + req.sb_ptr = (void *)svc->sb_phys; + + resp.result = QSEOS_RESULT_INCOMPLETE; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, + sizeof(req), &resp, sizeof(resp)); + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return -EINVAL; + } + + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err("Error SB registration req: resp.result = %d\n", + resp.result); + return -EPERM; + } + } else { + struct qseecom_command cmd; + struct qseecom_response resp; + struct qse_pr_init_sb_req_s sb_init_req; + struct qse_pr_init_sb_rsp_s sb_init_rsp; + + svc->sb_reg_req = kzalloc((sizeof(sb_init_req) + + sizeof(sb_init_rsp)), GFP_KERNEL); + + sb_init_req.pr_cmd = TZ_SCHED_CMD_ID_REGISTER_LISTENER; + sb_init_req.listener_id = svc->svc.listener_id; + sb_init_req.sb_len = svc->sb_length; + sb_init_req.sb_ptr = svc->sb_phys; + + memcpy(svc->sb_reg_req, &sb_init_req, sizeof(sb_init_req)); + + /* It will always be a new cmd from this method */ + cmd.cmd_type = TZ_SCHED_CMD_NEW; + cmd.sb_in_cmd_addr = (u8 *)(virt_to_phys(svc->sb_reg_req)); + cmd.sb_in_cmd_len = sizeof(sb_init_req); + + resp.cmd_status = TZ_SCHED_STATUS_INCOMPLETE; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &cmd, sizeof(cmd) + , &resp, sizeof(resp)); + + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return -EINVAL; + } + + if (resp.cmd_status != TZ_SCHED_STATUS_COMPLETE) { + pr_err("SB registration fail resp.cmd_status %d\n", + resp.cmd_status); + return -EINVAL; + } + memset(svc->sb_virt, 0, svc->sb_length); + } + return 0; +} + +static int qseecom_register_listener(struct qseecom_dev_handle *data, + void __user *argp) +{ + int ret = 0; + unsigned long flags; + struct qseecom_register_listener_req rcvd_lstnr; + struct qseecom_registered_listener_list *new_entry; + + ret = copy_from_user(&rcvd_lstnr, argp, sizeof(rcvd_lstnr)); + if (ret) { + pr_err("copy_from_user failed\n"); + return ret; + } + data->listener.id = 0; + data->service = true; + if (!__qseecom_is_svc_unique(data, &rcvd_lstnr)) { + pr_err("Service is not unique and is already registered\n"); + data->released = true; + return -EBUSY; + } + + new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + pr_err("kmalloc failed\n"); + return -ENOMEM; + } + memcpy(&new_entry->svc, &rcvd_lstnr, sizeof(rcvd_lstnr)); + new_entry->rcv_req_flag = 0; + + new_entry->svc.listener_id = rcvd_lstnr.listener_id; + new_entry->sb_length = rcvd_lstnr.sb_size; + if (__qseecom_set_sb_memory(new_entry, data, &rcvd_lstnr)) { + pr_err("qseecom_set_sb_memoryfailed\n"); + kzfree(new_entry); + return -ENOMEM; + } + + data->listener.id = rcvd_lstnr.listener_id; + init_waitqueue_head(&new_entry->rcv_req_wq); + + spin_lock_irqsave(&qseecom.registered_listener_list_lock, flags); + list_add_tail(&new_entry->list, &qseecom.registered_listener_list_head); + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, flags); + + return ret; +} + +static int qseecom_unregister_listener(struct qseecom_dev_handle *data) +{ + int ret = 0; + unsigned long flags; + uint32_t unmap_mem = 0; + struct qseecom_register_listener_ireq req; + struct qseecom_registered_listener_list *ptr_svc = NULL; + struct qseecom_command_scm_resp resp; + struct ion_handle *ihandle = NULL; /* Retrieve phy addr */ + + if (qseecom.qseos_version == QSEOS_VERSION_14) { + req.qsee_cmd_id = QSEOS_DEREGISTER_LISTENER; + req.listener_id = data->listener.id; + resp.result = QSEOS_RESULT_INCOMPLETE; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, + sizeof(req), &resp, sizeof(resp)); + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return ret; + } + + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err("SB deregistartion: result=%d\n", resp.result); + return -EPERM; + } + } else { + struct qse_pr_init_sb_req_s sb_init_req; + struct qseecom_command cmd; + struct qseecom_response resp; + struct qseecom_registered_listener_list *svc; + + svc = __qseecom_find_svc(data->listener.id); + sb_init_req.pr_cmd = TZ_SCHED_CMD_ID_REGISTER_LISTENER; + sb_init_req.listener_id = data->listener.id; + sb_init_req.sb_len = 0; + sb_init_req.sb_ptr = 0; + + memcpy(svc->sb_reg_req, &sb_init_req, sizeof(sb_init_req)); + + /* It will always be a new cmd from this method */ + cmd.cmd_type = TZ_SCHED_CMD_NEW; + cmd.sb_in_cmd_addr = (u8 *)(virt_to_phys(svc->sb_reg_req)); + cmd.sb_in_cmd_len = sizeof(sb_init_req); + resp.cmd_status = TZ_SCHED_STATUS_INCOMPLETE; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &cmd, sizeof(cmd), + &resp, sizeof(resp)); + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return ret; + } + kzfree(svc->sb_reg_req); + if (resp.cmd_status != TZ_SCHED_STATUS_COMPLETE) { + pr_err("Error with SB initialization\n"); + return -EPERM; + } + } + data->abort = 1; + spin_lock_irqsave(&qseecom.registered_listener_list_lock, flags); + list_for_each_entry(ptr_svc, &qseecom.registered_listener_list_head, + list) { + if (ptr_svc->svc.listener_id == data->listener.id) { + wake_up_all(&ptr_svc->rcv_req_wq); + break; + } + } + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, flags); + + while (atomic_read(&data->ioctl_count) > 1) { + if (wait_event_freezable(data->abort_wq, + atomic_read(&data->ioctl_count) <= 1)) { + pr_err("Interrupted from abort\n"); + ret = -ERESTARTSYS; + break; + } + } + + spin_lock_irqsave(&qseecom.registered_listener_list_lock, flags); + list_for_each_entry(ptr_svc, + &qseecom.registered_listener_list_head, + list) + { + if (ptr_svc->svc.listener_id == data->listener.id) { + if (ptr_svc->sb_virt) { + unmap_mem = 1; + ihandle = ptr_svc->ihandle; + } + list_del(&ptr_svc->list); + kzfree(ptr_svc); + break; + } + } + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, flags); + + /* Unmap the memory */ + if (unmap_mem) { + if (!IS_ERR_OR_NULL(ihandle)) { + ion_unmap_kernel(qseecom.ion_clnt, ihandle); + ion_free(qseecom.ion_clnt, ihandle); + } + } + data->released = true; + return ret; +} + +static int qseecom_set_client_mem_param(struct qseecom_dev_handle *data, + void __user *argp) +{ + ion_phys_addr_t pa; + int32_t ret; + unsigned int flags = 0; + struct qseecom_set_sb_mem_param_req req; + uint32_t len; + + /* Copy the relevant information needed for loading the image */ + if (__copy_from_user(&req, (void __user *)argp, sizeof(req))) + return -EFAULT; + + /* Get the handle of the shared fd */ + data->client.ihandle = ion_import_fd(qseecom.ion_clnt, req.ifd_data_fd); + if (IS_ERR_OR_NULL(data->client.ihandle)) { + pr_err("Ion client could not retrieve the handle\n"); + return -ENOMEM; + } + /* Get the physical address of the ION BUF */ + ret = ion_phys(qseecom.ion_clnt, data->client.ihandle, &pa, &len); + /* Populate the structure for sending scm call to load image */ + data->client.sb_virt = (char *) ion_map_kernel(qseecom.ion_clnt, + data->client.ihandle, + flags); + data->client.sb_phys = pa; + data->client.sb_length = req.sb_len; + data->client.user_virt_sb_base = req.virt_sb_base; + return 0; +} + + +static int __qseecom_listener_has_sent_rsp(struct qseecom_dev_handle *data) +{ + int ret; + ret = (qseecom.send_resp_flag != 0); + return ret || data->abort; +} + +static int __qseecom_process_incomplete_cmd(struct qseecom_dev_handle *data, + struct qseecom_command_scm_resp *resp) +{ + int ret = 0; + uint32_t lstnr; + unsigned long flags; + struct qseecom_client_listener_data_irsp send_data_rsp; + struct qseecom_registered_listener_list *ptr_svc = NULL; + + + while (resp->result == QSEOS_RESULT_INCOMPLETE) { + lstnr = resp->data; + /* + * Wake up blocking lsitener service with the lstnr id + */ + spin_lock_irqsave(&qseecom.registered_listener_list_lock, + flags); + list_for_each_entry(ptr_svc, + &qseecom.registered_listener_list_head, list) { + if (ptr_svc->svc.listener_id == lstnr) { + ptr_svc->rcv_req_flag = 1; + wake_up_interruptible(&ptr_svc->rcv_req_wq); + break; + } + } + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, + flags); + if (ptr_svc->svc.listener_id != lstnr) { + pr_warning("Service requested for does on exist\n"); + return -ERESTARTSYS; + } + pr_debug("waking up rcv_req_wq and " + "waiting for send_resp_wq\n"); + if (wait_event_freezable(qseecom.send_resp_wq, + __qseecom_listener_has_sent_rsp(data))) { + pr_warning("Interrupted: exiting send_cmd loop\n"); + return -ERESTARTSYS; + } + + if (data->abort) { + pr_err("Aborting listener service %d\n", + data->listener.id); + return -ENODEV; + } + qseecom.send_resp_flag = 0; + send_data_rsp.qsee_cmd_id = QSEOS_LISTENER_DATA_RSP_COMMAND; + send_data_rsp.listener_id = lstnr ; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, + (const void *)&send_data_rsp, + sizeof(send_data_rsp), resp, + sizeof(*resp)); + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return ret; + } + if (resp->result == QSEOS_RESULT_FAILURE) { + pr_err("Response result %d not supported\n", + resp->result); + return -EINVAL; + } + } + return ret; +} + +static int qseecom_load_app(struct qseecom_dev_handle *data, void __user *argp) +{ + struct qseecom_registered_app_list *entry = NULL; + unsigned long flags = 0; + u32 app_id = 0; + struct ion_handle *ihandle; /* Ion handle */ + struct qseecom_load_img_req load_img_req; + int32_t ret; + ion_phys_addr_t pa = 0; + uint32_t len; + struct qseecom_command_scm_resp resp; + struct qseecom_check_app_ireq req; + /* Copy the relevant information needed for loading the image */ + if (__copy_from_user(&load_img_req, + (void __user *)argp, + sizeof(struct qseecom_load_img_req))) { + pr_err("copy_from_user failed\n"); + return -EFAULT; + } + /* Vote for the SFPB clock */ + ret = qsee_vote_for_clock(CLK_SFPB); + if (ret) + pr_warning("Unable to vote for SFPB clock"); + + req.qsee_cmd_id = QSEOS_APP_LOOKUP_COMMAND; + memcpy(req.app_name, load_img_req.img_name, MAX_APP_NAME_SIZE); + + /* SCM_CALL to check if app_id for the mentioned app exists */ + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, + sizeof(struct qseecom_check_app_ireq), + &resp, sizeof(resp)); + if (ret) { + pr_err("scm_call to check if app is already loaded failed\n"); + return -EINVAL; + } + + if (resp.result == QSEOS_RESULT_FAILURE) + app_id = 0; + else + app_id = resp.data; + + if (app_id) { + pr_warn("App id %d (%s) already exists\n", app_id, + (char *)(req.app_name)); + spin_lock_irqsave(&qseecom.registered_app_list_lock, flags); + list_for_each_entry(entry, + &qseecom.registered_app_list_head, list){ + if (entry->app_id == app_id) { + entry->ref_cnt++; + break; + } + } + spin_unlock_irqrestore( + &qseecom.registered_app_list_lock, flags); + } else { + struct qseecom_load_app_ireq load_req; + + pr_warn("App (%s) does not exist, loading apps for first time\n", + (char *)(req.app_name)); + /* Get the handle of the shared fd */ + ihandle = ion_import_fd(qseecom.ion_clnt, + load_img_req.ifd_data_fd); + if (IS_ERR_OR_NULL(ihandle)) { + pr_err("Ion client could not retrieve the handle\n"); + qsee_disable_clock_vote(CLK_SFPB); + return -ENOMEM; + } + + /* Get the physical address of the ION BUF */ + ret = ion_phys(qseecom.ion_clnt, ihandle, &pa, &len); + + /* Populate the structure for sending scm call to load image */ + load_req.qsee_cmd_id = QSEOS_APP_START_COMMAND; + load_req.mdt_len = load_img_req.mdt_len; + load_req.img_len = load_img_req.img_len; + load_req.phy_addr = pa; + + /* SCM_CALL to load the app and get the app_id back */ + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &load_req, + sizeof(struct qseecom_load_app_ireq), + &resp, sizeof(resp)); + if (ret) { + pr_err("scm_call to load app failed\n"); + return -EINVAL; + } + + if (resp.result == QSEOS_RESULT_FAILURE) { + pr_err("scm_call rsp.result is QSEOS_RESULT_FAILURE\n"); + if (!IS_ERR_OR_NULL(ihandle)) + ion_free(qseecom.ion_clnt, ihandle); + qsee_disable_clock_vote(CLK_SFPB); + return -EFAULT; + } + + if (resp.result == QSEOS_RESULT_INCOMPLETE) { + ret = __qseecom_process_incomplete_cmd(data, &resp); + if (ret) { + pr_err("process_incomplete_cmd failed err: %d\n", + ret); + if (!IS_ERR_OR_NULL(ihandle)) + ion_free(qseecom.ion_clnt, ihandle); + qsee_disable_clock_vote(CLK_SFPB); + return ret; + } + } + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err("scm_call failed resp.result unknown, %d\n", + resp.result); + if (!IS_ERR_OR_NULL(ihandle)) + ion_free(qseecom.ion_clnt, ihandle); + qsee_disable_clock_vote(CLK_SFPB); + return -EFAULT; + } + + app_id = resp.data; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + pr_err("kmalloc failed\n"); + qsee_disable_clock_vote(CLK_SFPB); + return -ENOMEM; + } + entry->app_id = app_id; + entry->ref_cnt = 1; + + /* Deallocate the handle */ + if (!IS_ERR_OR_NULL(ihandle)) + ion_free(qseecom.ion_clnt, ihandle); + + spin_lock_irqsave(&qseecom.registered_app_list_lock, flags); + list_add_tail(&entry->list, &qseecom.registered_app_list_head); + spin_unlock_irqrestore(&qseecom.registered_app_list_lock, + flags); + + pr_warn("App with id %d (%s) now loaded\n", app_id, + (char *)(req.app_name)); + } + data->client.app_id = app_id; + load_img_req.app_id = app_id; + if (copy_to_user(argp, &load_img_req, sizeof(load_img_req))) { + pr_err("copy_to_user failed\n"); + kzfree(entry); + qsee_disable_clock_vote(CLK_SFPB); + return -EFAULT; + } + qsee_disable_clock_vote(CLK_SFPB); + return 0; +} + +static int __qseecom_cleanup_app(struct qseecom_dev_handle *data) +{ + wake_up_all(&qseecom.send_resp_wq); + while (atomic_read(&data->ioctl_count) > 1) { + if (wait_event_freezable(data->abort_wq, + atomic_read(&data->ioctl_count) <= 1)) { + pr_err("Interrupted from abort\n"); + return -ERESTARTSYS; + break; + } + } + /* Set unload app */ + return 1; +} + +static int qseecom_unload_app(struct qseecom_dev_handle *data) +{ + unsigned long flags; + int ret = 0; + struct qseecom_command_scm_resp resp; + struct qseecom_registered_app_list *ptr_app; + uint32_t unload = 0; + + if (qseecom.qseos_version == QSEOS_VERSION_14) { + spin_lock_irqsave(&qseecom.registered_app_list_lock, flags); + list_for_each_entry(ptr_app, &qseecom.registered_app_list_head, + list) { + if (ptr_app->app_id == data->client.app_id) { + if (ptr_app->ref_cnt == 1) { + unload = __qseecom_cleanup_app(data); + list_del(&ptr_app->list); + kzfree(ptr_app); + break; + } else { + ptr_app->ref_cnt--; + data->released = true; + pr_warn("Can't unload app with id %d (it is inuse)\n", + ptr_app->app_id); + break; + } + } + } + spin_unlock_irqrestore(&qseecom.registered_app_list_lock, + flags); + } + if (!IS_ERR_OR_NULL(data->client.ihandle)) { + ion_unmap_kernel(qseecom.ion_clnt, data->client.ihandle); + ion_free(qseecom.ion_clnt, data->client.ihandle); + } + + if ((unload) && (qseecom.qseos_version == QSEOS_VERSION_14)) { + struct qseecom_unload_app_ireq req; + + /* Populate the structure for sending scm call to load image */ + req.qsee_cmd_id = QSEOS_APP_SHUTDOWN_COMMAND; + req.app_id = data->client.app_id; + + /* SCM_CALL to unload the app */ + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, + sizeof(struct qseecom_unload_app_ireq), + &resp, sizeof(resp)); + if (ret) { + pr_err("scm_call to unload app (id = %d) failed\n", + req.app_id); + return -EFAULT; + } else { + pr_warn("App id %d now unloaded\n", req.app_id); + } + if (resp.result == QSEOS_RESULT_INCOMPLETE) { + ret = __qseecom_process_incomplete_cmd(data, &resp); + if (ret) { + pr_err("process_incomplete_cmd fail err: %d\n", + ret); + return ret; + } + } + } + + if (qseecom.qseos_version == QSEOS_VERSION_13) { + data->abort = 1; + wake_up_all(&qseecom.send_resp_wq); + while (atomic_read(&data->ioctl_count) > 0) { + if (wait_event_freezable(data->abort_wq, + atomic_read(&data->ioctl_count) <= 0)) { + pr_err("Interrupted from abort\n"); + ret = -ERESTARTSYS; + break; + } + } + } + data->released = true; + return ret; +} + +static uint32_t __qseecom_uvirt_to_kphys(struct qseecom_dev_handle *data, + uint32_t virt) +{ + return data->client.sb_phys + (virt - data->client.user_virt_sb_base); +} + +static int __qseecom_send_cmd_legacy(struct qseecom_dev_handle *data, + struct qseecom_send_cmd_req *req) +{ + int ret = 0; + unsigned long flags; + u32 reqd_len_sb_in = 0; + struct qseecom_command cmd; + struct qseecom_response resp; + + + if (req->cmd_req_buf == NULL || req->resp_buf == NULL) { + pr_err("cmd buffer or response buffer is null\n"); + return -EINVAL; + } + + if (req->cmd_req_len <= 0 || + req->resp_len <= 0 || + req->cmd_req_len > data->client.sb_length || + req->resp_len > data->client.sb_length) { + pr_err("cmd buffer length or " + "response buffer length not valid\n"); + return -EINVAL; + } + + reqd_len_sb_in = req->cmd_req_len + req->resp_len; + if (reqd_len_sb_in > data->client.sb_length) { + pr_debug("Not enough memory to fit cmd_buf and " + "resp_buf. Required: %u, Available: %u\n", + reqd_len_sb_in, data->client.sb_length); + return -ENOMEM; + } + cmd.cmd_type = TZ_SCHED_CMD_NEW; + cmd.sb_in_cmd_addr = (u8 *) data->client.sb_phys; + cmd.sb_in_cmd_len = req->cmd_req_len; + + resp.cmd_status = TZ_SCHED_STATUS_INCOMPLETE; + resp.sb_in_rsp_addr = (u8 *)data->client.sb_phys + req->cmd_req_len; + resp.sb_in_rsp_len = req->resp_len; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, (const void *)&cmd, + sizeof(cmd), &resp, sizeof(resp)); + + if (ret) { + pr_err("qseecom_scm_call_legacy failed with err: %d\n", ret); + return ret; + } + + 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 + */ + struct qseecom_registered_listener_list *ptr_svc = NULL; + /* + * 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(&qseecom.registered_listener_list_lock, + flags); + list_for_each_entry(ptr_svc, + &qseecom.registered_listener_list_head, list) { + ptr_svc->rcv_req_flag = 1; + wake_up_interruptible(&ptr_svc->rcv_req_wq); + } + spin_unlock_irqrestore(&qseecom.registered_listener_list_lock, + flags); + + pr_debug("waking up rcv_req_wq and " + "waiting for send_resp_wq\n"); + if (wait_event_freezable(qseecom.send_resp_wq, + __qseecom_listener_has_sent_rsp(data))) { + pr_warning("qseecom Interrupted: exiting send_cmd loop\n"); + return -ERESTARTSYS; + } + + if (data->abort) { + pr_err("Aborting driver\n"); + return -ENODEV; + } + qseecom.send_resp_flag = 0; + cmd.cmd_type = TZ_SCHED_CMD_PENDING; + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, (const void *)&cmd, + sizeof(cmd), &resp, sizeof(resp)); + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return ret; + } + } + return ret; +} + +static int __qseecom_send_cmd(struct qseecom_dev_handle *data, + struct qseecom_send_cmd_req *req) +{ + int ret = 0; + u32 reqd_len_sb_in = 0; + struct qseecom_client_send_data_ireq send_data_req; + struct qseecom_command_scm_resp resp; + + if (req->cmd_req_buf == NULL || req->resp_buf == NULL) { + pr_err("cmd buffer or response buffer is null\n"); + return -EINVAL; + } + + if (req->cmd_req_len <= 0 || + req->resp_len <= 0 || + req->cmd_req_len > data->client.sb_length || + req->resp_len > data->client.sb_length) { + pr_err("cmd buffer length or " + "response buffer length not valid\n"); + return -EINVAL; + } + + reqd_len_sb_in = req->cmd_req_len + req->resp_len; + if (reqd_len_sb_in > data->client.sb_length) { + pr_debug("Not enough memory to fit cmd_buf and " + "resp_buf. Required: %u, Available: %u\n", + reqd_len_sb_in, data->client.sb_length); + return -ENOMEM; + } + + send_data_req.qsee_cmd_id = QSEOS_CLIENT_SEND_DATA_COMMAND; + send_data_req.app_id = data->client.app_id; + send_data_req.req_ptr = (void *)(__qseecom_uvirt_to_kphys(data, + (uint32_t)req->cmd_req_buf)); + send_data_req.req_len = req->cmd_req_len; + send_data_req.rsp_ptr = (void *)(__qseecom_uvirt_to_kphys(data, + (uint32_t)req->resp_buf)); + send_data_req.rsp_len = req->resp_len; + + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, (const void *) &send_data_req, + sizeof(send_data_req), + &resp, sizeof(resp)); + if (ret) { + pr_err("qseecom_scm_call failed with err: %d\n", ret); + return ret; + } + + if (resp.result == QSEOS_RESULT_INCOMPLETE) { + ret = __qseecom_process_incomplete_cmd(data, &resp); + if (ret) { + pr_err("process_incomplete_cmd failed err: %d\n", ret); + return ret; + } + } else { + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err("Response result %d not supported\n", + resp.result); + ret = -EINVAL; + } + } + return ret; +} + + +static int qseecom_send_cmd(struct qseecom_dev_handle *data, void __user *argp) +{ + int ret = 0; + struct qseecom_send_cmd_req req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("copy_from_user failed\n"); + return ret; + } + if (qseecom.qseos_version == QSEOS_VERSION_14) + ret = __qseecom_send_cmd(data, &req); + else + ret = __qseecom_send_cmd_legacy(data, &req); + if (ret) + return ret; + + pr_debug("sending cmd_req->rsp size: %u, ptr: 0x%p\n", + req.resp_len, req.resp_buf); + return ret; +} + +static int __qseecom_send_cmd_req_clean_up( + struct qseecom_send_modfd_cmd_req *req) +{ + char *field; + uint32_t *update; + int ret = 0; + int i = 0; + + for (i = 0; i < MAX_ION_FD; i++) { + if (req->ifd_data[i].fd > 0) { + field = (char *)req->cmd_req_buf + + req->ifd_data[i].cmd_buf_offset; + update = (uint32_t *) field; + *update = 0; + } + } + return ret; +} + +static int __qseecom_update_with_phy_addr( + struct qseecom_send_modfd_cmd_req *req) +{ + struct ion_handle *ihandle; + char *field; + uint32_t *update; + ion_phys_addr_t pa; + int ret = 0; + int i = 0; + uint32_t length; + + for (i = 0; i < MAX_ION_FD; i++) { + if (req->ifd_data[i].fd > 0) { + /* Get the handle of the shared fd */ + ihandle = ion_import_fd(qseecom.ion_clnt, + req->ifd_data[i].fd); + if (IS_ERR_OR_NULL(ihandle)) { + pr_err("Ion client can't retrieve the handle\n"); + return -ENOMEM; + } + field = (char *) req->cmd_req_buf + + req->ifd_data[i].cmd_buf_offset; + update = (uint32_t *) field; + + /* Populate the cmd data structure with the phys_addr */ + ret = ion_phys(qseecom.ion_clnt, ihandle, &pa, &length); + if (ret) + return -ENOMEM; + + *update = (uint32_t)pa; + /* Deallocate the handle */ + if (!IS_ERR_OR_NULL(ihandle)) + ion_free(qseecom.ion_clnt, ihandle); + } + } + return ret; +} + +static int qseecom_send_modfd_cmd(struct qseecom_dev_handle *data, + void __user *argp) +{ + int ret = 0; + struct qseecom_send_modfd_cmd_req req; + struct qseecom_send_cmd_req send_cmd_req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("copy_from_user failed\n"); + return ret; + } + send_cmd_req.cmd_req_buf = req.cmd_req_buf; + send_cmd_req.cmd_req_len = req.cmd_req_len; + send_cmd_req.resp_buf = req.resp_buf; + send_cmd_req.resp_len = req.resp_len; + + ret = __qseecom_update_with_phy_addr(&req); + if (ret) + return ret; + if (qseecom.qseos_version == QSEOS_VERSION_14) + ret = __qseecom_send_cmd(data, &send_cmd_req); + else + ret = __qseecom_send_cmd_legacy(data, &send_cmd_req); + __qseecom_send_cmd_req_clean_up(&req); + + if (ret) + return ret; + + pr_debug("sending cmd_req->rsp size: %u, ptr: 0x%p\n", + req.resp_len, req.resp_buf); + return ret; +} + +static int __qseecom_listener_has_rcvd_req(struct qseecom_dev_handle *data, + struct qseecom_registered_listener_list *svc) +{ + int ret; + ret = (svc->rcv_req_flag != 0); + return ret || data->abort; +} + +static int qseecom_receive_req(struct qseecom_dev_handle *data) +{ + int ret = 0; + struct qseecom_registered_listener_list *this_lstnr; + + this_lstnr = __qseecom_find_svc(data->listener.id); + while (1) { + if (wait_event_freezable(this_lstnr->rcv_req_wq, + __qseecom_listener_has_rcvd_req(data, + this_lstnr))) { + pr_warning("Interrupted: exiting wait_rcv_req loop\n"); + /* woken up for different reason */ + return -ERESTARTSYS; + } + + if (data->abort) { + pr_err("Aborting driver!\n"); + return -ENODEV; + } + this_lstnr->rcv_req_flag = 0; + if (qseecom.qseos_version == QSEOS_VERSION_13) { + if (*((uint32_t *)this_lstnr->sb_virt) != 0) + break; + } else { + break; + } + } + return ret; +} + +static int qseecom_send_resp(void) +{ + qseecom.send_resp_flag = 1; + wake_up_interruptible(&qseecom.send_resp_wq); + return 0; +} + +static int qseecom_get_qseos_version(struct qseecom_dev_handle *data, + void __user *argp) +{ + struct qseecom_qseos_version_req req; + + if (copy_from_user(&req, argp, sizeof(req))) { + pr_err("copy_from_user failed"); + return -EINVAL; + } + req.qseos_version = qseecom.qseos_version; + if (copy_to_user(argp, &req, sizeof(req))) { + pr_err("copy_to_user failed"); + return -EINVAL; + } + return 0; +} + +static int qsee_vote_for_clock(int32_t clk_type) +{ + int ret = 0; + + if (!qsee_perf_client) + return ret; + + switch (clk_type) { + case CLK_DFAB: + /* Check if the clk is valid */ + if (IS_ERR_OR_NULL(qseecom_bus_clk)) { + pr_warn("qseecom bus clock is null or error"); + return -EINVAL; + } + mutex_lock(&qsee_bw_mutex); + if (!qsee_bw_count) { + ret = msm_bus_scale_client_update_request( + qsee_perf_client, 1); + if (ret) + pr_err("DFAB Bandwidth req failed (%d)\n", + ret); + else + qsee_bw_count++; + } + mutex_unlock(&qsee_bw_mutex); + break; + case CLK_SFPB: + mutex_lock(&qsee_sfpb_bw_mutex); + if (!qsee_sfpb_bw_count) { + ret = msm_bus_scale_client_update_request( + qsee_perf_client, 2); + if (ret) + pr_err("SFPB Bandwidth req failed (%d)\n", + ret); + else + qsee_sfpb_bw_count++; + } + mutex_unlock(&qsee_sfpb_bw_mutex); + break; + default: + pr_err("Clock type not defined\n"); + break; + } + return ret; +} + +static void qsee_disable_clock_vote(int32_t clk_type) +{ + int32_t ret = 0; + + if (!qsee_perf_client) + return; + + switch (clk_type) { + case CLK_DFAB: + /* Check if the DFAB clk is valid */ + if (IS_ERR_OR_NULL(qseecom_bus_clk)) { + pr_warn("qseecom bus clock is null or error"); + return; + } + mutex_lock(&qsee_bw_mutex); + if (qsee_bw_count > 0) { + if (qsee_bw_count-- == 1) { + ret = msm_bus_scale_client_update_request( + qsee_perf_client, 0); + if (ret) + pr_err("SFPB Bandwidth req fail (%d)\n", + ret); + } + } + mutex_unlock(&qsee_bw_mutex); + break; + case CLK_SFPB: + mutex_lock(&qsee_sfpb_bw_mutex); + if (qsee_sfpb_bw_count > 0) { + if (qsee_sfpb_bw_count-- == 1) { + ret = msm_bus_scale_client_update_request( + qsee_perf_client, 0); + if (ret) + pr_err("SFPB Bandwidth req fail (%d)\n", + ret); + } + } + mutex_unlock(&qsee_sfpb_bw_mutex); + break; + default: + pr_err("Clock type not defined\n"); + break; + } + +} + +static int qseecom_load_external_elf(struct qseecom_dev_handle *data, + void __user *argp) +{ + struct ion_handle *ihandle; /* Ion handle */ + struct qseecom_load_img_req load_img_req; + int ret; + int set_cpu_ret = 0; + ion_phys_addr_t pa = 0; + uint32_t len; + struct cpumask mask; + struct qseecom_load_app_ireq load_req; + struct qseecom_command_scm_resp resp; + + /* Copy the relevant information needed for loading the image */ + if (__copy_from_user(&load_img_req, + (void __user *)argp, + sizeof(struct qseecom_load_img_req))) { + pr_err("copy_from_user failed\n"); + return -EFAULT; + } + + /* Get the handle of the shared fd */ + ihandle = ion_import_fd(qseecom.ion_clnt, + load_img_req.ifd_data_fd); + if (IS_ERR_OR_NULL(ihandle)) { + pr_err("Ion client could not retrieve the handle\n"); + return -ENOMEM; + } + + /* Get the physical address of the ION BUF */ + ret = ion_phys(qseecom.ion_clnt, ihandle, &pa, &len); + + /* Populate the structure for sending scm call to load image */ + load_req.qsee_cmd_id = QSEOS_LOAD_EXTERNAL_ELF_COMMAND; + load_req.mdt_len = load_img_req.mdt_len; + load_req.img_len = load_img_req.img_len; + load_req.phy_addr = pa; + + /* SCM_CALL tied to Core0 */ + mask = CPU_MASK_CPU0; + set_cpu_ret = set_cpus_allowed_ptr(current, &mask); + if (set_cpu_ret) { + pr_err("set_cpus_allowed_ptr failed : ret %d\n", + set_cpu_ret); + ret = -EFAULT; + goto qseecom_load_external_elf_set_cpu_err; + } + + /* SCM_CALL to load the external elf */ + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &load_req, + sizeof(struct qseecom_load_app_ireq), + &resp, sizeof(resp)); + if (ret) { + pr_err("scm_call to load failed : ret %d\n", + ret); + ret = -EFAULT; + goto qseecom_load_external_elf_scm_err; + } + + if (resp.result == QSEOS_RESULT_INCOMPLETE) { + ret = __qseecom_process_incomplete_cmd(data, &resp); + if (ret) + pr_err("process_incomplete_cmd failed err: %d\n", + ret); + } else { + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err("scm_call to load image failed resp.result =%d\n", + resp.result); + ret = -EFAULT; + } + } + +qseecom_load_external_elf_scm_err: + /* Restore the CPU mask */ + mask = CPU_MASK_ALL; + set_cpu_ret = set_cpus_allowed_ptr(current, &mask); + if (set_cpu_ret) { + pr_err("set_cpus_allowed_ptr failed to restore mask: ret %d\n", + set_cpu_ret); + ret = -EFAULT; + } + +qseecom_load_external_elf_set_cpu_err: + /* Deallocate the handle */ + if (!IS_ERR_OR_NULL(ihandle)) + ion_free(qseecom.ion_clnt, ihandle); + + return ret; +} + +static int qseecom_unload_external_elf(struct qseecom_dev_handle *data) +{ + int ret = 0; + int set_cpu_ret = 0; + struct qseecom_command_scm_resp resp; + struct qseecom_unload_app_ireq req; + struct cpumask mask; + + /* Populate the structure for sending scm call to unload image */ + req.qsee_cmd_id = QSEOS_UNLOAD_EXTERNAL_ELF_COMMAND; + + /* SCM_CALL tied to Core0 */ + mask = CPU_MASK_CPU0; + ret = set_cpus_allowed_ptr(current, &mask); + if (ret) { + pr_err("set_cpus_allowed_ptr failed : ret %d\n", + ret); + return -EFAULT; + } + + /* SCM_CALL to unload the external elf */ + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, + sizeof(struct qseecom_unload_app_ireq), + &resp, sizeof(resp)); + if (ret) { + pr_err("scm_call to unload failed : ret %d\n", + ret); + ret = -EFAULT; + goto qseecom_unload_external_elf_scm_err; + } + if (resp.result == QSEOS_RESULT_INCOMPLETE) { + ret = __qseecom_process_incomplete_cmd(data, &resp); + if (ret) + pr_err("process_incomplete_cmd fail err: %d\n", + ret); + } else { + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err("scm_call to unload image failed resp.result =%d\n", + resp.result); + ret = -EFAULT; + } + } + +qseecom_unload_external_elf_scm_err: + /* Restore the CPU mask */ + mask = CPU_MASK_ALL; + set_cpu_ret = set_cpus_allowed_ptr(current, &mask); + if (set_cpu_ret) { + pr_err("set_cpus_allowed_ptr failed to restore mask: ret %d\n", + set_cpu_ret); + ret = -EFAULT; + } + + return ret; +} + +static long qseecom_ioctl(struct file *file, unsigned cmd, + unsigned long arg) +{ + int ret = 0; + struct qseecom_dev_handle *data = file->private_data; + void __user *argp = (void __user *) arg; + + if (data->abort) { + pr_err("Aborting qseecom driver\n"); + return -ENODEV; + } + + switch (cmd) { + case QSEECOM_IOCTL_REGISTER_LISTENER_REQ: { + pr_debug("ioctl register_listener_req()\n"); + atomic_inc(&data->ioctl_count); + ret = qseecom_register_listener(data, argp); + atomic_dec(&data->ioctl_count); + wake_up_all(&data->abort_wq); + if (ret) + pr_err("failed qseecom_register_listener: %d\n", ret); + break; + } + case QSEECOM_IOCTL_UNREGISTER_LISTENER_REQ: { + pr_debug("ioctl unregister_listener_req()\n"); + atomic_inc(&data->ioctl_count); + ret = qseecom_unregister_listener(data); + atomic_dec(&data->ioctl_count); + wake_up_all(&data->abort_wq); + if (ret) + pr_err("failed qseecom_unregister_listener: %d\n", ret); + break; + } + case QSEECOM_IOCTL_SEND_CMD_REQ: { + /* Only one client allowed here at a time */ + mutex_lock(&send_msg_lock); + atomic_inc(&data->ioctl_count); + ret = qseecom_send_cmd(data, argp); + atomic_dec(&data->ioctl_count); + wake_up_all(&data->abort_wq); + mutex_unlock(&send_msg_lock); + if (ret) + pr_err("failed qseecom_send_cmd: %d\n", ret); + break; + } + case QSEECOM_IOCTL_SEND_MODFD_CMD_REQ: { + /* Only one client allowed here at a time */ + mutex_lock(&send_msg_lock); + atomic_inc(&data->ioctl_count); + ret = qseecom_send_modfd_cmd(data, argp); + atomic_dec(&data->ioctl_count); + wake_up_all(&data->abort_wq); + mutex_unlock(&send_msg_lock); + if (ret) + pr_err("failed qseecom_send_cmd: %d\n", ret); + break; + } + case QSEECOM_IOCTL_RECEIVE_REQ: { + atomic_inc(&data->ioctl_count); + ret = qseecom_receive_req(data); + atomic_dec(&data->ioctl_count); + wake_up_all(&data->abort_wq); + if (ret) + pr_err("failed qseecom_receive_req: %d\n", ret); + break; + } + case QSEECOM_IOCTL_SEND_RESP_REQ: { + atomic_inc(&data->ioctl_count); + ret = qseecom_send_resp(); + atomic_dec(&data->ioctl_count); + wake_up_all(&data->abort_wq); + if (ret) + pr_err("failed qseecom_send_resp: %d\n", ret); + break; + } + case QSEECOM_IOCTL_SET_MEM_PARAM_REQ: { + ret = qseecom_set_client_mem_param(data, argp); + if (ret) + pr_err("failed Qqseecom_set_mem_param request: %d\n", + ret); + break; + } + case QSEECOM_IOCTL_LOAD_APP_REQ: { + mutex_lock(&app_access_lock); + atomic_inc(&data->ioctl_count); + ret = qseecom_load_app(data, argp); + atomic_dec(&data->ioctl_count); + mutex_unlock(&app_access_lock); + if (ret) + pr_err("failed load_app request: %d\n", ret); + break; + } + case QSEECOM_IOCTL_UNLOAD_APP_REQ: { + mutex_lock(&app_access_lock); + atomic_inc(&data->ioctl_count); + ret = qseecom_unload_app(data); + atomic_dec(&data->ioctl_count); + mutex_unlock(&app_access_lock); + if (ret) + pr_err("failed unload_app request: %d\n", ret); + break; + } + case QSEECOM_IOCTL_GET_QSEOS_VERSION_REQ: { + atomic_inc(&data->ioctl_count); + ret = qseecom_get_qseos_version(data, argp); + if (ret) + pr_err("qseecom_get_qseos_version: %d\n", ret); + atomic_dec(&data->ioctl_count); + break; + } + case QSEECOM_IOCTL_PERF_ENABLE_REQ:{ + atomic_inc(&data->ioctl_count); + ret = qsee_vote_for_clock(CLK_DFAB); + if (ret) + pr_err("Failed to vote for DFAB clock%d\n", ret); + atomic_dec(&data->ioctl_count); + break; + } + case QSEECOM_IOCTL_PERF_DISABLE_REQ:{ + atomic_inc(&data->ioctl_count); + qsee_disable_clock_vote(CLK_DFAB); + atomic_dec(&data->ioctl_count); + break; + } + case QSEECOM_IOCTL_LOAD_EXTERNAL_ELF_REQ: { + data->released = true; + if (qseecom.qseos_version == QSEOS_VERSION_13) { + pr_err("Loading External elf image unsupported in rev 0x13\n"); + ret = -EINVAL; + break; + } + mutex_lock(&app_access_lock); + atomic_inc(&data->ioctl_count); + ret = qseecom_load_external_elf(data, argp); + atomic_dec(&data->ioctl_count); + mutex_unlock(&app_access_lock); + if (ret) + pr_err("failed load_external_elf request: %d\n", ret); + break; + } + case QSEECOM_IOCTL_UNLOAD_EXTERNAL_ELF_REQ: { + data->released = true; + if (qseecom.qseos_version == QSEOS_VERSION_13) { + pr_err("Unloading External elf image unsupported in rev 0x13\n"); + ret = -EINVAL; + break; + } + mutex_lock(&app_access_lock); + atomic_inc(&data->ioctl_count); + ret = qseecom_unload_external_elf(data); + atomic_dec(&data->ioctl_count); + mutex_unlock(&app_access_lock); + if (ret) + pr_err("failed unload_app request: %d\n", ret); + break; + } + default: + return -EINVAL; + } + return ret; +} + +static int qseecom_open(struct inode *inode, struct file *file) +{ + int ret = 0; + struct qseecom_dev_handle *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + pr_err("kmalloc failed\n"); + return -ENOMEM; + } + file->private_data = data; + data->abort = 0; + data->service = false; + data->released = false; + init_waitqueue_head(&data->abort_wq); + atomic_set(&data->ioctl_count, 0); + if (qseecom.qseos_version == QSEOS_VERSION_13) { + int pil_error; + mutex_lock(&pil_access_lock); + if (pil_ref_cnt == 0) { + pil = pil_get("tzapps"); + if (IS_ERR(pil)) { + pr_err("Playready PIL image load failed\n"); + pil_error = PTR_ERR(pil); + pil = NULL; + pr_debug("tzapps image load FAILED\n"); + mutex_unlock(&pil_access_lock); + return pil_error; + } + } + pil_ref_cnt++; + mutex_unlock(&pil_access_lock); + } + return ret; +} + +static int qseecom_release(struct inode *inode, struct file *file) +{ + struct qseecom_dev_handle *data = file->private_data; + int ret = 0; + + if (data->released == false) { + pr_warn("data->released == false\n"); + if (data->service) + ret = qseecom_unregister_listener(data); + else + ret = qseecom_unload_app(data); + if (ret) { + pr_err("Close failed\n"); + return ret; + } + } + if (qseecom.qseos_version == QSEOS_VERSION_13) { + mutex_lock(&pil_access_lock); + if (pil_ref_cnt == 1) + pil_put(pil); + pil_ref_cnt--; + mutex_unlock(&pil_access_lock); + } + kfree(data); + qsee_disable_clock_vote(CLK_DFAB); + + return ret; +} + +static const struct file_operations qseecom_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qseecom_ioctl, + .open = qseecom_open, + .release = qseecom_release +}; + +static int __devinit qseecom_probe(struct platform_device *pdev) +{ + int rc; + struct device *class_dev; + char qsee_not_legacy = 0; + struct msm_bus_scale_pdata *qseecom_platform_support; + uint32_t system_call_id = QSEOS_CHECK_VERSION_CMD; + + qsee_bw_count = 0; + qseecom_bus_clk = NULL; + qsee_perf_client = 0; + + rc = alloc_chrdev_region(&qseecom_device_no, 0, 1, QSEECOM_DEV); + if (rc < 0) { + pr_err("alloc_chrdev_region failed %d\n", rc); + return rc; + } + + driver_class = class_create(THIS_MODULE, QSEECOM_DEV); + if (IS_ERR(driver_class)) { + rc = -ENOMEM; + pr_err("class_create failed %d\n", rc); + goto unregister_chrdev_region; + } + + class_dev = device_create(driver_class, NULL, qseecom_device_no, NULL, + QSEECOM_DEV); + if (!class_dev) { + pr_err("class_device_create failed %d\n", rc); + rc = -ENOMEM; + goto class_destroy; + } + + cdev_init(&qseecom_cdev, &qseecom_fops); + qseecom_cdev.owner = THIS_MODULE; + + rc = cdev_add(&qseecom_cdev, MKDEV(MAJOR(qseecom_device_no), 0), 1); + if (rc < 0) { + pr_err("cdev_add failed %d\n", rc); + goto err; + } + + INIT_LIST_HEAD(&qseecom.registered_listener_list_head); + spin_lock_init(&qseecom.registered_listener_list_lock); + INIT_LIST_HEAD(&qseecom.registered_app_list_head); + spin_lock_init(&qseecom.registered_app_list_lock); + init_waitqueue_head(&qseecom.send_resp_wq); + qseecom.send_resp_flag = 0; + + rc = scm_call(6, 1, &system_call_id, sizeof(system_call_id), + &qsee_not_legacy, sizeof(qsee_not_legacy)); + if (rc) { + pr_err("Failed to retrieve QSEE version information %d\n", rc); + goto err; + } + if (qsee_not_legacy) + qseecom.qseos_version = QSEOS_VERSION_14; + else { + qseecom.qseos_version = QSEOS_VERSION_13; + pil = NULL; + pil_ref_cnt = 0; + } + /* Create ION msm client */ + qseecom.ion_clnt = msm_ion_client_create(0x03, "qseecom-kernel"); + if (qseecom.ion_clnt == NULL) { + pr_err("Ion client cannot be created\n"); + rc = -ENOMEM; + goto err; + } + + /* register client for bus scaling */ + qseecom_platform_support = (struct msm_bus_scale_pdata *) + pdev->dev.platform_data; + qsee_perf_client = msm_bus_scale_register_client( + qseecom_platform_support); + if (!qsee_perf_client) { + pr_err("Unable to register bus client\n"); + } else { + qseecom_bus_clk = clk_get(class_dev, "bus_clk"); + if (IS_ERR(qseecom_bus_clk)) { + qseecom_bus_clk = NULL; + } else if (qseecom_bus_clk != NULL) { + pr_debug("Enabled DFAB clock"); + clk_set_rate(qseecom_bus_clk, 64000000); + } + } + return 0; + +err: + device_destroy(driver_class, qseecom_device_no); +class_destroy: + class_destroy(driver_class); +unregister_chrdev_region: + unregister_chrdev_region(qseecom_device_no, 1); + return rc; +} + +static int __devinit qseecom_remove(struct platform_device *pdev) +{ + if (pdev->dev.platform_data != NULL) + msm_bus_scale_unregister_client(qsee_perf_client); + return 0; +}; + +static struct platform_driver qseecom_plat_driver = { + .probe = qseecom_probe, + .remove = qseecom_remove, + .driver = { + .name = "qseecom", + .owner = THIS_MODULE, + }, +}; + +static int __devinit qseecom_init(void) +{ + return platform_driver_register(&qseecom_plat_driver); +} + +static void __devexit qseecom_exit(void) +{ + clk_put(qseecom_bus_clk); + + device_destroy(driver_class, qseecom_device_no); + class_destroy(driver_class); + unregister_chrdev_region(qseecom_device_no, 1); + ion_client_destroy(qseecom.ion_clnt); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Secure Execution Environment Communicator"); + +module_init(qseecom_init); +module_exit(qseecom_exit); diff --git a/drivers/misc/qseecom_legacy.h b/drivers/misc/qseecom_legacy.h new file mode 100644 index 0000000000000000000000000000000000000000..66f87e963ae754482594a077cc22c8dc5ca34b80 --- /dev/null +++ b/drivers/misc/qseecom_legacy.h @@ -0,0 +1,79 @@ +/* Qualcomm Secure Execution Environment Communicator (QSEECOM) driver + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __QSEECOM_LEGACY_H_ +#define __QSEECOM_LEGACY_H_ + +#include + +#define TZ_SCHED_CMD_ID_REGISTER_LISTENER 0x04 + +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 */ +__packed struct qse_pr_init_sb_req_s { + /* First 4 bytes should always be command id */ + uint32_t pr_cmd; + /* Pointer to the physical location of sb buffer */ + uint32_t sb_ptr; + /* length of shared buffer */ + uint32_t sb_len; + uint32_t listener_id; +}; + +__packed struct qse_pr_init_sb_rsp_s { + /* First 4 bytes should always be command id */ + uint32_t pr_cmd; + /* Return code, 0 for success, Approp error code otherwise */ + int32_t ret; +}; + +/* + * struct QSEECom_command - QSECom 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 qseecom_command { + uint32_t cmd_type; + uint8_t *sb_in_cmd_addr; + uint32_t sb_in_cmd_len; +}; + +/* + * struct QSEECom_response - QSECom 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 qseecom_response { + uint32_t cmd_status; + uint8_t *sb_in_rsp_addr; + uint32_t sb_in_rsp_len; +}; + +#endif /* __QSEECOM_LEGACY_H_ */ diff --git a/drivers/misc/smsc_hub.c b/drivers/misc/smsc_hub.c new file mode 100644 index 0000000000000000000000000000000000000000..bde25d98e3aeef12115e9d1dc86be9e0536672a2 --- /dev/null +++ b/drivers/misc/smsc_hub.c @@ -0,0 +1,372 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 SMSC3503_I2C_ADDR 0x08 +#define SMSC_GSBI_I2C_BUS_ID 10 +static const unsigned short normal_i2c[] = { +SMSC3503_I2C_ADDR, I2C_CLIENT_END }; + +struct hsic_hub { + struct regulator *hsic_hub_reg; + struct device *dev; + struct i2c_client *client; + struct msm_xo_voter *xo_handle; +}; +static struct hsic_hub *smsc_hub; + +/* APIs for setting/clearing bits and for reading/writing values */ +static inline int hsic_hub_get_u8(struct i2c_client *client, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + pr_err("%s:i2c_read8 failed\n", __func__); + return ret; +} + +static inline int hsic_hub_get_u16(struct i2c_client *client, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + pr_err("%s:i2c_read16 failed\n", __func__); + return ret; +} + +static inline int hsic_hub_write_word_data(struct i2c_client *client, u8 reg, + u16 value) +{ + int ret; + + ret = i2c_smbus_write_word_data(client, reg, value); + if (ret) + pr_err("%s:i2c_write16 failed\n", __func__); + return ret; +} + +static inline int hsic_hub_write_byte_data(struct i2c_client *client, u8 reg, + u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + if (ret) + pr_err("%s:i2c_write_byte_data failed\n", __func__); + return ret; +} + +static inline int hsic_hub_set_bits(struct i2c_client *client, u8 reg, + u8 value) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + pr_err("%s:i2c_read_byte_data failed\n", __func__); + return ret; + } + return i2c_smbus_write_byte_data(client, reg, (ret | value)); +} + +static inline int hsic_hub_clear_bits(struct i2c_client *client, u8 reg, + u8 value) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + pr_err("%s:i2c_read_byte_data failed\n", __func__); + return ret; + } + return i2c_smbus_write_byte_data(client, reg, (ret & ~value)); +} + +static int i2c_hsic_hub_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + /* CONFIG_N bit in SP_ILOCK register has to be set before changing + * other registers to change default configuration of hsic hub. + */ + hsic_hub_set_bits(client, SMSC3503_SP_ILOCK, CONFIG_N); + + /* Can change default configuartion like VID,PID, strings etc + * by writing new values to hsic hub registers. + */ + hsic_hub_write_word_data(client, SMSC3503_VENDORID, 0x05C6); + + /* CONFIG_N bit in SP_ILOCK register has to be cleared for new + * values in registers to be effective after writing to + * other registers. + */ + hsic_hub_clear_bits(client, SMSC3503_SP_ILOCK, CONFIG_N); + + return 0; +} + +static int i2c_hsic_hub_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id hsic_hub_id[] = { + {"i2c_hsic_hub", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, hsichub_id); + +static struct i2c_driver hsic_hub_driver = { + .driver = { + .name = "i2c_hsic_hub", + }, + .probe = i2c_hsic_hub_probe, + .remove = i2c_hsic_hub_remove, + .id_table = hsic_hub_id, +}; + +#define HSIC_HUB_VDD_VOL_MIN 1650000 /* uV */ +#define HSIC_HUB_VDD_VOL_MAX 1950000 /* uV */ +#define HSIC_HUB_VDD_LOAD 36000 /* uA */ +static int __devinit smsc_hub_probe(struct platform_device *pdev) +{ + int ret = 0; + const struct smsc_hub_platform_data *pdata; + struct i2c_adapter *i2c_adap; + struct i2c_board_info i2c_info; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data\n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + if (!pdata->hub_reset) + return -EINVAL; + + smsc_hub = kzalloc(sizeof(*smsc_hub), GFP_KERNEL); + if (!smsc_hub) + return -ENOMEM; + + smsc_hub->hsic_hub_reg = regulator_get(&pdev->dev, "EXT_HUB_VDDIO"); + if (IS_ERR(smsc_hub->hsic_hub_reg)) { + dev_err(&pdev->dev, "unable to get ext hub vddcx\n"); + ret = PTR_ERR(smsc_hub->hsic_hub_reg); + goto free_mem; + } + + ret = gpio_request(pdata->hub_reset, "HSIC_HUB_RESET_GPIO"); + if (ret < 0) { + dev_err(&pdev->dev, "gpio request failed for GPIO%d\n", + pdata->hub_reset); + goto gpio_req_fail; + } + + ret = regulator_set_voltage(smsc_hub->hsic_hub_reg, + HSIC_HUB_VDD_VOL_MIN, + HSIC_HUB_VDD_VOL_MAX); + if (ret) { + dev_err(&pdev->dev, "unable to set the voltage" + "for hsic hub reg\n"); + goto reg_set_voltage_fail; + } + + ret = regulator_set_optimum_mode(smsc_hub->hsic_hub_reg, + HSIC_HUB_VDD_LOAD); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to set optimum mode of regulator:" + "VDDCX\n"); + goto reg_optimum_mode_fail; + } + + ret = regulator_enable(smsc_hub->hsic_hub_reg); + if (ret) { + dev_err(&pdev->dev, "unable to enable ext hub vddcx\n"); + goto reg_enable_fail; + } + + smsc_hub->xo_handle = msm_xo_get(MSM_XO_TCXO_D1, "hsic_hub"); + if (IS_ERR(smsc_hub->xo_handle)) { + dev_err(&pdev->dev, "not able to get the handle" + "for TCXO D1 buffer\n"); + goto disable_regulator; + } + + ret = msm_xo_mode_vote(smsc_hub->xo_handle, MSM_XO_MODE_ON); + if (ret) { + dev_err(&pdev->dev, "failed to vote for TCXO" + "D1 buffer\n"); + goto xo_vote_fail; + } + + gpio_direction_output(pdata->hub_reset, 0); + /* Hub reset should be asserted for minimum 2microsec + * before deasserting. + */ + udelay(5); + gpio_direction_output(pdata->hub_reset, 1); + + ret = i2c_add_driver(&hsic_hub_driver); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add I2C hsic_hub_driver\n"); + goto i2c_add_fail; + } + usleep_range(10000, 12000); + i2c_adap = i2c_get_adapter(SMSC_GSBI_I2C_BUS_ID); + + if (!i2c_adap) { + dev_err(&pdev->dev, "failed to get i2c adapter\n"); + i2c_del_driver(&hsic_hub_driver); + goto i2c_add_fail; + } + + memset(&i2c_info, 0, sizeof(struct i2c_board_info)); + strlcpy(i2c_info.type, "i2c_hsic_hub", I2C_NAME_SIZE); + + smsc_hub->client = i2c_new_probed_device(i2c_adap, &i2c_info, + normal_i2c, NULL); + i2c_put_adapter(i2c_adap); + if (!smsc_hub->client) + dev_err(&pdev->dev, "failed to connect to smsc_hub" + "through I2C\n"); + +i2c_add_fail: + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +xo_vote_fail: + msm_xo_put(smsc_hub->xo_handle); +disable_regulator: + regulator_disable(smsc_hub->hsic_hub_reg); +reg_enable_fail: + regulator_set_optimum_mode(smsc_hub->hsic_hub_reg, 0); +reg_optimum_mode_fail: + regulator_set_voltage(smsc_hub->hsic_hub_reg, 0, + HSIC_HUB_VDD_VOL_MIN); +reg_set_voltage_fail: + gpio_free(pdata->hub_reset); +gpio_req_fail: + regulator_put(smsc_hub->hsic_hub_reg); +free_mem: + kfree(smsc_hub); + + return ret; +} + +static int smsc_hub_remove(struct platform_device *pdev) +{ + const struct smsc_hub_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (smsc_hub->client) { + i2c_unregister_device(smsc_hub->client); + smsc_hub->client = NULL; + i2c_del_driver(&hsic_hub_driver); + } + pm_runtime_disable(&pdev->dev); + msm_xo_put(smsc_hub->xo_handle); + + regulator_disable(smsc_hub->hsic_hub_reg); + regulator_set_optimum_mode(smsc_hub->hsic_hub_reg, 0); + regulator_set_voltage(smsc_hub->hsic_hub_reg, 0, + HSIC_HUB_VDD_VOL_MIN); + gpio_free(pdata->hub_reset); + regulator_put(smsc_hub->hsic_hub_reg); + kfree(smsc_hub); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int msm_smsc_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "SMSC HUB runtime idle\n"); + + return 0; +} + +static int smsc_hub_lpm_enter(struct device *dev) +{ + int ret; + + ret = msm_xo_mode_vote(smsc_hub->xo_handle, MSM_XO_MODE_OFF); + if (ret) { + pr_err("%s: failed to devote for TCXO" + "D1 buffer%d\n", __func__, ret); + } + return ret; +} + +static int smsc_hub_lpm_exit(struct device *dev) +{ + int ret; + + ret = msm_xo_mode_vote(smsc_hub->xo_handle, MSM_XO_MODE_ON); + if (ret) { + pr_err("%s: failed to vote for TCXO" + "D1 buffer%d\n", __func__, ret); + } + return ret; +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops smsc_hub_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(smsc_hub_lpm_enter, smsc_hub_lpm_exit) + SET_RUNTIME_PM_OPS(smsc_hub_lpm_enter, smsc_hub_lpm_exit, + msm_smsc_runtime_idle) +}; +#endif + +static struct platform_driver smsc_hub_driver = { + .driver = { + .name = "msm_smsc_hub", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &smsc_hub_dev_pm_ops, +#endif + }, + .remove = smsc_hub_remove, +}; + +static int __init smsc_hub_init(void) +{ + return platform_driver_probe(&smsc_hub_driver, smsc_hub_probe); +} + +static void __exit smsc_hub_exit(void) +{ + platform_driver_unregister(&smsc_hub_driver); +} +subsys_initcall(smsc_hub_init); +module_exit(smsc_hub_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SMSC HSIC HUB driver"); diff --git a/drivers/misc/tsif.c b/drivers/misc/tsif.c new file mode 100644 index 0000000000000000000000000000000000000000..2b09d7ca2191955b28704aef53f91018ecb58b04 --- /dev/null +++ b/drivers/misc/tsif.c @@ -0,0 +1,1580 @@ +/* + * TSIF Driver + * + * Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 TSIF_MAX_ID 1 + +#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(&tsif_device->pdev->dev, + 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(&tsif_device->pdev->dev, + 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(&tsif_device->pdev->dev, + 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, 1); + 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, 1); + 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; + } + + if ((pdev->id < 0) || (pdev->id > TSIF_MAX_ID)) { + 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 */ + +int tsif_get_active(void) +{ + struct msm_tsif_device *tsif_device; + list_for_each_entry(tsif_device, &tsif_devices, devlist) { + return tsif_device->pdev->id; + } + return -ENODEV; +} +EXPORT_SYMBOL(tsif_get_active); + +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) + return ERR_PTR(-ENODEV); + 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 0000000000000000000000000000000000000000..122c7edf3efc4e1938a02598e2d8eec9ec316237 --- /dev/null +++ b/drivers/misc/tsif_chrdev.c @@ -0,0 +1,230 @@ +/** + * TSIF driver client + * + * Character device that, being read + * returns stream of TSIF packets. + * + * 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 +#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; + int instance; + 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; + } + instance = tsif_get_active(); + if (instance >= 0) + rc = tsif_init_one(&the_devices[0], instance); + else + rc = instance; + 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/tspp.c b/drivers/misc/tspp.c new file mode 100644 index 0000000000000000000000000000000000000000..81c6b650c1745d834d97e8ee9524b46e3b25f988 --- /dev/null +++ b/drivers/misc/tspp.c @@ -0,0 +1,2019 @@ +/* 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 /* Needed by all modules */ +#include /* Needed for KERN_INFO */ +#include /* Needed for the macros */ +#include +#include /* IS_ERR */ +#include +#include +#include /* TASK_INTERRUPTIBLE */ +#include /* copy_to_user */ +#include +#include +#include /* kfree, kzalloc */ +#include +#include /* ioXXX */ +#include /* XXX_ mem_region */ +#include /* dma_XXX */ +#include /* msleep */ +#include +#include /* poll() file op */ +#include /* wait() macros, sleeping */ +#include /* tspp functions */ +#include /* BIT() macro */ +#include /* BAM stuff */ +#include +#include +#include + +#define TSPP_USE_DEBUGFS +#ifdef TSPP_USE_DEBUGFS +#include +#endif /* TSPP_USE_DEBUGFS */ + +/* + * General defines + */ +#define TSPP_USE_DMA_ALLOC_COHERENT +#define TSPP_TSIF_INSTANCES 2 +#define TSPP_FILTER_TABLES 3 +#define TSPP_MAX_DEVICES 3 +#define TSPP_NUM_CHANNELS 16 +#define TSPP_NUM_PRIORITIES 16 +#define TSPP_NUM_KEYS 8 +#define INVALID_CHANNEL 0xFFFFFFFF +#define TSPP_SPS_DESCRIPTOR_COUNT 32 +#define TSPP_PACKET_LENGTH 188 +#define TSPP_MIN_BUFFER_SIZE (TSPP_PACKET_LENGTH) +#define TSPP_MAX_BUFFER_SIZE (16 * 1024) /* maybe allow 64K? */ +#define TSPP_NUM_BUFFERS 16 +#define TSPP_TSIF_DEFAULT_TIME_LIMIT 60 +#define SPS_DESCRIPTOR_SIZE 8 +#define MIN_ACCEPTABLE_BUFFER_COUNT 2 +#define TSPP_DEBUG(msg...) pr_info(msg) + +/* + * 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 BIT(28) +#define TSIF_STS_CTL_PACK_AVAIL BIT(27) +#define TSIF_STS_CTL_1ST_PACKET BIT(26) +#define TSIF_STS_CTL_OVERFLOW BIT(25) +#define TSIF_STS_CTL_LOST_SYNC BIT(24) +#define TSIF_STS_CTL_TIMEOUT BIT(23) +#define TSIF_STS_CTL_INV_SYNC BIT(21) +#define TSIF_STS_CTL_INV_NULL BIT(20) +#define TSIF_STS_CTL_INV_ERROR BIT(19) +#define TSIF_STS_CTL_INV_ENABLE BIT(18) +#define TSIF_STS_CTL_INV_DATA BIT(17) +#define TSIF_STS_CTL_INV_CLOCK BIT(16) +#define TSIF_STS_CTL_SPARE BIT(15) +#define TSIF_STS_CTL_EN_NULL BIT(11) +#define TSIF_STS_CTL_EN_ERROR BIT(10) +#define TSIF_STS_CTL_LAST_BIT BIT(9) +#define TSIF_STS_CTL_EN_TIME_LIM BIT(8) +#define TSIF_STS_CTL_EN_TCR BIT(7) +#define TSIF_STS_CTL_TEST_MODE BIT(6) +#define TSIF_STS_CTL_EN_DM BIT(4) +#define TSIF_STS_CTL_STOP BIT(3) +#define TSIF_STS_CTL_START BIT(0) + +/* + * TSPP register offsets + */ +#define TSPP_RST 0x00 +#define TSPP_CLK_CONTROL 0x04 +#define TSPP_CONFIG 0x08 +#define TSPP_CONTROL 0x0C +#define TSPP_PS_DISABLE 0x10 +#define TSPP_MSG_IRQ_STATUS 0x14 +#define TSPP_MSG_IRQ_MASK 0x18 +#define TSPP_IRQ_STATUS 0x1C +#define TSPP_IRQ_MASK 0x20 +#define TSPP_IRQ_CLEAR 0x24 +#define TSPP_PIPE_ERROR_STATUS(_n) (0x28 + (_n << 2)) +#define TSPP_STATUS 0x68 +#define TSPP_CURR_TSP_HEADER 0x6C +#define TSPP_CURR_PID_FILTER 0x70 +#define TSPP_SYSTEM_KEY(_n) (0x74 + (_n << 2)) +#define TSPP_CBC_INIT_VAL(_n) (0x94 + (_n << 2)) +#define TSPP_DATA_KEY_RESET 0x9C +#define TSPP_KEY_VALID 0xA0 +#define TSPP_KEY_ERROR 0xA4 +#define TSPP_TEST_CTRL 0xA8 +#define TSPP_VERSION 0xAC +#define TSPP_GENERICS 0xB0 +#define TSPP_NOP 0xB4 + +/* + * Register bit definitions + */ +/* TSPP_RST */ +#define TSPP_RST_RESET BIT(0) + +/* TSPP_CLK_CONTROL */ +#define TSPP_CLK_CONTROL_FORCE_CRYPTO BIT(9) +#define TSPP_CLK_CONTROL_FORCE_PES_PL BIT(8) +#define TSPP_CLK_CONTROL_FORCE_PES_AF BIT(7) +#define TSPP_CLK_CONTROL_FORCE_RAW_CTRL BIT(6) +#define TSPP_CLK_CONTROL_FORCE_PERF_CNT BIT(5) +#define TSPP_CLK_CONTROL_FORCE_CTX_SEARCH BIT(4) +#define TSPP_CLK_CONTROL_FORCE_TSP_PROC BIT(3) +#define TSPP_CLK_CONTROL_FORCE_CONS_AHB2MEM BIT(2) +#define TSPP_CLK_CONTROL_FORCE_TS_AHB2MEM BIT(1) +#define TSPP_CLK_CONTROL_SET_CLKON BIT(0) + +/* TSPP_CONFIG */ +#define TSPP_CONFIG_SET_PACKET_LENGTH(_a, _b) (_a = (_a & 0xF0) | \ +((_b & 0xF) << 8)) +#define TSPP_CONFIG_GET_PACKET_LENGTH(_a) ((_a >> 8) & 0xF) +#define TSPP_CONFIG_DUP_WITH_DISC_EN BIT(7) +#define TSPP_CONFIG_PES_SYNC_ERROR_MASK BIT(6) +#define TSPP_CONFIG_PS_LEN_ERR_MASK BIT(5) +#define TSPP_CONFIG_PS_CONT_ERR_UNSP_MASK BIT(4) +#define TSPP_CONFIG_PS_CONT_ERR_MASK BIT(3) +#define TSPP_CONFIG_PS_DUP_TSP_MASK BIT(2) +#define TSPP_CONFIG_TSP_ERR_IND_MASK BIT(1) +#define TSPP_CONFIG_TSP_SYNC_ERR_MASK BIT(0) + +/* TSPP_CONTROL */ +#define TSPP_CONTROL_PID_FILTER_LOCK BIT(5) +#define TSPP_CONTROL_FORCE_KEY_CALC BIT(4) +#define TSPP_CONTROL_TSP_CONS_SRC_DIS BIT(3) +#define TSPP_CONTROL_TSP_TSIF1_SRC_DIS BIT(2) +#define TSPP_CONTROL_TSP_TSIF0_SRC_DIS BIT(1) +#define TSPP_CONTROL_PERF_COUNT_INIT BIT(0) + +/* TSPP_MSG_IRQ_STATUS + TSPP_MSG_IRQ_MASK */ +#define TSPP_MSG_TSPP_IRQ BIT(2) +#define TSPP_MSG_TSIF_1_IRQ BIT(1) +#define TSPP_MSG_TSIF_0_IRQ BIT(0) + +/* TSPP_IRQ_STATUS + TSPP_IRQ_MASK + TSPP_IRQ_CLEAR */ +#define TSPP_IRQ_STATUS_TSP_RD_CMPL BIT(19) +#define TSPP_IRQ_STATUS_KEY_ERROR BIT(18) +#define TSPP_IRQ_STATUS_KEY_SWITCHED_BAD BIT(17) +#define TSPP_IRQ_STATUS_KEY_SWITCHED BIT(16) +#define TSPP_IRQ_STATUS_PS_BROKEN(_n) BIT((_n)) + +/* TSPP_PIPE_ERROR_STATUS */ +#define TSPP_PIPE_PES_SYNC_ERROR BIT(3) +#define TSPP_PIPE_PS_LENGTH_ERROR BIT(2) +#define TSPP_PIPE_PS_CONTINUITY_ERROR BIT(1) +#define TSPP_PIP_PS_LOST_START BIT(0) + +/* TSPP_STATUS */ +#define TSPP_STATUS_TSP_PKT_AVAIL BIT(10) +#define TSPP_STATUS_TSIF1_DM_REQ BIT(6) +#define TSPP_STATUS_TSIF0_DM_REQ BIT(2) +#define TSPP_CURR_FILTER_TABLE BIT(0) + +/* TSPP_GENERICS */ +#define TSPP_GENERICS_CRYPTO_GEN BIT(12) +#define TSPP_GENERICS_MAX_CONS_PIPES BIT(7) +#define TSPP_GENERICS_MAX_PIPES BIT(2) +#define TSPP_GENERICS_TSIF_1_GEN BIT(1) +#define TSPP_GENERICS_TSIF_0_GEN BIT(0) + +/* + * TSPP memory regions + */ +#define TSPP_PID_FILTER_TABLE0 0x800 +#define TSPP_PID_FILTER_TABLE1 0x880 +#define TSPP_PID_FILTER_TABLE2 0x900 +#define TSPP_GLOBAL_PERFORMANCE 0x980 /* see tspp_global_performance */ +#define TSPP_PIPE_CONTEXT 0x990 /* see tspp_pipe_context */ +#define TSPP_PIPE_PERFORMANCE 0x998 /* see tspp_pipe_performance */ +#define TSPP_TSP_BUFF_WORD(_n) (0xC10 + (_n << 2)) +#define TSPP_DATA_KEY 0xCD0 + +#ifdef TSPP_USE_DEBUGFS +struct debugfs_entry { + const char *name; + mode_t mode; + int offset; +}; + +static const struct debugfs_entry 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}, +}; + +static const struct debugfs_entry debugfs_tspp_regs[] = { + {"rst", S_IRUGO | S_IWUSR, TSPP_RST}, + {"clk_control", S_IRUGO | S_IWUSR, TSPP_CLK_CONTROL}, + {"config", S_IRUGO | S_IWUSR, TSPP_CONFIG}, + {"control", S_IRUGO | S_IWUSR, TSPP_CONTROL}, + {"ps_disable", S_IRUGO | S_IWUSR, TSPP_PS_DISABLE}, + {"msg_irq_status", S_IRUGO | S_IWUSR, TSPP_MSG_IRQ_STATUS}, + {"msg_irq_mask", S_IRUGO | S_IWUSR, TSPP_MSG_IRQ_MASK}, + {"irq_status", S_IRUGO | S_IWUSR, TSPP_IRQ_STATUS}, + {"irq_mask", S_IRUGO | S_IWUSR, TSPP_IRQ_MASK}, + {"irq_clear", S_IRUGO | S_IWUSR, TSPP_IRQ_CLEAR}, + /* {"pipe_error_status",S_IRUGO | S_IWUSR, TSPP_PIPE_ERROR_STATUS}, */ + {"status", S_IRUGO | S_IWUSR, TSPP_STATUS}, + {"curr_tsp_header", S_IRUGO | S_IWUSR, TSPP_CURR_TSP_HEADER}, + {"curr_pid_filter", S_IRUGO | S_IWUSR, TSPP_CURR_PID_FILTER}, + /* {"system_key", S_IRUGO | S_IWUSR, TSPP_SYSTEM_KEY}, */ + /* {"cbc_init_val", S_IRUGO | S_IWUSR, TSPP_CBC_INIT_VAL}, */ + {"data_key_reset", S_IRUGO | S_IWUSR, TSPP_DATA_KEY_RESET}, + {"key_valid", S_IRUGO | S_IWUSR, TSPP_KEY_VALID}, + {"key_error", S_IRUGO | S_IWUSR, TSPP_KEY_ERROR}, + {"test_ctrl", S_IRUGO | S_IWUSR, TSPP_TEST_CTRL}, + {"version", S_IRUGO | S_IWUSR, TSPP_VERSION}, + {"generics", S_IRUGO | S_IWUSR, TSPP_GENERICS}, + {"pid_filter_table0", S_IRUGO | S_IWUSR, TSPP_PID_FILTER_TABLE0}, + {"pid_filter_table1", S_IRUGO | S_IWUSR, TSPP_PID_FILTER_TABLE1}, + {"pid_filter_table2", S_IRUGO | S_IWUSR, TSPP_PID_FILTER_TABLE2}, + {"global_performance", S_IRUGO | S_IWUSR, TSPP_GLOBAL_PERFORMANCE}, + {"pipe_context", S_IRUGO | S_IWUSR, TSPP_PIPE_CONTEXT}, + {"pipe_performance", S_IRUGO | S_IWUSR, TSPP_PIPE_PERFORMANCE}, + {"data_key", S_IRUGO | S_IWUSR, TSPP_DATA_KEY} +}; + +#endif /* TSPP_USE_DEBUGFS */ + +struct tspp_pid_filter { + u32 filter; /* see FILTER_ macros */ + u32 config; /* see FILTER_ macros */ +}; + +/* tsp_info */ +#define FILTER_HEADER_ERROR_MASK BIT(7) +#define FILTER_TRANS_END_DISABLE BIT(6) +#define FILTER_DEC_ON_ERROR_EN BIT(5) +#define FILTER_DECRYPT BIT(4) +#define FILTER_HAS_ENCRYPTION(_p) (_p->config & FILTER_DECRYPT) +#define FILTER_GET_PIPE_NUMBER0(_p) (_p->config & 0xF) +#define FILTER_SET_PIPE_NUMBER0(_p, _b) (_p->config = \ + (_p->config & ~0xF) | (_b & 0xF)) +#define FILTER_GET_PIPE_PROCESS0(_p) ((_p->filter >> 30) & 0x3) +#define FILTER_SET_PIPE_PROCESS0(_p, _b) (_p->filter = \ + (_p->filter & ~(0x3<<30)) | ((_b & 0x3) << 30)) +#define FILTER_GET_PIPE_PID(_p) ((_p->filter >> 13) & 0x1FFF) +#define FILTER_SET_PIPE_PID(_p, _b) (_p->filter = \ + (_p->filter & ~(0x1FFF<<13)) | ((_b & 0x1FFF) << 13)) +#define FILTER_GET_PID_MASK(_p) (_p->filter & 0x1FFF) +#define FILTER_SET_PID_MASK(_p, _b) (_p->filter = \ + (_p->filter & ~0x1FFF) | (_b & 0x1FFF)) +#define FILTER_GET_PIPE_PROCESS1(_p) ((_p->config >> 30) & 0x3) +#define FILTER_SET_PIPE_PROCESS1(_p, _b) (_p->config = \ + (_p->config & ~(0x3<<30)) | ((_b & 0x3) << 30)) +#define FILTER_GET_KEY_NUMBER(_p) ((_p->config >> 8) & 0x7) +#define FILTER_SET_KEY_NUMBER(_p, _b) (_p->config = \ + (_p->config & ~(0x7<<8)) | ((_b & 0x7) << 8)) + +struct tspp_global_performance_regs { + u32 tsp_total; + u32 tsp_ignored; + u32 tsp_error; + u32 tsp_sync; +}; + +struct tspp_pipe_context_regs { + u16 pes_bytes_left; + u16 count; + u32 tsif_suffix; +} __packed; +#define CONTEXT_GET_STATE(_a) (_a & 0x3) +#define CONTEXT_UNSPEC_LENGTH BIT(11) +#define CONTEXT_GET_CONT_COUNT(_a) ((_a >> 12) & 0xF) + +struct tspp_pipe_performance_regs { + u32 tsp_total; + u32 ps_duplicate_tsp; + u32 tsp_no_payload; + u32 tsp_broken_ps; + u32 ps_total_num; + u32 ps_continuity_error; + u32 ps_length_error; + u32 pes_sync_error; +}; + +struct tspp_tsif_device { + void __iomem *base; + u32 time_limit; + u32 ref_count; + + /* debugfs */ +#ifdef TSPP_USE_DEBUGFS + struct dentry *dent_tsif; + struct dentry *debugfs_tsif_regs[ARRAY_SIZE(debugfs_tsif_regs)]; +#endif /* TSPP_USE_DEBUGFS */ +}; + +/* this represents the actual hardware device */ +struct tspp_device { + struct platform_device *pdev; + void __iomem *base; + unsigned int tspp_irq; + unsigned int bam_irq; + u32 bam_handle; + struct sps_bam_props bam_props; + struct wake_lock wake_lock; + spinlock_t spinlock; + struct tasklet_struct tlet; + struct tspp_tsif_device tsif[TSPP_TSIF_INSTANCES]; + /* clocks */ + struct clk *tsif_pclk; + struct clk *tsif_ref_clk; + +#ifdef TSPP_USE_DEBUGFS + struct dentry *dent; + struct dentry *debugfs_regs[ARRAY_SIZE(debugfs_tspp_regs)]; +#endif /* TSPP_USE_DEBUGFS */ +}; + +enum tspp_buf_state { + TSPP_BUF_STATE_EMPTY, /* buffer has been allocated, but not waiting */ + TSPP_BUF_STATE_WAITING, /* buffer is waiting to be filled */ + TSPP_BUF_STATE_DATA /* buffer is not empty and can be read */ +}; + +struct tspp_mem_buffer { + struct sps_mem_buffer mem; + enum tspp_buf_state state; + size_t filled; /* how much data this buffer is holding */ + int read_index; /* where to start reading data from */ +}; + +/* this represents each char device 'channel' */ +struct tspp_channel { + struct cdev cdev; + struct device *dd; + struct tspp_device *pdev; + struct sps_pipe *pipe; + struct sps_connect config; + struct sps_register_event event; + struct tspp_mem_buffer buffer[TSPP_NUM_BUFFERS]; + wait_queue_head_t in_queue; /* set when data is received */ + int read; /* index into mem showing buffers ready to be read by user */ + int waiting; /* index into mem showing outstanding transfers */ + int id; /* channel id (0-15) */ + int used; /* is this channel in use? */ + int key; /* which encryption key index is used */ + u32 bufsize; /* size of the sps transfer buffers */ + int buffer_count; /* how many buffers are actually allocated */ + int filter_count; /* how many filters have been added to this channel */ + enum tspp_source src; + enum tspp_mode mode; +}; + +struct tspp_pid_filter_table { + struct tspp_pid_filter filter[TSPP_NUM_PRIORITIES]; +}; + +struct tspp_key_entry { + u32 even_lsb; + u32 even_msb; + u32 odd_lsb; + u32 odd_msb; +}; + +struct tspp_key_table { + struct tspp_key_entry entry[TSPP_NUM_KEYS]; +}; + +static struct tspp_pid_filter_table *tspp_filter_table[TSPP_FILTER_TABLES]; +static struct tspp_channel channel_list[TSPP_NUM_CHANNELS]; +static struct tspp_key_table *tspp_key_table; +static struct tspp_global_performance_regs *tspp_global_performance; +static struct tspp_pipe_context_regs *tspp_pipe_context; +static struct tspp_pipe_performance_regs *tspp_pipe_performance; +static struct class *tspp_class; +static int tspp_key_entry; +static dev_t tspp_minor; /* next minor number to assign */ +static int loopback_mode; /* put tsif interfaces into loopback mode */ + +/*** IRQ ***/ +static irqreturn_t tspp_isr(int irq, void *dev_id) +{ + struct tspp_device *device = dev_id; + u32 status, mask; + u32 data; + + status = readl_relaxed(device->base + TSPP_IRQ_STATUS); + mask = readl_relaxed(device->base + TSPP_IRQ_MASK); + status &= mask; + + if (!status) { + dev_warn(&device->pdev->dev, "Spurious interrupt"); + return IRQ_NONE; + } + + /* if (status & TSPP_IRQ_STATUS_TSP_RD_CMPL) */ + + if (status & TSPP_IRQ_STATUS_KEY_ERROR) { + /* read the key error info */ + data = readl_relaxed(device->base + TSPP_KEY_ERROR); + dev_info(&device->pdev->dev, "key error 0x%x", data); + } + if (status & TSPP_IRQ_STATUS_KEY_SWITCHED_BAD) { + data = readl_relaxed(device->base + TSPP_KEY_VALID); + dev_info(&device->pdev->dev, "key invalidated: 0x%x", data); + } + if (status & TSPP_IRQ_STATUS_KEY_SWITCHED) + dev_info(&device->pdev->dev, "key switched"); + + if (status & 0xffff) + dev_info(&device->pdev->dev, "broken pipe"); + + writel_relaxed(status, device->base + TSPP_IRQ_CLEAR); + wmb(); + return IRQ_HANDLED; +} + +/*** callbacks ***/ +static void tspp_sps_complete_cb(struct sps_event_notify *notify) +{ + struct tspp_channel *channel = notify->user; + tasklet_schedule(&channel->pdev->tlet); +} + +/*** tasklet ***/ +static void tspp_sps_complete_tlet(unsigned long data) +{ + int i; + int complete; + unsigned long flags; + struct sps_iovec iovec; + struct tspp_channel *channel; + struct tspp_device *device = (struct tspp_device *)data; + struct tspp_mem_buffer *buffer; + + spin_lock_irqsave(&device->spinlock, flags); + + for (i = 0; i < TSPP_NUM_CHANNELS; i++) { + complete = 0; + channel = &channel_list[i]; + buffer = &channel->buffer[channel->waiting]; + + /* get completions */ + if (buffer->state == TSPP_BUF_STATE_WAITING) { + if (sps_get_iovec(channel->pipe, &iovec) != 0) { + pr_err("tspp: Error in iovec on channel %i", + channel->id); + break; + } + if (iovec.size == 0) + break; + + if (iovec.addr != buffer->mem.phys_base) + pr_err("tspp: buffer mismatch 0x%08x", + buffer->mem.phys_base); + + complete = 1; + buffer->state = TSPP_BUF_STATE_DATA; + buffer->filled = iovec.size; + buffer->read_index = 0; + channel->waiting++; + if (channel->waiting == TSPP_NUM_BUFFERS) + channel->waiting = 0; + } + + if (complete) { + /* wake any waiting processes */ + wake_up_interruptible(&channel->in_queue); + } + } + + spin_unlock_irqrestore(&device->spinlock, flags); +} + +/*** GPIO functions ***/ +static void tspp_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 tspp_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("tspp: gpio_request(%d) <%s> failed: %d\n", + GPIO_PIN(g->gpio_cfg), g->label ?: "?", rc); + goto err; + } + } + return 0; +err: + tspp_gpios_free(table, i); + return rc; +} + +static int tspp_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("tspp_gpios_disable(0x%08x, GPIO_CFG_DISABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("tspp: 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 tspp_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("tspp: gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("tspp: 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: + tspp_gpios_disable(table, i); + return rc; +} + +static int tspp_gpios_request_enable(const struct msm_gpio *table, int size) +{ + int rc = tspp_gpios_request(table, size); + if (rc) + return rc; + rc = tspp_gpios_enable(table, size); + if (rc) + tspp_gpios_free(table, size); + return rc; +} + +static void tspp_gpios_disable_free(const struct msm_gpio *table, int size) +{ + tspp_gpios_disable(table, size); + tspp_gpios_free(table, size); +} + +static int tspp_start_gpios(struct tspp_device *device) +{ + struct msm_tspp_platform_data *pdata = + device->pdev->dev.platform_data; + return tspp_gpios_request_enable(pdata->gpios, pdata->num_gpios); +} + +static void tspp_stop_gpios(struct tspp_device *device) +{ + struct msm_tspp_platform_data *pdata = + device->pdev->dev.platform_data; + tspp_gpios_disable_free(pdata->gpios, pdata->num_gpios); +} + +/*** TSIF functions ***/ +static int tspp_start_tsif(struct tspp_tsif_device *tsif_device) +{ + int start_hardware = 0; + u32 ctl; + + if (tsif_device->ref_count == 0) { + start_hardware = 1; + } else if (tsif_device->ref_count > 0) { + ctl = readl_relaxed(tsif_device->base + TSIF_STS_CTL_OFF); + if ((ctl & TSIF_STS_CTL_START) != 1) { + /* this hardware should already be running */ + pr_warn("tspp: tsif hw not started but ref count > 0"); + start_hardware = 1; + } + } + + if (start_hardware) { + if (loopback_mode) { + ctl = TSIF_STS_CTL_EN_IRQ | + TSIF_STS_CTL_EN_NULL | + TSIF_STS_CTL_EN_ERROR | + TSIF_STS_CTL_TEST_MODE | + TSIF_STS_CTL_EN_DM; + TSPP_DEBUG("tspp: starting tsif hw in loopback mode 0x%x", ctl); + } else { + ctl = TSIF_STS_CTL_EN_IRQ | + TSIF_STS_CTL_EN_TIME_LIM | + TSIF_STS_CTL_EN_TCR | + TSIF_STS_CTL_EN_DM; + } + writel_relaxed(ctl, tsif_device->base + TSIF_STS_CTL_OFF); + writel_relaxed(tsif_device->time_limit, + tsif_device->base + TSIF_TIME_LIMIT_OFF); + wmb(); + writel_relaxed(ctl | TSIF_STS_CTL_START, + tsif_device->base + TSIF_STS_CTL_OFF); + wmb(); + ctl = readl_relaxed(tsif_device->base + TSIF_STS_CTL_OFF); + } + + tsif_device->ref_count++; + + return (ctl & TSIF_STS_CTL_START) ? 0 : -EFAULT; +} + +static void tspp_stop_tsif(struct tspp_tsif_device *tsif_device) +{ + if (tsif_device->ref_count == 0) + return; + + tsif_device->ref_count--; + + if (tsif_device->ref_count == 0) { + writel_relaxed(TSIF_STS_CTL_STOP, + tsif_device->base + TSIF_STS_CTL_OFF); + wmb(); + } +} + +/*** TSPP functions ***/ +static int tspp_get_key_entry(void) +{ + int i; + for (i = 0; i < TSPP_NUM_KEYS; i++) { + if (!(tspp_key_entry & (1 << i))) { + tspp_key_entry |= (1 << i); + return i; + } + } + return 1; +} + +static void tspp_free_key_entry(int entry) +{ + if (entry > TSPP_NUM_KEYS) { + pr_err("tspp_free_key_entry: index out of bounds"); + return; + } + + tspp_key_entry &= ~(1 << entry); +} + +static int tspp_alloc_buffer(struct sps_mem_buffer *mem, + struct tspp_channel *channel) +{ + if (channel->bufsize < TSPP_MIN_BUFFER_SIZE || + channel->bufsize > TSPP_MAX_BUFFER_SIZE) { + pr_err("tspp: bad buffer size"); + return 1; + } + + switch (channel->mode) { + case TSPP_MODE_DISABLED: + mem->size = 0; + pr_err("tspp: channel is disabled"); + return 1; + + case TSPP_MODE_PES: + /* give the user what he asks for */ + mem->size = channel->bufsize; + break; + + case TSPP_MODE_RAW: + /* must be a multiple of 192 */ + if (channel->bufsize < (TSPP_PACKET_LENGTH+4)) + mem->size = (TSPP_PACKET_LENGTH+4); + else + mem->size = (channel->bufsize / + (TSPP_PACKET_LENGTH+4)) * + (TSPP_PACKET_LENGTH+4); + break; + + case TSPP_MODE_RAW_NO_SUFFIX: + /* must be a multiple of 188 */ + mem->size = (channel->bufsize / TSPP_PACKET_LENGTH) * + TSPP_PACKET_LENGTH; + break; + } + +#ifdef TSPP_USE_DMA_ALLOC_COHERENT + mem->base = dma_alloc_coherent(NULL, mem->size, + &mem->phys_base, GFP_KERNEL); + if (mem->base == 0) { + pr_err("tspp dma alloc coherent failed %i", mem->size); + return -ENOMEM; + } +#else + mem->base = kmalloc(mem->size, GFP_KERNEL); + if (mem->base == 0) { + pr_err("tspp buffer allocation failed %i", mem->size); + return -ENOMEM; + } + mem->phys_base = dma_map_single(NULL, + mem->base, + mem->size, + DMA_FROM_DEVICE); +#endif + + return 0; +} + +static int tspp_global_reset(struct tspp_device *pdev) +{ + u32 i, val; + + /* stop all TSIFs */ + for (i = 0; i < TSPP_TSIF_INSTANCES; i++) { + pdev->tsif[i].ref_count = 1; /* allows stopping hw */ + tspp_stop_tsif(&pdev->tsif[i]); /* will reset ref_count to 0 */ + pdev->tsif[i].time_limit = TSPP_TSIF_DEFAULT_TIME_LIMIT; + } + writel_relaxed(TSPP_RST_RESET, pdev->base + TSPP_RST); + wmb(); + + /* BAM */ + if (sps_device_reset(pdev->bam_handle) != 0) { + pr_err("tspp: error resetting bam"); + return 1; + } + + /* TSPP tables */ + for (i = 0; i < TSPP_FILTER_TABLES; i++) + memset(tspp_filter_table[i], + 0, sizeof(struct tspp_pid_filter_table)); + + /* disable all filters */ + val = (2 << TSPP_NUM_CHANNELS) - 1; + writel_relaxed(val, pdev->base + TSPP_PS_DISABLE); + + /* TSPP registers */ + val = readl_relaxed(pdev->base + TSPP_CONTROL); + writel_relaxed(val | TSPP_CLK_CONTROL_FORCE_PERF_CNT, + pdev->base + TSPP_CONTROL); + wmb(); + memset(tspp_global_performance, 0, + sizeof(struct tspp_global_performance_regs)); + memset(tspp_pipe_context, 0, + sizeof(struct tspp_pipe_context_regs)); + memset(tspp_pipe_performance, 0, + sizeof(struct tspp_pipe_performance_regs)); + wmb(); + writel_relaxed(val & ~TSPP_CLK_CONTROL_FORCE_PERF_CNT, + pdev->base + TSPP_CONTROL); + wmb(); + + val = readl_relaxed(pdev->base + TSPP_CONFIG); + val &= ~(TSPP_CONFIG_PS_LEN_ERR_MASK | + TSPP_CONFIG_PS_CONT_ERR_UNSP_MASK | + TSPP_CONFIG_PS_CONT_ERR_MASK); + TSPP_CONFIG_SET_PACKET_LENGTH(val, TSPP_PACKET_LENGTH); + writel_relaxed(val, pdev->base + TSPP_CONFIG); + writel_relaxed(0x000fffff, pdev->base + TSPP_IRQ_MASK); + writel_relaxed(0x000fffff, pdev->base + TSPP_IRQ_CLEAR); + writel_relaxed(0, pdev->base + TSPP_RST); + wmb(); + + tspp_key_entry = 0; + + return 0; +} + +int tspp_open_stream(struct tspp_channel *channel, enum tspp_source src) +{ + u32 val; + struct tspp_device *pdev; + + if (!channel) + return 1; + + pdev = channel->pdev; + + switch (src) { + case TSPP_SOURCE_TSIF0: + /* make sure TSIF0 is running & enabled */ + if (tspp_start_tsif(&pdev->tsif[0]) != 0) { + pr_err("tspp: error starting tsif0"); + return 1; + } + val = readl_relaxed(pdev->base + TSPP_CONTROL); + writel_relaxed(val & ~TSPP_CONTROL_TSP_TSIF0_SRC_DIS, + pdev->base + TSPP_CONTROL); + wmb(); + break; + case TSPP_SOURCE_TSIF1: + /* make sure TSIF1 is running & enabled */ + if (tspp_start_tsif(&pdev->tsif[1]) != 0) { + pr_err("tspp: error starting tsif1"); + return 1; + } + val = readl_relaxed(pdev->base + TSPP_CONTROL); + writel_relaxed(val & ~TSPP_CONTROL_TSP_TSIF1_SRC_DIS, + pdev->base + TSPP_CONTROL); + wmb(); + break; + case TSPP_SOURCE_MEM: + break; + default: + pr_warn("tspp: channel %i invalid source %i", channel->id, src); + return 1; + } + + channel->src = src; + + return 0; +} +EXPORT_SYMBOL(tspp_open_stream); + +int tspp_close_stream(struct tspp_channel *channel) +{ + u32 val; + struct tspp_device *pdev; + + pdev = channel->pdev; + + switch (channel->src) { + case TSPP_SOURCE_TSIF0: + tspp_stop_tsif(&pdev->tsif[0]); + val = readl_relaxed(pdev->base + TSPP_CONTROL); + writel_relaxed(val | TSPP_CONTROL_TSP_TSIF0_SRC_DIS, + pdev->base + TSPP_CONTROL); + wmb(); + break; + case TSPP_SOURCE_TSIF1: + tspp_stop_tsif(&pdev->tsif[1]); + val = readl_relaxed(pdev->base + TSPP_CONTROL); + writel_relaxed(val | TSPP_CONTROL_TSP_TSIF1_SRC_DIS, + pdev->base + TSPP_CONTROL); + break; + case TSPP_SOURCE_MEM: + break; + case TSPP_SOURCE_NONE: + break; + } + + channel->src = -1; + return 0; +} +EXPORT_SYMBOL(tspp_close_stream); + +int tspp_open_channel(struct tspp_channel *channel) +{ + int rc = 0; + struct sps_connect *config = &channel->config; + struct sps_register_event *event = &channel->event; + + if (channel->used) { + pr_err("tspp channel already in use"); + return 1; + } + + /* mark it as used */ + channel->used = 1; + + /* start the bam */ + channel->pipe = sps_alloc_endpoint(); + if (channel->pipe == 0) { + pr_err("tspp: error allocating endpoint"); + rc = -ENOMEM; + goto err_sps_alloc; + } + + /* get default configuration */ + sps_get_config(channel->pipe, config); + + config->source = channel->pdev->bam_handle; + config->destination = SPS_DEV_HANDLE_MEM; + config->mode = SPS_MODE_SRC; + config->options = SPS_O_AUTO_ENABLE | + SPS_O_EOT | SPS_O_ACK_TRANSFERS; + config->src_pipe_index = channel->id; + config->desc.size = + (TSPP_SPS_DESCRIPTOR_COUNT + 1) * SPS_DESCRIPTOR_SIZE; + config->desc.base = dma_alloc_coherent(NULL, + config->desc.size, + &config->desc.phys_base, + GFP_KERNEL); + if (config->desc.base == 0) { + pr_err("tspp: error allocating sps descriptors"); + rc = -ENOMEM; + goto err_desc_alloc; + } + + memset(config->desc.base, 0, config->desc.size); + + rc = sps_connect(channel->pipe, config); + if (rc) { + pr_err("tspp: error connecting bam"); + goto err_connect; + } + + event->mode = SPS_TRIGGER_CALLBACK; + event->options = SPS_O_EOT; + event->callback = tspp_sps_complete_cb; + event->xfer_done = NULL; + event->user = channel; + + rc = sps_register_event(channel->pipe, event); + if (rc) { + pr_err("tspp: error registering event"); + goto err_event; + } + + rc = pm_runtime_get(&channel->pdev->pdev->dev); + if (rc < 0) { + dev_err(&channel->pdev->pdev->dev, + "Runtime PM: Unable to wake up tspp device, rc = %d", + rc); + } + + wake_lock(&channel->pdev->wake_lock); + return 0; + +err_event: + sps_disconnect(channel->pipe); +err_connect: + dma_free_coherent(NULL, config->desc.size, config->desc.base, + config->desc.phys_base); +err_desc_alloc: + sps_free_endpoint(channel->pipe); +err_sps_alloc: + return rc; +} +EXPORT_SYMBOL(tspp_open_channel); + +int tspp_close_channel(struct tspp_channel *channel) +{ + int i; + int id; + u32 val; + struct sps_connect *config = &channel->config; + struct tspp_device *pdev = channel->pdev; + + TSPP_DEBUG("tspp_close_channel"); + channel->used = 0; + + /* disable pipe (channel) */ + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + writel_relaxed(val | channel->id, pdev->base + TSPP_PS_DISABLE); + wmb(); + + /* unregister all filters for this channel */ + for (i = 0; i < TSPP_NUM_PRIORITIES; i++) { + struct tspp_pid_filter *tspp_filter = + &tspp_filter_table[channel->src]->filter[i]; + id = FILTER_GET_PIPE_NUMBER0(tspp_filter); + if (id == channel->id) { + if (FILTER_HAS_ENCRYPTION(tspp_filter)) + tspp_free_key_entry( + FILTER_GET_KEY_NUMBER(tspp_filter)); + tspp_filter->config = 0; + tspp_filter->filter = 0; + } + } + channel->filter_count = 0; + + /* stop the stream */ + tspp_close_stream(channel); + + /* disconnect the bam */ + if (sps_disconnect(channel->pipe) != 0) + pr_warn("tspp: Error freeing sps endpoint (%i)", channel->id); + + /* destroy the buffers */ + dma_free_coherent(NULL, config->desc.size, config->desc.base, + config->desc.phys_base); + + for (i = 0; i < TSPP_NUM_BUFFERS; i++) { + if (channel->buffer[i].mem.phys_base) { +#ifdef TSPP_USE_DMA_ALLOC_COHERENT + dma_free_coherent(NULL, + channel->buffer[i].mem.size, + channel->buffer[i].mem.base, + channel->buffer[i].mem.phys_base); +#else + dma_unmap_single(channel->dd, + channel->buffer[i].mem.phys_base, + channel->buffer[i].mem.size, + 0); + kfree(channel->buffer[i].mem.base); +#endif + channel->buffer[i].mem.phys_base = 0; + } + channel->buffer[i].mem.base = 0; + channel->buffer[i].state = TSPP_BUF_STATE_EMPTY; + } + channel->buffer_count = 0; + + wake_unlock(&channel->pdev->wake_lock); + return 0; +} +EXPORT_SYMBOL(tspp_close_channel); + +/* picks a stream for this channel */ +int tspp_select_source(struct tspp_channel *channel, + struct tspp_select_source *src) +{ + /* make sure the requested src id is in bounds */ + if (src->source > TSPP_SOURCE_MEM) { + pr_err("tspp source out of bounds"); + return 1; + } + + /* open the stream */ + tspp_open_stream(channel, src->source); + + return 0; +} +EXPORT_SYMBOL(tspp_select_source); + +int tspp_add_filter(struct tspp_channel *channel, + struct tspp_filter *filter) +{ + int i; + int other_channel; + int entry; + u32 val, pid, enabled; + struct tspp_device *pdev = channel->pdev; + struct tspp_pid_filter p; + + TSPP_DEBUG("tspp_add_filter"); + if (filter->source > TSPP_SOURCE_MEM) { + pr_err("tspp invalid source"); + return 1; + } + + if (filter->priority >= TSPP_NUM_PRIORITIES) { + pr_err("tspp invalid source"); + return 1; + } + + /* make sure this filter mode matches the channel mode */ + switch (channel->mode) { + case TSPP_MODE_DISABLED: + channel->mode = filter->mode; + break; + case TSPP_MODE_RAW: + case TSPP_MODE_PES: + case TSPP_MODE_RAW_NO_SUFFIX: + if (filter->mode != channel->mode) { + pr_err("tspp: wrong filter mode"); + return 1; + } + } + + if (filter->mode == TSPP_MODE_PES) { + for (i = 0; i < TSPP_NUM_PRIORITIES; i++) { + struct tspp_pid_filter *tspp_filter = + &tspp_filter_table[channel->src]->filter[i]; + pid = FILTER_GET_PIPE_PID((tspp_filter)); + enabled = FILTER_GET_PIPE_PROCESS0(tspp_filter); + if (enabled && (pid == filter->pid)) { + other_channel = + FILTER_GET_PIPE_NUMBER0(tspp_filter); + pr_err("tspp: pid 0x%x already in use by channel %i", + filter->pid, other_channel); + return 1; + } + } + } + + /* make sure this priority is not already in use */ + enabled = FILTER_GET_PIPE_PROCESS0( + (&(tspp_filter_table[channel->src]->filter[filter->priority]))); + if (enabled) { + pr_err("tspp: filter priority %i source %i is already enabled\n", + filter->priority, channel->src); + return 1; + } + + if (channel->mode == TSPP_MODE_PES) { + /* if we are already processing in PES mode, disable pipe + (channel) and filter to be updated */ + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + writel_relaxed(val | (1 << channel->id), + pdev->base + TSPP_PS_DISABLE); + wmb(); + } + + /* update entry */ + p.filter = 0; + p.config = 0; + FILTER_SET_PIPE_PROCESS0((&p), filter->mode); + FILTER_SET_PIPE_PID((&p), filter->pid); + FILTER_SET_PID_MASK((&p), filter->mask); + FILTER_SET_PIPE_NUMBER0((&p), channel->id); + FILTER_SET_PIPE_PROCESS1((&p), TSPP_MODE_DISABLED); + if (filter->decrypt) { + entry = tspp_get_key_entry(); + if (entry == -1) { + pr_err("tspp: no more keys available!"); + } else { + p.config |= FILTER_DECRYPT; + FILTER_SET_KEY_NUMBER((&p), entry); + } + } + TSPP_DEBUG("tspp_add_filter: mode=%i pid=%i mask=%i channel=%i", + filter->mode, filter->pid, filter->mask, channel->id); + + tspp_filter_table[channel->src]-> + filter[filter->priority].config = p.config; + tspp_filter_table[channel->src]-> + filter[filter->priority].filter = p.filter; + + /* reenable pipe */ + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + writel_relaxed(val & ~(1 << channel->id), pdev->base + TSPP_PS_DISABLE); + wmb(); + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + + /* allocate buffers if needed */ + if (channel->buffer_count == 0) { + TSPP_DEBUG("tspp: no buffers need %i", TSPP_NUM_BUFFERS); + for (i = 0; i < TSPP_NUM_BUFFERS; i++) { + if (tspp_alloc_buffer(&channel->buffer[i].mem, + channel) != 0) { + pr_warn("tspp: Can't allocate buffer %i", i); + } else { + channel->buffer[i].filled = 0; + channel->buffer[i].read_index = 0; + channel->buffer_count++; + + /* start the transfer */ + if (sps_transfer_one(channel->pipe, + channel->buffer[i].mem.phys_base, + channel->buffer[i].mem.size, + channel, + SPS_IOVEC_FLAG_INT | + SPS_IOVEC_FLAG_EOB)) + pr_err("tspp: can't submit transfer"); + else + channel->buffer[i].state = + TSPP_BUF_STATE_WAITING; + } + } + } + + if (channel->buffer_count < MIN_ACCEPTABLE_BUFFER_COUNT) { + pr_err("failed to allocate at least %i buffers", + MIN_ACCEPTABLE_BUFFER_COUNT); + return -ENOMEM; + } + + channel->filter_count++; + + return 0; +} +EXPORT_SYMBOL(tspp_add_filter); + +int tspp_remove_filter(struct tspp_channel *channel, + struct tspp_filter *filter) +{ + int entry; + u32 val; + struct tspp_device *pdev = channel->pdev; + int src = channel->src; + struct tspp_pid_filter *tspp_filter = + &(tspp_filter_table[src]->filter[filter->priority]); + + /* disable pipe (channel) */ + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + writel_relaxed(val | channel->id, pdev->base + TSPP_PS_DISABLE); + wmb(); + + /* update data keys */ + if (tspp_filter->config & FILTER_DECRYPT) { + entry = FILTER_GET_KEY_NUMBER(tspp_filter); + tspp_free_key_entry(entry); + } + + /* update pid table */ + tspp_filter->config = 0; + tspp_filter->filter = 0; + + channel->filter_count--; + + /* reenable pipe */ + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + writel_relaxed(val & ~(1 << channel->id), + pdev->base + TSPP_PS_DISABLE); + wmb(); + val = readl_relaxed(pdev->base + TSPP_PS_DISABLE); + + return 0; +} +EXPORT_SYMBOL(tspp_remove_filter); + +int tspp_set_key(struct tspp_channel *channel, struct tspp_key* key) +{ + int i; + int id; + int key_index; + int data; + + /* read the key index used by this channel */ + for (i = 0; i < TSPP_NUM_PRIORITIES; i++) { + struct tspp_pid_filter *tspp_filter = + &(tspp_filter_table[channel->src]->filter[i]); + id = FILTER_GET_PIPE_NUMBER0(tspp_filter); + if (id == channel->id) { + if (FILTER_HAS_ENCRYPTION(tspp_filter)) { + key_index = FILTER_GET_KEY_NUMBER(tspp_filter); + break; + } + } + } + if (i == TSPP_NUM_PRIORITIES) { + pr_err("tspp: no encryption on this channel"); + return 1; + } + + if (key->parity == TSPP_KEY_PARITY_EVEN) { + tspp_key_table->entry[key_index].even_lsb = key->lsb; + tspp_key_table->entry[key_index].even_msb = key->msb; + } else { + tspp_key_table->entry[key_index].odd_lsb = key->lsb; + tspp_key_table->entry[key_index].odd_msb = key->msb; + } + data = readl_relaxed(channel->pdev->base + TSPP_KEY_VALID); + + return 0; +} +EXPORT_SYMBOL(tspp_set_key); + +static int tspp_set_iv(struct tspp_channel *channel, struct tspp_iv *iv) +{ + struct tspp_device *pdev = channel->pdev; + + writel_relaxed(iv->data[0], pdev->base + TSPP_CBC_INIT_VAL(0)); + writel_relaxed(iv->data[1], pdev->base + TSPP_CBC_INIT_VAL(1)); + return 0; +} + +static int tspp_set_system_keys(struct tspp_channel *channel, + struct tspp_system_keys *keys) +{ + int i; + struct tspp_device *pdev = channel->pdev; + + for (i = 0; i < TSPP_NUM_SYSTEM_KEYS; i++) + writel_relaxed(keys->data[i], pdev->base + TSPP_SYSTEM_KEY(i)); + + return 0; +} + +static int tspp_set_buffer_size(struct tspp_channel *channel, + struct tspp_buffer *buf) +{ + if (buf->size < TSPP_MIN_BUFFER_SIZE) + channel->bufsize = TSPP_MIN_BUFFER_SIZE; + else if (buf->size > TSPP_MAX_BUFFER_SIZE) + channel->bufsize = TSPP_MAX_BUFFER_SIZE; + else + channel->bufsize = buf->size; + + TSPP_DEBUG("tspp channel %i buffer size %i", + channel->id, channel->bufsize); + + return 0; +} + +/*** File Operations ***/ +static ssize_t tspp_open(struct inode *inode, struct file *filp) +{ + struct tspp_channel *channel; + channel = container_of(inode->i_cdev, struct tspp_channel, cdev); + filp->private_data = channel; + + /* if this channel is already in use, quit */ + if (channel->used) { + pr_err("tspp channel %i already in use", + MINOR(channel->cdev.dev)); + return -EACCES; + } + + if (tspp_open_channel(channel) != 0) { + pr_err("tspp: error opening channel"); + return -EACCES; + } + + return 0; +} + +static unsigned int tspp_poll(struct file *filp, struct poll_table_struct *p) +{ + unsigned long flags; + unsigned int mask = 0; + struct tspp_channel *channel; + channel = filp->private_data; + + /* register the wait queue for this channel */ + poll_wait(filp, &channel->in_queue, p); + + spin_lock_irqsave(&channel->pdev->spinlock, flags); + if (channel->buffer[channel->read].state == TSPP_BUF_STATE_DATA) + mask = POLLIN | POLLRDNORM; + + spin_unlock_irqrestore(&channel->pdev->spinlock, flags); + + return mask; +} + +static ssize_t tspp_release(struct inode *inode, struct file *filp) +{ + struct tspp_channel *channel; + channel = filp->private_data; + + pr_info("tspp_release"); + tspp_close_channel(channel); + + return 0; +} + +static ssize_t tspp_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + size_t size = 0; + size_t transferred = 0; + struct tspp_channel *channel; + struct tspp_mem_buffer *buffer; + channel = filp->private_data; + + TSPP_DEBUG("tspp_read"); + buffer = &channel->buffer[channel->read]; + /* see if we have any buffers ready to read */ + while (buffer->state != TSPP_BUF_STATE_DATA) { + if (filp->f_flags & O_NONBLOCK) { + pr_warn("tspp: nothing to read on channel %i!", + channel->id); + return -EAGAIN; + } + /* go to sleep if there is nothing to read */ + TSPP_DEBUG("tspp: sleeping"); + if (wait_event_interruptible(channel->in_queue, + (buffer->state == TSPP_BUF_STATE_DATA))) { + pr_err("tspp: rude awakening\n"); + return -ERESTARTSYS; + } + } + + while (buffer->state == TSPP_BUF_STATE_DATA) { + size = min(count, buffer->filled); + TSPP_DEBUG("tspp: reading channel %i buffer %i size %i", + channel->id, channel->read, size); + if (size == 0) + break; + +#ifndef TSPP_USE_DMA_ALLOC_COHERENT + /* unmap buffer (invalidates processor cache) */ + if (buffer->mem.phys_base) { + dma_unmap_single(NULL, + buffer->mem.phys_base, + buffer->mem.size, + DMA_FROM_DEVICE); + buffer->mem.phys_base = 0; + } +#endif + + if (copy_to_user(buf, buffer->mem.base + + buffer->read_index, size)) { + pr_err("tspp: error copying to user buffer"); + return -EFAULT; + } + buf += size; + count -= size; + transferred += size; + buffer->read_index += size; + + /* after reading the end of the buffer, requeue it, + and set up for reading the next one */ + if (buffer->read_index == + channel->buffer[channel->read].filled) { + buffer->state = TSPP_BUF_STATE_WAITING; +#ifndef TSPP_USE_DMA_ALLOC_COHERENT + buffer->mem.phys_base = dma_map_single(NULL, + buffer->mem.base, + buffer->mem.size, + DMA_FROM_DEVICE); + if (!dma_mapping_error(NULL, + buffer->mem.phys_base)) { +#endif + if (sps_transfer_one(channel->pipe, + buffer->mem.phys_base, + buffer->mem.size, + channel, + SPS_IOVEC_FLAG_INT | + SPS_IOVEC_FLAG_EOT)) + pr_err("tspp: can't submit transfer"); + else { + channel->read++; + if (channel->read == TSPP_NUM_BUFFERS) + channel->read = 0; + } +#ifndef TSPP_USE_DMA_ALLOC_COHERENT + } +#endif + } + } + + return transferred; +} + +static long tspp_ioctl(struct file *filp, + unsigned int param0, unsigned long param1) +{ + int rc = -1; + struct tspp_channel *channel; + channel = filp->private_data; + + if (!param1) + return -EINVAL; + + switch (param0) { + case TSPP_IOCTL_SELECT_SOURCE: + rc = tspp_select_source(channel, + (struct tspp_select_source *)param1); + break; + case TSPP_IOCTL_ADD_FILTER: + rc = tspp_add_filter(channel, + (struct tspp_filter *)param1); + break; + case TSPP_IOCTL_REMOVE_FILTER: + rc = tspp_remove_filter(channel, + (struct tspp_filter *)param1); + break; + case TSPP_IOCTL_SET_KEY: + rc = tspp_set_key(channel, + (struct tspp_key *)param1); + break; + case TSPP_IOCTL_SET_IV: + rc = tspp_set_iv(channel, + (struct tspp_iv *)param1); + break; + case TSPP_IOCTL_SET_SYSTEM_KEYS: + rc = tspp_set_system_keys(channel, + (struct tspp_system_keys *)param1); + break; + case TSPP_IOCTL_BUFFER_SIZE: + rc = tspp_set_buffer_size(channel, + (struct tspp_buffer *)param1); + break; + case TSPP_IOCTL_LOOPBACK: + loopback_mode = param1; + rc = 0; + break; + default: + pr_err("tspp: Unknown ioctl %i", param0); + } + + /* normalize the return code in case one of the subfunctions does + something weird */ + if (rc != 0) + rc = 1; + + return rc; +} + +/*** debugfs ***/ +#ifdef TSPP_USE_DEBUGFS +static int debugfs_iomem_x32_set(void *data, u64 val) +{ + writel_relaxed(val, data); + wmb(); + return 0; +} + +static int debugfs_iomem_x32_get(void *data, u64 *val) +{ + *val = readl_relaxed(data); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get, + debugfs_iomem_x32_set, "0x%08llx"); + +static void tsif_debugfs_init(struct tspp_tsif_device *tsif_device, + int instance) +{ + char name[10]; + snprintf(name, 10, "tsif%i", instance); + tsif_device->dent_tsif = debugfs_create_dir( + name, 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_file( + debugfs_tsif_regs[i].name, + debugfs_tsif_regs[i].mode, + tsif_device->dent_tsif, + base + debugfs_tsif_regs[i].offset, + &fops_iomem_x32); + } + } +} + +static void tsif_debugfs_exit(struct tspp_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; + } +} + +static void tspp_debugfs_init(struct tspp_device *device, int instance) +{ + char name[10]; + snprintf(name, 10, "tspp%i", instance); + device->dent = debugfs_create_dir( + name, NULL); + if (device->dent) { + int i; + void __iomem *base = device->base; + for (i = 0; i < ARRAY_SIZE(debugfs_tspp_regs); i++) { + device->debugfs_regs[i] = + debugfs_create_file( + debugfs_tspp_regs[i].name, + debugfs_tspp_regs[i].mode, + device->dent, + base + debugfs_tspp_regs[i].offset, + &fops_iomem_x32); + } + } +} + +static void tspp_debugfs_exit(struct tspp_device *device) +{ + if (device->dent) { + int i; + debugfs_remove_recursive(device->dent); + device->dent = NULL; + for (i = 0; i < ARRAY_SIZE(debugfs_tspp_regs); i++) + device->debugfs_regs[i] = NULL; + } +} +#endif /* TSPP_USE_DEBUGFS */ + +static const struct file_operations tspp_fops = { + .owner = THIS_MODULE, + .read = tspp_read, + .open = tspp_open, + .poll = tspp_poll, + .release = tspp_release, + .unlocked_ioctl = tspp_ioctl, +}; + +static int tspp_channel_init(struct tspp_channel *channel) +{ + channel->bufsize = TSPP_MIN_BUFFER_SIZE; + channel->read = 0; + channel->waiting = 0; + cdev_init(&channel->cdev, &tspp_fops); + channel->cdev.owner = THIS_MODULE; + channel->id = MINOR(tspp_minor); + init_waitqueue_head(&channel->in_queue); + channel->buffer_count = 0; + channel->filter_count = 0; + + if (cdev_add(&channel->cdev, tspp_minor++, 1) != 0) { + pr_err("tspp: cdev_add failed"); + return 1; + } + + channel->dd = device_create(tspp_class, NULL, channel->cdev.dev, + channel, "tspp%02d", channel->id); + if (IS_ERR(channel->dd)) { + pr_err("tspp: device_create failed: %i", + (int)PTR_ERR(channel->dd)); + cdev_del(&channel->cdev); + return 1; + } + + return 0; +} + +static int __devinit msm_tspp_probe(struct platform_device *pdev) +{ + int rc = -ENODEV; + u32 version; + u32 i; + struct msm_tspp_platform_data *data; + struct tspp_device *device; + struct resource *mem_tsif0; + struct resource *mem_tsif1; + struct resource *mem_tspp; + struct resource *mem_bam; + struct tspp_channel *channel; + + /* must have platform data */ + data = pdev->dev.platform_data; + if (!data) { + pr_err("tspp: Platform data not available"); + rc = -EINVAL; + goto out; + } + + /* check for valid device id */ + if ((pdev->id < 0) || (pdev->id >= 1)) { + pr_err("tspp: Invalid device ID %d", pdev->id); + rc = -EINVAL; + goto out; + } + + /* OK, we will use this device */ + device = kzalloc(sizeof(struct tspp_device), GFP_KERNEL); + if (!device) { + pr_err("tspp: Failed to allocate memory for device"); + rc = -ENOMEM; + goto out; + } + + /* set up references */ + device->pdev = pdev; + platform_set_drvdata(pdev, device); + + /* map clocks */ + if (data->tsif_pclk) { + device->tsif_pclk = clk_get(NULL, data->tsif_pclk); + if (IS_ERR(device->tsif_pclk)) { + pr_err("tspp: failed to get %s", + data->tsif_pclk); + rc = PTR_ERR(device->tsif_pclk); + device->tsif_pclk = NULL; + goto err_pclock; + } + } + if (data->tsif_ref_clk) { + device->tsif_ref_clk = clk_get(NULL, data->tsif_ref_clk); + if (IS_ERR(device->tsif_ref_clk)) { + pr_err("tspp: failed to get %s", + data->tsif_ref_clk); + rc = PTR_ERR(device->tsif_ref_clk); + device->tsif_ref_clk = NULL; + goto err_refclock; + } + } + + /* map I/O memory */ + mem_tsif0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_tsif0) { + pr_err("tspp: Missing tsif0 MEM resource"); + rc = -ENXIO; + goto err_res_tsif0; + } + device->tsif[0].base = ioremap(mem_tsif0->start, + resource_size(mem_tsif0)); + if (!device->tsif[0].base) { + pr_err("tspp: ioremap failed"); + goto err_map_tsif0; + } + + mem_tsif1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!mem_tsif1) { + dev_err(&pdev->dev, "Missing tsif1 MEM resource"); + rc = -ENXIO; + goto err_res_tsif1; + } + device->tsif[1].base = ioremap(mem_tsif1->start, + resource_size(mem_tsif1)); + if (!device->tsif[1].base) { + dev_err(&pdev->dev, "ioremap failed"); + goto err_map_tsif1; + } + + mem_tspp = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!mem_tspp) { + dev_err(&pdev->dev, "Missing MEM resource"); + rc = -ENXIO; + goto err_res_dev; + } + device->base = ioremap(mem_tspp->start, resource_size(mem_tspp)); + if (!device->base) { + dev_err(&pdev->dev, "ioremap failed"); + goto err_map_dev; + } + + mem_bam = platform_get_resource(pdev, IORESOURCE_MEM, 3); + if (!mem_bam) { + pr_err("tspp: Missing bam MEM resource"); + rc = -ENXIO; + goto err_res_bam; + } + memset(&device->bam_props, 0, sizeof(device->bam_props)); + device->bam_props.phys_addr = mem_bam->start; + device->bam_props.virt_addr = ioremap(mem_bam->start, + resource_size(mem_bam)); + if (!device->bam_props.virt_addr) { + dev_err(&pdev->dev, "ioremap failed"); + goto err_map_bam; + } + + /* map TSPP IRQ */ + rc = platform_get_irq(pdev, 0); + if (rc > 0) { + device->tspp_irq = rc; + rc = request_irq(device->tspp_irq, tspp_isr, IRQF_SHARED, + dev_name(&pdev->dev), device); + if (rc) { + dev_err(&pdev->dev, "failed to request IRQ %d : %d", + device->tspp_irq, rc); + goto err_irq; + } + } else { + dev_err(&pdev->dev, "failed to get tspp IRQ"); + goto err_irq; + } + + /* BAM IRQ */ + device->bam_irq = TSIF_BAM_IRQ; + + /* GPIOs */ + rc = tspp_start_gpios(device); + if (rc) + goto err_gpio; + + /* power management */ + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + +#ifdef TSPP_USE_DEBUGFS + tspp_debugfs_init(device, 0); + + for (i = 0; i < TSPP_TSIF_INSTANCES; i++) + tsif_debugfs_init(&device->tsif[i], i); +#endif /* TSPP_USE_DEBUGFS */ + + wake_lock_init(&device->wake_lock, WAKE_LOCK_SUSPEND, + dev_name(&pdev->dev)); + + /* set up pointers to ram-based 'registers' */ + tspp_filter_table[0] = TSPP_PID_FILTER_TABLE0 + device->base; + tspp_filter_table[1] = TSPP_PID_FILTER_TABLE1 + device->base; + tspp_filter_table[2] = TSPP_PID_FILTER_TABLE2 + device->base; + tspp_key_table = TSPP_DATA_KEY + device->base; + tspp_global_performance = TSPP_GLOBAL_PERFORMANCE + device->base; + tspp_pipe_context = TSPP_PIPE_CONTEXT + device->base; + tspp_pipe_performance = TSPP_PIPE_PERFORMANCE + device->base; + + device->bam_props.summing_threshold = 0x10; + device->bam_props.irq = device->bam_irq; + device->bam_props.manage = SPS_BAM_MGR_LOCAL; + + if (sps_register_bam_device(&device->bam_props, + &device->bam_handle) != 0) { + pr_err("tspp: failed to register bam"); + goto err_bam; + } + + if (device->tsif_pclk && clk_enable(device->tsif_pclk) != 0) { + dev_err(&pdev->dev, "Can't start pclk"); + goto err_pclk; + } + if (device->tsif_ref_clk && clk_enable(device->tsif_ref_clk) != 0) { + dev_err(&pdev->dev, "Can't start ref clk"); + goto err_refclk; + } + + spin_lock_init(&device->spinlock); + tasklet_init(&device->tlet, tspp_sps_complete_tlet, + (unsigned long)device); + + /* initialize everything to a known state */ + tspp_global_reset(device); + + version = readl_relaxed(device->base + TSPP_VERSION); + if (version != 1) + pr_warn("tspp: unrecognized hw version=%i", version); + + /* update the channels with the device */ + for (i = 0; i < TSPP_NUM_CHANNELS; i++) { + channel = &channel_list[i]; + channel->pdev = device; + } + + /* everything is ok */ + return 0; + +err_refclk: + if (device->tsif_pclk) + clk_disable(device->tsif_pclk); +err_pclk: + sps_deregister_bam_device(device->bam_handle); +err_bam: +#ifdef TSPP_USE_DEBUGFS + tspp_debugfs_exit(device); + for (i = 0; i < TSPP_TSIF_INSTANCES; i++) + tsif_debugfs_exit(&device->tsif[i]); +#endif /* TSPP_USE_DEBUGFS */ +err_gpio: +err_irq: + tspp_stop_gpios(device); + iounmap(device->bam_props.virt_addr); +err_map_bam: +err_res_bam: + iounmap(device->base); +err_map_dev: +err_res_dev: + iounmap(device->tsif[1].base); +err_map_tsif1: +err_res_tsif1: + iounmap(device->tsif[0].base); +err_map_tsif0: +err_res_tsif0: + if (device->tsif_ref_clk) + clk_put(device->tsif_ref_clk); +err_refclock: + if (device->tsif_pclk) + clk_put(device->tsif_pclk); +err_pclock: + kfree(device); + +out: + return rc; +} + +static int __devexit msm_tspp_remove(struct platform_device *pdev) +{ +#ifdef TSPP_USE_DEBUGFS + u32 i; +#endif /* TSPP_USE_DEBUGFS */ + + struct tspp_device *device = platform_get_drvdata(pdev); + + sps_deregister_bam_device(device->bam_handle); + +#ifdef TSPP_USE_DEBUGFS + for (i = 0; i < TSPP_TSIF_INSTANCES; i++) + tsif_debugfs_exit(&device->tsif[i]); +#endif /* TSPP_USE_DEBUGFS */ + + wake_lock_destroy(&device->wake_lock); + free_irq(device->tspp_irq, device); + tspp_stop_gpios(device); + + iounmap(device->bam_props.virt_addr); + iounmap(device->base); + for (i = 0; i < TSPP_TSIF_INSTANCES; i++) + iounmap(device->tsif[i].base); + + if (device->tsif_ref_clk) + clk_put(device->tsif_ref_clk); + + if (device->tsif_pclk) + clk_put(device->tsif_pclk); + + pm_runtime_disable(&pdev->dev); + pm_runtime_put(&pdev->dev); + kfree(device); + + return 0; +} + +/*** power management ***/ + +static int tspp_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending..."); + return 0; +} + +static int tspp_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming..."); + return 0; +} + +static const struct dev_pm_ops tspp_dev_pm_ops = { + .runtime_suspend = tspp_runtime_suspend, + .runtime_resume = tspp_runtime_resume, +}; + +static struct platform_driver msm_tspp_driver = { + .probe = msm_tspp_probe, + .remove = __exit_p(msm_tspp_remove), + .driver = { + .name = "msm_tspp", + .pm = &tspp_dev_pm_ops, + }, +}; + + +static int __init mod_init(void) +{ + u32 i; + int rc; + + /* first register the driver, and check hardware */ + rc = platform_driver_register(&msm_tspp_driver); + if (rc) { + pr_err("tspp: platform_driver_register failed: %d", rc); + goto err_register; + } + + /* now make the char devs (channels) */ + rc = alloc_chrdev_region(&tspp_minor, 0, TSPP_NUM_CHANNELS, "tspp"); + if (rc) { + pr_err("tspp: alloc_chrdev_region failed: %d", rc); + goto err_devrgn; + } + + tspp_class = class_create(THIS_MODULE, "tspp"); + if (IS_ERR(tspp_class)) { + rc = PTR_ERR(tspp_class); + pr_err("tspp: Error creating class: %d", rc); + goto err_class; + } + + for (i = 0; i < TSPP_NUM_CHANNELS; i++) { + if (tspp_channel_init(&channel_list[i]) != 0) { + pr_err("tspp_channel_init failed"); + break; + } + } + + return 0; + +err_class: + unregister_chrdev_region(0, TSPP_NUM_CHANNELS); +err_devrgn: + platform_driver_unregister(&msm_tspp_driver); +err_register: + return rc; +} + +static void __exit mod_exit(void) +{ + u32 i; + struct tspp_channel *channel; + + /* first delete upper layer interface */ + for (i = 0; i < TSPP_NUM_CHANNELS; i++) { + channel = &channel_list[i]; + device_destroy(tspp_class, channel->cdev.dev); + cdev_del(&channel->cdev); + } + class_destroy(tspp_class); + unregister_chrdev_region(0, TSPP_NUM_CHANNELS); + + /* now delete low level driver */ + platform_driver_unregister(&msm_tspp_driver); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_DESCRIPTION("TSPP character device interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/tzcom.c b/drivers/misc/tzcom.c new file mode 100644 index 0000000000000000000000000000000000000000..3b943c806918ba19a24133884a2dd6a874565bc8 --- /dev/null +++ b/drivers/misc/tzcom.c @@ -0,0 +1,1248 @@ +/* Qualcomm TrustZone communicator driver + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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) + +#undef PWARN +#define PWARN(fmt, args...) pr_warning("%s(%i, %s): " fmt "\n", \ + __func__, current->pid, current->comm, ## args) + + +static uint32_t tzcom_perf_client; +static struct class *driver_class; +static dev_t tzcom_device_no; +static struct cdev tzcom_cdev; +struct ion_client *ion_clnt; +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); +static DEFINE_MUTEX(tzcom_bw_mutex); +static int tzcom_bw_count; +static struct clk *tzcom_bus_clk; +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; + int abort; + wait_queue_head_t abort_wq; + atomic_t ioctl_count; +}; + +static int tzcom_enable_bus_scaling(void) +{ + int ret = 0; + if (!tzcom_perf_client) + return -EINVAL; + + if (IS_ERR_OR_NULL(tzcom_bus_clk)) + return -EINVAL; + + mutex_lock(&tzcom_bw_mutex); + if (!tzcom_bw_count) { + ret = msm_bus_scale_client_update_request( + tzcom_perf_client, 1); + if (ret) { + pr_err("Bandwidth request failed (%d)\n", ret); + } else { + ret = clk_enable(tzcom_bus_clk); + if (ret) + pr_err("Clock enable failed\n"); + } + } + if (ret) + msm_bus_scale_client_update_request(tzcom_perf_client, 0); + else + tzcom_bw_count++; + mutex_unlock(&tzcom_bw_mutex); + return ret; +} + +static void tzcom_disable_bus_scaling(void) +{ + if (!tzcom_perf_client) + return ; + + if (IS_ERR_OR_NULL(tzcom_bus_clk)) + return ; + + mutex_lock(&tzcom_bw_mutex); + if (tzcom_bw_count > 0) + if (tzcom_bw_count-- == 1) { + msm_bus_scale_client_update_request(tzcom_perf_client, + 0); + clk_disable(tzcom_bus_clk); + } + mutex_unlock(&tzcom_bw_mutex); +} + +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) { + PERR("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)) { + PERR("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) { + PERR("copy_to_user failed"); + return ret; + } + + new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + PERR("kmalloc failed"); + 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, *next; + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PERR("copy_from_user failed"); + return ret; + } + + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + list_for_each_entry_safe(ptr, next, &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; +} + +static int __tzcom_is_cont_cmd(struct tzcom_data_t *data) +{ + int ret; + ret = (data->cont_cmd_flag != 0); + return ret || data->abort; +} + +/** + * +---------+ +-----+ +-----------------+ + * | 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, + struct tzcom_send_cmd_op_req *req) +{ + int ret = 0; + unsigned long flags; + u32 reqd_len_sb_in = 0; + u32 reqd_len_sb_out = 0; + 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; + + if (req->cmd_buf == NULL || req->resp_buf == NULL) { + PERR("cmd buffer or response buffer is null"); + return -EINVAL; + } + + if (req->cmd_len <= 0 || req->resp_len <= 0 || + req->cmd_len > sb_in_length || req->resp_len > sb_in_length) { + PERR("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)); + + ret = tzcom_scm_call((const void *) &cmd, sizeof(cmd), + &resp, sizeof(resp)); + mutex_unlock(&sb_in_lock); + + if (ret) { + PERR("tzcom_scm_call failed with err: %d", ret); + return ret; + } + + 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 || + reqd_len_sb_out < sizeof(*next_callback) || + next_callback->sb_out_cb_data_len > sb_out_length) { + PERR("Incorrect callback data length" + " Required: %u, Available: %u, Min: %u", + reqd_len_sb_out, sb_out_length, + sizeof(*next_callback)); + 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 = sizeof(*cb); + + 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, + __tzcom_is_cont_cmd(data))) { + PWARN("Interrupted: exiting send_cmd loop"); + return -ERESTARTSYS; + } + + if (data->abort) { + PERR("Aborting driver"); + return -ENODEV; + } + data->cont_cmd_flag = 0; + cmd.cmd_type = TZ_SCHED_CMD_PENDING; + mutex_lock(&sb_in_lock); + ret = tzcom_scm_call((const void *) &cmd, sizeof(cmd), &resp, + sizeof(resp)); + mutex_unlock(&sb_in_lock); + if (ret) { + PERR("tzcom_scm_call failed with err: %d", ret); + return ret; + } + } + + 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; + memcpy(req->resp_buf, resp.sb_in_rsp_addr, resp.sb_in_rsp_len); + /* Zero out memory for security purpose */ + memset(sb_in_virt, 0, reqd_len_sb_in); + mutex_unlock(&sb_in_lock); + + return ret; +} + + +static int tzcom_send_cmd(struct tzcom_data_t *data, void __user *argp) +{ + int ret = 0; + struct tzcom_send_cmd_op_req req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PERR("copy_from_user failed"); + return ret; + } + ret = __tzcom_send_cmd(data, &req); + if (ret) + return ret; + + 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 int __tzcom_send_cmd_req_clean_up( + struct tzcom_send_cmd_fd_op_req *req) +{ + char *field; + uint32_t *update; + int ret = 0; + int i = 0; + + for (i = 0; i < MAX_ION_FD; i++) { + if (req->ifd_data[i].fd != 0) { + field = (char *)req->cmd_buf + + req->ifd_data[i].cmd_buf_offset; + update = (uint32_t *) field; + *update = 0; + } + } + return ret; +} + +static int __tzcom_update_with_phy_addr( + struct tzcom_send_cmd_fd_op_req *req) +{ + struct ion_handle *ihandle; + char *field; + uint32_t *update; + ion_phys_addr_t pa; + int ret = 0; + int i = 0; + uint32_t length; + + for (i = 0; i < MAX_ION_FD; i++) { + if (req->ifd_data[i].fd != 0) { + /* Get the handle of the shared fd */ + ihandle = ion_import_fd(ion_clnt, req->ifd_data[i].fd); + if (ihandle == NULL) { + PERR("Ion client can't retrieve the handle\n"); + return -ENOMEM; + } + field = (char *) req->cmd_buf + + req->ifd_data[i].cmd_buf_offset; + update = (uint32_t *) field; + + /* Populate the cmd data structure with the phys_addr */ + ret = ion_phys(ion_clnt, ihandle, &pa, &length); + if (ret) + return -ENOMEM; + + *update = (uint32_t)pa; + ion_free(ion_clnt, ihandle); + } + } + return ret; +} + +static int tzcom_send_cmd_with_fd(struct tzcom_data_t *data, + void __user *argp) +{ + int ret = 0; + struct tzcom_send_cmd_fd_op_req req; + struct tzcom_send_cmd_op_req send_cmd_req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PERR("copy_from_user failed"); + return ret; + } + + send_cmd_req.cmd_id = req.cmd_id; + send_cmd_req.cmd_buf = req.cmd_buf; + send_cmd_req.cmd_len = req.cmd_len; + send_cmd_req.resp_buf = req.resp_buf; + send_cmd_req.resp_len = req.resp_len; + + ret = __tzcom_update_with_phy_addr(&req); + if (ret) + return ret; + ret = __tzcom_send_cmd(data, &send_cmd_req); + __tzcom_send_cmd_req_clean_up(&req); + + if (ret) + return ret; + + 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, *next; + 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_safe(entry, next, &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) { + PERR("copy_to_user failed"); + break; + } + list_del(&entry->list); + kfree(entry); + ret = 0; + } else { + PERR("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_is_next_cmd(struct tzcom_data_t *data, + struct tzcom_registered_svc_list *svc) +{ + int ret; + ret = (svc->next_cmd_flag != 0); + return ret || data->abort; +} + +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) { + PERR("copy_from_user failed"); + return ret; + } + + if (req.instance_id > atomic_read(&svc_instance_ctr)) { + PERR("Invalid instance_id for the request"); + return -EINVAL; + } + + if (!req.req_buf || req.req_len == 0) { + PERR("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, + __tzcom_is_next_cmd(data, this_svc))) { + PWARN("Interrupted: exiting wait_next_cmd loop"); + /* woken up for different reason */ + return -ERESTARTSYS; + } + + if (data->abort) { + PERR("Aborting driver"); + return -ENODEV; + } + 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) { + PERR("Not enough memory"); + return ret; + } + } + ret = copy_to_user(argp, &req, sizeof(req)); + if (ret) { + PERR("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) { + PERR("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) { + PWARN("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 int tzcom_abort(struct tzcom_data_t *data) +{ + int ret = 0; + unsigned long flags; + struct tzcom_registered_svc_list *lsvc, *nsvc; + if (data->abort) { + PERR("Already aborting"); + return -EINVAL; + } + + data->abort = 1; + + PDEBUG("Waking up cont_cmd_wq"); + wake_up_all(&data->cont_cmd_wq); + + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + PDEBUG("Before waking up service wait queues"); + list_for_each_entry_safe(lsvc, nsvc, + &data->registered_svc_list_head, list) { + wake_up_all(&lsvc->next_cmd_wq); + } + spin_unlock_irqrestore(&data->registered_svc_list_lock, flags); + + PDEBUG("ioctl_count before loop: %d", atomic_read(&data->ioctl_count)); + while (atomic_read(&data->ioctl_count) > 0) { + if (wait_event_interruptible(data->abort_wq, + atomic_read(&data->ioctl_count) <= 0)) { + PERR("Interrupted from abort"); + ret = -ERESTARTSYS; + break; + } + } + 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()"); + if (tzcom_data->abort) { + PERR("Aborting tzcom driver"); + return -ENODEV; + } + + switch (cmd) { + case TZCOM_IOCTL_REGISTER_SERVICE_REQ: { + PDEBUG("ioctl register_service_req()"); + atomic_inc(&tzcom_data->ioctl_count); + ret = tzcom_register_service(tzcom_data, argp); + atomic_dec(&tzcom_data->ioctl_count); + wake_up_interruptible(&tzcom_data->abort_wq); + if (ret) + PERR("failed tzcom_register_service: %d", ret); + break; + } + case TZCOM_IOCTL_UNREGISTER_SERVICE_REQ: { + PDEBUG("ioctl unregister_service_req()"); + atomic_inc(&tzcom_data->ioctl_count); + ret = tzcom_unregister_service(tzcom_data, argp); + atomic_dec(&tzcom_data->ioctl_count); + wake_up_interruptible(&tzcom_data->abort_wq); + if (ret) + PERR("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); + atomic_inc(&tzcom_data->ioctl_count); + ret = tzcom_send_cmd(tzcom_data, argp); + atomic_dec(&tzcom_data->ioctl_count); + wake_up_interruptible(&tzcom_data->abort_wq); + mutex_unlock(&send_cmd_lock); + if (ret) + PERR("failed tzcom_send_cmd: %d", ret); + break; + } + case TZCOM_IOCTL_SEND_CMD_FD_REQ: { + PDEBUG("ioctl send_cmd_req()"); + /* Only one client allowed here at a time */ + mutex_lock(&send_cmd_lock); + atomic_inc(&tzcom_data->ioctl_count); + ret = tzcom_send_cmd_with_fd(tzcom_data, argp); + atomic_dec(&tzcom_data->ioctl_count); + wake_up_interruptible(&tzcom_data->abort_wq); + mutex_unlock(&send_cmd_lock); + if (ret) + PERR("failed tzcom_send_cmd: %d", ret); + break; + } + case TZCOM_IOCTL_READ_NEXT_CMD_REQ: { + PDEBUG("ioctl read_next_cmd_req()"); + atomic_inc(&tzcom_data->ioctl_count); + ret = tzcom_read_next_cmd(tzcom_data, argp); + atomic_dec(&tzcom_data->ioctl_count); + wake_up_interruptible(&tzcom_data->abort_wq); + if (ret) + PERR("failed tzcom_read_next: %d", ret); + break; + } + case TZCOM_IOCTL_CONTINUE_CMD_REQ: { + PDEBUG("ioctl continue_cmd_req()"); + atomic_inc(&tzcom_data->ioctl_count); + ret = tzcom_cont_cmd(tzcom_data, argp); + atomic_dec(&tzcom_data->ioctl_count); + wake_up_interruptible(&tzcom_data->abort_wq); + if (ret) + PERR("failed tzcom_cont_cmd: %d", ret); + break; + } + case TZCOM_IOCTL_ABORT_REQ: { + PDEBUG("ioctl abort_req()"); + ret = tzcom_abort(tzcom_data); + if (ret) + PERR("failed tzcom_abort: %d", ret); + break; + } + default: + return -EINVAL; + } + return ret; +} + +static int tzcom_open(struct inode *inode, struct file *file) +{ + int ret; + 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"); + + ret = tzcom_enable_bus_scaling(); + + if (pil == NULL) { + pil = pil_get("tzapps"); + if (IS_ERR(pil)) { + PERR("Playready PIL image load failed"); + pil_error = PTR_ERR(pil); + pil = NULL; + return pil_error; + } + PDEBUG("tzapps 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 = TZ_SCHED_STATUS_INCOMPLETE; + + PDEBUG("Before scm_call for sb_init"); + ret = tzcom_scm_call(&cmd, sizeof(cmd), &resp, sizeof(resp)); + if (ret) { + PERR("tzcom_scm_call failed with err: %d", ret); + return ret; + } + PDEBUG("After scm_call for sb_init"); + + PDEBUG("tzcom_response after scm cmd_status: %u", resp.cmd_status); + if (resp.cmd_status == TZ_SCHED_STATUS_COMPLETE) { + 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 sb_in_rsp_addr: %p, sb_in_rsp_len: %u", + resp.sb_in_rsp_addr, resp.sb_in_rsp_len); + 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; + tzcom_data->abort = 0; + init_waitqueue_head(&tzcom_data->abort_wq); + atomic_set(&tzcom_data->ioctl_count, 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; + unsigned long flags; + PDEBUG("In here"); + + if (!tzcom_data->abort) { + PDEBUG("Calling abort"); + tzcom_abort(tzcom_data); + } + + PDEBUG("Before removing callback list"); + mutex_lock(&tzcom_data->callback_list_lock); + list_for_each_entry_safe(lcb, ncb, + &tzcom_data->callback_list_head, list) { + list_del(&lcb->list); + kfree(lcb); + } + mutex_unlock(&tzcom_data->callback_list_lock); + PDEBUG("After removing callback list"); + + PDEBUG("Before removing svc list"); + spin_lock_irqsave(&tzcom_data->registered_svc_list_lock, flags); + list_for_each_entry_safe(lsvc, nsvc, + &tzcom_data->registered_svc_list_head, list) { + list_del(&lsvc->list); + kfree(lsvc); + } + spin_unlock_irqrestore(&tzcom_data->registered_svc_list_lock, flags); + PDEBUG("After removing svc list"); + if (pil != NULL) { + pil_put(pil); + pil = NULL; + } + PDEBUG("Freeing tzcom data"); + kfree(tzcom_data); + tzcom_disable_bus_scaling(); + return 0; +} + +static struct msm_bus_paths tzcom_bw_table[] = { + { + .vectors = (struct msm_bus_vectors[]){ + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + }, + }, + .num_paths = 1, + }, + { + .vectors = (struct msm_bus_vectors[]){ + { + .src = MSM_BUS_MASTER_SPS, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ib = (492 * 8) * 1000000UL, + .ab = (492 * 8) * 100000UL, + }, + }, + .num_paths = 1, + }, + +}; + +static struct msm_bus_scale_pdata tzcom_bus_pdata = { + .usecase = tzcom_bw_table, + .num_usecases = ARRAY_SIZE(tzcom_bw_table), + .name = "tzcom", +}; +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"); + sb_in_phys = 0; + 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"); + sb_out_phys = 0; + 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)); + ion_clnt = msm_ion_client_create(0x03, "tzcom"); + /* Initialized in tzcom_open */ + pil = NULL; + + tzcom_perf_client = msm_bus_scale_register_client( + &tzcom_bus_pdata); + if (!tzcom_perf_client) + pr_err("Unable to register bus client"); + + tzcom_bus_clk = clk_get(class_dev, "bus_clk"); + if (IS_ERR(tzcom_bus_clk)) { + tzcom_bus_clk = NULL; + } else if (tzcom_bus_clk != NULL) { + pr_debug("Enabled DFAB clock\n"); + clk_set_rate(tzcom_bus_clk, 64000000); + } + 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(pil); + pil = NULL; + } + clk_put(tzcom_bus_clk); + device_destroy(driver_class, tzcom_device_no); + class_destroy(driver_class); + unregister_chrdev_region(tzcom_device_no, 1); + ion_client_destroy(ion_clnt); +} + + +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 0000000000000000000000000000000000000000..33634cf83d42d98fafc4fd0fe9a08b03d1306692 --- /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 f2eeb38efa653387b42cb8bf9ccc0ba4e0f17969..91165514156ea9bd75f1a409a2c08dfcec62d1ad 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 54c83224698e7cb4c447978b044fa94cdfcefb63..990faebf2e41dcb3399710bf175f143fbaa476e0 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -58,6 +58,18 @@ MODULE_ALIAS("mmc:block"); #define INAND_CMD38_ARG_SECTRIM1 0x81 #define INAND_CMD38_ARG_SECTRIM2 0x88 +#define MMC_SANITIZE_REQ_TIMEOUT 240000 /* msec */ +#define mmc_req_rel_wr(req) (((req->cmd_flags & REQ_FUA) || \ + (req->cmd_flags & REQ_META)) && \ + (rq_data_dir(req) == WRITE)) +#define PACKED_CMD_VER 0x01 +#define PACKED_CMD_WR 0x02 +#define MMC_BLK_UPDATE_STOP_REASON(stats, reason) \ + do { \ + if (stats->enabled) \ + stats->pack_stop_reason[reason]++; \ + } while (0) + static DEFINE_MUTEX(block_mutex); /* @@ -108,6 +120,7 @@ struct mmc_blk_data { struct device_attribute force_ro; struct device_attribute power_ro_lock; int area_type; + struct device_attribute num_wr_reqs_to_start_packing; }; static DEFINE_MUTEX(open_lock); @@ -123,9 +136,21 @@ enum mmc_blk_status { MMC_BLK_NOMEDIUM, }; +enum { + MMC_PACKED_N_IDX = -1, + MMC_PACKED_N_ZERO, + MMC_PACKED_N_SINGLE, +}; + module_param(perdev_minors, int, 0444); MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); +static inline void mmc_blk_clear_packed(struct mmc_queue_req *mqrq) +{ + mqrq->packed_cmd = MMC_PACKED_NONE; + mqrq->packed_num = MMC_PACKED_N_ZERO; +} + static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) { struct mmc_blk_data *md; @@ -259,6 +284,38 @@ static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, return ret; } +static ssize_t +num_wr_reqs_to_start_packing_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int num_wr_reqs_to_start_packing; + int ret; + + num_wr_reqs_to_start_packing = md->queue.num_wr_reqs_to_start_packing; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", num_wr_reqs_to_start_packing); + + mmc_blk_put(md); + return ret; +} + +static ssize_t +num_wr_reqs_to_start_packing_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int value; + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + + sscanf(buf, "%d", &value); + if (value >= 0) + md->queue.num_wr_reqs_to_start_packing = value; + + mmc_blk_put(md); + return count; +} + static int mmc_blk_open(struct block_device *bdev, fmode_t mode) { struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk); @@ -873,10 +930,10 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, { struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; - unsigned int from, nr, arg, trim_arg, erase_arg; + unsigned int from, nr, arg; int err = 0, type = MMC_BLK_SECDISCARD; - if (!(mmc_can_secure_erase_trim(card) || mmc_can_sanitize(card))) { + if (!(mmc_can_secure_erase_trim(card))) { err = -EOPNOTSUPP; goto out; } @@ -884,23 +941,10 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, from = blk_rq_pos(req); nr = blk_rq_sectors(req); - /* The sanitize operation is supported at v4.5 only */ - if (mmc_can_sanitize(card)) { - erase_arg = MMC_ERASE_ARG; - trim_arg = MMC_TRIM_ARG; - } else { - erase_arg = MMC_SECURE_ERASE_ARG; - trim_arg = MMC_SECURE_TRIM1_ARG; - } - - if (mmc_erase_group_aligned(card, from, nr)) - arg = erase_arg; - else if (mmc_can_trim(card)) - arg = trim_arg; - else { - err = -EINVAL; - goto out; - } + if (mmc_can_trim(card) && !mmc_erase_group_aligned(card, from, nr)) + arg = MMC_SECURE_TRIM1_ARG; + else + arg = MMC_SECURE_ERASE_ARG; retry: if (card->quirks & MMC_QUIRK_INAND_CMD38) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, @@ -952,6 +996,47 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, return err ? 0 : 1; } +static int mmc_blk_issue_sanitize_rq(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + int err = 0; + + BUG_ON(!card); + BUG_ON(!card->host); + + if (!(mmc_can_sanitize(card) && + (card->host->caps2 & MMC_CAP2_SANITIZE))) { + pr_warning("%s: %s - SANITIZE is not supported\n", + mmc_hostname(card->host), __func__); + err = -EOPNOTSUPP; + goto out; + } + + pr_debug("%s: %s - SANITIZE IN PROGRESS...\n", + mmc_hostname(card->host), __func__); + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_SANITIZE_START, 1, + MMC_SANITIZE_REQ_TIMEOUT); + + if (err) + pr_err("%s: %s - mmc_switch() with " + "EXT_CSD_SANITIZE_START failed. err=%d\n", + mmc_hostname(card->host), __func__, err); + + pr_debug("%s: %s - SANITIZE COMPLETED\n", mmc_hostname(card->host), + __func__); + +out: + spin_lock_irq(&md->lock); + __blk_end_request(req, err, blk_rq_bytes(req)); + spin_unlock_irq(&md->lock); + + return err ? 0 : 1; +} + static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) { struct mmc_blk_data *md = mq->data; @@ -1086,12 +1171,60 @@ static int mmc_blk_err_check(struct mmc_card *card, if (!brq->data.bytes_xfered) return MMC_BLK_RETRY; + if (mq_mrq->packed_cmd != MMC_PACKED_NONE) { + if (unlikely(brq->data.blocks << 9 != brq->data.bytes_xfered)) + return MMC_BLK_PARTIAL; + else + return MMC_BLK_SUCCESS; + } + if (blk_rq_bytes(req) != brq->data.bytes_xfered) return MMC_BLK_PARTIAL; return MMC_BLK_SUCCESS; } +static int mmc_blk_packed_err_check(struct mmc_card *card, + struct mmc_async_req *areq) +{ + struct mmc_queue_req *mq_rq = container_of(areq, struct mmc_queue_req, + mmc_active); + struct request *req = mq_rq->req; + int err, check, status; + u8 ext_csd[512]; + + check = mmc_blk_err_check(card, areq); + err = get_card_status(card, &status, 0); + if (err) { + pr_err("%s: error %d sending status command\n", + req->rq_disk->disk_name, err); + return MMC_BLK_ABORT; + } + + if (status & R1_EXP_EVENT) { + err = mmc_send_ext_csd(card, ext_csd); + if (err) { + pr_err("%s: error %d sending ext_csd\n", + req->rq_disk->disk_name, err); + return MMC_BLK_ABORT; + } + + if ((ext_csd[EXT_CSD_EXP_EVENTS_STATUS] & + EXT_CSD_PACKED_FAILURE) && + (ext_csd[EXT_CSD_PACKED_CMD_STATUS] & + EXT_CSD_PACKED_GENERIC_ERROR)) { + if (ext_csd[EXT_CSD_PACKED_CMD_STATUS] & + EXT_CSD_PACKED_INDEXED_ERROR) { + mq_rq->packed_fail_idx = + ext_csd[EXT_CSD_PACKED_FAILURE_INDEX] - 1; + return MMC_BLK_PARTIAL; + } + } + } + + return check; +} + static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, struct mmc_card *card, int disable_multi, @@ -1246,10 +1379,286 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, mmc_queue_bounce_pre(mqrq); } +static void mmc_blk_write_packing_control(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_host *host = mq->card->host; + int data_dir; + + if (!(host->caps2 & MMC_CAP2_PACKED_WR)) + return; + + /* + * In case the packing control is not supported by the host, it should + * not have an effect on the write packing. Therefore we have to enable + * the write packing + */ + if (!(host->caps2 & MMC_CAP2_PACKED_WR_CONTROL)) { + mq->wr_packing_enabled = true; + return; + } + + if (!req || (req && (req->cmd_flags & REQ_FLUSH))) { + if (mq->num_of_potential_packed_wr_reqs > + mq->num_wr_reqs_to_start_packing) + mq->wr_packing_enabled = true; + return; + } + + data_dir = rq_data_dir(req); + + if (data_dir == READ) { + mq->num_of_potential_packed_wr_reqs = 0; + mq->wr_packing_enabled = false; + return; + } else if (data_dir == WRITE) { + mq->num_of_potential_packed_wr_reqs++; + } + + if (mq->num_of_potential_packed_wr_reqs > + mq->num_wr_reqs_to_start_packing) + mq->wr_packing_enabled = true; + +} + +struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics(struct mmc_card *card) +{ + if (!card) + return NULL; + + return &card->wr_pack_stats; +} +EXPORT_SYMBOL(mmc_blk_get_packed_statistics); + +void mmc_blk_init_packed_statistics(struct mmc_card *card) +{ + int max_num_of_packed_reqs = 0; + + if (!card || !card->wr_pack_stats.packing_events) + return; + + max_num_of_packed_reqs = card->ext_csd.max_packed_writes; + + spin_lock(&card->wr_pack_stats.lock); + memset(card->wr_pack_stats.packing_events, 0, + (max_num_of_packed_reqs + 1) * + sizeof(*card->wr_pack_stats.packing_events)); + memset(&card->wr_pack_stats.pack_stop_reason, 0, + sizeof(card->wr_pack_stats.pack_stop_reason)); + card->wr_pack_stats.enabled = true; + spin_unlock(&card->wr_pack_stats.lock); +} +EXPORT_SYMBOL(mmc_blk_init_packed_statistics); + +static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) +{ + struct request_queue *q = mq->queue; + struct mmc_card *card = mq->card; + struct request *cur = req, *next = NULL; + struct mmc_blk_data *md = mq->data; + bool en_rel_wr = card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN; + unsigned int req_sectors = 0, phys_segments = 0; + unsigned int max_blk_count, max_phys_segs; + u8 put_back = 0; + u8 max_packed_rw = 0; + u8 reqs = 0; + struct mmc_wr_pack_stats *stats = &card->wr_pack_stats; + + mmc_blk_clear_packed(mq->mqrq_cur); + + if (!(md->flags & MMC_BLK_CMD23) || + !card->ext_csd.packed_event_en) + goto no_packed; + + if (!mq->wr_packing_enabled) + goto no_packed; + + if ((rq_data_dir(cur) == WRITE) && + (card->host->caps2 & MMC_CAP2_PACKED_WR)) + max_packed_rw = card->ext_csd.max_packed_writes; + + if (max_packed_rw == 0) + goto no_packed; + + if (mmc_req_rel_wr(cur) && + (md->flags & MMC_BLK_REL_WR) && + !en_rel_wr) { + goto no_packed; + } + + max_blk_count = min(card->host->max_blk_count, + card->host->max_req_size >> 9); + if (unlikely(max_blk_count > 0xffff)) + max_blk_count = 0xffff; + + max_phys_segs = queue_max_segments(q); + req_sectors += blk_rq_sectors(cur); + phys_segments += cur->nr_phys_segments; + + if (rq_data_dir(cur) == WRITE) { + req_sectors++; + phys_segments++; + } + + spin_lock(&stats->lock); + + while (reqs < max_packed_rw - 1) { + spin_lock_irq(q->queue_lock); + next = blk_fetch_request(q); + spin_unlock_irq(q->queue_lock); + if (!next) { + MMC_BLK_UPDATE_STOP_REASON(stats, EMPTY_QUEUE); + break; + } + + if (next->cmd_flags & REQ_DISCARD || + next->cmd_flags & REQ_FLUSH) { + MMC_BLK_UPDATE_STOP_REASON(stats, FLUSH_OR_DISCARD); + put_back = 1; + break; + } + + if (rq_data_dir(cur) != rq_data_dir(next)) { + MMC_BLK_UPDATE_STOP_REASON(stats, WRONG_DATA_DIR); + put_back = 1; + break; + } + + if (mmc_req_rel_wr(next) && + (md->flags & MMC_BLK_REL_WR) && + !en_rel_wr) { + MMC_BLK_UPDATE_STOP_REASON(stats, REL_WRITE); + put_back = 1; + break; + } + + req_sectors += blk_rq_sectors(next); + if (req_sectors > max_blk_count) { + if (stats->enabled) + stats->pack_stop_reason[EXCEEDS_SECTORS]++; + put_back = 1; + break; + } + + phys_segments += next->nr_phys_segments; + if (phys_segments > max_phys_segs) { + MMC_BLK_UPDATE_STOP_REASON(stats, EXCEEDS_SEGMENTS); + put_back = 1; + break; + } + + if (rq_data_dir(next) == WRITE) + mq->num_of_potential_packed_wr_reqs++; + list_add_tail(&next->queuelist, &mq->mqrq_cur->packed_list); + cur = next; + reqs++; + } + + if (put_back) { + spin_lock_irq(q->queue_lock); + blk_requeue_request(q, next); + spin_unlock_irq(q->queue_lock); + } + + if (stats->enabled) { + if (reqs + 1 <= card->ext_csd.max_packed_writes) + stats->packing_events[reqs + 1]++; + if (reqs + 1 == max_packed_rw) + MMC_BLK_UPDATE_STOP_REASON(stats, THRESHOLD); + } + + spin_unlock(&stats->lock); + + if (reqs > 0) { + list_add(&req->queuelist, &mq->mqrq_cur->packed_list); + mq->mqrq_cur->packed_num = ++reqs; + return reqs; + } + +no_packed: + mmc_blk_clear_packed(mq->mqrq_cur); + return 0; +} + +static void mmc_blk_packed_hdr_wrq_prep(struct mmc_queue_req *mqrq, + struct mmc_card *card, + struct mmc_queue *mq) +{ + struct mmc_blk_request *brq = &mqrq->brq; + struct request *req = mqrq->req; + struct request *prq; + struct mmc_blk_data *md = mq->data; + bool do_rel_wr; + u32 *packed_cmd_hdr = mqrq->packed_cmd_hdr; + u8 i = 1; + + mqrq->packed_cmd = MMC_PACKED_WRITE; + mqrq->packed_blocks = 0; + mqrq->packed_fail_idx = MMC_PACKED_N_IDX; + + memset(packed_cmd_hdr, 0, sizeof(mqrq->packed_cmd_hdr)); + packed_cmd_hdr[0] = (mqrq->packed_num << 16) | + (PACKED_CMD_WR << 8) | PACKED_CMD_VER; + + /* + * Argument for each entry of packed group + */ + list_for_each_entry(prq, &mqrq->packed_list, queuelist) { + do_rel_wr = mmc_req_rel_wr(prq) && (md->flags & MMC_BLK_REL_WR); + /* Argument of CMD23*/ + packed_cmd_hdr[(i * 2)] = + (do_rel_wr ? MMC_CMD23_ARG_REL_WR : 0) | + blk_rq_sectors(prq); + /* Argument of CMD18 or CMD25 */ + packed_cmd_hdr[((i * 2)) + 1] = + mmc_card_blockaddr(card) ? + blk_rq_pos(prq) : blk_rq_pos(prq) << 9; + mqrq->packed_blocks += blk_rq_sectors(prq); + i++; + } + + memset(brq, 0, sizeof(struct mmc_blk_request)); + brq->mrq.cmd = &brq->cmd; + brq->mrq.data = &brq->data; + brq->mrq.sbc = &brq->sbc; + brq->mrq.stop = &brq->stop; + + brq->sbc.opcode = MMC_SET_BLOCK_COUNT; + brq->sbc.arg = MMC_CMD23_ARG_PACKED | (mqrq->packed_blocks + 1); + brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC; + + brq->cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; + brq->cmd.arg = blk_rq_pos(req); + if (!mmc_card_blockaddr(card)) + brq->cmd.arg <<= 9; + brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + + brq->data.blksz = 512; + brq->data.blocks = mqrq->packed_blocks + 1; + brq->data.flags |= MMC_DATA_WRITE; + + brq->stop.opcode = MMC_STOP_TRANSMISSION; + brq->stop.arg = 0; + brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + + mmc_set_data_timeout(&brq->data, card); + + brq->data.sg = mqrq->sg; + brq->data.sg_len = mmc_queue_map_sg(mq, mqrq); + + mqrq->mmc_active.mrq = &brq->mrq; + mqrq->mmc_active.err_check = mmc_blk_packed_err_check; + + mmc_queue_bounce_pre(mqrq); +} + static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, struct mmc_blk_request *brq, struct request *req, int ret) { + struct mmc_queue_req *mq_rq; + mq_rq = container_of(brq, struct mmc_queue_req, brq); + /* * If this is an SD card and we're writing, we can first * mark the known good sectors as ok. @@ -1268,10 +1677,45 @@ static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, spin_unlock_irq(&md->lock); } } else { + if (mq_rq->packed_cmd == MMC_PACKED_NONE) { + spin_lock_irq(&md->lock); + ret = __blk_end_request(req, 0, brq->data.bytes_xfered); + spin_unlock_irq(&md->lock); + } + } + return ret; +} + +static int mmc_blk_end_packed_req(struct mmc_queue *mq, + struct mmc_queue_req *mq_rq) +{ + struct mmc_blk_data *md = mq->data; + struct request *prq; + int idx = mq_rq->packed_fail_idx, i = 0; + int ret = 0; + + while (!list_empty(&mq_rq->packed_list)) { + prq = list_entry_rq(mq_rq->packed_list.next); + if (idx == i) { + /* retry from error index */ + mq_rq->packed_num -= idx; + mq_rq->req = prq; + ret = 1; + + if (mq_rq->packed_num == MMC_PACKED_N_SINGLE) { + list_del_init(&prq->queuelist); + mmc_blk_clear_packed(mq_rq); + } + return ret; + } + list_del_init(&prq->queuelist); spin_lock_irq(&md->lock); - ret = __blk_end_request(req, 0, brq->data.bytes_xfered); + __blk_end_request(prq, 0, blk_rq_bytes(prq)); spin_unlock_irq(&md->lock); + i++; } + + mmc_blk_clear_packed(mq_rq); return ret; } @@ -1283,15 +1727,24 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) int ret = 1, disable_multi = 0, retry = 0, type; enum mmc_blk_status status; struct mmc_queue_req *mq_rq; - struct request *req; + struct request *req, *prq; struct mmc_async_req *areq; + const u8 packed_num = 2; + u8 reqs = 0; if (!rqc && !mq->mqrq_prev->req) return 0; + if (rqc) + reqs = mmc_blk_prep_packed_list(mq, rqc); + do { if (rqc) { - mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); + if (reqs >= packed_num) + mmc_blk_packed_hdr_wrq_prep(mq->mqrq_cur, + card, mq); + else + mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); areq = &mq->mqrq_cur->mmc_active; } else areq = NULL; @@ -1305,6 +1758,13 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) type = rq_data_dir(req) == READ ? MMC_BLK_READ : MMC_BLK_WRITE; mmc_queue_bounce_post(mq_rq); + /* + * Check BKOPS urgency from each R1 response + */ + if (mmc_card_mmc(card) && + (brq->cmd.resp[0] & R1_EXCEPTION_EVENT)) + mmc_card_set_check_bkops(card); + switch (status) { case MMC_BLK_SUCCESS: case MMC_BLK_PARTIAL: @@ -1312,10 +1772,17 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) * A block was successfully transferred. */ mmc_blk_reset_success(md, type); - spin_lock_irq(&md->lock); - ret = __blk_end_request(req, 0, + + if (mq_rq->packed_cmd != MMC_PACKED_NONE) { + ret = mmc_blk_end_packed_req(mq, mq_rq); + break; + } else { + spin_lock_irq(&md->lock); + ret = __blk_end_request(req, 0, brq->data.bytes_xfered); - spin_unlock_irq(&md->lock); + spin_unlock_irq(&md->lock); + } + /* * If the blk_end_request function returns non-zero even * though all data has been transferred and no errors @@ -1348,7 +1815,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) err = mmc_blk_reset(md, card->host, type); if (!err) break; - if (err == -ENODEV) + if (err == -ENODEV || + mq_rq->packed_cmd != MMC_PACKED_NONE) goto cmd_abort; /* Fall through */ } @@ -1377,27 +1845,66 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) } if (ret) { - /* - * In case of a incomplete request - * prepare it again and resend. - */ - mmc_blk_rw_rq_prep(mq_rq, card, disable_multi, mq); - mmc_start_req(card->host, &mq_rq->mmc_active, NULL); + if (mq_rq->packed_cmd == MMC_PACKED_NONE) { + /* + * In case of a incomplete request + * prepare it again and resend. + */ + mmc_blk_rw_rq_prep(mq_rq, card, + disable_multi, mq); + mmc_start_req(card->host, + &mq_rq->mmc_active, NULL); + } else { + mmc_blk_packed_hdr_wrq_prep(mq_rq, card, mq); + mmc_start_req(card->host, + &mq_rq->mmc_active, NULL); + } } } while (ret); return 1; cmd_abort: - spin_lock_irq(&md->lock); - if (mmc_card_removed(card)) - req->cmd_flags |= REQ_QUIET; - while (ret) - ret = __blk_end_request(req, -EIO, blk_rq_cur_bytes(req)); - spin_unlock_irq(&md->lock); + if (mq_rq->packed_cmd == MMC_PACKED_NONE) { + spin_lock_irq(&md->lock); + if (mmc_card_removed(card)) + req->cmd_flags |= REQ_QUIET; + while (ret) + ret = __blk_end_request(req, -EIO, + blk_rq_cur_bytes(req)); + spin_unlock_irq(&md->lock); + } else { + while (!list_empty(&mq_rq->packed_list)) { + prq = list_entry_rq(mq_rq->packed_list.next); + list_del_init(&prq->queuelist); + spin_lock_irq(&md->lock); + __blk_end_request(prq, -EIO, blk_rq_bytes(prq)); + spin_unlock_irq(&md->lock); + } + mmc_blk_clear_packed(mq_rq); + } start_new_req: if (rqc) { + /* + * If current request is packed, it needs to put back. + */ + if (mq->mqrq_cur->packed_cmd != MMC_PACKED_NONE) { + while (!list_empty(&mq->mqrq_cur->packed_list)) { + prq = list_entry_rq( + mq->mqrq_cur->packed_list.prev); + if (prq->queuelist.prev != + &mq->mqrq_cur->packed_list) { + list_del_init(&prq->queuelist); + spin_lock_irq(mq->queue->queue_lock); + blk_requeue_request(mq->queue, prq); + spin_unlock_irq(mq->queue->queue_lock); + } else { + list_del_init(&prq->queuelist); + } + } + mmc_blk_clear_packed(mq->mqrq_cur); + } mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); mmc_start_req(card->host, &mq->mqrq_cur->mmc_active, NULL); } @@ -1405,9 +1912,6 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) return 0; } -static int -mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card); - static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) { int ret; @@ -1436,7 +1940,14 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) goto out; } - if (req && req->cmd_flags & REQ_DISCARD) { + mmc_blk_write_packing_control(mq, req); + + if (req && req->cmd_flags & REQ_SANITIZE) { + /* complete ongoing async transfer before issuing sanitize */ + if (card->host && card->host->areq) + mmc_blk_issue_rw_rq(mq, NULL); + ret = mmc_blk_issue_sanitize_rq(mq, req); + } else if (req && req->cmd_flags & REQ_DISCARD) { /* complete ongoing async transfer before issuing discard */ if (card->host->areq) mmc_blk_issue_rw_rq(mq, NULL); @@ -1662,6 +2173,8 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md) if (md) { card = md->queue.card; + device_remove_file(disk_to_dev(md->disk), + &md->num_wr_reqs_to_start_packing); if (md->disk->flags & GENHD_FL_UP) { device_remove_file(disk_to_dev(md->disk), &md->force_ro); if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && @@ -1728,12 +2241,26 @@ static int mmc_add_disk(struct mmc_blk_data *md) if (ret) goto power_ro_lock_fail; } + + md->num_wr_reqs_to_start_packing.show = + num_wr_reqs_to_start_packing_show; + md->num_wr_reqs_to_start_packing.store = + num_wr_reqs_to_start_packing_store; + sysfs_attr_init(&md->num_wr_reqs_to_start_packing.attr); + md->num_wr_reqs_to_start_packing.attr.name = + "num_wr_reqs_to_start_packing"; + md->num_wr_reqs_to_start_packing.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(disk_to_dev(md->disk), + &md->num_wr_reqs_to_start_packing); + if (ret) + goto power_ro_lock_fail; + return ret; power_ro_lock_fail: - device_remove_file(disk_to_dev(md->disk), &md->force_ro); + device_remove_file(disk_to_dev(md->disk), &md->force_ro); force_ro_fail: - del_gendisk(md->disk); + del_gendisk(md->disk); return ret; } @@ -1777,6 +2304,10 @@ static const struct mmc_fixup blk_fixups[] = MMC_FIXUP(CID_NAME_ANY, CID_MANFID_MICRON, 0x200, add_quirk_mmc, MMC_QUIRK_LONG_READ_TIME), + /* Some INAND MCP devices advertise incorrect timeout values */ + MMC_FIXUP("SEM04G", 0x45, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_INAND_DATA_TIMEOUT), + END_FIXUP }; diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c index 759714ed6bee8f791b2cbd4a5959f2ca12d7e9ec..58efd5e64d9c390dd9aa8053cf93dc349254e56a 100644 --- a/drivers/mmc/card/mmc_test.c +++ b/drivers/mmc/card/mmc_test.c @@ -2890,7 +2890,8 @@ static ssize_t mtf_test_write(struct file *file, const char __user *buf, } #ifdef CONFIG_HIGHMEM - __free_pages(test->highmem, BUFFER_ORDER); + if (test->highmem) + __free_pages(test->highmem, BUFFER_ORDER); #endif kfree(test->buffer); kfree(test); diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 996f8e36e23d8aa8bbf7c333fd6d6d56b8ec79ad..ebcf7ed62c07ec31d5e7a8a3c3501ea947c27efb 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -24,6 +24,13 @@ #define MMC_QUEUE_SUSPENDED (1 << 0) +/* + * Based on benchmark tests the default num of requests to trigger the write + * packing was determined, to keep the read latency as low as possible and + * manage to keep the high write throughput. + */ +#define DEFAULT_NUM_REQS_TO_START_PACK 17 + /* * Prepare a MMC request. This just filters out odd stuff. */ @@ -51,13 +58,14 @@ static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; struct request_queue *q = mq->queue; + struct request *req; current->flags |= PF_MEMALLOC; down(&mq->thread_sem); do { - struct request *req = NULL; struct mmc_queue_req *tmp; + req = NULL; /* Must be set to NULL at each iteration */ spin_lock_irq(q->queue_lock); set_current_state(TASK_INTERRUPTIBLE); @@ -66,6 +74,9 @@ static int mmc_queue_thread(void *d) spin_unlock_irq(q->queue_lock); if (req || mq->mqrq_prev->req) { + if (mmc_card_doing_bkops(mq->card)) + mmc_interrupt_bkops(mq->card); + set_current_state(TASK_RUNNING); mq->issue_fn(mq, req); } else { @@ -73,6 +84,8 @@ static int mmc_queue_thread(void *d) set_current_state(TASK_RUNNING); break; } + + mmc_start_bkops(mq->card); up(&mq->thread_sem); schedule(); down(&mq->thread_sem); @@ -145,10 +158,15 @@ static void mmc_queue_setup_discard(struct request_queue *q, /* granularity must not be greater than max. discard */ if (card->pref_erase > max_discard) q->limits.discard_granularity = 0; - if (mmc_can_secure_erase_trim(card) || mmc_can_sanitize(card)) + if (mmc_can_secure_erase_trim(card)) queue_flag_set_unlocked(QUEUE_FLAG_SECDISCARD, q); } +static void mmc_queue_setup_sanitize(struct request_queue *q) +{ + queue_flag_set_unlocked(QUEUE_FLAG_SANITIZE, q); +} + /** * mmc_init_queue - initialise a queue structure. * @mq: mmc queue @@ -177,15 +195,21 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, memset(&mq->mqrq_cur, 0, sizeof(mq->mqrq_cur)); memset(&mq->mqrq_prev, 0, sizeof(mq->mqrq_prev)); + INIT_LIST_HEAD(&mqrq_cur->packed_list); + INIT_LIST_HEAD(&mqrq_prev->packed_list); mq->mqrq_cur = mqrq_cur; mq->mqrq_prev = mqrq_prev; mq->queue->queuedata = mq; + mq->num_wr_reqs_to_start_packing = DEFAULT_NUM_REQS_TO_START_PACK; blk_queue_prep_rq(mq->queue, mmc_prep_request); queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue); if (mmc_can_erase(card)) mmc_queue_setup_discard(mq->queue, card); + if ((mmc_can_sanitize(card) && (host->caps2 & MMC_CAP2_SANITIZE))) + mmc_queue_setup_sanitize(mq->queue); + #ifdef CONFIG_MMC_BLOCK_BOUNCE if (host->max_segs == 1) { unsigned int bouncesz; @@ -377,6 +401,35 @@ void mmc_queue_resume(struct mmc_queue *mq) } } +static unsigned int mmc_queue_packed_map_sg(struct mmc_queue *mq, + struct mmc_queue_req *mqrq, + struct scatterlist *sg) +{ + struct scatterlist *__sg; + unsigned int sg_len = 0; + struct request *req; + enum mmc_packed_cmd cmd; + + cmd = mqrq->packed_cmd; + + if (cmd == MMC_PACKED_WRITE) { + __sg = sg; + sg_set_buf(__sg, mqrq->packed_cmd_hdr, + sizeof(mqrq->packed_cmd_hdr)); + sg_len++; + __sg->page_link &= ~0x02; + } + + __sg = sg + sg_len; + list_for_each_entry(req, &mqrq->packed_list, queuelist) { + sg_len += blk_rq_map_sg(mq->queue, req, __sg); + __sg = sg + (sg_len - 1); + (__sg++)->page_link &= ~0x02; + } + sg_mark_end(sg + (sg_len - 1)); + return sg_len; +} + /* * Prepare the sg list(s) to be handed of to the host driver */ @@ -387,12 +440,19 @@ unsigned int mmc_queue_map_sg(struct mmc_queue *mq, struct mmc_queue_req *mqrq) struct scatterlist *sg; int i; - if (!mqrq->bounce_buf) - return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg); + if (!mqrq->bounce_buf) { + if (!list_empty(&mqrq->packed_list)) + return mmc_queue_packed_map_sg(mq, mqrq, mqrq->sg); + else + return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg); + } BUG_ON(!mqrq->bounce_sg); - sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg); + if (!list_empty(&mqrq->packed_list)) + sg_len = mmc_queue_packed_map_sg(mq, mqrq, mqrq->bounce_sg); + else + sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg); mqrq->bounce_sg_len = sg_len; diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index d2a1eb4b9f9fade36848726f4e36fc4db1463cca..6c29e0ef30b099d3d27ebc1a0d4e864f59f9e689 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -12,6 +12,11 @@ struct mmc_blk_request { struct mmc_data data; }; +enum mmc_packed_cmd { + MMC_PACKED_NONE = 0, + MMC_PACKED_WRITE, +}; + struct mmc_queue_req { struct request *req; struct mmc_blk_request brq; @@ -20,6 +25,12 @@ struct mmc_queue_req { struct scatterlist *bounce_sg; unsigned int bounce_sg_len; struct mmc_async_req mmc_active; + struct list_head packed_list; + u32 packed_cmd_hdr[128]; + unsigned int packed_blocks; + enum mmc_packed_cmd packed_cmd; + int packed_fail_idx; + u8 packed_num; }; struct mmc_queue { @@ -33,6 +44,9 @@ struct mmc_queue { struct mmc_queue_req mqrq[2]; struct mmc_queue_req *mqrq_cur; struct mmc_queue_req *mqrq_prev; + bool wr_packing_enabled; + int num_of_potential_packed_wr_reqs; + int num_wr_reqs_to_start_packing; }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index c60cee92a2b2fe9bfd4a9b863dbc95d0e76e2eae..0592f9d9e593aab13f9fb6bded441bb14c3483e9 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -122,6 +122,7 @@ static int mmc_bus_remove(struct device *dev) return 0; } +#ifdef CONFIG_PM_SLEEP static int mmc_bus_suspend(struct device *dev) { struct mmc_driver *drv = to_mmc_driver(dev->driver); @@ -143,6 +144,10 @@ static int mmc_bus_resume(struct device *dev) ret = drv->resume(card); return ret; } +#else +#define mmc_bus_suspend NULL +#define mmc_bus_resume NULL +#endif #ifdef CONFIG_PM_RUNTIME @@ -249,6 +254,8 @@ struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type) card->dev.release = mmc_release_card; card->dev.type = type; + spin_lock_init(&card->wr_pack_stats.lock); + return card; } @@ -351,6 +358,8 @@ void mmc_remove_card(struct mmc_card *card) device_del(&card->dev); } + kfree(card->wr_pack_stats.packing_events); + put_device(&card->dev); } diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 95902a7e8b51078d963df303a74cb48673974d0a..973ece6c03e22e3c498c10e92081c29d3ef71e6d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,12 @@ #include "sd_ops.h" #include "sdio_ops.h" +/* + * The Background operations can take a long time, depends on the house keeping + * operations the card has to perform + */ +#define MMC_BKOPS_MAX_TIMEOUT (4 * 60 * 1000) /* max time to wait in ms */ + static struct workqueue_struct *workqueue; /* @@ -135,6 +142,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) @@ -159,6 +169,24 @@ 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 + if (host->perf_enable) { + 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); @@ -239,12 +267,84 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->error = 0; mrq->stop->mrq = mrq; } +#ifdef CONFIG_MMC_PERF_PROFILING + if (host->perf_enable) + host->perf.start = ktime_get(); +#endif } mmc_host_clk_hold(host); led_trigger_event(host->led, LED_FULL); host->ops->request(host, mrq); } +/** + * mmc_start_bkops - start BKOPS for supported cards + * @card: MMC card to start BKOPS + * + * Start background operations whenever requested. + * when the urgent BKOPS bit is set in a R1 command response + * then background operations should be started immediately. +*/ +void mmc_start_bkops(struct mmc_card *card) +{ + int err; + unsigned long flags; + int timeout; + + BUG_ON(!card); + if (!card->ext_csd.bkops_en || !(card->host->caps2 & MMC_CAP2_BKOPS)) + return; + + if (mmc_card_check_bkops(card)) { + spin_lock_irqsave(&card->host->lock, flags); + mmc_card_clr_check_bkops(card); + spin_unlock_irqrestore(&card->host->lock, flags); + if (mmc_is_exception_event(card, EXT_CSD_URGENT_BKOPS)) + if (card->ext_csd.raw_bkops_status) + mmc_card_set_need_bkops(card); + } + + /* + * If card is already doing bkops or need for + * bkops flag is not set, then do nothing just + * return + */ + if (mmc_card_doing_bkops(card) || !mmc_card_need_bkops(card)) + return; + + mmc_claim_host(card->host); + + timeout = (card->ext_csd.raw_bkops_status >= EXT_CSD_BKOPS_LEVEL_2) ? + MMC_BKOPS_MAX_TIMEOUT : 0; + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BKOPS_START, 1, timeout); + if (err) { + pr_warning("%s: error %d starting bkops\n", + mmc_hostname(card->host), err); + mmc_card_clr_need_bkops(card); + goto out; + } + + spin_lock_irqsave(&card->host->lock, flags); + mmc_card_clr_need_bkops(card); + + /* + * For urgent bkops status (LEVEL_2 and more) + * bkops executed synchronously, otherwise + * the operation is in progress + */ + if (card->ext_csd.raw_bkops_status >= EXT_CSD_BKOPS_LEVEL_2) + mmc_card_set_check_bkops(card); + else + mmc_card_set_doing_bkops(card); + + spin_unlock_irqrestore(&card->host->lock, flags); +out: + mmc_release_host(card->host); +} +EXPORT_SYMBOL(mmc_start_bkops); + static void mmc_wait_done(struct mmc_request *mrq) { complete(&mrq->completion); @@ -362,7 +462,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (host->areq) mmc_post_req(host, host->areq->mrq, 0); - /* Cancel a prepared request if it was not started. */ + /* Cancel a prepared request if it was not started. */ if ((err || start_err) && areq) mmc_post_req(host, areq->mrq, -EINVAL); @@ -479,6 +579,69 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries EXPORT_SYMBOL(mmc_wait_for_cmd); +/** + * mmc_interrupt_bkops - interrupt ongoing BKOPS + * @card: MMC card to check BKOPS + * + * Send HPI command to interrupt ongoing background operations, + * to allow rapid servicing of foreground operations,e.g. read/ + * writes. Wait until the card comes out of the programming state + * to avoid errors in servicing read/write requests. + */ +int mmc_interrupt_bkops(struct mmc_card *card) +{ + int err = 0; + unsigned long flags; + + BUG_ON(!card); + + err = mmc_interrupt_hpi(card); + + spin_lock_irqsave(&card->host->lock, flags); + mmc_card_clr_doing_bkops(card); + spin_unlock_irqrestore(&card->host->lock, flags); + + return err; +} +EXPORT_SYMBOL(mmc_interrupt_bkops); + +int mmc_read_bkops_status(struct mmc_card *card) +{ + int err; + u8 ext_csd[512]; + + mmc_claim_host(card->host); + err = mmc_send_ext_csd(card, ext_csd); + mmc_release_host(card->host); + if (err) + return err; + + card->ext_csd.raw_bkops_status = ext_csd[EXT_CSD_BKOPS_STATUS]; + card->ext_csd.raw_exception_status = ext_csd[EXT_CSD_EXP_EVENTS_STATUS]; + + return 0; +} +EXPORT_SYMBOL(mmc_read_bkops_status); + +int mmc_is_exception_event(struct mmc_card *card, unsigned int value) +{ + int err; + + err = mmc_read_bkops_status(card); + if (err) { + pr_err("%s: Didn't read bkops status : %d\n", + mmc_hostname(card->host), err); + return 0; + } + + /* In eMMC 4.41, R1_EXCEPTION_EVENT is URGENT_BKOPS */ + if (card->ext_csd.rev == 5) + return 1; + + return (card->ext_csd.raw_exception_status & value) ? 1 : 0; +} +EXPORT_SYMBOL(mmc_is_exception_event); + /** * mmc_set_data_timeout - set the timeout for a data command * @data: data phase for command @@ -574,6 +737,11 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) data->timeout_ns = 100000000; /* 100ms */ } } + /* Increase the timeout values for some bad INAND MCP devices */ + if (card->quirks & MMC_QUIRK_INAND_DATA_TIMEOUT) { + data->timeout_ns = 4000000000u; /* 4s */ + data->timeout_clks = 0; + } } EXPORT_SYMBOL(mmc_set_data_timeout); @@ -623,6 +791,7 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) might_sleep(); add_wait_queue(&host->wq, &wait); + spin_lock_irqsave(&host->lock, flags); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); @@ -707,7 +876,7 @@ EXPORT_SYMBOL(mmc_release_host); * Internal function that does the actual ios call to the host driver, * optionally printing some debug output. */ -static inline void mmc_set_ios(struct mmc_host *host) +void mmc_set_ios(struct mmc_host *host) { struct mmc_ios *ios = &host->ios; @@ -721,6 +890,7 @@ static inline void mmc_set_ios(struct mmc_host *host) mmc_set_ungated(host); host->ops->set_ios(host, ios); } +EXPORT_SYMBOL(mmc_set_ios); /* * Control chip select pin on a host. @@ -763,6 +933,8 @@ void mmc_gate_clock(struct mmc_host *host) { unsigned long flags; + WARN_ON(!host->ios.clock); + spin_lock_irqsave(&host->clk_lock, flags); host->clk_old = host->ios.clock; host->ios.clock = 0; @@ -785,7 +957,7 @@ void mmc_ungate_clock(struct mmc_host *host) * we just ignore the call. */ if (host->clk_old) { - BUG_ON(host->ios.clock); + WARN_ON(host->ios.clock); /* This call will also set host->clk_gated to false */ __mmc_set_clock(host, host->clk_old); } @@ -1140,7 +1312,7 @@ static void mmc_poweroff_notify(struct mmc_host *host) /* Set the card state to no notification after the poweroff */ card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION; } - mmc_release_host(host); + mmc_release_host(host); } /* @@ -1154,7 +1326,7 @@ static void mmc_poweroff_notify(struct mmc_host *host) * If a host does all the power sequencing itself, ignore the * initial MMC_POWER_UP stage. */ -static void mmc_power_up(struct mmc_host *host) +void mmc_power_up(struct mmc_host *host) { int bit; @@ -1167,11 +1339,12 @@ static void mmc_power_up(struct mmc_host *host) bit = fls(host->ocr_avail) - 1; host->ios.vdd = bit; - if (mmc_host_is_spi(host)) + if (mmc_host_is_spi(host)) host->ios.chip_select = MMC_CS_HIGH; - else + else { host->ios.chip_select = MMC_CS_DONTCARE; - host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } host->ios.power_mode = MMC_POWER_UP; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; @@ -1204,7 +1377,8 @@ void mmc_power_off(struct mmc_host *host) host->ios.clock = 0; host->ios.vdd = 0; - + + mmc_poweroff_notify(host); /* * For eMMC 4.5 device send AWAKE command before * POWER_OFF_NOTIFY command, because in sleep state @@ -1972,8 +2146,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; @@ -2038,10 +2220,8 @@ EXPORT_SYMBOL(mmc_detect_card_removed); void mmc_rescan(struct work_struct *work) { - static const unsigned freqs[] = { 400000, 300000, 200000, 100000 }; struct mmc_host *host = container_of(work, struct mmc_host, detect.work); - int i; bool extend_wakelock = false; if (host->rescan_disable) @@ -2058,6 +2238,12 @@ void mmc_rescan(struct work_struct *work) host->bus_ops->detect(host); host->detect_change = 0; + /* If the card was removed the bus will be marked + * as dead - extend the wakelock so userspace + * can respond */ + if (host->bus_dead) + extend_wakelock = 1; + /* If the card was removed the bus will be marked * as dead - extend the wakelock so userspace @@ -2088,14 +2274,8 @@ void mmc_rescan(struct work_struct *work) goto out; mmc_claim_host(host); - for (i = 0; i < ARRAY_SIZE(freqs); i++) { - if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) { - extend_wakelock = true; - break; - } - if (freqs[i] <= host->f_min) - break; - } + if (!mmc_rescan_try_freq(host, host->f_min)) + extend_wakelock = true; mmc_release_host(host); out: @@ -2327,32 +2507,58 @@ int mmc_suspend_host(struct mmc_host *host) if (cancel_delayed_work(&host->detect)) wake_unlock(&host->detect_wake_lock); mmc_flush_scheduled_work(); - err = mmc_cache_ctrl(host, 0); if (err) goto out; mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { + /* + * A long response time is not acceptable for device drivers + * when doing suspend. Prevent mmc_claim_host in the suspend + * sequence, to potentially wait "forever" by trying to + * pre-claim the host. + * + * Skip try claim host for SDIO cards, doing so fixes deadlock + * conditions. The function driver suspend may again call into + * SDIO driver within a different context for enabling power + * save mode in the card and hence wait in mmc_claim_host + * causing deadlock. + */ + if (!(host->card && mmc_card_sdio(host->card))) + if (!mmc_try_claim_host(host)) + err = -EBUSY; - if (host->bus_ops->suspend) - err = host->bus_ops->suspend(host); - - if (err == -ENOSYS || !host->bus_ops->resume) { - /* - * We simply "remove" the card in this case. - * It will be redetected on resume. (Calling - * bus_ops->remove() with a claimed host can - * deadlock.) - */ - if (host->bus_ops->remove) - host->bus_ops->remove(host); - mmc_claim_host(host); - mmc_detach_bus(host); - mmc_power_off(host); - mmc_release_host(host); - host->pm_flags = 0; - err = 0; + if (!err) { + if (host->bus_ops->suspend) { + /* + * For eMMC 4.5 device send notify command + * before sleep, because in sleep state eMMC 4.5 + * devices respond to only RESET and AWAKE cmd + */ + mmc_poweroff_notify(host); + err = host->bus_ops->suspend(host); + } + if (!(host->card && mmc_card_sdio(host->card))) + mmc_do_release_host(host); + + if (err == -ENOSYS || !host->bus_ops->resume) { + /* + * We simply "remove" the card in this case. + * It will be redetected on resume. (Calling + * bus_ops->remove() with a claimed host can + * deadlock.) + * It will be redetected on resume. + */ + if (host->bus_ops->remove) + host->bus_ops->remove(host); + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_power_off(host); + mmc_release_host(host); + host->pm_flags = 0; + err = 0; + } } } mmc_bus_put(host); diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 3bdafbca354f4ecdce7f4db56ce9baf82ba11cbb..85d273775ac08260d207816c19e15e7be36f57f0 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -32,6 +32,8 @@ void mmc_detach_bus(struct mmc_host *host); void mmc_init_erase(struct mmc_card *card); +void mmc_power_up(struct mmc_host *host); +void mmc_power_off(struct mmc_host *host); void mmc_set_chip_select(struct mmc_host *host, int mode); void mmc_set_clock(struct mmc_host *host, unsigned int hz); void mmc_gate_clock(struct mmc_host *host); @@ -51,6 +53,8 @@ static inline void mmc_delay(unsigned int ms) if (ms < 1000 / HZ) { cond_resched(); mdelay(ms); + } else if (ms < jiffies_to_msecs(2)) { + usleep_range(ms * 1000, (ms + 1) * 1000); } else { msleep(ms); } diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 9ab5b17d488ae45f63756a43bfe8917e88b52e86..898e35890ad331de0b05d1bcd58b13418b4c3271 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -318,6 +318,164 @@ static const struct file_operations mmc_dbg_ext_csd_fops = { .llseek = default_llseek, }; +static int mmc_wr_pack_stats_open(struct inode *inode, struct file *filp) +{ + struct mmc_card *card = inode->i_private; + + filp->private_data = card; + card->wr_pack_stats.print_in_read = 1; + return 0; +} + +#define TEMP_BUF_SIZE 256 +static ssize_t mmc_wr_pack_stats_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct mmc_card *card = filp->private_data; + struct mmc_wr_pack_stats *pack_stats; + int i; + int max_num_of_packed_reqs = 0; + char *temp_buf; + + if (!card) + return cnt; + + if (!card->wr_pack_stats.print_in_read) + return 0; + + if (!card->wr_pack_stats.enabled) { + pr_info("%s: write packing statistics are disabled\n", + mmc_hostname(card->host)); + goto exit; + } + + pack_stats = &card->wr_pack_stats; + + if (!pack_stats->packing_events) { + pr_info("%s: NULL packing_events\n", mmc_hostname(card->host)); + goto exit; + } + + max_num_of_packed_reqs = card->ext_csd.max_packed_writes; + + temp_buf = kmalloc(TEMP_BUF_SIZE, GFP_KERNEL); + if (!temp_buf) + goto exit; + + spin_lock(&pack_stats->lock); + + snprintf(temp_buf, TEMP_BUF_SIZE, "%s: write packing statistics:\n", + mmc_hostname(card->host)); + strlcat(ubuf, temp_buf, cnt); + + for (i = 1 ; i <= max_num_of_packed_reqs ; ++i) { + if (pack_stats->packing_events[i]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: Packed %d reqs - %d times\n", + mmc_hostname(card->host), i, + pack_stats->packing_events[i]); + strlcat(ubuf, temp_buf, cnt); + } + } + + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: stopped packing due to the following reasons:\n", + mmc_hostname(card->host)); + strlcat(ubuf, temp_buf, cnt); + + if (pack_stats->pack_stop_reason[EXCEEDS_SEGMENTS]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: exceed max num of segments\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[EXCEEDS_SEGMENTS]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[EXCEEDS_SECTORS]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: exceed max num of sectors\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[EXCEEDS_SECTORS]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[WRONG_DATA_DIR]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: wrong data direction\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[WRONG_DATA_DIR]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[FLUSH_OR_DISCARD]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: flush or discard\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[FLUSH_OR_DISCARD]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[EMPTY_QUEUE]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: empty queue\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[EMPTY_QUEUE]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[REL_WRITE]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: rel write\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[REL_WRITE]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[THRESHOLD]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: Threshold\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[THRESHOLD]); + strlcat(ubuf, temp_buf, cnt); + } + + spin_unlock(&pack_stats->lock); + + kfree(temp_buf); + + pr_info("%s", ubuf); + +exit: + if (card->wr_pack_stats.print_in_read == 1) { + card->wr_pack_stats.print_in_read = 0; + return strnlen(ubuf, cnt); + } + + return 0; +} + +static ssize_t mmc_wr_pack_stats_write(struct file *filp, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct mmc_card *card = filp->private_data; + int value; + + if (!card) + return cnt; + + sscanf(ubuf, "%d", &value); + if (value) { + mmc_blk_init_packed_statistics(card); + } else { + spin_lock(&card->wr_pack_stats.lock); + card->wr_pack_stats.enabled = false; + spin_unlock(&card->wr_pack_stats.lock); + } + + return cnt; +} + +static const struct file_operations mmc_dbg_wr_pack_stats_fops = { + .open = mmc_wr_pack_stats_open, + .read = mmc_wr_pack_stats_read, + .write = mmc_wr_pack_stats_write, +}; + void mmc_add_card_debugfs(struct mmc_card *card) { struct mmc_host *host = card->host; @@ -350,6 +508,12 @@ void mmc_add_card_debugfs(struct mmc_card *card) &mmc_dbg_ext_csd_fops)) goto err; + if (mmc_card_mmc(card) && (card->ext_csd.rev >= 6) && + (card->host->caps2 & MMC_CAP2_PACKED_WR)) + if (!debugfs_create_file("wr_pack_stats", S_IRUSR, root, card, + &mmc_dbg_wr_pack_stats_fops)) + goto err; + return; err: diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index dd7b1203e867ee5fd6bb964ba178381b60416a59..850872d92e9f391d8da909d9593c225698c0327c 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -58,7 +58,8 @@ static ssize_t clkgate_delay_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = cls_dev_to_mmc_host(dev); - return snprintf(buf, PAGE_SIZE, "%lu\n", host->clkgate_delay); + return snprintf(buf, PAGE_SIZE, "%lu\n", + host->clkgate_delay); } static ssize_t clkgate_delay_store(struct device *dev, @@ -73,6 +74,9 @@ static ssize_t clkgate_delay_store(struct device *dev, spin_lock_irqsave(&host->clk_lock, flags); host->clkgate_delay = value; spin_unlock_irqrestore(&host->clk_lock, flags); + + pr_info("%s: clock gate delay set to %lu ms\n", + mmc_hostname(host), value); return count; } @@ -355,6 +359,66 @@ 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_drv, wtime_drv; + unsigned long rbytes_drv, wbytes_drv; + + spin_lock(&host->lock); + + rbytes_drv = host->perf.rbytes_drv; + wbytes_drv = host->perf.wbytes_drv; + + 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 driver Level:" + "%lu bytes in %lld microseconds\n" + "Read performance at driver Level:" + "%lu bytes in %lld microseconds\n", + 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); + spin_lock(&host->lock); + if (!value) { + memset(&host->perf, 0, sizeof(host->perf)); + host->perf_enable = false; + } else { + host->perf_enable = true; + } + 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 @@ -382,6 +446,11 @@ int mmc_add_host(struct mmc_host *host) #endif mmc_host_clk_sysfs_init(host); + 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)) register_pm_notifier(&host->pm_notify); @@ -409,6 +478,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 54df5adc04137741ee7710d6ef5ee119add388d7..1baa0c2ebbc0a82f0047533af849e562226a31e5 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -263,7 +263,7 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.rev = ext_csd[EXT_CSD_REV]; if (card->ext_csd.rev > 6) { - pr_err("%s: unrecognised EXT_CSD revision %d\n", + printk(KERN_ERR "%s: unrecognised EXT_CSD revision %d\n", mmc_hostname(card->host), card->ext_csd.rev); err = -EINVAL; goto out; @@ -480,6 +480,24 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } if (card->ext_csd.rev >= 5) { + /* check whether the eMMC card support BKOPS */ + if (ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1) { + card->ext_csd.bkops = 1; + card->ext_csd.bkops_en = ext_csd[EXT_CSD_BKOPS_EN]; + card->ext_csd.raw_bkops_status = + ext_csd[EXT_CSD_BKOPS_STATUS]; + if (!card->ext_csd.bkops_en && + card->host->caps2 & MMC_CAP2_INIT_BKOPS) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BKOPS_EN, 1, 0); + if (err) + pr_warning("%s: Enabling BKOPS failed\n", + mmc_hostname(card->host)); + else + card->ext_csd.bkops_en = 1; + } + } + /* check whether the eMMC card supports HPI */ if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x1) { card->ext_csd.hpi = 1; @@ -533,6 +551,10 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } else { card->ext_csd.data_tag_unit_size = 0; } + card->ext_csd.max_packed_writes = + ext_csd[EXT_CSD_MAX_PACKED_WRITES]; + card->ext_csd.max_packed_reads = + ext_csd[EXT_CSD_MAX_PACKED_READS]; } out: @@ -951,6 +973,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); } /* @@ -1267,6 +1292,43 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + if ((host->caps2 & MMC_CAP2_PACKED_CMD) && + (card->ext_csd.max_packed_writes > 0) && + (card->ext_csd.max_packed_reads > 0)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_EXP_EVENTS_CTRL, + EXT_CSD_PACKED_EVENT_EN, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + if (err) { + pr_warning("%s: Enabling packed event failed\n", + mmc_hostname(card->host)); + card->ext_csd.packed_event_en = 0; + err = 0; + } else { + card->ext_csd.packed_event_en = 1; + } + + } + + if (!oldcard) { + if ((host->caps2 & MMC_CAP2_PACKED_CMD) && + (card->ext_csd.max_packed_writes > 0)) { + /* + * We would like to keep the statistics in an index + * that equals the num of packed requests + * (1 to max_packed_writes) + */ + card->wr_pack_stats.packing_events = kzalloc( + (card->ext_csd.max_packed_writes + 1) * + sizeof(*card->wr_pack_stats.packing_events), + GFP_KERNEL); + if (!card->wr_pack_stats.packing_events) + goto free_card; + } + } + if (!oldcard) host->card = card; @@ -1291,7 +1353,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 69370f494e054f7951d287bba8b1d570510dc33d..590aa58fcd7b102724dbe51a97d13c9a5bfb3be5 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -335,6 +335,7 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD, ext_csd, 512); } +EXPORT_SYMBOL_GPL(mmc_send_ext_csd); int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp) { @@ -392,13 +393,23 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, (index << 16) | (value << 8) | set; - cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + cmd.flags = MMC_CMD_AC; + if (index == EXT_CSD_BKOPS_START && + card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2) + cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1; + else + cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B; cmd.cmd_timeout_ms = timeout_ms; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); if (err) return err; + /* No need to check card status in case of BKOPS switch*/ + if (index == EXT_CSD_BKOPS_START) + return 0; + + mmc_delay(1); /* Must check status to be sure of no errors */ do { err = mmc_send_status(card, &status); @@ -507,6 +518,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/quirks.c b/drivers/mmc/core/quirks.c index 06ee1aeaacec2693fd8f4a36b564902b1e738234..59f03404724f2ee540592ab33b20807749a5dc7b 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -30,6 +30,26 @@ #define SDIO_DEVICE_ID_STE_CW1200 0x2280 #endif +#ifndef SDIO_VENDOR_ID_MSM +#define SDIO_VENDOR_ID_MSM 0x0070 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_WCN1314 +#define SDIO_DEVICE_ID_MSM_WCN1314 0x2881 +#endif + +#ifndef SDIO_VENDOR_ID_MSM_QCA +#define SDIO_VENDOR_ID_MSM_QCA 0x271 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_QCA_AR6003_1 +#define SDIO_DEVICE_ID_MSM_QCA_AR6003_1 0x300 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_QCA_AR6003_2 +#define SDIO_DEVICE_ID_MSM_QCA_AR6003_2 0x301 +#endif + /* * This hook just adds a quirk for all sdio devices */ @@ -49,6 +69,15 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_MSM, SDIO_DEVICE_ID_MSM_WCN1314, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + + SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6003_1, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + + SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6003_2, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_NONSTD_FUNC_IF), diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 7c76a455b1e453b4f63bb29bb3bc3f57519e1e16..ff5821bc09c6bb42242355c42a60c3f1d44d1243 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -878,9 +878,9 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card, int ro = -1; if (host->ops->get_ro) { - mmc_host_clk_hold(card->host); + mmc_host_clk_hold(host); ro = host->ops->get_ro(host); - mmc_host_clk_release(card->host); + mmc_host_clk_release(host); } if (ro < 0) { @@ -1001,9 +1001,9 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * value registers for UHS-I cards. */ if (host->ops->enable_preset_value) { - mmc_host_clk_hold(card->host); + mmc_host_clk_hold(host); host->ops->enable_preset_value(host, true); - mmc_host_clk_release(card->host); + mmc_host_clk_release(host); } } else { /* @@ -1052,7 +1052,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); } /* @@ -1152,8 +1155,11 @@ static int mmc_sd_resume(struct mmc_host *host) if (err) { printk(KERN_ERR "%s: Re-init card rc = %d (retries = %d)\n", mmc_hostname(host), err, retries); - mdelay(5); retries--; + mmc_power_off(host); + usleep_range(5000, 5500); + mmc_power_up(host); + mmc_select_voltage(host, host->ocr); continue; } break; @@ -1285,6 +1291,10 @@ int mmc_attach_sd(struct mmc_host *host) err = mmc_sd_init_card(host, host->ocr, NULL); if (err) { retries--; + mmc_power_off(host); + usleep_range(5000, 5500); + mmc_power_up(host); + mmc_select_voltage(host, host->ocr); continue; } break; diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index c44f276cca2721431fa3acd18837f3a8d6ab37e4..81a4ba03ef469de48f4dc38a6ac8b6376223c49f 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -213,7 +213,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) @@ -223,7 +223,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) @@ -264,7 +267,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) @@ -274,10 +277,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); @@ -637,8 +640,11 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, /* * Call the optional HC's init_card function to handle quirks. */ - if (host->ops->init_card) + if (host->ops->init_card) { + mmc_host_clk_hold(host); host->ops->init_card(host, card); + mmc_host_clk_release(host); + } /* * If the host and card support UHS-I mode request the card @@ -807,9 +813,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); - else if (err) + 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; } finish: @@ -962,13 +971,16 @@ 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; } } if (!err && host->sdio_irqs) - mmc_signal_sdio_irq(host); + wake_up_process(host->sdio_irq_thread); mmc_release_host(host); /* @@ -1282,8 +1294,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 f1c7ed8f4d85a2a58b41c7b44d7899f115edaa02..2ca585d0fbbd3ab6130aed3bd5ed34efde300f19 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -55,7 +55,7 @@ static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, for (i = 0; i < nr_strings; i++) { buffer[i] = string; - strcpy(string, buf); + strlcpy(string, buf, sizeof(string)); string += strlen(string) + 1; buf += strlen(buf) + 1; } @@ -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/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index f573e7f9f74020dae69aa3ba861bbcadf66ffc4e..fca32742c8bb208f15e2de03dffa01441c39159d 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -28,18 +28,20 @@ #include "sdio_ops.h" -static int process_sdio_pending_irqs(struct mmc_card *card) +static int process_sdio_pending_irqs(struct mmc_host *host) { + struct mmc_card *card = host->card; int i, ret, count; unsigned char pending; struct sdio_func *func; /* * Optimization, if there is only 1 function interrupt registered - * call irq handler directly + * and we know an IRQ was signaled then call irq handler directly. + * Otherwise do the full probe. */ func = card->sdio_single_irq; - if (func) { + if (func && host->sdio_irq_pending) { func->irq_handler(func); return 1; } @@ -116,7 +118,8 @@ static int sdio_irq_thread(void *_host) ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); if (ret) break; - ret = process_sdio_pending_irqs(host->card); + ret = process_sdio_pending_irqs(host); + host->sdio_irq_pending = false; mmc_release_host(host); /* @@ -212,14 +215,14 @@ static void sdio_single_irq_set(struct mmc_card *card) card->sdio_single_irq = NULL; if ((card->host->caps & MMC_CAP_SDIO_IRQ) && - card->host->sdio_irqs == 1) + card->host->sdio_irqs == 1) for (i = 0; i < card->sdio_funcs; i++) { - func = card->sdio_func[i]; - if (func && func->irq_handler) { - card->sdio_single_irq = func; - break; - } - } + func = card->sdio_func[i]; + if (func && func->irq_handler) { + card->sdio_single_irq = func; + break; + } + } } /** diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 2bc06e7344db5970c754ab6cbf0a849830d8339f..96eae7d8072d2b7748547d9ee4a80785f32ecc24 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -458,6 +458,104 @@ 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_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_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_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_WP_SUPPORT + boolean "Qualcomm SDC3 write protection support" + depends on MMC_MSM_SDC3_SUPPORT + default n + help + Select Y to enable write protection support for Slot 3. + +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_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_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 3e7e26d0807346a82353544a0623086ce4249409..843ce060e51481afa8781f92492a988a75f0b0da 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,9 @@ tmio_mmc_core-y := tmio_mmc_pio.o tmio_mmc_core-$(subst m,y,$(CONFIG_MMC_SDHI)) += tmio_mmc_dma.o 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 1d14cda95e56c8191c2168b26d4e0543eb39ddac..ac8b164412a1e1c5de53077dbb8aff6387a41858 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-2012, Code Aurora Forum. All rights reserved. * * 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 @@ -19,14 +19,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -35,149 +38,328 @@ #include #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; +#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 +/* Use SPS only if transfer size is more than this macro */ +#define SPS_MIN_XFER_SIZE MCI_FIFOSIZE + +#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */ + +#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 + +static int msmsdcc_prep_xfer(struct msmsdcc_host *host, struct mmc_data + *data); + +static u64 dma_mask = DMA_BIT_MASK(32); static unsigned int msmsdcc_pwrsave = 1; -static unsigned int msmsdcc_piopoll = 1; -static unsigned int msmsdcc_sdioirq; -#define PIO_SPINMAX 30 -#define CMD_SPINMAX 20 +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 tuning_block_64[] = { + 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, + 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, + 0xF0FFF0FF, 0x3CCCFC0F, 0xCFCC33CC, 0xEEFFEFFF, + 0xFDFFFDFF, 0xFFBFFFDF, 0xFFF7FFBB, 0xDE7B7FF7 +}; +static const u32 tuning_block_128[] = { + 0xFF00FFFF, 0x0000FFFF, 0xCCCCFFFF, 0xCCCC33CC, + 0xCC3333CC, 0xFFFFCCCC, 0xFFFFEEFF, 0xFFEEEEFF, + 0xFFDDFFFF, 0xDDDDFFFF, 0xBBFFFFFF, 0xBBFFFFFF, + 0xFFFFFFBB, 0xFFFFFF77, 0x77FF7777, 0xFFEEDDBB, + 0x00FFFFFF, 0x00FFFFFF, 0xCCFFFF00, 0xCC33CCCC, + 0x3333CCCC, 0xFFCCCCCC, 0xFFEEFFFF, 0xEEEEFFFF, + 0xDDFFFFFF, 0xDDFFFFFF, 0xFFFFFFDD, 0xFFFFFFBB, + 0xFFFFBBBB, 0xFFFF77FF, 0xFF7777FF, 0xEEDDBB77 +}; -static inline void -msmsdcc_disable_clocks(struct msmsdcc_host *host, int deferr) -{ - WARN_ON(!host->clks_on); +#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 }; - BUG_ON(host->curr.mrq); +static void +msmsdcc_print_status(struct msmsdcc_host *host, char *hdr, uint32_t status) +{ + int i; - 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; - } + 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]); } + pr_debug("\n"); } +#endif -static inline int -msmsdcc_enable_clocks(struct msmsdcc_host *host) +static void +msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, + u32 c); +static inline void msmsdcc_sync_reg_wr(struct msmsdcc_host *host); +static inline void msmsdcc_delay(struct msmsdcc_host *host); +static void msmsdcc_dump_sdcc_state(struct msmsdcc_host *host); +static void msmsdcc_sg_start(struct msmsdcc_host *host); +static int msmsdcc_vreg_reset(struct msmsdcc_host *host); +static int msmsdcc_runtime_resume(struct device *dev); + +static inline unsigned short msmsdcc_get_nr_sg(struct msmsdcc_host *host) { - int rc; + unsigned short ret = NR_SG; - 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; + if (host->is_sps_mode) { + ret = SPS_MAX_DESCS; + } else { /* DMA or PIO mode */ + if (NR_SG > MAX_NR_SG_DMA_PIO) + ret = MAX_NR_SG_DMA_PIO; } - return 0; + + return ret; } -static inline unsigned int -msmsdcc_readl(struct msmsdcc_host *host, unsigned int reg) +/* Prevent idle power collapse(pc) while operating in peripheral mode */ +static void msmsdcc_pm_qos_update_latency(struct msmsdcc_host *host, int vote) { - return readl(host->base + reg); + if (!host->cpu_dma_latency) + return; + + if (vote) + pm_qos_update_request(&host->pm_qos_req_dma, + host->cpu_dma_latency); + else + pm_qos_update_request(&host->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); } -static inline void -msmsdcc_writel(struct msmsdcc_host *host, u32 data, unsigned int reg) +#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) +{ + return 0; +} +static inline int msmsdcc_sps_restore_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) { - writel(data, host->base + reg); - /* 3 clk delay required! */ - udelay(1 + ((3 * USEC_PER_SEC) / - (host->clk_rate ? host->clk_rate : msmsdcc_fmin))); + 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 */ -static void -msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, - u32 c); +/** + * Apply soft reset to all SDCC BAM pipes + * + * This function applies soft reset to SDCC BAM pipe. + * + * This function should be called to recover from error + * conditions encountered during CMD/DATA tranfsers with card. + * + * @host - Pointer to driver's host structure + * + */ +static void msmsdcc_sps_pipes_reset_and_restore(struct msmsdcc_host *host) +{ + int rc; -static void msmsdcc_reset_and_restore(struct msmsdcc_host *host) + /* Reset all SDCC BAM pipes */ + rc = msmsdcc_sps_reset_ep(host, &host->sps.prod); + if (rc) + pr_err("%s:msmsdcc_sps_reset_ep(prod) 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(cons) error=%d\n", + mmc_hostname(host->mmc), rc); + + /* Restore all BAM pipes connections */ + rc = msmsdcc_sps_restore_ep(host, &host->sps.prod); + if (rc) + pr_err("%s:msmsdcc_sps_restore_ep(prod) 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(cons) error=%d\n", + mmc_hostname(host->mmc), rc); +} + +/** + * Apply soft reset + * + * This function applies soft reset to SDCC core and 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(struct msmsdcc_host *host) { - u32 mci_clk = 0; - u32 mci_mask0 = 0; - int ret = 0; + /* + * Reset SDCC controller's DPSM (data path state machine + * and CPSM (command path state machine). + */ + writel_relaxed(0, host->base + MMCICOMMAND); + msmsdcc_sync_reg_wr(host); + writel_relaxed(0, host->base + MMCIDATACTRL); + msmsdcc_sync_reg_wr(host); +} - /* Save the controller state */ - mci_clk = readl(host->base + MMCICLOCK); - mci_mask0 = readl(host->base + MMCIMASK0); +static void msmsdcc_hard_reset(struct msmsdcc_host *host) +{ + int ret; /* 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); + 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_err("%s: Clock deassert failed at %u Hz" + " with err %d\n", mmc_hostname(host->mmc), + host->clk_rate, ret); - pr_info("%s: Controller has been re-initialiazed\n", - mmc_hostname(host->mmc)); + mb(); + /* Give some delay for clock reset to propogate to controller */ + msmsdcc_delay(host); +} - /* 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); +static void msmsdcc_reset_and_restore(struct msmsdcc_host *host) +{ + if (host->sdcc_version) { + if (host->is_sps_mode) { + /* Reset DML first */ + msmsdcc_dml_reset(host); + /* + * delay the SPS pipe reset in thread context as + * sps_connect/sps_disconnect APIs can be called + * only from non-atomic context. + */ + host->sps.pipe_reset_pending = true; + } + mb(); + msmsdcc_soft_reset(host); + + pr_debug("%s: Applied soft reset to Controller\n", + mmc_hostname(host->mmc)); + + if (host->is_sps_mode) + msmsdcc_dml_init(host); + } else { + /* Give Clock reset (hard reset) to controller */ + u32 mci_clk = 0; + u32 mci_mask0 = 0; + + /* Save the controller state */ + mci_clk = readl_relaxed(host->base + MMCICLOCK); + mci_mask0 = readl_relaxed(host->base + MMCIMASK0); + host->pwr = readl_relaxed(host->base + MMCIPOWER); + mb(); + + msmsdcc_hard_reset(host); + pr_debug("%s: Controller has been reinitialized\n", + mmc_hostname(host->mmc)); + + /* Restore the contoller state */ + writel_relaxed(host->pwr, host->base + MMCIPOWER); + msmsdcc_sync_reg_wr(host); + writel_relaxed(mci_clk, host->base + MMCICLOCK); + msmsdcc_sync_reg_wr(host); + writel_relaxed(mci_mask0, host->base + MMCIMASK0); + mb(); /* no delay required after writing to MASK0 register */ + } + + if (host->dummy_52_needed) + host->dummy_52_needed = 0; } -static void +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 + /* Clear current request information as current request has ended */ + memset(&host->curr, 0, sizeof(struct msmsdcc_curr_req)); + /* * Need to drop the host lock here; mmc_request_done may call * back into the driver... @@ -185,6 +367,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 @@ -192,37 +376,80 @@ msmsdcc_stop_data(struct msmsdcc_host *host) { host->curr.data = NULL; host->curr.got_dataend = 0; + host->curr.wait_for_auto_prog_done = 0; + host->curr.got_auto_prog_done = 0; + writel_relaxed(readl_relaxed(host->base + MMCIDATACTRL) & + (~(MCI_DPSM_ENABLE)), host->base + MMCIDATACTRL); + msmsdcc_sync_reg_wr(host); /* Allow the DPSM to be reset */ +} + +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_sync_reg_wr(struct msmsdcc_host *host) +{ + mb(); + if (!host->sdcc_version) + udelay(host->reg_write_delay); + else if (readl_relaxed(host->base + MCI_STATUS2) & + MCI_MCLK_REG_WR_ACTIVE) { + ktime_t start, diff; + + start = ktime_get(); + while (readl_relaxed(host->base + MCI_STATUS2) & + MCI_MCLK_REG_WR_ACTIVE) { + diff = ktime_sub(ktime_get(), start); + /* poll for max. 1 ms */ + if (ktime_to_us(diff) > 1000) { + pr_warning("%s: previous reg. write is" + " still active\n", + mmc_hostname(host->mmc)); + break; + } + } + } } -uint32_t msmsdcc_fifo_addr(struct msmsdcc_host *host) +static inline void msmsdcc_delay(struct msmsdcc_host *host) { - return host->memres->start + MMCIFIFO; + udelay(host->reg_write_delay); + } 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); + writel_relaxed(c, host->base + MMCICOMMAND); + /* + * As after sending the command, we don't write any of the + * controller registers and just wait for the + * CMD_RESPOND_END/CMD_SENT/Command failure notication + * from Controller. + */ + 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, (msmsdcc_readl(host, MMCIMASK0) & - (~MCI_IRQ_PIO)) | host->cmd_pio_irqmask, MMCIMASK0); - 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); + writel_relaxed(host->cmd_datactrl, host->base + MMCIDATACTRL); + msmsdcc_sync_reg_wr(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 @@ -231,15 +458,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"); @@ -248,6 +470,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) @@ -256,44 +479,67 @@ 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; } - dma_unmap_sg(mmc_dev(host->mmc), host->dma.sg, host->dma.num_ents, - host->dma.dir); + if (!mrq->data->host_cookie) + 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; - if (host->curr.got_dataend || mrq->data->error) { - + if ((host->curr.got_dataend && (!host->curr.wait_for_auto_prog_done || + (host->curr.wait_for_auto_prog_done && + host->curr.got_auto_prog_done))) || 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) + if (!mrq->data->error) { host->curr.data_xfered = host->curr.xfer_size; - if (!mrq->data->stop || mrq->cmd->error) { - host->curr.mrq = NULL; - host->curr.cmd = NULL; + host->curr.xfer_remain -= host->curr.xfer_size; + } + if (host->dummy_52_needed) { mrq->data->bytes_xfered = host->curr.data_xfered; - + host->dummy_52_sent = 1; + msmsdcc_start_command(host, &dummy52cmd, + MCI_CPSM_PROGENA); + goto out; + } + msmsdcc_stop_data(host); + if (!mrq->data->stop || mrq->cmd->error || + (mrq->sbc && !mrq->data->error)) { + mrq->data->bytes_xfered = host->curr.data_xfered; + del_timer(&host->req_tout_timer); + /* + * Clear current request information as current + * request has ended + */ + memset(&host->curr, 0, sizeof(struct msmsdcc_curr_req)); spin_unlock_irqrestore(&host->lock, flags); -#if BUSCLK_PWRSAVE - msmsdcc_disable_clocks(host, 1); -#endif + mmc_request_done(host->mmc, mrq); return; - } else + } else if (mrq->data->stop && ((mrq->sbc && mrq->data->error) + || !mrq->sbc)) { msmsdcc_start_command(host, mrq->data->stop, 0); + } } out: @@ -301,6 +547,211 @@ 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 */ + if (!mrq->data->host_cookie) + 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 && (!host->curr.wait_for_auto_prog_done || + (host->curr.wait_for_auto_prog_done && + host->curr.got_auto_prog_done))) || mrq->data->error) { + /* + * If we've already gotten our DATAEND / DATABLKEND + * for this request, then complete it through here. + */ + + if (!mrq->data->error) { + host->curr.data_xfered = host->curr.xfer_size; + host->curr.xfer_remain -= host->curr.xfer_size; + } + if (host->dummy_52_needed) { + mrq->data->bytes_xfered = host->curr.data_xfered; + host->dummy_52_sent = 1; + msmsdcc_start_command(host, &dummy52cmd, + MCI_CPSM_PROGENA); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + msmsdcc_stop_data(host); + if (!mrq->data->stop || mrq->cmd->error || + (mrq->sbc && !mrq->data->error)) { + mrq->data->bytes_xfered = host->curr.data_xfered; + del_timer(&host->req_tout_timer); + /* + * Clear current request information as current + * request has ended + */ + memset(&host->curr, 0, sizeof(struct msmsdcc_curr_req)); + spin_unlock_irqrestore(&host->lock, flags); + + mmc_request_done(host->mmc, mrq); + return; + } else if (mrq->data->stop && ((mrq->sbc && mrq->data->error) + || !mrq->sbc)) { + 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 */ + if (!mrq->data->host_cookie) + 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 || + (mrq->sbc && !mrq->data->error)) + msmsdcc_request_end(host, mrq); + else if (mrq->data->stop && ((mrq->sbc && mrq->data->error) + || !mrq->sbc)) + 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 int msmsdcc_enable_cdr_cm_sdc4_dll(struct msmsdcc_host *host); + static void msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd, unsigned int result, @@ -317,16 +768,32 @@ 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 bool msmsdcc_is_dma_possible(struct msmsdcc_host *host, + struct mmc_data *data) { - if (host->dma.channel == -1) - return -ENOENT; + bool ret = true; + u32 xfer_size = data->blksz * data->blocks; - if ((data->blksz * data->blocks) < MCI_FIFOSIZE) - return -EINVAL; - if ((data->blksz * data->blocks) % MCI_FIFOSIZE) - return -EINVAL; - return 0; + if (host->is_sps_mode) { + /* + * BAM Mode: Fall back on PIO if size is less + * than or equal to SPS_MIN_XFER_SIZE bytes. + */ + if (xfer_size <= SPS_MIN_XFER_SIZE) + ret = false; + } else if (host->is_dma_mode) { + /* + * ADM Mode: Fall back on PIO if size is less than FIFO size + * or not integer multiple of FIFO size + */ + if (xfer_size % MCI_FIFOSIZE) + ret = false; + } else { + /* PIO Mode */ + ret = false; + } + + return ret; } static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) @@ -334,49 +801,90 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) struct msmsdcc_nc_dmadata *nc; dmov_box *box; uint32_t rows; - uint32_t crci; unsigned int n; - int i, rc; + int i, err = 0, box_cmd_cnt = 0; struct scatterlist *sg = data->sg; + unsigned int len, offset; - rc = validate_dma(host, data); - if (rc) - return rc; + if ((host->dma.channel == -1) || (host->dma.crci == -1)) + return -ENOENT; + + BUG_ON((host->pdev_id < 1) || (host->pdev_id > 5)); host->dma.sg = data->sg; host->dma.num_ents = data->sg_len; - BUG_ON(host->dma.num_ents > NR_SG); /* Prevent memory corruption */ + /* Prevent memory corruption */ + BUG_ON(host->dma.num_ents > msmsdcc_get_nr_sg(host)); 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: - host->dma.sg = NULL; - host->dma.num_ents = 0; - return -ENOENT; - } - if (data->flags & MMC_DATA_READ) host->dma.dir = DMA_FROM_DEVICE; else host->dma.dir = DMA_TO_DEVICE; - host->curr.user_pages = 0; + if (!data->host_cookie) { + n = msmsdcc_prep_xfer(host, data); + if (unlikely(n < 0)) { + host->dma.sg = NULL; + host->dma.num_ents = 0; + return -ENOMEM; + } + } + /* host->curr.user_pages = (data->flags & MMC_DATA_USERPAGE); */ + host->curr.user_pages = 0; box = &nc->cmd[0]; + for (i = 0; i < host->dma.num_ents; i++) { + len = sg_dma_len(sg); + offset = 0; + + do { + /* Check if we can do DMA */ + if (!len || (box_cmd_cnt >= MMC_MAX_DMA_CMDS)) { + err = -ENOTSUPP; + goto unmap; + } + + box->cmd = CMD_MODE_BOX; + + if (len >= MMC_MAX_DMA_BOX_LENGTH) { + len = MMC_MAX_DMA_BOX_LENGTH; + len -= len % data->blksz; + } + rows = (len % MCI_FIFOSIZE) ? + (len / MCI_FIFOSIZE) + 1 : + (len / MCI_FIFOSIZE); + + if (data->flags & MMC_DATA_READ) { + box->src_row_addr = msmsdcc_fifo_addr(host); + box->dst_row_addr = sg_dma_address(sg) + offset; + box->src_dst_len = (MCI_FIFOSIZE << 16) | + (MCI_FIFOSIZE); + box->row_offset = MCI_FIFOSIZE; + box->num_rows = rows * ((1 << 16) + 1); + box->cmd |= CMD_SRC_CRCI(host->dma.crci); + } else { + box->src_row_addr = sg_dma_address(sg) + offset; + box->dst_row_addr = msmsdcc_fifo_addr(host); + box->src_dst_len = (MCI_FIFOSIZE << 16) | + (MCI_FIFOSIZE); + box->row_offset = (MCI_FIFOSIZE << 16); + box->num_rows = rows * ((1 << 16) + 1); + box->cmd |= CMD_DST_CRCI(host->dma.crci); + } + + offset += len; + len = sg_dma_len(sg) - offset; + box++; + box_cmd_cnt++; + } while (len); + sg++; + } + /* Mark last command */ + box--; + box->cmd |= CMD_LC; /* location of command block must be 64 bit aligned */ BUG_ON(host->dma.cmd_busaddr & 0x07); @@ -386,67 +894,156 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) 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) { - 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; - } + /* Flush all data to memory before starting dma */ + mb(); - for_each_sg(host->dma.sg, sg, n, i) { +unmap: + if (err) { + if (!data->host_cookie) + dma_unmap_sg(mmc_dev(host->mmc), host->dma.sg, + host->dma.num_ents, host->dma.dir); + pr_err("%s: cannot do DMA, fall back to PIO mode err=%d\n", + mmc_hostname(host->mmc), err); + } - box->cmd = CMD_MODE_BOX; + return err; +} - if (i == n - 1) - box->cmd |= CMD_LC; - rows = (sg_dma_len(sg) % MCI_FIFOSIZE) ? - (sg_dma_len(sg) / MCI_FIFOSIZE) + 1 : - (sg_dma_len(sg) / MCI_FIFOSIZE) ; +static int msmsdcc_prep_xfer(struct msmsdcc_host *host, + struct mmc_data *data) +{ + int rc = 0; + unsigned int dir; - if (data->flags & MMC_DATA_READ) { - box->src_row_addr = msmsdcc_fifo_addr(host); - box->dst_row_addr = sg_dma_address(sg); + /* Prevent memory corruption */ + BUG_ON(data->sg_len > msmsdcc_get_nr_sg(host)); - box->src_dst_len = (MCI_FIFOSIZE << 16) | - (MCI_FIFOSIZE); - box->row_offset = MCI_FIFOSIZE; + if (data->flags & MMC_DATA_READ) + dir = DMA_FROM_DEVICE; + else + dir = DMA_TO_DEVICE; - box->num_rows = rows * ((1 << 16) + 1); - box->cmd |= CMD_SRC_CRCI(crci); - } else { - box->src_row_addr = sg_dma_address(sg); - box->dst_row_addr = msmsdcc_fifo_addr(host); + /* Make sg buffers DMA ready */ + rc = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + dir); + + if (unlikely(rc != data->sg_len)) { + pr_err("%s: Unable to map in all sg elements, rc=%d\n", + mmc_hostname(host->mmc), rc); + rc = -ENOMEM; + goto dma_map_err; + } + + pr_debug("%s: %s: %s: sg_len=%d\n", + mmc_hostname(host->mmc), __func__, + dir == DMA_FROM_DEVICE ? "READ" : "WRITE", + data->sg_len); - box->src_dst_len = (MCI_FIFOSIZE << 16) | - (MCI_FIFOSIZE); - box->row_offset = (MCI_FIFOSIZE << 16); + goto out; + +dma_map_err: + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + data->flags); +out: + return rc; +} +#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) +{ + int rc = 0; + u32 flags; + int i; + u32 addr, len, data_cnt; + struct scatterlist *sg = data->sg; + struct sps_pipe *sps_pipe_handle; + + 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; + } - box->num_rows = rows * ((1 << 16) + 1); - box->cmd |= CMD_DST_CRCI(crci); + if (!data->host_cookie) { + rc = msmsdcc_prep_xfer(host, data); + if (unlikely(rc < 0)) { + host->dma.sg = NULL; + host->dma.num_ents = 0; + goto out; } - box++; } - return 0; -} + 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; -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; +dma_map_err: + /* unmap sg buffers */ + if (!data->host_cookie) + 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) { @@ -458,25 +1055,48 @@ msmsdcc_start_command_deferred(struct msmsdcc_host *host, if (/*interrupt*/0) *c |= MCI_CPSM_INTERRUPT; - if ((((cmd->opcode == 17) || (cmd->opcode == 18)) || - ((cmd->opcode == 24) || (cmd->opcode == 25))) || - (cmd->opcode == 53)) + /* DAT_CMD bit should be set for all ADTC */ + if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) *c |= MCI_CSPM_DATCMD; - if (host->prog_scan && (cmd->opcode == 12)) { + /* Check if AUTO CMD19 is required or not? */ + if (host->tuning_needed && + !(host->mmc->ios.timing == MMC_TIMING_MMC_HS200)) { + + /* + * For open ended block read operation (without CMD23), + * AUTO_CMD19 bit should be set while sending the READ command. + * For close ended block read operation (with CMD23), + * AUTO_CMD19 bit should be set while sending CMD23. + */ + if ((cmd->opcode == MMC_SET_BLOCK_COUNT && + host->curr.mrq->cmd->opcode == + MMC_READ_MULTIPLE_BLOCK) || + (!host->curr.mrq->sbc && + (cmd->opcode == MMC_READ_SINGLE_BLOCK || + cmd->opcode == MMC_READ_MULTIPLE_BLOCK))) { + msmsdcc_enable_cdr_cm_sdc4_dll(host); + *c |= MCI_CSPM_AUTO_CMD19; + } + } + + /* Clear CDR_EN bit for write operations */ + if (host->tuning_needed && cmd->mrq->data && + (cmd->mrq->data->flags & MMC_DATA_WRITE)) + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) & + ~MCI_CDR_EN), host->base + MCI_DLL_CONFIG); + + if ((cmd->flags & MMC_RSP_R1B) == MMC_RSP_R1B) { *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) { pr_err("%s: Overlapping command requests\n", - mmc_hostname(host->mmc)); + mmc_hostname(host->mmc)); } host->curr.cmd = cmd; } @@ -485,73 +1105,120 @@ static void msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data, struct mmc_command *cmd, u32 c) { - unsigned int datactrl, timeout; + unsigned int datactrl = 0, timeout; unsigned long long clks; + void __iomem *base = host->base; unsigned int pio_irqmask = 0; + BUG_ON(!data->sg); + BUG_ON(!data->sg_len); + host->curr.data = data; host->curr.xfer_size = data->blksz * data->blocks; host->curr.xfer_remain = host->curr.xfer_size; host->curr.data_xfered = 0; host->curr.got_dataend = 0; - - memset(&host->pio, 0, sizeof(host->pio)); + host->curr.got_auto_prog_done = 0; datactrl = MCI_DPSM_ENABLE | (data->blksz << 4); - if (!msmsdcc_config_dma(host, data)) - datactrl |= MCI_DPSM_DMAENABLE; - else { - host->pio.sg = data->sg; - host->pio.sg_len = data->sg_len; - host->pio.sg_off = 0; + if (host->curr.wait_for_auto_prog_done) + datactrl |= MCI_AUTO_PROG_DONE; + + if (msmsdcc_is_dma_possible(host, 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)) { if (data->flags & MMC_DATA_READ) { pio_irqmask = MCI_RXFIFOHALFFULLMASK; if (host->curr.xfer_remain < MCI_FIFOSIZE) pio_irqmask |= MCI_RXDATAAVLBLMASK; } else - pio_irqmask = MCI_TXFIFOHALFEMPTYMASK; + pio_irqmask = MCI_TXFIFOHALFEMPTYMASK | + MCI_TXFIFOEMPTYMASK; + + msmsdcc_sg_start(host); } if (data->flags & MMC_DATA_READ) - datactrl |= MCI_DPSM_DIRECTION; + datactrl |= (MCI_DPSM_DIRECTION | MCI_RX_DATA_PEND); + else if (host->curr.use_wr_data_pend) + datactrl |= MCI_DATA_PEND; 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 (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); - - msmsdcc_writel(host, host->curr.xfer_size, MMCIDATALENGTH); + /* SPS-BAM mode or PIO mode */ + writel_relaxed(timeout, base + MMCIDATATIMER); - msmsdcc_writel(host, (msmsdcc_readl(host, MMCIMASK0) & - (~MCI_IRQ_PIO)) | pio_irqmask, MMCIMASK0); + writel_relaxed(host->curr.xfer_size, base + MMCIDATALENGTH); - msmsdcc_writel(host, datactrl, MMCIDATACTRL); + writel_relaxed((readl_relaxed(host->base + MMCIMASK0) & + (~(MCI_IRQ_PIO))) | pio_irqmask, + host->base + MMCIMASK0); + writel_relaxed(datactrl, base + MMCIDATACTRL); if (cmd) { + /* Delay between data/command */ + msmsdcc_sync_reg_wr(host); /* Daisy-chain the command if requested */ msmsdcc_start_command(host, cmd, c); + } else { + /* + * We don't need delay after writing to DATA_CTRL + * register if we are not writing to CMD register + * immediately after this. As we already have delay + * before sending the command, we just need mb() here. + */ + mb(); } } } @@ -559,11 +1226,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); } @@ -573,15 +1235,32 @@ 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 + || data->mrq->cmd->opcode == + MMC_SEND_TUNING_BLOCK_HS200)) { + 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: CMD%d: Data timeout\n", + mmc_hostname(host->mmc), + data->mrq->cmd->opcode); + data->error = -ETIMEDOUT; + msmsdcc_dump_sdcc_state(host); + } } else if (status & MCI_RXOVERRUN) { pr_err("%s: RX overrun\n", mmc_hostname(host->mmc)); data->error = -EIO; @@ -590,23 +1269,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->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); @@ -619,16 +1303,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); @@ -638,103 +1322,267 @@ 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) +/* + * Copy up to a word (4 bytes) between a scatterlist + * and a temporary bounce buffer when the word lies across + * two pages. The temporary buffer can then be read to/ + * written from the FIFO once. + */ +static void _msmsdcc_sg_consume_word(struct msmsdcc_host *host) { - while (maxspin) { - if ((msmsdcc_readl(host, MMCISTATUS) & mask)) - return 0; - udelay(1); - --maxspin; + struct msmsdcc_pio_data *pio = &host->pio; + unsigned int bytes_avail; + + if (host->curr.data->flags & MMC_DATA_READ) + memcpy(pio->sg_miter.addr, pio->bounce_buf, + pio->bounce_buf_len); + else + memcpy(pio->bounce_buf, pio->sg_miter.addr, + pio->bounce_buf_len); + + while (pio->bounce_buf_len != 4) { + if (!sg_miter_next(&pio->sg_miter)) + break; + bytes_avail = min_t(unsigned int, pio->sg_miter.length, + 4 - pio->bounce_buf_len); + if (host->curr.data->flags & MMC_DATA_READ) + memcpy(pio->sg_miter.addr, + &pio->bounce_buf[pio->bounce_buf_len], + bytes_avail); + else + memcpy(&pio->bounce_buf[pio->bounce_buf_len], + pio->sg_miter.addr, bytes_avail); + + pio->sg_miter.consumed = bytes_avail; + pio->bounce_buf_len += bytes_avail; } - return -ETIMEDOUT; +} + +/* + * Use sg_miter_next to return as many 4-byte aligned + * chunks as possible, using a temporary 4 byte buffer + * for alignment if necessary + */ +static int msmsdcc_sg_next(struct msmsdcc_host *host, char **buf, int *len) +{ + struct msmsdcc_pio_data *pio = &host->pio; + unsigned int length, rlength; + char *buffer; + + if (!sg_miter_next(&pio->sg_miter)) + return 0; + + buffer = pio->sg_miter.addr; + length = pio->sg_miter.length; + + if (length < host->curr.xfer_remain) { + rlength = round_down(length, 4); + if (rlength) { + /* + * We have a 4-byte aligned chunk. + * The rounding will be reflected by + * a call to msmsdcc_sg_consumed + */ + length = rlength; + goto sg_next_end; + } + /* + * We have a length less than 4 bytes. Check to + * see if more buffer is available, and combine + * to make 4 bytes if possible. + */ + pio->bounce_buf_len = length; + memset(pio->bounce_buf, 0, 4); + + /* + * On a read, get 4 bytes from FIFO, and distribute + * (4-bouce_buf_len) bytes into consecutive + * sgl buffers when msmsdcc_sg_consumed is called + */ + if (host->curr.data->flags & MMC_DATA_READ) { + buffer = pio->bounce_buf; + length = 4; + goto sg_next_end; + } else { + _msmsdcc_sg_consume_word(host); + buffer = pio->bounce_buf; + length = pio->bounce_buf_len; + } + } + +sg_next_end: + *buf = buffer; + *len = length; + return 1; +} + +/* + * Update sg_miter.consumed based on how many bytes were + * consumed. If the bounce buffer was used to read from FIFO, + * redistribute into sgls. + */ +static void msmsdcc_sg_consumed(struct msmsdcc_host *host, + unsigned int length) +{ + struct msmsdcc_pio_data *pio = &host->pio; + + if (host->curr.data->flags & MMC_DATA_READ) { + if (length > pio->sg_miter.consumed) + /* + * consumed 4 bytes, but sgl + * describes < 4 bytes + */ + _msmsdcc_sg_consume_word(host); + else + pio->sg_miter.consumed = length; + } else + if (length < pio->sg_miter.consumed) + pio->sg_miter.consumed = length; +} + +static void msmsdcc_sg_start(struct msmsdcc_host *host) +{ + unsigned int sg_miter_flags = SG_MITER_ATOMIC; + + host->pio.bounce_buf_len = 0; + + if (host->curr.data->flags & MMC_DATA_READ) + sg_miter_flags |= SG_MITER_TO_SG; + else + sg_miter_flags |= SG_MITER_FROM_SG; + + sg_miter_start(&host->pio.sg_miter, host->curr.data->sg, + host->curr.data->sg_len, sg_miter_flags); +} + +static void msmsdcc_sg_stop(struct msmsdcc_host *host) +{ + sg_miter_stop(&host->pio.sg_miter); } 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; - u32 mci_mask0; + unsigned long flags; + unsigned int remain; + char *buffer; + + spin_lock(&host->lock); - status = msmsdcc_readl(host, MMCISTATUS); - mci_mask0 = msmsdcc_readl(host, MMCIMASK0); + status = readl_relaxed(base + MMCISTATUS); - if (((mci_mask0 & status) & MCI_IRQ_PIO) == 0) + if (((readl_relaxed(host->base + MMCIMASK0) & status) & + (MCI_IRQ_PIO)) == 0) { + spin_unlock(&host->lock); return IRQ_NONE; + } +#if IRQ_DEBUG + msmsdcc_print_status(host, "irq1-r", status); +#endif + local_irq_save(flags); do { - unsigned long flags; - unsigned int remain, len; - char *buffer; + unsigned int len; - if (!(status & (MCI_TXFIFOHALFEMPTY | MCI_RXDATAAVLBL))) { - if (host->curr.xfer_remain == 0 || !msmsdcc_piopoll) - break; + if (!(status & (MCI_TXFIFOHALFEMPTY | MCI_TXFIFOEMPTY + | MCI_RXDATAAVLBL))) + break; - if (msmsdcc_spin_on_status(host, - (MCI_TXFIFOHALFEMPTY | - MCI_RXDATAAVLBL), - PIO_SPINMAX)) { - break; - } - } + if (!msmsdcc_sg_next(host, &buffer, &remain)) + break; - /* Map the current scatter buffer */ - local_irq_save(flags); - buffer = kmap_atomic(sg_page(host->pio.sg)) - + 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); - local_irq_restore(flags); + /* len might have aligned to 32bits above */ + if (len > remain) + len = remain; - host->pio.sg_off += len; host->curr.xfer_remain -= len; host->curr.data_xfered += len; remain -= len; + msmsdcc_sg_consumed(host, 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; - } + status = readl_relaxed(base + MMCISTATUS); + } while (1); - /* Advance to next sg */ - host->pio.sg++; - host->pio.sg_off = 0; + msmsdcc_sg_stop(host); + local_irq_restore(flags); + + 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) { + /* + * back to back write to MASK0 register don't need + * synchronization delay. + */ + 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(); + } - status = msmsdcc_readl(host, MMCISTATUS); - } while (1); + spin_unlock(&host->lock); + + return IRQ_HANDLED; +} - if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE) - msmsdcc_writel(host, (mci_mask0 & (~MCI_IRQ_PIO)) | - MCI_RXDATAAVLBLMASK, MMCIMASK0); +static void +msmsdcc_request_start(struct msmsdcc_host *host, struct mmc_request *mrq); - if (!host->curr.xfer_remain) - msmsdcc_writel(host, (mci_mask0 & (~MCI_IRQ_PIO)) | 0, - MMCIMASK0); +static void msmsdcc_wait_for_rxdata(struct msmsdcc_host *host, + struct mmc_data *data) +{ + u32 loop_cnt = 0; - return IRQ_HANDLED; + /* + * 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) @@ -742,23 +1590,47 @@ 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 (mmc_resp_type(cmd)) + cmd->resp[0] = readl_relaxed(host->base + MMCIRESPONSE0); + /* + * Read rest of the response registers only if + * long response is expected for this command + */ + if (mmc_resp_type(cmd) & MMC_RSP_136) { + 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) { + if (status & (MCI_CMDTIMEOUT | MCI_AUTOCMD19TIMEOUT)) { + pr_debug("%s: CMD%d: Command timeout\n", + mmc_hostname(host->mmc), cmd->opcode); cmd->error = -ETIMEDOUT; - } else if (status & MCI_CMDCRCFAIL && - cmd->flags & MMC_RSP_CRC) { - pr_err("%s: Command CRC error\n", mmc_hostname(host->mmc)); + } else if ((status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) && + !host->tuning_in_progress) { + pr_err("%s: CMD%d: Command CRC error\n", + mmc_hostname(host->mmc), cmd->opcode); + msmsdcc_dump_sdcc_state(host); cmd->error = -EILSEQ; } + if (!cmd->error) { + if (cmd->cmd_timeout_ms > host->curr.req_tout_ms) { + host->curr.req_tout_ms = cmd->cmd_timeout_ms; + mod_timer(&host->req_tout_timer, (jiffies + + msecs_to_jiffies(host->curr.req_tout_ms))); + } + } + if (!cmd->data || cmd->error) { - if (host->curr.data && host->dma.sg) - msm_dmov_stop_cmd(host->dma.channel, - &host->dma.hdr, 0); + if (host->curr.data && host->dma.sg && + host->is_dma_mode) + msm_dmov_flush(host->dma.channel, 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); @@ -766,87 +1638,26 @@ 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; + host->prog_enable = 0; msmsdcc_request_end(host, cmd->mrq); - } else { + } else host->curr.cmd = cmd; - } } else { - if (host->prog_enable) { - host->prog_scan = false; - host->prog_enable = false; - } + host->prog_enable = 0; + host->curr.wait_for_auto_prog_done = 0; + if (host->dummy_52_needed) + host->dummy_52_needed = 0; + if (cmd->data && cmd->error) + msmsdcc_reset_and_restore(host); msmsdcc_request_end(host, cmd->mrq); } } - } 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); + } else if (cmd->data) { + if (cmd == host->curr.mrq->sbc) + msmsdcc_start_command(host, host->curr.mrq->cmd, 0); + else if ((cmd->data->flags & MMC_DATA_WRITE) && + !host->curr.use_wr_data_pend) + msmsdcc_start_data(host, cmd->data, NULL, 0); } } @@ -854,60 +1665,315 @@ 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); - if ((status & (~MCI_IRQ_PIO)) == 0) + struct mmc_command *cmd; + struct mmc_data *data; + + if (timer) { + timer = 0; + msmsdcc_delay(host); + } + + if (!host->clks_on) { + pr_debug("%s: %s: SDIO async irq received\n", + mmc_hostname(host->mmc), __func__); + + /* + * Only async interrupt can come when clocks are off, + * disable further interrupts and enable them when + * clocks are on. + */ + if (!host->sdcc_irq_disabled) { + disable_irq_nosync(irq); + host->sdcc_irq_disabled = 1; + } + + /* + * If mmc_card_wake_sdio_irq() is set, mmc core layer + * will take care of signaling sdio irq during + * mmc_sdio_resume(). + */ + if (host->sdcc_suspended) { + /* + * This is a wakeup interrupt so hold wakelock + * until SDCC resume is handled. + */ + wake_lock(&host->sdio_wlock); + } else { + spin_unlock(&host->lock); + mmc_signal_sdio_irq(host->mmc); + spin_lock(&host->lock); + } + ret = 1; + break; + } + + status = readl_relaxed(host->base + MMCISTATUS); + + if (((readl_relaxed(host->base + MMCIMASK0) & status) & + (~(MCI_IRQ_PIO))) == 0) break; - msmsdcc_writel(host, status, MMCICLEAR); - if (status & MCI_SDIOINTR) - status &= ~MCI_SDIOINTR; +#if IRQ_DEBUG + msmsdcc_print_status(host, "irq0-r", status); +#endif + status &= readl_relaxed(host->base + MMCIMASK0); + writel_relaxed(status, host->base + MMCICLEAR); + /* Allow clear to take effect*/ + if (host->clk_rate <= + msmsdcc_get_min_sup_clk_rate(host)) + msmsdcc_sync_reg_wr(host); +#if IRQ_DEBUG + msmsdcc_print_status(host, "irq0-p", status); +#endif - if (!status) + if (status & MCI_SDIOINTROPE) { + if (host->sdcc_suspending) + wake_lock(&host->sdio_suspend_wlock); + spin_unlock(&host->lock); + mmc_signal_sdio_irq(host->mmc); + spin_lock(&host->lock); + } + data = host->curr.data; + + if (host->dummy_52_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_sent = 0; + host->dummy_52_needed = 0; + if (data) { + msmsdcc_stop_data(host); + msmsdcc_request_end(host, data->mrq); + } + WARN(!data, "No data cmd for dummy CMD52\n"); + spin_unlock(&host->lock); + return IRQ_HANDLED; + } break; + } + + /* + * 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); + } - msmsdcc_handle_irq_data(host, status, base); + if (host->curr.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_flush(host->dma.channel, 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 || (host->curr.mrq->sbc + && !data->error)) + timer |= + msmsdcc_request_end(host, + data->mrq); + else if ((host->curr.mrq->sbc + && data->error) || + !host->curr.mrq->sbc) { + msmsdcc_start_command(host, + data->stop, + 0); + timer = 1; + } + } + } - if (status & MCI_SDIOINTOPER) { - cardint = 1; - status &= ~MCI_SDIOINTOPER; + /* Check for prog done */ + if (host->curr.wait_for_auto_prog_done && + (status & MCI_PROGDONE)) + host->curr.got_auto_prog_done = 1; + + /* Check for data done */ + if (!host->curr.got_dataend && (status & MCI_DATAEND)) + host->curr.got_dataend = 1; + + if (host->curr.got_dataend && + (!host->curr.wait_for_auto_prog_done || + (host->curr.wait_for_auto_prog_done && + host->curr.got_auto_prog_done))) { + /* + * 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); + if (!data->error) { + host->curr.data_xfered = + host->curr.xfer_size; + host->curr.xfer_remain -= + host->curr.xfer_size; + } + + if (!host->dummy_52_needed) { + msmsdcc_stop_data(host); + if (!data->stop || + (host->curr.mrq->sbc + && !data->error)) + msmsdcc_request_end( + host, + data->mrq); + else if ((host->curr.mrq->sbc + && data->error) || + !host->curr.mrq->sbc) { + msmsdcc_start_command( + host, + data->stop, 0); + timer = 1; + } + } else { + host->dummy_52_sent = 1; + msmsdcc_start_command(host, + &dummy52cmd, + MCI_CPSM_PROGENA); + } + } + } } + 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_pre_req(struct mmc_host *mmc, struct mmc_request *mrq, + bool is_first_request) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + int rc = 0; + + if (unlikely(!data)) { + pr_err("%s: %s cannot prepare null data\n", mmc_hostname(mmc), + __func__); + return; + } + if (unlikely(data->host_cookie)) { + /* Very wrong */ + data->host_cookie = 0; + pr_err("%s: %s Request reposted for prepare\n", + mmc_hostname(mmc), __func__); + return; + } + + if (!msmsdcc_is_dma_possible(host, data)) + return; + + rc = msmsdcc_prep_xfer(host, data); + if (unlikely(rc < 0)) { + data->host_cookie = 0; + return; + } + + data->host_cookie = 1; +} + +static void +msmsdcc_post_req(struct mmc_host *mmc, struct mmc_request *mrq, int err) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned int dir; + struct mmc_data *data = mrq->data; + + if (unlikely(!data)) { + pr_err("%s: %s cannot cleanup null data\n", mmc_hostname(mmc), + __func__); + return; + } + if (data->flags & MMC_DATA_READ) + dir = DMA_FROM_DEVICE; + else + dir = DMA_TO_DEVICE; + + if (data->host_cookie) + dma_unmap_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, dir); + + data->host_cookie = 0; +} + +static void +msmsdcc_request_start(struct msmsdcc_host *host, struct mmc_request *mrq) +{ + if (mrq->data) { + /* Queue/read data, daisy-chain command when data starts */ + if ((mrq->data->flags & MMC_DATA_READ) || + host->curr.use_wr_data_pend) + msmsdcc_start_data(host, mrq->data, + mrq->sbc ? mrq->sbc : mrq->cmd, + 0); + else + msmsdcc_start_command(host, + mrq->sbc ? mrq->sbc : 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. + */ + WARN(host->dummy_52_sent, "Dummy CMD52 in progress\n"); + if (host->plat->is_sdio_al_client) + msmsdcc_sdio_al_lpm(mmc, false); + + /* check if sps pipe reset is pending? */ + if (host->is_sps_mode && host->sps.pipe_reset_pending) { + msmsdcc_sps_pipes_reset_and_restore(host); + host->sps.pipe_reset_pending = false; + } spin_lock_irqsave(&host->lock, flags); - host->stats.reqs++; - if (host->eject) { if (mrq->data && !(mrq->data->flags & MMC_DATA_READ)) { mrq->cmd->error = 0; @@ -921,260 +1987,2748 @@ msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } - msmsdcc_enable_clocks(host); + /* + * Don't start the request if SDCC is not in proper state to handle it + */ + if (!host->pwr || !host->clks_on || host->sdcc_irq_disabled) { + WARN(1, "%s: %s: SDCC is in bad state. don't process" + " new request (CMD%d)\n", mmc_hostname(host->mmc), + __func__, mrq->cmd->opcode); + msmsdcc_dump_sdcc_state(host); + mrq->cmd->error = -EIO; + if (mrq->data) { + mrq->data->error = -EIO; + mrq->data->bytes_xfered = 0; + } + spin_unlock_irqrestore(&host->lock, flags); + mmc_request_done(mmc, mrq); + return; + } - host->curr.mrq = mrq; + WARN(host->curr.mrq, "%s: %s: New request (CMD%d) received while" + " other request (CMD%d) is in progress\n", + mmc_hostname(host->mmc), __func__, + mrq->cmd->opcode, host->curr.mrq->cmd->opcode); - 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); + /* + * Set timeout value to 10 secs (or more in case of buggy cards) + */ + if ((mmc->card) && (mmc->card->quirks & MMC_QUIRK_INAND_DATA_TIMEOUT)) + host->curr.req_tout_ms = 20000; else - msmsdcc_start_command(host, mrq->cmd, 0); + host->curr.req_tout_ms = MSM_MMC_REQ_TIMEOUT; + /* + * Kick the software request timeout timer here with the timeout + * value identified above + */ + mod_timer(&host->req_tout_timer, + (jiffies + + msecs_to_jiffies(host->curr.req_tout_ms))); + + host->curr.mrq = mrq; + if (mrq->data && (mrq->data->flags & MMC_DATA_WRITE)) { + if (mrq->cmd->opcode == SD_IO_RW_EXTENDED || + mrq->cmd->opcode == 54) { + if (!host->sdcc_version) + host->dummy_52_needed = 1; + else + /* + * SDCCv4 supports AUTO_PROG_DONE bit for SDIO + * write operations using CMD53 and CMD54. + * Setting this bit with CMD53 would + * automatically triggers PROG_DONE interrupt + * without the need of sending dummy CMD52. + */ + host->curr.wait_for_auto_prog_done = 1; + } else if (mrq->cmd->opcode == MMC_WRITE_BLOCK && + host->sdcc_version) { + host->curr.wait_for_auto_prog_done = 1; + } + if ((mrq->cmd->opcode == MMC_WRITE_BLOCK) || + (mrq->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK)) + host->curr.use_wr_data_pend = true; + } + + if (mrq->data && mrq->sbc) { + mrq->sbc->mrq = mrq; + mrq->sbc->data = mrq->data; + if (mrq->data->flags & MMC_DATA_WRITE) + host->curr.wait_for_auto_prog_done = 1; + } + msmsdcc_request_start(host, mrq); + + spin_unlock_irqrestore(&host->lock, flags); +} + +static inline int msmsdcc_vreg_set_voltage(struct msm_mmc_reg_data *vreg, + int min_uV, int max_uV) +{ + int rc = 0; + + 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); + } + } + + return rc; +} + +static inline int msmsdcc_vreg_set_optimum_mode(struct msm_mmc_reg_data *vreg, + int uA_load) +{ + int rc = 0; + + /* regulators that do not support regulator_set_voltage also + do not support regulator_set_optimum_mode */ + if (vreg->set_voltage_sup) { + 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; + } + + return rc; +} + +static inline int msmsdcc_vreg_init_reg(struct msm_mmc_reg_data *vreg, + struct device *dev) +{ + int rc = 0; + + /* 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); + goto out; + } + + if (regulator_count_voltages(vreg->reg) > 0) + vreg->set_voltage_sup = 1; + +out: + return rc; +} + +static inline void msmsdcc_vreg_deinit_reg(struct msm_mmc_reg_data *vreg) +{ + if (vreg->reg) + regulator_put(vreg->reg); +} + +/* 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); + + curr_slot = host->plat->vreg_data; + if (!curr_slot) + goto out; + + curr_vdd_reg = curr_slot->vdd_data; + curr_vccq_reg = curr_slot->vccq_data; + curr_vddp_reg = curr_slot->vddp_data; + + 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; + } + rc = msmsdcc_vreg_reset(host); + if (rc) + pr_err("msmsdcc.%d vreg reset failed (%d)\n", + host->pdev_id, rc); + 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; + + /* Put regulator in HPM (high power mode) */ + rc = msmsdcc_vreg_set_optimum_mode(vreg, vreg->hpm_uA); + if (rc < 0) + goto out; + + if (!vreg->is_enabled) { + /* Set voltage level */ + rc = msmsdcc_vreg_set_voltage(vreg, vreg->high_vol_level, + vreg->high_vol_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; + } + +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->high_vol_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; +} + +/* + * Reset vreg by ensuring it is off during probe. A call + * to enable vreg is needed to balance disable vreg + */ +static int msmsdcc_vreg_reset(struct msmsdcc_host *host) +{ + int rc; + + rc = msmsdcc_setup_vreg(host, 1); + if (rc) + return rc; + rc = msmsdcc_setup_vreg(host, 0); + return rc; +} + +static int msmsdcc_set_vddp_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_set_vddp_low_vol(struct msmsdcc_host *host) +{ + struct msm_mmc_slot_reg_data *curr_slot = host->plat->vreg_data; + int rc = 0; + + if (curr_slot && curr_slot->vddp_data) { + rc = msmsdcc_set_vddp_level(host, + curr_slot->vddp_data->low_vol_level); + + if (rc) + pr_err("%s: %s: failed to change vddp level to %d", + mmc_hostname(host->mmc), __func__, + curr_slot->vddp_data->low_vol_level); + } + + return rc; +} + +static inline int msmsdcc_set_vddp_high_vol(struct msmsdcc_host *host) +{ + struct msm_mmc_slot_reg_data *curr_slot = host->plat->vreg_data; + int rc = 0; + + if (curr_slot && curr_slot->vddp_data) { + rc = msmsdcc_set_vddp_level(host, + curr_slot->vddp_data->high_vol_level); + + if (rc) + pr_err("%s: %s: failed to change vddp level to %d", + mmc_hostname(host->mmc), __func__, + curr_slot->vddp_data->high_vol_level); + } + + return rc; +} + +static inline int msmsdcc_set_vccq_vol(struct msmsdcc_host *host, int level) +{ + struct msm_mmc_slot_reg_data *curr_slot = host->plat->vreg_data; + int rc = 0; + + if (curr_slot && curr_slot->vccq_data) { + rc = msmsdcc_vreg_set_voltage(curr_slot->vccq_data, + level, level); + if (rc) + pr_err("%s: %s: failed to change vccq level to %d", + mmc_hostname(host->mmc), __func__, level); + } + + return rc; +} + +static inline int msmsdcc_is_pwrsave(struct msmsdcc_host *host) +{ + if (host->clk_rate > 400000 && msmsdcc_pwrsave) + return 1; + return 0; +} + +/* + * Any function calling msmsdcc_setup_clocks must + * acquire clk_mutex. May sleep. + */ +static inline void msmsdcc_setup_clocks(struct msmsdcc_host *host, bool enable) +{ + if (enable) { + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_prepare_enable(host->dfab_pclk); + if (!IS_ERR(host->pclk)) + clk_prepare_enable(host->pclk); + clk_prepare_enable(host->clk); + mb(); + msmsdcc_delay(host); + } else { + mb(); + msmsdcc_delay(host); + clk_disable_unprepare(host->clk); + if (!IS_ERR(host->pclk)) + clk_disable_unprepare(host->pclk); + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_disable_unprepare(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 (req_clk < msmsdcc_get_min_sup_clk_rate(host)) { + sel_clk = msmsdcc_get_min_sup_clk_rate(host); + goto out; + } - 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++; + 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 { - host->stats.cmdpoll_misses++; + if ((req_clk < host->plat->msmsdcc_fmax) && + (req_clk > host->plat->msmsdcc_fmid)) + sel_clk = host->plat->msmsdcc_fmid; + else + sel_clk = req_clk; + } + +out: + 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_pull(curr->pull->on[i].no, + curr->pull->on[i].val); + else + msm_tlmm_set_pull(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 int msmsdcc_cfg_mpm_sdiowakeup(struct msmsdcc_host *host, + unsigned mode) +{ + int ret = 0; + unsigned int pin = host->plat->mpm_sdiowakeup_int; + + if (!pin) + return 0; + + 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; +} + +static u32 msmsdcc_setup_pwr(struct msmsdcc_host *host, struct mmc_ios *ios) +{ + u32 pwr = 0; + int ret = 0; + struct mmc_host *mmc = host->mmc; + + if (host->plat->translate_vdd && !host->sdio_gpio_lpm) + ret = host->plat->translate_vdd(mmc_dev(mmc), ios->vdd); + else if (!host->plat->translate_vdd && !host->sdio_gpio_lpm) + ret = msmsdcc_setup_vreg(host, !!ios->vdd); + + if (ret) { + pr_err("%s: Failed to setup voltage regulators\n", + mmc_hostname(host->mmc)); + goto out; + } + + switch (ios->power_mode) { + case MMC_POWER_OFF: + pwr = MCI_PWR_OFF; + msmsdcc_cfg_mpm_sdiowakeup(host, SDC_DAT1_DISABLE); + /* + * As VDD pad rail is always on, set low voltage for VDD + * pad rail when slot is unused (when card is not present + * or during system suspend). + */ + msmsdcc_set_vddp_low_vol(host); + msmsdcc_setup_pins(host, false); + break; + case MMC_POWER_UP: + /* writing PWR_UP bit is redundant */ + pwr = MCI_PWR_UP; + msmsdcc_cfg_mpm_sdiowakeup(host, SDC_DAT1_ENABLE); + + msmsdcc_set_vddp_high_vol(host); + msmsdcc_setup_pins(host, true); + break; + case MMC_POWER_ON: + pwr = MCI_PWR_ON; + break; + } + +out: + return pwr; +} + +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; + } +} + +/* Returns required bandwidth in Bytes per Sec */ +static unsigned int msmsdcc_get_bw_required(struct msmsdcc_host *host, + struct mmc_ios *ios) +{ + unsigned int bw; + + bw = host->clk_rate; + /* + * For DDR mode, SDCC controller clock will be at + * the double rate than the actual clock that goes to card. + */ + if (ios->bus_width == MMC_BUS_WIDTH_4) + bw /= 2; + else if (ios->bus_width == MMC_BUS_WIDTH_1) + bw /= 8; + + return bw; +} + +static int msmsdcc_msm_bus_get_vote_for_bw(struct msmsdcc_host *host, + unsigned int bw) +{ + unsigned int *table = host->plat->msm_bus_voting_data->bw_vecs; + unsigned int size = host->plat->msm_bus_voting_data->bw_vecs_size; + int i; + + if (host->msm_bus_vote.is_max_bw_needed && bw) + return host->msm_bus_vote.max_bw_vote; + + for (i = 0; i < size; i++) { + if (bw <= table[i]) + break; + } + + if (i && (i == size)) + i--; + + return i; +} + +static int msmsdcc_msm_bus_register(struct msmsdcc_host *host) +{ + int rc = 0; + struct msm_bus_scale_pdata *use_cases; + + if (host->plat->msm_bus_voting_data && + host->plat->msm_bus_voting_data->use_cases && + host->plat->msm_bus_voting_data->bw_vecs && + host->plat->msm_bus_voting_data->bw_vecs_size) { + use_cases = host->plat->msm_bus_voting_data->use_cases; + host->msm_bus_vote.client_handle = + msm_bus_scale_register_client(use_cases); + } else { + return 0; + } + + if (!host->msm_bus_vote.client_handle) { + pr_err("%s: msm_bus_scale_register_client() failed\n", + mmc_hostname(host->mmc)); + rc = -EFAULT; + } else { + /* cache the vote index for minimum and maximum bandwidth */ + host->msm_bus_vote.min_bw_vote = + msmsdcc_msm_bus_get_vote_for_bw(host, 0); + host->msm_bus_vote.max_bw_vote = + msmsdcc_msm_bus_get_vote_for_bw(host, UINT_MAX); + } + + return rc; +} + +static void msmsdcc_msm_bus_unregister(struct msmsdcc_host *host) +{ + if (host->msm_bus_vote.client_handle) + msm_bus_scale_unregister_client( + host->msm_bus_vote.client_handle); +} + +/* + * This function must be called with host lock acquired. + * Caller of this function should also ensure that msm bus client + * handle is not null. + */ +static inline int msmsdcc_msm_bus_set_vote(struct msmsdcc_host *host, + int vote, + unsigned long flags) +{ + int rc = 0; + + if (vote != host->msm_bus_vote.curr_vote) { + spin_unlock_irqrestore(&host->lock, flags); + rc = msm_bus_scale_client_update_request( + host->msm_bus_vote.client_handle, vote); + if (rc) + pr_err("%s: msm_bus_scale_client_update_request() failed." + " bus_client_handle=0x%x, vote=%d, err=%d\n", + mmc_hostname(host->mmc), + host->msm_bus_vote.client_handle, vote, rc); + spin_lock_irqsave(&host->lock, flags); + if (!rc) + host->msm_bus_vote.curr_vote = vote; + } + + return rc; +} + +/* + * Internal work. Work to set 0 bandwidth for msm bus. + */ +static void msmsdcc_msm_bus_work(struct work_struct *work) +{ + struct msmsdcc_host *host = container_of(work, + struct msmsdcc_host, + msm_bus_vote.vote_work.work); + unsigned long flags; + + if (!host->msm_bus_vote.client_handle) + return; + + spin_lock_irqsave(&host->lock, flags); + /* don't vote for 0 bandwidth if any request is in progress */ + if (!host->curr.mrq) + msmsdcc_msm_bus_set_vote(host, + host->msm_bus_vote.min_bw_vote, flags); + else + pr_warning("%s: %s: SDCC transfer in progress. skipping" + " bus voting to 0 bandwidth\n", + mmc_hostname(host->mmc), __func__); + spin_unlock_irqrestore(&host->lock, flags); +} + +/* + * This function cancels any scheduled delayed work + * and sets the bus vote based on ios argument. + * If "ios" argument is NULL, bandwidth required is 0 else + * calculate the bandwidth based on ios parameters. + */ +static void msmsdcc_msm_bus_cancel_work_and_set_vote( + struct msmsdcc_host *host, + struct mmc_ios *ios) +{ + unsigned long flags; + unsigned int bw; + int vote; + + if (!host->msm_bus_vote.client_handle) + return; + + bw = ios ? msmsdcc_get_bw_required(host, ios) : 0; + + cancel_delayed_work_sync(&host->msm_bus_vote.vote_work); + spin_lock_irqsave(&host->lock, flags); + vote = msmsdcc_msm_bus_get_vote_for_bw(host, bw); + msmsdcc_msm_bus_set_vote(host, vote, flags); + spin_unlock_irqrestore(&host->lock, flags); +} + +/* This function queues a work which will set the bandwidth requiement to 0 */ +static void msmsdcc_msm_bus_queue_work(struct msmsdcc_host *host) +{ + unsigned long flags; + + if (!host->msm_bus_vote.client_handle) + return; + + spin_lock_irqsave(&host->lock, flags); + if (host->msm_bus_vote.min_bw_vote != host->msm_bus_vote.curr_vote) + queue_delayed_work(system_nrt_wq, + &host->msm_bus_vote.vote_work, + msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY)); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void +msmsdcc_cfg_sdio_wakeup(struct msmsdcc_host *host, bool enable_wakeup_irq) +{ + struct mmc_host *mmc = host->mmc; + + /* + * SDIO_AL clients has different mechanism of handling LPM through + * sdio_al driver itself. The sdio wakeup interrupt is configured as + * part of that. Here, we are interested only in clients like WLAN. + */ + if (!(mmc->card && mmc_card_sdio(mmc->card)) + || host->plat->is_sdio_al_client) + goto out; + + if (!host->sdcc_suspended) { + /* + * When MSM is not in power collapse and we + * are disabling clocks, enable bit 22 in MASK0 + * to handle asynchronous SDIO interrupts. + */ + if (enable_wakeup_irq) { + writel_relaxed(MCI_SDIOINTMASK, host->base + MMCIMASK0); + mb(); + } else { + writel_relaxed(MCI_SDIOINTMASK, host->base + MMCICLEAR); + msmsdcc_sync_reg_wr(host); + } + goto out; + } else if (!mmc_card_wake_sdio_irq(mmc)) { + /* + * Wakeup MSM only if SDIO function drivers set + * MMC_PM_WAKE_SDIO_IRQ flag in their suspend call. + */ + goto out; + } + + if (enable_wakeup_irq) { + if (!host->plat->sdiowakeup_irq) { + /* + * When there is no gpio line that can be configured + * as wakeup interrupt handle it by configuring + * asynchronous sdio interrupts and DAT1 line. + */ + writel_relaxed(MCI_SDIOINTMASK, + host->base + MMCIMASK0); + mb(); + msmsdcc_cfg_mpm_sdiowakeup(host, SDC_DAT1_ENWAKE); + /* configure sdcc core interrupt as wakeup interrupt */ + msmsdcc_enable_irq_wake(host); + } else { + /* Let gpio line handle wakeup interrupt */ + writel_relaxed(0, host->base + MMCIMASK0); + mb(); + if (host->sdio_wakeupirq_disabled) { + host->sdio_wakeupirq_disabled = 0; + /* configure gpio line as wakeup interrupt */ + msmsdcc_enable_irq_wake(host); + enable_irq(host->plat->sdiowakeup_irq); + } + } + } else { + if (!host->plat->sdiowakeup_irq) { + /* + * We may not have cleared bit 22 in the interrupt + * handler as the clocks might be off at that time. + */ + writel_relaxed(MCI_SDIOINTMASK, host->base + MMCICLEAR); + msmsdcc_sync_reg_wr(host); + msmsdcc_cfg_mpm_sdiowakeup(host, SDC_DAT1_DISWAKE); + msmsdcc_disable_irq_wake(host); + } else if (!host->sdio_wakeupirq_disabled) { + disable_irq_nosync(host->plat->sdiowakeup_irq); + msmsdcc_disable_irq_wake(host); + host->sdio_wakeupirq_disabled = 1; + } + } +out: + return; +} + +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; + + + /* + * Disable SDCC core interrupt until set_ios is completed. + * This avoids any race conditions with interrupt raised + * when turning on/off the clocks. One possible + * scenario is SDIO operational interrupt while the clock + * is turned off. + * host->lock is being released intermittently below. + * Thus, prevent concurrent access to host. + */ + + mutex_lock(&host->clk_mutex); + DBG(host, "ios->clock = %u\n", ios->clock); + spin_lock_irqsave(&host->lock, flags); + if (!host->sdcc_irq_disabled) { + spin_unlock_irqrestore(&host->lock, flags); + disable_irq(host->core_irqres->start); + spin_lock_irqsave(&host->lock, flags); + host->sdcc_irq_disabled = 1; + } + spin_unlock_irqrestore(&host->lock, flags); + + pwr = msmsdcc_setup_pwr(host, ios); + + spin_lock_irqsave(&host->lock, flags); + if (ios->clock) { + if (!host->clks_on) { + spin_unlock_irqrestore(&host->lock, flags); + msmsdcc_setup_clocks(host, true); + spin_lock_irqsave(&host->lock, flags); + host->clks_on = 1; + writel_relaxed(host->mci_irqenable, + host->base + MMCIMASK0); + mb(); + msmsdcc_cfg_sdio_wakeup(host, false); + } + + 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) { + spin_unlock_irqrestore(&host->lock, flags); + rc = clk_set_rate(host->clk, clock); + spin_lock_irqsave(&host->lock, flags); + if (rc < 0) + pr_err("%s: failed to set clk rate %u\n", + mmc_hostname(mmc), clock); + host->clk_rate = clock; + host->reg_write_delay = + (1 + ((3 * USEC_PER_SEC) / + (host->clk_rate ? host->clk_rate : + msmsdcc_get_min_sup_clk_rate(host)))); + } + /* + * give atleast 2 MCLK cycles delay for clocks + * and SDCC core to stabilize + */ + mb(); + 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_MMC_HS200)) { + 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); + + /* Clear IO_PAD_PWR_SWITCH while powering off the card */ + if (!ios->vdd) + host->io_pad_pwr_switch = 0; + + if (host->io_pad_pwr_switch) + clk |= IO_PAD_PWR_SWITCH; + + /* Don't write into registers if clocks are disabled */ + if (host->clks_on) { + if (readl_relaxed(host->base + MMCICLOCK) != clk) { + writel_relaxed(clk, host->base + MMCICLOCK); + msmsdcc_sync_reg_wr(host); + } + if (readl_relaxed(host->base + MMCIPOWER) != pwr) { + host->pwr = pwr; + writel_relaxed(pwr, host->base + MMCIPOWER); + msmsdcc_sync_reg_wr(host); + } + } + + if (!(clk & MCI_CLK_ENABLE) && host->clks_on) { + msmsdcc_cfg_sdio_wakeup(host, true); + spin_unlock_irqrestore(&host->lock, flags); + /* + * May get a wake-up interrupt the instant we disable the + * clocks. This would disable the wake-up interrupt. + */ + msmsdcc_setup_clocks(host, false); + spin_lock_irqsave(&host->lock, flags); + host->clks_on = 0; + } + + if (host->tuning_in_progress) + WARN(!host->clks_on, + "tuning_in_progress but SDCC clocks are OFF\n"); + + /* Let interrupts be disabled if the host is powered off */ + if (ios->power_mode != MMC_POWER_OFF && host->sdcc_irq_disabled) { + enable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 0; + } + + spin_unlock_irqrestore(&host->lock, flags); + mutex_unlock(&host->clk_mutex); +} + +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); + msmsdcc_sync_reg_wr(host); + + 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; +} + +static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + + /* + * We may come here with clocks turned off in that case don't + * attempt to write into MASK0 register. While turning on the + * clocks mci_irqenable will be written to MASK0 register. + */ + + spin_lock_irqsave(&host->lock, flags); + if (enable) { + host->mci_irqenable |= MCI_SDIOINTOPERMASK; + if (host->clks_on) { + writel_relaxed(readl_relaxed(host->base + MMCIMASK0) | + MCI_SDIOINTOPERMASK, host->base + MMCIMASK0); + mb(); + } + } else { + host->mci_irqenable &= ~MCI_SDIOINTOPERMASK; + if (host->clks_on) { + writel_relaxed(readl_relaxed(host->base + MMCIMASK0) & + ~MCI_SDIOINTOPERMASK, host->base + MMCIMASK0); + mb(); + } + } + spin_unlock_irqrestore(&host->lock, flags); +} + +#ifdef CONFIG_PM_RUNTIME +static void msmsdcc_print_rpm_info(struct msmsdcc_host *host) +{ + struct device *dev = mmc_dev(host->mmc); + + pr_info("%s: RPM: runtime_status=%d, usage_count=%d," + " is_suspended=%d, disable_depth=%d, runtime_error=%d," + " request_pending=%d, request=%d\n", + mmc_hostname(host->mmc), dev->power.runtime_status, + atomic_read(&dev->power.usage_count), + dev->power.is_suspended, dev->power.disable_depth, + dev->power.runtime_error, dev->power.request_pending, + dev->power.request); +} + +static int msmsdcc_enable(struct mmc_host *mmc) +{ + int rc = 0; + struct device *dev = mmc->parent; + struct msmsdcc_host *host = mmc_priv(mmc); + + msmsdcc_pm_qos_update_latency(host, 1); + + if (mmc->card && mmc_card_sdio(mmc->card)) + goto out; + + if (host->sdcc_suspended && host->pending_resume && + !pm_runtime_suspended(dev)) { + host->pending_resume = false; + pm_runtime_get_noresume(dev); + rc = msmsdcc_runtime_resume(dev); + goto skip_get_sync; + } + + if (dev->power.runtime_status == RPM_SUSPENDING) { + if (mmc->suspend_task == current) { + pm_runtime_get_noresume(dev); + goto out; + } + } + + rc = pm_runtime_get_sync(dev); + +skip_get_sync: + if (rc < 0) { + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, rc); + msmsdcc_print_rpm_info(host); + return rc; + } +out: + msmsdcc_msm_bus_cancel_work_and_set_vote(host, &mmc->ios); + return 0; +} + +static int msmsdcc_disable(struct mmc_host *mmc) +{ + int rc; + struct msmsdcc_host *host = mmc_priv(mmc); + + msmsdcc_pm_qos_update_latency(host, 0); + + if (mmc->card && mmc_card_sdio(mmc->card)) { + rc = 0; + goto out; + } + + if (host->plat->disable_runtime_pm) + return -ENOTSUPP; + + rc = pm_runtime_put_sync(mmc->parent); + + /* + * Ignore -EAGAIN as that is not fatal, it means that + * either runtime usage count is non-zero or the runtime + * pm itself is disabled or not in proper state to process + * idle notification. + */ + if (rc < 0 && (rc != -EAGAIN)) { + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, rc); + msmsdcc_print_rpm_info(host); + return rc; + } + +out: + msmsdcc_msm_bus_queue_work(host); + return rc; +} +#else +static void msmsdcc_print_rpm_info(struct msmsdcc_host *host) {} + +static int msmsdcc_enable(struct mmc_host *mmc) +{ + struct device *dev = mmc->parent; + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + int rc = 0; + + msmsdcc_pm_qos_update_latency(host, 1); + + if (mmc->card && mmc_card_sdio(mmc->card)) { + rc = 0; + goto out; + } + + if (host->sdcc_suspended && host->pending_resume) { + host->pending_resume = false; + rc = msmsdcc_runtime_resume(dev); + goto out; + } + + mutex_lock(&host->clk_mutex); + spin_lock_irqsave(&host->lock, flags); + if (!host->clks_on) { + spin_unlock_irqrestore(&host->lock, flags); + msmsdcc_setup_clocks(host, true); + spin_lock_irqsave(&host->lock, flags); + host->clks_on = 1; + } + spin_unlock_irqrestore(&host->lock, flags); + mutex_unlock(&host->clk_mutex); + +out: + if (rc < 0) { + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, rc); + return rc; + } + msmsdcc_msm_bus_cancel_work_and_set_vote(host, &mmc->ios); + return 0; +} + +static int msmsdcc_disable(struct mmc_host *mmc) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + + msmsdcc_pm_qos_update_latency(host, 0); + + if (mmc->card && mmc_card_sdio(mmc->card)) + goto out; + + mutex_lock(&host->clk_mutex); + spin_lock_irqsave(&host->lock, flags); + if (host->clks_on) { + spin_unlock_irqrestore(&host->lock, flags); + msmsdcc_setup_clocks(host, false); + spin_lock_irqsave(&host->lock, flags); + host->clks_on = 0; + } + spin_unlock_irqrestore(&host->lock, flags); + mutex_unlock(&host->clk_mutex); + +out: + msmsdcc_msm_bus_queue_work(host); + return 0; +} +#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 rc = 0; + + spin_lock_irqsave(&host->lock, flags); + host->io_pad_pwr_switch = 0; + spin_unlock_irqrestore(&host->lock, flags); + + /* + * For eMMC cards, VccQ voltage range must be changed + * only if it operates in HS200 SDR 1.2V mode or in + * DDR 1.2V mode. + */ + if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_120) { + rc = msmsdcc_set_vccq_vol(host, 1200000); + goto out; + } + + if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) { + /* Change voltage level of VDDPX to high voltage */ + rc = msmsdcc_set_vddp_high_vol(host); + goto out; + } else if (ios->signal_voltage != MMC_SIGNAL_VOLTAGE_180) { + /* invalid selection. don't do anything */ + rc = -EINVAL; + 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)) { + rc = -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); + msmsdcc_sync_reg_wr(host); + spin_unlock_irqrestore(&host->lock, flags); + + /* + * Switch VDDPX from high voltage to low voltage + * to change the VDD of the SD IO pads. + */ + rc = msmsdcc_set_vddp_low_vol(host); + if (rc) + goto out; + + spin_lock_irqsave(&host->lock, flags); + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) | + IO_PAD_PWR_SWITCH), host->base + MMCICLOCK); + msmsdcc_sync_reg_wr(host); + 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); + /* Disable PWRSAVE would make sure that SD CLK is always running */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) + & ~MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + msmsdcc_sync_reg_wr(host); + 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__); + rc = -EAGAIN; + goto out_unlock; + } + +out_unlock: + /* Enable PWRSAVE */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) | + MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + msmsdcc_sync_reg_wr(host); + spin_unlock_irqrestore(&host->lock, flags); +out: + return rc; +} + +static inline void msmsdcc_cm_sdc4_dll_set_freq(struct msmsdcc_host *host) +{ + u32 mclk_freq = 0; + + /* 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); +} + +/* Initialize the DLL (Programmable Delay Line ) */ +static int msmsdcc_init_cm_sdc4_dll(struct msmsdcc_host *host) +{ + int rc = 0; + unsigned long flags; + u32 wait_cnt; + + spin_lock_irqsave(&host->lock, flags); + /* + * 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); + msmsdcc_sync_reg_wr(host); + + /* 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); + + /* Write 1 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); + + msmsdcc_cm_sdc4_dll_set_freq(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); + + /* 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); + + /* Set DLL_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_DLL_EN), host->base + MCI_DLL_CONFIG); + + /* Set CK_OUT_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_CK_OUT_EN), host->base + MCI_DLL_CONFIG); + + wait_cnt = 50; + /* 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 50us sec for LOCK bit to be set */ + if (--wait_cnt == 0) { + pr_err("%s: %s: DLL failed to LOCK\n", + mmc_hostname(host->mmc), __func__); + rc = -ETIMEDOUT; + goto out; + } + /* wait for 1us before polling again */ + udelay(1); + } + +out: + /* re-enable PWRSAVE */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) | + MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + msmsdcc_sync_reg_wr(host); + spin_unlock_irqrestore(&host->lock, flags); + + return rc; +} + +static inline int msmsdcc_dll_poll_ck_out_en(struct msmsdcc_host *host, + u8 poll) +{ + int rc = 0; + u32 wait_cnt = 50; + u8 ck_out_en = 0; + + /* poll for MCI_CK_OUT_EN bit. max. poll time = 50us */ + ck_out_en = !!(readl_relaxed(host->base + MCI_DLL_CONFIG) & + MCI_CK_OUT_EN); + + while (ck_out_en != poll) { + if (--wait_cnt == 0) { + pr_err("%s: %s: CK_OUT_EN bit is not %d\n", + mmc_hostname(host->mmc), __func__, poll); + rc = -ETIMEDOUT; + goto out; + } + udelay(1); + + ck_out_en = !!(readl_relaxed(host->base + MCI_DLL_CONFIG) & + MCI_CK_OUT_EN); + } +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). + * + * This function gets called when host spinlock acquired. + */ +static int msmsdcc_enable_cdr_cm_sdc4_dll(struct msmsdcc_host *host) +{ + int rc = 0; + u32 config; + + config = readl_relaxed(host->base + MCI_DLL_CONFIG); + config |= MCI_CDR_EN; + config &= ~(MCI_CDR_EXT_EN | MCI_CK_OUT_EN); + writel_relaxed(config, host->base + MCI_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of MCI_DLL_CONFIG register becomes '0' */ + rc = msmsdcc_dll_poll_ck_out_en(host, 0); + if (rc) + goto err_out; + + /* 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 becomes '1' */ + rc = msmsdcc_dll_poll_ck_out_en(host, 1); + if (rc) + goto err_out; + + goto out; + +err_out: + pr_err("%s: %s: Failed\n", mmc_hostname(host->mmc), __func__); +out: + return rc; +} + +static int msmsdcc_config_cm_sdc4_dll_phase(struct msmsdcc_host *host, + u8 phase) +{ + int rc = 0; + u8 grey_coded_phase_table[] = {0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, + 0xC, 0xD, 0xF, 0xE, 0xA, 0xB, 0x9, + 0x8}; + unsigned long flags; + u32 config; + + spin_lock_irqsave(&host->lock, flags); + + config = readl_relaxed(host->base + MCI_DLL_CONFIG); + config &= ~(MCI_CDR_EN | MCI_CK_OUT_EN); + config |= (MCI_CDR_EXT_EN | MCI_DLL_EN); + writel_relaxed(config, host->base + MCI_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of MCI_DLL_CONFIG register becomes '0' */ + rc = msmsdcc_dll_poll_ck_out_en(host, 0); + if (rc) + goto err_out; + + /* + * 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)) + | (grey_coded_phase_table[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 until CK_OUT_EN bit of MCI_DLL_CONFIG register becomes '1' */ + rc = msmsdcc_dll_poll_ck_out_en(host, 1); + if (rc) + goto err_out; + + config = readl_relaxed(host->base + MCI_DLL_CONFIG); + config |= MCI_CDR_EN; + config &= ~MCI_CDR_EXT_EN; + writel_relaxed(config, host->base + MCI_DLL_CONFIG); + goto out; + +err_out: + pr_err("%s: %s: Failed to set DLL phase: %d\n", + mmc_hostname(host->mmc), __func__, phase); +out: + spin_unlock_irqrestore(&host->lock, flags); + return rc; +} + +/* + * Find out the greatest range of consecuitive selected + * DLL clock output phases that can be used as sampling + * setting for SD3.0 UHS-I card read operation (in SDR104 + * timing mode) or for eMMC4.5 card read operation (in HS200 + * timing mode). + * Select the 3/4 of the range and configure the DLL with the + * selected DLL clock output phase. +*/ +static int find_most_appropriate_phase(struct msmsdcc_host *host, + u8 *phase_table, u8 total_phases) +{ + #define MAX_PHASES 16 + int ret; + u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} }; + u8 phases_per_row[MAX_PHASES] = {0}; + int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0; + int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0; + bool phase_0_found = false, phase_15_found = false; + + if (!total_phases || (total_phases > MAX_PHASES)) { + pr_err("%s: %s: invalid argument: total_phases=%d\n", + mmc_hostname(host->mmc), __func__, total_phases); + return -EINVAL; + } + + for (cnt = 0; cnt < total_phases; cnt++) { + ranges[row_index][col_index] = phase_table[cnt]; + phases_per_row[row_index] += 1; + col_index++; + + if ((cnt + 1) == total_phases) { + continue; + /* check if next phase in phase_table is consecutive or not */ + } else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) { + row_index++; + col_index = 0; + } + } + + if (row_index >= MAX_PHASES) + return -EINVAL; + + /* Check if phase-0 is present in first valid window? */ + if (!ranges[0][0]) { + phase_0_found = true; + phase_0_raw_index = 0; + /* Check if cycle exist between 2 valid windows */ + for (cnt = 1; cnt <= row_index; cnt++) { + if (phases_per_row[cnt]) { + for (i = 0; i < phases_per_row[cnt]; i++) { + if (ranges[cnt][i] == 15) { + phase_15_found = true; + phase_15_raw_index = cnt; + break; + } + } + } + } + } + + /* If 2 valid windows form cycle then merge them as single window */ + if (phase_0_found && phase_15_found) { + /* number of phases in raw where phase 0 is present */ + u8 phases_0 = phases_per_row[phase_0_raw_index]; + /* number of phases in raw where phase 15 is present */ + u8 phases_15 = phases_per_row[phase_15_raw_index]; + + if (phases_0 + phases_15 >= MAX_PHASES) + /* + * If there are more than 1 phase windows then total + * number of phases in both the windows should not be + * more than or equal to MAX_PHASES. + */ + return -EINVAL; + + /* Merge 2 cyclic windows */ + i = phases_15; + for (cnt = 0; cnt < phases_0; cnt++) { + ranges[phase_15_raw_index][i] = + ranges[phase_0_raw_index][cnt]; + if (++i >= MAX_PHASES) + break; + } + + phases_per_row[phase_0_raw_index] = 0; + phases_per_row[phase_15_raw_index] = phases_15 + phases_0; + } + + for (cnt = 0; cnt <= row_index; cnt++) { + if (phases_per_row[cnt] > curr_max) { + curr_max = phases_per_row[cnt]; + selected_row_index = cnt; + } + } + + i = ((curr_max * 3) / 4); + if (i) + i--; + + ret = (int)ranges[selected_row_index][i]; + + if (ret >= MAX_PHASES) { + ret = -EINVAL; + pr_err("%s: %s: invalid phase selected=%d\n", + mmc_hostname(host->mmc), __func__, ret); + } + + return ret; +} + +static int msmsdcc_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + int rc = 0; + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0; + const u32 *tuning_block_pattern = tuning_block_64; + int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */ + + pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); + + /* Tuning is only required for SDR104 modes */ + if (!host->tuning_needed) { + rc = 0; + goto exit; + } + + spin_lock_irqsave(&host->lock, flags); + 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"); + + host->tuning_in_progress = 1; + if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) && + (mmc->ios.bus_width == MMC_BUS_WIDTH_8)) { + tuning_block_pattern = tuning_block_128; + size = sizeof(tuning_block_128); + } + spin_unlock_irqrestore(&host->lock, flags); + + /* first of all reset the tuning block */ + rc = msmsdcc_init_cm_sdc4_dll(host); + if (rc) + goto out; + + data_buf = kmalloc(size, 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 = opcode; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = size; + 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, size); + memset(data_buf, 0, size); + mmc_wait_for_req(mmc, &mrq); + + if (!cmd.error && !data.error && + !memcmp(data_buf, tuning_block_pattern, size)) { + /* tuning is successful at this tuning point */ + tuned_phases[tuned_phase_cnt++] = phase; + pr_debug("%s: %s: found good phase = %d\n", + mmc_hostname(mmc), __func__, phase); + } + } while (++phase < 16); + + if (tuned_phase_cnt) { + rc = find_most_appropriate_phase(host, tuned_phases, + tuned_phase_cnt); + if (rc < 0) + goto kfree; + else + phase = (u8)rc; + + /* + * Finally set the selected phase in delay + * line hw block. + */ + rc = msmsdcc_config_cm_sdc4_dll_phase(host, phase); + if (rc) + goto kfree; + pr_debug("%s: %s: finally setting the tuning phase to %d\n", + mmc_hostname(mmc), __func__, phase); + } else { + /* tuning failed */ + pr_err("%s: %s: no tuning point found\n", + mmc_hostname(mmc), __func__); + msmsdcc_dump_sdcc_state(host); + rc = -EAGAIN; + } + +kfree: + kfree(data_buf); +out: + spin_lock_irqsave(&host->lock, flags); + host->tuning_in_progress = 0; + spin_unlock_irqrestore(&host->lock, flags); +exit: + pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); + return rc; +} + +static const struct mmc_host_ops msmsdcc_ops = { + .enable = msmsdcc_enable, + .disable = msmsdcc_disable, + .pre_req = msmsdcc_pre_req, + .post_req = msmsdcc_post_req, + .request = msmsdcc_request, + .set_ios = msmsdcc_set_ios, + .get_ro = msmsdcc_get_ro, + .enable_sdio_irq = msmsdcc_enable_sdio_irq, + .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); + if (host->plat->is_status_gpio_active_low) + status = !status; + } + 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 || 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) { + if (host->plat->status) + pr_info("%s: Slot status change detected " + "(%d -> %d)\n", + mmc_hostname(host->mmc), + host->oldstat, status); + else if (host->plat->is_status_gpio_active_low) + pr_info("%s: Slot status change detected " + "(%d -> %d) and the card detect GPIO" + " is ACTIVE_LOW\n", + mmc_hostname(host->mmc), + host->oldstat, status); + else + pr_info("%s: Slot status change detected " + "(%d -> %d) and the card detect GPIO" + " is ACTIVE_HIGH\n", + mmc_hostname(host->mmc), + host->oldstat, status); + mmc_detect_change(host->mmc, 0); + } + host->oldstat = status; + } else { + mmc_detect_change(host->mmc, 0); + } +} + +static irqreturn_t +msmsdcc_platform_status_irq(int irq, void *dev_id) +{ + struct msmsdcc_host *host = dev_id; + + pr_debug("%s: %d\n", __func__, irq); + msmsdcc_check_status((unsigned long) host); + return IRQ_HANDLED; +} + +static irqreturn_t +msmsdcc_platform_sdiowakeup_irq(int irq, void *dev_id) +{ + struct msmsdcc_host *host = dev_id; + + pr_debug("%s: SDIO Wake up IRQ : %d\n", mmc_hostname(host->mmc), irq); + spin_lock(&host->lock); + if (!host->sdio_wakeupirq_disabled) { + disable_irq_nosync(irq); + if (host->sdcc_suspended) { + wake_lock(&host->sdio_wlock); + msmsdcc_disable_irq_wake(host); + } + host->sdio_wakeupirq_disabled = 1; + } + if (host->plat->is_sdio_al_client) { + wake_lock(&host->sdio_wlock); + spin_unlock(&host->lock); + mmc_signal_sdio_irq(host->mmc); + goto out_unlocked; + } + spin_unlock(&host->lock); + +out_unlocked: + return IRQ_HANDLED; +} + +static void +msmsdcc_status_notify_cb(int card_present, void *dev_id) +{ + struct msmsdcc_host *host = dev_id; + + pr_debug("%s: card_present %d\n", mmc_hostname(host->mmc), + card_present); + msmsdcc_check_status((unsigned long) host); +} + +static int +msmsdcc_init_dma(struct msmsdcc_host *host) +{ + memset(&host->dma, 0, sizeof(struct msmsdcc_dma_data)); + host->dma.host = host; + host->dma.channel = -1; + host->dma.crci = -1; + + if (!host->dmares) + return -ENODEV; + + host->dma.nc = dma_alloc_coherent(NULL, + sizeof(struct msmsdcc_nc_dmadata), + &host->dma.nc_busaddr, + GFP_KERNEL); + if (host->dma.nc == NULL) { + pr_err("Unable to allocate DMA buffer\n"); + return -ENOMEM; + } + memset(host->dma.nc, 0x00, sizeof(struct msmsdcc_nc_dmadata)); + host->dma.cmd_busaddr = host->dma.nc_busaddr; + host->dma.cmdptr_busaddr = host->dma.nc_busaddr + + offsetof(struct msmsdcc_nc_dmadata, cmdptr); + host->dma.channel = host->dmares->start; + host->dma.crci = host->dma_crci_res->start; + + 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; + + /* Allocate maximum descriptor fifo size */ + sps_config->desc.size = SPS_MAX_DESC_FIFO_SIZE - + (SPS_MAX_DESC_FIFO_SIZE % SPS_MAX_DESC_LENGTH); + sps_config->desc.base = dma_alloc_coherent(mmc_dev(host->mmc), + sps_config->desc.size, + &sps_config->desc.phys_base, + GFP_KERNEL); + + if (!sps_config->desc.base) { + rc = -ENOMEM; + pr_err("%s: dma_alloc_coherent() failed!!! Can't allocate buffer\n" + , mmc_hostname(host->mmc)); + goto get_config_err; + } + 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; } - spin_unlock_irqrestore(&host->lock, flags); -} -static void msmsdcc_setup_gpio(struct msmsdcc_host *host, bool enable) -{ - struct msm_mmc_gpio_data *curr; - int i, rc = 0; + 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 be used for data transfer size even + * less than SDCC FIFO size. So let's set BAM summing + * thresold to SPS_MIN_XFER_SIZE bytes. + */ + bam.summing_threshold = SPS_MIN_XFER_SIZE; + /* 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); - if (!host->plat->gpio_data || host->gpio_config_status == enable) - return; + host->sps.src_pipe_index = SPS_SDCC_PRODUCER_PIPE_INDEX; + host->sps.dest_pipe_index = SPS_SDCC_CONSUMER_PIPE_INDEX; - 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); - } - } - host->gpio_config_status = enable; - return; + 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; +} -free_gpios: - for (; i >= 0; i--) - gpio_free(curr->gpio[i].no); +/** + * 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 void -msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +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); - u32 clk = 0, pwr = 0; - int rc; + int poll; unsigned long flags; spin_lock_irqsave(&host->lock, flags); - - msmsdcc_enable_clocks(host); - + poll = !!(mmc->caps & MMC_CAP_NEEDS_POLL); spin_unlock_irqrestore(&host->lock, flags); - 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; - } - - if (ios->bus_width == MMC_BUS_WIDTH_4) - clk |= (2 << 10); /* Set WIDEBUS */ - - if (ios->clock > 400000 && msmsdcc_pwrsave) - clk |= (1 << 9); /* PWRSAVE */ + return snprintf(buf, PAGE_SIZE, "%d\n", poll); +} - clk |= (1 << 12); /* FLOW_ENA */ - clk |= (1 << 15); /* feedback clock */ +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; - if (host->plat->translate_vdd) - pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd); + sscanf(buf, "%d", &value); - 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; + 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; +} - if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) - pwr |= MCI_OD; +static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR, + show_polling, set_polling); - msmsdcc_writel(host, clk, MMCICLOCK); +static ssize_t +show_sdcc_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); - if (host->pwr != pwr) { - host->pwr = pwr; - msmsdcc_writel(host, pwr, MMCIPOWER); - } -#if BUSCLK_PWRSAVE - spin_lock_irqsave(&host->lock, flags); - msmsdcc_disable_clocks(host, 1); - spin_unlock_irqrestore(&host->lock, flags); -#endif + return snprintf(buf, PAGE_SIZE, "%u\n", + host->msm_bus_vote.is_max_bw_needed); } -static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable) +static ssize_t +set_sdcc_to_mem_max_bus_bw(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); + uint32_t value; unsigned long flags; - u32 status; - 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); + if (!kstrtou32(buf, 0, &value)) { + spin_lock_irqsave(&host->lock, flags); + host->msm_bus_vote.is_max_bw_needed = !!value; + spin_unlock_irqrestore(&host->lock, flags); } - spin_unlock_irqrestore(&host->lock, flags); + + return count; } -static void msmsdcc_init_card(struct mmc_host *mmc, struct mmc_card *card) -{ - struct msmsdcc_host *host = mmc_priv(mmc); +static DEVICE_ATTR(max_bus_bw, S_IRUGO | S_IWUSR, + show_sdcc_to_mem_max_bus_bw, set_sdcc_to_mem_max_bus_bw); - if (host->plat->init_card) - host->plat->init_card(card); -} +static struct attribute *dev_attrs[] = { + &dev_attr_max_bus_bw.attr, + /* if polling is enabled, this will be filled with dev_attr_polling */ + NULL, + NULL, +}; -static const struct mmc_host_ops msmsdcc_ops = { - .request = msmsdcc_request, - .set_ios = msmsdcc_set_ios, - .enable_sdio_irq = msmsdcc_enable_sdio_irq, - .init_card = msmsdcc_init_card, +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, }; -static void -msmsdcc_check_status(unsigned long data) +#ifdef CONFIG_HAS_EARLYSUSPEND +static void msmsdcc_early_suspend(struct early_suspend *h) { - struct msmsdcc_host *host = (struct msmsdcc_host *)data; - unsigned int status; + 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->plat->status) { + if (host->polling_enabled) { + spin_lock_irqsave(&host->lock, flags); + host->mmc->caps |= MMC_CAP_NEEDS_POLL; mmc_detect_change(host->mmc, 0); - goto out; + spin_unlock_irqrestore(&host->lock, flags); } +}; +#endif - 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); - else - mmc_detect_change(host->mmc, 0); - } +static void msmsdcc_print_regs(const char *name, void __iomem *base, + u32 phys_base, unsigned int no_of_regs) +{ + unsigned int i; - host->oldstat = status; + if (!base) + return; -out: - if (host->timer.function) - mod_timer(&host->timer, jiffies + HZ); + pr_info("===== %s: Register Dumps @phys_base=0x%x, @virt_base=0x%x" + " =====\n", name, phys_base, (u32)base); + for (i = 0; i < no_of_regs; i = i + 4) { + pr_info("Reg=0x%.2x: 0x%.8x, 0x%.8x, 0x%.8x, 0x%.8x\n", i*4, + (u32)readl_relaxed(base + i*4), + (u32)readl_relaxed(base + ((i+1)*4)), + (u32)readl_relaxed(base + ((i+2)*4)), + (u32)readl_relaxed(base + ((i+3)*4))); + } } -static irqreturn_t -msmsdcc_platform_status_irq(int irq, void *dev_id) +static void msmsdcc_dump_sdcc_state(struct msmsdcc_host *host) { - struct msmsdcc_host *host = dev_id; + /* Dump current state of SDCC clocks, power and irq */ + pr_info("%s: SDCC PWR is %s\n", mmc_hostname(host->mmc), + (host->pwr ? "ON" : "OFF")); + pr_info("%s: SDCC clks are %s, MCLK rate=%d\n", + mmc_hostname(host->mmc), (host->clks_on ? "ON" : "OFF"), + (u32)clk_get_rate(host->clk)); + pr_info("%s: SDCC irq is %s\n", mmc_hostname(host->mmc), + (host->sdcc_irq_disabled ? "disabled" : "enabled")); + + /* Now dump SDCC registers. Don't print FIFO registers */ + if (host->clks_on) + msmsdcc_print_regs("SDCC-CORE", host->base, + host->core_memres->start, 28); + + if (host->curr.data) { + if (!msmsdcc_is_dma_possible(host, host->curr.data)) + pr_info("%s: PIO mode\n", mmc_hostname(host->mmc)); + else if (host->is_dma_mode) + pr_info("%s: ADM mode: busy=%d, chnl=%d, crci=%d\n", + mmc_hostname(host->mmc), host->dma.busy, + host->dma.channel, host->dma.crci); + else if (host->is_sps_mode) { + if (host->sps.busy && host->clks_on) + msmsdcc_print_regs("SDCC-DML", host->dml_base, + host->dml_memres->start, + 16); + pr_info("%s: SPS mode: busy=%d\n", + mmc_hostname(host->mmc), host->sps.busy); + } - pr_debug("%s: %d\n", __func__, irq); - msmsdcc_check_status((unsigned long) host); - return IRQ_HANDLED; + pr_info("%s: xfer_size=%d, data_xfered=%d, xfer_remain=%d\n", + mmc_hostname(host->mmc), host->curr.xfer_size, + host->curr.data_xfered, host->curr.xfer_remain); + } + + pr_info("%s: got_dataend=%d, prog_enable=%d," + " wait_for_auto_prog_done=%d, got_auto_prog_done=%d," + " req_tout_ms=%d\n", mmc_hostname(host->mmc), + host->curr.got_dataend, host->prog_enable, + host->curr.wait_for_auto_prog_done, + host->curr.got_auto_prog_done, host->curr.req_tout_ms); + msmsdcc_print_rpm_info(host); } -static void -msmsdcc_status_notify_cb(int card_present, void *dev_id) +static void msmsdcc_req_tout_timer_hdlr(unsigned long data) { - struct msmsdcc_host *host = dev_id; + struct msmsdcc_host *host = (struct msmsdcc_host *)data; + struct mmc_request *mrq; + unsigned long flags; - pr_debug("%s: card_present %d\n", mmc_hostname(host->mmc), - card_present); - msmsdcc_check_status((unsigned long) host); -} + spin_lock_irqsave(&host->lock, flags); + if (host->dummy_52_sent) { + pr_info("%s: %s: dummy CMD52 timeout\n", + mmc_hostname(host->mmc), __func__); + host->dummy_52_sent = 0; + } -static void -msmsdcc_busclk_expired(unsigned long _data) -{ - struct msmsdcc_host *host = (struct msmsdcc_host *) _data; + mrq = host->curr.mrq; - if (host->clks_on) - msmsdcc_disable_clocks(host, 0); + if (mrq && mrq->cmd) { + pr_info("%s: CMD%d: Request timeout\n", mmc_hostname(host->mmc), + mrq->cmd->opcode); + msmsdcc_dump_sdcc_state(host); + + if (!mrq->cmd->error) + mrq->cmd->error = -ETIMEDOUT; + host->dummy_52_needed = 0; + if (host->curr.data) { + 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_flush(host->dma.channel, 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 { + host->prog_enable = 0; + host->curr.wait_for_auto_prog_done = 0; + msmsdcc_reset_and_restore(host); + msmsdcc_request_end(host, mrq); + } + } + spin_unlock_irqrestore(&host->lock, flags); } -static int -msmsdcc_init_dma(struct msmsdcc_host *host) +static struct mmc_platform_data *msmsdcc_populate_pdata(struct device *dev) { - memset(&host->dma, 0, sizeof(struct msmsdcc_dma_data)); - host->dma.host = host; - host->dma.channel = -1; + int i, ret; + struct mmc_platform_data *pdata; + struct device_node *np = dev->of_node; + u32 bus_width = 0; + u32 *clk_table; + int clk_table_len; + u32 *sup_voltages; + int sup_volt_len; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "could not allocate memory for platform data\n"); + goto err; + } - if (!host->dmares) - return -ENODEV; + of_property_read_u32(np, "qcom,sdcc-bus-width", &bus_width); + if (bus_width == 8) { + pdata->mmc_bus_width = MMC_CAP_8_BIT_DATA; + } else if (bus_width == 4) { + pdata->mmc_bus_width = MMC_CAP_4_BIT_DATA; + } else { + dev_notice(dev, "Invalid bus width, default to 1 bit mode\n"); + pdata->mmc_bus_width = 0; + } - host->dma.nc = dma_alloc_coherent(NULL, - sizeof(struct msmsdcc_nc_dmadata), - &host->dma.nc_busaddr, - GFP_KERNEL); - if (host->dma.nc == NULL) { - pr_err("Unable to allocate DMA buffer\n"); - return -ENOMEM; + if (of_get_property(np, "qcom,sdcc-sup-voltages", &sup_volt_len)) { + size_t sz; + sz = sup_volt_len / sizeof(*sup_voltages); + if (sz > 0) { + sup_voltages = devm_kzalloc(dev, + sz * sizeof(*sup_voltages), GFP_KERNEL); + if (!sup_voltages) { + dev_err(dev, "No memory for supported voltage\n"); + goto err; + } + + ret = of_property_read_u32_array(np, + "qcom,sdcc-sup-voltages", sup_voltages, sz); + if (ret < 0) { + dev_err(dev, "error while reading voltage" + "ranges %d\n", ret); + goto err; + } + } else { + dev_err(dev, "No supported voltages\n"); + goto err; + } + for (i = 0; i < sz; i += 2) { + u32 mask; + + mask = mmc_vddrange_to_ocrmask(sup_voltages[i], + sup_voltages[i + 1]); + if (!mask) + dev_err(dev, "Invalide voltage range %d\n", i); + pdata->ocr_mask |= mask; + } + dev_dbg(dev, "OCR mask=0x%x\n", pdata->ocr_mask); + } else { + dev_err(dev, "Supported voltage range not specified\n"); } - memset(host->dma.nc, 0x00, sizeof(struct msmsdcc_nc_dmadata)); - host->dma.cmd_busaddr = host->dma.nc_busaddr; - host->dma.cmdptr_busaddr = host->dma.nc_busaddr + - offsetof(struct msmsdcc_nc_dmadata, cmdptr); - host->dma.channel = host->dmares->start; - return 0; + if (of_get_property(np, "qcom,sdcc-clk-rates", &clk_table_len)) { + size_t sz; + sz = clk_table_len / sizeof(*clk_table); + + if (sz > 0) { + clk_table = devm_kzalloc(dev, sz * sizeof(*clk_table), + GFP_KERNEL); + if (!clk_table) { + dev_err(dev, "No memory for clock table\n"); + goto err; + } + + ret = of_property_read_u32_array(np, + "qcom,sdcc-clk-rates", clk_table, sz); + if (ret < 0) { + dev_err(dev, "error while reading clk" + "table %d\n", ret); + goto err; + } + } else { + dev_err(dev, "clk_table not specified\n"); + goto err; + } + pdata->sup_clk_table = clk_table; + pdata->sup_clk_cnt = sz; + } else { + dev_err(dev, "Supported clock rates not specified\n"); + } + + if (of_get_property(np, "qcom,sdcc-nonremovable", NULL)) + pdata->nonremovable = true; + if (of_get_property(np, "qcom,sdcc-disable_cmd23", NULL)) + pdata->disable_cmd23 = true; + + return pdata; +err: + return NULL; } static int msmsdcc_probe(struct platform_device *pdev) { - struct msm_mmc_platform_data *plat = pdev->dev.platform_data; + struct mmc_platform_data *plat; struct msmsdcc_host *host; struct mmc_host *mmc; - struct resource *cmd_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; + struct resource *dma_crci_res = NULL; + int ret = 0; + int i; + + if (pdev->dev.of_node) { + plat = msmsdcc_populate_pdata(&pdev->dev); + of_property_read_u32((&pdev->dev)->of_node, + "cell-index", &pdev->id); + } else { + plat = pdev->dev.platform_data; + } /* must have platform data */ if (!plat) { @@ -1183,30 +4737,88 @@ 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 && !plat->sdiowakeup_irq) { + pr_err("%s: No wakeup IRQ for sdio_al client\n", __func__); + return -EINVAL; + } + if (pdev->resource == NULL || pdev->num_resources < 2) { pr_err("%s: Invalid resource\n", __func__); return -ENXIO; } + if (pdev->dev.of_node) { + /* + * Device tree iomem resources are only accessible by index. + * index = 0 -> SDCC register interface + * index = 1 -> DML register interface + * index = 2 -> BAM register interface + * IRQ resources: + * index = 0 -> SDCC IRQ + * index = 1 -> BAM IRQ + */ + core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dml_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1); + bam_memres = platform_get_resource(pdev, IORESOURCE_MEM, 2); + core_irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + bam_irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + } else { + for (i = 0; i < pdev->num_resources; i++) { + if (pdev->resource[i].flags & IORESOURCE_MEM) { + if (!strncmp(pdev->resource[i].name, + "sdcc_dml_addr", + sizeof("sdcc_dml_addr"))) + dml_memres = &pdev->resource[i]; + else if (!strncmp(pdev->resource[i].name, + "sdcc_bam_addr", + sizeof("sdcc_bam_addr"))) + bam_memres = &pdev->resource[i]; + else + core_memres = &pdev->resource[i]; - 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"); - stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - "status_irq"); + } + if (pdev->resource[i].flags & IORESOURCE_IRQ) { + if (!strncmp(pdev->resource[i].name, + "sdcc_bam_irq", + sizeof("sdcc_bam_irq"))) + bam_irqres = &pdev->resource[i]; + else + core_irqres = &pdev->resource[i]; + } + if (pdev->resource[i].flags & IORESOURCE_DMA) { + if (!strncmp(pdev->resource[i].name, + "sdcc_dma_chnl", + sizeof("sdcc_dma_chnl"))) + dmares = &pdev->resource[i]; + else if (!strncmp(pdev->resource[i].name, + "sdcc_dma_crci", + sizeof("sdcc_dma_crci"))) + dma_crci_res = &pdev->resource[i]; + } + } + } - if (!cmd_irqres || !memres) { - pr_err("%s: Invalid resource\n", __func__); + 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; @@ -1218,185 +4830,452 @@ msmsdcc_probe(struct platform_device *pdev) host->plat = plat; host->mmc = mmc; host->curr.cmd = NULL; - init_timer(&host->busclk_timer); - host->busclk_timer.data = (unsigned long) host; - host->busclk_timer.function = msmsdcc_busclk_expired; + if (!plat->disable_bam && 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 host_free; } - host->cmd_irqres = cmd_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; + host->dma_crci_res = dma_crci_res; spin_lock_init(&host->lock); + mutex_init(&host->clk_mutex); + +#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); - /* - * Setup DMA - */ - if (host->dmares) { + 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; + host->dma.crci = -1; + } + + /* + * Setup SDCC clock if derived from Dayatona + * fabric core clock. + */ + if (plat->pclk_src_dfab) { + host->dfab_pclk = clk_get(&pdev->dev, "bus_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_prepare_enable(host->dfab_pclk); + if (ret) + goto dfab_pclk_put; + } else + goto dma_free; } - /* Get our clocks */ - host->pclk = clk_get(&pdev->dev, "sdc_pclk"); - if (IS_ERR(host->pclk)) { - ret = PTR_ERR(host->pclk); - goto dma_free; + /* + * Setup main peripheral bus clock + */ + host->pclk = clk_get(&pdev->dev, "iface_clk"); + if (!IS_ERR(host->pclk)) { + ret = clk_prepare_enable(host->pclk); + if (ret) + goto pclk_put; + + host->pclk_rate = clk_get_rate(host->pclk); } - host->clk = clk_get(&pdev->dev, "sdc_clk"); + /* + * Setup SDC MMC clock + */ + host->clk = clk_get(&pdev->dev, "core_clk"); if (IS_ERR(host->clk)) { ret = PTR_ERR(host->clk); - goto pclk_put; + goto pclk_disable; } - ret = clk_set_rate(host->clk, msmsdcc_fmin); + 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_prepare_enable(host->clk); if (ret) goto clk_put; - host->pclk_rate = clk_get_rate(host->pclk); host->clk_rate = clk_get_rate(host->clk); + if (!host->clk_rate) + dev_err(&pdev->dev, "Failed to read MCLK\n"); + + /* + * Lookup the Controller Version, to identify the supported features + * Version number read as 0 would indicate SDCC3 or earlier versions + */ + host->sdcc_version = readl_relaxed(host->base + MCI_VERSION); + pr_info("%s: mci-version: %x\n", mmc_hostname(host->mmc), + host->sdcc_version); + /* + * Set the register write delay according to min. clock frequency + * supported and update later when the host->clk_rate changes. + */ + host->reg_write_delay = + (1 + ((3 * USEC_PER_SEC) / + msmsdcc_get_min_sup_clk_rate(host))); + + host->clks_on = 1; + /* Apply Hard reset to SDCC to put it in power on default state */ + msmsdcc_hard_reset(host); + +#define MSM_MMC_DEFAULT_CPUDMA_LATENCY 200 /* usecs */ + /* pm qos request to prevent apps idle power collapse */ + if (host->plat->cpu_dma_latency) + host->cpu_dma_latency = host->plat->cpu_dma_latency; + else + host->cpu_dma_latency = MSM_MMC_DEFAULT_CPUDMA_LATENCY; + pm_qos_add_request(&host->pm_qos_req_dma, + PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + + ret = msmsdcc_msm_bus_register(host); + if (ret) + goto pm_qos_remove; + + if (host->msm_bus_vote.client_handle) + INIT_DELAYED_WORK(&host->msm_bus_vote.vote_work, + msmsdcc_msm_bus_work); + + ret = msmsdcc_vreg_init(host, true); + if (ret) { + pr_err("%s: msmsdcc_vreg_init() failed (%d)\n", __func__, ret); + goto clk_disable; + } + + + /* 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; + } + mmc_dev(mmc)->dma_mask = &dma_mask; /* * 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 |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_ERASE; + + /* + * If we send the CMD23 before multi block write/read command + * then we need not to send CMD12 at the end of the transfer. + * If we don't send the CMD12 then only way to detect the PROG_DONE + * status is to use the AUTO_PROG_DONE status provided by SDCC4 + * controller. So let's enable the CMD23 for SDCC4 only. + */ + if (!plat->disable_cmd23 && host->sdcc_version) + mmc->caps |= MMC_CAP_CMD23; + + 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); + + mmc->caps2 |= MMC_CAP2_PACKED_WR; + mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; + mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC | MMC_CAP2_DETECT_ON_ERR); + mmc->caps2 |= MMC_CAP2_SANITIZE; + + if (pdev->dev.of_node) { + if (of_get_property((&pdev->dev)->of_node, + "qcom,sdcc-hs200", NULL)) + mmc->caps2 |= MMC_CAP2_HS200_1_8V_SDR; + } + + if (plat->nonremovable) + mmc->caps |= MMC_CAP_NONREMOVABLE; + mmc->caps |= MMC_CAP_SDIO_IRQ; + + mmc->caps2 |= MMC_CAP2_INIT_BKOPS | MMC_CAP2_BKOPS; + + 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_segs = msmsdcc_get_nr_sg(host); + mmc->max_blk_size = MMC_MAX_BLK_SIZE; + mmc->max_blk_count = MMC_MAX_BLK_CNT; - mmc->max_req_size = 33554432; /* MCI_DATA_LENGTH is 25 bits */ + mmc->max_req_size = MMC_MAX_REQ_SIZE; 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); + msmsdcc_sync_reg_wr(host); - msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0); - host->saved_irq0mask = MCI_IRQENABLE; + 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; + + ret = request_irq(core_irqres->start, msmsdcc_pio_irq, IRQF_SHARED, + DRIVER_NAME " (pio)", host); + if (ret) + goto irq_free; + + /* + * 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_wakeupirq_disabled) { + disable_irq_nosync(plat->sdiowakeup_irq); + host->sdio_wakeupirq_disabled = 1; + } + spin_unlock_irqrestore(&host->lock, flags); + } + } + + if (host->plat->mpm_sdiowakeup_int) { + wake_lock_init(&host->sdio_wlock, WAKE_LOCK_SUSPEND, + mmc_hostname(mmc)); + } + + wake_lock_init(&host->sdio_suspend_wlock, WAKE_LOCK_SUSPEND, + mmc_hostname(mmc)); /* * Setup card detect change */ - memset(&host->timer, 0, sizeof(host->timer)); + if (plat->status || plat->status_gpio) { + if (plat->status) + host->oldstat = plat->status(mmc_dev(host->mmc)); + else + host->oldstat = msmsdcc_slot_status(host); - if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) { - unsigned long irqflags = IRQF_SHARED | - (stat_irqres->flags & IRQF_TRIGGER_MASK); + 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; - } + 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(cmd_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. + */ + pm_runtime_enable(&(pdev)->dev); +#else + if (mmc->caps & MMC_CAP_NONREMOVABLE) { + 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" + " dmacrcri %d\n", mmc_hostname(mmc), + (unsigned long long)core_memres->start, + (unsigned int) core_irqres->start, + (unsigned int) plat->status_irq, host->dma.channel, + host->dma.crci); + + 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 + && host->dma.crci != -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) + dev_attrs[1] = &dev_attr_polling.attr; + + 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); + msmsdcc_msm_bus_unregister(host); + pm_qos_remove: + if (host->cpu_dma_latency) + pm_qos_remove_request(&host->pm_qos_req_dma); clk_put: clk_put(host->clk); + pclk_disable: + if (!IS_ERR(host->pclk)) + clk_disable_unprepare(host->pclk); pclk_put: - clk_put(host->pclk); -dma_free: - if (host->dmares) - dma_free_coherent(NULL, sizeof(struct msmsdcc_nc_dmadata), - host->dma.nc, host->dma.nc_busaddr); -ioremap_free: - tasklet_kill(&host->dma_tlet); + if (!IS_ERR(host->pclk)) + clk_put(host->pclk); + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_disable_unprepare(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); @@ -1404,83 +5283,515 @@ msmsdcc_probe(struct platform_device *pdev) 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); + + if (host->cpu_dma_latency) + pm_qos_remove_request(&host->pm_qos_req_dma); + + if (host->msm_bus_vote.client_handle) { + msmsdcc_msm_bus_cancel_work_and_set_vote(host, NULL); + msmsdcc_msm_bus_unregister(host); + } + + 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; + + mutex_lock(&host->clk_mutex); + 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_nosync(host->core_irqres->start); + host->sdcc_irq_disabled = 1; + } + + if (host->clks_on) { + spin_unlock_irqrestore(&host->lock, flags); + msmsdcc_setup_clocks(host, false); + spin_lock_irqsave(&host->lock, flags); + host->clks_on = 0; + } + + if (host->plat->sdio_lpm_gpio_setup && + !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_wakeupirq_disabled) { + msmsdcc_enable_irq_wake(host); + enable_irq(host->plat->sdiowakeup_irq); + host->sdio_wakeupirq_disabled = 0; + } + } else { + if (!host->sdio_wakeupirq_disabled) { + disable_irq_nosync(host->plat->sdiowakeup_irq); + host->sdio_wakeupirq_disabled = 1; + msmsdcc_disable_irq_wake(host); + } + if (host->plat->sdio_lpm_gpio_setup && + 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) { + spin_unlock_irqrestore(&host->lock, flags); + msmsdcc_setup_clocks(host, true); + spin_lock_irqsave(&host->lock, flags); + 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; + } + } + spin_unlock_irqrestore(&host->lock, flags); + mutex_unlock(&host->clk_mutex); + 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; + unsigned long flags; - if (mmc) { - struct msmsdcc_host *host = mmc_priv(mmc); + if (host->plat->is_sdio_al_client) { + rc = 0; + goto out; + } - if (host->stat_irq) - disable_irq(host->stat_irq); + pr_debug("%s: %s: start\n", mmc_hostname(mmc), __func__); + if (mmc) { + host->sdcc_suspending = 1; + mmc->suspend_task = current; - if (mmc->card && mmc->card->type != MMC_TYPE_SDIO) + /* + * 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); + /* If there is pending detect work abort runtime suspend */ + if (unlikely(work_busy(&mmc->detect.work))) + rc = -EAGAIN; + else rc = mmc_suspend_host(mmc); - if (!rc) - msmsdcc_writel(host, 0, MMCIMASK0); - if (host->clks_on) - msmsdcc_disable_clocks(host, 0); + pm_runtime_put_noidle(dev); + + if (!rc) { + spin_lock_irqsave(&host->lock, flags); + host->sdcc_suspended = true; + spin_unlock_irqrestore(&host->lock, flags); + if (mmc->card && mmc_card_sdio(mmc->card) && + mmc->ios.clock) { +#ifdef CONFIG_MMC_CLKGATE + /* + * If SDIO function driver doesn't want + * to power off the card, atleast turn off + * clocks to allow deep sleep (TCXO shutdown). + */ + mmc_host_clk_hold(mmc); + spin_lock_irqsave(&mmc->clk_lock, flags); + mmc->clk_old = mmc->ios.clock; + mmc->ios.clock = 0; + mmc->clk_gated = true; + spin_unlock_irqrestore(&mmc->clk_lock, flags); + mmc_set_ios(mmc); + mmc_host_clk_release(mmc); +#endif + } + } + host->sdcc_suspending = 0; + mmc->suspend_task = NULL; + if (rc && wake_lock_active(&host->sdio_suspend_wlock)) + wake_unlock(&host->sdio_suspend_wlock); } + pr_debug("%s: %s: ends with err=%d\n", mmc_hostname(mmc), __func__, rc); +out: + /* set bus bandwidth to 0 immediately */ + msmsdcc_msm_bus_cancel_work_and_set_vote(host, NULL); 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; + pr_debug("%s: %s: start\n", mmc_hostname(mmc), __func__); if (mmc) { - struct msmsdcc_host *host = mmc_priv(mmc); + if (mmc->card && mmc_card_sdio(mmc->card) && + mmc_card_keep_power(mmc)) { + mmc_host_clk_hold(mmc); + mmc->ios.clock = host->clk_rate; + mmc_set_ios(mmc); + mmc_host_clk_release(mmc); + } + + mmc_resume_host(mmc); - msmsdcc_enable_clocks(host); + /* + * FIXME: Clearing of flags must be handled in clients + * resume handler. + */ + spin_lock_irqsave(&host->lock, flags); + mmc->pm_flags = 0; + host->sdcc_suspended = false; + spin_unlock_irqrestore(&host->lock, flags); - msmsdcc_writel(host, host->saved_irq0mask, MMCIMASK0); + /* + * After resuming the host wait for sometime so that + * the SDIO work will be processed. + */ + if (mmc->card && mmc_card_sdio(mmc->card)) { + if ((host->plat->mpm_sdiowakeup_int || + host->plat->sdiowakeup_irq) && + wake_lock_active(&host->sdio_wlock)) + wake_lock_timeout(&host->sdio_wlock, 1); + } - 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 + wake_unlock(&host->sdio_suspend_wlock); } + pr_debug("%s: %s: end\n", mmc_hostname(mmc), __func__); 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_suspend_noirq(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + int rc = 0; + + /* + * After platform suspend there may be active request + * which might have enabled clocks. For example, in SDIO + * case, ksdioirq thread might have scheduled after sdcc + * suspend but before system freeze. In that case abort + * suspend and retry instead of keeping the clocks on + * during suspend and not allowing TCXO. + */ + + if (host->clks_on && !host->plat->is_sdio_al_client) { + pr_warn("%s: clocks are on after suspend, aborting system " + "suspend\n", mmc_hostname(mmc)); + rc = -EAGAIN; + } + + 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; + + if (mmc->card && mmc_card_sdio(mmc->card)) + rc = msmsdcc_runtime_resume(dev); + else + host->pending_resume = true; + + if (host->plat->status_irq) { + msmsdcc_check_status((unsigned long)host); + enable_irq(host->plat->status_irq); + } + + return rc; +} + #else -#define msmsdcc_suspend 0 -#define msmsdcc_resume 0 +static int msmsdcc_runtime_suspend(struct device *dev) +{ + return 0; +} +static int msmsdcc_runtime_idle(struct device *dev) +{ + return 0; +} +static int msmsdcc_pm_suspend(struct device *dev) +{ + return 0; +} +static int msmsdcc_pm_resume(struct device *dev) +{ + return 0; +} +static int msmsdcc_suspend_noirq(struct device *dev) +{ + return 0; +} +static int msmsdcc_runtime_resume(struct device *dev) +{ + return 0; +} #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, + .suspend_noirq = msmsdcc_suspend_noirq, +}; + +static const struct of_device_id msmsdcc_dt_match[] = { + {.compatible = "qcom,msm-sdcc"}, + +}; +MODULE_DEVICE_TABLE(of, msmsdcc_dt_match); + 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, + .of_match_table = msmsdcc_dt_match, }, }; -module_platform_driver(msmsdcc_driver); +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_DESCRIPTION("Qualcomm MSM 7X00A Multimedia Card Interface driver"); +module_init(msmsdcc_init); +module_exit(msmsdcc_exit); + +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[200]; + 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 402028d16b86836171a5c4a1c43ff7eea2ab8bb4..ecf4950e18bca6e8b45ec3d9e164cd5b62a041be 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-2012, Code Aurora Forum. All rights reserved. * * 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,24 @@ #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 +#include #define MMCIPOWER 0x000 #define MCI_PWR_OFF 0x00 @@ -27,10 +42,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 +62,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 @@ -59,6 +78,9 @@ #define MCI_DPSM_DIRECTION (1 << 1) #define MCI_DPSM_MODE (1 << 2) #define MCI_DPSM_DMAENABLE (1 << 3) +#define MCI_DATA_PEND (1 << 17) +#define MCI_AUTO_PROG_DONE (1 << 19) +#define MCI_RX_DATA_PEND (1 << 20) #define MMCIDATACNT 0x030 #define MMCISTATUS 0x034 @@ -86,8 +108,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 +122,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,23 +166,42 @@ #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 MCI_VERSION 0x050 #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 MCI_STATUS2 0x06C +#define MCI_MCLK_REG_WR_ACTIVE (1 << 0) #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) -#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. */ @@ -152,12 +209,52 @@ #define MCI_FIFOHALFSIZE (MCI_FIFOSIZE / 2) -#define NR_SG 32 +#define NR_SG 128 + +#define MSM_MMC_IDLE_TIMEOUT 5000 /* msecs */ + +/* Set the request timeout to 10secs */ +#define MSM_MMC_REQ_TIMEOUT 10000 /* msecs */ +#define MSM_MMC_DISABLE_TIMEOUT 200 /* msecs */ + +/* + * Controller HW limitations + */ +#define MCI_DATALENGTH_BITS 25 +#define MMC_MAX_REQ_SIZE ((1 << MCI_DATALENGTH_BITS) - 1) +/* MCI_DATA_CTL BLOCKSIZE up to 4096 */ +#define MMC_MAX_BLK_SIZE 4096 +#define MMC_MIN_BLK_SIZE 512 +#define MMC_MAX_BLK_CNT (MMC_MAX_REQ_SIZE / MMC_MIN_BLK_SIZE) + +/* 64KiB */ +#define MAX_SG_SIZE (64 * 1024) +#define MAX_NR_SG_DMA_PIO (MMC_MAX_REQ_SIZE / MAX_SG_SIZE) + +/* + * BAM limitations + */ +/* upto 16 bits (64K - 1) */ +#define SPS_MAX_DESC_FIFO_SIZE 65535 +/* 16KiB */ +#define SPS_MAX_DESC_SIZE (16 * 1024) +/* Each descriptor is of length 8 bytes */ +#define SPS_MAX_DESC_LENGTH 8 +#define SPS_MAX_DESCS (SPS_MAX_DESC_FIFO_SIZE / SPS_MAX_DESC_LENGTH) + +/* + * DMA limitations + */ +/* upto 16 bits (64K - 1) */ +#define MMC_MAX_DMA_ROWS (64 * 1024 - 1) +#define MMC_MAX_DMA_BOX_LENGTH (MMC_MAX_DMA_ROWS * MCI_FIFOSIZE) +#define MMC_MAX_DMA_CMDS (MAX_NR_SG_DMA_PIO * (MMC_MAX_REQ_SIZE / \ + MMC_MAX_DMA_BOX_LENGTH)) struct clk; struct msmsdcc_nc_dmadata { - dmov_box cmd[NR_SG]; + dmov_box cmd[MMC_MAX_DMA_CMDS]; uint32_t cmdptr; }; @@ -174,17 +271,18 @@ struct msmsdcc_dma_data { int num_ents; int channel; + int crci; struct msmsdcc_host *host; int busy; /* Set if DM is busy */ - int active; - unsigned int result; + unsigned int result; struct msm_dmov_errdata err; }; struct msmsdcc_pio_data { - struct scatterlist *sg; - unsigned int sg_len; - unsigned int sg_off; + struct sg_mapping_iter sg_miter; + char bounce_buf[4]; + /* valid bytes in bounce_buf */ + int bounce_buf_len; }; struct msmsdcc_curr_req { @@ -195,31 +293,65 @@ struct msmsdcc_curr_req { unsigned int xfer_remain; /* Bytes remaining to send */ unsigned int data_xfered; /* Bytes acked by BLKEND irq */ int got_dataend; + int wait_for_auto_prog_done; + int got_auto_prog_done; + bool use_wr_data_pend; int user_pages; + u32 req_tout_ms; }; -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; + bool pipe_reset_pending; + struct tasklet_struct tlet; +}; + +struct msmsdcc_msm_bus_vote { + uint32_t client_handle; + uint32_t curr_vote; + int min_bw_vote; + int max_bw_vote; + bool is_max_bw_needed; + struct delayed_work vote_work; }; struct msmsdcc_host { - struct resource *cmd_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; + struct resource *dma_crci_res; 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 */ @@ -227,30 +359,79 @@ 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; + u32 sdcc_version; - 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_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_sent; + + struct wake_lock sdio_wlock; + struct wake_lock sdio_suspend_wlock; + struct timer_list req_tout_timer; + unsigned long reg_write_delay; + bool io_pad_pwr_switch; + bool tuning_in_progress; + bool tuning_needed; + bool sdio_gpio_lpm; + bool irq_wake_enabled; + struct pm_qos_request pm_qos_req_dma; + u32 cpu_dma_latency; + bool sdcc_suspending; + bool sdcc_irq_disabled; + bool sdcc_suspended; + bool sdio_wakeupirq_disabled; + struct mutex clk_mutex; + bool pending_resume; + struct msmsdcc_msm_bus_vote msm_bus_vote; }; +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) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + int ret; + + ret = msmsdcc_sdio_al_lpm(mmc, false); + wake_unlock(&host->sdio_wlock); + return ret; +} +#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 0000000000000000000000000000000000000000..320f52e4e6042fa9f32c314e8578cda8cbc6cb98 --- /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 0000000000000000000000000000000000000000..f0e1b78e7a8abf5991415a89e00eb586369c0970 --- /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/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 56d4499d43889e42a971fcfffb9bd0c334645294..1c4697d9e6f6d5e97b9fe9be2bd9fe2ee608efc5 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -1389,6 +1389,8 @@ static void set_data_timeout(struct omap_hsmmc_host *host, dto -= 13; else dto = 0; + /* Use the maximum timeout value allowed in the standard of 14 + or 0xE */ if (dto > 14) dto = 14; } diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 69ef0beae104f3a83d4a36277b129e20ba5db207..b34b069b545d039f363554ec5d56afe6dddad211 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -367,6 +367,8 @@ static int o2_probe(struct sdhci_pci_chip *chip) pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch); } + slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC; + return 0; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 37601ec6a958a5eba9be8b85bb3cd1c742a53963..2f7a2f3869ebebecea7e0a97255c6e1546936e62 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -21,7 +21,6 @@ #include #include #include -#include #include @@ -43,76 +42,61 @@ #define MAX_TUNING_LOOP 40 static unsigned int debug_quirks = 0; -static unsigned int debug_quirks2; static void sdhci_finish_data(struct sdhci_host *); static void sdhci_send_command(struct sdhci_host *, struct mmc_command *); static void sdhci_finish_command(struct sdhci_host *); -static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); +static int sdhci_execute_tuning(struct mmc_host *mmc); static void sdhci_tuning_timer(unsigned long data); -#ifdef CONFIG_PM_RUNTIME -static int sdhci_runtime_pm_get(struct sdhci_host *host); -static int sdhci_runtime_pm_put(struct sdhci_host *host); -#else -static inline int sdhci_runtime_pm_get(struct sdhci_host *host) -{ - return 0; -} -static inline int sdhci_runtime_pm_put(struct sdhci_host *host) -{ - return 0; -} -#endif - static void sdhci_dumpregs(struct sdhci_host *host) { - pr_debug(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", + printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", mmc_hostname(host->mmc)); - pr_debug(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", sdhci_readl(host, SDHCI_DMA_ADDRESS), sdhci_readw(host, SDHCI_HOST_VERSION)); - pr_debug(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", sdhci_readw(host, SDHCI_BLOCK_SIZE), sdhci_readw(host, SDHCI_BLOCK_COUNT)); - pr_debug(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", sdhci_readl(host, SDHCI_ARGUMENT), sdhci_readw(host, SDHCI_TRANSFER_MODE)); - pr_debug(DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", sdhci_readl(host, SDHCI_PRESENT_STATE), sdhci_readb(host, SDHCI_HOST_CONTROL)); - pr_debug(DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", sdhci_readb(host, SDHCI_POWER_CONTROL), sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL)); - pr_debug(DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", sdhci_readb(host, SDHCI_WAKE_UP_CONTROL), sdhci_readw(host, SDHCI_CLOCK_CONTROL)); - pr_debug(DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", sdhci_readb(host, SDHCI_TIMEOUT_CONTROL), sdhci_readl(host, SDHCI_INT_STATUS)); - pr_debug(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", sdhci_readl(host, SDHCI_INT_ENABLE), sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); - pr_debug(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", sdhci_readw(host, SDHCI_ACMD12_ERR), sdhci_readw(host, SDHCI_SLOT_INT_STATUS)); - pr_debug(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", sdhci_readl(host, SDHCI_CAPABILITIES), sdhci_readl(host, SDHCI_CAPABILITIES_1)); - pr_debug(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", sdhci_readw(host, SDHCI_COMMAND), sdhci_readl(host, SDHCI_MAX_CURRENT)); - pr_debug(DRIVER_NAME ": Host ctl2: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": Host ctl2: 0x%08x\n", sdhci_readw(host, SDHCI_HOST_CONTROL2)); if (host->flags & SDHCI_USE_ADMA) - pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", + printk(KERN_DEBUG DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", readl(host->ioaddr + SDHCI_ADMA_ERROR), readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); - pr_debug(DRIVER_NAME ": ===========================================\n"); + printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n"); } /*****************************************************************************\ @@ -144,16 +128,12 @@ static void sdhci_mask_irqs(struct sdhci_host *host, u32 irqs) static void sdhci_set_card_detection(struct sdhci_host *host, bool enable) { - u32 present, irqs; + u32 irqs = SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT; if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) || (host->mmc->caps & MMC_CAP_NONREMOVABLE)) return; - present = sdhci_readl(host, SDHCI_PRESENT_STATE) & - SDHCI_CARD_PRESENT; - irqs = present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT; - if (enable) sdhci_unmask_irqs(host, irqs); else @@ -198,7 +178,7 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask) /* hw clears the bit when it's done */ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { if (timeout == 0) { - pr_err("%s: Reset 0x%x never completed.\n", + printk(KERN_ERR "%s: Reset 0x%x never completed.\n", mmc_hostname(host->mmc), (int)mask); sdhci_dumpregs(host); return; @@ -212,11 +192,6 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask) if (host->quirks & SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET) sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK, ier); - - if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { - if ((host->ops->enable_dma) && (mask & SDHCI_RESET_ALL)) - host->ops->enable_dma(host); - } } static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); @@ -274,14 +249,11 @@ static void sdhci_led_control(struct led_classdev *led, spin_lock_irqsave(&host->lock, flags); - if (host->runtime_suspended) - goto out; - if (brightness == LED_OFF) sdhci_deactivate_led(host); else sdhci_activate_led(host); -out: + spin_unlock_irqrestore(&host->lock, flags); } #endif @@ -426,12 +398,12 @@ static void sdhci_transfer_pio(struct sdhci_host *host) static char *sdhci_kmap_atomic(struct scatterlist *sg, unsigned long *flags) { local_irq_save(*flags); - return kmap_atomic(sg_page(sg)) + sg->offset; + return kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset; } static void sdhci_kunmap_atomic(void *buffer, unsigned long *flags) { - kunmap_atomic(buffer); + kunmap_atomic(buffer, KM_BIO_SRC_IRQ); local_irq_restore(*flags); } @@ -654,11 +626,12 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) /* timeout in us */ if (!data) target_timeout = cmd->cmd_timeout_ms * 1000; - else { - target_timeout = data->timeout_ns / 1000; - if (host->clock) - target_timeout += data->timeout_clks / host->clock; - } + else + target_timeout = data->timeout_ns / 1000 + + data->timeout_clks / host->clock; + + if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK) + host->timeout_clk = host->clock / 1000; /* * Figure out needed cycles. @@ -670,6 +643,7 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) * => * (1) / (2) > 2^6 */ + BUG_ON(!host->timeout_clk); count = 0; current_timeout = (1 << 13) * 1000 / host->timeout_clk; while (current_timeout < target_timeout) { @@ -679,7 +653,9 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) break; } - if (count >= 0xF) + if (count >= 0xF) { + printk(KERN_WARNING "%s: Too large timeout requested for CMD%d!\n", + mmc_hostname(host->mmc), cmd->opcode); count = 0xE; return count; @@ -972,7 +948,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) { if (timeout == 0) { - pr_err("%s: Controller never released " + printk(KERN_ERR "%s: Controller never released " "inhibit bit(s).\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); cmd->error = -EIO; @@ -994,7 +970,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) sdhci_set_transfer_mode(host, cmd); if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { - pr_err("%s: Unsupported response type!\n", + printk(KERN_ERR "%s: Unsupported response type!\n", mmc_hostname(host->mmc)); cmd->error = -EINVAL; tasklet_schedule(&host->finish_tasklet); @@ -1016,8 +992,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) flags |= SDHCI_CMD_INDEX; /* CMD19 is special in that the Data Present Select should be set */ - if (cmd->data || cmd->opcode == MMC_SEND_TUNING_BLOCK || - cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) + if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK)) flags |= SDHCI_CMD_DATA; sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); @@ -1067,15 +1042,12 @@ static void sdhci_finish_command(struct sdhci_host *host) static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) { int div = 0; /* Initialized for compiler warning */ - int real_div = div, clk_mul = 1; u16 clk = 0; unsigned long timeout; if (clock && clock == host->clock) return; - host->mmc->actual_clock = 0; - if (host->ops->set_clock) { host->ops->set_clock(host, clock); if (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) @@ -1113,8 +1085,6 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) * Control register. */ clk = SDHCI_PROG_CLOCK_MODE; - real_div = div; - clk_mul = host->clk_mul; div--; } } else { @@ -1128,7 +1098,6 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) break; } } - real_div = div; div >>= 1; } } else { @@ -1137,13 +1106,9 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) if ((host->max_clk / div) <= clock) break; } - real_div = div; div >>= 1; } - if (real_div) - host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div; - clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) << SDHCI_DIVIDER_HI_SHIFT; @@ -1155,7 +1120,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) { if (timeout == 0) { - pr_err("%s: Internal clock never " + printk(KERN_ERR "%s: Internal clock never " "stabilised.\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; @@ -1171,7 +1136,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) host->clock = clock; } -static int sdhci_set_power(struct sdhci_host *host, unsigned short power) +static void sdhci_set_power(struct sdhci_host *host, unsigned short power) { u8 pwr = 0; @@ -1194,13 +1159,13 @@ static int sdhci_set_power(struct sdhci_host *host, unsigned short power) } if (host->pwr == pwr) - return -1; + return; host->pwr = pwr; if (pwr == 0) { sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); - return 0; + return; } /* @@ -1227,8 +1192,6 @@ static int sdhci_set_power(struct sdhci_host *host, unsigned short power) */ if (host->quirks & SDHCI_QUIRK_DELAY_AFTER_POWER) mdelay(10); - - return power; } /*****************************************************************************\ @@ -1245,8 +1208,6 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host = mmc_priv(mmc); - sdhci_runtime_pm_get(host); - spin_lock_irqsave(&host->lock, flags); WARN_ON(host->mrq != NULL); @@ -1290,7 +1251,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) if ((host->flags & SDHCI_NEEDS_RETUNING) && !(present_state & (SDHCI_DOING_WRITE | SDHCI_DOING_READ))) { spin_unlock_irqrestore(&host->lock, flags); - sdhci_execute_tuning(mmc, mrq->cmd->opcode); + sdhci_execute_tuning(mmc); spin_lock_irqsave(&host->lock, flags); /* Restore original mmc_request structure */ @@ -1307,20 +1268,18 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) spin_unlock_irqrestore(&host->lock, flags); } -static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) +static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { + struct sdhci_host *host; unsigned long flags; - int vdd_bit = -1; u8 ctrl; + host = mmc_priv(mmc); + spin_lock_irqsave(&host->lock, flags); - if (host->flags & SDHCI_DEVICE_DEAD) { - spin_unlock_irqrestore(&host->lock, flags); - if (host->vmmc && ios->power_mode == MMC_POWER_OFF) - mmc_regulator_set_ocr(host->mmc, host->vmmc, 0); - return; - } + if (host->flags & SDHCI_DEVICE_DEAD) + goto out; /* * Reset the chip on each power off. @@ -1334,15 +1293,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_set_clock(host, ios->clock); if (ios->power_mode == MMC_POWER_OFF) - vdd_bit = sdhci_set_power(host, -1); + sdhci_set_power(host, -1); else - vdd_bit = sdhci_set_power(host, ios->vdd); - - if (host->vmmc && vdd_bit != -1) { - spin_unlock_irqrestore(&host->lock, flags); - mmc_regulator_set_ocr(host->mmc, host->vmmc, vdd_bit); - spin_lock_irqsave(&host->lock, flags); - } + sdhci_set_power(host, ios->vdd); if (host->ops->platform_send_init_74_clocks) host->ops->platform_send_init_74_clocks(host, ios->power_mode); @@ -1385,8 +1338,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) unsigned int clock; /* In case of UHS-I modes, set High Speed Enable */ - if ((ios->timing == MMC_TIMING_MMC_HS200) || - (ios->timing == MMC_TIMING_UHS_SDR50) || + if ((ios->timing == MMC_TIMING_UHS_SDR50) || (ios->timing == MMC_TIMING_UHS_SDR104) || (ios->timing == MMC_TIMING_UHS_DDR50) || (ios->timing == MMC_TIMING_UHS_SDR25)) @@ -1439,9 +1391,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* Select Bus Speed Mode for host */ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; - if (ios->timing == MMC_TIMING_MMC_HS200) - ctrl_2 |= SDHCI_CTRL_HS_SDR200; - else if (ios->timing == MMC_TIMING_UHS_SDR12) + if (ios->timing == MMC_TIMING_UHS_SDR12) ctrl_2 |= SDHCI_CTRL_UHS_SDR12; else if (ios->timing == MMC_TIMING_UHS_SDR25) ctrl_2 |= SDHCI_CTRL_UHS_SDR25; @@ -1469,20 +1419,12 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); +out: mmiowb(); spin_unlock_irqrestore(&host->lock, flags); } -static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) -{ - struct sdhci_host *host = mmc_priv(mmc); - - sdhci_runtime_pm_get(host); - sdhci_do_set_ios(host, ios); - sdhci_runtime_pm_put(host); -} - -static int sdhci_check_ro(struct sdhci_host *host) +static int check_ro(struct sdhci_host *host) { unsigned long flags; int is_readonly; @@ -1506,16 +1448,19 @@ static int sdhci_check_ro(struct sdhci_host *host) #define SAMPLE_COUNT 5 -static int sdhci_do_get_ro(struct sdhci_host *host) +static int sdhci_get_ro(struct mmc_host *mmc) { + struct sdhci_host *host; int i, ro_count; + host = mmc_priv(mmc); + if (!(host->quirks & SDHCI_QUIRK_UNSTABLE_RO_DETECT)) - return sdhci_check_ro(host); + return check_ro(host); ro_count = 0; for (i = 0; i < SAMPLE_COUNT; i++) { - if (sdhci_check_ro(host)) { + if (check_ro(host)) { if (++ro_count > SAMPLE_COUNT / 2) return 1; } @@ -1524,64 +1469,38 @@ static int sdhci_do_get_ro(struct sdhci_host *host) return 0; } -static void sdhci_hw_reset(struct mmc_host *mmc) +static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) { - struct sdhci_host *host = mmc_priv(mmc); - - if (host->ops && host->ops->hw_reset) - host->ops->hw_reset(host); -} + struct sdhci_host *host; + unsigned long flags; -static int sdhci_get_ro(struct mmc_host *mmc) -{ - struct sdhci_host *host = mmc_priv(mmc); - int ret; + host = mmc_priv(mmc); - sdhci_runtime_pm_get(host); - ret = sdhci_do_get_ro(host); - sdhci_runtime_pm_put(host); - return ret; -} + spin_lock_irqsave(&host->lock, flags); -static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable) -{ if (host->flags & SDHCI_DEVICE_DEAD) goto out; - if (enable) - host->flags |= SDHCI_SDIO_IRQ_ENABLED; - else - host->flags &= ~SDHCI_SDIO_IRQ_ENABLED; - - /* SDIO IRQ will be enabled as appropriate in runtime resume */ - if (host->runtime_suspended) - goto out; - if (enable) sdhci_unmask_irqs(host, SDHCI_INT_CARD_INT); else sdhci_mask_irqs(host, SDHCI_INT_CARD_INT); out: mmiowb(); -} - -static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) -{ - struct sdhci_host *host = mmc_priv(mmc); - unsigned long flags; - spin_lock_irqsave(&host->lock, flags); - sdhci_enable_sdio_irq_nolock(host, enable); spin_unlock_irqrestore(&host->lock, flags); } -static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, - struct mmc_ios *ios) +static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) { + struct sdhci_host *host; u8 pwr; u16 clk, ctrl; u32 present_state; + host = mmc_priv(mmc); + /* * Signal Voltage Switching is only applicable for Host Controllers * v3.00 and above. @@ -1607,7 +1526,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, if (!(ctrl & SDHCI_CTRL_VDD_180)) return 0; else { - pr_info(DRIVER_NAME ": Switching to 3.3V " + printk(KERN_INFO DRIVER_NAME ": Switching to 3.3V " "signalling voltage failed\n"); return -EIO; } @@ -1666,7 +1585,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, pwr |= SDHCI_POWER_ON; sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); - pr_info(DRIVER_NAME ": Switching to 1.8V signalling " + printk(KERN_INFO DRIVER_NAME ": Switching to 1.8V signalling " "voltage failed, retrying with S18R set to 0\n"); return -EAGAIN; } else @@ -1674,21 +1593,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, return 0; } -static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, - struct mmc_ios *ios) -{ - struct sdhci_host *host = mmc_priv(mmc); - int err; - - if (host->version < SDHCI_SPEC_300) - return 0; - sdhci_runtime_pm_get(host); - err = sdhci_do_start_signal_voltage_switch(host, ios); - sdhci_runtime_pm_put(host); - return err; -} - -static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) +static int sdhci_execute_tuning(struct mmc_host *mmc) { struct sdhci_host *host; u16 ctrl; @@ -1696,35 +1601,26 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) int tuning_loop_counter = MAX_TUNING_LOOP; unsigned long timeout; int err = 0; - bool requires_tuning_nonuhs = false; host = mmc_priv(mmc); - sdhci_runtime_pm_get(host); disable_irq(host->irq); spin_lock(&host->lock); ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* - * The Host Controller needs tuning only in case of SDR104 mode - * and for SDR50 mode when Use Tuning for SDR50 is set in the + * Host Controller needs tuning only in case of SDR104 mode + * and for SDR50 mode when Use Tuning for SDR50 is set in * Capabilities register. - * If the Host Controller supports the HS200 mode then the - * tuning function has to be executed. */ - if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) && - (host->flags & SDHCI_SDR50_NEEDS_TUNING || - host->flags & SDHCI_HS200_NEEDS_TUNING)) - requires_tuning_nonuhs = true; - if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) || - requires_tuning_nonuhs) + (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) && + (host->flags & SDHCI_SDR50_NEEDS_TUNING))) ctrl |= SDHCI_CTRL_EXEC_TUNING; else { spin_unlock(&host->lock); enable_irq(host->irq); - sdhci_runtime_pm_put(host); return 0; } @@ -1750,12 +1646,12 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) timeout = 150; do { struct mmc_command cmd = {0}; - struct mmc_request mrq = {NULL}; + struct mmc_request mrq = {0}; if (!tuning_loop_counter && !timeout) break; - cmd.opcode = opcode; + cmd.opcode = MMC_SEND_TUNING_BLOCK; cmd.arg = 0; cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; cmd.retries = 0; @@ -1770,17 +1666,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) * block to the Host Controller. So we set the block size * to 64 here. */ - if (cmd.opcode == MMC_SEND_TUNING_BLOCK_HS200) { - if (mmc->ios.bus_width == MMC_BUS_WIDTH_8) - sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 128), - SDHCI_BLOCK_SIZE); - else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) - sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), - SDHCI_BLOCK_SIZE); - } else { - sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), - SDHCI_BLOCK_SIZE); - } + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE); /* * The tuning block is sent by the card to the host controller. @@ -1806,7 +1692,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) spin_lock(&host->lock); if (!host->tuning_done) { - pr_info(DRIVER_NAME ": Timeout waiting for " + printk(KERN_INFO DRIVER_NAME ": Timeout waiting for " "Buffer Read Ready interrupt during tuning " "procedure, falling back to fixed sampling " "clock\n"); @@ -1836,7 +1722,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); } else { if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) { - pr_info(DRIVER_NAME ": Tuning procedure" + printk(KERN_INFO DRIVER_NAME ": Tuning procedure" " failed, falling back to fixed sampling" " clock\n"); err = -EIO; @@ -1878,16 +1764,18 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier); spin_unlock(&host->lock); enable_irq(host->irq); - sdhci_runtime_pm_put(host); return err; } -static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable) +static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) { + struct sdhci_host *host; u16 ctrl; unsigned long flags; + host = mmc_priv(mmc); + /* Host Controller v3.00 defines preset value registers */ if (host->version < SDHCI_SPEC_300) return; @@ -1903,30 +1791,18 @@ static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable) if (enable && !(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) { ctrl |= SDHCI_CTRL_PRESET_VAL_ENABLE; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); - host->flags |= SDHCI_PV_ENABLED; } else if (!enable && (ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) { ctrl &= ~SDHCI_CTRL_PRESET_VAL_ENABLE; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); - host->flags &= ~SDHCI_PV_ENABLED; } spin_unlock_irqrestore(&host->lock, flags); } -static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) -{ - struct sdhci_host *host = mmc_priv(mmc); - - sdhci_runtime_pm_get(host); - sdhci_do_enable_preset_value(host, enable); - sdhci_runtime_pm_put(host); -} - static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro, - .hw_reset = sdhci_hw_reset, .enable_sdio_irq = sdhci_enable_sdio_irq, .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .execute_tuning = sdhci_execute_tuning, @@ -1948,19 +1824,19 @@ static void sdhci_tasklet_card(unsigned long param) spin_lock_irqsave(&host->lock, flags); - /* Check host->mrq first in case we are runtime suspended */ - if (host->mrq && - !(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { - pr_err("%s: Card removed during transfer!\n", - mmc_hostname(host->mmc)); - pr_err("%s: Resetting controller.\n", - mmc_hostname(host->mmc)); + if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { + if (host->mrq) { + printk(KERN_ERR "%s: Card removed during transfer!\n", + mmc_hostname(host->mmc)); + printk(KERN_ERR "%s: Resetting controller.\n", + mmc_hostname(host->mmc)); - sdhci_reset(host, SDHCI_RESET_CMD); - sdhci_reset(host, SDHCI_RESET_DATA); + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); - host->mrq->cmd->error = -ENOMEDIUM; - tasklet_schedule(&host->finish_tasklet); + host->mrq->cmd->error = -ENOMEDIUM; + tasklet_schedule(&host->finish_tasklet); + } } spin_unlock_irqrestore(&host->lock, flags); @@ -1976,16 +1852,14 @@ static void sdhci_tasklet_finish(unsigned long param) host = (struct sdhci_host*)param; - spin_lock_irqsave(&host->lock, flags); - /* * If this tasklet gets rescheduled while running, it will * be run again afterwards but without any active request. */ - if (!host->mrq) { - spin_unlock_irqrestore(&host->lock, flags); + if (!host->mrq) return; - } + + spin_lock_irqsave(&host->lock, flags); del_timer(&host->timer); @@ -2029,7 +1903,6 @@ static void sdhci_tasklet_finish(unsigned long param) spin_unlock_irqrestore(&host->lock, flags); mmc_request_done(host->mmc, mrq); - sdhci_runtime_pm_put(host); } static void sdhci_timeout_timer(unsigned long data) @@ -2042,7 +1915,7 @@ static void sdhci_timeout_timer(unsigned long data) spin_lock_irqsave(&host->lock, flags); if (host->mrq) { - pr_err("%s: Timeout waiting for hardware " + printk(KERN_ERR "%s: Timeout waiting for hardware " "interrupt.\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); @@ -2088,7 +1961,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) BUG_ON(intmask == 0); if (!host->cmd) { - pr_err("%s: Got command interrupt 0x%08x even " + printk(KERN_ERR "%s: Got command interrupt 0x%08x even " "though no command operation was in progress.\n", mmc_hostname(host->mmc), (unsigned)intmask); sdhci_dumpregs(host); @@ -2163,14 +2036,12 @@ static void sdhci_show_adma_error(struct sdhci_host *host) { } static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) { - u32 command; BUG_ON(intmask == 0); /* CMD19 generates _only_ Buffer Read Ready interrupt */ if (intmask & SDHCI_INT_DATA_AVAIL) { - command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); - if (command == MMC_SEND_TUNING_BLOCK || - command == MMC_SEND_TUNING_BLOCK_HS200) { + if (SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) == + MMC_SEND_TUNING_BLOCK) { host->tuning_done = 1; wake_up(&host->buf_ready_int); return; @@ -2190,7 +2061,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } } - pr_err("%s: Got data interrupt 0x%08x even " + printk(KERN_ERR "%s: Got data interrupt 0x%08x even " "though no data operation was in progress.\n", mmc_hostname(host->mmc), (unsigned)intmask); sdhci_dumpregs(host); @@ -2207,7 +2078,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) != MMC_BUS_TEST_R) host->data->error = -EILSEQ; else if (intmask & SDHCI_INT_ADMA_ERROR) { - pr_err("%s: ADMA error\n", mmc_hostname(host->mmc)); + printk(KERN_ERR "%s: ADMA error\n", mmc_hostname(host->mmc)); sdhci_show_adma_error(host); host->data->error = -EIO; } @@ -2269,13 +2140,6 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) spin_lock(&host->lock); - if (host->runtime_suspended) { - spin_unlock(&host->lock); - pr_warning("%s: got irq while runtime suspended\n", - mmc_hostname(host->mmc)); - return IRQ_HANDLED; - } - intmask = sdhci_readl(host, SDHCI_INT_STATUS); if (!intmask || intmask == 0xffffffff) { @@ -2288,30 +2152,13 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) mmc_hostname(host->mmc), intmask); if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { - u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & - SDHCI_CARD_PRESENT; - - /* - * There is a observation on i.mx esdhc. INSERT bit will be - * immediately set again when it gets cleared, if a card is - * inserted. We have to mask the irq to prevent interrupt - * storm which will freeze the system. And the REMOVE gets - * the same situation. - * - * More testing are needed here to ensure it works for other - * platforms though. - */ - sdhci_mask_irqs(host, present ? SDHCI_INT_CARD_INSERT : - SDHCI_INT_CARD_REMOVE); - sdhci_unmask_irqs(host, present ? SDHCI_INT_CARD_REMOVE : - SDHCI_INT_CARD_INSERT); - sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT | - SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS); - intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); + SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS); tasklet_schedule(&host->card_tasklet); } + intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); + if (intmask & SDHCI_INT_CMD_MASK) { sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, SDHCI_INT_STATUS); @@ -2329,7 +2176,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask &= ~SDHCI_INT_ERROR; if (intmask & SDHCI_INT_BUS_POWER) { - pr_err("%s: Card is consuming too much power!\n", + printk(KERN_ERR "%s: Card is consuming too much power!\n", mmc_hostname(host->mmc)); sdhci_writel(host, SDHCI_INT_BUS_POWER, SDHCI_INT_STATUS); } @@ -2342,6 +2189,9 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask &= ~SDHCI_INT_CARD_INT; if (intmask) { + printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n", + mmc_hostname(host->mmc), intmask); + sdhci_dumpregs(host); unexpected |= intmask; sdhci_writel(host, intmask, SDHCI_INT_STATUS); } @@ -2376,10 +2226,9 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) #ifdef CONFIG_PM -int sdhci_suspend_host(struct sdhci_host *host) +int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) { int ret; - bool has_tuning_timer; if (host->ops->platform_suspend) host->ops->platform_suspend(host); @@ -2387,28 +2236,21 @@ int sdhci_suspend_host(struct sdhci_host *host) sdhci_disable_card_detection(host); /* Disable tuning since we are suspending */ - has_tuning_timer = host->version >= SDHCI_SPEC_300 && - host->tuning_count && host->tuning_mode == SDHCI_TUNING_MODE_1; - if (has_tuning_timer) { + if (host->version >= SDHCI_SPEC_300 && host->tuning_count && + host->tuning_mode == SDHCI_TUNING_MODE_1) { del_timer_sync(&host->tuning_timer); host->flags &= ~SDHCI_NEEDS_RETUNING; } ret = mmc_suspend_host(host->mmc); - if (ret) { - if (has_tuning_timer) { - host->flags |= SDHCI_NEEDS_RETUNING; - mod_timer(&host->tuning_timer, jiffies + - host->tuning_count * HZ); - } - - sdhci_enable_card_detection(host); - + if (ret) return ret; - } free_irq(host->irq, host); + if (host->vmmc) + ret = regulator_disable(host->vmmc); + return ret; } @@ -2418,6 +2260,13 @@ int sdhci_resume_host(struct sdhci_host *host) { int ret; + if (host->vmmc) { + int ret = regulator_enable(host->vmmc); + if (ret) + return ret; + } + + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) host->ops->enable_dma(host); @@ -2468,90 +2317,6 @@ EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups); #endif /* CONFIG_PM */ -#ifdef CONFIG_PM_RUNTIME - -static int sdhci_runtime_pm_get(struct sdhci_host *host) -{ - return pm_runtime_get_sync(host->mmc->parent); -} - -static int sdhci_runtime_pm_put(struct sdhci_host *host) -{ - pm_runtime_mark_last_busy(host->mmc->parent); - return pm_runtime_put_autosuspend(host->mmc->parent); -} - -int sdhci_runtime_suspend_host(struct sdhci_host *host) -{ - unsigned long flags; - int ret = 0; - - /* Disable tuning since we are suspending */ - if (host->version >= SDHCI_SPEC_300 && - host->tuning_mode == SDHCI_TUNING_MODE_1) { - del_timer_sync(&host->tuning_timer); - host->flags &= ~SDHCI_NEEDS_RETUNING; - } - - spin_lock_irqsave(&host->lock, flags); - sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); - spin_unlock_irqrestore(&host->lock, flags); - - synchronize_irq(host->irq); - - spin_lock_irqsave(&host->lock, flags); - host->runtime_suspended = true; - spin_unlock_irqrestore(&host->lock, flags); - - return ret; -} -EXPORT_SYMBOL_GPL(sdhci_runtime_suspend_host); - -int sdhci_runtime_resume_host(struct sdhci_host *host) -{ - unsigned long flags; - int ret = 0, host_flags = host->flags; - - if (host_flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { - if (host->ops->enable_dma) - host->ops->enable_dma(host); - } - - sdhci_init(host, 0); - - /* Force clock and power re-program */ - host->pwr = 0; - host->clock = 0; - sdhci_do_set_ios(host, &host->mmc->ios); - - sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios); - if (host_flags & SDHCI_PV_ENABLED) - sdhci_do_enable_preset_value(host, true); - - /* Set the re-tuning expiration flag */ - if ((host->version >= SDHCI_SPEC_300) && host->tuning_count && - (host->tuning_mode == SDHCI_TUNING_MODE_1)) - host->flags |= SDHCI_NEEDS_RETUNING; - - spin_lock_irqsave(&host->lock, flags); - - host->runtime_suspended = false; - - /* Enable SDIO IRQ */ - if ((host->flags & SDHCI_SDIO_IRQ_ENABLED)) - sdhci_enable_sdio_irq_nolock(host, true); - - /* Enable Card Detection */ - sdhci_enable_card_detection(host); - - spin_unlock_irqrestore(&host->lock, flags); - - return ret; -} -EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host); - -#endif - /*****************************************************************************\ * * * Device allocation/registration * @@ -2594,8 +2359,6 @@ int sdhci_add_host(struct sdhci_host *host) if (debug_quirks) host->quirks = debug_quirks; - if (debug_quirks2) - host->quirks2 = debug_quirks2; sdhci_reset(host, SDHCI_RESET_ALL); @@ -2603,7 +2366,7 @@ int sdhci_add_host(struct sdhci_host *host) host->version = (host->version & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT; if (host->version > SDHCI_SPEC_300) { - pr_err("%s: Unknown controller version (%d). " + printk(KERN_ERR "%s: Unknown controller version (%d). " "You may experience problems.\n", mmc_hostname(mmc), host->version); } @@ -2640,7 +2403,7 @@ int sdhci_add_host(struct sdhci_host *host) if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) { if (host->ops->enable_dma(host)) { - pr_warning("%s: No suitable DMA " + printk(KERN_WARNING "%s: No suitable DMA " "available. Falling back to PIO.\n", mmc_hostname(mmc)); host->flags &= @@ -2660,7 +2423,7 @@ int sdhci_add_host(struct sdhci_host *host) if (!host->adma_desc || !host->align_buffer) { kfree(host->adma_desc); kfree(host->align_buffer); - pr_warning("%s: Unable to allocate ADMA " + printk(KERN_WARNING "%s: Unable to allocate ADMA " "buffers. Falling back to standard DMA.\n", mmc_hostname(mmc)); host->flags &= ~SDHCI_USE_ADMA; @@ -2688,13 +2451,46 @@ int sdhci_add_host(struct sdhci_host *host) if (host->max_clk == 0 || host->quirks & SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN) { if (!host->ops->get_max_clock) { - pr_err("%s: Hardware doesn't specify base clock " + printk(KERN_ERR + "%s: Hardware doesn't specify base clock " "frequency.\n", mmc_hostname(mmc)); return -ENODEV; } host->max_clk = host->ops->get_max_clock(host); } + host->timeout_clk = + (caps[0] & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; + if (host->timeout_clk == 0) { + if (host->ops->get_timeout_clock) { + host->timeout_clk = host->ops->get_timeout_clock(host); + } else if (!(host->quirks & + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)) { + printk(KERN_ERR + "%s: Hardware doesn't specify timeout clock " + "frequency.\n", mmc_hostname(mmc)); + return -ENODEV; + } + } + 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. @@ -2727,26 +2523,6 @@ int sdhci_add_host(struct sdhci_host *host) } else mmc->f_min = host->max_clk / SDHCI_MAX_DIV_SPEC_200; - host->timeout_clk = - (caps[0] & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; - if (host->timeout_clk == 0) { - if (host->ops->get_timeout_clock) { - host->timeout_clk = host->ops->get_timeout_clock(host); - } else if (!(host->quirks & - SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)) { - pr_err("%s: Hardware doesn't specify timeout clock " - "frequency.\n", mmc_hostname(mmc)); - return -ENODEV; - } - } - if (caps[0] & SDHCI_TIMEOUT_CLK_UNIT) - host->timeout_clk *= 1000; - - if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK) - host->timeout_clk = mmc->f_max / 1000; - - mmc->max_discard_to = (1 << 27) / host->timeout_clk; - mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23; if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12) @@ -2793,14 +2569,10 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[1] & SDHCI_SUPPORT_DDR50) mmc->caps |= MMC_CAP_UHS_DDR50; - /* Does the host need tuning for SDR50? */ + /* Does the host needs tuning for SDR50? */ if (caps[1] & SDHCI_USE_SDR50_TUNING) host->flags |= SDHCI_SDR50_NEEDS_TUNING; - /* Does the host need tuning for HS200? */ - if (mmc->caps2 & MMC_CAP2_HS200) - host->flags |= SDHCI_HS200_NEEDS_TUNING; - /* Driver Type(s) (A, C, D) supported by the host */ if (caps[1] & SDHCI_DRIVER_TYPE_A) mmc->caps |= MMC_CAP_DRIVER_TYPE_A; @@ -2907,7 +2679,7 @@ int sdhci_add_host(struct sdhci_host *host) mmc->ocr_avail_mmc &= host->ocr_avail_mmc; if (mmc->ocr_avail == 0) { - pr_err("%s: Hardware doesn't report any " + printk(KERN_ERR "%s: Hardware doesn't report any " "support voltages.\n", mmc_hostname(mmc)); return -ENODEV; } @@ -2955,7 +2727,7 @@ int sdhci_add_host(struct sdhci_host *host) mmc->max_blk_size = (caps[0] & SDHCI_MAX_BLOCK_MASK) >> SDHCI_MAX_BLOCK_SHIFT; if (mmc->max_blk_size >= 3) { - pr_warning("%s: Invalid maximum block size, " + printk(KERN_WARNING "%s: Invalid maximum block size, " "assuming 512 bytes\n", mmc_hostname(mmc)); mmc->max_blk_size = 0; } @@ -2994,8 +2766,10 @@ int sdhci_add_host(struct sdhci_host *host) host->vmmc = regulator_get(mmc_dev(mmc), "vmmc"); if (IS_ERR(host->vmmc)) { - pr_info("%s: no vmmc regulator found\n", mmc_hostname(mmc)); + printk(KERN_INFO "%s: no vmmc regulator found\n", mmc_hostname(mmc)); host->vmmc = NULL; + } else { + regulator_enable(host->vmmc); } sdhci_init(host, 0); @@ -3021,7 +2795,7 @@ int sdhci_add_host(struct sdhci_host *host) mmc_add_host(mmc); - pr_info("%s: SDHCI controller on %s [%s] using %s\n", + printk(KERN_INFO "%s: SDHCI controller on %s [%s] using %s\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), (host->flags & SDHCI_USE_ADMA) ? "ADMA" : (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); @@ -3054,7 +2828,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) host->flags |= SDHCI_DEVICE_DEAD; if (host->mrq) { - pr_err("%s: Controller removed during " + printk(KERN_ERR "%s: Controller removed during " " transfer!\n", mmc_hostname(host->mmc)); host->mrq->cmd->error = -ENOMEDIUM; @@ -3084,8 +2858,10 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); - if (host->vmmc) + if (host->vmmc) { + regulator_disable(host->vmmc); regulator_put(host->vmmc); + } kfree(host->adma_desc); kfree(host->align_buffer); @@ -3111,9 +2887,9 @@ EXPORT_SYMBOL_GPL(sdhci_free_host); static int __init sdhci_drv_init(void) { - pr_info(DRIVER_NAME + printk(KERN_INFO DRIVER_NAME ": Secure Digital Host Controller Interface driver\n"); - pr_info(DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); + printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); return 0; } @@ -3126,11 +2902,9 @@ module_init(sdhci_drv_init); module_exit(sdhci_drv_exit); module_param(debug_quirks, uint, 0444); -module_param(debug_quirks2, uint, 0444); MODULE_AUTHOR("Pierre Ossman "); MODULE_DESCRIPTION("Secure Digital Host Controller Interface core driver"); MODULE_LICENSE("GPL"); MODULE_PARM_DESC(debug_quirks, "Force certain quirks."); -MODULE_PARM_DESC(debug_quirks2, "Force certain other quirks."); diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 5760c1a4b3f66ea3125b71d28ab4f62ceb4ee925..04796aff10e2ea9812850cb12b1d049a29905e3b 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -314,6 +314,17 @@ config MTD_SWAP The driver provides wear leveling by storing erase counter into the OOB. +config MTD_LAZYECCSTATS + bool "MTD Lazy ECC Stats collection support" + default y + help + Normally bad block counts for ECC stats are collected at boot time. + This option delays the badblock stats collection until ECCGETSTATS + ioctl is invoked on the partition. + + This can significantly decrease boot times depending on the size of + the partition. If unsure, say 'N'. + source "drivers/mtd/chips/Kconfig" source "drivers/mtd/maps/Kconfig" diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 4cdb2af7bf44e74dbb72bab223d39beabd7c265a..21f146fe7ff061206a192d098ff16bd68153194d 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -50,6 +50,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 a4dd1d822b6c0db81a2bafd7c9ed6b790ca4ac2e..8497c5f77d6614787d00fe868c038da701049317 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -12,6 +12,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 @@ -20,4 +21,4 @@ obj-$(CONFIG_MTD_M25P80) += m25p80.o obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o obj-$(CONFIG_MTD_SST25L) += sst25l.o -CFLAGS_docg3.o += -I$(src) \ No newline at end of file +CFLAGS_docg3.o += -I$(src) diff --git a/drivers/mtd/devices/msm_nand.c b/drivers/mtd/devices/msm_nand.c new file mode 100644 index 0000000000000000000000000000000000000000..1748df731528243f56c948aae8ebc36cdad60e6b --- /dev/null +++ b/drivers/mtd/devices/msm_nand.c @@ -0,0 +1,7128 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2012, 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; + +#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, 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, 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, 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, + 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_OPS_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_OPS_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if (ops->mode != MTD_OPS_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_OPS_AUTO_OOB) + start_sector = cwperpage - 1; + + if (ops->oobbuf && !ops->datbuf) { + page_count = ops->ooblen / ((ops->mode == MTD_OPS_AUTO_OOB) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OPS_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_OPS_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_OPS_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_OPS_AUTO_OOB)) { + cmd->cmd = 0; + if (n == (cwperpage - 1)) { + cmd->src = MSM_NAND_FLASH_BUFFER + + (512 - ((cwperpage - 1) << 2)); + sectoroobsize = (cwperpage << 2); + if (ops->mode != MTD_OPS_AUTO_OOB) + 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, + 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_OPS_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_OPS_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_OPS_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_OPS_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if (ops->mode != MTD_OPS_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_OPS_AUTO_OOB) + start_sector = cwperpage - 1; + + if (ops->oobbuf && !ops->datbuf) { + page_count = ops->ooblen / ((ops->mode == MTD_OPS_AUTO_OOB) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OPS_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_OPS_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_OPS_AUTO_OOB)) { + if (ops->mode != MTD_OPS_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_OPS_AUTO_OOB)) { + 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_OPS_AUTO_OOB) + 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, + 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_OPS_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_OPS_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_OPS_PLACE_OOB; + 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_OPS_RAW) { + if (ops->ooblen != 0 && ops->mode != MTD_OPS_AUTO_OOB) { + 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_OPS_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_OPS_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_OPS_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_OPS_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_OPS_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_OPS_AUTO_OOB) { + /* 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, + 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_OPS_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_OPS_RAW) { + if (ops->ooblen != 0 && ops->mode != MTD_OPS_AUTO_OOB) { + 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_OPS_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_OPS_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_OPS_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_OPS_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_OPS_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_OPS_AUTO_OOB) { + /* 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, + 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_OPS_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_OPS_PLACE_OOB; + 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, 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, 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, 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, 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_OPS_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, 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_OPS_PLACE_OOB) && (ops->mode != MTD_OPS_AUTO_OOB) && + (ops->mode != MTD_OPS_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_OPS_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_OPS_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OPS_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_OPS_AUTO_OOB) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OPS_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + if ((ops->mode == MTD_OPS_PLACE_OOB) && (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_OPS_PLACE_OOB) && (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_OPS_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_OPS_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_OPS_AUTO_OOB) { + 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_OPS_PLACE_OOB) { + 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_OPS_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, + 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_OPS_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_OPS_RAW) + ops->retlen = mtd->writesize * pages_read; + else + ops->retlen = (mtd->writesize + mtd->oobsize) + * pages_read; + } + if (ops->oobbuf != NULL) { + if (ops->mode == MTD_OPS_AUTO_OOB) + 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_OPS_PLACE_OOB; + 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_OPS_PLACE_OOB) && (ops->mode != MTD_OPS_AUTO_OOB) && + (ops->mode != MTD_OPS_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_OPS_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_OPS_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OPS_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_OPS_AUTO_OOB) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OPS_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + if ((ops->mode == MTD_OPS_AUTO_OOB) && (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_OPS_PLACE_OOB) && (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_OPS_PLACE_OOB) && (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_OPS_AUTO_OOB)) { + 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_OPS_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_OPS_RAW)) { + + /* Transfer user buf contents into nand ctlr buffer */ + if (ops->mode == MTD_OPS_AUTO_OOB) { + cmd->cmd = 0; + cmd->src = init_dma_addr; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = mtd->oobsize; + cmd++; + } + if (ops->mode == MTD_OPS_PLACE_OOB) { + 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_OPS_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, + 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_OPS_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_OPS_RAW) + ops->retlen = mtd->writesize * pages_written; + else + ops->retlen = (mtd->writesize + mtd->oobsize) + * pages_written; + } + if (ops->oobbuf != NULL) { + if (ops->mode == MTD_OPS_AUTO_OOB) + 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_OPS_PLACE_OOB; + 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, 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_OPS_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_OPS_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, + 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, + 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) & 0x3)) * + (supported_flash.pagesize >> 9); + + if ((supported_flash.oobsize > 64) && + (supported_flash.pagesize == 2048)) { + pr_info("msm_nand: Found a 2K page device with" + " %d oobsize - changing oobsize to 64 " + "bytes.\n", supported_flash.oobsize); + supported_flash.oobsize = 64; + } + } 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; */ + + /* Deregister the device */ + mtd_device_unregister(mtd); +} +EXPORT_SYMBOL_GPL(msm_nand_release); + +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, 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; +} + +static int setup_mtd_device(struct platform_device *pdev, + struct msm_nand_info *info) +{ + int i, err; + struct flash_platform_data *pdata = pdev->dev.platform_data; + + if (pdata) { + 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; + } + err = mtd_device_register(&info->mtd, pdata->parts, + pdata->nr_parts); + } else { + err = mtd_device_register(&info->mtd, NULL, 0); + } + return err; +} + +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); + + 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; + } + + err = setup_mtd_device(pdev, info); + if (err < 0) { + pr_err("%s: setup_mtd_device failed with err=%d\n", + __func__, err); + goto out_free_dma_buffer; + } + + 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) { + 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 0000000000000000000000000000000000000000..2729c6bae237f1cb4efd5d47de7ec1fc10367e7d --- /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 f1f06715d4e0b8e3f800e456ef83aa3d27fee3ce..11c9dce312c27984e15d6153a7b97bad4d9d8137 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -426,6 +426,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); queue_flag_set_unlocked(QUEUE_FLAG_NONROT, new->rq); diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index f2f482bec5736b21a562da5e4fda11375e8cf457..4e12bb7e317a088a3fd296678678bb9a85fd40bb 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -968,6 +969,9 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg) case ECCGETSTATS: { +#ifdef CONFIG_MTD_LAZYECCSTATS + part_fill_badblockstats(mtd); +#endif if (copy_to_user(argp, &mtd->ecc_stats, sizeof(struct mtd_ecc_stats))) return -EFAULT; diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 9651c06de0a9298f4db58265524ecaebe2c2b9b4..0c634ca8c3e6e393fb9ef8dc251e8611582dc8de 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -313,6 +313,21 @@ static inline void free_partition(struct mtd_part *p) kfree(p); } +void part_fill_badblockstats(struct mtd_info *mtd) +{ + struct mtd_part *part = PART(mtd); + if (part->master->_block_isbad) { + uint64_t offs = 0; + mtd->ecc_stats.badblocks = 0; + while (offs < mtd->size) { + if (mtd_block_isbad(part->master, + offs + part->offset)) + mtd->ecc_stats.badblocks++; + offs += mtd->erasesize; + } + } +} + /* * This function unregisters and destroy all slave MTD objects which are * attached to the given master MTD object. @@ -517,6 +532,10 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, slave->mtd.ecclayout = master->ecclayout; slave->mtd.ecc_strength = master->ecc_strength; + +#ifndef CONFIG_MTD_LAZYECCSTATS + part_fill_badblockstats(&(slave->mtd)); +#endif if (master->_block_isbad) { uint64_t offs = 0; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 47b19c0bb070e3da612b031848496f7551c2589f..315efbb3c2a339bb32ee2d65de89f5fcd6602415 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -3219,6 +3219,44 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, } EXPORT_SYMBOL(nand_scan_ident); +static void nand_panic_wait(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + int i; + + if (chip->state != FL_READY) + for (i = 0; i < 40; i++) { + if (chip->dev_ready(mtd)) + break; + mdelay(10); + } + chip->state = FL_READY; +} + +static int nand_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + /* Do not allow reads past end of device */ + if ((to + len) > mtd->size) + return -EINVAL; + if (!len) + return 0; + + nand_panic_wait(mtd); + + chip->ops.len = len; + chip->ops.datbuf = (uint8_t *)buf; + chip->ops.oobbuf = NULL; + + ret = nand_do_write_ops(mtd, to, &chip->ops); + + *retlen = chip->ops.retlen; + return ret; +} + /** * nand_scan_tail - [NAND Interface] Scan for the NAND device diff --git a/drivers/mtd/tests/Makefile b/drivers/mtd/tests/Makefile index b44dcab940d889346c4dfa3f22a8af348f32062c..8089d9d4041f5b02aa6d08223e1485ab1ddd8f1c 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 0000000000000000000000000000000000000000..67f0f8490370c08a6e9363756961b0b079395995 --- /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/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index c63a64cb608546b0a13fcd48842998786261dca9..c82f7d1a1331ad66e99701035227057e05715038 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -98,6 +98,7 @@ config MIPS_SIM_NET emulated by the MIPS Simulator. If you are not using a MIPSsim or are unsure, say N. +source "drivers/net/ethernet/msm/Kconfig" source "drivers/net/ethernet/myricom/Kconfig" config FEALNX diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 9676a5109d94a42d7ed8ee35a9a6ddbe776ecac7..50fdf5ec0faa26cd7a12c0427df5370c4d274cf1 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ obj-$(CONFIG_NET_VENDOR_MICREL) += micrel/ obj-$(CONFIG_NET_VENDOR_MICROCHIP) += microchip/ obj-$(CONFIG_MIPS_SIM_NET) += mipsnet.o +obj-$(CONFIG_ARCH_MSM) += msm/ obj-$(CONFIG_NET_VENDOR_MYRI) += myricom/ obj-$(CONFIG_FEALNX) += fealnx.o obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/ diff --git a/drivers/net/ethernet/msm/Kconfig b/drivers/net/ethernet/msm/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..095cb4d674503e50706f4258dae2fac6ed090312 --- /dev/null +++ b/drivers/net/ethernet/msm/Kconfig @@ -0,0 +1,52 @@ +# +# msm network device configuration +# + +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_SMUX + bool "RMNET SMUX Driver" + depends on N_SMUX + help + Implements RMNET over SMUX interface. + RMNET provides a virtual ethernet interface + for routing IP packets within the MSM using + HSUART 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 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. diff --git a/drivers/net/ethernet/msm/Makefile b/drivers/net/ethernet/msm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7d9d4c63b158a12f1e2bd1d96027fb5c74d50d15 --- /dev/null +++ b/drivers/net/ethernet/msm/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the msm networking support. +# + +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_MSM_RMNET_SMUX) += msm_rmnet_smux.o +obj-$(CONFIG_QFEC) += qfec.o diff --git a/drivers/net/ethernet/msm/msm_rmnet.c b/drivers/net/ethernet/msm/msm_rmnet.c new file mode 100644 index 0000000000000000000000000000000000000000..61df24167d9a0addf6d0dbf182a2eaf2609fdc9d --- /dev/null +++ b/drivers/net/ethernet/msm/msm_rmnet.c @@ -0,0 +1,846 @@ +/* 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; +} + +static void smd_net_data_handler(unsigned long arg); +static DECLARE_TASKLET(smd_net_data_tasklet, smd_net_data_handler, 0); + +/* 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; + + skb = dev_alloc_skb(sz + NET_IP_ALIGN); + if (skb == NULL) { + pr_err("[%s] rmnet_recv() cannot allocate skb\n", + dev->name); + /* out of memory, reschedule a later attempt */ + smd_net_data_tasklet.data = (unsigned long)dev; + tasklet_schedule(&smd_net_data_tasklet); + break; + } 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 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_rx_mode = 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_rx_mode = 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/ethernet/msm/msm_rmnet_bam.c b/drivers/net/ethernet/msm/msm_rmnet_bam.c new file mode 100644 index 0000000000000000000000000000000000000000..fbe8d3cc352e116bde14572ca2c52d7424d7b0cb --- /dev/null +++ b/drivers/net/ethernet/msm/msm_rmnet_bam.c @@ -0,0 +1,826 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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 *waiting_for_ul_skb; + spinlock_t lock; + spinlock_t tx_queue_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; +} + +/* 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; + /* if write() succeeds, skb access is unsafe in this process */ + bam_ret = msm_bam_dmux_write(p->ch_id, skb); + + if (bam_ret != 0 && bam_ret != -EAGAIN && bam_ret != -EFAULT) { + pr_err("[%s] %s: write returned error %d", + dev->name, __func__, bam_ret); + return -EPERM; + } + + return bam_ret; +} + +static void bam_write_done(void *dev, struct sk_buff *skb) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 opmode = p->operation_mode; + unsigned long flags; + + DBG1("%s: write complete\n", __func__); + 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", + ((struct net_device *)(dev))->name, p->stats.tx_packets, + skb->len, skb->mark); + dev_kfree_skb_any(skb); + + spin_lock_irqsave(&p->tx_queue_lock, flags); + if (netif_queue_stopped(dev) && + msm_bam_dmux_is_ch_low(p->ch_id)) { + DBG0("%s: Low WM hit, waking queue=%p\n", + __func__, skb); + netif_wake_queue(dev); + } + spin_unlock_irqrestore(&p->tx_queue_lock, flags); +} + +static void bam_notify(void *dev, int event, unsigned long data) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + + switch (event) { + case BAM_DMUX_RECEIVE: + bam_recv_notify(dev, (struct sk_buff *)(data)); + break; + case BAM_DMUX_WRITE_DONE: + bam_write_done(dev, (struct sk_buff *)(data)); + break; + case BAM_DMUX_UL_CONNECTED: + spin_lock_irqsave(&p->lock, flags); + if (p->waiting_for_ul_skb != NULL) { + struct sk_buff *skb; + int ret; + + skb = p->waiting_for_ul_skb; + p->waiting_for_ul_skb = NULL; + spin_unlock_irqrestore(&p->lock, flags); + ret = _rmnet_xmit(skb, dev); + if (ret) { + pr_err("%s: error %d dropping delayed TX SKB %p\n", + __func__, ret, skb); + dev_kfree_skb_any(skb); + } + netif_wake_queue(dev); + } else { + spin_unlock_irqrestore(&p->lock, flags); + } + break; + case BAM_DMUX_UL_DISCONNECTED: + break; + } +} + +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_notify); + + if (r < 0) { + DBG0("%s: ch=%d failed with rc %d\n", + __func__, p->ch_id, r); + 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) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + int awake; + int ret = 0; + + 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); + awake = msm_bam_dmux_ul_power_vote(); + if (!awake) { + /* send SKB once wakeup is complete */ + netif_stop_queue(dev); + p->waiting_for_ul_skb = skb; + spin_unlock_irqrestore(&p->lock, flags); + ret = 0; + goto exit; + } + spin_unlock_irqrestore(&p->lock, flags); + + ret = _rmnet_xmit(skb, dev); + if (ret == -EPERM) { + ret = NETDEV_TX_BUSY; + goto exit; + } + + /* + * detected SSR a bit early. shut some things down now, and leave + * the rest to the main ssr handling code when that happens later + */ + if (ret == -EFAULT) { + netif_carrier_off(dev); + dev_kfree_skb_any(skb); + ret = 0; + goto exit; + } + + if (ret == -EAGAIN) { + /* + * This should not happen + * EAGAIN means we attempted to overflow the high watermark + * Clearly the queue is not stopped like it should be, so + * stop it and return BUSY to the TCP/IP framework. It will + * retry this packet with the queue is restarted which happens + * in the write_done callback when the low watermark is hit. + */ + netif_stop_queue(dev); + ret = NETDEV_TX_BUSY; + goto exit; + } + + spin_lock_irqsave(&p->tx_queue_lock, flags); + if (msm_bam_dmux_is_ch_full(p->ch_id)) { + netif_stop_queue(dev); + DBG0("%s: High WM hit, stopping queue=%p\n", __func__, skb); + } + spin_unlock_irqrestore(&p->tx_queue_lock, flags); + +exit: + msm_bam_dmux_ul_power_unvote(); + return ret; +} + +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_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_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_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 struct net_device *netdevs[RMNET_DEVICE_COUNT]; +static struct platform_driver bam_rmnet_drivers[RMNET_DEVICE_COUNT]; + +static int bam_rmnet_probe(struct platform_device *pdev) +{ + int i; + char name[BAM_DMUX_CH_NAME_MAX_LEN]; + struct rmnet_private *p; + + for (i = 0; i < RMNET_DEVICE_COUNT; ++i) { + scnprintf(name, BAM_DMUX_CH_NAME_MAX_LEN, "bam_dmux_ch_%d", i); + if (!strncmp(pdev->name, name, BAM_DMUX_CH_NAME_MAX_LEN)) + break; + } + + p = netdev_priv(netdevs[i]); + if (p->in_reset) { + p->in_reset = 0; + msm_bam_dmux_open(p->ch_id, netdevs[i], bam_notify); + netif_carrier_on(netdevs[i]); + netif_start_queue(netdevs[i]); + } + + return 0; +} + +static int bam_rmnet_remove(struct platform_device *pdev) +{ + int i; + char name[BAM_DMUX_CH_NAME_MAX_LEN]; + struct rmnet_private *p; + + for (i = 0; i < RMNET_DEVICE_COUNT; ++i) { + scnprintf(name, BAM_DMUX_CH_NAME_MAX_LEN, "bam_dmux_ch_%d", i); + if (!strncmp(pdev->name, name, BAM_DMUX_CH_NAME_MAX_LEN)) + break; + } + + p = netdev_priv(netdevs[i]); + p->in_reset = 1; + if (p->waiting_for_ul_skb != NULL) { + dev_kfree_skb_any(p->waiting_for_ul_skb); + p->waiting_for_ul_skb = NULL; + } + msm_bam_dmux_close(p->ch_id); + netif_carrier_off(netdevs[i]); + netif_stop_queue(netdevs[i]); + return 0; +} + +static int __init rmnet_init(void) +{ + int ret; + struct device *d; + struct net_device *dev; + struct rmnet_private *p; + unsigned n; + char *tempname; + + 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) { + pr_err("%s: no memory for netdev %d\n", __func__, n); + return -ENOMEM; + } + + netdevs[n] = dev; + d = &(dev->dev); + p = netdev_priv(dev); + /* Initial config uses Ethernet */ + p->operation_mode = RMNET_MODE_LLP_ETH; + p->ch_id = n; + p->waiting_for_ul_skb = NULL; + p->in_reset = 0; + spin_lock_init(&p->lock); + spin_lock_init(&p->tx_queue_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) { + pr_err("%s: unable to register netdev" + " %d rc=%d\n", __func__, n, 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 + bam_rmnet_drivers[n].probe = bam_rmnet_probe; + bam_rmnet_drivers[n].remove = bam_rmnet_remove; + tempname = kmalloc(BAM_DMUX_CH_NAME_MAX_LEN, GFP_KERNEL); + if (tempname == NULL) + return -ENOMEM; + scnprintf(tempname, BAM_DMUX_CH_NAME_MAX_LEN, "bam_dmux_ch_%d", + n); + bam_rmnet_drivers[n].driver.name = tempname; + bam_rmnet_drivers[n].driver.owner = THIS_MODULE; + ret = platform_driver_register(&bam_rmnet_drivers[n]); + if (ret) { + pr_err("%s: registration failed n=%d rc=%d\n", + __func__, n, ret); + return ret; + } + } + return 0; +} + +module_init(rmnet_init); +MODULE_DESCRIPTION("MSM RMNET BAM TRANSPORT"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/net/ethernet/msm/msm_rmnet_sdio.c b/drivers/net/ethernet/msm/msm_rmnet_sdio.c new file mode 100644 index 0000000000000000000000000000000000000000..14fb612788c81ba68824c193a953e48bc8121d3f --- /dev/null +++ b/drivers/net/ethernet/msm/msm_rmnet_sdio.c @@ -0,0 +1,712 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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; + spinlock_t tx_queue_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); + unsigned long flags; + + if (skb) + dev_kfree_skb_any(skb); + + if (!p->in_reset) { + DBG1("%s: write complete skb=%p\n", __func__, skb); + + spin_lock_irqsave(&p->tx_queue_lock, flags); + 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); + } + spin_unlock_irqrestore(&p->tx_queue_lock, flags); + } 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); + unsigned long flags; + + 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); + + spin_lock_irqsave(&p->tx_queue_lock, flags); + 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); + } + spin_unlock_irqrestore(&p->tx_queue_lock, flags); + + 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_rx_mode = 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_rx_mode = 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); + spin_lock_init(&p->tx_queue_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/ethernet/msm/msm_rmnet_smux.c b/drivers/net/ethernet/msm/msm_rmnet_smux.c new file mode 100644 index 0000000000000000000000000000000000000000..fbb3489010dc80a85011e7b817cef482a6bd2424 --- /dev/null +++ b/drivers/net/ethernet/msm/msm_rmnet_smux.c @@ -0,0 +1,938 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 SMUX Module. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + + +/* Debug message support */ +static int msm_rmnet_smux_debug_mask; +module_param_named(debug_enable, msm_rmnet_smux_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_smux_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_SMUX_DEVICE_COUNT (1) + +/* allow larger frames */ +#define RMNET_DATA_LEN 2000 + +#define DEVICE_ID_INVALID -1 + +#define DEVICE_INACTIVE 0x00 +#define DEVICE_ACTIVE 0x01 + +#define HEADROOM_FOR_SMUX 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 + spinlock_t lock; + struct tasklet_struct tsklt; + /* IOCTL specified mode (protocol, QoS header) */ + u32 operation_mode; + uint8_t device_state; + uint8_t in_reset; +}; + +static struct net_device *netdevs[RMNET_SMUX_DEVICE_COUNT]; + +#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 snprintf(buf, PAGE_SIZE, "%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 /* CONFIG_HAS_EARLYSUSPEND */ + +/* 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 snprintf(buf, PAGE_SIZE, "%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 snprintf(buf, PAGE_SIZE, "%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 /* CONFIG_HAS_EARLYSUSPEND */ + 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 snprintf(buf, PAGE_SIZE, "%lu\n", timeout_us); +} + +DEVICE_ATTR(timeout, 0664, timeout_show, timeout_store); +#endif /* CONFIG_MSM_RMNET_DEBUG */ + +/* 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; +} + +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 void smux_read_done(void *rcv_dev, const void *meta_data) +{ + struct rmnet_private *p; + struct net_device *dev = rcv_dev; + u32 opmode; + unsigned long flags; + struct sk_buff *skb = NULL; + const struct smux_meta_read *read_meta_info = meta_data; + + if (!dev || !read_meta_info) { + DBG1("%s:invalid read_done callback recieved", __func__); + return; + } + + p = netdev_priv(dev); + + skb = (struct sk_buff *) read_meta_info->pkt_priv; + + if (!skb || skb->dev != dev) { + DBG1("%s: ERR:skb pointer NULL in READ_DONE CALLBACK", + __func__); + return; + } + + /* 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; + } + DBG2("[%s] Rx packet #%lu len=%d\n", + dev->name, p->stats.rx_packets, + skb->len); + /* Deliver to network stack */ + netif_rx(skb); + + return; +} + +static void smux_write_done(void *dev, const void *meta_data) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 opmode; + struct sk_buff *skb = NULL; + const struct smux_meta_write *write_meta_info = meta_data; + unsigned long flags; + + if (!dev || !write_meta_info) { + DBG1("%s: ERR:invalid WRITE_DONE callback recieved", __func__); + return; + } + + skb = (struct sk_buff *) write_meta_info->pkt_priv; + + if (!skb) { + DBG1("%s: ERR:skb pointer NULL in WRITE_DONE" + " CALLBACK", __func__); + return; + } + + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + DBG1("%s: write complete\n", __func__); + 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", + ((struct net_device *)(dev))->name, p->stats.tx_packets, + skb->len, skb->mark); + dev_kfree_skb_any(skb); + if (netif_queue_stopped(dev) && + msm_smux_is_ch_low(p->ch_id)) { + DBG0("%s: Low WM hit, waking queue=%p\n", + __func__, skb); + netif_wake_queue(dev); + } +} + +void rmnet_smux_notify(void *priv, int event_type, const void *metadata) +{ + struct rmnet_private *p; + struct net_device *dev; + unsigned long flags; + struct sk_buff *skb = NULL; + u32 opmode; + const struct smux_meta_disconnected *ssr_info; + const struct smux_meta_read *read_meta_info; + const struct smux_meta_write *write_meta_info = metadata; + + + if (!priv) + DBG0("%s: priv(cookie) NULL, ignoring notification:" + " %d\n", __func__, event_type); + + switch (event_type) { + case SMUX_CONNECTED: + p = netdev_priv(priv); + dev = priv; + + DBG0("[%s] SMUX_CONNECTED event dev:%s\n", __func__, dev->name); + + netif_carrier_on(dev); + netif_start_queue(dev); + + spin_lock_irqsave(&p->lock, flags); + p->device_state = DEVICE_ACTIVE; + spin_unlock_irqrestore(&p->lock, flags); + break; + + case SMUX_DISCONNECTED: + p = netdev_priv(priv); + dev = priv; + ssr_info = metadata; + + DBG0("[%s] SMUX_DISCONNECTED event dev:%s\n", + __func__, dev->name); + + if (ssr_info && ssr_info->is_ssr == 1) + DBG0("SSR detected on :%s\n", dev->name); + + netif_carrier_off(dev); + netif_stop_queue(dev); + + spin_lock_irqsave(&p->lock, flags); + p->device_state = DEVICE_INACTIVE; + spin_unlock_irqrestore(&p->lock, flags); + break; + + case SMUX_READ_DONE: + smux_read_done(priv, metadata); + break; + + case SMUX_READ_FAIL: + p = netdev_priv(priv); + dev = priv; + read_meta_info = metadata; + + if (!dev || !read_meta_info) { + DBG1("%s: ERR:invalid read failed callback" + " recieved", __func__); + return; + } + + skb = (struct sk_buff *) read_meta_info->pkt_priv; + + if (!skb) { + DBG1("%s: ERR:skb pointer NULL in read fail" + " CALLBACK", __func__); + return; + } + + DBG0("%s: read failed\n", __func__); + + opmode = p->operation_mode; + + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) + p->stats.rx_dropped++; + + dev_kfree_skb_any(skb); + break; + + case SMUX_WRITE_DONE: + smux_write_done(priv, metadata); + break; + + case SMUX_WRITE_FAIL: + p = netdev_priv(priv); + dev = priv; + write_meta_info = metadata; + + if (!dev || !write_meta_info) { + DBG1("%s: ERR:invalid WRITE_DONE" + "callback recieved", __func__); + return; + } + + skb = (struct sk_buff *) write_meta_info->pkt_priv; + + if (!skb) { + DBG1("%s: ERR:skb pointer NULL in" + " WRITE_DONE CALLBACK", __func__); + return; + } + + DBG0("%s: write failed\n", __func__); + + opmode = p->operation_mode; + + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) { + p->stats.tx_dropped++; + } + + dev_kfree_skb_any(skb); + break; + + case SMUX_LOW_WM_HIT: + dev = priv; + DBG0("[%s] Low WM hit dev:%s\n", __func__, dev->name); + netif_start_queue(dev); + break; + + case SMUX_HIGH_WM_HIT: + dev = priv; + DBG0("[%s] Low WM hit dev:%s\n", __func__, dev->name); + netif_stop_queue(dev); + break; + + default: + dev = priv; + DBG0("[%s] Invalid event:%d received on" + " dev: %s\n", __func__, event_type, dev->name); + break; + } + + return; +} + +int get_rx_buffers(void *priv, void **pkt_priv, void **buffer, int size) +{ + struct net_device *dev = (struct net_device *) priv; + struct sk_buff *skb = NULL; + void *ptr = NULL; + + DBG0("[%s] dev:%s\n", __func__, dev->name); + skb = __dev_alloc_skb(size, GFP_ATOMIC); + if (skb == NULL) { + DBG0("%s: unable to alloc skb\n", __func__); + return -ENOMEM; + } + + /* TODO skb_reserve(skb, NET_IP_ALIGN); for ethernet mode */ + /* Populate some params now. */ + skb->dev = dev; + ptr = skb_put(skb, size); + + skb_set_network_header(skb, 0); + + /* done with skb setup, return the buffer pointer. */ + *pkt_priv = skb; + *buffer = ptr; + + return 0; +} + +static int __rmnet_open(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + + DBG0("[%s] __rmnet_open()\n", dev->name); + + if (p->device_state == DEVICE_ACTIVE) { + return 0; + } else { + DBG0("[%s] Platform inactive\n", dev->name); + return -ENODEV; + } +} + +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) +{ + DBG0("[%s] rmnet_stop()\n", dev->name); + + 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); + int smux_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; + + /* if write() succeeds, skb access is unsafe in this process */ + smux_ret = msm_smux_write(p->ch_id, skb, skb->data, skb->len); + + if (smux_ret != 0 && smux_ret != -EAGAIN) { + pr_err("[%s] %s: write returned error %d", + dev->name, __func__, smux_ret); + return -EPERM; + } + + return smux_ret; +} + +static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int ret = 0; + + if (netif_queue_stopped(dev) || (p->device_state == DEVICE_INACTIVE)) { + pr_err("[%s]fatal: rmnet_xmit called when " + "netif_queue is stopped", dev->name); + return 0; + } + + ret = _rmnet_xmit(skb, dev); + + if (ret == -EPERM) { + /* Do not stop the queue here. + * It will lead to ir-recoverable state. + */ + ret = NETDEV_TX_BUSY; + goto exit; + } + + if (msm_smux_is_ch_full(p->ch_id) || (ret == -EAGAIN)) { + /* + * EAGAIN means we attempted to overflow the high watermark + * Clearly the queue is not stopped like it should be, so + * stop it and return BUSY to the TCP/IP framework. It will + * retry this packet with the queue is restarted which happens + * low watermark is called. + */ + netif_stop_queue(dev); + ret = NETDEV_TX_BUSY; + goto exit; + } +exit: + return ret; +} + +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_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_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_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_SMUX + + 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 */ + 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_SMUX + HEADROOM_FOR_QOS ; + dev->needed_tailroom = TAILROOM; + random_ether_addr(dev->dev_addr); + + dev->watchdog_timeo = 1000; /* 10 seconds? */ +} + + +static int smux_rmnet_probe(struct platform_device *pdev) +{ + int i; + int r; + struct rmnet_private *p; + + for (i = 0; i < RMNET_SMUX_DEVICE_COUNT; i++) { + p = netdev_priv(netdevs[i]); + + if ((p != NULL) && (p->device_state == DEVICE_INACTIVE)) { + r = msm_smux_open(p->ch_id, + netdevs[i], + rmnet_smux_notify, + get_rx_buffers); + + if (r < 0) { + DBG0("%s: ch=%d open failed with rc %d\n", + __func__, p->ch_id, r); + } + } + } + return 0; +} + +static int smux_rmnet_remove(struct platform_device *pdev) +{ + int i; + int r; + struct rmnet_private *p; + + for (i = 0; i < RMNET_SMUX_DEVICE_COUNT; i++) { + p = netdev_priv(netdevs[i]); + + if ((p != NULL) && (p->device_state == DEVICE_ACTIVE)) { + r = msm_smux_close(p->ch_id); + + if (r < 0) { + DBG0("%s: ch=%d close failed with rc %d\n", + __func__, p->ch_id, r); + continue; + } + netif_carrier_off(netdevs[i]); + netif_stop_queue(netdevs[i]); + } + } + return 0; +} + + +static struct platform_driver smux_rmnet_driver = { + .probe = smux_rmnet_probe, + .remove = smux_rmnet_remove, + .driver = { + .name = "SMUX_RMNET", + .owner = THIS_MODULE, + }, +}; + + +static int __init rmnet_init(void) +{ + int ret; + struct device *d; + struct net_device *dev; + struct rmnet_private *p; + unsigned n; + +#ifdef CONFIG_MSM_RMNET_DEBUG + timeout_us = 0; +#ifdef CONFIG_HAS_EARLYSUSPEND + timeout_suspend_us = 0; +#endif /* CONFIG_HAS_EARLYSUSPEND */ +#endif /* CONFIG_MSM_RMNET_DEBUG */ + + for (n = 0; n < RMNET_SMUX_DEVICE_COUNT; n++) { + dev = alloc_netdev(sizeof(struct rmnet_private), + "rmnet_smux%d", rmnet_setup); + + if (!dev) { + pr_err("%s: no memory for netdev %d\n", __func__, n); + return -ENOMEM; + } + + netdevs[n] = dev; + d = &(dev->dev); + p = netdev_priv(dev); + /* Initial config uses Ethernet */ + p->operation_mode = RMNET_MODE_LLP_ETH; + p->ch_id = n; + p->in_reset = 0; + 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) { + pr_err("%s: unable to register netdev" + " %d rc=%d\n", __func__, n, 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 /* CONFIG_HAS_EARLYSUSPEND */ +#endif /* CONFIG_MSM_RMNET_DEBUG */ + + } + + ret = platform_driver_register(&smux_rmnet_driver); + if (ret) { + pr_err("%s: registration failed n=%d rc=%d\n", + __func__, n, ret); + return ret; + } + return 0; +} + +module_init(rmnet_init); +MODULE_DESCRIPTION("MSM RMNET SMUX TRANSPORT"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/msm/qfec.c b/drivers/net/ethernet/msm/qfec.c new file mode 100644 index 0000000000000000000000000000000000000000..112e16afb57df5a45dc1f21cb3e8b1e087eaa18c --- /dev/null +++ b/drivers/net/ethernet/msm/qfec.c @@ -0,0 +1,2792 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#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 "Nov 29 2011" + +#define ETH_BUF_SIZE 0x600 +#define MAX_N_BD 50 +#define MAC_ADDR_SIZE 6 + +#define RX_TX_BD_RATIO 8 +#define TX_BD_NUM 256 +#define RX_BD_NUM 256 +#define TX_BD_TI_RATIO 4 +#define MAX_MDIO_REG 32 + +#define H_DPLX 0 +#define F_DPLX 1 +/* + * 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 +#define QFEC_MII_EXP_MASK (EXPANSION_LCWP | EXPANSION_ENABLENPAGE \ + | EXPANSION_NPCAPABLE) + +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) +{ + if (++p_ring->head == p_ring->len) + p_ring->head = 0; + p_ring->n_free--; +}; + +static inline void qfec_ring_tail_adv(struct ring *p_ring) +{ + if (++p_ring->tail == p_ring->len) + p_ring->tail = 0; + 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", 1 }, + { 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); +} + +/* + * set up the RX filter + */ +static void qfec_set_rx_mode(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + uint32_t filter_conf; + int index; + + /* Clear address filter entries */ + for (index = 1; index < MAC_ADR_MAX; ++index) { + qfec_reg_write(priv, MAC_ADR_HIGH_REG_N(index), 0); + qfec_reg_write(priv, MAC_ADR_LOW_REG_N(index), 0); + } + + if (dev->flags & IFF_PROMISC) { + /* Receive all frames */ + filter_conf = MAC_FR_FILTER_RA; + } else if ((dev->flags & IFF_MULTICAST) == 0) { + /* Unicast filtering only */ + filter_conf = MAC_FR_FILTER_HPF; + } else if ((netdev_mc_count(dev) > MAC_ADR_MAX - 1) || + (dev->flags & IFF_ALLMULTI)) { + /* Unicast filtering is enabled, Pass all multicast frames */ + filter_conf = MAC_FR_FILTER_HPF | MAC_FR_FILTER_PM; + } else { + struct netdev_hw_addr *ha; + + /* Both unicast and multicast filtering are enabled */ + filter_conf = MAC_FR_FILTER_HPF; + + index = 1; + + netdev_for_each_mc_addr(ha, dev) { + uint32_t high, low; + + high = (1 << 31) | (ha->addr[5] << 8) | (ha->addr[4]); + low = (ha->addr[3] << 24) | (ha->addr[2] << 16) | + (ha->addr[1] << 8) | (ha->addr[0]); + + qfec_reg_write(priv, MAC_ADR_HIGH_REG_N(index), high); + qfec_reg_write(priv, MAC_ADR_LOW_REG_N(index), low); + + index++; + } + } + + qfec_reg_write(priv, MAC_FR_FILTER_REG, filter_conf); +} + +/* + * 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); + + if (priv->mii.supports_gmii) { + /* Clear RGMII */ + qfec_reg_read(priv, SG_RG_SMII_STATUS_REG); + /* Disable RGMII int */ + qfec_reg_write(priv, INTRP_MASK_REG, 1); + } + + 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 : H_DPLX)); + + 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 = { + /* 19.2 MHz tcxo */ + 0, 0, ETH_NS_PRE_DIV(0) + | EMAC_PTP_NS_ROOT_EN + | EMAC_PTP_NS_CLK_EN + | CLK_SRC_TCXO +}; + +#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); +} + +/* + * MDIO show + */ +static int qfec_mdio_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + int n; + int l = 0; + int count = PAGE_SIZE; + + QFEC_LOG(QFEC_LOG_DBG2, "%s:\n", __func__); + + for (n = 0; n < MAX_MDIO_REG; n++) { + if (!(n % 8)) + l += snprintf(&buf[l], count - l, "\n %02x: ", n); + + l += snprintf(&buf[l], count - l, " %04x", + qfec_mdio_read(to_net_dev(dev), priv->phy_id, n)); + } + l += snprintf(&buf[l], count - l, "\n"); + + return l; +} + +/* + * 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 advert = qfec_mdio_read(dev, priv->phy_id, MII_ADVERTISE); + uint32_t lpa = qfec_mdio_read(dev, priv->phy_id, MII_LPA); + uint32_t mastCtrl = qfec_mdio_read(dev, priv->phy_id, MII_CTRL1000); + uint32_t mastStat = qfec_mdio_read(dev, priv->phy_id, MII_STAT1000); + uint32_t anExp = qfec_mdio_read(dev, priv->phy_id, MII_EXPANSION); + uint32_t status = advert & lpa; + uint32_t flow; + + if (priv->mii.supports_gmii) { + if (((anExp & QFEC_MII_EXP_MASK) == QFEC_MII_EXP_MASK) + && (mastCtrl & ADVERTISE_1000FULL) + && (mastStat & LPA_1000FULL)) { + *spd = SPD_1000; + *dplx = F_DPLX; + goto pause; + } + + else if (((anExp & QFEC_MII_EXP_MASK) == QFEC_MII_EXP_MASK) + && (mastCtrl & ADVERTISE_1000HALF) + && (mastStat & LPA_1000HALF)) { + *spd = SPD_1000; + *dplx = H_DPLX; + goto pause; + } + } + + /* mii speeds */ + if (status & QFEC_100) { + *spd = SPD_100; + *dplx = status & QFEC_100_FD ? F_DPLX : H_DPLX; + } + + else if (status & QFEC_10) { + *spd = SPD_10; + *dplx = status & QFEC_10_FD ? F_DPLX : H_DPLX; + } + + /* check pause */ +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 = H_DPLX; + unsigned int dplx = F_DPLX; + + 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"); +} + +/* + * process timestamp values + * The pbuf and next fields of the buffer descriptors are overwritten + * with the timestamp high and low register values. + * + * The low register is incremented by the value in the subsec_increment + * register and overflows at 0x8000 0000 causing the high register to + * increment. + * + * The subsec_increment register is recommended to be set to the number + * of nanosec corresponding to each clock tic, scaled by 2^31 / 10^9 + * (e.g. 40 * 2^32 / 10^9 = 85.9, or 86 for 25 MHz). However, the + * rounding error in this case will result in a 1 sec error / ~14 mins. + * + * An alternate approach is used. The subsec_increment is set to 1, + * and the concatenation of the 2 timestamp registers used to count + * clock tics. The 63-bit result is manipulated to determine the number + * of sec and ns. + */ + +/* + * convert 19.2 MHz clock tics into sec/ns + */ +#define TS_LOW_REG_BITS 31 + +#define MILLION 1000000UL +#define BILLION 1000000000UL + +#define F_CLK 19200000UL +#define F_CLK_PRE_SC 24 +#define F_CLK_INV_Q 56 +#define F_CLK_INV (((unsigned long long)1 << F_CLK_INV_Q) / F_CLK) +#define F_CLK_TO_NS_Q 25 +#define F_CLK_TO_NS \ + (((((unsigned long long)1<> F_CLK_PRE_SC; + t *= F_CLK_INV; + t >>= F_CLK_INV_Q - F_CLK_PRE_SC; + *sec = t; + + t = *cnt - (t * F_CLK); + subsec = t; + + if (subsec >= F_CLK) { + subsec -= F_CLK; + *sec += 1; + } + + subsec *= F_CLK_TO_NS; + subsec >>= F_CLK_TO_NS_Q; + *ns = subsec; +} + +/* + * read ethernet timestamp registers, pass up raw register values + * and values converted to sec/ns + */ +static void qfec_read_timestamp(struct buf_desc *p_bd, + struct skb_shared_hwtstamps *ts) +{ + unsigned long long cnt; + unsigned int sec; + unsigned int subsec; + + cnt = (unsigned long)qfec_bd_next_get(p_bd); + cnt <<= TS_LOW_REG_BITS; + cnt |= (unsigned long)qfec_bd_pbuf_get(p_bd); + + /* report raw counts as concatenated 63 bits */ + sec = cnt >> 32; + subsec = cnt & 0xffffffff; + + ts->hwtstamp = ktime_set(sec, subsec); + + /* translate counts to sec and ns */ + qfec_get_sec(&cnt, &sec, &subsec); + + ts->syststamp = ktime_set(sec, subsec); +} + +/* + * capture the current system time in the timestamp registers + */ +static int qfec_cmd(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + struct timeval tv; + + if (!strncmp(buf, "setTs", 5)) { + unsigned long long cnt; + uint32_t ts_hi; + uint32_t ts_lo; + unsigned long long subsec; + + do_gettimeofday(&tv); + + /* convert raw sec/usec to ns */ + subsec = tv.tv_usec; + subsec *= US_TO_F_CLK; + subsec >>= US_TO_F_CLK_Q; + + cnt = tv.tv_sec; + cnt *= F_CLK; + cnt += subsec; + + ts_hi = cnt >> 31; + ts_lo = cnt & 0x7FFFFFFF; + + qfec_reg_write(priv, TS_HI_UPDT_REG, ts_hi); + qfec_reg_write(priv, TS_LO_UPDT_REG, ts_lo); + + qfec_reg_write(priv, TS_CTL_REG, + qfec_reg_read(priv, TS_CTL_REG) | TS_CTL_TSINIT); + } else + pr_err("%s: unknown cmd, %s.\n", __func__, buf); + + return strnlen(buf, count); +} + +/* + * display ethernet tstamp and system time + */ +static int qfec_tstamp_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; + struct timeval tv; + unsigned long long cnt; + uint32_t sec; + uint32_t ns; + uint32_t ts_hi; + uint32_t ts_lo; + + /* insure that ts_hi didn't increment during read */ + do { + ts_hi = qfec_reg_read(priv, TS_HIGH_REG); + ts_lo = qfec_reg_read(priv, TS_LOW_REG); + } while (ts_hi != qfec_reg_read(priv, TS_HIGH_REG)); + + cnt = ts_hi; + cnt <<= TS_LOW_REG_BITS; + cnt |= ts_lo; + + do_gettimeofday(&tv); + + ts_hi = cnt >> 32; + ts_lo = cnt & 0xffffffff; + + qfec_get_sec(&cnt, &sec, &ns); + + l = snprintf(buf, count, + "%12u.%09u sec 0x%08x 0x%08x tstamp %12u.%06u time-of-day\n", + sec, ns, ts_hi, ts_lo, (int)tv.tv_sec, (int)tv.tv_usec); + + return l; +} + +/* + * 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)) + 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); + } + + qfec_reg_write(priv, STATUS_REG, STATUS_REG_RI); +} + +/* + * 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)) { + status &= ~(STATUS_REG_GPI | STATUS_REG_GMI | STATUS_REG_GLI); + CNTR_INC(priv, gmac_isr); + int_bits |= STATUS_REG_GPI | STATUS_REG_GMI | STATUS_REG_GLI; + qfec_reg_read(priv, SG_RG_SMII_STATUS_REG); + } + + /* 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 */ + priv->mii.supports_gmii = mii_check_gmii_support(&priv->mii); + if (priv->mii.supports_gmii) { + QFEC_LOG_ERR("%s: RGMII\n", __func__); + qfec_intf_sel(priv, INTFC_RGMII); + } else { + QFEC_LOG_ERR("%s: MII\n", __func__); + 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); + qfec_set_rx_mode(dev); + + /* 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); + + /* driver supports AN capable PHY only */ + qfec_mdio_write(dev, priv->phy_id, MII_BMCR, BMCR_RESET); + res = (BMCR_ANENABLE|BMCR_ANRESTART); + qfec_mdio_write(dev, priv->phy_id, MII_BMCR, res); + + /* 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); + + /* If there is no room, on the ring try to free some up */ + if (qfec_ring_room(p_ring) == 0) + qfec_tx_replenish(dev); + + /* 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"); + + priv->stats.multicast = qfec_reg_read(priv, NUM_MULTCST_FRM_RCVD_G); + + 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_set_rx_mode = qfec_set_rx_mode, + + .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(cmd, 0222, NULL, qfec_cmd); +static DEVICE_ATTR(cntrs, 0444, qfec_cntrs_show, NULL); +static DEVICE_ATTR(reg, 0444, qfec_reg_show, NULL); +static DEVICE_ATTR(mdio, 0444, qfec_mdio_show, NULL); +static DEVICE_ATTR(stats, 0444, qfec_stats_show, NULL); +static DEVICE_ATTR(tstamp, 0444, qfec_tstamp_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_cmd) || + device_create_file(&(dev->dev), &dev_attr_cntrs) || + device_create_file(&(dev->dev), &dev_attr_mdio) || + device_create_file(&(dev->dev), &dev_attr_reg) || + device_create_file(&(dev->dev), &dev_attr_stats) || + device_create_file(&(dev->dev), &dev_attr_tstamp)) + 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/ethernet/msm/qfec.h b/drivers/net/ethernet/msm/qfec.h new file mode 100644 index 0000000000000000000000000000000000000000..310406a9730386f212354dd70c69cd8c5ded9666 --- /dev/null +++ b/drivers/net/ethernet/msm/qfec.h @@ -0,0 +1,800 @@ +/* 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 MAC_ADR_HIGH_REG_N(n) (((n) < 16) ? \ + (MAC_ADR_0_HIGH_REG + (n) * 8) : \ + (MAC_ADR16_HIGH_REG + ((n) - 16) * 8)) +# define MAC_ADR_LOW_REG_N(n) (((n) < 16) ? \ + (MAC_ADR_0_LOW_REG + (n) * 8) : \ + (MAC_ADR16_LOW_REG + ((n) - 16) * 8)) + +# 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 +# define NUM_MULTCST_FRM_RCVD_G 0x0190 + +/* 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/ethernet/smsc/smc91x.h b/drivers/net/ethernet/smsc/smc91x.h index 5f53fbbf67be84acf2c1fe1e4f0e8541bce86617..1e1617ef1607a036acabbf7d62d6211d1d929a25 100644 --- a/drivers/net/ethernet/smsc/smc91x.h +++ b/drivers/net/ethernet/smsc/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/ethernet/smsc/smsc911x.c b/drivers/net/ethernet/smsc/smsc911x.c index cd3defb11ffb3e09822b5f20555796ddd8960fd2..b0d4c4cd8e06d5994ed5b5419e56b7a3683d70fb 100644 --- a/drivers/net/ethernet/smsc/smsc911x.c +++ b/drivers/net/ethernet/smsc/smsc911x.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -954,7 +955,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 { @@ -962,7 +963,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 @@ -973,7 +974,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); } } @@ -1485,7 +1486,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) */ @@ -1931,9 +1932,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); } @@ -2241,6 +2242,12 @@ 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); @@ -2436,9 +2443,10 @@ 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, - irq_flags | IRQF_SHARED, dev->name, dev); - if (retval) { + retval = request_any_context_irq(dev->irq, smsc911x_irqhandler, + irq_flags | IRQF_SHARED, dev->name, + dev); + if (retval < 0) { SMSC_WARN(pdata, probe, "Unable to claim requested irq: %d", dev->irq); goto out_disable_resources; @@ -2528,6 +2536,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; } @@ -2537,6 +2549,10 @@ 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/ethernet/smsc/smsc911x.h b/drivers/net/ethernet/smsc/smsc911x.h index 9ad5e5d39a03823c4389ee5842d0ecdd0b6db5fc..43e53980de4e3c0887989bdd74aff48aa5cd96c9 100644 --- a/drivers/net/ethernet/smsc/smsc911x.h +++ b/drivers/net/ethernet/smsc/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/pppolac.c b/drivers/net/pppolac.c new file mode 100644 index 0000000000000000000000000000000000000000..c94b8507d92bb4462798bc9ffed5e23ccadeb526 --- /dev/null +++ b/drivers/net/pppolac.c @@ -0,0 +1,449 @@ +/* drivers/net/pppolac.c + * + * Driver for PPP on L2TP Access Concentrator / PPPoLAC Socket (RFC 2661) + * + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* This driver handles L2TP data packets between a UDP socket and a PPP channel. + * The socket must keep connected, and only one session per socket is permitted. + * Sequencing of outgoing packets is controlled by LNS. Incoming packets with + * sequences are reordered within a sliding window of one second. Currently + * reordering only happens when a packet is received. It is done for simplicity + * since no additional locks or threads are required. This driver only works on + * IPv4 due to the lack of UDP encapsulation support in IPv6. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define L2TP_CONTROL_BIT 0x80 +#define L2TP_LENGTH_BIT 0x40 +#define L2TP_SEQUENCE_BIT 0x08 +#define L2TP_OFFSET_BIT 0x02 +#define L2TP_VERSION 0x02 +#define L2TP_VERSION_MASK 0x0F + +#define PPP_ADDR 0xFF +#define PPP_CTRL 0x03 + +union unaligned { + __u32 u32; +} __attribute__((packed)); + +static inline union unaligned *unaligned(void *ptr) +{ + return (union unaligned *)ptr; +} + +struct meta { + __u32 sequence; + __u32 timestamp; +}; + +static inline struct meta *skb_meta(struct sk_buff *skb) +{ + return (struct meta *)skb->cb; +} + +/******************************************************************************/ + +static int pppolac_recv_core(struct sock *sk_udp, struct sk_buff *skb) +{ + struct sock *sk = (struct sock *)sk_udp->sk_user_data; + struct pppolac_opt *opt = &pppox_sk(sk)->proto.lac; + struct meta *meta = skb_meta(skb); + __u32 now = jiffies; + __u8 bits; + __u8 *ptr; + + /* Drop the packet if L2TP header is missing. */ + if (skb->len < sizeof(struct udphdr) + 6) + goto drop; + + /* Put it back if it is a control packet. */ + if (skb->data[sizeof(struct udphdr)] & L2TP_CONTROL_BIT) + return opt->backlog_rcv(sk_udp, skb); + + /* Skip UDP header. */ + skb_pull(skb, sizeof(struct udphdr)); + + /* Check the version. */ + if ((skb->data[1] & L2TP_VERSION_MASK) != L2TP_VERSION) + goto drop; + bits = skb->data[0]; + ptr = &skb->data[2]; + + /* Check the length if it is present. */ + if (bits & L2TP_LENGTH_BIT) { + if ((ptr[0] << 8 | ptr[1]) != skb->len) + goto drop; + ptr += 2; + } + + /* Skip all fields including optional ones. */ + if (!skb_pull(skb, 6 + (bits & L2TP_SEQUENCE_BIT ? 4 : 0) + + (bits & L2TP_LENGTH_BIT ? 2 : 0) + + (bits & L2TP_OFFSET_BIT ? 2 : 0))) + goto drop; + + /* Skip the offset padding if it is present. */ + if (bits & L2TP_OFFSET_BIT && + !skb_pull(skb, skb->data[-2] << 8 | skb->data[-1])) + goto drop; + + /* Check the tunnel and the session. */ + if (unaligned(ptr)->u32 != opt->local) + goto drop; + + /* Check the sequence if it is present. */ + if (bits & L2TP_SEQUENCE_BIT) { + meta->sequence = ptr[4] << 8 | ptr[5]; + if ((__s16)(meta->sequence - opt->recv_sequence) < 0) + goto drop; + } + + /* Skip PPP address and control if they are present. */ + if (skb->len >= 2 && skb->data[0] == PPP_ADDR && + skb->data[1] == PPP_CTRL) + skb_pull(skb, 2); + + /* Fix PPP protocol if it is compressed. */ + if (skb->len >= 1 && skb->data[0] & 1) + skb_push(skb, 1)[0] = 0; + + /* Drop the packet if PPP protocol is missing. */ + if (skb->len < 2) + goto drop; + + /* Perform reordering if sequencing is enabled. */ + atomic_set(&opt->sequencing, bits & L2TP_SEQUENCE_BIT); + if (bits & L2TP_SEQUENCE_BIT) { + struct sk_buff *skb1; + + /* Insert the packet into receive queue in order. */ + skb_set_owner_r(skb, sk); + skb_queue_walk(&sk->sk_receive_queue, skb1) { + struct meta *meta1 = skb_meta(skb1); + __s16 order = meta->sequence - meta1->sequence; + if (order == 0) + goto drop; + if (order < 0) { + meta->timestamp = meta1->timestamp; + skb_insert(skb1, skb, &sk->sk_receive_queue); + skb = NULL; + break; + } + } + if (skb) { + meta->timestamp = now; + skb_queue_tail(&sk->sk_receive_queue, skb); + } + + /* Remove packets from receive queue as long as + * 1. the receive buffer is full, + * 2. they are queued longer than one second, or + * 3. there are no missing packets before them. */ + skb_queue_walk_safe(&sk->sk_receive_queue, skb, skb1) { + meta = skb_meta(skb); + if (atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf && + now - meta->timestamp < HZ && + meta->sequence != opt->recv_sequence) + break; + skb_unlink(skb, &sk->sk_receive_queue); + opt->recv_sequence = (__u16)(meta->sequence + 1); + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + } + return NET_RX_SUCCESS; + } + + /* Flush receive queue if sequencing is disabled. */ + skb_queue_purge(&sk->sk_receive_queue); + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + return NET_RX_SUCCESS; +drop: + kfree_skb(skb); + return NET_RX_DROP; +} + +static int pppolac_recv(struct sock *sk_udp, struct sk_buff *skb) +{ + sock_hold(sk_udp); + sk_receive_skb(sk_udp, skb, 0); + return 0; +} + +static struct sk_buff_head delivery_queue; + +static void pppolac_xmit_core(struct work_struct *delivery_work) +{ + mm_segment_t old_fs = get_fs(); + struct sk_buff *skb; + + set_fs(KERNEL_DS); + while ((skb = skb_dequeue(&delivery_queue))) { + struct sock *sk_udp = skb->sk; + struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; + struct msghdr msg = { + .msg_iov = (struct iovec *)&iov, + .msg_iovlen = 1, + .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, + }; + sk_udp->sk_prot->sendmsg(NULL, sk_udp, &msg, skb->len); + kfree_skb(skb); + } + set_fs(old_fs); +} + +static DECLARE_WORK(delivery_work, pppolac_xmit_core); + +static int pppolac_xmit(struct ppp_channel *chan, struct sk_buff *skb) +{ + struct sock *sk_udp = (struct sock *)chan->private; + struct pppolac_opt *opt = &pppox_sk(sk_udp->sk_user_data)->proto.lac; + + /* Install PPP address and control. */ + skb_push(skb, 2); + skb->data[0] = PPP_ADDR; + skb->data[1] = PPP_CTRL; + + /* Install L2TP header. */ + if (atomic_read(&opt->sequencing)) { + skb_push(skb, 10); + skb->data[0] = L2TP_SEQUENCE_BIT; + skb->data[6] = opt->xmit_sequence >> 8; + skb->data[7] = opt->xmit_sequence; + skb->data[8] = 0; + skb->data[9] = 0; + opt->xmit_sequence++; + } else { + skb_push(skb, 6); + skb->data[0] = 0; + } + skb->data[1] = L2TP_VERSION; + unaligned(&skb->data[2])->u32 = opt->remote; + + /* Now send the packet via the delivery queue. */ + skb_set_owner_w(skb, sk_udp); + skb_queue_tail(&delivery_queue, skb); + schedule_work(&delivery_work); + return 1; +} + +/******************************************************************************/ + +static struct ppp_channel_ops pppolac_channel_ops = { + .start_xmit = pppolac_xmit, +}; + +static int pppolac_connect(struct socket *sock, struct sockaddr *useraddr, + int addrlen, int flags) +{ + struct sock *sk = sock->sk; + struct pppox_sock *po = pppox_sk(sk); + struct sockaddr_pppolac *addr = (struct sockaddr_pppolac *)useraddr; + struct socket *sock_udp = NULL; + struct sock *sk_udp; + int error; + + if (addrlen != sizeof(struct sockaddr_pppolac) || + !addr->local.tunnel || !addr->local.session || + !addr->remote.tunnel || !addr->remote.session) { + return -EINVAL; + } + + lock_sock(sk); + error = -EALREADY; + if (sk->sk_state != PPPOX_NONE) + goto out; + + sock_udp = sockfd_lookup(addr->udp_socket, &error); + if (!sock_udp) + goto out; + sk_udp = sock_udp->sk; + lock_sock(sk_udp); + + /* Remove this check when IPv6 supports UDP encapsulation. */ + error = -EAFNOSUPPORT; + if (sk_udp->sk_family != AF_INET) + goto out; + error = -EPROTONOSUPPORT; + if (sk_udp->sk_protocol != IPPROTO_UDP) + goto out; + error = -EDESTADDRREQ; + if (sk_udp->sk_state != TCP_ESTABLISHED) + goto out; + error = -EBUSY; + if (udp_sk(sk_udp)->encap_type || sk_udp->sk_user_data) + goto out; + if (!sk_udp->sk_bound_dev_if) { + struct dst_entry *dst = sk_dst_get(sk_udp); + error = -ENODEV; + if (!dst) + goto out; + sk_udp->sk_bound_dev_if = dst->dev->ifindex; + dst_release(dst); + } + + po->chan.hdrlen = 12; + po->chan.private = sk_udp; + po->chan.ops = &pppolac_channel_ops; + po->chan.mtu = PPP_MTU - 80; + po->proto.lac.local = unaligned(&addr->local)->u32; + po->proto.lac.remote = unaligned(&addr->remote)->u32; + atomic_set(&po->proto.lac.sequencing, 1); + po->proto.lac.backlog_rcv = sk_udp->sk_backlog_rcv; + + error = ppp_register_channel(&po->chan); + if (error) + goto out; + + sk->sk_state = PPPOX_CONNECTED; + udp_sk(sk_udp)->encap_type = UDP_ENCAP_L2TPINUDP; + udp_sk(sk_udp)->encap_rcv = pppolac_recv; + sk_udp->sk_backlog_rcv = pppolac_recv_core; + sk_udp->sk_user_data = sk; +out: + if (sock_udp) { + release_sock(sk_udp); + if (error) + sockfd_put(sock_udp); + } + release_sock(sk); + return error; +} + +static int pppolac_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (!sk) + return 0; + + lock_sock(sk); + if (sock_flag(sk, SOCK_DEAD)) { + release_sock(sk); + return -EBADF; + } + + if (sk->sk_state != PPPOX_NONE) { + struct sock *sk_udp = (struct sock *)pppox_sk(sk)->chan.private; + lock_sock(sk_udp); + skb_queue_purge(&sk->sk_receive_queue); + pppox_unbind_sock(sk); + udp_sk(sk_udp)->encap_type = 0; + udp_sk(sk_udp)->encap_rcv = NULL; + sk_udp->sk_backlog_rcv = pppox_sk(sk)->proto.lac.backlog_rcv; + sk_udp->sk_user_data = NULL; + release_sock(sk_udp); + sockfd_put(sk_udp->sk_socket); + } + + sock_orphan(sk); + sock->sk = NULL; + release_sock(sk); + sock_put(sk); + return 0; +} + +/******************************************************************************/ + +static struct proto pppolac_proto = { + .name = "PPPOLAC", + .owner = THIS_MODULE, + .obj_size = sizeof(struct pppox_sock), +}; + +static struct proto_ops pppolac_proto_ops = { + .family = PF_PPPOX, + .owner = THIS_MODULE, + .release = pppolac_release, + .bind = sock_no_bind, + .connect = pppolac_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, + .poll = sock_no_poll, + .ioctl = pppox_ioctl, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .sendmsg = sock_no_sendmsg, + .recvmsg = sock_no_recvmsg, + .mmap = sock_no_mmap, +}; + +static int pppolac_create(struct net *net, struct socket *sock) +{ + struct sock *sk; + + sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppolac_proto); + if (!sk) + return -ENOMEM; + + sock_init_data(sock, sk); + sock->state = SS_UNCONNECTED; + sock->ops = &pppolac_proto_ops; + sk->sk_protocol = PX_PROTO_OLAC; + sk->sk_state = PPPOX_NONE; + return 0; +} + +/******************************************************************************/ + +static struct pppox_proto pppolac_pppox_proto = { + .create = pppolac_create, + .owner = THIS_MODULE, +}; + +static int __init pppolac_init(void) +{ + int error; + + error = proto_register(&pppolac_proto, 0); + if (error) + return error; + + error = register_pppox_proto(PX_PROTO_OLAC, &pppolac_pppox_proto); + if (error) + proto_unregister(&pppolac_proto); + else + skb_queue_head_init(&delivery_queue); + return error; +} + +static void __exit pppolac_exit(void) +{ + unregister_pppox_proto(PX_PROTO_OLAC); + proto_unregister(&pppolac_proto); +} + +module_init(pppolac_init); +module_exit(pppolac_exit); + +MODULE_DESCRIPTION("PPP on L2TP Access Concentrator (PPPoLAC)"); +MODULE_AUTHOR("Chia-chi Yeh "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pppopns.c b/drivers/net/pppopns.c new file mode 100644 index 0000000000000000000000000000000000000000..fb8198447938be6babddb34f7a1c7e8ce03f20b5 --- /dev/null +++ b/drivers/net/pppopns.c @@ -0,0 +1,428 @@ +/* drivers/net/pppopns.c + * + * Driver for PPP on PPTP Network Server / PPPoPNS Socket (RFC 2637) + * + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* This driver handles PPTP data packets between a RAW socket and a PPP channel. + * The socket is created in the kernel space and connected to the same address + * of the control socket. Outgoing packets are always sent with sequences but + * without acknowledgements. Incoming packets with sequences are reordered + * within a sliding window of one second. Currently reordering only happens when + * a packet is received. It is done for simplicity since no additional locks or + * threads are required. This driver should work on both IPv4 and IPv6. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GRE_HEADER_SIZE 8 + +#define PPTP_GRE_BITS htons(0x2001) +#define PPTP_GRE_BITS_MASK htons(0xEF7F) +#define PPTP_GRE_SEQ_BIT htons(0x1000) +#define PPTP_GRE_ACK_BIT htons(0x0080) +#define PPTP_GRE_TYPE htons(0x880B) + +#define PPP_ADDR 0xFF +#define PPP_CTRL 0x03 + +struct header { + __u16 bits; + __u16 type; + __u16 length; + __u16 call; + __u32 sequence; +} __attribute__((packed)); + +struct meta { + __u32 sequence; + __u32 timestamp; +}; + +static inline struct meta *skb_meta(struct sk_buff *skb) +{ + return (struct meta *)skb->cb; +} + +/******************************************************************************/ + +static int pppopns_recv_core(struct sock *sk_raw, struct sk_buff *skb) +{ + struct sock *sk = (struct sock *)sk_raw->sk_user_data; + struct pppopns_opt *opt = &pppox_sk(sk)->proto.pns; + struct meta *meta = skb_meta(skb); + __u32 now = jiffies; + struct header *hdr; + + /* Skip transport header */ + skb_pull(skb, skb_transport_header(skb) - skb->data); + + /* Drop the packet if GRE header is missing. */ + if (skb->len < GRE_HEADER_SIZE) + goto drop; + hdr = (struct header *)skb->data; + + /* Check the header. */ + if (hdr->type != PPTP_GRE_TYPE || hdr->call != opt->local || + (hdr->bits & PPTP_GRE_BITS_MASK) != PPTP_GRE_BITS) + goto drop; + + /* Skip all fields including optional ones. */ + if (!skb_pull(skb, GRE_HEADER_SIZE + + (hdr->bits & PPTP_GRE_SEQ_BIT ? 4 : 0) + + (hdr->bits & PPTP_GRE_ACK_BIT ? 4 : 0))) + goto drop; + + /* Check the length. */ + if (skb->len != ntohs(hdr->length)) + goto drop; + + /* Check the sequence if it is present. */ + if (hdr->bits & PPTP_GRE_SEQ_BIT) { + meta->sequence = ntohl(hdr->sequence); + if ((__s32)(meta->sequence - opt->recv_sequence) < 0) + goto drop; + } + + /* Skip PPP address and control if they are present. */ + if (skb->len >= 2 && skb->data[0] == PPP_ADDR && + skb->data[1] == PPP_CTRL) + skb_pull(skb, 2); + + /* Fix PPP protocol if it is compressed. */ + if (skb->len >= 1 && skb->data[0] & 1) + skb_push(skb, 1)[0] = 0; + + /* Drop the packet if PPP protocol is missing. */ + if (skb->len < 2) + goto drop; + + /* Perform reordering if sequencing is enabled. */ + if (hdr->bits & PPTP_GRE_SEQ_BIT) { + struct sk_buff *skb1; + + /* Insert the packet into receive queue in order. */ + skb_set_owner_r(skb, sk); + skb_queue_walk(&sk->sk_receive_queue, skb1) { + struct meta *meta1 = skb_meta(skb1); + __s32 order = meta->sequence - meta1->sequence; + if (order == 0) + goto drop; + if (order < 0) { + meta->timestamp = meta1->timestamp; + skb_insert(skb1, skb, &sk->sk_receive_queue); + skb = NULL; + break; + } + } + if (skb) { + meta->timestamp = now; + skb_queue_tail(&sk->sk_receive_queue, skb); + } + + /* Remove packets from receive queue as long as + * 1. the receive buffer is full, + * 2. they are queued longer than one second, or + * 3. there are no missing packets before them. */ + skb_queue_walk_safe(&sk->sk_receive_queue, skb, skb1) { + meta = skb_meta(skb); + if (atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf && + now - meta->timestamp < HZ && + meta->sequence != opt->recv_sequence) + break; + skb_unlink(skb, &sk->sk_receive_queue); + opt->recv_sequence = meta->sequence + 1; + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + } + return NET_RX_SUCCESS; + } + + /* Flush receive queue if sequencing is disabled. */ + skb_queue_purge(&sk->sk_receive_queue); + skb_orphan(skb); + ppp_input(&pppox_sk(sk)->chan, skb); + return NET_RX_SUCCESS; +drop: + kfree_skb(skb); + return NET_RX_DROP; +} + +static void pppopns_recv(struct sock *sk_raw, int length) +{ + struct sk_buff *skb; + while ((skb = skb_dequeue(&sk_raw->sk_receive_queue))) { + sock_hold(sk_raw); + sk_receive_skb(sk_raw, skb, 0); + } +} + +static struct sk_buff_head delivery_queue; + +static void pppopns_xmit_core(struct work_struct *delivery_work) +{ + mm_segment_t old_fs = get_fs(); + struct sk_buff *skb; + + set_fs(KERNEL_DS); + while ((skb = skb_dequeue(&delivery_queue))) { + struct sock *sk_raw = skb->sk; + struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len}; + struct msghdr msg = { + .msg_iov = (struct iovec *)&iov, + .msg_iovlen = 1, + .msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT, + }; + sk_raw->sk_prot->sendmsg(NULL, sk_raw, &msg, skb->len); + kfree_skb(skb); + } + set_fs(old_fs); +} + +static DECLARE_WORK(delivery_work, pppopns_xmit_core); + +static int pppopns_xmit(struct ppp_channel *chan, struct sk_buff *skb) +{ + struct sock *sk_raw = (struct sock *)chan->private; + struct pppopns_opt *opt = &pppox_sk(sk_raw->sk_user_data)->proto.pns; + struct header *hdr; + __u16 length; + + /* Install PPP address and control. */ + skb_push(skb, 2); + skb->data[0] = PPP_ADDR; + skb->data[1] = PPP_CTRL; + length = skb->len; + + /* Install PPTP GRE header. */ + hdr = (struct header *)skb_push(skb, 12); + hdr->bits = PPTP_GRE_BITS | PPTP_GRE_SEQ_BIT; + hdr->type = PPTP_GRE_TYPE; + hdr->length = htons(length); + hdr->call = opt->remote; + hdr->sequence = htonl(opt->xmit_sequence); + opt->xmit_sequence++; + + /* Now send the packet via the delivery queue. */ + skb_set_owner_w(skb, sk_raw); + skb_queue_tail(&delivery_queue, skb); + schedule_work(&delivery_work); + return 1; +} + +/******************************************************************************/ + +static struct ppp_channel_ops pppopns_channel_ops = { + .start_xmit = pppopns_xmit, +}; + +static int pppopns_connect(struct socket *sock, struct sockaddr *useraddr, + int addrlen, int flags) +{ + struct sock *sk = sock->sk; + struct pppox_sock *po = pppox_sk(sk); + struct sockaddr_pppopns *addr = (struct sockaddr_pppopns *)useraddr; + struct sockaddr_storage ss; + struct socket *sock_tcp = NULL; + struct socket *sock_raw = NULL; + struct sock *sk_tcp; + struct sock *sk_raw; + int error; + + if (addrlen != sizeof(struct sockaddr_pppopns)) + return -EINVAL; + + lock_sock(sk); + error = -EALREADY; + if (sk->sk_state != PPPOX_NONE) + goto out; + + sock_tcp = sockfd_lookup(addr->tcp_socket, &error); + if (!sock_tcp) + goto out; + sk_tcp = sock_tcp->sk; + error = -EPROTONOSUPPORT; + if (sk_tcp->sk_protocol != IPPROTO_TCP) + goto out; + addrlen = sizeof(struct sockaddr_storage); + error = kernel_getpeername(sock_tcp, (struct sockaddr *)&ss, &addrlen); + if (error) + goto out; + if (!sk_tcp->sk_bound_dev_if) { + struct dst_entry *dst = sk_dst_get(sk_tcp); + error = -ENODEV; + if (!dst) + goto out; + sk_tcp->sk_bound_dev_if = dst->dev->ifindex; + dst_release(dst); + } + + error = sock_create(ss.ss_family, SOCK_RAW, IPPROTO_GRE, &sock_raw); + if (error) + goto out; + sk_raw = sock_raw->sk; + sk_raw->sk_bound_dev_if = sk_tcp->sk_bound_dev_if; + error = kernel_connect(sock_raw, (struct sockaddr *)&ss, addrlen, 0); + if (error) + goto out; + + po->chan.hdrlen = 14; + po->chan.private = sk_raw; + po->chan.ops = &pppopns_channel_ops; + po->chan.mtu = PPP_MTU - 80; + po->proto.pns.local = addr->local; + po->proto.pns.remote = addr->remote; + po->proto.pns.data_ready = sk_raw->sk_data_ready; + po->proto.pns.backlog_rcv = sk_raw->sk_backlog_rcv; + + error = ppp_register_channel(&po->chan); + if (error) + goto out; + + sk->sk_state = PPPOX_CONNECTED; + lock_sock(sk_raw); + sk_raw->sk_data_ready = pppopns_recv; + sk_raw->sk_backlog_rcv = pppopns_recv_core; + sk_raw->sk_user_data = sk; + release_sock(sk_raw); +out: + if (sock_tcp) + sockfd_put(sock_tcp); + if (error && sock_raw) + sock_release(sock_raw); + release_sock(sk); + return error; +} + +static int pppopns_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (!sk) + return 0; + + lock_sock(sk); + if (sock_flag(sk, SOCK_DEAD)) { + release_sock(sk); + return -EBADF; + } + + if (sk->sk_state != PPPOX_NONE) { + struct sock *sk_raw = (struct sock *)pppox_sk(sk)->chan.private; + lock_sock(sk_raw); + skb_queue_purge(&sk->sk_receive_queue); + pppox_unbind_sock(sk); + sk_raw->sk_data_ready = pppox_sk(sk)->proto.pns.data_ready; + sk_raw->sk_backlog_rcv = pppox_sk(sk)->proto.pns.backlog_rcv; + sk_raw->sk_user_data = NULL; + release_sock(sk_raw); + sock_release(sk_raw->sk_socket); + } + + sock_orphan(sk); + sock->sk = NULL; + release_sock(sk); + sock_put(sk); + return 0; +} + +/******************************************************************************/ + +static struct proto pppopns_proto = { + .name = "PPPOPNS", + .owner = THIS_MODULE, + .obj_size = sizeof(struct pppox_sock), +}; + +static struct proto_ops pppopns_proto_ops = { + .family = PF_PPPOX, + .owner = THIS_MODULE, + .release = pppopns_release, + .bind = sock_no_bind, + .connect = pppopns_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, + .poll = sock_no_poll, + .ioctl = pppox_ioctl, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .sendmsg = sock_no_sendmsg, + .recvmsg = sock_no_recvmsg, + .mmap = sock_no_mmap, +}; + +static int pppopns_create(struct net *net, struct socket *sock) +{ + struct sock *sk; + + sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppopns_proto); + if (!sk) + return -ENOMEM; + + sock_init_data(sock, sk); + sock->state = SS_UNCONNECTED; + sock->ops = &pppopns_proto_ops; + sk->sk_protocol = PX_PROTO_OPNS; + sk->sk_state = PPPOX_NONE; + return 0; +} + +/******************************************************************************/ + +static struct pppox_proto pppopns_pppox_proto = { + .create = pppopns_create, + .owner = THIS_MODULE, +}; + +static int __init pppopns_init(void) +{ + int error; + + error = proto_register(&pppopns_proto, 0); + if (error) + return error; + + error = register_pppox_proto(PX_PROTO_OPNS, &pppopns_pppox_proto); + if (error) + proto_unregister(&pppopns_proto); + else + skb_queue_head_init(&delivery_queue); + return error; +} + +static void __exit pppopns_exit(void) +{ + unregister_pppox_proto(PX_PROTO_OPNS); + proto_unregister(&pppopns_proto); +} + +module_init(pppopns_init); +module_exit(pppopns_exit); + +MODULE_DESCRIPTION("PPP on PPTP Network Server (PPPoPNS)"); +MODULE_AUTHOR("Chia-chi Yeh "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 833e32f8d63baabebcee2f27b773ea3846695e41..4f026b05f75b35fa2daf295aad44062a0ee97868 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -483,5 +483,14 @@ config USB_VL600 http://ubuntuforums.org/showpost.php?p=10589647&postcount=17 +config MSM_RMNET_USB + tristate "RMNET USB Driver" + depends on USB_USBNET + help + Select this if you have a Qualcomm modem device connected via USB + supporting RMNET network interface. + + To compile this driver as a module, choose M here: the module + will be called rmnet_usb. If unsure, choose N. endmenu diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index a2e2d72c52a039015bce7f5b6a5b6fb8b02796df..629630403511508edf78b5bd00f99528625c4a51 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -29,5 +29,7 @@ obj-$(CONFIG_USB_SIERRA_NET) += sierra_net.o obj-$(CONFIG_USB_NET_CX82310_ETH) += cx82310_eth.o obj-$(CONFIG_USB_NET_CDC_NCM) += cdc_ncm.o obj-$(CONFIG_USB_VL600) += lg-vl600.o +rmnet_usb-y := rmnet_usb_ctrl.o rmnet_usb_data.o +obj-$(CONFIG_MSM_RMNET_USB) += rmnet_usb.o obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o diff --git a/drivers/net/usb/rmnet_usb_ctrl.c b/drivers/net/usb/rmnet_usb_ctrl.c new file mode 100644 index 0000000000000000000000000000000000000000..23ba900c1a73b71c1a3236cc10623434d497544c --- /dev/null +++ b/drivers/net/usb/rmnet_usb_ctrl.c @@ -0,0 +1,1060 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "rmnet_usb_ctrl.h" + +#define DEVICE_NAME "hsicctl" +#define NUM_CTRL_CHANNELS 4 +#define DEFAULT_READ_URB_LENGTH 0x1000 + +/*Output control lines.*/ +#define ACM_CTRL_DTR BIT(0) +#define ACM_CTRL_RTS BIT(1) + + +/*Input control lines.*/ +#define ACM_CTRL_DSR BIT(0) +#define ACM_CTRL_CTS BIT(1) +#define ACM_CTRL_RI BIT(2) +#define ACM_CTRL_CD BIT(3) + +/* polling interval for Interrupt ep */ +#define HS_INTERVAL 7 +#define FS_LS_INTERVAL 3 + +/*echo modem_wait > /sys/class/hsicctl/hsicctlx/modem_wait*/ +static ssize_t modem_wait_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t n) +{ + unsigned int mdm_wait; + struct rmnet_ctrl_dev *dev = dev_get_drvdata(d); + + if (!dev) + return -ENODEV; + + sscanf(buf, "%u", &mdm_wait); + + dev->mdm_wait_timeout = mdm_wait; + + return n; +} + +static ssize_t modem_wait_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_ctrl_dev *dev = dev_get_drvdata(d); + + if (!dev) + return -ENODEV; + + return snprintf(buf, PAGE_SIZE, "%u\n", dev->mdm_wait_timeout); +} + +static DEVICE_ATTR(modem_wait, 0664, modem_wait_show, modem_wait_store); + +static int ctl_msg_dbg_mask; +module_param_named(dump_ctrl_msg, ctl_msg_dbg_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); + +enum { + MSM_USB_CTL_DEBUG = 1U << 0, + MSM_USB_CTL_DUMP_BUFFER = 1U << 1, +}; + +#define DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (ctl_msg_dbg_mask & MSM_USB_CTL_DUMP_BUFFER) \ + print_hex_dump(KERN_INFO, prestr, DUMP_PREFIX_NONE, \ + 16, 1, buf, cnt, false); \ +} while (0) + +#define DBG(x...) \ + do { \ + if (ctl_msg_dbg_mask & MSM_USB_CTL_DEBUG) \ + pr_info(x); \ + } while (0) + +struct rmnet_ctrl_dev *ctrl_dev[NUM_CTRL_CHANNELS]; +struct class *ctrldev_classp; +static dev_t ctrldev_num; + +struct ctrl_pkt { + size_t data_size; + void *data; +}; + +struct ctrl_pkt_list_elem { + struct list_head list; + struct ctrl_pkt cpkt; +}; + +static void resp_avail_cb(struct urb *); + +static int is_dev_connected(struct rmnet_ctrl_dev *dev) +{ + if (dev) { + mutex_lock(&dev->dev_lock); + if (!dev->intf) { + mutex_unlock(&dev->dev_lock); + return 0; + } + mutex_unlock(&dev->dev_lock); + return 1; + } + return 0; +} + +static void notification_available_cb(struct urb *urb) +{ + int status; + struct usb_cdc_notification *ctrl; + struct usb_device *udev; + struct rmnet_ctrl_dev *dev = urb->context; + + udev = interface_to_usbdev(dev->intf); + + switch (urb->status) { + case 0: + /*success*/ + break; + + /*do not resubmit*/ + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + case -EPROTO: + return; + case -EPIPE: + pr_err_ratelimited("%s: Stall on int endpoint\n", __func__); + /* TBD : halt to be cleared in work */ + return; + + /*resubmit*/ + case -EOVERFLOW: + pr_err_ratelimited("%s: Babble error happened\n", __func__); + default: + pr_debug_ratelimited("%s: Non zero urb status = %d\n", + __func__, urb->status); + goto resubmit_int_urb; + } + + ctrl = urb->transfer_buffer; + + switch (ctrl->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: + dev->resp_avail_cnt++; + usb_fill_control_urb(dev->rcvurb, udev, + usb_rcvctrlpipe(udev, 0), + (unsigned char *)dev->in_ctlreq, + dev->rcvbuf, + DEFAULT_READ_URB_LENGTH, + resp_avail_cb, dev); + + status = usb_submit_urb(dev->rcvurb, GFP_ATOMIC); + if (status) { + dev_err(dev->devicep, + "%s: Error submitting Read URB %d\n", __func__, status); + goto resubmit_int_urb; + } + + if (!dev->resp_available) { + dev->resp_available = true; + wake_up(&dev->open_wait_queue); + } + + return; + default: + dev_err(dev->devicep, + "%s:Command not implemented\n", __func__); + } + +resubmit_int_urb: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(dev->devicep, "%s: Error re-submitting Int URB %d\n", + __func__, status); + + return; +} + +static void resp_avail_cb(struct urb *urb) +{ + struct usb_device *udev; + struct ctrl_pkt_list_elem *list_elem = NULL; + struct rmnet_ctrl_dev *dev = urb->context; + void *cpkt; + int status = 0; + size_t cpkt_size = 0; + + udev = interface_to_usbdev(dev->intf); + + switch (urb->status) { + case 0: + /*success*/ + dev->get_encap_resp_cnt++; + break; + + /*do not resubmit*/ + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + case -EPROTO: + return; + + /*resubmit*/ + case -EOVERFLOW: + pr_err_ratelimited("%s: Babble error happened\n", __func__); + default: + pr_debug_ratelimited("%s: Non zero urb status = %d\n", + __func__, urb->status); + goto resubmit_int_urb; + } + + dev_dbg(dev->devicep, "Read %d bytes for %s\n", + urb->actual_length, dev->name); + + cpkt = urb->transfer_buffer; + cpkt_size = urb->actual_length; + if (!cpkt_size) { + dev->zlp_cnt++; + dev_dbg(dev->devicep, "%s: zero length pkt received\n", + __func__); + goto resubmit_int_urb; + } + + list_elem = kmalloc(sizeof(struct ctrl_pkt_list_elem), GFP_ATOMIC); + if (!list_elem) { + dev_err(dev->devicep, "%s: list_elem alloc failed\n", __func__); + return; + } + list_elem->cpkt.data = kmalloc(cpkt_size, GFP_ATOMIC); + if (!list_elem->cpkt.data) { + dev_err(dev->devicep, "%s: list_elem->data alloc failed\n", + __func__); + kfree(list_elem); + return; + } + memcpy(list_elem->cpkt.data, cpkt, cpkt_size); + list_elem->cpkt.data_size = cpkt_size; + spin_lock(&dev->rx_lock); + list_add_tail(&list_elem->list, &dev->rx_list); + spin_unlock(&dev->rx_lock); + + wake_up(&dev->read_wait_queue); + +resubmit_int_urb: + /*re-submit int urb to check response available*/ + status = usb_submit_urb(dev->inturb, GFP_ATOMIC); + if (status) + dev_err(dev->devicep, "%s: Error re-submitting Int URB %d\n", + __func__, status); +} + +static int rmnet_usb_ctrl_start_rx(struct rmnet_ctrl_dev *dev) +{ + int retval = 0; + + retval = usb_submit_urb(dev->inturb, GFP_KERNEL); + if (retval < 0) + dev_err(dev->devicep, "%s Intr submit %d\n", __func__, retval); + + return retval; +} + +int rmnet_usb_ctrl_stop_rx(struct rmnet_ctrl_dev *dev) +{ + if (!is_dev_connected(dev)) { + dev_dbg(dev->devicep, "%s: Ctrl device disconnected\n", + __func__); + return -ENODEV; + } + + dev_dbg(dev->devicep, "%s\n", __func__); + + usb_kill_urb(dev->rcvurb); + usb_kill_urb(dev->inturb); + + return 0; +} + +int rmnet_usb_ctrl_start(struct rmnet_ctrl_dev *dev) +{ + int status = 0; + + mutex_lock(&dev->dev_lock); + if (dev->is_opened) + status = rmnet_usb_ctrl_start_rx(dev); + mutex_unlock(&dev->dev_lock); + + return status; +} + +static int rmnet_usb_ctrl_alloc_rx(struct rmnet_ctrl_dev *dev) +{ + int retval = -ENOMEM; + + dev->rcvurb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->rcvurb) { + pr_err("%s: Error allocating read urb\n", __func__); + goto nomem; + } + + dev->rcvbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL); + if (!dev->rcvbuf) { + pr_err("%s: Error allocating read buffer\n", __func__); + goto nomem; + } + + dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL); + if (!dev->in_ctlreq) { + pr_err("%s: Error allocating setup packet buffer\n", __func__); + goto nomem; + } + + return 0; + +nomem: + usb_free_urb(dev->rcvurb); + kfree(dev->rcvbuf); + kfree(dev->in_ctlreq); + + return retval; + +} +static int rmnet_usb_ctrl_write_cmd(struct rmnet_ctrl_dev *dev) +{ + struct usb_device *udev; + + if (!is_dev_connected(dev)) + return -ENODEV; + + udev = interface_to_usbdev(dev->intf); + dev->set_ctrl_line_state_cnt++; + return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE), + dev->cbits_tomdm, + dev->intf->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, USB_CTRL_SET_TIMEOUT); +} + +static void ctrl_write_callback(struct urb *urb) +{ + struct rmnet_ctrl_dev *dev = urb->context; + + if (urb->status) { + dev->tx_ctrl_err_cnt++; + pr_debug_ratelimited("Write status/size %d/%d\n", + urb->status, urb->actual_length); + } + + kfree(urb->setup_packet); + kfree(urb->transfer_buffer); + usb_free_urb(urb); + usb_autopm_put_interface_async(dev->intf); +} + +static int rmnet_usb_ctrl_write(struct rmnet_ctrl_dev *dev, char *buf, + size_t size) +{ + int result; + struct urb *sndurb; + struct usb_ctrlrequest *out_ctlreq; + struct usb_device *udev; + + if (!is_dev_connected(dev)) + return -ENETRESET; + + udev = interface_to_usbdev(dev->intf); + + sndurb = usb_alloc_urb(0, GFP_KERNEL); + if (!sndurb) { + dev_err(dev->devicep, "Error allocating read urb\n"); + return -ENOMEM; + } + + out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_KERNEL); + if (!out_ctlreq) { + usb_free_urb(sndurb); + dev_err(dev->devicep, "Error allocating setup packet buffer\n"); + return -ENOMEM; + } + + /* CDC Send Encapsulated Request packet */ + out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE); + out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; + out_ctlreq->wValue = 0; + out_ctlreq->wIndex = dev->intf->cur_altsetting->desc.bInterfaceNumber; + out_ctlreq->wLength = cpu_to_le16(size); + + usb_fill_control_urb(sndurb, udev, + usb_sndctrlpipe(udev, 0), + (unsigned char *)out_ctlreq, (void *)buf, size, + ctrl_write_callback, dev); + + result = usb_autopm_get_interface(dev->intf); + if (result < 0) { + dev_err(dev->devicep, "%s: Unable to resume interface: %d\n", + __func__, result); + + /* + * Revisit: if (result == -EPERM) + * rmnet_usb_suspend(dev->intf, PMSG_SUSPEND); + */ + + usb_free_urb(sndurb); + kfree(out_ctlreq); + return result; + } + + usb_anchor_urb(sndurb, &dev->tx_submitted); + dev->snd_encap_cmd_cnt++; + result = usb_submit_urb(sndurb, GFP_KERNEL); + if (result < 0) { + dev_err(dev->devicep, "%s: Submit URB error %d\n", + __func__, result); + dev->snd_encap_cmd_cnt--; + usb_autopm_put_interface(dev->intf); + usb_unanchor_urb(sndurb); + usb_free_urb(sndurb); + kfree(out_ctlreq); + return result; + } + + return size; +} + +static int rmnet_ctl_open(struct inode *inode, struct file *file) +{ + int retval = 0; + struct rmnet_ctrl_dev *dev = + container_of(inode->i_cdev, struct rmnet_ctrl_dev, cdev); + + if (!dev) + return -ENODEV; + + if (dev->is_opened) + goto already_opened; + + /*block open to get first response available from mdm*/ + if (dev->mdm_wait_timeout && !dev->resp_available) { + retval = wait_event_interruptible_timeout( + dev->open_wait_queue, + dev->resp_available, + msecs_to_jiffies(dev->mdm_wait_timeout * + 1000)); + if (retval == 0) { + dev_err(dev->devicep, "%s: Timeout opening %s\n", + __func__, dev->name); + return -ETIMEDOUT; + } else if (retval < 0) { + dev_err(dev->devicep, "%s: Error waiting for %s\n", + __func__, dev->name); + return retval; + } + } + + if (!dev->resp_available) { + dev_dbg(dev->devicep, "%s: Connection timedout opening %s\n", + __func__, dev->name); + return -ETIMEDOUT; + } + + mutex_lock(&dev->dev_lock); + dev->is_opened = 1; + mutex_unlock(&dev->dev_lock); + + file->private_data = dev; + +already_opened: + DBG("%s: Open called for %s\n", __func__, dev->name); + + return 0; +} + +static int rmnet_ctl_release(struct inode *inode, struct file *file) +{ + struct ctrl_pkt_list_elem *list_elem = NULL; + struct rmnet_ctrl_dev *dev; + unsigned long flag; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + DBG("%s Called on %s device\n", __func__, dev->name); + + spin_lock_irqsave(&dev->rx_lock, flag); + while (!list_empty(&dev->rx_list)) { + list_elem = list_first_entry( + &dev->rx_list, + struct ctrl_pkt_list_elem, + list); + list_del(&list_elem->list); + kfree(list_elem->cpkt.data); + kfree(list_elem); + } + spin_unlock_irqrestore(&dev->rx_lock, flag); + + mutex_lock(&dev->dev_lock); + dev->is_opened = 0; + mutex_unlock(&dev->dev_lock); + + rmnet_usb_ctrl_stop_rx(dev); + + if (is_dev_connected(dev)) + usb_kill_anchored_urbs(&dev->tx_submitted); + + file->private_data = NULL; + + return 0; +} + +static ssize_t rmnet_ctl_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int retval = 0; + int bytes_to_read; + struct rmnet_ctrl_dev *dev; + struct ctrl_pkt_list_elem *list_elem = NULL; + unsigned long flags; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + DBG("%s: Read from %s\n", __func__, dev->name); + +ctrl_read: + if (!is_dev_connected(dev)) { + dev_dbg(dev->devicep, "%s: Device not connected\n", + __func__); + return -ENETRESET; + } + spin_lock_irqsave(&dev->rx_lock, flags); + if (list_empty(&dev->rx_list)) { + spin_unlock_irqrestore(&dev->rx_lock, flags); + + retval = wait_event_interruptible(dev->read_wait_queue, + !list_empty(&dev->rx_list) || + !is_dev_connected(dev)); + if (retval < 0) + return retval; + + goto ctrl_read; + } + + list_elem = list_first_entry(&dev->rx_list, + struct ctrl_pkt_list_elem, list); + bytes_to_read = (uint32_t)(list_elem->cpkt.data_size); + if (bytes_to_read > count) { + spin_unlock_irqrestore(&dev->rx_lock, flags); + dev_err(dev->devicep, "%s: Packet size %d > buf size %d\n", + __func__, bytes_to_read, count); + return -ENOMEM; + } + spin_unlock_irqrestore(&dev->rx_lock, flags); + + if (copy_to_user(buf, list_elem->cpkt.data, bytes_to_read)) { + dev_err(dev->devicep, + "%s: copy_to_user failed for %s\n", + __func__, dev->name); + return -EFAULT; + } + spin_lock_irqsave(&dev->rx_lock, flags); + list_del(&list_elem->list); + spin_unlock_irqrestore(&dev->rx_lock, flags); + + kfree(list_elem->cpkt.data); + kfree(list_elem); + DBG("%s: Returning %d bytes to %s\n", __func__, bytes_to_read, + dev->name); + DUMP_BUFFER("Read: ", bytes_to_read, buf); + + return bytes_to_read; +} + +static ssize_t rmnet_ctl_write(struct file *file, const char __user * buf, + size_t size, loff_t *pos) +{ + int status; + void *wbuf; + struct rmnet_ctrl_dev *dev = file->private_data; + + if (!dev) + return -ENODEV; + + if (size <= 0) + return -EINVAL; + + if (!is_dev_connected(dev)) + return -ENETRESET; + + DBG("%s: Writing %i bytes on %s\n", __func__, size, dev->name); + + wbuf = kmalloc(size , GFP_KERNEL); + if (!wbuf) + return -ENOMEM; + + status = copy_from_user(wbuf , buf, size); + if (status) { + dev_err(dev->devicep, + "%s: Unable to copy data from userspace %d\n", + __func__, status); + kfree(wbuf); + return status; + } + DUMP_BUFFER("Write: ", size, buf); + + status = rmnet_usb_ctrl_write(dev, wbuf, size); + if (status == size) + return size; + + return status; +} + +static int rmnet_ctrl_tiocmset(struct rmnet_ctrl_dev *dev, unsigned int set, + unsigned int clear) +{ + int retval; + + mutex_lock(&dev->dev_lock); + if (set & TIOCM_DTR) + dev->cbits_tomdm |= ACM_CTRL_DTR; + + /* + * TBD if (set & TIOCM_RTS) + * dev->cbits_tomdm |= ACM_CTRL_RTS; + */ + + if (clear & TIOCM_DTR) + dev->cbits_tomdm &= ~ACM_CTRL_DTR; + + /* + * (clear & TIOCM_RTS) + * dev->cbits_tomdm &= ~ACM_CTRL_RTS; + */ + + mutex_unlock(&dev->dev_lock); + + retval = usb_autopm_get_interface(dev->intf); + if (retval < 0) { + dev_err(dev->devicep, "%s: Unable to resume interface: %d\n", + __func__, retval); + return retval; + } + + retval = rmnet_usb_ctrl_write_cmd(dev); + + usb_autopm_put_interface(dev->intf); + return retval; +} + +static int rmnet_ctrl_tiocmget(struct rmnet_ctrl_dev *dev) +{ + int ret; + + mutex_lock(&dev->dev_lock); + ret = + /* + * TBD(dev->cbits_tolocal & ACM_CTRL_DSR ? TIOCM_DSR : 0) | + * (dev->cbits_tolocal & ACM_CTRL_CTS ? TIOCM_CTS : 0) | + */ + (dev->cbits_tolocal & ACM_CTRL_CD ? TIOCM_CD : 0) | + /* + * TBD (dev->cbits_tolocal & ACM_CTRL_RI ? TIOCM_RI : 0) | + *(dev->cbits_tomdm & ACM_CTRL_RTS ? TIOCM_RTS : 0) | + */ + (dev->cbits_tomdm & ACM_CTRL_DTR ? TIOCM_DTR : 0); + mutex_unlock(&dev->dev_lock); + + return ret; +} + +static long rmnet_ctrl_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct rmnet_ctrl_dev *dev; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + switch (cmd) { + case TIOCMGET: + + ret = rmnet_ctrl_tiocmget(dev); + break; + case TIOCMSET: + ret = rmnet_ctrl_tiocmset(dev, arg, ~arg); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct file_operations ctrldev_fops = { + .owner = THIS_MODULE, + .read = rmnet_ctl_read, + .write = rmnet_ctl_write, + .unlocked_ioctl = rmnet_ctrl_ioctl, + .open = rmnet_ctl_open, + .release = rmnet_ctl_release, +}; + +int rmnet_usb_ctrl_probe(struct usb_interface *intf, + struct usb_host_endpoint *int_in, struct rmnet_ctrl_dev *dev) +{ + u16 wMaxPacketSize; + struct usb_endpoint_descriptor *ep; + struct usb_device *udev; + int interval; + int ret = 0; + + udev = interface_to_usbdev(intf); + + if (!dev) { + pr_err("%s: Ctrl device not found\n", __func__); + return -ENODEV; + } + dev->int_pipe = usb_rcvintpipe(udev, + int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + mutex_lock(&dev->dev_lock); + dev->intf = intf; + + /*TBD: for now just update CD status*/ + dev->cbits_tolocal = ACM_CTRL_CD; + + /*send DTR high to modem*/ + dev->cbits_tomdm = ACM_CTRL_DTR; + mutex_unlock(&dev->dev_lock); + + dev->resp_available = false; + dev->snd_encap_cmd_cnt = 0; + dev->get_encap_resp_cnt = 0; + dev->resp_avail_cnt = 0; + dev->tx_ctrl_err_cnt = 0; + dev->set_ctrl_line_state_cnt = 0; + + ret = rmnet_usb_ctrl_write_cmd(dev); + if (ret < 0) + return ret; + + dev->inturb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->inturb) { + dev_err(dev->devicep, "Error allocating int urb\n"); + return -ENOMEM; + } + + /*use max pkt size from ep desc*/ + ep = &dev->intf->cur_altsetting->endpoint[0].desc; + wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize); + + dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL); + if (!dev->intbuf) { + usb_free_urb(dev->inturb); + dev_err(dev->devicep, "Error allocating int buffer\n"); + return -ENOMEM; + } + + dev->in_ctlreq->bRequestType = + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); + dev->in_ctlreq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + dev->in_ctlreq->wValue = 0; + dev->in_ctlreq->wIndex = + dev->intf->cur_altsetting->desc.bInterfaceNumber; + dev->in_ctlreq->wLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH); + + interval = max((int)int_in->desc.bInterval, + (udev->speed == USB_SPEED_HIGH) ? HS_INTERVAL + : FS_LS_INTERVAL); + + usb_fill_int_urb(dev->inturb, udev, + dev->int_pipe, + dev->intbuf, wMaxPacketSize, + notification_available_cb, dev, interval); + + return rmnet_usb_ctrl_start_rx(dev); +} + +void rmnet_usb_ctrl_disconnect(struct rmnet_ctrl_dev *dev) +{ + rmnet_usb_ctrl_stop_rx(dev); + + mutex_lock(&dev->dev_lock); + + /*TBD: for now just update CD status*/ + dev->cbits_tolocal = ~ACM_CTRL_CD; + + dev->cbits_tomdm = ~ACM_CTRL_DTR; + dev->intf = NULL; + mutex_unlock(&dev->dev_lock); + + wake_up(&dev->read_wait_queue); + + usb_free_urb(dev->inturb); + dev->inturb = NULL; + + kfree(dev->intbuf); + dev->intbuf = NULL; + + usb_kill_anchored_urbs(&dev->tx_submitted); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 4096 +static ssize_t rmnet_usb_ctrl_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_ctrl_dev *dev; + char *buf; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < NUM_CTRL_CHANNELS; i++) { + dev = ctrl_dev[i]; + if (!dev) + continue; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\n#ctrl_dev: %p Name: %s#\n" + "snd encap cmd cnt %u\n" + "resp avail cnt: %u\n" + "get encap resp cnt: %u\n" + "set ctrl line state cnt: %u\n" + "tx_err_cnt: %u\n" + "cbits_tolocal: %d\n" + "cbits_tomdm: %d\n" + "mdm_wait_timeout: %u\n" + "zlp_cnt: %u\n" + "dev opened: %s\n", + dev, dev->name, + dev->snd_encap_cmd_cnt, + dev->resp_avail_cnt, + dev->get_encap_resp_cnt, + dev->set_ctrl_line_state_cnt, + dev->tx_ctrl_err_cnt, + dev->cbits_tolocal, + dev->cbits_tomdm, + dev->mdm_wait_timeout, + dev->zlp_cnt, + dev->is_opened ? "OPEN" : "CLOSE"); + + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t rmnet_usb_ctrl_reset_stats(struct file *file, const char __user * + buf, size_t count, loff_t *ppos) +{ + struct rmnet_ctrl_dev *dev; + int i; + + for (i = 0; i < NUM_CTRL_CHANNELS; i++) { + dev = ctrl_dev[i]; + if (!dev) + continue; + + dev->snd_encap_cmd_cnt = 0; + dev->resp_avail_cnt = 0; + dev->get_encap_resp_cnt = 0; + dev->set_ctrl_line_state_cnt = 0; + dev->tx_ctrl_err_cnt = 0; + dev->zlp_cnt = 0; + } + return count; +} + +const struct file_operations rmnet_usb_ctrl_stats_ops = { + .read = rmnet_usb_ctrl_read_stats, + .write = rmnet_usb_ctrl_reset_stats, +}; + +struct dentry *usb_ctrl_dent; +struct dentry *usb_ctrl_dfile; +static void rmnet_usb_ctrl_debugfs_init(void) +{ + usb_ctrl_dent = debugfs_create_dir("rmnet_usb_ctrl", 0); + if (IS_ERR(usb_ctrl_dent)) + return; + + usb_ctrl_dfile = debugfs_create_file("status", 0644, usb_ctrl_dent, 0, + &rmnet_usb_ctrl_stats_ops); + if (!usb_ctrl_dfile || IS_ERR(usb_ctrl_dfile)) + debugfs_remove(usb_ctrl_dent); +} + +static void rmnet_usb_ctrl_debugfs_exit(void) +{ + debugfs_remove(usb_ctrl_dfile); + debugfs_remove(usb_ctrl_dent); +} + +#else +static void rmnet_usb_ctrl_debugfs_init(void) { } +static void rmnet_usb_ctrl_debugfs_exit(void) { } +#endif + +int rmnet_usb_ctrl_init(void) +{ + struct rmnet_ctrl_dev *dev; + int n; + int status; + + for (n = 0; n < NUM_CTRL_CHANNELS; ++n) { + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + status = -ENOMEM; + goto error0; + } + /*for debug purpose*/ + snprintf(dev->name, CTRL_DEV_MAX_LEN, "hsicctl%d", n); + + mutex_init(&dev->dev_lock); + spin_lock_init(&dev->rx_lock); + init_waitqueue_head(&dev->read_wait_queue); + init_waitqueue_head(&dev->open_wait_queue); + INIT_LIST_HEAD(&dev->rx_list); + init_usb_anchor(&dev->tx_submitted); + + status = rmnet_usb_ctrl_alloc_rx(dev); + if (status < 0) { + kfree(dev); + goto error0; + } + + ctrl_dev[n] = dev; + } + + status = alloc_chrdev_region(&ctrldev_num, 0, NUM_CTRL_CHANNELS, + DEVICE_NAME); + if (IS_ERR_VALUE(status)) { + pr_err("ERROR:%s: alloc_chrdev_region() ret %i.\n", + __func__, status); + goto error0; + } + + ctrldev_classp = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(ctrldev_classp)) { + pr_err("ERROR:%s: class_create() ENOMEM\n", __func__); + status = -ENOMEM; + goto error1; + } + for (n = 0; n < NUM_CTRL_CHANNELS; ++n) { + cdev_init(&ctrl_dev[n]->cdev, &ctrldev_fops); + ctrl_dev[n]->cdev.owner = THIS_MODULE; + + status = cdev_add(&ctrl_dev[n]->cdev, (ctrldev_num + n), 1); + + if (IS_ERR_VALUE(status)) { + pr_err("%s: cdev_add() ret %i\n", __func__, status); + kfree(ctrl_dev[n]); + goto error2; + } + + ctrl_dev[n]->devicep = + device_create(ctrldev_classp, NULL, + (ctrldev_num + n), NULL, + DEVICE_NAME "%d", n); + + if (IS_ERR(ctrl_dev[n]->devicep)) { + pr_err("%s: device_create() ENOMEM\n", __func__); + status = -ENOMEM; + cdev_del(&ctrl_dev[n]->cdev); + kfree(ctrl_dev[n]); + goto error2; + } + /*create /sys/class/hsicctl/hsicctlx/modem_wait*/ + status = device_create_file(ctrl_dev[n]->devicep, + &dev_attr_modem_wait); + if (status) { + device_destroy(ctrldev_classp, + MKDEV(MAJOR(ctrldev_num), n)); + cdev_del(&ctrl_dev[n]->cdev); + kfree(ctrl_dev[n]); + goto error2; + } + dev_set_drvdata(ctrl_dev[n]->devicep, ctrl_dev[n]); + } + + rmnet_usb_ctrl_debugfs_init(); + pr_info("rmnet usb ctrl Initialized.\n"); + return 0; + +error2: + while (--n >= 0) { + cdev_del(&ctrl_dev[n]->cdev); + device_destroy(ctrldev_classp, + MKDEV(MAJOR(ctrldev_num), n)); + } + + class_destroy(ctrldev_classp); + n = NUM_CTRL_CHANNELS; +error1: + unregister_chrdev_region(MAJOR(ctrldev_num), NUM_CTRL_CHANNELS); +error0: + while (--n >= 0) + kfree(ctrl_dev[n]); + + return status; +} + +void rmnet_usb_ctrl_exit(void) +{ + int i; + + for (i = 0; i < NUM_CTRL_CHANNELS; ++i) { + if (!ctrl_dev[i]) + return; + + kfree(ctrl_dev[i]->in_ctlreq); + kfree(ctrl_dev[i]->rcvbuf); + kfree(ctrl_dev[i]->intbuf); + usb_free_urb(ctrl_dev[i]->rcvurb); + usb_free_urb(ctrl_dev[i]->inturb); +#if defined(DEBUG) + device_remove_file(ctrl_dev[i]->devicep, &dev_attr_modem_wait); +#endif + cdev_del(&ctrl_dev[i]->cdev); + kfree(ctrl_dev[i]); + ctrl_dev[i] = NULL; + device_destroy(ctrldev_classp, MKDEV(MAJOR(ctrldev_num), i)); + } + + class_destroy(ctrldev_classp); + unregister_chrdev_region(MAJOR(ctrldev_num), NUM_CTRL_CHANNELS); + rmnet_usb_ctrl_debugfs_exit(); +} diff --git a/drivers/net/usb/rmnet_usb_ctrl.h b/drivers/net/usb/rmnet_usb_ctrl.h new file mode 100644 index 0000000000000000000000000000000000000000..bc077269ad1bc4a6b7d91900cb11920c596a10a1 --- /dev/null +++ b/drivers/net/usb/rmnet_usb_ctrl.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __RMNET_USB_CTRL_H +#define __RMNET_USB_CTRL_H + +#include +#include +#include +#include +#include + +#define CTRL_DEV_MAX_LEN 10 + +struct rmnet_ctrl_dev { + + /*for debugging purpose*/ + char name[CTRL_DEV_MAX_LEN]; + + struct cdev cdev; + struct device *devicep; + + struct usb_interface *intf; + unsigned int int_pipe; + struct urb *rcvurb; + struct urb *inturb; + struct usb_anchor tx_submitted; + void *rcvbuf; + void *intbuf; + struct usb_ctrlrequest *in_ctlreq; + + spinlock_t rx_lock; + struct mutex dev_lock; + struct list_head rx_list; + wait_queue_head_t read_wait_queue; + wait_queue_head_t open_wait_queue; + + unsigned is_opened; + + /*input control lines (DSR, CTS, CD, RI)*/ + unsigned int cbits_tolocal; + + /*output control lines (DTR, RTS)*/ + unsigned int cbits_tomdm; + + /* + * track first resp available from mdm when it boots up + * to avoid bigger timeout value used by qmuxd + */ + bool resp_available; + + unsigned int mdm_wait_timeout; + + /*counters*/ + unsigned int snd_encap_cmd_cnt; + unsigned int get_encap_resp_cnt; + unsigned int resp_avail_cnt; + unsigned int set_ctrl_line_state_cnt; + unsigned int tx_ctrl_err_cnt; + unsigned int zlp_cnt; +}; + +extern struct rmnet_ctrl_dev *ctrl_dev[]; + +extern int rmnet_usb_ctrl_start(struct rmnet_ctrl_dev *); +extern int rmnet_usb_ctrl_stop_rx(struct rmnet_ctrl_dev *); +extern int rmnet_usb_ctrl_init(void); +extern void rmnet_usb_ctrl_exit(void); +extern int rmnet_usb_ctrl_probe(struct usb_interface *intf, + struct usb_host_endpoint *status, + struct rmnet_ctrl_dev *dev); +extern void rmnet_usb_ctrl_disconnect(struct rmnet_ctrl_dev *); + +#endif /* __RMNET_USB_H*/ diff --git a/drivers/net/usb/rmnet_usb_data.c b/drivers/net/usb/rmnet_usb_data.c new file mode 100644 index 0000000000000000000000000000000000000000..2183ee6ebec439ecf8f279ac7ef5c404a67a2520 --- /dev/null +++ b/drivers/net/usb/rmnet_usb_data.c @@ -0,0 +1,688 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rmnet_usb_ctrl.h" + +#define RMNET_DATA_LEN 2000 +#define HEADROOM_FOR_QOS 8 + +static int data_msg_dbg_mask; + +enum { + DEBUG_MASK_LVL0 = 1U << 0, + DEBUG_MASK_LVL1 = 1U << 1, + DEBUG_MASK_LVL2 = 1U << 2, +}; + +#define DBG(m, x...) do { \ + if (data_msg_dbg_mask & m) \ + pr_info(x); \ +} while (0) + +/*echo dbg_mask > /sys/class/net/rmnet_usbx/dbg_mask*/ +static ssize_t dbg_mask_store(struct device *d, + struct device_attribute *attr, + const char *buf, size_t n) +{ + unsigned int dbg_mask; + struct net_device *dev = to_net_dev(d); + struct usbnet *unet = netdev_priv(dev); + + if (!dev) + return -ENODEV; + + sscanf(buf, "%u", &dbg_mask); + /*enable dbg msgs for data driver*/ + data_msg_dbg_mask = dbg_mask; + + /*set default msg level*/ + unet->msg_enable = NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK; + + /*enable netif_xxx msgs*/ + if (dbg_mask & DEBUG_MASK_LVL0) + unet->msg_enable |= NETIF_MSG_IFUP | NETIF_MSG_IFDOWN; + if (dbg_mask & DEBUG_MASK_LVL1) + unet->msg_enable |= NETIF_MSG_TX_ERR | NETIF_MSG_RX_ERR + | NETIF_MSG_TX_QUEUED | NETIF_MSG_TX_DONE + | NETIF_MSG_RX_STATUS; + + return n; +} + +static ssize_t dbg_mask_show(struct device *d, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", data_msg_dbg_mask); +} + +static DEVICE_ATTR(dbg_mask, 0644, dbg_mask_show, dbg_mask_store); + +#define DBG0(x...) DBG(DEBUG_MASK_LVL0, x) +#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x) +#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x) + +static void rmnet_usb_setup(struct net_device *); +static int rmnet_ioctl(struct net_device *, struct ifreq *, int); + +static int rmnet_usb_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct usbnet *unet; + struct rmnet_ctrl_dev *dev; + int time = 0; + int retval = 0; + + unet = usb_get_intfdata(iface); + if (!unet) { + pr_err("%s:data device not found\n", __func__); + retval = -ENODEV; + goto fail; + } + + dev = (struct rmnet_ctrl_dev *)unet->data[1]; + if (!dev) { + dev_err(&unet->udev->dev, "%s: ctrl device not found\n", + __func__); + retval = -ENODEV; + goto fail; + } + + retval = usbnet_suspend(iface, message); + if (!retval) { + if (message.event & PM_EVENT_SUSPEND) { + time = usb_wait_anchor_empty_timeout(&dev->tx_submitted, + 1000); + if (!time) + usb_kill_anchored_urbs(&dev->tx_submitted); + + retval = rmnet_usb_ctrl_stop_rx(dev); + iface->dev.power.power_state.event = message.event; + } + /* TBD : do we need to set/clear usbnet->udev->reset_resume*/ + } else + dev_dbg(&unet->udev->dev, + "%s: device is busy can not suspend\n", __func__); + +fail: + return retval; +} + +static int rmnet_usb_resume(struct usb_interface *iface) +{ + int retval = 0; + int oldstate; + struct usbnet *unet; + struct rmnet_ctrl_dev *dev; + + unet = usb_get_intfdata(iface); + if (!unet) { + pr_err("%s:data device not found\n", __func__); + retval = -ENODEV; + goto fail; + } + + dev = (struct rmnet_ctrl_dev *)unet->data[1]; + if (!dev) { + dev_err(&unet->udev->dev, "%s: ctrl device not found\n", + __func__); + retval = -ENODEV; + goto fail; + } + oldstate = iface->dev.power.power_state.event; + iface->dev.power.power_state.event = PM_EVENT_ON; + + retval = usbnet_resume(iface); + if (!retval) { + if (oldstate & PM_EVENT_SUSPEND) + retval = rmnet_usb_ctrl_start(dev); + } +fail: + return retval; +} + +static int rmnet_usb_bind(struct usbnet *usbnet, struct usb_interface *iface) +{ + struct usb_host_endpoint *endpoint = NULL; + struct usb_host_endpoint *bulk_in = NULL; + struct usb_host_endpoint *bulk_out = NULL; + struct usb_host_endpoint *int_in = NULL; + struct usb_device *udev; + int status = 0; + int i; + int numends; + + udev = interface_to_usbdev(iface); + numends = iface->cur_altsetting->desc.bNumEndpoints; + for (i = 0; i < numends; i++) { + endpoint = iface->cur_altsetting->endpoint + i; + if (!endpoint) { + dev_err(&udev->dev, "%s: invalid endpoint %u\n", + __func__, i); + status = -EINVAL; + goto out; + } + if (usb_endpoint_is_bulk_in(&endpoint->desc)) + bulk_in = endpoint; + else if (usb_endpoint_is_bulk_out(&endpoint->desc)) + bulk_out = endpoint; + else if (usb_endpoint_is_int_in(&endpoint->desc)) + int_in = endpoint; + } + + if (!bulk_in || !bulk_out || !int_in) { + dev_err(&udev->dev, "%s: invalid endpoints\n", __func__); + status = -EINVAL; + goto out; + } + usbnet->in = usb_rcvbulkpipe(usbnet->udev, + bulk_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + usbnet->out = usb_sndbulkpipe(usbnet->udev, + bulk_out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + usbnet->status = int_in; + + /*change name of net device to rmnet_usbx here*/ + strlcpy(usbnet->net->name, "rmnet_usb%d", IFNAMSIZ); + + /*TBD: update rx_urb_size, curently set to eth frame len by usbnet*/ +out: + return status; +} + +static struct sk_buff *rmnet_usb_tx_fixup(struct usbnet *dev, + struct sk_buff *skb, gfp_t flags) +{ + struct QMI_QOS_HDR_S *qmih; + + if (test_bit(RMNET_MODE_QOS, &dev->data[0])) { + 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; + } + + DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n", + dev->net->name, dev->net->stats.tx_packets, skb->len, skb->mark); + + return skb; +} + +static __be16 rmnet_ip_type_trans(struct sk_buff *skb, + struct net_device *dev) +{ + __be16 protocol = 0; + + skb->dev = dev; + + 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); + } + + return protocol; +} + +static int rmnet_usb_rx_fixup(struct usbnet *dev, + struct sk_buff *skb) +{ + + if (test_bit(RMNET_MODE_LLP_IP, &dev->data[0])) + skb->protocol = rmnet_ip_type_trans(skb, dev->net); + else /*set zero for eth mode*/ + skb->protocol = 0; + + DBG1("[%s] Rx packet #%lu len=%d\n", + dev->net->name, dev->net->stats.rx_packets, skb->len); + + return 1; +} + +static int rmnet_usb_manage_power(struct usbnet *dev, int on) +{ + dev->intf->needs_remote_wakeup = on; + 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 struct net_device_stats *rmnet_get_stats(struct net_device *dev) +{ + return &dev->stats; +} + +static const struct net_device_ops rmnet_usb_ops_ether = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_get_stats = rmnet_get_stats, + /*.ndo_set_multicast_list = rmnet_set_multicast_list,*/ + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = usbnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const struct net_device_ops rmnet_usb_ops_ip = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_get_stats = rmnet_get_stats, + /*.ndo_set_multicast_list = rmnet_set_multicast_list,*/ + .ndo_tx_timeout = usbnet_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 usbnet *unet = netdev_priv(dev); + u32 old_opmode; + int prev_mtu = dev->mtu; + int rc = 0; + + old_opmode = unet->data[0]; /*data[0] saves operation mode*/ + /* Process IOCTL command */ + switch (cmd) { + case RMNET_IOCTL_SET_LLP_ETHERNET: /*Set Ethernet protocol*/ + /* Perform Ethernet config only if in IP mode currently*/ + if (test_bit(RMNET_MODE_LLP_IP, &unet->data[0])) { + ether_setup(dev); + random_ether_addr(dev->dev_addr); + dev->mtu = prev_mtu; + dev->netdev_ops = &rmnet_usb_ops_ether; + clear_bit(RMNET_MODE_LLP_IP, &unet->data[0]); + set_bit(RMNET_MODE_LLP_ETH, &unet->data[0]); + 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 (test_bit(RMNET_MODE_LLP_ETH, &unet->data[0])) { + + /* 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_QOS; + dev->netdev_ops = &rmnet_usb_ops_ip; + clear_bit(RMNET_MODE_LLP_ETH, &unet->data[0]); + set_bit(RMNET_MODE_LLP_IP, &unet->data[0]); + 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 *)(unet->data[0] + & (RMNET_MODE_LLP_ETH + | RMNET_MODE_LLP_IP)); + break; + + case RMNET_IOCTL_SET_QOS_ENABLE: /* Set QoS header enabled*/ + set_bit(RMNET_MODE_QOS, &unet->data[0]); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header enable\n", + dev->name); + break; + + case RMNET_IOCTL_SET_QOS_DISABLE: /* Set QoS header disabled */ + clear_bit(RMNET_MODE_QOS, &unet->data[0]); + 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 *)(unet->data[0] + & RMNET_MODE_QOS); + break; + + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode*/ + ifr->ifr_ifru.ifru_data = (void *)unet->data[0]; + break; + + case RMNET_IOCTL_OPEN: /* Open transport port */ + rc = usbnet_open(dev); + DBG0("[%s] rmnet_ioctl(): open transport port\n", dev->name); + break; + + case RMNET_IOCTL_CLOSE: /* Close transport port*/ + rc = usbnet_stop(dev); + DBG0("[%s] rmnet_ioctl(): close transport port\n", dev->name); + break; + + default: + dev_err(&unet->udev->dev, "[%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%08lx\n", + dev->name, __func__, cmd, old_opmode, unet->data[0]); + + return rc; +} + +static void rmnet_usb_setup(struct net_device *dev) +{ + /* Using Ethernet mode by default */ + dev->netdev_ops = &rmnet_usb_ops_ether; + + /* 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 rmnet_usb_data_status(struct seq_file *s, void *unused) +{ + struct usbnet *unet = s->private; + + seq_printf(s, "RMNET_MODE_LLP_IP: %d\n", + test_bit(RMNET_MODE_LLP_IP, &unet->data[0])); + seq_printf(s, "RMNET_MODE_LLP_ETH: %d\n", + test_bit(RMNET_MODE_LLP_ETH, &unet->data[0])); + seq_printf(s, "RMNET_MODE_QOS: %d\n", + test_bit(RMNET_MODE_QOS, &unet->data[0])); + seq_printf(s, "Net MTU: %u\n", unet->net->mtu); + seq_printf(s, "rx_urb_size: %u\n", unet->rx_urb_size); + seq_printf(s, "rx skb q len: %u\n", unet->rxq.qlen); + seq_printf(s, "rx skb done q len: %u\n", unet->done.qlen); + seq_printf(s, "rx errors: %lu\n", unet->net->stats.rx_errors); + seq_printf(s, "rx over errors: %lu\n", + unet->net->stats.rx_over_errors); + seq_printf(s, "rx length errors: %lu\n", + unet->net->stats.rx_length_errors); + seq_printf(s, "rx packets: %lu\n", unet->net->stats.rx_packets); + seq_printf(s, "rx bytes: %lu\n", unet->net->stats.rx_bytes); + seq_printf(s, "tx skb q len: %u\n", unet->txq.qlen); + seq_printf(s, "tx errors: %lu\n", unet->net->stats.tx_errors); + seq_printf(s, "tx packets: %lu\n", unet->net->stats.tx_packets); + seq_printf(s, "tx bytes: %lu\n", unet->net->stats.tx_bytes); + seq_printf(s, "suspend count: %d\n", unet->suspend_count); + seq_printf(s, "EVENT_DEV_OPEN: %d\n", + test_bit(EVENT_DEV_OPEN, &unet->flags)); + seq_printf(s, "EVENT_TX_HALT: %d\n", + test_bit(EVENT_TX_HALT, &unet->flags)); + seq_printf(s, "EVENT_RX_HALT: %d\n", + test_bit(EVENT_RX_HALT, &unet->flags)); + seq_printf(s, "EVENT_RX_MEMORY: %d\n", + test_bit(EVENT_RX_MEMORY, &unet->flags)); + seq_printf(s, "EVENT_DEV_ASLEEP: %d\n", + test_bit(EVENT_DEV_ASLEEP, &unet->flags)); + + return 0; +} + +static int rmnet_usb_data_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, rmnet_usb_data_status, inode->i_private); +} + +const struct file_operations rmnet_usb_data_fops = { + .open = rmnet_usb_data_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int rmnet_usb_data_debugfs_init(struct usbnet *unet) +{ + struct dentry *rmnet_usb_data_dbg_root; + struct dentry *rmnet_usb_data_dentry; + + rmnet_usb_data_dbg_root = debugfs_create_dir(unet->net->name, NULL); + if (!rmnet_usb_data_dbg_root || IS_ERR(rmnet_usb_data_dbg_root)) + return -ENODEV; + + rmnet_usb_data_dentry = debugfs_create_file("status", + S_IRUGO | S_IWUSR, + rmnet_usb_data_dbg_root, unet, + &rmnet_usb_data_fops); + + if (!rmnet_usb_data_dentry) { + debugfs_remove_recursive(rmnet_usb_data_dbg_root); + return -ENODEV; + } + + unet->data[2] = (unsigned long)rmnet_usb_data_dbg_root; + + return 0; +} + +static void rmnet_usb_data_debugfs_cleanup(struct usbnet *unet) +{ + struct dentry *root = (struct dentry *)unet->data[2]; + + debugfs_remove_recursive(root); + unet->data[2] = 0; +} + +static int rmnet_usb_probe(struct usb_interface *iface, + const struct usb_device_id *prod) +{ + struct usbnet *unet; + struct usb_device *udev; + struct driver_info *info; + unsigned int iface_num; + static int first_rmnet_iface_num = -EINVAL; + int status = 0; + + udev = interface_to_usbdev(iface); + iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + if (iface->num_altsetting != 1) { + dev_err(&udev->dev, "%s invalid num_altsetting %u\n", + __func__, iface->num_altsetting); + status = -EINVAL; + goto out; + } + + info = (struct driver_info *)prod->driver_info; + if (!test_bit(iface_num, &info->data)) + return -ENODEV; + + status = usbnet_probe(iface, prod); + if (status < 0) { + dev_err(&udev->dev, "usbnet_probe failed %d\n", status); + goto out; + } + unet = usb_get_intfdata(iface); + + /*set rmnet operation mode to eth by default*/ + set_bit(RMNET_MODE_LLP_ETH, &unet->data[0]); + + /*update net device*/ + rmnet_usb_setup(unet->net); + + /*create /sys/class/net/rmnet_usbx/dbg_mask*/ + status = device_create_file(&unet->net->dev, &dev_attr_dbg_mask); + if (status) + goto out; + + if (first_rmnet_iface_num == -EINVAL) + first_rmnet_iface_num = iface_num; + + /*save control device intstance */ + unet->data[1] = (unsigned long)ctrl_dev \ + [iface_num - first_rmnet_iface_num]; + + status = rmnet_usb_ctrl_probe(iface, unet->status, + (struct rmnet_ctrl_dev *)unet->data[1]); + if (status) + goto out; + + status = rmnet_usb_data_debugfs_init(unet); + if (status) + dev_dbg(&udev->dev, "mode debugfs file is not available\n"); + + /* allow modem to wake up suspended system */ + device_set_wakeup_enable(&udev->dev, 1); +out: + return status; +} + +static void rmnet_usb_disconnect(struct usb_interface *intf) +{ + struct usbnet *unet; + struct usb_device *udev; + struct rmnet_ctrl_dev *dev; + + udev = interface_to_usbdev(intf); + device_set_wakeup_enable(&udev->dev, 0); + + unet = usb_get_intfdata(intf); + if (!unet) { + dev_err(&udev->dev, "%s:data device not found\n", __func__); + return; + } + + rmnet_usb_data_debugfs_cleanup(unet); + + dev = (struct rmnet_ctrl_dev *)unet->data[1]; + if (!dev) { + dev_err(&udev->dev, "%s:ctrl device not found\n", __func__); + return; + } + unet->data[0] = 0; + unet->data[1] = 0; + rmnet_usb_ctrl_disconnect(dev); + device_remove_file(&unet->net->dev, &dev_attr_dbg_mask); + usbnet_disconnect(intf); +} + +/*bit position represents interface number*/ +#define PID9034_IFACE_MASK 0xF0 +#define PID9048_IFACE_MASK 0x1E0 +#define PID904C_IFACE_MASK 0x1C0 + +static const struct driver_info rmnet_info_pid9034 = { + .description = "RmNET net device", + .bind = rmnet_usb_bind, + .tx_fixup = rmnet_usb_tx_fixup, + .rx_fixup = rmnet_usb_rx_fixup, + .manage_power = rmnet_usb_manage_power, + .data = PID9034_IFACE_MASK, +}; + +static const struct driver_info rmnet_info_pid9048 = { + .description = "RmNET net device", + .bind = rmnet_usb_bind, + .tx_fixup = rmnet_usb_tx_fixup, + .rx_fixup = rmnet_usb_rx_fixup, + .manage_power = rmnet_usb_manage_power, + .data = PID9048_IFACE_MASK, +}; + +static const struct driver_info rmnet_info_pid904c = { + .description = "RmNET net device", + .bind = rmnet_usb_bind, + .tx_fixup = rmnet_usb_tx_fixup, + .rx_fixup = rmnet_usb_rx_fixup, + .manage_power = rmnet_usb_manage_power, + .data = PID904C_IFACE_MASK, +}; + +static const struct usb_device_id vidpids[] = { + { + USB_DEVICE(0x05c6, 0x9034), /* MDM9x15*/ + .driver_info = (unsigned long)&rmnet_info_pid9034, + }, + { + USB_DEVICE(0x05c6, 0x9048), /* MDM9x15*/ + .driver_info = (unsigned long)&rmnet_info_pid9048, + }, + { + USB_DEVICE(0x05c6, 0x904c), /* MDM9x15*/ + .driver_info = (unsigned long)&rmnet_info_pid904c, + }, + + { }, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, vidpids); + +static struct usb_driver rmnet_usb = { + .name = "rmnet_usb", + .id_table = vidpids, + .probe = rmnet_usb_probe, + .disconnect = rmnet_usb_disconnect, + .suspend = rmnet_usb_suspend, + .resume = rmnet_usb_resume, + .supports_autosuspend = true, +}; + +static int __init rmnet_usb_init(void) +{ + int retval; + + retval = usb_register(&rmnet_usb); + if (retval) { + err("usb_register failed: %d", retval); + return retval; + } + /* initialize rmnet ctrl device here*/ + retval = rmnet_usb_ctrl_init(); + if (retval) { + usb_deregister(&rmnet_usb); + err("rmnet_usb_cmux_init failed: %d", retval); + return retval; + } + + return 0; +} +module_init(rmnet_usb_init); + +static void __exit rmnet_usb_exit(void) +{ + rmnet_usb_ctrl_exit(); + usb_deregister(&rmnet_usb); +} +module_exit(rmnet_usb_exit); + +MODULE_DESCRIPTION("msm rmnet usb device"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index b38db48b1ce09b380d8cdea0ea444be0d97573d0..2b73d992885b10b9c3ff9cb0b5de1d56005b1689 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -232,7 +233,9 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb) return; } - skb->protocol = eth_type_trans (skb, dev->net); + if (!skb->protocol) + skb->protocol = eth_type_trans(skb, dev->net); + dev->net->stats.rx_packets++; dev->net->stats.rx_bytes += skb->len; @@ -353,6 +356,9 @@ static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags) return -ENOMEM; } + if (dev->net->type != ARPHRD_RAWIP) + skb_reserve(skb, NET_IP_ALIGN); + entry = (struct skb_data *) skb->cb; entry->urb = urb; entry->dev = dev; @@ -387,6 +393,7 @@ static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags) tasklet_schedule (&dev->bh); break; case 0: + usb_mark_last_busy(dev->udev); __usbnet_queue_skb(&dev->rxq, skb, rx_start); } } else { diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index d9252ff7fa61a4dd052f035dfc8594ba62567e1e..914f4fb1cc732dedc800000b9d840b440a32756b 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -273,6 +273,36 @@ 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 ATH6K_LEGACY_EXT + tristate "QCA AR6003 wlan SDIO driver - External" + depends on MMC_MSM && WLAN + select WIRELESS_EXT + select WEXT_PRIV + ---help--- + This module adds support for wireless adapters based on QCA AR6003 chipset + running over SDIO. Driver is built outside of kernel tree, this config + only enables configurations required for QCA AR6003 wlan driver. + +config WCNSS_CORE + tristate "Qualcomm WCNSS CORE driver" + depends on ARCH_MSM8960 + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + ---help--- + Core driver for the Qualcomm WCNSS triple play connectivity subsystem + 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 8db066f3eb8f0a7623e049fc214b4930e02c2887..9392311b7edcaa7862064e419cfb4a50e7d0efc7 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -63,3 +63,6 @@ obj-$(CONFIG_BCMDHD) += bcmdhd/ obj-$(CONFIG_BRCMFMAC) += brcm80211/ obj-$(CONFIG_BRCMSMAC) += brcm80211/ + +obj-$(CONFIG_LIBRA_SDIOIF) += libra/ +obj-$(CONFIG_WCNSS_CORE) += wcnss/ diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c index fa84e37bf091546035c6638130892a5bbe559c66..4f96a69be38f84f95ad4c9b5ea9a0fbbc3eeca60 100644 --- a/drivers/net/wireless/ath/ath9k/hw.c +++ b/drivers/net/wireless/ath/ath9k/hw.c @@ -538,6 +538,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/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c index cb006458fc4b62400a7f68e1778982c011eb6c79..740f09b2151d9807a4a83643214d2bf660df2831 100644 --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -621,8 +621,10 @@ static void ath9k_init_band_txpower(struct ath_softc *sc, int band) static void ath9k_init_txpower_limits(struct ath_softc *sc) { struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); struct ath9k_channel *curchan = ah->curchan; + ah->txchainmask = common->tx_chainmask; if (ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) ath9k_init_band_txpower(sc, IEEE80211_BAND_2GHZ); if (ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) diff --git a/drivers/net/wireless/ath/ath9k/pci.c b/drivers/net/wireless/ath/ath9k/pci.c index 77dc327def8dec509f50bfe4f490f1a29351f905..5e66310a3899bc2952da2c3a71638bf70881ef5c 100644 --- a/drivers/net/wireless/ath/ath9k/pci.c +++ b/drivers/net/wireless/ath/ath9k/pci.c @@ -148,6 +148,31 @@ static void ath_pci_aspm_init(struct ath_common *common) } } +static void ath_pci_aspm_init(struct ath_common *common) +{ + struct ath_softc *sc = (struct ath_softc *) common->priv; + struct ath_hw *ah = sc->sc_ah; + struct pci_dev *pdev = to_pci_dev(sc->dev); + struct pci_dev *parent; + int pos; + u8 aspm; + + if (!pci_is_pcie(pdev)) + return; + + parent = pdev->bus->self; + if (WARN_ON(!parent)) + return; + + pos = pci_pcie_cap(parent); + pci_read_config_byte(parent, pos + PCI_EXP_LNKCTL, &aspm); + if (aspm & (PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1)) { + ah->aspm_enabled = true; + /* Initialize PCIe PM and SERDES registers. */ + ath9k_hw_configpcipowersave(ah, 0, 0); + } +} + static const struct ath_bus_ops ath_pci_bus_ops = { .ath_bus_type = ATH_PCI, .read_cachesize = ath_pci_read_cachesize, diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c index e4d6dc2e37d12479ab2bdef3560d8f700e9e0d13..0cd9f472a5f50e86d84cc339e93ddf1e1c9dd32e 100644 --- a/drivers/net/wireless/b43/main.c +++ b/drivers/net/wireless/b43/main.c @@ -2527,6 +2527,13 @@ static int b43_upload_microcode(struct b43_wldev *dev) b43_print_fw_helptext(dev->wl, 1); err = -EOPNOTSUPP; goto error; + } else if (fwrev >= 598) { + b43err(dev->wl, "YOUR FIRMWARE IS TOO NEW. Support for " + "firmware 598 and up requires kernel 3.2 or newer. You " + "have to install older firmware or upgrade kernel.\n"); + b43_print_fw_helptext(dev->wl, 1); + err = -EOPNOTSUPP; + goto error; } dev->fw.rev = fwrev; dev->fw.patch = fwpatch; diff --git a/drivers/net/wireless/bcm4329/Kconfig b/drivers/net/wireless/bcm4329/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..ca5760d32385d886cae591f477b2bde753eb9f37 --- /dev/null +++ b/drivers/net/wireless/bcm4329/Kconfig @@ -0,0 +1,27 @@ +config BCM4329 + tristate "Broadcom 4329 wireless cards support" + depends on MMC + select WIRELESS_EXT + select WEXT_PRIV + ---help--- + This module adds support for wireless adapters based on + Broadcom 4329 chipset. + + This driver uses the kernel's wireless extensions subsystem. + + If you choose to build a module, it'll be called dhd. Say M if + unsure. + +config BCM4329_FW_PATH + depends on BCM4329 + string "Firmware path" + default "/system/etc/firmware/fw_bcm4329.bin" + ---help--- + Path to the firmware file. + +config BCM4329_NVRAM_PATH + depends on BCM4329 + string "NVRAM path" + default "/proc/calibration" + ---help--- + Path to the calibration file. diff --git a/drivers/net/wireless/bcm4329/Makefile b/drivers/net/wireless/bcm4329/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5a662be7fc532d7efaaa3eadb0122a973ae09e35 --- /dev/null +++ b/drivers/net/wireless/bcm4329/Makefile @@ -0,0 +1,21 @@ +# bcm4329 +DHDCFLAGS = -DLINUX -DBCMDRIVER -DBCMDONGLEHOST -DDHDTHREAD -DBCMWPA2 \ + -DUNRELEASEDCHIP -Dlinux -DDHD_SDALIGN=64 -DMAX_HDR_READ=64 \ + -DDHD_FIRSTREAD=64 -DDHD_GPL -DDHD_SCHED -DBDC -DTOE -DDHD_BCMEVENTS \ + -DSHOW_EVENTS -DBCMSDIO -DDHD_GPL -DBCMLXSDMMC -DBCMPLATFORM_BUS \ + -Wall -Wstrict-prototypes -Werror -DOOB_INTR_ONLY -DCUSTOMER_HW2 \ + -DDHD_USE_STATIC_BUF -DMMC_SDIO_ABORT -DDHD_DEBUG_TRAP -DSOFTAP \ + -DEMBEDDED_PLATFORM -DARP_OFFLOAD_SUPPORT -DPKT_FILTER_SUPPORT \ + -DGET_CUSTOM_MAC_ENABLE -DSET_RANDOM_MAC_SOFTAP -DCSCAN -DHW_OOB \ + -DKEEP_ALIVE -DPNO_SUPPORT \ + -Idrivers/net/wireless/bcm4329 -Idrivers/net/wireless/bcm4329/include + +DHDOFILES = dhd_linux.o linux_osl.o bcmutils.o dhd_common.o dhd_custom_gpio.o \ + wl_iw.o siutils.o sbutils.o aiutils.o hndpmu.o bcmwifi.o dhd_sdio.o \ + dhd_linux_sched.o dhd_cdc.o bcmsdh_sdmmc.o bcmsdh.o bcmsdh_linux.o \ + bcmsdh_sdmmc_linux.o + +obj-$(CONFIG_BCM4329) += bcm4329.o +bcm4329-objs += $(DHDOFILES) +EXTRA_CFLAGS = $(DHDCFLAGS) +EXTRA_LDFLAGS += --strip-debug diff --git a/drivers/net/wireless/bcm4329/aiutils.c b/drivers/net/wireless/bcm4329/aiutils.c new file mode 100644 index 0000000000000000000000000000000000000000..df48ac0d83d47310ed136e64034658f2a0dc288a --- /dev/null +++ b/drivers/net/wireless/bcm4329/aiutils.c @@ -0,0 +1,686 @@ +/* + * Misc utility routines for accessing chip-specific features + * of the SiliconBackplane-based Broadcom chips. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: aiutils.c,v 1.6.4.7.4.6 2010/04/21 20:43:47 Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "siutils_priv.h" + +STATIC uint32 +get_asd(si_t *sih, uint32 *eromptr, uint sp, uint ad, uint st, + uint32 *addrl, uint32 *addrh, uint32 *sizel, uint32 *sizeh); + + +/* EROM parsing */ + +static uint32 +get_erom_ent(si_t *sih, uint32 *eromptr, uint32 mask, uint32 match) +{ + uint32 ent; + uint inv = 0, nom = 0; + + while (TRUE) { + ent = R_REG(si_osh(sih), (uint32 *)(uintptr)(*eromptr)); + *eromptr += sizeof(uint32); + + if (mask == 0) + break; + + if ((ent & ER_VALID) == 0) { + inv++; + continue; + } + + if (ent == (ER_END | ER_VALID)) + break; + + if ((ent & mask) == match) + break; + + nom++; + } + + SI_MSG(("%s: Returning ent 0x%08x\n", __FUNCTION__, ent)); + if (inv + nom) + SI_MSG((" after %d invalid and %d non-matching entries\n", inv, nom)); + return ent; +} + +STATIC uint32 +get_asd(si_t *sih, uint32 *eromptr, uint sp, uint ad, uint st, + uint32 *addrl, uint32 *addrh, uint32 *sizel, uint32 *sizeh) +{ + uint32 asd, sz, szd; + + asd = get_erom_ent(sih, eromptr, ER_VALID, ER_VALID); + if (((asd & ER_TAG1) != ER_ADD) || + (((asd & AD_SP_MASK) >> AD_SP_SHIFT) != sp) || + ((asd & AD_ST_MASK) != st)) { + /* This is not what we want, "push" it back */ + *eromptr -= sizeof(uint32); + return 0; + } + *addrl = asd & AD_ADDR_MASK; + if (asd & AD_AG32) + *addrh = get_erom_ent(sih, eromptr, 0, 0); + else + *addrh = 0; + *sizeh = 0; + sz = asd & AD_SZ_MASK; + if (sz == AD_SZ_SZD) { + szd = get_erom_ent(sih, eromptr, 0, 0); + *sizel = szd & SD_SZ_MASK; + if (szd & SD_SG32) + *sizeh = get_erom_ent(sih, eromptr, 0, 0); + } else + *sizel = AD_SZ_BASE << (sz >> AD_SZ_SHIFT); + + SI_MSG((" SP %d, ad %d: st = %d, 0x%08x_0x%08x @ 0x%08x_0x%08x\n", + sp, ad, st, *sizeh, *sizel, *addrh, *addrl)); + + return asd; +} + +/* parse the enumeration rom to identify all cores */ +void +ai_scan(si_t *sih, void *regs, uint devid) +{ + si_info_t *sii = SI_INFO(sih); + chipcregs_t *cc = (chipcregs_t *)regs; + uint32 erombase, eromptr, eromlim; + + erombase = R_REG(sii->osh, &cc->eromptr); + + switch (BUSTYPE(sih->bustype)) { + case SI_BUS: + eromptr = (uintptr)REG_MAP(erombase, SI_CORE_SIZE); + break; + + case PCI_BUS: + /* Set wrappers address */ + sii->curwrap = (void *)((uintptr)regs + SI_CORE_SIZE); + + /* Now point the window at the erom */ + OSL_PCI_WRITE_CONFIG(sii->osh, PCI_BAR0_WIN, 4, erombase); + eromptr = (uint32)(uintptr)regs; + break; + + case SPI_BUS: + case SDIO_BUS: + eromptr = erombase; + break; + + case PCMCIA_BUS: + default: + SI_ERROR(("Don't know how to do AXI enumertion on bus %d\n", sih->bustype)); + ASSERT(0); + return; + } + eromlim = eromptr + ER_REMAPCONTROL; + + SI_MSG(("ai_scan: regs = 0x%p, erombase = 0x%08x, eromptr = 0x%08x, eromlim = 0x%08x\n", + regs, erombase, eromptr, eromlim)); + while (eromptr < eromlim) { + uint32 cia, cib, base, cid, mfg, crev, nmw, nsw, nmp, nsp; + uint32 mpd, asd, addrl, addrh, sizel, sizeh; + uint i, j, idx; + bool br; + + br = FALSE; + + /* Grok a component */ + cia = get_erom_ent(sih, &eromptr, ER_TAG, ER_CI); + if (cia == (ER_END | ER_VALID)) { + SI_MSG(("Found END of erom after %d cores\n", sii->numcores)); + return; + } + base = eromptr - sizeof(uint32); + cib = get_erom_ent(sih, &eromptr, 0, 0); + + if ((cib & ER_TAG) != ER_CI) { + SI_ERROR(("CIA not followed by CIB\n")); + goto error; + } + + cid = (cia & CIA_CID_MASK) >> CIA_CID_SHIFT; + mfg = (cia & CIA_MFG_MASK) >> CIA_MFG_SHIFT; + crev = (cib & CIB_REV_MASK) >> CIB_REV_SHIFT; + nmw = (cib & CIB_NMW_MASK) >> CIB_NMW_SHIFT; + nsw = (cib & CIB_NSW_MASK) >> CIB_NSW_SHIFT; + nmp = (cib & CIB_NMP_MASK) >> CIB_NMP_SHIFT; + nsp = (cib & CIB_NSP_MASK) >> CIB_NSP_SHIFT; + + SI_MSG(("Found component 0x%04x/0x%4x rev %d at erom addr 0x%08x, with nmw = %d, " + "nsw = %d, nmp = %d & nsp = %d\n", + mfg, cid, crev, base, nmw, nsw, nmp, nsp)); + + if (((mfg == MFGID_ARM) && (cid == DEF_AI_COMP)) || (nsp == 0)) + continue; + if ((nmw + nsw == 0)) { + /* A component which is not a core */ + if (cid == OOB_ROUTER_CORE_ID) { + asd = get_asd(sih, &eromptr, 0, 0, AD_ST_SLAVE, + &addrl, &addrh, &sizel, &sizeh); + if (asd != 0) { + sii->common_info->oob_router = addrl; + } + } + continue; + } + + idx = sii->numcores; +/* sii->eromptr[idx] = base; */ + sii->common_info->cia[idx] = cia; + sii->common_info->cib[idx] = cib; + sii->common_info->coreid[idx] = cid; + + for (i = 0; i < nmp; i++) { + mpd = get_erom_ent(sih, &eromptr, ER_VALID, ER_VALID); + if ((mpd & ER_TAG) != ER_MP) { + SI_ERROR(("Not enough MP entries for component 0x%x\n", cid)); + goto error; + } + SI_MSG((" Master port %d, mp: %d id: %d\n", i, + (mpd & MPD_MP_MASK) >> MPD_MP_SHIFT, + (mpd & MPD_MUI_MASK) >> MPD_MUI_SHIFT)); + } + + /* First Slave Address Descriptor should be port 0: + * the main register space for the core + */ + asd = get_asd(sih, &eromptr, 0, 0, AD_ST_SLAVE, &addrl, &addrh, &sizel, &sizeh); + if (asd == 0) { + /* Try again to see if it is a bridge */ + asd = get_asd(sih, &eromptr, 0, 0, AD_ST_BRIDGE, &addrl, &addrh, + &sizel, &sizeh); + if (asd != 0) + br = TRUE; + else + if ((addrh != 0) || (sizeh != 0) || (sizel != SI_CORE_SIZE)) { + SI_ERROR(("First Slave ASD for core 0x%04x malformed " + "(0x%08x)\n", cid, asd)); + goto error; + } + } + sii->common_info->coresba[idx] = addrl; + sii->common_info->coresba_size[idx] = sizel; + /* Get any more ASDs in port 0 */ + j = 1; + do { + asd = get_asd(sih, &eromptr, 0, j, AD_ST_SLAVE, &addrl, &addrh, + &sizel, &sizeh); + if ((asd != 0) && (j == 1) && (sizel == SI_CORE_SIZE)) + sii->common_info->coresba2[idx] = addrl; + sii->common_info->coresba2_size[idx] = sizel; + j++; + } while (asd != 0); + + /* Go through the ASDs for other slave ports */ + for (i = 1; i < nsp; i++) { + j = 0; + do { + asd = get_asd(sih, &eromptr, i, j++, AD_ST_SLAVE, &addrl, &addrh, + &sizel, &sizeh); + } while (asd != 0); + if (j == 0) { + SI_ERROR((" SP %d has no address descriptors\n", i)); + goto error; + } + } + + /* Now get master wrappers */ + for (i = 0; i < nmw; i++) { + asd = get_asd(sih, &eromptr, i, 0, AD_ST_MWRAP, &addrl, &addrh, + &sizel, &sizeh); + if (asd == 0) { + SI_ERROR(("Missing descriptor for MW %d\n", i)); + goto error; + } + if ((sizeh != 0) || (sizel != SI_CORE_SIZE)) { + SI_ERROR(("Master wrapper %d is not 4KB\n", i)); + goto error; + } + if (i == 0) + sii->common_info->wrapba[idx] = addrl; + } + + /* And finally slave wrappers */ + for (i = 0; i < nsw; i++) { + uint fwp = (nsp == 1) ? 0 : 1; + asd = get_asd(sih, &eromptr, fwp + i, 0, AD_ST_SWRAP, &addrl, &addrh, + &sizel, &sizeh); + if (asd == 0) { + SI_ERROR(("Missing descriptor for SW %d\n", i)); + goto error; + } + if ((sizeh != 0) || (sizel != SI_CORE_SIZE)) { + SI_ERROR(("Slave wrapper %d is not 4KB\n", i)); + goto error; + } + if ((nmw == 0) && (i == 0)) + sii->common_info->wrapba[idx] = addrl; + } + + /* Don't record bridges */ + if (br) + continue; + + /* Done with core */ + sii->numcores++; + } + + SI_ERROR(("Reached end of erom without finding END")); + +error: + sii->numcores = 0; + return; +} + +/* This function changes the logical "focus" to the indicated core. + * Return the current core's virtual address. + */ +void * +ai_setcoreidx(si_t *sih, uint coreidx) +{ + si_info_t *sii = SI_INFO(sih); + uint32 addr = sii->common_info->coresba[coreidx]; + uint32 wrap = sii->common_info->wrapba[coreidx]; + void *regs; + + if (coreidx >= sii->numcores) + return (NULL); + + /* + * If the user has provided an interrupt mask enabled function, + * then assert interrupts are disabled before switching the core. + */ + ASSERT((sii->intrsenabled_fn == NULL) || !(*(sii)->intrsenabled_fn)((sii)->intr_arg)); + + switch (BUSTYPE(sih->bustype)) { + case SI_BUS: + /* map new one */ + if (!sii->common_info->regs[coreidx]) { + sii->common_info->regs[coreidx] = REG_MAP(addr, SI_CORE_SIZE); + ASSERT(GOODREGS(sii->common_info->regs[coreidx])); + } + sii->curmap = regs = sii->common_info->regs[coreidx]; + if (!sii->common_info->wrappers[coreidx]) { + sii->common_info->wrappers[coreidx] = REG_MAP(wrap, SI_CORE_SIZE); + ASSERT(GOODREGS(sii->common_info->wrappers[coreidx])); + } + sii->curwrap = sii->common_info->wrappers[coreidx]; + break; + + + case SPI_BUS: + case SDIO_BUS: + sii->curmap = regs = (void *)((uintptr)addr); + sii->curwrap = (void *)((uintptr)wrap); + break; + + case PCMCIA_BUS: + default: + ASSERT(0); + regs = NULL; + break; + } + + sii->curmap = regs; + sii->curidx = coreidx; + + return regs; +} + +/* Return the number of address spaces in current core */ +int +ai_numaddrspaces(si_t *sih) +{ + return 2; +} + +/* Return the address of the nth address space in the current core */ +uint32 +ai_addrspace(si_t *sih, uint asidx) +{ + si_info_t *sii; + uint cidx; + + sii = SI_INFO(sih); + cidx = sii->curidx; + + if (asidx == 0) + return sii->common_info->coresba[cidx]; + else if (asidx == 1) + return sii->common_info->coresba2[cidx]; + else { + SI_ERROR(("%s: Need to parse the erom again to find addr space %d\n", + __FUNCTION__, asidx)); + return 0; + } +} + +/* Return the size of the nth address space in the current core */ +uint32 +ai_addrspacesize(si_t *sih, uint asidx) +{ + si_info_t *sii; + uint cidx; + + sii = SI_INFO(sih); + cidx = sii->curidx; + + if (asidx == 0) + return sii->common_info->coresba_size[cidx]; + else if (asidx == 1) + return sii->common_info->coresba2_size[cidx]; + else { + SI_ERROR(("%s: Need to parse the erom again to find addr space %d\n", + __FUNCTION__, asidx)); + return 0; + } +} + +uint +ai_flag(si_t *sih) +{ + si_info_t *sii; + aidmp_t *ai; + + sii = SI_INFO(sih); + ai = sii->curwrap; + + return (R_REG(sii->osh, &ai->oobselouta30) & 0x1f); +} + +void +ai_setint(si_t *sih, int siflag) +{ +} + +void +ai_write_wrap_reg(si_t *sih, uint32 offset, uint32 val) +{ + si_info_t *sii = SI_INFO(sih); + aidmp_t *ai = sii->curwrap; + W_REG(sii->osh, (uint32 *)((uint8 *)ai+offset), val); + return; +} + +uint +ai_corevendor(si_t *sih) +{ + si_info_t *sii; + uint32 cia; + + sii = SI_INFO(sih); + cia = sii->common_info->cia[sii->curidx]; + return ((cia & CIA_MFG_MASK) >> CIA_MFG_SHIFT); +} + +uint +ai_corerev(si_t *sih) +{ + si_info_t *sii; + uint32 cib; + + sii = SI_INFO(sih); + cib = sii->common_info->cib[sii->curidx]; + return ((cib & CIB_REV_MASK) >> CIB_REV_SHIFT); +} + +bool +ai_iscoreup(si_t *sih) +{ + si_info_t *sii; + aidmp_t *ai; + + sii = SI_INFO(sih); + ai = sii->curwrap; + + return (((R_REG(sii->osh, &ai->ioctrl) & (SICF_FGC | SICF_CLOCK_EN)) == SICF_CLOCK_EN) && + ((R_REG(sii->osh, &ai->resetctrl) & AIRC_RESET) == 0)); +} + +/* + * Switch to 'coreidx', issue a single arbitrary 32bit register mask&set operation, + * switch back to the original core, and return the new value. + * + * When using the silicon backplane, no fidleing with interrupts or core switches are needed. + * + * Also, when using pci/pcie, we can optimize away the core switching for pci registers + * and (on newer pci cores) chipcommon registers. + */ +uint +ai_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val) +{ + uint origidx = 0; + uint32 *r = NULL; + uint w; + uint intr_val = 0; + bool fast = FALSE; + si_info_t *sii; + + sii = SI_INFO(sih); + + ASSERT(GOODIDX(coreidx)); + ASSERT(regoff < SI_CORE_SIZE); + ASSERT((val & ~mask) == 0); + + if (coreidx >= SI_MAXCORES) + return 0; + + if (BUSTYPE(sih->bustype) == SI_BUS) { + /* If internal bus, we can always get at everything */ + fast = TRUE; + /* map if does not exist */ + if (!sii->common_info->wrappers[coreidx]) { + sii->common_info->regs[coreidx] = + REG_MAP(sii->common_info->coresba[coreidx], SI_CORE_SIZE); + ASSERT(GOODREGS(sii->common_info->regs[coreidx])); + } + r = (uint32 *)((uchar *)sii->common_info->regs[coreidx] + regoff); + } else if (BUSTYPE(sih->bustype) == PCI_BUS) { + /* If pci/pcie, we can get at pci/pcie regs and on newer cores to chipc */ + + if ((sii->common_info->coreid[coreidx] == CC_CORE_ID) && SI_FAST(sii)) { + /* Chipc registers are mapped at 12KB */ + + fast = TRUE; + r = (uint32 *)((char *)sii->curmap + PCI_16KB0_CCREGS_OFFSET + regoff); + } else if (sii->pub.buscoreidx == coreidx) { + /* pci registers are at either in the last 2KB of an 8KB window + * or, in pcie and pci rev 13 at 8KB + */ + fast = TRUE; + if (SI_FAST(sii)) + r = (uint32 *)((char *)sii->curmap + + PCI_16KB0_PCIREGS_OFFSET + regoff); + else + r = (uint32 *)((char *)sii->curmap + + ((regoff >= SBCONFIGOFF) ? + PCI_BAR0_PCISBR_OFFSET : PCI_BAR0_PCIREGS_OFFSET) + + regoff); + } + } + + if (!fast) { + INTR_OFF(sii, intr_val); + + /* save current core index */ + origidx = si_coreidx(&sii->pub); + + /* switch core */ + r = (uint32*) ((uchar*) ai_setcoreidx(&sii->pub, coreidx) + regoff); + } + ASSERT(r != NULL); + + /* mask and set */ + if (mask || val) { + w = (R_REG(sii->osh, r) & ~mask) | val; + W_REG(sii->osh, r, w); + } + + /* readback */ + w = R_REG(sii->osh, r); + + if (!fast) { + /* restore core index */ + if (origidx != coreidx) + ai_setcoreidx(&sii->pub, origidx); + + INTR_RESTORE(sii, intr_val); + } + + return (w); +} + +void +ai_core_disable(si_t *sih, uint32 bits) +{ + si_info_t *sii; + volatile uint32 dummy; + aidmp_t *ai; + + sii = SI_INFO(sih); + + ASSERT(GOODREGS(sii->curwrap)); + ai = sii->curwrap; + + /* if core is already in reset, just return */ + if (R_REG(sii->osh, &ai->resetctrl) & AIRC_RESET) + return; + + W_REG(sii->osh, &ai->ioctrl, bits); + dummy = R_REG(sii->osh, &ai->ioctrl); + OSL_DELAY(10); + + W_REG(sii->osh, &ai->resetctrl, AIRC_RESET); + OSL_DELAY(1); +} + +/* reset and re-enable a core + * inputs: + * bits - core specific bits that are set during and after reset sequence + * resetbits - core specific bits that are set only during reset sequence + */ +void +ai_core_reset(si_t *sih, uint32 bits, uint32 resetbits) +{ + si_info_t *sii; + aidmp_t *ai; + volatile uint32 dummy; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curwrap)); + ai = sii->curwrap; + + /* + * Must do the disable sequence first to work for arbitrary current core state. + */ + ai_core_disable(sih, (bits | resetbits)); + + /* + * Now do the initialization sequence. + */ + W_REG(sii->osh, &ai->ioctrl, (bits | SICF_FGC | SICF_CLOCK_EN)); + dummy = R_REG(sii->osh, &ai->ioctrl); + W_REG(sii->osh, &ai->resetctrl, 0); + OSL_DELAY(1); + + W_REG(sii->osh, &ai->ioctrl, (bits | SICF_CLOCK_EN)); + dummy = R_REG(sii->osh, &ai->ioctrl); + OSL_DELAY(1); +} + + +void +ai_core_cflags_wo(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + aidmp_t *ai; + uint32 w; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curwrap)); + ai = sii->curwrap; + + ASSERT((val & ~mask) == 0); + + if (mask || val) { + w = ((R_REG(sii->osh, &ai->ioctrl) & ~mask) | val); + W_REG(sii->osh, &ai->ioctrl, w); + } +} + +uint32 +ai_core_cflags(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + aidmp_t *ai; + uint32 w; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curwrap)); + ai = sii->curwrap; + + ASSERT((val & ~mask) == 0); + + if (mask || val) { + w = ((R_REG(sii->osh, &ai->ioctrl) & ~mask) | val); + W_REG(sii->osh, &ai->ioctrl, w); + } + + return R_REG(sii->osh, &ai->ioctrl); +} + +uint32 +ai_core_sflags(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + aidmp_t *ai; + uint32 w; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curwrap)); + ai = sii->curwrap; + + ASSERT((val & ~mask) == 0); + ASSERT((mask & ~SISF_CORE_BITS) == 0); + + if (mask || val) { + w = ((R_REG(sii->osh, &ai->iostatus) & ~mask) | val); + W_REG(sii->osh, &ai->iostatus, w); + } + + return R_REG(sii->osh, &ai->iostatus); +} diff --git a/drivers/net/wireless/bcm4329/bcmpcispi.c b/drivers/net/wireless/bcm4329/bcmpcispi.c new file mode 100644 index 0000000000000000000000000000000000000000..1a8b6717f9244028e9f6e86ad5aa95dd2a845174 --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmpcispi.c @@ -0,0 +1,630 @@ +/* + * Broadcom SPI over PCI-SPI Host Controller, low-level hardware driver + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmpcispi.c,v 1.22.2.4.4.5.6.1 2010/08/13 00:26:05 Exp $ + */ + +#include +#include + +#include /* SDIO Specs */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* to get msglevel bit values */ + +#include +#include +#include +#include /* BRCM PCI-SPI Host Controller Register definitions */ + + +/* ndis_osl.h needs to do a runtime check of the osh to map + * R_REG/W_REG to bus specific access similar to linux_osl.h. + * Until then... + */ +/* linux */ + +#define SPIPCI_RREG R_REG +#define SPIPCI_WREG W_REG + + +#define SPIPCI_ANDREG(osh, r, v) SPIPCI_WREG(osh, (r), (SPIPCI_RREG(osh, r) & (v))) +#define SPIPCI_ORREG(osh, r, v) SPIPCI_WREG(osh, (r), (SPIPCI_RREG(osh, r) | (v))) + + +int bcmpcispi_dump = 0; /* Set to dump complete trace of all SPI bus transactions */ + +typedef struct spih_info_ { + uint bar0; /* BAR0 of PCI Card */ + uint bar1; /* BAR1 of PCI Card */ + osl_t *osh; /* osh handle */ + spih_pciregs_t *pciregs; /* PCI Core Registers */ + spih_regs_t *regs; /* SPI Controller Registers */ + uint8 rev; /* PCI Card Revision ID */ +} spih_info_t; + + +/* Attach to PCI-SPI Host Controller Hardware */ +bool +spi_hw_attach(sdioh_info_t *sd) +{ + osl_t *osh; + spih_info_t *si; + + sd_trace(("%s: enter\n", __FUNCTION__)); + + osh = sd->osh; + + if ((si = (spih_info_t *)MALLOC(osh, sizeof(spih_info_t))) == NULL) { + sd_err(("%s: out of memory, malloced %d bytes\n", __FUNCTION__, MALLOCED(osh))); + return FALSE; + } + + bzero(si, sizeof(spih_info_t)); + + sd->controller = si; + + si->osh = sd->osh; + si->rev = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_REV, 4) & 0xFF; + + if (si->rev < 3) { + sd_err(("Host controller %d not supported, please upgrade to rev >= 3\n", si->rev)); + MFREE(osh, si, sizeof(spih_info_t)); + return (FALSE); + } + + sd_err(("Attaching to Generic PCI SPI Host Controller Rev %d\n", si->rev)); + + /* FPGA Revision < 3 not supported by driver anymore. */ + ASSERT(si->rev >= 3); + + si->bar0 = sd->bar0; + + /* Rev < 10 PciSpiHost has 2 BARs: + * BAR0 = PCI Core Registers + * BAR1 = PciSpiHost Registers (all other cores on backplane) + * + * Rev 10 and up use a different PCI core which only has a single + * BAR0 which contains the PciSpiHost Registers. + */ + if (si->rev < 10) { + si->pciregs = (spih_pciregs_t *)spi_reg_map(osh, + (uintptr)si->bar0, + sizeof(spih_pciregs_t)); + sd_err(("Mapped PCI Core regs to BAR0 at %p\n", si->pciregs)); + + si->bar1 = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_BAR1, 4); + si->regs = (spih_regs_t *)spi_reg_map(osh, + (uintptr)si->bar1, + sizeof(spih_regs_t)); + sd_err(("Mapped SPI Controller regs to BAR1 at %p\n", si->regs)); + } else { + si->regs = (spih_regs_t *)spi_reg_map(osh, + (uintptr)si->bar0, + sizeof(spih_regs_t)); + sd_err(("Mapped SPI Controller regs to BAR0 at %p\n", si->regs)); + si->pciregs = NULL; + } + /* Enable SPI Controller, 16.67MHz SPI Clock */ + SPIPCI_WREG(osh, &si->regs->spih_ctrl, 0x000000d1); + + /* Set extended feature register to defaults */ + SPIPCI_WREG(osh, &si->regs->spih_ext, 0x00000000); + + /* Set GPIO CS# High (de-asserted) */ + SPIPCI_WREG(osh, &si->regs->spih_gpio_data, SPIH_CS); + + /* set GPIO[0] to output for CS# */ + /* set GPIO[1] to output for power control */ + /* set GPIO[2] to input for card detect */ + SPIPCI_WREG(osh, &si->regs->spih_gpio_ctrl, (SPIH_CS | SPIH_SLOT_POWER)); + + /* Clear out the Read FIFO in case there is any stuff left in there from a previous run. */ + while ((SPIPCI_RREG(osh, &si->regs->spih_stat) & SPIH_RFEMPTY) == 0) { + SPIPCI_RREG(osh, &si->regs->spih_data); + } + + /* Wait for power to stabilize to the SDIO Card (100msec was insufficient) */ + OSL_DELAY(250000); + + /* Check card detect on FPGA Revision >= 4 */ + if (si->rev >= 4) { + if (SPIPCI_RREG(osh, &si->regs->spih_gpio_data) & SPIH_CARD_DETECT) { + sd_err(("%s: no card detected in SD slot\n", __FUNCTION__)); + spi_reg_unmap(osh, (uintptr)si->regs, sizeof(spih_regs_t)); + if (si->pciregs) { + spi_reg_unmap(osh, (uintptr)si->pciregs, sizeof(spih_pciregs_t)); + } + MFREE(osh, si, sizeof(spih_info_t)); + return FALSE; + } + } + + /* Interrupts are level sensitive */ + SPIPCI_WREG(osh, &si->regs->spih_int_edge, 0x80000000); + + /* Interrupts are active low. */ + SPIPCI_WREG(osh, &si->regs->spih_int_pol, 0x40000004); + + /* Enable interrupts through PCI Core. */ + if (si->pciregs) { + SPIPCI_WREG(osh, &si->pciregs->ICR, PCI_INT_PROP_EN); + } + + sd_trace(("%s: exit\n", __FUNCTION__)); + return TRUE; +} + +/* Detach and return PCI-SPI Hardware to unconfigured state */ +bool +spi_hw_detach(sdioh_info_t *sd) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + spih_pciregs_t *pciregs = si->pciregs; + + sd_trace(("%s: enter\n", __FUNCTION__)); + + SPIPCI_WREG(osh, ®s->spih_ctrl, 0x00000010); + SPIPCI_WREG(osh, ®s->spih_gpio_ctrl, 0x00000000); /* Disable GPIO for CS# */ + SPIPCI_WREG(osh, ®s->spih_int_mask, 0x00000000); /* Clear Intmask */ + SPIPCI_WREG(osh, ®s->spih_hex_disp, 0x0000DEAF); + SPIPCI_WREG(osh, ®s->spih_int_edge, 0x00000000); + SPIPCI_WREG(osh, ®s->spih_int_pol, 0x00000000); + SPIPCI_WREG(osh, ®s->spih_hex_disp, 0x0000DEAD); + + /* Disable interrupts through PCI Core. */ + if (si->pciregs) { + SPIPCI_WREG(osh, &pciregs->ICR, 0x00000000); + spi_reg_unmap(osh, (uintptr)pciregs, sizeof(spih_pciregs_t)); + } + spi_reg_unmap(osh, (uintptr)regs, sizeof(spih_regs_t)); + + MFREE(osh, si, sizeof(spih_info_t)); + + sd->controller = NULL; + + sd_trace(("%s: exit\n", __FUNCTION__)); + return TRUE; +} + +/* Switch between internal (PCI) and external clock oscillator */ +static bool +sdspi_switch_clock(sdioh_info_t *sd, bool ext_clk) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + + /* Switch to desired clock, and reset the PLL. */ + SPIPCI_WREG(osh, ®s->spih_pll_ctrl, ext_clk ? SPIH_EXT_CLK : 0); + + SPINWAIT(((SPIPCI_RREG(osh, ®s->spih_pll_status) & SPIH_PLL_LOCKED) + != SPIH_PLL_LOCKED), 1000); + if ((SPIPCI_RREG(osh, ®s->spih_pll_status) & SPIH_PLL_LOCKED) != SPIH_PLL_LOCKED) { + sd_err(("%s: timeout waiting for PLL to lock\n", __FUNCTION__)); + return (FALSE); + } + return (TRUE); + +} + +/* Configure PCI-SPI Host Controller's SPI Clock rate as a divisor into the + * base clock rate. The base clock is either the PCI Clock (33MHz) or the + * external clock oscillator at U17 on the PciSpiHost. + */ +bool +spi_start_clock(sdioh_info_t *sd, uint16 div) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + uint32 t, espr, disp; + uint32 disp_xtal_freq; + bool ext_clock = FALSE; + char disp_string[5]; + + if (div > 2048) { + sd_err(("%s: divisor %d too large; using max of 2048\n", __FUNCTION__, div)); + div = 2048; + } else if (div & (div - 1)) { /* Not a power of 2? */ + /* Round up to a power of 2 */ + while ((div + 1) & div) + div |= div >> 1; + div++; + } + + /* For FPGA Rev >= 5, the use of an external clock oscillator is supported. + * If the oscillator is populated, use it to provide the SPI base clock, + * otherwise, default to the PCI clock as the SPI base clock. + */ + if (si->rev >= 5) { + uint32 clk_tick; + /* Enable the External Clock Oscillator as PLL clock source. */ + if (!sdspi_switch_clock(sd, TRUE)) { + sd_err(("%s: error switching to external clock\n", __FUNCTION__)); + } + + /* Check to make sure the external clock is running. If not, then it + * is not populated on the card, so we will default to the PCI clock. + */ + clk_tick = SPIPCI_RREG(osh, ®s->spih_clk_count); + if (clk_tick == SPIPCI_RREG(osh, ®s->spih_clk_count)) { + + /* Switch back to the PCI clock as the clock source. */ + if (!sdspi_switch_clock(sd, FALSE)) { + sd_err(("%s: error switching to external clock\n", __FUNCTION__)); + } + } else { + ext_clock = TRUE; + } + } + + /* Hack to allow hot-swapping oscillators: + * 1. Force PCI clock as clock source, using sd_divisor of 0. + * 2. Swap oscillator + * 3. Set desired sd_divisor (will switch to external oscillator as clock source. + */ + if (div == 0) { + ext_clock = FALSE; + div = 2; + + /* Select PCI clock as the clock source. */ + if (!sdspi_switch_clock(sd, FALSE)) { + sd_err(("%s: error switching to external clock\n", __FUNCTION__)); + } + + sd_err(("%s: Ok to hot-swap oscillators.\n", __FUNCTION__)); + } + + /* If using the external oscillator, read the clock frequency from the controller + * The value read is in units of 10000Hz, and it's not a nice round number because + * it is calculated by the FPGA. So to make up for that, we round it off. + */ + if (ext_clock == TRUE) { + uint32 xtal_freq; + + OSL_DELAY(1000); + xtal_freq = SPIPCI_RREG(osh, ®s->spih_xtal_freq) * 10000; + + sd_info(("%s: Oscillator is %dHz\n", __FUNCTION__, xtal_freq)); + + + disp_xtal_freq = xtal_freq / 10000; + + /* Round it off to a nice number. */ + if ((disp_xtal_freq % 100) > 50) { + disp_xtal_freq += 100; + } + + disp_xtal_freq = (disp_xtal_freq / 100) * 100; + } else { + sd_err(("%s: no external oscillator installed, using PCI clock.\n", __FUNCTION__)); + disp_xtal_freq = 3333; + } + + /* Convert the SPI Clock frequency to BCD format. */ + sprintf(disp_string, "%04d", disp_xtal_freq / div); + + disp = (disp_string[0] - '0') << 12; + disp |= (disp_string[1] - '0') << 8; + disp |= (disp_string[2] - '0') << 4; + disp |= (disp_string[3] - '0'); + + /* Select the correct ESPR register value based on the divisor. */ + switch (div) { + case 1: espr = 0x0; break; + case 2: espr = 0x1; break; + case 4: espr = 0x2; break; + case 8: espr = 0x5; break; + case 16: espr = 0x3; break; + case 32: espr = 0x4; break; + case 64: espr = 0x6; break; + case 128: espr = 0x7; break; + case 256: espr = 0x8; break; + case 512: espr = 0x9; break; + case 1024: espr = 0xa; break; + case 2048: espr = 0xb; break; + default: espr = 0x0; ASSERT(0); break; + } + + t = SPIPCI_RREG(osh, ®s->spih_ctrl); + t &= ~3; + t |= espr & 3; + SPIPCI_WREG(osh, ®s->spih_ctrl, t); + + t = SPIPCI_RREG(osh, ®s->spih_ext); + t &= ~3; + t |= (espr >> 2) & 3; + SPIPCI_WREG(osh, ®s->spih_ext, t); + + SPIPCI_WREG(osh, ®s->spih_hex_disp, disp); + + /* For Rev 8, writing to the PLL_CTRL register resets + * the PLL, and it can re-acquire in 200uS. For + * Rev 7 and older, we use a software delay to allow + * the PLL to re-acquire, which takes more than 2mS. + */ + if (si->rev < 8) { + /* Wait for clock to settle. */ + OSL_DELAY(5000); + } + + sd_info(("%s: SPI_CTRL=0x%08x SPI_EXT=0x%08x\n", + __FUNCTION__, + SPIPCI_RREG(osh, ®s->spih_ctrl), + SPIPCI_RREG(osh, ®s->spih_ext))); + + return TRUE; +} + +/* Configure PCI-SPI Host Controller High-Speed Clocking mode setting */ +bool +spi_controller_highspeed_mode(sdioh_info_t *sd, bool hsmode) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + + if (si->rev >= 10) { + if (hsmode) { + SPIPCI_ORREG(osh, ®s->spih_ext, 0x10); + } else { + SPIPCI_ANDREG(osh, ®s->spih_ext, ~0x10); + } + } + + return TRUE; +} + +/* Disable device interrupt */ +void +spi_devintr_off(sdioh_info_t *sd) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + + sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); + if (sd->use_client_ints) { + sd->intmask &= ~SPIH_DEV_INTR; + SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); /* Clear Intmask */ + } +} + +/* Enable device interrupt */ +void +spi_devintr_on(sdioh_info_t *sd) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + + ASSERT(sd->lockcount == 0); + sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); + if (sd->use_client_ints) { + if (SPIPCI_RREG(osh, ®s->spih_ctrl) & 0x02) { + /* Ack in case one was pending but is no longer... */ + SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_DEV_INTR); + } + sd->intmask |= SPIH_DEV_INTR; + /* Set device intr in Intmask */ + SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); + } +} + +/* Check to see if an interrupt belongs to the PCI-SPI Host or a SPI Device */ +bool +spi_check_client_intr(sdioh_info_t *sd, int *is_dev_intr) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + bool ours = FALSE; + + uint32 raw_int, cur_int; + ASSERT(sd); + + if (is_dev_intr) + *is_dev_intr = FALSE; + raw_int = SPIPCI_RREG(osh, ®s->spih_int_status); + cur_int = raw_int & sd->intmask; + if (cur_int & SPIH_DEV_INTR) { + if (sd->client_intr_enabled && sd->use_client_ints) { + sd->intrcount++; + ASSERT(sd->intr_handler); + ASSERT(sd->intr_handler_arg); + (sd->intr_handler)(sd->intr_handler_arg); + if (is_dev_intr) + *is_dev_intr = TRUE; + } else { + sd_trace(("%s: Not ready for intr: enabled %d, handler 0x%p\n", + __FUNCTION__, sd->client_intr_enabled, sd->intr_handler)); + } + SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_DEV_INTR); + SPIPCI_RREG(osh, ®s->spih_int_status); + ours = TRUE; + } else if (cur_int & SPIH_CTLR_INTR) { + /* Interrupt is from SPI FIFO... just clear and ack it... */ + sd_trace(("%s: SPI CTLR interrupt: raw_int 0x%08x cur_int 0x%08x\n", + __FUNCTION__, raw_int, cur_int)); + + /* Clear the interrupt in the SPI_STAT register */ + SPIPCI_WREG(osh, ®s->spih_stat, 0x00000080); + + /* Ack the interrupt in the interrupt controller */ + SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_CTLR_INTR); + SPIPCI_RREG(osh, ®s->spih_int_status); + + ours = TRUE; + } else if (cur_int & SPIH_WFIFO_INTR) { + sd_trace(("%s: SPI WR FIFO Empty interrupt: raw_int 0x%08x cur_int 0x%08x\n", + __FUNCTION__, raw_int, cur_int)); + + /* Disable the FIFO Empty Interrupt */ + sd->intmask &= ~SPIH_WFIFO_INTR; + SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); + + sd->local_intrcount++; + sd->got_hcint = TRUE; + ours = TRUE; + } else { + /* Not an error: can share interrupts... */ + sd_trace(("%s: Not my interrupt: raw_int 0x%08x cur_int 0x%08x\n", + __FUNCTION__, raw_int, cur_int)); + ours = FALSE; + } + + return ours; +} + +static void +hexdump(char *pfx, unsigned char *msg, int msglen) +{ + int i, col; + char buf[80]; + + ASSERT(strlen(pfx) + 49 <= sizeof(buf)); + + col = 0; + + for (i = 0; i < msglen; i++, col++) { + if (col % 16 == 0) + strcpy(buf, pfx); + sprintf(buf + strlen(buf), "%02x", msg[i]); + if ((col + 1) % 16 == 0) + printf("%s\n", buf); + else + sprintf(buf + strlen(buf), " "); + } + + if (col % 16 != 0) + printf("%s\n", buf); +} + +/* Send/Receive an SPI Packet */ +void +spi_sendrecv(sdioh_info_t *sd, uint8 *msg_out, uint8 *msg_in, int msglen) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + uint32 count; + uint32 spi_data_out; + uint32 spi_data_in; + bool yield; + + sd_trace(("%s: enter\n", __FUNCTION__)); + + if (bcmpcispi_dump) { + printf("SENDRECV(len=%d)\n", msglen); + hexdump(" OUT: ", msg_out, msglen); + } + +#ifdef BCMSDYIELD + /* Only yield the CPU and wait for interrupt on Rev 8 and newer FPGA images. */ + yield = ((msglen > 500) && (si->rev >= 8)); +#else + yield = FALSE; +#endif /* BCMSDYIELD */ + + ASSERT(msglen % 4 == 0); + + + SPIPCI_ANDREG(osh, ®s->spih_gpio_data, ~SPIH_CS); /* Set GPIO CS# Low (asserted) */ + + for (count = 0; count < (uint32)msglen/4; count++) { + spi_data_out = ((uint32)((uint32 *)msg_out)[count]); + SPIPCI_WREG(osh, ®s->spih_data, spi_data_out); + } + +#ifdef BCMSDYIELD + if (yield) { + /* Ack the interrupt in the interrupt controller */ + SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_WFIFO_INTR); + SPIPCI_RREG(osh, ®s->spih_int_status); + + /* Enable the FIFO Empty Interrupt */ + sd->intmask |= SPIH_WFIFO_INTR; + sd->got_hcint = FALSE; + SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); + + } +#endif /* BCMSDYIELD */ + + /* Wait for write fifo to empty... */ + SPIPCI_ANDREG(osh, ®s->spih_gpio_data, ~0x00000020); /* Set GPIO 5 Low */ + + if (yield) { + ASSERT((SPIPCI_RREG(sd->osh, ®s->spih_stat) & SPIH_WFEMPTY) == 0); + } + + spi_waitbits(sd, yield); + SPIPCI_ORREG(osh, ®s->spih_gpio_data, 0x00000020); /* Set GPIO 5 High (de-asserted) */ + + for (count = 0; count < (uint32)msglen/4; count++) { + spi_data_in = SPIPCI_RREG(osh, ®s->spih_data); + ((uint32 *)msg_in)[count] = spi_data_in; + } + + /* Set GPIO CS# High (de-asserted) */ + SPIPCI_ORREG(osh, ®s->spih_gpio_data, SPIH_CS); + + if (bcmpcispi_dump) { + hexdump(" IN : ", msg_in, msglen); + } +} + +void +spi_spinbits(sdioh_info_t *sd) +{ + spih_info_t *si = (spih_info_t *)sd->controller; + osl_t *osh = si->osh; + spih_regs_t *regs = si->regs; + uint spin_count; /* Spin loop bound check */ + + spin_count = 0; + while ((SPIPCI_RREG(sd->osh, ®s->spih_stat) & SPIH_WFEMPTY) == 0) { + if (spin_count > SPI_SPIN_BOUND) { + sd_err(("%s: SPIH_WFEMPTY spin bits out of bound %u times \n", + __FUNCTION__, spin_count)); + ASSERT(FALSE); + } + spin_count++; + } + + /* Wait for SPI Transfer state machine to return to IDLE state. + * The state bits are only implemented in Rev >= 5 FPGA. These + * bits are hardwired to 00 for Rev < 5, so this check doesn't cause + * any problems. + */ + spin_count = 0; + while ((SPIPCI_RREG(osh, ®s->spih_stat) & SPIH_STATE_MASK) != 0) { + if (spin_count > SPI_SPIN_BOUND) { + sd_err(("%s: SPIH_STATE_MASK spin bits out of bound %u times \n", + __FUNCTION__, spin_count)); + ASSERT(FALSE); + } + spin_count++; + } +} diff --git a/drivers/net/wireless/bcm4329/bcmsdh.c b/drivers/net/wireless/bcm4329/bcmsdh.c new file mode 100644 index 0000000000000000000000000000000000000000..4bf5889e5a68e5a240f1205e7ea7ea268943b244 --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdh.c @@ -0,0 +1,652 @@ +/* + * BCMSDH interface glue + * implement bcmsdh API for SDIOH driver + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdh.c,v 1.35.2.1.4.8.6.13 2010/04/06 03:26:57 Exp $ + */ +/* ****************** BCMSDH Interface Functions *************************** */ + +#include +#include +#include +#include +#include +#include +#include + +#include /* BRCM API for SDIO clients (such as wl, dhd) */ +#include /* common SDIO/controller interface */ +#include /* BRCM sdio device core */ + +#include /* sdio spec */ + +#define SDIOH_API_ACCESS_RETRY_LIMIT 2 +const uint bcmsdh_msglevel = BCMSDH_ERROR_VAL; + + +struct bcmsdh_info +{ + bool init_success; /* underlying driver successfully attached */ + void *sdioh; /* handler for sdioh */ + uint32 vendevid; /* Target Vendor and Device ID on SD bus */ + osl_t *osh; + bool regfail; /* Save status of last reg_read/reg_write call */ + uint32 sbwad; /* Save backplane window address */ +}; +/* local copy of bcm sd handler */ +bcmsdh_info_t * l_bcmsdh = NULL; + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) +extern int +sdioh_enable_hw_oob_intr(void *sdioh, bool enable); + +void +bcmsdh_enable_hw_oob_intr(bcmsdh_info_t *sdh, bool enable) +{ + sdioh_enable_hw_oob_intr(sdh->sdioh, enable); +} +#endif + +bcmsdh_info_t * +bcmsdh_attach(osl_t *osh, void *cfghdl, void **regsva, uint irq) +{ + bcmsdh_info_t *bcmsdh; + + if ((bcmsdh = (bcmsdh_info_t *)MALLOC(osh, sizeof(bcmsdh_info_t))) == NULL) { + BCMSDH_ERROR(("bcmsdh_attach: out of memory, malloced %d bytes\n", MALLOCED(osh))); + return NULL; + } + bzero((char *)bcmsdh, sizeof(bcmsdh_info_t)); + + /* save the handler locally */ + l_bcmsdh = bcmsdh; + + if (!(bcmsdh->sdioh = sdioh_attach(osh, cfghdl, irq))) { + bcmsdh_detach(osh, bcmsdh); + return NULL; + } + + bcmsdh->osh = osh; + bcmsdh->init_success = TRUE; + + *regsva = (uint32 *)SI_ENUM_BASE; + + /* Report the BAR, to fix if needed */ + bcmsdh->sbwad = SI_ENUM_BASE; + return bcmsdh; +} + +int +bcmsdh_detach(osl_t *osh, void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + if (bcmsdh != NULL) { + if (bcmsdh->sdioh) { + sdioh_detach(osh, bcmsdh->sdioh); + bcmsdh->sdioh = NULL; + } + MFREE(osh, bcmsdh, sizeof(bcmsdh_info_t)); + } + + l_bcmsdh = NULL; + return 0; +} + +int +bcmsdh_iovar_op(void *sdh, const char *name, + void *params, int plen, void *arg, int len, bool set) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + return sdioh_iovar_op(bcmsdh->sdioh, name, params, plen, arg, len, set); +} + +bool +bcmsdh_intr_query(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + bool on; + + ASSERT(bcmsdh); + status = sdioh_interrupt_query(bcmsdh->sdioh, &on); + if (SDIOH_API_SUCCESS(status)) + return FALSE; + else + return on; +} + +int +bcmsdh_intr_enable(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + ASSERT(bcmsdh); + + status = sdioh_interrupt_set(bcmsdh->sdioh, TRUE); + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + +int +bcmsdh_intr_disable(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + ASSERT(bcmsdh); + + status = sdioh_interrupt_set(bcmsdh->sdioh, FALSE); + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + +int +bcmsdh_intr_reg(void *sdh, bcmsdh_cb_fn_t fn, void *argh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + ASSERT(bcmsdh); + + status = sdioh_interrupt_register(bcmsdh->sdioh, fn, argh); + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + +int +bcmsdh_intr_dereg(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + ASSERT(bcmsdh); + + status = sdioh_interrupt_deregister(bcmsdh->sdioh); + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + +#if defined(DHD_DEBUG) +bool +bcmsdh_intr_pending(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + ASSERT(sdh); + return sdioh_interrupt_pending(bcmsdh->sdioh); +} +#endif + + +int +bcmsdh_devremove_reg(void *sdh, bcmsdh_cb_fn_t fn, void *argh) +{ + ASSERT(sdh); + + /* don't support yet */ + return BCME_UNSUPPORTED; +} + +uint8 +bcmsdh_cfg_read(void *sdh, uint fnc_num, uint32 addr, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + int32 retry = 0; +#endif + uint8 data = 0; + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + do { + if (retry) /* wait for 1 ms till bus get settled down */ + OSL_DELAY(1000); +#endif + status = sdioh_cfg_read(bcmsdh->sdioh, fnc_num, addr, (uint8 *)&data); +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + } while (!SDIOH_API_SUCCESS(status) && (retry++ < SDIOH_API_ACCESS_RETRY_LIMIT)); +#endif + if (err) + *err = (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR); + + BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, uint8data = 0x%x\n", __FUNCTION__, + fnc_num, addr, data)); + + return data; +} + +void +bcmsdh_cfg_write(void *sdh, uint fnc_num, uint32 addr, uint8 data, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + int32 retry = 0; +#endif + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + do { + if (retry) /* wait for 1 ms till bus get settled down */ + OSL_DELAY(1000); +#endif + status = sdioh_cfg_write(bcmsdh->sdioh, fnc_num, addr, (uint8 *)&data); +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + } while (!SDIOH_API_SUCCESS(status) && (retry++ < SDIOH_API_ACCESS_RETRY_LIMIT)); +#endif + if (err) + *err = SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR; + + BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, uint8data = 0x%x\n", __FUNCTION__, + fnc_num, addr, data)); +} + +uint32 +bcmsdh_cfg_read_word(void *sdh, uint fnc_num, uint32 addr, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + uint32 data = 0; + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + + status = sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL, SDIOH_READ, fnc_num, + addr, &data, 4); + + if (err) + *err = (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR); + + BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, uint32data = 0x%x\n", __FUNCTION__, + fnc_num, addr, data)); + + return data; +} + +void +bcmsdh_cfg_write_word(void *sdh, uint fnc_num, uint32 addr, uint32 data, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + + status = sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL, SDIOH_WRITE, fnc_num, + addr, &data, 4); + + if (err) + *err = (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR); + + BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, uint32data = 0x%x\n", __FUNCTION__, fnc_num, + addr, data)); +} + + +int +bcmsdh_cis_read(void *sdh, uint func, uint8 *cis, uint length) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + + uint8 *tmp_buf, *tmp_ptr; + uint8 *ptr; + bool ascii = func & ~0xf; + func &= 0x7; + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + ASSERT(cis); + ASSERT(length <= SBSDIO_CIS_SIZE_LIMIT); + + status = sdioh_cis_read(bcmsdh->sdioh, func, cis, length); + + if (ascii) { + /* Move binary bits to tmp and format them into the provided buffer. */ + if ((tmp_buf = (uint8 *)MALLOC(bcmsdh->osh, length)) == NULL) { + BCMSDH_ERROR(("%s: out of memory\n", __FUNCTION__)); + return BCME_NOMEM; + } + bcopy(cis, tmp_buf, length); + for (tmp_ptr = tmp_buf, ptr = cis; ptr < (cis + length - 4); tmp_ptr++) { + ptr += sprintf((char*)ptr, "%.2x ", *tmp_ptr & 0xff); + if ((((tmp_ptr - tmp_buf) + 1) & 0xf) == 0) + ptr += sprintf((char *)ptr, "\n"); + } + MFREE(bcmsdh->osh, tmp_buf, length); + } + + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + + +static int +bcmsdhsdio_set_sbaddr_window(void *sdh, uint32 address) +{ + int err = 0; + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + bcmsdh_cfg_write(bcmsdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW, + (address >> 8) & SBSDIO_SBADDRLOW_MASK, &err); + if (!err) + bcmsdh_cfg_write(bcmsdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRMID, + (address >> 16) & SBSDIO_SBADDRMID_MASK, &err); + if (!err) + bcmsdh_cfg_write(bcmsdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRHIGH, + (address >> 24) & SBSDIO_SBADDRHIGH_MASK, &err); + + + return err; +} + +uint32 +bcmsdh_reg_read(void *sdh, uint32 addr, uint size) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + uint32 word = 0; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + + BCMSDH_INFO(("%s:fun = 1, addr = 0x%x, ", __FUNCTION__, addr)); + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + + if (bar0 != bcmsdh->sbwad) { + if (bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0)) + return 0xFFFFFFFF; + + bcmsdh->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + if (size == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + status = sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL, + SDIOH_READ, SDIO_FUNC_1, addr, &word, size); + + bcmsdh->regfail = !(SDIOH_API_SUCCESS(status)); + + BCMSDH_INFO(("uint32data = 0x%x\n", word)); + + /* if ok, return appropriately masked word */ + if (SDIOH_API_SUCCESS(status)) { + switch (size) { + case sizeof(uint8): + return (word & 0xff); + case sizeof(uint16): + return (word & 0xffff); + case sizeof(uint32): + return word; + default: + bcmsdh->regfail = TRUE; + + } + } + + /* otherwise, bad sdio access or invalid size */ + BCMSDH_ERROR(("%s: error reading addr 0x%04x size %d\n", __FUNCTION__, addr, size)); + return 0xFFFFFFFF; +} + +uint32 +bcmsdh_reg_write(void *sdh, uint32 addr, uint size, uint32 data) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + BCMSDH_INFO(("%s:fun = 1, addr = 0x%x, uint%ddata = 0x%x\n", + __FUNCTION__, addr, size*8, data)); + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + ASSERT(bcmsdh->init_success); + + if (bar0 != bcmsdh->sbwad) { + if ((err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))) + return err; + + bcmsdh->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + if (size == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + status = sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL, SDIOH_WRITE, SDIO_FUNC_1, + addr, &data, size); + bcmsdh->regfail = !(SDIOH_API_SUCCESS(status)); + + if (SDIOH_API_SUCCESS(status)) + return 0; + + BCMSDH_ERROR(("%s: error writing 0x%08x to addr 0x%04x size %d\n", + __FUNCTION__, data, addr, size)); + return 0xFFFFFFFF; +} + +bool +bcmsdh_regfail(void *sdh) +{ + return ((bcmsdh_info_t *)sdh)->regfail; +} + +int +bcmsdh_recv_buf(void *sdh, uint32 addr, uint fn, uint flags, + uint8 *buf, uint nbytes, void *pkt, + bcmsdh_cmplt_fn_t complete, void *handle) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + uint incr_fix; + uint width; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + ASSERT(bcmsdh); + ASSERT(bcmsdh->init_success); + + BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, size = %d\n", + __FUNCTION__, fn, addr, nbytes)); + + /* Async not implemented yet */ + ASSERT(!(flags & SDIO_REQ_ASYNC)); + if (flags & SDIO_REQ_ASYNC) + return BCME_UNSUPPORTED; + + if (bar0 != bcmsdh->sbwad) { + if ((err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))) + return err; + + bcmsdh->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + if (width == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + status = sdioh_request_buffer(bcmsdh->sdioh, SDIOH_DATA_PIO, incr_fix, + SDIOH_READ, fn, addr, width, nbytes, buf, pkt); + + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR); +} + +int +bcmsdh_send_buf(void *sdh, uint32 addr, uint fn, uint flags, + uint8 *buf, uint nbytes, void *pkt, + bcmsdh_cmplt_fn_t complete, void *handle) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + uint incr_fix; + uint width; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + ASSERT(bcmsdh); + ASSERT(bcmsdh->init_success); + + BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, size = %d\n", + __FUNCTION__, fn, addr, nbytes)); + + /* Async not implemented yet */ + ASSERT(!(flags & SDIO_REQ_ASYNC)); + if (flags & SDIO_REQ_ASYNC) + return BCME_UNSUPPORTED; + + if (bar0 != bcmsdh->sbwad) { + if ((err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))) + return err; + + bcmsdh->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + if (width == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + status = sdioh_request_buffer(bcmsdh->sdioh, SDIOH_DATA_PIO, incr_fix, + SDIOH_WRITE, fn, addr, width, nbytes, buf, pkt); + + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + +int +bcmsdh_rwdata(void *sdh, uint rw, uint32 addr, uint8 *buf, uint nbytes) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + SDIOH_API_RC status; + + ASSERT(bcmsdh); + ASSERT(bcmsdh->init_success); + ASSERT((addr & SBSDIO_SBWINDOW_MASK) == 0); + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + status = sdioh_request_buffer(bcmsdh->sdioh, SDIOH_DATA_PIO, SDIOH_DATA_INC, + (rw ? SDIOH_WRITE : SDIOH_READ), SDIO_FUNC_1, + addr, 4, nbytes, buf, NULL); + + return (SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR); +} + +int +bcmsdh_abort(void *sdh, uint fn) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + return sdioh_abort(bcmsdh->sdioh, fn); +} + +int +bcmsdh_start(void *sdh, int stage) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + return sdioh_start(bcmsdh->sdioh, stage); +} + +int +bcmsdh_stop(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + return sdioh_stop(bcmsdh->sdioh); +} + + +int +bcmsdh_query_device(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + bcmsdh->vendevid = (VENDOR_BROADCOM << 16) | 0; + return (bcmsdh->vendevid); +} + +uint +bcmsdh_query_iofnum(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + return (sdioh_query_iofnum(bcmsdh->sdioh)); +} + +int +bcmsdh_reset(bcmsdh_info_t *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + return sdioh_sdio_reset(bcmsdh->sdioh); +} + +void *bcmsdh_get_sdioh(bcmsdh_info_t *sdh) +{ + ASSERT(sdh); + return sdh->sdioh; +} + +/* Function to pass device-status bits to DHD. */ +uint32 +bcmsdh_get_dstatus(void *sdh) +{ + return 0; +} +uint32 +bcmsdh_cur_sbwad(void *sdh) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *)sdh; + + if (!bcmsdh) + bcmsdh = l_bcmsdh; + + return (bcmsdh->sbwad); +} + +void +bcmsdh_chipinfo(void *sdh, uint32 chip, uint32 chiprev) +{ + return; +} diff --git a/drivers/net/wireless/bcm4329/bcmsdh_linux.c b/drivers/net/wireless/bcm4329/bcmsdh_linux.c new file mode 100644 index 0000000000000000000000000000000000000000..6d6097b78f7d3018b70ba8da864e5c0bbe8437fd --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdh_linux.c @@ -0,0 +1,735 @@ +/* + * SDIO access interface for drivers - linux specific (pci only) + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdh_linux.c,v 1.42.10.10.2.14.4.2 2010/09/15 00:30:11 Exp $ + */ + +/** + * @file bcmsdh_linux.c + */ + +#define __UNDEF_NO_VERSION__ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#if defined(OOB_INTR_ONLY) +#include +extern void dhdsdio_isr(void * args); +#include +#include +#include +#endif /* defined(OOB_INTR_ONLY) */ +#if defined(CONFIG_MACH_SANDGATE2G) || defined(CONFIG_MACH_LOGICPD_PXA270) +#if !defined(BCMPLATFORM_BUS) +#define BCMPLATFORM_BUS +#endif /* !defined(BCMPLATFORM_BUS) */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)) +#include +#endif /* KERNEL_VERSION(2, 6, 19) */ +#endif /* CONFIG_MACH_SANDGATE2G || CONFIG_MACH_LOGICPD_PXA270 */ + +/** + * SDIO Host Controller info + */ +typedef struct bcmsdh_hc bcmsdh_hc_t; + +struct bcmsdh_hc { + bcmsdh_hc_t *next; +#ifdef BCMPLATFORM_BUS + struct device *dev; /* platform device handle */ +#else + struct pci_dev *dev; /* pci device handle */ +#endif /* BCMPLATFORM_BUS */ + osl_t *osh; + void *regs; /* SDIO Host Controller address */ + bcmsdh_info_t *sdh; /* SDIO Host Controller handle */ + void *ch; + unsigned int oob_irq; + unsigned long oob_flags; /* OOB Host specifiction as edge and etc */ + bool oob_irq_registered; +#if defined(OOB_INTR_ONLY) + spinlock_t irq_lock; +#endif +}; +static bcmsdh_hc_t *sdhcinfo = NULL; + +/* driver info, initialized when bcmsdh_register is called */ +static bcmsdh_driver_t drvinfo = {NULL, NULL}; + +/* debugging macros */ +#define SDLX_MSG(x) + +/** + * Checks to see if vendor and device IDs match a supported SDIO Host Controller. + */ +bool +bcmsdh_chipmatch(uint16 vendor, uint16 device) +{ + /* Add other vendors and devices as required */ + +#ifdef BCMSDIOH_STD + /* Check for Arasan host controller */ + if (vendor == VENDOR_SI_IMAGE) { + return (TRUE); + } + /* Check for BRCM 27XX Standard host controller */ + if (device == BCM27XX_SDIOH_ID && vendor == VENDOR_BROADCOM) { + return (TRUE); + } + /* Check for BRCM Standard host controller */ + if (device == SDIOH_FPGA_ID && vendor == VENDOR_BROADCOM) { + return (TRUE); + } + /* Check for TI PCIxx21 Standard host controller */ + if (device == PCIXX21_SDIOH_ID && vendor == VENDOR_TI) { + return (TRUE); + } + if (device == PCIXX21_SDIOH0_ID && vendor == VENDOR_TI) { + return (TRUE); + } + /* Ricoh R5C822 Standard SDIO Host */ + if (device == R5C822_SDIOH_ID && vendor == VENDOR_RICOH) { + return (TRUE); + } + /* JMicron Standard SDIO Host */ + if (device == JMICRON_SDIOH_ID && vendor == VENDOR_JMICRON) { + return (TRUE); + } + +#endif /* BCMSDIOH_STD */ +#ifdef BCMSDIOH_SPI + /* This is the PciSpiHost. */ + if (device == SPIH_FPGA_ID && vendor == VENDOR_BROADCOM) { + printf("Found PCI SPI Host Controller\n"); + return (TRUE); + } + +#endif /* BCMSDIOH_SPI */ + + return (FALSE); +} + +#if defined(BCMPLATFORM_BUS) +#if defined(BCMLXSDMMC) +/* forward declarations */ +int bcmsdh_probe(struct device *dev); +int bcmsdh_remove(struct device *dev); + +EXPORT_SYMBOL(bcmsdh_probe); +EXPORT_SYMBOL(bcmsdh_remove); + +#else +/* forward declarations */ +static int __devinit bcmsdh_probe(struct device *dev); +static int __devexit bcmsdh_remove(struct device *dev); +#endif /* BCMLXSDMMC */ + +#ifndef BCMLXSDMMC +static struct device_driver bcmsdh_driver = { + .name = "pxa2xx-mci", + .bus = &platform_bus_type, + .probe = bcmsdh_probe, + .remove = bcmsdh_remove, + .suspend = NULL, + .resume = NULL, + }; +#endif /* BCMLXSDMMC */ + +#ifndef BCMLXSDMMC +static +#endif /* BCMLXSDMMC */ +int bcmsdh_probe(struct device *dev) +{ + osl_t *osh = NULL; + bcmsdh_hc_t *sdhc = NULL; + ulong regs = 0; + bcmsdh_info_t *sdh = NULL; +#if !defined(BCMLXSDMMC) && defined(BCMPLATFORM_BUS) + struct platform_device *pdev; + struct resource *r; +#endif /* BCMLXSDMMC */ + int irq = 0; + uint32 vendevid; + unsigned long irq_flags = 0; + +#if !defined(BCMLXSDMMC) && defined(BCMPLATFORM_BUS) + pdev = to_platform_device(dev); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!r || irq == NO_IRQ) + return -ENXIO; +#endif /* BCMLXSDMMC */ + +#if defined(OOB_INTR_ONLY) +#ifdef HW_OOB + irq_flags = \ + IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE; +#else + irq_flags = IRQF_TRIGGER_FALLING; +#endif /* HW_OOB */ + irq = dhd_customer_oob_irq_map(&irq_flags); + if (irq < 0) { + SDLX_MSG(("%s: Host irq is not defined\n", __FUNCTION__)); + return 1; + } +#endif /* defined(OOB_INTR_ONLY) */ + /* allocate SDIO Host Controller state info */ + if (!(osh = osl_attach(dev, PCI_BUS, FALSE))) { + SDLX_MSG(("%s: osl_attach failed\n", __FUNCTION__)); + goto err; + } + if (!(sdhc = MALLOC(osh, sizeof(bcmsdh_hc_t)))) { + SDLX_MSG(("%s: out of memory, allocated %d bytes\n", + __FUNCTION__, + MALLOCED(osh))); + goto err; + } + bzero(sdhc, sizeof(bcmsdh_hc_t)); + sdhc->osh = osh; + + sdhc->dev = (void *)dev; + +#ifdef BCMLXSDMMC + if (!(sdh = bcmsdh_attach(osh, (void *)0, + (void **)®s, irq))) { + SDLX_MSG(("%s: bcmsdh_attach failed\n", __FUNCTION__)); + goto err; + } +#else + if (!(sdh = bcmsdh_attach(osh, (void *)r->start, + (void **)®s, irq))) { + SDLX_MSG(("%s: bcmsdh_attach failed\n", __FUNCTION__)); + goto err; + } +#endif /* BCMLXSDMMC */ + sdhc->sdh = sdh; + sdhc->oob_irq = irq; + sdhc->oob_flags = irq_flags; + sdhc->oob_irq_registered = FALSE; /* to make sure.. */ +#if defined(OOB_INTR_ONLY) + spin_lock_init(&sdhc->irq_lock); +#endif + + /* chain SDIO Host Controller info together */ + sdhc->next = sdhcinfo; + sdhcinfo = sdhc; + /* Read the vendor/device ID from the CIS */ + vendevid = bcmsdh_query_device(sdh); + + /* try to attach to the target device */ + if (!(sdhc->ch = drvinfo.attach((vendevid >> 16), + (vendevid & 0xFFFF), 0, 0, 0, 0, + (void *)regs, NULL, sdh))) { + SDLX_MSG(("%s: device attach failed\n", __FUNCTION__)); + goto err; + } + + return 0; + + /* error handling */ +err: + if (sdhc) { + if (sdhc->sdh) + bcmsdh_detach(sdhc->osh, sdhc->sdh); + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + } + if (osh) + osl_detach(osh); + return -ENODEV; +} + +#ifndef BCMLXSDMMC +static +#endif /* BCMLXSDMMC */ +int bcmsdh_remove(struct device *dev) +{ + bcmsdh_hc_t *sdhc, *prev; + osl_t *osh; + + sdhc = sdhcinfo; + drvinfo.detach(sdhc->ch); + bcmsdh_detach(sdhc->osh, sdhc->sdh); + /* find the SDIO Host Controller state for this pdev and take it out from the list */ + for (sdhc = sdhcinfo, prev = NULL; sdhc; sdhc = sdhc->next) { + if (sdhc->dev == (void *)dev) { + if (prev) + prev->next = sdhc->next; + else + sdhcinfo = NULL; + break; + } + prev = sdhc; + } + if (!sdhc) { + SDLX_MSG(("%s: failed\n", __FUNCTION__)); + return 0; + } + + + /* release SDIO Host Controller info */ + osh = sdhc->osh; + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + osl_detach(osh); + +#if !defined(BCMLXSDMMC) || defined(OOB_INTR_ONLY) + dev_set_drvdata(dev, NULL); +#endif /* !defined(BCMLXSDMMC) */ + + return 0; +} + +#else /* BCMPLATFORM_BUS */ + +#if !defined(BCMLXSDMMC) +/* forward declarations for PCI probe and remove functions. */ +static int __devinit bcmsdh_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent); +static void __devexit bcmsdh_pci_remove(struct pci_dev *pdev); + +/** + * pci id table + */ +static struct pci_device_id bcmsdh_pci_devid[] __devinitdata = { + { vendor: PCI_ANY_ID, + device: PCI_ANY_ID, + subvendor: PCI_ANY_ID, + subdevice: PCI_ANY_ID, + class: 0, + class_mask: 0, + driver_data: 0, + }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, bcmsdh_pci_devid); + +/** + * SDIO Host Controller pci driver info + */ +static struct pci_driver bcmsdh_pci_driver = { + node: {}, + name: "bcmsdh", + id_table: bcmsdh_pci_devid, + probe: bcmsdh_pci_probe, + remove: bcmsdh_pci_remove, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + save_state: NULL, +#endif + suspend: NULL, + resume: NULL, +}; + + +extern uint sd_pci_slot; /* Force detection to a particular PCI */ + /* slot only . Allows for having multiple */ + /* WL devices at once in a PC */ + /* Only one instance of dhd will be */ + /* usable at a time */ + /* Upper word is bus number, */ + /* lower word is slot number */ + /* Default value of 0xFFFFffff turns this */ + /* off */ +module_param(sd_pci_slot, uint, 0); + + +/** + * Detect supported SDIO Host Controller and attach if found. + * + * Determine if the device described by pdev is a supported SDIO Host + * Controller. If so, attach to it and attach to the target device. + */ +static int __devinit +bcmsdh_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + osl_t *osh = NULL; + bcmsdh_hc_t *sdhc = NULL; + ulong regs; + bcmsdh_info_t *sdh = NULL; + int rc; + + if (sd_pci_slot != 0xFFFFffff) { + if (pdev->bus->number != (sd_pci_slot>>16) || + PCI_SLOT(pdev->devfn) != (sd_pci_slot&0xffff)) { + SDLX_MSG(("%s: %s: bus %X, slot %X, vend %X, dev %X\n", + __FUNCTION__, + bcmsdh_chipmatch(pdev->vendor, pdev->device) ? + "Found compatible SDIOHC" : + "Probing unknown device", + pdev->bus->number, PCI_SLOT(pdev->devfn), + pdev->vendor, pdev->device)); + return -ENODEV; + } + SDLX_MSG(("%s: %s: bus %X, slot %X, vendor %X, device %X (good PCI location)\n", + __FUNCTION__, + bcmsdh_chipmatch(pdev->vendor, pdev->device) ? + "Using compatible SDIOHC" : + "WARNING, forced use of unkown device", + pdev->bus->number, PCI_SLOT(pdev->devfn), + pdev->vendor, pdev->device)); + } + + if ((pdev->vendor == VENDOR_TI) && ((pdev->device == PCIXX21_FLASHMEDIA_ID) || + (pdev->device == PCIXX21_FLASHMEDIA0_ID))) { + uint32 config_reg; + + SDLX_MSG(("%s: Disabling TI FlashMedia Controller.\n", __FUNCTION__)); + if (!(osh = osl_attach(pdev, PCI_BUS, FALSE))) { + SDLX_MSG(("%s: osl_attach failed\n", __FUNCTION__)); + goto err; + } + + config_reg = OSL_PCI_READ_CONFIG(osh, 0x4c, 4); + + /* + * Set MMC_SD_DIS bit in FlashMedia Controller. + * Disbling the SD/MMC Controller in the FlashMedia Controller + * allows the Standard SD Host Controller to take over control + * of the SD Slot. + */ + config_reg |= 0x02; + OSL_PCI_WRITE_CONFIG(osh, 0x4c, 4, config_reg); + osl_detach(osh); + } + /* match this pci device with what we support */ + /* we can't solely rely on this to believe it is our SDIO Host Controller! */ + if (!bcmsdh_chipmatch(pdev->vendor, pdev->device)) { + return -ENODEV; + } + + /* this is a pci device we might support */ + SDLX_MSG(("%s: Found possible SDIO Host Controller: bus %d slot %d func %d irq %d\n", + __FUNCTION__, + pdev->bus->number, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), pdev->irq)); + + /* use bcmsdh_query_device() to get the vendor ID of the target device so + * it will eventually appear in the Broadcom string on the console + */ + + /* allocate SDIO Host Controller state info */ + if (!(osh = osl_attach(pdev, PCI_BUS, FALSE))) { + SDLX_MSG(("%s: osl_attach failed\n", __FUNCTION__)); + goto err; + } + if (!(sdhc = MALLOC(osh, sizeof(bcmsdh_hc_t)))) { + SDLX_MSG(("%s: out of memory, allocated %d bytes\n", + __FUNCTION__, + MALLOCED(osh))); + goto err; + } + bzero(sdhc, sizeof(bcmsdh_hc_t)); + sdhc->osh = osh; + + sdhc->dev = pdev; + + /* map to address where host can access */ + pci_set_master(pdev); + rc = pci_enable_device(pdev); + if (rc) { + SDLX_MSG(("%s: Cannot enable PCI device\n", __FUNCTION__)); + goto err; + } + if (!(sdh = bcmsdh_attach(osh, (void *)(uintptr)pci_resource_start(pdev, 0), + (void **)®s, pdev->irq))) { + SDLX_MSG(("%s: bcmsdh_attach failed\n", __FUNCTION__)); + goto err; + } + + sdhc->sdh = sdh; + + /* try to attach to the target device */ + if (!(sdhc->ch = drvinfo.attach(VENDOR_BROADCOM, /* pdev->vendor, */ + bcmsdh_query_device(sdh) & 0xFFFF, 0, 0, 0, 0, + (void *)regs, NULL, sdh))) { + SDLX_MSG(("%s: device attach failed\n", __FUNCTION__)); + goto err; + } + + /* chain SDIO Host Controller info together */ + sdhc->next = sdhcinfo; + sdhcinfo = sdhc; + + return 0; + + /* error handling */ +err: + if (sdhc->sdh) + bcmsdh_detach(sdhc->osh, sdhc->sdh); + if (sdhc) + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + if (osh) + osl_detach(osh); + return -ENODEV; +} + + +/** + * Detach from target devices and SDIO Host Controller + */ +static void __devexit +bcmsdh_pci_remove(struct pci_dev *pdev) +{ + bcmsdh_hc_t *sdhc, *prev; + osl_t *osh; + + /* find the SDIO Host Controller state for this pdev and take it out from the list */ + for (sdhc = sdhcinfo, prev = NULL; sdhc; sdhc = sdhc->next) { + if (sdhc->dev == pdev) { + if (prev) + prev->next = sdhc->next; + else + sdhcinfo = NULL; + break; + } + prev = sdhc; + } + if (!sdhc) + return; + + drvinfo.detach(sdhc->ch); + + bcmsdh_detach(sdhc->osh, sdhc->sdh); + + /* release SDIO Host Controller info */ + osh = sdhc->osh; + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + osl_detach(osh); +} +#endif /* BCMLXSDMMC */ +#endif /* BCMPLATFORM_BUS */ + +extern int sdio_function_init(void); + +int +bcmsdh_register(bcmsdh_driver_t *driver) +{ + int error = 0; + + drvinfo = *driver; + +#if defined(BCMPLATFORM_BUS) +#if defined(BCMLXSDMMC) + SDLX_MSG(("Linux Kernel SDIO/MMC Driver\n")); + error = sdio_function_init(); +#else + SDLX_MSG(("Intel PXA270 SDIO Driver\n")); + error = driver_register(&bcmsdh_driver); +#endif /* defined(BCMLXSDMMC) */ + return error; +#endif /* defined(BCMPLATFORM_BUS) */ + +#if !defined(BCMPLATFORM_BUS) && !defined(BCMLXSDMMC) +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + if (!(error = pci_module_init(&bcmsdh_pci_driver))) + return 0; +#else + if (!(error = pci_register_driver(&bcmsdh_pci_driver))) + return 0; +#endif + + SDLX_MSG(("%s: pci_module_init failed 0x%x\n", __FUNCTION__, error)); +#endif /* BCMPLATFORM_BUS */ + + return error; +} + +extern void sdio_function_cleanup(void); + +void +bcmsdh_unregister(void) +{ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + if (bcmsdh_pci_driver.node.next) +#endif + +#if defined(BCMPLATFORM_BUS) && !defined(BCMLXSDMMC) + driver_unregister(&bcmsdh_driver); +#endif +#if defined(BCMLXSDMMC) + sdio_function_cleanup(); +#endif /* BCMLXSDMMC */ +#if !defined(BCMPLATFORM_BUS) && !defined(BCMLXSDMMC) + pci_unregister_driver(&bcmsdh_pci_driver); +#endif /* BCMPLATFORM_BUS */ +} + +#if defined(OOB_INTR_ONLY) +void bcmsdh_oob_intr_set(bool enable) +{ + static bool curstate = 1; + unsigned long flags; + + spin_lock_irqsave(&sdhcinfo->irq_lock, flags); + if (curstate != enable) { + if (enable) + enable_irq(sdhcinfo->oob_irq); + else + disable_irq_nosync(sdhcinfo->oob_irq); + curstate = enable; + } + spin_unlock_irqrestore(&sdhcinfo->irq_lock, flags); +} + +static irqreturn_t wlan_oob_irq(int irq, void *dev_id) +{ + dhd_pub_t *dhdp; + + dhdp = (dhd_pub_t *)dev_get_drvdata(sdhcinfo->dev); + + bcmsdh_oob_intr_set(0); + + if (dhdp == NULL) { + SDLX_MSG(("Out of band GPIO interrupt fired way too early\n")); + return IRQ_HANDLED; + } + + dhdsdio_isr((void *)dhdp->bus); + + return IRQ_HANDLED; +} + +int bcmsdh_register_oob_intr(void * dhdp) +{ + int error = 0; + + SDLX_MSG(("%s Enter\n", __FUNCTION__)); + +/* Example of HW_OOB for HW2: please refer to your host specifiction */ +/* sdhcinfo->oob_flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE; */ + + dev_set_drvdata(sdhcinfo->dev, dhdp); + + if (!sdhcinfo->oob_irq_registered) { + SDLX_MSG(("%s IRQ=%d Type=%X \n", __FUNCTION__, \ + (int)sdhcinfo->oob_irq, (int)sdhcinfo->oob_flags)); + /* Refer to customer Host IRQ docs about proper irqflags definition */ + error = request_irq(sdhcinfo->oob_irq, wlan_oob_irq, sdhcinfo->oob_flags, + "bcmsdh_sdmmc", NULL); + if (error) + return -ENODEV; + + enable_irq_wake(sdhcinfo->oob_irq); + sdhcinfo->oob_irq_registered = TRUE; + } + + return 0; +} + +void bcmsdh_set_irq(int flag) +{ + if (sdhcinfo->oob_irq_registered) { + SDLX_MSG(("%s Flag = %d", __FUNCTION__, flag)); + if (flag) { + enable_irq(sdhcinfo->oob_irq); + enable_irq_wake(sdhcinfo->oob_irq); + } else { + disable_irq_wake(sdhcinfo->oob_irq); + disable_irq(sdhcinfo->oob_irq); + } + } +} + +void bcmsdh_unregister_oob_intr(void) +{ + SDLX_MSG(("%s: Enter\n", __FUNCTION__)); + + if (sdhcinfo->oob_irq_registered) { + disable_irq_wake(sdhcinfo->oob_irq); + disable_irq(sdhcinfo->oob_irq); /* just in case.. */ + free_irq(sdhcinfo->oob_irq, NULL); + sdhcinfo->oob_irq_registered = FALSE; + } +} +#endif /* defined(OOB_INTR_ONLY) */ +/* Module parameters specific to each host-controller driver */ + +extern uint sd_msglevel; /* Debug message level */ +module_param(sd_msglevel, uint, 0); + +extern uint sd_power; /* 0 = SD Power OFF, 1 = SD Power ON. */ +module_param(sd_power, uint, 0); + +extern uint sd_clock; /* SD Clock Control, 0 = SD Clock OFF, 1 = SD Clock ON */ +module_param(sd_clock, uint, 0); + +extern uint sd_divisor; /* Divisor (-1 means external clock) */ +module_param(sd_divisor, uint, 0); + +extern uint sd_sdmode; /* Default is SD4, 0=SPI, 1=SD1, 2=SD4 */ +module_param(sd_sdmode, uint, 0); + +extern uint sd_hiok; /* Ok to use hi-speed mode */ +module_param(sd_hiok, uint, 0); + +extern uint sd_f2_blocksize; +module_param(sd_f2_blocksize, int, 0); + + +#ifdef BCMSDH_MODULE +EXPORT_SYMBOL(bcmsdh_attach); +EXPORT_SYMBOL(bcmsdh_detach); +EXPORT_SYMBOL(bcmsdh_intr_query); +EXPORT_SYMBOL(bcmsdh_intr_enable); +EXPORT_SYMBOL(bcmsdh_intr_disable); +EXPORT_SYMBOL(bcmsdh_intr_reg); +EXPORT_SYMBOL(bcmsdh_intr_dereg); + +#if defined(DHD_DEBUG) +EXPORT_SYMBOL(bcmsdh_intr_pending); +#endif + +EXPORT_SYMBOL(bcmsdh_devremove_reg); +EXPORT_SYMBOL(bcmsdh_cfg_read); +EXPORT_SYMBOL(bcmsdh_cfg_write); +EXPORT_SYMBOL(bcmsdh_cis_read); +EXPORT_SYMBOL(bcmsdh_reg_read); +EXPORT_SYMBOL(bcmsdh_reg_write); +EXPORT_SYMBOL(bcmsdh_regfail); +EXPORT_SYMBOL(bcmsdh_send_buf); +EXPORT_SYMBOL(bcmsdh_recv_buf); + +EXPORT_SYMBOL(bcmsdh_rwdata); +EXPORT_SYMBOL(bcmsdh_abort); +EXPORT_SYMBOL(bcmsdh_query_device); +EXPORT_SYMBOL(bcmsdh_query_iofnum); +EXPORT_SYMBOL(bcmsdh_iovar_op); +EXPORT_SYMBOL(bcmsdh_register); +EXPORT_SYMBOL(bcmsdh_unregister); +EXPORT_SYMBOL(bcmsdh_chipmatch); +EXPORT_SYMBOL(bcmsdh_reset); + +EXPORT_SYMBOL(bcmsdh_get_dstatus); +EXPORT_SYMBOL(bcmsdh_cfg_read_word); +EXPORT_SYMBOL(bcmsdh_cfg_write_word); +EXPORT_SYMBOL(bcmsdh_cur_sbwad); +EXPORT_SYMBOL(bcmsdh_chipinfo); + +#endif /* BCMSDH_MODULE */ diff --git a/drivers/net/wireless/bcm4329/bcmsdh_sdmmc.c b/drivers/net/wireless/bcm4329/bcmsdh_sdmmc.c new file mode 100644 index 0000000000000000000000000000000000000000..031367b8f18f996f6ccf50d64909ca3fa111b460 --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdh_sdmmc.c @@ -0,0 +1,1304 @@ +/* + * BCMSDH Function Driver for the native SDIO/MMC driver in the Linux Kernel + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdh_sdmmc.c,v 1.1.2.5.6.30.4.1 2010/09/02 23:12:21 Exp $ + */ +#include + +#include +#include +#include +#include +#include /* SDIO Device and Protocol Specs */ +#include /* SDIO Host Controller Specification */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* ioctl/iovars */ + +#include +#include +#include + +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) +#include +extern volatile bool dhd_mmc_suspend; +#endif +#include "bcmsdh_sdmmc.h" + +#ifndef BCMSDH_MODULE +extern int sdio_function_init(void); +extern void sdio_function_cleanup(void); +#endif /* BCMSDH_MODULE */ + +#if !defined(OOB_INTR_ONLY) +static void IRQHandler(struct sdio_func *func); +static void IRQHandlerF2(struct sdio_func *func); +#endif /* !defined(OOB_INTR_ONLY) */ +static int sdioh_sdmmc_get_cisaddr(sdioh_info_t *sd, uint32 regaddr); +extern int sdio_reset_comm(struct mmc_card *card); + +extern PBCMSDH_SDMMC_INSTANCE gInstance; + +uint sd_sdmode = SDIOH_MODE_SD4; /* Use SD4 mode by default */ +uint sd_f2_blocksize = 512; /* Default blocksize */ + +uint sd_divisor = 2; /* Default 48MHz/2 = 24MHz */ + +uint sd_power = 1; /* Default to SD Slot powered ON */ +uint sd_clock = 1; /* Default to SD Clock turned ON */ +uint sd_hiok = FALSE; /* Don't use hi-speed mode by default */ +uint sd_msglevel = 0x01; +uint sd_use_dma = TRUE; +DHD_PM_RESUME_WAIT_INIT(sdioh_request_byte_wait); +DHD_PM_RESUME_WAIT_INIT(sdioh_request_word_wait); +DHD_PM_RESUME_WAIT_INIT(sdioh_request_packet_wait); +DHD_PM_RESUME_WAIT_INIT(sdioh_request_buffer_wait); + +#define DMA_ALIGN_MASK 0x03 + +int sdioh_sdmmc_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 *data); + +static int +sdioh_sdmmc_card_enablefuncs(sdioh_info_t *sd) +{ + int err_ret; + uint32 fbraddr; + uint8 func; + + sd_trace(("%s\n", __FUNCTION__)); + + /* Get the Card's common CIS address */ + sd->com_cis_ptr = sdioh_sdmmc_get_cisaddr(sd, SDIOD_CCCR_CISPTR_0); + sd->func_cis_ptr[0] = sd->com_cis_ptr; + sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __FUNCTION__, sd->com_cis_ptr)); + + /* Get the Card's function CIS (for each function) */ + for (fbraddr = SDIOD_FBR_STARTADDR, func = 1; + func <= sd->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) { + sd->func_cis_ptr[func] = sdioh_sdmmc_get_cisaddr(sd, SDIOD_FBR_CISPTR_0 + fbraddr); + sd_info(("%s: Function %d CIS Ptr = 0x%x\n", + __FUNCTION__, func, sd->func_cis_ptr[func])); + } + + sd->func_cis_ptr[0] = sd->com_cis_ptr; + sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __FUNCTION__, sd->com_cis_ptr)); + + /* Enable Function 1 */ + sdio_claim_host(gInstance->func[1]); + err_ret = sdio_enable_func(gInstance->func[1]); + sdio_release_host(gInstance->func[1]); + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to enable F1 Err: 0x%08x", err_ret)); + } + + return FALSE; +} + +/* + * Public entry points & extern's + */ +extern sdioh_info_t * +sdioh_attach(osl_t *osh, void *bar0, uint irq) +{ + sdioh_info_t *sd; + int err_ret; + + sd_trace(("%s\n", __FUNCTION__)); + + if (gInstance == NULL) { + sd_err(("%s: SDIO Device not present\n", __FUNCTION__)); + return NULL; + } + + if ((sd = (sdioh_info_t *)MALLOC(osh, sizeof(sdioh_info_t))) == NULL) { + sd_err(("sdioh_attach: out of memory, malloced %d bytes\n", MALLOCED(osh))); + return NULL; + } + bzero((char *)sd, sizeof(sdioh_info_t)); + sd->osh = osh; + if (sdioh_sdmmc_osinit(sd) != 0) { + sd_err(("%s:sdioh_sdmmc_osinit() failed\n", __FUNCTION__)); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return NULL; + } + + sd->num_funcs = 2; + sd->sd_blockmode = TRUE; + sd->use_client_ints = TRUE; + sd->client_block_size[0] = 64; + + gInstance->sd = sd; + + /* Claim host controller */ + sdio_claim_host(gInstance->func[1]); + + sd->client_block_size[1] = 64; + err_ret = sdio_set_block_size(gInstance->func[1], 64); + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to set F1 blocksize\n")); + } + + /* Release host controller F1 */ + sdio_release_host(gInstance->func[1]); + + if (gInstance->func[2]) { + /* Claim host controller F2 */ + sdio_claim_host(gInstance->func[2]); + + sd->client_block_size[2] = sd_f2_blocksize; + err_ret = sdio_set_block_size(gInstance->func[2], sd_f2_blocksize); + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to set F2 blocksize to %d\n", + sd_f2_blocksize)); + } + + /* Release host controller F2 */ + sdio_release_host(gInstance->func[2]); + } + + sdioh_sdmmc_card_enablefuncs(sd); + + 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) { + + /* Disable Function 2 */ + sdio_claim_host(gInstance->func[2]); + sdio_disable_func(gInstance->func[2]); + sdio_release_host(gInstance->func[2]); + + /* Disable Function 1 */ + sdio_claim_host(gInstance->func[1]); + sdio_disable_func(gInstance->func[1]); + sdio_release_host(gInstance->func[1]); + + /* deregister irq */ + sdioh_sdmmc_osfree(sd); + + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + } + return SDIOH_API_RC_SUCCESS; +} + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) + +extern SDIOH_API_RC +sdioh_enable_func_intr(void) +{ + uint8 reg; + int err; + + if (gInstance->func[0]) { + sdio_claim_host(gInstance->func[0]); + + reg = sdio_readb(gInstance->func[0], SDIOD_CCCR_INTEN, &err); + if (err) { + sd_err(("%s: error for read SDIO_CCCR_IENx : 0x%x\n", __FUNCTION__, err)); + sdio_release_host(gInstance->func[0]); + return SDIOH_API_RC_FAIL; + } + + /* Enable F1 and F2 interrupts, set master enable */ + reg |= (INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN | INTR_CTL_MASTER_EN); + + sdio_writeb(gInstance->func[0], reg, SDIOD_CCCR_INTEN, &err); + sdio_release_host(gInstance->func[0]); + + if (err) { + sd_err(("%s: error for write SDIO_CCCR_IENx : 0x%x\n", __FUNCTION__, err)); + return SDIOH_API_RC_FAIL; + } + } + + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_disable_func_intr(void) +{ + uint8 reg; + int err; + + if (gInstance->func[0]) { + sdio_claim_host(gInstance->func[0]); + reg = sdio_readb(gInstance->func[0], SDIOD_CCCR_INTEN, &err); + if (err) { + sd_err(("%s: error for read SDIO_CCCR_IENx : 0x%x\n", __FUNCTION__, err)); + sdio_release_host(gInstance->func[0]); + return SDIOH_API_RC_FAIL; + } + + reg &= ~(INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN); + /* Disable master interrupt with the last function interrupt */ + if (!(reg & 0xFE)) + reg = 0; + sdio_writeb(gInstance->func[0], reg, SDIOD_CCCR_INTEN, &err); + + sdio_release_host(gInstance->func[0]); + if (err) { + sd_err(("%s: error for write SDIO_CCCR_IENx : 0x%x\n", __FUNCTION__, err)); + return SDIOH_API_RC_FAIL; + } + } + return SDIOH_API_RC_SUCCESS; +} +#endif /* defined(OOB_INTR_ONLY) && defined(HW_OOB) */ + +/* 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__)); + if (fn == NULL) { + sd_err(("%s: interrupt handler is NULL, not registering\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } +#if !defined(OOB_INTR_ONLY) + sd->intr_handler = fn; + sd->intr_handler_arg = argh; + sd->intr_handler_valid = TRUE; + + /* register and unmask irq */ + if (gInstance->func[2]) { + sdio_claim_host(gInstance->func[2]); + sdio_claim_irq(gInstance->func[2], IRQHandlerF2); + sdio_release_host(gInstance->func[2]); + } + + if (gInstance->func[1]) { + sdio_claim_host(gInstance->func[1]); + sdio_claim_irq(gInstance->func[1], IRQHandler); + sdio_release_host(gInstance->func[1]); + } +#elif defined(HW_OOB) + sdioh_enable_func_intr(); +#endif /* defined(OOB_INTR_ONLY) */ + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_interrupt_deregister(sdioh_info_t *sd) +{ + sd_trace(("%s: Entering\n", __FUNCTION__)); + +#if !defined(OOB_INTR_ONLY) + if (gInstance->func[1]) { + /* register and unmask irq */ + sdio_claim_host(gInstance->func[1]); + sdio_release_irq(gInstance->func[1]); + sdio_release_host(gInstance->func[1]); + } + + if (gInstance->func[2]) { + /* Claim host controller F2 */ + sdio_claim_host(gInstance->func[2]); + sdio_release_irq(gInstance->func[2]); + /* Release host controller F2 */ + sdio_release_host(gInstance->func[2]); + } + + sd->intr_handler_valid = FALSE; + sd->intr_handler = NULL; + sd->intr_handler_arg = NULL; +#elif defined(HW_OOB) + sdioh_disable_func_intr(); +#endif /* !defined(OOB_INTR_ONLY) */ + 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 + +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_RXCHAIN +}; + +const bcm_iovar_t sdioh_iovars[] = { + {"sd_msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0 }, + {"sd_blockmode", IOV_BLOCKMODE, 0, IOVT_BOOL, 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 }, + {"sd_rxchain", IOV_RXCHAIN, 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; + + 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_BLOCKMODE): + int_val = (int32)si->sd_blockmode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_BLOCKMODE): + si->sd_blockmode = (bool)int_val; + /* Haven't figured out how to make non-block mode with DMA */ + 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_SVAL(IOV_BLOCKSIZE): + { + uint func = ((uint32)int_val >> 16); + uint blksize = (uint16)int_val; + uint maxsize; + + if (func > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + + switch (func) { + case 0: maxsize = 32; break; + case 1: maxsize = BLOCK_SIZE_4318; break; + case 2: maxsize = BLOCK_SIZE_4328; break; + default: maxsize = 0; + } + if (blksize > maxsize) { + bcmerror = BCME_BADARG; + break; + } + if (!blksize) { + blksize = maxsize; + } + + /* Now set it */ + si->client_block_size[func] = blksize; + + break; + } + + case IOV_GVAL(IOV_RXCHAIN): + int_val = FALSE; + 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): + si->use_client_ints = (bool)int_val; + if (si->use_client_ints) + si->intmask |= CLIENT_INTR; + else + si->intmask &= ~CLIENT_INTR; + + 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; + 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; + 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)0; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_HOSTREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + + if (sd_ptr->offset < SD_SysAddr || sd_ptr->offset > SD_MaxCurCap) { + sd_err(("%s: bad offset 0x%x\n", __FUNCTION__, sd_ptr->offset)); + bcmerror = BCME_BADARG; + break; + } + + sd_trace(("%s: rreg%d at offset %d\n", __FUNCTION__, + (sd_ptr->offset & 1) ? 8 : ((sd_ptr->offset & 2) ? 16 : 32), + sd_ptr->offset)); + if (sd_ptr->offset & 1) + int_val = 8; /* sdioh_sdmmc_rreg8(si, sd_ptr->offset); */ + else if (sd_ptr->offset & 2) + int_val = 16; /* sdioh_sdmmc_rreg16(si, sd_ptr->offset); */ + else + int_val = 32; /* sdioh_sdmmc_rreg(si, sd_ptr->offset); */ + + bcopy(&int_val, arg, sizeof(int_val)); + break; + } + + case IOV_SVAL(IOV_HOSTREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + + if (sd_ptr->offset < SD_SysAddr || sd_ptr->offset > SD_MaxCurCap) { + sd_err(("%s: bad offset 0x%x\n", __FUNCTION__, sd_ptr->offset)); + bcmerror = BCME_BADARG; + break; + } + + sd_trace(("%s: wreg%d value 0x%08x at offset %d\n", __FUNCTION__, sd_ptr->value, + (sd_ptr->offset & 1) ? 8 : ((sd_ptr->offset & 2) ? 16 : 32), + sd_ptr->offset)); + break; + } + + case IOV_GVAL(IOV_DEVREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + uint8 data = 0; + + 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; + } + + default: + bcmerror = BCME_UNSUPPORTED; + break; + } +exit: + + return bcmerror; +} + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) + +SDIOH_API_RC +sdioh_enable_hw_oob_intr(sdioh_info_t *sd, bool enable) +{ + SDIOH_API_RC status; + uint8 data; + + if (enable) + data = 3; /* enable hw oob interrupt */ + else + data = 4; /* disable hw oob interrupt */ + data |= 4; /* Active HIGH */ + + status = sdioh_request_byte(sd, SDIOH_WRITE, 0, 0xf2, &data); + return status; +} +#endif /* defined(OOB_INTR_ONLY) && defined(HW_OOB) */ + +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; + status = sdioh_request_byte(sd, SDIOH_WRITE, fnc_num, addr, data); + return status; +} + +static int +sdioh_sdmmc_get_cisaddr(sdioh_info_t *sd, uint32 regaddr) +{ + /* read 24 bits and return valid 17 bit addr */ + int i; + uint32 scratch, regdata; + uint8 *ptr = (uint8 *)&scratch; + for (i = 0; i < 3; i++) { + if ((sdioh_sdmmc_card_regread (sd, 0, regaddr, 1, ®data)) != SUCCESS) + sd_err(("%s: Can't read!\n", __FUNCTION__)); + + *ptr++ = (uint8) regdata; + regaddr++; + } + + /* Only the lower 17-bits are valid */ + scratch = ltoh32(scratch); + scratch &= 0x0001FFFF; + return (scratch); +} + +extern SDIOH_API_RC +sdioh_cis_read(sdioh_info_t *sd, uint func, uint8 *cisd, uint32 length) +{ + uint32 count; + int offset; + uint32 foo; + uint8 *cis = cisd; + + sd_trace(("%s: Func = %d\n", __FUNCTION__, func)); + + if (!sd->func_cis_ptr[func]) { + bzero(cis, length); + sd_err(("%s: no func_cis_ptr[%d]\n", __FUNCTION__, func)); + return SDIOH_API_RC_FAIL; + } + + sd_err(("%s: func_cis_ptr[%d]=0x%04x\n", __FUNCTION__, func, sd->func_cis_ptr[func])); + + for (count = 0; count < length; count++) { + offset = sd->func_cis_ptr[func] + count; + if (sdioh_sdmmc_card_regread (sd, 0, offset, 1, &foo) < 0) { + sd_err(("%s: regread failed: Can't read CIS\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } + + *cis = (uint8)(foo & 0xff); + cis++; + } + + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_request_byte(sdioh_info_t *sd, uint rw, uint func, uint regaddr, uint8 *byte) +{ + int err_ret; + + sd_info(("%s: rw=%d, func=%d, addr=0x%05x\n", __FUNCTION__, rw, func, regaddr)); + + DHD_PM_RESUME_WAIT(sdioh_request_byte_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + if(rw) { /* CMD52 Write */ + if (func == 0) { + /* Can only directly write to some F0 registers. Handle F2 enable + * as a special case. + */ + if (regaddr == SDIOD_CCCR_IOEN) { + if (gInstance->func[2]) { + sdio_claim_host(gInstance->func[2]); + if (*byte & SDIO_FUNC_ENABLE_2) { + /* Enable Function 2 */ + err_ret = sdio_enable_func(gInstance->func[2]); + if (err_ret) { + sd_err(("bcmsdh_sdmmc: enable F2 failed:%d", + err_ret)); + } + } else { + /* Disable Function 2 */ + err_ret = sdio_disable_func(gInstance->func[2]); + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Disab F2 failed:%d", + err_ret)); + } + } + sdio_release_host(gInstance->func[2]); + } + } +#if defined(MMC_SDIO_ABORT) + /* to allow abort command through F1 */ + else if (regaddr == SDIOD_CCCR_IOABORT) { + sdio_claim_host(gInstance->func[func]); + /* + * this sdio_f0_writeb() can be replaced with another api + * depending upon MMC driver change. + * As of this time, this is temporaray one + */ + sdio_writeb(gInstance->func[func], *byte, regaddr, &err_ret); + sdio_release_host(gInstance->func[func]); + } +#endif /* MMC_SDIO_ABORT */ + else if (regaddr < 0xF0) { + sd_err(("bcmsdh_sdmmc: F0 Wr:0x%02x: write disallowed\n", regaddr)); + } else { + /* Claim host controller, perform F0 write, and release */ + sdio_claim_host(gInstance->func[func]); + sdio_f0_writeb(gInstance->func[func], *byte, regaddr, &err_ret); + sdio_release_host(gInstance->func[func]); + } + } else { + /* Claim host controller, perform Fn write, and release */ + sdio_claim_host(gInstance->func[func]); + sdio_writeb(gInstance->func[func], *byte, regaddr, &err_ret); + sdio_release_host(gInstance->func[func]); + } + } else { /* CMD52 Read */ + /* Claim host controller, perform Fn read, and release */ + sdio_claim_host(gInstance->func[func]); + + if (func == 0) { + *byte = sdio_f0_readb(gInstance->func[func], regaddr, &err_ret); + } else { + *byte = sdio_readb(gInstance->func[func], regaddr, &err_ret); + } + + sdio_release_host(gInstance->func[func]); + } + + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to %s byte F%d:@0x%05x=%02x, Err: %d\n", + rw ? "Write" : "Read", func, regaddr, *byte, err_ret)); + } + + return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + +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 err_ret = SDIOH_API_RC_FAIL; + + if (func == 0) { + sd_err(("%s: Only CMD52 allowed to F0.\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } + + sd_info(("%s: cmd_type=%d, rw=%d, func=%d, addr=0x%05x, nbytes=%d\n", + __FUNCTION__, cmd_type, rw, func, addr, nbytes)); + + DHD_PM_RESUME_WAIT(sdioh_request_word_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + /* Claim host controller */ + sdio_claim_host(gInstance->func[func]); + + if(rw) { /* CMD52 Write */ + if (nbytes == 4) { + sdio_writel(gInstance->func[func], *word, addr, &err_ret); + } else if (nbytes == 2) { + sdio_writew(gInstance->func[func], (*word & 0xFFFF), addr, &err_ret); + } else { + sd_err(("%s: Invalid nbytes: %d\n", __FUNCTION__, nbytes)); + } + } else { /* CMD52 Read */ + if (nbytes == 4) { + *word = sdio_readl(gInstance->func[func], addr, &err_ret); + } else if (nbytes == 2) { + *word = sdio_readw(gInstance->func[func], addr, &err_ret) & 0xFFFF; + } else { + sd_err(("%s: Invalid nbytes: %d\n", __FUNCTION__, nbytes)); + } + } + + /* Release host controller */ + sdio_release_host(gInstance->func[func]); + + if (err_ret) { + sd_err(("bcmsdh_sdmmc: Failed to %s word, Err: 0x%08x", + rw ? "Write" : "Read", err_ret)); + } + + return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + +static SDIOH_API_RC +sdioh_request_packet(sdioh_info_t *sd, uint fix_inc, uint write, uint func, + uint addr, void *pkt) +{ + bool fifo = (fix_inc == SDIOH_DATA_FIX); + uint32 SGCount = 0; + int err_ret = 0; + + void *pnext; + + sd_trace(("%s: Enter\n", __FUNCTION__)); + + ASSERT(pkt); + DHD_PM_RESUME_WAIT(sdioh_request_packet_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + + /* Claim host controller */ + sdio_claim_host(gInstance->func[func]); + for (pnext = pkt; pnext; pnext = PKTNEXT(sd->osh, pnext)) { + uint pkt_len = PKTLEN(sd->osh, pnext); + pkt_len += 3; + pkt_len &= 0xFFFFFFFC; + +#ifdef CONFIG_MMC_MSM7X00A + if ((pkt_len % 64) == 32) { + sd_trace(("%s: Rounding up TX packet +=32\n", __FUNCTION__)); + pkt_len += 32; + } +#endif /* CONFIG_MMC_MSM7X00A */ + /* Make sure the packet is aligned properly. If it isn't, then this + * is the fault of sdioh_request_buffer() which is supposed to give + * us something we can work with. + */ + ASSERT(((uint32)(PKTDATA(sd->osh, pkt)) & DMA_ALIGN_MASK) == 0); + + if ((write) && (!fifo)) { + err_ret = sdio_memcpy_toio(gInstance->func[func], addr, + ((uint8*)PKTDATA(sd->osh, pnext)), + pkt_len); + } else if (write) { + err_ret = sdio_memcpy_toio(gInstance->func[func], addr, + ((uint8*)PKTDATA(sd->osh, pnext)), + pkt_len); + } else if (fifo) { + err_ret = sdio_readsb(gInstance->func[func], + ((uint8*)PKTDATA(sd->osh, pnext)), + addr, + pkt_len); + } else { + err_ret = sdio_memcpy_fromio(gInstance->func[func], + ((uint8*)PKTDATA(sd->osh, pnext)), + addr, + pkt_len); + } + + if (err_ret) { + sd_err(("%s: %s FAILED %p[%d], addr=0x%05x, pkt_len=%d, ERR=0x%08x\n", + __FUNCTION__, + (write) ? "TX" : "RX", + pnext, SGCount, addr, pkt_len, err_ret)); + } else { + sd_trace(("%s: %s xfr'd %p[%d], addr=0x%05x, len=%d\n", + __FUNCTION__, + (write) ? "TX" : "RX", + pnext, SGCount, addr, pkt_len)); + } + + if (!fifo) { + addr += pkt_len; + } + SGCount ++; + + } + + /* Release host controller */ + sdio_release_host(gInstance->func[func]); + + sd_trace(("%s: Exit\n", __FUNCTION__)); + return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + + +/* + * This function takes a buffer or packet, and fixes everything up so that in the + * end, a DMA-able packet is created. + * + * A buffer does not have an associated packet pointer, and may or may not be aligned. + * A packet may consist of a single packet, or a packet chain. If it is a packet chain, + * then all the packets in the chain must be properly aligned. If the packet data is not + * aligned, then there may only be one packet, and in this case, it is copied to a new + * aligned packet. + * + */ +extern SDIOH_API_RC +sdioh_request_buffer(sdioh_info_t *sd, uint pio_dma, uint fix_inc, uint write, uint func, + uint addr, uint reg_width, uint buflen_u, uint8 *buffer, void *pkt) +{ + SDIOH_API_RC Status; + void *mypkt = NULL; + + sd_trace(("%s: Enter\n", __FUNCTION__)); + + DHD_PM_RESUME_WAIT(sdioh_request_buffer_wait); + DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL); + /* Case 1: we don't have a packet. */ + if (pkt == NULL) { + sd_data(("%s: Creating new %s Packet, len=%d\n", + __FUNCTION__, write ? "TX" : "RX", buflen_u)); +#ifdef DHD_USE_STATIC_BUF + if (!(mypkt = PKTGET_STATIC(sd->osh, buflen_u, write ? TRUE : FALSE))) { +#else + if (!(mypkt = PKTGET(sd->osh, buflen_u, write ? TRUE : FALSE))) { +#endif /* DHD_USE_STATIC_BUF */ + sd_err(("%s: PKTGET failed: len %d\n", + __FUNCTION__, buflen_u)); + return SDIOH_API_RC_FAIL; + } + + /* For a write, copy the buffer data into the packet. */ + if (write) { + bcopy(buffer, PKTDATA(sd->osh, mypkt), buflen_u); + } + + Status = sdioh_request_packet(sd, fix_inc, write, func, addr, mypkt); + + /* For a read, copy the packet data back to the buffer. */ + if (!write) { + bcopy(PKTDATA(sd->osh, mypkt), buffer, buflen_u); + } +#ifdef DHD_USE_STATIC_BUF + PKTFREE_STATIC(sd->osh, mypkt, write ? TRUE : FALSE); +#else + PKTFREE(sd->osh, mypkt, write ? TRUE : FALSE); +#endif /* DHD_USE_STATIC_BUF */ + } else if (((uint32)(PKTDATA(sd->osh, pkt)) & DMA_ALIGN_MASK) != 0) { + /* Case 2: We have a packet, but it is unaligned. */ + + /* In this case, we cannot have a chain. */ + ASSERT(PKTNEXT(sd->osh, pkt) == NULL); + + sd_data(("%s: Creating aligned %s Packet, len=%d\n", + __FUNCTION__, write ? "TX" : "RX", PKTLEN(sd->osh, pkt))); +#ifdef DHD_USE_STATIC_BUF + if (!(mypkt = PKTGET_STATIC(sd->osh, PKTLEN(sd->osh, pkt), write ? TRUE : FALSE))) { +#else + if (!(mypkt = PKTGET(sd->osh, PKTLEN(sd->osh, pkt), write ? TRUE : FALSE))) { +#endif /* DHD_USE_STATIC_BUF */ + sd_err(("%s: PKTGET failed: len %d\n", + __FUNCTION__, PKTLEN(sd->osh, pkt))); + return SDIOH_API_RC_FAIL; + } + + /* For a write, copy the buffer data into the packet. */ + if (write) { + bcopy(PKTDATA(sd->osh, pkt), + PKTDATA(sd->osh, mypkt), + PKTLEN(sd->osh, pkt)); + } + + Status = sdioh_request_packet(sd, fix_inc, write, func, addr, mypkt); + + /* For a read, copy the packet data back to the buffer. */ + if (!write) { + bcopy(PKTDATA(sd->osh, mypkt), + PKTDATA(sd->osh, pkt), + PKTLEN(sd->osh, mypkt)); + } +#ifdef DHD_USE_STATIC_BUF + PKTFREE_STATIC(sd->osh, mypkt, write ? TRUE : FALSE); +#else + PKTFREE(sd->osh, mypkt, write ? TRUE : FALSE); +#endif /* DHD_USE_STATIC_BUF */ + } else { /* case 3: We have a packet and it is aligned. */ + sd_data(("%s: Aligned %s Packet, direct DMA\n", + __FUNCTION__, write ? "Tx" : "Rx")); + Status = sdioh_request_packet(sd, fix_inc, write, func, addr, pkt); + } + + return (Status); +} + +/* this function performs "abort" for both of host & device */ +extern int +sdioh_abort(sdioh_info_t *sd, uint func) +{ +#if defined(MMC_SDIO_ABORT) + char t_func = (char) func; +#endif /* defined(MMC_SDIO_ABORT) */ + sd_trace(("%s: Enter\n", __FUNCTION__)); + +#if defined(MMC_SDIO_ABORT) + /* issue abort cmd52 command through F1 */ + sdioh_request_byte(sd, SD_IO_OP_WRITE, SDIO_FUNC_0, SDIOD_CCCR_IOABORT, &t_func); +#endif /* defined(MMC_SDIO_ABORT) */ + + sd_trace(("%s: Exit\n", __FUNCTION__)); + return SDIOH_API_RC_SUCCESS; +} + +/* Reset and re-initialize the device */ +int sdioh_sdio_reset(sdioh_info_t *si) +{ + sd_trace(("%s: Enter\n", __FUNCTION__)); + sd_trace(("%s: Exit\n", __FUNCTION__)); + return SDIOH_API_RC_SUCCESS; +} + +/* Disable device interrupt */ +void +sdioh_sdmmc_devintr_off(sdioh_info_t *sd) +{ + sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); + sd->intmask &= ~CLIENT_INTR; +} + +/* Enable device interrupt */ +void +sdioh_sdmmc_devintr_on(sdioh_info_t *sd) +{ + sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); + sd->intmask |= CLIENT_INTR; +} + +/* Read client card reg */ +int +sdioh_sdmmc_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 *data) +{ + + if ((func == 0) || (regsize == 1)) { + uint8 temp = 0; + + sdioh_request_byte(sd, SDIOH_READ, func, regaddr, &temp); + *data = temp; + *data &= 0xff; + sd_data(("%s: byte read data=0x%02x\n", + __FUNCTION__, *data)); + } else { + sdioh_request_word(sd, 0, SDIOH_READ, func, regaddr, data, regsize); + if (regsize == 2) + *data &= 0xffff; + + sd_data(("%s: word read data=0x%08x\n", + __FUNCTION__, *data)); + } + + return SUCCESS; +} + +#if !defined(OOB_INTR_ONLY) +/* bcmsdh_sdmmc interrupt handler */ +static void IRQHandler(struct sdio_func *func) +{ + sdioh_info_t *sd; + + sd_trace(("bcmsdh_sdmmc: ***IRQHandler\n")); + sd = gInstance->sd; + + ASSERT(sd != NULL); + sdio_release_host(gInstance->func[0]); + + if (sd->use_client_ints) { + sd->intrcount++; + ASSERT(sd->intr_handler); + ASSERT(sd->intr_handler_arg); + (sd->intr_handler)(sd->intr_handler_arg); + } else { + sd_err(("bcmsdh_sdmmc: ***IRQHandler\n")); + + sd_err(("%s: Not ready for intr: enabled %d, handler %p\n", + __FUNCTION__, sd->client_intr_enabled, sd->intr_handler)); + } + + sdio_claim_host(gInstance->func[0]); +} + +/* bcmsdh_sdmmc interrupt handler for F2 (dummy handler) */ +static void IRQHandlerF2(struct sdio_func *func) +{ + sdioh_info_t *sd; + + sd_trace(("bcmsdh_sdmmc: ***IRQHandlerF2\n")); + + sd = gInstance->sd; + + ASSERT(sd != NULL); +} +#endif /* !defined(OOB_INTR_ONLY) */ + +#ifdef NOTUSED +/* Write client card reg */ +static int +sdioh_sdmmc_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 data) +{ + + if ((func == 0) || (regsize == 1)) { + uint8 temp; + + temp = data & 0xff; + sdioh_request_byte(sd, SDIOH_READ, func, regaddr, &temp); + sd_data(("%s: byte write data=0x%02x\n", + __FUNCTION__, data)); + } else { + if (regsize == 2) + data &= 0xffff; + + sdioh_request_word(sd, 0, SDIOH_READ, func, regaddr, &data, regsize); + + sd_data(("%s: word write data=0x%08x\n", + __FUNCTION__, data)); + } + + return SUCCESS; +} +#endif /* NOTUSED */ + +int +sdioh_start(sdioh_info_t *si, int stage) +{ + int ret; + sdioh_info_t *sd = gInstance->sd; + + /* Need to do this stages as we can't enable the interrupt till + downloading of the firmware is complete, other wise polling + sdio access will come in way + */ + if (gInstance->func[0]) { + if (stage == 0) { + /* Since the power to the chip is killed, we will have + re enumerate the device again. Set the block size + and enable the fucntion 1 for in preparation for + downloading the code + */ + /* sdio_reset_comm() - has been fixed in latest kernel/msm.git for Linux + 2.6.27. The implementation prior to that is buggy, and needs broadcom's + patch for it + */ + if ((ret = sdio_reset_comm(gInstance->func[0]->card))) + sd_err(("%s Failed, error = %d\n", __FUNCTION__, ret)); + else { + sd->num_funcs = 2; + sd->sd_blockmode = TRUE; + sd->use_client_ints = TRUE; + sd->client_block_size[0] = 64; + + /* Claim host controller */ + sdio_claim_host(gInstance->func[1]); + + sd->client_block_size[1] = 64; + if (sdio_set_block_size(gInstance->func[1], 64)) { + sd_err(("bcmsdh_sdmmc: Failed to set F1 blocksize\n")); + } + + /* Release host controller F1 */ + sdio_release_host(gInstance->func[1]); + + if (gInstance->func[2]) { + /* Claim host controller F2 */ + sdio_claim_host(gInstance->func[2]); + + sd->client_block_size[2] = sd_f2_blocksize; + if (sdio_set_block_size(gInstance->func[2], + sd_f2_blocksize)) { + sd_err(("bcmsdh_sdmmc: Failed to set F2 " + "blocksize to %d\n", sd_f2_blocksize)); + } + + /* Release host controller F2 */ + sdio_release_host(gInstance->func[2]); + } + + sdioh_sdmmc_card_enablefuncs(sd); + } + } else { +#if !defined(OOB_INTR_ONLY) + sdio_claim_host(gInstance->func[0]); + sdio_claim_irq(gInstance->func[2], IRQHandlerF2); + sdio_claim_irq(gInstance->func[1], IRQHandler); + sdio_release_host(gInstance->func[0]); +#else /* defined(OOB_INTR_ONLY) */ +#if defined(HW_OOB) + sdioh_enable_func_intr(); +#endif + bcmsdh_oob_intr_set(TRUE); +#endif /* !defined(OOB_INTR_ONLY) */ + } + } + else + sd_err(("%s Failed\n", __FUNCTION__)); + + return (0); +} + +int +sdioh_stop(sdioh_info_t *si) +{ + /* MSM7201A Android sdio stack has bug with interrupt + So internaly within SDIO stack they are polling + which cause issue when device is turned off. So + unregister interrupt with SDIO stack to stop the + polling + */ + if (gInstance->func[0]) { +#if !defined(OOB_INTR_ONLY) + sdio_claim_host(gInstance->func[0]); + sdio_release_irq(gInstance->func[1]); + sdio_release_irq(gInstance->func[2]); + sdio_release_host(gInstance->func[0]); +#else /* defined(OOB_INTR_ONLY) */ +#if defined(HW_OOB) + sdioh_disable_func_intr(); +#endif + bcmsdh_oob_intr_set(FALSE); +#endif /* !defined(OOB_INTR_ONLY) */ + } + else + sd_err(("%s Failed\n", __FUNCTION__)); + return (0); +} diff --git a/drivers/net/wireless/bcm4329/bcmsdh_sdmmc_linux.c b/drivers/net/wireless/bcm4329/bcmsdh_sdmmc_linux.c new file mode 100644 index 0000000000000000000000000000000000000000..5a1a46c93571c0516f6f2c4ed827e38260fd4261 --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdh_sdmmc_linux.c @@ -0,0 +1,269 @@ +/* + * BCMSDH Function Driver for the native SDIO/MMC driver in the Linux Kernel + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdh_sdmmc_linux.c,v 1.1.2.5.6.17 2010/08/13 00:36:19 Exp $ + */ + +#include +#include +#include /* SDIO Specs */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* to get msglevel bit values */ + +#include /* request_irq() */ + +#include +#include +#include +#include + +#if !defined(SDIO_VENDOR_ID_BROADCOM) +#define SDIO_VENDOR_ID_BROADCOM 0x02d0 +#endif /* !defined(SDIO_VENDOR_ID_BROADCOM) */ + +#define SDIO_DEVICE_ID_BROADCOM_DEFAULT 0x0000 + +#if !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) +#define SDIO_DEVICE_ID_BROADCOM_4325_SDGWB 0x0492 /* BCM94325SDGWB */ +#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) */ +#if !defined(SDIO_DEVICE_ID_BROADCOM_4325) +#define SDIO_DEVICE_ID_BROADCOM_4325 0x0493 +#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325) */ +#if !defined(SDIO_DEVICE_ID_BROADCOM_4329) +#define SDIO_DEVICE_ID_BROADCOM_4329 0x4329 +#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4329) */ +#if !defined(SDIO_DEVICE_ID_BROADCOM_4319) +#define SDIO_DEVICE_ID_BROADCOM_4319 0x4319 +#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4329) */ + +#include + +#include + +extern void sdioh_sdmmc_devintr_off(sdioh_info_t *sd); +extern void sdioh_sdmmc_devintr_on(sdioh_info_t *sd); + +int sdio_function_init(void); +void sdio_function_cleanup(void); + +#define DESCRIPTION "bcmsdh_sdmmc Driver" +#define AUTHOR "Broadcom Corporation" + +/* module param defaults */ +static int clockoverride = 0; + +module_param(clockoverride, int, 0644); +MODULE_PARM_DESC(clockoverride, "SDIO card clock override"); + +PBCMSDH_SDMMC_INSTANCE gInstance; + +/* Maximum number of bcmsdh_sdmmc devices supported by driver */ +#define BCMSDH_SDMMC_MAX_DEVICES 1 + +extern int bcmsdh_probe(struct device *dev); +extern int bcmsdh_remove(struct device *dev); + +static int bcmsdh_sdmmc_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret = 0; + static struct sdio_func sdio_func_0; + sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); + sd_trace(("sdio_bcmsdh: func->class=%x\n", func->class)); + sd_trace(("sdio_vendor: 0x%04x\n", func->vendor)); + sd_trace(("sdio_device: 0x%04x\n", func->device)); + sd_trace(("Function#: 0x%04x\n", func->num)); + + if (func->num == 1) { + sdio_func_0.num = 0; + sdio_func_0.card = func->card; + gInstance->func[0] = &sdio_func_0; + if(func->device == 0x4) { /* 4318 */ + gInstance->func[2] = NULL; + sd_trace(("NIC found, calling bcmsdh_probe...\n")); + ret = bcmsdh_probe(&func->dev); + } + } + + gInstance->func[func->num] = func; + + if (func->num == 2) { + sd_trace(("F2 found, calling bcmsdh_probe...\n")); + ret = bcmsdh_probe(&func->dev); + } + + return ret; +} + +static void bcmsdh_sdmmc_remove(struct sdio_func *func) +{ + sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); + sd_info(("sdio_bcmsdh: func->class=%x\n", func->class)); + sd_info(("sdio_vendor: 0x%04x\n", func->vendor)); + sd_info(("sdio_device: 0x%04x\n", func->device)); + sd_info(("Function#: 0x%04x\n", func->num)); + + if (func->num == 2) { + sd_trace(("F2 found, calling bcmsdh_remove...\n")); + bcmsdh_remove(&func->dev); + } +} + +/* devices we support, null terminated */ +static const struct sdio_device_id bcmsdh_sdmmc_ids[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) }, + { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) }, + { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325) }, + { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4329) }, + { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4319) }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids); + +static struct sdio_driver bcmsdh_sdmmc_driver = { + .probe = bcmsdh_sdmmc_probe, + .remove = bcmsdh_sdmmc_remove, + .name = "bcmsdh_sdmmc", + .id_table = bcmsdh_sdmmc_ids, + }; + +struct sdos_info { + sdioh_info_t *sd; + spinlock_t lock; +}; + + +int +sdioh_sdmmc_osinit(sdioh_info_t *sd) +{ + struct sdos_info *sdos; + + sdos = (struct sdos_info*)MALLOC(sd->osh, sizeof(struct sdos_info)); + sd->sdos_info = (void*)sdos; + if (sdos == NULL) + return BCME_NOMEM; + + sdos->sd = sd; + spin_lock_init(&sdos->lock); + return BCME_OK; +} + +void +sdioh_sdmmc_osfree(sdioh_info_t *sd) +{ + struct sdos_info *sdos; + ASSERT(sd && sd->sdos_info); + + sdos = (struct sdos_info *)sd->sdos_info; + MFREE(sd->osh, sdos, sizeof(struct sdos_info)); +} + +/* Interrupt enable/disable */ +SDIOH_API_RC +sdioh_interrupt_set(sdioh_info_t *sd, bool enable) +{ + ulong flags; + struct sdos_info *sdos; + + sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling")); + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + +#if !defined(OOB_INTR_ONLY) + if (enable && !(sd->intr_handler && sd->intr_handler_arg)) { + sd_err(("%s: no handler registered, will not enable\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } +#endif /* !defined(OOB_INTR_ONLY) */ + + /* Ensure atomicity for enable/disable calls */ + spin_lock_irqsave(&sdos->lock, flags); + + sd->client_intr_enabled = enable; + if (enable) { + sdioh_sdmmc_devintr_on(sd); + } else { + sdioh_sdmmc_devintr_off(sd); + } + + spin_unlock_irqrestore(&sdos->lock, flags); + + return SDIOH_API_RC_SUCCESS; +} + + +#ifdef BCMSDH_MODULE +static int __init +bcmsdh_module_init(void) +{ + int error = 0; + sdio_function_init(); + return error; +} + +static void __exit +bcmsdh_module_cleanup(void) +{ + sdio_function_cleanup(); +} + +module_init(bcmsdh_module_init); +module_exit(bcmsdh_module_cleanup); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DESCRIPTION); +MODULE_AUTHOR(AUTHOR); + +#endif /* BCMSDH_MODULE */ +/* + * module init +*/ +int sdio_function_init(void) +{ + int error = 0; + sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); + + gInstance = kzalloc(sizeof(BCMSDH_SDMMC_INSTANCE), GFP_KERNEL); + if (!gInstance) + return -ENOMEM; + + error = sdio_register_driver(&bcmsdh_sdmmc_driver); + + return error; +} + +/* + * module cleanup +*/ +extern int bcmsdh_remove(struct device *dev); +void sdio_function_cleanup(void) +{ + sd_trace(("%s Enter\n", __FUNCTION__)); + + sdio_unregister_driver(&bcmsdh_sdmmc_driver); + + if (gInstance) + kfree(gInstance); +} diff --git a/drivers/net/wireless/bcm4329/bcmsdspi.c b/drivers/net/wireless/bcm4329/bcmsdspi.c new file mode 100644 index 0000000000000000000000000000000000000000..636539be5ea52b02453a5a35b65d6e94f22c24b3 --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdspi.c @@ -0,0 +1,1596 @@ +/* + * Broadcom BCMSDH to SPI Protocol Conversion Layer + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdspi.c,v 1.14.4.2.4.4.6.5 2010/03/10 03:09:48 Exp $ + */ + +#include + +#include +#include +#include +#include +#include +#include /* SDIO Device and Protocol Specs */ +#include /* SDIO Host Controller Specification */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* ioctl/iovars */ + +#include + + +#include +#include + +#include + +#define SD_PAGE 4096 + +/* Globals */ + +uint sd_msglevel = SDH_ERROR_VAL; +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 = 512; /* Default blocksize */ + +uint sd_divisor = 2; /* Default 33MHz/2 = 16MHz for dongle */ +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 */ +uint sd_pci_slot = 0xFFFFffff; /* Used to force selection of a particular PCI slot */ + +uint sd_toctl = 7; + +/* Prototypes */ +static bool sdspi_start_power(sdioh_info_t *sd); +static int sdspi_set_highspeed_mode(sdioh_info_t *sd, bool HSMode); +static int sdspi_card_enablefuncs(sdioh_info_t *sd); +static void sdspi_cmd_getrsp(sdioh_info_t *sd, uint32 *rsp_buffer, int count); +static int sdspi_cmd_issue(sdioh_info_t *sd, bool use_dma, uint32 cmd, uint32 arg, + uint32 *data, uint32 datalen); +static int sdspi_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, + int regsize, uint32 *data); +static int sdspi_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, + int regsize, uint32 data); +static int sdspi_driver_init(sdioh_info_t *sd); +static bool sdspi_reset(sdioh_info_t *sd, bool host_reset, bool client_reset); +static int sdspi_card_buf(sdioh_info_t *sd, int rw, int func, bool fifo, + uint32 addr, int nbytes, uint32 *data); +static int sdspi_abort(sdioh_info_t *sd, uint func); + +static int set_client_block_size(sdioh_info_t *sd, int func, int blocksize); + +static uint8 sdspi_crc7(unsigned char* p, uint32 len); +static uint16 sdspi_crc16(unsigned char* p, uint32 len); +static int sdspi_crc_onoff(sdioh_info_t *sd, bool use_crc); + +/* + * 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(("sdioh_attach: out of memory, malloced %d bytes\n", 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 = (uintptr)bar0; + sd->irq = irq; + sd->intr_handler = NULL; + sd->intr_handler_arg = NULL; + sd->intr_handler_valid = FALSE; + + /* Set defaults */ + sd->sd_blockmode = FALSE; + sd->use_client_ints = TRUE; + sd->sd_use_dma = FALSE; /* DMA Not supported */ + + /* Haven't figured out how to make bytemode work with dma */ + if (!sd->sd_blockmode) + sd->sd_use_dma = 0; + + 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 (sdspi_driver_init(sd) != SUCCESS) { + if (sdspi_driver_init(sd) != SUCCESS) { + sd_err(("%s:sdspi_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) { + if (sd->card_init_done) + sdspi_reset(sd, 1, 1); + + sd_info(("%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 + +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_CRC +}; + +const bcm_iovar_t sdioh_iovars[] = { + {"sd_msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0 }, + {"sd_blockmode", IOV_BLOCKMODE, 0, IOVT_BOOL, 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_crc", IOV_CRC, 0, IOVT_UINT32, 0 }, + {"sd_mode", IOV_SDMODE, 0, IOVT_UINT32, 100}, + {"sd_highspeed", IOV_HISPEED, 0, IOVT_UINT32, 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; + + 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_BLOCKMODE): + int_val = (int32)si->sd_blockmode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_BLOCKMODE): + si->sd_blockmode = (bool)int_val; + /* Haven't figured out how to make non-block mode with DMA */ + if (!si->sd_blockmode) + si->sd_use_dma = 0; + 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_SVAL(IOV_BLOCKSIZE): + { + uint func = ((uint32)int_val >> 16); + uint blksize = (uint16)int_val; + uint maxsize; + + if (func > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + + switch (func) { + case 0: maxsize = 32; break; + case 1: maxsize = BLOCK_SIZE_4318; break; + case 2: maxsize = BLOCK_SIZE_4328; break; + default: maxsize = 0; + } + if (blksize > maxsize) { + bcmerror = BCME_BADARG; + break; + } + if (!blksize) { + blksize = maxsize; + } + + /* Now set it */ + spi_lock(si); + bcmerror = set_client_block_size(si, func, blksize); + spi_unlock(si); + 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(("set clock failed!\n")); + 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_CRC): + int_val = (uint32)sd_crc; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_CRC): + /* Apply new setting, but don't change sd_crc until + * after the CRC-mode is selected in the device. This + * is required because the software must generate a + * correct CRC for the CMD59 in order to be able to + * turn OFF the CRC. + */ + sdspi_crc_onoff(si, int_val ? 1 : 0); + sd_crc = 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 (!sdspi_set_highspeed_mode(si, (bool)sd_hiok)) { + sd_err(("Failed changing highspeed mode to %d.\n", 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_HOSTREG): + { + break; + } + + case IOV_SVAL(IOV_HOSTREG): + { + sd_err(("IOV_HOSTREG unsupported\n")); + 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; + } + + + 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; + 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 foo; + uint8 *cis = cisd; + + sd_trace(("%s: Func = %d\n", __FUNCTION__, func)); + + if (!sd->func_cis_ptr[func]) { + bzero(cis, length); + return SDIOH_API_RC_FAIL; + } + + spi_lock(sd); + *cis = 0; + for (count = 0; count < length; count++) { + offset = sd->func_cis_ptr[func] + count; + if (sdspi_card_regread (sd, 0, offset, 1, &foo) < 0) { + sd_err(("%s: regread failed: Can't read CIS\n", __FUNCTION__)); + spi_unlock(sd); + return SDIOH_API_RC_FAIL; + } + *cis = (uint8)(foo & 0xff); + cis++; + } + spi_unlock(sd); + return SDIOH_API_RC_SUCCESS; +} + +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 rsp5; + + spi_lock(sd); + + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, rw == SDIOH_READ ? 0 : 1); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, rw == SDIOH_READ ? 0 : *byte); + + sd_trace(("%s: rw=%d, func=%d, regaddr=0x%08x\n", __FUNCTION__, rw, func, regaddr)); + + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, + SDIOH_CMD_52, cmd_arg, NULL, 0)) != SUCCESS) { + spi_unlock(sd); + return status; + } + + sdspi_cmd_getrsp(sd, &rsp5, 1); + if (rsp5 != 0x00) { + sd_err(("%s: rsp5 flags is 0x%x func=%d\n", + __FUNCTION__, rsp5, func)); + /* ASSERT(0); */ + spi_unlock(sd); + return SDIOH_API_RC_FAIL; + } + + if (rw == SDIOH_READ) + *byte = sd->card_rsp_data >> 24; + + 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 = sdspi_card_regread(sd, func, addr, nbytes, word); + else + status = sdspi_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: + * Bytemode: 1 block at a time. + */ + while (buflen > 0) { + if (sd->sd_blockmode) { + /* Max xfer is Page size */ + len = MIN(SD_PAGE, buflen); + + /* Round down to a block boundry */ + if (buflen > sd->client_block_size[func]) + len = (len/sd->client_block_size[func]) * + sd->client_block_size[func]; + } else { + /* Byte mode: One block at a time */ + len = MIN(sd->client_block_size[func], buflen); + } + + if (sdspi_card_buf(sd, rw, func, fifo, addr, len, (uint32 *)buffer) != SUCCESS) { + spi_unlock(sd); + return SDIOH_API_RC_FAIL; + } + buffer += len; + buflen -= len; + if (!fifo) + addr += len; + } + spi_unlock(sd); + return SDIOH_API_RC_SUCCESS; +} + +static int +sdspi_abort(sdioh_info_t *sd, uint func) +{ + uint8 spi_databuf[] = { 0x74, 0x80, 0x00, 0x0C, 0xFF, 0x95, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uint8 spi_rspbuf[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + int err = 0; + + sd_err(("Sending SPI Abort to F%d\n", func)); + spi_databuf[4] = func & 0x7; + /* write to function 0, addr 6 (IOABORT) func # in 3 LSBs. */ + spi_sendrecv(sd, spi_databuf, spi_rspbuf, sizeof(spi_databuf)); + + return err; +} + +extern int +sdioh_abort(sdioh_info_t *sd, uint fnum) +{ + int ret; + + spi_lock(sd); + ret = sdspi_abort(sd, fnum); + spi_unlock(sd); + + return ret; +} + +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 bool +sdspi_reset(sdioh_info_t *sd, bool host_reset, bool client_reset) +{ + if (!sd) + return TRUE; + + spi_lock(sd); + /* Reset client card */ + if (client_reset && (sd->adapter_slot != -1)) { + if (sdspi_card_regwrite(sd, 0, SDIOD_CCCR_IOABORT, 1, 0x8) != SUCCESS) + sd_err(("%s: Cannot write to card reg 0x%x\n", + __FUNCTION__, SDIOD_CCCR_IOABORT)); + else + sd->card_rca = 0; + } + + /* The host reset is a NOP in the sd-spi case. */ + if (host_reset) { + sd->sd_mode = SDIOH_MODE_SPI; + } + spi_unlock(sd); + return TRUE; +} + +static int +sdspi_host_init(sdioh_info_t *sd) +{ + sdspi_reset(sd, 1, 0); + + /* Default power on mode is SD1 */ + 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); +} + +#define CMD0_RETRIES 3 +#define CMD5_RETRIES 10 + +static int +get_ocr(sdioh_info_t *sd, uint32 *cmd_arg, uint32 *cmd_rsp) +{ + uint32 rsp5; + int retries, status; + + /* First issue a CMD0 to get the card into SPI mode. */ + for (retries = 0; retries <= CMD0_RETRIES; retries++) { + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, + SDIOH_CMD_0, *cmd_arg, NULL, 0)) != SUCCESS) { + sd_err(("%s: No response to CMD0\n", __FUNCTION__)); + continue; + } + + sdspi_cmd_getrsp(sd, &rsp5, 1); + + if (GFIELD(rsp5, SPI_RSP_ILL_CMD)) { + printf("%s: Card already initialized (continuing)\n", __FUNCTION__); + break; + } + + if (GFIELD(rsp5, SPI_RSP_IDLE)) { + printf("%s: Card in SPI mode\n", __FUNCTION__); + break; + } + } + + if (retries > CMD0_RETRIES) { + sd_err(("%s: Too many retries for CMD0\n", __FUNCTION__)); + return ERROR; + } + + /* Get the Card's Operation Condition. */ + /* Occasionally the board takes a while to become ready. */ + for (retries = 0; retries <= CMD5_RETRIES; retries++) { + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, + SDIOH_CMD_5, *cmd_arg, NULL, 0)) != SUCCESS) { + sd_err(("%s: No response to CMD5\n", __FUNCTION__)); + continue; + } + + printf("CMD5 response data was: 0x%08x\n", sd->card_rsp_data); + + if (GFIELD(sd->card_rsp_data, RSP4_CARD_READY)) { + printf("%s: Card ready\n", __FUNCTION__); + break; + } + } + + if (retries > CMD5_RETRIES) { + sd_err(("%s: Too many retries for CMD5\n", __FUNCTION__)); + return ERROR; + } + + *cmd_rsp = sd->card_rsp_data; + + sdspi_crc_onoff(sd, sd_crc ? 1 : 0); + + return (SUCCESS); +} + +static int +sdspi_crc_onoff(sdioh_info_t *sd, bool use_crc) +{ + uint32 args; + int status; + + args = use_crc ? 1 : 0; + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, + SDIOH_CMD_59, args, NULL, 0)) != SUCCESS) { + sd_err(("%s: No response to CMD59\n", __FUNCTION__)); + } + + sd_info(("CMD59 response data was: 0x%08x\n", sd->card_rsp_data)); + + sd_err(("SD-SPI CRC turned %s\n", use_crc ? "ON" : "OFF")); + return (SUCCESS); +} + +static int +sdspi_client_init(sdioh_info_t *sd) +{ + uint8 fn_ints; + + sd_trace(("%s: Powering up slot %d\n", __FUNCTION__, sd->adapter_slot)); + + /* Start at ~400KHz clock rate for initialization */ + if (!spi_start_clock(sd, 128)) { + sd_err(("spi_start_clock failed\n")); + return ERROR; + } + + if (!sdspi_start_power(sd)) { + sd_err(("sdspi_start_power failed\n")); + return ERROR; + } + + if (sd->num_funcs == 0) { + sd_err(("%s: No IO funcs!\n", __FUNCTION__)); + return ERROR; + } + + sdspi_card_enablefuncs(sd); + + set_client_block_size(sd, 1, BLOCK_SIZE_4318); + fn_ints = INTR_CTL_FUNC1_EN; + + if (sd->num_funcs >= 2) { + set_client_block_size(sd, 2, sd_f2_blocksize /* BLOCK_SIZE_4328 */); + fn_ints |= INTR_CTL_FUNC2_EN; + } + + /* Enable/Disable Client interrupts */ + /* Turn on here but disable at host controller */ + if (sdspi_card_regwrite(sd, 0, SDIOD_CCCR_INTEN, 1, + (fn_ints | INTR_CTL_MASTER_EN)) != SUCCESS) { + sd_err(("%s: Could not enable ints in CCCR\n", __FUNCTION__)); + return ERROR; + } + + /* Switch to High-speed clocking mode if both host and device support it */ + sdspi_set_highspeed_mode(sd, (bool)sd_hiok); + + /* After configuring for High-Speed mode, set the desired clock rate. */ + if (!spi_start_clock(sd, (uint16)sd_divisor)) { + sd_err(("spi_start_clock failed\n")); + return ERROR; + } + + sd->card_init_done = TRUE; + + return SUCCESS; +} + +static int +sdspi_set_highspeed_mode(sdioh_info_t *sd, bool HSMode) +{ + uint32 regdata; + int status; + bool hsmode; + + if (HSMode == TRUE) { + + sd_err(("Attempting to enable High-Speed mode.\n")); + + if ((status = sdspi_card_regread(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, ®data)) != SUCCESS) { + return status; + } + if (regdata & SDIO_SPEED_SHS) { + sd_err(("Device supports High-Speed mode.\n")); + + regdata |= SDIO_SPEED_EHS; + + sd_err(("Writing %08x to Card at %08x\n", + regdata, SDIOD_CCCR_SPEED_CONTROL)); + if ((status = sdspi_card_regwrite(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, regdata)) != BCME_OK) { + return status; + } + + hsmode = 1; + + sd_err(("High-speed clocking mode enabled.\n")); + } + else { + sd_err(("Device does not support High-Speed Mode.\n")); + hsmode = 0; + } + } else { + if ((status = sdspi_card_regread(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, ®data)) != SUCCESS) { + return status; + } + + regdata = ~SDIO_SPEED_EHS; + + sd_err(("Writing %08x to Card at %08x\n", + regdata, SDIOD_CCCR_SPEED_CONTROL)); + if ((status = sdspi_card_regwrite(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, regdata)) != BCME_OK) { + return status; + } + + sd_err(("Low-speed clocking mode enabled.\n")); + hsmode = 0; + } + + spi_controller_highspeed_mode(sd, hsmode); + + return TRUE; +} + +bool +sdspi_start_power(sdioh_info_t *sd) +{ + uint32 cmd_arg; + uint32 cmd_rsp; + + sd_trace(("%s\n", __FUNCTION__)); + + /* Get the Card's Operation Condition. Occasionally the board + * takes a while to become ready + */ + + cmd_arg = 0; + if (get_ocr(sd, &cmd_arg, &cmd_rsp) != SUCCESS) { + sd_err(("%s: Failed to get OCR; bailing\n", __FUNCTION__)); + return FALSE; + } + + sd_err(("mem_present = %d\n", GFIELD(cmd_rsp, RSP4_MEM_PRESENT))); + sd_err(("num_funcs = %d\n", GFIELD(cmd_rsp, RSP4_NUM_FUNCS))); + sd_err(("card_ready = %d\n", GFIELD(cmd_rsp, RSP4_CARD_READY))); + sd_err(("OCR = 0x%x\n", GFIELD(cmd_rsp, RSP4_IO_OCR))); + + /* Verify that the card supports I/O mode */ + if (GFIELD(cmd_rsp, RSP4_NUM_FUNCS) == 0) { + sd_err(("%s: Card does not support I/O\n", __FUNCTION__)); + return ERROR; + } + + sd->num_funcs = GFIELD(cmd_rsp, RSP4_NUM_FUNCS); + + /* Examine voltage: Arasan only supports 3.3 volts, + * so look for 3.2-3.3 Volts and also 3.3-3.4 volts. + */ + + if ((GFIELD(cmd_rsp, RSP4_IO_OCR) & (0x3 << 20)) == 0) { + sd_err(("This client does not support 3.3 volts!\n")); + return ERROR; + } + + + return TRUE; +} + +static int +sdspi_driver_init(sdioh_info_t *sd) +{ + sd_trace(("%s\n", __FUNCTION__)); + + if ((sdspi_host_init(sd)) != SUCCESS) { + return ERROR; + } + + if (sdspi_client_init(sd) != SUCCESS) { + return ERROR; + } + + return SUCCESS; +} + +static int +sdspi_card_enablefuncs(sdioh_info_t *sd) +{ + int status; + uint32 regdata; + uint32 regaddr, fbraddr; + uint8 func; + uint8 *ptr; + + sd_trace(("%s\n", __FUNCTION__)); + /* Get the Card's common CIS address */ + ptr = (uint8 *) &sd->com_cis_ptr; + for (regaddr = SDIOD_CCCR_CISPTR_0; regaddr <= SDIOD_CCCR_CISPTR_2; regaddr++) { + if ((status = sdspi_card_regread (sd, 0, regaddr, 1, ®data)) != SUCCESS) + return status; + + *ptr++ = (uint8) regdata; + } + + /* Only the lower 17-bits are valid */ + sd->com_cis_ptr &= 0x0001FFFF; + sd->func_cis_ptr[0] = sd->com_cis_ptr; + sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __FUNCTION__, sd->com_cis_ptr)); + + /* Get the Card's function CIS (for each function) */ + for (fbraddr = SDIOD_FBR_STARTADDR, func = 1; + func <= sd->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) { + ptr = (uint8 *) &sd->func_cis_ptr[func]; + for (regaddr = SDIOD_FBR_CISPTR_0; regaddr <= SDIOD_FBR_CISPTR_2; regaddr++) { + if ((status = sdspi_card_regread (sd, 0, regaddr + fbraddr, 1, ®data)) + != SUCCESS) + return status; + + *ptr++ = (uint8) regdata; + } + + /* Only the lower 17-bits are valid */ + sd->func_cis_ptr[func] &= 0x0001FFFF; + sd_info(("%s: Function %d CIS Ptr = 0x%x\n", + __FUNCTION__, func, sd->func_cis_ptr[func])); + } + + sd_info(("%s: write ESCI bit\n", __FUNCTION__)); + /* Enable continuous SPI interrupt (ESCI bit) */ + sdspi_card_regwrite(sd, 0, SDIOD_CCCR_BICTRL, 1, 0x60); + + sd_info(("%s: enable f1\n", __FUNCTION__)); + /* Enable function 1 on the card */ + regdata = SDIO_FUNC_ENABLE_1; + if ((status = sdspi_card_regwrite(sd, 0, SDIOD_CCCR_IOEN, 1, regdata)) != SUCCESS) + return status; + + sd_info(("%s: done\n", __FUNCTION__)); + return SUCCESS; +} + +/* Read client card reg */ +static int +sdspi_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 *data) +{ + int status; + uint32 cmd_arg; + uint32 rsp5; + + cmd_arg = 0; + + if ((func == 0) || (regsize == 1)) { + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, SDIOH_XFER_TYPE_READ); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, 0); + + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, SDIOH_CMD_52, cmd_arg, NULL, 0)) + != SUCCESS) + return status; + + sdspi_cmd_getrsp(sd, &rsp5, 1); + + if (rsp5 != 0x00) + sd_err(("%s: rsp5 flags is 0x%x\t %d\n", + __FUNCTION__, rsp5, func)); + + *data = sd->card_rsp_data >> 24; + } else { + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, regsize); + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 1); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 0); + cmd_arg = SFIELD(cmd_arg, CMD53_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD53_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_READ); + + sd->data_xfer_count = regsize; + + /* sdspi_cmd_issue() returns with the command complete bit + * in the ISR already cleared + */ + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, SDIOH_CMD_53, cmd_arg, NULL, 0)) + != SUCCESS) + return status; + + sdspi_cmd_getrsp(sd, &rsp5, 1); + + if (rsp5 != 0x00) + sd_err(("%s: rsp5 flags is 0x%x\t %d\n", + __FUNCTION__, rsp5, func)); + + *data = sd->card_rsp_data; + if (regsize == 2) { + *data &= 0xffff; + } + + sd_info(("%s: CMD53 func %d, addr 0x%x, size %d, data 0x%08x\n", + __FUNCTION__, func, regaddr, regsize, *data)); + + + } + + return SUCCESS; +} + +/* write a client register */ +static int +sdspi_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 data) +{ + int status; + uint32 cmd_arg, rsp5, flags; + + cmd_arg = 0; + + if ((func == 0) || (regsize == 1)) { + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, SDIOH_XFER_TYPE_WRITE); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, data & 0xff); + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, SDIOH_CMD_52, cmd_arg, NULL, 0)) + != SUCCESS) + return status; + + sdspi_cmd_getrsp(sd, &rsp5, 1); + flags = GFIELD(rsp5, RSP5_FLAGS); + if (flags && (flags != 0x10)) + sd_err(("%s: rsp5.rsp5.flags = 0x%x, expecting 0x10\n", + __FUNCTION__, flags)); + } + else { + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, regsize); + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 1); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 0); + cmd_arg = SFIELD(cmd_arg, CMD53_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD53_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_WRITE); + + sd->data_xfer_count = regsize; + sd->cmd53_wr_data = data; + + sd_info(("%s: CMD53 func %d, addr 0x%x, size %d, data 0x%08x\n", + __FUNCTION__, func, regaddr, regsize, data)); + + /* sdspi_cmd_issue() returns with the command complete bit + * in the ISR already cleared + */ + if ((status = sdspi_cmd_issue(sd, sd->sd_use_dma, SDIOH_CMD_53, cmd_arg, NULL, 0)) + != SUCCESS) + return status; + + sdspi_cmd_getrsp(sd, &rsp5, 1); + + if (rsp5 != 0x00) + sd_err(("%s: rsp5 flags = 0x%x, expecting 0x00\n", + __FUNCTION__, rsp5)); + + } + return SUCCESS; +} + +void +sdspi_cmd_getrsp(sdioh_info_t *sd, uint32 *rsp_buffer, int count /* num 32 bit words */) +{ + *rsp_buffer = sd->card_response; +} + +int max_errors = 0; + +#define SPI_MAX_PKT_LEN 768 +uint8 spi_databuf[SPI_MAX_PKT_LEN]; +uint8 spi_rspbuf[SPI_MAX_PKT_LEN]; + +/* datalen is used for CMD53 length only (0 for sd->data_xfer_count) */ +static int +sdspi_cmd_issue(sdioh_info_t *sd, bool use_dma, uint32 cmd, uint32 arg, + uint32 *data, uint32 datalen) +{ + uint32 cmd_reg; + uint32 cmd_arg = arg; + uint8 cmd_crc = 0x95; /* correct CRC for CMD0 and don't care for others. */ + uint16 dat_crc; + uint8 cmd52data = 0; + uint32 i, j; + uint32 spi_datalen = 0; + uint32 spi_pre_cmd_pad = 0; + uint32 spi_max_response_pad = 128; + + cmd_reg = 0; + cmd_reg = SFIELD(cmd_reg, SPI_DIR, 1); + cmd_reg = SFIELD(cmd_reg, SPI_CMD_INDEX, cmd); + + if (GFIELD(cmd_arg, CMD52_RW_FLAG) == 1) { /* Same for CMD52 and CMD53 */ + cmd_reg = SFIELD(cmd_reg, SPI_RW, 1); + } + + switch (cmd) { + case SDIOH_CMD_59: /* CRC_ON_OFF (SPI Mode Only) - Response R1 */ + cmd52data = arg & 0x1; + case SDIOH_CMD_0: /* Set Card to Idle State - No Response */ + case SDIOH_CMD_5: /* Send Operation condition - Response R4 */ + sd_trace(("%s: CMD%d\n", __FUNCTION__, cmd)); + spi_datalen = 44; + spi_pre_cmd_pad = 12; + spi_max_response_pad = 28; + break; + + case SDIOH_CMD_3: /* Ask card to send RCA - Response R6 */ + case SDIOH_CMD_7: /* Select card - Response R1 */ + case SDIOH_CMD_15: /* Set card to inactive state - Response None */ + sd_err(("%s: CMD%d is invalid for SPI Mode.\n", __FUNCTION__, cmd)); + return ERROR; + break; + + case SDIOH_CMD_52: /* IO R/W Direct (single byte) - Response R5 */ + cmd52data = GFIELD(cmd_arg, CMD52_DATA); + cmd_arg = arg; + cmd_reg = SFIELD(cmd_reg, SPI_FUNC, GFIELD(cmd_arg, CMD52_FUNCTION)); + cmd_reg = SFIELD(cmd_reg, SPI_ADDR, GFIELD(cmd_arg, CMD52_REG_ADDR)); + /* Display trace for byte write */ + if (GFIELD(cmd_arg, CMD52_RW_FLAG) == 1) { + sd_trace(("%s: CMD52: Wr F:%d @0x%04x=%02x\n", + __FUNCTION__, + GFIELD(cmd_arg, CMD52_FUNCTION), + GFIELD(cmd_arg, CMD52_REG_ADDR), + cmd52data)); + } + + spi_datalen = 32; + spi_max_response_pad = 28; + + break; + case SDIOH_CMD_53: /* IO R/W Extended (multiple bytes/blocks) */ + cmd_arg = arg; + cmd_reg = SFIELD(cmd_reg, SPI_FUNC, GFIELD(cmd_arg, CMD53_FUNCTION)); + cmd_reg = SFIELD(cmd_reg, SPI_ADDR, GFIELD(cmd_arg, CMD53_REG_ADDR)); + cmd_reg = SFIELD(cmd_reg, SPI_BLKMODE, 0); + cmd_reg = SFIELD(cmd_reg, SPI_OPCODE, GFIELD(cmd_arg, CMD53_OP_CODE)); + cmd_reg = SFIELD(cmd_reg, SPI_STUFF0, (sd->data_xfer_count>>8)); + cmd52data = (uint8)sd->data_xfer_count; + + /* Set upper bit in byte count if necessary, but don't set it for 512 bytes. */ + if ((sd->data_xfer_count > 255) && (sd->data_xfer_count < 512)) { + cmd_reg |= 1; + } + + if (GFIELD(cmd_reg, SPI_RW) == 1) { /* Write */ + spi_max_response_pad = 32; + spi_datalen = (sd->data_xfer_count + spi_max_response_pad) & 0xFFFC; + } else { /* Read */ + + spi_max_response_pad = 32; + spi_datalen = (sd->data_xfer_count + spi_max_response_pad) & 0xFFFC; + } + sd_trace(("%s: CMD53: %s F:%d @0x%04x len=0x%02x\n", + __FUNCTION__, + (GFIELD(cmd_reg, SPI_RW) == 1 ? "Wr" : "Rd"), + GFIELD(cmd_arg, CMD53_FUNCTION), + GFIELD(cmd_arg, CMD53_REG_ADDR), + cmd52data)); + break; + + default: + sd_err(("%s: Unknown command %d\n", __FUNCTION__, cmd)); + return ERROR; + } + + /* Set up and issue the SDIO command */ + memset(spi_databuf, SDSPI_IDLE_PAD, spi_datalen); + spi_databuf[spi_pre_cmd_pad + 0] = (cmd_reg & 0xFF000000) >> 24; + spi_databuf[spi_pre_cmd_pad + 1] = (cmd_reg & 0x00FF0000) >> 16; + spi_databuf[spi_pre_cmd_pad + 2] = (cmd_reg & 0x0000FF00) >> 8; + spi_databuf[spi_pre_cmd_pad + 3] = (cmd_reg & 0x000000FF); + spi_databuf[spi_pre_cmd_pad + 4] = cmd52data; + + /* Generate CRC7 for command, if CRC is enabled, otherwise, a + * default CRC7 of 0x95, which is correct for CMD0, is used. + */ + if (sd_crc) { + cmd_crc = sdspi_crc7(&spi_databuf[spi_pre_cmd_pad], 5); + } + spi_databuf[spi_pre_cmd_pad + 5] = cmd_crc; +#define SPI_STOP_TRAN 0xFD + + /* for CMD53 Write, put the data into the output buffer */ + if ((cmd == SDIOH_CMD_53) && (GFIELD(cmd_arg, CMD53_RW_FLAG) == 1)) { + if (datalen != 0) { + spi_databuf[spi_pre_cmd_pad + 9] = SDSPI_IDLE_PAD; + spi_databuf[spi_pre_cmd_pad + 10] = SDSPI_START_BLOCK; + + for (i = 0; i < sd->data_xfer_count; i++) { + spi_databuf[i + 11 + spi_pre_cmd_pad] = ((uint8 *)data)[i]; + } + if (sd_crc) { + dat_crc = sdspi_crc16(&spi_databuf[spi_pre_cmd_pad+11], i); + } else { + dat_crc = 0xAAAA; + } + spi_databuf[i + 11 + spi_pre_cmd_pad] = (dat_crc >> 8) & 0xFF; + spi_databuf[i + 12 + spi_pre_cmd_pad] = dat_crc & 0xFF; + } else if (sd->data_xfer_count == 2) { + spi_databuf[spi_pre_cmd_pad + 9] = SDSPI_IDLE_PAD; + spi_databuf[spi_pre_cmd_pad + 10] = SDSPI_START_BLOCK; + spi_databuf[spi_pre_cmd_pad + 11] = sd->cmd53_wr_data & 0xFF; + spi_databuf[spi_pre_cmd_pad + 12] = (sd->cmd53_wr_data & 0x0000FF00) >> 8; + if (sd_crc) { + dat_crc = sdspi_crc16(&spi_databuf[spi_pre_cmd_pad+11], 2); + } else { + dat_crc = 0x22AA; + } + spi_databuf[spi_pre_cmd_pad + 13] = (dat_crc >> 8) & 0xFF; + spi_databuf[spi_pre_cmd_pad + 14] = (dat_crc & 0xFF); + } else if (sd->data_xfer_count == 4) { + spi_databuf[spi_pre_cmd_pad + 9] = SDSPI_IDLE_PAD; + spi_databuf[spi_pre_cmd_pad + 10] = SDSPI_START_BLOCK; + spi_databuf[spi_pre_cmd_pad + 11] = sd->cmd53_wr_data & 0xFF; + spi_databuf[spi_pre_cmd_pad + 12] = (sd->cmd53_wr_data & 0x0000FF00) >> 8; + spi_databuf[spi_pre_cmd_pad + 13] = (sd->cmd53_wr_data & 0x00FF0000) >> 16; + spi_databuf[spi_pre_cmd_pad + 14] = (sd->cmd53_wr_data & 0xFF000000) >> 24; + if (sd_crc) { + dat_crc = sdspi_crc16(&spi_databuf[spi_pre_cmd_pad+11], 4); + } else { + dat_crc = 0x44AA; + } + spi_databuf[spi_pre_cmd_pad + 15] = (dat_crc >> 8) & 0xFF; + spi_databuf[spi_pre_cmd_pad + 16] = (dat_crc & 0xFF); + } else { + printf("CMD53 Write: size %d unsupported\n", sd->data_xfer_count); + } + } + + spi_sendrecv(sd, spi_databuf, spi_rspbuf, spi_datalen); + + for (i = spi_pre_cmd_pad + SDSPI_COMMAND_LEN; i < spi_max_response_pad; i++) { + if ((spi_rspbuf[i] & SDSPI_START_BIT_MASK) == 0) { + break; + } + } + + if (i == spi_max_response_pad) { + sd_err(("%s: Did not get a response for CMD%d\n", __FUNCTION__, cmd)); + return ERROR; + } + + /* Extract the response. */ + sd->card_response = spi_rspbuf[i]; + + /* for CMD53 Read, find the start of the response data... */ + if ((cmd == SDIOH_CMD_53) && (GFIELD(cmd_arg, CMD52_RW_FLAG) == 0)) { + for (; i < spi_max_response_pad; i++) { + if (spi_rspbuf[i] == SDSPI_START_BLOCK) { + break; + } + } + + if (i == spi_max_response_pad) { + printf("Did not get a start of data phase for CMD%d\n", cmd); + max_errors++; + sdspi_abort(sd, GFIELD(cmd_arg, CMD53_FUNCTION)); + } + sd->card_rsp_data = spi_rspbuf[i+1]; + sd->card_rsp_data |= spi_rspbuf[i+2] << 8; + sd->card_rsp_data |= spi_rspbuf[i+3] << 16; + sd->card_rsp_data |= spi_rspbuf[i+4] << 24; + + if (datalen != 0) { + i++; + for (j = 0; j < sd->data_xfer_count; j++) { + ((uint8 *)data)[j] = spi_rspbuf[i+j]; + } + if (sd_crc) { + uint16 recv_crc; + + recv_crc = spi_rspbuf[i+j] << 8 | spi_rspbuf[i+j+1]; + dat_crc = sdspi_crc16((uint8 *)data, datalen); + if (dat_crc != recv_crc) { + sd_err(("%s: Incorrect data CRC: expected 0x%04x, " + "received 0x%04x\n", + __FUNCTION__, dat_crc, recv_crc)); + } + } + } + return SUCCESS; + } + + sd->card_rsp_data = spi_rspbuf[i+4]; + sd->card_rsp_data |= spi_rspbuf[i+3] << 8; + sd->card_rsp_data |= spi_rspbuf[i+2] << 16; + sd->card_rsp_data |= spi_rspbuf[i+1] << 24; + + /* Display trace for byte read */ + if ((cmd == SDIOH_CMD_52) && (GFIELD(cmd_arg, CMD52_RW_FLAG) == 0)) { + sd_trace(("%s: CMD52: Rd F:%d @0x%04x=%02x\n", + __FUNCTION__, + GFIELD(cmd_arg, CMD53_FUNCTION), + GFIELD(cmd_arg, CMD53_REG_ADDR), + sd->card_rsp_data >> 24)); + } + + return SUCCESS; +} + +/* + * On entry: if single-block or non-block, buffer size <= block size. + * If multi-block, buffer size is unlimited. + * Question is how to handle the left-overs in either single- or multi-block. + * I think the caller should break the buffer up so this routine will always + * use block size == buffer size to handle the end piece of the buffer + */ + +static int +sdspi_card_buf(sdioh_info_t *sd, int rw, int func, bool fifo, uint32 addr, int nbytes, uint32 *data) +{ + int status; + uint32 cmd_arg; + uint32 rsp5; + int num_blocks, blocksize; + bool local_blockmode, local_dma; + bool read = rw == SDIOH_READ ? 1 : 0; + + ASSERT(nbytes); + + cmd_arg = 0; + sd_data(("%s: %s 53 func %d, %s, addr 0x%x, len %d bytes, r_cnt %d t_cnt %d\n", + __FUNCTION__, read ? "Rd" : "Wr", func, fifo ? "FIXED" : "INCR", + addr, nbytes, sd->r_cnt, sd->t_cnt)); + + if (read) sd->r_cnt++; else sd->t_cnt++; + + local_blockmode = sd->sd_blockmode; + local_dma = sd->sd_use_dma; + + /* Don't bother with block mode on small xfers */ + if (nbytes < sd->client_block_size[func]) { + sd_info(("setting local blockmode to false: nbytes (%d) != block_size (%d)\n", + nbytes, sd->client_block_size[func])); + local_blockmode = FALSE; + local_dma = FALSE; + } + + if (local_blockmode) { + blocksize = MIN(sd->client_block_size[func], nbytes); + num_blocks = nbytes/blocksize; + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, num_blocks); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 1); + } else { + num_blocks = 1; + blocksize = nbytes; + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, nbytes); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 0); + } + + if (fifo) + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 0); + else + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 1); + + cmd_arg = SFIELD(cmd_arg, CMD53_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD53_REG_ADDR, addr); + if (read) + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_READ); + else + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_WRITE); + + sd->data_xfer_count = nbytes; + if ((func == 2) && (fifo == 1)) { + sd_data(("%s: %s 53 func %d, %s, addr 0x%x, len %d bytes, r_cnt %d t_cnt %d\n", + __FUNCTION__, read ? "Rd" : "Wr", func, fifo ? "FIXED" : "INCR", + addr, nbytes, sd->r_cnt, sd->t_cnt)); + } + + /* sdspi_cmd_issue() returns with the command complete bit + * in the ISR already cleared + */ + if ((status = sdspi_cmd_issue(sd, local_dma, + SDIOH_CMD_53, cmd_arg, + data, nbytes)) != SUCCESS) { + sd_err(("%s: cmd_issue failed for %s\n", __FUNCTION__, (read ? "read" : "write"))); + return status; + } + + sdspi_cmd_getrsp(sd, &rsp5, 1); + + if (rsp5 != 0x00) { + sd_err(("%s: rsp5 flags = 0x%x, expecting 0x00\n", + __FUNCTION__, rsp5)); + return ERROR; + } + + return SUCCESS; +} + +static int +set_client_block_size(sdioh_info_t *sd, int func, int block_size) +{ + int base; + int err = 0; + + sd_err(("%s: Setting block size %d, func %d\n", __FUNCTION__, block_size, func)); + sd->client_block_size[func] = block_size; + + /* Set the block size in the SDIO Card register */ + base = func * SDIOD_FBR_SIZE; + err = sdspi_card_regwrite(sd, 0, base + SDIOD_CCCR_BLKSIZE_0, 1, block_size & 0xff); + if (!err) { + err = sdspi_card_regwrite(sd, 0, base + SDIOD_CCCR_BLKSIZE_1, 1, + (block_size >> 8) & 0xff); + } + + /* + * Do not set the block size in the SDIO Host register; that + * is func dependent and will get done on an individual + * transaction basis. + */ + + return (err ? BCME_SDIO_ERROR : 0); +} + +/* Reset and re-initialize the device */ +int +sdioh_sdio_reset(sdioh_info_t *si) +{ + si->card_init_done = FALSE; + return sdspi_client_init(si); +} + +#define CRC7_POLYNOM 0x09 +#define CRC7_CRCHIGHBIT 0x40 + +static uint8 sdspi_crc7(unsigned char* p, uint32 len) +{ + uint8 c, j, bit, crc = 0; + uint32 i; + + for (i = 0; i < len; i++) { + c = *p++; + for (j = 0x80; j; j >>= 1) { + bit = crc & CRC7_CRCHIGHBIT; + crc <<= 1; + if (c & j) bit ^= CRC7_CRCHIGHBIT; + if (bit) crc ^= CRC7_POLYNOM; + } + } + + /* Convert the CRC7 to an 8-bit SD CRC */ + crc = (crc << 1) | 1; + + return (crc); +} + +#define CRC16_POLYNOM 0x1021 +#define CRC16_CRCHIGHBIT 0x8000 + +static uint16 sdspi_crc16(unsigned char* p, uint32 len) +{ + uint32 i; + uint16 j, c, bit; + uint16 crc = 0; + + for (i = 0; i < len; i++) { + c = *p++; + for (j = 0x80; j; j >>= 1) { + bit = crc & CRC16_CRCHIGHBIT; + crc <<= 1; + if (c & j) bit ^= CRC16_CRCHIGHBIT; + if (bit) crc ^= CRC16_POLYNOM; + } + } + + return (crc); +} diff --git a/drivers/net/wireless/bcm4329/bcmsdspi_linux.c b/drivers/net/wireless/bcm4329/bcmsdspi_linux.c new file mode 100644 index 0000000000000000000000000000000000000000..e2e0ca6abe46c09865da1f166898ebcf6e8ff9de --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdspi_linux.c @@ -0,0 +1,252 @@ +/* + * Broadcom SPI Host Controller Driver - Linux Per-port + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdspi_linux.c,v 1.7.2.1.4.3 2008/06/30 21:09:36 Exp $ + */ + +#include +#include +#include + +#include /* SDIO Specs */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* to get msglevel bit values */ + +#include /* request_irq(), free_irq() */ + +#include +#include + +extern uint sd_crc; +module_param(sd_crc, uint, 0); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define KERNEL26 +#endif + +struct sdos_info { + sdioh_info_t *sd; + spinlock_t lock; + wait_queue_head_t intr_wait_queue; +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define BLOCKABLE() (!in_atomic()) +#else +#define BLOCKABLE() (!in_interrupt()) +#endif + +/* Interrupt handler */ +static irqreturn_t +sdspi_isr(int irq, void *dev_id +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) +, struct pt_regs *ptregs +#endif +) +{ + sdioh_info_t *sd; + struct sdos_info *sdos; + bool ours; + + sd = (sdioh_info_t *)dev_id; + sd->local_intrcount++; + + if (!sd->card_init_done) { + sd_err(("%s: Hey Bogus intr...not even initted: irq %d\n", __FUNCTION__, irq)); + return IRQ_RETVAL(FALSE); + } else { + ours = spi_check_client_intr(sd, NULL); + + /* For local interrupts, wake the waiting process */ + if (ours && sd->got_hcint) { + sdos = (struct sdos_info *)sd->sdos_info; + wake_up_interruptible(&sdos->intr_wait_queue); + } + + return IRQ_RETVAL(ours); + } +} + +/* Register with Linux for interrupts */ +int +spi_register_irq(sdioh_info_t *sd, uint irq) +{ + sd_trace(("Entering %s: irq == %d\n", __FUNCTION__, irq)); + if (request_irq(irq, sdspi_isr, IRQF_SHARED, "bcmsdspi", sd) < 0) { + sd_err(("%s: request_irq() failed\n", __FUNCTION__)); + return ERROR; + } + return SUCCESS; +} + +/* Free Linux irq */ +void +spi_free_irq(uint irq, sdioh_info_t *sd) +{ + free_irq(irq, sd); +} + +/* Map Host controller registers */ + +uint32 * +spi_reg_map(osl_t *osh, uintptr addr, int size) +{ + return (uint32 *)REG_MAP(addr, size); +} + +void +spi_reg_unmap(osl_t *osh, uintptr addr, int size) +{ + REG_UNMAP((void*)(uintptr)addr); +} + +int +spi_osinit(sdioh_info_t *sd) +{ + struct sdos_info *sdos; + + sdos = (struct sdos_info*)MALLOC(sd->osh, sizeof(struct sdos_info)); + sd->sdos_info = (void*)sdos; + if (sdos == NULL) + return BCME_NOMEM; + + sdos->sd = sd; + spin_lock_init(&sdos->lock); + init_waitqueue_head(&sdos->intr_wait_queue); + return BCME_OK; +} + +void +spi_osfree(sdioh_info_t *sd) +{ + struct sdos_info *sdos; + ASSERT(sd && sd->sdos_info); + + sdos = (struct sdos_info *)sd->sdos_info; + MFREE(sd->osh, sdos, sizeof(struct sdos_info)); +} + +/* Interrupt enable/disable */ +SDIOH_API_RC +sdioh_interrupt_set(sdioh_info_t *sd, bool enable) +{ + ulong flags; + struct sdos_info *sdos; + + sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling")); + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + + if (!(sd->host_init_done && sd->card_init_done)) { + sd_err(("%s: Card & Host are not initted - bailing\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } + + if (enable && !(sd->intr_handler && sd->intr_handler_arg)) { + sd_err(("%s: no handler registered, will not enable\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } + + /* Ensure atomicity for enable/disable calls */ + spin_lock_irqsave(&sdos->lock, flags); + + sd->client_intr_enabled = enable; + if (enable && !sd->lockcount) + spi_devintr_on(sd); + else + spi_devintr_off(sd); + + spin_unlock_irqrestore(&sdos->lock, flags); + + return SDIOH_API_RC_SUCCESS; +} + +/* Protect against reentrancy (disable device interrupts while executing) */ +void +spi_lock(sdioh_info_t *sd) +{ + ulong flags; + struct sdos_info *sdos; + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + + sd_trace(("%s: %d\n", __FUNCTION__, sd->lockcount)); + + spin_lock_irqsave(&sdos->lock, flags); + if (sd->lockcount) { + sd_err(("%s: Already locked!\n", __FUNCTION__)); + ASSERT(sd->lockcount == 0); + } + spi_devintr_off(sd); + sd->lockcount++; + spin_unlock_irqrestore(&sdos->lock, flags); +} + +/* Enable client interrupt */ +void +spi_unlock(sdioh_info_t *sd) +{ + ulong flags; + struct sdos_info *sdos; + + sd_trace(("%s: %d, %d\n", __FUNCTION__, sd->lockcount, sd->client_intr_enabled)); + ASSERT(sd->lockcount > 0); + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + + spin_lock_irqsave(&sdos->lock, flags); + if (--sd->lockcount == 0 && sd->client_intr_enabled) { + spi_devintr_on(sd); + } + spin_unlock_irqrestore(&sdos->lock, flags); +} + +void spi_waitbits(sdioh_info_t *sd, bool yield) +{ + struct sdos_info *sdos; + + sdos = (struct sdos_info *)sd->sdos_info; + +#ifndef BCMSDYIELD + ASSERT(!yield); +#endif + sd_trace(("%s: yield %d canblock %d\n", + __FUNCTION__, yield, BLOCKABLE())); + + /* Clear the "interrupt happened" flag and last intrstatus */ + sd->got_hcint = FALSE; + +#ifdef BCMSDYIELD + if (yield && BLOCKABLE()) { + /* Wait for the indication, the interrupt will be masked when the ISR fires. */ + wait_event_interruptible(sdos->intr_wait_queue, (sd->got_hcint)); + } else +#endif /* BCMSDYIELD */ + { + spi_spinbits(sd); + } + +} diff --git a/drivers/net/wireless/bcm4329/bcmsdstd.c b/drivers/net/wireless/bcm4329/bcmsdstd.c new file mode 100644 index 0000000000000000000000000000000000000000..0ca1f8ff8a24f4c8561a028b0edc41ebfa32c4a6 --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdstd.c @@ -0,0 +1,3127 @@ +/* + * 'Standard' SDIO HOST CONTROLLER driver + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdstd.c,v 1.64.4.1.4.4.2.18 2010/08/17 17:00:48 Exp $ + */ + +#include + +#include +#include +#include +#include +#include +#include /* SDIO Device and Protocol Specs */ +#include /* SDIO Host Controller Specification */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* ioctl/iovars */ +#include + + +#define SD_PAGE_BITS 12 +#define SD_PAGE (1 << SD_PAGE_BITS) + +#include + +/* Globals */ +uint sd_msglevel = SDH_ERROR_VAL; +uint sd_hiok = TRUE; /* Use hi-speed mode if available? */ +uint sd_sdmode = SDIOH_MODE_SD4; /* Use SD4 mode by default */ +uint sd_f2_blocksize = 64; /* Default blocksize */ + +#ifdef BCMSDYIELD +bool sd_yieldcpu = TRUE; /* Allow CPU yielding for buffer requests */ +uint sd_minyield = 0; /* Minimum xfer size to allow CPU yield */ +bool sd_forcerb = FALSE; /* Force sync readback in intrs_on/off */ +#endif + +uint sd_divisor = 2; /* Default 48MHz/2 = 24MHz */ + +uint sd_power = 1; /* Default to SD Slot powered ON */ +uint sd_clock = 1; /* Default to SD Clock turned ON */ +uint sd_pci_slot = 0xFFFFffff; /* Used to force selection of a particular PCI slot */ +uint8 sd_dma_mode = DMA_MODE_SDMA; /* Default to SDMA for now */ + +uint sd_toctl = 7; + +static bool trap_errs = FALSE; + +static const char *dma_mode_description[] = { "PIO", "SDMA", "ADMA1", "32b ADMA2", "64b ADMA2" }; + +/* Prototypes */ +static bool sdstd_start_clock(sdioh_info_t *sd, uint16 divisor); +static bool sdstd_start_power(sdioh_info_t *sd); +static bool sdstd_bus_width(sdioh_info_t *sd, int width); +static int sdstd_set_highspeed_mode(sdioh_info_t *sd, bool HSMode); +static int sdstd_set_dma_mode(sdioh_info_t *sd, int8 dma_mode); +static int sdstd_card_enablefuncs(sdioh_info_t *sd); +static void sdstd_cmd_getrsp(sdioh_info_t *sd, uint32 *rsp_buffer, int count); +static int sdstd_cmd_issue(sdioh_info_t *sd, bool use_dma, uint32 cmd, uint32 arg); +static int sdstd_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, + int regsize, uint32 *data); +static int sdstd_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, + int regsize, uint32 data); +static int sdstd_driver_init(sdioh_info_t *sd); +static bool sdstd_reset(sdioh_info_t *sd, bool host_reset, bool client_reset); +static int sdstd_card_buf(sdioh_info_t *sd, int rw, int func, bool fifo, + uint32 addr, int nbytes, uint32 *data); +static int sdstd_abort(sdioh_info_t *sd, uint func); +static int sdstd_check_errs(sdioh_info_t *sdioh_info, uint32 cmd, uint32 arg); +static int set_client_block_size(sdioh_info_t *sd, int func, int blocksize); +static void sd_map_dma(sdioh_info_t * sd); +static void sd_unmap_dma(sdioh_info_t * sd); +static void sd_clear_adma_dscr_buf(sdioh_info_t *sd); +static void sd_fill_dma_data_buf(sdioh_info_t *sd, uint8 data); +static void sd_create_adma_descriptor(sdioh_info_t *sd, + uint32 index, uint32 addr_phys, + uint16 length, uint16 flags); +static void sd_dump_adma_dscr(sdioh_info_t *sd); +static void sdstd_dumpregs(sdioh_info_t *sd); + + +/* + * Private register access routines. + */ + +/* 16 bit PCI regs */ + +extern uint16 sdstd_rreg16(sdioh_info_t *sd, uint reg); +uint16 +sdstd_rreg16(sdioh_info_t *sd, uint reg) +{ + + volatile uint16 data = *(volatile uint16 *)(sd->mem_space + reg); + sd_ctrl(("16: R Reg 0x%02x, Data 0x%x\n", reg, data)); + return data; +} + +extern void sdstd_wreg16(sdioh_info_t *sd, uint reg, uint16 data); +void +sdstd_wreg16(sdioh_info_t *sd, uint reg, uint16 data) +{ + *(volatile uint16 *)(sd->mem_space + reg) = (uint16)data; + sd_ctrl(("16: W Reg 0x%02x, Data 0x%x\n", reg, data)); +} + +static void +sdstd_or_reg16(sdioh_info_t *sd, uint reg, uint16 val) +{ + volatile uint16 data = *(volatile uint16 *)(sd->mem_space + reg); + sd_ctrl(("16: OR Reg 0x%02x, Val 0x%x\n", reg, val)); + data |= val; + *(volatile uint16 *)(sd->mem_space + reg) = (uint16)data; + +} +static void +sdstd_mod_reg16(sdioh_info_t *sd, uint reg, int16 mask, uint16 val) +{ + + volatile uint16 data = *(volatile uint16 *)(sd->mem_space + reg); + sd_ctrl(("16: MOD Reg 0x%02x, Mask 0x%x, Val 0x%x\n", reg, mask, val)); + data &= ~mask; + data |= (val & mask); + *(volatile uint16 *)(sd->mem_space + reg) = (uint16)data; +} + + +/* 32 bit PCI regs */ +static uint32 +sdstd_rreg(sdioh_info_t *sd, uint reg) +{ + volatile uint32 data = *(volatile uint32 *)(sd->mem_space + reg); + sd_ctrl(("32: R Reg 0x%02x, Data 0x%x\n", reg, data)); + return data; +} +static inline void +sdstd_wreg(sdioh_info_t *sd, uint reg, uint32 data) +{ + *(volatile uint32 *)(sd->mem_space + reg) = (uint32)data; + sd_ctrl(("32: W Reg 0x%02x, Data 0x%x\n", reg, data)); + +} + +/* 8 bit PCI regs */ +static inline void +sdstd_wreg8(sdioh_info_t *sd, uint reg, uint8 data) +{ + *(volatile uint8 *)(sd->mem_space + reg) = (uint8)data; + sd_ctrl(("08: W Reg 0x%02x, Data 0x%x\n", reg, data)); +} +static uint8 +sdstd_rreg8(sdioh_info_t *sd, uint reg) +{ + volatile uint8 data = *(volatile uint8 *)(sd->mem_space + reg); + sd_ctrl(("08: R Reg 0x%02x, Data 0x%x\n", reg, data)); + return data; +} + +/* + * Private work routines + */ + +sdioh_info_t *glob_sd; + +/* + * 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(("sdioh_attach: out of memory, malloced %d bytes\n", MALLOCED(osh))); + return NULL; + } + bzero((char *)sd, sizeof(sdioh_info_t)); + glob_sd = sd; + sd->osh = osh; + if (sdstd_osinit(sd) != 0) { + sd_err(("%s:sdstd_osinit() failed\n", __FUNCTION__)); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return NULL; + } + sd->mem_space = (volatile char *)sdstd_reg_map(osh, (uintptr)bar0, SDIOH_REG_WINSZ); + sd_init_dma(sd); + sd->irq = irq; + if (sd->mem_space == NULL) { + sd_err(("%s:ioremap() failed\n", __FUNCTION__)); + sdstd_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return NULL; + } + sd_info(("%s:sd->mem_space = %p\n", __FUNCTION__, sd->mem_space)); + sd->intr_handler = NULL; + sd->intr_handler_arg = NULL; + sd->intr_handler_valid = FALSE; + + /* Set defaults */ + sd->sd_blockmode = TRUE; + sd->use_client_ints = TRUE; + sd->sd_dma_mode = sd_dma_mode; + + if (!sd->sd_blockmode) + sd->sd_dma_mode = DMA_MODE_NONE; + + if (sdstd_driver_init(sd) != SUCCESS) { + /* If host CPU was reset without resetting SD bus or + SD device, the device will still have its RCA but + driver no longer knows what it is (since driver has been restarted). + go through once to clear the RCA and a gain reassign it. + */ + sd_info(("driver_init failed - Reset RCA and try again\n")); + if (sdstd_driver_init(sd) != SUCCESS) { + sd_err(("%s:driver_init() failed()\n", __FUNCTION__)); + if (sd->mem_space) { + sdstd_reg_unmap(osh, (uintptr)sd->mem_space, SDIOH_REG_WINSZ); + sd->mem_space = NULL; + } + sdstd_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return (NULL); + } + } + + OSL_DMADDRWIDTH(osh, 32); + + /* Always map DMA buffers, so we can switch between DMA modes. */ + sd_map_dma(sd); + + if (sdstd_register_irq(sd, irq) != SUCCESS) { + sd_err(("%s: sdstd_register_irq() failed for irq = %d\n", __FUNCTION__, irq)); + sdstd_free_irq(sd->irq, sd); + if (sd->mem_space) { + sdstd_reg_unmap(osh, (uintptr)sd->mem_space, SDIOH_REG_WINSZ); + sd->mem_space = NULL; + } + + sdstd_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_unmap_dma(sd); + sdstd_wreg16(sd, SD_IntrSignalEnable, 0); + sd_trace(("%s: freeing irq %d\n", __FUNCTION__, sd->irq)); + sdstd_free_irq(sd->irq, sd); + if (sd->card_init_done) + sdstd_reset(sd, 1, 1); + if (sd->mem_space) { + sdstd_reg_unmap(osh, (uintptr)sd->mem_space, SDIOH_REG_WINSZ); + sd->mem_space = NULL; + } + + sdstd_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + } + return SDIOH_API_RC_SUCCESS; +} + +/* Configure callback to client when we receive 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) +{ + uint16 intrstatus; + intrstatus = sdstd_rreg16(sd, SD_IntrStatus); + return !!(intrstatus & CLIENT_INTR); +} +#endif + +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_YIELDCPU, + IOV_MINYIELD, + IOV_FORCERB, + IOV_CLOCK +}; + +const bcm_iovar_t sdioh_iovars[] = { + {"sd_msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0 }, + {"sd_blockmode", IOV_BLOCKMODE, 0, IOVT_BOOL, 0 }, + {"sd_blocksize", IOV_BLOCKSIZE, 0, IOVT_UINT32, 0 }, /* ((fn << 16) | size) */ + {"sd_dma", IOV_DMA, 0, IOVT_UINT32, 0 }, +#ifdef BCMSDYIELD + {"sd_yieldcpu", IOV_YIELDCPU, 0, IOVT_BOOL, 0 }, + {"sd_minyield", IOV_MINYIELD, 0, IOVT_UINT32, 0 }, + {"sd_forcerb", IOV_FORCERB, 0, IOVT_BOOL, 0 }, +#endif + {"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}, + {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; + + 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_BLOCKMODE): + int_val = (int32)si->sd_blockmode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_BLOCKMODE): + si->sd_blockmode = (bool)int_val; + /* Haven't figured out how to make non-block mode with DMA */ + if (!si->sd_blockmode) + si->sd_dma_mode = DMA_MODE_NONE; + break; + +#ifdef BCMSDYIELD + case IOV_GVAL(IOV_YIELDCPU): + int_val = sd_yieldcpu; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_YIELDCPU): + sd_yieldcpu = (bool)int_val; + break; + + case IOV_GVAL(IOV_MINYIELD): + int_val = sd_minyield; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_MINYIELD): + sd_minyield = (bool)int_val; + break; + + case IOV_GVAL(IOV_FORCERB): + int_val = sd_forcerb; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_FORCERB): + sd_forcerb = (bool)int_val; + break; +#endif /* BCMSDYIELD */ + + 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_SVAL(IOV_BLOCKSIZE): + { + uint func = ((uint32)int_val >> 16); + uint blksize = (uint16)int_val; + uint maxsize; + + if (func > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + + switch (func) { + case 0: maxsize = 32; break; + case 1: maxsize = BLOCK_SIZE_4318; break; + case 2: maxsize = BLOCK_SIZE_4328; break; + default: maxsize = 0; + } + if (blksize > maxsize) { + bcmerror = BCME_BADARG; + break; + } + if (!blksize) { + blksize = maxsize; + } + + /* Now set it */ + sdstd_lock(si); + bcmerror = set_client_block_size(si, func, blksize); + sdstd_unlock(si); + break; + } + + case IOV_GVAL(IOV_DMA): + int_val = (int32)si->sd_dma_mode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_DMA): + si->sd_dma_mode = (char)int_val; + sdstd_set_dma_mode(si, si->sd_dma_mode); + 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): + si->use_client_ints = (bool)int_val; + if (si->use_client_ints) + si->intmask |= CLIENT_INTR; + else + si->intmask &= ~CLIENT_INTR; + 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 (!sdstd_start_clock(si, (uint16)sd_divisor)) { + sd_err(("set clock failed!\n")); + 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; + if (sd_power == 1) { + if (sdstd_driver_init(si) != SUCCESS) { + sd_err(("set SD Slot power failed!\n")); + bcmerror = BCME_ERROR; + } else { + sd_err(("SD Slot Powered ON.\n")); + } + } else { + uint8 pwr = 0; + + pwr = SFIELD(pwr, PWR_BUS_EN, 0); + sdstd_wreg8(si, SD_PwrCntrl, pwr); /* Set Voltage level */ + sd_err(("SD Slot Powered OFF.\n")); + } + 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; + if (sd_clock == 1) { + sd_info(("SD Clock turned ON.\n")); + if (!sdstd_start_clock(si, (uint16)sd_divisor)) { + sd_err(("sdstd_start_clock failed\n")); + bcmerror = BCME_ERROR; + } + } else { + /* turn off HC clock */ + sdstd_wreg16(si, SD_ClockCntrl, + sdstd_rreg16(si, SD_ClockCntrl) & ~((uint16)0x4)); + + sd_info(("SD Clock turned OFF.\n")); + } + 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; + + if (!sdstd_bus_width(si, sd_sdmode)) { + sd_err(("sdstd_bus_width failed\n")); + bcmerror = BCME_ERROR; + } + 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; + bcmerror = sdstd_set_highspeed_mode(si, (bool)sd_hiok); + 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_HOSTREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + + if (sd_ptr->offset < SD_SysAddr || sd_ptr->offset > SD_MaxCurCap) { + sd_err(("%s: bad offset 0x%x\n", __FUNCTION__, sd_ptr->offset)); + bcmerror = BCME_BADARG; + break; + } + + sd_trace(("%s: rreg%d at offset %d\n", __FUNCTION__, + (sd_ptr->offset & 1) ? 8 : ((sd_ptr->offset & 2) ? 16 : 32), + sd_ptr->offset)); + if (sd_ptr->offset & 1) + int_val = sdstd_rreg8(si, sd_ptr->offset); + else if (sd_ptr->offset & 2) + int_val = sdstd_rreg16(si, sd_ptr->offset); + else + int_val = sdstd_rreg(si, sd_ptr->offset); + + bcopy(&int_val, arg, sizeof(int_val)); + break; + } + + case IOV_SVAL(IOV_HOSTREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + + if (sd_ptr->offset < SD_SysAddr || sd_ptr->offset > SD_MaxCurCap) { + sd_err(("%s: bad offset 0x%x\n", __FUNCTION__, sd_ptr->offset)); + bcmerror = BCME_BADARG; + break; + } + + sd_trace(("%s: wreg%d value 0x%08x at offset %d\n", __FUNCTION__, sd_ptr->value, + (sd_ptr->offset & 1) ? 8 : ((sd_ptr->offset & 2) ? 16 : 32), + sd_ptr->offset)); + if (sd_ptr->offset & 1) + sdstd_wreg8(si, sd_ptr->offset, (uint8)sd_ptr->value); + else if (sd_ptr->offset & 2) + sdstd_wreg16(si, sd_ptr->offset, (uint16)sd_ptr->value); + else + sdstd_wreg(si, sd_ptr->offset, (uint32)sd_ptr->value); + + 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; + } + + + 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; + 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 foo; + uint8 *cis = cisd; + + sd_trace(("%s: Func = %d\n", __FUNCTION__, func)); + + if (!sd->func_cis_ptr[func]) { + bzero(cis, length); + return SDIOH_API_RC_FAIL; + } + + sdstd_lock(sd); + *cis = 0; + for (count = 0; count < length; count++) { + offset = sd->func_cis_ptr[func] + count; + if (sdstd_card_regread(sd, 0, offset, 1, &foo)) { + sd_err(("%s: regread failed: Can't read CIS\n", __FUNCTION__)); + sdstd_unlock(sd); + return SDIOH_API_RC_FAIL; + } + *cis = (uint8)(foo & 0xff); + cis++; + } + sdstd_unlock(sd); + return SDIOH_API_RC_SUCCESS; +} + +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 rsp5; + + sdstd_lock(sd); + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, rw == SDIOH_READ ? 0 : 1); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, rw == SDIOH_READ ? 0 : *byte); + + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_52, cmd_arg)) != SUCCESS) { + sdstd_unlock(sd); + return status; + } + + sdstd_cmd_getrsp(sd, &rsp5, 1); + if (sdstd_rreg16 (sd, SD_ErrorIntrStatus) != 0) { + sd_err(("%s: 1: ErrorintrStatus 0x%x\n", + __FUNCTION__, sdstd_rreg16(sd, SD_ErrorIntrStatus))); + } + if (GFIELD(rsp5, RSP5_FLAGS) != 0x10) + sd_err(("%s: rsp5 flags is 0x%x\t %d\n", + __FUNCTION__, GFIELD(rsp5, RSP5_FLAGS), func)); + + if (GFIELD(rsp5, RSP5_STUFF)) + sd_err(("%s: rsp5 stuff is 0x%x: should be 0\n", + __FUNCTION__, GFIELD(rsp5, RSP5_STUFF))); + + if (rw == SDIOH_READ) + *byte = GFIELD(rsp5, RSP5_DATA); + + sdstd_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; + bool swap = FALSE; + + sdstd_lock(sd); + + if (rw == SDIOH_READ) { + status = sdstd_card_regread(sd, func, addr, nbytes, word); + if (swap) + *word = BCMSWAP32(*word); + } else { + if (swap) + *word = BCMSWAP32(*word); + status = sdstd_card_regwrite(sd, func, addr, nbytes, *word); + } + + sdstd_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); + uint8 *localbuf = NULL, *tmpbuf = NULL; + uint tmplen = 0; + bool local_blockmode = sd->sd_blockmode; + + sdstd_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: + * Bytemode: 1 block at a time. + * Blockmode: Multiples of blocksizes at a time w/ max of SD_PAGE. + * Both: leftovers are handled last (will be sent via bytemode). + */ + while (buflen > 0) { + if (local_blockmode) { + /* Max xfer is Page size */ + len = MIN(SD_PAGE, buflen); + + /* Round down to a block boundry */ + if (buflen > sd->client_block_size[func]) + len = (len/sd->client_block_size[func]) * + sd->client_block_size[func]; + if ((func == SDIO_FUNC_1) && ((len % 4) == 3) && (rw == SDIOH_WRITE)) { + tmplen = len; + sd_err(("%s: Rounding up buffer to mod4 length.\n", __FUNCTION__)); + len++; + tmpbuf = buffer; + if ((localbuf = (uint8 *)MALLOC(sd->osh, len)) == NULL) { + sd_err(("out of memory, malloced %d bytes\n", + MALLOCED(sd->osh))); + sdstd_unlock(sd); + return SDIOH_API_RC_FAIL; + } + bcopy(buffer, localbuf, len); + buffer = localbuf; + } + } else { + /* Byte mode: One block at a time */ + len = MIN(sd->client_block_size[func], buflen); + } + + if (sdstd_card_buf(sd, rw, func, fifo, addr, len, (uint32 *)buffer) != SUCCESS) { + sdstd_unlock(sd); + return SDIOH_API_RC_FAIL; + } + + if (local_blockmode) { + if ((func == SDIO_FUNC_1) && ((tmplen % 4) == 3) && (rw == SDIOH_WRITE)) { + if (localbuf) + MFREE(sd->osh, localbuf, len); + len--; + buffer = tmpbuf; + sd_err(("%s: Restoring back buffer ptr and len.\n", __FUNCTION__)); + } + } + + buffer += len; + buflen -= len; + if (!fifo) + addr += len; + } + sdstd_unlock(sd); + return SDIOH_API_RC_SUCCESS; +} + +static +int sdstd_abort(sdioh_info_t *sd, uint func) +{ + int err = 0; + int retries; + + uint16 cmd_reg; + uint32 cmd_arg; + uint32 rsp5; + uint8 rflags; + + uint16 int_reg = 0; + uint16 plain_intstatus; + + /* Argument is write to F0 (CCCR) IOAbort with function number */ + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, SDIO_FUNC_0); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, SDIOD_CCCR_IOABORT); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, SD_IO_OP_WRITE); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, func); + + /* Command is CMD52 write */ + cmd_reg = 0; + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_48_BUSY); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_ABORT); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, SDIOH_CMD_52); + + if (sd->sd_mode == SDIOH_MODE_SPI) { + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 0); + } + + /* Wait for CMD_INHIBIT to go away as per spec section 3.6.1.1 */ + retries = RETRIES_SMALL; + while (GFIELD(sdstd_rreg(sd, SD_PresentState), PRES_CMD_INHIBIT)) { + if (retries == RETRIES_SMALL) + sd_err(("%s: Waiting for Command Inhibit, state 0x%08x\n", + __FUNCTION__, sdstd_rreg(sd, SD_PresentState))); + if (!--retries) { + sd_err(("%s: Command Inhibit timeout, state 0x%08x\n", + __FUNCTION__, sdstd_rreg(sd, SD_PresentState))); + if (trap_errs) + ASSERT(0); + err = BCME_SDIO_ERROR; + goto done; + } + } + + /* Clear errors from any previous commands */ + if ((plain_intstatus = sdstd_rreg16(sd, SD_ErrorIntrStatus)) != 0) { + sd_err(("abort: clearing errstat 0x%04x\n", plain_intstatus)); + sdstd_wreg16(sd, SD_ErrorIntrStatus, plain_intstatus); + } + plain_intstatus = sdstd_rreg16(sd, SD_IntrStatus); + if (plain_intstatus & ~(SFIELD(0, INTSTAT_CARD_INT, 1))) { + sd_err(("abort: intstatus 0x%04x\n", plain_intstatus)); + if (GFIELD(plain_intstatus, INTSTAT_CMD_COMPLETE)) { + sd_err(("SDSTD_ABORT: CMD COMPLETE SET BEFORE COMMAND GIVEN!!!\n")); + } + if (GFIELD(plain_intstatus, INTSTAT_CARD_REMOVAL)) { + sd_err(("SDSTD_ABORT: INTSTAT_CARD_REMOVAL\n")); + err = BCME_NODEVICE; + goto done; + } + } + + /* Issue the command */ + sdstd_wreg(sd, SD_Arg0, cmd_arg); + sdstd_wreg16(sd, SD_Command, cmd_reg); + + /* In interrupt mode return, expect later CMD_COMPLETE interrupt */ + if (!sd->polled_mode) + return err; + + /* Otherwise, wait for the command to complete */ + retries = RETRIES_LARGE; + do { + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + } while (--retries && + (GFIELD(int_reg, INTSTAT_ERROR_INT) == 0) && + (GFIELD(int_reg, INTSTAT_CMD_COMPLETE) == 0)); + + /* If command completion fails, do a cmd reset and note the error */ + if (!retries) { + sd_err(("%s: CMD_COMPLETE timeout: intr 0x%04x err 0x%04x state 0x%08x\n", + __FUNCTION__, int_reg, + sdstd_rreg16(sd, SD_ErrorIntrStatus), + sdstd_rreg(sd, SD_PresentState))); + + sdstd_wreg8(sd, SD_SoftwareReset, SFIELD(0, SW_RESET_CMD, 1)); + retries = RETRIES_LARGE; + do { + sd_trace(("%s: waiting for CMD line reset\n", __FUNCTION__)); + } while ((GFIELD(sdstd_rreg8(sd, SD_SoftwareReset), + SW_RESET_CMD)) && retries--); + + if (!retries) { + sd_err(("%s: Timeout waiting for CMD line reset\n", __FUNCTION__)); + } + + if (trap_errs) + ASSERT(0); + + err = BCME_SDIO_ERROR; + } + + /* Clear Command Complete interrupt */ + int_reg = SFIELD(0, INTSTAT_CMD_COMPLETE, 1); + sdstd_wreg16(sd, SD_IntrStatus, int_reg); + + /* Check for Errors */ + if ((plain_intstatus = sdstd_rreg16 (sd, SD_ErrorIntrStatus)) != 0) { + sd_err(("%s: ErrorintrStatus: 0x%x, " + "(intrstatus = 0x%x, present state 0x%x) clearing\n", + __FUNCTION__, plain_intstatus, + sdstd_rreg16(sd, SD_IntrStatus), + sdstd_rreg(sd, SD_PresentState))); + + sdstd_wreg16(sd, SD_ErrorIntrStatus, plain_intstatus); + + sdstd_wreg8(sd, SD_SoftwareReset, SFIELD(0, SW_RESET_DAT, 1)); + retries = RETRIES_LARGE; + do { + sd_trace(("%s: waiting for DAT line reset\n", __FUNCTION__)); + } while ((GFIELD(sdstd_rreg8(sd, SD_SoftwareReset), + SW_RESET_DAT)) && retries--); + + if (!retries) { + sd_err(("%s: Timeout waiting for DAT line reset\n", __FUNCTION__)); + } + + if (trap_errs) + ASSERT(0); + + /* ABORT is dataless, only cmd errs count */ + if (plain_intstatus & ERRINT_CMD_ERRS) + err = BCME_SDIO_ERROR; + } + + /* If command failed don't bother looking at response */ + if (err) + goto done; + + /* Otherwise, check the response */ + sdstd_cmd_getrsp(sd, &rsp5, 1); + rflags = GFIELD(rsp5, RSP5_FLAGS); + + if (rflags & SD_RSP_R5_ERRBITS) { + sd_err(("%s: R5 flags include errbits: 0x%02x\n", __FUNCTION__, rflags)); + + /* The CRC error flag applies to the previous command */ + if (rflags & (SD_RSP_R5_ERRBITS & ~SD_RSP_R5_COM_CRC_ERROR)) { + err = BCME_SDIO_ERROR; + goto done; + } + } + + if (((rflags & (SD_RSP_R5_IO_CURRENTSTATE0 | SD_RSP_R5_IO_CURRENTSTATE1)) != 0x10) && + ((rflags & (SD_RSP_R5_IO_CURRENTSTATE0 | SD_RSP_R5_IO_CURRENTSTATE1)) != 0x20)) { + sd_err(("%s: R5 flags has bad state: 0x%02x\n", __FUNCTION__, rflags)); + err = BCME_SDIO_ERROR; + goto done; + } + + if (GFIELD(rsp5, RSP5_STUFF)) { + sd_err(("%s: rsp5 stuff is 0x%x: should be 0\n", + __FUNCTION__, GFIELD(rsp5, RSP5_STUFF))); + err = BCME_SDIO_ERROR; + goto done; + } + +done: + if (err == BCME_NODEVICE) + return err; + + sdstd_wreg8(sd, SD_SoftwareReset, + SFIELD(SFIELD(0, SW_RESET_DAT, 1), SW_RESET_CMD, 1)); + + retries = RETRIES_LARGE; + do { + rflags = sdstd_rreg8(sd, SD_SoftwareReset); + if (!GFIELD(rflags, SW_RESET_DAT) && !GFIELD(rflags, SW_RESET_CMD)) + break; + } while (--retries); + + if (!retries) { + sd_err(("%s: Timeout waiting for DAT/CMD reset: 0x%02x\n", + __FUNCTION__, rflags)); + err = BCME_SDIO_ERROR; + } + + return err; +} + +extern int +sdioh_abort(sdioh_info_t *sd, uint fnum) +{ + int ret; + + sdstd_lock(sd); + ret = sdstd_abort(sd, fnum); + sdstd_unlock(sd); + + return ret; +} + +int +sdioh_start(sdioh_info_t *sd, int stage) +{ + return SUCCESS; +} + +int +sdioh_stop(sdioh_info_t *sd) +{ + return SUCCESS; +} + +static int +sdstd_check_errs(sdioh_info_t *sdioh_info, uint32 cmd, uint32 arg) +{ + uint16 regval; + uint retries; + uint function = 0; + + /* If no errors, we're done */ + if ((regval = sdstd_rreg16(sdioh_info, SD_ErrorIntrStatus)) == 0) + return SUCCESS; + + sd_info(("%s: ErrorIntrStatus 0x%04x (clearing), IntrStatus 0x%04x PresentState 0x%08x\n", + __FUNCTION__, regval, sdstd_rreg16(sdioh_info, SD_IntrStatus), + sdstd_rreg(sdioh_info, SD_PresentState))); + sdstd_wreg16(sdioh_info, SD_ErrorIntrStatus, regval); + + /* On command error, issue CMD reset */ + if (regval & ERRINT_CMD_ERRS) { + sd_trace(("%s: issuing CMD reset\n", __FUNCTION__)); + sdstd_wreg8(sdioh_info, SD_SoftwareReset, SFIELD(0, SW_RESET_CMD, 1)); + for (retries = RETRIES_LARGE; retries; retries--) + if (!(GFIELD(sdstd_rreg8(sdioh_info, SD_SoftwareReset), SW_RESET_CMD))) + break; + if (!retries) { + sd_err(("%s: Timeout waiting for CMD line reset\n", __FUNCTION__)); + } + } + + /* On data error, issue DAT reset */ + if (regval & ERRINT_DATA_ERRS) { + sd_trace(("%s: issuing DAT reset\n", __FUNCTION__)); + sdstd_wreg8(sdioh_info, SD_SoftwareReset, SFIELD(0, SW_RESET_DAT, 1)); + for (retries = RETRIES_LARGE; retries; retries--) + if (!(GFIELD(sdstd_rreg8(sdioh_info, SD_SoftwareReset), SW_RESET_DAT))) + break; + if (!retries) { + sd_err(("%s: Timeout waiting for DAT line reset\n", __FUNCTION__)); + } + } + + /* For an IO command (CMD52 or CMD53) issue an abort to the appropriate function */ + if (cmd == SDIOH_CMD_53) + function = GFIELD(arg, CMD53_FUNCTION); + else if (cmd == SDIOH_CMD_52) + function = GFIELD(arg, CMD52_FUNCTION); + if (function) { + sd_trace(("%s: requesting abort for function %d after cmd %d\n", + __FUNCTION__, function, cmd)); + sdstd_abort(sdioh_info, function); + } + + if (trap_errs) + ASSERT(0); + + return ERROR; +} + + + +/* + * Private/Static work routines + */ +static bool +sdstd_reset(sdioh_info_t *sd, bool host_reset, bool client_reset) +{ + int retries = RETRIES_LARGE; + uchar regval; + + if (!sd) + return TRUE; + + sdstd_lock(sd); + /* Reset client card */ + if (client_reset && (sd->adapter_slot != -1)) { + if (sdstd_card_regwrite(sd, 0, SDIOD_CCCR_IOABORT, 1, 0x8) != SUCCESS) + sd_err(("%s: Cannot write to card reg 0x%x\n", + __FUNCTION__, SDIOD_CCCR_IOABORT)); + else + sd->card_rca = 0; + } + + /* Reset host controller */ + if (host_reset) { + regval = SFIELD(0, SW_RESET_ALL, 1); + sdstd_wreg8(sd, SD_SoftwareReset, regval); + do { + sd_trace(("%s: waiting for reset\n", __FUNCTION__)); + } while ((sdstd_rreg8(sd, SD_SoftwareReset) & regval) && retries--); + + if (!retries) { + sd_err(("%s: Timeout waiting for host reset\n", __FUNCTION__)); + sdstd_unlock(sd); + return (FALSE); + } + + /* A reset should reset bus back to 1 bit mode */ + sd->sd_mode = SDIOH_MODE_SD1; + sdstd_set_dma_mode(sd, sd->sd_dma_mode); + } + sdstd_unlock(sd); + return TRUE; +} + +/* Disable device interrupt */ +void +sdstd_devintr_off(sdioh_info_t *sd) +{ + sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); + if (sd->use_client_ints) { + sd->intmask &= ~CLIENT_INTR; + sdstd_wreg16(sd, SD_IntrSignalEnable, sd->intmask); + sdstd_rreg16(sd, SD_IntrSignalEnable); /* Sync readback */ + } +} + +/* Enable device interrupt */ +void +sdstd_devintr_on(sdioh_info_t *sd) +{ + ASSERT(sd->lockcount == 0); + sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); + if (sd->use_client_ints) { + uint16 status = sdstd_rreg16(sd, SD_IntrStatusEnable); + sdstd_wreg16(sd, SD_IntrStatusEnable, SFIELD(status, INTSTAT_CARD_INT, 0)); + sdstd_wreg16(sd, SD_IntrStatusEnable, status); + + sd->intmask |= CLIENT_INTR; + sdstd_wreg16(sd, SD_IntrSignalEnable, sd->intmask); + sdstd_rreg16(sd, SD_IntrSignalEnable); /* Sync readback */ + } +} + +#ifdef BCMSDYIELD +/* Enable/disable other interrupts */ +void +sdstd_intrs_on(sdioh_info_t *sd, uint16 norm, uint16 err) +{ + if (err) { + norm = SFIELD(norm, INTSTAT_ERROR_INT, 1); + sdstd_wreg16(sd, SD_ErrorIntrSignalEnable, err); + } + + sd->intmask |= norm; + sdstd_wreg16(sd, SD_IntrSignalEnable, sd->intmask); + if (sd_forcerb) + sdstd_rreg16(sd, SD_IntrSignalEnable); /* Sync readback */ +} + +void +sdstd_intrs_off(sdioh_info_t *sd, uint16 norm, uint16 err) +{ + if (err) { + norm = SFIELD(norm, INTSTAT_ERROR_INT, 1); + sdstd_wreg16(sd, SD_ErrorIntrSignalEnable, 0); + } + + sd->intmask &= ~norm; + sdstd_wreg16(sd, SD_IntrSignalEnable, sd->intmask); + if (sd_forcerb) + sdstd_rreg16(sd, SD_IntrSignalEnable); /* Sync readback */ +} +#endif /* BCMSDYIELD */ + +static int +sdstd_host_init(sdioh_info_t *sd) +{ + int num_slots, full_slot; + uint8 reg8; + + uint32 card_ins; + int slot, first_bar = 0; + bool detect_slots = FALSE; + uint bar; + + /* Check for Arasan ID */ + if ((OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_VID, 4) & 0xFFFF) == VENDOR_SI_IMAGE) { + sd_info(("%s: Found Arasan Standard SDIO Host Controller\n", __FUNCTION__)); + sd->controller_type = SDIOH_TYPE_ARASAN_HDK; + detect_slots = TRUE; + } else if ((OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_VID, 4) & 0xFFFF) == VENDOR_BROADCOM) { + sd_info(("%s: Found Broadcom 27xx Standard SDIO Host Controller\n", __FUNCTION__)); + sd->controller_type = SDIOH_TYPE_BCM27XX; + detect_slots = FALSE; + } else if ((OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_VID, 4) & 0xFFFF) == VENDOR_TI) { + sd_info(("%s: Found TI PCIxx21 Standard SDIO Host Controller\n", __FUNCTION__)); + sd->controller_type = SDIOH_TYPE_TI_PCIXX21; + detect_slots = TRUE; + } else if ((OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_VID, 4) & 0xFFFF) == VENDOR_RICOH) { + sd_info(("%s: Ricoh Co Ltd R5C822 SD/SDIO/MMC/MS/MSPro Host Adapter\n", + __FUNCTION__)); + sd->controller_type = SDIOH_TYPE_RICOH_R5C822; + detect_slots = TRUE; + } else if ((OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_VID, 4) & 0xFFFF) == VENDOR_JMICRON) { + sd_info(("%s: JMicron Standard SDIO Host Controller\n", + __FUNCTION__)); + sd->controller_type = SDIOH_TYPE_JMICRON; + detect_slots = TRUE; + } else { + return ERROR; + } + + /* + * Determine num of slots + * Search each slot + */ + + first_bar = OSL_PCI_READ_CONFIG(sd->osh, SD_SlotInfo, 4) & 0x7; + num_slots = (OSL_PCI_READ_CONFIG(sd->osh, SD_SlotInfo, 4) & 0xff) >> 4; + num_slots &= 7; + num_slots++; /* map bits to num slots according to spec */ + + if (OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_VID, 4) == + ((SDIOH_FPGA_ID << 16) | VENDOR_BROADCOM)) { + sd_err(("%s: Found Broadcom Standard SDIO Host Controller FPGA\n", __FUNCTION__)); + /* Set BAR0 Window to SDIOSTH core */ + OSL_PCI_WRITE_CONFIG(sd->osh, PCI_BAR0_WIN, 4, 0x18001000); + + /* Set defaults particular to this controller. */ + detect_slots = TRUE; + num_slots = 1; + first_bar = 0; + + /* Controller supports ADMA2, so turn it on here. */ + sd->sd_dma_mode = DMA_MODE_ADMA2; + } + + /* Map in each slot on the board and query it to see if a + * card is inserted. Use the first populated slot found. + */ + if (sd->mem_space) { + sdstd_reg_unmap(sd->osh, (uintptr)sd->mem_space, SDIOH_REG_WINSZ); + sd->mem_space = NULL; + } + + full_slot = -1; + + for (slot = 0; slot < num_slots; slot++) { + bar = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_BAR0 + (4*(slot + first_bar)), 4); + sd->mem_space = (volatile char *)sdstd_reg_map(sd->osh, + (uintptr)bar, SDIOH_REG_WINSZ); + + sd->adapter_slot = -1; + + if (detect_slots) { + card_ins = GFIELD(sdstd_rreg(sd, SD_PresentState), PRES_CARD_PRESENT); + } else { + card_ins = TRUE; + } + + if (card_ins) { + sd_info(("%s: SDIO slot %d: Full\n", __FUNCTION__, slot)); + if (full_slot < 0) + full_slot = slot; + } else { + sd_info(("%s: SDIO slot %d: Empty\n", __FUNCTION__, slot)); + } + + if (sd->mem_space) { + sdstd_reg_unmap(sd->osh, (uintptr)sd->mem_space, SDIOH_REG_WINSZ); + sd->mem_space = NULL; + } + } + + if (full_slot < 0) { + sd_err(("No slots on SDIO controller are populated\n")); + return -1; + } + + bar = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_BAR0 + (4*(full_slot + first_bar)), 4); + sd->mem_space = (volatile char *)sdstd_reg_map(sd->osh, (uintptr)bar, SDIOH_REG_WINSZ); + + sd_err(("Using slot %d at BAR%d [0x%08x] mem_space 0x%p\n", + full_slot, + (full_slot + first_bar), + OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_BAR0 + (4*(full_slot + first_bar)), 4), + sd->mem_space)); + + + sd->adapter_slot = full_slot; + + sd->version = sdstd_rreg16(sd, SD_HostControllerVersion) & 0xFF; + switch (sd->version) { + case 0: + sd_err(("Host Controller version 1.0, Vendor Revision: 0x%02x\n", + sdstd_rreg16(sd, SD_HostControllerVersion) >> 8)); + break; + case 1: + case 2: + sd_err(("Host Controller version 2.0, Vendor Revision: 0x%02x\n", + sdstd_rreg16(sd, SD_HostControllerVersion) >> 8)); + break; + default: + sd_err(("%s: Host Controller version 0x%02x not supported.\n", + __FUNCTION__, sd->version)); + break; + } + + sd->caps = sdstd_rreg(sd, SD_Capabilities); /* Cache this for later use */ + sd->curr_caps = sdstd_rreg(sd, SD_MaxCurCap); + + sdstd_set_dma_mode(sd, sd->sd_dma_mode); + + + sdstd_reset(sd, 1, 0); + + /* Read SD4/SD1 mode */ + if ((reg8 = sdstd_rreg8(sd, SD_HostCntrl))) { + if (reg8 & SD4_MODE) { + sd_err(("%s: Host cntrlr already in 4 bit mode: 0x%x\n", + __FUNCTION__, reg8)); + } + } + + /* Default power on mode is SD1 */ + sd->sd_mode = SDIOH_MODE_SD1; + sd->polled_mode = TRUE; + sd->host_init_done = TRUE; + sd->card_init_done = FALSE; + sd->adapter_slot = full_slot; + + return (SUCCESS); +} +#define CMD5_RETRIES 200 +static int +get_ocr(sdioh_info_t *sd, uint32 *cmd_arg, uint32 *cmd_rsp) +{ + int retries, status; + + /* Get the Card's Operation Condition. Occasionally the board + * takes a while to become ready + */ + retries = CMD5_RETRIES; + do { + *cmd_rsp = 0; + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_5, *cmd_arg)) + != SUCCESS) { + sd_err(("%s: CMD5 failed\n", __FUNCTION__)); + return status; + } + sdstd_cmd_getrsp(sd, cmd_rsp, 1); + if (!GFIELD(*cmd_rsp, RSP4_CARD_READY)) + sd_trace(("%s: Waiting for card to become ready\n", __FUNCTION__)); + } while ((!GFIELD(*cmd_rsp, RSP4_CARD_READY)) && --retries); + if (!retries) + return ERROR; + + return (SUCCESS); +} + +static int +sdstd_client_init(sdioh_info_t *sd) +{ + uint32 cmd_arg, cmd_rsp; + int status; + uint8 fn_ints; + + + sd_trace(("%s: Powering up slot %d\n", __FUNCTION__, sd->adapter_slot)); + + /* Clear any pending ints */ + sdstd_wreg16(sd, SD_IntrStatus, 0x1ff); + sdstd_wreg16(sd, SD_ErrorIntrStatus, 0x0fff); + + /* Enable both Normal and Error Status. This does not enable + * interrupts, it only enables the status bits to + * become 'live' + */ + sdstd_wreg16(sd, SD_IntrStatusEnable, 0x1ff); + sdstd_wreg16(sd, SD_ErrorIntrStatusEnable, 0xffff); + + sdstd_wreg16(sd, SD_IntrSignalEnable, 0); /* Disable ints for now. */ + + /* Start at ~400KHz clock rate for initialization */ + if (!sdstd_start_clock(sd, 128)) { + sd_err(("sdstd_start_clock failed\n")); + return ERROR; + } + if (!sdstd_start_power(sd)) { + sd_err(("sdstd_start_power failed\n")); + return ERROR; + } + + if (sd->num_funcs == 0) { + sd_err(("%s: No IO funcs!\n", __FUNCTION__)); + return ERROR; + } + + /* In SPI mode, issue CMD0 first */ + if (sd->sd_mode == SDIOH_MODE_SPI) { + cmd_arg = 0; + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_0, cmd_arg)) + != SUCCESS) { + sd_err(("BCMSDIOH: cardinit: CMD0 failed!\n")); + return status; + } + } + + if (sd->sd_mode != SDIOH_MODE_SPI) { + uint16 rsp6_status; + + /* Card is operational. Ask it to send an RCA */ + cmd_arg = 0; + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_3, cmd_arg)) + != SUCCESS) { + sd_err(("%s: CMD3 failed!\n", __FUNCTION__)); + return status; + } + + /* Verify the card status returned with the cmd response */ + sdstd_cmd_getrsp(sd, &cmd_rsp, 1); + rsp6_status = GFIELD(cmd_rsp, RSP6_STATUS); + if (GFIELD(rsp6_status, RSP6STAT_COM_CRC_ERROR) || + GFIELD(rsp6_status, RSP6STAT_ILLEGAL_CMD) || + GFIELD(rsp6_status, RSP6STAT_ERROR)) { + sd_err(("%s: CMD3 response error. Response = 0x%x!\n", + __FUNCTION__, rsp6_status)); + return ERROR; + } + + /* Save the Card's RCA */ + sd->card_rca = GFIELD(cmd_rsp, RSP6_IO_RCA); + sd_info(("RCA is 0x%x\n", sd->card_rca)); + + if (rsp6_status) + sd_err(("raw status is 0x%x\n", rsp6_status)); + + /* Select the card */ + cmd_arg = SFIELD(0, CMD7_RCA, sd->card_rca); + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_7, cmd_arg)) + != SUCCESS) { + sd_err(("%s: CMD7 failed!\n", __FUNCTION__)); + return status; + } + sdstd_cmd_getrsp(sd, &cmd_rsp, 1); + if (cmd_rsp != SDIOH_CMD7_EXP_STATUS) { + sd_err(("%s: CMD7 response error. Response = 0x%x!\n", + __FUNCTION__, cmd_rsp)); + return ERROR; + } + } + + sdstd_card_enablefuncs(sd); + + if (!sdstd_bus_width(sd, sd_sdmode)) { + sd_err(("sdstd_bus_width failed\n")); + return ERROR; + } + + set_client_block_size(sd, 1, BLOCK_SIZE_4318); + fn_ints = INTR_CTL_FUNC1_EN; + + if (sd->num_funcs >= 2) { + set_client_block_size(sd, 2, sd_f2_blocksize /* BLOCK_SIZE_4328 */); + fn_ints |= INTR_CTL_FUNC2_EN; + } + + /* Enable/Disable Client interrupts */ + /* Turn on here but disable at host controller? */ + if (sdstd_card_regwrite(sd, 0, SDIOD_CCCR_INTEN, 1, + (fn_ints | INTR_CTL_MASTER_EN)) != SUCCESS) { + sd_err(("%s: Could not enable ints in CCCR\n", __FUNCTION__)); + return ERROR; + } + + /* Switch to High-speed clocking mode if both host and device support it */ + sdstd_set_highspeed_mode(sd, (bool)sd_hiok); + + /* After configuring for High-Speed mode, set the desired clock rate. */ + if (!sdstd_start_clock(sd, (uint16)sd_divisor)) { + sd_err(("sdstd_start_clock failed\n")); + return ERROR; + } + + sd->card_init_done = TRUE; + + return SUCCESS; +} + +static int +sdstd_set_highspeed_mode(sdioh_info_t *sd, bool HSMode) +{ + uint32 regdata; + int status; + uint8 reg8; + + reg8 = sdstd_rreg8(sd, SD_HostCntrl); + + + if (HSMode == TRUE) { + if (sd_hiok && (GFIELD(sd->caps, CAP_HIGHSPEED)) == 0) { + sd_err(("Host Controller does not support hi-speed mode.\n")); + return BCME_ERROR; + } + + sd_info(("Attempting to enable High-Speed mode.\n")); + + if ((status = sdstd_card_regread(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, ®data)) != SUCCESS) { + return BCME_SDIO_ERROR; + } + if (regdata & SDIO_SPEED_SHS) { + sd_info(("Device supports High-Speed mode.\n")); + + regdata |= SDIO_SPEED_EHS; + + sd_info(("Writing %08x to Card at %08x\n", + regdata, SDIOD_CCCR_SPEED_CONTROL)); + if ((status = sdstd_card_regwrite(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, regdata)) != BCME_OK) { + return BCME_SDIO_ERROR; + } + + if ((status = sdstd_card_regread(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, ®data)) != BCME_OK) { + return BCME_SDIO_ERROR; + } + + sd_info(("Read %08x to Card at %08x\n", regdata, SDIOD_CCCR_SPEED_CONTROL)); + + reg8 = SFIELD(reg8, HOST_HI_SPEED_EN, 1); + + sd_err(("High-speed clocking mode enabled.\n")); + } + else { + sd_err(("Device does not support High-Speed Mode.\n")); + reg8 = SFIELD(reg8, HOST_HI_SPEED_EN, 0); + } + } else { + /* Force off device bit */ + if ((status = sdstd_card_regread(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, ®data)) != BCME_OK) { + return status; + } + if (regdata & SDIO_SPEED_EHS) { + regdata &= ~SDIO_SPEED_EHS; + if ((status = sdstd_card_regwrite(sd, 0, SDIOD_CCCR_SPEED_CONTROL, + 1, regdata)) != BCME_OK) { + return status; + } + } + + sd_err(("High-speed clocking mode disabled.\n")); + reg8 = SFIELD(reg8, HOST_HI_SPEED_EN, 0); + } + + sdstd_wreg8(sd, SD_HostCntrl, reg8); + + return BCME_OK; +} + +/* Select DMA Mode: + * If dma_mode == DMA_MODE_AUTO, pick the "best" mode. + * Otherwise, pick the selected mode if supported. + * If not supported, use PIO mode. + */ +static int +sdstd_set_dma_mode(sdioh_info_t *sd, int8 dma_mode) +{ + uint8 reg8, dma_sel_bits = SDIOH_SDMA_MODE; + int8 prev_dma_mode = sd->sd_dma_mode; + + switch (prev_dma_mode) { + case DMA_MODE_AUTO: + sd_dma(("%s: Selecting best DMA mode supported by controller.\n", + __FUNCTION__)); + if (GFIELD(sd->caps, CAP_ADMA2)) { + sd->sd_dma_mode = DMA_MODE_ADMA2; + dma_sel_bits = SDIOH_ADMA2_MODE; + } else if (GFIELD(sd->caps, CAP_ADMA1)) { + sd->sd_dma_mode = DMA_MODE_ADMA1; + dma_sel_bits = SDIOH_ADMA1_MODE; + } else if (GFIELD(sd->caps, CAP_DMA)) { + sd->sd_dma_mode = DMA_MODE_SDMA; + } else { + sd->sd_dma_mode = DMA_MODE_NONE; + } + break; + case DMA_MODE_NONE: + sd->sd_dma_mode = DMA_MODE_NONE; + break; + case DMA_MODE_SDMA: + if (GFIELD(sd->caps, CAP_DMA)) { + sd->sd_dma_mode = DMA_MODE_SDMA; + } else { + sd_err(("%s: SDMA not supported by controller.\n", __FUNCTION__)); + sd->sd_dma_mode = DMA_MODE_NONE; + } + break; + case DMA_MODE_ADMA1: + if (GFIELD(sd->caps, CAP_ADMA1)) { + sd->sd_dma_mode = DMA_MODE_ADMA1; + dma_sel_bits = SDIOH_ADMA1_MODE; + } else { + sd_err(("%s: ADMA1 not supported by controller.\n", __FUNCTION__)); + sd->sd_dma_mode = DMA_MODE_NONE; + } + break; + case DMA_MODE_ADMA2: + if (GFIELD(sd->caps, CAP_ADMA2)) { + sd->sd_dma_mode = DMA_MODE_ADMA2; + dma_sel_bits = SDIOH_ADMA2_MODE; + } else { + sd_err(("%s: ADMA2 not supported by controller.\n", __FUNCTION__)); + sd->sd_dma_mode = DMA_MODE_NONE; + } + break; + case DMA_MODE_ADMA2_64: + sd_err(("%s: 64b ADMA2 not supported by driver.\n", __FUNCTION__)); + sd->sd_dma_mode = DMA_MODE_NONE; + break; + default: + sd_err(("%s: Unsupported DMA Mode %d requested.\n", __FUNCTION__, + prev_dma_mode)); + sd->sd_dma_mode = DMA_MODE_NONE; + break; + } + + /* clear SysAddr, only used for SDMA */ + sdstd_wreg(sd, SD_SysAddr, 0); + + sd_err(("%s: %s mode selected.\n", __FUNCTION__, dma_mode_description[sd->sd_dma_mode])); + + reg8 = sdstd_rreg8(sd, SD_HostCntrl); + reg8 = SFIELD(reg8, HOST_DMA_SEL, dma_sel_bits); + sdstd_wreg8(sd, SD_HostCntrl, reg8); + sd_dma(("%s: SD_HostCntrl=0x%02x\n", __FUNCTION__, reg8)); + + return BCME_OK; +} + + +bool +sdstd_start_clock(sdioh_info_t *sd, uint16 new_sd_divisor) +{ + uint rc, count; + uint16 divisor; + + /* turn off HC clock */ + sdstd_wreg16(sd, SD_ClockCntrl, + sdstd_rreg16(sd, SD_ClockCntrl) & ~((uint16)0x4)); /* Disable the HC clock */ + + /* Set divisor */ + + divisor = (new_sd_divisor >> 1) << 8; + + sd_info(("Clock control is 0x%x\n", sdstd_rreg16(sd, SD_ClockCntrl))); + sdstd_mod_reg16(sd, SD_ClockCntrl, 0xff00, divisor); + sd_info(("%s: Using clock divisor of %d (regval 0x%04x)\n", __FUNCTION__, + new_sd_divisor, divisor)); + + sd_info(("Primary Clock Freq = %d MHz\n", GFIELD(sd->caps, CAP_TO_CLKFREQ))); + + if (GFIELD(sd->caps, CAP_TO_CLKFREQ) == 50) { + sd_info(("%s: Resulting SDIO clock is %d %s\n", __FUNCTION__, + ((50 % new_sd_divisor) ? (50000 / new_sd_divisor) : (50 / new_sd_divisor)), + ((50 % new_sd_divisor) ? "KHz" : "MHz"))); + } else if (GFIELD(sd->caps, CAP_TO_CLKFREQ) == 48) { + sd_info(("%s: Resulting SDIO clock is %d %s\n", __FUNCTION__, + ((48 % new_sd_divisor) ? (48000 / new_sd_divisor) : (48 / new_sd_divisor)), + ((48 % new_sd_divisor) ? "KHz" : "MHz"))); + } else if (GFIELD(sd->caps, CAP_TO_CLKFREQ) == 33) { + sd_info(("%s: Resulting SDIO clock is %d %s\n", __FUNCTION__, + ((33 % new_sd_divisor) ? (33000 / new_sd_divisor) : (33 / new_sd_divisor)), + ((33 % new_sd_divisor) ? "KHz" : "MHz"))); + + } else if (sd->controller_type == SDIOH_TYPE_BCM27XX) { + } else { + sd_err(("Need to determine divisor for %d MHz clocks\n", + GFIELD(sd->caps, CAP_TO_CLKFREQ))); + sd_err(("Consult SD Host Controller Spec: Clock Control Register\n")); + return (FALSE); + } + + sdstd_or_reg16(sd, SD_ClockCntrl, 0x1); /* Enable the clock */ + + /* Wait for clock to stabilize */ + rc = (sdstd_rreg16(sd, SD_ClockCntrl) & 2); + count = 0; + while (!rc) { + OSL_DELAY(1); + sd_info(("Waiting for clock to become stable 0x%x\n", rc)); + rc = (sdstd_rreg16(sd, SD_ClockCntrl) & 2); + count++; + if (count > 10000) { + sd_err(("%s:Clocks failed to stabilize after %u attempts", + __FUNCTION__, count)); + return (FALSE); + } + } + /* Turn on clock */ + sdstd_or_reg16(sd, SD_ClockCntrl, 0x4); + + /* Set timeout control (adjust default value based on divisor). + * Disabling timeout interrupts during setting is advised by host spec. + */ + { + uint16 regdata; + uint toval; + + toval = sd_toctl; + divisor = new_sd_divisor; + + while (toval && !(divisor & 1)) { + toval -= 1; + divisor >>= 1; + } + + regdata = sdstd_rreg16(sd, SD_ErrorIntrStatusEnable); + sdstd_wreg16(sd, SD_ErrorIntrStatusEnable, (regdata & ~ERRINT_DATA_TIMEOUT_BIT)); + sdstd_wreg8(sd, SD_TimeoutCntrl, (uint8)toval); + sdstd_wreg16(sd, SD_ErrorIntrStatusEnable, regdata); + } + + OSL_DELAY(2); + + sd_info(("Final Clock control is 0x%x\n", sdstd_rreg16(sd, SD_ClockCntrl))); + + return TRUE; +} + +bool +sdstd_start_power(sdioh_info_t *sd) +{ + char *s; + uint32 cmd_arg; + uint32 cmd_rsp; + uint8 pwr = 0; + int volts; + + volts = 0; + s = NULL; + if (GFIELD(sd->caps, CAP_VOLT_1_8)) { + volts = 5; + s = "1.8"; + } + if (GFIELD(sd->caps, CAP_VOLT_3_0)) { + volts = 6; + s = "3.0"; + } + if (GFIELD(sd->caps, CAP_VOLT_3_3)) { + volts = 7; + s = "3.3"; + } + + pwr = SFIELD(pwr, PWR_VOLTS, volts); + pwr = SFIELD(pwr, PWR_BUS_EN, 1); + sdstd_wreg8(sd, SD_PwrCntrl, pwr); /* Set Voltage level */ + sd_info(("Setting Bus Power to %s Volts\n", s)); + + /* Wait for power to stabilize, Dongle takes longer than NIC. */ + OSL_DELAY(250000); + + /* Get the Card's Operation Condition. Occasionally the board + * takes a while to become ready + */ + cmd_arg = 0; + cmd_rsp = 0; + if (get_ocr(sd, &cmd_arg, &cmd_rsp) != SUCCESS) { + sd_err(("%s: Failed to get OCR bailing\n", __FUNCTION__)); + sdstd_reset(sd, 0, 1); + return FALSE; + } + + sd_info(("mem_present = %d\n", GFIELD(cmd_rsp, RSP4_MEM_PRESENT))); + sd_info(("num_funcs = %d\n", GFIELD(cmd_rsp, RSP4_NUM_FUNCS))); + sd_info(("card_ready = %d\n", GFIELD(cmd_rsp, RSP4_CARD_READY))); + sd_info(("OCR = 0x%x\n", GFIELD(cmd_rsp, RSP4_IO_OCR))); + + /* Verify that the card supports I/O mode */ + if (GFIELD(cmd_rsp, RSP4_NUM_FUNCS) == 0) { + sd_err(("%s: Card does not support I/O\n", __FUNCTION__)); + return ERROR; + } + sd->num_funcs = GFIELD(cmd_rsp, RSP4_NUM_FUNCS); + + /* Examine voltage: Arasan only supports 3.3 volts, + * so look for 3.2-3.3 Volts and also 3.3-3.4 volts. + */ + + if ((GFIELD(cmd_rsp, RSP4_IO_OCR) & (0x3 << 20)) == 0) { + sd_err(("This client does not support 3.3 volts!\n")); + return ERROR; + } + sd_info(("Leaving bus power at 3.3 Volts\n")); + + cmd_arg = SFIELD(0, CMD5_OCR, 0xfff000); + cmd_rsp = 0; + get_ocr(sd, &cmd_arg, &cmd_rsp); + sd_info(("OCR = 0x%x\n", GFIELD(cmd_rsp, RSP4_IO_OCR))); + return TRUE; +} + +bool +sdstd_bus_width(sdioh_info_t *sd, int new_mode) +{ + uint32 regdata; + int status; + uint8 reg8; + + sd_trace(("%s\n", __FUNCTION__)); + if (sd->sd_mode == new_mode) { + sd_info(("%s: Already at width %d\n", __FUNCTION__, new_mode)); + /* Could exit, but continue just in case... */ + } + + /* Set client side via reg 0x7 in CCCR */ + if ((status = sdstd_card_regread (sd, 0, SDIOD_CCCR_BICTRL, 1, ®data)) != SUCCESS) + return (bool)status; + regdata &= ~BUS_SD_DATA_WIDTH_MASK; + if (new_mode == SDIOH_MODE_SD4) { + sd_info(("Changing to SD4 Mode\n")); + regdata |= SD4_MODE; + } else if (new_mode == SDIOH_MODE_SD1) { + sd_info(("Changing to SD1 Mode\n")); + } else { + sd_err(("SPI Mode not supported by Standard Host Controller\n")); + } + + if ((status = sdstd_card_regwrite (sd, 0, SDIOD_CCCR_BICTRL, 1, regdata)) != SUCCESS) + return (bool)status; + + /* Set host side via Host reg */ + reg8 = sdstd_rreg8(sd, SD_HostCntrl) & ~SD4_MODE; + if (new_mode == SDIOH_MODE_SD4) + reg8 |= SD4_MODE; + sdstd_wreg8(sd, SD_HostCntrl, reg8); + + sd->sd_mode = new_mode; + + return TRUE; +} + +static int +sdstd_driver_init(sdioh_info_t *sd) +{ + sd_trace(("%s\n", __FUNCTION__)); + if ((sdstd_host_init(sd)) != SUCCESS) { + return ERROR; + } + + if (sdstd_client_init(sd) != SUCCESS) { + return ERROR; + } + + return SUCCESS; +} + +static int +sdstd_get_cisaddr(sdioh_info_t *sd, uint32 regaddr) +{ + /* read 24 bits and return valid 17 bit addr */ + int i; + uint32 scratch, regdata; + uint8 *ptr = (uint8 *)&scratch; + for (i = 0; i < 3; i++) { + if ((sdstd_card_regread (sd, 0, regaddr, 1, ®data)) != SUCCESS) + sd_err(("%s: Can't read!\n", __FUNCTION__)); + + *ptr++ = (uint8) regdata; + regaddr++; + } + /* Only the lower 17-bits are valid */ + scratch = ltoh32(scratch); + scratch &= 0x0001FFFF; + return (scratch); +} + +static int +sdstd_card_enablefuncs(sdioh_info_t *sd) +{ + int status; + uint32 regdata; + uint32 fbraddr; + uint8 func; + + sd_trace(("%s\n", __FUNCTION__)); + + /* Get the Card's common CIS address */ + sd->com_cis_ptr = sdstd_get_cisaddr(sd, SDIOD_CCCR_CISPTR_0); + sd->func_cis_ptr[0] = sd->com_cis_ptr; + sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __FUNCTION__, sd->com_cis_ptr)); + + /* Get the Card's function CIS (for each function) */ + for (fbraddr = SDIOD_FBR_STARTADDR, func = 1; + func <= sd->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) { + sd->func_cis_ptr[func] = sdstd_get_cisaddr(sd, SDIOD_FBR_CISPTR_0 + fbraddr); + sd_info(("%s: Function %d CIS Ptr = 0x%x\n", + __FUNCTION__, func, sd->func_cis_ptr[func])); + } + + /* Enable function 1 on the card */ + regdata = SDIO_FUNC_ENABLE_1; + if ((status = sdstd_card_regwrite(sd, 0, SDIOD_CCCR_IOEN, 1, regdata)) != SUCCESS) + return status; + + return SUCCESS; +} + +/* Read client card reg */ +static int +sdstd_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 *data) +{ + int status; + uint32 cmd_arg; + uint32 rsp5; + + + cmd_arg = 0; + + if ((func == 0) || (regsize == 1)) { + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, SDIOH_XFER_TYPE_READ); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, 0); + + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_52, cmd_arg)) + != SUCCESS) + return status; + + sdstd_cmd_getrsp(sd, &rsp5, 1); + if (sdstd_rreg16(sd, SD_ErrorIntrStatus) != 0) { + sd_err(("%s: 1: ErrorintrStatus 0x%x\n", + __FUNCTION__, sdstd_rreg16(sd, SD_ErrorIntrStatus))); + } + + if (GFIELD(rsp5, RSP5_FLAGS) != 0x10) + sd_err(("%s: rsp5 flags is 0x%x\t %d\n", + __FUNCTION__, GFIELD(rsp5, RSP5_FLAGS), func)); + + if (GFIELD(rsp5, RSP5_STUFF)) + sd_err(("%s: rsp5 stuff is 0x%x: should be 0\n", + __FUNCTION__, GFIELD(rsp5, RSP5_STUFF))); + *data = GFIELD(rsp5, RSP5_DATA); + } else { + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, regsize); + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 1); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 0); + cmd_arg = SFIELD(cmd_arg, CMD53_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD53_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_READ); + + sd->data_xfer_count = regsize; + + /* sdstd_cmd_issue() returns with the command complete bit + * in the ISR already cleared + */ + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_53, cmd_arg)) + != SUCCESS) + return status; + + sdstd_cmd_getrsp(sd, &rsp5, 1); + + if (GFIELD(rsp5, RSP5_FLAGS) != 0x10) + sd_err(("%s: rsp5 flags is 0x%x\t %d\n", + __FUNCTION__, GFIELD(rsp5, RSP5_FLAGS), func)); + + if (GFIELD(rsp5, RSP5_STUFF)) + sd_err(("%s: rsp5 stuff is 0x%x: should be 0\n", + __FUNCTION__, GFIELD(rsp5, RSP5_STUFF))); + + if (sd->polled_mode) { + volatile uint16 int_reg; + int retries = RETRIES_LARGE; + + /* Wait for Read Buffer to become ready */ + do { + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + } while (--retries && (GFIELD(int_reg, INTSTAT_BUF_READ_READY) == 0)); + + if (!retries) { + sd_err(("%s: Timeout on Buf_Read_Ready: " + "intStat: 0x%x errint: 0x%x PresentState 0x%x\n", + __FUNCTION__, int_reg, + sdstd_rreg16(sd, SD_ErrorIntrStatus), + sdstd_rreg(sd, SD_PresentState))); + sdstd_check_errs(sd, SDIOH_CMD_53, cmd_arg); + return (ERROR); + } + + /* Have Buffer Ready, so clear it and read the data */ + sdstd_wreg16(sd, SD_IntrStatus, SFIELD(0, INTSTAT_BUF_READ_READY, 1)); + if (regsize == 2) + *data = sdstd_rreg16(sd, SD_BufferDataPort0); + else + *data = sdstd_rreg(sd, SD_BufferDataPort0); + + /* Check Status. + * After the data is read, the Transfer Complete bit should be on + */ + retries = RETRIES_LARGE; + do { + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + } while (--retries && (GFIELD(int_reg, INTSTAT_XFER_COMPLETE) == 0)); + + /* Check for any errors from the data phase */ + if (sdstd_check_errs(sd, SDIOH_CMD_53, cmd_arg)) + return ERROR; + + if (!retries) { + sd_err(("%s: Timeout on xfer complete: " + "intr 0x%04x err 0x%04x state 0x%08x\n", + __FUNCTION__, int_reg, + sdstd_rreg16(sd, SD_ErrorIntrStatus), + sdstd_rreg(sd, SD_PresentState))); + return (ERROR); + } + + sdstd_wreg16(sd, SD_IntrStatus, SFIELD(0, INTSTAT_XFER_COMPLETE, 1)); + } + } + if (sd->polled_mode) { + if (regsize == 2) + *data &= 0xffff; + } + return SUCCESS; +} + +bool +check_client_intr(sdioh_info_t *sd) +{ + uint16 raw_int, cur_int, old_int; + + raw_int = sdstd_rreg16(sd, SD_IntrStatus); + cur_int = raw_int & sd->intmask; + + if (!cur_int) { + /* Not an error -- might share interrupts... */ + return FALSE; + } + + if (GFIELD(cur_int, INTSTAT_CARD_INT)) { + old_int = sdstd_rreg16(sd, SD_IntrStatusEnable); + sdstd_wreg16(sd, SD_IntrStatusEnable, SFIELD(old_int, INTSTAT_CARD_INT, 0)); + + if (sd->client_intr_enabled && sd->use_client_ints) { + sd->intrcount++; + ASSERT(sd->intr_handler); + ASSERT(sd->intr_handler_arg); + (sd->intr_handler)(sd->intr_handler_arg); + } else { + sd_err(("%s: Not ready for intr: enabled %d, handler %p\n", + __FUNCTION__, sd->client_intr_enabled, sd->intr_handler)); + } + sdstd_wreg16(sd, SD_IntrStatusEnable, old_int); + } else { + /* Local interrupt: disable, set flag, and save intrstatus */ + sdstd_wreg16(sd, SD_IntrSignalEnable, 0); + sdstd_wreg16(sd, SD_ErrorIntrSignalEnable, 0); + sd->local_intrcount++; + sd->got_hcint = TRUE; + sd->last_intrstatus = cur_int; + } + + return TRUE; +} + +void +sdstd_spinbits(sdioh_info_t *sd, uint16 norm, uint16 err) +{ + uint16 int_reg, err_reg; + int retries = RETRIES_LARGE; + + do { + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + err_reg = sdstd_rreg16(sd, SD_ErrorIntrStatus); + } while (--retries && !(int_reg & norm) && !(err_reg & err)); + + norm |= sd->intmask; + if (err_reg & err) + norm = SFIELD(norm, INTSTAT_ERROR_INT, 1); + sd->last_intrstatus = int_reg & norm; +} + +/* write a client register */ +static int +sdstd_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 data) +{ + int status; + uint32 cmd_arg, rsp5, flags; + + cmd_arg = 0; + + if ((func == 0) || (regsize == 1)) { + cmd_arg = SFIELD(cmd_arg, CMD52_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD52_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD52_RW_FLAG, SDIOH_XFER_TYPE_WRITE); + cmd_arg = SFIELD(cmd_arg, CMD52_RAW, 0); + cmd_arg = SFIELD(cmd_arg, CMD52_DATA, data & 0xff); + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_52, cmd_arg)) + != SUCCESS) + return status; + + sdstd_cmd_getrsp(sd, &rsp5, 1); + flags = GFIELD(rsp5, RSP5_FLAGS); + if (flags && (flags != 0x10)) + sd_err(("%s: rsp5.rsp5.flags = 0x%x, expecting 0x10\n", + __FUNCTION__, flags)); + } + else { + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, regsize); + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 1); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 0); + cmd_arg = SFIELD(cmd_arg, CMD53_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD53_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_WRITE); + + sd->data_xfer_count = regsize; + + /* sdstd_cmd_issue() returns with the command complete bit + * in the ISR already cleared + */ + if ((status = sdstd_cmd_issue(sd, USE_DMA(sd), SDIOH_CMD_53, cmd_arg)) + != SUCCESS) + return status; + + sdstd_cmd_getrsp(sd, &rsp5, 1); + + if (GFIELD(rsp5, RSP5_FLAGS) != 0x10) + sd_err(("%s: rsp5 flags = 0x%x, expecting 0x10\n", + __FUNCTION__, GFIELD(rsp5, RSP5_FLAGS))); + if (GFIELD(rsp5, RSP5_STUFF)) + sd_err(("%s: rsp5 stuff is 0x%x: expecting 0\n", + __FUNCTION__, GFIELD(rsp5, RSP5_STUFF))); + + if (sd->polled_mode) { + uint16 int_reg; + int retries = RETRIES_LARGE; + + /* Wait for Write Buffer to become ready */ + do { + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + } while (--retries && (GFIELD(int_reg, INTSTAT_BUF_WRITE_READY) == 0)); + + if (!retries) { + sd_err(("%s: Timeout on Buf_Write_Ready: intStat: 0x%x " + "errint: 0x%x PresentState 0x%x\n", + __FUNCTION__, int_reg, + sdstd_rreg16(sd, SD_ErrorIntrStatus), + sdstd_rreg(sd, SD_PresentState))); + sdstd_check_errs(sd, SDIOH_CMD_53, cmd_arg); + return (ERROR); + } + /* Clear Write Buf Ready bit */ + int_reg = 0; + int_reg = SFIELD(int_reg, INTSTAT_BUF_WRITE_READY, 1); + sdstd_wreg16(sd, SD_IntrStatus, int_reg); + + /* At this point we have Buffer Ready, so write the data */ + if (regsize == 2) + sdstd_wreg16(sd, SD_BufferDataPort0, (uint16) data); + else + sdstd_wreg(sd, SD_BufferDataPort0, data); + + /* Wait for Transfer Complete */ + retries = RETRIES_LARGE; + do { + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + } while (--retries && (GFIELD(int_reg, INTSTAT_XFER_COMPLETE) == 0)); + + /* Check for any errors from the data phase */ + if (sdstd_check_errs(sd, SDIOH_CMD_53, cmd_arg)) + return ERROR; + + if (retries == 0) { + sd_err(("%s: Timeout for xfer complete; State = 0x%x, " + "intr state=0x%x, Errintstatus 0x%x rcnt %d, tcnt %d\n", + __FUNCTION__, sdstd_rreg(sd, SD_PresentState), + int_reg, sdstd_rreg16(sd, SD_ErrorIntrStatus), + sd->r_cnt, sd->t_cnt)); + } + /* Clear the status bits */ + sdstd_wreg16(sd, SD_IntrStatus, SFIELD(int_reg, INTSTAT_CARD_INT, 0)); + } + } + return SUCCESS; +} + +void +sdstd_cmd_getrsp(sdioh_info_t *sd, uint32 *rsp_buffer, int count /* num 32 bit words */) +{ + int rsp_count; + int respaddr = SD_Response0; + + if (count > 4) + count = 4; + + for (rsp_count = 0; rsp_count < count; rsp_count++) { + *rsp_buffer++ = sdstd_rreg(sd, respaddr); + respaddr += 4; + } +} + +static int +sdstd_cmd_issue(sdioh_info_t *sdioh_info, bool use_dma, uint32 cmd, uint32 arg) +{ + uint16 cmd_reg; + int retries; + uint32 cmd_arg; + uint16 xfer_reg = 0; + + + if ((sdioh_info->sd_mode == SDIOH_MODE_SPI) && + ((cmd == SDIOH_CMD_3) || (cmd == SDIOH_CMD_7) || (cmd == SDIOH_CMD_15))) { + sd_err(("%s: Cmd %d is not for SPI\n", __FUNCTION__, cmd)); + return ERROR; + } + + retries = RETRIES_SMALL; + while ((GFIELD(sdstd_rreg(sdioh_info, SD_PresentState), PRES_CMD_INHIBIT)) && --retries) { + if (retries == RETRIES_SMALL) + sd_err(("%s: Waiting for Command Inhibit cmd = %d 0x%x\n", + __FUNCTION__, cmd, sdstd_rreg(sdioh_info, SD_PresentState))); + } + if (!retries) { + sd_err(("%s: Command Inhibit timeout\n", __FUNCTION__)); + if (trap_errs) + ASSERT(0); + return ERROR; + } + + + cmd_reg = 0; + switch (cmd) { + case SDIOH_CMD_0: /* Set Card to Idle State - No Response */ + sd_data(("%s: CMD%d\n", __FUNCTION__, cmd)); + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_NONE); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + break; + + case SDIOH_CMD_3: /* Ask card to send RCA - Response R6 */ + sd_data(("%s: CMD%d\n", __FUNCTION__, cmd)); + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_48); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + break; + + case SDIOH_CMD_5: /* Send Operation condition - Response R4 */ + sd_data(("%s: CMD%d\n", __FUNCTION__, cmd)); + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_48); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + break; + + case SDIOH_CMD_7: /* Select card - Response R1 */ + sd_data(("%s: CMD%d\n", __FUNCTION__, cmd)); + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_48); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + break; + + case SDIOH_CMD_15: /* Set card to inactive state - Response None */ + sd_data(("%s: CMD%d\n", __FUNCTION__, cmd)); + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_NONE); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + break; + + case SDIOH_CMD_52: /* IO R/W Direct (single byte) - Response R5 */ + + sd_data(("%s: CMD52 func(%d) addr(0x%x) %s data(0x%x)\n", + __FUNCTION__, + GFIELD(arg, CMD52_FUNCTION), + GFIELD(arg, CMD52_REG_ADDR), + GFIELD(arg, CMD52_RW_FLAG) ? "W" : "R", + GFIELD(arg, CMD52_DATA))); + + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_48); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + break; + + case SDIOH_CMD_53: /* IO R/W Extended (multiple bytes/blocks) */ + + sd_data(("%s: CMD53 func(%d) addr(0x%x) %s mode(%s) cnt(%d), %s\n", + __FUNCTION__, + GFIELD(arg, CMD53_FUNCTION), + GFIELD(arg, CMD53_REG_ADDR), + GFIELD(arg, CMD53_RW_FLAG) ? "W" : "R", + GFIELD(arg, CMD53_BLK_MODE) ? "Block" : "Byte", + GFIELD(arg, CMD53_BYTE_BLK_CNT), + GFIELD(arg, CMD53_OP_CODE) ? "Incrementing addr" : "Single addr")); + + cmd_arg = arg; + xfer_reg = 0; + + cmd_reg = SFIELD(cmd_reg, CMD_RESP_TYPE, RESP_TYPE_48); + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_DATA_EN, 1); + cmd_reg = SFIELD(cmd_reg, CMD_TYPE, CMD_TYPE_NORMAL); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX, cmd); + + use_dma = USE_DMA(sdioh_info) && GFIELD(cmd_arg, CMD53_BLK_MODE); + + if (GFIELD(cmd_arg, CMD53_BLK_MODE)) { + uint16 blocksize; + uint16 blockcount; + int func; + + ASSERT(sdioh_info->sd_blockmode); + + func = GFIELD(cmd_arg, CMD53_FUNCTION); + blocksize = MIN((int)sdioh_info->data_xfer_count, + sdioh_info->client_block_size[func]); + blockcount = GFIELD(cmd_arg, CMD53_BYTE_BLK_CNT); + + /* data_xfer_cnt is already setup so that for multiblock mode, + * it is the entire buffer length. For non-block or single block, + * it is < 64 bytes + */ + if (use_dma) { + switch (sdioh_info->sd_dma_mode) { + case DMA_MODE_SDMA: + sd_dma(("%s: SDMA: SysAddr reg was 0x%x now 0x%x\n", + __FUNCTION__, sdstd_rreg(sdioh_info, SD_SysAddr), + (uint32)sdioh_info->dma_phys)); + sdstd_wreg(sdioh_info, SD_SysAddr, sdioh_info->dma_phys); + break; + case DMA_MODE_ADMA1: + case DMA_MODE_ADMA2: + sd_dma(("%s: ADMA: Using ADMA\n", __FUNCTION__)); + sd_create_adma_descriptor(sdioh_info, 0, + sdioh_info->dma_phys, blockcount*blocksize, + ADMA2_ATTRIBUTE_VALID | ADMA2_ATTRIBUTE_END | + ADMA2_ATTRIBUTE_INT | ADMA2_ATTRIBUTE_ACT_TRAN); + /* Dump descriptor if DMA debugging is enabled. */ + if (sd_msglevel & SDH_DMA_VAL) { + sd_dump_adma_dscr(sdioh_info); + } + + sdstd_wreg(sdioh_info, SD_ADMA_SysAddr, + sdioh_info->adma2_dscr_phys); + break; + default: + sd_err(("%s: unsupported DMA mode %d.\n", + __FUNCTION__, sdioh_info->sd_dma_mode)); + break; + } + } + + sd_trace(("%s: Setting block count %d, block size %d bytes\n", + __FUNCTION__, blockcount, blocksize)); + sdstd_wreg16(sdioh_info, SD_BlockSize, blocksize); + sdstd_wreg16(sdioh_info, SD_BlockCount, blockcount); + + xfer_reg = SFIELD(xfer_reg, XFER_DMA_ENABLE, use_dma); + + if (sdioh_info->client_block_size[func] != blocksize) + set_client_block_size(sdioh_info, 1, blocksize); + + if (blockcount > 1) { + xfer_reg = SFIELD(xfer_reg, XFER_MULTI_BLOCK, 1); + xfer_reg = SFIELD(xfer_reg, XFER_BLK_COUNT_EN, 1); + xfer_reg = SFIELD(xfer_reg, XFER_CMD_12_EN, 0); + } else { + xfer_reg = SFIELD(xfer_reg, XFER_MULTI_BLOCK, 0); + xfer_reg = SFIELD(xfer_reg, XFER_BLK_COUNT_EN, 0); + xfer_reg = SFIELD(xfer_reg, XFER_CMD_12_EN, 0); + } + + if (GFIELD(cmd_arg, CMD53_RW_FLAG) == SDIOH_XFER_TYPE_READ) + xfer_reg = SFIELD(xfer_reg, XFER_DATA_DIRECTION, 1); + else + xfer_reg = SFIELD(xfer_reg, XFER_DATA_DIRECTION, 0); + + retries = RETRIES_SMALL; + while (GFIELD(sdstd_rreg(sdioh_info, SD_PresentState), + PRES_DAT_INHIBIT) && --retries) + sd_err(("%s: Waiting for Data Inhibit cmd = %d\n", + __FUNCTION__, cmd)); + if (!retries) { + sd_err(("%s: Data Inhibit timeout\n", __FUNCTION__)); + if (trap_errs) + ASSERT(0); + return ERROR; + } + sdstd_wreg16(sdioh_info, SD_TransferMode, xfer_reg); + + } else { /* Non block mode */ + uint16 bytes = GFIELD(cmd_arg, CMD53_BYTE_BLK_CNT); + /* The byte/block count field only has 9 bits, + * so, to do a 512-byte bytemode transfer, this + * field will contain 0, but we need to tell the + * controller we're transferring 512 bytes. + */ + if (bytes == 0) bytes = 512; + + if (use_dma) + sdstd_wreg(sdioh_info, SD_SysAddr, sdioh_info->dma_phys); + + /* PCI: Transfer Mode register 0x0c */ + xfer_reg = SFIELD(xfer_reg, XFER_DMA_ENABLE, bytes <= 4 ? 0 : use_dma); + xfer_reg = SFIELD(xfer_reg, XFER_CMD_12_EN, 0); + if (GFIELD(cmd_arg, CMD53_RW_FLAG) == SDIOH_XFER_TYPE_READ) + xfer_reg = SFIELD(xfer_reg, XFER_DATA_DIRECTION, 1); + else + xfer_reg = SFIELD(xfer_reg, XFER_DATA_DIRECTION, 0); + /* See table 2-8 Host Controller spec ver 1.00 */ + xfer_reg = SFIELD(xfer_reg, XFER_BLK_COUNT_EN, 0); /* Dont care */ + xfer_reg = SFIELD(xfer_reg, XFER_MULTI_BLOCK, 0); + + sdstd_wreg16(sdioh_info, SD_BlockSize, bytes); + + sdstd_wreg16(sdioh_info, SD_BlockCount, 1); + + retries = RETRIES_SMALL; + while (GFIELD(sdstd_rreg(sdioh_info, SD_PresentState), + PRES_DAT_INHIBIT) && --retries) + sd_err(("%s: Waiting for Data Inhibit cmd = %d\n", + __FUNCTION__, cmd)); + if (!retries) { + sd_err(("%s: Data Inhibit timeout\n", __FUNCTION__)); + if (trap_errs) + ASSERT(0); + return ERROR; + } + sdstd_wreg16(sdioh_info, SD_TransferMode, xfer_reg); + } + break; + + default: + sd_err(("%s: Unknown command\n", __FUNCTION__)); + return ERROR; + } + + if (sdioh_info->sd_mode == SDIOH_MODE_SPI) { + cmd_reg = SFIELD(cmd_reg, CMD_CRC_EN, 0); + cmd_reg = SFIELD(cmd_reg, CMD_INDEX_EN, 0); + } + + /* Setup and issue the SDIO command */ + sdstd_wreg(sdioh_info, SD_Arg0, arg); + sdstd_wreg16(sdioh_info, SD_Command, cmd_reg); + + /* If we are in polled mode, wait for the command to complete. + * In interrupt mode, return immediately. The calling function will + * know that the command has completed when the CMDATDONE interrupt + * is asserted + */ + if (sdioh_info->polled_mode) { + uint16 int_reg = 0; + int retries = RETRIES_LARGE; + + do { + int_reg = sdstd_rreg16(sdioh_info, SD_IntrStatus); + } while (--retries && + (GFIELD(int_reg, INTSTAT_ERROR_INT) == 0) && + (GFIELD(int_reg, INTSTAT_CMD_COMPLETE) == 0)); + + if (!retries) { + sd_err(("%s: CMD_COMPLETE timeout: intrStatus: 0x%x " + "error stat 0x%x state 0x%x\n", + __FUNCTION__, int_reg, + sdstd_rreg16(sdioh_info, SD_ErrorIntrStatus), + sdstd_rreg(sdioh_info, SD_PresentState))); + + /* Attempt to reset CMD line when we get a CMD timeout */ + sdstd_wreg8(sdioh_info, SD_SoftwareReset, SFIELD(0, SW_RESET_CMD, 1)); + retries = RETRIES_LARGE; + do { + sd_trace(("%s: waiting for CMD line reset\n", __FUNCTION__)); + } while ((GFIELD(sdstd_rreg8(sdioh_info, SD_SoftwareReset), + SW_RESET_CMD)) && retries--); + + if (!retries) { + sd_err(("%s: Timeout waiting for CMD line reset\n", __FUNCTION__)); + } + + if (trap_errs) + ASSERT(0); + return (ERROR); + } + + /* Clear Command Complete interrupt */ + int_reg = SFIELD(0, INTSTAT_CMD_COMPLETE, 1); + sdstd_wreg16(sdioh_info, SD_IntrStatus, int_reg); + + /* Check for Errors */ + if (sdstd_check_errs(sdioh_info, cmd, arg)) { + if (trap_errs) + ASSERT(0); + return ERROR; + } + } + return SUCCESS; +} + + +static int +sdstd_card_buf(sdioh_info_t *sd, int rw, int func, bool fifo, uint32 addr, int nbytes, uint32 *data) +{ + int status; + uint32 cmd_arg; + uint32 rsp5; + uint16 int_reg, int_bit; + uint flags; + int num_blocks, blocksize; + bool local_blockmode, local_dma; + bool read = rw == SDIOH_READ ? 1 : 0; + bool yield = FALSE; + + ASSERT(nbytes); + + cmd_arg = 0; + + sd_data(("%s: %s 53 addr 0x%x, len %d bytes, r_cnt %d t_cnt %d\n", + __FUNCTION__, read ? "Rd" : "Wr", addr, nbytes, sd->r_cnt, sd->t_cnt)); + + if (read) sd->r_cnt++; else sd->t_cnt++; + + local_blockmode = sd->sd_blockmode; + local_dma = USE_DMA(sd); + + /* Don't bother with block mode on small xfers */ + if (nbytes < sd->client_block_size[func]) { + sd_data(("setting local blockmode to false: nbytes (%d) != block_size (%d)\n", + nbytes, sd->client_block_size[func])); + local_blockmode = FALSE; + local_dma = FALSE; + } + + if (local_blockmode) { + blocksize = MIN(sd->client_block_size[func], nbytes); + num_blocks = nbytes/blocksize; + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, num_blocks); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 1); + } else { + num_blocks = 1; + blocksize = nbytes; + cmd_arg = SFIELD(cmd_arg, CMD53_BYTE_BLK_CNT, nbytes); + cmd_arg = SFIELD(cmd_arg, CMD53_BLK_MODE, 0); + } + + if (local_dma && !read) { + bcopy(data, sd->dma_buf, nbytes); + sd_sync_dma(sd, read, nbytes); + } + + if (fifo) + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 0); + else + cmd_arg = SFIELD(cmd_arg, CMD53_OP_CODE, 1); + + cmd_arg = SFIELD(cmd_arg, CMD53_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, CMD53_REG_ADDR, addr); + if (read) + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_READ); + else + cmd_arg = SFIELD(cmd_arg, CMD53_RW_FLAG, SDIOH_XFER_TYPE_WRITE); + + sd->data_xfer_count = nbytes; + + /* sdstd_cmd_issue() returns with the command complete bit + * in the ISR already cleared + */ + if ((status = sdstd_cmd_issue(sd, local_dma, SDIOH_CMD_53, cmd_arg)) != SUCCESS) { + sd_err(("%s: cmd_issue failed for %s\n", __FUNCTION__, (read ? "read" : "write"))); + return status; + } + + sdstd_cmd_getrsp(sd, &rsp5, 1); + + if ((flags = GFIELD(rsp5, RSP5_FLAGS)) != 0x10) { + sd_err(("%s: Rsp5: nbytes %d, dma %d blockmode %d, read %d " + "numblocks %d, blocksize %d\n", + __FUNCTION__, nbytes, local_dma, local_dma, read, num_blocks, blocksize)); + + if (flags & 1) + sd_err(("%s: rsp5: Command not accepted: arg out of range 0x%x, " + "bytes %d dma %d\n", + __FUNCTION__, flags, GFIELD(cmd_arg, CMD53_BYTE_BLK_CNT), + GFIELD(cmd_arg, CMD53_BLK_MODE))); + if (flags & 0x8) + sd_err(("%s: Rsp5: General Error\n", __FUNCTION__)); + + sd_err(("%s: rsp5 flags = 0x%x, expecting 0x10 returning error\n", + __FUNCTION__, flags)); + if (trap_errs) + ASSERT(0); + return ERROR; + } + + if (GFIELD(rsp5, RSP5_STUFF)) + sd_err(("%s: rsp5 stuff is 0x%x: expecting 0\n", + __FUNCTION__, GFIELD(rsp5, RSP5_STUFF))); + +#ifdef BCMSDYIELD + yield = sd_yieldcpu && ((uint)nbytes >= sd_minyield); +#endif + + if (!local_dma) { + int bytes, i; + uint32 tmp; + for (i = 0; i < num_blocks; i++) { + int words; + + /* Decide which status bit we're waiting for */ + if (read) + int_bit = SFIELD(0, INTSTAT_BUF_READ_READY, 1); + else + int_bit = SFIELD(0, INTSTAT_BUF_WRITE_READY, 1); + + /* If not on, wait for it (or for xfer error) */ + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + if (!(int_reg & int_bit)) + int_reg = sdstd_waitbits(sd, int_bit, ERRINT_TRANSFER_ERRS, yield); + + /* Confirm we got the bit w/o error */ + if (!(int_reg & int_bit) || GFIELD(int_reg, INTSTAT_ERROR_INT)) { + sd_err(("%s: Error or timeout for Buf_%s_Ready: intStat: 0x%x " + "errint: 0x%x PresentState 0x%x\n", + __FUNCTION__, read ? "Read" : "Write", int_reg, + sdstd_rreg16(sd, SD_ErrorIntrStatus), + sdstd_rreg(sd, SD_PresentState))); + sdstd_dumpregs(sd); + sdstd_check_errs(sd, SDIOH_CMD_53, cmd_arg); + return (ERROR); + } + + /* Clear Buf Ready bit */ + sdstd_wreg16(sd, SD_IntrStatus, int_bit); + + /* At this point we have Buffer Ready, write the data 4 bytes at a time */ + for (words = blocksize/4; words; words--) { + if (read) + *data = sdstd_rreg(sd, SD_BufferDataPort0); + else + sdstd_wreg(sd, SD_BufferDataPort0, *data); + data++; + } + + bytes = blocksize % 4; + + /* If no leftover bytes, go to next block */ + if (!bytes) + continue; + + switch (bytes) { + case 1: + /* R/W 8 bits */ + if (read) + *(data++) = (uint32)(sdstd_rreg8(sd, SD_BufferDataPort0)); + else + sdstd_wreg8(sd, SD_BufferDataPort0, + (uint8)(*(data++) & 0xff)); + break; + case 2: + /* R/W 16 bits */ + if (read) + *(data++) = (uint32)sdstd_rreg16(sd, SD_BufferDataPort0); + else + sdstd_wreg16(sd, SD_BufferDataPort0, (uint16)(*(data++))); + break; + case 3: + /* R/W 24 bits: + * SD_BufferDataPort0[0-15] | SD_BufferDataPort1[16-23] + */ + if (read) { + tmp = (uint32)sdstd_rreg16(sd, SD_BufferDataPort0); + tmp |= ((uint32)(sdstd_rreg8(sd, + SD_BufferDataPort1)) << 16); + *(data++) = tmp; + } else { + tmp = *(data++); + sdstd_wreg16(sd, SD_BufferDataPort0, (uint16)tmp & 0xffff); + sdstd_wreg8(sd, SD_BufferDataPort1, + (uint8)((tmp >> 16) & 0xff)); + } + break; + default: + sd_err(("%s: Unexpected bytes leftover %d\n", + __FUNCTION__, bytes)); + ASSERT(0); + break; + } + } + } /* End PIO processing */ + + /* Wait for Transfer Complete or Transfer Error */ + int_bit = SFIELD(0, INTSTAT_XFER_COMPLETE, 1); + + /* If not on, wait for it (or for xfer error) */ + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + if (!(int_reg & int_bit)) + int_reg = sdstd_waitbits(sd, int_bit, ERRINT_TRANSFER_ERRS, yield); + + /* Check for any errors from the data phase */ + if (sdstd_check_errs(sd, SDIOH_CMD_53, cmd_arg)) + return ERROR; + + /* May have gotten a software timeout if not blocking? */ + int_reg = sdstd_rreg16(sd, SD_IntrStatus); + if (!(int_reg & int_bit)) { + sd_err(("%s: Error or Timeout for xfer complete; %s, dma %d, State 0x%08x, " + "intr 0x%04x, Err 0x%04x, len = %d, rcnt %d, tcnt %d\n", + __FUNCTION__, read ? "R" : "W", local_dma, + sdstd_rreg(sd, SD_PresentState), int_reg, + sdstd_rreg16(sd, SD_ErrorIntrStatus), nbytes, + sd->r_cnt, sd->t_cnt)); + sdstd_dumpregs(sd); + return ERROR; + } + + /* Clear the status bits */ + int_reg = int_bit; + if (local_dma) { + /* DMA Complete */ + /* Reads in particular don't have DMA_COMPLETE set */ + int_reg = SFIELD(int_reg, INTSTAT_DMA_INT, 1); + } + sdstd_wreg16(sd, SD_IntrStatus, int_reg); + + /* Fetch data */ + if (local_dma && read) { + sd_sync_dma(sd, read, nbytes); + bcopy(sd->dma_buf, data, nbytes); + } + return SUCCESS; +} + +static int +set_client_block_size(sdioh_info_t *sd, int func, int block_size) +{ + int base; + int err = 0; + + + sd_err(("%s: Setting block size %d, func %d\n", __FUNCTION__, block_size, func)); + sd->client_block_size[func] = block_size; + + /* Set the block size in the SDIO Card register */ + base = func * SDIOD_FBR_SIZE; + err = sdstd_card_regwrite(sd, 0, base+SDIOD_CCCR_BLKSIZE_0, 1, block_size & 0xff); + if (!err) { + err = sdstd_card_regwrite(sd, 0, base+SDIOD_CCCR_BLKSIZE_1, 1, + (block_size >> 8) & 0xff); + } + + /* Do not set the block size in the SDIO Host register, that + * is func dependent and will get done on an individual + * transaction basis + */ + + return (err ? BCME_SDIO_ERROR : 0); +} + +/* Reset and re-initialize the device */ +int +sdioh_sdio_reset(sdioh_info_t *si) +{ + uint8 hreg; + + /* Reset the attached device (use slower clock for safety) */ + sdstd_start_clock(si, 128); + sdstd_reset(si, 0, 1); + + /* Reset portions of the host state accordingly */ + hreg = sdstd_rreg8(si, SD_HostCntrl); + hreg = SFIELD(hreg, HOST_HI_SPEED_EN, 0); + hreg = SFIELD(hreg, HOST_DATA_WIDTH, 0); + si->sd_mode = SDIOH_MODE_SD1; + + /* Reinitialize the card */ + si->card_init_done = FALSE; + return sdstd_client_init(si); +} + + +static void +sd_map_dma(sdioh_info_t * sd) +{ + + void *va; + + if ((va = DMA_ALLOC_CONSISTENT(sd->osh, SD_PAGE, + &sd->dma_start_phys, 0x12, 12)) == NULL) { + sd->sd_dma_mode = DMA_MODE_NONE; + sd->dma_start_buf = 0; + sd->dma_buf = (void *)0; + sd->dma_phys = 0; + sd->alloced_dma_size = SD_PAGE; + sd_err(("%s: DMA_ALLOC failed. Disabling DMA support.\n", __FUNCTION__)); + } else { + sd->dma_start_buf = va; + sd->dma_buf = (void *)ROUNDUP((uintptr)va, SD_PAGE); + sd->dma_phys = ROUNDUP((sd->dma_start_phys), SD_PAGE); + sd->alloced_dma_size = SD_PAGE; + sd_err(("%s: Mapped DMA Buffer %dbytes @virt/phys: %p/0x%lx\n", + __FUNCTION__, sd->alloced_dma_size, sd->dma_buf, sd->dma_phys)); + sd_fill_dma_data_buf(sd, 0xA5); + } + + if ((va = DMA_ALLOC_CONSISTENT(sd->osh, SD_PAGE, + &sd->adma2_dscr_start_phys, 0x12, 12)) == NULL) { + sd->sd_dma_mode = DMA_MODE_NONE; + sd->adma2_dscr_start_buf = 0; + sd->adma2_dscr_buf = (void *)0; + sd->adma2_dscr_phys = 0; + sd->alloced_adma2_dscr_size = 0; + sd_err(("%s: DMA_ALLOC failed for descriptor buffer. " + "Disabling DMA support.\n", __FUNCTION__)); + } else { + sd->adma2_dscr_start_buf = va; + sd->adma2_dscr_buf = (void *)ROUNDUP((uintptr)va, SD_PAGE); + sd->adma2_dscr_phys = ROUNDUP((sd->adma2_dscr_start_phys), SD_PAGE); + sd->alloced_adma2_dscr_size = SD_PAGE; + } + + sd_err(("%s: Mapped ADMA2 Descriptor Buffer %dbytes @virt/phys: %p/0x%lx\n", + __FUNCTION__, sd->alloced_adma2_dscr_size, sd->adma2_dscr_buf, + sd->adma2_dscr_phys)); + sd_clear_adma_dscr_buf(sd); +} + +static void +sd_unmap_dma(sdioh_info_t * sd) +{ + if (sd->dma_start_buf) { + DMA_FREE_CONSISTENT(sd->osh, sd->dma_start_buf, sd->alloced_dma_size, + sd->dma_start_phys, 0x12); + } + + if (sd->adma2_dscr_start_buf) { + DMA_FREE_CONSISTENT(sd->osh, sd->adma2_dscr_start_buf, sd->alloced_adma2_dscr_size, + sd->adma2_dscr_start_phys, 0x12); + } +} + +static void sd_clear_adma_dscr_buf(sdioh_info_t *sd) +{ + bzero((char *)sd->adma2_dscr_buf, SD_PAGE); + sd_dump_adma_dscr(sd); +} + +static void sd_fill_dma_data_buf(sdioh_info_t *sd, uint8 data) +{ + memset((char *)sd->dma_buf, data, SD_PAGE); +} + + +static void sd_create_adma_descriptor(sdioh_info_t *sd, uint32 index, + uint32 addr_phys, uint16 length, uint16 flags) +{ + adma2_dscr_32b_t *adma2_dscr_table; + adma1_dscr_t *adma1_dscr_table; + + adma2_dscr_table = sd->adma2_dscr_buf; + adma1_dscr_table = sd->adma2_dscr_buf; + + switch (sd->sd_dma_mode) { + case DMA_MODE_ADMA2: + sd_dma(("%s: creating ADMA2 descriptor for index %d\n", + __FUNCTION__, index)); + + adma2_dscr_table[index].phys_addr = addr_phys; + adma2_dscr_table[index].len_attr = length << 16; + adma2_dscr_table[index].len_attr |= flags; + break; + case DMA_MODE_ADMA1: + /* ADMA1 requires two descriptors, one for len + * and the other for data transfer + */ + index <<= 1; + + sd_dma(("%s: creating ADMA1 descriptor for index %d\n", + __FUNCTION__, index)); + + adma1_dscr_table[index].phys_addr_attr = length << 12; + adma1_dscr_table[index].phys_addr_attr |= (ADMA1_ATTRIBUTE_ACT_SET | + ADMA2_ATTRIBUTE_VALID); + adma1_dscr_table[index+1].phys_addr_attr = addr_phys & 0xFFFFF000; + adma1_dscr_table[index+1].phys_addr_attr |= (flags & 0x3f); + break; + default: + sd_err(("%s: cannot create ADMA descriptor for DMA mode %d\n", + __FUNCTION__, sd->sd_dma_mode)); + break; + } +} + + +static void sd_dump_adma_dscr(sdioh_info_t *sd) +{ + adma2_dscr_32b_t *adma2_dscr_table; + adma1_dscr_t *adma1_dscr_table; + uint32 i = 0; + uint16 flags; + char flags_str[32]; + + ASSERT(sd->adma2_dscr_buf != NULL); + + adma2_dscr_table = sd->adma2_dscr_buf; + adma1_dscr_table = sd->adma2_dscr_buf; + + switch (sd->sd_dma_mode) { + case DMA_MODE_ADMA2: + sd_err(("ADMA2 Descriptor Table (%dbytes) @virt/phys: %p/0x%lx\n", + SD_PAGE, sd->adma2_dscr_buf, sd->adma2_dscr_phys)); + sd_err((" #[Descr VA ] Buffer PA | Len | Flags (5:4 2 1 0)" + " |\n")); + while (adma2_dscr_table->len_attr & ADMA2_ATTRIBUTE_VALID) { + flags = adma2_dscr_table->len_attr & 0xFFFF; + sprintf(flags_str, "%s%s%s%s", + ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_LINK) ? "LINK " : + ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_TRAN) ? "TRAN " : + ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_NOP) ? "NOP " : "RSV ", + (flags & ADMA2_ATTRIBUTE_INT ? "INT " : " "), + (flags & ADMA2_ATTRIBUTE_END ? "END " : " "), + (flags & ADMA2_ATTRIBUTE_VALID ? "VALID" : "")); + sd_err(("%2d[0x%p]: 0x%08x | 0x%04x | 0x%04x (%s) |\n", + i, adma2_dscr_table, adma2_dscr_table->phys_addr, + adma2_dscr_table->len_attr >> 16, flags, flags_str)); + i++; + + /* Follow LINK descriptors or skip to next. */ + if ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_LINK) { + adma2_dscr_table = phys_to_virt( + adma2_dscr_table->phys_addr); + } else { + adma2_dscr_table++; + } + + } + break; + case DMA_MODE_ADMA1: + sd_err(("ADMA1 Descriptor Table (%dbytes) @virt/phys: %p/0x%lx\n", + SD_PAGE, sd->adma2_dscr_buf, sd->adma2_dscr_phys)); + sd_err((" #[Descr VA ] Buffer PA | Flags (5:4 2 1 0) |\n")); + + for (i = 0; adma1_dscr_table->phys_addr_attr & ADMA2_ATTRIBUTE_VALID; i++) { + flags = adma1_dscr_table->phys_addr_attr & 0x3F; + sprintf(flags_str, "%s%s%s%s", + ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_LINK) ? "LINK " : + ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_TRAN) ? "TRAN " : + ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_NOP) ? "NOP " : "SET ", + (flags & ADMA2_ATTRIBUTE_INT ? "INT " : " "), + (flags & ADMA2_ATTRIBUTE_END ? "END " : " "), + (flags & ADMA2_ATTRIBUTE_VALID ? "VALID" : "")); + sd_err(("%2d[0x%p]: 0x%08x | 0x%04x | (%s) |\n", + i, adma1_dscr_table, + adma1_dscr_table->phys_addr_attr & 0xFFFFF000, + flags, flags_str)); + + /* Follow LINK descriptors or skip to next. */ + if ((flags & ADMA2_ATTRIBUTE_ACT_LINK) == + ADMA2_ATTRIBUTE_ACT_LINK) { + adma1_dscr_table = phys_to_virt( + adma1_dscr_table->phys_addr_attr & 0xFFFFF000); + } else { + adma1_dscr_table++; + } + } + break; + default: + sd_err(("Unknown DMA Descriptor Table Format.\n")); + break; + } +} + +static void sdstd_dumpregs(sdioh_info_t *sd) +{ + sd_err(("IntrStatus: 0x%04x ErrorIntrStatus 0x%04x\n", + sdstd_rreg16(sd, SD_IntrStatus), + sdstd_rreg16(sd, SD_ErrorIntrStatus))); + sd_err(("IntrStatusEnable: 0x%04x ErrorIntrStatusEnable 0x%04x\n", + sdstd_rreg16(sd, SD_IntrStatusEnable), + sdstd_rreg16(sd, SD_ErrorIntrStatusEnable))); + sd_err(("IntrSignalEnable: 0x%04x ErrorIntrSignalEnable 0x%04x\n", + sdstd_rreg16(sd, SD_IntrSignalEnable), + sdstd_rreg16(sd, SD_ErrorIntrSignalEnable))); +} diff --git a/drivers/net/wireless/bcm4329/bcmsdstd_linux.c b/drivers/net/wireless/bcm4329/bcmsdstd_linux.c new file mode 100644 index 0000000000000000000000000000000000000000..a8b98e2054a040b65b3063053c83b6ee89d2bfbb --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmsdstd_linux.c @@ -0,0 +1,251 @@ +/* + * 'Standard' SDIO HOST CONTROLLER driver - linux portion + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdstd_linux.c,v 1.11.18.2.16.1 2010/08/17 17:03:13 Exp $ + */ + +#include +#include +#include +#include /* SDIO Specs */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* to get msglevel bit values */ + +#include /* request_irq() */ + +#include + +struct sdos_info { + sdioh_info_t *sd; + spinlock_t lock; + wait_queue_head_t intr_wait_queue; +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define BLOCKABLE() (!in_atomic()) +#else +#define BLOCKABLE() (!in_interrupt()) +#endif + +/* Interrupt handler */ +static irqreturn_t +sdstd_isr(int irq, void *dev_id +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) +, struct pt_regs *ptregs +#endif +) +{ + sdioh_info_t *sd; + struct sdos_info *sdos; + bool ours; + + sd = (sdioh_info_t *)dev_id; + + if (!sd->card_init_done) { + sd_err(("%s: Hey Bogus intr...not even initted: irq %d\n", __FUNCTION__, irq)); + return IRQ_RETVAL(FALSE); + } else { + ours = check_client_intr(sd); + + /* For local interrupts, wake the waiting process */ + if (ours && sd->got_hcint) { + sd_trace(("INTR->WAKE\n")); + sdos = (struct sdos_info *)sd->sdos_info; + wake_up_interruptible(&sdos->intr_wait_queue); + } + return IRQ_RETVAL(ours); + } +} + +/* Register with Linux for interrupts */ +int +sdstd_register_irq(sdioh_info_t *sd, uint irq) +{ + sd_trace(("Entering %s: irq == %d\n", __FUNCTION__, irq)); + if (request_irq(irq, sdstd_isr, IRQF_SHARED, "bcmsdstd", sd) < 0) { + sd_err(("%s: request_irq() failed\n", __FUNCTION__)); + return ERROR; + } + return SUCCESS; +} + +/* Free Linux irq */ +void +sdstd_free_irq(uint irq, sdioh_info_t *sd) +{ + free_irq(irq, sd); +} + +/* Map Host controller registers */ + +uint32 * +sdstd_reg_map(osl_t *osh, int32 addr, int size) +{ + return (uint32 *)REG_MAP(addr, size); +} + +void +sdstd_reg_unmap(osl_t *osh, int32 addr, int size) +{ + REG_UNMAP((void*)(uintptr)addr); +} + +int +sdstd_osinit(sdioh_info_t *sd) +{ + struct sdos_info *sdos; + + sdos = (struct sdos_info*)MALLOC(sd->osh, sizeof(struct sdos_info)); + sd->sdos_info = (void*)sdos; + if (sdos == NULL) + return BCME_NOMEM; + + sdos->sd = sd; + spin_lock_init(&sdos->lock); + init_waitqueue_head(&sdos->intr_wait_queue); + return BCME_OK; +} + +void +sdstd_osfree(sdioh_info_t *sd) +{ + struct sdos_info *sdos; + ASSERT(sd && sd->sdos_info); + + sdos = (struct sdos_info *)sd->sdos_info; + MFREE(sd->osh, sdos, sizeof(struct sdos_info)); +} + +/* Interrupt enable/disable */ +SDIOH_API_RC +sdioh_interrupt_set(sdioh_info_t *sd, bool enable) +{ + ulong flags; + struct sdos_info *sdos; + + sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling")); + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + + if (!(sd->host_init_done && sd->card_init_done)) { + sd_err(("%s: Card & Host are not initted - bailing\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } + + if (enable && !(sd->intr_handler && sd->intr_handler_arg)) { + sd_err(("%s: no handler registered, will not enable\n", __FUNCTION__)); + return SDIOH_API_RC_FAIL; + } + + /* Ensure atomicity for enable/disable calls */ + spin_lock_irqsave(&sdos->lock, flags); + + sd->client_intr_enabled = enable; + if (enable && !sd->lockcount) + sdstd_devintr_on(sd); + else + sdstd_devintr_off(sd); + + spin_unlock_irqrestore(&sdos->lock, flags); + + return SDIOH_API_RC_SUCCESS; +} + +/* Protect against reentrancy (disable device interrupts while executing) */ +void +sdstd_lock(sdioh_info_t *sd) +{ + ulong flags; + struct sdos_info *sdos; + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + + sd_trace(("%s: %d\n", __FUNCTION__, sd->lockcount)); + + spin_lock_irqsave(&sdos->lock, flags); + if (sd->lockcount) { + sd_err(("%s: Already locked! called from %p\n", + __FUNCTION__, + __builtin_return_address(0))); + ASSERT(sd->lockcount == 0); + } + sdstd_devintr_off(sd); + sd->lockcount++; + spin_unlock_irqrestore(&sdos->lock, flags); +} + +/* Enable client interrupt */ +void +sdstd_unlock(sdioh_info_t *sd) +{ + ulong flags; + struct sdos_info *sdos; + + sd_trace(("%s: %d, %d\n", __FUNCTION__, sd->lockcount, sd->client_intr_enabled)); + ASSERT(sd->lockcount > 0); + + sdos = (struct sdos_info *)sd->sdos_info; + ASSERT(sdos); + + spin_lock_irqsave(&sdos->lock, flags); + if (--sd->lockcount == 0 && sd->client_intr_enabled) { + sdstd_devintr_on(sd); + } + spin_unlock_irqrestore(&sdos->lock, flags); +} + +uint16 +sdstd_waitbits(sdioh_info_t *sd, uint16 norm, uint16 err, bool yield) +{ + struct sdos_info *sdos; + + sdos = (struct sdos_info *)sd->sdos_info; + +#ifndef BCMSDYIELD + ASSERT(!yield); +#endif + sd_trace(("%s: int 0x%02x err 0x%02x yield %d canblock %d\n", + __FUNCTION__, norm, err, yield, BLOCKABLE())); + + /* Clear the "interrupt happened" flag and last intrstatus */ + sd->got_hcint = FALSE; + sd->last_intrstatus = 0; + +#ifdef BCMSDYIELD + if (yield && BLOCKABLE()) { + /* Enable interrupts, wait for the indication, then disable */ + sdstd_intrs_on(sd, norm, err); + wait_event_interruptible(sdos->intr_wait_queue, (sd->got_hcint)); + sdstd_intrs_off(sd, norm, err); + } else +#endif /* BCMSDYIELD */ + { + sdstd_spinbits(sd, norm, err); + } + + sd_trace(("%s: last_intrstatus 0x%04x\n", __FUNCTION__, sd->last_intrstatus)); + + return sd->last_intrstatus; +} diff --git a/drivers/net/wireless/bcm4329/bcmspibrcm.c b/drivers/net/wireless/bcm4329/bcmspibrcm.c new file mode 100644 index 0000000000000000000000000000000000000000..0f131a40f4b8eb12a5cb7758dea622dbc5ea0e6d --- /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/bcm4329/bcmutils.c b/drivers/net/wireless/bcm4329/bcmutils.c new file mode 100644 index 0000000000000000000000000000000000000000..43c04ee92f38bd369658e283cfd7e1918b15096c --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmutils.c @@ -0,0 +1,1838 @@ +/* + * Driver O/S-independent utility routines + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: bcmutils.c,v 1.210.4.5.2.4.6.19 2010/04/26 06:05:25 Exp $ + */ + +#include +#include +#include +#include +#ifdef BCMDRIVER +#include +#include +#else +#include +#include +/* This case for external supplicant use */ +#if defined(BCMEXTSUP) +#include +#endif + +#endif /* BCMDRIVER */ +#include +#include +#include +#include +#include +#include +#include + + +#ifdef BCMDRIVER + + +/* copy a pkt buffer chain into a buffer */ +uint +pktcopy(osl_t *osh, void *p, uint offset, int len, uchar *buf) +{ + uint n, ret = 0; + + if (len < 0) + len = 4096; /* "infinite" */ + + /* skip 'offset' bytes */ + for (; p && offset; p = PKTNEXT(osh, p)) { + if (offset < (uint)PKTLEN(osh, p)) + break; + offset -= PKTLEN(osh, p); + } + + if (!p) + return 0; + + /* copy the data */ + for (; p && len; p = PKTNEXT(osh, p)) { + n = MIN((uint)PKTLEN(osh, p) - offset, (uint)len); + bcopy(PKTDATA(osh, p) + offset, buf, n); + buf += n; + len -= n; + ret += n; + offset = 0; + } + + return ret; +} + +/* copy a buffer into a pkt buffer chain */ +uint +pktfrombuf(osl_t *osh, void *p, uint offset, int len, uchar *buf) +{ + uint n, ret = 0; + + /* skip 'offset' bytes */ + for (; p && offset; p = PKTNEXT(osh, p)) { + if (offset < (uint)PKTLEN(osh, p)) + break; + offset -= PKTLEN(osh, p); + } + + if (!p) + return 0; + + /* copy the data */ + for (; p && len; p = PKTNEXT(osh, p)) { + n = MIN((uint)PKTLEN(osh, p) - offset, (uint)len); + bcopy(buf, PKTDATA(osh, p) + offset, n); + buf += n; + len -= n; + ret += n; + offset = 0; + } + + return ret; +} + + + +/* return total length of buffer chain */ +uint +pkttotlen(osl_t *osh, void *p) +{ + uint total; + + total = 0; + for (; p; p = PKTNEXT(osh, p)) + total += PKTLEN(osh, p); + return (total); +} + +/* return the last buffer of chained pkt */ +void * +pktlast(osl_t *osh, void *p) +{ + for (; PKTNEXT(osh, p); p = PKTNEXT(osh, p)) + ; + + return (p); +} + +/* count segments of a chained packet */ +uint +pktsegcnt(osl_t *osh, void *p) +{ + uint cnt; + + for (cnt = 0; p; p = PKTNEXT(osh, p)) + cnt++; + + return cnt; +} + + +/* + * osl multiple-precedence packet queue + * hi_prec is always >= the number of the highest non-empty precedence + */ +void * +pktq_penq(struct pktq *pq, int prec, void *p) +{ + struct pktq_prec *q; + + ASSERT(prec >= 0 && prec < pq->num_prec); + ASSERT(PKTLINK(p) == NULL); /* queueing chains not allowed */ + + ASSERT(!pktq_full(pq)); + ASSERT(!pktq_pfull(pq, prec)); + + q = &pq->q[prec]; + + if (q->head) + PKTSETLINK(q->tail, p); + else + q->head = p; + + q->tail = p; + q->len++; + + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (uint8)prec; + + return p; +} + +void * +pktq_penq_head(struct pktq *pq, int prec, void *p) +{ + struct pktq_prec *q; + + ASSERT(prec >= 0 && prec < pq->num_prec); + ASSERT(PKTLINK(p) == NULL); /* queueing chains not allowed */ + + ASSERT(!pktq_full(pq)); + ASSERT(!pktq_pfull(pq, prec)); + + q = &pq->q[prec]; + + if (q->head == NULL) + q->tail = p; + + PKTSETLINK(p, q->head); + q->head = p; + q->len++; + + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (uint8)prec; + + return p; +} + +void * +pktq_pdeq(struct pktq *pq, int prec) +{ + struct pktq_prec *q; + void *p; + + ASSERT(prec >= 0 && prec < pq->num_prec); + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + if ((q->head = PKTLINK(p)) == NULL) + q->tail = NULL; + + q->len--; + + pq->len--; + + PKTSETLINK(p, NULL); + + return p; +} + +void * +pktq_pdeq_tail(struct pktq *pq, int prec) +{ + struct pktq_prec *q; + void *p, *prev; + + ASSERT(prec >= 0 && prec < pq->num_prec); + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + for (prev = NULL; p != q->tail; p = PKTLINK(p)) + prev = p; + + if (prev) + PKTSETLINK(prev, NULL); + else + q->head = NULL; + + q->tail = prev; + q->len--; + + pq->len--; + + return p; +} + +void +pktq_pflush(osl_t *osh, struct pktq *pq, int prec, bool dir) +{ + struct pktq_prec *q; + void *p; + + q = &pq->q[prec]; + p = q->head; + while (p) { + q->head = PKTLINK(p); + PKTSETLINK(p, NULL); + PKTFREE(osh, p, dir); + q->len--; + pq->len--; + p = q->head; + } + ASSERT(q->len == 0); + q->tail = NULL; +} + +bool +pktq_pdel(struct pktq *pq, void *pktbuf, int prec) +{ + struct pktq_prec *q; + void *p; + + ASSERT(prec >= 0 && prec < pq->num_prec); + + if (!pktbuf) + return FALSE; + + q = &pq->q[prec]; + + if (q->head == pktbuf) { + if ((q->head = PKTLINK(pktbuf)) == NULL) + q->tail = NULL; + } else { + for (p = q->head; p && PKTLINK(p) != pktbuf; p = PKTLINK(p)) + ; + if (p == NULL) + return FALSE; + + PKTSETLINK(p, PKTLINK(pktbuf)); + if (q->tail == pktbuf) + q->tail = p; + } + + q->len--; + pq->len--; + PKTSETLINK(pktbuf, NULL); + return TRUE; +} + +void +pktq_init(struct pktq *pq, int num_prec, int max_len) +{ + int prec; + + ASSERT(num_prec > 0 && num_prec <= PKTQ_MAX_PREC); + + /* pq is variable size; only zero out what's requested */ + bzero(pq, OFFSETOF(struct pktq, q) + (sizeof(struct pktq_prec) * num_prec)); + + pq->num_prec = (uint16)num_prec; + + pq->max = (uint16)max_len; + + for (prec = 0; prec < num_prec; prec++) + pq->q[prec].max = pq->max; +} + +void * +pktq_deq(struct pktq *pq, int *prec_out) +{ + struct pktq_prec *q; + void *p; + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + if ((q->head = PKTLINK(p)) == NULL) + q->tail = NULL; + + q->len--; + + pq->len--; + + if (prec_out) + *prec_out = prec; + + PKTSETLINK(p, NULL); + + return p; +} + +void * +pktq_deq_tail(struct pktq *pq, int *prec_out) +{ + struct pktq_prec *q; + void *p, *prev; + int prec; + + if (pq->len == 0) + return NULL; + + for (prec = 0; prec < pq->hi_prec; prec++) + if (pq->q[prec].head) + break; + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + for (prev = NULL; p != q->tail; p = PKTLINK(p)) + prev = p; + + if (prev) + PKTSETLINK(prev, NULL); + else + q->head = NULL; + + q->tail = prev; + q->len--; + + pq->len--; + + if (prec_out) + *prec_out = prec; + + PKTSETLINK(p, NULL); + + return p; +} + +void * +pktq_peek(struct pktq *pq, int *prec_out) +{ + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + if (prec_out) + *prec_out = prec; + + return (pq->q[prec].head); +} + +void * +pktq_peek_tail(struct pktq *pq, int *prec_out) +{ + int prec; + + if (pq->len == 0) + return NULL; + + for (prec = 0; prec < pq->hi_prec; prec++) + if (pq->q[prec].head) + break; + + if (prec_out) + *prec_out = prec; + + return (pq->q[prec].tail); +} + +void +pktq_flush(osl_t *osh, struct pktq *pq, bool dir) +{ + int prec; + for (prec = 0; prec < pq->num_prec; prec++) + pktq_pflush(osh, pq, prec, dir); + ASSERT(pq->len == 0); +} + +/* Return sum of lengths of a specific set of precedences */ +int +pktq_mlen(struct pktq *pq, uint prec_bmp) +{ + int prec, len; + + len = 0; + + for (prec = 0; prec <= pq->hi_prec; prec++) + if (prec_bmp & (1 << prec)) + len += pq->q[prec].len; + + return len; +} + +/* Priority dequeue from a specific set of precedences */ +void * +pktq_mdeq(struct pktq *pq, uint prec_bmp, int *prec_out) +{ + struct pktq_prec *q; + void *p; + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + while ((prec_bmp & (1 << prec)) == 0 || pq->q[prec].head == NULL) + if (prec-- == 0) + return NULL; + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + if ((q->head = PKTLINK(p)) == NULL) + q->tail = NULL; + + q->len--; + + if (prec_out) + *prec_out = prec; + + pq->len--; + + PKTSETLINK(p, NULL); + + return p; +} +#endif /* BCMDRIVER */ + + + +const unsigned char bcm_ctype[] = { + _BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C, /* 0-7 */ + _BCM_C, _BCM_C|_BCM_S, _BCM_C|_BCM_S, _BCM_C|_BCM_S, _BCM_C|_BCM_S, _BCM_C|_BCM_S, _BCM_C, + _BCM_C, /* 8-15 */ + _BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C, /* 16-23 */ + _BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C,_BCM_C, /* 24-31 */ + _BCM_S|_BCM_SP,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P, /* 32-39 */ + _BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P, /* 40-47 */ + _BCM_D,_BCM_D,_BCM_D,_BCM_D,_BCM_D,_BCM_D,_BCM_D,_BCM_D, /* 48-55 */ + _BCM_D,_BCM_D,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P, /* 56-63 */ + _BCM_P, _BCM_U|_BCM_X, _BCM_U|_BCM_X, _BCM_U|_BCM_X, _BCM_U|_BCM_X, _BCM_U|_BCM_X, + _BCM_U|_BCM_X, _BCM_U, /* 64-71 */ + _BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U, /* 72-79 */ + _BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U,_BCM_U, /* 80-87 */ + _BCM_U,_BCM_U,_BCM_U,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_P, /* 88-95 */ + _BCM_P, _BCM_L|_BCM_X, _BCM_L|_BCM_X, _BCM_L|_BCM_X, _BCM_L|_BCM_X, _BCM_L|_BCM_X, + _BCM_L|_BCM_X, _BCM_L, /* 96-103 */ + _BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L, /* 104-111 */ + _BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L,_BCM_L, /* 112-119 */ + _BCM_L,_BCM_L,_BCM_L,_BCM_P,_BCM_P,_BCM_P,_BCM_P,_BCM_C, /* 120-127 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128-143 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 144-159 */ + _BCM_S|_BCM_SP, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, /* 160-175 */ + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, /* 176-191 */ + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, /* 192-207 */ + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_P, _BCM_U, _BCM_U, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_L, /* 208-223 */ + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, /* 224-239 */ + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_P, _BCM_L, _BCM_L, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L /* 240-255 */ +}; + +ulong +bcm_strtoul(char *cp, char **endp, uint base) +{ + ulong result, last_result = 0, value; + bool minus; + + minus = FALSE; + + while (bcm_isspace(*cp)) + cp++; + + if (cp[0] == '+') + cp++; + else if (cp[0] == '-') { + minus = TRUE; + cp++; + } + + if (base == 0) { + if (cp[0] == '0') { + if ((cp[1] == 'x') || (cp[1] == 'X')) { + base = 16; + cp = &cp[2]; + } else { + base = 8; + cp = &cp[1]; + } + } else + base = 10; + } else if (base == 16 && (cp[0] == '0') && ((cp[1] == 'x') || (cp[1] == 'X'))) { + cp = &cp[2]; + } + + result = 0; + + while (bcm_isxdigit(*cp) && + (value = bcm_isdigit(*cp) ? *cp-'0' : bcm_toupper(*cp)-'A'+10) < base) { + result = result*base + value; + /* Detected overflow */ + if (result < last_result && !minus) + return (ulong)-1; + last_result = result; + cp++; + } + + if (minus) + result = (ulong)(-(long)result); + + if (endp) + *endp = (char *)cp; + + return (result); +} + +int +bcm_atoi(char *s) +{ + return (int)bcm_strtoul(s, NULL, 10); +} + +/* return pointer to location of substring 'needle' in 'haystack' */ +char* +bcmstrstr(char *haystack, char *needle) +{ + int len, nlen; + int i; + + if ((haystack == NULL) || (needle == NULL)) + return (haystack); + + nlen = strlen(needle); + len = strlen(haystack) - nlen + 1; + + for (i = 0; i < len; i++) + if (memcmp(needle, &haystack[i], nlen) == 0) + return (&haystack[i]); + return (NULL); +} + +char* +bcmstrcat(char *dest, const char *src) +{ + char *p; + + p = dest + strlen(dest); + + while ((*p++ = *src++) != '\0') + ; + + return (dest); +} + +char* +bcmstrncat(char *dest, const char *src, uint size) +{ + char *endp; + char *p; + + p = dest + strlen(dest); + endp = p + size; + + while (p != endp && (*p++ = *src++) != '\0') + ; + + return (dest); +} + + +/**************************************************************************** +* Function: bcmstrtok +* +* Purpose: +* Tokenizes a string. This function is conceptually similiar to ANSI C strtok(), +* but allows strToken() to be used by different strings or callers at the same +* time. Each call modifies '*string' by substituting a NULL character for the +* first delimiter that is encountered, and updates 'string' to point to the char +* after the delimiter. Leading delimiters are skipped. +* +* Parameters: +* string (mod) Ptr to string ptr, updated by token. +* delimiters (in) Set of delimiter characters. +* tokdelim (out) Character that delimits the returned token. (May +* be set to NULL if token delimiter is not required). +* +* Returns: Pointer to the next token found. NULL when no more tokens are found. +***************************************************************************** +*/ +char * +bcmstrtok(char **string, const char *delimiters, char *tokdelim) +{ + unsigned char *str; + unsigned long map[8]; + int count; + char *nextoken; + + if (tokdelim != NULL) { + /* Prime the token delimiter */ + *tokdelim = '\0'; + } + + /* Clear control map */ + for (count = 0; count < 8; count++) { + map[count] = 0; + } + + /* Set bits in delimiter table */ + do { + map[*delimiters >> 5] |= (1 << (*delimiters & 31)); + } + while (*delimiters++); + + str = (unsigned char*)*string; + + /* Find beginning of token (skip over leading delimiters). Note that + * there is no token iff this loop sets str to point to the terminal + * null (*str == '\0') + */ + while (((map[*str >> 5] & (1 << (*str & 31))) && *str) || (*str == ' ')) { + str++; + } + + nextoken = (char*)str; + + /* Find the end of the token. If it is not the end of the string, + * put a null there. + */ + for (; *str; str++) { + if (map[*str >> 5] & (1 << (*str & 31))) { + if (tokdelim != NULL) { + *tokdelim = *str; + } + + *str++ = '\0'; + break; + } + } + + *string = (char*)str; + + /* Determine if a token has been found. */ + if (nextoken == (char *) str) { + return NULL; + } + else { + return nextoken; + } +} + + +#define xToLower(C) \ + ((C >= 'A' && C <= 'Z') ? (char)((int)C - (int)'A' + (int)'a') : C) + + +/**************************************************************************** +* Function: bcmstricmp +* +* Purpose: Compare to strings case insensitively. +* +* Parameters: s1 (in) First string to compare. +* s2 (in) Second string to compare. +* +* Returns: Return 0 if the two strings are equal, -1 if t1 < t2 and 1 if +* t1 > t2, when ignoring case sensitivity. +***************************************************************************** +*/ +int +bcmstricmp(const char *s1, const char *s2) +{ + char dc, sc; + + while (*s2 && *s1) { + dc = xToLower(*s1); + sc = xToLower(*s2); + if (dc < sc) return -1; + if (dc > sc) return 1; + s1++; + s2++; + } + + if (*s1 && !*s2) return 1; + if (!*s1 && *s2) return -1; + return 0; +} + + +/**************************************************************************** +* Function: bcmstrnicmp +* +* Purpose: Compare to strings case insensitively, upto a max of 'cnt' +* characters. +* +* Parameters: s1 (in) First string to compare. +* s2 (in) Second string to compare. +* cnt (in) Max characters to compare. +* +* Returns: Return 0 if the two strings are equal, -1 if t1 < t2 and 1 if +* t1 > t2, when ignoring case sensitivity. +***************************************************************************** +*/ +int +bcmstrnicmp(const char* s1, const char* s2, int cnt) +{ + char dc, sc; + + while (*s2 && *s1 && cnt) { + dc = xToLower(*s1); + sc = xToLower(*s2); + if (dc < sc) return -1; + if (dc > sc) return 1; + s1++; + s2++; + cnt--; + } + + if (!cnt) return 0; + if (*s1 && !*s2) return 1; + if (!*s1 && *s2) return -1; + return 0; +} + +/* parse a xx:xx:xx:xx:xx:xx format ethernet address */ +int +bcm_ether_atoe(char *p, struct ether_addr *ea) +{ + int i = 0; + + for (;;) { + ea->octet[i++] = (char) bcm_strtoul(p, &p, 16); + if (!*p++ || i == 6) + break; + } + + return (i == 6); +} + + +#if defined(CONFIG_USBRNDIS_RETAIL) || defined(NDIS_MINIPORT_DRIVER) +/* registry routine buffer preparation utility functions: + * parameter order is like strncpy, but returns count + * of bytes copied. Minimum bytes copied is null char(1)/wchar(2) + */ +ulong +wchar2ascii(char *abuf, ushort *wbuf, ushort wbuflen, ulong abuflen) +{ + ulong copyct = 1; + ushort i; + + if (abuflen == 0) + return 0; + + /* wbuflen is in bytes */ + wbuflen /= sizeof(ushort); + + for (i = 0; i < wbuflen; ++i) { + if (--abuflen == 0) + break; + *abuf++ = (char) *wbuf++; + ++copyct; + } + *abuf = '\0'; + + return copyct; +} +#endif /* CONFIG_USBRNDIS_RETAIL || NDIS_MINIPORT_DRIVER */ + +char * +bcm_ether_ntoa(const struct ether_addr *ea, char *buf) +{ + static const char template[] = "%02x:%02x:%02x:%02x:%02x:%02x"; + snprintf(buf, 18, template, + ea->octet[0]&0xff, ea->octet[1]&0xff, ea->octet[2]&0xff, + ea->octet[3]&0xff, ea->octet[4]&0xff, ea->octet[5]&0xff); + return (buf); +} + +char * +bcm_ip_ntoa(struct ipv4_addr *ia, char *buf) +{ + snprintf(buf, 16, "%d.%d.%d.%d", + ia->addr[0], ia->addr[1], ia->addr[2], ia->addr[3]); + return (buf); +} + +#ifdef BCMDRIVER + +void +bcm_mdelay(uint ms) +{ + uint i; + + for (i = 0; i < ms; i++) { + OSL_DELAY(1000); + } +} + + + + + + +#if defined(DHD_DEBUG) +/* pretty hex print a pkt buffer chain */ +void +prpkt(const char *msg, osl_t *osh, void *p0) +{ + void *p; + + if (msg && (msg[0] != '\0')) + printf("%s:\n", msg); + + for (p = p0; p; p = PKTNEXT(osh, p)) + prhex(NULL, PKTDATA(osh, p), PKTLEN(osh, p)); +} +#endif + +/* Takes an Ethernet frame and sets out-of-bound PKTPRIO. + * Also updates the inplace vlan tag if requested. + * For debugging, it returns an indication of what it did. + */ +uint +pktsetprio(void *pkt, bool update_vtag) +{ + struct ether_header *eh; + struct ethervlan_header *evh; + uint8 *pktdata; + int priority = 0; + int rc = 0; + + pktdata = (uint8 *) PKTDATA(NULL, pkt); + ASSERT(ISALIGNED((uintptr)pktdata, sizeof(uint16))); + + eh = (struct ether_header *) pktdata; + + if (ntoh16(eh->ether_type) == ETHER_TYPE_8021Q) { + uint16 vlan_tag; + int vlan_prio, dscp_prio = 0; + + evh = (struct ethervlan_header *)eh; + + vlan_tag = ntoh16(evh->vlan_tag); + vlan_prio = (int) (vlan_tag >> VLAN_PRI_SHIFT) & VLAN_PRI_MASK; + + if (ntoh16(evh->ether_type) == ETHER_TYPE_IP) { + uint8 *ip_body = pktdata + sizeof(struct ethervlan_header); + uint8 tos_tc = IP_TOS(ip_body); + dscp_prio = (int)(tos_tc >> IPV4_TOS_PREC_SHIFT); + } + + /* DSCP priority gets precedence over 802.1P (vlan tag) */ + if (dscp_prio != 0) { + priority = dscp_prio; + rc |= PKTPRIO_VDSCP; + } else { + priority = vlan_prio; + rc |= PKTPRIO_VLAN; + } + /* + * If the DSCP priority is not the same as the VLAN priority, + * then overwrite the priority field in the vlan tag, with the + * DSCP priority value. This is required for Linux APs because + * the VLAN driver on Linux, overwrites the skb->priority field + * with the priority value in the vlan tag + */ + if (update_vtag && (priority != vlan_prio)) { + vlan_tag &= ~(VLAN_PRI_MASK << VLAN_PRI_SHIFT); + vlan_tag |= (uint16)priority << VLAN_PRI_SHIFT; + evh->vlan_tag = hton16(vlan_tag); + rc |= PKTPRIO_UPD; + } + } else if (ntoh16(eh->ether_type) == ETHER_TYPE_IP) { + uint8 *ip_body = pktdata + sizeof(struct ether_header); + uint8 tos_tc = IP_TOS(ip_body); + priority = (int)(tos_tc >> IPV4_TOS_PREC_SHIFT); + rc |= PKTPRIO_DSCP; + } + + ASSERT(priority >= 0 && priority <= MAXPRIO); + PKTSETPRIO(pkt, priority); + return (rc | priority); +} + +static char bcm_undeferrstr[BCME_STRLEN]; + +static const char *bcmerrorstrtable[] = BCMERRSTRINGTABLE; + +/* Convert the error codes into related error strings */ +const char * +bcmerrorstr(int bcmerror) +{ + /* check if someone added a bcmerror code but forgot to add errorstring */ + ASSERT(ABS(BCME_LAST) == (ARRAYSIZE(bcmerrorstrtable) - 1)); + + if (bcmerror > 0 || bcmerror < BCME_LAST) { + snprintf(bcm_undeferrstr, BCME_STRLEN, "Undefined error %d", bcmerror); + return bcm_undeferrstr; + } + + ASSERT(strlen(bcmerrorstrtable[-bcmerror]) < BCME_STRLEN); + + return bcmerrorstrtable[-bcmerror]; +} + + + +/* iovar table lookup */ +const bcm_iovar_t* +bcm_iovar_lookup(const bcm_iovar_t *table, const char *name) +{ + const bcm_iovar_t *vi; + const char *lookup_name; + + /* skip any ':' delimited option prefixes */ + lookup_name = strrchr(name, ':'); + if (lookup_name != NULL) + lookup_name++; + else + lookup_name = name; + + ASSERT(table != NULL); + + for (vi = table; vi->name; vi++) { + if (!strcmp(vi->name, lookup_name)) + return vi; + } + /* ran to end of table */ + + return NULL; /* var name not found */ +} + +int +bcm_iovar_lencheck(const bcm_iovar_t *vi, void *arg, int len, bool set) +{ + int bcmerror = 0; + + /* length check on io buf */ + switch (vi->type) { + case IOVT_BOOL: + case IOVT_INT8: + case IOVT_INT16: + case IOVT_INT32: + case IOVT_UINT8: + case IOVT_UINT16: + case IOVT_UINT32: + /* all integers are int32 sized args at the ioctl interface */ + if (len < (int)sizeof(int)) { + bcmerror = BCME_BUFTOOSHORT; + } + break; + + case IOVT_BUFFER: + /* buffer must meet minimum length requirement */ + if (len < vi->minlen) { + bcmerror = BCME_BUFTOOSHORT; + } + break; + + case IOVT_VOID: + if (!set) { + /* Cannot return nil... */ + bcmerror = BCME_UNSUPPORTED; + } else if (len) { + /* Set is an action w/o parameters */ + bcmerror = BCME_BUFTOOLONG; + } + break; + + default: + /* unknown type for length check in iovar info */ + ASSERT(0); + bcmerror = BCME_UNSUPPORTED; + } + + return bcmerror; +} + +#endif /* BCMDRIVER */ + +/******************************************************************************* + * crc8 + * + * Computes a crc8 over the input data using the polynomial: + * + * x^8 + x^7 +x^6 + x^4 + x^2 + 1 + * + * The caller provides the initial value (either CRC8_INIT_VALUE + * or the previous returned value) to allow for processing of + * discontiguous blocks of data. When generating the CRC the + * caller is responsible for complementing the final return value + * and inserting it into the byte stream. When checking, a final + * return value of CRC8_GOOD_VALUE indicates a valid CRC. + * + * Reference: Dallas Semiconductor Application Note 27 + * Williams, Ross N., "A Painless Guide to CRC Error Detection Algorithms", + * ver 3, Aug 1993, ross@guest.adelaide.edu.au, Rocksoft Pty Ltd., + * ftp://ftp.rocksoft.com/clients/rocksoft/papers/crc_v3.txt + * + * **************************************************************************** + */ + +STATIC const uint8 crc8_table[256] = { + 0x00, 0xF7, 0xB9, 0x4E, 0x25, 0xD2, 0x9C, 0x6B, + 0x4A, 0xBD, 0xF3, 0x04, 0x6F, 0x98, 0xD6, 0x21, + 0x94, 0x63, 0x2D, 0xDA, 0xB1, 0x46, 0x08, 0xFF, + 0xDE, 0x29, 0x67, 0x90, 0xFB, 0x0C, 0x42, 0xB5, + 0x7F, 0x88, 0xC6, 0x31, 0x5A, 0xAD, 0xE3, 0x14, + 0x35, 0xC2, 0x8C, 0x7B, 0x10, 0xE7, 0xA9, 0x5E, + 0xEB, 0x1C, 0x52, 0xA5, 0xCE, 0x39, 0x77, 0x80, + 0xA1, 0x56, 0x18, 0xEF, 0x84, 0x73, 0x3D, 0xCA, + 0xFE, 0x09, 0x47, 0xB0, 0xDB, 0x2C, 0x62, 0x95, + 0xB4, 0x43, 0x0D, 0xFA, 0x91, 0x66, 0x28, 0xDF, + 0x6A, 0x9D, 0xD3, 0x24, 0x4F, 0xB8, 0xF6, 0x01, + 0x20, 0xD7, 0x99, 0x6E, 0x05, 0xF2, 0xBC, 0x4B, + 0x81, 0x76, 0x38, 0xCF, 0xA4, 0x53, 0x1D, 0xEA, + 0xCB, 0x3C, 0x72, 0x85, 0xEE, 0x19, 0x57, 0xA0, + 0x15, 0xE2, 0xAC, 0x5B, 0x30, 0xC7, 0x89, 0x7E, + 0x5F, 0xA8, 0xE6, 0x11, 0x7A, 0x8D, 0xC3, 0x34, + 0xAB, 0x5C, 0x12, 0xE5, 0x8E, 0x79, 0x37, 0xC0, + 0xE1, 0x16, 0x58, 0xAF, 0xC4, 0x33, 0x7D, 0x8A, + 0x3F, 0xC8, 0x86, 0x71, 0x1A, 0xED, 0xA3, 0x54, + 0x75, 0x82, 0xCC, 0x3B, 0x50, 0xA7, 0xE9, 0x1E, + 0xD4, 0x23, 0x6D, 0x9A, 0xF1, 0x06, 0x48, 0xBF, + 0x9E, 0x69, 0x27, 0xD0, 0xBB, 0x4C, 0x02, 0xF5, + 0x40, 0xB7, 0xF9, 0x0E, 0x65, 0x92, 0xDC, 0x2B, + 0x0A, 0xFD, 0xB3, 0x44, 0x2F, 0xD8, 0x96, 0x61, + 0x55, 0xA2, 0xEC, 0x1B, 0x70, 0x87, 0xC9, 0x3E, + 0x1F, 0xE8, 0xA6, 0x51, 0x3A, 0xCD, 0x83, 0x74, + 0xC1, 0x36, 0x78, 0x8F, 0xE4, 0x13, 0x5D, 0xAA, + 0x8B, 0x7C, 0x32, 0xC5, 0xAE, 0x59, 0x17, 0xE0, + 0x2A, 0xDD, 0x93, 0x64, 0x0F, 0xF8, 0xB6, 0x41, + 0x60, 0x97, 0xD9, 0x2E, 0x45, 0xB2, 0xFC, 0x0B, + 0xBE, 0x49, 0x07, 0xF0, 0x9B, 0x6C, 0x22, 0xD5, + 0xF4, 0x03, 0x4D, 0xBA, 0xD1, 0x26, 0x68, 0x9F +}; + +#define CRC_INNER_LOOP(n, c, x) \ + (c) = ((c) >> 8) ^ crc##n##_table[((c) ^ (x)) & 0xff] + +uint8 +hndcrc8( + uint8 *pdata, /* pointer to array of data to process */ + uint nbytes, /* number of input data bytes to process */ + uint8 crc /* either CRC8_INIT_VALUE or previous return value */ +) +{ + /* hard code the crc loop instead of using CRC_INNER_LOOP macro + * to avoid the undefined and unnecessary (uint8 >> 8) operation. + */ + while (nbytes-- > 0) + crc = crc8_table[(crc ^ *pdata++) & 0xff]; + + return crc; +} + +/******************************************************************************* + * crc16 + * + * Computes a crc16 over the input data using the polynomial: + * + * x^16 + x^12 +x^5 + 1 + * + * The caller provides the initial value (either CRC16_INIT_VALUE + * or the previous returned value) to allow for processing of + * discontiguous blocks of data. When generating the CRC the + * caller is responsible for complementing the final return value + * and inserting it into the byte stream. When checking, a final + * return value of CRC16_GOOD_VALUE indicates a valid CRC. + * + * Reference: Dallas Semiconductor Application Note 27 + * Williams, Ross N., "A Painless Guide to CRC Error Detection Algorithms", + * ver 3, Aug 1993, ross@guest.adelaide.edu.au, Rocksoft Pty Ltd., + * ftp://ftp.rocksoft.com/clients/rocksoft/papers/crc_v3.txt + * + * **************************************************************************** + */ + +static const uint16 crc16_table[256] = { + 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, + 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, + 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, + 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, + 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, + 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, + 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, + 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974, + 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB, + 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, + 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, + 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, + 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, + 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1, + 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, + 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, + 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, + 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, + 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, + 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, + 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, + 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, + 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134, + 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, + 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, + 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, + 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, + 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, + 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, + 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, + 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, + 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78 +}; + +uint16 +hndcrc16( + uint8 *pdata, /* pointer to array of data to process */ + uint nbytes, /* number of input data bytes to process */ + uint16 crc /* either CRC16_INIT_VALUE or previous return value */ +) +{ + while (nbytes-- > 0) + CRC_INNER_LOOP(16, crc, *pdata++); + return crc; +} + +STATIC const uint32 crc32_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; + +uint32 +hndcrc32( + uint8 *pdata, /* pointer to array of data to process */ + uint nbytes, /* number of input data bytes to process */ + uint32 crc /* either CRC32_INIT_VALUE or previous return value */ +) +{ + uint8 *pend; +#ifdef __mips__ + uint8 tmp[4]; + ulong *tptr = (ulong *)tmp; + + /* in case the beginning of the buffer isn't aligned */ + pend = (uint8 *)((uint)(pdata + 3) & 0xfffffffc); + nbytes -= (pend - pdata); + while (pdata < pend) + CRC_INNER_LOOP(32, crc, *pdata++); + + /* handle bulk of data as 32-bit words */ + pend = pdata + (nbytes & 0xfffffffc); + while (pdata < pend) { + *tptr = *(ulong *)pdata; + pdata += sizeof(ulong *); + CRC_INNER_LOOP(32, crc, tmp[0]); + CRC_INNER_LOOP(32, crc, tmp[1]); + CRC_INNER_LOOP(32, crc, tmp[2]); + CRC_INNER_LOOP(32, crc, tmp[3]); + } + + /* 1-3 bytes at end of buffer */ + pend = pdata + (nbytes & 0x03); + while (pdata < pend) + CRC_INNER_LOOP(32, crc, *pdata++); +#else + pend = pdata + nbytes; + while (pdata < pend) + CRC_INNER_LOOP(32, crc, *pdata++); +#endif /* __mips__ */ + + return crc; +} + +#ifdef notdef +#define CLEN 1499 /* CRC Length */ +#define CBUFSIZ (CLEN+4) +#define CNBUFS 5 /* # of bufs */ + +void testcrc32(void) +{ + uint j, k, l; + uint8 *buf; + uint len[CNBUFS]; + uint32 crcr; + uint32 crc32tv[CNBUFS] = + {0xd2cb1faa, 0xd385c8fa, 0xf5b4f3f3, 0x55789e20, 0x00343110}; + + ASSERT((buf = MALLOC(CBUFSIZ*CNBUFS)) != NULL); + + /* step through all possible alignments */ + for (l = 0; l <= 4; l++) { + for (j = 0; j < CNBUFS; j++) { + len[j] = CLEN; + for (k = 0; k < len[j]; k++) + *(buf + j*CBUFSIZ + (k+l)) = (j+k) & 0xff; + } + + for (j = 0; j < CNBUFS; j++) { + crcr = crc32(buf + j*CBUFSIZ + l, len[j], CRC32_INIT_VALUE); + ASSERT(crcr == crc32tv[j]); + } + } + + MFREE(buf, CBUFSIZ*CNBUFS); + return; +} +#endif /* notdef */ + +/* + * Advance from the current 1-byte tag/1-byte length/variable-length value + * triple, to the next, returning a pointer to the next. + * If the current or next TLV is invalid (does not fit in given buffer length), + * NULL is returned. + * *buflen is not modified if the TLV elt parameter is invalid, or is decremented + * by the TLV parameter's length if it is valid. + */ +bcm_tlv_t * +bcm_next_tlv(bcm_tlv_t *elt, int *buflen) +{ + int len; + + /* validate current elt */ + if (!bcm_valid_tlv(elt, *buflen)) + return NULL; + + /* advance to next elt */ + len = elt->len; + elt = (bcm_tlv_t*)(elt->data + len); + *buflen -= (2 + len); + + /* validate next elt */ + if (!bcm_valid_tlv(elt, *buflen)) + return NULL; + + return elt; +} + +/* + * Traverse a string of 1-byte tag/1-byte length/variable-length value + * triples, returning a pointer to the substring whose first element + * matches tag + */ +bcm_tlv_t * +bcm_parse_tlvs(void *buf, int buflen, uint key) +{ + bcm_tlv_t *elt; + int totlen; + + elt = (bcm_tlv_t*)buf; + totlen = buflen; + + /* find tagged parameter */ + while (totlen >= 2) { + int len = elt->len; + + /* validate remaining totlen */ + if ((elt->id == key) && (totlen >= (len + 2))) + return (elt); + + elt = (bcm_tlv_t*)((uint8*)elt + (len + 2)); + totlen -= (len + 2); + } + + return NULL; +} + +/* + * Traverse a string of 1-byte tag/1-byte length/variable-length value + * triples, returning a pointer to the substring whose first element + * matches tag. Stop parsing when we see an element whose ID is greater + * than the target key. + */ +bcm_tlv_t * +bcm_parse_ordered_tlvs(void *buf, int buflen, uint key) +{ + bcm_tlv_t *elt; + int totlen; + + elt = (bcm_tlv_t*)buf; + totlen = buflen; + + /* find tagged parameter */ + while (totlen >= 2) { + uint id = elt->id; + int len = elt->len; + + /* Punt if we start seeing IDs > than target key */ + if (id > key) + return (NULL); + + /* validate remaining totlen */ + if ((id == key) && (totlen >= (len + 2))) + return (elt); + + elt = (bcm_tlv_t*)((uint8*)elt + (len + 2)); + totlen -= (len + 2); + } + return NULL; +} + +#if defined(WLMSG_PRHDRS) || defined(WLMSG_PRPKT) || defined(WLMSG_ASSOC) || \ + defined(DHD_DEBUG) +int +bcm_format_flags(const bcm_bit_desc_t *bd, uint32 flags, char* buf, int len) +{ + int i; + char* p = buf; + char hexstr[16]; + int slen = 0; + uint32 bit; + const char* name; + + if (len < 2 || !buf) + return 0; + + buf[0] = '\0'; + len -= 1; + + for (i = 0; flags != 0; i++) { + bit = bd[i].bit; + name = bd[i].name; + if (bit == 0 && flags) { + /* print any unnamed bits */ + sprintf(hexstr, "0x%X", flags); + name = hexstr; + flags = 0; /* exit loop */ + } else if ((flags & bit) == 0) + continue; + slen += strlen(name); + if (len < slen) + break; + if (p != buf) p += sprintf(p, " "); /* btwn flag space */ + strcat(p, name); + p += strlen(name); + flags &= ~bit; + len -= slen; + slen = 1; /* account for btwn flag space */ + } + + /* indicate the str was too short */ + if (flags != 0) { + if (len == 0) + p--; /* overwrite last char */ + p += sprintf(p, ">"); + } + + return (int)(p - buf); +} + +/* print bytes formatted as hex to a string. return the resulting string length */ +int +bcm_format_hex(char *str, const void *bytes, int len) +{ + int i; + char *p = str; + const uint8 *src = (const uint8*)bytes; + + for (i = 0; i < len; i++) { + p += sprintf(p, "%02X", *src); + src++; + } + return (int)(p - str); +} + +/* pretty hex print a contiguous buffer */ +void +prhex(const char *msg, uchar *buf, uint nbytes) +{ + char line[128], *p; + uint i; + + if (msg && (msg[0] != '\0')) + printf("%s:\n", msg); + + p = line; + for (i = 0; i < nbytes; i++) { + if (i % 16 == 0) { + p += sprintf(p, " %04d: ", i); /* line prefix */ + } + p += sprintf(p, "%02x ", buf[i]); + if (i % 16 == 15) { + printf("%s\n", line); /* flush line */ + p = line; + } + } + + /* flush last partial line */ + if (p != line) + printf("%s\n", line); +} +#endif + + +/* Produce a human-readable string for boardrev */ +char * +bcm_brev_str(uint32 brev, char *buf) +{ + if (brev < 0x100) + snprintf(buf, 8, "%d.%d", (brev & 0xf0) >> 4, brev & 0xf); + else + snprintf(buf, 8, "%c%03x", ((brev & 0xf000) == 0x1000) ? 'P' : 'A', brev & 0xfff); + + return (buf); +} + +#define BUFSIZE_TODUMP_ATONCE 512 /* Buffer size */ + +/* dump large strings to console */ +void +printbig(char *buf) +{ + uint len, max_len; + char c; + + len = strlen(buf); + + max_len = BUFSIZE_TODUMP_ATONCE; + + while (len > max_len) { + c = buf[max_len]; + buf[max_len] = '\0'; + printf("%s", buf); + buf[max_len] = c; + + buf += max_len; + len -= max_len; + } + /* print the remaining string */ + printf("%s\n", buf); + return; +} + +/* routine to dump fields in a fileddesc structure */ +uint +bcmdumpfields(bcmutl_rdreg_rtn read_rtn, void *arg0, uint arg1, struct fielddesc *fielddesc_array, + char *buf, uint32 bufsize) +{ + uint filled_len; + int len; + struct fielddesc *cur_ptr; + + filled_len = 0; + cur_ptr = fielddesc_array; + + while (bufsize > 1) { + if (cur_ptr->nameandfmt == NULL) + break; + len = snprintf(buf, bufsize, cur_ptr->nameandfmt, + read_rtn(arg0, arg1, cur_ptr->offset)); + /* check for snprintf overflow or error */ + if (len < 0 || (uint32)len >= bufsize) + len = bufsize - 1; + buf += len; + bufsize -= len; + filled_len += len; + cur_ptr++; + } + return filled_len; +} + +uint +bcm_mkiovar(char *name, char *data, uint datalen, char *buf, uint buflen) +{ + uint len; + + len = strlen(name) + 1; + + if ((len + datalen) > buflen) + return 0; + + strncpy(buf, name, buflen); + + /* append data onto the end of the name string */ + memcpy(&buf[len], data, datalen); + len += datalen; + + return len; +} + +/* Quarter dBm units to mW + * Table starts at QDBM_OFFSET, so the first entry is mW for qdBm=153 + * Table is offset so the last entry is largest mW value that fits in + * a uint16. + */ + +#define QDBM_OFFSET 153 /* Offset for first entry */ +#define QDBM_TABLE_LEN 40 /* Table size */ + +/* Smallest mW value that will round up to the first table entry, QDBM_OFFSET. + * Value is ( mW(QDBM_OFFSET - 1) + mW(QDBM_OFFSET) ) / 2 + */ +#define QDBM_TABLE_LOW_BOUND 6493 /* Low bound */ + +/* Largest mW value that will round down to the last table entry, + * QDBM_OFFSET + QDBM_TABLE_LEN-1. + * Value is ( mW(QDBM_OFFSET + QDBM_TABLE_LEN - 1) + mW(QDBM_OFFSET + QDBM_TABLE_LEN) ) / 2. + */ +#define QDBM_TABLE_HIGH_BOUND 64938 /* High bound */ + +static const uint16 nqdBm_to_mW_map[QDBM_TABLE_LEN] = { +/* qdBm: +0 +1 +2 +3 +4 +5 +6 +7 */ +/* 153: */ 6683, 7079, 7499, 7943, 8414, 8913, 9441, 10000, +/* 161: */ 10593, 11220, 11885, 12589, 13335, 14125, 14962, 15849, +/* 169: */ 16788, 17783, 18836, 19953, 21135, 22387, 23714, 25119, +/* 177: */ 26607, 28184, 29854, 31623, 33497, 35481, 37584, 39811, +/* 185: */ 42170, 44668, 47315, 50119, 53088, 56234, 59566, 63096 +}; + +uint16 +bcm_qdbm_to_mw(uint8 qdbm) +{ + uint factor = 1; + int idx = qdbm - QDBM_OFFSET; + + if (idx >= QDBM_TABLE_LEN) { + /* clamp to max uint16 mW value */ + return 0xFFFF; + } + + /* scale the qdBm index up to the range of the table 0-40 + * where an offset of 40 qdBm equals a factor of 10 mW. + */ + while (idx < 0) { + idx += 40; + factor *= 10; + } + + /* return the mW value scaled down to the correct factor of 10, + * adding in factor/2 to get proper rounding. + */ + return ((nqdBm_to_mW_map[idx] + factor/2) / factor); +} + +uint8 +bcm_mw_to_qdbm(uint16 mw) +{ + uint8 qdbm; + int offset; + uint mw_uint = mw; + uint boundary; + + /* handle boundary case */ + if (mw_uint <= 1) + return 0; + + offset = QDBM_OFFSET; + + /* move mw into the range of the table */ + while (mw_uint < QDBM_TABLE_LOW_BOUND) { + mw_uint *= 10; + offset -= 40; + } + + for (qdbm = 0; qdbm < QDBM_TABLE_LEN-1; qdbm++) { + boundary = nqdBm_to_mW_map[qdbm] + (nqdBm_to_mW_map[qdbm+1] - + nqdBm_to_mW_map[qdbm])/2; + if (mw_uint < boundary) break; + } + + qdbm += (uint8)offset; + + return (qdbm); +} + + +uint +bcm_bitcount(uint8 *bitmap, uint length) +{ + uint bitcount = 0, i; + uint8 tmp; + for (i = 0; i < length; i++) { + tmp = bitmap[i]; + while (tmp) { + bitcount++; + tmp &= (tmp - 1); + } + } + return bitcount; +} + +#ifdef BCMDRIVER + +/* Initialization of bcmstrbuf structure */ +void +bcm_binit(struct bcmstrbuf *b, char *buf, uint size) +{ + b->origsize = b->size = size; + b->origbuf = b->buf = buf; +} + +/* Buffer sprintf wrapper to guard against buffer overflow */ +int +bcm_bprintf(struct bcmstrbuf *b, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = vsnprintf(b->buf, b->size, fmt, ap); + + /* Non Ansi C99 compliant returns -1, + * Ansi compliant return r >= b->size, + * bcmstdlib returns 0, handle all + */ + if ((r == -1) || (r >= (int)b->size) || (r == 0)) { + b->size = 0; + } else { + b->size -= r; + b->buf += r; + } + + va_end(ap); + + return r; +} + +void +bcm_inc_bytes(uchar *num, int num_bytes, uint8 amount) +{ + int i; + + for (i = 0; i < num_bytes; i++) { + num[i] += amount; + if (num[i] >= amount) + break; + amount = 1; + } +} + +int +bcm_cmp_bytes(uchar *arg1, uchar *arg2, uint8 nbytes) +{ + int i; + + for (i = nbytes - 1; i >= 0; i--) { + if (arg1[i] != arg2[i]) + return (arg1[i] - arg2[i]); + } + return 0; +} + +void +bcm_print_bytes(char *name, const uchar *data, int len) +{ + int i; + int per_line = 0; + + printf("%s: %d \n", name ? name : "", len); + for (i = 0; i < len; i++) { + printf("%02x ", *data++); + per_line++; + if (per_line == 16) { + per_line = 0; + printf("\n"); + } + } + printf("\n"); +} + +/* + * buffer length needed for wlc_format_ssid + * 32 SSID chars, max of 4 chars for each SSID char "\xFF", plus NULL. + */ + +#if defined(WLTINYDUMP) || defined(WLMSG_INFORM) || defined(WLMSG_ASSOC) || \ + defined(WLMSG_PRPKT) || defined(WLMSG_WSEC) +int +bcm_format_ssid(char* buf, const uchar ssid[], uint ssid_len) +{ + uint i, c; + char *p = buf; + char *endp = buf + SSID_FMT_BUF_LEN; + + if (ssid_len > DOT11_MAX_SSID_LEN) ssid_len = DOT11_MAX_SSID_LEN; + + for (i = 0; i < ssid_len; i++) { + c = (uint)ssid[i]; + if (c == '\\') { + *p++ = '\\'; + *p++ = '\\'; + } else if (bcm_isprint((uchar)c)) { + *p++ = (char)c; + } else { + p += snprintf(p, (endp - p), "\\x%02X", c); + } + } + *p = '\0'; + ASSERT(p < endp); + + return (int)(p - buf); +} +#endif + +#endif /* BCMDRIVER */ diff --git a/drivers/net/wireless/bcm4329/bcmwifi.c b/drivers/net/wireless/bcm4329/bcmwifi.c new file mode 100644 index 0000000000000000000000000000000000000000..803acf842a29e279b00811647c57d1a48e8f44dc --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmwifi.c @@ -0,0 +1,199 @@ +/* + * Misc utility routines used by kernel or app-level. + * Contents are wifi-specific, used by any kernel or app-level + * software that might want wifi things as it grows. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: bcmwifi.c,v 1.18.24.2.4.1 2009/09/25 00:32:01 Exp $ + */ + + +#include + +#ifdef BCMDRIVER +#include +#include +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) +#define tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c)) +#else +#include +#include +#include +#endif +#include + +#if defined(WIN32) && (defined(BCMDLL) || defined(WLMDLL)) +#include +#endif + + + + + +char * +wf_chspec_ntoa(chanspec_t chspec, char *buf) +{ + const char *band, *bw, *sb; + uint channel; + + band = ""; + bw = ""; + sb = ""; + channel = CHSPEC_CHANNEL(chspec); + + if ((CHSPEC_IS2G(chspec) && channel > CH_MAX_2G_CHANNEL) || + (CHSPEC_IS5G(chspec) && channel <= CH_MAX_2G_CHANNEL)) + band = (CHSPEC_IS2G(chspec)) ? "b" : "a"; + if (CHSPEC_IS40(chspec)) { + if (CHSPEC_SB_UPPER(chspec)) { + sb = "u"; + channel += CH_10MHZ_APART; + } else { + sb = "l"; + channel -= CH_10MHZ_APART; + } + } else if (CHSPEC_IS10(chspec)) { + bw = "n"; + } + + + snprintf(buf, 6, "%d%s%s%s", channel, band, bw, sb); + return (buf); +} + + +chanspec_t +wf_chspec_aton(char *a) +{ + char *endp = NULL; + uint channel, band, bw, ctl_sb; + char c; + + channel = strtoul(a, &endp, 10); + + + if (endp == a) + return 0; + + if (channel > MAXCHANNEL) + return 0; + + band = ((channel <= CH_MAX_2G_CHANNEL) ? WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G); + bw = WL_CHANSPEC_BW_20; + ctl_sb = WL_CHANSPEC_CTL_SB_NONE; + + a = endp; + + c = tolower(a[0]); + if (c == '\0') + goto done; + + + if (c == 'a' || c == 'b') { + band = (c == 'a') ? WL_CHANSPEC_BAND_5G : WL_CHANSPEC_BAND_2G; + a++; + c = tolower(a[0]); + if (c == '\0') + goto done; + } + + + if (c == 'n') { + bw = WL_CHANSPEC_BW_10; + } else if (c == 'l') { + bw = WL_CHANSPEC_BW_40; + ctl_sb = WL_CHANSPEC_CTL_SB_LOWER; + + if (channel <= (MAXCHANNEL - CH_20MHZ_APART)) + channel += CH_10MHZ_APART; + else + return 0; + } else if (c == 'u') { + bw = WL_CHANSPEC_BW_40; + ctl_sb = WL_CHANSPEC_CTL_SB_UPPER; + + if (channel > CH_20MHZ_APART) + channel -= CH_10MHZ_APART; + else + return 0; + } else { + return 0; + } + +done: + return (channel | band | bw | ctl_sb); +} + + +int +wf_mhz2channel(uint freq, uint start_factor) +{ + int ch = -1; + uint base; + int offset; + + + if (start_factor == 0) { + if (freq >= 2400 && freq <= 2500) + start_factor = WF_CHAN_FACTOR_2_4_G; + else if (freq >= 5000 && freq <= 6000) + start_factor = WF_CHAN_FACTOR_5_G; + } + + if (freq == 2484 && start_factor == WF_CHAN_FACTOR_2_4_G) + return 14; + + base = start_factor / 2; + + + if ((freq < base) || (freq > base + 1000)) + return -1; + + offset = freq - base; + ch = offset / 5; + + + if (offset != (ch * 5)) + return -1; + + + if (start_factor == WF_CHAN_FACTOR_2_4_G && (ch < 1 || ch > 13)) + return -1; + + return ch; +} + + +int +wf_channel2mhz(uint ch, uint start_factor) +{ + int freq; + + if ((start_factor == WF_CHAN_FACTOR_2_4_G && (ch < 1 || ch > 14)) || + (ch <= 200)) + freq = -1; + if ((start_factor == WF_CHAN_FACTOR_2_4_G) && (ch == 14)) + freq = 2484; + else + freq = ch * 5 + start_factor / 2; + + return freq; +} diff --git a/drivers/net/wireless/bcm4329/dhd.h b/drivers/net/wireless/bcm4329/dhd.h new file mode 100644 index 0000000000000000000000000000000000000000..0b2e9c23e3caa53e8f5b0a44e7686206cf0e26e4 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd.h @@ -0,0 +1,465 @@ +/* + * Header file describing the internal (inter-module) DHD interfaces. + * + * Provides type definitions and function prototypes used to link the + * DHD OS, bus, and protocol modules. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd.h,v 1.32.4.7.2.4.14.49.4.9 2011/01/14 22:40:45 Exp $ + */ + +/**************** + * Common types * + */ + +#ifndef _dhd_h_ +#define _dhd_h_ + +#if defined(LINUX) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The kernel threading is sdio-specific */ +#else /* LINUX */ +#define ENOMEM 1 +#define EFAULT 2 +#define EINVAL 3 +#define EIO 4 +#define ETIMEDOUT 5 +#define ERESTARTSYS 6 +#endif /* LINUX */ + +#include + +#ifdef DHD_DEBUG +#ifndef DHD_DEBUG_TRAP +#define DHD_DEBUG_TRAP +#endif +#endif + +/* Forward decls */ +struct dhd_bus; +struct dhd_prot; +struct dhd_info; + +/* The level of bus communication with the dongle */ +enum dhd_bus_state { + DHD_BUS_DOWN, /* Not ready for frame transfers */ + DHD_BUS_LOAD, /* Download access only (CPU reset) */ + DHD_BUS_DATA /* Ready for frame transfers */ +}; + +enum dhd_bus_wake_state { + WAKE_LOCK_OFF, + WAKE_LOCK_PRIV, + WAKE_LOCK_DPC, + WAKE_LOCK_IOCTL, + WAKE_LOCK_DOWNLOAD, + WAKE_LOCK_TMOUT, + WAKE_LOCK_WATCHDOG, + WAKE_LOCK_LINK_DOWN_TMOUT, + WAKE_LOCK_PNO_FIND_TMOUT, + WAKE_LOCK_SOFTAP_SET, + WAKE_LOCK_SOFTAP_STOP, + WAKE_LOCK_SOFTAP_START, + WAKE_LOCK_SOFTAP_THREAD, + WAKE_LOCK_MAX +}; +enum dhd_prealloc_index { + DHD_PREALLOC_PROT = 0, + DHD_PREALLOC_RXBUF, + DHD_PREALLOC_DATABUF, + DHD_PREALLOC_OSL_BUF +}; +#ifdef DHD_USE_STATIC_BUF +extern void * dhd_os_prealloc(int section, unsigned long size); +#endif +/* Common structure for module and instance linkage */ +typedef struct dhd_pub { + /* Linkage ponters */ + osl_t *osh; /* OSL handle */ + struct dhd_bus *bus; /* Bus module handle */ + struct dhd_prot *prot; /* Protocol module handle */ + struct dhd_info *info; /* Info module handle */ + + /* Internal dhd items */ + bool up; /* Driver up/down (to OS) */ + bool txoff; /* Transmit flow-controlled */ + bool dongle_reset; /* TRUE = DEVRESET put dongle into reset */ + enum dhd_bus_state busstate; + uint hdrlen; /* Total DHD header length (proto + bus) */ + uint maxctl; /* Max size rxctl request from proto to bus */ + uint rxsz; /* Rx buffer size bus module should use */ + uint8 wme_dp; /* wme discard priority */ + + /* Dongle media info */ + bool iswl; /* Dongle-resident driver is wl */ + ulong drv_version; /* Version of dongle-resident driver */ + struct ether_addr mac; /* MAC address obtained from dongle */ + dngl_stats_t dstats; /* Stats for dongle-based data */ + + /* Additional stats for the bus level */ + ulong tx_packets; /* Data packets sent to dongle */ + ulong tx_multicast; /* Multicast data packets sent to dongle */ + ulong tx_errors; /* Errors in sending data to dongle */ + ulong tx_ctlpkts; /* Control packets sent to dongle */ + ulong tx_ctlerrs; /* Errors sending control frames to dongle */ + ulong rx_packets; /* Packets sent up the network interface */ + ulong rx_multicast; /* Multicast packets sent up the network interface */ + ulong rx_errors; /* Errors processing rx data packets */ + ulong rx_ctlpkts; /* Control frames processed from dongle */ + ulong rx_ctlerrs; /* Errors in processing rx control frames */ + ulong rx_dropped; /* Packets dropped locally (no memory) */ + ulong rx_flushed; /* Packets flushed due to unscheduled sendup thread */ + ulong wd_dpc_sched; /* Number of times dhd dpc scheduled by watchdog timer */ + + ulong rx_readahead_cnt; /* Number of packets where header read-ahead was used. */ + ulong tx_realloc; /* Number of tx packets we had to realloc for headroom */ + ulong fc_packets; /* Number of flow control pkts recvd */ + + /* Last error return */ + int bcmerror; + uint tickcnt; + + /* Last error from dongle */ + int dongle_error; + + /* Suspend disable flag and "in suspend" flag */ + int suspend_disable_flag; /* "1" to disable all extra powersaving during suspend */ + int in_suspend; /* flag set to 1 when early suspend called */ + int hang_was_sent; /* flag that message was send at least once */ +#ifdef PNO_SUPPORT + int pno_enable; /* pno status : "1" is pno enable */ +#endif /* PNO_SUPPORT */ + int dtim_skip; /* dtim skip , default 0 means wake each dtim */ + + /* Pkt filter defination */ + char * pktfilter[100]; + int pktfilter_count; + + wl_country_t dhd_cspec; /* Current Locale info */ + char eventmask[WL_EVENTING_MASK_LEN]; + +} dhd_pub_t; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) + + #define DHD_PM_RESUME_WAIT_INIT(a) DECLARE_WAIT_QUEUE_HEAD(a); + #define _DHD_PM_RESUME_WAIT(a, b) do { \ + int retry = 0; \ + smp_mb(); \ + while (dhd_mmc_suspend && retry++ != b) { \ + wait_event_interruptible_timeout(a, FALSE, HZ/100); \ + } \ + } while (0) + #define DHD_PM_RESUME_WAIT(a) _DHD_PM_RESUME_WAIT(a, 200) + #define DHD_PM_RESUME_WAIT_FOREVER(a) _DHD_PM_RESUME_WAIT(a, ~0) + #define DHD_PM_RESUME_RETURN_ERROR(a) do { if (dhd_mmc_suspend) return a; } while (0) + #define DHD_PM_RESUME_RETURN do { if (dhd_mmc_suspend) return; } while (0) + + #define DHD_SPINWAIT_SLEEP_INIT(a) DECLARE_WAIT_QUEUE_HEAD(a); + #define SPINWAIT_SLEEP(a, exp, us) do { \ + uint countdown = (us) + 9999; \ + while ((exp) && (countdown >= 10000)) { \ + wait_event_interruptible_timeout(a, FALSE, HZ/100); \ + countdown -= 10000; \ + } \ + } while (0) + +#else + + #define DHD_PM_RESUME_WAIT_INIT(a) + #define DHD_PM_RESUME_WAIT(a) + #define DHD_PM_RESUME_WAIT_FOREVER(a) + #define DHD_PM_RESUME_RETURN_ERROR(a) + #define DHD_PM_RESUME_RETURN + + #define DHD_SPINWAIT_SLEEP_INIT(a) + #define SPINWAIT_SLEEP(a, exp, us) do { \ + uint countdown = (us) + 9; \ + while ((exp) && (countdown >= 10)) { \ + OSL_DELAY(10); \ + countdown -= 10; \ + } \ + } while (0) + +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */ + +#define DHD_IF_VIF 0x01 /* Virtual IF (Hidden from user) */ + +inline static void NETIF_ADDR_LOCK(struct net_device *dev) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)) + netif_addr_lock_bh(dev); +#endif +} + +inline static void NETIF_ADDR_UNLOCK(struct net_device *dev) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)) + netif_addr_unlock_bh(dev); +#endif +} + +/* Wakelock Functions */ +extern int dhd_os_wake_lock(dhd_pub_t *pub); +extern int dhd_os_wake_unlock(dhd_pub_t *pub); +extern int dhd_os_wake_lock_timeout(dhd_pub_t *pub); +extern int dhd_os_wake_lock_timeout_enable(dhd_pub_t *pub); + +extern void dhd_os_start_lock(dhd_pub_t *pub); +extern void dhd_os_start_unlock(dhd_pub_t *pub); +extern unsigned long dhd_os_spin_lock(dhd_pub_t *pub); +extern void dhd_os_spin_unlock(dhd_pub_t *pub, unsigned long flags); + +typedef struct dhd_if_event { + uint8 ifidx; + uint8 action; + uint8 flags; + uint8 bssidx; +} dhd_if_event_t; + +/* + * Exported from dhd OS modules (dhd_linux/dhd_ndis) + */ + +/* To allow osl_attach/detach calls from os-independent modules */ +osl_t *dhd_osl_attach(void *pdev, uint bustype); +void dhd_osl_detach(osl_t *osh); + +/* Indication from bus module regarding presence/insertion of dongle. + * Return dhd_pub_t pointer, used as handle to OS module in later calls. + * Returned structure should have bus and prot pointers filled in. + * bus_hdrlen specifies required headroom for bus module header. + */ +extern dhd_pub_t *dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen); +extern int dhd_net_attach(dhd_pub_t *dhdp, int idx); + +/* Indication from bus module regarding removal/absence of dongle */ +extern void dhd_detach(dhd_pub_t *dhdp); + +/* Indication from bus module to change flow-control state */ +extern void dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool on); + +extern bool dhd_prec_enq(dhd_pub_t *dhdp, struct pktq *q, void *pkt, int prec); + +/* Receive frame for delivery to OS. Callee disposes of rxp. */ +extern void dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *rxp, int numpkt); + +/* Return pointer to interface name */ +extern char *dhd_ifname(dhd_pub_t *dhdp, int idx); + +/* Request scheduling of the bus dpc */ +extern void dhd_sched_dpc(dhd_pub_t *dhdp); + +/* Notify tx completion */ +extern void dhd_txcomplete(dhd_pub_t *dhdp, void *txp, bool success); + +/* Query ioctl */ +extern int dhdcdc_query_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len); + +/* OS independent layer functions */ +extern int dhd_os_proto_block(dhd_pub_t * pub); +extern int dhd_os_proto_unblock(dhd_pub_t * pub); +extern int dhd_os_ioctl_resp_wait(dhd_pub_t * pub, uint * condition, bool * pending); +extern int dhd_os_ioctl_resp_wake(dhd_pub_t * pub); +extern unsigned int dhd_os_get_ioctl_resp_timeout(void); +extern void dhd_os_set_ioctl_resp_timeout(unsigned int timeout_msec); +extern void * dhd_os_open_image(char * filename); +extern int dhd_os_get_image_block(char * buf, int len, void * image); +extern void dhd_os_close_image(void * image); +extern void dhd_os_wd_timer(void *bus, uint wdtick); +extern void dhd_os_sdlock(dhd_pub_t * pub); +extern void dhd_os_sdunlock(dhd_pub_t * pub); +extern void dhd_os_sdlock_txq(dhd_pub_t * pub); +extern void dhd_os_sdunlock_txq(dhd_pub_t * pub); +extern void dhd_os_sdlock_rxq(dhd_pub_t * pub); +extern void dhd_os_sdunlock_rxq(dhd_pub_t * pub); +extern void dhd_os_sdlock_sndup_rxq(dhd_pub_t * pub); +extern void dhd_customer_gpio_wlan_ctrl(int onoff); +extern int dhd_custom_get_mac_address(unsigned char *buf); +extern void dhd_os_sdunlock_sndup_rxq(dhd_pub_t * pub); +extern void dhd_os_sdlock_eventq(dhd_pub_t * pub); +extern void dhd_os_sdunlock_eventq(dhd_pub_t * pub); +#ifdef DHD_DEBUG +extern int write_to_file(dhd_pub_t *dhd, uint8 *buf, int size); +#endif /* DHD_DEBUG */ +#if defined(OOB_INTR_ONLY) +extern int dhd_customer_oob_irq_map(unsigned long *irq_flags_ptr); +#endif /* defined(OOB_INTR_ONLY) */ +extern void dhd_os_sdtxlock(dhd_pub_t * pub); +extern void dhd_os_sdtxunlock(dhd_pub_t * pub); + +int setScheduler(struct task_struct *p, int policy, struct sched_param *param); + +typedef struct { + uint32 limit; /* Expiration time (usec) */ + uint32 increment; /* Current expiration increment (usec) */ + uint32 elapsed; /* Current elapsed time (usec) */ + uint32 tick; /* O/S tick time (usec) */ +} dhd_timeout_t; + +extern void dhd_timeout_start(dhd_timeout_t *tmo, uint usec); +extern int dhd_timeout_expired(dhd_timeout_t *tmo); + +extern int dhd_ifname2idx(struct dhd_info *dhd, char *name); +extern uint8 *dhd_bssidx2bssid(dhd_pub_t *dhd, int idx); +extern int wl_host_event(struct dhd_info *dhd, int *idx, void *pktdata, + wl_event_msg_t *, void **data_ptr); +extern void wl_event_to_host_order(wl_event_msg_t * evt); + +extern void dhd_common_init(void); + +extern int dhd_add_if(struct dhd_info *dhd, int ifidx, void *handle, + char *name, uint8 *mac_addr, uint32 flags, uint8 bssidx); +extern void dhd_del_if(struct dhd_info *dhd, int ifidx); + +extern void dhd_vif_add(struct dhd_info *dhd, int ifidx, char * name); +extern void dhd_vif_del(struct dhd_info *dhd, int ifidx); + +extern void dhd_event(struct dhd_info *dhd, char *evpkt, int evlen, int ifidx); +extern void dhd_vif_sendup(struct dhd_info *dhd, int ifidx, uchar *cp, int len); + + +/* Send packet to dongle via data channel */ +extern int dhd_sendpkt(dhd_pub_t *dhdp, int ifidx, void *pkt); + +/* Send event to host */ +extern void dhd_sendup_event(dhd_pub_t *dhdp, wl_event_msg_t *event, void *data); +extern int dhd_bus_devreset(dhd_pub_t *dhdp, uint8 flag); +extern uint dhd_bus_status(dhd_pub_t *dhdp); +extern int dhd_bus_start(dhd_pub_t *dhdp); + +extern void print_buf(void *pbuf, int len, int bytes_per_line); + + +typedef enum cust_gpio_modes { + WLAN_RESET_ON, + WLAN_RESET_OFF, + WLAN_POWER_ON, + WLAN_POWER_OFF +} cust_gpio_modes_t; + +extern int wl_iw_iscan_set_scan_broadcast_prep(struct net_device *dev, uint flag); +extern int wl_iw_send_priv_event(struct net_device *dev, char *flag); +extern int net_os_send_hang_message(struct net_device *dev); + +/* + * Insmod parameters for debug/test + */ + +/* Watchdog timer interval */ +extern uint dhd_watchdog_ms; + +#if defined(DHD_DEBUG) +/* Console output poll interval */ +extern uint dhd_console_ms; +#endif /* defined(DHD_DEBUG) */ + +/* Use interrupts */ +extern uint dhd_intr; + +/* Use polling */ +extern uint dhd_poll; + +/* ARP offload agent mode */ +extern uint dhd_arp_mode; + +/* ARP offload enable */ +extern uint dhd_arp_enable; + +/* Pkt filte enable control */ +extern uint dhd_pkt_filter_enable; + +/* Pkt filter init setup */ +extern uint dhd_pkt_filter_init; + +/* Pkt filter mode control */ +extern uint dhd_master_mode; + +/* Roaming mode control */ +extern uint dhd_roam; + +/* Roaming mode control */ +extern uint dhd_radio_up; + +/* Initial idletime ticks (may be -1 for immediate idle, 0 for no idle) */ +extern int dhd_idletime; +#define DHD_IDLETIME_TICKS 1 + +/* SDIO Drive Strength */ +extern uint dhd_sdiod_drive_strength; + +/* Override to force tx queueing all the time */ +extern uint dhd_force_tx_queueing; + +/* Default KEEP_ALIVE Period is 55 sec to prevent AP from sending Keep Alive probe frame */ +#define KEEP_ALIVE_PERIOD 55000 +#define NULL_PKT_STR "null_pkt" + +#ifdef SDTEST +/* Echo packet generator (SDIO), pkts/s */ +extern uint dhd_pktgen; + +/* Echo packet len (0 => sawtooth, max 1800) */ +extern uint dhd_pktgen_len; +#define MAX_PKTGEN_LEN 1800 +#endif + + +/* optionally set by a module_param_string() */ +#define MOD_PARAM_PATHLEN 2048 +extern char fw_path[MOD_PARAM_PATHLEN]; +extern char nv_path[MOD_PARAM_PATHLEN]; + +/* For supporting multiple interfaces */ +#define DHD_MAX_IFS 16 +#define DHD_DEL_IF -0xe +#define DHD_BAD_IF -0xf + + +extern void dhd_wait_for_event(dhd_pub_t *dhd, bool *lockvar); +extern void dhd_wait_event_wakeup(dhd_pub_t*dhd); + +/* dhd_commn arp offload wrapers */ +extern void dhd_arp_cleanup(dhd_pub_t *dhd); +int dhd_arp_get_arp_hostip_table(dhd_pub_t *dhd, void *buf, int buflen); +void dhd_arp_offload_add_ip(dhd_pub_t *dhd, u32 ipaddr); + +#define DHD_UNICAST_FILTER_NUM 0 +#define DHD_BROADCAST_FILTER_NUM 1 +#define DHD_MULTICAST4_FILTER_NUM 2 +#define DHD_MULTICAST6_FILTER_NUM 3 +extern int net_os_set_packet_filter(struct net_device *dev, int val); +extern int net_os_rxfilter_add_remove(struct net_device *dev, int val, int num); + +#endif /* _dhd_h_ */ diff --git a/drivers/net/wireless/bcm4329/dhd_bus.h b/drivers/net/wireless/bcm4329/dhd_bus.h new file mode 100644 index 0000000000000000000000000000000000000000..97af41b313d0f3a0e32aadf257fe1ec24dd0400e --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_bus.h @@ -0,0 +1,93 @@ +/* + * Header file describing the internal (inter-module) DHD interfaces. + * + * Provides type definitions and function prototypes used to link the + * DHD OS, bus, and protocol modules. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_bus.h,v 1.4.6.3.2.3.6.7 2010/08/13 01:35:24 Exp $ + */ + +#ifndef _dhd_bus_h_ +#define _dhd_bus_h_ + +/* + * Exported from dhd bus module (dhd_usb, dhd_sdio) + */ + +/* Indicate (dis)interest in finding dongles. */ +extern int dhd_bus_register(void); +extern void dhd_bus_unregister(void); + +/* Download firmware image and nvram image */ +extern bool dhd_bus_download_firmware(struct dhd_bus *bus, osl_t *osh, + char *fw_path, char *nv_path); + +/* Stop bus module: clear pending frames, disable data flow */ +extern void dhd_bus_stop(struct dhd_bus *bus, bool enforce_mutex); + +/* Initialize bus module: prepare for communication w/dongle */ +extern int dhd_bus_init(dhd_pub_t *dhdp, bool enforce_mutex); + +/* Send a data frame to the dongle. Callee disposes of txp. */ +extern int dhd_bus_txdata(struct dhd_bus *bus, void *txp); + +/* Send/receive a control message to/from the dongle. + * Expects caller to enforce a single outstanding transaction. + */ +extern int dhd_bus_txctl(struct dhd_bus *bus, uchar *msg, uint msglen); +extern int dhd_bus_rxctl(struct dhd_bus *bus, uchar *msg, uint msglen); + +/* Watchdog timer function */ +extern bool dhd_bus_watchdog(dhd_pub_t *dhd); + +#ifdef DHD_DEBUG +/* Device console input function */ +extern int dhd_bus_console_in(dhd_pub_t *dhd, uchar *msg, uint msglen); +#endif /* DHD_DEBUG */ + +/* Deferred processing for the bus, return TRUE requests reschedule */ +extern bool dhd_bus_dpc(struct dhd_bus *bus); +extern void dhd_bus_isr(bool * InterruptRecognized, bool * QueueMiniportHandleInterrupt, void *arg); + + +/* Check for and handle local prot-specific iovar commands */ +extern int dhd_bus_iovar_op(dhd_pub_t *dhdp, const char *name, + void *params, int plen, void *arg, int len, bool set); + +/* Add bus dump output to a buffer */ +extern void dhd_bus_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf); + +/* Clear any bus counters */ +extern void dhd_bus_clearcounts(dhd_pub_t *dhdp); + +/* return the dongle chipid */ +extern uint dhd_bus_chip(struct dhd_bus *bus); + +/* Set user-specified nvram parameters. */ +extern void dhd_bus_set_nvram_params(struct dhd_bus * bus, const char *nvram_params); + +extern void *dhd_bus_pub(struct dhd_bus *bus); +extern void *dhd_bus_txq(struct dhd_bus *bus); +extern uint dhd_bus_hdrlen(struct dhd_bus *bus); + +#endif /* _dhd_bus_h_ */ diff --git a/drivers/net/wireless/bcm4329/dhd_cdc.c b/drivers/net/wireless/bcm4329/dhd_cdc.c new file mode 100644 index 0000000000000000000000000000000000000000..4bec0b606dc9d2529ba87accabb1827dcf86f1a7 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_cdc.c @@ -0,0 +1,535 @@ +/* + * DHD Protocol Module for CDC and BDC. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_cdc.c,v 1.22.4.2.4.7.2.41 2010/06/23 19:58:18 Exp $ + * + * BDC is like CDC, except it includes a header for data packets to convey + * packet priority over the bus, and flags (e.g. to indicate checksum status + * for dongle offload). + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +extern int dhd_preinit_ioctls(dhd_pub_t *dhd); + +/* Packet alignment for most efficient SDIO (can change based on platform) */ +#ifndef DHD_SDALIGN +#define DHD_SDALIGN 32 +#endif +#if !ISPOWEROF2(DHD_SDALIGN) +#error DHD_SDALIGN is not a power of 2! +#endif + +#define RETRIES 2 /* # of retries to retrieve matching ioctl response */ +#define BUS_HEADER_LEN (16+DHD_SDALIGN) /* Must be atleast SDPCM_RESERVE + * defined in dhd_sdio.c (amount of header tha might be added) + * plus any space that might be needed for alignment padding. + */ +#define ROUND_UP_MARGIN 2048 /* Biggest SDIO block size possible for + * round off at the end of buffer + */ + +typedef struct dhd_prot { + uint16 reqid; + uint8 pending; + uint32 lastcmd; + uint8 bus_header[BUS_HEADER_LEN]; + cdc_ioctl_t msg; + unsigned char buf[WLC_IOCTL_MAXLEN + ROUND_UP_MARGIN]; +} dhd_prot_t; + +static int +dhdcdc_msg(dhd_pub_t *dhd) +{ + dhd_prot_t *prot = dhd->prot; + int len = ltoh32(prot->msg.len) + sizeof(cdc_ioctl_t); + int ret; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + dhd_os_wake_lock(dhd); + + /* NOTE : cdc->msg.len holds the desired length of the buffer to be + * returned. Only up to CDC_MAX_MSG_SIZE of this buffer area + * is actually sent to the dongle + */ + if (len > CDC_MAX_MSG_SIZE) + len = CDC_MAX_MSG_SIZE; + + /* Send request */ + ret = dhd_bus_txctl(dhd->bus, (uchar*)&prot->msg, len); + dhd_os_wake_unlock(dhd); + return ret; +} + +static int +dhdcdc_cmplt(dhd_pub_t *dhd, uint32 id, uint32 len) +{ + int ret; + dhd_prot_t *prot = dhd->prot; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + do { + ret = dhd_bus_rxctl(dhd->bus, (uchar*)&prot->msg, len+sizeof(cdc_ioctl_t)); + if (ret < 0) + break; + } while (CDC_IOC_ID(ltoh32(prot->msg.flags)) != id); + + return ret; +} + +int +dhdcdc_query_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len) +{ + dhd_prot_t *prot = dhd->prot; + cdc_ioctl_t *msg = &prot->msg; + void *info; + int ret = 0, retries = 0; + uint32 id, flags = 0; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + DHD_CTL(("%s: cmd %d len %d\n", __FUNCTION__, cmd, len)); + + + /* Respond "bcmerror" and "bcmerrorstr" with local cache */ + if (cmd == WLC_GET_VAR && buf) + { + if (!strcmp((char *)buf, "bcmerrorstr")) + { + strncpy((char *)buf, bcmerrorstr(dhd->dongle_error), BCME_STRLEN); + goto done; + } + else if (!strcmp((char *)buf, "bcmerror")) + { + *(int *)buf = dhd->dongle_error; + goto done; + } + } + + memset(msg, 0, sizeof(cdc_ioctl_t)); + + msg->cmd = htol32(cmd); + msg->len = htol32(len); + msg->flags = (++prot->reqid << CDCF_IOC_ID_SHIFT); + CDC_SET_IF_IDX(msg, ifidx); + msg->flags = htol32(msg->flags); + + if (buf) + memcpy(prot->buf, buf, len); + + if ((ret = dhdcdc_msg(dhd)) < 0) { + if (!dhd->hang_was_sent) + DHD_ERROR(("dhdcdc_query_ioctl: dhdcdc_msg failed w/status %d\n", ret)); + goto done; + } + +retry: + /* wait for interrupt and get first fragment */ + if ((ret = dhdcdc_cmplt(dhd, prot->reqid, len)) < 0) + goto done; + + flags = ltoh32(msg->flags); + id = (flags & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT; + + if ((id < prot->reqid) && (++retries < RETRIES)) + goto retry; + if (id != prot->reqid) { + DHD_ERROR(("%s: %s: unexpected request id %d (expected %d)\n", + dhd_ifname(dhd, ifidx), __FUNCTION__, id, prot->reqid)); + ret = -EINVAL; + goto done; + } + + /* Check info buffer */ + info = (void*)&msg[1]; + + /* Copy info buffer */ + if (buf) + { + if (ret < (int)len) + len = ret; + memcpy(buf, info, len); + } + + /* Check the ERROR flag */ + if (flags & CDCF_IOC_ERROR) + { + ret = ltoh32(msg->status); + /* Cache error from dongle */ + dhd->dongle_error = ret; + } + +done: + return ret; +} + +int +dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len) +{ + dhd_prot_t *prot = dhd->prot; + cdc_ioctl_t *msg = &prot->msg; + int ret = 0; + uint32 flags, id; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + DHD_CTL(("%s: cmd %d len %d\n", __FUNCTION__, cmd, len)); + + if (dhd->busstate == DHD_BUS_DOWN) { + DHD_ERROR(("%s : bus is down. we have nothing to do\n", __FUNCTION__)); + return -EIO; + } + + /* don't talk to the dongle if fw is about to be reloaded */ + if (dhd->hang_was_sent) { + DHD_ERROR(("%s: HANG was sent up earlier. Not talking to the chip\n", + __FUNCTION__)); + return -EIO; + } + + memset(msg, 0, sizeof(cdc_ioctl_t)); + + msg->cmd = htol32(cmd); + msg->len = htol32(len); + msg->flags = (++prot->reqid << CDCF_IOC_ID_SHIFT) | CDCF_IOC_SET; + CDC_SET_IF_IDX(msg, ifidx); + msg->flags = htol32(msg->flags); + + if (buf) + memcpy(prot->buf, buf, len); + + if ((ret = dhdcdc_msg(dhd)) < 0) + goto done; + + if ((ret = dhdcdc_cmplt(dhd, prot->reqid, len)) < 0) + goto done; + + flags = ltoh32(msg->flags); + id = (flags & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT; + + if (id != prot->reqid) { + DHD_ERROR(("%s: %s: unexpected request id %d (expected %d)\n", + dhd_ifname(dhd, ifidx), __FUNCTION__, id, prot->reqid)); + ret = -EINVAL; + goto done; + } + + /* Check the ERROR flag */ + if (flags & CDCF_IOC_ERROR) + { + ret = ltoh32(msg->status); + /* Cache error from dongle */ + dhd->dongle_error = ret; + } + +done: + return ret; +} + +extern int dhd_bus_interface(struct dhd_bus *bus, uint arg, void* arg2); +int +dhd_prot_ioctl(dhd_pub_t *dhd, int ifidx, wl_ioctl_t * ioc, void * buf, int len) +{ + dhd_prot_t *prot = dhd->prot; + int ret = -1; + + if ((dhd->busstate == DHD_BUS_DOWN) || dhd->hang_was_sent) { + DHD_ERROR(("%s : bus is down. we have nothing to do\n", __FUNCTION__)); + return ret; + } + dhd_os_proto_block(dhd); + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + ASSERT(len <= WLC_IOCTL_MAXLEN); + + if (len > WLC_IOCTL_MAXLEN) + goto done; + + if (prot->pending == TRUE) { + DHD_TRACE(("CDC packet is pending!!!! cmd=0x%x (%lu) lastcmd=0x%x (%lu)\n", + ioc->cmd, (unsigned long)ioc->cmd, prot->lastcmd, + (unsigned long)prot->lastcmd)); + if ((ioc->cmd == WLC_SET_VAR) || (ioc->cmd == WLC_GET_VAR)) { + DHD_TRACE(("iovar cmd=%s\n", (char*)buf)); + } + goto done; + } + + prot->pending = TRUE; + prot->lastcmd = ioc->cmd; + if (ioc->set) + ret = dhdcdc_set_ioctl(dhd, ifidx, ioc->cmd, buf, len); + else { + ret = dhdcdc_query_ioctl(dhd, ifidx, ioc->cmd, buf, len); + if (ret > 0) + ioc->used = ret - sizeof(cdc_ioctl_t); + } + + /* Too many programs assume ioctl() returns 0 on success */ + if (ret >= 0) + ret = 0; + else { + cdc_ioctl_t *msg = &prot->msg; + ioc->needed = ltoh32(msg->len); /* len == needed when set/query fails from dongle */ + } + + /* Intercept the wme_dp ioctl here */ + if ((!ret) && (ioc->cmd == WLC_SET_VAR) && (!strcmp(buf, "wme_dp"))) { + int slen, val = 0; + + slen = strlen("wme_dp") + 1; + if (len >= (int)(slen + sizeof(int))) + bcopy(((char *)buf + slen), &val, sizeof(int)); + dhd->wme_dp = (uint8) ltoh32(val); + } + + prot->pending = FALSE; + +done: + dhd_os_proto_unblock(dhd); + + return ret; +} + +int +dhd_prot_iovar_op(dhd_pub_t *dhdp, const char *name, + void *params, int plen, void *arg, int len, bool set) +{ + return BCME_UNSUPPORTED; +} + +void +dhd_prot_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf) +{ + bcm_bprintf(strbuf, "Protocol CDC: reqid %d\n", dhdp->prot->reqid); +} + + +void +dhd_prot_hdrpush(dhd_pub_t *dhd, int ifidx, void *pktbuf) +{ +#ifdef BDC + struct bdc_header *h; +#endif /* BDC */ + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + +#ifdef BDC + /* Push BDC header used to convey priority for buses that don't */ + + + PKTPUSH(dhd->osh, pktbuf, BDC_HEADER_LEN); + + h = (struct bdc_header *)PKTDATA(dhd->osh, pktbuf); + + h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT); + if (PKTSUMNEEDED(pktbuf)) + h->flags |= BDC_FLAG_SUM_NEEDED; + + + h->priority = (PKTPRIO(pktbuf) & BDC_PRIORITY_MASK); + h->flags2 = 0; + h->rssi = 0; +#endif /* BDC */ + BDC_SET_IF_IDX(h, ifidx); +} + + +bool +dhd_proto_fcinfo(dhd_pub_t *dhd, void *pktbuf, uint8 *fcbits) +{ +#ifdef BDC + struct bdc_header *h; + + if (PKTLEN(dhd->osh, pktbuf) < BDC_HEADER_LEN) { + DHD_ERROR(("%s: rx data too short (%d < %d)\n", + __FUNCTION__, PKTLEN(dhd->osh, pktbuf), BDC_HEADER_LEN)); + return BCME_ERROR; + } + + h = (struct bdc_header *)PKTDATA(dhd->osh, pktbuf); + + *fcbits = h->priority >> BDC_PRIORITY_FC_SHIFT; + if ((h->flags2 & BDC_FLAG2_FC_FLAG) == BDC_FLAG2_FC_FLAG) + return TRUE; +#endif + return FALSE; +} + + +int +dhd_prot_hdrpull(dhd_pub_t *dhd, int *ifidx, void *pktbuf) +{ +#ifdef BDC + struct bdc_header *h; +#endif + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + +#ifdef BDC + /* Pop BDC header used to convey priority for buses that don't */ + + if (PKTLEN(dhd->osh, pktbuf) < BDC_HEADER_LEN) { + DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__, + PKTLEN(dhd->osh, pktbuf), BDC_HEADER_LEN)); + return BCME_ERROR; + } + + h = (struct bdc_header *)PKTDATA(dhd->osh, pktbuf); + + if ((*ifidx = BDC_GET_IF_IDX(h)) >= DHD_MAX_IFS) { + DHD_ERROR(("%s: rx data ifnum out of range (%d)\n", + __FUNCTION__, *ifidx)); + return BCME_ERROR; + } + + if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) != BDC_PROTO_VER) { + DHD_ERROR(("%s: non-BDC packet received, flags 0x%x\n", + dhd_ifname(dhd, *ifidx), h->flags)); + return BCME_ERROR; + } + + if (h->flags & BDC_FLAG_SUM_GOOD) { + DHD_INFO(("%s: BDC packet received with good rx-csum, flags 0x%x\n", + dhd_ifname(dhd, *ifidx), h->flags)); + PKTSETSUMGOOD(pktbuf, TRUE); + } + + PKTSETPRIO(pktbuf, (h->priority & BDC_PRIORITY_MASK)); + + PKTPULL(dhd->osh, pktbuf, BDC_HEADER_LEN); +#endif /* BDC */ + + return 0; +} + +int +dhd_prot_attach(dhd_pub_t *dhd) +{ + dhd_prot_t *cdc; + +#ifndef DHD_USE_STATIC_BUF + if (!(cdc = (dhd_prot_t *)MALLOC(dhd->osh, sizeof(dhd_prot_t)))) { + DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); + goto fail; + } +#else + if (!(cdc = (dhd_prot_t *)dhd_os_prealloc(DHD_PREALLOC_PROT, sizeof(dhd_prot_t)))) { + DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); + goto fail; + } +#endif /* DHD_USE_STATIC_BUF */ + memset(cdc, 0, sizeof(dhd_prot_t)); + + /* ensure that the msg buf directly follows the cdc msg struct */ + if ((uintptr)(&cdc->msg + 1) != (uintptr)cdc->buf) { + DHD_ERROR(("dhd_prot_t is not correctly defined\n")); + goto fail; + } + + dhd->prot = cdc; +#ifdef BDC + dhd->hdrlen += BDC_HEADER_LEN; +#endif + dhd->maxctl = WLC_IOCTL_MAXLEN + sizeof(cdc_ioctl_t) + ROUND_UP_MARGIN; + return 0; + +fail: +#ifndef DHD_USE_STATIC_BUF + if (cdc != NULL) + MFREE(dhd->osh, cdc, sizeof(dhd_prot_t)); +#endif + return BCME_NOMEM; +} + +/* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */ +void +dhd_prot_detach(dhd_pub_t *dhd) +{ +#ifndef DHD_USE_STATIC_BUF + MFREE(dhd->osh, dhd->prot, sizeof(dhd_prot_t)); +#endif + dhd->prot = NULL; +} + +void +dhd_prot_dstats(dhd_pub_t *dhd) +{ + /* No stats from dongle added yet, copy bus stats */ + dhd->dstats.tx_packets = dhd->tx_packets; + dhd->dstats.tx_errors = dhd->tx_errors; + dhd->dstats.rx_packets = dhd->rx_packets; + dhd->dstats.rx_errors = dhd->rx_errors; + dhd->dstats.rx_dropped = dhd->rx_dropped; + dhd->dstats.multicast = dhd->rx_multicast; + return; +} + +int +dhd_prot_init(dhd_pub_t *dhd) +{ + int ret = 0; + char buf[128]; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + dhd_os_proto_block(dhd); + + /* Get the device MAC address */ + strcpy(buf, "cur_etheraddr"); + ret = dhdcdc_query_ioctl(dhd, 0, WLC_GET_VAR, buf, sizeof(buf)); + if (ret < 0) { + dhd_os_proto_unblock(dhd); + return ret; + } + memcpy(dhd->mac.octet, buf, ETHER_ADDR_LEN); + + dhd_os_proto_unblock(dhd); + +#ifdef EMBEDDED_PLATFORM + ret = dhd_preinit_ioctls(dhd); +#endif /* EMBEDDED_PLATFORM */ + + /* Always assumes wl for now */ + dhd->iswl = TRUE; + + return ret; +} + +void +dhd_prot_stop(dhd_pub_t *dhd) +{ + /* Nothing to do for CDC */ +} diff --git a/drivers/net/wireless/bcm4329/dhd_common.c b/drivers/net/wireless/bcm4329/dhd_common.c new file mode 100644 index 0000000000000000000000000000000000000000..8b89e390a4054c060c44ee8a59dd5c0a06884876 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_common.c @@ -0,0 +1,2461 @@ +/* + * Broadcom Dongle Host Driver (DHD), common DHD core. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_common.c,v 1.5.6.8.2.6.6.69.4.25 2011-02-11 21:16:02 Exp $ + */ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef SET_RANDOM_MAC_SOFTAP +#include +#include +#endif + +#ifdef GET_CUSTOM_MAC_ENABLE +int wifi_get_mac_addr(unsigned char *buf); +#endif /* GET_CUSTOM_MAC_ENABLE */ + +int dhd_msg_level; + +#include + +char fw_path[MOD_PARAM_PATHLEN]; +char nv_path[MOD_PARAM_PATHLEN]; + +/* Last connection success/failure status */ +uint32 dhd_conn_event; +uint32 dhd_conn_status; +uint32 dhd_conn_reason; + +#define htod32(i) i +#define htod16(i) i +#define dtoh32(i) i +#define dtoh16(i) i + +extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len); +extern void dhd_ind_scan_confirm(void *h, bool status); +extern int dhd_wl_ioctl(dhd_pub_t *dhd, uint cmd, char *buf, uint buflen); +void dhd_iscan_lock(void); +void dhd_iscan_unlock(void); + +#if defined(SOFTAP) +extern bool ap_fw_loaded; +#endif +#if defined(KEEP_ALIVE) +int dhd_keep_alive_onoff(dhd_pub_t *dhd, int ka_on); +#endif /* KEEP_ALIVE */ + +/* Packet alignment for most efficient SDIO (can change based on platform) */ +#ifndef DHD_SDALIGN +#define DHD_SDALIGN 32 +#endif +#if !ISPOWEROF2(DHD_SDALIGN) +#error DHD_SDALIGN is not a power of 2! +#endif + +#ifdef DHD_DEBUG +const char dhd_version[] = "Dongle Host Driver, version " EPI_VERSION_STR "\nCompiled on " + __DATE__ " at " __TIME__; +#else +const char dhd_version[] = "Dongle Host Driver, version " EPI_VERSION_STR; +#endif + +void dhd_set_timer(void *bus, uint wdtick); + +/* IOVar table */ +enum { + IOV_VERSION = 1, + IOV_MSGLEVEL, + IOV_BCMERRORSTR, + IOV_BCMERROR, + IOV_WDTICK, + IOV_DUMP, +#ifdef DHD_DEBUG + IOV_CONS, + IOV_DCONSOLE_POLL, +#endif + IOV_CLEARCOUNTS, + IOV_LOGDUMP, + IOV_LOGCAL, + IOV_LOGSTAMP, + IOV_GPIOOB, + IOV_IOCTLTIMEOUT, + IOV_LAST +}; + +const bcm_iovar_t dhd_iovars[] = { + {"version", IOV_VERSION, 0, IOVT_BUFFER, sizeof(dhd_version) }, +#ifdef DHD_DEBUG + {"msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0 }, +#endif /* DHD_DEBUG */ + {"bcmerrorstr", IOV_BCMERRORSTR, 0, IOVT_BUFFER, BCME_STRLEN }, + {"bcmerror", IOV_BCMERROR, 0, IOVT_INT8, 0 }, + {"wdtick", IOV_WDTICK, 0, IOVT_UINT32, 0 }, + {"dump", IOV_DUMP, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN }, +#ifdef DHD_DEBUG + {"dconpoll", IOV_DCONSOLE_POLL, 0, IOVT_UINT32, 0 }, + {"cons", IOV_CONS, 0, IOVT_BUFFER, 0 }, +#endif + {"clearcounts", IOV_CLEARCOUNTS, 0, IOVT_VOID, 0 }, + {"gpioob", IOV_GPIOOB, 0, IOVT_UINT32, 0 }, + {"ioctl_timeout", IOV_IOCTLTIMEOUT, 0, IOVT_UINT32, 0 }, + {NULL, 0, 0, 0, 0 } +}; + +void +dhd_common_init(void) +{ + /* Init global variables at run-time, not as part of the declaration. + * This is required to support init/de-init of the driver. Initialization + * of globals as part of the declaration results in non-deterministic + * behaviour since the value of the globals may be different on the + * first time that the driver is initialized vs subsequent initializations. + */ + dhd_msg_level = DHD_ERROR_VAL; +#ifdef CONFIG_BCM4329_FW_PATH + strncpy(fw_path, CONFIG_BCM4329_FW_PATH, MOD_PARAM_PATHLEN-1); +#else + fw_path[0] = '\0'; +#endif +#ifdef CONFIG_BCM4329_NVRAM_PATH + strncpy(nv_path, CONFIG_BCM4329_NVRAM_PATH, MOD_PARAM_PATHLEN-1); +#else + nv_path[0] = '\0'; +#endif +} + +static int +dhd_dump(dhd_pub_t *dhdp, char *buf, int buflen) +{ + char eabuf[ETHER_ADDR_STR_LEN]; + + struct bcmstrbuf b; + struct bcmstrbuf *strbuf = &b; + + bcm_binit(strbuf, buf, buflen); + + /* Base DHD info */ + bcm_bprintf(strbuf, "%s\n", dhd_version); + bcm_bprintf(strbuf, "\n"); + bcm_bprintf(strbuf, "pub.up %d pub.txoff %d pub.busstate %d\n", + dhdp->up, dhdp->txoff, dhdp->busstate); + bcm_bprintf(strbuf, "pub.hdrlen %d pub.maxctl %d pub.rxsz %d\n", + dhdp->hdrlen, dhdp->maxctl, dhdp->rxsz); + bcm_bprintf(strbuf, "pub.iswl %d pub.drv_version %ld pub.mac %s\n", + dhdp->iswl, dhdp->drv_version, bcm_ether_ntoa(&dhdp->mac, eabuf)); + bcm_bprintf(strbuf, "pub.bcmerror %d tickcnt %d\n", dhdp->bcmerror, dhdp->tickcnt); + + bcm_bprintf(strbuf, "dongle stats:\n"); + bcm_bprintf(strbuf, "tx_packets %ld tx_bytes %ld tx_errors %ld tx_dropped %ld\n", + dhdp->dstats.tx_packets, dhdp->dstats.tx_bytes, + dhdp->dstats.tx_errors, dhdp->dstats.tx_dropped); + bcm_bprintf(strbuf, "rx_packets %ld rx_bytes %ld rx_errors %ld rx_dropped %ld\n", + dhdp->dstats.rx_packets, dhdp->dstats.rx_bytes, + dhdp->dstats.rx_errors, dhdp->dstats.rx_dropped); + bcm_bprintf(strbuf, "multicast %ld\n", dhdp->dstats.multicast); + + bcm_bprintf(strbuf, "bus stats:\n"); + bcm_bprintf(strbuf, "tx_packets %ld tx_multicast %ld tx_errors %ld\n", + dhdp->tx_packets, dhdp->tx_multicast, dhdp->tx_errors); + bcm_bprintf(strbuf, "tx_ctlpkts %ld tx_ctlerrs %ld\n", + dhdp->tx_ctlpkts, dhdp->tx_ctlerrs); + bcm_bprintf(strbuf, "rx_packets %ld rx_multicast %ld rx_errors %ld \n", + dhdp->rx_packets, dhdp->rx_multicast, dhdp->rx_errors); + bcm_bprintf(strbuf, "rx_ctlpkts %ld rx_ctlerrs %ld rx_dropped %ld rx_flushed %ld\n", + dhdp->rx_ctlpkts, dhdp->rx_ctlerrs, dhdp->rx_dropped, dhdp->rx_flushed); + bcm_bprintf(strbuf, "rx_readahead_cnt %ld tx_realloc %ld fc_packets %ld\n", + dhdp->rx_readahead_cnt, dhdp->tx_realloc, dhdp->fc_packets); + bcm_bprintf(strbuf, "wd_dpc_sched %ld\n", dhdp->wd_dpc_sched); + bcm_bprintf(strbuf, "\n"); + + /* Add any prot info */ + dhd_prot_dump(dhdp, strbuf); + bcm_bprintf(strbuf, "\n"); + + /* Add any bus info */ + dhd_bus_dump(dhdp, strbuf); + + return (!strbuf->size ? BCME_BUFTOOSHORT : 0); +} + +static int +dhd_doiovar(dhd_pub_t *dhd_pub, const bcm_iovar_t *vi, uint32 actionid, const char *name, + void *params, int plen, void *arg, int len, int val_size) +{ + int bcmerror = 0; + int32 int_val = 0; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid))) != 0) + goto exit; + + if (plen >= (int)sizeof(int_val)) + bcopy(params, &int_val, sizeof(int_val)); + + switch (actionid) { + case IOV_GVAL(IOV_VERSION): + /* Need to have checked buffer length */ + strncpy((char*)arg, dhd_version, len); + break; + + case IOV_GVAL(IOV_MSGLEVEL): + int_val = (int32)dhd_msg_level; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_MSGLEVEL): + dhd_msg_level = int_val; + break; + + case IOV_GVAL(IOV_BCMERRORSTR): + strncpy((char *)arg, bcmerrorstr(dhd_pub->bcmerror), BCME_STRLEN); + ((char *)arg)[BCME_STRLEN - 1] = 0x00; + break; + + case IOV_GVAL(IOV_BCMERROR): + int_val = (int32)dhd_pub->bcmerror; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_WDTICK): + int_val = (int32)dhd_watchdog_ms; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_WDTICK): + if (!dhd_pub->up) { + bcmerror = BCME_NOTUP; + break; + } + dhd_os_wd_timer(dhd_pub, (uint)int_val); + break; + + case IOV_GVAL(IOV_DUMP): + bcmerror = dhd_dump(dhd_pub, arg, len); + break; + +#ifdef DHD_DEBUG + case IOV_GVAL(IOV_DCONSOLE_POLL): + int_val = (int32)dhd_console_ms; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_DCONSOLE_POLL): + dhd_console_ms = (uint)int_val; + break; + + case IOV_SVAL(IOV_CONS): + if (len > 0) + bcmerror = dhd_bus_console_in(dhd_pub, arg, len - 1); + break; +#endif + + case IOV_SVAL(IOV_CLEARCOUNTS): + dhd_pub->tx_packets = dhd_pub->rx_packets = 0; + dhd_pub->tx_errors = dhd_pub->rx_errors = 0; + dhd_pub->tx_ctlpkts = dhd_pub->rx_ctlpkts = 0; + dhd_pub->tx_ctlerrs = dhd_pub->rx_ctlerrs = 0; + dhd_pub->rx_dropped = 0; + dhd_pub->rx_readahead_cnt = 0; + dhd_pub->tx_realloc = 0; + dhd_pub->wd_dpc_sched = 0; + memset(&dhd_pub->dstats, 0, sizeof(dhd_pub->dstats)); + dhd_bus_clearcounts(dhd_pub); + break; + + + case IOV_GVAL(IOV_IOCTLTIMEOUT): { + int_val = (int32)dhd_os_get_ioctl_resp_timeout(); + bcopy(&int_val, arg, sizeof(int_val)); + break; + } + + case IOV_SVAL(IOV_IOCTLTIMEOUT): { + if (int_val <= 0) + bcmerror = BCME_BADARG; + else + dhd_os_set_ioctl_resp_timeout((unsigned int)int_val); + break; + } + + + default: + bcmerror = BCME_UNSUPPORTED; + break; + } + +exit: + return bcmerror; +} + +/* Store the status of a connection attempt for later retrieval by an iovar */ +void +dhd_store_conn_status(uint32 event, uint32 status, uint32 reason) +{ + /* Do not overwrite a WLC_E_PRUNE with a WLC_E_SET_SSID + * because an encryption/rsn mismatch results in both events, and + * the important information is in the WLC_E_PRUNE. + */ + if (!(event == WLC_E_SET_SSID && status == WLC_E_STATUS_FAIL && + dhd_conn_event == WLC_E_PRUNE)) { + dhd_conn_event = event; + dhd_conn_status = status; + dhd_conn_reason = reason; + } +} + +bool +dhd_prec_enq(dhd_pub_t *dhdp, struct pktq *q, void *pkt, int prec) +{ + void *p; + int eprec = -1; /* precedence to evict from */ + bool discard_oldest; + + /* Fast case, precedence queue is not full and we are also not + * exceeding total queue length + */ + if (!pktq_pfull(q, prec) && !pktq_full(q)) { + pktq_penq(q, prec, pkt); + return TRUE; + } + + /* Determine precedence from which to evict packet, if any */ + if (pktq_pfull(q, prec)) + eprec = prec; + else if (pktq_full(q)) { + p = pktq_peek_tail(q, &eprec); + ASSERT(p); + if (eprec > prec) + return FALSE; + } + + /* Evict if needed */ + if (eprec >= 0) { + /* Detect queueing to unconfigured precedence */ + ASSERT(!pktq_pempty(q, eprec)); + discard_oldest = AC_BITMAP_TST(dhdp->wme_dp, eprec); + if (eprec == prec && !discard_oldest) + return FALSE; /* refuse newer (incoming) packet */ + /* Evict packet according to discard policy */ + p = discard_oldest ? pktq_pdeq(q, eprec) : pktq_pdeq_tail(q, eprec); + if (p == NULL) { + DHD_ERROR(("%s: pktq_penq() failed, oldest %d.", + __FUNCTION__, discard_oldest)); + ASSERT(p); + } + + PKTFREE(dhdp->osh, p, TRUE); + } + + /* Enqueue */ + p = pktq_penq(q, prec, pkt); + if (p == NULL) { + DHD_ERROR(("%s: pktq_penq() failed.", __FUNCTION__)); + ASSERT(p); + } + + return TRUE; +} + +static int +dhd_iovar_op(dhd_pub_t *dhd_pub, const char *name, + void *params, int plen, void *arg, int len, bool set) +{ + int bcmerror = 0; + int val_size; + const bcm_iovar_t *vi = NULL; + uint32 actionid; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + ASSERT(name); + ASSERT(len >= 0); + + /* Get MUST have return space */ + ASSERT(set || (arg && len)); + + /* Set does NOT take qualifiers */ + ASSERT(!set || (!params && !plen)); + + if ((vi = bcm_iovar_lookup(dhd_iovars, name)) == NULL) { + bcmerror = BCME_UNSUPPORTED; + goto exit; + } + + DHD_CTL(("%s: %s %s, len %d plen %d\n", __FUNCTION__, + name, (set ? "set" : "get"), len, plen)); + + /* set up 'params' pointer in case this is a set command so that + * the convenience int and bool code can be common to set and get + */ + 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 + /* all other types are integer sized */ + val_size = sizeof(int); + + actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid); + bcmerror = dhd_doiovar(dhd_pub, vi, actionid, name, params, plen, arg, len, val_size); + +exit: + return bcmerror; +} + +int +dhd_ioctl(dhd_pub_t *dhd_pub, dhd_ioctl_t *ioc, void *buf, uint buflen) +{ + int bcmerror = 0; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (!buf) return BCME_BADARG; + + switch (ioc->cmd) { + case DHD_GET_MAGIC: + if (buflen < sizeof(int)) + bcmerror = BCME_BUFTOOSHORT; + else + *(int*)buf = DHD_IOCTL_MAGIC; + break; + + case DHD_GET_VERSION: + if (buflen < sizeof(int)) + bcmerror = -BCME_BUFTOOSHORT; + else + *(int*)buf = DHD_IOCTL_VERSION; + break; + + case DHD_GET_VAR: + case DHD_SET_VAR: { + char *arg; + uint arglen; + + /* scan past the name to any arguments */ + for (arg = buf, arglen = buflen; *arg && arglen; arg++, arglen--); + + if (*arg) { + bcmerror = BCME_BUFTOOSHORT; + break; + } + + /* account for the NUL terminator */ + arg++, arglen--; + + /* call with the appropriate arguments */ + if (ioc->cmd == DHD_GET_VAR) + bcmerror = dhd_iovar_op(dhd_pub, buf, arg, arglen, + buf, buflen, IOV_GET); + else + bcmerror = dhd_iovar_op(dhd_pub, buf, NULL, 0, arg, arglen, IOV_SET); + if (bcmerror != BCME_UNSUPPORTED) + break; + + /* not in generic table, try protocol module */ + if (ioc->cmd == DHD_GET_VAR) + bcmerror = dhd_prot_iovar_op(dhd_pub, buf, arg, + arglen, buf, buflen, IOV_GET); + else + bcmerror = dhd_prot_iovar_op(dhd_pub, buf, + NULL, 0, arg, arglen, IOV_SET); + if (bcmerror != BCME_UNSUPPORTED) + break; + + /* if still not found, try bus module */ + if (ioc->cmd == DHD_GET_VAR) + bcmerror = dhd_bus_iovar_op(dhd_pub, buf, + arg, arglen, buf, buflen, IOV_GET); + else + bcmerror = dhd_bus_iovar_op(dhd_pub, buf, + NULL, 0, arg, arglen, IOV_SET); + + break; + } + + default: + bcmerror = BCME_UNSUPPORTED; + } + + return bcmerror; +} + + +#ifdef SHOW_EVENTS +static void +wl_show_host_event(wl_event_msg_t *event, void *event_data) +{ + uint i, status, reason; + bool group = FALSE, flush_txq = FALSE, link = FALSE; + char *auth_str, *event_name; + uchar *buf; + char err_msg[256], eabuf[ETHER_ADDR_STR_LEN]; + static struct {uint event; char *event_name;} event_names[] = { + {WLC_E_SET_SSID, "SET_SSID"}, + {WLC_E_JOIN, "JOIN"}, + {WLC_E_START, "START"}, + {WLC_E_AUTH, "AUTH"}, + {WLC_E_AUTH_IND, "AUTH_IND"}, + {WLC_E_DEAUTH, "DEAUTH"}, + {WLC_E_DEAUTH_IND, "DEAUTH_IND"}, + {WLC_E_ASSOC, "ASSOC"}, + {WLC_E_ASSOC_IND, "ASSOC_IND"}, + {WLC_E_REASSOC, "REASSOC"}, + {WLC_E_REASSOC_IND, "REASSOC_IND"}, + {WLC_E_DISASSOC, "DISASSOC"}, + {WLC_E_DISASSOC_IND, "DISASSOC_IND"}, + {WLC_E_QUIET_START, "START_QUIET"}, + {WLC_E_QUIET_END, "END_QUIET"}, + {WLC_E_BEACON_RX, "BEACON_RX"}, + {WLC_E_LINK, "LINK"}, + {WLC_E_MIC_ERROR, "MIC_ERROR"}, + {WLC_E_NDIS_LINK, "NDIS_LINK"}, + {WLC_E_ROAM, "ROAM"}, + {WLC_E_TXFAIL, "TXFAIL"}, + {WLC_E_PMKID_CACHE, "PMKID_CACHE"}, + {WLC_E_RETROGRADE_TSF, "RETROGRADE_TSF"}, + {WLC_E_PRUNE, "PRUNE"}, + {WLC_E_AUTOAUTH, "AUTOAUTH"}, + {WLC_E_EAPOL_MSG, "EAPOL_MSG"}, + {WLC_E_SCAN_COMPLETE, "SCAN_COMPLETE"}, + {WLC_E_ADDTS_IND, "ADDTS_IND"}, + {WLC_E_DELTS_IND, "DELTS_IND"}, + {WLC_E_BCNSENT_IND, "BCNSENT_IND"}, + {WLC_E_BCNRX_MSG, "BCNRX_MSG"}, + {WLC_E_BCNLOST_MSG, "BCNLOST_MSG"}, + {WLC_E_ROAM_PREP, "ROAM_PREP"}, + {WLC_E_PFN_NET_FOUND, "PNO_NET_FOUND"}, + {WLC_E_PFN_NET_LOST, "PNO_NET_LOST"}, + {WLC_E_RESET_COMPLETE, "RESET_COMPLETE"}, + {WLC_E_JOIN_START, "JOIN_START"}, + {WLC_E_ROAM_START, "ROAM_START"}, + {WLC_E_ASSOC_START, "ASSOC_START"}, + {WLC_E_IBSS_ASSOC, "IBSS_ASSOC"}, + {WLC_E_RADIO, "RADIO"}, + {WLC_E_PSM_WATCHDOG, "PSM_WATCHDOG"}, + {WLC_E_PROBREQ_MSG, "PROBREQ_MSG"}, + {WLC_E_SCAN_CONFIRM_IND, "SCAN_CONFIRM_IND"}, + {WLC_E_PSK_SUP, "PSK_SUP"}, + {WLC_E_COUNTRY_CODE_CHANGED, "COUNTRY_CODE_CHANGED"}, + {WLC_E_EXCEEDED_MEDIUM_TIME, "EXCEEDED_MEDIUM_TIME"}, + {WLC_E_ICV_ERROR, "ICV_ERROR"}, + {WLC_E_UNICAST_DECODE_ERROR, "UNICAST_DECODE_ERROR"}, + {WLC_E_MULTICAST_DECODE_ERROR, "MULTICAST_DECODE_ERROR"}, + {WLC_E_TRACE, "TRACE"}, + {WLC_E_ACTION_FRAME, "ACTION FRAME"}, + {WLC_E_ACTION_FRAME_COMPLETE, "ACTION FRAME TX COMPLETE"}, + {WLC_E_IF, "IF"}, + {WLC_E_RSSI, "RSSI"}, + {WLC_E_PFN_SCAN_COMPLETE, "SCAN_COMPLETE"} + }; + uint event_type, flags, auth_type, datalen; + event_type = ntoh32(event->event_type); + flags = ntoh16(event->flags); + status = ntoh32(event->status); + reason = ntoh32(event->reason); + auth_type = ntoh32(event->auth_type); + datalen = ntoh32(event->datalen); + /* debug dump of event messages */ + sprintf(eabuf, "%02x:%02x:%02x:%02x:%02x:%02x", + (uchar)event->addr.octet[0]&0xff, + (uchar)event->addr.octet[1]&0xff, + (uchar)event->addr.octet[2]&0xff, + (uchar)event->addr.octet[3]&0xff, + (uchar)event->addr.octet[4]&0xff, + (uchar)event->addr.octet[5]&0xff); + + event_name = "UNKNOWN"; + for (i = 0; i < ARRAYSIZE(event_names); i++) { + if (event_names[i].event == event_type) + event_name = event_names[i].event_name; + } + + DHD_EVENT(("EVENT: %s, event ID = %d\n", event_name, event_type)); + + if (flags & WLC_EVENT_MSG_LINK) + link = TRUE; + if (flags & WLC_EVENT_MSG_GROUP) + group = TRUE; + if (flags & WLC_EVENT_MSG_FLUSHTXQ) + flush_txq = TRUE; + + switch (event_type) { + case WLC_E_START: + case WLC_E_DEAUTH: + case WLC_E_DISASSOC: + DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name, eabuf)); + break; + + case WLC_E_ASSOC_IND: + case WLC_E_REASSOC_IND: + DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name, eabuf)); + break; + + case WLC_E_ASSOC: + case WLC_E_REASSOC: + if (status == WLC_E_STATUS_SUCCESS) { + DHD_EVENT(("MACEVENT: %s, MAC %s, SUCCESS\n", event_name, eabuf)); + } else if (status == WLC_E_STATUS_TIMEOUT) { + DHD_EVENT(("MACEVENT: %s, MAC %s, TIMEOUT\n", event_name, eabuf)); + } else if (status == WLC_E_STATUS_FAIL) { + DHD_EVENT(("MACEVENT: %s, MAC %s, FAILURE, reason %d\n", + event_name, eabuf, (int)reason)); + } else { + DHD_EVENT(("MACEVENT: %s, MAC %s, unexpected status %d\n", + event_name, eabuf, (int)status)); + } + break; + + case WLC_E_DEAUTH_IND: + case WLC_E_DISASSOC_IND: + DHD_EVENT(("MACEVENT: %s, MAC %s, reason %d\n", event_name, eabuf, (int)reason)); + break; + + case WLC_E_AUTH: + case WLC_E_AUTH_IND: + if (auth_type == DOT11_OPEN_SYSTEM) + auth_str = "Open System"; + else if (auth_type == DOT11_SHARED_KEY) + auth_str = "Shared Key"; + else { + sprintf(err_msg, "AUTH unknown: %d", (int)auth_type); + auth_str = err_msg; + } + if (event_type == WLC_E_AUTH_IND) { + DHD_EVENT(("MACEVENT: %s, MAC %s, %s\n", event_name, eabuf, auth_str)); + } else if (status == WLC_E_STATUS_SUCCESS) { + DHD_EVENT(("MACEVENT: %s, MAC %s, %s, SUCCESS\n", + event_name, eabuf, auth_str)); + } else if (status == WLC_E_STATUS_TIMEOUT) { + DHD_EVENT(("MACEVENT: %s, MAC %s, %s, TIMEOUT\n", + event_name, eabuf, auth_str)); + } else if (status == WLC_E_STATUS_FAIL) { + DHD_EVENT(("MACEVENT: %s, MAC %s, %s, FAILURE, reason %d\n", + event_name, eabuf, auth_str, (int)reason)); + } + + break; + + case WLC_E_JOIN: + case WLC_E_ROAM: + case WLC_E_SET_SSID: + if (status == WLC_E_STATUS_SUCCESS) { + DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name, eabuf)); + } else if (status == WLC_E_STATUS_FAIL) { + DHD_EVENT(("MACEVENT: %s, failed\n", event_name)); + } else if (status == WLC_E_STATUS_NO_NETWORKS) { + DHD_EVENT(("MACEVENT: %s, no networks found\n", event_name)); + } else { + DHD_EVENT(("MACEVENT: %s, unexpected status %d\n", + event_name, (int)status)); + } + break; + + case WLC_E_BEACON_RX: + if (status == WLC_E_STATUS_SUCCESS) { + DHD_EVENT(("MACEVENT: %s, SUCCESS\n", event_name)); + } else if (status == WLC_E_STATUS_FAIL) { + DHD_EVENT(("MACEVENT: %s, FAIL\n", event_name)); + } else { + DHD_EVENT(("MACEVENT: %s, status %d\n", event_name, status)); + } + break; + + case WLC_E_LINK: + DHD_EVENT(("MACEVENT: %s %s\n", event_name, link?"UP":"DOWN")); + break; + + case WLC_E_MIC_ERROR: + DHD_EVENT(("MACEVENT: %s, MAC %s, Group %d, Flush %d\n", + event_name, eabuf, group, flush_txq)); + break; + + case WLC_E_ICV_ERROR: + case WLC_E_UNICAST_DECODE_ERROR: + case WLC_E_MULTICAST_DECODE_ERROR: + DHD_EVENT(("MACEVENT: %s, MAC %s\n", + event_name, eabuf)); + break; + + case WLC_E_TXFAIL: + DHD_EVENT(("MACEVENT: %s, RA %s\n", event_name, eabuf)); + break; + + case WLC_E_SCAN_COMPLETE: + case WLC_E_PMKID_CACHE: + DHD_EVENT(("MACEVENT: %s\n", event_name)); + break; + + case WLC_E_PFN_NET_FOUND: + case WLC_E_PFN_NET_LOST: + case WLC_E_PFN_SCAN_COMPLETE: + DHD_EVENT(("PNOEVENT: %s\n", event_name)); + break; + + case WLC_E_PSK_SUP: + case WLC_E_PRUNE: + DHD_EVENT(("MACEVENT: %s, status %d, reason %d\n", + event_name, (int)status, (int)reason)); + break; + + case WLC_E_TRACE: + { + static uint32 seqnum_prev = 0; + msgtrace_hdr_t hdr; + uint32 nblost; + char *s, *p; + + buf = (uchar *) event_data; + memcpy(&hdr, buf, MSGTRACE_HDRLEN); + + if (hdr.version != MSGTRACE_VERSION) { + printf("\nMACEVENT: %s [unsupported version --> " + "dhd version:%d dongle version:%d]\n", + event_name, MSGTRACE_VERSION, hdr.version); + /* Reset datalen to avoid display below */ + datalen = 0; + break; + } + + /* There are 2 bytes available at the end of data */ + buf[MSGTRACE_HDRLEN + ntoh16(hdr.len)] = '\0'; + + if (ntoh32(hdr.discarded_bytes) || ntoh32(hdr.discarded_printf)) { + printf("\nWLC_E_TRACE: [Discarded traces in dongle -->" + "discarded_bytes %d discarded_printf %d]\n", + ntoh32(hdr.discarded_bytes), ntoh32(hdr.discarded_printf)); + } + + nblost = ntoh32(hdr.seqnum) - seqnum_prev - 1; + if (nblost > 0) { + printf("\nWLC_E_TRACE: [Event lost --> seqnum %d nblost %d\n", + ntoh32(hdr.seqnum), nblost); + } + seqnum_prev = ntoh32(hdr.seqnum); + + /* Display the trace buffer. Advance from \n to \n to avoid display big + * printf (issue with Linux printk ) + */ + p = (char *)&buf[MSGTRACE_HDRLEN]; + while ((s = strstr(p, "\n")) != NULL) { + *s = '\0'; + printf("%s\n", p); + p = s + 1; + } + printf("%s\n", p); + + /* Reset datalen to avoid display below */ + datalen = 0; + } + break; + + + case WLC_E_RSSI: + DHD_EVENT(("MACEVENT: %s %d\n", event_name, ntoh32(*((int *)event_data)))); + break; + + default: + DHD_EVENT(("MACEVENT: %s %d, MAC %s, status %d, reason %d, auth %d\n", + event_name, event_type, eabuf, (int)status, (int)reason, + (int)auth_type)); + break; + } + + /* show any appended data */ + if (datalen) { + buf = (uchar *) event_data; + DHD_EVENT((" data (%d) : ", datalen)); + for (i = 0; i < datalen; i++) + DHD_EVENT((" 0x%02x ", *buf++)); + DHD_EVENT(("\n")); + } +} +#endif /* SHOW_EVENTS */ + +int +wl_host_event(struct dhd_info *dhd, int *ifidx, void *pktdata, + wl_event_msg_t *event, void **data_ptr) +{ + /* check whether packet is a BRCM event pkt */ + bcm_event_t *pvt_data = (bcm_event_t *)pktdata; + char *event_data; + uint32 type, status; + uint16 flags; + int evlen; + + if (bcmp(BRCM_OUI, &pvt_data->bcm_hdr.oui[0], DOT11_OUI_LEN)) { + DHD_ERROR(("%s: mismatched OUI, bailing\n", __FUNCTION__)); + return (BCME_ERROR); + } + + /* BRCM event pkt may be unaligned - use xxx_ua to load user_subtype. */ + if (ntoh16_ua((void *)&pvt_data->bcm_hdr.usr_subtype) != BCMILCP_BCM_SUBTYPE_EVENT) { + DHD_ERROR(("%s: mismatched subtype, bailing\n", __FUNCTION__)); + return (BCME_ERROR); + } + + *data_ptr = &pvt_data[1]; + event_data = *data_ptr; + + /* memcpy since BRCM event pkt may be unaligned. */ + memcpy(event, &pvt_data->event, sizeof(wl_event_msg_t)); + + type = ntoh32_ua((void *)&event->event_type); + flags = ntoh16_ua((void *)&event->flags); + status = ntoh32_ua((void *)&event->status); + evlen = ntoh32_ua((void *)&event->datalen) + sizeof(bcm_event_t); + + switch (type) { + case WLC_E_IF: + { + dhd_if_event_t *ifevent = (dhd_if_event_t *)event_data; + DHD_TRACE(("%s: if event\n", __FUNCTION__)); + + if (ifevent->ifidx > 0 && ifevent->ifidx < DHD_MAX_IFS) + { + if (ifevent->action == WLC_E_IF_ADD) + dhd_add_if(dhd, ifevent->ifidx, + NULL, event->ifname, + pvt_data->eth.ether_dhost, + ifevent->flags, ifevent->bssidx); + else + dhd_del_if(dhd, ifevent->ifidx); + } else { + DHD_ERROR(("%s: Invalid ifidx %d for %s\n", + __FUNCTION__, ifevent->ifidx, event->ifname)); + } + } + /* send up the if event: btamp user needs it */ + *ifidx = dhd_ifname2idx(dhd, event->ifname); + /* push up to external supp/auth */ + dhd_event(dhd, (char *)pvt_data, evlen, *ifidx); + break; + + +#ifdef P2P + case WLC_E_NDIS_LINK: + break; +#endif + /* fall through */ + /* These are what external supplicant/authenticator wants */ + case WLC_E_LINK: + case WLC_E_ASSOC_IND: + case WLC_E_REASSOC_IND: + case WLC_E_DISASSOC_IND: + case WLC_E_MIC_ERROR: + default: + /* Fall through: this should get _everything_ */ + + *ifidx = dhd_ifname2idx(dhd, event->ifname); + /* push up to external supp/auth */ + dhd_event(dhd, (char *)pvt_data, evlen, *ifidx); + DHD_TRACE(("%s: MAC event %d, flags %x, status %x\n", + __FUNCTION__, type, flags, status)); + + /* put it back to WLC_E_NDIS_LINK */ + if (type == WLC_E_NDIS_LINK) { + uint32 temp; + + temp = ntoh32_ua((void *)&event->event_type); + DHD_TRACE(("Converted to WLC_E_LINK type %d\n", temp)); + + temp = ntoh32(WLC_E_NDIS_LINK); + memcpy((void *)(&pvt_data->event.event_type), &temp, + sizeof(pvt_data->event.event_type)); + } + break; + } + +#ifdef SHOW_EVENTS + wl_show_host_event(event, event_data); +#endif /* SHOW_EVENTS */ + + return (BCME_OK); +} + + +void +wl_event_to_host_order(wl_event_msg_t *evt) +{ + /* Event struct members passed from dongle to host are stored in network + * byte order. Convert all members to host-order. + */ + evt->event_type = ntoh32(evt->event_type); + evt->flags = ntoh16(evt->flags); + evt->status = ntoh32(evt->status); + evt->reason = ntoh32(evt->reason); + evt->auth_type = ntoh32(evt->auth_type); + evt->datalen = ntoh32(evt->datalen); + evt->version = ntoh16(evt->version); +} + +void print_buf(void *pbuf, int len, int bytes_per_line) +{ + int i, j = 0; + unsigned char *buf = pbuf; + + if (bytes_per_line == 0) { + bytes_per_line = len; + } + + for (i = 0; i < len; i++) { + printf("%2.2x", *buf++); + j++; + if (j == bytes_per_line) { + printf("\n"); + j = 0; + } else { + printf(":"); + } + } + printf("\n"); +} + +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) + +#ifdef PKT_FILTER_SUPPORT +/* Convert user's input in hex pattern to byte-size mask */ +static int +wl_pattern_atoh(char *src, char *dst) +{ + int i; + if (strncmp(src, "0x", 2) != 0 && + strncmp(src, "0X", 2) != 0) { + DHD_ERROR(("Mask invalid format. Needs to start with 0x\n")); + return -1; + } + src = src + 2; /* Skip past 0x */ + if (strlen(src) % 2 != 0) { + DHD_ERROR(("Mask invalid format. Needs to be of even length\n")); + return -1; + } + for (i = 0; *src != '\0'; i++) { + char num[3]; + strncpy(num, src, 2); + num[2] = '\0'; + dst[i] = (uint8)strtoul(num, NULL, 16); + src += 2; + } + return i; +} + +void +dhd_pktfilter_offload_enable(dhd_pub_t * dhd, char *arg, int enable, int master_mode) +{ + char *argv[8]; + int i = 0; + const char *str; + int buf_len; + int str_len; + char *arg_save = 0, *arg_org = 0; + int rc; + char buf[128]; + wl_pkt_filter_enable_t enable_parm; + wl_pkt_filter_enable_t * pkt_filterp; + + if (!arg) + return; + + if (!(arg_save = MALLOC(dhd->osh, strlen(arg) + 1))) { + DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); + goto fail; + } + arg_org = arg_save; + memcpy(arg_save, arg, strlen(arg) + 1); + + argv[i] = bcmstrtok(&arg_save, " ", 0); + + i = 0; + if (NULL == argv[i]) { + DHD_ERROR(("No args provided\n")); + goto fail; + } + + str = "pkt_filter_enable"; + str_len = strlen(str); + strncpy(buf, str, str_len); + buf[str_len] = '\0'; + buf_len = str_len + 1; + + pkt_filterp = (wl_pkt_filter_enable_t *)(buf + str_len + 1); + + /* Parse packet filter id. */ + enable_parm.id = htod32(strtoul(argv[i], NULL, 0)); + + /* Parse enable/disable value. */ + enable_parm.enable = htod32(enable); + + buf_len += sizeof(enable_parm); + memcpy((char *)pkt_filterp, + &enable_parm, + sizeof(enable_parm)); + + /* Enable/disable the specified filter. */ + rc = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, buf_len); + rc = rc >= 0 ? 0 : rc; + if (rc) + DHD_TRACE(("%s: failed to add pktfilter %s, retcode = %d\n", + __FUNCTION__, arg, rc)); + else + DHD_TRACE(("%s: successfully added pktfilter %s\n", + __FUNCTION__, arg)); + + /* Contorl the master mode */ + bcm_mkiovar("pkt_filter_mode", (char *)&master_mode, 4, buf, sizeof(buf)); + rc = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, sizeof(buf)); + rc = rc >= 0 ? 0 : rc; + if (rc) + DHD_TRACE(("%s: failed to add pktfilter %s, retcode = %d\n", + __FUNCTION__, arg, rc)); + +fail: + if (arg_org) + MFREE(dhd->osh, arg_org, strlen(arg) + 1); +} + +void +dhd_pktfilter_offload_set(dhd_pub_t * dhd, char *arg) +{ + const char *str; + wl_pkt_filter_t pkt_filter; + wl_pkt_filter_t *pkt_filterp; + int buf_len; + int str_len; + int rc; + uint32 mask_size; + uint32 pattern_size; + char *argv[8], * buf = 0; + int i = 0; + char *arg_save = 0, *arg_org = 0; +#define BUF_SIZE 2048 + + if (!arg) + return; + + if (!(arg_save = MALLOC(dhd->osh, strlen(arg) + 1))) { + DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); + goto fail; + } + + arg_org = arg_save; + + if (!(buf = MALLOC(dhd->osh, BUF_SIZE))) { + DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); + goto fail; + } + + memcpy(arg_save, arg, strlen(arg) + 1); + + if (strlen(arg) > BUF_SIZE) { + DHD_ERROR(("Not enough buffer %d < %d\n", (int)strlen(arg), (int)sizeof(buf))); + goto fail; + } + + argv[i] = bcmstrtok(&arg_save, " ", 0); + while (argv[i++]) + argv[i] = bcmstrtok(&arg_save, " ", 0); + + i = 0; + if (NULL == argv[i]) { + DHD_ERROR(("No args provided\n")); + goto fail; + } + + str = "pkt_filter_add"; + str_len = strlen(str); + strncpy(buf, str, str_len); + buf[ str_len ] = '\0'; + buf_len = str_len + 1; + + pkt_filterp = (wl_pkt_filter_t *) (buf + str_len + 1); + + /* Parse packet filter id. */ + pkt_filter.id = htod32(strtoul(argv[i], NULL, 0)); + + if (NULL == argv[++i]) { + DHD_ERROR(("Polarity not provided\n")); + goto fail; + } + + /* Parse filter polarity. */ + pkt_filter.negate_match = htod32(strtoul(argv[i], NULL, 0)); + + if (NULL == argv[++i]) { + DHD_ERROR(("Filter type not provided\n")); + goto fail; + } + + /* Parse filter type. */ + pkt_filter.type = htod32(strtoul(argv[i], NULL, 0)); + + if (NULL == argv[++i]) { + DHD_ERROR(("Offset not provided\n")); + goto fail; + } + + /* Parse pattern filter offset. */ + pkt_filter.u.pattern.offset = htod32(strtoul(argv[i], NULL, 0)); + + if (NULL == argv[++i]) { + DHD_ERROR(("Bitmask not provided\n")); + goto fail; + } + + /* Parse pattern filter mask. */ + mask_size = + htod32(wl_pattern_atoh(argv[i], (char *) pkt_filterp->u.pattern.mask_and_pattern)); + + if (NULL == argv[++i]) { + DHD_ERROR(("Pattern not provided\n")); + goto fail; + } + + /* Parse pattern filter pattern. */ + pattern_size = + htod32(wl_pattern_atoh(argv[i], + (char *) &pkt_filterp->u.pattern.mask_and_pattern[mask_size])); + + if (mask_size != pattern_size) { + DHD_ERROR(("Mask and pattern not the same size\n")); + goto fail; + } + + pkt_filter.u.pattern.size_bytes = mask_size; + buf_len += WL_PKT_FILTER_FIXED_LEN; + buf_len += (WL_PKT_FILTER_PATTERN_FIXED_LEN + 2 * mask_size); + + /* Keep-alive attributes are set in local variable (keep_alive_pkt), and + ** then memcpy'ed into buffer (keep_alive_pktp) since there is no + ** guarantee that the buffer is properly aligned. + */ + memcpy((char *)pkt_filterp, + &pkt_filter, + WL_PKT_FILTER_FIXED_LEN + WL_PKT_FILTER_PATTERN_FIXED_LEN); + + rc = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, buf_len); + rc = rc >= 0 ? 0 : rc; + + if (rc) + DHD_TRACE(("%s: failed to add pktfilter %s, retcode = %d\n", + __FUNCTION__, arg, rc)); + else + DHD_TRACE(("%s: successfully added pktfilter %s\n", + __FUNCTION__, arg)); + +fail: + if (arg_org) + MFREE(dhd->osh, arg_org, strlen(arg) + 1); + + if (buf) + MFREE(dhd->osh, buf, BUF_SIZE); +} +#endif + +#ifdef ARP_OFFLOAD_SUPPORT +void +dhd_arp_offload_set(dhd_pub_t * dhd, int arp_mode) +{ + char iovbuf[32]; + int retcode; + + bcm_mkiovar("arp_ol", (char *)&arp_mode, 4, iovbuf, sizeof(iovbuf)); + retcode = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + retcode = retcode >= 0 ? 0 : retcode; + if (retcode) + DHD_TRACE(("%s: failed to set ARP offload mode to 0x%x, retcode = %d\n", + __FUNCTION__, arp_mode, retcode)); + else + DHD_TRACE(("%s: successfully set ARP offload mode to 0x%x\n", + __FUNCTION__, arp_mode)); +} + +void +dhd_arp_offload_enable(dhd_pub_t * dhd, int arp_enable) +{ + char iovbuf[32]; + int retcode; + + bcm_mkiovar("arpoe", (char *)&arp_enable, 4, iovbuf, sizeof(iovbuf)); + retcode = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + retcode = retcode >= 0 ? 0 : retcode; + if (retcode) + DHD_TRACE(("%s: failed to enabe ARP offload to %d, retcode = %d\n", + __FUNCTION__, arp_enable, retcode)); + else + DHD_TRACE(("%s: successfully enabed ARP offload to %d\n", + __FUNCTION__, arp_enable)); +} +#endif + + +void dhd_arp_cleanup(dhd_pub_t *dhd) +{ +#ifdef ARP_OFFLOAD_SUPPORT + int ret = 0; + int iov_len = 0; + char iovbuf[128]; + + if (dhd == NULL) return; + + dhd_os_proto_block(dhd); + + iov_len = bcm_mkiovar("arp_hostip_clear", 0, 0, iovbuf, sizeof(iovbuf)); + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, iov_len)) < 0) + DHD_ERROR(("%s failed code %d\n", __FUNCTION__, ret)); + + iov_len = bcm_mkiovar("arp_table_clear", 0, 0, iovbuf, sizeof(iovbuf)); + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, iov_len)) < 0) + DHD_ERROR(("%s failed code %d\n", __FUNCTION__, ret)); + + dhd_os_proto_unblock(dhd); + +#endif /* ARP_OFFLOAD_SUPPORT */ +} + +void dhd_arp_offload_add_ip(dhd_pub_t *dhd, u32 ipaddr) +{ +#ifdef ARP_OFFLOAD_SUPPORT + int iov_len = 0; + char iovbuf[32]; + int retcode; + + dhd_os_proto_block(dhd); + + iov_len = bcm_mkiovar("arp_hostip", (char *)&ipaddr, 4, iovbuf, sizeof(iovbuf)); + retcode = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, iov_len); + + dhd_os_proto_unblock(dhd); + + if (retcode) + DHD_TRACE(("%s: ARP ip addr add failed, retcode = %d\n", + __FUNCTION__, retcode)); + else + DHD_TRACE(("%s: ARP ipaddr entry added\n", + __FUNCTION__)); +#endif /* ARP_OFFLOAD_SUPPORT */ +} + + +int dhd_arp_get_arp_hostip_table(dhd_pub_t *dhd, void *buf, int buflen) +{ +#ifdef ARP_OFFLOAD_SUPPORT + int retcode; + int iov_len = 0; + + if (!buf) + return -1; + + dhd_os_proto_block(dhd); + + iov_len = bcm_mkiovar("arp_hostip", 0, 0, buf, buflen); + retcode = dhdcdc_query_ioctl(dhd, 0, WLC_GET_VAR, buf, buflen); + + dhd_os_proto_unblock(dhd); + + if (retcode) { + DHD_TRACE(("%s: ioctl WLC_GET_VAR error %d\n", + __FUNCTION__, retcode)); + + return -1; + } +#endif /* ARP_OFFLOAD_SUPPORT */ + return 0; +} + + +int +dhd_preinit_ioctls(dhd_pub_t *dhd) +{ + char iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + '\0' + bitvec */ + uint up = 0; + char buf[128], *ptr; + uint power_mode = PM_FAST; + uint32 dongle_align = DHD_SDALIGN; + uint32 glom = 0; + uint bcn_timeout = 4; + int scan_assoc_time = 40; + int scan_unassoc_time = 40; + uint32 listen_interval = LISTEN_INTERVAL; /* Default Listen Interval in Beacons */ +#if defined(SOFTAP) + uint dtim = 1; +#endif + int ret = 0; +#ifdef GET_CUSTOM_MAC_ENABLE + struct ether_addr ea_addr; +#endif /* GET_CUSTOM_MAC_ENABLE */ + + dhd_os_proto_block(dhd); + +#ifdef GET_CUSTOM_MAC_ENABLE + /* + ** Read MAC address from external customer place + ** NOTE that default mac address has to be present in otp or nvram file + ** to bring up firmware but unique per board mac address maybe provided + ** by customer code + */ + ret = dhd_custom_get_mac_address(ea_addr.octet); + if (!ret) { + bcm_mkiovar("cur_etheraddr", (void *)&ea_addr, ETHER_ADDR_LEN, buf, sizeof(buf)); + ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, sizeof(buf)); + if (ret < 0) { + DHD_ERROR(("%s: can't set MAC address , error=%d\n", __FUNCTION__, ret)); + } else + memcpy(dhd->mac.octet, (void *)&ea_addr, ETHER_ADDR_LEN); + } +#endif /* GET_CUSTOM_MAC_ENABLE */ + +#ifdef SET_RANDOM_MAC_SOFTAP + if (strstr(fw_path, "apsta") != NULL) { + uint rand_mac; + + srandom32((uint)jiffies); + rand_mac = random32(); + iovbuf[0] = 0x02; /* locally administered bit */ + iovbuf[1] = 0x1A; + iovbuf[2] = 0x11; + iovbuf[3] = (unsigned char)(rand_mac & 0x0F) | 0xF0; + iovbuf[4] = (unsigned char)(rand_mac >> 8); + iovbuf[5] = (unsigned char)(rand_mac >> 16); + + printk("Broadcom Dongle Host Driver mac=%02x:%02x:%02x:%02x:%02x:%02x\n", + iovbuf[0], iovbuf[1], iovbuf[2], iovbuf[3], iovbuf[4], iovbuf[5]); + + bcm_mkiovar("cur_etheraddr", (void *)iovbuf, ETHER_ADDR_LEN, buf, sizeof(buf)); + ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, sizeof(buf)); + if (ret < 0) { + DHD_ERROR(("%s: can't set MAC address , error=%d\n", __FUNCTION__, ret)); + } else + memcpy(dhd->mac.octet, iovbuf, ETHER_ADDR_LEN); + } +#endif /* SET_RANDOM_MAC_SOFTAP */ + + /* Set Country code */ + if (dhd->dhd_cspec.ccode[0] != 0) { + bcm_mkiovar("country", (char *)&dhd->dhd_cspec, \ + sizeof(wl_country_t), iovbuf, sizeof(iovbuf)); + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf))) < 0) { + DHD_ERROR(("%s: country code setting failed\n", __FUNCTION__)); + } + } + + /* Set Listen Interval */ + bcm_mkiovar("assoc_listen", (char *)&listen_interval, 4, iovbuf, sizeof(iovbuf)); + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf))) < 0) + DHD_ERROR(("%s assoc_listen failed %d\n", __FUNCTION__, ret)); + + /* query for 'ver' to get version info from firmware */ + memset(buf, 0, sizeof(buf)); + ptr = buf; + bcm_mkiovar("ver", 0, 0, buf, sizeof(buf)); + dhdcdc_query_ioctl(dhd, 0, WLC_GET_VAR, buf, sizeof(buf)); + bcmstrtok(&ptr, "\n", 0); + /* Print fw version info */ + DHD_ERROR(("Firmware version = %s\n", buf)); + + /* Set PowerSave mode */ + dhdcdc_set_ioctl(dhd, 0, WLC_SET_PM, (char *)&power_mode, sizeof(power_mode)); + + /* Match Host and Dongle rx alignment */ + bcm_mkiovar("bus:txglomalign", (char *)&dongle_align, 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + + /* disable glom option per default */ + bcm_mkiovar("bus:txglom", (char *)&glom, 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + + /* Setup timeout if Beacons are lost and roam is off to report link down */ + bcm_mkiovar("bcn_timeout", (char *)&bcn_timeout, 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + + /* Enable/Disable build-in roaming to allowed ext supplicant to take of romaing */ + bcm_mkiovar("roam_off", (char *)&dhd_roam, 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + +#if defined(SOFTAP) + if (ap_fw_loaded == TRUE) { + dhdcdc_set_ioctl(dhd, 0, WLC_SET_DTIMPRD, (char *)&dtim, sizeof(dtim)); + } +#endif + + if (dhd_roam == 0) + { + /* set internal roaming roaming parameters */ + int roam_scan_period = 30; /* in sec */ + int roam_fullscan_period = 120; /* in sec */ + int roam_trigger = -85; + int roam_delta = 15; + int band; + int band_temp_set = WLC_BAND_2G; + + if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_ROAM_SCAN_PERIOD, \ + (char *)&roam_scan_period, sizeof(roam_scan_period)) < 0) + DHD_ERROR(("%s: roam scan setup failed\n", __FUNCTION__)); + + bcm_mkiovar("fullroamperiod", (char *)&roam_fullscan_period, \ + 4, iovbuf, sizeof(iovbuf)); + if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, \ + iovbuf, sizeof(iovbuf)) < 0) + DHD_ERROR(("%s: roam fullscan setup failed\n", __FUNCTION__)); + + if (dhdcdc_query_ioctl(dhd, 0, WLC_GET_BAND, \ + (char *)&band, sizeof(band)) < 0) + DHD_ERROR(("%s: roam delta setting failed\n", __FUNCTION__)); + else { + if ((band == WLC_BAND_AUTO) || (band == WLC_BAND_ALL)) + { + /* temp set band to insert new roams values */ + if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_BAND, \ + (char *)&band_temp_set, sizeof(band_temp_set)) < 0) + DHD_ERROR(("%s: local band seting failed\n", __FUNCTION__)); + } + if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_ROAM_DELTA, \ + (char *)&roam_delta, sizeof(roam_delta)) < 0) + DHD_ERROR(("%s: roam delta setting failed\n", __FUNCTION__)); + + if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_ROAM_TRIGGER, \ + (char *)&roam_trigger, sizeof(roam_trigger)) < 0) + DHD_ERROR(("%s: roam trigger setting failed\n", __FUNCTION__)); + + /* Restore original band settinngs */ + if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_BAND, \ + (char *)&band, sizeof(band)) < 0) + DHD_ERROR(("%s: Original band restore failed\n", __FUNCTION__)); + } + } + + /* Force STA UP */ + if (dhd_radio_up) + dhdcdc_set_ioctl(dhd, 0, WLC_UP, (char *)&up, sizeof(up)); + + /* Setup event_msgs */ + bcm_mkiovar("event_msgs", dhd->eventmask, WL_EVENTING_MASK_LEN, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + + dhdcdc_set_ioctl(dhd, 0, WLC_SET_SCAN_CHANNEL_TIME, (char *)&scan_assoc_time, + sizeof(scan_assoc_time)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_SCAN_UNASSOC_TIME, (char *)&scan_unassoc_time, + sizeof(scan_unassoc_time)); + +#ifdef ARP_OFFLOAD_SUPPORT + /* Set and enable ARP offload feature */ + if (dhd_arp_enable) + dhd_arp_offload_set(dhd, dhd_arp_mode); + dhd_arp_offload_enable(dhd, dhd_arp_enable); +#endif /* ARP_OFFLOAD_SUPPORT */ + +#ifdef PKT_FILTER_SUPPORT + { + int i; + /* Set up pkt filter */ + if (dhd_pkt_filter_enable) { + for (i = 0; i < dhd->pktfilter_count; i++) { + dhd_pktfilter_offload_set(dhd, dhd->pktfilter[i]); + dhd_pktfilter_offload_enable(dhd, dhd->pktfilter[i], + dhd_pkt_filter_init, dhd_master_mode); + } + } + } +#endif /* PKT_FILTER_SUPPORT */ + +#if defined(KEEP_ALIVE) + { + /* Set Keep Alive : be sure to use FW with -keepalive */ + int res; + + if (ap_fw_loaded == FALSE) { + if ((res = dhd_keep_alive_onoff(dhd, 1)) < 0) + DHD_ERROR(("%s set keeplive failed %d\n", \ + __FUNCTION__, res)); + } + } +#endif + + dhd_os_proto_unblock(dhd); + + return 0; +} + +#ifdef SIMPLE_ISCAN + +uint iscan_thread_id; +iscan_buf_t * iscan_chain = 0; + +iscan_buf_t * +dhd_iscan_allocate_buf(dhd_pub_t *dhd, iscan_buf_t **iscanbuf) +{ + iscan_buf_t *iscanbuf_alloc = 0; + iscan_buf_t *iscanbuf_head; + + dhd_iscan_lock(); + + iscanbuf_alloc = (iscan_buf_t*)MALLOC(dhd->osh, sizeof(iscan_buf_t)); + if (iscanbuf_alloc == NULL) + goto fail; + + iscanbuf_alloc->next = NULL; + iscanbuf_head = *iscanbuf; + + DHD_ISCAN(("%s: addr of allocated node = 0x%X" + "addr of iscanbuf_head = 0x%X dhd = 0x%X\n", + __FUNCTION__, iscanbuf_alloc, iscanbuf_head, dhd)); + + if (iscanbuf_head == NULL) { + *iscanbuf = iscanbuf_alloc; + DHD_ISCAN(("%s: Head is allocated\n", __FUNCTION__)); + goto fail; + } + + while (iscanbuf_head->next) + iscanbuf_head = iscanbuf_head->next; + + iscanbuf_head->next = iscanbuf_alloc; + +fail: + dhd_iscan_unlock(); + return iscanbuf_alloc; +} + +void +dhd_iscan_free_buf(void *dhdp, iscan_buf_t *iscan_delete) +{ + iscan_buf_t *iscanbuf_free = 0; + iscan_buf_t *iscanbuf_prv = 0; + iscan_buf_t *iscanbuf_cur = iscan_chain; + dhd_pub_t *dhd = dhd_bus_pub(dhdp); + + dhd_iscan_lock(); + /* If iscan_delete is null then delete the entire + * chain or else delete specific one provided + */ + if (!iscan_delete) { + while (iscanbuf_cur) { + iscanbuf_free = iscanbuf_cur; + iscanbuf_cur = iscanbuf_cur->next; + iscanbuf_free->next = 0; + MFREE(dhd->osh, iscanbuf_free, sizeof(iscan_buf_t)); + } + iscan_chain = 0; + } else { + while (iscanbuf_cur) { + if (iscanbuf_cur == iscan_delete) + break; + iscanbuf_prv = iscanbuf_cur; + iscanbuf_cur = iscanbuf_cur->next; + } + if (iscanbuf_prv) + iscanbuf_prv->next = iscan_delete->next; + + iscan_delete->next = 0; + MFREE(dhd->osh, iscan_delete, sizeof(iscan_buf_t)); + + if (!iscanbuf_prv) + iscan_chain = 0; + } + dhd_iscan_unlock(); +} + +iscan_buf_t * +dhd_iscan_result_buf(void) +{ + return iscan_chain; +} + + + +/* +* print scan cache +* print partial iscan_skip list differently +*/ +int +dhd_iscan_print_cache(iscan_buf_t *iscan_skip) +{ + int i = 0, l = 0; + iscan_buf_t *iscan_cur; + wl_iscan_results_t *list; + wl_scan_results_t *results; + wl_bss_info_t UNALIGNED *bi; + + dhd_iscan_lock(); + + iscan_cur = dhd_iscan_result_buf(); + + while (iscan_cur) { + list = (wl_iscan_results_t *)iscan_cur->iscan_buf; + if (!list) + break; + + results = (wl_scan_results_t *)&list->results; + if (!results) + break; + + if (results->version != WL_BSS_INFO_VERSION) { + DHD_ISCAN(("%s: results->version %d != WL_BSS_INFO_VERSION\n", + __FUNCTION__, results->version)); + goto done; + } + + bi = results->bss_info; + for (i = 0; i < results->count; i++) { + if (!bi) + break; + + DHD_ISCAN(("%s[%2.2d:%2.2d] %X:%X:%X:%X:%X:%X\n", + iscan_cur != iscan_skip?"BSS":"bss", l, i, + bi->BSSID.octet[0], bi->BSSID.octet[1], bi->BSSID.octet[2], + bi->BSSID.octet[3], bi->BSSID.octet[4], bi->BSSID.octet[5])); + + bi = (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)); + } + iscan_cur = iscan_cur->next; + l++; + } + +done: + dhd_iscan_unlock(); + return 0; +} + +/* +* delete disappeared AP from specific scan cache but skip partial list in iscan_skip +*/ +int +dhd_iscan_delete_bss(void *dhdp, void *addr, iscan_buf_t *iscan_skip) +{ + int i = 0, j = 0, l = 0; + iscan_buf_t *iscan_cur; + wl_iscan_results_t *list; + wl_scan_results_t *results; + wl_bss_info_t UNALIGNED *bi, *bi_new, *bi_next; + + uchar *s_addr = addr; + + dhd_iscan_lock(); + DHD_ISCAN(("%s: BSS to remove %X:%X:%X:%X:%X:%X\n", + __FUNCTION__, s_addr[0], s_addr[1], s_addr[2], + s_addr[3], s_addr[4], s_addr[5])); + + iscan_cur = dhd_iscan_result_buf(); + + while (iscan_cur) { + if (iscan_cur != iscan_skip) { + list = (wl_iscan_results_t *)iscan_cur->iscan_buf; + if (!list) + break; + + results = (wl_scan_results_t *)&list->results; + if (!results) + break; + + if (results->version != WL_BSS_INFO_VERSION) { + DHD_ERROR(("%s: results->version %d != WL_BSS_INFO_VERSION\n", + __FUNCTION__, results->version)); + goto done; + } + + bi = results->bss_info; + for (i = 0; i < results->count; i++) { + if (!bi) + break; + + if (!memcmp(bi->BSSID.octet, addr, ETHER_ADDR_LEN)) { + DHD_ISCAN(("%s: Del BSS[%2.2d:%2.2d] %X:%X:%X:%X:%X:%X\n", + __FUNCTION__, l, i, bi->BSSID.octet[0], + bi->BSSID.octet[1], bi->BSSID.octet[2], + bi->BSSID.octet[3], bi->BSSID.octet[4], + bi->BSSID.octet[5])); + + bi_new = bi; + bi = (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)); +/* + if(bi && bi_new) { + bcopy(bi, bi_new, results->buflen - + dtoh32(bi_new->length)); + results->buflen -= dtoh32(bi_new->length); + } +*/ + results->buflen -= dtoh32(bi_new->length); + results->count--; + + for (j = i; j < results->count; j++) { + if (bi && bi_new) { + DHD_ISCAN(("%s: Moved up BSS[%2.2d:%2.2d]" + "%X:%X:%X:%X:%X:%X\n", + __FUNCTION__, l, j, bi->BSSID.octet[0], + bi->BSSID.octet[1], bi->BSSID.octet[2], + bi->BSSID.octet[3], bi->BSSID.octet[4], + bi->BSSID.octet[5])); + + bi_next = (wl_bss_info_t *)((uintptr)bi + + dtoh32(bi->length)); + bcopy(bi, bi_new, dtoh32(bi->length)); + bi_new = (wl_bss_info_t *)((uintptr)bi_new + + dtoh32(bi_new->length)); + bi = bi_next; + } + } + + if (results->count == 0) { + /* Prune now empty partial scan list */ + dhd_iscan_free_buf(dhdp, iscan_cur); + goto done; + } + break; + } + bi = (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)); + } + } + iscan_cur = iscan_cur->next; + l++; + } + +done: + dhd_iscan_unlock(); + return 0; +} + +int +dhd_iscan_remove_duplicates(void * dhdp, iscan_buf_t *iscan_cur) +{ + int i = 0; + wl_iscan_results_t *list; + wl_scan_results_t *results; + wl_bss_info_t UNALIGNED *bi, *bi_new, *bi_next; + + dhd_iscan_lock(); + + DHD_ISCAN(("%s: Scan cache before delete\n", + __FUNCTION__)); + dhd_iscan_print_cache(iscan_cur); + + if (!iscan_cur) + goto done; + + list = (wl_iscan_results_t *)iscan_cur->iscan_buf; + if (!list) + goto done; + + results = (wl_scan_results_t *)&list->results; + if (!results) + goto done; + + if (results->version != WL_BSS_INFO_VERSION) { + DHD_ERROR(("%s: results->version %d != WL_BSS_INFO_VERSION\n", + __FUNCTION__, results->version)); + goto done; + } + + bi = results->bss_info; + for (i = 0; i < results->count; i++) { + if (!bi) + break; + + DHD_ISCAN(("%s: Find dups for BSS[%2.2d] %X:%X:%X:%X:%X:%X\n", + __FUNCTION__, i, bi->BSSID.octet[0], bi->BSSID.octet[1], bi->BSSID.octet[2], + bi->BSSID.octet[3], bi->BSSID.octet[4], bi->BSSID.octet[5])); + + dhd_iscan_delete_bss(dhdp, bi->BSSID.octet, iscan_cur); + + bi = (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)); + } + +done: + DHD_ISCAN(("%s: Scan cache after delete\n", __FUNCTION__)); + dhd_iscan_print_cache(iscan_cur); + dhd_iscan_unlock(); + return 0; +} + +void +dhd_iscan_ind_scan_confirm(void *dhdp, bool status) +{ + + dhd_ind_scan_confirm(dhdp, status); +} + +int +dhd_iscan_request(void * dhdp, uint16 action) +{ + int rc; + wl_iscan_params_t params; + dhd_pub_t *dhd = dhd_bus_pub(dhdp); + char buf[WLC_IOCTL_SMLEN]; + + + memset(¶ms, 0, sizeof(wl_iscan_params_t)); + memcpy(¶ms.params.bssid, ðer_bcast, ETHER_ADDR_LEN); + + params.params.bss_type = DOT11_BSSTYPE_ANY; + params.params.scan_type = DOT11_SCANTYPE_ACTIVE; + + params.params.nprobes = htod32(-1); + params.params.active_time = htod32(-1); + params.params.passive_time = htod32(-1); + params.params.home_time = htod32(-1); + params.params.channel_num = htod32(0); + + params.version = htod32(ISCAN_REQ_VERSION); + params.action = htod16(action); + params.scan_duration = htod16(0); + + bcm_mkiovar("iscan", (char *)¶ms, sizeof(wl_iscan_params_t), buf, WLC_IOCTL_SMLEN); + rc = dhd_wl_ioctl(dhdp, WLC_SET_VAR, buf, WLC_IOCTL_SMLEN); + + return rc; +} + +static int +dhd_iscan_get_partial_result(void *dhdp, uint *scan_count) +{ + wl_iscan_results_t *list_buf; + wl_iscan_results_t list; + wl_scan_results_t *results; + iscan_buf_t *iscan_cur; + int status = -1; + dhd_pub_t *dhd = dhd_bus_pub(dhdp); + int rc; + + + iscan_cur = dhd_iscan_allocate_buf(dhd, &iscan_chain); + if (!iscan_cur) { + DHD_ERROR(("%s: Failed to allocate node\n", __FUNCTION__)); + dhd_iscan_free_buf(dhdp, 0); + dhd_iscan_request(dhdp, WL_SCAN_ACTION_ABORT); + goto fail; + } + + dhd_iscan_lock(); + + memset(iscan_cur->iscan_buf, 0, WLC_IW_ISCAN_MAXLEN); + list_buf = (wl_iscan_results_t*)iscan_cur->iscan_buf; + results = &list_buf->results; + results->buflen = WL_ISCAN_RESULTS_FIXED_SIZE; + results->version = 0; + results->count = 0; + + memset(&list, 0, sizeof(list)); + list.results.buflen = htod32(WLC_IW_ISCAN_MAXLEN); + bcm_mkiovar("iscanresults", (char *)&list, WL_ISCAN_RESULTS_FIXED_SIZE, + iscan_cur->iscan_buf, WLC_IW_ISCAN_MAXLEN); + rc = dhd_wl_ioctl(dhdp, WLC_GET_VAR, iscan_cur->iscan_buf, WLC_IW_ISCAN_MAXLEN); + + results->buflen = dtoh32(results->buflen); + results->version = dtoh32(results->version); + *scan_count = results->count = dtoh32(results->count); + status = dtoh32(list_buf->status); + + dhd_iscan_unlock(); + + if (!(*scan_count)) + dhd_iscan_free_buf(dhdp, iscan_cur); + else + dhd_iscan_remove_duplicates(dhdp, iscan_cur); + + +fail: + return status; +} + +#endif + +/* + * returns = TRUE if associated, FALSE if not associated + */ +bool is_associated(dhd_pub_t *dhd, void *bss_buf) +{ + char bssid[ETHER_ADDR_LEN], zbuf[ETHER_ADDR_LEN]; + int ret = -1; + + bzero(bssid, ETHER_ADDR_LEN); + bzero(zbuf, ETHER_ADDR_LEN); + + ret = dhdcdc_set_ioctl(dhd, 0, WLC_GET_BSSID, (char *)bssid, ETHER_ADDR_LEN); + DHD_TRACE((" %s WLC_GET_BSSID ioctl res = %d\n", __FUNCTION__, ret)); + + if (ret == BCME_NOTASSOCIATED) { + DHD_TRACE(("%s: not associated! res:%d\n", __FUNCTION__, ret)); + } + + if (ret < 0) + return FALSE; + + if ((memcmp(bssid, zbuf, ETHER_ADDR_LEN) != 0)) { + /* STA is assocoated BSSID is non zero */ + + if (bss_buf) { + /* return bss if caller provided buf */ + memcpy(bss_buf, bssid, ETHER_ADDR_LEN); + } + return TRUE; + } else { + DHD_TRACE(("%s: WLC_GET_BSSID ioctl returned zero bssid\n", __FUNCTION__)); + return FALSE; + } +} + +/* Function to estimate possible DTIM_SKIP value */ +int dhd_get_dtim_skip(dhd_pub_t *dhd) +{ + int bcn_li_dtim; + char buf[128]; + int ret; + int dtim_assoc = 0; + + if ((dhd->dtim_skip == 0) || (dhd->dtim_skip == 1)) + bcn_li_dtim = 3; + else + bcn_li_dtim = dhd->dtim_skip; + + /* Read DTIM value if associated */ + memset(buf, 0, sizeof(buf)); + bcm_mkiovar("dtim_assoc", 0, 0, buf, sizeof(buf)); + if ((ret = dhdcdc_query_ioctl(dhd, 0, WLC_GET_VAR, buf, sizeof(buf))) < 0) { + DHD_ERROR(("%s failed code %d\n", __FUNCTION__, ret)); + bcn_li_dtim = 1; + goto exit; + } + else + dtim_assoc = dtoh32(*(int *)buf); + + DHD_ERROR(("%s bcn_li_dtim=%d DTIM=%d Listen=%d\n", \ + __FUNCTION__, bcn_li_dtim, dtim_assoc, LISTEN_INTERVAL)); + + /* if not assocated just eixt */ + if (dtim_assoc == 0) { + goto exit; + } + + /* check if sta listen interval fits into AP dtim */ + if (dtim_assoc > LISTEN_INTERVAL) { + /* AP DTIM to big for our Listen Interval : no dtim skiping */ + bcn_li_dtim = 1; + DHD_ERROR(("%s DTIM=%d > Listen=%d : too big ...\n", \ + __FUNCTION__, dtim_assoc, LISTEN_INTERVAL)); + goto exit; + } + + if ((bcn_li_dtim * dtim_assoc) > LISTEN_INTERVAL) { + /* Round up dtim_skip to fit into STAs Listen Interval */ + bcn_li_dtim = (int)(LISTEN_INTERVAL / dtim_assoc); + DHD_TRACE(("%s agjust dtim_skip as %d\n", __FUNCTION__, bcn_li_dtim)); + } + +exit: + return bcn_li_dtim; +} + +#ifdef PNO_SUPPORT +int dhd_pno_clean(dhd_pub_t *dhd) +{ + char iovbuf[128]; + int pfn_enabled = 0; + int iov_len = 0; + int ret; + + /* Disable pfn */ + iov_len = bcm_mkiovar("pfn", (char *)&pfn_enabled, 4, iovbuf, sizeof(iovbuf)); + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf))) >= 0) { + /* clear pfn */ + iov_len = bcm_mkiovar("pfnclear", 0, 0, iovbuf, sizeof(iovbuf)); + if (iov_len) { + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, iov_len)) < 0) { + DHD_ERROR(("%s failed code %d\n", __FUNCTION__, ret)); + } + } + else { + ret = -1; + DHD_ERROR(("%s failed code %d\n", __FUNCTION__, iov_len)); + } + } + else + DHD_ERROR(("%s failed code %d\n", __FUNCTION__, ret)); + + return ret; +} + +int dhd_pno_enable(dhd_pub_t *dhd, int pfn_enabled) +{ + char iovbuf[128]; + int ret = -1; + + if ((!dhd) && ((pfn_enabled != 0) || (pfn_enabled != 1))) { + DHD_ERROR(("%s error exit\n", __FUNCTION__)); + return ret; + } + + memset(iovbuf, 0, sizeof(iovbuf)); + + /* Check if disassoc to enable pno */ + if (pfn_enabled && (is_associated(dhd, NULL) == TRUE)) { + DHD_ERROR(("%s pno enable called in assoc mode ret=%d\n", \ + __FUNCTION__, ret)); + return ret; + } + + /* Enable/disable PNO */ + if ((ret = bcm_mkiovar("pfn", (char *)&pfn_enabled, 4, iovbuf, sizeof(iovbuf))) > 0) { + if ((ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf))) < 0) { + DHD_ERROR(("%s failed for error=%d\n", __FUNCTION__, ret)); + return ret; + } + else { + dhd->pno_enable = pfn_enabled; + DHD_TRACE(("%s set pno as %d\n", __FUNCTION__, dhd->pno_enable)); + } + } + else DHD_ERROR(("%s failed err=%d\n", __FUNCTION__, ret)); + + return ret; +} + +/* Function to execute combined scan */ +int +dhd_pno_set(dhd_pub_t *dhd, wlc_ssid_t* ssids_local, int nssid, ushort scan_fr, \ + int pno_repeat, int pno_freq_expo_max) +{ + int err = -1; + char iovbuf[128]; + int k, i; + wl_pfn_param_t pfn_param; + wl_pfn_t pfn_element; + + DHD_TRACE(("%s nssid=%d nchan=%d\n", __FUNCTION__, nssid, scan_fr)); + + if ((!dhd) && (!ssids_local)) { + DHD_ERROR(("%s error exit\n", __FUNCTION__)); + err = -1; + } + + /* Check for broadcast ssid */ + for (k = 0; k < nssid; k++) { + if (!ssids_local[k].SSID_len) { + DHD_ERROR(("%d: Broadcast SSID is ilegal for PNO setting\n", k)); + return err; + } + } +/* #define PNO_DUMP 1 */ +#ifdef PNO_DUMP + { + int j; + for (j = 0; j < nssid; j++) { + DHD_ERROR(("%d: scan for %s size =%d\n", j, + ssids_local[j].SSID, ssids_local[j].SSID_len)); + } + } +#endif /* PNO_DUMP */ + + /* clean up everything */ + if ((err = dhd_pno_clean(dhd)) < 0) { + DHD_ERROR(("%s failed error=%d\n", __FUNCTION__, err)); + return err; + } + memset(&pfn_param, 0, sizeof(pfn_param)); + memset(&pfn_element, 0, sizeof(pfn_element)); + + /* set pfn parameters */ + pfn_param.version = htod32(PFN_VERSION); + pfn_param.flags = htod16((PFN_LIST_ORDER << SORT_CRITERIA_BIT)); + + /* check and set extra pno params */ + if ((pno_repeat != 0) || (pno_freq_expo_max != 0)) { + pfn_param.flags |= htod16(ENABLE << ENABLE_ADAPTSCAN_BIT); + pfn_param.repeat_scan = htod32(pno_repeat); + pfn_param.max_freq_adjust = htod32(pno_freq_expo_max); + } + + /* set up pno scan fr */ + if (scan_fr != 0) + pfn_param.scan_freq = htod32(scan_fr); + + if (pfn_param.scan_freq > PNO_SCAN_MAX_FW_SEC) { + DHD_ERROR(("%s pno freq above %d sec\n", __FUNCTION__, PNO_SCAN_MAX_FW_SEC)); + return err; + } + if (pfn_param.scan_freq < PNO_SCAN_MIN_FW_SEC) { + DHD_ERROR(("%s pno freq less %d sec\n", __FUNCTION__, PNO_SCAN_MIN_FW_SEC)); + return err; + } + + bcm_mkiovar("pfn_set", (char *)&pfn_param, sizeof(pfn_param), iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); + + /* set all pfn ssid */ + for (i = 0; i < nssid; i++) { + + pfn_element.bss_type = htod32(DOT11_BSSTYPE_INFRASTRUCTURE); + pfn_element.auth = (DOT11_OPEN_SYSTEM); + pfn_element.infra = htod32(1); + + memcpy((char *)pfn_element.ssid.SSID, ssids_local[i].SSID, ssids_local[i].SSID_len); + pfn_element.ssid.SSID_len = ssids_local[i].SSID_len; + + if ((err = + bcm_mkiovar("pfn_add", (char *)&pfn_element, + sizeof(pfn_element), iovbuf, sizeof(iovbuf))) > 0) { + if ((err = + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf))) < 0) { + DHD_ERROR(("%s failed for i=%d error=%d\n", + __FUNCTION__, i, err)); + return err; + } + else + DHD_ERROR(("%s set OK with PNO time=%d repeat=%d max_adjust=%d\n", \ + __FUNCTION__, pfn_param.scan_freq, \ + pfn_param.repeat_scan, pfn_param.max_freq_adjust)); + } + else DHD_ERROR(("%s failed err=%d\n", __FUNCTION__, err)); + } + + /* Enable PNO */ + /* dhd_pno_enable(dhd, 1); */ + return err; +} + +int dhd_pno_get_status(dhd_pub_t *dhd) +{ + int ret = -1; + + if (!dhd) + return ret; + else + return (dhd->pno_enable); +} + +#endif /* PNO_SUPPORT */ + +#if defined(KEEP_ALIVE) +int dhd_keep_alive_onoff(dhd_pub_t *dhd, int ka_on) +{ + char buf[256]; + char *buf_ptr = buf; + wl_keep_alive_pkt_t keep_alive_pkt; + char * str; + int str_len, buf_len; + int res = 0; + int keep_alive_period = KEEP_ALIVE_PERIOD; /* in ms */ + + DHD_TRACE(("%s: ka:%d\n", __FUNCTION__, ka_on)); + + if (ka_on) { /* on suspend */ + keep_alive_pkt.period_msec = keep_alive_period; + + } else { + /* on resume, turn off keep_alive packets */ + keep_alive_pkt.period_msec = 0; + } + + /* IOC var name */ + str = "keep_alive"; + str_len = strlen(str); + strncpy(buf, str, str_len); + buf[str_len] = '\0'; + buf_len = str_len + 1; + + /* set ptr to IOCTL payload after the var name */ + buf_ptr += buf_len; /* include term Z */ + + /* copy Keep-alive attributes from local var keep_alive_pkt */ + str = NULL_PKT_STR; + keep_alive_pkt.len_bytes = strlen(str); + + memcpy(buf_ptr, &keep_alive_pkt, WL_KEEP_ALIVE_FIXED_LEN); + buf_ptr += WL_KEEP_ALIVE_FIXED_LEN; + + /* copy packet data */ + memcpy(buf_ptr, str, keep_alive_pkt.len_bytes); + buf_len += (WL_KEEP_ALIVE_FIXED_LEN + keep_alive_pkt.len_bytes); + + res = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, buf_len); + return res; +} +#endif /* defined(KEEP_ALIVE) */ + +#if defined(CSCAN) + +/* Androd ComboSCAN support */ +/* + * data parsing from ComboScan tlv list +*/ +int +wl_iw_parse_data_tlv(char** list_str, void *dst, int dst_size, const char token, + int input_size, int *bytes_left) +{ + char* str = *list_str; + uint16 short_temp; + uint32 int_temp; + + if ((list_str == NULL) || (*list_str == NULL) ||(bytes_left == NULL) || (*bytes_left < 0)) { + DHD_ERROR(("%s error paramters\n", __FUNCTION__)); + return -1; + } + + /* Clean all dest bytes */ + memset(dst, 0, dst_size); + while (*bytes_left > 0) { + + if (str[0] != token) { + DHD_TRACE(("%s NOT Type=%d get=%d left_parse=%d \n", + __FUNCTION__, token, str[0], *bytes_left)); + return -1; + } + + *bytes_left -= 1; + str += 1; + + if (input_size == 1) { + memcpy(dst, str, input_size); + } + else if (input_size == 2) { + memcpy(dst, (char *)htod16(memcpy(&short_temp, str, input_size)), + input_size); + } + else if (input_size == 4) { + memcpy(dst, (char *)htod32(memcpy(&int_temp, str, input_size)), + input_size); + } + + *bytes_left -= input_size; + str += input_size; + *list_str = str; + return 1; + } + return 1; +} + +/* + * channel list parsing from cscan tlv list +*/ +int +wl_iw_parse_channel_list_tlv(char** list_str, uint16* channel_list, + int channel_num, int *bytes_left) +{ + char* str = *list_str; + int idx = 0; + + if ((list_str == NULL) || (*list_str == NULL) ||(bytes_left == NULL) || (*bytes_left < 0)) { + DHD_ERROR(("%s error paramters\n", __FUNCTION__)); + return -1; + } + + while (*bytes_left > 0) { + + if (str[0] != CSCAN_TLV_TYPE_CHANNEL_IE) { + *list_str = str; + DHD_TRACE(("End channel=%d left_parse=%d %d\n", idx, *bytes_left, str[0])); + return idx; + } + /* Get proper CSCAN_TLV_TYPE_CHANNEL_IE */ + *bytes_left -= 1; + str += 1; + + if (str[0] == 0) { + /* All channels */ + channel_list[idx] = 0x0; + } + else { + channel_list[idx] = (uint16)str[0]; + DHD_TRACE(("%s channel=%d \n", __FUNCTION__, channel_list[idx])); + } + *bytes_left -= 1; + str += 1; + + if (idx++ > 255) { + DHD_ERROR(("%s Too many channels \n", __FUNCTION__)); + return -1; + } + } + + *list_str = str; + return idx; +} + +/* + * SSIDs list parsing from cscan tlv list + */ +int +wl_iw_parse_ssid_list_tlv(char** list_str, wlc_ssid_t* ssid, int max, int *bytes_left) +{ + char* str = *list_str; + int idx = 0; + + if ((list_str == NULL) || (*list_str == NULL) || (*bytes_left < 0)) { + DHD_ERROR(("%s error paramters\n", __FUNCTION__)); + return -1; + } + + while (*bytes_left > 0) { + + if (str[0] != CSCAN_TLV_TYPE_SSID_IE) { + *list_str = str; + DHD_TRACE(("nssid=%d left_parse=%d %d\n", idx, *bytes_left, str[0])); + return idx; + } + + /* Get proper CSCAN_TLV_TYPE_SSID_IE */ + *bytes_left -= 1; + str += 1; + + if (str[0] == 0) { + /* Broadcast SSID */ + ssid[idx].SSID_len = 0; + memset((char*)ssid[idx].SSID, 0x0, DOT11_MAX_SSID_LEN); + *bytes_left -= 1; + str += 1; + + DHD_TRACE(("BROADCAST SCAN left=%d\n", *bytes_left)); + } + else if (str[0] <= DOT11_MAX_SSID_LEN) { + /* Get proper SSID size */ + ssid[idx].SSID_len = str[0]; + *bytes_left -= 1; + str += 1; + + /* Get SSID */ + if (ssid[idx].SSID_len > *bytes_left) { + DHD_ERROR(("%s out of memory range len=%d but left=%d\n", + __FUNCTION__, ssid[idx].SSID_len, *bytes_left)); + return -1; + } + + memcpy((char*)ssid[idx].SSID, str, ssid[idx].SSID_len); + + *bytes_left -= ssid[idx].SSID_len; + str += ssid[idx].SSID_len; + + DHD_TRACE(("%s :size=%d left=%d\n", + (char*)ssid[idx].SSID, ssid[idx].SSID_len, *bytes_left)); + } + else { + DHD_ERROR(("### SSID size more that %d\n", str[0])); + return -1; + } + + if (idx++ > max) { + DHD_ERROR(("%s number of SSIDs more that %d\n", __FUNCTION__, idx)); + return -1; + } + } + + *list_str = str; + return idx; +} + +/* Parse a comma-separated list from list_str into ssid array, starting + * at index idx. Max specifies size of the ssid array. Parses ssids + * and returns updated idx; if idx >= max not all fit, the excess have + * not been copied. Returns -1 on empty string, or on ssid too long. + */ +int +wl_iw_parse_ssid_list(char** list_str, wlc_ssid_t* ssid, int idx, int max) +{ + char* str, *ptr; + + if ((list_str == NULL) || (*list_str == NULL)) + return -1; + + for (str = *list_str; str != NULL; str = ptr) { + + /* check for next TAG */ + if (!strncmp(str, GET_CHANNEL, strlen(GET_CHANNEL))) { + *list_str = str + strlen(GET_CHANNEL); + return idx; + } + + if ((ptr = strchr(str, ',')) != NULL) { + *ptr++ = '\0'; + } + + if (strlen(str) > DOT11_MAX_SSID_LEN) { + DHD_ERROR(("ssid <%s> exceeds %d\n", str, DOT11_MAX_SSID_LEN)); + return -1; + } + + if (strlen(str) == 0) + ssid[idx].SSID_len = 0; + + if (idx < max) { + strcpy((char*)ssid[idx].SSID, str); + ssid[idx].SSID_len = strlen(str); + } + idx++; + } + return idx; +} + +/* + * Parse channel list from iwpriv CSCAN + */ +int +wl_iw_parse_channel_list(char** list_str, uint16* channel_list, int channel_num) +{ + int num; + int val; + char* str; + char* endptr = NULL; + + if ((list_str == NULL)||(*list_str == NULL)) + return -1; + + str = *list_str; + num = 0; + while (strncmp(str, GET_NPROBE, strlen(GET_NPROBE))) { + val = (int)strtoul(str, &endptr, 0); + if (endptr == str) { + printf("could not parse channel number starting at" + " substring \"%s\" in list:\n%s\n", + str, *list_str); + return -1; + } + str = endptr + strspn(endptr, " ,"); + + if (num == channel_num) { + DHD_ERROR(("too many channels (more than %d) in channel list:\n%s\n", + channel_num, *list_str)); + return -1; + } + + channel_list[num++] = (uint16)val; + } + *list_str = str; + return num; +} + +#endif diff --git a/drivers/net/wireless/bcm4329/dhd_custom_gpio.c b/drivers/net/wireless/bcm4329/dhd_custom_gpio.c new file mode 100644 index 0000000000000000000000000000000000000000..4d32863e29829c855a5f7357cd622c1efe428760 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_custom_gpio.c @@ -0,0 +1,272 @@ +/* +* Customer code to add GPIO control during WLAN start/stop +* Copyright (C) 1999-2010, Broadcom Corporation +* +* Unless you and Broadcom execute a separate written software license +* agreement governing use of this software, this software is licensed to you +* under the terms of the GNU General Public License version 2 (the "GPL"), +* available at http://www.broadcom.com/licenses/GPLv2.php, with the +* following added to such license: +* +* As a special exception, the copyright holders of this software give you +* permission to link this software with independent modules, and to copy and +* distribute the resulting executable under terms of your choice, provided that +* you also meet, for each linked independent module, the terms and conditions of +* the license of that module. An independent module is a module which is not +* derived from this software. The special exception does not apply to any +* modifications of the software. +* +* Notwithstanding the above, under no circumstances may you combine this +* software in any way with any other Broadcom software provided under a license +* other than the GPL, without Broadcom's express prior written consent. +* +* $Id: dhd_custom_gpio.c,v 1.1.4.8.4.4 2011/01/20 20:23:09 Exp $ +*/ + + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#define WL_ERROR(x) printf x +#define WL_TRACE(x) + +#ifdef CUSTOMER_HW +extern void bcm_wlan_power_off(int); +extern void bcm_wlan_power_on(int); +#endif /* CUSTOMER_HW */ +#ifdef CUSTOMER_HW2 +int wifi_set_carddetect(int on); +int wifi_set_power(int on, unsigned long msec); +int wifi_get_irq_number(unsigned long *irq_flags_ptr); +int wifi_get_mac_addr(unsigned char *buf); +void *wifi_get_country_code(char *ccode); +#endif + +#if defined(OOB_INTR_ONLY) + +#if defined(BCMLXSDMMC) +extern int sdioh_mmc_irq(int irq); +#endif /* (BCMLXSDMMC) */ + +#ifdef CUSTOMER_HW3 +#include +#endif + +/* Customer specific Host GPIO defintion */ +static int dhd_oob_gpio_num = -1; /* GG 19 */ + +module_param(dhd_oob_gpio_num, int, 0644); +MODULE_PARM_DESC(dhd_oob_gpio_num, "DHD oob gpio number"); + +int dhd_customer_oob_irq_map(unsigned long *irq_flags_ptr) +{ + int host_oob_irq = 0; + +#ifdef CUSTOMER_HW2 + host_oob_irq = wifi_get_irq_number(irq_flags_ptr); + +#else /* for NOT CUSTOMER_HW2 */ +#if defined(CUSTOM_OOB_GPIO_NUM) + if (dhd_oob_gpio_num < 0) { + dhd_oob_gpio_num = CUSTOM_OOB_GPIO_NUM; + } +#endif + + if (dhd_oob_gpio_num < 0) { + WL_ERROR(("%s: ERROR customer specific Host GPIO is NOT defined \n", + __FUNCTION__)); + return (dhd_oob_gpio_num); + } + + WL_ERROR(("%s: customer specific Host GPIO number is (%d)\n", + __FUNCTION__, dhd_oob_gpio_num)); + +#if defined CUSTOMER_HW + host_oob_irq = MSM_GPIO_TO_INT(dhd_oob_gpio_num); +#elif defined CUSTOMER_HW3 + gpio_request(dhd_oob_gpio_num, "oob irq"); + host_oob_irq = gpio_to_irq(dhd_oob_gpio_num); + gpio_direction_input(dhd_oob_gpio_num); +#endif /* CUSTOMER_HW */ +#endif /* CUSTOMER_HW2 */ + + return (host_oob_irq); +} +#endif /* defined(OOB_INTR_ONLY) */ + +/* Customer function to control hw specific wlan gpios */ +void +dhd_customer_gpio_wlan_ctrl(int onoff) +{ + switch (onoff) { + case WLAN_RESET_OFF: + WL_TRACE(("%s: call customer specific GPIO to insert WLAN RESET\n", + __FUNCTION__)); +#ifdef CUSTOMER_HW + bcm_wlan_power_off(2); +#endif /* CUSTOMER_HW */ +#ifdef CUSTOMER_HW2 + wifi_set_power(0, 0); +#endif + WL_ERROR(("=========== WLAN placed in RESET ========\n")); + break; + + case WLAN_RESET_ON: + WL_TRACE(("%s: callc customer specific GPIO to remove WLAN RESET\n", + __FUNCTION__)); +#ifdef CUSTOMER_HW + bcm_wlan_power_on(2); +#endif /* CUSTOMER_HW */ +#ifdef CUSTOMER_HW2 + wifi_set_power(1, 0); +#endif + WL_ERROR(("=========== WLAN going back to live ========\n")); + break; + + case WLAN_POWER_OFF: + WL_TRACE(("%s: call customer specific GPIO to turn off WL_REG_ON\n", + __FUNCTION__)); +#ifdef CUSTOMER_HW + bcm_wlan_power_off(1); +#endif /* CUSTOMER_HW */ + break; + + case WLAN_POWER_ON: + WL_TRACE(("%s: call customer specific GPIO to turn on WL_REG_ON\n", + __FUNCTION__)); +#ifdef CUSTOMER_HW + bcm_wlan_power_on(1); + /* Lets customer power to get stable */ + OSL_DELAY(50); +#endif /* CUSTOMER_HW */ + break; + } +} + +#ifdef GET_CUSTOM_MAC_ENABLE +/* Function to get custom MAC address */ +int +dhd_custom_get_mac_address(unsigned char *buf) +{ + int ret = 0; + + WL_TRACE(("%s Enter\n", __FUNCTION__)); + if (!buf) + return -EINVAL; + + /* Customer access to MAC address stored outside of DHD driver */ +#ifdef CUSTOMER_HW2 + ret = wifi_get_mac_addr(buf); +#endif + +#ifdef EXAMPLE_GET_MAC + /* EXAMPLE code */ + { + struct ether_addr ea_example = {{0x00, 0x11, 0x22, 0x33, 0x44, 0xFF}}; + bcopy((char *)&ea_example, buf, sizeof(struct ether_addr)); + } +#endif /* EXAMPLE_GET_MAC */ + + return ret; +} +#endif /* GET_CUSTOM_MAC_ENABLE */ + +/* Customized Locale table : OPTIONAL feature */ +const struct cntry_locales_custom translate_custom_table[] = { +/* Table should be filled out based on custom platform regulatory requirement */ +#ifdef EXAMPLE_TABLE + {"", "XY", 4}, /* universal */ + {"US", "US", 69}, /* input ISO "US" to : US regrev 69 */ + {"CA", "US", 69}, /* input ISO "CA" to : US regrev 69 */ + {"EU", "EU", 5}, /* European union countries */ + {"AT", "EU", 5}, + {"BE", "EU", 5}, + {"BG", "EU", 5}, + {"CY", "EU", 5}, + {"CZ", "EU", 5}, + {"DK", "EU", 5}, + {"EE", "EU", 5}, + {"FI", "EU", 5}, + {"FR", "EU", 5}, + {"DE", "EU", 5}, + {"GR", "EU", 5}, + {"HU", "EU", 5}, + {"IE", "EU", 5}, + {"IT", "EU", 5}, + {"LV", "EU", 5}, + {"LI", "EU", 5}, + {"LT", "EU", 5}, + {"LU", "EU", 5}, + {"MT", "EU", 5}, + {"NL", "EU", 5}, + {"PL", "EU", 5}, + {"PT", "EU", 5}, + {"RO", "EU", 5}, + {"SK", "EU", 5}, + {"SI", "EU", 5}, + {"ES", "EU", 5}, + {"SE", "EU", 5}, + {"GB", "EU", 5}, /* input ISO "GB" to : EU regrev 05 */ + {"IL", "IL", 0}, + {"CH", "CH", 0}, + {"TR", "TR", 0}, + {"NO", "NO", 0}, + {"KR", "XY", 3}, + {"AU", "XY", 3}, + {"CN", "XY", 3}, /* input ISO "CN" to : XY regrev 03 */ + {"TW", "XY", 3}, + {"AR", "XY", 3}, + {"MX", "XY", 3} +#endif /* EXAMPLE_TABLE */ +}; + + +/* Customized Locale convertor +* input : ISO 3166-1 country abbreviation +* output: customized cspec +*/ +void get_customized_country_code(char *country_iso_code, wl_country_t *cspec) +{ +#ifdef CUSTOMER_HW2 + struct cntry_locales_custom *cloc_ptr; + + if (!cspec) + return; + + cloc_ptr = wifi_get_country_code(country_iso_code); + if (cloc_ptr) { + strlcpy(cspec->ccode, cloc_ptr->custom_locale, WLC_CNTRY_BUF_SZ); + cspec->rev = cloc_ptr->custom_locale_rev; + } + return; +#else + int size, i; + + size = ARRAYSIZE(translate_custom_table); + + if (cspec == 0) + return; + + if (size == 0) + return; + + for (i = 0; i < size; i++) { + if (strcmp(country_iso_code, translate_custom_table[i].iso_abbrev) == 0) { + memcpy(cspec->ccode, translate_custom_table[i].custom_locale, WLC_CNTRY_BUF_SZ); + cspec->rev = translate_custom_table[i].custom_locale_rev; + return; + } + } + memcpy(cspec->ccode, translate_custom_table[0].custom_locale, WLC_CNTRY_BUF_SZ); + cspec->rev = translate_custom_table[0].custom_locale_rev; + return; +#endif +} diff --git a/drivers/net/wireless/bcm4329/dhd_dbg.h b/drivers/net/wireless/bcm4329/dhd_dbg.h new file mode 100644 index 0000000000000000000000000000000000000000..b48c1d70f1441c8dde928f24ac7d9b3823d50d93 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_dbg.h @@ -0,0 +1,100 @@ +/* + * Debug/trace/assert driver definitions for Dongle Host Driver. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_dbg.h,v 1.5.6.2.4.2.14.10 2010/05/21 21:49:38 Exp $ + */ + +#ifndef _dhd_dbg_ +#define _dhd_dbg_ + +#ifdef DHD_DEBUG + +#define DHD_ERROR(args) do {if ((dhd_msg_level & DHD_ERROR_VAL) && (net_ratelimit())) \ + printf args;} while (0) +#define DHD_TRACE(args) do {if (dhd_msg_level & DHD_TRACE_VAL) printf args;} while (0) +#define DHD_INFO(args) do {if (dhd_msg_level & DHD_INFO_VAL) printf args;} while (0) +#define DHD_DATA(args) do {if (dhd_msg_level & DHD_DATA_VAL) printf args;} while (0) +#define DHD_CTL(args) do {if (dhd_msg_level & DHD_CTL_VAL) printf args;} while (0) +#define DHD_TIMER(args) do {if (dhd_msg_level & DHD_TIMER_VAL) printf args;} while (0) +#define DHD_HDRS(args) do {if (dhd_msg_level & DHD_HDRS_VAL) printf args;} while (0) +#define DHD_BYTES(args) do {if (dhd_msg_level & DHD_BYTES_VAL) printf args;} while (0) +#define DHD_INTR(args) do {if (dhd_msg_level & DHD_INTR_VAL) printf args;} while (0) +#define DHD_GLOM(args) do {if (dhd_msg_level & DHD_GLOM_VAL) printf args;} while (0) +#define DHD_EVENT(args) do {if (dhd_msg_level & DHD_EVENT_VAL) printf args;} while (0) +#define DHD_BTA(args) do {if (dhd_msg_level & DHD_BTA_VAL) printf args;} while (0) +#define DHD_ISCAN(args) do {if (dhd_msg_level & DHD_ISCAN_VAL) printf args;} while (0) + +#define DHD_ERROR_ON() (dhd_msg_level & DHD_ERROR_VAL) +#define DHD_TRACE_ON() (dhd_msg_level & DHD_TRACE_VAL) +#define DHD_INFO_ON() (dhd_msg_level & DHD_INFO_VAL) +#define DHD_DATA_ON() (dhd_msg_level & DHD_DATA_VAL) +#define DHD_CTL_ON() (dhd_msg_level & DHD_CTL_VAL) +#define DHD_TIMER_ON() (dhd_msg_level & DHD_TIMER_VAL) +#define DHD_HDRS_ON() (dhd_msg_level & DHD_HDRS_VAL) +#define DHD_BYTES_ON() (dhd_msg_level & DHD_BYTES_VAL) +#define DHD_INTR_ON() (dhd_msg_level & DHD_INTR_VAL) +#define DHD_GLOM_ON() (dhd_msg_level & DHD_GLOM_VAL) +#define DHD_EVENT_ON() (dhd_msg_level & DHD_EVENT_VAL) +#define DHD_BTA_ON() (dhd_msg_level & DHD_BTA_VAL) +#define DHD_ISCAN_ON() (dhd_msg_level & DHD_ISCAN_VAL) + +#else /* DHD_DEBUG */ + +#define DHD_ERROR(args) do {if (net_ratelimit()) printf args;} while (0) +#define DHD_TRACE(args) +#define DHD_INFO(args) +#define DHD_DATA(args) +#define DHD_CTL(args) +#define DHD_TIMER(args) +#define DHD_HDRS(args) +#define DHD_BYTES(args) +#define DHD_INTR(args) +#define DHD_GLOM(args) +#define DHD_EVENT(args) +#define DHD_BTA(args) +#define DHD_ISCAN(args) + +#define DHD_ERROR_ON() 0 +#define DHD_TRACE_ON() 0 +#define DHD_INFO_ON() 0 +#define DHD_DATA_ON() 0 +#define DHD_CTL_ON() 0 +#define DHD_TIMER_ON() 0 +#define DHD_HDRS_ON() 0 +#define DHD_BYTES_ON() 0 +#define DHD_INTR_ON() 0 +#define DHD_GLOM_ON() 0 +#define DHD_EVENT_ON() 0 +#define DHD_BTA_ON() 0 +#define DHD_ISCAN_ON() 0 +#endif /* DHD_DEBUG */ + +#define DHD_LOG(args) + +#define DHD_NONE(args) +extern int dhd_msg_level; + +/* Defines msg bits */ +#include + +#endif /* _dhd_dbg_ */ diff --git a/drivers/net/wireless/bcm4329/dhd_linux.c b/drivers/net/wireless/bcm4329/dhd_linux.c new file mode 100644 index 0000000000000000000000000000000000000000..5f5b418abc260336c83f394aad90179d05e2d021 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_linux.c @@ -0,0 +1,3442 @@ +/* + * Broadcom Dongle Host Driver (DHD), Linux-specific network interface + * Basically selected code segments from usb-cdc.c and usb-rndis.c + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_linux.c,v 1.65.4.9.2.12.2.104.4.40 2011/02/03 19:55:18 Exp $ + */ + +#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_HAS_WAKELOCK +#include +#endif +#ifdef CUSTOMER_HW2 +#include +#ifdef CONFIG_WIFI_CONTROL_FUNC +#include +static struct wifi_platform_data *wifi_control_data = NULL; +#endif +struct semaphore wifi_control_sem; + +static struct resource *wifi_irqres = NULL; + +int wifi_get_irq_number(unsigned long *irq_flags_ptr) +{ + if (wifi_irqres) { + *irq_flags_ptr = wifi_irqres->flags & IRQF_TRIGGER_MASK; + return (int)wifi_irqres->start; + } +#ifdef CUSTOM_OOB_GPIO_NUM + return CUSTOM_OOB_GPIO_NUM; +#else + return -1; +#endif +} + +int wifi_set_carddetect(int on) +{ + printk("%s = %d\n", __FUNCTION__, on); +#ifdef CONFIG_WIFI_CONTROL_FUNC + if (wifi_control_data && wifi_control_data->set_carddetect) { + wifi_control_data->set_carddetect(on); + } +#endif + return 0; +} + +int wifi_set_power(int on, unsigned long msec) +{ + printk("%s = %d\n", __FUNCTION__, on); +#ifdef CONFIG_WIFI_CONTROL_FUNC + if (wifi_control_data && wifi_control_data->set_power) { + wifi_control_data->set_power(on); + } +#endif + if (msec) + mdelay(msec); + return 0; +} + +int wifi_set_reset(int on, unsigned long msec) +{ + DHD_TRACE(("%s = %d\n", __FUNCTION__, on)); +#ifdef CONFIG_WIFI_CONTROL_FUNC + if (wifi_control_data && wifi_control_data->set_reset) { + wifi_control_data->set_reset(on); + } +#endif + if (msec) + mdelay(msec); + return 0; +} + +int wifi_get_mac_addr(unsigned char *buf) +{ + DHD_TRACE(("%s\n", __FUNCTION__)); + if (!buf) + return -EINVAL; +#ifdef CONFIG_WIFI_CONTROL_FUNC + if (wifi_control_data && wifi_control_data->get_mac_addr) { + return wifi_control_data->get_mac_addr(buf); + } +#endif + return -EOPNOTSUPP; +} + +void *wifi_get_country_code(char *ccode) +{ + DHD_TRACE(("%s\n", __FUNCTION__)); +#ifdef CONFIG_WIFI_CONTROL_FUNC + if (!ccode) + return NULL; + if (wifi_control_data && wifi_control_data->get_country_code) { + return wifi_control_data->get_country_code(ccode); + } +#endif + return NULL; +} + +static int wifi_probe(struct platform_device *pdev) +{ +#ifdef CONFIG_WIFI_CONTROL_FUNC + struct wifi_platform_data *wifi_ctrl = + (struct wifi_platform_data *)(pdev->dev.platform_data); + + wifi_control_data = wifi_ctrl; +#endif + + DHD_TRACE(("## %s\n", __FUNCTION__)); + wifi_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "bcm4329_wlan_irq"); + + wifi_set_power(1, 0); /* Power On */ + wifi_set_carddetect(1); /* CardDetect (0->1) */ + + up(&wifi_control_sem); + return 0; +} + +static int wifi_remove(struct platform_device *pdev) +{ +#ifdef CONFIG_WIFI_CONTROL_FUNC + struct wifi_platform_data *wifi_ctrl = + (struct wifi_platform_data *)(pdev->dev.platform_data); + + wifi_control_data = wifi_ctrl; +#endif + DHD_TRACE(("## %s\n", __FUNCTION__)); + wifi_set_power(0, 0); /* Power Off */ + wifi_set_carddetect(0); /* CardDetect (1->0) */ + + up(&wifi_control_sem); + return 0; +} + +static int wifi_suspend(struct platform_device *pdev, pm_message_t state) +{ + DHD_TRACE(("##> %s\n", __FUNCTION__)); +#if defined(OOB_INTR_ONLY) + bcmsdh_oob_intr_set(0); +#endif /* (OOB_INTR_ONLY) */ + return 0; +} +static int wifi_resume(struct platform_device *pdev) +{ + DHD_TRACE(("##> %s\n", __FUNCTION__)); +#if defined(OOB_INTR_ONLY) + bcmsdh_oob_intr_set(1); +#endif /* (OOB_INTR_ONLY) */ + return 0; +} + +static struct platform_driver wifi_device = { + .probe = wifi_probe, + .remove = wifi_remove, + .suspend = wifi_suspend, + .resume = wifi_resume, + .driver = { + .name = "bcm4329_wlan", + } +}; + +int wifi_add_dev(void) +{ + DHD_TRACE(("## Calling platform_driver_register\n")); + return platform_driver_register(&wifi_device); +} + +void wifi_del_dev(void) +{ + DHD_TRACE(("## Unregister platform_driver_register\n")); + platform_driver_unregister(&wifi_device); +} +#endif /* defined(CUSTOMER_HW2) */ + +static int dhd_device_event(struct notifier_block *this, unsigned long event, + void *ptr); + +static struct notifier_block dhd_notifier = { + .notifier_call = dhd_device_event +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) +#include +volatile bool dhd_mmc_suspend = FALSE; +DECLARE_WAIT_QUEUE_HEAD(dhd_dpc_wait); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */ + +#if defined(OOB_INTR_ONLY) +extern void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable); +#endif /* defined(OOB_INTR_ONLY) */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +MODULE_LICENSE("GPL v2"); +#endif /* LinuxVer */ + +#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) +const char * +print_tainted() +{ + return ""; +} +#endif /* LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) */ + +/* Linux wireless extension support */ +#if defined(CONFIG_WIRELESS_EXT) +#include +#endif /* defined(CONFIG_WIRELESS_EXT) */ + +extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len); + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */ + +#ifdef PKT_FILTER_SUPPORT +extern void dhd_pktfilter_offload_set(dhd_pub_t * dhd, char *arg); +extern void dhd_pktfilter_offload_enable(dhd_pub_t * dhd, char *arg, int enable, int master_mode); +#endif + +/* Interface control information */ +typedef struct dhd_if { + struct dhd_info *info; /* back pointer to dhd_info */ + /* OS/stack specifics */ + struct net_device *net; + struct net_device_stats stats; + int idx; /* iface idx in dongle */ + int state; /* interface state */ + uint subunit; /* subunit */ + uint8 mac_addr[ETHER_ADDR_LEN]; /* assigned MAC address */ + bool attached; /* Delayed attachment when unset */ + bool txflowcontrol; /* Per interface flow control indicator */ + char name[IFNAMSIZ+1]; /* linux interface name */ +} dhd_if_t; + +/* Local private structure (extension of pub) */ +typedef struct dhd_info { +#if defined(CONFIG_WIRELESS_EXT) + wl_iw_t iw; /* wireless extensions state (must be first) */ +#endif /* defined(CONFIG_WIRELESS_EXT) */ + + dhd_pub_t pub; + + /* OS/stack specifics */ + dhd_if_t *iflist[DHD_MAX_IFS]; + + struct mutex proto_sem; + wait_queue_head_t ioctl_resp_wait; + struct timer_list timer; + bool wd_timer_valid; + struct tasklet_struct tasklet; + spinlock_t sdlock; + spinlock_t txqlock; + spinlock_t dhd_lock; + + /* Thread based operation */ + bool threads_only; + struct mutex sdsem; + long watchdog_pid; + struct semaphore watchdog_sem; + struct completion watchdog_exited; + long dpc_pid; + struct semaphore dpc_sem; + struct completion dpc_exited; + + /* Wakelocks */ +#ifdef CONFIG_HAS_WAKELOCK + struct wake_lock wl_wifi; /* Wifi wakelock */ + struct wake_lock wl_rxwake; /* Wifi rx wakelock */ +#endif + spinlock_t wl_lock; + int wl_count; + int wl_packet; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)) + struct mutex wl_start_lock; /* mutex when START called to prevent any other Linux calls */ +#endif + /* Thread to issue ioctl for multicast */ + long sysioc_pid; + struct semaphore sysioc_sem; + struct completion sysioc_exited; + bool set_multicast; + bool set_macaddress; + struct ether_addr macvalue; + wait_queue_head_t ctrl_wait; + atomic_t pend_8021x_cnt; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif /* CONFIG_HAS_EARLYSUSPEND */ +} dhd_info_t; + +/* Definitions to provide path to the firmware and nvram + * example nvram_path[MOD_PARAM_PATHLEN]="/projects/wlan/nvram.txt" + */ +char firmware_path[MOD_PARAM_PATHLEN]; +char nvram_path[MOD_PARAM_PATHLEN]; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) +struct semaphore dhd_registration_sem; +#define DHD_REGISTRATION_TIMEOUT 12000 /* msec : allowed time to finished dhd registration */ +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) */ +/* load firmware and/or nvram values from the filesystem */ +module_param_string(firmware_path, firmware_path, MOD_PARAM_PATHLEN, 0); +module_param_string(nvram_path, nvram_path, MOD_PARAM_PATHLEN, 0); + +/* Error bits */ +module_param(dhd_msg_level, int, 0); + +/* Spawn a thread for system ioctls (set mac, set mcast) */ +uint dhd_sysioc = TRUE; +module_param(dhd_sysioc, uint, 0); + +/* Watchdog interval */ +uint dhd_watchdog_ms = 10; +module_param(dhd_watchdog_ms, uint, 0); + +#ifdef DHD_DEBUG +/* Console poll interval */ +uint dhd_console_ms = 0; +module_param(dhd_console_ms, uint, 0); +#endif /* DHD_DEBUG */ + +/* ARP offload agent mode : Enable ARP Host Auto-Reply and ARP Peer Auto-Reply */ +uint dhd_arp_mode = 0xb; +module_param(dhd_arp_mode, uint, 0); + +/* ARP offload enable */ +uint dhd_arp_enable = TRUE; +module_param(dhd_arp_enable, uint, 0); + +/* Global Pkt filter enable control */ +uint dhd_pkt_filter_enable = TRUE; +module_param(dhd_pkt_filter_enable, uint, 0); + +/* Pkt filter init setup */ +uint dhd_pkt_filter_init = 0; +module_param(dhd_pkt_filter_init, uint, 0); + +/* Pkt filter mode control */ +uint dhd_master_mode = TRUE; +module_param(dhd_master_mode, uint, 1); + +/* Watchdog thread priority, -1 to use kernel timer */ +int dhd_watchdog_prio = 97; +module_param(dhd_watchdog_prio, int, 0); + +/* DPC thread priority, -1 to use tasklet */ +int dhd_dpc_prio = 98; +module_param(dhd_dpc_prio, int, 0); + +/* DPC thread priority, -1 to use tasklet */ +extern int dhd_dongle_memsize; +module_param(dhd_dongle_memsize, int, 0); + +/* Control fw roaming */ +#ifdef CUSTOMER_HW2 +uint dhd_roam = 0; +#else +uint dhd_roam = 1; +#endif + +/* Control radio state */ +uint dhd_radio_up = 1; + +/* Network inteface name */ +char iface_name[IFNAMSIZ]; +module_param_string(iface_name, iface_name, IFNAMSIZ, 0); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define DAEMONIZE(a) daemonize(a); \ + allow_signal(SIGKILL); \ + allow_signal(SIGTERM); +#else /* Linux 2.4 (w/o preemption patch) */ +#define RAISE_RX_SOFTIRQ() \ + cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ) +#define DAEMONIZE(a) daemonize(); \ + do { if (a) \ + strncpy(current->comm, a, MIN(sizeof(current->comm), (strlen(a) + 1))); \ + } while (0); +#endif /* LINUX_VERSION_CODE */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define BLOCKABLE() (!in_atomic()) +#else +#define BLOCKABLE() (!in_interrupt()) +#endif + +/* The following are specific to the SDIO dongle */ + +/* IOCTL response timeout */ +int dhd_ioctl_timeout_msec = IOCTL_RESP_TIMEOUT; + +/* Idle timeout for backplane clock */ +int dhd_idletime = DHD_IDLETIME_TICKS; +module_param(dhd_idletime, int, 0); + +/* Use polling */ +uint dhd_poll = FALSE; +module_param(dhd_poll, uint, 0); + +/* Use interrupts */ +uint dhd_intr = TRUE; +module_param(dhd_intr, uint, 0); + +/* SDIO Drive Strength (in milliamps) */ +uint dhd_sdiod_drive_strength = 6; +module_param(dhd_sdiod_drive_strength, uint, 0); + +/* Tx/Rx bounds */ +extern uint dhd_txbound; +extern uint dhd_rxbound; +module_param(dhd_txbound, uint, 0); +module_param(dhd_rxbound, uint, 0); + +/* Deferred transmits */ +extern uint dhd_deferred_tx; +module_param(dhd_deferred_tx, uint, 0); + + + +#ifdef SDTEST +/* Echo packet generator (pkts/s) */ +uint dhd_pktgen = 0; +module_param(dhd_pktgen, uint, 0); + +/* Echo packet len (0 => sawtooth, max 2040) */ +uint dhd_pktgen_len = 0; +module_param(dhd_pktgen_len, uint, 0); +#endif + +/* Version string to report */ +#ifdef DHD_DEBUG +#ifndef SRCBASE +#define SRCBASE "drivers/net/wireless/bcm4329" +#endif +#define DHD_COMPILED "\nCompiled in " SRCBASE +#else +#define DHD_COMPILED +#endif + +static char dhd_version[] = "Dongle Host Driver, version " EPI_VERSION_STR +#ifdef DHD_DEBUG +"\nCompiled in " SRCBASE " on " __DATE__ " at " __TIME__ +#endif +; + + +#if defined(CONFIG_WIRELESS_EXT) +struct iw_statistics *dhd_get_wireless_stats(struct net_device *dev); +#endif /* defined(CONFIG_WIRELESS_EXT) */ + +static void dhd_dpc(ulong data); +/* forward decl */ +extern int dhd_wait_pend8021x(struct net_device *dev); + +#ifdef TOE +#ifndef BDC +#error TOE requires BDC +#endif /* !BDC */ +static int dhd_toe_get(dhd_info_t *dhd, int idx, uint32 *toe_ol); +static int dhd_toe_set(dhd_info_t *dhd, int idx, uint32 toe_ol); +#endif /* TOE */ + +static int dhd_wl_host_event(dhd_info_t *dhd, int *ifidx, void *pktdata, + wl_event_msg_t *event_ptr, void **data_ptr); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) +static int dhd_sleep_pm_callback(struct notifier_block *nfb, unsigned long action, void *ignored) +{ + int ret = NOTIFY_DONE; + + switch (action) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + dhd_mmc_suspend = TRUE; + ret = NOTIFY_OK; + break; + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + dhd_mmc_suspend = FALSE; + ret = NOTIFY_OK; + break; + } + smp_mb(); + return ret; +} + +static struct notifier_block dhd_sleep_pm_notifier = { + .notifier_call = dhd_sleep_pm_callback, + .priority = 0 +}; +extern int register_pm_notifier(struct notifier_block *nb); +extern int unregister_pm_notifier(struct notifier_block *nb); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */ + +static void dhd_set_packet_filter(int value, dhd_pub_t *dhd) +{ +#ifdef PKT_FILTER_SUPPORT + DHD_TRACE(("%s: %d\n", __FUNCTION__, value)); + /* 1 - Enable packet filter, only allow unicast packet to send up */ + /* 0 - Disable packet filter */ + if (dhd_pkt_filter_enable) { + int i; + + for (i = 0; i < dhd->pktfilter_count; i++) { + dhd_pktfilter_offload_set(dhd, dhd->pktfilter[i]); + dhd_pktfilter_offload_enable(dhd, dhd->pktfilter[i], + value, dhd_master_mode); + } + } +#endif +} + + + +#if defined(CONFIG_HAS_EARLYSUSPEND) +static int dhd_set_suspend(int value, dhd_pub_t *dhd) +{ + int power_mode = PM_MAX; + /* wl_pkt_filter_enable_t enable_parm; */ + char iovbuf[32]; + int bcn_li_dtim = 3; +#ifdef CUSTOMER_HW2 + uint roamvar = 1; +#endif /* CUSTOMER_HW2 */ + + DHD_TRACE(("%s: enter, value = %d in_suspend = %d\n", + __FUNCTION__, value, dhd->in_suspend)); + + if (dhd && dhd->up) { + if (value && dhd->in_suspend) { + + /* Kernel suspended */ + DHD_TRACE(("%s: force extra Suspend setting \n", __FUNCTION__)); + + dhdcdc_set_ioctl(dhd, 0, WLC_SET_PM, + (char *)&power_mode, sizeof(power_mode)); + + /* Enable packet filter, only allow unicast packet to send up */ + dhd_set_packet_filter(1, dhd); + + /* if dtim skip setup as default force it to wake each thrid dtim + * for better power saving. + * Note that side effect is chance to miss BC/MC packet + */ + bcn_li_dtim = dhd_get_dtim_skip(dhd); + bcm_mkiovar("bcn_li_dtim", (char *)&bcn_li_dtim, + 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); +#ifdef CUSTOMER_HW2 + /* Disable build-in roaming during suspend */ + bcm_mkiovar("roam_off", (char *)&roamvar, 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); +#endif /* CUSTOMER_HW2 */ + + } else { + + /* Kernel resumed */ + DHD_TRACE(("%s: Remove extra suspend setting \n", __FUNCTION__)); + + power_mode = PM_FAST; + dhdcdc_set_ioctl(dhd, 0, WLC_SET_PM, (char *)&power_mode, + sizeof(power_mode)); + + /* disable pkt filter */ + dhd_set_packet_filter(0, dhd); + + /* restore pre-suspend setting for dtim_skip */ + bcm_mkiovar("bcn_li_dtim", (char *)&dhd->dtim_skip, + 4, iovbuf, sizeof(iovbuf)); + + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); +#ifdef CUSTOMER_HW2 + roamvar = dhd_roam; + bcm_mkiovar("roam_off", (char *)&roamvar, 4, iovbuf, sizeof(iovbuf)); + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf)); +#endif /* CUSTOMER_HW2 */ + } + } + + return 0; +} + +static void dhd_suspend_resume_helper(struct dhd_info *dhd, int val) +{ + dhd_pub_t *dhdp = &dhd->pub; + + dhd_os_wake_lock(dhdp); + dhd_os_proto_block(dhdp); + /* Set flag when early suspend was called */ + dhdp->in_suspend = val; + if (!dhdp->suspend_disable_flag) + dhd_set_suspend(val, dhdp); + dhd_os_proto_unblock(dhdp); + dhd_os_wake_unlock(dhdp); +} + +static void dhd_early_suspend(struct early_suspend *h) +{ + struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend); + + DHD_TRACE(("%s: enter\n", __FUNCTION__)); + + if (dhd) + dhd_suspend_resume_helper(dhd, 1); +} + +static void dhd_late_resume(struct early_suspend *h) +{ + struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend); + + DHD_TRACE(("%s: enter\n", __FUNCTION__)); + + if (dhd) + dhd_suspend_resume_helper(dhd, 0); +} +#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */ + +/* + * Generalized timeout mechanism. Uses spin sleep with exponential back-off until + * the sleep time reaches one jiffy, then switches over to task delay. Usage: + * + * dhd_timeout_start(&tmo, usec); + * while (!dhd_timeout_expired(&tmo)) + * if (poll_something()) + * break; + * if (dhd_timeout_expired(&tmo)) + * fatal(); + */ + +void +dhd_timeout_start(dhd_timeout_t *tmo, uint usec) +{ + tmo->limit = usec; + tmo->increment = 0; + tmo->elapsed = 0; + tmo->tick = 1000000 / HZ; +} + +int +dhd_timeout_expired(dhd_timeout_t *tmo) +{ + /* Does nothing the first call */ + if (tmo->increment == 0) { + tmo->increment = 1; + return 0; + } + + if (tmo->elapsed >= tmo->limit) + return 1; + + /* Add the delay that's about to take place */ + tmo->elapsed += tmo->increment; + + if (tmo->increment < tmo->tick) { + OSL_DELAY(tmo->increment); + tmo->increment *= 2; + if (tmo->increment > tmo->tick) + tmo->increment = tmo->tick; + } else { + wait_queue_head_t delay_wait; + DECLARE_WAITQUEUE(wait, current); + int pending; + init_waitqueue_head(&delay_wait); + add_wait_queue(&delay_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + pending = signal_pending(current); + remove_wait_queue(&delay_wait, &wait); + set_current_state(TASK_RUNNING); + if (pending) + return 1; /* Interrupted */ + } + + return 0; +} + +static int +dhd_net2idx(dhd_info_t *dhd, struct net_device *net) +{ + int i = 0; + + ASSERT(dhd); + while (i < DHD_MAX_IFS) { + if (dhd->iflist[i] && (dhd->iflist[i]->net == net)) + return i; + i++; + } + + return DHD_BAD_IF; +} + +int +dhd_ifname2idx(dhd_info_t *dhd, char *name) +{ + int i = DHD_MAX_IFS; + + ASSERT(dhd); + + if (name == NULL || *name == '\0') + return 0; + + while (--i > 0) + if (dhd->iflist[i] && !strncmp(dhd->iflist[i]->name, name, IFNAMSIZ)) + break; + + DHD_TRACE(("%s: return idx %d for \"%s\"\n", __FUNCTION__, i, name)); + + return i; /* default - the primary interface */ +} + +char * +dhd_ifname(dhd_pub_t *dhdp, int ifidx) +{ + dhd_info_t *dhd = (dhd_info_t *)dhdp->info; + + ASSERT(dhd); + + if (ifidx < 0 || ifidx >= DHD_MAX_IFS) { + DHD_ERROR(("%s: ifidx %d out of range\n", __FUNCTION__, ifidx)); + return ""; + } + + if (dhd->iflist[ifidx] == NULL) { + DHD_ERROR(("%s: null i/f %d\n", __FUNCTION__, ifidx)); + return ""; + } + + if (dhd->iflist[ifidx]->net) + return dhd->iflist[ifidx]->net->name; + + return ""; +} + +static void +_dhd_set_multicast_list(dhd_info_t *dhd, int ifidx) +{ + struct net_device *dev; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) + struct netdev_hw_addr *ha; +#else + struct dev_mc_list *mclist; +#endif + uint32 allmulti, cnt; + + wl_ioctl_t ioc; + char *buf, *bufp; + uint buflen; + int ret; + + ASSERT(dhd && dhd->iflist[ifidx]); + dev = dhd->iflist[ifidx]->net; + + NETIF_ADDR_LOCK(dev); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) + cnt = netdev_mc_count(dev); +#else + cnt = dev->mc_count; +#endif + NETIF_ADDR_UNLOCK(dev); + + /* Determine initial value of allmulti flag */ + allmulti = (dev->flags & IFF_ALLMULTI) ? TRUE : FALSE; + + /* Send down the multicast list first. */ + buflen = sizeof("mcast_list") + sizeof(cnt) + (cnt * ETHER_ADDR_LEN); + if (!(bufp = buf = MALLOC(dhd->pub.osh, buflen))) { + DHD_ERROR(("%s: out of memory for mcast_list, cnt %d\n", + dhd_ifname(&dhd->pub, ifidx), cnt)); + return; + } + + strcpy(bufp, "mcast_list"); + bufp += strlen("mcast_list") + 1; + + cnt = htol32(cnt); + memcpy(bufp, &cnt, sizeof(cnt)); + bufp += sizeof(cnt); + + NETIF_ADDR_LOCK(dev); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) + netdev_for_each_mc_addr(ha, dev) { + if (!cnt) + break; + memcpy(bufp, ha->addr, ETHER_ADDR_LEN); + bufp += ETHER_ADDR_LEN; + cnt--; + } +#else + for (mclist = dev->mc_list; (mclist && (cnt > 0)); cnt--, mclist = mclist->next) { + memcpy(bufp, (void *)mclist->dmi_addr, ETHER_ADDR_LEN); + bufp += ETHER_ADDR_LEN; + } +#endif + NETIF_ADDR_UNLOCK(dev); + + memset(&ioc, 0, sizeof(ioc)); + ioc.cmd = WLC_SET_VAR; + ioc.buf = buf; + ioc.len = buflen; + ioc.set = TRUE; + + ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len); + if (ret < 0) { + DHD_ERROR(("%s: set mcast_list failed, cnt %d\n", + dhd_ifname(&dhd->pub, ifidx), cnt)); + allmulti = cnt ? TRUE : allmulti; + } + + MFREE(dhd->pub.osh, buf, buflen); + + /* Now send the allmulti setting. This is based on the setting in the + * net_device flags, but might be modified above to be turned on if we + * were trying to set some addresses and dongle rejected it... + */ + + buflen = sizeof("allmulti") + sizeof(allmulti); + if (!(buf = MALLOC(dhd->pub.osh, buflen))) { + DHD_ERROR(("%s: out of memory for allmulti\n", dhd_ifname(&dhd->pub, ifidx))); + return; + } + allmulti = htol32(allmulti); + + if (!bcm_mkiovar("allmulti", (void*)&allmulti, sizeof(allmulti), buf, buflen)) { + DHD_ERROR(("%s: mkiovar failed for allmulti, datalen %d buflen %u\n", + dhd_ifname(&dhd->pub, ifidx), (int)sizeof(allmulti), buflen)); + MFREE(dhd->pub.osh, buf, buflen); + return; + } + + + memset(&ioc, 0, sizeof(ioc)); + ioc.cmd = WLC_SET_VAR; + ioc.buf = buf; + ioc.len = buflen; + ioc.set = TRUE; + + ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len); + if (ret < 0) { + DHD_ERROR(("%s: set allmulti %d failed\n", + dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti))); + } + + MFREE(dhd->pub.osh, buf, buflen); + + /* Finally, pick up the PROMISC flag as well, like the NIC driver does */ + + allmulti = (dev->flags & IFF_PROMISC) ? TRUE : FALSE; + allmulti = htol32(allmulti); + + memset(&ioc, 0, sizeof(ioc)); + ioc.cmd = WLC_SET_PROMISC; + ioc.buf = &allmulti; + ioc.len = sizeof(allmulti); + ioc.set = TRUE; + + ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len); + if (ret < 0) { + DHD_ERROR(("%s: set promisc %d failed\n", + dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti))); + } +} + +static int +_dhd_set_mac_address(dhd_info_t *dhd, int ifidx, struct ether_addr *addr) +{ + char buf[32]; + wl_ioctl_t ioc; + int ret; + + DHD_TRACE(("%s enter\n", __FUNCTION__)); + if (!bcm_mkiovar("cur_etheraddr", (char*)addr, ETHER_ADDR_LEN, buf, 32)) { + DHD_ERROR(("%s: mkiovar failed for cur_etheraddr\n", dhd_ifname(&dhd->pub, ifidx))); + return -1; + } + memset(&ioc, 0, sizeof(ioc)); + ioc.cmd = WLC_SET_VAR; + ioc.buf = buf; + ioc.len = 32; + ioc.set = TRUE; + + ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len); + if (ret < 0) { + DHD_ERROR(("%s: set cur_etheraddr failed\n", dhd_ifname(&dhd->pub, ifidx))); + } else { + memcpy(dhd->iflist[ifidx]->net->dev_addr, addr, ETHER_ADDR_LEN); + } + + return ret; +} + +#ifdef SOFTAP +extern struct net_device *ap_net_dev; +/* semaphore that the soft AP CODE waits on */ +extern struct semaphore ap_eth_sema; +#endif + +static void +dhd_op_if(dhd_if_t *ifp) +{ + dhd_info_t *dhd; + int ret = 0, err = 0; +#ifdef SOFTAP + unsigned long flags; +#endif + + ASSERT(ifp && ifp->info && ifp->idx); /* Virtual interfaces only */ + + dhd = ifp->info; + + DHD_TRACE(("%s: idx %d, state %d\n", __FUNCTION__, ifp->idx, ifp->state)); + + switch (ifp->state) { + case WLC_E_IF_ADD: + /* + * Delete the existing interface before overwriting it + * in case we missed the WLC_E_IF_DEL event. + */ + if (ifp->net != NULL) { + DHD_ERROR(("%s: ERROR: netdev:%s already exists, try free & unregister \n", + __FUNCTION__, ifp->net->name)); + netif_stop_queue(ifp->net); + unregister_netdev(ifp->net); + free_netdev(ifp->net); + } + /* Allocate etherdev, including space for private structure */ + if (!(ifp->net = alloc_etherdev(sizeof(dhd)))) { + DHD_ERROR(("%s: OOM - alloc_etherdev\n", __FUNCTION__)); + ret = -ENOMEM; + } + if (ret == 0) { + strcpy(ifp->net->name, ifp->name); + memcpy(netdev_priv(ifp->net), &dhd, sizeof(dhd)); + if ((err = dhd_net_attach(&dhd->pub, ifp->idx)) != 0) { + DHD_ERROR(("%s: dhd_net_attach failed, err %d\n", + __FUNCTION__, err)); + ret = -EOPNOTSUPP; + } else { +#ifdef SOFTAP + flags = dhd_os_spin_lock(&dhd->pub); + /* save ptr to wl0.1 netdev for use in wl_iw.c */ + ap_net_dev = ifp->net; + /* signal to the SOFTAP 'sleeper' thread, wl0.1 is ready */ + up(&ap_eth_sema); + dhd_os_spin_unlock(&dhd->pub, flags); +#endif + DHD_TRACE(("\n ==== pid:%x, net_device for if:%s created ===\n\n", + current->pid, ifp->net->name)); + ifp->state = 0; + } + } + break; + case WLC_E_IF_DEL: + if (ifp->net != NULL) { + DHD_TRACE(("\n%s: got 'WLC_E_IF_DEL' state\n", __FUNCTION__)); + netif_stop_queue(ifp->net); + unregister_netdev(ifp->net); + ret = DHD_DEL_IF; /* Make sure the free_netdev() is called */ + } + break; + default: + DHD_ERROR(("%s: bad op %d\n", __FUNCTION__, ifp->state)); + ASSERT(!ifp->state); + break; + } + + if (ret < 0) { + if (ifp->net) { + free_netdev(ifp->net); + } + dhd->iflist[ifp->idx] = NULL; + MFREE(dhd->pub.osh, ifp, sizeof(*ifp)); +#ifdef SOFTAP + flags = dhd_os_spin_lock(&dhd->pub); + if (ifp->net == ap_net_dev) + ap_net_dev = NULL; /* NULL SOFTAP global as well */ + dhd_os_spin_unlock(&dhd->pub, flags); +#endif /* SOFTAP */ + } +} + +static int +_dhd_sysioc_thread(void *data) +{ + dhd_info_t *dhd = (dhd_info_t *)data; + int i; +#ifdef SOFTAP + bool in_ap = FALSE; + unsigned long flags; +#endif + + DAEMONIZE("dhd_sysioc"); + + while (down_interruptible(&dhd->sysioc_sem) == 0) { + dhd_os_start_lock(&dhd->pub); + dhd_os_wake_lock(&dhd->pub); + for (i = 0; i < DHD_MAX_IFS; i++) { + if (dhd->iflist[i]) { + DHD_TRACE(("%s: interface %d\n",__FUNCTION__, i)); +#ifdef SOFTAP + flags = dhd_os_spin_lock(&dhd->pub); + in_ap = (ap_net_dev != NULL); + dhd_os_spin_unlock(&dhd->pub, flags); +#endif /* SOFTAP */ + if (dhd->iflist[i]->state) + dhd_op_if(dhd->iflist[i]); +#ifdef SOFTAP + if (dhd->iflist[i] == NULL) { + DHD_TRACE(("%s: interface %d just been removed!\n\n", __FUNCTION__, i)); + continue; + } + + if (in_ap && dhd->set_macaddress) { + DHD_TRACE(("attempt to set MAC for %s in AP Mode blocked.\n", dhd->iflist[i]->net->name)); + dhd->set_macaddress = FALSE; + continue; + } + + if (in_ap && dhd->set_multicast) { + DHD_TRACE(("attempt to set MULTICAST list for %s in AP Mode blocked.\n", dhd->iflist[i]->net->name)); + dhd->set_multicast = FALSE; + continue; + } +#endif /* SOFTAP */ + if (dhd->set_multicast) { + dhd->set_multicast = FALSE; + _dhd_set_multicast_list(dhd, i); + } + if (dhd->set_macaddress) { + dhd->set_macaddress = FALSE; + _dhd_set_mac_address(dhd, i, &dhd->macvalue); + } + } + } + dhd_os_wake_unlock(&dhd->pub); + dhd_os_start_unlock(&dhd->pub); + } + DHD_TRACE(("%s: stopped\n",__FUNCTION__)); + complete_and_exit(&dhd->sysioc_exited, 0); +} + +static int +dhd_set_mac_address(struct net_device *dev, void *addr) +{ + int ret = 0; + + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + struct sockaddr *sa = (struct sockaddr *)addr; + int ifidx; + + DHD_TRACE(("%s: Enter\n",__FUNCTION__)); + ifidx = dhd_net2idx(dhd, dev); + if (ifidx == DHD_BAD_IF) + return -1; + + ASSERT(dhd->sysioc_pid >= 0); + memcpy(&dhd->macvalue, sa->sa_data, ETHER_ADDR_LEN); + dhd->set_macaddress = TRUE; + up(&dhd->sysioc_sem); + + return ret; +} + +static void +dhd_set_multicast_list(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ifidx; + + DHD_TRACE(("%s: Enter\n",__FUNCTION__)); + ifidx = dhd_net2idx(dhd, dev); + if (ifidx == DHD_BAD_IF) + return; + + ASSERT(dhd->sysioc_pid >= 0); + dhd->set_multicast = TRUE; + up(&dhd->sysioc_sem); +} + +int +dhd_sendpkt(dhd_pub_t *dhdp, int ifidx, void *pktbuf) +{ + int ret; + dhd_info_t *dhd = (dhd_info_t *)(dhdp->info); + + /* Reject if down */ + if (!dhdp->up || (dhdp->busstate == DHD_BUS_DOWN)) { + return -ENODEV; + } + + /* Update multicast statistic */ + if (PKTLEN(dhdp->osh, pktbuf) >= ETHER_ADDR_LEN) { + uint8 *pktdata = (uint8 *)PKTDATA(dhdp->osh, pktbuf); + struct ether_header *eh = (struct ether_header *)pktdata; + + if (ETHER_ISMULTI(eh->ether_dhost)) + dhdp->tx_multicast++; + if (ntoh16(eh->ether_type) == ETHER_TYPE_802_1X) + atomic_inc(&dhd->pend_8021x_cnt); + } + + /* Look into the packet and update the packet priority */ + if ((PKTPRIO(pktbuf) == 0)) + pktsetprio(pktbuf, FALSE); + + /* If the protocol uses a data header, apply it */ + dhd_prot_hdrpush(dhdp, ifidx, pktbuf); + + /* Use bus module to send data frame */ +#ifdef BCMDBUS + ret = dbus_send_pkt(dhdp->dbus, pktbuf, NULL /* pktinfo */); +#else + ret = dhd_bus_txdata(dhdp->bus, pktbuf); +#endif /* BCMDBUS */ + + return ret; +} + +static int +dhd_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + int ret; + void *pktbuf; + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net); + int ifidx; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + dhd_os_wake_lock(&dhd->pub); + + /* Reject if down */ + if (!dhd->pub.up || (dhd->pub.busstate == DHD_BUS_DOWN)) { + DHD_ERROR(("%s: xmit rejected pub.up=%d busstate=%d\n", + __FUNCTION__, dhd->pub.up, dhd->pub.busstate)); + netif_stop_queue(net); + /* Send Event when bus down detected during data session */ + if (dhd->pub.busstate == DHD_BUS_DOWN) { + DHD_ERROR(("%s: Event HANG send up\n", __FUNCTION__)); + net_os_send_hang_message(net); + } + dhd_os_wake_unlock(&dhd->pub); + return -ENODEV; + } + + ifidx = dhd_net2idx(dhd, net); + if (ifidx == DHD_BAD_IF) { + DHD_ERROR(("%s: bad ifidx %d\n", __FUNCTION__, ifidx)); + netif_stop_queue(net); + dhd_os_wake_unlock(&dhd->pub); + return -ENODEV; + } + + /* Make sure there's enough room for any header */ + if (skb_headroom(skb) < dhd->pub.hdrlen) { + struct sk_buff *skb2; + + DHD_INFO(("%s: insufficient headroom\n", + dhd_ifname(&dhd->pub, ifidx))); + dhd->pub.tx_realloc++; + skb2 = skb_realloc_headroom(skb, dhd->pub.hdrlen); + dev_kfree_skb(skb); + if ((skb = skb2) == NULL) { + DHD_ERROR(("%s: skb_realloc_headroom failed\n", + dhd_ifname(&dhd->pub, ifidx))); + ret = -ENOMEM; + goto done; + } + } + + /* Convert to packet */ + if (!(pktbuf = PKTFRMNATIVE(dhd->pub.osh, skb))) { + DHD_ERROR(("%s: PKTFRMNATIVE failed\n", + dhd_ifname(&dhd->pub, ifidx))); + dev_kfree_skb_any(skb); + ret = -ENOMEM; + goto done; + } + + ret = dhd_sendpkt(&dhd->pub, ifidx, pktbuf); + +done: + if (ret) + dhd->pub.dstats.tx_dropped++; + else + dhd->pub.tx_packets++; + + dhd_os_wake_unlock(&dhd->pub); + + /* Return ok: we always eat the packet */ + return 0; +} + +void +dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool state) +{ + struct net_device *net; + dhd_info_t *dhd = dhdp->info; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + dhdp->txoff = state; + ASSERT(dhd && dhd->iflist[ifidx]); + net = dhd->iflist[ifidx]->net; + if (state == ON) + netif_stop_queue(net); + else + netif_wake_queue(net); +} + +void +dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *pktbuf, int numpkt) +{ + dhd_info_t *dhd = (dhd_info_t *)dhdp->info; + struct sk_buff *skb; + uchar *eth; + uint len; + void * data, *pnext, *save_pktbuf; + int i; + dhd_if_t *ifp; + wl_event_msg_t event; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + save_pktbuf = pktbuf; + + for (i = 0; pktbuf && i < numpkt; i++, pktbuf = pnext) { + + pnext = PKTNEXT(dhdp->osh, pktbuf); + PKTSETNEXT(wl->sh.osh, pktbuf, NULL); + + + skb = PKTTONATIVE(dhdp->osh, pktbuf); + + /* Get the protocol, maintain skb around eth_type_trans() + * The main reason for this hack is for the limitation of + * Linux 2.4 where 'eth_type_trans' uses the 'net->hard_header_len' + * to perform skb_pull inside vs ETH_HLEN. Since to avoid + * coping of the packet coming from the network stack to add + * BDC, Hardware header etc, during network interface registration + * we set the 'net->hard_header_len' to ETH_HLEN + extra space required + * for BDC, Hardware header etc. and not just the ETH_HLEN + */ + eth = skb->data; + len = skb->len; + + ifp = dhd->iflist[ifidx]; + if (ifp == NULL) + ifp = dhd->iflist[0]; + + ASSERT(ifp); + skb->dev = ifp->net; + skb->protocol = eth_type_trans(skb, skb->dev); + + if (skb->pkt_type == PACKET_MULTICAST) { + dhd->pub.rx_multicast++; + } + + skb->data = eth; + skb->len = len; + + /* Strip header, count, deliver upward */ + skb_pull(skb, ETH_HLEN); + + /* Process special event packets and then discard them */ + if (ntoh16(skb->protocol) == ETHER_TYPE_BRCM) + dhd_wl_host_event(dhd, &ifidx, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + skb->mac_header, +#else + skb->mac.raw, +#endif + &event, + &data); + + ASSERT(ifidx < DHD_MAX_IFS && dhd->iflist[ifidx]); + if (dhd->iflist[ifidx] && !dhd->iflist[ifidx]->state) + ifp = dhd->iflist[ifidx]; + + if (ifp->net) + ifp->net->last_rx = jiffies; + + dhdp->dstats.rx_bytes += skb->len; + dhdp->rx_packets++; /* Local count */ + + if (in_interrupt()) { + netif_rx(skb); + } else { + /* If the receive is not processed inside an ISR, + * the softirqd must be woken explicitly to service + * the NET_RX_SOFTIRQ. In 2.6 kernels, this is handled + * by netif_rx_ni(), but in earlier kernels, we need + * to do it manually. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + netif_rx_ni(skb); +#else + ulong flags; + netif_rx(skb); + local_irq_save(flags); + RAISE_RX_SOFTIRQ(); + local_irq_restore(flags); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) */ + } + } + dhd_os_wake_lock_timeout_enable(dhdp); +} + +void +dhd_event(struct dhd_info *dhd, char *evpkt, int evlen, int ifidx) +{ + /* Linux version has nothing to do */ + return; +} + +void +dhd_txcomplete(dhd_pub_t *dhdp, void *txp, bool success) +{ + uint ifidx; + dhd_info_t *dhd = (dhd_info_t *)(dhdp->info); + struct ether_header *eh; + uint16 type; + + dhd_prot_hdrpull(dhdp, &ifidx, txp); + + eh = (struct ether_header *)PKTDATA(dhdp->osh, txp); + type = ntoh16(eh->ether_type); + + if (type == ETHER_TYPE_802_1X) + atomic_dec(&dhd->pend_8021x_cnt); + +} + +static struct net_device_stats * +dhd_get_stats(struct net_device *net) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net); + dhd_if_t *ifp; + int ifidx; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + ifidx = dhd_net2idx(dhd, net); + if (ifidx == DHD_BAD_IF) + return NULL; + + ifp = dhd->iflist[ifidx]; + ASSERT(dhd && ifp); + + if (dhd->pub.up) { + /* Use the protocol to get dongle stats */ + dhd_prot_dstats(&dhd->pub); + } + + /* Copy dongle stats to net device stats */ + ifp->stats.rx_packets = dhd->pub.dstats.rx_packets; + ifp->stats.tx_packets = dhd->pub.dstats.tx_packets; + ifp->stats.rx_bytes = dhd->pub.dstats.rx_bytes; + ifp->stats.tx_bytes = dhd->pub.dstats.tx_bytes; + ifp->stats.rx_errors = dhd->pub.dstats.rx_errors; + ifp->stats.tx_errors = dhd->pub.dstats.tx_errors; + ifp->stats.rx_dropped = dhd->pub.dstats.rx_dropped; + ifp->stats.tx_dropped = dhd->pub.dstats.tx_dropped; + ifp->stats.multicast = dhd->pub.dstats.multicast; + + return &ifp->stats; +} + +static int +dhd_watchdog_thread(void *data) +{ + dhd_info_t *dhd = (dhd_info_t *)data; + + /* This thread doesn't need any user-level access, + * so get rid of all our resources + */ +#ifdef DHD_SCHED + if (dhd_watchdog_prio > 0) { + struct sched_param param; + param.sched_priority = (dhd_watchdog_prio < MAX_RT_PRIO)? + dhd_watchdog_prio:(MAX_RT_PRIO-1); + setScheduler(current, SCHED_FIFO, ¶m); + } +#endif /* DHD_SCHED */ + + DAEMONIZE("dhd_watchdog"); + + /* Run until signal received */ + while (1) { + if (down_interruptible (&dhd->watchdog_sem) == 0) { + dhd_os_sdlock(&dhd->pub); + if (dhd->pub.dongle_reset == FALSE) { + DHD_TIMER(("%s:\n", __FUNCTION__)); + /* Call the bus module watchdog */ + dhd_bus_watchdog(&dhd->pub); + + /* Count the tick for reference */ + dhd->pub.tickcnt++; + + /* Reschedule the watchdog */ + if (dhd->wd_timer_valid) + mod_timer(&dhd->timer, jiffies + dhd_watchdog_ms * HZ / 1000); + } + dhd_os_sdunlock(&dhd->pub); + dhd_os_wake_unlock(&dhd->pub); + } else { + break; + } + } + + complete_and_exit(&dhd->watchdog_exited, 0); +} + +static void +dhd_watchdog(ulong data) +{ + dhd_info_t *dhd = (dhd_info_t *)data; + + dhd_os_wake_lock(&dhd->pub); + if (dhd->pub.dongle_reset) { + dhd_os_wake_unlock(&dhd->pub); + return; + } + + if (dhd->watchdog_pid >= 0) { + up(&dhd->watchdog_sem); + return; + } + + dhd_os_sdlock(&dhd->pub); + /* Call the bus module watchdog */ + dhd_bus_watchdog(&dhd->pub); + + /* Count the tick for reference */ + dhd->pub.tickcnt++; + + /* Reschedule the watchdog */ + if (dhd->wd_timer_valid) + mod_timer(&dhd->timer, jiffies + dhd_watchdog_ms * HZ / 1000); + dhd_os_sdunlock(&dhd->pub); + dhd_os_wake_unlock(&dhd->pub); +} + +static int +dhd_dpc_thread(void *data) +{ + dhd_info_t *dhd = (dhd_info_t *)data; + + /* This thread doesn't need any user-level access, + * so get rid of all our resources + */ +#ifdef DHD_SCHED + if (dhd_dpc_prio > 0) + { + struct sched_param param; + param.sched_priority = (dhd_dpc_prio < MAX_RT_PRIO)?dhd_dpc_prio:(MAX_RT_PRIO-1); + setScheduler(current, SCHED_FIFO, ¶m); + } +#endif /* DHD_SCHED */ + + DAEMONIZE("dhd_dpc"); + + /* Run until signal received */ + while (1) { + if (down_interruptible(&dhd->dpc_sem) == 0) { + /* Call bus dpc unless it indicated down (then clean stop) */ + if (dhd->pub.busstate != DHD_BUS_DOWN) { + if (dhd_bus_dpc(dhd->pub.bus)) { + up(&dhd->dpc_sem); + } + else { + dhd_os_wake_unlock(&dhd->pub); + } + } else { + if (dhd->pub.up) + dhd_bus_stop(dhd->pub.bus, TRUE); + dhd_os_wake_unlock(&dhd->pub); + } + } + else + break; + } + + complete_and_exit(&dhd->dpc_exited, 0); +} + +static void +dhd_dpc(ulong data) +{ + dhd_info_t *dhd; + + dhd = (dhd_info_t *)data; + + /* Call bus dpc unless it indicated down (then clean stop) */ + if (dhd->pub.busstate != DHD_BUS_DOWN) { + if (dhd_bus_dpc(dhd->pub.bus)) + tasklet_schedule(&dhd->tasklet); + } else { + dhd_bus_stop(dhd->pub.bus, TRUE); + } +} + +void +dhd_sched_dpc(dhd_pub_t *dhdp) +{ + dhd_info_t *dhd = (dhd_info_t *)dhdp->info; + + dhd_os_wake_lock(dhdp); + if (dhd->dpc_pid >= 0) { + up(&dhd->dpc_sem); + return; + } + + tasklet_schedule(&dhd->tasklet); +} + +#ifdef TOE +/* Retrieve current toe component enables, which are kept as a bitmap in toe_ol iovar */ +static int +dhd_toe_get(dhd_info_t *dhd, int ifidx, uint32 *toe_ol) +{ + wl_ioctl_t ioc; + char buf[32]; + int ret; + + memset(&ioc, 0, sizeof(ioc)); + + ioc.cmd = WLC_GET_VAR; + ioc.buf = buf; + ioc.len = (uint)sizeof(buf); + ioc.set = FALSE; + + strcpy(buf, "toe_ol"); + if ((ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 0) { + /* Check for older dongle image that doesn't support toe_ol */ + if (ret == -EIO) { + DHD_ERROR(("%s: toe not supported by device\n", + dhd_ifname(&dhd->pub, ifidx))); + return -EOPNOTSUPP; + } + + DHD_INFO(("%s: could not get toe_ol: ret=%d\n", dhd_ifname(&dhd->pub, ifidx), ret)); + return ret; + } + + memcpy(toe_ol, buf, sizeof(uint32)); + return 0; +} + +/* Set current toe component enables in toe_ol iovar, and set toe global enable iovar */ +static int +dhd_toe_set(dhd_info_t *dhd, int ifidx, uint32 toe_ol) +{ + wl_ioctl_t ioc; + char buf[32]; + int toe, ret; + + memset(&ioc, 0, sizeof(ioc)); + + ioc.cmd = WLC_SET_VAR; + ioc.buf = buf; + ioc.len = (uint)sizeof(buf); + ioc.set = TRUE; + + /* Set toe_ol as requested */ + + strcpy(buf, "toe_ol"); + memcpy(&buf[sizeof("toe_ol")], &toe_ol, sizeof(uint32)); + + if ((ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 0) { + DHD_ERROR(("%s: could not set toe_ol: ret=%d\n", + dhd_ifname(&dhd->pub, ifidx), ret)); + return ret; + } + + /* Enable toe globally only if any components are enabled. */ + + toe = (toe_ol != 0); + + strcpy(buf, "toe"); + memcpy(&buf[sizeof("toe")], &toe, sizeof(uint32)); + + if ((ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 0) { + DHD_ERROR(("%s: could not set toe: ret=%d\n", dhd_ifname(&dhd->pub, ifidx), ret)); + return ret; + } + + return 0; +} +#endif /* TOE */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) +static void dhd_ethtool_get_drvinfo(struct net_device *net, + struct ethtool_drvinfo *info) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net); + + sprintf(info->driver, "wl"); + sprintf(info->version, "%lu", dhd->pub.drv_version); +} + +struct ethtool_ops dhd_ethtool_ops = { + .get_drvinfo = dhd_ethtool_get_drvinfo +}; +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) */ + + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 4, 2) +static int +dhd_ethtool(dhd_info_t *dhd, void *uaddr) +{ + struct ethtool_drvinfo info; + char drvname[sizeof(info.driver)]; + uint32 cmd; +#ifdef TOE + struct ethtool_value edata; + uint32 toe_cmpnt, csum_dir; + int ret; +#endif + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* all ethtool calls start with a cmd word */ + if (copy_from_user(&cmd, uaddr, sizeof (uint32))) + return -EFAULT; + + switch (cmd) { + case ETHTOOL_GDRVINFO: + /* Copy out any request driver name */ + if (copy_from_user(&info, uaddr, sizeof(info))) + return -EFAULT; + strncpy(drvname, info.driver, sizeof(info.driver)); + drvname[sizeof(info.driver)-1] = '\0'; + + /* clear struct for return */ + memset(&info, 0, sizeof(info)); + info.cmd = cmd; + + /* if dhd requested, identify ourselves */ + if (strcmp(drvname, "?dhd") == 0) { + sprintf(info.driver, "dhd"); + strcpy(info.version, EPI_VERSION_STR); + } + + /* otherwise, require dongle to be up */ + else if (!dhd->pub.up) { + DHD_ERROR(("%s: dongle is not up\n", __FUNCTION__)); + return -ENODEV; + } + + /* finally, report dongle driver type */ + else if (dhd->pub.iswl) + sprintf(info.driver, "wl"); + else + sprintf(info.driver, "xx"); + + sprintf(info.version, "%lu", dhd->pub.drv_version); + if (copy_to_user(uaddr, &info, sizeof(info))) + return -EFAULT; + DHD_CTL(("%s: given %*s, returning %s\n", __FUNCTION__, + (int)sizeof(drvname), drvname, info.driver)); + break; + +#ifdef TOE + /* Get toe offload components from dongle */ + case ETHTOOL_GRXCSUM: + case ETHTOOL_GTXCSUM: + if ((ret = dhd_toe_get(dhd, 0, &toe_cmpnt)) < 0) + return ret; + + csum_dir = (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + edata.cmd = cmd; + edata.data = (toe_cmpnt & csum_dir) ? 1 : 0; + + if (copy_to_user(uaddr, &edata, sizeof(edata))) + return -EFAULT; + break; + + /* Set toe offload components in dongle */ + case ETHTOOL_SRXCSUM: + case ETHTOOL_STXCSUM: + if (copy_from_user(&edata, uaddr, sizeof(edata))) + return -EFAULT; + + /* Read the current settings, update and write back */ + if ((ret = dhd_toe_get(dhd, 0, &toe_cmpnt)) < 0) + return ret; + + csum_dir = (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + if (edata.data != 0) + toe_cmpnt |= csum_dir; + else + toe_cmpnt &= ~csum_dir; + + if ((ret = dhd_toe_set(dhd, 0, toe_cmpnt)) < 0) + return ret; + + /* If setting TX checksum mode, tell Linux the new mode */ + if (cmd == ETHTOOL_STXCSUM) { + if (edata.data) + dhd->iflist[0]->net->features |= NETIF_F_IP_CSUM; + else + dhd->iflist[0]->net->features &= ~NETIF_F_IP_CSUM; + } + + break; +#endif /* TOE */ + + default: + return -EOPNOTSUPP; + } + + return 0; +} +#endif /* LINUX_VERSION_CODE > KERNEL_VERSION(2, 4, 2) */ + +static int +dhd_ioctl_entry(struct net_device *net, struct ifreq *ifr, int cmd) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net); + dhd_ioctl_t ioc; + int bcmerror = 0; + int buflen = 0; + void *buf = NULL; + uint driver = 0; + int ifidx; + bool is_set_key_cmd; + int ret; + + dhd_os_wake_lock(&dhd->pub); + + /* send to dongle only if we are not waiting for reload already */ + if (dhd->pub.hang_was_sent) { + DHD_ERROR(("%s: HANG was sent up earlier\n", __FUNCTION__)); + dhd_os_wake_lock_timeout_enable(&dhd->pub); + dhd_os_wake_unlock(&dhd->pub); + return OSL_ERROR(BCME_DONGLE_DOWN); + } + + ifidx = dhd_net2idx(dhd, net); + DHD_TRACE(("%s: ifidx %d, cmd 0x%04x\n", __FUNCTION__, ifidx, cmd)); + + if (ifidx == DHD_BAD_IF) { + dhd_os_wake_unlock(&dhd->pub); + return -1; + } + +#if defined(CONFIG_WIRELESS_EXT) + /* linux wireless extensions */ + if ((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST)) { + /* may recurse, do NOT lock */ + ret = wl_iw_ioctl(net, ifr, cmd); + dhd_os_wake_unlock(&dhd->pub); + return ret; + } +#endif /* defined(CONFIG_WIRELESS_EXT) */ + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 4, 2) + if (cmd == SIOCETHTOOL) { + ret = dhd_ethtool(dhd, (void*)ifr->ifr_data); + dhd_os_wake_unlock(&dhd->pub); + return ret; + } +#endif /* LINUX_VERSION_CODE > KERNEL_VERSION(2, 4, 2) */ + + if (cmd != SIOCDEVPRIVATE) { + dhd_os_wake_unlock(&dhd->pub); + return -EOPNOTSUPP; + } + + memset(&ioc, 0, sizeof(ioc)); + + /* Copy the ioc control structure part of ioctl request */ + if (copy_from_user(&ioc, ifr->ifr_data, sizeof(wl_ioctl_t))) { + bcmerror = -BCME_BADADDR; + goto done; + } + + /* Copy out any buffer passed */ + if (ioc.buf) { + buflen = MIN(ioc.len, DHD_IOCTL_MAXLEN); + /* optimization for direct ioctl calls from kernel */ + /* + if (segment_eq(get_fs(), KERNEL_DS)) { + buf = ioc.buf; + } else { + */ + { + if (!(buf = (char*)MALLOC(dhd->pub.osh, buflen))) { + bcmerror = -BCME_NOMEM; + goto done; + } + if (copy_from_user(buf, ioc.buf, buflen)) { + bcmerror = -BCME_BADADDR; + goto done; + } + } + } + + /* To differentiate between wl and dhd read 4 more byes */ + if ((copy_from_user(&driver, (char *)ifr->ifr_data + sizeof(wl_ioctl_t), + sizeof(uint)) != 0)) { + bcmerror = -BCME_BADADDR; + goto done; + } + + if (!capable(CAP_NET_ADMIN)) { + bcmerror = -BCME_EPERM; + goto done; + } + + /* check for local dhd ioctl and handle it */ + if (driver == DHD_IOCTL_MAGIC) { + bcmerror = dhd_ioctl((void *)&dhd->pub, &ioc, buf, buflen); + if (bcmerror) + dhd->pub.bcmerror = bcmerror; + goto done; + } + + /* send to dongle (must be up, and wl) */ + if (dhd->pub.busstate != DHD_BUS_DATA) { + DHD_ERROR(("%s DONGLE_DOWN\n", __FUNCTION__)); + bcmerror = BCME_DONGLE_DOWN; + goto done; + } + + if (!dhd->pub.iswl) { + bcmerror = BCME_DONGLE_DOWN; + goto done; + } + + /* Intercept WLC_SET_KEY IOCTL - serialize M4 send and set key IOCTL to + * prevent M4 encryption. + */ + is_set_key_cmd = ((ioc.cmd == WLC_SET_KEY) || + ((ioc.cmd == WLC_SET_VAR) && + !(strncmp("wsec_key", ioc.buf, 9))) || + ((ioc.cmd == WLC_SET_VAR) && + !(strncmp("bsscfg:wsec_key", ioc.buf, 15)))); + if (is_set_key_cmd) { + dhd_wait_pend8021x(net); + } + + bcmerror = dhd_prot_ioctl(&dhd->pub, ifidx, (wl_ioctl_t *)&ioc, buf, buflen); + +done: + if ((bcmerror == -ETIMEDOUT) || ((dhd->pub.busstate == DHD_BUS_DOWN) && + (!dhd->pub.dongle_reset))) { + DHD_ERROR(("%s: Event HANG send up\n", __FUNCTION__)); + net_os_send_hang_message(net); + } + + if (!bcmerror && buf && ioc.buf) { + if (copy_to_user(ioc.buf, buf, buflen)) + bcmerror = -EFAULT; + } + + if (buf) + MFREE(dhd->pub.osh, buf, buflen); + + dhd_os_wake_unlock(&dhd->pub); + + return OSL_ERROR(bcmerror); +} + +static int +dhd_stop(struct net_device *net) +{ +#if !defined(IGNORE_ETH0_DOWN) + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net); + + DHD_TRACE(("%s: Enter %s\n", __FUNCTION__, net->name)); + if (dhd->pub.up == 0) { + return 0; + } + + /* Set state and stop OS transmissions */ + dhd->pub.up = 0; + netif_stop_queue(net); +#else + DHD_ERROR(("BYPASS %s:due to BRCM compilation : under investigation ...\n", __FUNCTION__)); +#endif /* !defined(IGNORE_ETH0_DOWN) */ + dhd->pub.hang_was_sent = 0; + OLD_MOD_DEC_USE_COUNT; + return 0; +} + +static int +dhd_open(struct net_device *net) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net); +#ifdef TOE + uint32 toe_ol; +#endif + int ifidx; + + /* Force start if ifconfig_up gets called before START command */ + wl_control_wl_start(net); + + ifidx = dhd_net2idx(dhd, net); + DHD_TRACE(("%s: ifidx %d\n", __FUNCTION__, ifidx)); + + if (ifidx == DHD_BAD_IF) + return -1; + + if ((dhd->iflist[ifidx]) && (dhd->iflist[ifidx]->state == WLC_E_IF_DEL)) { + DHD_ERROR(("%s: Error: called when IF already deleted\n", __FUNCTION__)); + return -1; + } + + if (ifidx == 0) { /* do it only for primary eth0 */ + + atomic_set(&dhd->pend_8021x_cnt, 0); + + memcpy(net->dev_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN); + +#ifdef TOE + /* Get current TOE mode from dongle */ + if (dhd_toe_get(dhd, ifidx, &toe_ol) >= 0 && (toe_ol & TOE_TX_CSUM_OL) != 0) + dhd->iflist[ifidx]->net->features |= NETIF_F_IP_CSUM; + else + dhd->iflist[ifidx]->net->features &= ~NETIF_F_IP_CSUM; +#endif + } + /* Allow transmit calls */ + netif_start_queue(net); + dhd->pub.up = 1; + + OLD_MOD_INC_USE_COUNT; + return 0; +} + +osl_t * +dhd_osl_attach(void *pdev, uint bustype) +{ + return osl_attach(pdev, bustype, TRUE); +} + +void +dhd_osl_detach(osl_t *osh) +{ + if (MALLOCED(osh)) { + DHD_ERROR(("%s: MEMORY LEAK %d bytes\n", __FUNCTION__, MALLOCED(osh))); + } + osl_detach(osh); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && 1 + up(&dhd_registration_sem); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) */ +} + +int +dhd_add_if(dhd_info_t *dhd, int ifidx, void *handle, char *name, + uint8 *mac_addr, uint32 flags, uint8 bssidx) +{ + dhd_if_t *ifp; + + DHD_TRACE(("%s: idx %d, handle->%p\n", __FUNCTION__, ifidx, handle)); + + ASSERT(dhd && (ifidx < DHD_MAX_IFS)); + + ifp = dhd->iflist[ifidx]; + if (!ifp && !(ifp = MALLOC(dhd->pub.osh, sizeof(dhd_if_t)))) { + DHD_ERROR(("%s: OOM - dhd_if_t\n", __FUNCTION__)); + return -ENOMEM; + } + + memset(ifp, 0, sizeof(dhd_if_t)); + ifp->info = dhd; + dhd->iflist[ifidx] = ifp; + strncpy(ifp->name, name, IFNAMSIZ); + ifp->name[IFNAMSIZ] = '\0'; + if (mac_addr != NULL) + memcpy(&ifp->mac_addr, mac_addr, ETHER_ADDR_LEN); + + if (handle == NULL) { + ifp->state = WLC_E_IF_ADD; + ifp->idx = ifidx; + ASSERT(dhd->sysioc_pid >= 0); + up(&dhd->sysioc_sem); + } else + ifp->net = (struct net_device *)handle; + + return 0; +} + +void +dhd_del_if(dhd_info_t *dhd, int ifidx) +{ + dhd_if_t *ifp; + + DHD_TRACE(("%s: idx %d\n", __FUNCTION__, ifidx)); + + ASSERT(dhd && ifidx && (ifidx < DHD_MAX_IFS)); + ifp = dhd->iflist[ifidx]; + if (!ifp) { + DHD_ERROR(("%s: Null interface\n", __FUNCTION__)); + return; + } + + ifp->state = WLC_E_IF_DEL; + ifp->idx = ifidx; + ASSERT(dhd->sysioc_pid >= 0); + up(&dhd->sysioc_sem); +} + + +dhd_pub_t * +dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen) +{ + dhd_info_t *dhd = NULL; + struct net_device *net; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + /* updates firmware nvram path if it was provided as module paramters */ + if ((firmware_path != NULL) && (firmware_path[0] != '\0')) + strcpy(fw_path, firmware_path); + if ((nvram_path != NULL) && (nvram_path[0] != '\0')) + strcpy(nv_path, nvram_path); + + /* Allocate etherdev, including space for private structure */ + if (!(net = alloc_etherdev(sizeof(dhd)))) { + DHD_ERROR(("%s: OOM - alloc_etherdev\n", __FUNCTION__)); + goto fail; + } + + /* Allocate primary dhd_info */ + if (!(dhd = MALLOC(osh, sizeof(dhd_info_t)))) { + DHD_ERROR(("%s: OOM - alloc dhd_info\n", __FUNCTION__)); + goto fail; + } + + memset(dhd, 0, sizeof(dhd_info_t)); + + /* + * Save the dhd_info into the priv + */ + memcpy(netdev_priv(net), &dhd, sizeof(dhd)); + dhd->pub.osh = osh; + + /* Set network interface name if it was provided as module parameter */ + if (iface_name[0]) { + int len; + char ch; + strncpy(net->name, iface_name, IFNAMSIZ); + net->name[IFNAMSIZ - 1] = 0; + len = strlen(net->name); + ch = net->name[len - 1]; + if ((ch > '9' || ch < '0') && (len < IFNAMSIZ - 2)) + strcat(net->name, "%d"); + } + + if (dhd_add_if(dhd, 0, (void *)net, net->name, NULL, 0, 0) == DHD_BAD_IF) + goto fail; + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 31)) + net->open = NULL; +#else + net->netdev_ops = NULL; +#endif + + mutex_init(&dhd->proto_sem); + /* Initialize other structure content */ + init_waitqueue_head(&dhd->ioctl_resp_wait); + init_waitqueue_head(&dhd->ctrl_wait); + + /* Initialize the spinlocks */ + spin_lock_init(&dhd->sdlock); + spin_lock_init(&dhd->txqlock); + spin_lock_init(&dhd->dhd_lock); + + /* Initialize Wakelock stuff */ + spin_lock_init(&dhd->wl_lock); + dhd->wl_count = 0; + dhd->wl_packet = 0; +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&dhd->wl_wifi, WAKE_LOCK_SUSPEND, "wlan_wake"); + wake_lock_init(&dhd->wl_rxwake, WAKE_LOCK_SUSPEND, "wlan_rx_wake"); +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)) + mutex_init(&dhd->wl_start_lock); +#endif + /* Link to info module */ + dhd->pub.info = dhd; + + /* Link to bus module */ + dhd->pub.bus = bus; + dhd->pub.hdrlen = bus_hdrlen; + + /* Attach and link in the protocol */ + if (dhd_prot_attach(&dhd->pub) != 0) { + DHD_ERROR(("dhd_prot_attach failed\n")); + goto fail; + } +#if defined(CONFIG_WIRELESS_EXT) + /* Attach and link in the iw */ + if (wl_iw_attach(net, (void *)&dhd->pub) != 0) { + DHD_ERROR(("wl_iw_attach failed\n")); + goto fail; + } +#endif /* defined(CONFIG_WIRELESS_EXT) */ + + /* Set up the watchdog timer */ + init_timer(&dhd->timer); + dhd->timer.data = (ulong)dhd; + dhd->timer.function = dhd_watchdog; + + /* Initialize thread based operation and lock */ + mutex_init(&dhd->sdsem); + if ((dhd_watchdog_prio >= 0) && (dhd_dpc_prio >= 0)) { + dhd->threads_only = TRUE; + } + else { + dhd->threads_only = FALSE; + } + + if (dhd_dpc_prio >= 0) { + /* Initialize watchdog thread */ + sema_init(&dhd->watchdog_sem, 0); + init_completion(&dhd->watchdog_exited); + dhd->watchdog_pid = kernel_thread(dhd_watchdog_thread, dhd, 0); + } else { + dhd->watchdog_pid = -1; + } + + /* Set up the bottom half handler */ + if (dhd_dpc_prio >= 0) { + /* Initialize DPC thread */ + sema_init(&dhd->dpc_sem, 0); + init_completion(&dhd->dpc_exited); + dhd->dpc_pid = kernel_thread(dhd_dpc_thread, dhd, 0); + } else { + tasklet_init(&dhd->tasklet, dhd_dpc, (ulong)dhd); + dhd->dpc_pid = -1; + } + + if (dhd_sysioc) { + sema_init(&dhd->sysioc_sem, 0); + init_completion(&dhd->sysioc_exited); + dhd->sysioc_pid = kernel_thread(_dhd_sysioc_thread, dhd, 0); + } else { + dhd->sysioc_pid = -1; + } + + /* + * Save the dhd_info into the priv + */ + memcpy(netdev_priv(net), &dhd, sizeof(dhd)); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) + register_pm_notifier(&dhd_sleep_pm_notifier); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */ + +#ifdef CONFIG_HAS_EARLYSUSPEND + dhd->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20; + dhd->early_suspend.suspend = dhd_early_suspend; + dhd->early_suspend.resume = dhd_late_resume; + register_early_suspend(&dhd->early_suspend); +#endif + + register_inetaddr_notifier(&dhd_notifier); + + return &dhd->pub; + +fail: + if (net) + free_netdev(net); + if (dhd) + dhd_detach(&dhd->pub); + + return NULL; +} + + +int +dhd_bus_start(dhd_pub_t *dhdp) +{ + int ret = -1; + dhd_info_t *dhd = (dhd_info_t*)dhdp->info; +#ifdef EMBEDDED_PLATFORM + char iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + '\0' + bitvec */ +#endif /* EMBEDDED_PLATFORM */ + + ASSERT(dhd); + + DHD_TRACE(("%s: \n", __FUNCTION__)); + + dhd_os_sdlock(dhdp); + + /* try to download image and nvram to the dongle */ + if (dhd->pub.busstate == DHD_BUS_DOWN) { + if (!(dhd_bus_download_firmware(dhd->pub.bus, dhd->pub.osh, + fw_path, nv_path))) { + DHD_ERROR(("%s: dhdsdio_probe_download failed. firmware = %s nvram = %s\n", + __FUNCTION__, fw_path, nv_path)); + dhd_os_sdunlock(dhdp); + return -1; + } + } + + /* Start the watchdog timer */ + dhd->pub.tickcnt = 0; + dhd_os_wd_timer(&dhd->pub, dhd_watchdog_ms); + + /* Bring up the bus */ + if ((ret = dhd_bus_init(&dhd->pub, FALSE)) != 0) { + DHD_ERROR(("%s, dhd_bus_init failed %d\n", __FUNCTION__, ret)); + dhd_os_sdunlock(dhdp); + return ret; + } +#if defined(OOB_INTR_ONLY) + /* Host registration for OOB interrupt */ + if (bcmsdh_register_oob_intr(dhdp)) { + dhd->wd_timer_valid = FALSE; + del_timer_sync(&dhd->timer); + DHD_ERROR(("%s Host failed to resgister for OOB\n", __FUNCTION__)); + dhd_os_sdunlock(dhdp); + return -ENODEV; + } + + /* Enable oob at firmware */ + dhd_enable_oob_intr(dhd->pub.bus, TRUE); +#endif /* defined(OOB_INTR_ONLY) */ + + /* If bus is not ready, can't come up */ + if (dhd->pub.busstate != DHD_BUS_DATA) { + dhd->wd_timer_valid = FALSE; + del_timer_sync(&dhd->timer); + DHD_ERROR(("%s failed bus is not ready\n", __FUNCTION__)); + dhd_os_sdunlock(dhdp); + return -ENODEV; + } + + dhd_os_sdunlock(dhdp); + +#ifdef EMBEDDED_PLATFORM + bcm_mkiovar("event_msgs", dhdp->eventmask, WL_EVENTING_MASK_LEN, iovbuf, sizeof(iovbuf)); + dhdcdc_query_ioctl(dhdp, 0, WLC_GET_VAR, iovbuf, sizeof(iovbuf)); + bcopy(iovbuf, dhdp->eventmask, WL_EVENTING_MASK_LEN); + + setbit(dhdp->eventmask, WLC_E_SET_SSID); + setbit(dhdp->eventmask, WLC_E_PRUNE); + setbit(dhdp->eventmask, WLC_E_AUTH); + setbit(dhdp->eventmask, WLC_E_REASSOC); + setbit(dhdp->eventmask, WLC_E_REASSOC_IND); + setbit(dhdp->eventmask, WLC_E_DEAUTH_IND); + setbit(dhdp->eventmask, WLC_E_DISASSOC_IND); + setbit(dhdp->eventmask, WLC_E_DISASSOC); + setbit(dhdp->eventmask, WLC_E_JOIN); + setbit(dhdp->eventmask, WLC_E_ASSOC_IND); + setbit(dhdp->eventmask, WLC_E_PSK_SUP); + setbit(dhdp->eventmask, WLC_E_LINK); + setbit(dhdp->eventmask, WLC_E_NDIS_LINK); + setbit(dhdp->eventmask, WLC_E_MIC_ERROR); + setbit(dhdp->eventmask, WLC_E_PMKID_CACHE); + setbit(dhdp->eventmask, WLC_E_TXFAIL); + setbit(dhdp->eventmask, WLC_E_JOIN_START); + setbit(dhdp->eventmask, WLC_E_SCAN_COMPLETE); + setbit(dhdp->eventmask, WLC_E_RELOAD); +#ifdef PNO_SUPPORT + setbit(dhdp->eventmask, WLC_E_PFN_NET_FOUND); +#endif /* PNO_SUPPORT */ + +/* enable dongle roaming event */ + setbit(dhdp->eventmask, WLC_E_ROAM); + + dhdp->pktfilter_count = 4; + /* Setup filter to allow only unicast */ + dhdp->pktfilter[0] = "100 0 0 0 0x01 0x00"; + dhdp->pktfilter[1] = NULL; + dhdp->pktfilter[2] = NULL; + dhdp->pktfilter[3] = NULL; +#endif /* EMBEDDED_PLATFORM */ + + /* Bus is ready, do any protocol initialization */ + if ((ret = dhd_prot_init(&dhd->pub)) < 0) + return ret; + + return 0; +} + +int +dhd_iovar(dhd_pub_t *pub, int ifidx, char *name, char *cmd_buf, uint cmd_len, int set) +{ + char buf[strlen(name) + 1 + cmd_len]; + int len = sizeof(buf); + wl_ioctl_t ioc; + int ret; + + len = bcm_mkiovar(name, cmd_buf, cmd_len, buf, len); + + memset(&ioc, 0, sizeof(ioc)); + + ioc.cmd = set? WLC_SET_VAR : WLC_GET_VAR; + ioc.buf = buf; + ioc.len = len; + ioc.set = set; + + ret = dhd_prot_ioctl(pub, ifidx, &ioc, ioc.buf, ioc.len); + if (!set && ret >= 0) + memcpy(cmd_buf, buf, cmd_len); + + return ret; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 31)) +static struct net_device_ops dhd_ops_pri = { + .ndo_open = dhd_open, + .ndo_stop = dhd_stop, + .ndo_get_stats = dhd_get_stats, + .ndo_do_ioctl = dhd_ioctl_entry, + .ndo_start_xmit = dhd_start_xmit, + .ndo_set_mac_address = dhd_set_mac_address, + .ndo_set_multicast_list = dhd_set_multicast_list, +}; + +static struct net_device_ops dhd_ops_virt = { + .ndo_get_stats = dhd_get_stats, + .ndo_do_ioctl = dhd_ioctl_entry, + .ndo_start_xmit = dhd_start_xmit, + .ndo_set_mac_address = dhd_set_mac_address, + .ndo_set_multicast_list = dhd_set_multicast_list, +}; +#endif + +static int dhd_device_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; + dhd_info_t *dhd; + dhd_pub_t *dhd_pub; + + if (!ifa) + return NOTIFY_DONE; + + dhd = *(dhd_info_t **)netdev_priv(ifa->ifa_dev->dev); + dhd_pub = &dhd->pub; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 31)) + if (ifa->ifa_dev->dev->netdev_ops == &dhd_ops_pri) { +#else + if (ifa->ifa_dev->dev->open == &dhd_open) { +#endif + switch (event) { + case NETDEV_UP: + DHD_TRACE(("%s: [%s] Up IP: 0x%x\n", + __FUNCTION__, ifa->ifa_label, ifa->ifa_address)); + + dhd_arp_cleanup(dhd_pub); + break; + + case NETDEV_DOWN: + DHD_TRACE(("%s: [%s] Down IP: 0x%x\n", + __FUNCTION__, ifa->ifa_label, ifa->ifa_address)); + + dhd_arp_cleanup(dhd_pub); + break; + + default: + DHD_TRACE(("%s: [%s] Event: %lu\n", + __FUNCTION__, ifa->ifa_label, event)); + break; + } + } + return NOTIFY_DONE; +} + +int +dhd_net_attach(dhd_pub_t *dhdp, int ifidx) +{ + dhd_info_t *dhd = (dhd_info_t *)dhdp->info; + struct net_device *net; + uint8 temp_addr[ETHER_ADDR_LEN] = { 0x00, 0x90, 0x4c, 0x11, 0x22, 0x33 }; + + DHD_TRACE(("%s: ifidx %d\n", __FUNCTION__, ifidx)); + + ASSERT(dhd && dhd->iflist[ifidx]); + net = dhd->iflist[ifidx]->net; + + ASSERT(net); +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 31)) + ASSERT(!net->open); + net->get_stats = dhd_get_stats; + net->do_ioctl = dhd_ioctl_entry; + net->hard_start_xmit = dhd_start_xmit; + net->set_mac_address = dhd_set_mac_address; + net->set_multicast_list = dhd_set_multicast_list; + net->open = net->stop = NULL; +#else + ASSERT(!net->netdev_ops); + net->netdev_ops = &dhd_ops_virt; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 31)) + net->open = dhd_open; + net->stop = dhd_stop; +#else + net->netdev_ops = &dhd_ops_pri; +#endif + + /* + * We have to use the primary MAC for virtual interfaces + */ + if (ifidx != 0) { + /* for virtual interfaces use the primary MAC */ + memcpy(temp_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN); + } + + if (ifidx == 1) { + DHD_TRACE(("%s ACCESS POINT MAC: \n", __FUNCTION__)); + /* ACCESSPOINT INTERFACE CASE */ + temp_addr[0] |= 0x02; /* set bit 2 , - Locally Administered address */ + } + net->hard_header_len = ETH_HLEN + dhd->pub.hdrlen; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + net->ethtool_ops = &dhd_ethtool_ops; +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) */ + +#if defined(CONFIG_WIRELESS_EXT) +#if WIRELESS_EXT < 19 + net->get_wireless_stats = dhd_get_wireless_stats; +#endif /* WIRELESS_EXT < 19 */ +#if WIRELESS_EXT > 12 + net->wireless_handlers = (struct iw_handler_def *)&wl_iw_handler_def; +#endif /* WIRELESS_EXT > 12 */ +#endif /* defined(CONFIG_WIRELESS_EXT) */ + + dhd->pub.rxsz = net->mtu + net->hard_header_len + dhd->pub.hdrlen; + + memcpy(net->dev_addr, temp_addr, ETHER_ADDR_LEN); + + if (register_netdev(net) != 0) { + DHD_ERROR(("%s: couldn't register the net device\n", __FUNCTION__)); + goto fail; + } + + printf("%s: Broadcom Dongle Host Driver mac=%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", net->name, + dhd->pub.mac.octet[0], dhd->pub.mac.octet[1], dhd->pub.mac.octet[2], + dhd->pub.mac.octet[3], dhd->pub.mac.octet[4], dhd->pub.mac.octet[5]); + + +#if defined(CONFIG_WIRELESS_EXT) +#if defined(CONFIG_FIRST_SCAN) +#ifdef SOFTAP + if (ifidx == 0) + /* Don't call for SOFTAP Interface in SOFTAP MODE */ + wl_iw_iscan_set_scan_broadcast_prep(net, 1); +#else + wl_iw_iscan_set_scan_broadcast_prep(net, 1); +#endif /* SOFTAP */ +#endif /* CONFIG_FIRST_SCAN */ +#endif /* CONFIG_WIRELESS_EXT */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + up(&dhd_registration_sem); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) */ + return 0; + +fail: +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 31)) + net->open = NULL; +#else + net->netdev_ops = NULL; +#endif + return BCME_ERROR; +} + +void +dhd_bus_detach(dhd_pub_t *dhdp) +{ + dhd_info_t *dhd; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (dhdp) { + dhd = (dhd_info_t *)dhdp->info; + if (dhd) { + /* Stop the protocol module */ + dhd_prot_stop(&dhd->pub); + + /* Stop the bus module */ + dhd_bus_stop(dhd->pub.bus, TRUE); +#if defined(OOB_INTR_ONLY) + bcmsdh_unregister_oob_intr(); +#endif /* defined(OOB_INTR_ONLY) */ + + /* Clear the watchdog timer */ + dhd->wd_timer_valid = FALSE; + del_timer_sync(&dhd->timer); + } + } +} + +void +dhd_detach(dhd_pub_t *dhdp) +{ + dhd_info_t *dhd; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (dhdp) { + dhd = (dhd_info_t *)dhdp->info; + if (dhd) { + dhd_if_t *ifp; + int i; + + unregister_inetaddr_notifier(&dhd_notifier); + +#if defined(CONFIG_HAS_EARLYSUSPEND) + if (dhd->early_suspend.suspend) + unregister_early_suspend(&dhd->early_suspend); +#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */ +#if defined(CONFIG_WIRELESS_EXT) + /* Attach and link in the iw */ + wl_iw_detach(); +#endif + if (dhd->sysioc_pid >= 0) { + KILL_PROC(dhd->sysioc_pid, SIGTERM); + wait_for_completion(&dhd->sysioc_exited); + } + + for (i = 1; i < DHD_MAX_IFS; i++) + if (dhd->iflist[i]) { + dhd->iflist[i]->state = WLC_E_IF_DEL; + dhd->iflist[i]->idx = i; + dhd_op_if(dhd->iflist[i]); + } + + ifp = dhd->iflist[0]; + ASSERT(ifp); +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 31)) + if (ifp->net->open) { +#else + if (ifp->net->netdev_ops == &dhd_ops_pri) { +#endif + dhd_stop(ifp->net); + unregister_netdev(ifp->net); + } + + if (dhd->watchdog_pid >= 0) + { + KILL_PROC(dhd->watchdog_pid, SIGTERM); + wait_for_completion(&dhd->watchdog_exited); + } + + if (dhd->dpc_pid >= 0) + { + KILL_PROC(dhd->dpc_pid, SIGTERM); + wait_for_completion(&dhd->dpc_exited); + } + else + tasklet_kill(&dhd->tasklet); + + dhd_bus_detach(dhdp); + + if (dhdp->prot) + dhd_prot_detach(dhdp); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) + unregister_pm_notifier(&dhd_sleep_pm_notifier); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */ + free_netdev(ifp->net); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_destroy(&dhd->wl_wifi); + wake_lock_destroy(&dhd->wl_rxwake); +#endif + MFREE(dhd->pub.osh, ifp, sizeof(*ifp)); + MFREE(dhd->pub.osh, dhd, sizeof(*dhd)); + } + } +} + +static void __exit +dhd_module_cleanup(void) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + dhd_bus_unregister(); +#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) + wifi_del_dev(); +#endif + /* Call customer gpio to turn off power with WL_REG_ON signal */ + dhd_customer_gpio_wlan_ctrl(WLAN_POWER_OFF); +} + +static int __init +dhd_module_init(void) +{ + int error; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* Sanity check on the module parameters */ + do { + /* Both watchdog and DPC as tasklets are ok */ + if ((dhd_watchdog_prio < 0) && (dhd_dpc_prio < 0)) + break; + + /* If both watchdog and DPC are threads, TX must be deferred */ + if ((dhd_watchdog_prio >= 0) && (dhd_dpc_prio >= 0) && dhd_deferred_tx) + break; + + DHD_ERROR(("Invalid module parameters.\n")); + return -EINVAL; + } while (0); + + /* Call customer gpio to turn on power with WL_REG_ON signal */ + dhd_customer_gpio_wlan_ctrl(WLAN_POWER_ON); + +#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) + sema_init(&wifi_control_sem, 0); + + error = wifi_add_dev(); + if (error) { + DHD_ERROR(("%s: platform_driver_register failed\n", __FUNCTION__)); + goto fail_0; + } + + /* Waiting callback after platform_driver_register is done or exit with error */ + if (down_timeout(&wifi_control_sem, msecs_to_jiffies(5000)) != 0) { + error = -EINVAL; + DHD_ERROR(("%s: platform_driver_register timeout\n", __FUNCTION__)); + goto fail_1; + } +#endif /* #if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + sema_init(&dhd_registration_sem, 0); +#endif + + error = dhd_bus_register(); + + if (!error) + printf("\n%s\n", dhd_version); + else { + DHD_ERROR(("%s: sdio_register_driver failed\n", __FUNCTION__)); + goto fail_1; + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + /* + * Wait till MMC sdio_register_driver callback called and made driver attach. + * It's needed to make sync up exit from dhd insmod and + * Kernel MMC sdio device callback registration + */ + if (down_timeout(&dhd_registration_sem, msecs_to_jiffies(DHD_REGISTRATION_TIMEOUT)) != 0) { + error = -EINVAL; + DHD_ERROR(("%s: sdio_register_driver timeout\n", __FUNCTION__)); + goto fail_2; + } +#endif + return error; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) +fail_2: + dhd_bus_unregister(); +#endif +fail_1: +#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) + wifi_del_dev(); +fail_0: +#endif /* defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) */ + + /* Call customer gpio to turn off power with WL_REG_ON signal */ + dhd_customer_gpio_wlan_ctrl(WLAN_POWER_OFF); + + return error; +} + +module_init(dhd_module_init); +module_exit(dhd_module_cleanup); + +/* + * OS specific functions required to implement DHD driver in OS independent way + */ +int +dhd_os_proto_block(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + + if (dhd) { + mutex_lock(&dhd->proto_sem); + return 1; + } + + return 0; +} + +int +dhd_os_proto_unblock(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + + if (dhd) { + mutex_unlock(&dhd->proto_sem); + return 1; + } + + return 0; +} + +unsigned int +dhd_os_get_ioctl_resp_timeout(void) +{ + return ((unsigned int)dhd_ioctl_timeout_msec); +} + +void +dhd_os_set_ioctl_resp_timeout(unsigned int timeout_msec) +{ + dhd_ioctl_timeout_msec = (int)timeout_msec; +} + +int +dhd_os_ioctl_resp_wait(dhd_pub_t *pub, uint *condition, bool *pending) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + DECLARE_WAITQUEUE(wait, current); + int timeout = dhd_ioctl_timeout_msec; + + /* Convert timeout in millsecond to jiffies */ + /* timeout = timeout * HZ / 1000; */ + timeout = msecs_to_jiffies(timeout); + + /* Wait until control frame is available */ + add_wait_queue(&dhd->ioctl_resp_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + smp_mb(); + while (!(*condition) && (!signal_pending(current) && timeout)) { + timeout = schedule_timeout(timeout); + smp_mb(); + } + + if (signal_pending(current)) + *pending = TRUE; + + set_current_state(TASK_RUNNING); + remove_wait_queue(&dhd->ioctl_resp_wait, &wait); + + return timeout; +} + +int +dhd_os_ioctl_resp_wake(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + + if (waitqueue_active(&dhd->ioctl_resp_wait)) { + wake_up_interruptible(&dhd->ioctl_resp_wait); + } + + return 0; +} + +void +dhd_os_wd_timer(void *bus, uint wdtick) +{ + dhd_pub_t *pub = bus; + dhd_info_t *dhd = (dhd_info_t *)pub->info; + unsigned long flags; + int del_timer_flag = FALSE; + + flags = dhd_os_spin_lock(pub); + + /* don't start the wd until fw is loaded */ + if (pub->busstate != DHD_BUS_DOWN) { + if (wdtick) { + dhd_watchdog_ms = (uint)wdtick; + dhd->wd_timer_valid = TRUE; + /* Re arm the timer, at last watchdog period */ + mod_timer(&dhd->timer, jiffies + dhd_watchdog_ms * HZ / 1000); + } else if (dhd->wd_timer_valid == TRUE) { + /* Totally stop the timer */ + dhd->wd_timer_valid = FALSE; + del_timer_flag = TRUE; + } + } + dhd_os_spin_unlock(pub, flags); + if (del_timer_flag) { + del_timer_sync(&dhd->timer); + } +} + +void * +dhd_os_open_image(char *filename) +{ + struct file *fp; + + fp = filp_open(filename, O_RDONLY, 0); + /* + * 2.6.11 (FC4) supports filp_open() but later revs don't? + * Alternative: + * fp = open_namei(AT_FDCWD, filename, O_RD, 0); + * ??? + */ + if (IS_ERR(fp)) + fp = NULL; + + return fp; +} + +int +dhd_os_get_image_block(char *buf, int len, void *image) +{ + struct file *fp = (struct file *)image; + int rdlen; + + if (!image) + return 0; + + rdlen = kernel_read(fp, fp->f_pos, buf, len); + if (rdlen > 0) + fp->f_pos += rdlen; + + return rdlen; +} + +void +dhd_os_close_image(void *image) +{ + if (image) + filp_close((struct file *)image, NULL); +} + + +void +dhd_os_sdlock(dhd_pub_t *pub) +{ + dhd_info_t *dhd; + + dhd = (dhd_info_t *)(pub->info); + + if (dhd->threads_only) + mutex_lock(&dhd->sdsem); + else + spin_lock_bh(&dhd->sdlock); +} + +void +dhd_os_sdunlock(dhd_pub_t *pub) +{ + dhd_info_t *dhd; + + dhd = (dhd_info_t *)(pub->info); + + if (dhd->threads_only) + mutex_unlock(&dhd->sdsem); + else + spin_unlock_bh(&dhd->sdlock); +} + +void +dhd_os_sdlock_txq(dhd_pub_t *pub) +{ + dhd_info_t *dhd; + + dhd = (dhd_info_t *)(pub->info); + spin_lock_bh(&dhd->txqlock); +} + +void +dhd_os_sdunlock_txq(dhd_pub_t *pub) +{ + dhd_info_t *dhd; + + dhd = (dhd_info_t *)(pub->info); + spin_unlock_bh(&dhd->txqlock); +} +void +dhd_os_sdlock_rxq(dhd_pub_t *pub) +{ +} +void +dhd_os_sdunlock_rxq(dhd_pub_t *pub) +{ +} + +void +dhd_os_sdtxlock(dhd_pub_t *pub) +{ + dhd_os_sdlock(pub); +} + +void +dhd_os_sdtxunlock(dhd_pub_t *pub) +{ + dhd_os_sdunlock(pub); +} + +#ifdef DHD_USE_STATIC_BUF +void * dhd_os_prealloc(int section, unsigned long size) +{ +#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) + void *alloc_ptr = NULL; + if (wifi_control_data && wifi_control_data->mem_prealloc) + { + alloc_ptr = wifi_control_data->mem_prealloc(section, size); + if (alloc_ptr) + { + DHD_INFO(("success alloc section %d\n", section)); + bzero(alloc_ptr, size); + return alloc_ptr; + } + } + + DHD_ERROR(("can't alloc section %d\n", section)); + return 0; +#else +return MALLOC(0, size); +#endif /* #if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) */ +} +#endif /* DHD_USE_STATIC_BUF */ +#if defined(CONFIG_WIRELESS_EXT) +struct iw_statistics * +dhd_get_wireless_stats(struct net_device *dev) +{ + int res = 0; + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + res = wl_iw_get_wireless_stats(dev, &dhd->iw.wstats); + + if (res == 0) + return &dhd->iw.wstats; + else + return NULL; +} +#endif /* defined(CONFIG_WIRELESS_EXT) */ + +static int +dhd_wl_host_event(dhd_info_t *dhd, int *ifidx, void *pktdata, + wl_event_msg_t *event, void **data) +{ + int bcmerror = 0; + + ASSERT(dhd != NULL); + + bcmerror = wl_host_event(dhd, ifidx, pktdata, event, data); + if (bcmerror != BCME_OK) + return (bcmerror); + +#if defined(CONFIG_WIRELESS_EXT) + ASSERT(dhd->iflist[*ifidx] != NULL); + + if (ntoh32(event->event_type) == WLC_E_IF) { + DHD_INFO(("<0> interface:%d OP:%d don't pass to wext," + "net_device might not be created yet\n", + *ifidx, ntoh32(event->event_type))); + return bcmerror; + } + + ASSERT(dhd->iflist[*ifidx]->net != NULL); + + if (dhd->iflist[*ifidx]->net) + wl_iw_event(dhd->iflist[*ifidx]->net, event, *data); +#endif /* defined(CONFIG_WIRELESS_EXT) */ + + return (bcmerror); +} + +/* send up locally generated event */ +void +dhd_sendup_event(dhd_pub_t *dhdp, wl_event_msg_t *event, void *data) +{ + switch (ntoh32(event->event_type)) { + default: + break; + } +} + +void dhd_wait_for_event(dhd_pub_t *dhd, bool *lockvar) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) + struct dhd_info *dhdinfo = dhd->info; + dhd_os_sdunlock(dhd); + wait_event_interruptible_timeout(dhdinfo->ctrl_wait, (*lockvar == FALSE), HZ * 2); + dhd_os_sdlock(dhd); +#endif + return; +} + +void dhd_wait_event_wakeup(dhd_pub_t *dhd) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) + struct dhd_info *dhdinfo = dhd->info; + if (waitqueue_active(&dhdinfo->ctrl_wait)) + wake_up_interruptible(&dhdinfo->ctrl_wait); +#endif + return; +} + +int +dhd_dev_reset(struct net_device *dev, uint8 flag) +{ + int ret; + + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + ret = dhd_bus_devreset(&dhd->pub, flag); + if (ret) { + DHD_ERROR(("%s: dhd_bus_devreset: %d\n", __FUNCTION__, ret)); + return ret; + } + DHD_ERROR(("%s: WLAN %s DONE\n", __FUNCTION__, flag ? "OFF" : "ON")); + + return ret; +} + +int net_os_set_suspend_disable(struct net_device *dev, int val) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + if (dhd) { + ret = dhd->pub.suspend_disable_flag; + dhd->pub.suspend_disable_flag = val; + } + return ret; +} + +int net_os_set_suspend(struct net_device *dev, int val) +{ + int ret = 0; +#if defined(CONFIG_HAS_EARLYSUSPEND) + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + if (dhd) { + dhd_os_proto_block(&dhd->pub); + ret = dhd_set_suspend(val, &dhd->pub); + dhd_os_proto_unblock(&dhd->pub); + } +#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */ + return ret; +} + +int net_os_set_dtim_skip(struct net_device *dev, int val) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + if (dhd) + dhd->pub.dtim_skip = val; + + return 0; +} + +int net_os_rxfilter_add_remove(struct net_device *dev, int add_remove, int num) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + char *filterp = NULL; + int ret = 0; + + if (!dhd || (num == DHD_UNICAST_FILTER_NUM)) + return ret; + if (num >= dhd->pub.pktfilter_count) + return -EINVAL; + if (add_remove) { + switch (num) { + case DHD_BROADCAST_FILTER_NUM: + filterp = "101 0 0 0 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF"; + break; + case DHD_MULTICAST4_FILTER_NUM: + filterp = "102 0 0 0 0xFFFFFF 0x01005E"; + break; + case DHD_MULTICAST6_FILTER_NUM: + filterp = "103 0 0 0 0xFFFF 0x3333"; + break; + default: + return -EINVAL; + } + } + dhd->pub.pktfilter[num] = filterp; + return ret; +} + +int net_os_set_packet_filter(struct net_device *dev, int val) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + /* Packet filtering is set only if we still in early-suspend and + * we need either to turn it ON or turn it OFF + * We can always turn it OFF in case of early-suspend, but we turn it + * back ON only if suspend_disable_flag was not set + */ + if (dhd && dhd->pub.up) { + dhd_os_proto_block(&dhd->pub); + if (dhd->pub.in_suspend) { + if (!val || (val && !dhd->pub.suspend_disable_flag)) + dhd_set_packet_filter(val, &dhd->pub); + } + dhd_os_proto_unblock(&dhd->pub); + } + return ret; +} + + +void +dhd_dev_init_ioctl(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + dhd_preinit_ioctls(&dhd->pub); +} + +#ifdef PNO_SUPPORT +/* Linux wrapper to call common dhd_pno_clean */ +int +dhd_dev_pno_reset(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + return (dhd_pno_clean(&dhd->pub)); +} + + +/* Linux wrapper to call common dhd_pno_enable */ +int +dhd_dev_pno_enable(struct net_device *dev, int pfn_enabled) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + return (dhd_pno_enable(&dhd->pub, pfn_enabled)); +} + + +/* Linux wrapper to call common dhd_pno_set */ +int +dhd_dev_pno_set(struct net_device *dev, wlc_ssid_t* ssids_local, int nssid, + ushort scan_fr, int pno_repeat, int pno_freq_expo_max) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + return (dhd_pno_set(&dhd->pub, ssids_local, nssid, scan_fr, pno_repeat, pno_freq_expo_max)); +} + +/* Linux wrapper to get pno status */ +int +dhd_dev_get_pno_status(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + return (dhd_pno_get_status(&dhd->pub)); +} + +#endif /* PNO_SUPPORT */ + +int net_os_send_hang_message(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + if (dhd) { + if (!dhd->pub.hang_was_sent) { + dhd->pub.hang_was_sent = 1; + ret = wl_iw_send_priv_event(dev, "HANG"); + } + } + return ret; +} + +void dhd_bus_country_set(struct net_device *dev, wl_country_t *cspec) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + if (dhd && dhd->pub.up) + memcpy(&dhd->pub.dhd_cspec, cspec, sizeof(wl_country_t)); +} + +char *dhd_bus_country_get(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + + if (dhd && (dhd->pub.dhd_cspec.ccode[0] != 0)) + return dhd->pub.dhd_cspec.ccode; + return NULL; +} + +void dhd_os_start_lock(dhd_pub_t *pub) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)) + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + + if (dhd) + mutex_lock(&dhd->wl_start_lock); +#endif +} + +void dhd_os_start_unlock(dhd_pub_t *pub) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)) + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + + if (dhd) + mutex_unlock(&dhd->wl_start_lock); +#endif +} + +static int +dhd_get_pend_8021x_cnt(dhd_info_t *dhd) +{ + return (atomic_read(&dhd->pend_8021x_cnt)); +} + +#define MAX_WAIT_FOR_8021X_TX 10 + +int +dhd_wait_pend8021x(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int timeout = 10 * HZ / 1000; + int ntimes = MAX_WAIT_FOR_8021X_TX; + int pend = dhd_get_pend_8021x_cnt(dhd); + + while (ntimes && pend) { + if (pend) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout); + set_current_state(TASK_RUNNING); + ntimes--; + } + pend = dhd_get_pend_8021x_cnt(dhd); + } + return pend; +} + +#ifdef DHD_DEBUG +int +write_to_file(dhd_pub_t *dhd, uint8 *buf, int size) +{ + int ret = 0; + struct file *fp; + mm_segment_t old_fs; + loff_t pos = 0; + + /* change to KERNEL_DS address limit */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + /* open file to write */ + fp = filp_open("/tmp/mem_dump", O_WRONLY|O_CREAT, 0640); + if (!fp) { + printf("%s: open file error\n", __FUNCTION__); + ret = -1; + goto exit; + } + + /* Write buf to file */ + fp->f_op->write(fp, buf, size, &pos); + +exit: + /* free buf before return */ + MFREE(dhd->osh, buf, size); + /* close file before return */ + if (fp) + filp_close(fp, current->files); + /* restore previous address limit */ + set_fs(old_fs); + + return ret; +} +#endif /* DHD_DEBUG */ + +int dhd_os_wake_lock_timeout(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + unsigned long flags; + int ret = 0; + + if (dhd) { + spin_lock_irqsave(&dhd->wl_lock, flags); + ret = dhd->wl_packet; +#ifdef CONFIG_HAS_WAKELOCK + if (dhd->wl_packet) + wake_lock_timeout(&dhd->wl_rxwake, HZ); +#endif + dhd->wl_packet = 0; + spin_unlock_irqrestore(&dhd->wl_lock, flags); + } + /* printk("%s: %d\n", __FUNCTION__, ret); */ + return ret; +} + +int net_os_wake_lock_timeout(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + if (dhd) + ret = dhd_os_wake_lock_timeout(&dhd->pub); + return ret; +} + +int dhd_os_wake_lock_timeout_enable(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + unsigned long flags; + + if (dhd) { + spin_lock_irqsave(&dhd->wl_lock, flags); + dhd->wl_packet = 1; + spin_unlock_irqrestore(&dhd->wl_lock, flags); + } + /* printk("%s\n",__func__); */ + return 0; +} + +int net_os_wake_lock_timeout_enable(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + if (dhd) + ret = dhd_os_wake_lock_timeout_enable(&dhd->pub); + return ret; +} + +int dhd_os_wake_lock(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + unsigned long flags; + int ret = 0; + + if (dhd) { + spin_lock_irqsave(&dhd->wl_lock, flags); +#ifdef CONFIG_HAS_WAKELOCK + if (!dhd->wl_count) + wake_lock(&dhd->wl_wifi); +#endif + dhd->wl_count++; + ret = dhd->wl_count; + spin_unlock_irqrestore(&dhd->wl_lock, flags); + } + /* printk("%s: %d\n", __FUNCTION__, ret); */ + return ret; +} + +int net_os_wake_lock(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + if (dhd) + ret = dhd_os_wake_lock(&dhd->pub); + return ret; +} + +int dhd_os_wake_unlock(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + unsigned long flags; + int ret = 0; + + dhd_os_wake_lock_timeout(pub); + if (dhd) { + spin_lock_irqsave(&dhd->wl_lock, flags); + if (dhd->wl_count) { + dhd->wl_count--; +#ifdef CONFIG_HAS_WAKELOCK + if (!dhd->wl_count) + wake_unlock(&dhd->wl_wifi); +#endif + ret = dhd->wl_count; + } + spin_unlock_irqrestore(&dhd->wl_lock, flags); + } + /* printk("%s: %d\n", __FUNCTION__, ret); */ + return ret; +} + +int net_os_wake_unlock(struct net_device *dev) +{ + dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); + int ret = 0; + + if (dhd) + ret = dhd_os_wake_unlock(&dhd->pub); + return ret; +} + +unsigned long dhd_os_spin_lock(dhd_pub_t *pub) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + unsigned long flags = 0; + + if (dhd) + spin_lock_irqsave(&dhd->dhd_lock, flags); + + return flags; +} + +void dhd_os_spin_unlock(dhd_pub_t *pub, unsigned long flags) +{ + dhd_info_t *dhd = (dhd_info_t *)(pub->info); + + if (dhd) + spin_unlock_irqrestore(&dhd->dhd_lock, flags); +} diff --git a/drivers/net/wireless/bcm4329/dhd_linux_sched.c b/drivers/net/wireless/bcm4329/dhd_linux_sched.c new file mode 100644 index 0000000000000000000000000000000000000000..480b416657ee2a74d0bb00fffdd7354bc5965f32 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_linux_sched.c @@ -0,0 +1,38 @@ +/* + * Expose some of the kernel scheduler routines + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_linux_sched.c,v 1.1.34.1.6.1 2009/01/16 01:17:40 Exp $ + */ +#include +#include +#include +#include + +int setScheduler(struct task_struct *p, int policy, struct sched_param *param) +{ + int rc = 0; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) + rc = sched_setscheduler(p, policy, param); +#endif /* LinuxVer */ + return rc; +} diff --git a/drivers/net/wireless/bcm4329/dhd_proto.h b/drivers/net/wireless/bcm4329/dhd_proto.h new file mode 100644 index 0000000000000000000000000000000000000000..7ef6929a5bf728a693c9b761220d4a760d4c2498 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_proto.h @@ -0,0 +1,102 @@ +/* + * Header file describing the internal (inter-module) DHD interfaces. + * + * Provides type definitions and function prototypes used to link the + * DHD OS, bus, and protocol modules. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_proto.h,v 1.2.82.1.4.1.16.7 2010/05/10 12:54:59 Exp $ + */ + +#ifndef _dhd_proto_h_ +#define _dhd_proto_h_ + +#include +#include + +#ifndef IOCTL_RESP_TIMEOUT +#define IOCTL_RESP_TIMEOUT 3000 /* In milli second */ +#endif + +#ifndef IOCTL_CHIP_ACTIVE_TIMEOUT +#define IOCTL_CHIP_ACTIVE_TIMEOUT 10 /* In milli second */ +#endif + +/* + * Exported from the dhd protocol module (dhd_cdc, dhd_rndis) + */ + +/* Linkage, sets prot link and updates hdrlen in pub */ +extern int dhd_prot_attach(dhd_pub_t *dhdp); + +/* Unlink, frees allocated protocol memory (including dhd_prot) */ +extern void dhd_prot_detach(dhd_pub_t *dhdp); + +/* Initialize protocol: sync w/dongle state. + * Sets dongle media info (iswl, drv_version, mac address). + */ +extern int dhd_prot_init(dhd_pub_t *dhdp); + +/* Stop protocol: sync w/dongle state. */ +extern void dhd_prot_stop(dhd_pub_t *dhdp); + +extern bool dhd_proto_fcinfo(dhd_pub_t *dhd, void *pktbuf, uint8 *fcbits); + +/* Add any protocol-specific data header. + * Caller must reserve prot_hdrlen prepend space. + */ +extern void dhd_prot_hdrpush(dhd_pub_t *, int ifidx, void *txp); + +/* Remove any protocol-specific data header. */ +extern int dhd_prot_hdrpull(dhd_pub_t *, int *ifidx, void *rxp); + +/* Use protocol to issue ioctl to dongle */ +extern int dhd_prot_ioctl(dhd_pub_t *dhd, int ifidx, wl_ioctl_t * ioc, void * buf, int len); + +/* Check for and handle local prot-specific iovar commands */ +extern int dhd_prot_iovar_op(dhd_pub_t *dhdp, const char *name, + void *params, int plen, void *arg, int len, bool set); + +/* Add prot dump output to a buffer */ +extern void dhd_prot_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf); + +/* Update local copy of dongle statistics */ +extern void dhd_prot_dstats(dhd_pub_t *dhdp); + +extern int dhd_ioctl(dhd_pub_t * dhd_pub, dhd_ioctl_t *ioc, void * buf, uint buflen); + +extern int dhd_preinit_ioctls(dhd_pub_t *dhd); + +/******************************** + * For version-string expansion * + */ +#if defined(BDC) +#define DHD_PROTOCOL "bdc" +#elif defined(CDC) +#define DHD_PROTOCOL "cdc" +#elif defined(RNDIS) +#define DHD_PROTOCOL "rndis" +#else +#define DHD_PROTOCOL "unknown" +#endif /* proto */ + +#endif /* _dhd_proto_h_ */ diff --git a/drivers/net/wireless/bcm4329/dhd_sdio.c b/drivers/net/wireless/bcm4329/dhd_sdio.c new file mode 100644 index 0000000000000000000000000000000000000000..e9093e81062442820313da200485f209af1e71b6 --- /dev/null +++ b/drivers/net/wireless/bcm4329/dhd_sdio.c @@ -0,0 +1,5824 @@ +/* + * DHD Bus Module for SDIO + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhd_sdio.c,v 1.157.2.27.2.33.2.129.4.1 2010/09/02 23:13:16 Exp $ + */ + +#include +#include +#include + +#ifdef BCMEMBEDIMAGE +#include BCMEMBEDIMAGE +#endif /* BCMEMBEDIMAGE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DHD_DEBUG +#include +#endif /* DHD_DEBUG */ +#ifdef DHD_DEBUG_TRAP +#include +#endif /* DHD_DEBUG_TRAP */ + +#define QLEN 256 /* bulk rx and tx queue lengths */ +#define FCHI (QLEN - 10) +#define FCLOW (FCHI / 2) +#define PRIOMASK 7 + +#define TXRETRIES 2 /* # of retries for tx frames */ + +#if defined(CONFIG_MACH_SANDGATE2G) +#define DHD_RXBOUND 250 /* Default for max rx frames in one scheduling */ +#else +#define DHD_RXBOUND 50 /* Default for max rx frames in one scheduling */ +#endif /* defined(CONFIG_MACH_SANDGATE2G) */ + +#define DHD_TXBOUND 20 /* Default for max tx frames in one scheduling */ + +#define DHD_TXMINMAX 1 /* Max tx frames if rx still pending */ + +#define MEMBLOCK 2048 /* Block size used for downloading of dongle image */ +#define MAX_DATA_BUF (32 * 1024) /* Must be large enough to hold biggest possible glom */ + +/* Packet alignment for most efficient SDIO (can change based on platform) */ +#ifndef DHD_SDALIGN +#define DHD_SDALIGN 32 +#endif +#if !ISPOWEROF2(DHD_SDALIGN) +#error DHD_SDALIGN is not a power of 2! +#endif + +#ifndef DHD_FIRSTREAD +#define DHD_FIRSTREAD 32 +#endif +#if !ISPOWEROF2(DHD_FIRSTREAD) +#error DHD_FIRSTREAD is not a power of 2! +#endif + +/* Total length of frame header for dongle protocol */ +#define SDPCM_HDRLEN (SDPCM_FRAMETAG_LEN + SDPCM_SWHEADER_LEN) +#ifdef SDTEST +#define SDPCM_RESERVE (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN) +#else +#define SDPCM_RESERVE (SDPCM_HDRLEN + DHD_SDALIGN) +#endif + +/* Space for header read, limit for data packets */ +#ifndef MAX_HDR_READ +#define MAX_HDR_READ 32 +#endif +#if !ISPOWEROF2(MAX_HDR_READ) +#error MAX_HDR_READ is not a power of 2! +#endif + +#define MAX_RX_DATASZ 2048 + +/* Maximum milliseconds to wait for F2 to come up */ +#define DHD_WAIT_F2RDY 3000 + +/* Bump up limit on waiting for HT to account for first startup; + * if the image is doing a CRC calculation before programming the PMU + * for HT availability, it could take a couple hundred ms more, so + * max out at a 1 second (1000000us). + */ +#if (PMU_MAX_TRANSITION_DLY < 1000000) +#undef PMU_MAX_TRANSITION_DLY +#define PMU_MAX_TRANSITION_DLY 1000000 +#endif + +/* Value for ChipClockCSR during initial setup */ +#define DHD_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ) +#define DHD_INIT_CLKCTL2 (SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP) + +/* Flags for SDH calls */ +#define F2SYNC (SDIO_REQ_4BYTE | SDIO_REQ_FIXED) + +/* Packet free applicable unconditionally for sdio and sdspi. Conditional if + * bufpool was present for gspi bus. + */ +#define PKTFREE2() if ((bus->bus != SPI_BUS) || bus->usebufpool) \ + PKTFREE(bus->dhd->osh, pkt, FALSE); +DHD_SPINWAIT_SLEEP_INIT(sdioh_spinwait_sleep); +extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len); + +extern void bcmsdh_set_irq(int flag); + +#ifdef DHD_DEBUG +/* Device console log buffer state */ +typedef struct dhd_console { + uint count; /* Poll interval msec counter */ + uint log_addr; /* Log struct address (fixed) */ + hndrte_log_t log; /* Log struct (host copy) */ + uint bufsize; /* Size of log buffer */ + uint8 *buf; /* Log buffer (host copy) */ + uint last; /* Last buffer read index */ +} dhd_console_t; +#endif /* DHD_DEBUG */ + +/* Private data for SDIO bus interaction */ +typedef struct dhd_bus { + dhd_pub_t *dhd; + + bcmsdh_info_t *sdh; /* Handle for BCMSDH calls */ + si_t *sih; /* Handle for SI calls */ + char *vars; /* Variables (from CIS and/or other) */ + uint varsz; /* Size of variables buffer */ + uint32 sbaddr; /* Current SB window pointer (-1, invalid) */ + + sdpcmd_regs_t *regs; /* Registers for SDIO core */ + uint sdpcmrev; /* SDIO core revision */ + uint armrev; /* CPU core revision */ + uint ramrev; /* SOCRAM core revision */ + uint32 ramsize; /* Size of RAM in SOCRAM (bytes) */ + uint32 orig_ramsize; /* Size of RAM in SOCRAM (bytes) */ + + uint32 bus; /* gSPI or SDIO bus */ + uint32 hostintmask; /* Copy of Host Interrupt Mask */ + uint32 intstatus; /* Intstatus bits (events) pending */ + bool dpc_sched; /* Indicates DPC schedule (intrpt rcvd) */ + bool fcstate; /* State of dongle flow-control */ + + uint16 cl_devid; /* cached devid for dhdsdio_probe_attach() */ + char *fw_path; /* module_param: path to firmware image */ + char *nv_path; /* module_param: path to nvram vars file */ + const char *nvram_params; /* user specified nvram params. */ + + uint blocksize; /* Block size of SDIO transfers */ + uint roundup; /* Max roundup limit */ + + struct pktq txq; /* Queue length used for flow-control */ + uint8 flowcontrol; /* per prio flow control bitmask */ + uint8 tx_seq; /* Transmit sequence number (next) */ + uint8 tx_max; /* Maximum transmit sequence allowed */ + + uint8 hdrbuf[MAX_HDR_READ + DHD_SDALIGN]; + uint8 *rxhdr; /* Header of current rx frame (in hdrbuf) */ + uint16 nextlen; /* Next Read Len from last header */ + uint8 rx_seq; /* Receive sequence number (expected) */ + bool rxskip; /* Skip receive (awaiting NAK ACK) */ + + void *glomd; /* Packet containing glomming descriptor */ + void *glom; /* Packet chain for glommed superframe */ + uint glomerr; /* Glom packet read errors */ + + uint8 *rxbuf; /* Buffer for receiving control packets */ + uint rxblen; /* Allocated length of rxbuf */ + uint8 *rxctl; /* Aligned pointer into rxbuf */ + uint8 *databuf; /* Buffer for receiving big glom packet */ + uint8 *dataptr; /* Aligned pointer into databuf */ + uint rxlen; /* Length of valid data in buffer */ + + uint8 sdpcm_ver; /* Bus protocol reported by dongle */ + + bool intr; /* Use interrupts */ + bool poll; /* Use polling */ + bool ipend; /* Device interrupt is pending */ + bool intdis; /* Interrupts disabled by isr */ + uint intrcount; /* Count of device interrupt callbacks */ + uint lastintrs; /* Count as of last watchdog timer */ + uint spurious; /* Count of spurious interrupts */ + uint pollrate; /* Ticks between device polls */ + uint polltick; /* Tick counter */ + uint pollcnt; /* Count of active polls */ + +#ifdef DHD_DEBUG + dhd_console_t console; /* Console output polling support */ + uint console_addr; /* Console address from shared struct */ +#endif /* DHD_DEBUG */ + + uint regfails; /* Count of R_REG/W_REG failures */ + + uint clkstate; /* State of sd and backplane clock(s) */ + bool activity; /* Activity flag for clock down */ + int32 idletime; /* Control for activity timeout */ + int32 idlecount; /* Activity timeout counter */ + int32 idleclock; /* How to set bus driver when idle */ + int32 sd_divisor; /* Speed control to bus driver */ + int32 sd_mode; /* Mode control to bus driver */ + int32 sd_rxchain; /* If bcmsdh api accepts PKT chains */ + bool use_rxchain; /* If dhd should use PKT chains */ + bool sleeping; /* Is SDIO bus sleeping? */ + bool rxflow_mode; /* Rx flow control mode */ + bool rxflow; /* Is rx flow control on */ + uint prev_rxlim_hit; /* Is prev rx limit exceeded (per dpc schedule) */ + bool alp_only; /* Don't use HT clock (ALP only) */ + /* Field to decide if rx of control frames happen in rxbuf or lb-pool */ + bool usebufpool; + +#ifdef SDTEST + /* external loopback */ + bool ext_loop; + uint8 loopid; + + /* pktgen configuration */ + uint pktgen_freq; /* Ticks between bursts */ + uint pktgen_count; /* Packets to send each burst */ + uint pktgen_print; /* Bursts between count displays */ + uint pktgen_total; /* Stop after this many */ + uint pktgen_minlen; /* Minimum packet data len */ + uint pktgen_maxlen; /* Maximum packet data len */ + uint pktgen_mode; /* Configured mode: tx, rx, or echo */ + uint pktgen_stop; /* Number of tx failures causing stop */ + + /* active pktgen fields */ + uint pktgen_tick; /* Tick counter for bursts */ + uint pktgen_ptick; /* Burst counter for printing */ + uint pktgen_sent; /* Number of test packets generated */ + uint pktgen_rcvd; /* Number of test packets received */ + uint pktgen_fail; /* Number of failed send attempts */ + uint16 pktgen_len; /* Length of next packet to send */ +#endif /* SDTEST */ + + /* Some additional counters */ + uint tx_sderrs; /* Count of tx attempts with sd errors */ + uint fcqueued; /* Tx packets that got queued */ + uint rxrtx; /* Count of rtx requests (NAK to dongle) */ + uint rx_toolong; /* Receive frames too long to receive */ + uint rxc_errors; /* SDIO errors when reading control frames */ + uint rx_hdrfail; /* SDIO errors on header reads */ + uint rx_badhdr; /* Bad received headers (roosync?) */ + uint rx_badseq; /* Mismatched rx sequence number */ + uint fc_rcvd; /* Number of flow-control events received */ + uint fc_xoff; /* Number which turned on flow-control */ + uint fc_xon; /* Number which turned off flow-control */ + uint rxglomfail; /* Failed deglom attempts */ + uint rxglomframes; /* Number of glom frames (superframes) */ + uint rxglompkts; /* Number of packets from glom frames */ + uint f2rxhdrs; /* Number of header reads */ + uint f2rxdata; /* Number of frame data reads */ + uint f2txdata; /* Number of f2 frame writes */ + uint f1regdata; /* Number of f1 register accesses */ + + uint8 *ctrl_frame_buf; + uint32 ctrl_frame_len; + bool ctrl_frame_stat; +} dhd_bus_t; + +/* clkstate */ +#define CLK_NONE 0 +#define CLK_SDONLY 1 +#define CLK_PENDING 2 /* Not used yet */ +#define CLK_AVAIL 3 + +#define DHD_NOPMU(dhd) (FALSE) + +#ifdef DHD_DEBUG +static int qcount[NUMPRIO]; +static int tx_packets[NUMPRIO]; +#endif /* DHD_DEBUG */ + +/* Deferred transmit */ +const uint dhd_deferred_tx = 1; + +extern uint dhd_watchdog_ms; +extern void dhd_os_wd_timer(void *bus, uint wdtick); + +/* Tx/Rx bounds */ +uint dhd_txbound; +uint dhd_rxbound; +uint dhd_txminmax; + +/* override the RAM size if possible */ +#define DONGLE_MIN_MEMSIZE (128 *1024) +int dhd_dongle_memsize; + +static bool dhd_doflow; +static bool dhd_alignctl; + +static bool sd1idle; + +static bool retrydata; +#define RETRYCHAN(chan) (((chan) == SDPCM_EVENT_CHANNEL) || retrydata) + +static const uint watermark = 8; +static const uint firstread = DHD_FIRSTREAD; + +#define HDATLEN (firstread - (SDPCM_HDRLEN)) + +/* Retry count for register access failures */ +static const uint retry_limit = 2; + +/* Force even SD lengths (some host controllers mess up on odd bytes) */ +static bool forcealign; + +#define ALIGNMENT 4 + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) +extern void bcmsdh_enable_hw_oob_intr(void *sdh, bool enable); +#endif + +#if defined(OOB_INTR_ONLY) && defined(SDIO_ISR_THREAD) +#error OOB_INTR_ONLY is NOT working with SDIO_ISR_THREAD +#endif /* defined(OOB_INTR_ONLY) && defined(SDIO_ISR_THREAD) */ +#define PKTALIGN(osh, p, len, align) \ + do { \ + uint datalign; \ + datalign = (uintptr)PKTDATA((osh), (p)); \ + datalign = ROUNDUP(datalign, (align)) - datalign; \ + ASSERT(datalign < (align)); \ + ASSERT(PKTLEN((osh), (p)) >= ((len) + datalign)); \ + if (datalign) \ + PKTPULL((osh), (p), datalign); \ + PKTSETLEN((osh), (p), (len)); \ + } while (0) + +/* Limit on rounding up frames */ +static const uint max_roundup = 512; + +/* Try doing readahead */ +static bool dhd_readahead; + + +/* To check if there's window offered */ +#define DATAOK(bus) \ + (((uint8)(bus->tx_max - bus->tx_seq) != 0) && \ + (((uint8)(bus->tx_max - bus->tx_seq) & 0x80) == 0)) + +/* Macros to get register read/write status */ +/* NOTE: these assume a local dhdsdio_bus_t *bus! */ +#define R_SDREG(regvar, regaddr, retryvar) \ +do { \ + retryvar = 0; \ + do { \ + regvar = R_REG(bus->dhd->osh, regaddr); \ + } while (bcmsdh_regfail(bus->sdh) && (++retryvar <= retry_limit)); \ + if (retryvar) { \ + bus->regfails += (retryvar-1); \ + if (retryvar > retry_limit) { \ + DHD_ERROR(("%s: FAILED" #regvar "READ, LINE %d\n", \ + __FUNCTION__, __LINE__)); \ + regvar = 0; \ + } \ + } \ +} while (0) + +#define W_SDREG(regval, regaddr, retryvar) \ +do { \ + retryvar = 0; \ + do { \ + W_REG(bus->dhd->osh, regaddr, regval); \ + } while (bcmsdh_regfail(bus->sdh) && (++retryvar <= retry_limit)); \ + if (retryvar) { \ + bus->regfails += (retryvar-1); \ + if (retryvar > retry_limit) \ + DHD_ERROR(("%s: FAILED REGISTER WRITE, LINE %d\n", \ + __FUNCTION__, __LINE__)); \ + } \ +} while (0) + + +#define DHD_BUS SDIO_BUS + +#define PKT_AVAILABLE() (intstatus & I_HMB_FRAME_IND) + +#define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE) + +#define GSPI_PR55150_BAILOUT + + +#ifdef SDTEST +static void dhdsdio_testrcv(dhd_bus_t *bus, void *pkt, uint seq); +static void dhdsdio_sdtest_set(dhd_bus_t *bus, bool start); +#endif + +#ifdef DHD_DEBUG_TRAP +static int dhdsdio_checkdied(dhd_bus_t *bus, uint8 *data, uint size); +#endif /* DHD_DEBUG_TRAP */ +static int dhdsdio_download_state(dhd_bus_t *bus, bool enter); + +static void dhdsdio_release(dhd_bus_t *bus, osl_t *osh); +static void dhdsdio_release_malloc(dhd_bus_t *bus, osl_t *osh); +static void dhdsdio_disconnect(void *ptr); +static bool dhdsdio_chipmatch(uint16 chipid); +static bool dhdsdio_probe_attach(dhd_bus_t *bus, osl_t *osh, void *sdh, + void * regsva, uint16 devid); +static bool dhdsdio_probe_malloc(dhd_bus_t *bus, osl_t *osh, void *sdh); +static bool dhdsdio_probe_init(dhd_bus_t *bus, osl_t *osh, void *sdh); +static void dhdsdio_release_dongle(dhd_bus_t *bus, osl_t *osh, int reset_flag); + +static uint process_nvram_vars(char *varbuf, uint len); + +static void dhd_dongle_setmemsize(struct dhd_bus *bus, int mem_size); +static int dhd_bcmsdh_recv_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags, + uint8 *buf, uint nbytes, + void *pkt, bcmsdh_cmplt_fn_t complete, void *handle); +static int dhd_bcmsdh_send_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags, + uint8 *buf, uint nbytes, + void *pkt, bcmsdh_cmplt_fn_t complete, void *handle); + +static bool dhdsdio_download_firmware(struct dhd_bus *bus, osl_t *osh, void *sdh); +static int _dhdsdio_download_firmware(struct dhd_bus *bus); + +static int dhdsdio_download_code_file(struct dhd_bus *bus, char *image_path); +static int dhdsdio_download_nvram(struct dhd_bus *bus); +#ifdef BCMEMBEDIMAGE +static int dhdsdio_download_code_array(struct dhd_bus *bus); +#endif + + +static void +dhd_dongle_setmemsize(struct dhd_bus *bus, int mem_size) +{ + int32 min_size = DONGLE_MIN_MEMSIZE; + /* Restrict the memsize to user specified limit */ + DHD_ERROR(("user: Restrict the dongle ram size to %d, min accepted %d\n", + dhd_dongle_memsize, min_size)); + if ((dhd_dongle_memsize > min_size) && + (dhd_dongle_memsize < (int32)bus->orig_ramsize)) + bus->ramsize = dhd_dongle_memsize; +} + +static int +dhdsdio_set_siaddr_window(dhd_bus_t *bus, uint32 address) +{ + int err = 0; + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW, + (address >> 8) & SBSDIO_SBADDRLOW_MASK, &err); + if (!err) + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRMID, + (address >> 16) & SBSDIO_SBADDRMID_MASK, &err); + if (!err) + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRHIGH, + (address >> 24) & SBSDIO_SBADDRHIGH_MASK, &err); + return err; +} + + +/* Turn backplane clock on or off */ +static int +dhdsdio_htclk(dhd_bus_t *bus, bool on, bool pendok) +{ + int err; + uint8 clkctl, clkreq, devctl; + bcmsdh_info_t *sdh; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + +#if defined(OOB_INTR_ONLY) + pendok = FALSE; +#endif + clkctl = 0; + sdh = bus->sdh; + + + if (on) { + /* Request HT Avail */ + clkreq = bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ; + + if ((bus->sih->chip == BCM4329_CHIP_ID) && (bus->sih->chiprev == 0)) + clkreq |= SBSDIO_FORCE_ALP; + + + + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, clkreq, &err); + if (err) { + DHD_ERROR(("%s: HT Avail request error: %d\n", __FUNCTION__, err)); + return BCME_ERROR; + } + + if (pendok && + ((bus->sih->buscoretype == PCMCIA_CORE_ID) && (bus->sih->buscorerev == 9))) { + uint32 dummy, retries; + R_SDREG(dummy, &bus->regs->clockctlstatus, retries); + } + + /* Check current status */ + clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (err) { + DHD_ERROR(("%s: HT Avail read error: %d\n", __FUNCTION__, err)); + return BCME_ERROR; + } + + /* Go to pending and await interrupt if appropriate */ + if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) { + /* Allow only clock-available interrupt */ + devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err); + if (err) { + DHD_ERROR(("%s: Devctl access error setting CA: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + + devctl |= SBSDIO_DEVCTL_CA_INT_ONLY; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err); + DHD_INFO(("CLKCTL: set PENDING\n")); + bus->clkstate = CLK_PENDING; + return BCME_OK; + } else if (bus->clkstate == CLK_PENDING) { + /* Cancel CA-only interrupt filter */ + devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err); + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err); + } + + /* Otherwise, wait here (polling) for HT Avail */ + if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { + SPINWAIT_SLEEP(sdioh_spinwait_sleep, + ((clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, &err)), + !SBSDIO_CLKAV(clkctl, bus->alp_only)), PMU_MAX_TRANSITION_DLY); + } + if (err) { + DHD_ERROR(("%s: HT Avail request error: %d\n", __FUNCTION__, err)); + return BCME_ERROR; + } + if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { + DHD_ERROR(("%s: HT Avail timeout (%d): clkctl 0x%02x\n", + __FUNCTION__, PMU_MAX_TRANSITION_DLY, clkctl)); + return BCME_ERROR; + } + + + /* Mark clock available */ + bus->clkstate = CLK_AVAIL; + DHD_INFO(("CLKCTL: turned ON\n")); + +#if defined(DHD_DEBUG) + if (bus->alp_only == TRUE) { +#if !defined(BCMLXSDMMC) + if (!SBSDIO_ALPONLY(clkctl)) { + DHD_ERROR(("%s: HT Clock, when ALP Only\n", __FUNCTION__)); + } +#endif /* !defined(BCMLXSDMMC) */ + } else { + if (SBSDIO_ALPONLY(clkctl)) { + DHD_ERROR(("%s: HT Clock should be on.\n", __FUNCTION__)); + } + } +#endif /* defined (DHD_DEBUG) */ + + bus->activity = TRUE; + } else { + clkreq = 0; + + if (bus->clkstate == CLK_PENDING) { + /* Cancel CA-only interrupt filter */ + devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err); + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err); + } + + bus->clkstate = CLK_SDONLY; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, clkreq, &err); + DHD_INFO(("CLKCTL: turned OFF\n")); + if (err) { + DHD_ERROR(("%s: Failed access turning clock off: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + } + return BCME_OK; +} + +/* Change idle/active SD state */ +static int +dhdsdio_sdclk(dhd_bus_t *bus, bool on) +{ + int err; + int32 iovalue; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (on) { + if (bus->idleclock == DHD_IDLE_STOP) { + /* Turn on clock and restore mode */ + iovalue = 1; + err = bcmsdh_iovar_op(bus->sdh, "sd_clock", NULL, 0, + &iovalue, sizeof(iovalue), TRUE); + if (err) { + DHD_ERROR(("%s: error enabling sd_clock: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + + iovalue = bus->sd_mode; + err = bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0, + &iovalue, sizeof(iovalue), TRUE); + if (err) { + DHD_ERROR(("%s: error changing sd_mode: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + } else if (bus->idleclock != DHD_IDLE_ACTIVE) { + /* Restore clock speed */ + iovalue = bus->sd_divisor; + err = bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0, + &iovalue, sizeof(iovalue), TRUE); + if (err) { + DHD_ERROR(("%s: error restoring sd_divisor: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + } + bus->clkstate = CLK_SDONLY; + } else { + /* Stop or slow the SD clock itself */ + if ((bus->sd_divisor == -1) || (bus->sd_mode == -1)) { + DHD_TRACE(("%s: can't idle clock, divisor %d mode %d\n", + __FUNCTION__, bus->sd_divisor, bus->sd_mode)); + return BCME_ERROR; + } + if (bus->idleclock == DHD_IDLE_STOP) { + if (sd1idle) { + /* Change to SD1 mode and turn off clock */ + iovalue = 1; + err = bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0, + &iovalue, sizeof(iovalue), TRUE); + if (err) { + DHD_ERROR(("%s: error changing sd_clock: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + } + + iovalue = 0; + err = bcmsdh_iovar_op(bus->sdh, "sd_clock", NULL, 0, + &iovalue, sizeof(iovalue), TRUE); + if (err) { + DHD_ERROR(("%s: error disabling sd_clock: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + } else if (bus->idleclock != DHD_IDLE_ACTIVE) { + /* Set divisor to idle value */ + iovalue = bus->idleclock; + err = bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0, + &iovalue, sizeof(iovalue), TRUE); + if (err) { + DHD_ERROR(("%s: error changing sd_divisor: %d\n", + __FUNCTION__, err)); + return BCME_ERROR; + } + } + bus->clkstate = CLK_NONE; + } + + return BCME_OK; +} + +/* Transition SD and backplane clock readiness */ +static int +dhdsdio_clkctl(dhd_bus_t *bus, uint target, bool pendok) +{ + int ret = BCME_OK; +#ifdef DHD_DEBUG + uint oldstate = bus->clkstate; +#endif /* DHD_DEBUG */ + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* Early exit if we're already there */ + if (bus->clkstate == target) { + if (target == CLK_AVAIL) { + dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms); + bus->activity = TRUE; + } + return ret; + } + + switch (target) { + case CLK_AVAIL: + /* Make sure SD clock is available */ + if (bus->clkstate == CLK_NONE) + dhdsdio_sdclk(bus, TRUE); + /* Now request HT Avail on the backplane */ + ret = dhdsdio_htclk(bus, TRUE, pendok); + if (ret == BCME_OK) { + dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms); + bus->activity = TRUE; + } + break; + + case CLK_SDONLY: + /* Remove HT request, or bring up SD clock */ + if (bus->clkstate == CLK_NONE) + ret = dhdsdio_sdclk(bus, TRUE); + else if (bus->clkstate == CLK_AVAIL) + ret = dhdsdio_htclk(bus, FALSE, FALSE); + else + DHD_ERROR(("dhdsdio_clkctl: request for %d -> %d\n", + bus->clkstate, target)); + if (ret == BCME_OK) + dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms); + break; + + case CLK_NONE: + /* Make sure to remove HT request */ + if (bus->clkstate == CLK_AVAIL) + ret = dhdsdio_htclk(bus, FALSE, FALSE); + /* Now remove the SD clock */ + ret = dhdsdio_sdclk(bus, FALSE); + dhd_os_wd_timer(bus->dhd, 0); + break; + } +#ifdef DHD_DEBUG + DHD_INFO(("dhdsdio_clkctl: %d -> %d\n", oldstate, bus->clkstate)); +#endif /* DHD_DEBUG */ + + return ret; +} + +int +dhdsdio_bussleep(dhd_bus_t *bus, bool sleep) +{ + bcmsdh_info_t *sdh = bus->sdh; + sdpcmd_regs_t *regs = bus->regs; + uint retries = 0; + + DHD_INFO(("dhdsdio_bussleep: request %s (currently %s)\n", + (sleep ? "SLEEP" : "WAKE"), + (bus->sleeping ? "SLEEP" : "WAKE"))); + + /* Done if we're already in the requested state */ + if (sleep == bus->sleeping) + return BCME_OK; + + /* Going to sleep: set the alarm and turn off the lights... */ + if (sleep) { + /* Don't sleep if something is pending */ + if (bus->dpc_sched || bus->rxskip || pktq_len(&bus->txq)) + return BCME_BUSY; + + + /* Disable SDIO interrupts (no longer interested) */ + bcmsdh_intr_disable(bus->sdh); + + /* Make sure the controller has the bus up */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + /* Tell device to start using OOB wakeup */ + W_SDREG(SMB_USE_OOB, ®s->tosbmailbox, retries); + if (retries > retry_limit) + DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n")); + + /* Turn off our contribution to the HT clock request */ + dhdsdio_clkctl(bus, CLK_SDONLY, FALSE); + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, + SBSDIO_FORCE_HW_CLKREQ_OFF, NULL); + + /* Isolate the bus */ + if (bus->sih->chip != BCM4329_CHIP_ID && bus->sih->chip != BCM4319_CHIP_ID) { + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, + SBSDIO_DEVCTL_PADS_ISO, NULL); + } + + /* Change state */ + bus->sleeping = TRUE; + + } else { + /* Waking up: bus power up is ok, set local state */ + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, + 0, NULL); + + /* Force pad isolation off if possible (in case power never toggled) */ + if ((bus->sih->buscoretype == PCMCIA_CORE_ID) && (bus->sih->buscorerev >= 10)) + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, 0, NULL); + + + /* Make sure the controller has the bus up */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + /* Send misc interrupt to indicate OOB not needed */ + W_SDREG(0, ®s->tosbmailboxdata, retries); + if (retries <= retry_limit) + W_SDREG(SMB_DEV_INT, ®s->tosbmailbox, retries); + + if (retries > retry_limit) + DHD_ERROR(("CANNOT SIGNAL CHIP TO CLEAR OOB!!\n")); + + /* Make sure we have SD bus access */ + dhdsdio_clkctl(bus, CLK_SDONLY, FALSE); + + /* Change state */ + bus->sleeping = FALSE; + + /* Enable interrupts again */ + if (bus->intr && (bus->dhd->busstate == DHD_BUS_DATA)) { + bus->intdis = FALSE; + bcmsdh_intr_enable(bus->sdh); + } + } + + return BCME_OK; +} +#if defined(OOB_INTR_ONLY) +void +dhd_enable_oob_intr(struct dhd_bus *bus, bool enable) +{ +#if defined(HW_OOB) + bcmsdh_enable_hw_oob_intr(bus->sdh, enable); +#else + sdpcmd_regs_t *regs = bus->regs; + uint retries = 0; + + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + if (enable == TRUE) { + + /* Tell device to start using OOB wakeup */ + W_SDREG(SMB_USE_OOB, ®s->tosbmailbox, retries); + if (retries > retry_limit) + DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n")); + + } else { + /* Send misc interrupt to indicate OOB not needed */ + W_SDREG(0, ®s->tosbmailboxdata, retries); + if (retries <= retry_limit) + W_SDREG(SMB_DEV_INT, ®s->tosbmailbox, retries); + } + + /* Turn off our contribution to the HT clock request */ + dhdsdio_clkctl(bus, CLK_SDONLY, FALSE); +#endif /* !defined(HW_OOB) */ +} +#endif /* defined(OOB_INTR_ONLY) */ + +#define BUS_WAKE(bus) \ + do { \ + if ((bus)->sleeping) \ + dhdsdio_bussleep((bus), FALSE); \ + } while (0); + + +/* Writes a HW/SW header into the packet and sends it. */ +/* Assumes: (a) header space already there, (b) caller holds lock */ +static int +dhdsdio_txpkt(dhd_bus_t *bus, void *pkt, uint chan, bool free_pkt) +{ + int ret; + osl_t *osh; + uint8 *frame; + uint16 len, pad = 0; + uint32 swheader; + uint retries = 0; + bcmsdh_info_t *sdh; + void *new; + int i; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + sdh = bus->sdh; + osh = bus->dhd->osh; + + if (bus->dhd->dongle_reset) { + ret = BCME_NOTREADY; + goto done; + } + + frame = (uint8*)PKTDATA(osh, pkt); + + /* Add alignment padding, allocate new packet if needed */ + if ((pad = ((uintptr)frame % DHD_SDALIGN))) { + if (PKTHEADROOM(osh, pkt) < pad) { + DHD_INFO(("%s: insufficient headroom %d for %d pad\n", + __FUNCTION__, (int)PKTHEADROOM(osh, pkt), pad)); + bus->dhd->tx_realloc++; + new = PKTGET(osh, (PKTLEN(osh, pkt) + DHD_SDALIGN), TRUE); + if (!new) { + DHD_ERROR(("%s: couldn't allocate new %d-byte packet\n", + __FUNCTION__, PKTLEN(osh, pkt) + DHD_SDALIGN)); + ret = BCME_NOMEM; + goto done; + } + + PKTALIGN(osh, new, PKTLEN(osh, pkt), DHD_SDALIGN); + bcopy(PKTDATA(osh, pkt), PKTDATA(osh, new), PKTLEN(osh, pkt)); + if (free_pkt) + PKTFREE(osh, pkt, TRUE); + /* free the pkt if canned one is not used */ + free_pkt = TRUE; + pkt = new; + frame = (uint8*)PKTDATA(osh, pkt); + ASSERT(((uintptr)frame % DHD_SDALIGN) == 0); + pad = 0; + } else { + PKTPUSH(osh, pkt, pad); + frame = (uint8*)PKTDATA(osh, pkt); + + ASSERT((pad + SDPCM_HDRLEN) <= (int) PKTLEN(osh, pkt)); + bzero(frame, pad + SDPCM_HDRLEN); + } + } + ASSERT(pad < DHD_SDALIGN); + + /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ + len = (uint16)PKTLEN(osh, pkt); + *(uint16*)frame = htol16(len); + *(((uint16*)frame) + 1) = htol16(~len); + + /* Software tag: channel, sequence number, data offset */ + swheader = ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) | bus->tx_seq | + (((pad + SDPCM_HDRLEN) << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK); + htol32_ua_store(swheader, frame + SDPCM_FRAMETAG_LEN); + htol32_ua_store(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader)); + +#ifdef DHD_DEBUG + tx_packets[PKTPRIO(pkt)]++; + if (DHD_BYTES_ON() && + (((DHD_CTL_ON() && (chan == SDPCM_CONTROL_CHANNEL)) || + (DHD_DATA_ON() && (chan != SDPCM_CONTROL_CHANNEL))))) { + prhex("Tx Frame", frame, len); + } else if (DHD_HDRS_ON()) { + prhex("TxHdr", frame, MIN(len, 16)); + } +#endif + + /* Raise len to next SDIO block to eliminate tail command */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + uint16 pad = bus->blocksize - (len % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize)) +#ifdef NOTUSED + if (pad <= PKTTAILROOM(osh, pkt)) +#endif /* NOTUSED */ + len += pad; + } else if (len % DHD_SDALIGN) { + len += DHD_SDALIGN - (len % DHD_SDALIGN); + } + + /* Some controllers have trouble with odd bytes -- round to even */ + if (forcealign && (len & (ALIGNMENT - 1))) { +#ifdef NOTUSED + if (PKTTAILROOM(osh, pkt)) +#endif + len = ROUNDUP(len, ALIGNMENT); +#ifdef NOTUSED + else + DHD_ERROR(("%s: sending unrounded %d-byte packet\n", __FUNCTION__, len)); +#endif + } + + do { + ret = dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC, + frame, len, pkt, NULL, NULL); + bus->f2txdata++; + ASSERT(ret != BCME_PENDING); + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + DHD_INFO(("%s: sdio error %d, abort command and terminate frame.\n", + __FUNCTION__, ret)); + bus->tx_sderrs++; + + bcmsdh_abort(sdh, SDIO_FUNC_2); + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, NULL); + bus->f1regdata++; + + for (i = 0; i < 3; i++) { + uint8 hi, lo; + hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCHI, NULL); + lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCLO, NULL); + bus->f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } + if (ret == 0) { + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + } + } while ((ret < 0) && retrydata && retries++ < TXRETRIES); + +done: + /* restore pkt buffer pointer before calling tx complete routine */ + PKTPULL(osh, pkt, SDPCM_HDRLEN + pad); + dhd_os_sdunlock(bus->dhd); + dhd_txcomplete(bus->dhd, pkt, ret != 0); + dhd_os_sdlock(bus->dhd); + + if (free_pkt) + PKTFREE(osh, pkt, TRUE); + + return ret; +} + +int +dhd_bus_txdata(struct dhd_bus *bus, void *pkt) +{ + int ret = BCME_ERROR; + osl_t *osh; + uint datalen, prec; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + osh = bus->dhd->osh; + datalen = PKTLEN(osh, pkt); + +#ifdef SDTEST + /* Push the test header if doing loopback */ + if (bus->ext_loop) { + uint8* data; + PKTPUSH(osh, pkt, SDPCM_TEST_HDRLEN); + data = PKTDATA(osh, pkt); + *data++ = SDPCM_TEST_ECHOREQ; + *data++ = (uint8)bus->loopid++; + *data++ = (datalen >> 0); + *data++ = (datalen >> 8); + datalen += SDPCM_TEST_HDRLEN; + } +#endif /* SDTEST */ + + /* Add space for the header */ + PKTPUSH(osh, pkt, SDPCM_HDRLEN); + ASSERT(ISALIGNED((uintptr)PKTDATA(osh, pkt), 2)); + + prec = PRIO2PREC((PKTPRIO(pkt) & PRIOMASK)); + + + /* Check for existing queue, current flow-control, pending event, or pending clock */ + if (dhd_deferred_tx || bus->fcstate || pktq_len(&bus->txq) || bus->dpc_sched || + (!DATAOK(bus)) || (bus->flowcontrol & NBITVAL(prec)) || + (bus->clkstate != CLK_AVAIL)) { + DHD_TRACE(("%s: deferring pktq len %d\n", __FUNCTION__, + pktq_len(&bus->txq))); + bus->fcqueued++; + + /* Priority based enq */ + dhd_os_sdlock_txq(bus->dhd); + if (dhd_prec_enq(bus->dhd, &bus->txq, pkt, prec) == FALSE) { + PKTPULL(osh, pkt, SDPCM_HDRLEN); + dhd_txcomplete(bus->dhd, pkt, FALSE); + PKTFREE(osh, pkt, TRUE); + DHD_ERROR(("%s: out of bus->txq !!!\n", __FUNCTION__)); + ret = BCME_NORESOURCE; + } else { + ret = BCME_OK; + } + dhd_os_sdunlock_txq(bus->dhd); + + if ((pktq_len(&bus->txq) >= FCHI) && dhd_doflow) + dhd_txflowcontrol(bus->dhd, 0, ON); + +#ifdef DHD_DEBUG + if (pktq_plen(&bus->txq, prec) > qcount[prec]) + qcount[prec] = pktq_plen(&bus->txq, prec); +#endif + /* Schedule DPC if needed to send queued packet(s) */ + if (dhd_deferred_tx && !bus->dpc_sched) { + bus->dpc_sched = TRUE; + dhd_sched_dpc(bus->dhd); + } + } else { + /* Lock: we're about to use shared data/code (and SDIO) */ + dhd_os_sdlock(bus->dhd); + + /* Otherwise, send it now */ + BUS_WAKE(bus); + /* Make sure back plane ht clk is on, no pending allowed */ + dhdsdio_clkctl(bus, CLK_AVAIL, TRUE); + +#ifndef SDTEST + DHD_TRACE(("%s: calling txpkt\n", __FUNCTION__)); + ret = dhdsdio_txpkt(bus, pkt, SDPCM_DATA_CHANNEL, TRUE); +#else + ret = dhdsdio_txpkt(bus, pkt, + (bus->ext_loop ? SDPCM_TEST_CHANNEL : SDPCM_DATA_CHANNEL), TRUE); +#endif + if (ret) + bus->dhd->tx_errors++; + else + bus->dhd->dstats.tx_bytes += datalen; + + if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, TRUE); + } + + dhd_os_sdunlock(bus->dhd); + } + + + return ret; +} + +static uint +dhdsdio_sendfromq(dhd_bus_t *bus, uint maxframes) +{ + void *pkt; + uint32 intstatus = 0; + uint retries = 0; + int ret = 0, prec_out; + uint cnt = 0; + uint datalen; + uint8 tx_prec_map; + + dhd_pub_t *dhd = bus->dhd; + sdpcmd_regs_t *regs = bus->regs; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + tx_prec_map = ~bus->flowcontrol; + + /* Send frames until the limit or some other event */ + for (cnt = 0; (cnt < maxframes) && DATAOK(bus); cnt++) { + dhd_os_sdlock_txq(bus->dhd); + if ((pkt = pktq_mdeq(&bus->txq, tx_prec_map, &prec_out)) == NULL) { + dhd_os_sdunlock_txq(bus->dhd); + break; + } + dhd_os_sdunlock_txq(bus->dhd); + datalen = PKTLEN(bus->dhd->osh, pkt) - SDPCM_HDRLEN; + +#ifndef SDTEST + ret = dhdsdio_txpkt(bus, pkt, SDPCM_DATA_CHANNEL, TRUE); +#else + ret = dhdsdio_txpkt(bus, pkt, + (bus->ext_loop ? SDPCM_TEST_CHANNEL : SDPCM_DATA_CHANNEL), TRUE); +#endif + if (ret) + bus->dhd->tx_errors++; + else + bus->dhd->dstats.tx_bytes += datalen; + + /* In poll mode, need to check for other events */ + if (!bus->intr && cnt) + { + /* Check device status, signal pending interrupt */ + R_SDREG(intstatus, ®s->intstatus, retries); + bus->f2txdata++; + if (bcmsdh_regfail(bus->sdh)) + break; + if (intstatus & bus->hostintmask) + bus->ipend = TRUE; + } + } + + /* Deflow-control stack if needed */ + if (dhd_doflow && dhd->up && (dhd->busstate == DHD_BUS_DATA) && + dhd->txoff && (pktq_len(&bus->txq) < FCLOW)) + dhd_txflowcontrol(dhd, 0, OFF); + + return cnt; +} + +int +dhd_bus_txctl(struct dhd_bus *bus, uchar *msg, uint msglen) +{ + uint8 *frame; + uint16 len; + uint32 swheader; + uint retries = 0; + bcmsdh_info_t *sdh = bus->sdh; + uint8 doff = 0; + int ret = -1; + int i; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (bus->dhd->dongle_reset) + return -EIO; + + /* Back the pointer to make a room for bus header */ + frame = msg - SDPCM_HDRLEN; + len = (msglen += SDPCM_HDRLEN); + + /* Add alignment padding (optional for ctl frames) */ + if (dhd_alignctl) { + if ((doff = ((uintptr)frame % DHD_SDALIGN))) { + frame -= doff; + len += doff; + msglen += doff; + bzero(frame, doff + SDPCM_HDRLEN); + } + ASSERT(doff < DHD_SDALIGN); + } + doff += SDPCM_HDRLEN; + + /* Round send length to next SDIO block */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + uint16 pad = bus->blocksize - (len % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize)) + len += pad; + } else if (len % DHD_SDALIGN) { + len += DHD_SDALIGN - (len % DHD_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (forcealign && (len & (ALIGNMENT - 1))) + len = ROUNDUP(len, ALIGNMENT); + + ASSERT(ISALIGNED((uintptr)frame, 2)); + + + /* Need to lock here to protect txseq and SDIO tx calls */ + dhd_os_sdlock(bus->dhd); + + BUS_WAKE(bus); + + /* Make sure backplane clock is on */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ + *(uint16*)frame = htol16((uint16)msglen); + *(((uint16*)frame) + 1) = htol16(~msglen); + + /* Software tag: channel, sequence number, data offset */ + swheader = ((SDPCM_CONTROL_CHANNEL << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) + | bus->tx_seq | ((doff << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK); + htol32_ua_store(swheader, frame + SDPCM_FRAMETAG_LEN); + htol32_ua_store(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader)); + + if (!DATAOK(bus)) { + DHD_INFO(("%s: No bus credit bus->tx_max %d, bus->tx_seq %d\n", + __FUNCTION__, bus->tx_max, bus->tx_seq)); + bus->ctrl_frame_stat = TRUE; + /* Send from dpc */ + bus->ctrl_frame_buf = frame; + bus->ctrl_frame_len = len; + + dhd_wait_for_event(bus->dhd, &bus->ctrl_frame_stat); + + if (bus->ctrl_frame_stat == FALSE) { + DHD_INFO(("%s: ctrl_frame_stat == FALSE\n", __FUNCTION__)); + ret = 0; + } else { + if (!bus->dhd->hang_was_sent) + DHD_ERROR(("%s: ctrl_frame_stat == TRUE\n", __FUNCTION__)); + ret = -1; + } + } + + if (ret == -1) { +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_CTL_ON()) { + prhex("Tx Frame", frame, len); + } else if (DHD_HDRS_ON()) { + prhex("TxHdr", frame, MIN(len, 16)); + } +#endif + + do { + bus->ctrl_frame_stat = FALSE; + ret = dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC, + frame, len, NULL, NULL, NULL); + ASSERT(ret != BCME_PENDING); + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + DHD_INFO(("%s: sdio error %d, abort command and terminate frame.\n", + __FUNCTION__, ret)); + bus->tx_sderrs++; + + bcmsdh_abort(sdh, SDIO_FUNC_2); + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, NULL); + bus->f1regdata++; + + for (i = 0; i < 3; i++) { + uint8 hi, lo; + hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCHI, NULL); + lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCLO, NULL); + bus->f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } + if (ret == 0) { + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + } + } while ((ret < 0) && retries++ < TXRETRIES); + } + + if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, TRUE); + } + + dhd_os_sdunlock(bus->dhd); + + if (ret) + bus->dhd->tx_ctlerrs++; + else + bus->dhd->tx_ctlpkts++; + + return ret ? -EIO : 0; +} + +int +dhd_bus_rxctl(struct dhd_bus *bus, uchar *msg, uint msglen) +{ + int timeleft; + uint rxlen = 0; + bool pending; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (bus->dhd->dongle_reset) + return -EIO; + + /* Wait until control frame is available */ + timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->rxlen, &pending); + + dhd_os_sdlock(bus->dhd); + rxlen = bus->rxlen; + bcopy(bus->rxctl, msg, MIN(msglen, rxlen)); + bus->rxlen = 0; + dhd_os_sdunlock(bus->dhd); + + if (rxlen) { + DHD_CTL(("%s: resumed on rxctl frame, got %d expected %d\n", + __FUNCTION__, rxlen, msglen)); + } else if (timeleft == 0) { + DHD_ERROR(("%s: resumed on timeout\n", __FUNCTION__)); +#ifdef DHD_DEBUG_TRAP + dhd_os_sdlock(bus->dhd); + dhdsdio_checkdied(bus, NULL, 0); + dhd_os_sdunlock(bus->dhd); +#endif /* DHD_DEBUG_TRAP */ + } else if (pending == TRUE) { + DHD_CTL(("%s: cancelled\n", __FUNCTION__)); + return -ERESTARTSYS; + } else { + DHD_CTL(("%s: resumed for unknown reason?\n", __FUNCTION__)); +#ifdef DHD_DEBUG_TRAP + dhd_os_sdlock(bus->dhd); + dhdsdio_checkdied(bus, NULL, 0); + dhd_os_sdunlock(bus->dhd); +#endif /* DHD_DEBUG_TRAP */ + } + + if (rxlen) + bus->dhd->rx_ctlpkts++; + else + bus->dhd->rx_ctlerrs++; + + return rxlen ? (int)rxlen : -ETIMEDOUT; +} + +/* IOVar table */ +enum { + IOV_INTR = 1, + IOV_POLLRATE, + IOV_SDREG, + IOV_SBREG, + IOV_SDCIS, + IOV_MEMBYTES, + IOV_MEMSIZE, +#ifdef DHD_DEBUG_TRAP + IOV_CHECKDIED, +#endif + IOV_DOWNLOAD, + IOV_FORCEEVEN, + IOV_SDIOD_DRIVE, + IOV_READAHEAD, + IOV_SDRXCHAIN, + IOV_ALIGNCTL, + IOV_SDALIGN, + IOV_DEVRESET, + IOV_CPU, +#ifdef SDTEST + IOV_PKTGEN, + IOV_EXTLOOP, +#endif /* SDTEST */ + IOV_SPROM, + IOV_TXBOUND, + IOV_RXBOUND, + IOV_TXMINMAX, + IOV_IDLETIME, + IOV_IDLECLOCK, + IOV_SD1IDLE, + IOV_SLEEP, + IOV_VARS +}; + +const bcm_iovar_t dhdsdio_iovars[] = { + {"intr", IOV_INTR, 0, IOVT_BOOL, 0 }, + {"sleep", IOV_SLEEP, 0, IOVT_BOOL, 0 }, + {"pollrate", IOV_POLLRATE, 0, IOVT_UINT32, 0 }, + {"idletime", IOV_IDLETIME, 0, IOVT_INT32, 0 }, + {"idleclock", IOV_IDLECLOCK, 0, IOVT_INT32, 0 }, + {"sd1idle", IOV_SD1IDLE, 0, IOVT_BOOL, 0 }, + {"membytes", IOV_MEMBYTES, 0, IOVT_BUFFER, 2 * sizeof(int) }, + {"memsize", IOV_MEMSIZE, 0, IOVT_UINT32, 0 }, + {"download", IOV_DOWNLOAD, 0, IOVT_BOOL, 0 }, + {"vars", IOV_VARS, 0, IOVT_BUFFER, 0 }, + {"sdiod_drive", IOV_SDIOD_DRIVE, 0, IOVT_UINT32, 0 }, + {"readahead", IOV_READAHEAD, 0, IOVT_BOOL, 0 }, + {"sdrxchain", IOV_SDRXCHAIN, 0, IOVT_BOOL, 0 }, + {"alignctl", IOV_ALIGNCTL, 0, IOVT_BOOL, 0 }, + {"sdalign", IOV_SDALIGN, 0, IOVT_BOOL, 0 }, + {"devreset", IOV_DEVRESET, 0, IOVT_BOOL, 0 }, +#ifdef DHD_DEBUG + {"sdreg", IOV_SDREG, 0, IOVT_BUFFER, sizeof(sdreg_t) }, + {"sbreg", IOV_SBREG, 0, IOVT_BUFFER, sizeof(sdreg_t) }, + {"sd_cis", IOV_SDCIS, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN }, + {"forcealign", IOV_FORCEEVEN, 0, IOVT_BOOL, 0 }, + {"txbound", IOV_TXBOUND, 0, IOVT_UINT32, 0 }, + {"rxbound", IOV_RXBOUND, 0, IOVT_UINT32, 0 }, + {"txminmax", IOV_TXMINMAX, 0, IOVT_UINT32, 0 }, + {"cpu", IOV_CPU, 0, IOVT_BOOL, 0 }, +#endif /* DHD_DEBUG */ +#ifdef DHD_DEBUG_TRAP + {"checkdied", IOV_CHECKDIED, 0, IOVT_BUFFER, 0 }, +#endif /* DHD_DEBUG_TRAP */ +#ifdef SDTEST + {"extloop", IOV_EXTLOOP, 0, IOVT_BOOL, 0 }, + {"pktgen", IOV_PKTGEN, 0, IOVT_BUFFER, sizeof(dhd_pktgen_t) }, +#endif /* SDTEST */ + + {NULL, 0, 0, 0, 0 } +}; + +static void +dhd_dump_pct(struct bcmstrbuf *strbuf, char *desc, uint num, uint div) +{ + uint q1, q2; + + if (!div) { + bcm_bprintf(strbuf, "%s N/A", desc); + } else { + q1 = num / div; + q2 = (100 * (num - (q1 * div))) / div; + bcm_bprintf(strbuf, "%s %d.%02d", desc, q1, q2); + } +} + +void +dhd_bus_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf) +{ + dhd_bus_t *bus = dhdp->bus; + + bcm_bprintf(strbuf, "Bus SDIO structure:\n"); + bcm_bprintf(strbuf, "hostintmask 0x%08x intstatus 0x%08x sdpcm_ver %d\n", + bus->hostintmask, bus->intstatus, bus->sdpcm_ver); + bcm_bprintf(strbuf, "fcstate %d qlen %d tx_seq %d, max %d, rxskip %d rxlen %d rx_seq %d\n", + bus->fcstate, pktq_len(&bus->txq), bus->tx_seq, bus->tx_max, bus->rxskip, + bus->rxlen, bus->rx_seq); + bcm_bprintf(strbuf, "intr %d intrcount %d lastintrs %d spurious %d\n", + bus->intr, bus->intrcount, bus->lastintrs, bus->spurious); + bcm_bprintf(strbuf, "pollrate %d pollcnt %d regfails %d\n", + bus->pollrate, bus->pollcnt, bus->regfails); + + bcm_bprintf(strbuf, "\nAdditional counters:\n"); + bcm_bprintf(strbuf, "tx_sderrs %d fcqueued %d rxrtx %d rx_toolong %d rxc_errors %d\n", + bus->tx_sderrs, bus->fcqueued, bus->rxrtx, bus->rx_toolong, + bus->rxc_errors); + bcm_bprintf(strbuf, "rx_hdrfail %d badhdr %d badseq %d\n", + bus->rx_hdrfail, bus->rx_badhdr, bus->rx_badseq); + bcm_bprintf(strbuf, "fc_rcvd %d, fc_xoff %d, fc_xon %d\n", + bus->fc_rcvd, bus->fc_xoff, bus->fc_xon); + bcm_bprintf(strbuf, "rxglomfail %d, rxglomframes %d, rxglompkts %d\n", + bus->rxglomfail, bus->rxglomframes, bus->rxglompkts); + bcm_bprintf(strbuf, "f2rx (hdrs/data) %d (%d/%d), f2tx %d f1regs %d\n", + (bus->f2rxhdrs + bus->f2rxdata), bus->f2rxhdrs, bus->f2rxdata, + bus->f2txdata, bus->f1regdata); + { + dhd_dump_pct(strbuf, "\nRx: pkts/f2rd", bus->dhd->rx_packets, + (bus->f2rxhdrs + bus->f2rxdata)); + dhd_dump_pct(strbuf, ", pkts/f1sd", bus->dhd->rx_packets, bus->f1regdata); + dhd_dump_pct(strbuf, ", pkts/sd", bus->dhd->rx_packets, + (bus->f2rxhdrs + bus->f2rxdata + bus->f1regdata)); + dhd_dump_pct(strbuf, ", pkts/int", bus->dhd->rx_packets, bus->intrcount); + bcm_bprintf(strbuf, "\n"); + + dhd_dump_pct(strbuf, "Rx: glom pct", (100 * bus->rxglompkts), + bus->dhd->rx_packets); + dhd_dump_pct(strbuf, ", pkts/glom", bus->rxglompkts, bus->rxglomframes); + bcm_bprintf(strbuf, "\n"); + + dhd_dump_pct(strbuf, "Tx: pkts/f2wr", bus->dhd->tx_packets, bus->f2txdata); + dhd_dump_pct(strbuf, ", pkts/f1sd", bus->dhd->tx_packets, bus->f1regdata); + dhd_dump_pct(strbuf, ", pkts/sd", bus->dhd->tx_packets, + (bus->f2txdata + bus->f1regdata)); + dhd_dump_pct(strbuf, ", pkts/int", bus->dhd->tx_packets, bus->intrcount); + bcm_bprintf(strbuf, "\n"); + + dhd_dump_pct(strbuf, "Total: pkts/f2rw", + (bus->dhd->tx_packets + bus->dhd->rx_packets), + (bus->f2txdata + bus->f2rxhdrs + bus->f2rxdata)); + dhd_dump_pct(strbuf, ", pkts/f1sd", + (bus->dhd->tx_packets + bus->dhd->rx_packets), bus->f1regdata); + dhd_dump_pct(strbuf, ", pkts/sd", + (bus->dhd->tx_packets + bus->dhd->rx_packets), + (bus->f2txdata + bus->f2rxhdrs + bus->f2rxdata + bus->f1regdata)); + dhd_dump_pct(strbuf, ", pkts/int", + (bus->dhd->tx_packets + bus->dhd->rx_packets), bus->intrcount); + bcm_bprintf(strbuf, "\n\n"); + } + +#ifdef SDTEST + if (bus->pktgen_count) { + bcm_bprintf(strbuf, "pktgen config and count:\n"); + bcm_bprintf(strbuf, "freq %d count %d print %d total %d min %d len %d\n", + bus->pktgen_freq, bus->pktgen_count, bus->pktgen_print, + bus->pktgen_total, bus->pktgen_minlen, bus->pktgen_maxlen); + bcm_bprintf(strbuf, "send attempts %d rcvd %d fail %d\n", + bus->pktgen_sent, bus->pktgen_rcvd, bus->pktgen_fail); + } +#endif /* SDTEST */ +#ifdef DHD_DEBUG + bcm_bprintf(strbuf, "dpc_sched %d host interrupt%spending\n", + bus->dpc_sched, (bcmsdh_intr_pending(bus->sdh) ? " " : " not ")); + bcm_bprintf(strbuf, "blocksize %d roundup %d\n", bus->blocksize, bus->roundup); +#endif /* DHD_DEBUG */ + bcm_bprintf(strbuf, "clkstate %d activity %d idletime %d idlecount %d sleeping %d\n", + bus->clkstate, bus->activity, bus->idletime, bus->idlecount, bus->sleeping); +} + +void +dhd_bus_clearcounts(dhd_pub_t *dhdp) +{ + dhd_bus_t *bus = (dhd_bus_t *)dhdp->bus; + + bus->intrcount = bus->lastintrs = bus->spurious = bus->regfails = 0; + bus->rxrtx = bus->rx_toolong = bus->rxc_errors = 0; + bus->rx_hdrfail = bus->rx_badhdr = bus->rx_badseq = 0; + bus->tx_sderrs = bus->fc_rcvd = bus->fc_xoff = bus->fc_xon = 0; + bus->rxglomfail = bus->rxglomframes = bus->rxglompkts = 0; + bus->f2rxhdrs = bus->f2rxdata = bus->f2txdata = bus->f1regdata = 0; +} + +#ifdef SDTEST +static int +dhdsdio_pktgen_get(dhd_bus_t *bus, uint8 *arg) +{ + dhd_pktgen_t pktgen; + + pktgen.version = DHD_PKTGEN_VERSION; + pktgen.freq = bus->pktgen_freq; + pktgen.count = bus->pktgen_count; + pktgen.print = bus->pktgen_print; + pktgen.total = bus->pktgen_total; + pktgen.minlen = bus->pktgen_minlen; + pktgen.maxlen = bus->pktgen_maxlen; + pktgen.numsent = bus->pktgen_sent; + pktgen.numrcvd = bus->pktgen_rcvd; + pktgen.numfail = bus->pktgen_fail; + pktgen.mode = bus->pktgen_mode; + pktgen.stop = bus->pktgen_stop; + + bcopy(&pktgen, arg, sizeof(pktgen)); + + return 0; +} + +static int +dhdsdio_pktgen_set(dhd_bus_t *bus, uint8 *arg) +{ + dhd_pktgen_t pktgen; + uint oldcnt, oldmode; + + bcopy(arg, &pktgen, sizeof(pktgen)); + if (pktgen.version != DHD_PKTGEN_VERSION) + return BCME_BADARG; + + oldcnt = bus->pktgen_count; + oldmode = bus->pktgen_mode; + + bus->pktgen_freq = pktgen.freq; + bus->pktgen_count = pktgen.count; + bus->pktgen_print = pktgen.print; + bus->pktgen_total = pktgen.total; + bus->pktgen_minlen = pktgen.minlen; + bus->pktgen_maxlen = pktgen.maxlen; + bus->pktgen_mode = pktgen.mode; + bus->pktgen_stop = pktgen.stop; + + bus->pktgen_tick = bus->pktgen_ptick = 0; + bus->pktgen_len = MAX(bus->pktgen_len, bus->pktgen_minlen); + bus->pktgen_len = MIN(bus->pktgen_len, bus->pktgen_maxlen); + + /* Clear counts for a new pktgen (mode change, or was stopped) */ + if (bus->pktgen_count && (!oldcnt || oldmode != bus->pktgen_mode)) + bus->pktgen_sent = bus->pktgen_rcvd = bus->pktgen_fail = 0; + + return 0; +} +#endif /* SDTEST */ + +static int +dhdsdio_membytes(dhd_bus_t *bus, bool write, uint32 address, uint8 *data, uint size) +{ + int bcmerror = 0; + uint32 sdaddr; + uint dsize; + + /* Determine initial transfer parameters */ + sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK; + if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK) + dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr); + else + dsize = size; + + /* Set the backplane window to include the start address */ + if ((bcmerror = dhdsdio_set_siaddr_window(bus, address))) { + DHD_ERROR(("%s: window change failed\n", __FUNCTION__)); + goto xfer_done; + } + + /* Do the transfer(s) */ + while (size) { + DHD_INFO(("%s: %s %d bytes at offset 0x%08x in window 0x%08x\n", + __FUNCTION__, (write ? "write" : "read"), dsize, sdaddr, + (address & SBSDIO_SBWINDOW_MASK))); + if ((bcmerror = bcmsdh_rwdata(bus->sdh, write, sdaddr, data, dsize))) { + DHD_ERROR(("%s: membytes transfer failed\n", __FUNCTION__)); + break; + } + + /* Adjust for next transfer (if any) */ + if ((size -= dsize)) { + data += dsize; + address += dsize; + if ((bcmerror = dhdsdio_set_siaddr_window(bus, address))) { + DHD_ERROR(("%s: window change failed\n", __FUNCTION__)); + break; + } + sdaddr = 0; + dsize = MIN(SBSDIO_SB_OFT_ADDR_LIMIT, size); + } + } + +xfer_done: + /* Return the window to backplane enumeration space for core access */ + if (dhdsdio_set_siaddr_window(bus, bcmsdh_cur_sbwad(bus->sdh))) { + DHD_ERROR(("%s: FAILED to set window back to 0x%x\n", __FUNCTION__, + bcmsdh_cur_sbwad(bus->sdh))); + } + + return bcmerror; +} + +#ifdef DHD_DEBUG_TRAP +static int +dhdsdio_readshared(dhd_bus_t *bus, sdpcm_shared_t *sh) +{ + uint32 addr; + int rv; + + /* Read last word in memory to determine address of sdpcm_shared structure */ + if ((rv = dhdsdio_membytes(bus, FALSE, bus->ramsize - 4, (uint8 *)&addr, 4)) < 0) + return rv; + + addr = ltoh32(addr); + + DHD_INFO(("sdpcm_shared address 0x%08X\n", addr)); + + /* + * Check if addr is valid. + * NVRAM length at the end of memory should have been overwritten. + */ + if (addr == 0 || ((~addr >> 16) & 0xffff) == (addr & 0xffff)) { + DHD_ERROR(("%s: address (0x%08x) of sdpcm_shared invalid\n", __FUNCTION__, addr)); + return BCME_ERROR; + } + + /* Read hndrte_shared structure */ + if ((rv = dhdsdio_membytes(bus, FALSE, addr, (uint8 *)sh, sizeof(sdpcm_shared_t))) < 0) + return rv; + + /* Endianness */ + sh->flags = ltoh32(sh->flags); + sh->trap_addr = ltoh32(sh->trap_addr); + sh->assert_exp_addr = ltoh32(sh->assert_exp_addr); + sh->assert_file_addr = ltoh32(sh->assert_file_addr); + sh->assert_line = ltoh32(sh->assert_line); + sh->console_addr = ltoh32(sh->console_addr); + sh->msgtrace_addr = ltoh32(sh->msgtrace_addr); + + if ((sh->flags & SDPCM_SHARED_VERSION_MASK) != SDPCM_SHARED_VERSION) { + DHD_ERROR(("%s: sdpcm_shared version %d in dhd " + "is different than sdpcm_shared version %d in dongle\n", + __FUNCTION__, SDPCM_SHARED_VERSION, + sh->flags & SDPCM_SHARED_VERSION_MASK)); + return BCME_ERROR; + } + + return BCME_OK; +} + +static int +dhdsdio_checkdied(dhd_bus_t *bus, uint8 *data, uint size) +{ + int bcmerror = 0; + uint msize = 512; + char *mbuffer = NULL; + uint maxstrlen = 256; + char *str = NULL; + trap_t tr; + sdpcm_shared_t sdpcm_shared; + struct bcmstrbuf strbuf; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (data == NULL) { + /* + * Called after a rx ctrl timeout. "data" is NULL. + * allocate memory to trace the trap or assert. + */ + size = msize; + mbuffer = data = MALLOC(bus->dhd->osh, msize); + if (mbuffer == NULL) { + DHD_ERROR(("%s: MALLOC(%d) failed \n", __FUNCTION__, msize)); + bcmerror = BCME_NOMEM; + goto done; + } + } + + if ((str = MALLOC(bus->dhd->osh, maxstrlen)) == NULL) { + DHD_ERROR(("%s: MALLOC(%d) failed \n", __FUNCTION__, maxstrlen)); + bcmerror = BCME_NOMEM; + goto done; + } + + if ((bcmerror = dhdsdio_readshared(bus, &sdpcm_shared)) < 0) + goto done; + + bcm_binit(&strbuf, data, size); + + bcm_bprintf(&strbuf, "msgtrace address : 0x%08X\nconsole address : 0x%08X\n", + sdpcm_shared.msgtrace_addr, sdpcm_shared.console_addr); + + if ((sdpcm_shared.flags & SDPCM_SHARED_ASSERT_BUILT) == 0) { + /* NOTE: Misspelled assert is intentional - DO NOT FIX. + * (Avoids conflict with real asserts for programmatic parsing of output.) + */ + bcm_bprintf(&strbuf, "Assrt not built in dongle\n"); + } + + if ((sdpcm_shared.flags & (SDPCM_SHARED_ASSERT|SDPCM_SHARED_TRAP)) == 0) { + /* NOTE: Misspelled assert is intentional - DO NOT FIX. + * (Avoids conflict with real asserts for programmatic parsing of output.) + */ + bcm_bprintf(&strbuf, "No trap%s in dongle", + (sdpcm_shared.flags & SDPCM_SHARED_ASSERT_BUILT) + ?"/assrt" :""); + } else { + if (sdpcm_shared.flags & SDPCM_SHARED_ASSERT) { + /* Download assert */ + bcm_bprintf(&strbuf, "Dongle assert"); + if (sdpcm_shared.assert_exp_addr != 0) { + str[0] = '\0'; + if ((bcmerror = dhdsdio_membytes(bus, FALSE, + sdpcm_shared.assert_exp_addr, + (uint8 *)str, maxstrlen)) < 0) + goto done; + + str[maxstrlen - 1] = '\0'; + bcm_bprintf(&strbuf, " expr \"%s\"", str); + } + + if (sdpcm_shared.assert_file_addr != 0) { + str[0] = '\0'; + if ((bcmerror = dhdsdio_membytes(bus, FALSE, + sdpcm_shared.assert_file_addr, + (uint8 *)str, maxstrlen)) < 0) + goto done; + + str[maxstrlen - 1] = '\0'; + bcm_bprintf(&strbuf, " file \"%s\"", str); + } + + bcm_bprintf(&strbuf, " line %d ", sdpcm_shared.assert_line); + } + + if (sdpcm_shared.flags & SDPCM_SHARED_TRAP) { + if ((bcmerror = dhdsdio_membytes(bus, FALSE, + sdpcm_shared.trap_addr, + (uint8*)&tr, sizeof(trap_t))) < 0) + goto done; + + bcm_bprintf(&strbuf, + "Dongle trap type 0x%x @ epc 0x%x, cpsr 0x%x, spsr 0x%x, sp 0x%x," + "lp 0x%x, rpc 0x%x Trap offset 0x%x, " + "r0 0x%x, r1 0x%x, r2 0x%x, r3 0x%x, r4 0x%x, r5 0x%x, r6 0x%x, r7 0x%x\n", + tr.type, tr.epc, tr.cpsr, tr.spsr, tr.r13, tr.r14, tr.pc, + sdpcm_shared.trap_addr, + tr.r0, tr.r1, tr.r2, tr.r3, tr.r4, tr.r5, tr.r6, tr.r7); + } + } + + if (sdpcm_shared.flags & (SDPCM_SHARED_ASSERT | SDPCM_SHARED_TRAP)) { + DHD_ERROR(("%s: %s\n", __FUNCTION__, strbuf.origbuf)); + } + +done: + if (mbuffer) + MFREE(bus->dhd->osh, mbuffer, msize); + if (str) + MFREE(bus->dhd->osh, str, maxstrlen); + + return bcmerror; +} +#endif /* DHD_DEBUG_TRAP */ + +#ifdef DHD_DEBUG +#define CONSOLE_LINE_MAX 192 + +static int +dhdsdio_readconsole(dhd_bus_t *bus) +{ + dhd_console_t *c = &bus->console; + uint8 line[CONSOLE_LINE_MAX], ch; + uint32 n, idx, addr; + int rv; + + /* Don't do anything until FWREADY updates console address */ + if (bus->console_addr == 0) + return 0; + + /* Read console log struct */ + addr = bus->console_addr + OFFSETOF(hndrte_cons_t, log); + if ((rv = dhdsdio_membytes(bus, FALSE, addr, (uint8 *)&c->log, sizeof(c->log))) < 0) + return rv; + + /* Allocate console buffer (one time only) */ + if (c->buf == NULL) { + c->bufsize = ltoh32(c->log.buf_size); + if ((c->buf = MALLOC(bus->dhd->osh, c->bufsize)) == NULL) + return BCME_NOMEM; + } + + idx = ltoh32(c->log.idx); + + /* Protect against corrupt value */ + if (idx > c->bufsize) + return BCME_ERROR; + + /* Skip reading the console buffer if the index pointer has not moved */ + if (idx == c->last) + return BCME_OK; + + /* Read the console buffer */ + addr = ltoh32(c->log.buf); + if ((rv = dhdsdio_membytes(bus, FALSE, addr, c->buf, c->bufsize)) < 0) + return rv; + + while (c->last != idx) { + for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) { + if (c->last == idx) { + /* This would output a partial line. Instead, back up + * the buffer pointer and output this line next time around. + */ + if (c->last >= n) + c->last -= n; + else + c->last = c->bufsize - n; + goto break2; + } + ch = c->buf[c->last]; + c->last = (c->last + 1) % c->bufsize; + if (ch == '\n') + break; + line[n] = ch; + } + + if (n > 0) { + if (line[n - 1] == '\r') + n--; + line[n] = 0; + printf("CONSOLE: %s\n", line); + } + } +break2: + + return BCME_OK; +} +#endif /* DHD_DEBUG */ + +int +dhdsdio_downloadvars(dhd_bus_t *bus, void *arg, int len) +{ + int bcmerror = BCME_OK; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* Basic sanity checks */ + if (bus->dhd->up) { + bcmerror = BCME_NOTDOWN; + goto err; + } + if (!len) { + bcmerror = BCME_BUFTOOSHORT; + goto err; + } + + /* Free the old ones and replace with passed variables */ + if (bus->vars) + MFREE(bus->dhd->osh, bus->vars, bus->varsz); + + bus->vars = MALLOC(bus->dhd->osh, len); + bus->varsz = bus->vars ? len : 0; + if (bus->vars == NULL) { + bcmerror = BCME_NOMEM; + goto err; + } + + /* Copy the passed variables, which should include the terminating double-null */ + bcopy(arg, bus->vars, bus->varsz); +err: + return bcmerror; +} + +static int +dhdsdio_doiovar(dhd_bus_t *bus, const bcm_iovar_t *vi, uint32 actionid, const char *name, + void *params, int plen, void *arg, int len, int val_size) +{ + int bcmerror = 0; + int32 int_val = 0; + bool bool_val = 0; + + DHD_TRACE(("%s: Enter, action %d name %s params %p plen %d arg %p len %d val_size %d\n", + __FUNCTION__, actionid, name, params, plen, arg, len, val_size)); + + if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid))) != 0) + goto exit; + + if (plen >= (int)sizeof(int_val)) + bcopy(params, &int_val, sizeof(int_val)); + + bool_val = (int_val != 0) ? TRUE : FALSE; + + + /* Some ioctls use the bus */ + dhd_os_sdlock(bus->dhd); + + /* Check if dongle is in reset. If so, only allow DEVRESET iovars */ + if (bus->dhd->dongle_reset && !(actionid == IOV_SVAL(IOV_DEVRESET) || + actionid == IOV_GVAL(IOV_DEVRESET))) { + bcmerror = BCME_NOTREADY; + goto exit; + } + + /* Handle sleep stuff before any clock mucking */ + if (vi->varid == IOV_SLEEP) { + if (IOV_ISSET(actionid)) { + bcmerror = dhdsdio_bussleep(bus, bool_val); + } else { + int_val = (int32)bus->sleeping; + bcopy(&int_val, arg, val_size); + } + goto exit; + } + + /* Request clock to allow SDIO accesses */ + if (!bus->dhd->dongle_reset) { + BUS_WAKE(bus); + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + } + + switch (actionid) { + case IOV_GVAL(IOV_INTR): + int_val = (int32)bus->intr; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_INTR): + bus->intr = bool_val; + bus->intdis = FALSE; + if (bus->dhd->up) { + if (bus->intr) { + DHD_INTR(("%s: enable SDIO device interrupts\n", __FUNCTION__)); + bcmsdh_intr_enable(bus->sdh); + } else { + DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__)); + bcmsdh_intr_disable(bus->sdh); + } + } + break; + + case IOV_GVAL(IOV_POLLRATE): + int_val = (int32)bus->pollrate; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_POLLRATE): + bus->pollrate = (uint)int_val; + bus->poll = (bus->pollrate != 0); + break; + + case IOV_GVAL(IOV_IDLETIME): + int_val = bus->idletime; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_IDLETIME): + if ((int_val < 0) && (int_val != DHD_IDLE_IMMEDIATE)) { + bcmerror = BCME_BADARG; + } else { + bus->idletime = int_val; + } + break; + + case IOV_GVAL(IOV_IDLECLOCK): + int_val = (int32)bus->idleclock; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_IDLECLOCK): + bus->idleclock = int_val; + break; + + case IOV_GVAL(IOV_SD1IDLE): + int_val = (int32)sd1idle; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_SD1IDLE): + sd1idle = bool_val; + break; + + + case IOV_SVAL(IOV_MEMBYTES): + case IOV_GVAL(IOV_MEMBYTES): + { + uint32 address; + uint size, dsize; + uint8 *data; + + bool set = (actionid == IOV_SVAL(IOV_MEMBYTES)); + + ASSERT(plen >= 2*sizeof(int)); + + address = (uint32)int_val; + bcopy((char *)params + sizeof(int_val), &int_val, sizeof(int_val)); + size = (uint)int_val; + + /* Do some validation */ + dsize = set ? plen - (2 * sizeof(int)) : len; + if (dsize < size) { + DHD_ERROR(("%s: error on %s membytes, addr 0x%08x size %d dsize %d\n", + __FUNCTION__, (set ? "set" : "get"), address, size, dsize)); + bcmerror = BCME_BADARG; + break; + } + + DHD_INFO(("%s: Request to %s %d bytes at address 0x%08x\n", __FUNCTION__, + (set ? "write" : "read"), size, address)); + + /* If we know about SOCRAM, check for a fit */ + if ((bus->orig_ramsize) && + ((address > bus->orig_ramsize) || (address + size > bus->orig_ramsize))) { + DHD_ERROR(("%s: ramsize 0x%08x doesn't have %d bytes at 0x%08x\n", + __FUNCTION__, bus->orig_ramsize, size, address)); + bcmerror = BCME_BADARG; + break; + } + + /* Generate the actual data pointer */ + data = set ? (uint8*)params + 2 * sizeof(int): (uint8*)arg; + + /* Call to do the transfer */ + bcmerror = dhdsdio_membytes(bus, set, address, data, size); + + break; + } + + case IOV_GVAL(IOV_MEMSIZE): + int_val = (int32)bus->ramsize; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_SDIOD_DRIVE): + int_val = (int32)dhd_sdiod_drive_strength; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_SDIOD_DRIVE): + dhd_sdiod_drive_strength = int_val; + si_sdiod_drive_strength_init(bus->sih, bus->dhd->osh, dhd_sdiod_drive_strength); + break; + + case IOV_SVAL(IOV_DOWNLOAD): + bcmerror = dhdsdio_download_state(bus, bool_val); + break; + + case IOV_SVAL(IOV_VARS): + bcmerror = dhdsdio_downloadvars(bus, arg, len); + break; + + case IOV_GVAL(IOV_READAHEAD): + int_val = (int32)dhd_readahead; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_READAHEAD): + if (bool_val && !dhd_readahead) + bus->nextlen = 0; + dhd_readahead = bool_val; + break; + + case IOV_GVAL(IOV_SDRXCHAIN): + int_val = (int32)bus->use_rxchain; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_SDRXCHAIN): + if (bool_val && !bus->sd_rxchain) + bcmerror = BCME_UNSUPPORTED; + else + bus->use_rxchain = bool_val; + break; + case IOV_GVAL(IOV_ALIGNCTL): + int_val = (int32)dhd_alignctl; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_ALIGNCTL): + dhd_alignctl = bool_val; + break; + + case IOV_GVAL(IOV_SDALIGN): + int_val = DHD_SDALIGN; + bcopy(&int_val, arg, val_size); + break; + +#ifdef DHD_DEBUG + case IOV_GVAL(IOV_VARS): + if (bus->varsz < (uint)len) + bcopy(bus->vars, arg, bus->varsz); + else + bcmerror = BCME_BUFTOOSHORT; + break; +#endif /* DHD_DEBUG */ + +#ifdef DHD_DEBUG + case IOV_GVAL(IOV_SDREG): + { + sdreg_t *sd_ptr; + uint32 addr, size; + + sd_ptr = (sdreg_t *)params; + + addr = (uintptr)bus->regs + sd_ptr->offset; + size = sd_ptr->func; + int_val = (int32)bcmsdh_reg_read(bus->sdh, addr, size); + if (bcmsdh_regfail(bus->sdh)) + bcmerror = BCME_SDIO_ERROR; + bcopy(&int_val, arg, sizeof(int32)); + break; + } + + case IOV_SVAL(IOV_SDREG): + { + sdreg_t *sd_ptr; + uint32 addr, size; + + sd_ptr = (sdreg_t *)params; + + addr = (uintptr)bus->regs + sd_ptr->offset; + size = sd_ptr->func; + bcmsdh_reg_write(bus->sdh, addr, size, sd_ptr->value); + if (bcmsdh_regfail(bus->sdh)) + bcmerror = BCME_SDIO_ERROR; + break; + } + + /* Same as above, but offset is not backplane (not SDIO core) */ + case IOV_GVAL(IOV_SBREG): + { + sdreg_t sdreg; + uint32 addr, size; + + bcopy(params, &sdreg, sizeof(sdreg)); + + addr = SI_ENUM_BASE + sdreg.offset; + size = sdreg.func; + int_val = (int32)bcmsdh_reg_read(bus->sdh, addr, size); + if (bcmsdh_regfail(bus->sdh)) + bcmerror = BCME_SDIO_ERROR; + bcopy(&int_val, arg, sizeof(int32)); + break; + } + + case IOV_SVAL(IOV_SBREG): + { + sdreg_t sdreg; + uint32 addr, size; + + bcopy(params, &sdreg, sizeof(sdreg)); + + addr = SI_ENUM_BASE + sdreg.offset; + size = sdreg.func; + bcmsdh_reg_write(bus->sdh, addr, size, sdreg.value); + if (bcmsdh_regfail(bus->sdh)) + bcmerror = BCME_SDIO_ERROR; + break; + } + + case IOV_GVAL(IOV_SDCIS): + { + *(char *)arg = 0; + + bcmstrcat(arg, "\nFunc 0\n"); + bcmsdh_cis_read(bus->sdh, 0x10, (uint8 *)arg + strlen(arg), SBSDIO_CIS_SIZE_LIMIT); + bcmstrcat(arg, "\nFunc 1\n"); + bcmsdh_cis_read(bus->sdh, 0x11, (uint8 *)arg + strlen(arg), SBSDIO_CIS_SIZE_LIMIT); + bcmstrcat(arg, "\nFunc 2\n"); + bcmsdh_cis_read(bus->sdh, 0x12, (uint8 *)arg + strlen(arg), SBSDIO_CIS_SIZE_LIMIT); + break; + } + + case IOV_GVAL(IOV_FORCEEVEN): + int_val = (int32)forcealign; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_FORCEEVEN): + forcealign = bool_val; + break; + + case IOV_GVAL(IOV_TXBOUND): + int_val = (int32)dhd_txbound; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_TXBOUND): + dhd_txbound = (uint)int_val; + break; + + case IOV_GVAL(IOV_RXBOUND): + int_val = (int32)dhd_rxbound; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_RXBOUND): + dhd_rxbound = (uint)int_val; + break; + + case IOV_GVAL(IOV_TXMINMAX): + int_val = (int32)dhd_txminmax; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_TXMINMAX): + dhd_txminmax = (uint)int_val; + break; + + + +#endif /* DHD_DEBUG */ + + +#ifdef SDTEST + case IOV_GVAL(IOV_EXTLOOP): + int_val = (int32)bus->ext_loop; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_EXTLOOP): + bus->ext_loop = bool_val; + break; + + case IOV_GVAL(IOV_PKTGEN): + bcmerror = dhdsdio_pktgen_get(bus, arg); + break; + + case IOV_SVAL(IOV_PKTGEN): + bcmerror = dhdsdio_pktgen_set(bus, arg); + break; +#endif /* SDTEST */ + + + case IOV_SVAL(IOV_DEVRESET): + DHD_TRACE(("%s: Called set IOV_DEVRESET=%d dongle_reset=%d busstate=%d\n", + __FUNCTION__, bool_val, bus->dhd->dongle_reset, + bus->dhd->busstate)); + + ASSERT(bus->dhd->osh); + /* ASSERT(bus->cl_devid); */ + + dhd_bus_devreset(bus->dhd, (uint8)bool_val); + + break; + + case IOV_GVAL(IOV_DEVRESET): + DHD_TRACE(("%s: Called get IOV_DEVRESET\n", __FUNCTION__)); + + /* Get its status */ + int_val = (bool) bus->dhd->dongle_reset; + bcopy(&int_val, arg, val_size); + + break; + + default: + bcmerror = BCME_UNSUPPORTED; + break; + } + +exit: + if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, TRUE); + } + + dhd_os_sdunlock(bus->dhd); + + if (actionid == IOV_SVAL(IOV_DEVRESET) && bool_val == FALSE) + dhd_preinit_ioctls((dhd_pub_t *) bus->dhd); + + return bcmerror; +} + +static int +dhdsdio_write_vars(dhd_bus_t *bus) +{ + int bcmerror = 0; + uint32 varsize; + uint32 varaddr; + uint8 *vbuffer; + uint32 varsizew; +#ifdef DHD_DEBUG + char *nvram_ularray; +#endif /* DHD_DEBUG */ + + /* Even if there are no vars are to be written, we still need to set the ramsize. */ + varsize = bus->varsz ? ROUNDUP(bus->varsz, 4) : 0; + varaddr = (bus->ramsize - 4) - varsize; + + if (bus->vars) { + vbuffer = (uint8 *)MALLOC(bus->dhd->osh, varsize); + if (!vbuffer) + return BCME_NOMEM; + + bzero(vbuffer, varsize); + bcopy(bus->vars, vbuffer, bus->varsz); + + /* Write the vars list */ + bcmerror = dhdsdio_membytes(bus, TRUE, varaddr, vbuffer, varsize); +#ifdef DHD_DEBUG + /* Verify NVRAM bytes */ + DHD_INFO(("Compare NVRAM dl & ul; varsize=%d\n", varsize)); + nvram_ularray = (char*)MALLOC(bus->dhd->osh, varsize); + if (!nvram_ularray) + return BCME_NOMEM; + + /* Upload image to verify downloaded contents. */ + memset(nvram_ularray, 0xaa, varsize); + + /* Read the vars list to temp buffer for comparison */ + bcmerror = dhdsdio_membytes(bus, FALSE, varaddr, nvram_ularray, varsize); + if (bcmerror) { + DHD_ERROR(("%s: error %d on reading %d nvram bytes at 0x%08x\n", + __FUNCTION__, bcmerror, varsize, varaddr)); + } + /* Compare the org NVRAM with the one read from RAM */ + if (memcmp(vbuffer, nvram_ularray, varsize)) { + DHD_ERROR(("%s: Downloaded NVRAM image is corrupted.\n", __FUNCTION__)); + } else + DHD_ERROR(("%s: Download, Upload and compare of NVRAM succeeded.\n", + __FUNCTION__)); + + MFREE(bus->dhd->osh, nvram_ularray, varsize); +#endif /* DHD_DEBUG */ + + MFREE(bus->dhd->osh, vbuffer, varsize); + } + + /* adjust to the user specified RAM */ + DHD_INFO(("Physical memory size: %d, usable memory size: %d\n", + bus->orig_ramsize, bus->ramsize)); + DHD_INFO(("Vars are at %d, orig varsize is %d\n", + varaddr, varsize)); + varsize = ((bus->orig_ramsize - 4) - varaddr); + + /* + * Determine the length token: + * Varsize, converted to words, in lower 16-bits, checksum in upper 16-bits. + */ + if (bcmerror) { + varsizew = 0; + } else { + varsizew = varsize / 4; + varsizew = (~varsizew << 16) | (varsizew & 0x0000FFFF); + varsizew = htol32(varsizew); + } + + DHD_INFO(("New varsize is %d, length token=0x%08x\n", varsize, varsizew)); + + /* Write the length token to the last word */ + bcmerror = dhdsdio_membytes(bus, TRUE, (bus->orig_ramsize - 4), + (uint8*)&varsizew, 4); + + return bcmerror; +} + +static int +dhdsdio_download_state(dhd_bus_t *bus, bool enter) +{ + uint retries; + int bcmerror = 0; + + /* To enter download state, disable ARM and reset SOCRAM. + * To exit download state, simply reset ARM (default is RAM boot). + */ + if (enter) { + + bus->alp_only = TRUE; + + if (!(si_setcore(bus->sih, ARM7S_CORE_ID, 0)) && + !(si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) { + DHD_ERROR(("%s: Failed to find ARM core!\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + goto fail; + } + + si_core_disable(bus->sih, 0); + if (bcmsdh_regfail(bus->sdh)) { + bcmerror = BCME_SDIO_ERROR; + goto fail; + } + + if (!(si_setcore(bus->sih, SOCRAM_CORE_ID, 0))) { + DHD_ERROR(("%s: Failed to find SOCRAM core!\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + goto fail; + } + + si_core_reset(bus->sih, 0, 0); + if (bcmsdh_regfail(bus->sdh)) { + DHD_ERROR(("%s: Failure trying reset SOCRAM core?\n", __FUNCTION__)); + bcmerror = BCME_SDIO_ERROR; + goto fail; + } + + /* Clear the top bit of memory */ + if (bus->ramsize) { + uint32 zeros = 0; + dhdsdio_membytes(bus, TRUE, bus->ramsize - 4, (uint8*)&zeros, 4); + } + } else { + if (!(si_setcore(bus->sih, SOCRAM_CORE_ID, 0))) { + DHD_ERROR(("%s: Failed to find SOCRAM core!\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + goto fail; + } + + if (!si_iscoreup(bus->sih)) { + DHD_ERROR(("%s: SOCRAM core is down after reset?\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + goto fail; + } + + if ((bcmerror = dhdsdio_write_vars(bus))) { + DHD_ERROR(("%s: no vars written to RAM\n", __FUNCTION__)); + bcmerror = 0; + } + + if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0) && + !si_setcore(bus->sih, SDIOD_CORE_ID, 0)) { + DHD_ERROR(("%s: Can't change back to SDIO core?\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + goto fail; + } + W_SDREG(0xFFFFFFFF, &bus->regs->intstatus, retries); + + + if (!(si_setcore(bus->sih, ARM7S_CORE_ID, 0)) && + !(si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) { + DHD_ERROR(("%s: Failed to find ARM core!\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + goto fail; + } + + si_core_reset(bus->sih, 0, 0); + if (bcmsdh_regfail(bus->sdh)) { + DHD_ERROR(("%s: Failure trying to reset ARM core?\n", __FUNCTION__)); + bcmerror = BCME_SDIO_ERROR; + goto fail; + } + + /* Allow HT Clock now that the ARM is running. */ + bus->alp_only = FALSE; + + bus->dhd->busstate = DHD_BUS_LOAD; + } + +fail: + /* Always return to SDIOD core */ + if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0)) + si_setcore(bus->sih, SDIOD_CORE_ID, 0); + + return bcmerror; +} + +int +dhd_bus_iovar_op(dhd_pub_t *dhdp, const char *name, + void *params, int plen, void *arg, int len, bool set) +{ + dhd_bus_t *bus = dhdp->bus; + const bcm_iovar_t *vi = NULL; + int bcmerror = 0; + int val_size; + uint32 actionid; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + ASSERT(name); + ASSERT(len >= 0); + + /* Get MUST have return space */ + ASSERT(set || (arg && len)); + + /* Set does NOT take qualifiers */ + ASSERT(!set || (!params && !plen)); + + /* Look up var locally; if not found pass to host driver */ + if ((vi = bcm_iovar_lookup(dhdsdio_iovars, name)) == NULL) { + dhd_os_sdlock(bus->dhd); + + BUS_WAKE(bus); + + /* Turn on clock in case SD command needs backplane */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + bcmerror = bcmsdh_iovar_op(bus->sdh, name, params, plen, arg, len, set); + + /* Check for bus configuration changes of interest */ + + /* If it was divisor change, read the new one */ + if (set && strcmp(name, "sd_divisor") == 0) { + if (bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0, + &bus->sd_divisor, sizeof(int32), FALSE) != BCME_OK) { + bus->sd_divisor = -1; + DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, name)); + } else { + DHD_INFO(("%s: noted %s update, value now %d\n", + __FUNCTION__, name, bus->sd_divisor)); + } + } + /* If it was a mode change, read the new one */ + if (set && strcmp(name, "sd_mode") == 0) { + if (bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0, + &bus->sd_mode, sizeof(int32), FALSE) != BCME_OK) { + bus->sd_mode = -1; + DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, name)); + } else { + DHD_INFO(("%s: noted %s update, value now %d\n", + __FUNCTION__, name, bus->sd_mode)); + } + } + /* Similar check for blocksize change */ + if (set && strcmp(name, "sd_blocksize") == 0) { + int32 fnum = 2; + if (bcmsdh_iovar_op(bus->sdh, "sd_blocksize", &fnum, sizeof(int32), + &bus->blocksize, sizeof(int32), FALSE) != BCME_OK) { + bus->blocksize = 0; + DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, "sd_blocksize")); + } else { + DHD_INFO(("%s: noted %s update, value now %d\n", + __FUNCTION__, "sd_blocksize", bus->blocksize)); + } + } + bus->roundup = MIN(max_roundup, bus->blocksize); + + if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, TRUE); + } + + dhd_os_sdunlock(bus->dhd); + goto exit; + } + + DHD_CTL(("%s: %s %s, len %d plen %d\n", __FUNCTION__, + name, (set ? "set" : "get"), len, plen)); + + /* set up 'params' pointer in case this is a set command so that + * the convenience int and bool code can be common to set and get + */ + 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 + /* all other types are integer sized */ + val_size = sizeof(int); + + actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid); + bcmerror = dhdsdio_doiovar(bus, vi, actionid, name, params, plen, arg, len, val_size); + +exit: + return bcmerror; +} + +void +dhd_bus_stop(struct dhd_bus *bus, bool enforce_mutex) +{ + osl_t *osh = bus->dhd->osh; + uint32 local_hostintmask; + uint8 saveclk; + uint retries; + int err; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (enforce_mutex) + dhd_os_sdlock(bus->dhd); + + BUS_WAKE(bus); + + /* Change our idea of bus state */ + bus->dhd->busstate = DHD_BUS_DOWN; + + /* Enable clock for device interrupts */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + /* Disable and clear interrupts at the chip level also */ + W_SDREG(0, &bus->regs->hostintmask, retries); + local_hostintmask = bus->hostintmask; + bus->hostintmask = 0; + + /* Force clocks on backplane to be sure F2 interrupt propagates */ + saveclk = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (!err) { + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, + (saveclk | SBSDIO_FORCE_HT), &err); + } + if (err) { + DHD_ERROR(("%s: Failed to force clock for F2: err %d\n", __FUNCTION__, err)); + } + + /* Turn off the bus (F2), free any pending packets */ + DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__)); + bcmsdh_intr_disable(bus->sdh); + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, SDIO_FUNC_ENABLE_1, NULL); + + /* Clear any pending interrupts now that F2 is disabled */ + W_SDREG(local_hostintmask, &bus->regs->intstatus, retries); + + /* Turn off the backplane clock (only) */ + dhdsdio_clkctl(bus, CLK_SDONLY, FALSE); + + /* Clear the data packet queues */ + pktq_flush(osh, &bus->txq, TRUE); + + /* Clear any held glomming stuff */ + if (bus->glomd) + PKTFREE(osh, bus->glomd, FALSE); + + if (bus->glom) + PKTFREE(osh, bus->glom, FALSE); + + bus->glom = bus->glomd = NULL; + + /* Clear rx control and wake any waiters */ + bus->rxlen = 0; + dhd_os_ioctl_resp_wake(bus->dhd); + + /* Reset some F2 state stuff */ + bus->rxskip = FALSE; + bus->tx_seq = bus->rx_seq = 0; + + if (enforce_mutex) + dhd_os_sdunlock(bus->dhd); +} + +int +dhd_bus_init(dhd_pub_t *dhdp, bool enforce_mutex) +{ + dhd_bus_t *bus = dhdp->bus; + dhd_timeout_t tmo; + uint retries = 0; + uint8 ready, enable; + int err, ret = BCME_ERROR; + uint8 saveclk; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + ASSERT(bus->dhd); + if (!bus->dhd) + return BCME_OK; + + if (enforce_mutex) + dhd_os_sdlock(bus->dhd); + + /* Make sure backplane clock is on, needed to generate F2 interrupt */ + err = dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + if ((err != BCME_OK) || (bus->clkstate != CLK_AVAIL)) { + DHD_ERROR(("%s: Failed to set backplane clock: err %d\n", __FUNCTION__, err)); + goto exit; + } + + /* Force clocks on backplane to be sure F2 interrupt propagates */ + saveclk = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (!err) { + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, + (saveclk | SBSDIO_FORCE_HT), &err); + } + if (err) { + DHD_ERROR(("%s: Failed to force clock for F2: err %d\n", __FUNCTION__, err)); + goto exit; + } + + /* Enable function 2 (frame transfers) */ + W_SDREG((SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT), + &bus->regs->tosbmailboxdata, retries); + enable = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2); + + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, enable, NULL); + + /* Give the dongle some time to do its thing and set IOR2 */ + dhd_timeout_start(&tmo, DHD_WAIT_F2RDY * 1000); + + ready = 0; + while (ready != enable && !dhd_timeout_expired(&tmo)) + ready = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IORDY, NULL); + + + DHD_INFO(("%s: enable 0x%02x, ready 0x%02x (waited %uus)\n", + __FUNCTION__, enable, ready, tmo.elapsed)); + + + /* If F2 successfully enabled, set core and enable interrupts */ + if (ready == enable) { + /* Make sure we're talking to the core. */ + if (!(bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0))) + bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0); + + /* Set up the interrupt mask and enable interrupts */ + bus->hostintmask = HOSTINTMASK; + W_SDREG(bus->hostintmask, &bus->regs->hostintmask, retries); + + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_WATERMARK, (uint8)watermark, &err); + + /* Set bus state according to enable result */ + dhdp->busstate = DHD_BUS_DATA; + + /* bcmsdh_intr_unmask(bus->sdh); */ + + bus->intdis = FALSE; + if (bus->intr) { + DHD_INTR(("%s: enable SDIO device interrupts\n", __FUNCTION__)); + bcmsdh_intr_enable(bus->sdh); + } else { + DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__)); + bcmsdh_intr_disable(bus->sdh); + } + + } + + + else { + /* Disable F2 again */ + enable = SDIO_FUNC_ENABLE_1; + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, enable, NULL); + } + + /* Restore previous clock setting */ + bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, saveclk, &err); + + + /* If we didn't come up, turn off backplane clock */ + if (dhdp->busstate != DHD_BUS_DATA) + dhdsdio_clkctl(bus, CLK_NONE, FALSE); + + ret = BCME_OK; +exit: + if (enforce_mutex) + dhd_os_sdunlock(bus->dhd); + + return ret; +} + +static void +dhdsdio_rxfail(dhd_bus_t *bus, bool abort, bool rtx) +{ + bcmsdh_info_t *sdh = bus->sdh; + sdpcmd_regs_t *regs = bus->regs; + uint retries = 0; + uint16 lastrbc; + uint8 hi, lo; + int err; + + DHD_ERROR(("%s: %sterminate frame%s\n", __FUNCTION__, + (abort ? "abort command, " : ""), (rtx ? ", send NAK" : ""))); + + if (abort) { + bcmsdh_abort(sdh, SDIO_FUNC_2); + } + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL, SFC_RF_TERM, &err); + bus->f1regdata++; + + /* Wait until the packet has been flushed (device/FIFO stable) */ + for (lastrbc = retries = 0xffff; retries > 0; retries--) { + hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_RFRAMEBCHI, NULL); + lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_RFRAMEBCLO, NULL); + bus->f1regdata += 2; + + if ((hi == 0) && (lo == 0)) + break; + + if ((hi > (lastrbc >> 8)) && (lo > (lastrbc & 0x00ff))) { + DHD_ERROR(("%s: count growing: last 0x%04x now 0x%04x\n", + __FUNCTION__, lastrbc, ((hi << 8) + lo))); + } + lastrbc = (hi << 8) + lo; + } + + if (!retries) { + DHD_ERROR(("%s: count never zeroed: last 0x%04x\n", __FUNCTION__, lastrbc)); + } else { + DHD_INFO(("%s: flush took %d iterations\n", __FUNCTION__, (0xffff - retries))); + } + + if (rtx) { + bus->rxrtx++; + W_SDREG(SMB_NAK, ®s->tosbmailbox, retries); + bus->f1regdata++; + if (retries <= retry_limit) { + bus->rxskip = TRUE; + } + } + + /* Clear partial in any case */ + bus->nextlen = 0; + + /* If we can't reach the device, signal failure */ + if (err || bcmsdh_regfail(sdh)) + bus->dhd->busstate = DHD_BUS_DOWN; +} + +static void +dhdsdio_read_control(dhd_bus_t *bus, uint8 *hdr, uint len, uint doff) +{ + bcmsdh_info_t *sdh = bus->sdh; + uint rdlen, pad; + + int sdret; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* Control data already received in aligned rxctl */ + if ((bus->bus == SPI_BUS) && (!bus->usebufpool)) + goto gotpkt; + + ASSERT(bus->rxbuf); + /* Set rxctl for frame (w/optional alignment) */ + bus->rxctl = bus->rxbuf; + if (dhd_alignctl) { + bus->rxctl += firstread; + if ((pad = ((uintptr)bus->rxctl % DHD_SDALIGN))) + bus->rxctl += (DHD_SDALIGN - pad); + bus->rxctl -= firstread; + } + ASSERT(bus->rxctl >= bus->rxbuf); + + /* Copy the already-read portion over */ + bcopy(hdr, bus->rxctl, firstread); + if (len <= firstread) + goto gotpkt; + + /* Copy the full data pkt in gSPI case and process ioctl. */ + if (bus->bus == SPI_BUS) { + bcopy(hdr, bus->rxctl, len); + goto gotpkt; + } + + /* Raise rdlen to next SDIO block to avoid tail command */ + rdlen = len - firstread; + if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) { + pad = bus->blocksize - (rdlen % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize) && + ((len + pad) < bus->dhd->maxctl)) + rdlen += pad; + } else if (rdlen % DHD_SDALIGN) { + rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (forcealign && (rdlen & (ALIGNMENT - 1))) + rdlen = ROUNDUP(rdlen, ALIGNMENT); + + /* Drop if the read is too big or it exceeds our maximum */ + if ((rdlen + firstread) > bus->dhd->maxctl) { + DHD_ERROR(("%s: %d-byte control read exceeds %d-byte buffer\n", + __FUNCTION__, rdlen, bus->dhd->maxctl)); + bus->dhd->rx_errors++; + dhdsdio_rxfail(bus, FALSE, FALSE); + goto done; + } + + if ((len - doff) > bus->dhd->maxctl) { + DHD_ERROR(("%s: %d-byte ctl frame (%d-byte ctl data) exceeds %d-byte limit\n", + __FUNCTION__, len, (len - doff), bus->dhd->maxctl)); + bus->dhd->rx_errors++; bus->rx_toolong++; + dhdsdio_rxfail(bus, FALSE, FALSE); + goto done; + } + + + /* Read remainder of frame body into the rxctl buffer */ + sdret = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC, + (bus->rxctl + firstread), rdlen, NULL, NULL, NULL); + bus->f2rxdata++; + ASSERT(sdret != BCME_PENDING); + + /* Control frame failures need retransmission */ + if (sdret < 0) { + DHD_ERROR(("%s: read %d control bytes failed: %d\n", __FUNCTION__, rdlen, sdret)); + bus->rxc_errors++; /* dhd.rx_ctlerrs is higher level */ + dhdsdio_rxfail(bus, TRUE, TRUE); + goto done; + } + +gotpkt: + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_CTL_ON()) { + prhex("RxCtrl", bus->rxctl, len); + } +#endif + + /* Point to valid data and indicate its length */ + bus->rxctl += doff; + bus->rxlen = len - doff; + +done: + /* Awake any waiters */ + dhd_os_ioctl_resp_wake(bus->dhd); +} + +static uint8 +dhdsdio_rxglom(dhd_bus_t *bus, uint8 rxseq) +{ + uint16 dlen, totlen; + uint8 *dptr, num = 0; + + uint16 sublen, check; + void *pfirst, *plast, *pnext, *save_pfirst; + osl_t *osh = bus->dhd->osh; + + int errcode; + uint8 chan, seq, doff, sfdoff; + uint8 txmax; + + int ifidx = 0; + bool usechain = bus->use_rxchain; + + /* If packets, issue read(s) and send up packet chain */ + /* Return sequence numbers consumed? */ + + DHD_TRACE(("dhdsdio_rxglom: start: glomd %p glom %p\n", bus->glomd, bus->glom)); + + /* If there's a descriptor, generate the packet chain */ + if (bus->glomd) { + dhd_os_sdlock_rxq(bus->dhd); + + pfirst = plast = pnext = NULL; + dlen = (uint16)PKTLEN(osh, bus->glomd); + dptr = PKTDATA(osh, bus->glomd); + if (!dlen || (dlen & 1)) { + DHD_ERROR(("%s: bad glomd len (%d), ignore descriptor\n", + __FUNCTION__, dlen)); + dlen = 0; + } + + for (totlen = num = 0; dlen; num++) { + /* Get (and move past) next length */ + sublen = ltoh16_ua(dptr); + dlen -= sizeof(uint16); + dptr += sizeof(uint16); + if ((sublen < SDPCM_HDRLEN) || + ((num == 0) && (sublen < (2 * SDPCM_HDRLEN)))) { + DHD_ERROR(("%s: descriptor len %d bad: %d\n", + __FUNCTION__, num, sublen)); + pnext = NULL; + break; + } + if (sublen % DHD_SDALIGN) { + DHD_ERROR(("%s: sublen %d not a multiple of %d\n", + __FUNCTION__, sublen, DHD_SDALIGN)); + usechain = FALSE; + } + totlen += sublen; + + /* For last frame, adjust read len so total is a block multiple */ + if (!dlen) { + sublen += (ROUNDUP(totlen, bus->blocksize) - totlen); + totlen = ROUNDUP(totlen, bus->blocksize); + } + + /* Allocate/chain packet for next subframe */ + if ((pnext = PKTGET(osh, sublen + DHD_SDALIGN, FALSE)) == NULL) { + DHD_ERROR(("%s: PKTGET failed, num %d len %d\n", + __FUNCTION__, num, sublen)); + break; + } + ASSERT(!PKTLINK(pnext)); + if (!pfirst) { + ASSERT(!plast); + pfirst = plast = pnext; + } else { + ASSERT(plast); + PKTSETNEXT(osh, plast, pnext); + plast = pnext; + } + + /* Adhere to start alignment requirements */ + PKTALIGN(osh, pnext, sublen, DHD_SDALIGN); + } + + /* If all allocations succeeded, save packet chain in bus structure */ + if (pnext) { + DHD_GLOM(("%s: allocated %d-byte packet chain for %d subframes\n", + __FUNCTION__, totlen, num)); + if (DHD_GLOM_ON() && bus->nextlen) { + if (totlen != bus->nextlen) { + DHD_GLOM(("%s: glomdesc mismatch: nextlen %d glomdesc %d " + "rxseq %d\n", __FUNCTION__, bus->nextlen, + totlen, rxseq)); + } + } + bus->glom = pfirst; + pfirst = pnext = NULL; + } else { + if (pfirst) + PKTFREE(osh, pfirst, FALSE); + bus->glom = NULL; + num = 0; + } + + /* Done with descriptor packet */ + PKTFREE(osh, bus->glomd, FALSE); + bus->glomd = NULL; + bus->nextlen = 0; + + dhd_os_sdunlock_rxq(bus->dhd); + } + + /* Ok -- either we just generated a packet chain, or had one from before */ + if (bus->glom) { + if (DHD_GLOM_ON()) { + DHD_GLOM(("%s: attempt superframe read, packet chain:\n", __FUNCTION__)); + for (pnext = bus->glom; pnext; pnext = PKTNEXT(osh, pnext)) { + DHD_GLOM((" %p: %p len 0x%04x (%d)\n", + pnext, (uint8*)PKTDATA(osh, pnext), + PKTLEN(osh, pnext), PKTLEN(osh, pnext))); + } + } + + pfirst = bus->glom; + dlen = (uint16)pkttotlen(osh, pfirst); + + /* Do an SDIO read for the superframe. Configurable iovar to + * read directly into the chained packet, or allocate a large + * packet and and copy into the chain. + */ + if (usechain) { + errcode = dhd_bcmsdh_recv_buf(bus, + bcmsdh_cur_sbwad(bus->sdh), SDIO_FUNC_2, + F2SYNC, (uint8*)PKTDATA(osh, pfirst), + dlen, pfirst, NULL, NULL); + } else if (bus->dataptr) { + errcode = dhd_bcmsdh_recv_buf(bus, + bcmsdh_cur_sbwad(bus->sdh), SDIO_FUNC_2, + F2SYNC, bus->dataptr, + dlen, NULL, NULL, NULL); + sublen = (uint16)pktfrombuf(osh, pfirst, 0, dlen, bus->dataptr); + if (sublen != dlen) { + DHD_ERROR(("%s: FAILED TO COPY, dlen %d sublen %d\n", + __FUNCTION__, dlen, sublen)); + errcode = -1; + } + pnext = NULL; + } else { + DHD_ERROR(("COULDN'T ALLOC %d-BYTE GLOM, FORCE FAILURE\n", dlen)); + errcode = -1; + } + bus->f2rxdata++; + ASSERT(errcode != BCME_PENDING); + + /* On failure, kill the superframe, allow a couple retries */ + if (errcode < 0) { + DHD_ERROR(("%s: glom read of %d bytes failed: %d\n", + __FUNCTION__, dlen, errcode)); + bus->dhd->rx_errors++; + + if (bus->glomerr++ < 3) { + dhdsdio_rxfail(bus, TRUE, TRUE); + } else { + bus->glomerr = 0; + dhdsdio_rxfail(bus, TRUE, FALSE); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE(osh, bus->glom, FALSE); + dhd_os_sdunlock_rxq(bus->dhd); + bus->rxglomfail++; + bus->glom = NULL; + } + return 0; + } + +#ifdef DHD_DEBUG + if (DHD_GLOM_ON()) { + prhex("SUPERFRAME", PKTDATA(osh, pfirst), + MIN(PKTLEN(osh, pfirst), 48)); + } +#endif + + + /* Validate the superframe header */ + dptr = (uint8 *)PKTDATA(osh, pfirst); + sublen = ltoh16_ua(dptr); + check = ltoh16_ua(dptr + sizeof(uint16)); + + chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]); + bus->nextlen = dptr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET]; + if ((bus->nextlen << 4) > MAX_RX_DATASZ) { + DHD_INFO(("%s: got frame w/nextlen too large (%d) seq %d\n", + __FUNCTION__, bus->nextlen, seq)); + bus->nextlen = 0; + } + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + txmax = SDPCM_WINDOW_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + + errcode = 0; + if ((uint16)~(sublen^check)) { + DHD_ERROR(("%s (superframe): HW hdr error: len/check 0x%04x/0x%04x\n", + __FUNCTION__, sublen, check)); + errcode = -1; + } else if (ROUNDUP(sublen, bus->blocksize) != dlen) { + DHD_ERROR(("%s (superframe): len 0x%04x, rounded 0x%04x, expect 0x%04x\n", + __FUNCTION__, sublen, ROUNDUP(sublen, bus->blocksize), dlen)); + errcode = -1; + } else if (SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]) != SDPCM_GLOM_CHANNEL) { + DHD_ERROR(("%s (superframe): bad channel %d\n", __FUNCTION__, + SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]))); + errcode = -1; + } else if (SDPCM_GLOMDESC(&dptr[SDPCM_FRAMETAG_LEN])) { + DHD_ERROR(("%s (superframe): got second descriptor?\n", __FUNCTION__)); + errcode = -1; + } else if ((doff < SDPCM_HDRLEN) || + (doff > (PKTLEN(osh, pfirst) - SDPCM_HDRLEN))) { + DHD_ERROR(("%s (superframe): Bad data offset %d: HW %d pkt %d min %d\n", + __FUNCTION__, doff, sublen, PKTLEN(osh, pfirst), SDPCM_HDRLEN)); + errcode = -1; + } + + /* Check sequence number of superframe SW header */ + if (rxseq != seq) { + DHD_INFO(("%s: (superframe) rx_seq %d, expected %d\n", + __FUNCTION__, seq, rxseq)); + bus->rx_badseq++; + rxseq = seq; + } + + /* Check window for sanity */ + if ((uint8)(txmax - bus->tx_seq) > 0x40) { + DHD_ERROR(("%s: got unlikely tx max %d with tx_seq %d\n", + __FUNCTION__, txmax, bus->tx_seq)); + txmax = bus->tx_seq + 2; + } + bus->tx_max = txmax; + + /* Remove superframe header, remember offset */ + PKTPULL(osh, pfirst, doff); + sfdoff = doff; + + /* Validate all the subframe headers */ + for (num = 0, pnext = pfirst; pnext && !errcode; + num++, pnext = PKTNEXT(osh, pnext)) { + dptr = (uint8 *)PKTDATA(osh, pnext); + dlen = (uint16)PKTLEN(osh, pnext); + sublen = ltoh16_ua(dptr); + check = ltoh16_ua(dptr + sizeof(uint16)); + chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); +#ifdef DHD_DEBUG + if (DHD_GLOM_ON()) { + prhex("subframe", dptr, 32); + } +#endif + + if ((uint16)~(sublen^check)) { + DHD_ERROR(("%s (subframe %d): HW hdr error: " + "len/check 0x%04x/0x%04x\n", + __FUNCTION__, num, sublen, check)); + errcode = -1; + } else if ((sublen > dlen) || (sublen < SDPCM_HDRLEN)) { + DHD_ERROR(("%s (subframe %d): length mismatch: " + "len 0x%04x, expect 0x%04x\n", + __FUNCTION__, num, sublen, dlen)); + errcode = -1; + } else if ((chan != SDPCM_DATA_CHANNEL) && + (chan != SDPCM_EVENT_CHANNEL)) { + DHD_ERROR(("%s (subframe %d): bad channel %d\n", + __FUNCTION__, num, chan)); + errcode = -1; + } else if ((doff < SDPCM_HDRLEN) || (doff > sublen)) { + DHD_ERROR(("%s (subframe %d): Bad data offset %d: HW %d min %d\n", + __FUNCTION__, num, doff, sublen, SDPCM_HDRLEN)); + errcode = -1; + } + } + + if (errcode) { + /* Terminate frame on error, request a couple retries */ + if (bus->glomerr++ < 3) { + /* Restore superframe header space */ + PKTPUSH(osh, pfirst, sfdoff); + dhdsdio_rxfail(bus, TRUE, TRUE); + } else { + bus->glomerr = 0; + dhdsdio_rxfail(bus, TRUE, FALSE); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE(osh, bus->glom, FALSE); + dhd_os_sdunlock_rxq(bus->dhd); + bus->rxglomfail++; + bus->glom = NULL; + } + bus->nextlen = 0; + return 0; + } + + /* Basic SD framing looks ok - process each packet (header) */ + save_pfirst = pfirst; + bus->glom = NULL; + plast = NULL; + + dhd_os_sdlock_rxq(bus->dhd); + for (num = 0; pfirst; rxseq++, pfirst = pnext) { + pnext = PKTNEXT(osh, pfirst); + PKTSETNEXT(osh, pfirst, NULL); + + dptr = (uint8 *)PKTDATA(osh, pfirst); + sublen = ltoh16_ua(dptr); + chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + + DHD_GLOM(("%s: Get subframe %d, %p(%p/%d), sublen %d chan %d seq %d\n", + __FUNCTION__, num, pfirst, PKTDATA(osh, pfirst), + PKTLEN(osh, pfirst), sublen, chan, seq)); + + ASSERT((chan == SDPCM_DATA_CHANNEL) || (chan == SDPCM_EVENT_CHANNEL)); + + if (rxseq != seq) { + DHD_GLOM(("%s: rx_seq %d, expected %d\n", + __FUNCTION__, seq, rxseq)); + bus->rx_badseq++; + rxseq = seq; + } + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_DATA_ON()) { + prhex("Rx Subframe Data", dptr, dlen); + } +#endif + + PKTSETLEN(osh, pfirst, sublen); + PKTPULL(osh, pfirst, doff); + + if (PKTLEN(osh, pfirst) == 0) { + PKTFREE(bus->dhd->osh, pfirst, FALSE); + if (plast) { + PKTSETNEXT(osh, plast, pnext); + } else { + ASSERT(save_pfirst == pfirst); + save_pfirst = pnext; + } + continue; + } else if (dhd_prot_hdrpull(bus->dhd, &ifidx, pfirst) != 0) { + DHD_ERROR(("%s: rx protocol error\n", __FUNCTION__)); + bus->dhd->rx_errors++; + PKTFREE(osh, pfirst, FALSE); + if (plast) { + PKTSETNEXT(osh, plast, pnext); + } else { + ASSERT(save_pfirst == pfirst); + save_pfirst = pnext; + } + continue; + } + + /* this packet will go up, link back into chain and count it */ + PKTSETNEXT(osh, pfirst, pnext); + plast = pfirst; + num++; + +#ifdef DHD_DEBUG + if (DHD_GLOM_ON()) { + DHD_GLOM(("%s subframe %d to stack, %p(%p/%d) nxt/lnk %p/%p\n", + __FUNCTION__, num, pfirst, + PKTDATA(osh, pfirst), PKTLEN(osh, pfirst), + PKTNEXT(osh, pfirst), PKTLINK(pfirst))); + prhex("", (uint8 *)PKTDATA(osh, pfirst), + MIN(PKTLEN(osh, pfirst), 32)); + } +#endif /* DHD_DEBUG */ + } + dhd_os_sdunlock_rxq(bus->dhd); + if (num) { + dhd_os_sdunlock(bus->dhd); + dhd_rx_frame(bus->dhd, ifidx, save_pfirst, num); + dhd_os_sdlock(bus->dhd); + } + + bus->rxglomframes++; + bus->rxglompkts += num; + } + return num; +} + +/* Return TRUE if there may be more frames to read */ +static uint +dhdsdio_readframes(dhd_bus_t *bus, uint maxframes, bool *finished) +{ + osl_t *osh = bus->dhd->osh; + bcmsdh_info_t *sdh = bus->sdh; + + uint16 len, check; /* Extracted hardware header fields */ + uint8 chan, seq, doff; /* Extracted software header fields */ + uint8 fcbits; /* Extracted fcbits from software header */ + uint8 delta; + + void *pkt; /* Packet for event or data frames */ + uint16 pad; /* Number of pad bytes to read */ + uint16 rdlen; /* Total number of bytes to read */ + uint8 rxseq; /* Next sequence number to expect */ + uint rxleft = 0; /* Remaining number of frames allowed */ + int sdret; /* Return code from bcmsdh calls */ + uint8 txmax; /* Maximum tx sequence offered */ + bool len_consistent; /* Result of comparing readahead len and len from hw-hdr */ + uint8 *rxbuf; + int ifidx = 0; + uint rxcount = 0; /* Total frames read */ + +#if defined(DHD_DEBUG) || defined(SDTEST) + bool sdtest = FALSE; /* To limit message spew from test mode */ +#endif + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + ASSERT(maxframes); + +#ifdef SDTEST + /* Allow pktgen to override maxframes */ + if (bus->pktgen_count && (bus->pktgen_mode == DHD_PKTGEN_RECV)) { + maxframes = bus->pktgen_count; + sdtest = TRUE; + } +#endif + + /* Not finished unless we encounter no more frames indication */ + *finished = FALSE; + + + for (rxseq = bus->rx_seq, rxleft = maxframes; + !bus->rxskip && rxleft && bus->dhd->busstate != DHD_BUS_DOWN; + rxseq++, rxleft--) { + + /* Handle glomming separately */ + if (bus->glom || bus->glomd) { + uint8 cnt; + DHD_GLOM(("%s: calling rxglom: glomd %p, glom %p\n", + __FUNCTION__, bus->glomd, bus->glom)); + cnt = dhdsdio_rxglom(bus, rxseq); + DHD_GLOM(("%s: rxglom returned %d\n", __FUNCTION__, cnt)); + rxseq += cnt - 1; + rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1; + continue; + } + + /* Try doing single read if we can */ + if (dhd_readahead && bus->nextlen) { + uint16 nextlen = bus->nextlen; + bus->nextlen = 0; + + if (bus->bus == SPI_BUS) { + rdlen = len = nextlen; + } + else { + rdlen = len = nextlen << 4; + + /* Pad read to blocksize for efficiency */ + if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) { + pad = bus->blocksize - (rdlen % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize) && + ((rdlen + pad + firstread) < MAX_RX_DATASZ)) + rdlen += pad; + } else if (rdlen % DHD_SDALIGN) { + rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN); + } + } + + /* We use bus->rxctl buffer in WinXP for initial control pkt receives. + * Later we use buffer-poll for data as well as control packets. + * This is required becuase dhd receives full frame in gSPI unlike SDIO. + * After the frame is received we have to distinguish whether it is data + * or non-data frame. + */ + /* Allocate a packet buffer */ + dhd_os_sdlock_rxq(bus->dhd); + if (!(pkt = PKTGET(osh, rdlen + DHD_SDALIGN, FALSE))) { + if (bus->bus == SPI_BUS) { + bus->usebufpool = FALSE; + bus->rxctl = bus->rxbuf; + if (dhd_alignctl) { + bus->rxctl += firstread; + if ((pad = ((uintptr)bus->rxctl % DHD_SDALIGN))) + bus->rxctl += (DHD_SDALIGN - pad); + bus->rxctl -= firstread; + } + ASSERT(bus->rxctl >= bus->rxbuf); + rxbuf = bus->rxctl; + /* Read the entire frame */ + sdret = dhd_bcmsdh_recv_buf(bus, + bcmsdh_cur_sbwad(sdh), + SDIO_FUNC_2, + F2SYNC, rxbuf, rdlen, + NULL, NULL, NULL); + bus->f2rxdata++; + ASSERT(sdret != BCME_PENDING); + + + /* Control frame failures need retransmission */ + if (sdret < 0) { + DHD_ERROR(("%s: read %d control bytes failed: %d\n", + __FUNCTION__, rdlen, sdret)); + /* dhd.rx_ctlerrs is higher level */ + bus->rxc_errors++; + dhd_os_sdunlock_rxq(bus->dhd); + dhdsdio_rxfail(bus, TRUE, + (bus->bus == SPI_BUS) ? FALSE : TRUE); + continue; + } + } else { + /* Give up on data, request rtx of events */ + DHD_ERROR(("%s (nextlen): PKTGET failed: len %d rdlen %d " + "expected rxseq %d\n", + __FUNCTION__, len, rdlen, rxseq)); + /* Just go try again w/normal header read */ + dhd_os_sdunlock_rxq(bus->dhd); + continue; + } + } else { + if (bus->bus == SPI_BUS) + bus->usebufpool = TRUE; + + ASSERT(!PKTLINK(pkt)); + PKTALIGN(osh, pkt, rdlen, DHD_SDALIGN); + rxbuf = (uint8 *)PKTDATA(osh, pkt); + /* Read the entire frame */ + sdret = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), + SDIO_FUNC_2, + F2SYNC, rxbuf, rdlen, + pkt, NULL, NULL); + bus->f2rxdata++; + ASSERT(sdret != BCME_PENDING); + + if (sdret < 0) { + DHD_ERROR(("%s (nextlen): read %d bytes failed: %d\n", + __FUNCTION__, rdlen, sdret)); + PKTFREE(bus->dhd->osh, pkt, FALSE); + bus->dhd->rx_errors++; + dhd_os_sdunlock_rxq(bus->dhd); + /* Force retry w/normal header read. Don't attemp NAK for + * gSPI + */ + dhdsdio_rxfail(bus, TRUE, + (bus->bus == SPI_BUS) ? FALSE : TRUE); + continue; + } + } + dhd_os_sdunlock_rxq(bus->dhd); + + /* Now check the header */ + bcopy(rxbuf, bus->rxhdr, SDPCM_HDRLEN); + + /* Extract hardware header fields */ + len = ltoh16_ua(bus->rxhdr); + check = ltoh16_ua(bus->rxhdr + sizeof(uint16)); + + /* All zeros means readahead info was bad */ + if (!(len|check)) { + DHD_INFO(("%s (nextlen): read zeros in HW header???\n", + __FUNCTION__)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE2(); + dhd_os_sdunlock_rxq(bus->dhd); + GSPI_PR55150_BAILOUT; + continue; + } + + /* Validate check bytes */ + if ((uint16)~(len^check)) { + DHD_ERROR(("%s (nextlen): HW hdr error: nextlen/len/check" + " 0x%04x/0x%04x/0x%04x\n", __FUNCTION__, nextlen, + len, check)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE2(); + dhd_os_sdunlock_rxq(bus->dhd); + bus->rx_badhdr++; + dhdsdio_rxfail(bus, FALSE, FALSE); + GSPI_PR55150_BAILOUT; + continue; + } + + /* Validate frame length */ + if (len < SDPCM_HDRLEN) { + DHD_ERROR(("%s (nextlen): HW hdr length invalid: %d\n", + __FUNCTION__, len)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE2(); + dhd_os_sdunlock_rxq(bus->dhd); + GSPI_PR55150_BAILOUT; + continue; + } + + /* Check for consistency with readahead info */ + len_consistent = (nextlen != (ROUNDUP(len, 16) >> 4)); + if (len_consistent) { + /* Mismatch, force retry w/normal header (may be >4K) */ + DHD_ERROR(("%s (nextlen): mismatch, nextlen %d len %d rnd %d; " + "expected rxseq %d\n", + __FUNCTION__, nextlen, len, ROUNDUP(len, 16), rxseq)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE2(); + dhd_os_sdunlock_rxq(bus->dhd); + dhdsdio_rxfail(bus, TRUE, (bus->bus == SPI_BUS) ? FALSE : TRUE); + GSPI_PR55150_BAILOUT; + continue; + } + + + /* Extract software header fields */ + chan = SDPCM_PACKET_CHANNEL(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + txmax = SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + bus->nextlen = + bus->rxhdr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET]; + if ((bus->nextlen << 4) > MAX_RX_DATASZ) { + DHD_INFO(("%s (nextlen): got frame w/nextlen too large" + " (%d), seq %d\n", __FUNCTION__, bus->nextlen, + seq)); + bus->nextlen = 0; + } + + bus->dhd->rx_readahead_cnt ++; + /* Handle Flow Control */ + fcbits = SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + delta = 0; + if (~bus->flowcontrol & fcbits) { + bus->fc_xoff++; + delta = 1; + } + if (bus->flowcontrol & ~fcbits) { + bus->fc_xon++; + delta = 1; + } + + if (delta) { + bus->fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Check and update sequence number */ + if (rxseq != seq) { + DHD_INFO(("%s (nextlen): rx_seq %d, expected %d\n", + __FUNCTION__, seq, rxseq)); + bus->rx_badseq++; + rxseq = seq; + } + + /* Check window for sanity */ + if ((uint8)(txmax - bus->tx_seq) > 0x40) { + DHD_ERROR(("%s: got unlikely tx max %d with tx_seq %d\n", + __FUNCTION__, txmax, bus->tx_seq)); + txmax = bus->tx_seq + 2; + } + bus->tx_max = txmax; + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_DATA_ON()) { + prhex("Rx Data", rxbuf, len); + } else if (DHD_HDRS_ON()) { + prhex("RxHdr", bus->rxhdr, SDPCM_HDRLEN); + } +#endif + + if (chan == SDPCM_CONTROL_CHANNEL) { + if (bus->bus == SPI_BUS) { + dhdsdio_read_control(bus, rxbuf, len, doff); + if (bus->usebufpool) { + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE(bus->dhd->osh, pkt, FALSE); + dhd_os_sdunlock_rxq(bus->dhd); + } + continue; + } else { + DHD_ERROR(("%s (nextlen): readahead on control" + " packet %d?\n", __FUNCTION__, seq)); + /* Force retry w/normal header read */ + bus->nextlen = 0; + dhdsdio_rxfail(bus, FALSE, TRUE); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE2(); + dhd_os_sdunlock_rxq(bus->dhd); + continue; + } + } + + if ((bus->bus == SPI_BUS) && !bus->usebufpool) { + DHD_ERROR(("Received %d bytes on %d channel. Running out of " + "rx pktbuf's or not yet malloced.\n", len, chan)); + continue; + } + + /* Validate data offset */ + if ((doff < SDPCM_HDRLEN) || (doff > len)) { + DHD_ERROR(("%s (nextlen): bad data offset %d: HW len %d min %d\n", + __FUNCTION__, doff, len, SDPCM_HDRLEN)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE2(); + dhd_os_sdunlock_rxq(bus->dhd); + ASSERT(0); + dhdsdio_rxfail(bus, FALSE, FALSE); + continue; + } + + /* All done with this one -- now deliver the packet */ + goto deliver; + } + /* gSPI frames should not be handled in fractions */ + if (bus->bus == SPI_BUS) { + break; + } + + /* Read frame header (hardware and software) */ + sdret = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC, + bus->rxhdr, firstread, NULL, NULL, NULL); + bus->f2rxhdrs++; + ASSERT(sdret != BCME_PENDING); + + if (sdret < 0) { + DHD_ERROR(("%s: RXHEADER FAILED: %d\n", __FUNCTION__, sdret)); + bus->rx_hdrfail++; + dhdsdio_rxfail(bus, TRUE, TRUE); + continue; + } + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() || DHD_HDRS_ON()) { + prhex("RxHdr", bus->rxhdr, SDPCM_HDRLEN); + } +#endif + + /* Extract hardware header fields */ + len = ltoh16_ua(bus->rxhdr); + check = ltoh16_ua(bus->rxhdr + sizeof(uint16)); + + /* All zeros means no more frames */ + if (!(len|check)) { + *finished = TRUE; + break; + } + + /* Validate check bytes */ + if ((uint16)~(len^check)) { + DHD_ERROR(("%s: HW hdr error: len/check 0x%04x/0x%04x\n", + __FUNCTION__, len, check)); + bus->rx_badhdr++; + dhdsdio_rxfail(bus, FALSE, FALSE); + continue; + } + + /* Validate frame length */ + if (len < SDPCM_HDRLEN) { + DHD_ERROR(("%s: HW hdr length invalid: %d\n", __FUNCTION__, len)); + continue; + } + + /* Extract software header fields */ + chan = SDPCM_PACKET_CHANNEL(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + seq = SDPCM_PACKET_SEQUENCE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + doff = SDPCM_DOFFSET_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + txmax = SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + /* Validate data offset */ + if ((doff < SDPCM_HDRLEN) || (doff > len)) { + DHD_ERROR(("%s: Bad data offset %d: HW len %d, min %d seq %d\n", + __FUNCTION__, doff, len, SDPCM_HDRLEN, seq)); + bus->rx_badhdr++; + ASSERT(0); + dhdsdio_rxfail(bus, FALSE, FALSE); + continue; + } + + /* Save the readahead length if there is one */ + bus->nextlen = bus->rxhdr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET]; + if ((bus->nextlen << 4) > MAX_RX_DATASZ) { + DHD_INFO(("%s (nextlen): got frame w/nextlen too large (%d), seq %d\n", + __FUNCTION__, bus->nextlen, seq)); + bus->nextlen = 0; + } + + /* Handle Flow Control */ + fcbits = SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]); + + delta = 0; + if (~bus->flowcontrol & fcbits) { + bus->fc_xoff++; + delta = 1; + } + if (bus->flowcontrol & ~fcbits) { + bus->fc_xon++; + delta = 1; + } + + if (delta) { + bus->fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Check and update sequence number */ + if (rxseq != seq) { + DHD_INFO(("%s: rx_seq %d, expected %d\n", __FUNCTION__, seq, rxseq)); + bus->rx_badseq++; + rxseq = seq; + } + + /* Check window for sanity */ + if ((uint8)(txmax - bus->tx_seq) > 0x40) { + DHD_ERROR(("%s: got unlikely tx max %d with tx_seq %d\n", + __FUNCTION__, txmax, bus->tx_seq)); + txmax = bus->tx_seq + 2; + } + bus->tx_max = txmax; + + /* Call a separate function for control frames */ + if (chan == SDPCM_CONTROL_CHANNEL) { + dhdsdio_read_control(bus, bus->rxhdr, len, doff); + continue; + } + + ASSERT((chan == SDPCM_DATA_CHANNEL) || (chan == SDPCM_EVENT_CHANNEL) || + (chan == SDPCM_TEST_CHANNEL) || (chan == SDPCM_GLOM_CHANNEL)); + + /* Length to read */ + rdlen = (len > firstread) ? (len - firstread) : 0; + + /* May pad read to blocksize for efficiency */ + if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) { + pad = bus->blocksize - (rdlen % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize) && + ((rdlen + pad + firstread) < MAX_RX_DATASZ)) + rdlen += pad; + } else if (rdlen % DHD_SDALIGN) { + rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (forcealign && (rdlen & (ALIGNMENT - 1))) + rdlen = ROUNDUP(rdlen, ALIGNMENT); + + if ((rdlen + firstread) > MAX_RX_DATASZ) { + /* Too long -- skip this frame */ + DHD_ERROR(("%s: too long: len %d rdlen %d\n", __FUNCTION__, len, rdlen)); + bus->dhd->rx_errors++; bus->rx_toolong++; + dhdsdio_rxfail(bus, FALSE, FALSE); + continue; + } + + dhd_os_sdlock_rxq(bus->dhd); + if (!(pkt = PKTGET(osh, (rdlen + firstread + DHD_SDALIGN), FALSE))) { + /* Give up on data, request rtx of events */ + DHD_ERROR(("%s: PKTGET failed: rdlen %d chan %d\n", + __FUNCTION__, rdlen, chan)); + bus->dhd->rx_dropped++; + dhd_os_sdunlock_rxq(bus->dhd); + dhdsdio_rxfail(bus, FALSE, RETRYCHAN(chan)); + continue; + } + dhd_os_sdunlock_rxq(bus->dhd); + + ASSERT(!PKTLINK(pkt)); + + /* Leave room for what we already read, and align remainder */ + ASSERT(firstread < (PKTLEN(osh, pkt))); + PKTPULL(osh, pkt, firstread); + PKTALIGN(osh, pkt, rdlen, DHD_SDALIGN); + + /* Read the remaining frame data */ + sdret = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC, + ((uint8 *)PKTDATA(osh, pkt)), rdlen, pkt, NULL, NULL); + bus->f2rxdata++; + ASSERT(sdret != BCME_PENDING); + + if (sdret < 0) { + DHD_ERROR(("%s: read %d %s bytes failed: %d\n", __FUNCTION__, rdlen, + ((chan == SDPCM_EVENT_CHANNEL) ? "event" : + ((chan == SDPCM_DATA_CHANNEL) ? "data" : "test")), sdret)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE(bus->dhd->osh, pkt, FALSE); + dhd_os_sdunlock_rxq(bus->dhd); + bus->dhd->rx_errors++; + dhdsdio_rxfail(bus, TRUE, RETRYCHAN(chan)); + continue; + } + + /* Copy the already-read portion */ + PKTPUSH(osh, pkt, firstread); + bcopy(bus->rxhdr, PKTDATA(osh, pkt), firstread); + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_DATA_ON()) { + prhex("Rx Data", PKTDATA(osh, pkt), len); + } +#endif + +deliver: + /* Save superframe descriptor and allocate packet frame */ + if (chan == SDPCM_GLOM_CHANNEL) { + if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_FRAMETAG_LEN])) { + DHD_GLOM(("%s: got glom descriptor, %d bytes:\n", + __FUNCTION__, len)); +#ifdef DHD_DEBUG + if (DHD_GLOM_ON()) { + prhex("Glom Data", PKTDATA(osh, pkt), len); + } +#endif + PKTSETLEN(osh, pkt, len); + ASSERT(doff == SDPCM_HDRLEN); + PKTPULL(osh, pkt, SDPCM_HDRLEN); + bus->glomd = pkt; + } else { + DHD_ERROR(("%s: glom superframe w/o descriptor!\n", __FUNCTION__)); + dhdsdio_rxfail(bus, FALSE, FALSE); + } + continue; + } + + /* Fill in packet len and prio, deliver upward */ + PKTSETLEN(osh, pkt, len); + PKTPULL(osh, pkt, doff); + +#ifdef SDTEST + /* Test channel packets are processed separately */ + if (chan == SDPCM_TEST_CHANNEL) { + dhdsdio_testrcv(bus, pkt, seq); + continue; + } +#endif /* SDTEST */ + + if (PKTLEN(osh, pkt) == 0) { + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE(bus->dhd->osh, pkt, FALSE); + dhd_os_sdunlock_rxq(bus->dhd); + continue; + } else if (dhd_prot_hdrpull(bus->dhd, &ifidx, pkt) != 0) { + DHD_ERROR(("%s: rx protocol error\n", __FUNCTION__)); + dhd_os_sdlock_rxq(bus->dhd); + PKTFREE(bus->dhd->osh, pkt, FALSE); + dhd_os_sdunlock_rxq(bus->dhd); + bus->dhd->rx_errors++; + continue; + } + + + /* Unlock during rx call */ + dhd_os_sdunlock(bus->dhd); + dhd_rx_frame(bus->dhd, ifidx, pkt, 1); + dhd_os_sdlock(bus->dhd); + } + rxcount = maxframes - rxleft; +#ifdef DHD_DEBUG + /* Message if we hit the limit */ + if (!rxleft && !sdtest) + DHD_DATA(("%s: hit rx limit of %d frames\n", __FUNCTION__, maxframes)); + else +#endif /* DHD_DEBUG */ + DHD_DATA(("%s: processed %d frames\n", __FUNCTION__, rxcount)); + /* Back off rxseq if awaiting rtx, update rx_seq */ + if (bus->rxskip) + rxseq--; + bus->rx_seq = rxseq; + + return rxcount; +} + +static uint32 +dhdsdio_hostmail(dhd_bus_t *bus) +{ + sdpcmd_regs_t *regs = bus->regs; + uint32 intstatus = 0; + uint32 hmb_data; + uint8 fcbits; + uint retries = 0; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* Read mailbox data and ack that we did so */ + R_SDREG(hmb_data, ®s->tohostmailboxdata, retries); + if (retries <= retry_limit) + W_SDREG(SMB_INT_ACK, ®s->tosbmailbox, retries); + bus->f1regdata += 2; + + /* Dongle recomposed rx frames, accept them again */ + if (hmb_data & HMB_DATA_NAKHANDLED) { + DHD_INFO(("Dongle reports NAK handled, expect rtx of %d\n", bus->rx_seq)); + if (!bus->rxskip) { + DHD_ERROR(("%s: unexpected NAKHANDLED!\n", __FUNCTION__)); + } + bus->rxskip = FALSE; + intstatus |= I_HMB_FRAME_IND; + } + + /* + * DEVREADY does not occur with gSPI. + */ + if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) { + bus->sdpcm_ver = (hmb_data & HMB_DATA_VERSION_MASK) >> HMB_DATA_VERSION_SHIFT; + if (bus->sdpcm_ver != SDPCM_PROT_VERSION) + DHD_ERROR(("Version mismatch, dongle reports %d, expecting %d\n", + bus->sdpcm_ver, SDPCM_PROT_VERSION)); + else + DHD_INFO(("Dongle ready, protocol version %d\n", bus->sdpcm_ver)); + } + + /* + * Flow Control has been moved into the RX headers and this out of band + * method isn't used any more. Leae this here for possibly remaining backward + * compatible with older dongles + */ + if (hmb_data & HMB_DATA_FC) { + fcbits = (hmb_data & HMB_DATA_FCDATA_MASK) >> HMB_DATA_FCDATA_SHIFT; + + if (fcbits & ~bus->flowcontrol) + bus->fc_xoff++; + if (bus->flowcontrol & ~fcbits) + bus->fc_xon++; + + bus->fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Shouldn't be any others */ + if (hmb_data & ~(HMB_DATA_DEVREADY | + HMB_DATA_NAKHANDLED | + HMB_DATA_FC | + HMB_DATA_FWREADY | + HMB_DATA_FCDATA_MASK | + HMB_DATA_VERSION_MASK)) { + DHD_ERROR(("Unknown mailbox data content: 0x%02x\n", hmb_data)); + } + + return intstatus; +} + +bool +dhdsdio_dpc(dhd_bus_t *bus) +{ + bcmsdh_info_t *sdh = bus->sdh; + sdpcmd_regs_t *regs = bus->regs; + uint32 intstatus, newstatus = 0; + uint retries = 0; + uint rxlimit = dhd_rxbound; /* Rx frames to read before resched */ + uint txlimit = dhd_txbound; /* Tx frames to send before resched */ + uint framecnt = 0; /* Temporary counter of tx/rx frames */ + bool rxdone = TRUE; /* Flag for no more read data */ + bool resched = FALSE; /* Flag indicating resched wanted */ + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + /* Start with leftover status bits */ + intstatus = bus->intstatus; + + dhd_os_sdlock(bus->dhd); + + /* If waiting for HTAVAIL, check status */ + if (bus->clkstate == CLK_PENDING) { + int err; + uint8 clkctl, devctl = 0; + +#ifdef DHD_DEBUG + /* Check for inconsistent device control */ + devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err); + if (err) { + DHD_ERROR(("%s: error reading DEVCTL: %d\n", __FUNCTION__, err)); + bus->dhd->busstate = DHD_BUS_DOWN; + } else { + ASSERT(devctl & SBSDIO_DEVCTL_CA_INT_ONLY); + } +#endif /* DHD_DEBUG */ + + /* Read CSR, if clock on switch to AVAIL, else ignore */ + clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (err) { + DHD_ERROR(("%s: error reading CSR: %d\n", __FUNCTION__, err)); + bus->dhd->busstate = DHD_BUS_DOWN; + } + + DHD_INFO(("DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", devctl, clkctl)); + + if (SBSDIO_HTAV(clkctl)) { + devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err); + if (err) { + DHD_ERROR(("%s: error reading DEVCTL: %d\n", + __FUNCTION__, err)); + bus->dhd->busstate = DHD_BUS_DOWN; + } + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err); + if (err) { + DHD_ERROR(("%s: error writing DEVCTL: %d\n", + __FUNCTION__, err)); + bus->dhd->busstate = DHD_BUS_DOWN; + } + bus->clkstate = CLK_AVAIL; + } else { + goto clkwait; + } + } + + BUS_WAKE(bus); + + /* Make sure backplane clock is on */ + dhdsdio_clkctl(bus, CLK_AVAIL, TRUE); + if (bus->clkstate == CLK_PENDING) + goto clkwait; + + /* Pending interrupt indicates new device status */ + if (bus->ipend) { + bus->ipend = FALSE; + R_SDREG(newstatus, ®s->intstatus, retries); + bus->f1regdata++; + if (bcmsdh_regfail(bus->sdh)) + newstatus = 0; + newstatus &= bus->hostintmask; + bus->fcstate = !!(newstatus & I_HMB_FC_STATE); + if (newstatus) { + W_SDREG(newstatus, ®s->intstatus, retries); + bus->f1regdata++; + } + } + + /* Merge new bits with previous */ + intstatus |= newstatus; + bus->intstatus = 0; + + /* Handle flow-control change: read new state in case our ack + * crossed another change interrupt. If change still set, assume + * FC ON for safety, let next loop through do the debounce. + */ + if (intstatus & I_HMB_FC_CHANGE) { + intstatus &= ~I_HMB_FC_CHANGE; + W_SDREG(I_HMB_FC_CHANGE, ®s->intstatus, retries); + R_SDREG(newstatus, ®s->intstatus, retries); + bus->f1regdata += 2; + bus->fcstate = !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE)); + intstatus |= (newstatus & bus->hostintmask); + } + + /* Handle host mailbox indication */ + if (intstatus & I_HMB_HOST_INT) { + intstatus &= ~I_HMB_HOST_INT; + intstatus |= dhdsdio_hostmail(bus); + } + + /* Generally don't ask for these, can get CRC errors... */ + if (intstatus & I_WR_OOSYNC) { + DHD_ERROR(("Dongle reports WR_OOSYNC\n")); + intstatus &= ~I_WR_OOSYNC; + } + + if (intstatus & I_RD_OOSYNC) { + DHD_ERROR(("Dongle reports RD_OOSYNC\n")); + intstatus &= ~I_RD_OOSYNC; + } + + if (intstatus & I_SBINT) { + DHD_ERROR(("Dongle reports SBINT\n")); + intstatus &= ~I_SBINT; + } + + /* Would be active due to wake-wlan in gSPI */ + if (intstatus & I_CHIPACTIVE) { + DHD_INFO(("Dongle reports CHIPACTIVE\n")); + intstatus &= ~I_CHIPACTIVE; + } + + /* Ignore frame indications if rxskip is set */ + if (bus->rxskip) + intstatus &= ~I_HMB_FRAME_IND; + + /* On frame indication, read available frames */ + if (PKT_AVAILABLE()) { + framecnt = dhdsdio_readframes(bus, rxlimit, &rxdone); + if (rxdone || bus->rxskip) + intstatus &= ~I_HMB_FRAME_IND; + rxlimit -= MIN(framecnt, rxlimit); + } + + /* Keep still-pending events for next scheduling */ + bus->intstatus = intstatus; + +clkwait: + /* Re-enable interrupts to detect new device events (mailbox, rx frame) + * or clock availability. (Allows tx loop to check ipend if desired.) + * (Unless register access seems hosed, as we may not be able to ACK...) + */ + if (bus->intr && bus->intdis && !bcmsdh_regfail(sdh)) { + DHD_INTR(("%s: enable SDIO interrupts, rxdone %d framecnt %d\n", + __FUNCTION__, rxdone, framecnt)); + bus->intdis = FALSE; +#if defined(OOB_INTR_ONLY) + bcmsdh_oob_intr_set(1); +#endif /* (OOB_INTR_ONLY) */ + bcmsdh_intr_enable(sdh); + } + + if (DATAOK(bus) && bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL)) { + int ret, i; + + ret = dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC, + (uint8 *)bus->ctrl_frame_buf, (uint32)bus->ctrl_frame_len, + NULL, NULL, NULL); + ASSERT(ret != BCME_PENDING); + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + DHD_INFO(("%s: sdio error %d, abort command and terminate frame.\n", + __FUNCTION__, ret)); + bus->tx_sderrs++; + + bcmsdh_abort(sdh, SDIO_FUNC_2); + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, NULL); + bus->f1regdata++; + + for (i = 0; i < 3; i++) { + uint8 hi, lo; + hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCHI, NULL); + lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_WFRAMEBCLO, NULL); + bus->f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } + if (ret == 0) { + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + } + + printf("Return_dpc value is : %d\n", ret); + bus->ctrl_frame_stat = FALSE; + dhd_wait_event_wakeup(bus->dhd); + } + /* Send queued frames (limit 1 if rx may still be pending) */ + else if ((bus->clkstate == CLK_AVAIL) && !bus->fcstate && + pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit && DATAOK(bus)) { + framecnt = rxdone ? txlimit : MIN(txlimit, dhd_txminmax); + framecnt = dhdsdio_sendfromq(bus, framecnt); + txlimit -= framecnt; + } + + /* Resched if events or tx frames are pending, else await next interrupt */ + /* On failed register access, all bets are off: no resched or interrupts */ + if ((bus->dhd->busstate == DHD_BUS_DOWN) || bcmsdh_regfail(sdh)) { + DHD_ERROR(("%s: failed backplane access over SDIO, halting operation %d \n", + __FUNCTION__, bcmsdh_regfail(sdh))); + bus->dhd->busstate = DHD_BUS_DOWN; + bus->intstatus = 0; + } else if (bus->clkstate == CLK_PENDING) { + DHD_INFO(("%s: rescheduled due to CLK_PENDING awaiting \ + I_CHIPACTIVE interrupt", __FUNCTION__)); + resched = TRUE; + } else if (bus->intstatus || bus->ipend || + (!bus->fcstate && pktq_mlen(&bus->txq, ~bus->flowcontrol) && DATAOK(bus)) || + PKT_AVAILABLE()) { /* Read multiple frames */ + resched = TRUE; + } + + + bus->dpc_sched = resched; + + /* If we're done for now, turn off clock request. */ + if ((bus->clkstate != CLK_PENDING) && bus->idletime == DHD_IDLE_IMMEDIATE) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, FALSE); + } + + dhd_os_sdunlock(bus->dhd); + + return resched; +} + +bool +dhd_bus_dpc(struct dhd_bus *bus) +{ + bool resched; + + /* Call the DPC directly. */ + DHD_TRACE(("Calling dhdsdio_dpc() from %s\n", __FUNCTION__)); + resched = dhdsdio_dpc(bus); + + return resched; +} + +void +dhdsdio_isr(void *arg) +{ + dhd_bus_t *bus = (dhd_bus_t*)arg; + bcmsdh_info_t *sdh; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (!bus) { + DHD_ERROR(("%s : bus is null pointer , exit \n", __FUNCTION__)); + return; + } + sdh = bus->sdh; + + if (bus->dhd->busstate == DHD_BUS_DOWN) { + DHD_ERROR(("%s : bus is down. we have nothing to do\n", __FUNCTION__)); + return; + } + /* Count the interrupt call */ + bus->intrcount++; + bus->ipend = TRUE; + + /* Shouldn't get this interrupt if we're sleeping? */ + if (bus->sleeping) { + DHD_ERROR(("INTERRUPT WHILE SLEEPING??\n")); + return; + } + + /* Disable additional interrupts (is this needed now)? */ + if (bus->intr) { + DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__)); + } else { + DHD_ERROR(("dhdsdio_isr() w/o interrupt configured!\n")); + } + + bcmsdh_intr_disable(sdh); + bus->intdis = TRUE; + +#if defined(SDIO_ISR_THREAD) + DHD_TRACE(("Calling dhdsdio_dpc() from %s\n", __FUNCTION__)); + dhd_os_wake_lock(bus->dhd); + while (dhdsdio_dpc(bus)); + dhd_os_wake_unlock(bus->dhd); +#else + bus->dpc_sched = TRUE; + dhd_sched_dpc(bus->dhd); +#endif + +} + +#ifdef SDTEST +static void +dhdsdio_pktgen_init(dhd_bus_t *bus) +{ + /* Default to specified length, or full range */ + if (dhd_pktgen_len) { + bus->pktgen_maxlen = MIN(dhd_pktgen_len, MAX_PKTGEN_LEN); + bus->pktgen_minlen = bus->pktgen_maxlen; + } else { + bus->pktgen_maxlen = MAX_PKTGEN_LEN; + bus->pktgen_minlen = 0; + } + bus->pktgen_len = (uint16)bus->pktgen_minlen; + + /* Default to per-watchdog burst with 10s print time */ + bus->pktgen_freq = 1; + bus->pktgen_print = 10000 / dhd_watchdog_ms; + bus->pktgen_count = (dhd_pktgen * dhd_watchdog_ms + 999) / 1000; + + /* Default to echo mode */ + bus->pktgen_mode = DHD_PKTGEN_ECHO; + bus->pktgen_stop = 1; +} + +static void +dhdsdio_pktgen(dhd_bus_t *bus) +{ + void *pkt; + uint8 *data; + uint pktcount; + uint fillbyte; + osl_t *osh = bus->dhd->osh; + uint16 len; + + /* Display current count if appropriate */ + if (bus->pktgen_print && (++bus->pktgen_ptick >= bus->pktgen_print)) { + bus->pktgen_ptick = 0; + printf("%s: send attempts %d rcvd %d\n", + __FUNCTION__, bus->pktgen_sent, bus->pktgen_rcvd); + } + + /* For recv mode, just make sure dongle has started sending */ + if (bus->pktgen_mode == DHD_PKTGEN_RECV) { + if (!bus->pktgen_rcvd) + dhdsdio_sdtest_set(bus, TRUE); + return; + } + + /* Otherwise, generate or request the specified number of packets */ + for (pktcount = 0; pktcount < bus->pktgen_count; pktcount++) { + /* Stop if total has been reached */ + if (bus->pktgen_total && (bus->pktgen_sent >= bus->pktgen_total)) { + bus->pktgen_count = 0; + break; + } + + /* Allocate an appropriate-sized packet */ + len = bus->pktgen_len; + if (!(pkt = PKTGET(osh, (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN), + TRUE))) {; + DHD_ERROR(("%s: PKTGET failed!\n", __FUNCTION__)); + break; + } + PKTALIGN(osh, pkt, (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN), DHD_SDALIGN); + data = (uint8*)PKTDATA(osh, pkt) + SDPCM_HDRLEN; + + /* Write test header cmd and extra based on mode */ + switch (bus->pktgen_mode) { + case DHD_PKTGEN_ECHO: + *data++ = SDPCM_TEST_ECHOREQ; + *data++ = (uint8)bus->pktgen_sent; + break; + + case DHD_PKTGEN_SEND: + *data++ = SDPCM_TEST_DISCARD; + *data++ = (uint8)bus->pktgen_sent; + break; + + case DHD_PKTGEN_RXBURST: + *data++ = SDPCM_TEST_BURST; + *data++ = (uint8)bus->pktgen_count; + break; + + default: + DHD_ERROR(("Unrecognized pktgen mode %d\n", bus->pktgen_mode)); + PKTFREE(osh, pkt, TRUE); + bus->pktgen_count = 0; + return; + } + + /* Write test header length field */ + *data++ = (len >> 0); + *data++ = (len >> 8); + + /* Then fill in the remainder -- N/A for burst, but who cares... */ + for (fillbyte = 0; fillbyte < len; fillbyte++) + *data++ = SDPCM_TEST_FILL(fillbyte, (uint8)bus->pktgen_sent); + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_DATA_ON()) { + data = (uint8*)PKTDATA(osh, pkt) + SDPCM_HDRLEN; + prhex("dhdsdio_pktgen: Tx Data", data, PKTLEN(osh, pkt) - SDPCM_HDRLEN); + } +#endif + + /* Send it */ + if (dhdsdio_txpkt(bus, pkt, SDPCM_TEST_CHANNEL, TRUE)) { + bus->pktgen_fail++; + if (bus->pktgen_stop && bus->pktgen_stop == bus->pktgen_fail) + bus->pktgen_count = 0; + } + bus->pktgen_sent++; + + /* Bump length if not fixed, wrap at max */ + if (++bus->pktgen_len > bus->pktgen_maxlen) + bus->pktgen_len = (uint16)bus->pktgen_minlen; + + /* Special case for burst mode: just send one request! */ + if (bus->pktgen_mode == DHD_PKTGEN_RXBURST) + break; + } +} + +static void +dhdsdio_sdtest_set(dhd_bus_t *bus, bool start) +{ + void *pkt; + uint8 *data; + osl_t *osh = bus->dhd->osh; + + /* Allocate the packet */ + if (!(pkt = PKTGET(osh, SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN, TRUE))) { + DHD_ERROR(("%s: PKTGET failed!\n", __FUNCTION__)); + return; + } + PKTALIGN(osh, pkt, (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN), DHD_SDALIGN); + data = (uint8*)PKTDATA(osh, pkt) + SDPCM_HDRLEN; + + /* Fill in the test header */ + *data++ = SDPCM_TEST_SEND; + *data++ = start; + *data++ = (bus->pktgen_maxlen >> 0); + *data++ = (bus->pktgen_maxlen >> 8); + + /* Send it */ + if (dhdsdio_txpkt(bus, pkt, SDPCM_TEST_CHANNEL, TRUE)) + bus->pktgen_fail++; +} + + +static void +dhdsdio_testrcv(dhd_bus_t *bus, void *pkt, uint seq) +{ + osl_t *osh = bus->dhd->osh; + uint8 *data; + uint pktlen; + + uint8 cmd; + uint8 extra; + uint16 len; + uint16 offset; + + /* Check for min length */ + if ((pktlen = PKTLEN(osh, pkt)) < SDPCM_TEST_HDRLEN) { + DHD_ERROR(("dhdsdio_restrcv: toss runt frame, pktlen %d\n", pktlen)); + PKTFREE(osh, pkt, FALSE); + return; + } + + /* Extract header fields */ + data = PKTDATA(osh, pkt); + cmd = *data++; + extra = *data++; + len = *data++; len += *data++ << 8; + + /* Check length for relevant commands */ + if (cmd == SDPCM_TEST_DISCARD || cmd == SDPCM_TEST_ECHOREQ || cmd == SDPCM_TEST_ECHORSP) { + if (pktlen != len + SDPCM_TEST_HDRLEN) { + DHD_ERROR(("dhdsdio_testrcv: frame length mismatch, pktlen %d seq %d" + " cmd %d extra %d len %d\n", pktlen, seq, cmd, extra, len)); + PKTFREE(osh, pkt, FALSE); + return; + } + } + + /* Process as per command */ + switch (cmd) { + case SDPCM_TEST_ECHOREQ: + /* Rx->Tx turnaround ok (even on NDIS w/current implementation) */ + *(uint8 *)(PKTDATA(osh, pkt)) = SDPCM_TEST_ECHORSP; + if (dhdsdio_txpkt(bus, pkt, SDPCM_TEST_CHANNEL, TRUE) == 0) { + bus->pktgen_sent++; + } else { + bus->pktgen_fail++; + PKTFREE(osh, pkt, FALSE); + } + bus->pktgen_rcvd++; + break; + + case SDPCM_TEST_ECHORSP: + if (bus->ext_loop) { + PKTFREE(osh, pkt, FALSE); + bus->pktgen_rcvd++; + break; + } + + for (offset = 0; offset < len; offset++, data++) { + if (*data != SDPCM_TEST_FILL(offset, extra)) { + DHD_ERROR(("dhdsdio_testrcv: echo data mismatch: " + "offset %d (len %d) expect 0x%02x rcvd 0x%02x\n", + offset, len, SDPCM_TEST_FILL(offset, extra), *data)); + break; + } + } + PKTFREE(osh, pkt, FALSE); + bus->pktgen_rcvd++; + break; + + case SDPCM_TEST_DISCARD: + PKTFREE(osh, pkt, FALSE); + bus->pktgen_rcvd++; + break; + + case SDPCM_TEST_BURST: + case SDPCM_TEST_SEND: + default: + DHD_INFO(("dhdsdio_testrcv: unsupported or unknown command, pktlen %d seq %d" + " cmd %d extra %d len %d\n", pktlen, seq, cmd, extra, len)); + PKTFREE(osh, pkt, FALSE); + break; + } + + /* For recv mode, stop at limie (and tell dongle to stop sending) */ + if (bus->pktgen_mode == DHD_PKTGEN_RECV) { + if (bus->pktgen_total && (bus->pktgen_rcvd >= bus->pktgen_total)) { + bus->pktgen_count = 0; + dhdsdio_sdtest_set(bus, FALSE); + } + } +} +#endif /* SDTEST */ + +extern bool +dhd_bus_watchdog(dhd_pub_t *dhdp) +{ + dhd_bus_t *bus; + + DHD_TIMER(("%s: Enter\n", __FUNCTION__)); + + bus = dhdp->bus; + + if (bus->dhd->dongle_reset) + return FALSE; + + /* Ignore the timer if simulating bus down */ + if (bus->sleeping) + return FALSE; + + /* Poll period: check device if appropriate. */ + if (bus->poll && (++bus->polltick >= bus->pollrate)) { + uint32 intstatus = 0; + + /* Reset poll tick */ + bus->polltick = 0; + + /* Check device if no interrupts */ + if (!bus->intr || (bus->intrcount == bus->lastintrs)) { + + if (!bus->dpc_sched) { + uint8 devpend; + devpend = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0, + SDIOD_CCCR_INTPEND, NULL); + intstatus = devpend & (INTR_STATUS_FUNC1 | INTR_STATUS_FUNC2); + } + + /* If there is something, make like the ISR and schedule the DPC */ + if (intstatus) { + bus->pollcnt++; + bus->ipend = TRUE; + if (bus->intr) { + bcmsdh_intr_disable(bus->sdh); + } + bus->dpc_sched = TRUE; + dhd_sched_dpc(bus->dhd); + + } + } + + /* Update interrupt tracking */ + bus->lastintrs = bus->intrcount; + } + +#ifdef DHD_DEBUG + /* Poll for console output periodically */ + if (dhdp->busstate == DHD_BUS_DATA && dhd_console_ms != 0) { + bus->console.count += dhd_watchdog_ms; + if (bus->console.count >= dhd_console_ms) { + bus->console.count -= dhd_console_ms; + /* Make sure backplane clock is on */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + if (dhdsdio_readconsole(bus) < 0) + dhd_console_ms = 0; /* On error, stop trying */ + } + } +#endif /* DHD_DEBUG */ + +#ifdef SDTEST + /* Generate packets if configured */ + if (bus->pktgen_count && (++bus->pktgen_tick >= bus->pktgen_freq)) { + /* Make sure backplane clock is on */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + bus->pktgen_tick = 0; + dhdsdio_pktgen(bus); + } +#endif + + /* On idle timeout clear activity flag and/or turn off clock */ + if ((bus->idletime > 0) && (bus->clkstate == CLK_AVAIL)) { + if (++bus->idlecount >= bus->idletime) { + bus->idlecount = 0; + if (bus->activity) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, FALSE); + } + } + } + + return bus->ipend; +} + +#ifdef DHD_DEBUG +extern int +dhd_bus_console_in(dhd_pub_t *dhdp, uchar *msg, uint msglen) +{ + dhd_bus_t *bus = dhdp->bus; + uint32 addr, val; + int rv; + void *pkt; + + /* Address could be zero if CONSOLE := 0 in dongle Makefile */ + if (bus->console_addr == 0) + return BCME_UNSUPPORTED; + + /* Exclusive bus access */ + dhd_os_sdlock(bus->dhd); + + /* Don't allow input if dongle is in reset */ + if (bus->dhd->dongle_reset) { + dhd_os_sdunlock(bus->dhd); + return BCME_NOTREADY; + } + + /* Request clock to allow SDIO accesses */ + BUS_WAKE(bus); + /* No pend allowed since txpkt is called later, ht clk has to be on */ + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + /* Zero cbuf_index */ + addr = bus->console_addr + OFFSETOF(hndrte_cons_t, cbuf_idx); + val = htol32(0); + if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)&val, sizeof(val))) < 0) + goto done; + + /* Write message into cbuf */ + addr = bus->console_addr + OFFSETOF(hndrte_cons_t, cbuf); + if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)msg, msglen)) < 0) + goto done; + + /* Write length into vcons_in */ + addr = bus->console_addr + OFFSETOF(hndrte_cons_t, vcons_in); + val = htol32(msglen); + if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)&val, sizeof(val))) < 0) + goto done; + + /* Bump dongle by sending an empty event pkt. + * sdpcm_sendup (RX) checks for virtual console input. + */ + if (((pkt = PKTGET(bus->dhd->osh, 4 + SDPCM_RESERVE, TRUE)) != NULL) && + bus->clkstate == CLK_AVAIL) + dhdsdio_txpkt(bus, pkt, SDPCM_EVENT_CHANNEL, TRUE); + +done: + if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) { + bus->activity = FALSE; + dhdsdio_clkctl(bus, CLK_NONE, TRUE); + } + + dhd_os_sdunlock(bus->dhd); + + return rv; +} +#endif /* DHD_DEBUG */ + +#ifdef DHD_DEBUG +static void +dhd_dump_cis(uint fn, uint8 *cis) +{ + uint byte, tag, tdata; + DHD_INFO(("Function %d CIS:\n", fn)); + + for (tdata = byte = 0; byte < SBSDIO_CIS_SIZE_LIMIT; byte++) { + if ((byte % 16) == 0) + DHD_INFO((" ")); + DHD_INFO(("%02x ", cis[byte])); + if ((byte % 16) == 15) + DHD_INFO(("\n")); + if (!tdata--) { + tag = cis[byte]; + if (tag == 0xff) + break; + else if (!tag) + tdata = 0; + else if ((byte + 1) < SBSDIO_CIS_SIZE_LIMIT) + tdata = cis[byte + 1] + 1; + else + DHD_INFO(("]")); + } + } + if ((byte % 16) != 15) + DHD_INFO(("\n")); +} +#endif /* DHD_DEBUG */ + +static bool +dhdsdio_chipmatch(uint16 chipid) +{ + if (chipid == BCM4325_CHIP_ID) + return TRUE; + if (chipid == BCM4329_CHIP_ID) + return TRUE; + if (chipid == BCM4315_CHIP_ID) + return TRUE; + if (chipid == BCM4319_CHIP_ID) + return TRUE; + return FALSE; +} + +static void * +dhdsdio_probe(uint16 venid, uint16 devid, uint16 bus_no, uint16 slot, + uint16 func, uint bustype, void *regsva, osl_t * osh, void *sdh) +{ + int ret; + dhd_bus_t *bus; + + /* Init global variables at run-time, not as part of the declaration. + * This is required to support init/de-init of the driver. Initialization + * of globals as part of the declaration results in non-deterministic + * behavior since the value of the globals may be different on the + * first time that the driver is initialized vs subsequent initializations. + */ + dhd_txbound = DHD_TXBOUND; + dhd_rxbound = DHD_RXBOUND; + dhd_alignctl = TRUE; + sd1idle = TRUE; + dhd_readahead = TRUE; + retrydata = FALSE; + dhd_doflow = FALSE; + dhd_dongle_memsize = 0; + dhd_txminmax = DHD_TXMINMAX; + + forcealign = TRUE; + + + dhd_common_init(); + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + DHD_INFO(("%s: venid 0x%04x devid 0x%04x\n", __FUNCTION__, venid, devid)); + + /* We make assumptions about address window mappings */ + ASSERT((uintptr)regsva == SI_ENUM_BASE); + + /* BCMSDH passes venid and devid based on CIS parsing -- but low-power start + * means early parse could fail, so here we should get either an ID + * we recognize OR (-1) indicating we must request power first. + */ + /* Check the Vendor ID */ + switch (venid) { + case 0x0000: + case VENDOR_BROADCOM: + break; + default: + DHD_ERROR(("%s: unknown vendor: 0x%04x\n", + __FUNCTION__, venid)); + return NULL; + } + + /* Check the Device ID and make sure it's one that we support */ + switch (devid) { + case BCM4325_D11DUAL_ID: /* 4325 802.11a/g id */ + case BCM4325_D11G_ID: /* 4325 802.11g 2.4Ghz band id */ + case BCM4325_D11A_ID: /* 4325 802.11a 5Ghz band id */ + DHD_INFO(("%s: found 4325 Dongle\n", __FUNCTION__)); + break; + case BCM4329_D11NDUAL_ID: /* 4329 802.11n dualband device */ + case BCM4329_D11N2G_ID: /* 4329 802.11n 2.4G device */ + case BCM4329_D11N5G_ID: /* 4329 802.11n 5G device */ + case 0x4329: + DHD_INFO(("%s: found 4329 Dongle\n", __FUNCTION__)); + break; + case BCM4315_D11DUAL_ID: /* 4315 802.11a/g id */ + case BCM4315_D11G_ID: /* 4315 802.11g id */ + case BCM4315_D11A_ID: /* 4315 802.11a id */ + DHD_INFO(("%s: found 4315 Dongle\n", __FUNCTION__)); + break; + case BCM4319_D11N_ID: /* 4319 802.11n id */ + case BCM4319_D11N2G_ID: /* 4319 802.11n2g id */ + case BCM4319_D11N5G_ID: /* 4319 802.11n5g id */ + DHD_INFO(("%s: found 4319 Dongle\n", __FUNCTION__)); + break; + case 0: + DHD_INFO(("%s: allow device id 0, will check chip internals\n", + __FUNCTION__)); + break; + + default: + DHD_ERROR(("%s: skipping 0x%04x/0x%04x, not a dongle\n", + __FUNCTION__, venid, devid)); + return NULL; + } + + if (osh == NULL) { + /* Ask the OS interface part for an OSL handle */ + if (!(osh = dhd_osl_attach(sdh, DHD_BUS))) { + DHD_ERROR(("%s: osl_attach failed!\n", __FUNCTION__)); + return NULL; + } + } + + /* Allocate private bus interface state */ + if (!(bus = MALLOC(osh, sizeof(dhd_bus_t)))) { + DHD_ERROR(("%s: MALLOC of dhd_bus_t failed\n", __FUNCTION__)); + goto fail; + } + bzero(bus, sizeof(dhd_bus_t)); + bus->sdh = sdh; + bus->cl_devid = (uint16)devid; + bus->bus = DHD_BUS; + bus->tx_seq = SDPCM_SEQUENCE_WRAP - 1; + bus->usebufpool = FALSE; /* Use bufpool if allocated, else use locally malloced rxbuf */ + + /* attempt to attach to the dongle */ + if (!(dhdsdio_probe_attach(bus, osh, sdh, regsva, devid))) { + DHD_ERROR(("%s: dhdsdio_probe_attach failed\n", __FUNCTION__)); + goto fail; + } + + /* Attach to the dhd/OS/network interface */ + if (!(bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE))) { + DHD_ERROR(("%s: dhd_attach failed\n", __FUNCTION__)); + goto fail; + } + + /* Allocate buffers */ + if (!(dhdsdio_probe_malloc(bus, osh, sdh))) { + DHD_ERROR(("%s: dhdsdio_probe_malloc failed\n", __FUNCTION__)); + goto fail; + } + + if (!(dhdsdio_probe_init(bus, osh, sdh))) { + DHD_ERROR(("%s: dhdsdio_probe_init failed\n", __FUNCTION__)); + goto fail; + } + + /* Register interrupt callback, but mask it (not operational yet). */ + DHD_INTR(("%s: disable SDIO interrupts (not interested yet)\n", __FUNCTION__)); + bcmsdh_intr_disable(sdh); + if ((ret = bcmsdh_intr_reg(sdh, dhdsdio_isr, bus)) != 0) { + DHD_ERROR(("%s: FAILED: bcmsdh_intr_reg returned %d\n", + __FUNCTION__, ret)); + goto fail; + } + DHD_INTR(("%s: registered SDIO interrupt function ok\n", __FUNCTION__)); + + DHD_INFO(("%s: completed!!\n", __FUNCTION__)); + + + /* if firmware path present try to download and bring up bus */ + if ((ret = dhd_bus_start(bus->dhd)) != 0) { +#if 1 + DHD_ERROR(("%s: failed\n", __FUNCTION__)); + goto fail; +#else + if (ret == BCME_NOTUP) { + DHD_ERROR(("%s: dongle is not responding\n", __FUNCTION__)); + goto fail; + } +#endif + } + /* Ok, have the per-port tell the stack we're open for business */ + if (dhd_net_attach(bus->dhd, 0) != 0) { + DHD_ERROR(("%s: Net attach failed!!\n", __FUNCTION__)); + goto fail; + } + + return bus; + +fail: + dhdsdio_release(bus, osh); + return NULL; +} + + +static bool +dhdsdio_probe_attach(struct dhd_bus *bus, osl_t *osh, void *sdh, void *regsva, + uint16 devid) +{ + uint8 clkctl = 0; + int err = 0; + + bus->alp_only = TRUE; + + /* Return the window to backplane enumeration space for core access */ + if (dhdsdio_set_siaddr_window(bus, SI_ENUM_BASE)) { + DHD_ERROR(("%s: FAILED to return to SI_ENUM_BASE\n", __FUNCTION__)); + } + +#ifdef DHD_DEBUG + printf("F1 signature read @0x18000000=0x%4x\n", + bcmsdh_reg_read(bus->sdh, SI_ENUM_BASE, 4)); + + +#endif /* DHD_DEBUG */ + + + /* Force PLL off until si_attach() programs PLL control regs */ + + + + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, DHD_INIT_CLKCTL1, &err); + if (!err) + clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err); + + if (err || ((clkctl & ~SBSDIO_AVBITS) != DHD_INIT_CLKCTL1)) { + DHD_ERROR(("dhdsdio_probe: ChipClkCSR access: err %d wrote 0x%02x read 0x%02x\n", + err, DHD_INIT_CLKCTL1, clkctl)); + goto fail; + } + + +#ifdef DHD_DEBUG + if (DHD_INFO_ON()) { + uint fn, numfn; + uint8 *cis[SDIOD_MAX_IOFUNCS]; + int err = 0; + + numfn = bcmsdh_query_iofnum(sdh); + ASSERT(numfn <= SDIOD_MAX_IOFUNCS); + + /* Make sure ALP is available before trying to read CIS */ + SPINWAIT(((clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, NULL)), + !SBSDIO_ALPAV(clkctl)), PMU_MAX_TRANSITION_DLY); + + /* Now request ALP be put on the bus */ + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, + DHD_INIT_CLKCTL2, &err); + OSL_DELAY(65); + + for (fn = 0; fn <= numfn; fn++) { + if (!(cis[fn] = MALLOC(osh, SBSDIO_CIS_SIZE_LIMIT))) { + DHD_INFO(("dhdsdio_probe: fn %d cis malloc failed\n", fn)); + break; + } + bzero(cis[fn], SBSDIO_CIS_SIZE_LIMIT); + + if ((err = bcmsdh_cis_read(sdh, fn, cis[fn], SBSDIO_CIS_SIZE_LIMIT))) { + DHD_INFO(("dhdsdio_probe: fn %d cis read err %d\n", fn, err)); + MFREE(osh, cis[fn], SBSDIO_CIS_SIZE_LIMIT); + break; + } + dhd_dump_cis(fn, cis[fn]); + } + + while (fn-- > 0) { + ASSERT(cis[fn]); + MFREE(osh, cis[fn], SBSDIO_CIS_SIZE_LIMIT); + } + + if (err) { + DHD_ERROR(("dhdsdio_probe: failure reading or parsing CIS\n")); + goto fail; + } + } +#endif /* DHD_DEBUG */ + + /* si_attach() will provide an SI handle and scan the backplane */ + if (!(bus->sih = si_attach((uint)devid, osh, regsva, DHD_BUS, sdh, + &bus->vars, &bus->varsz))) { + DHD_ERROR(("%s: si_attach failed!\n", __FUNCTION__)); + goto fail; + } + + bcmsdh_chipinfo(sdh, bus->sih->chip, bus->sih->chiprev); + + if (!dhdsdio_chipmatch((uint16)bus->sih->chip)) { + DHD_ERROR(("%s: unsupported chip: 0x%04x\n", + __FUNCTION__, bus->sih->chip)); + goto fail; + } + + si_sdiod_drive_strength_init(bus->sih, osh, dhd_sdiod_drive_strength); + + + /* Get info on the ARM and SOCRAM cores... */ + if (!DHD_NOPMU(bus)) { + if ((si_setcore(bus->sih, ARM7S_CORE_ID, 0)) || + (si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) { + bus->armrev = si_corerev(bus->sih); + } else { + DHD_ERROR(("%s: failed to find ARM core!\n", __FUNCTION__)); + goto fail; + } + if (!(bus->orig_ramsize = si_socram_size(bus->sih))) { + DHD_ERROR(("%s: failed to find SOCRAM memory!\n", __FUNCTION__)); + goto fail; + } + bus->ramsize = bus->orig_ramsize; + if (dhd_dongle_memsize) + dhd_dongle_setmemsize(bus, dhd_dongle_memsize); + + DHD_ERROR(("DHD: dongle ram size is set to %d(orig %d)\n", + bus->ramsize, bus->orig_ramsize)); + } + + /* ...but normally deal with the SDPCMDEV core */ + if (!(bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0)) && + !(bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0))) { + DHD_ERROR(("%s: failed to find SDIODEV core!\n", __FUNCTION__)); + goto fail; + } + bus->sdpcmrev = si_corerev(bus->sih); + + /* Set core control so an SDIO reset does a backplane reset */ + OR_REG(osh, &bus->regs->corecontrol, CC_BPRESEN); + + pktq_init(&bus->txq, (PRIOMASK + 1), QLEN); + + /* Locate an appropriately-aligned portion of hdrbuf */ + bus->rxhdr = (uint8 *)ROUNDUP((uintptr)&bus->hdrbuf[0], DHD_SDALIGN); + + /* Set the poll and/or interrupt flags */ + bus->intr = (bool)dhd_intr; + if ((bus->poll = (bool)dhd_poll)) + bus->pollrate = 1; + + return TRUE; + +fail: + return FALSE; +} + +static bool +dhdsdio_probe_malloc(dhd_bus_t *bus, osl_t *osh, void *sdh) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + +#ifndef DHD_USE_STATIC_BUF + if (bus->dhd->maxctl) { + bus->rxblen = ROUNDUP((bus->dhd->maxctl + SDPCM_HDRLEN), ALIGNMENT) + DHD_SDALIGN; + if (!(bus->rxbuf = MALLOC(osh, bus->rxblen))) { + DHD_ERROR(("%s: MALLOC of %d-byte rxbuf failed\n", + __FUNCTION__, bus->rxblen)); + goto fail; + } + } + + /* Allocate buffer to receive glomed packet */ + if (!(bus->databuf = MALLOC(osh, MAX_DATA_BUF))) { + DHD_ERROR(("%s: MALLOC of %d-byte databuf failed\n", + __FUNCTION__, MAX_DATA_BUF)); + /* release rxbuf which was already located as above */ + if (!bus->rxblen) MFREE(osh, bus->rxbuf, bus->rxblen); + goto fail; + } +#else + if (bus->dhd->maxctl) { + bus->rxblen = ROUNDUP((bus->dhd->maxctl + SDPCM_HDRLEN), ALIGNMENT) + DHD_SDALIGN; + if (!(bus->rxbuf = dhd_os_prealloc(DHD_PREALLOC_RXBUF, bus->rxblen))) { + DHD_ERROR(("%s: MALLOC of %d-byte rxbuf failed\n", + __FUNCTION__, bus->rxblen)); + goto fail; + } + } + /* Allocate buffer to receive glomed packet */ + if (!(bus->databuf = dhd_os_prealloc(DHD_PREALLOC_DATABUF, MAX_DATA_BUF))) { + DHD_ERROR(("%s: MALLOC of %d-byte databuf failed\n", + __FUNCTION__, MAX_DATA_BUF)); + goto fail; + } +#endif /* DHD_USE_STATIC_BUF */ + + /* Align the buffer */ + if ((uintptr)bus->databuf % DHD_SDALIGN) + bus->dataptr = bus->databuf + (DHD_SDALIGN - ((uintptr)bus->databuf % DHD_SDALIGN)); + else + bus->dataptr = bus->databuf; + + return TRUE; + +fail: + return FALSE; +} + + +static bool +dhdsdio_probe_init(dhd_bus_t *bus, osl_t *osh, void *sdh) +{ + int32 fnum; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + +#ifdef SDTEST + dhdsdio_pktgen_init(bus); +#endif /* SDTEST */ + + /* Disable F2 to clear any intermediate frame state on the dongle */ + bcmsdh_cfg_write(sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, SDIO_FUNC_ENABLE_1, NULL); + + bus->dhd->busstate = DHD_BUS_DOWN; + bus->sleeping = FALSE; + bus->rxflow = FALSE; + bus->prev_rxlim_hit = 0; + + + /* Done with backplane-dependent accesses, can drop clock... */ + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL); + + /* ...and initialize clock/power states */ + bus->clkstate = CLK_SDONLY; + bus->idletime = (int32)dhd_idletime; + bus->idleclock = DHD_IDLE_ACTIVE; + + /* Query the SD clock speed */ + if (bcmsdh_iovar_op(sdh, "sd_divisor", NULL, 0, + &bus->sd_divisor, sizeof(int32), FALSE) != BCME_OK) { + DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, "sd_divisor")); + bus->sd_divisor = -1; + } else { + DHD_INFO(("%s: Initial value for %s is %d\n", + __FUNCTION__, "sd_divisor", bus->sd_divisor)); + } + + /* Query the SD bus mode */ + if (bcmsdh_iovar_op(sdh, "sd_mode", NULL, 0, + &bus->sd_mode, sizeof(int32), FALSE) != BCME_OK) { + DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, "sd_mode")); + bus->sd_mode = -1; + } else { + DHD_INFO(("%s: Initial value for %s is %d\n", + __FUNCTION__, "sd_mode", bus->sd_mode)); + } + + /* Query the F2 block size, set roundup accordingly */ + fnum = 2; + if (bcmsdh_iovar_op(sdh, "sd_blocksize", &fnum, sizeof(int32), + &bus->blocksize, sizeof(int32), FALSE) != BCME_OK) { + bus->blocksize = 0; + DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, "sd_blocksize")); + } else { + DHD_INFO(("%s: Initial value for %s is %d\n", + __FUNCTION__, "sd_blocksize", bus->blocksize)); + } + bus->roundup = MIN(max_roundup, bus->blocksize); + + /* Query if bus module supports packet chaining, default to use if supported */ + if (bcmsdh_iovar_op(sdh, "sd_rxchain", NULL, 0, + &bus->sd_rxchain, sizeof(int32), FALSE) != BCME_OK) { + bus->sd_rxchain = FALSE; + } else { + DHD_INFO(("%s: bus module (through bcmsdh API) %s chaining\n", + __FUNCTION__, (bus->sd_rxchain ? "supports" : "does not support"))); + } + bus->use_rxchain = (bool)bus->sd_rxchain; + + return TRUE; +} + +bool +dhd_bus_download_firmware(struct dhd_bus *bus, osl_t *osh, + char *fw_path, char *nv_path) +{ + bool ret; + bus->fw_path = fw_path; + bus->nv_path = nv_path; + + ret = dhdsdio_download_firmware(bus, osh, bus->sdh); + + return ret; +} + +static bool +dhdsdio_download_firmware(struct dhd_bus *bus, osl_t *osh, void *sdh) +{ + bool ret; + + /* Download the firmware */ + dhd_os_wake_lock(bus->dhd); + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); + + ret = _dhdsdio_download_firmware(bus) == 0; + + dhdsdio_clkctl(bus, CLK_SDONLY, FALSE); + dhd_os_wake_unlock(bus->dhd); + return ret; +} + +/* Detach and free everything */ +static void +dhdsdio_release(dhd_bus_t *bus, osl_t *osh) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (bus) { + ASSERT(osh); + + + /* De-register interrupt handler */ + bcmsdh_intr_disable(bus->sdh); + bcmsdh_intr_dereg(bus->sdh); + + if (bus->dhd) { + + dhdsdio_release_dongle(bus, osh, TRUE); + + dhd_detach(bus->dhd); + bus->dhd = NULL; + } + + dhdsdio_release_malloc(bus, osh); + + + MFREE(osh, bus, sizeof(dhd_bus_t)); + } + + if (osh) + dhd_osl_detach(osh); + + DHD_TRACE(("%s: Disconnected\n", __FUNCTION__)); +} + +static void +dhdsdio_release_malloc(dhd_bus_t *bus, osl_t *osh) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (bus->dhd && bus->dhd->dongle_reset) + return; + + if (bus->rxbuf) { +#ifndef DHD_USE_STATIC_BUF + MFREE(osh, bus->rxbuf, bus->rxblen); +#endif + bus->rxctl = bus->rxbuf = NULL; + bus->rxlen = 0; + } + + if (bus->databuf) { +#ifndef DHD_USE_STATIC_BUF + MFREE(osh, bus->databuf, MAX_DATA_BUF); +#endif + bus->databuf = NULL; + } +} + + +static void +dhdsdio_release_dongle(dhd_bus_t *bus, osl_t *osh, int reset_flag) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if ((bus->dhd && bus->dhd->dongle_reset) && reset_flag) + return; + + if (bus->sih) { + dhdsdio_clkctl(bus, CLK_AVAIL, FALSE); +#if !defined(BCMLXSDMMC) + si_watchdog(bus->sih, 4); +#endif /* !defined(BCMLXSDMMC) */ + dhdsdio_clkctl(bus, CLK_NONE, FALSE); + si_detach(bus->sih); + if (bus->vars && bus->varsz) + MFREE(osh, bus->vars, bus->varsz); + bus->vars = NULL; + } + + DHD_TRACE(("%s: Disconnected\n", __FUNCTION__)); +} + +static void +dhdsdio_disconnect(void *ptr) +{ + dhd_bus_t *bus = (dhd_bus_t *)ptr; + + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + if (bus) { + ASSERT(bus->dhd); + dhdsdio_release(bus, bus->dhd->osh); + } + + DHD_TRACE(("%s: Disconnected\n", __FUNCTION__)); +} + + +/* Register/Unregister functions are called by the main DHD entry + * point (e.g. module insertion) to link with the bus driver, in + * order to look for or await the device. + */ + +static bcmsdh_driver_t dhd_sdio = { + dhdsdio_probe, + dhdsdio_disconnect +}; + +int +dhd_bus_register(void) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + return bcmsdh_register(&dhd_sdio); +} + +void +dhd_bus_unregister(void) +{ + DHD_TRACE(("%s: Enter\n", __FUNCTION__)); + + bcmsdh_unregister(); +} + +#ifdef BCMEMBEDIMAGE +static int +dhdsdio_download_code_array(struct dhd_bus *bus) +{ + int bcmerror = -1; + int offset = 0; + + DHD_INFO(("%s: download embedded firmware...\n", __FUNCTION__)); + + /* Download image */ + while ((offset + MEMBLOCK) < sizeof(dlarray)) { + bcmerror = dhdsdio_membytes(bus, TRUE, offset, dlarray + offset, MEMBLOCK); + if (bcmerror) { + DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n", + __FUNCTION__, bcmerror, MEMBLOCK, offset)); + goto err; + } + + offset += MEMBLOCK; + } + + if (offset < sizeof(dlarray)) { + bcmerror = dhdsdio_membytes(bus, TRUE, offset, + dlarray + offset, sizeof(dlarray) - offset); + if (bcmerror) { + DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n", + __FUNCTION__, bcmerror, sizeof(dlarray) - offset, offset)); + goto err; + } + } + +#ifdef DHD_DEBUG + /* Upload and compare the downloaded code */ + { + unsigned char *ularray; + + ularray = MALLOC(bus->dhd->osh, bus->ramsize); + /* Upload image to verify downloaded contents. */ + offset = 0; + memset(ularray, 0xaa, bus->ramsize); + while ((offset + MEMBLOCK) < sizeof(dlarray)) { + bcmerror = dhdsdio_membytes(bus, FALSE, offset, ularray + offset, MEMBLOCK); + if (bcmerror) { + DHD_ERROR(("%s: error %d on reading %d membytes at 0x%08x\n", + __FUNCTION__, bcmerror, MEMBLOCK, offset)); + goto err; + } + + offset += MEMBLOCK; + } + + if (offset < sizeof(dlarray)) { + bcmerror = dhdsdio_membytes(bus, FALSE, offset, + ularray + offset, sizeof(dlarray) - offset); + if (bcmerror) { + DHD_ERROR(("%s: error %d on reading %d membytes at 0x%08x\n", + __FUNCTION__, bcmerror, sizeof(dlarray) - offset, offset)); + goto err; + } + } + + if (memcmp(dlarray, ularray, sizeof(dlarray))) { + DHD_ERROR(("%s: Downloaded image is corrupted.\n", __FUNCTION__)); + ASSERT(0); + goto err; + } else + DHD_ERROR(("%s: Download, Upload and compare succeeded.\n", __FUNCTION__)); + + MFREE(bus->dhd->osh, ularray, bus->ramsize); + } +#endif /* DHD_DEBUG */ + +err: + return bcmerror; +} +#endif /* BCMEMBEDIMAGE */ + +static int +dhdsdio_download_code_file(struct dhd_bus *bus, char *fw_path) +{ + int bcmerror = -1; + int offset = 0; + uint len; + void *image = NULL; + uint8 *memblock = NULL, *memptr; + + DHD_INFO(("%s: download firmware %s\n", __FUNCTION__, fw_path)); + + image = dhd_os_open_image(fw_path); + if (image == NULL) + goto err; + + memptr = memblock = MALLOC(bus->dhd->osh, MEMBLOCK + DHD_SDALIGN); + if (memblock == NULL) { + DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__, MEMBLOCK)); + goto err; + } + if ((uint32)(uintptr)memblock % DHD_SDALIGN) + memptr += (DHD_SDALIGN - ((uint32)(uintptr)memblock % DHD_SDALIGN)); + + /* Download image */ + while ((len = dhd_os_get_image_block((char*)memptr, MEMBLOCK, image))) { + bcmerror = dhdsdio_membytes(bus, TRUE, offset, memptr, len); + if (bcmerror) { + DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n", + __FUNCTION__, bcmerror, MEMBLOCK, offset)); + goto err; + } + + offset += MEMBLOCK; + } + +err: + if (memblock) + MFREE(bus->dhd->osh, memblock, MEMBLOCK + DHD_SDALIGN); + + if (image) + dhd_os_close_image(image); + + return bcmerror; +} + +/* + * ProcessVars:Takes a buffer of "=\n" lines read from a file and ending in a NUL. + * Removes carriage returns, empty lines, comment lines, and converts newlines to NULs. + * Shortens buffer as needed and pads with NULs. End of buffer is marked by two NULs. +*/ + +static uint +process_nvram_vars(char *varbuf, uint len) +{ + char *dp; + bool findNewline; + int column; + uint buf_len, n; + + dp = varbuf; + + findNewline = FALSE; + column = 0; + + for (n = 0; n < len; n++) { + if (varbuf[n] == 0) + break; + if (varbuf[n] == '\r') + continue; + if (findNewline && varbuf[n] != '\n') + continue; + findNewline = FALSE; + if (varbuf[n] == '#') { + findNewline = TRUE; + continue; + } + if (varbuf[n] == '\n') { + if (column == 0) + continue; + *dp++ = 0; + column = 0; + continue; + } + *dp++ = varbuf[n]; + column++; + } + buf_len = dp - varbuf; + + while (dp < varbuf + n) + *dp++ = 0; + + return buf_len; +} + +/* + EXAMPLE: nvram_array + nvram_arry format: + name=value + Use carriage return at the end of each assignment, and an empty string with + carriage return at the end of array. + + For example: + unsigned char nvram_array[] = {"name1=value1\n", "name2=value2\n", "\n"}; + Hex values start with 0x, and mac addr format: xx:xx:xx:xx:xx:xx. + + Search "EXAMPLE: nvram_array" to see how the array is activated. +*/ + +void +dhd_bus_set_nvram_params(struct dhd_bus * bus, const char *nvram_params) +{ + bus->nvram_params = nvram_params; +} + +static int +dhdsdio_download_nvram(struct dhd_bus *bus) +{ + int bcmerror = -1; + uint len; + void * image = NULL; + char * memblock = NULL; + char *bufp; + char *nv_path; + bool nvram_file_exists; + + nv_path = bus->nv_path; + + nvram_file_exists = ((nv_path != NULL) && (nv_path[0] != '\0')); + if (!nvram_file_exists && (bus->nvram_params == NULL)) + return (0); + + if (nvram_file_exists) { + image = dhd_os_open_image(nv_path); + if (image == NULL) + goto err; + } + + memblock = MALLOC(bus->dhd->osh, MEMBLOCK); + if (memblock == NULL) { + DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", + __FUNCTION__, MEMBLOCK)); + goto err; + } + + /* Download variables */ + if (nvram_file_exists) { + len = dhd_os_get_image_block(memblock, MEMBLOCK, image); + } + else { + len = strlen(bus->nvram_params); + ASSERT(len <= MEMBLOCK); + if (len > MEMBLOCK) + len = MEMBLOCK; + memcpy(memblock, bus->nvram_params, len); + } + + if (len > 0 && len < MEMBLOCK) { + bufp = (char *)memblock; + bufp[len] = 0; + len = process_nvram_vars(bufp, len); + bufp += len; + *bufp++ = 0; + if (len) + bcmerror = dhdsdio_downloadvars(bus, memblock, len + 1); + if (bcmerror) { + DHD_ERROR(("%s: error downloading vars: %d\n", + __FUNCTION__, bcmerror)); + } + } + else { + DHD_ERROR(("%s: error reading nvram file: %d\n", + __FUNCTION__, len)); + bcmerror = BCME_SDIO_ERROR; + } + +err: + if (memblock) + MFREE(bus->dhd->osh, memblock, MEMBLOCK); + + if (image) + dhd_os_close_image(image); + + return bcmerror; +} + +static int +_dhdsdio_download_firmware(struct dhd_bus *bus) +{ + int bcmerror = -1; + + bool embed = FALSE; /* download embedded firmware */ + bool dlok = FALSE; /* download firmware succeeded */ + + /* Out immediately if no image to download */ + if ((bus->fw_path == NULL) || (bus->fw_path[0] == '\0')) { +#ifdef BCMEMBEDIMAGE + embed = TRUE; +#else + return bcmerror; +#endif + } + + /* Keep arm in reset */ + if (dhdsdio_download_state(bus, TRUE)) { + DHD_ERROR(("%s: error placing ARM core in reset\n", __FUNCTION__)); + goto err; + } + + /* External image takes precedence if specified */ + if ((bus->fw_path != NULL) && (bus->fw_path[0] != '\0')) { + if (dhdsdio_download_code_file(bus, bus->fw_path)) { + DHD_ERROR(("%s: dongle image file download failed\n", __FUNCTION__)); +#ifdef BCMEMBEDIMAGE + embed = TRUE; +#else + goto err; +#endif + } + else { + embed = FALSE; + dlok = TRUE; + } + } +#ifdef BCMEMBEDIMAGE + if (embed) { + if (dhdsdio_download_code_array(bus)) { + DHD_ERROR(("%s: dongle image array download failed\n", __FUNCTION__)); + goto err; + } + else { + dlok = TRUE; + } + } +#endif + if (!dlok) { + DHD_ERROR(("%s: dongle image download failed\n", __FUNCTION__)); + goto err; + } + + /* EXAMPLE: nvram_array */ + /* If a valid nvram_arry is specified as above, it can be passed down to dongle */ + /* dhd_bus_set_nvram_params(bus, (char *)&nvram_array); */ + + /* External nvram takes precedence if specified */ + if (dhdsdio_download_nvram(bus)) { + DHD_ERROR(("%s: dongle nvram file download failed\n", __FUNCTION__)); + } + + /* Take arm out of reset */ + if (dhdsdio_download_state(bus, FALSE)) { + DHD_ERROR(("%s: error getting out of ARM core reset\n", __FUNCTION__)); + goto err; + } + + bcmerror = 0; + +err: + return bcmerror; +} + +static int +dhd_bcmsdh_recv_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags, uint8 *buf, uint nbytes, + void *pkt, bcmsdh_cmplt_fn_t complete, void *handle) +{ + int status; + + /* 4329: GSPI check */ + status = bcmsdh_recv_buf(bus->sdh, addr, fn, flags, buf, nbytes, pkt, complete, handle); + return status; +} + +static int +dhd_bcmsdh_send_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags, uint8 *buf, uint nbytes, + void *pkt, bcmsdh_cmplt_fn_t complete, void *handle) +{ + return (bcmsdh_send_buf(bus->sdh, addr, fn, flags, buf, nbytes, pkt, complete, handle)); +} + +uint +dhd_bus_chip(struct dhd_bus *bus) +{ + ASSERT(bus->sih != NULL); + return bus->sih->chip; +} + +void * +dhd_bus_pub(struct dhd_bus *bus) +{ + return bus->dhd; +} + +void * +dhd_bus_txq(struct dhd_bus *bus) +{ + return &bus->txq; +} + +uint +dhd_bus_hdrlen(struct dhd_bus *bus) +{ + return SDPCM_HDRLEN; +} + +int +dhd_bus_devreset(dhd_pub_t *dhdp, uint8 flag) +{ + int bcmerror = 0; + dhd_bus_t *bus; + + bus = dhdp->bus; + + if (flag == TRUE) { + if (!bus->dhd->dongle_reset) { + dhd_os_sdlock(dhdp); + /* Turning off watchdog */ + dhd_os_wd_timer(dhdp, 0); +#if !defined(IGNORE_ETH0_DOWN) + /* Force flow control as protection when stop come before ifconfig_down */ + dhd_txflowcontrol(bus->dhd, 0, ON); +#endif /* !defined(IGNORE_ETH0_DOWN) */ + /* Expect app to have torn down any connection before calling */ + /* Stop the bus, disable F2 */ + dhd_bus_stop(bus, FALSE); +#if defined(OOB_INTR_ONLY) + bcmsdh_set_irq(FALSE); +#endif /* defined(OOB_INTR_ONLY) */ + /* Clean tx/rx buffer pointers, detach from the dongle */ + dhdsdio_release_dongle(bus, bus->dhd->osh, TRUE); + + bus->dhd->dongle_reset = TRUE; + bus->dhd->up = FALSE; + dhd_os_sdunlock(dhdp); + + DHD_TRACE(("%s: WLAN OFF DONE\n", __FUNCTION__)); + /* App can now remove power from device */ + } else + bcmerror = BCME_SDIO_ERROR; + } else { + /* App must have restored power to device before calling */ + + DHD_TRACE(("\n\n%s: == WLAN ON ==\n", __FUNCTION__)); + + if (bus->dhd->dongle_reset) { + /* Turn on WLAN */ + dhd_os_sdlock(dhdp); + + /* Reset SD client */ + bcmsdh_reset(bus->sdh); + + /* Attempt to re-attach & download */ + if (dhdsdio_probe_attach(bus, bus->dhd->osh, bus->sdh, + (uint32 *)SI_ENUM_BASE, + bus->cl_devid)) { + /* Attempt to download binary to the dongle */ + if (dhdsdio_probe_init(bus, bus->dhd->osh, bus->sdh) && + dhdsdio_download_firmware(bus, bus->dhd->osh, bus->sdh)) { + + /* Re-init bus, enable F2 transfer */ + bcmerror = dhd_bus_init((dhd_pub_t *) bus->dhd, FALSE); + if (bcmerror == BCME_OK) { +#if defined(OOB_INTR_ONLY) + bcmsdh_set_irq(TRUE); + dhd_enable_oob_intr(bus, TRUE); +#endif /* defined(OOB_INTR_ONLY) */ + bus->dhd->dongle_reset = FALSE; + bus->dhd->up = TRUE; +#if !defined(IGNORE_ETH0_DOWN) + /* Restore flow control */ + dhd_txflowcontrol(bus->dhd, 0, OFF); +#endif + /* Turning on watchdog back */ + dhd_os_wd_timer(dhdp, dhd_watchdog_ms); + + DHD_TRACE(("%s: WLAN ON DONE\n", __FUNCTION__)); + } else { + dhd_bus_stop(bus, FALSE); + dhdsdio_release_dongle(bus, bus->dhd->osh, FALSE); + } + } else + bcmerror = BCME_SDIO_ERROR; + } else + bcmerror = BCME_SDIO_ERROR; + + dhd_os_sdunlock(dhdp); + } else { + bcmerror = BCME_NOTDOWN; + DHD_ERROR(("%s: Set DEVRESET=FALSE invoked when device is on\n", + __FUNCTION__)); + bcmerror = BCME_SDIO_ERROR; + } + } + return bcmerror; +} diff --git a/drivers/net/wireless/bcm4329/dngl_stats.h b/drivers/net/wireless/bcm4329/dngl_stats.h new file mode 100644 index 0000000000000000000000000000000000000000..e5db54e7edfe02e7eeacad81cb7f66533ddb0cdd --- /dev/null +++ b/drivers/net/wireless/bcm4329/dngl_stats.h @@ -0,0 +1,43 @@ +/* + * Common stats definitions for clients of dongle + * ports + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dngl_stats.h,v 1.2.140.3 2008/05/26 16:52:08 Exp $ + */ + +#ifndef _dngl_stats_h_ +#define _dngl_stats_h_ + +typedef struct { + unsigned long rx_packets; /* total packets received */ + unsigned long tx_packets; /* total packets transmitted */ + unsigned long rx_bytes; /* total bytes received */ + unsigned long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors; /* bad packets received */ + unsigned long tx_errors; /* packet transmit problems */ + unsigned long rx_dropped; /* packets dropped by dongle */ + unsigned long tx_dropped; /* packets dropped by dongle */ + unsigned long multicast; /* multicast packets received */ +} dngl_stats_t; + +#endif /* _dngl_stats_h_ */ diff --git a/drivers/net/wireless/bcm4329/hndpmu.c b/drivers/net/wireless/bcm4329/hndpmu.c new file mode 100644 index 0000000000000000000000000000000000000000..307347a43bde20c0296a157eae9d35b1a059318b --- /dev/null +++ b/drivers/net/wireless/bcm4329/hndpmu.c @@ -0,0 +1,131 @@ +/* + * Misc utility routines for accessing PMU corerev specific features + * of the SiliconBackplane-based Broadcom chips. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: hndpmu.c,v 1.95.2.17.4.11.2.63 2010/07/21 13:55:09 Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* debug/trace */ +#define PMU_ERROR(args) + +#define PMU_MSG(args) + + +/* SDIO Pad drive strength to select value mappings */ +typedef struct { + uint8 strength; /* Pad Drive Strength in mA */ + uint8 sel; /* Chip-specific select value */ +} sdiod_drive_str_t; + +/* SDIO Drive Strength to sel value table for PMU Rev 1 */ +static const sdiod_drive_str_t sdiod_drive_strength_tab1[] = { + {4, 0x2}, + {2, 0x3}, + {1, 0x0}, + {0, 0x0} }; + +/* SDIO Drive Strength to sel value table for PMU Rev 2, 3 */ +static const sdiod_drive_str_t sdiod_drive_strength_tab2[] = { + {12, 0x7}, + {10, 0x6}, + {8, 0x5}, + {6, 0x4}, + {4, 0x2}, + {2, 0x1}, + {0, 0x0} }; + +#define SDIOD_DRVSTR_KEY(chip, pmu) (((chip) << 16) | (pmu)) + +void +si_sdiod_drive_strength_init(si_t *sih, osl_t *osh, uint32 drivestrength) +{ + chipcregs_t *cc; + uint origidx, intr_val = 0; + sdiod_drive_str_t *str_tab = NULL; + uint32 str_mask = 0; + uint32 str_shift = 0; + + if (!(sih->cccaps & CC_CAP_PMU)) { + return; + } + + /* Remember original core before switch to chipc */ + cc = (chipcregs_t *) si_switch_core(sih, CC_CORE_ID, &origidx, &intr_val); + + switch (SDIOD_DRVSTR_KEY(sih->chip, sih->pmurev)) { + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 1): + str_tab = (sdiod_drive_str_t *)&sdiod_drive_strength_tab1; + str_mask = 0x30000000; + str_shift = 28; + break; + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 2): + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 3): + case SDIOD_DRVSTR_KEY(BCM4315_CHIP_ID, 4): + str_tab = (sdiod_drive_str_t *)&sdiod_drive_strength_tab2; + str_mask = 0x00003800; + str_shift = 11; + break; + + default: + PMU_MSG(("No SDIO Drive strength init done for chip %x rev %d pmurev %d\n", + sih->chip, sih->chiprev, sih->pmurev)); + + break; + } + + if (str_tab != NULL) { + uint32 drivestrength_sel = 0; + uint32 cc_data_temp; + int i; + + for (i = 0; str_tab[i].strength != 0; i ++) { + if (drivestrength >= str_tab[i].strength) { + drivestrength_sel = str_tab[i].sel; + break; + } + } + + W_REG(osh, &cc->chipcontrol_addr, 1); + cc_data_temp = R_REG(osh, &cc->chipcontrol_data); + cc_data_temp &= ~str_mask; + drivestrength_sel <<= str_shift; + cc_data_temp |= drivestrength_sel; + W_REG(osh, &cc->chipcontrol_data, cc_data_temp); + + PMU_MSG(("SDIO: %dmA drive strength selected, set to 0x%08x\n", + drivestrength, cc_data_temp)); + } + + /* Return to original core */ + si_restore_core(sih, origidx, intr_val); +} diff --git a/drivers/net/wireless/bcm4329/include/Makefile b/drivers/net/wireless/bcm4329/include/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..439ead14a0e66b3d374685183bc40334c2243c2c --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/Makefile @@ -0,0 +1,21 @@ +# +# include/Makefile +# +# Copyright 2005, Broadcom, Inc. +# +# $Id: Makefile,v 13.5 2005/02/17 19:11:31 Exp $ +# + +SRCBASE = .. + +TARGETS = epivers.h + + +all release: + bash epivers.sh + +clean: + rm -rf ${TARGETS} *.prev + + +.PHONY: all release clean diff --git a/drivers/net/wireless/bcm4329/include/aidmp.h b/drivers/net/wireless/bcm4329/include/aidmp.h new file mode 100644 index 0000000000000000000000000000000000000000..a927e5dae58661f864026eb33a4e50ddf947cf21 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/aidmp.h @@ -0,0 +1,368 @@ +/* + * Broadcom AMBA Interconnect definitions. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: aidmp.h,v 13.2.10.1 2008/05/07 20:32:12 Exp $ + */ + + +#ifndef _AIDMP_H +#define _AIDMP_H + + +#define MFGID_ARM 0x43b +#define MFGID_BRCM 0x4bf +#define MFGID_MIPS 0x4a7 + + +#define CC_SIM 0 +#define CC_EROM 1 +#define CC_CORESIGHT 9 +#define CC_VERIF 0xb +#define CC_OPTIMO 0xd +#define CC_GEN 0xe +#define CC_PRIMECELL 0xf + + +#define ER_EROMENTRY 0x000 +#define ER_REMAPCONTROL 0xe00 +#define ER_REMAPSELECT 0xe04 +#define ER_MASTERSELECT 0xe10 +#define ER_ITCR 0xf00 +#define ER_ITIP 0xf04 + + +#define ER_TAG 0xe +#define ER_TAG1 0x6 +#define ER_VALID 1 +#define ER_CI 0 +#define ER_MP 2 +#define ER_ADD 4 +#define ER_END 0xe +#define ER_BAD 0xffffffff + + +#define CIA_MFG_MASK 0xfff00000 +#define CIA_MFG_SHIFT 20 +#define CIA_CID_MASK 0x000fff00 +#define CIA_CID_SHIFT 8 +#define CIA_CCL_MASK 0x000000f0 +#define CIA_CCL_SHIFT 4 + + +#define CIB_REV_MASK 0xff000000 +#define CIB_REV_SHIFT 24 +#define CIB_NSW_MASK 0x00f80000 +#define CIB_NSW_SHIFT 19 +#define CIB_NMW_MASK 0x0007c000 +#define CIB_NMW_SHIFT 14 +#define CIB_NSP_MASK 0x00003e00 +#define CIB_NSP_SHIFT 9 +#define CIB_NMP_MASK 0x000001f0 +#define CIB_NMP_SHIFT 4 + + +#define MPD_MUI_MASK 0x0000ff00 +#define MPD_MUI_SHIFT 8 +#define MPD_MP_MASK 0x000000f0 +#define MPD_MP_SHIFT 4 + + +#define AD_ADDR_MASK 0xfffff000 +#define AD_SP_MASK 0x00000f00 +#define AD_SP_SHIFT 8 +#define AD_ST_MASK 0x000000c0 +#define AD_ST_SHIFT 6 +#define AD_ST_SLAVE 0x00000000 +#define AD_ST_BRIDGE 0x00000040 +#define AD_ST_SWRAP 0x00000080 +#define AD_ST_MWRAP 0x000000c0 +#define AD_SZ_MASK 0x00000030 +#define AD_SZ_SHIFT 4 +#define AD_SZ_4K 0x00000000 +#define AD_SZ_8K 0x00000010 +#define AD_SZ_16K 0x00000020 +#define AD_SZ_SZD 0x00000030 +#define AD_AG32 0x00000008 +#define AD_ADDR_ALIGN 0x00000fff +#define AD_SZ_BASE 0x00001000 + + +#define SD_SZ_MASK 0xfffff000 +#define SD_SG32 0x00000008 +#define SD_SZ_ALIGN 0x00000fff + + +#ifndef _LANGUAGE_ASSEMBLY + +typedef volatile struct _aidmp { + uint32 oobselina30; + uint32 oobselina74; + uint32 PAD[6]; + uint32 oobselinb30; + uint32 oobselinb74; + uint32 PAD[6]; + uint32 oobselinc30; + uint32 oobselinc74; + uint32 PAD[6]; + uint32 oobselind30; + uint32 oobselind74; + uint32 PAD[38]; + uint32 oobselouta30; + uint32 oobselouta74; + uint32 PAD[6]; + uint32 oobseloutb30; + uint32 oobseloutb74; + uint32 PAD[6]; + uint32 oobseloutc30; + uint32 oobseloutc74; + uint32 PAD[6]; + uint32 oobseloutd30; + uint32 oobseloutd74; + uint32 PAD[38]; + uint32 oobsynca; + uint32 oobseloutaen; + uint32 PAD[6]; + uint32 oobsyncb; + uint32 oobseloutben; + uint32 PAD[6]; + uint32 oobsyncc; + uint32 oobseloutcen; + uint32 PAD[6]; + uint32 oobsyncd; + uint32 oobseloutden; + uint32 PAD[38]; + uint32 oobaextwidth; + uint32 oobainwidth; + uint32 oobaoutwidth; + uint32 PAD[5]; + uint32 oobbextwidth; + uint32 oobbinwidth; + uint32 oobboutwidth; + uint32 PAD[5]; + uint32 oobcextwidth; + uint32 oobcinwidth; + uint32 oobcoutwidth; + uint32 PAD[5]; + uint32 oobdextwidth; + uint32 oobdinwidth; + uint32 oobdoutwidth; + uint32 PAD[37]; + uint32 ioctrlset; + uint32 ioctrlclear; + uint32 ioctrl; + uint32 PAD[61]; + uint32 iostatus; + uint32 PAD[127]; + uint32 ioctrlwidth; + uint32 iostatuswidth; + uint32 PAD[62]; + uint32 resetctrl; + uint32 resetstatus; + uint32 resetreadid; + uint32 resetwriteid; + uint32 PAD[60]; + uint32 errlogctrl; + uint32 errlogdone; + uint32 errlogstatus; + uint32 errlogaddrlo; + uint32 errlogaddrhi; + uint32 errlogid; + uint32 errloguser; + uint32 errlogflags; + uint32 PAD[56]; + uint32 intstatus; + uint32 PAD[127]; + uint32 config; + uint32 PAD[63]; + uint32 itcr; + uint32 PAD[3]; + uint32 itipooba; + uint32 itipoobb; + uint32 itipoobc; + uint32 itipoobd; + uint32 PAD[4]; + uint32 itipoobaout; + uint32 itipoobbout; + uint32 itipoobcout; + uint32 itipoobdout; + uint32 PAD[4]; + uint32 itopooba; + uint32 itopoobb; + uint32 itopoobc; + uint32 itopoobd; + uint32 PAD[4]; + uint32 itopoobain; + uint32 itopoobbin; + uint32 itopoobcin; + uint32 itopoobdin; + uint32 PAD[4]; + uint32 itopreset; + uint32 PAD[15]; + uint32 peripherialid4; + uint32 peripherialid5; + uint32 peripherialid6; + uint32 peripherialid7; + uint32 peripherialid0; + uint32 peripherialid1; + uint32 peripherialid2; + uint32 peripherialid3; + uint32 componentid0; + uint32 componentid1; + uint32 componentid2; + uint32 componentid3; +} aidmp_t; + +#endif + + +#define OOB_BUSCONFIG 0x020 +#define OOB_STATUSA 0x100 +#define OOB_STATUSB 0x104 +#define OOB_STATUSC 0x108 +#define OOB_STATUSD 0x10c +#define OOB_ENABLEA0 0x200 +#define OOB_ENABLEA1 0x204 +#define OOB_ENABLEA2 0x208 +#define OOB_ENABLEA3 0x20c +#define OOB_ENABLEB0 0x280 +#define OOB_ENABLEB1 0x284 +#define OOB_ENABLEB2 0x288 +#define OOB_ENABLEB3 0x28c +#define OOB_ENABLEC0 0x300 +#define OOB_ENABLEC1 0x304 +#define OOB_ENABLEC2 0x308 +#define OOB_ENABLEC3 0x30c +#define OOB_ENABLED0 0x380 +#define OOB_ENABLED1 0x384 +#define OOB_ENABLED2 0x388 +#define OOB_ENABLED3 0x38c +#define OOB_ITCR 0xf00 +#define OOB_ITIPOOBA 0xf10 +#define OOB_ITIPOOBB 0xf14 +#define OOB_ITIPOOBC 0xf18 +#define OOB_ITIPOOBD 0xf1c +#define OOB_ITOPOOBA 0xf30 +#define OOB_ITOPOOBB 0xf34 +#define OOB_ITOPOOBC 0xf38 +#define OOB_ITOPOOBD 0xf3c + + +#define AI_OOBSELINA30 0x000 +#define AI_OOBSELINA74 0x004 +#define AI_OOBSELINB30 0x020 +#define AI_OOBSELINB74 0x024 +#define AI_OOBSELINC30 0x040 +#define AI_OOBSELINC74 0x044 +#define AI_OOBSELIND30 0x060 +#define AI_OOBSELIND74 0x064 +#define AI_OOBSELOUTA30 0x100 +#define AI_OOBSELOUTA74 0x104 +#define AI_OOBSELOUTB30 0x120 +#define AI_OOBSELOUTB74 0x124 +#define AI_OOBSELOUTC30 0x140 +#define AI_OOBSELOUTC74 0x144 +#define AI_OOBSELOUTD30 0x160 +#define AI_OOBSELOUTD74 0x164 +#define AI_OOBSYNCA 0x200 +#define AI_OOBSELOUTAEN 0x204 +#define AI_OOBSYNCB 0x220 +#define AI_OOBSELOUTBEN 0x224 +#define AI_OOBSYNCC 0x240 +#define AI_OOBSELOUTCEN 0x244 +#define AI_OOBSYNCD 0x260 +#define AI_OOBSELOUTDEN 0x264 +#define AI_OOBAEXTWIDTH 0x300 +#define AI_OOBAINWIDTH 0x304 +#define AI_OOBAOUTWIDTH 0x308 +#define AI_OOBBEXTWIDTH 0x320 +#define AI_OOBBINWIDTH 0x324 +#define AI_OOBBOUTWIDTH 0x328 +#define AI_OOBCEXTWIDTH 0x340 +#define AI_OOBCINWIDTH 0x344 +#define AI_OOBCOUTWIDTH 0x348 +#define AI_OOBDEXTWIDTH 0x360 +#define AI_OOBDINWIDTH 0x364 +#define AI_OOBDOUTWIDTH 0x368 +#define AI_IOCTRLSET 0x400 +#define AI_IOCTRLCLEAR 0x404 +#define AI_IOCTRL 0x408 +#define AI_IOSTATUS 0x500 +#define AI_IOCTRLWIDTH 0x700 +#define AI_IOSTATUSWIDTH 0x704 +#define AI_RESETCTRL 0x800 +#define AI_RESETSTATUS 0x804 +#define AI_RESETREADID 0x808 +#define AI_RESETWRITEID 0x80c +#define AI_ERRLOGCTRL 0xa00 +#define AI_ERRLOGDONE 0xa04 +#define AI_ERRLOGSTATUS 0xa08 +#define AI_ERRLOGADDRLO 0xa0c +#define AI_ERRLOGADDRHI 0xa10 +#define AI_ERRLOGID 0xa14 +#define AI_ERRLOGUSER 0xa18 +#define AI_ERRLOGFLAGS 0xa1c +#define AI_INTSTATUS 0xa00 +#define AI_CONFIG 0xe00 +#define AI_ITCR 0xf00 +#define AI_ITIPOOBA 0xf10 +#define AI_ITIPOOBB 0xf14 +#define AI_ITIPOOBC 0xf18 +#define AI_ITIPOOBD 0xf1c +#define AI_ITIPOOBAOUT 0xf30 +#define AI_ITIPOOBBOUT 0xf34 +#define AI_ITIPOOBCOUT 0xf38 +#define AI_ITIPOOBDOUT 0xf3c +#define AI_ITOPOOBA 0xf50 +#define AI_ITOPOOBB 0xf54 +#define AI_ITOPOOBC 0xf58 +#define AI_ITOPOOBD 0xf5c +#define AI_ITOPOOBAIN 0xf70 +#define AI_ITOPOOBBIN 0xf74 +#define AI_ITOPOOBCIN 0xf78 +#define AI_ITOPOOBDIN 0xf7c +#define AI_ITOPRESET 0xf90 +#define AI_PERIPHERIALID4 0xfd0 +#define AI_PERIPHERIALID5 0xfd4 +#define AI_PERIPHERIALID6 0xfd8 +#define AI_PERIPHERIALID7 0xfdc +#define AI_PERIPHERIALID0 0xfe0 +#define AI_PERIPHERIALID1 0xfe4 +#define AI_PERIPHERIALID2 0xfe8 +#define AI_PERIPHERIALID3 0xfec +#define AI_COMPONENTID0 0xff0 +#define AI_COMPONENTID1 0xff4 +#define AI_COMPONENTID2 0xff8 +#define AI_COMPONENTID3 0xffc + + +#define AIRC_RESET 1 + + +#define AICFG_OOB 0x00000020 +#define AICFG_IOS 0x00000010 +#define AICFG_IOC 0x00000008 +#define AICFG_TO 0x00000004 +#define AICFG_ERRL 0x00000002 +#define AICFG_RST 0x00000001 + +#endif diff --git a/drivers/net/wireless/bcm4329/include/bcmcdc.h b/drivers/net/wireless/bcm4329/include/bcmcdc.h new file mode 100644 index 0000000000000000000000000000000000000000..c2a860beab246041312ead7072d7d7f57231e2b3 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmcdc.h @@ -0,0 +1,100 @@ +/* + * CDC network driver ioctl/indication encoding + * Broadcom 802.11abg Networking Device Driver + * + * Definitions subject to change without notice. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmcdc.h,v 13.14.16.3.16.4 2009/04/12 16:58:45 Exp $ + */ +#include + +typedef struct cdc_ioctl { + uint32 cmd; /* ioctl command value */ + uint32 len; /* lower 16: output buflen; upper 16: input buflen (excludes header) */ + uint32 flags; /* flag defns given below */ + uint32 status; /* status code returned from the device */ +} cdc_ioctl_t; + +/* Max valid buffer size that can be sent to the dongle */ +#define CDC_MAX_MSG_SIZE ETHER_MAX_LEN + +/* len field is divided into input and output buffer lengths */ +#define CDCL_IOC_OUTLEN_MASK 0x0000FFFF /* maximum or expected response length, */ + /* excluding IOCTL header */ +#define CDCL_IOC_OUTLEN_SHIFT 0 +#define CDCL_IOC_INLEN_MASK 0xFFFF0000 /* input buffer length, excluding IOCTL header */ +#define CDCL_IOC_INLEN_SHIFT 16 + +/* CDC flag definitions */ +#define CDCF_IOC_ERROR 0x01 /* 0=success, 1=ioctl cmd failed */ +#define CDCF_IOC_SET 0x02 /* 0=get, 1=set cmd */ +#define CDCF_IOC_IF_MASK 0xF000 /* I/F index */ +#define CDCF_IOC_IF_SHIFT 12 +#define CDCF_IOC_ID_MASK 0xFFFF0000 /* used to uniquely id an ioctl req/resp pairing */ +#define CDCF_IOC_ID_SHIFT 16 /* # of bits of shift for ID Mask */ + +#define CDC_IOC_IF_IDX(flags) (((flags) & CDCF_IOC_IF_MASK) >> CDCF_IOC_IF_SHIFT) +#define CDC_IOC_ID(flags) (((flags) & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT) + +#define CDC_GET_IF_IDX(hdr) \ + ((int)((((hdr)->flags) & CDCF_IOC_IF_MASK) >> CDCF_IOC_IF_SHIFT)) +#define CDC_SET_IF_IDX(hdr, idx) \ + ((hdr)->flags = (((hdr)->flags & ~CDCF_IOC_IF_MASK) | ((idx) << CDCF_IOC_IF_SHIFT))) + +/* + * BDC header + * + * The BDC header is used on data packets to convey priority across USB. + */ + +#define BDC_HEADER_LEN 4 + +#define BDC_PROTO_VER 1 /* Protocol version */ + +#define BDC_FLAG_VER_MASK 0xf0 /* Protocol version mask */ +#define BDC_FLAG_VER_SHIFT 4 /* Protocol version shift */ + +#define BDC_FLAG__UNUSED 0x03 /* Unassigned */ +#define BDC_FLAG_SUM_GOOD 0x04 /* Dongle has verified good RX checksums */ +#define BDC_FLAG_SUM_NEEDED 0x08 /* Dongle needs to do TX checksums */ + +#define BDC_PRIORITY_MASK 0x7 + +#define BDC_FLAG2_FC_FLAG 0x10 /* flag to indicate if pkt contains */ + /* FLOW CONTROL info only */ +#define BDC_PRIORITY_FC_SHIFT 4 /* flow control info shift */ + +#define BDC_FLAG2_IF_MASK 0x0f /* APSTA: interface on which the packet was received */ +#define BDC_FLAG2_IF_SHIFT 0 + +#define BDC_GET_IF_IDX(hdr) \ + ((int)((((hdr)->flags2) & BDC_FLAG2_IF_MASK) >> BDC_FLAG2_IF_SHIFT)) +#define BDC_SET_IF_IDX(hdr, idx) \ + ((hdr)->flags2 = (((hdr)->flags2 & ~BDC_FLAG2_IF_MASK) | ((idx) << BDC_FLAG2_IF_SHIFT))) + +struct bdc_header { + uint8 flags; /* Flags */ + uint8 priority; /* 802.1d Priority 0:2 bits, 4:7 flow control info for usb */ + uint8 flags2; + uint8 rssi; +}; diff --git a/drivers/net/wireless/bcm4329/include/bcmdefs.h b/drivers/net/wireless/bcm4329/include/bcmdefs.h new file mode 100644 index 0000000000000000000000000000000000000000..f4e99461971b012cd9a56e1b09a0f8fc81532d10 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmdefs.h @@ -0,0 +1,114 @@ +/* + * Misc system wide definitions + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: bcmdefs.h,v 13.38.4.10.2.7.6.11 2010/02/01 05:51:55 Exp $ + */ + + +#ifndef _bcmdefs_h_ +#define _bcmdefs_h_ + +#define STATIC static + +#define SI_BUS 0 +#define PCI_BUS 1 +#define PCMCIA_BUS 2 +#define SDIO_BUS 3 +#define JTAG_BUS 4 +#define USB_BUS 5 +#define SPI_BUS 6 + + +#ifdef BCMBUSTYPE +#define BUSTYPE(bus) (BCMBUSTYPE) +#else +#define BUSTYPE(bus) (bus) +#endif + + +#ifdef BCMCHIPTYPE +#define CHIPTYPE(bus) (BCMCHIPTYPE) +#else +#define CHIPTYPE(bus) (bus) +#endif + + + +#if defined(BCMSPROMBUS) +#define SPROMBUS (BCMSPROMBUS) +#elif defined(SI_PCMCIA_SROM) +#define SPROMBUS (PCMCIA_BUS) +#else +#define SPROMBUS (PCI_BUS) +#endif + + +#ifdef BCMCHIPID +#define CHIPID(chip) (BCMCHIPID) +#else +#define CHIPID(chip) (chip) +#endif + + +#define DMADDR_MASK_32 0x0 +#define DMADDR_MASK_30 0xc0000000 +#define DMADDR_MASK_0 0xffffffff + +#define DMADDRWIDTH_30 30 +#define DMADDRWIDTH_32 32 +#define DMADDRWIDTH_63 63 +#define DMADDRWIDTH_64 64 + + +#define BCMEXTRAHDROOM 164 + + +#define BCMDONGLEHDRSZ 12 +#define BCMDONGLEPADSZ 16 + +#define BCMDONGLEOVERHEAD (BCMDONGLEHDRSZ + BCMDONGLEPADSZ) + + + +#define BITFIELD_MASK(width) \ + (((unsigned)1 << (width)) - 1) +#define GFIELD(val, field) \ + (((val) >> field ## _S) & field ## _M) +#define SFIELD(val, field, bits) \ + (((val) & (~(field ## _M << field ## _S))) | \ + ((unsigned)(bits) << field ## _S)) + + +#ifdef BCMSMALL +#undef BCMSPACE +#define bcmspace FALSE +#else +#define BCMSPACE +#define bcmspace TRUE +#endif + + +#define MAXSZ_NVRAM_VARS 4096 + +#define LOCATOR_EXTERN static + +#endif diff --git a/drivers/net/wireless/bcm4329/include/bcmdevs.h b/drivers/net/wireless/bcm4329/include/bcmdevs.h new file mode 100644 index 0000000000000000000000000000000000000000..14853f17795c7e5374438c666e4d35a6e24cde72 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmdevs.h @@ -0,0 +1,124 @@ +/* + * Broadcom device-specific manifest constants. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmdevs.h,v 13.172.4.5.4.10.2.36 2010/05/25 08:33:44 Exp $ + */ + + +#ifndef _BCMDEVS_H +#define _BCMDEVS_H + + +#define VENDOR_EPIGRAM 0xfeda +#define VENDOR_BROADCOM 0x14e4 +#define VENDOR_SI_IMAGE 0x1095 +#define VENDOR_TI 0x104c +#define VENDOR_RICOH 0x1180 +#define VENDOR_JMICRON 0x197b + + +#define VENDOR_BROADCOM_PCMCIA 0x02d0 + + +#define VENDOR_BROADCOM_SDIO 0x00BF + + +#define BCM_DNGL_VID 0xa5c +#define BCM_DNGL_BL_PID_4320 0xbd11 +#define BCM_DNGL_BL_PID_4328 0xbd12 +#define BCM_DNGL_BL_PID_4322 0xbd13 +#define BCM_DNGL_BL_PID_4325 0xbd14 +#define BCM_DNGL_BL_PID_4315 0xbd15 +#define BCM_DNGL_BL_PID_4319 0xbd16 +#define BCM_DNGL_BDC_PID 0xbdc + +#define BCM4325_D11DUAL_ID 0x431b +#define BCM4325_D11G_ID 0x431c +#define BCM4325_D11A_ID 0x431d +#define BCM4329_D11NDUAL_ID 0x432e +#define BCM4329_D11N2G_ID 0x432f +#define BCM4329_D11N5G_ID 0x4330 +#define BCM4336_D11N_ID 0x4343 +#define BCM4315_D11DUAL_ID 0x4334 +#define BCM4315_D11G_ID 0x4335 +#define BCM4315_D11A_ID 0x4336 +#define BCM4319_D11N_ID 0x4337 +#define BCM4319_D11N2G_ID 0x4338 +#define BCM4319_D11N5G_ID 0x4339 + + +#define SDIOH_FPGA_ID 0x43f2 +#define SPIH_FPGA_ID 0x43f5 +#define BCM4710_DEVICE_ID 0x4710 +#define BCM27XX_SDIOH_ID 0x2702 +#define PCIXX21_FLASHMEDIA0_ID 0x8033 +#define PCIXX21_SDIOH0_ID 0x8034 +#define PCIXX21_FLASHMEDIA_ID 0x803b +#define PCIXX21_SDIOH_ID 0x803c +#define R5C822_SDIOH_ID 0x0822 +#define JMICRON_SDIOH_ID 0x2381 + + +#define BCM4306_CHIP_ID 0x4306 +#define BCM4311_CHIP_ID 0x4311 +#define BCM4312_CHIP_ID 0x4312 +#define BCM4315_CHIP_ID 0x4315 +#define BCM4318_CHIP_ID 0x4318 +#define BCM4319_CHIP_ID 0x4319 +#define BCM4320_CHIP_ID 0x4320 +#define BCM4321_CHIP_ID 0x4321 +#define BCM4322_CHIP_ID 0x4322 +#define BCM4325_CHIP_ID 0x4325 +#define BCM4328_CHIP_ID 0x4328 +#define BCM4329_CHIP_ID 0x4329 +#define BCM4336_CHIP_ID 0x4336 +#define BCM4402_CHIP_ID 0x4402 +#define BCM4704_CHIP_ID 0x4704 +#define BCM4710_CHIP_ID 0x4710 +#define BCM4712_CHIP_ID 0x4712 +#define BCM4785_CHIP_ID 0x4785 +#define BCM5350_CHIP_ID 0x5350 +#define BCM5352_CHIP_ID 0x5352 +#define BCM5354_CHIP_ID 0x5354 +#define BCM5365_CHIP_ID 0x5365 + + + +#define BCM4303_PKG_ID 2 +#define BCM4309_PKG_ID 1 +#define BCM4712LARGE_PKG_ID 0 +#define BCM4712SMALL_PKG_ID 1 +#define BCM4712MID_PKG_ID 2 +#define BCM4328USBD11G_PKG_ID 2 +#define BCM4328USBDUAL_PKG_ID 3 +#define BCM4328SDIOD11G_PKG_ID 4 +#define BCM4328SDIODUAL_PKG_ID 5 +#define BCM4329_289PIN_PKG_ID 0 +#define BCM4329_182PIN_PKG_ID 1 +#define BCM5354E_PKG_ID 1 +#define HDLSIM5350_PKG_ID 1 +#define HDLSIM_PKG_ID 14 +#define HWSIM_PKG_ID 15 + + +#endif diff --git a/drivers/net/wireless/bcm4329/include/bcmendian.h b/drivers/net/wireless/bcm4329/include/bcmendian.h new file mode 100644 index 0000000000000000000000000000000000000000..ae468383aa74084d28092f02a57f8b8a50f2f87d --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmendian.h @@ -0,0 +1,205 @@ +/* + * Byte order utilities + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmendian.h,v 1.31.302.1.16.1 2009/02/03 18:34:31 Exp $ + * + * This file by default provides proper behavior on little-endian architectures. + * On big-endian architectures, IL_BIGENDIAN should be defined. + */ + + +#ifndef _BCMENDIAN_H_ +#define _BCMENDIAN_H_ + +#include + + +#define BCMSWAP16(val) \ + ((uint16)((((uint16)(val) & (uint16)0x00ffU) << 8) | \ + (((uint16)(val) & (uint16)0xff00U) >> 8))) + + +#define BCMSWAP32(val) \ + ((uint32)((((uint32)(val) & (uint32)0x000000ffU) << 24) | \ + (((uint32)(val) & (uint32)0x0000ff00U) << 8) | \ + (((uint32)(val) & (uint32)0x00ff0000U) >> 8) | \ + (((uint32)(val) & (uint32)0xff000000U) >> 24))) + + +#define BCMSWAP32BY16(val) \ + ((uint32)((((uint32)(val) & (uint32)0x0000ffffU) << 16) | \ + (((uint32)(val) & (uint32)0xffff0000U) >> 16))) + + +static INLINE uint16 +bcmswap16(uint16 val) +{ + return BCMSWAP16(val); +} + +static INLINE uint32 +bcmswap32(uint32 val) +{ + return BCMSWAP32(val); +} + +static INLINE uint32 +bcmswap32by16(uint32 val) +{ + return BCMSWAP32BY16(val); +} + + + + +static INLINE void +bcmswap16_buf(uint16 *buf, uint len) +{ + len = len / 2; + + while (len--) { + *buf = bcmswap16(*buf); + buf++; + } +} + +#ifndef hton16 +#ifndef IL_BIGENDIAN +#define HTON16(i) BCMSWAP16(i) +#define HTON32(i) BCMSWAP32(i) +#define hton16(i) bcmswap16(i) +#define hton32(i) bcmswap32(i) +#define ntoh16(i) bcmswap16(i) +#define ntoh32(i) bcmswap32(i) +#define HTOL16(i) (i) +#define HTOL32(i) (i) +#define ltoh16(i) (i) +#define ltoh32(i) (i) +#define htol16(i) (i) +#define htol32(i) (i) +#else +#define HTON16(i) (i) +#define HTON32(i) (i) +#define hton16(i) (i) +#define hton32(i) (i) +#define ntoh16(i) (i) +#define ntoh32(i) (i) +#define HTOL16(i) BCMSWAP16(i) +#define HTOL32(i) BCMSWAP32(i) +#define ltoh16(i) bcmswap16(i) +#define ltoh32(i) bcmswap32(i) +#define htol16(i) bcmswap16(i) +#define htol32(i) bcmswap32(i) +#endif +#endif + +#ifndef IL_BIGENDIAN +#define ltoh16_buf(buf, i) +#define htol16_buf(buf, i) +#else +#define ltoh16_buf(buf, i) bcmswap16_buf((uint16 *)buf, i) +#define htol16_buf(buf, i) bcmswap16_buf((uint16 *)buf, i) +#endif + + +static INLINE void +htol16_ua_store(uint16 val, uint8 *bytes) +{ + bytes[0] = val & 0xff; + bytes[1] = val >> 8; +} + + +static INLINE void +htol32_ua_store(uint32 val, uint8 *bytes) +{ + bytes[0] = val & 0xff; + bytes[1] = (val >> 8) & 0xff; + bytes[2] = (val >> 16) & 0xff; + bytes[3] = val >> 24; +} + + +static INLINE void +hton16_ua_store(uint16 val, uint8 *bytes) +{ + bytes[0] = val >> 8; + bytes[1] = val & 0xff; +} + + +static INLINE void +hton32_ua_store(uint32 val, uint8 *bytes) +{ + bytes[0] = val >> 24; + bytes[1] = (val >> 16) & 0xff; + bytes[2] = (val >> 8) & 0xff; + bytes[3] = val & 0xff; +} + +#define _LTOH16_UA(cp) ((cp)[0] | ((cp)[1] << 8)) +#define _LTOH32_UA(cp) ((cp)[0] | ((cp)[1] << 8) | ((cp)[2] << 16) | ((cp)[3] << 24)) +#define _NTOH16_UA(cp) (((cp)[0] << 8) | (cp)[1]) +#define _NTOH32_UA(cp) (((cp)[0] << 24) | ((cp)[1] << 16) | ((cp)[2] << 8) | (cp)[3]) + + +static INLINE uint16 +ltoh16_ua(const void *bytes) +{ + return _LTOH16_UA((const uint8 *)bytes); +} + + +static INLINE uint32 +ltoh32_ua(const void *bytes) +{ + return _LTOH32_UA((const uint8 *)bytes); +} + + +static INLINE uint16 +ntoh16_ua(const void *bytes) +{ + return _NTOH16_UA((const uint8 *)bytes); +} + + +static INLINE uint32 +ntoh32_ua(const void *bytes) +{ + return _NTOH32_UA((const uint8 *)bytes); +} + +#define ltoh_ua(ptr) \ + (sizeof(*(ptr)) == sizeof(uint8) ? *(const uint8 *)ptr : \ + sizeof(*(ptr)) == sizeof(uint16) ? _LTOH16_UA((const uint8 *)ptr) : \ + sizeof(*(ptr)) == sizeof(uint32) ? _LTOH32_UA((const uint8 *)ptr) : \ + 0xfeedf00d) + +#define ntoh_ua(ptr) \ + (sizeof(*(ptr)) == sizeof(uint8) ? *(const uint8 *)ptr : \ + sizeof(*(ptr)) == sizeof(uint16) ? _NTOH16_UA((const uint8 *)ptr) : \ + sizeof(*(ptr)) == sizeof(uint32) ? _NTOH32_UA((const uint8 *)ptr) : \ + 0xfeedf00d) + +#endif diff --git a/drivers/net/wireless/bcm4329/include/bcmpcispi.h b/drivers/net/wireless/bcm4329/include/bcmpcispi.h new file mode 100644 index 0000000000000000000000000000000000000000..7d98fb7cbdc8533f43f6a0d87834b19125002a22 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmpcispi.h @@ -0,0 +1,205 @@ +/* + * Broadcom PCI-SPI Host Controller Register Definitions + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmpcispi.h,v 13.11.8.3 2008/07/09 21:23:29 Exp $ + */ + +/* cpp contortions to concatenate w/arg prescan */ +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif /* PAD */ + +/* ++---------------------------------------------------------------------------+ +| | +| 7 6 5 4 3 2 1 0 | +| 0x0000 SPI_CTRL SPIE SPE 0 MSTR CPOL CPHA SPR1 SPR0 | +| 0x0004 SPI_STAT SPIF WCOL ST1 ST0 WFFUL WFEMP RFFUL RFEMP | +| 0x0008 SPI_DATA Bits 31:0, data to send out on MOSI | +| 0x000C SPI_EXT ICNT1 ICNT0 BSWAP *HSMODE ESPR1 ESPR0 | +| 0x0020 GPIO_OE 0=input, 1=output PWR_OE CS_OE | +| 0x0024 GPIO_DATA CARD:1=missing, 0=present CARD PWR_DAT CS_DAT | +| 0x0040 INT_EDGE 0=level, 1=edge DEV_E SPI_E | +| 0x0044 INT_POL 1=active high, 0=active low DEV_P SPI_P | +| 0x0048 INTMASK DEV SPI | +| 0x004C INTSTATUS DEV SPI | +| 0x0060 HEXDISP Reset value: 0x14e443f5. In hexdisp mode, value | +| shows on the Raggedstone1 4-digit 7-segment display. | +| 0x0064 CURRENT_MA Low 16 bits indicate card current consumption in mA | +| 0x006C DISP_SEL Display mode (0=hexdisp, 1=current) DSP | +| 0x00C0 PLL_CTL bit31=ext_clk, remainder unused. | +| 0x00C4 PLL_STAT LOCK | +| 0x00C8 CLK_FREQ | +| 0x00CC CLK_CNT | +| | +| *Notes: HSMODE is not implemented, never set this bit! | +| BSWAP is available in rev >= 8 | +| | ++---------------------------------------------------------------------------+ +*/ + +typedef volatile struct { + uint32 spih_ctrl; /* 0x00 SPI Control Register */ + uint32 spih_stat; /* 0x04 SPI Status Register */ + uint32 spih_data; /* 0x08 SPI Data Register, 32-bits wide */ + uint32 spih_ext; /* 0x0C SPI Extension Register */ + uint32 PAD[4]; /* 0x10-0x1F PADDING */ + + uint32 spih_gpio_ctrl; /* 0x20 SPI GPIO Control Register */ + uint32 spih_gpio_data; /* 0x24 SPI GPIO Data Register */ + uint32 PAD[6]; /* 0x28-0x3F PADDING */ + + uint32 spih_int_edge; /* 0x40 SPI Interrupt Edge Register (0=Level, 1=Edge) */ + uint32 spih_int_pol; /* 0x44 SPI Interrupt Polarity Register (0=Active Low, */ + /* 1=Active High) */ + uint32 spih_int_mask; /* 0x48 SPI Interrupt Mask */ + uint32 spih_int_status; /* 0x4C SPI Interrupt Status */ + uint32 PAD[4]; /* 0x50-0x5F PADDING */ + + uint32 spih_hex_disp; /* 0x60 SPI 4-digit hex display value */ + uint32 spih_current_ma; /* 0x64 SPI SD card current consumption in mA */ + uint32 PAD[1]; /* 0x68 PADDING */ + uint32 spih_disp_sel; /* 0x6c SPI 4-digit hex display mode select (1=current) */ + uint32 PAD[4]; /* 0x70-0x7F PADDING */ + uint32 PAD[8]; /* 0x80-0x9F PADDING */ + uint32 PAD[8]; /* 0xA0-0xBF PADDING */ + uint32 spih_pll_ctrl; /* 0xC0 PLL Control Register */ + uint32 spih_pll_status; /* 0xC4 PLL Status Register */ + uint32 spih_xtal_freq; /* 0xC8 External Clock Frequency in units of 10000Hz */ + uint32 spih_clk_count; /* 0xCC External Clock Count Register */ + +} spih_regs_t; + +typedef volatile struct { + uint32 cfg_space[0x40]; /* 0x000-0x0FF PCI Configuration Space (Read Only) */ + uint32 P_IMG_CTRL0; /* 0x100 PCI Image0 Control Register */ + + uint32 P_BA0; /* 0x104 32 R/W PCI Image0 Base Address register */ + uint32 P_AM0; /* 0x108 32 R/W PCI Image0 Address Mask register */ + uint32 P_TA0; /* 0x10C 32 R/W PCI Image0 Translation Address register */ + uint32 P_IMG_CTRL1; /* 0x110 32 R/W PCI Image1 Control register */ + uint32 P_BA1; /* 0x114 32 R/W PCI Image1 Base Address register */ + uint32 P_AM1; /* 0x118 32 R/W PCI Image1 Address Mask register */ + uint32 P_TA1; /* 0x11C 32 R/W PCI Image1 Translation Address register */ + uint32 P_IMG_CTRL2; /* 0x120 32 R/W PCI Image2 Control register */ + uint32 P_BA2; /* 0x124 32 R/W PCI Image2 Base Address register */ + uint32 P_AM2; /* 0x128 32 R/W PCI Image2 Address Mask register */ + uint32 P_TA2; /* 0x12C 32 R/W PCI Image2 Translation Address register */ + uint32 P_IMG_CTRL3; /* 0x130 32 R/W PCI Image3 Control register */ + uint32 P_BA3; /* 0x134 32 R/W PCI Image3 Base Address register */ + uint32 P_AM3; /* 0x138 32 R/W PCI Image3 Address Mask register */ + uint32 P_TA3; /* 0x13C 32 R/W PCI Image3 Translation Address register */ + uint32 P_IMG_CTRL4; /* 0x140 32 R/W PCI Image4 Control register */ + uint32 P_BA4; /* 0x144 32 R/W PCI Image4 Base Address register */ + uint32 P_AM4; /* 0x148 32 R/W PCI Image4 Address Mask register */ + uint32 P_TA4; /* 0x14C 32 R/W PCI Image4 Translation Address register */ + uint32 P_IMG_CTRL5; /* 0x150 32 R/W PCI Image5 Control register */ + uint32 P_BA5; /* 0x154 32 R/W PCI Image5 Base Address register */ + uint32 P_AM5; /* 0x158 32 R/W PCI Image5 Address Mask register */ + uint32 P_TA5; /* 0x15C 32 R/W PCI Image5 Translation Address register */ + uint32 P_ERR_CS; /* 0x160 32 R/W PCI Error Control and Status register */ + uint32 P_ERR_ADDR; /* 0x164 32 R PCI Erroneous Address register */ + uint32 P_ERR_DATA; /* 0x168 32 R PCI Erroneous Data register */ + + uint32 PAD[5]; /* 0x16C-0x17F PADDING */ + + uint32 WB_CONF_SPC_BAR; /* 0x180 32 R WISHBONE Configuration Space Base Address */ + uint32 W_IMG_CTRL1; /* 0x184 32 R/W WISHBONE Image1 Control register */ + uint32 W_BA1; /* 0x188 32 R/W WISHBONE Image1 Base Address register */ + uint32 W_AM1; /* 0x18C 32 R/W WISHBONE Image1 Address Mask register */ + uint32 W_TA1; /* 0x190 32 R/W WISHBONE Image1 Translation Address reg */ + uint32 W_IMG_CTRL2; /* 0x194 32 R/W WISHBONE Image2 Control register */ + uint32 W_BA2; /* 0x198 32 R/W WISHBONE Image2 Base Address register */ + uint32 W_AM2; /* 0x19C 32 R/W WISHBONE Image2 Address Mask register */ + uint32 W_TA2; /* 0x1A0 32 R/W WISHBONE Image2 Translation Address reg */ + uint32 W_IMG_CTRL3; /* 0x1A4 32 R/W WISHBONE Image3 Control register */ + uint32 W_BA3; /* 0x1A8 32 R/W WISHBONE Image3 Base Address register */ + uint32 W_AM3; /* 0x1AC 32 R/W WISHBONE Image3 Address Mask register */ + uint32 W_TA3; /* 0x1B0 32 R/W WISHBONE Image3 Translation Address reg */ + uint32 W_IMG_CTRL4; /* 0x1B4 32 R/W WISHBONE Image4 Control register */ + uint32 W_BA4; /* 0x1B8 32 R/W WISHBONE Image4 Base Address register */ + uint32 W_AM4; /* 0x1BC 32 R/W WISHBONE Image4 Address Mask register */ + uint32 W_TA4; /* 0x1C0 32 R/W WISHBONE Image4 Translation Address reg */ + uint32 W_IMG_CTRL5; /* 0x1C4 32 R/W WISHBONE Image5 Control register */ + uint32 W_BA5; /* 0x1C8 32 R/W WISHBONE Image5 Base Address register */ + uint32 W_AM5; /* 0x1CC 32 R/W WISHBONE Image5 Address Mask register */ + uint32 W_TA5; /* 0x1D0 32 R/W WISHBONE Image5 Translation Address reg */ + uint32 W_ERR_CS; /* 0x1D4 32 R/W WISHBONE Error Control and Status reg */ + uint32 W_ERR_ADDR; /* 0x1D8 32 R WISHBONE Erroneous Address register */ + uint32 W_ERR_DATA; /* 0x1DC 32 R WISHBONE Erroneous Data register */ + uint32 CNF_ADDR; /* 0x1E0 32 R/W Configuration Cycle register */ + uint32 CNF_DATA; /* 0x1E4 32 R/W Configuration Cycle Generation Data reg */ + + uint32 INT_ACK; /* 0x1E8 32 R Interrupt Acknowledge register */ + uint32 ICR; /* 0x1EC 32 R/W Interrupt Control register */ + uint32 ISR; /* 0x1F0 32 R/W Interrupt Status register */ +} spih_pciregs_t; + +/* + * PCI Core interrupt enable and status bit definitions. + */ + +/* PCI Core ICR Register bit definitions */ +#define PCI_INT_PROP_EN (1 << 0) /* Interrupt Propagation Enable */ +#define PCI_WB_ERR_INT_EN (1 << 1) /* Wishbone Error Interrupt Enable */ +#define PCI_PCI_ERR_INT_EN (1 << 2) /* PCI Error Interrupt Enable */ +#define PCI_PAR_ERR_INT_EN (1 << 3) /* Parity Error Interrupt Enable */ +#define PCI_SYS_ERR_INT_EN (1 << 4) /* System Error Interrupt Enable */ +#define PCI_SOFTWARE_RESET (1U << 31) /* Software reset of the PCI Core. */ + + +/* PCI Core ISR Register bit definitions */ +#define PCI_INT_PROP_ST (1 << 0) /* Interrupt Propagation Status */ +#define PCI_WB_ERR_INT_ST (1 << 1) /* Wishbone Error Interrupt Status */ +#define PCI_PCI_ERR_INT_ST (1 << 2) /* PCI Error Interrupt Status */ +#define PCI_PAR_ERR_INT_ST (1 << 3) /* Parity Error Interrupt Status */ +#define PCI_SYS_ERR_INT_ST (1 << 4) /* System Error Interrupt Status */ + + +/* Registers on the Wishbone bus */ +#define SPIH_CTLR_INTR (1 << 0) /* SPI Host Controller Core Interrupt */ +#define SPIH_DEV_INTR (1 << 1) /* SPI Device Interrupt */ +#define SPIH_WFIFO_INTR (1 << 2) /* SPI Tx FIFO Empty Intr (FPGA Rev >= 8) */ + +/* GPIO Bit definitions */ +#define SPIH_CS (1 << 0) /* SPI Chip Select (active low) */ +#define SPIH_SLOT_POWER (1 << 1) /* SD Card Slot Power Enable */ +#define SPIH_CARD_DETECT (1 << 2) /* SD Card Detect */ + +/* SPI Status Register Bit definitions */ +#define SPIH_STATE_MASK 0x30 /* SPI Transfer State Machine state mask */ +#define SPIH_STATE_SHIFT 4 /* SPI Transfer State Machine state shift */ +#define SPIH_WFFULL (1 << 3) /* SPI Write FIFO Full */ +#define SPIH_WFEMPTY (1 << 2) /* SPI Write FIFO Empty */ +#define SPIH_RFFULL (1 << 1) /* SPI Read FIFO Full */ +#define SPIH_RFEMPTY (1 << 0) /* SPI Read FIFO Empty */ + +#define SPIH_EXT_CLK (1U << 31) /* Use External Clock as PLL Clock source. */ + +#define SPIH_PLL_NO_CLK (1 << 1) /* Set to 1 if the PLL's input clock is lost. */ +#define SPIH_PLL_LOCKED (1 << 3) /* Set to 1 when the PLL is locked. */ + +/* Spin bit loop bound check */ +#define SPI_SPIN_BOUND 0xf4240 /* 1 million */ diff --git a/drivers/net/wireless/bcm4329/include/bcmperf.h b/drivers/net/wireless/bcm4329/include/bcmperf.h new file mode 100644 index 0000000000000000000000000000000000000000..2a78784e85d36e58a6d9181c12d522d61d5f5026 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmperf.h @@ -0,0 +1,36 @@ +/* + * Performance counters software interface. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmperf.h,v 13.5 2007/09/14 22:00:59 Exp $ + */ +/* essai */ +#ifndef _BCMPERF_H_ +#define _BCMPERF_H_ +/* get cache hits and misses */ +#define BCMPERF_ENABLE_INSTRCOUNT() +#define BCMPERF_ENABLE_ICACHE_MISS() +#define BCMPERF_ENABLE_ICACHE_HIT() +#define BCMPERF_GETICACHE_MISS(x) ((x) = 0) +#define BCMPERF_GETICACHE_HIT(x) ((x) = 0) +#define BCMPERF_GETINSTRCOUNT(x) ((x) = 0) +#endif /* _BCMPERF_H_ */ diff --git a/drivers/net/wireless/bcm4329/include/bcmsdbus.h b/drivers/net/wireless/bcm4329/include/bcmsdbus.h new file mode 100644 index 0000000000000000000000000000000000000000..b7b67bc66248e743f9ef14ff804f801644b42bb0 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmsdbus.h @@ -0,0 +1,117 @@ +/* + * Definitions for API from sdio common code (bcmsdh) to individual + * host controller drivers. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdbus.h,v 13.11.14.2.6.6 2009/10/27 17:20:28 Exp $ + */ + +#ifndef _sdio_api_h_ +#define _sdio_api_h_ + + +#define SDIOH_API_RC_SUCCESS (0x00) +#define SDIOH_API_RC_FAIL (0x01) +#define SDIOH_API_SUCCESS(status) (status == 0) + +#define SDIOH_READ 0 /* Read request */ +#define SDIOH_WRITE 1 /* Write request */ + +#define SDIOH_DATA_FIX 0 /* Fixed addressing */ +#define SDIOH_DATA_INC 1 /* Incremental addressing */ + +#define SDIOH_CMD_TYPE_NORMAL 0 /* Normal command */ +#define SDIOH_CMD_TYPE_APPEND 1 /* Append command */ +#define SDIOH_CMD_TYPE_CUTTHRU 2 /* Cut-through command */ + +#define SDIOH_DATA_PIO 0 /* PIO mode */ +#define SDIOH_DATA_DMA 1 /* DMA mode */ + + +typedef int SDIOH_API_RC; + +/* SDio Host structure */ +typedef struct sdioh_info sdioh_info_t; + +/* callback function, taking one arg */ +typedef void (*sdioh_cb_fn_t)(void *); + +/* attach, return handler on success, NULL if failed. + * The handler shall be provided by all subsequent calls. No local cache + * cfghdl points to the starting address of pci device mapped memory + */ +extern sdioh_info_t * sdioh_attach(osl_t *osh, void *cfghdl, uint irq); +extern SDIOH_API_RC sdioh_detach(osl_t *osh, sdioh_info_t *si); +extern SDIOH_API_RC sdioh_interrupt_register(sdioh_info_t *si, sdioh_cb_fn_t fn, void *argh); +extern SDIOH_API_RC sdioh_interrupt_deregister(sdioh_info_t *si); + +/* query whether SD interrupt is enabled or not */ +extern SDIOH_API_RC sdioh_interrupt_query(sdioh_info_t *si, bool *onoff); + +/* enable or disable SD interrupt */ +extern SDIOH_API_RC sdioh_interrupt_set(sdioh_info_t *si, bool enable_disable); + +#if defined(DHD_DEBUG) +extern bool sdioh_interrupt_pending(sdioh_info_t *si); +#endif + +/* read or write one byte using cmd52 */ +extern SDIOH_API_RC sdioh_request_byte(sdioh_info_t *si, uint rw, uint fnc, uint addr, uint8 *byte); + +/* read or write 2/4 bytes using cmd53 */ +extern SDIOH_API_RC sdioh_request_word(sdioh_info_t *si, uint cmd_type, uint rw, uint fnc, + uint addr, uint32 *word, uint nbyte); + +/* read or write any buffer using cmd53 */ +extern SDIOH_API_RC sdioh_request_buffer(sdioh_info_t *si, uint pio_dma, uint fix_inc, + uint rw, uint fnc_num, uint32 addr, uint regwidth, uint32 buflen, uint8 *buffer, + void *pkt); + +/* get cis data */ +extern SDIOH_API_RC sdioh_cis_read(sdioh_info_t *si, uint fuc, uint8 *cis, uint32 length); + +extern SDIOH_API_RC sdioh_cfg_read(sdioh_info_t *si, uint fuc, uint32 addr, uint8 *data); +extern SDIOH_API_RC sdioh_cfg_write(sdioh_info_t *si, uint fuc, uint32 addr, uint8 *data); + +/* query number of io functions */ +extern uint sdioh_query_iofnum(sdioh_info_t *si); + +/* handle iovars */ +extern int sdioh_iovar_op(sdioh_info_t *si, const char *name, + void *params, int plen, void *arg, int len, bool set); + +/* Issue abort to the specified function and clear controller as needed */ +extern int sdioh_abort(sdioh_info_t *si, uint fnc); + +/* Start and Stop SDIO without re-enumerating the SD card. */ +extern int sdioh_start(sdioh_info_t *si, int stage); +extern int sdioh_stop(sdioh_info_t *si); + +/* Reset and re-initialize the device */ +extern int sdioh_sdio_reset(sdioh_info_t *si); + +/* Helper function */ +void *bcmsdh_get_sdioh(bcmsdh_info_t *sdh); + + + +#endif /* _sdio_api_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/bcmsdh.h b/drivers/net/wireless/bcm4329/include/bcmsdh.h new file mode 100644 index 0000000000000000000000000000000000000000..f5dee5c5844599504a796431e86e65389a6bedd9 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmsdh.h @@ -0,0 +1,208 @@ +/* + * SDIO host client driver interface of Broadcom HNBU + * export functions to client drivers + * abstract OS and BUS specific details of SDIO + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdh.h,v 13.35.14.7.6.8 2009/10/14 04:22:25 Exp $ + */ + +#ifndef _bcmsdh_h_ +#define _bcmsdh_h_ + +#define BCMSDH_ERROR_VAL 0x0001 /* Error */ +#define BCMSDH_INFO_VAL 0x0002 /* Info */ +extern const uint bcmsdh_msglevel; + +#define BCMSDH_ERROR(x) +#define BCMSDH_INFO(x) + +/* forward declarations */ +typedef struct bcmsdh_info bcmsdh_info_t; +typedef void (*bcmsdh_cb_fn_t)(void *); + +/* Attach and build an interface to the underlying SD host driver. + * - Allocates resources (structs, arrays, mem, OS handles, etc) needed by bcmsdh. + * - Returns the bcmsdh handle and virtual address base for register access. + * The returned handle should be used in all subsequent calls, but the bcmsh + * implementation may maintain a single "default" handle (e.g. the first or + * most recent one) to enable single-instance implementations to pass NULL. + */ +extern bcmsdh_info_t *bcmsdh_attach(osl_t *osh, void *cfghdl, void **regsva, uint irq); + +/* Detach - freeup resources allocated in attach */ +extern int bcmsdh_detach(osl_t *osh, void *sdh); + +/* Query if SD device interrupts are enabled */ +extern bool bcmsdh_intr_query(void *sdh); + +/* Enable/disable SD interrupt */ +extern int bcmsdh_intr_enable(void *sdh); +extern int bcmsdh_intr_disable(void *sdh); + +/* Register/deregister device interrupt handler. */ +extern int bcmsdh_intr_reg(void *sdh, bcmsdh_cb_fn_t fn, void *argh); +extern int bcmsdh_intr_dereg(void *sdh); + +#if defined(DHD_DEBUG) +/* Query pending interrupt status from the host controller */ +extern bool bcmsdh_intr_pending(void *sdh); +#endif + +#ifdef BCMLXSDMMC +extern int bcmsdh_claim_host_and_lock(void *sdh); +extern int bcmsdh_release_host_and_unlock(void *sdh); +#endif /* BCMLXSDMMC */ + +/* Register a callback to be called if and when bcmsdh detects + * device removal. No-op in the case of non-removable/hardwired devices. + */ +extern int bcmsdh_devremove_reg(void *sdh, bcmsdh_cb_fn_t fn, void *argh); + +/* Access SDIO address space (e.g. CCCR) using CMD52 (single-byte interface). + * fn: function number + * addr: unmodified SDIO-space address + * data: data byte to write + * err: pointer to error code (or NULL) + */ +extern uint8 bcmsdh_cfg_read(void *sdh, uint func, uint32 addr, int *err); +extern void bcmsdh_cfg_write(void *sdh, uint func, uint32 addr, uint8 data, int *err); + +/* Read/Write 4bytes from/to cfg space */ +extern uint32 bcmsdh_cfg_read_word(void *sdh, uint fnc_num, uint32 addr, int *err); +extern void bcmsdh_cfg_write_word(void *sdh, uint fnc_num, uint32 addr, uint32 data, int *err); + +/* Read CIS content for specified function. + * fn: function whose CIS is being requested (0 is common CIS) + * cis: pointer to memory location to place results + * length: number of bytes to read + * Internally, this routine uses the values from the cis base regs (0x9-0xB) + * to form an SDIO-space address to read the data from. + */ +extern int bcmsdh_cis_read(void *sdh, uint func, uint8 *cis, uint length); + +/* Synchronous access to device (client) core registers via CMD53 to F1. + * addr: backplane address (i.e. >= regsva from attach) + * size: register width in bytes (2 or 4) + * data: data for register write + */ +extern uint32 bcmsdh_reg_read(void *sdh, uint32 addr, uint size); +extern uint32 bcmsdh_reg_write(void *sdh, uint32 addr, uint size, uint32 data); + +/* Indicate if last reg read/write failed */ +extern bool bcmsdh_regfail(void *sdh); + +/* Buffer transfer to/from device (client) core via cmd53. + * fn: function number + * addr: backplane address (i.e. >= regsva from attach) + * flags: backplane width, address increment, sync/async + * buf: pointer to memory data buffer + * nbytes: number of bytes to transfer to/from buf + * pkt: pointer to packet associated with buf (if any) + * complete: callback function for command completion (async only) + * handle: handle for completion callback (first arg in callback) + * Returns 0 or error code. + * NOTE: Async operation is not currently supported. + */ +typedef void (*bcmsdh_cmplt_fn_t)(void *handle, int status, bool sync_waiting); +extern int bcmsdh_send_buf(void *sdh, uint32 addr, uint fn, uint flags, + uint8 *buf, uint nbytes, void *pkt, + bcmsdh_cmplt_fn_t complete, void *handle); +extern int bcmsdh_recv_buf(void *sdh, uint32 addr, uint fn, uint flags, + uint8 *buf, uint nbytes, void *pkt, + bcmsdh_cmplt_fn_t complete, void *handle); + +/* Flags bits */ +#define SDIO_REQ_4BYTE 0x1 /* Four-byte target (backplane) width (vs. two-byte) */ +#define SDIO_REQ_FIXED 0x2 /* Fixed address (FIFO) (vs. incrementing address) */ +#define SDIO_REQ_ASYNC 0x4 /* Async request (vs. sync request) */ + +/* Pending (non-error) return code */ +#define BCME_PENDING 1 + +/* Read/write to memory block (F1, no FIFO) via CMD53 (sync only). + * rw: read or write (0/1) + * addr: direct SDIO address + * buf: pointer to memory data buffer + * nbytes: number of bytes to transfer to/from buf + * Returns 0 or error code. + */ +extern int bcmsdh_rwdata(void *sdh, uint rw, uint32 addr, uint8 *buf, uint nbytes); + +/* Issue an abort to the specified function */ +extern int bcmsdh_abort(void *sdh, uint fn); + +/* Start SDIO Host Controller communication */ +extern int bcmsdh_start(void *sdh, int stage); + +/* Stop SDIO Host Controller communication */ +extern int bcmsdh_stop(void *sdh); + +/* Returns the "Device ID" of target device on the SDIO bus. */ +extern int bcmsdh_query_device(void *sdh); + +/* Returns the number of IO functions reported by the device */ +extern uint bcmsdh_query_iofnum(void *sdh); + +/* Miscellaneous knob tweaker. */ +extern int bcmsdh_iovar_op(void *sdh, const char *name, + void *params, int plen, void *arg, int len, bool set); + +/* Reset and reinitialize the device */ +extern int bcmsdh_reset(bcmsdh_info_t *sdh); + +/* helper functions */ + +extern void *bcmsdh_get_sdioh(bcmsdh_info_t *sdh); + +/* callback functions */ +typedef struct { + /* attach to device */ + void *(*attach)(uint16 vend_id, uint16 dev_id, uint16 bus, uint16 slot, + uint16 func, uint bustype, void * regsva, osl_t * osh, + void * param); + /* detach from device */ + void (*detach)(void *ch); +} bcmsdh_driver_t; + +/* platform specific/high level functions */ +extern int bcmsdh_register(bcmsdh_driver_t *driver); +extern void bcmsdh_unregister(void); +extern bool bcmsdh_chipmatch(uint16 vendor, uint16 device); +extern void bcmsdh_device_remove(void * sdh); + +#if defined(OOB_INTR_ONLY) +extern int bcmsdh_register_oob_intr(void * dhdp); +extern void bcmsdh_unregister_oob_intr(void); +extern void bcmsdh_oob_intr_set(bool enable); +#endif /* defined(OOB_INTR_ONLY) */ +/* Function to pass device-status bits to DHD. */ +extern uint32 bcmsdh_get_dstatus(void *sdh); + +/* Function to return current window addr */ +extern uint32 bcmsdh_cur_sbwad(void *sdh); + +/* Function to pass chipid and rev to lower layers for controlling pr's */ +extern void bcmsdh_chipinfo(void *sdh, uint32 chip, uint32 chiprev); + + +#endif /* _bcmsdh_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/bcmsdh_sdmmc.h b/drivers/net/wireless/bcm4329/include/bcmsdh_sdmmc.h new file mode 100644 index 0000000000000000000000000000000000000000..4e6d1b5bd94f5a93479f4cdad936062758a800a4 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmsdh_sdmmc.h @@ -0,0 +1,122 @@ +/* + * BCMSDH Function Driver for the native SDIO/MMC driver in the Linux Kernel + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdh_sdmmc.h,v 13.1.2.1.8.7 2009/10/27 18:22:52 Exp $ + */ + +#ifndef __BCMSDH_SDMMC_H__ +#define __BCMSDH_SDMMC_H__ + +#define sd_err(x) +#define sd_trace(x) +#define sd_info(x) +#define sd_debug(x) +#define sd_data(x) +#define sd_ctrl(x) + +#define sd_sync_dma(sd, read, nbytes) +#define sd_init_dma(sd) +#define sd_ack_intr(sd) +#define sd_wakeup(sd); + +/* Allocate/init/free per-OS private data */ +extern int sdioh_sdmmc_osinit(sdioh_info_t *sd); +extern void sdioh_sdmmc_osfree(sdioh_info_t *sd); + +#define sd_log(x) + +#define SDIOH_ASSERT(exp) \ + do { if (!(exp)) \ + printf("!!!ASSERT fail: file %s lines %d", __FILE__, __LINE__); \ + } while (0) + +#define BLOCK_SIZE_4318 64 +#define BLOCK_SIZE_4328 512 + +/* internal return code */ +#define SUCCESS 0 +#define ERROR 1 + +/* private bus modes */ +#define SDIOH_MODE_SD4 2 +#define CLIENT_INTR 0x100 /* Get rid of this! */ + +struct sdioh_info { + osl_t *osh; /* osh handler */ + bool client_intr_enabled; /* interrupt connnected flag */ + bool intr_handler_valid; /* client driver interrupt handler valid */ + sdioh_cb_fn_t intr_handler; /* registered interrupt handler */ + void *intr_handler_arg; /* argument to call interrupt handler */ + uint16 intmask; /* Current active interrupts */ + void *sdos_info; /* Pointer to per-OS private data */ + + uint irq; /* Client irq */ + int intrcount; /* Client interrupts */ + + bool sd_use_dma; /* DMA on CMD53 */ + bool sd_blockmode; /* sd_blockmode == FALSE => 64 Byte Cmd 53s. */ + /* Must be on for sd_multiblock to be effective */ + bool use_client_ints; /* If this is false, make sure to restore */ + int sd_mode; /* SD1/SD4/SPI */ + int client_block_size[SDIOD_MAX_IOFUNCS]; /* Blocksize */ + uint8 num_funcs; /* Supported funcs on client */ + uint32 com_cis_ptr; + uint32 func_cis_ptr[SDIOD_MAX_IOFUNCS]; + uint max_dma_len; + uint max_dma_descriptors; /* DMA Descriptors supported by this controller. */ +// SDDMA_DESCRIPTOR SGList[32]; /* Scatter/Gather DMA List */ +}; + +/************************************************************ + * Internal interfaces: per-port references into bcmsdh_sdmmc.c + */ + +/* Global message bits */ +extern uint sd_msglevel; + +/* OS-independent interrupt handler */ +extern bool check_client_intr(sdioh_info_t *sd); + +/* Core interrupt enable/disable of device interrupts */ +extern void sdioh_sdmmc_devintr_on(sdioh_info_t *sd); +extern void sdioh_sdmmc_devintr_off(sdioh_info_t *sd); + + +/************************************************************** + * Internal interfaces: bcmsdh_sdmmc.c references to per-port code + */ + +/* Register mapping routines */ +extern uint32 *sdioh_sdmmc_reg_map(osl_t *osh, int32 addr, int size); +extern void sdioh_sdmmc_reg_unmap(osl_t *osh, int32 addr, int size); + +/* Interrupt (de)registration routines */ +extern int sdioh_sdmmc_register_irq(sdioh_info_t *sd, uint irq); +extern void sdioh_sdmmc_free_irq(uint irq, sdioh_info_t *sd); + +typedef struct _BCMSDH_SDMMC_INSTANCE { + sdioh_info_t *sd; + struct sdio_func *func[SDIOD_MAX_IOFUNCS]; +} BCMSDH_SDMMC_INSTANCE, *PBCMSDH_SDMMC_INSTANCE; + +#endif /* __BCMSDH_SDMMC_H__ */ diff --git a/drivers/net/wireless/bcm4329/include/bcmsdpcm.h b/drivers/net/wireless/bcm4329/include/bcmsdpcm.h new file mode 100644 index 0000000000000000000000000000000000000000..77aca4500ad8f3f1ea05de463616352652fc9b04 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmsdpcm.h @@ -0,0 +1,263 @@ +/* + * Broadcom SDIO/PCMCIA + * Software-specific definitions shared between device and host side + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdpcm.h,v 1.1.2.4 2010/07/02 01:15:46 Exp $ + */ + +#ifndef _bcmsdpcm_h_ +#define _bcmsdpcm_h_ + +/* + * Software allocation of To SB Mailbox resources + */ + +/* intstatus bits */ +#define I_SMB_NAK I_SMB_SW0 /* To SB Mailbox Frame NAK */ +#define I_SMB_INT_ACK I_SMB_SW1 /* To SB Mailbox Host Interrupt ACK */ +#define I_SMB_USE_OOB I_SMB_SW2 /* To SB Mailbox Use OOB Wakeup */ +#define I_SMB_DEV_INT I_SMB_SW3 /* To SB Mailbox Miscellaneous Interrupt */ + +/* tosbmailbox bits corresponding to intstatus bits */ +#define SMB_NAK (1 << 0) /* To SB Mailbox Frame NAK */ +#define SMB_INT_ACK (1 << 1) /* To SB Mailbox Host Interrupt ACK */ +#define SMB_USE_OOB (1 << 2) /* To SB Mailbox Use OOB Wakeup */ +#define SMB_DEV_INT (1 << 3) /* To SB Mailbox Miscellaneous Interrupt */ +#define SMB_MASK 0x0000000f /* To SB Mailbox Mask */ + +/* tosbmailboxdata */ +#define SMB_DATA_VERSION_MASK 0x00ff0000 /* host protocol version (sent with F2 enable) */ +#define SMB_DATA_VERSION_SHIFT 16 /* host protocol version (sent with F2 enable) */ + +/* + * Software allocation of To Host Mailbox resources + */ + +/* intstatus bits */ +#define I_HMB_FC_STATE I_HMB_SW0 /* To Host Mailbox Flow Control State */ +#define I_HMB_FC_CHANGE I_HMB_SW1 /* To Host Mailbox Flow Control State Changed */ +#define I_HMB_FRAME_IND I_HMB_SW2 /* To Host Mailbox Frame Indication */ +#define I_HMB_HOST_INT I_HMB_SW3 /* To Host Mailbox Miscellaneous Interrupt */ + +/* tohostmailbox bits corresponding to intstatus bits */ +#define HMB_FC_ON (1 << 0) /* To Host Mailbox Flow Control State */ +#define HMB_FC_CHANGE (1 << 1) /* To Host Mailbox Flow Control State Changed */ +#define HMB_FRAME_IND (1 << 2) /* To Host Mailbox Frame Indication */ +#define HMB_HOST_INT (1 << 3) /* To Host Mailbox Miscellaneous Interrupt */ +#define HMB_MASK 0x0000000f /* To Host Mailbox Mask */ + +/* tohostmailboxdata */ +#define HMB_DATA_NAKHANDLED 1 /* we're ready to retransmit NAK'd frame to host */ +#define HMB_DATA_DEVREADY 2 /* we're ready to to talk to host after enable */ +#define HMB_DATA_FC 4 /* per prio flowcontrol update flag to host */ +#define HMB_DATA_FWREADY 8 /* firmware is ready for protocol activity */ + +#define HMB_DATA_FCDATA_MASK 0xff000000 /* per prio flowcontrol data */ +#define HMB_DATA_FCDATA_SHIFT 24 /* per prio flowcontrol data */ + +#define HMB_DATA_VERSION_MASK 0x00ff0000 /* device protocol version (with devready) */ +#define HMB_DATA_VERSION_SHIFT 16 /* device protocol version (with devready) */ + +/* + * Software-defined protocol header + */ + +/* Current protocol version */ +#define SDPCM_PROT_VERSION 4 + +/* SW frame header */ +#define SDPCM_SEQUENCE_MASK 0x000000ff /* Sequence Number Mask */ +#define SDPCM_PACKET_SEQUENCE(p) (((uint8 *)p)[0] & 0xff) /* p starts w/SW Header */ + +#define SDPCM_CHANNEL_MASK 0x00000f00 /* Channel Number Mask */ +#define SDPCM_CHANNEL_SHIFT 8 /* Channel Number Shift */ +#define SDPCM_PACKET_CHANNEL(p) (((uint8 *)p)[1] & 0x0f) /* p starts w/SW Header */ + +#define SDPCM_FLAGS_MASK 0x0000f000 /* Mask of flag bits */ +#define SDPCM_FLAGS_SHIFT 12 /* Flag bits shift */ +#define SDPCM_PACKET_FLAGS(p) ((((uint8 *)p)[1] & 0xf0) >> 4) /* p starts w/SW Header */ + +/* Next Read Len: lookahead length of next frame, in 16-byte units (rounded up) */ +#define SDPCM_NEXTLEN_MASK 0x00ff0000 /* Next Read Len Mask */ +#define SDPCM_NEXTLEN_SHIFT 16 /* Next Read Len Shift */ +#define SDPCM_NEXTLEN_VALUE(p) ((((uint8 *)p)[2] & 0xff) << 4) /* p starts w/SW Header */ +#define SDPCM_NEXTLEN_OFFSET 2 + +/* Data Offset from SOF (HW Tag, SW Tag, Pad) */ +#define SDPCM_DOFFSET_OFFSET 3 /* Data Offset */ +#define SDPCM_DOFFSET_VALUE(p) (((uint8 *)p)[SDPCM_DOFFSET_OFFSET] & 0xff) +#define SDPCM_DOFFSET_MASK 0xff000000 +#define SDPCM_DOFFSET_SHIFT 24 + +#define SDPCM_FCMASK_OFFSET 4 /* Flow control */ +#define SDPCM_FCMASK_VALUE(p) (((uint8 *)p)[SDPCM_FCMASK_OFFSET ] & 0xff) +#define SDPCM_WINDOW_OFFSET 5 /* Credit based fc */ +#define SDPCM_WINDOW_VALUE(p) (((uint8 *)p)[SDPCM_WINDOW_OFFSET] & 0xff) +#define SDPCM_VERSION_OFFSET 6 /* Version # */ +#define SDPCM_VERSION_VALUE(p) (((uint8 *)p)[SDPCM_VERSION_OFFSET] & 0xff) +#define SDPCM_UNUSED_OFFSET 7 /* Spare */ +#define SDPCM_UNUSED_VALUE(p) (((uint8 *)p)[SDPCM_UNUSED_OFFSET] & 0xff) + +#define SDPCM_SWHEADER_LEN 8 /* SW header is 64 bits */ + +/* logical channel numbers */ +#define SDPCM_CONTROL_CHANNEL 0 /* Control Request/Response Channel Id */ +#define SDPCM_EVENT_CHANNEL 1 /* Asyc Event Indication Channel Id */ +#define SDPCM_DATA_CHANNEL 2 /* Data Xmit/Recv Channel Id */ +#define SDPCM_GLOM_CHANNEL 3 /* For coalesced packets (superframes) */ +#define SDPCM_TEST_CHANNEL 15 /* Reserved for test/debug packets */ +#define SDPCM_MAX_CHANNEL 15 + +#define SDPCM_SEQUENCE_WRAP 256 /* wrap-around val for eight-bit frame seq number */ + +#define SDPCM_FLAG_RESVD0 0x01 +#define SDPCM_FLAG_RESVD1 0x02 +#define SDPCM_FLAG_GSPI_TXENAB 0x04 +#define SDPCM_FLAG_GLOMDESC 0x08 /* Superframe descriptor mask */ + +/* For GLOM_CHANNEL frames, use a flag to indicate descriptor frame */ +#define SDPCM_GLOMDESC_FLAG (SDPCM_FLAG_GLOMDESC << SDPCM_FLAGS_SHIFT) + +#define SDPCM_GLOMDESC(p) (((uint8 *)p)[1] & 0x80) + +/* For TEST_CHANNEL packets, define another 4-byte header */ +#define SDPCM_TEST_HDRLEN 4 /* Generally: Cmd(1), Ext(1), Len(2); + * Semantics of Ext byte depend on command. + * Len is current or requested frame length, not + * including test header; sent little-endian. + */ +#define SDPCM_TEST_DISCARD 0x01 /* Receiver discards. Ext is a pattern id. */ +#define SDPCM_TEST_ECHOREQ 0x02 /* Echo request. Ext is a pattern id. */ +#define SDPCM_TEST_ECHORSP 0x03 /* Echo response. Ext is a pattern id. */ +#define SDPCM_TEST_BURST 0x04 /* Receiver to send a burst. Ext is a frame count */ +#define SDPCM_TEST_SEND 0x05 /* Receiver sets send mode. Ext is boolean on/off */ + +/* Handy macro for filling in datagen packets with a pattern */ +#define SDPCM_TEST_FILL(byteno, id) ((uint8)(id + byteno)) + +/* + * Software counters (first part matches hardware counters) + */ + +typedef volatile struct { + uint32 cmd52rd; /* Cmd52RdCount, SDIO: cmd52 reads */ + uint32 cmd52wr; /* Cmd52WrCount, SDIO: cmd52 writes */ + uint32 cmd53rd; /* Cmd53RdCount, SDIO: cmd53 reads */ + uint32 cmd53wr; /* Cmd53WrCount, SDIO: cmd53 writes */ + uint32 abort; /* AbortCount, SDIO: aborts */ + uint32 datacrcerror; /* DataCrcErrorCount, SDIO: frames w/CRC error */ + uint32 rdoutofsync; /* RdOutOfSyncCount, SDIO/PCMCIA: Rd Frm out of sync */ + uint32 wroutofsync; /* RdOutOfSyncCount, SDIO/PCMCIA: Wr Frm out of sync */ + uint32 writebusy; /* WriteBusyCount, SDIO: device asserted "busy" */ + uint32 readwait; /* ReadWaitCount, SDIO: no data ready for a read cmd */ + uint32 readterm; /* ReadTermCount, SDIO: read frame termination cmds */ + uint32 writeterm; /* WriteTermCount, SDIO: write frames termination cmds */ + uint32 rxdescuflo; /* receive descriptor underflows */ + uint32 rxfifooflo; /* receive fifo overflows */ + uint32 txfifouflo; /* transmit fifo underflows */ + uint32 runt; /* runt (too short) frames recv'd from bus */ + uint32 badlen; /* frame's rxh len does not match its hw tag len */ + uint32 badcksum; /* frame's hw tag chksum doesn't agree with len value */ + uint32 seqbreak; /* break in sequence # space from one rx frame to the next */ + uint32 rxfcrc; /* frame rx header indicates crc error */ + uint32 rxfwoos; /* frame rx header indicates write out of sync */ + uint32 rxfwft; /* frame rx header indicates write frame termination */ + uint32 rxfabort; /* frame rx header indicates frame aborted */ + uint32 woosint; /* write out of sync interrupt */ + uint32 roosint; /* read out of sync interrupt */ + uint32 rftermint; /* read frame terminate interrupt */ + uint32 wftermint; /* write frame terminate interrupt */ +} sdpcmd_cnt_t; + +/* + * Register Access Macros + */ + +#define SDIODREV_IS(var, val) ((var) == (val)) +#define SDIODREV_GE(var, val) ((var) >= (val)) +#define SDIODREV_GT(var, val) ((var) > (val)) +#define SDIODREV_LT(var, val) ((var) < (val)) +#define SDIODREV_LE(var, val) ((var) <= (val)) + +#define SDIODDMAREG32(h, dir, chnl) \ + ((dir) == DMA_TX ? \ + (void *)(uintptr)&((h)->regs->dma.sdiod32.dma32regs[chnl].xmt) : \ + (void *)(uintptr)&((h)->regs->dma.sdiod32.dma32regs[chnl].rcv)) + +#define SDIODDMAREG64(h, dir, chnl) \ + ((dir) == DMA_TX ? \ + (void *)(uintptr)&((h)->regs->dma.sdiod64.dma64regs[chnl].xmt) : \ + (void *)(uintptr)&((h)->regs->dma.sdiod64.dma64regs[chnl].rcv)) + +#define SDIODDMAREG(h, dir, chnl) \ + (SDIODREV_LT((h)->corerev, 1) ? \ + SDIODDMAREG32((h), (dir), (chnl)) : \ + SDIODDMAREG64((h), (dir), (chnl))) + +#define PCMDDMAREG(h, dir, chnl) \ + ((dir) == DMA_TX ? \ + (void *)(uintptr)&((h)->regs->dma.pcm32.dmaregs.xmt) : \ + (void *)(uintptr)&((h)->regs->dma.pcm32.dmaregs.rcv)) + +#define SDPCMDMAREG(h, dir, chnl, coreid) \ + ((coreid) == SDIOD_CORE_ID ? \ + SDIODDMAREG(h, dir, chnl) : \ + PCMDDMAREG(h, dir, chnl)) + +#define SDIODFIFOREG(h, corerev) \ + (SDIODREV_LT((corerev), 1) ? \ + ((dma32diag_t *)(uintptr)&((h)->regs->dma.sdiod32.dmafifo)) : \ + ((dma32diag_t *)(uintptr)&((h)->regs->dma.sdiod64.dmafifo))) + +#define PCMDFIFOREG(h) \ + ((dma32diag_t *)(uintptr)&((h)->regs->dma.pcm32.dmafifo)) + +#define SDPCMFIFOREG(h, coreid, corerev) \ + ((coreid) == SDIOD_CORE_ID ? \ + SDIODFIFOREG(h, corerev) : \ + PCMDFIFOREG(h)) + +/* + * Shared structure between dongle and the host + * The structure contains pointers to trap or assert information shared with the host + */ +#define SDPCM_SHARED_VERSION 0x0002 +#define SDPCM_SHARED_VERSION_MASK 0x00FF +#define SDPCM_SHARED_ASSERT_BUILT 0x0100 +#define SDPCM_SHARED_ASSERT 0x0200 +#define SDPCM_SHARED_TRAP 0x0400 + +typedef struct { + uint32 flags; + uint32 trap_addr; + uint32 assert_exp_addr; + uint32 assert_file_addr; + uint32 assert_line; + uint32 console_addr; /* Address of hndrte_cons_t */ + uint32 msgtrace_addr; + uint8 tag[32]; +} sdpcm_shared_t; + +extern sdpcm_shared_t sdpcm_shared; + +#endif /* _bcmsdpcm_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/bcmsdspi.h b/drivers/net/wireless/bcm4329/include/bcmsdspi.h new file mode 100644 index 0000000000000000000000000000000000000000..eaae10d8bf19d7a95d1dc24d5c55d411083fc41f --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmsdspi.h @@ -0,0 +1,131 @@ +/* + * SD-SPI Protocol Conversion - BCMSDH->SPI Translation Layer + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdspi.h,v 13.8.10.2 2008/06/30 21:09:40 Exp $ + */ + +/* global msglevel for debug messages - bitvals come from sdiovar.h */ + +#define sd_err(x) +#define sd_trace(x) +#define sd_info(x) +#define sd_debug(x) +#define sd_data(x) +#define sd_ctrl(x) + +#define sd_log(x) + +#define SDIOH_ASSERT(exp) \ + do { if (!(exp)) \ + printf("!!!ASSERT fail: file %s lines %d", __FILE__, __LINE__); \ + } while (0) + +#define BLOCK_SIZE_4318 64 +#define BLOCK_SIZE_4328 512 + +/* internal return code */ +#define SUCCESS 0 +#undef ERROR +#define ERROR 1 + +/* private bus modes */ +#define SDIOH_MODE_SPI 0 + +#define USE_BLOCKMODE 0x2 /* Block mode can be single block or multi */ +#define USE_MULTIBLOCK 0x4 + +struct sdioh_info { + uint cfg_bar; /* pci cfg address for bar */ + uint32 caps; /* cached value of capabilities reg */ + uint bar0; /* BAR0 for PCI Device */ + osl_t *osh; /* osh handler */ + void *controller; /* Pointer to SPI Controller's private data struct */ + + uint lockcount; /* nest count of sdspi_lock() calls */ + bool client_intr_enabled; /* interrupt connnected flag */ + bool intr_handler_valid; /* client driver interrupt handler valid */ + sdioh_cb_fn_t intr_handler; /* registered interrupt handler */ + void *intr_handler_arg; /* argument to call interrupt handler */ + bool initialized; /* card initialized */ + uint32 target_dev; /* Target device ID */ + uint32 intmask; /* Current active interrupts */ + void *sdos_info; /* Pointer to per-OS private data */ + + uint32 controller_type; /* Host controller type */ + uint8 version; /* Host Controller Spec Compliance Version */ + uint irq; /* Client irq */ + uint32 intrcount; /* Client interrupts */ + uint32 local_intrcount; /* Controller interrupts */ + bool host_init_done; /* Controller initted */ + bool card_init_done; /* Client SDIO interface initted */ + bool polled_mode; /* polling for command completion */ + + bool sd_use_dma; /* DMA on CMD53 */ + bool sd_blockmode; /* sd_blockmode == FALSE => 64 Byte Cmd 53s. */ + /* Must be on for sd_multiblock to be effective */ + bool use_client_ints; /* If this is false, make sure to restore */ + bool got_hcint; /* Host Controller interrupt. */ + /* polling hack in wl_linux.c:wl_timer() */ + int adapter_slot; /* Maybe dealing with multiple slots/controllers */ + int sd_mode; /* SD1/SD4/SPI */ + int client_block_size[SDIOD_MAX_IOFUNCS]; /* Blocksize */ + uint32 data_xfer_count; /* Current register transfer size */ + uint32 cmd53_wr_data; /* Used to pass CMD53 write data */ + uint32 card_response; /* Used to pass back response status byte */ + uint32 card_rsp_data; /* Used to pass back response data word */ + uint16 card_rca; /* Current Address */ + uint8 num_funcs; /* Supported funcs on client */ + uint32 com_cis_ptr; + uint32 func_cis_ptr[SDIOD_MAX_IOFUNCS]; + void *dma_buf; + ulong dma_phys; + int r_cnt; /* rx count */ + int t_cnt; /* tx_count */ +}; + +/************************************************************ + * Internal interfaces: per-port references into bcmsdspi.c + */ + +/* Global message bits */ +extern uint sd_msglevel; + +/************************************************************** + * Internal interfaces: bcmsdspi.c references to per-port code + */ + +/* Register mapping routines */ +extern uint32 *spi_reg_map(osl_t *osh, uintptr addr, int size); +extern void spi_reg_unmap(osl_t *osh, uintptr addr, int size); + +/* Interrupt (de)registration routines */ +extern int spi_register_irq(sdioh_info_t *sd, uint irq); +extern void spi_free_irq(uint irq, sdioh_info_t *sd); + +/* OS-specific interrupt wrappers (atomic interrupt enable/disable) */ +extern void spi_lock(sdioh_info_t *sd); +extern void spi_unlock(sdioh_info_t *sd); + +/* Allocate/init/free per-OS private data */ +extern int spi_osinit(sdioh_info_t *sd); +extern void spi_osfree(sdioh_info_t *sd); diff --git a/drivers/net/wireless/bcm4329/include/bcmsdstd.h b/drivers/net/wireless/bcm4329/include/bcmsdstd.h new file mode 100644 index 0000000000000000000000000000000000000000..974b3d41698d454e44316d25871c2e5b91c22735 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmsdstd.h @@ -0,0 +1,223 @@ +/* + * 'Standard' SDIO HOST CONTROLLER driver + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmsdstd.h,v 13.16.18.1.16.3 2009/12/10 01:09:23 Exp $ + */ + +/* global msglevel for debug messages - bitvals come from sdiovar.h */ + +#define sd_err(x) do { if (sd_msglevel & SDH_ERROR_VAL) printf x; } while (0) +#define sd_trace(x) +#define sd_info(x) +#define sd_debug(x) +#define sd_data(x) +#define sd_ctrl(x) +#define sd_dma(x) + +#define sd_sync_dma(sd, read, nbytes) +#define sd_init_dma(sd) +#define sd_ack_intr(sd) +#define sd_wakeup(sd); +/* Allocate/init/free per-OS private data */ +extern int sdstd_osinit(sdioh_info_t *sd); +extern void sdstd_osfree(sdioh_info_t *sd); + +#define sd_log(x) + +#define SDIOH_ASSERT(exp) \ + do { if (!(exp)) \ + printf("!!!ASSERT fail: file %s lines %d", __FILE__, __LINE__); \ + } while (0) + +#define BLOCK_SIZE_4318 64 +#define BLOCK_SIZE_4328 512 + +/* internal return code */ +#define SUCCESS 0 +#define ERROR 1 + +/* private bus modes */ +#define SDIOH_MODE_SPI 0 +#define SDIOH_MODE_SD1 1 +#define SDIOH_MODE_SD4 2 + +#define MAX_SLOTS 6 /* For PCI: Only 6 BAR entries => 6 slots */ +#define SDIOH_REG_WINSZ 0x100 /* Number of registers in Standard Host Controller */ + +#define SDIOH_TYPE_ARASAN_HDK 1 +#define SDIOH_TYPE_BCM27XX 2 +#define SDIOH_TYPE_TI_PCIXX21 4 /* TI PCIxx21 Standard Host Controller */ +#define SDIOH_TYPE_RICOH_R5C822 5 /* Ricoh Co Ltd R5C822 SD/SDIO/MMC/MS/MSPro Host Adapter */ +#define SDIOH_TYPE_JMICRON 6 /* JMicron Standard SDIO Host Controller */ + +/* For linux, allow yielding for dongle */ +#define BCMSDYIELD + +/* Expected card status value for CMD7 */ +#define SDIOH_CMD7_EXP_STATUS 0x00001E00 + +#define RETRIES_LARGE 100000 +#define RETRIES_SMALL 100 + + +#define USE_BLOCKMODE 0x2 /* Block mode can be single block or multi */ +#define USE_MULTIBLOCK 0x4 + +#define USE_FIFO 0x8 /* Fifo vs non-fifo */ + +#define CLIENT_INTR 0x100 /* Get rid of this! */ + + +struct sdioh_info { + uint cfg_bar; /* pci cfg address for bar */ + uint32 caps; /* cached value of capabilities reg */ + uint32 curr_caps; /* max current capabilities reg */ + + osl_t *osh; /* osh handler */ + volatile char *mem_space; /* pci device memory va */ + uint lockcount; /* nest count of sdstd_lock() calls */ + bool client_intr_enabled; /* interrupt connnected flag */ + bool intr_handler_valid; /* client driver interrupt handler valid */ + sdioh_cb_fn_t intr_handler; /* registered interrupt handler */ + void *intr_handler_arg; /* argument to call interrupt handler */ + bool initialized; /* card initialized */ + uint target_dev; /* Target device ID */ + uint16 intmask; /* Current active interrupts */ + void *sdos_info; /* Pointer to per-OS private data */ + + uint32 controller_type; /* Host controller type */ + uint8 version; /* Host Controller Spec Compliance Version */ + uint irq; /* Client irq */ + int intrcount; /* Client interrupts */ + int local_intrcount; /* Controller interrupts */ + bool host_init_done; /* Controller initted */ + bool card_init_done; /* Client SDIO interface initted */ + bool polled_mode; /* polling for command completion */ + + bool sd_blockmode; /* sd_blockmode == FALSE => 64 Byte Cmd 53s. */ + /* Must be on for sd_multiblock to be effective */ + bool use_client_ints; /* If this is false, make sure to restore */ + /* polling hack in wl_linux.c:wl_timer() */ + int adapter_slot; /* Maybe dealing with multiple slots/controllers */ + int sd_mode; /* SD1/SD4/SPI */ + int client_block_size[SDIOD_MAX_IOFUNCS]; /* Blocksize */ + uint32 data_xfer_count; /* Current transfer */ + uint16 card_rca; /* Current Address */ + int8 sd_dma_mode; /* DMA Mode (PIO, SDMA, ... ADMA2) on CMD53 */ + uint8 num_funcs; /* Supported funcs on client */ + uint32 com_cis_ptr; + uint32 func_cis_ptr[SDIOD_MAX_IOFUNCS]; + void *dma_buf; /* DMA Buffer virtual address */ + ulong dma_phys; /* DMA Buffer physical address */ + void *adma2_dscr_buf; /* ADMA2 Descriptor Buffer virtual address */ + ulong adma2_dscr_phys; /* ADMA2 Descriptor Buffer physical address */ + + /* adjustments needed to make the dma align properly */ + void *dma_start_buf; + ulong dma_start_phys; + uint alloced_dma_size; + void *adma2_dscr_start_buf; + ulong adma2_dscr_start_phys; + uint alloced_adma2_dscr_size; + + int r_cnt; /* rx count */ + int t_cnt; /* tx_count */ + bool got_hcint; /* local interrupt flag */ + uint16 last_intrstatus; /* to cache intrstatus */ +}; + +#define DMA_MODE_NONE 0 +#define DMA_MODE_SDMA 1 +#define DMA_MODE_ADMA1 2 +#define DMA_MODE_ADMA2 3 +#define DMA_MODE_ADMA2_64 4 +#define DMA_MODE_AUTO -1 + +#define USE_DMA(sd) ((bool)((sd->sd_dma_mode > 0) ? TRUE : FALSE)) + +/* SDIO Host Control Register DMA Mode Definitions */ +#define SDIOH_SDMA_MODE 0 +#define SDIOH_ADMA1_MODE 1 +#define SDIOH_ADMA2_MODE 2 +#define SDIOH_ADMA2_64_MODE 3 + +#define ADMA2_ATTRIBUTE_VALID (1 << 0) /* ADMA Descriptor line valid */ +#define ADMA2_ATTRIBUTE_END (1 << 1) /* End of Descriptor */ +#define ADMA2_ATTRIBUTE_INT (1 << 2) /* Interrupt when line is done */ +#define ADMA2_ATTRIBUTE_ACT_NOP (0 << 4) /* Skip current line, go to next. */ +#define ADMA2_ATTRIBUTE_ACT_RSV (1 << 4) /* Same as NOP */ +#define ADMA1_ATTRIBUTE_ACT_SET (1 << 4) /* ADMA1 Only - set transfer length */ +#define ADMA2_ATTRIBUTE_ACT_TRAN (2 << 4) /* Transfer Data of one descriptor line. */ +#define ADMA2_ATTRIBUTE_ACT_LINK (3 << 4) /* Link Descriptor */ + +/* ADMA2 Descriptor Table Entry for 32-bit Address */ +typedef struct adma2_dscr_32b { + uint32 len_attr; + uint32 phys_addr; +} adma2_dscr_32b_t; + +/* ADMA1 Descriptor Table Entry */ +typedef struct adma1_dscr { + uint32 phys_addr_attr; +} adma1_dscr_t; + +/************************************************************ + * Internal interfaces: per-port references into bcmsdstd.c + */ + +/* Global message bits */ +extern uint sd_msglevel; + +/* OS-independent interrupt handler */ +extern bool check_client_intr(sdioh_info_t *sd); + +/* Core interrupt enable/disable of device interrupts */ +extern void sdstd_devintr_on(sdioh_info_t *sd); +extern void sdstd_devintr_off(sdioh_info_t *sd); + +/* Enable/disable interrupts for local controller events */ +extern void sdstd_intrs_on(sdioh_info_t *sd, uint16 norm, uint16 err); +extern void sdstd_intrs_off(sdioh_info_t *sd, uint16 norm, uint16 err); + +/* Wait for specified interrupt and error bits to be set */ +extern void sdstd_spinbits(sdioh_info_t *sd, uint16 norm, uint16 err); + + +/************************************************************** + * Internal interfaces: bcmsdstd.c references to per-port code + */ + +/* Register mapping routines */ +extern uint32 *sdstd_reg_map(osl_t *osh, int32 addr, int size); +extern void sdstd_reg_unmap(osl_t *osh, int32 addr, int size); + +/* Interrupt (de)registration routines */ +extern int sdstd_register_irq(sdioh_info_t *sd, uint irq); +extern void sdstd_free_irq(uint irq, sdioh_info_t *sd); + +/* OS-specific interrupt wrappers (atomic interrupt enable/disable) */ +extern void sdstd_lock(sdioh_info_t *sd); +extern void sdstd_unlock(sdioh_info_t *sd); + +/* OS-specific wait-for-interrupt-or-status */ +extern uint16 sdstd_waitbits(sdioh_info_t *sd, uint16 norm, uint16 err, bool yield); diff --git a/drivers/net/wireless/bcm4329/include/bcmspi.h b/drivers/net/wireless/bcm4329/include/bcmspi.h new file mode 100644 index 0000000000000000000000000000000000000000..2e2bc935716fb9257484c5701386b37ecbb73e70 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmspi.h @@ -0,0 +1,36 @@ +/* + * Broadcom SPI Low-Level Hardware Driver API + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmspi.h,v 13.3.10.2 2008/06/30 21:09:40 Exp $ + */ + +extern void spi_devintr_off(sdioh_info_t *sd); +extern void spi_devintr_on(sdioh_info_t *sd); +extern bool spi_start_clock(sdioh_info_t *sd, uint16 new_sd_divisor); +extern bool spi_controller_highspeed_mode(sdioh_info_t *sd, bool hsmode); +extern bool spi_check_client_intr(sdioh_info_t *sd, int *is_dev_intr); +extern bool spi_hw_attach(sdioh_info_t *sd); +extern bool spi_hw_detach(sdioh_info_t *sd); +extern void spi_sendrecv(sdioh_info_t *sd, uint8 *msg_out, uint8 *msg_in, int msglen); +extern void spi_spinbits(sdioh_info_t *sd); +extern void spi_waitbits(sdioh_info_t *sd, bool yield); diff --git a/drivers/net/wireless/bcm4329/include/bcmspibrcm.h b/drivers/net/wireless/bcm4329/include/bcmspibrcm.h new file mode 100644 index 0000000000000000000000000000000000000000..9dce878d11e2576ffac751c9cb6687fcea98459e --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmspibrcm.h @@ -0,0 +1,134 @@ +/* + * SD-SPI Protocol Conversion - BCMSDH->gSPI Translation 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.h,v 1.4.4.1.4.3.6.1 2008/09/27 17:03:25 Exp $ + */ + +/* global msglevel for debug messages - bitvals come from sdiovar.h */ + +#define sd_err(x) +#define sd_trace(x) +#define sd_info(x) +#define sd_debug(x) +#define sd_data(x) +#define sd_ctrl(x) + +#define sd_log(x) + +#define SDIOH_ASSERT(exp) \ + do { if (!(exp)) \ + printf("!!!ASSERT fail: file %s lines %d", __FILE__, __LINE__); \ + } while (0) + +#define BLOCK_SIZE_F1 64 +#define BLOCK_SIZE_F2 2048 +#define BLOCK_SIZE_F3 2048 + +/* internal return code */ +#define SUCCESS 0 +#undef ERROR +#define ERROR 1 +#define ERROR_UF 2 +#define ERROR_OF 3 + +/* private bus modes */ +#define SDIOH_MODE_SPI 0 + +#define USE_BLOCKMODE 0x2 /* Block mode can be single block or multi */ +#define USE_MULTIBLOCK 0x4 + +struct sdioh_info { + uint cfg_bar; /* pci cfg address for bar */ + uint32 caps; /* cached value of capabilities reg */ + void *bar0; /* BAR0 for PCI Device */ + osl_t *osh; /* osh handler */ + void *controller; /* Pointer to SPI Controller's private data struct */ + + uint lockcount; /* nest count of spi_lock() calls */ + bool client_intr_enabled; /* interrupt connnected flag */ + bool intr_handler_valid; /* client driver interrupt handler valid */ + sdioh_cb_fn_t intr_handler; /* registered interrupt handler */ + void *intr_handler_arg; /* argument to call interrupt handler */ + bool initialized; /* card initialized */ + uint32 target_dev; /* Target device ID */ + uint32 intmask; /* Current active interrupts */ + void *sdos_info; /* Pointer to per-OS private data */ + + uint32 controller_type; /* Host controller type */ + uint8 version; /* Host Controller Spec Compliance Version */ + uint irq; /* Client irq */ + uint32 intrcount; /* Client interrupts */ + uint32 local_intrcount; /* Controller interrupts */ + bool host_init_done; /* Controller initted */ + bool card_init_done; /* Client SDIO interface initted */ + bool polled_mode; /* polling for command completion */ + + bool sd_use_dma; /* DMA on CMD53 */ + bool sd_blockmode; /* sd_blockmode == FALSE => 64 Byte Cmd 53s. */ + /* Must be on for sd_multiblock to be effective */ + bool use_client_ints; /* If this is false, make sure to restore */ + /* polling hack in wl_linux.c:wl_timer() */ + int adapter_slot; /* Maybe dealing with multiple slots/controllers */ + int sd_mode; /* SD1/SD4/SPI */ + int client_block_size[SPI_MAX_IOFUNCS]; /* Blocksize */ + uint32 data_xfer_count; /* Current transfer */ + uint16 card_rca; /* Current Address */ + uint8 num_funcs; /* Supported funcs on client */ + uint32 card_dstatus; /* 32bit device status */ + uint32 com_cis_ptr; + uint32 func_cis_ptr[SPI_MAX_IOFUNCS]; + void *dma_buf; + ulong dma_phys; + int r_cnt; /* rx count */ + int t_cnt; /* tx_count */ + uint32 wordlen; /* host processor 16/32bits */ + uint32 prev_fun; + uint32 chip; + uint32 chiprev; + bool resp_delay_all; + bool dwordmode; + + struct spierrstats_t spierrstats; +}; + +/************************************************************ + * Internal interfaces: per-port references into bcmspibrcm.c + */ + +/* Global message bits */ +extern uint sd_msglevel; + +/************************************************************** + * Internal interfaces: bcmspibrcm.c references to per-port code + */ + +/* Interrupt (de)registration routines */ +extern int spi_register_irq(sdioh_info_t *sd, uint irq); +extern void spi_free_irq(uint irq, sdioh_info_t *sd); + +/* OS-specific interrupt wrappers (atomic interrupt enable/disable) */ +extern void spi_lock(sdioh_info_t *sd); +extern void spi_unlock(sdioh_info_t *sd); + +/* Allocate/init/free per-OS private data */ +extern int spi_osinit(sdioh_info_t *sd); +extern void spi_osfree(sdioh_info_t *sd); + +#define SPI_RW_FLAG_M BITFIELD_MASK(1) /* Bit [31] - R/W Command Bit */ +#define SPI_RW_FLAG_S 31 +#define SPI_ACCESS_M BITFIELD_MASK(1) /* Bit [30] - Fixed/Incr Access */ +#define SPI_ACCESS_S 30 +#define SPI_FUNCTION_M BITFIELD_MASK(2) /* Bit [29:28] - Function Number */ +#define SPI_FUNCTION_S 28 +#define SPI_REG_ADDR_M BITFIELD_MASK(17) /* Bit [27:11] - Address */ +#define SPI_REG_ADDR_S 11 +#define SPI_LEN_M BITFIELD_MASK(11) /* Bit [10:0] - Packet length */ +#define SPI_LEN_S 0 diff --git a/drivers/net/wireless/bcm4329/include/bcmutils.h b/drivers/net/wireless/bcm4329/include/bcmutils.h new file mode 100644 index 0000000000000000000000000000000000000000..f85ed351d663f10a25e34030b54b43954790121f --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmutils.h @@ -0,0 +1,637 @@ +/* + * Misc useful os-independent macros and functions. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: bcmutils.h,v 13.184.4.6.2.1.18.25 2010/04/26 06:05:24 Exp $ + */ + + +#ifndef _bcmutils_h_ +#define _bcmutils_h_ + +#ifdef __cplusplus +extern "C" { +#endif + + +#define _BCM_U 0x01 +#define _BCM_L 0x02 +#define _BCM_D 0x04 +#define _BCM_C 0x08 +#define _BCM_P 0x10 +#define _BCM_S 0x20 +#define _BCM_X 0x40 +#define _BCM_SP 0x80 + +extern const unsigned char bcm_ctype[]; +#define bcm_ismask(x) (bcm_ctype[(int)(unsigned char)(x)]) + +#define bcm_isalnum(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L|_BCM_D)) != 0) +#define bcm_isalpha(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L)) != 0) +#define bcm_iscntrl(c) ((bcm_ismask(c)&(_BCM_C)) != 0) +#define bcm_isdigit(c) ((bcm_ismask(c)&(_BCM_D)) != 0) +#define bcm_isgraph(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D)) != 0) +#define bcm_islower(c) ((bcm_ismask(c)&(_BCM_L)) != 0) +#define bcm_isprint(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D|_BCM_SP)) != 0) +#define bcm_ispunct(c) ((bcm_ismask(c)&(_BCM_P)) != 0) +#define bcm_isspace(c) ((bcm_ismask(c)&(_BCM_S)) != 0) +#define bcm_isupper(c) ((bcm_ismask(c)&(_BCM_U)) != 0) +#define bcm_isxdigit(c) ((bcm_ismask(c)&(_BCM_D|_BCM_X)) != 0) +#define bcm_tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c)) +#define bcm_toupper(c) (bcm_islower((c)) ? ((c) + 'A' - 'a') : (c)) + + + +struct bcmstrbuf { + char *buf; + unsigned int size; + char *origbuf; + unsigned int origsize; +}; + + +#ifdef BCMDRIVER +#include + +#define GPIO_PIN_NOTDEFINED 0x20 + + +#define SPINWAIT(exp, us) { \ + uint countdown = (us) + 9; \ + while ((exp) && (countdown >= 10)) {\ + OSL_DELAY(10); \ + countdown -= 10; \ + } \ +} + + + +#ifndef PKTQ_LEN_DEFAULT +#define PKTQ_LEN_DEFAULT 128 +#endif +#ifndef PKTQ_MAX_PREC +#define PKTQ_MAX_PREC 16 +#endif + +typedef struct pktq_prec { + void *head; + void *tail; + uint16 len; + uint16 max; +} pktq_prec_t; + + + +struct pktq { + uint16 num_prec; + uint16 hi_prec; + uint16 max; + uint16 len; + + struct pktq_prec q[PKTQ_MAX_PREC]; +}; + + +struct spktq { + uint16 num_prec; + uint16 hi_prec; + uint16 max; + uint16 len; + + struct pktq_prec q[1]; +}; + +#define PKTQ_PREC_ITER(pq, prec) for (prec = (pq)->num_prec - 1; prec >= 0; prec--) + + + + +struct ether_addr; + +extern int ether_isbcast(const void *ea); +extern int ether_isnulladdr(const void *ea); + + + +#define pktq_psetmax(pq, prec, _max) ((pq)->q[prec].max = (_max)) +#define pktq_plen(pq, prec) ((pq)->q[prec].len) +#define pktq_pavail(pq, prec) ((pq)->q[prec].max - (pq)->q[prec].len) +#define pktq_pfull(pq, prec) ((pq)->q[prec].len >= (pq)->q[prec].max) +#define pktq_pempty(pq, prec) ((pq)->q[prec].len == 0) + +#define pktq_ppeek(pq, prec) ((pq)->q[prec].head) +#define pktq_ppeek_tail(pq, prec) ((pq)->q[prec].tail) + +extern void *pktq_penq(struct pktq *pq, int prec, void *p); +extern void *pktq_penq_head(struct pktq *pq, int prec, void *p); +extern void *pktq_pdeq(struct pktq *pq, int prec); +extern void *pktq_pdeq_tail(struct pktq *pq, int prec); + +extern bool pktq_pdel(struct pktq *pq, void *p, int prec); + + +extern void pktq_pflush(osl_t *osh, struct pktq *pq, int prec, bool dir); + +extern void pktq_flush(osl_t *osh, struct pktq *pq, bool dir); + + + +extern int pktq_mlen(struct pktq *pq, uint prec_bmp); +extern void *pktq_mdeq(struct pktq *pq, uint prec_bmp, int *prec_out); + + + +#define pktq_len(pq) ((int)(pq)->len) +#define pktq_max(pq) ((int)(pq)->max) +#define pktq_avail(pq) ((int)((pq)->max - (pq)->len)) +#define pktq_full(pq) ((pq)->len >= (pq)->max) +#define pktq_empty(pq) ((pq)->len == 0) + + +#define pktenq(pq, p) pktq_penq(((struct pktq *)pq), 0, (p)) +#define pktenq_head(pq, p) pktq_penq_head(((struct pktq *)pq), 0, (p)) +#define pktdeq(pq) pktq_pdeq(((struct pktq *)pq), 0) +#define pktdeq_tail(pq) pktq_pdeq_tail(((struct pktq *)pq), 0) +#define pktqinit(pq, len) pktq_init(((struct pktq *)pq), 1, len) + +extern void pktq_init(struct pktq *pq, int num_prec, int max_len); + +extern void *pktq_deq(struct pktq *pq, int *prec_out); +extern void *pktq_deq_tail(struct pktq *pq, int *prec_out); +extern void *pktq_peek(struct pktq *pq, int *prec_out); +extern void *pktq_peek_tail(struct pktq *pq, int *prec_out); + + + +extern uint pktcopy(osl_t *osh, void *p, uint offset, int len, uchar *buf); +extern uint pktfrombuf(osl_t *osh, void *p, uint offset, int len, uchar *buf); +extern uint pkttotlen(osl_t *osh, void *p); +extern void *pktlast(osl_t *osh, void *p); +extern uint pktsegcnt(osl_t *osh, void *p); + + +extern uint pktsetprio(void *pkt, bool update_vtag); +#define PKTPRIO_VDSCP 0x100 +#define PKTPRIO_VLAN 0x200 +#define PKTPRIO_UPD 0x400 +#define PKTPRIO_DSCP 0x800 + + +extern int bcm_atoi(char *s); +extern ulong bcm_strtoul(char *cp, char **endp, uint base); +extern char *bcmstrstr(char *haystack, char *needle); +extern char *bcmstrcat(char *dest, const char *src); +extern char *bcmstrncat(char *dest, const char *src, uint size); +extern ulong wchar2ascii(char *abuf, ushort *wbuf, ushort wbuflen, ulong abuflen); +char* bcmstrtok(char **string, const char *delimiters, char *tokdelim); +int bcmstricmp(const char *s1, const char *s2); +int bcmstrnicmp(const char* s1, const char* s2, int cnt); + + + +extern char *bcm_ether_ntoa(const struct ether_addr *ea, char *buf); +extern int bcm_ether_atoe(char *p, struct ether_addr *ea); + + +struct ipv4_addr; +extern char *bcm_ip_ntoa(struct ipv4_addr *ia, char *buf); + + +extern void bcm_mdelay(uint ms); + +extern char *getvar(char *vars, const char *name); +extern int getintvar(char *vars, const char *name); +extern uint getgpiopin(char *vars, char *pin_name, uint def_pin); +#define bcm_perf_enable() +#define bcmstats(fmt) +#define bcmlog(fmt, a1, a2) +#define bcmdumplog(buf, size) *buf = '\0' +#define bcmdumplogent(buf, idx) -1 + +#define bcmtslog(tstamp, fmt, a1, a2) +#define bcmprinttslogs() +#define bcmprinttstamp(us) + + + + +typedef struct bcm_iovar { + const char *name; + uint16 varid; + uint16 flags; + uint16 type; + uint16 minlen; +} bcm_iovar_t; + + + + +#define IOV_GET 0 +#define IOV_SET 1 + + +#define IOV_GVAL(id) ((id)*2) +#define IOV_SVAL(id) (((id)*2)+IOV_SET) +#define IOV_ISSET(actionid) ((actionid & IOV_SET) == IOV_SET) + + + +extern const bcm_iovar_t *bcm_iovar_lookup(const bcm_iovar_t *table, const char *name); +extern int bcm_iovar_lencheck(const bcm_iovar_t *table, void *arg, int len, bool set); + +#endif + + +#define IOVT_VOID 0 +#define IOVT_BOOL 1 +#define IOVT_INT8 2 +#define IOVT_UINT8 3 +#define IOVT_INT16 4 +#define IOVT_UINT16 5 +#define IOVT_INT32 6 +#define IOVT_UINT32 7 +#define IOVT_BUFFER 8 +#define BCM_IOVT_VALID(type) (((unsigned int)(type)) <= IOVT_BUFFER) + + +#define BCM_IOV_TYPE_INIT { \ + "void", \ + "bool", \ + "int8", \ + "uint8", \ + "int16", \ + "uint16", \ + "int32", \ + "uint32", \ + "buffer", \ + "" } + +#define BCM_IOVT_IS_INT(type) (\ + (type == IOVT_BOOL) || \ + (type == IOVT_INT8) || \ + (type == IOVT_UINT8) || \ + (type == IOVT_INT16) || \ + (type == IOVT_UINT16) || \ + (type == IOVT_INT32) || \ + (type == IOVT_UINT32)) + + + +#define BCME_STRLEN 64 +#define VALID_BCMERROR(e) ((e <= 0) && (e >= BCME_LAST)) + + + + +#define BCME_OK 0 +#define BCME_ERROR -1 +#define BCME_BADARG -2 +#define BCME_BADOPTION -3 +#define BCME_NOTUP -4 +#define BCME_NOTDOWN -5 +#define BCME_NOTAP -6 +#define BCME_NOTSTA -7 +#define BCME_BADKEYIDX -8 +#define BCME_RADIOOFF -9 +#define BCME_NOTBANDLOCKED -10 +#define BCME_NOCLK -11 +#define BCME_BADRATESET -12 +#define BCME_BADBAND -13 +#define BCME_BUFTOOSHORT -14 +#define BCME_BUFTOOLONG -15 +#define BCME_BUSY -16 +#define BCME_NOTASSOCIATED -17 +#define BCME_BADSSIDLEN -18 +#define BCME_OUTOFRANGECHAN -19 +#define BCME_BADCHAN -20 +#define BCME_BADADDR -21 +#define BCME_NORESOURCE -22 +#define BCME_UNSUPPORTED -23 +#define BCME_BADLEN -24 +#define BCME_NOTREADY -25 +#define BCME_EPERM -26 +#define BCME_NOMEM -27 +#define BCME_ASSOCIATED -28 +#define BCME_RANGE -29 +#define BCME_NOTFOUND -30 +#define BCME_WME_NOT_ENABLED -31 +#define BCME_TSPEC_NOTFOUND -32 +#define BCME_ACM_NOTSUPPORTED -33 +#define BCME_NOT_WME_ASSOCIATION -34 +#define BCME_SDIO_ERROR -35 +#define BCME_DONGLE_DOWN -36 +#define BCME_VERSION -37 +#define BCME_TXFAIL -38 +#define BCME_RXFAIL -39 +#define BCME_NODEVICE -40 +#define BCME_UNFINISHED -41 +#define BCME_LAST BCME_UNFINISHED + + +#define BCMERRSTRINGTABLE { \ + "OK", \ + "Undefined error", \ + "Bad Argument", \ + "Bad Option", \ + "Not up", \ + "Not down", \ + "Not AP", \ + "Not STA", \ + "Bad Key Index", \ + "Radio Off", \ + "Not band locked", \ + "No clock", \ + "Bad Rate valueset", \ + "Bad Band", \ + "Buffer too short", \ + "Buffer too long", \ + "Busy", \ + "Not Associated", \ + "Bad SSID len", \ + "Out of Range Channel", \ + "Bad Channel", \ + "Bad Address", \ + "Not Enough Resources", \ + "Unsupported", \ + "Bad length", \ + "Not Ready", \ + "Not Permitted", \ + "No Memory", \ + "Associated", \ + "Not In Range", \ + "Not Found", \ + "WME Not Enabled", \ + "TSPEC Not Found", \ + "ACM Not Supported", \ + "Not WME Association", \ + "SDIO Bus Error", \ + "Dongle Not Accessible", \ + "Incorrect version", \ + "TX Failure", \ + "RX Failure", \ + "Device Not Present", \ + "Command not finished", \ +} + +#ifndef ABS +#define ABS(a) (((a) < 0)?-(a):(a)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b))?(a):(b)) +#endif + +#define CEIL(x, y) (((x) + ((y)-1)) / (y)) +#define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y)) +#define ISALIGNED(a, x) (((a) & ((x)-1)) == 0) +#define ALIGN_ADDR(addr, boundary) (void *)(((uintptr)(addr) + (boundary) - 1) \ + & ~((boundary) - 1)) +#define ISPOWEROF2(x) ((((x)-1)&(x)) == 0) +#define VALID_MASK(mask) !((mask) & ((mask) + 1)) +#ifndef OFFSETOF +#define OFFSETOF(type, member) ((uint)(uintptr)&((type *)0)->member) +#endif +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) +#endif + + +#ifndef setbit +#ifndef NBBY +#define NBBY 8 +#endif +#define setbit(a, i) (((uint8 *)a)[(i)/NBBY] |= 1<<((i)%NBBY)) +#define clrbit(a, i) (((uint8 *)a)[(i)/NBBY] &= ~(1<<((i)%NBBY))) +#define isset(a, i) (((const uint8 *)a)[(i)/NBBY] & (1<<((i)%NBBY))) +#define isclr(a, i) ((((const uint8 *)a)[(i)/NBBY] & (1<<((i)%NBBY))) == 0) +#endif + +#define NBITS(type) (sizeof(type) * 8) +#define NBITVAL(nbits) (1 << (nbits)) +#define MAXBITVAL(nbits) ((1 << (nbits)) - 1) +#define NBITMASK(nbits) MAXBITVAL(nbits) +#define MAXNBVAL(nbyte) MAXBITVAL((nbyte) * 8) + + +#define MUX(pred, true, false) ((pred) ? (true) : (false)) + + +#define MODDEC(x, bound) MUX((x) == 0, (bound) - 1, (x) - 1) +#define MODINC(x, bound) MUX((x) == (bound) - 1, 0, (x) + 1) + + +#define MODDEC_POW2(x, bound) (((x) - 1) & ((bound) - 1)) +#define MODINC_POW2(x, bound) (((x) + 1) & ((bound) - 1)) + + +#define MODADD(x, y, bound) \ + MUX((x) + (y) >= (bound), (x) + (y) - (bound), (x) + (y)) +#define MODSUB(x, y, bound) \ + MUX(((int)(x)) - ((int)(y)) < 0, (x) - (y) + (bound), (x) - (y)) + + +#define MODADD_POW2(x, y, bound) (((x) + (y)) & ((bound) - 1)) +#define MODSUB_POW2(x, y, bound) (((x) - (y)) & ((bound) - 1)) + + +#define CRC8_INIT_VALUE 0xff +#define CRC8_GOOD_VALUE 0x9f +#define CRC16_INIT_VALUE 0xffff +#define CRC16_GOOD_VALUE 0xf0b8 +#define CRC32_INIT_VALUE 0xffffffff +#define CRC32_GOOD_VALUE 0xdebb20e3 + + +typedef struct bcm_bit_desc { + uint32 bit; + const char* name; +} bcm_bit_desc_t; + + +typedef struct bcm_tlv { + uint8 id; + uint8 len; + uint8 data[1]; +} bcm_tlv_t; + + +#define bcm_valid_tlv(elt, buflen) ((buflen) >= 2 && (int)(buflen) >= (int)(2 + (elt)->len)) + + +#define ETHER_ADDR_STR_LEN 18 + + +#ifdef IL_BIGENDIAN +static INLINE uint32 +load32_ua(uint8 *a) +{ + return ((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3]); +} + +static INLINE void +store32_ua(uint8 *a, uint32 v) +{ + a[0] = (v >> 24) & 0xff; + a[1] = (v >> 16) & 0xff; + a[2] = (v >> 8) & 0xff; + a[3] = v & 0xff; +} + +static INLINE uint16 +load16_ua(uint8 *a) +{ + return ((a[0] << 8) | a[1]); +} + +static INLINE void +store16_ua(uint8 *a, uint16 v) +{ + a[0] = (v >> 8) & 0xff; + a[1] = v & 0xff; +} + +#else + +static INLINE uint32 +load32_ua(uint8 *a) +{ + return ((a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0]); +} + +static INLINE void +store32_ua(uint8 *a, uint32 v) +{ + a[3] = (v >> 24) & 0xff; + a[2] = (v >> 16) & 0xff; + a[1] = (v >> 8) & 0xff; + a[0] = v & 0xff; +} + +static INLINE uint16 +load16_ua(uint8 *a) +{ + return ((a[1] << 8) | a[0]); +} + +static INLINE void +store16_ua(uint8 *a, uint16 v) +{ + a[1] = (v >> 8) & 0xff; + a[0] = v & 0xff; +} + +#endif + + + +static INLINE void +xor_128bit_block(const uint8 *src1, const uint8 *src2, uint8 *dst) +{ + if ( +#ifdef __i386__ + 1 || +#endif + (((uintptr)src1 | (uintptr)src2 | (uintptr)dst) & 3) == 0) { + + + ((uint32 *)dst)[0] = ((uint32 *)src1)[0] ^ ((uint32 *)src2)[0]; + ((uint32 *)dst)[1] = ((uint32 *)src1)[1] ^ ((uint32 *)src2)[1]; + ((uint32 *)dst)[2] = ((uint32 *)src1)[2] ^ ((uint32 *)src2)[2]; + ((uint32 *)dst)[3] = ((uint32 *)src1)[3] ^ ((uint32 *)src2)[3]; + } else { + + int k; + for (k = 0; k < 16; k++) + dst[k] = src1[k] ^ src2[k]; + } +} + + + +extern uint8 hndcrc8(uint8 *p, uint nbytes, uint8 crc); +extern uint16 hndcrc16(uint8 *p, uint nbytes, uint16 crc); +extern uint32 hndcrc32(uint8 *p, uint nbytes, uint32 crc); + +#if defined(DHD_DEBUG) || defined(WLMSG_PRHDRS) || defined(WLMSG_PRPKT) || \ + defined(WLMSG_ASSOC) +extern int bcm_format_flags(const bcm_bit_desc_t *bd, uint32 flags, char* buf, int len); +extern int bcm_format_hex(char *str, const void *bytes, int len); +extern void prhex(const char *msg, uchar *buf, uint len); +#endif +extern char *bcm_brev_str(uint32 brev, char *buf); +extern void printbig(char *buf); + + +extern bcm_tlv_t *bcm_next_tlv(bcm_tlv_t *elt, int *buflen); +extern bcm_tlv_t *bcm_parse_tlvs(void *buf, int buflen, uint key); +extern bcm_tlv_t *bcm_parse_ordered_tlvs(void *buf, int buflen, uint key); + + +extern const char *bcmerrorstr(int bcmerror); + + +typedef uint32 mbool; +#define mboolset(mb, bit) ((mb) |= (bit)) +#define mboolclr(mb, bit) ((mb) &= ~(bit)) +#define mboolisset(mb, bit) (((mb) & (bit)) != 0) +#define mboolmaskset(mb, mask, val) ((mb) = (((mb) & ~(mask)) | (val))) + + +extern uint16 bcm_qdbm_to_mw(uint8 qdbm); +extern uint8 bcm_mw_to_qdbm(uint16 mw); + + +struct fielddesc { + const char *nameandfmt; + uint32 offset; + uint32 len; +}; + +extern void bcm_binit(struct bcmstrbuf *b, char *buf, uint size); +extern int bcm_bprintf(struct bcmstrbuf *b, const char *fmt, ...); +extern void bcm_inc_bytes(uchar *num, int num_bytes, uint8 amount); +extern int bcm_cmp_bytes(uchar *arg1, uchar *arg2, uint8 nbytes); +extern void bcm_print_bytes(char *name, const uchar *cdata, int len); + +typedef uint32 (*bcmutl_rdreg_rtn)(void *arg0, uint arg1, uint32 offset); +extern uint bcmdumpfields(bcmutl_rdreg_rtn func_ptr, void *arg0, uint arg1, struct fielddesc *str, + char *buf, uint32 bufsize); + +extern uint bcm_mkiovar(char *name, char *data, uint datalen, char *buf, uint len); +extern uint bcm_bitcount(uint8 *bitmap, uint bytelength); + +#if defined(WLTINYDUMP) || defined(WLMSG_INFORM) || defined(WLMSG_ASSOC) || \ + defined(WLMSG_PRPKT) || defined(WLMSG_WSEC) +extern int bcm_format_ssid(char* buf, const uchar ssid[], uint ssid_len); +#endif + + +#define SSID_FMT_BUF_LEN ((4 * DOT11_MAX_SSID_LEN) + 1) + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/drivers/net/wireless/bcm4329/include/bcmwifi.h b/drivers/net/wireless/bcm4329/include/bcmwifi.h new file mode 100644 index 0000000000000000000000000000000000000000..038aedcdb3c8c22ea2eef1d1cbe0e361f28ca0de --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/bcmwifi.h @@ -0,0 +1,154 @@ +/* + * Misc utility routines for WL and Apps + * This header file housing the define and function prototype use by + * both the wl driver, tools & Apps. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: bcmwifi.h,v 1.15.30.4 2010/03/10 20:10:52 Exp $ + */ + + +#ifndef _bcmwifi_h_ +#define _bcmwifi_h_ + + + +typedef uint16 chanspec_t; + + +#define CH_UPPER_SB 0x01 +#define CH_LOWER_SB 0x02 +#define CH_EWA_VALID 0x04 +#define CH_20MHZ_APART 4 +#define CH_10MHZ_APART 2 +#define CH_5MHZ_APART 1 +#define CH_MAX_2G_CHANNEL 14 +#define WLC_MAX_2G_CHANNEL CH_MAX_2G_CHANNEL +#define MAXCHANNEL 224 + +#define WL_CHANSPEC_CHAN_MASK 0x00ff +#define WL_CHANSPEC_CHAN_SHIFT 0 + +#define WL_CHANSPEC_CTL_SB_MASK 0x0300 +#define WL_CHANSPEC_CTL_SB_SHIFT 8 +#define WL_CHANSPEC_CTL_SB_LOWER 0x0100 +#define WL_CHANSPEC_CTL_SB_UPPER 0x0200 +#define WL_CHANSPEC_CTL_SB_NONE 0x0300 + +#define WL_CHANSPEC_BW_MASK 0x0C00 +#define WL_CHANSPEC_BW_SHIFT 10 +#define WL_CHANSPEC_BW_10 0x0400 +#define WL_CHANSPEC_BW_20 0x0800 +#define WL_CHANSPEC_BW_40 0x0C00 + +#define WL_CHANSPEC_BAND_MASK 0xf000 +#define WL_CHANSPEC_BAND_SHIFT 12 +#define WL_CHANSPEC_BAND_5G 0x1000 +#define WL_CHANSPEC_BAND_2G 0x2000 +#define INVCHANSPEC 255 + + +#define WF_CHAN_FACTOR_2_4_G 4814 +#define WF_CHAN_FACTOR_5_G 10000 +#define WF_CHAN_FACTOR_4_G 8000 + + +#define LOWER_20_SB(channel) ((channel > CH_10MHZ_APART) ? (channel - CH_10MHZ_APART) : 0) +#define UPPER_20_SB(channel) ((channel < (MAXCHANNEL - CH_10MHZ_APART)) ? \ + (channel + CH_10MHZ_APART) : 0) +#define CHSPEC_WLCBANDUNIT(chspec) (CHSPEC_IS5G(chspec) ? BAND_5G_INDEX : BAND_2G_INDEX) +#define CH20MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_20 | \ + WL_CHANSPEC_CTL_SB_NONE | (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define NEXT_20MHZ_CHAN(channel) ((channel < (MAXCHANNEL - CH_20MHZ_APART)) ? \ + (channel + CH_20MHZ_APART) : 0) +#define CH40MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | WL_CHANSPEC_BW_40 | \ + ((channel) <= CH_MAX_2G_CHANNEL ? WL_CHANSPEC_BAND_2G : \ + WL_CHANSPEC_BAND_5G)) +#define CHSPEC_CHANNEL(chspec) ((uint8)(chspec & WL_CHANSPEC_CHAN_MASK)) +#define CHSPEC_BAND(chspec) (chspec & WL_CHANSPEC_BAND_MASK) + +#ifdef WL20MHZ_ONLY + +#define CHSPEC_CTL_SB(chspec) WL_CHANSPEC_CTL_SB_NONE +#define CHSPEC_BW(chspec) WL_CHANSPEC_BW_20 +#define CHSPEC_IS10(chspec) 0 +#define CHSPEC_IS20(chspec) 1 +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) 0 +#endif + +#else + +#define CHSPEC_CTL_SB(chspec) (chspec & WL_CHANSPEC_CTL_SB_MASK) +#define CHSPEC_BW(chspec) (chspec & WL_CHANSPEC_BW_MASK) +#define CHSPEC_IS10(chspec) ((chspec & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_10) +#define CHSPEC_IS20(chspec) ((chspec & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_20) +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40) +#endif + +#endif + +#define CHSPEC_IS5G(chspec) ((chspec & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G) +#define CHSPEC_IS2G(chspec) ((chspec & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_2G) +#define CHSPEC_SB_NONE(chspec) ((chspec & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_NONE) +#define CHSPEC_SB_UPPER(chspec) ((chspec & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_UPPER) +#define CHSPEC_SB_LOWER(chspec) ((chspec & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_LOWER) +#define CHSPEC_CTL_CHAN(chspec) ((CHSPEC_SB_LOWER(chspec)) ? \ + (LOWER_20_SB(((chspec) & WL_CHANSPEC_CHAN_MASK))) : \ + (UPPER_20_SB(((chspec) & WL_CHANSPEC_CHAN_MASK)))) + +#define CHSPEC2WLC_BAND(chspec) (CHSPEC_IS5G((chspec))? WLC_BAND_5G: WLC_BAND_2G) + +#define CHANSPEC_STR_LEN 8 + + +#define WLC_MAXRATE 108 +#define WLC_RATE_1M 2 +#define WLC_RATE_2M 4 +#define WLC_RATE_5M5 11 +#define WLC_RATE_11M 22 +#define WLC_RATE_6M 12 +#define WLC_RATE_9M 18 +#define WLC_RATE_12M 24 +#define WLC_RATE_18M 36 +#define WLC_RATE_24M 48 +#define WLC_RATE_36M 72 +#define WLC_RATE_48M 96 +#define WLC_RATE_54M 108 + +#define WLC_2G_25MHZ_OFFSET 5 + + +extern char * wf_chspec_ntoa(chanspec_t chspec, char *buf); + + +extern chanspec_t wf_chspec_aton(char *a); + + +extern int wf_mhz2channel(uint freq, uint start_factor); + + +extern int wf_channel2mhz(uint channel, uint start_factor); + +#endif diff --git a/drivers/net/wireless/bcm4329/include/dhdioctl.h b/drivers/net/wireless/bcm4329/include/dhdioctl.h new file mode 100644 index 0000000000000000000000000000000000000000..980a1430100362616d75bd93e0b67dd92e329e8d --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/dhdioctl.h @@ -0,0 +1,123 @@ +/* + * Definitions for ioctls to access DHD iovars. + * Based on wlioctl.h (for Broadcom 802.11abg driver). + * (Moves towards generic ioctls for BCM drivers/iovars.) + * + * Definitions subject to change without notice. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: dhdioctl.h,v 13.7.8.1.4.1.16.5 2010/05/21 21:49:38 Exp $ + */ + +#ifndef _dhdioctl_h_ +#define _dhdioctl_h_ + +#include + + +/* require default structure packing */ +#define BWL_DEFAULT_PACKING +#include + + +/* Linux network driver ioctl encoding */ +typedef struct dhd_ioctl { + uint cmd; /* common ioctl definition */ + void *buf; /* pointer to user buffer */ + uint len; /* length of user buffer */ + bool set; /* get or set request (optional) */ + uint used; /* bytes read or written (optional) */ + uint needed; /* bytes needed (optional) */ + uint driver; /* to identify target driver */ +} dhd_ioctl_t; + +/* per-driver magic numbers */ +#define DHD_IOCTL_MAGIC 0x00444944 + +/* bump this number if you change the ioctl interface */ +#define DHD_IOCTL_VERSION 1 + +#define DHD_IOCTL_MAXLEN 8192 /* max length ioctl buffer required */ +#define DHD_IOCTL_SMLEN 256 /* "small" length ioctl buffer required */ + +/* common ioctl definitions */ +#define DHD_GET_MAGIC 0 +#define DHD_GET_VERSION 1 +#define DHD_GET_VAR 2 +#define DHD_SET_VAR 3 + +/* message levels */ +#define DHD_ERROR_VAL 0x0001 +#define DHD_TRACE_VAL 0x0002 +#define DHD_INFO_VAL 0x0004 +#define DHD_DATA_VAL 0x0008 +#define DHD_CTL_VAL 0x0010 +#define DHD_TIMER_VAL 0x0020 +#define DHD_HDRS_VAL 0x0040 +#define DHD_BYTES_VAL 0x0080 +#define DHD_INTR_VAL 0x0100 +#define DHD_LOG_VAL 0x0200 +#define DHD_GLOM_VAL 0x0400 +#define DHD_EVENT_VAL 0x0800 +#define DHD_BTA_VAL 0x1000 +#define DHD_ISCAN_VAL 0x2000 + +#ifdef SDTEST +/* For pktgen iovar */ +typedef struct dhd_pktgen { + uint version; /* To allow structure change tracking */ + uint freq; /* Max ticks between tx/rx attempts */ + uint count; /* Test packets to send/rcv each attempt */ + uint print; /* Print counts every attempts */ + uint total; /* Total packets (or bursts) */ + uint minlen; /* Minimum length of packets to send */ + uint maxlen; /* Maximum length of packets to send */ + uint numsent; /* Count of test packets sent */ + uint numrcvd; /* Count of test packets received */ + uint numfail; /* Count of test send failures */ + uint mode; /* Test mode (type of test packets) */ + uint stop; /* Stop after this many tx failures */ +} dhd_pktgen_t; + +/* Version in case structure changes */ +#define DHD_PKTGEN_VERSION 2 + +/* Type of test packets to use */ +#define DHD_PKTGEN_ECHO 1 /* Send echo requests */ +#define DHD_PKTGEN_SEND 2 /* Send discard packets */ +#define DHD_PKTGEN_RXBURST 3 /* Request dongle send N packets */ +#define DHD_PKTGEN_RECV 4 /* Continuous rx from continuous tx dongle */ +#endif /* SDTEST */ + +/* Enter idle immediately (no timeout) */ +#define DHD_IDLE_IMMEDIATE (-1) + +/* Values for idleclock iovar: other values are the sd_divisor to use when idle */ +#define DHD_IDLE_ACTIVE 0 /* Do not request any SD clock change when idle */ +#define DHD_IDLE_STOP (-1) /* Request SD clock be stopped (and use SD1 mode) */ + + +/* require default structure packing */ +#include + + +#endif /* _dhdioctl_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/epivers.h b/drivers/net/wireless/bcm4329/include/epivers.h new file mode 100644 index 0000000000000000000000000000000000000000..cd66a9501cb61b214bd09493182c43771bbf724f --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/epivers.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: epivers.h.in,v 13.25 2005/10/28 18:35:33 Exp $ + * +*/ + + +#ifndef _epivers_h_ +#define _epivers_h_ + +#define EPI_MAJOR_VERSION 4 + +#define EPI_MINOR_VERSION 218 + +#define EPI_RC_NUMBER 248 + +#define EPI_INCREMENTAL_NUMBER 23 + +#define EPI_BUILD_NUMBER 0 + +#define EPI_VERSION 4, 218, 248, 23 + +#define EPI_VERSION_NUM 0x04daf817 + + +#define EPI_VERSION_STR "4.218.248.23" +#define EPI_ROUTER_VERSION_STR "4.219.248.23" + +#endif diff --git a/drivers/net/wireless/bcm4329/include/hndpmu.h b/drivers/net/wireless/bcm4329/include/hndpmu.h new file mode 100644 index 0000000000000000000000000000000000000000..e829b3df2d0bf62bee8e6c34bf6a13070300c5e9 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/hndpmu.h @@ -0,0 +1,34 @@ +/* + * HND SiliconBackplane PMU support. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: hndpmu.h,v 13.14.4.3.4.3.8.7 2010/04/09 13:20:51 Exp $ + */ + +#ifndef _hndpmu_h_ +#define _hndpmu_h_ + + +extern void si_pmu_otp_power(si_t *sih, osl_t *osh, bool on); +extern void si_sdiod_drive_strength_init(si_t *sih, osl_t *osh, uint32 drivestrength); + +#endif /* _hndpmu_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/hndrte_armtrap.h b/drivers/net/wireless/bcm4329/include/hndrte_armtrap.h new file mode 100644 index 0000000000000000000000000000000000000000..ca3281b6d901b40c4a998e19e63ccfd8875b1068 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/hndrte_armtrap.h @@ -0,0 +1,88 @@ +/* + * HNDRTE arm trap handling. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: hndrte_armtrap.h,v 13.3.196.2 2010/07/15 19:06:11 Exp $ + */ + +#ifndef _hndrte_armtrap_h +#define _hndrte_armtrap_h + + +/* ARM trap handling */ + +/* Trap types defined by ARM (see arminc.h) */ + +/* Trap locations in lo memory */ +#define TRAP_STRIDE 4 +#define FIRST_TRAP TR_RST +#define LAST_TRAP (TR_FIQ * TRAP_STRIDE) + +#if defined(__ARM_ARCH_4T__) +#define MAX_TRAP_TYPE (TR_FIQ + 1) +#elif defined(__ARM_ARCH_7M__) +#define MAX_TRAP_TYPE (TR_ISR + ARMCM3_NUMINTS) +#endif /* __ARM_ARCH_7M__ */ + +/* The trap structure is defined here as offsets for assembly */ +#define TR_TYPE 0x00 +#define TR_EPC 0x04 +#define TR_CPSR 0x08 +#define TR_SPSR 0x0c +#define TR_REGS 0x10 +#define TR_REG(n) (TR_REGS + (n) * 4) +#define TR_SP TR_REG(13) +#define TR_LR TR_REG(14) +#define TR_PC TR_REG(15) + +#define TRAP_T_SIZE 80 + +#ifndef _LANGUAGE_ASSEMBLY + +#include + +typedef struct _trap_struct { + uint32 type; + uint32 epc; + uint32 cpsr; + uint32 spsr; + uint32 r0; + uint32 r1; + uint32 r2; + uint32 r3; + uint32 r4; + uint32 r5; + uint32 r6; + uint32 r7; + uint32 r8; + uint32 r9; + uint32 r10; + uint32 r11; + uint32 r12; + uint32 r13; + uint32 r14; + uint32 pc; +} trap_t; + +#endif /* !_LANGUAGE_ASSEMBLY */ + +#endif /* _hndrte_armtrap_h */ diff --git a/drivers/net/wireless/bcm4329/include/hndrte_cons.h b/drivers/net/wireless/bcm4329/include/hndrte_cons.h new file mode 100644 index 0000000000000000000000000000000000000000..a42417478a16b44e055d3e1bf840770f09b58c60 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/hndrte_cons.h @@ -0,0 +1,63 @@ +/* + * Console support for hndrte. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: hndrte_cons.h,v 13.1.2.4 2010/07/15 19:06:11 Exp $ + */ + +#include + +#define CBUF_LEN (128) + +#define LOG_BUF_LEN 1024 + +typedef struct { + uint32 buf; /* Can't be pointer on (64-bit) hosts */ + uint buf_size; + uint idx; + char *_buf_compat; /* Redundant pointer for backward compat. */ +} hndrte_log_t; + +typedef struct { + /* Virtual UART + * When there is no UART (e.g. Quickturn), the host should write a complete + * input line directly into cbuf and then write the length into vcons_in. + * This may also be used when there is a real UART (at risk of conflicting with + * the real UART). vcons_out is currently unused. + */ + volatile uint vcons_in; + volatile uint vcons_out; + + /* Output (logging) buffer + * Console output is written to a ring buffer log_buf at index log_idx. + * The host may read the output when it sees log_idx advance. + * Output will be lost if the output wraps around faster than the host polls. + */ + hndrte_log_t log; + + /* Console input line buffer + * Characters are read one at a time into cbuf until is received, then + * the buffer is processed as a command line. Also used for virtual UART. + */ + uint cbuf_idx; + char cbuf[CBUF_LEN]; +} hndrte_cons_t; diff --git a/drivers/net/wireless/bcm4329/include/hndsoc.h b/drivers/net/wireless/bcm4329/include/hndsoc.h new file mode 100644 index 0000000000000000000000000000000000000000..35424175f55e04f271ef709aeae4084134e495bc --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/hndsoc.h @@ -0,0 +1,195 @@ +/* + * Broadcom HND chip & on-chip-interconnect-related definitions. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: hndsoc.h,v 13.3.10.3 2008/08/06 03:43:25 Exp $ + */ + +#ifndef _HNDSOC_H +#define _HNDSOC_H + +/* Include the soci specific files */ +#include +#include + +/* + * SOC Interconnect Address Map. + * All regions may not exist on all chips. + */ +#define SI_SDRAM_BASE 0x00000000 /* Physical SDRAM */ +#define SI_PCI_MEM 0x08000000 /* Host Mode sb2pcitranslation0 (64 MB) */ +#define SI_PCI_MEM_SZ (64 * 1024 * 1024) +#define SI_PCI_CFG 0x0c000000 /* Host Mode sb2pcitranslation1 (64 MB) */ +#define SI_SDRAM_SWAPPED 0x10000000 /* Byteswapped Physical SDRAM */ + +#define SI_ENUM_BASE 0x18000000 /* Enumeration space base */ +#define SI_CORE_SIZE 0x1000 /* each core gets 4Kbytes for registers */ +#ifndef SI_MAXCORES +#define SI_MAXCORES 16 /* Max cores (this is arbitrary, for software + * convenience and could be changed if we + * make any larger chips + */ +#endif + +#define SI_FASTRAM 0x19000000 /* On-chip RAM on chips that also have DDR */ + +#define SI_FLASH2 0x1c000000 /* Flash Region 2 (region 1 shadowed here) */ +#define SI_FLASH2_SZ 0x02000000 /* Size of Flash Region 2 */ +#define SI_ARMCM3_ROM 0x1e000000 /* ARM Cortex-M3 ROM */ +#define SI_FLASH1 0x1fc00000 /* MIPS Flash Region 1 */ +#define SI_FLASH1_SZ 0x00400000 /* MIPS Size of Flash Region 1 */ +#define SI_ARM7S_ROM 0x20000000 /* ARM7TDMI-S ROM */ +#define SI_ARMCM3_SRAM2 0x60000000 /* ARM Cortex-M3 SRAM Region 2 */ +#define SI_ARM7S_SRAM2 0x80000000 /* ARM7TDMI-S SRAM Region 2 */ +#define SI_ARM_FLASH1 0xffff0000 /* ARM Flash Region 1 */ +#define SI_ARM_FLASH1_SZ 0x00010000 /* ARM Size of Flash Region 1 */ + +#define SI_PCI_DMA 0x40000000 /* Client Mode sb2pcitranslation2 (1 GB) */ +#define SI_PCI_DMA2 0x80000000 /* Client Mode sb2pcitranslation2 (1 GB) */ +#define SI_PCI_DMA_SZ 0x40000000 /* Client Mode sb2pcitranslation2 size in bytes */ +#define SI_PCIE_DMA_L32 0x00000000 /* PCIE Client Mode sb2pcitranslation2 + * (2 ZettaBytes), low 32 bits + */ +#define SI_PCIE_DMA_H32 0x80000000 /* PCIE Client Mode sb2pcitranslation2 + * (2 ZettaBytes), high 32 bits + */ + +/* core codes */ +#define NODEV_CORE_ID 0x700 /* Invalid coreid */ +#define CC_CORE_ID 0x800 /* chipcommon core */ +#define ILINE20_CORE_ID 0x801 /* iline20 core */ +#define SRAM_CORE_ID 0x802 /* sram core */ +#define SDRAM_CORE_ID 0x803 /* sdram core */ +#define PCI_CORE_ID 0x804 /* pci core */ +#define MIPS_CORE_ID 0x805 /* mips core */ +#define ENET_CORE_ID 0x806 /* enet mac core */ +#define CODEC_CORE_ID 0x807 /* v90 codec core */ +#define USB_CORE_ID 0x808 /* usb 1.1 host/device core */ +#define ADSL_CORE_ID 0x809 /* ADSL core */ +#define ILINE100_CORE_ID 0x80a /* iline100 core */ +#define IPSEC_CORE_ID 0x80b /* ipsec core */ +#define UTOPIA_CORE_ID 0x80c /* utopia core */ +#define PCMCIA_CORE_ID 0x80d /* pcmcia core */ +#define SOCRAM_CORE_ID 0x80e /* internal memory core */ +#define MEMC_CORE_ID 0x80f /* memc sdram core */ +#define OFDM_CORE_ID 0x810 /* OFDM phy core */ +#define EXTIF_CORE_ID 0x811 /* external interface core */ +#define D11_CORE_ID 0x812 /* 802.11 MAC core */ +#define APHY_CORE_ID 0x813 /* 802.11a phy core */ +#define BPHY_CORE_ID 0x814 /* 802.11b phy core */ +#define GPHY_CORE_ID 0x815 /* 802.11g phy core */ +#define MIPS33_CORE_ID 0x816 /* mips3302 core */ +#define USB11H_CORE_ID 0x817 /* usb 1.1 host core */ +#define USB11D_CORE_ID 0x818 /* usb 1.1 device core */ +#define USB20H_CORE_ID 0x819 /* usb 2.0 host core */ +#define USB20D_CORE_ID 0x81a /* usb 2.0 device core */ +#define SDIOH_CORE_ID 0x81b /* sdio host core */ +#define ROBO_CORE_ID 0x81c /* roboswitch core */ +#define ATA100_CORE_ID 0x81d /* parallel ATA core */ +#define SATAXOR_CORE_ID 0x81e /* serial ATA & XOR DMA core */ +#define GIGETH_CORE_ID 0x81f /* gigabit ethernet core */ +#define PCIE_CORE_ID 0x820 /* pci express core */ +#define NPHY_CORE_ID 0x821 /* 802.11n 2x2 phy core */ +#define SRAMC_CORE_ID 0x822 /* SRAM controller core */ +#define MINIMAC_CORE_ID 0x823 /* MINI MAC/phy core */ +#define ARM11_CORE_ID 0x824 /* ARM 1176 core */ +#define ARM7S_CORE_ID 0x825 /* ARM7tdmi-s core */ +#define LPPHY_CORE_ID 0x826 /* 802.11a/b/g phy core */ +#define PMU_CORE_ID 0x827 /* PMU core */ +#define SSNPHY_CORE_ID 0x828 /* 802.11n single-stream phy core */ +#define SDIOD_CORE_ID 0x829 /* SDIO device core */ +#define ARMCM3_CORE_ID 0x82a /* ARM Cortex M3 core */ +#define QNPHY_CORE_ID 0x82b /* 802.11n 4x4 phy core */ +#define MIPS74K_CORE_ID 0x82c /* mips 74k core */ +#define GMAC_CORE_ID 0x82d /* Gigabit MAC core */ +#define DMEMC_CORE_ID 0x82e /* DDR1/2 memory controller core */ +#define PCIERC_CORE_ID 0x82f /* PCIE Root Complex core */ +#define OCP_CORE_ID 0x830 /* OCP2OCP bridge core */ +#define SC_CORE_ID 0x831 /* shared common core */ +#define AHB_CORE_ID 0x832 /* OCP2AHB bridge core */ +#define SPIH_CORE_ID 0x833 /* SPI host core */ +#define I2S_CORE_ID 0x834 /* I2S core */ +#define OOB_ROUTER_CORE_ID 0x367 /* OOB router core ID */ +#define DEF_AI_COMP 0xfff /* Default component, in ai chips it maps all + * unused address ranges + */ + +/* There are TWO constants on all HND chips: SI_ENUM_BASE above, + * and chipcommon being the first core: + */ +#define SI_CC_IDX 0 + +/* SOC Interconnect types (aka chip types) */ +#define SOCI_SB 0 +#define SOCI_AI 1 + +/* Common core control flags */ +#define SICF_BIST_EN 0x8000 +#define SICF_PME_EN 0x4000 +#define SICF_CORE_BITS 0x3ffc +#define SICF_FGC 0x0002 +#define SICF_CLOCK_EN 0x0001 + +/* Common core status flags */ +#define SISF_BIST_DONE 0x8000 +#define SISF_BIST_ERROR 0x4000 +#define SISF_GATED_CLK 0x2000 +#define SISF_DMA64 0x1000 +#define SISF_CORE_BITS 0x0fff + +/* A register that is common to all cores to + * communicate w/PMU regarding clock control. + */ +#define SI_CLK_CTL_ST 0x1e0 /* clock control and status */ + +/* clk_ctl_st register */ +#define CCS_FORCEALP 0x00000001 /* force ALP request */ +#define CCS_FORCEHT 0x00000002 /* force HT request */ +#define CCS_FORCEILP 0x00000004 /* force ILP request */ +#define CCS_ALPAREQ 0x00000008 /* ALP Avail Request */ +#define CCS_HTAREQ 0x00000010 /* HT Avail Request */ +#define CCS_FORCEHWREQOFF 0x00000020 /* Force HW Clock Request Off */ +#define CCS_ALPAVAIL 0x00010000 /* ALP is available */ +#define CCS_HTAVAIL 0x00020000 /* HT is available */ +#define CCS0_HTAVAIL 0x00010000 /* HT avail in chipc and pcmcia on 4328a0 */ +#define CCS0_ALPAVAIL 0x00020000 /* ALP avail in chipc and pcmcia on 4328a0 */ + +/* Not really related to SOC Interconnect, but a couple of software + * conventions for the use the flash space: + */ + +/* Minumum amount of flash we support */ +#define FLASH_MIN 0x00020000 /* Minimum flash size */ + +/* A boot/binary may have an embedded block that describes its size */ +#define BISZ_OFFSET 0x3e0 /* At this offset into the binary */ +#define BISZ_MAGIC 0x4249535a /* Marked with this value: 'BISZ' */ +#define BISZ_MAGIC_IDX 0 /* Word 0: magic */ +#define BISZ_TXTST_IDX 1 /* 1: text start */ +#define BISZ_TXTEND_IDX 2 /* 2: text end */ +#define BISZ_DATAST_IDX 3 /* 3: data start */ +#define BISZ_DATAEND_IDX 4 /* 4: data end */ +#define BISZ_BSSST_IDX 5 /* 5: bss start */ +#define BISZ_BSSEND_IDX 6 /* 6: bss end */ +#define BISZ_SIZE 7 /* descriptor size in 32-bit intergers */ + +#endif /* _HNDSOC_H */ diff --git a/drivers/net/wireless/bcm4329/include/linux_osl.h b/drivers/net/wireless/bcm4329/include/linux_osl.h new file mode 100644 index 0000000000000000000000000000000000000000..b059c2adb17dbdd4d1ac764d29f5125a8de123ae --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/linux_osl.h @@ -0,0 +1,322 @@ +/* + * Linux OS Independent Layer + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: linux_osl.h,v 13.131.30.8 2010/04/26 05:42:18 Exp $ + */ + + +#ifndef _linux_osl_h_ +#define _linux_osl_h_ + +#include + + +#include + + +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if GCC_VERSION > 30100 +#define ASSERT(exp) do {} while (0) +#else + +#define ASSERT(exp) +#endif +#endif + + +#define OSL_DELAY(usec) osl_delay(usec) +extern void osl_delay(uint usec); + + + +#define OSL_PCMCIA_READ_ATTR(osh, offset, buf, size) \ + osl_pcmcia_read_attr((osh), (offset), (buf), (size)) +#define OSL_PCMCIA_WRITE_ATTR(osh, offset, buf, size) \ + osl_pcmcia_write_attr((osh), (offset), (buf), (size)) +extern void osl_pcmcia_read_attr(osl_t *osh, uint offset, void *buf, int size); +extern void osl_pcmcia_write_attr(osl_t *osh, uint offset, void *buf, int size); + + +#define OSL_PCI_READ_CONFIG(osh, offset, size) \ + osl_pci_read_config((osh), (offset), (size)) +#define OSL_PCI_WRITE_CONFIG(osh, offset, size, val) \ + osl_pci_write_config((osh), (offset), (size), (val)) +extern uint32 osl_pci_read_config(osl_t *osh, uint offset, uint size); +extern void osl_pci_write_config(osl_t *osh, uint offset, uint size, uint val); + + +#define OSL_PCI_BUS(osh) osl_pci_bus(osh) +#define OSL_PCI_SLOT(osh) osl_pci_slot(osh) +extern uint osl_pci_bus(osl_t *osh); +extern uint osl_pci_slot(osl_t *osh); + + +typedef struct { + bool pkttag; + uint pktalloced; + bool mmbus; + pktfree_cb_fn_t tx_fn; + void *tx_ctx; +} osl_pubinfo_t; + + +extern osl_t *osl_attach(void *pdev, uint bustype, bool pkttag); +extern void osl_detach(osl_t *osh); + +#define PKTFREESETCB(osh, _tx_fn, _tx_ctx) \ + do { \ + ((osl_pubinfo_t*)osh)->tx_fn = _tx_fn; \ + ((osl_pubinfo_t*)osh)->tx_ctx = _tx_ctx; \ + } while (0) + + +#define BUS_SWAP32(v) (v) + + +#define MALLOC(osh, size) osl_malloc((osh), (size)) +#define MFREE(osh, addr, size) osl_mfree((osh), (addr), (size)) +#define MALLOCED(osh) osl_malloced((osh)) + + +#define MALLOC_FAILED(osh) osl_malloc_failed((osh)) + +extern void *osl_malloc(osl_t *osh, uint size); +extern void osl_mfree(osl_t *osh, void *addr, uint size); +extern uint osl_malloced(osl_t *osh); +extern uint osl_malloc_failed(osl_t *osh); + + +#define DMA_CONSISTENT_ALIGN PAGE_SIZE +#define DMA_ALLOC_CONSISTENT(osh, size, pap, dmah, alignbits) \ + osl_dma_alloc_consistent((osh), (size), (pap)) +#define DMA_FREE_CONSISTENT(osh, va, size, pa, dmah) \ + osl_dma_free_consistent((osh), (void*)(va), (size), (pa)) +extern void *osl_dma_alloc_consistent(osl_t *osh, uint size, ulong *pap); +extern void osl_dma_free_consistent(osl_t *osh, void *va, uint size, ulong pa); + + +#define DMA_TX 1 +#define DMA_RX 2 + + +#define DMA_MAP(osh, va, size, direction, p, dmah) \ + osl_dma_map((osh), (va), (size), (direction)) +#define DMA_UNMAP(osh, pa, size, direction, p, dmah) \ + osl_dma_unmap((osh), (pa), (size), (direction)) +extern uint osl_dma_map(osl_t *osh, void *va, uint size, int direction); +extern void osl_dma_unmap(osl_t *osh, uint pa, uint size, int direction); + + +#define OSL_DMADDRWIDTH(osh, addrwidth) do {} while (0) + + +#include +#define OSL_WRITE_REG(osh, r, v) (bcmsdh_reg_write(NULL, (uintptr)(r), sizeof(*(r)), (v))) +#define OSL_READ_REG(osh, r) (bcmsdh_reg_read(NULL, (uintptr)(r), sizeof(*(r)))) + +#define SELECT_BUS_WRITE(osh, mmap_op, bus_op) if (((osl_pubinfo_t*)(osh))->mmbus) \ + mmap_op else bus_op +#define SELECT_BUS_READ(osh, mmap_op, bus_op) (((osl_pubinfo_t*)(osh))->mmbus) ? \ + mmap_op : bus_op + + + + +#ifndef printf +#define printf(fmt, args...) printk(fmt, ## args) +#endif +#include +#include + + +#ifndef IL_BIGENDIAN +#define R_REG(osh, r) (\ + SELECT_BUS_READ(osh, sizeof(*(r)) == sizeof(uint8) ? readb((volatile uint8*)(r)) : \ + sizeof(*(r)) == sizeof(uint16) ? readw((volatile uint16*)(r)) : \ + readl((volatile uint32*)(r)), OSL_READ_REG(osh, r)) \ +) +#define W_REG(osh, r, v) do { \ + SELECT_BUS_WRITE(osh, \ + switch (sizeof(*(r))) { \ + case sizeof(uint8): writeb((uint8)(v), (volatile uint8*)(r)); break; \ + case sizeof(uint16): writew((uint16)(v), (volatile uint16*)(r)); break; \ + case sizeof(uint32): writel((uint32)(v), (volatile uint32*)(r)); break; \ + }, \ + (OSL_WRITE_REG(osh, r, v))); \ + } while (0) +#else +#define R_REG(osh, r) (\ + SELECT_BUS_READ(osh, \ + ({ \ + __typeof(*(r)) __osl_v; \ + switch (sizeof(*(r))) { \ + case sizeof(uint8): __osl_v = \ + readb((volatile uint8*)((uintptr)(r)^3)); break; \ + case sizeof(uint16): __osl_v = \ + readw((volatile uint16*)((uintptr)(r)^2)); break; \ + case sizeof(uint32): __osl_v = \ + readl((volatile uint32*)(r)); break; \ + } \ + __osl_v; \ + }), \ + OSL_READ_REG(osh, r)) \ +) +#define W_REG(osh, r, v) do { \ + SELECT_BUS_WRITE(osh, \ + switch (sizeof(*(r))) { \ + case sizeof(uint8): writeb((uint8)(v), \ + (volatile uint8*)((uintptr)(r)^3)); break; \ + case sizeof(uint16): writew((uint16)(v), \ + (volatile uint16*)((uintptr)(r)^2)); break; \ + case sizeof(uint32): writel((uint32)(v), \ + (volatile uint32*)(r)); break; \ + }, \ + (OSL_WRITE_REG(osh, r, v))); \ + } while (0) +#endif + +#define AND_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) & (v)) +#define OR_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) | (v)) + + +#define bcopy(src, dst, len) memcpy((dst), (src), (len)) +#define bcmp(b1, b2, len) memcmp((b1), (b2), (len)) +#define bzero(b, len) memset((b), '\0', (len)) + + +#define OSL_UNCACHED(va) ((void*)va) + + +#if defined(__i386__) +#define OSL_GETCYCLES(x) rdtscl((x)) +#else +#define OSL_GETCYCLES(x) ((x) = 0) +#endif + + +#define BUSPROBE(val, addr) ({ (val) = R_REG(NULL, (addr)); 0; }) + + +#if !defined(CONFIG_MMC_MSM7X00A) +#define REG_MAP(pa, size) ioremap_nocache((unsigned long)(pa), (unsigned long)(size)) +#else +#define REG_MAP(pa, size) (void *)(0) +#endif +#define REG_UNMAP(va) iounmap((va)) + + +#define R_SM(r) *(r) +#define W_SM(r, v) (*(r) = (v)) +#define BZERO_SM(r, len) memset((r), '\0', (len)) + + +#define PKTGET(osh, len, send) osl_pktget((osh), (len)) +#define PKTFREE(osh, skb, send) osl_pktfree((osh), (skb), (send)) +#ifdef DHD_USE_STATIC_BUF +#define PKTGET_STATIC(osh, len, send) osl_pktget_static((osh), (len)) +#define PKTFREE_STATIC(osh, skb, send) osl_pktfree_static((osh), (skb), (send)) +#endif +#define PKTDATA(osh, skb) (((struct sk_buff*)(skb))->data) +#define PKTLEN(osh, skb) (((struct sk_buff*)(skb))->len) +#define PKTHEADROOM(osh, skb) (PKTDATA(osh, skb)-(((struct sk_buff*)(skb))->head)) +#define PKTTAILROOM(osh, skb) ((((struct sk_buff*)(skb))->end)-(((struct sk_buff*)(skb))->tail)) +#define PKTNEXT(osh, skb) (((struct sk_buff*)(skb))->next) +#define PKTSETNEXT(osh, skb, x) (((struct sk_buff*)(skb))->next = (struct sk_buff*)(x)) +#define PKTSETLEN(osh, skb, len) __skb_trim((struct sk_buff*)(skb), (len)) +#define PKTPUSH(osh, skb, bytes) skb_push((struct sk_buff*)(skb), (bytes)) +#define PKTPULL(osh, skb, bytes) skb_pull((struct sk_buff*)(skb), (bytes)) +#define PKTDUP(osh, skb) osl_pktdup((osh), (skb)) +#define PKTTAG(skb) ((void*)(((struct sk_buff*)(skb))->cb)) +#define PKTALLOCED(osh) ((osl_pubinfo_t *)(osh))->pktalloced +#define PKTSETPOOL(osh, skb, x, y) do {} while (0) +#define PKTPOOL(osh, skb) FALSE +#define PKTPOOLLEN(osh, pktp) (0) +#define PKTPOOLAVAIL(osh, pktp) (0) +#define PKTPOOLADD(osh, pktp, p) BCME_ERROR +#define PKTPOOLGET(osh, pktp) NULL +#define PKTLIST_DUMP(osh, buf) + +extern void *osl_pktget(osl_t *osh, uint len); +extern void osl_pktfree(osl_t *osh, void *skb, bool send); +extern void *osl_pktget_static(osl_t *osh, uint len); +extern void osl_pktfree_static(osl_t *osh, void *skb, bool send); +extern void *osl_pktdup(osl_t *osh, void *skb); + + + +static INLINE void * +osl_pkt_frmnative(osl_pubinfo_t *osh, struct sk_buff *skb) +{ + struct sk_buff *nskb; + + if (osh->pkttag) + bzero((void*)skb->cb, OSL_PKTTAG_SZ); + + + for (nskb = skb; nskb; nskb = nskb->next) { + osh->pktalloced++; + } + + return (void *)skb; +} +#define PKTFRMNATIVE(osh, skb) osl_pkt_frmnative(((osl_pubinfo_t *)osh), (struct sk_buff*)(skb)) + + +static INLINE struct sk_buff * +osl_pkt_tonative(osl_pubinfo_t *osh, void *pkt) +{ + struct sk_buff *nskb; + + if (osh->pkttag) + bzero(((struct sk_buff*)pkt)->cb, OSL_PKTTAG_SZ); + + + for (nskb = (struct sk_buff *)pkt; nskb; nskb = nskb->next) { + osh->pktalloced--; + } + + return (struct sk_buff *)pkt; +} +#define PKTTONATIVE(osh, pkt) osl_pkt_tonative((osl_pubinfo_t *)(osh), (pkt)) + +#define PKTLINK(skb) (((struct sk_buff*)(skb))->prev) +#define PKTSETLINK(skb, x) (((struct sk_buff*)(skb))->prev = (struct sk_buff*)(x)) +#define PKTPRIO(skb) (((struct sk_buff*)(skb))->priority) +#define PKTSETPRIO(skb, x) (((struct sk_buff*)(skb))->priority = (x)) +#define PKTSUMNEEDED(skb) (((struct sk_buff*)(skb))->ip_summed == CHECKSUM_HW) +#define PKTSETSUMGOOD(skb, x) (((struct sk_buff*)(skb))->ip_summed = \ + ((x) ? CHECKSUM_UNNECESSARY : CHECKSUM_NONE)) + +#define PKTSHARED(skb) (((struct sk_buff*)(skb))->cloned) + + +#define OSL_ERROR(bcmerror) osl_error(bcmerror) +extern int osl_error(int bcmerror); + + +#define PKTBUFSZ 2048 + + +#define OSL_SYSUPTIME() ((uint32)jiffies * (1000 / HZ)) +#endif diff --git a/drivers/net/wireless/bcm4329/include/linuxver.h b/drivers/net/wireless/bcm4329/include/linuxver.h new file mode 100644 index 0000000000000000000000000000000000000000..6ed22658a72ba7e2311cb4ce21a23f70674ba574 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/linuxver.h @@ -0,0 +1,447 @@ +/* + * Linux-specific abstractions to gain some independence from linux kernel versions. + * Pave over some 2.2 versus 2.4 versus 2.6 kernel differences. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: linuxver.h,v 13.38.8.1.8.6 2010/04/29 05:00:46 Exp $ + */ + + +#ifndef _linuxver_h_ +#define _linuxver_h_ + +#include +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) +#include +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33)) +#include +#endif +#include + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 0)) + +#ifdef __UNDEF_NO_VERSION__ +#undef __NO_VERSION__ +#else +#define __NO_VERSION__ +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) +#define module_param(_name_, _type_, _perm_) MODULE_PARM(_name_, "i") +#define module_param_string(_name_, _string_, _size_, _perm_) \ + MODULE_PARM(_string_, "c" __MODULE_STRING(_size_)) +#endif + + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 9)) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)) +#undef IP_TOS +#endif +#include + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 41)) +#include +#else +#include +#ifndef work_struct +#define work_struct tq_struct +#endif +#ifndef INIT_WORK +#define INIT_WORK(_work, _func, _data) INIT_TQUEUE((_work), (_func), (_data)) +#endif +#ifndef schedule_work +#define schedule_work(_work) schedule_task((_work)) +#endif +#ifndef flush_scheduled_work +#define flush_scheduled_work() flush_scheduled_tasks() +#endif +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#define MY_INIT_WORK(_work, _func, _data) INIT_WORK(_work, _func) +#else +#define MY_INIT_WORK(_work, _func, _data) INIT_WORK(_work, _func, _data) +typedef void (*work_func_t)(void *work); +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + +#ifndef IRQ_NONE +typedef void irqreturn_t; +#define IRQ_NONE +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif +#else +typedef irqreturn_t(*FN_ISR) (int irq, void *dev_id, struct pt_regs *ptregs); +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#define IRQF_SHARED SA_SHIRQ +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 17) +#ifdef CONFIG_NET_RADIO +#define CONFIG_WIRELESS_EXT +#endif +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 67) +#ifndef SANDGATE2G +#define MOD_INC_USE_COUNT +#endif +#endif + + +#ifndef __exit +#define __exit +#endif +#ifndef __devexit +#define __devexit +#endif +#ifndef __devinit +#define __devinit __init +#endif +#ifndef __devinitdata +#define __devinitdata +#endif +#ifndef __devexit_p +#define __devexit_p(x) x +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)) + +#define pci_get_drvdata(dev) (dev)->sysdata +#define pci_set_drvdata(dev, value) (dev)->sysdata = (value) + + + +struct pci_device_id { + unsigned int vendor, device; + unsigned int subvendor, subdevice; + unsigned int class, class_mask; + unsigned long driver_data; +}; + +struct pci_driver { + struct list_head node; + char *name; + const struct pci_device_id *id_table; + int (*probe)(struct pci_dev *dev, + const struct pci_device_id *id); + void (*remove)(struct pci_dev *dev); + void (*suspend)(struct pci_dev *dev); + void (*resume)(struct pci_dev *dev); +}; + +#define MODULE_DEVICE_TABLE(type, name) +#define PCI_ANY_ID (~0) + + +#define pci_module_init pci_register_driver +extern int pci_register_driver(struct pci_driver *drv); +extern void pci_unregister_driver(struct pci_driver *drv); + +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)) +#define pci_module_init pci_register_driver +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 2, 18)) +#ifdef MODULE +#define module_init(x) int init_module(void) { return x(); } +#define module_exit(x) void cleanup_module(void) { x(); } +#else +#define module_init(x) __initcall(x); +#define module_exit(x) __exitcall(x); +#endif +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 48)) +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 13)) +#define pci_resource_start(dev, bar) ((dev)->base_address[(bar)]) +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 44)) +#define pci_resource_start(dev, bar) ((dev)->resource[(bar)].start) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 23)) +#define pci_enable_device(dev) do { } while (0) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 14)) +#define net_device device +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 42)) + + + +#ifndef PCI_DMA_TODEVICE +#define PCI_DMA_TODEVICE 1 +#define PCI_DMA_FROMDEVICE 2 +#endif + +typedef u32 dma_addr_t; + + +static inline int get_order(unsigned long size) +{ + int order; + + size = (size-1) >> (PAGE_SHIFT-1); + order = -1; + do { + size >>= 1; + order++; + } while (size); + return order; +} + +static inline void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, + dma_addr_t *dma_handle) +{ + void *ret; + int gfp = GFP_ATOMIC | GFP_DMA; + + ret = (void *)__get_free_pages(gfp, get_order(size)); + + if (ret != NULL) { + memset(ret, 0, size); + *dma_handle = virt_to_bus(ret); + } + return ret; +} +static inline void pci_free_consistent(struct pci_dev *hwdev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + free_pages((unsigned long)vaddr, get_order(size)); +} +#define pci_map_single(cookie, address, size, dir) virt_to_bus(address) +#define pci_unmap_single(cookie, address, size, dir) + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 3, 43)) + +#define dev_kfree_skb_any(a) dev_kfree_skb(a) +#define netif_down(dev) do { (dev)->start = 0; } while (0) + + +#ifndef _COMPAT_NETDEVICE_H + + + +#define dev_kfree_skb_irq(a) dev_kfree_skb(a) +#define netif_wake_queue(dev) \ + do { clear_bit(0, &(dev)->tbusy); mark_bh(NET_BH); } while (0) +#define netif_stop_queue(dev) set_bit(0, &(dev)->tbusy) + +static inline void netif_start_queue(struct net_device *dev) +{ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; +} + +#define netif_queue_stopped(dev) (dev)->tbusy +#define netif_running(dev) (dev)->start + +#endif + +#define netif_device_attach(dev) netif_start_queue(dev) +#define netif_device_detach(dev) netif_stop_queue(dev) + + +#define tasklet_struct tq_struct +static inline void tasklet_schedule(struct tasklet_struct *tasklet) +{ + queue_task(tasklet, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +static inline void tasklet_init(struct tasklet_struct *tasklet, + void (*func)(unsigned long), + unsigned long data) +{ + tasklet->next = NULL; + tasklet->sync = 0; + tasklet->routine = (void (*)(void *))func; + tasklet->data = (void *)data; +} +#define tasklet_kill(tasklet) { do {} while (0); } + + +#define del_timer_sync(timer) del_timer(timer) + +#else + +#define netif_down(dev) + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 3)) + + +#define PREPARE_TQUEUE(_tq, _routine, _data) \ + do { \ + (_tq)->routine = _routine; \ + (_tq)->data = _data; \ + } while (0) + + +#define INIT_TQUEUE(_tq, _routine, _data) \ + do { \ + INIT_LIST_HEAD(&(_tq)->list); \ + (_tq)->sync = 0; \ + PREPARE_TQUEUE((_tq), (_routine), (_data)); \ + } while (0) + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 6)) + + + +static inline int +pci_save_state(struct pci_dev *dev, u32 *buffer) +{ + int i; + if (buffer) { + for (i = 0; i < 16; i++) + pci_read_config_dword(dev, i * 4, &buffer[i]); + } + return 0; +} + +static inline int +pci_restore_state(struct pci_dev *dev, u32 *buffer) +{ + int i; + + if (buffer) { + for (i = 0; i < 16; i++) + pci_write_config_dword(dev, i * 4, buffer[i]); + } + + else { + for (i = 0; i < 6; i ++) + pci_write_config_dword(dev, + PCI_BASE_ADDRESS_0 + (i * 4), + pci_resource_start(dev, i)); + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + } + return 0; +} + +#endif + + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 19)) +#define read_c0_count() read_32bit_cp0_register(CP0_COUNT) +#endif + + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) +#ifndef SET_MODULE_OWNER +#define SET_MODULE_OWNER(dev) do {} while (0) +#define OLD_MOD_INC_USE_COUNT MOD_INC_USE_COUNT +#define OLD_MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT +#else +#define OLD_MOD_INC_USE_COUNT do {} while (0) +#define OLD_MOD_DEC_USE_COUNT do {} while (0) +#endif +#else +#ifndef SET_MODULE_OWNER +#define SET_MODULE_OWNER(dev) do {} while (0) +#endif +#ifndef MOD_INC_USE_COUNT +#define MOD_INC_USE_COUNT do {} while (0) +#endif +#ifndef MOD_DEC_USE_COUNT +#define MOD_DEC_USE_COUNT do {} while (0) +#endif +#define OLD_MOD_INC_USE_COUNT MOD_INC_USE_COUNT +#define OLD_MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT +#endif + +#ifndef SET_NETDEV_DEV +#define SET_NETDEV_DEV(net, pdev) do {} while (0) +#endif + +#ifndef HAVE_FREE_NETDEV +#define free_netdev(dev) kfree(dev) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) + +#define af_packet_priv data +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11) +#define DRV_SUSPEND_STATE_TYPE pm_message_t +#else +#define DRV_SUSPEND_STATE_TYPE uint32 +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#define CHECKSUM_HW CHECKSUM_PARTIAL +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) +#define KILL_PROC(pid, sig) \ +{ \ + struct task_struct *tsk; \ + tsk = pid_task(find_vpid(pid), PIDTYPE_PID); \ + if (tsk) send_sig(sig, tsk, 1); \ +} +#else +#define KILL_PROC(pid, sig) \ +{ \ + kill_proc(pid, sig, 1); \ +} +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) +#define netdev_priv(dev) dev->priv +#endif + +#endif diff --git a/drivers/net/wireless/bcm4329/include/miniopt.h b/drivers/net/wireless/bcm4329/include/miniopt.h new file mode 100644 index 0000000000000000000000000000000000000000..3667fb1e215b8507891af11b307fd2a8cfda55de --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/miniopt.h @@ -0,0 +1,77 @@ +/* + * Command line options parser. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: miniopt.h,v 1.1.6.2 2009/01/14 23:52:48 Exp $ + */ + + +#ifndef MINI_OPT_H +#define MINI_OPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---- Include Files ---------------------------------------------------- */ +/* ---- Constants and Types ---------------------------------------------- */ + +#define MINIOPT_MAXKEY 128 /* Max options */ +typedef struct miniopt { + + /* These are persistent after miniopt_init() */ + const char* name; /* name for prompt in error strings */ + const char* flags; /* option chars that take no args */ + bool longflags; /* long options may be flags */ + bool opt_end; /* at end of options (passed a "--") */ + + /* These are per-call to miniopt() */ + + int consumed; /* number of argv entries cosumed in + * the most recent call to miniopt() + */ + bool positional; + bool good_int; /* 'val' member is the result of a sucessful + * strtol conversion of the option value + */ + char opt; + char key[MINIOPT_MAXKEY]; + char* valstr; /* positional param, or value for the option, + * or null if the option had + * no accompanying value + */ + uint uval; /* strtol translation of valstr */ + int val; /* strtol translation of valstr */ +} miniopt_t; + +void miniopt_init(miniopt_t *t, const char* name, const char* flags, bool longflags); +int miniopt(miniopt_t *t, char **argv); + + +/* ---- Variable Externs ------------------------------------------------- */ +/* ---- Function Prototypes ---------------------------------------------- */ + + +#ifdef __cplusplus + } +#endif + +#endif /* MINI_OPT_H */ diff --git a/drivers/net/wireless/bcm4329/include/msgtrace.h b/drivers/net/wireless/bcm4329/include/msgtrace.h new file mode 100644 index 0000000000000000000000000000000000000000..1479086dba3e73125ca975ea5d1c47a520c7745d --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/msgtrace.h @@ -0,0 +1,72 @@ +/* + * Trace messages sent over HBUS + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: msgtrace.h,v 1.1.2.4 2009/01/27 04:09:40 Exp $ + */ + +#ifndef _MSGTRACE_H +#define _MSGTRACE_H + +#ifndef _TYPEDEFS_H_ +#include +#endif + + +/* This marks the start of a packed structure section. */ +#include + +#define MSGTRACE_VERSION 1 + +/* Message trace header */ +typedef BWL_PRE_PACKED_STRUCT struct msgtrace_hdr { + uint8 version; + uint8 spare; + uint16 len; /* Len of the trace */ + uint32 seqnum; /* Sequence number of message. Useful if the messsage has been lost + * because of DMA error or a bus reset (ex: SDIO Func2) + */ + uint32 discarded_bytes; /* Number of discarded bytes because of trace overflow */ + uint32 discarded_printf; /* Number of discarded printf because of trace overflow */ +} BWL_POST_PACKED_STRUCT msgtrace_hdr_t; + +#define MSGTRACE_HDRLEN sizeof(msgtrace_hdr_t) + +/* The hbus driver generates traces when sending a trace message. This causes endless traces. + * This flag must be set to TRUE in any hbus traces. The flag is reset in the function msgtrace_put. + * This prevents endless traces but generates hasardous lost of traces only in bus device code. + * It is recommendat to set this flag in macro SD_TRACE but not in SD_ERROR for avoiding missing + * hbus error traces. hbus error trace should not generates endless traces. + */ +extern bool msgtrace_hbus_trace; + +typedef void (*msgtrace_func_send_t)(void *hdl1, void *hdl2, uint8 *hdr, + uint16 hdrlen, uint8 *buf, uint16 buflen); + +extern void msgtrace_sent(void); +extern void msgtrace_put(char *buf, int count); +extern void msgtrace_init(void *hdl1, void *hdl2, msgtrace_func_send_t func_send); + +/* This marks the end of a packed structure section. */ +#include + +#endif /* _MSGTRACE_H */ diff --git a/drivers/net/wireless/bcm4329/include/osl.h b/drivers/net/wireless/bcm4329/include/osl.h new file mode 100644 index 0000000000000000000000000000000000000000..5599e536eeeae428896374176c6573da08fffe41 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/osl.h @@ -0,0 +1,55 @@ +/* + * OS Abstraction Layer + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: osl.h,v 13.37.32.1 2008/11/20 00:51:15 Exp $ + */ + + +#ifndef _osl_h_ +#define _osl_h_ + + +typedef struct osl_info osl_t; +typedef struct osl_dmainfo osldma_t; + +#define OSL_PKTTAG_SZ 32 + + +typedef void (*pktfree_cb_fn_t)(void *ctx, void *pkt, unsigned int status); + +#include + + + + +#define SET_REG(osh, r, mask, val) W_REG((osh), (r), ((R_REG((osh), r) & ~(mask)) | (val))) + +#ifndef AND_REG +#define AND_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) & (v)) +#endif + +#ifndef OR_REG +#define OR_REG(osh, r, v) W_REG(osh, (r), R_REG(osh, r) | (v)) +#endif + + +#endif diff --git a/drivers/net/wireless/bcm4329/include/packed_section_end.h b/drivers/net/wireless/bcm4329/include/packed_section_end.h new file mode 100644 index 0000000000000000000000000000000000000000..5b61c18fcd08d72cbc324b061c35f037592a1783 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/packed_section_end.h @@ -0,0 +1,54 @@ +/* + * Declare directives for structure packing. No padding will be provided + * between the members of packed structures, and therefore, there is no + * guarantee that structure members will be aligned. + * + * Declaring packed structures is compiler specific. In order to handle all + * cases, packed structures should be delared as: + * + * #include + * + * typedef BWL_PRE_PACKED_STRUCT struct foobar_t { + * some_struct_members; + * } BWL_POST_PACKED_STRUCT foobar_t; + * + * #include + * + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: packed_section_end.h,v 1.1.6.3 2008/12/10 00:27:54 Exp $ + */ + + + + +#ifdef BWL_PACKED_SECTION + #undef BWL_PACKED_SECTION +#else + #error "BWL_PACKED_SECTION is NOT defined!" +#endif + + + + + +#undef BWL_PRE_PACKED_STRUCT +#undef BWL_POST_PACKED_STRUCT diff --git a/drivers/net/wireless/bcm4329/include/packed_section_start.h b/drivers/net/wireless/bcm4329/include/packed_section_start.h new file mode 100644 index 0000000000000000000000000000000000000000..cb93aa64079a159d6e97274d5b7d4fc21f7cc4b1 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/packed_section_start.h @@ -0,0 +1,61 @@ +/* + * Declare directives for structure packing. No padding will be provided + * between the members of packed structures, and therefore, there is no + * guarantee that structure members will be aligned. + * + * Declaring packed structures is compiler specific. In order to handle all + * cases, packed structures should be delared as: + * + * #include + * + * typedef BWL_PRE_PACKED_STRUCT struct foobar_t { + * some_struct_members; + * } BWL_POST_PACKED_STRUCT foobar_t; + * + * #include + * + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: packed_section_start.h,v 1.1.6.3 2008/12/10 00:27:54 Exp $ + */ + + + + +#ifdef BWL_PACKED_SECTION + #error "BWL_PACKED_SECTION is already defined!" +#else + #define BWL_PACKED_SECTION +#endif + + + + + +#if defined(__GNUC__) + #define BWL_PRE_PACKED_STRUCT + #define BWL_POST_PACKED_STRUCT __attribute__((packed)) +#elif defined(__CC_ARM) + #define BWL_PRE_PACKED_STRUCT __packed + #define BWL_POST_PACKED_STRUCT +#else + #error "Unknown compiler!" +#endif diff --git a/drivers/net/wireless/bcm4329/include/pcicfg.h b/drivers/net/wireless/bcm4329/include/pcicfg.h new file mode 100644 index 0000000000000000000000000000000000000000..898962c942a84cf0dbeb57b7a855d6aeae021b0a --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/pcicfg.h @@ -0,0 +1,52 @@ +/* + * pcicfg.h: PCI configuration constants and structures. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: pcicfg.h,v 1.41.12.3 2008/06/26 22:49:41 Exp $ + */ + + +#ifndef _h_pcicfg_ +#define _h_pcicfg_ + + +#define PCI_CFG_VID 0 +#define PCI_CFG_CMD 4 +#define PCI_CFG_REV 8 +#define PCI_CFG_BAR0 0x10 +#define PCI_CFG_BAR1 0x14 +#define PCI_BAR0_WIN 0x80 +#define PCI_INT_STATUS 0x90 +#define PCI_INT_MASK 0x94 + +#define PCIE_EXTCFG_OFFSET 0x100 +#define PCI_BAR0_PCIREGS_OFFSET (6 * 1024) +#define PCI_BAR0_PCISBR_OFFSET (4 * 1024) + +#define PCI_BAR0_WINSZ (16 * 1024) + + +#define PCI_16KB0_PCIREGS_OFFSET (8 * 1024) +#define PCI_16KB0_CCREGS_OFFSET (12 * 1024) +#define PCI_16KBB0_WINSZ (16 * 1024) + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/802.11.h b/drivers/net/wireless/bcm4329/include/proto/802.11.h new file mode 100644 index 0000000000000000000000000000000000000000..fd26317361da46a41beac9d359d5c8e0b01410d5 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/802.11.h @@ -0,0 +1,1433 @@ +/* + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * Fundamental types and constants relating to 802.11 + * + * $Id: 802.11.h,v 9.219.4.1.4.5.6.11 2010/02/09 13:23:26 Exp $ + */ + + +#ifndef _802_11_H_ +#define _802_11_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#ifndef _NET_ETHERNET_H_ +#include +#endif + +#include + + +#include + + +#define DOT11_TU_TO_US 1024 + + +#define DOT11_A3_HDR_LEN 24 +#define DOT11_A4_HDR_LEN 30 +#define DOT11_MAC_HDR_LEN DOT11_A3_HDR_LEN +#define DOT11_FCS_LEN 4 +#define DOT11_ICV_LEN 4 +#define DOT11_ICV_AES_LEN 8 +#define DOT11_QOS_LEN 2 +#define DOT11_HTC_LEN 4 + +#define DOT11_KEY_INDEX_SHIFT 6 +#define DOT11_IV_LEN 4 +#define DOT11_IV_TKIP_LEN 8 +#define DOT11_IV_AES_OCB_LEN 4 +#define DOT11_IV_AES_CCM_LEN 8 +#define DOT11_IV_MAX_LEN 8 + + +#define DOT11_MAX_MPDU_BODY_LEN 2304 + +#define DOT11_MAX_MPDU_LEN (DOT11_A4_HDR_LEN + \ + DOT11_QOS_LEN + \ + DOT11_IV_AES_CCM_LEN + \ + DOT11_MAX_MPDU_BODY_LEN + \ + DOT11_ICV_LEN + \ + DOT11_FCS_LEN) + +#define DOT11_MAX_SSID_LEN 32 + + +#define DOT11_DEFAULT_RTS_LEN 2347 +#define DOT11_MAX_RTS_LEN 2347 + + +#define DOT11_MIN_FRAG_LEN 256 +#define DOT11_MAX_FRAG_LEN 2346 +#define DOT11_DEFAULT_FRAG_LEN 2346 + + +#define DOT11_MIN_BEACON_PERIOD 1 +#define DOT11_MAX_BEACON_PERIOD 0xFFFF + + +#define DOT11_MIN_DTIM_PERIOD 1 +#define DOT11_MAX_DTIM_PERIOD 0xFF + + +#define DOT11_LLC_SNAP_HDR_LEN 8 +#define DOT11_OUI_LEN 3 +BWL_PRE_PACKED_STRUCT struct dot11_llc_snap_header { + uint8 dsap; + uint8 ssap; + uint8 ctl; + uint8 oui[DOT11_OUI_LEN]; + uint16 type; +} BWL_POST_PACKED_STRUCT; + + +#define RFC1042_HDR_LEN (ETHER_HDR_LEN + DOT11_LLC_SNAP_HDR_LEN) + + + +BWL_PRE_PACKED_STRUCT struct dot11_header { + uint16 fc; + uint16 durid; + struct ether_addr a1; + struct ether_addr a2; + struct ether_addr a3; + uint16 seq; + struct ether_addr a4; +} BWL_POST_PACKED_STRUCT; + + + +BWL_PRE_PACKED_STRUCT struct dot11_rts_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +#define DOT11_RTS_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_cts_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; +} BWL_POST_PACKED_STRUCT; +#define DOT11_CTS_LEN 10 + +BWL_PRE_PACKED_STRUCT struct dot11_ack_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ACK_LEN 10 + +BWL_PRE_PACKED_STRUCT struct dot11_ps_poll_frame { + uint16 fc; + uint16 durid; + struct ether_addr bssid; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +#define DOT11_PS_POLL_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_cf_end_frame { + uint16 fc; + uint16 durid; + struct ether_addr ra; + struct ether_addr bssid; +} BWL_POST_PACKED_STRUCT; +#define DOT11_CS_END_LEN 16 + +BWL_PRE_PACKED_STRUCT struct dot11_action_wifi_vendor_specific { + uint8 category; + uint8 OUI[3]; + uint8 type; + uint8 subtype; + uint8 data[1040]; + struct dot11_action_wifi_vendor_specific* next_node; +} BWL_POST_PACKED_STRUCT; + +typedef struct dot11_action_wifi_vendor_specific dot11_action_wifi_vendor_specific_t; + +#define DOT11_BA_CTL_POLICY_NORMAL 0x0000 +#define DOT11_BA_CTL_POLICY_NOACK 0x0001 +#define DOT11_BA_CTL_POLICY_MASK 0x0001 + +#define DOT11_BA_CTL_MTID 0x0002 +#define DOT11_BA_CTL_COMPRESSED 0x0004 + +#define DOT11_BA_CTL_NUMMSDU_MASK 0x0FC0 +#define DOT11_BA_CTL_NUMMSDU_SHIFT 6 + +#define DOT11_BA_CTL_TID_MASK 0xF000 +#define DOT11_BA_CTL_TID_SHIFT 12 + + +BWL_PRE_PACKED_STRUCT struct dot11_ctl_header { + uint16 fc; + uint16 durid; + struct ether_addr ra; + struct ether_addr ta; +} BWL_POST_PACKED_STRUCT; +#define DOT11_CTL_HDR_LEN 16 + + +BWL_PRE_PACKED_STRUCT struct dot11_bar { + uint16 bar_control; + uint16 seqnum; +} BWL_POST_PACKED_STRUCT; +#define DOT11_BAR_LEN 4 + +#define DOT11_BA_BITMAP_LEN 128 +#define DOT11_BA_CMP_BITMAP_LEN 8 + +BWL_PRE_PACKED_STRUCT struct dot11_ba { + uint16 ba_control; + uint16 seqnum; + uint8 bitmap[DOT11_BA_BITMAP_LEN]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_BA_LEN 4 + + +BWL_PRE_PACKED_STRUCT struct dot11_management_header { + uint16 fc; + uint16 durid; + struct ether_addr da; + struct ether_addr sa; + struct ether_addr bssid; + uint16 seq; +} BWL_POST_PACKED_STRUCT; +#define DOT11_MGMT_HDR_LEN 24 + + + +BWL_PRE_PACKED_STRUCT struct dot11_bcn_prb { + uint32 timestamp[2]; + uint16 beacon_interval; + uint16 capability; +} BWL_POST_PACKED_STRUCT; +#define DOT11_BCN_PRB_LEN 12 + +BWL_PRE_PACKED_STRUCT struct dot11_auth { + uint16 alg; + uint16 seq; + uint16 status; +} BWL_POST_PACKED_STRUCT; +#define DOT11_AUTH_FIXED_LEN 6 + +BWL_PRE_PACKED_STRUCT struct dot11_assoc_req { + uint16 capability; + uint16 listen; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ASSOC_REQ_FIXED_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_reassoc_req { + uint16 capability; + uint16 listen; + struct ether_addr ap; +} BWL_POST_PACKED_STRUCT; +#define DOT11_REASSOC_REQ_FIXED_LEN 10 + +BWL_PRE_PACKED_STRUCT struct dot11_assoc_resp { + uint16 capability; + uint16 status; + uint16 aid; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ASSOC_RESP_FIXED_LEN 6 + +BWL_PRE_PACKED_STRUCT struct dot11_action_measure { + uint8 category; + uint8 action; + uint8 token; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ACTION_MEASURE_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_action_ht_ch_width { + uint8 category; + uint8 action; + uint8 ch_width; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_action_ht_mimops { + uint8 category; + uint8 action; + uint8 control; +} BWL_POST_PACKED_STRUCT; + +#define SM_PWRSAVE_ENABLE 1 +#define SM_PWRSAVE_MODE 2 + + +BWL_PRE_PACKED_STRUCT struct dot11_power_cnst { + uint8 id; + uint8 len; + uint8 power; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_power_cnst dot11_power_cnst_t; + +BWL_PRE_PACKED_STRUCT struct dot11_power_cap { + uint8 min; + uint8 max; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_power_cap dot11_power_cap_t; + +BWL_PRE_PACKED_STRUCT struct dot11_tpc_rep { + uint8 id; + uint8 len; + uint8 tx_pwr; + uint8 margin; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_tpc_rep dot11_tpc_rep_t; +#define DOT11_MNG_IE_TPC_REPORT_LEN 2 + +BWL_PRE_PACKED_STRUCT struct dot11_supp_channels { + uint8 id; + uint8 len; + uint8 first_channel; + uint8 num_channels; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_supp_channels dot11_supp_channels_t; + + +BWL_PRE_PACKED_STRUCT struct dot11_extch { + uint8 id; + uint8 len; + uint8 extch; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_extch dot11_extch_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_brcm_extch { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + uint8 extch; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_brcm_extch dot11_brcm_extch_ie_t; + +#define BRCM_EXTCH_IE_LEN 5 +#define BRCM_EXTCH_IE_TYPE 53 +#define DOT11_EXTCH_IE_LEN 1 +#define DOT11_EXT_CH_MASK 0x03 +#define DOT11_EXT_CH_UPPER 0x01 +#define DOT11_EXT_CH_LOWER 0x03 +#define DOT11_EXT_CH_NONE 0x00 + +BWL_PRE_PACKED_STRUCT struct dot11_action_frmhdr { + uint8 category; + uint8 action; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_ACTION_FRMHDR_LEN 2 + + +BWL_PRE_PACKED_STRUCT struct dot11_channel_switch { + uint8 id; + uint8 len; + uint8 mode; + uint8 channel; + uint8 count; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_channel_switch dot11_chan_switch_ie_t; + +#define DOT11_SWITCH_IE_LEN 3 + +#define DOT11_CSA_MODE_ADVISORY 0 +#define DOT11_CSA_MODE_NO_TX 1 + +BWL_PRE_PACKED_STRUCT struct dot11_action_switch_channel { + uint8 category; + uint8 action; + dot11_chan_switch_ie_t chan_switch_ie; + dot11_brcm_extch_ie_t extch_ie; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_csa_body { + uint8 mode; + uint8 reg; + uint8 channel; + uint8 count; +} BWL_POST_PACKED_STRUCT; + + +BWL_PRE_PACKED_STRUCT struct dot11_ext_csa { + uint8 id; + uint8 len; + struct dot11_csa_body b; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11y_action_ext_csa { + uint8 category; + uint8 action; + struct dot11_csa_body b; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ext_csa dot11_ext_csa_ie_t; +#define DOT11_EXT_CSA_IE_LEN 4 + +BWL_PRE_PACKED_STRUCT struct dot11_action_ext_csa { + uint8 category; + uint8 action; + dot11_ext_csa_ie_t chan_switch_ie; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct dot11_obss_coex { + uint8 id; + uint8 len; + uint8 info; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_obss_coex dot11_obss_coex_t; +#define DOT11_OBSS_COEXINFO_LEN 1 + +#define DOT11_OBSS_COEX_INFO_REQ 0x01 +#define DOT11_OBSS_COEX_40MHZ_INTOLERANT 0x02 +#define DOT11_OBSS_COEX_20MHZ_WIDTH_REQ 0x04 + +BWL_PRE_PACKED_STRUCT struct dot11_obss_chanlist { + uint8 id; + uint8 len; + uint8 regclass; + uint8 chanlist[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_obss_chanlist dot11_obss_chanlist_t; +#define DOT11_OBSS_CHANLIST_FIXED_LEN 1 + +BWL_PRE_PACKED_STRUCT struct dot11_extcap_ie { + uint8 id; + uint8 len; + uint8 cap; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_extcap_ie dot11_extcap_ie_t; +#define DOT11_EXTCAP_LEN 1 + + + +#define DOT11_MEASURE_TYPE_BASIC 0 +#define DOT11_MEASURE_TYPE_CCA 1 +#define DOT11_MEASURE_TYPE_RPI 2 + + +#define DOT11_MEASURE_MODE_ENABLE (1<<1) +#define DOT11_MEASURE_MODE_REQUEST (1<<2) +#define DOT11_MEASURE_MODE_REPORT (1<<3) + +#define DOT11_MEASURE_MODE_LATE (1<<0) +#define DOT11_MEASURE_MODE_INCAPABLE (1<<1) +#define DOT11_MEASURE_MODE_REFUSED (1<<2) + +#define DOT11_MEASURE_BASIC_MAP_BSS ((uint8)(1<<0)) +#define DOT11_MEASURE_BASIC_MAP_OFDM ((uint8)(1<<1)) +#define DOT11_MEASURE_BASIC_MAP_UKNOWN ((uint8)(1<<2)) +#define DOT11_MEASURE_BASIC_MAP_RADAR ((uint8)(1<<3)) +#define DOT11_MEASURE_BASIC_MAP_UNMEAS ((uint8)(1<<4)) + +BWL_PRE_PACKED_STRUCT struct dot11_meas_req { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + uint8 channel; + uint8 start_time[8]; + uint16 duration; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_meas_req dot11_meas_req_t; +#define DOT11_MNG_IE_MREQ_LEN 14 + +#define DOT11_MNG_IE_MREQ_FIXED_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_meas_rep { + uint8 id; + uint8 len; + uint8 token; + uint8 mode; + uint8 type; + BWL_PRE_PACKED_STRUCT union + { + BWL_PRE_PACKED_STRUCT struct { + uint8 channel; + uint8 start_time[8]; + uint16 duration; + uint8 map; + } BWL_POST_PACKED_STRUCT basic; + uint8 data[1]; + } BWL_POST_PACKED_STRUCT rep; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_meas_rep dot11_meas_rep_t; + + +#define DOT11_MNG_IE_MREP_FIXED_LEN 3 + +BWL_PRE_PACKED_STRUCT struct dot11_meas_rep_basic { + uint8 channel; + uint8 start_time[8]; + uint16 duration; + uint8 map; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_meas_rep_basic dot11_meas_rep_basic_t; +#define DOT11_MEASURE_BASIC_REP_LEN 12 + +BWL_PRE_PACKED_STRUCT struct dot11_quiet { + uint8 id; + uint8 len; + uint8 count; + uint8 period; + uint16 duration; + uint16 offset; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_quiet dot11_quiet_t; + +BWL_PRE_PACKED_STRUCT struct chan_map_tuple { + uint8 channel; + uint8 map; +} BWL_POST_PACKED_STRUCT; +typedef struct chan_map_tuple chan_map_tuple_t; + +BWL_PRE_PACKED_STRUCT struct dot11_ibss_dfs { + uint8 id; + uint8 len; + uint8 eaddr[ETHER_ADDR_LEN]; + uint8 interval; + chan_map_tuple_t map[1]; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_ibss_dfs dot11_ibss_dfs_t; + + +#define WME_OUI "\x00\x50\xf2" +#define WME_VER 1 +#define WME_TYPE 2 +#define WME_SUBTYPE_IE 0 +#define WME_SUBTYPE_PARAM_IE 1 +#define WME_SUBTYPE_TSPEC 2 + + +#define AC_BE 0 +#define AC_BK 1 +#define AC_VI 2 +#define AC_VO 3 +#define AC_COUNT 4 + +typedef uint8 ac_bitmap_t; + +#define AC_BITMAP_NONE 0x0 +#define AC_BITMAP_ALL 0xf +#define AC_BITMAP_TST(ab, ac) (((ab) & (1 << (ac))) != 0) +#define AC_BITMAP_SET(ab, ac) (((ab) |= (1 << (ac)))) +#define AC_BITMAP_RESET(ab, ac) (((ab) &= ~(1 << (ac)))) + + +BWL_PRE_PACKED_STRUCT struct wme_ie { + uint8 oui[3]; + uint8 type; + uint8 subtype; + uint8 version; + uint8 qosinfo; +} BWL_POST_PACKED_STRUCT; +typedef struct wme_ie wme_ie_t; +#define WME_IE_LEN 7 + +BWL_PRE_PACKED_STRUCT struct edcf_acparam { + uint8 ACI; + uint8 ECW; + uint16 TXOP; +} BWL_POST_PACKED_STRUCT; +typedef struct edcf_acparam edcf_acparam_t; + + +BWL_PRE_PACKED_STRUCT struct wme_param_ie { + uint8 oui[3]; + uint8 type; + uint8 subtype; + uint8 version; + uint8 qosinfo; + uint8 rsvd; + edcf_acparam_t acparam[AC_COUNT]; +} BWL_POST_PACKED_STRUCT; +typedef struct wme_param_ie wme_param_ie_t; +#define WME_PARAM_IE_LEN 24 + + +#define WME_QI_AP_APSD_MASK 0x80 +#define WME_QI_AP_APSD_SHIFT 7 +#define WME_QI_AP_COUNT_MASK 0x0f +#define WME_QI_AP_COUNT_SHIFT 0 + + +#define WME_QI_STA_MAXSPLEN_MASK 0x60 +#define WME_QI_STA_MAXSPLEN_SHIFT 5 +#define WME_QI_STA_APSD_ALL_MASK 0xf +#define WME_QI_STA_APSD_ALL_SHIFT 0 +#define WME_QI_STA_APSD_BE_MASK 0x8 +#define WME_QI_STA_APSD_BE_SHIFT 3 +#define WME_QI_STA_APSD_BK_MASK 0x4 +#define WME_QI_STA_APSD_BK_SHIFT 2 +#define WME_QI_STA_APSD_VI_MASK 0x2 +#define WME_QI_STA_APSD_VI_SHIFT 1 +#define WME_QI_STA_APSD_VO_MASK 0x1 +#define WME_QI_STA_APSD_VO_SHIFT 0 + + +#define EDCF_AIFSN_MIN 1 +#define EDCF_AIFSN_MAX 15 +#define EDCF_AIFSN_MASK 0x0f +#define EDCF_ACM_MASK 0x10 +#define EDCF_ACI_MASK 0x60 +#define EDCF_ACI_SHIFT 5 +#define EDCF_AIFSN_SHIFT 12 + + +#define EDCF_ECW_MIN 0 +#define EDCF_ECW_MAX 15 +#define EDCF_ECW2CW(exp) ((1 << (exp)) - 1) +#define EDCF_ECWMIN_MASK 0x0f +#define EDCF_ECWMAX_MASK 0xf0 +#define EDCF_ECWMAX_SHIFT 4 + + +#define EDCF_TXOP_MIN 0 +#define EDCF_TXOP_MAX 65535 +#define EDCF_TXOP2USEC(txop) ((txop) << 5) + + +#define NON_EDCF_AC_BE_ACI_STA 0x02 + + +#define EDCF_AC_BE_ACI_STA 0x03 +#define EDCF_AC_BE_ECW_STA 0xA4 +#define EDCF_AC_BE_TXOP_STA 0x0000 +#define EDCF_AC_BK_ACI_STA 0x27 +#define EDCF_AC_BK_ECW_STA 0xA4 +#define EDCF_AC_BK_TXOP_STA 0x0000 +#define EDCF_AC_VI_ACI_STA 0x42 +#define EDCF_AC_VI_ECW_STA 0x43 +#define EDCF_AC_VI_TXOP_STA 0x005e +#define EDCF_AC_VO_ACI_STA 0x62 +#define EDCF_AC_VO_ECW_STA 0x32 +#define EDCF_AC_VO_TXOP_STA 0x002f + + +#define EDCF_AC_BE_ACI_AP 0x03 +#define EDCF_AC_BE_ECW_AP 0x64 +#define EDCF_AC_BE_TXOP_AP 0x0000 +#define EDCF_AC_BK_ACI_AP 0x27 +#define EDCF_AC_BK_ECW_AP 0xA4 +#define EDCF_AC_BK_TXOP_AP 0x0000 +#define EDCF_AC_VI_ACI_AP 0x41 +#define EDCF_AC_VI_ECW_AP 0x43 +#define EDCF_AC_VI_TXOP_AP 0x005e +#define EDCF_AC_VO_ACI_AP 0x61 +#define EDCF_AC_VO_ECW_AP 0x32 +#define EDCF_AC_VO_TXOP_AP 0x002f + + +BWL_PRE_PACKED_STRUCT struct edca_param_ie { + uint8 qosinfo; + uint8 rsvd; + edcf_acparam_t acparam[AC_COUNT]; +} BWL_POST_PACKED_STRUCT; +typedef struct edca_param_ie edca_param_ie_t; +#define EDCA_PARAM_IE_LEN 18 + + +BWL_PRE_PACKED_STRUCT struct qos_cap_ie { + uint8 qosinfo; +} BWL_POST_PACKED_STRUCT; +typedef struct qos_cap_ie qos_cap_ie_t; + +BWL_PRE_PACKED_STRUCT struct dot11_qbss_load_ie { + uint8 id; + uint8 length; + uint16 station_count; + uint8 channel_utilization; + uint16 aac; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_qbss_load_ie dot11_qbss_load_ie_t; + + +#define FIXED_MSDU_SIZE 0x8000 +#define MSDU_SIZE_MASK 0x7fff + + + +#define INTEGER_SHIFT 13 +#define FRACTION_MASK 0x1FFF + + +BWL_PRE_PACKED_STRUCT struct dot11_management_notification { + uint8 category; + uint8 action; + uint8 token; + uint8 status; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT; +#define DOT11_MGMT_NOTIFICATION_LEN 4 + + +#define WME_ADDTS_REQUEST 0 +#define WME_ADDTS_RESPONSE 1 +#define WME_DELTS_REQUEST 2 + + +#define WME_ADMISSION_ACCEPTED 0 +#define WME_INVALID_PARAMETERS 1 +#define WME_ADMISSION_REFUSED 3 + + +#define BCN_PRB_SSID(body) ((char*)(body) + DOT11_BCN_PRB_LEN) + + +#define DOT11_OPEN_SYSTEM 0 +#define DOT11_SHARED_KEY 1 + +#define DOT11_OPEN_SHARED 2 +#define DOT11_CHALLENGE_LEN 128 + + +#define FC_PVER_MASK 0x3 +#define FC_PVER_SHIFT 0 +#define FC_TYPE_MASK 0xC +#define FC_TYPE_SHIFT 2 +#define FC_SUBTYPE_MASK 0xF0 +#define FC_SUBTYPE_SHIFT 4 +#define FC_TODS 0x100 +#define FC_TODS_SHIFT 8 +#define FC_FROMDS 0x200 +#define FC_FROMDS_SHIFT 9 +#define FC_MOREFRAG 0x400 +#define FC_MOREFRAG_SHIFT 10 +#define FC_RETRY 0x800 +#define FC_RETRY_SHIFT 11 +#define FC_PM 0x1000 +#define FC_PM_SHIFT 12 +#define FC_MOREDATA 0x2000 +#define FC_MOREDATA_SHIFT 13 +#define FC_WEP 0x4000 +#define FC_WEP_SHIFT 14 +#define FC_ORDER 0x8000 +#define FC_ORDER_SHIFT 15 + + +#define SEQNUM_SHIFT 4 +#define SEQNUM_MAX 0x1000 +#define FRAGNUM_MASK 0xF + + + + +#define FC_TYPE_MNG 0 +#define FC_TYPE_CTL 1 +#define FC_TYPE_DATA 2 + + +#define FC_SUBTYPE_ASSOC_REQ 0 +#define FC_SUBTYPE_ASSOC_RESP 1 +#define FC_SUBTYPE_REASSOC_REQ 2 +#define FC_SUBTYPE_REASSOC_RESP 3 +#define FC_SUBTYPE_PROBE_REQ 4 +#define FC_SUBTYPE_PROBE_RESP 5 +#define FC_SUBTYPE_BEACON 8 +#define FC_SUBTYPE_ATIM 9 +#define FC_SUBTYPE_DISASSOC 10 +#define FC_SUBTYPE_AUTH 11 +#define FC_SUBTYPE_DEAUTH 12 +#define FC_SUBTYPE_ACTION 13 +#define FC_SUBTYPE_ACTION_NOACK 14 + + +#define FC_SUBTYPE_CTL_WRAPPER 7 +#define FC_SUBTYPE_BLOCKACK_REQ 8 +#define FC_SUBTYPE_BLOCKACK 9 +#define FC_SUBTYPE_PS_POLL 10 +#define FC_SUBTYPE_RTS 11 +#define FC_SUBTYPE_CTS 12 +#define FC_SUBTYPE_ACK 13 +#define FC_SUBTYPE_CF_END 14 +#define FC_SUBTYPE_CF_END_ACK 15 + + +#define FC_SUBTYPE_DATA 0 +#define FC_SUBTYPE_DATA_CF_ACK 1 +#define FC_SUBTYPE_DATA_CF_POLL 2 +#define FC_SUBTYPE_DATA_CF_ACK_POLL 3 +#define FC_SUBTYPE_NULL 4 +#define FC_SUBTYPE_CF_ACK 5 +#define FC_SUBTYPE_CF_POLL 6 +#define FC_SUBTYPE_CF_ACK_POLL 7 +#define FC_SUBTYPE_QOS_DATA 8 +#define FC_SUBTYPE_QOS_DATA_CF_ACK 9 +#define FC_SUBTYPE_QOS_DATA_CF_POLL 10 +#define FC_SUBTYPE_QOS_DATA_CF_ACK_POLL 11 +#define FC_SUBTYPE_QOS_NULL 12 +#define FC_SUBTYPE_QOS_CF_POLL 14 +#define FC_SUBTYPE_QOS_CF_ACK_POLL 15 + + +#define FC_SUBTYPE_ANY_QOS(s) (((s) & 8) != 0) +#define FC_SUBTYPE_ANY_NULL(s) (((s) & 4) != 0) +#define FC_SUBTYPE_ANY_CF_POLL(s) (((s) & 2) != 0) +#define FC_SUBTYPE_ANY_CF_ACK(s) (((s) & 1) != 0) + + +#define FC_KIND_MASK (FC_TYPE_MASK | FC_SUBTYPE_MASK) + +#define FC_KIND(t, s) (((t) << FC_TYPE_SHIFT) | ((s) << FC_SUBTYPE_SHIFT)) + +#define FC_SUBTYPE(fc) (((fc) & FC_SUBTYPE_MASK) >> FC_SUBTYPE_SHIFT) +#define FC_TYPE(fc) (((fc) & FC_TYPE_MASK) >> FC_TYPE_SHIFT) + +#define FC_ASSOC_REQ FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ASSOC_REQ) +#define FC_ASSOC_RESP FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ASSOC_RESP) +#define FC_REASSOC_REQ FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_REASSOC_REQ) +#define FC_REASSOC_RESP FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_REASSOC_RESP) +#define FC_PROBE_REQ FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_PROBE_REQ) +#define FC_PROBE_RESP FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_PROBE_RESP) +#define FC_BEACON FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_BEACON) +#define FC_DISASSOC FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_DISASSOC) +#define FC_AUTH FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_AUTH) +#define FC_DEAUTH FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_DEAUTH) +#define FC_ACTION FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ACTION) +#define FC_ACTION_NOACK FC_KIND(FC_TYPE_MNG, FC_SUBTYPE_ACTION_NOACK) + +#define FC_CTL_WRAPPER FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CTL_WRAPPER) +#define FC_BLOCKACK_REQ FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_BLOCKACK_REQ) +#define FC_BLOCKACK FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_BLOCKACK) +#define FC_PS_POLL FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_PS_POLL) +#define FC_RTS FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_RTS) +#define FC_CTS FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CTS) +#define FC_ACK FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_ACK) +#define FC_CF_END FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CF_END) +#define FC_CF_END_ACK FC_KIND(FC_TYPE_CTL, FC_SUBTYPE_CF_END_ACK) + +#define FC_DATA FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_DATA) +#define FC_NULL_DATA FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_NULL) +#define FC_DATA_CF_ACK FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_DATA_CF_ACK) +#define FC_QOS_DATA FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_QOS_DATA) +#define FC_QOS_NULL FC_KIND(FC_TYPE_DATA, FC_SUBTYPE_QOS_NULL) + + + + +#define QOS_PRIO_SHIFT 0 +#define QOS_PRIO_MASK 0x0007 +#define QOS_PRIO(qos) (((qos) & QOS_PRIO_MASK) >> QOS_PRIO_SHIFT) + + +#define QOS_TID_SHIFT 0 +#define QOS_TID_MASK 0x000f +#define QOS_TID(qos) (((qos) & QOS_TID_MASK) >> QOS_TID_SHIFT) + + +#define QOS_EOSP_SHIFT 4 +#define QOS_EOSP_MASK 0x0010 +#define QOS_EOSP(qos) (((qos) & QOS_EOSP_MASK) >> QOS_EOSP_SHIFT) + + +#define QOS_ACK_NORMAL_ACK 0 +#define QOS_ACK_NO_ACK 1 +#define QOS_ACK_NO_EXP_ACK 2 +#define QOS_ACK_BLOCK_ACK 3 +#define QOS_ACK_SHIFT 5 +#define QOS_ACK_MASK 0x0060 +#define QOS_ACK(qos) (((qos) & QOS_ACK_MASK) >> QOS_ACK_SHIFT) + + +#define QOS_AMSDU_SHIFT 7 +#define QOS_AMSDU_MASK 0x0080 + + + + + + +#define DOT11_MNG_AUTH_ALGO_LEN 2 +#define DOT11_MNG_AUTH_SEQ_LEN 2 +#define DOT11_MNG_BEACON_INT_LEN 2 +#define DOT11_MNG_CAP_LEN 2 +#define DOT11_MNG_AP_ADDR_LEN 6 +#define DOT11_MNG_LISTEN_INT_LEN 2 +#define DOT11_MNG_REASON_LEN 2 +#define DOT11_MNG_AID_LEN 2 +#define DOT11_MNG_STATUS_LEN 2 +#define DOT11_MNG_TIMESTAMP_LEN 8 + + +#define DOT11_AID_MASK 0x3fff + + +#define DOT11_RC_RESERVED 0 +#define DOT11_RC_UNSPECIFIED 1 +#define DOT11_RC_AUTH_INVAL 2 +#define DOT11_RC_DEAUTH_LEAVING 3 +#define DOT11_RC_INACTIVITY 4 +#define DOT11_RC_BUSY 5 +#define DOT11_RC_INVAL_CLASS_2 6 +#define DOT11_RC_INVAL_CLASS_3 7 +#define DOT11_RC_DISASSOC_LEAVING 8 +#define DOT11_RC_NOT_AUTH 9 +#define DOT11_RC_BAD_PC 10 +#define DOT11_RC_BAD_CHANNELS 11 + + + +#define DOT11_RC_UNSPECIFIED_QOS 32 +#define DOT11_RC_INSUFFCIENT_BW 33 +#define DOT11_RC_EXCESSIVE_FRAMES 34 +#define DOT11_RC_TX_OUTSIDE_TXOP 35 +#define DOT11_RC_LEAVING_QBSS 36 +#define DOT11_RC_BAD_MECHANISM 37 +#define DOT11_RC_SETUP_NEEDED 38 +#define DOT11_RC_TIMEOUT 39 + +#define DOT11_RC_MAX 23 + + +#define DOT11_SC_SUCCESS 0 +#define DOT11_SC_FAILURE 1 +#define DOT11_SC_CAP_MISMATCH 10 +#define DOT11_SC_REASSOC_FAIL 11 +#define DOT11_SC_ASSOC_FAIL 12 +#define DOT11_SC_AUTH_MISMATCH 13 +#define DOT11_SC_AUTH_SEQ 14 +#define DOT11_SC_AUTH_CHALLENGE_FAIL 15 +#define DOT11_SC_AUTH_TIMEOUT 16 +#define DOT11_SC_ASSOC_BUSY_FAIL 17 +#define DOT11_SC_ASSOC_RATE_MISMATCH 18 +#define DOT11_SC_ASSOC_SHORT_REQUIRED 19 +#define DOT11_SC_ASSOC_PBCC_REQUIRED 20 +#define DOT11_SC_ASSOC_AGILITY_REQUIRED 21 +#define DOT11_SC_ASSOC_SPECTRUM_REQUIRED 22 +#define DOT11_SC_ASSOC_BAD_POWER_CAP 23 +#define DOT11_SC_ASSOC_BAD_SUP_CHANNELS 24 +#define DOT11_SC_ASSOC_SHORTSLOT_REQUIRED 25 +#define DOT11_SC_ASSOC_ERPBCC_REQUIRED 26 +#define DOT11_SC_ASSOC_DSSOFDM_REQUIRED 27 + +#define DOT11_SC_DECLINED 37 +#define DOT11_SC_INVALID_PARAMS 38 + + +#define DOT11_MNG_DS_PARAM_LEN 1 +#define DOT11_MNG_IBSS_PARAM_LEN 2 + + +#define DOT11_MNG_TIM_FIXED_LEN 3 +#define DOT11_MNG_TIM_DTIM_COUNT 0 +#define DOT11_MNG_TIM_DTIM_PERIOD 1 +#define DOT11_MNG_TIM_BITMAP_CTL 2 +#define DOT11_MNG_TIM_PVB 3 + + +#define TLV_TAG_OFF 0 +#define TLV_LEN_OFF 1 +#define TLV_HDR_LEN 2 +#define TLV_BODY_OFF 2 + + +#define DOT11_MNG_SSID_ID 0 +#define DOT11_MNG_RATES_ID 1 +#define DOT11_MNG_FH_PARMS_ID 2 +#define DOT11_MNG_DS_PARMS_ID 3 +#define DOT11_MNG_CF_PARMS_ID 4 +#define DOT11_MNG_TIM_ID 5 +#define DOT11_MNG_IBSS_PARMS_ID 6 +#define DOT11_MNG_COUNTRY_ID 7 +#define DOT11_MNG_HOPPING_PARMS_ID 8 +#define DOT11_MNG_HOPPING_TABLE_ID 9 +#define DOT11_MNG_REQUEST_ID 10 +#define DOT11_MNG_QBSS_LOAD_ID 11 +#define DOT11_MNG_EDCA_PARAM_ID 12 +#define DOT11_MNG_CHALLENGE_ID 16 +#define DOT11_MNG_PWR_CONSTRAINT_ID 32 +#define DOT11_MNG_PWR_CAP_ID 33 +#define DOT11_MNG_TPC_REQUEST_ID 34 +#define DOT11_MNG_TPC_REPORT_ID 35 +#define DOT11_MNG_SUPP_CHANNELS_ID 36 +#define DOT11_MNG_CHANNEL_SWITCH_ID 37 +#define DOT11_MNG_MEASURE_REQUEST_ID 38 +#define DOT11_MNG_MEASURE_REPORT_ID 39 +#define DOT11_MNG_QUIET_ID 40 +#define DOT11_MNG_IBSS_DFS_ID 41 +#define DOT11_MNG_ERP_ID 42 +#define DOT11_MNG_TS_DELAY_ID 43 +#define DOT11_MNG_HT_CAP 45 +#define DOT11_MNG_QOS_CAP_ID 46 +#define DOT11_MNG_NONERP_ID 47 +#define DOT11_MNG_RSN_ID 48 +#define DOT11_MNG_EXT_RATES_ID 50 +#define DOT11_MNG_REGCLASS_ID 59 +#define DOT11_MNG_EXT_CSA_ID 60 +#define DOT11_MNG_HT_ADD 61 +#define DOT11_MNG_EXT_CHANNEL_OFFSET 62 +#define DOT11_MNG_WAPI_ID 68 +#define DOT11_MNG_HT_BSS_COEXINFO_ID 72 +#define DOT11_MNG_HT_BSS_CHANNEL_REPORT_ID 73 +#define DOT11_MNG_HT_OBSS_ID 74 +#define DOT11_MNG_EXT_CAP 127 +#define DOT11_MNG_WPA_ID 221 +#define DOT11_MNG_PROPR_ID 221 + + +#define DOT11_RATE_BASIC 0x80 +#define DOT11_RATE_MASK 0x7F + + +#define DOT11_MNG_ERP_LEN 1 +#define DOT11_MNG_NONERP_PRESENT 0x01 +#define DOT11_MNG_USE_PROTECTION 0x02 +#define DOT11_MNG_BARKER_PREAMBLE 0x04 + +#define DOT11_MGN_TS_DELAY_LEN 4 +#define TS_DELAY_FIELD_SIZE 4 + + +#define DOT11_CAP_ESS 0x0001 +#define DOT11_CAP_IBSS 0x0002 +#define DOT11_CAP_POLLABLE 0x0004 +#define DOT11_CAP_POLL_RQ 0x0008 +#define DOT11_CAP_PRIVACY 0x0010 +#define DOT11_CAP_SHORT 0x0020 +#define DOT11_CAP_PBCC 0x0040 +#define DOT11_CAP_AGILITY 0x0080 +#define DOT11_CAP_SPECTRUM 0x0100 +#define DOT11_CAP_SHORTSLOT 0x0400 +#define DOT11_CAP_CCK_OFDM 0x2000 + + +#define DOT11_OBSS_COEX_MNG_SUPPORT 0x01 + + +#define DOT11_ACTION_HDR_LEN 2 +#define DOT11_ACTION_CAT_ERR_MASK 0x80 +#define DOT11_ACTION_CAT_MASK 0x7F +#define DOT11_ACTION_CAT_SPECT_MNG 0 +#define DOT11_ACTION_CAT_BLOCKACK 3 +#define DOT11_ACTION_CAT_PUBLIC 4 +#define DOT11_ACTION_CAT_HT 7 +#define DOT11_ACTION_CAT_VS 127 +#define DOT11_ACTION_NOTIFICATION 0x11 + +#define DOT11_ACTION_ID_M_REQ 0 +#define DOT11_ACTION_ID_M_REP 1 +#define DOT11_ACTION_ID_TPC_REQ 2 +#define DOT11_ACTION_ID_TPC_REP 3 +#define DOT11_ACTION_ID_CHANNEL_SWITCH 4 +#define DOT11_ACTION_ID_EXT_CSA 5 + + +#define DOT11_ACTION_ID_HT_CH_WIDTH 0 +#define DOT11_ACTION_ID_HT_MIMO_PS 1 + + +#define DOT11_PUB_ACTION_BSS_COEX_MNG 0 +#define DOT11_PUB_ACTION_CHANNEL_SWITCH 4 + + +#define DOT11_BA_ACTION_ADDBA_REQ 0 +#define DOT11_BA_ACTION_ADDBA_RESP 1 +#define DOT11_BA_ACTION_DELBA 2 + + +#define DOT11_ADDBA_PARAM_AMSDU_SUP 0x0001 +#define DOT11_ADDBA_PARAM_POLICY_MASK 0x0002 +#define DOT11_ADDBA_PARAM_POLICY_SHIFT 1 +#define DOT11_ADDBA_PARAM_TID_MASK 0x003c +#define DOT11_ADDBA_PARAM_TID_SHIFT 2 +#define DOT11_ADDBA_PARAM_BSIZE_MASK 0xffc0 +#define DOT11_ADDBA_PARAM_BSIZE_SHIFT 6 + +#define DOT11_ADDBA_POLICY_DELAYED 0 +#define DOT11_ADDBA_POLICY_IMMEDIATE 1 + +BWL_PRE_PACKED_STRUCT struct dot11_addba_req { + uint8 category; + uint8 action; + uint8 token; + uint16 addba_param_set; + uint16 timeout; + uint16 start_seqnum; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_addba_req dot11_addba_req_t; +#define DOT11_ADDBA_REQ_LEN 9 + +BWL_PRE_PACKED_STRUCT struct dot11_addba_resp { + uint8 category; + uint8 action; + uint8 token; + uint16 status; + uint16 addba_param_set; + uint16 timeout; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_addba_resp dot11_addba_resp_t; +#define DOT11_ADDBA_RESP_LEN 9 + + +#define DOT11_DELBA_PARAM_INIT_MASK 0x0800 +#define DOT11_DELBA_PARAM_INIT_SHIFT 11 +#define DOT11_DELBA_PARAM_TID_MASK 0xf000 +#define DOT11_DELBA_PARAM_TID_SHIFT 12 + +BWL_PRE_PACKED_STRUCT struct dot11_delba { + uint8 category; + uint8 action; + uint16 delba_param_set; + uint16 reason; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_delba dot11_delba_t; +#define DOT11_DELBA_LEN 6 + + +#define DOT11_BSSTYPE_INFRASTRUCTURE 0 +#define DOT11_BSSTYPE_INDEPENDENT 1 +#define DOT11_BSSTYPE_ANY 2 +#define DOT11_SCANTYPE_ACTIVE 0 +#define DOT11_SCANTYPE_PASSIVE 1 + + +#define PREN_PREAMBLE 24 +#define PREN_MM_EXT 8 +#define PREN_PREAMBLE_EXT 4 + + +#define NPHY_RIFS_TIME 2 + + +#define APHY_SLOT_TIME 9 +#define APHY_SIFS_TIME 16 +#define APHY_DIFS_TIME (APHY_SIFS_TIME + (2 * APHY_SLOT_TIME)) +#define APHY_PREAMBLE_TIME 16 +#define APHY_SIGNAL_TIME 4 +#define APHY_SYMBOL_TIME 4 +#define APHY_SERVICE_NBITS 16 +#define APHY_TAIL_NBITS 6 +#define APHY_CWMIN 15 + + +#define BPHY_SLOT_TIME 20 +#define BPHY_SIFS_TIME 10 +#define BPHY_DIFS_TIME 50 +#define BPHY_PLCP_TIME 192 +#define BPHY_PLCP_SHORT_TIME 96 +#define BPHY_CWMIN 31 + + +#define DOT11_OFDM_SIGNAL_EXTENSION 6 + +#define PHY_CWMAX 1023 + +#define DOT11_MAXNUMFRAGS 16 + + +typedef struct d11cnt { + uint32 txfrag; + uint32 txmulti; + uint32 txfail; + uint32 txretry; + uint32 txretrie; + uint32 rxdup; + uint32 txrts; + uint32 txnocts; + uint32 txnoack; + uint32 rxfrag; + uint32 rxmulti; + uint32 rxcrc; + uint32 txfrmsnt; + uint32 rxundec; +} d11cnt_t; + + +#define BRCM_PROP_OUI "\x00\x90\x4C" + + + + +BWL_PRE_PACKED_STRUCT struct brcm_prop_ie_s { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + uint16 cap; +} BWL_POST_PACKED_STRUCT; +typedef struct brcm_prop_ie_s brcm_prop_ie_t; + +#define BRCM_PROP_IE_LEN 6 + +#define DPT_IE_TYPE 2 + + +#define BRCM_OUI "\x00\x10\x18" + + +BWL_PRE_PACKED_STRUCT struct brcm_ie { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 ver; + uint8 assoc; + uint8 flags; + uint8 flags1; + uint16 amsdu_mtu_pref; +} BWL_POST_PACKED_STRUCT; +typedef struct brcm_ie brcm_ie_t; +#define BRCM_IE_LEN 11 +#define BRCM_IE_VER 2 +#define BRCM_IE_LEGACY_AES_VER 1 + + +#ifdef WLAFTERBURNER +#define BRF_ABCAP 0x1 +#define BRF_ABRQRD 0x2 +#define BRF_ABCOUNTER_MASK 0xf0 +#define BRF_ABCOUNTER_SHIFT 4 +#endif +#define BRF_LZWDS 0x4 +#define BRF_BLOCKACK 0x8 + + +#define BRF1_AMSDU 0x1 +#define BRF1_WMEPS 0x4 +#define BRF1_PSOFIX 0x8 + +#ifdef WLAFTERBURNER +#define AB_WDS_TIMEOUT_MAX 15 +#define AB_WDS_TIMEOUT_MIN 1 +#endif + +#define AB_GUARDCOUNT 10 + +#define MCSSET_LEN 16 +#define MAX_MCS_NUM (128) + +BWL_PRE_PACKED_STRUCT struct ht_cap_ie { + uint16 cap; + uint8 params; + uint8 supp_mcs[MCSSET_LEN]; + uint16 ext_htcap; + uint32 txbf_cap; + uint8 as_cap; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_cap_ie ht_cap_ie_t; + + + +BWL_PRE_PACKED_STRUCT struct ht_prop_cap_ie { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + ht_cap_ie_t cap_ie; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_prop_cap_ie ht_prop_cap_ie_t; +#define HT_PROP_IE_OVERHEAD 4 +#define HT_CAP_IE_LEN 26 +#define HT_CAP_IE_TYPE 51 + +#define HT_CAP_LDPC_CODING 0x0001 +#define HT_CAP_40MHZ 0x0002 +#define HT_CAP_MIMO_PS_MASK 0x000C +#define HT_CAP_MIMO_PS_SHIFT 0x0002 +#define HT_CAP_MIMO_PS_OFF 0x0003 +#define HT_CAP_MIMO_PS_RTS 0x0001 +#define HT_CAP_MIMO_PS_ON 0x0000 +#define HT_CAP_GF 0x0010 +#define HT_CAP_SHORT_GI_20 0x0020 +#define HT_CAP_SHORT_GI_40 0x0040 +#define HT_CAP_TX_STBC 0x0080 +#define HT_CAP_RX_STBC_MASK 0x0300 +#define HT_CAP_RX_STBC_SHIFT 8 +#define HT_CAP_DELAYED_BA 0x0400 +#define HT_CAP_MAX_AMSDU 0x0800 +#define HT_CAP_DSSS_CCK 0x1000 +#define HT_CAP_PSMP 0x2000 +#define HT_CAP_40MHZ_INTOLERANT 0x4000 +#define HT_CAP_LSIG_TXOP 0x8000 + +#define HT_CAP_RX_STBC_NO 0x0 +#define HT_CAP_RX_STBC_ONE_STREAM 0x1 +#define HT_CAP_RX_STBC_TWO_STREAM 0x2 +#define HT_CAP_RX_STBC_THREE_STREAM 0x3 + +#define HT_MAX_AMSDU 7935 +#define HT_MIN_AMSDU 3835 + +#define HT_PARAMS_RX_FACTOR_MASK 0x03 +#define HT_PARAMS_DENSITY_MASK 0x1C +#define HT_PARAMS_DENSITY_SHIFT 2 + + +#define AMPDU_MAX_MPDU_DENSITY 7 +#define AMPDU_RX_FACTOR_64K 3 +#define AMPDU_RX_FACTOR_BASE 8*1024 +#define AMPDU_DELIMITER_LEN 4 + +#define HT_CAP_EXT_PCO 0x0001 +#define HT_CAP_EXT_PCO_TTIME_MASK 0x0006 +#define HT_CAP_EXT_PCO_TTIME_SHIFT 1 +#define HT_CAP_EXT_MCS_FEEDBACK_MASK 0x0300 +#define HT_CAP_EXT_MCS_FEEDBACK_SHIFT 8 +#define HT_CAP_EXT_HTC 0x0400 +#define HT_CAP_EXT_RD_RESP 0x0800 + +BWL_PRE_PACKED_STRUCT struct ht_add_ie { + uint8 ctl_ch; + uint8 byte1; + uint16 opmode; + uint16 misc_bits; + uint8 basic_mcs[MCSSET_LEN]; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_add_ie ht_add_ie_t; + + + +BWL_PRE_PACKED_STRUCT struct ht_prop_add_ie { + uint8 id; + uint8 len; + uint8 oui[3]; + uint8 type; + ht_add_ie_t add_ie; +} BWL_POST_PACKED_STRUCT; +typedef struct ht_prop_add_ie ht_prop_add_ie_t; + +#define HT_ADD_IE_LEN 22 +#define HT_ADD_IE_TYPE 52 + + +#define HT_BW_ANY 0x04 +#define HT_RIFS_PERMITTED 0x08 + + +#define HT_OPMODE_MASK 0x0003 +#define HT_OPMODE_SHIFT 0 +#define HT_OPMODE_PURE 0x0000 +#define HT_OPMODE_OPTIONAL 0x0001 +#define HT_OPMODE_HT20IN40 0x0002 +#define HT_OPMODE_MIXED 0x0003 +#define HT_OPMODE_NONGF 0x0004 +#define DOT11N_TXBURST 0x0008 +#define DOT11N_OBSS_NONHT 0x0010 + + +#define HT_BASIC_STBC_MCS 0x007f +#define HT_DUAL_STBC_PROT 0x0080 +#define HT_SECOND_BCN 0x0100 +#define HT_LSIG_TXOP 0x0200 +#define HT_PCO_ACTIVE 0x0400 +#define HT_PCO_PHASE 0x0800 +#define HT_DUALCTS_PROTECTION 0x0080 + + +#define DOT11N_2G_TXBURST_LIMIT 6160 +#define DOT11N_5G_TXBURST_LIMIT 3080 + + +#define GET_HT_OPMODE(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + >> HT_OPMODE_SHIFT) +#define HT_MIXEDMODE_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + == HT_OPMODE_MIXED) +#define HT_HT20_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + == HT_OPMODE_HT20IN40) +#define HT_OPTIONAL_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_MASK) \ + == HT_OPMODE_OPTIONAL) +#define HT_USE_PROTECTION(add_ie) (HT_HT20_PRESENT((add_ie)) || \ + HT_MIXEDMODE_PRESENT((add_ie))) +#define HT_NONGF_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & HT_OPMODE_NONGF) \ + == HT_OPMODE_NONGF) +#define DOT11N_TXBURST_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & DOT11N_TXBURST) \ + == DOT11N_TXBURST) +#define DOT11N_OBSS_NONHT_PRESENT(add_ie) ((ltoh16_ua(&add_ie->opmode) & DOT11N_OBSS_NONHT) \ + == DOT11N_OBSS_NONHT) + +BWL_PRE_PACKED_STRUCT struct obss_params { + uint16 passive_dwell; + uint16 active_dwell; + uint16 bss_widthscan_interval; + uint16 passive_total; + uint16 active_total; + uint16 chanwidth_transition_dly; + uint16 activity_threshold; +} BWL_POST_PACKED_STRUCT; +typedef struct obss_params obss_params_t; + +BWL_PRE_PACKED_STRUCT struct dot11_obss_ie { + uint8 id; + uint8 len; + obss_params_t obss_params; +} BWL_POST_PACKED_STRUCT; +typedef struct dot11_obss_ie dot11_obss_ie_t; +#define DOT11_OBSS_SCAN_IE_LEN sizeof(obss_params_t) + + +BWL_PRE_PACKED_STRUCT struct vndr_ie { + uchar id; + uchar len; + uchar oui [3]; + uchar data [1]; +} BWL_POST_PACKED_STRUCT; +typedef struct vndr_ie vndr_ie_t; + +#define VNDR_IE_HDR_LEN 2 +#define VNDR_IE_MIN_LEN 3 +#define VNDR_IE_MAX_LEN 256 + + +#define WPA_VERSION 1 +#define WPA_OUI "\x00\x50\xF2" + +#define WPA2_VERSION 1 +#define WPA2_VERSION_LEN 2 +#define WPA2_OUI "\x00\x0F\xAC" + +#define WPA_OUI_LEN 3 + + +#define RSN_AKM_NONE 0 +#define RSN_AKM_UNSPECIFIED 1 +#define RSN_AKM_PSK 2 + + +#define DOT11_MAX_DEFAULT_KEYS 4 +#define DOT11_MAX_KEY_SIZE 32 +#define DOT11_MAX_IV_SIZE 16 +#define DOT11_EXT_IV_FLAG (1<<5) +#define DOT11_WPA_KEY_RSC_LEN 8 + +#define WEP1_KEY_SIZE 5 +#define WEP1_KEY_HEX_SIZE 10 +#define WEP128_KEY_SIZE 13 +#define WEP128_KEY_HEX_SIZE 26 +#define TKIP_MIC_SIZE 8 +#define TKIP_EOM_SIZE 7 +#define TKIP_EOM_FLAG 0x5a +#define TKIP_KEY_SIZE 32 +#define TKIP_MIC_AUTH_TX 16 +#define TKIP_MIC_AUTH_RX 24 +#define TKIP_MIC_SUP_RX TKIP_MIC_AUTH_TX +#define TKIP_MIC_SUP_TX TKIP_MIC_AUTH_RX +#define AES_KEY_SIZE 16 +#define AES_MIC_SIZE 8 + +#define SMS4_KEY_LEN 16 +#define SMS4_WPI_CBC_MAC_LEN 16 + + +#include + + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/802.11e.h b/drivers/net/wireless/bcm4329/include/proto/802.11e.h new file mode 100644 index 0000000000000000000000000000000000000000..1dd6f45b1ed8e5382d0fb4ead09113ea0bd6e9a3 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/802.11e.h @@ -0,0 +1,131 @@ +/* + * 802.11e protocol header file + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: 802.11e.h,v 1.5.56.1 2008/11/20 00:51:18 Exp $ + */ + +#ifndef _802_11e_H_ +#define _802_11e_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +/* This marks the start of a packed structure section. */ +#include + + +/* WME Traffic Specification (TSPEC) element */ +#define WME_TSPEC_HDR_LEN 2 /* WME TSPEC header length */ +#define WME_TSPEC_BODY_OFF 2 /* WME TSPEC body offset */ + +#define WME_CATEGORY_CODE_OFFSET 0 /* WME Category code offset */ +#define WME_ACTION_CODE_OFFSET 1 /* WME Action code offset */ +#define WME_TOKEN_CODE_OFFSET 2 /* WME Token code offset */ +#define WME_STATUS_CODE_OFFSET 3 /* WME Status code offset */ + +BWL_PRE_PACKED_STRUCT struct tsinfo { + uint8 octets[3]; +} BWL_POST_PACKED_STRUCT; + +typedef struct tsinfo tsinfo_t; + +/* 802.11e TSPEC IE */ +typedef BWL_PRE_PACKED_STRUCT struct tspec { + uint8 oui[DOT11_OUI_LEN]; /* WME_OUI */ + uint8 type; /* WME_TYPE */ + uint8 subtype; /* WME_SUBTYPE_TSPEC */ + uint8 version; /* WME_VERSION */ + tsinfo_t tsinfo; /* TS Info bit field */ + uint16 nom_msdu_size; /* (Nominal or fixed) MSDU Size (bytes) */ + uint16 max_msdu_size; /* Maximum MSDU Size (bytes) */ + uint32 min_srv_interval; /* Minimum Service Interval (us) */ + uint32 max_srv_interval; /* Maximum Service Interval (us) */ + uint32 inactivity_interval; /* Inactivity Interval (us) */ + uint32 suspension_interval; /* Suspension Interval (us) */ + uint32 srv_start_time; /* Service Start Time (us) */ + uint32 min_data_rate; /* Minimum Data Rate (bps) */ + uint32 mean_data_rate; /* Mean Data Rate (bps) */ + uint32 peak_data_rate; /* Peak Data Rate (bps) */ + uint32 max_burst_size; /* Maximum Burst Size (bytes) */ + uint32 delay_bound; /* Delay Bound (us) */ + uint32 min_phy_rate; /* Minimum PHY Rate (bps) */ + uint16 surplus_bw; /* Surplus Bandwidth Allowance (range 1.0-8.0) */ + uint16 medium_time; /* Medium Time (32 us/s periods) */ +} BWL_POST_PACKED_STRUCT tspec_t; + +#define WME_TSPEC_LEN (sizeof(tspec_t)) /* not including 2-bytes of header */ + +/* ts_info */ +/* 802.1D priority is duplicated - bits 13-11 AND bits 3-1 */ +#define TS_INFO_TID_SHIFT 1 /* TS info. TID shift */ +#define TS_INFO_TID_MASK (0xf << TS_INFO_TID_SHIFT) /* TS info. TID mask */ +#define TS_INFO_CONTENTION_SHIFT 7 /* TS info. contention shift */ +#define TS_INFO_CONTENTION_MASK (0x1 << TS_INFO_CONTENTION_SHIFT) /* TS info. contention mask */ +#define TS_INFO_DIRECTION_SHIFT 5 /* TS info. direction shift */ +#define TS_INFO_DIRECTION_MASK (0x3 << TS_INFO_DIRECTION_SHIFT) /* TS info. direction mask */ +#define TS_INFO_PSB_SHIFT 2 /* TS info. PSB bit Shift */ +#define TS_INFO_PSB_MASK (1 << TS_INFO_PSB_SHIFT) /* TS info. PSB mask */ +#define TS_INFO_UPLINK (0 << TS_INFO_DIRECTION_SHIFT) /* TS info. uplink */ +#define TS_INFO_DOWNLINK (1 << TS_INFO_DIRECTION_SHIFT) /* TS info. downlink */ +#define TS_INFO_BIDIRECTIONAL (3 << TS_INFO_DIRECTION_SHIFT) /* TS info. bidirectional */ +#define TS_INFO_USER_PRIO_SHIFT 3 /* TS info. user priority shift */ +/* TS info. user priority mask */ +#define TS_INFO_USER_PRIO_MASK (0x7 << TS_INFO_USER_PRIO_SHIFT) + +/* Macro to get/set bit(s) field in TSINFO */ +#define WLC_CAC_GET_TID(pt) ((((pt).octets[0]) & TS_INFO_TID_MASK) >> TS_INFO_TID_SHIFT) +#define WLC_CAC_GET_DIR(pt) ((((pt).octets[0]) & \ + TS_INFO_DIRECTION_MASK) >> TS_INFO_DIRECTION_SHIFT) +#define WLC_CAC_GET_PSB(pt) ((((pt).octets[1]) & TS_INFO_PSB_MASK) >> TS_INFO_PSB_SHIFT) +#define WLC_CAC_GET_USER_PRIO(pt) ((((pt).octets[1]) & \ + TS_INFO_USER_PRIO_MASK) >> TS_INFO_USER_PRIO_SHIFT) + +#define WLC_CAC_SET_TID(pt, id) ((((pt).octets[0]) & (~TS_INFO_TID_MASK)) | \ + ((id) << TS_INFO_TID_SHIFT)) +#define WLC_CAC_SET_USER_PRIO(pt, prio) ((((pt).octets[0]) & (~TS_INFO_USER_PRIO_MASK)) | \ + ((prio) << TS_INFO_USER_PRIO_SHIFT)) + +/* 802.11e QBSS Load IE */ +#define QBSS_LOAD_IE_LEN 5 /* QBSS Load IE length */ +#define QBSS_LOAD_AAC_OFF 3 /* AAC offset in IE */ + +#define CAC_ADDTS_RESP_TIMEOUT 300 /* default ADDTS response timeout in ms */ + +/* 802.11e ADDTS status code */ +#define DOT11E_STATUS_ADMISSION_ACCEPTED 0 /* TSPEC Admission accepted status */ +#define DOT11E_STATUS_ADDTS_INVALID_PARAM 1 /* TSPEC invalid parameter status */ +#define DOT11E_STATUS_ADDTS_REFUSED_NSBW 3 /* ADDTS refused (non-sufficient BW) */ +#define DOT11E_STATUS_ADDTS_REFUSED_AWHILE 47 /* ADDTS refused but could retry later */ + +/* 802.11e DELTS status code */ +#define DOT11E_STATUS_QSTA_LEAVE_QBSS 36 /* STA leave QBSS */ +#define DOT11E_STATUS_END_TS 37 /* END TS */ +#define DOT11E_STATUS_UNKNOWN_TS 38 /* UNKNOWN TS */ +#define DOT11E_STATUS_QSTA_REQ_TIMEOUT 39 /* STA ADDTS request timeout */ + + +/* This marks the end of a packed structure section. */ +#include + +#endif /* _802_11e_CAC_H_ */ diff --git a/drivers/net/wireless/bcm4329/include/proto/802.1d.h b/drivers/net/wireless/bcm4329/include/proto/802.1d.h new file mode 100644 index 0000000000000000000000000000000000000000..45c728bc2976975ff498785810052838b9ac9767 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/802.1d.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * Fundamental types and constants relating to 802.1D + * + * $Id: 802.1d.h,v 9.3 2007/04/10 21:33:06 Exp $ + */ + + +#ifndef _802_1_D_ +#define _802_1_D_ + + +#define PRIO_8021D_NONE 2 +#define PRIO_8021D_BK 1 +#define PRIO_8021D_BE 0 +#define PRIO_8021D_EE 3 +#define PRIO_8021D_CL 4 +#define PRIO_8021D_VI 5 +#define PRIO_8021D_VO 6 +#define PRIO_8021D_NC 7 +#define MAXPRIO 7 +#define NUMPRIO (MAXPRIO + 1) + +#define ALLPRIO -1 + + +#define PRIO2PREC(prio) \ + (((prio) == PRIO_8021D_NONE || (prio) == PRIO_8021D_BE) ? ((prio^2)) : (prio)) + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/bcmeth.h b/drivers/net/wireless/bcm4329/include/proto/bcmeth.h new file mode 100644 index 0000000000000000000000000000000000000000..fdb5a2a5648fc84be018feb3f046d8e4c9d7ebc1 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/bcmeth.h @@ -0,0 +1,83 @@ +/* + * Broadcom Ethernettype protocol definitions + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmeth.h,v 9.9.46.1 2008/11/20 00:51:20 Exp $ + */ + + + + +#ifndef _BCMETH_H_ +#define _BCMETH_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + + +#include + + + + + + + +#define BCMILCP_SUBTYPE_RATE 1 +#define BCMILCP_SUBTYPE_LINK 2 +#define BCMILCP_SUBTYPE_CSA 3 +#define BCMILCP_SUBTYPE_LARQ 4 +#define BCMILCP_SUBTYPE_VENDOR 5 +#define BCMILCP_SUBTYPE_FLH 17 + +#define BCMILCP_SUBTYPE_VENDOR_LONG 32769 +#define BCMILCP_SUBTYPE_CERT 32770 +#define BCMILCP_SUBTYPE_SES 32771 + + +#define BCMILCP_BCM_SUBTYPE_RESERVED 0 +#define BCMILCP_BCM_SUBTYPE_EVENT 1 +#define BCMILCP_BCM_SUBTYPE_SES 2 + + +#define BCMILCP_BCM_SUBTYPE_DPT 4 + +#define BCMILCP_BCM_SUBTYPEHDR_MINLENGTH 8 +#define BCMILCP_BCM_SUBTYPEHDR_VERSION 0 + + +typedef BWL_PRE_PACKED_STRUCT struct bcmeth_hdr +{ + uint16 subtype; + uint16 length; + uint8 version; + uint8 oui[3]; + + uint16 usr_subtype; +} BWL_POST_PACKED_STRUCT bcmeth_hdr_t; + + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/bcmevent.h b/drivers/net/wireless/bcm4329/include/proto/bcmevent.h new file mode 100644 index 0000000000000000000000000000000000000000..1f8ecb14d97a05f62ea263595e1530e756c46b09 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/bcmevent.h @@ -0,0 +1,212 @@ +/* + * Broadcom Event protocol definitions + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * + * Dependencies: proto/bcmeth.h + * + * $Id: bcmevent.h,v 9.34.4.1.20.16.64.1 2010/11/08 21:57:03 Exp $ + * + */ + + + + +#ifndef _BCMEVENT_H_ +#define _BCMEVENT_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + + +#include + +#define BCM_EVENT_MSG_VERSION 1 +#define BCM_MSG_IFNAME_MAX 16 + + +#define WLC_EVENT_MSG_LINK 0x01 +#define WLC_EVENT_MSG_FLUSHTXQ 0x02 +#define WLC_EVENT_MSG_GROUP 0x04 + + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint16 version; + uint16 flags; + uint32 event_type; + uint32 status; + uint32 reason; + uint32 auth_type; + uint32 datalen; + struct ether_addr addr; + char ifname[BCM_MSG_IFNAME_MAX]; +} BWL_POST_PACKED_STRUCT wl_event_msg_t; + + +typedef BWL_PRE_PACKED_STRUCT struct bcm_event { + struct ether_header eth; + bcmeth_hdr_t bcm_hdr; + wl_event_msg_t event; + +} BWL_POST_PACKED_STRUCT bcm_event_t; + +#define BCM_MSG_LEN (sizeof(bcm_event_t) - sizeof(bcmeth_hdr_t) - sizeof(struct ether_header)) + + +#define WLC_E_SET_SSID 0 +#define WLC_E_JOIN 1 +#define WLC_E_START 2 +#define WLC_E_AUTH 3 +#define WLC_E_AUTH_IND 4 +#define WLC_E_DEAUTH 5 +#define WLC_E_DEAUTH_IND 6 +#define WLC_E_ASSOC 7 +#define WLC_E_ASSOC_IND 8 +#define WLC_E_REASSOC 9 +#define WLC_E_REASSOC_IND 10 +#define WLC_E_DISASSOC 11 +#define WLC_E_DISASSOC_IND 12 +#define WLC_E_QUIET_START 13 +#define WLC_E_QUIET_END 14 +#define WLC_E_BEACON_RX 15 +#define WLC_E_LINK 16 +#define WLC_E_MIC_ERROR 17 +#define WLC_E_NDIS_LINK 18 +#define WLC_E_ROAM 19 +#define WLC_E_TXFAIL 20 +#define WLC_E_PMKID_CACHE 21 +#define WLC_E_RETROGRADE_TSF 22 +#define WLC_E_PRUNE 23 +#define WLC_E_AUTOAUTH 24 +#define WLC_E_EAPOL_MSG 25 +#define WLC_E_SCAN_COMPLETE 26 +#define WLC_E_ADDTS_IND 27 +#define WLC_E_DELTS_IND 28 +#define WLC_E_BCNSENT_IND 29 +#define WLC_E_BCNRX_MSG 30 +#define WLC_E_BCNLOST_MSG 31 +#define WLC_E_ROAM_PREP 32 +#define WLC_E_PFN_NET_FOUND 33 +#define WLC_E_PFN_NET_LOST 34 +#define WLC_E_RESET_COMPLETE 35 +#define WLC_E_JOIN_START 36 +#define WLC_E_ROAM_START 37 +#define WLC_E_ASSOC_START 38 +#define WLC_E_IBSS_ASSOC 39 +#define WLC_E_RADIO 40 +#define WLC_E_PSM_WATCHDOG 41 +#define WLC_E_PROBREQ_MSG 44 +#define WLC_E_SCAN_CONFIRM_IND 45 +#define WLC_E_PSK_SUP 46 +#define WLC_E_COUNTRY_CODE_CHANGED 47 +#define WLC_E_EXCEEDED_MEDIUM_TIME 48 +#define WLC_E_ICV_ERROR 49 +#define WLC_E_UNICAST_DECODE_ERROR 50 +#define WLC_E_MULTICAST_DECODE_ERROR 51 +#define WLC_E_TRACE 52 +#define WLC_E_IF 54 +#define WLC_E_RSSI 56 +#define WLC_E_PFN_SCAN_COMPLETE 57 +#define WLC_E_ACTION_FRAME 58 +#define WLC_E_ACTION_FRAME_COMPLETE 59 + +#define WLC_E_ESCAN_RESULT 69 +#define WLC_E_WAKE_EVENT 70 +#define WLC_E_RELOAD 71 +#define WLC_E_LAST 72 + + + +#define WLC_E_STATUS_SUCCESS 0 +#define WLC_E_STATUS_FAIL 1 +#define WLC_E_STATUS_TIMEOUT 2 +#define WLC_E_STATUS_NO_NETWORKS 3 +#define WLC_E_STATUS_ABORT 4 +#define WLC_E_STATUS_NO_ACK 5 +#define WLC_E_STATUS_UNSOLICITED 6 +#define WLC_E_STATUS_ATTEMPT 7 +#define WLC_E_STATUS_PARTIAL 8 +#define WLC_E_STATUS_NEWSCAN 9 +#define WLC_E_STATUS_NEWASSOC 10 +#define WLC_E_STATUS_11HQUIET 11 +#define WLC_E_STATUS_SUPPRESS 12 +#define WLC_E_STATUS_NOCHANS 13 +#define WLC_E_STATUS_CCXFASTRM 14 +#define WLC_E_STATUS_CS_ABORT 15 + + +#define WLC_E_REASON_INITIAL_ASSOC 0 +#define WLC_E_REASON_LOW_RSSI 1 +#define WLC_E_REASON_DEAUTH 2 +#define WLC_E_REASON_DISASSOC 3 +#define WLC_E_REASON_BCNS_LOST 4 +#define WLC_E_REASON_FAST_ROAM_FAILED 5 +#define WLC_E_REASON_DIRECTED_ROAM 6 +#define WLC_E_REASON_TSPEC_REJECTED 7 +#define WLC_E_REASON_BETTER_AP 8 + + +#define WLC_E_PRUNE_ENCR_MISMATCH 1 +#define WLC_E_PRUNE_BCAST_BSSID 2 +#define WLC_E_PRUNE_MAC_DENY 3 +#define WLC_E_PRUNE_MAC_NA 4 +#define WLC_E_PRUNE_REG_PASSV 5 +#define WLC_E_PRUNE_SPCT_MGMT 6 +#define WLC_E_PRUNE_RADAR 7 +#define WLC_E_RSN_MISMATCH 8 +#define WLC_E_PRUNE_NO_COMMON_RATES 9 +#define WLC_E_PRUNE_BASIC_RATES 10 +#define WLC_E_PRUNE_CIPHER_NA 12 +#define WLC_E_PRUNE_KNOWN_STA 13 +#define WLC_E_PRUNE_WDS_PEER 15 +#define WLC_E_PRUNE_QBSS_LOAD 16 +#define WLC_E_PRUNE_HOME_AP 17 + + +#define WLC_E_SUP_OTHER 0 +#define WLC_E_SUP_DECRYPT_KEY_DATA 1 +#define WLC_E_SUP_BAD_UCAST_WEP128 2 +#define WLC_E_SUP_BAD_UCAST_WEP40 3 +#define WLC_E_SUP_UNSUP_KEY_LEN 4 +#define WLC_E_SUP_PW_KEY_CIPHER 5 +#define WLC_E_SUP_MSG3_TOO_MANY_IE 6 +#define WLC_E_SUP_MSG3_IE_MISMATCH 7 +#define WLC_E_SUP_NO_INSTALL_FLAG 8 +#define WLC_E_SUP_MSG3_NO_GTK 9 +#define WLC_E_SUP_GRP_KEY_CIPHER 10 +#define WLC_E_SUP_GRP_MSG1_NO_GTK 11 +#define WLC_E_SUP_GTK_DECRYPT_FAIL 12 +#define WLC_E_SUP_SEND_FAIL 13 +#define WLC_E_SUP_DEAUTH 14 +#define WLC_E_SUP_WPA_PSK_TMO 15 + + +#define WLC_E_IF_ADD 1 +#define WLC_E_IF_DEL 2 + +#define WLC_E_RELOAD_STATUS1 1 + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/bcmip.h b/drivers/net/wireless/bcm4329/include/proto/bcmip.h new file mode 100644 index 0000000000000000000000000000000000000000..9d2fd6fba484538682a522c23b9372eb404b9a51 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/bcmip.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * Fundamental constants relating to IP Protocol + * + * $Id: bcmip.h,v 9.16.186.4 2009/01/27 04:25:25 Exp $ + */ + + +#ifndef _bcmip_h_ +#define _bcmip_h_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + + +#include + + + +#define IP_VER_OFFSET 0x0 +#define IP_VER_MASK 0xf0 +#define IP_VER_SHIFT 4 +#define IP_VER_4 4 +#define IP_VER_6 6 + +#define IP_VER(ip_body) \ + ((((uint8 *)(ip_body))[IP_VER_OFFSET] & IP_VER_MASK) >> IP_VER_SHIFT) + +#define IP_PROT_ICMP 0x1 +#define IP_PROT_TCP 0x6 +#define IP_PROT_UDP 0x11 + + +#define IPV4_VER_HL_OFFSET 0 +#define IPV4_TOS_OFFSET 1 +#define IPV4_PKTLEN_OFFSET 2 +#define IPV4_PKTFLAG_OFFSET 6 +#define IPV4_PROT_OFFSET 9 +#define IPV4_CHKSUM_OFFSET 10 +#define IPV4_SRC_IP_OFFSET 12 +#define IPV4_DEST_IP_OFFSET 16 +#define IPV4_OPTIONS_OFFSET 20 + + +#define IPV4_VER_MASK 0xf0 +#define IPV4_VER_SHIFT 4 + +#define IPV4_HLEN_MASK 0x0f +#define IPV4_HLEN(ipv4_body) (4 * (((uint8 *)(ipv4_body))[IPV4_VER_HL_OFFSET] & IPV4_HLEN_MASK)) + +#define IPV4_ADDR_LEN 4 + +#define IPV4_ADDR_NULL(a) ((((uint8 *)(a))[0] | ((uint8 *)(a))[1] | \ + ((uint8 *)(a))[2] | ((uint8 *)(a))[3]) == 0) + +#define IPV4_ADDR_BCAST(a) ((((uint8 *)(a))[0] & ((uint8 *)(a))[1] & \ + ((uint8 *)(a))[2] & ((uint8 *)(a))[3]) == 0xff) + +#define IPV4_TOS_DSCP_MASK 0xfc +#define IPV4_TOS_DSCP_SHIFT 2 + +#define IPV4_TOS(ipv4_body) (((uint8 *)(ipv4_body))[IPV4_TOS_OFFSET]) + +#define IPV4_TOS_PREC_MASK 0xe0 +#define IPV4_TOS_PREC_SHIFT 5 + +#define IPV4_TOS_LOWDELAY 0x10 +#define IPV4_TOS_THROUGHPUT 0x8 +#define IPV4_TOS_RELIABILITY 0x4 + +#define IPV4_PROT(ipv4_body) (((uint8 *)(ipv4_body))[IPV4_PROT_OFFSET]) + +#define IPV4_FRAG_RESV 0x8000 +#define IPV4_FRAG_DONT 0x4000 +#define IPV4_FRAG_MORE 0x2000 +#define IPV4_FRAG_OFFSET_MASK 0x1fff + +#define IPV4_ADDR_STR_LEN 16 + + +BWL_PRE_PACKED_STRUCT struct ipv4_addr { + uint8 addr[IPV4_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct ipv4_hdr { + uint8 version_ihl; + uint8 tos; + uint16 tot_len; + uint16 id; + uint16 frag; + uint8 ttl; + uint8 prot; + uint16 hdr_chksum; + uint8 src_ip[IPV4_ADDR_LEN]; + uint8 dst_ip[IPV4_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; + + +#define IPV6_PAYLOAD_LEN_OFFSET 4 +#define IPV6_NEXT_HDR_OFFSET 6 +#define IPV6_HOP_LIMIT_OFFSET 7 +#define IPV6_SRC_IP_OFFSET 8 +#define IPV6_DEST_IP_OFFSET 24 + + +#define IPV6_TRAFFIC_CLASS(ipv6_body) \ + (((((uint8 *)(ipv6_body))[0] & 0x0f) << 4) | \ + ((((uint8 *)(ipv6_body))[1] & 0xf0) >> 4)) + +#define IPV6_FLOW_LABEL(ipv6_body) \ + (((((uint8 *)(ipv6_body))[1] & 0x0f) << 16) | \ + (((uint8 *)(ipv6_body))[2] << 8) | \ + (((uint8 *)(ipv6_body))[3])) + +#define IPV6_PAYLOAD_LEN(ipv6_body) \ + ((((uint8 *)(ipv6_body))[IPV6_PAYLOAD_LEN_OFFSET + 0] << 8) | \ + ((uint8 *)(ipv6_body))[IPV6_PAYLOAD_LEN_OFFSET + 1]) + +#define IPV6_NEXT_HDR(ipv6_body) \ + (((uint8 *)(ipv6_body))[IPV6_NEXT_HDR_OFFSET]) + +#define IPV6_PROT(ipv6_body) IPV6_NEXT_HDR(ipv6_body) + +#define IPV6_ADDR_LEN 16 + + +#ifndef IP_TOS +#define IP_TOS(ip_body) \ + (IP_VER(ip_body) == IP_VER_4 ? IPV4_TOS(ip_body) : \ + IP_VER(ip_body) == IP_VER_6 ? IPV6_TRAFFIC_CLASS(ip_body) : 0) +#endif + + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/eapol.h b/drivers/net/wireless/bcm4329/include/proto/eapol.h new file mode 100644 index 0000000000000000000000000000000000000000..95e76ff18c6b90aafec06fc1811fd1d909cf0133 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/eapol.h @@ -0,0 +1,172 @@ +/* + * 802.1x EAPOL definitions + * + * See + * IEEE Std 802.1X-2001 + * IEEE 802.1X RADIUS Usage Guidelines + * + * Copyright (C) 2002 Broadcom Corporation + * + * $Id: eapol.h,v 9.18.260.1.2.1.6.6 2009/04/08 05:00:08 Exp $ + */ + +#ifndef _eapol_h_ +#define _eapol_h_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +/* This marks the start of a packed structure section. */ +#include + +#define AKW_BLOCK_LEN 8 /* The only def we need here */ + +/* EAPOL for 802.3/Ethernet */ +typedef struct { + struct ether_header eth; /* 802.3/Ethernet header */ + unsigned char version; /* EAPOL protocol version */ + unsigned char type; /* EAPOL type */ + unsigned short length; /* Length of body */ + unsigned char body[1]; /* Body (optional) */ +} eapol_header_t; + +#define EAPOL_HEADER_LEN 18 + +/* EAPOL version */ +#define WPA2_EAPOL_VERSION 2 +#define WPA_EAPOL_VERSION 1 +#define LEAP_EAPOL_VERSION 1 +#define SES_EAPOL_VERSION 1 + +/* EAPOL types */ +#define EAP_PACKET 0 +#define EAPOL_START 1 +#define EAPOL_LOGOFF 2 +#define EAPOL_KEY 3 +#define EAPOL_ASF 4 + +/* EAPOL-Key types */ +#define EAPOL_RC4_KEY 1 +#define EAPOL_WPA2_KEY 2 /* 802.11i/WPA2 */ +#define EAPOL_WPA_KEY 254 /* WPA */ + +/* RC4 EAPOL-Key header field sizes */ +#define EAPOL_KEY_REPLAY_LEN 8 +#define EAPOL_KEY_IV_LEN 16 +#define EAPOL_KEY_SIG_LEN 16 + +/* RC4 EAPOL-Key */ +typedef BWL_PRE_PACKED_STRUCT struct { + unsigned char type; /* Key Descriptor Type */ + unsigned short length; /* Key Length (unaligned) */ + unsigned char replay[EAPOL_KEY_REPLAY_LEN]; /* Replay Counter */ + unsigned char iv[EAPOL_KEY_IV_LEN]; /* Key IV */ + unsigned char index; /* Key Flags & Index */ + unsigned char signature[EAPOL_KEY_SIG_LEN]; /* Key Signature */ + unsigned char key[1]; /* Key (optional) */ +} BWL_POST_PACKED_STRUCT eapol_key_header_t; + +#define EAPOL_KEY_HEADER_LEN 44 + +/* RC4 EAPOL-Key flags */ +#define EAPOL_KEY_FLAGS_MASK 0x80 +#define EAPOL_KEY_BROADCAST 0 +#define EAPOL_KEY_UNICAST 0x80 + +/* RC4 EAPOL-Key index */ +#define EAPOL_KEY_INDEX_MASK 0x7f + +/* WPA/802.11i/WPA2 EAPOL-Key header field sizes */ +#define EAPOL_WPA_KEY_REPLAY_LEN 8 +#define EAPOL_WPA_KEY_NONCE_LEN 32 +#define EAPOL_WPA_KEY_IV_LEN 16 +#define EAPOL_WPA_KEY_ID_LEN 8 +#define EAPOL_WPA_KEY_RSC_LEN 8 +#define EAPOL_WPA_KEY_MIC_LEN 16 +#define EAPOL_WPA_KEY_DATA_LEN (EAPOL_WPA_MAX_KEY_SIZE + AKW_BLOCK_LEN) +#define EAPOL_WPA_MAX_KEY_SIZE 32 + +/* WPA EAPOL-Key */ +typedef BWL_PRE_PACKED_STRUCT struct { + unsigned char type; /* Key Descriptor Type */ + unsigned short key_info; /* Key Information (unaligned) */ + unsigned short key_len; /* Key Length (unaligned) */ + unsigned char replay[EAPOL_WPA_KEY_REPLAY_LEN]; /* Replay Counter */ + unsigned char nonce[EAPOL_WPA_KEY_NONCE_LEN]; /* Nonce */ + unsigned char iv[EAPOL_WPA_KEY_IV_LEN]; /* Key IV */ + unsigned char rsc[EAPOL_WPA_KEY_RSC_LEN]; /* Key RSC */ + unsigned char id[EAPOL_WPA_KEY_ID_LEN]; /* WPA:Key ID, 802.11i/WPA2: Reserved */ + unsigned char mic[EAPOL_WPA_KEY_MIC_LEN]; /* Key MIC */ + unsigned short data_len; /* Key Data Length */ + unsigned char data[EAPOL_WPA_KEY_DATA_LEN]; /* Key data */ +} BWL_POST_PACKED_STRUCT eapol_wpa_key_header_t; + +#define EAPOL_WPA_KEY_LEN 95 + +/* WPA/802.11i/WPA2 KEY KEY_INFO bits */ +#define WPA_KEY_DESC_V1 0x01 +#define WPA_KEY_DESC_V2 0x02 +#define WPA_KEY_PAIRWISE 0x08 +#define WPA_KEY_INSTALL 0x40 +#define WPA_KEY_ACK 0x80 +#define WPA_KEY_MIC 0x100 +#define WPA_KEY_SECURE 0x200 +#define WPA_KEY_ERROR 0x400 +#define WPA_KEY_REQ 0x800 + +/* WPA-only KEY KEY_INFO bits */ +#define WPA_KEY_INDEX_0 0x00 +#define WPA_KEY_INDEX_1 0x10 +#define WPA_KEY_INDEX_2 0x20 +#define WPA_KEY_INDEX_3 0x30 +#define WPA_KEY_INDEX_MASK 0x30 +#define WPA_KEY_INDEX_SHIFT 0x04 + +/* 802.11i/WPA2-only KEY KEY_INFO bits */ +#define WPA_KEY_ENCRYPTED_DATA 0x1000 + +/* Key Data encapsulation */ +typedef BWL_PRE_PACKED_STRUCT struct { + uint8 type; + uint8 length; + uint8 oui[3]; + uint8 subtype; + uint8 data[1]; +} BWL_POST_PACKED_STRUCT eapol_wpa2_encap_data_t; + +#define EAPOL_WPA2_ENCAP_DATA_HDR_LEN 6 + +#define WPA2_KEY_DATA_SUBTYPE_GTK 1 +#define WPA2_KEY_DATA_SUBTYPE_STAKEY 2 +#define WPA2_KEY_DATA_SUBTYPE_MAC 3 +#define WPA2_KEY_DATA_SUBTYPE_PMKID 4 + +/* GTK encapsulation */ +typedef BWL_PRE_PACKED_STRUCT struct { + uint8 flags; + uint8 reserved; + uint8 gtk[EAPOL_WPA_MAX_KEY_SIZE]; +} BWL_POST_PACKED_STRUCT eapol_wpa2_key_gtk_encap_t; + +#define EAPOL_WPA2_KEY_GTK_ENCAP_HDR_LEN 2 + +#define WPA2_GTK_INDEX_MASK 0x03 +#define WPA2_GTK_INDEX_SHIFT 0x00 + +#define WPA2_GTK_TRANSMIT 0x04 + +/* STAKey encapsulation */ +typedef BWL_PRE_PACKED_STRUCT struct { + uint8 reserved[2]; + uint8 mac[ETHER_ADDR_LEN]; + uint8 stakey[EAPOL_WPA_MAX_KEY_SIZE]; +} BWL_POST_PACKED_STRUCT eapol_wpa2_key_stakey_encap_t; + +#define WPA2_KEY_DATA_PAD 0xdd + + +/* This marks the end of a packed structure section. */ +#include + +#endif /* _eapol_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/proto/ethernet.h b/drivers/net/wireless/bcm4329/include/proto/ethernet.h new file mode 100644 index 0000000000000000000000000000000000000000..9ad2ea0c70fd3f5d2afd43403301e828b8bd3819 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/ethernet.h @@ -0,0 +1,148 @@ +/* + * From FreeBSD 2.2.7: Fundamental constants relating to ethernet. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: ethernet.h,v 9.45.56.5 2010/02/22 22:04:36 Exp $ + */ + + +#ifndef _NET_ETHERNET_H_ +#define _NET_ETHERNET_H_ + +#ifndef _TYPEDEFS_H_ +#include "typedefs.h" +#endif + + +#include + + + +#define ETHER_ADDR_LEN 6 + + +#define ETHER_TYPE_LEN 2 + + +#define ETHER_CRC_LEN 4 + + +#define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN) + + +#define ETHER_MIN_LEN 64 + + +#define ETHER_MIN_DATA 46 + + +#define ETHER_MAX_LEN 1518 + + +#define ETHER_MAX_DATA 1500 + + +#define ETHER_TYPE_MIN 0x0600 +#define ETHER_TYPE_IP 0x0800 +#define ETHER_TYPE_ARP 0x0806 +#define ETHER_TYPE_8021Q 0x8100 +#define ETHER_TYPE_BRCM 0x886c +#define ETHER_TYPE_802_1X 0x888e +#define ETHER_TYPE_WAI 0x88b4 +#ifdef BCMWPA2 +#define ETHER_TYPE_802_1X_PREAUTH 0x88c7 +#endif + + +#define ETHER_BRCM_SUBTYPE_LEN 4 +#define ETHER_BRCM_CRAM 1 + + +#define ETHER_DEST_OFFSET (0 * ETHER_ADDR_LEN) +#define ETHER_SRC_OFFSET (1 * ETHER_ADDR_LEN) +#define ETHER_TYPE_OFFSET (2 * ETHER_ADDR_LEN) + + +#define ETHER_IS_VALID_LEN(foo) \ + ((foo) >= ETHER_MIN_LEN && (foo) <= ETHER_MAX_LEN) + + +#ifndef __INCif_etherh + +BWL_PRE_PACKED_STRUCT struct ether_header { + uint8 ether_dhost[ETHER_ADDR_LEN]; + uint8 ether_shost[ETHER_ADDR_LEN]; + uint16 ether_type; +} BWL_POST_PACKED_STRUCT; + + +BWL_PRE_PACKED_STRUCT struct ether_addr { + uint8 octet[ETHER_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; +#endif + + +#define ETHER_SET_LOCALADDR(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] | 2)) +#define ETHER_IS_LOCALADDR(ea) (((uint8 *)(ea))[0] & 2) +#define ETHER_CLR_LOCALADDR(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] & 0xd)) +#define ETHER_TOGGLE_LOCALADDR(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] ^ 2)) + + +#define ETHER_SET_UNICAST(ea) (((uint8 *)(ea))[0] = (((uint8 *)(ea))[0] & ~1)) + + +#define ETHER_ISMULTI(ea) (((const uint8 *)(ea))[0] & 1) + + + +#define ether_cmp(a, b) (!(((short*)a)[0] == ((short*)b)[0]) | \ + !(((short*)a)[1] == ((short*)b)[1]) | \ + !(((short*)a)[2] == ((short*)b)[2])) + + +#define ether_copy(s, d) { \ + ((short*)d)[0] = ((short*)s)[0]; \ + ((short*)d)[1] = ((short*)s)[1]; \ + ((short*)d)[2] = ((short*)s)[2]; } + + +static const struct ether_addr ether_bcast = {{255, 255, 255, 255, 255, 255}}; +static const struct ether_addr ether_null = {{0, 0, 0, 0, 0, 0}}; + +#define ETHER_ISBCAST(ea) ((((uint8 *)(ea))[0] & \ + ((uint8 *)(ea))[1] & \ + ((uint8 *)(ea))[2] & \ + ((uint8 *)(ea))[3] & \ + ((uint8 *)(ea))[4] & \ + ((uint8 *)(ea))[5]) == 0xff) +#define ETHER_ISNULLADDR(ea) ((((uint8 *)(ea))[0] | \ + ((uint8 *)(ea))[1] | \ + ((uint8 *)(ea))[2] | \ + ((uint8 *)(ea))[3] | \ + ((uint8 *)(ea))[4] | \ + ((uint8 *)(ea))[5]) == 0) + + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/sdspi.h b/drivers/net/wireless/bcm4329/include/proto/sdspi.h new file mode 100644 index 0000000000000000000000000000000000000000..7739e68a24408a688de1556d8887b0679307e44d --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/sdspi.h @@ -0,0 +1,71 @@ +/* + * SD-SPI Protocol Standard + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sdspi.h,v 9.1.20.1 2008/05/06 22:59:19 Exp $ + */ + +#define SPI_START_M BITFIELD_MASK(1) /* Bit [31] - Start Bit */ +#define SPI_START_S 31 +#define SPI_DIR_M BITFIELD_MASK(1) /* Bit [30] - Direction */ +#define SPI_DIR_S 30 +#define SPI_CMD_INDEX_M BITFIELD_MASK(6) /* Bits [29:24] - Command number */ +#define SPI_CMD_INDEX_S 24 +#define SPI_RW_M BITFIELD_MASK(1) /* Bit [23] - Read=0, Write=1 */ +#define SPI_RW_S 23 +#define SPI_FUNC_M BITFIELD_MASK(3) /* Bits [22:20] - Function Number */ +#define SPI_FUNC_S 20 +#define SPI_RAW_M BITFIELD_MASK(1) /* Bit [19] - Read After Wr */ +#define SPI_RAW_S 19 +#define SPI_STUFF_M BITFIELD_MASK(1) /* Bit [18] - Stuff bit */ +#define SPI_STUFF_S 18 +#define SPI_BLKMODE_M BITFIELD_MASK(1) /* Bit [19] - Blockmode 1=blk */ +#define SPI_BLKMODE_S 19 +#define SPI_OPCODE_M BITFIELD_MASK(1) /* Bit [18] - OP Code */ +#define SPI_OPCODE_S 18 +#define SPI_ADDR_M BITFIELD_MASK(17) /* Bits [17:1] - Address */ +#define SPI_ADDR_S 1 +#define SPI_STUFF0_M BITFIELD_MASK(1) /* Bit [0] - Stuff bit */ +#define SPI_STUFF0_S 0 + +#define SPI_RSP_START_M BITFIELD_MASK(1) /* Bit [7] - Start Bit (always 0) */ +#define SPI_RSP_START_S 7 +#define SPI_RSP_PARAM_ERR_M BITFIELD_MASK(1) /* Bit [6] - Parameter Error */ +#define SPI_RSP_PARAM_ERR_S 6 +#define SPI_RSP_RFU5_M BITFIELD_MASK(1) /* Bit [5] - RFU (Always 0) */ +#define SPI_RSP_RFU5_S 5 +#define SPI_RSP_FUNC_ERR_M BITFIELD_MASK(1) /* Bit [4] - Function number error */ +#define SPI_RSP_FUNC_ERR_S 4 +#define SPI_RSP_CRC_ERR_M BITFIELD_MASK(1) /* Bit [3] - COM CRC Error */ +#define SPI_RSP_CRC_ERR_S 3 +#define SPI_RSP_ILL_CMD_M BITFIELD_MASK(1) /* Bit [2] - Illegal Command error */ +#define SPI_RSP_ILL_CMD_S 2 +#define SPI_RSP_RFU1_M BITFIELD_MASK(1) /* Bit [1] - RFU (Always 0) */ +#define SPI_RSP_RFU1_S 1 +#define SPI_RSP_IDLE_M BITFIELD_MASK(1) /* Bit [0] - In idle state */ +#define SPI_RSP_IDLE_S 0 + +/* SD-SPI Protocol Definitions */ +#define SDSPI_COMMAND_LEN 6 /* Number of bytes in an SD command */ +#define SDSPI_START_BLOCK 0xFE /* SD Start Block Token */ +#define SDSPI_IDLE_PAD 0xFF /* SD-SPI idle value for MOSI */ +#define SDSPI_START_BIT_MASK 0x80 diff --git a/drivers/net/wireless/bcm4329/include/proto/vlan.h b/drivers/net/wireless/bcm4329/include/proto/vlan.h new file mode 100644 index 0000000000000000000000000000000000000000..670bc44c6bd6d6e605641c9a3e1f3aa37af9d7e2 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/vlan.h @@ -0,0 +1,63 @@ +/* + * 802.1Q VLAN protocol definitions + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: vlan.h,v 9.4.196.2 2008/12/07 21:19:20 Exp $ + */ + + +#ifndef _vlan_h_ +#define _vlan_h_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + + +#include + +#define VLAN_VID_MASK 0xfff +#define VLAN_CFI_SHIFT 12 +#define VLAN_PRI_SHIFT 13 + +#define VLAN_PRI_MASK 7 + +#define VLAN_TAG_LEN 4 +#define VLAN_TAG_OFFSET (2 * ETHER_ADDR_LEN) + +#define VLAN_TPID 0x8100 + +struct ethervlan_header { + uint8 ether_dhost[ETHER_ADDR_LEN]; + uint8 ether_shost[ETHER_ADDR_LEN]; + uint16 vlan_type; + uint16 vlan_tag; + uint16 ether_type; +}; + +#define ETHERVLAN_HDR_LEN (ETHER_HDR_LEN + VLAN_TAG_LEN) + + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/proto/wpa.h b/drivers/net/wireless/bcm4329/include/proto/wpa.h new file mode 100644 index 0000000000000000000000000000000000000000..f5d0cd539777221e6918d0584e3088d785182276 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/proto/wpa.h @@ -0,0 +1,159 @@ +/* + * Fundamental types and constants relating to WPA + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: wpa.h,v 1.16.166.1.20.1 2008/11/20 00:51:31 Exp $ + */ + + +#ifndef _proto_wpa_h_ +#define _proto_wpa_h_ + +#include +#include + + + +#include + + + + +#define DOT11_RC_INVALID_WPA_IE 13 +#define DOT11_RC_MIC_FAILURE 14 +#define DOT11_RC_4WH_TIMEOUT 15 +#define DOT11_RC_GTK_UPDATE_TIMEOUT 16 +#define DOT11_RC_WPA_IE_MISMATCH 17 +#define DOT11_RC_INVALID_MC_CIPHER 18 +#define DOT11_RC_INVALID_UC_CIPHER 19 +#define DOT11_RC_INVALID_AKMP 20 +#define DOT11_RC_BAD_WPA_VERSION 21 +#define DOT11_RC_INVALID_WPA_CAP 22 +#define DOT11_RC_8021X_AUTH_FAIL 23 + +#define WPA2_PMKID_LEN 16 + + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint8 tag; + uint8 length; + uint8 oui[3]; + uint8 oui_type; + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT version; +} BWL_POST_PACKED_STRUCT wpa_ie_fixed_t; +#define WPA_IE_OUITYPE_LEN 4 +#define WPA_IE_FIXED_LEN 8 +#define WPA_IE_TAG_FIXED_LEN 6 + +typedef BWL_PRE_PACKED_STRUCT struct { + uint8 tag; + uint8 length; + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT version; +} BWL_POST_PACKED_STRUCT wpa_rsn_ie_fixed_t; +#define WPA_RSN_IE_FIXED_LEN 4 +#define WPA_RSN_IE_TAG_FIXED_LEN 2 +typedef uint8 wpa_pmkid_t[WPA2_PMKID_LEN]; + + +typedef BWL_PRE_PACKED_STRUCT struct +{ + uint8 oui[3]; + uint8 type; +} BWL_POST_PACKED_STRUCT wpa_suite_t, wpa_suite_mcast_t; +#define WPA_SUITE_LEN 4 + + +typedef BWL_PRE_PACKED_STRUCT struct +{ + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT count; + wpa_suite_t list[1]; +} BWL_POST_PACKED_STRUCT wpa_suite_ucast_t, wpa_suite_auth_key_mgmt_t; +#define WPA_IE_SUITE_COUNT_LEN 2 +typedef BWL_PRE_PACKED_STRUCT struct +{ + BWL_PRE_PACKED_STRUCT struct { + uint8 low; + uint8 high; + } BWL_POST_PACKED_STRUCT count; + wpa_pmkid_t list[1]; +} BWL_POST_PACKED_STRUCT wpa_pmkid_list_t; + + +#define WPA_CIPHER_NONE 0 +#define WPA_CIPHER_WEP_40 1 +#define WPA_CIPHER_TKIP 2 +#define WPA_CIPHER_AES_OCB 3 +#define WPA_CIPHER_AES_CCM 4 +#define WPA_CIPHER_WEP_104 5 + +#define IS_WPA_CIPHER(cipher) ((cipher) == WPA_CIPHER_NONE || \ + (cipher) == WPA_CIPHER_WEP_40 || \ + (cipher) == WPA_CIPHER_WEP_104 || \ + (cipher) == WPA_CIPHER_TKIP || \ + (cipher) == WPA_CIPHER_AES_OCB || \ + (cipher) == WPA_CIPHER_AES_CCM) + + +#define WPA_TKIP_CM_DETECT 60 +#define WPA_TKIP_CM_BLOCK 60 + + +#define RSN_CAP_LEN 2 + + +#define RSN_CAP_PREAUTH 0x0001 +#define RSN_CAP_NOPAIRWISE 0x0002 +#define RSN_CAP_PTK_REPLAY_CNTR_MASK 0x000C +#define RSN_CAP_PTK_REPLAY_CNTR_SHIFT 2 +#define RSN_CAP_GTK_REPLAY_CNTR_MASK 0x0030 +#define RSN_CAP_GTK_REPLAY_CNTR_SHIFT 4 +#define RSN_CAP_1_REPLAY_CNTR 0 +#define RSN_CAP_2_REPLAY_CNTRS 1 +#define RSN_CAP_4_REPLAY_CNTRS 2 +#define RSN_CAP_16_REPLAY_CNTRS 3 + + +#define WPA_CAP_4_REPLAY_CNTRS RSN_CAP_4_REPLAY_CNTRS +#define WPA_CAP_16_REPLAY_CNTRS RSN_CAP_16_REPLAY_CNTRS +#define WPA_CAP_REPLAY_CNTR_SHIFT RSN_CAP_PTK_REPLAY_CNTR_SHIFT +#define WPA_CAP_REPLAY_CNTR_MASK RSN_CAP_PTK_REPLAY_CNTR_MASK + + +#define WPA_CAP_LEN RSN_CAP_LEN + +#define WPA_CAP_WPA2_PREAUTH RSN_CAP_PREAUTH + + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/sbchipc.h b/drivers/net/wireless/bcm4329/include/sbchipc.h new file mode 100644 index 0000000000000000000000000000000000000000..39e5c8d6aed0c11ad2fdbffd71734b0ddefdbeae --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbchipc.h @@ -0,0 +1,1026 @@ +/* + * SiliconBackplane Chipcommon core hardware definitions. + * + * The chipcommon core provides chip identification, SB control, + * jtag, 0/1/2 uarts, clock frequency control, a watchdog interrupt timer, + * gpio interface, extbus, and support for serial and parallel flashes. + * + * $Id: sbchipc.h,v 13.103.2.5.4.5.2.9 2009/07/03 14:23:21 Exp $ + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + */ + + +#ifndef _SBCHIPC_H +#define _SBCHIPC_H + +#ifndef _LANGUAGE_ASSEMBLY + + +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif + +typedef volatile struct { + uint32 chipid; + uint32 capabilities; + uint32 corecontrol; + uint32 bist; + + + uint32 otpstatus; + uint32 otpcontrol; + uint32 otpprog; + uint32 PAD; + + + uint32 intstatus; + uint32 intmask; + uint32 chipcontrol; + uint32 chipstatus; + + + uint32 jtagcmd; + uint32 jtagir; + uint32 jtagdr; + uint32 jtagctrl; + + + uint32 flashcontrol; + uint32 flashaddress; + uint32 flashdata; + uint32 PAD[1]; + + + uint32 broadcastaddress; + uint32 broadcastdata; + + + uint32 gpiopullup; + uint32 gpiopulldown; + uint32 gpioin; + uint32 gpioout; + uint32 gpioouten; + uint32 gpiocontrol; + uint32 gpiointpolarity; + uint32 gpiointmask; + + + uint32 gpioevent; + uint32 gpioeventintmask; + + + uint32 watchdog; + + + uint32 gpioeventintpolarity; + + + uint32 gpiotimerval; + uint32 gpiotimeroutmask; + + + uint32 clockcontrol_n; + uint32 clockcontrol_sb; + uint32 clockcontrol_pci; + uint32 clockcontrol_m2; + uint32 clockcontrol_m3; + uint32 clkdiv; + uint32 PAD[2]; + + + uint32 pll_on_delay; + uint32 fref_sel_delay; + uint32 slow_clk_ctl; + uint32 PAD[1]; + + + uint32 system_clk_ctl; + uint32 clkstatestretch; + uint32 PAD[13]; + + + uint32 eromptr; + + + uint32 pcmcia_config; + uint32 pcmcia_memwait; + uint32 pcmcia_attrwait; + uint32 pcmcia_iowait; + uint32 ide_config; + uint32 ide_memwait; + uint32 ide_attrwait; + uint32 ide_iowait; + uint32 prog_config; + uint32 prog_waitcount; + uint32 flash_config; + uint32 flash_waitcount; + uint32 PAD[4]; + uint32 PAD[40]; + + + + uint32 clk_ctl_st; + uint32 hw_war; + uint32 PAD[70]; + + + uint8 uart0data; + uint8 uart0imr; + uint8 uart0fcr; + uint8 uart0lcr; + uint8 uart0mcr; + uint8 uart0lsr; + uint8 uart0msr; + uint8 uart0scratch; + uint8 PAD[248]; + + uint8 uart1data; + uint8 uart1imr; + uint8 uart1fcr; + uint8 uart1lcr; + uint8 uart1mcr; + uint8 uart1lsr; + uint8 uart1msr; + uint8 uart1scratch; + uint32 PAD[126]; + + + uint32 pmucontrol; + uint32 pmucapabilities; + uint32 pmustatus; + uint32 res_state; + uint32 res_pending; + uint32 pmutimer; + uint32 min_res_mask; + uint32 max_res_mask; + uint32 res_table_sel; + uint32 res_dep_mask; + uint32 res_updn_timer; + uint32 res_timer; + uint32 clkstretch; + uint32 pmuwatchdog; + uint32 gpiosel; + uint32 gpioenable; + uint32 res_req_timer_sel; + uint32 res_req_timer; + uint32 res_req_mask; + uint32 PAD; + uint32 chipcontrol_addr; + uint32 chipcontrol_data; + uint32 regcontrol_addr; + uint32 regcontrol_data; + uint32 pllcontrol_addr; + uint32 pllcontrol_data; + uint32 PAD[102]; + uint16 otp[768]; +} chipcregs_t; + +#endif + +#define CC_CHIPID 0 +#define CC_CAPABILITIES 4 +#define CC_OTPST 0x10 +#define CC_CHIPST 0x2c +#define CC_JTAGCMD 0x30 +#define CC_JTAGIR 0x34 +#define CC_JTAGDR 0x38 +#define CC_JTAGCTRL 0x3c +#define CC_WATCHDOG 0x80 +#define CC_CLKC_N 0x90 +#define CC_CLKC_M0 0x94 +#define CC_CLKC_M1 0x98 +#define CC_CLKC_M2 0x9c +#define CC_CLKC_M3 0xa0 +#define CC_CLKDIV 0xa4 +#define CC_SYS_CLK_CTL 0xc0 +#define CC_CLK_CTL_ST SI_CLK_CTL_ST +#define CC_EROMPTR 0xfc +#define PMU_CTL 0x600 +#define PMU_CAP 0x604 +#define PMU_ST 0x608 +#define PMU_RES_STATE 0x60c +#define PMU_TIMER 0x614 +#define PMU_MIN_RES_MASK 0x618 +#define PMU_MAX_RES_MASK 0x61c +#define PMU_REG_CONTROL_ADDR 0x658 +#define PMU_REG_CONTROL_DATA 0x65C +#define PMU_PLL_CONTROL_ADDR 0x660 +#define PMU_PLL_CONTROL_DATA 0x664 +#define CC_OTP 0x800 + + +#define CID_ID_MASK 0x0000ffff +#define CID_REV_MASK 0x000f0000 +#define CID_REV_SHIFT 16 +#define CID_PKG_MASK 0x00f00000 +#define CID_PKG_SHIFT 20 +#define CID_CC_MASK 0x0f000000 +#define CID_CC_SHIFT 24 +#define CID_TYPE_MASK 0xf0000000 +#define CID_TYPE_SHIFT 28 + + +#define CC_CAP_UARTS_MASK 0x00000003 +#define CC_CAP_MIPSEB 0x00000004 +#define CC_CAP_UCLKSEL 0x00000018 +#define CC_CAP_UINTCLK 0x00000008 +#define CC_CAP_UARTGPIO 0x00000020 +#define CC_CAP_EXTBUS_MASK 0x000000c0 +#define CC_CAP_EXTBUS_NONE 0x00000000 +#define CC_CAP_EXTBUS_FULL 0x00000040 +#define CC_CAP_EXTBUS_PROG 0x00000080 +#define CC_CAP_FLASH_MASK 0x00000700 +#define CC_CAP_PLL_MASK 0x00038000 +#define CC_CAP_PWR_CTL 0x00040000 +#define CC_CAP_OTPSIZE 0x00380000 +#define CC_CAP_OTPSIZE_SHIFT 19 +#define CC_CAP_OTPSIZE_BASE 5 +#define CC_CAP_JTAGP 0x00400000 +#define CC_CAP_ROM 0x00800000 +#define CC_CAP_BKPLN64 0x08000000 +#define CC_CAP_PMU 0x10000000 +#define CC_CAP_ECI 0x20000000 + + +#define PLL_NONE 0x00000000 +#define PLL_TYPE1 0x00010000 +#define PLL_TYPE2 0x00020000 +#define PLL_TYPE3 0x00030000 +#define PLL_TYPE4 0x00008000 +#define PLL_TYPE5 0x00018000 +#define PLL_TYPE6 0x00028000 +#define PLL_TYPE7 0x00038000 + + +#define ILP_CLOCK 32000 + + +#define ALP_CLOCK 20000000 + + +#define HT_CLOCK 80000000 + + +#define CC_UARTCLKO 0x00000001 +#define CC_SE 0x00000002 +#define CC_UARTCLKEN 0x00000008 + + +#define CHIPCTRL_4321A0_DEFAULT 0x3a4 +#define CHIPCTRL_4321A1_DEFAULT 0x0a4 +#define CHIPCTRL_4321_PLL_DOWN 0x800000 + + +#define OTPS_OL_MASK 0x000000ff +#define OTPS_OL_MFG 0x00000001 +#define OTPS_OL_OR1 0x00000002 +#define OTPS_OL_OR2 0x00000004 +#define OTPS_OL_GU 0x00000008 +#define OTPS_GUP_MASK 0x00000f00 +#define OTPS_GUP_SHIFT 8 +#define OTPS_GUP_HW 0x00000100 +#define OTPS_GUP_SW 0x00000200 +#define OTPS_GUP_CI 0x00000400 +#define OTPS_GUP_FUSE 0x00000800 +#define OTPS_READY 0x00001000 +#define OTPS_RV(x) (1 << (16 + (x))) +#define OTPS_RV_MASK 0x0fff0000 + + +#define OTPC_PROGSEL 0x00000001 +#define OTPC_PCOUNT_MASK 0x0000000e +#define OTPC_PCOUNT_SHIFT 1 +#define OTPC_VSEL_MASK 0x000000f0 +#define OTPC_VSEL_SHIFT 4 +#define OTPC_TMM_MASK 0x00000700 +#define OTPC_TMM_SHIFT 8 +#define OTPC_ODM 0x00000800 +#define OTPC_PROGEN 0x80000000 + + +#define OTPP_COL_MASK 0x000000ff +#define OTPP_COL_SHIFT 0 +#define OTPP_ROW_MASK 0x0000ff00 +#define OTPP_ROW_SHIFT 8 +#define OTPP_OC_MASK 0x0f000000 +#define OTPP_OC_SHIFT 24 +#define OTPP_READERR 0x10000000 +#define OTPP_VALUE_MASK 0x20000000 +#define OTPP_VALUE_SHIFT 29 +#define OTPP_START_BUSY 0x80000000 + + +#define OTPPOC_READ 0 +#define OTPPOC_BIT_PROG 1 +#define OTPPOC_VERIFY 3 +#define OTPPOC_INIT 4 +#define OTPPOC_SET 5 +#define OTPPOC_RESET 6 +#define OTPPOC_OCST 7 +#define OTPPOC_ROW_LOCK 8 +#define OTPPOC_PRESCN_TEST 9 + + +#define JCMD_START 0x80000000 +#define JCMD_BUSY 0x80000000 +#define JCMD_STATE_MASK 0x60000000 +#define JCMD_STATE_TLR 0x00000000 +#define JCMD_STATE_PIR 0x20000000 +#define JCMD_STATE_PDR 0x40000000 +#define JCMD_STATE_RTI 0x60000000 +#define JCMD0_ACC_MASK 0x0000f000 +#define JCMD0_ACC_IRDR 0x00000000 +#define JCMD0_ACC_DR 0x00001000 +#define JCMD0_ACC_IR 0x00002000 +#define JCMD0_ACC_RESET 0x00003000 +#define JCMD0_ACC_IRPDR 0x00004000 +#define JCMD0_ACC_PDR 0x00005000 +#define JCMD0_IRW_MASK 0x00000f00 +#define JCMD_ACC_MASK 0x000f0000 +#define JCMD_ACC_IRDR 0x00000000 +#define JCMD_ACC_DR 0x00010000 +#define JCMD_ACC_IR 0x00020000 +#define JCMD_ACC_RESET 0x00030000 +#define JCMD_ACC_IRPDR 0x00040000 +#define JCMD_ACC_PDR 0x00050000 +#define JCMD_ACC_PIR 0x00060000 +#define JCMD_ACC_IRDR_I 0x00070000 +#define JCMD_ACC_DR_I 0x00080000 +#define JCMD_IRW_MASK 0x00001f00 +#define JCMD_IRW_SHIFT 8 +#define JCMD_DRW_MASK 0x0000003f + + +#define JCTRL_FORCE_CLK 4 +#define JCTRL_EXT_EN 2 +#define JCTRL_EN 1 + + +#define CLKD_SFLASH 0x0f000000 +#define CLKD_SFLASH_SHIFT 24 +#define CLKD_OTP 0x000f0000 +#define CLKD_OTP_SHIFT 16 +#define CLKD_JTAG 0x00000f00 +#define CLKD_JTAG_SHIFT 8 +#define CLKD_UART 0x000000ff + + +#define CI_GPIO 0x00000001 +#define CI_EI 0x00000002 +#define CI_TEMP 0x00000004 +#define CI_SIRQ 0x00000008 +#define CI_ECI 0x00000010 +#define CI_PMU 0x00000020 +#define CI_UART 0x00000040 +#define CI_WDRESET 0x80000000 + + +#define SCC_SS_MASK 0x00000007 +#define SCC_SS_LPO 0x00000000 +#define SCC_SS_XTAL 0x00000001 +#define SCC_SS_PCI 0x00000002 +#define SCC_LF 0x00000200 +#define SCC_LP 0x00000400 +#define SCC_FS 0x00000800 +#define SCC_IP 0x00001000 +#define SCC_XC 0x00002000 +#define SCC_XP 0x00004000 +#define SCC_CD_MASK 0xffff0000 +#define SCC_CD_SHIFT 16 + + +#define SYCC_IE 0x00000001 +#define SYCC_AE 0x00000002 +#define SYCC_FP 0x00000004 +#define SYCC_AR 0x00000008 +#define SYCC_HR 0x00000010 +#define SYCC_CD_MASK 0xffff0000 +#define SYCC_CD_SHIFT 16 + + +#define CF_EN 0x00000001 +#define CF_EM_MASK 0x0000000e +#define CF_EM_SHIFT 1 +#define CF_EM_FLASH 0 +#define CF_EM_SYNC 2 +#define CF_EM_PCMCIA 4 +#define CF_DS 0x00000010 +#define CF_BS 0x00000020 +#define CF_CD_MASK 0x000000c0 +#define CF_CD_SHIFT 6 +#define CF_CD_DIV2 0x00000000 +#define CF_CD_DIV3 0x00000040 +#define CF_CD_DIV4 0x00000080 +#define CF_CE 0x00000100 +#define CF_SB 0x00000200 + + +#define PM_W0_MASK 0x0000003f +#define PM_W1_MASK 0x00001f00 +#define PM_W1_SHIFT 8 +#define PM_W2_MASK 0x001f0000 +#define PM_W2_SHIFT 16 +#define PM_W3_MASK 0x1f000000 +#define PM_W3_SHIFT 24 + + +#define PA_W0_MASK 0x0000003f +#define PA_W1_MASK 0x00001f00 +#define PA_W1_SHIFT 8 +#define PA_W2_MASK 0x001f0000 +#define PA_W2_SHIFT 16 +#define PA_W3_MASK 0x1f000000 +#define PA_W3_SHIFT 24 + + +#define PI_W0_MASK 0x0000003f +#define PI_W1_MASK 0x00001f00 +#define PI_W1_SHIFT 8 +#define PI_W2_MASK 0x001f0000 +#define PI_W2_SHIFT 16 +#define PI_W3_MASK 0x1f000000 +#define PI_W3_SHIFT 24 + + +#define PW_W0_MASK 0x0000001f +#define PW_W1_MASK 0x00001f00 +#define PW_W1_SHIFT 8 +#define PW_W2_MASK 0x001f0000 +#define PW_W2_SHIFT 16 +#define PW_W3_MASK 0x1f000000 +#define PW_W3_SHIFT 24 + +#define PW_W0 0x0000000c +#define PW_W1 0x00000a00 +#define PW_W2 0x00020000 +#define PW_W3 0x01000000 + + +#define FW_W0_MASK 0x0000003f +#define FW_W1_MASK 0x00001f00 +#define FW_W1_SHIFT 8 +#define FW_W2_MASK 0x001f0000 +#define FW_W2_SHIFT 16 +#define FW_W3_MASK 0x1f000000 +#define FW_W3_SHIFT 24 + + +#define WATCHDOG_CLOCK 48000000 +#define WATCHDOG_CLOCK_5354 32000 + + +#define PCTL_ILP_DIV_MASK 0xffff0000 +#define PCTL_ILP_DIV_SHIFT 16 +#define PCTL_PLL_PLLCTL_UPD 0x00000400 +#define PCTL_NOILP_ON_WAIT 0x00000200 +#define PCTL_HT_REQ_EN 0x00000100 +#define PCTL_ALP_REQ_EN 0x00000080 +#define PCTL_XTALFREQ_MASK 0x0000007c +#define PCTL_XTALFREQ_SHIFT 2 +#define PCTL_ILP_DIV_EN 0x00000002 +#define PCTL_LPO_SEL 0x00000001 + + +#define CSTRETCH_HT 0xffff0000 +#define CSTRETCH_ALP 0x0000ffff + + +#define GPIO_ONTIME_SHIFT 16 + + +#define CN_N1_MASK 0x3f +#define CN_N2_MASK 0x3f00 +#define CN_N2_SHIFT 8 +#define CN_PLLC_MASK 0xf0000 +#define CN_PLLC_SHIFT 16 + + +#define CC_M1_MASK 0x3f +#define CC_M2_MASK 0x3f00 +#define CC_M2_SHIFT 8 +#define CC_M3_MASK 0x3f0000 +#define CC_M3_SHIFT 16 +#define CC_MC_MASK 0x1f000000 +#define CC_MC_SHIFT 24 + + +#define CC_F6_2 0x02 +#define CC_F6_3 0x03 +#define CC_F6_4 0x05 +#define CC_F6_5 0x09 +#define CC_F6_6 0x11 +#define CC_F6_7 0x21 + +#define CC_F5_BIAS 5 + +#define CC_MC_BYPASS 0x08 +#define CC_MC_M1 0x04 +#define CC_MC_M1M2 0x02 +#define CC_MC_M1M2M3 0x01 +#define CC_MC_M1M3 0x11 + + +#define CC_T2_BIAS 2 +#define CC_T2M2_BIAS 3 + +#define CC_T2MC_M1BYP 1 +#define CC_T2MC_M2BYP 2 +#define CC_T2MC_M3BYP 4 + + +#define CC_T6_MMASK 1 +#define CC_T6_M0 120000000 +#define CC_T6_M1 100000000 +#define SB2MIPS_T6(sb) (2 * (sb)) + + +#define CC_CLOCK_BASE1 24000000 +#define CC_CLOCK_BASE2 12500000 + + +#define CLKC_5350_N 0x0311 +#define CLKC_5350_M 0x04020009 + + +#define FLASH_NONE 0x000 +#define SFLASH_ST 0x100 +#define SFLASH_AT 0x200 +#define PFLASH 0x700 + + +#define CC_CFG_EN 0x0001 +#define CC_CFG_EM_MASK 0x000e +#define CC_CFG_EM_ASYNC 0x0000 +#define CC_CFG_EM_SYNC 0x0002 +#define CC_CFG_EM_PCMCIA 0x0004 +#define CC_CFG_EM_IDE 0x0006 +#define CC_CFG_DS 0x0010 +#define CC_CFG_CD_MASK 0x00e0 +#define CC_CFG_CE 0x0100 +#define CC_CFG_SB 0x0200 +#define CC_CFG_IS 0x0400 + + +#define CC_EB_BASE 0x1a000000 +#define CC_EB_PCMCIA_MEM 0x1a000000 +#define CC_EB_PCMCIA_IO 0x1a200000 +#define CC_EB_PCMCIA_CFG 0x1a400000 +#define CC_EB_IDE 0x1a800000 +#define CC_EB_PCMCIA1_MEM 0x1a800000 +#define CC_EB_PCMCIA1_IO 0x1aa00000 +#define CC_EB_PCMCIA1_CFG 0x1ac00000 +#define CC_EB_PROGIF 0x1b000000 + + + +#define SFLASH_OPCODE 0x000000ff +#define SFLASH_ACTION 0x00000700 +#define SFLASH_CS_ACTIVE 0x00001000 +#define SFLASH_START 0x80000000 +#define SFLASH_BUSY SFLASH_START + + +#define SFLASH_ACT_OPONLY 0x0000 +#define SFLASH_ACT_OP1D 0x0100 +#define SFLASH_ACT_OP3A 0x0200 +#define SFLASH_ACT_OP3A1D 0x0300 +#define SFLASH_ACT_OP3A4D 0x0400 +#define SFLASH_ACT_OP3A4X4D 0x0500 +#define SFLASH_ACT_OP3A1X4D 0x0700 + + +#define SFLASH_ST_WREN 0x0006 +#define SFLASH_ST_WRDIS 0x0004 +#define SFLASH_ST_RDSR 0x0105 +#define SFLASH_ST_WRSR 0x0101 +#define SFLASH_ST_READ 0x0303 +#define SFLASH_ST_PP 0x0302 +#define SFLASH_ST_SE 0x02d8 +#define SFLASH_ST_BE 0x00c7 +#define SFLASH_ST_DP 0x00b9 +#define SFLASH_ST_RES 0x03ab +#define SFLASH_ST_CSA 0x1000 + + +#define SFLASH_ST_WIP 0x01 +#define SFLASH_ST_WEL 0x02 +#define SFLASH_ST_BP_MASK 0x1c +#define SFLASH_ST_BP_SHIFT 2 +#define SFLASH_ST_SRWD 0x80 + + +#define SFLASH_AT_READ 0x07e8 +#define SFLASH_AT_PAGE_READ 0x07d2 +#define SFLASH_AT_BUF1_READ +#define SFLASH_AT_BUF2_READ +#define SFLASH_AT_STATUS 0x01d7 +#define SFLASH_AT_BUF1_WRITE 0x0384 +#define SFLASH_AT_BUF2_WRITE 0x0387 +#define SFLASH_AT_BUF1_ERASE_PROGRAM 0x0283 +#define SFLASH_AT_BUF2_ERASE_PROGRAM 0x0286 +#define SFLASH_AT_BUF1_PROGRAM 0x0288 +#define SFLASH_AT_BUF2_PROGRAM 0x0289 +#define SFLASH_AT_PAGE_ERASE 0x0281 +#define SFLASH_AT_BLOCK_ERASE 0x0250 +#define SFLASH_AT_BUF1_WRITE_ERASE_PROGRAM 0x0382 +#define SFLASH_AT_BUF2_WRITE_ERASE_PROGRAM 0x0385 +#define SFLASH_AT_BUF1_LOAD 0x0253 +#define SFLASH_AT_BUF2_LOAD 0x0255 +#define SFLASH_AT_BUF1_COMPARE 0x0260 +#define SFLASH_AT_BUF2_COMPARE 0x0261 +#define SFLASH_AT_BUF1_REPROGRAM 0x0258 +#define SFLASH_AT_BUF2_REPROGRAM 0x0259 + + +#define SFLASH_AT_READY 0x80 +#define SFLASH_AT_MISMATCH 0x40 +#define SFLASH_AT_ID_MASK 0x38 +#define SFLASH_AT_ID_SHIFT 3 + + + +#define UART_RX 0 +#define UART_TX 0 +#define UART_DLL 0 +#define UART_IER 1 +#define UART_DLM 1 +#define UART_IIR 2 +#define UART_FCR 2 +#define UART_LCR 3 +#define UART_MCR 4 +#define UART_LSR 5 +#define UART_MSR 6 +#define UART_SCR 7 +#define UART_LCR_DLAB 0x80 +#define UART_LCR_WLEN8 0x03 +#define UART_MCR_OUT2 0x08 +#define UART_MCR_LOOP 0x10 +#define UART_LSR_RX_FIFO 0x80 +#define UART_LSR_TDHR 0x40 +#define UART_LSR_THRE 0x20 +#define UART_LSR_BREAK 0x10 +#define UART_LSR_FRAMING 0x08 +#define UART_LSR_PARITY 0x04 +#define UART_LSR_OVERRUN 0x02 +#define UART_LSR_RXRDY 0x01 +#define UART_FCR_FIFO_ENABLE 1 + + +#define UART_IIR_FIFO_MASK 0xc0 +#define UART_IIR_INT_MASK 0xf +#define UART_IIR_MDM_CHG 0x0 +#define UART_IIR_NOINT 0x1 +#define UART_IIR_THRE 0x2 +#define UART_IIR_RCVD_DATA 0x4 +#define UART_IIR_RCVR_STATUS 0x6 +#define UART_IIR_CHAR_TIME 0xc + + +#define UART_IER_EDSSI 8 +#define UART_IER_ELSI 4 +#define UART_IER_ETBEI 2 +#define UART_IER_ERBFI 1 + + +#define PST_INTPEND 0x0040 +#define PST_SBCLKST 0x0030 +#define PST_SBCLKST_ILP 0x0010 +#define PST_SBCLKST_ALP 0x0020 +#define PST_SBCLKST_HT 0x0030 +#define PST_ALPAVAIL 0x0008 +#define PST_HTAVAIL 0x0004 +#define PST_RESINIT 0x0003 + + +#define PCAP_REV_MASK 0x000000ff +#define PCAP_RC_MASK 0x00001f00 +#define PCAP_RC_SHIFT 8 +#define PCAP_TC_MASK 0x0001e000 +#define PCAP_TC_SHIFT 13 +#define PCAP_PC_MASK 0x001e0000 +#define PCAP_PC_SHIFT 17 +#define PCAP_VC_MASK 0x01e00000 +#define PCAP_VC_SHIFT 21 +#define PCAP_CC_MASK 0x1e000000 +#define PCAP_CC_SHIFT 25 +#define PCAP5_PC_MASK 0x003e0000 +#define PCAP5_PC_SHIFT 17 +#define PCAP5_VC_MASK 0x07c00000 +#define PCAP5_VC_SHIFT 22 +#define PCAP5_CC_MASK 0xf8000000 +#define PCAP5_CC_SHIFT 27 + + + +#define PRRT_TIME_MASK 0x03ff +#define PRRT_INTEN 0x0400 +#define PRRT_REQ_ACTIVE 0x0800 +#define PRRT_ALP_REQ 0x1000 +#define PRRT_HT_REQ 0x2000 + + +#define PMURES_BIT(bit) (1 << (bit)) + + +#define PMURES_MAX_RESNUM 30 + + + + +#define PMU0_PLL0_PLLCTL0 0 +#define PMU0_PLL0_PC0_PDIV_MASK 1 +#define PMU0_PLL0_PC0_PDIV_FREQ 25000 +#define PMU0_PLL0_PC0_DIV_ARM_MASK 0x00000038 +#define PMU0_PLL0_PC0_DIV_ARM_SHIFT 3 +#define PMU0_PLL0_PC0_DIV_ARM_BASE 8 + + +#define PMU0_PLL0_PC0_DIV_ARM_110MHZ 0 +#define PMU0_PLL0_PC0_DIV_ARM_97_7MHZ 1 +#define PMU0_PLL0_PC0_DIV_ARM_88MHZ 2 +#define PMU0_PLL0_PC0_DIV_ARM_80MHZ 3 +#define PMU0_PLL0_PC0_DIV_ARM_73_3MHZ 4 +#define PMU0_PLL0_PC0_DIV_ARM_67_7MHZ 5 +#define PMU0_PLL0_PC0_DIV_ARM_62_9MHZ 6 +#define PMU0_PLL0_PC0_DIV_ARM_58_6MHZ 7 + + +#define PMU0_PLL0_PLLCTL1 1 +#define PMU0_PLL0_PC1_WILD_INT_MASK 0xf0000000 +#define PMU0_PLL0_PC1_WILD_INT_SHIFT 28 +#define PMU0_PLL0_PC1_WILD_FRAC_MASK 0x0fffff00 +#define PMU0_PLL0_PC1_WILD_FRAC_SHIFT 8 +#define PMU0_PLL0_PC1_STOP_MOD 0x00000040 + + +#define PMU0_PLL0_PLLCTL2 2 +#define PMU0_PLL0_PC2_WILD_INT_MASK 0xf +#define PMU0_PLL0_PC2_WILD_INT_SHIFT 4 + + +#define RES4328_EXT_SWITCHER_PWM 0 +#define RES4328_BB_SWITCHER_PWM 1 +#define RES4328_BB_SWITCHER_BURST 2 +#define RES4328_BB_EXT_SWITCHER_BURST 3 +#define RES4328_ILP_REQUEST 4 +#define RES4328_RADIO_SWITCHER_PWM 5 +#define RES4328_RADIO_SWITCHER_BURST 6 +#define RES4328_ROM_SWITCH 7 +#define RES4328_PA_REF_LDO 8 +#define RES4328_RADIO_LDO 9 +#define RES4328_AFE_LDO 10 +#define RES4328_PLL_LDO 11 +#define RES4328_BG_FILTBYP 12 +#define RES4328_TX_FILTBYP 13 +#define RES4328_RX_FILTBYP 14 +#define RES4328_XTAL_PU 15 +#define RES4328_XTAL_EN 16 +#define RES4328_BB_PLL_FILTBYP 17 +#define RES4328_RF_PLL_FILTBYP 18 +#define RES4328_BB_PLL_PU 19 + +#define RES5354_EXT_SWITCHER_PWM 0 +#define RES5354_BB_SWITCHER_PWM 1 +#define RES5354_BB_SWITCHER_BURST 2 +#define RES5354_BB_EXT_SWITCHER_BURST 3 +#define RES5354_ILP_REQUEST 4 +#define RES5354_RADIO_SWITCHER_PWM 5 +#define RES5354_RADIO_SWITCHER_BURST 6 +#define RES5354_ROM_SWITCH 7 +#define RES5354_PA_REF_LDO 8 +#define RES5354_RADIO_LDO 9 +#define RES5354_AFE_LDO 10 +#define RES5354_PLL_LDO 11 +#define RES5354_BG_FILTBYP 12 +#define RES5354_TX_FILTBYP 13 +#define RES5354_RX_FILTBYP 14 +#define RES5354_XTAL_PU 15 +#define RES5354_XTAL_EN 16 +#define RES5354_BB_PLL_FILTBYP 17 +#define RES5354_RF_PLL_FILTBYP 18 +#define RES5354_BB_PLL_PU 19 + + + +#define DOT11MAC_880MHZ_CLK_DIVISOR_SHIFT 8 +#define DOT11MAC_880MHZ_CLK_DIVISOR_MASK (0xFF << DOT11MAC_880MHZ_CLK_DIVISOR_SHIFT) +#define DOT11MAC_880MHZ_CLK_DIVISOR_VAL (0xE << DOT11MAC_880MHZ_CLK_DIVISOR_SHIFT) + + +#define PMU2_PHY_PLL_PLLCTL 4 +#define PMU2_SI_PLL_PLLCTL 10 + + +#define RES4325_BUCK_BOOST_BURST 0 +#define RES4325_CBUCK_BURST 1 +#define RES4325_CBUCK_PWM 2 +#define RES4325_CLDO_CBUCK_BURST 3 +#define RES4325_CLDO_CBUCK_PWM 4 +#define RES4325_BUCK_BOOST_PWM 5 +#define RES4325_ILP_REQUEST 6 +#define RES4325_ABUCK_BURST 7 +#define RES4325_ABUCK_PWM 8 +#define RES4325_LNLDO1_PU 9 +#define RES4325_OTP_PU 10 +#define RES4325_LNLDO3_PU 11 +#define RES4325_LNLDO4_PU 12 +#define RES4325_XTAL_PU 13 +#define RES4325_ALP_AVAIL 14 +#define RES4325_RX_PWRSW_PU 15 +#define RES4325_TX_PWRSW_PU 16 +#define RES4325_RFPLL_PWRSW_PU 17 +#define RES4325_LOGEN_PWRSW_PU 18 +#define RES4325_AFE_PWRSW_PU 19 +#define RES4325_BBPLL_PWRSW_PU 20 +#define RES4325_HT_AVAIL 21 + + +#define RES4325B0_CBUCK_LPOM 1 +#define RES4325B0_CBUCK_BURST 2 +#define RES4325B0_CBUCK_PWM 3 +#define RES4325B0_CLDO_PU 4 + + +#define RES4325C1_OTP_PWRSW_PU 10 +#define RES4325C1_LNLDO2_PU 12 + + +#define CST4325_SPROM_OTP_SEL_MASK 0x00000003 +#define CST4325_DEFCIS_SEL 0 +#define CST4325_SPROM_SEL 1 +#define CST4325_OTP_SEL 2 +#define CST4325_OTP_PWRDN 3 +#define CST4325_SDIO_USB_MODE_MASK 0x00000004 +#define CST4325_SDIO_USB_MODE_SHIFT 2 +#define CST4325_RCAL_VALID_MASK 0x00000008 +#define CST4325_RCAL_VALID_SHIFT 3 +#define CST4325_RCAL_VALUE_MASK 0x000001f0 +#define CST4325_RCAL_VALUE_SHIFT 4 +#define CST4325_PMUTOP_2B_MASK 0x00000200 +#define CST4325_PMUTOP_2B_SHIFT 9 + +#define RES4329_RESERVED0 0 +#define RES4329_CBUCK_LPOM 1 +#define RES4329_CBUCK_BURST 2 +#define RES4329_CBUCK_PWM 3 +#define RES4329_CLDO_PU 4 +#define RES4329_PALDO_PU 5 +#define RES4329_ILP_REQUEST 6 +#define RES4329_RESERVED7 7 +#define RES4329_RESERVED8 8 +#define RES4329_LNLDO1_PU 9 +#define RES4329_OTP_PU 10 +#define RES4329_RESERVED11 11 +#define RES4329_LNLDO2_PU 12 +#define RES4329_XTAL_PU 13 +#define RES4329_ALP_AVAIL 14 +#define RES4329_RX_PWRSW_PU 15 +#define RES4329_TX_PWRSW_PU 16 +#define RES4329_RFPLL_PWRSW_PU 17 +#define RES4329_LOGEN_PWRSW_PU 18 +#define RES4329_AFE_PWRSW_PU 19 +#define RES4329_BBPLL_PWRSW_PU 20 +#define RES4329_HT_AVAIL 21 + +#define CST4329_SPROM_OTP_SEL_MASK 0x00000003 +#define CST4329_DEFCIS_SEL 0 +#define CST4329_SPROM_SEL 1 +#define CST4329_OTP_SEL 2 +#define CST4329_OTP_PWRDN 3 +#define CST4329_SPI_SDIO_MODE_MASK 0x00000004 +#define CST4329_SPI_SDIO_MODE_SHIFT 2 + + +#define RES4312_SWITCHER_BURST 0 +#define RES4312_SWITCHER_PWM 1 +#define RES4312_PA_REF_LDO 2 +#define RES4312_CORE_LDO_BURST 3 +#define RES4312_CORE_LDO_PWM 4 +#define RES4312_RADIO_LDO 5 +#define RES4312_ILP_REQUEST 6 +#define RES4312_BG_FILTBYP 7 +#define RES4312_TX_FILTBYP 8 +#define RES4312_RX_FILTBYP 9 +#define RES4312_XTAL_PU 10 +#define RES4312_ALP_AVAIL 11 +#define RES4312_BB_PLL_FILTBYP 12 +#define RES4312_RF_PLL_FILTBYP 13 +#define RES4312_HT_AVAIL 14 + +#define RES4322_RF_LDO 0 +#define RES4322_ILP_REQUEST 1 +#define RES4322_XTAL_PU 2 +#define RES4322_ALP_AVAIL 3 +#define RES4322_SI_PLL_ON 4 +#define RES4322_HT_SI_AVAIL 5 +#define RES4322_PHY_PLL_ON 6 +#define RES4322_HT_PHY_AVAIL 7 +#define RES4322_OTP_PU 8 + + +#define CST4322_XTAL_FREQ_20_40MHZ 0x00000020 +#define CST4322_SPROM_OTP_SEL_MASK 0x000000c0 +#define CST4322_SPROM_OTP_SEL_SHIFT 6 +#define CST4322_NO_SPROM_OTP 0 +#define CST4322_SPROM_PRESENT 1 +#define CST4322_OTP_PRESENT 2 +#define CST4322_PCI_OR_USB 0x00000100 +#define CST4322_BOOT_MASK 0x00000600 +#define CST4322_BOOT_SHIFT 9 +#define CST4322_BOOT_FROM_SRAM 0 +#define CST4322_BOOT_FROM_ROM 1 +#define CST4322_BOOT_FROM_FLASH 2 +#define CST4322_BOOT_FROM_INVALID 3 +#define CST4322_ILP_DIV_EN 0x00000800 +#define CST4322_FLASH_TYPE_MASK 0x00001000 +#define CST4322_FLASH_TYPE_SHIFT 12 +#define CST4322_FLASH_TYPE_SHIFT_ST 0 +#define CST4322_FLASH_TYPE_SHIFT_ATMEL 1 +#define CST4322_ARM_TAP_SEL 0x00002000 +#define CST4322_RES_INIT_MODE_MASK 0x0000c000 +#define CST4322_RES_INIT_MODE_SHIFT 14 +#define CST4322_RES_INIT_MODE_ILPAVAIL 0 +#define CST4322_RES_INIT_MODE_ILPREQ 1 +#define CST4322_RES_INIT_MODE_ALPAVAIL 2 +#define CST4322_RES_INIT_MODE_HTAVAIL 3 +#define CST4322_PCIPLLCLK_GATING 0x00010000 +#define CST4322_CLK_SWITCH_PCI_TO_ALP 0x00020000 +#define CST4322_PCI_CARDBUS_MODE 0x00040000 + +#define RES4315_CBUCK_LPOM 1 +#define RES4315_CBUCK_BURST 2 +#define RES4315_CBUCK_PWM 3 +#define RES4315_CLDO_PU 4 +#define RES4315_PALDO_PU 5 +#define RES4315_ILP_REQUEST 6 +#define RES4315_LNLDO1_PU 9 +#define RES4315_OTP_PU 10 +#define RES4315_LNLDO2_PU 12 +#define RES4315_XTAL_PU 13 +#define RES4315_ALP_AVAIL 14 +#define RES4315_RX_PWRSW_PU 15 +#define RES4315_TX_PWRSW_PU 16 +#define RES4315_RFPLL_PWRSW_PU 17 +#define RES4315_LOGEN_PWRSW_PU 18 +#define RES4315_AFE_PWRSW_PU 19 +#define RES4315_BBPLL_PWRSW_PU 20 +#define RES4315_HT_AVAIL 21 + +#define CST4315_SPROM_OTP_SEL_MASK 0x00000003 +#define CST4315_DEFCIS_SEL 0x00000000 +#define CST4315_SPROM_SEL 0x00000001 +#define CST4315_OTP_SEL 0x00000002 +#define CST4315_OTP_PWRDN 0x00000003 +#define CST4315_SDIO_MODE 0x00000004 +#define CST4315_RCAL_VALID 0x00000008 +#define CST4315_RCAL_VALUE_MASK 0x000001f0 +#define CST4315_RCAL_VALUE_SHIFT 4 +#define CST4315_PALDO_EXTPNP 0x00000200 +#define CST4315_CBUCK_MODE_MASK 0x00000c00 +#define CST4315_CBUCK_MODE_BURST 0x00000400 +#define CST4315_CBUCK_MODE_LPBURST 0x00000c00 + +#define PMU_MAX_TRANSITION_DLY 15000 + + +#define PMURES_UP_TRANSITION 2 + + + + + +#define ECI_BW_20 0x0 +#define ECI_BW_25 0x1 +#define ECI_BW_30 0x2 +#define ECI_BW_35 0x3 +#define ECI_BW_40 0x4 +#define ECI_BW_45 0x5 +#define ECI_BW_50 0x6 +#define ECI_BW_ALL 0x7 + + +#define WLAN_NUM_ANT1 TXANT_0 +#define WLAN_NUM_ANT2 TXANT_1 + +#endif diff --git a/drivers/net/wireless/bcm4329/include/sbconfig.h b/drivers/net/wireless/bcm4329/include/sbconfig.h new file mode 100644 index 0000000000000000000000000000000000000000..da18ccbe9ab832daa9aeef4bac014143283001e5 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbconfig.h @@ -0,0 +1,276 @@ +/* + * Broadcom SiliconBackplane hardware register definitions. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbconfig.h,v 13.67.30.1 2008/05/07 20:17:27 Exp $ + */ + + +#ifndef _SBCONFIG_H +#define _SBCONFIG_H + + +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif + + +#define SB_BUS_SIZE 0x10000 +#define SB_BUS_BASE(b) (SI_ENUM_BASE + (b) * SB_BUS_SIZE) +#define SB_BUS_MAXCORES (SB_BUS_SIZE / SI_CORE_SIZE) + + +#define SBCONFIGOFF 0xf00 +#define SBCONFIGSIZE 256 + +#define SBIPSFLAG 0x08 +#define SBTPSFLAG 0x18 +#define SBTMERRLOGA 0x48 +#define SBTMERRLOG 0x50 +#define SBADMATCH3 0x60 +#define SBADMATCH2 0x68 +#define SBADMATCH1 0x70 +#define SBIMSTATE 0x90 +#define SBINTVEC 0x94 +#define SBTMSTATELOW 0x98 +#define SBTMSTATEHIGH 0x9c +#define SBBWA0 0xa0 +#define SBIMCONFIGLOW 0xa8 +#define SBIMCONFIGHIGH 0xac +#define SBADMATCH0 0xb0 +#define SBTMCONFIGLOW 0xb8 +#define SBTMCONFIGHIGH 0xbc +#define SBBCONFIG 0xc0 +#define SBBSTATE 0xc8 +#define SBACTCNFG 0xd8 +#define SBFLAGST 0xe8 +#define SBIDLOW 0xf8 +#define SBIDHIGH 0xfc + + + +#define SBIMERRLOGA 0xea8 +#define SBIMERRLOG 0xeb0 +#define SBTMPORTCONNID0 0xed8 +#define SBTMPORTLOCK0 0xef8 + +#ifndef _LANGUAGE_ASSEMBLY + +typedef volatile struct _sbconfig { + uint32 PAD[2]; + uint32 sbipsflag; + uint32 PAD[3]; + uint32 sbtpsflag; + uint32 PAD[11]; + uint32 sbtmerrloga; + uint32 PAD; + uint32 sbtmerrlog; + uint32 PAD[3]; + uint32 sbadmatch3; + uint32 PAD; + uint32 sbadmatch2; + uint32 PAD; + uint32 sbadmatch1; + uint32 PAD[7]; + uint32 sbimstate; + uint32 sbintvec; + uint32 sbtmstatelow; + uint32 sbtmstatehigh; + uint32 sbbwa0; + uint32 PAD; + uint32 sbimconfiglow; + uint32 sbimconfighigh; + uint32 sbadmatch0; + uint32 PAD; + uint32 sbtmconfiglow; + uint32 sbtmconfighigh; + uint32 sbbconfig; + uint32 PAD; + uint32 sbbstate; + uint32 PAD[3]; + uint32 sbactcnfg; + uint32 PAD[3]; + uint32 sbflagst; + uint32 PAD[3]; + uint32 sbidlow; + uint32 sbidhigh; +} sbconfig_t; + +#endif + + +#define SBIPS_INT1_MASK 0x3f +#define SBIPS_INT1_SHIFT 0 +#define SBIPS_INT2_MASK 0x3f00 +#define SBIPS_INT2_SHIFT 8 +#define SBIPS_INT3_MASK 0x3f0000 +#define SBIPS_INT3_SHIFT 16 +#define SBIPS_INT4_MASK 0x3f000000 +#define SBIPS_INT4_SHIFT 24 + + +#define SBTPS_NUM0_MASK 0x3f +#define SBTPS_F0EN0 0x40 + + +#define SBTMEL_CM 0x00000007 +#define SBTMEL_CI 0x0000ff00 +#define SBTMEL_EC 0x0f000000 +#define SBTMEL_ME 0x80000000 + + +#define SBIM_PC 0xf +#define SBIM_AP_MASK 0x30 +#define SBIM_AP_BOTH 0x00 +#define SBIM_AP_TS 0x10 +#define SBIM_AP_TK 0x20 +#define SBIM_AP_RSV 0x30 +#define SBIM_IBE 0x20000 +#define SBIM_TO 0x40000 +#define SBIM_BY 0x01800000 +#define SBIM_RJ 0x02000000 + + +#define SBTML_RESET 0x0001 +#define SBTML_REJ_MASK 0x0006 +#define SBTML_REJ 0x0002 +#define SBTML_TMPREJ 0x0004 + +#define SBTML_SICF_SHIFT 16 + + +#define SBTMH_SERR 0x0001 +#define SBTMH_INT 0x0002 +#define SBTMH_BUSY 0x0004 +#define SBTMH_TO 0x0020 + +#define SBTMH_SISF_SHIFT 16 + + +#define SBBWA_TAB0_MASK 0xffff +#define SBBWA_TAB1_MASK 0xffff +#define SBBWA_TAB1_SHIFT 16 + + +#define SBIMCL_STO_MASK 0x7 +#define SBIMCL_RTO_MASK 0x70 +#define SBIMCL_RTO_SHIFT 4 +#define SBIMCL_CID_MASK 0xff0000 +#define SBIMCL_CID_SHIFT 16 + + +#define SBIMCH_IEM_MASK 0xc +#define SBIMCH_TEM_MASK 0x30 +#define SBIMCH_TEM_SHIFT 4 +#define SBIMCH_BEM_MASK 0xc0 +#define SBIMCH_BEM_SHIFT 6 + + +#define SBAM_TYPE_MASK 0x3 +#define SBAM_AD64 0x4 +#define SBAM_ADINT0_MASK 0xf8 +#define SBAM_ADINT0_SHIFT 3 +#define SBAM_ADINT1_MASK 0x1f8 +#define SBAM_ADINT1_SHIFT 3 +#define SBAM_ADINT2_MASK 0x1f8 +#define SBAM_ADINT2_SHIFT 3 +#define SBAM_ADEN 0x400 +#define SBAM_ADNEG 0x800 +#define SBAM_BASE0_MASK 0xffffff00 +#define SBAM_BASE0_SHIFT 8 +#define SBAM_BASE1_MASK 0xfffff000 +#define SBAM_BASE1_SHIFT 12 +#define SBAM_BASE2_MASK 0xffff0000 +#define SBAM_BASE2_SHIFT 16 + + +#define SBTMCL_CD_MASK 0xff +#define SBTMCL_CO_MASK 0xf800 +#define SBTMCL_CO_SHIFT 11 +#define SBTMCL_IF_MASK 0xfc0000 +#define SBTMCL_IF_SHIFT 18 +#define SBTMCL_IM_MASK 0x3000000 +#define SBTMCL_IM_SHIFT 24 + + +#define SBTMCH_BM_MASK 0x3 +#define SBTMCH_RM_MASK 0x3 +#define SBTMCH_RM_SHIFT 2 +#define SBTMCH_SM_MASK 0x30 +#define SBTMCH_SM_SHIFT 4 +#define SBTMCH_EM_MASK 0x300 +#define SBTMCH_EM_SHIFT 8 +#define SBTMCH_IM_MASK 0xc00 +#define SBTMCH_IM_SHIFT 10 + + +#define SBBC_LAT_MASK 0x3 +#define SBBC_MAX0_MASK 0xf0000 +#define SBBC_MAX0_SHIFT 16 +#define SBBC_MAX1_MASK 0xf00000 +#define SBBC_MAX1_SHIFT 20 + + +#define SBBS_SRD 0x1 +#define SBBS_HRD 0x2 + + +#define SBIDL_CS_MASK 0x3 +#define SBIDL_AR_MASK 0x38 +#define SBIDL_AR_SHIFT 3 +#define SBIDL_SYNCH 0x40 +#define SBIDL_INIT 0x80 +#define SBIDL_MINLAT_MASK 0xf00 +#define SBIDL_MINLAT_SHIFT 8 +#define SBIDL_MAXLAT 0xf000 +#define SBIDL_MAXLAT_SHIFT 12 +#define SBIDL_FIRST 0x10000 +#define SBIDL_CW_MASK 0xc0000 +#define SBIDL_CW_SHIFT 18 +#define SBIDL_TP_MASK 0xf00000 +#define SBIDL_TP_SHIFT 20 +#define SBIDL_IP_MASK 0xf000000 +#define SBIDL_IP_SHIFT 24 +#define SBIDL_RV_MASK 0xf0000000 +#define SBIDL_RV_SHIFT 28 +#define SBIDL_RV_2_2 0x00000000 +#define SBIDL_RV_2_3 0x10000000 + + +#define SBIDH_RC_MASK 0x000f +#define SBIDH_RCE_MASK 0x7000 +#define SBIDH_RCE_SHIFT 8 +#define SBCOREREV(sbidh) \ + ((((sbidh) & SBIDH_RCE_MASK) >> SBIDH_RCE_SHIFT) | ((sbidh) & SBIDH_RC_MASK)) +#define SBIDH_CC_MASK 0x8ff0 +#define SBIDH_CC_SHIFT 4 +#define SBIDH_VC_MASK 0xffff0000 +#define SBIDH_VC_SHIFT 16 + +#define SB_COMMIT 0xfd8 + + +#define SB_VEND_BCM 0x4243 + +#endif diff --git a/drivers/net/wireless/bcm4329/include/sbhnddma.h b/drivers/net/wireless/bcm4329/include/sbhnddma.h new file mode 100644 index 0000000000000000000000000000000000000000..7681395f5b3b94d20dbe592c5641b45053d8dce4 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbhnddma.h @@ -0,0 +1,294 @@ +/* + * Generic Broadcom Home Networking Division (HND) DMA engine HW interface + * This supports the following chips: BCM42xx, 44xx, 47xx . + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbhnddma.h,v 13.11.250.5.16.1 2009/07/21 14:04:51 Exp $ + */ + + +#ifndef _sbhnddma_h_ +#define _sbhnddma_h_ + + + + + + + +typedef volatile struct { + uint32 control; + uint32 addr; + uint32 ptr; + uint32 status; +} dma32regs_t; + +typedef volatile struct { + dma32regs_t xmt; + dma32regs_t rcv; +} dma32regp_t; + +typedef volatile struct { + uint32 fifoaddr; + uint32 fifodatalow; + uint32 fifodatahigh; + uint32 pad; +} dma32diag_t; + + +typedef volatile struct { + uint32 ctrl; + uint32 addr; +} dma32dd_t; + + +#define D32RINGALIGN_BITS 12 +#define D32MAXRINGSZ (1 << D32RINGALIGN_BITS) +#define D32RINGALIGN (1 << D32RINGALIGN_BITS) +#define D32MAXDD (D32MAXRINGSZ / sizeof (dma32dd_t)) + + +#define XC_XE ((uint32)1 << 0) +#define XC_SE ((uint32)1 << 1) +#define XC_LE ((uint32)1 << 2) +#define XC_FL ((uint32)1 << 4) +#define XC_PD ((uint32)1 << 11) +#define XC_AE ((uint32)3 << 16) +#define XC_AE_SHIFT 16 + + +#define XP_LD_MASK 0xfff + + +#define XS_CD_MASK 0x0fff +#define XS_XS_MASK 0xf000 +#define XS_XS_SHIFT 12 +#define XS_XS_DISABLED 0x0000 +#define XS_XS_ACTIVE 0x1000 +#define XS_XS_IDLE 0x2000 +#define XS_XS_STOPPED 0x3000 +#define XS_XS_SUSP 0x4000 +#define XS_XE_MASK 0xf0000 +#define XS_XE_SHIFT 16 +#define XS_XE_NOERR 0x00000 +#define XS_XE_DPE 0x10000 +#define XS_XE_DFU 0x20000 +#define XS_XE_BEBR 0x30000 +#define XS_XE_BEDA 0x40000 +#define XS_AD_MASK 0xfff00000 +#define XS_AD_SHIFT 20 + + +#define RC_RE ((uint32)1 << 0) +#define RC_RO_MASK 0xfe +#define RC_RO_SHIFT 1 +#define RC_FM ((uint32)1 << 8) +#define RC_SH ((uint32)1 << 9) +#define RC_OC ((uint32)1 << 10) +#define RC_PD ((uint32)1 << 11) +#define RC_AE ((uint32)3 << 16) +#define RC_AE_SHIFT 16 + + +#define RP_LD_MASK 0xfff + + +#define RS_CD_MASK 0x0fff +#define RS_RS_MASK 0xf000 +#define RS_RS_SHIFT 12 +#define RS_RS_DISABLED 0x0000 +#define RS_RS_ACTIVE 0x1000 +#define RS_RS_IDLE 0x2000 +#define RS_RS_STOPPED 0x3000 +#define RS_RE_MASK 0xf0000 +#define RS_RE_SHIFT 16 +#define RS_RE_NOERR 0x00000 +#define RS_RE_DPE 0x10000 +#define RS_RE_DFO 0x20000 +#define RS_RE_BEBW 0x30000 +#define RS_RE_BEDA 0x40000 +#define RS_AD_MASK 0xfff00000 +#define RS_AD_SHIFT 20 + + +#define FA_OFF_MASK 0xffff +#define FA_SEL_MASK 0xf0000 +#define FA_SEL_SHIFT 16 +#define FA_SEL_XDD 0x00000 +#define FA_SEL_XDP 0x10000 +#define FA_SEL_RDD 0x40000 +#define FA_SEL_RDP 0x50000 +#define FA_SEL_XFD 0x80000 +#define FA_SEL_XFP 0x90000 +#define FA_SEL_RFD 0xc0000 +#define FA_SEL_RFP 0xd0000 +#define FA_SEL_RSD 0xe0000 +#define FA_SEL_RSP 0xf0000 + + +#define CTRL_BC_MASK 0x1fff +#define CTRL_AE ((uint32)3 << 16) +#define CTRL_AE_SHIFT 16 +#define CTRL_EOT ((uint32)1 << 28) +#define CTRL_IOC ((uint32)1 << 29) +#define CTRL_EOF ((uint32)1 << 30) +#define CTRL_SOF ((uint32)1 << 31) + + +#define CTRL_CORE_MASK 0x0ff00000 + + + + +typedef volatile struct { + uint32 control; + uint32 ptr; + uint32 addrlow; + uint32 addrhigh; + uint32 status0; + uint32 status1; +} dma64regs_t; + +typedef volatile struct { + dma64regs_t tx; + dma64regs_t rx; +} dma64regp_t; + +typedef volatile struct { + uint32 fifoaddr; + uint32 fifodatalow; + uint32 fifodatahigh; + uint32 pad; +} dma64diag_t; + + +typedef volatile struct { + uint32 ctrl1; + uint32 ctrl2; + uint32 addrlow; + uint32 addrhigh; +} dma64dd_t; + + +#define D64RINGALIGN_BITS 13 +#define D64MAXRINGSZ (1 << D64RINGALIGN_BITS) +#define D64RINGALIGN (1 << D64RINGALIGN_BITS) +#define D64MAXDD (D64MAXRINGSZ / sizeof (dma64dd_t)) + + +#define D64_XC_XE 0x00000001 +#define D64_XC_SE 0x00000002 +#define D64_XC_LE 0x00000004 +#define D64_XC_FL 0x00000010 +#define D64_XC_PD 0x00000800 +#define D64_XC_AE 0x00030000 +#define D64_XC_AE_SHIFT 16 + + +#define D64_XP_LD_MASK 0x00000fff + + +#define D64_XS0_CD_MASK 0x00001fff +#define D64_XS0_XS_MASK 0xf0000000 +#define D64_XS0_XS_SHIFT 28 +#define D64_XS0_XS_DISABLED 0x00000000 +#define D64_XS0_XS_ACTIVE 0x10000000 +#define D64_XS0_XS_IDLE 0x20000000 +#define D64_XS0_XS_STOPPED 0x30000000 +#define D64_XS0_XS_SUSP 0x40000000 + +#define D64_XS1_AD_MASK 0x0001ffff +#define D64_XS1_XE_MASK 0xf0000000 +#define D64_XS1_XE_SHIFT 28 +#define D64_XS1_XE_NOERR 0x00000000 +#define D64_XS1_XE_DPE 0x10000000 +#define D64_XS1_XE_DFU 0x20000000 +#define D64_XS1_XE_DTE 0x30000000 +#define D64_XS1_XE_DESRE 0x40000000 +#define D64_XS1_XE_COREE 0x50000000 + + +#define D64_RC_RE 0x00000001 +#define D64_RC_RO_MASK 0x000000fe +#define D64_RC_RO_SHIFT 1 +#define D64_RC_FM 0x00000100 +#define D64_RC_SH 0x00000200 +#define D64_RC_OC 0x00000400 +#define D64_RC_PD 0x00000800 +#define D64_RC_AE 0x00030000 +#define D64_RC_AE_SHIFT 16 + + +#define D64_RP_LD_MASK 0x00000fff + + +#define D64_RS0_CD_MASK 0x00001fff +#define D64_RS0_RS_MASK 0xf0000000 +#define D64_RS0_RS_SHIFT 28 +#define D64_RS0_RS_DISABLED 0x00000000 +#define D64_RS0_RS_ACTIVE 0x10000000 +#define D64_RS0_RS_IDLE 0x20000000 +#define D64_RS0_RS_STOPPED 0x30000000 +#define D64_RS0_RS_SUSP 0x40000000 + +#define D64_RS1_AD_MASK 0x0001ffff +#define D64_RS1_RE_MASK 0xf0000000 +#define D64_RS1_RE_SHIFT 28 +#define D64_RS1_RE_NOERR 0x00000000 +#define D64_RS1_RE_DPO 0x10000000 +#define D64_RS1_RE_DFU 0x20000000 +#define D64_RS1_RE_DTE 0x30000000 +#define D64_RS1_RE_DESRE 0x40000000 +#define D64_RS1_RE_COREE 0x50000000 + + +#define D64_FA_OFF_MASK 0xffff +#define D64_FA_SEL_MASK 0xf0000 +#define D64_FA_SEL_SHIFT 16 +#define D64_FA_SEL_XDD 0x00000 +#define D64_FA_SEL_XDP 0x10000 +#define D64_FA_SEL_RDD 0x40000 +#define D64_FA_SEL_RDP 0x50000 +#define D64_FA_SEL_XFD 0x80000 +#define D64_FA_SEL_XFP 0x90000 +#define D64_FA_SEL_RFD 0xc0000 +#define D64_FA_SEL_RFP 0xd0000 +#define D64_FA_SEL_RSD 0xe0000 +#define D64_FA_SEL_RSP 0xf0000 + + +#define D64_CTRL1_EOT ((uint32)1 << 28) +#define D64_CTRL1_IOC ((uint32)1 << 29) +#define D64_CTRL1_EOF ((uint32)1 << 30) +#define D64_CTRL1_SOF ((uint32)1 << 31) + + +#define D64_CTRL2_BC_MASK 0x00007fff +#define D64_CTRL2_AE 0x00030000 +#define D64_CTRL2_AE_SHIFT 16 +#define D64_CTRL2_PARITY 0x00040000 + + +#define D64_CTRL_CORE_MASK 0x0ff00000 + + +#endif diff --git a/drivers/net/wireless/bcm4329/include/sbpcmcia.h b/drivers/net/wireless/bcm4329/include/sbpcmcia.h new file mode 100644 index 0000000000000000000000000000000000000000..d6d80334258a1b057143bf49c6106cfc819da379 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbpcmcia.h @@ -0,0 +1,109 @@ +/* + * BCM43XX Sonics SiliconBackplane PCMCIA core hardware definitions. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbpcmcia.h,v 13.31.4.1.2.3.8.7 2009/06/22 05:14:24 Exp $ + */ + + +#ifndef _SBPCMCIA_H +#define _SBPCMCIA_H + + + + +#define PCMCIA_FCR (0x700 / 2) + +#define FCR0_OFF 0 +#define FCR1_OFF (0x40 / 2) +#define FCR2_OFF (0x80 / 2) +#define FCR3_OFF (0xc0 / 2) + +#define PCMCIA_FCR0 (0x700 / 2) +#define PCMCIA_FCR1 (0x740 / 2) +#define PCMCIA_FCR2 (0x780 / 2) +#define PCMCIA_FCR3 (0x7c0 / 2) + + + +#define PCMCIA_COR 0 + +#define COR_RST 0x80 +#define COR_LEV 0x40 +#define COR_IRQEN 0x04 +#define COR_BLREN 0x01 +#define COR_FUNEN 0x01 + + +#define PCICIA_FCSR (2 / 2) +#define PCICIA_PRR (4 / 2) +#define PCICIA_SCR (6 / 2) +#define PCICIA_ESR (8 / 2) + + +#define PCM_MEMOFF 0x0000 +#define F0_MEMOFF 0x1000 +#define F1_MEMOFF 0x2000 +#define F2_MEMOFF 0x3000 +#define F3_MEMOFF 0x4000 + + +#define MEM_ADDR0 (0x728 / 2) +#define MEM_ADDR1 (0x72a / 2) +#define MEM_ADDR2 (0x72c / 2) + + +#define PCMCIA_ADDR0 (0x072e / 2) +#define PCMCIA_ADDR1 (0x0730 / 2) +#define PCMCIA_ADDR2 (0x0732 / 2) + +#define MEM_SEG (0x0734 / 2) +#define SROM_CS (0x0736 / 2) +#define SROM_DATAL (0x0738 / 2) +#define SROM_DATAH (0x073a / 2) +#define SROM_ADDRL (0x073c / 2) +#define SROM_ADDRH (0x073e / 2) +#define SROM_INFO2 (0x0772 / 2) +#define SROM_INFO (0x07be / 2) + + +#define SROM_IDLE 0 +#define SROM_WRITE 1 +#define SROM_READ 2 +#define SROM_WEN 4 +#define SROM_WDS 7 +#define SROM_DONE 8 + + +#define SRI_SZ_MASK 0x03 +#define SRI_BLANK 0x04 +#define SRI_OTP 0x80 + + + +#define SBTML_INT_ACK 0x40000 +#define SBTML_INT_EN 0x20000 + + +#define SBTMH_INT_STATUS 0x40000 + +#endif diff --git a/drivers/net/wireless/bcm4329/include/sbsdio.h b/drivers/net/wireless/bcm4329/include/sbsdio.h new file mode 100644 index 0000000000000000000000000000000000000000..75aaf4d88f7d7b37f4ecbd3f31054f2be28a5ac1 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbsdio.h @@ -0,0 +1,166 @@ +/* + * SDIO device core hardware definitions. + * sdio is a portion of the pcmcia core in core rev 3 - rev 8 + * + * SDIO core support 1bit, 4 bit SDIO mode as well as SPI mode. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbsdio.h,v 13.29.4.1.22.3 2009/03/11 20:26:57 Exp $ + */ + +#ifndef _SBSDIO_H +#define _SBSDIO_H + +#define SBSDIO_NUM_FUNCTION 3 /* as of sdiod rev 0, supports 3 functions */ + +/* function 1 miscellaneous registers */ +#define SBSDIO_SPROM_CS 0x10000 /* sprom command and status */ +#define SBSDIO_SPROM_INFO 0x10001 /* sprom info register */ +#define SBSDIO_SPROM_DATA_LOW 0x10002 /* sprom indirect access data byte 0 */ +#define SBSDIO_SPROM_DATA_HIGH 0x10003 /* sprom indirect access data byte 1 */ +#define SBSDIO_SPROM_ADDR_LOW 0x10004 /* sprom indirect access addr byte 0 */ +#define SBSDIO_SPROM_ADDR_HIGH 0x10005 /* sprom indirect access addr byte 0 */ +#define SBSDIO_CHIP_CTRL_DATA 0x10006 /* xtal_pu (gpio) output */ +#define SBSDIO_CHIP_CTRL_EN 0x10007 /* xtal_pu (gpio) enable */ +#define SBSDIO_WATERMARK 0x10008 /* rev < 7, watermark for sdio device */ +#define SBSDIO_DEVICE_CTL 0x10009 /* control busy signal generation */ + +/* registers introduced in rev 8, some content (mask/bits) defs in sbsdpcmdev.h */ +#define SBSDIO_FUNC1_SBADDRLOW 0x1000A /* SB Address Window Low (b15) */ +#define SBSDIO_FUNC1_SBADDRMID 0x1000B /* SB Address Window Mid (b23:b16) */ +#define SBSDIO_FUNC1_SBADDRHIGH 0x1000C /* SB Address Window High (b31:b24) */ +#define SBSDIO_FUNC1_FRAMECTRL 0x1000D /* Frame Control (frame term/abort) */ +#define SBSDIO_FUNC1_CHIPCLKCSR 0x1000E /* ChipClockCSR (ALP/HT ctl/status) */ +#define SBSDIO_FUNC1_SDIOPULLUP 0x1000F /* SdioPullUp (on cmd, d0-d2) */ +#define SBSDIO_FUNC1_WFRAMEBCLO 0x10019 /* Write Frame Byte Count Low */ +#define SBSDIO_FUNC1_WFRAMEBCHI 0x1001A /* Write Frame Byte Count High */ +#define SBSDIO_FUNC1_RFRAMEBCLO 0x1001B /* Read Frame Byte Count Low */ +#define SBSDIO_FUNC1_RFRAMEBCHI 0x1001C /* Read Frame Byte Count High */ + +#define SBSDIO_FUNC1_MISC_REG_START 0x10000 /* f1 misc register start */ +#define SBSDIO_FUNC1_MISC_REG_LIMIT 0x1001C /* f1 misc register end */ + +/* SBSDIO_SPROM_CS */ +#define SBSDIO_SPROM_IDLE 0 +#define SBSDIO_SPROM_WRITE 1 +#define SBSDIO_SPROM_READ 2 +#define SBSDIO_SPROM_WEN 4 +#define SBSDIO_SPROM_WDS 7 +#define SBSDIO_SPROM_DONE 8 + +/* SBSDIO_SPROM_INFO */ +#define SROM_SZ_MASK 0x03 /* SROM size, 1: 4k, 2: 16k */ +#define SROM_BLANK 0x04 /* depreciated in corerev 6 */ +#define SROM_OTP 0x80 /* OTP present */ + +/* SBSDIO_CHIP_CTRL */ +#define SBSDIO_CHIP_CTRL_XTAL 0x01 /* or'd with onchip xtal_pu, + * 1: power on oscillator + * (for 4318 only) + */ +/* SBSDIO_WATERMARK */ +#define SBSDIO_WATERMARK_MASK 0x7f /* number of words - 1 for sd device + * to wait before sending data to host + */ + +/* SBSDIO_DEVICE_CTL */ +#define SBSDIO_DEVCTL_SETBUSY 0x01 /* 1: device will assert busy signal when + * receiving CMD53 + */ +#define SBSDIO_DEVCTL_SPI_INTR_SYNC 0x02 /* 1: assertion of sdio interrupt is + * synchronous to the sdio clock + */ +#define SBSDIO_DEVCTL_CA_INT_ONLY 0x04 /* 1: mask all interrupts to host + * except the chipActive (rev 8) + */ +#define SBSDIO_DEVCTL_PADS_ISO 0x08 /* 1: isolate internal sdio signals, put + * external pads in tri-state; requires + * sdio bus power cycle to clear (rev 9) + */ +#define SBSDIO_DEVCTL_SB_RST_CTL 0x30 /* Force SD->SB reset mapping (rev 11) */ +#define SBSDIO_DEVCTL_RST_CORECTL 0x00 /* Determined by CoreControl bit */ +#define SBSDIO_DEVCTL_RST_BPRESET 0x10 /* Force backplane reset */ +#define SBSDIO_DEVCTL_RST_NOBPRESET 0x20 /* Force no backplane reset */ + + +/* SBSDIO_FUNC1_CHIPCLKCSR */ +#define SBSDIO_FORCE_ALP 0x01 /* Force ALP request to backplane */ +#define SBSDIO_FORCE_HT 0x02 /* Force HT request to backplane */ +#define SBSDIO_FORCE_ILP 0x04 /* Force ILP request to backplane */ +#define SBSDIO_ALP_AVAIL_REQ 0x08 /* Make ALP ready (power up xtal) */ +#define SBSDIO_HT_AVAIL_REQ 0x10 /* Make HT ready (power up PLL) */ +#define SBSDIO_FORCE_HW_CLKREQ_OFF 0x20 /* Squelch clock requests from HW */ +#define SBSDIO_ALP_AVAIL 0x40 /* Status: ALP is ready */ +#define SBSDIO_HT_AVAIL 0x80 /* Status: HT is ready */ +/* In rev8, actual avail bits followed original docs */ +#define SBSDIO_Rev8_HT_AVAIL 0x40 +#define SBSDIO_Rev8_ALP_AVAIL 0x80 + +#define SBSDIO_AVBITS (SBSDIO_HT_AVAIL | SBSDIO_ALP_AVAIL) +#define SBSDIO_ALPAV(regval) ((regval) & SBSDIO_AVBITS) +#define SBSDIO_HTAV(regval) (((regval) & SBSDIO_AVBITS) == SBSDIO_AVBITS) +#define SBSDIO_ALPONLY(regval) (SBSDIO_ALPAV(regval) && !SBSDIO_HTAV(regval)) +#define SBSDIO_CLKAV(regval, alponly) (SBSDIO_ALPAV(regval) && \ + (alponly ? 1 : SBSDIO_HTAV(regval))) + +/* SBSDIO_FUNC1_SDIOPULLUP */ +#define SBSDIO_PULLUP_D0 0x01 /* Enable D0/MISO pullup */ +#define SBSDIO_PULLUP_D1 0x02 /* Enable D1/INT# pullup */ +#define SBSDIO_PULLUP_D2 0x04 /* Enable D2 pullup */ +#define SBSDIO_PULLUP_CMD 0x08 /* Enable CMD/MOSI pullup */ +#define SBSDIO_PULLUP_ALL 0x0f /* All valid bits */ + +/* function 1 OCP space */ +#define SBSDIO_SB_OFT_ADDR_MASK 0x07FFF /* sb offset addr is <= 15 bits, 32k */ +#define SBSDIO_SB_OFT_ADDR_LIMIT 0x08000 +#define SBSDIO_SB_ACCESS_2_4B_FLAG 0x08000 /* with b15, maps to 32-bit SB access */ + +/* some duplication with sbsdpcmdev.h here */ +/* valid bits in SBSDIO_FUNC1_SBADDRxxx regs */ +#define SBSDIO_SBADDRLOW_MASK 0x80 /* Valid bits in SBADDRLOW */ +#define SBSDIO_SBADDRMID_MASK 0xff /* Valid bits in SBADDRMID */ +#define SBSDIO_SBADDRHIGH_MASK 0xffU /* Valid bits in SBADDRHIGH */ +#define SBSDIO_SBWINDOW_MASK 0xffff8000 /* Address bits from SBADDR regs */ + +/* direct(mapped) cis space */ +#define SBSDIO_CIS_BASE_COMMON 0x1000 /* MAPPED common CIS address */ +#define SBSDIO_CIS_SIZE_LIMIT 0x200 /* maximum bytes in one CIS */ +#define SBSDIO_OTP_CIS_SIZE_LIMIT 0x078 /* maximum bytes OTP CIS */ + +#define SBSDIO_CIS_OFT_ADDR_MASK 0x1FFFF /* cis offset addr is < 17 bits */ + +#define SBSDIO_CIS_MANFID_TUPLE_LEN 6 /* manfid tuple length, include tuple, + * link bytes + */ + +/* indirect cis access (in sprom) */ +#define SBSDIO_SPROM_CIS_OFFSET 0x8 /* 8 control bytes first, CIS starts from + * 8th byte + */ + +#define SBSDIO_BYTEMODE_DATALEN_MAX 64 /* sdio byte mode: maximum length of one + * data comamnd + */ + +#define SBSDIO_CORE_ADDR_MASK 0x1FFFF /* sdio core function one address mask */ + +#endif /* _SBSDIO_H */ diff --git a/drivers/net/wireless/bcm4329/include/sbsdpcmdev.h b/drivers/net/wireless/bcm4329/include/sbsdpcmdev.h new file mode 100644 index 0000000000000000000000000000000000000000..7c7c7e4de0f6d219aae8d4d9dbbb964cfb91d260 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbsdpcmdev.h @@ -0,0 +1,288 @@ +/* + * Broadcom SiliconBackplane SDIO/PCMCIA hardware-specific device core support + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbsdpcmdev.h,v 13.29.4.1.4.6.6.2 2008/12/31 21:16:51 Exp $ + */ + +#ifndef _sbsdpcmdev_h_ +#define _sbsdpcmdev_h_ + +/* cpp contortions to concatenate w/arg prescan */ +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif /* PAD */ + + +typedef volatile struct { + dma64regs_t xmt; /* dma tx */ + uint32 PAD[2]; + dma64regs_t rcv; /* dma rx */ + uint32 PAD[2]; +} dma64p_t; + +/* dma64 sdiod corerev >= 1 */ +typedef volatile struct { + dma64p_t dma64regs[2]; + dma64diag_t dmafifo; /* DMA Diagnostic Regs, 0x280-0x28c */ + uint32 PAD[92]; +} sdiodma64_t; + +/* dma32 sdiod corerev == 0 */ +typedef volatile struct { + dma32regp_t dma32regs[2]; /* dma tx & rx, 0x200-0x23c */ + dma32diag_t dmafifo; /* DMA Diagnostic Regs, 0x240-0x24c */ + uint32 PAD[108]; +} sdiodma32_t; + +/* dma32 regs for pcmcia core */ +typedef volatile struct { + dma32regp_t dmaregs; /* DMA Regs, 0x200-0x21c, rev8 */ + dma32diag_t dmafifo; /* DMA Diagnostic Regs, 0x220-0x22c */ + uint32 PAD[116]; +} pcmdma32_t; + +/* core registers */ +typedef volatile struct { + uint32 corecontrol; /* CoreControl, 0x000, rev8 */ + uint32 corestatus; /* CoreStatus, 0x004, rev8 */ + uint32 PAD[1]; + uint32 biststatus; /* BistStatus, 0x00c, rev8 */ + + /* PCMCIA access */ + uint16 pcmciamesportaladdr; /* PcmciaMesPortalAddr, 0x010, rev8 */ + uint16 PAD[1]; + uint16 pcmciamesportalmask; /* PcmciaMesPortalMask, 0x014, rev8 */ + uint16 PAD[1]; + uint16 pcmciawrframebc; /* PcmciaWrFrameBC, 0x018, rev8 */ + uint16 PAD[1]; + uint16 pcmciaunderflowtimer; /* PcmciaUnderflowTimer, 0x01c, rev8 */ + uint16 PAD[1]; + + /* interrupt */ + uint32 intstatus; /* IntStatus, 0x020, rev8 */ + uint32 hostintmask; /* IntHostMask, 0x024, rev8 */ + uint32 intmask; /* IntSbMask, 0x028, rev8 */ + uint32 sbintstatus; /* SBIntStatus, 0x02c, rev8 */ + uint32 sbintmask; /* SBIntMask, 0x030, rev8 */ + uint32 PAD[3]; + uint32 tosbmailbox; /* ToSBMailbox, 0x040, rev8 */ + uint32 tohostmailbox; /* ToHostMailbox, 0x044, rev8 */ + uint32 tosbmailboxdata; /* ToSbMailboxData, 0x048, rev8 */ + uint32 tohostmailboxdata; /* ToHostMailboxData, 0x04c, rev8 */ + + /* synchronized access to registers in SDIO clock domain */ + uint32 sdioaccess; /* SdioAccess, 0x050, rev8 */ + uint32 PAD[3]; + + /* PCMCIA frame control */ + uint8 pcmciaframectrl; /* pcmciaFrameCtrl, 0x060, rev8 */ + uint8 PAD[3]; + uint8 pcmciawatermark; /* pcmciaWaterMark, 0x064, rev8 */ + uint8 PAD[155]; + + /* interrupt batching control */ + uint32 intrcvlazy; /* IntRcvLazy, 0x100, rev8 */ + uint32 PAD[3]; + + /* counters */ + uint32 cmd52rd; /* Cmd52RdCount, 0x110, rev8, SDIO: cmd52 reads */ + uint32 cmd52wr; /* Cmd52WrCount, 0x114, rev8, SDIO: cmd52 writes */ + uint32 cmd53rd; /* Cmd53RdCount, 0x118, rev8, SDIO: cmd53 reads */ + uint32 cmd53wr; /* Cmd53WrCount, 0x11c, rev8, SDIO: cmd53 writes */ + uint32 abort; /* AbortCount, 0x120, rev8, SDIO: aborts */ + uint32 datacrcerror; /* DataCrcErrorCount, 0x124, rev8, SDIO: frames w/bad CRC */ + uint32 rdoutofsync; /* RdOutOfSyncCount, 0x128, rev8, SDIO/PCMCIA: Rd Frm OOS */ + uint32 wroutofsync; /* RdOutOfSyncCount, 0x12c, rev8, SDIO/PCMCIA: Wr Frm OOS */ + uint32 writebusy; /* WriteBusyCount, 0x130, rev8, SDIO: dev asserted "busy" */ + uint32 readwait; /* ReadWaitCount, 0x134, rev8, SDIO: read: no data avail */ + uint32 readterm; /* ReadTermCount, 0x138, rev8, SDIO: rd frm terminates */ + uint32 writeterm; /* WriteTermCount, 0x13c, rev8, SDIO: wr frm terminates */ + uint32 PAD[40]; + uint32 clockctlstatus; /* ClockCtlStatus, 0x1e0, rev8 */ + uint32 PAD[7]; + + /* DMA engines */ + volatile union { + pcmdma32_t pcm32; + sdiodma32_t sdiod32; + sdiodma64_t sdiod64; + } dma; + + /* SDIO/PCMCIA CIS region */ + char cis[512]; /* 512 byte CIS, 0x400-0x5ff, rev6 */ + + /* PCMCIA function control registers */ + char pcmciafcr[256]; /* PCMCIA FCR, 0x600-6ff, rev6 */ + uint16 PAD[55]; + + /* PCMCIA backplane access */ + uint16 backplanecsr; /* BackplaneCSR, 0x76E, rev6 */ + uint16 backplaneaddr0; /* BackplaneAddr0, 0x770, rev6 */ + uint16 backplaneaddr1; /* BackplaneAddr1, 0x772, rev6 */ + uint16 backplaneaddr2; /* BackplaneAddr2, 0x774, rev6 */ + uint16 backplaneaddr3; /* BackplaneAddr3, 0x776, rev6 */ + uint16 backplanedata0; /* BackplaneData0, 0x778, rev6 */ + uint16 backplanedata1; /* BackplaneData1, 0x77a, rev6 */ + uint16 backplanedata2; /* BackplaneData2, 0x77c, rev6 */ + uint16 backplanedata3; /* BackplaneData3, 0x77e, rev6 */ + uint16 PAD[31]; + + /* sprom "size" & "blank" info */ + uint16 spromstatus; /* SPROMStatus, 0x7BE, rev2 */ + uint32 PAD[464]; + + /* Sonics SiliconBackplane registers */ + sbconfig_t sbconfig; /* SbConfig Regs, 0xf00-0xfff, rev8 */ +} sdpcmd_regs_t; + +/* corecontrol */ +#define CC_CISRDY (1 << 0) /* CIS Ready */ +#define CC_BPRESEN (1 << 1) /* CCCR RES signal causes backplane reset */ +#define CC_F2RDY (1 << 2) /* set CCCR IOR2 bit */ +#define CC_CLRPADSISO (1 << 3) /* clear SDIO pads isolation bit (rev 11) */ + +/* corestatus */ +#define CS_PCMCIAMODE (1 << 0) /* Device Mode; 0=SDIO, 1=PCMCIA */ +#define CS_SMARTDEV (1 << 1) /* 1=smartDev enabled */ +#define CS_F2ENABLED (1 << 2) /* 1=host has enabled the device */ + +#define PCMCIA_MES_PA_MASK 0x7fff /* PCMCIA Message Portal Address Mask */ +#define PCMCIA_MES_PM_MASK 0x7fff /* PCMCIA Message Portal Mask Mask */ +#define PCMCIA_WFBC_MASK 0xffff /* PCMCIA Write Frame Byte Count Mask */ +#define PCMCIA_UT_MASK 0x07ff /* PCMCIA Underflow Timer Mask */ + +/* intstatus */ +#define I_SMB_SW0 (1 << 0) /* To SB Mail S/W interrupt 0 */ +#define I_SMB_SW1 (1 << 1) /* To SB Mail S/W interrupt 1 */ +#define I_SMB_SW2 (1 << 2) /* To SB Mail S/W interrupt 2 */ +#define I_SMB_SW3 (1 << 3) /* To SB Mail S/W interrupt 3 */ +#define I_SMB_SW_MASK 0x0000000f /* To SB Mail S/W interrupts mask */ +#define I_SMB_SW_SHIFT 0 /* To SB Mail S/W interrupts shift */ +#define I_HMB_SW0 (1 << 4) /* To Host Mail S/W interrupt 0 */ +#define I_HMB_SW1 (1 << 5) /* To Host Mail S/W interrupt 1 */ +#define I_HMB_SW2 (1 << 6) /* To Host Mail S/W interrupt 2 */ +#define I_HMB_SW3 (1 << 7) /* To Host Mail S/W interrupt 3 */ +#define I_HMB_SW_MASK 0x000000f0 /* To Host Mail S/W interrupts mask */ +#define I_HMB_SW_SHIFT 4 /* To Host Mail S/W interrupts shift */ +#define I_WR_OOSYNC (1 << 8) /* Write Frame Out Of Sync */ +#define I_RD_OOSYNC (1 << 9) /* Read Frame Out Of Sync */ +#define I_PC (1 << 10) /* descriptor error */ +#define I_PD (1 << 11) /* data error */ +#define I_DE (1 << 12) /* Descriptor protocol Error */ +#define I_RU (1 << 13) /* Receive descriptor Underflow */ +#define I_RO (1 << 14) /* Receive fifo Overflow */ +#define I_XU (1 << 15) /* Transmit fifo Underflow */ +#define I_RI (1 << 16) /* Receive Interrupt */ +#define I_BUSPWR (1 << 17) /* SDIO Bus Power Change (rev 9) */ +#define I_XI (1 << 24) /* Transmit Interrupt */ +#define I_RF_TERM (1 << 25) /* Read Frame Terminate */ +#define I_WF_TERM (1 << 26) /* Write Frame Terminate */ +#define I_PCMCIA_XU (1 << 27) /* PCMCIA Transmit FIFO Underflow */ +#define I_SBINT (1 << 28) /* sbintstatus Interrupt */ +#define I_CHIPACTIVE (1 << 29) /* chip transitioned from doze to active state */ +#define I_SRESET (1 << 30) /* CCCR RES interrupt */ +#define I_IOE2 (1U << 31) /* CCCR IOE2 Bit Changed */ +#define I_ERRORS (I_PC | I_PD | I_DE | I_RU | I_RO | I_XU) /* DMA Errors */ +#define I_DMA (I_RI | I_XI | I_ERRORS) + +/* sbintstatus */ +#define I_SB_SERR (1 << 8) /* Backplane SError (write) */ +#define I_SB_RESPERR (1 << 9) /* Backplane Response Error (read) */ +#define I_SB_SPROMERR (1 << 10) /* Error accessing the sprom */ + +/* sdioaccess */ +#define SDA_DATA_MASK 0x000000ff /* Read/Write Data Mask */ +#define SDA_ADDR_MASK 0x000fff00 /* Read/Write Address Mask */ +#define SDA_ADDR_SHIFT 8 /* Read/Write Address Shift */ +#define SDA_WRITE 0x01000000 /* Write bit */ +#define SDA_READ 0x00000000 /* Write bit cleared for Read */ +#define SDA_BUSY 0x80000000 /* Busy bit */ + +/* sdioaccess-accessible register address spaces */ +#define SDA_CCCR_SPACE 0x000 /* sdioAccess CCCR register space */ +#define SDA_F1_FBR_SPACE 0x100 /* sdioAccess F1 FBR register space */ +#define SDA_F2_FBR_SPACE 0x200 /* sdioAccess F2 FBR register space */ +#define SDA_F1_REG_SPACE 0x300 /* sdioAccess F1 core-specific register space */ + +/* SDA_F1_REG_SPACE sdioaccess-accessible F1 reg space register offsets */ +#define SDA_CHIPCONTROLDATA 0x006 /* ChipControlData */ +#define SDA_CHIPCONTROLENAB 0x007 /* ChipControlEnable */ +#define SDA_F2WATERMARK 0x008 /* Function 2 Watermark */ +#define SDA_DEVICECONTROL 0x009 /* DeviceControl */ +#define SDA_SBADDRLOW 0x00a /* SbAddrLow */ +#define SDA_SBADDRMID 0x00b /* SbAddrMid */ +#define SDA_SBADDRHIGH 0x00c /* SbAddrHigh */ +#define SDA_FRAMECTRL 0x00d /* FrameCtrl */ +#define SDA_CHIPCLOCKCSR 0x00e /* ChipClockCSR */ +#define SDA_SDIOPULLUP 0x00f /* SdioPullUp */ +#define SDA_SDIOWRFRAMEBCLOW 0x019 /* SdioWrFrameBCLow */ +#define SDA_SDIOWRFRAMEBCHIGH 0x01a /* SdioWrFrameBCHigh */ +#define SDA_SDIORDFRAMEBCLOW 0x01b /* SdioRdFrameBCLow */ +#define SDA_SDIORDFRAMEBCHIGH 0x01c /* SdioRdFrameBCHigh */ + +/* SDA_F2WATERMARK */ +#define SDA_F2WATERMARK_MASK 0x7f /* F2Watermark Mask */ + +/* SDA_SBADDRLOW */ +#define SDA_SBADDRLOW_MASK 0x80 /* SbAddrLow Mask */ + +/* SDA_SBADDRMID */ +#define SDA_SBADDRMID_MASK 0xff /* SbAddrMid Mask */ + +/* SDA_SBADDRHIGH */ +#define SDA_SBADDRHIGH_MASK 0xff /* SbAddrHigh Mask */ + +/* SDA_FRAMECTRL */ +#define SFC_RF_TERM (1 << 0) /* Read Frame Terminate */ +#define SFC_WF_TERM (1 << 1) /* Write Frame Terminate */ +#define SFC_CRC4WOOS (1 << 2) /* HW reports CRC error for write out of sync */ +#define SFC_ABORTALL (1 << 3) /* Abort cancels all in-progress frames */ + +/* pcmciaframectrl */ +#define PFC_RF_TERM (1 << 0) /* Read Frame Terminate */ +#define PFC_WF_TERM (1 << 1) /* Write Frame Terminate */ + +/* intrcvlazy */ +#define IRL_TO_MASK 0x00ffffff /* timeout */ +#define IRL_FC_MASK 0xff000000 /* frame count */ +#define IRL_FC_SHIFT 24 /* frame count */ + +/* rx header */ +typedef volatile struct { + uint16 len; + uint16 flags; +} sdpcmd_rxh_t; + +/* rx header flags */ +#define RXF_CRC 0x0001 /* CRC error detected */ +#define RXF_WOOS 0x0002 /* write frame out of sync */ +#define RXF_WF_TERM 0x0004 /* write frame terminated */ +#define RXF_ABORT 0x0008 /* write frame aborted */ +#define RXF_DISCARD (RXF_CRC | RXF_WOOS | RXF_WF_TERM | RXF_ABORT) /* bad frame */ + +/* HW frame tag */ +#define SDPCM_FRAMETAG_LEN 4 /* HW frametag: 2 bytes len, 2 bytes check val */ + +#endif /* _sbsdpcmdev_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/sbsocram.h b/drivers/net/wireless/bcm4329/include/sbsocram.h new file mode 100644 index 0000000000000000000000000000000000000000..5ede0b66d97f9fdd0602f8e962199dfb6669d8ee --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sbsocram.h @@ -0,0 +1,150 @@ +/* + * BCM47XX Sonics SiliconBackplane embedded ram core + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbsocram.h,v 13.9.162.2 2008/12/12 14:13:27 Exp $ + */ + + +#ifndef _SBSOCRAM_H +#define _SBSOCRAM_H + +#ifndef _LANGUAGE_ASSEMBLY + + +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif + + +typedef volatile struct sbsocramregs { + uint32 coreinfo; + uint32 bwalloc; + uint32 extracoreinfo; + uint32 biststat; + uint32 bankidx; + uint32 standbyctrl; + + uint32 errlogstatus; + uint32 errlogaddr; + + uint32 cambankidx; + uint32 cambankstandbyctrl; + uint32 cambankpatchctrl; + uint32 cambankpatchtblbaseaddr; + uint32 cambankcmdreg; + uint32 cambankdatareg; + uint32 cambankmaskreg; + uint32 PAD[17]; + uint32 extmemconfig; + uint32 extmemparitycsr; + uint32 extmemparityerrdata; + uint32 extmemparityerrcnt; + uint32 extmemwrctrlandsize; + uint32 PAD[84]; + uint32 workaround; + uint32 pwrctl; +} sbsocramregs_t; + +#endif + + +#define SR_COREINFO 0x00 +#define SR_BWALLOC 0x04 +#define SR_BISTSTAT 0x0c +#define SR_BANKINDEX 0x10 +#define SR_BANKSTBYCTL 0x14 +#define SR_PWRCTL 0x1e8 + + +#define SRCI_PT_MASK 0x00070000 +#define SRCI_PT_SHIFT 16 + +#define SRCI_PT_OCP_OCP 0 +#define SRCI_PT_AXI_OCP 1 +#define SRCI_PT_ARM7AHB_OCP 2 +#define SRCI_PT_CM3AHB_OCP 3 +#define SRCI_PT_AXI_AXI 4 +#define SRCI_PT_AHB_AXI 5 + +#define SRCI_LSS_MASK 0x00f00000 +#define SRCI_LSS_SHIFT 20 +#define SRCI_LRS_MASK 0x0f000000 +#define SRCI_LRS_SHIFT 24 + + +#define SRCI_MS0_MASK 0xf +#define SR_MS0_BASE 16 + + +#define SRCI_ROMNB_MASK 0xf000 +#define SRCI_ROMNB_SHIFT 12 +#define SRCI_ROMBSZ_MASK 0xf00 +#define SRCI_ROMBSZ_SHIFT 8 +#define SRCI_SRNB_MASK 0xf0 +#define SRCI_SRNB_SHIFT 4 +#define SRCI_SRBSZ_MASK 0xf +#define SRCI_SRBSZ_SHIFT 0 + +#define SR_BSZ_BASE 14 + + +#define SRSC_SBYOVR_MASK 0x80000000 +#define SRSC_SBYOVR_SHIFT 31 +#define SRSC_SBYOVRVAL_MASK 0x60000000 +#define SRSC_SBYOVRVAL_SHIFT 29 +#define SRSC_SBYEN_MASK 0x01000000 +#define SRSC_SBYEN_SHIFT 24 + + +#define SRPC_PMU_STBYDIS_MASK 0x00000010 +#define SRPC_PMU_STBYDIS_SHIFT 4 +#define SRPC_STBYOVRVAL_MASK 0x00000008 +#define SRPC_STBYOVRVAL_SHIFT 3 +#define SRPC_STBYOVR_MASK 0x00000007 +#define SRPC_STBYOVR_SHIFT 0 + + +#define SRECC_NUM_BANKS_MASK 0x000000F0 +#define SRECC_NUM_BANKS_SHIFT 4 +#define SRECC_BANKSIZE_MASK 0x0000000F +#define SRECC_BANKSIZE_SHIFT 0 + +#define SRECC_BANKSIZE(value) (1 << (value)) + + +#define SRCBPC_PATCHENABLE 0x80000000 + +#define SRP_ADDRESS 0x0001FFFC +#define SRP_VALID 0x8000 + + +#define SRCMD_WRITE 0x00020000 +#define SRCMD_READ 0x00010000 +#define SRCMD_DONE 0x80000000 + +#define SRCMD_DONE_DLY 1000 + + +#endif diff --git a/drivers/net/wireless/bcm4329/include/sdio.h b/drivers/net/wireless/bcm4329/include/sdio.h new file mode 100644 index 0000000000000000000000000000000000000000..280cb845fb04f484224a86cb37dc65b35a46d96b --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sdio.h @@ -0,0 +1,566 @@ +/* + * SDIO spec header file + * Protocol and standard (common) device definitions + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sdio.h,v 13.24.4.1.4.1.16.1 2009/08/12 01:08:02 Exp $ + */ + +#ifndef _SDIO_H +#define _SDIO_H + + +/* CCCR structure for function 0 */ +typedef volatile struct { + uint8 cccr_sdio_rev; /* RO, cccr and sdio revision */ + uint8 sd_rev; /* RO, sd spec revision */ + uint8 io_en; /* I/O enable */ + uint8 io_rdy; /* I/O ready reg */ + uint8 intr_ctl; /* Master and per function interrupt enable control */ + uint8 intr_status; /* RO, interrupt pending status */ + uint8 io_abort; /* read/write abort or reset all functions */ + uint8 bus_inter; /* bus interface control */ + uint8 capability; /* RO, card capability */ + + uint8 cis_base_low; /* 0x9 RO, common CIS base address, LSB */ + uint8 cis_base_mid; + uint8 cis_base_high; /* 0xB RO, common CIS base address, MSB */ + + /* suspend/resume registers */ + uint8 bus_suspend; /* 0xC */ + uint8 func_select; /* 0xD */ + uint8 exec_flag; /* 0xE */ + uint8 ready_flag; /* 0xF */ + + uint8 fn0_blk_size[2]; /* 0x10(LSB), 0x11(MSB) */ + + uint8 power_control; /* 0x12 (SDIO version 1.10) */ + + uint8 speed_control; /* 0x13 */ +} sdio_regs_t; + +/* SDIO Device CCCR offsets */ +#define SDIOD_CCCR_REV 0x00 +#define SDIOD_CCCR_SDREV 0x01 +#define SDIOD_CCCR_IOEN 0x02 +#define SDIOD_CCCR_IORDY 0x03 +#define SDIOD_CCCR_INTEN 0x04 +#define SDIOD_CCCR_INTPEND 0x05 +#define SDIOD_CCCR_IOABORT 0x06 +#define SDIOD_CCCR_BICTRL 0x07 +#define SDIOD_CCCR_CAPABLITIES 0x08 +#define SDIOD_CCCR_CISPTR_0 0x09 +#define SDIOD_CCCR_CISPTR_1 0x0A +#define SDIOD_CCCR_CISPTR_2 0x0B +#define SDIOD_CCCR_BUSSUSP 0x0C +#define SDIOD_CCCR_FUNCSEL 0x0D +#define SDIOD_CCCR_EXECFLAGS 0x0E +#define SDIOD_CCCR_RDYFLAGS 0x0F +#define SDIOD_CCCR_BLKSIZE_0 0x10 +#define SDIOD_CCCR_BLKSIZE_1 0x11 +#define SDIOD_CCCR_POWER_CONTROL 0x12 +#define SDIOD_CCCR_SPEED_CONTROL 0x13 + +/* Broadcom extensions (corerev >= 1) */ +#define SDIOD_CCCR_BRCM_SEPINT 0xf2 + +/* cccr_sdio_rev */ +#define SDIO_REV_SDIOID_MASK 0xf0 /* SDIO spec revision number */ +#define SDIO_REV_CCCRID_MASK 0x0f /* CCCR format version number */ + +/* sd_rev */ +#define SD_REV_PHY_MASK 0x0f /* SD format version number */ + +/* io_en */ +#define SDIO_FUNC_ENABLE_1 0x02 /* function 1 I/O enable */ +#define SDIO_FUNC_ENABLE_2 0x04 /* function 2 I/O enable */ + +/* io_rdys */ +#define SDIO_FUNC_READY_1 0x02 /* function 1 I/O ready */ +#define SDIO_FUNC_READY_2 0x04 /* function 2 I/O ready */ + +/* intr_ctl */ +#define INTR_CTL_MASTER_EN 0x1 /* interrupt enable master */ +#define INTR_CTL_FUNC1_EN 0x2 /* interrupt enable for function 1 */ +#define INTR_CTL_FUNC2_EN 0x4 /* interrupt enable for function 2 */ + +/* intr_status */ +#define INTR_STATUS_FUNC1 0x2 /* interrupt pending for function 1 */ +#define INTR_STATUS_FUNC2 0x4 /* interrupt pending for function 2 */ + +/* io_abort */ +#define IO_ABORT_RESET_ALL 0x08 /* I/O card reset */ +#define IO_ABORT_FUNC_MASK 0x07 /* abort selction: function x */ + +/* bus_inter */ +#define BUS_CARD_DETECT_DIS 0x80 /* Card Detect disable */ +#define BUS_SPI_CONT_INTR_CAP 0x40 /* support continuous SPI interrupt */ +#define BUS_SPI_CONT_INTR_EN 0x20 /* continuous SPI interrupt enable */ +#define BUS_SD_DATA_WIDTH_MASK 0x03 /* bus width mask */ +#define BUS_SD_DATA_WIDTH_4BIT 0x02 /* bus width 4-bit mode */ +#define BUS_SD_DATA_WIDTH_1BIT 0x00 /* bus width 1-bit mode */ + +/* capability */ +#define SDIO_CAP_4BLS 0x80 /* 4-bit support for low speed card */ +#define SDIO_CAP_LSC 0x40 /* low speed card */ +#define SDIO_CAP_E4MI 0x20 /* enable interrupt between block of data in 4-bit mode */ +#define SDIO_CAP_S4MI 0x10 /* support interrupt between block of data in 4-bit mode */ +#define SDIO_CAP_SBS 0x08 /* support suspend/resume */ +#define SDIO_CAP_SRW 0x04 /* support read wait */ +#define SDIO_CAP_SMB 0x02 /* support multi-block transfer */ +#define SDIO_CAP_SDC 0x01 /* Support Direct commands during multi-byte transfer */ + +/* power_control */ +#define SDIO_POWER_SMPC 0x01 /* supports master power control (RO) */ +#define SDIO_POWER_EMPC 0x02 /* enable master power control (allow > 200mA) (RW) */ + +/* speed_control (control device entry into high-speed clocking mode) */ +#define SDIO_SPEED_SHS 0x01 /* supports high-speed [clocking] mode (RO) */ +#define SDIO_SPEED_EHS 0x02 /* enable high-speed [clocking] mode (RW) */ + +/* brcm sepint */ +#define SDIO_SEPINT_MASK 0x01 /* route sdpcmdev intr onto separate pad (chip-specific) */ +#define SDIO_SEPINT_OE 0x02 /* 1 asserts output enable for above pad */ +#define SDIO_SEPINT_ACT_HI 0x04 /* use active high interrupt level instead of active low */ + +/* FBR structure for function 1-7, FBR addresses and register offsets */ +typedef volatile struct { + uint8 devctr; /* device interface, CSA control */ + uint8 ext_dev; /* extended standard I/O device type code */ + uint8 pwr_sel; /* power selection support */ + uint8 PAD[6]; /* reserved */ + + uint8 cis_low; /* CIS LSB */ + uint8 cis_mid; + uint8 cis_high; /* CIS MSB */ + uint8 csa_low; /* code storage area, LSB */ + uint8 csa_mid; + uint8 csa_high; /* code storage area, MSB */ + uint8 csa_dat_win; /* data access window to function */ + + uint8 fnx_blk_size[2]; /* block size, little endian */ +} sdio_fbr_t; + +/* Maximum number of I/O funcs */ +#define SDIOD_MAX_IOFUNCS 7 + +/* SDIO Device FBR Start Address */ +#define SDIOD_FBR_STARTADDR 0x100 + +/* SDIO Device FBR Size */ +#define SDIOD_FBR_SIZE 0x100 + +/* Macro to calculate FBR register base */ +#define SDIOD_FBR_BASE(n) ((n) * 0x100) + +/* Function register offsets */ +#define SDIOD_FBR_DEVCTR 0x00 /* basic info for function */ +#define SDIOD_FBR_EXT_DEV 0x01 /* extended I/O device code */ +#define SDIOD_FBR_PWR_SEL 0x02 /* power selection bits */ + +/* SDIO Function CIS ptr offset */ +#define SDIOD_FBR_CISPTR_0 0x09 +#define SDIOD_FBR_CISPTR_1 0x0A +#define SDIOD_FBR_CISPTR_2 0x0B + +/* Code Storage Area pointer */ +#define SDIOD_FBR_CSA_ADDR_0 0x0C +#define SDIOD_FBR_CSA_ADDR_1 0x0D +#define SDIOD_FBR_CSA_ADDR_2 0x0E +#define SDIOD_FBR_CSA_DATA 0x0F + +/* SDIO Function I/O Block Size */ +#define SDIOD_FBR_BLKSIZE_0 0x10 +#define SDIOD_FBR_BLKSIZE_1 0x11 + +/* devctr */ +#define SDIOD_FBR_DEVCTR_DIC 0x0f /* device interface code */ +#define SDIOD_FBR_DECVTR_CSA 0x40 /* CSA support flag */ +#define SDIOD_FBR_DEVCTR_CSA_EN 0x80 /* CSA enabled */ +/* interface codes */ +#define SDIOD_DIC_NONE 0 /* SDIO standard interface is not supported */ +#define SDIOD_DIC_UART 1 +#define SDIOD_DIC_BLUETOOTH_A 2 +#define SDIOD_DIC_BLUETOOTH_B 3 +#define SDIOD_DIC_GPS 4 +#define SDIOD_DIC_CAMERA 5 +#define SDIOD_DIC_PHS 6 +#define SDIOD_DIC_WLAN 7 +#define SDIOD_DIC_EXT 0xf /* extended device interface, read ext_dev register */ + +/* pwr_sel */ +#define SDIOD_PWR_SEL_SPS 0x01 /* supports power selection */ +#define SDIOD_PWR_SEL_EPS 0x02 /* enable power selection (low-current mode) */ + +/* misc defines */ +#define SDIO_FUNC_0 0 +#define SDIO_FUNC_1 1 +#define SDIO_FUNC_2 2 +#define SDIO_FUNC_3 3 +#define SDIO_FUNC_4 4 +#define SDIO_FUNC_5 5 +#define SDIO_FUNC_6 6 +#define SDIO_FUNC_7 7 + +#define SD_CARD_TYPE_UNKNOWN 0 /* bad type or unrecognized */ +#define SD_CARD_TYPE_IO 1 /* IO only card */ +#define SD_CARD_TYPE_MEMORY 2 /* memory only card */ +#define SD_CARD_TYPE_COMBO 3 /* IO and memory combo card */ + +#define SDIO_MAX_BLOCK_SIZE 2048 /* maximum block size for block mode operation */ +#define SDIO_MIN_BLOCK_SIZE 1 /* minimum block size for block mode operation */ + +/* Card registers: status bit position */ +#define CARDREG_STATUS_BIT_OUTOFRANGE 31 +#define CARDREG_STATUS_BIT_COMCRCERROR 23 +#define CARDREG_STATUS_BIT_ILLEGALCOMMAND 22 +#define CARDREG_STATUS_BIT_ERROR 19 +#define CARDREG_STATUS_BIT_IOCURRENTSTATE3 12 +#define CARDREG_STATUS_BIT_IOCURRENTSTATE2 11 +#define CARDREG_STATUS_BIT_IOCURRENTSTATE1 10 +#define CARDREG_STATUS_BIT_IOCURRENTSTATE0 9 +#define CARDREG_STATUS_BIT_FUN_NUM_ERROR 4 + + + +#define SD_CMD_GO_IDLE_STATE 0 /* mandatory for SDIO */ +#define SD_CMD_SEND_OPCOND 1 +#define SD_CMD_MMC_SET_RCA 3 +#define SD_CMD_IO_SEND_OP_COND 5 /* mandatory for SDIO */ +#define SD_CMD_SELECT_DESELECT_CARD 7 +#define SD_CMD_SEND_CSD 9 +#define SD_CMD_SEND_CID 10 +#define SD_CMD_STOP_TRANSMISSION 12 +#define SD_CMD_SEND_STATUS 13 +#define SD_CMD_GO_INACTIVE_STATE 15 +#define SD_CMD_SET_BLOCKLEN 16 +#define SD_CMD_READ_SINGLE_BLOCK 17 +#define SD_CMD_READ_MULTIPLE_BLOCK 18 +#define SD_CMD_WRITE_BLOCK 24 +#define SD_CMD_WRITE_MULTIPLE_BLOCK 25 +#define SD_CMD_PROGRAM_CSD 27 +#define SD_CMD_SET_WRITE_PROT 28 +#define SD_CMD_CLR_WRITE_PROT 29 +#define SD_CMD_SEND_WRITE_PROT 30 +#define SD_CMD_ERASE_WR_BLK_START 32 +#define SD_CMD_ERASE_WR_BLK_END 33 +#define SD_CMD_ERASE 38 +#define SD_CMD_LOCK_UNLOCK 42 +#define SD_CMD_IO_RW_DIRECT 52 /* mandatory for SDIO */ +#define SD_CMD_IO_RW_EXTENDED 53 /* mandatory for SDIO */ +#define SD_CMD_APP_CMD 55 +#define SD_CMD_GEN_CMD 56 +#define SD_CMD_READ_OCR 58 +#define SD_CMD_CRC_ON_OFF 59 /* mandatory for SDIO */ +#define SD_ACMD_SD_STATUS 13 +#define SD_ACMD_SEND_NUM_WR_BLOCKS 22 +#define SD_ACMD_SET_WR_BLOCK_ERASE_CNT 23 +#define SD_ACMD_SD_SEND_OP_COND 41 +#define SD_ACMD_SET_CLR_CARD_DETECT 42 +#define SD_ACMD_SEND_SCR 51 + +/* argument for SD_CMD_IO_RW_DIRECT and SD_CMD_IO_RW_EXTENDED */ +#define SD_IO_OP_READ 0 /* Read_Write: Read */ +#define SD_IO_OP_WRITE 1 /* Read_Write: Write */ +#define SD_IO_RW_NORMAL 0 /* no RAW */ +#define SD_IO_RW_RAW 1 /* RAW */ +#define SD_IO_BYTE_MODE 0 /* Byte Mode */ +#define SD_IO_BLOCK_MODE 1 /* BlockMode */ +#define SD_IO_FIXED_ADDRESS 0 /* fix Address */ +#define SD_IO_INCREMENT_ADDRESS 1 /* IncrementAddress */ + +/* build SD_CMD_IO_RW_DIRECT Argument */ +#define SDIO_IO_RW_DIRECT_ARG(rw, raw, func, addr, data) \ + ((((rw) & 1) << 31) | (((func) & 0x7) << 28) | (((raw) & 1) << 27) | \ + (((addr) & 0x1FFFF) << 9) | ((data) & 0xFF)) + +/* build SD_CMD_IO_RW_EXTENDED Argument */ +#define SDIO_IO_RW_EXTENDED_ARG(rw, blk, func, addr, inc_addr, count) \ + ((((rw) & 1) << 31) | (((func) & 0x7) << 28) | (((blk) & 1) << 27) | \ + (((inc_addr) & 1) << 26) | (((addr) & 0x1FFFF) << 9) | ((count) & 0x1FF)) + +/* SDIO response parameters */ +#define SD_RSP_NO_NONE 0 +#define SD_RSP_NO_1 1 +#define SD_RSP_NO_2 2 +#define SD_RSP_NO_3 3 +#define SD_RSP_NO_4 4 +#define SD_RSP_NO_5 5 +#define SD_RSP_NO_6 6 + + /* Modified R6 response (to CMD3) */ +#define SD_RSP_MR6_COM_CRC_ERROR 0x8000 +#define SD_RSP_MR6_ILLEGAL_COMMAND 0x4000 +#define SD_RSP_MR6_ERROR 0x2000 + + /* Modified R1 in R4 Response (to CMD5) */ +#define SD_RSP_MR1_SBIT 0x80 +#define SD_RSP_MR1_PARAMETER_ERROR 0x40 +#define SD_RSP_MR1_RFU5 0x20 +#define SD_RSP_MR1_FUNC_NUM_ERROR 0x10 +#define SD_RSP_MR1_COM_CRC_ERROR 0x08 +#define SD_RSP_MR1_ILLEGAL_COMMAND 0x04 +#define SD_RSP_MR1_RFU1 0x02 +#define SD_RSP_MR1_IDLE_STATE 0x01 + + /* R5 response (to CMD52 and CMD53) */ +#define SD_RSP_R5_COM_CRC_ERROR 0x80 +#define SD_RSP_R5_ILLEGAL_COMMAND 0x40 +#define SD_RSP_R5_IO_CURRENTSTATE1 0x20 +#define SD_RSP_R5_IO_CURRENTSTATE0 0x10 +#define SD_RSP_R5_ERROR 0x08 +#define SD_RSP_R5_RFU 0x04 +#define SD_RSP_R5_FUNC_NUM_ERROR 0x02 +#define SD_RSP_R5_OUT_OF_RANGE 0x01 + +#define SD_RSP_R5_ERRBITS 0xCB + + +/* ------------------------------------------------ + * SDIO Commands and responses + * + * I/O only commands are: + * CMD0, CMD3, CMD5, CMD7, CMD15, CMD52, CMD53 + * ------------------------------------------------ + */ + +/* SDIO Commands */ +#define SDIOH_CMD_0 0 +#define SDIOH_CMD_3 3 +#define SDIOH_CMD_5 5 +#define SDIOH_CMD_7 7 +#define SDIOH_CMD_15 15 +#define SDIOH_CMD_52 52 +#define SDIOH_CMD_53 53 +#define SDIOH_CMD_59 59 + +/* SDIO Command Responses */ +#define SDIOH_RSP_NONE 0 +#define SDIOH_RSP_R1 1 +#define SDIOH_RSP_R2 2 +#define SDIOH_RSP_R3 3 +#define SDIOH_RSP_R4 4 +#define SDIOH_RSP_R5 5 +#define SDIOH_RSP_R6 6 + +/* + * SDIO Response Error flags + */ +#define SDIOH_RSP5_ERROR_FLAGS 0xCB + +/* ------------------------------------------------ + * SDIO Command structures. I/O only commands are: + * + * CMD0, CMD3, CMD5, CMD7, CMD15, CMD52, CMD53 + * ------------------------------------------------ + */ + +#define CMD5_OCR_M BITFIELD_MASK(24) +#define CMD5_OCR_S 0 + +#define CMD7_RCA_M BITFIELD_MASK(16) +#define CMD7_RCA_S 16 + +#define CMD_15_RCA_M BITFIELD_MASK(16) +#define CMD_15_RCA_S 16 + +#define CMD52_DATA_M BITFIELD_MASK(8) /* Bits [7:0] - Write Data/Stuff bits of CMD52 + */ +#define CMD52_DATA_S 0 +#define CMD52_REG_ADDR_M BITFIELD_MASK(17) /* Bits [25:9] - register address */ +#define CMD52_REG_ADDR_S 9 +#define CMD52_RAW_M BITFIELD_MASK(1) /* Bit 27 - Read after Write flag */ +#define CMD52_RAW_S 27 +#define CMD52_FUNCTION_M BITFIELD_MASK(3) /* Bits [30:28] - Function number */ +#define CMD52_FUNCTION_S 28 +#define CMD52_RW_FLAG_M BITFIELD_MASK(1) /* Bit 31 - R/W flag */ +#define CMD52_RW_FLAG_S 31 + + +#define CMD53_BYTE_BLK_CNT_M BITFIELD_MASK(9) /* Bits [8:0] - Byte/Block Count of CMD53 */ +#define CMD53_BYTE_BLK_CNT_S 0 +#define CMD53_REG_ADDR_M BITFIELD_MASK(17) /* Bits [25:9] - register address */ +#define CMD53_REG_ADDR_S 9 +#define CMD53_OP_CODE_M BITFIELD_MASK(1) /* Bit 26 - R/W Operation Code */ +#define CMD53_OP_CODE_S 26 +#define CMD53_BLK_MODE_M BITFIELD_MASK(1) /* Bit 27 - Block Mode */ +#define CMD53_BLK_MODE_S 27 +#define CMD53_FUNCTION_M BITFIELD_MASK(3) /* Bits [30:28] - Function number */ +#define CMD53_FUNCTION_S 28 +#define CMD53_RW_FLAG_M BITFIELD_MASK(1) /* Bit 31 - R/W flag */ +#define CMD53_RW_FLAG_S 31 + +/* ------------------------------------------------------ + * SDIO Command Response structures for SD1 and SD4 modes + * ----------------------------------------------------- + */ +#define RSP4_IO_OCR_M BITFIELD_MASK(24) /* Bits [23:0] - Card's OCR Bits [23:0] */ +#define RSP4_IO_OCR_S 0 +#define RSP4_STUFF_M BITFIELD_MASK(3) /* Bits [26:24] - Stuff bits */ +#define RSP4_STUFF_S 24 +#define RSP4_MEM_PRESENT_M BITFIELD_MASK(1) /* Bit 27 - Memory present */ +#define RSP4_MEM_PRESENT_S 27 +#define RSP4_NUM_FUNCS_M BITFIELD_MASK(3) /* Bits [30:28] - Number of I/O funcs */ +#define RSP4_NUM_FUNCS_S 28 +#define RSP4_CARD_READY_M BITFIELD_MASK(1) /* Bit 31 - SDIO card ready */ +#define RSP4_CARD_READY_S 31 + +#define RSP6_STATUS_M BITFIELD_MASK(16) /* Bits [15:0] - Card status bits [19,22,23,12:0] + */ +#define RSP6_STATUS_S 0 +#define RSP6_IO_RCA_M BITFIELD_MASK(16) /* Bits [31:16] - RCA bits[31-16] */ +#define RSP6_IO_RCA_S 16 + +#define RSP1_AKE_SEQ_ERROR_M BITFIELD_MASK(1) /* Bit 3 - Authentication seq error */ +#define RSP1_AKE_SEQ_ERROR_S 3 +#define RSP1_APP_CMD_M BITFIELD_MASK(1) /* Bit 5 - Card expects ACMD */ +#define RSP1_APP_CMD_S 5 +#define RSP1_READY_FOR_DATA_M BITFIELD_MASK(1) /* Bit 8 - Ready for data (buff empty) */ +#define RSP1_READY_FOR_DATA_S 8 +#define RSP1_CURR_STATE_M BITFIELD_MASK(4) /* Bits [12:9] - State of card + * when Cmd was received + */ +#define RSP1_CURR_STATE_S 9 +#define RSP1_EARSE_RESET_M BITFIELD_MASK(1) /* Bit 13 - Erase seq cleared */ +#define RSP1_EARSE_RESET_S 13 +#define RSP1_CARD_ECC_DISABLE_M BITFIELD_MASK(1) /* Bit 14 - Card ECC disabled */ +#define RSP1_CARD_ECC_DISABLE_S 14 +#define RSP1_WP_ERASE_SKIP_M BITFIELD_MASK(1) /* Bit 15 - Partial blocks erased due to W/P */ +#define RSP1_WP_ERASE_SKIP_S 15 +#define RSP1_CID_CSD_OVERW_M BITFIELD_MASK(1) /* Bit 16 - Illegal write to CID or R/O bits + * of CSD + */ +#define RSP1_CID_CSD_OVERW_S 16 +#define RSP1_ERROR_M BITFIELD_MASK(1) /* Bit 19 - General/Unknown error */ +#define RSP1_ERROR_S 19 +#define RSP1_CC_ERROR_M BITFIELD_MASK(1) /* Bit 20 - Internal Card Control error */ +#define RSP1_CC_ERROR_S 20 +#define RSP1_CARD_ECC_FAILED_M BITFIELD_MASK(1) /* Bit 21 - Card internal ECC failed + * to correct data + */ +#define RSP1_CARD_ECC_FAILED_S 21 +#define RSP1_ILLEGAL_CMD_M BITFIELD_MASK(1) /* Bit 22 - Cmd not legal for the card state */ +#define RSP1_ILLEGAL_CMD_S 22 +#define RSP1_COM_CRC_ERROR_M BITFIELD_MASK(1) /* Bit 23 - CRC check of previous command failed + */ +#define RSP1_COM_CRC_ERROR_S 23 +#define RSP1_LOCK_UNLOCK_FAIL_M BITFIELD_MASK(1) /* Bit 24 - Card lock-unlock Cmd Seq error */ +#define RSP1_LOCK_UNLOCK_FAIL_S 24 +#define RSP1_CARD_LOCKED_M BITFIELD_MASK(1) /* Bit 25 - Card locked by the host */ +#define RSP1_CARD_LOCKED_S 25 +#define RSP1_WP_VIOLATION_M BITFIELD_MASK(1) /* Bit 26 - Attempt to program + * write-protected blocks + */ +#define RSP1_WP_VIOLATION_S 26 +#define RSP1_ERASE_PARAM_M BITFIELD_MASK(1) /* Bit 27 - Invalid erase blocks */ +#define RSP1_ERASE_PARAM_S 27 +#define RSP1_ERASE_SEQ_ERR_M BITFIELD_MASK(1) /* Bit 28 - Erase Cmd seq error */ +#define RSP1_ERASE_SEQ_ERR_S 28 +#define RSP1_BLK_LEN_ERR_M BITFIELD_MASK(1) /* Bit 29 - Block length error */ +#define RSP1_BLK_LEN_ERR_S 29 +#define RSP1_ADDR_ERR_M BITFIELD_MASK(1) /* Bit 30 - Misaligned address */ +#define RSP1_ADDR_ERR_S 30 +#define RSP1_OUT_OF_RANGE_M BITFIELD_MASK(1) /* Bit 31 - Cmd arg was out of range */ +#define RSP1_OUT_OF_RANGE_S 31 + + +#define RSP5_DATA_M BITFIELD_MASK(8) /* Bits [0:7] - data */ +#define RSP5_DATA_S 0 +#define RSP5_FLAGS_M BITFIELD_MASK(8) /* Bit [15:8] - Rsp flags */ +#define RSP5_FLAGS_S 8 +#define RSP5_STUFF_M BITFIELD_MASK(16) /* Bits [31:16] - Stuff bits */ +#define RSP5_STUFF_S 16 + +/* ---------------------------------------------- + * SDIO Command Response structures for SPI mode + * ---------------------------------------------- + */ +#define SPIRSP4_IO_OCR_M BITFIELD_MASK(16) /* Bits [15:0] - Card's OCR Bits [23:8] */ +#define SPIRSP4_IO_OCR_S 0 +#define SPIRSP4_STUFF_M BITFIELD_MASK(3) /* Bits [18:16] - Stuff bits */ +#define SPIRSP4_STUFF_S 16 +#define SPIRSP4_MEM_PRESENT_M BITFIELD_MASK(1) /* Bit 19 - Memory present */ +#define SPIRSP4_MEM_PRESENT_S 19 +#define SPIRSP4_NUM_FUNCS_M BITFIELD_MASK(3) /* Bits [22:20] - Number of I/O funcs */ +#define SPIRSP4_NUM_FUNCS_S 20 +#define SPIRSP4_CARD_READY_M BITFIELD_MASK(1) /* Bit 23 - SDIO card ready */ +#define SPIRSP4_CARD_READY_S 23 +#define SPIRSP4_IDLE_STATE_M BITFIELD_MASK(1) /* Bit 24 - idle state */ +#define SPIRSP4_IDLE_STATE_S 24 +#define SPIRSP4_ILLEGAL_CMD_M BITFIELD_MASK(1) /* Bit 26 - Illegal Cmd error */ +#define SPIRSP4_ILLEGAL_CMD_S 26 +#define SPIRSP4_COM_CRC_ERROR_M BITFIELD_MASK(1) /* Bit 27 - COM CRC error */ +#define SPIRSP4_COM_CRC_ERROR_S 27 +#define SPIRSP4_FUNC_NUM_ERROR_M BITFIELD_MASK(1) /* Bit 28 - Function number error + */ +#define SPIRSP4_FUNC_NUM_ERROR_S 28 +#define SPIRSP4_PARAM_ERROR_M BITFIELD_MASK(1) /* Bit 30 - Parameter Error Bit */ +#define SPIRSP4_PARAM_ERROR_S 30 +#define SPIRSP4_START_BIT_M BITFIELD_MASK(1) /* Bit 31 - Start Bit */ +#define SPIRSP4_START_BIT_S 31 + +#define SPIRSP5_DATA_M BITFIELD_MASK(8) /* Bits [23:16] - R/W Data */ +#define SPIRSP5_DATA_S 16 +#define SPIRSP5_IDLE_STATE_M BITFIELD_MASK(1) /* Bit 24 - Idle state */ +#define SPIRSP5_IDLE_STATE_S 24 +#define SPIRSP5_ILLEGAL_CMD_M BITFIELD_MASK(1) /* Bit 26 - Illegal Cmd error */ +#define SPIRSP5_ILLEGAL_CMD_S 26 +#define SPIRSP5_COM_CRC_ERROR_M BITFIELD_MASK(1) /* Bit 27 - COM CRC error */ +#define SPIRSP5_COM_CRC_ERROR_S 27 +#define SPIRSP5_FUNC_NUM_ERROR_M BITFIELD_MASK(1) /* Bit 28 - Function number error + */ +#define SPIRSP5_FUNC_NUM_ERROR_S 28 +#define SPIRSP5_PARAM_ERROR_M BITFIELD_MASK(1) /* Bit 30 - Parameter Error Bit */ +#define SPIRSP5_PARAM_ERROR_S 30 +#define SPIRSP5_START_BIT_M BITFIELD_MASK(1) /* Bit 31 - Start Bit */ +#define SPIRSP5_START_BIT_S 31 + +/* RSP6 card status format; Pg 68 Physical Layer spec v 1.10 */ +#define RSP6STAT_AKE_SEQ_ERROR_M BITFIELD_MASK(1) /* Bit 3 - Authentication seq error + */ +#define RSP6STAT_AKE_SEQ_ERROR_S 3 +#define RSP6STAT_APP_CMD_M BITFIELD_MASK(1) /* Bit 5 - Card expects ACMD */ +#define RSP6STAT_APP_CMD_S 5 +#define RSP6STAT_READY_FOR_DATA_M BITFIELD_MASK(1) /* Bit 8 - Ready for data + * (buff empty) + */ +#define RSP6STAT_READY_FOR_DATA_S 8 +#define RSP6STAT_CURR_STATE_M BITFIELD_MASK(4) /* Bits [12:9] - Card state at + * Cmd reception + */ +#define RSP6STAT_CURR_STATE_S 9 +#define RSP6STAT_ERROR_M BITFIELD_MASK(1) /* Bit 13 - General/Unknown error Bit 19 + */ +#define RSP6STAT_ERROR_S 13 +#define RSP6STAT_ILLEGAL_CMD_M BITFIELD_MASK(1) /* Bit 14 - Illegal cmd for + * card state Bit 22 + */ +#define RSP6STAT_ILLEGAL_CMD_S 14 +#define RSP6STAT_COM_CRC_ERROR_M BITFIELD_MASK(1) /* Bit 15 - CRC previous command + * failed Bit 23 + */ +#define RSP6STAT_COM_CRC_ERROR_S 15 + +#define SDIOH_XFER_TYPE_READ SD_IO_OP_READ +#define SDIOH_XFER_TYPE_WRITE SD_IO_OP_WRITE + +#endif /* _SDIO_H */ diff --git a/drivers/net/wireless/bcm4329/include/sdioh.h b/drivers/net/wireless/bcm4329/include/sdioh.h new file mode 100644 index 0000000000000000000000000000000000000000..8123452eac2b3a2bb77c02358584aa07103d9c8f --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sdioh.h @@ -0,0 +1,299 @@ +/* + * SDIO Host Controller Spec header file + * Register map and definitions for the Standard Host Controller + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sdioh.h,v 13.13.18.1.16.3 2009/12/08 22:34:21 Exp $ + */ + +#ifndef _SDIOH_H +#define _SDIOH_H + +#define SD_SysAddr 0x000 +#define SD_BlockSize 0x004 +#define SD_BlockCount 0x006 +#define SD_Arg0 0x008 +#define SD_Arg1 0x00A +#define SD_TransferMode 0x00C +#define SD_Command 0x00E +#define SD_Response0 0x010 +#define SD_Response1 0x012 +#define SD_Response2 0x014 +#define SD_Response3 0x016 +#define SD_Response4 0x018 +#define SD_Response5 0x01A +#define SD_Response6 0x01C +#define SD_Response7 0x01E +#define SD_BufferDataPort0 0x020 +#define SD_BufferDataPort1 0x022 +#define SD_PresentState 0x024 +#define SD_HostCntrl 0x028 +#define SD_PwrCntrl 0x029 +#define SD_BlockGapCntrl 0x02A +#define SD_WakeupCntrl 0x02B +#define SD_ClockCntrl 0x02C +#define SD_TimeoutCntrl 0x02E +#define SD_SoftwareReset 0x02F +#define SD_IntrStatus 0x030 +#define SD_ErrorIntrStatus 0x032 +#define SD_IntrStatusEnable 0x034 +#define SD_ErrorIntrStatusEnable 0x036 +#define SD_IntrSignalEnable 0x038 +#define SD_ErrorIntrSignalEnable 0x03A +#define SD_CMD12ErrorStatus 0x03C +#define SD_Capabilities 0x040 +#define SD_Capabilities_Reserved 0x044 +#define SD_MaxCurCap 0x048 +#define SD_MaxCurCap_Reserved 0x04C +#define SD_ADMA_SysAddr 0x58 +#define SD_SlotInterruptStatus 0x0FC +#define SD_HostControllerVersion 0x0FE + +/* SD specific registers in PCI config space */ +#define SD_SlotInfo 0x40 + +/* SD_Capabilities reg (0x040) */ +#define CAP_TO_CLKFREQ_M BITFIELD_MASK(6) +#define CAP_TO_CLKFREQ_S 0 +#define CAP_TO_CLKUNIT_M BITFIELD_MASK(1) +#define CAP_TO_CLKUNIT_S 7 +#define CAP_BASECLK_M BITFIELD_MASK(6) +#define CAP_BASECLK_S 8 +#define CAP_MAXBLOCK_M BITFIELD_MASK(2) +#define CAP_MAXBLOCK_S 16 +#define CAP_ADMA2_M BITFIELD_MASK(1) +#define CAP_ADMA2_S 19 +#define CAP_ADMA1_M BITFIELD_MASK(1) +#define CAP_ADMA1_S 20 +#define CAP_HIGHSPEED_M BITFIELD_MASK(1) +#define CAP_HIGHSPEED_S 21 +#define CAP_DMA_M BITFIELD_MASK(1) +#define CAP_DMA_S 22 +#define CAP_SUSPEND_M BITFIELD_MASK(1) +#define CAP_SUSPEND_S 23 +#define CAP_VOLT_3_3_M BITFIELD_MASK(1) +#define CAP_VOLT_3_3_S 24 +#define CAP_VOLT_3_0_M BITFIELD_MASK(1) +#define CAP_VOLT_3_0_S 25 +#define CAP_VOLT_1_8_M BITFIELD_MASK(1) +#define CAP_VOLT_1_8_S 26 +#define CAP_64BIT_HOST_M BITFIELD_MASK(1) +#define CAP_64BIT_HOST_S 28 + +/* SD_MaxCurCap reg (0x048) */ +#define CAP_CURR_3_3_M BITFIELD_MASK(8) +#define CAP_CURR_3_3_S 0 +#define CAP_CURR_3_0_M BITFIELD_MASK(8) +#define CAP_CURR_3_0_S 8 +#define CAP_CURR_1_8_M BITFIELD_MASK(8) +#define CAP_CURR_1_8_S 16 + +/* SD_SysAddr: Offset 0x0000, Size 4 bytes */ + +/* SD_BlockSize: Offset 0x004, Size 2 bytes */ +#define BLKSZ_BLKSZ_M BITFIELD_MASK(12) +#define BLKSZ_BLKSZ_S 0 +#define BLKSZ_BNDRY_M BITFIELD_MASK(3) +#define BLKSZ_BNDRY_S 12 + +/* SD_BlockCount: Offset 0x006, size 2 bytes */ + +/* SD_Arg0: Offset 0x008, size = 4 bytes */ +/* SD_TransferMode Offset 0x00C, size = 2 bytes */ +#define XFER_DMA_ENABLE_M BITFIELD_MASK(1) +#define XFER_DMA_ENABLE_S 0 +#define XFER_BLK_COUNT_EN_M BITFIELD_MASK(1) +#define XFER_BLK_COUNT_EN_S 1 +#define XFER_CMD_12_EN_M BITFIELD_MASK(1) +#define XFER_CMD_12_EN_S 2 +#define XFER_DATA_DIRECTION_M BITFIELD_MASK(1) +#define XFER_DATA_DIRECTION_S 4 +#define XFER_MULTI_BLOCK_M BITFIELD_MASK(1) +#define XFER_MULTI_BLOCK_S 5 + +/* SD_Command: Offset 0x00E, size = 2 bytes */ +/* resp_type field */ +#define RESP_TYPE_NONE 0 +#define RESP_TYPE_136 1 +#define RESP_TYPE_48 2 +#define RESP_TYPE_48_BUSY 3 +/* type field */ +#define CMD_TYPE_NORMAL 0 +#define CMD_TYPE_SUSPEND 1 +#define CMD_TYPE_RESUME 2 +#define CMD_TYPE_ABORT 3 + +#define CMD_RESP_TYPE_M BITFIELD_MASK(2) /* Bits [0-1] - Response type */ +#define CMD_RESP_TYPE_S 0 +#define CMD_CRC_EN_M BITFIELD_MASK(1) /* Bit 3 - CRC enable */ +#define CMD_CRC_EN_S 3 +#define CMD_INDEX_EN_M BITFIELD_MASK(1) /* Bit 4 - Enable index checking */ +#define CMD_INDEX_EN_S 4 +#define CMD_DATA_EN_M BITFIELD_MASK(1) /* Bit 5 - Using DAT line */ +#define CMD_DATA_EN_S 5 +#define CMD_TYPE_M BITFIELD_MASK(2) /* Bit [6-7] - Normal, abort, resume, etc + */ +#define CMD_TYPE_S 6 +#define CMD_INDEX_M BITFIELD_MASK(6) /* Bits [8-13] - Command number */ +#define CMD_INDEX_S 8 + +/* SD_BufferDataPort0 : Offset 0x020, size = 2 or 4 bytes */ +/* SD_BufferDataPort1 : Offset 0x022, size = 2 bytes */ +/* SD_PresentState : Offset 0x024, size = 4 bytes */ +#define PRES_CMD_INHIBIT_M BITFIELD_MASK(1) /* Bit 0 May use CMD */ +#define PRES_CMD_INHIBIT_S 0 +#define PRES_DAT_INHIBIT_M BITFIELD_MASK(1) /* Bit 1 May use DAT */ +#define PRES_DAT_INHIBIT_S 1 +#define PRES_DAT_BUSY_M BITFIELD_MASK(1) /* Bit 2 DAT is busy */ +#define PRES_DAT_BUSY_S 2 +#define PRES_PRESENT_RSVD_M BITFIELD_MASK(5) /* Bit [3-7] rsvd */ +#define PRES_PRESENT_RSVD_S 3 +#define PRES_WRITE_ACTIVE_M BITFIELD_MASK(1) /* Bit 8 Write is active */ +#define PRES_WRITE_ACTIVE_S 8 +#define PRES_READ_ACTIVE_M BITFIELD_MASK(1) /* Bit 9 Read is active */ +#define PRES_READ_ACTIVE_S 9 +#define PRES_WRITE_DATA_RDY_M BITFIELD_MASK(1) /* Bit 10 Write buf is avail */ +#define PRES_WRITE_DATA_RDY_S 10 +#define PRES_READ_DATA_RDY_M BITFIELD_MASK(1) /* Bit 11 Read buf data avail */ +#define PRES_READ_DATA_RDY_S 11 +#define PRES_CARD_PRESENT_M BITFIELD_MASK(1) /* Bit 16 Card present - debounced */ +#define PRES_CARD_PRESENT_S 16 +#define PRES_CARD_STABLE_M BITFIELD_MASK(1) /* Bit 17 Debugging */ +#define PRES_CARD_STABLE_S 17 +#define PRES_CARD_PRESENT_RAW_M BITFIELD_MASK(1) /* Bit 18 Not debounced */ +#define PRES_CARD_PRESENT_RAW_S 18 +#define PRES_WRITE_ENABLED_M BITFIELD_MASK(1) /* Bit 19 Write protected? */ +#define PRES_WRITE_ENABLED_S 19 +#define PRES_DAT_SIGNAL_M BITFIELD_MASK(4) /* Bit [20-23] Debugging */ +#define PRES_DAT_SIGNAL_S 20 +#define PRES_CMD_SIGNAL_M BITFIELD_MASK(1) /* Bit 24 Debugging */ +#define PRES_CMD_SIGNAL_S 24 + +/* SD_HostCntrl: Offset 0x028, size = 1 bytes */ +#define HOST_LED_M BITFIELD_MASK(1) /* Bit 0 LED On/Off */ +#define HOST_LED_S 0 +#define HOST_DATA_WIDTH_M BITFIELD_MASK(1) /* Bit 1 4 bit enable */ +#define HOST_DATA_WIDTH_S 1 +#define HOST_HI_SPEED_EN_M BITFIELD_MASK(1) /* Bit 2 High speed vs low speed */ +#define HOST_DMA_SEL_S 3 +#define HOST_DMA_SEL_M BITFIELD_MASK(2) /* Bit 4:3 DMA Select */ +#define HOST_HI_SPEED_EN_S 2 + +/* misc defines */ +#define SD1_MODE 0x1 /* SD Host Cntrlr Spec */ +#define SD4_MODE 0x2 /* SD Host Cntrlr Spec */ + +/* SD_PwrCntrl: Offset 0x029, size = 1 bytes */ +#define PWR_BUS_EN_M BITFIELD_MASK(1) /* Bit 0 Power the bus */ +#define PWR_BUS_EN_S 0 +#define PWR_VOLTS_M BITFIELD_MASK(3) /* Bit [1-3] Voltage Select */ +#define PWR_VOLTS_S 1 + +/* SD_SoftwareReset: Offset 0x02F, size = 1 byte */ +#define SW_RESET_ALL_M BITFIELD_MASK(1) /* Bit 0 Reset All */ +#define SW_RESET_ALL_S 0 +#define SW_RESET_CMD_M BITFIELD_MASK(1) /* Bit 1 CMD Line Reset */ +#define SW_RESET_CMD_S 1 +#define SW_RESET_DAT_M BITFIELD_MASK(1) /* Bit 2 DAT Line Reset */ +#define SW_RESET_DAT_S 2 + +/* SD_IntrStatus: Offset 0x030, size = 2 bytes */ +/* Defs also serve SD_IntrStatusEnable and SD_IntrSignalEnable */ +#define INTSTAT_CMD_COMPLETE_M BITFIELD_MASK(1) /* Bit 0 */ +#define INTSTAT_CMD_COMPLETE_S 0 +#define INTSTAT_XFER_COMPLETE_M BITFIELD_MASK(1) +#define INTSTAT_XFER_COMPLETE_S 1 +#define INTSTAT_BLOCK_GAP_EVENT_M BITFIELD_MASK(1) +#define INTSTAT_BLOCK_GAP_EVENT_S 2 +#define INTSTAT_DMA_INT_M BITFIELD_MASK(1) +#define INTSTAT_DMA_INT_S 3 +#define INTSTAT_BUF_WRITE_READY_M BITFIELD_MASK(1) +#define INTSTAT_BUF_WRITE_READY_S 4 +#define INTSTAT_BUF_READ_READY_M BITFIELD_MASK(1) +#define INTSTAT_BUF_READ_READY_S 5 +#define INTSTAT_CARD_INSERTION_M BITFIELD_MASK(1) +#define INTSTAT_CARD_INSERTION_S 6 +#define INTSTAT_CARD_REMOVAL_M BITFIELD_MASK(1) +#define INTSTAT_CARD_REMOVAL_S 7 +#define INTSTAT_CARD_INT_M BITFIELD_MASK(1) +#define INTSTAT_CARD_INT_S 8 +#define INTSTAT_ERROR_INT_M BITFIELD_MASK(1) /* Bit 15 */ +#define INTSTAT_ERROR_INT_S 15 + +/* SD_ErrorIntrStatus: Offset 0x032, size = 2 bytes */ +/* Defs also serve SD_ErrorIntrStatusEnable and SD_ErrorIntrSignalEnable */ +#define ERRINT_CMD_TIMEOUT_M BITFIELD_MASK(1) +#define ERRINT_CMD_TIMEOUT_S 0 +#define ERRINT_CMD_CRC_M BITFIELD_MASK(1) +#define ERRINT_CMD_CRC_S 1 +#define ERRINT_CMD_ENDBIT_M BITFIELD_MASK(1) +#define ERRINT_CMD_ENDBIT_S 2 +#define ERRINT_CMD_INDEX_M BITFIELD_MASK(1) +#define ERRINT_CMD_INDEX_S 3 +#define ERRINT_DATA_TIMEOUT_M BITFIELD_MASK(1) +#define ERRINT_DATA_TIMEOUT_S 4 +#define ERRINT_DATA_CRC_M BITFIELD_MASK(1) +#define ERRINT_DATA_CRC_S 5 +#define ERRINT_DATA_ENDBIT_M BITFIELD_MASK(1) +#define ERRINT_DATA_ENDBIT_S 6 +#define ERRINT_CURRENT_LIMIT_M BITFIELD_MASK(1) +#define ERRINT_CURRENT_LIMIT_S 7 +#define ERRINT_AUTO_CMD12_M BITFIELD_MASK(1) +#define ERRINT_AUTO_CMD12_S 8 +#define ERRINT_VENDOR_M BITFIELD_MASK(4) +#define ERRINT_VENDOR_S 12 + +/* Also provide definitions in "normal" form to allow combined masks */ +#define ERRINT_CMD_TIMEOUT_BIT 0x0001 +#define ERRINT_CMD_CRC_BIT 0x0002 +#define ERRINT_CMD_ENDBIT_BIT 0x0004 +#define ERRINT_CMD_INDEX_BIT 0x0008 +#define ERRINT_DATA_TIMEOUT_BIT 0x0010 +#define ERRINT_DATA_CRC_BIT 0x0020 +#define ERRINT_DATA_ENDBIT_BIT 0x0040 +#define ERRINT_CURRENT_LIMIT_BIT 0x0080 +#define ERRINT_AUTO_CMD12_BIT 0x0100 + +/* Masks to select CMD vs. DATA errors */ +#define ERRINT_CMD_ERRS (ERRINT_CMD_TIMEOUT_BIT | ERRINT_CMD_CRC_BIT |\ + ERRINT_CMD_ENDBIT_BIT | ERRINT_CMD_INDEX_BIT) +#define ERRINT_DATA_ERRS (ERRINT_DATA_TIMEOUT_BIT | ERRINT_DATA_CRC_BIT |\ + ERRINT_DATA_ENDBIT_BIT) +#define ERRINT_TRANSFER_ERRS (ERRINT_CMD_ERRS | ERRINT_DATA_ERRS) + +/* SD_WakeupCntr_BlockGapCntrl : Offset 0x02A , size = bytes */ +/* SD_ClockCntrl : Offset 0x02C , size = bytes */ +/* SD_SoftwareReset_TimeoutCntrl : Offset 0x02E , size = bytes */ +/* SD_IntrStatus : Offset 0x030 , size = bytes */ +/* SD_ErrorIntrStatus : Offset 0x032 , size = bytes */ +/* SD_IntrStatusEnable : Offset 0x034 , size = bytes */ +/* SD_ErrorIntrStatusEnable : Offset 0x036 , size = bytes */ +/* SD_IntrSignalEnable : Offset 0x038 , size = bytes */ +/* SD_ErrorIntrSignalEnable : Offset 0x03A , size = bytes */ +/* SD_CMD12ErrorStatus : Offset 0x03C , size = bytes */ +/* SD_Capabilities : Offset 0x040 , size = bytes */ +/* SD_MaxCurCap : Offset 0x048 , size = bytes */ +/* SD_MaxCurCap_Reserved: Offset 0x04C , size = bytes */ +/* SD_SlotInterruptStatus: Offset 0x0FC , size = bytes */ +/* SD_HostControllerVersion : Offset 0x0FE , size = bytes */ + +#endif /* _SDIOH_H */ diff --git a/drivers/net/wireless/bcm4329/include/sdiovar.h b/drivers/net/wireless/bcm4329/include/sdiovar.h new file mode 100644 index 0000000000000000000000000000000000000000..0179d4cb96db68e67ea4e2ed222b32bdbb6325eb --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/sdiovar.h @@ -0,0 +1,58 @@ +/* + * Structure used by apps whose drivers access SDIO drivers. + * Pulled out separately so dhdu and wlu can both use it. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sdiovar.h,v 13.5.14.2.16.2 2009/12/08 22:34:21 Exp $ + */ + +#ifndef _sdiovar_h_ +#define _sdiovar_h_ + +#include + +/* require default structure packing */ +#define BWL_DEFAULT_PACKING +#include + +typedef struct sdreg { + int func; + int offset; + int value; +} sdreg_t; + +/* Common msglevel constants */ +#define SDH_ERROR_VAL 0x0001 /* Error */ +#define SDH_TRACE_VAL 0x0002 /* Trace */ +#define SDH_INFO_VAL 0x0004 /* Info */ +#define SDH_DEBUG_VAL 0x0008 /* Debug */ +#define SDH_DATA_VAL 0x0010 /* Data */ +#define SDH_CTRL_VAL 0x0020 /* Control Regs */ +#define SDH_LOG_VAL 0x0040 /* Enable bcmlog */ +#define SDH_DMA_VAL 0x0080 /* DMA */ + +#define NUM_PREV_TRANSACTIONS 16 + + +#include + +#endif /* _sdiovar_h_ */ diff --git a/drivers/net/wireless/bcm4329/include/siutils.h b/drivers/net/wireless/bcm4329/include/siutils.h new file mode 100644 index 0000000000000000000000000000000000000000..cb9f1407b73beb37c43a599ee6e043023deb7b5e --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/siutils.h @@ -0,0 +1,235 @@ +/* + * Misc utility routines for accessing the SOC Interconnects + * of Broadcom HNBU chips. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: siutils.h,v 13.197.4.2.4.3.8.16 2010/06/23 21:36:05 Exp $ + */ + + +#ifndef _siutils_h_ +#define _siutils_h_ + + +struct si_pub { + uint socitype; + + uint bustype; + uint buscoretype; + uint buscorerev; + uint buscoreidx; + int ccrev; + uint32 cccaps; + int pmurev; + uint32 pmucaps; + uint boardtype; + uint boardvendor; + uint boardflags; + uint chip; + uint chiprev; + uint chippkg; + uint32 chipst; + bool issim; + uint socirev; + bool pci_pr32414; +}; + +#if defined(WLC_HIGH) && !defined(WLC_LOW) +typedef struct si_pub si_t; +#else +typedef const struct si_pub si_t; +#endif + + +#define SI_OSH NULL + + +#define XTAL 0x1 +#define PLL 0x2 + + +#define CLK_FAST 0 +#define CLK_DYNAMIC 2 + + +#define GPIO_DRV_PRIORITY 0 +#define GPIO_APP_PRIORITY 1 +#define GPIO_HI_PRIORITY 2 + + +#define GPIO_PULLUP 0 +#define GPIO_PULLDN 1 + + +#define GPIO_REGEVT 0 +#define GPIO_REGEVT_INTMSK 1 +#define GPIO_REGEVT_INTPOL 2 + + +#define SI_DEVPATH_BUFSZ 16 + + +#define SI_DOATTACH 1 +#define SI_PCIDOWN 2 +#define SI_PCIUP 3 + +#define ISSIM_ENAB(sih) 0 + + +#if defined(BCMPMUCTL) +#define PMUCTL_ENAB(sih) (BCMPMUCTL) +#else +#define PMUCTL_ENAB(sih) ((sih)->cccaps & CC_CAP_PMU) +#endif + + +#if defined(BCMPMUCTL) && BCMPMUCTL +#define CCCTL_ENAB(sih) (0) +#define CCPLL_ENAB(sih) (0) +#else +#define CCCTL_ENAB(sih) ((sih)->cccaps & CC_CAP_PWR_CTL) +#define CCPLL_ENAB(sih) ((sih)->cccaps & CC_CAP_PLL_MASK) +#endif + +typedef void (*gpio_handler_t)(uint32 stat, void *arg); + + + +extern si_t *si_attach(uint pcidev, osl_t *osh, void *regs, uint bustype, + void *sdh, char **vars, uint *varsz); +extern si_t *si_kattach(osl_t *osh); +extern void si_detach(si_t *sih); +extern bool si_pci_war16165(si_t *sih); + +extern uint si_corelist(si_t *sih, uint coreid[]); +extern uint si_coreid(si_t *sih); +extern uint si_flag(si_t *sih); +extern uint si_intflag(si_t *sih); +extern uint si_coreidx(si_t *sih); +extern uint si_coreunit(si_t *sih); +extern uint si_corevendor(si_t *sih); +extern uint si_corerev(si_t *sih); +extern void *si_osh(si_t *sih); +extern void si_setosh(si_t *sih, osl_t *osh); +extern uint si_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val); +extern void *si_coreregs(si_t *sih); +extern void si_write_wrapperreg(si_t *sih, uint32 offset, uint32 val); +extern uint32 si_core_cflags(si_t *sih, uint32 mask, uint32 val); +extern void si_core_cflags_wo(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_core_sflags(si_t *sih, uint32 mask, uint32 val); +extern bool si_iscoreup(si_t *sih); +extern uint si_findcoreidx(si_t *sih, uint coreid, uint coreunit); +extern void *si_setcoreidx(si_t *sih, uint coreidx); +extern void *si_setcore(si_t *sih, uint coreid, uint coreunit); +extern void *si_switch_core(si_t *sih, uint coreid, uint *origidx, uint *intr_val); +extern void si_restore_core(si_t *sih, uint coreid, uint intr_val); +extern int si_numaddrspaces(si_t *sih); +extern uint32 si_addrspace(si_t *sih, uint asidx); +extern uint32 si_addrspacesize(si_t *sih, uint asidx); +extern int si_corebist(si_t *sih); +extern void si_core_reset(si_t *sih, uint32 bits, uint32 resetbits); +extern void si_core_tofixup(si_t *sih); +extern void si_core_disable(si_t *sih, uint32 bits); +extern uint32 si_clock_rate(uint32 pll_type, uint32 n, uint32 m); +extern uint32 si_clock(si_t *sih); +extern void si_clock_pmu_spuravoid(si_t *sih, bool spuravoid); +extern uint32 si_alp_clock(si_t *sih); +extern uint32 si_ilp_clock(si_t *sih); +extern void si_pci_setup(si_t *sih, uint coremask); +extern void si_pcmcia_init(si_t *sih); +extern void si_setint(si_t *sih, int siflag); +extern bool si_backplane64(si_t *sih); +extern void si_register_intr_callback(si_t *sih, void *intrsoff_fn, void *intrsrestore_fn, + void *intrsenabled_fn, void *intr_arg); +extern void si_deregister_intr_callback(si_t *sih); +extern void si_clkctl_init(si_t *sih); +extern uint16 si_clkctl_fast_pwrup_delay(si_t *sih); +extern bool si_clkctl_cc(si_t *sih, uint mode); +extern int si_clkctl_xtal(si_t *sih, uint what, bool on); +extern uint32 si_gpiotimerval(si_t *sih, uint32 mask, uint32 val); +extern bool si_backplane64(si_t *sih); +extern void si_btcgpiowar(si_t *sih); +extern bool si_deviceremoved(si_t *sih); +extern uint32 si_socram_size(si_t *sih); + +extern void si_watchdog(si_t *sih, uint ticks); +extern void si_watchdog_ms(si_t *sih, uint32 ms); +extern void *si_gpiosetcore(si_t *sih); +extern uint32 si_gpiocontrol(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioouten(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioout(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioin(si_t *sih); +extern uint32 si_gpiointpolarity(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpiointmask(si_t *sih, uint32 mask, uint32 val, uint8 priority); +extern uint32 si_gpioled(si_t *sih, uint32 mask, uint32 val); +extern uint32 si_gpioreserve(si_t *sih, uint32 gpio_num, uint8 priority); +extern uint32 si_gpiorelease(si_t *sih, uint32 gpio_num, uint8 priority); +extern uint32 si_gpiopull(si_t *sih, bool updown, uint32 mask, uint32 val); +extern uint32 si_gpioevent(si_t *sih, uint regtype, uint32 mask, uint32 val); +extern uint32 si_gpio_int_enable(si_t *sih, bool enable); + + +extern void *si_gpio_handler_register(si_t *sih, uint32 e, bool lev, gpio_handler_t cb, void *arg); +extern void si_gpio_handler_unregister(si_t *sih, void* gpioh); +extern void si_gpio_handler_process(si_t *sih); + + +extern bool si_pci_pmecap(si_t *sih); +struct osl_info; +extern bool si_pci_fastpmecap(struct osl_info *osh); +extern bool si_pci_pmeclr(si_t *sih); +extern void si_pci_pmeen(si_t *sih); +extern uint si_pcie_readreg(void *sih, uint addrtype, uint offset); + +extern void si_sdio_init(si_t *sih); + +extern uint16 si_d11_devid(si_t *sih); +extern int si_corepciid(si_t *sih, uint func, uint16 *pcivendor, uint16 *pcidevice, + uint8 *pciclass, uint8 *pcisubclass, uint8 *pciprogif, uint8 *pciheader); + +#define si_eci_init(sih) (0) +#define si_eci_notify_bt(sih, type, val, interrupt) (0) + + + +extern int si_devpath(si_t *sih, char *path, int size); + +extern char *si_getdevpathvar(si_t *sih, const char *name); +extern int si_getdevpathintvar(si_t *sih, const char *name); + + +extern uint8 si_pcieclkreq(si_t *sih, uint32 mask, uint32 val); +extern void si_war42780_clkreq(si_t *sih, bool clkreq); +extern void si_pci_sleep(si_t *sih); +extern void si_pci_down(si_t *sih); +extern void si_pci_up(si_t *sih); +extern void si_pcie_war_ovr_disable(si_t *sih); +extern void si_pcie_extendL1timer(si_t *sih, bool extend); +extern int si_pci_fixcfg(si_t *sih); + + + + + + + +#endif diff --git a/drivers/net/wireless/bcm4329/include/spid.h b/drivers/net/wireless/bcm4329/include/spid.h new file mode 100644 index 0000000000000000000000000000000000000000..c740296de9af0a9f24d4569930e2c06819db0694 --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/spid.h @@ -0,0 +1,153 @@ +/* + * SPI device spec header file + * + * 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: spid.h,v 1.7.10.1.16.3 2009/04/09 19:23:14 Exp $ + */ + +#ifndef _SPI_H +#define _SPI_H + +/* + * Brcm SPI Device Register Map. + * + */ + +typedef volatile struct { + uint8 config; /* 0x00, len, endian, clock, speed, polarity, wakeup */ + uint8 response_delay; /* 0x01, read response delay in bytes (corerev < 3) */ + uint8 status_enable; /* 0x02, status-enable, intr with status, response_delay + * function selection, command/data error check + */ + uint8 reset_bp; /* 0x03, reset on wlan/bt backplane reset (corerev >= 1) */ + uint16 intr_reg; /* 0x04, Intr status register */ + uint16 intr_en_reg; /* 0x06, Intr mask register */ + uint32 status_reg; /* 0x08, RO, Status bits of last spi transfer */ + uint16 f1_info_reg; /* 0x0c, RO, enabled, ready for data transfer, blocksize */ + uint16 f2_info_reg; /* 0x0e, RO, enabled, ready for data transfer, blocksize */ + uint16 f3_info_reg; /* 0x10, RO, enabled, ready for data transfer, blocksize */ + uint32 test_read; /* 0x14, RO 0xfeedbead signature */ + uint32 test_rw; /* 0x18, RW */ + uint8 resp_delay_f0; /* 0x1c, read resp delay bytes for F0 (corerev >= 3) */ + uint8 resp_delay_f1; /* 0x1d, read resp delay bytes for F1 (corerev >= 3) */ + uint8 resp_delay_f2; /* 0x1e, read resp delay bytes for F2 (corerev >= 3) */ + uint8 resp_delay_f3; /* 0x1f, read resp delay bytes for F3 (corerev >= 3) */ +} spi_regs_t; + +/* SPI device register offsets */ +#define SPID_CONFIG 0x00 +#define SPID_RESPONSE_DELAY 0x01 +#define SPID_STATUS_ENABLE 0x02 +#define SPID_RESET_BP 0x03 /* (corerev >= 1) */ +#define SPID_INTR_REG 0x04 /* 16 bits - Interrupt status */ +#define SPID_INTR_EN_REG 0x06 /* 16 bits - Interrupt mask */ +#define SPID_STATUS_REG 0x08 /* 32 bits */ +#define SPID_F1_INFO_REG 0x0C /* 16 bits */ +#define SPID_F2_INFO_REG 0x0E /* 16 bits */ +#define SPID_F3_INFO_REG 0x10 /* 16 bits */ +#define SPID_TEST_READ 0x14 /* 32 bits */ +#define SPID_TEST_RW 0x18 /* 32 bits */ +#define SPID_RESP_DELAY_F0 0x1c /* 8 bits (corerev >= 3) */ +#define SPID_RESP_DELAY_F1 0x1d /* 8 bits (corerev >= 3) */ +#define SPID_RESP_DELAY_F2 0x1e /* 8 bits (corerev >= 3) */ +#define SPID_RESP_DELAY_F3 0x1f /* 8 bits (corerev >= 3) */ + +/* Bit masks for SPID_CONFIG device register */ +#define WORD_LENGTH_32 0x1 /* 0/1 16/32 bit word length */ +#define ENDIAN_BIG 0x2 /* 0/1 Little/Big Endian */ +#define CLOCK_PHASE 0x4 /* 0/1 clock phase delay */ +#define CLOCK_POLARITY 0x8 /* 0/1 Idle state clock polarity is low/high */ +#define HIGH_SPEED_MODE 0x10 /* 1/0 High Speed mode / Normal mode */ +#define INTR_POLARITY 0x20 /* 1/0 Interrupt active polarity is high/low */ +#define WAKE_UP 0x80 /* 0/1 Wake-up command from Host to WLAN */ + +/* Bit mask for SPID_RESPONSE_DELAY device register */ +#define RESPONSE_DELAY_MASK 0xFF /* Configurable rd response delay in multiples of 8 bits */ + +/* Bit mask for SPID_STATUS_ENABLE device register */ +#define STATUS_ENABLE 0x1 /* 1/0 Status sent/not sent to host after read/write */ +#define INTR_WITH_STATUS 0x2 /* 0/1 Do-not / do-interrupt if status is sent */ +#define RESP_DELAY_ALL 0x4 /* Applicability of resp delay to F1 or all func's read */ +#define DWORD_PKT_LEN_EN 0x8 /* Packet len denoted in dwords instead of bytes */ +#define CMD_ERR_CHK_EN 0x20 /* Command error check enable */ +#define DATA_ERR_CHK_EN 0x40 /* Data error check enable */ + +/* Bit mask for SPID_RESET_BP device register */ +#define RESET_ON_WLAN_BP_RESET 0x4 /* enable reset for WLAN backplane */ +#define RESET_ON_BT_BP_RESET 0x8 /* enable reset for BT backplane */ +#define RESET_SPI 0x80 /* reset the above enabled logic */ + +/* Bit mask for SPID_INTR_REG device register */ +#define DATA_UNAVAILABLE 0x0001 /* Requested data not available; Clear by writing a "1" */ +#define F2_F3_FIFO_RD_UNDERFLOW 0x0002 +#define F2_F3_FIFO_WR_OVERFLOW 0x0004 +#define COMMAND_ERROR 0x0008 /* Cleared by writing 1 */ +#define DATA_ERROR 0x0010 /* Cleared by writing 1 */ +#define F2_PACKET_AVAILABLE 0x0020 +#define F3_PACKET_AVAILABLE 0x0040 +#define F1_OVERFLOW 0x0080 /* Due to last write. Bkplane has pending write requests */ +#define MISC_INTR0 0x0100 +#define MISC_INTR1 0x0200 +#define MISC_INTR2 0x0400 +#define MISC_INTR3 0x0800 +#define MISC_INTR4 0x1000 +#define F1_INTR 0x2000 +#define F2_INTR 0x4000 +#define F3_INTR 0x8000 + +/* Bit mask for 32bit SPID_STATUS_REG device register */ +#define STATUS_DATA_NOT_AVAILABLE 0x00000001 +#define STATUS_UNDERFLOW 0x00000002 +#define STATUS_OVERFLOW 0x00000004 +#define STATUS_F2_INTR 0x00000008 +#define STATUS_F3_INTR 0x00000010 +#define STATUS_F2_RX_READY 0x00000020 +#define STATUS_F3_RX_READY 0x00000040 +#define STATUS_HOST_CMD_DATA_ERR 0x00000080 +#define STATUS_F2_PKT_AVAILABLE 0x00000100 +#define STATUS_F2_PKT_LEN_MASK 0x000FFE00 +#define STATUS_F2_PKT_LEN_SHIFT 9 +#define STATUS_F3_PKT_AVAILABLE 0x00100000 +#define STATUS_F3_PKT_LEN_MASK 0xFFE00000 +#define STATUS_F3_PKT_LEN_SHIFT 21 + +/* Bit mask for 16 bits SPID_F1_INFO_REG device register */ +#define F1_ENABLED 0x0001 +#define F1_RDY_FOR_DATA_TRANSFER 0x0002 +#define F1_MAX_PKT_SIZE 0x01FC + +/* Bit mask for 16 bits SPID_F2_INFO_REG device register */ +#define F2_ENABLED 0x0001 +#define F2_RDY_FOR_DATA_TRANSFER 0x0002 +#define F2_MAX_PKT_SIZE 0x3FFC + +/* Bit mask for 16 bits SPID_F3_INFO_REG device register */ +#define F3_ENABLED 0x0001 +#define F3_RDY_FOR_DATA_TRANSFER 0x0002 +#define F3_MAX_PKT_SIZE 0x3FFC + +/* Bit mask for 32 bits SPID_TEST_READ device register read in 16bit LE mode */ +#define TEST_RO_DATA_32BIT_LE 0xFEEDBEAD + +/* Maximum number of I/O funcs */ +#define SPI_MAX_IOFUNCS 4 + +#define SPI_MAX_PKT_LEN (2048*4) + +/* Misc defines */ +#define SPI_FUNC_0 0 +#define SPI_FUNC_1 1 +#define SPI_FUNC_2 2 +#define SPI_FUNC_3 3 + +#define WAIT_F2RXFIFORDY 100 +#define WAIT_F2RXFIFORDY_DELAY 20 + +#endif /* _SPI_H */ diff --git a/drivers/net/wireless/bcm4329/include/trxhdr.h b/drivers/net/wireless/bcm4329/include/trxhdr.h new file mode 100644 index 0000000000000000000000000000000000000000..8f5eed9410eb49787bc4b4d8d8ba7104d26a607d --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/trxhdr.h @@ -0,0 +1,46 @@ +/* + * TRX image file header format. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: trxhdr.h,v 13.11.310.1 2008/08/17 12:58:58 Exp $ + */ + +#include + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ +#define TRX_VERSION 1 /* Version 1 */ +#define TRX_MAX_LEN 0x3A0000 /* Max length */ +#define TRX_NO_HEADER 1 /* Do not write TRX header */ +#define TRX_GZ_FILES 0x2 /* Contains up to TRX_MAX_OFFSET individual gzip files */ +#define TRX_MAX_OFFSET 3 /* Max number of individual files */ +#define TRX_UNCOMP_IMAGE 0x20 /* Trx contains uncompressed rtecdc.bin image */ + +struct trx_header { + uint32 magic; /* "HDR0" */ + uint32 len; /* Length of file including header */ + uint32 crc32; /* 32-bit CRC from flag_version to end of file */ + uint32 flag_version; /* 0:15 flags, 16:31 version */ + uint32 offsets[TRX_MAX_OFFSET]; /* Offsets of partitions from start of header */ +}; + +/* Compatibility */ +typedef struct trx_header TRXHDR, *PTRXHDR; diff --git a/drivers/net/wireless/bcm4329/include/typedefs.h b/drivers/net/wireless/bcm4329/include/typedefs.h new file mode 100644 index 0000000000000000000000000000000000000000..4d9dd761ed64aacbb12123180129e6317ebb95ce --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/typedefs.h @@ -0,0 +1,303 @@ +/* + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: typedefs.h,v 1.85.34.1.2.5 2009/01/27 04:09:40 Exp $ + */ + + +#ifndef _TYPEDEFS_H_ +#define _TYPEDEFS_H_ + +#ifdef SITE_TYPEDEFS + + + +#include "site_typedefs.h" + +#else + + + +#ifdef __cplusplus + +#define TYPEDEF_BOOL +#ifndef FALSE +#define FALSE false +#endif +#ifndef TRUE +#define TRUE true +#endif + +#else + + +#endif + +#if defined(__x86_64__) +#define TYPEDEF_UINTPTR +typedef unsigned long long int uintptr; +#endif + + + + +#if defined(TARGETOS_nucleus) + +#include + + +#define TYPEDEF_FLOAT_T +#endif + +#if defined(_NEED_SIZE_T_) +typedef long unsigned int size_t; +#endif + +#ifdef __DJGPP__ +typedef long unsigned int size_t; +#endif + + + + + +#define TYPEDEF_UINT +#ifndef TARGETENV_android +#define TYPEDEF_USHORT +#define TYPEDEF_ULONG +#endif +#ifdef __KERNEL__ +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)) +#define TYPEDEF_BOOL +#endif +#endif + + + + + +#if defined(__GNUC__) && defined(__STRICT_ANSI__) +#define TYPEDEF_INT64 +#define TYPEDEF_UINT64 +#endif + + +#if defined(__ICL) + +#define TYPEDEF_INT64 + +#if defined(__STDC__) +#define TYPEDEF_UINT64 +#endif + +#endif + +#if !defined(__DJGPP__) && !defined(TARGETOS_nucleus) + + +#if defined(__KERNEL__) + +#include + +#else + + +#include + +#endif + +#endif + + + + +#define USE_TYPEDEF_DEFAULTS + +#endif + + + + +#ifdef USE_TYPEDEF_DEFAULTS +#undef USE_TYPEDEF_DEFAULTS + +#ifndef TYPEDEF_BOOL +typedef unsigned char bool; +#endif + + + +#ifndef TYPEDEF_UCHAR +typedef unsigned char uchar; +#endif + +#ifndef TYPEDEF_USHORT +typedef unsigned short ushort; +#endif + +#ifndef TYPEDEF_UINT +typedef unsigned int uint; +#endif + +#ifndef TYPEDEF_ULONG +typedef unsigned long ulong; +#endif + + + +#ifndef TYPEDEF_UINT8 +typedef unsigned char uint8; +#endif + +#ifndef TYPEDEF_UINT16 +typedef unsigned short uint16; +#endif + +#ifndef TYPEDEF_UINT32 +typedef unsigned int uint32; +#endif + +#ifndef TYPEDEF_UINT64 +typedef unsigned long long uint64; +#endif + +#ifndef TYPEDEF_UINTPTR +typedef unsigned int uintptr; +#endif + +#ifndef TYPEDEF_INT8 +typedef signed char int8; +#endif + +#ifndef TYPEDEF_INT16 +typedef signed short int16; +#endif + +#ifndef TYPEDEF_INT32 +typedef signed int int32; +#endif + +#ifndef TYPEDEF_INT64 +typedef signed long long int64; +#endif + + + +#ifndef TYPEDEF_FLOAT32 +typedef float float32; +#endif + +#ifndef TYPEDEF_FLOAT64 +typedef double float64; +#endif + + + +#ifndef TYPEDEF_FLOAT_T + +#if defined(FLOAT32) +typedef float32 float_t; +#else +typedef float64 float_t; +#endif + +#endif + + + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef OFF +#define OFF 0 +#endif + +#ifndef ON +#define ON 1 +#endif + +#define AUTO (-1) + + + +#ifndef PTRSZ +#define PTRSZ sizeof(char*) +#endif + + + +#if defined(__GNUC__) + #define BWL_COMPILER_GNU +#elif defined(__CC_ARM) + #define BWL_COMPILER_ARMCC +#else + #error "Unknown compiler!" +#endif + + +#ifndef INLINE + #if defined(BWL_COMPILER_MICROSOFT) + #define INLINE __inline + #elif defined(BWL_COMPILER_GNU) + #define INLINE __inline__ + #elif defined(BWL_COMPILER_ARMCC) + #define INLINE __inline + #else + #define INLINE + #endif +#endif + +#undef TYPEDEF_BOOL +#undef TYPEDEF_UCHAR +#undef TYPEDEF_USHORT +#undef TYPEDEF_UINT +#undef TYPEDEF_ULONG +#undef TYPEDEF_UINT8 +#undef TYPEDEF_UINT16 +#undef TYPEDEF_UINT32 +#undef TYPEDEF_UINT64 +#undef TYPEDEF_UINTPTR +#undef TYPEDEF_INT8 +#undef TYPEDEF_INT16 +#undef TYPEDEF_INT32 +#undef TYPEDEF_INT64 +#undef TYPEDEF_FLOAT32 +#undef TYPEDEF_FLOAT64 +#undef TYPEDEF_FLOAT_T + +#endif + + +#define UNUSED_PARAMETER(x) (void)(x) + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/include/wlioctl.h b/drivers/net/wireless/bcm4329/include/wlioctl.h new file mode 100644 index 0000000000000000000000000000000000000000..00c61f10782f608acf41d64788e341616b17430d --- /dev/null +++ b/drivers/net/wireless/bcm4329/include/wlioctl.h @@ -0,0 +1,1673 @@ +/* + * Custom OID/ioctl definitions for + * Broadcom 802.11abg Networking Device Driver + * + * Definitions subject to change without notice. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: wlioctl.h,v 1.601.4.15.2.14.2.62.4.3 2011/02/09 23:31:02 Exp $ + */ + + +#ifndef _wlioctl_h_ +#define _wlioctl_h_ + +#include +#include +#include +#include +#include +#include + + + +#define ACTION_FRAME_SIZE 1040 + +typedef struct wl_action_frame { + struct ether_addr da; + uint16 len; + uint32 packetId; + uint8 data[ACTION_FRAME_SIZE]; +} wl_action_frame_t; + +#define WL_WIFI_ACTION_FRAME_SIZE sizeof(struct wl_action_frame) + + +#define BWL_DEFAULT_PACKING +#include + +#define RWL_ACTION_WIFI_CATEGORY 127 +#define RWL_WIFI_OUI_BYTE1 0x90 +#define RWL_WIFI_OUI_BYTE2 0x4C +#define RWL_WIFI_OUI_BYTE3 0x0F +#define RWL_WIFI_ACTION_FRAME_SIZE sizeof(struct dot11_action_wifi_vendor_specific) +#define RWL_WIFI_DEFAULT 0x00 +#define RWL_WIFI_FIND_MY_PEER 0x09 +#define RWL_WIFI_FOUND_PEER 0x0A +#define RWL_ACTION_WIFI_FRAG_TYPE 0x55 + +typedef struct ssid_info +{ + uint8 ssid_len; + uint8 ssid[32]; +} ssid_info_t; + +typedef struct cnt_rx +{ + uint32 cnt_rxundec; + uint32 cnt_rxframe; +} cnt_rx_t; + + + +#define RWL_REF_MAC_ADDRESS_OFFSET 17 +#define RWL_DUT_MAC_ADDRESS_OFFSET 23 +#define RWL_WIFI_CLIENT_CHANNEL_OFFSET 50 +#define RWL_WIFI_SERVER_CHANNEL_OFFSET 51 + + + + + +#define WL_BSS_INFO_VERSION 108 + + +typedef struct wl_bss_info { + uint32 version; + uint32 length; + struct ether_addr BSSID; + uint16 beacon_period; + uint16 capability; + uint8 SSID_len; + uint8 SSID[32]; + struct { + uint count; + uint8 rates[16]; + } rateset; + chanspec_t chanspec; + uint16 atim_window; + uint8 dtim_period; + int16 RSSI; + int8 phy_noise; + + uint8 n_cap; + uint32 nbss_cap; + uint8 ctl_ch; + uint32 reserved32[1]; + uint8 flags; + uint8 reserved[3]; + uint8 basic_mcs[MCSSET_LEN]; + + uint16 ie_offset; + uint32 ie_length; + + +} wl_bss_info_t; + +typedef struct wlc_ssid { + uint32 SSID_len; + uchar SSID[32]; +} wlc_ssid_t; + + +#define WL_BSSTYPE_INFRA 1 +#define WL_BSSTYPE_INDEP 0 +#define WL_BSSTYPE_ANY 2 + + +#define WL_SCANFLAGS_PASSIVE 0x01 +#define WL_SCANFLAGS_PROHIBITED 0x04 + +typedef struct wl_scan_params { + wlc_ssid_t ssid; + struct ether_addr bssid; + int8 bss_type; + int8 scan_type; + int32 nprobes; + int32 active_time; + int32 passive_time; + int32 home_time; + int32 channel_num; + uint16 channel_list[1]; +} wl_scan_params_t; + +#define WL_SCAN_PARAMS_FIXED_SIZE 64 + + +#define WL_SCAN_PARAMS_COUNT_MASK 0x0000ffff +#define WL_SCAN_PARAMS_NSSID_SHIFT 16 + +#define WL_SCAN_ACTION_START 1 +#define WL_SCAN_ACTION_CONTINUE 2 +#define WL_SCAN_ACTION_ABORT 3 + +#define ISCAN_REQ_VERSION 1 + + +typedef struct wl_iscan_params { + uint32 version; + uint16 action; + uint16 scan_duration; + wl_scan_params_t params; +} wl_iscan_params_t; + +#define WL_ISCAN_PARAMS_FIXED_SIZE (OFFSETOF(wl_iscan_params_t, params) + sizeof(wlc_ssid_t)) + +typedef struct wl_scan_results { + uint32 buflen; + uint32 version; + uint32 count; + wl_bss_info_t bss_info[1]; +} wl_scan_results_t; + +#define WL_SCAN_RESULTS_FIXED_SIZE 12 + + +#define WL_SCAN_RESULTS_SUCCESS 0 +#define WL_SCAN_RESULTS_PARTIAL 1 +#define WL_SCAN_RESULTS_PENDING 2 +#define WL_SCAN_RESULTS_ABORTED 3 +#define WL_SCAN_RESULTS_NO_MEM 4 + +#define ESCAN_REQ_VERSION 1 + +typedef struct wl_escan_params { + uint32 version; + uint16 action; + uint16 sync_id; + wl_scan_params_t params; +} wl_escan_params_t; + +#define WL_ESCAN_PARAMS_FIXED_SIZE (OFFSETOF(wl_escan_params_t, params) + sizeof(wlc_ssid_t)) + +typedef struct wl_escan_result { + uint32 buflen; + uint32 version; + uint16 sync_id; + uint16 bss_count; + wl_bss_info_t bss_info[1]; +} wl_escan_result_t; + +#define WL_ESCAN_RESULTS_FIXED_SIZE (sizeof(wl_escan_result_t) - sizeof(wl_bss_info_t)) + + +typedef struct wl_iscan_results { + uint32 status; + wl_scan_results_t results; +} wl_iscan_results_t; + +#define WL_ISCAN_RESULTS_FIXED_SIZE \ + (WL_SCAN_RESULTS_FIXED_SIZE + OFFSETOF(wl_iscan_results_t, results)) + +#define WL_NUMRATES 16 +typedef struct wl_rateset { + uint32 count; + uint8 rates[WL_NUMRATES]; +} wl_rateset_t; + + +typedef struct wl_uint32_list { + + uint32 count; + + uint32 element[1]; +} wl_uint32_list_t; + + +typedef struct wl_assoc_params { + struct ether_addr bssid; + uint16 bssid_cnt; + int32 chanspec_num; + chanspec_t chanspec_list[1]; +} wl_assoc_params_t; +#define WL_ASSOC_PARAMS_FIXED_SIZE (sizeof(wl_assoc_params_t) - sizeof(chanspec_t)) + + +typedef wl_assoc_params_t wl_reassoc_params_t; +#define WL_REASSOC_PARAMS_FIXED_SIZE WL_ASSOC_PARAMS_FIXED_SIZE + + +typedef struct wl_join_params { + wlc_ssid_t ssid; + wl_assoc_params_t params; +} wl_join_params_t; +#define WL_JOIN_PARAMS_FIXED_SIZE (sizeof(wl_join_params_t) - sizeof(chanspec_t)) + +#define WLC_CNTRY_BUF_SZ 4 + +typedef struct wl_country { + char country_abbrev[WLC_CNTRY_BUF_SZ]; + int32 rev; + char ccode[WLC_CNTRY_BUF_SZ]; +} wl_country_t; + +typedef enum sup_auth_status { + + WLC_SUP_DISCONNECTED = 0, + WLC_SUP_CONNECTING, + WLC_SUP_IDREQUIRED, + WLC_SUP_AUTHENTICATING, + WLC_SUP_AUTHENTICATED, + WLC_SUP_KEYXCHANGE, + WLC_SUP_KEYED, + WLC_SUP_TIMEOUT, + WLC_SUP_LAST_BASIC_STATE, + + + WLC_SUP_KEYXCHANGE_WAIT_M1 = WLC_SUP_AUTHENTICATED, + + WLC_SUP_KEYXCHANGE_PREP_M2 = WLC_SUP_KEYXCHANGE, + + WLC_SUP_KEYXCHANGE_WAIT_M3 = WLC_SUP_LAST_BASIC_STATE, + + WLC_SUP_KEYXCHANGE_PREP_M4, + WLC_SUP_KEYXCHANGE_WAIT_G1, + WLC_SUP_KEYXCHANGE_PREP_G2 +} sup_auth_status_t; + + +#define CRYPTO_ALGO_OFF 0 +#define CRYPTO_ALGO_WEP1 1 +#define CRYPTO_ALGO_TKIP 2 +#define CRYPTO_ALGO_WEP128 3 +#define CRYPTO_ALGO_AES_CCM 4 +#define CRYPTO_ALGO_AES_OCB_MSDU 5 +#define CRYPTO_ALGO_AES_OCB_MPDU 6 +#define CRYPTO_ALGO_NALG 7 +#define CRYPTO_ALGO_SMS4 11 + +#define WSEC_GEN_MIC_ERROR 0x0001 +#define WSEC_GEN_REPLAY 0x0002 +#define WSEC_GEN_ICV_ERROR 0x0004 + +#define WL_SOFT_KEY (1 << 0) +#define WL_PRIMARY_KEY (1 << 1) +#define WL_KF_RES_4 (1 << 4) +#define WL_KF_RES_5 (1 << 5) +#define WL_IBSS_PEER_GROUP_KEY (1 << 6) + +typedef struct wl_wsec_key { + uint32 index; + uint32 len; + uint8 data[DOT11_MAX_KEY_SIZE]; + uint32 pad_1[18]; + uint32 algo; + uint32 flags; + uint32 pad_2[2]; + int pad_3; + int iv_initialized; + int pad_4; + + struct { + uint32 hi; + uint16 lo; + } rxiv; + uint32 pad_5[2]; + struct ether_addr ea; +} wl_wsec_key_t; + +#define WSEC_MIN_PSK_LEN 8 +#define WSEC_MAX_PSK_LEN 64 + + +#define WSEC_PASSPHRASE (1<<0) + + +typedef struct { + ushort key_len; + ushort flags; + uint8 key[WSEC_MAX_PSK_LEN]; +} wsec_pmk_t; + + +#define WEP_ENABLED 0x0001 +#define TKIP_ENABLED 0x0002 +#define AES_ENABLED 0x0004 +#define WSEC_SWFLAG 0x0008 +#define SES_OW_ENABLED 0x0040 +#define SMS4_ENABLED 0x0100 + + +#define WPA_AUTH_DISABLED 0x0000 +#define WPA_AUTH_NONE 0x0001 +#define WPA_AUTH_UNSPECIFIED 0x0002 +#define WPA_AUTH_PSK 0x0004 + +#define WPA2_AUTH_UNSPECIFIED 0x0040 +#define WPA2_AUTH_PSK 0x0080 +#define BRCM_AUTH_PSK 0x0100 +#define BRCM_AUTH_DPT 0x0200 +#define WPA_AUTH_WAPI 0x0400 + +#define WPA_AUTH_PFN_ANY 0xffffffff + + +#define MAXPMKID 16 + +typedef struct _pmkid { + struct ether_addr BSSID; + uint8 PMKID[WPA2_PMKID_LEN]; +} pmkid_t; + +typedef struct _pmkid_list { + uint32 npmkid; + pmkid_t pmkid[1]; +} pmkid_list_t; + +typedef struct _pmkid_cand { + struct ether_addr BSSID; + uint8 preauth; +} pmkid_cand_t; + +typedef struct _pmkid_cand_list { + uint32 npmkid_cand; + pmkid_cand_t pmkid_cand[1]; +} pmkid_cand_list_t; + + + + +typedef struct { + uint32 val; + struct ether_addr ea; +} scb_val_t; + + + +typedef struct channel_info { + int hw_channel; + int target_channel; + int scan_channel; +} channel_info_t; + + +struct maclist { + uint count; + struct ether_addr ea[1]; +}; + + +typedef struct get_pktcnt { + uint rx_good_pkt; + uint rx_bad_pkt; + uint tx_good_pkt; + uint tx_bad_pkt; + uint rx_ocast_good_pkt; +} get_pktcnt_t; + + +typedef struct wl_ioctl { + uint cmd; + void *buf; + uint len; + uint8 set; + uint used; + uint needed; +} wl_ioctl_t; + + + +#define WLC_IOCTL_MAGIC 0x14e46c77 + + +#define WLC_IOCTL_VERSION 1 + +#define WLC_IOCTL_MAXLEN 8192 +#define WLC_IOCTL_SMLEN 256 +#define WLC_IOCTL_MEDLEN 1536 + + + +#define WLC_GET_MAGIC 0 +#define WLC_GET_VERSION 1 +#define WLC_UP 2 +#define WLC_DOWN 3 +#define WLC_GET_LOOP 4 +#define WLC_SET_LOOP 5 +#define WLC_DUMP 6 +#define WLC_GET_MSGLEVEL 7 +#define WLC_SET_MSGLEVEL 8 +#define WLC_GET_PROMISC 9 +#define WLC_SET_PROMISC 10 + +#define WLC_GET_RATE 12 + +#define WLC_GET_INSTANCE 14 + + + + +#define WLC_GET_INFRA 19 +#define WLC_SET_INFRA 20 +#define WLC_GET_AUTH 21 +#define WLC_SET_AUTH 22 +#define WLC_GET_BSSID 23 +#define WLC_SET_BSSID 24 +#define WLC_GET_SSID 25 +#define WLC_SET_SSID 26 +#define WLC_RESTART 27 + +#define WLC_GET_CHANNEL 29 +#define WLC_SET_CHANNEL 30 +#define WLC_GET_SRL 31 +#define WLC_SET_SRL 32 +#define WLC_GET_LRL 33 +#define WLC_SET_LRL 34 +#define WLC_GET_PLCPHDR 35 +#define WLC_SET_PLCPHDR 36 +#define WLC_GET_RADIO 37 +#define WLC_SET_RADIO 38 +#define WLC_GET_PHYTYPE 39 +#define WLC_DUMP_RATE 40 +#define WLC_SET_RATE_PARAMS 41 + + +#define WLC_GET_KEY 44 +#define WLC_SET_KEY 45 +#define WLC_GET_REGULATORY 46 +#define WLC_SET_REGULATORY 47 +#define WLC_GET_PASSIVE_SCAN 48 +#define WLC_SET_PASSIVE_SCAN 49 +#define WLC_SCAN 50 +#define WLC_SCAN_RESULTS 51 +#define WLC_DISASSOC 52 +#define WLC_REASSOC 53 +#define WLC_GET_ROAM_TRIGGER 54 +#define WLC_SET_ROAM_TRIGGER 55 +#define WLC_GET_ROAM_DELTA 56 +#define WLC_SET_ROAM_DELTA 57 +#define WLC_GET_ROAM_SCAN_PERIOD 58 +#define WLC_SET_ROAM_SCAN_PERIOD 59 +#define WLC_EVM 60 +#define WLC_GET_TXANT 61 +#define WLC_SET_TXANT 62 +#define WLC_GET_ANTDIV 63 +#define WLC_SET_ANTDIV 64 + + +#define WLC_GET_CLOSED 67 +#define WLC_SET_CLOSED 68 +#define WLC_GET_MACLIST 69 +#define WLC_SET_MACLIST 70 +#define WLC_GET_RATESET 71 +#define WLC_SET_RATESET 72 + +#define WLC_LONGTRAIN 74 +#define WLC_GET_BCNPRD 75 +#define WLC_SET_BCNPRD 76 +#define WLC_GET_DTIMPRD 77 +#define WLC_SET_DTIMPRD 78 +#define WLC_GET_SROM 79 +#define WLC_SET_SROM 80 +#define WLC_GET_WEP_RESTRICT 81 +#define WLC_SET_WEP_RESTRICT 82 +#define WLC_GET_COUNTRY 83 +#define WLC_SET_COUNTRY 84 +#define WLC_GET_PM 85 +#define WLC_SET_PM 86 +#define WLC_GET_WAKE 87 +#define WLC_SET_WAKE 88 + +#define WLC_GET_FORCELINK 90 +#define WLC_SET_FORCELINK 91 +#define WLC_FREQ_ACCURACY 92 +#define WLC_CARRIER_SUPPRESS 93 +#define WLC_GET_PHYREG 94 +#define WLC_SET_PHYREG 95 +#define WLC_GET_RADIOREG 96 +#define WLC_SET_RADIOREG 97 +#define WLC_GET_REVINFO 98 +#define WLC_GET_UCANTDIV 99 +#define WLC_SET_UCANTDIV 100 +#define WLC_R_REG 101 +#define WLC_W_REG 102 + + +#define WLC_GET_MACMODE 105 +#define WLC_SET_MACMODE 106 +#define WLC_GET_MONITOR 107 +#define WLC_SET_MONITOR 108 +#define WLC_GET_GMODE 109 +#define WLC_SET_GMODE 110 +#define WLC_GET_LEGACY_ERP 111 +#define WLC_SET_LEGACY_ERP 112 +#define WLC_GET_RX_ANT 113 +#define WLC_GET_CURR_RATESET 114 +#define WLC_GET_SCANSUPPRESS 115 +#define WLC_SET_SCANSUPPRESS 116 +#define WLC_GET_AP 117 +#define WLC_SET_AP 118 +#define WLC_GET_EAP_RESTRICT 119 +#define WLC_SET_EAP_RESTRICT 120 +#define WLC_SCB_AUTHORIZE 121 +#define WLC_SCB_DEAUTHORIZE 122 +#define WLC_GET_WDSLIST 123 +#define WLC_SET_WDSLIST 124 +#define WLC_GET_ATIM 125 +#define WLC_SET_ATIM 126 +#define WLC_GET_RSSI 127 +#define WLC_GET_PHYANTDIV 128 +#define WLC_SET_PHYANTDIV 129 +#define WLC_AP_RX_ONLY 130 +#define WLC_GET_TX_PATH_PWR 131 +#define WLC_SET_TX_PATH_PWR 132 +#define WLC_GET_WSEC 133 +#define WLC_SET_WSEC 134 +#define WLC_GET_PHY_NOISE 135 +#define WLC_GET_BSS_INFO 136 +#define WLC_GET_PKTCNTS 137 +#define WLC_GET_LAZYWDS 138 +#define WLC_SET_LAZYWDS 139 +#define WLC_GET_BANDLIST 140 +#define WLC_GET_BAND 141 +#define WLC_SET_BAND 142 +#define WLC_SCB_DEAUTHENTICATE 143 +#define WLC_GET_SHORTSLOT 144 +#define WLC_GET_SHORTSLOT_OVERRIDE 145 +#define WLC_SET_SHORTSLOT_OVERRIDE 146 +#define WLC_GET_SHORTSLOT_RESTRICT 147 +#define WLC_SET_SHORTSLOT_RESTRICT 148 +#define WLC_GET_GMODE_PROTECTION 149 +#define WLC_GET_GMODE_PROTECTION_OVERRIDE 150 +#define WLC_SET_GMODE_PROTECTION_OVERRIDE 151 +#define WLC_UPGRADE 152 + + +#define WLC_GET_IGNORE_BCNS 155 +#define WLC_SET_IGNORE_BCNS 156 +#define WLC_GET_SCB_TIMEOUT 157 +#define WLC_SET_SCB_TIMEOUT 158 +#define WLC_GET_ASSOCLIST 159 +#define WLC_GET_CLK 160 +#define WLC_SET_CLK 161 +#define WLC_GET_UP 162 +#define WLC_OUT 163 +#define WLC_GET_WPA_AUTH 164 +#define WLC_SET_WPA_AUTH 165 +#define WLC_GET_UCFLAGS 166 +#define WLC_SET_UCFLAGS 167 +#define WLC_GET_PWRIDX 168 +#define WLC_SET_PWRIDX 169 +#define WLC_GET_TSSI 170 +#define WLC_GET_SUP_RATESET_OVERRIDE 171 +#define WLC_SET_SUP_RATESET_OVERRIDE 172 + + + + + +#define WLC_GET_PROTECTION_CONTROL 178 +#define WLC_SET_PROTECTION_CONTROL 179 +#define WLC_GET_PHYLIST 180 +#define WLC_ENCRYPT_STRENGTH 181 +#define WLC_DECRYPT_STATUS 182 +#define WLC_GET_KEY_SEQ 183 +#define WLC_GET_SCAN_CHANNEL_TIME 184 +#define WLC_SET_SCAN_CHANNEL_TIME 185 +#define WLC_GET_SCAN_UNASSOC_TIME 186 +#define WLC_SET_SCAN_UNASSOC_TIME 187 +#define WLC_GET_SCAN_HOME_TIME 188 +#define WLC_SET_SCAN_HOME_TIME 189 +#define WLC_GET_SCAN_NPROBES 190 +#define WLC_SET_SCAN_NPROBES 191 +#define WLC_GET_PRB_RESP_TIMEOUT 192 +#define WLC_SET_PRB_RESP_TIMEOUT 193 +#define WLC_GET_ATTEN 194 +#define WLC_SET_ATTEN 195 +#define WLC_GET_SHMEM 196 +#define WLC_SET_SHMEM 197 + + +#define WLC_SET_WSEC_TEST 200 +#define WLC_SCB_DEAUTHENTICATE_FOR_REASON 201 +#define WLC_TKIP_COUNTERMEASURES 202 +#define WLC_GET_PIOMODE 203 +#define WLC_SET_PIOMODE 204 +#define WLC_SET_ASSOC_PREFER 205 +#define WLC_GET_ASSOC_PREFER 206 +#define WLC_SET_ROAM_PREFER 207 +#define WLC_GET_ROAM_PREFER 208 +#define WLC_SET_LED 209 +#define WLC_GET_LED 210 +#define WLC_GET_INTERFERENCE_MODE 211 +#define WLC_SET_INTERFERENCE_MODE 212 +#define WLC_GET_CHANNEL_QA 213 +#define WLC_START_CHANNEL_QA 214 +#define WLC_GET_CHANNEL_SEL 215 +#define WLC_START_CHANNEL_SEL 216 +#define WLC_GET_VALID_CHANNELS 217 +#define WLC_GET_FAKEFRAG 218 +#define WLC_SET_FAKEFRAG 219 +#define WLC_GET_PWROUT_PERCENTAGE 220 +#define WLC_SET_PWROUT_PERCENTAGE 221 +#define WLC_SET_BAD_FRAME_PREEMPT 222 +#define WLC_GET_BAD_FRAME_PREEMPT 223 +#define WLC_SET_LEAP_LIST 224 +#define WLC_GET_LEAP_LIST 225 +#define WLC_GET_CWMIN 226 +#define WLC_SET_CWMIN 227 +#define WLC_GET_CWMAX 228 +#define WLC_SET_CWMAX 229 +#define WLC_GET_WET 230 +#define WLC_SET_WET 231 +#define WLC_GET_PUB 232 + + +#define WLC_GET_KEY_PRIMARY 235 +#define WLC_SET_KEY_PRIMARY 236 + +#define WLC_GET_ACI_ARGS 238 +#define WLC_SET_ACI_ARGS 239 +#define WLC_UNSET_CALLBACK 240 +#define WLC_SET_CALLBACK 241 +#define WLC_GET_RADAR 242 +#define WLC_SET_RADAR 243 +#define WLC_SET_SPECT_MANAGMENT 244 +#define WLC_GET_SPECT_MANAGMENT 245 +#define WLC_WDS_GET_REMOTE_HWADDR 246 +#define WLC_WDS_GET_WPA_SUP 247 +#define WLC_SET_CS_SCAN_TIMER 248 +#define WLC_GET_CS_SCAN_TIMER 249 +#define WLC_MEASURE_REQUEST 250 +#define WLC_INIT 251 +#define WLC_SEND_QUIET 252 +#define WLC_KEEPALIVE 253 +#define WLC_SEND_PWR_CONSTRAINT 254 +#define WLC_UPGRADE_STATUS 255 +#define WLC_CURRENT_PWR 256 +#define WLC_GET_SCAN_PASSIVE_TIME 257 +#define WLC_SET_SCAN_PASSIVE_TIME 258 +#define WLC_LEGACY_LINK_BEHAVIOR 259 +#define WLC_GET_CHANNELS_IN_COUNTRY 260 +#define WLC_GET_COUNTRY_LIST 261 +#define WLC_GET_VAR 262 +#define WLC_SET_VAR 263 +#define WLC_NVRAM_GET 264 +#define WLC_NVRAM_SET 265 +#define WLC_NVRAM_DUMP 266 +#define WLC_REBOOT 267 +#define WLC_SET_WSEC_PMK 268 +#define WLC_GET_AUTH_MODE 269 +#define WLC_SET_AUTH_MODE 270 +#define WLC_GET_WAKEENTRY 271 +#define WLC_SET_WAKEENTRY 272 +#define WLC_NDCONFIG_ITEM 273 +#define WLC_NVOTPW 274 +#define WLC_OTPW 275 +#define WLC_IOV_BLOCK_GET 276 +#define WLC_IOV_MODULES_GET 277 +#define WLC_SOFT_RESET 278 +#define WLC_GET_ALLOW_MODE 279 +#define WLC_SET_ALLOW_MODE 280 +#define WLC_GET_DESIRED_BSSID 281 +#define WLC_SET_DESIRED_BSSID 282 +#define WLC_DISASSOC_MYAP 283 +#define WLC_GET_NBANDS 284 +#define WLC_GET_BANDSTATES 285 +#define WLC_GET_WLC_BSS_INFO 286 +#define WLC_GET_ASSOC_INFO 287 +#define WLC_GET_OID_PHY 288 +#define WLC_SET_OID_PHY 289 +#define WLC_SET_ASSOC_TIME 290 +#define WLC_GET_DESIRED_SSID 291 +#define WLC_GET_CHANSPEC 292 +#define WLC_GET_ASSOC_STATE 293 +#define WLC_SET_PHY_STATE 294 +#define WLC_GET_SCAN_PENDING 295 +#define WLC_GET_SCANREQ_PENDING 296 +#define WLC_GET_PREV_ROAM_REASON 297 +#define WLC_SET_PREV_ROAM_REASON 298 +#define WLC_GET_BANDSTATES_PI 299 +#define WLC_GET_PHY_STATE 300 +#define WLC_GET_BSS_WPA_RSN 301 +#define WLC_GET_BSS_WPA2_RSN 302 +#define WLC_GET_BSS_BCN_TS 303 +#define WLC_GET_INT_DISASSOC 304 +#define WLC_SET_NUM_PEERS 305 +#define WLC_GET_NUM_BSS 306 +#define WLC_LAST 307 + + + +#define WL_RADIO_SW_DISABLE (1<<0) +#define WL_RADIO_HW_DISABLE (1<<1) +#define WL_RADIO_MPC_DISABLE (1<<2) +#define WL_RADIO_COUNTRY_DISABLE (1<<3) + + +#define WL_TXPWR_OVERRIDE (1U<<31) + +#define WL_PHY_PAVARS_LEN 6 + + +#define WL_DIAG_INTERRUPT 1 +#define WL_DIAG_LOOPBACK 2 +#define WL_DIAG_MEMORY 3 +#define WL_DIAG_LED 4 +#define WL_DIAG_REG 5 +#define WL_DIAG_SROM 6 +#define WL_DIAG_DMA 7 + +#define WL_DIAGERR_SUCCESS 0 +#define WL_DIAGERR_FAIL_TO_RUN 1 +#define WL_DIAGERR_NOT_SUPPORTED 2 +#define WL_DIAGERR_INTERRUPT_FAIL 3 +#define WL_DIAGERR_LOOPBACK_FAIL 4 +#define WL_DIAGERR_SROM_FAIL 5 +#define WL_DIAGERR_SROM_BADCRC 6 +#define WL_DIAGERR_REG_FAIL 7 +#define WL_DIAGERR_MEMORY_FAIL 8 +#define WL_DIAGERR_NOMEM 9 +#define WL_DIAGERR_DMA_FAIL 10 + +#define WL_DIAGERR_MEMORY_TIMEOUT 11 +#define WL_DIAGERR_MEMORY_BADPATTERN 12 + + +#define WLC_BAND_AUTO 0 +#define WLC_BAND_5G 1 +#define WLC_BAND_2G 2 +#define WLC_BAND_ALL 3 + + +#define WL_CHAN_FREQ_RANGE_2G 0 +#define WL_CHAN_FREQ_RANGE_5GL 1 +#define WL_CHAN_FREQ_RANGE_5GM 2 +#define WL_CHAN_FREQ_RANGE_5GH 3 + + +#define WLC_PHY_TYPE_A 0 +#define WLC_PHY_TYPE_B 1 +#define WLC_PHY_TYPE_G 2 +#define WLC_PHY_TYPE_N 4 +#define WLC_PHY_TYPE_LP 5 +#define WLC_PHY_TYPE_SSN 6 +#define WLC_PHY_TYPE_NULL 0xf + + +#define WLC_MACMODE_DISABLED 0 +#define WLC_MACMODE_DENY 1 +#define WLC_MACMODE_ALLOW 2 + + +#define GMODE_LEGACY_B 0 +#define GMODE_AUTO 1 +#define GMODE_ONLY 2 +#define GMODE_B_DEFERRED 3 +#define GMODE_PERFORMANCE 4 +#define GMODE_LRS 5 +#define GMODE_MAX 6 + + +#define WLC_PLCP_AUTO -1 +#define WLC_PLCP_SHORT 0 +#define WLC_PLCP_LONG 1 + + +#define WLC_PROTECTION_AUTO -1 +#define WLC_PROTECTION_OFF 0 +#define WLC_PROTECTION_ON 1 +#define WLC_PROTECTION_MMHDR_ONLY 2 +#define WLC_PROTECTION_CTS_ONLY 3 + + +#define WLC_PROTECTION_CTL_OFF 0 +#define WLC_PROTECTION_CTL_LOCAL 1 +#define WLC_PROTECTION_CTL_OVERLAP 2 + + +#define WLC_N_PROTECTION_OFF 0 +#define WLC_N_PROTECTION_OPTIONAL 1 +#define WLC_N_PROTECTION_20IN40 2 +#define WLC_N_PROTECTION_MIXEDMODE 3 + + +#define WLC_N_PREAMBLE_MIXEDMODE 0 +#define WLC_N_PREAMBLE_GF 1 + + +#define WLC_N_BW_20ALL 0 +#define WLC_N_BW_40ALL 1 +#define WLC_N_BW_20IN2G_40IN5G 2 + + +#define WLC_N_TXRX_CHAIN0 0 +#define WLC_N_TXRX_CHAIN1 1 + + +#define WLC_N_SGI_20 0x01 +#define WLC_N_SGI_40 0x02 + + +#define PM_OFF 0 +#define PM_MAX 1 +#define PM_FAST 2 + +#define LISTEN_INTERVAL 10 + +#define INTERFERE_NONE 0 +#define NON_WLAN 1 +#define WLAN_MANUAL 2 +#define WLAN_AUTO 3 +#define AUTO_ACTIVE (1 << 7) + +typedef struct wl_aci_args { + int enter_aci_thresh; + int exit_aci_thresh; + int usec_spin; + int glitch_delay; + uint16 nphy_adcpwr_enter_thresh; + uint16 nphy_adcpwr_exit_thresh; + uint16 nphy_repeat_ctr; + uint16 nphy_num_samples; + uint16 nphy_undetect_window_sz; + uint16 nphy_b_energy_lo_aci; + uint16 nphy_b_energy_md_aci; + uint16 nphy_b_energy_hi_aci; +} wl_aci_args_t; + +#define WL_ACI_ARGS_LEGACY_LENGTH 16 + + + +#define WL_ERROR_VAL 0x00000001 +#define WL_TRACE_VAL 0x00000002 +#define WL_PRHDRS_VAL 0x00000004 +#define WL_PRPKT_VAL 0x00000008 +#define WL_INFORM_VAL 0x00000010 +#define WL_TMP_VAL 0x00000020 +#define WL_OID_VAL 0x00000040 +#define WL_RATE_VAL 0x00000080 +#define WL_ASSOC_VAL 0x00000100 +#define WL_PRUSR_VAL 0x00000200 +#define WL_PS_VAL 0x00000400 +#define WL_TXPWR_VAL 0x00000800 +#define WL_PORT_VAL 0x00001000 +#define WL_DUAL_VAL 0x00002000 +#define WL_WSEC_VAL 0x00004000 +#define WL_WSEC_DUMP_VAL 0x00008000 +#define WL_LOG_VAL 0x00010000 +#define WL_NRSSI_VAL 0x00020000 +#define WL_LOFT_VAL 0x00040000 +#define WL_REGULATORY_VAL 0x00080000 +#define WL_PHYCAL_VAL 0x00100000 +#define WL_RADAR_VAL 0x00200000 +#define WL_MPC_VAL 0x00400000 +#define WL_APSTA_VAL 0x00800000 +#define WL_DFS_VAL 0x01000000 +#define WL_BA_VAL 0x02000000 +#define WL_MBSS_VAL 0x04000000 +#define WL_CAC_VAL 0x08000000 +#define WL_AMSDU_VAL 0x10000000 +#define WL_AMPDU_VAL 0x20000000 +#define WL_FFPLD_VAL 0x40000000 + + +#define WL_DPT_VAL 0x00000001 +#define WL_SCAN_VAL 0x00000002 +#define WL_WOWL_VAL 0x00000004 +#define WL_COEX_VAL 0x00000008 +#define WL_RTDC_VAL 0x00000010 +#define WL_BTA_VAL 0x00000040 + + +#define WL_LED_NUMGPIO 16 + + +#define WL_LED_OFF 0 +#define WL_LED_ON 1 +#define WL_LED_ACTIVITY 2 +#define WL_LED_RADIO 3 +#define WL_LED_ARADIO 4 +#define WL_LED_BRADIO 5 +#define WL_LED_BGMODE 6 +#define WL_LED_WI1 7 +#define WL_LED_WI2 8 +#define WL_LED_WI3 9 +#define WL_LED_ASSOC 10 +#define WL_LED_INACTIVE 11 +#define WL_LED_ASSOCACT 12 +#define WL_LED_NUMBEHAVIOR 13 + + +#define WL_LED_BEH_MASK 0x7f +#define WL_LED_AL_MASK 0x80 + + +#define WL_NUMCHANNELS 64 +#define WL_NUMCHANSPECS 100 + + +#define WL_WDS_WPA_ROLE_AUTH 0 +#define WL_WDS_WPA_ROLE_SUP 1 +#define WL_WDS_WPA_ROLE_AUTO 255 + + +#define WL_EVENTING_MASK_LEN 16 + + +#define VNDR_IE_CMD_LEN 4 + + +#define VNDR_IE_BEACON_FLAG 0x1 +#define VNDR_IE_PRBRSP_FLAG 0x2 +#define VNDR_IE_ASSOCRSP_FLAG 0x4 +#define VNDR_IE_AUTHRSP_FLAG 0x8 +#define VNDR_IE_PRBREQ_FLAG 0x10 +#define VNDR_IE_ASSOCREQ_FLAG 0x20 +#define VNDR_IE_CUSTOM_FLAG 0x100 + +#define VNDR_IE_INFO_HDR_LEN (sizeof(uint32)) + +typedef struct { + uint32 pktflag; + vndr_ie_t vndr_ie_data; +} vndr_ie_info_t; + +typedef struct { + int iecount; + vndr_ie_info_t vndr_ie_list[1]; +} vndr_ie_buf_t; + +typedef struct { + char cmd[VNDR_IE_CMD_LEN]; + vndr_ie_buf_t vndr_ie_buffer; +} vndr_ie_setbuf_t; + + + + +#define WL_JOIN_PREF_RSSI 1 +#define WL_JOIN_PREF_WPA 2 +#define WL_JOIN_PREF_BAND 3 + + +#define WLJP_BAND_ASSOC_PREF 255 + + +#define WL_WPA_ACP_MCS_ANY "\x00\x00\x00\x00" + +struct tsinfo_arg { + uint8 octets[3]; +}; + + +#define NFIFO 6 + +#define WL_CNT_T_VERSION 5 +#define WL_CNT_EXT_T_VERSION 1 + +typedef struct { + uint16 version; + uint16 length; + + + uint32 txframe; + uint32 txbyte; + uint32 txretrans; + uint32 txerror; + uint32 txctl; + uint32 txprshort; + uint32 txserr; + uint32 txnobuf; + uint32 txnoassoc; + uint32 txrunt; + uint32 txchit; + uint32 txcmiss; + + + uint32 txuflo; + uint32 txphyerr; + uint32 txphycrs; + + + uint32 rxframe; + uint32 rxbyte; + uint32 rxerror; + uint32 rxctl; + uint32 rxnobuf; + uint32 rxnondata; + uint32 rxbadds; + uint32 rxbadcm; + uint32 rxfragerr; + uint32 rxrunt; + uint32 rxgiant; + uint32 rxnoscb; + uint32 rxbadproto; + uint32 rxbadsrcmac; + uint32 rxbadda; + uint32 rxfilter; + + + uint32 rxoflo; + uint32 rxuflo[NFIFO]; + + uint32 d11cnt_txrts_off; + uint32 d11cnt_rxcrc_off; + uint32 d11cnt_txnocts_off; + + + uint32 dmade; + uint32 dmada; + uint32 dmape; + uint32 reset; + uint32 tbtt; + uint32 txdmawar; + uint32 pkt_callback_reg_fail; + + + uint32 txallfrm; + uint32 txrtsfrm; + uint32 txctsfrm; + uint32 txackfrm; + uint32 txdnlfrm; + uint32 txbcnfrm; + uint32 txfunfl[8]; + uint32 txtplunfl; + uint32 txphyerror; + uint32 rxfrmtoolong; + uint32 rxfrmtooshrt; + uint32 rxinvmachdr; + uint32 rxbadfcs; + uint32 rxbadplcp; + uint32 rxcrsglitch; + uint32 rxstrt; + uint32 rxdfrmucastmbss; + uint32 rxmfrmucastmbss; + uint32 rxcfrmucast; + uint32 rxrtsucast; + uint32 rxctsucast; + uint32 rxackucast; + uint32 rxdfrmocast; + uint32 rxmfrmocast; + uint32 rxcfrmocast; + uint32 rxrtsocast; + uint32 rxctsocast; + uint32 rxdfrmmcast; + uint32 rxmfrmmcast; + uint32 rxcfrmmcast; + uint32 rxbeaconmbss; + uint32 rxdfrmucastobss; + uint32 rxbeaconobss; + uint32 rxrsptmout; + uint32 bcntxcancl; + uint32 rxf0ovfl; + uint32 rxf1ovfl; + uint32 rxf2ovfl; + uint32 txsfovfl; + uint32 pmqovfl; + uint32 rxcgprqfrm; + uint32 rxcgprsqovfl; + uint32 txcgprsfail; + uint32 txcgprssuc; + uint32 prs_timeout; + uint32 rxnack; + uint32 frmscons; + uint32 txnack; + uint32 txglitch_nack; + uint32 txburst; + + + uint32 txfrag; + uint32 txmulti; + uint32 txfail; + uint32 txretry; + uint32 txretrie; + uint32 rxdup; + uint32 txrts; + uint32 txnocts; + uint32 txnoack; + uint32 rxfrag; + uint32 rxmulti; + uint32 rxcrc; + uint32 txfrmsnt; + uint32 rxundec; + + + uint32 tkipmicfaill; + uint32 tkipcntrmsr; + uint32 tkipreplay; + uint32 ccmpfmterr; + uint32 ccmpreplay; + uint32 ccmpundec; + uint32 fourwayfail; + uint32 wepundec; + uint32 wepicverr; + uint32 decsuccess; + uint32 tkipicverr; + uint32 wepexcluded; + + uint32 txchanrej; + uint32 psmwds; + uint32 phywatchdog; + + + uint32 prq_entries_handled; + uint32 prq_undirected_entries; + uint32 prq_bad_entries; + uint32 atim_suppress_count; + uint32 bcn_template_not_ready; + uint32 bcn_template_not_ready_done; + uint32 late_tbtt_dpc; + + + uint32 rx1mbps; + uint32 rx2mbps; + uint32 rx5mbps5; + uint32 rx6mbps; + uint32 rx9mbps; + uint32 rx11mbps; + uint32 rx12mbps; + uint32 rx18mbps; + uint32 rx24mbps; + uint32 rx36mbps; + uint32 rx48mbps; + uint32 rx54mbps; + uint32 rx108mbps; + uint32 rx162mbps; + uint32 rx216mbps; + uint32 rx270mbps; + uint32 rx324mbps; + uint32 rx378mbps; + uint32 rx432mbps; + uint32 rx486mbps; + uint32 rx540mbps; + + uint32 pktengrxducast; + uint32 pktengrxdmcast; +} wl_cnt_t; + +typedef struct { + uint16 version; + uint16 length; + + uint32 rxampdu_sgi; + uint32 rxampdu_stbc; + uint32 rxmpdu_sgi; + uint32 rxmpdu_stbc; + uint32 rxmcs0_40M; + uint32 rxmcs1_40M; + uint32 rxmcs2_40M; + uint32 rxmcs3_40M; + uint32 rxmcs4_40M; + uint32 rxmcs5_40M; + uint32 rxmcs6_40M; + uint32 rxmcs7_40M; + uint32 rxmcs32_40M; + + uint32 txfrmsnt_20Mlo; + uint32 txfrmsnt_20Mup; + uint32 txfrmsnt_40M; + + uint32 rx_20ul; +} wl_cnt_ext_t; + +#define WL_RXDIV_STATS_T_VERSION 1 +typedef struct { + uint16 version; + uint16 length; + + uint32 rxant[4]; +} wl_rxdiv_stats_t; + +#define WL_DELTA_STATS_T_VERSION 1 + +typedef struct { + uint16 version; + uint16 length; + + + uint32 txframe; + uint32 txbyte; + uint32 txretrans; + uint32 txfail; + + + uint32 rxframe; + uint32 rxbyte; + + + uint32 rx1mbps; + uint32 rx2mbps; + uint32 rx5mbps5; + uint32 rx6mbps; + uint32 rx9mbps; + uint32 rx11mbps; + uint32 rx12mbps; + uint32 rx18mbps; + uint32 rx24mbps; + uint32 rx36mbps; + uint32 rx48mbps; + uint32 rx54mbps; + uint32 rx108mbps; + uint32 rx162mbps; + uint32 rx216mbps; + uint32 rx270mbps; + uint32 rx324mbps; + uint32 rx378mbps; + uint32 rx432mbps; + uint32 rx486mbps; + uint32 rx540mbps; +} wl_delta_stats_t; + +#define WL_WME_CNT_VERSION 1 + +typedef struct { + uint32 packets; + uint32 bytes; +} wl_traffic_stats_t; + +typedef struct { + uint16 version; + uint16 length; + + wl_traffic_stats_t tx[AC_COUNT]; + wl_traffic_stats_t tx_failed[AC_COUNT]; + wl_traffic_stats_t rx[AC_COUNT]; + wl_traffic_stats_t rx_failed[AC_COUNT]; + + wl_traffic_stats_t forward[AC_COUNT]; + + wl_traffic_stats_t tx_expired[AC_COUNT]; + +} wl_wme_cnt_t; + + + +#define WLC_ROAM_TRIGGER_DEFAULT 0 +#define WLC_ROAM_TRIGGER_BANDWIDTH 1 +#define WLC_ROAM_TRIGGER_DISTANCE 2 +#define WLC_ROAM_TRIGGER_MAX_VALUE 2 + + +enum { + PFN_LIST_ORDER, + PFN_RSSI +}; + +enum { + DISABLE, + ENABLE +}; + +#define SORT_CRITERIA_BIT 0 +#define AUTO_NET_SWITCH_BIT 1 +#define ENABLE_BKGRD_SCAN_BIT 2 +#define IMMEDIATE_SCAN_BIT 3 +#define AUTO_CONNECT_BIT 4 +#define ENABLE_BD_SCAN_BIT 5 +#define ENABLE_ADAPTSCAN_BIT 6 + +#define SORT_CRITERIA_MASK 0x01 +#define AUTO_NET_SWITCH_MASK 0x02 +#define ENABLE_BKGRD_SCAN_MASK 0x04 +#define IMMEDIATE_SCAN_MASK 0x08 +#define AUTO_CONNECT_MASK 0x10 +#define ENABLE_BD_SCAN_MASK 0x20 +#define ENABLE_ADAPTSCAN_MASK 0x40 + +#define PFN_VERSION 1 + +#define MAX_PFN_LIST_COUNT 16 + + +typedef struct wl_pfn_param { + int32 version; + int32 scan_freq; + int32 lost_network_timeout; + int16 flags; + int16 rssi_margin; + int32 repeat_scan; + int32 max_freq_adjust; +} wl_pfn_param_t; + +typedef struct wl_pfn { + wlc_ssid_t ssid; + int32 bss_type; + int32 infra; + int32 auth; + uint32 wpa_auth; + int32 wsec; +} wl_pfn_t; + +#define PNO_SCAN_MAX_FW 508*1000 +#define PNO_SCAN_MAX_FW_SEC PNO_SCAN_MAX_FW/1000 +#define PNO_SCAN_MIN_FW_SEC 10 + + +#define TOE_TX_CSUM_OL 0x00000001 +#define TOE_RX_CSUM_OL 0x00000002 + + +#define TOE_ERRTEST_TX_CSUM 0x00000001 +#define TOE_ERRTEST_RX_CSUM 0x00000002 +#define TOE_ERRTEST_RX_CSUM2 0x00000004 + +struct toe_ol_stats_t { + + uint32 tx_summed; + + + uint32 tx_iph_fill; + uint32 tx_tcp_fill; + uint32 tx_udp_fill; + uint32 tx_icmp_fill; + + + uint32 rx_iph_good; + uint32 rx_iph_bad; + uint32 rx_tcp_good; + uint32 rx_tcp_bad; + uint32 rx_udp_good; + uint32 rx_udp_bad; + uint32 rx_icmp_good; + uint32 rx_icmp_bad; + + + uint32 tx_tcp_errinj; + uint32 tx_udp_errinj; + uint32 tx_icmp_errinj; + + + uint32 rx_tcp_errinj; + uint32 rx_udp_errinj; + uint32 rx_icmp_errinj; +}; + + +#define ARP_OL_AGENT 0x00000001 +#define ARP_OL_SNOOP 0x00000002 +#define ARP_OL_HOST_AUTO_REPLY 0x00000004 +#define ARP_OL_PEER_AUTO_REPLY 0x00000008 + + +#define ARP_ERRTEST_REPLY_PEER 0x1 +#define ARP_ERRTEST_REPLY_HOST 0x2 + +#define ARP_MULTIHOMING_MAX 8 + + +struct arp_ol_stats_t { + uint32 host_ip_entries; + uint32 host_ip_overflow; + + uint32 arp_table_entries; + uint32 arp_table_overflow; + + uint32 host_request; + uint32 host_reply; + uint32 host_service; + + uint32 peer_request; + uint32 peer_request_drop; + uint32 peer_reply; + uint32 peer_reply_drop; + uint32 peer_service; +}; + + + + + +typedef struct wl_keep_alive_pkt { + uint32 period_msec; + uint16 len_bytes; + uint8 data[1]; +} wl_keep_alive_pkt_t; + +#define WL_KEEP_ALIVE_FIXED_LEN OFFSETOF(wl_keep_alive_pkt_t, data) + + + + + +typedef enum wl_pkt_filter_type { + WL_PKT_FILTER_TYPE_PATTERN_MATCH +} wl_pkt_filter_type_t; + +#define WL_PKT_FILTER_TYPE wl_pkt_filter_type_t + + +typedef struct wl_pkt_filter_pattern { + uint32 offset; + uint32 size_bytes; + uint8 mask_and_pattern[1]; +} wl_pkt_filter_pattern_t; + + +typedef struct wl_pkt_filter { + uint32 id; + uint32 type; + uint32 negate_match; + union { + wl_pkt_filter_pattern_t pattern; + } u; +} wl_pkt_filter_t; + +#define WL_PKT_FILTER_FIXED_LEN OFFSETOF(wl_pkt_filter_t, u) +#define WL_PKT_FILTER_PATTERN_FIXED_LEN OFFSETOF(wl_pkt_filter_pattern_t, mask_and_pattern) + + +typedef struct wl_pkt_filter_enable { + uint32 id; + uint32 enable; +} wl_pkt_filter_enable_t; + + +typedef struct wl_pkt_filter_list { + uint32 num; + wl_pkt_filter_t filter[1]; +} wl_pkt_filter_list_t; + +#define WL_PKT_FILTER_LIST_FIXED_LEN OFFSETOF(wl_pkt_filter_list_t, filter) + + +typedef struct wl_pkt_filter_stats { + uint32 num_pkts_matched; + uint32 num_pkts_forwarded; + uint32 num_pkts_discarded; +} wl_pkt_filter_stats_t; + + +typedef struct wl_seq_cmd_ioctl { + uint32 cmd; + uint32 len; +} wl_seq_cmd_ioctl_t; + +#define WL_SEQ_CMD_ALIGN_BYTES 4 + + +#define WL_SEQ_CMDS_GET_IOCTL_FILTER(cmd) \ + (((cmd) == WLC_GET_MAGIC) || \ + ((cmd) == WLC_GET_VERSION) || \ + ((cmd) == WLC_GET_AP) || \ + ((cmd) == WLC_GET_INSTANCE)) + + + +#define WL_PKTENG_PER_TX_START 0x01 +#define WL_PKTENG_PER_TX_STOP 0x02 +#define WL_PKTENG_PER_RX_START 0x04 +#define WL_PKTENG_PER_RX_WITH_ACK_START 0x05 +#define WL_PKTENG_PER_TX_WITH_ACK_START 0x06 +#define WL_PKTENG_PER_RX_STOP 0x08 +#define WL_PKTENG_PER_MASK 0xff + +#define WL_PKTENG_SYNCHRONOUS 0x100 + +typedef struct wl_pkteng { + uint32 flags; + uint32 delay; + uint32 nframes; + uint32 length; + uint8 seqno; + struct ether_addr dest; + struct ether_addr src; +} wl_pkteng_t; + +#define NUM_80211b_RATES 4 +#define NUM_80211ag_RATES 8 +#define NUM_80211n_RATES 32 +#define NUM_80211_RATES (NUM_80211b_RATES+NUM_80211ag_RATES+NUM_80211n_RATES) +typedef struct wl_pkteng_stats { + uint32 lostfrmcnt; + int32 rssi; + int32 snr; + uint16 rxpktcnt[NUM_80211_RATES+1]; +} wl_pkteng_stats_t; + +#define WL_WOWL_MAGIC (1 << 0) +#define WL_WOWL_NET (1 << 1) +#define WL_WOWL_DIS (1 << 2) +#define WL_WOWL_RETR (1 << 3) +#define WL_WOWL_BCN (1 << 4) +#define WL_WOWL_TST (1 << 5) +#define WL_WOWL_BCAST (1 << 15) + +#define MAGIC_PKT_MINLEN 102 + +typedef struct { + uint masksize; + uint offset; + uint patternoffset; + uint patternsize; + + +} wl_wowl_pattern_t; + +typedef struct { + uint count; + wl_wowl_pattern_t pattern[1]; +} wl_wowl_pattern_list_t; + +typedef struct { + uint8 pci_wakeind; + uint16 ucode_wakeind; +} wl_wowl_wakeind_t; + + +typedef struct wl_txrate_class { + uint8 init_rate; + uint8 min_rate; + uint8 max_rate; +} wl_txrate_class_t; + + + + +#define WLC_OBSS_SCAN_PASSIVE_DWELL_DEFAULT 100 +#define WLC_OBSS_SCAN_PASSIVE_DWELL_MIN 5 +#define WLC_OBSS_SCAN_PASSIVE_DWELL_MAX 1000 +#define WLC_OBSS_SCAN_ACTIVE_DWELL_DEFAULT 20 +#define WLC_OBSS_SCAN_ACTIVE_DWELL_MIN 10 +#define WLC_OBSS_SCAN_ACTIVE_DWELL_MAX 1000 +#define WLC_OBSS_SCAN_WIDTHSCAN_INTERVAL_DEFAULT 300 +#define WLC_OBSS_SCAN_WIDTHSCAN_INTERVAL_MIN 10 +#define WLC_OBSS_SCAN_WIDTHSCAN_INTERVAL_MAX 900 +#define WLC_OBSS_SCAN_CHANWIDTH_TRANSITION_DLY_DEFAULT 5 +#define WLC_OBSS_SCAN_CHANWIDTH_TRANSITION_DLY_MIN 5 +#define WLC_OBSS_SCAN_CHANWIDTH_TRANSITION_DLY_MAX 100 +#define WLC_OBSS_SCAN_PASSIVE_TOTAL_PER_CHANNEL_DEFAULT 200 +#define WLC_OBSS_SCAN_PASSIVE_TOTAL_PER_CHANNEL_MIN 200 +#define WLC_OBSS_SCAN_PASSIVE_TOTAL_PER_CHANNEL_MAX 10000 +#define WLC_OBSS_SCAN_ACTIVE_TOTAL_PER_CHANNEL_DEFAULT 20 +#define WLC_OBSS_SCAN_ACTIVE_TOTAL_PER_CHANNEL_MIN 20 +#define WLC_OBSS_SCAN_ACTIVE_TOTAL_PER_CHANNEL_MAX 10000 +#define WLC_OBSS_SCAN_ACTIVITY_THRESHOLD_DEFAULT 25 +#define WLC_OBSS_SCAN_ACTIVITY_THRESHOLD_MIN 0 +#define WLC_OBSS_SCAN_ACTIVITY_THRESHOLD_MAX 100 + + +typedef struct wl_obss_scan_arg { + int16 passive_dwell; + int16 active_dwell; + int16 bss_widthscan_interval; + int16 passive_total; + int16 active_total; + int16 chanwidth_transition_delay; + int16 activity_threshold; +} wl_obss_scan_arg_t; +#define WL_OBSS_SCAN_PARAM_LEN sizeof(wl_obss_scan_arg_t) +#define WL_MIN_NUM_OBSS_SCAN_ARG 7 + +#define WL_COEX_INFO_MASK 0x07 +#define WL_COEX_INFO_REQ 0x01 +#define WL_COEX_40MHZ_INTOLERANT 0x02 +#define WL_COEX_WIDTH20 0x04 + +typedef struct wl_action_obss_coex_req { + uint8 info; + uint8 num; + uint8 ch_list[1]; +} wl_action_obss_coex_req_t; + + +#define MAX_RSSI_LEVELS 8 + + +typedef struct wl_rssi_event { + + uint32 rate_limit_msec; + + uint8 num_rssi_levels; + + int8 rssi_levels[MAX_RSSI_LEVELS]; +} wl_rssi_event_t; + + + +#define WLFEATURE_DISABLE_11N 0x00000001 +#define WLFEATURE_DISABLE_11N_STBC_TX 0x00000002 +#define WLFEATURE_DISABLE_11N_STBC_RX 0x00000004 +#define WLFEATURE_DISABLE_11N_SGI_TX 0x00000008 +#define WLFEATURE_DISABLE_11N_SGI_RX 0x00000010 +#define WLFEATURE_DISABLE_11N_AMPDU_TX 0x00000020 +#define WLFEATURE_DISABLE_11N_AMPDU_RX 0x00000040 +#define WLFEATURE_DISABLE_11N_GF 0x00000080 + + + +#include + + +#include + + +typedef BWL_PRE_PACKED_STRUCT struct sta_prbreq_wps_ie_hdr { + struct ether_addr staAddr; + uint16 ieLen; +} BWL_POST_PACKED_STRUCT sta_prbreq_wps_ie_hdr_t; + +typedef BWL_PRE_PACKED_STRUCT struct sta_prbreq_wps_ie_data { + sta_prbreq_wps_ie_hdr_t hdr; + uint8 ieData[1]; +} BWL_POST_PACKED_STRUCT sta_prbreq_wps_ie_data_t; + +typedef BWL_PRE_PACKED_STRUCT struct sta_prbreq_wps_ie_list { + uint32 totLen; + uint8 ieDataList[1]; +} BWL_POST_PACKED_STRUCT sta_prbreq_wps_ie_list_t; + + +#include + +#endif diff --git a/drivers/net/wireless/bcm4329/linux_osl.c b/drivers/net/wireless/bcm4329/linux_osl.c new file mode 100644 index 0000000000000000000000000000000000000000..cf72a077bd9089a249a5332cbd05af2f969fe6aa --- /dev/null +++ b/drivers/net/wireless/bcm4329/linux_osl.c @@ -0,0 +1,625 @@ +/* + * Linux OS Independent Layer + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: linux_osl.c,v 1.125.12.3.8.7 2010/05/04 21:10:04 Exp $ + */ + + +#define LINUX_OSL + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCI_CFG_RETRY 10 + +#define OS_HANDLE_MAGIC 0x1234abcd +#define BCM_MEM_FILENAME_LEN 24 + +#ifdef DHD_USE_STATIC_BUF +#define MAX_STATIC_BUF_NUM 16 +#define STATIC_BUF_SIZE (PAGE_SIZE*2) +#define STATIC_BUF_TOTAL_LEN (MAX_STATIC_BUF_NUM*STATIC_BUF_SIZE) +typedef struct bcm_static_buf { + struct mutex static_sem; + unsigned char *buf_ptr; + unsigned char buf_use[MAX_STATIC_BUF_NUM]; +} bcm_static_buf_t; + +static bcm_static_buf_t *bcm_static_buf = 0; + +#define MAX_STATIC_PKT_NUM 8 +typedef struct bcm_static_pkt { + struct sk_buff *skb_4k[MAX_STATIC_PKT_NUM]; + struct sk_buff *skb_8k[MAX_STATIC_PKT_NUM]; + struct mutex osl_pkt_sem; + unsigned char pkt_use[MAX_STATIC_PKT_NUM*2]; +} bcm_static_pkt_t; +static bcm_static_pkt_t *bcm_static_skb = 0; + +#endif +typedef struct bcm_mem_link { + struct bcm_mem_link *prev; + struct bcm_mem_link *next; + uint size; + int line; + char file[BCM_MEM_FILENAME_LEN]; +} bcm_mem_link_t; + +struct osl_info { + osl_pubinfo_t pub; + uint magic; + void *pdev; + uint malloced; + uint failed; + uint bustype; + bcm_mem_link_t *dbgmem_list; +}; + +static int16 linuxbcmerrormap[] = +{ 0, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -E2BIG, + -E2BIG, + -EBUSY, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EFAULT, + -ENOMEM, + -EOPNOTSUPP, + -EMSGSIZE, + -EINVAL, + -EPERM, + -ENOMEM, + -EINVAL, + -ERANGE, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EIO, + -ENODEV, + -EINVAL, + -EIO, + -EIO, + -EINVAL, + -EINVAL, + + + +#if BCME_LAST != -41 +#error "You need to add a OS error translation in the linuxbcmerrormap \ + for new error code defined in bcmutils.h" +#endif +}; + + +int +osl_error(int bcmerror) +{ + if (bcmerror > 0) + bcmerror = 0; + else if (bcmerror < BCME_LAST) + bcmerror = BCME_ERROR; + + + return linuxbcmerrormap[-bcmerror]; +} + +void * dhd_os_prealloc(int section, unsigned long size); +osl_t * +osl_attach(void *pdev, uint bustype, bool pkttag) +{ + osl_t *osh; + gfp_t flags; + + flags = (in_atomic()) ? GFP_ATOMIC : GFP_KERNEL; + osh = kmalloc(sizeof(osl_t), flags); + ASSERT(osh); + + bzero(osh, sizeof(osl_t)); + + + ASSERT(ABS(BCME_LAST) == (ARRAYSIZE(linuxbcmerrormap) - 1)); + + osh->magic = OS_HANDLE_MAGIC; + osh->malloced = 0; + osh->failed = 0; + osh->dbgmem_list = NULL; + osh->pdev = pdev; + osh->pub.pkttag = pkttag; + osh->bustype = bustype; + + switch (bustype) { + case PCI_BUS: + case SI_BUS: + case PCMCIA_BUS: + osh->pub.mmbus = TRUE; + break; + case JTAG_BUS: + case SDIO_BUS: + case USB_BUS: + case SPI_BUS: + osh->pub.mmbus = FALSE; + break; + default: + ASSERT(FALSE); + break; + } + +#ifdef DHD_USE_STATIC_BUF + + + if (!bcm_static_buf) { + if (!(bcm_static_buf = (bcm_static_buf_t *)dhd_os_prealloc(3, STATIC_BUF_SIZE+ + STATIC_BUF_TOTAL_LEN))) { + printk("can not alloc static buf!\n"); + } + else { + /* printk("alloc static buf at %x!\n", (unsigned int)bcm_static_buf); */ + } + + mutex_init(&bcm_static_buf->static_sem); + + + bcm_static_buf->buf_ptr = (unsigned char *)bcm_static_buf + STATIC_BUF_SIZE; + + } + + if (!bcm_static_skb) + { + int i; + void *skb_buff_ptr = 0; + bcm_static_skb = (bcm_static_pkt_t *)((char *)bcm_static_buf + 2048); + skb_buff_ptr = dhd_os_prealloc(4, 0); + + bcopy(skb_buff_ptr, bcm_static_skb, sizeof(struct sk_buff *)*16); + for (i = 0; i < MAX_STATIC_PKT_NUM*2; i++) + bcm_static_skb->pkt_use[i] = 0; + + mutex_init(&bcm_static_skb->osl_pkt_sem); + } +#endif + return osh; +} + +void +osl_detach(osl_t *osh) +{ + if (osh == NULL) + return; + +#ifdef DHD_USE_STATIC_BUF + if (bcm_static_buf) { + bcm_static_buf = 0; + } + if (bcm_static_skb) { + bcm_static_skb = 0; + } +#endif + ASSERT(osh->magic == OS_HANDLE_MAGIC); + kfree(osh); +} + + +void* +osl_pktget(osl_t *osh, uint len) +{ + struct sk_buff *skb; + gfp_t flags; + + flags = (in_atomic()) ? GFP_ATOMIC : GFP_KERNEL; + if ((skb = __dev_alloc_skb(len, flags))) { + skb_put(skb, len); + skb->priority = 0; + + + osh->pub.pktalloced++; + } + + return ((void*) skb); +} + + +void +osl_pktfree(osl_t *osh, void *p, bool send) +{ + struct sk_buff *skb, *nskb; + + skb = (struct sk_buff*) p; + + if (send && osh->pub.tx_fn) + osh->pub.tx_fn(osh->pub.tx_ctx, p, 0); + + + while (skb) { + nskb = skb->next; + skb->next = NULL; + + + if (skb->destructor) { + + dev_kfree_skb_any(skb); + } else { + + dev_kfree_skb(skb); + } + + osh->pub.pktalloced--; + + skb = nskb; + } +} + +#ifdef DHD_USE_STATIC_BUF +void* +osl_pktget_static(osl_t *osh, uint len) +{ + int i = 0; + struct sk_buff *skb; + + + if (len > (PAGE_SIZE*2)) + { + printk("Do we really need this big skb??\n"); + return osl_pktget(osh, len); + } + + + mutex_lock(&bcm_static_skb->osl_pkt_sem); + if (len <= PAGE_SIZE) + { + + for (i = 0; i < MAX_STATIC_PKT_NUM; i++) + { + if (bcm_static_skb->pkt_use[i] == 0) + break; + } + + if (i != MAX_STATIC_PKT_NUM) + { + bcm_static_skb->pkt_use[i] = 1; + mutex_unlock(&bcm_static_skb->osl_pkt_sem); + + skb = bcm_static_skb->skb_4k[i]; + skb->tail = skb->data + len; + skb->len = len; + + return skb; + } + } + + + for (i = 0; i < MAX_STATIC_PKT_NUM; i++) + { + if (bcm_static_skb->pkt_use[i+MAX_STATIC_PKT_NUM] == 0) + break; + } + + if (i != MAX_STATIC_PKT_NUM) + { + bcm_static_skb->pkt_use[i+MAX_STATIC_PKT_NUM] = 1; + mutex_unlock(&bcm_static_skb->osl_pkt_sem); + skb = bcm_static_skb->skb_8k[i]; + skb->tail = skb->data + len; + skb->len = len; + + return skb; + } + + + + mutex_unlock(&bcm_static_skb->osl_pkt_sem); + printk("all static pkt in use!\n"); + return osl_pktget(osh, len); +} + + +void +osl_pktfree_static(osl_t *osh, void *p, bool send) +{ + int i; + + for (i = 0; i < MAX_STATIC_PKT_NUM*2; i++) + { + if (p == bcm_static_skb->skb_4k[i]) + { + mutex_lock(&bcm_static_skb->osl_pkt_sem); + bcm_static_skb->pkt_use[i] = 0; + mutex_unlock(&bcm_static_skb->osl_pkt_sem); + + + return; + } + } + return osl_pktfree(osh, p, send); +} +#endif +uint32 +osl_pci_read_config(osl_t *osh, uint offset, uint size) +{ + uint val = 0; + uint retry = PCI_CFG_RETRY; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + + ASSERT(size == 4); + + do { + pci_read_config_dword(osh->pdev, offset, &val); + if (val != 0xffffffff) + break; + } while (retry--); + + + return (val); +} + +void +osl_pci_write_config(osl_t *osh, uint offset, uint size, uint val) +{ + uint retry = PCI_CFG_RETRY; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + + ASSERT(size == 4); + + do { + pci_write_config_dword(osh->pdev, offset, val); + if (offset != PCI_BAR0_WIN) + break; + if (osl_pci_read_config(osh, offset, size) == val) + break; + } while (retry--); + +} + + +uint +osl_pci_bus(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + + return ((struct pci_dev *)osh->pdev)->bus->number; +} + + +uint +osl_pci_slot(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + + return PCI_SLOT(((struct pci_dev *)osh->pdev)->devfn); +} + +static void +osl_pcmcia_attr(osl_t *osh, uint offset, char *buf, int size, bool write) +{ +} + +void +osl_pcmcia_read_attr(osl_t *osh, uint offset, void *buf, int size) +{ + osl_pcmcia_attr(osh, offset, (char *) buf, size, FALSE); +} + +void +osl_pcmcia_write_attr(osl_t *osh, uint offset, void *buf, int size) +{ + osl_pcmcia_attr(osh, offset, (char *) buf, size, TRUE); +} + + + +void* +osl_malloc(osl_t *osh, uint size) +{ + void *addr; + gfp_t flags; + + if (osh) + ASSERT(osh->magic == OS_HANDLE_MAGIC); + +#ifdef DHD_USE_STATIC_BUF + if (bcm_static_buf) + { + int i = 0; + if ((size >= PAGE_SIZE)&&(size <= STATIC_BUF_SIZE)) + { + mutex_lock(&bcm_static_buf->static_sem); + + for (i = 0; i < MAX_STATIC_BUF_NUM; i++) + { + if (bcm_static_buf->buf_use[i] == 0) + break; + } + + if (i == MAX_STATIC_BUF_NUM) + { + mutex_unlock(&bcm_static_buf->static_sem); + printk("all static buff in use!\n"); + goto original; + } + + bcm_static_buf->buf_use[i] = 1; + mutex_unlock(&bcm_static_buf->static_sem); + + bzero(bcm_static_buf->buf_ptr+STATIC_BUF_SIZE*i, size); + if (osh) + osh->malloced += size; + + return ((void *)(bcm_static_buf->buf_ptr+STATIC_BUF_SIZE*i)); + } + } +original: +#endif + flags = (in_atomic()) ? GFP_ATOMIC : GFP_KERNEL; + if ((addr = kmalloc(size, flags)) == NULL) { + if (osh) + osh->failed++; + return (NULL); + } + if (osh) + osh->malloced += size; + + return (addr); +} + +void +osl_mfree(osl_t *osh, void *addr, uint size) +{ +#ifdef DHD_USE_STATIC_BUF + if (bcm_static_buf) + { + if ((addr > (void *)bcm_static_buf) && ((unsigned char *)addr + <= ((unsigned char *)bcm_static_buf + STATIC_BUF_TOTAL_LEN))) + { + int buf_idx = 0; + + buf_idx = ((unsigned char *)addr - bcm_static_buf->buf_ptr)/STATIC_BUF_SIZE; + + mutex_lock(&bcm_static_buf->static_sem); + bcm_static_buf->buf_use[buf_idx] = 0; + mutex_unlock(&bcm_static_buf->static_sem); + + if (osh) { + ASSERT(osh->magic == OS_HANDLE_MAGIC); + osh->malloced -= size; + } + return; + } + } +#endif + if (osh) { + ASSERT(osh->magic == OS_HANDLE_MAGIC); + osh->malloced -= size; + } + kfree(addr); +} + +uint +osl_malloced(osl_t *osh) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + return (osh->malloced); +} + +uint +osl_malloc_failed(osl_t *osh) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + return (osh->failed); +} + +void* +osl_dma_alloc_consistent(osl_t *osh, uint size, ulong *pap) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + return (pci_alloc_consistent(osh->pdev, size, (dma_addr_t*)pap)); +} + +void +osl_dma_free_consistent(osl_t *osh, void *va, uint size, ulong pa) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + pci_free_consistent(osh->pdev, size, va, (dma_addr_t)pa); +} + +uint +osl_dma_map(osl_t *osh, void *va, uint size, int direction) +{ + int dir; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + dir = (direction == DMA_TX)? PCI_DMA_TODEVICE: PCI_DMA_FROMDEVICE; + return (pci_map_single(osh->pdev, va, size, dir)); +} + +void +osl_dma_unmap(osl_t *osh, uint pa, uint size, int direction) +{ + int dir; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + dir = (direction == DMA_TX)? PCI_DMA_TODEVICE: PCI_DMA_FROMDEVICE; + pci_unmap_single(osh->pdev, (uint32)pa, size, dir); +} + + +void +osl_delay(uint usec) +{ + uint d; + + while (usec > 0) { + d = MIN(usec, 1000); + udelay(d); + usec -= d; + } +} + + + +void * +osl_pktdup(osl_t *osh, void *skb) +{ + void * p; + gfp_t flags; + + flags = (in_atomic()) ? GFP_ATOMIC : GFP_KERNEL; + if ((p = skb_clone((struct sk_buff*)skb, flags)) == NULL) + return NULL; + + + if (osh->pub.pkttag) + bzero((void*)((struct sk_buff *)p)->cb, OSL_PKTTAG_SZ); + + + osh->pub.pktalloced++; + return (p); +} diff --git a/drivers/net/wireless/bcm4329/miniopt.c b/drivers/net/wireless/bcm4329/miniopt.c new file mode 100644 index 0000000000000000000000000000000000000000..6a184a75f06b23fe4363ab2888ff8e6b8fdc30f2 --- /dev/null +++ b/drivers/net/wireless/bcm4329/miniopt.c @@ -0,0 +1,163 @@ +/* + * Description. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: miniopt.c,v 1.1.6.4 2009/09/25 00:32:01 Exp $ + */ + +/* ---- Include Files ---------------------------------------------------- */ + +#include +#include +#include +#include +#include "miniopt.h" + + +/* ---- Public Variables ------------------------------------------------- */ +/* ---- Private Constants and Types -------------------------------------- */ + + + +/* ---- Private Variables ------------------------------------------------ */ +/* ---- Private Function Prototypes -------------------------------------- */ +/* ---- Functions -------------------------------------------------------- */ + +/* ----------------------------------------------------------------------- */ +void +miniopt_init(miniopt_t *t, const char* name, const char* flags, bool longflags) +{ + static const char *null_flags = ""; + + memset(t, 0, sizeof(miniopt_t)); + t->name = name; + if (flags == NULL) + t->flags = null_flags; + else + t->flags = flags; + t->longflags = longflags; +} + + +/* ----------------------------------------------------------------------- */ +int +miniopt(miniopt_t *t, char **argv) +{ + int keylen; + char *p, *eq, *valstr, *endptr = NULL; + int err = 0; + + t->consumed = 0; + t->positional = FALSE; + memset(t->key, 0, MINIOPT_MAXKEY); + t->opt = '\0'; + t->valstr = NULL; + t->good_int = FALSE; + valstr = NULL; + + if (*argv == NULL) { + err = -1; + goto exit; + } + + p = *argv++; + t->consumed++; + + if (!t->opt_end && !strcmp(p, "--")) { + t->opt_end = TRUE; + if (*argv == NULL) { + err = -1; + goto exit; + } + p = *argv++; + t->consumed++; + } + + if (t->opt_end) { + t->positional = TRUE; + valstr = p; + } + else if (!strncmp(p, "--", 2)) { + eq = strchr(p, '='); + if (eq == NULL && !t->longflags) { + fprintf(stderr, + "%s: missing \" = \" in long param \"%s\"\n", t->name, p); + err = 1; + goto exit; + } + keylen = eq ? (eq - (p + 2)) : (int)strlen(p) - 2; + if (keylen > 63) keylen = 63; + memcpy(t->key, p + 2, keylen); + + if (eq) { + valstr = eq + 1; + if (*valstr == '\0') { + fprintf(stderr, + "%s: missing value after \" = \" in long param \"%s\"\n", + t->name, p); + err = 1; + goto exit; + } + } + } + else if (!strncmp(p, "-", 1)) { + t->opt = p[1]; + if (strlen(p) > 2) { + fprintf(stderr, + "%s: only single char options, error on param \"%s\"\n", + t->name, p); + err = 1; + goto exit; + } + if (strchr(t->flags, t->opt)) { + /* this is a flag option, no value expected */ + valstr = NULL; + } else { + if (*argv == NULL) { + fprintf(stderr, + "%s: missing value parameter after \"%s\"\n", t->name, p); + err = 1; + goto exit; + } + valstr = *argv; + argv++; + t->consumed++; + } + } else { + t->positional = TRUE; + valstr = p; + } + + /* parse valstr as int just in case */ + if (valstr) { + t->uval = (uint)strtoul(valstr, &endptr, 0); + t->val = (int)t->uval; + t->good_int = (*endptr == '\0'); + } + + t->valstr = valstr; + +exit: + if (err == 1) + t->opt = '?'; + + return err; +} diff --git a/drivers/net/wireless/bcm4329/sbutils.c b/drivers/net/wireless/bcm4329/sbutils.c new file mode 100644 index 0000000000000000000000000000000000000000..46cd51010b78bff1c16d580b4f684dfadc678ec5 --- /dev/null +++ b/drivers/net/wireless/bcm4329/sbutils.c @@ -0,0 +1,1004 @@ +/* + * Misc utility routines for accessing chip-specific features + * of the SiliconBackplane-based Broadcom chips. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: sbutils.c,v 1.662.4.10.2.7.4.2 2010/04/19 05:48:48 Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "siutils_priv.h" + +/* local prototypes */ +static uint _sb_coreidx(si_info_t *sii, uint32 sba); +static uint _sb_scan(si_info_t *sii, uint32 sba, void *regs, uint bus, uint32 sbba, + uint ncores); +static uint32 _sb_coresba(si_info_t *sii); +static void *_sb_setcoreidx(si_info_t *sii, uint coreidx); + +#define SET_SBREG(sii, r, mask, val) \ + W_SBREG((sii), (r), ((R_SBREG((sii), (r)) & ~(mask)) | (val))) +#define REGS2SB(va) (sbconfig_t*) ((int8*)(va) + SBCONFIGOFF) + +/* sonicsrev */ +#define SONICS_2_2 (SBIDL_RV_2_2 >> SBIDL_RV_SHIFT) +#define SONICS_2_3 (SBIDL_RV_2_3 >> SBIDL_RV_SHIFT) + +#define R_SBREG(sii, sbr) sb_read_sbreg((sii), (sbr)) +#define W_SBREG(sii, sbr, v) sb_write_sbreg((sii), (sbr), (v)) +#define AND_SBREG(sii, sbr, v) W_SBREG((sii), (sbr), (R_SBREG((sii), (sbr)) & (v))) +#define OR_SBREG(sii, sbr, v) W_SBREG((sii), (sbr), (R_SBREG((sii), (sbr)) | (v))) + +static uint32 +sb_read_sbreg(si_info_t *sii, volatile uint32 *sbr) +{ + uint8 tmp; + uint32 val, intr_val = 0; + + + /* + * compact flash only has 11 bits address, while we needs 12 bits address. + * MEM_SEG will be OR'd with other 11 bits address in hardware, + * so we program MEM_SEG with 12th bit when necessary(access sb regsiters). + * For normal PCMCIA bus(CFTable_regwinsz > 2k), do nothing special + */ + if (PCMCIA(sii)) { + INTR_OFF(sii, intr_val); + tmp = 1; + OSL_PCMCIA_WRITE_ATTR(sii->osh, MEM_SEG, &tmp, 1); + sbr = (volatile uint32 *)((uintptr)sbr & ~(1 << 11)); /* mask out bit 11 */ + } + + val = R_REG(sii->osh, sbr); + + if (PCMCIA(sii)) { + tmp = 0; + OSL_PCMCIA_WRITE_ATTR(sii->osh, MEM_SEG, &tmp, 1); + INTR_RESTORE(sii, intr_val); + } + + return (val); +} + +static void +sb_write_sbreg(si_info_t *sii, volatile uint32 *sbr, uint32 v) +{ + uint8 tmp; + volatile uint32 dummy; + uint32 intr_val = 0; + + + /* + * compact flash only has 11 bits address, while we needs 12 bits address. + * MEM_SEG will be OR'd with other 11 bits address in hardware, + * so we program MEM_SEG with 12th bit when necessary(access sb regsiters). + * For normal PCMCIA bus(CFTable_regwinsz > 2k), do nothing special + */ + if (PCMCIA(sii)) { + INTR_OFF(sii, intr_val); + tmp = 1; + OSL_PCMCIA_WRITE_ATTR(sii->osh, MEM_SEG, &tmp, 1); + sbr = (volatile uint32 *)((uintptr)sbr & ~(1 << 11)); /* mask out bit 11 */ + } + + if (BUSTYPE(sii->pub.bustype) == PCMCIA_BUS) { +#ifdef IL_BIGENDIAN + dummy = R_REG(sii->osh, sbr); + W_REG(sii->osh, ((volatile uint16 *)sbr + 1), (uint16)((v >> 16) & 0xffff)); + dummy = R_REG(sii->osh, sbr); + W_REG(sii->osh, (volatile uint16 *)sbr, (uint16)(v & 0xffff)); +#else + dummy = R_REG(sii->osh, sbr); + W_REG(sii->osh, (volatile uint16 *)sbr, (uint16)(v & 0xffff)); + dummy = R_REG(sii->osh, sbr); + W_REG(sii->osh, ((volatile uint16 *)sbr + 1), (uint16)((v >> 16) & 0xffff)); +#endif /* IL_BIGENDIAN */ + } else + W_REG(sii->osh, sbr, v); + + if (PCMCIA(sii)) { + tmp = 0; + OSL_PCMCIA_WRITE_ATTR(sii->osh, MEM_SEG, &tmp, 1); + INTR_RESTORE(sii, intr_val); + } +} + +uint +sb_coreid(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + return ((R_SBREG(sii, &sb->sbidhigh) & SBIDH_CC_MASK) >> SBIDH_CC_SHIFT); +} + +uint +sb_flag(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + return R_SBREG(sii, &sb->sbtpsflag) & SBTPS_NUM0_MASK; +} + +void +sb_setint(si_t *sih, int siflag) +{ + si_info_t *sii; + sbconfig_t *sb; + uint32 vec; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + if (siflag == -1) + vec = 0; + else + vec = 1 << siflag; + W_SBREG(sii, &sb->sbintvec, vec); +} + +/* return core index of the core with address 'sba' */ +static uint +_sb_coreidx(si_info_t *sii, uint32 sba) +{ + uint i; + + for (i = 0; i < sii->numcores; i ++) + if (sba == sii->common_info->coresba[i]) + return i; + return BADIDX; +} + +/* return core address of the current core */ +static uint32 +_sb_coresba(si_info_t *sii) +{ + uint32 sbaddr; + + + switch (BUSTYPE(sii->pub.bustype)) { + case SI_BUS: { + sbconfig_t *sb = REGS2SB(sii->curmap); + sbaddr = sb_base(R_SBREG(sii, &sb->sbadmatch0)); + break; + } + + case PCI_BUS: + sbaddr = OSL_PCI_READ_CONFIG(sii->osh, PCI_BAR0_WIN, sizeof(uint32)); + break; + + case PCMCIA_BUS: { + uint8 tmp = 0; + OSL_PCMCIA_READ_ATTR(sii->osh, PCMCIA_ADDR0, &tmp, 1); + sbaddr = (uint32)tmp << 12; + OSL_PCMCIA_READ_ATTR(sii->osh, PCMCIA_ADDR1, &tmp, 1); + sbaddr |= (uint32)tmp << 16; + OSL_PCMCIA_READ_ATTR(sii->osh, PCMCIA_ADDR2, &tmp, 1); + sbaddr |= (uint32)tmp << 24; + break; + } + + case SPI_BUS: + case SDIO_BUS: + sbaddr = (uint32)(uintptr)sii->curmap; + break; + + + default: + sbaddr = BADCOREADDR; + break; + } + + return sbaddr; +} + +uint +sb_corevendor(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + return ((R_SBREG(sii, &sb->sbidhigh) & SBIDH_VC_MASK) >> SBIDH_VC_SHIFT); +} + +uint +sb_corerev(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + uint sbidh; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + sbidh = R_SBREG(sii, &sb->sbidhigh); + + return (SBCOREREV(sbidh)); +} + +/* set core-specific control flags */ +void +sb_core_cflags_wo(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + sbconfig_t *sb; + uint32 w; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + ASSERT((val & ~mask) == 0); + + /* mask and set */ + w = (R_SBREG(sii, &sb->sbtmstatelow) & ~(mask << SBTML_SICF_SHIFT)) | + (val << SBTML_SICF_SHIFT); + W_SBREG(sii, &sb->sbtmstatelow, w); +} + +/* set/clear core-specific control flags */ +uint32 +sb_core_cflags(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + sbconfig_t *sb; + uint32 w; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + ASSERT((val & ~mask) == 0); + + /* mask and set */ + if (mask || val) { + w = (R_SBREG(sii, &sb->sbtmstatelow) & ~(mask << SBTML_SICF_SHIFT)) | + (val << SBTML_SICF_SHIFT); + W_SBREG(sii, &sb->sbtmstatelow, w); + } + + /* return the new value + * for write operation, the following readback ensures the completion of write opration. + */ + return (R_SBREG(sii, &sb->sbtmstatelow) >> SBTML_SICF_SHIFT); +} + +/* set/clear core-specific status flags */ +uint32 +sb_core_sflags(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + sbconfig_t *sb; + uint32 w; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + ASSERT((val & ~mask) == 0); + ASSERT((mask & ~SISF_CORE_BITS) == 0); + + /* mask and set */ + if (mask || val) { + w = (R_SBREG(sii, &sb->sbtmstatehigh) & ~(mask << SBTMH_SISF_SHIFT)) | + (val << SBTMH_SISF_SHIFT); + W_SBREG(sii, &sb->sbtmstatehigh, w); + } + + /* return the new value */ + return (R_SBREG(sii, &sb->sbtmstatehigh) >> SBTMH_SISF_SHIFT); +} + +bool +sb_iscoreup(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + return ((R_SBREG(sii, &sb->sbtmstatelow) & + (SBTML_RESET | SBTML_REJ_MASK | (SICF_CLOCK_EN << SBTML_SICF_SHIFT))) == + (SICF_CLOCK_EN << SBTML_SICF_SHIFT)); +} + +/* + * Switch to 'coreidx', issue a single arbitrary 32bit register mask&set operation, + * switch back to the original core, and return the new value. + * + * When using the silicon backplane, no fidleing with interrupts or core switches are needed. + * + * Also, when using pci/pcie, we can optimize away the core switching for pci registers + * and (on newer pci cores) chipcommon registers. + */ +uint +sb_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val) +{ + uint origidx = 0; + uint32 *r = NULL; + uint w; + uint intr_val = 0; + bool fast = FALSE; + si_info_t *sii; + + sii = SI_INFO(sih); + + ASSERT(GOODIDX(coreidx)); + ASSERT(regoff < SI_CORE_SIZE); + ASSERT((val & ~mask) == 0); + + if (coreidx >= SI_MAXCORES) + return 0; + + if (BUSTYPE(sii->pub.bustype) == SI_BUS) { + /* If internal bus, we can always get at everything */ + fast = TRUE; + /* map if does not exist */ + if (!sii->common_info->regs[coreidx]) { + sii->common_info->regs[coreidx] = + REG_MAP(sii->common_info->coresba[coreidx], SI_CORE_SIZE); + ASSERT(GOODREGS(sii->common_info->regs[coreidx])); + } + r = (uint32 *)((uchar *)sii->common_info->regs[coreidx] + regoff); + } else if (BUSTYPE(sii->pub.bustype) == PCI_BUS) { + /* If pci/pcie, we can get at pci/pcie regs and on newer cores to chipc */ + + if ((sii->common_info->coreid[coreidx] == CC_CORE_ID) && SI_FAST(sii)) { + /* Chipc registers are mapped at 12KB */ + + fast = TRUE; + r = (uint32 *)((char *)sii->curmap + PCI_16KB0_CCREGS_OFFSET + regoff); + } else if (sii->pub.buscoreidx == coreidx) { + /* pci registers are at either in the last 2KB of an 8KB window + * or, in pcie and pci rev 13 at 8KB + */ + fast = TRUE; + if (SI_FAST(sii)) + r = (uint32 *)((char *)sii->curmap + + PCI_16KB0_PCIREGS_OFFSET + regoff); + else + r = (uint32 *)((char *)sii->curmap + + ((regoff >= SBCONFIGOFF) ? + PCI_BAR0_PCISBR_OFFSET : PCI_BAR0_PCIREGS_OFFSET) + + regoff); + } + } + + if (!fast) { + INTR_OFF(sii, intr_val); + + /* save current core index */ + origidx = si_coreidx(&sii->pub); + + /* switch core */ + r = (uint32*) ((uchar*)sb_setcoreidx(&sii->pub, coreidx) + regoff); + } + ASSERT(r != NULL); + + /* mask and set */ + if (mask || val) { + if (regoff >= SBCONFIGOFF) { + w = (R_SBREG(sii, r) & ~mask) | val; + W_SBREG(sii, r, w); + } else { + w = (R_REG(sii->osh, r) & ~mask) | val; + W_REG(sii->osh, r, w); + } + } + + /* readback */ + if (regoff >= SBCONFIGOFF) + w = R_SBREG(sii, r); + else { + if ((CHIPID(sii->pub.chip) == BCM5354_CHIP_ID) && + (coreidx == SI_CC_IDX) && + (regoff == OFFSETOF(chipcregs_t, watchdog))) { + w = val; + } else + w = R_REG(sii->osh, r); + } + + if (!fast) { + /* restore core index */ + if (origidx != coreidx) + sb_setcoreidx(&sii->pub, origidx); + + INTR_RESTORE(sii, intr_val); + } + + return (w); +} + +/* Scan the enumeration space to find all cores starting from the given + * bus 'sbba'. Append coreid and other info to the lists in 'si'. 'sba' + * is the default core address at chip POR time and 'regs' is the virtual + * address that the default core is mapped at. 'ncores' is the number of + * cores expected on bus 'sbba'. It returns the total number of cores + * starting from bus 'sbba', inclusive. + */ +#define SB_MAXBUSES 2 +static uint +_sb_scan(si_info_t *sii, uint32 sba, void *regs, uint bus, uint32 sbba, uint numcores) +{ + uint next; + uint ncc = 0; + uint i; + + if (bus >= SB_MAXBUSES) { + SI_ERROR(("_sb_scan: bus 0x%08x at level %d is too deep to scan\n", sbba, bus)); + return 0; + } + SI_MSG(("_sb_scan: scan bus 0x%08x assume %u cores\n", sbba, numcores)); + + /* Scan all cores on the bus starting from core 0. + * Core addresses must be contiguous on each bus. + */ + for (i = 0, next = sii->numcores; i < numcores && next < SB_BUS_MAXCORES; i++, next++) { + sii->common_info->coresba[next] = sbba + (i * SI_CORE_SIZE); + + /* keep and reuse the initial register mapping */ + if ((BUSTYPE(sii->pub.bustype) == SI_BUS) && + (sii->common_info->coresba[next] == sba)) { + SI_MSG(("_sb_scan: reuse mapped regs %p for core %u\n", regs, next)); + sii->common_info->regs[next] = regs; + } + + /* change core to 'next' and read its coreid */ + sii->curmap = _sb_setcoreidx(sii, next); + sii->curidx = next; + + sii->common_info->coreid[next] = sb_coreid(&sii->pub); + + /* core specific processing... */ + /* chipc provides # cores */ + if (sii->common_info->coreid[next] == CC_CORE_ID) { + chipcregs_t *cc = (chipcregs_t *)sii->curmap; + uint32 ccrev = sb_corerev(&sii->pub); + + /* determine numcores - this is the total # cores in the chip */ + if (((ccrev == 4) || (ccrev >= 6))) + numcores = (R_REG(sii->osh, &cc->chipid) & CID_CC_MASK) >> + CID_CC_SHIFT; + else { + /* Older chips */ + uint chip = sii->pub.chip; + + if (chip == BCM4306_CHIP_ID) /* < 4306c0 */ + numcores = 6; + else if (chip == BCM4704_CHIP_ID) + numcores = 9; + else if (chip == BCM5365_CHIP_ID) + numcores = 7; + else { + SI_ERROR(("sb_chip2numcores: unsupported chip 0x%x\n", + chip)); + ASSERT(0); + numcores = 1; + } + } + SI_MSG(("_sb_scan: there are %u cores in the chip %s\n", numcores, + sii->pub.issim ? "QT" : "")); + } + /* scan bridged SB(s) and add results to the end of the list */ + else if (sii->common_info->coreid[next] == OCP_CORE_ID) { + sbconfig_t *sb = REGS2SB(sii->curmap); + uint32 nsbba = R_SBREG(sii, &sb->sbadmatch1); + uint nsbcc; + + sii->numcores = next + 1; + + if ((nsbba & 0xfff00000) != SI_ENUM_BASE) + continue; + nsbba &= 0xfffff000; + if (_sb_coreidx(sii, nsbba) != BADIDX) + continue; + + nsbcc = (R_SBREG(sii, &sb->sbtmstatehigh) & 0x000f0000) >> 16; + nsbcc = _sb_scan(sii, sba, regs, bus + 1, nsbba, nsbcc); + if (sbba == SI_ENUM_BASE) + numcores -= nsbcc; + ncc += nsbcc; + } + } + + SI_MSG(("_sb_scan: found %u cores on bus 0x%08x\n", i, sbba)); + + sii->numcores = i + ncc; + return sii->numcores; +} + +/* scan the sb enumerated space to identify all cores */ +void +sb_scan(si_t *sih, void *regs, uint devid) +{ + si_info_t *sii; + uint32 origsba; + + sii = SI_INFO(sih); + + /* Save the current core info and validate it later till we know + * for sure what is good and what is bad. + */ + origsba = _sb_coresba(sii); + + /* scan all SB(s) starting from SI_ENUM_BASE */ + sii->numcores = _sb_scan(sii, origsba, regs, 0, SI_ENUM_BASE, 1); +} + +/* + * This function changes logical "focus" to the indicated core; + * must be called with interrupts off. + * Moreover, callers should keep interrupts off during switching out of and back to d11 core + */ +void * +sb_setcoreidx(si_t *sih, uint coreidx) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + if (coreidx >= sii->numcores) + return (NULL); + + /* + * If the user has provided an interrupt mask enabled function, + * then assert interrupts are disabled before switching the core. + */ + ASSERT((sii->intrsenabled_fn == NULL) || !(*(sii)->intrsenabled_fn)((sii)->intr_arg)); + + sii->curmap = _sb_setcoreidx(sii, coreidx); + sii->curidx = coreidx; + + return (sii->curmap); +} + +/* This function changes the logical "focus" to the indicated core. + * Return the current core's virtual address. + */ +static void * +_sb_setcoreidx(si_info_t *sii, uint coreidx) +{ + uint32 sbaddr = sii->common_info->coresba[coreidx]; + void *regs; + + switch (BUSTYPE(sii->pub.bustype)) { + case SI_BUS: + /* map new one */ + if (!sii->common_info->regs[coreidx]) { + sii->common_info->regs[coreidx] = REG_MAP(sbaddr, SI_CORE_SIZE); + ASSERT(GOODREGS(sii->common_info->regs[coreidx])); + } + regs = sii->common_info->regs[coreidx]; + break; + + case PCI_BUS: + /* point bar0 window */ + OSL_PCI_WRITE_CONFIG(sii->osh, PCI_BAR0_WIN, 4, sbaddr); + regs = sii->curmap; + break; + + case PCMCIA_BUS: { + uint8 tmp = (sbaddr >> 12) & 0x0f; + OSL_PCMCIA_WRITE_ATTR(sii->osh, PCMCIA_ADDR0, &tmp, 1); + tmp = (sbaddr >> 16) & 0xff; + OSL_PCMCIA_WRITE_ATTR(sii->osh, PCMCIA_ADDR1, &tmp, 1); + tmp = (sbaddr >> 24) & 0xff; + OSL_PCMCIA_WRITE_ATTR(sii->osh, PCMCIA_ADDR2, &tmp, 1); + regs = sii->curmap; + break; + } + case SPI_BUS: + case SDIO_BUS: + /* map new one */ + if (!sii->common_info->regs[coreidx]) { + sii->common_info->regs[coreidx] = (void *)(uintptr)sbaddr; + ASSERT(GOODREGS(sii->common_info->regs[coreidx])); + } + regs = sii->common_info->regs[coreidx]; + break; + + + default: + ASSERT(0); + regs = NULL; + break; + } + + return regs; +} + +/* Return the address of sbadmatch0/1/2/3 register */ +static volatile uint32 * +sb_admatch(si_info_t *sii, uint asidx) +{ + sbconfig_t *sb; + volatile uint32 *addrm; + + sb = REGS2SB(sii->curmap); + + switch (asidx) { + case 0: + addrm = &sb->sbadmatch0; + break; + + case 1: + addrm = &sb->sbadmatch1; + break; + + case 2: + addrm = &sb->sbadmatch2; + break; + + case 3: + addrm = &sb->sbadmatch3; + break; + + default: + SI_ERROR(("%s: Address space index (%d) out of range\n", __FUNCTION__, asidx)); + return 0; + } + + return (addrm); +} + +/* Return the number of address spaces in current core */ +int +sb_numaddrspaces(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + /* + 1 because of enumeration space */ + return ((R_SBREG(sii, &sb->sbidlow) & SBIDL_AR_MASK) >> SBIDL_AR_SHIFT) + 1; +} + +/* Return the address of the nth address space in the current core */ +uint32 +sb_addrspace(si_t *sih, uint asidx) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + return (sb_base(R_SBREG(sii, sb_admatch(sii, asidx)))); +} + +/* Return the size of the nth address space in the current core */ +uint32 +sb_addrspacesize(si_t *sih, uint asidx) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + return (sb_size(R_SBREG(sii, sb_admatch(sii, asidx)))); +} + + +/* do buffered registers update */ +void +sb_commit(si_t *sih) +{ + si_info_t *sii; + uint origidx; + uint intr_val = 0; + + sii = SI_INFO(sih); + + origidx = sii->curidx; + ASSERT(GOODIDX(origidx)); + + INTR_OFF(sii, intr_val); + + /* switch over to chipcommon core if there is one, else use pci */ + if (sii->pub.ccrev != NOREV) { + chipcregs_t *ccregs = (chipcregs_t *)si_setcore(sih, CC_CORE_ID, 0); + + /* do the buffer registers update */ + W_REG(sii->osh, &ccregs->broadcastaddress, SB_COMMIT); + W_REG(sii->osh, &ccregs->broadcastdata, 0x0); + } else + ASSERT(0); + + /* restore core index */ + sb_setcoreidx(sih, origidx); + INTR_RESTORE(sii, intr_val); +} + +void +sb_core_disable(si_t *sih, uint32 bits) +{ + si_info_t *sii; + volatile uint32 dummy; + sbconfig_t *sb; + + sii = SI_INFO(sih); + + ASSERT(GOODREGS(sii->curmap)); + sb = REGS2SB(sii->curmap); + + /* if core is already in reset, just return */ + if (R_SBREG(sii, &sb->sbtmstatelow) & SBTML_RESET) + return; + + /* if clocks are not enabled, put into reset and return */ + if ((R_SBREG(sii, &sb->sbtmstatelow) & (SICF_CLOCK_EN << SBTML_SICF_SHIFT)) == 0) + goto disable; + + /* set target reject and spin until busy is clear (preserve core-specific bits) */ + OR_SBREG(sii, &sb->sbtmstatelow, SBTML_REJ); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); + SPINWAIT((R_SBREG(sii, &sb->sbtmstatehigh) & SBTMH_BUSY), 100000); + if (R_SBREG(sii, &sb->sbtmstatehigh) & SBTMH_BUSY) + SI_ERROR(("%s: target state still busy\n", __FUNCTION__)); + + if (R_SBREG(sii, &sb->sbidlow) & SBIDL_INIT) { + OR_SBREG(sii, &sb->sbimstate, SBIM_RJ); + dummy = R_SBREG(sii, &sb->sbimstate); + OSL_DELAY(1); + SPINWAIT((R_SBREG(sii, &sb->sbimstate) & SBIM_BY), 100000); + } + + /* set reset and reject while enabling the clocks */ + W_SBREG(sii, &sb->sbtmstatelow, + (((bits | SICF_FGC | SICF_CLOCK_EN) << SBTML_SICF_SHIFT) | + SBTML_REJ | SBTML_RESET)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(10); + + /* don't forget to clear the initiator reject bit */ + if (R_SBREG(sii, &sb->sbidlow) & SBIDL_INIT) + AND_SBREG(sii, &sb->sbimstate, ~SBIM_RJ); + +disable: + /* leave reset and reject asserted */ + W_SBREG(sii, &sb->sbtmstatelow, ((bits << SBTML_SICF_SHIFT) | SBTML_REJ | SBTML_RESET)); + OSL_DELAY(1); +} + +/* reset and re-enable a core + * inputs: + * bits - core specific bits that are set during and after reset sequence + * resetbits - core specific bits that are set only during reset sequence + */ +void +sb_core_reset(si_t *sih, uint32 bits, uint32 resetbits) +{ + si_info_t *sii; + sbconfig_t *sb; + volatile uint32 dummy; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curmap)); + sb = REGS2SB(sii->curmap); + + /* + * Must do the disable sequence first to work for arbitrary current core state. + */ + sb_core_disable(sih, (bits | resetbits)); + + /* + * Now do the initialization sequence. + */ + + /* set reset while enabling the clock and forcing them on throughout the core */ + W_SBREG(sii, &sb->sbtmstatelow, + (((bits | resetbits | SICF_FGC | SICF_CLOCK_EN) << SBTML_SICF_SHIFT) | + SBTML_RESET)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); + + if (R_SBREG(sii, &sb->sbtmstatehigh) & SBTMH_SERR) { + W_SBREG(sii, &sb->sbtmstatehigh, 0); + } + if ((dummy = R_SBREG(sii, &sb->sbimstate)) & (SBIM_IBE | SBIM_TO)) { + AND_SBREG(sii, &sb->sbimstate, ~(SBIM_IBE | SBIM_TO)); + } + + /* clear reset and allow it to propagate throughout the core */ + W_SBREG(sii, &sb->sbtmstatelow, + ((bits | resetbits | SICF_FGC | SICF_CLOCK_EN) << SBTML_SICF_SHIFT)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); + + /* leave clock enabled */ + W_SBREG(sii, &sb->sbtmstatelow, ((bits | SICF_CLOCK_EN) << SBTML_SICF_SHIFT)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); +} + +void +sb_core_tofixup(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + + if ((BUSTYPE(sii->pub.bustype) != PCI_BUS) || PCIE(sii) || + (PCI(sii) && (sii->pub.buscorerev >= 5))) + return; + + ASSERT(GOODREGS(sii->curmap)); + sb = REGS2SB(sii->curmap); + + if (BUSTYPE(sii->pub.bustype) == SI_BUS) { + SET_SBREG(sii, &sb->sbimconfiglow, + SBIMCL_RTO_MASK | SBIMCL_STO_MASK, + (0x5 << SBIMCL_RTO_SHIFT) | 0x3); + } else { + if (sb_coreid(sih) == PCI_CORE_ID) { + SET_SBREG(sii, &sb->sbimconfiglow, + SBIMCL_RTO_MASK | SBIMCL_STO_MASK, + (0x3 << SBIMCL_RTO_SHIFT) | 0x2); + } else { + SET_SBREG(sii, &sb->sbimconfiglow, (SBIMCL_RTO_MASK | SBIMCL_STO_MASK), 0); + } + } + + sb_commit(sih); +} + +/* + * Set the initiator timeout for the "master core". + * The master core is defined to be the core in control + * of the chip and so it issues accesses to non-memory + * locations (Because of dma *any* core can access memeory). + * + * The routine uses the bus to decide who is the master: + * SI_BUS => mips + * JTAG_BUS => chipc + * PCI_BUS => pci or pcie + * PCMCIA_BUS => pcmcia + * SDIO_BUS => pcmcia + * + * This routine exists so callers can disable initiator + * timeouts so accesses to very slow devices like otp + * won't cause an abort. The routine allows arbitrary + * settings of the service and request timeouts, though. + * + * Returns the timeout state before changing it or -1 + * on error. + */ + +#define TO_MASK (SBIMCL_RTO_MASK | SBIMCL_STO_MASK) + +uint32 +sb_set_initiator_to(si_t *sih, uint32 to, uint idx) +{ + si_info_t *sii; + uint origidx; + uint intr_val = 0; + uint32 tmp, ret = 0xffffffff; + sbconfig_t *sb; + + sii = SI_INFO(sih); + + if ((to & ~TO_MASK) != 0) + return ret; + + /* Figure out the master core */ + if (idx == BADIDX) { + switch (BUSTYPE(sii->pub.bustype)) { + case PCI_BUS: + idx = sii->pub.buscoreidx; + break; + case JTAG_BUS: + idx = SI_CC_IDX; + break; + case PCMCIA_BUS: + case SDIO_BUS: + idx = si_findcoreidx(sih, PCMCIA_CORE_ID, 0); + break; + case SI_BUS: + idx = si_findcoreidx(sih, MIPS33_CORE_ID, 0); + break; + default: + ASSERT(0); + } + if (idx == BADIDX) + return ret; + } + + INTR_OFF(sii, intr_val); + origidx = si_coreidx(sih); + + sb = REGS2SB(sb_setcoreidx(sih, idx)); + + tmp = R_SBREG(sii, &sb->sbimconfiglow); + ret = tmp & TO_MASK; + W_SBREG(sii, &sb->sbimconfiglow, (tmp & ~TO_MASK) | to); + + sb_commit(sih); + sb_setcoreidx(sih, origidx); + INTR_RESTORE(sii, intr_val); + return ret; +} + +uint32 +sb_base(uint32 admatch) +{ + uint32 base; + uint type; + + type = admatch & SBAM_TYPE_MASK; + ASSERT(type < 3); + + base = 0; + + if (type == 0) { + base = admatch & SBAM_BASE0_MASK; + } else if (type == 1) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + base = admatch & SBAM_BASE1_MASK; + } else if (type == 2) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + base = admatch & SBAM_BASE2_MASK; + } + + return (base); +} + +uint32 +sb_size(uint32 admatch) +{ + uint32 size; + uint type; + + type = admatch & SBAM_TYPE_MASK; + ASSERT(type < 3); + + size = 0; + + if (type == 0) { + size = 1 << (((admatch & SBAM_ADINT0_MASK) >> SBAM_ADINT0_SHIFT) + 1); + } else if (type == 1) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + size = 1 << (((admatch & SBAM_ADINT1_MASK) >> SBAM_ADINT1_SHIFT) + 1); + } else if (type == 2) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + size = 1 << (((admatch & SBAM_ADINT2_MASK) >> SBAM_ADINT2_SHIFT) + 1); + } + + return (size); +} diff --git a/drivers/net/wireless/bcm4329/siutils.c b/drivers/net/wireless/bcm4329/siutils.c new file mode 100644 index 0000000000000000000000000000000000000000..1814db0f9dd650d1364ad838c1d516b8b0190a2e --- /dev/null +++ b/drivers/net/wireless/bcm4329/siutils.c @@ -0,0 +1,1527 @@ +/* + * Misc utility routines for accessing chip-specific features + * of the SiliconBackplane-based Broadcom chips. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: siutils.c,v 1.662.4.4.4.16.4.28 2010/06/23 21:37:54 Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "siutils_priv.h" + +/* local prototypes */ +static si_info_t *si_doattach(si_info_t *sii, uint devid, osl_t *osh, void *regs, + uint bustype, void *sdh, char **vars, uint *varsz); +static bool si_buscore_prep(si_info_t *sii, uint bustype, uint devid, void *sdh); +static bool si_buscore_setup(si_info_t *sii, chipcregs_t *cc, uint bustype, uint32 savewin, + uint *origidx, void *regs); + + +/* global variable to indicate reservation/release of gpio's */ +static uint32 si_gpioreservation = 0; +static void *common_info_alloced = NULL; + +/* global flag to prevent shared resources from being initialized multiple times in si_attach() */ + +/* + * Allocate a si handle. + * devid - pci device id (used to determine chip#) + * osh - opaque OS handle + * regs - virtual address of initial core registers + * bustype - pci/pcmcia/sb/sdio/etc + * vars - pointer to a pointer area for "environment" variables + * varsz - pointer to int to return the size of the vars + */ +si_t * +si_attach(uint devid, osl_t *osh, void *regs, + uint bustype, void *sdh, char **vars, uint *varsz) +{ + si_info_t *sii; + + /* alloc si_info_t */ + if ((sii = MALLOC(osh, sizeof (si_info_t))) == NULL) { + SI_ERROR(("si_attach: malloc failed! malloced %d bytes\n", MALLOCED(osh))); + return (NULL); + } + + if (si_doattach(sii, devid, osh, regs, bustype, sdh, vars, varsz) == NULL) { + if (NULL != sii->common_info) + MFREE(osh, sii->common_info, sizeof(si_common_info_t)); + MFREE(osh, sii, sizeof(si_info_t)); + return (NULL); + } + sii->vars = vars ? *vars : NULL; + sii->varsz = varsz ? *varsz : 0; + + return (si_t *)sii; +} + +/* global kernel resource */ +static si_info_t ksii; + +static uint32 wd_msticks; /* watchdog timer ticks normalized to ms */ + +/* generic kernel variant of si_attach() */ +si_t * +si_kattach(osl_t *osh) +{ + static bool ksii_attached = FALSE; + + if (!ksii_attached) { + void *regs = REG_MAP(SI_ENUM_BASE, SI_CORE_SIZE); + + if (si_doattach(&ksii, BCM4710_DEVICE_ID, osh, regs, + SI_BUS, NULL, + osh != SI_OSH ? &ksii.vars : NULL, + osh != SI_OSH ? &ksii.varsz : NULL) == NULL) { + if (NULL != ksii.common_info) + MFREE(osh, ksii.common_info, sizeof(si_common_info_t)); + SI_ERROR(("si_kattach: si_doattach failed\n")); + REG_UNMAP(regs); + return NULL; + } + REG_UNMAP(regs); + + /* save ticks normalized to ms for si_watchdog_ms() */ + if (PMUCTL_ENAB(&ksii.pub)) { + /* based on 32KHz ILP clock */ + wd_msticks = 32; + } else { + wd_msticks = ALP_CLOCK / 1000; + } + + ksii_attached = TRUE; + SI_MSG(("si_kattach done. ccrev = %d, wd_msticks = %d\n", + ksii.pub.ccrev, wd_msticks)); + } + + return &ksii.pub; +} + + +static bool +si_buscore_prep(si_info_t *sii, uint bustype, uint devid, void *sdh) +{ + /* need to set memseg flag for CF card first before any sb registers access */ + if (BUSTYPE(bustype) == PCMCIA_BUS) + sii->memseg = TRUE; + + + if (BUSTYPE(bustype) == SDIO_BUS) { + int err; + uint8 clkset; + + /* Try forcing SDIO core to do ALPAvail request only */ + clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err); + if (!err) { + uint8 clkval; + + /* If register supported, wait for ALPAvail and then force ALP */ + clkval = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, NULL); + if ((clkval & ~SBSDIO_AVBITS) == clkset) { + SPINWAIT(((clkval = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, + SBSDIO_FUNC1_CHIPCLKCSR, NULL)), !SBSDIO_ALPAV(clkval)), + PMU_MAX_TRANSITION_DLY); + if (!SBSDIO_ALPAV(clkval)) { + SI_ERROR(("timeout on ALPAV wait, clkval 0x%02x\n", + clkval)); + return FALSE; + } + clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP; + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, + clkset, &err); + OSL_DELAY(65); + } + } + + /* Also, disable the extra SDIO pull-ups */ + bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SDIOPULLUP, 0, NULL); + } + + + return TRUE; +} + +static bool +si_buscore_setup(si_info_t *sii, chipcregs_t *cc, uint bustype, uint32 savewin, + uint *origidx, void *regs) +{ + bool pci, pcie; + uint i; + uint pciidx, pcieidx, pcirev, pcierev; + + cc = si_setcoreidx(&sii->pub, SI_CC_IDX); + ASSERT((uintptr)cc); + + /* get chipcommon rev */ + sii->pub.ccrev = (int)si_corerev(&sii->pub); + + /* get chipcommon chipstatus */ + if (sii->pub.ccrev >= 11) + sii->pub.chipst = R_REG(sii->osh, &cc->chipstatus); + + /* get chipcommon capabilites */ + sii->pub.cccaps = R_REG(sii->osh, &cc->capabilities); + + /* get pmu rev and caps */ + if (sii->pub.cccaps & CC_CAP_PMU) { + sii->pub.pmucaps = R_REG(sii->osh, &cc->pmucapabilities); + sii->pub.pmurev = sii->pub.pmucaps & PCAP_REV_MASK; + } + + SI_MSG(("Chipc: rev %d, caps 0x%x, chipst 0x%x pmurev %d, pmucaps 0x%x\n", + sii->pub.ccrev, sii->pub.cccaps, sii->pub.chipst, sii->pub.pmurev, + sii->pub.pmucaps)); + + /* figure out bus/orignal core idx */ + sii->pub.buscoretype = NODEV_CORE_ID; + sii->pub.buscorerev = NOREV; + sii->pub.buscoreidx = BADIDX; + + pci = pcie = FALSE; + pcirev = pcierev = NOREV; + pciidx = pcieidx = BADIDX; + + for (i = 0; i < sii->numcores; i++) { + uint cid, crev; + + si_setcoreidx(&sii->pub, i); + cid = si_coreid(&sii->pub); + crev = si_corerev(&sii->pub); + + /* Display cores found */ + SI_MSG(("CORE[%d]: id 0x%x rev %d base 0x%x regs 0x%p\n", + i, cid, crev, sii->common_info->coresba[i], sii->common_info->regs[i])); + + if (BUSTYPE(bustype) == PCI_BUS) { + if (cid == PCI_CORE_ID) { + pciidx = i; + pcirev = crev; + pci = TRUE; + } else if (cid == PCIE_CORE_ID) { + pcieidx = i; + pcierev = crev; + pcie = TRUE; + } + } else if ((BUSTYPE(bustype) == PCMCIA_BUS) && + (cid == PCMCIA_CORE_ID)) { + sii->pub.buscorerev = crev; + sii->pub.buscoretype = cid; + sii->pub.buscoreidx = i; + } + else if (((BUSTYPE(bustype) == SDIO_BUS) || + (BUSTYPE(bustype) == SPI_BUS)) && + ((cid == PCMCIA_CORE_ID) || + (cid == SDIOD_CORE_ID))) { + sii->pub.buscorerev = crev; + sii->pub.buscoretype = cid; + sii->pub.buscoreidx = i; + } + + /* find the core idx before entering this func. */ + if ((savewin && (savewin == sii->common_info->coresba[i])) || + (regs == sii->common_info->regs[i])) + *origidx = i; + } + + + SI_MSG(("Buscore id/type/rev %d/0x%x/%d\n", sii->pub.buscoreidx, sii->pub.buscoretype, + sii->pub.buscorerev)); + + if (BUSTYPE(sii->pub.bustype) == SI_BUS && (CHIPID(sii->pub.chip) == BCM4712_CHIP_ID) && + (sii->pub.chippkg != BCM4712LARGE_PKG_ID) && (sii->pub.chiprev <= 3)) + OR_REG(sii->osh, &cc->slow_clk_ctl, SCC_SS_XTAL); + + + /* Make sure any on-chip ARM is off (in case strapping is wrong), or downloaded code was + * already running. + */ + if ((BUSTYPE(bustype) == SDIO_BUS) || (BUSTYPE(bustype) == SPI_BUS)) { + if (si_setcore(&sii->pub, ARM7S_CORE_ID, 0) || + si_setcore(&sii->pub, ARMCM3_CORE_ID, 0)) + si_core_disable(&sii->pub, 0); + } + + /* return to the original core */ + si_setcoreidx(&sii->pub, *origidx); + + return TRUE; +} + + + +static si_info_t * +si_doattach(si_info_t *sii, uint devid, osl_t *osh, void *regs, + uint bustype, void *sdh, char **vars, uint *varsz) +{ + struct si_pub *sih = &sii->pub; + uint32 w, savewin; + chipcregs_t *cc; + char *pvars = NULL; + uint origidx; + + ASSERT(GOODREGS(regs)); + + bzero((uchar*)sii, sizeof(si_info_t)); + + + { + if (NULL == (common_info_alloced = (void *)MALLOC(osh, sizeof(si_common_info_t)))) { + SI_ERROR(("si_doattach: malloc failed! malloced %dbytes\n", MALLOCED(osh))); + return (NULL); + } + bzero((uchar*)(common_info_alloced), sizeof(si_common_info_t)); + } + sii->common_info = (si_common_info_t *)common_info_alloced; + sii->common_info->attach_count++; + + savewin = 0; + + sih->buscoreidx = BADIDX; + + sii->curmap = regs; + sii->sdh = sdh; + sii->osh = osh; + + + /* find Chipcommon address */ + if (bustype == PCI_BUS) { + savewin = OSL_PCI_READ_CONFIG(sii->osh, PCI_BAR0_WIN, sizeof(uint32)); + if (!GOODCOREADDR(savewin, SI_ENUM_BASE)) + savewin = SI_ENUM_BASE; + OSL_PCI_WRITE_CONFIG(sii->osh, PCI_BAR0_WIN, 4, SI_ENUM_BASE); + cc = (chipcregs_t *)regs; + } else + if ((bustype == SDIO_BUS) || (bustype == SPI_BUS)) { + cc = (chipcregs_t *)sii->curmap; + } else { + cc = (chipcregs_t *)REG_MAP(SI_ENUM_BASE, SI_CORE_SIZE); + } + + sih->bustype = bustype; + if (bustype != BUSTYPE(bustype)) { + SI_ERROR(("si_doattach: bus type %d does not match configured bus type %d\n", + bustype, BUSTYPE(bustype))); + return NULL; + } + + /* bus/core/clk setup for register access */ + if (!si_buscore_prep(sii, bustype, devid, sdh)) { + SI_ERROR(("si_doattach: si_core_clk_prep failed %d\n", bustype)); + return NULL; + } + + /* ChipID recognition. + * We assume we can read chipid at offset 0 from the regs arg. + * If we add other chiptypes (or if we need to support old sdio hosts w/o chipcommon), + * some way of recognizing them needs to be added here. + */ + w = R_REG(osh, &cc->chipid); + sih->socitype = (w & CID_TYPE_MASK) >> CID_TYPE_SHIFT; + /* Might as wll fill in chip id rev & pkg */ + sih->chip = w & CID_ID_MASK; + sih->chiprev = (w & CID_REV_MASK) >> CID_REV_SHIFT; + sih->chippkg = (w & CID_PKG_MASK) >> CID_PKG_SHIFT; + if ((CHIPID(sih->chip) == BCM4329_CHIP_ID) && (sih->chippkg != BCM4329_289PIN_PKG_ID)) + sih->chippkg = BCM4329_182PIN_PKG_ID; + sih->issim = IS_SIM(sih->chippkg); + + /* scan for cores */ + if (CHIPTYPE(sii->pub.socitype) == SOCI_SB) { + SI_MSG(("Found chip type SB (0x%08x)\n", w)); + sb_scan(&sii->pub, regs, devid); + } else if (CHIPTYPE(sii->pub.socitype) == SOCI_AI) { + SI_MSG(("Found chip type AI (0x%08x)\n", w)); + /* pass chipc address instead of original core base */ + ai_scan(&sii->pub, (void *)cc, devid); + } else { + SI_ERROR(("Found chip of unkown type (0x%08x)\n", w)); + return NULL; + } + /* no cores found, bail out */ + if (sii->numcores == 0) { + SI_ERROR(("si_doattach: could not find any cores\n")); + return NULL; + } + /* bus/core/clk setup */ + origidx = SI_CC_IDX; + if (!si_buscore_setup(sii, cc, bustype, savewin, &origidx, regs)) { + SI_ERROR(("si_doattach: si_buscore_setup failed\n")); + return NULL; + } + + pvars = NULL; + + + + if (sii->pub.ccrev >= 20) { + cc = (chipcregs_t *)si_setcore(sih, CC_CORE_ID, 0); + W_REG(osh, &cc->gpiopullup, 0); + W_REG(osh, &cc->gpiopulldown, 0); + si_setcoreidx(sih, origidx); + } + + /* Skip PMU initialization from the Dongle Host. + * Firmware will take care of it when it comes up. + */ + + + + return (sii); +} + +/* may be called with core in reset */ +void +si_detach(si_t *sih) +{ + si_info_t *sii; + uint idx; + + sii = SI_INFO(sih); + + if (sii == NULL) + return; + + if (BUSTYPE(sih->bustype) == SI_BUS) + for (idx = 0; idx < SI_MAXCORES; idx++) + if (sii->common_info->regs[idx]) { + REG_UNMAP(sii->common_info->regs[idx]); + sii->common_info->regs[idx] = NULL; + } + + + if (1 == sii->common_info->attach_count--) { + MFREE(sii->osh, sii->common_info, sizeof(si_common_info_t)); + common_info_alloced = NULL; + } + +#if !defined(BCMBUSTYPE) || (BCMBUSTYPE == SI_BUS) + if (sii != &ksii) +#endif /* !BCMBUSTYPE || (BCMBUSTYPE == SI_BUS) */ + MFREE(sii->osh, sii, sizeof(si_info_t)); +} + +void * +si_osh(si_t *sih) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + return sii->osh; +} + +void +si_setosh(si_t *sih, osl_t *osh) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + if (sii->osh != NULL) { + SI_ERROR(("osh is already set....\n")); + ASSERT(!sii->osh); + } + sii->osh = osh; +} + +/* register driver interrupt disabling and restoring callback functions */ +void +si_register_intr_callback(si_t *sih, void *intrsoff_fn, void *intrsrestore_fn, + void *intrsenabled_fn, void *intr_arg) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + sii->intr_arg = intr_arg; + sii->intrsoff_fn = (si_intrsoff_t)intrsoff_fn; + sii->intrsrestore_fn = (si_intrsrestore_t)intrsrestore_fn; + sii->intrsenabled_fn = (si_intrsenabled_t)intrsenabled_fn; + /* save current core id. when this function called, the current core + * must be the core which provides driver functions(il, et, wl, etc.) + */ + sii->dev_coreid = sii->common_info->coreid[sii->curidx]; +} + +void +si_deregister_intr_callback(si_t *sih) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + sii->intrsoff_fn = NULL; +} + +uint +si_intflag(si_t *sih) +{ + si_info_t *sii = SI_INFO(sih); + if (CHIPTYPE(sih->socitype) == SOCI_SB) { + sbconfig_t *ccsbr = (sbconfig_t *)((uintptr)((ulong) + (sii->common_info->coresba[SI_CC_IDX]) + SBCONFIGOFF)); + return R_REG(sii->osh, &ccsbr->sbflagst); + } else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return R_REG(sii->osh, ((uint32 *)(uintptr) + (sii->common_info->oob_router + OOB_STATUSA))); + else { + ASSERT(0); + return 0; + } +} + +uint +si_flag(si_t *sih) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_flag(sih); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_flag(sih); + else { + ASSERT(0); + return 0; + } +} + +void +si_setint(si_t *sih, int siflag) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + sb_setint(sih, siflag); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + ai_setint(sih, siflag); + else + ASSERT(0); +} + +uint +si_coreid(si_t *sih) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + return sii->common_info->coreid[sii->curidx]; +} + +uint +si_coreidx(si_t *sih) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + return sii->curidx; +} + +/* return the core-type instantiation # of the current core */ +uint +si_coreunit(si_t *sih) +{ + si_info_t *sii; + uint idx; + uint coreid; + uint coreunit; + uint i; + + sii = SI_INFO(sih); + coreunit = 0; + + idx = sii->curidx; + + ASSERT(GOODREGS(sii->curmap)); + coreid = si_coreid(sih); + + /* count the cores of our type */ + for (i = 0; i < idx; i++) + if (sii->common_info->coreid[i] == coreid) + coreunit++; + + return (coreunit); +} + +uint +si_corevendor(si_t *sih) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_corevendor(sih); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_corevendor(sih); + else { + ASSERT(0); + return 0; + } +} + +bool +si_backplane64(si_t *sih) +{ + return ((sih->cccaps & CC_CAP_BKPLN64) != 0); +} + +uint +si_corerev(si_t *sih) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_corerev(sih); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_corerev(sih); + else { + ASSERT(0); + return 0; + } +} + +/* return index of coreid or BADIDX if not found */ +uint +si_findcoreidx(si_t *sih, uint coreid, uint coreunit) +{ + si_info_t *sii; + uint found; + uint i; + + sii = SI_INFO(sih); + + found = 0; + + for (i = 0; i < sii->numcores; i++) + if (sii->common_info->coreid[i] == coreid) { + if (found == coreunit) + return (i); + found++; + } + + return (BADIDX); +} + +/* return list of found cores */ +uint +si_corelist(si_t *sih, uint coreid[]) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + bcopy((uchar*)sii->common_info->coreid, (uchar*)coreid, (sii->numcores * sizeof(uint))); + return (sii->numcores); +} + +/* return current register mapping */ +void * +si_coreregs(si_t *sih) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curmap)); + + return (sii->curmap); +} + +/* + * This function changes logical "focus" to the indicated core; + * must be called with interrupts off. + * Moreover, callers should keep interrupts off during switching out of and back to d11 core + */ +void * +si_setcore(si_t *sih, uint coreid, uint coreunit) +{ + uint idx; + + idx = si_findcoreidx(sih, coreid, coreunit); + if (!GOODIDX(idx)) + return (NULL); + + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_setcoreidx(sih, idx); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_setcoreidx(sih, idx); + else { + ASSERT(0); + return NULL; + } +} + +void * +si_setcoreidx(si_t *sih, uint coreidx) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_setcoreidx(sih, coreidx); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_setcoreidx(sih, coreidx); + else { + ASSERT(0); + return NULL; + } +} + +/* Turn off interrupt as required by sb_setcore, before switch core */ +void *si_switch_core(si_t *sih, uint coreid, uint *origidx, uint *intr_val) +{ + void *cc; + si_info_t *sii; + + sii = SI_INFO(sih); + + INTR_OFF(sii, *intr_val); + *origidx = sii->curidx; + cc = si_setcore(sih, coreid, 0); + ASSERT(cc != NULL); + + return cc; +} + +/* restore coreidx and restore interrupt */ +void si_restore_core(si_t *sih, uint coreid, uint intr_val) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + si_setcoreidx(sih, coreid); + INTR_RESTORE(sii, intr_val); +} + +int +si_numaddrspaces(si_t *sih) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_numaddrspaces(sih); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_numaddrspaces(sih); + else { + ASSERT(0); + return 0; + } +} + +uint32 +si_addrspace(si_t *sih, uint asidx) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_addrspace(sih, asidx); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_addrspace(sih, asidx); + else { + ASSERT(0); + return 0; + } +} + +uint32 +si_addrspacesize(si_t *sih, uint asidx) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_addrspacesize(sih, asidx); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_addrspacesize(sih, asidx); + else { + ASSERT(0); + return 0; + } +} + +uint32 +si_core_cflags(si_t *sih, uint32 mask, uint32 val) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_core_cflags(sih, mask, val); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_core_cflags(sih, mask, val); + else { + ASSERT(0); + return 0; + } +} + +void +si_core_cflags_wo(si_t *sih, uint32 mask, uint32 val) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + sb_core_cflags_wo(sih, mask, val); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + ai_core_cflags_wo(sih, mask, val); + else + ASSERT(0); +} + +uint32 +si_core_sflags(si_t *sih, uint32 mask, uint32 val) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_core_sflags(sih, mask, val); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_core_sflags(sih, mask, val); + else { + ASSERT(0); + return 0; + } +} + +bool +si_iscoreup(si_t *sih) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_iscoreup(sih); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_iscoreup(sih); + else { + ASSERT(0); + return FALSE; + } +} + +void +si_write_wrapperreg(si_t *sih, uint32 offset, uint32 val) +{ + /* only for 4319, no requirement for SOCI_SB */ + if (CHIPTYPE(sih->socitype) == SOCI_AI) { + ai_write_wrap_reg(sih, offset, val); + } + else + return; + + return; +} + +uint +si_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + return sb_corereg(sih, coreidx, regoff, mask, val); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + return ai_corereg(sih, coreidx, regoff, mask, val); + else { + ASSERT(0); + return 0; + } +} + +void +si_core_disable(si_t *sih, uint32 bits) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + sb_core_disable(sih, bits); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + ai_core_disable(sih, bits); +} + +void +si_core_reset(si_t *sih, uint32 bits, uint32 resetbits) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + sb_core_reset(sih, bits, resetbits); + else if (CHIPTYPE(sih->socitype) == SOCI_AI) + ai_core_reset(sih, bits, resetbits); +} + +void +si_core_tofixup(si_t *sih) +{ + if (CHIPTYPE(sih->socitype) == SOCI_SB) + sb_core_tofixup(sih); +} + +/* Run bist on current core. Caller needs to take care of core-specific bist hazards */ +int +si_corebist(si_t *sih) +{ + uint32 cflags; + int result = 0; + + /* Read core control flags */ + cflags = si_core_cflags(sih, 0, 0); + + /* Set bist & fgc */ + si_core_cflags(sih, 0, (SICF_BIST_EN | SICF_FGC)); + + /* Wait for bist done */ + SPINWAIT(((si_core_sflags(sih, 0, 0) & SISF_BIST_DONE) == 0), 100000); + + if (si_core_sflags(sih, 0, 0) & SISF_BIST_ERROR) + result = BCME_ERROR; + + /* Reset core control flags */ + si_core_cflags(sih, 0xffff, cflags); + + return result; +} + +static uint32 +factor6(uint32 x) +{ + switch (x) { + case CC_F6_2: return 2; + case CC_F6_3: return 3; + case CC_F6_4: return 4; + case CC_F6_5: return 5; + case CC_F6_6: return 6; + case CC_F6_7: return 7; + default: return 0; + } +} + +/* calculate the speed the SI would run at given a set of clockcontrol values */ +uint32 +si_clock_rate(uint32 pll_type, uint32 n, uint32 m) +{ + uint32 n1, n2, clock, m1, m2, m3, mc; + + n1 = n & CN_N1_MASK; + n2 = (n & CN_N2_MASK) >> CN_N2_SHIFT; + + if (pll_type == PLL_TYPE6) { + if (m & CC_T6_MMASK) + return CC_T6_M1; + else + return CC_T6_M0; + } else if ((pll_type == PLL_TYPE1) || + (pll_type == PLL_TYPE3) || + (pll_type == PLL_TYPE4) || + (pll_type == PLL_TYPE7)) { + n1 = factor6(n1); + n2 += CC_F5_BIAS; + } else if (pll_type == PLL_TYPE2) { + n1 += CC_T2_BIAS; + n2 += CC_T2_BIAS; + ASSERT((n1 >= 2) && (n1 <= 7)); + ASSERT((n2 >= 5) && (n2 <= 23)); + } else if (pll_type == PLL_TYPE5) { + return (100000000); + } else + ASSERT(0); + /* PLL types 3 and 7 use BASE2 (25Mhz) */ + if ((pll_type == PLL_TYPE3) || + (pll_type == PLL_TYPE7)) { + clock = CC_CLOCK_BASE2 * n1 * n2; + } else + clock = CC_CLOCK_BASE1 * n1 * n2; + + if (clock == 0) + return 0; + + m1 = m & CC_M1_MASK; + m2 = (m & CC_M2_MASK) >> CC_M2_SHIFT; + m3 = (m & CC_M3_MASK) >> CC_M3_SHIFT; + mc = (m & CC_MC_MASK) >> CC_MC_SHIFT; + + if ((pll_type == PLL_TYPE1) || + (pll_type == PLL_TYPE3) || + (pll_type == PLL_TYPE4) || + (pll_type == PLL_TYPE7)) { + m1 = factor6(m1); + if ((pll_type == PLL_TYPE1) || (pll_type == PLL_TYPE3)) + m2 += CC_F5_BIAS; + else + m2 = factor6(m2); + m3 = factor6(m3); + + switch (mc) { + case CC_MC_BYPASS: return (clock); + case CC_MC_M1: return (clock / m1); + case CC_MC_M1M2: return (clock / (m1 * m2)); + case CC_MC_M1M2M3: return (clock / (m1 * m2 * m3)); + case CC_MC_M1M3: return (clock / (m1 * m3)); + default: return (0); + } + } else { + ASSERT(pll_type == PLL_TYPE2); + + m1 += CC_T2_BIAS; + m2 += CC_T2M2_BIAS; + m3 += CC_T2_BIAS; + ASSERT((m1 >= 2) && (m1 <= 7)); + ASSERT((m2 >= 3) && (m2 <= 10)); + ASSERT((m3 >= 2) && (m3 <= 7)); + + if ((mc & CC_T2MC_M1BYP) == 0) + clock /= m1; + if ((mc & CC_T2MC_M2BYP) == 0) + clock /= m2; + if ((mc & CC_T2MC_M3BYP) == 0) + clock /= m3; + + return (clock); + } +} + + +/* set chip watchdog reset timer to fire in 'ticks' */ +void +si_watchdog(si_t *sih, uint ticks) +{ + if (PMUCTL_ENAB(sih)) { + + if ((sih->chip == BCM4319_CHIP_ID) && (sih->chiprev == 0) && (ticks != 0)) { + si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, clk_ctl_st), ~0, 0x2); + si_setcore(sih, USB20D_CORE_ID, 0); + si_core_disable(sih, 1); + si_setcore(sih, CC_CORE_ID, 0); + } + + if (ticks == 1) + ticks = 2; + si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, pmuwatchdog), ~0, ticks); + } else { + /* instant NMI */ + si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, watchdog), ~0, ticks); + } +} + +#if !defined(BCMBUSTYPE) || (BCMBUSTYPE == SI_BUS) +/* trigger watchdog reset after ms milliseconds */ +void +si_watchdog_ms(si_t *sih, uint32 ms) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + si_watchdog(sih, wd_msticks * ms); +} +#endif + + + +/* initialize the sdio core */ +void +si_sdio_init(si_t *sih) +{ + si_info_t *sii = SI_INFO(sih); + + if (((sih->buscoretype == PCMCIA_CORE_ID) && (sih->buscorerev >= 8)) || + (sih->buscoretype == SDIOD_CORE_ID)) { + uint idx; + sdpcmd_regs_t *sdpregs; + + /* get the current core index */ + idx = sii->curidx; + ASSERT(idx == si_findcoreidx(sih, D11_CORE_ID, 0)); + + /* switch to sdio core */ + if (!(sdpregs = (sdpcmd_regs_t *)si_setcore(sih, PCMCIA_CORE_ID, 0))) + sdpregs = (sdpcmd_regs_t *)si_setcore(sih, SDIOD_CORE_ID, 0); + ASSERT(sdpregs); + + SI_MSG(("si_sdio_init: For PCMCIA/SDIO Corerev %d, enable ints from core %d " + "through SD core %d (%p)\n", + sih->buscorerev, idx, sii->curidx, sdpregs)); + + /* enable backplane error and core interrupts */ + W_REG(sii->osh, &sdpregs->hostintmask, I_SBINT); + W_REG(sii->osh, &sdpregs->sbintmask, (I_SB_SERR | I_SB_RESPERR | (1 << idx))); + + /* switch back to previous core */ + si_setcoreidx(sih, idx); + } + + /* enable interrupts */ + bcmsdh_intr_enable(sii->sdh); + +} + + +/* change logical "focus" to the gpio core for optimized access */ +void * +si_gpiosetcore(si_t *sih) +{ + return (si_setcoreidx(sih, SI_CC_IDX)); +} + +/* mask&set gpiocontrol bits */ +uint32 +si_gpiocontrol(si_t *sih, uint32 mask, uint32 val, uint8 priority) +{ + uint regoff; + + regoff = 0; + + /* gpios could be shared on router platforms + * ignore reservation if it's high priority (e.g., test apps) + */ + if ((priority != GPIO_HI_PRIORITY) && + (BUSTYPE(sih->bustype) == SI_BUS) && (val || mask)) { + mask = priority ? (si_gpioreservation & mask) : + ((si_gpioreservation | mask) & ~(si_gpioreservation)); + val &= mask; + } + + regoff = OFFSETOF(chipcregs_t, gpiocontrol); + return (si_corereg(sih, SI_CC_IDX, regoff, mask, val)); +} + +/* mask&set gpio output enable bits */ +uint32 +si_gpioouten(si_t *sih, uint32 mask, uint32 val, uint8 priority) +{ + uint regoff; + + regoff = 0; + + /* gpios could be shared on router platforms + * ignore reservation if it's high priority (e.g., test apps) + */ + if ((priority != GPIO_HI_PRIORITY) && + (BUSTYPE(sih->bustype) == SI_BUS) && (val || mask)) { + mask = priority ? (si_gpioreservation & mask) : + ((si_gpioreservation | mask) & ~(si_gpioreservation)); + val &= mask; + } + + regoff = OFFSETOF(chipcregs_t, gpioouten); + return (si_corereg(sih, SI_CC_IDX, regoff, mask, val)); +} + +/* mask&set gpio output bits */ +uint32 +si_gpioout(si_t *sih, uint32 mask, uint32 val, uint8 priority) +{ + uint regoff; + + regoff = 0; + + /* gpios could be shared on router platforms + * ignore reservation if it's high priority (e.g., test apps) + */ + if ((priority != GPIO_HI_PRIORITY) && + (BUSTYPE(sih->bustype) == SI_BUS) && (val || mask)) { + mask = priority ? (si_gpioreservation & mask) : + ((si_gpioreservation | mask) & ~(si_gpioreservation)); + val &= mask; + } + + regoff = OFFSETOF(chipcregs_t, gpioout); + return (si_corereg(sih, SI_CC_IDX, regoff, mask, val)); +} + +/* reserve one gpio */ +uint32 +si_gpioreserve(si_t *sih, uint32 gpio_bitmask, uint8 priority) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + /* only cores on SI_BUS share GPIO's and only applcation users need to + * reserve/release GPIO + */ + if ((BUSTYPE(sih->bustype) != SI_BUS) || (!priority)) { + ASSERT((BUSTYPE(sih->bustype) == SI_BUS) && (priority)); + return -1; + } + /* make sure only one bit is set */ + if ((!gpio_bitmask) || ((gpio_bitmask) & (gpio_bitmask - 1))) { + ASSERT((gpio_bitmask) && !((gpio_bitmask) & (gpio_bitmask - 1))); + return -1; + } + + /* already reserved */ + if (si_gpioreservation & gpio_bitmask) + return -1; + /* set reservation */ + si_gpioreservation |= gpio_bitmask; + + return si_gpioreservation; +} + +/* release one gpio */ +/* + * releasing the gpio doesn't change the current value on the GPIO last write value + * persists till some one overwrites it + */ + +uint32 +si_gpiorelease(si_t *sih, uint32 gpio_bitmask, uint8 priority) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + /* only cores on SI_BUS share GPIO's and only applcation users need to + * reserve/release GPIO + */ + if ((BUSTYPE(sih->bustype) != SI_BUS) || (!priority)) { + ASSERT((BUSTYPE(sih->bustype) == SI_BUS) && (priority)); + return -1; + } + /* make sure only one bit is set */ + if ((!gpio_bitmask) || ((gpio_bitmask) & (gpio_bitmask - 1))) { + ASSERT((gpio_bitmask) && !((gpio_bitmask) & (gpio_bitmask - 1))); + return -1; + } + + /* already released */ + if (!(si_gpioreservation & gpio_bitmask)) + return -1; + + /* clear reservation */ + si_gpioreservation &= ~gpio_bitmask; + + return si_gpioreservation; +} + +/* return the current gpioin register value */ +uint32 +si_gpioin(si_t *sih) +{ + si_info_t *sii; + uint regoff; + + sii = SI_INFO(sih); + regoff = 0; + + regoff = OFFSETOF(chipcregs_t, gpioin); + return (si_corereg(sih, SI_CC_IDX, regoff, 0, 0)); +} + +/* mask&set gpio interrupt polarity bits */ +uint32 +si_gpiointpolarity(si_t *sih, uint32 mask, uint32 val, uint8 priority) +{ + si_info_t *sii; + uint regoff; + + sii = SI_INFO(sih); + regoff = 0; + + /* gpios could be shared on router platforms */ + if ((BUSTYPE(sih->bustype) == SI_BUS) && (val || mask)) { + mask = priority ? (si_gpioreservation & mask) : + ((si_gpioreservation | mask) & ~(si_gpioreservation)); + val &= mask; + } + + regoff = OFFSETOF(chipcregs_t, gpiointpolarity); + return (si_corereg(sih, SI_CC_IDX, regoff, mask, val)); +} + +/* mask&set gpio interrupt mask bits */ +uint32 +si_gpiointmask(si_t *sih, uint32 mask, uint32 val, uint8 priority) +{ + si_info_t *sii; + uint regoff; + + sii = SI_INFO(sih); + regoff = 0; + + /* gpios could be shared on router platforms */ + if ((BUSTYPE(sih->bustype) == SI_BUS) && (val || mask)) { + mask = priority ? (si_gpioreservation & mask) : + ((si_gpioreservation | mask) & ~(si_gpioreservation)); + val &= mask; + } + + regoff = OFFSETOF(chipcregs_t, gpiointmask); + return (si_corereg(sih, SI_CC_IDX, regoff, mask, val)); +} + +/* assign the gpio to an led */ +uint32 +si_gpioled(si_t *sih, uint32 mask, uint32 val) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + if (sih->ccrev < 16) + return -1; + + /* gpio led powersave reg */ + return (si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, gpiotimeroutmask), mask, val)); +} + +/* mask&set gpio timer val */ +uint32 +si_gpiotimerval(si_t *sih, uint32 mask, uint32 gpiotimerval) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + if (sih->ccrev < 16) + return -1; + + return (si_corereg(sih, SI_CC_IDX, + OFFSETOF(chipcregs_t, gpiotimerval), mask, gpiotimerval)); +} + +uint32 +si_gpiopull(si_t *sih, bool updown, uint32 mask, uint32 val) +{ + si_info_t *sii; + uint offs; + + sii = SI_INFO(sih); + if (sih->ccrev < 20) + return -1; + + offs = (updown ? OFFSETOF(chipcregs_t, gpiopulldown) : OFFSETOF(chipcregs_t, gpiopullup)); + return (si_corereg(sih, SI_CC_IDX, offs, mask, val)); +} + +uint32 +si_gpioevent(si_t *sih, uint regtype, uint32 mask, uint32 val) +{ + si_info_t *sii; + uint offs; + + sii = SI_INFO(sih); + if (sih->ccrev < 11) + return -1; + + if (regtype == GPIO_REGEVT) + offs = OFFSETOF(chipcregs_t, gpioevent); + else if (regtype == GPIO_REGEVT_INTMSK) + offs = OFFSETOF(chipcregs_t, gpioeventintmask); + else if (regtype == GPIO_REGEVT_INTPOL) + offs = OFFSETOF(chipcregs_t, gpioeventintpolarity); + else + return -1; + + return (si_corereg(sih, SI_CC_IDX, offs, mask, val)); +} + +void * +si_gpio_handler_register(si_t *sih, uint32 event, + bool level, gpio_handler_t cb, void *arg) +{ + si_info_t *sii; + gpioh_item_t *gi; + + ASSERT(event); + ASSERT(cb != NULL); + + sii = SI_INFO(sih); + if (sih->ccrev < 11) + return NULL; + + if ((gi = MALLOC(sii->osh, sizeof(gpioh_item_t))) == NULL) + return NULL; + + bzero(gi, sizeof(gpioh_item_t)); + gi->event = event; + gi->handler = cb; + gi->arg = arg; + gi->level = level; + + gi->next = sii->gpioh_head; + sii->gpioh_head = gi; + + return (void *)(gi); +} + +void +si_gpio_handler_unregister(si_t *sih, void *gpioh) +{ + si_info_t *sii; + gpioh_item_t *p, *n; + + sii = SI_INFO(sih); + if (sih->ccrev < 11) + return; + + ASSERT(sii->gpioh_head != NULL); + if ((void*)sii->gpioh_head == gpioh) { + sii->gpioh_head = sii->gpioh_head->next; + MFREE(sii->osh, gpioh, sizeof(gpioh_item_t)); + return; + } else { + p = sii->gpioh_head; + n = p->next; + while (n) { + if ((void*)n == gpioh) { + p->next = n->next; + MFREE(sii->osh, gpioh, sizeof(gpioh_item_t)); + return; + } + p = n; + n = n->next; + } + } + + ASSERT(0); /* Not found in list */ +} + +void +si_gpio_handler_process(si_t *sih) +{ + si_info_t *sii; + gpioh_item_t *h; + uint32 status; + uint32 level = si_gpioin(sih); + uint32 edge = si_gpioevent(sih, GPIO_REGEVT, 0, 0); + + sii = SI_INFO(sih); + for (h = sii->gpioh_head; h != NULL; h = h->next) { + if (h->handler) { + status = (h->level ? level : edge); + + if (status & h->event) + h->handler(status, h->arg); + } + } + + si_gpioevent(sih, GPIO_REGEVT, edge, edge); /* clear edge-trigger status */ +} + +uint32 +si_gpio_int_enable(si_t *sih, bool enable) +{ + si_info_t *sii; + uint offs; + + sii = SI_INFO(sih); + if (sih->ccrev < 11) + return -1; + + offs = OFFSETOF(chipcregs_t, intmask); + return (si_corereg(sih, SI_CC_IDX, offs, CI_GPIO, (enable ? CI_GPIO : 0))); +} + + +/* Return the RAM size of the SOCRAM core */ +uint32 +si_socram_size(si_t *sih) +{ + si_info_t *sii; + uint origidx; + uint intr_val = 0; + + sbsocramregs_t *regs; + bool wasup; + uint corerev; + uint32 coreinfo; + uint memsize = 0; + + sii = SI_INFO(sih); + + /* Block ints and save current core */ + INTR_OFF(sii, intr_val); + origidx = si_coreidx(sih); + + /* Switch to SOCRAM core */ + if (!(regs = si_setcore(sih, SOCRAM_CORE_ID, 0))) + goto done; + + /* Get info for determining size */ + if (!(wasup = si_iscoreup(sih))) + si_core_reset(sih, 0, 0); + corerev = si_corerev(sih); + coreinfo = R_REG(sii->osh, ®s->coreinfo); + + /* Calculate size from coreinfo based on rev */ + if (corerev == 0) + memsize = 1 << (16 + (coreinfo & SRCI_MS0_MASK)); + else if (corerev < 3) { + memsize = 1 << (SR_BSZ_BASE + (coreinfo & SRCI_SRBSZ_MASK)); + memsize *= (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT; + } else { + uint nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT; + uint bsz = (coreinfo & SRCI_SRBSZ_MASK); + uint lss = (coreinfo & SRCI_LSS_MASK) >> SRCI_LSS_SHIFT; + if (lss != 0) + nb --; + memsize = nb * (1 << (bsz + SR_BSZ_BASE)); + if (lss != 0) + memsize += (1 << ((lss - 1) + SR_BSZ_BASE)); + } + + /* Return to previous state and core */ + if (!wasup) + si_core_disable(sih, 0); + si_setcoreidx(sih, origidx); + +done: + INTR_RESTORE(sii, intr_val); + + return memsize; +} + + +void +si_btcgpiowar(si_t *sih) +{ + si_info_t *sii; + uint origidx; + uint intr_val = 0; + chipcregs_t *cc; + + sii = SI_INFO(sih); + + /* Make sure that there is ChipCommon core present && + * UART_TX is strapped to 1 + */ + if (!(sih->cccaps & CC_CAP_UARTGPIO)) + return; + + /* si_corereg cannot be used as we have to guarantee 8-bit read/writes */ + INTR_OFF(sii, intr_val); + + origidx = si_coreidx(sih); + + cc = (chipcregs_t *)si_setcore(sih, CC_CORE_ID, 0); + ASSERT(cc != NULL); + + W_REG(sii->osh, &cc->uart0mcr, R_REG(sii->osh, &cc->uart0mcr) | 0x04); + + /* restore the original index */ + si_setcoreidx(sih, origidx); + + INTR_RESTORE(sii, intr_val); +} + +/* check if the device is removed */ +bool +si_deviceremoved(si_t *sih) +{ + uint32 w; + si_info_t *sii; + + sii = SI_INFO(sih); + + switch (BUSTYPE(sih->bustype)) { + case PCI_BUS: + ASSERT(sii->osh != NULL); + w = OSL_PCI_READ_CONFIG(sii->osh, PCI_CFG_VID, sizeof(uint32)); + if ((w & 0xFFFF) != VENDOR_BROADCOM) + return TRUE; + else + return FALSE; + default: + return FALSE; + } + return FALSE; +} diff --git a/drivers/net/wireless/bcm4329/siutils_priv.h b/drivers/net/wireless/bcm4329/siutils_priv.h new file mode 100644 index 0000000000000000000000000000000000000000..e8ad7e50958a81d129d16da324ba18336e9a403c --- /dev/null +++ b/drivers/net/wireless/bcm4329/siutils_priv.h @@ -0,0 +1,213 @@ +/* + * Include file private to the SOC Interconnect support files. + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: siutils_priv.h,v 1.3.10.5.4.2 2009/09/22 13:28:16 Exp $ + */ + +#ifndef _siutils_priv_h_ +#define _siutils_priv_h_ + +/* debug/trace */ +#define SI_ERROR(args) + +#define SI_MSG(args) + +#define IS_SIM(chippkg) ((chippkg == HDLSIM_PKG_ID) || (chippkg == HWSIM_PKG_ID)) + +typedef uint32 (*si_intrsoff_t)(void *intr_arg); +typedef void (*si_intrsrestore_t)(void *intr_arg, uint32 arg); +typedef bool (*si_intrsenabled_t)(void *intr_arg); + +typedef struct gpioh_item { + void *arg; + bool level; + gpio_handler_t handler; + uint32 event; + struct gpioh_item *next; +} gpioh_item_t; + +/* misc si info needed by some of the routines */ +typedef struct si_common_info { + void *regs[SI_MAXCORES]; /* other regs va */ + void *regs2[SI_MAXCORES]; /* va of each core second register set (usbh20) */ + uint coreid[SI_MAXCORES]; /* id of each core */ + uint32 cia[SI_MAXCORES]; /* erom cia entry for each core */ + uint32 cib[SI_MAXCORES]; /* erom cia entry for each core */ + uint32 coresba_size[SI_MAXCORES]; /* backplane address space size */ + uint32 coresba2_size[SI_MAXCORES]; /* second address space size */ + uint32 coresba[SI_MAXCORES]; /* backplane address of each core */ + uint32 coresba2[SI_MAXCORES]; /* address of each core second register set (usbh20) */ + void *wrappers[SI_MAXCORES]; /* other cores wrapper va */ + uint32 wrapba[SI_MAXCORES]; /* address of controlling wrapper */ + uint32 oob_router; /* oob router registers for axi */ + uint8 attach_count; +} si_common_info_t; + +typedef struct si_info { + struct si_pub pub; /* back plane public state (must be first field) */ + + void *osh; /* osl os handle */ + void *sdh; /* bcmsdh handle */ + void *pch; /* PCI/E core handle */ + uint dev_coreid; /* the core provides driver functions */ + void *intr_arg; /* interrupt callback function arg */ + si_intrsoff_t intrsoff_fn; /* turns chip interrupts off */ + si_intrsrestore_t intrsrestore_fn; /* restore chip interrupts */ + si_intrsenabled_t intrsenabled_fn; /* check if interrupts are enabled */ + + + gpioh_item_t *gpioh_head; /* GPIO event handlers list */ + + bool memseg; /* flag to toggle MEM_SEG register */ + + char *vars; + uint varsz; + + void *curmap; /* current regs va */ + + uint curidx; /* current core index */ + uint numcores; /* # discovered cores */ + void *curwrap; /* current wrapper va */ + si_common_info_t *common_info; /* Common information for all the cores in a chip */ +} si_info_t; + +#define SI_INFO(sih) (si_info_t *)(uintptr)sih + +#define GOODCOREADDR(x, b) (((x) >= (b)) && ((x) < ((b) + SI_MAXCORES * SI_CORE_SIZE)) && \ + ISALIGNED((x), SI_CORE_SIZE)) +#define GOODREGS(regs) ((regs) != NULL && ISALIGNED((uintptr)(regs), SI_CORE_SIZE)) +#define BADCOREADDR 0 +#define GOODIDX(idx) (((uint)idx) < SI_MAXCORES) +#define BADIDX (SI_MAXCORES + 1) +#define NOREV -1 /* Invalid rev */ + +#define PCI(si) ((BUSTYPE((si)->pub.bustype) == PCI_BUS) && \ + ((si)->pub.buscoretype == PCI_CORE_ID)) +#define PCIE(si) ((BUSTYPE((si)->pub.bustype) == PCI_BUS) && \ + ((si)->pub.buscoretype == PCIE_CORE_ID)) +#define PCMCIA(si) ((BUSTYPE((si)->pub.bustype) == PCMCIA_BUS) && ((si)->memseg == TRUE)) + +/* Newer chips can access PCI/PCIE and CC core without requiring to change + * PCI BAR0 WIN + */ +#define SI_FAST(si) (((si)->pub.buscoretype == PCIE_CORE_ID) || \ + (((si)->pub.buscoretype == PCI_CORE_ID) && (si)->pub.buscorerev >= 13)) + +#define PCIEREGS(si) (((char *)((si)->curmap) + PCI_16KB0_PCIREGS_OFFSET)) +#define CCREGS_FAST(si) (((char *)((si)->curmap) + PCI_16KB0_CCREGS_OFFSET)) + +/* + * Macros to disable/restore function core(D11, ENET, ILINE20, etc) interrupts before/ + * after core switching to avoid invalid register accesss inside ISR. + */ +#define INTR_OFF(si, intr_val) \ + if ((si)->intrsoff_fn && (si)->common_info->coreid[(si)->curidx] == (si)->dev_coreid) { \ + intr_val = (*(si)->intrsoff_fn)((si)->intr_arg); } +#define INTR_RESTORE(si, intr_val) \ + if ((si)->intrsrestore_fn && (si)->common_info->coreid[(si)->curidx] == (si)->dev_coreid) {\ + (*(si)->intrsrestore_fn)((si)->intr_arg, intr_val); } + +/* dynamic clock control defines */ +#define LPOMINFREQ 25000 /* low power oscillator min */ +#define LPOMAXFREQ 43000 /* low power oscillator max */ +#define XTALMINFREQ 19800000 /* 20 MHz - 1% */ +#define XTALMAXFREQ 20200000 /* 20 MHz + 1% */ +#define PCIMINFREQ 25000000 /* 25 MHz */ +#define PCIMAXFREQ 34000000 /* 33 MHz + fudge */ + +#define ILP_DIV_5MHZ 0 /* ILP = 5 MHz */ +#define ILP_DIV_1MHZ 4 /* ILP = 1 MHz */ + +#define PCI_FORCEHT(si) \ + (((PCIE(si)) && (si->pub.chip == BCM4311_CHIP_ID) && ((si->pub.chiprev <= 1))) || \ + ((PCI(si) || PCIE(si)) && (si->pub.chip == BCM4321_CHIP_ID))) + +/* GPIO Based LED powersave defines */ +#define DEFAULT_GPIO_ONTIME 10 /* Default: 10% on */ +#define DEFAULT_GPIO_OFFTIME 90 /* Default: 10% on */ + +#ifndef DEFAULT_GPIOTIMERVAL +#define DEFAULT_GPIOTIMERVAL ((DEFAULT_GPIO_ONTIME << GPIO_ONTIME_SHIFT) | DEFAULT_GPIO_OFFTIME) +#endif + +/* Silicon Backplane externs */ +extern void sb_scan(si_t *sih, void *regs, uint devid); +extern uint sb_coreid(si_t *sih); +extern uint sb_flag(si_t *sih); +extern void sb_setint(si_t *sih, int siflag); +extern uint sb_corevendor(si_t *sih); +extern uint sb_corerev(si_t *sih); +extern uint sb_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val); +extern bool sb_iscoreup(si_t *sih); +extern void *sb_setcoreidx(si_t *sih, uint coreidx); +extern uint32 sb_core_cflags(si_t *sih, uint32 mask, uint32 val); +extern void sb_core_cflags_wo(si_t *sih, uint32 mask, uint32 val); +extern uint32 sb_core_sflags(si_t *sih, uint32 mask, uint32 val); +extern void sb_commit(si_t *sih); +extern uint32 sb_base(uint32 admatch); +extern uint32 sb_size(uint32 admatch); +extern void sb_core_reset(si_t *sih, uint32 bits, uint32 resetbits); +extern void sb_core_tofixup(si_t *sih); +extern void sb_core_disable(si_t *sih, uint32 bits); +extern uint32 sb_addrspace(si_t *sih, uint asidx); +extern uint32 sb_addrspacesize(si_t *sih, uint asidx); +extern int sb_numaddrspaces(si_t *sih); + +extern uint32 sb_set_initiator_to(si_t *sih, uint32 to, uint idx); + + + +/* Wake-on-wireless-LAN (WOWL) */ +extern bool sb_pci_pmecap(si_t *sih); +struct osl_info; +extern bool sb_pci_fastpmecap(struct osl_info *osh); +extern bool sb_pci_pmeclr(si_t *sih); +extern void sb_pci_pmeen(si_t *sih); +extern uint sb_pcie_readreg(void *sih, uint addrtype, uint offset); + +/* AMBA Interconnect exported externs */ +extern si_t *ai_attach(uint pcidev, osl_t *osh, void *regs, uint bustype, + void *sdh, char **vars, uint *varsz); +extern si_t *ai_kattach(osl_t *osh); +extern void ai_scan(si_t *sih, void *regs, uint devid); + +extern uint ai_flag(si_t *sih); +extern void ai_setint(si_t *sih, int siflag); +extern uint ai_coreidx(si_t *sih); +extern uint ai_corevendor(si_t *sih); +extern uint ai_corerev(si_t *sih); +extern bool ai_iscoreup(si_t *sih); +extern void *ai_setcoreidx(si_t *sih, uint coreidx); +extern uint32 ai_core_cflags(si_t *sih, uint32 mask, uint32 val); +extern void ai_core_cflags_wo(si_t *sih, uint32 mask, uint32 val); +extern uint32 ai_core_sflags(si_t *sih, uint32 mask, uint32 val); +extern uint ai_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val); +extern void ai_core_reset(si_t *sih, uint32 bits, uint32 resetbits); +extern void ai_core_disable(si_t *sih, uint32 bits); +extern int ai_numaddrspaces(si_t *sih); +extern uint32 ai_addrspace(si_t *sih, uint asidx); +extern uint32 ai_addrspacesize(si_t *sih, uint asidx); +extern void ai_write_wrap_reg(si_t *sih, uint32 offset, uint32 val); + + +#endif /* _siutils_priv_h_ */ diff --git a/drivers/net/wireless/bcm4329/wl_iw.c b/drivers/net/wireless/bcm4329/wl_iw.c new file mode 100644 index 0000000000000000000000000000000000000000..434e584f830c2c6b5972a749afac356aac26c3fa --- /dev/null +++ b/drivers/net/wireless/bcm4329/wl_iw.c @@ -0,0 +1,8455 @@ +/* + * Linux Wireless Extensions support + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: wl_iw.c,v 1.51.4.9.2.6.4.142.4.78 2011/02/11 21:27:52 Exp $ + */ + + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +typedef void wlc_info_t; +typedef void wl_info_t; +typedef const struct si_pub si_t; +#include + +#include +#include +#include +#define WL_ERROR(x) printf x +#define WL_TRACE(x) +#define WL_ASSOC(x) +#define WL_INFORM(x) +#define WL_WSEC(x) +#define WL_SCAN(x) +#define WL_PNO(x) +#define WL_TRACE_COEX(x) + +#include + + + +#ifndef IW_ENCODE_ALG_SM4 +#define IW_ENCODE_ALG_SM4 0x20 +#endif + +#ifndef IW_AUTH_WAPI_ENABLED +#define IW_AUTH_WAPI_ENABLED 0x20 +#endif + +#ifndef IW_AUTH_WAPI_VERSION_1 +#define IW_AUTH_WAPI_VERSION_1 0x00000008 +#endif + +#ifndef IW_AUTH_CIPHER_SMS4 +#define IW_AUTH_CIPHER_SMS4 0x00000020 +#endif + +#ifndef IW_AUTH_KEY_MGMT_WAPI_PSK +#define IW_AUTH_KEY_MGMT_WAPI_PSK 4 +#endif + +#ifndef IW_AUTH_KEY_MGMT_WAPI_CERT +#define IW_AUTH_KEY_MGMT_WAPI_CERT 8 +#endif + + +#define IW_WSEC_ENABLED(wsec) ((wsec) & (WEP_ENABLED | TKIP_ENABLED | AES_ENABLED | SMS4_ENABLED)) + +#include +#include + +#define WL_IW_USE_ISCAN 1 +#define ENABLE_ACTIVE_PASSIVE_SCAN_SUPPRESS 1 + +#if defined(SOFTAP) +#define WL_SOFTAP(x) printk x +static struct net_device *priv_dev; +static bool ap_cfg_running = FALSE; +bool ap_fw_loaded = FALSE; +static long ap_cfg_pid = -1; +struct net_device *ap_net_dev = NULL; +struct semaphore ap_eth_sema; +static struct completion ap_cfg_exited; +static int wl_iw_set_ap_security(struct net_device *dev, struct ap_profile *ap); +static int wl_iw_softap_deassoc_stations(struct net_device *dev, u8 *mac); +#endif + +#define WL_IW_IOCTL_CALL(func_call) \ + do { \ + func_call; \ + } while (0) + +static int g_onoff = G_WLAN_SET_ON; +wl_iw_extra_params_t g_wl_iw_params; +static struct mutex wl_cache_lock; + +extern bool wl_iw_conn_status_str(uint32 event_type, uint32 status, + uint32 reason, char* stringBuf, uint buflen); +#include +extern void dhd_customer_gpio_wlan_ctrl(int onoff); +extern uint dhd_dev_reset(struct net_device *dev, uint8 flag); +extern void dhd_dev_init_ioctl(struct net_device *dev); +int dev_iw_write_cfg1_bss_var(struct net_device *dev, int val); + +uint wl_msg_level = WL_ERROR_VAL; + +#define MAX_WLIW_IOCTL_LEN 1024 + + +#if defined(IL_BIGENDIAN) +#include +#define htod32(i) (bcmswap32(i)) +#define htod16(i) (bcmswap16(i)) +#define dtoh32(i) (bcmswap32(i)) +#define dtoh16(i) (bcmswap16(i)) +#define htodchanspec(i) htod16(i) +#define dtohchanspec(i) dtoh16(i) +#else +#define htod32(i) i +#define htod16(i) i +#define dtoh32(i) i +#define dtoh16(i) i +#define htodchanspec(i) i +#define dtohchanspec(i) i +#endif + +#ifdef CONFIG_WIRELESS_EXT + +extern struct iw_statistics *dhd_get_wireless_stats(struct net_device *dev); +extern int dhd_wait_pend8021x(struct net_device *dev); +#endif + +#if WIRELESS_EXT < 19 +#define IW_IOCTL_IDX(cmd) ((cmd) - SIOCIWFIRST) +#define IW_EVENT_IDX(cmd) ((cmd) - IWEVFIRST) +#endif + +static void *g_scan = NULL; +static volatile uint g_scan_specified_ssid; +static wlc_ssid_t g_specific_ssid; + +static wlc_ssid_t g_ssid; + +bool btcoex_is_sco_active(struct net_device *dev); +static wl_iw_ss_cache_ctrl_t g_ss_cache_ctrl; +#if defined(CONFIG_FIRST_SCAN) +static volatile uint g_first_broadcast_scan; +static volatile uint g_first_counter_scans; +#define MAX_ALLOWED_BLOCK_SCAN_FROM_FIRST_SCAN 3 +#endif + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) +#define DAEMONIZE(a) daemonize(a); \ + allow_signal(SIGKILL); \ + allow_signal(SIGTERM); +#else +#define RAISE_RX_SOFTIRQ() \ + cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ) +#define DAEMONIZE(a) daemonize(); \ + do { if (a) \ + strncpy(current->comm, a, MIN(sizeof(current->comm), (strlen(a) + 1))); \ + } while (0); +#endif + +#if defined(WL_IW_USE_ISCAN) +#if !defined(CSCAN) +static void wl_iw_free_ss_cache(void); +static int wl_iw_run_ss_cache_timer(int kick_off); +#endif +#if defined(CONFIG_FIRST_SCAN) +int wl_iw_iscan_set_scan_broadcast_prep(struct net_device *dev, uint flag); +#endif +static int dev_wlc_bufvar_set(struct net_device *dev, char *name, char *buf, int len); +#define ISCAN_STATE_IDLE 0 +#define ISCAN_STATE_SCANING 1 + +#define WLC_IW_ISCAN_MAXLEN 2048 +typedef struct iscan_buf { + struct iscan_buf * next; + char iscan_buf[WLC_IW_ISCAN_MAXLEN]; +} iscan_buf_t; + +typedef struct iscan_info { + struct net_device *dev; + struct timer_list timer; + uint32 timer_ms; + uint32 timer_on; + int iscan_state; + iscan_buf_t * list_hdr; + iscan_buf_t * list_cur; + + + long sysioc_pid; + struct semaphore sysioc_sem; + struct completion sysioc_exited; + + uint32 scan_flag; +#if defined CSCAN + char ioctlbuf[WLC_IOCTL_MEDLEN]; +#else + char ioctlbuf[WLC_IOCTL_SMLEN]; +#endif + wl_iscan_params_t *iscan_ex_params_p; + int iscan_ex_param_size; +} iscan_info_t; +#define COEX_DHCP 1 + +#define BT_DHCP_eSCO_FIX +#define BT_DHCP_USE_FLAGS +#define BT_DHCP_OPPORTUNITY_WINDOW_TIME 2500 +#define BT_DHCP_FLAG_FORCE_TIME 5500 +static void wl_iw_bt_flag_set(struct net_device *dev, bool set); +static void wl_iw_bt_release(void); + +typedef enum bt_coex_status { + BT_DHCP_IDLE = 0, + BT_DHCP_START, + BT_DHCP_OPPORTUNITY_WINDOW, + BT_DHCP_FLAG_FORCE_TIMEOUT +} coex_status_t; + +typedef struct bt_info { + struct net_device *dev; + struct timer_list timer; + uint32 timer_ms; + uint32 timer_on; + bool dhcp_done; + int bt_state; + + long bt_pid; + struct semaphore bt_sem; + struct completion bt_exited; +} bt_info_t; + +bt_info_t *g_bt = NULL; +static void wl_iw_bt_timerfunc(ulong data); +iscan_info_t *g_iscan = NULL; +static void wl_iw_timerfunc(ulong data); +static void wl_iw_set_event_mask(struct net_device *dev); +static int +wl_iw_iscan(iscan_info_t *iscan, wlc_ssid_t *ssid, uint16 action); +#endif +static int +wl_iw_set_scan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +); + +#ifndef CSCAN +static int +wl_iw_get_scan( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +); + +static uint +wl_iw_get_scan_prep( + wl_scan_results_t *list, + struct iw_request_info *info, + char *extra, + short max_size +); +#endif + +static void swap_key_from_BE( + wl_wsec_key_t *key +) +{ + key->index = htod32(key->index); + key->len = htod32(key->len); + key->algo = htod32(key->algo); + key->flags = htod32(key->flags); + key->rxiv.hi = htod32(key->rxiv.hi); + key->rxiv.lo = htod16(key->rxiv.lo); + key->iv_initialized = htod32(key->iv_initialized); +} + +static void swap_key_to_BE( + wl_wsec_key_t *key +) +{ + key->index = dtoh32(key->index); + key->len = dtoh32(key->len); + key->algo = dtoh32(key->algo); + key->flags = dtoh32(key->flags); + key->rxiv.hi = dtoh32(key->rxiv.hi); + key->rxiv.lo = dtoh16(key->rxiv.lo); + key->iv_initialized = dtoh32(key->iv_initialized); +} + +static int +dev_wlc_ioctl( + struct net_device *dev, + int cmd, + void *arg, + int len +) +{ + struct ifreq ifr; + wl_ioctl_t ioc; + mm_segment_t fs; + int ret = -EINVAL; + + if (!dev) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return ret; + } + + net_os_wake_lock(dev); + + WL_INFORM(("\n%s, PID:%x: send Local IOCTL -> dhd: cmd:0x%x, buf:%p, len:%d ,\n", + __FUNCTION__, current->pid, cmd, arg, len)); + + if (g_onoff == G_WLAN_SET_ON) { + memset(&ioc, 0, sizeof(ioc)); + ioc.cmd = cmd; + ioc.buf = arg; + ioc.len = len; + + strcpy(ifr.ifr_name, dev->name); + ifr.ifr_data = (caddr_t) &ioc; + + ret = dev_open(dev); + if (ret) { + WL_ERROR(("%s: Error dev_open: %d\n", __func__, ret)); + net_os_wake_unlock(dev); + return ret; + } + + fs = get_fs(); + set_fs(get_ds()); +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 31)) + ret = dev->do_ioctl(dev, &ifr, SIOCDEVPRIVATE); +#else + ret = dev->netdev_ops->ndo_do_ioctl(dev, &ifr, SIOCDEVPRIVATE); +#endif + set_fs(fs); + } + else { + WL_TRACE(("%s: call after driver stop : ignored\n", __FUNCTION__)); + } + + net_os_wake_unlock(dev); + + return ret; +} + + +static int +dev_wlc_intvar_get_reg( + struct net_device *dev, + char *name, + uint reg, + int *retval) +{ + union { + char buf[WLC_IOCTL_SMLEN]; + int val; + } var; + int error; + + uint len; + len = bcm_mkiovar(name, (char *)(®), sizeof(reg), (char *)(&var), sizeof(var.buf)); + ASSERT(len); + error = dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)&var, len); + + *retval = dtoh32(var.val); + return (error); +} + + +static int +dev_wlc_intvar_set_reg( + struct net_device *dev, + char *name, + char *addr, + char * val) +{ + char reg_addr[8]; + + memset(reg_addr, 0, sizeof(reg_addr)); + memcpy((char *)®_addr[0], (char *)addr, 4); + memcpy((char *)®_addr[4], (char *)val, 4); + + return (dev_wlc_bufvar_set(dev, name, (char *)®_addr[0], sizeof(reg_addr))); +} + + +static int +dev_wlc_intvar_set( + struct net_device *dev, + char *name, + int val) +{ + char buf[WLC_IOCTL_SMLEN]; + uint len; + + val = htod32(val); + len = bcm_mkiovar(name, (char *)(&val), sizeof(val), buf, sizeof(buf)); + ASSERT(len); + + return (dev_wlc_ioctl(dev, WLC_SET_VAR, buf, len)); +} + +#if defined(WL_IW_USE_ISCAN) +static int +dev_iw_iovar_setbuf( + struct net_device *dev, + char *iovar, + void *param, + int paramlen, + void *bufptr, + int buflen) +{ + int iolen; + + iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen); + ASSERT(iolen); + + if (iolen == 0) + return 0; + + return (dev_wlc_ioctl(dev, WLC_SET_VAR, bufptr, iolen)); +} + +static int +dev_iw_iovar_getbuf( + struct net_device *dev, + char *iovar, + void *param, + int paramlen, + void *bufptr, + int buflen) +{ + int iolen; + + iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen); + ASSERT(iolen); + + return (dev_wlc_ioctl(dev, WLC_GET_VAR, bufptr, buflen)); +} +#endif + + +#if WIRELESS_EXT > 17 +static int +dev_wlc_bufvar_set( + struct net_device *dev, + char *name, + char *buf, int len) +{ + static char ioctlbuf[MAX_WLIW_IOCTL_LEN]; + uint buflen; + + buflen = bcm_mkiovar(name, buf, len, ioctlbuf, sizeof(ioctlbuf)); + ASSERT(buflen); + + return (dev_wlc_ioctl(dev, WLC_SET_VAR, ioctlbuf, buflen)); +} +#endif + + +static int +dev_wlc_bufvar_get( + struct net_device *dev, + char *name, + char *buf, int buflen) +{ + static char ioctlbuf[MAX_WLIW_IOCTL_LEN]; + int error; + uint len; + + len = bcm_mkiovar(name, NULL, 0, ioctlbuf, sizeof(ioctlbuf)); + ASSERT(len); + error = dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)ioctlbuf, MAX_WLIW_IOCTL_LEN); + if (!error) + bcopy(ioctlbuf, buf, buflen); + + return (error); +} + + + +static int +dev_wlc_intvar_get( + struct net_device *dev, + char *name, + int *retval) +{ + union { + char buf[WLC_IOCTL_SMLEN]; + int val; + } var; + int error; + + uint len; + uint data_null; + + len = bcm_mkiovar(name, (char *)(&data_null), 0, (char *)(&var), sizeof(var.buf)); + ASSERT(len); + error = dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)&var, len); + + *retval = dtoh32(var.val); + + return (error); +} + + +#if WIRELESS_EXT > 12 +static int +wl_iw_set_active_scan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int as = 0; + int error = 0; + char *p = extra; + +#if defined(WL_IW_USE_ISCAN) + if (g_iscan->iscan_state == ISCAN_STATE_IDLE) +#endif + error = dev_wlc_ioctl(dev, WLC_SET_PASSIVE_SCAN, &as, sizeof(as)); +#if defined(WL_IW_USE_ISCAN) + else + g_iscan->scan_flag = as; +#endif + p += snprintf(p, MAX_WX_STRING, "OK"); + + wrqu->data.length = p - extra + 1; + return error; +} + +static int +wl_iw_set_passive_scan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int ps = 1; + int error = 0; + char *p = extra; + +#if defined(WL_IW_USE_ISCAN) + if (g_iscan->iscan_state == ISCAN_STATE_IDLE) { +#endif + + + if (g_scan_specified_ssid == 0) { + error = dev_wlc_ioctl(dev, WLC_SET_PASSIVE_SCAN, &ps, sizeof(ps)); + } +#if defined(WL_IW_USE_ISCAN) + } + else + g_iscan->scan_flag = ps; +#endif + + p += snprintf(p, MAX_WX_STRING, "OK"); + + wrqu->data.length = p - extra + 1; + return error; +} + + +static int +wl_iw_set_txpower( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = 0; + char *p = extra; + int txpower = -1; + + txpower = bcm_atoi(extra + strlen(TXPOWER_SET_CMD) + 1); + if ((txpower >= 0) && (txpower <= 127)) { + txpower |= WL_TXPWR_OVERRIDE; + txpower = htod32(txpower); + + error = dev_wlc_intvar_set(dev, "qtxpower", txpower); + p += snprintf(p, MAX_WX_STRING, "OK"); + WL_TRACE(("%s: set TXpower 0x%X is OK\n", __FUNCTION__, txpower)); + } else { + WL_ERROR(("%s: set tx power failed\n", __FUNCTION__)); + p += snprintf(p, MAX_WX_STRING, "FAIL"); + } + + wrqu->data.length = p - extra + 1; + return error; +} + +static int +wl_iw_get_macaddr( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error; + char buf[128]; + struct ether_addr *id; + char *p = extra; + + + strcpy(buf, "cur_etheraddr"); + error = dev_wlc_ioctl(dev, WLC_GET_VAR, buf, sizeof(buf)); + id = (struct ether_addr *) buf; + p += snprintf(p, MAX_WX_STRING, "Macaddr = %02X:%02X:%02X:%02X:%02X:%02X\n", + id->octet[0], id->octet[1], id->octet[2], + id->octet[3], id->octet[4], id->octet[5]); + wrqu->data.length = p - extra + 1; + + return error; +} + + +static int +wl_iw_set_country( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + char country_code[WLC_CNTRY_BUF_SZ]; + int error = 0; + char *p = extra; + int country_offset; + int country_code_size; + wl_country_t cspec = {{0}, 0, {0}}; + char smbuf[WLC_IOCTL_SMLEN]; + + cspec.rev = -1; + memset(country_code, 0, sizeof(country_code)); + memset(smbuf, 0, sizeof(smbuf)); + + country_offset = strcspn(extra, " "); + country_code_size = strlen(extra) - country_offset; + + if (country_offset != 0) { + strncpy(country_code, extra + country_offset +1, + MIN(country_code_size, sizeof(country_code))); + + + memcpy(cspec.country_abbrev, country_code, WLC_CNTRY_BUF_SZ); + memcpy(cspec.ccode, country_code, WLC_CNTRY_BUF_SZ); + + get_customized_country_code((char *)&cspec.country_abbrev, &cspec); + + if ((error = dev_iw_iovar_setbuf(dev, "country", &cspec, \ + sizeof(cspec), smbuf, sizeof(smbuf))) >= 0) { + p += snprintf(p, MAX_WX_STRING, "OK"); + WL_ERROR(("%s: set country for %s as %s rev %d is OK\n", \ + __FUNCTION__, country_code, cspec.ccode, cspec.rev)); + dhd_bus_country_set(dev, &cspec); + goto exit; + } + } + + WL_ERROR(("%s: set country for %s as %s rev %d failed\n", \ + __FUNCTION__, country_code, cspec.ccode, cspec.rev)); + + p += snprintf(p, MAX_WX_STRING, "FAIL"); + +exit: + wrqu->data.length = p - extra + 1; + return error; +} + +#ifdef CUSTOMER_HW2 +static int +wl_iw_set_power_mode( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = 0; + char *p = extra; + static int pm = PM_FAST; + int pm_local = PM_OFF; + char powermode_val = 0; + + WL_TRACE_COEX(("%s: DHCP session cmd:%s\n", __FUNCTION__, extra)); + + strncpy((char *)&powermode_val, extra + strlen("POWERMODE") + 1, 1); + + if (strnicmp((char *)&powermode_val, "1", strlen("1")) == 0) { + + dev_wlc_ioctl(dev, WLC_GET_PM, &pm, sizeof(pm)); + dev_wlc_ioctl(dev, WLC_SET_PM, &pm_local, sizeof(pm_local)); + + /* Disable packet filtering if necessary */ + net_os_set_packet_filter(dev, 0); + + g_bt->dhcp_done = false; + WL_TRACE_COEX(("%s: DHCP start, pm:%d changed to pm:%d\n", + __FUNCTION__, pm, pm_local)); + + } else if (strnicmp((char *)&powermode_val, "0", strlen("0")) == 0) { + + dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)); + + /* Enable packet filtering if was turned off */ + net_os_set_packet_filter(dev, 1); + + g_bt->dhcp_done = true; + + } else { + WL_ERROR(("%s Unkwown yet power setting, ignored\n", + __FUNCTION__)); + } + + p += snprintf(p, MAX_WX_STRING, "OK"); + + wrqu->data.length = p - extra + 1; + + return error; +} +#endif + + +bool btcoex_is_sco_active(struct net_device *dev) +{ + int ioc_res = 0; + bool res = false; + int sco_id_cnt = 0; + int param27; + int i; + + for (i = 0; i < 12; i++) { + + ioc_res = dev_wlc_intvar_get_reg(dev, "btc_params", 27, ¶m27); + + WL_TRACE_COEX(("%s, sample[%d], btc params: 27:%x\n", + __FUNCTION__, i, param27)); + + if (ioc_res < 0) { + WL_ERROR(("%s ioc read btc params error\n", __FUNCTION__)); + break; + } + + if ((param27 & 0x6) == 2) { + sco_id_cnt++; + } + + if (sco_id_cnt > 2) { + WL_TRACE_COEX(("%s, sco/esco detected, pkt id_cnt:%d samples:%d\n", + __FUNCTION__, sco_id_cnt, i)); + res = true; + break; + } + + msleep(5); + } + + return res; +} + +#if defined(BT_DHCP_eSCO_FIX) + +static int set_btc_esco_params(struct net_device *dev, bool trump_sco) +{ + static bool saved_status = false; + + char buf_reg50va_dhcp_on[8] = { 50, 00, 00, 00, 0x22, 0x80, 0x00, 0x00 }; + char buf_reg51va_dhcp_on[8] = { 51, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; + char buf_reg64va_dhcp_on[8] = { 64, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; + char buf_reg65va_dhcp_on[8] = { 65, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; + char buf_reg71va_dhcp_on[8] = { 71, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; + + uint32 regaddr; + static uint32 saved_reg50; + static uint32 saved_reg51; + static uint32 saved_reg64; + static uint32 saved_reg65; + static uint32 saved_reg71; + + if (trump_sco) { + + WL_TRACE_COEX(("Do new SCO/eSCO coex algo {save & override} \n")); + + if ((!dev_wlc_intvar_get_reg(dev, "btc_params", 50, &saved_reg50)) && + (!dev_wlc_intvar_get_reg(dev, "btc_params", 51, &saved_reg51)) && + (!dev_wlc_intvar_get_reg(dev, "btc_params", 64, &saved_reg64)) && + (!dev_wlc_intvar_get_reg(dev, "btc_params", 65, &saved_reg65)) && + (!dev_wlc_intvar_get_reg(dev, "btc_params", 71, &saved_reg71))) { + + saved_status = TRUE; + WL_TRACE_COEX(("%s saved bt_params[50,51,64,65,71]:" + " 0x%x 0x%x 0x%x 0x%x 0x%x\n", + __FUNCTION__, saved_reg50, saved_reg51, + saved_reg64, saved_reg65, saved_reg71)); + + } else { + WL_ERROR((":%s: save btc_params failed\n", + __FUNCTION__)); + saved_status = false; + return -1; + } + + WL_TRACE_COEX(("override with [50,51,64,65,71]:" + " 0x%x 0x%x 0x%x 0x%x 0x%x\n", + *(u32 *)(buf_reg50va_dhcp_on+4), + *(u32 *)(buf_reg51va_dhcp_on+4), + *(u32 *)(buf_reg64va_dhcp_on+4), + *(u32 *)(buf_reg65va_dhcp_on+4), + *(u32 *)(buf_reg71va_dhcp_on+4))); + + dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg50va_dhcp_on[0], 8); + dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg51va_dhcp_on[0], 8); + dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg64va_dhcp_on[0], 8); + dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg65va_dhcp_on[0], 8); + dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg71va_dhcp_on[0], 8); + + saved_status = true; + + } else if (saved_status) { + + WL_TRACE_COEX(("Do new SCO/eSCO coex algo {save & override} \n")); + + regaddr = 50; + dev_wlc_intvar_set_reg(dev, "btc_params", + (char *)®addr, (char *)&saved_reg50); + regaddr = 51; + dev_wlc_intvar_set_reg(dev, "btc_params", + (char *)®addr, (char *)&saved_reg51); + regaddr = 64; + dev_wlc_intvar_set_reg(dev, "btc_params", + (char *)®addr, (char *)&saved_reg64); + regaddr = 65; + dev_wlc_intvar_set_reg(dev, "btc_params", + (char *)®addr, (char *)&saved_reg65); + regaddr = 71; + dev_wlc_intvar_set_reg(dev, "btc_params", + (char *)®addr, (char *)&saved_reg71); + + WL_TRACE_COEX(("restore bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n", + saved_reg50, saved_reg51, saved_reg64, + saved_reg65, saved_reg71)); + + saved_status = false; + } else { + WL_ERROR((":%s att to restore not saved BTCOEX params\n", + __FUNCTION__)); + return -1; + } + return 0; +} +#endif + +static int +wl_iw_get_power_mode( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error; + char *p = extra; + int pm_local = PM_FAST; + + error = dev_wlc_ioctl(dev, WLC_GET_PM, &pm_local, sizeof(pm_local)); + if (!error) { + WL_TRACE(("%s: Powermode = %d\n", __func__, pm_local)); + if (pm_local == PM_OFF) + pm_local = 1; /* Active */ + else + pm_local = 0; /* Auto */ + p += snprintf(p, MAX_WX_STRING, "powermode = %d", pm_local); + } + else { + WL_TRACE(("%s: Error = %d\n", __func__, error)); + p += snprintf(p, MAX_WX_STRING, "FAIL"); + } + wrqu->data.length = p - extra + 1; + return error; +} + +static int +wl_iw_set_btcoex_dhcp( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = 0; + char *p = extra; +#ifndef CUSTOMER_HW2 + static int pm = PM_FAST; + int pm_local = PM_OFF; +#endif + char powermode_val = 0; + char buf_reg66va_dhcp_on[8] = { 66, 00, 00, 00, 0x10, 0x27, 0x00, 0x00 }; + char buf_reg41va_dhcp_on[8] = { 41, 00, 00, 00, 0x33, 0x00, 0x00, 0x00 }; + char buf_reg68va_dhcp_on[8] = { 68, 00, 00, 00, 0x90, 0x01, 0x00, 0x00 }; + + uint32 regaddr; + static uint32 saved_reg66; + static uint32 saved_reg41; + static uint32 saved_reg68; + static bool saved_status = FALSE; + + char buf_flag7_default[8] = { 7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00}; + +#ifdef CUSTOMER_HW2 + strncpy((char *)&powermode_val, extra + strlen("BTCOEXMODE") + 1, 1); +#else + strncpy((char *)&powermode_val, extra + strlen("POWERMODE") + 1, 1); +#endif + + if (strnicmp((char *)&powermode_val, "1", strlen("1")) == 0) { + + WL_TRACE_COEX(("%s: DHCP session start, cmd:%s\n", __FUNCTION__, extra)); + + if ((saved_status == FALSE) && +#ifndef CUSTOMER_HW2 + (!dev_wlc_ioctl(dev, WLC_GET_PM, &pm, sizeof(pm))) && +#endif + (!dev_wlc_intvar_get_reg(dev, "btc_params", 66, &saved_reg66)) && + (!dev_wlc_intvar_get_reg(dev, "btc_params", 41, &saved_reg41)) && + (!dev_wlc_intvar_get_reg(dev, "btc_params", 68, &saved_reg68))) { + WL_TRACE_COEX(("save regs {66,41,68} ->: 0x%x 0x%x 0x%x\n", \ + saved_reg66, saved_reg41, saved_reg68)); + +#ifndef CUSTOMER_HW2 + dev_wlc_ioctl(dev, WLC_SET_PM, &pm_local, sizeof(pm_local)); +#endif + + if (btcoex_is_sco_active(dev)) { + + dev_wlc_bufvar_set(dev, "btc_params", \ + (char *)&buf_reg66va_dhcp_on[0], \ + sizeof(buf_reg66va_dhcp_on)); + + dev_wlc_bufvar_set(dev, "btc_params", \ + (char *)&buf_reg41va_dhcp_on[0], \ + sizeof(buf_reg41va_dhcp_on)); + + dev_wlc_bufvar_set(dev, "btc_params", \ + (char *)&buf_reg68va_dhcp_on[0], \ + sizeof(buf_reg68va_dhcp_on)); + saved_status = TRUE; + + g_bt->bt_state = BT_DHCP_START; + g_bt->timer_on = 1; + mod_timer(&g_bt->timer, g_bt->timer.expires); + WL_TRACE_COEX(("%s enable BT DHCP Timer\n", \ + __FUNCTION__)); + } + } + else if (saved_status == TRUE) { + WL_ERROR(("%s was called w/o DHCP OFF. Continue\n", __FUNCTION__)); + } + } +#ifdef CUSTOMER_HW2 + else if (strnicmp((char *)&powermode_val, "2", strlen("2")) == 0) { +#else + else if (strnicmp((char *)&powermode_val, "0", strlen("0")) == 0) { +#endif + +#ifndef CUSTOMER_HW2 + dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)); +#endif + + WL_TRACE_COEX(("%s disable BT DHCP Timer\n", __FUNCTION__)); + if (g_bt->timer_on) { + g_bt->timer_on = 0; + del_timer_sync(&g_bt->timer); + + if (g_bt->bt_state != BT_DHCP_IDLE) { + WL_TRACE_COEX(("%s bt->bt_state:%d\n", + __FUNCTION__, g_bt->bt_state)); + + up(&g_bt->bt_sem); + } + } + + if (saved_status == TRUE) { + dev_wlc_bufvar_set(dev, "btc_flags", \ + (char *)&buf_flag7_default[0], sizeof(buf_flag7_default)); + + regaddr = 66; + dev_wlc_intvar_set_reg(dev, "btc_params", \ + (char *)®addr, (char *)&saved_reg66); + regaddr = 41; + dev_wlc_intvar_set_reg(dev, "btc_params", \ + (char *)®addr, (char *)&saved_reg41); + regaddr = 68; + dev_wlc_intvar_set_reg(dev, "btc_params", \ + (char *)®addr, (char *)&saved_reg68); + + WL_TRACE_COEX(("restore regs {66,41,68} <- 0x%x 0x%x 0x%x\n", \ + saved_reg66, saved_reg41, saved_reg68)); + } + saved_status = FALSE; + } + else { + WL_ERROR(("%s Unkwown yet power setting, ignored\n", + __FUNCTION__)); + } + + p += snprintf(p, MAX_WX_STRING, "OK"); + + wrqu->data.length = p - extra + 1; + + return error; +} + +static int +wl_iw_set_suspend( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int suspend_flag; + int ret_now; + int ret = 0; + + suspend_flag = *(extra + strlen(SETSUSPEND_CMD) + 1) - '0'; + + if (suspend_flag != 0) + suspend_flag = 1; + + ret_now = net_os_set_suspend_disable(dev, suspend_flag); + + if (ret_now != suspend_flag) { + if (!(ret = net_os_set_suspend(dev, ret_now))) + WL_ERROR(("%s: Suspend Flag %d -> %d\n", \ + __FUNCTION__, ret_now, suspend_flag)); + else + WL_ERROR(("%s: failed %d\n", __FUNCTION__, ret)); + } + + return ret; +} + + +int +wl_format_ssid(char* ssid_buf, uint8* ssid, int ssid_len) +{ + int i, c; + char *p = ssid_buf; + + if (ssid_len > 32) ssid_len = 32; + + for (i = 0; i < ssid_len; i++) { + c = (int)ssid[i]; + if (c == '\\') { + *p++ = '\\'; + *p++ = '\\'; + } else if (isprint((uchar)c)) { + *p++ = (char)c; + } else { + p += sprintf(p, "\\x%02X", c); + } + } + *p = '\0'; + + return p - ssid_buf; +} + +static int +wl_iw_get_link_speed( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = 0; + char *p = extra; + static int link_speed; + + net_os_wake_lock(dev); + if (g_onoff == G_WLAN_SET_ON) { + error = dev_wlc_ioctl(dev, WLC_GET_RATE, &link_speed, sizeof(link_speed)); + link_speed *= 500000; + } + + p += snprintf(p, MAX_WX_STRING, "LinkSpeed %d", link_speed/1000000); + + wrqu->data.length = p - extra + 1; + + net_os_wake_unlock(dev); + return error; +} + + +static int +wl_iw_get_dtim_skip( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = -1; + char *p = extra; + char iovbuf[32]; + + net_os_wake_lock(dev); + if (g_onoff == G_WLAN_SET_ON) { + + memset(iovbuf, 0, sizeof(iovbuf)); + strcpy(iovbuf, "bcn_li_dtim"); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_VAR, + &iovbuf, sizeof(iovbuf))) >= 0) { + + p += snprintf(p, MAX_WX_STRING, "Dtim_skip %d", iovbuf[0]); + WL_TRACE(("%s: get dtim_skip = %d\n", __FUNCTION__, iovbuf[0])); + wrqu->data.length = p - extra + 1; + } + else + WL_ERROR(("%s: get dtim_skip failed code %d\n", \ + __FUNCTION__, error)); + } + net_os_wake_unlock(dev); + return error; +} + + +static int +wl_iw_set_dtim_skip( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = -1; + char *p = extra; + int bcn_li_dtim; + char iovbuf[32]; + + net_os_wake_lock(dev); + if (g_onoff == G_WLAN_SET_ON) { + + bcn_li_dtim = htod32((uint)*(extra + strlen(DTIM_SKIP_SET_CMD) + 1) - '0'); + + if ((bcn_li_dtim >= 0) || ((bcn_li_dtim <= 5))) { + + memset(iovbuf, 0, sizeof(iovbuf)); + bcm_mkiovar("bcn_li_dtim", (char *)&bcn_li_dtim, + 4, iovbuf, sizeof(iovbuf)); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_VAR, + &iovbuf, sizeof(iovbuf))) >= 0) { + p += snprintf(p, MAX_WX_STRING, "OK"); + + net_os_set_dtim_skip(dev, bcn_li_dtim); + + WL_TRACE(("%s: set dtim_skip %d OK\n", __FUNCTION__, \ + bcn_li_dtim)); + goto exit; + } + else WL_ERROR(("%s: set dtim_skip %d failed code %d\n", \ + __FUNCTION__, bcn_li_dtim, error)); + } + else WL_ERROR(("%s Incorrect dtim_skip setting %d, ignored\n", \ + __FUNCTION__, bcn_li_dtim)); + } + + p += snprintf(p, MAX_WX_STRING, "FAIL"); + +exit: + wrqu->data.length = p - extra + 1; + net_os_wake_unlock(dev); + return error; +} + + +static int +wl_iw_get_band( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = -1; + char *p = extra; + static int band; + + net_os_wake_lock(dev); + + if (g_onoff == G_WLAN_SET_ON) { + error = dev_wlc_ioctl(dev, WLC_GET_BAND, &band, sizeof(band)); + + p += snprintf(p, MAX_WX_STRING, "Band %d", band); + + wrqu->data.length = p - extra + 1; + } + + net_os_wake_unlock(dev); + return error; +} + + +static int +wl_iw_set_band( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = -1; + char *p = extra; + uint band; + + net_os_wake_lock(dev); + + if (g_onoff == G_WLAN_SET_ON) { + + band = htod32((uint)*(extra + strlen(BAND_SET_CMD) + 1) - '0'); + + if ((band == WLC_BAND_AUTO) || (band == WLC_BAND_5G) || (band == WLC_BAND_2G)) { + + if ((error = dev_wlc_ioctl(dev, WLC_SET_BAND, + &band, sizeof(band))) >= 0) { + p += snprintf(p, MAX_WX_STRING, "OK"); + WL_TRACE(("%s: set band %d OK\n", __FUNCTION__, band)); + goto exit; + } + else WL_ERROR(("%s: set band %d failed code %d\n", __FUNCTION__, \ + band, error)); + } + else WL_ERROR(("%s Incorrect band setting %d, ignored\n", __FUNCTION__, band)); + } + + p += snprintf(p, MAX_WX_STRING, "FAIL"); + +exit: + wrqu->data.length = p - extra + 1; + net_os_wake_unlock(dev); + return error; +} + +#ifdef PNO_SUPPORT + +static int +wl_iw_set_pno_reset( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = -1; + char *p = extra; + + net_os_wake_lock(dev); + if ((g_onoff == G_WLAN_SET_ON) && (dev != NULL)) { + + if ((error = dhd_dev_pno_reset(dev)) >= 0) { + p += snprintf(p, MAX_WX_STRING, "OK"); + WL_TRACE(("%s: set OK\n", __FUNCTION__)); + goto exit; + } + else WL_ERROR(("%s: failed code %d\n", __FUNCTION__, error)); + } + + p += snprintf(p, MAX_WX_STRING, "FAIL"); + +exit: + wrqu->data.length = p - extra + 1; + net_os_wake_unlock(dev); + return error; +} + + + +static int +wl_iw_set_pno_enable( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error = -1; + char *p = extra; + int pfn_enabled; + + net_os_wake_lock(dev); + pfn_enabled = htod32((uint)*(extra + strlen(PNOENABLE_SET_CMD) + 1) - '0'); + + if ((g_onoff == G_WLAN_SET_ON) && (dev != NULL)) { + + if ((error = dhd_dev_pno_enable(dev, pfn_enabled)) >= 0) { + p += snprintf(p, MAX_WX_STRING, "OK"); + WL_TRACE(("%s: set OK\n", __FUNCTION__)); + goto exit; + } + else WL_ERROR(("%s: failed code %d\n", __FUNCTION__, error)); + } + + p += snprintf(p, MAX_WX_STRING, "FAIL"); + +exit: + wrqu->data.length = p - extra + 1; + net_os_wake_unlock(dev); + return error; +} + + + +static int +wl_iw_set_pno_set( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int res = -1; + wlc_ssid_t ssids_local[MAX_PFN_LIST_COUNT]; + int nssid = 0; + cmd_tlv_t *cmd_tlv_temp; + char *str_ptr; + int tlv_size_left; + int pno_time; + int pno_repeat; + int pno_freq_expo_max; + +#ifdef PNO_SET_DEBUG + int i; + char pno_in_example[] = {'P', 'N', 'O', 'S', 'E', 'T', 'U', 'P', ' ', \ + 'S', '1', '2', '0', + 'S', + 0x04, + 'B', 'R', 'C', 'M', + 'S', + 0x04, + 'G', 'O', 'O', 'G', + 'T', + '1','E', + 'R', + '2', + 'M', + '2', + 0x00 + }; +#endif + + net_os_wake_lock(dev); + WL_ERROR(("\n### %s: info->cmd:%x, info->flags:%x, u.data=0x%p, u.len=%d\n", + __FUNCTION__, info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (g_onoff == G_WLAN_SET_OFF) { + WL_TRACE(("%s: driver is not up yet after START\n", __FUNCTION__)); + goto exit_proc; + } + + if (wrqu->data.length < (strlen(PNOSETUP_SET_CMD) + sizeof(cmd_tlv_t))) { + WL_ERROR(("%s aggument=%d less %d\n", __FUNCTION__, \ + wrqu->data.length, strlen(PNOSETUP_SET_CMD) + sizeof(cmd_tlv_t))); + goto exit_proc; + } + +#ifdef PNO_SET_DEBUG + if (!(extra = kmalloc(sizeof(pno_in_example) +100, GFP_KERNEL))) { + res = -ENOMEM; + goto exit_proc; + } + memcpy(extra, pno_in_example, sizeof(pno_in_example)); + wrqu->data.length = sizeof(pno_in_example); + for (i = 0; i < wrqu->data.length; i++) + printf("%02X ", extra[i]); + printf("\n"); +#endif + + str_ptr = extra; +#ifdef PNO_SET_DEBUG + str_ptr += strlen("PNOSETUP "); + tlv_size_left = wrqu->data.length - strlen("PNOSETUP "); +#else + str_ptr += strlen(PNOSETUP_SET_CMD); + tlv_size_left = wrqu->data.length - strlen(PNOSETUP_SET_CMD); +#endif + + cmd_tlv_temp = (cmd_tlv_t *)str_ptr; + memset(ssids_local, 0, sizeof(ssids_local)); + pno_repeat = pno_freq_expo_max = 0; + + if ((cmd_tlv_temp->prefix == PNO_TLV_PREFIX) && \ + (cmd_tlv_temp->version == PNO_TLV_VERSION) && \ + (cmd_tlv_temp->subver == PNO_TLV_SUBVERSION)) + { + str_ptr += sizeof(cmd_tlv_t); + tlv_size_left -= sizeof(cmd_tlv_t); + + if ((nssid = wl_iw_parse_ssid_list_tlv(&str_ptr, ssids_local, \ + MAX_PFN_LIST_COUNT, &tlv_size_left)) <= 0) { + WL_ERROR(("SSID is not presented or corrupted ret=%d\n", nssid)); + goto exit_proc; + } + else { + if ((str_ptr[0] != PNO_TLV_TYPE_TIME) || (tlv_size_left <= 1)) { + WL_ERROR(("%s scan duration corrupted field size %d\n", \ + __FUNCTION__, tlv_size_left)); + goto exit_proc; + } + str_ptr++; + pno_time = simple_strtoul(str_ptr, &str_ptr, 16); + WL_PNO(("%s: pno_time=%d\n", __FUNCTION__, pno_time)); + + if (str_ptr[0] != 0) { + if ((str_ptr[0] != PNO_TLV_FREQ_REPEAT)) { + WL_ERROR(("%s pno repeat : corrupted field\n", \ + __FUNCTION__)); + goto exit_proc; + } + str_ptr++; + pno_repeat = simple_strtoul(str_ptr, &str_ptr, 16); + WL_PNO(("%s :got pno_repeat=%d\n", __FUNCTION__, pno_repeat)); + if (str_ptr[0] != PNO_TLV_FREQ_EXPO_MAX) { + WL_ERROR(("%s FREQ_EXPO_MAX corrupted field size\n", \ + __FUNCTION__)); + goto exit_proc; + } + str_ptr++; + pno_freq_expo_max = simple_strtoul(str_ptr, &str_ptr, 16); + WL_PNO(("%s: pno_freq_expo_max=%d\n", \ + __FUNCTION__, pno_freq_expo_max)); + } + } + } + else { + WL_ERROR(("%s get wrong TLV command\n", __FUNCTION__)); + goto exit_proc; + } + + res = dhd_dev_pno_set(dev, ssids_local, nssid, pno_time, pno_repeat, pno_freq_expo_max); + +exit_proc: + net_os_wake_unlock(dev); + return res; +} +#endif + +static int +wl_iw_get_rssi( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + static int rssi = 0; + static wlc_ssid_t ssid = {0}; + int error = 0; + char *p = extra; + static char ssidbuf[SSID_FMT_BUF_LEN]; + scb_val_t scb_val; + + net_os_wake_lock(dev); + + bzero(&scb_val, sizeof(scb_val_t)); + + if (g_onoff == G_WLAN_SET_ON) { + error = dev_wlc_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t)); + if (error) { + WL_ERROR(("%s: Fails %d\n", __FUNCTION__, error)); + } else { + rssi = dtoh32(scb_val.val); + + error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)); + if (!error) { + ssid.SSID_len = dtoh32(ssid.SSID_len); + wl_format_ssid(ssidbuf, ssid.SSID, dtoh32(ssid.SSID_len)); + } + } + } + + WL_ASSOC(("%s ssid_len:%d, rssi:%d\n", __FUNCTION__, ssid.SSID_len, rssi)); + + if (error || (ssid.SSID_len == 0)) { + p += snprintf(p, MAX_WX_STRING, "FAIL"); + } else { + p += snprintf(p, MAX_WX_STRING, "%s rssi %d ", ssidbuf, rssi); + } + wrqu->data.length = p - extra + 1; + + net_os_wake_unlock(dev); + return error; +} + +int +wl_iw_send_priv_event( + struct net_device *dev, + char *flag +) +{ + union iwreq_data wrqu; + char extra[IW_CUSTOM_MAX + 1]; + int cmd; + + cmd = IWEVCUSTOM; + memset(&wrqu, 0, sizeof(wrqu)); + if (strlen(flag) > sizeof(extra)) + return -1; + + strcpy(extra, flag); + wrqu.data.length = strlen(extra); + wireless_send_event(dev, cmd, &wrqu, extra); + net_os_wake_lock_timeout_enable(dev); + WL_TRACE(("Send IWEVCUSTOM Event as %s\n", extra)); + + return 0; +} + + +int +wl_control_wl_start(struct net_device *dev) +{ + int ret = 0; + wl_iw_t *iw; + + WL_TRACE(("Enter %s \n", __FUNCTION__)); + + if (!dev) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return -1; + } + + iw = *(wl_iw_t **)netdev_priv(dev); + + if (!iw) { + WL_ERROR(("%s: wl is null\n", __FUNCTION__)); + return -1; + } + dhd_os_start_lock(iw->pub); + + if (g_onoff == G_WLAN_SET_OFF) { + dhd_customer_gpio_wlan_ctrl(WLAN_RESET_ON); + +#if defined(BCMLXSDMMC) + sdioh_start(NULL, 0); +#endif + + ret = dhd_dev_reset(dev, 0); + + if (ret == BCME_OK) { +#if defined(BCMLXSDMMC) + sdioh_start(NULL, 1); +#endif + dhd_dev_init_ioctl(dev); + g_onoff = G_WLAN_SET_ON; + } + } + WL_TRACE(("Exited %s \n", __FUNCTION__)); + + dhd_os_start_unlock(iw->pub); + return ret; +} + + +static int +wl_iw_control_wl_off( + struct net_device *dev, + struct iw_request_info *info +) +{ + int ret = 0; + wl_iw_t *iw; + + WL_TRACE(("Enter %s\n", __FUNCTION__)); + + if (!dev) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return -1; + } + + iw = *(wl_iw_t **)netdev_priv(dev); + if (!iw) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return -1; + } + dhd_os_start_lock(iw->pub); + +#ifdef SOFTAP + ap_cfg_running = FALSE; +#endif + + if (g_onoff == G_WLAN_SET_ON) { + g_onoff = G_WLAN_SET_OFF; +#if defined(WL_IW_USE_ISCAN) + g_iscan->iscan_state = ISCAN_STATE_IDLE; +#endif + + dhd_dev_reset(dev, 1); + +#if defined(WL_IW_USE_ISCAN) +#if !defined(CSCAN) + wl_iw_free_ss_cache(); + wl_iw_run_ss_cache_timer(0); + + g_ss_cache_ctrl.m_link_down = 1; +#endif + memset(g_scan, 0, G_SCAN_RESULTS); + g_scan_specified_ssid = 0; +#if defined(CONFIG_FIRST_SCAN) + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_IDLE; + g_first_counter_scans = 0; +#endif +#endif + +#if defined(BCMLXSDMMC) + sdioh_stop(NULL); +#endif + + net_os_set_dtim_skip(dev, 0); + + dhd_customer_gpio_wlan_ctrl(WLAN_RESET_OFF); + + wl_iw_send_priv_event(dev, "STOP"); + } + + dhd_os_start_unlock(iw->pub); + + WL_TRACE(("Exited %s\n", __FUNCTION__)); + + return ret; +} + +static int +wl_iw_control_wl_on( + struct net_device *dev, + struct iw_request_info *info +) +{ + int ret = 0; + + WL_TRACE(("Enter %s \n", __FUNCTION__)); + + if ((ret = wl_control_wl_start(dev)) != BCME_OK) { + WL_ERROR(("%s failed first attemp\n", __FUNCTION__)); + dhd_customer_gpio_wlan_ctrl(WLAN_RESET_OFF); + if ((ret = wl_control_wl_start(dev)) != BCME_OK) { + WL_ERROR(("%s failed second attemp\n", __FUNCTION__)); + net_os_send_hang_message(dev); + return ret; + } + } + + wl_iw_send_priv_event(dev, "START"); + +#ifdef SOFTAP + if (!ap_fw_loaded) { + wl_iw_iscan_set_scan_broadcast_prep(dev, 0); + } +#else + wl_iw_iscan_set_scan_broadcast_prep(dev, 0); +#endif + + WL_TRACE(("Exited %s \n", __FUNCTION__)); + + return ret; +} + +#ifdef SOFTAP +static struct ap_profile my_ap; +static int set_ap_cfg(struct net_device *dev, struct ap_profile *ap); +static int get_assoc_sta_list(struct net_device *dev, char *buf, int len); +static int set_ap_mac_list(struct net_device *dev, void *buf); + +#define PTYPE_STRING 0 +#define PTYPE_INTDEC 1 +#define PTYPE_INTHEX 2 +#define PTYPE_STR_HEX 3 +int get_parmeter_from_string( + char **str_ptr, const char *token, int param_type, void *dst, int param_max_len); + +#endif + +int hex2num(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +int hex2byte(const char *hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) + return -1; + b = hex2num(*hex++); + if (b < 0) + return -1; + return (a << 4) | b; +} + + + +int hstr_2_buf(const char *txt, u8 *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int a, b; + + a = hex2num(*txt++); + if (a < 0) + return -1; + b = hex2num(*txt++); + if (b < 0) + return -1; + *buf++ = (a << 4) | b; + } + + return 0; +} + +#if defined(SOFTAP) && defined(SOFTAP_TLV_CFG) + +static int wl_iw_softap_cfg_tlv( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int res = -1; + char *str_ptr; + int tlv_size_left; + + +#define SOFTAP_TLV_DEBUG 1 +#ifdef SOFTAP_TLV_DEBUG +char softap_cmd_example[] = { + + 'S', 'O', 'F', 'T', 'A', 'P', 'S', 'E', 'T', ' ', + + SOFTAP_TLV_PREFIX, SOFTAP_TLV_VERSION, + SOFTAP_TLV_SUBVERSION, SOFTAP_TLV_RESERVED, + + TLV_TYPE_SSID, 9, 'B', 'R', 'C', 'M', ',', 'G', 'O', 'O', 'G', + + TLV_TYPE_SECUR, 4, 'O', 'P', 'E', 'N', + + TLV_TYPE_KEY, 4, 0x31, 0x32, 0x33, 0x34, + + TLV_TYPE_CHANNEL, 4, 0x06, 0x00, 0x00, 0x00 +}; +#endif + + +#ifdef SOFTAP_TLV_DEBUG + { + int i; + if (!(extra = kmalloc(sizeof(softap_cmd_example) +10, GFP_KERNEL))) + return -ENOMEM; + memcpy(extra, softap_cmd_example, sizeof(softap_cmd_example)); + wrqu->data.length = sizeof(softap_cmd_example); + print_buf(extra, wrqu->data.length, 16); + for (i = 0; i < wrqu->data.length; i++) + printf("%c ", extra[i]); + printf("\n"); + } +#endif + + WL_ERROR(("\n### %s: info->cmd:%x, info->flags:%x, u.data=0x%p, u.len=%d\n", + __FUNCTION__, info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (g_onoff == G_WLAN_SET_OFF) { + WL_TRACE(("%s: driver is not up yet after START\n", __FUNCTION__)); + return -1; + } + + if (wrqu->data.length < (strlen(SOFTAP_SET_CMD) + sizeof(cmd_tlv_t))) { + WL_ERROR(("%s argument=%d less %d\n", __FUNCTION__, + wrqu->data.length, strlen(SOFTAP_SET_CMD) + sizeof(cmd_tlv_t))); + return -1; + } + + str_ptr = extra + strlen(SOFTAP_SET_CMD)+1; + tlv_size_left = wrqu->data.length - (strlen(SOFTAP_SET_CMD)+1); + + memset(&my_ap, 0, sizeof(my_ap)); + + return res; +} +#endif + + +#ifdef SOFTAP +int init_ap_profile_from_string(char *param_str, struct ap_profile *ap_cfg) +{ + char *str_ptr = param_str; + char sub_cmd[16]; + int ret = 0; + + memset(sub_cmd, 0, sizeof(sub_cmd)); + memset(ap_cfg, 0, sizeof(struct ap_profile)); + + if (get_parmeter_from_string(&str_ptr, "ASCII_CMD=", + PTYPE_STRING, sub_cmd, SSID_LEN) != 0) { + return -1; + } + if (strncmp(sub_cmd, "AP_CFG", 6)) { + WL_ERROR(("ERROR: sub_cmd:%s != 'AP_CFG'!\n", sub_cmd)); + return -1; + } + + ret = get_parmeter_from_string(&str_ptr, "SSID=", PTYPE_STRING, ap_cfg->ssid, SSID_LEN); + + ret |= get_parmeter_from_string(&str_ptr, "SEC=", PTYPE_STRING, ap_cfg->sec, SEC_LEN); + + ret |= get_parmeter_from_string(&str_ptr, "KEY=", PTYPE_STRING, ap_cfg->key, KEY_LEN); + + ret |= get_parmeter_from_string(&str_ptr, "CHANNEL=", PTYPE_INTDEC, &ap_cfg->channel, 5); + + get_parmeter_from_string(&str_ptr, "PREAMBLE=", PTYPE_INTDEC, &ap_cfg->preamble, 5); + + get_parmeter_from_string(&str_ptr, "MAX_SCB=", PTYPE_INTDEC, &ap_cfg->max_scb, 5); + + get_parmeter_from_string(&str_ptr, "HIDDEN=", PTYPE_INTDEC, &ap_cfg->closednet, 5); + + get_parmeter_from_string(&str_ptr, "COUNTRY=", PTYPE_STRING, &ap_cfg->country_code, 3); + + return ret; +} +#endif + + +#ifdef SOFTAP +static int iwpriv_set_ap_config(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *ext) +{ + int res = 0; + char *extra = NULL; + struct ap_profile *ap_cfg = &my_ap; + + WL_TRACE(("%s: info->cmd:%x, info->flags:%x, u.data:%p, u.len:%d\n", + __FUNCTION__, + info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (wrqu->data.length != 0) { + + char *str_ptr; + + if (!(extra = kmalloc(wrqu->data.length+1, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(extra, wrqu->data.pointer, wrqu->data.length)) { + kfree(extra); + return -EFAULT; + } + + extra[wrqu->data.length] = 0; + WL_SOFTAP((" Got str param in iw_point:\n %s\n", extra)); + + memset(ap_cfg, 0, sizeof(struct ap_profile)); + + str_ptr = extra; + + if ((res = init_ap_profile_from_string(extra, ap_cfg)) < 0) { + WL_ERROR(("%s failed to parse %d\n", __FUNCTION__, res)); + kfree(extra); + return -1; + } + + } else { + WL_ERROR(("IWPRIV argument len = 0 \n")); + return -1; + } + + if ((res = set_ap_cfg(dev, ap_cfg)) < 0) + WL_ERROR(("%s failed to set_ap_cfg %d\n", __FUNCTION__, res)); + + kfree(extra); + + return res; +} +#endif + + +#ifdef SOFTAP +static int iwpriv_get_assoc_list(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *p_iwrq, + char *extra) +{ + int i, ret = 0; + char mac_buf[256]; + struct maclist *sta_maclist = (struct maclist *)mac_buf; + + char mac_lst[384]; + char *p_mac_str; + char *p_mac_str_end; + + if ((!dev) || (!extra)) { + return -EINVAL; + } + + net_os_wake_lock(dev); + + WL_TRACE(("\n %s: IWPRIV IOCTL: cmd:%hx, flags:%hx, extra:%p, iwp.len:%d, \ + iwp.len:%p, iwp.flags:%x \n", __FUNCTION__, info->cmd, info->flags, \ + extra, p_iwrq->data.length, p_iwrq->data.pointer, p_iwrq->data.flags)); + + memset(sta_maclist, 0, sizeof(mac_buf)); + + sta_maclist->count = 8; + + WL_SOFTAP(("%s: net device:%s, buf_sz:%d\n", + __FUNCTION__, dev->name, sizeof(mac_buf))); + + if ((ret = get_assoc_sta_list(dev, mac_buf, sizeof(mac_buf))) < 0) { + WL_ERROR(("%s: sta list ioctl error:%d\n", + __FUNCTION__, ret)); + goto func_exit; + } + + WL_SOFTAP(("%s: got %d stations\n", __FUNCTION__, + sta_maclist->count)); + + memset(mac_lst, 0, sizeof(mac_lst)); + p_mac_str = mac_lst; + p_mac_str_end = &mac_lst[sizeof(mac_lst)-1]; + + for (i = 0; i < 8; i++) { + struct ether_addr *id = &sta_maclist->ea[i]; + if (!ETHER_ISNULLADDR(id->octet)) { + scb_val_t scb_val; + int rssi = 0; + + bzero(&scb_val, sizeof(scb_val_t)); + + if ((p_mac_str_end - p_mac_str) <= 36) { + WL_ERROR(("%s: mac list buf is < 36 for item[%i] item\n", + __FUNCTION__, i)); + break; + } + + p_mac_str += snprintf(p_mac_str, MAX_WX_STRING, + "\nMac[%d]=%02X:%02X:%02X:%02X:%02X:%02X,", i, + id->octet[0], id->octet[1], id->octet[2], + id->octet[3], id->octet[4], id->octet[5]); + + bcopy(id->octet, &scb_val.ea, 6); + ret = dev_wlc_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t)); + if (ret < 0) { + snprintf(p_mac_str, MAX_WX_STRING, "RSSI:ERR"); + WL_ERROR(("%s: RSSI ioctl error:%d\n", + __FUNCTION__, ret)); + break; + } + + rssi = dtoh32(scb_val.val); + p_mac_str += snprintf(p_mac_str, MAX_WX_STRING, + "RSSI:%d", rssi); + } + } + + p_iwrq->data.length = strlen(mac_lst) + 1; + + WL_SOFTAP(("%s: data to user:\n%s\n usr_ptr:%p\n", __FUNCTION__, + mac_lst, p_iwrq->data.pointer)); + + if (p_iwrq->data.length) { + bcopy(mac_lst, extra, p_iwrq->data.length); + } + +func_exit: + net_os_wake_unlock(dev); + + WL_TRACE(("Exited %s \n", __FUNCTION__)); + return ret; +} +#endif + + +#ifdef SOFTAP +#define MAC_FILT_MAX 8 +static int iwpriv_set_mac_filters(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *ext) +{ + int i, ret = -1; + char * extra = NULL; + int mac_cnt = 0; + int mac_mode = 0; + struct ether_addr *p_ea; + struct mac_list_set mflist_set; + + WL_SOFTAP((">>> Got IWPRIV SET_MAC_FILTER IOCTL: info->cmd:%x, \ + info->flags:%x, u.data:%p, u.len:%d\n", + info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (wrqu->data.length != 0) { + + char *str_ptr; + + if (!(extra = kmalloc(wrqu->data.length+1, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(extra, wrqu->data.pointer, wrqu->data.length)) { + kfree(extra); + return -EFAULT; + } + + extra[wrqu->data.length] = 0; + WL_SOFTAP((" Got parameter string in iw_point:\n %s \n", extra)); + + memset(&mflist_set, 0, sizeof(mflist_set)); + + str_ptr = extra; + + if (get_parmeter_from_string(&str_ptr, "MAC_MODE=", + PTYPE_INTDEC, &mac_mode, 4) != 0) { + WL_ERROR(("ERROR: 'MAC_MODE=' token is missing\n")); + goto exit_proc; + } + + p_ea = &mflist_set.mac_list.ea[0]; + + if (get_parmeter_from_string(&str_ptr, "MAC_CNT=", + PTYPE_INTDEC, &mac_cnt, 4) != 0) { + WL_ERROR(("ERROR: 'MAC_CNT=' token param is missing \n")); + goto exit_proc; + } + + if (mac_cnt > MAC_FILT_MAX) { + WL_ERROR(("ERROR: number of MAC filters > MAX\n")); + goto exit_proc; + } + + for (i=0; i < mac_cnt; i++) + if (get_parmeter_from_string(&str_ptr, "MAC=", + PTYPE_STR_HEX, &p_ea[i], 12) != 0) { + WL_ERROR(("ERROR: MAC_filter[%d] is missing !\n", i)); + goto exit_proc; + } + + WL_SOFTAP(("MAC_MODE=:%d, MAC_CNT=%d, MACs:..\n", mac_mode, mac_cnt)); + for (i = 0; i < mac_cnt; i++) { + WL_SOFTAP(("mac_filt[%d]:", i)); + print_buf(&p_ea[i], 6, 0); + } + + mflist_set.mode = mac_mode; + mflist_set.mac_list.count = mac_cnt; + set_ap_mac_list(dev, &mflist_set); + + wrqu->data.pointer = NULL; + wrqu->data.length = 0; + ret = 0; + + } else { + WL_ERROR(("IWPRIV argument len is 0\n")); + return -1; + } + + exit_proc: + kfree(extra); + return ret; +} +#endif + + +#ifdef SOFTAP +static int iwpriv_set_ap_sta_disassoc(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *ext) +{ + int res = 0; + char sta_mac[6] = {0, 0, 0, 0, 0, 0}; + char cmd_buf[256]; + char *str_ptr = cmd_buf; + + WL_SOFTAP((">>%s called\n args: info->cmd:%x," + " info->flags:%x, u.data.p:%p, u.data.len:%d\n", + __FUNCTION__, info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (wrqu->data.length != 0) { + + if (copy_from_user(cmd_buf, wrqu->data.pointer, wrqu->data.length)) { + return -EFAULT; + } + + if (get_parmeter_from_string(&str_ptr, + "MAC=", PTYPE_STR_HEX, sta_mac, 12) == 0) { + res = wl_iw_softap_deassoc_stations(dev, sta_mac); + } else { + WL_ERROR(("ERROR: STA_MAC= token not found\n")); + } + } + + return res; +} +#endif + +#endif + + +#if WIRELESS_EXT < 13 +struct iw_request_info +{ + __u16 cmd; + __u16 flags; +}; + +typedef int (*iw_handler)(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra); +#endif + +static int +wl_iw_config_commit( + struct net_device *dev, + struct iw_request_info *info, + void *zwrq, + char *extra +) +{ + wlc_ssid_t ssid; + int error; + struct sockaddr bssid; + + WL_TRACE(("%s: SIOCSIWCOMMIT\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)))) + return error; + + ssid.SSID_len = dtoh32(ssid.SSID_len); + + if (!ssid.SSID_len) + return 0; + + bzero(&bssid, sizeof(struct sockaddr)); + if ((error = dev_wlc_ioctl(dev, WLC_REASSOC, &bssid, ETHER_ADDR_LEN))) { + WL_ERROR(("%s: WLC_REASSOC to %s failed \n", __FUNCTION__, ssid.SSID)); + return error; + } + + return 0; +} + +static int +wl_iw_get_name( + struct net_device *dev, + struct iw_request_info *info, + char *cwrq, + char *extra +) +{ + WL_TRACE(("%s: SIOCGIWNAME\n", dev->name)); + + strcpy(cwrq, "IEEE 802.11-DS"); + + return 0; +} + +static int +wl_iw_set_freq( + struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, + char *extra +) +{ + int error, chan; + uint sf = 0; + + WL_TRACE(("%s %s: SIOCSIWFREQ\n", __FUNCTION__, dev->name)); + +#if defined(SOFTAP) + if (ap_cfg_running) { + WL_TRACE(("%s:>> not executed, 'SOFT_AP is active' \n", __FUNCTION__)); + return 0; + } +#endif + + + if (fwrq->e == 0 && fwrq->m < MAXCHANNEL) { + chan = fwrq->m; + } + + + else { + + if (fwrq->e >= 6) { + fwrq->e -= 6; + while (fwrq->e--) + fwrq->m *= 10; + } else if (fwrq->e < 6) { + while (fwrq->e++ < 6) + fwrq->m /= 10; + } + + if (fwrq->m > 4000 && fwrq->m < 5000) + sf = WF_CHAN_FACTOR_4_G; + + chan = wf_mhz2channel(fwrq->m, sf); + } + chan = htod32(chan); + if ((error = dev_wlc_ioctl(dev, WLC_SET_CHANNEL, &chan, sizeof(chan)))) + return error; + + g_wl_iw_params.target_channel = chan; + + return -EINPROGRESS; +} + +static int +wl_iw_get_freq( + struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, + char *extra +) +{ + channel_info_t ci; + int error; + + WL_TRACE(("%s: SIOCGIWFREQ\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci)))) + return error; + + fwrq->m = dtoh32(ci.hw_channel); + fwrq->e = dtoh32(0); + return 0; +} + +static int +wl_iw_set_mode( + struct net_device *dev, + struct iw_request_info *info, + __u32 *uwrq, + char *extra +) +{ + int infra = 0, ap = 0, error = 0; + + WL_TRACE(("%s: SIOCSIWMODE\n", dev->name)); + + switch (*uwrq) { + case IW_MODE_MASTER: + infra = ap = 1; + break; + case IW_MODE_ADHOC: + case IW_MODE_AUTO: + break; + case IW_MODE_INFRA: + infra = 1; + break; + default: + return -EINVAL; + } + infra = htod32(infra); + ap = htod32(ap); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_INFRA, &infra, sizeof(infra))) || + (error = dev_wlc_ioctl(dev, WLC_SET_AP, &ap, sizeof(ap)))) + return error; + + + return -EINPROGRESS; +} + +static int +wl_iw_get_mode( + struct net_device *dev, + struct iw_request_info *info, + __u32 *uwrq, + char *extra +) +{ + int error, infra = 0, ap = 0; + + WL_TRACE(("%s: SIOCGIWMODE\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_INFRA, &infra, sizeof(infra))) || + (error = dev_wlc_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap)))) + return error; + + infra = dtoh32(infra); + ap = dtoh32(ap); + *uwrq = infra ? ap ? IW_MODE_MASTER : IW_MODE_INFRA : IW_MODE_ADHOC; + + return 0; +} + +static int +wl_iw_get_range( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + struct iw_range *range = (struct iw_range *) extra; + wl_uint32_list_t *list; + wl_rateset_t rateset; + int8 *channels; + int error, i, k; + uint sf, ch; + + int phytype; + int bw_cap = 0, sgi_tx = 0, nmode = 0; + channel_info_t ci; + uint8 nrate_list2copy = 0; + uint16 nrate_list[4][8] = { {13, 26, 39, 52, 78, 104, 117, 130}, + {14, 29, 43, 58, 87, 116, 130, 144}, + {27, 54, 81, 108, 162, 216, 243, 270}, + {30, 60, 90, 120, 180, 240, 270, 300}}; + + WL_TRACE(("%s: SIOCGIWRANGE\n", dev->name)); + + if (!extra) + return -EINVAL; + + channels = kmalloc((MAXCHANNEL+1)*4, GFP_KERNEL); + if (!channels) { + WL_ERROR(("Could not alloc channels\n")); + return -ENOMEM; + } + list = (wl_uint32_list_t *)channels; + + dwrq->length = sizeof(struct iw_range); + memset(range, 0, sizeof(range)); + + range->min_nwid = range->max_nwid = 0; + + list->count = htod32(MAXCHANNEL); + if ((error = dev_wlc_ioctl(dev, WLC_GET_VALID_CHANNELS, channels, (MAXCHANNEL+1)*4))) { + kfree(channels); + return error; + } + for (i = 0; i < dtoh32(list->count) && i < IW_MAX_FREQUENCIES; i++) { + range->freq[i].i = dtoh32(list->element[i]); + + ch = dtoh32(list->element[i]); + if (ch <= CH_MAX_2G_CHANNEL) + sf = WF_CHAN_FACTOR_2_4_G; + else + sf = WF_CHAN_FACTOR_5_G; + + range->freq[i].m = wf_channel2mhz(ch, sf); + range->freq[i].e = 6; + } + range->num_frequency = range->num_channels = i; + + range->max_qual.qual = 5; + + range->max_qual.level = 0x100 - 200; + + range->max_qual.noise = 0x100 - 200; + + range->sensitivity = 65535; + +#if WIRELESS_EXT > 11 + + range->avg_qual.qual = 3; + + range->avg_qual.level = 0x100 + WL_IW_RSSI_GOOD; + + range->avg_qual.noise = 0x100 - 75; +#endif + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, sizeof(rateset)))) { + kfree(channels); + return error; + } + rateset.count = dtoh32(rateset.count); + range->num_bitrates = rateset.count; + for (i = 0; i < rateset.count && i < IW_MAX_BITRATES; i++) + range->bitrate[i] = (rateset.rates[i]& 0x7f) * 500000; + dev_wlc_intvar_get(dev, "nmode", &nmode); + dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &phytype, sizeof(phytype)); + + if (nmode == 1 && phytype == WLC_PHY_TYPE_SSN) { + dev_wlc_intvar_get(dev, "mimo_bw_cap", &bw_cap); + dev_wlc_intvar_get(dev, "sgi_tx", &sgi_tx); + dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(channel_info_t)); + ci.hw_channel = dtoh32(ci.hw_channel); + + if (bw_cap == 0 || + (bw_cap == 2 && ci.hw_channel <= 14)) { + if (sgi_tx == 0) + nrate_list2copy = 0; + else + nrate_list2copy = 1; + } + if (bw_cap == 1 || + (bw_cap == 2 && ci.hw_channel >= 36)) { + if (sgi_tx == 0) + nrate_list2copy = 2; + else + nrate_list2copy = 3; + } + range->num_bitrates += 8; + for (k = 0; i < range->num_bitrates; k++, i++) { + + range->bitrate[i] = (nrate_list[nrate_list2copy][k]) * 500000; + } + } + + if ((error = dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &i, sizeof(i)))) { + kfree(channels); + return error; + } + i = dtoh32(i); + if (i == WLC_PHY_TYPE_A) + range->throughput = 24000000; + else + range->throughput = 1500000; + + range->min_rts = 0; + range->max_rts = 2347; + range->min_frag = 256; + range->max_frag = 2346; + + range->max_encoding_tokens = DOT11_MAX_DEFAULT_KEYS; + range->num_encoding_sizes = 4; + range->encoding_size[0] = WEP1_KEY_SIZE; + range->encoding_size[1] = WEP128_KEY_SIZE; +#if WIRELESS_EXT > 17 + range->encoding_size[2] = TKIP_KEY_SIZE; +#else + range->encoding_size[2] = 0; +#endif + range->encoding_size[3] = AES_KEY_SIZE; + + range->min_pmp = 0; + range->max_pmp = 0; + range->min_pmt = 0; + range->max_pmt = 0; + range->pmp_flags = 0; + range->pm_capa = 0; + + range->num_txpower = 2; + range->txpower[0] = 1; + range->txpower[1] = 255; + range->txpower_capa = IW_TXPOW_MWATT; + +#if WIRELESS_EXT > 10 + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 19; + + range->retry_capa = IW_RETRY_LIMIT; + range->retry_flags = IW_RETRY_LIMIT; + range->r_time_flags = 0; + + range->min_retry = 1; + range->max_retry = 255; + + range->min_r_time = 0; + range->max_r_time = 0; +#endif + +#if WIRELESS_EXT > 17 + range->enc_capa = IW_ENC_CAPA_WPA; + range->enc_capa |= IW_ENC_CAPA_CIPHER_TKIP; + range->enc_capa |= IW_ENC_CAPA_CIPHER_CCMP; +#ifdef BCMWPA2 + range->enc_capa |= IW_ENC_CAPA_WPA2; +#endif + + IW_EVENT_CAPA_SET_KERNEL(range->event_capa); + + IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP); + IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN); + IW_EVENT_CAPA_SET(range->event_capa, IWEVTXDROP); + IW_EVENT_CAPA_SET(range->event_capa, IWEVMICHAELMICFAILURE); +#ifdef BCMWPA2 + IW_EVENT_CAPA_SET(range->event_capa, IWEVPMKIDCAND); +#endif +#endif + + kfree(channels); + + return 0; +} + +static int +rssi_to_qual(int rssi) +{ + if (rssi <= WL_IW_RSSI_NO_SIGNAL) + return 0; + else if (rssi <= WL_IW_RSSI_VERY_LOW) + return 1; + else if (rssi <= WL_IW_RSSI_LOW) + return 2; + else if (rssi <= WL_IW_RSSI_GOOD) + return 3; + else if (rssi <= WL_IW_RSSI_VERY_GOOD) + return 4; + else + return 5; +} + +static int +wl_iw_set_spy( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = *(wl_iw_t **)netdev_priv(dev); + struct sockaddr *addr = (struct sockaddr *) extra; + int i; + + WL_TRACE(("%s: SIOCSIWSPY\n", dev->name)); + + if (!extra) + return -EINVAL; + + iw->spy_num = MIN(ARRAYSIZE(iw->spy_addr), dwrq->length); + for (i = 0; i < iw->spy_num; i++) + memcpy(&iw->spy_addr[i], addr[i].sa_data, ETHER_ADDR_LEN); + memset(iw->spy_qual, 0, sizeof(iw->spy_qual)); + + return 0; +} + +static int +wl_iw_get_spy( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = *(wl_iw_t **)netdev_priv(dev); + struct sockaddr *addr = (struct sockaddr *) extra; + struct iw_quality *qual = (struct iw_quality *) &addr[iw->spy_num]; + int i; + + WL_TRACE(("%s: SIOCGIWSPY\n", dev->name)); + + if (!extra) + return -EINVAL; + + dwrq->length = iw->spy_num; + for (i = 0; i < iw->spy_num; i++) { + memcpy(addr[i].sa_data, &iw->spy_addr[i], ETHER_ADDR_LEN); + addr[i].sa_family = AF_UNIX; + memcpy(&qual[i], &iw->spy_qual[i], sizeof(struct iw_quality)); + iw->spy_qual[i].updated = 0; + } + + return 0; +} + + +static int +wl_iw_ch_to_chanspec(int ch, wl_join_params_t *join_params, int *join_params_size) +{ + chanspec_t chanspec = 0; + + if (ch != 0) { + + join_params->params.chanspec_num = 1; + join_params->params.chanspec_list[0] = ch; + + if (join_params->params.chanspec_list[0]) + chanspec |= WL_CHANSPEC_BAND_2G; + else + chanspec |= WL_CHANSPEC_BAND_5G; + + chanspec |= WL_CHANSPEC_BW_20; + chanspec |= WL_CHANSPEC_CTL_SB_NONE; + + *join_params_size += WL_ASSOC_PARAMS_FIXED_SIZE + + join_params->params.chanspec_num * sizeof(chanspec_t); + + join_params->params.chanspec_list[0] &= WL_CHANSPEC_CHAN_MASK; + join_params->params.chanspec_list[0] |= chanspec; + join_params->params.chanspec_list[0] = + htodchanspec(join_params->params.chanspec_list[0]); + + join_params->params.chanspec_num = htod32(join_params->params.chanspec_num); + + WL_TRACE(("%s join_params->params.chanspec_list[0]= %X\n", \ + __FUNCTION__, join_params->params.chanspec_list[0])); + } + return 1; +} + +static int +wl_iw_set_wap( + struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *awrq, + char *extra +) +{ + int error = -EINVAL; + wl_join_params_t join_params; + int join_params_size; + + WL_TRACE(("%s: SIOCSIWAP\n", dev->name)); + + if (awrq->sa_family != ARPHRD_ETHER) { + WL_ERROR(("Invalid Header...sa_family\n")); + return -EINVAL; + } + + + if (ETHER_ISBCAST(awrq->sa_data) || ETHER_ISNULLADDR(awrq->sa_data)) { + scb_val_t scbval; + + bzero(&scbval, sizeof(scb_val_t)); + + (void) dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t)); + return 0; + } + + + + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid); + + memcpy(join_params.ssid.SSID, g_ssid.SSID, g_ssid.SSID_len); + join_params.ssid.SSID_len = htod32(g_ssid.SSID_len); + memcpy(&join_params.params.bssid, awrq->sa_data, ETHER_ADDR_LEN); + + WL_ASSOC(("%s target_channel=%d\n", __FUNCTION__, g_wl_iw_params.target_channel)); + wl_iw_ch_to_chanspec(g_wl_iw_params.target_channel, &join_params, &join_params_size); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_SSID, &join_params, join_params_size))) { + WL_ERROR(("%s Invalid ioctl data=%d\n", __FUNCTION__, error)); + return error; + } + + if (g_ssid.SSID_len) { + WL_ASSOC(("%s: join SSID=%s BSSID="MACSTR" ch=%d\n", __FUNCTION__, \ + g_ssid.SSID, MAC2STR((u8 *)awrq->sa_data), \ + g_wl_iw_params.target_channel)); + } + + + memset(&g_ssid, 0, sizeof(g_ssid)); + return 0; +} + +static int +wl_iw_get_wap( + struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *awrq, + char *extra +) +{ + WL_TRACE(("%s: SIOCGIWAP\n", dev->name)); + + awrq->sa_family = ARPHRD_ETHER; + memset(awrq->sa_data, 0, ETHER_ADDR_LEN); + + + (void) dev_wlc_ioctl(dev, WLC_GET_BSSID, awrq->sa_data, ETHER_ADDR_LEN); + + return 0; +} + +#if WIRELESS_EXT > 17 +static int +wl_iw_mlme( + struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *awrq, + char *extra +) +{ + struct iw_mlme *mlme; + scb_val_t scbval; + int error = -EINVAL; + + WL_TRACE(("%s: SIOCSIWMLME DISASSOC/DEAUTH\n", dev->name)); + + mlme = (struct iw_mlme *)extra; + if (mlme == NULL) { + WL_ERROR(("Invalid ioctl data.\n")); + return error; + } + + scbval.val = mlme->reason_code; + bcopy(&mlme->addr.sa_data, &scbval.ea, ETHER_ADDR_LEN); + + if (mlme->cmd == IW_MLME_DISASSOC) { + scbval.val = htod32(scbval.val); + error = dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t)); + } + else if (mlme->cmd == IW_MLME_DEAUTH) { + scbval.val = htod32(scbval.val); + error = dev_wlc_ioctl(dev, WLC_SCB_DEAUTHENTICATE_FOR_REASON, &scbval, + sizeof(scb_val_t)); + } + else { + WL_ERROR(("Invalid ioctl data.\n")); + return error; + } + + return error; +} +#endif + +#ifndef WL_IW_USE_ISCAN +static int +wl_iw_get_aplist( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_scan_results_t *list; + struct sockaddr *addr = (struct sockaddr *) extra; + struct iw_quality qual[IW_MAX_AP]; + wl_bss_info_t *bi = NULL; + int error, i; + uint buflen = dwrq->length; + + WL_TRACE(("%s: SIOCGIWAPLIST\n", dev->name)); + + if (!extra) + return -EINVAL; + + list = kmalloc(buflen, GFP_KERNEL); + if (!list) + return -ENOMEM; + memset(list, 0, buflen); + list->buflen = htod32(buflen); + if ((error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, buflen))) { + WL_ERROR(("%d: Scan results error %d\n", __LINE__, error)); + kfree(list); + return error; + } + list->buflen = dtoh32(list->buflen); + list->version = dtoh32(list->version); + list->count = dtoh32(list->count); + if (list->version != WL_BSS_INFO_VERSION) { + WL_ERROR(("%s: list->version %d != WL_BSS_INFO_VERSION\n", \ + __FUNCTION__, list->version)); + kfree(list); + return -EINVAL; + } + + for (i = 0, dwrq->length = 0; i < list->count && dwrq->length < IW_MAX_AP; i++) { + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : list->bss_info; + + if ((dtoh32(bi->length) > buflen) || + (((uintptr)bi + dtoh32(bi->length)) > ((uintptr)list + buflen))) { + WL_ERROR(("%s: Scan results out of bounds: %u\n",__FUNCTION__,dtoh32(bi->length))); + kfree(list); + return -E2BIG; + } + + if (!(dtoh16(bi->capability) & DOT11_CAP_ESS)) + continue; + + memcpy(addr[dwrq->length].sa_data, &bi->BSSID, ETHER_ADDR_LEN); + addr[dwrq->length].sa_family = ARPHRD_ETHER; + qual[dwrq->length].qual = rssi_to_qual(dtoh16(bi->RSSI)); + qual[dwrq->length].level = 0x100 + dtoh16(bi->RSSI); + qual[dwrq->length].noise = 0x100 + bi->phy_noise; + +#if WIRELESS_EXT > 18 + qual[dwrq->length].updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; +#else + qual[dwrq->length].updated = 7; +#endif + + dwrq->length++; + } + + kfree(list); + + if (dwrq->length) { + memcpy(&addr[dwrq->length], qual, sizeof(struct iw_quality) * dwrq->length); + + dwrq->flags = 1; + } + return 0; +} +#endif + +#ifdef WL_IW_USE_ISCAN +static int +wl_iw_iscan_get_aplist( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_scan_results_t *list; + iscan_buf_t * buf; + iscan_info_t *iscan = g_iscan; + + struct sockaddr *addr = (struct sockaddr *) extra; + struct iw_quality qual[IW_MAX_AP]; + wl_bss_info_t *bi = NULL; + int i; + + WL_TRACE(("%s: SIOCGIWAPLIST\n", dev->name)); + + if (!extra) + return -EINVAL; + + if ((!iscan) || (iscan->sysioc_pid < 0)) { + WL_ERROR(("%s error\n", __FUNCTION__)); + return 0; + } + + buf = iscan->list_hdr; + + while (buf) { + list = &((wl_iscan_results_t*)buf->iscan_buf)->results; + if (list->version != WL_BSS_INFO_VERSION) { + WL_ERROR(("%s : list->version %d != WL_BSS_INFO_VERSION\n", \ + __FUNCTION__, list->version)); + return -EINVAL; + } + + bi = NULL; + for (i = 0, dwrq->length = 0; i < list->count && dwrq->length < IW_MAX_AP; i++) { + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) + : list->bss_info; + + if ((dtoh32(bi->length) > WLC_IW_ISCAN_MAXLEN) || + (((uintptr)bi + dtoh32(bi->length)) > ((uintptr)list + WLC_IW_ISCAN_MAXLEN))) { + WL_ERROR(("%s: Scan results out of bounds: %u\n",__FUNCTION__,dtoh32(bi->length))); + return -E2BIG; + } + + if (!(dtoh16(bi->capability) & DOT11_CAP_ESS)) + continue; + + memcpy(addr[dwrq->length].sa_data, &bi->BSSID, ETHER_ADDR_LEN); + addr[dwrq->length].sa_family = ARPHRD_ETHER; + qual[dwrq->length].qual = rssi_to_qual(dtoh16(bi->RSSI)); + qual[dwrq->length].level = 0x100 + dtoh16(bi->RSSI); + qual[dwrq->length].noise = 0x100 + bi->phy_noise; + +#if WIRELESS_EXT > 18 + qual[dwrq->length].updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; +#else + qual[dwrq->length].updated = 7; +#endif + + dwrq->length++; + } + buf = buf->next; + } + if (dwrq->length) { + memcpy(&addr[dwrq->length], qual, sizeof(struct iw_quality) * dwrq->length); + + dwrq->flags = 1; + } + return 0; +} + +static int +wl_iw_iscan_prep(wl_scan_params_t *params, wlc_ssid_t *ssid) +{ + int err = 0; + + memcpy(¶ms->bssid, ðer_bcast, ETHER_ADDR_LEN); + params->bss_type = DOT11_BSSTYPE_ANY; + params->scan_type = 0; + params->nprobes = -1; + params->active_time = -1; + params->passive_time = -1; + params->home_time = -1; + params->channel_num = 0; +#if defined(CONFIG_FIRST_SCAN) + if (g_first_broadcast_scan == BROADCAST_SCAN_FIRST_STARTED) + params->passive_time = 30; +#endif + params->nprobes = htod32(params->nprobes); + params->active_time = htod32(params->active_time); + params->passive_time = htod32(params->passive_time); + params->home_time = htod32(params->home_time); + if (ssid && ssid->SSID_len) + memcpy(¶ms->ssid, ssid, sizeof(wlc_ssid_t)); + + return err; +} + +static int +wl_iw_iscan(iscan_info_t *iscan, wlc_ssid_t *ssid, uint16 action) +{ + int err = 0; + + iscan->iscan_ex_params_p->version = htod32(ISCAN_REQ_VERSION); + iscan->iscan_ex_params_p->action = htod16(action); + iscan->iscan_ex_params_p->scan_duration = htod16(0); + + WL_SCAN(("%s : nprobes=%d\n", __FUNCTION__, iscan->iscan_ex_params_p->params.nprobes)); + WL_SCAN(("active_time=%d\n", iscan->iscan_ex_params_p->params.active_time)); + WL_SCAN(("passive_time=%d\n", iscan->iscan_ex_params_p->params.passive_time)); + WL_SCAN(("home_time=%d\n", iscan->iscan_ex_params_p->params.home_time)); + WL_SCAN(("scan_type=%d\n", iscan->iscan_ex_params_p->params.scan_type)); + WL_SCAN(("bss_type=%d\n", iscan->iscan_ex_params_p->params.bss_type)); + + if ((err = dev_iw_iovar_setbuf(iscan->dev, "iscan", iscan->iscan_ex_params_p, \ + iscan->iscan_ex_param_size, iscan->ioctlbuf, sizeof(iscan->ioctlbuf)))) { + WL_ERROR(("Set ISCAN for %s failed with %d\n", __FUNCTION__, err)); + err = -1; + } + + return err; +} + +static void +wl_iw_timerfunc(ulong data) +{ + iscan_info_t *iscan = (iscan_info_t *)data; + if (iscan) { + iscan->timer_on = 0; + if (iscan->iscan_state != ISCAN_STATE_IDLE) { + WL_SCAN(("timer trigger\n")); + up(&iscan->sysioc_sem); + } + } +} +static void wl_iw_set_event_mask(struct net_device *dev) +{ + char eventmask[WL_EVENTING_MASK_LEN]; + char iovbuf[WL_EVENTING_MASK_LEN + 12]; + + dev_iw_iovar_getbuf(dev, "event_msgs", "", 0, iovbuf, sizeof(iovbuf)); + bcopy(iovbuf, eventmask, WL_EVENTING_MASK_LEN); + setbit(eventmask, WLC_E_SCAN_COMPLETE); + dev_iw_iovar_setbuf(dev, "event_msgs", eventmask, WL_EVENTING_MASK_LEN, + iovbuf, sizeof(iovbuf)); +} + +static uint32 +wl_iw_iscan_get(iscan_info_t *iscan) +{ + iscan_buf_t * buf; + iscan_buf_t * ptr; + wl_iscan_results_t * list_buf; + wl_iscan_results_t list; + wl_scan_results_t *results; + uint32 status; + int res; + + mutex_lock(&wl_cache_lock); + if (iscan->list_cur) { + buf = iscan->list_cur; + iscan->list_cur = buf->next; + } + else { + buf = kmalloc(sizeof(iscan_buf_t), GFP_KERNEL); + if (!buf) { + WL_ERROR(("%s can't alloc iscan_buf_t : going to abort currect iscan\n", \ + __FUNCTION__)); + mutex_unlock(&wl_cache_lock); + return WL_SCAN_RESULTS_NO_MEM; + } + buf->next = NULL; + if (!iscan->list_hdr) + iscan->list_hdr = buf; + else { + ptr = iscan->list_hdr; + while (ptr->next) { + ptr = ptr->next; + } + ptr->next = buf; + } + } + memset(buf->iscan_buf, 0, WLC_IW_ISCAN_MAXLEN); + list_buf = (wl_iscan_results_t*)buf->iscan_buf; + results = &list_buf->results; + results->buflen = WL_ISCAN_RESULTS_FIXED_SIZE; + results->version = 0; + results->count = 0; + + memset(&list, 0, sizeof(list)); + list.results.buflen = htod32(WLC_IW_ISCAN_MAXLEN); + res = dev_iw_iovar_getbuf( + iscan->dev, + "iscanresults", + &list, + WL_ISCAN_RESULTS_FIXED_SIZE, + buf->iscan_buf, + WLC_IW_ISCAN_MAXLEN); + if (res == 0) { + results->buflen = dtoh32(results->buflen); + results->version = dtoh32(results->version); + results->count = dtoh32(results->count); + WL_SCAN(("results->count = %d\n", results->count)); + + WL_SCAN(("results->buflen = %d\n", results->buflen)); + status = dtoh32(list_buf->status); + } else { + WL_ERROR(("%s returns error %d\n", __FUNCTION__, res)); + status = WL_SCAN_RESULTS_NO_MEM; + } + mutex_unlock(&wl_cache_lock); + return status; +} + +static void wl_iw_force_specific_scan(iscan_info_t *iscan) +{ + WL_SCAN(("%s force Specific SCAN for %s\n", __FUNCTION__, g_specific_ssid.SSID)); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_lock(); +#endif + (void) dev_wlc_ioctl(iscan->dev, WLC_SCAN, &g_specific_ssid, sizeof(g_specific_ssid)); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_unlock(); +#endif +} + +static void wl_iw_send_scan_complete(iscan_info_t *iscan) +{ +#ifndef SANDGATE2G + union iwreq_data wrqu; + + memset(&wrqu, 0, sizeof(wrqu)); + + wireless_send_event(iscan->dev, SIOCGIWSCAN, &wrqu, NULL); +#if defined(CONFIG_FIRST_SCAN) + if (g_first_broadcast_scan == BROADCAST_SCAN_FIRST_STARTED) + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_RESULT_READY; +#endif + WL_SCAN(("Send Event ISCAN complete\n")); +#endif +} + +static int +_iscan_sysioc_thread(void *data) +{ + uint32 status; + iscan_info_t *iscan = (iscan_info_t *)data; + static bool iscan_pass_abort = FALSE; + + DAEMONIZE("iscan_sysioc"); + + status = WL_SCAN_RESULTS_PARTIAL; + while (down_interruptible(&iscan->sysioc_sem) == 0) { + + net_os_wake_lock(iscan->dev); + +#if defined(SOFTAP) + if (ap_cfg_running) { + WL_SCAN(("%s skipping SCAN ops in AP mode !!!\n", __FUNCTION__)); + net_os_wake_unlock(iscan->dev); + continue; + } +#endif + + if (iscan->timer_on) { + iscan->timer_on = 0; + del_timer_sync(&iscan->timer); + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_lock(); +#endif + status = wl_iw_iscan_get(iscan); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_unlock(); +#endif + + if (g_scan_specified_ssid && (iscan_pass_abort == TRUE)) { + WL_SCAN(("%s Get results from specific scan status=%d\n", __FUNCTION__, status)); + wl_iw_send_scan_complete(iscan); + iscan_pass_abort = FALSE; + status = -1; + } + + switch (status) { + case WL_SCAN_RESULTS_PARTIAL: + WL_SCAN(("iscanresults incomplete\n")); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_lock(); +#endif + + wl_iw_iscan(iscan, NULL, WL_SCAN_ACTION_CONTINUE); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_unlock(); +#endif + + mod_timer(&iscan->timer, jiffies + iscan->timer_ms*HZ/1000); + iscan->timer_on = 1; + break; + case WL_SCAN_RESULTS_SUCCESS: + WL_SCAN(("iscanresults complete\n")); + iscan->iscan_state = ISCAN_STATE_IDLE; + wl_iw_send_scan_complete(iscan); + break; + case WL_SCAN_RESULTS_PENDING: + WL_SCAN(("iscanresults pending\n")); + + mod_timer(&iscan->timer, jiffies + iscan->timer_ms*HZ/1000); + iscan->timer_on = 1; + break; + case WL_SCAN_RESULTS_ABORTED: + WL_SCAN(("iscanresults aborted\n")); + iscan->iscan_state = ISCAN_STATE_IDLE; + if (g_scan_specified_ssid == 0) + wl_iw_send_scan_complete(iscan); + else { + iscan_pass_abort = TRUE; + wl_iw_force_specific_scan(iscan); + } + break; + case WL_SCAN_RESULTS_NO_MEM: + WL_SCAN(("iscanresults can't alloc memory: skip\n")); + iscan->iscan_state = ISCAN_STATE_IDLE; + break; + default: + WL_SCAN(("iscanresults returned unknown status %d\n", status)); + break; + } + + net_os_wake_unlock(iscan->dev); + } + + if (iscan->timer_on) { + iscan->timer_on = 0; + del_timer_sync(&iscan->timer); + } + + complete_and_exit(&iscan->sysioc_exited, 0); +} +#endif + +#if !defined(CSCAN) + +static void +wl_iw_set_ss_cache_timer_flag(void) +{ + g_ss_cache_ctrl.m_timer_expired = 1; + WL_TRACE(("%s called\n", __FUNCTION__)); +} + +static int +wl_iw_init_ss_cache_ctrl(void) +{ + WL_TRACE(("%s :\n", __FUNCTION__)); + g_ss_cache_ctrl.m_prev_scan_mode = 0; + g_ss_cache_ctrl.m_cons_br_scan_cnt = 0; + g_ss_cache_ctrl.m_cache_head = NULL; + g_ss_cache_ctrl.m_link_down = 0; + g_ss_cache_ctrl.m_timer_expired = 0; + memset(g_ss_cache_ctrl.m_active_bssid, 0, ETHER_ADDR_LEN); + + g_ss_cache_ctrl.m_timer = kmalloc(sizeof(struct timer_list), GFP_KERNEL); + if (!g_ss_cache_ctrl.m_timer) { + return -ENOMEM; + } + g_ss_cache_ctrl.m_timer->function = (void *)wl_iw_set_ss_cache_timer_flag; + init_timer(g_ss_cache_ctrl.m_timer); + + return 0; +} + + + +static void +wl_iw_free_ss_cache(void) +{ + wl_iw_ss_cache_t *node, *cur; + wl_iw_ss_cache_t **spec_scan_head; + + WL_TRACE(("%s called\n", __FUNCTION__)); + + mutex_lock(&wl_cache_lock); + spec_scan_head = &g_ss_cache_ctrl.m_cache_head; + node = *spec_scan_head; + + for (;node;) { + WL_TRACE(("%s : SSID - %s\n", __FUNCTION__, node->bss_info->SSID)); + cur = node; + node = cur->next; + kfree(cur); + } + *spec_scan_head = NULL; + mutex_unlock(&wl_cache_lock); +} + + + +static int +wl_iw_run_ss_cache_timer(int kick_off) +{ + struct timer_list **timer; + + timer = &g_ss_cache_ctrl.m_timer; + + if (*timer) { + if (kick_off) { + (*timer)->expires = jiffies + 30000 * HZ / 1000; + add_timer(*timer); + WL_TRACE(("%s : timer starts \n", __FUNCTION__)); + } else { + del_timer_sync(*timer); + WL_TRACE(("%s : timer stops \n", __FUNCTION__)); + } + } + + return 0; +} + + +void +wl_iw_release_ss_cache_ctrl(void) +{ + WL_TRACE(("%s :\n", __FUNCTION__)); + wl_iw_free_ss_cache(); + wl_iw_run_ss_cache_timer(0); + if (g_ss_cache_ctrl.m_timer) { + kfree(g_ss_cache_ctrl.m_timer); + } +} + + + +static void +wl_iw_reset_ss_cache(void) +{ + wl_iw_ss_cache_t *node, *prev, *cur; + wl_iw_ss_cache_t **spec_scan_head; + + mutex_lock(&wl_cache_lock); + spec_scan_head = &g_ss_cache_ctrl.m_cache_head; + node = *spec_scan_head; + prev = node; + + for (;node;) { + WL_TRACE(("%s : node SSID %s \n", __FUNCTION__, node->bss_info->SSID)); + if (!node->dirty) { + cur = node; + if (cur == *spec_scan_head) { + *spec_scan_head = cur->next; + prev = *spec_scan_head; + } + else { + prev->next = cur->next; + } + node = cur->next; + + WL_TRACE(("%s : Del node : SSID %s\n", __FUNCTION__, cur->bss_info->SSID)); + kfree(cur); + continue; + } + + node->dirty = 0; + prev = node; + node = node->next; + } + mutex_unlock(&wl_cache_lock); +} + + +static int +wl_iw_add_bss_to_ss_cache(wl_scan_results_t *ss_list) +{ + + wl_iw_ss_cache_t *node, *prev, *leaf; + wl_iw_ss_cache_t **spec_scan_head; + wl_bss_info_t *bi = NULL; + int i; + + if (!ss_list->count) { + return 0; + } + + mutex_lock(&wl_cache_lock); + spec_scan_head = &g_ss_cache_ctrl.m_cache_head; + + for (i = 0; i < ss_list->count; i++) { + + node = *spec_scan_head; + prev = node; + + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : ss_list->bss_info; + + WL_TRACE(("%s : find %d with specific SSID %s\n", __FUNCTION__, i, bi->SSID)); + for (;node;) { + if (!memcmp(&node->bss_info->BSSID, &bi->BSSID, ETHER_ADDR_LEN)) { + + WL_TRACE(("dirty marked : SSID %s\n", bi->SSID)); + node->dirty = 1; + break; + } + prev = node; + node = node->next; + } + + if (node) { + continue; + } + leaf = kmalloc(bi->length + WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN, GFP_KERNEL); + if (!leaf) { + WL_ERROR(("Memory alloc failure %d\n", \ + bi->length + WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN)); + mutex_unlock(&wl_cache_lock); + return -ENOMEM; + } + + memcpy(leaf->bss_info, bi, bi->length); + leaf->next = NULL; + leaf->dirty = 1; + leaf->count = 1; + leaf->version = ss_list->version; + + if (!prev) { + *spec_scan_head = leaf; + } + else { + prev->next = leaf; + } + } + mutex_unlock(&wl_cache_lock); + return 0; +} + + +static int +wl_iw_merge_scan_cache(struct iw_request_info *info, char *extra, uint buflen_from_user, +__u16 *merged_len) +{ + wl_iw_ss_cache_t *node; + wl_scan_results_t *list_merge; + + mutex_lock(&wl_cache_lock); + node = g_ss_cache_ctrl.m_cache_head; + for (;node;) { + list_merge = (wl_scan_results_t *)&node->buflen; + WL_TRACE(("%s: Cached Specific APs list=%d\n", __FUNCTION__, list_merge->count)); + if (buflen_from_user - *merged_len > 0) { + *merged_len += (__u16) wl_iw_get_scan_prep(list_merge, info, + extra + *merged_len, buflen_from_user - *merged_len); + } + else { + WL_TRACE(("%s: exit with break\n", __FUNCTION__)); + break; + } + node = node->next; + } + mutex_unlock(&wl_cache_lock); + return 0; +} + + +static int +wl_iw_delete_bss_from_ss_cache(void *addr) +{ + + wl_iw_ss_cache_t *node, *prev; + wl_iw_ss_cache_t **spec_scan_head; + + mutex_lock(&wl_cache_lock); + spec_scan_head = &g_ss_cache_ctrl.m_cache_head; + node = *spec_scan_head; + prev = node; + for (;node;) { + if (!memcmp(&node->bss_info->BSSID, addr, ETHER_ADDR_LEN)) { + if (node == *spec_scan_head) { + *spec_scan_head = node->next; + } + else { + prev->next = node->next; + } + + WL_TRACE(("%s : Del node : %s\n", __FUNCTION__, node->bss_info->SSID)); + kfree(node); + break; + } + + prev = node; + node = node->next; + } + + memset(addr, 0, ETHER_ADDR_LEN); + mutex_unlock(&wl_cache_lock); + return 0; +} + +#endif + + +static int +wl_iw_set_scan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int error; + WL_TRACE(("%s dev:%s: SIOCSIWSCAN : SCAN\n", __FUNCTION__, dev->name)); + +#if defined(CSCAN) + WL_ERROR(("%s: Scan from SIOCGIWSCAN not supported\n", __FUNCTION__)); + return -EINVAL; +#endif + +#if defined(SOFTAP) + if (ap_cfg_running) { + WL_TRACE(("\n>%s: Not executed, reason -'SOFTAP is active'\n", __FUNCTION__)); + return 0; + } +#endif + + if (g_onoff == G_WLAN_SET_OFF) + return 0; + + memset(&g_specific_ssid, 0, sizeof(g_specific_ssid)); +#ifndef WL_IW_USE_ISCAN + g_scan_specified_ssid = 0; +#endif + +#if WIRELESS_EXT > 17 + + if (wrqu->data.length == sizeof(struct iw_scan_req)) { + if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { + struct iw_scan_req *req = (struct iw_scan_req *)extra; +#if defined(CONFIG_FIRST_SCAN) + if (g_first_broadcast_scan != BROADCAST_SCAN_FIRST_RESULT_CONSUMED) { + WL_ERROR(("%s Ignoring SC %s first BC is not done = %d\n", \ + __FUNCTION__, req->essid, \ + g_first_broadcast_scan)); + return -EBUSY; + } +#endif + if (g_scan_specified_ssid) { + WL_SCAN(("%s Specific SCAN is not done ignore scan for = %s \n", \ + __FUNCTION__, req->essid)); + return -EBUSY; + } + else { + g_specific_ssid.SSID_len = MIN(sizeof(g_specific_ssid.SSID), \ + req->essid_len); + memcpy(g_specific_ssid.SSID, req->essid, g_specific_ssid.SSID_len); + g_specific_ssid.SSID_len = htod32(g_specific_ssid.SSID_len); + g_scan_specified_ssid = 1; + WL_TRACE(("### Specific scan ssid=%s len=%d\n", \ + g_specific_ssid.SSID, g_specific_ssid.SSID_len)); + } + } + } +#endif + + if ((error = dev_wlc_ioctl(dev, WLC_SCAN, &g_specific_ssid, sizeof(g_specific_ssid)))) { + WL_SCAN(("Set SCAN for %s failed with %d\n", g_specific_ssid.SSID, error)); + g_scan_specified_ssid = 0; + return -EBUSY; + } + + return 0; +} + +#ifdef WL_IW_USE_ISCAN +int +wl_iw_iscan_set_scan_broadcast_prep(struct net_device *dev, uint flag) +{ + wlc_ssid_t ssid; + iscan_info_t *iscan = g_iscan; + +#if defined(CONFIG_FIRST_SCAN) + if (g_first_broadcast_scan == BROADCAST_SCAN_FIRST_IDLE) { + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_STARTED; + WL_SCAN(("%s: First Brodcast scan was forced\n", __FUNCTION__)); + } + else if (g_first_broadcast_scan == BROADCAST_SCAN_FIRST_STARTED) { + WL_SCAN(("%s: ignore ISCAN request first BS is not done yet\n", __FUNCTION__)); + return 0; + } +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + if (flag) + rtnl_lock(); +#endif + + dev_wlc_ioctl(dev, WLC_SET_PASSIVE_SCAN, &iscan->scan_flag, sizeof(iscan->scan_flag)); + wl_iw_set_event_mask(dev); + + WL_SCAN(("+++: Set Broadcast ISCAN\n")); + + memset(&ssid, 0, sizeof(ssid)); + + iscan->list_cur = iscan->list_hdr; + iscan->iscan_state = ISCAN_STATE_SCANING; + + memset(&iscan->iscan_ex_params_p->params, 0, iscan->iscan_ex_param_size); + wl_iw_iscan_prep(&iscan->iscan_ex_params_p->params, &ssid); + wl_iw_iscan(iscan, &ssid, WL_SCAN_ACTION_START); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + if (flag) + rtnl_unlock(); +#endif + + mod_timer(&iscan->timer, jiffies + iscan->timer_ms*HZ/1000); + + iscan->timer_on = 1; + + return 0; +} + +static int +wl_iw_iscan_set_scan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + wlc_ssid_t ssid; + iscan_info_t *iscan = g_iscan; + int ret = 0; + + WL_SCAN(("%s: SIOCSIWSCAN : ISCAN\n", dev->name)); + +#if defined(CSCAN) + WL_ERROR(("%s: Scan from SIOCGIWSCAN not supported\n", __FUNCTION__)); + return -EINVAL; +#endif + + net_os_wake_lock(dev); + +#if defined(SOFTAP) + if (ap_cfg_running) { + WL_SCAN(("\n>%s: Not executed, reason -'SOFTAP is active'\n", __FUNCTION__)); + goto set_scan_end; + } +#endif + + if (g_onoff == G_WLAN_SET_OFF) { + WL_SCAN(("%s: driver is not up yet after START\n", __FUNCTION__)); + goto set_scan_end; + } + +#ifdef PNO_SUPPORT + if (dhd_dev_get_pno_status(dev)) { + WL_SCAN(("%s: Scan called when PNO is active\n", __FUNCTION__)); + } +#endif + + if ((!iscan) || (iscan->sysioc_pid < 0)) { + WL_ERROR(("%s error\n", __FUNCTION__)); + goto set_scan_end; + } + + if (g_scan_specified_ssid) { + WL_SCAN(("%s Specific SCAN already running ignoring BC scan\n", \ + __FUNCTION__)); + ret = EBUSY; + goto set_scan_end; + } + + memset(&ssid, 0, sizeof(ssid)); + +#if WIRELESS_EXT > 17 + + if (wrqu->data.length == sizeof(struct iw_scan_req)) { + if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { + int as = 0; + struct iw_scan_req *req = (struct iw_scan_req *)extra; + ssid.SSID_len = MIN(sizeof(ssid.SSID), req->essid_len); + memcpy(ssid.SSID, req->essid, ssid.SSID_len); + ssid.SSID_len = htod32(ssid.SSID_len); + dev_wlc_ioctl(dev, WLC_SET_PASSIVE_SCAN, &as, sizeof(as)); + wl_iw_set_event_mask(dev); + ret = wl_iw_set_scan(dev, info, wrqu, extra); + goto set_scan_end; + } + else { + g_scan_specified_ssid = 0; + + if (iscan->iscan_state == ISCAN_STATE_SCANING) { + WL_SCAN(("%s ISCAN already in progress \n", __FUNCTION__)); + goto set_scan_end; + } + } + } +#endif + +#if defined(CONFIG_FIRST_SCAN) && !defined(CSCAN) + if (g_first_broadcast_scan < BROADCAST_SCAN_FIRST_RESULT_CONSUMED) { + if (++g_first_counter_scans == MAX_ALLOWED_BLOCK_SCAN_FROM_FIRST_SCAN) { + + WL_ERROR(("%s Clean up First scan flag which is %d\n", \ + __FUNCTION__, g_first_broadcast_scan)); + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_RESULT_CONSUMED; + } + else { + WL_ERROR(("%s Ignoring Broadcast Scan:First Scan is not done yet %d\n", \ + __FUNCTION__, g_first_counter_scans)); + ret = -EBUSY; + goto set_scan_end; + } + } +#endif + + wl_iw_iscan_set_scan_broadcast_prep(dev, 0); + +set_scan_end: + net_os_wake_unlock(dev); + return ret; +} +#endif + +#if WIRELESS_EXT > 17 +static bool +ie_is_wpa_ie(uint8 **wpaie, uint8 **tlvs, int *tlvs_len) +{ + uint8 *ie = *wpaie; + + if ((ie[1] >= 6) && + !bcmp((const void *)&ie[2], (const void *)(WPA_OUI "\x01"), 4)) { + return TRUE; + } + + ie += ie[1] + 2; + + *tlvs_len -= (int)(ie - *tlvs); + + *tlvs = ie; + return FALSE; +} + +static bool +ie_is_wps_ie(uint8 **wpsie, uint8 **tlvs, int *tlvs_len) +{ + uint8 *ie = *wpsie; + + if ((ie[1] >= 4) && + !bcmp((const void *)&ie[2], (const void *)(WPA_OUI "\x04"), 4)) { + return TRUE; + } + + ie += ie[1] + 2; + + *tlvs_len -= (int)(ie - *tlvs); + + *tlvs = ie; + return FALSE; +} +#endif + +static inline int _wpa_snprintf_hex(char *buf, size_t buf_size, const u8 *data, + size_t len, int uppercase) +{ + size_t i; + char *pos = buf, *end = buf + buf_size; + int ret; + if (buf_size == 0) + return 0; + for (i = 0; i < len; i++) { + ret = snprintf(pos, end - pos, uppercase ? "%02X" : "%02x", + data[i]); + if (ret < 0 || ret >= end - pos) { + end[-1] = '\0'; + return pos - buf; + } + pos += ret; + } + end[-1] = '\0'; + return pos - buf; +} + + +int wpa_snprintf_hex(char *buf, size_t buf_size, const u8 *data, size_t len) +{ + return _wpa_snprintf_hex(buf, buf_size, data, len, 0); +} + +static int +wl_iw_handle_scanresults_ies(char **event_p, char *end, + struct iw_request_info *info, wl_bss_info_t *bi) +{ +#if WIRELESS_EXT > 17 + struct iw_event iwe; + char *event; + char *buf; + int custom_event_len; + + event = *event_p; + if (bi->ie_length) { + + bcm_tlv_t *ie; + uint8 *ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); + int ptr_len = bi->ie_length; + +#ifdef BCMWPA2 + if ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_RSN_ID))) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); + } + ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); +#endif + + while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) { + + if (ie_is_wps_ie(((uint8 **)&ie), &ptr, &ptr_len)) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); + break; + } + } + + ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); + ptr_len = bi->ie_length; + while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) { + if (ie_is_wpa_ie(((uint8 **)&ie), &ptr, &ptr_len)) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); + break; + } + } + + ptr = ((uint8 *)bi) + sizeof(wl_bss_info_t); + ptr_len = bi->ie_length; + + while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WAPI_ID))) { + WL_TRACE(("%s: found a WAPI IE...\n", __FUNCTION__)); +#ifdef WAPI_IE_USE_GENIE + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)ie); +#else + iwe.cmd = IWEVCUSTOM; + custom_event_len = strlen("wapi_ie=") + 2*(ie->len + 2); + iwe.u.data.length = custom_event_len; + + buf = kmalloc(custom_event_len+1, GFP_KERNEL); + if (buf == NULL) + { + WL_ERROR(("malloc(%d) returned NULL...\n", custom_event_len)); + break; + } + + memcpy(buf, "wapi_ie=", 8); + wpa_snprintf_hex(buf + 8, 2+1, &(ie->id), 1); + wpa_snprintf_hex(buf + 10, 2+1, &(ie->len), 1); + wpa_snprintf_hex(buf + 12, 2*ie->len+1, ie->data, ie->len); + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, buf); + kfree(buf); +#endif + break; + } + *event_p = event; + } +#endif + + return 0; +} + +#ifndef CSCAN +static uint +wl_iw_get_scan_prep( + wl_scan_results_t *list, + struct iw_request_info *info, + char *extra, + short max_size) +{ + int i, j; + struct iw_event iwe; + wl_bss_info_t *bi = NULL; + char *event = extra, *end = extra + max_size - WE_ADD_EVENT_FIX, *value; + int ret = 0; + int channel; + + if (!list) { + WL_ERROR(("%s: Null list pointer",__FUNCTION__)); + return ret; + } + + for (i = 0; i < list->count && i < IW_MAX_AP; i++) + { + if (list->version != WL_BSS_INFO_VERSION) { + WL_ERROR(("%s : list->version %d != WL_BSS_INFO_VERSION\n", \ + __FUNCTION__, list->version)); + return ret; + } + + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : list->bss_info; + + WL_TRACE(("%s : %s\n", __FUNCTION__, bi->SSID)); + + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, &bi->BSSID, ETHER_ADDR_LEN); + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_ADDR_LEN); + + iwe.u.data.length = dtoh32(bi->SSID_len); + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, bi->SSID); + + if (dtoh16(bi->capability) & (DOT11_CAP_ESS | DOT11_CAP_IBSS)) { + iwe.cmd = SIOCGIWMODE; + if (dtoh16(bi->capability) & DOT11_CAP_ESS) + iwe.u.mode = IW_MODE_INFRA; + else + iwe.u.mode = IW_MODE_ADHOC; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_UINT_LEN); + } + + iwe.cmd = SIOCGIWFREQ; + channel = (bi->ctl_ch == 0) ? CHSPEC_CHANNEL(bi->chanspec) : bi->ctl_ch; + iwe.u.freq.m = wf_channel2mhz(channel, + channel <= CH_MAX_2G_CHANNEL ? + WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G); + iwe.u.freq.e = 6; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_FREQ_LEN); + + iwe.cmd = IWEVQUAL; + iwe.u.qual.qual = rssi_to_qual(dtoh16(bi->RSSI)); + iwe.u.qual.level = 0x100 + dtoh16(bi->RSSI); + iwe.u.qual.noise = 0x100 + bi->phy_noise; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_QUAL_LEN); + + wl_iw_handle_scanresults_ies(&event, end, info, bi); + + iwe.cmd = SIOCGIWENCODE; + if (dtoh16(bi->capability) & DOT11_CAP_PRIVACY) + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + else + iwe.u.data.flags = IW_ENCODE_DISABLED; + iwe.u.data.length = 0; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)event); + + if (bi->rateset.count) { + if (((event -extra) + IW_EV_LCP_LEN) <= (uintptr)end) { + value = event + IW_EV_LCP_LEN; + iwe.cmd = SIOCGIWRATE; + + iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; + for (j = 0; j < bi->rateset.count && j < IW_MAX_BITRATES; j++) { + iwe.u.bitrate.value = (bi->rateset.rates[j] & 0x7f) * 500000; + value = IWE_STREAM_ADD_VALUE(info, event, value, end, &iwe, + IW_EV_PARAM_LEN); + } + event = value; + } + } + } + + if ((ret = (event - extra)) < 0) { + WL_ERROR(("==> Wrong size\n")); + ret = 0; + } + WL_TRACE(("%s: size=%d bytes prepared \n", __FUNCTION__, (unsigned int)(event - extra))); + return (uint)ret; +} + +static int +wl_iw_get_scan( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + channel_info_t ci; + wl_scan_results_t *list_merge; + wl_scan_results_t *list = (wl_scan_results_t *) g_scan; + int error; + uint buflen_from_user = dwrq->length; + uint len = G_SCAN_RESULTS; + __u16 len_ret = 0; +#if !defined(CSCAN) + __u16 merged_len = 0; +#endif +#if defined(WL_IW_USE_ISCAN) + iscan_info_t *iscan = g_iscan; + iscan_buf_t * p_buf; +#if !defined(CSCAN) + uint32 counter = 0; +#endif +#endif + WL_TRACE(("%s: buflen_from_user %d: \n", dev->name, buflen_from_user)); + + if (!extra) { + WL_TRACE(("%s: wl_iw_get_scan return -EINVAL\n", dev->name)); + return -EINVAL; + } + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci)))) + return error; + ci.scan_channel = dtoh32(ci.scan_channel); + if (ci.scan_channel) + return -EAGAIN; + +#if !defined(CSCAN) + if (g_ss_cache_ctrl.m_timer_expired) { + wl_iw_free_ss_cache(); + g_ss_cache_ctrl.m_timer_expired ^= 1; + } + if ((!g_scan_specified_ssid && g_ss_cache_ctrl.m_prev_scan_mode) || + g_ss_cache_ctrl.m_cons_br_scan_cnt > 4) { + g_ss_cache_ctrl.m_cons_br_scan_cnt = 0; + + wl_iw_reset_ss_cache(); + } + g_ss_cache_ctrl.m_prev_scan_mode = g_scan_specified_ssid; + if (g_scan_specified_ssid) { + g_ss_cache_ctrl.m_cons_br_scan_cnt = 0; + } + else { + g_ss_cache_ctrl.m_cons_br_scan_cnt++; + } +#endif + + if (g_scan_specified_ssid) { + + list = kmalloc(len, GFP_KERNEL); + if (!list) { + WL_TRACE(("%s: wl_iw_get_scan return -ENOMEM\n", dev->name)); + g_scan_specified_ssid = 0; + return -ENOMEM; + } + } + + memset(list, 0, len); + list->buflen = htod32(len); + if ((error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, len))) { + WL_ERROR(("%s: %s : Scan_results ERROR %d\n", dev->name, __FUNCTION__, error)); + dwrq->length = len; + if (g_scan_specified_ssid) { + g_scan_specified_ssid = 0; + kfree(list); + } + return 0; + } + list->buflen = dtoh32(list->buflen); + list->version = dtoh32(list->version); + list->count = dtoh32(list->count); + + if (list->version != WL_BSS_INFO_VERSION) { + WL_ERROR(("%s : list->version %d != WL_BSS_INFO_VERSION\n", + __FUNCTION__, list->version)); + if (g_scan_specified_ssid) { + g_scan_specified_ssid = 0; + kfree(list); + } + return -EINVAL; + } + +#if !defined(CSCAN) + if (g_scan_specified_ssid) { + + wl_iw_add_bss_to_ss_cache(list); + kfree(list); + } + + mutex_lock(&wl_cache_lock); +#if defined(WL_IW_USE_ISCAN) + if (g_scan_specified_ssid) + WL_TRACE(("%s: Specified scan APs from scan=%d\n", __FUNCTION__, list->count)); + p_buf = iscan->list_hdr; + + while (p_buf != iscan->list_cur) { + list_merge = &((wl_iscan_results_t*)p_buf->iscan_buf)->results; + WL_TRACE(("%s: Bcast APs list=%d\n", __FUNCTION__, list_merge->count)); + counter += list_merge->count; + if (list_merge->count > 0) + len_ret += (__u16) wl_iw_get_scan_prep(list_merge, info, + extra+len_ret, buflen_from_user -len_ret); + p_buf = p_buf->next; + } + WL_TRACE(("%s merged with total Bcast APs=%d\n", __FUNCTION__, counter)); +#else + list_merge = (wl_scan_results_t *) g_scan; + len_ret = (__u16) wl_iw_get_scan_prep(list_merge, info, extra, buflen_from_user); +#endif + mutex_unlock(&wl_cache_lock); + if (g_ss_cache_ctrl.m_link_down) { + wl_iw_delete_bss_from_ss_cache(g_ss_cache_ctrl.m_active_bssid); + } + + wl_iw_merge_scan_cache(info, extra+len_ret, buflen_from_user-len_ret, &merged_len); + len_ret += merged_len; + wl_iw_run_ss_cache_timer(0); + wl_iw_run_ss_cache_timer(1); +#else + + if (g_scan_specified_ssid) { + WL_TRACE(("%s: Specified scan APs in the list =%d\n", __FUNCTION__, list->count)); + len_ret = (__u16) wl_iw_get_scan_prep(list, info, extra, buflen_from_user); + kfree(list); + +#if defined(WL_IW_USE_ISCAN) + p_buf = iscan->list_hdr; + + while (p_buf != iscan->list_cur) { + list_merge = &((wl_iscan_results_t*)p_buf->iscan_buf)->results; + WL_TRACE(("%s: Bcast APs list=%d\n", __FUNCTION__, list_merge->count)); + if (list_merge->count > 0) + len_ret += (__u16) wl_iw_get_scan_prep(list_merge, info, + extra+len_ret, buflen_from_user -len_ret); + p_buf = p_buf->next; + } +#else + list_merge = (wl_scan_results_t *) g_scan; + WL_TRACE(("%s: Bcast APs list=%d\n", __FUNCTION__, list_merge->count)); + if (list_merge->count > 0) + len_ret += (__u16) wl_iw_get_scan_prep(list_merge, info, extra+len_ret, + buflen_from_user -len_ret); +#endif + } + else { + list = (wl_scan_results_t *) g_scan; + len_ret = (__u16) wl_iw_get_scan_prep(list, info, extra, buflen_from_user); + } +#endif + +#if defined(WL_IW_USE_ISCAN) + + g_scan_specified_ssid = 0; +#endif + + if ((len_ret + WE_ADD_EVENT_FIX) < buflen_from_user) + len = len_ret; + + dwrq->length = len; + dwrq->flags = 0; + + WL_TRACE(("%s return to WE %d bytes APs=%d\n", __FUNCTION__, dwrq->length, list->count)); + return 0; +} +#endif + +#if defined(WL_IW_USE_ISCAN) +static int +wl_iw_iscan_get_scan( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_scan_results_t *list; + struct iw_event iwe; + wl_bss_info_t *bi = NULL; + int ii, j; + int apcnt; + char *event = extra, *end = extra + dwrq->length, *value; + iscan_info_t *iscan = g_iscan; + iscan_buf_t * p_buf; + uint32 counter = 0; + uint8 channel; +#if !defined(CSCAN) + __u16 merged_len = 0; + uint buflen_from_user = dwrq->length; +#endif + + WL_SCAN(("%s %s buflen_from_user %d:\n", dev->name, __FUNCTION__, dwrq->length)); + +#if defined(SOFTAP) + if (ap_cfg_running) { + WL_TRACE(("%s: Not executed, reason -'SOFTAP is active'\n", __FUNCTION__)); + return -EINVAL; + } +#endif + + if (!extra) { + WL_TRACE(("%s: INVALID SIOCGIWSCAN GET bad parameter\n", dev->name)); + return -EINVAL; + } + +#if defined(CONFIG_FIRST_SCAN) + if (g_first_broadcast_scan < BROADCAST_SCAN_FIRST_RESULT_READY) { + WL_TRACE(("%s %s: first ISCAN results are NOT ready yet \n", \ + dev->name, __FUNCTION__)); + return -EAGAIN; + } +#endif + + if ((!iscan) || (iscan->sysioc_pid < 0)) { + WL_ERROR(("%ssysioc_pid\n", __FUNCTION__)); + return -EAGAIN; + } + +#if !defined(CSCAN) + if (g_ss_cache_ctrl.m_timer_expired) { + wl_iw_free_ss_cache(); + g_ss_cache_ctrl.m_timer_expired ^= 1; + } + if (g_scan_specified_ssid) { + return wl_iw_get_scan(dev, info, dwrq, extra); + } + else { + if (g_ss_cache_ctrl.m_link_down) { + wl_iw_delete_bss_from_ss_cache(g_ss_cache_ctrl.m_active_bssid); + } + if (g_ss_cache_ctrl.m_prev_scan_mode || g_ss_cache_ctrl.m_cons_br_scan_cnt > 4) { + g_ss_cache_ctrl.m_cons_br_scan_cnt = 0; + + wl_iw_reset_ss_cache(); + } + g_ss_cache_ctrl.m_prev_scan_mode = g_scan_specified_ssid; + g_ss_cache_ctrl.m_cons_br_scan_cnt++; + } +#endif + + WL_TRACE(("%s: SIOCGIWSCAN GET broadcast results\n", dev->name)); + apcnt = 0; + p_buf = iscan->list_hdr; + + while (p_buf != iscan->list_cur) { + list = &((wl_iscan_results_t*)p_buf->iscan_buf)->results; + + counter += list->count; + + if (list->version != WL_BSS_INFO_VERSION) { + WL_ERROR(("%s : list->version %d != WL_BSS_INFO_VERSION\n", + __FUNCTION__, list->version)); + return -EINVAL; + } + + bi = NULL; + for (ii = 0; ii < list->count && apcnt < IW_MAX_AP; apcnt++, ii++) { + bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : list->bss_info; + + if ((dtoh32(bi->length) > WLC_IW_ISCAN_MAXLEN) || + (((uintptr)bi + dtoh32(bi->length)) > ((uintptr)list + WLC_IW_ISCAN_MAXLEN))) { + WL_ERROR(("%s: Scan results out of bounds: %u\n",__FUNCTION__,dtoh32(bi->length))); + return -E2BIG; + } + + if (event + ETHER_ADDR_LEN + bi->SSID_len + IW_EV_UINT_LEN + IW_EV_FREQ_LEN + + IW_EV_QUAL_LEN >= end) + return -E2BIG; + + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, &bi->BSSID, ETHER_ADDR_LEN); + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_ADDR_LEN); + + iwe.u.data.length = dtoh32(bi->SSID_len); + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, bi->SSID); + + if (dtoh16(bi->capability) & (DOT11_CAP_ESS | DOT11_CAP_IBSS)) { + iwe.cmd = SIOCGIWMODE; + if (dtoh16(bi->capability) & DOT11_CAP_ESS) + iwe.u.mode = IW_MODE_INFRA; + else + iwe.u.mode = IW_MODE_ADHOC; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_UINT_LEN); + } + + iwe.cmd = SIOCGIWFREQ; + channel = (bi->ctl_ch == 0) ? CHSPEC_CHANNEL(bi->chanspec) : bi->ctl_ch; + iwe.u.freq.m = wf_channel2mhz(channel, + channel <= CH_MAX_2G_CHANNEL ? + WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G); + iwe.u.freq.e = 6; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_FREQ_LEN); + + iwe.cmd = IWEVQUAL; + iwe.u.qual.qual = rssi_to_qual(dtoh16(bi->RSSI)); + iwe.u.qual.level = 0x100 + dtoh16(bi->RSSI); + iwe.u.qual.noise = 0x100 + bi->phy_noise; + event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_QUAL_LEN); + + wl_iw_handle_scanresults_ies(&event, end, info, bi); + + iwe.cmd = SIOCGIWENCODE; + if (dtoh16(bi->capability) & DOT11_CAP_PRIVACY) + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + else + iwe.u.data.flags = IW_ENCODE_DISABLED; + iwe.u.data.length = 0; + event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)event); + + if (bi->rateset.count) { + if (event + IW_MAX_BITRATES*IW_EV_PARAM_LEN >= end) + return -E2BIG; + + value = event + IW_EV_LCP_LEN; + iwe.cmd = SIOCGIWRATE; + + iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; + for (j = 0; j < bi->rateset.count && j < IW_MAX_BITRATES; j++) { + iwe.u.bitrate.value = (bi->rateset.rates[j] & 0x7f) * 500000; + value = IWE_STREAM_ADD_VALUE(info, event, value, end, &iwe, + IW_EV_PARAM_LEN); + } + event = value; + } + } + p_buf = p_buf->next; + } + + dwrq->length = event - extra; + dwrq->flags = 0; + +#if !defined(CSCAN) + wl_iw_merge_scan_cache(info, event, buflen_from_user - dwrq->length, &merged_len); + dwrq->length += merged_len; + wl_iw_run_ss_cache_timer(0); + wl_iw_run_ss_cache_timer(1); +#endif /* CSCAN */ +#if defined(CONFIG_FIRST_SCAN) + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_RESULT_CONSUMED; +#endif + + WL_TRACE(("%s return to WE %d bytes APs=%d\n", __FUNCTION__, dwrq->length, counter)); + + return 0; +} +#endif + +static int +wl_iw_set_essid( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + int error; + wl_join_params_t join_params; + int join_params_size; + + WL_TRACE(("%s: SIOCSIWESSID\n", dev->name)); + + + memset(&g_ssid, 0, sizeof(g_ssid)); + + CHECK_EXTRA_FOR_NULL(extra); + + if (dwrq->length && extra) { +#if WIRELESS_EXT > 20 + g_ssid.SSID_len = MIN(sizeof(g_ssid.SSID), dwrq->length); +#else + g_ssid.SSID_len = MIN(sizeof(g_ssid.SSID), dwrq->length-1); +#endif + memcpy(g_ssid.SSID, extra, g_ssid.SSID_len); + } else { + + g_ssid.SSID_len = 0; + } + g_ssid.SSID_len = htod32(g_ssid.SSID_len); + + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid); + + memcpy(&join_params.ssid.SSID, g_ssid.SSID, g_ssid.SSID_len); + join_params.ssid.SSID_len = htod32(g_ssid.SSID_len); + memcpy(&join_params.params.bssid, ðer_bcast, ETHER_ADDR_LEN); + + wl_iw_ch_to_chanspec(g_wl_iw_params.target_channel, &join_params, &join_params_size); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_SSID, &join_params, join_params_size))) { + WL_ERROR(("Invalid ioctl data=%d\n", error)); + return error; + } + + if (g_ssid.SSID_len) { + WL_TRACE(("%s: join SSID=%s ch=%d\n", __FUNCTION__, \ + g_ssid.SSID, g_wl_iw_params.target_channel)); + } + return 0; +} + +static int +wl_iw_get_essid( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wlc_ssid_t ssid; + int error; + + WL_TRACE(("%s: SIOCGIWESSID\n", dev->name)); + + if (!extra) + return -EINVAL; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)))) { + WL_ERROR(("Error getting the SSID\n")); + return error; + } + + ssid.SSID_len = dtoh32(ssid.SSID_len); + + memcpy(extra, ssid.SSID, ssid.SSID_len); + + dwrq->length = ssid.SSID_len; + + dwrq->flags = 1; + + return 0; +} + +static int +wl_iw_set_nick( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = *(wl_iw_t **)netdev_priv(dev); + + WL_TRACE(("%s: SIOCSIWNICKN\n", dev->name)); + + if (!extra) + return -EINVAL; + + if (dwrq->length > sizeof(iw->nickname)) + return -E2BIG; + + memcpy(iw->nickname, extra, dwrq->length); + iw->nickname[dwrq->length - 1] = '\0'; + + return 0; +} + +static int +wl_iw_get_nick( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_iw_t *iw = *(wl_iw_t **)netdev_priv(dev); + + WL_TRACE(("%s: SIOCGIWNICKN\n", dev->name)); + + if (!extra) + return -EINVAL; + + strcpy(extra, iw->nickname); + dwrq->length = strlen(extra) + 1; + + return 0; +} + +static int wl_iw_set_rate( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + wl_rateset_t rateset; + int error, rate, i, error_bg, error_a; + + WL_TRACE(("%s: SIOCSIWRATE\n", dev->name)); + + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, sizeof(rateset)))) + return error; + + rateset.count = dtoh32(rateset.count); + + if (vwrq->value < 0) { + + rate = rateset.rates[rateset.count - 1] & 0x7f; + } else if (vwrq->value < rateset.count) { + + rate = rateset.rates[vwrq->value] & 0x7f; + } else { + + rate = vwrq->value / 500000; + } + + if (vwrq->fixed) { + + error_bg = dev_wlc_intvar_set(dev, "bg_rate", rate); + error_a = dev_wlc_intvar_set(dev, "a_rate", rate); + + if (error_bg && error_a) + return (error_bg | error_a); + } else { + + + error_bg = dev_wlc_intvar_set(dev, "bg_rate", 0); + + error_a = dev_wlc_intvar_set(dev, "a_rate", 0); + + if (error_bg && error_a) + return (error_bg | error_a); + + + for (i = 0; i < rateset.count; i++) + if ((rateset.rates[i] & 0x7f) > rate) + break; + rateset.count = htod32(i); + + + if ((error = dev_wlc_ioctl(dev, WLC_SET_RATESET, &rateset, sizeof(rateset)))) + return error; + } + + return 0; +} + +static int wl_iw_get_rate( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, rate; + + WL_TRACE(("%s: SIOCGIWRATE\n", dev->name)); + + + if ((error = dev_wlc_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate)))) + return error; + rate = dtoh32(rate); + vwrq->value = rate * 500000; + + return 0; +} + +static int +wl_iw_set_rts( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, rts; + + WL_TRACE(("%s: SIOCSIWRTS\n", dev->name)); + + if (vwrq->disabled) + rts = DOT11_DEFAULT_RTS_LEN; + else if (vwrq->value < 0 || vwrq->value > DOT11_DEFAULT_RTS_LEN) + return -EINVAL; + else + rts = vwrq->value; + + if ((error = dev_wlc_intvar_set(dev, "rtsthresh", rts))) + return error; + + return 0; +} + +static int +wl_iw_get_rts( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, rts; + + WL_TRACE(("%s: SIOCGIWRTS\n", dev->name)); + + if ((error = dev_wlc_intvar_get(dev, "rtsthresh", &rts))) + return error; + + vwrq->value = rts; + vwrq->disabled = (rts >= DOT11_DEFAULT_RTS_LEN); + vwrq->fixed = 1; + + return 0; +} + +static int +wl_iw_set_frag( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, frag; + + WL_TRACE(("%s: SIOCSIWFRAG\n", dev->name)); + + if (vwrq->disabled) + frag = DOT11_DEFAULT_FRAG_LEN; + else if (vwrq->value < 0 || vwrq->value > DOT11_DEFAULT_FRAG_LEN) + return -EINVAL; + else + frag = vwrq->value; + + if ((error = dev_wlc_intvar_set(dev, "fragthresh", frag))) + return error; + + return 0; +} + +static int +wl_iw_get_frag( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, fragthreshold; + + WL_TRACE(("%s: SIOCGIWFRAG\n", dev->name)); + + if ((error = dev_wlc_intvar_get(dev, "fragthresh", &fragthreshold))) + return error; + + vwrq->value = fragthreshold; + vwrq->disabled = (fragthreshold >= DOT11_DEFAULT_FRAG_LEN); + vwrq->fixed = 1; + + return 0; +} + +static int +wl_iw_set_txpow( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, disable; + uint16 txpwrmw; + WL_TRACE(("%s: SIOCSIWTXPOW\n", dev->name)); + + + disable = vwrq->disabled ? WL_RADIO_SW_DISABLE : 0; + disable += WL_RADIO_SW_DISABLE << 16; + + disable = htod32(disable); + if ((error = dev_wlc_ioctl(dev, WLC_SET_RADIO, &disable, sizeof(disable)))) + return error; + + + if (disable & WL_RADIO_SW_DISABLE) + return 0; + + + if (!(vwrq->flags & IW_TXPOW_MWATT)) + return -EINVAL; + + + if (vwrq->value < 0) + return 0; + + if (vwrq->value > 0xffff) txpwrmw = 0xffff; + else txpwrmw = (uint16)vwrq->value; + + + error = dev_wlc_intvar_set(dev, "qtxpower", (int)(bcm_mw_to_qdbm(txpwrmw))); + return error; +} + +static int +wl_iw_get_txpow( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, disable, txpwrdbm; + uint8 result; + + WL_TRACE(("%s: SIOCGIWTXPOW\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_RADIO, &disable, sizeof(disable))) || + (error = dev_wlc_intvar_get(dev, "qtxpower", &txpwrdbm))) + return error; + + disable = dtoh32(disable); + result = (uint8)(txpwrdbm & ~WL_TXPWR_OVERRIDE); + vwrq->value = (int32)bcm_qdbm_to_mw(result); + vwrq->fixed = 0; + vwrq->disabled = (disable & (WL_RADIO_SW_DISABLE | WL_RADIO_HW_DISABLE)) ? 1 : 0; + vwrq->flags = IW_TXPOW_MWATT; + + return 0; +} + +#if WIRELESS_EXT > 10 +static int +wl_iw_set_retry( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, lrl, srl; + + WL_TRACE(("%s: SIOCSIWRETRY\n", dev->name)); + + + if (vwrq->disabled || (vwrq->flags & IW_RETRY_LIFETIME)) + return -EINVAL; + + + if (vwrq->flags & IW_RETRY_LIMIT) { + + +#if WIRELESS_EXT > 20 + if ((vwrq->flags & IW_RETRY_LONG) ||(vwrq->flags & IW_RETRY_MAX) || + !((vwrq->flags & IW_RETRY_SHORT) || (vwrq->flags & IW_RETRY_MIN))) { +#else + if ((vwrq->flags & IW_RETRY_MAX) || !(vwrq->flags & IW_RETRY_MIN)) { +#endif + lrl = htod32(vwrq->value); + if ((error = dev_wlc_ioctl(dev, WLC_SET_LRL, &lrl, sizeof(lrl)))) + return error; + } + + +#if WIRELESS_EXT > 20 + if ((vwrq->flags & IW_RETRY_SHORT) ||(vwrq->flags & IW_RETRY_MIN) || + !((vwrq->flags & IW_RETRY_LONG) || (vwrq->flags & IW_RETRY_MAX))) { +#else + if ((vwrq->flags & IW_RETRY_MIN) || !(vwrq->flags & IW_RETRY_MAX)) { +#endif + srl = htod32(vwrq->value); + if ((error = dev_wlc_ioctl(dev, WLC_SET_SRL, &srl, sizeof(srl)))) + return error; + } + } + return 0; +} + +static int +wl_iw_get_retry( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, lrl, srl; + + WL_TRACE(("%s: SIOCGIWRETRY\n", dev->name)); + + vwrq->disabled = 0; + + + if ((vwrq->flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME) + return -EINVAL; + + + if ((error = dev_wlc_ioctl(dev, WLC_GET_LRL, &lrl, sizeof(lrl))) || + (error = dev_wlc_ioctl(dev, WLC_GET_SRL, &srl, sizeof(srl)))) + return error; + + lrl = dtoh32(lrl); + srl = dtoh32(srl); + + + if (vwrq->flags & IW_RETRY_MAX) { + vwrq->flags = IW_RETRY_LIMIT | IW_RETRY_MAX; + vwrq->value = lrl; + } else { + vwrq->flags = IW_RETRY_LIMIT; + vwrq->value = srl; + if (srl != lrl) + vwrq->flags |= IW_RETRY_MIN; + } + + return 0; +} +#endif + +static int +wl_iw_set_encode( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_wsec_key_t key; + int error, val, wsec; + + WL_TRACE(("%s: SIOCSIWENCODE\n", dev->name)); + + memset(&key, 0, sizeof(key)); + + if ((dwrq->flags & IW_ENCODE_INDEX) == 0) { + + for (key.index = 0; key.index < DOT11_MAX_DEFAULT_KEYS; key.index++) { + val = htod32(key.index); + if ((error = dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, sizeof(val)))) + return error; + val = dtoh32(val); + if (val) + break; + } + + if (key.index == DOT11_MAX_DEFAULT_KEYS) + key.index = 0; + } else { + key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + if (key.index >= DOT11_MAX_DEFAULT_KEYS) + return -EINVAL; + } + + + if (!extra || !dwrq->length || (dwrq->flags & IW_ENCODE_NOKEY)) { + + val = htod32(key.index); + if ((error = dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY, &val, sizeof(val)))) + return error; + } else { + key.len = dwrq->length; + + if (dwrq->length > sizeof(key.data)) + return -EINVAL; + + memcpy(key.data, extra, dwrq->length); + + key.flags = WL_PRIMARY_KEY; + switch (key.len) { + case WEP1_KEY_SIZE: + key.algo = CRYPTO_ALGO_WEP1; + break; + case WEP128_KEY_SIZE: + key.algo = CRYPTO_ALGO_WEP128; + break; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) + case TKIP_KEY_SIZE: + key.algo = CRYPTO_ALGO_TKIP; + break; +#endif + case AES_KEY_SIZE: + key.algo = CRYPTO_ALGO_AES_CCM; + break; + default: + return -EINVAL; + } + + + swap_key_from_BE(&key); + if ((error = dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)))) + return error; + } + + + val = (dwrq->flags & IW_ENCODE_DISABLED) ? 0 : WEP_ENABLED; + + if ((error = dev_wlc_intvar_get(dev, "wsec", &wsec))) + return error; + + wsec &= ~(WEP_ENABLED); + wsec |= val; + + if ((error = dev_wlc_intvar_set(dev, "wsec", wsec))) + return error; + + + val = (dwrq->flags & IW_ENCODE_RESTRICTED) ? 1 : 0; + val = htod32(val); + if ((error = dev_wlc_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val)))) + return error; + + return 0; +} + +static int +wl_iw_get_encode( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_wsec_key_t key; + int error, val, wsec, auth; + + WL_TRACE(("%s: SIOCGIWENCODE\n", dev->name)); + + + bzero(&key, sizeof(wl_wsec_key_t)); + + if ((dwrq->flags & IW_ENCODE_INDEX) == 0) { + + for (key.index = 0; key.index < DOT11_MAX_DEFAULT_KEYS; key.index++) { + val = key.index; + if ((error = dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, sizeof(val)))) + return error; + val = dtoh32(val); + if (val) + break; + } + } else + key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + + if (key.index >= DOT11_MAX_DEFAULT_KEYS) + key.index = 0; + + + + if ((error = dev_wlc_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec))) || + (error = dev_wlc_ioctl(dev, WLC_GET_AUTH, &auth, sizeof(auth)))) + return error; + + swap_key_to_BE(&key); + + wsec = dtoh32(wsec); + auth = dtoh32(auth); + + dwrq->length = MIN(DOT11_MAX_KEY_SIZE, key.len); + + + dwrq->flags = key.index + 1; + if (!(wsec & (WEP_ENABLED | TKIP_ENABLED | AES_ENABLED))) { + + dwrq->flags |= IW_ENCODE_DISABLED; + } + if (auth) { + + dwrq->flags |= IW_ENCODE_RESTRICTED; + } + + + if (dwrq->length && extra) + memcpy(extra, key.data, dwrq->length); + + return 0; +} + +static int +wl_iw_set_power( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, pm; + + WL_TRACE(("%s: SIOCSIWPOWER\n", dev->name)); + + pm = vwrq->disabled ? PM_OFF : PM_MAX; + + pm = htod32(pm); + if ((error = dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)))) + return error; + + return 0; +} + +static int +wl_iw_get_power( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error, pm; + + WL_TRACE(("%s: SIOCGIWPOWER\n", dev->name)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_PM, &pm, sizeof(pm)))) + return error; + + pm = dtoh32(pm); + vwrq->disabled = pm ? 0 : 1; + vwrq->flags = IW_POWER_ALL_R; + + return 0; +} + +#if WIRELESS_EXT > 17 +static int +wl_iw_set_wpaie( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *iwp, + char *extra +) +{ + uchar buf[WLC_IOCTL_SMLEN] = {0}; + uchar *p = buf; + int wapi_ie_size; + + WL_TRACE(("%s: SIOCSIWGENIE\n", dev->name)); + + CHECK_EXTRA_FOR_NULL(extra); + + if (extra[0] == DOT11_MNG_WAPI_ID) + { + wapi_ie_size = iwp->length; + memcpy(p, extra, iwp->length); + dev_wlc_bufvar_set(dev, "wapiie", buf, wapi_ie_size); + } + else + dev_wlc_bufvar_set(dev, "wpaie", extra, iwp->length); + + return 0; +} + +static int +wl_iw_get_wpaie( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *iwp, + char *extra +) +{ + WL_TRACE(("%s: SIOCGIWGENIE\n", dev->name)); + iwp->length = 64; + dev_wlc_bufvar_get(dev, "wpaie", extra, iwp->length); + return 0; +} + +static int +wl_iw_set_encodeext( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra +) +{ + wl_wsec_key_t key; + int error; + struct iw_encode_ext *iwe; + + WL_WSEC(("%s: SIOCSIWENCODEEXT\n", dev->name)); + + CHECK_EXTRA_FOR_NULL(extra); + + memset(&key, 0, sizeof(key)); + iwe = (struct iw_encode_ext *)extra; + + + if (dwrq->flags & IW_ENCODE_DISABLED) { + + } + + + key.index = 0; + if (dwrq->flags & IW_ENCODE_INDEX) + key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + + key.len = iwe->key_len; + + + if (!ETHER_ISMULTI(iwe->addr.sa_data)) + bcopy((void *)&iwe->addr.sa_data, (char *)&key.ea, ETHER_ADDR_LEN); + + + if (key.len == 0) { + if (iwe->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + WL_WSEC(("Changing the the primary Key to %d\n", key.index)); + + key.index = htod32(key.index); + error = dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY, + &key.index, sizeof(key.index)); + if (error) + return error; + } + + else { + swap_key_from_BE(&key); + dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + } + } + else { + if (iwe->key_len > sizeof(key.data)) + return -EINVAL; + + WL_WSEC(("Setting the key index %d\n", key.index)); + if (iwe->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + WL_WSEC(("key is a Primary Key\n")); + key.flags = WL_PRIMARY_KEY; + } + + bcopy((void *)iwe->key, key.data, iwe->key_len); + + if (iwe->alg == IW_ENCODE_ALG_TKIP) { + uint8 keybuf[8]; + bcopy(&key.data[24], keybuf, sizeof(keybuf)); + bcopy(&key.data[16], &key.data[24], sizeof(keybuf)); + bcopy(keybuf, &key.data[16], sizeof(keybuf)); + } + + + if (iwe->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) { + uchar *ivptr; + ivptr = (uchar *)iwe->rx_seq; + key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | + (ivptr[3] << 8) | ivptr[2]; + key.rxiv.lo = (ivptr[1] << 8) | ivptr[0]; + key.iv_initialized = TRUE; + } + + switch (iwe->alg) { + case IW_ENCODE_ALG_NONE: + key.algo = CRYPTO_ALGO_OFF; + break; + case IW_ENCODE_ALG_WEP: + if (iwe->key_len == WEP1_KEY_SIZE) + key.algo = CRYPTO_ALGO_WEP1; + else + key.algo = CRYPTO_ALGO_WEP128; + break; + case IW_ENCODE_ALG_TKIP: + key.algo = CRYPTO_ALGO_TKIP; + break; + case IW_ENCODE_ALG_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + break; + case IW_ENCODE_ALG_SM4: + key.algo = CRYPTO_ALGO_SMS4; + if (iwe->ext_flags & IW_ENCODE_EXT_GROUP_KEY) { + key.flags &= ~WL_PRIMARY_KEY; + } + break; + default: + break; + } + swap_key_from_BE(&key); + + dhd_wait_pend8021x(dev); + + error = dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + if (error) + return error; + } + return 0; +} + +#if WIRELESS_EXT > 17 +#ifdef BCMWPA2 +struct { + pmkid_list_t pmkids; + pmkid_t foo[MAXPMKID-1]; +} pmkid_list; + +static int +wl_iw_set_pmksa( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + struct iw_pmksa *iwpmksa; + uint i; + int ret = 0; + char eabuf[ETHER_ADDR_STR_LEN]; + + WL_WSEC(("%s: SIOCSIWPMKSA\n", dev->name)); + CHECK_EXTRA_FOR_NULL(extra); + + iwpmksa = (struct iw_pmksa *)extra; + bzero((char *)eabuf, ETHER_ADDR_STR_LEN); + + if (iwpmksa->cmd == IW_PMKSA_FLUSH) { + WL_WSEC(("wl_iw_set_pmksa - IW_PMKSA_FLUSH\n")); + bzero((char *)&pmkid_list, sizeof(pmkid_list)); + } + + else if (iwpmksa->cmd == IW_PMKSA_REMOVE) { + { + pmkid_list_t pmkid, *pmkidptr; + uint j; + pmkidptr = &pmkid; + + bcopy(&iwpmksa->bssid.sa_data[0], &pmkidptr->pmkid[0].BSSID, ETHER_ADDR_LEN); + bcopy(&iwpmksa->pmkid[0], &pmkidptr->pmkid[0].PMKID, WPA2_PMKID_LEN); + + WL_WSEC(("wl_iw_set_pmksa,IW_PMKSA_REMOVE - PMKID: %s = ", + bcm_ether_ntoa(&pmkidptr->pmkid[0].BSSID, + eabuf))); + for (j = 0; j < WPA2_PMKID_LEN; j++) + WL_WSEC(("%02x ", pmkidptr->pmkid[0].PMKID[j])); + WL_WSEC(("\n")); + } + + for (i = 0; i < pmkid_list.pmkids.npmkid; i++) + if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_list.pmkids.pmkid[i].BSSID, + ETHER_ADDR_LEN)) + break; + + if ((pmkid_list.pmkids.npmkid > 0) && (i < pmkid_list.pmkids.npmkid)) { + bzero(&pmkid_list.pmkids.pmkid[i], sizeof(pmkid_t)); + for (; i < (pmkid_list.pmkids.npmkid - 1); i++) { + bcopy(&pmkid_list.pmkids.pmkid[i+1].BSSID, + &pmkid_list.pmkids.pmkid[i].BSSID, + ETHER_ADDR_LEN); + bcopy(&pmkid_list.pmkids.pmkid[i+1].PMKID, + &pmkid_list.pmkids.pmkid[i].PMKID, + WPA2_PMKID_LEN); + } + pmkid_list.pmkids.npmkid--; + } + else + ret = -EINVAL; + } + + else if (iwpmksa->cmd == IW_PMKSA_ADD) { + for (i = 0; i < pmkid_list.pmkids.npmkid; i++) + if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_list.pmkids.pmkid[i].BSSID, + ETHER_ADDR_LEN)) + break; + if (i < MAXPMKID) { + bcopy(&iwpmksa->bssid.sa_data[0], + &pmkid_list.pmkids.pmkid[i].BSSID, + ETHER_ADDR_LEN); + bcopy(&iwpmksa->pmkid[0], &pmkid_list.pmkids.pmkid[i].PMKID, + WPA2_PMKID_LEN); + if (i == pmkid_list.pmkids.npmkid) + pmkid_list.pmkids.npmkid++; + } + else + ret = -EINVAL; + + { + uint j; + uint k; + k = pmkid_list.pmkids.npmkid; + WL_WSEC(("wl_iw_set_pmksa,IW_PMKSA_ADD - PMKID: %s = ", + bcm_ether_ntoa(&pmkid_list.pmkids.pmkid[k].BSSID, + eabuf))); + for (j = 0; j < WPA2_PMKID_LEN; j++) + WL_WSEC(("%02x ", pmkid_list.pmkids.pmkid[k].PMKID[j])); + WL_WSEC(("\n")); + } + } + WL_WSEC(("PRINTING pmkid LIST - No of elements %d, ret = %d\n", pmkid_list.pmkids.npmkid, ret)); + for (i = 0; i < pmkid_list.pmkids.npmkid; i++) { + uint j; + WL_WSEC(("PMKID[%d]: %s = ", i, + bcm_ether_ntoa(&pmkid_list.pmkids.pmkid[i].BSSID, + eabuf))); + for (j = 0; j < WPA2_PMKID_LEN; j++) + WL_WSEC(("%02x ", pmkid_list.pmkids.pmkid[i].PMKID[j])); + WL_WSEC(("\n")); + } + WL_WSEC(("\n")); + + if (!ret) + ret = dev_wlc_bufvar_set(dev, "pmkid_info", (char *)&pmkid_list, sizeof(pmkid_list)); + return ret; +} +#endif +#endif + +static int +wl_iw_get_encodeext( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + WL_WSEC(("%s: SIOCGIWENCODEEXT\n", dev->name)); + return 0; +} + +static int +wl_iw_set_wpaauth( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error = 0; + int paramid; + int paramval; + int val = 0; + wl_iw_t *iw = *(wl_iw_t **)netdev_priv(dev); + + WL_WSEC(("%s: SIOCSIWAUTH\n", dev->name)); + +#if defined(SOFTAP) + if (ap_cfg_running) { + WL_TRACE(("%s: Not executed, reason -'SOFTAP is active'\n", __FUNCTION__)); + return 0; + } +#endif + + paramid = vwrq->flags & IW_AUTH_INDEX; + paramval = vwrq->value; + + WL_WSEC(("%s: SIOCSIWAUTH, paramid = 0x%0x, paramval = 0x%0x\n", + dev->name, paramid, paramval)); + + switch (paramid) { + case IW_AUTH_WPA_VERSION: + + if (paramval & IW_AUTH_WPA_VERSION_DISABLED) + val = WPA_AUTH_DISABLED; + else if (paramval & (IW_AUTH_WPA_VERSION_WPA)) + val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; +#ifdef BCMWPA2 + else if (paramval & IW_AUTH_WPA_VERSION_WPA2) + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; +#endif + else if (paramval & IW_AUTH_WAPI_VERSION_1) + val = WPA_AUTH_WAPI; + WL_WSEC(("%s: %d: setting wpa_auth to 0x%0x\n", __FUNCTION__, __LINE__, val)); + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", val))) + return error; + break; + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + + + if (paramval & (IW_AUTH_CIPHER_WEP40 | IW_AUTH_CIPHER_WEP104)) + val = WEP_ENABLED; + if (paramval & IW_AUTH_CIPHER_TKIP) + val = TKIP_ENABLED; + if (paramval & IW_AUTH_CIPHER_CCMP) + val = AES_ENABLED; + if (paramval & IW_AUTH_CIPHER_SMS4) + val = SMS4_ENABLED; + + if (paramid == IW_AUTH_CIPHER_PAIRWISE) { + iw->pwsec = val; + val |= iw->gwsec; + } + else { + iw->gwsec = val; + val |= iw->pwsec; + } + + if (iw->privacy_invoked && !val) { + WL_WSEC(("%s: %s: 'Privacy invoked' TRUE but clearing wsec, assuming " + "we're a WPS enrollee\n", dev->name, __FUNCTION__)); + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", TRUE))) { + WL_ERROR(("Failed to set iovar is_WPS_enrollee\n")); + return error; + } + } else if (val) { + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", FALSE))) { + WL_ERROR(("Failed to clear iovar is_WPS_enrollee\n")); + return error; + } + } + + if ((error = dev_wlc_intvar_set(dev, "wsec", val))) { + WL_ERROR(("Failed to set 'wsec'iovar\n")); + return error; + } + + break; + + case IW_AUTH_KEY_MGMT: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) { + WL_ERROR(("Failed to get 'wpa_auth'iovar\n")); + return error; + } + + if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { + if (paramval & IW_AUTH_KEY_MGMT_PSK) + val = WPA_AUTH_PSK; + else + val = WPA_AUTH_UNSPECIFIED; + } +#ifdef BCMWPA2 + else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { + if (paramval & IW_AUTH_KEY_MGMT_PSK) + val = WPA2_AUTH_PSK; + else + val = WPA2_AUTH_UNSPECIFIED; + } +#endif + if (paramval & (IW_AUTH_KEY_MGMT_WAPI_PSK | IW_AUTH_KEY_MGMT_WAPI_CERT)) + val = WPA_AUTH_WAPI; + WL_WSEC(("%s: %d: setting wpa_auth to %d\n", __FUNCTION__, __LINE__, val)); + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", val))) { + WL_ERROR(("Failed to set 'wpa_auth'iovar\n")); + return error; + } + + break; + case IW_AUTH_TKIP_COUNTERMEASURES: + if ((error = dev_wlc_bufvar_set(dev, "tkip_countermeasures", \ + (char *)¶mval, sizeof(paramval)))) + WL_WSEC(("%s: tkip_countermeasures failed %d\n", __FUNCTION__, error)); + break; + + case IW_AUTH_80211_AUTH_ALG: + + WL_WSEC(("Setting the D11auth %d\n", paramval)); + if (paramval == IW_AUTH_ALG_OPEN_SYSTEM) + val = 0; + else if (paramval == IW_AUTH_ALG_SHARED_KEY) + val = 1; + else if (paramval == (IW_AUTH_ALG_OPEN_SYSTEM | IW_AUTH_ALG_SHARED_KEY)) + val = 2; + else + error = 1; + if (!error && (error = dev_wlc_intvar_set(dev, "auth", val))) + return error; + break; + + case IW_AUTH_WPA_ENABLED: + if (paramval == 0) { + iw->pwsec = 0; + iw->gwsec = 0; + if ((error = dev_wlc_intvar_get(dev, "wsec", &val))) { + WL_ERROR(("Failed to get 'wsec'iovar\n")); + return error; + } + if (val & (TKIP_ENABLED | AES_ENABLED)) { + val &= ~(TKIP_ENABLED | AES_ENABLED); + dev_wlc_intvar_set(dev, "wsec", val); + } + val = 0; + + WL_INFORM(("%s: %d: setting wpa_auth to %d\n", + __FUNCTION__, __LINE__, val)); + error = dev_wlc_intvar_set(dev, "wpa_auth", 0); + if (error) + WL_ERROR(("Failed to set 'wpa_auth'iovar\n")); + return error; + } + + + break; + + case IW_AUTH_DROP_UNENCRYPTED: + error = dev_wlc_bufvar_set(dev, "wsec_restrict", \ + (char *)¶mval, sizeof(paramval)); + if (error) + WL_ERROR(("%s: wsec_restrict %d\n", __FUNCTION__, error)); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + error = dev_wlc_bufvar_set(dev, "rx_unencrypted_eapol", \ + (char *)¶mval, sizeof(paramval)); + if (error) + WL_WSEC(("%s: rx_unencrypted_eapol %d\n", __FUNCTION__, error)); + break; + +#if WIRELESS_EXT > 17 + case IW_AUTH_ROAMING_CONTROL: + WL_INFORM(("%s: IW_AUTH_ROAMING_CONTROL\n", __FUNCTION__)); + + break; + case IW_AUTH_PRIVACY_INVOKED: { + int wsec; + + if (paramval == 0) { + iw->privacy_invoked = FALSE; + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", FALSE))) { + WL_WSEC(("Failed to clear iovar is_WPS_enrollee\n")); + return error; + } + } else { + iw->privacy_invoked = TRUE; + if ((error = dev_wlc_intvar_get(dev, "wsec", &wsec))) + return error; + + if (!(IW_WSEC_ENABLED(wsec))) { + + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", TRUE))) { + WL_WSEC(("Failed to set iovar is_WPS_enrollee\n")); + return error; + } + } else { + if ((error = dev_wlc_intvar_set(dev, "is_WPS_enrollee", FALSE))) { + WL_WSEC(("Failed to clear iovar is_WPS_enrollee\n")); + return error; + } + } + } + break; + } +#endif + case IW_AUTH_WAPI_ENABLED: + if ((error = dev_wlc_intvar_get(dev, "wsec", &val))) + return error; + if (paramval) { + val |= SMS4_ENABLED; + if ((error = dev_wlc_intvar_set(dev, "wsec", val))) { + WL_ERROR(("%s: setting wsec to 0x%0x returned error %d\n", + __FUNCTION__, val, error)); + return error; + } + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", WPA_AUTH_WAPI))) { + WL_ERROR(("%s: setting wpa_auth(WPA_AUTH_WAPI) returned %d\n", + __FUNCTION__, error)); + return error; + } + } + + break; + default: + break; + } + return 0; +} +#ifdef BCMWPA2 +#define VAL_PSK(_val) (((_val) & WPA_AUTH_PSK) || ((_val) & WPA2_AUTH_PSK)) +#else +#define VAL_PSK(_val) (((_val) & WPA_AUTH_PSK)) +#endif + +static int +wl_iw_get_wpaauth( + struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, + char *extra +) +{ + int error; + int paramid; + int paramval = 0; + int val; + wl_iw_t *iw = *(wl_iw_t **)netdev_priv(dev); + + WL_TRACE(("%s: SIOCGIWAUTH\n", dev->name)); + + paramid = vwrq->flags & IW_AUTH_INDEX; + + switch (paramid) { + case IW_AUTH_WPA_VERSION: + + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + if (val & (WPA_AUTH_NONE | WPA_AUTH_DISABLED)) + paramval = IW_AUTH_WPA_VERSION_DISABLED; + else if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) + paramval = IW_AUTH_WPA_VERSION_WPA; +#ifdef BCMWPA2 + else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) + paramval = IW_AUTH_WPA_VERSION_WPA2; +#endif + break; + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + if (paramid == IW_AUTH_CIPHER_PAIRWISE) + val = iw->pwsec; + else + val = iw->gwsec; + + paramval = 0; + if (val) { + if (val & WEP_ENABLED) + paramval |= (IW_AUTH_CIPHER_WEP40 | IW_AUTH_CIPHER_WEP104); + if (val & TKIP_ENABLED) + paramval |= (IW_AUTH_CIPHER_TKIP); + if (val & AES_ENABLED) + paramval |= (IW_AUTH_CIPHER_CCMP); + } + else + paramval = IW_AUTH_CIPHER_NONE; + break; + case IW_AUTH_KEY_MGMT: + + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + if (VAL_PSK(val)) + paramval = IW_AUTH_KEY_MGMT_PSK; + else + paramval = IW_AUTH_KEY_MGMT_802_1X; + + break; + case IW_AUTH_TKIP_COUNTERMEASURES: + error = dev_wlc_bufvar_get(dev, "tkip_countermeasures", \ + (char *)¶mval, sizeof(paramval)); + if (error) + WL_ERROR(("%s get tkip_countermeasures %d\n", __FUNCTION__, error)); + break; + + case IW_AUTH_DROP_UNENCRYPTED: + error = dev_wlc_bufvar_get(dev, "wsec_restrict", \ + (char *)¶mval, sizeof(paramval)); + if (error) + WL_ERROR(("%s get wsec_restrict %d\n", __FUNCTION__, error)); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + error = dev_wlc_bufvar_get(dev, "rx_unencrypted_eapol", \ + (char *)¶mval, sizeof(paramval)); + if (error) + WL_ERROR(("%s get rx_unencrypted_eapol %d\n", __FUNCTION__, error)); + break; + + case IW_AUTH_80211_AUTH_ALG: + + if ((error = dev_wlc_intvar_get(dev, "auth", &val))) + return error; + if (!val) + paramval = IW_AUTH_ALG_OPEN_SYSTEM; + else + paramval = IW_AUTH_ALG_SHARED_KEY; + break; + case IW_AUTH_WPA_ENABLED: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + return error; + if (val) + paramval = TRUE; + else + paramval = FALSE; + break; +#if WIRELESS_EXT > 17 + case IW_AUTH_ROAMING_CONTROL: + WL_ERROR(("%s: IW_AUTH_ROAMING_CONTROL\n", __FUNCTION__)); + + break; + case IW_AUTH_PRIVACY_INVOKED: + paramval = iw->privacy_invoked; + break; +#endif + } + vwrq->value = paramval; + return 0; +} +#endif + + +#ifdef SOFTAP + +static int ap_macmode = MACLIST_MODE_DISABLED; +static struct mflist ap_black_list; +static int +wl_iw_parse_wep(char *keystr, wl_wsec_key_t *key) +{ + char hex[] = "XX"; + unsigned char *data = key->data; + + switch (strlen(keystr)) { + case 5: + case 13: + case 16: + key->len = strlen(keystr); + memcpy(data, keystr, key->len + 1); + break; + case 12: + case 28: + case 34: + case 66: + if (!strnicmp(keystr, "0x", 2)) + keystr += 2; + else + return -1; + case 10: + case 26: + case 32: + case 64: + key->len = strlen(keystr) / 2; + while (*keystr) { + strncpy(hex, keystr, 2); + *data++ = (char) bcm_strtoul(hex, NULL, 16); + keystr += 2; + } + break; + default: + return -1; + } + + switch (key->len) { + case 5: + key->algo = CRYPTO_ALGO_WEP1; + break; + case 13: + key->algo = CRYPTO_ALGO_WEP128; + break; + case 16: + key->algo = CRYPTO_ALGO_AES_CCM; + break; + case 32: + key->algo = CRYPTO_ALGO_TKIP; + break; + default: + return -1; + } + + key->flags |= WL_PRIMARY_KEY; + + return 0; +} + +#ifdef EXT_WPA_CRYPTO +#define SHA1HashSize 20 +extern void pbkdf2_sha1(const char *passphrase, const char *ssid, size_t ssid_len, + int iterations, u8 *buf, size_t buflen); + +#else + +#define SHA1HashSize 20 +int pbkdf2_sha1(const char *passphrase, const char *ssid, size_t ssid_len, + int iterations, u8 *buf, size_t buflen) +{ + WL_ERROR(("WARNING: %s is not implemented !!!\n", __FUNCTION__)); + return -1; +} + +#endif + + +int dev_iw_write_cfg1_bss_var(struct net_device *dev, int val) +{ + struct { + int cfg; + int val; + } bss_setbuf; + + int bss_set_res; + char smbuf[WLC_IOCTL_SMLEN]; + memset(smbuf, 0, sizeof(smbuf)); + + bss_setbuf.cfg = 1; + bss_setbuf.val = val; + + bss_set_res = dev_iw_iovar_setbuf(dev, "bss", + &bss_setbuf, sizeof(bss_setbuf), smbuf, sizeof(smbuf)); + WL_TRACE(("%s: bss_set_result:%d set with %d\n", __FUNCTION__, bss_set_res, val)); + + return bss_set_res; +} + + +int dev_iw_read_cfg1_bss_var(struct net_device *dev, int *val) +{ + int bsscfg_idx = 1; + int bss_set_res; + char smbuf[WLC_IOCTL_SMLEN]; + memset(smbuf, 0, sizeof(smbuf)); + + bss_set_res = dev_iw_iovar_getbuf(dev, "bss", \ + &bsscfg_idx, sizeof(bsscfg_idx), smbuf, sizeof(smbuf)); + *val = *(int*)smbuf; + *val = dtoh32(*val); + WL_TRACE(("%s: status=%d bss_get_result=%d\n", __FUNCTION__, bss_set_res, *val)); + return bss_set_res; +} + + +#ifndef AP_ONLY +static int wl_bssiovar_mkbuf( + const char *iovar, + int bssidx, + void *param, + int paramlen, + void *bufptr, + int buflen, + int *perr) +{ + const char *prefix = "bsscfg:"; + int8 *p; + uint prefixlen; + uint namelen; + uint iolen; + + prefixlen = strlen(prefix); + namelen = strlen(iovar) + 1; + iolen = prefixlen + namelen + sizeof(int) + paramlen; + + if (buflen < 0 || iolen > (uint)buflen) { + *perr = BCME_BUFTOOSHORT; + return 0; + } + + p = (int8 *)bufptr; + + memcpy(p, prefix, prefixlen); + p += prefixlen; + + memcpy(p, iovar, namelen); + p += namelen; + + bssidx = htod32(bssidx); + memcpy(p, &bssidx, sizeof(int32)); + p += sizeof(int32); + + if (paramlen) + memcpy(p, param, paramlen); + + *perr = 0; + return iolen; +} +#endif + + +int get_user_params(char *user_params, struct iw_point *dwrq) +{ + int ret = 0; + + if (copy_from_user(user_params, dwrq->pointer, dwrq->length)) { + WL_ERROR(("\n%s: no user params: uptr:%p, ulen:%d\n", + __FUNCTION__, dwrq->pointer, dwrq->length)); + return -EFAULT; + } + + WL_TRACE(("\n%s: iwpriv user params:%s\n", __FUNCTION__, user_params)); + + return ret; +} + + +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) + +#if defined(CSCAN) + +static int +wl_iw_combined_scan_set(struct net_device *dev, wlc_ssid_t* ssids_local, int nssid, int nchan) +{ + int params_size = WL_SCAN_PARAMS_FIXED_SIZE + WL_NUMCHANNELS * sizeof(uint16); + int err = 0; + char *p; + int i; + iscan_info_t *iscan = g_iscan; + + WL_SCAN(("%s nssid=%d nchan=%d\n", __FUNCTION__, nssid, nchan)); + + if ((!dev) && (!g_iscan) && (!iscan->iscan_ex_params_p)) { + WL_ERROR(("%s error exit\n", __FUNCTION__)); + err = -1; + goto exit; + } + +#ifdef PNO_SUPPORT + if (dhd_dev_get_pno_status(dev)) { + WL_ERROR(("%s: Scan called when PNO is active\n", __FUNCTION__)); + } +#endif + + params_size += WL_SCAN_PARAMS_SSID_MAX * sizeof(wlc_ssid_t); + + if (nssid > 0) { + i = OFFSETOF(wl_scan_params_t, channel_list) + nchan * sizeof(uint16); + i = ROUNDUP(i, sizeof(uint32)); + if (i + nssid * sizeof(wlc_ssid_t) > params_size) { + printf("additional ssids exceed params_size\n"); + err = -1; + goto exit; + } + + p = ((char*)&iscan->iscan_ex_params_p->params) + i; + memcpy(p, ssids_local, nssid * sizeof(wlc_ssid_t)); + p += nssid * sizeof(wlc_ssid_t); + } else { + p = (char*)iscan->iscan_ex_params_p->params.channel_list + nchan * sizeof(uint16); + } + + iscan->iscan_ex_params_p->params.channel_num = \ + htod32((nssid << WL_SCAN_PARAMS_NSSID_SHIFT) | \ + (nchan & WL_SCAN_PARAMS_COUNT_MASK)); + + nssid = \ + (uint)((iscan->iscan_ex_params_p->params.channel_num >> WL_SCAN_PARAMS_NSSID_SHIFT) & \ + WL_SCAN_PARAMS_COUNT_MASK); + + params_size = (int) (p - (char*)iscan->iscan_ex_params_p + nssid * sizeof(wlc_ssid_t)); + iscan->iscan_ex_param_size = params_size; + + iscan->list_cur = iscan->list_hdr; + iscan->iscan_state = ISCAN_STATE_SCANING; + wl_iw_set_event_mask(dev); + mod_timer(&iscan->timer, jiffies + iscan->timer_ms*HZ/1000); + + iscan->timer_on = 1; + +#ifdef SCAN_DUMP + { + int i; + WL_SCAN(("\n### List of SSIDs to scan ###\n")); + for (i = 0; i < nssid; i++) { + if (!ssids_local[i].SSID_len) + WL_SCAN(("%d: Broadcast scan\n", i)); + else + WL_SCAN(("%d: scan for %s size =%d\n", i, \ + ssids_local[i].SSID, ssids_local[i].SSID_len)); + } + WL_SCAN(("### List of channels to scan ###\n")); + for (i = 0; i < nchan; i++) + { + WL_SCAN(("%d ", iscan->iscan_ex_params_p->params.channel_list[i])); + } + WL_SCAN(("\nnprobes=%d\n", iscan->iscan_ex_params_p->params.nprobes)); + WL_SCAN(("active_time=%d\n", iscan->iscan_ex_params_p->params.active_time)); + WL_SCAN(("passive_time=%d\n", iscan->iscan_ex_params_p->params.passive_time)); + WL_SCAN(("home_time=%d\n", iscan->iscan_ex_params_p->params.home_time)); + WL_SCAN(("scan_type=%d\n", iscan->iscan_ex_params_p->params.scan_type)); + WL_SCAN(("\n###################\n")); + } +#endif + + if (params_size > WLC_IOCTL_MEDLEN) { + WL_ERROR(("Set ISCAN for %s due to params_size=%d \n", \ + __FUNCTION__, params_size)); + err = -1; + } + + if ((err = dev_iw_iovar_setbuf(dev, "iscan", iscan->iscan_ex_params_p, \ + iscan->iscan_ex_param_size, \ + iscan->ioctlbuf, sizeof(iscan->ioctlbuf)))) { + WL_ERROR(("Set ISCAN for %s failed with %d\n", __FUNCTION__, err)); + err = -1; + } + +exit: + + return err; +} + + +static int iwpriv_set_cscan(struct net_device *dev, struct iw_request_info *info, \ + union iwreq_data *wrqu, char *ext) +{ + int res = 0; + char *extra = NULL; + iscan_info_t *iscan = g_iscan; + wlc_ssid_t ssids_local[WL_SCAN_PARAMS_SSID_MAX]; + int nssid = 0; + int nchan = 0; + + WL_TRACE(("\%s: info->cmd:%x, info->flags:%x, u.data=0x%p, u.len=%d\n", + __FUNCTION__, info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (g_onoff == G_WLAN_SET_OFF) { + WL_TRACE(("%s: driver is not up yet after START\n", __FUNCTION__)); + return -1; + } + +#ifdef PNO_SET_DEBUG + wl_iw_set_pno_set(dev, info, wrqu, extra); + return 0; +#endif + + if (wrqu->data.length != 0) { + + char *str_ptr; + + if (!iscan->iscan_ex_params_p) { + return -EFAULT; + } + + if (!(extra = kmalloc(wrqu->data.length+1, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(extra, wrqu->data.pointer, wrqu->data.length)) { + kfree(extra); + return -EFAULT; + } + + extra[wrqu->data.length] = 0; + WL_ERROR(("Got str param in iw_point:\n %s\n", extra)); + + str_ptr = extra; + + if (strncmp(str_ptr, GET_SSID, strlen(GET_SSID))) { + WL_ERROR(("%s Error: extracting SSID='' string\n", __FUNCTION__)); + goto exit_proc; + } + str_ptr += strlen(GET_SSID); + nssid = wl_iw_parse_ssid_list(&str_ptr, ssids_local, nssid, \ + WL_SCAN_PARAMS_SSID_MAX); + if (nssid == -1) { + WL_ERROR(("%s wrong ssid list", __FUNCTION__)); + return -1; + } + + if (iscan->iscan_ex_param_size > WLC_IOCTL_MAXLEN) { + WL_ERROR(("%s wrong ex_param_size %d", \ + __FUNCTION__, iscan->iscan_ex_param_size)); + return -1; + } + memset(iscan->iscan_ex_params_p, 0, iscan->iscan_ex_param_size); + + + wl_iw_iscan_prep(&iscan->iscan_ex_params_p->params, NULL); + iscan->iscan_ex_params_p->version = htod32(ISCAN_REQ_VERSION); + iscan->iscan_ex_params_p->action = htod16(WL_SCAN_ACTION_START); + iscan->iscan_ex_params_p->scan_duration = htod16(0); + + + if ((nchan = wl_iw_parse_channel_list(&str_ptr, \ + &iscan->iscan_ex_params_p->params.channel_list[0], \ + WL_NUMCHANNELS)) == -1) { + WL_ERROR(("%s missing channel list\n", __FUNCTION__)); + return -1; + } + + + get_parmeter_from_string(&str_ptr, \ + GET_NPROBE, PTYPE_INTDEC, \ + &iscan->iscan_ex_params_p->params.nprobes, 2); + + get_parmeter_from_string(&str_ptr, GET_ACTIVE_ASSOC_DWELL, PTYPE_INTDEC, \ + &iscan->iscan_ex_params_p->params.active_time, 4); + + get_parmeter_from_string(&str_ptr, GET_PASSIVE_ASSOC_DWELL, PTYPE_INTDEC, \ + &iscan->iscan_ex_params_p->params.passive_time, 4); + + get_parmeter_from_string(&str_ptr, GET_HOME_DWELL, PTYPE_INTDEC, \ + &iscan->iscan_ex_params_p->params.home_time, 4); + + get_parmeter_from_string(&str_ptr, GET_SCAN_TYPE, PTYPE_INTDEC, \ + &iscan->iscan_ex_params_p->params.scan_type, 1); + + res = wl_iw_combined_scan_set(dev, ssids_local, nssid, nchan); + + } else { + WL_ERROR(("IWPRIV argument len = 0 \n")); + return -1; + } + +exit_proc: + + kfree(extra); + + return res; +} + + +static int +wl_iw_set_cscan( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra +) +{ + int res = -1; + iscan_info_t *iscan = g_iscan; + wlc_ssid_t ssids_local[WL_SCAN_PARAMS_SSID_MAX]; + int nssid = 0; + int nchan = 0; + cscan_tlv_t *cscan_tlv_temp; + char type; + char *str_ptr; + int tlv_size_left; +#ifdef TLV_DEBUG + int i; + char tlv_in_example[] = { 'C', 'S', 'C', 'A', 'N', ' ', \ + 0x53, 0x01, 0x00, 0x00, + 'S', + 0x00, + 'S', + 0x04, + 'B', 'R', 'C', 'M', + 'C', + 0x06, + 'P', + 0x94, + 0x11, + 'T', + 0x01 + }; +#endif + + WL_TRACE(("\n### %s: info->cmd:%x, info->flags:%x, u.data=0x%p, u.len=%d\n", + __FUNCTION__, info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + net_os_wake_lock(dev); + + if (g_onoff == G_WLAN_SET_OFF) { + WL_TRACE(("%s: driver is not up yet after START\n", __FUNCTION__)); + goto exit_proc; + } + + + if (wrqu->data.length < (strlen(CSCAN_COMMAND) + sizeof(cscan_tlv_t))) { + WL_ERROR(("%s aggument=%d less %d\n", __FUNCTION__, \ + wrqu->data.length, strlen(CSCAN_COMMAND) + sizeof(cscan_tlv_t))); + goto exit_proc; + } + +#ifdef TLV_DEBUG + memcpy(extra, tlv_in_example, sizeof(tlv_in_example)); + wrqu->data.length = sizeof(tlv_in_example); + for (i = 0; i < wrqu->data.length; i++) + printf("%02X ", extra[i]); + printf("\n"); +#endif + + str_ptr = extra; + str_ptr += strlen(CSCAN_COMMAND); + tlv_size_left = wrqu->data.length - strlen(CSCAN_COMMAND); + + cscan_tlv_temp = (cscan_tlv_t *)str_ptr; + memset(ssids_local, 0, sizeof(ssids_local)); + + if ((cscan_tlv_temp->prefix == CSCAN_TLV_PREFIX) && \ + (cscan_tlv_temp->version == CSCAN_TLV_VERSION) && \ + (cscan_tlv_temp->subver == CSCAN_TLV_SUBVERSION)) + { + str_ptr += sizeof(cscan_tlv_t); + tlv_size_left -= sizeof(cscan_tlv_t); + + + if ((nssid = wl_iw_parse_ssid_list_tlv(&str_ptr, ssids_local, \ + WL_SCAN_PARAMS_SSID_MAX, &tlv_size_left)) <= 0) { + WL_ERROR(("SSID is not presented or corrupted ret=%d\n", nssid)); + goto exit_proc; + } + else { + + memset(iscan->iscan_ex_params_p, 0, iscan->iscan_ex_param_size); + + + wl_iw_iscan_prep(&iscan->iscan_ex_params_p->params, NULL); + iscan->iscan_ex_params_p->version = htod32(ISCAN_REQ_VERSION); + iscan->iscan_ex_params_p->action = htod16(WL_SCAN_ACTION_START); + iscan->iscan_ex_params_p->scan_duration = htod16(0); + + + while (tlv_size_left > 0) + { + type = str_ptr[0]; + switch (type) { + case CSCAN_TLV_TYPE_CHANNEL_IE: + + if ((nchan = wl_iw_parse_channel_list_tlv(&str_ptr, \ + &iscan->iscan_ex_params_p->params.channel_list[0], \ + WL_NUMCHANNELS, &tlv_size_left)) == -1) { + WL_ERROR(("%s missing channel list\n", \ + __FUNCTION__)); + goto exit_proc; + } + break; + case CSCAN_TLV_TYPE_NPROBE_IE: + if ((res = wl_iw_parse_data_tlv(&str_ptr, \ + &iscan->iscan_ex_params_p->params.nprobes, \ + sizeof(iscan->iscan_ex_params_p->params.nprobes), \ + type, sizeof(char), &tlv_size_left)) == -1) { + WL_ERROR(("%s return %d\n", \ + __FUNCTION__, res)); + goto exit_proc; + } + break; + case CSCAN_TLV_TYPE_ACTIVE_IE: + if ((res = wl_iw_parse_data_tlv(&str_ptr, \ + &iscan->iscan_ex_params_p->params.active_time, \ + sizeof(iscan->iscan_ex_params_p->params.active_time), \ + type, sizeof(short), &tlv_size_left)) == -1) { + WL_ERROR(("%s return %d\n", \ + __FUNCTION__, res)); + goto exit_proc; + } + break; + case CSCAN_TLV_TYPE_PASSIVE_IE: + if ((res = wl_iw_parse_data_tlv(&str_ptr, \ + &iscan->iscan_ex_params_p->params.passive_time, \ + sizeof(iscan->iscan_ex_params_p->params.passive_time), \ + type, sizeof(short), &tlv_size_left)) == -1) { + WL_ERROR(("%s return %d\n", \ + __FUNCTION__, res)); + goto exit_proc; + } + break; + case CSCAN_TLV_TYPE_HOME_IE: + if ((res = wl_iw_parse_data_tlv(&str_ptr, \ + &iscan->iscan_ex_params_p->params.home_time, \ + sizeof(iscan->iscan_ex_params_p->params.home_time), \ + type, sizeof(short), &tlv_size_left)) == -1) { + WL_ERROR(("%s return %d\n", \ + __FUNCTION__, res)); + goto exit_proc; + } + break; + case CSCAN_TLV_TYPE_STYPE_IE: + if ((res = wl_iw_parse_data_tlv(&str_ptr, \ + &iscan->iscan_ex_params_p->params.scan_type, \ + sizeof(iscan->iscan_ex_params_p->params.scan_type), \ + type, sizeof(char), &tlv_size_left)) == -1) { + WL_ERROR(("%s return %d\n", \ + __FUNCTION__, res)); + goto exit_proc; + } + break; + + default : + WL_ERROR(("%s get unkwown type %X\n", \ + __FUNCTION__, type)); + goto exit_proc; + break; + } + } + } + } + else { + WL_ERROR(("%s get wrong TLV command\n", __FUNCTION__)); + goto exit_proc; + } + +#if defined(CONFIG_FIRST_SCAN) + if (g_first_broadcast_scan < BROADCAST_SCAN_FIRST_RESULT_CONSUMED) { + if (++g_first_counter_scans == MAX_ALLOWED_BLOCK_SCAN_FROM_FIRST_SCAN) { + + WL_ERROR(("%s Clean up First scan flag which is %d\n", \ + __FUNCTION__, g_first_broadcast_scan)); + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_RESULT_CONSUMED; + } + else { + WL_ERROR(("%s Ignoring CSCAN : First Scan is not done yet %d\n", \ + __FUNCTION__, g_first_counter_scans)); + res = -EBUSY; + goto exit_proc; + } + } +#endif + + res = wl_iw_combined_scan_set(dev, ssids_local, nssid, nchan); + +exit_proc: + net_os_wake_unlock(dev); + return res; +} + +#endif + +#ifdef SOFTAP +#ifndef AP_ONLY + +static int thr_wait_for_2nd_eth_dev(void *data) +{ + struct net_device *dev = (struct net_device *)data; + wl_iw_t *iw; + int ret = 0; + unsigned long flags; + + net_os_wake_lock(dev); + + DAEMONIZE("wl0_eth_wthread"); + + WL_TRACE(("\n>%s thread started:, PID:%x\n", __FUNCTION__, current->pid)); + iw = *(wl_iw_t **)netdev_priv(dev); + if (!iw) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + ret = -1; + goto fail; + } + +#ifndef BCMSDIOH_STD + if (down_timeout(&ap_eth_sema, msecs_to_jiffies(5000)) != 0) { + WL_ERROR(("\n%s: sap_eth_sema timeout \n", __FUNCTION__)); + ret = -1; + goto fail; + } +#endif + + flags = dhd_os_spin_lock(iw->pub); + if (!ap_net_dev) { + WL_ERROR((" ap_net_dev is null !!!")); + ret = -1; + dhd_os_spin_unlock(iw->pub, flags); + goto fail; + } + + WL_TRACE(("\n>%s: Thread:'softap ethdev IF:%s is detected !!!'\n\n", + __FUNCTION__, ap_net_dev->name)); + + ap_cfg_running = TRUE; + + dhd_os_spin_unlock(iw->pub, flags); + + bcm_mdelay(500); + + wl_iw_send_priv_event(priv_dev, "AP_SET_CFG_OK"); + +fail: + WL_TRACE(("\n>%s, thread completed\n", __FUNCTION__)); + + net_os_wake_unlock(dev); + + complete_and_exit(&ap_cfg_exited, 0); + return ret; +} +#endif +#ifndef AP_ONLY +static int last_auto_channel = 6; +#endif +static int get_softap_auto_channel(struct net_device *dev, struct ap_profile *ap) +{ + int chosen = 0; + wl_uint32_list_t request; + int rescan = 0; + int retry = 0; + int updown = 0; + int ret = 0; + wlc_ssid_t null_ssid; + int res = 0; +#ifndef AP_ONLY + int iolen = 0; + int mkvar_err = 0; + int bsscfg_index = 1; + char buf[WLC_IOCTL_SMLEN]; +#endif + WL_SOFTAP(("Enter %s\n", __FUNCTION__)); + +#ifndef AP_ONLY + if (ap_cfg_running) { + ap->channel = last_auto_channel; + return res; + } +#endif + memset(&null_ssid, 0, sizeof(wlc_ssid_t)); + res |= dev_wlc_ioctl(dev, WLC_UP, &updown, sizeof(updown)); +#ifdef AP_ONLY + res |= dev_wlc_ioctl(dev, WLC_SET_SSID, &null_ssid, sizeof(null_ssid)); +#else + iolen = wl_bssiovar_mkbuf("ssid", bsscfg_index, (char *)(&null_ssid), \ + null_ssid.SSID_len+4, buf, sizeof(buf), &mkvar_err); + ASSERT(iolen); + res |= dev_wlc_ioctl(dev, WLC_SET_VAR, buf, iolen); +#endif + auto_channel_retry: + request.count = htod32(0); + ret = dev_wlc_ioctl(dev, WLC_START_CHANNEL_SEL, &request, sizeof(request)); + if (ret < 0) { + WL_ERROR(("can't start auto channel scan\n")); + goto fail; + } + + get_channel_retry: + bcm_mdelay(500); + + ret = dev_wlc_ioctl(dev, WLC_GET_CHANNEL_SEL, &chosen, sizeof(chosen)); + if (ret < 0 || dtoh32(chosen) == 0) { + if (retry++ < 3) + goto get_channel_retry; + else { + WL_ERROR(("can't get auto channel sel, err = %d, \ + chosen = %d\n", ret, chosen)); + goto fail; + } + } + if ((chosen == 1) && (!rescan++)) + goto auto_channel_retry; + WL_SOFTAP(("Set auto channel = %d\n", chosen)); + ap->channel = chosen; + if ((res = dev_wlc_ioctl(dev, WLC_DOWN, &updown, sizeof(updown))) < 0) { + WL_ERROR(("%s fail to set up err =%d\n", __FUNCTION__, res)); + goto fail; + } +#ifndef AP_ONLY + if (!res) + last_auto_channel = ap->channel; +#endif + +fail : + return res; +} + + +static int set_ap_cfg(struct net_device *dev, struct ap_profile *ap) +{ + int updown = 0; + int channel = 0; + + wlc_ssid_t ap_ssid; + int max_assoc = 8; + + int res = 0; + int apsta_var = 0; +#ifndef AP_ONLY + int mpc = 0; + int iolen = 0; + int mkvar_err = 0; + int bsscfg_index = 1; + char buf[WLC_IOCTL_SMLEN]; +#endif + + if (!dev) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return -1; + } + + net_os_wake_lock(dev); + + WL_SOFTAP(("wl_iw: set ap profile:\n")); + WL_SOFTAP((" ssid = '%s'\n", ap->ssid)); + WL_SOFTAP((" security = '%s'\n", ap->sec)); + if (ap->key[0] != '\0') + WL_SOFTAP((" key = '%s'\n", ap->key)); + WL_SOFTAP((" channel = %d\n", ap->channel)); + WL_SOFTAP((" max scb = %d\n", ap->max_scb)); + +#ifdef AP_ONLY + if (ap_cfg_running) { + wl_iw_softap_deassoc_stations(dev, NULL); + ap_cfg_running = FALSE; + } +#endif + + if (ap_cfg_running == FALSE) { + +#ifndef AP_ONLY + sema_init(&ap_eth_sema, 0); + + mpc = 0; + if ((res = dev_wlc_intvar_set(dev, "mpc", mpc))) { + WL_ERROR(("%s fail to set mpc\n", __FUNCTION__)); + goto fail; + } +#endif + + updown = 0; + if ((res = dev_wlc_ioctl(dev, WLC_DOWN, &updown, sizeof(updown)))) { + WL_ERROR(("%s fail to set updown\n", __FUNCTION__)); + goto fail; + } + +#ifdef AP_ONLY + apsta_var = 0; + if ((res = dev_wlc_ioctl(dev, WLC_SET_AP, &apsta_var, sizeof(apsta_var)))) { + WL_ERROR(("%s fail to set apsta_var 0\n", __FUNCTION__)); + goto fail; + } + apsta_var = 1; + if ((res = dev_wlc_ioctl(dev, WLC_SET_AP, &apsta_var, sizeof(apsta_var)))) { + WL_ERROR(("%s fail to set apsta_var 1\n", __FUNCTION__)); + goto fail; + } + res = dev_wlc_ioctl(dev, WLC_GET_AP, &apsta_var, sizeof(apsta_var)); +#else + apsta_var = 1; + iolen = wl_bssiovar_mkbuf("apsta", + bsscfg_index, &apsta_var, sizeof(apsta_var)+4, + buf, sizeof(buf), &mkvar_err); + + if (iolen <= 0) + goto fail; + + if ((res = dev_wlc_ioctl(dev, WLC_SET_VAR, buf, iolen)) < 0) { + WL_ERROR(("%s fail to set apsta \n", __FUNCTION__)); + goto fail; + } + WL_TRACE(("\n>in %s: apsta set result: %d \n", __FUNCTION__, res)); +#endif + + updown = 1; + if ((res = dev_wlc_ioctl(dev, WLC_UP, &updown, sizeof(updown))) < 0) { + WL_ERROR(("%s fail to set apsta \n", __FUNCTION__)); + goto fail; + } + + } else { + + if (!ap_net_dev) { + WL_ERROR(("%s: ap_net_dev is null\n", __FUNCTION__)); + goto fail; + } + + res = wl_iw_softap_deassoc_stations(ap_net_dev, NULL); + + + if ((res = dev_iw_write_cfg1_bss_var(dev, 0)) < 0) { + WL_ERROR(("%s fail to set bss down\n", __FUNCTION__)); + goto fail; + } + } + + if (strlen(ap->country_code)) { + WL_ERROR(("%s: Igonored: Country MUST be specified \ + COUNTRY command with \n", __FUNCTION__)); + } else { + WL_SOFTAP(("%s: Country code is not specified," + " will use Radio's default\n", + __FUNCTION__)); + } + + iolen = wl_bssiovar_mkbuf("closednet", + bsscfg_index, &ap->closednet, sizeof(ap->closednet)+4, + buf, sizeof(buf), &mkvar_err); + ASSERT(iolen); + if ((res = dev_wlc_ioctl(dev, WLC_SET_VAR, buf, iolen)) < 0) { + WL_ERROR(("%s failed to set 'closednet'for apsta \n", __FUNCTION__)); + goto fail; + } + + + if ((ap->channel == 0) && (get_softap_auto_channel(dev, ap) < 0)) { + ap->channel = 1; + WL_ERROR(("%s auto channel failed, pick up channel=%d\n", \ + __FUNCTION__, ap->channel)); + } + + channel = ap->channel; + if ((res = dev_wlc_ioctl(dev, WLC_SET_CHANNEL, &channel, sizeof(channel)))) { + WL_ERROR(("%s fail to set channel\n", __FUNCTION__)); + goto fail; + } + + if (ap_cfg_running == FALSE) { + updown = 0; + if ((res = dev_wlc_ioctl(dev, WLC_UP, &updown, sizeof(updown)))) { + WL_ERROR(("%s fail to set up\n", __FUNCTION__)); + goto fail; + } + } + + max_assoc = ap->max_scb; + if ((res = dev_wlc_intvar_set(dev, "maxassoc", max_assoc))) { + WL_ERROR(("%s fail to set maxassoc\n", __FUNCTION__)); + goto fail; + } + + ap_ssid.SSID_len = strlen(ap->ssid); + strncpy(ap_ssid.SSID, ap->ssid, ap_ssid.SSID_len); + +#ifdef AP_ONLY + if ((res = wl_iw_set_ap_security(dev, &my_ap)) != 0) { + WL_ERROR(("ERROR:%d in:%s, wl_iw_set_ap_security is skipped\n", \ + res, __FUNCTION__)); + goto fail; + } + wl_iw_send_priv_event(dev, "ASCII_CMD=AP_BSS_START"); + ap_cfg_running = TRUE; +#else + iolen = wl_bssiovar_mkbuf("ssid", bsscfg_index, (char *)(&ap_ssid), + ap_ssid.SSID_len+4, buf, sizeof(buf), &mkvar_err); + ASSERT(iolen); + if ((res = dev_wlc_ioctl(dev, WLC_SET_VAR, buf, iolen)) != 0) { + WL_ERROR(("ERROR:%d in:%s, Security & BSS reconfiguration is skipped\n", \ + res, __FUNCTION__)); + goto fail; + } + if (ap_cfg_running == FALSE) { + init_completion(&ap_cfg_exited); + ap_cfg_pid = kernel_thread(thr_wait_for_2nd_eth_dev, dev, 0); + } else { + ap_cfg_pid = -1; + if (ap_net_dev == NULL) { + WL_ERROR(("%s ERROR: ap_net_dev is NULL !!!\n", __FUNCTION__)); + goto fail; + } + + WL_ERROR(("%s: %s Configure security & restart AP bss \n", \ + __FUNCTION__, ap_net_dev->name)); + + if ((res = wl_iw_set_ap_security(ap_net_dev, &my_ap)) < 0) { + WL_ERROR(("%s fail to set security : %d\n", __FUNCTION__, res)); + goto fail; + } + + if ((res = dev_iw_write_cfg1_bss_var(dev, 1)) < 0) { + WL_ERROR(("%s fail to set bss up\n", __FUNCTION__)); + goto fail; + } + } +#endif +fail: + WL_SOFTAP(("%s exit with %d\n", __FUNCTION__, res)); + + net_os_wake_unlock(dev); + + return res; +} + + +static int wl_iw_set_ap_security(struct net_device *dev, struct ap_profile *ap) +{ + int wsec = 0; + int wpa_auth = 0; + int res = 0; + int i; + char *ptr; +#ifdef AP_ONLY + int mpc = 0; + wlc_ssid_t ap_ssid; +#endif + wl_wsec_key_t key; + + WL_SOFTAP(("\nsetting SOFTAP security mode:\n")); + WL_SOFTAP(("wl_iw: set ap profile:\n")); + WL_SOFTAP((" ssid = '%s'\n", ap->ssid)); + WL_SOFTAP((" security = '%s'\n", ap->sec)); + if (ap->key[0] != '\0') { + WL_SOFTAP((" key = '%s'\n", ap->key)); + } + WL_SOFTAP((" channel = %d\n", ap->channel)); + WL_SOFTAP((" max scb = %d\n", ap->max_scb)); + + if (strnicmp(ap->sec, "open", strlen("open")) == 0) { + wsec = 0; + res = dev_wlc_intvar_set(dev, "wsec", wsec); + wpa_auth = WPA_AUTH_DISABLED; + res |= dev_wlc_intvar_set(dev, "wpa_auth", wpa_auth); + + WL_SOFTAP(("=====================\n")); + WL_SOFTAP((" wsec & wpa_auth set 'OPEN', result:&d %d\n", res)); + WL_SOFTAP(("=====================\n")); + + } else if (strnicmp(ap->sec, "wep", strlen("wep")) == 0) { + + memset(&key, 0, sizeof(key)); + + wsec = WEP_ENABLED; + res = dev_wlc_intvar_set(dev, "wsec", wsec); + + key.index = 0; + if (wl_iw_parse_wep(ap->key, &key)) { + WL_SOFTAP(("wep key parse err!\n")); + return -1; + } + + key.index = htod32(key.index); + key.len = htod32(key.len); + key.algo = htod32(key.algo); + key.flags = htod32(key.flags); + + res |= dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + + wpa_auth = WPA_AUTH_DISABLED; + res |= dev_wlc_intvar_set(dev, "wpa_auth", wpa_auth); + + WL_SOFTAP(("=====================\n")); + WL_SOFTAP((" wsec & auth set 'WEP', result:&d %d\n", res)); + WL_SOFTAP(("=====================\n")); + + } else if (strnicmp(ap->sec, "wpa2-psk", strlen("wpa2-psk")) == 0) { + wsec_pmk_t psk; + size_t key_len; + + wsec = AES_ENABLED; + dev_wlc_intvar_set(dev, "wsec", wsec); + + key_len = strlen(ap->key); + if (key_len < WSEC_MIN_PSK_LEN || key_len > WSEC_MAX_PSK_LEN) { + WL_SOFTAP(("passphrase must be between %d and %d characters long\n", + WSEC_MIN_PSK_LEN, WSEC_MAX_PSK_LEN)); + return -1; + } + + if (key_len < WSEC_MAX_PSK_LEN) { + unsigned char output[2*SHA1HashSize]; + char key_str_buf[WSEC_MAX_PSK_LEN+1]; + + memset(output, 0, sizeof(output)); + pbkdf2_sha1(ap->key, ap->ssid, strlen(ap->ssid), 4096, output, 32); + + ptr = key_str_buf; + for (i = 0; i < (WSEC_MAX_PSK_LEN/8); i++) { + sprintf(ptr, "%02x%02x%02x%02x", (uint)output[i*4], \ + (uint)output[i*4+1], (uint)output[i*4+2], \ + (uint)output[i*4+3]); + ptr += 8; + } + WL_SOFTAP(("%s: passphase = %s\n", __FUNCTION__, key_str_buf)); + + psk.key_len = htod16((ushort)WSEC_MAX_PSK_LEN); + memcpy(psk.key, key_str_buf, psk.key_len); + } else { + psk.key_len = htod16((ushort) key_len); + memcpy(psk.key, ap->key, key_len); + } + psk.flags = htod16(WSEC_PASSPHRASE); + dev_wlc_ioctl(dev, WLC_SET_WSEC_PMK, &psk, sizeof(psk)); + + wpa_auth = WPA2_AUTH_PSK; + dev_wlc_intvar_set(dev, "wpa_auth", wpa_auth); + + } else if (strnicmp(ap->sec, "wpa-psk", strlen("wpa-psk")) == 0) { + + wsec_pmk_t psk; + size_t key_len; + + wsec = TKIP_ENABLED; + res = dev_wlc_intvar_set(dev, "wsec", wsec); + + key_len = strlen(ap->key); + if (key_len < WSEC_MIN_PSK_LEN || key_len > WSEC_MAX_PSK_LEN) { + WL_SOFTAP(("passphrase must be between %d and %d characters long\n", + WSEC_MIN_PSK_LEN, WSEC_MAX_PSK_LEN)); + return -1; + } + + if (key_len < WSEC_MAX_PSK_LEN) { + unsigned char output[2*SHA1HashSize]; + char key_str_buf[WSEC_MAX_PSK_LEN+1]; + bzero(output, 2*SHA1HashSize); + + WL_SOFTAP(("%s: do passhash...\n", __FUNCTION__)); + + pbkdf2_sha1(ap->key, ap->ssid, strlen(ap->ssid), 4096, output, 32); + + ptr = key_str_buf; + for (i = 0; i < (WSEC_MAX_PSK_LEN/8); i++) { + WL_SOFTAP(("[%02d]: %08x\n", i, *((unsigned int *)&output[i*4]))); + + sprintf(ptr, "%02x%02x%02x%02x", (uint)output[i*4], + (uint)output[i*4+1], (uint)output[i*4+2], + (uint)output[i*4+3]); + ptr += 8; + } + WL_SOFTAP(("%s: passphase = %s\n", __FUNCTION__, key_str_buf)); + + psk.key_len = htod16((ushort)WSEC_MAX_PSK_LEN); + memcpy(psk.key, key_str_buf, psk.key_len); + } else { + psk.key_len = htod16((ushort) key_len); + memcpy(psk.key, ap->key, key_len); + } + + psk.flags = htod16(WSEC_PASSPHRASE); + res |= dev_wlc_ioctl(dev, WLC_SET_WSEC_PMK, &psk, sizeof(psk)); + + wpa_auth = WPA_AUTH_PSK; + res |= dev_wlc_intvar_set(dev, "wpa_auth", wpa_auth); + + WL_SOFTAP((" wsec & auth set 'wpa-psk' (TKIP), result:&d %d\n", res)); + } + +#ifdef AP_ONLY + ap_ssid.SSID_len = strlen(ap->ssid); + strncpy(ap_ssid.SSID, ap->ssid, ap_ssid.SSID_len); + res |= dev_wlc_ioctl(dev, WLC_SET_SSID, &ap_ssid, sizeof(ap_ssid)); + mpc = 0; + res |= dev_wlc_intvar_set(dev, "mpc", mpc); + if (strnicmp(ap->sec, "wep", strlen("wep")) == 0) { + res |= dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)); + } +#endif + return res; +} + + + +int get_parmeter_from_string( + char **str_ptr, const char *token, + int param_type, void *dst, int param_max_len) +{ + char int_str[7] = "0"; + int parm_str_len; + char *param_str_begin; + char *param_str_end; + + if ((*str_ptr) && !strncmp(*str_ptr, token, strlen(token))) { + + strsep(str_ptr, "=,"); + param_str_begin = *str_ptr; + strsep(str_ptr, "=,"); + + if (*str_ptr == NULL) { + parm_str_len = strlen(param_str_begin); + } else { + param_str_end = *str_ptr-1; + parm_str_len = param_str_end - param_str_begin; + } + + WL_TRACE((" 'token:%s', len:%d, ", token, parm_str_len)); + + if (parm_str_len > param_max_len) { + WL_TRACE((" WARNING: extracted param len:%d is > MAX:%d\n", + parm_str_len, param_max_len)); + + parm_str_len = param_max_len; + } + + switch (param_type) { + + case PTYPE_INTDEC: { + int *pdst_int = dst; + char *eptr; + + if (parm_str_len > sizeof(int_str)) + parm_str_len = sizeof(int_str); + + memcpy(int_str, param_str_begin, parm_str_len); + + *pdst_int = simple_strtoul(int_str, &eptr, 10); + + WL_TRACE((" written as integer:%d\n", *pdst_int)); + } + break; + case PTYPE_STR_HEX: { + u8 *buf = dst; + + param_max_len = param_max_len >> 1; + hstr_2_buf(param_str_begin, buf, param_max_len); + print_buf(buf, param_max_len, 0); + } + break; + default: + memcpy(dst, param_str_begin, parm_str_len); + *((char *)dst + parm_str_len) = 0; + WL_TRACE((" written as a string:%s\n", (char *)dst)); + break; + } + + return 0; + } else { + WL_ERROR(("\n %s: No token:%s in str:%s\n", + __FUNCTION__, token, *str_ptr)); + + return -1; + } +} + +static int wl_iw_softap_deassoc_stations(struct net_device *dev, u8 *mac) +{ + int i; + int res = 0; + char mac_buf[128] = {0}; + char z_mac[6] = {0, 0, 0, 0, 0, 0}; + char *sta_mac; + struct maclist *assoc_maclist = (struct maclist *) mac_buf; + bool deauth_all = false; + + if (mac == NULL) { + deauth_all = true; + sta_mac = z_mac; + } else { + sta_mac = mac; + } + + memset(assoc_maclist, 0, sizeof(mac_buf)); + assoc_maclist->count = 8; + + res = dev_wlc_ioctl(dev, WLC_GET_ASSOCLIST, assoc_maclist, 128); + if (res != 0) { + WL_SOFTAP(("%s: Error:%d Couldn't get ASSOC List\n", __FUNCTION__, res)); + return res; + } + + if (assoc_maclist->count) { + for (i = 0; i < assoc_maclist->count; i++) { + scb_val_t scbval; + + scbval.val = htod32(1); + bcopy(&assoc_maclist->ea[i], &scbval.ea, ETHER_ADDR_LEN); + + if (deauth_all || (memcmp(&scbval.ea, sta_mac, ETHER_ADDR_LEN) == 0)) { + WL_SOFTAP(("%s, deauth STA:%d \n", __FUNCTION__, i)); + res |= dev_wlc_ioctl(dev, WLC_SCB_DEAUTHENTICATE_FOR_REASON, + &scbval, sizeof(scb_val_t)); + } + } + } else { + WL_SOFTAP((" STA ASSOC list is empty\n")); + } + + if (res != 0) { + WL_ERROR(("%s: Error:%d\n", __FUNCTION__, res)); + } else if (assoc_maclist->count) { + bcm_mdelay(200); + } + return res; +} + + +static int iwpriv_softap_stop(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *ext) +{ + int res = 0; + + WL_SOFTAP(("got iwpriv AP_BSS_STOP\n")); + + if ((!dev) && (!ap_net_dev)) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return res; + } + + net_os_wake_lock(dev); + + if ((ap_cfg_running == TRUE)) { +#ifdef AP_ONLY + wl_iw_softap_deassoc_stations(dev, NULL); +#else + wl_iw_softap_deassoc_stations(ap_net_dev, NULL); + + if ((res = dev_iw_write_cfg1_bss_var(dev, 2)) < 0) + WL_ERROR(("%s failed to del BSS err = %d", __FUNCTION__, res)); +#endif + + bcm_mdelay(100); + + wrqu->data.length = 0; + ap_cfg_running = FALSE; + } + else + WL_ERROR(("%s: was called when SoftAP is OFF : move on\n", __FUNCTION__)); + + WL_SOFTAP(("%s Done with %d\n", __FUNCTION__, res)); + + net_os_wake_unlock(dev); + + return res; +} + + +static int iwpriv_fw_reload(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *ext) +{ + int ret = -1; + char extra[256]; + char *fwstr = fw_path; + + WL_SOFTAP(("current firmware_path[]=%s\n", fwstr)); + + WL_TRACE((">Got FW_RELOAD cmd:" + "info->cmd:%x, info->flags:%x, u.data:%p, u.len:%d, \ + fw_path:%p, len:%d \n", + info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length, fwstr, strlen(fwstr))); + + if ((wrqu->data.length > 4) && (wrqu->data.length < sizeof(extra))) { + + char *str_ptr; + + if (copy_from_user(extra, wrqu->data.pointer, wrqu->data.length)) { + ret = -EFAULT; + goto exit_proc; + } + + extra[wrqu->data.length] = 8; + str_ptr = extra; + + if (get_parmeter_from_string(&str_ptr, "FW_PATH=", PTYPE_STRING, fwstr, 255) != 0) { + WL_ERROR(("Error: extracting FW_PATH='' string\n")); + goto exit_proc; + } + + if (strstr(fwstr, "apsta") != NULL) { + WL_SOFTAP(("GOT APSTA FIRMWARE\n")); + ap_fw_loaded = TRUE; + } else { + WL_SOFTAP(("GOT STA FIRMWARE\n")); + ap_fw_loaded = FALSE; + } + + WL_SOFTAP(("SET firmware_path[]=%s , str_p:%p\n", fwstr, fwstr)); + ret = 0; + } else { + WL_ERROR(("Error: ivalid param len:%d\n", wrqu->data.length)); + } + +exit_proc: + return ret; +} +#endif + +#ifdef SOFTAP +static int iwpriv_wpasupp_loop_tst(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *ext) +{ + int res = 0; + char *params = NULL; + + WL_TRACE((">Got IWPRIV wp_supp loopback cmd test:" + "info->cmd:%x, info->flags:%x, u.data:%p, u.len:%d\n", + info->cmd, info->flags, + wrqu->data.pointer, wrqu->data.length)); + + if (wrqu->data.length != 0) { + + if (!(params = kmalloc(wrqu->data.length+1, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(params, wrqu->data.pointer, wrqu->data.length)) { + kfree(params); + return -EFAULT; + } + + params[wrqu->data.length] = 0; + WL_SOFTAP(("\n>> copied from user:\n %s\n", params)); + } else { + WL_ERROR(("ERROR param length is 0\n")); + return -EFAULT; + } + + res = wl_iw_send_priv_event(dev, params); + kfree(params); + + return res; +} +#endif + + +static int +iwpriv_en_ap_bss( + struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + int res = 0; + + if (!dev) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return -1; + } + + net_os_wake_lock(dev); + + WL_SOFTAP(("%s: rcvd IWPRIV IOCTL: for dev:%s\n", __FUNCTION__, dev->name)); + +#ifndef AP_ONLY + if (ap_cfg_pid >= 0) { + wait_for_completion(&ap_cfg_exited); + ap_cfg_pid = -1; + } + + if ((res = wl_iw_set_ap_security(dev, &my_ap)) != 0) { + WL_ERROR((" %s ERROR setting SOFTAP security in :%d\n", __FUNCTION__, res)); + } + else { + if ((res = dev_iw_write_cfg1_bss_var(dev, 1)) < 0) + WL_ERROR(("%s fail to set bss up err=%d\n", __FUNCTION__, res)); + else + bcm_mdelay(100); + } + +#endif + WL_SOFTAP(("%s done with res %d \n", __FUNCTION__, res)); + + net_os_wake_unlock(dev); + + return res; +} + +static int +get_assoc_sta_list(struct net_device *dev, char *buf, int len) +{ + WL_TRACE(("%s: dev_wlc_ioctl(dev:%p, cmd:%d, buf:%p, len:%d)\n", + __FUNCTION__, dev, WLC_GET_ASSOCLIST, buf, len)); + + return dev_wlc_ioctl(dev, WLC_GET_ASSOCLIST, buf, len); + +} + + +void check_error(int res, const char *msg, const char *func, int line) +{ + if (res != 0) + WL_ERROR(("%s, %d function:%s, line:%d\n", msg, res, func, line)); +} + +static int +set_ap_mac_list(struct net_device *dev, void *buf) +{ + struct mac_list_set *mac_list_set = (struct mac_list_set *)buf; + struct maclist *maclist = (struct maclist *)&mac_list_set->mac_list; + int length; + int i; + int mac_mode = mac_list_set->mode; + int ioc_res = 0; + ap_macmode = mac_list_set->mode; + + bzero(&ap_black_list, sizeof(struct mflist)); + + if (mac_mode == MACLIST_MODE_DISABLED) { + + ioc_res = dev_wlc_ioctl(dev, WLC_SET_MACMODE, &mac_mode, sizeof(mac_mode)); + check_error(ioc_res, "ioctl ERROR:", __FUNCTION__, __LINE__); + WL_SOFTAP(("%s: MAC filtering disabled\n", __FUNCTION__)); + } else { + + scb_val_t scbval; + char mac_buf[256] = {0}; + struct maclist *assoc_maclist = (struct maclist *) mac_buf; + + bcopy(maclist, &ap_black_list, sizeof(ap_black_list)); + + ioc_res = dev_wlc_ioctl(dev, WLC_SET_MACMODE, &mac_mode, sizeof(mac_mode)); + check_error(ioc_res, "ioctl ERROR:", __FUNCTION__, __LINE__); + + length = sizeof(maclist->count) + maclist->count*ETHER_ADDR_LEN; + dev_wlc_ioctl(dev, WLC_SET_MACLIST, maclist, length); + + WL_SOFTAP(("%s: applied MAC List, mode:%d, length %d:\n", + __FUNCTION__, mac_mode, length)); + for (i = 0; i < maclist->count; i++) + WL_SOFTAP(("mac %d: %02X:%02X:%02X:%02X:%02X:%02X\n", + i, maclist->ea[i].octet[0], maclist->ea[i].octet[1], \ + maclist->ea[i].octet[2], \ + maclist->ea[i].octet[3], maclist->ea[i].octet[4], \ + maclist->ea[i].octet[5])); + + assoc_maclist->count = 8; + ioc_res = dev_wlc_ioctl(dev, WLC_GET_ASSOCLIST, assoc_maclist, 256); + check_error(ioc_res, "ioctl ERROR:", __FUNCTION__, __LINE__); + WL_SOFTAP((" Cur assoc clients:%d\n", assoc_maclist->count)); + + if (assoc_maclist->count) + for (i = 0; i < assoc_maclist->count; i++) { + int j; + bool assoc_mac_matched = false; + + WL_SOFTAP(("\n Cheking assoc STA: ")); + print_buf(&assoc_maclist->ea[i], 6, 7); + WL_SOFTAP(("with the b/w list:")); + + for (j = 0; j < maclist->count; j++) + if (!bcmp(&assoc_maclist->ea[i], &maclist->ea[j], + ETHER_ADDR_LEN)) { + + assoc_mac_matched = true; + break; + } + + if (((mac_mode == MACLIST_MODE_ALLOW) && !assoc_mac_matched) || + ((mac_mode == MACLIST_MODE_DENY) && assoc_mac_matched)) { + + WL_SOFTAP(("b-match or w-mismatch," + " do deauth/disassoc \n")); + scbval.val = htod32(1); + bcopy(&assoc_maclist->ea[i], &scbval.ea, \ + ETHER_ADDR_LEN); + ioc_res = dev_wlc_ioctl(dev, + WLC_SCB_DEAUTHENTICATE_FOR_REASON, + &scbval, sizeof(scb_val_t)); + check_error(ioc_res, + "ioctl ERROR:", + __FUNCTION__, __LINE__); + + } else { + WL_SOFTAP((" no b/w list hits, let it be\n")); + } + } else { + WL_SOFTAP(("No ASSOC CLIENTS\n")); + } + } + + WL_SOFTAP(("%s iocres:%d\n", __FUNCTION__, ioc_res)); + return ioc_res; +} +#endif + + +#ifdef SOFTAP +int set_macfilt_from_string(struct mflist *pmflist, char **param_str) +{ + return 0; +} +#endif + + +#ifdef SOFTAP +#define PARAM_OFFSET PROFILE_OFFSET + +int wl_iw_process_private_ascii_cmd( + struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *dwrq, + char *cmd_str) +{ + int ret = 0; + char *sub_cmd = cmd_str + PROFILE_OFFSET + strlen("ASCII_CMD="); + + WL_SOFTAP(("\n %s: ASCII_CMD: offs_0:%s, offset_32:\n'%s'\n", + __FUNCTION__, cmd_str, cmd_str + PROFILE_OFFSET)); + + if (strnicmp(sub_cmd, "AP_CFG", strlen("AP_CFG")) == 0) { + + WL_SOFTAP((" AP_CFG \n")); + + + if (init_ap_profile_from_string(cmd_str+PROFILE_OFFSET, &my_ap) != 0) { + WL_ERROR(("ERROR: SoftAP CFG prams !\n")); + ret = -1; + } else { + ret = set_ap_cfg(dev, &my_ap); + } + + } else if (strnicmp(sub_cmd, "AP_BSS_START", strlen("AP_BSS_START")) == 0) { + + WL_SOFTAP(("\n SOFTAP - ENABLE BSS \n")); + + WL_SOFTAP(("\n!!! got 'WL_AP_EN_BSS' from WPA supplicant, dev:%s\n", dev->name)); + +#ifndef AP_ONLY + if (ap_net_dev == NULL) { + printf("\n ERROR: SOFTAP net_dev* is NULL !!!\n"); + } else { + if ((ret = iwpriv_en_ap_bss(ap_net_dev, info, dwrq, cmd_str)) < 0) + WL_ERROR(("%s line %d fail to set bss up\n", \ + __FUNCTION__, __LINE__)); + } +#else + if ((ret = iwpriv_en_ap_bss(dev, info, dwrq, cmd_str)) < 0) + WL_ERROR(("%s line %d fail to set bss up\n", \ + __FUNCTION__, __LINE__)); +#endif + } else if (strnicmp(sub_cmd, "ASSOC_LST", strlen("ASSOC_LST")) == 0) { + /* no code yet */ + } else if (strnicmp(sub_cmd, "AP_BSS_STOP", strlen("AP_BSS_STOP")) == 0) { + WL_SOFTAP((" \n temp DOWN SOFTAP\n")); +#ifndef AP_ONLY + if ((ret = dev_iw_write_cfg1_bss_var(dev, 0)) < 0) { + WL_ERROR(("%s line %d fail to set bss down\n", \ + __FUNCTION__, __LINE__)); + } +#endif + } + + return ret; +} +#endif + +static int wl_iw_set_priv( + struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *ext +) +{ + int ret = 0; + char * extra; + + if (!(extra = kmalloc(dwrq->length, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(extra, dwrq->pointer, dwrq->length)) { + kfree(extra); + return -EFAULT; + } + + WL_TRACE(("%s: SIOCSIWPRIV request %s, info->cmd:%x, info->flags:%d\n dwrq->length:%d", + dev->name, extra, info->cmd, info->flags, dwrq->length)); + + net_os_wake_lock(dev); + + if (dwrq->length && extra) { + if (strnicmp(extra, "START", strlen("START")) == 0) { + wl_iw_control_wl_on(dev, info); + WL_TRACE(("%s, Received regular START command\n", __FUNCTION__)); + } + + if (g_onoff == G_WLAN_SET_OFF) { + WL_TRACE(("%s, missing START, Fail\n", __FUNCTION__)); + kfree(extra); + net_os_wake_unlock(dev); + return -EFAULT; + } + + if (strnicmp(extra, "SCAN-ACTIVE", strlen("SCAN-ACTIVE")) == 0) { +#ifdef ENABLE_ACTIVE_PASSIVE_SCAN_SUPPRESS + WL_TRACE(("%s: active scan setting suppressed\n", dev->name)); +#else + ret = wl_iw_set_active_scan(dev, info, (union iwreq_data *)dwrq, extra); +#endif + } else if (strnicmp(extra, "SCAN-PASSIVE", strlen("SCAN-PASSIVE")) == 0) +#ifdef ENABLE_ACTIVE_PASSIVE_SCAN_SUPPRESS + WL_TRACE(("%s: passive scan setting suppressed\n", dev->name)); +#else + ret = wl_iw_set_passive_scan(dev, info, (union iwreq_data *)dwrq, extra); +#endif + else if (strnicmp(extra, "RSSI", strlen("RSSI")) == 0) + ret = wl_iw_get_rssi(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, "LINKSPEED", strlen("LINKSPEED")) == 0) + ret = wl_iw_get_link_speed(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, "MACADDR", strlen("MACADDR")) == 0) + ret = wl_iw_get_macaddr(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, "COUNTRY", strlen("COUNTRY")) == 0) + ret = wl_iw_set_country(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, "STOP", strlen("STOP")) == 0) + ret = wl_iw_control_wl_off(dev, info); + else if (strnicmp(extra, BAND_GET_CMD, strlen(BAND_GET_CMD)) == 0) + ret = wl_iw_get_band(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, BAND_SET_CMD, strlen(BAND_SET_CMD)) == 0) + ret = wl_iw_set_band(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, DTIM_SKIP_GET_CMD, strlen(DTIM_SKIP_GET_CMD)) == 0) + ret = wl_iw_get_dtim_skip(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, DTIM_SKIP_SET_CMD, strlen(DTIM_SKIP_SET_CMD)) == 0) + ret = wl_iw_set_dtim_skip(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, SETSUSPEND_CMD, strlen(SETSUSPEND_CMD)) == 0) + ret = wl_iw_set_suspend(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, TXPOWER_SET_CMD, strlen(TXPOWER_SET_CMD)) == 0) + ret = wl_iw_set_txpower(dev, info, (union iwreq_data *)dwrq, extra); +#if defined(PNO_SUPPORT) + else if (strnicmp(extra, PNOSSIDCLR_SET_CMD, strlen(PNOSSIDCLR_SET_CMD)) == 0) + ret = wl_iw_set_pno_reset(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, PNOSETUP_SET_CMD, strlen(PNOSETUP_SET_CMD)) == 0) + ret = wl_iw_set_pno_set(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, PNOENABLE_SET_CMD, strlen(PNOENABLE_SET_CMD)) == 0) + ret = wl_iw_set_pno_enable(dev, info, (union iwreq_data *)dwrq, extra); +#endif +#if defined(CSCAN) + else if (strnicmp(extra, CSCAN_COMMAND, strlen(CSCAN_COMMAND)) == 0) + ret = wl_iw_set_cscan(dev, info, (union iwreq_data *)dwrq, extra); +#endif +#ifdef CUSTOMER_HW2 + else if (strnicmp(extra, "POWERMODE", strlen("POWERMODE")) == 0) + ret = wl_iw_set_power_mode(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, "BTCOEXMODE", strlen("BTCOEXMODE")) == 0) { + WL_TRACE_COEX(("%s:got Framwrork cmd: 'BTCOEXMODE'\n", __FUNCTION__)); + ret = wl_iw_set_btcoex_dhcp(dev, info, (union iwreq_data *)dwrq, extra); + } +#else + else if (strnicmp(extra, "POWERMODE", strlen("POWERMODE")) == 0) + ret = wl_iw_set_btcoex_dhcp(dev, info, (union iwreq_data *)dwrq, extra); +#endif + else if (strnicmp(extra, "GETPOWER", strlen("GETPOWER")) == 0) + ret = wl_iw_get_power_mode(dev, info, (union iwreq_data *)dwrq, extra); + else if (strnicmp(extra, RXFILTER_START_CMD, strlen(RXFILTER_START_CMD)) == 0) + ret = net_os_set_packet_filter(dev, 1); + else if (strnicmp(extra, RXFILTER_STOP_CMD, strlen(RXFILTER_STOP_CMD)) == 0) + ret = net_os_set_packet_filter(dev, 0); + else if (strnicmp(extra, RXFILTER_ADD_CMD, strlen(RXFILTER_ADD_CMD)) == 0) { + int filter_num = *(extra + strlen(RXFILTER_ADD_CMD) + 1) - '0'; + ret = net_os_rxfilter_add_remove(dev, TRUE, filter_num); + } + else if (strnicmp(extra, RXFILTER_REMOVE_CMD, strlen(RXFILTER_REMOVE_CMD)) == 0) { + int filter_num = *(extra + strlen(RXFILTER_REMOVE_CMD) + 1) - '0'; + ret = net_os_rxfilter_add_remove(dev, FALSE, filter_num); + } +#ifdef SOFTAP +#ifdef SOFTAP_TLV_CFG + else if (strnicmp(extra, SOFTAP_SET_CMD, strlen(SOFTAP_SET_CMD)) == 0) { + wl_iw_softap_cfg_tlv(dev, info, (union iwreq_data *)dwrq, extra); + } +#endif + else if (strnicmp(extra, "ASCII_CMD", strlen("ASCII_CMD")) == 0) { + wl_iw_process_private_ascii_cmd(dev, info, (union iwreq_data *)dwrq, extra); + } else if (strnicmp(extra, "AP_MAC_LIST_SET", strlen("AP_MAC_LIST_SET")) == 0) { + WL_SOFTAP(("penguin, set AP_MAC_LIST_SET\n")); + set_ap_mac_list(dev, (extra + PROFILE_OFFSET)); + } +#endif + else { + WL_TRACE(("Unknown PRIVATE command: %s: ignored\n", extra)); + snprintf(extra, MAX_WX_STRING, "OK"); + dwrq->length = strlen("OK") + 1; + } + } + + net_os_wake_unlock(dev); + + if (extra) { + if (copy_to_user(dwrq->pointer, extra, dwrq->length)) { + kfree(extra); + return -EFAULT; + } + + kfree(extra); + } + + return ret; +} + +static const iw_handler wl_iw_handler[] = +{ + (iw_handler) wl_iw_config_commit, + (iw_handler) wl_iw_get_name, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_freq, + (iw_handler) wl_iw_get_freq, + (iw_handler) wl_iw_set_mode, + (iw_handler) wl_iw_get_mode, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_get_range, + (iw_handler) wl_iw_set_priv, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_spy, + (iw_handler) wl_iw_get_spy, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_wap, + (iw_handler) wl_iw_get_wap, +#if WIRELESS_EXT > 17 + (iw_handler) wl_iw_mlme, +#else + (iw_handler) NULL, +#endif +#if defined(WL_IW_USE_ISCAN) + (iw_handler) wl_iw_iscan_get_aplist, +#else + (iw_handler) wl_iw_get_aplist, +#endif +#if WIRELESS_EXT > 13 +#if defined(WL_IW_USE_ISCAN) + (iw_handler) wl_iw_iscan_set_scan, + (iw_handler) wl_iw_iscan_get_scan, +#else + (iw_handler) wl_iw_set_scan, + (iw_handler) wl_iw_get_scan, +#endif +#else + (iw_handler) NULL, + (iw_handler) NULL, +#endif + (iw_handler) wl_iw_set_essid, + (iw_handler) wl_iw_get_essid, + (iw_handler) wl_iw_set_nick, + (iw_handler) wl_iw_get_nick, + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_rate, + (iw_handler) wl_iw_get_rate, + (iw_handler) wl_iw_set_rts, + (iw_handler) wl_iw_get_rts, + (iw_handler) wl_iw_set_frag, + (iw_handler) wl_iw_get_frag, + (iw_handler) wl_iw_set_txpow, + (iw_handler) wl_iw_get_txpow, +#if WIRELESS_EXT > 10 + (iw_handler) wl_iw_set_retry, + (iw_handler) wl_iw_get_retry, +#endif + (iw_handler) wl_iw_set_encode, + (iw_handler) wl_iw_get_encode, + (iw_handler) wl_iw_set_power, + (iw_handler) wl_iw_get_power, +#if WIRELESS_EXT > 17 + (iw_handler) NULL, + (iw_handler) NULL, + (iw_handler) wl_iw_set_wpaie, + (iw_handler) wl_iw_get_wpaie, + (iw_handler) wl_iw_set_wpaauth, + (iw_handler) wl_iw_get_wpaauth, + (iw_handler) wl_iw_set_encodeext, + (iw_handler) wl_iw_get_encodeext, +#ifdef BCMWPA2 + (iw_handler) wl_iw_set_pmksa, +#endif +#endif +}; + +#if WIRELESS_EXT > 12 +static const iw_handler wl_iw_priv_handler[] = { + NULL, + (iw_handler)wl_iw_set_active_scan, + NULL, + (iw_handler)wl_iw_get_rssi, + NULL, + (iw_handler)wl_iw_set_passive_scan, + NULL, + (iw_handler)wl_iw_get_link_speed, + NULL, + (iw_handler)wl_iw_get_macaddr, + NULL, + (iw_handler)wl_iw_control_wl_off, + NULL, + (iw_handler)wl_iw_control_wl_on, +#ifdef SOFTAP + NULL, + (iw_handler)iwpriv_set_ap_config, + + NULL, + (iw_handler)iwpriv_get_assoc_list, + + NULL, + (iw_handler)iwpriv_set_mac_filters, + + NULL, + (iw_handler)iwpriv_en_ap_bss, + + NULL, + (iw_handler)iwpriv_wpasupp_loop_tst, + + NULL, + (iw_handler)iwpriv_softap_stop, + + NULL, + (iw_handler)iwpriv_fw_reload, + + NULL, + (iw_handler)iwpriv_set_ap_sta_disassoc, +#endif +#if defined(CSCAN) + + NULL, + (iw_handler)iwpriv_set_cscan +#endif +}; + +static const struct iw_priv_args wl_iw_priv_args[] = { + { + WL_IW_SET_ACTIVE_SCAN, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "SCAN-ACTIVE" + }, + { + WL_IW_GET_RSSI, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "RSSI" + }, + { + WL_IW_SET_PASSIVE_SCAN, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "SCAN-PASSIVE" + }, + { + WL_IW_GET_LINK_SPEED, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "LINKSPEED" + }, + { + WL_IW_GET_CURR_MACADDR, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "Macaddr" + }, + { + WL_IW_SET_STOP, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "STOP" + }, + { + WL_IW_SET_START, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "START" + }, + +#ifdef SOFTAP + { + WL_SET_AP_CFG, + IW_PRIV_TYPE_CHAR | 256, + 0, + "AP_SET_CFG" + }, + + { + WL_AP_STA_LIST, + IW_PRIV_TYPE_CHAR | 0, + IW_PRIV_TYPE_CHAR | 1024, + "AP_GET_STA_LIST" + }, + + { + WL_AP_MAC_FLTR, + IW_PRIV_TYPE_CHAR | 256, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | 0, + "AP_SET_MAC_FLTR" + }, + + { + WL_AP_BSS_START, + 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + "AP_BSS_START" + }, + + { + AP_LPB_CMD, + IW_PRIV_TYPE_CHAR | 256, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | 0, + "AP_LPB_CMD" + }, + + { + WL_AP_STOP, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | 0, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | 0, + "AP_BSS_STOP" + }, + + { + WL_FW_RELOAD, + IW_PRIV_TYPE_CHAR | 256, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | 0, + "WL_FW_RELOAD" + }, + + { + WL_AP_STA_DISASSOC, + IW_PRIV_TYPE_CHAR | 256, + IW_PRIV_TYPE_CHAR | 0, + "AP_STA_DISASSOC" + }, +#endif +#if defined(CSCAN) + { + WL_COMBO_SCAN, + IW_PRIV_TYPE_CHAR | 1024, + 0, + "CSCAN" + }, +#endif +}; + +const struct iw_handler_def wl_iw_handler_def = +{ + .num_standard = ARRAYSIZE(wl_iw_handler), + .standard = (iw_handler *) wl_iw_handler, + .num_private = ARRAYSIZE(wl_iw_priv_handler), + .num_private_args = ARRAY_SIZE(wl_iw_priv_args), + .private = (iw_handler *)wl_iw_priv_handler, + .private_args = (void *) wl_iw_priv_args, + +#if WIRELESS_EXT >= 19 + get_wireless_stats: dhd_get_wireless_stats, +#endif +}; +#endif + + +int wl_iw_ioctl( + struct net_device *dev, + struct ifreq *rq, + int cmd +) +{ + struct iwreq *wrq = (struct iwreq *) rq; + struct iw_request_info info; + iw_handler handler; + char *extra = NULL; + int token_size = 1, max_tokens = 0, ret = 0; + + net_os_wake_lock(dev); + + WL_TRACE(("%s: cmd:%x alled via dhd->do_ioctl()entry point\n", __FUNCTION__, cmd)); + if (cmd < SIOCIWFIRST || + IW_IOCTL_IDX(cmd) >= ARRAYSIZE(wl_iw_handler) || + !(handler = wl_iw_handler[IW_IOCTL_IDX(cmd)])) { + WL_ERROR(("%s: error in cmd=%x : not supported\n", __FUNCTION__, cmd)); + net_os_wake_unlock(dev); + return -EOPNOTSUPP; + } + + switch (cmd) { + + case SIOCSIWESSID: + case SIOCGIWESSID: + case SIOCSIWNICKN: + case SIOCGIWNICKN: + max_tokens = IW_ESSID_MAX_SIZE + 1; + break; + + case SIOCSIWENCODE: + case SIOCGIWENCODE: +#if WIRELESS_EXT > 17 + case SIOCSIWENCODEEXT: + case SIOCGIWENCODEEXT: +#endif + max_tokens = wrq->u.data.length; + break; + + case SIOCGIWRANGE: + max_tokens = sizeof(struct iw_range) + 500; + break; + + case SIOCGIWAPLIST: + token_size = sizeof(struct sockaddr) + sizeof(struct iw_quality); + max_tokens = IW_MAX_AP; + break; + +#if WIRELESS_EXT > 13 + case SIOCGIWSCAN: +#if defined(WL_IW_USE_ISCAN) + if (g_iscan) + max_tokens = wrq->u.data.length; + else +#endif + max_tokens = IW_SCAN_MAX_DATA; + break; +#endif + + case SIOCSIWSPY: + token_size = sizeof(struct sockaddr); + max_tokens = IW_MAX_SPY; + break; + + case SIOCGIWSPY: + token_size = sizeof(struct sockaddr) + sizeof(struct iw_quality); + max_tokens = IW_MAX_SPY; + break; + +#if WIRELESS_EXT > 17 + case SIOCSIWPMKSA: + case SIOCSIWGENIE: +#endif + case SIOCSIWPRIV: + max_tokens = wrq->u.data.length; + break; + } + + if (max_tokens && wrq->u.data.pointer) { + if (wrq->u.data.length > max_tokens) { + WL_ERROR(("%s: error in cmd=%x wrq->u.data.length=%d > max_tokens=%d\n", \ + __FUNCTION__, cmd, wrq->u.data.length, max_tokens)); + ret = -E2BIG; + goto wl_iw_ioctl_done; + } + if (!(extra = kmalloc(max_tokens * token_size, GFP_KERNEL))) { + ret = -ENOMEM; + goto wl_iw_ioctl_done; + } + + if (copy_from_user(extra, wrq->u.data.pointer, wrq->u.data.length * token_size)) { + kfree(extra); + ret = -EFAULT; + goto wl_iw_ioctl_done; + } + } + + info.cmd = cmd; + info.flags = 0; + + ret = handler(dev, &info, &wrq->u, extra); + + if (extra) { + if (copy_to_user(wrq->u.data.pointer, extra, wrq->u.data.length * token_size)) { + kfree(extra); + ret = -EFAULT; + goto wl_iw_ioctl_done; + } + + kfree(extra); + } + +wl_iw_ioctl_done: + + net_os_wake_unlock(dev); + + return ret; +} + + +bool +wl_iw_conn_status_str(uint32 event_type, uint32 status, uint32 reason, + char* stringBuf, uint buflen) +{ + typedef struct conn_fail_event_map_t { + uint32 inEvent; + uint32 inStatus; + uint32 inReason; + const char* outName; + const char* outCause; + } conn_fail_event_map_t; + + +# define WL_IW_DONT_CARE 9999 + const conn_fail_event_map_t event_map [] = { + + + {WLC_E_SET_SSID, WLC_E_STATUS_SUCCESS, WL_IW_DONT_CARE, + "Conn", "Success"}, + {WLC_E_SET_SSID, WLC_E_STATUS_NO_NETWORKS, WL_IW_DONT_CARE, + "Conn", "NoNetworks"}, + {WLC_E_SET_SSID, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE, + "Conn", "ConfigMismatch"}, + {WLC_E_PRUNE, WL_IW_DONT_CARE, WLC_E_PRUNE_ENCR_MISMATCH, + "Conn", "EncrypMismatch"}, + {WLC_E_PRUNE, WL_IW_DONT_CARE, WLC_E_RSN_MISMATCH, + "Conn", "RsnMismatch"}, + {WLC_E_AUTH, WLC_E_STATUS_TIMEOUT, WL_IW_DONT_CARE, + "Conn", "AuthTimeout"}, + {WLC_E_AUTH, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE, + "Conn", "AuthFail"}, + {WLC_E_AUTH, WLC_E_STATUS_NO_ACK, WL_IW_DONT_CARE, + "Conn", "AuthNoAck"}, + {WLC_E_REASSOC, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE, + "Conn", "ReassocFail"}, + {WLC_E_REASSOC, WLC_E_STATUS_TIMEOUT, WL_IW_DONT_CARE, + "Conn", "ReassocTimeout"}, + {WLC_E_REASSOC, WLC_E_STATUS_ABORT, WL_IW_DONT_CARE, + "Conn", "ReassocAbort"}, + {WLC_E_PSK_SUP, WLC_SUP_KEYED, WL_IW_DONT_CARE, + "Sup", "ConnSuccess"}, + {WLC_E_PSK_SUP, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Sup", "WpaHandshakeFail"}, + {WLC_E_DEAUTH_IND, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Conn", "Deauth"}, + {WLC_E_DISASSOC_IND, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Conn", "DisassocInd"}, + {WLC_E_DISASSOC, WL_IW_DONT_CARE, WL_IW_DONT_CARE, + "Conn", "Disassoc"} + }; + + const char* name = ""; + const char* cause = NULL; + int i; + + + for (i = 0; i < sizeof(event_map)/sizeof(event_map[0]); i++) { + const conn_fail_event_map_t* row = &event_map[i]; + if (row->inEvent == event_type && + (row->inStatus == status || row->inStatus == WL_IW_DONT_CARE) && + (row->inReason == reason || row->inReason == WL_IW_DONT_CARE)) { + name = row->outName; + cause = row->outCause; + break; + } + } + + + if (cause) { + memset(stringBuf, 0, buflen); + snprintf(stringBuf, buflen, "%s %s %02d %02d", + name, cause, status, reason); + WL_INFORM(("Connection status: %s\n", stringBuf)); + return TRUE; + } else { + return FALSE; + } +} + +#if WIRELESS_EXT > 14 + +static bool +wl_iw_check_conn_fail(wl_event_msg_t *e, char* stringBuf, uint buflen) +{ + uint32 event = ntoh32(e->event_type); + uint32 status = ntoh32(e->status); + uint32 reason = ntoh32(e->reason); + + if (wl_iw_conn_status_str(event, status, reason, stringBuf, buflen)) { + return TRUE; + } + else + return FALSE; +} +#endif + +#ifndef IW_CUSTOM_MAX +#define IW_CUSTOM_MAX 256 +#endif + +void +wl_iw_event(struct net_device *dev, wl_event_msg_t *e, void* data) +{ +#if WIRELESS_EXT > 13 + union iwreq_data wrqu; + char extra[IW_CUSTOM_MAX + 1]; + int cmd = 0; + uint32 event_type = ntoh32(e->event_type); + uint16 flags = ntoh16(e->flags); + uint32 datalen = ntoh32(e->datalen); + uint32 status = ntoh32(e->status); + uint32 toto; +#if defined(ROAM_NOT_USED) + static uint32 roam_no_success = 0; + static bool roam_no_success_send = FALSE; +#endif + memset(&wrqu, 0, sizeof(wrqu)); + memset(extra, 0, sizeof(extra)); + + if (!dev) { + WL_ERROR(("%s: dev is null\n", __FUNCTION__)); + return; + } + + net_os_wake_lock(dev); + + WL_TRACE(("%s: dev=%s event=%d \n", __FUNCTION__, dev->name, event_type)); + + switch (event_type) { + + case WLC_E_RELOAD: + WL_ERROR(("%s: Firmware ERROR %d\n", __FUNCTION__, status)); + net_os_send_hang_message(dev); + goto wl_iw_event_end; + +#if defined(SOFTAP) + case WLC_E_PRUNE: + if (ap_cfg_running) { + char *macaddr = (char *)&e->addr; + WL_SOFTAP(("PRUNE received, %02X:%02X:%02X:%02X:%02X:%02X!\n", + macaddr[0], macaddr[1], macaddr[2], macaddr[3], \ + macaddr[4], macaddr[5])); + + if (ap_macmode) { + int i; + for (i = 0; i < ap_black_list.count; i++) { + if (!bcmp(macaddr, &ap_black_list.ea[i], \ + sizeof(struct ether_addr))) { + WL_SOFTAP(("mac in black list, ignore it\n")); + break; + } + } + + if (i == ap_black_list.count) { + char mac_buf[32] = {0}; + sprintf(mac_buf, "STA_BLOCK %02X:%02X:%02X:%02X:%02X:%02X", + macaddr[0], macaddr[1], macaddr[2], + macaddr[3], macaddr[4], macaddr[5]); + wl_iw_send_priv_event(priv_dev, mac_buf); + } + } + } + break; +#endif + case WLC_E_TXFAIL: + cmd = IWEVTXDROP; + memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN); + wrqu.addr.sa_family = ARPHRD_ETHER; + break; +#if WIRELESS_EXT > 14 + case WLC_E_JOIN: + case WLC_E_ASSOC_IND: + case WLC_E_REASSOC_IND: +#if defined(SOFTAP) + WL_SOFTAP(("STA connect received %d\n", event_type)); + if (ap_cfg_running) { + wl_iw_send_priv_event(priv_dev, "STA_JOIN"); + goto wl_iw_event_end; + } +#endif + memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN); + wrqu.addr.sa_family = ARPHRD_ETHER; + cmd = IWEVREGISTERED; + break; + case WLC_E_ROAM: + if (status == WLC_E_STATUS_SUCCESS) { + WL_ASSOC(("%s: WLC_E_ROAM: success\n", __FUNCTION__)); +#if defined(ROAM_NOT_USED) + roam_no_success_send = FALSE; + roam_no_success = 0; +#endif + goto wl_iw_event_end; + } +#if defined(ROAM_NOT_USED) + else if (status == WLC_E_STATUS_NO_NETWORKS) { + roam_no_success++; + if ((roam_no_success == 5) && (roam_no_success_send == FALSE)) { + roam_no_success_send = TRUE; + bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN); + bzero(&extra, ETHER_ADDR_LEN); + cmd = SIOCGIWAP; + WL_ERROR(("%s ROAMING did not succeeded , send Link Down\n", \ + __FUNCTION__)); + } else { + WL_TRACE(("##### ROAMING did not succeeded %d\n", roam_no_success)); + goto wl_iw_event_end; + } + } +#endif + break; + case WLC_E_DEAUTH_IND: + case WLC_E_DISASSOC_IND: +#if defined(SOFTAP) + WL_SOFTAP(("STA disconnect received %d\n", event_type)); + if (ap_cfg_running) { + wl_iw_send_priv_event(priv_dev, "STA_LEAVE"); + goto wl_iw_event_end; + } +#endif + cmd = SIOCGIWAP; + bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN); + wrqu.addr.sa_family = ARPHRD_ETHER; + bzero(&extra, ETHER_ADDR_LEN); + break; + case WLC_E_LINK: + case WLC_E_NDIS_LINK: + cmd = SIOCGIWAP; + if (!(flags & WLC_EVENT_MSG_LINK)) { +#ifdef SOFTAP +#ifdef AP_ONLY + if (ap_cfg_running) { +#else + if (ap_cfg_running && !strncmp(dev->name, "wl0.1", 5)) { +#endif + WL_SOFTAP(("AP DOWN %d\n", event_type)); + wl_iw_send_priv_event(priv_dev, "AP_DOWN"); + } else { + WL_TRACE(("STA_Link Down\n")); + g_ss_cache_ctrl.m_link_down = 1; + } +#else + g_ss_cache_ctrl.m_link_down = 1; +#endif + WL_TRACE(("Link Down\n")); + + bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN); + bzero(&extra, ETHER_ADDR_LEN); + } + else { + memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN); + g_ss_cache_ctrl.m_link_down = 0; + + memcpy(g_ss_cache_ctrl.m_active_bssid, &e->addr, ETHER_ADDR_LEN); + +#ifdef SOFTAP +#ifdef AP_ONLY + if (ap_cfg_running) { +#else + if (ap_cfg_running && !strncmp(dev->name, "wl0.1", 5)) { +#endif + WL_SOFTAP(("AP UP %d\n", event_type)); + wl_iw_send_priv_event(priv_dev, "AP_UP"); + } else { + WL_TRACE(("STA_LINK_UP\n")); +#if defined(ROAM_NOT_USED) + roam_no_success_send = FALSE; + roam_no_success = 0; +#endif + } +#endif + WL_TRACE(("Link UP\n")); + + } + net_os_wake_lock_timeout_enable(dev); + wrqu.addr.sa_family = ARPHRD_ETHER; + break; + case WLC_E_ACTION_FRAME: + cmd = IWEVCUSTOM; + if (datalen + 1 <= sizeof(extra)) { + wrqu.data.length = datalen + 1; + extra[0] = WLC_E_ACTION_FRAME; + memcpy(&extra[1], data, datalen); + WL_TRACE(("WLC_E_ACTION_FRAME len %d \n", wrqu.data.length)); + } + break; + + case WLC_E_ACTION_FRAME_COMPLETE: + cmd = IWEVCUSTOM; + memcpy(&toto, data, 4); + if (sizeof(status) + 1 <= sizeof(extra)) { + wrqu.data.length = sizeof(status) + 1; + extra[0] = WLC_E_ACTION_FRAME_COMPLETE; + memcpy(&extra[1], &status, sizeof(status)); + printf("wl_iw_event status %d PacketId %d \n", status, toto); + printf("WLC_E_ACTION_FRAME_COMPLETE len %d \n", wrqu.data.length); + } + break; +#endif +#if WIRELESS_EXT > 17 + case WLC_E_MIC_ERROR: { + struct iw_michaelmicfailure *micerrevt = (struct iw_michaelmicfailure *)&extra; + cmd = IWEVMICHAELMICFAILURE; + wrqu.data.length = sizeof(struct iw_michaelmicfailure); + if (flags & WLC_EVENT_MSG_GROUP) + micerrevt->flags |= IW_MICFAILURE_GROUP; + else + micerrevt->flags |= IW_MICFAILURE_PAIRWISE; + memcpy(micerrevt->src_addr.sa_data, &e->addr, ETHER_ADDR_LEN); + micerrevt->src_addr.sa_family = ARPHRD_ETHER; + + break; + } +#ifdef BCMWPA2 + case WLC_E_PMKID_CACHE: { + if (data) + { + struct iw_pmkid_cand *iwpmkidcand = (struct iw_pmkid_cand *)&extra; + pmkid_cand_list_t *pmkcandlist; + pmkid_cand_t *pmkidcand; + int count; + + cmd = IWEVPMKIDCAND; + pmkcandlist = data; + count = ntoh32_ua((uint8 *)&pmkcandlist->npmkid_cand); + ASSERT(count >= 0); + wrqu.data.length = sizeof(struct iw_pmkid_cand); + pmkidcand = pmkcandlist->pmkid_cand; + while (count) { + bzero(iwpmkidcand, sizeof(struct iw_pmkid_cand)); + if (pmkidcand->preauth) + iwpmkidcand->flags |= IW_PMKID_CAND_PREAUTH; + bcopy(&pmkidcand->BSSID, &iwpmkidcand->bssid.sa_data, + ETHER_ADDR_LEN); +#ifndef SANDGATE2G + wireless_send_event(dev, cmd, &wrqu, extra); +#endif + pmkidcand++; + count--; + } + } + goto wl_iw_event_end; + } +#endif +#endif + + case WLC_E_SCAN_COMPLETE: +#if defined(WL_IW_USE_ISCAN) + if ((g_iscan) && (g_iscan->sysioc_pid >= 0) && + (g_iscan->iscan_state != ISCAN_STATE_IDLE)) + { + up(&g_iscan->sysioc_sem); + } else { + cmd = SIOCGIWSCAN; + wrqu.data.length = strlen(extra); + WL_TRACE(("Event WLC_E_SCAN_COMPLETE from specific scan %d\n", \ + g_iscan->iscan_state)); + } +#else + cmd = SIOCGIWSCAN; + wrqu.data.length = strlen(extra); + WL_TRACE(("Event WLC_E_SCAN_COMPLETE\n")); +#endif + break; + + case WLC_E_PFN_NET_FOUND: + { + wlc_ssid_t * ssid; + ssid = (wlc_ssid_t *)data; + WL_TRACE(("%s Event WLC_E_PFN_NET_FOUND, send %s up : find %s len=%d\n", \ + __FUNCTION__, PNO_EVENT_UP, ssid->SSID, ssid->SSID_len)); + net_os_wake_lock_timeout_enable(dev); + cmd = IWEVCUSTOM; + memset(&wrqu, 0, sizeof(wrqu)); + strcpy(extra, PNO_EVENT_UP); + wrqu.data.length = strlen(extra); + } + break; + + default: + + WL_TRACE(("Unknown Event %d: ignoring\n", event_type)); + break; + } +#ifndef SANDGATE2G + if (cmd) { +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 31)) + if (cmd == SIOCGIWSCAN) + wireless_send_event(dev, cmd, &wrqu, NULL); + else +#endif + wireless_send_event(dev, cmd, &wrqu, extra); + } +#endif + +#if WIRELESS_EXT > 14 + memset(extra, 0, sizeof(extra)); + if (wl_iw_check_conn_fail(e, extra, sizeof(extra))) { + cmd = IWEVCUSTOM; + wrqu.data.length = strlen(extra); +#ifndef SANDGATE2G + wireless_send_event(dev, cmd, &wrqu, extra); +#endif + } +#endif +wl_iw_event_end: + net_os_wake_unlock(dev); +#endif +} + +int wl_iw_get_wireless_stats(struct net_device *dev, struct iw_statistics *wstats) +{ + int res = 0; + wl_cnt_t cnt; + int phy_noise; + int rssi; + scb_val_t scb_val; + + phy_noise = 0; + if ((res = dev_wlc_ioctl(dev, WLC_GET_PHY_NOISE, &phy_noise, sizeof(phy_noise)))) + goto done; + + phy_noise = dtoh32(phy_noise); + WL_TRACE(("wl_iw_get_wireless_stats phy noise=%d\n", phy_noise)); + + bzero(&scb_val, sizeof(scb_val_t)); + if ((res = dev_wlc_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t)))) + goto done; + + rssi = dtoh32(scb_val.val); + WL_TRACE(("wl_iw_get_wireless_stats rssi=%d\n", rssi)); + if (rssi <= WL_IW_RSSI_NO_SIGNAL) + wstats->qual.qual = 0; + else if (rssi <= WL_IW_RSSI_VERY_LOW) + wstats->qual.qual = 1; + else if (rssi <= WL_IW_RSSI_LOW) + wstats->qual.qual = 2; + else if (rssi <= WL_IW_RSSI_GOOD) + wstats->qual.qual = 3; + else if (rssi <= WL_IW_RSSI_VERY_GOOD) + wstats->qual.qual = 4; + else + wstats->qual.qual = 5; + + + wstats->qual.level = 0x100 + rssi; + wstats->qual.noise = 0x100 + phy_noise; +#if WIRELESS_EXT > 18 + wstats->qual.updated |= (IW_QUAL_ALL_UPDATED | IW_QUAL_DBM); +#else + wstats->qual.updated |= 7; +#endif + +#if WIRELESS_EXT > 11 + WL_TRACE(("wl_iw_get_wireless_stats counters=%d\n", (int)sizeof(wl_cnt_t))); + + memset(&cnt, 0, sizeof(wl_cnt_t)); + res = dev_wlc_bufvar_get(dev, "counters", (char *)&cnt, sizeof(wl_cnt_t)); + if (res) + { + WL_ERROR(("wl_iw_get_wireless_stats counters failed error=%d\n", res)); + goto done; + } + + cnt.version = dtoh16(cnt.version); + if (cnt.version != WL_CNT_T_VERSION) { + WL_TRACE(("\tIncorrect version of counters struct: expected %d; got %d\n", + WL_CNT_T_VERSION, cnt.version)); + goto done; + } + + wstats->discard.nwid = 0; + wstats->discard.code = dtoh32(cnt.rxundec); + wstats->discard.fragment = dtoh32(cnt.rxfragerr); + wstats->discard.retries = dtoh32(cnt.txfail); + wstats->discard.misc = dtoh32(cnt.rxrunt) + dtoh32(cnt.rxgiant); + wstats->miss.beacon = 0; + + WL_TRACE(("wl_iw_get_wireless_stats counters txframe=%d txbyte=%d\n", + dtoh32(cnt.txframe), dtoh32(cnt.txbyte))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxfrmtoolong=%d\n", dtoh32(cnt.rxfrmtoolong))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxbadplcp=%d\n", dtoh32(cnt.rxbadplcp))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxundec=%d\n", dtoh32(cnt.rxundec))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxfragerr=%d\n", dtoh32(cnt.rxfragerr))); + WL_TRACE(("wl_iw_get_wireless_stats counters txfail=%d\n", dtoh32(cnt.txfail))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxrunt=%d\n", dtoh32(cnt.rxrunt))); + WL_TRACE(("wl_iw_get_wireless_stats counters rxgiant=%d\n", dtoh32(cnt.rxgiant))); + +#endif + +done: + return res; +} +static void +wl_iw_bt_flag_set( + struct net_device *dev, + bool set) +{ +#if defined(BT_DHCP_USE_FLAGS) + char buf_flag7_dhcp_on[8] = { 7, 00, 00, 00, 0x1, 0x0, 0x00, 0x00 }; + char buf_flag7_default[8] = { 7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00}; +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_lock(); +#endif + +#if defined(BT_DHCP_eSCO_FIX) + set_btc_esco_params(dev, set); +#endif + +#if defined(BT_DHCP_USE_FLAGS) + WL_TRACE_COEX(("WI-FI priority boost via bt flags, set:%d\n", set)); + if (set == TRUE) { + dev_wlc_bufvar_set(dev, "btc_flags", + (char *)&buf_flag7_dhcp_on[0], sizeof(buf_flag7_dhcp_on)); + } + else { + dev_wlc_bufvar_set(dev, "btc_flags", + (char *)&buf_flag7_default[0], sizeof(buf_flag7_default)); + } +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) + rtnl_unlock(); +#endif +} + +static void +wl_iw_bt_timerfunc(ulong data) +{ + bt_info_t *bt_local = (bt_info_t *)data; + bt_local->timer_on = 0; + WL_TRACE(("%s\n", __FUNCTION__)); + + up(&bt_local->bt_sem); +} + +static int +_bt_dhcp_sysioc_thread(void *data) +{ + DAEMONIZE("dhcp_sysioc"); + + while (down_interruptible(&g_bt->bt_sem) == 0) { + + net_os_wake_lock(g_bt->dev); + + if (g_bt->timer_on) { + g_bt->timer_on = 0; + del_timer_sync(&g_bt->timer); + } + + switch (g_bt->bt_state) { + case BT_DHCP_START: + WL_TRACE_COEX(("%s bt_dhcp stm: started \n", __FUNCTION__)); + g_bt->bt_state = BT_DHCP_OPPORTUNITY_WINDOW; + mod_timer(&g_bt->timer, jiffies + BT_DHCP_OPPORTUNITY_WINDOW_TIME*HZ/1000); + g_bt->timer_on = 1; + break; + + case BT_DHCP_OPPORTUNITY_WINDOW: + if (g_bt->dhcp_done) { + WL_TRACE_COEX(("%s DHCP Done before T1 expiration\n", \ + __FUNCTION__)); + g_bt->bt_state = BT_DHCP_IDLE; + g_bt->timer_on = 0; + break; + } + + WL_TRACE_COEX(("%s DHCP T1:%d expired\n", \ + __FUNCTION__, BT_DHCP_OPPORTUNITY_WINDOW_TIME)); + if (g_bt->dev) wl_iw_bt_flag_set(g_bt->dev, TRUE); + g_bt->bt_state = BT_DHCP_FLAG_FORCE_TIMEOUT; + mod_timer(&g_bt->timer, jiffies + BT_DHCP_FLAG_FORCE_TIME*HZ/1000); + g_bt->timer_on = 1; + break; + + case BT_DHCP_FLAG_FORCE_TIMEOUT: + if (g_bt->dhcp_done) { + WL_TRACE_COEX(("%s DHCP Done before T2 expiration\n", \ + __FUNCTION__)); + } else { + WL_TRACE_COEX(("%s DHCP wait interval T2:%d msec expired\n", + __FUNCTION__, BT_DHCP_FLAG_FORCE_TIME)); + } + + if (g_bt->dev) wl_iw_bt_flag_set(g_bt->dev, FALSE); + g_bt->bt_state = BT_DHCP_IDLE; + g_bt->timer_on = 0; + break; + + default: + WL_ERROR(("%s error g_status=%d !!!\n", __FUNCTION__, \ + g_bt->bt_state)); + if (g_bt->dev) wl_iw_bt_flag_set(g_bt->dev, FALSE); + g_bt->bt_state = BT_DHCP_IDLE; + g_bt->timer_on = 0; + break; + } + + net_os_wake_unlock(g_bt->dev); + } + + if (g_bt->timer_on) { + g_bt->timer_on = 0; + del_timer_sync(&g_bt->timer); + } + + complete_and_exit(&g_bt->bt_exited, 0); +} + +static void +wl_iw_bt_release(void) +{ + bt_info_t *bt_local = g_bt; + + if (!bt_local) { + return; + } + + if (bt_local->bt_pid >= 0) { + KILL_PROC(bt_local->bt_pid, SIGTERM); + wait_for_completion(&bt_local->bt_exited); + } + kfree(bt_local); + g_bt = NULL; +} + +static int +wl_iw_bt_init(struct net_device *dev) +{ + bt_info_t *bt_dhcp = NULL; + + bt_dhcp = kmalloc(sizeof(bt_info_t), GFP_KERNEL); + if (!bt_dhcp) + return -ENOMEM; + + memset(bt_dhcp, 0, sizeof(bt_info_t)); + bt_dhcp->bt_pid = -1; + g_bt = bt_dhcp; + bt_dhcp->dev = dev; + bt_dhcp->bt_state = BT_DHCP_IDLE; + + + bt_dhcp->timer_ms = 10; + init_timer(&bt_dhcp->timer); + bt_dhcp->timer.data = (ulong)bt_dhcp; + bt_dhcp->timer.function = wl_iw_bt_timerfunc; + + sema_init(&bt_dhcp->bt_sem, 0); + init_completion(&bt_dhcp->bt_exited); + bt_dhcp->bt_pid = kernel_thread(_bt_dhcp_sysioc_thread, bt_dhcp, 0); + if (bt_dhcp->bt_pid < 0) { + WL_ERROR(("Failed in %s\n", __FUNCTION__)); + return -ENOMEM; + } + + return 0; +} + +int wl_iw_attach(struct net_device *dev, void *dhdp) +{ + int params_size; + wl_iw_t *iw; +#if defined(WL_IW_USE_ISCAN) + iscan_info_t *iscan = NULL; +#endif + + mutex_init(&wl_cache_lock); + +#if defined(WL_IW_USE_ISCAN) + if (!dev) + return 0; + + memset(&g_wl_iw_params, 0, sizeof(wl_iw_extra_params_t)); + +#ifdef CSCAN + params_size = (WL_SCAN_PARAMS_FIXED_SIZE + OFFSETOF(wl_iscan_params_t, params)) + + (WL_NUMCHANNELS * sizeof(uint16)) + WL_SCAN_PARAMS_SSID_MAX * sizeof(wlc_ssid_t); +#else + params_size = (WL_SCAN_PARAMS_FIXED_SIZE + OFFSETOF(wl_iscan_params_t, params)); +#endif + iscan = kmalloc(sizeof(iscan_info_t), GFP_KERNEL); + if (!iscan) + return -ENOMEM; + memset(iscan, 0, sizeof(iscan_info_t)); + + iscan->iscan_ex_params_p = (wl_iscan_params_t*)kmalloc(params_size, GFP_KERNEL); + if (!iscan->iscan_ex_params_p) + return -ENOMEM; + iscan->iscan_ex_param_size = params_size; + iscan->sysioc_pid = -1; + + g_iscan = iscan; + iscan->dev = dev; + iscan->iscan_state = ISCAN_STATE_IDLE; +#if defined(CONFIG_FIRST_SCAN) + g_first_broadcast_scan = BROADCAST_SCAN_FIRST_IDLE; + g_first_counter_scans = 0; + g_iscan->scan_flag = 0; +#endif + + iscan->timer_ms = 8000; + init_timer(&iscan->timer); + iscan->timer.data = (ulong)iscan; + iscan->timer.function = wl_iw_timerfunc; + + sema_init(&iscan->sysioc_sem, 0); + init_completion(&iscan->sysioc_exited); + iscan->sysioc_pid = kernel_thread(_iscan_sysioc_thread, iscan, 0); + if (iscan->sysioc_pid < 0) + return -ENOMEM; +#endif + + iw = *(wl_iw_t **)netdev_priv(dev); + iw->pub = (dhd_pub_t *)dhdp; +#ifdef SOFTAP + priv_dev = dev; +#endif + g_scan = NULL; + + g_scan = (void *)kmalloc(G_SCAN_RESULTS, GFP_KERNEL); + if (!g_scan) + return -ENOMEM; + + memset(g_scan, 0, G_SCAN_RESULTS); + g_scan_specified_ssid = 0; + +#if !defined(CSCAN) + wl_iw_init_ss_cache_ctrl(); +#endif + + wl_iw_bt_init(dev); + + return 0; +} + +void wl_iw_detach(void) +{ +#if defined(WL_IW_USE_ISCAN) + iscan_buf_t *buf; + iscan_info_t *iscan = g_iscan; + + if (!iscan) + return; + if (iscan->sysioc_pid >= 0) { + KILL_PROC(iscan->sysioc_pid, SIGTERM); + wait_for_completion(&iscan->sysioc_exited); + } + mutex_lock(&wl_cache_lock); + while (iscan->list_hdr) { + buf = iscan->list_hdr->next; + kfree(iscan->list_hdr); + iscan->list_hdr = buf; + } + kfree(iscan->iscan_ex_params_p); + kfree(iscan); + g_iscan = NULL; + mutex_unlock(&wl_cache_lock); +#endif + + if (g_scan) + kfree(g_scan); + + g_scan = NULL; +#if !defined(CSCAN) + wl_iw_release_ss_cache_ctrl(); +#endif + wl_iw_bt_release(); +#ifdef SOFTAP + if (ap_cfg_running) { + WL_TRACE(("\n%s AP is going down\n", __FUNCTION__)); + wl_iw_send_priv_event(priv_dev, "AP_DOWN"); + } +#endif +} diff --git a/drivers/net/wireless/bcm4329/wl_iw.h b/drivers/net/wireless/bcm4329/wl_iw.h new file mode 100644 index 0000000000000000000000000000000000000000..ee6c699936ea0b7bd212e22159eb1ae2b3712ac7 --- /dev/null +++ b/drivers/net/wireless/bcm4329/wl_iw.h @@ -0,0 +1,309 @@ +/* + * Linux Wireless Extensions support + * + * Copyright (C) 1999-2010, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: wl_iw.h,v 1.5.34.1.6.36.4.18 2011/02/10 19:33:12 Exp $ + */ + + +#ifndef _wl_iw_h_ +#define _wl_iw_h_ + +#include + +#include +#include +#include + +#define WL_SCAN_PARAMS_SSID_MAX 10 +#define GET_SSID "SSID=" +#define GET_CHANNEL "CH=" +#define GET_NPROBE "NPROBE=" +#define GET_ACTIVE_ASSOC_DWELL "ACTIVE=" +#define GET_PASSIVE_ASSOC_DWELL "PASSIVE=" +#define GET_HOME_DWELL "HOME=" +#define GET_SCAN_TYPE "TYPE=" + +#define BAND_GET_CMD "GETBAND" +#define BAND_SET_CMD "SETBAND" +#define DTIM_SKIP_GET_CMD "DTIMSKIPGET" +#define DTIM_SKIP_SET_CMD "DTIMSKIPSET" +#define SETSUSPEND_CMD "SETSUSPENDOPT" +#define PNOSSIDCLR_SET_CMD "PNOSSIDCLR" +#define PNOSETUP_SET_CMD "PNOSETUP " +#define PNOENABLE_SET_CMD "PNOFORCE" +#define PNODEBUG_SET_CMD "PNODEBUG" +#define TXPOWER_SET_CMD "TXPOWER" +#define RXFILTER_START_CMD "RXFILTER-START" +#define RXFILTER_STOP_CMD "RXFILTER-STOP" +#define RXFILTER_ADD_CMD "RXFILTER-ADD" +#define RXFILTER_REMOVE_CMD "RXFILTER-REMOVE" + +#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + + +typedef struct wl_iw_extra_params { + int target_channel; +} wl_iw_extra_params_t; + +struct cntry_locales_custom { + char iso_abbrev[WLC_CNTRY_BUF_SZ]; + char custom_locale[WLC_CNTRY_BUF_SZ]; + int32 custom_locale_rev; +}; + +#define WL_IW_RSSI_MINVAL -200 +#define WL_IW_RSSI_NO_SIGNAL -91 +#define WL_IW_RSSI_VERY_LOW -80 +#define WL_IW_RSSI_LOW -70 +#define WL_IW_RSSI_GOOD -68 +#define WL_IW_RSSI_VERY_GOOD -58 +#define WL_IW_RSSI_EXCELLENT -57 +#define WL_IW_RSSI_INVALID 0 +#define MAX_WX_STRING 80 +#define isprint(c) bcm_isprint(c) +#define WL_IW_SET_ACTIVE_SCAN (SIOCIWFIRSTPRIV+1) +#define WL_IW_GET_RSSI (SIOCIWFIRSTPRIV+3) +#define WL_IW_SET_PASSIVE_SCAN (SIOCIWFIRSTPRIV+5) +#define WL_IW_GET_LINK_SPEED (SIOCIWFIRSTPRIV+7) +#define WL_IW_GET_CURR_MACADDR (SIOCIWFIRSTPRIV+9) +#define WL_IW_SET_STOP (SIOCIWFIRSTPRIV+11) +#define WL_IW_SET_START (SIOCIWFIRSTPRIV+13) + + +#define WL_SET_AP_CFG (SIOCIWFIRSTPRIV+15) +#define WL_AP_STA_LIST (SIOCIWFIRSTPRIV+17) +#define WL_AP_MAC_FLTR (SIOCIWFIRSTPRIV+19) +#define WL_AP_BSS_START (SIOCIWFIRSTPRIV+21) +#define AP_LPB_CMD (SIOCIWFIRSTPRIV+23) +#define WL_AP_STOP (SIOCIWFIRSTPRIV+25) +#define WL_FW_RELOAD (SIOCIWFIRSTPRIV+27) +#define WL_AP_STA_DISASSOC (SIOCIWFIRSTPRIV+29) +#define WL_COMBO_SCAN (SIOCIWFIRSTPRIV+31) + +#define G_SCAN_RESULTS (8*1024) +#define WE_ADD_EVENT_FIX 0x80 +#define G_WLAN_SET_ON 0 +#define G_WLAN_SET_OFF 1 + +#define CHECK_EXTRA_FOR_NULL(extra) \ +if (!extra) { \ + WL_ERROR(("%s: error : extra is null pointer\n", __FUNCTION__)); \ + return -EINVAL; \ +} + +typedef struct wl_iw { + char nickname[IW_ESSID_MAX_SIZE]; + + struct iw_statistics wstats; + + int spy_num; + uint32 pwsec; + uint32 gwsec; + bool privacy_invoked; + + struct ether_addr spy_addr[IW_MAX_SPY]; + struct iw_quality spy_qual[IW_MAX_SPY]; + void *wlinfo; + dhd_pub_t * pub; +} wl_iw_t; + +#define WLC_IW_SS_CACHE_MAXLEN 2048 +#define WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN 32 +#define WLC_IW_BSS_INFO_MAXLEN \ + (WLC_IW_SS_CACHE_MAXLEN - WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN) + +typedef struct wl_iw_ss_cache { + struct wl_iw_ss_cache *next; + int dirty; + uint32 buflen; + uint32 version; + uint32 count; + wl_bss_info_t bss_info[1]; +} wl_iw_ss_cache_t; + +typedef struct wl_iw_ss_cache_ctrl { + wl_iw_ss_cache_t *m_cache_head; + int m_link_down; + int m_timer_expired; + char m_active_bssid[ETHER_ADDR_LEN]; + uint m_prev_scan_mode; + uint m_cons_br_scan_cnt; + struct timer_list *m_timer; +} wl_iw_ss_cache_ctrl_t; + +typedef enum broadcast_first_scan { + BROADCAST_SCAN_FIRST_IDLE = 0, + BROADCAST_SCAN_FIRST_STARTED, + BROADCAST_SCAN_FIRST_RESULT_READY, + BROADCAST_SCAN_FIRST_RESULT_CONSUMED +} broadcast_first_scan_t; +#ifdef SOFTAP +#define SSID_LEN 33 +#define SEC_LEN 16 +#define KEY_LEN 65 +#define PROFILE_OFFSET 32 +struct ap_profile { + uint8 ssid[SSID_LEN]; + uint8 sec[SEC_LEN]; + uint8 key[KEY_LEN]; + uint32 channel; + uint32 preamble; + uint32 max_scb; + uint32 closednet; + char country_code[WLC_CNTRY_BUF_SZ]; +}; + + +#define MACLIST_MODE_DISABLED 0 +#define MACLIST_MODE_DENY 1 +#define MACLIST_MODE_ALLOW 2 +struct mflist { + uint count; + struct ether_addr ea[16]; +}; + +struct mac_list_set { + uint32 mode; + struct mflist mac_list; +}; +#endif + +#if WIRELESS_EXT > 12 +#include +extern const struct iw_handler_def wl_iw_handler_def; +#endif + +extern int wl_iw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +extern void wl_iw_event(struct net_device *dev, wl_event_msg_t *e, void* data); +extern int wl_iw_get_wireless_stats(struct net_device *dev, struct iw_statistics *wstats); +int wl_iw_attach(struct net_device *dev, void * dhdp); +void wl_iw_detach(void); +int wl_control_wl_start(struct net_device *dev); + +extern int net_os_wake_lock(struct net_device *dev); +extern int net_os_wake_unlock(struct net_device *dev); +extern int net_os_wake_lock_timeout(struct net_device *dev); +extern int net_os_wake_lock_timeout_enable(struct net_device *dev); +extern int net_os_set_suspend_disable(struct net_device *dev, int val); +extern int net_os_set_suspend(struct net_device *dev, int val); +extern int net_os_set_dtim_skip(struct net_device *dev, int val); +extern void get_customized_country_code(char *country_iso_code, wl_country_t *cspec); +extern char *dhd_bus_country_get(struct net_device *dev); +extern int dhd_get_dtim_skip(dhd_pub_t *dhd); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) +#define IWE_STREAM_ADD_EVENT(info, stream, ends, iwe, extra) \ + iwe_stream_add_event(info, stream, ends, iwe, extra) +#define IWE_STREAM_ADD_VALUE(info, event, value, ends, iwe, event_len) \ + iwe_stream_add_value(info, event, value, ends, iwe, event_len) +#define IWE_STREAM_ADD_POINT(info, stream, ends, iwe, extra) \ + iwe_stream_add_point(info, stream, ends, iwe, extra) +#else +#define IWE_STREAM_ADD_EVENT(info, stream, ends, iwe, extra) \ + iwe_stream_add_event(stream, ends, iwe, extra) +#define IWE_STREAM_ADD_VALUE(info, event, value, ends, iwe, event_len) \ + iwe_stream_add_value(event, value, ends, iwe, event_len) +#define IWE_STREAM_ADD_POINT(info, stream, ends, iwe, extra) \ + iwe_stream_add_point(stream, ends, iwe, extra) +#endif + +extern int dhd_pno_enable(dhd_pub_t *dhd, int pfn_enabled); +extern int dhd_pno_clean(dhd_pub_t *dhd); +extern int dhd_pno_set(dhd_pub_t *dhd, wlc_ssid_t* ssids_local, int nssid, \ + ushort scan_fr, int pno_repeat, int pno_freq_expo_max); +extern int dhd_pno_get_status(dhd_pub_t *dhd); +extern int dhd_dev_pno_reset(struct net_device *dev); +extern int dhd_dev_pno_set(struct net_device *dev, wlc_ssid_t* ssids_local, \ + int nssid, ushort scan_fr, int pno_repeat, int pno_freq_expo_max); +extern int dhd_dev_pno_enable(struct net_device *dev, int pfn_enabled); +extern int dhd_dev_get_pno_status(struct net_device *dev); +extern void dhd_bus_country_set(struct net_device *dev, wl_country_t *cspec); + +#define PNO_TLV_PREFIX 'S' +#define PNO_TLV_VERSION '1' +#define PNO_TLV_SUBVERSION '2' +#define PNO_TLV_RESERVED '0' +#define PNO_TLV_TYPE_SSID_IE 'S' +#define PNO_TLV_TYPE_TIME 'T' +#define PNO_TLV_FREQ_REPEAT 'R' +#define PNO_TLV_FREQ_EXPO_MAX 'M' +#define PNO_EVENT_UP "PNO_EVENT" + +typedef struct cmd_tlv { + char prefix; + char version; + char subver; + char reserved; +} cmd_tlv_t; + +#ifdef SOFTAP_TLV_CFG +#define SOFTAP_SET_CMD "SOFTAPSET " +#define SOFTAP_TLV_PREFIX 'A' +#define SOFTAP_TLV_VERSION '1' +#define SOFTAP_TLV_SUBVERSION '0' +#define SOFTAP_TLV_RESERVED '0' + +#define TLV_TYPE_SSID 'S' +#define TLV_TYPE_SECUR 'E' +#define TLV_TYPE_KEY 'K' +#define TLV_TYPE_CHANNEL 'C' +#endif + +#if defined(CSCAN) + +typedef struct cscan_tlv { + char prefix; + char version; + char subver; + char reserved; +} cscan_tlv_t; + +#define CSCAN_COMMAND "CSCAN " +#define CSCAN_TLV_PREFIX 'S' +#define CSCAN_TLV_VERSION 1 +#define CSCAN_TLV_SUBVERSION 0 +#define CSCAN_TLV_TYPE_SSID_IE 'S' +#define CSCAN_TLV_TYPE_CHANNEL_IE 'C' +#define CSCAN_TLV_TYPE_NPROBE_IE 'N' +#define CSCAN_TLV_TYPE_ACTIVE_IE 'A' +#define CSCAN_TLV_TYPE_PASSIVE_IE 'P' +#define CSCAN_TLV_TYPE_HOME_IE 'H' +#define CSCAN_TLV_TYPE_STYPE_IE 'T' + +extern int wl_iw_parse_channel_list_tlv(char** list_str, uint16* channel_list, \ + int channel_num, int *bytes_left); + +extern int wl_iw_parse_data_tlv(char** list_str, void *dst, int dst_size, \ + const char token, int input_size, int *bytes_left); + +extern int wl_iw_parse_ssid_list_tlv(char** list_str, wlc_ssid_t* ssid, \ + int max, int *bytes_left); + +extern int wl_iw_parse_ssid_list(char** list_str, wlc_ssid_t* ssid, int idx, int max); + +extern int wl_iw_parse_channel_list(char** list_str, uint16* channel_list, int channel_num); + +#endif + +#endif diff --git a/drivers/net/wireless/bcmdhd/bcmwifi.c b/drivers/net/wireless/bcmdhd/bcmwifi.c new file mode 100644 index 0000000000000000000000000000000000000000..bc975e8be1b6a36e0f45d0a9ee4b593f7ea441a0 --- /dev/null +++ b/drivers/net/wireless/bcmdhd/bcmwifi.c @@ -0,0 +1,930 @@ +/* + * Misc utility routines used by kernel or app-level. + * Contents are wifi-specific, used by any kernel or app-level + * software that might want wifi things as it grows. + * + * Copyright (C) 1999-2012, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * $Id: bcmwifi.c 309193 2012-01-19 00:03:57Z $ + */ + +#include +#include + +#ifdef BCMDRIVER +#include +#include +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) +#define tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c)) +#else +#include +#include +#include +#ifndef ASSERT +#define ASSERT(exp) +#endif +#endif +#include + +#if defined(WIN32) && (defined(BCMDLL) || defined(WLMDLL)) +#include +#endif + +#ifndef D11AC_IOTYPES + + + + + + + +char * +wf_chspec_ntoa(chanspec_t chspec, char *buf) +{ + const char *band, *bw, *sb; + uint channel; + + band = ""; + bw = ""; + sb = ""; + channel = CHSPEC_CHANNEL(chspec); + + if ((CHSPEC_IS2G(chspec) && channel > CH_MAX_2G_CHANNEL) || + (CHSPEC_IS5G(chspec) && channel <= CH_MAX_2G_CHANNEL)) + band = (CHSPEC_IS2G(chspec)) ? "b" : "a"; + if (CHSPEC_IS40(chspec)) { + if (CHSPEC_SB_UPPER(chspec)) { + sb = "u"; + channel += CH_10MHZ_APART; + } else { + sb = "l"; + channel -= CH_10MHZ_APART; + } + } else if (CHSPEC_IS10(chspec)) { + bw = "n"; + } + + + snprintf(buf, 6, "%d%s%s%s", channel, band, bw, sb); + return (buf); +} + + +chanspec_t +wf_chspec_aton(const char *a) +{ + char *endp = NULL; + uint channel, band, bw, ctl_sb; + char c; + + channel = strtoul(a, &endp, 10); + + + if (endp == a) + return 0; + + if (channel > MAXCHANNEL) + return 0; + + band = ((channel <= CH_MAX_2G_CHANNEL) ? WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G); + bw = WL_CHANSPEC_BW_20; + ctl_sb = WL_CHANSPEC_CTL_SB_NONE; + + a = endp; + + c = tolower(a[0]); + if (c == '\0') + goto done; + + + if (c == 'a' || c == 'b') { + band = (c == 'a') ? WL_CHANSPEC_BAND_5G : WL_CHANSPEC_BAND_2G; + a++; + c = tolower(a[0]); + if (c == '\0') + goto done; + } + + + if (c == 'n') { + bw = WL_CHANSPEC_BW_10; + } else if (c == 'l') { + bw = WL_CHANSPEC_BW_40; + ctl_sb = WL_CHANSPEC_CTL_SB_LOWER; + + if (channel <= (MAXCHANNEL - CH_20MHZ_APART)) + channel += CH_10MHZ_APART; + else + return 0; + } else if (c == 'u') { + bw = WL_CHANSPEC_BW_40; + ctl_sb = WL_CHANSPEC_CTL_SB_UPPER; + + if (channel > CH_20MHZ_APART) + channel -= CH_10MHZ_APART; + else + return 0; + } else { + return 0; + } + +done: + return (channel | band | bw | ctl_sb); +} + + +bool +wf_chspec_malformed(chanspec_t chanspec) +{ + + if (!CHSPEC_IS5G(chanspec) && !CHSPEC_IS2G(chanspec)) + return TRUE; + + if (!CHSPEC_IS40(chanspec) && !CHSPEC_IS20(chanspec)) + return TRUE; + + + if (CHSPEC_IS20(chanspec)) { + if (!CHSPEC_SB_NONE(chanspec)) + return TRUE; + } else { + if (!CHSPEC_SB_UPPER(chanspec) && !CHSPEC_SB_LOWER(chanspec)) + return TRUE; + } + + return FALSE; +} + + +uint8 +wf_chspec_ctlchan(chanspec_t chspec) +{ + uint8 ctl_chan; + + + if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_NONE) { + return CHSPEC_CHANNEL(chspec); + } else { + + ASSERT(CHSPEC_BW(chspec) == WL_CHANSPEC_BW_40); + + if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_UPPER) { + + ctl_chan = UPPER_20_SB(CHSPEC_CHANNEL(chspec)); + } else { + ASSERT(CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_LOWER); + + ctl_chan = LOWER_20_SB(CHSPEC_CHANNEL(chspec)); + } + } + + return ctl_chan; +} + +chanspec_t +wf_chspec_ctlchspec(chanspec_t chspec) +{ + chanspec_t ctl_chspec = 0; + uint8 channel; + + ASSERT(!wf_chspec_malformed(chspec)); + + + if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_NONE) { + return chspec; + } else { + if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_UPPER) { + channel = UPPER_20_SB(CHSPEC_CHANNEL(chspec)); + } else { + channel = LOWER_20_SB(CHSPEC_CHANNEL(chspec)); + } + ctl_chspec = channel | WL_CHANSPEC_BW_20 | WL_CHANSPEC_CTL_SB_NONE; + ctl_chspec |= CHSPEC_BAND(chspec); + } + return ctl_chspec; +} + +#else + + + + + + +static const char *wf_chspec_bw_str[] = +{ + "5", + "10", + "20", + "40", + "80", + "160", + "80+80", + "na" +}; + +static const uint8 wf_chspec_bw_mhz[] = +{5, 10, 20, 40, 80, 160, 160}; + +#define WF_NUM_BW \ + (sizeof(wf_chspec_bw_mhz)/sizeof(uint8)) + + +static const uint8 wf_5g_40m_chans[] = +{38, 46, 54, 62, 102, 110, 118, 126, 134, 142, 151, 159}; +#define WF_NUM_5G_40M_CHANS \ + (sizeof(wf_5g_40m_chans)/sizeof(uint8)) + + +static const uint8 wf_5g_80m_chans[] = +{42, 58, 106, 122, 138, 155}; +#define WF_NUM_5G_80M_CHANS \ + (sizeof(wf_5g_80m_chans)/sizeof(uint8)) + + +static const uint8 wf_5g_160m_chans[] = +{50, 114}; +#define WF_NUM_5G_160M_CHANS \ + (sizeof(wf_5g_160m_chans)/sizeof(uint8)) + + + +static uint +bw_chspec_to_mhz(chanspec_t chspec) +{ + uint bw; + + bw = (chspec & WL_CHANSPEC_BW_MASK) >> WL_CHANSPEC_BW_SHIFT; + return (bw >= WF_NUM_BW ? 0 : wf_chspec_bw_mhz[bw]); +} + + +static uint8 +center_chan_to_edge(uint bw) +{ + + return (uint8)(((bw - 20) / 2) / 5); +} + + +static uint8 +channel_low_edge(uint center_ch, uint bw) +{ + return (uint8)(center_ch - center_chan_to_edge(bw)); +} + + +static int +channel_to_sb(uint center_ch, uint ctl_ch, uint bw) +{ + uint lowest = channel_low_edge(center_ch, bw); + uint sb; + + if ((ctl_ch - lowest) % 4) { + + return -1; + } + + sb = ((ctl_ch - lowest) / 4); + + + if (sb >= (bw / 20)) { + + return -1; + } + + return sb; +} + + +static uint8 +channel_to_ctl_chan(uint center_ch, uint bw, uint sb) +{ + return (uint8)(channel_low_edge(center_ch, bw) + sb * 4); +} + + +static int +channel_80mhz_to_id(uint ch) +{ + uint i; + for (i = 0; i < WF_NUM_5G_80M_CHANS; i ++) { + if (ch == wf_5g_80m_chans[i]) + return i; + } + + return -1; +} + + +char * +wf_chspec_ntoa(chanspec_t chspec, char *buf) +{ + const char *band; + uint ctl_chan; + + if (wf_chspec_malformed(chspec)) + return NULL; + + band = ""; + + + if ((CHSPEC_IS2G(chspec) && CHSPEC_CHANNEL(chspec) > CH_MAX_2G_CHANNEL) || + (CHSPEC_IS5G(chspec) && CHSPEC_CHANNEL(chspec) <= CH_MAX_2G_CHANNEL)) + band = (CHSPEC_IS2G(chspec)) ? "2g" : "5g"; + + + ctl_chan = wf_chspec_ctlchan(chspec); + + + if (CHSPEC_IS20(chspec)) { + snprintf(buf, CHANSPEC_STR_LEN, "%s%d", band, ctl_chan); + } else if (!CHSPEC_IS8080(chspec)) { + const char *bw; + const char *sb = ""; + + bw = wf_chspec_bw_str[(chspec & WL_CHANSPEC_BW_MASK) >> WL_CHANSPEC_BW_SHIFT]; + +#ifdef CHANSPEC_NEW_40MHZ_FORMAT + + if (CHSPEC_IS40(chspec) && CHSPEC_IS2G(chspec)) { + sb = CHSPEC_SB_UPPER(chspec) ? "u" : "l"; + } + + snprintf(buf, CHANSPEC_STR_LEN, "%s%d/%s%s", band, ctl_chan, bw, sb); +#else + + if (CHSPEC_IS40(chspec)) { + sb = CHSPEC_SB_UPPER(chspec) ? "u" : "l"; + snprintf(buf, CHANSPEC_STR_LEN, "%s%d%s", band, ctl_chan, sb); + } else { + snprintf(buf, CHANSPEC_STR_LEN, "%s%d/%s", band, ctl_chan, bw); + } +#endif + + } else { + + uint chan1 = (chspec & WL_CHANSPEC_CHAN1_MASK) >> WL_CHANSPEC_CHAN1_SHIFT; + uint chan2 = (chspec & WL_CHANSPEC_CHAN2_MASK) >> WL_CHANSPEC_CHAN2_SHIFT; + + + chan1 = (chan1 < WF_NUM_5G_80M_CHANS) ? wf_5g_80m_chans[chan1] : 0; + chan2 = (chan2 < WF_NUM_5G_80M_CHANS) ? wf_5g_80m_chans[chan2] : 0; + + + snprintf(buf, CHANSPEC_STR_LEN, "%d/80+80/%d-%d", ctl_chan, chan1, chan2); + } + + return (buf); +} + +static int +read_uint(const char **p, unsigned int *num) +{ + unsigned long val; + char *endp = NULL; + + val = strtoul(*p, &endp, 10); + + if (endp == *p) + return 0; + + + *p = endp; + + *num = (unsigned int)val; + + return 1; +} + + +chanspec_t +wf_chspec_aton(const char *a) +{ + chanspec_t chspec; + uint chspec_ch, chspec_band, bw, chspec_bw, chspec_sb; + uint num, ctl_ch; + uint ch1, ch2; + char c, sb_ul = '\0'; + int i; + + bw = 20; + chspec_sb = 0; + chspec_ch = ch1 = ch2 = 0; + + + if (!read_uint(&a, &num)) + return 0; + + + c = tolower(a[0]); + if (c == 'g') { + a ++; + + + if (num == 2) + chspec_band = WL_CHANSPEC_BAND_2G; + else if (num == 5) + chspec_band = WL_CHANSPEC_BAND_5G; + else + return 0; + + + if (!read_uint(&a, &ctl_ch)) + return 0; + + c = tolower(a[0]); + } + else { + + ctl_ch = num; + chspec_band = ((ctl_ch <= CH_MAX_2G_CHANNEL) ? + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G); + } + + if (c == '\0') { + + chspec_bw = WL_CHANSPEC_BW_20; + goto done_read; + } + + a ++; + + + if (c == 'u' || c == 'l') { + sb_ul = c; + chspec_bw = WL_CHANSPEC_BW_40; + goto done_read; + } + + + if (c != '/') + return 0; + + + if (!read_uint(&a, &bw)) + return 0; + + + if (bw == 20) { + chspec_bw = WL_CHANSPEC_BW_20; + } else if (bw == 40) { + chspec_bw = WL_CHANSPEC_BW_40; + } else if (bw == 80) { + chspec_bw = WL_CHANSPEC_BW_80; + } else if (bw == 160) { + chspec_bw = WL_CHANSPEC_BW_160; + } else { + return 0; + } + + + + c = tolower(a[0]); + + + if (chspec_band == WL_CHANSPEC_BAND_2G && bw == 40) { + if (c == 'u' || c == 'l') { + a ++; + sb_ul = c; + goto done_read; + } + } + + + if (c == '+') { + + static const char *plus80 = "80/"; + + + chspec_bw = WL_CHANSPEC_BW_8080; + + a ++; + + + for (i = 0; i < 3; i++) { + if (*a++ != *plus80++) { + return 0; + } + } + + + if (!read_uint(&a, &ch1)) + return 0; + + + if (a[0] != '-') + return 0; + a ++; + + + if (!read_uint(&a, &ch2)) + return 0; + } + +done_read: + + while (a[0] == ' ') { + a ++; + } + + + if (a[0] != '\0') + return 0; + + + + + if (sb_ul != '\0') { + if (sb_ul == 'l') { + chspec_ch = UPPER_20_SB(ctl_ch); + chspec_sb = WL_CHANSPEC_CTL_SB_LLL; + } else if (sb_ul == 'u') { + chspec_ch = LOWER_20_SB(ctl_ch); + chspec_sb = WL_CHANSPEC_CTL_SB_LLU; + } + } + + else if (chspec_bw == WL_CHANSPEC_BW_20) { + chspec_ch = ctl_ch; + chspec_sb = 0; + } + + else if (chspec_bw != WL_CHANSPEC_BW_8080) { + + const uint8 *center_ch = NULL; + int num_ch = 0; + int sb = -1; + + if (chspec_bw == WL_CHANSPEC_BW_40) { + center_ch = wf_5g_40m_chans; + num_ch = WF_NUM_5G_40M_CHANS; + } else if (chspec_bw == WL_CHANSPEC_BW_80) { + center_ch = wf_5g_80m_chans; + num_ch = WF_NUM_5G_80M_CHANS; + } else if (chspec_bw == WL_CHANSPEC_BW_160) { + center_ch = wf_5g_160m_chans; + num_ch = WF_NUM_5G_160M_CHANS; + } else { + return 0; + } + + for (i = 0; i < num_ch; i ++) { + sb = channel_to_sb(center_ch[i], ctl_ch, bw); + if (sb >= 0) { + chspec_ch = center_ch[i]; + chspec_sb = sb << WL_CHANSPEC_CTL_SB_SHIFT; + break; + } + } + + + if (sb < 0) { + return 0; + } + } + + else { + int ch1_id = 0, ch2_id = 0; + int sb; + + ch1_id = channel_80mhz_to_id(ch1); + ch2_id = channel_80mhz_to_id(ch2); + + + if (ch1 >= ch2 || ch1_id < 0 || ch2_id < 0) + return 0; + + + chspec_ch = (((uint16)ch1_id << WL_CHANSPEC_CHAN1_SHIFT) | + ((uint16)ch2_id << WL_CHANSPEC_CHAN2_SHIFT)); + + + + + sb = channel_to_sb(ch1, ctl_ch, bw); + if (sb < 0) { + + sb = channel_to_sb(ch2, ctl_ch, bw); + if (sb < 0) { + + return 0; + } + + sb += 4; + } + + chspec_sb = sb << WL_CHANSPEC_CTL_SB_SHIFT; + } + + chspec = (chspec_ch | chspec_band | chspec_bw | chspec_sb); + + if (wf_chspec_malformed(chspec)) + return 0; + + return chspec; +} + + +bool +wf_chspec_malformed(chanspec_t chanspec) +{ + uint chspec_bw = CHSPEC_BW(chanspec); + uint chspec_ch = CHSPEC_CHANNEL(chanspec); + + + if (CHSPEC_IS2G(chanspec)) { + + if (chspec_bw != WL_CHANSPEC_BW_20 && + chspec_bw != WL_CHANSPEC_BW_40) { + return TRUE; + } + } else if (CHSPEC_IS5G(chanspec)) { + if (chspec_bw == WL_CHANSPEC_BW_8080) { + uint ch1_id, ch2_id; + + + ch1_id = CHSPEC_CHAN1(chanspec); + ch2_id = CHSPEC_CHAN2(chanspec); + if (ch1_id >= WF_NUM_5G_80M_CHANS || ch2_id >= WF_NUM_5G_80M_CHANS) + return TRUE; + + + if (ch2_id <= ch1_id) + return TRUE; + } else if (chspec_bw == WL_CHANSPEC_BW_20 || chspec_bw == WL_CHANSPEC_BW_40 || + chspec_bw == WL_CHANSPEC_BW_80 || chspec_bw == WL_CHANSPEC_BW_160) { + + if (chspec_ch > MAXCHANNEL) { + return TRUE; + } + } else { + + return TRUE; + } + } else { + + return TRUE; + } + + + if (chspec_bw == WL_CHANSPEC_BW_20) { + if (CHSPEC_CTL_SB(chanspec) != WL_CHANSPEC_CTL_SB_LLL) + return TRUE; + } else if (chspec_bw == WL_CHANSPEC_BW_40) { + if (CHSPEC_CTL_SB(chanspec) > WL_CHANSPEC_CTL_SB_LLU) + return TRUE; + } else if (chspec_bw == WL_CHANSPEC_BW_80) { + if (CHSPEC_CTL_SB(chanspec) > WL_CHANSPEC_CTL_SB_LUU) + return TRUE; + } + + return FALSE; +} + + +bool +wf_chspec_valid(chanspec_t chanspec) +{ + uint chspec_bw = CHSPEC_BW(chanspec); + uint chspec_ch = CHSPEC_CHANNEL(chanspec); + + if (wf_chspec_malformed(chanspec)) + return FALSE; + + if (CHSPEC_IS2G(chanspec)) { + + if (chspec_bw == WL_CHANSPEC_BW_20) { + if (chspec_ch >= 1 && chspec_ch <= 14) + return TRUE; + } else if (chspec_bw == WL_CHANSPEC_BW_40) { + if (chspec_ch >= 3 && chspec_ch <= 11) + return TRUE; + } + } else if (CHSPEC_IS5G(chanspec)) { + if (chspec_bw == WL_CHANSPEC_BW_8080) { + uint16 ch1, ch2; + + ch1 = wf_5g_80m_chans[CHSPEC_CHAN1(chanspec)]; + ch2 = wf_5g_80m_chans[CHSPEC_CHAN2(chanspec)]; + + + if (ch2 > ch1 + CH_80MHZ_APART) + return TRUE; + } else { + const uint8 *center_ch; + uint num_ch, i; + + if (chspec_bw == WL_CHANSPEC_BW_20 || chspec_bw == WL_CHANSPEC_BW_40) { + center_ch = wf_5g_40m_chans; + num_ch = WF_NUM_5G_40M_CHANS; + } else if (chspec_bw == WL_CHANSPEC_BW_80) { + center_ch = wf_5g_80m_chans; + num_ch = WF_NUM_5G_80M_CHANS; + } else if (chspec_bw == WL_CHANSPEC_BW_160) { + center_ch = wf_5g_160m_chans; + num_ch = WF_NUM_5G_160M_CHANS; + } else { + + return FALSE; + } + + + if (chspec_bw == WL_CHANSPEC_BW_20) { + + for (i = 0; i < num_ch; i ++) { + if (chspec_ch == (uint)LOWER_20_SB(center_ch[i]) || + chspec_ch == (uint)UPPER_20_SB(center_ch[i])) + break; + } + + if (i == num_ch) { + + if (chspec_ch == 34 || chspec_ch == 38 || + chspec_ch == 42 || chspec_ch == 46) + i = 0; + } + } else { + + for (i = 0; i < num_ch; i ++) { + if (chspec_ch == center_ch[i]) + break; + } + } + + if (i < num_ch) { + + return TRUE; + } + } + } + + return FALSE; +} + + +uint8 +wf_chspec_ctlchan(chanspec_t chspec) +{ + uint center_chan; + uint bw_mhz; + uint sb; + + ASSERT(!wf_chspec_malformed(chspec)); + + + if (CHSPEC_IS20(chspec)) { + return CHSPEC_CHANNEL(chspec); + } else { + sb = CHSPEC_CTL_SB(chspec) >> WL_CHANSPEC_CTL_SB_SHIFT; + + if (CHSPEC_IS8080(chspec)) { + bw_mhz = 80; + + if (sb < 4) { + center_chan = CHSPEC_CHAN1(chspec); + } + else { + center_chan = CHSPEC_CHAN2(chspec); + sb -= 4; + } + + + center_chan = wf_5g_80m_chans[center_chan]; + } + else { + bw_mhz = bw_chspec_to_mhz(chspec); + center_chan = CHSPEC_CHANNEL(chspec) >> WL_CHANSPEC_CHAN_SHIFT; + } + + return (channel_to_ctl_chan(center_chan, bw_mhz, sb)); + } +} + + +chanspec_t +wf_chspec_ctlchspec(chanspec_t chspec) +{ + chanspec_t ctl_chspec = chspec; + uint8 ctl_chan; + + ASSERT(!wf_chspec_malformed(chspec)); + + + if (!CHSPEC_IS20(chspec)) { + ctl_chan = wf_chspec_ctlchan(chspec); + ctl_chspec = ctl_chan | WL_CHANSPEC_BW_20; + ctl_chspec |= CHSPEC_BAND(chspec); + } + return ctl_chspec; +} + +#endif + + +extern chanspec_t wf_chspec_primary40_chspec(chanspec_t chspec) +{ + chanspec_t chspec40 = chspec; + uint center_chan; + uint sb; + + ASSERT(!wf_chspec_malformed(chspec)); + + if (CHSPEC_IS80(chspec)) { + center_chan = CHSPEC_CHANNEL(chspec); + sb = CHSPEC_CTL_SB(chspec); + + if (sb == WL_CHANSPEC_CTL_SB_UL) { + + sb = WL_CHANSPEC_CTL_SB_L; + center_chan += CH_20MHZ_APART; + } else if (sb == WL_CHANSPEC_CTL_SB_UU) { + + sb = WL_CHANSPEC_CTL_SB_U; + center_chan += CH_20MHZ_APART; + } else { + + + center_chan -= CH_20MHZ_APART; + } + + + chspec40 = (WL_CHANSPEC_BAND_5G | WL_CHANSPEC_BW_40 | + sb | center_chan); + } + + return chspec40; +} + + +int +wf_mhz2channel(uint freq, uint start_factor) +{ + int ch = -1; + uint base; + int offset; + + + if (start_factor == 0) { + if (freq >= 2400 && freq <= 2500) + start_factor = WF_CHAN_FACTOR_2_4_G; + else if (freq >= 5000 && freq <= 6000) + start_factor = WF_CHAN_FACTOR_5_G; + } + + if (freq == 2484 && start_factor == WF_CHAN_FACTOR_2_4_G) + return 14; + + base = start_factor / 2; + + + if ((freq < base) || (freq > base + 1000)) + return -1; + + offset = freq - base; + ch = offset / 5; + + + if (offset != (ch * 5)) + return -1; + + + if (start_factor == WF_CHAN_FACTOR_2_4_G && (ch < 1 || ch > 13)) + return -1; + + return ch; +} + + +int +wf_channel2mhz(uint ch, uint start_factor) +{ + int freq; + + if ((start_factor == WF_CHAN_FACTOR_2_4_G && (ch < 1 || ch > 14)) || + (ch > 200)) + freq = -1; + else if ((start_factor == WF_CHAN_FACTOR_2_4_G) && (ch == 14)) + freq = 2484; + else + freq = ch * 5 + start_factor / 2; + + return freq; +} diff --git a/drivers/net/wireless/bcmdhd/include/bcmwifi.h b/drivers/net/wireless/bcmdhd/include/bcmwifi.h new file mode 100644 index 0000000000000000000000000000000000000000..f3f593f49cc13906543959b8d1254774875068db --- /dev/null +++ b/drivers/net/wireless/bcmdhd/include/bcmwifi.h @@ -0,0 +1,345 @@ +/* + * Misc utility routines for WL and Apps + * This header file housing the define and function prototype use by + * both the wl driver, tools & Apps. + * + * Copyright (C) 1999-2012, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: bcmwifi.h 309193 2012-01-19 00:03:57Z $ + */ + +#ifndef _bcmwifi_h_ +#define _bcmwifi_h_ + + + +typedef uint16 chanspec_t; + + +#define CH_UPPER_SB 0x01 +#define CH_LOWER_SB 0x02 +#define CH_EWA_VALID 0x04 +#define CH_80MHZ_APART 16 +#define CH_40MHZ_APART 8 +#define CH_20MHZ_APART 4 +#define CH_10MHZ_APART 2 +#define CH_5MHZ_APART 1 +#define CH_MAX_2G_CHANNEL 14 +#define MAXCHANNEL 224 +#define CHSPEC_CTLOVLP(sp1, sp2, sep) ABS(wf_chspec_ctlchan(sp1) - wf_chspec_ctlchan(sp2)) < (sep) + + +#undef D11AC_IOTYPES +#define D11AC_IOTYPES + +#ifndef D11AC_IOTYPES + +#define WL_CHANSPEC_CHAN_MASK 0x00ff +#define WL_CHANSPEC_CHAN_SHIFT 0 + +#define WL_CHANSPEC_CTL_SB_MASK 0x0300 +#define WL_CHANSPEC_CTL_SB_SHIFT 8 +#define WL_CHANSPEC_CTL_SB_LOWER 0x0100 +#define WL_CHANSPEC_CTL_SB_UPPER 0x0200 +#define WL_CHANSPEC_CTL_SB_NONE 0x0300 + +#define WL_CHANSPEC_BW_MASK 0x0C00 +#define WL_CHANSPEC_BW_SHIFT 10 +#define WL_CHANSPEC_BW_10 0x0400 +#define WL_CHANSPEC_BW_20 0x0800 +#define WL_CHANSPEC_BW_40 0x0C00 + +#define WL_CHANSPEC_BAND_MASK 0xf000 +#define WL_CHANSPEC_BAND_SHIFT 12 +#define WL_CHANSPEC_BAND_5G 0x1000 +#define WL_CHANSPEC_BAND_2G 0x2000 +#define INVCHANSPEC 255 + + +#define LOWER_20_SB(channel) (((channel) > CH_10MHZ_APART) ? ((channel) - CH_10MHZ_APART) : 0) +#define UPPER_20_SB(channel) (((channel) < (MAXCHANNEL - CH_10MHZ_APART)) ? \ + ((channel) + CH_10MHZ_APART) : 0) +#define CHSPEC_WLCBANDUNIT(chspec) (CHSPEC_IS5G(chspec) ? BAND_5G_INDEX : BAND_2G_INDEX) +#define CH20MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_20 | \ + WL_CHANSPEC_CTL_SB_NONE | (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define NEXT_20MHZ_CHAN(channel) (((channel) < (MAXCHANNEL - CH_20MHZ_APART)) ? \ + ((channel) + CH_20MHZ_APART) : 0) +#define CH40MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | WL_CHANSPEC_BW_40 | \ + ((channel) <= CH_MAX_2G_CHANNEL ? WL_CHANSPEC_BAND_2G : \ + WL_CHANSPEC_BAND_5G)) +#define CHSPEC_CHANNEL(chspec) ((uint8)((chspec) & WL_CHANSPEC_CHAN_MASK)) +#define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK) + + +#define CHSPEC_CTL_SB(chspec) ((chspec) & WL_CHANSPEC_CTL_SB_MASK) +#define CHSPEC_BW(chspec) ((chspec) & WL_CHANSPEC_BW_MASK) + +#ifdef WL11N_20MHZONLY + +#define CHSPEC_IS10(chspec) 0 +#define CHSPEC_IS20(chspec) 1 +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) 0 +#endif + +#else + +#define CHSPEC_IS10(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_10) +#define CHSPEC_IS20(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_20) +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40) +#endif + +#endif + +#define CHSPEC_IS5G(chspec) (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G) +#define CHSPEC_IS2G(chspec) (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_2G) +#define CHSPEC_SB_NONE(chspec) (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_NONE) +#define CHSPEC_SB_UPPER(chspec) (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_UPPER) +#define CHSPEC_SB_LOWER(chspec) (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_LOWER) +#define CHSPEC_CTL_CHAN(chspec) ((CHSPEC_SB_LOWER(chspec)) ? \ + (LOWER_20_SB(((chspec) & WL_CHANSPEC_CHAN_MASK))) : \ + (UPPER_20_SB(((chspec) & WL_CHANSPEC_CHAN_MASK)))) +#define CHSPEC2WLC_BAND(chspec) (CHSPEC_IS5G(chspec) ? WLC_BAND_5G : WLC_BAND_2G) + +#define CHANSPEC_STR_LEN 8 + +#else + +#define WL_CHANSPEC_CHAN_MASK 0x00ff +#define WL_CHANSPEC_CHAN_SHIFT 0 +#define WL_CHANSPEC_CHAN1_MASK 0x000f +#define WL_CHANSPEC_CHAN1_SHIFT 0 +#define WL_CHANSPEC_CHAN2_MASK 0x00f0 +#define WL_CHANSPEC_CHAN2_SHIFT 4 + +#define WL_CHANSPEC_CTL_SB_MASK 0x0700 +#define WL_CHANSPEC_CTL_SB_SHIFT 8 +#define WL_CHANSPEC_CTL_SB_LLL 0x0000 +#define WL_CHANSPEC_CTL_SB_LLU 0x0100 +#define WL_CHANSPEC_CTL_SB_LUL 0x0200 +#define WL_CHANSPEC_CTL_SB_LUU 0x0300 +#define WL_CHANSPEC_CTL_SB_ULL 0x0400 +#define WL_CHANSPEC_CTL_SB_ULU 0x0500 +#define WL_CHANSPEC_CTL_SB_UUL 0x0600 +#define WL_CHANSPEC_CTL_SB_UUU 0x0700 +#define WL_CHANSPEC_CTL_SB_LL WL_CHANSPEC_CTL_SB_LLL +#define WL_CHANSPEC_CTL_SB_LU WL_CHANSPEC_CTL_SB_LLU +#define WL_CHANSPEC_CTL_SB_UL WL_CHANSPEC_CTL_SB_LUL +#define WL_CHANSPEC_CTL_SB_UU WL_CHANSPEC_CTL_SB_LUU +#define WL_CHANSPEC_CTL_SB_L WL_CHANSPEC_CTL_SB_LLL +#define WL_CHANSPEC_CTL_SB_U WL_CHANSPEC_CTL_SB_LLU +#define WL_CHANSPEC_CTL_SB_LOWER WL_CHANSPEC_CTL_SB_LLL +#define WL_CHANSPEC_CTL_SB_UPPER WL_CHANSPEC_CTL_SB_LLU + +#define WL_CHANSPEC_BW_MASK 0x3800 +#define WL_CHANSPEC_BW_SHIFT 11 +#define WL_CHANSPEC_BW_5 0x0000 +#define WL_CHANSPEC_BW_10 0x0800 +#define WL_CHANSPEC_BW_20 0x1000 +#define WL_CHANSPEC_BW_40 0x1800 +#define WL_CHANSPEC_BW_80 0x2000 +#define WL_CHANSPEC_BW_160 0x2800 +#define WL_CHANSPEC_BW_8080 0x3000 + +#define WL_CHANSPEC_BAND_MASK 0xc000 +#define WL_CHANSPEC_BAND_SHIFT 14 +#define WL_CHANSPEC_BAND_2G 0x0000 +#define WL_CHANSPEC_BAND_3G 0x4000 +#define WL_CHANSPEC_BAND_4G 0x8000 +#define WL_CHANSPEC_BAND_5G 0xc000 +#define INVCHANSPEC 255 + + +#define LOWER_20_SB(channel) (((channel) > CH_10MHZ_APART) ? \ + ((channel) - CH_10MHZ_APART) : 0) +#define UPPER_20_SB(channel) (((channel) < (MAXCHANNEL - CH_10MHZ_APART)) ? \ + ((channel) + CH_10MHZ_APART) : 0) +#define LOWER_40_SB(channel) ((channel) - CH_20MHZ_APART) +#define UPPER_40_SB(channel) ((channel) + CH_20MHZ_APART) +#define CHSPEC_WLCBANDUNIT(chspec) (CHSPEC_IS5G(chspec) ? BAND_5G_INDEX : BAND_2G_INDEX) +#define CH20MHZ_CHSPEC(channel) (chanspec_t)((chanspec_t)(channel) | WL_CHANSPEC_BW_20 | \ + (((channel) <= CH_MAX_2G_CHANNEL) ? \ + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G)) +#define NEXT_20MHZ_CHAN(channel) (((channel) < (MAXCHANNEL - CH_20MHZ_APART)) ? \ + ((channel) + CH_20MHZ_APART) : 0) +#define CH40MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | WL_CHANSPEC_BW_40 | \ + ((channel) <= CH_MAX_2G_CHANNEL ? WL_CHANSPEC_BAND_2G : \ + WL_CHANSPEC_BAND_5G)) +#define CH80MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | \ + WL_CHANSPEC_BW_80 | WL_CHANSPEC_BAND_5G) +#define CH160MHZ_CHSPEC(channel, ctlsb) (chanspec_t) \ + ((channel) | (ctlsb) | \ + WL_CHANSPEC_BW_160 | WL_CHANSPEC_BAND_5G) + + +#define CHSPEC_CHANNEL(chspec) ((uint8)((chspec) & WL_CHANSPEC_CHAN_MASK)) +#define CHSPEC_CHAN1(chspec) ((chspec) & WL_CHANSPEC_CHAN1_MASK) +#define CHSPEC_CHAN2(chspec) ((chspec) & WL_CHANSPEC_CHAN2_MASK) +#define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK) +#define CHSPEC_CTL_SB(chspec) ((chspec) & WL_CHANSPEC_CTL_SB_MASK) +#define CHSPEC_BW(chspec) ((chspec) & WL_CHANSPEC_BW_MASK) + +#ifdef WL11N_20MHZONLY + +#define CHSPEC_IS10(chspec) 0 +#define CHSPEC_IS20(chspec) 1 +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) 0 +#endif +#ifndef CHSPEC_IS80 +#define CHSPEC_IS80(chspec) 0 +#endif +#ifndef CHSPEC_IS160 +#define CHSPEC_IS160(chspec) 0 +#endif +#ifndef CHSPEC_IS8080 +#define CHSPEC_IS8080(chspec) 0 +#endif + +#else + +#define CHSPEC_IS10(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_10) +#define CHSPEC_IS20(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_20) +#ifndef CHSPEC_IS40 +#define CHSPEC_IS40(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40) +#endif +#ifndef CHSPEC_IS80 +#define CHSPEC_IS80(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_80) +#endif +#ifndef CHSPEC_IS160 +#define CHSPEC_IS160(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_160) +#endif +#ifndef CHSPEC_IS8080 +#define CHSPEC_IS8080(chspec) (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_8080) +#endif + +#endif + +#define CHSPEC_IS5G(chspec) (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G) +#define CHSPEC_IS2G(chspec) (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_2G) +#define CHSPEC_SB_UPPER(chspec) \ + ((((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_UPPER) && \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40)) +#define CHSPEC_SB_LOWER(chspec) \ + ((((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_LOWER) && \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40)) +#define CHSPEC2WLC_BAND(chspec) (CHSPEC_IS5G(chspec) ? WLC_BAND_5G : WLC_BAND_2G) + + +#define CHANSPEC_STR_LEN 20 + + + +#define WL_LCHANSPEC_CHAN_MASK 0x00ff +#define WL_LCHANSPEC_CHAN_SHIFT 0 + +#define WL_LCHANSPEC_CTL_SB_MASK 0x0300 +#define WL_LCHANSPEC_CTL_SB_SHIFT 8 +#define WL_LCHANSPEC_CTL_SB_LOWER 0x0100 +#define WL_LCHANSPEC_CTL_SB_UPPER 0x0200 +#define WL_LCHANSPEC_CTL_SB_NONE 0x0300 + +#define WL_LCHANSPEC_BW_MASK 0x0C00 +#define WL_LCHANSPEC_BW_SHIFT 10 +#define WL_LCHANSPEC_BW_10 0x0400 +#define WL_LCHANSPEC_BW_20 0x0800 +#define WL_LCHANSPEC_BW_40 0x0C00 + +#define WL_LCHANSPEC_BAND_MASK 0xf000 +#define WL_LCHANSPEC_BAND_SHIFT 12 +#define WL_LCHANSPEC_BAND_5G 0x1000 +#define WL_LCHANSPEC_BAND_2G 0x2000 + +#define LCHSPEC_CHANNEL(chspec) ((uint8)((chspec) & WL_LCHANSPEC_CHAN_MASK)) +#define LCHSPEC_BAND(chspec) ((chspec) & WL_LCHANSPEC_BAND_MASK) +#define LCHSPEC_CTL_SB(chspec) ((chspec) & WL_LCHANSPEC_CTL_SB_MASK) +#define LCHSPEC_BW(chspec) ((chspec) & WL_LCHANSPEC_BW_MASK) +#define LCHSPEC_IS10(chspec) (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_10) +#define LCHSPEC_IS20(chspec) (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_20) +#define LCHSPEC_IS40(chspec) (((chspec) & WL_LCHANSPEC_BW_MASK) == WL_LCHANSPEC_BW_40) +#define LCHSPEC_IS5G(chspec) (((chspec) & WL_LCHANSPEC_BAND_MASK) == WL_LCHANSPEC_BAND_5G) +#define LCHSPEC_IS2G(chspec) (((chspec) & WL_LCHANSPEC_BAND_MASK) == WL_LCHANSPEC_BAND_2G) + +#define LCHSPEC_CREATE(chan, band, bw, sb) ((uint16)((chan) | (sb) | (bw) | (band))) + +#endif + + + + +#define WF_CHAN_FACTOR_2_4_G 4814 + + +#define WF_CHAN_FACTOR_5_G 10000 + + +#define WF_CHAN_FACTOR_4_G 8000 + + +#define WLC_MAXRATE 108 +#define WLC_RATE_1M 2 +#define WLC_RATE_2M 4 +#define WLC_RATE_5M5 11 +#define WLC_RATE_11M 22 +#define WLC_RATE_6M 12 +#define WLC_RATE_9M 18 +#define WLC_RATE_12M 24 +#define WLC_RATE_18M 36 +#define WLC_RATE_24M 48 +#define WLC_RATE_36M 72 +#define WLC_RATE_48M 96 +#define WLC_RATE_54M 108 + +#define WLC_2G_25MHZ_OFFSET 5 + + +extern char * wf_chspec_ntoa(chanspec_t chspec, char *buf); + + +extern chanspec_t wf_chspec_aton(const char *a); + + +extern bool wf_chspec_malformed(chanspec_t chanspec); + + +extern bool wf_chspec_valid(chanspec_t chanspec); + + +extern uint8 wf_chspec_ctlchan(chanspec_t chspec); + + +extern chanspec_t wf_chspec_ctlchspec(chanspec_t chspec); + + +extern chanspec_t wf_chspec_primary40_chspec(chanspec_t chspec); + + +extern int wf_mhz2channel(uint freq, uint start_factor); + + +extern int wf_channel2mhz(uint channel, uint start_factor); + +#endif diff --git a/drivers/net/wireless/bcmdhd/include/htsf.h b/drivers/net/wireless/bcmdhd/include/htsf.h new file mode 100644 index 0000000000000000000000000000000000000000..d875edb816c9c24e0997618b8a8f886b123566a1 --- /dev/null +++ b/drivers/net/wireless/bcmdhd/include/htsf.h @@ -0,0 +1,74 @@ +/* + * Time stamps for latency measurements + * + * Copyright (C) 1999-2011, Broadcom Corporation + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2 (the "GPL"), + * available at http://www.broadcom.com/licenses/GPLv2.php, with the + * following added to such license: + * + * As a special exception, the copyright holders of this software give you + * permission to link this software with independent modules, and to copy and + * distribute the resulting executable under terms of your choice, provided that + * you also meet, for each linked independent module, the terms and conditions of + * the license of that module. An independent module is a module which is not + * derived from this software. The special exception does not apply to any + * modifications of the software. + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a license + * other than the GPL, without Broadcom's express prior written consent. + * + * $Id: htsf.h 277737 2011-08-16 17:54:59Z $ + */ +#ifndef _HTSF_H_ +#define _HTSF_H_ + +#define HTSFMAGIC 0xCDCDABAB /* in network order for tcpdump */ +#define HTSFENDMAGIC 0xEFEFABAB /* to distinguish from RT2 magic */ +#define HTSF_HOSTOFFSET 102 +#define HTSF_DNGLOFFSET HTSF_HOSTOFFSET - 4 +#define HTSF_DNGLOFFSET2 HTSF_HOSTOFFSET + 106 +#define HTSF_MIN_PKTLEN 200 +#define ETHER_TYPE_BRCM_PKTDLYSTATS 0x886d + +typedef enum htsfts_type { + T10, + T20, + T30, + T40, + T50, + T60, + T70, + T80, + T90, + TA0, + TE0 +} htsf_timestamp_t; + +typedef struct { + uint32 magic; + uint32 prio; + uint32 seqnum; + uint32 misc; + uint32 c10; + uint32 t10; + uint32 c20; + uint32 t20; + uint32 t30; + uint32 t40; + uint32 t50; + uint32 t60; + uint32 t70; + uint32 t80; + uint32 t90; + uint32 cA0; + uint32 tA0; + uint32 cE0; + uint32 tE0; + uint32 endmagic; +} htsfts_t; + +#endif /* _HTSF_H_ */ diff --git a/drivers/net/wireless/bcmdhd/include/wlc_clm_rates.h b/drivers/net/wireless/bcmdhd/include/wlc_clm_rates.h new file mode 100644 index 0000000000000000000000000000000000000000..d9061bb8c5d705576f81c6222448a489cbbae510 --- /dev/null +++ b/drivers/net/wireless/bcmdhd/include/wlc_clm_rates.h @@ -0,0 +1,255 @@ +/* + * Indices for 802.11 a/b/g/n/ac 1-3 chain symmetric transmit rates + * Copyright (C) 2012, 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: wlc_clm_rates.h 252708 2011-04-12 06:45:56Z $ + */ + +#ifndef _WLC_CLM_RATES_H_ +#define _WLC_CLM_RATES_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum clm_rates { + /************ + * 1 chain * + ************ + */ + + /* 1 Stream */ + CLM_RATE_1X1_DSSS_1 = 0, + CLM_RATE_1X1_DSSS_2 = 1, + CLM_RATE_1X1_DSSS_5_5 = 2, + CLM_RATE_1X1_DSSS_11 = 3, + + CLM_RATE_1X1_OFDM_6 = 4, + CLM_RATE_1X1_OFDM_9 = 5, + CLM_RATE_1X1_OFDM_12 = 6, + CLM_RATE_1X1_OFDM_18 = 7, + CLM_RATE_1X1_OFDM_24 = 8, + CLM_RATE_1X1_OFDM_36 = 9, + CLM_RATE_1X1_OFDM_48 = 10, + CLM_RATE_1X1_OFDM_54 = 11, + + CLM_RATE_1X1_MCS0 = 12, + CLM_RATE_1X1_MCS1 = 13, + CLM_RATE_1X1_MCS2 = 14, + CLM_RATE_1X1_MCS3 = 15, + CLM_RATE_1X1_MCS4 = 16, + CLM_RATE_1X1_MCS5 = 17, + CLM_RATE_1X1_MCS6 = 18, + CLM_RATE_1X1_MCS7 = 19, + + CLM_RATE_1X1_VHT0SS1 = 12, + CLM_RATE_1X1_VHT1SS1 = 13, + CLM_RATE_1X1_VHT2SS1 = 14, + CLM_RATE_1X1_VHT3SS1 = 15, + CLM_RATE_1X1_VHT4SS1 = 16, + CLM_RATE_1X1_VHT5SS1 = 17, + CLM_RATE_1X1_VHT6SS1 = 18, + CLM_RATE_1X1_VHT7SS1 = 19, + CLM_RATE_1X1_VHT8SS1 = 20, + CLM_RATE_1X1_VHT9SS1 = 21, + + + /************ + * 2 chains * + ************ + */ + + /* 1 Stream expanded + 1 */ + CLM_RATE_1X2_DSSS_1 = 22, + CLM_RATE_1X2_DSSS_2 = 23, + CLM_RATE_1X2_DSSS_5_5 = 24, + CLM_RATE_1X2_DSSS_11 = 25, + + CLM_RATE_1X2_CDD_OFDM_6 = 26, + CLM_RATE_1X2_CDD_OFDM_9 = 27, + CLM_RATE_1X2_CDD_OFDM_12 = 28, + CLM_RATE_1X2_CDD_OFDM_18 = 29, + CLM_RATE_1X2_CDD_OFDM_24 = 30, + CLM_RATE_1X2_CDD_OFDM_36 = 31, + CLM_RATE_1X2_CDD_OFDM_48 = 32, + CLM_RATE_1X2_CDD_OFDM_54 = 33, + + CLM_RATE_1X2_CDD_MCS0 = 34, + CLM_RATE_1X2_CDD_MCS1 = 35, + CLM_RATE_1X2_CDD_MCS2 = 36, + CLM_RATE_1X2_CDD_MCS3 = 37, + CLM_RATE_1X2_CDD_MCS4 = 38, + CLM_RATE_1X2_CDD_MCS5 = 39, + CLM_RATE_1X2_CDD_MCS6 = 40, + CLM_RATE_1X2_CDD_MCS7 = 41, + + CLM_RATE_1X2_VHT0SS1 = 34, + CLM_RATE_1X2_VHT1SS1 = 35, + CLM_RATE_1X2_VHT2SS1 = 36, + CLM_RATE_1X2_VHT3SS1 = 37, + CLM_RATE_1X2_VHT4SS1 = 38, + CLM_RATE_1X2_VHT5SS1 = 39, + CLM_RATE_1X2_VHT6SS1 = 40, + CLM_RATE_1X2_VHT7SS1 = 41, + CLM_RATE_1X2_VHT8SS1 = 42, + CLM_RATE_1X2_VHT9SS1 = 43, + + /* 2 Streams */ + CLM_RATE_2X2_STBC_MCS0 = 44, + CLM_RATE_2X2_STBC_MCS1 = 45, + CLM_RATE_2X2_STBC_MCS2 = 46, + CLM_RATE_2X2_STBC_MCS3 = 47, + CLM_RATE_2X2_STBC_MCS4 = 48, + CLM_RATE_2X2_STBC_MCS5 = 49, + CLM_RATE_2X2_STBC_MCS6 = 50, + CLM_RATE_2X2_STBC_MCS7 = 51, + + CLM_RATE_2X2_STBC_VHT0SS1 = 44, + CLM_RATE_2X2_STBC_VHT1SS1 = 45, + CLM_RATE_2X2_STBC_VHT2SS1 = 46, + CLM_RATE_2X2_STBC_VHT3SS1 = 47, + CLM_RATE_2X2_STBC_VHT4SS1 = 48, + CLM_RATE_2X2_STBC_VHT5SS1 = 49, + CLM_RATE_2X2_STBC_VHT6SS1 = 50, + CLM_RATE_2X2_STBC_VHT7SS1 = 51, + CLM_RATE_2X2_STBC_VHT8SS1 = 52, + CLM_RATE_2X2_STBC_VHT9SS1 = 53, + + CLM_RATE_2X2_SDM_MCS8 = 54, + CLM_RATE_2X2_SDM_MCS9 = 55, + CLM_RATE_2X2_SDM_MCS10 = 56, + CLM_RATE_2X2_SDM_MCS11 = 57, + CLM_RATE_2X2_SDM_MCS12 = 58, + CLM_RATE_2X2_SDM_MCS13 = 59, + CLM_RATE_2X2_SDM_MCS14 = 60, + CLM_RATE_2X2_SDM_MCS15 = 61, + + CLM_RATE_2X2_VHT0SS2 = 54, + CLM_RATE_2X2_VHT1SS2 = 55, + CLM_RATE_2X2_VHT2SS2 = 56, + CLM_RATE_2X2_VHT3SS2 = 57, + CLM_RATE_2X2_VHT4SS2 = 58, + CLM_RATE_2X2_VHT5SS2 = 59, + CLM_RATE_2X2_VHT6SS2 = 60, + CLM_RATE_2X2_VHT7SS2 = 61, + CLM_RATE_2X2_VHT8SS2 = 62, + CLM_RATE_2X2_VHT9SS2 = 63, + + + /************ + * 3 chains * + ************ + */ + + /* 1 Stream expanded + 2 */ + CLM_RATE_1X3_DSSS_1 = 64, + CLM_RATE_1X3_DSSS_2 = 65, + CLM_RATE_1X3_DSSS_5_5 = 66, + CLM_RATE_1X3_DSSS_11 = 67, + + CLM_RATE_1X3_CDD_OFDM_6 = 68, + CLM_RATE_1X3_CDD_OFDM_9 = 69, + CLM_RATE_1X3_CDD_OFDM_12 = 70, + CLM_RATE_1X3_CDD_OFDM_18 = 71, + CLM_RATE_1X3_CDD_OFDM_24 = 72, + CLM_RATE_1X3_CDD_OFDM_36 = 73, + CLM_RATE_1X3_CDD_OFDM_48 = 74, + CLM_RATE_1X3_CDD_OFDM_54 = 75, + + CLM_RATE_1X3_CDD_MCS0 = 76, + CLM_RATE_1X3_CDD_MCS1 = 77, + CLM_RATE_1X3_CDD_MCS2 = 78, + CLM_RATE_1X3_CDD_MCS3 = 79, + CLM_RATE_1X3_CDD_MCS4 = 80, + CLM_RATE_1X3_CDD_MCS5 = 81, + CLM_RATE_1X3_CDD_MCS6 = 82, + CLM_RATE_1X3_CDD_MCS7 = 83, + + CLM_RATE_1X3_VHT0SS1 = 76, + CLM_RATE_1X3_VHT1SS1 = 77, + CLM_RATE_1X3_VHT2SS1 = 78, + CLM_RATE_1X3_VHT3SS1 = 79, + CLM_RATE_1X3_VHT4SS1 = 80, + CLM_RATE_1X3_VHT5SS1 = 81, + CLM_RATE_1X3_VHT6SS1 = 82, + CLM_RATE_1X3_VHT7SS1 = 83, + CLM_RATE_1X3_VHT8SS1 = 84, + CLM_RATE_1X3_VHT9SS1 = 85, + + /* 2 Streams expanded + 1 */ + CLM_RATE_2X3_STBC_MCS0 = 86, + CLM_RATE_2X3_STBC_MCS1 = 87, + CLM_RATE_2X3_STBC_MCS2 = 88, + CLM_RATE_2X3_STBC_MCS3 = 89, + CLM_RATE_2X3_STBC_MCS4 = 90, + CLM_RATE_2X3_STBC_MCS5 = 91, + CLM_RATE_2X3_STBC_MCS6 = 92, + CLM_RATE_2X3_STBC_MCS7 = 93, + + CLM_RATE_2X3_STBC_VHT0SS1 = 86, + CLM_RATE_2X3_STBC_VHT1SS1 = 87, + CLM_RATE_2X3_STBC_VHT2SS1 = 88, + CLM_RATE_2X3_STBC_VHT3SS1 = 89, + CLM_RATE_2X3_STBC_VHT4SS1 = 90, + CLM_RATE_2X3_STBC_VHT5SS1 = 91, + CLM_RATE_2X3_STBC_VHT6SS1 = 92, + CLM_RATE_2X3_STBC_VHT7SS1 = 93, + CLM_RATE_2X3_STBC_VHT8SS1 = 94, + CLM_RATE_2X3_STBC_VHT9SS1 = 95, + + CLM_RATE_2X3_SDM_MCS8 = 96, + CLM_RATE_2X3_SDM_MCS9 = 97, + CLM_RATE_2X3_SDM_MCS10 = 98, + CLM_RATE_2X3_SDM_MCS11 = 99, + CLM_RATE_2X3_SDM_MCS12 = 100, + CLM_RATE_2X3_SDM_MCS13 = 101, + CLM_RATE_2X3_SDM_MCS14 = 102, + CLM_RATE_2X3_SDM_MCS15 = 103, + + CLM_RATE_2X3_VHT0SS2 = 96, + CLM_RATE_2X3_VHT1SS2 = 97, + CLM_RATE_2X3_VHT2SS2 = 98, + CLM_RATE_2X3_VHT3SS2 = 99, + CLM_RATE_2X3_VHT4SS2 = 100, + CLM_RATE_2X3_VHT5SS2 = 101, + CLM_RATE_2X3_VHT6SS2 = 102, + CLM_RATE_2X3_VHT7SS2 = 103, + CLM_RATE_2X3_VHT8SS2 = 104, + CLM_RATE_2X3_VHT9SS2 = 105, + + /* 3 Streams */ + CLM_RATE_3X3_SDM_MCS16 = 106, + CLM_RATE_3X3_SDM_MCS17 = 107, + CLM_RATE_3X3_SDM_MCS18 = 108, + CLM_RATE_3X3_SDM_MCS19 = 109, + CLM_RATE_3X3_SDM_MCS20 = 110, + CLM_RATE_3X3_SDM_MCS21 = 111, + CLM_RATE_3X3_SDM_MCS22 = 112, + CLM_RATE_3X3_SDM_MCS23 = 113, + + CLM_RATE_3X3_VHT0SS3 = 106, + CLM_RATE_3X3_VHT1SS3 = 107, + CLM_RATE_3X3_VHT2SS3 = 108, + CLM_RATE_3X3_VHT3SS3 = 109, + CLM_RATE_3X3_VHT4SS3 = 110, + CLM_RATE_3X3_VHT5SS3 = 111, + CLM_RATE_3X3_VHT6SS3 = 112, + CLM_RATE_3X3_VHT7SS3 = 113, + CLM_RATE_3X3_VHT8SS3 = 114, + CLM_RATE_3X3_VHT9SS3 = 115, + + /* Number of rate codes */ + CLM_NUMRATES = 116, + } clm_rates_t; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _WLC_CLM_RATES_H_ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-rxon.c b/drivers/net/wireless/iwlwifi/iwl-agn-rxon.c index 2e1a31797a9e59c16af11c7630e2b576f236b11e..826dd3e2d640cea458e8df961a834cbd689a2eac 100644 --- a/drivers/net/wireless/iwlwifi/iwl-agn-rxon.c +++ b/drivers/net/wireless/iwlwifi/iwl-agn-rxon.c @@ -551,6 +551,9 @@ int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed) mutex_lock(&priv->mutex); + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + goto out; + if (unlikely(test_bit(STATUS_SCANNING, &priv->status))) { IWL_DEBUG_MAC80211(priv, "leave - scanning\n"); goto out; diff --git a/drivers/net/wireless/libra/Makefile b/drivers/net/wireless/libra/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..849de059a1e50b1b5e3b02347df4742476e22df1 --- /dev/null +++ b/drivers/net/wireless/libra/Makefile @@ -0,0 +1,14 @@ + +# Makefile for wlan sdio if driver + +librasdioif-objs += libra_sdioif.o ../wcnss/qcomwlan_secif.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 0000000000000000000000000000000000000000..aa7970ae2de57443c8343548195aba49cfc70012 --- /dev/null +++ b/drivers/net/wireless/libra/libra_sdioif.c @@ -0,0 +1,547 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#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; +static notify_card_removal_t *libra_notify_card_removal_hdlr; +static shutdown_handler_t *libra_sdio_shutdown_hdlr; + +int libra_enable_sdio_irq_in_chip(struct sdio_func *func, u8 enable) +{ + unsigned char reg = 0; + int err = 0; + + sdio_claim_host(func); + + /* Read the value into reg */ + libra_sdiocmd52(func, SDIO_CCCR_IENx, ®, 0, &err); + if (err) + printk(KERN_ERR "%s: Could not read SDIO_CCCR_IENx register " + "err=%d\n", __func__, err); + + if (libra_mmc_host) { + if (enable) { + reg |= 1 << func->num; + reg |= 1; + } else { + reg &= ~(1 << func->num); + } + libra_sdiocmd52(func, SDIO_CCCR_IENx, ®, 1, &err); + if (err) + printk(KERN_ERR "%s: Could not enable/disable irq " + "err=%d\n", __func__, err); + } + sdio_release_host(func); + + return err; +} +EXPORT_SYMBOL(libra_enable_sdio_irq_in_chip); + +/** + * 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; + } + + libra_enable_sdio_irq_in_chip(func, 0); + + 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) +{ + if (libra_notify_card_removal_hdlr) + libra_notify_card_removal_hdlr(); + 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 void libra_sdio_shutdown(struct device *dev) +{ + if (libra_sdio_shutdown_hdlr) { + libra_sdio_shutdown_hdlr(); + printk(KERN_INFO "%s : Notified shutdown event to Libra driver.\n", + __func__); + } +} + +int libra_sdio_register_shutdown_hdlr( + shutdown_handler_t *libra_shutdown_hdlr) +{ + libra_sdio_shutdown_hdlr = libra_shutdown_hdlr; + return 0; +} +EXPORT_SYMBOL(libra_sdio_register_shutdown_hdlr); + +int libra_sdio_notify_card_removal( + notify_card_removal_t *libra_sdio_notify_card_removal_hdlr) +{ + libra_notify_card_removal_hdlr = libra_sdio_notify_card_removal_hdlr; + return 0; +} +EXPORT_SYMBOL(libra_sdio_notify_card_removal); + +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, + .drv.shutdown = libra_sdio_shutdown, +}; + +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; + libra_notify_card_removal_hdlr = NULL; + libra_sdio_shutdown_hdlr = 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 0000000000000000000000000000000000000000..ef0111ad9421b4c5b1de237cfe8c9cbca55cfd31 --- /dev/null +++ b/drivers/net/wireless/libra/qcomwlan7x27a_pwrif.c @@ -0,0 +1,225 @@ +/* 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 + +#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 level_min; + unsigned int level_max; + unsigned int pmapp_id; + unsigned int is_vreg_pin_controlled; + struct regulator *reg; +}; + + +static struct wlan_vreg_info vreg_info[] = { + {"bt", 3050000, 3050000, 21, 1, NULL}, + {"msme1", 1800000, 1800000, 2, 0, NULL}, + {"wlan_tcx0", 1800000, 1800000, 53, 0, NULL}, + {"wlan4", 1200000, 1200000, 23, 0, NULL}, + {"wlan2", 1350000, 1350000, 9, 1, NULL}, + {"wlan3", 1200000, 1200000, 10, 1, NULL}, +}; + +static int qrf6285_init_regs(void) +{ + struct regulator_bulk_data regs[ARRAY_SIZE(vreg_info)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + regs[i].supply = vreg_info[i].vreg_id; + regs[i].min_uV = vreg_info[i].level_min; + regs[i].max_uV = vreg_info[i].level_max; + } + + rc = regulator_bulk_get(NULL, ARRAY_SIZE(regs), regs); + if (rc) { + pr_err("%s: could not get regulators: %d\n", __func__, rc); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(regs); i++) + vreg_info[i].reg = regs[i].consumer; + + return 0; + +out: + return rc; +} + +int chip_power_qrf6285(bool on) +{ + static bool init_done; + int rc = 0, index = 0; + + if (unlikely(!init_done)) { + rc = qrf6285_init_regs(); + if (rc) + return rc; + else + init_done = true; + } + + 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; + } + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_A0, + PMAPP_CLOCK_VOTE_ON); + if (rc) { + pr_err("%s: Configuring A0 to always" + " on failed %d\n", __func__, rc); + goto clock_vote_fail; + } + } else { + gpio_set_value_cansleep(WLAN_GPIO_EXT_POR_N, 0); + rc = gpio_direction_input(WLAN_GPIO_EXT_POR_N); + if (rc) { + pr_err("WLAN reset GPIO %d set direction failed %d\n", + WLAN_GPIO_EXT_POR_N, rc); + } + gpio_free(WLAN_GPIO_EXT_POR_N); + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_A0, + PMAPP_CLOCK_VOTE_OFF); + if (rc) { + pr_err("%s: Configuring A0 to turn OFF" + " failed %d\n", __func__, rc); + } + } + + for (index = 0; index < ARRAY_SIZE(vreg_info); index++) { + if (on) { + + rc = regulator_set_voltage(vreg_info[index].reg, + vreg_info[index].level_min, + vreg_info[index].level_max); + if (rc) { + pr_err("%s:%s set voltage failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + + goto vreg_fail; + } + + rc = regulator_enable(vreg_info[index].reg); + if (rc) { + pr_err("%s:%s vreg enable failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + + goto vreg_fail; + } + + if (vreg_info[index].is_vreg_pin_controlled) { + rc = pmapp_vreg_lpm_pincntrl_vote(id, + vreg_info[index].pmapp_id, + PMAPP_CLOCK_ID_A0, 1); + if (rc) { + pr_err("%s:%s pmapp_vreg_lpm_pincntrl" + " for enable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + goto vreg_clock_vote_fail; + } + } + + /*At this point CLK_PWR_REQ is high*/ + if (WLAN_VREG_L6 == index) { + /* + * Configure A0 clock 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 A0 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_lpm_pincntrl_vote(id, + vreg_info[index].pmapp_id, + PMAPP_CLOCK_ID_A0, 0); + if (rc) { + pr_err("%s:%s pmapp_vreg_lpm_pincntrl" + " for disable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + } + } + rc = regulator_disable(vreg_info[index].reg); + 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 = regulator_disable(vreg_info[index].reg); + if (rc) { + pr_err("%s:%s vreg disable failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + } + index--; + } + if (!on) + goto fail; +clock_vote_fail: + gpio_set_value_cansleep(WLAN_GPIO_EXT_POR_N, 0); + rc = gpio_direction_input(WLAN_GPIO_EXT_POR_N); + if (rc) { + pr_err("WLAN reset GPIO %d set direction failed %d\n", + WLAN_GPIO_EXT_POR_N, rc); + } +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 0000000000000000000000000000000000000000..52b1a51d8a1a33587f923b2ef74793325927d15b --- /dev/null +++ b/drivers/net/wireless/libra/qcomwlan_pwrif.c @@ -0,0 +1,359 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 GPIO_WLAN_DEEP_SLEEP_N 230 +#define GPIO_WLAN_DEEP_SLEEP_N_DRAGON 82 +#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 char *vregs_qwlan_pc_name[] = { + "8058_l20_pc", + "8058_l8_pc", + NULL, + NULL, + "8901_l0_pc", + "8058_s2_pc", + NULL, + }; + 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 int vregs_qwlan_peek_current[] = { + 4000, + 150000, + 60000, + 0, + 32000, + 130000, + 0, + }; + static const bool vregs_is_pin_controlled_default[] = { + 1, + 1, + 0, + 0, + 1, + 1, + 0, + }; + static const bool vregs_is_pin_controlled_dragon[] = { + 0, + 0, + 0, + 0, + 0, + 1, + 0, + }; + bool const *vregs_is_pin_controlled; + static struct regulator *vregs_qwlan[ARRAY_SIZE(vregs_qwlan_name)]; + static struct regulator *vregs_pc_qwlan[ARRAY_SIZE(vregs_qwlan_name)]; + static struct msm_xo_voter *wlan_clock; + int ret, i, rc = 0; + unsigned wlan_gpio_deep_sleep = GPIO_WLAN_DEEP_SLEEP_N; + + vregs_is_pin_controlled = vregs_is_pin_controlled_default; + + if (machine_is_msm8x60_dragon()) { + wlan_gpio_deep_sleep = GPIO_WLAN_DEEP_SLEEP_N_DRAGON; + vregs_is_pin_controlled = vregs_is_pin_controlled_dragon; + } + /* 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(wlan_gpio_deep_sleep, "WLAN_DEEP_SLEEP_N"); + if (rc) { + pr_err("WLAN reset GPIO %d request failed\n", + wlan_gpio_deep_sleep); + goto fail; + } + rc = gpio_direction_output(wlan_gpio_deep_sleep, + WLAN_RESET); + if (rc < 0) { + pr_err("WLAN reset GPIO %d set output direction failed", + wlan_gpio_deep_sleep); + 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(wlan_gpio_deep_sleep, WLAN_RESET); + gpio_free(wlan_gpio_deep_sleep); + } + + /* WLAN VREG settings */ + for (i = 0; i < ARRAY_SIZE(vregs_qwlan_name); i++) { + if (on && !wlan_on) { + 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]) { + vregs_pc_qwlan[i] = regulator_get(NULL, + vregs_qwlan_pc_name[i]); + if (IS_ERR(vregs_pc_qwlan[i])) { + pr_err("regulator get of %s failed " + "(%ld)\n", + vregs_qwlan_pc_name[i], + PTR_ERR(vregs_pc_qwlan[i])); + rc = PTR_ERR(vregs_pc_qwlan[i]); + goto vreg_fail; + } + } + + if (vregs_qwlan_peek_current[i]) { + rc = regulator_set_optimum_mode(vregs_qwlan[i], + vregs_qwlan_peek_current[i]); + if (rc < 0) + pr_err("vreg %s set optimum mode" + " failed to %d (%d)\n", + vregs_qwlan_name[i], rc, + vregs_qwlan_peek_current[i]); + } + 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; + } + if (vregs_is_pin_controlled[i]) { + rc = regulator_enable(vregs_pc_qwlan[i]); + if (rc < 0) { + pr_err("vreg %s enable failed (%d)\n", + vregs_qwlan_pc_name[i], rc); + goto vreg_fail; + } + } + } else if (!on && wlan_on) { + + if (vregs_qwlan_peek_current[i]) { + /* For legacy reasons we pass 1mA current to + * put regulator in LPM mode. + */ + rc = regulator_set_optimum_mode(vregs_qwlan[i], + 1000); + if (rc < 0) + pr_info("vreg %s set optimum mode" + "failed (%d)\n", + vregs_qwlan_name[i], rc); + rc = regulator_set_voltage(vregs_qwlan[i], 0 , + vregs_qwlan_val_max[i]); + if (rc) + pr_err("regulator_set_voltage(%s)" + "failed (%d)\n", + vregs_qwlan_name[i], rc); + + } + + if (vregs_is_pin_controlled[i]) { + rc = regulator_disable(vregs_pc_qwlan[i]); + if (rc < 0) { + pr_err("vreg %s disable failed (%d)\n", + vregs_qwlan_pc_name[i], rc); + goto vreg_fail; + } + regulator_put(vregs_pc_qwlan[i]); + } + + 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; + } + regulator_put(vregs_qwlan[i]); + } + } + if (on) { + gpio_set_value_cansleep(wlan_gpio_deep_sleep, WLAN_RESET_OUT); + wlan_on = true; + } + else + wlan_on = false; + return 0; + +vreg_fail: + regulator_put(vregs_qwlan[i]); + if (vregs_is_pin_controlled[i]) + regulator_put(vregs_pc_qwlan[i]); +vreg_get_fail: + i--; + while (i >= 0) { + 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); + } + if (vregs_is_pin_controlled[i]) { + ret = !on ? regulator_enable(vregs_pc_qwlan[i]) : + regulator_disable(vregs_pc_qwlan[i]); + if (ret < 0) { + pr_err("vreg %s %s failed (%d) in err path\n", + vregs_qwlan_pc_name[i], + !on ? "enable" : "disable", ret); + } + } + regulator_put(vregs_qwlan[i]); + if (vregs_is_pin_controlled[i]) + regulator_put(vregs_pc_qwlan[i]); + i--; + } + if (!on) + goto fail; +fail_xo_mode_vote: + msm_xo_put(wlan_clock); +fail_gpio_dir_out: + gpio_free(wlan_gpio_deep_sleep); +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/rtlwifi/rtl8192cu/rf.c b/drivers/net/wireless/rtlwifi/rtl8192cu/rf.c index 506b9a078ed1885bea8583722d3275a0b5a94c93..476342647589a9d0006447a5e10e1c9aacb7653d 100644 --- a/drivers/net/wireless/rtlwifi/rtl8192cu/rf.c +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/rf.c @@ -104,7 +104,7 @@ void rtl92cu_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, tx_agc[RF90_PATH_A] = 0x10101010; tx_agc[RF90_PATH_B] = 0x10101010; } else if (rtlpriv->dm.dynamic_txhighpower_lvl == - TXHIGHPWRLEVEL_LEVEL1) { + TXHIGHPWRLEVEL_LEVEL2) { tx_agc[RF90_PATH_A] = 0x00000000; tx_agc[RF90_PATH_B] = 0x00000000; } else{ diff --git a/drivers/net/wireless/wcnss/Makefile b/drivers/net/wireless/wcnss/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..c077848dda2728cfd301101dba3ab1be954b4dda --- /dev/null +++ b/drivers/net/wireless/wcnss/Makefile @@ -0,0 +1,6 @@ + +# Makefile for WCNSS triple-play driver + +wcnsscore-objs += wcnss_wlan.o wcnss_riva.o qcomwlan_secif.o + +obj-$(CONFIG_WCNSS_CORE) += wcnsscore.o diff --git a/drivers/net/wireless/wcnss/qcomwlan_secif.c b/drivers/net/wireless/wcnss/qcomwlan_secif.c new file mode 100644 index 0000000000000000000000000000000000000000..e2be75cf01531c41aabae2af5373f59a2be5f1e8 --- /dev/null +++ b/drivers/net/wireless/wcnss/qcomwlan_secif.c @@ -0,0 +1,63 @@ +/* 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 + +/* + * 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 0000000000000000000000000000000000000000..23365ffdd8bc4b1c607ab06231159fe7fd6cc394 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_riva.c @@ -0,0 +1,399 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 void __iomem *msm_riva_base; +static struct msm_xo_voter *wlan_clock; +static const char *id = "WLAN"; +static LIST_HEAD(power_on_lock_list); +static DEFINE_MUTEX(list_lock); +static DEFINE_SEMAPHORE(riva_power_on_lock); + +#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_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, 3000000, 515000, NULL}, + {"iris_vdddig", VREG_NULL_CONFIG, 1200000, 0, 1200000, 10000, 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}, +}; + +struct host_driver { + char name[20]; + struct list_head list; +}; + + +static int configure_iris_xo(struct device *dev, bool use_48mhz_xo, int on) +{ + u32 reg = 0; + int rc = 0; + struct clk *cxo = clk_get(dev, "cxo"); + if (IS_ERR(cxo)) { + pr_err("Couldn't get cxo clock\n"); + return PTR_ERR(cxo); + } + + 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 */ + rc = clk_prepare_enable(cxo); + if (rc) { + pr_err("cxo enable failed\n"); + goto fail; + } + 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); + clk_disable_unprepare(cxo); + + if (!use_48mhz_xo) { + wlan_clock = msm_xo_get(MSM_XO_TCXO_A2, id); + if (IS_ERR(wlan_clock)) { + rc = PTR_ERR(wlan_clock); + pr_err("Failed to get MSM_XO_TCXO_A2 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); + + clk_put(cxo); + return rc; + +msm_xo_vote_fail: + msm_xo_put(wlan_clock); + +fail: + clk_put(cxo); + 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, reg_cnt; + + 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; + reg_cnt = regulator_count_voltages(regulators[i].regulator); + /* Set voltage to nominal. Exclude swtiches e.g. LVS */ + if ((regulators[i].nominal_min || regulators[i].max_voltage) + && (reg_cnt > 0)) { + 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 && (reg_cnt > 0)) { + 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) { + down(&riva_power_on_lock); + /* 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(dev, cfg->use_48mhz_xo, + WCNSS_WLAN_SWITCH_ON); + if (rc) + goto fail_iris_xo; + up(&riva_power_on_lock); + + } else { + configure_iris_xo(dev, 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: + up(&riva_power_on_lock); + return rc; +} +EXPORT_SYMBOL(wcnss_wlan_power); + +/* + * During SSR Riva should not be 'powered on' until all the host drivers + * finish their shutdown routines. Host drivers use below APIs to + * synchronize power-on. Riva will not be 'powered on' until all the + * requests(to lock power-on) are freed. + */ +int req_riva_power_on_lock(char *driver_name) +{ + struct host_driver *node; + + if (!driver_name) + goto err; + + node = kmalloc(sizeof(struct host_driver), GFP_KERNEL); + if (!node) + goto err; + strncpy(node->name, driver_name, sizeof(node->name)); + + mutex_lock(&list_lock); + /* Lock when the first request is added */ + if (list_empty(&power_on_lock_list)) + down(&riva_power_on_lock); + list_add(&node->list, &power_on_lock_list); + mutex_unlock(&list_lock); + + return 0; + +err: + return -EINVAL; +} +EXPORT_SYMBOL(req_riva_power_on_lock); + +int free_riva_power_on_lock(char *driver_name) +{ + int ret = -1; + struct host_driver *node; + + mutex_lock(&list_lock); + list_for_each_entry(node, &power_on_lock_list, list) { + if (!strncmp(node->name, driver_name, sizeof(node->name))) { + list_del(&node->list); + kfree(node); + ret = 0; + break; + } + } + /* unlock when the last host driver frees the lock */ + if (list_empty(&power_on_lock_list)) + up(&riva_power_on_lock); + mutex_unlock(&list_lock); + + return ret; +} +EXPORT_SYMBOL(free_riva_power_on_lock); diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c new file mode 100644 index 0000000000000000000000000000000000000000..ad9dc7d7521b75540a8027a317b41d0f3cca61f6 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_wlan.c @@ -0,0 +1,532 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 DEVICE "wcnss_wlan" +#define VERSION "1.01" +#define WCNSS_PIL_DEVICE "wcnss" + +/* module params */ +#define WCNSS_CONFIG_UNSPECIFIED (-1) +static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present"); + +static struct { + struct platform_device *pdev; + void *pil; + struct resource *mmio_res; + struct resource *tx_irq_res; + struct resource *rx_irq_res; + struct resource *gpios_5wire; + const struct dev_pm_ops *pm_ops; + int triggered; + int smd_channel_ready; + unsigned int serial_number; + int thermal_mitigation; + void (*tm_notify)(struct device *, int); + struct wcnss_wlan_config wlan_config; + struct delayed_work wcnss_work; +} *penv = NULL; + +static ssize_t wcnss_serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number); +} + +static ssize_t wcnss_serial_number_store(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + unsigned int value; + + if (!penv) + return -ENODEV; + + if (sscanf(buf, "%08X", &value) != 1) + return -EINVAL; + + penv->serial_number = value; + return count; +} + +static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR, + wcnss_serial_number_show, wcnss_serial_number_store); + + +static ssize_t wcnss_thermal_mitigation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation); +} + +static ssize_t wcnss_thermal_mitigation_store(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + int value; + + if (!penv) + return -ENODEV; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + penv->thermal_mitigation = value; + if (penv->tm_notify) + (penv->tm_notify)(dev, value); + return count; +} + +static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR, + wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store); + +static int wcnss_create_sysfs(struct device *dev) +{ + int ret; + + if (!dev) + return -ENODEV; + + ret = device_create_file(dev, &dev_attr_serial_number); + if (ret) + return ret; + + ret = device_create_file(dev, &dev_attr_thermal_mitigation); + if (ret) { + device_remove_file(dev, &dev_attr_serial_number); + return ret; + } + return 0; +} + +static void wcnss_remove_sysfs(struct device *dev) +{ + if (dev) { + device_remove_file(dev, &dev_attr_serial_number); + device_remove_file(dev, &dev_attr_thermal_mitigation); + } +} + +static void wcnss_post_bootup(struct work_struct *work) +{ + pr_info("%s: Cancel APPS vote for Iris & Riva\n", __func__); + + /* Since Riva is up, cancel any APPS vote for Iris & Riva VREGs */ + wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config, + WCNSS_WLAN_SWITCH_OFF); +} + +static int +wcnss_gpios_config(struct resource *gpios_5wire, bool enable) +{ + int i, j; + int rc = 0; + + for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) { + if (enable) { + rc = gpio_request(i, gpios_5wire->name); + if (rc) { + pr_err("WCNSS gpio_request %d err %d\n", i, rc); + goto fail; + } + } else + gpio_free(i); + } + + return rc; + +fail: + for (j = i-1; j >= gpios_5wire->start; j--) + gpio_free(j); + return rc; +} + +static int __devinit +wcnss_wlan_ctrl_probe(struct platform_device *pdev) +{ + if (!penv) + return -ENODEV; + + penv->smd_channel_ready = 1; + + pr_info("%s: SMD ctrl channel up\n", __func__); + + /* Schedule a work to do any post boot up activity */ + INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup); + schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000)); + + 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 platform_device *wcnss_get_platform_device(void) +{ + if (penv && penv->pdev) + return penv->pdev; + return NULL; +} +EXPORT_SYMBOL(wcnss_get_platform_device); + +struct wcnss_wlan_config *wcnss_get_wlan_config(void) +{ + if (penv && penv->pdev) + return &penv->wlan_config; + return NULL; +} +EXPORT_SYMBOL(wcnss_get_wlan_config); + +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); + +void wcnss_wlan_unregister_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops) +{ + if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) { + if (pm_ops->suspend != penv->pm_ops->suspend || + pm_ops->resume != penv->pm_ops->resume) + pr_err("PM APIs dont match with registered APIs\n"); + penv->pm_ops = NULL; + } +} +EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops); + +void wcnss_register_thermal_mitigation(struct device *dev, + void (*tm_notify)(struct device *, int)) +{ + if (penv && dev && tm_notify) + penv->tm_notify = tm_notify; +} +EXPORT_SYMBOL(wcnss_register_thermal_mitigation); + +void wcnss_unregister_thermal_mitigation( + void (*tm_notify)(struct device *, int)) +{ + if (penv && tm_notify) { + if (tm_notify != penv->tm_notify) + pr_err("tm_notify doesn't match registered\n"); + penv->tm_notify = NULL; + } +} +EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation); + +unsigned int wcnss_get_serial_number(void) +{ + if (penv) + return penv->serial_number; + return 0; +} +EXPORT_SYMBOL(wcnss_get_serial_number); + +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 +wcnss_trigger_config(struct platform_device *pdev) +{ + int ret; + struct qcom_wcnss_opts *pdata; + + /* make sure we are only triggered once */ + if (penv->triggered) + return 0; + penv->triggered = 1; + + /* initialize the WCNSS device configuration */ + pdata = pdev->dev.platform_data; + if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) + has_48mhz_xo = pdata->has_48mhz_xo; + penv->wlan_config.use_48mhz_xo = has_48mhz_xo; + + penv->thermal_mitigation = 0; + + penv->gpios_5wire = platform_get_resource_byname(pdev, IORESOURCE_IO, + "wcnss_gpios_5wire"); + + /* allocate 5-wire GPIO resources */ + if (!penv->gpios_5wire) { + dev_err(&pdev->dev, "insufficient IO resources\n"); + ret = -ENOENT; + goto fail_gpio_res; + } + + /* Configure 5 wire GPIOs */ + ret = wcnss_gpios_config(penv->gpios_5wire, true); + if (ret) { + dev_err(&pdev->dev, "WCNSS gpios config failed.\n"); + goto fail_gpio_res; + } + + /* 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; + } + + /* register sysfs entries */ + ret = wcnss_create_sysfs(&pdev->dev); + if (ret) + goto fail_sysfs; + + return 0; + +fail_sysfs: +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: + wcnss_gpios_config(penv->gpios_5wire, false); +fail_gpio_res: + kfree(penv); + penv = NULL; + return ret; +} + +#ifndef MODULE +static int wcnss_node_open(struct inode *inode, struct file *file) +{ + struct platform_device *pdev; + + pr_info(DEVICE " triggered by userspace\n"); + + pdev = penv->pdev; + return wcnss_trigger_config(pdev); +} + +static const struct file_operations wcnss_node_fops = { + .owner = THIS_MODULE, + .open = wcnss_node_open, +}; + +static struct miscdevice wcnss_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DEVICE, + .fops = &wcnss_node_fops, +}; +#endif /* ifndef MODULE */ + + +static int __devinit +wcnss_wlan_probe(struct platform_device *pdev) +{ + /* 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; + +#ifdef MODULE + + /* + * Since we were built as a module, we are running because + * the module was loaded, therefore we assume userspace + * applications are available to service PIL, so we can + * trigger the WCNSS configuration now + */ + pr_info(DEVICE " probed in MODULE mode\n"); + return wcnss_trigger_config(pdev); + +#else + + /* + * Since we were built into the kernel we'll be called as part + * of kernel initialization. We don't know if userspace + * applications are available to service PIL at this time + * (they probably are not), so we simply create a device node + * here. When userspace is available it should touch the + * device so that we know that WCNSS configuration can take + * place + */ + pr_info(DEVICE " probed in built-in mode\n"); + return misc_register(&wcnss_misc); + +#endif +} + +static int __devexit +wcnss_wlan_remove(struct platform_device *pdev) +{ + wcnss_remove_sysfs(&pdev->dev); + 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); + + + 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/of/Kconfig b/drivers/of/Kconfig index 8e84ce9765a9257a8429cd33838ab7e4bbd8fb15..fe151b5481077b06744f0f7adf929f994f124a05 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -91,8 +91,20 @@ config OF_PCI_IRQ help OpenFirmware PCI IRQ routing helpers +config OF_SPMI + def_tristate SPMI + depends on SPMI + help + OpenFirmware SPMI bus accessors + config OF_MTD depends on MTD def_bool y +config OF_SLIMBUS + def_tristate SLIMBUS + depends on SLIMBUS + help + OpenFirmware SLIMBUS accessors + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index aa90e602c8a7e5dfbe66770edf8e85dadcc27173..c3a31c8c5bae02a0f914bb38285a8b245d4580e4 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -12,4 +12,6 @@ obj-$(CONFIG_OF_SELFTEST) += selftest.o obj-$(CONFIG_OF_MDIO) += of_mdio.o obj-$(CONFIG_OF_PCI) += of_pci.o obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o +obj-$(CONFIG_OF_SPMI) += of_spmi.o obj-$(CONFIG_OF_MTD) += of_mtd.o +obj-$(CONFIG_OF_SLIMBUS) += of_slimbus.o diff --git a/drivers/of/of_slimbus.c b/drivers/of/of_slimbus.c new file mode 100644 index 0000000000000000000000000000000000000000..512ca7350a0e962c0590058fa61963d88162f6ac --- /dev/null +++ b/drivers/of/of_slimbus.c @@ -0,0 +1,83 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* OF helpers for SLIMbus */ +#include +#include +#include +#include +#include +#include + +int of_register_slim_devices(struct slim_controller *ctrl) +{ + struct device_node *node; + struct slim_boardinfo *binfo = NULL; + int n = 0; + int ret = 0; + + if (!ctrl->dev.of_node) + return -EINVAL; + + for_each_child_of_node(ctrl->dev.of_node, node) { + struct property *prop; + struct slim_device *slim; + char *name; + prop = of_find_property(node, "elemental-addr", NULL); + if (!prop || prop->length != 6) { + dev_err(&ctrl->dev, "of_slim: invalid E-addr"); + continue; + } + name = kzalloc(SLIMBUS_NAME_SIZE, GFP_KERNEL); + if (!name) { + dev_err(&ctrl->dev, "of_slim: out of memory"); + ret = -ENOMEM; + goto of_slim_err; + } + if (of_modalias_node(node, name, SLIMBUS_NAME_SIZE) < 0) { + dev_err(&ctrl->dev, "of_slim: modalias failure on %s\n", + node->full_name); + kfree(name); + continue; + } + slim = kzalloc(sizeof(struct slim_device), GFP_KERNEL); + if (!slim) { + dev_err(&ctrl->dev, "of_slim: out of memory"); + ret = -ENOMEM; + kfree(name); + goto of_slim_err; + } + memcpy(slim->e_addr, prop->value, 6); + + binfo = krealloc(binfo, (n + 1) * sizeof(struct slim_boardinfo), + GFP_KERNEL); + if (!binfo) { + dev_err(&ctrl->dev, "out of memory"); + kfree(name); + kfree(slim); + return -ENOMEM; + } + slim->name = (const char *)name; + binfo[n].bus_num = ctrl->nr; + binfo[n].slim_slave = slim; + n++; + } + return slim_register_board_info(binfo, n); +of_slim_err: + n--; + while (n >= 0) { + kfree(binfo[n].slim_slave->name); + kfree(binfo[n].slim_slave); + } + kfree(binfo); + return ret; +} diff --git a/drivers/of/of_spmi.c b/drivers/of/of_spmi.c new file mode 100644 index 0000000000000000000000000000000000000000..61085c99eb659eba5cf405b6da756af161fe2bb6 --- /dev/null +++ b/drivers/of/of_spmi.c @@ -0,0 +1,407 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 of_spmi_dev_info { + struct spmi_controller *ctrl; + struct spmi_boardinfo b_info; +}; + +struct of_spmi_res_info { + struct device_node *node; + uint32_t num_reg; + uint32_t num_irq; +}; + +/* + * Initialize r_info structure for safe usage + */ +static inline void of_spmi_init_resource(struct of_spmi_res_info *r_info, + struct device_node *node) +{ + r_info->node = node; + r_info->num_reg = 0; + r_info->num_irq = 0; +} + +/* + * Allocate dev_node array for spmi_device + */ +static inline int of_spmi_alloc_device_store(struct of_spmi_dev_info *d_info, + uint32_t num_dev_node) +{ + d_info->b_info.num_dev_node = num_dev_node; + d_info->b_info.dev_node = kzalloc(sizeof(struct spmi_resource) * + num_dev_node, GFP_KERNEL); + if (!d_info->b_info.dev_node) + return -ENOMEM; + + return 0; +} + +/* + * Calculate the number of resources to allocate + * + * The caller is responsible for initializing the of_spmi_res_info structure. + */ +static void of_spmi_sum_node_resources(struct of_spmi_res_info *r_info, + bool has_reg) +{ + struct of_irq oirq; + uint64_t size; + uint32_t flags; + int i = 0; + + while (of_irq_map_one(r_info->node, i, &oirq) == 0) + i++; + + r_info->num_irq += i; + + if (!has_reg) + return; + + /* + * We can't use of_address_to_resource here since it includes + * address translation; and address translation assumes that no + * parent buses have a size-cell of 0. But SPMI does have a + * size-cell of 0. + */ + i = 0; + while (of_get_address(r_info->node, i, &size, &flags) != NULL) + i++; + + r_info->num_reg += i; +} + +/* + * free spmi_resource for the spmi_device + */ +static void of_spmi_free_device_resources(struct of_spmi_dev_info *d_info) +{ + int i; + + for (i = 0; i < d_info->b_info.num_dev_node; i++) + kfree(d_info->b_info.dev_node[i].resource); + + kfree(d_info->b_info.dev_node); +} + +/* + * Gather node resources and populate + */ +static void of_spmi_populate_node_resources(struct of_spmi_dev_info *d_info, + struct of_spmi_res_info *r_info, + int idx) + +{ + uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg; + int i; + struct resource *res; + const __be32 *addrp; + uint64_t size; + uint32_t flags; + + res = d_info->b_info.dev_node[idx].resource; + d_info->b_info.dev_node[idx].of_node = r_info->node; + + if ((num_irq || num_reg) && (res != NULL)) { + for (i = 0; i < num_reg; i++, res++) { + /* Addresses are always 16 bits */ + addrp = of_get_address(r_info->node, i, &size, &flags); + BUG_ON(!addrp); + res->start = be32_to_cpup(addrp); + res->end = res->start + size - 1; + res->flags = flags; + } + WARN_ON(of_irq_to_resource_table(r_info->node, res, num_irq) != + num_irq); + } +} + +/* + * Allocate enough memory to handle the resources associated with the + * device_node. The number of device nodes included in this allocation + * depends on whether the spmi-dev-container flag is specified or not. + */ +static int of_spmi_allocate_node_resources(struct of_spmi_dev_info *d_info, + struct of_spmi_res_info *r_info, + uint32_t idx) +{ + uint32_t num_irq = r_info->num_irq, num_reg = r_info->num_reg; + struct resource *res = NULL; + + if (num_irq || num_reg) { + res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); + if (!res) + return -ENOMEM; + } + d_info->b_info.dev_node[idx].num_resources = num_reg + num_irq; + d_info->b_info.dev_node[idx].resource = res; + + return 0; +} + +/* + * create a single spmi_device + */ +static int of_spmi_create_device(struct of_spmi_dev_info *d_info, + struct device_node *node) +{ + struct spmi_controller *ctrl = d_info->ctrl; + struct spmi_boardinfo *b_info = &d_info->b_info; + void *result; + int rc; + + rc = of_modalias_node(node, b_info->name, sizeof(b_info->name)); + if (rc < 0) { + dev_err(&ctrl->dev, "of_spmi modalias failure on %s\n", + node->full_name); + return rc; + } + + b_info->of_node = of_node_get(node); + result = spmi_new_device(ctrl, b_info); + + if (result == NULL) { + dev_err(&ctrl->dev, "of_spmi: Failure registering %s\n", + node->full_name); + of_node_put(node); + return -ENODEV; + } + + return 0; +} + +/* + * Walks all children of a node containing the spmi-dev-container + * binding. This special type of spmi_device can include resources + * from more than one device node. + */ +static void of_spmi_walk_dev_container(struct of_spmi_dev_info *d_info, + struct device_node *container) +{ + struct of_spmi_res_info r_info = {}; + struct spmi_controller *ctrl = d_info->ctrl; + struct device_node *node; + int rc, i, num_dev_node = 0; + + if (!of_device_is_available(container)) + return; + + /* + * Count the total number of device_nodes so we know how much + * device_store to allocate. + */ + for_each_child_of_node(container, node) { + if (!of_device_is_available(node)) + continue; + num_dev_node++; + } + + rc = of_spmi_alloc_device_store(d_info, num_dev_node); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " device resources\n", __func__); + return; + } + + i = 0; + for_each_child_of_node(container, node) { + if (!of_device_is_available(node)) + continue; + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 1); + rc = of_spmi_allocate_node_resources(d_info, &r_info, i); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " resources\n", __func__); + of_spmi_free_device_resources(d_info); + return; + } + of_spmi_populate_node_resources(d_info, &r_info, i); + i++; + } + + rc = of_spmi_create_device(d_info, container); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to create device for" + " node %s\n", __func__, container->full_name); + of_spmi_free_device_resources(d_info); + return; + } +} + +/* + * Walks all children of a node containing the spmi-slave-container + * binding. This indicates that all spmi_devices created from this + * point all share the same slave_id. + */ +static void of_spmi_walk_slave_container(struct of_spmi_dev_info *d_info, + struct device_node *container) +{ + struct spmi_controller *ctrl = d_info->ctrl; + struct device_node *node; + int rc; + + for_each_child_of_node(container, node) { + struct of_spmi_res_info r_info; + + if (!of_device_is_available(node)) + continue; + + /** + * Check to see if this node contains children which + * should be all created as the same spmi_device. + */ + if (of_get_property(node, "spmi-dev-container", NULL)) { + of_spmi_walk_dev_container(d_info, node); + continue; + } + + rc = of_spmi_alloc_device_store(d_info, 1); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " device resources\n", __func__); + goto slave_err; + } + + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 1); + + rc = of_spmi_allocate_node_resources(d_info, &r_info, 0); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " resources\n", __func__); + goto slave_err; + } + + of_spmi_populate_node_resources(d_info, &r_info, 0); + + rc = of_spmi_create_device(d_info, node); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to create device for" + " node %s\n", __func__, node->full_name); + goto slave_err; + } + } + return; + +slave_err: + of_spmi_free_device_resources(d_info); +} + +int of_spmi_register_devices(struct spmi_controller *ctrl) +{ + struct device_node *node = ctrl->dev.of_node; + + /* Only register child devices if the ctrl has a node pointer set */ + if (!node) + return -ENODEV; + + if (of_get_property(node, "spmi-slave-container", NULL)) { + dev_err(&ctrl->dev, "%s: structural error: spmi-slave-container" + " is prohibited at the root level\n", __func__); + return -EINVAL; + } else if (of_get_property(node, "spmi-dev-container", NULL)) { + dev_err(&ctrl->dev, "%s: structural error: spmi-dev-container" + " is prohibited at the root level\n", __func__); + return -EINVAL; + } + + /** + * Make best effort to launch as many nodes as possible. If there are + * syntax errors, we will simply ignore that subtree and keep going. + */ + for_each_child_of_node(ctrl->dev.of_node, node) { + struct of_spmi_dev_info d_info = {}; + const __be32 *slave_id; + int len, rc, have_dev_container = 0; + + slave_id = of_get_property(node, "reg", &len); + if (!slave_id) { + dev_err(&ctrl->dev, "%s: invalid sid " + "on %s\n", __func__, node->full_name); + continue; + } + + d_info.b_info.slave_id = be32_to_cpup(slave_id); + d_info.ctrl = ctrl; + + if (of_get_property(node, "spmi-dev-container", NULL)) + have_dev_container = 1; + if (of_get_property(node, "spmi-slave-container", NULL)) { + if (have_dev_container) + of_spmi_walk_dev_container(&d_info, node); + else + of_spmi_walk_slave_container(&d_info, node); + } else { + struct of_spmi_res_info r_info; + + /** + * A dev container at the second level without a slave + * container is considered an error. + */ + if (have_dev_container) { + dev_err(&ctrl->dev, "%s: structural error," + " node %s has spmi-dev-container without" + " specifying spmi-slave-container\n", + __func__, node->full_name); + continue; + } + + if (!of_device_is_available(node)) + continue; + + rc = of_spmi_alloc_device_store(&d_info, 1); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " device resources\n", __func__); + continue; + } + + of_spmi_init_resource(&r_info, node); + of_spmi_sum_node_resources(&r_info, 0); + rc = of_spmi_allocate_node_resources(&d_info, + &r_info, 0); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to allocate" + " resources\n", __func__); + of_spmi_free_device_resources(&d_info); + continue; + } + + of_spmi_populate_node_resources(&d_info, &r_info, 0); + + rc = of_spmi_create_device(&d_info, node); + if (rc) { + dev_err(&ctrl->dev, "%s: unable to create" + " device\n", __func__); + of_spmi_free_device_resources(&d_info); + continue; + } + } + } + + return 0; +} +EXPORT_SYMBOL(of_spmi_register_devices); + +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 8390dca2b4e1b53fd67b90359fd2d70bcbafec6e..17b5df53eb8ae627eb2b0f625415da6cf2a264da 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 782953ae4c03789450050ad748d207a58550a900..58c62bd765bfc1d272edfda01c7663f9faf203f5 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 0000000000000000000000000000000000000000..23efb008c3379a45fb084d0996e402775ac09a56 --- /dev/null +++ b/drivers/platform/msm/Kconfig @@ -0,0 +1,52 @@ +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 \ + || ARCH_APQ8064 || ARCH_MSM9615 || ARCH_MSMCOPPER)) + 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 USB_BAM + boolean "USB BAM Driver" + depends on SPS && USB_GADGET + help + Enabling this option adds USB BAM Driver. + USB BAM driver was added to supports SPS Peripheral-to-Peripheral + transfers between the USB and other peripheral. + +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). + +config SPS_SUPPORT_NDP_BAM + bool "SPS support NDP BAM" + depends on SPS + default n + help + No-Data-Path BAM is used to improve BAM performance. + +endmenu diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..92eb492591acf53f8663358c2f14319275728a9b --- /dev/null +++ b/drivers/platform/msm/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the MSM specific device drivers. +# +obj-$(CONFIG_MSM_SSBI) += ssbi.o +obj-$(CONFIG_USB_BAM) += usb_bam.o +obj-$(CONFIG_SPS) += sps/ diff --git a/drivers/platform/msm/sps/Makefile b/drivers/platform/msm/sps/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f19e162cf7040a8434a4ed74a2ca6f0a6e7d42ba --- /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 0000000000000000000000000000000000000000..cf98f684e07d6bc3c52cc2052fefd6e1ba344346 --- /dev/null +++ b/drivers/platform/msm/sps/bam.c @@ -0,0 +1,1402 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 0x2f + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + +/* Maximum number of execution environment */ +#define BAM_MAX_EES 8 + +/** + * BAM Hardware registers. + * + */ +#define CTRL (0x0) +#define REVISION (0x4) +#define SW_REVISION (0x80) +#define NUM_PIPES (0x3c) +#define TIMER (0x40) +#define TIMER_CTRL (0x44) +#define DESC_CNT_TRSHLD (0x8) +#define IRQ_SRCS (0xc) +#define IRQ_SRCS_MSK (0x10) +#define IRQ_SRCS_UNMASKED (0x30) +#define IRQ_STTS (0x14) +#define IRQ_CLR (0x18) +#define IRQ_EN (0x1c) +#define AHB_MASTER_ERR_CTRLS (0x24) +#define AHB_MASTER_ERR_ADDR (0x28) +#define AHB_MASTER_ERR_DATA (0x2c) +#define TRUST_REG (0x70) +#define TEST_BUS_SEL (0x74) +#define TEST_BUS_REG (0x78) +#define CNFG_BITS (0x7c) +#define IRQ_SRCS_EE(n) (0x800 + 128 * (n)) +#define IRQ_SRCS_MSK_EE(n) (0x804 + 128 * (n)) +#define IRQ_SRCS_UNMASKED_EE(n) (0x808 + 128 * (n)) + +#define P_CTRL(n) (0x1000 + 4096 * (n)) +#define P_RST(n) (0x1004 + 4096 * (n)) +#define P_HALT(n) (0x1008 + 4096 * (n)) +#define P_IRQ_STTS(n) (0x1010 + 4096 * (n)) +#define P_IRQ_CLR(n) (0x1014 + 4096 * (n)) +#define P_IRQ_EN(n) (0x1018 + 4096 * (n)) +#define P_TIMER(n) (0x101c + 4096 * (n)) +#define P_TIMER_CTRL(n) (0x1020 + 4096 * (n)) +#define P_PRDCR_SDBND(n) (0x1024 + 4096 * (n)) +#define P_CNSMR_SDBND(n) (0x1028 + 4096 * (n)) +#define P_TRUST_REG(n) (0x1030 + 4096 * (n)) +#define P_EVNT_DEST_ADDR(n) (0x182c + 4096 * (n)) +#define P_EVNT_REG(n) (0x1818 + 4096 * (n)) +#define P_SW_OFSTS(n) (0x1800 + 4096 * (n)) +#define P_DATA_FIFO_ADDR(n) (0x1824 + 4096 * (n)) +#define P_DESC_FIFO_ADDR(n) (0x181c + 4096 * (n)) +#define P_EVNT_GEN_TRSHLD(n) (0x1828 + 4096 * (n)) +#define P_FIFO_SIZES(n) (0x1820 + 4096 * (n)) +#define P_RETR_CNTXT(n) (0x1834 + 4096 * (n)) +#define P_SI_CNTXT(n) (0x1838 + 4096 * (n)) +#define P_DF_CNTXT(n) (0x1830 + 4096 * (n)) +#define P_AU_PSM_CNTXT_1(n) (0x1804 + 4096 * (n)) +#define P_PSM_CNTXT_2(n) (0x1808 + 4096 * (n)) +#define P_PSM_CNTXT_3(n) (0x180c + 4096 * (n)) +#define P_PSM_CNTXT_4(n) (0x1810 + 4096 * (n)) +#define P_PSM_CNTXT_5(n) (0x1814 + 4096 * (n)) + +/** + * BAM Hardware registers bitmask. + * format: _ + * + */ +/* CTRL */ +#define CACHE_MISS_ERR_RESP_EN 0x80000 +#define LOCAL_CLK_GATING 0x60000 +#define IBC_DISABLE 0x10000 +#define BAM_CACHED_DESC_STORE 0x8000 +#define BAM_DESC_CACHE_SEL 0x6000 +#define BAM_EN_ACCUM 0x10 +#define BAM_EN 0x2 +#define BAM_SW_RST 0x1 + +/* REVISION */ +#define BAM_INACTIV_TMR_BASE 0xff000000 +#define BAM_CMD_DESC_EN 0x800000 +#define BAM_DESC_CACHE_DEPTH 0x600000 +#define BAM_NUM_INACTIV_TMRS 0x100000 +#define BAM_INACTIV_TMRS_EXST 0x80000 +#define BAM_HIGH_FREQUENCY_BAM 0x40000 +#define BAM_HAS_NO_BYPASS 0x20000 +#define BAM_SECURED 0x10000 +#define BAM_USE_VMIDMT 0x8000 +#define BAM_AXI_ACTIVE 0x4000 +#define BAM_CE_BUFFER_SIZE 0x2000 +#define BAM_NUM_EES 0xf00 +#define BAM_REVISION 0xff + +/* SW_REVISION */ +#define BAM_MAJOR 0xf0000000 +#define BAM_MINOR 0xfff0000 +#define BAM_STEP 0xffff + +/* NUM_PIPES */ +#define BAM_NON_PIPE_GRP 0xff000000 +#define BAM_PERIPH_NON_PIPE_GRP 0xff0000 +#define BAM_NUM_PIPES 0xff + +/* TIMER */ +#define BAM_TIMER 0xffff + +/* TIMER_CTRL */ +#define TIMER_RST 0x80000000 +#define TIMER_RUN 0x40000000 +#define TIMER_MODE 0x20000000 +#define TIMER_TRSHLD 0xffff + +/* DESC_CNT_TRSHLD */ +#define BAM_DESC_CNT_TRSHLD 0xffff + +/* IRQ_SRCS */ +#define BAM_IRQ 0x80000000 +#define P_IRQ 0x7fffffff + +/* IRQ_STTS */ +#define IRQ_STTS_BAM_TIMER_IRQ 0x10 +#define IRQ_STTS_BAM_EMPTY_IRQ 0x8 +#define IRQ_STTS_BAM_ERROR_IRQ 0x4 +#define IRQ_STTS_BAM_HRESP_ERR_IRQ 0x2 + +/* IRQ_CLR */ +#define IRQ_CLR_BAM_TIMER_IRQ 0x10 +#define IRQ_CLR_BAM_EMPTY_CLR 0x8 +#define IRQ_CLR_BAM_ERROR_CLR 0x4 +#define IRQ_CLR_BAM_HRESP_ERR_CLR 0x2 + +/* IRQ_EN */ +#define IRQ_EN_BAM_TIMER_IRQ 0x10 +#define IRQ_EN_BAM_EMPTY_EN 0x8 +#define IRQ_EN_BAM_ERROR_EN 0x4 +#define IRQ_EN_BAM_HRESP_ERR_EN 0x2 + +/* AHB_MASTER_ERR_CTRLS */ +#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 + +/* TRUST_REG */ +#define LOCK_EE_CTRL 0x2000 +#define BAM_VMID 0x1f00 +#define BAM_RST_BLOCK 0x80 +#define BAM_EE 0x7 + +/* TEST_BUS_SEL */ +#define BAM_SW_EVENTS_ZERO 0x200000 +#define BAM_SW_EVENTS_SEL 0x180000 +#define BAM_DATA_ERASE 0x40000 +#define BAM_DATA_FLUSH 0x20000 +#define BAM_CLK_ALWAYS_ON 0x10000 +#define BAM_TESTBUS_SEL 0x7f + +/* CNFG_BITS */ +#define CNFG_BITS_MULTIPLE_EVENTS_DESC_AVAIL_EN 0x40000000 +#define CNFG_BITS_MULTIPLE_EVENTS_SIZE_EN 0x20000000 +#define CNFG_BITS_BAM_ZLT_W_CD_SUPPORT 0x10000000 +#define CNFG_BITS_BAM_CD_ENABLE 0x8000000 +#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 + +/* P_ctrln */ +#define P_LOCK_GROUP 0x1f0000 +#define P_WRITE_NWD 0x800 +#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 + +/* P_RSTn */ +#define P_RST_P_SW_RST 0x1 + +/* P_HALTn */ +#define P_HALT_P_PROD_HALTED 0x2 +#define P_HALT_P_HALT 0x1 + +/* P_TRUST_REGn */ +#define BAM_P_VMID 0x1f00 +#define BAM_P_SUP_GROUP 0xf8 +#define BAM_P_EE 0x7 + +/* P_IRQ_STTSn */ +#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 + +/* P_IRQ_CLRn */ +#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 + +/* P_IRQ_ENn */ +#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 + +/* P_TIMERn */ +#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_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_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 + +/* P_EVNT_GEN_TRSHLDn */ +#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_DF_CNTXT_WB_ACCUMULATED 0xffff0000 +#define P_DF_CNTXT_DF_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 + +#else + +/* 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 +#endif + +#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; + + SPS_DBG2("sps:%s:bam=0x%x(va).ee=%d.", __func__, (u32) base, ee); + + 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); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + bam_write_reg_field(base, CTRL, CACHE_MISS_ERR_RESP_EN, 1); + + bam_write_reg_field(base, CTRL, LOCAL_CLK_GATING, 1); +#endif + + 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; + + SPS_DBG2("sps:%s:bam=0x%x(va).", __func__, (u32) base); + + /* + * 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) { + SPS_ERR("sps:bam 0x%x(va) the number of pipes is more than " + "the maximum number allowed.", (u32) base); + 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; + + SPS_DBG2("sps:%s:bam=0x%x(va).", __func__, (u32) base); + + if (!bam_read_reg_field(base, CTRL, BAM_EN)) { + SPS_ERR("sps:%s:bam 0x%x(va) is not enabled.\n", + __func__, (u32) base); + 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:%s:bam 0x%x(va) Invalid BAM version 0x%x.\n", + __func__, (u32) base, ver); + return -ENODEV; + } + + return 0; +} + +/** + * Disable a BAM device + * + */ +void bam_exit(void *base, u32 ee) +{ + SPS_DBG2("sps:%s:bam=0x%x(va).ee=%d.", __func__, (u32) base, 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); +} + +/** + * Output BAM register content + * including the TEST_BUS register content under + * different TEST_BUS_SEL values. + */ +static void bam_output_register_content(void *base) +{ + u32 num_pipes; + u32 test_bus_selection[] = {0x1, 0x2, 0x3, 0x4, 0xD, 0x10, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}; + u32 i; + u32 size = sizeof(test_bus_selection) / sizeof(u32); + + for (i = 0; i < size; i++) { + bam_write_reg_field(base, TEST_BUS_SEL, BAM_TESTBUS_SEL, + test_bus_selection[i]); + + SPS_INFO("sps:bam 0x%x(va);BAM_TEST_BUS_REG is" + "0x%x when BAM_TEST_BUS_SEL is 0x%x.", + (u32) base, bam_read_reg(base, TEST_BUS_REG), + bam_read_reg_field(base, TEST_BUS_SEL, + BAM_TESTBUS_SEL)); + } + + print_bam_reg(base); + + num_pipes = bam_read_reg_field(base, NUM_PIPES, + BAM_NUM_PIPES); + SPS_INFO("sps:bam 0x%x(va) has %d pipes.", + (u32) base, num_pipes); + + for (i = 0; i < num_pipes; i++) + print_bam_pipe_reg(base, i); + +} + +/** + * Get BAM IRQ source and clear global IRQ status + */ +u32 bam_check_irq_source(void *base, u32 ee, u32 mask, + enum sps_callback_case *cb_case) +{ + u32 source = bam_read_reg(base, IRQ_SRCS_EE(ee)); + u32 clr = source & (1UL << 31); + + if (clr) { + u32 status = 0; + status = bam_read_reg(base, IRQ_STTS); + + if (status & IRQ_STTS_BAM_ERROR_IRQ) { + SPS_ERR("sps:bam 0x%x(va);bam irq status=" + "0x%x.\nsps: BAM_ERROR_IRQ\n", + (u32) base, status); + bam_output_register_content(base); + *cb_case = SPS_CALLBACK_BAM_ERROR_IRQ; + } else if (status & IRQ_STTS_BAM_HRESP_ERR_IRQ) { + SPS_ERR("sps:bam 0x%x(va);bam irq status=" + "0x%x.\nsps: BAM_HRESP_ERR_IRQ\n", + (u32) base, status); + bam_output_register_content(base); + *cb_case = SPS_CALLBACK_BAM_HRESP_ERR_IRQ; + } else + SPS_INFO("sps:bam 0x%x(va);bam irq status=" + "0x%x.", (u32) base, status); + + bam_write_reg(base, IRQ_CLR, status); + } + + source &= (mask|(1UL << 31)); + return source; +} + +/** + * Initialize a BAM pipe + */ +int bam_pipe_init(void *base, u32 pipe, struct bam_pipe_parameters *param, + u32 ee) +{ + SPS_DBG2("sps:%s:bam=0x%x(va).pipe=%d.", __func__, (u32) base, pipe); + + /* 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); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + bam_write_reg_field(base, P_CTRL(pipe), P_LOCK_GROUP, + param->lock_group); + + SPS_DBG("sps:bam=0x%x(va).pipe=%d.lock_group=%d.\n", + (u32) base, pipe, param->lock_group); +#endif + + 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_DBG2("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); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + bam_write_reg_field(base, P_CTRL(pipe), P_WRITE_NWD, + param->write_nwd); + + SPS_DBG("sps:%s WRITE_NWD bit for this bam2bam pipe.", + param->write_nwd ? "Set" : "Do not set"); +#endif + } + + /* 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) +{ + SPS_DBG2("sps:%s:bam=0x%x(va).pipe=%d.", __func__, (u32) base, pipe); + + 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) +{ + SPS_DBG2("sps:%s:bam=0x%x(va).pipe=%d.", __func__, (u32) base, pipe); + + bam_write_reg_field(base, P_CTRL(pipe), P_EN, 1); +} + +/** + * Diasble a BAM pipe + * + */ +void bam_pipe_disable(void *base, u32 pipe) +{ + SPS_DBG2("sps:%s:bam=0x%x(va).pipe=%d.", __func__, (u32) base, 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) +{ + SPS_DBG2("sps:%s:bam=0x%x(va).pipe=%d.", __func__, (u32) base, pipe); + + 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); +#ifndef CONFIG_SPS_SUPPORT_NDP_BAM + bam_write_reg(base, P_IRQ_DEST_ADDR(pipe), irq_gen_addr); + bam_write_reg_field(base, IRQ_SIC_SEL, (1 << pipe), 1); +#endif + 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. + */ +#ifndef CONFIG_SPS_SUPPORT_NDP_BAM + bam_write_reg(base, P_IRQ_DEST_ADDR(pipe), irq_gen_addr); +#endif + 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)); +} + +#ifdef CONFIG_DEBUG_FS +/* output the content of BAM-level registers */ +void print_bam_reg(void *virt_addr) +{ + int i, n; + u32 *bam = (u32 *) virt_addr; + u32 ctrl; + u32 ver; + u32 pipes; + + if (bam == NULL) + return; + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + ctrl = bam[0x0 / 4]; + ver = bam[0x4 / 4]; + pipes = bam[0x3c / 4]; +#else + ctrl = bam[0xf80 / 4]; + ver = bam[0xf84 / 4]; + pipes = bam[0xfbc / 4]; +#endif + + SPS_INFO("\nsps:----- Content of BAM-level registers -----\n"); + + SPS_INFO("BAM_CTRL: 0x%x.\n", ctrl); + SPS_INFO("BAM_REVISION: 0x%x.\n", ver); + SPS_INFO("NUM_PIPES: 0x%x.\n", pipes); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + for (i = 0x0; i < 0x80; i += 0x10) +#else + for (i = 0xf80; i < 0x1000; i += 0x10) +#endif + SPS_INFO("bam addr 0x%x: 0x%x,0x%x,0x%x,0x%x.\n", i, + bam[i / 4], bam[(i / 4) + 1], + bam[(i / 4) + 2], bam[(i / 4) + 3]); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + for (i = 0x800, n = 0; n++ < 8; i += 0x80) +#else + for (i = 0x1800, n = 0; n++ < 4; i += 0x80) +#endif + SPS_INFO("bam addr 0x%x: 0x%x,0x%x,0x%x,0x%x.\n", i, + bam[i / 4], bam[(i / 4) + 1], + bam[(i / 4) + 2], bam[(i / 4) + 3]); + + SPS_INFO("\nsps:----- Content of BAM-level registers -----\n"); +} + +/* output the content of BAM pipe registers */ +void print_bam_pipe_reg(void *virt_addr, u32 pipe_index) +{ + int i; + u32 *bam = (u32 *) virt_addr; + u32 pipe = pipe_index; + + if (bam == NULL) + return; + + SPS_INFO("\nsps:----- Content of Pipe %d registers -----\n", + pipe); + + SPS_INFO("-- Pipe Management Registers --\n"); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + for (i = 0x1000 + 0x1000 * pipe; i < 0x1000 + 0x1000 * pipe + 0x80; + i += 0x10) +#else + for (i = 0x0000 + 0x80 * pipe; i < 0x0000 + 0x80 * (pipe + 1); + i += 0x10) +#endif + SPS_INFO("bam addr 0x%x: 0x%x,0x%x,0x%x,0x%x.\n", i, + bam[i / 4], bam[(i / 4) + 1], + bam[(i / 4) + 2], bam[(i / 4) + 3]); + + SPS_INFO("-- Pipe Configuration and Internal State Registers --\n"); + +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + for (i = 0x1800 + 0x1000 * pipe; i < 0x1800 + 0x1000 * pipe + 0x40; + i += 0x10) +#else + for (i = 0x1000 + 0x40 * pipe; i < 0x1000 + 0x40 * (pipe + 1); + i += 0x10) +#endif + SPS_INFO("bam addr 0x%x: 0x%x,0x%x,0x%x,0x%x.\n", i, + bam[i / 4], bam[(i / 4) + 1], + bam[(i / 4) + 2], bam[(i / 4) + 3]); + + SPS_INFO("\nsps:----- Content of Pipe %d registers -----\n", + pipe); +} + +/* output the content of selected BAM-level registers */ +void print_bam_selected_reg(void *virt_addr) +{ + void *base = virt_addr; + + if (base == NULL) + return; + + SPS_INFO("\nsps:----- Content of BAM-level registers -----\n"); + + SPS_INFO("BAM_CTRL: 0x%x\n" + "BAM_REVISION: 0x%x\n" + "BAM_NUM_EES: %d\n" +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + "BAM_CMD_DESC_EN: 0x%x\n" +#endif + "BAM_NUM_PIPES: %d\n" + "BAM_DESC_CNT_TRSHLD: 0x%x (%d)\n" + "BAM_IRQ_SRCS: 0x%x\n" + "BAM_IRQ_SRCS_MSK: 0x%x\n" + "BAM_EE: %d\n" + "BAM_CNFG_BITS: 0x%x\n", + bam_read_reg(base, CTRL), + bam_read_reg_field(base, REVISION, BAM_REVISION), + bam_read_reg_field(base, REVISION, BAM_NUM_EES), +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + bam_read_reg_field(base, REVISION, BAM_CMD_DESC_EN), +#endif + bam_read_reg_field(base, NUM_PIPES, BAM_NUM_PIPES), + bam_read_reg_field(base, DESC_CNT_TRSHLD, BAM_DESC_CNT_TRSHLD), + bam_read_reg_field(base, DESC_CNT_TRSHLD, BAM_DESC_CNT_TRSHLD), + bam_read_reg(base, IRQ_SRCS), + bam_read_reg(base, IRQ_SRCS_MSK), + bam_read_reg_field(base, TRUST_REG, BAM_EE), + bam_read_reg(base, CNFG_BITS)); + + SPS_INFO("\nsps:----- Content of BAM-level registers -----\n"); +} + +/* output the content of selected BAM pipe registers */ +void print_bam_pipe_selected_reg(void *virt_addr, u32 pipe_index) +{ + void *base = virt_addr; + u32 pipe = pipe_index; + + if (base == NULL) + return; + + SPS_INFO("\nsps:----- Registers of Pipe %d -----\n", pipe); + + SPS_INFO("BAM_P_CTRL: 0x%x\n" + "BAM_P_SYS_MODE: %d\n" + "BAM_P_DIRECTION: %d\n" +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + "BAM_P_LOCK_GROUP: 0x%x (%d)\n" +#endif + "BAM_P_EE: %d\n" + "BAM_P_IRQ_STTS: 0x%x\n" + "BAM_P_IRQ_STTS_P_TRNSFR_END_IRQ: 0x%x\n" + "BAM_P_IRQ_STTS_P_PRCSD_DESC_IRQ: 0x%x\n" + "BAM_P_IRQ_EN: 0x%x\n" + "BAM_P_PRDCR_SDBNDn_BAM_P_BYTES_FREE: 0x%x (%d)\n" + "BAM_P_CNSMR_SDBNDn_BAM_P_BYTES_AVAIL: 0x%x (%d)\n" + "BAM_P_SW_DESC_OFST: 0x%x\n" + "BAM_P_DESC_FIFO_PEER_OFST: 0x%x\n" + "BAM_P_EVNT_DEST_ADDR: 0x%x\n" + "BAM_P_DESC_FIFO_ADDR: 0x%x\n" + "BAM_P_DESC_FIFO_SIZE: 0x%x (%d)\n" + "BAM_P_DATA_FIFO_ADDR: 0x%x\n" + "BAM_P_DATA_FIFO_SIZE: 0x%x (%d)\n" + "BAM_P_EVNT_GEN_TRSHLD: 0x%x (%d)\n", + bam_read_reg(base, P_CTRL(pipe)), + bam_read_reg_field(base, P_CTRL(pipe), P_SYS_MODE), + bam_read_reg_field(base, P_CTRL(pipe), P_DIRECTION), +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM + bam_read_reg_field(base, P_CTRL(pipe), P_LOCK_GROUP), + bam_read_reg_field(base, P_CTRL(pipe), P_LOCK_GROUP), +#endif + bam_read_reg_field(base, P_TRUST_REG(pipe), BAM_P_EE), + bam_read_reg(base, P_IRQ_STTS(pipe)), + bam_read_reg_field(base, P_IRQ_STTS(pipe), + P_IRQ_STTS_P_TRNSFR_END_IRQ), + bam_read_reg_field(base, P_IRQ_STTS(pipe), + P_IRQ_STTS_P_PRCSD_DESC_IRQ), + bam_read_reg(base, P_IRQ_EN(pipe)), + bam_read_reg_field(base, P_PRDCR_SDBND(pipe), + P_PRDCR_SDBNDn_BAM_P_BYTES_FREE), + bam_read_reg_field(base, P_PRDCR_SDBND(pipe), + P_PRDCR_SDBNDn_BAM_P_BYTES_FREE), + bam_read_reg_field(base, P_CNSMR_SDBND(pipe), + P_CNSMR_SDBNDn_BAM_P_BYTES_AVAIL), + bam_read_reg_field(base, P_CNSMR_SDBND(pipe), + P_CNSMR_SDBNDn_BAM_P_BYTES_AVAIL), + bam_read_reg_field(base, P_SW_OFSTS(pipe), SW_DESC_OFST), + bam_read_reg_field(base, P_EVNT_REG(pipe), + P_DESC_FIFO_PEER_OFST), + bam_read_reg(base, P_EVNT_DEST_ADDR(pipe)), + bam_read_reg(base, P_DESC_FIFO_ADDR(pipe)), + bam_read_reg_field(base, P_FIFO_SIZES(pipe), P_DESC_FIFO_SIZE), + bam_read_reg_field(base, P_FIFO_SIZES(pipe), P_DESC_FIFO_SIZE), + bam_read_reg(base, P_DATA_FIFO_ADDR(pipe)), + bam_read_reg_field(base, P_FIFO_SIZES(pipe), P_DATA_FIFO_SIZE), + bam_read_reg_field(base, P_FIFO_SIZES(pipe), P_DATA_FIFO_SIZE), + bam_read_reg_field(base, P_EVNT_GEN_TRSHLD(pipe), + P_EVNT_GEN_TRSHLD_P_TRSHLD), + bam_read_reg_field(base, P_EVNT_GEN_TRSHLD(pipe), + P_EVNT_GEN_TRSHLD_P_TRSHLD)); +} + +/* output descriptor FIFO of a pipe */ +void print_bam_pipe_desc_fifo(void *virt_addr, u32 pipe_index) +{ + void *base = virt_addr; + u32 pipe = pipe_index; + u32 desc_fifo_addr; + u32 desc_fifo_size; + u32 *desc_fifo; + int i; + + if (base == NULL) + return; + + desc_fifo_addr = bam_read_reg(base, P_DESC_FIFO_ADDR(pipe)); + desc_fifo_size = bam_read_reg_field(base, P_FIFO_SIZES(pipe), + P_DESC_FIFO_SIZE); + + if (desc_fifo_addr == 0) { + SPS_ERR("sps:%s:desc FIFO address of Pipe %d is NULL.\n", + __func__, pipe); + return; + } else if (desc_fifo_size == 0) { + SPS_ERR("sps:%s:desc FIFO size of Pipe %d is 0.\n", + __func__, pipe); + return; + } + + SPS_INFO("\nsps:----- descriptor FIFO of Pipe %d -----\n", pipe); + + SPS_INFO("BAM_P_DESC_FIFO_ADDR: 0x%x\n" + "BAM_P_DESC_FIFO_SIZE: 0x%x (%d)\n\n", + desc_fifo_addr, desc_fifo_size, desc_fifo_size); + + desc_fifo = (u32 *) phys_to_virt(desc_fifo_addr); + + SPS_INFO("-------------------- begin of FIFO --------------------\n"); + + for (i = 0; i < desc_fifo_size; i += 0x10) + SPS_INFO("addr 0x%x: 0x%x, 0x%x, 0x%x, 0x%x.\n", + desc_fifo_addr + i, + desc_fifo[i / 4], desc_fifo[(i / 4) + 1], + desc_fifo[(i / 4) + 2], desc_fifo[(i / 4) + 3]); + + SPS_INFO("-------------------- end of FIFO --------------------\n"); +} +#endif diff --git a/drivers/platform/msm/sps/bam.h b/drivers/platform/msm/sps/bam.h new file mode 100644 index 0000000000000000000000000000000000000000..3521ffaa780f5abfc16681d7956a5dd38ca9634f --- /dev/null +++ b/drivers/platform/msm/sps/bam.h @@ -0,0 +1,408 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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() */ +#include "spsi.h" + +/* 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, +}; + +/* NWD written Type */ +enum bam_write_nwd { + BAM_WRITE_NWD_DISABLE = 0, + BAM_WRITE_NWD_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; + enum bam_write_nwd write_nwd; + u32 desc_base; /* Physical address of descriptor FIFO */ + u32 desc_size; /* Size (bytes) of descriptor FIFO */ + u32 lock_group; /* The lock group this pipe belongs to */ + 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 IRQ source and clear global IRQ status + * + * This function gets BAM IRQ source. + * Clear global IRQ status if it is non-zero. + * + * @base - BAM virtual base address. + * + * @ee - BAM execution environment index + * + * @mask - active pipes mask. + * + * @case - callback case. + * + * @return IRQ status + * + */ +u32 bam_check_irq_source(void *base, u32 ee, u32 mask, + enum sps_callback_case *cb_case); + +/** + * 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 0000000000000000000000000000000000000000..fbaea09db324313e01b54cdbefc877dd7015225a --- /dev/null +++ b/drivers/platform/msm/sps/sps.c @@ -0,0 +1,2164 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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 +u8 debugfs_record_enabled; +u8 logging_option; +u8 debug_level_option; +u8 print_limit_option; +u8 reg_dump_option; + +static char *debugfs_buf; +static u32 debugfs_buf_size; +static u32 debugfs_buf_used; +static int wraparound; + +struct dentry *dent; +struct dentry *dfile_info; +struct dentry *dfile_logging_option; +struct dentry *dfile_debug_level_option; +struct dentry *dfile_print_limit_option; +struct dentry *dfile_reg_dump_option; +struct dentry *dfile_bam_addr; + +static struct sps_bam *phy2bam(u32 phys_addr); + +/* record debug info for debugfs */ +void sps_debugfs_record(const char *msg) +{ + if (debugfs_record_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 = 0; + int size; + + if (debugfs_record_enabled) { + 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 + */ +static ssize_t sps_set_info(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long missing; + char str[MAX_MSG_LEN]; + int i; + u32 buf_size_kb = 0; + u32 new_buf_size; + + 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: input buffer size is %dKB\n", buf_size_kb); + + if ((logging_option == 0) || (logging_option == 2)) { + pr_info("sps:debugfs: need to first turn on recording.\n"); + return -EFAULT; + } + + if (buf_size_kb < 1) { + pr_info("sps:debugfs: buffer size should be " + "no less than 1KB.\n"); + return -EFAULT; + } + + new_buf_size = buf_size_kb * SZ_1K; + + if (debugfs_record_enabled) { + if (debugfs_buf_size == new_buf_size) { + /* need do nothing */ + pr_info("sps:debugfs: input buffer size " + "is the same as before.\n"); + return count; + } else { + /* release the current buffer */ + debugfs_record_enabled = false; + debugfs_buf_used = 0; + wraparound = false; + kfree(debugfs_buf); + debugfs_buf = NULL; + } + } + + /* allocate new buffer */ + debugfs_buf_size = new_buf_size; + + 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; + } + + debugfs_buf_used = 0; + wraparound = false; + debugfs_record_enabled = true; + + return count; +} + +const struct file_operations sps_info_ops = { + .read = sps_read_info, + .write = sps_set_info, +}; + +/* return the current logging option to userspace */ +static ssize_t sps_read_logging_option(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char value[MAX_MSG_LEN]; + int nbytes; + + nbytes = snprintf(value, MAX_MSG_LEN, "%d\n", logging_option); + + return simple_read_from_buffer(ubuf, count, ppos, value, nbytes); +} + +/* + * set the logging option + */ +static ssize_t sps_set_logging_option(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long missing; + char str[MAX_MSG_LEN]; + int i; + u8 option = 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) + option = (option * 10) + (str[i] - '0'); + + pr_info("sps:debugfs: try to change logging option to %d\n", option); + + if (option > 3) { + pr_err("sps:debugfs: invalid logging option:%d\n", option); + return count; + } + + if (((option == 0) || (option == 2)) && + ((logging_option == 1) || (logging_option == 3))) { + debugfs_record_enabled = false; + kfree(debugfs_buf); + debugfs_buf = NULL; + debugfs_buf_used = 0; + debugfs_buf_size = 0; + wraparound = false; + } + + logging_option = option; + + return count; +} + +const struct file_operations sps_logging_option_ops = { + .read = sps_read_logging_option, + .write = sps_set_logging_option, +}; + +/* + * input the bam physical address + */ +static ssize_t sps_set_bam_addr(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long missing; + char str[MAX_MSG_LEN]; + u32 i; + u32 bam_addr = 0; + struct sps_bam *bam; + u32 num_pipes = 0; + void *vir_addr; + + 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) + bam_addr = (bam_addr * 10) + (str[i] - '0'); + + pr_info("sps:debugfs:input BAM physical address:0x%x\n", bam_addr); + + bam = phy2bam(bam_addr); + + if (bam == NULL) { + pr_err("sps:debugfs:BAM 0x%x is not registered.", bam_addr); + return count; + } else { + vir_addr = bam->base; + num_pipes = bam->props.num_pipes; + } + + switch (reg_dump_option) { + case 1: /* output all registers of this BAM */ + print_bam_reg(vir_addr); + for (i = 0; i < num_pipes; i++) + print_bam_pipe_reg(vir_addr, i); + break; + case 2: /* output BAM-level registers */ + print_bam_reg(vir_addr); + break; + case 3: /* output selected BAM-level registers */ + print_bam_selected_reg(vir_addr); + break; + case 4: /* output selected registers of all pipes */ + for (i = 0; i < num_pipes; i++) + print_bam_pipe_selected_reg(vir_addr, i); + break; + case 5: /* output selected registers of some pipes */ + print_bam_pipe_selected_reg(vir_addr, 4); + print_bam_pipe_selected_reg(vir_addr, 5); + break; + case 6: /* output desc FIFO of all active pipes */ + for (i = 0; i < num_pipes; i++) + print_bam_pipe_desc_fifo(vir_addr, i); + break; + case 7: /* output desc FIFO of some pipes */ + print_bam_pipe_desc_fifo(vir_addr, 4); + print_bam_pipe_desc_fifo(vir_addr, 5); + break; + case 8: /* output selected registers and valid desc FIFO of all pipes */ + for (i = 0; i < num_pipes; i++) { + print_bam_pipe_selected_reg(vir_addr, i); + print_bam_pipe_desc_fifo(vir_addr, i); + } + break; + case 9: /* output selected registers and desc FIFO of some pipes */ + print_bam_pipe_selected_reg(vir_addr, 4); + print_bam_pipe_desc_fifo(vir_addr, 4); + print_bam_pipe_selected_reg(vir_addr, 5); + print_bam_pipe_desc_fifo(vir_addr, 5); + break; + default: + pr_info("sps:no dump option is chosen yet."); + } + + return count; +} + +const struct file_operations sps_bam_addr_ops = { + .write = sps_set_bam_addr, +}; + +static void sps_debugfs_init(void) +{ + debugfs_record_enabled = false; + logging_option = 0; + debug_level_option = 0; + print_limit_option = 0; + reg_dump_option = 0; + 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_info = debugfs_create_file("info", 0666, dent, 0, + &sps_info_ops); + if (!dfile_info || IS_ERR(dfile_info)) { + pr_err("sps:fail to create the file for debug_fs info.\n"); + goto info_err; + } + + dfile_logging_option = debugfs_create_file("logging_option", 0666, + dent, 0, &sps_logging_option_ops); + if (!dfile_logging_option || IS_ERR(dfile_logging_option)) { + pr_err("sps:fail to create the file for debug_fs " + "logging_option.\n"); + goto logging_option_err; + } + + dfile_debug_level_option = debugfs_create_u8("debug_level_option", + 0666, dent, &debug_level_option); + if (!dfile_debug_level_option || IS_ERR(dfile_debug_level_option)) { + pr_err("sps:fail to create the file for debug_fs " + "debug_level_option.\n"); + goto debug_level_option_err; + } + + dfile_print_limit_option = debugfs_create_u8("print_limit_option", + 0666, dent, &print_limit_option); + if (!dfile_print_limit_option || IS_ERR(dfile_print_limit_option)) { + pr_err("sps:fail to create the file for debug_fs " + "print_limit_option.\n"); + goto print_limit_option_err; + } + + dfile_reg_dump_option = debugfs_create_u8("reg_dump_option", 0666, + dent, ®_dump_option); + if (!dfile_reg_dump_option || IS_ERR(dfile_reg_dump_option)) { + pr_err("sps:fail to create the file for debug_fs " + "reg_dump_option.\n"); + goto reg_dump_option_err; + } + + dfile_bam_addr = debugfs_create_file("bam_addr", 0666, + dent, 0, &sps_bam_addr_ops); + if (!dfile_bam_addr || IS_ERR(dfile_bam_addr)) { + pr_err("sps:fail to create the file for debug_fs " + "bam_addr.\n"); + goto bam_addr_err; + } + + return; + +bam_addr_err: + debugfs_remove(dfile_reg_dump_option); +reg_dump_option_err: + debugfs_remove(dfile_print_limit_option); +print_limit_option_err: + debugfs_remove(dfile_debug_level_option); +debug_level_option_err: + debugfs_remove(dfile_logging_option); +logging_option_err: + debugfs_remove(dfile_info); +info_err: + debugfs_remove(dent); +} + +static void sps_debugfs_exit(void) +{ + if (dfile_info) + debugfs_remove(dfile_info); + if (dfile_logging_option) + debugfs_remove(dfile_logging_option); + if (dfile_debug_level_option) + debugfs_remove(dfile_debug_level_option); + if (dfile_print_limit_option) + debugfs_remove(dfile_print_limit_option); + if (dfile_reg_dump_option) + debugfs_remove(dfile_reg_dump_option); + if (dfile_bam_addr) + debugfs_remove(dfile_bam_addr); + 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_DBG2("sps:%s.", __func__); + + success = false; + + result = sps_mem_init(sps->pipemem_phys_base, sps->pipemem_size); + if (result) { + SPS_ERR("sps: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("sps:Fail to init SPS resource manager"); + goto exit_err; + } + + result = sps_bam_driver_init(sps->options); + if (result) { + SPS_ERR("sps: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:Fail to IO map BAM-DMA BAM registers.\n"); + goto exit_err; + } + + SPS_DBG2("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:Fail to IO map BAM-DMA peripheral reg.\n"); + goto exit_err; + } + + SPS_DBG2("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: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: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_DBG2("sps:%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: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) +{ + SPS_DBG("sps:%s.", __func__); + + 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) +{ + SPS_DBG("sps:%s.", __func__); + + if (client->client_state != SPS_STATE_DISCONNECT) { + SPS_ERR("sps: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; + + SPS_DBG("sps:%s.", __func__); + + list_for_each_entry(bam, &sps->bams_q, list) { + if (bam->props.phys_addr == phys_addr) + return bam; + } + + return NULL; +} + +/** + * Find the handle of a BAM device based on the physical address + * + * This function finds a BAM device in the BAM registration list that + * matches the specified physical address, and returns its handle. + * + * @phys_addr - physical address of the BAM + * + * @h - device handle of the BAM + * + * @return 0 on success, negative value on error + * + */ +int sps_phy2h(u32 phys_addr, u32 *handle) +{ + struct sps_bam *bam; + + SPS_DBG("sps:%s.", __func__); + + if (handle == NULL) { + SPS_ERR("sps:%s:handle is NULL.\n", __func__); + return SPS_ERROR; + } + + list_for_each_entry(bam, &sps->bams_q, list) { + if (bam->props.phys_addr == phys_addr) { + *handle = (u32) bam; + return 0; + } + } + + SPS_ERR("sps: BAM device 0x%x is not registered yet.\n", phys_addr); + + return -ENODEV; +} +EXPORT_SYMBOL(sps_phy2h); + +/** + * Setup desc/data FIFO for bam-to-bam connection + * + * @mem_buffer - Pointer to struct for allocated memory properties. + * + * @addr - address of FIFO + * + * @size - FIFO size + * + * @use_offset - use address offset instead of absolute address + * + * @return 0 on success, negative value on error + * + */ +int sps_setup_bam2bam_fifo(struct sps_mem_buffer *mem_buffer, + u32 addr, u32 size, int use_offset) +{ + SPS_DBG("sps:%s.", __func__); + + if ((mem_buffer == NULL) || (size == 0)) { + SPS_ERR("sps:invalid buffer address or size."); + return SPS_ERROR; + } + + if (use_offset) { + if ((addr + size) <= sps->pipemem_size) + mem_buffer->phys_base = sps->pipemem_phys_base + addr; + else { + SPS_ERR("sps:requested mem is out of " + "pipe mem range.\n"); + return SPS_ERROR; + } + } else { + if (addr >= sps->pipemem_phys_base && + (addr + size) <= (sps->pipemem_phys_base + + sps->pipemem_size)) + mem_buffer->phys_base = addr; + else { + SPS_ERR("sps:requested mem is out of " + "pipe mem range.\n"); + return SPS_ERROR; + } + } + + mem_buffer->base = spsi_get_mem_ptr(mem_buffer->phys_base); + mem_buffer->size = size; + + memset(mem_buffer->base, 0, mem_buffer->size); + + return 0; +} +EXPORT_SYMBOL(sps_setup_bam2bam_fifo); + +/** + * 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; + + SPS_DBG("sps:%s.", __func__); + + 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("sps:Can't find BAM device for handle 0x%x.", h); + + return NULL; +} + +/** + * Lock BAM device + * + * This function obtains the BAM spinlock 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("sps:Connection is not in connected state."); + return NULL; + } + + spin_lock_irqsave(&bam->connection_lock, bam->irqsave_flags); + + /* Verify client owns this pipe */ + pipe_index = pipe->pipe_index; + if (pipe_index >= bam->props.num_pipes || + pipe != bam->pipes[pipe_index]) { + SPS_ERR("sps:Client not owner of BAM 0x%x pipe: %d (max %d)", + bam->props.phys_addr, pipe_index, + bam->props.num_pipes); + spin_unlock_irqrestore(&bam->connection_lock, + bam->irqsave_flags); + return NULL; + } + + return bam; +} + +/** + * Unlock BAM device + * + * This function releases the BAM spinlock on the client's connection. + * + * @bam - pointer to BAM device struct + * + */ +static inline void sps_bam_unlock(struct sps_bam *bam) +{ + spin_unlock_irqrestore(&bam->connection_lock, bam->irqsave_flags); +} + +/** + * 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; + + SPS_DBG2("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (connect == NULL) { + SPS_ERR("sps:%s:connection is NULL.\n", __func__); + return SPS_ERROR; + } + + if (sps == NULL) + return -ENODEV; + + if (!sps->is_ready) { + SPS_ERR("sps:sps_connect:sps driver is not ready.\n"); + return -EAGAIN; + } + + if ((connect->lock_group != SPSRM_CLEAR) + && (connect->lock_group > BAM_MAX_P_LOCK_GROUP_NUM)) { + SPS_ERR("sps:The value of pipe lock group is invalid.\n"); + return SPS_ERROR; + } + + 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("sps:Invalid BAM device handle: 0x%x", dev); + result = SPS_ERROR; + goto exit_err; + } + + SPS_DBG2("sps: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; + + SPS_DBG2("sps:%s.", __func__); + + if (pipe == NULL) { + SPS_ERR("sps:Invalid pipe."); + return SPS_ERROR; + } + + bam = pipe->bam; + if (bam == NULL) { + SPS_ERR("sps:BAM device of this pipe is NULL."); + return SPS_ERROR; + } + + SPS_DBG2("sps: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("sps:Client context is corrupt"); + goto exit_err; + } + + /* Disconnect the BAM pipe */ + mutex_lock(&bam->lock); + result = sps_rm_state_change(pipe, SPS_STATE_DISCONNECT); + mutex_unlock(&bam->lock); + if (result) + goto exit_err; + + sps_rm_config_init(&pipe->connect); + result = 0; + +exit_err: + + 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_DBG2("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (reg == NULL) { + SPS_ERR("sps:%s:registered event is NULL.\n", __func__); + return SPS_ERROR; + } + + if (sps == NULL) + return -ENODEV; + + if (!sps->is_ready) { + SPS_ERR("sps: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("sps:Fail 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_DBG2("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } + + 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_DBG2("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } + + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (transfer == NULL) { + SPS_ERR("sps:%s:transfer is NULL.\n", __func__); + return SPS_ERROR; + } + + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } + + if ((flags & SPS_IOVEC_FLAG_NWD) && + !(flags & (SPS_IOVEC_FLAG_EOT | SPS_IOVEC_FLAG_CMD))) { + SPS_ERR("sps:NWD is only valid with EOT or CMD.\n"); + return SPS_ERROR; + } else if ((flags & SPS_IOVEC_FLAG_EOT) && + (flags & SPS_IOVEC_FLAG_CMD)) { + SPS_ERR("sps:EOT and CMD are not allowed to coexist.\n"); + return SPS_ERROR; + } else if (!(flags & SPS_IOVEC_FLAG_CMD) && + (flags & (SPS_IOVEC_FLAG_LOCK | SPS_IOVEC_FLAG_UNLOCK))) { + SPS_ERR("sps:pipe lock and unlock flags are only valid with " + "Command Descriptor.\n"); + return SPS_ERROR; + } else if ((flags & SPS_IOVEC_FLAG_LOCK) && + (flags & SPS_IOVEC_FLAG_UNLOCK)) { + SPS_ERR("sps:Can't lock and unlock a pipe by the same" + "Command Descriptor.\n"); + return SPS_ERROR; + } else if ((flags & SPS_IOVEC_FLAG_IMME) && + (flags & SPS_IOVEC_FLAG_CMD)) { + SPS_ERR("sps:Immediate and CMD are not allowed to coexist.\n"); + return SPS_ERROR; + } else if ((flags & SPS_IOVEC_FLAG_IMME) && + (flags & SPS_IOVEC_FLAG_NWD)) { + SPS_ERR("sps:Immediate and NWD are not allowed to coexist.\n"); + return SPS_ERROR; + } + + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (notify == NULL) { + SPS_ERR("sps:%s:event_notify is NULL.\n", __func__); + return SPS_ERROR; + } + + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (empty == NULL) { + SPS_ERR("sps:%s:result pointer is NULL.\n", __func__); + return SPS_ERROR; + } + + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (count == NULL) { + SPS_ERR("sps:%s:result pointer is NULL.\n", __func__); + return SPS_ERROR; + } + + 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_DBG2("sps:%s: dev = 0x%x", __func__, dev); + + if (dev == 0) { + SPS_ERR("sps:%s:device handle should not be 0.\n", __func__); + return SPS_ERROR; + } + + mutex_lock(&sps->lock); + /* Search for the target BAM device */ + bam = sps_h2bam(dev); + if (bam == NULL) { + SPS_ERR("sps: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("sps:Fail 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; + + SPS_DBG("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (config == NULL) { + SPS_ERR("sps:%s:config pointer is NULL.\n", __func__); + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (config == NULL) { + SPS_ERR("sps:%s:config pointer is NULL.\n", __func__); + return SPS_ERROR; + } + + 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; + + SPS_DBG("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (connect == NULL) { + SPS_ERR("sps:%s:connection is NULL.\n", __func__); + return SPS_ERROR; + } + + if (owner != SPS_OWNER_REMOTE) { + SPS_ERR("sps: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) +{ + SPS_DBG("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } + + if (sps == NULL) + return -ENODEV; + + if (!sps->is_ready) { + SPS_ERR("sps:sps_alloc_mem:sps driver is not ready."); + return -EAGAIN; + } + + if (mem_buffer == NULL || mem_buffer->size == 0) { + SPS_ERR("sps:invalid memory buffer address or size"); + return SPS_ERROR; + } + + mem_buffer->phys_base = sps_mem_alloc_io(mem_buffer->size); + if (mem_buffer->phys_base == SPS_ADDR_INVALID) { + SPS_ERR("sps:invalid address of allocated memory"); + 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) +{ + SPS_DBG("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } + + if (mem_buffer == NULL || mem_buffer->phys_base == SPS_ADDR_INVALID) { + SPS_ERR("sps:invalid memory to free"); + return SPS_ERROR; + } + + sps_mem_free_io(mem_buffer->phys_base, mem_buffer->size); + + return 0; +} +EXPORT_SYMBOL(sps_free_mem); + +/** + * Get the number of unused descriptors in the descriptor FIFO + * of a pipe + * + */ +int sps_get_unused_desc_num(struct sps_pipe *h, u32 *desc_num) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (desc_num == NULL) { + SPS_ERR("sps:%s:result pointer is NULL.\n", __func__); + return SPS_ERROR; + } + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_get_unused_desc_num(bam, pipe->pipe_index, + desc_num); + + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_get_unused_desc_num); + +/** + * 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; + + SPS_DBG2("sps:%s.", __func__); + + if (bam_props == NULL) { + SPS_ERR("sps:%s:bam_props is NULL.\n", __func__); + return SPS_ERROR; + } else if (dev_handle == NULL) { + SPS_ERR("sps:%s:device handle is NULL.\n", __func__); + return SPS_ERROR; + } + + 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:sps_register_bam_device:sps driver not ready.\n"); + return -EAGAIN; + } + + /* 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("sps: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("sps: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("sps:BAM is 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("sps:Unable to map BAM IO mem:0x%x size:0x%x", + bam_props->phys_addr, bam_props->virt_size); + goto exit_err; + } + } + + bam = kzalloc(sizeof(*bam), GFP_KERNEL); + if (bam == NULL) { + SPS_ERR("sps: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_DBG2("sps: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("sps:Fail 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 (bam != NULL) { + if (virt_addr != NULL) + iounmap(bam->props.virt_addr); + 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("sps:Fail to init BAM-DMA BAM: phys 0x%0x", + bam->props.phys_addr); + return SPS_ERROR; + } + } +#endif /* CONFIG_SPS_SUPPORT_BAMDMA */ + + SPS_INFO("sps:BAM 0x%x is registered.", 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; + + SPS_DBG2("sps:%s.", __func__); + + if (dev_handle == 0) { + SPS_ERR("sps:%s:device handle should not be 0.\n", __func__); + return SPS_ERROR; + } + + bam = sps_h2bam(dev_handle); + if (bam == NULL) { + SPS_ERR("sps:did not find a BAM for this handle"); + return SPS_ERROR; + } + + SPS_DBG2("sps: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; + + SPS_DBG("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (iovec == NULL) { + SPS_ERR("sps:%s:iovec pointer is NULL.\n", __func__); + return SPS_ERROR; + } + + 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("sps:%s.", __func__); + + if (h == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } else if (timer_ctrl == NULL) { + SPS_ERR("sps:%s:timer_ctrl pointer is NULL.\n", __func__); + return SPS_ERROR; + } else if (timer_result == NULL) { + SPS_ERR("sps:%s:result pointer is NULL.\n", __func__); + 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; + + SPS_DBG("sps:%s.", __func__); + + ctx = kzalloc(sizeof(struct sps_pipe), GFP_KERNEL); + if (ctx == NULL) { + SPS_ERR("sps:Fail to allocate pipe context."); + 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; + + SPS_DBG("sps:%s.", __func__); + + if (ctx == NULL) { + SPS_ERR("sps:%s:pipe is NULL.\n", __func__); + return SPS_ERROR; + } + + 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; + + SPS_DBG("sps:%s.", __func__); + + 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; +} + +/** + * Read data from device tree + */ +static int get_device_tree_data(struct platform_device *pdev) +{ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + struct resource *resource; + + SPS_DBG("sps:%s.", __func__); + + if (of_property_read_u32((&pdev->dev)->of_node, + "qcom,bam-dma-res-pipes", + &sps->bamdma_restricted_pipes)) { + SPS_ERR("sps:Fail to get restricted bamdma pipes.\n"); + return -EINVAL; + } else + SPS_DBG("sps:bamdma_restricted_pipes=0x%x.", + sps->bamdma_restricted_pipes); + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + 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); + } else { + SPS_ERR("sps:BAM DMA BAM mem unavailable."); + return -ENODEV; + } + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); + 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); + } else { + SPS_ERR("sps:BAM DMA mem unavailable."); + return -ENODEV; + } + + resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (resource) { + sps->bamdma_irq = resource->start; + SPS_DBG("sps:bamdma_irq=%d.", sps->bamdma_irq); + } else { + SPS_ERR("sps:BAM DMA IRQ unavailable."); + return -ENODEV; + } +#endif + + return 0; +} + +static int __devinit msm_sps_probe(struct platform_device *pdev) +{ + int ret; + + SPS_DBG2("sps:%s.", __func__); + + if (pdev->dev.of_node) { + if (get_device_tree_data(pdev)) { + SPS_ERR("sps:Fail to get data from device tree."); + return -ENODEV; + } else + SPS_DBG("sps:get data from device tree."); + } else { + if (get_platform_data(pdev)) { + SPS_ERR("sps:Fail to get platform data."); + return -ENODEV; + } else + SPS_DBG("sps:get platform data."); + } + + /* 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_set_rate(sps->dfab_clk, 64000000); + if (ret) { + SPS_ERR("sps:failed to set dfab_clk rate. " + "ret=%d", ret); + clk_put(sps->dfab_clk); + goto clk_err; + } + } + + sps->pmem_clk = clk_get(sps->dev, "mem_clk"); + if (IS_ERR(sps->pmem_clk)) { + SPS_ERR("sps:fail to get pmem_clk."); + goto clk_err; + } else { + ret = clk_prepare_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_prepare_enable(sps->bamdma_clk); + if (ret) { + SPS_ERR("sps:failed to enable bamdma_clk. ret=%d", ret); + goto clk_err; + } + } + + ret = clk_prepare_enable(sps->dfab_clk); + if (ret) { + SPS_ERR("sps:failed to enable dfab_clk. ret=%d", ret); + goto clk_err; + } +#endif + ret = sps_device_init(); + if (ret) { + SPS_ERR("sps:sps_device_init err."); +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + clk_disable_unprepare(sps->dfab_clk); +#endif + goto sps_device_init_err; + } +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + clk_disable_unprepare(sps->dfab_clk); +#endif + sps->is_ready = true; + + SPS_INFO("sps: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("sps:%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 of_device_id msm_sps_match[] = { + { .compatible = "qcom,msm_sps", + }, + {} +}; + +static struct platform_driver msm_sps_driver = { + .probe = msm_sps_probe, + .driver = { + .name = SPS_DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = msm_sps_match, + }, + .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("sps:%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("sps:%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 0000000000000000000000000000000000000000..245ccd2a5181cbc7dc534b07368fb378e12050a8 --- /dev/null +++ b/drivers/platform/msm/sps/sps_bam.c @@ -0,0 +1,2007 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#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 | \ + SPS_IOVEC_FLAG_NWD | SPS_IOVEC_FLAG_CMD | SPS_IOVEC_FLAG_LOCK | \ + SPS_IOVEC_FLAG_UNLOCK | SPS_IOVEC_FLAG_IMME) + +/* 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: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; + enum sps_callback_case cb_case; + source = bam_check_irq_source(dev->base, dev->props.ee, + mask, &cb_case); + + SPS_DBG1("sps:bam_isr:bam=0x%x;source=0x%x;mask=0x%x.", + BAM_ID(dev), source, mask); + + if ((source & (1UL << 31)) && (dev->props.callback)) { + SPS_INFO("sps:bam_isr:bam=0x%x;callback for case %d.", + BAM_ID(dev), cb_case); + dev->props.callback(cb_case, dev->props.user); + } + + /* 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; + + SPS_DBG1("sps:bam_isr for MTI:bam=0x%x;source=0x%x.", + BAM_ID(dev), source); + } + + /* 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("sps: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("sps: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("sps:Failed to enable 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; + + /* Register BAM IRQ for apps wakeup */ + if (dev->props.options & SPS_BAM_OPT_IRQ_WAKEUP) { + result = enable_irq_wake(dev->props.irq); + + if (result) { + SPS_ERR("sps:Fail to enable wakeup irq " + "BAM 0x%x IRQ %d", + BAM_ID(dev), dev->props.irq); + return SPS_ERROR; + } else + SPS_DBG2("sps:Enable wakeup irq for " + "BAM 0x%x IRQ %d", + BAM_ID(dev), dev->props.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("sps:Fail 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("sps: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_DBG1("sps: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("sps: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("sps: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("sps: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_DBG2("sps: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("sps: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_DBG2("sps: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("sps: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_DBG2("sps: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); + + spin_lock_init(&dev->connection_lock); + + if ((dev->props.options & SPS_BAM_OPT_ENABLE_AT_BOOT)) + if (sps_bam_enable(dev)) { + SPS_ERR("sps:Fail to enable bam device"); + return SPS_ERROR; + } + + SPS_DBG2("sps: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_DBG2("sps: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_DBG2("sps: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("sps: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("sps: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("sps:Fail 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("sps: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("sps: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("sps: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("sps: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("sps: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("sps:Disconnect BAM 0x%x pipe %d with events pending", + BAM_ID(dev), pipe_index); + + sps_event = list_entry((&pipe->sys.events_q)->next, + typeof(*sps_event), list); + + while (&sps_event->list != (&pipe->sys.events_q)) { + struct sps_q_event *sps_event_delete = sps_event; + + list_del(&sps_event->list); + sps_event = list_entry(sps_event->list.next, + typeof(*sps_event), list); + kfree(sps_event_delete); + } + } + + /* 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("sps: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; + hw_params.lock_group = map_pipe->lock_group; + + /* 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("sps: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("sps: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("sps: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); + + /* set NWD bit for BAM2BAM producer pipe */ + if (bam_pipe->mode == SPS_MODE_SRC) { + if ((params->options & SPS_O_WRITE_NWD) == 0) + hw_params.write_nwd = BAM_WRITE_NWD_DISABLE; + else + hw_params.write_nwd = BAM_WRITE_NWD_ENABLE; + } + } 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("sps: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("sps:Invalid pipe %d on BAM 0x%x for connect", + pipe_index, BAM_ID(dev)); + return SPS_ERROR; + } + + if (bam_pipe_is_enabled(dev->base, pipe_index)) { + SPS_ERR("sps:BAM 0x%x pipe %d sharing violation", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + if (bam_pipe_init(dev->base, pipe_index, &hw_params, dev->props.ee)) { + SPS_ERR("sps: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("sps: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) { + u32 size = pipe->num_descs * sizeof(void *); + if (pipe->desc_size + size <= PAGE_SIZE) + kfree(pipe->sys.desc_cache); + else + vfree(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("sps: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_DBG2("sps: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("sps: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("sps: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 *); + + if (pipe->desc_size + size <= PAGE_SIZE) + pipe->sys.desc_cache = + kzalloc(pipe->desc_size + size, GFP_KERNEL); + else { + pipe->sys.desc_cache = + vmalloc(pipe->desc_size + size); + memset(pipe->sys.desc_cache, 0, pipe->desc_size + size); + } + + if (pipe->sys.desc_cache == NULL) { + /*** MUST BE LAST POINT OF FAILURE (see below) *****/ + SPS_ERR("sps: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("sps: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); + if (index < 0) + SPS_ERR("sps:Negative event index: " + "BAM 0x%x pipe %d mode %d", + BAM_ID(dev), pipe_index, reg->mode); + else { + 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("sps: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("sps: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_DBG2("sps:Descriptor FIFO is full for BAM " + "0x%x pipe %d after pipe_handler_eot", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + } else { + SPS_DBG2("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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; +} + +/** + * Get the number of unused descriptors in the descriptor FIFO + * of a pipe + */ +int sps_bam_pipe_get_unused_desc_num(struct sps_bam *dev, u32 pipe_index, + u32 *desc_num) +{ + u32 sw_offset, peer_offset, fifo_size; + u32 desc_size = sizeof(struct sps_iovec); + struct sps_pipe *pipe = dev->pipes[pipe_index]; + + if (pipe == NULL) + return SPS_ERROR; + + fifo_size = pipe->desc_size; + + sw_offset = bam_pipe_get_desc_read_offset(dev->base, pipe_index); + peer_offset = bam_pipe_get_desc_write_offset(dev->base, pipe_index); + + if (sw_offset <= peer_offset) + *desc_num = (peer_offset - sw_offset) / desc_size; + else + *desc_num = (peer_offset + fifo_size - sw_offset) / desc_size; + + return 0; +} diff --git a/drivers/platform/msm/sps/sps_bam.h b/drivers/platform/msm/sps/sps_bam.h new file mode 100644 index 0000000000000000000000000000000000000000..6004b75e0a98c2afa3e29679f9b2a1550fce82e5 --- /dev/null +++ b/drivers/platform/msm/sps/sps_bam.h @@ -0,0 +1,564 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_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; + spinlock_t connection_lock; + unsigned long irqsave_flags; + + /* 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); + + +/** + * Get the number of unused descriptors in the descriptor FIFO + * of a pipe + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @desc_num - number of unused descriptors + * + */ +int sps_bam_pipe_get_unused_desc_num(struct sps_bam *dev, u32 pipe_index, + u32 *desc_num); + +#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 0000000000000000000000000000000000000000..5bd7c655f27e7a7083b0525198043332d567a335 --- /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 0000000000000000000000000000000000000000..f8b4f51db20f3765b56d9f1c14000b84c958278f --- /dev/null +++ b/drivers/platform/msm/sps/sps_dma.c @@ -0,0 +1,917 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#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) +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM +#define DMA_REVISION (0x00000004) +#define DMA_CONFIG (0x00000008) +#define DMA_CHNL_CONFIG(n) (0x00001000 + 4096 * (n)) +#else +#define DMA_CHNL_CONFIG(n) (0x00000004 + 4 * (n)) +#define DMA_CONFIG (0x00000040) +#endif + +/** + * masks + */ + +/* DMA_CHNL_confign */ +#ifdef CONFIG_SPS_SUPPORT_NDP_BAM +#define DMA_CHNL_PRODUCER_PIPE_ENABLED 0x40000 +#define DMA_CHNL_CONSUMER_PIPE_ENABLED 0x20000 +#endif +#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("sps: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("sps: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("sps: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("sps:Fail 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("sps:Fail to disable BAM-DMA BAM:%x", 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("sps:BAM-DMA BAM device is 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); + + if (dev->bam == NULL) { + SPS_ERR("sps:BAM-DMA BAM device is not found " + "from the handle."); + goto exit_err; + } + + /* 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("sps: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("sps: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_DBG2("sps:BAM-DMA is controlled locally: %x", + dev->phys_addr); + dev->local = true; + } else { + SPS_DBG2("sps: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("sps: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("sps: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("sps: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("sps: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("sps:Fail 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("sps:BAM-DMA: BAM limit exceeded: %d", + num_bams); + return SPS_ERROR; + } + } else { + SPS_ERR("sps: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: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("sps: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("sps: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("sps: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("sps: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_DBG2("sps: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: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("sps: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:sps_free_dma_chan. Invalid pipe indices." + "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("sps: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("sps: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("sps: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("sps:BAM-DMA: invalid BAM: %x", + bam->props.phys_addr); + goto exit_err; + } + if (pipe_index >= dev->num_pipes) { + SPS_ERR("sps: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("sps: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_DBG2("sps: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("sps:BAM-DMA: invalid BAM"); + goto exit_err; + } + if (pipe_index >= dev->num_pipes) { + SPS_ERR("sps: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("sps: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_DBG2("sps: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("sps: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 0000000000000000000000000000000000000000..d98a8b1483b640b897ac3d172c22aba82eceafc4 --- /dev/null +++ b/drivers/platform/msm/sps/sps_map.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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: %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("sps: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("sps:Can't 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 0000000000000000000000000000000000000000..692e47cd1f3a5388b5131c8d6ce426bfbb2a5207 --- /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 0000000000000000000000000000000000000000..1b19b1262ed162ea7b437dc4aa899b273820a72e --- /dev/null +++ b/drivers/platform/msm/sps/sps_mem.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_DBG2("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_DBG2("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) +{ +#ifndef CONFIG_SPS_SUPPORT_NDP_BAM + int res; +#endif + /* 2^8=128. The desc-fifo and data-fifo minimal allocation. */ + int min_alloc_order = 8; + +#ifndef CONFIG_SPS_SUPPORT_NDP_BAM + 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); +#endif + + pool = gen_pool_create(min_alloc_order, nid); + + if (!pool) { + SPS_ERR("sps:Failed to create a new memory pool.\n"); + return -ENOMEM; + } + +#ifndef CONFIG_SPS_SUPPORT_NDP_BAM + res = gen_pool_add(pool, (u32) iomem_virt, iomem_size, nid); + if (res) + return res; +#endif + + 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 0000000000000000000000000000000000000000..c980eb00b9d5e919cc411faa61e400e8a7b451f1 --- /dev/null +++ b/drivers/platform/msm/sps/sps_rm.c @@ -0,0 +1,817 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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" + +/* 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("sps: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)) { + SPS_ERR("sps:The end point is already connected.\n"); + 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)) { + SPS_ERR("sps:The end point is empty.\n"); + 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; + if (pipe->connect.lock_group != SPSRM_CLEAR) + map->src.lock_group = pipe->connect.lock_group; + } 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; + if (pipe->connect.lock_group != SPSRM_CLEAR) + map->dest.lock_group = pipe->connect.lock_group; + } + 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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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("sps: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)) { + SPS_ERR("sps:failed to assign a connection to the client.\n"); + 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("sps:Failed to find connection mapping"); + return SPS_ERROR; + } + } + + mutex_lock(&sps_rm->lock); + /* Check client state */ + if (IS_SPS_STATE_OK(pipe)) { + SPS_ERR("sps: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("sps: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)) { + SPS_ERR("sps:Fail to allocate resource for" + " BAM 0x%x pipe %d", + (u32) pipe->bam, pipe->pipe_index); + 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("sps: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("sps: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("sps: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("sps: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("sps: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 0000000000000000000000000000000000000000..e8ab832d7202c044bdd998ab8eaf40307d7218ef --- /dev/null +++ b/drivers/platform/msm/sps/spsi.h @@ -0,0 +1,396 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#include + +#include "sps_map.h" + +#define BAM_MAX_PIPES 31 +#define BAM_MAX_P_LOCK_GROUP_NUM 31 + +/* 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) + +/* "Clear" value for the connection parameter struct */ +#define SPSRM_CLEAR 0xcccccccc + +#ifdef CONFIG_DEBUG_FS +extern u8 debugfs_record_enabled; +extern u8 logging_option; +extern u8 debug_level_option; +extern u8 print_limit_option; + +#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 { \ + if (unlikely(print_limit_option > 2)) \ + pr_err_ratelimited(msg, ##args); \ + else \ + pr_err(msg, ##args); \ + if (unlikely(debugfs_record_enabled)) \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_INFO(msg, args...) do { \ + if (unlikely(print_limit_option > 1)) \ + pr_info_ratelimited(msg, ##args); \ + else \ + pr_info(msg, ##args); \ + if (unlikely(debugfs_record_enabled)) \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_DBG(msg, args...) do { \ + if ((unlikely(logging_option > 1)) \ + && (unlikely(debug_level_option > 3))) {\ + if (unlikely(print_limit_option > 0)) \ + pr_info_ratelimited(msg, ##args); \ + else \ + pr_info(msg, ##args); \ + } else \ + pr_debug(msg, ##args); \ + if (unlikely(debugfs_record_enabled)) \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_DBG1(msg, args...) do { \ + if ((unlikely(logging_option > 1)) \ + && (unlikely(debug_level_option > 2))) {\ + if (unlikely(print_limit_option > 0)) \ + pr_info_ratelimited(msg, ##args); \ + else \ + pr_info(msg, ##args); \ + } else \ + pr_debug(msg, ##args); \ + if (unlikely(debugfs_record_enabled)) \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_DBG2(msg, args...) do { \ + if ((unlikely(logging_option > 1)) \ + && (unlikely(debug_level_option > 1))) {\ + if (unlikely(print_limit_option > 0)) \ + pr_info_ratelimited(msg, ##args); \ + else \ + pr_info(msg, ##args); \ + } else \ + pr_debug(msg, ##args); \ + if (unlikely(debugfs_record_enabled)) \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_DBG3(msg, args...) do { \ + if ((unlikely(logging_option > 1)) \ + && (unlikely(debug_level_option > 0))) {\ + if (unlikely(print_limit_option > 0)) \ + pr_info_ratelimited(msg, ##args); \ + else \ + pr_info(msg, ##args); \ + } else \ + pr_debug(msg, ##args); \ + if (unlikely(debugfs_record_enabled)) \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#else +#define SPS_DBG3(x...) pr_debug(x) +#define SPS_DBG2(x...) pr_debug(x) +#define SPS_DBG1(x...) pr_debug(x) +#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 */ + u32 lock_group; /* The lock group this pipe belongs to */ + 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 *); + +/* output the content of BAM-level registers */ +void print_bam_reg(void *); + +/* output the content of BAM pipe registers */ +void print_bam_pipe_reg(void *, u32); + +/* output the content of selected BAM-level registers */ +void print_bam_selected_reg(void *); + +/* output the content of selected BAM pipe registers */ +void print_bam_pipe_selected_reg(void *, u32); + +/* output descriptor FIFO of a pipe */ +void print_bam_pipe_desc_fifo(void *, u32); +#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 0000000000000000000000000000000000000000..265d56f32c41a40acb10593de5afe80765c92b40 --- /dev/null +++ b/drivers/platform/msm/ssbi.c @@ -0,0 +1,441 @@ +/* Copyright (c) 2009-2012, 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 +#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 + +/* 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)) + +struct msm_ssbi { + struct device *dev; + struct device *slave; + void __iomem *base; + bool use_rlock; + spinlock_t lock; + remote_spinlock_t rspin_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); + } + + if (ssbi->controller_type == FSM_SBI_CTRL_SSBI) + cmd = SSBI_FSM_CMD_READ(addr); + else + cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); + + 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; + + if (ssbi->controller_type == FSM_SBI_CTRL_SSBI) + ssbi_writel(ssbi, SSBI_FSM_CMD_WRITE(addr, *buf), + SSBI2_CMD); + else + 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; + + if (ssbi->use_rlock) { + remote_spin_lock_irqsave(&ssbi->rspin_lock, flags); + ret = ssbi->read(ssbi, addr, buf, len); + remote_spin_unlock_irqrestore(&ssbi->rspin_lock, flags); + } else { + 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; + + if (ssbi->use_rlock) { + remote_spin_lock_irqsave(&ssbi->rspin_lock, flags); + ret = ssbi->write(ssbi, addr, buf, len); + remote_spin_unlock_irqrestore(&ssbi->rspin_lock, flags); + } else { + 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; + } + + 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_ssbi_add_slave; + } + ssbi->use_rlock = 1; + } + + 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/platform/msm/usb_bam.c b/drivers/platform/msm/usb_bam.c new file mode 100644 index 0000000000000000000000000000000000000000..1df6d3418116b1570ded8367ec23a24ee0b220f9 --- /dev/null +++ b/drivers/platform/msm/usb_bam.c @@ -0,0 +1,394 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 USB_SUMMING_THRESHOLD 512 +#define CONNECTIONS_NUM 4 + +static struct sps_bam_props usb_props; +static struct sps_pipe *sps_pipes[CONNECTIONS_NUM][2]; +static struct sps_connect sps_connections[CONNECTIONS_NUM][2]; +static struct sps_mem_buffer data_mem_buf[CONNECTIONS_NUM][2]; +static struct sps_mem_buffer desc_mem_buf[CONNECTIONS_NUM][2]; +static struct platform_device *usb_bam_pdev; +static struct workqueue_struct *usb_bam_wq; + +struct usb_bam_wake_event_info { + struct sps_register_event event; + int (*callback)(void *); + void *param; + struct work_struct wake_w; +}; + +struct usb_bam_connect_info { + u8 idx; + u8 *src_pipe; + u8 *dst_pipe; + struct usb_bam_wake_event_info peer_event; + bool enabled; +}; + +static struct usb_bam_connect_info usb_bam_connections[CONNECTIONS_NUM]; + +static inline int bam_offset(struct msm_usb_bam_platform_data *pdata) +{ + return pdata->usb_active_bam * CONNECTIONS_NUM * 2; +} + +static int connect_pipe(u8 connection_idx, enum usb_bam_pipe_dir pipe_dir, + u8 *usb_pipe_idx) +{ + int ret; + struct sps_pipe **pipe = &sps_pipes[connection_idx][pipe_dir]; + struct sps_connect *connection = + &sps_connections[connection_idx][pipe_dir]; + struct msm_usb_bam_platform_data *pdata = + (struct msm_usb_bam_platform_data *) + (usb_bam_pdev->dev.platform_data); + struct usb_bam_pipe_connect *pipe_connection = + (struct usb_bam_pipe_connect *)(pdata->connections + + bam_offset(pdata) + (2*connection_idx+pipe_dir)); + + *pipe = sps_alloc_endpoint(); + if (*pipe == NULL) { + pr_err("%s: sps_alloc_endpoint failed\n", __func__); + return -ENOMEM; + } + + ret = sps_get_config(*pipe, connection); + if (ret) { + pr_err("%s: tx get config failed %d\n", __func__, ret); + goto get_config_failed; + } + + ret = sps_phy2h(pipe_connection->src_phy_addr, &(connection->source)); + if (ret) { + pr_err("%s: sps_phy2h failed (src BAM) %d\n", __func__, ret); + goto get_config_failed; + } + + connection->src_pipe_index = pipe_connection->src_pipe_index; + ret = sps_phy2h(pipe_connection->dst_phy_addr, + &(connection->destination)); + if (ret) { + pr_err("%s: sps_phy2h failed (dst BAM) %d\n", __func__, ret); + goto get_config_failed; + } + connection->dest_pipe_index = pipe_connection->dst_pipe_index; + + if (pipe_dir == USB_TO_PEER_PERIPHERAL) { + connection->mode = SPS_MODE_SRC; + *usb_pipe_idx = connection->src_pipe_index; + } else { + connection->mode = SPS_MODE_DEST; + *usb_pipe_idx = connection->dest_pipe_index; + } + + ret = sps_setup_bam2bam_fifo( + &data_mem_buf[connection_idx][pipe_dir], + pipe_connection->data_fifo_base_offset, + pipe_connection->data_fifo_size, 1); + if (ret) { + pr_err("%s: data fifo setup failure %d\n", __func__, ret); + goto fifo_setup_error; + } + connection->data = data_mem_buf[connection_idx][pipe_dir]; + + ret = sps_setup_bam2bam_fifo( + &desc_mem_buf[connection_idx][pipe_dir], + pipe_connection->desc_fifo_base_offset, + pipe_connection->desc_fifo_size, 1); + if (ret) { + pr_err("%s: desc. fifo setup failure %d\n", __func__, ret); + goto fifo_setup_error; + } + connection->desc = desc_mem_buf[connection_idx][pipe_dir]; + connection->event_thresh = 512; + + ret = sps_connect(*pipe, connection); + if (ret < 0) { + pr_err("%s: tx connect error %d\n", __func__, ret); + goto error; + } + return 0; + +error: + sps_disconnect(*pipe); +fifo_setup_error: +get_config_failed: + sps_free_endpoint(*pipe); + return ret; +} + +int usb_bam_connect(u8 idx, u8 *src_pipe_idx, u8 *dst_pipe_idx) +{ + struct usb_bam_connect_info *connection = &usb_bam_connections[idx]; + int ret; + + if (idx >= CONNECTIONS_NUM) { + pr_err("%s: Invalid connection index\n", + __func__); + return -EINVAL; + } + + if (connection->enabled) { + pr_info("%s: connection %d was already established\n", + __func__, idx); + return 0; + } + connection->src_pipe = src_pipe_idx; + connection->dst_pipe = dst_pipe_idx; + connection->idx = idx; + + /* open USB -> Peripheral pipe */ + ret = connect_pipe(connection->idx, USB_TO_PEER_PERIPHERAL, + connection->src_pipe); + if (ret) { + pr_err("%s: src pipe connection failure\n", __func__); + return ret; + } + /* open Peripheral -> USB pipe */ + ret = connect_pipe(connection->idx, PEER_PERIPHERAL_TO_USB, + connection->dst_pipe); + if (ret) { + pr_err("%s: dst pipe connection failure\n", __func__); + return ret; + } + connection->enabled = 1; + + return 0; +} + +static void usb_bam_wake_work(struct work_struct *w) +{ + struct usb_bam_wake_event_info *wake_event_info = + container_of(w, struct usb_bam_wake_event_info, wake_w); + + wake_event_info->callback(wake_event_info->param); +} + +static void usb_bam_wake_cb(struct sps_event_notify *notify) +{ + struct usb_bam_wake_event_info *wake_event_info = + (struct usb_bam_wake_event_info *)notify->user; + + queue_work(usb_bam_wq, &wake_event_info->wake_w); +} + +int usb_bam_register_wake_cb(u8 idx, + int (*callback)(void *user), void* param) +{ + struct sps_pipe *pipe = sps_pipes[idx][PEER_PERIPHERAL_TO_USB]; + struct sps_connect *sps_connection = + &sps_connections[idx][PEER_PERIPHERAL_TO_USB]; + struct usb_bam_connect_info *connection = &usb_bam_connections[idx]; + struct usb_bam_wake_event_info *wake_event_info = + &connection->peer_event; + int ret; + + wake_event_info->param = param; + wake_event_info->callback = callback; + wake_event_info->event.mode = SPS_TRIGGER_CALLBACK; + wake_event_info->event.xfer_done = NULL; + wake_event_info->event.callback = callback ? usb_bam_wake_cb : NULL; + wake_event_info->event.user = wake_event_info; + wake_event_info->event.options = SPS_O_WAKEUP; + ret = sps_register_event(pipe, &wake_event_info->event); + if (ret) { + pr_err("%s: sps_register_event() failed %d\n", __func__, ret); + return ret; + } + + sps_connection->options = callback ? + (SPS_O_AUTO_ENABLE | SPS_O_WAKEUP | SPS_O_WAKEUP_IS_ONESHOT) : + SPS_O_AUTO_ENABLE; + ret = sps_set_config(pipe, sps_connection); + if (ret) { + pr_err("%s: sps_set_config() failed %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int usb_bam_init(void) +{ + u32 h_usb; + int ret; + void *usb_virt_addr; + struct msm_usb_bam_platform_data *pdata = + (struct msm_usb_bam_platform_data *) + (usb_bam_pdev->dev.platform_data); + struct resource *res; + int irq; + + res = platform_get_resource(usb_bam_pdev, IORESOURCE_MEM, + pdata->usb_active_bam); + if (!res) { + dev_err(&usb_bam_pdev->dev, "Unable to get memory resource\n"); + return -ENODEV; + } + + irq = platform_get_irq(usb_bam_pdev, pdata->usb_active_bam); + if (irq < 0) { + dev_err(&usb_bam_pdev->dev, "Unable to get IRQ resource\n"); + return irq; + } + + usb_virt_addr = ioremap(res->start, resource_size(res)); + if (!usb_virt_addr) { + pr_err("%s: ioremap failed\n", __func__); + return -ENOMEM; + } + usb_props.phys_addr = res->start; + usb_props.virt_addr = usb_virt_addr; + usb_props.virt_size = resource_size(res); + usb_props.irq = irq; + usb_props.summing_threshold = USB_SUMMING_THRESHOLD; + usb_props.num_pipes = pdata->usb_bam_num_pipes; + + ret = sps_register_bam_device(&usb_props, &h_usb); + if (ret < 0) { + pr_err("%s: register bam error %d\n", __func__, ret); + return -EFAULT; + } + + return 0; +} + +static char *bam_enable_strings[2] = { + [HSUSB_BAM] = "hsusb", + [HSIC_BAM] = "hsic", +}; + +static ssize_t +usb_bam_show_enable(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct msm_usb_bam_platform_data *pdata = + (struct msm_usb_bam_platform_data *) + (usb_bam_pdev->dev.platform_data); + + if (!pdev || !pdata) + return 0; + return scnprintf(buf, PAGE_SIZE, "%s\n", + bam_enable_strings[pdata->usb_active_bam]); +} + +static ssize_t usb_bam_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct msm_usb_bam_platform_data *pdata = + (struct msm_usb_bam_platform_data *) + (usb_bam_pdev->dev.platform_data); + char str[10], *pstr; + int ret, i; + + strlcpy(str, buf, sizeof(str)); + pstr = strim(str); + + for (i = 0; i < ARRAY_SIZE(bam_enable_strings); i++) { + if (!strncmp(pstr, bam_enable_strings[i], sizeof(str))) + pdata->usb_active_bam = i; + } + + dev_dbg(&pdev->dev, "active_bam=%s\n", + bam_enable_strings[pdata->usb_active_bam]); + + ret = usb_bam_init(); + if (ret) { + dev_err(&pdev->dev, "failed to initialize usb bam\n"); + return ret; + } + + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUSR, usb_bam_show_enable, + usb_bam_store_enable); + +static int usb_bam_probe(struct platform_device *pdev) +{ + int ret, i; + + dev_dbg(&pdev->dev, "usb_bam_probe\n"); + + for (i = 0; i < CONNECTIONS_NUM; i++) { + usb_bam_connections[i].enabled = 0; + INIT_WORK(&usb_bam_connections[i].peer_event.wake_w, + usb_bam_wake_work); + } + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "missing platform_data\n"); + return -ENODEV; + } + usb_bam_pdev = pdev; + + ret = device_create_file(&pdev->dev, &dev_attr_enable); + if (ret) + dev_err(&pdev->dev, "failed to create device file\n"); + + usb_bam_wq = alloc_workqueue("usb_bam_wq", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!usb_bam_wq) { + pr_err("unable to create workqueue usb_bam_wq\n"); + return -ENOMEM; + } + + return ret; +} + +static int usb_bam_remove(struct platform_device *pdev) +{ + destroy_workqueue(usb_bam_wq); + + return 0; +} + +static struct platform_driver usb_bam_driver = { + .probe = usb_bam_probe, + .remove = usb_bam_remove, + .driver = { .name = "usb_bam", }, +}; + +static int __init init(void) +{ + return platform_driver_register(&usb_bam_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + platform_driver_unregister(&usb_bam_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION("MSM USB BAM DRIVER"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 99dc29f2f2f2ba84b16430a51548817b094b9c3a..a263750043313c53f8dac660a456a01ca2a53211 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -288,6 +288,133 @@ config CHARGER_MAX8998 Say Y to enable support for the battery charger control sysfs and platform data of MAX8998/LP3974 PMICs. +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 PMIC8XXX_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 || PM8921_CHARGER) + depends on I2C + 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 SMB349_CHARGER + tristate "smb349 charger" + depends on I2C + help + Say Y to enable support for the SMB349 switching mode based charger + and sysfs. The driver supports controlling charger-enable and + current limiting capabilities. The driver also lets the + SMB349 be operated as a slave device via the power supply + framework. + +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 PM8XXX_CCADC + tristate "PM8XXX battery current adc driver" + depends on MFD_PM8921_CORE + help + Say Y here to enable support for pm8921 chip bms subdevice + +config LTC4088_CHARGER + tristate "LTC4088 Charger driver" + depends on GPIOLIB + help + Say Y here to enable support for ltc4088 chip charger. It controls the + operations through GPIO pins. + +config PM8921_BMS + select PM8XXX_CCADC + tristate "PM8921 Battery Monitoring System driver" + depends on MFD_PM8921_CORE + help + Say Y here to enable support for pm8921 chip bms subdevice + config CHARGER_SMB347 tristate "Summit Microelectronics SMB347 Battery Charger" depends on I2C diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b6b243416c0ee1947a47efd85b205341dc0f1463..007d75bce4a65b6645887355575c2d77776d2c49 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -43,4 +43,18 @@ obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_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_SMB349_CHARGER) += smb349.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_PM8XXX_CCADC) += pm8xxx-ccadc.o +obj-$(CONFIG_PM8921_BMS) += pm8921-bms.o +obj-$(CONFIG_PM8921_CHARGER) += pm8921-charger.o +obj-$(CONFIG_LTC4088_CHARGER) += ltc4088-charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o diff --git a/drivers/power/bq27520_fuelgauger.c b/drivers/power/bq27520_fuelgauger.c new file mode 100644 index 0000000000000000000000000000000000000000..3c191cd691cb83f9c26ac57ddd62e8a00b8ca8eb --- /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_battery_gauge_register(&bq27520_batt_gauge); + 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(); + + 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 0000000000000000000000000000000000000000..516a861dd4e4f1683e7d7802c4dd9a50c2914fcf --- /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 0000000000000000000000000000000000000000..7ebbf4602a15665eeb972598ddaa0a8d12f94de8 --- /dev/null +++ b/drivers/power/isl9519q.c @@ -0,0 +1,849 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#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_SEC 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; + int suspended; + int charge_at_resume; + struct power_supply dc_psy; + spinlock_t lock; + bool notify_by_pmic; + bool trickle; +}; + +static struct isl9519q_struct *the_isl_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; + } + + pr_debug("reg=0x%x.val=0x%x.\n", reg, *val); + + return 0; +} + +static int isl9519q_write_reg(struct i2c_client *client, int reg, + u16 val) +{ + int ret; + struct isl9519q_struct *isl_chg; + + pr_debug("reg=0x%x.val=0x%x.\n", reg, val); + + 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; +} + +/** + * Read charge-current via ADC. + * + * The ISL CCMON (charge-current-monitor) pin is connected to + * the PMIC MPP#X pin. + * This not required when notify_by_pmic is used where the PMIC + * uses BMS to notify the ISL on charging-done / charge-resume. + */ +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("called for %d\n", channel); + ret = adc_channel_open(channel, &h); + if (ret) { + pr_err("couldnt open channel %d ret=%d\n", channel, ret); + goto out; + } + init_completion(&conv_complete_evt); + ret = adc_channel_request_conv(h, &conv_complete_evt); + if (ret) { + pr_err("couldnt request conv channel %d ret=%d\n", + channel, ret); + goto out; + } + ret = wait_for_completion_interruptible(&conv_complete_evt); + if (ret) { + pr_err("wait interrupted channel %d ret=%d\n", channel, ret); + goto out; + } + ret = adc_channel_read_result(h, &adc_chan_result); + if (ret) { + pr_err("couldnt read result channel %d ret=%d\n", + channel, ret); + goto out; + } + ret = adc_channel_close(h); + if (ret) + pr_err("couldnt close channel %d ret=%d\n", channel, ret); + if (mv_reading) + *mv_reading = (int)adc_chan_result.measurement; + + pr_debug("done for %d\n", channel); + return adc_chan_result.physical; +out: + *mv_reading = 0; + pr_debug("done with error for %d\n", channel); + + return -EINVAL; +} + +static bool is_trickle_charging(struct isl9519q_struct *isl_chg) +{ + u16 ctrl = 0; + int ret; + + ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &ctrl); + + if (!ret) { + pr_debug("control_reg=0x%x.\n", ctrl); + } else { + dev_err(&isl_chg->client->dev, + "%s couldnt read cntrl reg\n", __func__); + } + + if (ctrl & TRCKL_CHG_STATUS_BIT) + return true; + + return false; +} + +static void isl_adapter_check_ichg(struct isl9519q_struct *isl_chg) +{ + int ichg; /* isl charger current */ + int mv_reading = 0; + + ichg = 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__, ichg); + + if (ichg >= 0 && ichg <= isl_chg->term_current) + msm_charger_notify_event(&isl_chg->adapter_hw_chg, + CHG_DONE_EVENT); + + isl_chg->trickle = is_trickle_charging(isl_chg); + if (isl_chg->trickle) + msm_charger_notify_event(&isl_chg->adapter_hw_chg, + CHG_BATT_BEGIN_FAST_CHARGING); +} + +/** + * isl9519q_worker + * + * Periodic task required to kick the ISL HW watchdog to keep + * charging. + * + * @isl9519_work: work context. + */ +static void isl9519q_worker(struct work_struct *isl9519_work) +{ + struct isl9519q_struct *isl_chg; + + 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) { + pr_debug("stop charging.\n"); + isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0); + return; /* Stop periodic worker */ + } + + /* Kick the dog by writting to CHG_CURRENT_REG */ + isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, + isl_chg->chgcurrent); + + if (isl_chg->notify_by_pmic) + isl_chg->trickle = is_trickle_charging(isl_chg); + else + isl_adapter_check_ichg(isl_chg); + + schedule_delayed_work(&isl_chg->charge_work, + (ISL9519_CHG_PERIOD_SEC * HZ)); +} + +static int isl9519q_start_charging(struct isl9519q_struct *isl_chg, + int chg_voltage, int chg_current) +{ + pr_debug("\n"); + + if (isl_chg->charging) { + pr_warn("already charging.\n"); + return 0; + } + + if (isl_chg->suspended) { + pr_warn("suspended - can't start charging.\n"); + isl_chg->charge_at_resume = 1; + return 0; + } + + dev_dbg(&isl_chg->client->dev, + "%s starting timed work.period=%d seconds.\n", + __func__, (int) ISL9519_CHG_PERIOD_SEC); + + /* + * The ISL will start charging from the worker context. + * This API might be called from interrupt context. + */ + schedule_delayed_work(&isl_chg->charge_work, 1); + + isl_chg->charging = true; + + return 0; +} + +static int isl9519q_stop_charging(struct isl9519q_struct *isl_chg) +{ + pr_debug("\n"); + + if (!(isl_chg->charging)) { + pr_warn("already not charging.\n"); + return 0; + } + + if (isl_chg->suspended) { + isl_chg->charge_at_resume = 0; + return 0; + } + + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + + isl_chg->charging = false; + isl_chg->trickle = false; + /* + * The ISL will stop charging from the worker context. + * This API might be called from interrupt context. + */ + schedule_delayed_work(&isl_chg->charge_work, 1); + + return 0; +} + +static int isl_adapter_start_charging(struct msm_hardware_charger *hw_chg, + int chg_voltage, int chg_current) +{ + int rc; + struct isl9519q_struct *isl_chg; + + isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); + rc = isl9519q_start_charging(isl_chg, chg_voltage, chg_current); + + return rc; +} + +static int isl_adapter_stop_charging(struct msm_hardware_charger *hw_chg) +{ + int rc; + struct isl9519q_struct *isl_chg; + + isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); + rc = isl9519q_stop_charging(isl_chg); + + return rc; +} + +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; +} + +static enum power_supply_property pm_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CHARGE_TYPE, +}; + +static char *pm_power_supplied_to[] = { + "battery", +}; + +static int get_prop_charge_type(struct isl9519q_struct *isl_chg) +{ + if (!isl_chg->present) + return POWER_SUPPLY_CHARGE_TYPE_NONE; + + if (isl_chg->trickle) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + if (isl_chg->charging) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} +static int pm_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct isl9519q_struct *isl_chg = container_of(psy, + struct isl9519q_struct, + dc_psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = isl_chg->chgcurrent; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (int)isl_chg->present; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(isl_chg); + break; + default: + return -EINVAL; + } + return 0; +} + +static int pm_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct isl9519q_struct *isl_chg = container_of(psy, + struct isl9519q_struct, + dc_psy); + unsigned long flags; + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval) { + isl_chg->present = val->intval; + } else { + isl_chg->present = 0; + if (isl_chg->charging) + goto stop_charging; + } + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (val->intval) { + if (isl_chg->chgcurrent != val->intval) + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (val->intval && isl_chg->present) { + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_FAST) + goto start_charging; + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE) + goto stop_charging; + } else { + return -EINVAL; + } + break; + default: + return -EINVAL; + } + power_supply_changed(&isl_chg->dc_psy); + return 0; + +start_charging: + spin_lock_irqsave(&isl_chg->lock, flags); + rc = isl9519q_start_charging(isl_chg, 0, isl_chg->chgcurrent); + if (rc) + pr_err("Failed to start charging rc=%d\n", rc); + spin_unlock_irqrestore(&isl_chg->lock, flags); + power_supply_changed(&isl_chg->dc_psy); + return rc; + +stop_charging: + spin_lock_irqsave(&isl_chg->lock, flags); + rc = isl9519q_stop_charging(isl_chg); + if (rc) + pr_err("Failed to start charging rc=%d\n", rc); + spin_unlock_irqrestore(&isl_chg->lock, flags); + power_supply_changed(&isl_chg->dc_psy); + return rc; +} + +#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_init_adapter(struct isl9519q_struct *isl_chg) +{ + int ret; + struct i2c_client *client = isl_chg->client; + struct isl_platform_data *pdata = client->dev.platform_data; + + 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 = isl_adapter_start_charging; + isl_chg->adapter_hw_chg.stop_charging = isl_adapter_stop_charging; + isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched; + + 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 out; + } + + 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); + + 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; + } + + return 0; + +unregister: + msm_charger_unregister(&isl_chg->adapter_hw_chg); +free_gpio: + gpio_free(pdata->valid_n_gpio); +out: + return ret; + +} + +static int __devinit isl9519q_init_ext_chg(struct isl9519q_struct *isl_chg) +{ + int ret; + + isl_chg->dc_psy.name = "dc"; + isl_chg->dc_psy.type = POWER_SUPPLY_TYPE_MAINS; + isl_chg->dc_psy.supplied_to = pm_power_supplied_to; + isl_chg->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to); + isl_chg->dc_psy.properties = pm_power_props; + isl_chg->dc_psy.num_properties = ARRAY_SIZE(pm_power_props); + isl_chg->dc_psy.get_property = pm_power_get_property; + isl_chg->dc_psy.set_property = pm_power_set_property; + + ret = power_supply_register(&isl_chg->client->dev, &isl_chg->dc_psy); + if (ret) { + pr_err("failed to register dc charger.ret=%d.\n", ret); + return ret; + } + + return 0; +} +static int set_reg(void *data, u64 val) +{ + int addr = (int)data; + int ret; + u16 temp; + + temp = (u16) val; + ret = isl9519q_write_reg(the_isl_chg->client, addr, temp); + + if (ret) { + pr_err("isl9519q_write_reg to %x value =%d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + return 0; +} +static int get_reg(void *data, u64 *val) +{ + int addr = (int)data; + int ret; + u16 temp; + + ret = isl9519q_read_reg(the_isl_chg->client, addr, &temp); + if (ret) { + pr_err("isl9519q_read_reg to %x value =%d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + + *val = temp; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); + +static void create_debugfs_entries(struct isl9519q_struct *isl_chg) +{ + isl_chg->dent = debugfs_create_dir("isl9519q", NULL); + + if (IS_ERR(isl_chg->dent)) { + pr_err("isl9519q driver couldn't create debugfs dir\n"); + return; + } + + debugfs_create_file("CHG_CURRENT_REG", 0644, isl_chg->dent, + (void *) CHG_CURRENT_REG, ®_fops); + debugfs_create_file("MAX_SYS_VOLTAGE_REG", 0644, isl_chg->dent, + (void *) MAX_SYS_VOLTAGE_REG, ®_fops); + debugfs_create_file("CONTROL_REG", 0644, isl_chg->dent, + (void *) CONTROL_REG, ®_fops); + debugfs_create_file("MIN_SYS_VOLTAGE_REG", 0644, isl_chg->dent, + (void *) MIN_SYS_VOLTAGE_REG, ®_fops); + debugfs_create_file("INPUT_CURRENT_REG", 0644, isl_chg->dent, + (void *) INPUT_CURRENT_REG, ®_fops); + debugfs_create_file("MANUFACTURER_ID_REG", 0644, isl_chg->dent, + (void *) MANUFACTURER_ID_REG, ®_fops); + debugfs_create_file("DEVICE_ID_REG", 0644, isl_chg->dent, + (void *) DEVICE_ID_REG, ®_fops); +} + +static void remove_debugfs_entries(struct isl9519q_struct *isl_chg) +{ + if (isl_chg->dent) + debugfs_remove_recursive(isl_chg->dent); +} + +static int __devinit isl9519q_hwinit(struct isl9519q_struct *isl_chg) +{ + int ret; + + ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, + isl_chg->max_system_voltage); + if (ret) { + pr_err("Failed to set MAX_SYS_VOLTAGE rc=%d\n", ret); + return ret; + } + + ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG, + isl_chg->min_system_voltage); + if (ret) { + pr_err("Failed to set MIN_SYS_VOLTAGE rc=%d\n", ret); + return ret; + } + + if (isl_chg->input_current) { + ret = isl9519q_write_reg(isl_chg->client, + INPUT_CURRENT_REG, + isl_chg->input_current); + if (ret) { + pr_err("Failed to set INPUT_CURRENT rc=%d\n", ret); + return ret; + } + } + return 0; +} + +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; + + pr_debug("\n"); + + 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; + } + + spin_lock_init(&isl_chg->lock); + + INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_worker); + 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 is Notified by PMIC to start/stop charging, rather than + * handling interrupt from ISL for End-Of-Chargring, and + * monitoring the charge-current periodically. The valid_n_gpio + * is also not used, dc-present is detected by PMIC. + */ + isl_chg->notify_by_pmic = (client->irq == 0); + i2c_set_clientdata(client, isl_chg); + + 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; + } + } + + 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_hwinit(isl_chg); + if (ret) + goto free_isl_chg; + + if (isl_chg->notify_by_pmic) + ret = isl9519q_init_ext_chg(isl_chg); + else + ret = isl9519q_init_adapter(isl_chg); + + if (ret) + goto free_isl_chg; + + the_isl_chg = isl_chg; + create_debugfs_entries(isl_chg); + + pr_info("OK.\n"); + + return 0; + +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); + if (isl_chg->notify_by_pmic) { + power_supply_unregister(&isl_chg->dc_psy); + } else { + msm_charger_notify_event(&isl_chg->adapter_hw_chg, + CHG_REMOVED_EVENT); + msm_charger_unregister(&isl_chg->adapter_hw_chg); + } + remove_debugfs_entries(isl_chg); + the_isl_chg = NULL; + kfree(isl_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; + + isl_chg->suspended = 1; + 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__); + isl_chg->suspended = 0; + if (isl_chg->charge_at_resume) { + isl_chg->charge_at_resume = 0; + isl9519q_start_charging(isl_chg, 0, 0); + } + 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); +} + +late_initcall_sync(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/ltc4088-charger.c b/drivers/power/ltc4088-charger.c new file mode 100644 index 0000000000000000000000000000000000000000..dbc75cd463ad21b8552438ac220455be3b4447e4 --- /dev/null +++ b/drivers/power/ltc4088-charger.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 +#include + +#define MAX_CURRENT_UA(n) (n) +#define MAX_CURRENT_MA(n) (n * MAX_CURRENT_UA(1000)) + +/** + * ltc4088_max_current - A typical current values supported by the charger + * @LTC4088_MAX_CURRENT_100mA: 100mA current + * @LTC4088_MAX_CURRENT_500mA: 500mA current + * @LTC4088_MAX_CURRENT_1A: 1A current + */ +enum ltc4088_max_current { + LTC4088_MAX_CURRENT_100mA = 100, + LTC4088_MAX_CURRENT_500mA = 500, + LTC4088_MAX_CURRENT_1A = 1000, +}; + +/** + * struct ltc4088_chg_chip - Device information + * @dev: Device pointer to access the parent + * @lock: Enable mutual exclusion + * @usb_psy: USB device information + * @gpio_mode_select_d0: GPIO #pin for D0 charger line + * @gpio_mode_select_d1: GPIO #pin for D1 charger line + * @gpio_mode_select_d2: GPIO #pin for D2 charger line + * @max_current: Maximum current that is supplied at this time + */ +struct ltc4088_chg_chip { + struct device *dev; + struct mutex lock; + struct power_supply usb_psy; + unsigned int gpio_mode_select_d0; + unsigned int gpio_mode_select_d1; + unsigned int gpio_mode_select_d2; + unsigned int max_current; +}; + +static enum power_supply_property pm_power_props[] = { + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *pm_power_supplied_to[] = { + "battery", +}; + +static int ltc4088_set_charging(struct ltc4088_chg_chip *chip, bool enable) +{ + mutex_lock(&chip->lock); + + if (enable) { + gpio_set_value_cansleep(chip->gpio_mode_select_d2, 0); + } else { + /* When disabling charger, set the max current to 0 also */ + chip->max_current = 0; + gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1); + gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1); + gpio_set_value_cansleep(chip->gpio_mode_select_d2, 1); + } + + mutex_unlock(&chip->lock); + + return 0; +} + +static void ltc4088_set_max_current(struct ltc4088_chg_chip *chip, int value) +{ + mutex_lock(&chip->lock); + + /* If current is less than 100mA, we can not support that granularity */ + if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA)) { + chip->max_current = 0; + gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1); + gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1); + } else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA)) { + chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA); + gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0); + gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0); + } else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A)) { + chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA); + gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0); + gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1); + } else { + chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A); + gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1); + gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0); + } + + mutex_unlock(&chip->lock); +} + +static void ltc4088_set_charging_off(struct ltc4088_chg_chip *chip) +{ + gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1); + gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1); +} + +static int ltc4088_set_initial_state(struct ltc4088_chg_chip *chip) +{ + int rc; + + rc = gpio_request(chip->gpio_mode_select_d0, "ltc4088_D0"); + if (rc) { + pr_err("gpio request failed for GPIO %d\n", + chip->gpio_mode_select_d0); + return rc; + } + + rc = gpio_request(chip->gpio_mode_select_d1, "ltc4088_D1"); + if (rc) { + pr_err("gpio request failed for GPIO %d\n", + chip->gpio_mode_select_d1); + goto gpio_err_d0; + } + + rc = gpio_request(chip->gpio_mode_select_d2, "ltc4088_D2"); + if (rc) { + pr_err("gpio request failed for GPIO %d\n", + chip->gpio_mode_select_d2); + goto gpio_err_d1; + } + + rc = gpio_direction_output(chip->gpio_mode_select_d0, 0); + if (rc) { + pr_err("failed to set direction for GPIO %d\n", + chip->gpio_mode_select_d0); + goto gpio_err_d2; + } + + rc = gpio_direction_output(chip->gpio_mode_select_d1, 0); + if (rc) { + pr_err("failed to set direction for GPIO %d\n", + chip->gpio_mode_select_d1); + goto gpio_err_d2; + } + + rc = gpio_direction_output(chip->gpio_mode_select_d2, 1); + if (rc) { + pr_err("failed to set direction for GPIO %d\n", + chip->gpio_mode_select_d2); + goto gpio_err_d2; + } + + return 0; + +gpio_err_d2: + gpio_free(chip->gpio_mode_select_d2); +gpio_err_d1: + gpio_free(chip->gpio_mode_select_d1); +gpio_err_d0: + gpio_free(chip->gpio_mode_select_d0); + return rc; +} + +static int pm_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ltc4088_chg_chip *chip; + + if (psy->type == POWER_SUPPLY_TYPE_USB) { + chip = container_of(psy, struct ltc4088_chg_chip, + usb_psy); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (chip->max_current) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chip->max_current; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static int pm_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ltc4088_chg_chip *chip; + + if (psy->type == POWER_SUPPLY_TYPE_USB) { + chip = container_of(psy, struct ltc4088_chg_chip, + usb_psy); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ltc4088_set_charging(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + ltc4088_set_max_current(chip, val->intval); + break; + default: + return -EINVAL; + } + } + return 0; +} + +static int __devinit ltc4088_charger_probe(struct platform_device *pdev) +{ + int rc; + struct ltc4088_chg_chip *chip; + const struct ltc4088_charger_platform_data *pdata + = pdev->dev.platform_data; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct ltc4088_chg_chip), + GFP_KERNEL); + if (!chip) { + pr_err("Cannot allocate pm_chg_chip\n"); + return -ENOMEM; + } + + chip->dev = &pdev->dev; + + if (pdata->gpio_mode_select_d0 < 0 || + pdata->gpio_mode_select_d1 < 0 || + pdata->gpio_mode_select_d2 < 0) { + pr_err("Invalid platform data supplied\n"); + rc = -EINVAL; + goto free_chip; + } + + mutex_init(&chip->lock); + + chip->gpio_mode_select_d0 = pdata->gpio_mode_select_d0; + chip->gpio_mode_select_d1 = pdata->gpio_mode_select_d1; + chip->gpio_mode_select_d2 = pdata->gpio_mode_select_d2; + + 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->usb_psy.set_property = pm_power_set_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_chip; + } + + platform_set_drvdata(pdev, chip); + + rc = ltc4088_set_initial_state(chip); + if (rc < 0) { + pr_err("setting initial state failed rc = %d\n", rc); + goto unregister_usb; + } + + return 0; + +unregister_usb: + platform_set_drvdata(pdev, NULL); + power_supply_unregister(&chip->usb_psy); +free_chip: + kfree(chip); + + return rc; +} + +static int __devexit ltc4088_charger_remove(struct platform_device *pdev) +{ + struct ltc4088_chg_chip *chip = platform_get_drvdata(pdev); + + ltc4088_set_charging_off(chip); + + gpio_free(chip->gpio_mode_select_d2); + gpio_free(chip->gpio_mode_select_d1); + gpio_free(chip->gpio_mode_select_d0); + + power_supply_unregister(&chip->usb_psy); + + platform_set_drvdata(pdev, NULL); + mutex_destroy(&chip->lock); + kfree(chip); + + return 0; +} + +static struct platform_driver ltc4088_charger_driver = { + .probe = ltc4088_charger_probe, + .remove = __devexit_p(ltc4088_charger_remove), + .driver = { + .name = LTC4088_CHARGER_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ltc4088_charger_init(void) +{ + return platform_driver_register(<c4088_charger_driver); +} + +static void __exit ltc4088_charger_exit(void) +{ + platform_driver_unregister(<c4088_charger_driver); +} + +subsys_initcall(ltc4088_charger_init); +module_exit(ltc4088_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("LTC4088 charger/battery driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" LTC4088_CHARGER_DEV_NAME); diff --git a/drivers/power/msm_battery.c b/drivers/power/msm_battery.c new file mode 100644 index 0000000000000000000000000000000000000000..055539934e4ea247b56bec7b70c28fa04d37ff57 --- /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 0 + +#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 0000000000000000000000000000000000000000..8594ec289d351eaed1fe6b46da6b8604bb612249 --- /dev/null +++ b/drivers/power/msm_charger.c @@ -0,0 +1,1286 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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) +{ + int capacity; + + if (msm_batt_gauge && msm_batt_gauge->get_batt_remaining_capacity) + capacity = msm_batt_gauge->get_batt_remaining_capacity(); + else + capacity = msm_chg.get_batt_capacity_percent(); + + if (capacity <= 10) + pr_err("battery capacity very low = %d\n", capacity); + + return capacity; +} + +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; +} + +static void msm_enable_system_current(struct msm_hardware_charger_priv *priv) +{ + if (priv->hw_chg->start_system_current) + priv->hw_chg->start_system_current(priv->hw_chg, + priv->max_source_current); +} + +static void msm_disable_system_current(struct msm_hardware_charger_priv *priv) +{ + if (priv->hw_chg->stop_system_current) + priv->hw_chg->stop_system_current(priv->hw_chg); +} + +/* 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) +{ + struct msm_hardware_charger_priv *old_chg_priv = NULL; + + 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) { + /* + * a better charger was found, ask the current charger + * to stop charging if it was charging + */ + 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; + old_chg_priv = msm_chg.current_chg_priv; + 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); + + msm_enable_system_current(msm_chg.current_chg_priv); + /* + * since a better charger was chosen, ask the old + * charger to stop providing system current + */ + if (old_chg_priv != NULL) + msm_disable_system_current(old_chg_priv); + + 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) { + msm_disable_system_current(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; + msm_enable_system_current(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 determine_initial_batt_status(void) +{ + 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(); + + /* 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) +{ + wake_lock_destroy(&msm_chg.wl); + 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: + kfree(priv); + return rc; +} +EXPORT_SYMBOL(msm_charger_register); + +void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge) +{ + int rc; + + if (msm_batt_gauge) { + msm_batt_gauge = batt_gauge; + pr_err("msm-charger %s multiple battery gauge called\n", + __func__); + } else { + 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; + } + + 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); + 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 0000000000000000000000000000000000000000..80b1f87cd90749e76b18e23e6724a0960a124f3a --- /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 0000000000000000000000000000000000000000..b0439bcdb9cb09e0410aaf0fdac6a1e1e7970f5c --- /dev/null +++ b/drivers/power/pm8921-bms.c @@ -0,0 +1,2720 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#define BMS_CONTROL 0x224 +#define BMS_S1_DELAY 0x225 +#define BMS_OUTPUT0 0x230 +#define BMS_OUTPUT1 0x231 +#define BMS_TOLERANCES 0x232 +#define BMS_TEST1 0x237 + +#define ADC_ARB_SECP_CNTRL 0x190 +#define ADC_ARB_SECP_AMUX_CNTRL 0x191 +#define ADC_ARB_SECP_ANA_PARAM 0x192 +#define ADC_ARB_SECP_DIG_PARAM 0x193 +#define ADC_ARB_SECP_RSV 0x194 +#define ADC_ARB_SECP_DATA1 0x195 +#define ADC_ARB_SECP_DATA0 0x196 + +#define ADC_ARB_BMS_CNTRL 0x18D +#define AMUX_TRIM_2 0x322 +#define TEST_PROGRAM_REV 0x339 + +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_soc_params { + uint16_t last_good_ocv_raw; + int cc; + + int last_good_ocv_uv; +}; + +struct pm8921_rbatt_params { + uint16_t ocv_for_rbatt_raw; + uint16_t vsense_for_rbatt_raw; + uint16_t vbatt_for_rbatt_raw; + + int ocv_for_rbatt_uv; + int vsense_for_rbatt_uv; + int vbatt_for_rbatt_uv; +}; + +/** + * struct pm8921_bms_chip - + * @bms_output_lock: lock to prevent concurrent bms reads + * + * @last_ocv_uv_mutex: mutex to protect simultaneous invocations of calculate + * state of charge, note that last_ocv_uv could be + * changed as soc is adjusted. This mutex protects + * simultaneous updates of last_ocv_uv as well. This mutex + * also protects changes to *_at_100 variables used in + * faking 100% SOC. + */ +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 sf_lut *pc_sf_lut; + struct sf_lut *rbatt_sf_lut; + int delta_rbatt_mohm; + struct work_struct calib_hkadc_work; + struct delayed_work calib_ccadc_work; + unsigned int calib_delay_ms; + unsigned int revision; + unsigned int xoadc_v0625_usb_present; + unsigned int xoadc_v0625_usb_absent; + unsigned int xoadc_v0625; + unsigned int xoadc_v125; + unsigned int batt_temp_channel; + unsigned int vbat_channel; + unsigned int ref625mv_channel; + unsigned int ref1p25v_channel; + unsigned int batt_id_channel; + unsigned int pmic_bms_irq[PM_BMS_MAX_INTS]; + DECLARE_BITMAP(enabled_irqs, PM_BMS_MAX_INTS); + struct mutex bms_output_lock; + struct single_row_lut *adjusted_fcc_temp_lut; + unsigned int charging_began; + unsigned int start_percent; + unsigned int end_percent; + enum battery_type batt_type; + uint16_t ocv_reading_at_100; + int cc_reading_at_100; + int max_voltage_uv; + + int batt_temp_suspend; + int soc_rbatt_suspend; + int default_rbatt_mohm; + int amux_2_trim_delta; + uint16_t prev_last_good_ocv_raw; + unsigned int rconn_mohm; + struct mutex last_ocv_uv_mutex; + int last_ocv_uv; + int last_cc_uah; /* used for Iavg calc for UUC */ + struct timeval t; + int last_uuc_uah; + int enable_fcc_learning; +}; + +static struct pm8921_bms_chip *the_chip; + +#define DEFAULT_RBATT_MOHMS 128 +#define DEFAULT_OCV_MICROVOLTS 3900000 +#define DEFAULT_CHARGE_CYCLES 0 + +static int last_usb_cal_delta_uv = 1800; +module_param(last_usb_cal_delta_uv, int, 0644); + +static int last_chargecycles = DEFAULT_CHARGE_CYCLES; +static int last_charge_increase; +module_param(last_chargecycles, int, 0644); +module_param(last_charge_increase, int, 0644); + +static int last_rbatt = -EINVAL; +static int last_soc = -EINVAL; +static int last_real_fcc_mah = -EINVAL; +static int last_real_fcc_batt_temp = -EINVAL; + +static int bms_ops_set(const char *val, const struct kernel_param *kp) +{ + if (*(int *)kp->arg == -EINVAL) + return param_set_int(val, kp); + else + return 0; +} + +static struct kernel_param_ops bms_param_ops = { + .set = bms_ops_set, + .get = param_get_int, +}; + +module_param_cb(last_rbatt, &bms_param_ops, &last_rbatt, 0644); +module_param_cb(last_soc, &bms_param_ops, &last_soc, 0644); + +/* + * bms_fake_battery is set in setups where a battery emulator is used instead + * of a real battery. This makes the bms driver report a different/fake value + * regardless of the calculated state of charge. + */ +static int bms_fake_battery = -EINVAL; +module_param(bms_fake_battery, int, 0644); + +/* bms_start_XXX and bms_end_XXX are read only */ +static int bms_start_percent; +static int bms_start_ocv_uv; +static int bms_start_cc_uah; +static int bms_end_percent; +static int bms_end_ocv_uv; +static int bms_end_cc_uah; + +static int bms_ro_ops_set(const char *val, const struct kernel_param *kp) +{ + return -EINVAL; +} + +static struct kernel_param_ops bms_ro_param_ops = { + .set = bms_ro_ops_set, + .get = param_get_int, +}; +module_param_cb(bms_start_percent, &bms_ro_param_ops, &bms_start_percent, 0644); +module_param_cb(bms_start_ocv_uv, &bms_ro_param_ops, &bms_start_ocv_uv, 0644); +module_param_cb(bms_start_cc_uah, &bms_ro_param_ops, &bms_start_cc_uah, 0644); + +module_param_cb(bms_end_percent, &bms_ro_param_ops, &bms_end_percent, 0644); +module_param_cb(bms_end_ocv_uv, &bms_ro_param_ops, &bms_end_ocv_uv, 0644); +module_param_cb(bms_end_cc_uah, &bms_ro_param_ops, &bms_end_cc_uah, 0644); + +static int interpolate_fcc(struct pm8921_bms_chip *chip, int batt_temp); +static void readjust_fcc_table(void) +{ + struct single_row_lut *temp, *old; + int i, fcc, ratio; + + if (!the_chip->fcc_temp_lut) { + pr_err("The static fcc lut table is NULL\n"); + return; + } + + temp = kzalloc(sizeof(struct single_row_lut), GFP_KERNEL); + if (!temp) { + pr_err("Cannot allocate memory for adjusted fcc table\n"); + return; + } + + fcc = interpolate_fcc(the_chip, last_real_fcc_batt_temp); + + temp->cols = the_chip->fcc_temp_lut->cols; + for (i = 0; i < the_chip->fcc_temp_lut->cols; i++) { + temp->x[i] = the_chip->fcc_temp_lut->x[i]; + ratio = div_u64(the_chip->fcc_temp_lut->y[i] * 1000, fcc); + temp->y[i] = (ratio * last_real_fcc_mah); + temp->y[i] /= 1000; + pr_debug("temp=%d, staticfcc=%d, adjfcc=%d, ratio=%d\n", + temp->x[i], the_chip->fcc_temp_lut->y[i], + temp->y[i], ratio); + } + + old = the_chip->adjusted_fcc_temp_lut; + the_chip->adjusted_fcc_temp_lut = temp; + kfree(old); +} + +static int bms_last_real_fcc_set(const char *val, + const struct kernel_param *kp) +{ + int rc = 0; + + if (last_real_fcc_mah == -EINVAL) + rc = param_set_int(val, kp); + if (rc) { + pr_err("Failed to set last_real_fcc_mah rc=%d\n", rc); + return rc; + } + if (last_real_fcc_batt_temp != -EINVAL) + readjust_fcc_table(); + return rc; +} +static struct kernel_param_ops bms_last_real_fcc_param_ops = { + .set = bms_last_real_fcc_set, + .get = param_get_int, +}; +module_param_cb(last_real_fcc_mah, &bms_last_real_fcc_param_ops, + &last_real_fcc_mah, 0644); + +static int bms_last_real_fcc_batt_temp_set(const char *val, + const struct kernel_param *kp) +{ + int rc = 0; + + if (last_real_fcc_batt_temp == -EINVAL) + rc = param_set_int(val, kp); + if (rc) { + pr_err("Failed to set last_real_fcc_batt_temp rc=%d\n", rc); + return rc; + } + if (last_real_fcc_mah != -EINVAL) + readjust_fcc_table(); + return rc; +} + +static struct kernel_param_ops bms_last_real_fcc_batt_temp_param_ops = { + .set = bms_last_real_fcc_batt_temp_set, + .get = param_get_int, +}; +module_param_cb(last_real_fcc_batt_temp, &bms_last_real_fcc_batt_temp_param_ops, + &last_real_fcc_batt_temp, 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_enable_irq(struct pm8921_bms_chip *chip, int interrupt) +{ + if (!__test_and_set_bit(interrupt, chip->enabled_irqs)) { + dev_dbg(chip->dev, "%s %d\n", __func__, + chip->pmic_bms_irq[interrupt]); + enable_irq(chip->pmic_bms_irq[interrupt]); + } +} + +static void pm8921_bms_disable_irq(struct pm8921_bms_chip *chip, int interrupt) +{ + if (__test_and_clear_bit(interrupt, chip->enabled_irqs)) { + pr_debug("%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; +} + +static int usb_chg_plugged_in(void) +{ + union power_supply_propval ret = {0,}; + static struct power_supply *psy; + + if (psy == NULL) { + psy = power_supply_get_by_name("usb"); + if (psy == NULL) + return 0; + } + + if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret)) + return 0; + + return ret.intval; +} + +#define HOLD_OREG_DATA BIT(1) +static int pm_bms_lock_output_data(struct pm8921_bms_chip *chip) +{ + int rc; + + rc = pm_bms_masked_write(chip, BMS_CONTROL, HOLD_OREG_DATA, + HOLD_OREG_DATA); + if (rc) { + pr_err("couldnt lock bms output rc = %d\n", rc); + return rc; + } + return 0; +} + +static int pm_bms_unlock_output_data(struct pm8921_bms_chip *chip) +{ + int rc; + + rc = pm_bms_masked_write(chip, BMS_CONTROL, HOLD_OREG_DATA, 0); + if (rc) { + pr_err("fail to unlock BMS_CONTROL rc = %d\n", 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 97656 +#define V_PER_BIT_DIV_FACTOR 1000 +#define XOADC_INTRINSIC_OFFSET 0x6000 +static int xoadc_reading_to_microvolt(unsigned int a) +{ + if (a <= XOADC_INTRINSIC_OFFSET) + return 0; + + return (a - XOADC_INTRINSIC_OFFSET) + * V_PER_BIT_MUL_FACTOR / V_PER_BIT_DIV_FACTOR; +} + +#define XOADC_CALIB_UV 625000 +#define VBATT_MUL_FACTOR 3 +static int adjust_xo_vbatt_reading(struct pm8921_bms_chip *chip, + int usb_chg, unsigned int uv) +{ + s64 numerator, denominator; + int local_delta; + + if (uv == 0) + return 0; + + /* dont adjust if not calibrated */ + if (chip->xoadc_v0625 == 0 || chip->xoadc_v125 == 0) { + pr_debug("No cal yet return %d\n", VBATT_MUL_FACTOR * uv); + return VBATT_MUL_FACTOR * uv; + } + + if (usb_chg) + local_delta = last_usb_cal_delta_uv; + else + local_delta = 0; + + pr_debug("using delta = %d\n", local_delta); + numerator = ((s64)uv - chip->xoadc_v0625 - local_delta) + * XOADC_CALIB_UV; + denominator = (s64)chip->xoadc_v125 - chip->xoadc_v0625 - local_delta; + if (denominator == 0) + return uv * VBATT_MUL_FACTOR; + return (XOADC_CALIB_UV + local_delta + div_s64(numerator, denominator)) + * VBATT_MUL_FACTOR; +} + +#define CC_RESOLUTION_N_V1 1085069 +#define CC_RESOLUTION_D_V1 100000 +#define CC_RESOLUTION_N_V2 868056 +#define CC_RESOLUTION_D_V2 10000 +static s64 cc_to_microvolt_v1(s64 cc) +{ + return div_s64(cc * CC_RESOLUTION_N_V1, CC_RESOLUTION_D_V1); +} + +static s64 cc_to_microvolt_v2(s64 cc) +{ + return div_s64(cc * CC_RESOLUTION_N_V2, CC_RESOLUTION_D_V2); +} + +static s64 cc_to_microvolt(struct pm8921_bms_chip *chip, s64 cc) +{ + /* + * resolution (the value of a single bit) was changed after revision 2.0 + * for more accurate readings + */ + return (chip->revision < PM8XXX_REVISION_8921_2p0) ? + cc_to_microvolt_v1((s64)cc) : + cc_to_microvolt_v2((s64)cc); +} + +#define CC_READING_TICKS 55 +#define SLEEP_CLK_HZ 32768 +#define SECONDS_PER_HOUR 3600 +/** + * ccmicrovolt_to_nvh - + * @cc_uv: coulumb counter converted to uV + * + * RETURNS: coulumb counter based charge in nVh + * (nano Volt Hour) + */ +static s64 ccmicrovolt_to_nvh(s64 cc_uv) +{ + return div_s64(cc_uv * CC_READING_TICKS * 1000, + SLEEP_CLK_HZ * SECONDS_PER_HOUR); +} + +/* 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 adjust_xo_vbatt_reading_for_mbg(struct pm8921_bms_chip *chip, + int result) +{ + int64_t numerator; + int64_t denominator; + + if (chip->amux_2_trim_delta == 0) + return result; + + numerator = (s64)result * 1000000; + denominator = (1000000 + (410 * (s64)chip->amux_2_trim_delta)); + return div_s64(numerator, denominator); +} + +static int convert_vbatt_raw_to_uv(struct pm8921_bms_chip *chip, + int usb_chg, + uint16_t reading, int *result) +{ + *result = xoadc_reading_to_microvolt(reading); + pr_debug("raw = %04x vbatt = %u\n", reading, *result); + *result = adjust_xo_vbatt_reading(chip, usb_chg, *result); + pr_debug("after adj vbatt = %u\n", *result); + *result = adjust_xo_vbatt_reading_for_mbg(chip, *result); + return 0; +} + +static int convert_vsense_to_uv(struct pm8921_bms_chip *chip, + int16_t reading, int *result) +{ + *result = pm8xxx_ccadc_reading_to_microvolt(chip->revision, reading); + pr_debug("raw = %04x vsense = %d\n", reading, *result); + *result = pm8xxx_cc_adjust_for_gain(*result); + pr_debug("after adj vsense = %d\n", *result); + return 0; +} + +static int read_vsense_avg(struct pm8921_bms_chip *chip, int *result) +{ + int rc; + int16_t reading; + + rc = pm_bms_read_output_data(chip, VSENSE_AVG, &reading); + if (rc) { + pr_err("fail to read VSENSE_AVG rc = %d\n", rc); + return rc; + } + + convert_vsense_to_uv(chip, 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_debug("x %d less than known range return y = %d lut = %pS\n", + x, lut->y[0], lut); + return lut->y[0]; + } + if (x > lut->x[lut->cols - 1]) { + pr_debug("x %d more than known range return y = %d lut = %pS\n", + x, lut->y[lut->cols - 1], lut); + 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) +{ + /* batt_temp is in tenths of degC - convert it to degC for lookups */ + batt_temp = batt_temp/10; + return interpolate_single_lut(chip->fcc_temp_lut, batt_temp); +} + +static int interpolate_fcc_adjusted(struct pm8921_bms_chip *chip, int batt_temp) +{ + /* batt_temp is in tenths of degC - convert it to degC for lookups */ + batt_temp = batt_temp/10; + return interpolate_single_lut(chip->adjusted_fcc_temp_lut, batt_temp); +} + +static int interpolate_scalingfactor_fcc(struct pm8921_bms_chip *chip, + int cycles) +{ + /* + * sf table could be null when no battery aging data is available, in + * that case return 100% + */ + if (chip->fcc_sf_lut) + return interpolate_single_lut(chip->fcc_sf_lut, cycles); + else + return 100; +} + +static int interpolate_scalingfactor(struct pm8921_bms_chip *chip, + struct sf_lut *sf_lut, + int row_entry, int pc) +{ + int i, scalefactorrow1, scalefactorrow2, scalefactor; + int rows, cols; + int row1 = 0; + int row2 = 0; + + /* + * sf table could be null when no battery aging data is available, in + * that case return 100% + */ + if (!sf_lut) + return 100; + + rows = sf_lut->rows; + cols = sf_lut->cols; + if (pc > sf_lut->percent[0]) { + pr_debug("pc %d greater than known pc ranges for sfd\n", pc); + row1 = 0; + row2 = 0; + } + if (pc < sf_lut->percent[rows - 1]) { + pr_debug("pc %d less than known pc ranges for sf", pc); + row1 = rows - 1; + row2 = rows - 1; + } + for (i = 0; i < rows; i++) { + if (pc == sf_lut->percent[i]) { + row1 = i; + row2 = i; + break; + } + if (pc > sf_lut->percent[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + + if (row_entry < sf_lut->row_entries[0]) + row_entry = sf_lut->row_entries[0]; + if (row_entry > sf_lut->row_entries[cols - 1]) + row_entry = sf_lut->row_entries[cols - 1]; + + for (i = 0; i < cols; i++) + if (row_entry <= sf_lut->row_entries[i]) + break; + if (row_entry == sf_lut->row_entries[i]) { + scalefactor = linear_interpolate( + sf_lut->sf[row1][i], + sf_lut->percent[row1], + sf_lut->sf[row2][i], + sf_lut->percent[row2], + pc); + return scalefactor; + } + + scalefactorrow1 = linear_interpolate( + sf_lut->sf[row1][i - 1], + sf_lut->row_entries[i - 1], + sf_lut->sf[row1][i], + sf_lut->row_entries[i], + row_entry); + + scalefactorrow2 = linear_interpolate( + sf_lut->sf[row2][i - 1], + sf_lut->row_entries[i - 1], + sf_lut->sf[row2][i], + sf_lut->row_entries[i], + row_entry); + + scalefactor = linear_interpolate( + scalefactorrow1, + sf_lut->percent[row1], + scalefactorrow2, + sf_lut->percent[row2], + pc); + + return scalefactor; +} + +static int is_between(int left, int right, int value) +{ + if (left >= right && left >= value && value >= right) + return 1; + if (left <= right && left <= value && value <= right) + return 1; + + return 0; +} + +static int interpolate_pc(struct pm8921_bms_chip *chip, + int batt_temp, int ocv) +{ + int i, j, pcj, pcj_minus_one, pc; + int rows = chip->pc_temp_ocv_lut->rows; + int cols = chip->pc_temp_ocv_lut->cols; + + /* batt_temp is in tenths of degC - convert it to degC for lookups */ + batt_temp = batt_temp/10; + + if (batt_temp < chip->pc_temp_ocv_lut->temp[0]) { + pr_debug("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_debug("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; + } + } + } + + /* + * batt_temp is within temperature for + * column j-1 and j + */ + 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]; + + pcj_minus_one = 0; + pcj = 0; + for (i = 0; i < rows-1; i++) { + if (pcj == 0 + && is_between(chip->pc_temp_ocv_lut->ocv[i][j], + chip->pc_temp_ocv_lut->ocv[i+1][j], ocv)) { + pcj = 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); + } + + if (pcj_minus_one == 0 + && is_between(chip->pc_temp_ocv_lut->ocv[i][j-1], + chip->pc_temp_ocv_lut->ocv[i+1][j-1], ocv)) { + + pcj_minus_one = linear_interpolate( + chip->pc_temp_ocv_lut->percent[i], + chip->pc_temp_ocv_lut->ocv[i][j-1], + chip->pc_temp_ocv_lut->percent[i + 1], + chip->pc_temp_ocv_lut->ocv[i+1][j-1], + ocv); + } + + if (pcj && pcj_minus_one) { + pc = linear_interpolate( + pcj_minus_one, + chip->pc_temp_ocv_lut->temp[j-1], + pcj, + chip->pc_temp_ocv_lut->temp[j], + batt_temp); + return pc; + } + } + + if (pcj) + return pcj; + + if (pcj_minus_one) + return pcj_minus_one; + + pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%", + ocv, batt_temp); + return 100; +} + +#define BMS_MODE_BIT BIT(6) +#define EN_VBAT_BIT BIT(5) +#define OVERRIDE_MODE_DELAY_MS 20 +int pm8921_bms_get_simultaneous_battery_voltage_and_current(int *ibat_ua, + int *vbat_uv) +{ + int16_t vsense_raw; + int16_t vbat_raw; + int vsense_uv; + int usb_chg; + + if (the_chip == NULL) { + pr_err("Called to early\n"); + return -EINVAL; + } + + mutex_lock(&the_chip->bms_output_lock); + + pm8xxx_writeb(the_chip->dev->parent, BMS_S1_DELAY, 0x00); + pm_bms_masked_write(the_chip, BMS_CONTROL, + BMS_MODE_BIT | EN_VBAT_BIT, BMS_MODE_BIT | EN_VBAT_BIT); + + msleep(OVERRIDE_MODE_DELAY_MS); + + pm_bms_lock_output_data(the_chip); + pm_bms_read_output_data(the_chip, VSENSE_AVG, &vsense_raw); + pm_bms_read_output_data(the_chip, VBATT_AVG, &vbat_raw); + pm_bms_unlock_output_data(the_chip); + pm_bms_masked_write(the_chip, BMS_CONTROL, + BMS_MODE_BIT | EN_VBAT_BIT, 0); + + pm8xxx_writeb(the_chip->dev->parent, BMS_S1_DELAY, 0x0B); + + mutex_unlock(&the_chip->bms_output_lock); + + usb_chg = usb_chg_plugged_in(); + + convert_vbatt_raw_to_uv(the_chip, usb_chg, vbat_raw, vbat_uv); + convert_vsense_to_uv(the_chip, vsense_raw, &vsense_uv); + *ibat_ua = vsense_uv * 1000 / (int)the_chip->r_sense; + + pr_debug("vsense_raw = 0x%x vbat_raw = 0x%x" + " ibat_ua = %d vbat_uv = %d\n", + (uint16_t)vsense_raw, (uint16_t)vbat_raw, + *ibat_ua, *vbat_uv); + return 0; +} +EXPORT_SYMBOL(pm8921_bms_get_simultaneous_battery_voltage_and_current); + +static int read_rbatt_params_raw(struct pm8921_bms_chip *chip, + struct pm8921_rbatt_params *raw) +{ + int usb_chg; + + mutex_lock(&chip->bms_output_lock); + pm_bms_lock_output_data(chip); + + pm_bms_read_output_data(chip, + OCV_FOR_RBATT, &raw->ocv_for_rbatt_raw); + pm_bms_read_output_data(chip, + VBATT_FOR_RBATT, &raw->vbatt_for_rbatt_raw); + pm_bms_read_output_data(chip, + VSENSE_FOR_RBATT, &raw->vsense_for_rbatt_raw); + + pm_bms_unlock_output_data(chip); + mutex_unlock(&chip->bms_output_lock); + + usb_chg = usb_chg_plugged_in(); + convert_vbatt_raw_to_uv(chip, usb_chg, + raw->vbatt_for_rbatt_raw, &raw->vbatt_for_rbatt_uv); + convert_vbatt_raw_to_uv(chip, usb_chg, + raw->ocv_for_rbatt_raw, &raw->ocv_for_rbatt_uv); + convert_vsense_to_uv(chip, raw->vsense_for_rbatt_raw, + &raw->vsense_for_rbatt_uv); + + pr_debug("vbatt_for_rbatt_raw = 0x%x, vbatt_for_rbatt= %duV\n", + raw->vbatt_for_rbatt_raw, raw->vbatt_for_rbatt_uv); + pr_debug("ocv_for_rbatt_raw = 0x%x, ocv_for_rbatt= %duV\n", + raw->ocv_for_rbatt_raw, raw->ocv_for_rbatt_uv); + pr_debug("vsense_for_rbatt_raw = 0x%x, vsense_for_rbatt= %duV\n", + raw->vsense_for_rbatt_raw, raw->vsense_for_rbatt_uv); + return 0; +} + +#define MBG_TRANSIENT_ERROR_RAW 51 +static void adjust_pon_ocv_raw(struct pm8921_bms_chip *chip, + struct pm8921_soc_params *raw) +{ + /* in 8921 parts the PON ocv is taken when the MBG is not settled. + * decrease the pon ocv by 15mV raw value to account for it + * Since a 1/3rd of vbatt is supplied to the adc the raw value + * needs to be adjusted by 5mV worth bits + */ + if (raw->last_good_ocv_raw >= MBG_TRANSIENT_ERROR_RAW) + raw->last_good_ocv_raw -= MBG_TRANSIENT_ERROR_RAW; +} + +static int read_soc_params_raw(struct pm8921_bms_chip *chip, + struct pm8921_soc_params *raw) +{ + int usb_chg; + + mutex_lock(&chip->bms_output_lock); + pm_bms_lock_output_data(chip); + + pm_bms_read_output_data(chip, + LAST_GOOD_OCV_VALUE, &raw->last_good_ocv_raw); + read_cc(chip, &raw->cc); + + pm_bms_unlock_output_data(chip); + mutex_unlock(&chip->bms_output_lock); + + usb_chg = usb_chg_plugged_in(); + + if (chip->prev_last_good_ocv_raw == 0) { + chip->prev_last_good_ocv_raw = raw->last_good_ocv_raw; + adjust_pon_ocv_raw(chip, raw); + convert_vbatt_raw_to_uv(chip, usb_chg, + raw->last_good_ocv_raw, &raw->last_good_ocv_uv); + chip->last_ocv_uv = raw->last_good_ocv_uv; + } else if (chip->prev_last_good_ocv_raw != raw->last_good_ocv_raw) { + chip->prev_last_good_ocv_raw = raw->last_good_ocv_raw; + convert_vbatt_raw_to_uv(chip, usb_chg, + raw->last_good_ocv_raw, &raw->last_good_ocv_uv); + chip->last_ocv_uv = raw->last_good_ocv_uv; + /* forget the old cc value upon ocv */ + chip->last_cc_uah = 0; + } else { + raw->last_good_ocv_uv = chip->last_ocv_uv; + } + + /* fake a high OCV if we are just done charging */ + if (chip->ocv_reading_at_100 != raw->last_good_ocv_raw) { + chip->ocv_reading_at_100 = 0; + chip->cc_reading_at_100 = 0; + } else { + /* + * force 100% ocv by selecting the highest voltage the + * battery could ever reach + */ + raw->last_good_ocv_uv = chip->max_voltage_uv; + chip->last_ocv_uv = chip->max_voltage_uv; + } + pr_debug("0p625 = %duV\n", chip->xoadc_v0625); + pr_debug("1p25 = %duV\n", chip->xoadc_v125); + pr_debug("last_good_ocv_raw= 0x%x, last_good_ocv_uv= %duV\n", + raw->last_good_ocv_raw, raw->last_good_ocv_uv); + pr_debug("cc_raw= 0x%x\n", raw->cc); + return 0; +} + +static int get_rbatt(struct pm8921_bms_chip *chip, int soc_rbatt, int batt_temp) +{ + int rbatt, scalefactor; + + rbatt = (last_rbatt < 0) ? chip->default_rbatt_mohm : last_rbatt; + pr_debug("rbatt before scaling = %d\n", rbatt); + if (chip->rbatt_sf_lut == NULL) { + pr_debug("RBATT = %d\n", rbatt); + return rbatt; + } + /* Convert the batt_temp to DegC from deciDegC */ + batt_temp = batt_temp / 10; + scalefactor = interpolate_scalingfactor(chip, chip->rbatt_sf_lut, + batt_temp, soc_rbatt); + pr_debug("rbatt sf = %d for batt_temp = %d, soc_rbatt = %d\n", + scalefactor, batt_temp, soc_rbatt); + rbatt = (rbatt * scalefactor) / 100; + + rbatt += the_chip->rconn_mohm; + pr_debug("adding rconn_mohm = %d rbatt = %d\n", + the_chip->rconn_mohm, rbatt); + + if (is_between(20, 10, soc_rbatt)) + rbatt = rbatt + + ((20 - soc_rbatt) * chip->delta_rbatt_mohm) / 10; + else + if (is_between(10, 0, soc_rbatt)) + rbatt = rbatt + chip->delta_rbatt_mohm; + + pr_debug("RBATT = %d\n", rbatt); + return rbatt; +} + +static int calculate_rbatt_resume(struct pm8921_bms_chip *chip, + struct pm8921_rbatt_params *raw) +{ + unsigned int r_batt; + + if (raw->ocv_for_rbatt_uv <= 0 + || raw->ocv_for_rbatt_uv <= raw->vbatt_for_rbatt_uv + || raw->vsense_for_rbatt_raw <= 0) { + pr_debug("rbatt readings unavailable ocv = %d, vbatt = %d," + "vsen = %d\n", + raw->ocv_for_rbatt_uv, + raw->vbatt_for_rbatt_uv, + raw->vsense_for_rbatt_raw); + return -EINVAL; + } + r_batt = ((raw->ocv_for_rbatt_uv - raw->vbatt_for_rbatt_uv) + * chip->r_sense) / raw->vsense_for_rbatt_uv; + pr_debug("r_batt = %umilliOhms", r_batt); + return r_batt; +} + +static int calculate_fcc_uah(struct pm8921_bms_chip *chip, int batt_temp, + int chargecycles) +{ + int initfcc, result, scalefactor = 0; + + if (chip->adjusted_fcc_temp_lut == NULL) { + initfcc = interpolate_fcc(chip, batt_temp); + + scalefactor = interpolate_scalingfactor_fcc(chip, chargecycles); + + /* Multiply the initial FCC value by the scale factor. */ + result = (initfcc * scalefactor * 1000) / 100; + pr_debug("fcc = %d uAh\n", result); + return result; + } else { + return 1000 * interpolate_fcc_adjusted(chip, batt_temp); + } +} + +static int get_battery_uvolts(struct pm8921_bms_chip *chip, int *uvolts) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_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); + *uvolts = (int)result.physical; + return 0; +} + +static int adc_based_ocv(struct pm8921_bms_chip *chip, int *ocv) +{ + int vbatt, rbatt, ibatt_ua, rc; + + rc = get_battery_uvolts(chip, &vbatt); + if (rc) { + pr_err("failed to read vbatt from adc rc = %d\n", rc); + return rc; + } + + rc = pm8921_bms_get_battery_current(&ibatt_ua); + if (rc) { + pr_err("failed to read batt current rc = %d\n", rc); + return rc; + } + + rbatt = (last_rbatt < 0) ? chip->default_rbatt_mohm : last_rbatt; + *ocv = vbatt + (ibatt_ua * rbatt)/1000; + return 0; +} + +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(chip, + chip->pc_sf_lut, 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; +} + +/** + * calculate_cc_uah - + * @chip: the bms chip pointer + * @cc: the cc reading from bms h/w + * @val: return value + * @coulumb_counter: adjusted coulumb counter for 100% + * + * RETURNS: in val pointer coulumb counter based charger in uAh + * (micro Amp hour) + */ +static void calculate_cc_uah(struct pm8921_bms_chip *chip, int cc, int *val) +{ + int64_t cc_voltage_uv, cc_nvh, cc_uah; + + cc_voltage_uv = cc; + cc_voltage_uv -= chip->cc_reading_at_100; + pr_debug("cc = %d. after subtracting 0x%x cc = %lld\n", + cc, chip->cc_reading_at_100, + cc_voltage_uv); + cc_voltage_uv = cc_to_microvolt(chip, cc_voltage_uv); + cc_voltage_uv = pm8xxx_cc_adjust_for_gain(cc_voltage_uv); + pr_debug("cc_voltage_uv = %lld microvolts\n", cc_voltage_uv); + cc_nvh = ccmicrovolt_to_nvh(cc_voltage_uv); + pr_debug("cc_nvh = %lld nano_volt_hour\n", cc_nvh); + cc_uah = div_s64(cc_nvh, chip->r_sense); + *val = cc_uah; +} + +static int calculate_uuc_uah_at_given_current(struct pm8921_bms_chip *chip, + int batt_temp, int chargecycles, + int rbatt, int fcc_uah, int i_ma) +{ + int unusable_uv, pc_unusable, uuc; + + /* calculate unusable charge with itest */ + unusable_uv = (rbatt * i_ma) + (chip->v_failure * 1000); + pc_unusable = calculate_pc(chip, unusable_uv, batt_temp, chargecycles); + uuc = (fcc_uah * pc_unusable) / 100; + pr_debug("For i_ma = %d, unusable_uv = %d unusable_pc = %d uuc = %d\n", + i_ma, unusable_uv, pc_unusable, uuc); + return uuc; +} + +/* soc_rbatt when uuc_reported should be equal to uuc_now */ +#define SOC_RBATT_CHG 80 +#define SOC_RBATT_DISCHG 10 +static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip, + int rbatt, int fcc_uah, int cc_uah, + int soc_rbatt, int batt_temp, int chargecycles) +{ + struct timeval now; + int delta_time_s; + int delta_cc_uah; + int iavg_ua, iavg_ma; + int uuc_uah_itest, uuc_uah_iavg, uuc_now, uuc_reported; + s64 stepsize = 0; + int firsttime = 0; + + delta_cc_uah = cc_uah - chip->last_cc_uah; + do_gettimeofday(&now); + if (chip->t.tv_sec != 0) { + delta_time_s = (now.tv_sec - chip->t.tv_sec); + } else { + /* uuc calculation for the first time */ + delta_time_s = 0; + firsttime = 1; + } + + if (delta_time_s != 0) + iavg_ua = div_s64((s64)delta_cc_uah * 3600, delta_time_s); + else + iavg_ua = 0; + + iavg_ma = iavg_ua/1000; + + pr_debug("t.tv_sec = %d, now.tv_sec = %d\n", (int)chip->t.tv_sec, + (int)now.tv_sec); + + pr_debug("delta_time_s = %d iavg_ma = %d\n", delta_time_s, iavg_ma); + + if (iavg_ma == 0) { + pr_debug("Iavg = 0 returning last uuc = %d\n", + chip->last_uuc_uah); + uuc_reported = chip->last_uuc_uah; + goto out; + } + + /* calculate unusable charge with itest */ + uuc_uah_itest = calculate_uuc_uah_at_given_current(chip, + batt_temp, chargecycles, + rbatt, fcc_uah, chip->i_test); + + pr_debug("itest = %d uuc_itest = %d\n", chip->i_test, uuc_uah_itest); + + /* calculate unusable charge with iavg */ + iavg_ma = max(0, iavg_ma); + uuc_uah_iavg = calculate_uuc_uah_at_given_current(chip, + batt_temp, chargecycles, + rbatt, fcc_uah, iavg_ma); + pr_debug("iavg = %d uuc_iavg = %d\n", iavg_ma, uuc_uah_iavg); + + if (firsttime) { + if (cc_uah < chip->last_cc_uah) + chip->last_uuc_uah = uuc_uah_itest; + else + chip->last_uuc_uah = uuc_uah_iavg; + pr_debug("firsttime uuc_prev = %d\n", chip->last_uuc_uah); + } + + uuc_now = min(uuc_uah_itest, uuc_uah_iavg); + + uuc_reported = -EINVAL; + if (cc_uah < chip->last_cc_uah) { + /* charging */ + if (uuc_now < chip->last_uuc_uah) { + stepsize = max(1, (SOC_RBATT_CHG - soc_rbatt)); + /* uuc_reported = uuc_prev + deltauuc / stepsize */ + uuc_reported = div_s64 (stepsize * chip->last_uuc_uah + + (uuc_now - chip->last_uuc_uah), + stepsize); + uuc_reported = max(0, uuc_reported); + } + } else { + if (uuc_now > chip->last_uuc_uah) { + stepsize = max(1, (soc_rbatt - SOC_RBATT_DISCHG)); + /* uuc_reported = uuc_prev + deltauuc / stepsize */ + uuc_reported = div_s64 (stepsize * chip->last_uuc_uah + + (uuc_now - chip->last_uuc_uah), + stepsize); + uuc_reported = max(0, uuc_reported); + } + } + if (uuc_reported == -EINVAL) + uuc_reported = chip->last_uuc_uah; + + pr_debug("uuc_now = %d uuc_prev = %d stepsize = %d uuc_reported = %d\n", + uuc_now, chip->last_uuc_uah, (int)stepsize, + uuc_reported); + +out: + /* remember the reported uuc */ + chip->last_uuc_uah = uuc_reported; + + /* remember cc_uah */ + chip->last_cc_uah = cc_uah; + + /* remember this time */ + chip->t = now; + + return uuc_reported; +} + +/* calculate remainging charge at the time of ocv */ +static int calculate_remaining_charge_uah(struct pm8921_bms_chip *chip, + struct pm8921_soc_params *raw, + int fcc_uah, int batt_temp, + int chargecycles) +{ + int ocv, pc; + + ocv = raw->last_good_ocv_uv; + pc = calculate_pc(chip, ocv, batt_temp, chargecycles); + pr_debug("ocv = %d pc = %d\n", ocv, pc); + return (fcc_uah * pc) / 100; +} + +static void calculate_soc_params(struct pm8921_bms_chip *chip, + struct pm8921_soc_params *raw, + int batt_temp, int chargecycles, + int *fcc_uah, + int *unusable_charge_uah, + int *remaining_charge_uah, + int *cc_uah, + int *rbatt) +{ + int soc_rbatt; + + *fcc_uah = calculate_fcc_uah(chip, batt_temp, chargecycles); + pr_debug("FCC = %uuAh batt_temp = %d, cycles = %d\n", + *fcc_uah, batt_temp, chargecycles); + + + /* calculate remainging charge */ + *remaining_charge_uah = calculate_remaining_charge_uah(chip, raw, + *fcc_uah, batt_temp, chargecycles); + pr_debug("RC = %uuAh\n", *remaining_charge_uah); + + /* calculate cc micro_volt_hour */ + calculate_cc_uah(chip, raw->cc, cc_uah); + pr_debug("cc_uah = %duAh raw->cc = %x cc = %lld after subtracting %x\n", + *cc_uah, raw->cc, + (int64_t)raw->cc - chip->cc_reading_at_100, + chip->cc_reading_at_100); + + soc_rbatt = ((*remaining_charge_uah - *cc_uah) * 100) / *fcc_uah; + if (soc_rbatt < 0) + soc_rbatt = 0; + *rbatt = get_rbatt(chip, soc_rbatt, batt_temp); + + *unusable_charge_uah = calculate_unusable_charge_uah(chip, *rbatt, + *fcc_uah, *cc_uah, soc_rbatt, + batt_temp, + chargecycles); + pr_debug("UUC = %uuAh\n", *unusable_charge_uah); +} + +static int calculate_real_fcc_uah(struct pm8921_bms_chip *chip, + struct pm8921_soc_params *raw, + int batt_temp, int chargecycles, + int *ret_fcc_uah) +{ + int fcc_uah, unusable_charge_uah; + int remaining_charge_uah; + int cc_uah; + int real_fcc_uah; + int rbatt; + + calculate_soc_params(chip, raw, batt_temp, chargecycles, + &fcc_uah, + &unusable_charge_uah, + &remaining_charge_uah, + &cc_uah, + &rbatt); + + real_fcc_uah = remaining_charge_uah - cc_uah; + *ret_fcc_uah = fcc_uah; + pr_debug("real_fcc = %d, RC = %d CC = %d fcc = %d\n", + real_fcc_uah, remaining_charge_uah, cc_uah, fcc_uah); + return real_fcc_uah; +} + +static int bound_soc(int soc) +{ + soc = max(0, soc); + soc = min(100, soc); + return soc; +} + +static int last_soc_est = -EINVAL; +static int adjust_soc(struct pm8921_bms_chip *chip, int soc, int batt_temp, + int rbatt , int fcc_uah, int uuc_uah, int cc_uah) +{ + int ibat_ua = 0, vbat_uv = 0; + int ocv_est_uv = 0, soc_est = 0, pc_est = 0, pc = 0; + int delta_ocv_uv = 0; + int n = 0; + int rc_new_uah = 0; + int pc_new = 0; + int soc_new = 0; + int m = 0; + + pm8921_bms_get_simultaneous_battery_voltage_and_current(&ibat_ua, + &vbat_uv); + + if (ibat_ua < 0) + goto out; + ocv_est_uv = vbat_uv + (ibat_ua * rbatt)/1000; + pc_est = calculate_pc(chip, ocv_est_uv, batt_temp, last_chargecycles); + soc_est = div_s64((s64)fcc_uah * pc_est - uuc_uah*100, + (s64)fcc_uah - uuc_uah); + soc_est = bound_soc(soc_est); + + /* + * do not adjust if soc_est is between 45 and 25 OR soc_est is + * same as what bms calculated + */ + if (is_between(45, 25, soc_est) || soc_est == soc) + goto out; + + if (last_soc_est == -EINVAL) + last_soc_est = soc; + + n = min(200, max(1 , soc + soc_est + last_soc_est)); + /* remember the last soc_est in last_soc_est */ + last_soc_est = soc_est; + + pc = calculate_pc(chip, chip->last_ocv_uv, + batt_temp, last_chargecycles); + if (pc > 0) { + pc_new = calculate_pc(chip, chip->last_ocv_uv - (++m * 1000), + batt_temp, last_chargecycles); + while (pc_new == pc) { + /* start taking 10mV steps */ + m = m + 10; + pc_new = calculate_pc(chip, + chip->last_ocv_uv - (m * 1000), + batt_temp, last_chargecycles); + } + } else { + /* + * pc is already at the lowest point, + * assume 1 millivolt translates to 1% pc + */ + pc = 1; + pc_new = 0; + m = 1; + } + + delta_ocv_uv = div_s64((soc - soc_est) * (s64)m * 1000, + n * (pc - pc_new)); + chip->last_ocv_uv -= delta_ocv_uv; + + if (chip->last_ocv_uv >= chip->max_voltage_uv) + chip->last_ocv_uv = chip->max_voltage_uv; + + /* calculate the soc based on this new ocv */ + pc_new = calculate_pc(chip, chip->last_ocv_uv, + batt_temp, last_chargecycles); + rc_new_uah = (fcc_uah * pc_new) / 100; + soc_new = (rc_new_uah - cc_uah - uuc_uah)*100 / (fcc_uah - uuc_uah); + soc_new = bound_soc(soc_new); + + /* + * if soc_new is ZERO force it higher so that phone doesnt report soc=0 + * soc = 0 should happen only when soc_est == 0 + */ + if (soc_new == 0 && soc_est != 0) + soc_new = 1; + + soc = soc_new; + +out: + pr_debug("ibat_ua = %d, vbat_uv = %d, ocv_est_uv = %d, pc_est = %d, " + "soc_est = %d, n = %d, delta_ocv_uv = %d, last_ocv_uv = %d, " + "pc_new = %d, soc_new = %d\n", + ibat_ua, vbat_uv, ocv_est_uv, pc_est, + soc_est, n, delta_ocv_uv, chip->last_ocv_uv, + pc_new, soc_new); + + return soc; +} + +/* + * Remaining Usable Charge = remaining_charge (charge at ocv instance) + * - coloumb counter charge + * - unusable charge (due to battery resistance) + * SOC% = (remaining usable charge/ fcc - usable_charge); + */ +static int calculate_state_of_charge(struct pm8921_bms_chip *chip, + struct pm8921_soc_params *raw, + int batt_temp, int chargecycles) +{ + int remaining_usable_charge_uah, fcc_uah, unusable_charge_uah; + int remaining_charge_uah, soc; + int cc_uah; + int rbatt; + + calculate_soc_params(chip, raw, batt_temp, chargecycles, + &fcc_uah, + &unusable_charge_uah, + &remaining_charge_uah, + &cc_uah, + &rbatt); + + /* calculate remaining usable charge */ + remaining_usable_charge_uah = remaining_charge_uah + - cc_uah + - unusable_charge_uah; + + pr_debug("RUC = %duAh\n", remaining_usable_charge_uah); + if (fcc_uah - unusable_charge_uah <= 0) { + pr_warn("FCC = %duAh, UUC = %duAh forcing soc = 0\n", + fcc_uah, unusable_charge_uah); + soc = 0; + } else { + soc = (remaining_usable_charge_uah * 100) + / (fcc_uah - unusable_charge_uah); + } + + if (soc > 100) + soc = 100; + pr_debug("SOC = %u%%\n", soc); + + if (bms_fake_battery != -EINVAL) { + pr_debug("Returning Fake SOC = %d%%\n", bms_fake_battery); + return bms_fake_battery; + } + + if (soc < 0) { + pr_err("bad rem_usb_chg = %d rem_chg %d," + "cc_uah %d, unusb_chg %d\n", + remaining_usable_charge_uah, + remaining_charge_uah, + cc_uah, unusable_charge_uah); + + pr_err("for bad rem_usb_chg last_ocv_uv = %d" + "chargecycles = %d, batt_temp = %d" + "fcc = %d soc =%d\n", + chip->last_ocv_uv, chargecycles, batt_temp, + fcc_uah, soc); + soc = 0; + } + + soc = adjust_soc(chip, soc, batt_temp, rbatt, + fcc_uah, unusable_charge_uah, cc_uah); + + if (last_soc == -EINVAL || soc <= last_soc) { + last_soc = soc; + } else { + /* + * soc > last_soc + * the device must be charging for reporting a higher soc, if + * not ignore this soc and continue reporting the last_soc + */ + if (the_chip->start_percent != -EINVAL) + last_soc = soc; + else + pr_debug("soc = %d reporting last_soc = %d\n", soc, + last_soc); + } + + pr_debug("Reported SOC = %u%%\n", last_soc); + return last_soc; +} +#define MIN_DELTA_625_UV 1000 +static void calib_hkadc(struct pm8921_bms_chip *chip) +{ + int voltage, rc; + struct pm8xxx_adc_chan_result result; + int usb_chg; + int this_delta; + + rc = pm8xxx_adc_read(the_chip->ref1p25v_channel, &result); + if (rc) { + pr_err("ADC failed for 1.25volts rc = %d\n", rc); + return; + } + voltage = xoadc_reading_to_microvolt(result.adc_code); + + pr_debug("result 1.25v = 0x%x, voltage = %duV adc_meas = %lld\n", + result.adc_code, voltage, result.measurement); + + chip->xoadc_v125 = voltage; + + rc = pm8xxx_adc_read(the_chip->ref625mv_channel, &result); + if (rc) { + pr_err("ADC failed for 1.25volts rc = %d\n", rc); + return; + } + voltage = xoadc_reading_to_microvolt(result.adc_code); + + usb_chg = usb_chg_plugged_in(); + pr_debug("result 0.625V = 0x%x, voltage = %duV adc_meas = %lld " + "usb_chg = %d\n", + result.adc_code, voltage, result.measurement, + usb_chg); + + if (usb_chg) + chip->xoadc_v0625_usb_present = voltage; + else + chip->xoadc_v0625_usb_absent = voltage; + + chip->xoadc_v0625 = voltage; + if (chip->xoadc_v0625_usb_present && chip->xoadc_v0625_usb_absent) { + this_delta = chip->xoadc_v0625_usb_present + - chip->xoadc_v0625_usb_absent; + pr_debug("this_delta= %duV\n", this_delta); + if (this_delta > MIN_DELTA_625_UV) + last_usb_cal_delta_uv = this_delta; + pr_debug("625V_present= %d, 625V_absent= %d, delta = %duV\n", + chip->xoadc_v0625_usb_present, + chip->xoadc_v0625_usb_absent, + last_usb_cal_delta_uv); + } +} + +static void calibrate_hkadc_work(struct work_struct *work) +{ + struct pm8921_bms_chip *chip = container_of(work, + struct pm8921_bms_chip, calib_hkadc_work); + + calib_hkadc(chip); +} + +void pm8921_bms_calibrate_hkadc(void) +{ + schedule_work(&the_chip->calib_hkadc_work); +} + +static void calibrate_ccadc_work(struct work_struct *work) +{ + struct pm8921_bms_chip *chip = container_of(work, + struct pm8921_bms_chip, calib_ccadc_work.work); + + pm8xxx_calib_ccadc(); + calib_hkadc(chip); + schedule_delayed_work(&chip->calib_ccadc_work, + round_jiffies_relative(msecs_to_jiffies + (chip->calib_delay_ms))); +} + +int pm8921_bms_get_vsense_avg(int *result) +{ + int rc = -EINVAL; + + if (the_chip) { + mutex_lock(&the_chip->bms_output_lock); + pm_bms_lock_output_data(the_chip); + rc = read_vsense_avg(the_chip, result); + pm_bms_unlock_output_data(the_chip); + mutex_unlock(&the_chip->bms_output_lock); + } + + pr_err("called before initialization\n"); + return rc; +} +EXPORT_SYMBOL(pm8921_bms_get_vsense_avg); + +int pm8921_bms_get_battery_current(int *result_ua) +{ + int vsense; + + if (!the_chip) { + pr_err("called before initialization\n"); + return -EINVAL; + } + if (the_chip->r_sense == 0) { + pr_err("r_sense is zero\n"); + return -EINVAL; + } + + mutex_lock(&the_chip->bms_output_lock); + pm_bms_lock_output_data(the_chip); + read_vsense_avg(the_chip, &vsense); + pm_bms_unlock_output_data(the_chip); + mutex_unlock(&the_chip->bms_output_lock); + pr_debug("vsense=%duV\n", vsense); + /* cast for signed division */ + *result_ua = vsense * 1000 / (int)the_chip->r_sense; + pr_debug("ibat=%duA\n", *result_ua); + return 0; +} +EXPORT_SYMBOL(pm8921_bms_get_battery_current); + +int pm8921_bms_get_percent_charge(void) +{ + int batt_temp, rc; + struct pm8xxx_adc_chan_result result; + struct pm8921_soc_params raw; + int soc; + + if (!the_chip) { + pr_err("called before initialization\n"); + return -EINVAL; + } + + rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + the_chip->batt_temp_channel, rc); + return rc; + } + pr_debug("batt_temp phy = %lld meas = 0x%llx", result.physical, + result.measurement); + batt_temp = (int)result.physical; + + mutex_lock(&the_chip->last_ocv_uv_mutex); + read_soc_params_raw(the_chip, &raw); + + soc = calculate_state_of_charge(the_chip, &raw, + batt_temp, last_chargecycles); + mutex_unlock(&the_chip->last_ocv_uv_mutex); + return soc; +} +EXPORT_SYMBOL_GPL(pm8921_bms_get_percent_charge); + +int pm8921_bms_get_rbatt(void) +{ + int batt_temp, rc; + struct pm8xxx_adc_chan_result result; + struct pm8921_soc_params raw; + int fcc_uah; + int unusable_charge_uah; + int remaining_charge_uah; + int cc_uah; + int rbatt; + + if (!the_chip) { + pr_err("called before initialization\n"); + return -EINVAL; + } + + rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + the_chip->batt_temp_channel, rc); + return rc; + } + pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + batt_temp = (int)result.physical; + + mutex_lock(&the_chip->last_ocv_uv_mutex); + + read_soc_params_raw(the_chip, &raw); + + calculate_soc_params(the_chip, &raw, batt_temp, last_chargecycles, + &fcc_uah, + &unusable_charge_uah, + &remaining_charge_uah, + &cc_uah, + &rbatt); + mutex_unlock(&the_chip->last_ocv_uv_mutex); + + return rbatt; +} +EXPORT_SYMBOL_GPL(pm8921_bms_get_rbatt); + +int pm8921_bms_get_fcc(void) +{ + int batt_temp, rc; + struct pm8xxx_adc_chan_result result; + + if (!the_chip) { + pr_err("called before initialization\n"); + return -EINVAL; + } + + rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + the_chip->batt_temp_channel, rc); + return rc; + } + pr_debug("batt_temp phy = %lld meas = 0x%llx", result.physical, + result.measurement); + batt_temp = (int)result.physical; + return calculate_fcc_uah(the_chip, batt_temp, last_chargecycles); +} +EXPORT_SYMBOL_GPL(pm8921_bms_get_fcc); + +#define IBAT_TOL_MASK 0x0F +#define OCV_TOL_MASK 0xF0 +#define IBAT_TOL_DEFAULT 0x03 +#define IBAT_TOL_NOCHG 0x0F +#define OCV_TOL_DEFAULT 0x20 +#define OCV_TOL_NO_OCV 0x00 +void pm8921_bms_charging_began(void) +{ + int batt_temp, rc; + struct pm8xxx_adc_chan_result result; + struct pm8921_soc_params raw; + + rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + the_chip->batt_temp_channel, rc); + return; + } + pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + batt_temp = (int)result.physical; + + mutex_lock(&the_chip->last_ocv_uv_mutex); + read_soc_params_raw(the_chip, &raw); + + the_chip->start_percent = calculate_state_of_charge(the_chip, &raw, + batt_temp, last_chargecycles); + mutex_unlock(&the_chip->last_ocv_uv_mutex); + + bms_start_percent = the_chip->start_percent; + bms_start_ocv_uv = raw.last_good_ocv_uv; + calculate_cc_uah(the_chip, raw.cc, &bms_start_cc_uah); + pm_bms_masked_write(the_chip, BMS_TOLERANCES, + IBAT_TOL_MASK, IBAT_TOL_DEFAULT); + pr_debug("start_percent = %u%%\n", the_chip->start_percent); +} +EXPORT_SYMBOL_GPL(pm8921_bms_charging_began); + +#define DELTA_FCC_PERCENT 3 +#define MIN_START_PERCENT_FOR_LEARNING 30 +void pm8921_bms_charging_end(int is_battery_full) +{ + int batt_temp, rc; + struct pm8xxx_adc_chan_result result; + struct pm8921_soc_params raw; + + if (the_chip == NULL) + return; + + rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + the_chip->batt_temp_channel, rc); + return; + } + pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + batt_temp = (int)result.physical; + + mutex_lock(&the_chip->last_ocv_uv_mutex); + + read_soc_params_raw(the_chip, &raw); + + calculate_cc_uah(the_chip, raw.cc, &bms_end_cc_uah); + + bms_end_ocv_uv = raw.last_good_ocv_uv; + + if (is_battery_full && the_chip->enable_fcc_learning + && the_chip->start_percent <= MIN_START_PERCENT_FOR_LEARNING) { + int fcc_uah, new_fcc_uah, delta_fcc_uah; + + new_fcc_uah = calculate_real_fcc_uah(the_chip, &raw, + batt_temp, last_chargecycles, + &fcc_uah); + delta_fcc_uah = new_fcc_uah - fcc_uah; + if (delta_fcc_uah < 0) + delta_fcc_uah = -delta_fcc_uah; + + if (delta_fcc_uah * 100 > (DELTA_FCC_PERCENT * fcc_uah)) { + /* new_fcc_uah is outside the scope limit it */ + if (new_fcc_uah > fcc_uah) + new_fcc_uah + = (fcc_uah + + (DELTA_FCC_PERCENT * fcc_uah) / 100); + else + new_fcc_uah + = (fcc_uah - + (DELTA_FCC_PERCENT * fcc_uah) / 100); + + pr_debug("delta_fcc=%d > %d percent of fcc=%d" + "restring it to %d\n", + delta_fcc_uah, DELTA_FCC_PERCENT, + fcc_uah, new_fcc_uah); + } + + last_real_fcc_mah = new_fcc_uah/1000; + last_real_fcc_batt_temp = batt_temp; + readjust_fcc_table(); + + } + + if (is_battery_full) { + the_chip->ocv_reading_at_100 = raw.last_good_ocv_raw; + the_chip->cc_reading_at_100 = raw.cc; + + the_chip->last_ocv_uv = the_chip->max_voltage_uv; + raw.last_good_ocv_uv = the_chip->max_voltage_uv; + /* + * since we are treating this as an ocv event + * forget the old cc value + */ + the_chip->last_cc_uah = 0; + pr_debug("EOC ocv_reading = 0x%x cc = 0x%x\n", + the_chip->ocv_reading_at_100, + the_chip->cc_reading_at_100); + } + + the_chip->end_percent = calculate_state_of_charge(the_chip, &raw, + batt_temp, last_chargecycles); + mutex_unlock(&the_chip->last_ocv_uv_mutex); + + bms_end_percent = the_chip->end_percent; + + if (the_chip->end_percent > the_chip->start_percent) { + last_charge_increase += + the_chip->end_percent - the_chip->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", + the_chip->end_percent, + last_charge_increase, + last_chargecycles); + the_chip->start_percent = -EINVAL; + the_chip->end_percent = -EINVAL; + pm_bms_masked_write(the_chip, BMS_TOLERANCES, + IBAT_TOL_MASK, IBAT_TOL_NOCHG); +} +EXPORT_SYMBOL_GPL(pm8921_bms_charging_end); + +int pm8921_bms_stop_ocv_updates(struct pm8921_bms_chip *chip) +{ + pr_debug("stopping ocv updates\n"); + return pm_bms_masked_write(chip, BMS_TOLERANCES, + OCV_TOL_MASK, OCV_TOL_NO_OCV); +} +EXPORT_SYMBOL_GPL(pm8921_bms_stop_ocv_updates); + +int pm8921_bms_start_ocv_updates(struct pm8921_bms_chip *chip) +{ + pr_debug("stopping ocv updates\n"); + return pm_bms_masked_write(chip, BMS_TOLERANCES, + OCV_TOL_MASK, OCV_TOL_DEFAULT); +} +EXPORT_SYMBOL_GPL(pm8921_bms_start_ocv_updates); + +static irqreturn_t pm8921_bms_sbi_write_ok_handler(int irq, void *data) +{ + pr_debug("irq = %d triggered", irq); + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_cc_thr_handler(int irq, void *data) +{ + pr_debug("irq = %d triggered", irq); + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_vsense_thr_handler(int irq, void *data) +{ + pr_debug("irq = %d triggered", irq); + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_vsense_for_r_handler(int irq, void *data) +{ + pr_debug("irq = %d triggered", irq); + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_ocv_for_r_handler(int irq, void *data) +{ + struct pm8921_bms_chip *chip = data; + + pr_debug("irq = %d triggered", irq); + schedule_work(&chip->calib_hkadc_work); + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_good_ocv_handler(int irq, void *data) +{ + struct pm8921_bms_chip *chip = data; + + pr_debug("irq = %d triggered", irq); + schedule_work(&chip->calib_hkadc_work); + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_vsense_avg_handler(int irq, void *data) +{ + pr_debug("irq = %d triggered", irq); + 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; +} + +static int pm8921_bms_suspend(struct device *dev) +{ + int rc; + struct pm8xxx_adc_chan_result result; + struct pm8921_bms_chip *chip = dev_get_drvdata(dev); + struct pm8921_soc_params raw; + int fcc_uah; + int remaining_charge_uah; + int cc_uah; + + chip->batt_temp_suspend = 0; + rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + chip->batt_temp_channel, rc); + } + chip->batt_temp_suspend = (int)result.physical; + + mutex_lock(&chip->last_ocv_uv_mutex); + read_soc_params_raw(chip, &raw); + + fcc_uah = calculate_fcc_uah(chip, + chip->batt_temp_suspend, last_chargecycles); + pr_debug("FCC = %uuAh batt_temp = %d, cycles = %d\n", + fcc_uah, chip->batt_temp_suspend, last_chargecycles); + /* calculate remainging charge */ + remaining_charge_uah = calculate_remaining_charge_uah(chip, &raw, + fcc_uah, chip->batt_temp_suspend, + last_chargecycles); + pr_debug("RC = %uuAh\n", remaining_charge_uah); + + /* calculate cc micro_volt_hour */ + calculate_cc_uah(chip, raw.cc, &cc_uah); + pr_debug("cc_uah = %duAh raw->cc = %x cc = %lld after subtracting %x\n", + cc_uah, raw.cc, + (int64_t)raw.cc - chip->cc_reading_at_100, + chip->cc_reading_at_100); + chip->soc_rbatt_suspend = ((remaining_charge_uah - cc_uah) * 100) + / fcc_uah; + mutex_unlock(&chip->last_ocv_uv_mutex); + + return 0; +} + +#define DELTA_RBATT_PERCENT 10 +static int pm8921_bms_resume(struct device *dev) +{ + struct pm8921_rbatt_params raw; + struct pm8921_bms_chip *chip = dev_get_drvdata(dev); + int rbatt; + int expected_rbatt; + int scalefactor; + int delta_rbatt; + + read_rbatt_params_raw(chip, &raw); + rbatt = calculate_rbatt_resume(chip, &raw); + + if (rbatt < 0) + return 0; + + expected_rbatt + = (last_rbatt < 0) ? chip->default_rbatt_mohm : last_rbatt; + + if (chip->rbatt_sf_lut) { + scalefactor = interpolate_scalingfactor(chip, + chip->rbatt_sf_lut, + chip->batt_temp_suspend / 10, + chip->soc_rbatt_suspend); + rbatt = rbatt * 100 / scalefactor; + } + + delta_rbatt = expected_rbatt - rbatt; + if (delta_rbatt) + delta_rbatt = -delta_rbatt; + /* + * only update last_rbatt if rbatt is within some + * percent of expected_rbatt + */ + if (delta_rbatt * 100 <= DELTA_RBATT_PERCENT * expected_rbatt) + last_rbatt = rbatt; + + return 0; +} + +static const struct dev_pm_ops pm8921_pm_ops = { + .suspend = pm8921_bms_suspend, + .resume = pm8921_bms_resume, +}; +#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); + } + + /* The charger will call start charge later if usb is present */ + pm_bms_masked_write(chip, BMS_TOLERANCES, + IBAT_TOL_MASK, IBAT_TOL_NOCHG); + return 0; +} + +static void check_initial_ocv(struct pm8921_bms_chip *chip) +{ + int ocv_uv, rc; + int16_t ocv_raw; + int usb_chg; + + /* + * Check if a ocv is available in bms hw, + * if not compute it here at boot time and save it + * in the last_ocv_uv. + */ + ocv_uv = 0; + pm_bms_read_output_data(chip, LAST_GOOD_OCV_VALUE, &ocv_raw); + usb_chg = usb_chg_plugged_in(); + rc = convert_vbatt_raw_to_uv(chip, usb_chg, ocv_raw, &ocv_uv); + if (rc || ocv_uv == 0) { + rc = adc_based_ocv(chip, &ocv_uv); + if (rc) { + pr_err("failed to read adc based ocv_uv rc = %d\n", rc); + ocv_uv = DEFAULT_OCV_MICROVOLTS; + } + } + chip->last_ocv_uv = ocv_uv; + pr_debug("ocv_uv = %d last_ocv_uv = %d\n", ocv_uv, chip->last_ocv_uv); +} + +static int64_t read_battery_id(struct pm8921_bms_chip *chip) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_adc_read(chip->batt_id_channel, &result); + if (rc) { + pr_err("error reading batt id channel = %d, rc = %d\n", + chip->vbat_channel, rc); + return rc; + } + pr_debug("batt_id phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + return result.adc_code; +} + +#define PALLADIUM_ID_MIN 0x7F40 +#define PALLADIUM_ID_MAX 0x7F5A +#define DESAY_5200_ID_MIN 0x7F7F +#define DESAY_5200_ID_MAX 0x802F +static int set_battery_data(struct pm8921_bms_chip *chip) +{ + int64_t battery_id; + + if (chip->batt_type == BATT_DESAY) + goto desay; + else if (chip->batt_type == BATT_PALLADIUM) + goto palladium; + + battery_id = read_battery_id(chip); + if (battery_id < 0) { + pr_err("cannot read battery id err = %lld\n", battery_id); + return battery_id; + } + + if (is_between(PALLADIUM_ID_MIN, PALLADIUM_ID_MAX, battery_id)) { + goto palladium; + } else if (is_between(DESAY_5200_ID_MIN, DESAY_5200_ID_MAX, + battery_id)) { + goto desay; + } else { + pr_warn("invalid battid, palladium 1500 assumed batt_id %llx\n", + battery_id); + goto palladium; + } + +palladium: + chip->fcc = palladium_1500_data.fcc; + chip->fcc_temp_lut = palladium_1500_data.fcc_temp_lut; + chip->fcc_sf_lut = palladium_1500_data.fcc_sf_lut; + chip->pc_temp_ocv_lut = palladium_1500_data.pc_temp_ocv_lut; + chip->pc_sf_lut = palladium_1500_data.pc_sf_lut; + chip->rbatt_sf_lut = palladium_1500_data.rbatt_sf_lut; + chip->default_rbatt_mohm + = palladium_1500_data.default_rbatt_mohm; + chip->delta_rbatt_mohm = palladium_1500_data.delta_rbatt_mohm; + return 0; +desay: + chip->fcc = desay_5200_data.fcc; + chip->fcc_temp_lut = desay_5200_data.fcc_temp_lut; + chip->pc_temp_ocv_lut = desay_5200_data.pc_temp_ocv_lut; + chip->pc_sf_lut = desay_5200_data.pc_sf_lut; + chip->rbatt_sf_lut = desay_5200_data.rbatt_sf_lut; + chip->default_rbatt_mohm = desay_5200_data.default_rbatt_mohm; + chip->delta_rbatt_mohm = desay_5200_data.delta_rbatt_mohm; + return 0; +} + +enum bms_request_operation { + CALC_RBATT, + CALC_FCC, + CALC_PC, + CALC_SOC, + CALIB_HKADC, + CALIB_CCADC, + GET_VBAT_VSENSE_SIMULTANEOUS, + STOP_OCV, + START_OCV, +}; + +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; + int ibat_ua, vbat_uv; + struct pm8921_soc_params raw; + struct pm8921_rbatt_params rraw; + + read_soc_params_raw(the_chip, &raw); + read_rbatt_params_raw(the_chip, &rraw); + + *val = 0; + + /* global irq number passed in via data */ + switch (param) { + case CALC_RBATT: + *val = calculate_rbatt_resume(the_chip, &rraw); + break; + case CALC_FCC: + *val = calculate_fcc_uah(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, &raw, + test_batt_temp, test_chargecycle); + break; + case CALIB_HKADC: + /* reading this will trigger calibration */ + *val = 0; + calib_hkadc(the_chip); + break; + case CALIB_CCADC: + /* reading this will trigger calibration */ + *val = 0; + pm8xxx_calib_ccadc(); + break; + case GET_VBAT_VSENSE_SIMULTANEOUS: + /* reading this will call simultaneous vbat and vsense */ + *val = + pm8921_bms_get_simultaneous_battery_voltage_and_current( + &ibat_ua, + &vbat_uv); + default: + ret = -EINVAL; + } + return ret; +} + +static int set_calc(void *data, u64 val) +{ + int param = (int)data; + int ret = 0; + + switch (param) { + case STOP_OCV: + pm8921_bms_stop_ocv_updates(the_chip); + break; + case START_OCV: + pm8921_bms_start_ocv_updates(the_chip); + break; + default: + ret = -EINVAL; + } + return ret; +} +DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, set_calc, "%llu\n"); + +static int get_reading(void *data, u64 * val) +{ + int param = (int)data; + int ret = 0; + struct pm8921_soc_params raw; + struct pm8921_rbatt_params rraw; + + read_soc_params_raw(the_chip, &raw); + read_rbatt_params_raw(the_chip, &rraw); + + *val = 0; + + switch (param) { + case CC_MSB: + case CC_LSB: + *val = raw.cc; + break; + case LAST_GOOD_OCV_VALUE: + *val = raw.last_good_ocv_uv; + break; + case VBATT_FOR_RBATT: + *val = rraw.vbatt_for_rbatt_uv; + break; + case VSENSE_FOR_RBATT: + *val = rraw.vsense_for_rbatt_uv; + break; + case OCV_FOR_RBATT: + *val = rraw.ocv_for_rbatt_uv; + break; + case VSENSE_AVG: + read_vsense_avg(the_chip, (uint *)val); + break; + default: + ret = -EINVAL; + } + return ret; +} +DEFINE_SIMPLE_ATTRIBUTE(reading_fops, get_reading, NULL, "%lld\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("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("read_vsense_avg", 0644, chip->dent, + (void *)VSENSE_AVG, &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); + debugfs_create_file("calib_hkadc", 0644, chip->dent, + (void *)CALIB_HKADC, &calc_fops); + debugfs_create_file("calib_ccadc", 0644, chip->dent, + (void *)CALIB_CCADC, &calc_fops); + debugfs_create_file("stop_ocv", 0644, chip->dent, + (void *)STOP_OCV, &calc_fops); + debugfs_create_file("start_ocv", 0644, chip->dent, + (void *)START_OCV, &calc_fops); + + debugfs_create_file("simultaneous", 0644, chip->dent, + (void *)GET_VBAT_VSENSE_SIMULTANEOUS, &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); + } +} + +#define REG_SBI_CONFIG 0x04F +#define PAGE3_ENABLE_MASK 0x6 +#define PROGRAM_REV_MASK 0x0F +#define PROGRAM_REV 0x9 +static int read_ocv_trim(struct pm8921_bms_chip *chip) +{ + int rc; + u8 reg, sbi_config; + + rc = pm8xxx_readb(chip->dev->parent, REG_SBI_CONFIG, &sbi_config); + if (rc) { + pr_err("error = %d reading sbi config reg\n", rc); + return rc; + } + + reg = sbi_config | PAGE3_ENABLE_MASK; + rc = pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, reg); + if (rc) { + pr_err("error = %d writing sbi config reg\n", rc); + return rc; + } + + rc = pm8xxx_readb(chip->dev->parent, TEST_PROGRAM_REV, ®); + if (rc) + pr_err("Error %d reading %d addr %d\n", + rc, reg, TEST_PROGRAM_REV); + pr_err("program rev reg is 0x%x\n", reg); + reg &= PROGRAM_REV_MASK; + + /* If the revision is equal or higher do not adjust trim delta */ + if (reg >= PROGRAM_REV) { + chip->amux_2_trim_delta = 0; + goto restore_sbi_config; + } + + rc = pm8xxx_readb(chip->dev->parent, AMUX_TRIM_2, ®); + if (rc) { + pr_err("error = %d reading trim reg\n", rc); + return rc; + } + + pr_err("trim reg is 0x%x\n", reg); + chip->amux_2_trim_delta = abs(0x49 - reg); + pr_err("trim delta is %d\n", chip->amux_2_trim_delta); + +restore_sbi_config: + rc = pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, sbi_config); + if (rc) { + pr_err("error = %d writing sbi config reg\n", rc); + return rc; + } + + return 0; +} + +static int __devinit pm8921_bms_probe(struct platform_device *pdev) +{ + int rc = 0; + int vbatt; + 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; + } + + mutex_init(&chip->bms_output_lock); + mutex_init(&chip->last_ocv_uv_mutex); + 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->max_voltage_uv = pdata->max_voltage_uv; + chip->batt_type = pdata->battery_type; + chip->rconn_mohm = pdata->rconn_mohm; + chip->start_percent = -EINVAL; + chip->end_percent = -EINVAL; + rc = set_battery_data(chip); + if (rc) { + pr_err("%s bad battery data %d\n", __func__, rc); + goto free_chip; + } + + if (chip->pc_temp_ocv_lut == NULL) { + pr_err("temp ocv lut table is NULL\n"); + rc = -EINVAL; + goto free_chip; + } + + /* set defaults in the battery data */ + if (chip->default_rbatt_mohm <= 0) + chip->default_rbatt_mohm = DEFAULT_RBATT_MOHMS; + + chip->batt_temp_channel = pdata->bms_cdata.batt_temp_channel; + chip->vbat_channel = pdata->bms_cdata.vbat_channel; + chip->ref625mv_channel = pdata->bms_cdata.ref625mv_channel; + chip->ref1p25v_channel = pdata->bms_cdata.ref1p25v_channel; + chip->batt_id_channel = pdata->bms_cdata.batt_id_channel; + chip->revision = pm8xxx_get_revision(chip->dev->parent); + chip->enable_fcc_learning = pdata->enable_fcc_learning; + INIT_WORK(&chip->calib_hkadc_work, calibrate_hkadc_work); + + rc = request_irqs(chip, pdev); + if (rc) { + pr_err("couldn't register interrupts rc = %d\n", rc); + goto free_chip; + } + + rc = pm8921_bms_hw_init(chip); + if (rc) { + pr_err("couldn't init hardware rc = %d\n", rc); + goto free_irqs; + } + + platform_set_drvdata(pdev, chip); + the_chip = chip; + create_debugfs_entries(chip); + + rc = read_ocv_trim(chip); + if (rc) { + pr_err("couldn't adjust ocv_trim rc= %d\n", rc); + goto free_irqs; + } + check_initial_ocv(chip); + + INIT_DELAYED_WORK(&chip->calib_ccadc_work, calibrate_ccadc_work); + /* begin calibration only on chips > 2.0 */ + if (chip->revision >= PM8XXX_REVISION_8921_2p0) + schedule_delayed_work(&chip->calib_ccadc_work, 0); + + /* initial hkadc calibration */ + schedule_work(&chip->calib_hkadc_work); + /* enable the vbatt reading interrupts for scheduling hkadc calib */ + pm8921_bms_enable_irq(chip, PM8921_BMS_GOOD_OCV); + pm8921_bms_enable_irq(chip, PM8921_BMS_OCV_FOR_R); + + get_battery_uvolts(chip, &vbatt); + pr_info("OK battery_capacity_at_boot=%d volt = %d ocv = %d\n", + pm8921_bms_get_percent_charge(), + vbatt, chip->last_ocv_uv); + return 0; + +free_irqs: + free_irqs(chip); +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); + kfree(chip->adjusted_fcc_temp_lut); + 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, + .pm = &pm8921_pm_ops + }, +}; + +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 0000000000000000000000000000000000000000..c983389eb35690ce226192f01e894a64f9ed13ad --- /dev/null +++ b/drivers/power/pm8921-charger.c @@ -0,0 +1,3973 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#define IUSB_FINE_RES 0x2B6 + +/* check EOC every 10 seconds */ +#define EOC_CHECK_PERIOD_MS 10000 +/* check for USB unplug every 200 msecs */ +#define UNPLUG_CHECK_WAIT_PERIOD_MS 200 + +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, +}; + +struct fsm_state_to_batt_status { + enum chg_fsm_state fsm_state; + int batt_state; +}; + +static struct fsm_state_to_batt_status map[] = { + {FSM_STATE_OFF_0, POWER_SUPPLY_STATUS_UNKNOWN}, + {FSM_STATE_BATFETDET_START_12, POWER_SUPPLY_STATUS_UNKNOWN}, + {FSM_STATE_BATFETDET_END_16, POWER_SUPPLY_STATUS_UNKNOWN}, + /* + * for CHG_HIGHI_1 report NOT_CHARGING if battery missing, + * too hot/cold, charger too hot + */ + {FSM_STATE_ON_CHG_HIGHI_1, POWER_SUPPLY_STATUS_FULL}, + {FSM_STATE_ATC_2A, POWER_SUPPLY_STATUS_CHARGING}, + {FSM_STATE_ATC_2B, POWER_SUPPLY_STATUS_CHARGING}, + {FSM_STATE_ON_BAT_3, POWER_SUPPLY_STATUS_DISCHARGING}, + {FSM_STATE_ATC_FAIL_4, POWER_SUPPLY_STATUS_DISCHARGING}, + {FSM_STATE_DELAY_5, POWER_SUPPLY_STATUS_UNKNOWN }, + {FSM_STATE_ON_CHG_AND_BAT_6, POWER_SUPPLY_STATUS_CHARGING}, + {FSM_STATE_FAST_CHG_7, POWER_SUPPLY_STATUS_CHARGING}, + {FSM_STATE_TRKL_CHG_8, POWER_SUPPLY_STATUS_CHARGING}, + {FSM_STATE_CHG_FAIL_9, POWER_SUPPLY_STATUS_DISCHARGING}, + {FSM_STATE_EOC_10, POWER_SUPPLY_STATUS_FULL}, + {FSM_STATE_ON_CHG_VREGOK_11, POWER_SUPPLY_STATUS_NOT_CHARGING}, + {FSM_STATE_ATC_PAUSE_13, POWER_SUPPLY_STATUS_NOT_CHARGING}, + {FSM_STATE_FAST_CHG_PAUSE_14, POWER_SUPPLY_STATUS_NOT_CHARGING}, + {FSM_STATE_TRKL_CHG_PAUSE_15, POWER_SUPPLY_STATUS_NOT_CHARGING}, + {FSM_STATE_START_BOOT, POWER_SUPPLY_STATUS_NOT_CHARGING}, + {FSM_STATE_FLCB_VREGOK, POWER_SUPPLY_STATUS_NOT_CHARGING}, + {FSM_STATE_FLCB, POWER_SUPPLY_STATUS_NOT_CHARGING}, +}; + +enum chg_regulation_loop { + VDD_LOOP = BIT(3), + BAT_CURRENT_LOOP = BIT(2), + INPUT_CURRENT_LOOP = BIT(1), + INPUT_VOLTAGE_LOOP = BIT(0), + CHG_ALL_LOOPS = VDD_LOOP | BAT_CURRENT_LOOP + | INPUT_CURRENT_LOOP | INPUT_VOLTAGE_LOOP, +}; + +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_battery_full; + int is_charging; + struct work_struct work; +}; + +/** + * struct pm8921_chg_chip -device information + * @dev: device pointer to access the parent + * @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_mv: the max volts the batt should be charged up to + * @min_voltage_mv: the min battery voltage before turning the FETon + * @cool_temp_dc: the cool temp threshold in deciCelcius + * @warm_temp_dc: the warm temp threshold in deciCelcius + * @resume_voltage_delta: the voltage delta from vdd max 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 max_bat_chg_current; + unsigned int pmic_chg_irq[PM_CHG_MAX_INTS]; + unsigned int safety_time; + unsigned int ttrkl_time; + unsigned int update_time; + unsigned int max_voltage_mv; + unsigned int min_voltage_mv; + int cool_temp_dc; + int warm_temp_dc; + unsigned int temp_check_period; + unsigned int cool_bat_chg_current; + unsigned int warm_bat_chg_current; + unsigned int cool_bat_voltage; + unsigned int warm_bat_voltage; + unsigned int is_bat_cool; + unsigned int is_bat_warm; + unsigned int resume_voltage_delta; + unsigned int term_current; + unsigned int vbat_channel; + unsigned int batt_temp_channel; + unsigned int batt_id_channel; + struct power_supply usb_psy; + struct power_supply dc_psy; + struct power_supply *ext_psy; + struct power_supply batt_psy; + struct dentry *dent; + struct bms_notify bms_notify; + bool keep_btm_on_suspend; + bool ext_charging; + bool ext_charge_done; + bool iusb_fine_res; + DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS); + struct work_struct battery_id_valid_work; + int64_t batt_id_min; + int64_t batt_id_max; + int trkl_voltage; + int weak_voltage; + int trkl_current; + int weak_current; + int vin_min; + unsigned int *thermal_mitigation; + int thermal_levels; + struct delayed_work update_heartbeat_work; + struct delayed_work eoc_work; + struct delayed_work unplug_check_work; + struct delayed_work vin_collapse_check_work; + struct wake_lock eoc_wake_lock; + enum pm8921_chg_cold_thr cold_thr; + enum pm8921_chg_hot_thr hot_thr; + int rconn_mohm; + enum pm8921_chg_led_src_config led_src_config; +}; + +/* user space parameter to limit usb current */ +static unsigned int usb_max_current; +/* + * usb_target_ma is used for wall charger + * adaptive input current limiting only. Use + * pm_iusbmax_get() to get current maximum usb current setting. + */ +static int usb_target_ma; +static int charging_disabled; +static int thermal_mitigation; + +static struct pm8921_chg_chip *the_chip; + +static struct pm8xxx_adc_arb_btm_param btm_config; + +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; +} + +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) +{ + return pm_chg_get_rt_status(chip, USBIN_VALID_IRQ); +} + +/* Treat OverVoltage/UnderVoltage as source missing */ +static int is_dc_chg_plugged_in(struct pm8921_chg_chip *chip) +{ + return pm_chg_get_rt_status(chip, DCIN_VALID_IRQ); +} + +#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 READ_BANK_6 0x60 +static int pm_chg_get_regulation_loop(struct pm8921_chg_chip *chip) +{ + u8 temp; + int err; + + temp = READ_BANK_6; + 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; + } + + /* return the lower 4 bits */ + return temp & CHG_ALL_LOOPS; +} + +#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_FAILED_CLEAR BIT(0) +#define ATC_FAILED_CLEAR BIT(1) +static int pm_chg_failed_clear(struct pm8921_chg_chip *chip, int clear) +{ + int rc; + + rc = pm_chg_masked_write(chip, CHG_CNTRL_3, ATC_FAILED_CLEAR, + clear ? ATC_FAILED_CLEAR : 0); + rc |= pm_chg_masked_write(chip, CHG_CNTRL_3, CHG_FAILED_CLEAR, + clear ? CHG_FAILED_CLEAR : 0); + return rc; +} + +#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); +} + +static int pm_is_chg_charge_dis(struct pm8921_chg_chip *chip) +{ + u8 temp; + + pm8xxx_readb(chip->dev->parent, CHG_CNTRL, &temp); + return temp & CHG_CHARGE_DIS_BIT; +} +#define PM8921_CHG_V_MIN_MV 3240 +#define PM8921_CHG_V_STEP_MV 20 +#define PM8921_CHG_V_STEP_10MV_OFFSET_BIT BIT(7) +#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) +{ + int remainder; + u8 temp = 0; + + 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; + + remainder = voltage % 20; + if (remainder >= 10) { + temp |= PM8921_CHG_V_STEP_10MV_OFFSET_BIT; + } + + pr_debug("voltage=%d setting %02x\n", voltage, temp); + return pm8xxx_writeb(chip->dev->parent, CHG_VDD_MAX, temp); +} + +static int pm_chg_vddmax_get(struct pm8921_chg_chip *chip, int *voltage) +{ + u8 temp; + int rc; + + rc = pm8xxx_readb(chip->dev->parent, CHG_VDD_MAX, &temp); + if (rc) { + pr_err("rc = %d while reading vdd max\n", rc); + *voltage = 0; + return rc; + } + *voltage = (int)(temp & PM8921_CHG_V_MASK) * PM8921_CHG_V_STEP_MV + + PM8921_CHG_V_MIN_MV; + if (temp & PM8921_CHG_V_STEP_10MV_OFFSET_BIT) + *voltage = *voltage + 10; + return 0; +} + +static int pm_chg_vddmax_set(struct pm8921_chg_chip *chip, int voltage) +{ + int current_mv, ret, steps, i; + bool increase; + + ret = 0; + + if (voltage < PM8921_CHG_VDDMAX_MIN + || voltage > PM8921_CHG_VDDMAX_MAX) { + pr_err("bad mV=%d asked to set\n", voltage); + return -EINVAL; + } + + ret = pm_chg_vddmax_get(chip, ¤t_mv); + if (ret) { + pr_err("Failed to read vddmax rc=%d\n", ret); + return -EINVAL; + } + if (current_mv == voltage) + return 0; + + /* Only change in increments when USB is present */ + if (is_usb_chg_plugged_in(chip)) { + if (current_mv < voltage) { + steps = (voltage - current_mv) / PM8921_CHG_V_STEP_MV; + increase = true; + } else { + steps = (current_mv - voltage) / PM8921_CHG_V_STEP_MV; + increase = false; + } + for (i = 0; i < steps; i++) { + if (increase) + current_mv += PM8921_CHG_V_STEP_MV; + else + current_mv -= PM8921_CHG_V_STEP_MV; + ret |= __pm_chg_vddmax_set(chip, current_mv); + } + } + ret |= __pm_chg_vddmax_set(chip, voltage); + return ret; +} + +#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_VINMIN_MIN_MV 3800 +#define PM8921_CHG_VINMIN_STEP_MV 100 +#define PM8921_CHG_VINMIN_USABLE_MAX 6500 +#define PM8921_CHG_VINMIN_USABLE_MIN 4300 +#define PM8921_CHG_VINMIN_MASK 0x1F +static int pm_chg_vinmin_set(struct pm8921_chg_chip *chip, int voltage) +{ + u8 temp; + + if (voltage < PM8921_CHG_VINMIN_USABLE_MIN + || voltage > PM8921_CHG_VINMIN_USABLE_MAX) { + pr_err("bad mV=%d asked to set\n", voltage); + return -EINVAL; + } + temp = (voltage - PM8921_CHG_VINMIN_MIN_MV) / PM8921_CHG_VINMIN_STEP_MV; + pr_debug("voltage=%d setting %02x\n", voltage, temp); + return pm_chg_masked_write(chip, CHG_VIN_MIN, PM8921_CHG_VINMIN_MASK, + temp); +} + +static int pm_chg_vinmin_get(struct pm8921_chg_chip *chip) +{ + u8 temp; + int rc, voltage_mv; + + rc = pm8xxx_readb(chip->dev->parent, CHG_VIN_MIN, &temp); + temp &= PM8921_CHG_VINMIN_MASK; + + voltage_mv = PM8921_CHG_VINMIN_MIN_MV + + (int)temp * PM8921_CHG_VINMIN_STEP_MV; + + return voltage_mv; +} + +#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_MA 50 +#define PM8921_CHG_ITERM_MAX_MA 200 +#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_MA + || chg_current > PM8921_CHG_ITERM_MAX_MA) { + 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_ITERM, PM8921_CHG_ITERM_MASK, + temp); +} + +static int pm_chg_iterm_get(struct pm8921_chg_chip *chip, int *chg_current) +{ + u8 temp; + int rc; + + rc = pm8xxx_readb(chip->dev->parent, CHG_ITERM, &temp); + if (rc) { + pr_err("err=%d reading CHG_ITEM\n", rc); + *chg_current = 0; + return rc; + } + temp &= PM8921_CHG_ITERM_MASK; + *chg_current = (int)temp * PM8921_CHG_ITERM_STEP_MA + + PM8921_CHG_ITERM_MIN_MA; + return 0; +} + +struct usb_ma_limit_entry { + int usb_ma; + u8 value; +}; + +static struct usb_ma_limit_entry usb_ma_table[] = { + {100, 0x0}, + {200, 0x1}, + {500, 0x2}, + {600, 0x3}, + {700, 0x4}, + {800, 0x5}, + {850, 0x6}, + {900, 0x8}, + {950, 0x7}, + {1000, 0x9}, + {1100, 0xA}, + {1200, 0xB}, + {1300, 0xC}, + {1400, 0xD}, + {1500, 0xE}, + {1600, 0xF}, +}; + +#define PM8921_CHG_IUSB_MASK 0x1C +#define PM8921_CHG_IUSB_SHIFT 2 +#define PM8921_CHG_IUSB_MAX 7 +#define PM8921_CHG_IUSB_MIN 0 +#define PM8917_IUSB_FINE_RES BIT(0) +static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int reg_val) +{ + u8 temp, fineres; + int rc; + + fineres = PM8917_IUSB_FINE_RES & usb_ma_table[reg_val].value; + reg_val = usb_ma_table[reg_val].value >> 1; + + if (reg_val < PM8921_CHG_IUSB_MIN || reg_val > PM8921_CHG_IUSB_MAX) { + pr_err("bad mA=%d asked to set\n", reg_val); + return -EINVAL; + } + temp = reg_val << PM8921_CHG_IUSB_SHIFT; + + /* IUSB_FINE_RES */ + if (chip->iusb_fine_res) { + /* Clear IUSB_FINE_RES bit to avoid overshoot */ + rc = pm_chg_masked_write(chip, IUSB_FINE_RES, + PM8917_IUSB_FINE_RES, 0); + + rc |= pm_chg_masked_write(chip, PBL_ACCESS2, + PM8921_CHG_IUSB_MASK, temp); + + if (rc) { + pr_err("Failed to write PBL_ACCESS2 rc=%d\n", rc); + return rc; + } + + if (fineres) { + rc = pm_chg_masked_write(chip, IUSB_FINE_RES, + PM8917_IUSB_FINE_RES, fineres); + if (rc) + pr_err("Failed to write ISUB_FINE_RES rc=%d\n", + rc); + } + } else { + rc = pm_chg_masked_write(chip, PBL_ACCESS2, + PM8921_CHG_IUSB_MASK, temp); + if (rc) + pr_err("Failed to write PBL_ACCESS2 rc=%d\n", rc); + } + + return rc; +} + +static int pm_chg_iusbmax_get(struct pm8921_chg_chip *chip, int *mA) +{ + u8 temp, fineres; + int rc, i; + + fineres = 0; + *mA = 0; + rc = pm8xxx_readb(chip->dev->parent, PBL_ACCESS2, &temp); + if (rc) { + pr_err("err=%d reading PBL_ACCESS2\n", rc); + return rc; + } + + if (chip->iusb_fine_res) { + rc = pm8xxx_readb(chip->dev->parent, IUSB_FINE_RES, &fineres); + if (rc) { + pr_err("err=%d reading IUSB_FINE_RES\n", rc); + return rc; + } + } + temp &= PM8921_CHG_IUSB_MASK; + temp = temp >> PM8921_CHG_IUSB_SHIFT; + + temp = (temp << 1) | (fineres & PM8917_IUSB_FINE_RES); + for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) { + if (usb_ma_table[i].value == temp) + break; + } + + *mA = usb_ma_table[i].usb_ma; + + return rc; +} + +#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); +} + +#define PM8921_CHG_TCHG_MASK 0x7F +#define PM8921_CHG_TCHG_MIN 4 +#define PM8921_CHG_TCHG_MAX 512 +#define PM8921_CHG_TCHG_STEP 4 +static int pm_chg_tchg_max_set(struct pm8921_chg_chip *chip, int minutes) +{ + u8 temp; + + if (minutes < PM8921_CHG_TCHG_MIN || minutes > PM8921_CHG_TCHG_MAX) { + pr_err("bad max minutes =%d asked to set\n", minutes); + return -EINVAL; + } + + temp = (minutes - 1)/PM8921_CHG_TCHG_STEP; + return pm_chg_masked_write(chip, CHG_TCHG_MAX, PM8921_CHG_TCHG_MASK, + temp); +} + +#define PM8921_CHG_TTRKL_MASK 0x1F +#define PM8921_CHG_TTRKL_MIN 1 +#define PM8921_CHG_TTRKL_MAX 64 +static int pm_chg_ttrkl_max_set(struct pm8921_chg_chip *chip, int minutes) +{ + u8 temp; + + if (minutes < PM8921_CHG_TTRKL_MIN || minutes > PM8921_CHG_TTRKL_MAX) { + pr_err("bad max minutes =%d asked to set\n", minutes); + return -EINVAL; + } + + temp = minutes - 1; + return pm_chg_masked_write(chip, CHG_TTRKL_MAX, PM8921_CHG_TTRKL_MASK, + temp); +} + +#define PM8921_CHG_VTRKL_MIN_MV 2050 +#define PM8921_CHG_VTRKL_MAX_MV 2800 +#define PM8921_CHG_VTRKL_STEP_MV 50 +#define PM8921_CHG_VTRKL_SHIFT 4 +#define PM8921_CHG_VTRKL_MASK 0xF0 +static int pm_chg_vtrkl_low_set(struct pm8921_chg_chip *chip, int millivolts) +{ + u8 temp; + + if (millivolts < PM8921_CHG_VTRKL_MIN_MV + || millivolts > PM8921_CHG_VTRKL_MAX_MV) { + pr_err("bad voltage = %dmV asked to set\n", millivolts); + return -EINVAL; + } + + temp = (millivolts - PM8921_CHG_VTRKL_MIN_MV)/PM8921_CHG_VTRKL_STEP_MV; + temp = temp << PM8921_CHG_VTRKL_SHIFT; + return pm_chg_masked_write(chip, CHG_VTRICKLE, PM8921_CHG_VTRKL_MASK, + temp); +} + +#define PM8921_CHG_VWEAK_MIN_MV 2100 +#define PM8921_CHG_VWEAK_MAX_MV 3600 +#define PM8921_CHG_VWEAK_STEP_MV 100 +#define PM8921_CHG_VWEAK_MASK 0x0F +static int pm_chg_vweak_set(struct pm8921_chg_chip *chip, int millivolts) +{ + u8 temp; + + if (millivolts < PM8921_CHG_VWEAK_MIN_MV + || millivolts > PM8921_CHG_VWEAK_MAX_MV) { + pr_err("bad voltage = %dmV asked to set\n", millivolts); + return -EINVAL; + } + + temp = (millivolts - PM8921_CHG_VWEAK_MIN_MV)/PM8921_CHG_VWEAK_STEP_MV; + return pm_chg_masked_write(chip, CHG_VTRICKLE, PM8921_CHG_VWEAK_MASK, + temp); +} + +#define PM8921_CHG_ITRKL_MIN_MA 50 +#define PM8921_CHG_ITRKL_MAX_MA 200 +#define PM8921_CHG_ITRKL_MASK 0x0F +#define PM8921_CHG_ITRKL_STEP_MA 10 +static int pm_chg_itrkl_set(struct pm8921_chg_chip *chip, int milliamps) +{ + u8 temp; + + if (milliamps < PM8921_CHG_ITRKL_MIN_MA + || milliamps > PM8921_CHG_ITRKL_MAX_MA) { + pr_err("bad current = %dmA asked to set\n", milliamps); + return -EINVAL; + } + + temp = (milliamps - PM8921_CHG_ITRKL_MIN_MA)/PM8921_CHG_ITRKL_STEP_MA; + + return pm_chg_masked_write(chip, CHG_ITRICKLE, PM8921_CHG_ITRKL_MASK, + temp); +} + +#define PM8921_CHG_IWEAK_MIN_MA 325 +#define PM8921_CHG_IWEAK_MAX_MA 525 +#define PM8921_CHG_IWEAK_SHIFT 7 +#define PM8921_CHG_IWEAK_MASK 0x80 +static int pm_chg_iweak_set(struct pm8921_chg_chip *chip, int milliamps) +{ + u8 temp; + + if (milliamps < PM8921_CHG_IWEAK_MIN_MA + || milliamps > PM8921_CHG_IWEAK_MAX_MA) { + pr_err("bad current = %dmA asked to set\n", milliamps); + return -EINVAL; + } + + if (milliamps < PM8921_CHG_IWEAK_MAX_MA) + temp = 0; + else + temp = 1; + + temp = temp << PM8921_CHG_IWEAK_SHIFT; + return pm_chg_masked_write(chip, CHG_ITRICKLE, PM8921_CHG_IWEAK_MASK, + temp); +} + +#define PM8921_CHG_BATT_TEMP_THR_COLD BIT(1) +#define PM8921_CHG_BATT_TEMP_THR_COLD_SHIFT 1 +static int pm_chg_batt_cold_temp_config(struct pm8921_chg_chip *chip, + enum pm8921_chg_cold_thr cold_thr) +{ + u8 temp; + + temp = cold_thr << PM8921_CHG_BATT_TEMP_THR_COLD_SHIFT; + temp = temp & PM8921_CHG_BATT_TEMP_THR_COLD; + return pm_chg_masked_write(chip, CHG_CNTRL_2, + PM8921_CHG_BATT_TEMP_THR_COLD, + temp); +} + +#define PM8921_CHG_BATT_TEMP_THR_HOT BIT(0) +#define PM8921_CHG_BATT_TEMP_THR_HOT_SHIFT 0 +static int pm_chg_batt_hot_temp_config(struct pm8921_chg_chip *chip, + enum pm8921_chg_hot_thr hot_thr) +{ + u8 temp; + + temp = hot_thr << PM8921_CHG_BATT_TEMP_THR_HOT_SHIFT; + temp = temp & PM8921_CHG_BATT_TEMP_THR_HOT; + return pm_chg_masked_write(chip, CHG_CNTRL_2, + PM8921_CHG_BATT_TEMP_THR_HOT, + temp); +} + +#define PM8921_CHG_LED_SRC_CONFIG_SHIFT 4 +#define PM8921_CHG_LED_SRC_CONFIG_MASK 0x30 +static int pm_chg_led_src_config(struct pm8921_chg_chip *chip, + enum pm8921_chg_led_src_config led_src_config) +{ + u8 temp; + + if (led_src_config < LED_SRC_GND || + led_src_config > LED_SRC_BYPASS) + return -EINVAL; + + if (led_src_config == LED_SRC_BYPASS) + return 0; + + temp = led_src_config << PM8921_CHG_LED_SRC_CONFIG_SHIFT; + + return pm_chg_masked_write(chip, CHG_CNTRL_3, + PM8921_CHG_LED_SRC_CONFIG_MASK, temp); +} + +static void disable_input_voltage_regulation(struct pm8921_chg_chip *chip) +{ + u8 temp; + int rc; + + rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70); + if (rc) { + pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc); + return; + } + rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp); + if (rc) { + pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc); + return; + } + /* set the input voltage disable bit and the write bit */ + temp |= 0x81; + rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp); + if (rc) { + pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc); + return; + } +} + +static void enable_input_voltage_regulation(struct pm8921_chg_chip *chip) +{ + u8 temp; + int rc; + + rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70); + if (rc) { + pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc); + return; + } + rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp); + if (rc) { + pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc); + return; + } + /* unset the input voltage disable bit */ + temp &= 0xFE; + /* set the write bit */ + temp |= 0x80; + rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp); + if (rc) { + pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc); + return; + } +} + +static int64_t read_battery_id(struct pm8921_chg_chip *chip) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_adc_read(chip->batt_id_channel, &result); + if (rc) { + pr_err("error reading batt id channel = %d, rc = %d\n", + chip->vbat_channel, rc); + return rc; + } + pr_debug("batt_id phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + return result.physical; +} + +static int is_battery_valid(struct pm8921_chg_chip *chip) +{ + int64_t rc; + + if (chip->batt_id_min == 0 && chip->batt_id_max == 0) + return 1; + + rc = read_battery_id(chip); + if (rc < 0) { + pr_err("error reading batt id channel = %d, rc = %lld\n", + chip->vbat_channel, rc); + /* assume battery id is valid when adc error happens */ + return 1; + } + + if (rc < chip->batt_id_min || rc > chip->batt_id_max) { + pr_err("batt_id phy =%lld is not valid\n", rc); + return 0; + } + return 1; +} + +static void check_battery_valid(struct pm8921_chg_chip *chip) +{ + if (is_battery_valid(chip) == 0) { + pr_err("batt_id not valid, disbling charging\n"); + pm_chg_auto_enable(chip, 0); + } else { + pm_chg_auto_enable(chip, !charging_disabled); + } +} + +static void battery_id_valid(struct work_struct *work) +{ + struct pm8921_chg_chip *chip = container_of(work, + struct pm8921_chg_chip, battery_id_valid_work); + + check_battery_valid(chip); +} + +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 pm8921_chg_is_enabled(struct pm8921_chg_chip *chip, int interrupt) +{ + return test_bit(interrupt, chip->enabled_irqs); +} + +static bool is_ext_charging(struct pm8921_chg_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (!chip->ext_psy) + return false; + if (chip->ext_psy->get_property(chip->ext_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &ret)) + return false; + if (ret.intval > POWER_SUPPLY_CHARGE_TYPE_NONE) + return ret.intval; + + return false; +} + +static bool is_ext_trickle_charging(struct pm8921_chg_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (!chip->ext_psy) + return false; + if (chip->ext_psy->get_property(chip->ext_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &ret)) + return false; + if (ret.intval == POWER_SUPPLY_CHARGE_TYPE_TRICKLE) + return true; + + return false; +} + +static int is_battery_charging(int fsm_state) +{ + if (is_ext_charging(the_chip)) + return 1; + + 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(n->is_battery_full); + n->is_battery_full = 0; + } +} + +static void bms_notify_check(struct pm8921_chg_chip *chip) +{ + int fsm_state, new_is_charging; + + 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)); + } +} + +static enum power_supply_property pm_power_props_usb[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static enum power_supply_property pm_power_props_mains[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *pm_power_supplied_to[] = { + "battery", +}; + +#define USB_WALL_THRESHOLD_MA 500 +static int pm_power_get_property_mains(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + /* Check if called before init */ + if (!the_chip) + return -EINVAL; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (charging_disabled) + return 0; + + /* check external charger first before the dc path */ + if (is_ext_charging(the_chip)) { + val->intval = 1; + return 0; + } + + if (pm_is_chg_charge_dis(the_chip)) { + val->intval = 0; + return 0; + } + + if (the_chip->dc_present) { + val->intval = 1; + return 0; + } + + /* USB with max current greater than 500 mA connected */ + if (usb_target_ma > USB_WALL_THRESHOLD_MA) + val->intval = is_usb_chg_plugged_in(the_chip); + return 0; + + break; + default: + return -EINVAL; + } + return 0; +} + +static int pm_power_get_property_usb(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int current_max; + + /* Check if called before init */ + if (!the_chip) + return -EINVAL; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (pm_is_chg_charge_dis(the_chip)) { + val->intval = 0; + } else { + pm_chg_iusbmax_get(the_chip, ¤t_max); + val->intval = current_max; + } + break; + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (charging_disabled) + return 0; + + /* + * if drawing any current from usb is disabled behave + * as if no usb cable is connected + */ + if (pm_is_chg_charge_dis(the_chip)) + return 0; + + /* USB charging */ + if (usb_target_ma < USB_WALL_THRESHOLD_MA) + val->intval = is_usb_chg_plugged_in(the_chip); + else + return 0; + 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, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_ENERGY_FULL, +}; + +static int get_prop_battery_uvolts(struct pm8921_chg_chip *chip) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_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\n", result.physical, + result.measurement); + return (int)result.physical; +} + +static unsigned int voltage_based_capacity(struct pm8921_chg_chip *chip) +{ + unsigned int current_voltage_uv = get_prop_battery_uvolts(chip); + unsigned int current_voltage_mv = current_voltage_uv / 1000; + unsigned int low_voltage = chip->min_voltage_mv; + unsigned int high_voltage = chip->max_voltage_mv; + + if (current_voltage_mv <= low_voltage) + return 0; + else if (current_voltage_mv >= high_voltage) + return 100; + else + return (current_voltage_mv - low_voltage) * 100 + / (high_voltage - low_voltage); +} + +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_batt_capacity(struct pm8921_chg_chip *chip) +{ + int percent_soc; + + if (!get_prop_batt_present(chip)) + percent_soc = voltage_based_capacity(chip); + else + percent_soc = pm8921_bms_get_percent_charge(); + + if (percent_soc == -ENXIO) + percent_soc = voltage_based_capacity(chip); + + if (percent_soc <= 10) + pr_warn("low battery charge = %d%%\n", percent_soc); + + return percent_soc; +} + +static int get_prop_batt_current(struct pm8921_chg_chip *chip) +{ + int result_ua, rc; + + rc = pm8921_bms_get_battery_current(&result_ua); + if (rc == -ENXIO) { + rc = pm8xxx_ccadc_get_battery_current(&result_ua); + } + + if (rc) { + pr_err("unable to get batt current rc = %d\n", rc); + return rc; + } else { + return result_ua; + } +} + +static int get_prop_batt_fcc(struct pm8921_chg_chip *chip) +{ + int rc; + + rc = pm8921_bms_get_fcc(); + if (rc < 0) + pr_err("unable to get batt fcc rc = %d\n", rc); + return rc; +} + +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_charge_type(struct pm8921_chg_chip *chip) +{ + int temp; + + if (!get_prop_batt_present(chip)) + return POWER_SUPPLY_CHARGE_TYPE_NONE; + + if (is_ext_trickle_charging(chip)) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + if (is_ext_charging(chip)) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + + 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 batt_state = POWER_SUPPLY_STATUS_DISCHARGING; + int fsm_state = pm_chg_get_fsm_state(chip); + int i; + + if (chip->ext_psy) { + if (chip->ext_charge_done) + return POWER_SUPPLY_STATUS_FULL; + if (chip->ext_charging) + return POWER_SUPPLY_STATUS_CHARGING; + } + + for (i = 0; i < ARRAY_SIZE(map); i++) + if (map[i].fsm_state == fsm_state) + batt_state = map[i].batt_state; + + if (fsm_state == FSM_STATE_ON_CHG_HIGHI_1) { + if (!pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ) + || !pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ) + || pm_chg_get_rt_status(chip, CHGHOT_IRQ) + || pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ)) + + batt_state = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + return batt_state; +} + +#define MAX_TOLERABLE_BATT_TEMP_DDC 680 +static int get_prop_batt_temp(struct pm8921_chg_chip *chip) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + chip->vbat_channel, rc); + return rc; + } + pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + if (result.physical > MAX_TOLERABLE_BATT_TEMP_DDC) + pr_err("BATT_TEMP= %d > 68degC, device will be shutdown\n", + (int) result.physical); + + return (int)result.physical; +} + +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_mv * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = chip->min_voltage_mv * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_prop_battery_uvolts(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_prop_batt_current(chip); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = get_prop_batt_fcc(chip) * 1000; + 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"); + } +} + +/* assumes vbus_lock is held */ +static void __pm8921_charger_vbus_draw(unsigned int mA) +{ + int i, rc; + if (!the_chip) { + pr_err("called before init\n"); + return; + } + + if (mA >= 0 && mA <= 2) { + usb_chg_current = 0; + rc = pm_chg_iusbmax_set(the_chip, 0); + if (rc) { + pr_err("unable to set iusb to %d rc = %d\n", 0, 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; + } + + /* Check if IUSB_FINE_RES is available */ + if ((usb_ma_table[i].value & PM8917_IUSB_FINE_RES) + && !the_chip->iusb_fine_res) + i--; + if (i < 0) + i = 0; + rc = pm_chg_iusbmax_set(the_chip, i); + if (rc) { + pr_err("unable to set iusb to %d rc = %d\n", i, 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); + + if (!the_chip) { + pr_err("chip not yet initalized\n"); + return; + } + + /* + * Reject VBUS requests if USB connection is the only available + * power source. This makes sure that if booting without + * battery the iusb_max value is not decreased avoiding potential + * brown_outs. + * + * This would also apply when the battery has been + * removed from the running system. + */ + if (!get_prop_batt_present(the_chip) + && !is_dc_chg_plugged_in(the_chip)) { + pr_err("rejected: no other power source connected\n"); + return; + } + + if (usb_max_current && mA > usb_max_current) { + pr_warn("restricting usb current to %d instead of %d\n", + usb_max_current, mA); + mA = usb_max_current; + } + if (usb_target_ma == 0 && mA > USB_WALL_THRESHOLD_MA) + usb_target_ma = mA; + + spin_lock_irqsave(&vbus_lock, flags); + if (the_chip) { + if (mA > USB_WALL_THRESHOLD_MA) + __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA); + else + __pm8921_charger_vbus_draw(mA); + } else { + /* + * called before pmic initialized, + * save this value and use it at probe + */ + if (mA > USB_WALL_THRESHOLD_MA) + usb_chg_current = USB_WALL_THRESHOLD_MA; + else + usb_chg_current = mA; + } + spin_unlock_irqrestore(&vbus_lock, flags); +} +EXPORT_SYMBOL_GPL(pm8921_charger_vbus_draw); + +int pm8921_charger_enable(bool enable) +{ + int rc; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + enable = !!enable; + rc = pm_chg_auto_enable(the_chip, enable); + if (rc) + pr_err("Failed rc=%d\n", rc); + return rc; +} +EXPORT_SYMBOL(pm8921_charger_enable); + +int pm8921_is_usb_chg_plugged_in(void) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + return is_usb_chg_plugged_in(the_chip); +} +EXPORT_SYMBOL(pm8921_is_usb_chg_plugged_in); + +int pm8921_is_dc_chg_plugged_in(void) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + return is_dc_chg_plugged_in(the_chip); +} +EXPORT_SYMBOL(pm8921_is_dc_chg_plugged_in); + +int pm8921_is_battery_present(void) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + return get_prop_batt_present(the_chip); +} +EXPORT_SYMBOL(pm8921_is_battery_present); + +/* + * Disabling the charge current limit causes current + * current limits to have no monitoring. An adequate charger + * capable of supplying high current while sustaining VIN_MIN + * is required if the limiting is disabled. + */ +int pm8921_disable_input_current_limit(bool disable) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + if (disable) { + pr_warn("Disabling input current limit!\n"); + + return pm8xxx_writeb(the_chip->dev->parent, + CHG_BUCK_CTRL_TEST3, 0xF2); + } + return 0; +} +EXPORT_SYMBOL(pm8921_disable_input_current_limit); + +int pm8921_set_max_battery_charge_current(int ma) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + return pm_chg_ibatmax_set(the_chip, ma); +} +EXPORT_SYMBOL(pm8921_set_max_battery_charge_current); + +int pm8921_disable_source_current(bool disable) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + if (disable) + pr_warn("current drawn from chg=0, battery provides current\n"); + return pm_chg_charge_dis(the_chip, disable); +} +EXPORT_SYMBOL(pm8921_disable_source_current); + +int pm8921_regulate_input_voltage(int voltage) +{ + int rc; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + rc = pm_chg_vinmin_set(the_chip, voltage); + + if (rc == 0) + the_chip->vin_min = voltage; + + return rc; +} + +#define USB_OV_THRESHOLD_MASK 0x60 +#define USB_OV_THRESHOLD_SHIFT 5 +int pm8921_usb_ovp_set_threshold(enum pm8921_usb_ov_threshold ov) +{ + u8 temp; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + if (ov > PM_USB_OV_7V) { + pr_err("limiting to over voltage threshold to 7volts\n"); + ov = PM_USB_OV_7V; + } + + temp = USB_OV_THRESHOLD_MASK & (ov << USB_OV_THRESHOLD_SHIFT); + + return pm_chg_masked_write(the_chip, USB_OVP_CONTROL, + USB_OV_THRESHOLD_MASK, temp); +} +EXPORT_SYMBOL(pm8921_usb_ovp_set_threshold); + +#define USB_DEBOUNCE_TIME_MASK 0x06 +#define USB_DEBOUNCE_TIME_SHIFT 1 +int pm8921_usb_ovp_set_hystersis(enum pm8921_usb_debounce_time ms) +{ + u8 temp; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + if (ms > PM_USB_DEBOUNCE_80P5MS) { + pr_err("limiting debounce to 80.5ms\n"); + ms = PM_USB_DEBOUNCE_80P5MS; + } + + temp = USB_DEBOUNCE_TIME_MASK & (ms << USB_DEBOUNCE_TIME_SHIFT); + + return pm_chg_masked_write(the_chip, USB_OVP_CONTROL, + USB_DEBOUNCE_TIME_MASK, temp); +} +EXPORT_SYMBOL(pm8921_usb_ovp_set_hystersis); + +#define USB_OVP_DISABLE_MASK 0x80 +int pm8921_usb_ovp_disable(int disable) +{ + u8 temp = 0; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + if (disable) + temp = USB_OVP_DISABLE_MASK; + + return pm_chg_masked_write(the_chip, USB_OVP_CONTROL, + USB_OVP_DISABLE_MASK, temp); +} + +bool pm8921_is_battery_charging(int *source) +{ + int fsm_state, is_charging, dc_present, usb_present; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + fsm_state = pm_chg_get_fsm_state(the_chip); + is_charging = is_battery_charging(fsm_state); + if (is_charging == 0) { + *source = PM8921_CHG_SRC_NONE; + return is_charging; + } + + if (source == NULL) + return is_charging; + + /* the battery is charging, the source is requested, find it */ + dc_present = is_dc_chg_plugged_in(the_chip); + usb_present = is_usb_chg_plugged_in(the_chip); + + if (dc_present && !usb_present) + *source = PM8921_CHG_SRC_DC; + + if (usb_present && !dc_present) + *source = PM8921_CHG_SRC_USB; + + if (usb_present && dc_present) + /* + * The system always chooses dc for charging since it has + * higher priority. + */ + *source = PM8921_CHG_SRC_DC; + + return is_charging; +} +EXPORT_SYMBOL(pm8921_is_battery_charging); + +int pm8921_set_usb_power_supply_type(enum power_supply_type type) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + if (type < POWER_SUPPLY_TYPE_USB) + return -EINVAL; + + the_chip->usb_psy.type = type; + power_supply_changed(&the_chip->usb_psy); + power_supply_changed(&the_chip->dc_psy); + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_set_usb_power_supply_type); + +int pm8921_batt_temperature(void) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + return get_prop_batt_temp(the_chip); +} + +static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip) +{ + int usb_present; + + pm_chg_failed_clear(chip, 1); + 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); + power_supply_changed(&chip->batt_psy); + pm8921_bms_calibrate_hkadc(); + } + if (usb_present) { + schedule_delayed_work(&chip->unplug_check_work, + round_jiffies_relative(msecs_to_jiffies + (UNPLUG_CHECK_WAIT_PERIOD_MS))); + pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); + } else { + /* USB unplugged reset target current */ + usb_target_ma = 0; + pm8921_chg_disable_irq(chip, CHG_GONE_IRQ); + } + enable_input_voltage_regulation(chip); + bms_notify_check(chip); +} + +static void handle_stop_ext_chg(struct pm8921_chg_chip *chip) +{ + if (!chip->ext_psy) { + pr_debug("external charger not registered.\n"); + return; + } + + if (!chip->ext_charging) { + pr_debug("already not charging.\n"); + return; + } + + power_supply_set_charge_type(chip->ext_psy, + POWER_SUPPLY_CHARGE_TYPE_NONE); + pm8921_disable_source_current(false); /* release BATFET */ + power_supply_changed(&chip->dc_psy); + chip->ext_charging = false; + chip->ext_charge_done = false; + bms_notify_check(chip); + /* Update battery charging LEDs and user space battery info */ + power_supply_changed(&chip->batt_psy); +} + +static void handle_start_ext_chg(struct pm8921_chg_chip *chip) +{ + int dc_present; + int batt_present; + int batt_temp_ok; + int vbat_ov; + unsigned long delay = + round_jiffies_relative(msecs_to_jiffies(EOC_CHECK_PERIOD_MS)); + + if (!chip->ext_psy) { + pr_debug("external charger not registered.\n"); + return; + } + + if (chip->ext_charging) { + pr_debug("already charging.\n"); + return; + } + + dc_present = is_dc_chg_plugged_in(the_chip); + batt_present = pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ); + batt_temp_ok = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ); + + if (!dc_present) { + pr_warn("%s. dc not present.\n", __func__); + return; + } + if (!batt_present) { + pr_warn("%s. battery not present.\n", __func__); + return; + } + if (!batt_temp_ok) { + pr_warn("%s. battery temperature not ok.\n", __func__); + return; + } + pm8921_disable_source_current(true); /* Force BATFET=ON */ + vbat_ov = pm_chg_get_rt_status(chip, VBAT_OV_IRQ); + if (vbat_ov) { + pr_warn("%s. battery over voltage.\n", __func__); + return; + } + + power_supply_set_online(chip->ext_psy, dc_present); + power_supply_set_charge_type(chip->ext_psy, + POWER_SUPPLY_CHARGE_TYPE_FAST); + power_supply_changed(&chip->dc_psy); + chip->ext_charging = true; + chip->ext_charge_done = false; + bms_notify_check(chip); + /* Start BMS */ + schedule_delayed_work(&chip->eoc_work, delay); + wake_lock(&chip->eoc_wake_lock); + /* Update battery charging LEDs and user space battery info */ + power_supply_changed(&chip->batt_psy); +} + +static void turn_off_usb_ovp_fet(struct pm8921_chg_chip *chip) +{ + u8 temp; + int rc; + + rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0x30); + if (rc) { + pr_err("Failed to write 0x30 to USB_OVP_TEST rc = %d\n", rc); + return; + } + rc = pm8xxx_readb(chip->dev->parent, USB_OVP_TEST, &temp); + if (rc) { + pr_err("Failed to read from USB_OVP_TEST rc = %d\n", rc); + return; + } + /* set ovp fet disable bit and the write bit */ + temp |= 0x81; + rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, temp); + if (rc) { + pr_err("Failed to write 0x%x USB_OVP_TEST rc=%d\n", temp, rc); + return; + } +} + +static void turn_on_usb_ovp_fet(struct pm8921_chg_chip *chip) +{ + u8 temp; + int rc; + + rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0x30); + if (rc) { + pr_err("Failed to write 0x30 to USB_OVP_TEST rc = %d\n", rc); + return; + } + rc = pm8xxx_readb(chip->dev->parent, USB_OVP_TEST, &temp); + if (rc) { + pr_err("Failed to read from USB_OVP_TEST rc = %d\n", rc); + return; + } + /* unset ovp fet disable bit and set the write bit */ + temp &= 0xFE; + temp |= 0x80; + rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, temp); + if (rc) { + pr_err("Failed to write 0x%x to USB_OVP_TEST rc = %d\n", + temp, rc); + return; + } +} + +static int param_open_ovp_counter = 10; +module_param(param_open_ovp_counter, int, 0644); + +#define WRITE_BANK_4 0xC0 +#define USB_OVP_DEBOUNCE_TIME 0x06 +static void unplug_ovp_fet_open(struct pm8921_chg_chip *chip) +{ + int chg_gone = 0, usb_chg_plugged_in = 0; + int count = 0; + + while (count++ < param_open_ovp_counter) { + pm_chg_masked_write(chip, USB_OVP_CONTROL, + USB_OVP_DEBOUNCE_TIME, 0x0); + usleep(10); + usb_chg_plugged_in = is_usb_chg_plugged_in(chip); + chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ); + pr_debug("OVP FET count = %d chg_gone=%d, usb_valid = %d\n", + count, chg_gone, usb_chg_plugged_in); + + /* note usb_chg_plugged_in=0 => chg_gone=1 */ + if (chg_gone == 1 && usb_chg_plugged_in == 1) { + pr_debug("since chg_gone = 1 dis ovp_fet for 20msec\n"); + turn_off_usb_ovp_fet(chip); + + msleep(20); + + turn_on_usb_ovp_fet(chip); + } else { + break; + } + } + pm_chg_masked_write(chip, USB_OVP_CONTROL, + USB_OVP_DEBOUNCE_TIME, 0x2); + pr_debug("Exit count=%d chg_gone=%d, usb_valid=%d\n", + count, chg_gone, usb_chg_plugged_in); + return; +} + +static int find_usb_ma_value(int value) +{ + int i; + + for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) { + if (value >= usb_ma_table[i].usb_ma) + break; + } + + return i; +} + +static void decrease_usb_ma_value(int *value) +{ + int i; + + if (value) { + i = find_usb_ma_value(*value); + if (i > 0) + i--; + *value = usb_ma_table[i].usb_ma; + } +} + +static void increase_usb_ma_value(int *value) +{ + int i; + + if (value) { + i = find_usb_ma_value(*value); + + if (i < (ARRAY_SIZE(usb_ma_table) - 1)) + i++; + /* Get next correct entry if IUSB_FINE_RES is not available */ + while (!the_chip->iusb_fine_res + && (usb_ma_table[i].value & PM8917_IUSB_FINE_RES) + && i < (ARRAY_SIZE(usb_ma_table) - 1)) + i++; + + *value = usb_ma_table[i].usb_ma; + } +} + +static void vin_collapse_check_worker(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct pm8921_chg_chip *chip = container_of(dwork, + struct pm8921_chg_chip, vin_collapse_check_work); + + /* AICL only for wall-chargers */ + if (is_usb_chg_plugged_in(chip) && + usb_target_ma > USB_WALL_THRESHOLD_MA) { + /* decrease usb_target_ma */ + decrease_usb_ma_value(&usb_target_ma); + /* reset here, increase in unplug_check_worker */ + __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA); + pr_debug("usb_now=%d, usb_target = %d\n", + USB_WALL_THRESHOLD_MA, usb_target_ma); + } else { + handle_usb_insertion_removal(chip); + } +} + +#define VIN_MIN_COLLAPSE_CHECK_MS 50 +static irqreturn_t usbin_valid_irq_handler(int irq, void *data) +{ + if (usb_target_ma) + schedule_delayed_work(&the_chip->vin_collapse_check_work, + round_jiffies_relative(msecs_to_jiffies + (VIN_MIN_COLLAPSE_CHECK_MS))); + else + handle_usb_insertion_removal(data); + return IRQ_HANDLED; +} + +static irqreturn_t usbin_ov_irq_handler(int irq, void *data) +{ + pr_err("USB OverVoltage\n"); + 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); + schedule_work(&chip->battery_id_valid_work); + handle_start_ext_chg(chip); + pr_debug("battery present=%d", status); + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} + +/* + * this interrupt used to restart charging a battery. + * + * Note: When DC-inserted the VBAT can't go low. + * VPH_PWR is provided by the ext-charger. + * After End-Of-Charging from DC, charging can be resumed only + * if DC is removed and then inserted after the battery was in use. + * Therefore the handle_start_ext_chg() is not called. + */ +static irqreturn_t vbatdet_low_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int high_transition; + + high_transition = pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ); + + if (high_transition) { + /* enable auto charging */ + pm_chg_auto_enable(chip, !charging_disabled); + pr_info("batt fell below resume voltage %s\n", + charging_disabled ? "" : "charger enabled"); + } + 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) +{ + pr_err("USB UnderVoltage\n"); + 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_debug("fsm_state=%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)); + + handle_stop_ext_chg(chip); + + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + + bms_notify_check(chip); + + return IRQ_HANDLED; +} + +static irqreturn_t chgfail_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int ret; + + ret = pm_chg_failed_clear(chip, 1); + if (ret) + pr_err("Failed to write CHG_FAILED_CLEAR bit\n"); + + pr_err("batt_present = %d, batt_temp_ok = %d, state_changed_to=%d\n", + get_prop_batt_present(chip), + pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ), + 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; + + 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); + + bms_notify_check(chip); + + return IRQ_HANDLED; +} + +static int param_vin_disable_counter = 5; +module_param(param_vin_disable_counter, int, 0644); + +static void attempt_reverse_boost_fix(struct pm8921_chg_chip *chip, + int count, int usb_ma) +{ + __pm8921_charger_vbus_draw(500); + pr_debug("count = %d iusb=500mA\n", count); + disable_input_voltage_regulation(chip); + pr_debug("count = %d disable_input_regulation\n", count); + + msleep(20); + + pr_debug("count = %d end sleep 20ms chg_gone=%d, usb_valid = %d\n", + count, + pm_chg_get_rt_status(chip, CHG_GONE_IRQ), + is_usb_chg_plugged_in(chip)); + pr_debug("count = %d restoring input regulation and usb_ma = %d\n", + count, usb_ma); + enable_input_voltage_regulation(chip); + __pm8921_charger_vbus_draw(usb_ma); +} + +#define VIN_ACTIVE_BIT BIT(0) +#define UNPLUG_WRKARND_RESTORE_WAIT_PERIOD_US 200 +#define VIN_MIN_INCREASE_MV 100 +static void unplug_check_worker(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct pm8921_chg_chip *chip = container_of(dwork, + struct pm8921_chg_chip, unplug_check_work); + u8 reg_loop; + int ibat, usb_chg_plugged_in, usb_ma; + int chg_gone = 0; + + reg_loop = 0; + usb_chg_plugged_in = is_usb_chg_plugged_in(chip); + if (!usb_chg_plugged_in) { + pr_debug("Stopping Unplug Check Worker since USB is removed" + "reg_loop = %d, fsm = %d ibat = %d\n", + pm_chg_get_regulation_loop(chip), + pm_chg_get_fsm_state(chip), + get_prop_batt_current(chip) + ); + return; + } + + pm_chg_iusbmax_get(chip, &usb_ma); + if (usb_ma == 500 && !usb_target_ma) { + pr_debug("Stopping Unplug Check Worker since USB == 500mA\n"); + disable_input_voltage_regulation(chip); + return; + } + + if (usb_ma <= 100) { + pr_debug( + "Unenumerated yet or suspended usb_ma = %d skipping\n", + usb_ma); + goto check_again_later; + } + if (pm8921_chg_is_enabled(chip, CHG_GONE_IRQ)) + pr_debug("chg gone irq is enabled\n"); + + reg_loop = pm_chg_get_regulation_loop(chip); + pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma); + + if ((reg_loop & VIN_ACTIVE_BIT) && (usb_ma > USB_WALL_THRESHOLD_MA)) { + decrease_usb_ma_value(&usb_ma); + usb_target_ma = usb_ma; + /* end AICL here */ + __pm8921_charger_vbus_draw(usb_ma); + pr_debug("usb_now=%d, usb_target = %d\n", + usb_ma, usb_target_ma); + } + + reg_loop = pm_chg_get_regulation_loop(chip); + pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma); + + if (reg_loop & VIN_ACTIVE_BIT) { + ibat = get_prop_batt_current(chip); + + pr_debug("ibat = %d fsm = %d reg_loop = 0x%x\n", + ibat, pm_chg_get_fsm_state(chip), reg_loop); + if (ibat > 0) { + int count = 0; + + while (count++ < param_vin_disable_counter + && usb_chg_plugged_in == 1) { + attempt_reverse_boost_fix(chip, count, usb_ma); + usb_chg_plugged_in + = is_usb_chg_plugged_in(chip); + } + } + } + + usb_chg_plugged_in = is_usb_chg_plugged_in(chip); + chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ); + + if (chg_gone == 1 && usb_chg_plugged_in == 1) { + /* run the worker directly */ + pr_debug(" ver5 step: chg_gone=%d, usb_valid = %d\n", + chg_gone, usb_chg_plugged_in); + unplug_ovp_fet_open(chip); + } + + if (!(reg_loop & VIN_ACTIVE_BIT)) { + /* only increase iusb_max if vin loop not active */ + if (usb_ma < usb_target_ma) { + increase_usb_ma_value(&usb_ma); + __pm8921_charger_vbus_draw(usb_ma); + pr_debug("usb_now=%d, usb_target = %d\n", + usb_ma, usb_target_ma); + } else { + usb_target_ma = usb_ma; + } + } +check_again_later: + /* schedule to check again later */ + schedule_delayed_work(&chip->unplug_check_work, + round_jiffies_relative(msecs_to_jiffies + (UNPLUG_CHECK_WAIT_PERIOD_MS))); +} + +static irqreturn_t loop_change_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("fsm_state=%d reg_loop=0x%x\n", + pm_chg_get_fsm_state(data), + pm_chg_get_regulation_loop(data)); + schedule_work(&chip->unplug_check_work.work); + return IRQ_HANDLED; +} + +static irqreturn_t fastchg_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int high_transition; + + high_transition = pm_chg_get_rt_status(chip, FASTCHG_IRQ); + if (high_transition && !delayed_work_pending(&chip->eoc_work)) { + wake_lock(&chip->eoc_wake_lock); + schedule_delayed_work(&chip->eoc_work, + round_jiffies_relative(msecs_to_jiffies + (EOC_CHECK_PERIOD_MS))); + } + power_supply_changed(&chip->batt_psy); + bms_notify_check(chip); + 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)); + handle_stop_ext_chg(chip); + 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; + + handle_stop_ext_chg(chip); + 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); + handle_stop_ext_chg(chip); + 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)); + handle_stop_ext_chg(chip); + + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + return IRQ_HANDLED; +} + +static irqreturn_t chg_gone_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int chg_gone, usb_chg_plugged_in; + + usb_chg_plugged_in = is_usb_chg_plugged_in(chip); + chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ); + + pr_debug("chg_gone=%d, usb_valid = %d\n", chg_gone, usb_chg_plugged_in); + 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); + return IRQ_HANDLED; +} +/* + * + * bat_temp_ok_irq_handler - is edge triggered, hence it will + * fire for two cases: + * + * If the interrupt line switches to high temperature is okay + * and thus charging begins. + * If bat_temp_ok is low this means the temperature is now + * too hot or cold, so charging is stopped. + * + */ +static irqreturn_t bat_temp_ok_irq_handler(int irq, void *data) +{ + int bat_temp_ok; + struct pm8921_chg_chip *chip = data; + + bat_temp_ok = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ); + + pr_debug("batt_temp_ok = %d fsm_state%d\n", + bat_temp_ok, pm_chg_get_fsm_state(data)); + + if (bat_temp_ok) + handle_start_ext_chg(chip); + else + handle_stop_ext_chg(chip); + + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + bms_notify_check(chip); + 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) +{ + struct pm8921_chg_chip *chip = data; + int dc_present; + + dc_present = pm_chg_get_rt_status(chip, DCIN_VALID_IRQ); + if (chip->ext_psy) + power_supply_set_online(chip->ext_psy, dc_present); + chip->dc_present = dc_present; + if (dc_present) + handle_start_ext_chg(chip); + else + handle_stop_ext_chg(chip); + return IRQ_HANDLED; +} + +static irqreturn_t dcin_ov_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + handle_stop_ext_chg(chip); + return IRQ_HANDLED; +} + +static irqreturn_t dcin_uv_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + handle_stop_ext_chg(chip); + + return IRQ_HANDLED; +} + +static int __pm_batt_external_power_changed_work(struct device *dev, void *data) +{ + struct power_supply *psy = &the_chip->batt_psy; + struct power_supply *epsy = dev_get_drvdata(dev); + int i, dcin_irq; + + /* Only search for external supply if none is registered */ + if (!the_chip->ext_psy) { + dcin_irq = the_chip->pmic_chg_irq[DCIN_VALID_IRQ]; + for (i = 0; i < epsy->num_supplicants; i++) { + if (!strncmp(epsy->supplied_to[i], psy->name, 7)) { + if (!strncmp(epsy->name, "dc", 2)) { + the_chip->ext_psy = epsy; + dcin_valid_irq_handler(dcin_irq, + the_chip); + } + } + } + } + return 0; +} + +static void pm_batt_external_power_changed(struct power_supply *psy) +{ + /* Only look for an external supply if it hasn't been registered */ + if (!the_chip->ext_psy) + class_for_each_device(power_supply_class, NULL, psy, + __pm_batt_external_power_changed_work); +} + +/** + * update_heartbeat - internal function to update userspace + * per update_time minutes + * + */ +static void update_heartbeat(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct pm8921_chg_chip *chip = container_of(dwork, + struct pm8921_chg_chip, update_heartbeat_work); + + pm_chg_failed_clear(chip, 1); + power_supply_changed(&chip->batt_psy); + schedule_delayed_work(&chip->update_heartbeat_work, + round_jiffies_relative(msecs_to_jiffies + (chip->update_time))); +} +#define VDD_LOOP_ACTIVE_BIT BIT(3) +#define VDD_MAX_INCREASE_MV 400 +static int vdd_max_increase_mv = VDD_MAX_INCREASE_MV; +module_param(vdd_max_increase_mv, int, 0644); + +static int ichg_threshold_ua = -400000; +module_param(ichg_threshold_ua, int, 0644); +static void adjust_vdd_max_for_fastchg(struct pm8921_chg_chip *chip) +{ + int ichg_meas_ua, vbat_uv; + int ichg_meas_ma; + int adj_vdd_max_mv, programmed_vdd_max; + int vbat_batt_terminal_uv; + int vbat_batt_terminal_mv; + int reg_loop; + int delta_mv = 0; + + if (chip->rconn_mohm == 0) { + pr_debug("Exiting as rconn_mohm is 0\n"); + return; + } + /* adjust vdd_max only in normal temperature zone */ + if (chip->is_bat_cool || chip->is_bat_warm) { + pr_debug("Exiting is_bat_cool = %d is_batt_warm = %d\n", + chip->is_bat_cool, chip->is_bat_warm); + return; + } + + reg_loop = pm_chg_get_regulation_loop(chip); + if (!(reg_loop & VDD_LOOP_ACTIVE_BIT)) { + pr_debug("Exiting Vdd loop is not active reg loop=0x%x\n", + reg_loop); + return; + } + + pm8921_bms_get_simultaneous_battery_voltage_and_current(&ichg_meas_ua, + &vbat_uv); + if (ichg_meas_ua >= 0) { + pr_debug("Exiting ichg_meas_ua = %d > 0\n", ichg_meas_ua); + return; + } + if (ichg_meas_ua <= ichg_threshold_ua) { + pr_debug("Exiting ichg_meas_ua = %d < ichg_threshold_ua = %d\n", + ichg_meas_ua, ichg_threshold_ua); + return; + } + ichg_meas_ma = ichg_meas_ua / 1000; + + /* rconn_mohm is in milliOhms */ + vbat_batt_terminal_uv = vbat_uv + ichg_meas_ma * the_chip->rconn_mohm; + vbat_batt_terminal_mv = vbat_batt_terminal_uv/1000; + pm_chg_vddmax_get(the_chip, &programmed_vdd_max); + + delta_mv = chip->max_voltage_mv - vbat_batt_terminal_mv; + + adj_vdd_max_mv = programmed_vdd_max + delta_mv; + pr_debug("vdd_max needs to be changed by %d mv from %d to %d\n", + delta_mv, + programmed_vdd_max, + adj_vdd_max_mv); + + if (adj_vdd_max_mv < chip->max_voltage_mv) { + pr_debug("adj vdd_max lower than default max voltage\n"); + return; + } + + if (adj_vdd_max_mv > (chip->max_voltage_mv + vdd_max_increase_mv)) + adj_vdd_max_mv = chip->max_voltage_mv + vdd_max_increase_mv; + + pr_debug("adjusting vdd_max_mv to %d to make " + "vbat_batt_termial_uv = %d to %d\n", + adj_vdd_max_mv, vbat_batt_terminal_uv, chip->max_voltage_mv); + pm_chg_vddmax_set(chip, adj_vdd_max_mv); +} + +enum { + CHG_IN_PROGRESS, + CHG_NOT_IN_PROGRESS, + CHG_FINISHED, +}; + +#define VBAT_TOLERANCE_MV 70 +#define CHG_DISABLE_MSLEEP 100 +static int is_charging_finished(struct pm8921_chg_chip *chip) +{ + int vbat_meas_uv, vbat_meas_mv, vbat_programmed, vbatdet_low; + int ichg_meas_ma, iterm_programmed; + int regulation_loop, fast_chg, vcp; + int rc; + static int last_vbat_programmed = -EINVAL; + + if (!is_ext_charging(chip)) { + /* return if the battery is not being fastcharged */ + fast_chg = pm_chg_get_rt_status(chip, FASTCHG_IRQ); + pr_debug("fast_chg = %d\n", fast_chg); + if (fast_chg == 0) + return CHG_NOT_IN_PROGRESS; + + vcp = pm_chg_get_rt_status(chip, VCP_IRQ); + pr_debug("vcp = %d\n", vcp); + if (vcp == 1) + return CHG_IN_PROGRESS; + + vbatdet_low = pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ); + pr_debug("vbatdet_low = %d\n", vbatdet_low); + if (vbatdet_low == 1) + return CHG_IN_PROGRESS; + + /* reset count if battery is hot/cold */ + rc = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ); + pr_debug("batt_temp_ok = %d\n", rc); + if (rc == 0) + return CHG_IN_PROGRESS; + + /* reset count if battery voltage is less than vddmax */ + vbat_meas_uv = get_prop_battery_uvolts(chip); + if (vbat_meas_uv < 0) + return CHG_IN_PROGRESS; + vbat_meas_mv = vbat_meas_uv / 1000; + + rc = pm_chg_vddmax_get(chip, &vbat_programmed); + if (rc) { + pr_err("couldnt read vddmax rc = %d\n", rc); + return CHG_IN_PROGRESS; + } + pr_debug("vddmax = %d vbat_meas_mv=%d\n", + vbat_programmed, vbat_meas_mv); + + if (last_vbat_programmed == -EINVAL) + last_vbat_programmed = vbat_programmed; + if (last_vbat_programmed != vbat_programmed) { + /* vddmax changed, reset and check again */ + pr_debug("vddmax = %d last_vdd_max=%d\n", + vbat_programmed, last_vbat_programmed); + last_vbat_programmed = vbat_programmed; + return CHG_IN_PROGRESS; + } + + regulation_loop = pm_chg_get_regulation_loop(chip); + if (regulation_loop < 0) { + pr_err("couldnt read the regulation loop err=%d\n", + regulation_loop); + return CHG_IN_PROGRESS; + } + pr_debug("regulation_loop=%d\n", regulation_loop); + + if (regulation_loop != 0 && regulation_loop != VDD_LOOP) + return CHG_IN_PROGRESS; + } /* !is_ext_charging */ + + /* reset count if battery chg current is more than iterm */ + rc = pm_chg_iterm_get(chip, &iterm_programmed); + if (rc) { + pr_err("couldnt read iterm rc = %d\n", rc); + return CHG_IN_PROGRESS; + } + + ichg_meas_ma = (get_prop_batt_current(chip)) / 1000; + pr_debug("iterm_programmed = %d ichg_meas_ma=%d\n", + iterm_programmed, ichg_meas_ma); + /* + * ichg_meas_ma < 0 means battery is drawing current + * ichg_meas_ma > 0 means battery is providing current + */ + if (ichg_meas_ma > 0) + return CHG_IN_PROGRESS; + + if (ichg_meas_ma * -1 > iterm_programmed) + return CHG_IN_PROGRESS; + + return CHG_FINISHED; +} + +/** + * eoc_worker - internal function to check if battery EOC + * has happened + * + * If all conditions favouring, if the charge current is + * less than the term current for three consecutive times + * an EOC has happened. + * The wakelock is released if there is no need to reshedule + * - this happens when the battery is removed or EOC has + * happened + */ +#define CONSECUTIVE_COUNT 3 +static void eoc_worker(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct pm8921_chg_chip *chip = container_of(dwork, + struct pm8921_chg_chip, eoc_work); + static int count; + int end; + + pm_chg_failed_clear(chip, 1); + end = is_charging_finished(chip); + + if (end == CHG_NOT_IN_PROGRESS) { + count = 0; + wake_unlock(&chip->eoc_wake_lock); + return; + } + + if (end == CHG_FINISHED) { + count++; + } else { + count = 0; + } + + if (count == CONSECUTIVE_COUNT) { + count = 0; + pr_info("End of Charging\n"); + + pm_chg_auto_enable(chip, 0); + + if (is_ext_charging(chip)) + chip->ext_charge_done = true; + + if (chip->is_bat_warm || chip->is_bat_cool) + chip->bms_notify.is_battery_full = 0; + else + chip->bms_notify.is_battery_full = 1; + /* declare end of charging by invoking chgdone interrupt */ + chgdone_irq_handler(chip->pmic_chg_irq[CHGDONE_IRQ], chip); + wake_unlock(&chip->eoc_wake_lock); + } else { + adjust_vdd_max_for_fastchg(chip); + pr_debug("EOC count = %d\n", count); + schedule_delayed_work(&chip->eoc_work, + round_jiffies_relative(msecs_to_jiffies + (EOC_CHECK_PERIOD_MS))); + } +} + +static void btm_configure_work(struct work_struct *work) +{ + int rc; + + rc = pm8xxx_adc_btm_configure(&btm_config); + if (rc) + pr_err("failed to configure btm rc=%d", rc); +} + +DECLARE_WORK(btm_config_work, btm_configure_work); + +static void set_appropriate_battery_current(struct pm8921_chg_chip *chip) +{ + unsigned int chg_current = chip->max_bat_chg_current; + + if (chip->is_bat_cool) + chg_current = min(chg_current, chip->cool_bat_chg_current); + + if (chip->is_bat_warm) + chg_current = min(chg_current, chip->warm_bat_chg_current); + + if (thermal_mitigation != 0 && chip->thermal_mitigation) + chg_current = min(chg_current, + chip->thermal_mitigation[thermal_mitigation]); + + pm_chg_ibatmax_set(the_chip, chg_current); +} + +#define TEMP_HYSTERISIS_DEGC 2 +static void battery_cool(bool enter) +{ + pr_debug("enter = %d\n", enter); + if (enter == the_chip->is_bat_cool) + return; + the_chip->is_bat_cool = enter; + if (enter) { + btm_config.low_thr_temp = + the_chip->cool_temp_dc + TEMP_HYSTERISIS_DEGC; + set_appropriate_battery_current(the_chip); + pm_chg_vddmax_set(the_chip, the_chip->cool_bat_voltage); + pm_chg_vbatdet_set(the_chip, + the_chip->cool_bat_voltage + - the_chip->resume_voltage_delta); + } else { + btm_config.low_thr_temp = the_chip->cool_temp_dc; + set_appropriate_battery_current(the_chip); + pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv); + pm_chg_vbatdet_set(the_chip, + the_chip->max_voltage_mv + - the_chip->resume_voltage_delta); + } + schedule_work(&btm_config_work); +} + +static void battery_warm(bool enter) +{ + pr_debug("enter = %d\n", enter); + if (enter == the_chip->is_bat_warm) + return; + the_chip->is_bat_warm = enter; + if (enter) { + btm_config.high_thr_temp = + the_chip->warm_temp_dc - TEMP_HYSTERISIS_DEGC; + set_appropriate_battery_current(the_chip); + pm_chg_vddmax_set(the_chip, the_chip->warm_bat_voltage); + pm_chg_vbatdet_set(the_chip, + the_chip->warm_bat_voltage + - the_chip->resume_voltage_delta); + } else { + btm_config.high_thr_temp = the_chip->warm_temp_dc; + set_appropriate_battery_current(the_chip); + pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv); + pm_chg_vbatdet_set(the_chip, + the_chip->max_voltage_mv + - the_chip->resume_voltage_delta); + } + schedule_work(&btm_config_work); +} + +static int configure_btm(struct pm8921_chg_chip *chip) +{ + int rc; + + if (chip->warm_temp_dc != INT_MIN) + btm_config.btm_warm_fn = battery_warm; + else + btm_config.btm_warm_fn = NULL; + + if (chip->cool_temp_dc != INT_MIN) + btm_config.btm_cool_fn = battery_cool; + else + btm_config.btm_cool_fn = NULL; + + btm_config.low_thr_temp = chip->cool_temp_dc; + btm_config.high_thr_temp = chip->warm_temp_dc; + btm_config.interval = chip->temp_check_period; + rc = pm8xxx_adc_btm_configure(&btm_config); + if (rc) + pr_err("failed to configure btm rc = %d\n", rc); + rc = pm8xxx_adc_btm_start(); + if (rc) + pr_err("failed to start btm rc = %d\n", rc); + + return rc; +} + +/** + * set_disable_status_param - + * + * Internal function to disable battery charging and also disable drawing + * any current from the source. The device is forced to run on a battery + * after this. + */ +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 int rconn_mohm; +static int set_rconn_mohm(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; + } + if (chip) + chip->rconn_mohm = rconn_mohm; + return 0; +} +module_param_call(rconn_mohm, set_rconn_mohm, param_get_uint, + &rconn_mohm, 0644); +/** + * set_thermal_mitigation_level - + * + * Internal function to control battery charging current to reduce + * temperature + */ +static int set_therm_mitigation_level(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; + } + + if (!chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + if (!chip->thermal_mitigation) { + pr_err("no thermal mitigation\n"); + return -EINVAL; + } + + if (thermal_mitigation < 0 + || thermal_mitigation >= chip->thermal_levels) { + pr_err("out of bound level selected\n"); + return -EINVAL; + } + + set_appropriate_battery_current(chip); + return ret; +} +module_param_call(thermal_mitigation, set_therm_mitigation_level, + param_get_uint, + &thermal_mitigation, 0644); + +static int set_usb_max_current(const char *val, struct kernel_param *kp) +{ + int ret, mA; + 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; + } + if (chip) { + pr_warn("setting current max to %d\n", usb_max_current); + pm_chg_iusbmax_get(chip, &mA); + if (mA > usb_max_current) + pm8921_charger_vbus_draw(usb_max_current); + return 0; + } + return -EINVAL; +} +module_param_call(usb_max_current, set_usb_max_current, + param_get_uint, &usb_max_current, 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 */ +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); + if (chip->usb_present) { + schedule_delayed_work(&chip->unplug_check_work, + round_jiffies_relative(msecs_to_jiffies + (UNPLUG_CHECK_WAIT_PERIOD_MS))); + pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); + } + + 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_INSERTED_IRQ); + pm8921_chg_enable_irq(chip, USBIN_OV_IRQ); + pm8921_chg_enable_irq(chip, USBIN_UV_IRQ); + pm8921_chg_enable_irq(chip, DCIN_OV_IRQ); + pm8921_chg_enable_irq(chip, DCIN_UV_IRQ); + pm8921_chg_enable_irq(chip, CHGFAIL_IRQ); + pm8921_chg_enable_irq(chip, FASTCHG_IRQ); + pm8921_chg_enable_irq(chip, VBATDET_LOW_IRQ); + pm8921_chg_enable_irq(chip, BAT_TEMP_OK_IRQ); + + spin_lock_irqsave(&vbus_lock, flags); + if (usb_chg_current) { + /* reissue a vbus draw call */ + __pm8921_charger_vbus_draw(usb_chg_current); + fastchg_irq_handler(chip->pmic_chg_irq[FASTCHG_IRQ], chip); + } + 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(); + } + + check_battery_valid(chip); + + 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, 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 | IRQF_TRIGGER_FALLING, + 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 | IRQF_TRIGGER_FALLING, + 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 | IRQF_TRIGGER_FALLING, + chg_gone_irq_handler), + CHG_IRQ(BAT_TEMP_OK_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + 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, 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; + } + chip->pmic_chg_irq[chg_irq_data[i].irq_id] = res->start; + 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); + chip->pmic_chg_irq[chg_irq_data[i].irq_id] = 0; + goto err_out; + } + pm8921_chg_disable_irq(chip, chg_irq_data[i].irq_id); + } + return 0; + +err_out: + free_irqs(chip); + return -EINVAL; +} + +static void pm8921_chg_force_19p2mhz_clk(struct pm8921_chg_chip *chip) +{ + int err; + u8 temp; + + temp = 0xD1; + 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; + } + + temp = 0xD3; + 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; + } + + temp = 0xD1; + 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; + } + + temp = 0xD5; + 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; + } + + udelay(183); + + temp = 0xD1; + 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; + } + + temp = 0xD0; + 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; + } + udelay(32); + + temp = 0xD1; + 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; + } + + temp = 0xD3; + 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; + } +} + +static void pm8921_chg_set_hw_clk_switching(struct pm8921_chg_chip *chip) +{ + int err; + u8 temp; + + temp = 0xD1; + 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; + } + + temp = 0xD0; + 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; + } +} + +#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 +#define VREF_BATT_THERM_FORCE_ON BIT(7) +static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) +{ + int rc; + int vdd_safe; + + 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; + } + + vdd_safe = chip->max_voltage_mv + VDD_MAX_INCREASE_MV; + + if (vdd_safe > PM8921_CHG_VDDSAFE_MAX) + vdd_safe = PM8921_CHG_VDDSAFE_MAX; + + rc = pm_chg_vddsafe_set(chip, vdd_safe); + + if (rc) { + pr_err("Failed to set safe voltage to %d rc=%d\n", + chip->max_voltage_mv, rc); + return rc; + } + rc = pm_chg_vbatdet_set(chip, + chip->max_voltage_mv + - chip->resume_voltage_delta); + if (rc) { + pr_err("Failed to set vbatdet comprator voltage to %d rc=%d\n", + chip->max_voltage_mv - chip->resume_voltage_delta, rc); + return rc; + } + + rc = pm_chg_vddmax_set(chip, chip->max_voltage_mv); + if (rc) { + pr_err("Failed to set max voltage to %d rc=%d\n", + chip->max_voltage_mv, 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; + } + + rc = pm_chg_ibatmax_set(chip, chip->max_bat_chg_current); + 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; + } + + if (chip->safety_time != 0) { + rc = pm_chg_tchg_max_set(chip, chip->safety_time); + if (rc) { + pr_err("Failed to set max time to %d minutes rc=%d\n", + chip->safety_time, rc); + return rc; + } + } + + if (chip->ttrkl_time != 0) { + rc = pm_chg_ttrkl_max_set(chip, chip->ttrkl_time); + if (rc) { + pr_err("Failed to set trkl time to %d minutes rc=%d\n", + chip->safety_time, rc); + return rc; + } + } + + if (chip->vin_min != 0) { + rc = pm_chg_vinmin_set(chip, chip->vin_min); + if (rc) { + pr_err("Failed to set vin min to %d mV rc=%d\n", + chip->vin_min, rc); + return rc; + } + } else { + chip->vin_min = pm_chg_vinmin_get(chip); + } + + 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; + } + + if (chip->trkl_voltage != 0) { + rc = pm_chg_vtrkl_low_set(chip, chip->trkl_voltage); + if (rc) { + pr_err("Failed to set trkl voltage to %dmv rc=%d\n", + chip->trkl_voltage, rc); + return rc; + } + } + + if (chip->weak_voltage != 0) { + rc = pm_chg_vweak_set(chip, chip->weak_voltage); + if (rc) { + pr_err("Failed to set weak voltage to %dmv rc=%d\n", + chip->weak_voltage, rc); + return rc; + } + } + + if (chip->trkl_current != 0) { + rc = pm_chg_itrkl_set(chip, chip->trkl_current); + if (rc) { + pr_err("Failed to set trkl current to %dmA rc=%d\n", + chip->trkl_voltage, rc); + return rc; + } + } + + if (chip->weak_current != 0) { + rc = pm_chg_iweak_set(chip, chip->weak_current); + if (rc) { + pr_err("Failed to set weak current to %dmA rc=%d\n", + chip->weak_current, rc); + return rc; + } + } + + rc = pm_chg_batt_cold_temp_config(chip, chip->cold_thr); + if (rc) { + pr_err("Failed to set cold config %d rc=%d\n", + chip->cold_thr, rc); + } + + rc = pm_chg_batt_hot_temp_config(chip, chip->hot_thr); + if (rc) { + pr_err("Failed to set hot config %d rc=%d\n", + chip->hot_thr, rc); + } + + rc = pm_chg_led_src_config(chip, chip->led_src_config); + if (rc) { + pr_err("Failed to set charger LED src config %d rc=%d\n", + chip->led_src_config, 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, 0xCE); + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD8); + + /* software workaround for correct battery_id detection */ + pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_0, 0xFF); + pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_1, 0xFF); + pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_2, 0xFF); + pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_3, 0xFF); + pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0D); + udelay(100); + pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0C); + } + + /* Workarounds for die 3.0 */ + if (pm8xxx_get_revision(chip->dev->parent) == PM8XXX_REVISION_8921_3p0) + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xAC); + + /* Enable isub_fine resolution AICL for PM8917 */ + if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8917) + chip->iusb_fine_res = true; + + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD9); + + /* Disable EOC FSM processing */ + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x91); + + pm8921_chg_force_19p2mhz_clk(chip); + + rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, + VREF_BATT_THERM_FORCE_ON); + if (rc) + pr_err("Failed to Force Vref therm rc=%d\n", rc); + + 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_loop(void *data, u64 * val) +{ + u8 temp; + + if (!the_chip) { + pr_err("%s called before init\n", __func__); + return -EINVAL; + } + temp = pm_chg_get_regulation_loop(the_chip); + *val = temp; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(reg_loop_fops, get_reg_loop, NULL, "0x%02llx\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"); + +enum { + BAT_WARM_ZONE, + BAT_COOL_ZONE, +}; +static int get_warm_cool(void *data, u64 * val) +{ + if (!the_chip) { + pr_err("%s called before init\n", __func__); + return -EINVAL; + } + if ((int)data == BAT_WARM_ZONE) + *val = the_chip->is_bat_warm; + if ((int)data == BAT_COOL_ZONE) + *val = the_chip->is_bat_cool; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(warm_cool_fops, get_warm_cool, NULL, "0x%lld\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); + + debugfs_create_file("REGULATION_LOOP_CONTROL", 0644, chip->dent, NULL, + ®_loop_fops); + + debugfs_create_file("BAT_WARM_ZONE", 0644, chip->dent, + (void *)BAT_WARM_ZONE, &warm_cool_fops); + debugfs_create_file("BAT_COOL_ZONE", 0644, chip->dent, + (void *)BAT_COOL_ZONE, &warm_cool_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 pm8921_charger_suspend_noirq(struct device *dev) +{ + int rc; + struct pm8921_chg_chip *chip = dev_get_drvdata(dev); + + rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, 0); + if (rc) + pr_err("Failed to Force Vref therm off rc=%d\n", rc); + pm8921_chg_set_hw_clk_switching(chip); + return 0; +} + +static int pm8921_charger_resume_noirq(struct device *dev) +{ + int rc; + struct pm8921_chg_chip *chip = dev_get_drvdata(dev); + + pm8921_chg_force_19p2mhz_clk(chip); + + rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, + VREF_BATT_THERM_FORCE_ON); + if (rc) + pr_err("Failed to Force Vref therm on rc=%d\n", rc); + return 0; +} + +static int pm8921_charger_resume(struct device *dev) +{ + int rc; + struct pm8921_chg_chip *chip = dev_get_drvdata(dev); + + if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN) + && !(chip->keep_btm_on_suspend)) { + rc = pm8xxx_adc_btm_configure(&btm_config); + if (rc) + pr_err("couldn't reconfigure btm rc=%d\n", rc); + + rc = pm8xxx_adc_btm_start(); + if (rc) + pr_err("couldn't restart btm rc=%d\n", rc); + } + if (pm8921_chg_is_enabled(chip, LOOP_CHANGE_IRQ)) { + disable_irq_wake(chip->pmic_chg_irq[LOOP_CHANGE_IRQ]); + pm8921_chg_disable_irq(chip, LOOP_CHANGE_IRQ); + } + return 0; +} + +static int pm8921_charger_suspend(struct device *dev) +{ + int rc; + struct pm8921_chg_chip *chip = dev_get_drvdata(dev); + + if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN) + && !(chip->keep_btm_on_suspend)) { + rc = pm8xxx_adc_btm_end(); + if (rc) + pr_err("Failed to disable BTM on suspend rc=%d\n", rc); + } + + if (is_usb_chg_plugged_in(chip)) { + pm8921_chg_enable_irq(chip, LOOP_CHANGE_IRQ); + enable_irq_wake(chip->pmic_chg_irq[LOOP_CHANGE_IRQ]); + } + + return 0; +} +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->ttrkl_time = pdata->ttrkl_time; + chip->update_time = pdata->update_time; + chip->max_voltage_mv = pdata->max_voltage; + chip->min_voltage_mv = pdata->min_voltage; + chip->resume_voltage_delta = pdata->resume_voltage_delta; + chip->term_current = pdata->term_current; + chip->vbat_channel = pdata->charger_cdata.vbat_channel; + chip->batt_temp_channel = pdata->charger_cdata.batt_temp_channel; + chip->batt_id_channel = pdata->charger_cdata.batt_id_channel; + chip->batt_id_min = pdata->batt_id_min; + chip->batt_id_max = pdata->batt_id_max; + if (pdata->cool_temp != INT_MIN) + chip->cool_temp_dc = pdata->cool_temp * 10; + else + chip->cool_temp_dc = INT_MIN; + + if (pdata->warm_temp != INT_MIN) + chip->warm_temp_dc = pdata->warm_temp * 10; + else + chip->warm_temp_dc = INT_MIN; + + chip->temp_check_period = pdata->temp_check_period; + chip->max_bat_chg_current = pdata->max_bat_chg_current; + chip->cool_bat_chg_current = pdata->cool_bat_chg_current; + chip->warm_bat_chg_current = pdata->warm_bat_chg_current; + chip->cool_bat_voltage = pdata->cool_bat_voltage; + chip->warm_bat_voltage = pdata->warm_bat_voltage; + chip->keep_btm_on_suspend = pdata->keep_btm_on_suspend; + chip->trkl_voltage = pdata->trkl_voltage; + chip->weak_voltage = pdata->weak_voltage; + chip->trkl_current = pdata->trkl_current; + chip->weak_current = pdata->weak_current; + chip->vin_min = pdata->vin_min; + chip->thermal_mitigation = pdata->thermal_mitigation; + chip->thermal_levels = pdata->thermal_levels; + + chip->cold_thr = pdata->cold_thr; + chip->hot_thr = pdata->hot_thr; + chip->rconn_mohm = pdata->rconn_mohm; + chip->led_src_config = pdata->led_src_config; + + 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_usb, + chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props_usb), + chip->usb_psy.get_property = pm_power_get_property_usb, + + chip->dc_psy.name = "pm8921-dc", + 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_mains, + chip->dc_psy.num_properties = ARRAY_SIZE(pm_power_props_mains), + chip->dc_psy.get_property = pm_power_get_property_mains, + + 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, + chip->batt_psy.external_power_changed = pm_batt_external_power_changed, + 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_chip; + } + + rc = power_supply_register(chip->dev, &chip->dc_psy); + if (rc < 0) { + pr_err("power_supply_register usb 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; + } + + platform_set_drvdata(pdev, chip); + the_chip = chip; + + wake_lock_init(&chip->eoc_wake_lock, WAKE_LOCK_SUSPEND, "pm8921_eoc"); + INIT_DELAYED_WORK(&chip->eoc_work, eoc_worker); + INIT_DELAYED_WORK(&chip->vin_collapse_check_work, + vin_collapse_check_worker); + INIT_DELAYED_WORK(&chip->unplug_check_work, unplug_check_worker); + + rc = request_irqs(chip, pdev); + if (rc) { + pr_err("couldn't register interrupts rc=%d\n", rc); + goto unregister_batt; + } + + enable_irq_wake(chip->pmic_chg_irq[USBIN_VALID_IRQ]); + enable_irq_wake(chip->pmic_chg_irq[USBIN_OV_IRQ]); + enable_irq_wake(chip->pmic_chg_irq[USBIN_UV_IRQ]); + enable_irq_wake(chip->pmic_chg_irq[BAT_TEMP_OK_IRQ]); + enable_irq_wake(chip->pmic_chg_irq[VBATDET_LOW_IRQ]); + enable_irq_wake(chip->pmic_chg_irq[FASTCHG_IRQ]); + /* + * if both the cool_temp_dc and warm_temp_dc are invalid device doesnt + * care for jeita compliance + */ + if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN)) { + rc = configure_btm(chip); + if (rc) { + pr_err("couldn't register with btm rc=%d\n", rc); + goto free_irq; + } + } + + create_debugfs_entries(chip); + + INIT_WORK(&chip->bms_notify.work, bms_notify); + INIT_WORK(&chip->battery_id_valid_work, battery_id_valid); + + /* determine what state the charger is in */ + determine_initial_state(chip); + + if (chip->update_time) { + INIT_DELAYED_WORK(&chip->update_heartbeat_work, + update_heartbeat); + schedule_delayed_work(&chip->update_heartbeat_work, + round_jiffies_relative(msecs_to_jiffies + (chip->update_time))); + } + 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 const struct dev_pm_ops pm8921_pm_ops = { + .suspend = pm8921_charger_suspend, + .suspend_noirq = pm8921_charger_suspend_noirq, + .resume_noirq = pm8921_charger_resume_noirq, + .resume = pm8921_charger_resume, +}; +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, + .pm = &pm8921_pm_ops, + }, +}; + +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/pm8xxx-ccadc.c b/drivers/power/pm8xxx-ccadc.c new file mode 100644 index 0000000000000000000000000000000000000000..ce72a5b5c24c571eaab32b488ac4efcf15068440 --- /dev/null +++ b/drivers/power/pm8xxx-ccadc.c @@ -0,0 +1,734 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 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 CCADC_FULLSCALE_TRIM1 0x34C +#define CCADC_FULLSCALE_TRIM0 0x34D + +/* note : TRIM1 is the msb and TRIM0 is the lsb */ +#define ADC_ARB_SECP_CNTRL 0x190 +#define ADC_ARB_SECP_AMUX_CNTRL 0x191 +#define ADC_ARB_SECP_ANA_PARAM 0x192 +#define ADC_ARB_SECP_DIG_PARAM 0x193 +#define ADC_ARB_SECP_RSV 0x194 +#define ADC_ARB_SECP_DATA1 0x195 +#define ADC_ARB_SECP_DATA0 0x196 + +#define ADC_ARB_BMS_CNTRL 0x18D + +#define START_CONV_BIT BIT(7) +#define EOC_CONV_BIT BIT(6) +#define SEL_CCADC_BIT BIT(1) +#define EN_ARB_BIT BIT(0) + +#define CCADC_CALIB_DIG_PARAM 0xE3 +#define CCADC_CALIB_RSV_GND 0x40 +#define CCADC_CALIB_RSV_25MV 0x80 +#define CCADC_CALIB_ANA_PARAM 0x1B +#define SAMPLE_COUNT 16 +#define ADC_WAIT_COUNT 10 + +#define CCADC_MAX_25MV 30000 +#define CCADC_MIN_25MV 20000 +#define CCADC_MAX_0UV -4000 +#define CCADC_MIN_0UV -7000 + +#define CCADC_INTRINSIC_OFFSET 0xC000 + +struct pm8xxx_ccadc_chip { + struct device *dev; + struct dentry *dent; + u16 ccadc_offset; + int ccadc_gain_uv; + unsigned int revision; + int eoc_irq; + int r_sense; +}; + +static struct pm8xxx_ccadc_chip *the_chip; + +#ifdef DEBUG +static s64 microvolt_to_ccadc_reading_v1(s64 uv) +{ + return div_s64(uv * CCADC_READING_RESOLUTION_D_V1, + CCADC_READING_RESOLUTION_N_V1); +} + +static s64 microvolt_to_ccadc_reading_v2(s64 uv) +{ + return div_s64(uv * CCADC_READING_RESOLUTION_D_V2, + CCADC_READING_RESOLUTION_N_V2); +} + +static s64 microvolt_to_ccadc_reading(struct pm8xxx_ccadc_chip *chip, s64 cc) +{ + /* + * resolution (the value of a single bit) was changed after revision 2.0 + * for more accurate readings + */ + return (the_chip->revision < PM8XXX_REVISION_8921_2p0) ? + microvolt_to_ccadc_reading_v1((s64)cc) : + microvolt_to_ccadc_reading_v2((s64)cc); +} +#endif + +static int cc_adjust_for_offset(u16 raw) +{ + /* this has the intrinsic offset */ + return (int)raw - the_chip->ccadc_offset; +} + +#define GAIN_REFERENCE_UV 25000 +/* + * gain compensation for ccadc readings - common for vsense based and + * couloumb counter based readings + */ +s64 pm8xxx_cc_adjust_for_gain(s64 uv) +{ + if (the_chip == NULL || the_chip->ccadc_gain_uv == 0) + return uv; + + return div_s64(uv * GAIN_REFERENCE_UV, the_chip->ccadc_gain_uv); +} +EXPORT_SYMBOL(pm8xxx_cc_adjust_for_gain); + +static int pm_ccadc_masked_write(struct pm8xxx_ccadc_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 REG_SBI_CONFIG 0x04F +#define PAGE3_ENABLE_MASK 0x6 +static int calib_ccadc_enable_trim_access(struct pm8xxx_ccadc_chip *chip, + u8 *sbi_config) +{ + u8 reg; + int rc; + + rc = pm8xxx_readb(chip->dev->parent, REG_SBI_CONFIG, sbi_config); + if (rc) { + pr_err("error = %d reading sbi config reg\n", rc); + return rc; + } + + reg = *sbi_config | PAGE3_ENABLE_MASK; + return pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, reg); +} + +static int calib_ccadc_restore_trim_access(struct pm8xxx_ccadc_chip *chip, + u8 sbi_config) +{ + return pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, sbi_config); +} + +static int calib_ccadc_enable_arbiter(struct pm8xxx_ccadc_chip *chip) +{ + int rc; + + /* enable Arbiter, must be sent twice */ + rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL, + SEL_CCADC_BIT | EN_ARB_BIT, SEL_CCADC_BIT | EN_ARB_BIT); + if (rc < 0) { + pr_err("error = %d enabling arbiter for offset\n", rc); + return rc; + } + rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL, + SEL_CCADC_BIT | EN_ARB_BIT, SEL_CCADC_BIT | EN_ARB_BIT); + if (rc < 0) { + pr_err("error = %d writing ADC_ARB_SECP_CNTRL\n", rc); + return rc; + } + return 0; +} + +static int calib_start_conv(struct pm8xxx_ccadc_chip *chip, + u16 *result) +{ + int rc, i; + u8 data_msb, data_lsb, reg; + + /* Start conversion */ + rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL, + START_CONV_BIT, START_CONV_BIT); + if (rc < 0) { + pr_err("error = %d starting offset meas\n", rc); + return rc; + } + + /* Wait for End of conversion */ + for (i = 0; i < ADC_WAIT_COUNT; i++) { + rc = pm8xxx_readb(chip->dev->parent, + ADC_ARB_SECP_CNTRL, ®); + if (rc < 0) { + pr_err("error = %d read eoc for offset\n", rc); + return rc; + } + if ((reg & (START_CONV_BIT | EOC_CONV_BIT)) != EOC_CONV_BIT) + msleep(20); + else + break; + } + if (i == ADC_WAIT_COUNT) { + pr_err("waited too long for offset eoc returning -EBUSY\n"); + return -EBUSY; + } + + rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_DATA0, &data_lsb); + if (rc < 0) { + pr_err("error = %d reading offset lsb\n", rc); + return rc; + } + + rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_DATA1, &data_msb); + if (rc < 0) { + pr_err("error = %d reading offset msb\n", rc); + return rc; + } + + *result = (data_msb << 8) | data_lsb; + return 0; +} + +static int calib_ccadc_read_trim(struct pm8xxx_ccadc_chip *chip, + int addr, u8 *data_msb, u8 *data_lsb) +{ + int rc; + u8 sbi_config; + + calib_ccadc_enable_trim_access(chip, &sbi_config); + rc = pm8xxx_readb(chip->dev->parent, addr, data_msb); + if (rc < 0) { + pr_err("error = %d read msb\n", rc); + return rc; + } + rc = pm8xxx_readb(chip->dev->parent, addr + 1, data_lsb); + if (rc < 0) { + pr_err("error = %d read lsb\n", rc); + return rc; + } + calib_ccadc_restore_trim_access(chip, sbi_config); + return 0; +} + +static void calib_ccadc_read_offset_and_gain(struct pm8xxx_ccadc_chip *chip, + int *gain, u16 *offset) +{ + u8 data_msb; + u8 data_lsb; + int rc; + + rc = calib_ccadc_read_trim(chip, CCADC_FULLSCALE_TRIM1, + &data_msb, &data_lsb); + *gain = (data_msb << 8) | data_lsb; + + rc = calib_ccadc_read_trim(chip, CCADC_OFFSET_TRIM1, + &data_msb, &data_lsb); + *offset = (data_msb << 8) | data_lsb; + + pr_debug("raw gain trim = 0x%x offset trim =0x%x\n", *gain, *offset); + *gain = pm8xxx_ccadc_reading_to_microvolt(chip->revision, + (s64)*gain - *offset); + pr_debug("gain uv = %duV offset=0x%x\n", *gain, *offset); +} + +#define CCADC_PROGRAM_TRIM_COUNT 2 +#define ADC_ARB_BMS_CNTRL_CCADC_SHIFT 4 +#define ADC_ARB_BMS_CNTRL_CONV_MASK 0x03 +#define BMS_CONV_IN_PROGRESS 0x2 + +static int calib_ccadc_program_trim(struct pm8xxx_ccadc_chip *chip, + int addr, u8 data_msb, u8 data_lsb, + int wait) +{ + int i, rc, loop; + u8 cntrl, sbi_config; + bool in_progress = 0; + + loop = wait ? CCADC_PROGRAM_TRIM_COUNT : 0; + + calib_ccadc_enable_trim_access(chip, &sbi_config); + + for (i = 0; i < loop; i++) { + rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_BMS_CNTRL, &cntrl); + if (rc < 0) { + pr_err("error = %d reading ADC_ARB_BMS_CNTRL\n", rc); + return rc; + } + + /* break if a ccadc conversion is not happening */ + in_progress = (((cntrl >> ADC_ARB_BMS_CNTRL_CCADC_SHIFT) + & ADC_ARB_BMS_CNTRL_CONV_MASK) == BMS_CONV_IN_PROGRESS); + + if (!in_progress) + break; + } + + if (in_progress) { + pr_debug("conv in progress cannot write trim,returing EBUSY\n"); + return -EBUSY; + } + + rc = pm8xxx_writeb(chip->dev->parent, addr, data_msb); + if (rc < 0) { + pr_err("error = %d write msb = 0x%x\n", rc, data_msb); + return rc; + } + rc = pm8xxx_writeb(chip->dev->parent, addr + 1, data_lsb); + if (rc < 0) { + pr_err("error = %d write lsb = 0x%x\n", rc, data_lsb); + return rc; + } + calib_ccadc_restore_trim_access(chip, sbi_config); + return 0; +} + +void pm8xxx_calib_ccadc(void) +{ + u8 data_msb, data_lsb, sec_cntrl; + int result_offset, result_gain; + u16 result; + int i, rc; + + rc = pm8xxx_readb(the_chip->dev->parent, + ADC_ARB_SECP_CNTRL, &sec_cntrl); + if (rc < 0) { + pr_err("error = %d reading ADC_ARB_SECP_CNTRL\n", rc); + return; + } + + rc = calib_ccadc_enable_arbiter(the_chip); + if (rc < 0) { + pr_err("error = %d enabling arbiter for offset\n", rc); + goto bail; + } + + /* + * Set decimation ratio to 4k, lower ratio may be used in order to speed + * up, pending verification through bench + */ + rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM, + CCADC_CALIB_DIG_PARAM); + if (rc < 0) { + pr_err("error = %d writing ADC_ARB_SECP_DIG_PARAM\n", rc); + goto bail; + } + + result_offset = 0; + for (i = 0; i < SAMPLE_COUNT; i++) { + /* Short analog inputs to CCADC internally to ground */ + rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV, + CCADC_CALIB_RSV_GND); + if (rc < 0) { + pr_err("error = %d selecting gnd voltage\n", rc); + goto bail; + } + + /* Enable CCADC */ + rc = pm8xxx_writeb(the_chip->dev->parent, + ADC_ARB_SECP_ANA_PARAM, CCADC_CALIB_ANA_PARAM); + if (rc < 0) { + pr_err("error = %d enabling ccadc\n", rc); + goto bail; + } + + rc = calib_start_conv(the_chip, &result); + if (rc < 0) { + pr_err("error = %d for zero volt measurement\n", rc); + goto bail; + } + + result_offset += result; + } + + result_offset = result_offset / SAMPLE_COUNT; + + + pr_debug("offset result_offset = 0x%x, voltage = %llduV\n", + result_offset, + pm8xxx_ccadc_reading_to_microvolt(the_chip->revision, + ((s64)result_offset - CCADC_INTRINSIC_OFFSET))); + + the_chip->ccadc_offset = result_offset; + data_msb = the_chip->ccadc_offset >> 8; + data_lsb = the_chip->ccadc_offset; + + rc = calib_ccadc_program_trim(the_chip, CCADC_OFFSET_TRIM1, + data_msb, data_lsb, 1); + if (rc) { + pr_debug("error = %d programming offset trim 0x%02x 0x%02x\n", + rc, data_msb, data_lsb); + /* enable the interrupt and write it when it fires */ + enable_irq(the_chip->eoc_irq); + } + + rc = calib_ccadc_enable_arbiter(the_chip); + if (rc < 0) { + pr_err("error = %d enabling arbiter for gain\n", rc); + goto bail; + } + + /* + * Set decimation ratio to 4k, lower ratio may be used in order to speed + * up, pending verification through bench + */ + rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM, + CCADC_CALIB_DIG_PARAM); + if (rc < 0) { + pr_err("error = %d enabling decimation ration for gain\n", rc); + goto bail; + } + + result_gain = 0; + for (i = 0; i < SAMPLE_COUNT; i++) { + rc = pm8xxx_writeb(the_chip->dev->parent, + ADC_ARB_SECP_RSV, CCADC_CALIB_RSV_25MV); + if (rc < 0) { + pr_err("error = %d selecting 25mV for gain\n", rc); + goto bail; + } + + /* Enable CCADC */ + rc = pm8xxx_writeb(the_chip->dev->parent, + ADC_ARB_SECP_ANA_PARAM, CCADC_CALIB_ANA_PARAM); + if (rc < 0) { + pr_err("error = %d enabling ccadc\n", rc); + goto bail; + } + + rc = calib_start_conv(the_chip, &result); + if (rc < 0) { + pr_err("error = %d for adc reading 25mV\n", rc); + goto bail; + } + + result_gain += result; + } + result_gain = result_gain / SAMPLE_COUNT; + + /* + * result_offset includes INTRINSIC OFFSET + * the_chip->ccadc_gain_uv will be the actual voltage + * measured for 25000UV + */ + the_chip->ccadc_gain_uv = pm8xxx_ccadc_reading_to_microvolt( + the_chip->revision, + ((s64)result_gain - result_offset)); + + pr_debug("gain result_gain = 0x%x, voltage = %d microVolts\n", + result_gain, the_chip->ccadc_gain_uv); + + data_msb = result_gain >> 8; + data_lsb = result_gain; + rc = calib_ccadc_program_trim(the_chip, CCADC_FULLSCALE_TRIM1, + data_msb, data_lsb, 0); + if (rc) + pr_debug("error = %d programming gain trim\n", rc); +bail: + pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_CNTRL, sec_cntrl); +} +EXPORT_SYMBOL(pm8xxx_calib_ccadc); + +static irqreturn_t pm8921_bms_ccadc_eoc_handler(int irq, void *data) +{ + u8 data_msb, data_lsb; + struct pm8xxx_ccadc_chip *chip = data; + int rc; + + pr_debug("irq = %d triggered\n", irq); + data_msb = chip->ccadc_offset >> 8; + data_lsb = chip->ccadc_offset; + + rc = calib_ccadc_program_trim(chip, CCADC_OFFSET_TRIM1, + data_msb, data_lsb, 0); + disable_irq_nosync(chip->eoc_irq); + + return IRQ_HANDLED; +} + +#define CCADC_IBAT_DIG_PARAM 0xA3 +#define CCADC_IBAT_RSV 0x10 +#define CCADC_IBAT_ANA_PARAM 0x1A +static int ccadc_get_rsense_voltage(int *voltage_uv) +{ + u16 raw; + int result; + int rc = 0; + + rc = calib_ccadc_enable_arbiter(the_chip); + if (rc < 0) { + pr_err("error = %d enabling arbiter for offset\n", rc); + return rc; + } + + rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM, + CCADC_IBAT_DIG_PARAM); + if (rc < 0) { + pr_err("error = %d writing ADC_ARB_SECP_DIG_PARAM\n", rc); + return rc; + } + + rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV, + CCADC_IBAT_RSV); + if (rc < 0) { + pr_err("error = %d selecting rsense\n", rc); + return rc; + } + + rc = pm8xxx_writeb(the_chip->dev->parent, + ADC_ARB_SECP_ANA_PARAM, CCADC_IBAT_ANA_PARAM); + if (rc < 0) { + pr_err("error = %d enabling ccadc\n", rc); + return rc; + } + + rc = calib_start_conv(the_chip, &raw); + if (rc < 0) { + pr_err("error = %d for zero volt measurement\n", rc); + return rc; + } + + pr_debug("Vsense raw = 0x%x\n", raw); + result = cc_adjust_for_offset(raw); + pr_debug("Vsense after offset raw = 0x%x offset=0x%x\n", + result, + the_chip->ccadc_offset); + *voltage_uv = pm8xxx_ccadc_reading_to_microvolt(the_chip->revision, + ((s64)result)); + pr_debug("Vsense before gain of %d = %d uV\n", the_chip->ccadc_gain_uv, + *voltage_uv); + *voltage_uv = pm8xxx_cc_adjust_for_gain(*voltage_uv); + + pr_debug("Vsense = %d uV\n", *voltage_uv); + return 0; +} + +int pm8xxx_ccadc_get_battery_current(int *bat_current_ua) +{ + int voltage_uv = 0, rc; + + rc = ccadc_get_rsense_voltage(&voltage_uv); + if (rc) { + pr_err("cant get voltage across rsense rc = %d\n", rc); + return rc; + } + + *bat_current_ua = voltage_uv * 1000/the_chip->r_sense; + /* + * ccadc reads +ve current when the battery is charging + * We need to return -ve if the battery is charging + */ + *bat_current_ua = -1 * (*bat_current_ua); + pr_debug("bat current = %d ma\n", *bat_current_ua); + return 0; +} +EXPORT_SYMBOL(pm8xxx_ccadc_get_battery_current); + +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 int get_calc(void *data, u64 * val) +{ + int ibat, rc; + + rc = pm8xxx_ccadc_get_battery_current(&ibat); + *val = ibat; + return rc; +} +DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, NULL, "%lld\n"); + +static void create_debugfs_entries(struct pm8xxx_ccadc_chip *chip) +{ + chip->dent = debugfs_create_dir("pm8xxx-ccadc", NULL); + + if (IS_ERR(chip->dent)) { + pr_err("ccadc couldnt create debugfs dir\n"); + return; + } + + 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("CCADC_FULLSCALE_TRIM1", 0644, chip->dent, + (void *)CCADC_FULLSCALE_TRIM1, ®_fops); + debugfs_create_file("CCADC_FULLSCALE_TRIM0", 0644, chip->dent, + (void *)CCADC_FULLSCALE_TRIM0, ®_fops); + + debugfs_create_file("show_ibatt", 0644, chip->dent, + (void *)0, &calc_fops); +} + +static int __devinit pm8xxx_ccadc_probe(struct platform_device *pdev) +{ + int rc = 0; + struct pm8xxx_ccadc_chip *chip; + struct resource *res; + const struct pm8xxx_ccadc_platform_data *pdata + = pdev->dev.platform_data; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "PM8921_BMS_CCADC_EOC"); + if (!res) { + pr_err("failed to get irq\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8xxx_ccadc_chip), GFP_KERNEL); + if (!chip) { + pr_err("Cannot allocate pm_bms_chip\n"); + return -ENOMEM; + } + chip->dev = &pdev->dev; + chip->revision = pm8xxx_get_revision(chip->dev->parent); + chip->eoc_irq = res->start; + chip->r_sense = pdata->r_sense; + + calib_ccadc_read_offset_and_gain(chip, + &chip->ccadc_gain_uv, + &chip->ccadc_offset); + rc = request_irq(chip->eoc_irq, + pm8921_bms_ccadc_eoc_handler, IRQF_TRIGGER_RISING, + "bms_eoc_ccadc", chip); + if (rc) { + pr_err("failed to request %d irq rc= %d\n", chip->eoc_irq, rc); + goto free_chip; + } + disable_irq_nosync(chip->eoc_irq); + + platform_set_drvdata(pdev, chip); + the_chip = chip; + + create_debugfs_entries(chip); + + return 0; + +free_chip: + kfree(chip); + return rc; +} + +static int __devexit pm8xxx_ccadc_remove(struct platform_device *pdev) +{ + struct pm8xxx_ccadc_chip *chip = platform_get_drvdata(pdev); + + debugfs_remove_recursive(chip->dent); + the_chip = NULL; + kfree(chip); + return 0; +} + +static struct platform_driver pm8xxx_ccadc_driver = { + .probe = pm8xxx_ccadc_probe, + .remove = __devexit_p(pm8xxx_ccadc_remove), + .driver = { + .name = PM8XXX_CCADC_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_ccadc_init(void) +{ + return platform_driver_register(&pm8xxx_ccadc_driver); +} + +static void __exit pm8xxx_ccadc_exit(void) +{ + platform_driver_unregister(&pm8xxx_ccadc_driver); +} + +module_init(pm8xxx_ccadc_init); +module_exit(pm8xxx_ccadc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8XXX ccadc driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_CCADC_DEV_NAME); diff --git a/drivers/power/pmic8058-charger.c b/drivers/power/pmic8058-charger.c new file mode 100644 index 0000000000000000000000000000000000000000..70b5d598dc2c136c9f38dadf2fda162fbad928bf --- /dev/null +++ b/drivers/power/pmic8058-charger.c @@ -0,0 +1,2047 @@ +/* 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 + +/* 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 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 struct pmic8058_charger_data chg_data; + +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 = pm8xxx_batt_alarm_threshold_set( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR, resume_mv); + if (!rc) + rc = pm8xxx_batt_alarm_threshold_set( + PM8XXX_BATT_ALARM_UPPER_COMPARATOR, 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 ret; + + ret = pm8xxx_read_irq_stat(pm8058_chg.dev->parent, irq); + 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; + + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL_2 = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_VMAX_SEL, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_VMAX_SEL = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_VBAT_DET, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_VBAT_DET = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_IMAX, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_IMAX = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TRICKLE, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TRICKLE = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_ITERM, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_ITERM = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TTRKL_MAX, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TTRKL_MAX = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TCHG_MAX, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TCHG_MAX = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEMP_THRESH, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_THRESH = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEMP_REG, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_REG = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_PULSE, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_PULSE = 0x%x\n", temp); + + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_1, + &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_1 = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_3, + &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_3 = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_10, + &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_10 = 0x%x\n", + temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_11, + &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_11 = 0x%x\n", + temp); + + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_1, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_1 = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_3, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_3 = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_10, &temp); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_10 = 0x%x\n", temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_11, &temp); + 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 = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_USB_SUSPEND); + else + temp &= ~BIT(CHG_USB_SUSPEND); + + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, temp); +} + +static int pm_chg_auto_disable(int value) +{ + u8 temp; + int ret; + + ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp); + if (ret) + return ret; + if (value) + temp |= BIT(CHARGE_AUTO_DIS); + else + temp &= ~BIT(CHARGE_AUTO_DIS); + + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp); +} + +static int pm_chg_batt_temp_disable(int value) +{ + u8 temp; + int ret; + + ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_BATT_TEMP_DIS); + else + temp &= ~BIT(CHG_BATT_TEMP_DIS); + + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp); +} + +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 pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_VBAT_DET, temp); +} + +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 pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_IMAX, temp); +} + +#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 pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_VMAX_SEL, temp); +} + +static int pm_chg_failed_clear(int value) +{ + u8 temp; + int ret; + + ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_FAILED_CLEAR); + else + temp &= ~BIT(CHG_FAILED_CLEAR); + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp); +} + +static int pm_chg_iterm_set(int chg_current) +{ + u8 temp; + + temp = (chg_current / PM8058_CHG_I_TERM_STEP_MA) - 1; + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_ITERM, temp); +} + +static int pm_chg_tchg_set(int minutes) +{ + u8 temp; + + temp = (minutes >> PM8058_CHG_T_TCHG_SHIFT) - 1; + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TCHG_MAX, temp); +} + +static int pm_chg_ttrkl_set(int minutes) +{ + u8 temp; + + temp = minutes - 1; + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TTRKL_MAX, + temp); +} + +static int pm_chg_enum_done_enable(int value) +{ + u8 temp; + int ret; + + ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp); + if (ret) + return ret; + if (value) + temp |= BIT(ENUM_DONE); + else + temp &= ~BIT(ENUM_DONE); + + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp); +} + +static uint32_t get_fsm_state(void) +{ + u8 temp; + + temp = 0x00; + pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, temp); + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, &temp); + return (uint32_t)temp; +} + +static int get_fsm_status(void *data, u64 * val) +{ + *val = get_fsm_state(); + return 0; +} + +enum pmic8058_chg_state pmic8058_get_fsm_state(void) +{ + if (!pm8058_chg.inited) { + pr_err("%s: called when not inited\n", __func__); + return -EINVAL; + } + + return get_fsm_state(); +} + +static int pm_chg_disable(int value) +{ + u8 temp; + int ret; + + ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_CHARGE_DIS); + else + temp &= ~BIT(CHG_CHARGE_DIS); + + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, temp); +} + +static void pm8058_start_system_current(struct msm_hardware_charger *hw_chg, + int max_current) +{ + int ret = 0; + + if (pm8058_chg.disabled) + return; + + ret = pm_chg_imaxsel_set(max_current); + ret |= pm_chg_enum_done_enable(1); + ret |= pm_chg_disable(0); + if (ret) + pr_err("%s: failed to turn on system power err=%d", + __func__, ret); +} + +static void pm8058_stop_system_current(struct msm_hardware_charger *hw_chg) +{ + int ret = 0; + + ret = pm_chg_enum_done_enable(0); + ret |= pm_chg_disable(1); + if (ret) + pr_err("%s: failed to turn off system power err=%d", + __func__, ret); +} + +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 PMIC8058_CHG_STATE_ATC: + case PMIC8058_CHG_STATE_FAST_CHG: + case PMIC8058_CHG_STATE_TRKL_CHG: + rc = pm8xxx_batt_alarm_disable( + PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (!rc) + rc = pm8xxx_batt_alarm_disable( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + 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; + + if (hw_chg->type == CHG_TYPE_AC && chg_data.max_source_current) + chg_current = chg_data.max_source_current; + + 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 = pm8xxx_readb(pm8058_chg.dev->parent, + PM8058_OVP_TEST_REG, + &old); + temp = old | BIT(FORCE_OVP_OFF); + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_OVP_TEST_REG, + temp); + temp = 0xFC; + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_CHG_TEST, temp); + /* 10 ms sleep is for the VCHG to discharge */ + msleep(10); + temp = 0xF0; + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_CHG_TEST, + temp); + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_OVP_TEST_REG, + old); + + 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 = pm8xxx_readb(pm8058_chg.dev->parent, + PM8058_OVP_TEST_REG, &old); + temp = old | BIT(FORCE_OVP_OFF); + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_OVP_TEST_REG, temp); + temp = 0xFC; + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_CHG_TEST, temp); + /* 10 ms sleep is for the VCHG to discharge */ + msleep(10); + temp = 0xF0; + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_CHG_TEST, temp); + ret = pm8xxx_writeb(pm8058_chg.dev->parent, + PM8058_OVP_TEST_REG, old); + + 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(AUTO_CHGDONE_IRQ); + 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 (!pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, temp)) { + pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, &temp); + 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_irq(res->start, + pm8058_chg_auto_chgdone_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_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_irq(res->start, + 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_irq(res->start, + 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_irq(res->start, + 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_irq(res->start, + 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_irq(res->start, + 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_irq(res->start, + 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 = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp); + 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 = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp); + if (rc) + return rc; + if (on) + temp |= BIT(CHG_CHARGE_BAT); + else + temp &= ~BIT(CHG_CHARGE_BAT); + + return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, temp); + +} +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__); + + /* 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); + + 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; + + 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; + pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_2, temp); + temp = 0x84; + pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_2, temp); + msleep(2); + temp = 0x80; + pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_2, temp); + return 0; +} + +static int get_reg(void *data, u64 * val) +{ + int i = (int)data; + int ret; + u8 temp; + + ret = pm8xxx_readb(pm8058_chg.dev->parent, i, &temp); + 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 = pm8xxx_writeb(pm8058_chg.dev->parent, i, temp); + 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, + .start_system_current = pm8058_start_system_current, + .stop_system_current = pm8058_stop_system_current, +}; + +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 = pm8xxx_batt_alarm_disable( + PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (!rc) + rc = pm8xxx_batt_alarm_disable( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + 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) +{ + int rc; + /* enable low comparator */ + rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (!rc) + return pm8xxx_batt_alarm_enable( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + + return rc; + +} + +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 |= pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST, temp); + ret |= pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEST, &old); + old = old & ~BIT(IGNORE_LL); + temp = 0x90 | (0xF & old); + ret |= pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST, temp); + + return ret; +} + +static int __devinit pm8058_charger_probe(struct platform_device *pdev) +{ + struct pmic8058_charger_data *pdata; + int rc = 0; + + pm8058_chg.pdata = pdev->dev.platform_data; + pm8058_chg.dev = &pdev->dev; + pdata = (struct pmic8058_charger_data *) pm8058_chg.pdata; + + if (pdata == NULL) { + pr_err("%s: pdata not present\n", __func__); + return -EINVAL; + } + + if (pdata->charger_data_valid) { + usb_hw_chg.type = pdata->charger_type; + chg_data.charger_type = pdata->charger_type; + chg_data.max_source_current = pdata->max_source_current; + } + + 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 = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (!rc) + rc = pm8xxx_batt_alarm_disable( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + 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 = pm8xxx_batt_alarm_threshold_set( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR, resume_mv); + if (!rc) + rc = pm8xxx_batt_alarm_threshold_set( + PM8XXX_BATT_ALARM_UPPER_COMPARATOR, 4300); + if (rc) { + pr_err("%s: unable to set batt alarm threshold\n", __func__); + goto free_irq; + } + + rc = pm8xxx_batt_alarm_hold_time_set( + PM8XXX_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 = pm8xxx_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 = pm8xxx_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 = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (!rc) + rc = pm8xxx_batt_alarm_disable( + PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + if (rc) + pr_err("%s: unable to set batt alarm state\n", __func__); + + rc |= pm8xxx_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/power_supply_core.c b/drivers/power/power_supply_core.c index f58d1805bd4e692f160e0a3d35414bd9baddbe21..be6ba043c8369cab1d9913b2dab42fb161c7d8e5 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -25,6 +25,61 @@ EXPORT_SYMBOL_GPL(power_supply_class); static struct device_type power_supply_dev_type; +/** + * power_supply_set_current_limit - set current limit + * @psy: the power supply to control + * @limit: current limit in uA from the power supply. + * 0 will disable the power supply. + * + * This function will set a maximum supply current from a source + * and it will disable the charger when limit is 0. + */ +int power_supply_set_current_limit(struct power_supply *psy, int limit) +{ + const union power_supply_propval ret = {limit,}; + + if (psy->set_property) + return psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, + &ret); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(power_supply_set_current_limit); + +/** + * power_supply_set_online - set online state of the power supply + * @psy: the power supply to control + * @enable: sets online property of power supply + */ +int power_supply_set_online(struct power_supply *psy, bool enable) +{ + const union power_supply_propval ret = {enable,}; + + if (psy->set_property) + return psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, + &ret); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(power_supply_set_online); + +/** + * power_supply_set_charge_type - set charge type of the power supply + * @psy: the power supply to control + * @enable: sets charge type property of power supply + */ +int power_supply_set_charge_type(struct power_supply *psy, int charge_type) +{ + const union power_supply_propval ret = {charge_type,}; + + if (psy->set_property) + return psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_TYPE, + &ret); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(power_supply_set_charge_type); + static int __power_supply_changed_work(struct device *dev, void *data) { struct power_supply *psy = (struct power_supply *)data; diff --git a/drivers/power/qci_battery.c b/drivers/power/qci_battery.c new file mode 100644 index 0000000000000000000000000000000000000000..724bcba27ae1bab624fc2688d5e91f95bcc27df1 --- /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 0000000000000000000000000000000000000000..dcbb62b533145c45b7bf14570bc627d9e9c2310f --- /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 0000000000000000000000000000000000000000..7ff8e2871b99af086420eaee557eeb648d76795b --- /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/power/smb349.c b/drivers/power/smb349.c new file mode 100644 index 0000000000000000000000000000000000000000..4c072850e02539963255dd56eb2cab9b8dbe54e9 --- /dev/null +++ b/drivers/power/smb349.c @@ -0,0 +1,700 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 SMB349_MASK(BITS, POS) ((unsigned char)(((1 << BITS) - 1) << POS)) + +/* Register definitions */ +#define CHG_CURRENT_REG 0x00 +#define CHG_OTHER_CURRENT_REG 0x01 +#define VAR_FUNC_REG 0x02 +#define FLOAT_VOLTAGE_REG 0x03 +#define CHG_CTRL_REG 0x04 +#define STAT_TIMER_REG 0x05 +#define PIN_ENABLE_CTRL_REG 0x06 +#define THERM_CTRL_A_REG 0x07 +#define SYSOK_USB3_SELECT_REG 0x08 +#define CTRL_FUNCTIONS_REG 0x09 +#define OTG_TLIM_THERM_CNTRL_REG 0x0A +#define HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG 0x0B +#define FAULT_IRQ_REG 0x0C +#define STATUS_IRQ_REG 0x0D +#define SYSOK_REG 0x0E +#define CMD_A_REG 0x30 +#define CMD_B_REG 0x31 +#define CMD_C_REG 0x33 +#define IRQ_A_REG 0x35 +#define IRQ_B_REG 0x36 +#define IRQ_C_REG 0x37 +#define IRQ_D_REG 0x38 +#define IRQ_E_REG 0x39 +#define IRQ_F_REG 0x3A +#define STATUS_A_REG 0x3B +#define STATUS_B_REG 0x3C +#define STATUS_C_REG 0x3D +#define STATUS_D_REG 0x3E +#define STATUS_E_REG 0x3F + +/* Status bits and masks */ +#define CHG_STATUS_MASK SMB349_MASK(2, 1) +#define CHG_ENABLE_STATUS_BIT BIT(0) + +/* Control bits and masks */ +#define FAST_CHG_CURRENT_MASK SMB349_MASK(4, 4) +#define AC_INPUT_CURRENT_LIMIT_MASK SMB349_MASK(4, 0) +#define PRE_CHG_CURRENT_MASK SMB349_MASK(3, 5) +#define TERMINATION_CURRENT_MASK SMB349_MASK(3, 2) +#define PRE_CHG_TO_FAST_CHG_THRESH_MASK SMB349_MASK(2, 6) +#define FLOAT_VOLTAGE_MASK SMB349_MASK(6, 0) +#define CHG_ENABLE_BIT BIT(1) +#define VOLATILE_W_PERM_BIT BIT(7) +#define USB_SELECTION_BIT BIT(1) +#define SYSTEM_FET_ENABLE_BIT BIT(7) +#define AUTOMATIC_INPUT_CURR_LIMIT_BIT BIT(4) +#define AUTOMATIC_POWER_SOURCE_DETECTION_BIT BIT(2) +#define BATT_OV_END_CHG_BIT BIT(1) +#define VCHG_FUNCTION BIT(0) +#define CURR_TERM_END_CHG_BIT BIT(6) + +struct smb349_struct { + struct i2c_client *client; + bool charging; + bool present; + int chg_current_ma; + + int en_n_gpio; + int chg_susp_gpio; + struct dentry *dent; + spinlock_t lock; + struct work_struct hwinit_work; + + struct power_supply dc_psy; +}; + +struct chg_ma_limit_entry { + int fast_chg_ma_limit; + int ac_input_ma_limit; + u8 chg_current_value; +}; + +static struct smb349_struct *the_smb349_chg; + +static int smb349_read_reg(struct i2c_client *client, int reg, + u8 *val) +{ + s32 ret; + struct smb349_struct *smb349_chg; + + smb349_chg = i2c_get_clientdata(client); + ret = i2c_smbus_read_byte_data(smb349_chg->client, reg); + if (ret < 0) { + dev_err(&smb349_chg->client->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return ret; + } else { + *val = ret; + } + + return 0; +} + +static int smb349_write_reg(struct i2c_client *client, int reg, + u8 val) +{ + s32 ret; + struct smb349_struct *smb349_chg; + + smb349_chg = i2c_get_clientdata(client); + ret = i2c_smbus_write_byte_data(smb349_chg->client, reg, val); + if (ret < 0) { + dev_err(&smb349_chg->client->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return ret; + } + return 0; +} + +static int smb349_masked_write(struct i2c_client *client, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + rc = smb349_read_reg(client, reg, &temp); + if (rc) { + pr_err("smb349_read_reg failed: reg=%03X, rc=%d\n", reg, rc); + return rc; + } + temp &= ~mask; + temp |= val & mask; + rc = smb349_write_reg(client, reg, temp); + if (rc) { + pr_err("smb349_write failed: reg=%03X, rc=%d\n", reg, rc); + return rc; + } + return 0; +} + +static enum power_supply_property pm_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CHARGE_TYPE, +}; + +static char *pm_power_supplied_to[] = { + "battery", +}; + +static int get_prop_charge_type(struct smb349_struct *smb349_chg) +{ + if (smb349_chg->charging) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int pm_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb349_struct *smb349_chg = container_of(psy, + struct smb349_struct, + dc_psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = smb349_chg->chg_current_ma; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (int)smb349_chg->present; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(smb349_chg); + break; + default: + return -EINVAL; + } + return 0; +} + +#define SMB349_FAST_CHG_MIN_MA 1000 +#define SMB349_FAST_CHG_STEP_MA 200 +#define SMB349_FAST_CHG_MAX_MA 4000 +#define SMB349_FAST_CHG_SHIFT 4 +static int chg_current_set(struct smb349_struct *smb349_chg) +{ + u8 temp; + + if ((smb349_chg->chg_current_ma < SMB349_FAST_CHG_MIN_MA) || + (smb349_chg->chg_current_ma > SMB349_FAST_CHG_MAX_MA)) { + pr_err("bad mA=%d asked to set\n", smb349_chg->chg_current_ma); + return -EINVAL; + } + + temp = (smb349_chg->chg_current_ma - SMB349_FAST_CHG_MIN_MA) + / SMB349_FAST_CHG_STEP_MA; + + temp = temp << SMB349_FAST_CHG_SHIFT; + pr_debug("fastchg limit=%d setting %02x\n", + smb349_chg->chg_current_ma, temp); + return smb349_masked_write(smb349_chg->client, CHG_CURRENT_REG, + FAST_CHG_CURRENT_MASK, temp); +} + +static int set_reg(void *data, u64 val) +{ + int addr = (int)data; + int ret; + u8 temp; + + temp = (u16) val; + ret = smb349_write_reg(the_smb349_chg->client, addr, temp); + + if (ret) { + pr_err("smb349_write_reg to %x value =%d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + return 0; +} +static int get_reg(void *data, u64 *val) +{ + int addr = (int)data; + int ret; + u8 temp; + + ret = smb349_read_reg(the_smb349_chg->client, addr, &temp); + if (ret) { + pr_err("smb349_read_reg to %x value =%d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + + *val = temp; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); + +static void create_debugfs_entries(struct smb349_struct *smb349_chg) +{ + struct dentry *file; + smb349_chg->dent = debugfs_create_dir(SMB349_NAME, NULL); + if (IS_ERR(smb349_chg->dent)) { + pr_err("smb349 driver couldn't create debugfs dir\n"); + return; + } + + file = debugfs_create_file("CHG_CURRENT_REG", 0644, smb349_chg->dent, + (void *) CHG_CURRENT_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("CHG_OTHER_CURRENT_REG", 0644, + smb349_chg->dent, (void *) CHG_OTHER_CURRENT_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("VAR_FUNC_REG", 0644, smb349_chg->dent, + (void *) VAR_FUNC_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("FLOAT_VOLTAGE_REG", 0644, smb349_chg->dent, + (void *) FLOAT_VOLTAGE_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("CHG_CTRL_REG", 0644, smb349_chg->dent, + (void *) CHG_CTRL_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("STAT_TIMER_REG", 0644, smb349_chg->dent, + (void *) STAT_TIMER_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("PIN_ENABLE_CTRL_REG", 0644, + smb349_chg->dent, (void *) PIN_ENABLE_CTRL_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("PIN_ENABLE_CTRL_REG", 0644, + smb349_chg->dent, (void *) PIN_ENABLE_CTRL_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("PIN_ENABLE_CTRL_REG", 0644, + smb349_chg->dent, (void *) PIN_ENABLE_CTRL_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("THERM_CTRL_A_REG", 0644, smb349_chg->dent, + (void *) THERM_CTRL_A_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("SYSOK_USB3_SELECT_REG", 0644, + smb349_chg->dent, (void *) SYSOK_USB3_SELECT_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("CTRL_FUNCTIONS_REG", 0644, + smb349_chg->dent, (void *) CTRL_FUNCTIONS_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("OTG_TLIM_THERM_CNTRL_REG", 0644, + smb349_chg->dent, (void *) OTG_TLIM_THERM_CNTRL_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG", + 0644, smb349_chg->dent, + (void *) HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("SYSOK_REG", 0644, smb349_chg->dent, + (void *) SYSOK_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("CMD_A_REG", 0644, smb349_chg->dent, + (void *) CMD_A_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("CMD_B_REG", 0644, smb349_chg->dent, + (void *) CMD_B_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("CMD_C_REG", 0644, smb349_chg->dent, + (void *) CMD_C_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("STATUS_A_REG", 0644, smb349_chg->dent, + (void *) STATUS_A_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("STATUS_B_REG", 0644, smb349_chg->dent, + (void *) STATUS_B_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("STATUS_C_REG", 0644, smb349_chg->dent, + (void *) STATUS_C_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("STATUS_D_REG", 0644, smb349_chg->dent, + (void *) STATUS_D_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } + file = debugfs_create_file("STATUS_E_REG", 0644, smb349_chg->dent, + (void *) STATUS_E_REG, ®_fops); + if (IS_ERR(file)) { + pr_err("smb349 driver couldn't create debugfs files\n"); + return; + } +} + +static void remove_debugfs_entries(struct smb349_struct *smb349_chg) +{ + if (smb349_chg->dent) + debugfs_remove_recursive(smb349_chg->dent); +} + +static int smb349_hwinit(struct smb349_struct *smb349_chg) +{ + int ret; + + ret = smb349_write_reg(smb349_chg->client, CMD_A_REG, + VOLATILE_W_PERM_BIT); + if (ret) { + pr_err("Failed to set VOLATILE_W_PERM_BIT rc=%d\n", ret); + return ret; + } + + ret = smb349_masked_write(smb349_chg->client, CHG_CTRL_REG, + CURR_TERM_END_CHG_BIT, CURR_TERM_END_CHG_BIT); + if (ret) { + pr_err("Failed to set CURR_TERM_END_CHG_BIT rc=%d\n", ret); + return ret; + } + + ret = chg_current_set(smb349_chg); + if (ret) { + pr_err("Failed to set FAST_CHG_CURRENT rc=%d\n", ret); + return ret; + } + + return 0; +} + +static int smb349_stop_charging(struct smb349_struct *smb349_chg) +{ + unsigned long flags; + + if (smb349_chg->charging) + gpio_set_value_cansleep(smb349_chg->en_n_gpio, 0); + + spin_lock_irqsave(&smb349_chg->lock, flags); + pr_debug("stop charging %d\n", smb349_chg->charging); + smb349_chg->charging = 0; + spin_unlock_irqrestore(&smb349_chg->lock, flags); + power_supply_changed(&smb349_chg->dc_psy); + return 0; +} + +static int smb349_start_charging(struct smb349_struct *smb349_chg) +{ + unsigned long flags; + int rc; + + rc = 0; + if (!smb349_chg->charging) { + gpio_set_value_cansleep(smb349_chg->en_n_gpio, 1); + /* + * Write non-default values, charger chip reloads from + * non-volatile memory if it was in suspend mode + * + */ + rc = schedule_work(&smb349_chg->hwinit_work); + } + + spin_lock_irqsave(&smb349_chg->lock, flags); + pr_debug("start charging %d\n", smb349_chg->charging); + smb349_chg->charging = 1; + spin_unlock_irqrestore(&smb349_chg->lock, flags); + power_supply_changed(&smb349_chg->dc_psy); + return rc; +} + +static int pm_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb349_struct *smb349_chg = container_of(psy, + struct smb349_struct, + dc_psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval) { + smb349_chg->present = val->intval; + } else { + smb349_chg->present = 0; + if (smb349_chg->charging) + return smb349_stop_charging(smb349_chg); + } + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (val->intval) { + if (smb349_chg->chg_current_ma != val->intval) + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (val->intval && smb349_chg->present) { + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_FAST) + return smb349_start_charging(smb349_chg); + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE) + return smb349_stop_charging(smb349_chg); + } else { + return -EINVAL; + } + break; + default: + return -EINVAL; + } + power_supply_changed(&smb349_chg->dc_psy); + return 0; +} + +static void hwinit_worker(struct work_struct *work) +{ + int ret; + struct smb349_struct *smb349_chg = container_of(work, + struct smb349_struct, hwinit_work); + + ret = smb349_hwinit(smb349_chg); + if (ret) + pr_err("Failed to re-initilaze registers\n"); +} + +static int __devinit smb349_init_ext_chg(struct smb349_struct *smb349_chg) +{ + int ret; + + smb349_chg->dc_psy.name = "dc"; + smb349_chg->dc_psy.type = POWER_SUPPLY_TYPE_MAINS; + smb349_chg->dc_psy.supplied_to = pm_power_supplied_to; + smb349_chg->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to); + smb349_chg->dc_psy.properties = pm_power_props; + smb349_chg->dc_psy.num_properties = ARRAY_SIZE(pm_power_props); + smb349_chg->dc_psy.get_property = pm_power_get_property; + smb349_chg->dc_psy.set_property = pm_power_set_property; + + ret = power_supply_register(&smb349_chg->client->dev, + &smb349_chg->dc_psy); + if (ret) { + pr_err("failed to register power_supply. ret=%d.\n", ret); + return ret; + } + + return 0; +} + +static int __devinit smb349_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct smb349_platform_data *pdata; + struct smb349_struct *smb349_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; + } + + smb349_chg = kzalloc(sizeof(*smb349_chg), GFP_KERNEL); + if (!smb349_chg) { + ret = -ENOMEM; + goto out; + } + + smb349_chg->client = client; + smb349_chg->chg_current_ma = pdata->chg_current_ma; + ret = gpio_request(pdata->chg_susp_gpio, "smb349_suspend"); + if (ret) { + dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n", + __func__, pdata->chg_susp_gpio, ret); + goto free_smb349_chg; + } + smb349_chg->chg_susp_gpio = pdata->chg_susp_gpio; + + ret = gpio_request(pdata->en_n_gpio, "smb349_charger_enable"); + if (ret) { + dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n", + __func__, pdata->en_n_gpio, ret); + goto chg_susp_gpio_fail; + } + smb349_chg->en_n_gpio = pdata->en_n_gpio; + + i2c_set_clientdata(client, smb349_chg); + + ret = smb349_hwinit(smb349_chg); + if (ret) + goto free_smb349_chg; + + ret = smb349_init_ext_chg(smb349_chg); + if (ret) + goto chg_en_gpio_fail; + + the_smb349_chg = smb349_chg; + + create_debugfs_entries(smb349_chg); + INIT_WORK(&smb349_chg->hwinit_work, hwinit_worker); + + pr_info("OK connector present = %d\n", smb349_chg->present); + return 0; + +chg_en_gpio_fail: + gpio_free(smb349_chg->en_n_gpio); +chg_susp_gpio_fail: + gpio_free(smb349_chg->chg_susp_gpio); +free_smb349_chg: + kfree(smb349_chg); +out: + return ret; +} + +static int __devexit smb349_remove(struct i2c_client *client) +{ + const struct smb349_platform_data *pdata; + struct smb349_struct *smb349_chg = i2c_get_clientdata(client); + + flush_work(&smb349_chg->hwinit_work); + pdata = client->dev.platform_data; + power_supply_unregister(&smb349_chg->dc_psy); + gpio_free(pdata->en_n_gpio); + gpio_free(pdata->chg_susp_gpio); + remove_debugfs_entries(smb349_chg); + kfree(smb349_chg); + return 0; +} + +static int smb349_suspend(struct device *dev) +{ + struct smb349_struct *smb349_chg = dev_get_drvdata(dev); + + pr_debug("suspend\n"); + if (smb349_chg->charging) + return -EBUSY; + return 0; +} + +static int smb349_resume(struct device *dev) +{ + pr_debug("resume\n"); + + return 0; +} + +static const struct dev_pm_ops smb349_pm_ops = { + .suspend = smb349_suspend, + .resume = smb349_resume, +}; + +static const struct i2c_device_id smb349_id[] = { + {SMB349_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb349_id); + +static struct i2c_driver smb349_driver = { + .driver = { + .name = SMB349_NAME, + .owner = THIS_MODULE, + .pm = &smb349_pm_ops, + }, + .probe = smb349_probe, + .remove = __devexit_p(smb349_remove), + .id_table = smb349_id, +}; + +static int __init smb349_init(void) +{ + return i2c_add_driver(&smb349_driver); +} +module_init(smb349_init); + +static void __exit smb349_exit(void) +{ + return i2c_del_driver(&smb349_driver); +} +module_exit(smb349_exit); + +MODULE_DESCRIPTION("Driver for SMB349 charger chip"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:" SMB349_NAME); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 36db5a441ebacda2498fc4cfcc5b0e24d9437a9f..2bbc7963f304a34f79db987cadf2d574ab0d7965 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -366,5 +366,65 @@ config REGULATOR_WM8994 This driver provides support for the voltage regulators on the WM8994 CODEC. +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_PM8XXX + tristate "Qualcomm PM8XXX PMIC Voltage regulators" + depends on MFD_PM8XXX + help + This driver supports voltage regulators in Qualcomm PM8XXX PMIC chips. + PM8XXX chips provide several different varieties of LDO and switching + regulators. They also provide negative charge pumps and voltage + switches. + +config REGULATOR_MSM_GPIO + tristate "MSM 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. + +config REGULATOR_STUB + tristate "Stub Regulator" + help + This driver adds stub regulator support. The driver is absent of any + real hardware based implementation. It allows for clients to register + their regulator device constraints and use all of the standard + regulator interfaces. This is useful for bringing up new platforms + when the real hardware based implementation may not be yet available. + Clients can use the real regulator device names with proper + constraint checking while the real driver is being developed. + +config REGULATOR_QPNP + depends on OF_SPMI + depends on MSM_QPNP + tristate "Qualcomm QPNP regulator support" + help + This driver supports voltage regulators in Qualcomm PMIC chips which + comply with QPNP. QPNP is a SPMI based PMIC implementation. These + chips provide several different varieties of LDO and switching + regulators. They also provide voltage switches and boost regulators. endif - diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 94b52745e9579ec3fbb9790efef74a5d41315770..7fa396ffc5e81ae5886b14b2b08f4f91e6423f58 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -44,6 +44,13 @@ obj-$(CONFIG_REGULATOR_TPS65217) += tps65217-regulator.o obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o obj-$(CONFIG_REGULATOR_TPS6586X) += tps6586x-regulator.o obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o +obj-$(CONFIG_REGULATOR_AAT2870) += aat2870-regulator.o +obj-$(CONFIG_REGULATOR_PMIC8058) += pmic8058-regulator.o +obj-$(CONFIG_REGULATOR_PMIC8901) += pmic8901-regulator.o +obj-$(CONFIG_REGULATOR_MSM_GPIO) += msm-gpio-regulator.o +obj-$(CONFIG_REGULATOR_PM8058_XO) += pm8058-xo.o +obj-$(CONFIG_REGULATOR_PM8XXX) += pm8xxx-regulator.o +obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o @@ -52,6 +59,6 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o - +obj-$(CONFIG_REGULATOR_QPNP) += qpnp-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 046fb1bd86199c46561774a8a2bbd2a6118a52fc..986d55b9c5e8f4c8e7a1f1ee2b275b4e4043bbe4 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -51,6 +53,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; static struct dentry *debugfs_root; @@ -77,6 +80,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; @@ -170,6 +174,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) @@ -191,6 +204,8 @@ static int regulator_check_consumers(struct regulator_dev *rdev, int *min_uV, int *max_uV) { struct regulator *regulator; + int init_min_uV = *min_uV; + int init_max_uV = *max_uV; list_for_each_entry(regulator, &rdev->consumer_list, list) { /* @@ -200,6 +215,13 @@ static int regulator_check_consumers(struct regulator_dev *rdev, if (!regulator->min_uV && !regulator->max_uV) continue; + if (init_max_uV < regulator->min_uV + || init_min_uV > regulator->max_uV) + rdev_err(rdev, "requested voltage range [%d, %d] does " + "not fit within previously voted range: " + "[%d, %d]\n", init_min_uV, init_max_uV, + regulator->min_uV, regulator->max_uV); + if (*max_uV > regulator->max_uV) *max_uV = regulator->max_uV; if (*min_uV < regulator->min_uV) @@ -632,7 +654,7 @@ 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 || @@ -665,6 +687,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); } @@ -959,7 +989,8 @@ static int set_machine_constraints(struct regulator_dev *rdev, } } - print_constraints(rdev); + if (!suppress_info_printing) + print_constraints(rdev); return 0; out: kfree(rdev->constraints); @@ -981,7 +1012,8 @@ static int set_supply(struct regulator_dev *rdev, { int err; - rdev_info(rdev, "supplied by %s\n", rdev_get_name(supply_rdev)); + if (!suppress_info_printing) + rdev_info(rdev, "supplied by %s\n", rdev_get_name(supply_rdev)); rdev->supply = create_regulator(supply_rdev, &rdev->dev, "SUPPLY"); if (rdev->supply == NULL) { @@ -1535,7 +1567,11 @@ int regulator_enable(struct regulator *regulator) } mutex_lock(&rdev->mutex); + ret = _regulator_enable(rdev); + if (ret == 0) + regulator->enabled++; + mutex_unlock(&rdev->mutex); if (ret != 0 && rdev->supply) @@ -1608,6 +1644,8 @@ int regulator_disable(struct regulator *regulator) mutex_lock(&rdev->mutex); ret = _regulator_disable(rdev); + if (ret == 0) + regulator->enabled--; mutex_unlock(&rdev->mutex); if (ret == 0 && rdev->supply) @@ -1949,6 +1987,7 @@ static int _regulator_do_set_voltage(struct regulator_dev *rdev, int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV) { struct regulator_dev *rdev = regulator->rdev; + int prev_min_uV, prev_max_uV; int ret = 0; mutex_lock(&rdev->mutex); @@ -1971,12 +2010,19 @@ 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; + + prev_min_uV = regulator->min_uV; + prev_max_uV = regulator->max_uV; + regulator->min_uV = min_uV; regulator->max_uV = max_uV; ret = regulator_check_consumers(rdev, &min_uV, &max_uV); - if (ret < 0) + if (ret < 0) { + regulator->min_uV = prev_min_uV; + regulator->max_uV = prev_max_uV; goto out; + } ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); @@ -2553,6 +2599,42 @@ int regulator_bulk_enable(int num_consumers, } EXPORT_SYMBOL_GPL(regulator_bulk_enable); +/** + * regulator_bulk_set_voltage - set voltage for multiple regulator consumers + * + * @num_consumers: Number of consumers + * @consumers: Consumer data; clients are stored here. + * @return 0 on success, an errno on failure + * + * This convenience API allows the voted voltage ranges of multiple regulator + * clients to be set in a single API call. If any consumers cannot have their + * voltages set, this function returns WITHOUT withdrawing votes for any + * consumers that have already been set. + */ +int regulator_bulk_set_voltage(int num_consumers, + struct regulator_bulk_data *consumers) +{ + int i; + int rc; + + for (i = 0; i < num_consumers; i++) { + if (!consumers[i].min_uV && !consumers[i].max_uV) + continue; + rc = regulator_set_voltage(consumers[i].consumer, + consumers[i].min_uV, + consumers[i].max_uV); + if (rc) + goto err; + } + + return 0; + +err: + pr_err("Failed to set voltage for %s: %d\n", consumers[i].supply, rc); + return rc; +} +EXPORT_SYMBOL_GPL(regulator_bulk_set_voltage); + /** * regulator_bulk_disable - disable multiple regulator consumers * @@ -2806,19 +2888,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) { + 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 (!rdev->debugfs) { + if (IS_ERR(rdev->debugfs) || !rdev->debugfs) { rdev_warn(rdev, "Failed to create debugfs directory\n"); - return; + rdev->debugfs = NULL; + goto error; } debugfs_create_u32("use_count", 0444, rdev->debugfs, &rdev->use_count); debugfs_create_u32("open_count", 0444, rdev->debugfs, &rdev->open_count); + 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 @@ -2962,7 +3381,10 @@ struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc, list_add(&rdev->list, ®ulator_list); + mutex_unlock(®ulator_list_mutex); rdev_init_debugfs(rdev); + return rdev; + out: mutex_unlock(®ulator_list_mutex); return rdev; @@ -3117,6 +3539,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 @@ -3273,7 +3711,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); @@ -3284,7 +3723,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/msm-gpio-regulator.c b/drivers/regulator/msm-gpio-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..5c99f4c30780133b31974c8c25f0e1e6ad87e3d2 --- /dev/null +++ b/drivers/regulator/msm-gpio-regulator.c @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +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, NULL); + 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 0000000000000000000000000000000000000000..0d57c020087e36623aa4522abbe0965232c3215f --- /dev/null +++ b/drivers/regulator/pm8058-xo.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 +#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 device *dev; + 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_xo_buffer *xo, + 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 = pm8xxx_writeb(xo->dev->parent, addr, reg); + + if (rc) + pr_err("FAIL: pm8xxx_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); + int rc; + + rc = pm8058_xo_buffer_write(xo, 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); + int rc; + + rc = pm8058_xo_buffer_write(xo, 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_xo_buffer *xo) +{ + int rc; + + /* Save the current control register state */ + rc = pm8xxx_readb(xo->dev->parent, xo->ctrl_addr, &xo->ctrl_reg); + + if (rc) + pr_err("FAIL: pm8xxx_read: rc=%d\n", rc); + return rc; +} + +static int __devinit pm8058_xo_buffer_probe(struct platform_device *pdev) +{ + struct regulator_desc *rdesc; + struct pm8058_xo_buffer *xo; + int rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8058_XO_ID_MAX) { + rdesc = &pm8058_xo_buffer_desc[pdev->id]; + xo = &pm8058_xo_buffer[pdev->id]; + xo->pdata = pdev->dev.platform_data; + xo->dev = &pdev->dev; + + rc = pm8058_init_xo_buffer(xo); + if (rc) + goto bail; + + xo->rdev = regulator_register(rdesc, &pdev->dev, + &xo->pdata->init_data, xo, NULL); + 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/pm8xxx-regulator.c b/drivers/regulator/pm8xxx-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..833c5132a8912338fb75e3a2b339ea1b4a0b3f41 --- /dev/null +++ b/drivers/regulator/pm8xxx-regulator.c @@ -0,0 +1,3281 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* Debug Flag Definitions */ +enum { + PM8XXX_VREG_DEBUG_REQUEST = BIT(0), + PM8XXX_VREG_DEBUG_DUPLICATE = BIT(1), + PM8XXX_VREG_DEBUG_INIT = BIT(2), + PM8XXX_VREG_DEBUG_WRITES = BIT(3), /* SSBI writes */ +}; + +static int pm8xxx_vreg_debug_mask; +module_param_named( + debug_mask, pm8xxx_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +/* 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 + +/* + * 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 0x04 +#define LDO_TEST_LPM_SEL_CTRL 0x00 +#define LDO_TEST_LPM_SEL_TCXO 0x04 + +/* 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_DISABLE 0x40 +#define VS_PULL_DOWN_ENABLE 0x00 + +#define VS_MODE_MASK 0x30 +#define VS_MODE_NORMAL 0x10 +#define VS_MODE_LPM 0x20 + +#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 + +/* TEST register */ +#define VS_OCP_MASK 0x10 +#define VS_OCP_ENABLE 0x00 +#define VS_OCP_DISABLE 0x10 + +/* 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 + +#define VS300_MODE_MASK 0x18 +#define VS300_MODE_NORMAL 0x00 +#define VS300_MODE_LPM 0x08 + +/* 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) + +/* Boost masks and values */ +#define BOOST_ENABLE_MASK 0x80 +#define BOOST_DISABLE 0x00 +#define BOOST_ENABLE 0x80 +#define BOOST_VPROG_MASK 0x1F + +#define BOOST_UV_MIN 4000000 +#define BOOST_UV_MAX 5550000 +#define BOOST_UV_STEP 50000 + +#define BOOST_SET_POINTS ((BOOST_UV_MAX - BOOST_UV_MIN) \ + / BOOST_UV_STEP + 1) + +#define vreg_err(vreg, fmt, ...) \ + pr_err("%s: " fmt, vreg->rdesc.name, ##__VA_ARGS__) + +/* Determines which label to add to the print. */ +enum pm8xxx_regulator_action { + PM8XXX_REGULATOR_ACTION_INIT, + PM8XXX_REGULATOR_ACTION_ENABLE, + PM8XXX_REGULATOR_ACTION_DISABLE, + PM8XXX_REGULATOR_ACTION_VOLTAGE, + PM8XXX_REGULATOR_ACTION_MODE, + PM8XXX_REGULATOR_ACTION_PIN_CTRL, +}; + +/* Debug state printing */ +static void pm8xxx_vreg_show_state(struct regulator_dev *rdev, + enum pm8xxx_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 pm8xxx_vreg_masked_write(struct pm8xxx_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->rdesc.name, addr, rc); + } else { + *reg_save = reg; + vreg->write_count++; + if (pm8xxx_vreg_debug_mask & PM8XXX_VREG_DEBUG_WRITES) + pr_info("%s: write(0x%03X)=0x%02X\n", + vreg->rdesc.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 pm8xxx_vreg_masked_write_forced(struct pm8xxx_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->rdesc.name, addr, rc); + } else { + *reg_save = reg; + vreg->write_count++; + if (pm8xxx_vreg_debug_mask & PM8XXX_VREG_DEBUG_WRITES) + pr_info("%s: write(0x%03X)=0x%02X\n", vreg->rdesc.name, + addr, reg); + } + + return rc; +} + +static int pm8xxx_vreg_is_pin_controlled(struct pm8xxx_vreg *vreg) +{ + int ret = 0; + + switch (vreg->type) { + case PM8XXX_REGULATOR_TYPE_PLDO: + case PM8XXX_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 PM8XXX_REGULATOR_TYPE_SMPS: + ret = vreg->sleep_ctrl_reg + & (SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK); + break; + case PM8XXX_REGULATOR_TYPE_VS: + ret = vreg->ctrl_reg & VS_PIN_CTRL_MASK; + break; + default: + 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 pm8xxx_vreg_pin_control_is_enabled(struct regulator_dev *rdev) +{ + struct pm8xxx_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 _pm8xxx_vreg_is_enabled(struct pm8xxx_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 PM8XXX_REGULATOR_TYPE_FTSMPS: + if ((vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK) + != FTSMPS_VCTRL_BAND_OFF) + rc = 1; + break; + case PM8XXX_REGULATOR_TYPE_VS300: + if ((vreg->ctrl_reg & VS300_CTRL_ENABLE_MASK) + != VS300_CTRL_DISABLE) + rc = 1; + break; + case PM8XXX_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 pm8xxx_vreg_is_enabled(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int enabled; + + if (vreg->type == PM8XXX_REGULATOR_TYPE_PLDO + || vreg->type == PM8XXX_REGULATOR_TYPE_NLDO + || vreg->type == PM8XXX_REGULATOR_TYPE_SMPS + || vreg->type == PM8XXX_REGULATOR_TYPE_VS) { + /* Pin controllable */ + mutex_lock(&vreg->pc_lock); + enabled = vreg->is_enabled; + mutex_unlock(&vreg->pc_lock); + } else { + /* Not pin controlable */ + enabled = _pm8xxx_vreg_is_enabled(vreg); + } + + return enabled; +} + +static int pm8xxx_pldo_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_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 pm8xxx_pldo_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_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 = pm8xxx_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 = pm8xxx_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 = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8xxx_nldo_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_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 pm8xxx_nldo_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_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 = pm8xxx_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 = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int _pm8xxx_nldo1200_get_voltage(struct pm8xxx_vreg *vreg) +{ + int uV = 0; + int vprog; + + if (!NLDO1200_IN_ADVANCED_MODE(vreg)) { + pr_warn("%s: currently in legacy mode; voltage unknown.\n", + vreg->rdesc.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 pm8xxx_nldo1200_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + + return _pm8xxx_nldo1200_get_voltage(vreg); +} + +static int pm8xxx_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 _pm8xxx_nldo1200_set_voltage(struct pm8xxx_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 = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8xxx_nldo1200_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = _pm8xxx_nldo1200_set_voltage(vreg, min_uV, max_uV); + + if (!rc) + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8xxx_smps_get_voltage_advanced(struct pm8xxx_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 pm8xxx_smps_get_voltage_legacy(struct pm8xxx_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 _pm8xxx_smps_get_voltage(struct pm8xxx_vreg *vreg) +{ + if (SMPS_IN_ADVANCED_MODE(vreg)) + return pm8xxx_smps_get_voltage_advanced(vreg); + + return pm8xxx_smps_get_voltage_legacy(vreg); +} + +static int pm8xxx_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 pm8xxx_smps_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int uV; + + mutex_lock(&vreg->pc_lock); + uV = _pm8xxx_smps_get_voltage(vreg); + mutex_unlock(&vreg->pc_lock); + + return uV; +} + +static int pm8xxx_smps_set_voltage_advanced(struct pm8xxx_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 (!_pm8xxx_vreg_is_enabled(vreg) && !force_on) + band = SMPS_ADVANCED_BAND_OFF; + + /* Set advanced mode bit to 1. */ + rc = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8xxx_smps_set_voltage_legacy(struct pm8xxx_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 = pm8xxx_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 = pm8xxx_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 = (_pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8xxx_smps_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&vreg->pc_lock); + + if (SMPS_IN_ADVANCED_MODE(vreg) || !pm8xxx_vreg_is_pin_controlled(vreg)) + rc = pm8xxx_smps_set_voltage_advanced(vreg, min_uV, max_uV, 0); + else + rc = pm8xxx_smps_set_voltage_legacy(vreg, min_uV, max_uV); + + mutex_unlock(&vreg->pc_lock); + + if (!rc) + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int _pm8xxx_ftsmps_get_voltage(struct pm8xxx_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 pm8xxx_ftsmps_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + + return _pm8xxx_ftsmps_get_voltage(vreg); +} + +static int pm8xxx_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 _pm8xxx_ftsmps_set_voltage(struct pm8xxx_vreg *vreg, int min_uV, + int max_uV, int force_on) +{ + int rc = 0; + 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 (_pm8xxx_vreg_is_enabled(vreg) || force_on) { + rc = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8xxx_ftsmps_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = _pm8xxx_ftsmps_set_voltage(vreg, min_uV, max_uV, 0); + + if (!rc) + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8xxx_ncp_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_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 pm8xxx_ncp_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_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 = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, val, + NCP_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8xxx_boost_get_voltage(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + u8 vprog; + + vprog = vreg->ctrl_reg & BOOST_VPROG_MASK; + + return BOOST_UV_STEP * vprog + BOOST_UV_MIN; +} + +static int pm8xxx_boost_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector >= BOOST_SET_POINTS) + return 0; + + return selector * BOOST_UV_STEP + BOOST_UV_MIN; +} + +static int pm8xxx_boost_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + int uV = min_uV; + u8 val; + + if (uV < BOOST_UV_MIN && max_uV >= BOOST_UV_MIN) + uV = BOOST_UV_MIN; + + if (uV < BOOST_UV_MIN || uV > BOOST_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, BOOST_UV_MIN, BOOST_UV_MAX); + return -EINVAL; + } + + val = (uV - BOOST_UV_MIN + BOOST_UV_STEP - 1) / BOOST_UV_STEP; + uV = val * BOOST_UV_STEP + BOOST_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 = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, val, + BOOST_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static unsigned int pm8xxx_ldo_get_mode(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm8xxx_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 == PM8XXX_VREG_PIN_FN_ENABLE)) { + /* HPM */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_HPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + } else { + /* LPM */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8xxx_nldo1200_get_mode(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_nldo1200_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct pm8xxx_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 = _pm8xxx_nldo1200_set_voltage(vreg, vreg->save_uV, + vreg->save_uV); + if (rc) + goto bail; + } + + if (mode == REGULATOR_MODE_NORMAL) { + /* HPM */ + rc = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8xxx_smps_get_mode(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_smps_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm8xxx_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 == PM8XXX_VREG_PIN_FN_ENABLE)) { + /* HPM */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + SMPS_CLK_CTRL_PWM, SMPS_CLK_CTRL_MASK, + &vreg->clk_ctrl_reg); + } else { + /* LPM */ + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8xxx_ftsmps_get_mode(struct regulator_dev *rdev) +{ + struct pm8xxx_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 pm8xxx_ftsmps_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + if (mode == REGULATOR_MODE_NORMAL) { + /* HPM */ + rc = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8xxx_vreg_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct pm8xxx_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 pm8xxx_ldo_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_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 == PM8XXX_VREG_PIN_FN_ENABLE)) + val = LDO_CTRL_PM_HPM; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_ldo_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_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 != PM8XXX_VREG_PIN_FN_MODE) { + rc = pm8xxx_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 == PM8XXX_VREG_PIN_FN_MODE) { + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_nldo1200_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, NLDO1200_ENABLE, + NLDO1200_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_nldo1200_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, NLDO1200_DISABLE, + NLDO1200_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_smps_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + int val; + + mutex_lock(&vreg->pc_lock); + + if (SMPS_IN_ADVANCED_MODE(vreg) + || !pm8xxx_vreg_is_pin_controlled(vreg)) { + /* Enable in advanced mode if not using pin control. */ + rc = pm8xxx_smps_set_voltage_advanced(vreg, vreg->save_uV, + vreg->save_uV, 1); + } else { + rc = pm8xxx_smps_set_voltage_legacy(vreg, vreg->save_uV, + vreg->save_uV); + if (rc) + goto bail; + + rc = pm8xxx_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 == PM8XXX_VREG_PIN_FN_ENABLE)) + val = SMPS_CLK_CTRL_PWM; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_smps_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_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 = pm8xxx_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 != PM8XXX_VREG_PIN_FN_MODE) { + rc = pm8xxx_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 == PM8XXX_VREG_PIN_FN_MODE) + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_ftsmps_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = _pm8xxx_ftsmps_set_voltage(vreg, vreg->save_uV, vreg->save_uV, 1); + + if (rc) + vreg_err(vreg, "set voltage failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_ftsmps_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_vs_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + if (vreg->pdata.ocp_enable) { + /* Disable OCP. */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr, + VS_OCP_DISABLE, VS_OCP_MASK, &vreg->test_reg[0]); + if (rc) + goto done; + + /* Enable the switch while OCP is disabled. */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + VS_ENABLE | VS_MODE_NORMAL, + VS_ENABLE_MASK | VS_MODE_MASK, + &vreg->ctrl_reg); + if (rc) + goto done; + + /* Wait for inrush current to subside, then enable OCP. */ + udelay(vreg->pdata.ocp_enable_time); + rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr, + VS_OCP_ENABLE, VS_OCP_MASK, &vreg->test_reg[0]); + } else { + /* Enable the switch without touching OCP. */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, VS_ENABLE, + VS_ENABLE_MASK, &vreg->ctrl_reg); + } + +done: + if (!rc) + vreg->is_enabled = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_vs_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_vs300_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + if (vreg->pdata.ocp_enable) { + /* Disable OCP. */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr, + VS_OCP_DISABLE, VS_OCP_MASK, &vreg->test_reg[0]); + if (rc) + goto done; + + /* Enable the switch while OCP is disabled. */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + VS300_CTRL_ENABLE | VS300_MODE_NORMAL, + VS300_CTRL_ENABLE_MASK | VS300_MODE_MASK, + &vreg->ctrl_reg); + if (rc) + goto done; + + /* Wait for inrush current to subside, then enable OCP. */ + udelay(vreg->pdata.ocp_enable_time); + rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr, + VS_OCP_ENABLE, VS_OCP_MASK, &vreg->test_reg[0]); + } else { + /* Enable the regulator without touching OCP. */ + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + VS300_CTRL_ENABLE, VS300_CTRL_ENABLE_MASK, + &vreg->ctrl_reg); + } + +done: + if (rc) { + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + } else { + vreg->is_enabled = true; + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + } + + return rc; +} + +static int pm8xxx_vs300_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, VS300_CTRL_DISABLE, + VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_ncp_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, NCP_ENABLE, + NCP_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_ncp_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, NCP_DISABLE, + NCP_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_boost_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, BOOST_ENABLE, + BOOST_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8xxx_boost_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, BOOST_DISABLE, + BOOST_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8xxx_ldo_pin_control_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_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 & PM8XXX_VREG_PIN_CTRL_EN0) + val |= LDO_TEST_PIN_CTRL_EN0; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN1) + val |= LDO_TEST_PIN_CTRL_EN1; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN2) + val |= LDO_TEST_PIN_CTRL_EN2; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN3) + val |= LDO_TEST_PIN_CTRL_EN3; + + bank = (vreg->pdata.pin_fn == PM8XXX_VREG_PIN_FN_ENABLE ? 5 : 6); + rc = pm8xxx_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 = pm8xxx_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 = pm8xxx_vreg_masked_write(vreg, vreg->test_addr, val, mask, + &vreg->test_reg[0]); + if (rc) + goto bail; + + if (vreg->pdata.pin_fn == PM8XXX_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 = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8xxx_ldo_pin_control_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8xxx_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 = pm8xxx_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 = pm8xxx_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 = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8xxx_smps_pin_control_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + u8 val = 0; + + mutex_lock(&vreg->pc_lock); + + if (vreg->pdata.pin_fn == PM8XXX_VREG_PIN_FN_ENABLE) { + /* Pin control ON/OFF */ + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN0) + val |= SMPS_PIN_CTRL_EN0; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN1) + val |= SMPS_PIN_CTRL_EN1; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN2) + val |= SMPS_PIN_CTRL_EN2; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN3) + val |= SMPS_PIN_CTRL_EN3; + } else { + /* Pin control LPM/HPM */ + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN0) + val |= SMPS_PIN_CTRL_LPM_EN0; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN1) + val |= SMPS_PIN_CTRL_LPM_EN1; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN2) + val |= SMPS_PIN_CTRL_LPM_EN2; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN3) + val |= SMPS_PIN_CTRL_LPM_EN3; + } + + rc = pm8xxx_smps_set_voltage_legacy(vreg, vreg->save_uV, vreg->save_uV); + if (rc) + goto bail; + + rc = pm8xxx_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 = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + ((vreg->pdata.pin_fn == PM8XXX_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 = pm8xxx_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + (vreg->pdata.pin_fn == PM8XXX_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8xxx_smps_pin_control_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8xxx_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 = pm8xxx_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 = pm8xxx_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 = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8xxx_vs_pin_control_enable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + u8 val = 0; + + mutex_lock(&vreg->pc_lock); + + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN0) + val |= VS_PIN_CTRL_EN0; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN1) + val |= VS_PIN_CTRL_EN1; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN2) + val |= VS_PIN_CTRL_EN2; + if (vreg->pdata.pin_ctrl & PM8XXX_VREG_PIN_CTRL_EN3) + val |= VS_PIN_CTRL_EN3; + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8xxx_vs_pin_control_disable(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + else + pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8xxx_enable_time(struct regulator_dev *rdev) +{ + struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->pdata.enable_time; +} + +static const char const *pm8xxx_print_actions[] = { + [PM8XXX_REGULATOR_ACTION_INIT] = "initial ", + [PM8XXX_REGULATOR_ACTION_ENABLE] = "enable ", + [PM8XXX_REGULATOR_ACTION_DISABLE] = "disable ", + [PM8XXX_REGULATOR_ACTION_VOLTAGE] = "set voltage", + [PM8XXX_REGULATOR_ACTION_MODE] = "set mode ", + [PM8XXX_REGULATOR_ACTION_PIN_CTRL] = "pin control", +}; + +static void pm8xxx_vreg_show_state(struct regulator_dev *rdev, + enum pm8xxx_regulator_action action) +{ + struct pm8xxx_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 = pm8xxx_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 (!((pm8xxx_vreg_debug_mask & PM8XXX_VREG_DEBUG_DUPLICATE) + || ((pm8xxx_vreg_debug_mask & PM8XXX_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 & PM8XXX_VREG_PIN_CTRL_EN0) + pc_en0 = " EN0"; + if (pc & PM8XXX_VREG_PIN_CTRL_EN1) + pc_en1 = " EN1"; + if (pc & PM8XXX_VREG_PIN_CTRL_EN2) + pc_en2 = " EN2"; + if (pc & PM8XXX_VREG_PIN_CTRL_EN3) + pc_en3 = " EN3"; + if (pc == PM8XXX_VREG_PIN_CTRL_NONE) + pc_total = " none"; + } else { + pc_total = " none"; + } + + mutex_unlock(&vreg->pc_lock); + + enable_label = pm8xxx_vreg_is_enabled(rdev) ? "on " : "off"; + + switch (vreg->type) { + case PM8XXX_REGULATOR_TYPE_PLDO: + uV = pm8xxx_pldo_get_voltage(rdev); + mode = pm8xxx_ldo_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s, pc=%s%s%s%s%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"), + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case PM8XXX_REGULATOR_TYPE_NLDO: + uV = pm8xxx_nldo_get_voltage(rdev); + mode = pm8xxx_ldo_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s, pc=%s%s%s%s%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"), + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case PM8XXX_REGULATOR_TYPE_NLDO1200: + uV = pm8xxx_nldo1200_get_voltage(rdev); + mode = pm8xxx_nldo1200_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM")); + break; + case PM8XXX_REGULATOR_TYPE_SMPS: + uV = pm8xxx_smps_get_voltage(rdev); + mode = pm8xxx_smps_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s, pc=%s%s%s%s%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"), + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case PM8XXX_REGULATOR_TYPE_FTSMPS: + uV = pm8xxx_ftsmps_get_voltage(rdev); + mode = pm8xxx_ftsmps_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM")); + break; + case PM8XXX_REGULATOR_TYPE_VS: + pr_info("%s %-9s: %s, pc=%s%s%s%s%s\n", + action_label, vreg->rdesc.name, enable_label, + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case PM8XXX_REGULATOR_TYPE_VS300: + pr_info("%s %-9s: %s\n", + action_label, vreg->rdesc.name, enable_label); + break; + case PM8XXX_REGULATOR_TYPE_NCP: + uV = pm8xxx_ncp_get_voltage(rdev); + pr_info("%s %-9s: %s, v=%7d uV\n", + action_label, vreg->rdesc.name, enable_label, uV); + break; + case PM8XXX_REGULATOR_TYPE_BOOST: + uV = pm8xxx_boost_get_voltage(rdev); + pr_info("%s %-9s: %s, v=%7d uV\n", + action_label, vreg->rdesc.name, enable_label, uV); + break; + default: + break; + } +} + +/* Real regulator operations. */ +static struct regulator_ops pm8xxx_pldo_ops = { + .enable = pm8xxx_ldo_enable, + .disable = pm8xxx_ldo_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_pldo_set_voltage, + .get_voltage = pm8xxx_pldo_get_voltage, + .list_voltage = pm8xxx_pldo_list_voltage, + .set_mode = pm8xxx_ldo_set_mode, + .get_mode = pm8xxx_ldo_get_mode, + .get_optimum_mode = pm8xxx_vreg_get_optimum_mode, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_nldo_ops = { + .enable = pm8xxx_ldo_enable, + .disable = pm8xxx_ldo_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_nldo_set_voltage, + .get_voltage = pm8xxx_nldo_get_voltage, + .list_voltage = pm8xxx_nldo_list_voltage, + .set_mode = pm8xxx_ldo_set_mode, + .get_mode = pm8xxx_ldo_get_mode, + .get_optimum_mode = pm8xxx_vreg_get_optimum_mode, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_nldo1200_ops = { + .enable = pm8xxx_nldo1200_enable, + .disable = pm8xxx_nldo1200_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_nldo1200_set_voltage, + .get_voltage = pm8xxx_nldo1200_get_voltage, + .list_voltage = pm8xxx_nldo1200_list_voltage, + .set_mode = pm8xxx_nldo1200_set_mode, + .get_mode = pm8xxx_nldo1200_get_mode, + .get_optimum_mode = pm8xxx_vreg_get_optimum_mode, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_smps_ops = { + .enable = pm8xxx_smps_enable, + .disable = pm8xxx_smps_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_smps_set_voltage, + .get_voltage = pm8xxx_smps_get_voltage, + .list_voltage = pm8xxx_smps_list_voltage, + .set_mode = pm8xxx_smps_set_mode, + .get_mode = pm8xxx_smps_get_mode, + .get_optimum_mode = pm8xxx_vreg_get_optimum_mode, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_ftsmps_ops = { + .enable = pm8xxx_ftsmps_enable, + .disable = pm8xxx_ftsmps_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_ftsmps_set_voltage, + .get_voltage = pm8xxx_ftsmps_get_voltage, + .list_voltage = pm8xxx_ftsmps_list_voltage, + .set_mode = pm8xxx_ftsmps_set_mode, + .get_mode = pm8xxx_ftsmps_get_mode, + .get_optimum_mode = pm8xxx_vreg_get_optimum_mode, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_vs_ops = { + .enable = pm8xxx_vs_enable, + .disable = pm8xxx_vs_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_vs300_ops = { + .enable = pm8xxx_vs300_enable, + .disable = pm8xxx_vs300_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_ncp_ops = { + .enable = pm8xxx_ncp_enable, + .disable = pm8xxx_ncp_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_ncp_set_voltage, + .get_voltage = pm8xxx_ncp_get_voltage, + .list_voltage = pm8xxx_ncp_list_voltage, + .enable_time = pm8xxx_enable_time, +}; + +static struct regulator_ops pm8xxx_boost_ops = { + .enable = pm8xxx_boost_enable, + .disable = pm8xxx_boost_disable, + .is_enabled = pm8xxx_vreg_is_enabled, + .set_voltage = pm8xxx_boost_set_voltage, + .get_voltage = pm8xxx_boost_get_voltage, + .list_voltage = pm8xxx_boost_list_voltage, + .enable_time = pm8xxx_enable_time, +}; + +/* Pin control regulator operations. */ +static struct regulator_ops pm8xxx_ldo_pc_ops = { + .enable = pm8xxx_ldo_pin_control_enable, + .disable = pm8xxx_ldo_pin_control_disable, + .is_enabled = pm8xxx_vreg_pin_control_is_enabled, +}; + +static struct regulator_ops pm8xxx_smps_pc_ops = { + .enable = pm8xxx_smps_pin_control_enable, + .disable = pm8xxx_smps_pin_control_disable, + .is_enabled = pm8xxx_vreg_pin_control_is_enabled, +}; + +static struct regulator_ops pm8xxx_vs_pc_ops = { + .enable = pm8xxx_vs_pin_control_enable, + .disable = pm8xxx_vs_pin_control_disable, + .is_enabled = pm8xxx_vreg_pin_control_is_enabled, +}; + +static struct regulator_ops *pm8xxx_reg_ops[PM8XXX_REGULATOR_TYPE_MAX] = { + [PM8XXX_REGULATOR_TYPE_PLDO] = &pm8xxx_pldo_ops, + [PM8XXX_REGULATOR_TYPE_NLDO] = &pm8xxx_nldo_ops, + [PM8XXX_REGULATOR_TYPE_NLDO1200] = &pm8xxx_nldo1200_ops, + [PM8XXX_REGULATOR_TYPE_SMPS] = &pm8xxx_smps_ops, + [PM8XXX_REGULATOR_TYPE_FTSMPS] = &pm8xxx_ftsmps_ops, + [PM8XXX_REGULATOR_TYPE_VS] = &pm8xxx_vs_ops, + [PM8XXX_REGULATOR_TYPE_VS300] = &pm8xxx_vs300_ops, + [PM8XXX_REGULATOR_TYPE_NCP] = &pm8xxx_ncp_ops, + [PM8XXX_REGULATOR_TYPE_BOOST] = &pm8xxx_boost_ops, +}; + +static struct regulator_ops *pm8xxx_reg_pc_ops[PM8XXX_REGULATOR_TYPE_MAX] = { + [PM8XXX_REGULATOR_TYPE_PLDO] = &pm8xxx_ldo_pc_ops, + [PM8XXX_REGULATOR_TYPE_NLDO] = &pm8xxx_ldo_pc_ops, + [PM8XXX_REGULATOR_TYPE_SMPS] = &pm8xxx_smps_pc_ops, + [PM8XXX_REGULATOR_TYPE_VS] = &pm8xxx_vs_pc_ops, +}; + +static unsigned pm8xxx_n_voltages[PM8XXX_REGULATOR_TYPE_MAX] = { + [PM8XXX_REGULATOR_TYPE_PLDO] = PLDO_SET_POINTS, + [PM8XXX_REGULATOR_TYPE_NLDO] = NLDO_SET_POINTS, + [PM8XXX_REGULATOR_TYPE_NLDO1200] = NLDO1200_SET_POINTS, + [PM8XXX_REGULATOR_TYPE_SMPS] = SMPS_ADVANCED_SET_POINTS, + [PM8XXX_REGULATOR_TYPE_FTSMPS] = FTSMPS_SET_POINTS, + [PM8XXX_REGULATOR_TYPE_VS] = 0, + [PM8XXX_REGULATOR_TYPE_VS300] = 0, + [PM8XXX_REGULATOR_TYPE_NCP] = NCP_SET_POINTS, + [PM8XXX_REGULATOR_TYPE_BOOST] = BOOST_SET_POINTS, +}; + +static int pm8xxx_init_ldo(struct pm8xxx_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 = pm8xxx_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 = !!_pm8xxx_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 pm8xxx_init_nldo1200(struct pm8xxx_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 = _pm8xxx_nldo1200_get_voltage(vreg); + + /* Set pull down enable based on platform data. */ + rc = pm8xxx_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 pm8xxx_init_smps(struct pm8xxx_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 = _pm8xxx_smps_get_voltage(vreg); + + if (is_real) { + /* Set advanced mode pull down enable based on platform data. */ + rc = pm8xxx_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 = !!_pm8xxx_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 = pm8xxx_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 pm8xxx_init_ftsmps(struct pm8xxx_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 = _pm8xxx_ftsmps_get_voltage(vreg); + + /* Set pull down enable based on platform data. */ + rc = pm8xxx_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 pm8xxx_init_vs(struct pm8xxx_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; + } + + /* Save the current test register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[0]); + 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 = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, + (vreg->pdata.pull_down_enable ? VS_PULL_DOWN_ENABLE + : VS_PULL_DOWN_DISABLE), + VS_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, + "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + + vreg->is_enabled = !!_pm8xxx_vreg_is_enabled(vreg); + } + + return rc; +} + +static int pm8xxx_init_vs300(struct pm8xxx_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; + } + + /* Save the current test register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[0]); + if (rc) { + vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc); + return rc; + } + + /* Set pull down enable based on platform data. */ + rc = pm8xxx_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, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8xxx_init_ncp(struct pm8xxx_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; +} + +static int pm8xxx_init_boost(struct pm8xxx_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; +} + +static int __devinit pm8xxx_vreg_probe(struct platform_device *pdev) +{ + struct pm8xxx_regulator_core_platform_data *core_data; + const struct pm8xxx_regulator_platform_data *pdata; + enum pm8xxx_vreg_pin_function pin_fn; + struct regulator_desc *rdesc; + struct pm8xxx_vreg *vreg; + unsigned pin_ctrl; + int rc = 0; + + if (pdev == NULL) { + pr_err("no platform device specified\n"); + return -EINVAL; + } + + core_data = pdev->dev.platform_data; + if (core_data == NULL) { + pr_err("no core data specified\n"); + return -EINVAL; + } + + pdata = core_data->pdata; + vreg = core_data->vreg; + if (pdata == NULL) { + pr_err("no pdata specified\n"); + return -EINVAL; + } else if (vreg == NULL) { + pr_err("no vreg specified\n"); + return -EINVAL; + } + + if (vreg->rdesc.name == NULL) { + pr_err("regulator name missing\n"); + return -EINVAL; + } else if (vreg->type < 0 || vreg->type >= PM8XXX_REGULATOR_TYPE_MAX) { + pr_err("%s: regulator type=%d is invalid\n", vreg->rdesc.name, + vreg->type); + return -EINVAL; + } else if (core_data->is_pin_controlled + && pm8xxx_reg_pc_ops[vreg->type] == NULL) { + pr_err("%s: regulator type=%d does not support pin control\n", + vreg->rdesc.name, vreg->type); + return -EINVAL; + } else if (core_data->is_pin_controlled + && vreg->rdesc_pc.name == NULL) { + pr_err("%s: regulator pin control name missing\n", + vreg->rdesc.name); + return -EINVAL; + } + + if (core_data->is_pin_controlled) + rdesc = &vreg->rdesc_pc; + else + rdesc = &vreg->rdesc; + if (!pdata) { + pr_err("%s requires platform data\n", vreg->rdesc.name); + return -EINVAL; + } + + rdesc->id = pdev->id; + rdesc->owner = THIS_MODULE; + rdesc->type = REGULATOR_VOLTAGE; + if (core_data->is_pin_controlled) { + rdesc->ops = pm8xxx_reg_pc_ops[vreg->type]; + rdesc->n_voltages = 0; + } else { + rdesc->ops = pm8xxx_reg_ops[vreg->type]; + rdesc->n_voltages = pm8xxx_n_voltages[vreg->type]; + } + + mutex_lock(&vreg->pc_lock); + + if (!core_data->is_pin_controlled) { + /* 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 pm8xxx_regulator_platform_data)); + vreg->pdata.pin_ctrl = pin_ctrl; + vreg->pdata.pin_fn = pin_fn; + vreg->dev = &pdev->dev; + } else { + /* Pin control regulator */ + if ((pdata->pin_ctrl & PM8XXX_VREG_PIN_CTRL_ALL) + == PM8XXX_VREG_PIN_CTRL_NONE) { + pr_err("%s: no pin control input specified\n", + vreg->rdesc.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; + } + + /* Initialize register values. */ + switch (vreg->type) { + case PM8XXX_REGULATOR_TYPE_PLDO: + case PM8XXX_REGULATOR_TYPE_NLDO: + rc = pm8xxx_init_ldo(vreg, !core_data->is_pin_controlled); + break; + case PM8XXX_REGULATOR_TYPE_NLDO1200: + rc = pm8xxx_init_nldo1200(vreg); + break; + case PM8XXX_REGULATOR_TYPE_SMPS: + rc = pm8xxx_init_smps(vreg, !core_data->is_pin_controlled); + break; + case PM8XXX_REGULATOR_TYPE_FTSMPS: + rc = pm8xxx_init_ftsmps(vreg); + break; + case PM8XXX_REGULATOR_TYPE_VS: + rc = pm8xxx_init_vs(vreg, !core_data->is_pin_controlled); + break; + case PM8XXX_REGULATOR_TYPE_VS300: + rc = pm8xxx_init_vs300(vreg); + break; + case PM8XXX_REGULATOR_TYPE_NCP: + rc = pm8xxx_init_ncp(vreg); + break; + case PM8XXX_REGULATOR_TYPE_BOOST: + rc = pm8xxx_init_boost(vreg); + break; + default: + break; + } + + mutex_unlock(&vreg->pc_lock); + + if (rc) + goto bail; + + if (!core_data->is_pin_controlled) { + vreg->rdev = regulator_register(rdesc, &pdev->dev, + &(pdata->init_data), vreg, NULL); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + vreg->rdev = NULL; + pr_err("regulator_register failed: %s, rc=%d\n", + vreg->rdesc.name, rc); + } + } else { + vreg->rdev_pc = regulator_register(rdesc, &pdev->dev, + &(pdata->init_data), vreg, NULL); + 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", + vreg->rdesc.name, rc); + } + } + if ((pm8xxx_vreg_debug_mask & PM8XXX_VREG_DEBUG_INIT) && !rc + && vreg->rdev) + pm8xxx_vreg_show_state(vreg->rdev, + PM8XXX_REGULATOR_ACTION_INIT); + + platform_set_drvdata(pdev, core_data); + +bail: + if (rc) + pr_err("error for %s, rc=%d\n", vreg->rdesc.name, rc); + + return rc; +} + +static int __devexit pm8xxx_vreg_remove(struct platform_device *pdev) +{ + struct pm8xxx_regulator_core_platform_data *core_data; + + core_data = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (core_data) { + if (core_data->is_pin_controlled) + regulator_unregister(core_data->vreg->rdev_pc); + else + regulator_unregister(core_data->vreg->rdev); + } + + return 0; +} + +static struct platform_driver pm8xxx_vreg_driver = { + .probe = pm8xxx_vreg_probe, + .remove = __devexit_p(pm8xxx_vreg_remove), + .driver = { + .name = PM8XXX_REGULATOR_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_vreg_init(void) +{ + return platform_driver_register(&pm8xxx_vreg_driver); +} +postcore_initcall(pm8xxx_vreg_init); + +static void __exit pm8xxx_vreg_exit(void) +{ + platform_driver_unregister(&pm8xxx_vreg_driver); +} +module_exit(pm8xxx_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC PM8XXX regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_REGULATOR_DEV_NAME); diff --git a/drivers/regulator/pmic8058-regulator.c b/drivers/regulator/pmic8058-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..f9b03194c3e7d63e4e94cf1d6d5ffd07722279a7 --- /dev/null +++ b/drivers/regulator/pmic8058-regulator.c @@ -0,0 +1,1756 @@ +/* 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 + +/* 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 device *dev; + 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, int uV, + int force_on); +static int pm8058_smps_set_voltage_legacy(struct pm8058_vreg *vreg, 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_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_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, int on) +{ + int rc = 0, i; + + for (i = 0; + (i < GLOBAL_ENABLE_MAX) && !rc && vreg->global_enable[i]; i++) + rc = pm8058_vreg_write(vreg, 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, 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(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; + + 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(vreg, 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(vreg, 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, 0); + if (rc) + goto bail; + } else { + /* Pin control LPM/HPM */ + rc = pm8058_vreg_write(vreg, 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(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 = pm8058_vreg_write(vreg, 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, 0); + if (rc) + goto bail; + + rc = pm8058_smps_set_voltage_legacy(vreg, + vreg->save_uV); + if (rc) + goto bail; + + rc = pm8058_vreg_write(vreg, 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(vreg, 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(vreg, 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(vreg, vreg->ctrl_addr, + val, SMPS_LEGACY_ENABLE, + &vreg->ctrl_reg); + if (rc) + goto bail; + } + + rc = pm8058_vreg_write(vreg, 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, + 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(vreg, 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, 0); + if (rc) + goto bail; + } else { + /* Pin control off */ + if (_pm8058_vreg_is_enabled(vreg)) + val = LVS_ENABLE; + + rc = pm8058_vreg_write(vreg, 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); + 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, 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, vreg->save_uV, 1); + else + rc = pm8058_vreg_write(vreg, 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); + int rc = 0; + + /* Disable in global control register. */ + rc = pm8058_vreg_set_global_enable(vreg, 0); + if (rc) + goto bail; + + /* Turn off pin control. */ + rc = pm8058_vreg_set_pin_ctrl(vreg, 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(vreg, vreg->ctrl_addr, + SMPS_ADVANCED_BAND_OFF, SMPS_ADVANCED_BAND_MASK, + &vreg->ctrl_reg); + else + rc = pm8058_vreg_write(vreg, 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_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(vreg, 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(vreg, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Write range extension. */ + rc = pm8058_vreg_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; + + /* Write fine step, range select and program voltage update. */ + rc = pm8058_vreg_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]); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_nldo_set_voltage(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(vreg, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Write fine step. */ + rc = pm8058_vreg_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]); +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); + + if (vreg->is_nmos) + return pm8058_nldo_set_voltage(vreg, min_uV); + else + return pm8058_pldo_set_voltage(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, + 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(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 = pm8058_vreg_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 = new_uV; + +bail: + return rc; +} + +static int pm8058_smps_set_voltage_legacy(struct pm8058_vreg *vreg, 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(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 = pm8058_vreg_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 = (_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(vreg, 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); + 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, min_uV, 0); + else + rc = pm8058_smps_set_voltage_legacy(vreg, 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); + 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(vreg, 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, 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(vreg, 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, 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(vreg, 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(vreg, 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, 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, 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, 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(vreg, 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, 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(vreg, 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, 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, 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, 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, 1); + } else { + /* Turn off pin control. */ + rc = pm8058_vreg_set_pin_ctrl(vreg, 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); + 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, new_mode); + break; + case REGULATOR_TYPE_SMPS: + rc = pm8058_smps_set_mode(vreg, new_mode); + break; + case REGULATOR_TYPE_LVS: + rc = pm8058_lvs_set_mode(vreg, 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_vreg *vreg) +{ + int rc = 0, i; + + for (i = 0; i < MASTER_ENABLE_COUNT; i++) { + rc = pm8xxx_readb(vreg->dev->parent, m_en[i].addr, + &(m_en[i].reg)); + if (rc) + goto bail; + } + +bail: + if (rc) + pr_err("%s: pm8xxx_read failed, rc=%d\n", __func__, rc); + + return rc; +} + +static int pm8058_init_ldo(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 = 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 ((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(vreg, 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_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 = 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 = 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(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; + + if (!SMPS_IN_ADVANCED_MODE(vreg)) { + /* Set legacy mode pull down enable based on platform data. */ + rc = pm8058_vreg_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: + return rc; +} + +static int pm8058_init_lvs(struct pm8058_vreg *vreg) +{ + int rc = 0; + + vreg->optimum = REGULATOR_MODE_FAST; + + /* Set pull down enable based on platform data. */ + rc = pm8058_vreg_write(vreg, 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_vreg *vreg) +{ + int rc = 0; + + /* Save the current test1 register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[0]); + if (rc) + goto bail; + + vreg->optimum = REGULATOR_MODE_FAST; + +bail: + return rc; +} + +static int pm8058_init_regulator(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(vreg); + if (!rc) + master_enable_inited = 1; + } + + /* save the current control register state */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) + goto bail; + + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + rc = pm8058_init_ldo(vreg); + break; + case REGULATOR_TYPE_SMPS: + rc = pm8058_init_smps(vreg); + break; + case REGULATOR_TYPE_LVS: + rc = pm8058_init_lvs(vreg); + break; + case REGULATOR_TYPE_NCP: + rc = pm8058_init_ncp(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_vreg *vreg; + const char *reg_name = NULL; + int rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8058_VREG_MAX) { + 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; + vreg->dev = &pdev->dev; + + rc = pm8058_init_regulator(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, NULL); + 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 0000000000000000000000000000000000000000..02c95497cef9392d44c8d681abc98c14bc097c3f --- /dev/null +++ b/drivers/regulator/pmic8901-regulator.c @@ -0,0 +1,1018 @@ +/* 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_VS 2 + +/* 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 device *dev; + struct pm8901_vreg_pdata *pdata; + struct regulator_dev *rdev; + 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 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) == PM8XXX_REVISION_8901_1p0 || \ + (rev) == PM8XXX_REVISION_8901_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, \ + } + +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 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_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) + *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); + 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(vreg, 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); + int rc; + + rc = pm8901_vreg_write(vreg, 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); + int rc; + + /* Disassert local enable bit in CTRL register. */ + rc = pm8901_vreg_write(vreg, 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_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(vreg, 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(vreg, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Write range extension. */ + rc = pm8901_vreg_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; + + /* Write fine step, range select and program voltage update. */ + rc = pm8901_vreg_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]); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_nldo_set_voltage(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(vreg, 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(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) + 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); + + if (vreg->is_nmos) + return pm8901_nldo_set_voltage(vreg, min_uV); + else + return pm8901_pldo_set_voltage(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); + 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(vreg, 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); + int rc; + u8 val, band; + + if (IS_PMIC_8901_V1(pm8xxx_get_revision(vreg->dev->parent))) + 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(vreg, vreg->ctrl_addr, band | val, + SMPS_VCTRL_BAND_MASK | SMPS_VCTRL_VPROG_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8901_vreg_write(vreg, 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); + 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(pm8xxx_get_revision(vreg->dev->parent))) + 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); + 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(vreg, 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); + 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(vreg, 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 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, +}; + +#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_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_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 = 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; + } + + /* Set pull down enable based on platform data. */ + rc = pm8901_vreg_write(vreg, 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_vreg *vreg) +{ + int rc; + + /* 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; + + /* Set pull down enable based on platform data. */ + rc = pm8901_vreg_write(vreg, 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_vreg *vreg) +{ + int rc = 0; + + /* Set pull down enable based on platform data. */ + rc = pm8901_vreg_write(vreg, 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_vreg *vreg) +{ + int rc; + + /* Store current regulator register values. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8xxx_readb(vreg->dev->parent, vreg->pmr_addr, &vreg->pmr_reg); + 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(vreg); + else if (vreg->type == REGULATOR_TYPE_SMPS) + rc = pm8901_init_smps(vreg); + else if (vreg->type == REGULATOR_TYPE_VS) + rc = pm8901_init_vs(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_vreg *vreg; + const char *reg_name = NULL; + int rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8901_VREG_MAX) { + rdesc = &pm8901_vreg_descrip[pdev->id]; + vreg = &pm8901_vreg[pdev->id]; + vreg->pdata = pdev->dev.platform_data; + reg_name = pm8901_vreg_descrip[pdev->id].name; + vreg->dev = &pdev->dev; + + rc = pm8901_init_regulator(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, NULL); + 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/regulator/qpnp-regulator.c b/drivers/regulator/qpnp-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..120d17efa34ba5bf76d27d8c9589557c7d6d1848 --- /dev/null +++ b/drivers/regulator/qpnp-regulator.c @@ -0,0 +1,1472 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* Debug Flag Definitions */ +enum { + QPNP_VREG_DEBUG_REQUEST = BIT(0), /* Show requests */ + QPNP_VREG_DEBUG_DUPLICATE = BIT(1), /* Show duplicate requests */ + QPNP_VREG_DEBUG_INIT = BIT(2), /* Show state after probe */ + QPNP_VREG_DEBUG_WRITES = BIT(3), /* Show SPMI writes */ + QPNP_VREG_DEBUG_READS = BIT(4), /* Show SPMI reads */ +}; + +static int qpnp_vreg_debug_mask; +module_param_named( + debug_mask, qpnp_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +#define vreg_err(vreg, fmt, ...) \ + pr_err("%s: " fmt, vreg->rdesc.name, ##__VA_ARGS__) + +/* These types correspond to unique register layouts. */ +enum qpnp_regulator_logical_type { + QPNP_REGULATOR_LOGICAL_TYPE_SMPS, + QPNP_REGULATOR_LOGICAL_TYPE_LDO, + QPNP_REGULATOR_LOGICAL_TYPE_VS, + QPNP_REGULATOR_LOGICAL_TYPE_BOOST, + QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS, +}; + +enum qpnp_regulator_type { + QPNP_REGULATOR_TYPE_HF_BUCK = 0x03, + QPNP_REGULATOR_TYPE_LDO = 0x04, + QPNP_REGULATOR_TYPE_VS = 0x05, + QPNP_REGULATOR_TYPE_BOOST = 0x1B, + QPNP_REGULATOR_TYPE_FTS = 0x1C, +}; + +enum qpnp_regulator_subtype { + QPNP_REGULATOR_SUBTYPE_GP_CTL = 0x08, + QPNP_REGULATOR_SUBTYPE_RF_CTL = 0x09, + QPNP_REGULATOR_SUBTYPE_N50 = 0x01, + QPNP_REGULATOR_SUBTYPE_N150 = 0x02, + QPNP_REGULATOR_SUBTYPE_N300 = 0x03, + QPNP_REGULATOR_SUBTYPE_N600 = 0x04, + QPNP_REGULATOR_SUBTYPE_N1200 = 0x05, + QPNP_REGULATOR_SUBTYPE_P50 = 0x08, + QPNP_REGULATOR_SUBTYPE_P150 = 0x09, + QPNP_REGULATOR_SUBTYPE_P300 = 0x0A, + QPNP_REGULATOR_SUBTYPE_P600 = 0x0B, + QPNP_REGULATOR_SUBTYPE_P1200 = 0x0C, + QPNP_REGULATOR_SUBTYPE_LV100 = 0x01, + QPNP_REGULATOR_SUBTYPE_LV300 = 0x02, + QPNP_REGULATOR_SUBTYPE_MV300 = 0x08, + QPNP_REGULATOR_SUBTYPE_MV500 = 0x09, + QPNP_REGULATOR_SUBTYPE_HDMI = 0x10, + QPNP_REGULATOR_SUBTYPE_OTG = 0x11, + QPNP_REGULATOR_SUBTYPE_5V_BOOST = 0x01, + QPNP_REGULATOR_SUBTYPE_FTS_CTL = 0x08, +}; + +enum qpnp_common_regulator_registers { + QPNP_COMMON_REG_TYPE = 0x04, + QPNP_COMMON_REG_SUBTYPE = 0x05, + QPNP_COMMON_REG_VOLTAGE_RANGE = 0x40, + QPNP_COMMON_REG_VOLTAGE_SET = 0x41, + QPNP_COMMON_REG_MODE = 0x45, + QPNP_COMMON_REG_ENABLE = 0x46, + QPNP_COMMON_REG_PULL_DOWN = 0x48, +}; + +enum qpnp_ldo_registers { + QPNP_LDO_REG_SOFT_START = 0x4C, +}; + +enum qpnp_vs_registers { + QPNP_VS_REG_OCP = 0x4A, + QPNP_VS_REG_SOFT_START = 0x4C, +}; + +enum qpnp_boost_registers { + QPNP_BOOST_REG_CURRENT_LIMIT = 0x40, +}; + +/* Used for indexing into ctrl_reg. These are offets from 0x40 */ +enum qpnp_common_control_register_index { + QPNP_COMMON_IDX_VOLTAGE_RANGE = 0, + QPNP_COMMON_IDX_VOLTAGE_SET = 1, + QPNP_COMMON_IDX_MODE = 5, + QPNP_COMMON_IDX_ENABLE = 6, +}; + +enum qpnp_boost_control_register_index { + QPNP_BOOST_IDX_CURRENT_LIMIT = 0, +}; + +/* Common regulator control register layout */ +#define QPNP_COMMON_ENABLE_MASK 0x80 +#define QPNP_COMMON_ENABLE 0x80 +#define QPNP_COMMON_DISABLE 0x00 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN3_MASK 0x08 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN2_MASK 0x04 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN1_MASK 0x02 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN0_MASK 0x01 +#define QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK 0x0F + +/* Common regulator mode register layout */ +#define QPNP_COMMON_MODE_HPM_MASK 0x80 +#define QPNP_COMMON_MODE_AUTO_MASK 0x40 +#define QPNP_COMMON_MODE_BYPASS_MASK 0x20 +#define QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK 0x10 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK 0x08 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK 0x04 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK 0x02 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK 0x01 +#define QPNP_COMMON_MODE_FOLLOW_ALL_MASK 0x1F + +/* Common regulator pull down control register layout */ +#define QPNP_COMMON_PULL_DOWN_ENABLE_MASK 0x80 + +/* LDO regulator current limit control register layout */ +#define QPNP_LDO_CURRENT_LIMIT_ENABLE_MASK 0x80 + +/* LDO regulator soft start control register layout */ +#define QPNP_LDO_SOFT_START_ENABLE_MASK 0x80 + +/* VS regulator over current protection control register layout */ +#define QPNP_VS_OCP_ENABLE_MASK 0x80 +#define QPNP_VS_OCP_OVERRIDE_MASK 0x01 +#define QPNP_VS_OCP_DISABLE 0x00 + +/* VS regulator soft start control register layout */ +#define QPNP_VS_SOFT_START_ENABLE_MASK 0x80 +#define QPNP_VS_SOFT_START_SEL_MASK 0x03 + +/* Boost regulator current limit control register layout */ +#define QPNP_BOOST_CURRENT_LIMIT_ENABLE_MASK 0x80 +#define QPNP_BOOST_CURRENT_LIMIT_MASK 0x07 + +/* + * 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 + +struct qpnp_voltage_range { + int min_uV; + int max_uV; + int step_uV; + int set_point_min_uV; + unsigned n_voltages; + u8 range_sel; +}; + +struct qpnp_voltage_set_points { + struct qpnp_voltage_range *range; + int count; + unsigned n_voltages; +}; + +struct qpnp_regulator_mapping { + enum qpnp_regulator_type type; + enum qpnp_regulator_subtype subtype; + enum qpnp_regulator_logical_type logical_type; + struct regulator_ops *ops; + struct qpnp_voltage_set_points *set_points; + int hpm_min_load; +}; + +struct qpnp_regulator { + struct regulator_desc rdesc; + struct spmi_device *spmi_dev; + struct regulator_dev *rdev; + struct qpnp_voltage_set_points *set_points; + enum qpnp_regulator_logical_type logical_type; + int enable_time; + int ocp_enable_time; + int ocp_enable; + int system_load; + int hpm_min_load; + u32 write_count; + u32 prev_write_count; + u16 base_addr; + /* ctrl_reg provides a shadow copy of register values 0x40 to 0x47. */ + u8 ctrl_reg[8]; +}; + +#define QPNP_VREG_MAP(_type, _subtype, _logical_type, _ops_val, \ + _set_points_val, _hpm_min_load) \ + { \ + .type = QPNP_REGULATOR_TYPE_##_type, \ + .subtype = QPNP_REGULATOR_SUBTYPE_##_subtype, \ + .logical_type = QPNP_REGULATOR_LOGICAL_TYPE_##_logical_type, \ + .ops = &qpnp_##_ops_val##_ops, \ + .set_points = &_set_points_val##_set_points, \ + .hpm_min_load = _hpm_min_load, \ + } + +#define VOLTAGE_RANGE(_range_sel, _min_uV, _set_point_min_uV, _max_uV, \ + _step_uV) \ + { \ + .min_uV = _min_uV, \ + .set_point_min_uV = _set_point_min_uV, \ + .max_uV = _max_uV, \ + .step_uV = _step_uV, \ + .range_sel = _range_sel, \ + } + +#define SET_POINTS(_ranges) \ +{ \ + .range = _ranges, \ + .count = ARRAY_SIZE(_ranges), \ +}; + +/* + * These tables contain the physically available PMIC regulator voltage setpoint + * ranges. Where two ranges overlap in hardware, one of the ranges is trimmed + * to ensure that the setpoints available to software are monotonically + * increasing and unique. The set_voltage callback functions expect these + * properties to hold. + */ +static struct qpnp_voltage_range pldo_ranges[] = { + VOLTAGE_RANGE(2, 750000, 750000, 1537500, 12500), + VOLTAGE_RANGE(3, 1500000, 1550000, 3075000, 25000), + VOLTAGE_RANGE(4, 1750000, 3100000, 4900000, 50000), +}; + +static struct qpnp_voltage_range nldo1_ranges[] = { + VOLTAGE_RANGE(2, 750000, 750000, 1537500, 12500), +}; + +static struct qpnp_voltage_range nldo2_ranges[] = { + VOLTAGE_RANGE(1, 375000, 375000, 768750, 6250), + VOLTAGE_RANGE(2, 750000, 775000, 1537500, 12500), +}; + +static struct qpnp_voltage_range smps_ranges[] = { + VOLTAGE_RANGE(0, 375000, 375000, 1562500, 12500), + VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 25000), +}; + +static struct qpnp_voltage_range ftsmps_ranges[] = { + VOLTAGE_RANGE(0, 80000, 350000, 1355000, 5000), + VOLTAGE_RANGE(1, 160000, 1360000, 2710000, 10000), +}; + +static struct qpnp_voltage_range boost_ranges[] = { + VOLTAGE_RANGE(0, 4000000, 4000000, 5550000, 50000), +}; + +static struct qpnp_voltage_set_points pldo_set_points = SET_POINTS(pldo_ranges); +static struct qpnp_voltage_set_points nldo1_set_points + = SET_POINTS(nldo1_ranges); +static struct qpnp_voltage_set_points nldo2_set_points + = SET_POINTS(nldo2_ranges); +static struct qpnp_voltage_set_points smps_set_points = SET_POINTS(smps_ranges); +static struct qpnp_voltage_set_points ftsmps_set_points + = SET_POINTS(ftsmps_ranges); +static struct qpnp_voltage_set_points boost_set_points + = SET_POINTS(boost_ranges); +static struct qpnp_voltage_set_points none_set_points; + +static struct qpnp_voltage_set_points *all_set_points[] = { + &pldo_set_points, + &nldo1_set_points, + &nldo2_set_points, + &smps_set_points, + &ftsmps_set_points, + &boost_set_points, +}; + +/* Determines which label to add to a debug print statement. */ +enum qpnp_regulator_action { + QPNP_REGULATOR_ACTION_INIT, + QPNP_REGULATOR_ACTION_ENABLE, + QPNP_REGULATOR_ACTION_DISABLE, + QPNP_REGULATOR_ACTION_VOLTAGE, + QPNP_REGULATOR_ACTION_MODE, +}; + +static void qpnp_vreg_show_state(struct regulator_dev *rdev, + enum qpnp_regulator_action action); + +#define DEBUG_PRINT_BUFFER_SIZE 64 +static void fill_string(char *str, size_t str_len, u8 *buf, int buf_len) +{ + int pos = 0; + int i; + + for (i = 0; i < buf_len; i++) { + pos += scnprintf(str + pos, str_len - pos, "0x%02X", buf[i]); + if (i < buf_len - 1) + pos += scnprintf(str + pos, str_len - pos, ", "); + } +} + +static inline int qpnp_vreg_read(struct qpnp_regulator *vreg, u16 addr, u8 *buf, + int len) +{ + char str[DEBUG_PRINT_BUFFER_SIZE]; + int rc = 0; + + rc = spmi_ext_register_readl(vreg->spmi_dev->ctrl, vreg->spmi_dev->sid, + vreg->base_addr + addr, buf, len); + + if (!rc && (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_READS)) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buf, len); + pr_info(" %-11s: read(0x%04X), sid=%d, len=%d; %s\n", + vreg->rdesc.name, vreg->base_addr + addr, + vreg->spmi_dev->sid, len, str); + } + + return rc; +} + +static inline int qpnp_vreg_write(struct qpnp_regulator *vreg, u16 addr, + u8 *buf, int len) +{ + char str[DEBUG_PRINT_BUFFER_SIZE]; + int rc = 0; + + if (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_WRITES) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buf, len); + pr_info("%-11s: write(0x%04X), sid=%d, len=%d; %s\n", + vreg->rdesc.name, vreg->base_addr + addr, + vreg->spmi_dev->sid, len, str); + } + + rc = spmi_ext_register_writel(vreg->spmi_dev->ctrl, + vreg->spmi_dev->sid, vreg->base_addr + addr, buf, len); + if (!rc) + vreg->write_count += len; + + return rc; +} + +/* + * qpnp_vreg_write_optimized - write the minimum sized contiguous subset of buf + * @vreg: qpnp_regulator pointer for this regulator + * @addr: local SPMI address offset from this peripheral's base address + * @buf: new data to write into the SPMI registers + * @buf_save: old data in the registers + * @len: number of bytes to write + * + * This function checks for unchanged register values between buf and buf_save + * starting at both ends of buf. Only the contiguous subset in the middle of + * buf starting and ending with new values is sent. + * + * Consider the following example: + * buf offset: 0 1 2 3 4 5 6 7 + * reg state: U U C C U C U U + * (U = unchanged, C = changed) + * In this example registers 2 through 5 will be written with a single + * transaction. + */ +static inline int qpnp_vreg_write_optimized(struct qpnp_regulator *vreg, + u16 addr, u8 *buf, u8 *buf_save, int len) +{ + int i, rc, start, end; + + for (i = 0; i < len; i++) + if (buf[i] != buf_save[i]) + break; + start = i; + + for (i = len - 1; i >= 0; i--) + if (buf[i] != buf_save[i]) + break; + end = i; + + if (start > end) { + /* No modified register values present. */ + return 0; + } + + rc = qpnp_vreg_write(vreg, addr + start, &buf[start], end - start + 1); + if (!rc) + for (i = start; i <= end; i++) + buf_save[i] = buf[i]; + + return rc; +} + +/* + * 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. + */ +static int qpnp_vreg_masked_write(struct qpnp_regulator *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 = qpnp_vreg_write(vreg, addr, ®, 1); + + if (rc) { + vreg_err(vreg, "write failed; addr=0x%03X, rc=%d\n", + addr, rc); + } else { + *reg_save = reg; + } + } + + return rc; +} + +/* + * Perform a masked read-modify-write to a PMIC register only if the new value + * differs from the value currently in the register. This removes redundant + * register writing. + */ +static int qpnp_vreg_masked_read_write(struct qpnp_regulator *vreg, u16 addr, + u8 val, u8 mask) +{ + int rc; + u8 reg; + + rc = qpnp_vreg_read(vreg, addr, ®, 1); + if (rc) { + vreg_err(vreg, "read failed; addr=0x%03X, rc=%d\n", addr, rc); + return rc; + } + + return qpnp_vreg_masked_write(vreg, addr, val, mask, ®); +} + +static int qpnp_regulator_common_is_enabled(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + + return (vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE] + & QPNP_COMMON_ENABLE_MASK) + == QPNP_COMMON_ENABLE; +} + +static int qpnp_regulator_common_enable(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE, + QPNP_COMMON_ENABLE, QPNP_COMMON_ENABLE_MASK, + &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]); + + if (rc) + vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int qpnp_regulator_vs_enable(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc; + u8 reg; + + if (vreg->ocp_enable == QPNP_REGULATOR_ENABLE) { + /* Disable OCP */ + reg = QPNP_VS_OCP_DISABLE; + rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1); + if (rc) + goto fail; + } + + rc = qpnp_regulator_common_enable(rdev); + if (rc) + goto fail; + + if (vreg->ocp_enable == QPNP_REGULATOR_ENABLE) { + /* Wait for inrush current to subsided, then enable OCP. */ + udelay(vreg->ocp_enable_time); + reg = QPNP_VS_OCP_ENABLE_MASK; + rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1); + if (rc) + goto fail; + } + + return rc; +fail: + vreg_err(vreg, "qpnp_vreg_write failed, rc=%d\n", rc); + + return rc; +} + +static int qpnp_regulator_common_disable(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE, + QPNP_COMMON_DISABLE, QPNP_COMMON_ENABLE_MASK, + &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]); + + if (rc) + vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int qpnp_regulator_select_voltage(struct qpnp_regulator *vreg, + int min_uV, int max_uV, int *range_sel, int *voltage_sel) +{ + struct qpnp_voltage_range *range; + int 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].set_point_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) + break; + range = &vreg->set_points->range[i]; + *range_sel = range->range_sel; + + /* + * Force uV to be an allowed set point by applying a ceiling function to + * the uV value. + */ + *voltage_sel = (uV - range->min_uV + range->step_uV - 1) + / range->step_uV; + uV = *voltage_sel * range->step_uV + range->min_uV; + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point; " + "next set point: %d\n", + min_uV, max_uV, uV); + return -EINVAL; + } + + return 0; +} + +static int qpnp_regulator_common_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc, range_sel, voltage_sel; + u8 buf[2]; + + rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel, + &voltage_sel); + if (rc) { + vreg_err(vreg, "could not set voltage, rc=%d\n", rc); + return rc; + } + + buf[0] = range_sel; + buf[1] = voltage_sel; + if ((vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] != range_sel) + && (vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET] == voltage_sel)) { + /* Handle latched range change. */ + rc = qpnp_vreg_write(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE, + buf, 2); + if (!rc) { + vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] = buf[0]; + vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET] = buf[1]; + } + } else { + /* Either write can be optimized away safely. */ + rc = qpnp_vreg_write_optimized(vreg, + QPNP_COMMON_REG_VOLTAGE_RANGE, buf, + &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE], 2); + } + + if (rc) + vreg_err(vreg, "SPMI write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int qpnp_regulator_common_get_voltage(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + struct qpnp_voltage_range *range = NULL; + int range_sel, voltage_sel, i; + + range_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE]; + voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]; + + for (i = 0; i < vreg->set_points->count; i++) { + if (vreg->set_points->range[i].range_sel == range_sel) { + range = &vreg->set_points->range[i]; + break; + } + } + + if (!range) { + vreg_err(vreg, "voltage unknown, range %d is invalid\n", + range_sel); + return VOLTAGE_UNKNOWN; + } + + return range->step_uV * voltage_sel + range->min_uV; +} + +static int qpnp_regulator_boost_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc, range_sel, voltage_sel; + + rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel, + &voltage_sel); + if (rc) { + vreg_err(vreg, "could not set voltage, rc=%d\n", rc); + return rc; + } + + /* + * Boost type regulators do not have range select register so only + * voltage set register needs to be written. + */ + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_VOLTAGE_SET, + voltage_sel, 0xFF, &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]); + + if (rc) + vreg_err(vreg, "SPMI write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int qpnp_regulator_boost_get_voltage(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]; + + return boost_ranges[0].step_uV * voltage_sel + boost_ranges[0].min_uV; +} + +static int qpnp_regulator_common_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int uV = 0; + int i; + + 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].set_point_min_uV; + break; + } else { + selector -= vreg->set_points->range[i].n_voltages; + } + } + + return uV; +} + +static unsigned int qpnp_regulator_common_get_mode(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + + return (vreg->ctrl_reg[QPNP_COMMON_IDX_MODE] + & QPNP_COMMON_MODE_HPM_MASK) + ? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE; +} + +static int qpnp_regulator_common_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc = 0; + u8 val; + + if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) { + vreg_err(vreg, "invalid mode: %u\n", mode); + return -EINVAL; + } + + val = (mode == REGULATOR_MODE_NORMAL ? QPNP_COMMON_MODE_HPM_MASK : 0); + + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_MODE, val, + QPNP_COMMON_MODE_HPM_MASK, + &vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]); + + if (rc) + vreg_err(vreg, "SPMI write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int qpnp_regulator_common_get_optimum_mode( + struct regulator_dev *rdev, int input_uV, int output_uV, + int load_uA) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA + vreg->system_load >= vreg->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return mode; +} + +static int qpnp_regulator_common_enable_time(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + + return vreg->enable_time; +} + +static const char const *qpnp_print_actions[] = { + [QPNP_REGULATOR_ACTION_INIT] = "initial ", + [QPNP_REGULATOR_ACTION_ENABLE] = "enable ", + [QPNP_REGULATOR_ACTION_DISABLE] = "disable ", + [QPNP_REGULATOR_ACTION_VOLTAGE] = "set voltage", + [QPNP_REGULATOR_ACTION_MODE] = "set mode ", +}; + +static void qpnp_vreg_show_state(struct regulator_dev *rdev, + enum qpnp_regulator_action action) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + const char *action_label = qpnp_print_actions[action]; + unsigned int mode = 0; + int uV = 0; + const char *mode_label = ""; + enum qpnp_regulator_logical_type type; + const char *enable_label; + char pc_enable_label[5] = {'\0'}; + char pc_mode_label[8] = {'\0'}; + bool show_req, show_dupe, show_init, has_changed; + u8 en_reg, mode_reg; + + /* Do not print unless appropriate flags are set. */ + show_req = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_REQUEST; + show_dupe = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_DUPLICATE; + show_init = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_INIT; + has_changed = vreg->write_count != vreg->prev_write_count; + if (!((show_init && action == QPNP_REGULATOR_ACTION_INIT) + || (show_req && (has_changed || show_dupe)))) { + return; + } + + vreg->prev_write_count = vreg->write_count; + + type = vreg->logical_type; + + enable_label = qpnp_regulator_common_is_enabled(rdev) ? "on " : "off"; + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) + uV = qpnp_regulator_common_get_voltage(rdev); + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST) + uV = qpnp_regulator_boost_get_voltage(rdev); + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) { + mode = qpnp_regulator_common_get_mode(rdev); + mode_label = mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"; + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) { + en_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]; + pc_enable_label[0] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN3_MASK ? '3' : '_'; + pc_enable_label[1] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN2_MASK ? '2' : '_'; + pc_enable_label[2] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN1_MASK ? '1' : '_'; + pc_enable_label[3] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN0_MASK ? '0' : '_'; + } + + switch (type) { + case QPNP_REGULATOR_LOGICAL_TYPE_SMPS: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + pc_mode_label[1] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_'; + pc_mode_label[2] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK ? '3' : '_'; + pc_mode_label[3] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK ? '2' : '_'; + pc_mode_label[4] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK ? '1' : '_'; + pc_mode_label[5] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK ? '0' : '_'; + + pr_info("%s %-11s: %s, v=%7d uV, mode=%s, pc_en=%s, " + "alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + mode_label, pc_enable_label, pc_mode_label); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_LDO: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + pc_mode_label[1] = + mode_reg & QPNP_COMMON_MODE_BYPASS_MASK ? 'B' : '_'; + pc_mode_label[2] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_'; + pc_mode_label[3] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK ? '3' : '_'; + pc_mode_label[4] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK ? '2' : '_'; + pc_mode_label[5] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK ? '1' : '_'; + pc_mode_label[6] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK ? '0' : '_'; + + pr_info("%s %-11s: %s, v=%7d uV, mode=%s, pc_en=%s, " + "alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + mode_label, pc_enable_label, pc_mode_label); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_VS: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + pc_mode_label[1] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_'; + + pr_info("%s %-11s: %s, pc_en=%s, alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, + pc_enable_label, pc_mode_label); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_BOOST: + pr_info("%s %-11s: %s, v=%7d uV\n", + action_label, vreg->rdesc.name, enable_label, uV); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + + pr_info("%s %-11s: %s, v=%7d uV, mode=%s, alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + mode_label, pc_mode_label); + break; + default: + break; + } +} + +static struct regulator_ops qpnp_smps_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_common_set_voltage, + .get_voltage = qpnp_regulator_common_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .set_mode = qpnp_regulator_common_set_mode, + .get_mode = qpnp_regulator_common_get_mode, + .get_optimum_mode = qpnp_regulator_common_get_optimum_mode, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_ldo_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_common_set_voltage, + .get_voltage = qpnp_regulator_common_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .set_mode = qpnp_regulator_common_set_mode, + .get_mode = qpnp_regulator_common_get_mode, + .get_optimum_mode = qpnp_regulator_common_get_optimum_mode, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_vs_ops = { + .enable = qpnp_regulator_vs_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_boost_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_boost_set_voltage, + .get_voltage = qpnp_regulator_boost_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_ftsmps_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_common_set_voltage, + .get_voltage = qpnp_regulator_common_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .set_mode = qpnp_regulator_common_set_mode, + .get_mode = qpnp_regulator_common_get_mode, + .get_optimum_mode = qpnp_regulator_common_get_optimum_mode, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static const struct qpnp_regulator_mapping supported_regulators[] = { + QPNP_VREG_MAP(HF_BUCK, GP_CTL, SMPS, smps, smps, 100000), + QPNP_VREG_MAP(LDO, N300, LDO, ldo, nldo1, 10000), + QPNP_VREG_MAP(LDO, N600, LDO, ldo, nldo2, 10000), + QPNP_VREG_MAP(LDO, N1200, LDO, ldo, nldo2, 10000), + QPNP_VREG_MAP(LDO, P50, LDO, ldo, pldo, 5000), + QPNP_VREG_MAP(LDO, P150, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(LDO, P300, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(LDO, P600, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(LDO, P1200, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(VS, LV100, VS, vs, none, 0), + QPNP_VREG_MAP(VS, LV300, VS, vs, none, 0), + QPNP_VREG_MAP(VS, MV300, VS, vs, none, 0), + QPNP_VREG_MAP(VS, MV500, VS, vs, none, 0), + QPNP_VREG_MAP(VS, HDMI, VS, vs, none, 0), + QPNP_VREG_MAP(VS, OTG, VS, vs, none, 0), + QPNP_VREG_MAP(BOOST, 5V_BOOST, BOOST, boost, boost, 0), + QPNP_VREG_MAP(FTS, FTS_CTL, FTSMPS, ftsmps, ftsmps, 100000), +}; + +static int qpnp_regulator_match(struct qpnp_regulator *vreg) +{ + const struct qpnp_regulator_mapping *mapping; + int rc, i; + u8 raw_type[2], type, subtype; + + rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_TYPE, raw_type, 2); + if (rc) { + vreg_err(vreg, "could not read type register, rc=%d\n", rc); + return rc; + } + type = raw_type[0]; + subtype = raw_type[1]; + + rc = -ENODEV; + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { + mapping = &supported_regulators[i]; + if (mapping->type == type && mapping->subtype == subtype) { + vreg->logical_type = mapping->logical_type; + vreg->set_points = mapping->set_points; + vreg->hpm_min_load = mapping->hpm_min_load; + vreg->rdesc.ops = mapping->ops; + vreg->rdesc.n_voltages + = mapping->set_points->n_voltages; + rc = 0; + break; + } + } + + return rc; +} + +static int qpnp_regulator_init_registers(struct qpnp_regulator *vreg, + struct qpnp_regulator_platform_data *pdata) +{ + int rc, i; + enum qpnp_regulator_logical_type type; + u8 ctrl_reg[8], reg, mask; + + type = vreg->logical_type; + + rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE, + vreg->ctrl_reg, 8); + if (rc) { + vreg_err(vreg, "spmi read failed, rc=%d\n", rc); + return rc; + } + + for (i = 0; i < ARRAY_SIZE(ctrl_reg); i++) + ctrl_reg[i] = vreg->ctrl_reg[i]; + + /* Set up enable pin control. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) + && !(pdata->pin_ctrl_enable + & QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_ENABLE] &= + ~QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK; + ctrl_reg[QPNP_COMMON_IDX_ENABLE] |= + pdata->pin_ctrl_enable & QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK; + } + + /* Set up auto mode control. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS + || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) + && (pdata->auto_mode_enable != QPNP_REGULATOR_USE_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_AUTO_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + (pdata->auto_mode_enable ? QPNP_COMMON_MODE_AUTO_MASK : 0); + } + + /* Set up mode pin control. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO) + && !(pdata->pin_ctrl_hpm + & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_FOLLOW_ALL_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_ALL_MASK; + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_VS + && !(pdata->pin_ctrl_hpm & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK; + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + && pdata->bypass_mode_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_BYPASS_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + (pdata->bypass_mode_enable + ? QPNP_COMMON_MODE_BYPASS_MASK : 0); + } + + /* Set boost current limit. */ + if (type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST + && pdata->boost_current_limit + != QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT) { + ctrl_reg[QPNP_BOOST_IDX_CURRENT_LIMIT] &= + ~QPNP_BOOST_CURRENT_LIMIT_MASK; + ctrl_reg[QPNP_BOOST_IDX_CURRENT_LIMIT] |= + pdata->boost_current_limit & QPNP_BOOST_CURRENT_LIMIT_MASK; + } + + /* Write back any control register values that were modified. */ + rc = qpnp_vreg_write_optimized(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE, + ctrl_reg, vreg->ctrl_reg, 8); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + + /* Set pull down. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) + && pdata->pull_down_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg = pdata->pull_down_enable + ? QPNP_COMMON_PULL_DOWN_ENABLE_MASK : 0; + rc = qpnp_vreg_write(vreg, QPNP_COMMON_REG_PULL_DOWN, ®, 1); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS + && pdata->pull_down_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + /* FTSMPS has other bits in the pull down control register. */ + reg = pdata->pull_down_enable + ? QPNP_COMMON_PULL_DOWN_ENABLE_MASK : 0; + rc = qpnp_vreg_masked_read_write(vreg, + QPNP_COMMON_REG_PULL_DOWN, reg, + QPNP_COMMON_PULL_DOWN_ENABLE_MASK); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + } + + /* Set soft start for LDO. */ + if (type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + && pdata->soft_start_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg = pdata->soft_start_enable + ? QPNP_LDO_SOFT_START_ENABLE_MASK : 0; + rc = qpnp_vreg_write(vreg, QPNP_LDO_REG_SOFT_START, ®, 1); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + } + + /* Set soft start strength and over current protection for VS. */ + if (type == QPNP_REGULATOR_LOGICAL_TYPE_VS) { + reg = 0; + mask = 0; + if (pdata->soft_start_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg |= pdata->soft_start_enable + ? QPNP_VS_SOFT_START_ENABLE_MASK : 0; + mask |= QPNP_VS_SOFT_START_ENABLE_MASK; + } + if (pdata->vs_soft_start_strength + != QPNP_VS_SOFT_START_STR_HW_DEFAULT) { + reg |= pdata->vs_soft_start_strength + & QPNP_VS_SOFT_START_SEL_MASK; + mask |= QPNP_VS_SOFT_START_SEL_MASK; + } + rc = qpnp_vreg_masked_read_write(vreg, QPNP_VS_REG_SOFT_START, + reg, mask); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + + if (pdata->ocp_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg = pdata->ocp_enable ? QPNP_VS_OCP_ENABLE_MASK : 0; + rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", + rc); + return rc; + } + } + } + + return rc; +} + +/* Fill in pdata elements based on values found in device tree. */ +static int qpnp_regulator_get_dt_config(struct spmi_device *spmi, + struct qpnp_regulator_platform_data *pdata) +{ + struct resource *res; + struct device_node *node = spmi->dev.of_node; + int rc = 0; + + pdata->init_data.constraints.input_uV + = pdata->init_data.constraints.max_uV; + + res = qpnp_get_resource(spmi, 0, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&spmi->dev, "%s: node is missing base address\n", + __func__); + return -EINVAL; + } + pdata->base_addr = res->start; + + /* + * Initialize configuration parameters to use hardware default in case + * no value is specified via device tree. + */ + pdata->auto_mode_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->bypass_mode_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->ocp_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->pull_down_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->soft_start_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->boost_current_limit = QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT; + pdata->pin_ctrl_enable = QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT; + pdata->pin_ctrl_hpm = QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT; + pdata->vs_soft_start_strength = QPNP_VS_SOFT_START_STR_HW_DEFAULT; + + /* These bindings are optional, so it is okay if they are not found. */ + of_property_read_u32(node, "qcom,auto-mode-enable", + &pdata->auto_mode_enable); + of_property_read_u32(node, "qcom,bypass-mode-enable", + &pdata->bypass_mode_enable); + of_property_read_u32(node, "qcom,ocp-enable", &pdata->ocp_enable); + of_property_read_u32(node, "qcom,pull-down-enable", + &pdata->pull_down_enable); + of_property_read_u32(node, "qcom,soft-start-enable", + &pdata->soft_start_enable); + of_property_read_u32(node, "qcom,boost-current-limit", + &pdata->boost_current_limit); + of_property_read_u32(node, "qcom,pin-ctrl-enable", + &pdata->pin_ctrl_enable); + of_property_read_u32(node, "qcom,pin-ctrl-hpm", &pdata->pin_ctrl_hpm); + of_property_read_u32(node, "qcom,vs-soft-start-strength", + &pdata->vs_soft_start_strength); + of_property_read_u32(node, "qcom,system-load", &pdata->system_load); + of_property_read_u32(node, "qcom,enable-time", &pdata->enable_time); + of_property_read_u32(node, "qcom,ocp-enable-time", + &pdata->ocp_enable_time); + + return rc; +} + +static struct of_device_id spmi_match_table[]; + +#define MAX_NAME_LEN 127 + +static int __devinit qpnp_regulator_probe(struct spmi_device *spmi) +{ + struct qpnp_regulator_platform_data *pdata; + struct qpnp_regulator *vreg; + struct regulator_desc *rdesc; + struct qpnp_regulator_platform_data of_pdata; + struct regulator_init_data *init_data; + char *reg_name; + int rc; + bool is_dt; + + vreg = kzalloc(sizeof(struct qpnp_regulator), GFP_KERNEL); + if (!vreg) { + dev_err(&spmi->dev, "%s: Can't allocate qpnp_regulator\n", + __func__); + return -ENOMEM; + } + + is_dt = of_match_device(spmi_match_table, &spmi->dev); + + /* Check if device tree is in use. */ + if (is_dt) { + init_data = of_get_regulator_init_data(&spmi->dev, + spmi->dev.of_node); + if (!init_data) { + dev_err(&spmi->dev, "%s: unable to allocate memory\n", + __func__); + kfree(vreg); + return -ENOMEM; + } + memset(&of_pdata, 0, + sizeof(struct qpnp_regulator_platform_data)); + memcpy(&of_pdata.init_data, init_data, + sizeof(struct regulator_init_data)); + + if (of_get_property(spmi->dev.of_node, "parent-supply", NULL)) + of_pdata.init_data.supply_regulator = "parent"; + + rc = qpnp_regulator_get_dt_config(spmi, &of_pdata); + if (rc) { + dev_err(&spmi->dev, "%s: DT parsing failed, rc=%d\n", + __func__, rc); + kfree(vreg); + return -ENOMEM; + } + + pdata = &of_pdata; + } else { + pdata = spmi->dev.platform_data; + } + + if (pdata == NULL) { + dev_err(&spmi->dev, "%s: no platform data specified\n", + __func__); + kfree(vreg); + return -EINVAL; + } + + vreg->spmi_dev = spmi; + vreg->prev_write_count = -1; + vreg->write_count = 0; + vreg->base_addr = pdata->base_addr; + vreg->enable_time = pdata->enable_time; + vreg->system_load = pdata->system_load; + vreg->ocp_enable = pdata->ocp_enable; + vreg->ocp_enable_time = pdata->ocp_enable_time; + + rdesc = &vreg->rdesc; + rdesc->id = spmi->ctrl->nr; + rdesc->owner = THIS_MODULE; + rdesc->type = REGULATOR_VOLTAGE; + + reg_name = kzalloc(strnlen(pdata->init_data.constraints.name, + MAX_NAME_LEN) + 1, GFP_KERNEL); + if (!reg_name) { + dev_err(&spmi->dev, "%s: Can't allocate regulator name\n", + __func__); + kfree(vreg); + return -ENOMEM; + } + strlcpy(reg_name, pdata->init_data.constraints.name, + strnlen(pdata->init_data.constraints.name, MAX_NAME_LEN) + 1); + rdesc->name = reg_name; + + dev_set_drvdata(&spmi->dev, vreg); + + rc = qpnp_regulator_match(vreg); + if (rc) { + vreg_err(vreg, "regulator type unknown, rc=%d\n", rc); + goto bail; + } + + if (is_dt && rdesc->ops) { + /* Fill in ops and mode masks when using device tree. */ + if (rdesc->ops->enable) + pdata->init_data.constraints.valid_ops_mask + |= REGULATOR_CHANGE_STATUS; + if (rdesc->ops->get_voltage) + pdata->init_data.constraints.valid_ops_mask + |= REGULATOR_CHANGE_VOLTAGE; + if (rdesc->ops->get_mode) { + pdata->init_data.constraints.valid_ops_mask + |= REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_DRMS; + pdata->init_data.constraints.valid_modes_mask + = REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + } + } + + rc = qpnp_regulator_init_registers(vreg, pdata); + if (rc) { + vreg_err(vreg, "common initialization failed, rc=%d\n", rc); + goto bail; + } + + vreg->rdev = regulator_register(rdesc, &spmi->dev, + &(pdata->init_data), vreg, spmi->dev.of_node); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + vreg_err(vreg, "regulator_register failed, rc=%d\n", rc); + goto bail; + } + + qpnp_vreg_show_state(vreg->rdev, QPNP_REGULATOR_ACTION_INIT); + + return 0; + +bail: + if (rc) + vreg_err(vreg, "probe failed, rc=%d\n", rc); + + kfree(vreg->rdesc.name); + kfree(vreg); + + return rc; +} + +static int __devexit qpnp_regulator_remove(struct spmi_device *spmi) +{ + struct qpnp_regulator *vreg; + + vreg = dev_get_drvdata(&spmi->dev); + dev_set_drvdata(&spmi->dev, NULL); + + if (vreg) { + regulator_unregister(vreg->rdev); + kfree(vreg->rdesc.name); + kfree(vreg); + } + + return 0; +} + +static struct of_device_id spmi_match_table[] = { + { .compatible = QPNP_REGULATOR_DRIVER_NAME, }, + {} +}; + +static const struct spmi_device_id qpnp_regulator_id[] = { + { QPNP_REGULATOR_DRIVER_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(spmi, qpnp_regulator_id); + +static struct spmi_driver qpnp_regulator_driver = { + .driver = { + .name = QPNP_REGULATOR_DRIVER_NAME, + .of_match_table = spmi_match_table, + .owner = THIS_MODULE, + }, + .probe = qpnp_regulator_probe, + .remove = __devexit_p(qpnp_regulator_remove), + .id_table = qpnp_regulator_id, +}; + +/* + * Pre-compute the number of set points available for each regulator type to + * avoid unnecessary calculations later in runtime. + */ +static void qpnp_regulator_set_point_init(void) +{ + struct qpnp_voltage_set_points **set_points; + int i, j, temp; + + set_points = all_set_points; + + for (i = 0; i < ARRAY_SIZE(all_set_points); i++) { + temp = 0; + for (j = 0; j < all_set_points[i]->count; j++) { + all_set_points[i]->range[j].n_voltages + = (all_set_points[i]->range[j].max_uV + - all_set_points[i]->range[j].set_point_min_uV) + / all_set_points[i]->range[j].step_uV + 1; + temp += all_set_points[i]->range[j].n_voltages; + } + all_set_points[i]->n_voltages = temp; + } +} + +/** + * qpnp_regulator_init() - register spmi driver for qpnp-regulator + * + * This initialization function should be called in systems in which driver + * registration ordering must be controlled precisely. + */ +int __init qpnp_regulator_init(void) +{ + static bool has_registered; + + if (has_registered) + return 0; + else + has_registered = true; + + qpnp_regulator_set_point_init(); + + return spmi_driver_register(&qpnp_regulator_driver); +} +EXPORT_SYMBOL(qpnp_regulator_init); + +static void __exit qpnp_regulator_exit(void) +{ + spmi_driver_unregister(&qpnp_regulator_driver); +} + +MODULE_DESCRIPTION("QPNP PMIC regulator driver"); +MODULE_LICENSE("GPL v2"); + +arch_initcall(qpnp_regulator_init); +module_exit(qpnp_regulator_exit); diff --git a/drivers/regulator/stub-regulator.c b/drivers/regulator/stub-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..1c4b935d90dec5d655b49ddfb9a50ab78238b963 --- /dev/null +++ b/drivers/regulator/stub-regulator.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#define STUB_REGULATOR_MAX_NAME 40 + +struct regulator_stub { + struct regulator_desc rdesc; + struct regulator_dev *rdev; + int voltage; + bool enabled; + int mode; + int hpm_min_load; + int system_uA; + char name[STUB_REGULATOR_MAX_NAME]; +}; + +static int regulator_stub_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + vreg_priv->voltage = min_uV; + return 0; +} + +static int regulator_stub_get_voltage(struct regulator_dev *rdev) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + return vreg_priv->voltage; +} + +static int regulator_stub_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct regulation_constraints *constraints = rdev->constraints; + + if (selector >= 2) + return -EINVAL; + else if (selector == 0) + return constraints->min_uV; + else + return constraints->max_uV; +} + +static unsigned int regulator_stub_get_mode(struct regulator_dev *rdev) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + return vreg_priv->mode; +} + +static int regulator_stub_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + + if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) { + dev_err(&rdev->dev, "%s: invalid mode requested %u\n", + __func__, mode); + return -EINVAL; + } + vreg_priv->mode = mode; + return 0; +} + +static unsigned int regulator_stub_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA + vreg_priv->system_uA >= vreg_priv->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return mode; +} + +static int regulator_stub_enable(struct regulator_dev *rdev) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + vreg_priv->enabled = true; + return 0; +} + +static int regulator_stub_disable(struct regulator_dev *rdev) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + vreg_priv->enabled = false; + return 0; +} + +static int regulator_stub_is_enabled(struct regulator_dev *rdev) +{ + struct regulator_stub *vreg_priv = rdev_get_drvdata(rdev); + return vreg_priv->enabled; +} + +/* Real regulator operations. */ +static struct regulator_ops regulator_stub_ops = { + .enable = regulator_stub_enable, + .disable = regulator_stub_disable, + .is_enabled = regulator_stub_is_enabled, + .set_voltage = regulator_stub_set_voltage, + .get_voltage = regulator_stub_get_voltage, + .list_voltage = regulator_stub_list_voltage, + .set_mode = regulator_stub_set_mode, + .get_mode = regulator_stub_get_mode, + .get_optimum_mode = regulator_stub_get_optimum_mode, +}; + +static void regulator_stub_cleanup(struct regulator_stub *vreg_priv) +{ + if (vreg_priv && vreg_priv->rdev) + regulator_unregister(vreg_priv->rdev); + kfree(vreg_priv); +} + +static int __devinit regulator_stub_probe(struct platform_device *pdev) +{ + struct stub_regulator_pdata *vreg_pdata; + struct regulator_desc *rdesc; + struct regulator_stub *vreg_priv; + int rc; + + vreg_pdata = pdev->dev.platform_data; + if (!vreg_pdata) { + dev_err(&pdev->dev, "%s: no platform data\n", __func__); + return -EINVAL; + } + + vreg_priv = kzalloc(sizeof(*vreg_priv), GFP_KERNEL); + if (!vreg_priv) { + dev_err(&pdev->dev, "%s: Unable to allocate memory\n", + __func__); + return -ENOMEM; + } + dev_set_drvdata(&pdev->dev, vreg_priv); + + rdesc = &vreg_priv->rdesc; + strncpy(vreg_priv->name, vreg_pdata->init_data.constraints.name, + STUB_REGULATOR_MAX_NAME); + rdesc->name = vreg_priv->name; + rdesc->ops = ®ulator_stub_ops; + + /* + * Ensure that voltage set points are handled correctly for regulators + * which have a specified voltage constraint range, as well as those + * that do not. + */ + if (vreg_pdata->init_data.constraints.min_uV == 0 && + vreg_pdata->init_data.constraints.max_uV == 0) + rdesc->n_voltages = 0; + else + rdesc->n_voltages = 2; + + rdesc->id = pdev->id; + rdesc->owner = THIS_MODULE; + rdesc->type = REGULATOR_VOLTAGE; + vreg_priv->system_uA = vreg_pdata->system_uA; + vreg_priv->hpm_min_load = vreg_pdata->hpm_min_load; + vreg_priv->voltage = vreg_pdata->init_data.constraints.min_uV; + + vreg_priv->rdev = regulator_register(rdesc, &pdev->dev, + &(vreg_pdata->init_data), vreg_priv, NULL); + if (IS_ERR(vreg_priv->rdev)) { + rc = PTR_ERR(vreg_priv->rdev); + vreg_priv->rdev = NULL; + dev_err(&pdev->dev, "%s: regulator_register failed\n", + __func__); + goto err_probe; + } + + return 0; + +err_probe: + regulator_stub_cleanup(vreg_priv); + return rc; +} + +static int __devexit regulator_stub_remove(struct platform_device *pdev) +{ + struct regulator_stub *vreg_priv = dev_get_drvdata(&pdev->dev); + + regulator_stub_cleanup(vreg_priv); + return 0; +} + +static struct platform_driver regulator_stub_driver = { + .probe = regulator_stub_probe, + .remove = __devexit_p(regulator_stub_remove), + .driver = { + .name = STUB_REGULATOR_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +int __init regulator_stub_init(void) +{ + static int registered; + + if (registered) + return 0; + else + registered = 1; + return platform_driver_register(®ulator_stub_driver); +} +postcore_initcall(regulator_stub_init); +EXPORT_SYMBOL(regulator_stub_init); + +static void __exit regulator_stub_exit(void) +{ + platform_driver_unregister(®ulator_stub_driver); +} +module_exit(regulator_stub_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("stub regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform: " STUB_REGULATOR_DRIVER_NAME); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 8c8377d50c4c4578724c6149bbd084744e7c92fb..fc0d02a79dd9cec942e624ef517a44dcb8b7f46f 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 @@ -106,6 +123,24 @@ config RTC_INTF_DEV_UIE_EMUL clock several times per second, please enable this option only if you know that you really need it. +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_DRV_TEST tristate "Test driver/device" help @@ -728,6 +763,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 @@ -738,6 +804,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 @@ -1077,6 +1150,15 @@ config RTC_DRV_PUV3 This drive can also be built as a module. If so, the module will be called rtc-puv3. +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. + config RTC_DRV_LOONGSON1 tristate "loongson1 RTC support" depends on MACH_LOONGSON1 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 727ae7786e6c3806face77564b82e76c52a5b01f..8a3cecda4a75677c9e79f98646cf62e288caeaaa 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_RTC_HCTOSYS) += hctosys.o obj-$(CONFIG_RTC_CLASS) += rtc-core.o rtc-core-y := class.o interface.o +obj-$(CONFIG_RTC_INTF_ALARM) += alarm.o +obj-$(CONFIG_RTC_INTF_ALARM_DEV) += alarm-dev.o rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o @@ -67,6 +69,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 +81,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_PM8XXX) += rtc-pm8xxx.o diff --git a/drivers/rtc/alarm-dev.c b/drivers/rtc/alarm-dev.c new file mode 100644 index 0000000000000000000000000000000000000000..bfcaebc9ce429bd5d4c9fee6cd9d2b706fa1d624 --- /dev/null +++ b/drivers/rtc/alarm-dev.c @@ -0,0 +1,287 @@ +/* drivers/rtc/alarm-dev.c + * + * Copyright (C) 2007-2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ANDROID_ALARM_PRINT_INFO (1U << 0) +#define ANDROID_ALARM_PRINT_IO (1U << 1) +#define ANDROID_ALARM_PRINT_INT (1U << 2) + +static int debug_mask = ANDROID_ALARM_PRINT_INFO; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define pr_alarm(debug_level_mask, args...) \ + do { \ + if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) { \ + pr_info(args); \ + } \ + } while (0) + +#define ANDROID_ALARM_WAKEUP_MASK ( \ + ANDROID_ALARM_RTC_WAKEUP_MASK | \ + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK) + +/* support old usespace code */ +#define ANDROID_ALARM_SET_OLD _IOW('a', 2, time_t) /* set alarm */ +#define ANDROID_ALARM_SET_AND_WAIT_OLD _IOW('a', 3, time_t) + +static int alarm_opened; +static DEFINE_SPINLOCK(alarm_slock); +static struct wake_lock alarm_wake_lock; +static DECLARE_WAIT_QUEUE_HEAD(alarm_wait_queue); +static uint32_t alarm_pending; +static uint32_t alarm_enabled; +static uint32_t wait_pending; + +static struct alarm alarms[ANDROID_ALARM_TYPE_COUNT]; + +static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rv = 0; + unsigned long flags; + struct timespec new_alarm_time; + struct timespec new_rtc_time; + struct timespec tmp_time; + enum android_alarm_type alarm_type = ANDROID_ALARM_IOCTL_TO_TYPE(cmd); + uint32_t alarm_type_mask = 1U << alarm_type; + + if (alarm_type >= ANDROID_ALARM_TYPE_COUNT) + return -EINVAL; + + if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_GET_TIME(0)) { + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EPERM; + if (file->private_data == NULL && + cmd != ANDROID_ALARM_SET_RTC) { + spin_lock_irqsave(&alarm_slock, flags); + if (alarm_opened) { + spin_unlock_irqrestore(&alarm_slock, flags); + return -EBUSY; + } + alarm_opened = 1; + file->private_data = (void *)1; + spin_unlock_irqrestore(&alarm_slock, flags); + } + } + + switch (ANDROID_ALARM_BASE_CMD(cmd)) { + case ANDROID_ALARM_CLEAR(0): + spin_lock_irqsave(&alarm_slock, flags); + pr_alarm(IO, "alarm %d clear\n", alarm_type); + alarm_try_to_cancel(&alarms[alarm_type]); + if (alarm_pending) { + alarm_pending &= ~alarm_type_mask; + if (!alarm_pending && !wait_pending) + wake_unlock(&alarm_wake_lock); + } + alarm_enabled &= ~alarm_type_mask; + spin_unlock_irqrestore(&alarm_slock, flags); + break; + + case ANDROID_ALARM_SET_OLD: + case ANDROID_ALARM_SET_AND_WAIT_OLD: + if (get_user(new_alarm_time.tv_sec, (int __user *)arg)) { + rv = -EFAULT; + goto err1; + } + new_alarm_time.tv_nsec = 0; + goto from_old_alarm_set; + + case ANDROID_ALARM_SET_AND_WAIT(0): + case ANDROID_ALARM_SET(0): + if (copy_from_user(&new_alarm_time, (void __user *)arg, + sizeof(new_alarm_time))) { + rv = -EFAULT; + goto err1; + } +from_old_alarm_set: + spin_lock_irqsave(&alarm_slock, flags); + pr_alarm(IO, "alarm %d set %ld.%09ld\n", alarm_type, + new_alarm_time.tv_sec, new_alarm_time.tv_nsec); + alarm_enabled |= alarm_type_mask; + alarm_start_range(&alarms[alarm_type], + timespec_to_ktime(new_alarm_time), + timespec_to_ktime(new_alarm_time)); + spin_unlock_irqrestore(&alarm_slock, flags); + if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0) + && cmd != ANDROID_ALARM_SET_AND_WAIT_OLD) + break; + /* fall though */ + case ANDROID_ALARM_WAIT: + spin_lock_irqsave(&alarm_slock, flags); + pr_alarm(IO, "alarm wait\n"); + if (!alarm_pending && wait_pending) { + wake_unlock(&alarm_wake_lock); + wait_pending = 0; + } + spin_unlock_irqrestore(&alarm_slock, flags); + rv = wait_event_interruptible(alarm_wait_queue, alarm_pending); + if (rv) + goto err1; + spin_lock_irqsave(&alarm_slock, flags); + rv = alarm_pending; + wait_pending = 1; + alarm_pending = 0; + spin_unlock_irqrestore(&alarm_slock, flags); + break; + case ANDROID_ALARM_SET_RTC: + if (copy_from_user(&new_rtc_time, (void __user *)arg, + sizeof(new_rtc_time))) { + rv = -EFAULT; + goto err1; + } + rv = alarm_set_rtc(new_rtc_time); + spin_lock_irqsave(&alarm_slock, flags); + alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK; + wake_up(&alarm_wait_queue); + spin_unlock_irqrestore(&alarm_slock, flags); + if (rv < 0) + goto err1; + break; + case ANDROID_ALARM_GET_TIME(0): + switch (alarm_type) { + case ANDROID_ALARM_RTC_WAKEUP: + case ANDROID_ALARM_RTC: + getnstimeofday(&tmp_time); + break; + case ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP: + case ANDROID_ALARM_ELAPSED_REALTIME: + tmp_time = + ktime_to_timespec(alarm_get_elapsed_realtime()); + break; + case ANDROID_ALARM_TYPE_COUNT: + case ANDROID_ALARM_SYSTEMTIME: + ktime_get_ts(&tmp_time); + break; + } + if (copy_to_user((void __user *)arg, &tmp_time, + sizeof(tmp_time))) { + rv = -EFAULT; + goto err1; + } + break; + + default: + rv = -EINVAL; + goto err1; + } +err1: + return rv; +} + +static int alarm_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static int alarm_release(struct inode *inode, struct file *file) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&alarm_slock, flags); + if (file->private_data != 0) { + for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) { + uint32_t alarm_type_mask = 1U << i; + if (alarm_enabled & alarm_type_mask) { + pr_alarm(INFO, "alarm_release: clear alarm, " + "pending %d\n", + !!(alarm_pending & alarm_type_mask)); + alarm_enabled &= ~alarm_type_mask; + } + spin_unlock_irqrestore(&alarm_slock, flags); + alarm_cancel(&alarms[i]); + spin_lock_irqsave(&alarm_slock, flags); + } + if (alarm_pending | wait_pending) { + if (alarm_pending) + pr_alarm(INFO, "alarm_release: clear " + "pending alarms %x\n", alarm_pending); + wake_unlock(&alarm_wake_lock); + wait_pending = 0; + alarm_pending = 0; + } + alarm_opened = 0; + } + spin_unlock_irqrestore(&alarm_slock, flags); + return 0; +} + +static void alarm_triggered(struct alarm *alarm) +{ + unsigned long flags; + uint32_t alarm_type_mask = 1U << alarm->type; + + pr_alarm(INT, "alarm_triggered type %d\n", alarm->type); + spin_lock_irqsave(&alarm_slock, flags); + if (alarm_enabled & alarm_type_mask) { + wake_lock_timeout(&alarm_wake_lock, 5 * HZ); + alarm_enabled &= ~alarm_type_mask; + alarm_pending |= alarm_type_mask; + wake_up(&alarm_wait_queue); + } + spin_unlock_irqrestore(&alarm_slock, flags); +} + +static const struct file_operations alarm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = alarm_ioctl, + .open = alarm_open, + .release = alarm_release, +}; + +static struct miscdevice alarm_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "alarm", + .fops = &alarm_fops, +}; + +static int __init alarm_dev_init(void) +{ + int err; + int i; + + err = misc_register(&alarm_device); + if (err) + return err; + + for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) + alarm_init(&alarms[i], i, alarm_triggered); + wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm"); + + return 0; +} + +static void __exit alarm_dev_exit(void) +{ + misc_deregister(&alarm_device); + wake_lock_destroy(&alarm_wake_lock); +} + +module_init(alarm_dev_init); +module_exit(alarm_dev_exit); + diff --git a/drivers/rtc/alarm.c b/drivers/rtc/alarm.c new file mode 100644 index 0000000000000000000000000000000000000000..9340af7dc86e3a88132ae83ae54bd0213961185f --- /dev/null +++ b/drivers/rtc/alarm.c @@ -0,0 +1,615 @@ +/* drivers/rtc/alarm.c + * + * Copyright (C) 2007-2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ANDROID_ALARM_PRINT_ERROR (1U << 0) +#define ANDROID_ALARM_PRINT_INIT_STATUS (1U << 1) +#define ANDROID_ALARM_PRINT_TSET (1U << 2) +#define ANDROID_ALARM_PRINT_CALL (1U << 3) +#define ANDROID_ALARM_PRINT_SUSPEND (1U << 4) +#define ANDROID_ALARM_PRINT_INT (1U << 5) +#define ANDROID_ALARM_PRINT_FLOW (1U << 6) + +static int debug_mask = ANDROID_ALARM_PRINT_ERROR | \ + ANDROID_ALARM_PRINT_INIT_STATUS; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define pr_alarm(debug_level_mask, args...) \ + do { \ + if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) { \ + pr_info(args); \ + } \ + } while (0) + +#define ANDROID_ALARM_WAKEUP_MASK ( \ + ANDROID_ALARM_RTC_WAKEUP_MASK | \ + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK) + +/* support old usespace code */ +#define ANDROID_ALARM_SET_OLD _IOW('a', 2, time_t) /* set alarm */ +#define ANDROID_ALARM_SET_AND_WAIT_OLD _IOW('a', 3, time_t) + +struct alarm_queue { + struct rb_root alarms; + struct rb_node *first; + struct hrtimer timer; + ktime_t delta; + bool stopped; + ktime_t stopped_time; +}; + +static struct rtc_device *alarm_rtc_dev; +static DEFINE_SPINLOCK(alarm_slock); +static DEFINE_MUTEX(alarm_setrtc_mutex); +static struct wake_lock alarm_rtc_wake_lock; +static struct platform_device *alarm_platform_dev; +struct alarm_queue alarms[ANDROID_ALARM_TYPE_COUNT]; +static bool suspended; + +static void update_timer_locked(struct alarm_queue *base, bool head_removed) +{ + struct alarm *alarm; + bool is_wakeup = base == &alarms[ANDROID_ALARM_RTC_WAKEUP] || + base == &alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP]; + + if (base->stopped) { + pr_alarm(FLOW, "changed alarm while setting the wall time\n"); + return; + } + + if (is_wakeup && !suspended && head_removed) + wake_unlock(&alarm_rtc_wake_lock); + + if (!base->first) + return; + + alarm = container_of(base->first, struct alarm, node); + + pr_alarm(FLOW, "selected alarm, type %d, func %pF at %lld\n", + alarm->type, alarm->function, ktime_to_ns(alarm->expires)); + + if (is_wakeup && suspended) { + pr_alarm(FLOW, "changed alarm while suspened\n"); + wake_lock_timeout(&alarm_rtc_wake_lock, 1 * HZ); + return; + } + + hrtimer_try_to_cancel(&base->timer); + base->timer.node.expires = ktime_add(base->delta, alarm->expires); + base->timer._softexpires = ktime_add(base->delta, alarm->softexpires); + hrtimer_start_expires(&base->timer, HRTIMER_MODE_ABS); +} + +static void alarm_enqueue_locked(struct alarm *alarm) +{ + struct alarm_queue *base = &alarms[alarm->type]; + struct rb_node **link = &base->alarms.rb_node; + struct rb_node *parent = NULL; + struct alarm *entry; + int leftmost = 1; + bool was_first = false; + + pr_alarm(FLOW, "added alarm, type %d, func %pF at %lld\n", + alarm->type, alarm->function, ktime_to_ns(alarm->expires)); + + if (base->first == &alarm->node) { + base->first = rb_next(&alarm->node); + was_first = true; + } + if (!RB_EMPTY_NODE(&alarm->node)) { + rb_erase(&alarm->node, &base->alarms); + RB_CLEAR_NODE(&alarm->node); + } + + while (*link) { + parent = *link; + entry = rb_entry(parent, struct alarm, node); + /* + * We dont care about collisions. Nodes with + * the same expiry time stay together. + */ + if (alarm->expires.tv64 < entry->expires.tv64) { + link = &(*link)->rb_left; + } else { + link = &(*link)->rb_right; + leftmost = 0; + } + } + if (leftmost) + base->first = &alarm->node; + if (leftmost || was_first) + update_timer_locked(base, was_first); + + rb_link_node(&alarm->node, parent, link); + rb_insert_color(&alarm->node, &base->alarms); +} + +/** + * alarm_init - initialize an alarm + * @alarm: the alarm to be initialized + * @type: the alarm type to be used + * @function: alarm callback function + */ +void alarm_init(struct alarm *alarm, + enum android_alarm_type type, void (*function)(struct alarm *)) +{ + RB_CLEAR_NODE(&alarm->node); + alarm->type = type; + alarm->function = function; + + pr_alarm(FLOW, "created alarm, type %d, func %pF\n", type, function); +} + + +/** + * alarm_start_range - (re)start an alarm + * @alarm: the alarm to be added + * @start: earliest expiry time + * @end: expiry time + */ +void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end) +{ + unsigned long flags; + + spin_lock_irqsave(&alarm_slock, flags); + alarm->softexpires = start; + alarm->expires = end; + alarm_enqueue_locked(alarm); + spin_unlock_irqrestore(&alarm_slock, flags); +} + +/** + * alarm_try_to_cancel - try to deactivate an alarm + * @alarm: alarm to stop + * + * Returns: + * 0 when the alarm was not active + * 1 when the alarm was active + * -1 when the alarm may currently be excuting the callback function and + * cannot be stopped (it may also be inactive) + */ +int alarm_try_to_cancel(struct alarm *alarm) +{ + struct alarm_queue *base = &alarms[alarm->type]; + unsigned long flags; + bool first = false; + int ret = 0; + + spin_lock_irqsave(&alarm_slock, flags); + if (!RB_EMPTY_NODE(&alarm->node)) { + pr_alarm(FLOW, "canceled alarm, type %d, func %pF at %lld\n", + alarm->type, alarm->function, + ktime_to_ns(alarm->expires)); + ret = 1; + if (base->first == &alarm->node) { + base->first = rb_next(&alarm->node); + first = true; + } + rb_erase(&alarm->node, &base->alarms); + RB_CLEAR_NODE(&alarm->node); + if (first) + update_timer_locked(base, true); + } else + pr_alarm(FLOW, "tried to cancel alarm, type %d, func %pF\n", + alarm->type, alarm->function); + spin_unlock_irqrestore(&alarm_slock, flags); + if (!ret && hrtimer_callback_running(&base->timer)) + ret = -1; + return ret; +} + +/** + * alarm_cancel - cancel an alarm and wait for the handler to finish. + * @alarm: the alarm to be cancelled + * + * Returns: + * 0 when the alarm was not active + * 1 when the alarm was active + */ +int alarm_cancel(struct alarm *alarm) +{ + for (;;) { + int ret = alarm_try_to_cancel(alarm); + if (ret >= 0) + return ret; + cpu_relax(); + } +} + +/** + * alarm_set_rtc - set the kernel and rtc walltime + * @new_time: timespec value containing the new time + */ +int alarm_set_rtc(struct timespec new_time) +{ + int i; + int ret; + unsigned long flags; + struct rtc_time rtc_new_rtc_time; + struct timespec tmp_time; + + rtc_time_to_tm(new_time.tv_sec, &rtc_new_rtc_time); + + pr_alarm(TSET, "set rtc %ld %ld - rtc %02d:%02d:%02d %02d/%02d/%04d\n", + new_time.tv_sec, new_time.tv_nsec, + rtc_new_rtc_time.tm_hour, rtc_new_rtc_time.tm_min, + rtc_new_rtc_time.tm_sec, rtc_new_rtc_time.tm_mon + 1, + rtc_new_rtc_time.tm_mday, + rtc_new_rtc_time.tm_year + 1900); + + mutex_lock(&alarm_setrtc_mutex); + spin_lock_irqsave(&alarm_slock, flags); + wake_lock(&alarm_rtc_wake_lock); + getnstimeofday(&tmp_time); + 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))); + spin_unlock_irqrestore(&alarm_slock, flags); + ret = do_settimeofday(&new_time); + spin_lock_irqsave(&alarm_slock, flags); + for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) { + alarms[i].stopped = false; + update_timer_locked(&alarms[i], false); + } + spin_unlock_irqrestore(&alarm_slock, flags); + if (ret < 0) { + pr_alarm(ERROR, "alarm_set_rtc: Failed to set time\n"); + goto err; + } + if (!alarm_rtc_dev) { + pr_alarm(ERROR, + "alarm_set_rtc: no RTC, time will be lost on reboot\n"); + goto err; + } + ret = rtc_set_time(alarm_rtc_dev, &rtc_new_rtc_time); + if (ret < 0) + pr_alarm(ERROR, "alarm_set_rtc: " + "Failed to set RTC, time will be lost on reboot\n"); +err: + wake_unlock(&alarm_rtc_wake_lock); + mutex_unlock(&alarm_setrtc_mutex); + 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 + * + * returns the time in ktime_t format + */ +ktime_t alarm_get_elapsed_realtime(void) +{ + ktime_t now; + unsigned long flags; + struct alarm_queue *base = &alarms[ANDROID_ALARM_ELAPSED_REALTIME]; + + spin_lock_irqsave(&alarm_slock, flags); + now = base->stopped ? base->stopped_time : ktime_get_real(); + now = ktime_sub(now, base->delta); + spin_unlock_irqrestore(&alarm_slock, flags); + return now; +} + +static enum hrtimer_restart alarm_timer_triggered(struct hrtimer *timer) +{ + struct alarm_queue *base; + struct alarm *alarm; + unsigned long flags; + ktime_t now; + + spin_lock_irqsave(&alarm_slock, flags); + + base = container_of(timer, struct alarm_queue, timer); + now = base->stopped ? base->stopped_time : hrtimer_cb_get_time(timer); + now = ktime_sub(now, base->delta); + + pr_alarm(INT, "alarm_timer_triggered type %d at %lld\n", + base - alarms, ktime_to_ns(now)); + + while (base->first) { + alarm = container_of(base->first, struct alarm, node); + if (alarm->softexpires.tv64 > now.tv64) { + pr_alarm(FLOW, "don't call alarm, %pF, %lld (s %lld)\n", + alarm->function, ktime_to_ns(alarm->expires), + ktime_to_ns(alarm->softexpires)); + break; + } + base->first = rb_next(&alarm->node); + rb_erase(&alarm->node, &base->alarms); + RB_CLEAR_NODE(&alarm->node); + pr_alarm(CALL, "call alarm, type %d, func %pF, %lld (s %lld)\n", + alarm->type, alarm->function, + ktime_to_ns(alarm->expires), + ktime_to_ns(alarm->softexpires)); + spin_unlock_irqrestore(&alarm_slock, flags); + alarm->function(alarm); + spin_lock_irqsave(&alarm_slock, flags); + } + if (!base->first) + pr_alarm(FLOW, "no more alarms of type %d\n", base - alarms); + update_timer_locked(base, true); + spin_unlock_irqrestore(&alarm_slock, flags); + return HRTIMER_NORESTART; +} + +static void alarm_triggered_func(void *p) +{ + struct rtc_device *rtc = alarm_rtc_dev; + if (!(rtc->irq_data & RTC_AF)) + return; + pr_alarm(INT, "rtc alarm triggered\n"); + wake_lock_timeout(&alarm_rtc_wake_lock, 1 * HZ); +} + +static int alarm_suspend(struct platform_device *pdev, pm_message_t state) +{ + int err = 0; + unsigned long flags; + struct rtc_wkalrm rtc_alarm; + struct rtc_time rtc_current_rtc_time; + unsigned long rtc_current_time; + unsigned long rtc_alarm_time; + struct timespec rtc_delta; + struct timespec wall_time; + struct alarm_queue *wakeup_queue = NULL; + struct alarm_queue *tmp_queue = NULL; + + pr_alarm(SUSPEND, "alarm_suspend(%p, %d)\n", pdev, state.event); + + spin_lock_irqsave(&alarm_slock, flags); + suspended = true; + spin_unlock_irqrestore(&alarm_slock, flags); + + hrtimer_cancel(&alarms[ANDROID_ALARM_RTC_WAKEUP].timer); + hrtimer_cancel(&alarms[ + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].timer); + + tmp_queue = &alarms[ANDROID_ALARM_RTC_WAKEUP]; + if (tmp_queue->first) + wakeup_queue = tmp_queue; + tmp_queue = &alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP]; + if (tmp_queue->first && (!wakeup_queue || + hrtimer_get_expires(&tmp_queue->timer).tv64 < + hrtimer_get_expires(&wakeup_queue->timer).tv64)) + wakeup_queue = tmp_queue; + if (wakeup_queue) { + rtc_read_time(alarm_rtc_dev, &rtc_current_rtc_time); + getnstimeofday(&wall_time); + rtc_tm_to_time(&rtc_current_rtc_time, &rtc_current_time); + set_normalized_timespec(&rtc_delta, + wall_time.tv_sec - rtc_current_time, + wall_time.tv_nsec); + + rtc_alarm_time = timespec_sub(ktime_to_timespec( + hrtimer_get_expires(&wakeup_queue->timer)), + rtc_delta).tv_sec; + + rtc_time_to_tm(rtc_alarm_time, &rtc_alarm.time); + rtc_alarm.enabled = 1; + rtc_set_alarm(alarm_rtc_dev, &rtc_alarm); + rtc_read_time(alarm_rtc_dev, &rtc_current_rtc_time); + rtc_tm_to_time(&rtc_current_rtc_time, &rtc_current_time); + pr_alarm(SUSPEND, + "rtc alarm set at %ld, now %ld, rtc delta %ld.%09ld\n", + rtc_alarm_time, rtc_current_time, + rtc_delta.tv_sec, rtc_delta.tv_nsec); + if (rtc_current_time + 1 >= rtc_alarm_time) { + pr_alarm(SUSPEND, "alarm about to go off\n"); + memset(&rtc_alarm, 0, sizeof(rtc_alarm)); + rtc_alarm.enabled = 0; + rtc_set_alarm(alarm_rtc_dev, &rtc_alarm); + + spin_lock_irqsave(&alarm_slock, flags); + suspended = false; + wake_lock_timeout(&alarm_rtc_wake_lock, 2 * HZ); + update_timer_locked(&alarms[ANDROID_ALARM_RTC_WAKEUP], + false); + update_timer_locked(&alarms[ + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP], false); + err = -EBUSY; + spin_unlock_irqrestore(&alarm_slock, flags); + } + } + return err; +} + +static int alarm_resume(struct platform_device *pdev) +{ + struct rtc_wkalrm alarm; + unsigned long flags; + + pr_alarm(SUSPEND, "alarm_resume(%p)\n", pdev); + + memset(&alarm, 0, sizeof(alarm)); + alarm.enabled = 0; + rtc_set_alarm(alarm_rtc_dev, &alarm); + + spin_lock_irqsave(&alarm_slock, flags); + suspended = false; + update_timer_locked(&alarms[ANDROID_ALARM_RTC_WAKEUP], false); + update_timer_locked(&alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP], + false); + spin_unlock_irqrestore(&alarm_slock, flags); + + return 0; +} + +static struct rtc_task alarm_rtc_task = { + .func = alarm_triggered_func +}; + +static int rtc_alarm_add_device(struct device *dev, + struct class_interface *class_intf) +{ + int err; + struct rtc_device *rtc = to_rtc_device(dev); + + mutex_lock(&alarm_setrtc_mutex); + + if (alarm_rtc_dev) { + err = -EBUSY; + goto err1; + } + + alarm_platform_dev = + platform_device_register_simple("alarm", -1, NULL, 0); + if (IS_ERR(alarm_platform_dev)) { + err = PTR_ERR(alarm_platform_dev); + goto err2; + } + err = rtc_irq_register(rtc, &alarm_rtc_task); + if (err) + goto err3; + alarm_rtc_dev = rtc; + pr_alarm(INIT_STATUS, "using rtc device, %s, for alarms", rtc->name); + mutex_unlock(&alarm_setrtc_mutex); + + return 0; + +err3: + platform_device_unregister(alarm_platform_dev); +err2: +err1: + mutex_unlock(&alarm_setrtc_mutex); + return err; +} + +static void rtc_alarm_remove_device(struct device *dev, + struct class_interface *class_intf) +{ + if (dev == &alarm_rtc_dev->dev) { + pr_alarm(INIT_STATUS, "lost rtc device for alarms"); + rtc_irq_unregister(alarm_rtc_dev, &alarm_rtc_task); + platform_device_unregister(alarm_platform_dev); + alarm_rtc_dev = NULL; + } +} + +static struct class_interface rtc_alarm_interface = { + .add_dev = &rtc_alarm_add_device, + .remove_dev = &rtc_alarm_remove_device, +}; + +static struct platform_driver alarm_driver = { + .suspend = alarm_suspend, + .resume = alarm_resume, + .driver = { + .name = "alarm" + } +}; + +static int __init alarm_late_init(void) +{ + unsigned long flags; + struct timespec tmp_time, system_time; + + /* this needs to run after the rtc is read at boot */ + spin_lock_irqsave(&alarm_slock, flags); + /* We read the current rtc and system time so we can later calulate + * elasped realtime to be (boot_systemtime + rtc - boot_rtc) == + * (rtc - (boot_rtc - boot_systemtime)) + */ + getnstimeofday(&tmp_time); + ktime_get_ts(&system_time); + alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].delta = + alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta = + timespec_to_ktime(timespec_sub(tmp_time, system_time)); + + spin_unlock_irqrestore(&alarm_slock, flags); + return 0; +} + +static int __init alarm_driver_init(void) +{ + int err; + int i; + + for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) { + hrtimer_init(&alarms[i].timer, + CLOCK_REALTIME, HRTIMER_MODE_ABS); + alarms[i].timer.function = alarm_timer_triggered; + } + hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].timer, + CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + alarms[ANDROID_ALARM_SYSTEMTIME].timer.function = alarm_timer_triggered; + err = platform_driver_register(&alarm_driver); + if (err < 0) + goto err1; + wake_lock_init(&alarm_rtc_wake_lock, WAKE_LOCK_SUSPEND, "alarm_rtc"); + rtc_alarm_interface.class = rtc_class; + err = class_interface_register(&rtc_alarm_interface); + if (err < 0) + goto err2; + + return 0; + +err2: + wake_lock_destroy(&alarm_rtc_wake_lock); + platform_driver_unregister(&alarm_driver); +err1: + return err; +} + +static void __exit alarm_exit(void) +{ + class_interface_unregister(&rtc_alarm_interface); + wake_lock_destroy(&alarm_rtc_wake_lock); + platform_driver_unregister(&alarm_driver); +} + +late_initcall(alarm_late_init); +module_init(alarm_driver_init); +module_exit(alarm_exit); + diff --git a/drivers/rtc/hctosys.c b/drivers/rtc/hctosys.c index bc90b091f1954faecf087f2094c6094e31606da2..29735c23fe83b793121f69ac5d901e3a481bfe16 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 0000000000000000000000000000000000000000..c17e461be4089986fe87073a327245324e38c1b7 --- /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 0000000000000000000000000000000000000000..690bc398cd6c00aa6237fa00fb64b8dd1fef1330 --- /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-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c index d00bd24342a311555cb35f444296cddfb27bf408..e53374ec5fbcae8a2a46fbc8d682a8b15f6d5084 100644 --- a/drivers/rtc/rtc-pm8xxx.c +++ b/drivers/rtc/rtc-pm8xxx.c @@ -15,36 +15,31 @@ #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 +#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 PM8xxx_RTC_ENABLE BIT(7) +#define PM8xxx_RTC_ALARM_ENABLE BIT(1) +#define PM8xxx_RTC_ABORT_ENABLE BIT(0) -#define NUM_8_BIT_RTC_REGS 0x4 +#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. - * @rtc_base: address of rtc control register. - * @rtc_read_base: base address of read registers. - * @rtc_write_base: base address of write registers. - * @alarm_rw_base: base address of alarm registers. - * @ctrl_reg: rtc control register. - * @rtc_dev: device structure. - * @ctrl_reg_lock: spinlock protecting access to ctrl_reg. + * 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; @@ -62,8 +57,9 @@ struct pm8xxx_rtc { * 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 base, int count) { int i, rc; struct device *parent = rtc_dd->rtc_dev->parent; @@ -71,7 +67,7 @@ static int pm8xxx_read_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, for (i = 0; i < count; i++) { rc = pm8xxx_readb(parent, base + i, &rtc_val[i]); if (rc < 0) { - dev_err(rtc_dd->rtc_dev, "PMIC read failed\n"); + dev_err(rtc_dd->rtc_dev, "PM8xxx read failed\n"); return rc; } } @@ -80,7 +76,7 @@ static int pm8xxx_read_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, } static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, - int base, int count) + int base, int count) { int i, rc; struct device *parent = rtc_dd->rtc_dev->parent; @@ -88,7 +84,7 @@ static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, for (i = 0; i < count; i++) { rc = pm8xxx_writeb(parent, base + i, rtc_val[i]); if (rc < 0) { - dev_err(rtc_dd->rtc_dev, "PMIC write failed\n"); + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed\n"); return rc; } } @@ -96,6 +92,7 @@ static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, return 0; } + /* * Steps to write the RTC registers. * 1. Disable alarm if enabled. @@ -103,19 +100,20 @@ static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, * 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) +static int +pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) { - int rc, i; + int rc; unsigned long secs, irq_flags; - u8 value[NUM_8_BIT_RTC_REGS], reg = 0, alarm_enabled = 0, ctrl_reg; + 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); - for (i = 0; i < NUM_8_BIT_RTC_REGS; i++) { - value[i] = secs & 0xFF; - secs >>= 8; - } + 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); @@ -126,21 +124,20 @@ static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) alarm_enabled = 1; ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE; rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, - 1); + 1); if (rc < 0) { - dev_err(dev, "Write to RTC control register " - "failed\n"); + dev_err(dev, "PM8xxx write failed\n"); goto rtc_rw_fail; } - rtc_dd->ctrl_reg = ctrl_reg; } 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, "Write to RTC write data register failed\n"); + dev_err(dev, "PM8xxx write failed\n"); goto rtc_rw_fail; } @@ -148,14 +145,14 @@ static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) rc = pm8xxx_write_wrapper(rtc_dd, value + 1, rtc_dd->rtc_write_base + 1, 3); if (rc < 0) { - dev_err(dev, "Write to RTC write data register failed\n"); + 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 write data register failed\n"); + dev_err(dev, "Write to RTC register failed\n"); goto rtc_rw_fail; } @@ -164,13 +161,13 @@ static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1); if (rc < 0) { - dev_err(dev, "Write to RTC control register " - "failed\n"); + dev_err(dev, "PM8xxx write failed\n"); goto rtc_rw_fail; } - rtc_dd->ctrl_reg = ctrl_reg; } + rtc_dd->ctrl_reg = ctrl_reg; + rtc_rw_fail: if (alarm_enabled) spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); @@ -178,17 +175,18 @@ static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) return rc; } -static int pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) +static int +pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) { int rc; - u8 value[NUM_8_BIT_RTC_REGS], reg; + 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 read data register failed\n"); + dev_err(dev, "RTC time read failed\n"); return rc; } @@ -198,7 +196,7 @@ static int pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) */ rc = pm8xxx_read_wrapper(rtc_dd, ®, rtc_dd->rtc_read_base, 1); if (rc < 0) { - dev_err(dev, "RTC read data register failed\n"); + dev_err(dev, "PM8xxx read failed\n"); return rc; } @@ -206,76 +204,96 @@ static int pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) rc = pm8xxx_read_wrapper(rtc_dd, value, rtc_dd->rtc_read_base, NUM_8_BIT_RTC_REGS); if (rc < 0) { - dev_err(dev, "RTC read data register failed\n"); + dev_err(dev, "RTC time read failed\n"); return rc; } } - secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); + 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 RTC\n"); + 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); + 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) +static int +pm8xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) { - int rc, i; - u8 value[NUM_8_BIT_RTC_REGS], ctrl_reg; - unsigned long secs, irq_flags; + 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); - for (i = 0; i < NUM_8_BIT_RTC_REGS; i++) { - value[i] = secs & 0xFF; - secs >>= 8; + /* + * 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 register failed\n"); + 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 = (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, "Write to RTC control register failed\n"); + 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); + 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) +static int +pm8xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) { int rc; - u8 value[NUM_8_BIT_RTC_REGS]; + u8 value[4]; unsigned long secs; struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); @@ -286,25 +304,28 @@ static int pm8xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) return rc; } - secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); + 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 alarm time read from RTC\n"); + 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_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 enable) + +static int +pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) { int rc; unsigned long irq_flags; @@ -313,12 +334,12 @@ static int pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags); ctrl_reg = rtc_dd->ctrl_reg; - ctrl_reg = (enable) ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) : + 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, "Write to RTC control register failed\n"); + dev_err(dev, "PM8xxx write failed\n"); goto rtc_rw_fail; } @@ -354,8 +375,7 @@ static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id) 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, "Write to RTC control register " - "failed\n"); + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n"); goto rtc_alarm_handled; } @@ -366,8 +386,7 @@ static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id) 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, "RTC Alarm control register read " - "failed\n"); + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n"); goto rtc_alarm_handled; } @@ -375,8 +394,7 @@ static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id) 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, "Write to RTC Alarm control register" - " failed\n"); + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n"); rtc_alarm_handled: return IRQ_HANDLED; @@ -390,7 +408,7 @@ static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev) struct pm8xxx_rtc *rtc_dd; struct resource *rtc_resource; const struct pm8xxx_rtc_platform_data *pdata = - dev_get_platdata(&pdev->dev); + pdev->dev.platform_data; if (pdata != NULL) rtc_write_enable = pdata->rtc_write_enable; @@ -401,7 +419,7 @@ static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev) return -ENOMEM; } - /* Initialise spinlock to protect RTC control register */ + /* 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); @@ -426,12 +444,12 @@ static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev) 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; + 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, "RTC control register read failed!\n"); + dev_err(&pdev->dev, "PM8xxx read failed!\n"); goto fail_rtc_enable; } @@ -440,12 +458,19 @@ static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev) rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1); if (rc < 0) { - dev_err(&pdev->dev, "Write to RTC control register " - "failed\n"); + dev_err(&pdev->dev, "PM8xxx write failed!\n"); goto fail_rtc_enable; } } + /* Enable abort enable feature */ + ctrl_reg |= PM8xxx_RTC_ABORT_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; @@ -485,20 +510,7 @@ static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev) return rc; } -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; -} - -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM static int pm8xxx_rtc_resume(struct device *dev) { struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); @@ -518,21 +530,87 @@ static int pm8xxx_rtc_suspend(struct device *dev) 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 void pm8xxx_rtc_shutdown(struct platform_device *pdev) +{ + u8 value[4] = {0, 0, 0, 0}; + u8 reg; + int rc; + unsigned long irq_flags; + bool rtc_alarm_powerup = false; + struct pm8xxx_rtc *rtc_dd = platform_get_drvdata(pdev); + struct pm8xxx_rtc_platform_data *pdata = pdev->dev.platform_data; + + if (pdata != NULL) + rtc_alarm_powerup = pdata->rtc_alarm_powerup; + + if (!rtc_alarm_powerup) { + + spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags); + dev_dbg(&pdev->dev, "Disabling alarm interrupts\n"); + + /* Disable RTC alarms */ + reg = rtc_dd->ctrl_reg; + reg &= ~PM8xxx_RTC_ALARM_ENABLE; + rc = pm8xxx_write_wrapper(rtc_dd, ®, rtc_dd->rtc_base, 1); + if (rc < 0) { + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed\n"); + goto fail_alarm_disable; + } + + /* Clear Alarm register */ + rc = pm8xxx_write_wrapper(rtc_dd, value, + rtc_dd->alarm_rw_base, NUM_8_BIT_RTC_REGS); + if (rc < 0) + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed\n"); -static SIMPLE_DEV_PM_OPS(pm8xxx_rtc_pm_ops, pm8xxx_rtc_suspend, pm8xxx_rtc_resume); +fail_alarm_disable: + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + } +} static struct platform_driver pm8xxx_rtc_driver = { .probe = pm8xxx_rtc_probe, .remove = __devexit_p(pm8xxx_rtc_remove), + .shutdown = pm8xxx_rtc_shutdown, .driver = { .name = PM8XXX_RTC_DEV_NAME, .owner = THIS_MODULE, +#ifdef CONFIG_PM .pm = &pm8xxx_rtc_pm_ops, +#endif }, }; -module_platform_driver(pm8xxx_rtc_driver); +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"); diff --git a/drivers/scsi/isci/phy.c b/drivers/scsi/isci/phy.c index fab3586840b5d174148cccd1ba5cde6dc1d883fc..ef9c77dbb3042e06dba5ee9827064f1df7413697 100644 --- a/drivers/scsi/isci/phy.c +++ b/drivers/scsi/isci/phy.c @@ -293,6 +293,18 @@ sci_phy_link_layer_initialization(struct isci_phy *iphy, writel(sp_timeouts, &llr->sas_phy_timeouts); + sp_timeouts = readl(&iphy->link_layer_registers->sas_phy_timeouts); + + /* Clear the default 0x36 (54us) RATE_CHANGE timeout value. */ + sp_timeouts &= ~SCU_SAS_PHYTOV_GEN_VAL(RATE_CHANGE, 0xFF); + + /* Set RATE_CHANGE timeout value to 0x3B (59us). This ensures SCU can + * lock with 3Gb drive when SCU max rate is set to 1.5Gb. + */ + sp_timeouts |= SCU_SAS_PHYTOV_GEN_VAL(RATE_CHANGE, 0x3B); + + writel(sp_timeouts, &iphy->link_layer_registers->sas_phy_timeouts); + if (is_a2(ihost->pdev)) { /* Program the max ARB time for the PHY to 700us so we * inter-operate with the PMC expander which shuts down diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..a6a068d3f4915a3998d0672277f86afd183bd1f7 --- /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 0000000000000000000000000000000000000000..436822d1f1e627010ef827d9083bd1c40fcac932 --- /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 0000000000000000000000000000000000000000..fa9d1dfe3f08e94663872fe9850178aed5b21cfd --- /dev/null +++ b/drivers/slimbus/slim-msm-ctrl.c @@ -0,0 +1,2270 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +/* 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 +#define MSM_SLIM_AUTOSUSPEND MSEC_PER_SEC + +/* + * 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 MSM_MAX_NSATS 2 +#define MSM_MAX_SATCH 32 + +#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 +#define QC_MSM_DEVS 5 + +#define PGD_THIS_EE(r, v) ((v) ? PGD_THIS_EE_V2(r) : PGD_THIS_EE_V1(r)) +#define PGD_PORT(r, p, v) ((v) ? PGD_PORT_V2(r, p) : PGD_PORT_V1(r, p)) +#define CFG_PORT(r, v) ((v) ? CFG_PORT_V2(r) : CFG_PORT_V1(r)) + +#define PGD_THIS_EE_V2(r) (dev->base + (r ## _V2) + (dev->ee * 0x1000)) +#define PGD_PORT_V2(r, p) (dev->base + (r ## _V2) + ((p) * 0x1000)) +#define CFG_PORT_V2(r) ((r ## _V2)) +/* Component registers */ +enum comp_reg_v2 { + COMP_CFG_V2 = 4, + COMP_TRUST_CFG_V2 = 0x3000, +}; + +/* Manager PGD registers */ +enum pgd_reg_v2 { + PGD_CFG_V2 = 0x800, + PGD_STAT_V2 = 0x804, + PGD_INT_EN_V2 = 0x810, + PGD_INT_STAT_V2 = 0x814, + PGD_INT_CLR_V2 = 0x818, + PGD_OWN_EEn_V2 = 0x300C, + PGD_PORT_INT_EN_EEn_V2 = 0x5000, + PGD_PORT_INT_ST_EEn_V2 = 0x5004, + PGD_PORT_INT_CL_EEn_V2 = 0x5008, + PGD_PORT_CFGn_V2 = 0x14000, + PGD_PORT_STATn_V2 = 0x14004, + PGD_PORT_PARAMn_V2 = 0x14008, + PGD_PORT_BLKn_V2 = 0x1400C, + PGD_PORT_TRANn_V2 = 0x14010, + PGD_PORT_MCHANn_V2 = 0x14014, + PGD_PORT_PSHPLLn_V2 = 0x14018, + PGD_PORT_PC_CFGn_V2 = 0x8000, + PGD_PORT_PC_VALn_V2 = 0x8004, + PGD_PORT_PC_VFR_TSn_V2 = 0x8008, + PGD_PORT_PC_VFR_STn_V2 = 0x800C, + PGD_PORT_PC_VFR_CLn_V2 = 0x8010, + PGD_IE_STAT_V2 = 0x820, + PGD_VE_STAT_V2 = 0x830, +}; + +#define PGD_THIS_EE_V1(r) (dev->base + (r ## _V1) + (dev->ee * 16)) +#define PGD_PORT_V1(r, p) (dev->base + (r ## _V1) + ((p) * 32)) +#define CFG_PORT_V1(r) ((r ## _V1)) +/* Component registers */ +enum comp_reg_v1 { + COMP_CFG_V1 = 0, + COMP_TRUST_CFG_V1 = 0x14, +}; + +/* Manager PGD registers */ +enum pgd_reg_v1 { + PGD_CFG_V1 = 0x1000, + PGD_STAT_V1 = 0x1004, + PGD_INT_EN_V1 = 0x1010, + PGD_INT_STAT_V1 = 0x1014, + PGD_INT_CLR_V1 = 0x1018, + PGD_OWN_EEn_V1 = 0x1020, + PGD_PORT_INT_EN_EEn_V1 = 0x1030, + PGD_PORT_INT_ST_EEn_V1 = 0x1034, + PGD_PORT_INT_CL_EEn_V1 = 0x1038, + PGD_PORT_CFGn_V1 = 0x1080, + PGD_PORT_STATn_V1 = 0x1084, + PGD_PORT_PARAMn_V1 = 0x1088, + PGD_PORT_BLKn_V1 = 0x108C, + PGD_PORT_TRANn_V1 = 0x1090, + PGD_PORT_MCHANn_V1 = 0x1094, + PGD_PORT_PSHPLLn_V1 = 0x1098, + PGD_PORT_PC_CFGn_V1 = 0x1600, + PGD_PORT_PC_VALn_V1 = 0x1604, + PGD_PORT_PC_VFR_TSn_V1 = 0x1608, + PGD_PORT_PC_VFR_STn_V1 = 0x160C, + PGD_PORT_PC_VFR_CLn_V1 = 0x1610, + PGD_IE_STAT_V1 = 0x1700, + PGD_VE_STAT_V1 = 0x1710, +}; + +/* 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, +}; + +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, +}; + +enum msm_ctrl_state { + MSM_CTRL_AWAKE, + MSM_CTRL_SLEEPING, + MSM_CTRL_ASLEEP, +}; + +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; + struct resource *slew_mem; + 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[MSM_MAX_NSATS]; + 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 pipe_b; + struct completion reconf; + bool reconf_busy; + bool chan_active; + enum msm_ctrl_state state; + int nsats; + u32 ver; +}; + +struct msm_sat_chan { + u8 chan; + u16 chanh; + int req_rem; + int req_def; +}; + +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]; + struct msm_sat_chan *satch; + u8 nsatch; + bool sent_capability; + bool pending_reconf; + bool pending_capability; + int shead; + int stail; + spinlock_t lock; +}; + +static struct msm_slim_sat *msm_slim_alloc_sat(struct msm_slim_ctrl *dev); + +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 int msm_slim_get_ctrl(struct msm_slim_ctrl *dev) +{ +#ifdef CONFIG_PM_RUNTIME + int ref = 0; + int ret = pm_runtime_get_sync(dev->dev); + if (ret >= 0) { + ref = atomic_read(&dev->dev->power.usage_count); + if (ref <= 0) { + dev_err(dev->dev, "reference count -ve:%d", ref); + ret = -ENODEV; + } + } + return ret; +#else + return -ENODEV; +#endif +} +static void msm_slim_put_ctrl(struct msm_slim_ctrl *dev) +{ +#ifdef CONFIG_PM_RUNTIME + int ref; + pm_runtime_mark_last_busy(dev->dev); + ref = atomic_read(&dev->dev->power.usage_count); + if (ref <= 0) + dev_err(dev->dev, "reference count mismatch:%d", ref); + else + pm_runtime_put(dev->dev); +#endif +} + +static struct msm_slim_sat *addr_to_sat(struct msm_slim_ctrl *dev, u8 laddr) +{ + struct msm_slim_sat *sat = NULL; + int i = 0; + while (!sat && i < dev->nsats) { + if (laddr == dev->satd[i]->satcl.laddr) + sat = dev->satd[i]; + i++; + } + return sat; +} + +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) { + u8 laddr = (u8)((rx_buf[0] >> 16) & 0xFF); + struct msm_slim_sat *sat = addr_to_sat(dev, laddr); + if (sat) + msm_sat_enqueue(sat, rx_buf, len); + else + dev_err(dev->dev, "unknown sat:%d message", + laddr); + writel_relaxed(MGR_INT_RX_MSG_RCVD, + dev->base + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through before + * queuing work + */ + mb(); + if (sat) + 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); + 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 if (mc == SLIM_MSG_MC_REPORT_INFORMATION) { + u8 *buf = (u8 *)rx_buf; + u8 l_addr = buf[2]; + u16 ele = (u16)buf[4] << 4; + ele |= ((buf[3] & 0xf0) >> 4); + dev_err(dev->dev, "Slim-dev:%d report inf element:0x%x", + l_addr, ele); + for (i = 0; i < len - 5; i++) + dev_err(dev->dev, "offset:0x%x:bit mask:%x", + i, buf[i+5]); + writel_relaxed(MGR_INT_RX_MSG_RCVD, dev->base + + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through + * before exiting + */ + mb(); + } 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(PGD_THIS_EE(PGD_PORT_INT_ST_EEn, dev->ver)); + if (pstat != 0) { + int i = 0; + for (i = dev->pipe_b; i < MSM_SLIM_NPORTS; i++) { + if (pstat & 1 << i) { + u32 val = readl_relaxed(PGD_PORT(PGD_PORT_STATn, + i, dev->ver)); + 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, PGD_THIS_EE(PGD_PORT_INT_CL_EEn, + dev->ver)); + } + /* + * 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(PGD_THIS_EE(PGD_PORT_INT_EN_EEn, + dev->ver)); + writel_relaxed(set_cfg, PGD_PORT(PGD_PORT_CFGn, pn, dev->ver)); + writel_relaxed(DEF_BLKSZ, PGD_PORT(PGD_PORT_BLKn, pn, dev->ver)); + writel_relaxed(DEF_TRANSZ, PGD_PORT(PGD_PORT_TRANn, pn, dev->ver)); + writel_relaxed((int_port | 1 << pn) , PGD_THIS_EE(PGD_PORT_INT_EN_EEn, + dev->ver)); + /* 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(PGD_PORT(PGD_PORT_STATn, (pn + dev->pipe_b), + dev->ver)); + 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; + int msgv = -1; + u8 la = txn->la; + u8 mc = (u8)(txn->mc & 0xFF); + /* + * Voting for runtime PM: Slimbus has 2 possible use cases: + * 1. messaging + * 2. Data channels + * Messaging case goes through messaging slots and data channels + * use their own slots + * This "get" votes for messaging bandwidth + */ + if (!(txn->mc & SLIM_MSG_CLK_PAUSE_SEQ_FLG)) + msgv = msm_slim_get_ctrl(dev); + mutex_lock(&dev->tx_lock); + if (dev->state == MSM_CTRL_ASLEEP || + ((!(txn->mc & SLIM_MSG_CLK_PAUSE_SEQ_FLG)) && + dev->state == MSM_CTRL_SLEEPING)) { + dev_err(dev->dev, "runtime or system PM suspended state"); + mutex_unlock(&dev->tx_lock); + if (msgv >= 0) + msm_slim_put_ctrl(dev); + return -EBUSY; + } + if (txn->mt == SLIM_MSG_MT_CORE && + mc == SLIM_MSG_MC_BEGIN_RECONFIGURATION) { + if (dev->reconf_busy) { + wait_for_completion(&dev->reconf); + dev->reconf_busy = false; + } + /* This "get" votes for data channels */ + if (dev->ctrl.sched.usedslots != 0 && + !dev->chan_active) { + int chv = msm_slim_get_ctrl(dev); + if (chv >= 0) + dev->chan_active = true; + } + } + 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); + if (msgv >= 0) + msm_slim_put_ctrl(dev); + return -EPROTONOSUPPORT; + } + if (txn->mt == SLIM_MSG_MT_CORE && txn->la == 0xFF && + (mc == SLIM_MSG_MC_CONNECT_SOURCE || + mc == SLIM_MSG_MC_CONNECT_SINK || + 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, mc, 0, la); + else + *pbuf = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, 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) && + ((mc >= SLIM_MSG_MC_REQUEST_INFORMATION && + mc <= SLIM_MSG_MC_REPORT_INFORMATION) || + (mc >= SLIM_MSG_MC_REQUEST_VALUE && + 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 && + (mc == SLIM_MSG_MC_CONNECT_SOURCE || + mc == SLIM_MSG_MC_CONNECT_SINK || + mc == SLIM_MSG_MC_DISCONNECT_PORT)) { + if (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); + if (msgv >= 0) + msm_slim_put_ctrl(dev); + return 0; + } + if (dev->err) { + dev_err(dev->dev, "pipe-port connect err:%d", dev->err); + mutex_unlock(&dev->tx_lock); + if (msgv >= 0) + msm_slim_put_ctrl(dev); + return dev->err; + } + *(puc) = *(puc) + dev->pipe_b; + } + if (txn->mt == SLIM_MSG_MT_CORE && + 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 (mc == SLIM_MSG_MC_RECONFIGURE_NOW) { + if ((txn->mc == (SLIM_MSG_MC_RECONFIGURE_NOW | + SLIM_MSG_CLK_PAUSE_SEQ_FLG)) && + timeout) { + timeout = wait_for_completion_timeout(&dev->reconf, HZ); + dev->reconf_busy = false; + if (timeout) { + clk_disable_unprepare(dev->rclk); + disable_irq(dev->irq); + } + } + if ((txn->mc == (SLIM_MSG_MC_RECONFIGURE_NOW | + SLIM_MSG_CLK_PAUSE_SEQ_FLG)) && + !timeout) { + dev->reconf_busy = false; + dev_err(dev->dev, "clock pause failed"); + mutex_unlock(&dev->tx_lock); + return -ETIMEDOUT; + } + if (txn->mt == SLIM_MSG_MT_CORE && + txn->mc == SLIM_MSG_MC_RECONFIGURE_NOW) { + if (dev->ctrl.sched.usedslots == 0 && + dev->chan_active) { + dev->chan_active = false; + msm_slim_put_ctrl(dev); + } + } + } + mutex_unlock(&dev->tx_lock); + if (msgv >= 0) + msm_slim_put_ctrl(dev); + + if (!timeout) + dev_err(dev->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, + txn->mt); + + 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_clk_pause_wakeup(struct slim_controller *ctrl) +{ + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + enable_irq(dev->irq); + clk_prepare_enable(dev->rclk); + writel_relaxed(1, dev->base + FRM_WAKEUP); + /* Make sure framer wakeup write goes through before exiting function */ + mb(); + /* + * Workaround: Currently, slave is reporting lost-sync messages + * after slimbus comes out of clock pause. + * Transaction with slave fail before slave reports that message + * Give some time for that report to come + * Slimbus wakes up in clock gear 10 at 24.576MHz. With each superframe + * being 250 usecs, we wait for 20 superframes here to ensure + * we get the message + */ + usleep_range(5000, 5000); + return 0; +} + +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) { + for (i = 0; i < sat->nsatch; i++) { + if (buf[5] == sat->satch[i].chan) + break; + } + if (i >= sat->nsatch) + return -ENOTCONN; + oper = ((buf[3] & 0xC0) >> 6); + /* part of grp. activating/removing 1 will take care of rest */ + ret = slim_control_ch(&sat->satcl, sat->satch[i].chanh, oper, + false); + if (!ret) { + for (i = 5; i < len; i++) { + int j; + for (j = 0; j < sat->nsatch; j++) { + if (buf[i] == sat->satch[j].chan) { + if (oper == SLIM_CH_REMOVE) + sat->satch[j].req_rem++; + else + sat->satch[j].req_def++; + break; + } + } + } + } + } else { + u16 chh[40]; + struct slim_ch prop; + u32 exp; + u8 coeff, cc; + u8 prrate = buf[6]; + if (len <= 8) + return -EINVAL; + for (i = 8; i < len; i++) { + int j = 0; + for (j = 0; j < sat->nsatch; j++) { + if (sat->satch[j].chan == buf[i]) { + chh[i - 8] = sat->satch[j].chanh; + break; + } + } + if (j < sat->nsatch) { + u16 dummy; + ret = slim_query_ch(&sat->satcl, buf[i], + &dummy); + if (ret) + return ret; + if (mc == SLIM_USR_MC_DEF_ACT_CHAN) + sat->satch[j].req_def++; + continue; + } + if (sat->nsatch >= MSM_MAX_SATCH) + return -EXFULL; + ret = slim_query_ch(&sat->satcl, buf[i], &chh[i - 8]); + if (ret) + return ret; + sat->satch[j].chan = buf[i]; + sat->satch[j].chanh = chh[i - 8]; + if (mc == SLIM_USR_MC_DEF_ACT_CHAN) + sat->satch[j].req_def++; + sat->nsatch++; + } + 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, &chh[0]); + else + ret = slim_define_ch(&sat->satcl, &prop, + &chh[0], 1, false, NULL); + dev_dbg(dev->dev, "define sat grp returned:%d", ret); + if (ret) + return 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, + chh[0], + 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; + if (!ret && !pm_runtime_enabled(dev->dev) && + laddr == (QC_MSM_DEVS - 1)) + pm_runtime_enable(dev->dev); + + if (!ret && msm_is_sat_dev(e_addr)) { + struct msm_slim_sat *sat = addr_to_sat(dev, + laddr); + if (!sat) + sat = msm_slim_alloc_sat(dev); + if (!sat) + return; + + sat->satcl.laddr = laddr; + msm_sat_enqueue(sat, (u32 *)buf, len); + queue_work(sat->wq, &sat->wd); + } + } 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); + pm_runtime_mark_last_busy(dev->dev); + } else if (mc == SLIM_MSG_MC_REPORT_INFORMATION) { + u8 l_addr = buf[2]; + u16 ele = (u16)buf[4] << 4; + ele |= ((buf[3] & 0xf0) >> 4); + dev_err(dev->dev, "Slim-dev:%d report inf element:0x%x", + l_addr, ele); + for (i = 0; i < len - 5; i++) + dev_err(dev->dev, "offset:0x%x:bit mask:%x", + i, buf[i+5]); + } 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; + u8 len, mc, mt; + u32 bw_sl; + int ret = 0; + int satv = -1; + bool gen_ack = false; + u8 tid; + u8 wbuf[8]; + int i; + 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]; + + if (pm_runtime_enabled(dev->dev)) { + satv = msm_slim_get_ctrl(dev); + if (satv >= 0) + sat->pending_capability = true; + } + slim_assign_laddr(&dev->ctrl, e_addr, 6, &laddr); + sat->satcl.laddr = laddr; + /* + * Since capability message is already sent, present + * message will indicate subsystem hosting this + * satellite has restarted. + * Remove all active channels of this satellite + * when this is detected + */ + if (sat->sent_capability) { + for (i = 0; i < sat->nsatch; i++) { + enum slim_ch_state chs = + slim_get_ch_state(&sat->satcl, + sat->satch[i].chanh); + pr_err("Slim-SSR, sat:%d, rm chan:%d", + laddr, + sat->satch[i].chan); + if (chs == SLIM_CH_ACTIVE) + slim_control_ch(&sat->satcl, + sat->satch[i].chanh, + SLIM_CH_REMOVE, true); + } + } + } else if (mt != SLIM_MSG_MT_CORE && + mc != SLIM_MSG_MC_REPORT_PRESENT) { + satv = msm_slim_get_ctrl(dev); + } + switch (mc) { + case SLIM_MSG_MC_REPORT_PRESENT: + /* Remove runtime_pm vote once satellite acks */ + if (mt != SLIM_MSG_MT_CORE) { + if (pm_runtime_enabled(dev->dev) && + sat->pending_capability) { + msm_slim_put_ctrl(dev); + sat->pending_capability = false; + } + continue; + } + /* send a Manager capability msg */ + if (sat->sent_capability) { + if (mt == SLIM_MSG_MT_CORE) + goto send_capability; + else + continue; + } + ret = slim_add_device(&dev->ctrl, &sat->satcl); + if (ret) { + dev_err(dev->dev, + "Satellite-init failed"); + continue; + } + /* Satellite-channels */ + sat->satch = kzalloc(MSM_MAX_SATCH * + sizeof(struct msm_sat_chan), + GFP_KERNEL); +send_capability: + 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); + } + if (!sat->pending_reconf) { + int chv = msm_slim_get_ctrl(dev); + if (chv >= 0) + sat->pending_reconf = true; + } + break; + case SLIM_USR_MC_RECONFIG_NOW: + tid = buf[3]; + gen_ack = true; + ret = slim_reconfigure_now(&sat->satcl); + for (i = 0; i < sat->nsatch; i++) { + struct msm_sat_chan *sch = &sat->satch[i]; + if (sch->req_rem) { + if (!ret) + slim_dealloc_ch(&sat->satcl, + sch->chanh); + sch->req_rem--; + } else if (sch->req_def) { + if (ret) + slim_dealloc_ch(&sat->satcl, + sch->chanh); + sch->req_def--; + } + } + if (sat->pending_reconf) { + msm_slim_put_ctrl(dev); + sat->pending_reconf = false; + } + 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) { + if (mc != SLIM_MSG_MC_REPORT_PRESENT && satv >= 0) + msm_slim_put_ctrl(dev); + 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); + if (satv >= 0) + msm_slim_put_ctrl(dev); + } +} + +static struct msm_slim_sat *msm_slim_alloc_sat(struct msm_slim_ctrl *dev) +{ + struct msm_slim_sat *sat; + char *name; + if (dev->nsats >= MSM_MAX_NSATS) + return NULL; + + sat = kzalloc(sizeof(struct msm_slim_sat), GFP_KERNEL); + if (!sat) { + dev_err(dev->dev, "no memory for satellite"); + return NULL; + } + name = kzalloc(SLIMBUS_NAME_SIZE, GFP_KERNEL); + if (!name) { + dev_err(dev->dev, "no memory for satellite name"); + kfree(sat); + return NULL; + } + dev->satd[dev->nsats] = sat; + sat->dev = dev; + snprintf(name, SLIMBUS_NAME_SIZE, "msm_sat%d", dev->nsats); + sat->satcl.name = name; + spin_lock_init(&sat->lock); + INIT_WORK(&sat->wd, slim_sat_rxprocess); + sat->wq = create_singlethread_workqueue(sat->satcl.name); + if (!sat->wq) { + kfree(name); + kfree(sat); + return NULL; + } + /* + * Both sats will be allocated from RX thread and RX thread will + * process messages sequentially. No synchronization necessary + */ + dev->nsats++; + return sat; +} + +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) { + u8 laddr; + laddr = (u8)((buffer[0] >> 16) & 0xff); + sat = addr_to_sat(dev, laddr); + } + } else if ((index * 4) >= msg_len) { + index = 0; + 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 */ + + init_completion(notify); + if (!dev->use_rx_msgqs) + goto rx_thread_create; + + /* 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; + } + } + +rx_thread_create: + /* 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"); + /* Tear-down BAMs or return? */ + if (!dev->use_rx_msgqs) + return -EIO; + else + 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: + dev->use_rx_msgqs = 0; + 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, + }, + }, + }; + + if (!dev->use_rx_msgqs) + goto init_rx_msgq; + + 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, "disabling BAM: reg-bam failed 0x%x\n", ret); + dev->use_rx_msgqs = 0; + goto init_rx_msgq; + } + dev->bam.hdl = bam_handle; + dev_dbg(dev->dev, "SLIM BAM registered, handle = 0x%x\n", bam_handle); + +init_rx_msgq: + ret = msm_slim_init_rx_msgq(dev); + if (ret) + dev_err(dev->dev, "msm_slim_init_rx_msgq failed 0x%x\n", ret); + if (!dev->use_rx_msgqs && bam_handle) { + 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 void msm_slim_prg_slew(struct platform_device *pdev, + struct msm_slim_ctrl *dev) +{ + struct resource *slew_io; + void __iomem *slew_reg; + /* SLEW RATE register for this slimbus */ + dev->slew_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "slimbus_slew_reg"); + if (!dev->slew_mem) { + dev_dbg(&pdev->dev, "no slimbus slew resource\n"); + return; + } + slew_io = request_mem_region(dev->slew_mem->start, + resource_size(dev->slew_mem), pdev->name); + if (!slew_io) { + dev_dbg(&pdev->dev, "slimbus-slew mem claimed\n"); + dev->slew_mem = NULL; + return; + } + + slew_reg = ioremap(dev->slew_mem->start, resource_size(dev->slew_mem)); + if (!slew_reg) { + dev_dbg(dev->dev, "slew register mapping failed"); + release_mem_region(dev->slew_mem->start, + resource_size(dev->slew_mem)); + dev->slew_mem = NULL; + return; + } + writel_relaxed(1, slew_reg); + /* Make sure slimbus-slew rate enabling goes through */ + wmb(); + iounmap(slew_reg); +} + +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; + } + if (pdev->dev.of_node) { + + ret = of_property_read_u32(pdev->dev.of_node, "cell-index", + &dev->ctrl.nr); + if (ret) { + dev_err(&pdev->dev, "Cell index not specified:%d", ret); + goto err_of_init_failed; + } + /* Optional properties */ + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,min-clk-gear", &dev->ctrl.min_cg); + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,max-clk-gear", &dev->ctrl.max_cg); + pr_err("min_cg:%d, max_cg:%d, ret:%d", dev->ctrl.min_cg, + dev->ctrl.max_cg, ret); + } else { + 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.wakeup = msm_clk_pause_wakeup; + 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->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; + dev->ctrl.dev.parent = &pdev->dev; + dev->ctrl.dev.of_node = pdev->dev.of_node; + + 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; + } + + msm_slim_prg_slew(pdev, dev); + + /* Register with framework before enabling frame, clock */ + ret = slim_add_numbered_controller(&dev->ctrl); + if (ret) { + dev_err(dev->dev, "error adding controller\n"); + goto err_ctrl_failed; + } + + + dev->rclk = clk_get(dev->dev, "core_clk"); + if (!dev->rclk) { + dev_err(dev->dev, "slimbus clock not found"); + goto err_clk_get_failed; + } + clk_set_rate(dev->rclk, SLIM_ROOT_FREQ); + clk_prepare_enable(dev->rclk); + + dev->ver = readl_relaxed(dev->base); + /* Version info in 16 MSbits */ + dev->ver >>= 16; + /* Component register initialization */ + writel_relaxed(1, dev->base + CFG_PORT(COMP_CFG, dev->ver)); + writel_relaxed((EE_MGR_RSC_GRP | EE_NGD_2 | EE_NGD_1), + dev->base + CFG_PORT(COMP_TRUST_CFG, dev->ver)); + + /* + * 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((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 + CFG_PORT(PGD_CFG, dev->ver)); + writel_relaxed(0x3F<<17, dev->base + CFG_PORT(PGD_OWN_EEn, dev->ver) + + (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 + CFG_PORT(COMP_CFG, dev->ver)); + /* + * Make sure that all writes have gone through before exiting this + * function + */ + mb(); + if (pdev->dev.of_node) + of_register_slim_devices(&dev->ctrl); + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, MSM_SLIM_AUTOSUSPEND); + pm_runtime_set_active(&pdev->dev); + + dev_dbg(dev->dev, "MSM SB controller is up!\n"); + return 0; + +err_ctrl_failed: + writel_relaxed(0, dev->base + CFG_PORT(COMP_CFG, dev->ver)); +err_clk_get_failed: + kfree(dev->satd); +err_request_irq_failed: + msm_slim_sps_exit(dev); +err_sps_init_failed: +err_of_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 resource *slew_mem = dev->slew_mem; + int i; + for (i = 0; i < dev->nsats; i++) { + struct msm_slim_sat *sat = dev->satd[i]; + int j; + for (j = 0; j < sat->nsatch; j++) + slim_dealloc_ch(&sat->satcl, sat->satch[j].chanh); + slim_remove_device(&sat->satcl); + kfree(sat->satch); + destroy_workqueue(sat->wq); + kfree(sat->satcl.name); + kfree(sat); + } + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + free_irq(dev->irq, dev); + slim_del_controller(&dev->ctrl); + 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"); + if (bam_mem) + release_mem_region(bam_mem->start, resource_size(bam_mem)); + if (slew_mem) + release_mem_region(slew_mem->start, resource_size(slew_mem)); + slim_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "slimbus_physical"); + if (slim_mem) + release_mem_region(slim_mem->start, resource_size(slim_mem)); + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int msm_slim_runtime_idle(struct device *device) +{ + dev_dbg(device, "pm_runtime: idle...\n"); + pm_request_autosuspend(device); + return -EAGAIN; +} +#endif + +/* + * If PM_RUNTIME is not defined, these 2 functions become helper + * functions to be called from system suspend/resume. So they are not + * inside ifdef CONFIG_PM_RUNTIME + */ +#ifdef CONFIG_PM_SLEEP +static int msm_slim_runtime_suspend(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct msm_slim_ctrl *dev = platform_get_drvdata(pdev); + int ret; + dev_dbg(device, "pm_runtime: suspending...\n"); + dev->state = MSM_CTRL_SLEEPING; + ret = slim_ctrl_clk_pause(&dev->ctrl, false, SLIM_CLK_UNSPECIFIED); + if (ret) { + dev_err(device, "clk pause not entered:%d", ret); + dev->state = MSM_CTRL_AWAKE; + } else { + dev->state = MSM_CTRL_ASLEEP; + } + return ret; +} + +static int msm_slim_runtime_resume(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct msm_slim_ctrl *dev = platform_get_drvdata(pdev); + int ret = 0; + dev_dbg(device, "pm_runtime: resuming...\n"); + if (dev->state == MSM_CTRL_ASLEEP) + ret = slim_ctrl_clk_pause(&dev->ctrl, true, 0); + if (ret) { + dev_err(device, "clk pause not exited:%d", ret); + dev->state = MSM_CTRL_ASLEEP; + } else { + dev->state = MSM_CTRL_AWAKE; + } + return ret; +} + +static int msm_slim_suspend(struct device *dev) +{ + int ret = 0; + if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { + dev_dbg(dev, "system suspend"); + ret = msm_slim_runtime_suspend(dev); + } + if (ret == -EBUSY) { + /* + * If the clock pause failed due to active channels, there is + * a possibility that some audio stream is active during suspend + * We dont want to return suspend failure in that case so that + * display and relevant components can still go to suspend. + * If there is some other error, then it should be passed-on + * to system level suspend + */ + ret = 0; + } + return ret; +} + +static int msm_slim_resume(struct device *dev) +{ + /* If runtime_pm is enabled, this resume shouldn't do anything */ + if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { + int ret; + dev_dbg(dev, "system resume"); + ret = msm_slim_runtime_resume(dev); + if (!ret) { + pm_runtime_mark_last_busy(dev); + pm_request_autosuspend(dev); + } + return ret; + + } + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +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 of_device_id msm_slim_dt_match[] = { + { + .compatible = "qcom,slim-msm", + }, + {} +}; + +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, + .of_match_table = msm_slim_dt_match, + }, +}; + +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 0000000000000000000000000000000000000000..24da4d1359be912e0a8c0384f28245eaf53db2af --- /dev/null +++ b/drivers/slimbus/slimbus.c @@ -0,0 +1,2953 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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_HDL_TO_CHIDX(hdl) ((u16)(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->num_dev = 0; + if (!ctrl->min_cg) + ctrl->min_cg = SLIM_MIN_CLK_GEAR; + if (!ctrl->max_cg) + ctrl->max_cg = SLIM_MAX_CLK_GEAR; + 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 + init_completion(&ctrl->pause_comp); + /* + * 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, u16 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, u16 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) { + ret = wait_for_completion_timeout(&complete, HZ); + if (!ret) { + struct slim_msg_txn *txn; + dev_err(&ctrl->dev, "slimbus Read timed out"); + mutex_lock(&ctrl->m_ctrl); + txn = ctrl->txnt[tid]; + /* Invalidate the transaction */ + ctrl->txnt[tid] = NULL; + mutex_unlock(&ctrl->m_ctrl); + kfree(txn); + ret = -ETIMEDOUT; + } else + ret = 0; + } + + } 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; + int ret = -EINVAL; + 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; + u16 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] = ctrl->chans[ch].chan; + 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; + u16 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_src: Connect source port to channel. + * @sb: client handle + * @srch: source 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 1 source port. + * Channel specified in chanh needs to be allocated first. + * Returns -EALREADY if source is already configured for this channel. + * Returns -ENOTCONN if channel is not allocated + */ +int slim_connect_src(struct slim_device *sb, u32 srch, u16 chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + int ret; + u8 chan = SLIM_HDL_TO_CHIDX(chanh); + struct slim_ich *slc = &ctrl->chans[chan]; + enum slim_port_flow flow = SLIM_HDL_TO_FLOW(srch); + + if (flow != SLIM_SRC) + return -EINVAL; + + mutex_lock(&ctrl->m_ctrl); + + if (slc->state == SLIM_CH_FREE) { + ret = -ENOTCONN; + goto connect_src_err; + } + /* + * Once channel is removed, its ports can be considered disconnected + * So its ports can be reassigned. Source port is zeroed + * when channel is deallocated. + */ + if (slc->srch) { + ret = -EALREADY; + goto connect_src_err; + } + + ret = connect_port_ch(ctrl, chan, srch, SLIM_SRC); + + if (!ret) + slc->srch = srch; + +connect_src_err: + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_connect_src); + +/* + * slim_connect_sink: Connect sink port(s) to channel. + * @sb: client handle + * @sinkh: sink handle(s) to be connected to this channel + * @nsink: number of sinks + * @chanh: Channel with which the ports need to be associated with. + * Per slimbus specification, a channel may have multiple sink-ports. + * Channel specified in chanh needs to be allocated first. + * Returns -EALREADY if sink is already configured for this channel. + * Returns -ENOTCONN if channel is not allocated + */ +int slim_connect_sink(struct slim_device *sb, u32 *sinkh, int nsink, u16 chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + int j; + int ret = 0; + u8 chan = SLIM_HDL_TO_CHIDX(chanh); + struct slim_ich *slc = &ctrl->chans[chan]; + + if (!sinkh || !nsink) + return -EINVAL; + + mutex_lock(&ctrl->m_ctrl); + + /* + * Once channel is removed, its ports can be considered disconnected + * So its ports can be reassigned. Sink ports are freed when channel + * is deallocated. + */ + if (slc->state == SLIM_CH_FREE) { + ret = -ENOTCONN; + goto connect_sink_err; + } + + for (j = 0; j < nsink; j++) { + enum slim_port_flow flow = SLIM_HDL_TO_FLOW(sinkh[j]); + if (flow != SLIM_SINK) + ret = -EINVAL; + else + ret = connect_port_ch(ctrl, chan, sinkh[j], SLIM_SINK); + if (ret) { + for (j = j - 1; j >= 0; j--) + disconnect_port_ch(ctrl, sinkh[j]); + goto connect_sink_err; + } + } + + slc->sinkh = krealloc(slc->sinkh, (sizeof(u32) * (slc->nsink + nsink)), + GFP_KERNEL); + if (!slc->sinkh) { + ret = -ENOMEM; + for (j = 0; j < nsink; j++) + disconnect_port_ch(ctrl, sinkh[j]); + goto connect_sink_err; + } + + memcpy(slc->sinkh + slc->nsink, sinkh, (sizeof(u32) * nsink)); + slc->nsink += nsink; + +connect_sink_err: + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_connect_sink); + +/* + * 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; + /* Channel is already active and other end is transmitting data */ + if (slc->state >= SLIM_CH_ACTIVE) + return; + 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->nsink; i++) { + ph = slc->sinkh[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->srch; + la = SLIM_HDL_TO_LA(ph); + if (la == SLIM_LA_MANAGER) + ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG; + + kfree(slc->sinkh); + slc->sinkh = NULL; + slc->srch = 0; + slc->nsink = 0; + 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. + * -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; + ctrl->chans[i].chan = (u8)(ctrl->reserved + i); + + mutex_unlock(&ctrl->m_ctrl); + return 0; +} +EXPORT_SYMBOL_GPL(slim_alloc_ch); + +/* + * slim_query_ch: Get reference-counted handle for a channel number. Every + * channel is reference counted by upto one as producer and the others as + * consumer) + * @sb: client handle + * @chan: slimbus channel number + * @chanh: return channel handle + * If request channel number is not in use, it is allocated, and reference + * count is set to one. If the channel was was already allocated, this API + * will return handle to that channel and reference count is incremented. + * -EXFULL is returned if all channels are in use + */ +int slim_query_ch(struct slim_device *sb, u8 ch, u16 *chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + u16 i, j; + int ret = 0; + if (!ctrl || !chanh) + return -EINVAL; + mutex_lock(&ctrl->m_ctrl); + /* start with modulo number */ + i = ch % ctrl->nchans; + + for (j = 0; j < ctrl->nchans; j++) { + if (ctrl->chans[i].chan == ch) { + *chanh = i; + ctrl->chans[i].ref++; + if (ctrl->chans[i].state == SLIM_CH_FREE) + ctrl->chans[i].state = SLIM_CH_ALLOCATED; + goto query_out; + } + i = (i + 1) % ctrl->nchans; + } + + /* Channel not in table yet */ + ret = -EXFULL; + for (j = 0; j < ctrl->nchans; j++) { + if (ctrl->chans[i].state == SLIM_CH_FREE) { + ctrl->chans[i].state = + SLIM_CH_ALLOCATED; + *chanh = i; + ctrl->chans[i].ref++; + ctrl->chans[i].chan = ch; + ctrl->chans[i].nextgrp = 0; + ret = 0; + break; + } + i = (i + 1) % ctrl->nchans; + } +query_out: + mutex_unlock(&ctrl->m_ctrl); + dev_dbg(&ctrl->dev, "query ch:%d,hdl:%d,ref:%d,ret:%d", + ch, i, ctrl->chans[i].ref, ret); + return ret; +} +EXPORT_SYMBOL_GPL(slim_query_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. + * -ENOTCONN is returned if deallocation is tried on a channel that's not + * allocated. + */ +int slim_dealloc_ch(struct slim_device *sb, u16 chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + u8 chan = SLIM_HDL_TO_CHIDX(chanh); + struct slim_ich *slc = &ctrl->chans[chan]; + if (!ctrl) + return -EINVAL; + + mutex_lock(&ctrl->m_ctrl); + if (slc->state == SLIM_CH_FREE) { + mutex_unlock(&ctrl->m_ctrl); + return -ENOTCONN; + } + if (slc->ref > 1) { + slc->ref--; + mutex_unlock(&ctrl->m_ctrl); + dev_dbg(&ctrl->dev, "remove chan:%d,hdl:%d,ref:%d", + slc->chan, chanh, slc->ref); + return 0; + } + 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; + } + slc->ref--; + slc->state = SLIM_CH_FREE; + mutex_unlock(&ctrl->m_ctrl); + dev_dbg(&ctrl->dev, "remove chan:%d,hdl:%d,ref:%d", + slc->chan, chanh, slc->ref); + 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 = SLIM_HDL_TO_CHIDX(chanh); + 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 channel is already used with different parameters. + * -ENXIO is returned if the channel is not yet allocated. + */ +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 = SLIM_HDL_TO_CHIDX(chanh[i]); + struct slim_ich *slc = &ctrl->chans[chan]; + dev_dbg(&ctrl->dev, "define_ch: ch:%d, state:%d", chan, + (int)ctrl->chans[chan].state); + if (slc->state < SLIM_CH_ALLOCATED) { + ret = -ENXIO; + goto err_define_ch; + } + if (slc->state >= SLIM_CH_DEFINED && slc->ref >= 2) { + if (prop->ratem != slc->prop.ratem || + prop->sampleszbits != slc->prop.sampleszbits || + prop->baser != slc->prop.baser) { + ret = -EISCONN; + goto err_define_ch; + } + } else if (slc->state > SLIM_CH_DEFINED) { + ret = -EISCONN; + goto err_define_ch; + } else { + ctrl->chans[chan].prop = *prop; + ret = slim_nextdefine_ch(sb, chan); + if (ret) + 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; + } + + if (grp) + *grph = chanh[0]; + for (i = 0; i < nchan; i++) { + u8 chan = SLIM_HDL_TO_CHIDX(chanh[i]); + struct slim_ich *slc = &ctrl->chans[chan]; + if (slc->state == SLIM_CH_ALLOCATED) + slc->state = SLIM_CH_DEFINED; + } +err_define_ch: + dev_dbg(&ctrl->dev, "define_ch: ch:%d, ret:%d", *chanh, ret); + 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]; + } + } + /* Leave some slots for messaging space */ + if (opensl1[1] == 0 && opensl1[0] == 0) + return -EXFULL; + 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 = NULL; + 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]; + } + } + /* Leave some slots for messaging space */ + if (opensl3[1] == 0 && opensl3[0] == 0) + return -EXFULL; + /* 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 = SLIM_HDL_TO_CHIDX(slc1->nextgrp); + 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 = SLIM_HDL_TO_CHIDX(slc1->nextgrp); + 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); + /* + * If number of slots are 0, that means channels are inactive. + * It is very likely that the manager will call clock pause very soon. + * By making sure that bus is in MAX_GEAR, clk pause sequence will take + * minimum amount of time. + */ + if (ctrl->sched.usedslots != 0) { + while ((usedsl * 2 <= availsl) && (*clkgear > ctrl->min_cg)) { + *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 <= ctrl->max_cg; (*clkgear)++) { + ret = slim_sched_chans(sb, *clkgear, &ctrlw, &subfrml); + + if (ret == 0) { + *subfrmc = getsubfrmcoding(&ctrlw, &subfrml, &msgsl); + if ((msgsl >> (ctrl->max_cg - *clkgear) < + ctrl->sched.pending_msgsl) && + (*clkgear < ctrl->max_cg)) + 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_change_existing_chans(struct slim_controller *ctrl, int coeff) +{ + struct slim_ich **arr; + int len, i; + if (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++) { + struct slim_ich *slc = arr[i]; + if (slc->state == SLIM_CH_ACTIVE || + slc->state == SLIM_CH_SUSPENDED) + slc->offset = slc->newoff; + slc->interval = slc->newintr; + } +} +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) { + if (slc->state == SLIM_CH_PENDING_ACTIVE) { + 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->state = SLIM_CH_ACTIVE; + slc->def++; + } + 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->def = 1; + 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); + } + /* Change already active channel if reconfig succeeded */ + if (!revert) { + slim_change_existing_chans(ctrl, SLIM_COEFF_1); + slim_change_existing_chans(ctrl, SLIM_COEFF_3); + } +} + +/* + * 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); + if (slc->state < SLIM_CH_ACTIVE) + 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] = slc->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) { + struct slim_ich *slc = &ctrl->chans[pch->chan]; + dev_dbg(&ctrl->dev, "remove chan:%x\n", pch->chan); + wbuf[0] = slc->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) { + struct slim_ich *slc = &ctrl->chans[pch->chan]; + dev_dbg(&ctrl->dev, "suspend chan:%x\n", pch->chan); + wbuf[0] = slc->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; + dev_dbg(&ctrl->dev, "new-intr:%d, old-intr:%d, dist:%d\n", + slc->newintr, slc->interval, segdist); + dev_dbg(&ctrl->dev, "new-off:%d, old-off:%d\n", + slc->newoff, slc->offset); + + if (slc->state < SLIM_CH_ACTIVE || slc->def < slc->ref || + slc->newintr != slc->interval || + slc->newoff != slc->offset) { + segdist |= 0x200; + segdist >>= curexp; + segdist |= (slc->newoff << (curexp + 1)) & 0xC00; + wbuf[0] = slc->chan; + 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; + dev_dbg(&ctrl->dev, "new-intr:%d, old-intr:%d, dist:%d\n", + slc->newintr, slc->interval, segdist); + dev_dbg(&ctrl->dev, "new-off:%d, old-off:%d\n", + slc->newoff, slc->offset); + + if (slc->state < SLIM_CH_ACTIVE || slc->def < slc->ref || + slc->newintr != slc->interval || + slc->newoff != slc->offset) { + segdist |= 0x200; + segdist >>= curexp; + segdist |= 0xC00; + wbuf[0] = slc->chan; + 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. + * -EINVAL is returned if individual control of a grouped-channel is attempted. + */ +int slim_control_ch(struct slim_device *sb, u16 chanh, + enum slim_ch_control chctrl, bool commit) +{ + struct slim_controller *ctrl = sb->ctrl; + int ret = 0; + /* Get rid of the group flag in MSB if any */ + u8 chan = SLIM_HDL_TO_CHIDX(chanh); + struct slim_ich *slc = &ctrl->chans[chan]; + if (!(slc->nextgrp & SLIM_START_GRP)) + return -EINVAL; + + mutex_lock(&sb->sldev_reconf); + mutex_lock(&ctrl->m_ctrl); + do { + slc = &ctrl->chans[chan]; + dev_dbg(&ctrl->dev, "chan:%d,ctrl:%d,def:%d", chan, chctrl, + slc->def); + 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; + } + if (slc->def > 0) + slc->def--; + if (slc->def == 0) + ret = add_pending_ch(&sb->mark_removal, chan); + if (ret) + break; + } + + if (!(slc->nextgrp & SLIM_END_GRP)) + chan = SLIM_HDL_TO_CHIDX(slc->nextgrp); + } 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); + +/* + * slim_ctrl_clk_pause: Called by slimbus controller to request clock to be + * paused or woken up out of clock pause + * or woken up from clock pause + * @ctrl: controller requesting bus to be paused or woken up + * @wakeup: Wakeup this controller from clock pause. + * @restart: Restart time value per spec used for clock pause. This value + * isn't used when controller is to be woken up. + * This API executes clock pause reconfiguration sequence if wakeup is false. + * If wakeup is true, controller's wakeup is called + * Slimbus clock is idle and can be disabled by the controller later. + */ +int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart) +{ + int ret = 0; + int i; + + if (wakeup == false && restart > SLIM_CLK_UNSPECIFIED) + return -EINVAL; + mutex_lock(&ctrl->m_ctrl); + if (wakeup) { + if (ctrl->clk_state == SLIM_CLK_ACTIVE) { + mutex_unlock(&ctrl->m_ctrl); + return 0; + } + wait_for_completion(&ctrl->pause_comp); + /* + * Slimbus framework will call controller wakeup + * Controller should make sure that it sets active framer + * out of clock pause by doing appropriate setting + */ + if (ctrl->clk_state == SLIM_CLK_PAUSED && ctrl->wakeup) + ret = ctrl->wakeup(ctrl); + if (!ret) + ctrl->clk_state = SLIM_CLK_ACTIVE; + mutex_unlock(&ctrl->m_ctrl); + return ret; + } else { + switch (ctrl->clk_state) { + case SLIM_CLK_ENTERING_PAUSE: + case SLIM_CLK_PAUSE_FAILED: + /* + * If controller is already trying to enter clock pause, + * let it finish. + * In case of error, retry + * In both cases, previous clock pause has signalled + * completion. + */ + wait_for_completion(&ctrl->pause_comp); + /* retry upon failure */ + if (ctrl->clk_state == SLIM_CLK_PAUSE_FAILED) { + ctrl->clk_state = SLIM_CLK_ACTIVE; + break; + } else { + mutex_unlock(&ctrl->m_ctrl); + /* + * Signal completion so that wakeup can wait on + * it. + */ + complete(&ctrl->pause_comp); + return 0; + } + break; + case SLIM_CLK_PAUSED: + /* already paused */ + mutex_unlock(&ctrl->m_ctrl); + return 0; + case SLIM_CLK_ACTIVE: + default: + break; + } + } + /* Pending response for a message */ + for (i = 0; i < ctrl->last_tid; i++) { + if (ctrl->txnt[i]) { + ret = -EBUSY; + mutex_unlock(&ctrl->m_ctrl); + return -EBUSY; + } + } + ctrl->clk_state = SLIM_CLK_ENTERING_PAUSE; + mutex_unlock(&ctrl->m_ctrl); + + mutex_lock(&ctrl->sched.m_reconf); + /* Data channels active */ + if (ctrl->sched.usedslots) { + ret = -EBUSY; + goto clk_pause_ret; + } + + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_BEGIN_RECONFIGURATION, + 0, SLIM_MSG_MT_CORE, NULL, NULL, 0, 3, NULL, 0, NULL); + if (ret) + goto clk_pause_ret; + + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_NEXT_PAUSE_CLOCK, 0, + SLIM_MSG_MT_CORE, NULL, &restart, 1, 4, NULL, 0, NULL); + if (ret) + goto clk_pause_ret; + + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_RECONFIGURE_NOW, 0, + SLIM_MSG_MT_CORE, NULL, NULL, 0, 3, NULL, 0, NULL); + if (ret) + goto clk_pause_ret; + +clk_pause_ret: + if (ret) + ctrl->clk_state = SLIM_CLK_PAUSE_FAILED; + else + ctrl->clk_state = SLIM_CLK_PAUSED; + complete(&ctrl->pause_comp); + mutex_unlock(&ctrl->sched.m_reconf); + return ret; +} + +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 00c024039c9713a7b8468d91f5a7e42316db65d5..6946b06608c316e7eed5ed92fd60cad94a4abd82 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -403,6 +403,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 9d75d2198ff58bcce9ca60e587b72112a0b3c292..a593faa92fdaa4fc40210d9016a79ae9a041a362 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -45,6 +45,8 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx.o obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o +obj-$(CONFIG_SPI_QSD) += spi_qsd.o +obj-$(CONFIG_SPI_QUP) += spi_qsd.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o 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 0000000000000000000000000000000000000000..2c86e83dffd554c753d981ffc11b99b3e59718e8 --- /dev/null +++ b/drivers/spi/spi_qsd.c @@ -0,0 +1,2175 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 +#include +#include +#include +#include "spi_qsd.h" + +static inline int msm_spi_configure_gsbi(struct msm_spi *dd, + struct platform_device *pdev) +{ + struct resource *resource; + unsigned long gsbi_mem_phys_addr; + size_t gsbi_mem_size; + void __iomem *gsbi_base; + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!resource) + return 0; + + gsbi_mem_phys_addr = resource->start; + gsbi_mem_size = resource_size(resource); + if (!devm_request_mem_region(&pdev->dev, gsbi_mem_phys_addr, + gsbi_mem_size, SPI_DRV_NAME)) + return -ENXIO; + + gsbi_base = devm_ioremap(&pdev->dev, gsbi_mem_phys_addr, + gsbi_mem_size); + if (!gsbi_base) + return -ENXIO; + + /* Set GSBI to SPI mode */ + writel_relaxed(GSBI_SPI_CONFIG, gsbi_base + GSBI_CTRL_REG); + + return 0; +} + +static inline void msm_spi_register_init(struct msm_spi *dd) +{ + 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); + if (dd->qup_ver) + writel_relaxed(0x00000000, dd->base + QUP_OPERATIONAL_MASK); +} + +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) { + dev_err(dd->dev, "%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]); + } + + for (i = 0; i < ARRAY_SIZE(spi_cs_rsrcs); ++i) { + if (dd->cs_gpios[i].valid) { + gpio_free(dd->cs_gpios[i].gpio_num); + dd->cs_gpios[i].valid = 0; + } + } +} + +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 -EINVAL; + } + + 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 -EINVAL; + } + + *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; + pr_err("%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; + + /* Adding one to round off to the nearest jiffy */ + timeout = jiffies + msecs_to_jiffies(delay * SPI_DEFAULT_TIMEOUT) + 1; + while (!msm_spi_is_valid_state(dd)) { + if (time_after(jiffies, timeout)) { + if (!msm_spi_is_valid_state(dd)) { + if (dd->cur_msg) + dd->cur_msg->status = -EIO; + dev_err(dd->dev, "%s: SPI operational state" + "not valid\n", __func__); + return -ETIMEDOUT; + } else + return 0; + } + /* + * 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 -EIO; + 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 -EIO; + + 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); + atomic_set(&dd->tx_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(); +} + +/* Figure which irq occured and call the relevant functions */ +static inline 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 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); + if (atomic_inc_return(&dd->tx_irq_called) == 1) + return IRQ_HANDLED; + 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 = NULL; + 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) + pr_err( + "%s:Internal Loopback does not support > fifo size" + "for write-then-read transactions\n", + __func__); + else if (dd->write_len && !dd->read_len) + pr_err( + "%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, 1); + msm_dmov_flush(dd->rx_dma_chan, 1); + } + 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 inline void write_force_cs(struct msm_spi *dd, bool set_flag) +{ + u32 spi_ioc; + u32 spi_ioc_orig; + + spi_ioc = readl_relaxed(dd->base + SPI_IO_CONTROL); + spi_ioc_orig = spi_ioc; + if (set_flag) + spi_ioc |= SPI_IO_C_FORCE_CS; + else + spi_ioc &= ~SPI_IO_C_FORCE_CS; + + if (spi_ioc != spi_ioc_orig) + writel_relaxed(spi_ioc, dd->base + SPI_IO_CONTROL); +} + +static void msm_spi_process_message(struct msm_spi *dd) +{ + int xfrs_grped = 0; + int cs_num; + int rc; + + dd->write_xfr_cnt = dd->read_xfr_cnt = 0; + cs_num = dd->cur_msg->spi->chip_select; + if ((!(dd->cur_msg->spi->mode & SPI_LOOP)) && + (!(dd->cs_gpios[cs_num].valid)) && + (dd->cs_gpios[cs_num].gpio_num >= 0)) { + rc = gpio_request(dd->cs_gpios[cs_num].gpio_num, + spi_cs_rsrcs[cs_num]); + if (rc) { + dev_err(dd->dev, "gpio_request for pin %d failed with " + "error %d\n", dd->cs_gpios[cs_num].gpio_num, + rc); + return; + } + dd->cs_gpios[cs_num].valid = 1; + } + + if (dd->qup_ver) { + write_force_cs(dd, 0); + list_for_each_entry(dd->cur_transfer, + &dd->cur_msg->transfers, + transfer_list) { + struct spi_transfer *t = dd->cur_transfer; + struct spi_transfer *nxt; + + if (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) + write_force_cs(dd, 1); + else + write_force_cs(dd, 0); + } + + dd->cur_msg_len = dd->cur_transfer->len; + msm_spi_process_transfer(dd); + } + } else { + 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) + goto error; + 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; + goto error; + } + } + + dd->cur_tx_transfer = dd->cur_transfer; + dd->cur_rx_transfer = dd->cur_transfer; + msm_spi_process_transfer(dd); + } + } + + return; + +error: + if (dd->cs_gpios[cs_num].valid) { + gpio_free(dd->cs_gpios[cs_num].gpio_num); + dd->cs_gpios[cs_num].valid = 0; + } +} + +/* 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_prepare_enable(dd->clk); + clk_prepare_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_unprepare(dd->clk); + clk_disable_unprepare(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_prepare_enable(dd->clk); + clk_prepare_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_unprepare(dd->clk); + clk_disable_unprepare(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++; + if ((atomic_inc_return(&dd->tx_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_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, 1); + msm_dmov_flush(dd->rx_dma_chan, 1); + 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; + + 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_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, 1); + msm_dmov_flush(dd->rx_dma_chan, 1); + + return 0; +} + +struct msm_spi_platform_data *msm_spi_dt_to_pdata(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct msm_spi_platform_data *pdata; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + pr_err("Unable to allocate platform data\n"); + return NULL; + } + + of_property_read_u32(node, "spi-max-frequency", + &pdata->max_clock_speed); + + return pdata; +} + +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; + enum of_gpio_flags flags; + + 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); + + if (pdev->dev.of_node) { + dd->qup_ver = SPI_QUP_VERSION_BFAM; + master->dev.of_node = pdev->dev.of_node; + pdata = msm_spi_dt_to_pdata(pdev); + if (!pdata) { + rc = -ENOMEM; + goto err_probe_exit; + } + + for (i = 0; i < ARRAY_SIZE(spi_rsrcs); ++i) { + dd->spi_gpios[i] = of_get_gpio_flags(pdev->dev.of_node, + i, &flags); + } + + for (i = 0; i < ARRAY_SIZE(spi_cs_rsrcs); ++i) { + dd->cs_gpios[i].gpio_num = of_get_named_gpio_flags( + pdev->dev.of_node, "cs-gpios", + i, &flags); + dd->cs_gpios[i].valid = 0; + } + } else { + pdata = pdev->dev.platform_data; + dd->qup_ver = SPI_QUP_VERSION_NONE; + + for (i = 0; i < ARRAY_SIZE(spi_rsrcs); ++i) { + resource = platform_get_resource(pdev, IORESOURCE_IO, + i); + dd->spi_gpios[i] = resource ? resource->start : -1; + } + + for (i = 0; i < ARRAY_SIZE(spi_cs_rsrcs); ++i) { + resource = platform_get_resource(pdev, IORESOURCE_IO, + i + ARRAY_SIZE(spi_rsrcs)); + dd->cs_gpios[i].gpio_num = resource ? + resource->start : -1; + dd->cs_gpios[i].valid = 0; + } + } + + dd->pdata = pdata; + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + rc = -ENXIO; + goto err_probe_res; + } + + dd->mem_phys_addr = resource->start; + dd->mem_size = resource_size(resource); + + 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(pdev, IORESOURCE_DMA, 0); + if (resource) { + dd->rx_dma_chan = resource->start; + dd->tx_dma_chan = resource->end; + resource = platform_get_resource(pdev, IORESOURCE_DMA, + 1); + 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; + } + } + } + + 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 (!devm_request_mem_region(&pdev->dev, dd->mem_phys_addr, + dd->mem_size, SPI_DRV_NAME)) { + rc = -ENXIO; + goto err_probe_reqmem; + } + + dd->base = devm_ioremap(&pdev->dev, dd->mem_phys_addr, dd->mem_size); + if (!dd->base) { + rc = -ENOMEM; + goto err_probe_reqmem; + } + + 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, "core_clk"); + if (IS_ERR(dd->clk)) { + dev_err(&pdev->dev, "%s: unable to get core_clk\n", __func__); + rc = PTR_ERR(dd->clk); + goto err_probe_clk_get; + } + + dd->pclk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(dd->pclk)) { + dev_err(&pdev->dev, "%s: unable to get iface_clk\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_prepare_enable(dd->clk); + if (rc) { + dev_err(&pdev->dev, "%s: unable to enable core_clk\n", + __func__); + goto err_probe_clk_enable; + } + + clk_enabled = 1; + rc = clk_prepare_enable(dd->pclk); + if (rc) { + dev_err(&pdev->dev, "%s: unable to enable iface_clk\n", + __func__); + goto err_probe_pclk_enable; + } + + pclk_enabled = 1; + rc = msm_spi_configure_gsbi(dd, pdev); + if (rc) + goto err_probe_gsbi; + + msm_spi_calculate_fifo_size(dd); + if (dd->use_dma) { + rc = msm_spi_init_dma(dd); + if (rc) + goto err_probe_dma; + } + + msm_spi_register_init(dd); + /* + * 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_unprepare(dd->clk); + clk_disable_unprepare(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, 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: + spi_unregister_master(master); +err_probe_reg_master: +err_probe_irq: +err_probe_state: + msm_spi_teardown_dma(dd); +err_probe_dma: +err_probe_gsbi: + if (pclk_enabled) + clk_disable_unprepare(dd->pclk); +err_probe_pclk_enable: + if (clk_enabled) + clk_disable_unprepare(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: +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_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_teardown_dma(dd); + if (pdata && pdata->gpio_release) + pdata->gpio_release(); + + msm_spi_free_gpios(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 of_device_id msm_spi_dt_match[] = { + { + .compatible = "qcom,spi-qup-v2", + }, + {} +}; + +static struct platform_driver msm_spi_driver = { + .driver = { + .name = SPI_DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = msm_spi_dt_match, + }, + .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); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.4"); +MODULE_ALIAS("platform:"SPI_DRV_NAME); diff --git a/drivers/spi/spi_qsd.h b/drivers/spi/spi_qsd.h new file mode 100644 index 0000000000000000000000000000000000000000..b0d72b744f66abb04b63d2d8cb191c03918bb362 --- /dev/null +++ b/drivers/spi/spi_qsd.h @@ -0,0 +1,493 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _SPI_QSD_H +#define _SPI_QSD_H + +#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 + +#else + +#define QSD_REG(x) +#define QUP_REG(x) (x) + +#define QUP_CONFIG 0x0000 /* N & NO_INPUT/NO_OUPUT bits */ +#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 +#endif + +#define GSBI_CTRL_REG 0x0 +#define GSBI_SPI_CONFIG 0x30 +#define QUP_HARDWARE_VER 0x0030 +#define QUP_OPERATIONAL_MASK 0x0028 +#define QUP_ERROR_FLAGS 0x0308 + +#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_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_FORCE_CS 0x00000800 +#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 + +#define SPI_QUP_VERSION_NONE 0x0 +#define SPI_QUP_VERSION_BFAM 0x2 + +static char const * const spi_rsrcs[] = { + "spi_clk", + "spi_miso", + "spi_mosi" +}; + +static char const * const spi_cs_rsrcs[] = { + "spi_cs", + "spi_cs1", + "spi_cs2", + "spi_cs3", +}; + +enum msm_spi_mode { + SPI_FIFO_MODE = 0x0, /* 00 */ + SPI_BLOCK_MODE = 0x1, /* 01 */ + SPI_DMOV_MODE = 0x2, /* 10 */ + SPI_BAM_MODE = 0x3, /* 11 */ + SPI_MODE_NONE = 0xFF, /* invalid value */ +}; + +/* Structure for SPI CS GPIOs */ +struct spi_cs_gpio { + int gpio_num; + bool valid; +}; + +/* 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; +}; + +static struct pm_qos_request 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_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}, +#else + {"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; + 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; + int input_fifo_size; + int output_fifo_size; + u32 rx_bytes_remaining; + u32 tx_bytes_remaining; + u32 clock_speed; + int 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) + int irq_out; + int 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; + atomic_t tx_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, miso and mosi */ + int spi_gpios[ARRAY_SIZE(spi_rsrcs)]; + /* SPI CS GPIOs for each slave */ + struct spi_cs_gpio cs_gpios[ARRAY_SIZE(spi_cs_rsrcs)]; + int qup_ver; +}; + +/* 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); +static inline irqreturn_t msm_spi_qup_irq(int irq, void *dev_id); + +#if defined(CONFIG_SPI_QSD) || defined(CONFIG_SPI_QSD_MODULE) +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, + struct platform_device *pdev, + struct spi_master *master) +{ + int rc; + + dd->irq_in = platform_get_irq(pdev, 0); + dd->irq_out = platform_get_irq(pdev, 1); + dd->irq_err = platform_get_irq(pdev, 2); + if ((dd->irq_in < 0) || (dd->irq_out < 0) || (dd->irq_err < 0)) + return -EINVAL; + + rc = devm_request_irq(dd->dev, dd->irq_in, msm_spi_input_irq, + IRQF_TRIGGER_RISING, pdev->name, dd); + if (rc) + goto error_irq; + + rc = devm_request_irq(dd->dev, dd->irq_out, msm_spi_output_irq, + IRQF_TRIGGER_RISING, pdev->name, dd); + if (rc) + goto error_irq; + + rc = devm_request_irq(dd->dev, dd->irq_err, msm_spi_error_irq, + IRQF_TRIGGER_RISING, pdev->name, master); + if (rc) + goto error_irq; + +error_irq: + return rc; +} + +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); +} + +#else +/* In QUP the same interrupt line is used for input, output and error*/ +static inline int msm_spi_request_irq(struct msm_spi *dd, + struct platform_device *pdev, + struct spi_master *master) +{ + dd->irq_in = platform_get_irq(pdev, 0); + if (dd->irq_in < 0) + return -EINVAL; + + return devm_request_irq(dd->dev, dd->irq_in, msm_spi_qup_irq, + IRQF_TRIGGER_HIGH, pdev->name, dd); +} + +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 -EINVAL; + if (msm_spi_set_state(dd, SPI_OP_STATE_PAUSE)) + return -EINVAL; + 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 +#endif diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 830adbed1d7ace083697312cf4ed585606c4a35b..aaf026574f875b734c2bbd6f80955e06223bf8e7 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/spmi/Kconfig b/drivers/spmi/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..84fd46286a600cfd652b813771a5e8c7303614cf --- /dev/null +++ b/drivers/spmi/Kconfig @@ -0,0 +1,38 @@ +# +# SPMI driver configuration +# +menuconfig SPMI + bool "SPMI support" + help + SPMI (System Power Management Interface) is a two-wire + serial interface between baseband and application processors + and Power Management Integrated Circuits (PMIC). + +if SPMI +config SPMI_MSM_PMIC_ARB + tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" + help + If you say yes to this option, support will be included for the + built-in SPMI PMIC Arbiter interface on Qualcomm MSM family + processors. + + This is required for communicating with Qualcomm PMICs and + other devices that have the SPMI interface. + +config MSM_QPNP + depends on ARCH_MSMCOPPER + depends on OF_SPMI + bool "MSM QPNP" + help + Say 'y' here to include support for the Qualcomm QPNP + +config MSM_QPNP_INT + depends on SPARSE_IRQ + depends on ARCH_MSMCOPPER + depends on OF_SPMI + depends on MSM_QPNP + bool "MSM QPNP INT" + help + Say 'y' here to include support for the Qualcomm QPNP interrupt + support. QPNP is a SPMI based PMIC implementation. +endif diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d59a6107b29f359c2a2c3403de3ee4ea8d63c3f5 --- /dev/null +++ b/drivers/spmi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for kernel SPMI framework. +# +obj-$(CONFIG_SPMI) += spmi.o +obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o +obj-$(CONFIG_MSM_QPNP) += qpnp.o +obj-$(CONFIG_MSM_QPNP_INT) += qpnp-int.o diff --git a/drivers/spmi/qpnp-int.c b/drivers/spmi/qpnp-int.c new file mode 100644 index 0000000000000000000000000000000000000000..2998c0119e0e6f186d287be78a0331a20d41cd10 --- /dev/null +++ b/drivers/spmi/qpnp-int.c @@ -0,0 +1,510 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 QPNPINT_MAX_BUSSES 1 + +/* 16 slave_ids, 256 per_ids per slave, and 8 ints per per_id */ +#define QPNPINT_NR_IRQS (16 * 256 * 8) + +enum qpnpint_regs { + QPNPINT_REG_RT_STS = 0x10, + QPNPINT_REG_SET_TYPE = 0x11, + QPNPINT_REG_POLARITY_HIGH = 0x12, + QPNPINT_REG_POLARITY_LOW = 0x13, + QPNPINT_REG_LATCHED_CLR = 0x14, + QPNPINT_REG_EN_SET = 0x15, + QPNPINT_REG_EN_CLR = 0x16, + QPNPINT_REG_LATCHED_STS = 0x18, +}; + +struct q_perip_data { + uint8_t type; /* bitmap */ + uint8_t pol_high; /* bitmap */ + uint8_t pol_low; /* bitmap */ + uint8_t int_en; /* bitmap */ + uint8_t use_count; +}; + +struct q_irq_data { + uint32_t priv_d; /* data to optimize arbiter interactions */ + struct q_chip_data *chip_d; + struct q_perip_data *per_d; + uint8_t mask_shift; + uint8_t spmi_slave; + uint16_t spmi_offset; +}; + +struct q_chip_data { + int bus_nr; + struct irq_domain domain; + struct qpnp_local_int cb; + struct spmi_controller *spmi_ctrl; + struct radix_tree_root per_tree; +}; + +static struct q_chip_data chip_data[QPNPINT_MAX_BUSSES] __read_mostly; + +/** + * qpnpint_encode_hwirq - translate between qpnp_irq_spec and + * hwirq representation. + * + * slave_offset = (addr->slave * 256 * 8); + * perip_offset = slave_offset + (addr->perip * 8); + * return perip_offset + addr->irq; + */ +static inline int qpnpint_encode_hwirq(struct qpnp_irq_spec *spec) +{ + uint32_t hwirq; + + if (spec->slave > 15 || spec->irq > 7) + return -EINVAL; + + hwirq = (spec->slave << 11); + hwirq |= (spec->per << 3); + hwirq |= spec->irq; + + return hwirq; +} +/** + * qpnpint_decode_hwirq - translate between hwirq and + * qpnp_irq_spec representation. + */ +static inline int qpnpint_decode_hwirq(unsigned long hwirq, + struct qpnp_irq_spec *spec) +{ + if (hwirq > 65535) + return -EINVAL; + + spec->slave = (hwirq >> 11) & 0xF; + spec->per = (hwirq >> 3) & 0xFF; + spec->irq = hwirq & 0x7; + return 0; +} + +static int qpnpint_spmi_write(struct q_irq_data *irq_d, uint8_t reg, + void *buf, uint32_t len) +{ + struct q_chip_data *chip_d = irq_d->chip_d; + int rc; + + if (!chip_d->spmi_ctrl) + return -ENODEV; + + rc = spmi_ext_register_writel(chip_d->spmi_ctrl, irq_d->spmi_slave, + irq_d->spmi_offset + reg, buf, len); + return rc; +} + +static void qpnpint_irq_mask(struct irq_data *d) +{ + struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d); + struct q_chip_data *chip_d = irq_d->chip_d; + struct q_perip_data *per_d = irq_d->per_d; + struct qpnp_irq_spec q_spec; + int rc; + + pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq); + + if (chip_d->cb.mask) { + rc = qpnpint_decode_hwirq(d->hwirq, &q_spec); + if (rc) + pr_err("%s: decode failed on hwirq %lu\n", + __func__, d->hwirq); + else + chip_d->cb.mask(chip_d->spmi_ctrl, &q_spec, + irq_d->priv_d); + } + + per_d->int_en &= ~irq_d->mask_shift; + + rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_CLR, + (u8 *)&irq_d->mask_shift, 1); + if (rc) + pr_err("%s: spmi failure on irq %d\n", + __func__, d->irq); +} + +static void qpnpint_irq_mask_ack(struct irq_data *d) +{ + struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d); + struct q_chip_data *chip_d = irq_d->chip_d; + struct q_perip_data *per_d = irq_d->per_d; + struct qpnp_irq_spec q_spec; + int rc; + + pr_debug("hwirq %lu irq: %d mask: 0x%x\n", d->hwirq, d->irq, + irq_d->mask_shift); + + if (chip_d->cb.mask) { + rc = qpnpint_decode_hwirq(d->hwirq, &q_spec); + if (rc) + pr_err("%s: decode failed on hwirq %lu\n", + __func__, d->hwirq); + else + chip_d->cb.mask(chip_d->spmi_ctrl, &q_spec, + irq_d->priv_d); + } + + per_d->int_en &= ~irq_d->mask_shift; + + rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_CLR, + &irq_d->mask_shift, 1); + if (rc) + pr_err("%s: spmi failure on irq %d\n", + __func__, d->irq); + + rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_LATCHED_CLR, + &irq_d->mask_shift, 1); + if (rc) + pr_err("%s: spmi failure on irq %d\n", + __func__, d->irq); +} + +static void qpnpint_irq_unmask(struct irq_data *d) +{ + struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d); + struct q_chip_data *chip_d = irq_d->chip_d; + struct q_perip_data *per_d = irq_d->per_d; + struct qpnp_irq_spec q_spec; + int rc; + + pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq); + + if (chip_d->cb.unmask) { + rc = qpnpint_decode_hwirq(d->hwirq, &q_spec); + if (rc) + pr_err("%s: decode failed on hwirq %lu\n", + __func__, d->hwirq); + else + chip_d->cb.unmask(chip_d->spmi_ctrl, &q_spec, + irq_d->priv_d); + } + + per_d->int_en |= irq_d->mask_shift; + rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_SET, + &irq_d->mask_shift, 1); + if (rc) + pr_err("%s: spmi failure on irq %d\n", + __func__, d->irq); +} + +static int qpnpint_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d); + struct q_perip_data *per_d = irq_d->per_d; + int rc; + u8 buf[3]; + + pr_debug("hwirq %lu irq: %d flow: 0x%x\n", d->hwirq, + d->irq, flow_type); + + per_d->pol_high &= ~irq_d->mask_shift; + per_d->pol_low &= ~irq_d->mask_shift; + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + per_d->type |= irq_d->mask_shift; /* edge trig */ + if (flow_type & IRQF_TRIGGER_RISING) + per_d->pol_high |= irq_d->mask_shift; + if (flow_type & IRQF_TRIGGER_FALLING) + per_d->pol_low |= irq_d->mask_shift; + } else { + if ((flow_type & IRQF_TRIGGER_HIGH) && + (flow_type & IRQF_TRIGGER_LOW)) + return -EINVAL; + per_d->type &= ~irq_d->mask_shift; /* level trig */ + if (flow_type & IRQF_TRIGGER_HIGH) + per_d->pol_high |= irq_d->mask_shift; + else + per_d->pol_high &= ~irq_d->mask_shift; + } + + buf[0] = per_d->type; + buf[1] = per_d->pol_high; + buf[2] = per_d->pol_low; + + rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_SET_TYPE, &buf, 3); + if (rc) + pr_err("%s: spmi failure on irq %d\n", + __func__, d->irq); + return rc; +} + +static struct irq_chip qpnpint_chip = { + .name = "qpnp-int", + .irq_mask = qpnpint_irq_mask, + .irq_mask_ack = qpnpint_irq_mask_ack, + .irq_unmask = qpnpint_irq_unmask, + .irq_set_type = qpnpint_irq_set_type, +}; + +static int qpnpint_init_irq_data(struct q_chip_data *chip_d, + struct q_irq_data *irq_d, + unsigned long hwirq) +{ + struct qpnp_irq_spec q_spec; + int rc; + + irq_d->mask_shift = 1 << (hwirq & 0x7); + rc = qpnpint_decode_hwirq(hwirq, &q_spec); + if (rc < 0) + return rc; + irq_d->spmi_slave = q_spec.slave; + irq_d->spmi_offset = q_spec.per << 8; + irq_d->per_d->use_count++; + irq_d->chip_d = chip_d; + + if (chip_d->cb.register_priv_data) + rc = chip_d->cb.register_priv_data(chip_d->spmi_ctrl, &q_spec, + &irq_d->priv_d); + return rc; +} + +static struct q_irq_data *qpnpint_alloc_irq_data( + struct q_chip_data *chip_d, + unsigned long hwirq) +{ + struct q_irq_data *irq_d; + struct q_perip_data *per_d; + + irq_d = kzalloc(sizeof(struct q_irq_data), GFP_KERNEL); + if (!irq_d) + return ERR_PTR(-ENOMEM); + + /** + * The Peripheral Tree is keyed from the slave + per_id. We're + * ignoring the irq bits here since this peripheral structure + * should be common for all irqs on the same peripheral. + */ + per_d = radix_tree_lookup(&chip_d->per_tree, (hwirq & ~0x7)); + if (!per_d) { + per_d = kzalloc(sizeof(struct q_perip_data), GFP_KERNEL); + if (!per_d) + return ERR_PTR(-ENOMEM); + radix_tree_insert(&chip_d->per_tree, + (hwirq & ~0x7), per_d); + } + irq_d->per_d = per_d; + + return irq_d; +} + +static int qpnpint_register_int(uint32_t busno, unsigned long hwirq) +{ + int irq, rc; + struct irq_domain *domain; + struct q_irq_data *irq_d; + + pr_debug("busno = %u hwirq = %lu\n", busno, hwirq); + + if (hwirq < 0 || hwirq >= 32768) { + pr_err("%s: hwirq %lu out of qpnp interrupt bounds\n", + __func__, hwirq); + return -EINVAL; + } + + if (busno < 0 || busno > QPNPINT_MAX_BUSSES) { + pr_err("%s: invalid bus number %d\n", __func__, busno); + return -EINVAL; + } + + domain = &chip_data[busno].domain; + irq = irq_domain_to_irq(domain, hwirq); + + rc = irq_alloc_desc_at(irq, numa_node_id()); + if (rc < 0) { + if (rc != -EEXIST) + pr_err("%s: failed to alloc irq at %d with " + "rc %d\n", __func__, irq, rc); + return rc; + } + irq_d = qpnpint_alloc_irq_data(&chip_data[busno], hwirq); + if (IS_ERR(irq_d)) { + pr_err("%s: failed to alloc irq data %d with " + "rc %d\n", __func__, irq, rc); + rc = PTR_ERR(irq_d); + goto register_err_cleanup; + } + rc = qpnpint_init_irq_data(&chip_data[busno], irq_d, hwirq); + if (rc) { + pr_err("%s: failed to init irq data %d with " + "rc %d\n", __func__, irq, rc); + goto register_err_cleanup; + } + + irq_domain_register_irq(domain, hwirq); + + irq_set_chip_and_handler(irq, + &qpnpint_chip, + handle_level_irq); + irq_set_chip_data(irq, irq_d); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + return 0; + +register_err_cleanup: + irq_free_desc(irq); + if (!IS_ERR(irq_d)) { + if (irq_d->per_d->use_count == 1) + kfree(irq_d->per_d); + else + irq_d->per_d->use_count--; + kfree(irq_d); + } + return rc; +} + +static int qpnpint_irq_domain_dt_translate(struct irq_domain *d, + struct device_node *controller, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + struct qpnp_irq_spec addr; + struct q_chip_data *chip_d = d->priv; + int ret; + + pr_debug("%s: intspec[0] 0x%x intspec[1] 0x%x intspec[2] 0x%x\n", + __func__, intspec[0], intspec[1], intspec[2]); + + if (d->of_node != controller) + return -EINVAL; + if (intsize != 3) + return -EINVAL; + + addr.irq = intspec[2] & 0x7; + addr.per = intspec[1] & 0xFF; + addr.slave = intspec[0] & 0xF; + + ret = qpnpint_encode_hwirq(&addr); + if (ret < 0) { + pr_err("%s: invalid intspec\n", __func__); + return ret; + } + *out_hwirq = ret; + *out_type = IRQ_TYPE_NONE; + + /** + * Register the interrupt if it's not already registered. + * This implies that mapping a qpnp interrupt allocates + * resources. + */ + ret = qpnpint_register_int(chip_d->bus_nr, *out_hwirq); + if (ret && ret != -EEXIST) { + pr_err("%s: Cannot register hwirq %lu\n", __func__, *out_hwirq); + return ret; + } + + return 0; +} + +const struct irq_domain_ops qpnpint_irq_domain_ops = { + .dt_translate = qpnpint_irq_domain_dt_translate, +}; + +int qpnpint_register_controller(unsigned int busno, + struct qpnp_local_int *li_cb) +{ + if (busno >= QPNPINT_MAX_BUSSES) + return -EINVAL; + chip_data[busno].cb = *li_cb; + chip_data[busno].spmi_ctrl = spmi_busnum_to_ctrl(busno); + if (!chip_data[busno].spmi_ctrl) + return -ENOENT; + + return 0; +} +EXPORT_SYMBOL(qpnpint_register_controller); + +int qpnpint_handle_irq(struct spmi_controller *spmi_ctrl, + struct qpnp_irq_spec *spec) +{ + struct irq_domain *domain; + unsigned long hwirq, busno; + int irq; + + pr_debug("spec slave = %u per = %u irq = %u\n", + spec->slave, spec->per, spec->irq); + + if (!spec || !spmi_ctrl) + return -EINVAL; + + busno = spmi_ctrl->nr; + if (busno >= QPNPINT_MAX_BUSSES) + return -EINVAL; + + hwirq = qpnpint_encode_hwirq(spec); + if (hwirq < 0) { + pr_err("%s: invalid irq spec passed\n", __func__); + return -EINVAL; + } + + domain = &chip_data[busno].domain; + irq = irq_domain_to_irq(domain, hwirq); + + generic_handle_irq(irq); + + return 0; +} +EXPORT_SYMBOL(qpnpint_handle_irq); + +/** + * This assumes that there's a relationship between the order of the interrupt + * controllers specified to of_irq_match() is the SPMI device topology. If + * this ever turns out to be a bad assumption, then of_irq_init_cb_t should + * be modified to pass a parameter to this function. + */ +static int qpnpint_cnt __initdata; + +int __init qpnpint_of_init(struct device_node *node, struct device_node *parent) +{ + struct q_chip_data *chip_d = &chip_data[qpnpint_cnt]; + struct irq_domain *domain = &chip_d->domain; + + INIT_RADIX_TREE(&chip_d->per_tree, GFP_ATOMIC); + + domain->irq_base = irq_domain_find_free_range(0, QPNPINT_NR_IRQS); + domain->nr_irq = QPNPINT_NR_IRQS; + domain->of_node = of_node_get(node); + domain->priv = chip_d; + domain->ops = &qpnpint_irq_domain_ops; + irq_domain_add(domain); + + pr_info("irq_base = %d\n", domain->irq_base); + + qpnpint_cnt++; + + return 0; +} +EXPORT_SYMBOL(qpnpint_of_init); diff --git a/drivers/spmi/qpnp.c b/drivers/spmi/qpnp.c new file mode 100644 index 0000000000000000000000000000000000000000..a164efbe835594b2ae4a7373135e8e1a721194aa --- /dev/null +++ b/drivers/spmi/qpnp.c @@ -0,0 +1,56 @@ +/* Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 handling based on platform.c. + */ + +#include +#include + +/** + * qpnp_get_resource - get a resource for a device + * @dev: qpnp device + * @type: resource type + * @num: resource index + */ +struct resource *qpnp_get_resource(struct spmi_device *dev, + unsigned int node_idx, unsigned int type, + unsigned int res_num) +{ + int i; + + for (i = 0; i < dev->dev_node[node_idx].num_resources; i++) { + struct resource *r = &dev->dev_node[node_idx].resource[i]; + + if (type == resource_type(r) && res_num-- == 0) + return r; + } + return NULL; +} +EXPORT_SYMBOL_GPL(qpnp_get_resource); + +/** + * qpnp_get_irq - get an IRQ for a device + * @dev: qpnp device + * @num: IRQ number index + */ +int qpnp_get_irq(struct spmi_device *dev, unsigned int node_idx, + unsigned int res_num) +{ + struct resource *r = qpnp_get_resource(dev, node_idx, + IORESOURCE_IRQ, res_num); + + return r ? r->start : -ENXIO; +} +EXPORT_SYMBOL_GPL(qpnp_get_irq); + diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c new file mode 100644 index 0000000000000000000000000000000000000000..f22b900cf6ed3654f161117827360e2237fa8c34 --- /dev/null +++ b/drivers/spmi/spmi-pmic-arb.c @@ -0,0 +1,722 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +#define SPMI_PMIC_ARB_NAME "spmi_pmic_arb" + +/* PMIC Arbiter configuration registers */ +#define PMIC_ARB_VERSION 0x0000 +#define PMIC_ARB_INT_EN 0x0004 + +/* PMIC Arbiter channel registers */ +#define PMIC_ARB_CMD(N) (0x0800 + (0x80 * (N))) +#define PMIC_ARB_CONFIG(N) (0x0804 + (0x80 * (N))) +#define PMIC_ARB_STATUS(N) (0x0808 + (0x80 * (N))) +#define PMIC_ARB_WDATA0(N) (0x0810 + (0x80 * (N))) +#define PMIC_ARB_WDATA1(N) (0x0814 + (0x80 * (N))) +#define PMIC_ARB_RDATA0(N) (0x0818 + (0x80 * (N))) +#define PMIC_ARB_RDATA1(N) (0x081C + (0x80 * (N))) + +/* Interrupt Controller */ +#define SPMI_PIC_OWNER_ACC_STATUS(M, N) (0x0000 + ((32 * (M)) + (4 * (N)))) +#define SPMI_PIC_ACC_ENABLE(N) (0x0200 + (4 * (N))) +#define SPMI_PIC_IRQ_STATUS(N) (0x0600 + (4 * (N))) +#define SPMI_PIC_IRQ_CLEAR(N) (0x0A00 + (4 * (N))) + +/* Channel Status fields */ +enum pmic_arb_chnl_status { + PMIC_ARB_STATUS_DONE = (1 << 0), + PMIC_ARB_STATUS_FAILURE = (1 << 1), + PMIC_ARB_STATUS_DENIED = (1 << 2), + PMIC_ARB_STATUS_DROPPED = (1 << 3), +}; + +/* Command register fields */ +#define PMIC_ARB_CMD_MAX_BYTE_COUNT 8 + +/* Command Opcodes */ +enum pmic_arb_cmd_op_code { + PMIC_ARB_OP_EXT_WRITEL = 0, + PMIC_ARB_OP_EXT_READL = 1, + PMIC_ARB_OP_EXT_WRITE = 2, + PMIC_ARB_OP_RESET = 3, + PMIC_ARB_OP_SLEEP = 4, + PMIC_ARB_OP_SHUTDOWN = 5, + PMIC_ARB_OP_WAKEUP = 6, + PMIC_ARB_OP_AUTHENTICATE = 7, + PMIC_ARB_OP_MSTR_READ = 8, + PMIC_ARB_OP_MSTR_WRITE = 9, + PMIC_ARB_OP_EXT_READ = 13, + PMIC_ARB_OP_WRITE = 14, + PMIC_ARB_OP_READ = 15, + PMIC_ARB_OP_ZERO_WRITE = 16, +}; + +/* Maximum number of support PMIC peripherals */ +#define PMIC_ARB_MAX_PERIPHS 256 +#define PMIC_ARB_PERIPH_ID_VALID (1 << 15) +#define PMIC_ARB_TIMEOUT_US 100 + +#define PMIC_ARB_APID_MASK 0xFF +#define PMIC_ARB_PPID_MASK 0xFFF +/* extract PPID and APID from interrupt map in .dts config file format */ +#define PMIC_ARB_DEV_TRE_2_PPID(MAP_COMPRS_VAL) \ + ((MAP_COMPRS_VAL) >> (20)) +#define PMIC_ARB_DEV_TRE_2_APID(MAP_COMPRS_VAL) \ + ((MAP_COMPRS_VAL) & PMIC_ARB_APID_MASK) + +/** + * base - base address of the PMIC Arbiter core registers. + * intr - base address of the SPMI interrupt control registers + */ +struct spmi_pmic_arb_dev { + struct spmi_controller controller; + struct device *dev; + struct device *slave; + void __iomem *base; + void __iomem *intr; + int pic_irq; + spinlock_t lock; + u8 owner; + u8 channel; + u8 min_apid; + u8 max_apid; + u16 periph_id_map[PMIC_ARB_MAX_PERIPHS]; +}; + +static u32 pmic_arb_read(struct spmi_pmic_arb_dev *dev, u32 offset) +{ + u32 val = readl_relaxed(dev->base + offset); + pr_debug("address 0x%p, val 0x%x\n", dev->base + offset, val); + return val; +} + +static void pmic_arb_write(struct spmi_pmic_arb_dev *dev, u32 offset, u32 val) +{ + pr_debug("address 0x%p, val 0x%x\n", dev->base + offset, val); + writel_relaxed(val, dev->base + offset); +} + +static int pmic_arb_wait_for_done(struct spmi_pmic_arb_dev *dev) +{ + u32 status = 0; + u32 timeout = PMIC_ARB_TIMEOUT_US; + u32 offset = PMIC_ARB_STATUS(dev->channel); + + while (timeout--) { + status = pmic_arb_read(dev, offset); + + if (status & PMIC_ARB_STATUS_DONE) { + if (status & PMIC_ARB_STATUS_DENIED) { + dev_err(dev->dev, + "%s: transaction denied (0x%x)\n", + __func__, status); + return -EPERM; + } + + if (status & PMIC_ARB_STATUS_FAILURE) { + dev_err(dev->dev, + "%s: transaction failed (0x%x)\n", + __func__, status); + return -EIO; + } + + if (status & PMIC_ARB_STATUS_DROPPED) { + dev_err(dev->dev, + "%s: transaction dropped (0x%x)\n", + __func__, status); + return -EIO; + } + + return 0; + } + udelay(1); + } + + dev_err(dev->dev, "%s: timeout, status 0x%x\n", __func__, status); + return -ETIMEDOUT; +} + +static void pa_read_data(struct spmi_pmic_arb_dev *dev, u8 *buf, u32 reg, u8 bc) +{ + u32 data = pmic_arb_read(dev, reg); + + switch (bc & 0x3) { + case 3: + *buf++ = data & 0xff; + data >>= 8; + case 2: + *buf++ = data & 0xff; + data >>= 8; + case 1: + *buf++ = data & 0xff; + data >>= 8; + case 0: + *buf++ = data & 0xff; + default: + break; + } +} + +static void +pa_write_data(struct spmi_pmic_arb_dev *dev, u8 *buf, u32 reg, u8 bc) +{ + u32 data = 0; + + switch (bc & 0x3) { + case 3: + data = (buf[0]|buf[1]<<8|buf[2]<<16|buf[3]<<24); + break; + case 2: + data = (buf[0]|buf[1]<<8|buf[2]<<16); + break; + case 1: + data = (buf[0]|buf[1]<<8); + break; + case 0: + data = (buf[0]); + break; + default: + break; + } + + pmic_arb_write(dev, reg, data); +} + +/* Non-data command */ +static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl); + unsigned long flags; + u32 cmd; + int rc; + + pr_debug("op:0x%x sid:%d\n", opc, sid); + + /* Check for valid non-data command */ + if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP) + return -EINVAL; + + cmd = ((opc | 0x40) << 27) | ((sid & 0xf) << 20); + + spin_lock_irqsave(&pmic_arb->lock, flags); + pmic_arb_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); + rc = pmic_arb_wait_for_done(pmic_arb); + spin_unlock_irqrestore(&pmic_arb->lock, flags); + + return rc; +} + +static int pmic_arb_read_cmd(struct spmi_controller *ctrl, + u8 opc, u8 sid, u16 addr, u8 bc, u8 *buf) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl); + unsigned long flags; + u32 cmd; + int rc; + + pr_debug("op:0x%x sid:%d bc:%d addr:0x%x\n", opc, sid, bc, addr); + + /* Check the opcode */ + if (opc >= 0x60 && opc <= 0x7F) + opc = PMIC_ARB_OP_READ; + else if (opc >= 0x20 && opc <= 0x2F) + opc = PMIC_ARB_OP_EXT_READ; + else if (opc >= 0x38 && opc <= 0x3F) + opc = PMIC_ARB_OP_EXT_READL; + else + return -EINVAL; + + cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); + + spin_lock_irqsave(&pmic_arb->lock, flags); + pmic_arb_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); + rc = pmic_arb_wait_for_done(pmic_arb); + if (rc) + goto done; + + /* Read from FIFO, note 'bc' is actually number of bytes minus 1 */ + pa_read_data(pmic_arb, buf, PMIC_ARB_RDATA0(pmic_arb->channel), bc); + + if (bc > 3) + pa_read_data(pmic_arb, buf + 4, + PMIC_ARB_RDATA1(pmic_arb->channel), bc); + +done: + spin_unlock_irqrestore(&pmic_arb->lock, flags); + return rc; +} + +static int pmic_arb_write_cmd(struct spmi_controller *ctrl, + u8 opc, u8 sid, u16 addr, u8 bc, u8 *buf) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl); + unsigned long flags; + u32 cmd; + int rc; + + pr_debug("op:0x%x sid:%d bc:%d addr:0x%x\n", opc, sid, bc, addr); + + /* Check the opcode */ + if (opc >= 0x40 && opc <= 0x5F) + opc = PMIC_ARB_OP_WRITE; + else if (opc >= 0x00 && opc <= 0x0F) + opc = PMIC_ARB_OP_EXT_WRITE; + else if (opc >= 0x30 && opc <= 0x37) + opc = PMIC_ARB_OP_EXT_WRITEL; + else if (opc >= 0x80 && opc <= 0xFF) + opc = PMIC_ARB_OP_ZERO_WRITE; + else + return -EINVAL; + + cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); + + /* Write data to FIFOs */ + spin_lock_irqsave(&pmic_arb->lock, flags); + pa_write_data(pmic_arb, buf, PMIC_ARB_WDATA0(pmic_arb->channel), bc); + + if (bc > 3) + pa_write_data(pmic_arb, buf + 4, + PMIC_ARB_WDATA1(pmic_arb->channel), bc); + + /* Start the transaction */ + pmic_arb_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); + rc = pmic_arb_wait_for_done(pmic_arb); + spin_unlock_irqrestore(&pmic_arb->lock, flags); + + return rc; +} + +/* APID to PPID */ +static u16 get_peripheral_id(struct spmi_pmic_arb_dev *pmic_arb, u8 apid) +{ + return pmic_arb->periph_id_map[apid] & PMIC_ARB_PPID_MASK; +} + +/* APID to PPID, returns valid flag */ +static int is_apid_valid(struct spmi_pmic_arb_dev *pmic_arb, u8 apid) +{ + return pmic_arb->periph_id_map[apid] & PMIC_ARB_PERIPH_ID_VALID; +} + +/* PPID to APID */ +static uint32_t map_peripheral_id(struct spmi_pmic_arb_dev *pmic_arb, u16 ppid) +{ + int first = pmic_arb->min_apid; + int last = pmic_arb->max_apid; + int i; + + /* Search table for a matching PPID */ + for (i = first; i <= last; ++i) { + if ((pmic_arb->periph_id_map[i] & PMIC_ARB_PPID_MASK) == ppid) + return i; + } + + dev_err(pmic_arb->dev, "Unknown ppid 0x%x\n", ppid); + return PMIC_ARB_MAX_PERIPHS; +} + +/* Enable interrupt at the PMIC Arbiter PIC */ +static int pmic_arb_pic_enable(struct spmi_controller *ctrl, + struct qpnp_irq_spec *spec, uint32_t data) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl); + u8 apid = data & PMIC_ARB_APID_MASK; + unsigned long flags; + u32 status; + + dev_dbg(pmic_arb->dev, "PIC enable, apid:0x%x, sid:0x%x, pid:0x%x\n", + apid, spec->slave, spec->per); + + if (data < pmic_arb->min_apid || data > pmic_arb->max_apid) { + dev_err(pmic_arb->dev, "int enable: invalid APID %d\n", data); + return -EINVAL; + } + + if (!is_apid_valid(pmic_arb, apid)) { + dev_err(pmic_arb->dev, "int enable: int not supported\n"); + return -EINVAL; + } + + spin_lock_irqsave(&pmic_arb->lock, flags); + status = readl_relaxed(pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid)); + if (!status) { + writel_relaxed(0x1, pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid)); + /* Interrupt needs to be enabled before returning to caller */ + wmb(); + } + spin_unlock_irqrestore(&pmic_arb->lock, flags); + return 0; +} + +/* Disable interrupt at the PMIC Arbiter PIC */ +static int pmic_arb_pic_disable(struct spmi_controller *ctrl, + struct qpnp_irq_spec *spec, uint32_t data) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl); + u8 apid = data & PMIC_ARB_APID_MASK; + unsigned long flags; + u32 status; + + dev_dbg(pmic_arb->dev, "PIC disable, apid:0x%x, sid:0x%x, pid:0x%x\n", + apid, spec->slave, spec->per); + + if (data < pmic_arb->min_apid || data > pmic_arb->max_apid) { + dev_err(pmic_arb->dev, "int disable: invalid APID %d\n", data); + return -EINVAL; + } + + if (!is_apid_valid(pmic_arb, apid)) { + dev_err(pmic_arb->dev, "int disable: int not supported\n"); + return -EINVAL; + } + + spin_lock_irqsave(&pmic_arb->lock, flags); + status = readl_relaxed(pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid)); + if (status) { + writel_relaxed(0x0, pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid)); + /* Interrupt needs to be disabled before returning to caller */ + wmb(); + } + spin_unlock_irqrestore(&pmic_arb->lock, flags); + return 0; +} + +static irqreturn_t +periph_interrupt(struct spmi_pmic_arb_dev *pmic_arb, u8 apid) +{ + u16 ppid = get_peripheral_id(pmic_arb, apid); + void __iomem *base = pmic_arb->intr; + u8 sid = (ppid >> 8) & 0x0F; + u8 pid = ppid & 0xFF; + u32 status; + int i; + + if (!is_apid_valid(pmic_arb, apid)) { + dev_err(pmic_arb->dev, "unknown peripheral id 0x%x\n", ppid); + /* return IRQ_NONE; */ + } + + /* Read the peripheral specific interrupt bits */ + status = readl_relaxed(base + SPMI_PIC_IRQ_STATUS(apid)); + + /* Clear the peripheral interrupts */ + writel_relaxed(status, base + SPMI_PIC_IRQ_CLEAR(apid)); + /* Interrupt needs to be cleared/acknowledged before exiting ISR */ + mb(); + + dev_dbg(pmic_arb->dev, + "interrupt, apid:0x%x, sid:0x%x, pid:0x%x, intr:0x%x\n", + apid, sid, pid, status); + + /* Send interrupt notification */ + for (i = 0; status && i < 8; ++i, status >>= 1) { + if (status & 0x1) { + struct qpnp_irq_spec irq_spec = { + .slave = sid, + .per = pid, + .irq = i, + }; + qpnpint_handle_irq(&pmic_arb->controller, &irq_spec); + } + } + return IRQ_HANDLED; +} + +/* Peripheral interrupt handler */ +static irqreturn_t pmic_arb_periph_irq(int irq, void *dev_id) +{ + struct spmi_pmic_arb_dev *pmic_arb = dev_id; + void __iomem *intr = pmic_arb->intr; + u8 ee = pmic_arb->owner; + u32 ret = IRQ_NONE; + u32 status; + + int first = pmic_arb->min_apid >> 5; + int last = pmic_arb->max_apid >> 5; + int i, j; + + dev_dbg(pmic_arb->dev, "Peripheral interrupt detected\n"); + + /* Check the accumulated interrupt status */ + for (i = first; i <= last; ++i) { + status = readl_relaxed(intr + SPMI_PIC_OWNER_ACC_STATUS(ee, i)); + + for (j = 0; status && j < 32; ++j, status >>= 1) { + if (status & 0x1) { + u8 id = (i * 32) + j; + ret |= periph_interrupt(pmic_arb, id); + } + } + } + + return ret; +} + +/* Callback to register an APID for specific slave/peripheral */ +static int pmic_arb_intr_priv_data(struct spmi_controller *ctrl, + struct qpnp_irq_spec *spec, uint32_t *data) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl); + u16 ppid = ((spec->slave & 0x0F) << 8) | (spec->per & 0xFF); + *data = map_peripheral_id(pmic_arb, ppid); + return 0; +} + +static int __devinit +spmi_pmic_arb_get_property(struct platform_device *pdev, char *pname, u32 *prop) +{ + int ret = of_property_read_u32(pdev->dev.of_node, pname, prop); + + if (ret) + dev_err(&pdev->dev, "missing property: %s\n", pname); + else + pr_debug("%s = 0x%x\n", pname, *prop); + + return ret; +} + +static int __devinit spmi_pmic_arb_get_map_data(struct platform_device *pdev, + struct spmi_pmic_arb_dev *pmic_arb) +{ + int i; + int ret; + int map_size; + u32 *map_data; + const int map_width = sizeof(*map_data); + const struct device_node *of_node = pdev->dev.of_node; + + /* Get size of the mapping table (in bytes) */ + if (!of_get_property(of_node, "qcom,pmic-arb-ppid-map", &map_size)) { + dev_err(&pdev->dev, "missing ppid mapping table\n"); + return -ENODEV; + } + + /* Map size can't exceed the maximum number of peripherals */ + if (map_size == 0 || map_size > map_width * PMIC_ARB_MAX_PERIPHS) { + dev_err(&pdev->dev, "map size of %d is not valid\n", map_size); + return -ENODEV; + } + + map_data = kzalloc(map_size, GFP_KERNEL); + if (!map_data) { + dev_err(&pdev->dev, "can not allocate map data\n"); + return -ENOMEM; + } + + ret = of_property_read_u32_array(of_node, + "qcom,pmic-arb-ppid-map", map_data, map_size/sizeof(u32)); + if (ret) { + dev_err(&pdev->dev, "invalid or missing property: ppid-map\n"); + goto err; + }; + + pmic_arb->max_apid = 0; + pmic_arb->min_apid = PMIC_ARB_MAX_PERIPHS - 1; + + /* Build the mapping table from the data */ + for (i = 0; i < map_size/sizeof(u32);) { + u32 map_compressed_val = map_data[i++]; + u32 ppid = PMIC_ARB_DEV_TRE_2_PPID(map_compressed_val) ; + u32 apid = PMIC_ARB_DEV_TRE_2_APID(map_compressed_val) ; + + if (pmic_arb->periph_id_map[apid] & PMIC_ARB_PERIPH_ID_VALID) + dev_warn(&pdev->dev, "duplicate APID 0x%x\n", apid); + + pmic_arb->periph_id_map[apid] = ppid | PMIC_ARB_PERIPH_ID_VALID; + + if (apid > pmic_arb->max_apid) + pmic_arb->max_apid = apid; + + if (apid < pmic_arb->min_apid) + pmic_arb->min_apid = apid; + } + + pr_debug("%d value(s) mapped, min:%d, max:%d\n", + map_size/map_width, pmic_arb->min_apid, pmic_arb->max_apid); + +err: + kfree(map_data); + return ret; +} + +static struct qpnp_local_int spmi_pmic_arb_intr_cb = { + .mask = pmic_arb_pic_disable, + .unmask = pmic_arb_pic_enable, + .register_priv_data = pmic_arb_intr_priv_data, +}; + +static int __devinit spmi_pmic_arb_probe(struct platform_device *pdev) +{ + struct spmi_pmic_arb_dev *pmic_arb; + struct resource *mem_res; + u32 cell_index; + u32 prop; + int ret = 0; + + pr_debug("SPMI PMIC Arbiter\n"); + + pmic_arb = devm_kzalloc(&pdev->dev, + sizeof(struct spmi_pmic_arb_dev), GFP_KERNEL); + if (!pmic_arb) { + dev_err(&pdev->dev, "can not allocate pmic_arb data\n"); + return -ENOMEM; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + dev_err(&pdev->dev, "missing base memory resource\n"); + return -ENODEV; + } + + pmic_arb->base = devm_ioremap(&pdev->dev, + mem_res->start, resource_size(mem_res)); + if (!pmic_arb->base) { + dev_err(&pdev->dev, "ioremap of 'base' failed\n"); + return -ENOMEM; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!mem_res) { + dev_err(&pdev->dev, "missing mem resource (interrupts)\n"); + return -ENODEV; + } + + pmic_arb->intr = devm_ioremap(&pdev->dev, + mem_res->start, resource_size(mem_res)); + if (!pmic_arb->intr) { + dev_err(&pdev->dev, "ioremap of 'intr' failed\n"); + return -ENOMEM; + } + + pmic_arb->pic_irq = platform_get_irq(pdev, 0); + if (!pmic_arb->pic_irq) { + dev_err(&pdev->dev, "missing IRQ resource\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, pmic_arb->pic_irq, + pmic_arb_periph_irq, IRQF_TRIGGER_HIGH, pdev->name, pmic_arb); + if (ret) { + dev_err(&pdev->dev, "request IRQ failed\n"); + return ret; + } + + /* Get properties from the device tree */ + ret = spmi_pmic_arb_get_property(pdev, "cell-index", &cell_index); + if (ret) + return -ENODEV; + + ret = spmi_pmic_arb_get_map_data(pdev, pmic_arb); + if (ret) + return ret; + + ret = spmi_pmic_arb_get_property(pdev, "qcom,pmic-arb-ee", &prop); + if (ret) + return -ENODEV; + pmic_arb->owner = (u8)prop; + + ret = spmi_pmic_arb_get_property(pdev, "qcom,pmic-arb-channel", &prop); + if (ret) + return -ENODEV; + pmic_arb->channel = (u8)prop; + + pmic_arb->dev = &pdev->dev; + platform_set_drvdata(pdev, pmic_arb); + spmi_set_ctrldata(&pmic_arb->controller, pmic_arb); + + spin_lock_init(&pmic_arb->lock); + + pmic_arb->controller.nr = cell_index; + pmic_arb->controller.dev.parent = pdev->dev.parent; + pmic_arb->controller.dev.of_node = of_node_get(pdev->dev.of_node); + + /* Callbacks */ + pmic_arb->controller.cmd = pmic_arb_cmd; + pmic_arb->controller.read_cmd = pmic_arb_read_cmd; + pmic_arb->controller.write_cmd = pmic_arb_write_cmd; + + ret = spmi_add_controller(&pmic_arb->controller); + if (ret) + goto err_add_controller; + + /* Register the interrupt enable/disable functions */ + qpnpint_register_controller(cell_index, &spmi_pmic_arb_intr_cb); + + /* Register device(s) from the device tree */ + of_spmi_register_devices(&pmic_arb->controller); + + pr_debug("PMIC Arb Version 0x%x\n", + pmic_arb_read(pmic_arb, PMIC_ARB_VERSION)); + + return 0; + +err_add_controller: + platform_set_drvdata(pdev, NULL); + return ret; +} + +static int __devexit spmi_pmic_arb_remove(struct platform_device *pdev) +{ + struct spmi_pmic_arb_dev *pmic_arb = platform_get_drvdata(pdev); + + free_irq(pmic_arb->pic_irq, pmic_arb); + platform_set_drvdata(pdev, NULL); + spmi_del_controller(&pmic_arb->controller); + return 0; +} + +static struct of_device_id spmi_pmic_arb_match_table[] = { + { .compatible = "qcom,spmi-pmic-arb", + }, + {} +}; + +static struct platform_driver spmi_pmic_arb_driver = { + .probe = spmi_pmic_arb_probe, + .remove = __exit_p(spmi_pmic_arb_remove), + .driver = { + .name = SPMI_PMIC_ARB_NAME, + .owner = THIS_MODULE, + .of_match_table = spmi_pmic_arb_match_table, + }, +}; + +static int __init spmi_pmic_arb_init(void) +{ + return platform_driver_register(&spmi_pmic_arb_driver); +} +postcore_initcall(spmi_pmic_arb_init); + +static void __exit spmi_pmic_arb_exit(void) +{ + platform_driver_unregister(&spmi_pmic_arb_driver); +} +module_exit(spmi_pmic_arb_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:spmi_pmic_arb"); diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c new file mode 100644 index 0000000000000000000000000000000000000000..3f635329e97c16d98c232ef7cadf8e25f913501a --- /dev/null +++ b/drivers/spmi/spmi.c @@ -0,0 +1,790 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 spmii_boardinfo { + struct list_head list; + struct spmi_boardinfo board_info; +}; + +static DEFINE_MUTEX(board_lock); +static LIST_HEAD(board_list); +static LIST_HEAD(spmi_ctrl_list); +static DEFINE_IDR(ctrl_idr); +static struct device_type spmi_ctrl_type = { 0 }; + +#define to_spmi(dev) platform_get_drvdata(to_platform_device(dev)) + +/* Forward declarations */ +struct bus_type spmi_bus_type; +static int spmi_register_controller(struct spmi_controller *ctrl); + +/** + * spmi_busnum_to_ctrl: Map bus number to controller + * @busnum: bus number + * Returns controller representing this bus number + */ +struct spmi_controller *spmi_busnum_to_ctrl(u32 bus_num) +{ + struct spmi_controller *ctrl; + + mutex_lock(&board_lock); + list_for_each_entry(ctrl, &spmi_ctrl_list, list) { + if (bus_num == ctrl->nr) { + mutex_unlock(&board_lock); + return ctrl; + } + } + mutex_unlock(&board_lock); + return NULL; +} +EXPORT_SYMBOL_GPL(spmi_busnum_to_ctrl); + +/** + * spmi_add_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 SPMI framework registers the controller. + * Function will return -EBUSY if the number is in use. + */ +int spmi_add_controller(struct spmi_controller *ctrl) +{ + int id; + int status; + + pr_debug("adding controller for bus %d (0x%p)\n", ctrl->nr, ctrl); + + if (ctrl->nr & ~MAX_ID_MASK) { + pr_err("invalid bus identifier %d\n", ctrl->nr); + return -EINVAL; + } + +retry: + if (idr_pre_get(&ctrl_idr, GFP_KERNEL) == 0) { + pr_err("no free memory for idr\n"); + return -ENOMEM; + } + + mutex_lock(&board_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(&board_lock); + if (status == -EAGAIN) + goto retry; + + if (status == 0) + status = spmi_register_controller(ctrl); + return status; +} +EXPORT_SYMBOL_GPL(spmi_add_controller); + +/** + * spmi_del_controller: Controller tear-down. + * @ctrl: controller to which this device is to be added to. + * + * Controller added with the above API is torn down using this API. + */ +int spmi_del_controller(struct spmi_controller *ctrl) +{ + return -ENXIO; +} +EXPORT_SYMBOL_GPL(spmi_del_controller); + +#define spmi_device_attr_gr NULL +#define spmi_device_uevent NULL +static void spmi_dev_release(struct device *dev) +{ + struct spmi_device *spmidev = to_spmi_device(dev); + kfree(spmidev); +} + +static struct device_type spmi_dev_type = { + .groups = spmi_device_attr_gr, + .uevent = spmi_device_uevent, + .release = spmi_dev_release, +}; + +/** + * spmi_alloc_device: Allocate a new SPMI devices. + * @ctrl: controller to which this device is to be added to. + * Context: can sleep + * + * Allows a driver to allocate and initialize a SPMI device without + * registering it immediately. This allows a driver to directly fill + * the spmi_device structure before calling spmi_add_device(). + * + * Caller is responsible to call spmi_add_device() on the returned + * spmi_device. If the caller needs to discard the spmi_device without + * adding it, then spmi_dev_put() should be called. + */ +struct spmi_device *spmi_alloc_device(struct spmi_controller *ctrl) +{ + struct spmi_device *spmidev; + + if (!ctrl) { + pr_err("Missing SPMI controller\n"); + return NULL; + } + + spmidev = kzalloc(sizeof(*spmidev), GFP_KERNEL); + if (!spmidev) { + dev_err(&ctrl->dev, "unable to allocate spmi_device\n"); + return NULL; + } + + spmidev->ctrl = ctrl; + spmidev->dev.parent = ctrl->dev.parent; + spmidev->dev.bus = &spmi_bus_type; + spmidev->dev.type = &spmi_dev_type; + device_initialize(&spmidev->dev); + + return spmidev; +} +EXPORT_SYMBOL_GPL(spmi_alloc_device); + +/* Validate the SPMI device structure */ +static struct device *get_valid_device(struct spmi_device *spmidev) +{ + struct device *dev; + + if (!spmidev) + return NULL; + + dev = &spmidev->dev; + if (dev->bus != &spmi_bus_type || dev->type != &spmi_dev_type) + return NULL; + + return dev; +} + +/** + * spmi_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 spmi_add_device(struct spmi_device *spmidev) +{ + int rc; + struct device *dev = get_valid_device(spmidev); + + if (!dev) { + pr_err("%s: invalid SPMI device\n", __func__); + return -EINVAL; + } + + /* Set the device name */ + dev_set_name(dev, "%s-%p", spmidev->name, spmidev); + + /* Device may be bound to an active driver when this returns */ + rc = device_add(dev); + + if (rc < 0) + dev_err(dev, "Can't add %s, status %d\n", dev_name(dev), rc); + else + dev_dbg(dev, "device %s registered\n", dev_name(dev)); + + return rc; +} +EXPORT_SYMBOL_GPL(spmi_add_device); + +/** + * spmi_new_device: Instantiates a new SPMI device + * @ctrl: controller to which this device is to be added to. + * @info: board information for this device. + * + * Returns the new device or NULL. + */ +struct spmi_device *spmi_new_device(struct spmi_controller *ctrl, + struct spmi_boardinfo const *info) +{ + struct spmi_device *spmidev; + int rc; + + if (!ctrl || !info) + return NULL; + + spmidev = spmi_alloc_device(ctrl); + if (!spmidev) + return NULL; + + spmidev->name = info->name; + spmidev->sid = info->slave_id; + spmidev->dev.of_node = info->of_node; + spmidev->dev.platform_data = (void *)info->platform_data; + spmidev->num_dev_node = info->num_dev_node; + spmidev->dev_node = info->dev_node; + + rc = spmi_add_device(spmidev); + if (rc < 0) { + spmi_dev_put(spmidev); + return NULL; + } + + return spmidev; +} +EXPORT_SYMBOL_GPL(spmi_new_device); + +/* spmi_remove_device: Remove the effect of spmi_add_device() */ +void spmi_remove_device(struct spmi_device *spmi_dev) +{ + device_unregister(&spmi_dev->dev); +} +EXPORT_SYMBOL_GPL(spmi_remove_device); + +/* If controller is not present, only add to boards list */ +static void spmi_match_ctrl_to_boardinfo(struct spmi_controller *ctrl, + struct spmi_boardinfo *bi) +{ + struct spmi_device *spmidev; + + spmidev = spmi_new_device(ctrl, bi); + if (!spmidev) + dev_err(ctrl->dev.parent, "can't create new device for %s\n", + bi->name); +} + +/** + * spmi_register_board_info: Board-initialization routine. + * @bus_num: controller number (bus) on which this device will sit. + * @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 spmi_register_board_info(int busnum, + struct spmi_boardinfo const *info, unsigned n) +{ + int i; + struct spmii_boardinfo *bi; + + bi = kzalloc(n * sizeof(*bi), GFP_KERNEL); + if (!bi) + return -ENOMEM; + + for (i = 0; i < n; i++, bi++, info++) { + struct spmi_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, &spmi_ctrl_list, list) + if (ctrl->nr == busnum) + spmi_match_ctrl_to_boardinfo(ctrl, + &bi->board_info); + mutex_unlock(&board_lock); + } + return 0; +} +EXPORT_SYMBOL_GPL(spmi_register_board_info); + +/* ------------------------------------------------------------------------- */ + +static inline int +spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid) +{ + BUG_ON(!ctrl || !ctrl->cmd); + return ctrl->cmd(ctrl, opcode, sid); +} + +static inline int spmi_read_cmd(struct spmi_controller *ctrl, + u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf) +{ + BUG_ON(!ctrl || !ctrl->read_cmd); + return ctrl->read_cmd(ctrl, opcode, sid, addr, bc, buf); +} + +static inline int spmi_write_cmd(struct spmi_controller *ctrl, + u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf) +{ + BUG_ON(!ctrl || !ctrl->write_cmd); + return ctrl->write_cmd(ctrl, opcode, sid, addr, bc, buf); +} + +/* + * register read/write: 5-bit address, 1 byte of data + * extended register read/write: 8-bit address, up to 16 bytes of data + * extended register read/write long: 16-bit address, up to 8 bytes of data + */ + +/** + * spmi_register_read() - register read + * @dev: SPMI device. + * @sid: slave identifier. + * @ad: slave register address (5-bit address). + * @buf: buffer to be populated with data from the Slave. + * + * Reads 1 byte of data from a Slave device register. + */ +int spmi_register_read(struct spmi_controller *ctrl, u8 sid, u8 addr, u8 *buf) +{ + /* 4-bit Slave Identifier, 5-bit register address */ + if (sid > SPMI_MAX_SLAVE_ID || addr > 0x1F) + return -EINVAL; + + return spmi_read_cmd(ctrl, SPMI_CMD_READ, sid, addr, 0, buf); +} +EXPORT_SYMBOL_GPL(spmi_register_read); + +/** + * spmi_ext_register_read() - extended register read + * @dev: SPMI device. + * @sid: slave identifier. + * @ad: slave register address (8-bit address). + * @len: the request number of bytes to read (up to 16 bytes). + * @buf: buffer to be populated with data from the Slave. + * + * Reads up to 16 bytes of data from the extended register space on a + * Slave device. + */ +int spmi_ext_register_read(struct spmi_controller *ctrl, + u8 sid, u8 addr, u8 *buf, int len) +{ + /* 4-bit Slave Identifier, 8-bit register address, up to 16 bytes */ + if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 16) + return -EINVAL; + + return spmi_read_cmd(ctrl, SPMI_CMD_EXT_READ, sid, addr, len - 1, buf); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_read); + +/** + * spmi_ext_register_readl() - extended register read long + * @dev: SPMI device. + * @sid: slave identifier. + * @ad: slave register address (16-bit address). + * @len: the request number of bytes to read (up to 8 bytes). + * @buf: buffer to be populated with data from the Slave. + * + * Reads up to 8 bytes of data from the extended register space on a + * Slave device using 16-bit address. + */ +int spmi_ext_register_readl(struct spmi_controller *ctrl, + u8 sid, u16 addr, u8 *buf, int len) +{ + /* 4-bit Slave Identifier, 16-bit register address, up to 8 bytes */ + if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 8) + return -EINVAL; + + return spmi_read_cmd(ctrl, SPMI_CMD_EXT_READL, sid, addr, len - 1, buf); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_readl); + +/** + * spmi_register_write() - register write + * @dev: SPMI device. + * @sid: slave identifier. + * @ad: slave register address (5-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * + * Writes 1 byte of data to a Slave device register. + */ +int spmi_register_write(struct spmi_controller *ctrl, u8 sid, u8 addr, u8 *buf) +{ + u8 op = SPMI_CMD_WRITE; + + /* 4-bit Slave Identifier, 5-bit register address */ + if (sid > SPMI_MAX_SLAVE_ID || addr > 0x1F) + return -EINVAL; + + return spmi_write_cmd(ctrl, op, sid, addr, 0, buf); +} +EXPORT_SYMBOL_GPL(spmi_register_write); + +/** + * spmi_register_zero_write() - register zero write + * @dev: SPMI device. + * @sid: slave identifier. + * @data: the data to be written to register 0 (7-bits). + * + * Writes data to register 0 of the Slave device. + */ +int spmi_register_zero_write(struct spmi_controller *ctrl, u8 sid, u8 data) +{ + u8 op = SPMI_CMD_ZERO_WRITE; + + /* 4-bit Slave Identifier, 5-bit register address */ + if (sid > SPMI_MAX_SLAVE_ID) + return -EINVAL; + + return spmi_write_cmd(ctrl, op, sid, 0, 0, &data); +} +EXPORT_SYMBOL_GPL(spmi_register_zero_write); + +/** + * spmi_ext_register_write() - extended register write + * @dev: SPMI device. + * @sid: slave identifier. + * @ad: slave register address (8-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * @len: the request number of bytes to read (up to 16 bytes). + * + * Writes up to 16 bytes of data to the extended register space of a + * Slave device. + */ +int spmi_ext_register_write(struct spmi_controller *ctrl, + u8 sid, u8 addr, u8 *buf, int len) +{ + u8 op = SPMI_CMD_EXT_WRITE; + + /* 4-bit Slave Identifier, 8-bit register address, up to 16 bytes */ + if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 16) + return -EINVAL; + + return spmi_write_cmd(ctrl, op, sid, addr, len - 1, buf); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_write); + +/** + * spmi_ext_register_writel() - extended register write long + * @dev: SPMI device. + * @sid: slave identifier. + * @ad: slave register address (16-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * @len: the request number of bytes to read (up to 8 bytes). + * + * Writes up to 8 bytes of data to the extended register space of a + * Slave device using 16-bit address. + */ +int spmi_ext_register_writel(struct spmi_controller *ctrl, + u8 sid, u16 addr, u8 *buf, int len) +{ + u8 op = SPMI_CMD_EXT_WRITEL; + + /* 4-bit Slave Identifier, 16-bit register address, up to 8 bytes */ + if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 8) + return -EINVAL; + + return spmi_write_cmd(ctrl, op, sid, addr, len - 1, buf); +} +EXPORT_SYMBOL_GPL(spmi_ext_register_writel); + +/** + * spmi_command_reset() - sends RESET command to the specified slave + * @dev: SPMI device. + * @sid: slave identifier. + * + * The Reset command initializes the Slave and forces all registers to + * their reset values. The Slave shall enter the STARTUP state after + * receiving a Reset command. + * + * Returns + * -EINVAL for invalid Slave Identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +int spmi_command_reset(struct spmi_controller *ctrl, u8 sid) +{ + if (sid > SPMI_MAX_SLAVE_ID) + return -EINVAL; + return spmi_cmd(ctrl, SPMI_CMD_RESET, sid); +} +EXPORT_SYMBOL_GPL(spmi_command_reset); + +/** + * spmi_command_sleep() - sends SLEEP command to the specified slave + * @dev: SPMI device. + * @sid: slave identifier. + * + * The Sleep command causes the Slave to enter the user defined SLEEP state. + * + * Returns + * -EINVAL for invalid Slave Identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +int spmi_command_sleep(struct spmi_controller *ctrl, u8 sid) +{ + if (sid > SPMI_MAX_SLAVE_ID) + return -EINVAL; + return spmi_cmd(ctrl, SPMI_CMD_SLEEP, sid); +} +EXPORT_SYMBOL_GPL(spmi_command_sleep); + +/** + * spmi_command_wakeup() - sends WAKEUP command to the specified slave + * @dev: SPMI device. + * @sid: slave identifier. + * + * The Wakeup command causes the Slave to move from the SLEEP state to + * the ACTIVE state. + * + * Returns + * -EINVAL for invalid Slave Identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +int spmi_command_wakeup(struct spmi_controller *ctrl, u8 sid) +{ + if (sid > SPMI_MAX_SLAVE_ID) + return -EINVAL; + return spmi_cmd(ctrl, SPMI_CMD_WAKEUP, sid); +} +EXPORT_SYMBOL_GPL(spmi_command_wakeup); + +/** + * spmi_command_shutdown() - sends SHUTDOWN command to the specified slave + * @dev: SPMI device. + * @sid: slave identifier. + * + * The Shutdown command causes the Slave to enter the SHUTDOWN state. + * + * Returns + * -EINVAL for invalid Slave Identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +int spmi_command_shutdown(struct spmi_controller *ctrl, u8 sid) +{ + if (sid > SPMI_MAX_SLAVE_ID) + return -EINVAL; + return spmi_cmd(ctrl, SPMI_CMD_SHUTDOWN, sid); +} +EXPORT_SYMBOL_GPL(spmi_command_shutdown); + +/* ------------------------------------------------------------------------- */ + +static const struct spmi_device_id *spmi_match(const struct spmi_device_id *id, + const struct spmi_device *spmi_dev) +{ + while (id->name[0]) { + if (strncmp(spmi_dev->name, id->name, SPMI_NAME_SIZE) == 0) + return id; + id++; + } + return NULL; +} + +static int spmi_device_match(struct device *dev, struct device_driver *drv) +{ + struct spmi_device *spmi_dev; + struct spmi_driver *sdrv = to_spmi_driver(drv); + + if (dev->type == &spmi_dev_type) + spmi_dev = to_spmi_device(dev); + else + return 0; + + /* Attempt an OF style match */ + if (of_driver_match_device(dev, drv)) + return 1; + + if (sdrv->id_table) + return spmi_match(sdrv->id_table, spmi_dev) != NULL; + + if (drv->name) + return strncmp(spmi_dev->name, drv->name, SPMI_NAME_SIZE) == 0; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int spmi_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + struct spmi_device *spmi_dev = NULL; + struct spmi_driver *driver; + if (dev->type == &spmi_dev_type) + spmi_dev = to_spmi_device(dev); + + if (!spmi_dev || !dev->driver) + return 0; + + driver = to_spmi_driver(dev->driver); + if (!driver->suspend) + return 0; + + return driver->suspend(spmi_dev, mesg); +} + +static int spmi_legacy_resume(struct device *dev) +{ + struct spmi_device *spmi_dev = NULL; + struct spmi_driver *driver; + if (dev->type == &spmi_dev_type) + spmi_dev = to_spmi_device(dev); + + if (!spmi_dev || !dev->driver) + return 0; + + driver = to_spmi_driver(dev->driver); + if (!driver->resume) + return 0; + + return driver->resume(spmi_dev); +} + +static int spmi_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 spmi_legacy_suspend(dev, PMSG_SUSPEND); +} + +static int spmi_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 spmi_legacy_resume(dev); +} + +#else +#define spmi_pm_suspend NULL +#define spmi_pm_resume NULL +#endif + +static const struct dev_pm_ops spmi_pm_ops = { + .suspend = spmi_pm_suspend, + .resume = spmi_pm_resume, + SET_RUNTIME_PM_OPS( + pm_generic_suspend, + pm_generic_resume, + pm_generic_runtime_idle + ) +}; +struct bus_type spmi_bus_type = { + .name = "spmi", + .match = spmi_device_match, + .pm = &spmi_pm_ops, +}; +EXPORT_SYMBOL_GPL(spmi_bus_type); + +struct device spmi_dev = { + .init_name = "spmi", +}; + +static int spmi_drv_probe(struct device *dev) +{ + const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); + + return sdrv->probe(to_spmi_device(dev)); +} + +static int spmi_drv_remove(struct device *dev) +{ + const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); + + return sdrv->remove(to_spmi_device(dev)); +} + +static void spmi_drv_shutdown(struct device *dev) +{ + const struct spmi_driver *sdrv = to_spmi_driver(dev->driver); + + sdrv->shutdown(to_spmi_device(dev)); +} + +/** + * spmi_driver_register: Client driver registration with SPMI framework. + * @drv: client driver to be associated with client-device. + * + * This API will register the client driver with the SPMI framework. + * It is called from the driver's module-init function. + */ +int spmi_driver_register(struct spmi_driver *drv) +{ + drv->driver.bus = &spmi_bus_type; + + if (drv->probe) + drv->driver.probe = spmi_drv_probe; + + if (drv->remove) + drv->driver.remove = spmi_drv_remove; + + if (drv->shutdown) + drv->driver.shutdown = spmi_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(spmi_driver_register); + +static int spmi_register_controller(struct spmi_controller *ctrl) +{ + int ret = 0; + + /* Can't register until after driver model init */ + if (WARN_ON(!spmi_bus_type.p)) { + ret = -EAGAIN; + goto exit; + } + + dev_set_name(&ctrl->dev, "spmi-%d", ctrl->nr); + ctrl->dev.bus = &spmi_bus_type; + ctrl->dev.type = &spmi_ctrl_type; + ret = device_register(&ctrl->dev); + if (ret) + goto exit; + + dev_dbg(&ctrl->dev, "Bus spmi-%d registered: dev:%x\n", + ctrl->nr, (u32)&ctrl->dev); + + mutex_lock(&board_lock); + list_add_tail(&ctrl->list, &spmi_ctrl_list); + mutex_unlock(&board_lock); + + return 0; + +exit: + mutex_lock(&board_lock); + idr_remove(&ctrl_idr, ctrl->nr); + mutex_unlock(&board_lock); + return ret; +} + +static void __exit spmi_exit(void) +{ + device_unregister(&spmi_dev); + bus_unregister(&spmi_bus_type); +} + +static int __init spmi_init(void) +{ + int retval; + + retval = bus_register(&spmi_bus_type); + if (!retval) + retval = device_register(&spmi_dev); + + if (retval) + bus_unregister(&spmi_bus_type); + + return retval; +} +postcore_initcall(spmi_init); +module_exit(spmi_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_DESCRIPTION("SPMI module"); +MODULE_ALIAS("platform:spmi"); diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 97d412d91458b258401af63d701cd0bbbe9ec5cf..bcec934a43edd9faf07d3b224b0ca9b0a06b50cd 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -84,6 +84,8 @@ source "drivers/staging/zram/Kconfig" source "drivers/staging/zcache/Kconfig" +source "drivers/staging/qcache/Kconfig" + source "drivers/staging/zsmalloc/Kconfig" source "drivers/staging/wlags49_h2/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index ffe7d44374e606cf770f87ca46a5ad8f45753592..c31f2ec1cc43b8bfc4ea24c933e636c355ab8f58 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_DX_SEP) += sep/ obj-$(CONFIG_IIO) += iio/ obj-$(CONFIG_ZRAM) += zram/ obj-$(CONFIG_ZCACHE) += zcache/ +obj-$(CONFIG_QCACHE) += qcache/ obj-$(CONFIG_ZSMALLOC) += zsmalloc/ obj-$(CONFIG_WLAGS49_H2) += wlags49_h2/ obj-$(CONFIG_WLAGS49_H25) += wlags49_h25/ diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c index bda20bf15948703c4f0071eb47b588618c98fff1..8390f5dca196ff5c6da7d6520bd57c9b170b6534 100644 --- a/drivers/staging/android/ashmem.c +++ b/drivers/staging/android/ashmem.c @@ -28,7 +28,8 @@ #include #include #include -#include "ashmem.h" +#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 */ }; @@ -322,6 +325,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); @@ -622,6 +626,90 @@ static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, return ret; } +#ifdef CONFIG_OUTER_CACHE +static unsigned int 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; + + spin_lock(¤t->mm->page_table_lock); + pgd_ptr = pgd_offset(current->mm, virtaddr); + if (pgd_none(*pgd) || pgd_bad(*pgd)) { + pr_err("Failed to convert virtaddr %x to pgd_ptr\n", + virtaddr); + goto done; + } + + pmd_ptr = pmd_offset(pgd_ptr, virtaddr); + if (pmd_none(*pmd_ptr) || pmd_bad(*pmd_ptr)) { + pr_err("Failed to convert pgd_ptr %p to pmd_ptr\n", + (void *)pgd_ptr); + goto done; + } + + pte_ptr = pte_offset_map(pmd_ptr, virtaddr); + if (!pte_ptr) { + pr_err("Failed to convert pmd_ptr %p to pte_ptr\n", + (void *)pmd_ptr); + goto done; + } + pte = *pte_ptr; + physaddr = pte_pfn(pte); + pte_unmap(pte_ptr); +done: + spin_unlock(¤t->mm->page_table_lock); + physaddr <<= PAGE_SHIFT; + return physaddr; +} +#endif + +static int ashmem_cache_op(struct ashmem_area *asma, + void (*cache_func)(unsigned long vstart, unsigned long length, + unsigned long pstart)) +{ + int ret = 0; + struct vm_area_struct *vma; +#ifdef CONFIG_OUTER_CACHE + unsigned long vaddr; +#endif + if (!asma->vm_start) + return -EINVAL; + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, asma->vm_start); + if (!vma) { + ret = -EINVAL; + goto done; + } + if (vma->vm_file != asma->file) { + ret = -EINVAL; + goto done; + } + if ((asma->vm_start + asma->size) > (vma->vm_start + vma->vm_end)) { + ret = -EINVAL; + goto done; + } +#ifndef CONFIG_OUTER_CACHE + cache_func(asma->vm_start, asma->size, 0); +#else + for (vaddr = asma->vm_start; vaddr < asma->vm_start + asma->size; + vaddr += PAGE_SIZE) { + unsigned long physaddr; + physaddr = virtaddr_to_physaddr(vaddr); + if (!physaddr) + return -EINVAL; + cache_func(vaddr, PAGE_SIZE, physaddr); + } +#endif +done: + up_read(¤t->mm->mmap_sem); + if (ret) + asma->vm_start = 0; + return ret; +} + static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; @@ -667,11 +755,73 @@ 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_cache_op(asma, &clean_and_invalidate_caches); + break; + case ASHMEM_CACHE_CLEAN_RANGE: + ret = ashmem_cache_op(asma, &clean_caches); + break; + case ASHMEM_CACHE_INV_RANGE: + ret = ashmem_cache_op(asma, &invalidate_caches); + 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 const struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, diff --git a/drivers/staging/android/binder.c b/drivers/staging/android/binder.c index c2832124bb3e7ade105ef66d82471d2d509d2110..6cd30f1d5bc6b474f5c495dfbedd8c4822652556 100644 --- a/drivers/staging/android/binder.c +++ b/drivers/staging/android/binder.c @@ -98,9 +98,9 @@ enum { BINDER_DEBUG_BUFFER_ALLOC = 1U << 13, BINDER_DEBUG_PRIORITY_CAP = 1U << 14, BINDER_DEBUG_BUFFER_ALLOC_ASYNC = 1U << 15, + BINDER_DEBUG_TOP_ERRORS = 1U << 16, }; -static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR | - BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION; +static uint32_t binder_debug_mask; module_param_named(debug_mask, binder_debug_mask, uint, S_IWUSR | S_IRUGO); static bool binder_debug_no_lock; @@ -644,8 +644,9 @@ static int binder_update_page_range(struct binder_proc *proc, int allocate, goto free_range; if (vma == NULL) { - printk(KERN_ERR "binder: %d: binder_alloc_buf failed to " - "map pages in userspace, no vma\n", proc->pid); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d: binder_alloc_buf failed to " + "map pages in userspace, no vma\n", proc->pid); goto err_no_vma; } @@ -657,8 +658,9 @@ static int binder_update_page_range(struct binder_proc *proc, int allocate, BUG_ON(*page); *page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (*page == NULL) { - printk(KERN_ERR "binder: %d: binder_alloc_buf failed " - "for page at %p\n", proc->pid, page_addr); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d: binder_alloc_buf failed " + "for page at %p\n", proc->pid, page_addr); goto err_alloc_page_failed; } tmp_area.addr = page_addr; @@ -666,18 +668,20 @@ static int binder_update_page_range(struct binder_proc *proc, int allocate, page_array_ptr = page; ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr); if (ret) { - printk(KERN_ERR "binder: %d: binder_alloc_buf failed " - "to map page at %p in kernel\n", - proc->pid, page_addr); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d: binder_alloc_buf failed " + "to map page at %p in kernel\n", + proc->pid, page_addr); goto err_map_kernel_failed; } user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset; ret = vm_insert_page(vma, user_page_addr, page[0]); if (ret) { - printk(KERN_ERR "binder: %d: binder_alloc_buf failed " - "to map page at %lx in userspace\n", - proc->pid, user_page_addr); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d: binder_alloc_buf failed " + "to map page at %lx in userspace\n", + proc->pid, user_page_addr); goto err_vm_insert_page_failed; } /* vm_insert_page does not seem to increment the refcount */ @@ -724,8 +728,9 @@ static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, size_t size; if (proc->vma == NULL) { - printk(KERN_ERR "binder: %d: binder_alloc_buf, no vma\n", - proc->pid); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d: binder_alloc_buf, no vma\n", + proc->pid); return NULL; } @@ -762,8 +767,9 @@ static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, } } if (best_fit == NULL) { - printk(KERN_ERR "binder: %d: binder_alloc_buf size %zd failed, " - "no address space\n", proc->pid, size); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d: binder_alloc_buf size %zd failed, " + "no address space\n", proc->pid, size); return NULL; } if (n == NULL) { @@ -997,8 +1003,9 @@ static int binder_inc_node(struct binder_node *node, int strong, int internal, node->internal_strong_refs == 0 && !(node == binder_context_mgr_node && node->has_strong_ref)) { - printk(KERN_ERR "binder: invalid inc strong " - "node for %d\n", node->debug_id); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: invalid inc strong " + "node for %d\n", node->debug_id); return -EINVAL; } node->internal_strong_refs++; @@ -1013,8 +1020,9 @@ static int binder_inc_node(struct binder_node *node, int strong, int internal, node->local_weak_refs++; if (!node->has_weak_ref && list_empty(&node->work.entry)) { if (target_list == NULL) { - printk(KERN_ERR "binder: invalid inc weak node " - "for %d\n", node->debug_id); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: invalid inc weak node " + "for %d\n", node->debug_id); return -EINVAL; } list_add_tail(&node->work.entry, target_list); @@ -1276,11 +1284,13 @@ static void binder_send_failed_reply(struct binder_transaction *t, target_thread->return_error = error_code; wake_up_interruptible(&target_thread->wait); } else { - printk(KERN_ERR "binder: reply failed, target " - "thread, %d:%d, has error code %d " - "already\n", target_thread->proc->pid, - target_thread->pid, - target_thread->return_error); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: reply failed, target " + "thread, %d:%d, has error code %d " + "already\n", + target_thread->proc->pid, + target_thread->pid, + target_thread->return_error); } return; } else { @@ -1331,9 +1341,10 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, if (*offp > buffer->data_size - sizeof(*fp) || buffer->data_size < sizeof(*fp) || !IS_ALIGNED(*offp, sizeof(void *))) { - printk(KERN_ERR "binder: transaction release %d bad" - "offset %zd, size %zd\n", debug_id, - *offp, buffer->data_size); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: transaction release %d bad" + "offset %zd, size %zd\n", debug_id, + *offp, buffer->data_size); continue; } fp = (struct flat_binder_object *)(buffer->data + *offp); @@ -1342,8 +1353,10 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, case BINDER_TYPE_WEAK_BINDER: { struct binder_node *node = binder_get_node(proc, fp->binder); if (node == NULL) { - printk(KERN_ERR "binder: transaction release %d" - " bad node %p\n", debug_id, fp->binder); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: transaction release %d" + " bad node %p\n", debug_id, + fp->binder); break; } binder_debug(BINDER_DEBUG_TRANSACTION, @@ -1355,9 +1368,10 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, case BINDER_TYPE_WEAK_HANDLE: { struct binder_ref *ref = binder_get_ref(proc, fp->handle); if (ref == NULL) { - printk(KERN_ERR "binder: transaction release %d" - " bad handle %ld\n", debug_id, - fp->handle); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: transaction release %d" + " bad handle %ld\n", debug_id, + fp->handle); break; } binder_debug(BINDER_DEBUG_TRANSACTION, @@ -1374,8 +1388,9 @@ static void binder_transaction_buffer_release(struct binder_proc *proc, break; default: - printk(KERN_ERR "binder: transaction release %d bad " - "object type %lx\n", debug_id, fp->type); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: transaction release %d bad " + "object type %lx\n", debug_id, fp->type); break; } } @@ -1925,10 +1940,12 @@ int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, break; } case BC_ATTEMPT_ACQUIRE: - printk(KERN_ERR "binder: BC_ATTEMPT_ACQUIRE not supported\n"); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: BC_ATTEMPT_ACQUIRE not supported\n"); return -EINVAL; case BC_ACQUIRE_RESULT: - printk(KERN_ERR "binder: BC_ACQUIRE_RESULT not supported\n"); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: BC_ACQUIRE_RESULT not supported\n"); return -EINVAL; case BC_FREE_BUFFER: { @@ -2165,8 +2182,9 @@ int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, } break; default: - printk(KERN_ERR "binder: %d:%d unknown command %d\n", - proc->pid, thread->pid, cmd); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d:%d unknown command %d\n", + proc->pid, thread->pid, cmd); return -EINVAL; } *consumed = ptr - buffer; @@ -2701,16 +2719,18 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) break; case BINDER_SET_CONTEXT_MGR: if (binder_context_mgr_node != NULL) { - printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n"); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: BINDER_SET_CONTEXT_MGR already set\n"); ret = -EBUSY; goto err; } if (binder_context_mgr_uid != -1) { if (binder_context_mgr_uid != current->cred->euid) { - printk(KERN_ERR "binder: BINDER_SET_" - "CONTEXT_MGR bad uid %d != %d\n", - current->cred->euid, - binder_context_mgr_uid); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: BINDER_SET_" + "CONTEXT_MGR bad uid %d != %d\n", + current->cred->euid, + binder_context_mgr_uid); ret = -EPERM; goto err; } @@ -2753,7 +2773,9 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) mutex_unlock(&binder_lock); wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); if (ret && ret != -ERESTARTSYS) - printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: %d:%d ioctl %x %lx returned %d\n", + proc->pid, current->pid, cmd, arg, ret); return ret; } @@ -2829,7 +2851,8 @@ static int binder_mmap(struct file *filp, struct vm_area_struct *vma) #ifdef CONFIG_CPU_CACHE_VIPT if (cache_is_vipt_aliasing()) { while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) { - printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer); vma->vm_start += PAGE_SIZE; } } @@ -2876,8 +2899,10 @@ static int binder_mmap(struct file *filp, struct vm_area_struct *vma) err_already_mapped: mutex_unlock(&binder_mmap_lock); err_bad_arg: - printk(KERN_ERR "binder_mmap: %d %lx-%lx %s failed %d\n", - proc->pid, vma->vm_start, vma->vm_end, failure_string, ret); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder_mmap: %d %lx-%lx %s failed %d\n", + proc->pid, vma->vm_start, vma->vm_end, failure_string, + ret); return ret; } @@ -3031,9 +3056,10 @@ static void binder_deferred_release(struct binder_proc *proc) if (t) { t->buffer = NULL; buffer->transaction = NULL; - printk(KERN_ERR "binder: release proc %d, " - "transaction %d, not freed\n", - proc->pid, t->debug_id); + binder_debug(BINDER_DEBUG_TOP_ERRORS, + "binder: release proc %d, " + "transaction %d, not freed\n", + proc->pid, t->debug_id); /*BUG();*/ } binder_free_buf(proc, buffer); 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/drivers/staging/dream/Kconfig b/drivers/staging/dream/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..0c30b19a5a7c38d40322058b3efb7304980ef6a8 --- /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 0000000000000000000000000000000000000000..fbea0abcc864cce7fe74515f98f5621916913939 --- /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 0000000000000000000000000000000000000000..99d3b8dd798bc617b3d341579b4eecaddfa1360d --- /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 100644 index 0000000000000000000000000000000000000000..66c1590d3933655f5100349ae7c840057b70b667 --- /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 0000000000000000000000000000000000000000..e7f72e72fb2b0d86d203b2a5b61a5c4fcb2df5cf --- /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 0000000000000000000000000000000000000000..fe7eebe3a9d7393d5b1a0d91fbf69fca8c42d694 --- /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 0000000000000000000000000000000000000000..4da12853879200c646c1b51aadc1ada073fea012 --- /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 0000000000000000000000000000000000000000..668328cfe25feb27b4cac4f0822a5897c5590b39 --- /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 0000000000000000000000000000000000000000..6fb9c4737fca9750677e70d7c75b45bcc7b06f2b --- /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 0000000000000000000000000000000000000000..07e31933fcea4ce4dd150fda0923be46e9f004c0 --- /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/qcache/Kconfig b/drivers/staging/qcache/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..389341cf5d9c24c3e1b36052f20bd83d9b8c1dd9 --- /dev/null +++ b/drivers/staging/qcache/Kconfig @@ -0,0 +1,8 @@ +config QCACHE + tristate "Dynamic compression of clean pagecache pages" + depends on CLEANCACHE + select LZO_COMPRESS + select LZO_DECOMPRESS + default n + help + Qcache is the backend for fmem diff --git a/drivers/staging/qcache/Makefile b/drivers/staging/qcache/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4fdf05c147a3b61b9950371fe69eb9a787366195 --- /dev/null +++ b/drivers/staging/qcache/Makefile @@ -0,0 +1,3 @@ +qcache-y := qcache-main.o tmem.o fmem.o + +obj-$(CONFIG_QCACHE) += qcache.o diff --git a/drivers/staging/qcache/fmem.c b/drivers/staging/qcache/fmem.c new file mode 100644 index 0000000000000000000000000000000000000000..8f9e0ef4e1bb95333fcc8c44e3432f4e523c691f --- /dev/null +++ b/drivers/staging/qcache/fmem.c @@ -0,0 +1,344 @@ +/* + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#ifdef CONFIG_MEMORY_HOTPLUG +#include +#include +#endif +#include "tmem.h" +#include + +struct fmem_data fmem_data; +enum fmem_state fmem_state; +static spinlock_t fmem_state_lock; + +#ifdef CONFIG_MEMORY_HOTPLUG +static unsigned int section_powered_off[NR_MEM_SECTIONS]; +static unsigned int fmem_section_start, fmem_section_end; +#endif + +void *fmem_map_virtual_area(int cacheability) +{ + unsigned long addr; + const struct mem_type *type; + int ret; + + addr = (unsigned long) fmem_data.area->addr; + type = get_mem_type(cacheability); + ret = ioremap_pages(addr, fmem_data.phys, fmem_data.size, type); + if (ret) + return ERR_PTR(ret); + + fmem_data.virt = fmem_data.area->addr; + + return fmem_data.virt; +} + +void fmem_unmap_virtual_area(void) +{ + unmap_kernel_range((unsigned long)fmem_data.virt, fmem_data.size); + fmem_data.virt = NULL; +} + +static int fmem_probe(struct platform_device *pdev) +{ + struct fmem_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata->phys) + pdata->phys = allocate_contiguous_ebi_nomap(pdata->size, + pdata->align); + +#ifdef CONFIG_MEMORY_HOTPLUG + fmem_section_start = pdata->phys >> PA_SECTION_SHIFT; + fmem_section_end = (pdata->phys - 1 + pdata->size) >> PA_SECTION_SHIFT; +#endif + fmem_data.phys = pdata->phys + pdata->reserved_size_low; + fmem_data.size = pdata->size - pdata->reserved_size_low - + pdata->reserved_size_high; + fmem_data.reserved_size_low = pdata->reserved_size_low; + fmem_data.reserved_size_high = pdata->reserved_size_high; + + if (!fmem_data.size) + return -ENODEV; + + fmem_data.area = get_vm_area(fmem_data.size, VM_IOREMAP); + if (!fmem_data.area) + return -ENOMEM; + + if (!fmem_map_virtual_area(MT_DEVICE_CACHED)) { + remove_vm_area(fmem_data.area->addr); + return -ENOMEM; + } + pr_info("fmem phys %lx virt %p size %lx\n", + fmem_data.phys, fmem_data.virt, fmem_data.size); + + spin_lock_init(&fmem_state_lock); + + return 0; +} + +static int fmem_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver fmem_driver = { + .probe = fmem_probe, + .remove = fmem_remove, + .driver = { .name = "fmem" } +}; + +#ifdef CONFIG_SYSFS +static ssize_t fmem_state_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + if (fmem_state == FMEM_T_STATE) + return snprintf(buf, 3, "t\n"); + else if (fmem_state == FMEM_C_STATE) + return snprintf(buf, 3, "c\n"); +#ifdef CONFIG_MEMORY_HOTPLUG + else if (fmem_state == FMEM_O_STATE) + return snprintf(buf, 3, "o\n"); +#endif + else if (fmem_state == FMEM_UNINITIALIZED) + return snprintf(buf, 15, "uninitialized\n"); + return snprintf(buf, 3, "?\n"); +} + +static ssize_t fmem_state_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret = -EINVAL; + + if (!strncmp(buf, "t", 1)) + ret = fmem_set_state(FMEM_T_STATE); + else if (!strncmp(buf, "c", 1)) + ret = fmem_set_state(FMEM_C_STATE); +#ifdef CONFIG_MEMORY_HOTPLUG + else if (!strncmp(buf, "o", 1)) + ret = fmem_set_state(FMEM_O_STATE); +#endif + if (ret) + return ret; + return 1; +} + +static struct kobj_attribute fmem_state_attr = { + .attr = { .name = "state", .mode = 0644 }, + .show = fmem_state_show, + .store = fmem_state_store, +}; + +static struct attribute *fmem_attrs[] = { + &fmem_state_attr.attr, + NULL, +}; + +static struct attribute_group fmem_attr_group = { + .attrs = fmem_attrs, + .name = "fmem", +}; + +static int fmem_create_sysfs(void) +{ + int ret = 0; + + ret = sysfs_create_group(mm_kobj, &fmem_attr_group); + if (ret) + pr_err("fmem: can't create sysfs\n"); + return ret; +} + +#endif + +#ifdef CONFIG_MEMORY_HOTPLUG +bool fmem_is_disjoint(unsigned long start_pfn, unsigned long nr_pages) +{ + unsigned long fmem_start_pfn, fmem_end_pfn; + unsigned long unstable_end_pfn; + unsigned long highest_start_pfn, lowest_end_pfn; + + fmem_start_pfn = (fmem_data.phys - fmem_data.reserved_size_low) + >> PAGE_SHIFT; + fmem_end_pfn = (fmem_data.phys + fmem_data.size + + fmem_data.reserved_size_high - 1) >> PAGE_SHIFT; + unstable_end_pfn = start_pfn + nr_pages - 1; + + highest_start_pfn = max(fmem_start_pfn, start_pfn); + lowest_end_pfn = min(fmem_end_pfn, unstable_end_pfn); + + return lowest_end_pfn < highest_start_pfn; +} + +static int fmem_mem_going_offline_callback(void *arg) +{ + struct memory_notify *marg = arg; + + if (fmem_is_disjoint(marg->start_pfn, marg->nr_pages)) + return 0; + return fmem_set_state(FMEM_O_STATE); +} + +static void fmem_mem_online_callback(void *arg) +{ + struct memory_notify *marg = arg; + int i; + + section_powered_off[marg->start_pfn >> PFN_SECTION_SHIFT] = 0; + + if (fmem_state != FMEM_O_STATE) + return; + + for (i = fmem_section_start; i <= fmem_section_end; i++) { + if (section_powered_off[i]) + return; + } + + fmem_set_state(FMEM_T_STATE); +} + +static void fmem_mem_offline_callback(void *arg) +{ + struct memory_notify *marg = arg; + + section_powered_off[marg->start_pfn >> PFN_SECTION_SHIFT] = 1; +} + +static int fmem_memory_callback(struct notifier_block *self, + unsigned long action, void *arg) +{ + int ret = 0; + + if (fmem_state == FMEM_UNINITIALIZED) + return NOTIFY_OK; + + switch (action) { + case MEM_ONLINE: + fmem_mem_online_callback(arg); + break; + case MEM_GOING_OFFLINE: + ret = fmem_mem_going_offline_callback(arg); + break; + case MEM_OFFLINE: + fmem_mem_offline_callback(arg); + break; + case MEM_GOING_ONLINE: + case MEM_CANCEL_ONLINE: + case MEM_CANCEL_OFFLINE: + break; + } + if (ret) + ret = notifier_from_errno(ret); + else + ret = NOTIFY_OK; + return ret; +} +#endif + +static int __init fmem_init(void) +{ +#ifdef CONFIG_MEMORY_HOTPLUG + hotplug_memory_notifier(fmem_memory_callback, 0); +#endif + return platform_driver_register(&fmem_driver); +} + +static void __exit fmem_exit(void) +{ + platform_driver_unregister(&fmem_driver); +} + +struct fmem_data *fmem_get_info(void) +{ + return &fmem_data; +} +EXPORT_SYMBOL(fmem_get_info); + +void lock_fmem_state(void) +{ + spin_lock(&fmem_state_lock); +} + +void unlock_fmem_state(void) +{ + spin_unlock(&fmem_state_lock); +} + +int fmem_set_state(enum fmem_state new_state) +{ + int ret = 0; + int create_sysfs = 0; + + lock_fmem_state(); + if (fmem_state == new_state) + goto out; + + if (fmem_state == FMEM_UNINITIALIZED) { + if (new_state == FMEM_T_STATE) { + tmem_enable(); + create_sysfs = 1; + goto out_set; + } else { + ret = -EINVAL; + goto out; + } + } + +#ifdef CONFIG_MEMORY_HOTPLUG + if (fmem_state == FMEM_C_STATE && new_state == FMEM_O_STATE) { + ret = -EAGAIN; + goto out; + } + + if (fmem_state == FMEM_O_STATE && new_state == FMEM_C_STATE) { + pr_warn("attempting to use powered off memory as fmem\n"); + ret = -EAGAIN; + goto out; + } +#endif + + if (new_state == FMEM_T_STATE) { + void *v; + v = fmem_map_virtual_area(MT_DEVICE_CACHED); + if (IS_ERR_OR_NULL(v)) { + ret = PTR_ERR(v); + goto out; + } + tmem_enable(); + } else { + tmem_disable(); + fmem_unmap_virtual_area(); + } + +out_set: + fmem_state = new_state; +out: + unlock_fmem_state(); +#ifdef CONFIG_SYSFS + if (create_sysfs) + fmem_create_sysfs(); +#endif + return ret; +} +EXPORT_SYMBOL(fmem_set_state); + +arch_initcall(fmem_init); +module_exit(fmem_exit); diff --git a/drivers/staging/qcache/qcache-main.c b/drivers/staging/qcache/qcache-main.c new file mode 100644 index 0000000000000000000000000000000000000000..063c6fc16e3366b023daeb5faab020744e73e155 --- /dev/null +++ b/drivers/staging/qcache/qcache-main.c @@ -0,0 +1,1358 @@ +/* + * Copyright (c) 2010,2011, Dan Magenheimer, Oracle Corp. + * Copyright (c) 2010,2011, Nitin Gupta + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * Qcache provides an in-kernel "host implementation" for transcendent memory + * and, thus indirectly, for cleancache and frontswap. Qcache includes a + * page-accessible memory [1] interface, utilizing lzo1x compression: + * 1) "compression buddies" ("zbud") is used for ephemeral pages + * Zbud allows pairs (and potentially, + * in the future, more than a pair of) compressed pages to be closely linked + * so that reclaiming can be done via the kernel's physical-page-oriented + * "shrinker" interface. + * + * [1] For a definition of page-accessible memory (aka PAM), see: + * http://marc.info/?l=linux-mm&m=127811271605009 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tmem.h" + +#if !defined(CONFIG_CLEANCACHE) +#error "qcache is useless without CONFIG_CLEANCACHE" +#endif +#include + +#define ZCACHE_GFP_MASK \ + (__GFP_FS | __GFP_NORETRY | __GFP_NOWARN | __GFP_NOMEMALLOC) + +#define MAX_POOLS_PER_CLIENT 16 + +#define MAX_CLIENTS 16 +#define LOCAL_CLIENT ((uint16_t)-1) + +MODULE_LICENSE("GPL"); + +struct zcache_client { + struct tmem_pool *tmem_pools[MAX_POOLS_PER_CLIENT]; + struct xv_pool *xvpool; + bool allocated; + atomic_t refcount; +}; + +struct qcache_info { + void *addr; + unsigned long *bitmap; + spinlock_t lock; + unsigned pages; +}; +static struct qcache_info qcache_info; +static unsigned long zcache_qc_allocated; +static unsigned long zcache_qc_freed; +static unsigned long zcache_qc_used; +static unsigned long zcache_qc_max_used; + +static struct zcache_client zcache_host; +static struct zcache_client zcache_clients[MAX_CLIENTS]; + +static inline uint16_t get_client_id_from_client(struct zcache_client *cli) +{ + BUG_ON(cli == NULL); + if (cli == &zcache_host) + return LOCAL_CLIENT; + return cli - &zcache_clients[0]; +} + +static inline bool is_local_client(struct zcache_client *cli) +{ + return cli == &zcache_host; +} + +/********** + * Compression buddies ("zbud") provides for packing two (or, possibly + * in the future, more) compressed ephemeral pages into a single "raw" + * (physical) page and tracking them with data structures so that + * the raw pages can be easily reclaimed. + * + * A zbud page ("zbpg") is an aligned page containing a list_head, + * a lock, and two "zbud headers". The remainder of the physical + * page is divided up into aligned 64-byte "chunks" which contain + * the compressed data for zero, one, or two zbuds. Each zbpg + * resides on: (1) an "unused list" if it has no zbuds; (2) a + * "buddied" list if it is fully populated with two zbuds; or + * (3) one of PAGE_SIZE/64 "unbuddied" lists indexed by how many chunks + * the one unbuddied zbud uses. The data inside a zbpg cannot be + * read or written unless the zbpg's lock is held. + */ + +#define ZBH_SENTINEL 0x43214321 +#define ZBPG_SENTINEL 0xdeadbeef + +#define ZBUD_MAX_BUDS 2 + +struct zbud_hdr { + uint16_t client_id; + uint16_t pool_id; + struct tmem_oid oid; + uint32_t index; + uint16_t size; /* compressed size in bytes, zero means unused */ + DECL_SENTINEL +}; + +struct zbud_page { + struct list_head bud_list; + spinlock_t lock; + struct zbud_hdr buddy[ZBUD_MAX_BUDS]; + DECL_SENTINEL + /* followed by NUM_CHUNK aligned CHUNK_SIZE-byte chunks */ +}; + +#define CHUNK_SHIFT 6 +#define CHUNK_SIZE (1 << CHUNK_SHIFT) +#define CHUNK_MASK (~(CHUNK_SIZE-1)) +#define NCHUNKS (((PAGE_SIZE - sizeof(struct zbud_page)) & \ + CHUNK_MASK) >> CHUNK_SHIFT) +#define MAX_CHUNK (NCHUNKS-1) + +static struct { + struct list_head list; + unsigned count; +} zbud_unbuddied[NCHUNKS]; +/* list N contains pages with N chunks USED and NCHUNKS-N unused */ +/* element 0 is never used but optimizing that isn't worth it */ +static unsigned long zbud_cumul_chunk_counts[NCHUNKS]; + +struct list_head zbud_buddied_list; +static unsigned long zcache_zbud_buddied_count; + +/* protects the buddied list and all unbuddied lists */ +static DEFINE_SPINLOCK(zbud_budlists_spinlock); + +static atomic_t zcache_zbud_curr_raw_pages; +static atomic_t zcache_zbud_curr_zpages; +static unsigned long zcache_zbud_curr_zbytes; +static unsigned long zcache_zbud_cumul_zpages; +static unsigned long zcache_zbud_cumul_zbytes; +static unsigned long zcache_compress_poor; +static unsigned long zcache_mean_compress_poor; + +/* forward references */ +static void *zcache_get_free_page(void); + +static void *qcache_alloc(void) +{ + void *addr; + unsigned long flags; + int offset; + struct qcache_info *qc = &qcache_info; + + spin_lock_irqsave(&qc->lock, flags); + offset = bitmap_find_free_region(qc->bitmap, qc->pages, 0); + + if (offset < 0) { + spin_unlock_irqrestore(&qc->lock, flags); + return NULL; + } + + zcache_qc_allocated++; + zcache_qc_used++; + zcache_qc_max_used = max(zcache_qc_max_used, zcache_qc_used); + spin_unlock_irqrestore(&qc->lock, flags); + + addr = qc->addr + offset * PAGE_SIZE; + + return addr; +} + +static void qcache_free(void *addr) +{ + unsigned long flags; + int offset; + struct qcache_info *qc = &qcache_info; + + offset = (addr - qc->addr) / PAGE_SIZE; + + spin_lock_irqsave(&qc->lock, flags); + bitmap_release_region(qc->bitmap, offset, 0); + + zcache_qc_freed++; + zcache_qc_used--; + spin_unlock_irqrestore(&qc->lock, flags); +} + +/* + * zbud helper functions + */ + +static inline unsigned zbud_max_buddy_size(void) +{ + return MAX_CHUNK << CHUNK_SHIFT; +} + +static inline unsigned zbud_size_to_chunks(unsigned size) +{ + BUG_ON(size == 0 || size > zbud_max_buddy_size()); + return (size + CHUNK_SIZE - 1) >> CHUNK_SHIFT; +} + +static inline int zbud_budnum(struct zbud_hdr *zh) +{ + unsigned offset = (unsigned long)zh & (PAGE_SIZE - 1); + struct zbud_page *zbpg = NULL; + unsigned budnum = -1U; + int i; + + for (i = 0; i < ZBUD_MAX_BUDS; i++) + if (offset == offsetof(typeof(*zbpg), buddy[i])) { + budnum = i; + break; + } + BUG_ON(budnum == -1U); + return budnum; +} + +static char *zbud_data(struct zbud_hdr *zh, unsigned size) +{ + struct zbud_page *zbpg; + char *p; + unsigned budnum; + + ASSERT_SENTINEL(zh, ZBH); + budnum = zbud_budnum(zh); + BUG_ON(size == 0 || size > zbud_max_buddy_size()); + zbpg = container_of(zh, struct zbud_page, buddy[budnum]); + p = (char *)zbpg; + if (budnum == 0) + p += ((sizeof(struct zbud_page) + CHUNK_SIZE - 1) & + CHUNK_MASK); + else if (budnum == 1) + p += PAGE_SIZE - ((size + CHUNK_SIZE - 1) & CHUNK_MASK); + return p; +} + +/* + * zbud raw page management + */ + +static struct zbud_page *zbud_alloc_raw_page(void) +{ + struct zbud_page *zbpg = NULL; + struct zbud_hdr *zh0, *zh1; + + zbpg = zcache_get_free_page(); + if (likely(zbpg != NULL)) { + INIT_LIST_HEAD(&zbpg->bud_list); + zh0 = &zbpg->buddy[0]; zh1 = &zbpg->buddy[1]; + spin_lock_init(&zbpg->lock); + atomic_inc(&zcache_zbud_curr_raw_pages); + INIT_LIST_HEAD(&zbpg->bud_list); + SET_SENTINEL(zbpg, ZBPG); + zh0->size = 0; zh1->size = 0; + tmem_oid_set_invalid(&zh0->oid); + tmem_oid_set_invalid(&zh1->oid); + } + return zbpg; +} + +static void zbud_free_raw_page(struct zbud_page *zbpg) +{ + struct zbud_hdr *zh0 = &zbpg->buddy[0], *zh1 = &zbpg->buddy[1]; + + ASSERT_SENTINEL(zbpg, ZBPG); + BUG_ON(!list_empty(&zbpg->bud_list)); + BUG_ON(zh0->size != 0 || tmem_oid_valid(&zh0->oid)); + BUG_ON(zh1->size != 0 || tmem_oid_valid(&zh1->oid)); + INVERT_SENTINEL(zbpg, ZBPG); + spin_unlock(&zbpg->lock); + qcache_free(zbpg); +} + +/* + * core zbud handling routines + */ + +static unsigned zbud_free(struct zbud_hdr *zh) +{ + unsigned size; + + ASSERT_SENTINEL(zh, ZBH); + BUG_ON(!tmem_oid_valid(&zh->oid)); + size = zh->size; + BUG_ON(zh->size == 0 || zh->size > zbud_max_buddy_size()); + zh->size = 0; + tmem_oid_set_invalid(&zh->oid); + INVERT_SENTINEL(zh, ZBH); + zcache_zbud_curr_zbytes -= size; + atomic_dec(&zcache_zbud_curr_zpages); + return size; +} + +static void zbud_free_and_delist(struct zbud_hdr *zh) +{ + unsigned chunks; + struct zbud_hdr *zh_other; + unsigned budnum = zbud_budnum(zh), size; + struct zbud_page *zbpg = + container_of(zh, struct zbud_page, buddy[budnum]); + + spin_lock(&zbpg->lock); + if (list_empty(&zbpg->bud_list)) { + spin_unlock(&zbpg->lock); + return; + } + size = zbud_free(zh); + zh_other = &zbpg->buddy[(budnum == 0) ? 1 : 0]; + if (zh_other->size == 0) { /* was unbuddied: unlist and free */ + chunks = zbud_size_to_chunks(size) ; + spin_lock(&zbud_budlists_spinlock); + BUG_ON(list_empty(&zbud_unbuddied[chunks].list)); + list_del_init(&zbpg->bud_list); + zbud_unbuddied[chunks].count--; + spin_unlock(&zbud_budlists_spinlock); + zbud_free_raw_page(zbpg); + } else { /* was buddied: move remaining buddy to unbuddied list */ + chunks = zbud_size_to_chunks(zh_other->size) ; + spin_lock(&zbud_budlists_spinlock); + list_del_init(&zbpg->bud_list); + zcache_zbud_buddied_count--; + list_add_tail(&zbpg->bud_list, &zbud_unbuddied[chunks].list); + zbud_unbuddied[chunks].count++; + spin_unlock(&zbud_budlists_spinlock); + spin_unlock(&zbpg->lock); + } +} + +static struct zbud_hdr *zbud_create(uint16_t client_id, uint16_t pool_id, + struct tmem_oid *oid, + uint32_t index, struct page *page, + void *cdata, unsigned size) +{ + struct zbud_hdr *zh0, *zh1, *zh = NULL; + struct zbud_page *zbpg = NULL, *ztmp; + unsigned nchunks; + char *to; + int i, found_good_buddy = 0; + + nchunks = zbud_size_to_chunks(size) ; + for (i = MAX_CHUNK - nchunks + 1; i > 0; i--) { + spin_lock(&zbud_budlists_spinlock); + if (!list_empty(&zbud_unbuddied[i].list)) { + list_for_each_entry_safe(zbpg, ztmp, + &zbud_unbuddied[i].list, bud_list) { + if (spin_trylock(&zbpg->lock)) { + found_good_buddy = i; + goto found_unbuddied; + } + } + } + spin_unlock(&zbud_budlists_spinlock); + } + /* didn't find a good buddy, try allocating a new page */ + zbpg = zbud_alloc_raw_page(); + if (unlikely(zbpg == NULL)) + goto out; + /* ok, have a page, now compress the data before taking locks */ + spin_lock(&zbpg->lock); + spin_lock(&zbud_budlists_spinlock); + list_add_tail(&zbpg->bud_list, &zbud_unbuddied[nchunks].list); + zbud_unbuddied[nchunks].count++; + zh = &zbpg->buddy[0]; + goto init_zh; + +found_unbuddied: + zh0 = &zbpg->buddy[0]; zh1 = &zbpg->buddy[1]; + BUG_ON(!((zh0->size == 0) ^ (zh1->size == 0))); + if (zh0->size != 0) { /* buddy0 in use, buddy1 is vacant */ + ASSERT_SENTINEL(zh0, ZBH); + zh = zh1; + } else if (zh1->size != 0) { /* buddy1 in use, buddy0 is vacant */ + ASSERT_SENTINEL(zh1, ZBH); + zh = zh0; + } else + BUG(); + list_del_init(&zbpg->bud_list); + zbud_unbuddied[found_good_buddy].count--; + list_add_tail(&zbpg->bud_list, &zbud_buddied_list); + zcache_zbud_buddied_count++; + +init_zh: + SET_SENTINEL(zh, ZBH); + zh->size = size; + zh->index = index; + zh->oid = *oid; + zh->pool_id = pool_id; + zh->client_id = client_id; + /* can wait to copy the data until the list locks are dropped */ + spin_unlock(&zbud_budlists_spinlock); + + to = zbud_data(zh, size); + memcpy(to, cdata, size); + spin_unlock(&zbpg->lock); + zbud_cumul_chunk_counts[nchunks]++; + atomic_inc(&zcache_zbud_curr_zpages); + zcache_zbud_cumul_zpages++; + zcache_zbud_curr_zbytes += size; + zcache_zbud_cumul_zbytes += size; +out: + return zh; +} + +static int zbud_decompress(struct page *page, struct zbud_hdr *zh) +{ + struct zbud_page *zbpg; + unsigned budnum = zbud_budnum(zh); + size_t out_len = PAGE_SIZE; + char *to_va, *from_va; + unsigned size; + int ret = 0; + + zbpg = container_of(zh, struct zbud_page, buddy[budnum]); + spin_lock(&zbpg->lock); + if (list_empty(&zbpg->bud_list)) { + ret = -EINVAL; + goto out; + } + ASSERT_SENTINEL(zh, ZBH); + BUG_ON(zh->size == 0 || zh->size > zbud_max_buddy_size()); + to_va = kmap_atomic(page); + size = zh->size; + from_va = zbud_data(zh, size); + ret = lzo1x_decompress_safe(from_va, size, to_va, &out_len); + BUG_ON(ret != LZO_E_OK); + BUG_ON(out_len != PAGE_SIZE); + kunmap_atomic(to_va); +out: + spin_unlock(&zbpg->lock); + return ret; +} + +static struct tmem_pool *zcache_get_pool_by_id(uint16_t cli_id, + uint16_t poolid); +static void zcache_put_pool(struct tmem_pool *pool); + +static void zbud_init(void) +{ + int i; + + INIT_LIST_HEAD(&zbud_buddied_list); + zcache_zbud_buddied_count = 0; + for (i = 0; i < NCHUNKS; i++) { + INIT_LIST_HEAD(&zbud_unbuddied[i].list); + zbud_unbuddied[i].count = 0; + } +} + +#ifdef CONFIG_SYSFS +/* + * These sysfs routines show a nice distribution of how many zbpg's are + * currently (and have ever been placed) in each unbuddied list. It's fun + * to watch but can probably go away before final merge. + */ +static int zbud_show_unbuddied_list_counts(char *buf) +{ + int i; + char *p = buf; + + for (i = 0; i < NCHUNKS; i++) + p += sprintf(p, "%u ", zbud_unbuddied[i].count); + return p - buf; +} + +static int zbud_show_cumul_chunk_counts(char *buf) +{ + unsigned long i, chunks = 0, total_chunks = 0, sum_total_chunks = 0; + unsigned long total_chunks_lte_21 = 0, total_chunks_lte_32 = 0; + unsigned long total_chunks_lte_42 = 0; + char *p = buf; + + for (i = 0; i < NCHUNKS; i++) { + p += sprintf(p, "%lu ", zbud_cumul_chunk_counts[i]); + chunks += zbud_cumul_chunk_counts[i]; + total_chunks += zbud_cumul_chunk_counts[i]; + sum_total_chunks += i * zbud_cumul_chunk_counts[i]; + if (i == 21) + total_chunks_lte_21 = total_chunks; + if (i == 32) + total_chunks_lte_32 = total_chunks; + if (i == 42) + total_chunks_lte_42 = total_chunks; + } + p += sprintf(p, "<=21:%lu <=32:%lu <=42:%lu, mean:%lu\n", + total_chunks_lte_21, total_chunks_lte_32, total_chunks_lte_42, + chunks == 0 ? 0 : sum_total_chunks / chunks); + return p - buf; +} +#endif + +/* + * zcache core code starts here + */ + +/* useful stats not collected by cleancache or frontswap */ +static unsigned long zcache_flush_total; +static unsigned long zcache_flush_found; +static unsigned long zcache_flobj_total; +static unsigned long zcache_flobj_found; +static unsigned long zcache_failed_eph_puts; + +/* + * Tmem operations assume the poolid implies the invoking client. + * Zcache only has one client (the kernel itself): LOCAL_CLIENT. + * RAMster has each client numbered by cluster node, and a KVM version + * of zcache would have one client per guest and each client might + * have a poolid==N. + */ +static struct tmem_pool *zcache_get_pool_by_id(uint16_t cli_id, uint16_t poolid) +{ + struct tmem_pool *pool = NULL; + struct zcache_client *cli = NULL; + + if (cli_id == LOCAL_CLIENT) + cli = &zcache_host; + else { + if (cli_id >= MAX_CLIENTS) + goto out; + cli = &zcache_clients[cli_id]; + if (cli == NULL) + goto out; + atomic_inc(&cli->refcount); + } + if (poolid < MAX_POOLS_PER_CLIENT) { + pool = cli->tmem_pools[poolid]; + if (pool != NULL) + atomic_inc(&pool->refcount); + } +out: + return pool; +} + +static void zcache_put_pool(struct tmem_pool *pool) +{ + struct zcache_client *cli = NULL; + + if (pool == NULL) + BUG(); + cli = pool->client; + atomic_dec(&pool->refcount); + atomic_dec(&cli->refcount); +} + +int zcache_new_client(uint16_t cli_id) +{ + struct zcache_client *cli = NULL; + int ret = -1; + + if (cli_id == LOCAL_CLIENT) + cli = &zcache_host; + else if ((unsigned int)cli_id < MAX_CLIENTS) + cli = &zcache_clients[cli_id]; + if (cli == NULL) + goto out; + if (cli->allocated) + goto out; + cli->allocated = 1; + ret = 0; +out: + return ret; +} + +/* counters for debugging */ +static unsigned long zcache_failed_get_free_pages; +static unsigned long zcache_failed_alloc; +static unsigned long zcache_put_to_flush; +static unsigned long zcache_aborted_preload; +static unsigned long zcache_aborted_shrink; + +/* + * Ensure that memory allocation requests in zcache don't result + * in direct reclaim requests via the shrinker, which would cause + * an infinite loop. Maybe a GFP flag would be better? + */ +static DEFINE_SPINLOCK(zcache_direct_reclaim_lock); + +/* + * for now, used named slabs so can easily track usage; later can + * either just use kmalloc, or perhaps add a slab-like allocator + * to more carefully manage total memory utilization + */ +static struct kmem_cache *zcache_objnode_cache; +static struct kmem_cache *zcache_obj_cache; +static atomic_t zcache_curr_obj_count = ATOMIC_INIT(0); +static unsigned long zcache_curr_obj_count_max; +static atomic_t zcache_curr_objnode_count = ATOMIC_INIT(0); +static unsigned long zcache_curr_objnode_count_max; + +/* + * to avoid memory allocation recursion (e.g. due to direct reclaim), we + * preload all necessary data structures so the hostops callbacks never + * actually do a malloc + */ +struct zcache_preload { + void *page; + struct tmem_obj *obj; + int nr; + struct tmem_objnode *objnodes[OBJNODE_TREE_MAX_PATH]; +}; +static DEFINE_PER_CPU(struct zcache_preload, zcache_preloads) = { 0, }; + +static int zcache_do_preload(struct tmem_pool *pool) +{ + struct zcache_preload *kp; + struct tmem_objnode *objnode; + struct tmem_obj *obj; + void *page; + int ret = -ENOMEM; + + if (unlikely(zcache_objnode_cache == NULL)) + goto out; + if (unlikely(zcache_obj_cache == NULL)) + goto out; + if (!spin_trylock(&zcache_direct_reclaim_lock)) { + zcache_aborted_preload++; + goto out; + } + preempt_disable(); + kp = &__get_cpu_var(zcache_preloads); + while (kp->nr < ARRAY_SIZE(kp->objnodes)) { + preempt_enable_no_resched(); + objnode = kmem_cache_alloc(zcache_objnode_cache, + ZCACHE_GFP_MASK); + if (unlikely(objnode == NULL)) { + zcache_failed_alloc++; + goto unlock_out; + } + preempt_disable(); + kp = &__get_cpu_var(zcache_preloads); + if (kp->nr < ARRAY_SIZE(kp->objnodes)) + kp->objnodes[kp->nr++] = objnode; + else + kmem_cache_free(zcache_objnode_cache, objnode); + } + preempt_enable_no_resched(); + obj = kmem_cache_alloc(zcache_obj_cache, ZCACHE_GFP_MASK); + if (unlikely(obj == NULL)) { + zcache_failed_alloc++; + goto unlock_out; + } + page = qcache_alloc(); + if (unlikely(page == NULL)) { + zcache_failed_get_free_pages++; + kmem_cache_free(zcache_obj_cache, obj); + goto unlock_out; + } + preempt_disable(); + kp = &__get_cpu_var(zcache_preloads); + if (kp->obj == NULL) + kp->obj = obj; + else + kmem_cache_free(zcache_obj_cache, obj); + if (kp->page == NULL) + kp->page = page; + else + qcache_free(page); + ret = 0; +unlock_out: + spin_unlock(&zcache_direct_reclaim_lock); +out: + return ret; +} + +static void *zcache_get_free_page(void) +{ + struct zcache_preload *kp; + void *page; + + kp = &__get_cpu_var(zcache_preloads); + page = kp->page; + BUG_ON(page == NULL); + kp->page = NULL; + return page; +} + +/* + * zcache implementation for tmem host ops + */ + +static struct tmem_objnode *zcache_objnode_alloc(struct tmem_pool *pool) +{ + struct tmem_objnode *objnode = NULL; + unsigned long count; + struct zcache_preload *kp; + + kp = &__get_cpu_var(zcache_preloads); + if (kp->nr <= 0) + goto out; + objnode = kp->objnodes[kp->nr - 1]; + BUG_ON(objnode == NULL); + kp->objnodes[kp->nr - 1] = NULL; + kp->nr--; + count = atomic_inc_return(&zcache_curr_objnode_count); + if (count > zcache_curr_objnode_count_max) + zcache_curr_objnode_count_max = count; +out: + return objnode; +} + +static void zcache_objnode_free(struct tmem_objnode *objnode, + struct tmem_pool *pool) +{ + atomic_dec(&zcache_curr_objnode_count); + BUG_ON(atomic_read(&zcache_curr_objnode_count) < 0); + kmem_cache_free(zcache_objnode_cache, objnode); +} + +static struct tmem_obj *zcache_obj_alloc(struct tmem_pool *pool) +{ + struct tmem_obj *obj = NULL; + unsigned long count; + struct zcache_preload *kp; + + kp = &__get_cpu_var(zcache_preloads); + obj = kp->obj; + BUG_ON(obj == NULL); + kp->obj = NULL; + count = atomic_inc_return(&zcache_curr_obj_count); + if (count > zcache_curr_obj_count_max) + zcache_curr_obj_count_max = count; + return obj; +} + +static void zcache_obj_free(struct tmem_obj *obj, struct tmem_pool *pool) +{ + atomic_dec(&zcache_curr_obj_count); + BUG_ON(atomic_read(&zcache_curr_obj_count) < 0); + kmem_cache_free(zcache_obj_cache, obj); +} + +static void zcache_flush_all_obj(void) +{ + struct tmem_pool *pool; + int pool_id; + struct zcache_preload *kp; + + kp = &__get_cpu_var(zcache_preloads); + + for (pool_id = 0; pool_id < MAX_POOLS_PER_CLIENT; pool_id++) { + pool = zcache_get_pool_by_id(LOCAL_CLIENT, pool_id); + tmem_flush_pool(pool); + if (pool) + zcache_put_pool(pool); + } + if (kp->page) { + qcache_free(kp->page); + kp->page = NULL; + } + if (zcache_qc_used) + pr_warn("pages used not 0 after qcache flush all, is %ld\n", + zcache_qc_used); +} + +/* + * When zcache is disabled ("frozen"), pools can be created and destroyed, + * but all puts (and thus all other operations that require memory allocation) + * must fail. If zcache is unfrozen, accepts puts, then frozen again, + * data consistency requires all puts while frozen to be converted into + * flushes. + */ +static bool zcache_freeze; + +static void zcache_control(bool freeze) +{ + zcache_freeze = freeze; +} + +static struct tmem_hostops zcache_hostops = { + .obj_alloc = zcache_obj_alloc, + .obj_free = zcache_obj_free, + .objnode_alloc = zcache_objnode_alloc, + .objnode_free = zcache_objnode_free, + .flush_all_obj = zcache_flush_all_obj, + .control = zcache_control, +}; + +/* + * zcache implementations for PAM page descriptor ops + */ + +static atomic_t zcache_curr_eph_pampd_count = ATOMIC_INIT(0); +static unsigned long zcache_curr_eph_pampd_count_max; + +/* forward reference */ +static int zcache_compress(struct page *from, void **out_va, size_t *out_len); + +static void *zcache_pampd_create(char *data, size_t size, bool raw, int eph, + struct tmem_pool *pool, struct tmem_oid *oid, + uint32_t index) +{ + void *pampd = NULL, *cdata; + size_t clen; + int ret; + unsigned long count; + struct page *page = (struct page *)(data); + struct zcache_client *cli = pool->client; + uint16_t client_id = get_client_id_from_client(cli); + + ret = zcache_compress(page, &cdata, &clen); + if (ret == 0) + goto out; + if (clen == 0 || clen > zbud_max_buddy_size()) { + zcache_compress_poor++; + goto out; + } + pampd = (void *)zbud_create(client_id, pool->pool_id, oid, + index, page, cdata, clen); + if (pampd != NULL) { + count = atomic_inc_return(&zcache_curr_eph_pampd_count); + if (count > zcache_curr_eph_pampd_count_max) + zcache_curr_eph_pampd_count_max = count; + } +out: + return pampd; +} + +/* + * fill the pageframe corresponding to the struct page with the data + * from the passed pampd + */ +static int zcache_pampd_get_data(char *data, size_t *bufsize, bool raw, + void *pampd, struct tmem_pool *pool, + struct tmem_oid *oid, uint32_t index) +{ + BUG(); + return 0; +} + +/* + * fill the pageframe corresponding to the struct page with the data + * from the passed pampd + */ +static int zcache_pampd_get_data_and_free(char *data, size_t *bufsize, bool raw, + void *pampd, struct tmem_pool *pool, + struct tmem_oid *oid, uint32_t index) +{ + int ret = 0; + + zbud_decompress((struct page *)(data), pampd); + zbud_free_and_delist((struct zbud_hdr *)pampd); + atomic_dec(&zcache_curr_eph_pampd_count); + return ret; +} + +/* + * free the pampd and remove it from any zcache lists + * pampd must no longer be pointed to from any tmem data structures! + */ +static void zcache_pampd_free(void *pampd, struct tmem_pool *pool, + struct tmem_oid *oid, uint32_t index) +{ + zbud_free_and_delist((struct zbud_hdr *)pampd); + atomic_dec(&zcache_curr_eph_pampd_count); + BUG_ON(atomic_read(&zcache_curr_eph_pampd_count) < 0); +} + +static void zcache_pampd_free_obj(struct tmem_pool *pool, struct tmem_obj *obj) +{ +} + +static void zcache_pampd_new_obj(struct tmem_obj *obj) +{ +} + +static int zcache_pampd_replace_in_obj(void *pampd, struct tmem_obj *obj) +{ + return -1; +} + +static bool zcache_pampd_is_remote(void *pampd) +{ + return 0; +} + +static struct tmem_pamops zcache_pamops = { + .create = zcache_pampd_create, + .get_data = zcache_pampd_get_data, + .get_data_and_free = zcache_pampd_get_data_and_free, + .free = zcache_pampd_free, + .free_obj = zcache_pampd_free_obj, + .new_obj = zcache_pampd_new_obj, + .replace_in_obj = zcache_pampd_replace_in_obj, + .is_remote = zcache_pampd_is_remote, +}; + +/* + * zcache compression/decompression and related per-cpu stuff + */ + +#define LZO_WORKMEM_BYTES LZO1X_1_MEM_COMPRESS +#define LZO_DSTMEM_PAGE_ORDER 1 +static DEFINE_PER_CPU(unsigned char *, zcache_workmem); +static DEFINE_PER_CPU(unsigned char *, zcache_dstmem); + +static int zcache_compress(struct page *from, void **out_va, size_t *out_len) +{ + int ret = 0; + unsigned char *dmem = __get_cpu_var(zcache_dstmem); + unsigned char *wmem = __get_cpu_var(zcache_workmem); + char *from_va; + + BUG_ON(!irqs_disabled()); + if (unlikely(dmem == NULL || wmem == NULL)) + goto out; /* no buffer, so can't compress */ + from_va = kmap_atomic(from); + mb(); + ret = lzo1x_1_compress(from_va, PAGE_SIZE, dmem, out_len, wmem); + BUG_ON(ret != LZO_E_OK); + *out_va = dmem; + kunmap_atomic(from_va); + ret = 1; +out: + return ret; +} + +#ifdef CONFIG_SYSFS +#define ZCACHE_SYSFS_RO(_name) \ + static ssize_t zcache_##_name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ + { \ + return sprintf(buf, "%lu\n", zcache_##_name); \ + } \ + static struct kobj_attribute zcache_##_name##_attr = { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = zcache_##_name##_show, \ + } + +#define ZCACHE_SYSFS_RO_ATOMIC(_name) \ + static ssize_t zcache_##_name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ + { \ + return sprintf(buf, "%d\n", atomic_read(&zcache_##_name)); \ + } \ + static struct kobj_attribute zcache_##_name##_attr = { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = zcache_##_name##_show, \ + } + +#define ZCACHE_SYSFS_RO_CUSTOM(_name, _func) \ + static ssize_t zcache_##_name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ + { \ + return _func(buf); \ + } \ + static struct kobj_attribute zcache_##_name##_attr = { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = zcache_##_name##_show, \ + } + +ZCACHE_SYSFS_RO(curr_obj_count_max); +ZCACHE_SYSFS_RO(curr_objnode_count_max); +ZCACHE_SYSFS_RO(flush_total); +ZCACHE_SYSFS_RO(flush_found); +ZCACHE_SYSFS_RO(flobj_total); +ZCACHE_SYSFS_RO(flobj_found); +ZCACHE_SYSFS_RO(failed_eph_puts); +ZCACHE_SYSFS_RO(zbud_curr_zbytes); +ZCACHE_SYSFS_RO(zbud_cumul_zpages); +ZCACHE_SYSFS_RO(zbud_cumul_zbytes); +ZCACHE_SYSFS_RO(zbud_buddied_count); +ZCACHE_SYSFS_RO(failed_get_free_pages); +ZCACHE_SYSFS_RO(failed_alloc); +ZCACHE_SYSFS_RO(put_to_flush); +ZCACHE_SYSFS_RO(aborted_preload); +ZCACHE_SYSFS_RO(aborted_shrink); +ZCACHE_SYSFS_RO(compress_poor); +ZCACHE_SYSFS_RO(mean_compress_poor); +ZCACHE_SYSFS_RO(qc_allocated); +ZCACHE_SYSFS_RO(qc_freed); +ZCACHE_SYSFS_RO(qc_used); +ZCACHE_SYSFS_RO(qc_max_used); +ZCACHE_SYSFS_RO_ATOMIC(zbud_curr_raw_pages); +ZCACHE_SYSFS_RO_ATOMIC(zbud_curr_zpages); +ZCACHE_SYSFS_RO_ATOMIC(curr_obj_count); +ZCACHE_SYSFS_RO_ATOMIC(curr_objnode_count); +ZCACHE_SYSFS_RO_CUSTOM(zbud_unbuddied_list_counts, + zbud_show_unbuddied_list_counts); +ZCACHE_SYSFS_RO_CUSTOM(zbud_cumul_chunk_counts, + zbud_show_cumul_chunk_counts); + +static struct attribute *qcache_attrs[] = { + &zcache_curr_obj_count_attr.attr, + &zcache_curr_obj_count_max_attr.attr, + &zcache_curr_objnode_count_attr.attr, + &zcache_curr_objnode_count_max_attr.attr, + &zcache_flush_total_attr.attr, + &zcache_flobj_total_attr.attr, + &zcache_flush_found_attr.attr, + &zcache_flobj_found_attr.attr, + &zcache_failed_eph_puts_attr.attr, + &zcache_compress_poor_attr.attr, + &zcache_mean_compress_poor_attr.attr, + &zcache_zbud_curr_raw_pages_attr.attr, + &zcache_zbud_curr_zpages_attr.attr, + &zcache_zbud_curr_zbytes_attr.attr, + &zcache_zbud_cumul_zpages_attr.attr, + &zcache_zbud_cumul_zbytes_attr.attr, + &zcache_zbud_buddied_count_attr.attr, + &zcache_failed_get_free_pages_attr.attr, + &zcache_failed_alloc_attr.attr, + &zcache_put_to_flush_attr.attr, + &zcache_aborted_preload_attr.attr, + &zcache_aborted_shrink_attr.attr, + &zcache_zbud_unbuddied_list_counts_attr.attr, + &zcache_zbud_cumul_chunk_counts_attr.attr, + &zcache_qc_allocated_attr.attr, + &zcache_qc_freed_attr.attr, + &zcache_qc_used_attr.attr, + &zcache_qc_max_used_attr.attr, + NULL, +}; + +static struct attribute_group qcache_attr_group = { + .attrs = qcache_attrs, + .name = "qcache", +}; + +#endif /* CONFIG_SYSFS */ + +/* + * zcache shims between cleancache ops and tmem + */ + +static int zcache_put_page(int cli_id, int pool_id, struct tmem_oid *oidp, + uint32_t index, struct page *page) +{ + struct tmem_pool *pool; + int ret = -1; + + BUG_ON(!irqs_disabled()); + pool = zcache_get_pool_by_id(cli_id, pool_id); + if (unlikely(pool == NULL)) + goto out; + if (!zcache_freeze && zcache_do_preload(pool) == 0) { + /* preload does preempt_disable on success */ + ret = tmem_put(pool, oidp, index, (char *)(page), + PAGE_SIZE, 0, is_ephemeral(pool)); + if (ret < 0) { + zcache_failed_eph_puts++; + } + zcache_put_pool(pool); + preempt_enable_no_resched(); + } else { + zcache_put_to_flush++; + if (atomic_read(&pool->obj_count) > 0) + /* the put fails whether the flush succeeds or not */ + (void)tmem_flush_page(pool, oidp, index); + zcache_put_pool(pool); + } +out: + return ret; +} + +static int zcache_get_page(int cli_id, int pool_id, struct tmem_oid *oidp, + uint32_t index, struct page *page) +{ + struct tmem_pool *pool; + int ret = -1; + unsigned long flags; + size_t size = PAGE_SIZE; + + local_irq_save(flags); + pool = zcache_get_pool_by_id(cli_id, pool_id); + if (likely(pool != NULL)) { + if (atomic_read(&pool->obj_count) > 0) + ret = tmem_get(pool, oidp, index, (char *)(page), + &size, 0, is_ephemeral(pool)); + zcache_put_pool(pool); + } + local_irq_restore(flags); + return ret; +} + +static int zcache_flush_page(int cli_id, int pool_id, + struct tmem_oid *oidp, uint32_t index) +{ + struct tmem_pool *pool; + int ret = -1; + unsigned long flags; + + local_irq_save(flags); + zcache_flush_total++; + pool = zcache_get_pool_by_id(cli_id, pool_id); + if (likely(pool != NULL)) { + if (atomic_read(&pool->obj_count) > 0) + ret = tmem_flush_page(pool, oidp, index); + zcache_put_pool(pool); + } + if (ret >= 0) + zcache_flush_found++; + local_irq_restore(flags); + return ret; +} + +static int zcache_flush_object(int cli_id, int pool_id, + struct tmem_oid *oidp) +{ + struct tmem_pool *pool; + int ret = -1; + unsigned long flags; + + local_irq_save(flags); + zcache_flobj_total++; + pool = zcache_get_pool_by_id(cli_id, pool_id); + if (likely(pool != NULL)) { + if (atomic_read(&pool->obj_count) > 0) + ret = tmem_flush_object(pool, oidp); + zcache_put_pool(pool); + } + if (ret >= 0) + zcache_flobj_found++; + local_irq_restore(flags); + return ret; +} + +static int zcache_destroy_pool(int cli_id, int pool_id) +{ + struct tmem_pool *pool = NULL; + struct zcache_client *cli = NULL; + int ret = -1; + + if (pool_id < 0) + goto out; + if (cli_id == LOCAL_CLIENT) + cli = &zcache_host; + else if ((unsigned int)cli_id < MAX_CLIENTS) + cli = &zcache_clients[cli_id]; + if (cli == NULL) + goto out; + atomic_inc(&cli->refcount); + pool = cli->tmem_pools[pool_id]; + if (pool == NULL) + goto out; + cli->tmem_pools[pool_id] = NULL; + /* wait for pool activity on other cpus to quiesce */ + while (atomic_read(&pool->refcount) != 0) + ; + atomic_dec(&cli->refcount); + local_bh_disable(); + ret = tmem_destroy_pool(pool); + local_bh_enable(); + kfree(pool); + pr_info("qcache: destroyed pool id=%d, cli_id=%d\n", + pool_id, cli_id); +out: + return ret; +} + +static int zcache_new_pool(uint16_t cli_id, uint32_t flags) +{ + int poolid = -1; + struct tmem_pool *pool; + struct zcache_client *cli = NULL; + + if (cli_id == LOCAL_CLIENT) + cli = &zcache_host; + else if ((unsigned int)cli_id < MAX_CLIENTS) + cli = &zcache_clients[cli_id]; + if (cli == NULL) + goto out; + atomic_inc(&cli->refcount); + pool = kmalloc(sizeof(struct tmem_pool), GFP_KERNEL); + if (pool == NULL) { + pr_info("qcache: pool creation failed: out of memory\n"); + goto out; + } + + for (poolid = 0; poolid < MAX_POOLS_PER_CLIENT; poolid++) + if (cli->tmem_pools[poolid] == NULL) + break; + if (poolid >= MAX_POOLS_PER_CLIENT) { + pr_info("qcache: pool creation failed: max exceeded\n"); + kfree(pool); + poolid = -1; + goto out; + } + atomic_set(&pool->refcount, 0); + pool->client = cli; + pool->pool_id = poolid; + tmem_new_pool(pool, flags); + cli->tmem_pools[poolid] = pool; + pr_info("qcache: created %s tmem pool, id=%d, client=%d\n", + flags & TMEM_POOL_PERSIST ? "persistent" : "ephemeral", + poolid, cli_id); +out: + if (cli != NULL) + atomic_dec(&cli->refcount); + return poolid; +} + +/********** + * Two kernel functionalities currently can be layered on top of tmem. + * These are "cleancache" which is used as a second-chance cache for clean + * page cache pages; and "frontswap" which is used for swap pages + * to avoid writes to disk. A generic "shim" is provided here for each + * to translate in-kernel semantics to zcache semantics. + */ + +static void zcache_cleancache_put_page(int pool_id, + struct cleancache_filekey key, + pgoff_t index, struct page *page) +{ + u32 ind = (u32) index; + struct tmem_oid oid = *(struct tmem_oid *)&key; + + if (likely(ind == index)) + (void)zcache_put_page(LOCAL_CLIENT, pool_id, &oid, index, page); +} + +static int zcache_cleancache_get_page(int pool_id, + struct cleancache_filekey key, + pgoff_t index, struct page *page) +{ + u32 ind = (u32) index; + struct tmem_oid oid = *(struct tmem_oid *)&key; + int ret = -1; + + if (likely(ind == index)) + ret = zcache_get_page(LOCAL_CLIENT, pool_id, &oid, index, page); + return ret; +} + +static void zcache_cleancache_flush_page(int pool_id, + struct cleancache_filekey key, + pgoff_t index) +{ + u32 ind = (u32) index; + struct tmem_oid oid = *(struct tmem_oid *)&key; + + if (likely(ind == index)) + (void)zcache_flush_page(LOCAL_CLIENT, pool_id, &oid, ind); +} + +static void zcache_cleancache_flush_inode(int pool_id, + struct cleancache_filekey key) +{ + struct tmem_oid oid = *(struct tmem_oid *)&key; + + (void)zcache_flush_object(LOCAL_CLIENT, pool_id, &oid); +} + +static void zcache_cleancache_flush_fs(int pool_id) +{ + if (pool_id >= 0) + (void)zcache_destroy_pool(LOCAL_CLIENT, pool_id); +} + +static int zcache_cleancache_init_fs(size_t pagesize) +{ + BUG_ON(sizeof(struct cleancache_filekey) != + sizeof(struct tmem_oid)); + BUG_ON(pagesize != PAGE_SIZE); + return zcache_new_pool(LOCAL_CLIENT, 0); +} + +static int zcache_cleancache_init_shared_fs(char *uuid, size_t pagesize) +{ + /* shared pools are unsupported and map to private */ + BUG_ON(sizeof(struct cleancache_filekey) != + sizeof(struct tmem_oid)); + BUG_ON(pagesize != PAGE_SIZE); + return zcache_new_pool(LOCAL_CLIENT, 0); +} + +static struct cleancache_ops zcache_cleancache_ops = { + .put_page = zcache_cleancache_put_page, + .get_page = zcache_cleancache_get_page, + .invalidate_page = zcache_cleancache_flush_page, + .invalidate_inode = zcache_cleancache_flush_inode, + .invalidate_fs = zcache_cleancache_flush_fs, + .init_shared_fs = zcache_cleancache_init_shared_fs, + .init_fs = zcache_cleancache_init_fs +}; + +struct cleancache_ops zcache_cleancache_register_ops(void) +{ + struct cleancache_ops old_ops = + cleancache_register_ops(&zcache_cleancache_ops); + + return old_ops; +} + +static int __init qcache_init(void) +{ + int ret = 0; + struct qcache_info *qc = &qcache_info; + struct fmem_data *fdp; + int bitmap_size; + unsigned int cpu; + struct cleancache_ops old_ops; + +#ifdef CONFIG_SYSFS + ret = sysfs_create_group(mm_kobj, &qcache_attr_group); + if (ret) { + pr_err("qcache: can't create sysfs\n"); + goto out; + } +#endif /* CONFIG_SYSFS */ + + fdp = fmem_get_info(); + qc->addr = fdp->virt; + qc->pages = fdp->size >> PAGE_SHIFT; + if (!qc->pages) + goto out; + + tmem_register_hostops(&zcache_hostops); + tmem_register_pamops(&zcache_pamops); + for_each_online_cpu(cpu) { + per_cpu(zcache_dstmem, cpu) = (void *)__get_free_pages( + GFP_KERNEL | __GFP_REPEAT, + LZO_DSTMEM_PAGE_ORDER), + per_cpu(zcache_workmem, cpu) = + kzalloc(LZO1X_MEM_COMPRESS, + GFP_KERNEL | __GFP_REPEAT); + } + zcache_objnode_cache = kmem_cache_create("zcache_objnode", + sizeof(struct tmem_objnode), 0, 0, NULL); + zcache_obj_cache = kmem_cache_create("zcache_obj", + sizeof(struct tmem_obj), 0, 0, NULL); + ret = zcache_new_client(LOCAL_CLIENT); + if (ret) { + pr_err("qcache: can't create client\n"); + goto out; + } + + zbud_init(); + old_ops = zcache_cleancache_register_ops(); + pr_info("qcache: cleancache enabled using kernel " + "transcendent memory and compression buddies\n"); + if (old_ops.init_fs != NULL) + pr_warning("qcache: cleancache_ops overridden"); + + + bitmap_size = BITS_TO_LONGS(qc->pages) * sizeof(long); + + qc->bitmap = kzalloc(bitmap_size, GFP_KERNEL); + if (!qc->bitmap) { + pr_info("can't allocate qcache bitmap!\n"); + ret = -ENOMEM; + goto out; + } + spin_lock_init(&qc->lock); + + fmem_set_state(FMEM_T_STATE); + +out: + return ret; +} + +module_init(qcache_init) diff --git a/drivers/staging/qcache/tmem.c b/drivers/staging/qcache/tmem.c new file mode 100644 index 0000000000000000000000000000000000000000..e5c3f305541937ce139c2470f3d867d81e2d869e --- /dev/null +++ b/drivers/staging/qcache/tmem.c @@ -0,0 +1,833 @@ +/* + * In-kernel transcendent memory (generic implementation) + * + * Copyright (c) 2009-2011, Dan Magenheimer, Oracle Corp. + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * The primary purpose of Transcedent Memory ("tmem") is to map object-oriented + * "handles" (triples containing a pool id, and object id, and an index), to + * pages in a page-accessible memory (PAM). Tmem references the PAM pages via + * an abstract "pampd" (PAM page-descriptor), which can be operated on by a + * set of functions (pamops). Each pampd contains some representation of + * PAGE_SIZE bytes worth of data. Tmem must support potentially millions of + * pages and must be able to insert, find, and delete these pages at a + * potential frequency of thousands per second concurrently across many CPUs, + * (and, if used with KVM, across many vcpus across many guests). + * Tmem is tracked with a hierarchy of data structures, organized by + * the elements in a handle-tuple: pool_id, object_id, and page index. + * One or more "clients" (e.g. guests) each provide one or more tmem_pools. + * Each pool, contains a hash table of rb_trees of tmem_objs. Each + * tmem_obj contains a radix-tree-like tree of pointers, with intermediate + * nodes called tmem_objnodes. Each leaf pointer in this tree points to + * a pampd, which is accessible only through a small set of callbacks + * registered by the PAM implementation (see tmem_register_pamops). Tmem + * does all memory allocation via a set of callbacks registered by the tmem + * host implementation (e.g. see tmem_register_hostops). + */ + +#include +#include +#include + +#include "tmem.h" + +/* data structure sentinels used for debugging... see tmem.h */ +#define POOL_SENTINEL 0x87658765 +#define OBJ_SENTINEL 0x12345678 +#define OBJNODE_SENTINEL 0xfedcba09 + +static bool tmem_enabled; + +static void lock_tmem_state(void) +{ + lock_fmem_state(); +} + +static void unlock_tmem_state(void) +{ + unlock_fmem_state(); +} + +/* + * A tmem host implementation must use this function to register callbacks + * for memory allocation. + */ +static struct tmem_hostops tmem_hostops; + +static void tmem_objnode_tree_init(void); + +void tmem_register_hostops(struct tmem_hostops *m) +{ + tmem_objnode_tree_init(); + tmem_hostops = *m; +} + +/* + * A tmem host implementation must use this function to register + * callbacks for a page-accessible memory (PAM) implementation + */ +static struct tmem_pamops tmem_pamops; + +void tmem_register_pamops(struct tmem_pamops *m) +{ + tmem_pamops = *m; +} + +/* + * Oid's are potentially very sparse and tmem_objs may have an indeterminately + * short life, being added and deleted at a relatively high frequency. + * So an rb_tree is an ideal data structure to manage tmem_objs. But because + * of the potentially huge number of tmem_objs, each pool manages a hashtable + * of rb_trees to reduce search, insert, delete, and rebalancing time. + * Each hashbucket also has a lock to manage concurrent access. + * + * The following routines manage tmem_objs. When any tmem_obj is accessed, + * the hashbucket lock must be held. + */ + +/* searches for object==oid in pool, returns locked object if found */ +static struct tmem_obj *tmem_obj_find(struct tmem_hashbucket *hb, + struct tmem_oid *oidp) +{ + struct rb_node *rbnode; + struct tmem_obj *obj; + + rbnode = hb->obj_rb_root.rb_node; + while (rbnode) { + BUG_ON(RB_EMPTY_NODE(rbnode)); + obj = rb_entry(rbnode, struct tmem_obj, rb_tree_node); + switch (tmem_oid_compare(oidp, &obj->oid)) { + case 0: /* equal */ + goto out; + case -1: + rbnode = rbnode->rb_left; + break; + case 1: + rbnode = rbnode->rb_right; + break; + } + } + obj = NULL; +out: + return obj; +} + +static void tmem_pampd_destroy_all_in_obj(struct tmem_obj *); + +/* free an object that has no more pampds in it */ +static void tmem_obj_free(struct tmem_obj *obj, struct tmem_hashbucket *hb) +{ + struct tmem_pool *pool; + + BUG_ON(obj == NULL); + ASSERT_SENTINEL(obj, OBJ); + BUG_ON(obj->pampd_count > 0); + pool = obj->pool; + BUG_ON(pool == NULL); + if (obj->objnode_tree_root != NULL) /* may be "stump" with no leaves */ + tmem_pampd_destroy_all_in_obj(obj); + BUG_ON(obj->objnode_tree_root != NULL); + BUG_ON((long)obj->objnode_count != 0); + atomic_dec(&pool->obj_count); + BUG_ON(atomic_read(&pool->obj_count) < 0); + INVERT_SENTINEL(obj, OBJ); + obj->pool = NULL; + tmem_oid_set_invalid(&obj->oid); + rb_erase(&obj->rb_tree_node, &hb->obj_rb_root); +} + +/* + * initialize, and insert an tmem_object_root (called only if find failed) + */ +static void tmem_obj_init(struct tmem_obj *obj, struct tmem_hashbucket *hb, + struct tmem_pool *pool, + struct tmem_oid *oidp) +{ + struct rb_root *root = &hb->obj_rb_root; + struct rb_node **new = &(root->rb_node), *parent = NULL; + struct tmem_obj *this; + + BUG_ON(pool == NULL); + atomic_inc(&pool->obj_count); + obj->objnode_tree_height = 0; + obj->objnode_tree_root = NULL; + obj->pool = pool; + obj->oid = *oidp; + obj->objnode_count = 0; + obj->pampd_count = 0; + (*tmem_pamops.new_obj)(obj); + SET_SENTINEL(obj, OBJ); + while (*new) { + BUG_ON(RB_EMPTY_NODE(*new)); + this = rb_entry(*new, struct tmem_obj, rb_tree_node); + parent = *new; + switch (tmem_oid_compare(oidp, &this->oid)) { + case 0: + BUG(); /* already present; should never happen! */ + break; + case -1: + new = &(*new)->rb_left; + break; + case 1: + new = &(*new)->rb_right; + break; + } + } + rb_link_node(&obj->rb_tree_node, parent, new); + rb_insert_color(&obj->rb_tree_node, root); +} + +/* + * Tmem is managed as a set of tmem_pools with certain attributes, such as + * "ephemeral" vs "persistent". These attributes apply to all tmem_objs + * and all pampds that belong to a tmem_pool. A tmem_pool is created + * or deleted relatively rarely (for example, when a filesystem is + * mounted or unmounted. + */ + +/* flush all data from a pool and, optionally, free it */ +static void tmem_pool_flush(struct tmem_pool *pool, bool destroy) +{ + struct rb_node *rbnode; + struct tmem_obj *obj; + struct tmem_hashbucket *hb = &pool->hashbucket[0]; + int i; + + BUG_ON(pool == NULL); + for (i = 0; i < TMEM_HASH_BUCKETS; i++, hb++) { + spin_lock(&hb->lock); + rbnode = rb_first(&hb->obj_rb_root); + while (rbnode != NULL) { + obj = rb_entry(rbnode, struct tmem_obj, rb_tree_node); + rbnode = rb_next(rbnode); + tmem_pampd_destroy_all_in_obj(obj); + tmem_obj_free(obj, hb); + (*tmem_hostops.obj_free)(obj, pool); + } + spin_unlock(&hb->lock); + } + if (destroy) + list_del(&pool->pool_list); +} + +/* + * A tmem_obj contains a radix-tree-like tree in which the intermediate + * nodes are called tmem_objnodes. (The kernel lib/radix-tree.c implementation + * is very specialized and tuned for specific uses and is not particularly + * suited for use from this code, though some code from the core algorithms has + * been reused, thus the copyright notices below). Each tmem_objnode contains + * a set of pointers which point to either a set of intermediate tmem_objnodes + * or a set of of pampds. + * + * Portions Copyright (C) 2001 Momchil Velikov + * Portions Copyright (C) 2001 Christoph Hellwig + * Portions Copyright (C) 2005 SGI, Christoph Lameter + */ + +struct tmem_objnode_tree_path { + struct tmem_objnode *objnode; + int offset; +}; + +/* objnode height_to_maxindex translation */ +static unsigned long tmem_objnode_tree_h2max[OBJNODE_TREE_MAX_PATH + 1]; + +static void tmem_objnode_tree_init(void) +{ + unsigned int ht, tmp; + + for (ht = 0; ht < ARRAY_SIZE(tmem_objnode_tree_h2max); ht++) { + tmp = ht * OBJNODE_TREE_MAP_SHIFT; + if (tmp >= OBJNODE_TREE_INDEX_BITS) + tmem_objnode_tree_h2max[ht] = ~0UL; + else + tmem_objnode_tree_h2max[ht] = + (~0UL >> (OBJNODE_TREE_INDEX_BITS - tmp - 1)) >> 1; + } +} + +static struct tmem_objnode *tmem_objnode_alloc(struct tmem_obj *obj) +{ + struct tmem_objnode *objnode; + + ASSERT_SENTINEL(obj, OBJ); + BUG_ON(obj->pool == NULL); + ASSERT_SENTINEL(obj->pool, POOL); + objnode = (*tmem_hostops.objnode_alloc)(obj->pool); + if (unlikely(objnode == NULL)) + goto out; + objnode->obj = obj; + SET_SENTINEL(objnode, OBJNODE); + memset(&objnode->slots, 0, sizeof(objnode->slots)); + objnode->slots_in_use = 0; + obj->objnode_count++; +out: + return objnode; +} + +static void tmem_objnode_free(struct tmem_objnode *objnode) +{ + struct tmem_pool *pool; + int i; + + BUG_ON(objnode == NULL); + for (i = 0; i < OBJNODE_TREE_MAP_SIZE; i++) + BUG_ON(objnode->slots[i] != NULL); + ASSERT_SENTINEL(objnode, OBJNODE); + INVERT_SENTINEL(objnode, OBJNODE); + BUG_ON(objnode->obj == NULL); + ASSERT_SENTINEL(objnode->obj, OBJ); + pool = objnode->obj->pool; + BUG_ON(pool == NULL); + ASSERT_SENTINEL(pool, POOL); + objnode->obj->objnode_count--; + objnode->obj = NULL; + (*tmem_hostops.objnode_free)(objnode, pool); +} + +/* + * lookup index in object and return associated pampd (or NULL if not found) + */ +static void **__tmem_pampd_lookup_in_obj(struct tmem_obj *obj, uint32_t index) +{ + unsigned int height, shift; + struct tmem_objnode **slot = NULL; + + BUG_ON(obj == NULL); + ASSERT_SENTINEL(obj, OBJ); + BUG_ON(obj->pool == NULL); + ASSERT_SENTINEL(obj->pool, POOL); + + height = obj->objnode_tree_height; + if (index > tmem_objnode_tree_h2max[obj->objnode_tree_height]) + goto out; + if (height == 0 && obj->objnode_tree_root) { + slot = &obj->objnode_tree_root; + goto out; + } + shift = (height-1) * OBJNODE_TREE_MAP_SHIFT; + slot = &obj->objnode_tree_root; + while (height > 0) { + if (*slot == NULL) + goto out; + slot = (struct tmem_objnode **) + ((*slot)->slots + + ((index >> shift) & OBJNODE_TREE_MAP_MASK)); + shift -= OBJNODE_TREE_MAP_SHIFT; + height--; + } +out: + return slot != NULL ? (void **)slot : NULL; +} + +static void *tmem_pampd_lookup_in_obj(struct tmem_obj *obj, uint32_t index) +{ + struct tmem_objnode **slot; + + slot = (struct tmem_objnode **)__tmem_pampd_lookup_in_obj(obj, index); + return slot != NULL ? *slot : NULL; +} + +static void *tmem_pampd_replace_in_obj(struct tmem_obj *obj, uint32_t index, + void *new_pampd) +{ + struct tmem_objnode **slot; + void *ret = NULL; + + slot = (struct tmem_objnode **)__tmem_pampd_lookup_in_obj(obj, index); + if ((slot != NULL) && (*slot != NULL)) { + void *old_pampd = *(void **)slot; + *(void **)slot = new_pampd; + (*tmem_pamops.free)(old_pampd, obj->pool, NULL, 0); + ret = new_pampd; + } + return ret; +} + +static int tmem_pampd_add_to_obj(struct tmem_obj *obj, uint32_t index, + void *pampd) +{ + int ret = 0; + struct tmem_objnode *objnode = NULL, *newnode, *slot; + unsigned int height, shift; + int offset = 0; + + /* if necessary, extend the tree to be higher */ + if (index > tmem_objnode_tree_h2max[obj->objnode_tree_height]) { + height = obj->objnode_tree_height + 1; + if (index > tmem_objnode_tree_h2max[height]) + while (index > tmem_objnode_tree_h2max[height]) + height++; + if (obj->objnode_tree_root == NULL) { + obj->objnode_tree_height = height; + goto insert; + } + do { + newnode = tmem_objnode_alloc(obj); + if (!newnode) { + ret = -ENOMEM; + goto out; + } + newnode->slots[0] = obj->objnode_tree_root; + newnode->slots_in_use = 1; + obj->objnode_tree_root = newnode; + obj->objnode_tree_height++; + } while (height > obj->objnode_tree_height); + } +insert: + slot = obj->objnode_tree_root; + height = obj->objnode_tree_height; + shift = (height-1) * OBJNODE_TREE_MAP_SHIFT; + while (height > 0) { + if (slot == NULL) { + /* add a child objnode. */ + slot = tmem_objnode_alloc(obj); + if (!slot) { + ret = -ENOMEM; + goto out; + } + if (objnode) { + + objnode->slots[offset] = slot; + objnode->slots_in_use++; + } else + obj->objnode_tree_root = slot; + } + /* go down a level */ + offset = (index >> shift) & OBJNODE_TREE_MAP_MASK; + objnode = slot; + slot = objnode->slots[offset]; + shift -= OBJNODE_TREE_MAP_SHIFT; + height--; + } + BUG_ON(slot != NULL); + if (objnode) { + objnode->slots_in_use++; + objnode->slots[offset] = pampd; + } else + obj->objnode_tree_root = pampd; + obj->pampd_count++; +out: + return ret; +} + +static void *tmem_pampd_delete_from_obj(struct tmem_obj *obj, uint32_t index) +{ + struct tmem_objnode_tree_path path[OBJNODE_TREE_MAX_PATH + 1]; + struct tmem_objnode_tree_path *pathp = path; + struct tmem_objnode *slot = NULL; + unsigned int height, shift; + int offset; + + BUG_ON(obj == NULL); + ASSERT_SENTINEL(obj, OBJ); + BUG_ON(obj->pool == NULL); + ASSERT_SENTINEL(obj->pool, POOL); + height = obj->objnode_tree_height; + if (index > tmem_objnode_tree_h2max[height]) + goto out; + slot = obj->objnode_tree_root; + if (height == 0 && obj->objnode_tree_root) { + obj->objnode_tree_root = NULL; + goto out; + } + shift = (height - 1) * OBJNODE_TREE_MAP_SHIFT; + pathp->objnode = NULL; + do { + if (slot == NULL) + goto out; + pathp++; + offset = (index >> shift) & OBJNODE_TREE_MAP_MASK; + pathp->offset = offset; + pathp->objnode = slot; + slot = slot->slots[offset]; + shift -= OBJNODE_TREE_MAP_SHIFT; + height--; + } while (height > 0); + if (slot == NULL) + goto out; + while (pathp->objnode) { + pathp->objnode->slots[pathp->offset] = NULL; + pathp->objnode->slots_in_use--; + if (pathp->objnode->slots_in_use) { + if (pathp->objnode == obj->objnode_tree_root) { + while (obj->objnode_tree_height > 0 && + obj->objnode_tree_root->slots_in_use == 1 && + obj->objnode_tree_root->slots[0]) { + struct tmem_objnode *to_free = + obj->objnode_tree_root; + + obj->objnode_tree_root = + to_free->slots[0]; + obj->objnode_tree_height--; + to_free->slots[0] = NULL; + to_free->slots_in_use = 0; + tmem_objnode_free(to_free); + } + } + goto out; + } + tmem_objnode_free(pathp->objnode); /* 0 slots used, free it */ + pathp--; + } + obj->objnode_tree_height = 0; + obj->objnode_tree_root = NULL; + +out: + if (slot != NULL) + obj->pampd_count--; + BUG_ON(obj->pampd_count < 0); + return slot; +} + +/* recursively walk the objnode_tree destroying pampds and objnodes */ +static void tmem_objnode_node_destroy(struct tmem_obj *obj, + struct tmem_objnode *objnode, + unsigned int ht) +{ + int i; + + if (ht == 0) + return; + for (i = 0; i < OBJNODE_TREE_MAP_SIZE; i++) { + if (objnode->slots[i]) { + if (ht == 1) { + obj->pampd_count--; + (*tmem_pamops.free)(objnode->slots[i], + obj->pool, NULL, 0); + objnode->slots[i] = NULL; + continue; + } + tmem_objnode_node_destroy(obj, objnode->slots[i], ht-1); + tmem_objnode_free(objnode->slots[i]); + objnode->slots[i] = NULL; + } + } +} + +static void tmem_pampd_destroy_all_in_obj(struct tmem_obj *obj) +{ + if (obj->objnode_tree_root == NULL) + return; + if (obj->objnode_tree_height == 0) { + obj->pampd_count--; + (*tmem_pamops.free)(obj->objnode_tree_root, obj->pool, NULL, 0); + } else { + tmem_objnode_node_destroy(obj, obj->objnode_tree_root, + obj->objnode_tree_height); + tmem_objnode_free(obj->objnode_tree_root); + obj->objnode_tree_height = 0; + } + obj->objnode_tree_root = NULL; + (*tmem_pamops.free_obj)(obj->pool, obj); +} + +/* + * Tmem is operated on by a set of well-defined actions: + * "put", "get", "flush", "flush_object", "new pool" and "destroy pool". + * (The tmem ABI allows for subpages and exchanges but these operations + * are not included in this implementation.) + * + * These "tmem core" operations are implemented in the following functions. + */ + +/* + * "Put" a page, e.g. copy a page from the kernel into newly allocated + * PAM space (if such space is available). Tmem_put is complicated by + * a corner case: What if a page with matching handle already exists in + * tmem? To guarantee coherency, one of two actions is necessary: Either + * the data for the page must be overwritten, or the page must be + * "flushed" so that the data is not accessible to a subsequent "get". + * Since these "duplicate puts" are relatively rare, this implementation + * always flushes for simplicity. + */ +int tmem_put(struct tmem_pool *pool, struct tmem_oid *oidp, uint32_t index, + char *data, size_t size, bool raw, bool ephemeral) +{ + struct tmem_obj *obj = NULL, *objfound = NULL, *objnew = NULL; + void *pampd = NULL, *pampd_del = NULL; + int ret = -ENOMEM; + struct tmem_hashbucket *hb; + + lock_tmem_state(); + if (!tmem_enabled) + goto disabled; + hb = &pool->hashbucket[tmem_oid_hash(oidp)]; + spin_lock(&hb->lock); + obj = objfound = tmem_obj_find(hb, oidp); + if (obj != NULL) { + pampd = tmem_pampd_lookup_in_obj(objfound, index); + if (pampd != NULL) { + /* if found, is a dup put, flush the old one */ + pampd_del = tmem_pampd_delete_from_obj(obj, index); + BUG_ON(pampd_del != pampd); + (*tmem_pamops.free)(pampd, pool, oidp, index); + if (obj->pampd_count == 0) { + objnew = obj; + objfound = NULL; + } + pampd = NULL; + } + } else { + obj = objnew = (*tmem_hostops.obj_alloc)(pool); + if (unlikely(obj == NULL)) { + ret = -ENOMEM; + goto out; + } + tmem_obj_init(obj, hb, pool, oidp); + } + BUG_ON(obj == NULL); + BUG_ON(((objnew != obj) && (objfound != obj)) || (objnew == objfound)); + pampd = (*tmem_pamops.create)(data, size, raw, ephemeral, + obj->pool, &obj->oid, index); + if (unlikely(pampd == NULL)) + goto free; + ret = tmem_pampd_add_to_obj(obj, index, pampd); + if (unlikely(ret == -ENOMEM)) + /* may have partially built objnode tree ("stump") */ + goto delete_and_free; + goto out; + +delete_and_free: + (void)tmem_pampd_delete_from_obj(obj, index); +free: + if (pampd) + (*tmem_pamops.free)(pampd, pool, NULL, 0); + if (objnew) { + tmem_obj_free(objnew, hb); + (*tmem_hostops.obj_free)(objnew, pool); + } +out: + spin_unlock(&hb->lock); +disabled: + unlock_tmem_state(); + return ret; +} + +/* + * "Get" a page, e.g. if one can be found, copy the tmem page with the + * matching handle from PAM space to the kernel. By tmem definition, + * when a "get" is successful on an ephemeral page, the page is "flushed", + * and when a "get" is successful on a persistent page, the page is retained + * in tmem. Note that to preserve + * coherency, "get" can never be skipped if tmem contains the data. + * That is, if a get is done with a certain handle and fails, any + * subsequent "get" must also fail (unless of course there is a + * "put" done with the same handle). + + */ +int tmem_get(struct tmem_pool *pool, struct tmem_oid *oidp, uint32_t index, + char *data, size_t *size, bool raw, int get_and_free) +{ + struct tmem_obj *obj; + void *pampd; + bool ephemeral = is_ephemeral(pool); + int ret = -1; + struct tmem_hashbucket *hb; + bool free = (get_and_free == 1) || ((get_and_free == 0) && ephemeral); + bool lock_held = false; + + lock_tmem_state(); + if (!tmem_enabled) + goto disabled; + hb = &pool->hashbucket[tmem_oid_hash(oidp)]; + spin_lock(&hb->lock); + lock_held = true; + obj = tmem_obj_find(hb, oidp); + if (obj == NULL) + goto out; + if (free) + pampd = tmem_pampd_delete_from_obj(obj, index); + else + pampd = tmem_pampd_lookup_in_obj(obj, index); + if (pampd == NULL) + goto out; + if (free) { + if (obj->pampd_count == 0) { + tmem_obj_free(obj, hb); + (*tmem_hostops.obj_free)(obj, pool); + obj = NULL; + } + } + if (tmem_pamops.is_remote(pampd)) { + lock_held = false; + spin_unlock(&hb->lock); + } + if (free) + ret = (*tmem_pamops.get_data_and_free)( + data, size, raw, pampd, pool, oidp, index); + else + ret = (*tmem_pamops.get_data)( + data, size, raw, pampd, pool, oidp, index); + if (ret < 0) + goto out; + ret = 0; +out: + if (lock_held) + spin_unlock(&hb->lock); +disabled: + unlock_tmem_state(); + return ret; +} + +/* + * If a page in tmem matches the handle, "flush" this page from tmem such + * that any subsequent "get" does not succeed (unless, of course, there + * was another "put" with the same handle). + */ +int tmem_flush_page(struct tmem_pool *pool, + struct tmem_oid *oidp, uint32_t index) +{ + struct tmem_obj *obj; + void *pampd; + int ret = -1; + struct tmem_hashbucket *hb; + + hb = &pool->hashbucket[tmem_oid_hash(oidp)]; + spin_lock(&hb->lock); + obj = tmem_obj_find(hb, oidp); + if (obj == NULL) + goto out; + pampd = tmem_pampd_delete_from_obj(obj, index); + if (pampd == NULL) + goto out; + (*tmem_pamops.free)(pampd, pool, oidp, index); + if (obj->pampd_count == 0) { + tmem_obj_free(obj, hb); + (*tmem_hostops.obj_free)(obj, pool); + } + ret = 0; + +out: + spin_unlock(&hb->lock); + return ret; +} + +/* + * If a page in tmem matches the handle, replace the page so that any + * subsequent "get" gets the new page. Returns 0 if + * there was a page to replace, else returns -1. + */ +int tmem_replace(struct tmem_pool *pool, struct tmem_oid *oidp, + uint32_t index, void *new_pampd) +{ + struct tmem_obj *obj; + int ret = -1; + struct tmem_hashbucket *hb; + + lock_tmem_state(); + if (!tmem_enabled) + goto disabled; + hb = &pool->hashbucket[tmem_oid_hash(oidp)]; + spin_lock(&hb->lock); + obj = tmem_obj_find(hb, oidp); + if (obj == NULL) + goto out; + new_pampd = tmem_pampd_replace_in_obj(obj, index, new_pampd); + ret = (*tmem_pamops.replace_in_obj)(new_pampd, obj); +out: + spin_unlock(&hb->lock); +disabled: + unlock_tmem_state(); + return ret; +} + +/* + * "Flush" all pages in tmem matching this oid. + */ +int tmem_flush_object(struct tmem_pool *pool, struct tmem_oid *oidp) +{ + struct tmem_obj *obj; + struct tmem_hashbucket *hb; + int ret = -1; + + hb = &pool->hashbucket[tmem_oid_hash(oidp)]; + spin_lock(&hb->lock); + obj = tmem_obj_find(hb, oidp); + if (obj == NULL) + goto out; + tmem_pampd_destroy_all_in_obj(obj); + tmem_obj_free(obj, hb); + (*tmem_hostops.obj_free)(obj, pool); + ret = 0; + +out: + spin_unlock(&hb->lock); + return ret; +} + +/* + * "Flush" all pages (and tmem_objs) from this tmem_pool and disable + * all subsequent access to this tmem_pool. + */ +int tmem_destroy_pool(struct tmem_pool *pool) +{ + int ret = -1; + + if (pool == NULL) + goto out; + tmem_pool_flush(pool, 1); + ret = 0; +out: + return ret; +} + +int tmem_flush_pool(struct tmem_pool *pool) +{ + int ret = -1; + + if (pool == NULL) + goto out; + tmem_pool_flush(pool, 0); + ret = 0; +out: + return ret; +} + +static LIST_HEAD(tmem_global_pool_list); + +/* + * Create a new tmem_pool with the provided flag and return + * a pool id provided by the tmem host implementation. + */ +void tmem_new_pool(struct tmem_pool *pool, uint32_t flags) +{ + int persistent = flags & TMEM_POOL_PERSIST; + int shared = flags & TMEM_POOL_SHARED; + struct tmem_hashbucket *hb = &pool->hashbucket[0]; + int i; + + for (i = 0; i < TMEM_HASH_BUCKETS; i++, hb++) { + hb->obj_rb_root = RB_ROOT; + spin_lock_init(&hb->lock); + } + INIT_LIST_HEAD(&pool->pool_list); + atomic_set(&pool->obj_count, 0); + SET_SENTINEL(pool, POOL); + list_add_tail(&pool->pool_list, &tmem_global_pool_list); + pool->persistent = persistent; + pool->shared = shared; +} + +/* The following must be called with tmem state locked */ +static void tmem_cleanup(void) +{ + (*tmem_hostops.flush_all_obj)(); +} + +void tmem_enable(void) +{ + pr_info("turning tmem on\n"); + tmem_enabled = true; + + (*tmem_hostops.control)(false); +} + +void tmem_disable(void) +{ + pr_info("turning tmem off\n"); + tmem_enabled = false; + + tmem_cleanup(); + (*tmem_hostops.control)(true); +} diff --git a/drivers/staging/qcache/tmem.h b/drivers/staging/qcache/tmem.h new file mode 100644 index 0000000000000000000000000000000000000000..dd8a6eacb171faccde0ab95c3a8101458c410c30 --- /dev/null +++ b/drivers/staging/qcache/tmem.h @@ -0,0 +1,212 @@ +/* + * tmem.h + * + * Transcendent memory + * + * Copyright (c) 2009-2011, Dan Magenheimer, Oracle Corp. + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + */ + +#ifndef _TMEM_H_ +#define _TMEM_H_ + +#include +#include +#include +#include +#include + +/* + * These are pre-defined by the Xen<->Linux ABI + */ +#define TMEM_PUT_PAGE 4 +#define TMEM_GET_PAGE 5 +#define TMEM_FLUSH_PAGE 6 +#define TMEM_FLUSH_OBJECT 7 +#define TMEM_POOL_PERSIST 1 +#define TMEM_POOL_SHARED 2 +#define TMEM_POOL_PRECOMPRESSED 4 +#define TMEM_POOL_PAGESIZE_SHIFT 4 +#define TMEM_POOL_PAGESIZE_MASK 0xf +#define TMEM_POOL_RESERVED_BITS 0x00ffff00 + +/* + * sentinels have proven very useful for debugging but can be removed + * or disabled before final merge. + */ +#define SENTINELS +#ifdef SENTINELS +#define DECL_SENTINEL uint32_t sentinel; +#define SET_SENTINEL(_x, _y) (_x->sentinel = _y##_SENTINEL) +#define INVERT_SENTINEL(_x, _y) (_x->sentinel = ~_y##_SENTINEL) +#define ASSERT_SENTINEL(_x, _y) WARN_ON(_x->sentinel != _y##_SENTINEL) +#define ASSERT_INVERTED_SENTINEL(_x, _y) WARN_ON(_x->sentinel != ~_y##_SENTINEL) +#else +#define DECL_SENTINEL +#define SET_SENTINEL(_x, _y) do { } while (0) +#define INVERT_SENTINEL(_x, _y) do { } while (0) +#define ASSERT_SENTINEL(_x, _y) do { } while (0) +#define ASSERT_INVERTED_SENTINEL(_x, _y) do { } while (0) +#endif + +/* + * A pool is the highest-level data structure managed by tmem and + * usually corresponds to a large independent set of pages such as + * a filesystem. Each pool has an id, and certain attributes and counters. + * It also contains a set of hash buckets, each of which contains an rbtree + * of objects and a lock to manage concurrency within the pool. + */ + +#define TMEM_HASH_BUCKET_BITS 8 +#define TMEM_HASH_BUCKETS (1<persistent) +#define is_ephemeral(_p) (!(_p->persistent)) + +/* + * An object id ("oid") is large: 192-bits (to ensure, for example, files + * in a modern filesystem can be uniquely identified). + */ + +struct tmem_oid { + uint64_t oid[3]; +}; + +static inline void tmem_oid_set_invalid(struct tmem_oid *oidp) +{ + oidp->oid[0] = oidp->oid[1] = oidp->oid[2] = -1UL; +} + +static inline bool tmem_oid_valid(struct tmem_oid *oidp) +{ + return oidp->oid[0] != -1UL || oidp->oid[1] != -1UL || + oidp->oid[2] != -1UL; +} + +static inline int tmem_oid_compare(struct tmem_oid *left, + struct tmem_oid *right) +{ + int ret; + + if (left->oid[2] == right->oid[2]) { + if (left->oid[1] == right->oid[1]) { + if (left->oid[0] == right->oid[0]) + ret = 0; + else if (left->oid[0] < right->oid[0]) + ret = -1; + else + return 1; + } else if (left->oid[1] < right->oid[1]) + ret = -1; + else + ret = 1; + } else if (left->oid[2] < right->oid[2]) + ret = -1; + else + ret = 1; + return ret; +} + +static inline unsigned tmem_oid_hash(struct tmem_oid *oidp) +{ + return hash_long(oidp->oid[0] ^ oidp->oid[1] ^ oidp->oid[2], + TMEM_HASH_BUCKET_BITS); +} + +/* + * A tmem_obj contains an identifier (oid), pointers to the parent + * pool and the rb_tree to which it belongs, counters, and an ordered + * set of pampds, structured in a radix-tree-like tree. The intermediate + * nodes of the tree are called tmem_objnodes. + */ + +struct tmem_objnode; + +struct tmem_obj { + struct tmem_oid oid; + struct tmem_pool *pool; + struct rb_node rb_tree_node; + struct tmem_objnode *objnode_tree_root; + unsigned int objnode_tree_height; + unsigned long objnode_count; + long pampd_count; + void *extra; /* for private use by pampd implementation */ + DECL_SENTINEL +}; + +#define OBJNODE_TREE_MAP_SHIFT 6 +#define OBJNODE_TREE_MAP_SIZE (1UL << OBJNODE_TREE_MAP_SHIFT) +#define OBJNODE_TREE_MAP_MASK (OBJNODE_TREE_MAP_SIZE-1) +#define OBJNODE_TREE_INDEX_BITS (8 /* CHAR_BIT */ * sizeof(unsigned long)) +#define OBJNODE_TREE_MAX_PATH \ + (OBJNODE_TREE_INDEX_BITS/OBJNODE_TREE_MAP_SHIFT + 2) + +struct tmem_objnode { + struct tmem_obj *obj; + DECL_SENTINEL + void *slots[OBJNODE_TREE_MAP_SIZE]; + unsigned int slots_in_use; +}; + +/* pampd abstract datatype methods provided by the PAM implementation */ +struct tmem_pamops { + void *(*create)(char *, size_t, bool, int, + struct tmem_pool *, struct tmem_oid *, uint32_t); + int (*get_data)(char *, size_t *, bool, void *, struct tmem_pool *, + struct tmem_oid *, uint32_t); + int (*get_data_and_free)(char *, size_t *, bool, void *, + struct tmem_pool *, struct tmem_oid *, + uint32_t); + void (*free)(void *, struct tmem_pool *, struct tmem_oid *, uint32_t); + void (*free_obj)(struct tmem_pool *, struct tmem_obj *); + bool (*is_remote)(void *); + void (*new_obj)(struct tmem_obj *); + int (*replace_in_obj)(void *, struct tmem_obj *); +}; +extern void tmem_register_pamops(struct tmem_pamops *m); + +/* memory allocation methods provided by the host implementation */ +struct tmem_hostops { + struct tmem_obj *(*obj_alloc)(struct tmem_pool *); + void (*obj_free)(struct tmem_obj *, struct tmem_pool *); + struct tmem_objnode *(*objnode_alloc)(struct tmem_pool *); + void (*objnode_free)(struct tmem_objnode *, struct tmem_pool *); + void (*flush_all_obj)(void); + void (*control)(bool); +}; +extern void tmem_register_hostops(struct tmem_hostops *m); + +/* core tmem accessor functions */ +extern int tmem_put(struct tmem_pool *, struct tmem_oid *, uint32_t index, + char *, size_t, bool, bool); +extern int tmem_get(struct tmem_pool *, struct tmem_oid *, uint32_t index, + char *, size_t *, bool, int); +extern int tmem_replace(struct tmem_pool *, struct tmem_oid *, uint32_t index, + void *); +extern int tmem_flush_page(struct tmem_pool *, struct tmem_oid *, + uint32_t index); +extern int tmem_flush_object(struct tmem_pool *, struct tmem_oid *); +extern int tmem_destroy_pool(struct tmem_pool *); +extern int tmem_flush_pool(struct tmem_pool *); +extern void tmem_new_pool(struct tmem_pool *, uint32_t); + +extern void tmem_enable(void); +extern void tmem_disable(void); +#endif /* _TMEM_H */ diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 514a691abea0e2bf7ed517c7651f61474e82b293..fbe0dd701942d4161c7e86e5d0e0c4366b5058cd 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -19,6 +19,60 @@ config THERMAL_HWMON depends on HWMON=y || HWMON=THERMAL default y +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_TSENS8960 + tristate "Qualcomm 8960 Tsens Temperature Alarm" + depends on THERMAL + 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. + +config THERMAL_MONITOR + bool "Monitor thermal state and limit CPU Frequency" + depends on THERMAL_TSENS8960 + default n + help + This enables thermal monitoring capability in the kernel in the + absence of a system wide thermal monitoring entity or until such an + entity starts running in the userspace. Monitors TSENS temperature + and limits the max frequency of the cores. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index a9fff0bf4b1486f8754d3168f8b6724752f0553f..275a692f493c0242d162006c9ec33dae17b564a0 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,4 +3,9 @@ # obj-$(CONFIG_THERMAL) += thermal_sys.o +obj-$(CONFIG_THERMAL_MSM_POPMEM) += msm_popmem-tm.o +obj-$(CONFIG_THERMAL_TSENS) += msm_tsens.o +obj-$(CONFIG_THERMAL_TSENS8960) += msm8960_tsens.o +obj-$(CONFIG_THERMAL_PM8XXX) += pm8xxx-tm.o +obj-$(CONFIG_THERMAL_MONITOR) += msm_thermal.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o \ No newline at end of file diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c new file mode 100644 index 0000000000000000000000000000000000000000..78a1292f8be3c72a375dfa18f3c82e6a7046785d --- /dev/null +++ b/drivers/thermal/msm8960_tsens.c @@ -0,0 +1,1082 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 MSM8960 TSENS driver + * + */ + +#include +#include +#include +#include +#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, +}; + +/* MSM8960 TSENS register info */ +#define TSENS_CAL_DEGC 30 +#define TSENS_MAIN_SENSOR 0 + +#define TSENS_8960_QFPROM_ADDR0 (MSM_QFPROM_BASE + 0x00000404) +#define TSENS_8960_QFPROM_SPARE_ADDR0 (MSM_QFPROM_BASE + 0x00000414) +#define TSENS_8960_CONFIG 0x9b +#define TSENS_8960_CONFIG_SHIFT 0 +#define TSENS_8960_CONFIG_MASK (0xf << TSENS_8960_CONFIG_SHIFT) +#define TSENS_CNTL_ADDR (MSM_CLK_CTL_BASE + 0x00003620) +#define TSENS_EN BIT(0) +#define TSENS_SW_RST BIT(1) +#define TSENS_ADC_CLK_SEL BIT(2) +#define SENSOR0_EN BIT(3) +#define SENSOR1_EN BIT(4) +#define SENSOR2_EN BIT(5) +#define SENSOR3_EN BIT(6) +#define SENSOR4_EN BIT(7) +#define SENSORS_EN (SENSOR0_EN | SENSOR1_EN | \ + SENSOR2_EN | SENSOR3_EN | SENSOR4_EN) +#define TSENS_STATUS_CNTL_OFFSET 8 +#define TSENS_MIN_STATUS_MASK BIT((tsens_status_cntl_start)) +#define TSENS_LOWER_STATUS_CLR BIT((tsens_status_cntl_start + 1)) +#define TSENS_UPPER_STATUS_CLR BIT((tsens_status_cntl_start + 2)) +#define TSENS_MAX_STATUS_MASK BIT((tsens_status_cntl_start + 3)) + +#define TSENS_MEASURE_PERIOD 4 /* 1 sec. default */ +#define TSENS_8960_SLP_CLK_ENA BIT(26) + +#define TSENS_THRESHOLD_ADDR (MSM_CLK_CTL_BASE + 0x00003624) +#define TSENS_THRESHOLD_MAX_CODE 0xff +#define TSENS_THRESHOLD_MIN_CODE 0 +#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT 24 +#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT 16 +#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT 8 +#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT 0 +#define TSENS_THRESHOLD_MAX_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << \ + TSENS_THRESHOLD_MAX_LIMIT_SHIFT) +#define TSENS_THRESHOLD_MIN_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << \ + TSENS_THRESHOLD_MIN_LIMIT_SHIFT) +#define TSENS_THRESHOLD_UPPER_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << \ + TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) +#define TSENS_THRESHOLD_LOWER_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << \ + TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) +/* Initial temperature threshold values */ +#define TSENS_LOWER_LIMIT_TH 0x50 +#define TSENS_UPPER_LIMIT_TH 0xdf +#define TSENS_MIN_LIMIT_TH 0x0 +#define TSENS_MAX_LIMIT_TH 0xff + +#define TSENS_S0_STATUS_ADDR (MSM_CLK_CTL_BASE + 0x00003628) +#define TSENS_STATUS_ADDR_OFFSET 2 +#define TSENS_SENSOR_STATUS_SIZE 4 +#define TSENS_INT_STATUS_ADDR (MSM_CLK_CTL_BASE + 0x0000363c) + +#define TSENS_LOWER_INT_MASK BIT(1) +#define TSENS_UPPER_INT_MASK BIT(2) +#define TSENS_MAX_INT_MASK BIT(3) +#define TSENS_TRDY_MASK BIT(7) + +#define TSENS_8960_CONFIG_ADDR (MSM_CLK_CTL_BASE + 0x00003640) +#define TSENS_TRDY_RDY_MIN_TIME 1000 +#define TSENS_TRDY_RDY_MAX_TIME 1100 +#define TSENS_SENSOR_SHIFT 16 +#define TSENS_RED_SHIFT 8 +#define TSENS_8960_QFPROM_SHIFT 4 +#define TSENS_SENSOR_QFPROM_SHIFT 2 +#define TSENS_SENSOR0_SHIFT 3 +#define TSENS_MASK1 1 + +#define TSENS_8660_QFPROM_ADDR (MSM_QFPROM_BASE + 0x000000bc) +#define TSENS_8660_QFPROM_RED_TEMP_SENSOR0_SHIFT 24 +#define TSENS_8660_QFPROM_TEMP_SENSOR0_SHIFT 16 +#define TSENS_8660_QFPROM_TEMP_SENSOR0_MASK (255 \ + << TSENS_8660_QFPROM_TEMP_SENSOR0_SHIFT) +#define TSENS_8660_CONFIG 01 +#define TSENS_8660_CONFIG_SHIFT 28 +#define TSENS_8660_CONFIG_MASK (3 << TSENS_8660_CONFIG_SHIFT) +#define TSENS_8660_SLP_CLK_ENA BIT(24) + +#define TSENS_8064_SENSOR5_EN BIT(8) +#define TSENS_8064_SENSOR6_EN BIT(9) +#define TSENS_8064_SENSOR7_EN BIT(10) +#define TSENS_8064_SENSOR8_EN BIT(11) +#define TSENS_8064_SENSOR9_EN BIT(12) +#define TSENS_8064_SENSOR10_EN BIT(13) +#define TSENS_8064_SENSORS_EN (SENSORS_EN | \ + TSENS_8064_SENSOR5_EN | \ + TSENS_8064_SENSOR6_EN | \ + TSENS_8064_SENSOR7_EN | \ + TSENS_8064_SENSOR8_EN | \ + TSENS_8064_SENSOR9_EN | \ + TSENS_8064_SENSOR10_EN) +#define TSENS_8064_STATUS_CNTL (MSM_CLK_CTL_BASE + 0x00003660) +#define TSENS_8064_S5_STATUS_ADDR (MSM_CLK_CTL_BASE + 0x00003664) +#define TSENS_8064_SEQ_SENSORS 5 +#define TSENS_8064_S4_S5_OFFSET 40 +#define TSENS_CNTL_RESUME_MASK 0xfffffff9 +#define TSENS_8960_SENSOR_MASK 0xf8 +#define TSENS_8064_SENSOR_MASK 0x3ff8 + +static int tsens_status_cntl_start; + +struct tsens_tm_device_sensor { + struct thermal_zone_device *tz_dev; + enum thermal_device_mode mode; + unsigned int sensor_num; + struct work_struct work; + int offset; + int calib_data; + int calib_data_backup; + uint32_t slope_mul_tsens_factor; +}; + +struct tsens_tm_device { + bool prev_reading_avail; + int tsens_factor; + uint32_t tsens_num_sensor; + enum platform_type hw_type; + int pm_tsens_thr_data; + int pm_tsens_cntl; + struct tsens_tm_device_sensor sensor[0]; +}; + +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 sensor_num) +{ + int degcbeforefactor, degc; + degcbeforefactor = (adc_code * + tmdev->sensor[sensor_num].slope_mul_tsens_factor + + tmdev->sensor[sensor_num].offset); + + if (degcbeforefactor == 0) + degc = degcbeforefactor; + else if (degcbeforefactor > 0) + degc = (degcbeforefactor + tmdev->tsens_factor/2) + / tmdev->tsens_factor; + else + degc = (degcbeforefactor - tmdev->tsens_factor/2) + / tmdev->tsens_factor; + return degc; +} + +static int tsens_tz_degC_to_code(int degC, int sensor_num) +{ + int code = (degC * tmdev->tsens_factor - + tmdev->sensor[sensor_num].offset + + tmdev->sensor[sensor_num].slope_mul_tsens_factor/2) + / tmdev->sensor[sensor_num].slope_mul_tsens_factor; + + if (code > TSENS_THRESHOLD_MAX_CODE) + code = TSENS_THRESHOLD_MAX_CODE; + else if (code < TSENS_THRESHOLD_MIN_CODE) + code = TSENS_THRESHOLD_MIN_CODE; + return code; +} + +static void tsens8960_get_temp(int sensor_num, unsigned long *temp) +{ + unsigned int code, offset = 0, sensor_addr; + + if (!tmdev->prev_reading_avail) { + while (!(readl_relaxed(TSENS_INT_STATUS_ADDR) + & TSENS_TRDY_MASK)) + usleep_range(TSENS_TRDY_RDY_MIN_TIME, + TSENS_TRDY_RDY_MAX_TIME); + tmdev->prev_reading_avail = true; + } + + sensor_addr = (unsigned int)TSENS_S0_STATUS_ADDR; + if (tmdev->hw_type == APQ_8064 && + sensor_num >= TSENS_8064_SEQ_SENSORS) + offset = TSENS_8064_S4_S5_OFFSET; + code = readl_relaxed(sensor_addr + offset + + (sensor_num << TSENS_STATUS_ADDR_OFFSET)); + *temp = tsens_tz_code_to_degC(code, sensor_num); +} + +static int tsens_tz_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + + if (!tm_sensor || tm_sensor->mode != THERMAL_DEVICE_ENABLED || !temp) + return -EINVAL; + + tsens8960_get_temp(tm_sensor->sensor_num, temp); + + return 0; +} + +int tsens_get_temp(struct tsens_device *device, unsigned long *temp) +{ + if (!tmdev) + return -ENODEV; + + tsens8960_get_temp(device->sensor_num, temp); + + return 0; +} +EXPORT_SYMBOL(tsens_get_temp); + +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; +} + +/* Function to enable the mode. + * If the main sensor is disabled all the sensors are disable and + * the clock is disabled. + * If the main sensor is not enabled and sub sensor is enabled + * returns with an error stating the main sensor is not enabled. + */ +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, i; + + if (!tm_sensor) + return -EINVAL; + + if (mode != tm_sensor->mode) { + reg = readl_relaxed(TSENS_CNTL_ADDR); + + mask = 1 << (tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT); + if (mode == THERMAL_DEVICE_ENABLED) { + if ((mask != SENSOR0_EN) && !(reg & SENSOR0_EN)) { + pr_info("Main sensor not enabled\n"); + return -EINVAL; + } + writel_relaxed(reg | TSENS_SW_RST, TSENS_CNTL_ADDR); + if (tmdev->hw_type == MSM_8960 || + tmdev->hw_type == MDM_9615 || + tmdev->hw_type == APQ_8064) + reg |= mask | TSENS_8960_SLP_CLK_ENA + | TSENS_EN; + else + reg |= mask | TSENS_8660_SLP_CLK_ENA + | TSENS_EN; + tmdev->prev_reading_avail = false; + } else { + reg &= ~mask; + if (!(reg & SENSOR0_EN)) { + if (tmdev->hw_type == APQ_8064) + reg &= ~(TSENS_8064_SENSORS_EN | + TSENS_8960_SLP_CLK_ENA | + TSENS_EN); + else if (tmdev->hw_type == MSM_8960 || + tmdev->hw_type == MDM_9615) + reg &= ~(SENSORS_EN | + TSENS_8960_SLP_CLK_ENA | + TSENS_EN); + else + reg &= ~(SENSORS_EN | + TSENS_8660_SLP_CLK_ENA | + TSENS_EN); + + for (i = 1; i < tmdev->tsens_num_sensor; i++) + tmdev->sensor[i].mode = mode; + + } + } + writel_relaxed(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 = TSENS_THRESHOLD_MIN_CODE; + hi_code = TSENS_THRESHOLD_MAX_CODE; + + if (tmdev->hw_type == APQ_8064) + reg_cntl = readl_relaxed(TSENS_8064_STATUS_CNTL); + else + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + + reg_th = readl_relaxed(TSENS_THRESHOLD_ADDR); + switch (trip) { + case TSENS_TRIP_STAGE3: + code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + mask = TSENS_MAX_STATUS_MASK; + + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE2: + code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + mask = TSENS_UPPER_STATUS_CLR; + + if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE1: + code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + mask = TSENS_LOWER_STATUS_CLR; + + if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE0: + code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + mask = TSENS_MIN_STATUS_MASK; + + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + break; + default: + return -EINVAL; + } + + if (mode == THERMAL_TRIP_ACTIVATION_DISABLED) { + if (tmdev->hw_type == APQ_8064) + writel_relaxed(reg_cntl | mask, TSENS_8064_STATUS_CNTL); + else + writel_relaxed(reg_cntl | mask, TSENS_CNTL_ADDR); + } else { + if (code < lo_code || code > hi_code) { + pr_info("%s with invalid code %x\n", __func__, code); + return -EINVAL; + } + if (tmdev->hw_type == APQ_8064) + writel_relaxed(reg_cntl & ~mask, + TSENS_8064_STATUS_CNTL); + else + writel_relaxed(reg_cntl & ~mask, TSENS_CNTL_ADDR); + } + mb(); + 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_relaxed(TSENS_THRESHOLD_ADDR); + switch (trip) { + case TSENS_TRIP_STAGE3: + reg = (reg & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE2: + reg = (reg & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE1: + reg = (reg & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE0: + reg = (reg & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + break; + default: + return -EINVAL; + } + + *temp = tsens_tz_code_to_degC(reg, tm_sensor->sensor_num); + + 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_notify(struct thermal_zone_device *thermal, + int count, enum thermal_trip_type type) +{ + /* TSENS driver does not shutdown the device. + All Thermal notification are sent to the + thermal daemon to take appropriate action */ + return 1; +} + +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, + tm_sensor->sensor_num); + if (!tm_sensor || trip < 0) + return -EINVAL; + + lo_code = TSENS_THRESHOLD_MIN_CODE; + hi_code = TSENS_THRESHOLD_MAX_CODE; + + if (tmdev->hw_type == APQ_8064) + reg_cntl = readl_relaxed(TSENS_8064_STATUS_CNTL); + else + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + reg_th = readl_relaxed(TSENS_THRESHOLD_ADDR); + switch (trip) { + case TSENS_TRIP_STAGE3: + code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + reg_th &= ~TSENS_THRESHOLD_MAX_LIMIT_MASK; + + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE2: + code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + reg_th &= ~TSENS_THRESHOLD_UPPER_LIMIT_MASK; + + if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE1: + code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + reg_th &= ~TSENS_THRESHOLD_LOWER_LIMIT_MASK; + + if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE0: + code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT; + reg_th &= ~TSENS_THRESHOLD_MIN_LIMIT_MASK; + + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT; + break; + default: + return -EINVAL; + } + + if (code_err_chk < lo_code || code_err_chk > hi_code) + return -EINVAL; + + writel_relaxed(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, + .notify = tsens_tz_notify, +}; + +static void notify_uspace_tsens_fn(struct work_struct *work) +{ + struct tsens_tm_device_sensor *tm = container_of(work, + struct tsens_tm_device_sensor, work); + + sysfs_notify(&tm->tz_dev->device.kobj, + NULL, "type"); +} + +static irqreturn_t tsens_isr(int irq, void *data) +{ + struct tsens_tm_device *tm = data; + unsigned int threshold, threshold_low, i, code, reg, sensor, mask; + unsigned int sensor_addr; + bool upper_th_x, lower_th_x; + int adc_code; + + if (tmdev->hw_type == APQ_8064) { + reg = readl_relaxed(TSENS_8064_STATUS_CNTL); + writel_relaxed(reg | TSENS_LOWER_STATUS_CLR | + TSENS_UPPER_STATUS_CLR, TSENS_8064_STATUS_CNTL); + } else { + reg = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(reg | TSENS_LOWER_STATUS_CLR | + TSENS_UPPER_STATUS_CLR, TSENS_CNTL_ADDR); + } + mask = ~(TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR); + threshold = readl_relaxed(TSENS_THRESHOLD_ADDR); + threshold_low = (threshold & TSENS_THRESHOLD_LOWER_LIMIT_MASK) + >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT; + threshold = (threshold & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT; + sensor = readl_relaxed(TSENS_CNTL_ADDR); + if (tmdev->hw_type == APQ_8064) { + reg = readl_relaxed(TSENS_8064_STATUS_CNTL); + sensor &= (uint32_t) TSENS_8064_SENSORS_EN; + } else { + reg = sensor; + sensor &= (uint32_t) SENSORS_EN; + } + sensor >>= TSENS_SENSOR0_SHIFT; + sensor_addr = (unsigned int)TSENS_S0_STATUS_ADDR; + for (i = 0; i < tmdev->tsens_num_sensor; i++) { + if (i == TSENS_8064_SEQ_SENSORS) + sensor_addr += TSENS_8064_S4_S5_OFFSET; + if (sensor & TSENS_MASK1) { + code = readl_relaxed(sensor_addr); + 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) { + /* Notify user space */ + schedule_work(&tm->sensor[i].work); + adc_code = readl_relaxed(sensor_addr); + pr_info("\nTrip point triggered by " + "current temperature (%d degrees) " + "measured by Temperature-Sensor %d\n", + tsens_tz_code_to_degC(adc_code, i), i); + } + } + sensor >>= 1; + sensor_addr += TSENS_SENSOR_STATUS_SIZE; + } + if (tmdev->hw_type == APQ_8064) + writel_relaxed(reg & mask, TSENS_8064_STATUS_CNTL); + else + writel_relaxed(reg & mask, TSENS_CNTL_ADDR); + mb(); + return IRQ_HANDLED; +} + +static void tsens8960_sensor_mode_init(void) +{ + unsigned int reg_cntl = 0; + + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + if (tmdev->hw_type == MSM_8960 || tmdev->hw_type == MDM_9615 || + tmdev->hw_type == APQ_8064) { + writel_relaxed(reg_cntl & + ~((((1 << tmdev->tsens_num_sensor) - 1) >> 1) + << (TSENS_SENSOR0_SHIFT + 1)), TSENS_CNTL_ADDR); + tmdev->sensor[TSENS_MAIN_SENSOR].mode = THERMAL_DEVICE_ENABLED; + } +} + +#ifdef CONFIG_PM +static int tsens_suspend(struct device *dev) +{ + int i = 0; + + tmdev->pm_tsens_thr_data = readl_relaxed(TSENS_THRESHOLD_ADDR); + tmdev->pm_tsens_cntl = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(tmdev->pm_tsens_cntl & + ~(TSENS_8960_SLP_CLK_ENA | TSENS_EN), TSENS_CNTL_ADDR); + tmdev->prev_reading_avail = 0; + for (i = 0; i < tmdev->tsens_num_sensor; i++) + tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED; + disable_irq_nosync(TSENS_UPPER_LOWER_INT); + mb(); + return 0; +} + +static int tsens_resume(struct device *dev) +{ + unsigned int reg_cntl = 0, reg_cfg = 0, reg_sensor_mask = 0; + unsigned int reg_status_cntl = 0, reg_thr_data = 0, i = 0; + + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(reg_cntl | TSENS_SW_RST, TSENS_CNTL_ADDR); + + if (tmdev->hw_type == MSM_8960 || tmdev->hw_type == MDM_9615) { + reg_cntl |= TSENS_8960_SLP_CLK_ENA | + (TSENS_MEASURE_PERIOD << 18) | + TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK | + SENSORS_EN; + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + } else if (tmdev->hw_type == APQ_8064) { + reg_cntl |= TSENS_8960_SLP_CLK_ENA | + (TSENS_MEASURE_PERIOD << 18) | + (((1 << tmdev->tsens_num_sensor) - 1) + << TSENS_SENSOR0_SHIFT); + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + reg_status_cntl = readl_relaxed(TSENS_8064_STATUS_CNTL); + reg_status_cntl |= TSENS_MIN_STATUS_MASK | + TSENS_MAX_STATUS_MASK; + writel_relaxed(reg_status_cntl, TSENS_8064_STATUS_CNTL); + } + + reg_cfg = readl_relaxed(TSENS_8960_CONFIG_ADDR); + reg_cfg = (reg_cfg & ~TSENS_8960_CONFIG_MASK) | + (TSENS_8960_CONFIG << TSENS_8960_CONFIG_SHIFT); + writel_relaxed(reg_cfg, TSENS_8960_CONFIG_ADDR); + + writel_relaxed((tmdev->pm_tsens_cntl & TSENS_CNTL_RESUME_MASK), + TSENS_CNTL_ADDR); + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(tmdev->pm_tsens_thr_data, TSENS_THRESHOLD_ADDR); + reg_thr_data = readl_relaxed(TSENS_THRESHOLD_ADDR); + if (tmdev->hw_type == MSM_8960 || tmdev->hw_type == MDM_9615) + reg_sensor_mask = ((reg_cntl & TSENS_8960_SENSOR_MASK) + >> TSENS_SENSOR0_SHIFT); + else { + reg_sensor_mask = ((reg_cntl & TSENS_8064_SENSOR_MASK) + >> TSENS_SENSOR0_SHIFT); + } + + for (i = 0; i < tmdev->tsens_num_sensor; i++) { + if (reg_sensor_mask & TSENS_MASK1) + tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED; + reg_sensor_mask >>= 1; + } + + enable_irq(TSENS_UPPER_LOWER_INT); + mb(); + return 0; +} + +static const struct dev_pm_ops tsens_pm_ops = { + .suspend = tsens_suspend, + .resume = tsens_resume, +}; +#endif + +static void tsens_disable_mode(void) +{ + unsigned int reg_cntl = 0; + + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + if (tmdev->hw_type == MSM_8960 || tmdev->hw_type == MDM_9615 || + tmdev->hw_type == APQ_8064) + writel_relaxed(reg_cntl & + ~((((1 << tmdev->tsens_num_sensor) - 1) << + TSENS_SENSOR0_SHIFT) | TSENS_8960_SLP_CLK_ENA + | TSENS_EN), TSENS_CNTL_ADDR); + else if (tmdev->hw_type == MSM_8660) + writel_relaxed(reg_cntl & + ~((((1 << tmdev->tsens_num_sensor) - 1) << + TSENS_SENSOR0_SHIFT) | TSENS_8660_SLP_CLK_ENA + | TSENS_EN), TSENS_CNTL_ADDR); +} + +static void tsens_hw_init(void) +{ + unsigned int reg_cntl = 0, reg_cfg = 0, reg_thr = 0; + unsigned int reg_status_cntl = 0; + + reg_cntl = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(reg_cntl | TSENS_SW_RST, TSENS_CNTL_ADDR); + + if (tmdev->hw_type == MSM_8960 || tmdev->hw_type == MDM_9615) { + reg_cntl |= TSENS_8960_SLP_CLK_ENA | + (TSENS_MEASURE_PERIOD << 18) | + TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR | + TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK | + SENSORS_EN; + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + reg_cntl |= TSENS_EN; + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + + reg_cfg = readl_relaxed(TSENS_8960_CONFIG_ADDR); + reg_cfg = (reg_cfg & ~TSENS_8960_CONFIG_MASK) | + (TSENS_8960_CONFIG << TSENS_8960_CONFIG_SHIFT); + writel_relaxed(reg_cfg, TSENS_8960_CONFIG_ADDR); + } else if (tmdev->hw_type == MSM_8660) { + reg_cntl |= TSENS_8660_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 << tmdev->tsens_num_sensor) - 1) << + TSENS_SENSOR0_SHIFT); + + /* set TSENS_CONFIG bits (bits 29:28 of TSENS_CNTL) to '01'; + this setting found to be optimal. */ + reg_cntl = (reg_cntl & ~TSENS_8660_CONFIG_MASK) | + (TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT); + + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + } else if (tmdev->hw_type == APQ_8064) { + reg_cntl |= TSENS_8960_SLP_CLK_ENA | + (TSENS_MEASURE_PERIOD << 18) | + (((1 << tmdev->tsens_num_sensor) - 1) + << TSENS_SENSOR0_SHIFT); + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + reg_status_cntl = readl_relaxed(TSENS_8064_STATUS_CNTL); + reg_status_cntl |= TSENS_LOWER_STATUS_CLR | + TSENS_UPPER_STATUS_CLR | + TSENS_MIN_STATUS_MASK | + TSENS_MAX_STATUS_MASK; + writel_relaxed(reg_status_cntl, TSENS_8064_STATUS_CNTL); + reg_cntl |= TSENS_EN; + writel_relaxed(reg_cntl, TSENS_CNTL_ADDR); + + reg_cfg = readl_relaxed(TSENS_8960_CONFIG_ADDR); + reg_cfg = (reg_cfg & ~TSENS_8960_CONFIG_MASK) | + (TSENS_8960_CONFIG << TSENS_8960_CONFIG_SHIFT); + writel_relaxed(reg_cfg, TSENS_8960_CONFIG_ADDR); + } + + reg_thr |= (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) | + (TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) | + (TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) | + (TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT); + writel_relaxed(reg_thr, TSENS_THRESHOLD_ADDR); +} + +static int tsens_calib_sensors8660(void) +{ + uint32_t *main_sensor_addr, sensor_shift, red_sensor_shift; + uint32_t sensor_mask, red_sensor_mask; + + main_sensor_addr = TSENS_8660_QFPROM_ADDR; + sensor_shift = TSENS_SENSOR_SHIFT; + red_sensor_shift = sensor_shift + TSENS_RED_SHIFT; + sensor_mask = TSENS_THRESHOLD_MAX_CODE << sensor_shift; + red_sensor_mask = TSENS_THRESHOLD_MAX_CODE << red_sensor_shift; + tmdev->sensor[TSENS_MAIN_SENSOR].calib_data = + (readl_relaxed(main_sensor_addr) & sensor_mask) + >> sensor_shift; + tmdev->sensor[TSENS_MAIN_SENSOR].calib_data_backup = + (readl_relaxed(main_sensor_addr) + & red_sensor_mask) >> red_sensor_shift; + if (tmdev->sensor[TSENS_MAIN_SENSOR].calib_data_backup) + tmdev->sensor[TSENS_MAIN_SENSOR].calib_data = + tmdev->sensor[TSENS_MAIN_SENSOR].calib_data_backup; + if (!tmdev->sensor[TSENS_MAIN_SENSOR].calib_data) { + pr_err("%s: No temperature sensor data for calibration" + " in QFPROM!\n", __func__); + return -ENODEV; + } + + tmdev->sensor[TSENS_MAIN_SENSOR].offset = tmdev->tsens_factor * + TSENS_CAL_DEGC - + tmdev->sensor[TSENS_MAIN_SENSOR].slope_mul_tsens_factor * + tmdev->sensor[TSENS_MAIN_SENSOR].calib_data; + + tmdev->prev_reading_avail = false; + INIT_WORK(&tmdev->sensor[TSENS_MAIN_SENSOR].work, + notify_uspace_tsens_fn); + + return 0; +} + +static int tsens_calib_sensors8960(void) +{ + uint32_t i; + uint8_t *main_sensor_addr, *backup_sensor_addr; + for (i = 0; i < tmdev->tsens_num_sensor; i++) { + main_sensor_addr = TSENS_8960_QFPROM_ADDR0 + i; + backup_sensor_addr = TSENS_8960_QFPROM_SPARE_ADDR0 + i; + + tmdev->sensor[i].calib_data = readb_relaxed(main_sensor_addr); + tmdev->sensor[i].calib_data_backup = + readb_relaxed(backup_sensor_addr); + if (tmdev->sensor[i].calib_data_backup) + tmdev->sensor[i].calib_data = + tmdev->sensor[i].calib_data_backup; + if (!tmdev->sensor[i].calib_data) { + WARN(1, "%s: No temperature sensor:%d data for" + " calibration in QFPROM!\n", __func__, i); + return -ENODEV; + } + tmdev->sensor[i].offset = (TSENS_CAL_DEGC * + tmdev->tsens_factor) + - (tmdev->sensor[i].calib_data * + tmdev->sensor[i].slope_mul_tsens_factor); + tmdev->prev_reading_avail = false; + INIT_WORK(&tmdev->sensor[i].work, notify_uspace_tsens_fn); + } + + return 0; +} + +static int tsens_check_version_support(void) +{ + int rc = 0; + + if (tmdev->hw_type == MSM_8960) + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 1) + rc = -ENODEV; + + return rc; +} + +static int tsens_calib_sensors(void) +{ + int rc = -ENODEV; + + if (tmdev->hw_type == MSM_8660) + rc = tsens_calib_sensors8660(); + else if (tmdev->hw_type == MSM_8960 || tmdev->hw_type == MDM_9615 || + tmdev->hw_type == APQ_8064) + rc = tsens_calib_sensors8960(); + + return rc; +} + +int msm_tsens_early_init(struct tsens_platform_data *pdata) +{ + int rc = 0, i; + + if (!pdata) { + pr_err("No TSENS Platform data\n"); + return -EINVAL; + } + + tmdev = kzalloc(sizeof(struct tsens_tm_device) + + pdata->tsens_num_sensor * + sizeof(struct tsens_tm_device_sensor), + GFP_ATOMIC); + if (tmdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < pdata->tsens_num_sensor; i++) + tmdev->sensor[i].slope_mul_tsens_factor = pdata->slope[i]; + tmdev->tsens_factor = pdata->tsens_factor; + tmdev->tsens_num_sensor = pdata->tsens_num_sensor; + tmdev->hw_type = pdata->hw_type; + + rc = tsens_check_version_support(); + if (rc < 0) { + kfree(tmdev); + tmdev = NULL; + return rc; + } + + rc = tsens_calib_sensors(); + if (rc < 0) { + kfree(tmdev); + tmdev = NULL; + return rc; + } + + if (tmdev->hw_type == APQ_8064) + tsens_status_cntl_start = 0; + else + tsens_status_cntl_start = TSENS_STATUS_CNTL_OFFSET; + + tsens_hw_init(); + + pr_debug("msm_tsens_early_init: done\n"); + + return rc; +} + +static int __devinit tsens_tm_probe(struct platform_device *pdev) +{ + int rc, i; + + if (!tmdev) { + pr_info("%s : TSENS early init not done.\n", __func__); + return -EFAULT; + } + + for (i = 0; i < tmdev->tsens_num_sensor; i++) { + char name[18]; + snprintf(name, sizeof(name), "tsens_tz_sensor%d", i); + tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED; + tmdev->sensor[i].sensor_num = i; + 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 (IS_ERR(tmdev->sensor[i].tz_dev)) { + pr_err("%s: thermal_zone_device_register() failed.\n", + __func__); + rc = -ENODEV; + goto fail; + } + tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED; + } + + tsens8960_sensor_mode_init(); + + rc = request_irq(TSENS_UPPER_LOWER_INT, tsens_isr, + IRQF_TRIGGER_RISING, "tsens_interrupt", tmdev); + if (rc < 0) { + pr_err("%s: request_irq FAIL: %d\n", __func__, rc); + for (i = 0; i < tmdev->tsens_num_sensor; i++) + thermal_zone_device_unregister(tmdev->sensor[i].tz_dev); + goto fail; + } + + pr_debug("%s: OK\n", __func__); + mb(); + return 0; +fail: + tsens_disable_mode(); + kfree(tmdev); + tmdev = NULL; + mb(); + return rc; +} + +static int __devexit tsens_tm_remove(struct platform_device *pdev) +{ + int i; + + tsens_disable_mode(); + mb(); + free_irq(TSENS_UPPER_LOWER_INT, tmdev); + for (i = 0; i < tmdev->tsens_num_sensor; i++) + thermal_zone_device_unregister(tmdev->sensor[i].tz_dev); + kfree(tmdev); + tmdev = NULL; + return 0; +} + +static struct platform_driver tsens_tm_driver = { + .probe = tsens_tm_probe, + .remove = tsens_tm_remove, + .driver = { + .name = "tsens8960-tm", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tsens_pm_ops, +#endif + }, +}; + +static int __init _tsens_tm_init(void) +{ + return platform_driver_register(&tsens_tm_driver); +} +module_init(_tsens_tm_init); + +static void __exit _tsens_tm_remove(void) +{ + platform_driver_unregister(&tsens_tm_driver); +} +module_exit(_tsens_tm_remove); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM8960 Temperature Sensor driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:tsens8960-tm"); diff --git a/drivers/thermal/msm_popmem-tm.c b/drivers/thermal/msm_popmem-tm.c new file mode 100644 index 0000000000000000000000000000000000000000..583b2db07dbcc29d976a25209b9d491ab8933e7a --- /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_thermal.c b/drivers/thermal/msm_thermal.c new file mode 100644 index 0000000000000000000000000000000000000000..e0d8d145ec6b5be6a968c4fd918f4b52674ba6e8 --- /dev/null +++ b/drivers/thermal/msm_thermal.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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 DEF_TEMP_SENSOR 0 +#define DEF_THERMAL_CHECK_MS 1000 +#define DEF_ALLOWED_MAX_HIGH 60 +#define DEF_ALLOWED_MAX_FREQ 918000 + +static int enabled; +static int allowed_max_high = DEF_ALLOWED_MAX_HIGH; +static int allowed_max_low = (DEF_ALLOWED_MAX_HIGH - 10); +static int allowed_max_freq = DEF_ALLOWED_MAX_FREQ; +static int check_interval_ms = DEF_THERMAL_CHECK_MS; + +module_param(allowed_max_high, int, 0); +module_param(allowed_max_freq, int, 0); +module_param(check_interval_ms, int, 0); + +static struct delayed_work check_temp_work; + +static int update_cpu_max_freq(struct cpufreq_policy *cpu_policy, + int cpu, int max_freq) +{ + int ret = 0; + + if (!cpu_policy) + return -EINVAL; + + cpufreq_verify_within_limits(cpu_policy, + cpu_policy->min, max_freq); + cpu_policy->user_policy.max = max_freq; + + ret = cpufreq_update_policy(cpu); + if (!ret) + pr_info("msm_thermal: Limiting core%d max frequency to %d\n", + cpu, max_freq); + + return ret; +} + +static void check_temp(struct work_struct *work) +{ + struct cpufreq_policy *cpu_policy = NULL; + struct tsens_device tsens_dev; + unsigned long temp = 0; + unsigned int max_freq = 0; + int update_policy = 0; + int cpu = 0; + int ret = 0; + + tsens_dev.sensor_num = DEF_TEMP_SENSOR; + ret = tsens_get_temp(&tsens_dev, &temp); + if (ret) { + pr_debug("msm_thermal: Unable to read TSENS sensor %d\n", + tsens_dev.sensor_num); + goto reschedule; + } + + for_each_possible_cpu(cpu) { + update_policy = 0; + cpu_policy = cpufreq_cpu_get(cpu); + if (!cpu_policy) { + pr_debug("msm_thermal: NULL policy on cpu %d\n", cpu); + continue; + } + if (temp >= allowed_max_high) { + if (cpu_policy->max > allowed_max_freq) { + update_policy = 1; + max_freq = allowed_max_freq; + } else { + pr_debug("msm_thermal: policy max for cpu %d " + "already < allowed_max_freq\n", cpu); + } + } else if (temp < allowed_max_low) { + if (cpu_policy->max < cpu_policy->cpuinfo.max_freq) { + max_freq = cpu_policy->cpuinfo.max_freq; + update_policy = 1; + } else { + pr_debug("msm_thermal: policy max for cpu %d " + "already at max allowed\n", cpu); + } + } + + if (update_policy) + update_cpu_max_freq(cpu_policy, cpu, max_freq); + + cpufreq_cpu_put(cpu_policy); + } + +reschedule: + if (enabled) + schedule_delayed_work(&check_temp_work, + msecs_to_jiffies(check_interval_ms)); +} + +static void disable_msm_thermal(void) +{ + int cpu = 0; + struct cpufreq_policy *cpu_policy = NULL; + + /* make sure check_temp is no longer running */ + cancel_delayed_work(&check_temp_work); + flush_scheduled_work(); + + for_each_possible_cpu(cpu) { + cpu_policy = cpufreq_cpu_get(cpu); + if (cpu_policy) { + if (cpu_policy->max < cpu_policy->cpuinfo.max_freq) + update_cpu_max_freq(cpu_policy, cpu, + cpu_policy-> + cpuinfo.max_freq); + cpufreq_cpu_put(cpu_policy); + } + } +} + +static int set_enabled(const char *val, const struct kernel_param *kp) +{ + int ret = 0; + + ret = param_set_bool(val, kp); + if (!enabled) + disable_msm_thermal(); + else + pr_info("msm_thermal: no action for enabled = %d\n", enabled); + + pr_info("msm_thermal: enabled = %d\n", enabled); + + return ret; +} + +static struct kernel_param_ops module_ops = { + .set = set_enabled, + .get = param_get_bool, +}; + +module_param_cb(enabled, &module_ops, &enabled, 0644); +MODULE_PARM_DESC(enabled, "enforce thermal limit on cpu"); + +static int __init msm_thermal_init(void) +{ + int ret = 0; + + enabled = 1; + INIT_DELAYED_WORK(&check_temp_work, check_temp); + + schedule_delayed_work(&check_temp_work, 0); + + return ret; +} +fs_initcall(msm_thermal_init); + diff --git a/drivers/thermal/msm_tsens.c b/drivers/thermal/msm_tsens.c new file mode 100644 index 0000000000000000000000000000000000000000..401ad88f6678412298c265a588c46b9d1227f40e --- /dev/null +++ b/drivers/thermal/msm_tsens.c @@ -0,0 +1,665 @@ +/* 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 +#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; + uint32_t pm_tsens_thr_data; +}; + +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) { + /* 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; +} + +#ifdef CONFIG_PM +static int tsens_suspend(struct device *dev) +{ + unsigned int reg; + + tmdev->pm_tsens_thr_data = readl_relaxed(TSENS_THRESHOLD_ADDR); + reg = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(reg & ~(TSENS_SLP_CLK_ENA | TSENS_EN), TSENS_CNTL_ADDR); + tmdev->prev_reading_avail = 0; + + disable_irq_nosync(TSENS_UPPER_LOWER_INT); + mb(); + return 0; +} + +static int tsens_resume(struct device *dev) +{ + unsigned int reg; + + reg = readl_relaxed(TSENS_CNTL_ADDR); + writel_relaxed(reg | TSENS_SW_RST, TSENS_CNTL_ADDR); + reg |= TSENS_SLP_CLK_ENA | TSENS_EN | (TSENS_MEASURE_PERIOD << 16) | + TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK | + (((1 << TSENS_NUM_SENSORS) - 1) << 3); + + reg = (reg & ~TSENS_CONFIG_MASK) | (TSENS_CONFIG << TSENS_CONFIG_SHIFT); + writel_relaxed(reg, TSENS_CNTL_ADDR); + + if (tmdev->sensor->mode == THERMAL_DEVICE_DISABLED) { + writel_relaxed(reg & ~((((1 << TSENS_NUM_SENSORS) - 1) << 3) + | TSENS_SLP_CLK_ENA | TSENS_EN), TSENS_CNTL_ADDR); + } + + writel_relaxed(tmdev->pm_tsens_thr_data, TSENS_THRESHOLD_ADDR); + + enable_irq(TSENS_UPPER_LOWER_INT); + mb(); + return 0; +} + +static const struct dev_pm_ops tsens_pm_ops = { + .suspend = tsens_suspend, + .resume = tsens_resume, +}; +#endif + +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; + 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, +#ifdef CONFIG_PM + .pm = &tsens_pm_ops, +#endif + }, +}; + +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 0000000000000000000000000000000000000000..50238f307c14d5db483132a0bc282bf27f4b84d1 --- /dev/null +++ b/drivers/thermal/pm8xxx-tm.c @@ -0,0 +1,725 @@ +/* + * 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 +#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_pm8058_adc(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + DECLARE_COMPLETION_ONSTACK(wait); + struct adc_chan_result adc_result = { + .physical = 0lu, + }; + int rc; + + if (!chip || !temp) + return -EINVAL; + + *temp = chip->temp; + + rc = adc_channel_request_conv(chip->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(chip->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; + chip->temp = adc_result.physical; + + return 0; +} + +static int pm8xxx_tz_get_temp_pm8xxx_adc(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + struct pm8xxx_adc_chan_result result = { + .physical = 0lu, + }; + int rc; + + if (!chip || !temp) + return -EINVAL; + + *temp = chip->temp; + + rc = pm8xxx_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_pm8xxx_adc = { + .get_temp = pm8xxx_tz_get_temp_pm8xxx_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_pm8058_adc = { + .get_temp = pm8xxx_tz_get_temp_pm8058_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 pm8xxx_init_adc(struct pm8xxx_tm_chip *chip, bool enable) +{ + int rc = 0; + + if (chip->cdata.adc_type == PM8XXX_TM_ADC_PM8058_ADC) { + if (enable) { + rc = adc_channel_open(chip->cdata.adc_channel, + &(chip->adc_handle)); + if (rc < 0) + pr_err("adc_channel_open() failed.\n"); + } else { + adc_channel_close(chip->adc_handle); + } + } + + 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; + } + + rc = pm8xxx_init_adc(chip, true); + if (rc < 0) { + pr_err("Unable to initialize adc\n"); + goto err_free_chip; + } + + /* Select proper thermal zone ops functions based on ADC type. */ + if (chip->cdata.adc_type == PM8XXX_TM_ADC_PM8XXX_ADC) + tz_ops = &pm8xxx_thermal_zone_ops_pm8xxx_adc; + else if (chip->cdata.adc_type == PM8XXX_TM_ADC_PM8058_ADC) + tz_ops = &pm8xxx_thermal_zone_ops_pm8058_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_fail_adc; + } + + 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_fail_adc: + pm8xxx_init_adc(chip, false); +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); + pm8xxx_init_adc(chip, false); + 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/thermal_sys.c b/drivers/thermal/thermal_sys.c index 022bacb71a7ede51571a6976830859d3f674bb71..fd1a2fc664a39445ef9b91ea9de440ce82a10fbe 100644 --- a/drivers/thermal/thermal_sys.c +++ b/drivers/thermal/thermal_sys.c @@ -186,6 +186,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: @@ -195,6 +201,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) @@ -217,6 +251,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) @@ -284,30 +342,54 @@ static DEVICE_ATTR(mode, 0644, mode_show, mode_store); 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), }; /* sys I/F for cooling device */ @@ -1049,6 +1131,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/Kconfig b/drivers/tty/Kconfig index 830cd62d84925e548cd7480b2d6fb3a4e582a08d..27c49a601d9eb4558a0915d379b41bd907a3a6bd 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -323,6 +323,25 @@ config N_GSM This line discipline provides support for the GSM MUX protocol and presents the mux as a set of 61 individual tty devices. +config N_SMUX + tristate "SMUX line discipline support" + depends on NET && SERIAL_MSM_HS + help + This line discipline provides support for the Serial MUX protocol + and provides a TTY and kernel API for multiple logical channels. + +config N_SMUX_LOOPBACK + tristate "SMUX line discipline loopback support" + depends on N_SMUX + help + Provides loopback and unit testing support for the Serial MUX Protocol. + +config SMUX_CTL + tristate "SMUX control driver" + depends on N_SMUX + help + Support for SMUX control driver on top of serial MUX. + config TRACE_ROUTER tristate "Trace data router for MIPI P1149.7 cJTAG standard" depends on TRACE_SINK diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 2953059530e4a18c3fbdc61bf94abf4aec921144..9ebb54a5ac845746d70812e30551fe83028cfc39 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -6,6 +6,9 @@ obj-$(CONFIG_AUDIT) += tty_audit.o obj-$(CONFIG_MAGIC_SYSRQ) += sysrq.o obj-$(CONFIG_N_HDLC) += n_hdlc.o obj-$(CONFIG_N_GSM) += n_gsm.o +obj-$(CONFIG_N_SMUX) += n_smux.o +obj-$(CONFIG_N_SMUX_LOOPBACK) += smux_test.o smux_loopback.o +obj-$(CONFIG_SMUX_CTL) += smux_ctl.o obj-$(CONFIG_TRACE_ROUTER) += n_tracerouter.o obj-$(CONFIG_TRACE_SINK) += n_tracesink.o obj-$(CONFIG_R3964) += n_r3964.o diff --git a/drivers/tty/hvc/hvc_dcc.c b/drivers/tty/hvc/hvc_dcc.c index 44fbebab5075f98f337d880288993b01ad9e7271..81429c203726c98973bc74d714fb8973f4bc21fe 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/n_smux.c b/drivers/tty/n_smux.c new file mode 100644 index 0000000000000000000000000000000000000000..df335d27e6479ebbf65ddede0e41f98ab4a7e049 --- /dev/null +++ b/drivers/tty/n_smux.c @@ -0,0 +1,2934 @@ +/* drivers/tty/n_smux.c + * + * Copyright (c) 2012, 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 "smux_private.h" +#include "smux_loopback.h" + +#define SMUX_NOTIFY_FIFO_SIZE 128 +#define SMUX_TX_QUEUE_SIZE 256 +#define SMUX_GET_RX_BUFF_MAX_RETRY_CNT 2 +#define SMUX_WM_LOW 2 +#define SMUX_WM_HIGH 4 +#define SMUX_PKT_LOG_SIZE 80 + +/* Maximum size we can accept in a single RX buffer */ +#define TTY_RECEIVE_ROOM 65536 +#define TTY_BUFFER_FULL_WAIT_MS 50 + +/* maximum sleep time between wakeup attempts */ +#define SMUX_WAKEUP_DELAY_MAX (1 << 20) + +/* minimum delay for scheduling delayed work */ +#define SMUX_WAKEUP_DELAY_MIN (1 << 15) + +/* inactivity timeout for no rx/tx activity */ +#define SMUX_INACTIVITY_TIMEOUT_MS 1000 + +enum { + MSM_SMUX_DEBUG = 1U << 0, + MSM_SMUX_INFO = 1U << 1, + MSM_SMUX_POWER_INFO = 1U << 2, + MSM_SMUX_PKT = 1U << 3, +}; + +static int smux_debug_mask; +module_param_named(debug_mask, smux_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +/* Simulated wakeup used for testing */ +int smux_byte_loopback; +module_param_named(byte_loopback, smux_byte_loopback, + int, S_IRUGO | S_IWUSR | S_IWGRP); +int smux_simulate_wakeup_delay = 1; +module_param_named(simulate_wakeup_delay, smux_simulate_wakeup_delay, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define SMUX_DBG(x...) do { \ + if (smux_debug_mask & MSM_SMUX_DEBUG) \ + pr_info(x); \ +} while (0) + +#define SMUX_LOG_PKT_RX(pkt) do { \ + if (smux_debug_mask & MSM_SMUX_PKT) \ + smux_log_pkt(pkt, 1); \ +} while (0) + +#define SMUX_LOG_PKT_TX(pkt) do { \ + if (smux_debug_mask & MSM_SMUX_PKT) \ + smux_log_pkt(pkt, 0); \ +} while (0) + +/** + * Return true if channel is fully opened (both + * local and remote sides are in the OPENED state). + */ +#define IS_FULLY_OPENED(ch) \ + (ch && (ch)->local_state == SMUX_LCH_LOCAL_OPENED \ + && (ch)->remote_state == SMUX_LCH_REMOTE_OPENED) + +static struct platform_device smux_devs[] = { + {.name = "SMUX_CTL", .id = -1}, + {.name = "SMUX_RMNET", .id = -1}, + {.name = "SMUX_DUN_DATA_HSUART", .id = 0}, + {.name = "SMUX_RMNET_DATA_HSUART", .id = 1}, + {.name = "SMUX_RMNET_CTL_HSUART", .id = 0}, + {.name = "SMUX_DIAG", .id = -1}, +}; + +enum { + SMUX_CMD_STATUS_RTC = 1 << 0, + SMUX_CMD_STATUS_RTR = 1 << 1, + SMUX_CMD_STATUS_RI = 1 << 2, + SMUX_CMD_STATUS_DCD = 1 << 3, + SMUX_CMD_STATUS_FLOW_CNTL = 1 << 4, +}; + +/* Channel mode */ +enum { + SMUX_LCH_MODE_NORMAL, + SMUX_LCH_MODE_LOCAL_LOOPBACK, + SMUX_LCH_MODE_REMOTE_LOOPBACK, +}; + +enum { + SMUX_RX_IDLE, + SMUX_RX_MAGIC, + SMUX_RX_HDR, + SMUX_RX_PAYLOAD, + SMUX_RX_FAILURE, +}; + +/** + * Power states. + * + * The _FLUSH states are internal transitional states and are not part of the + * official state machine. + */ +enum { + SMUX_PWR_OFF, + SMUX_PWR_TURNING_ON, + SMUX_PWR_ON, + SMUX_PWR_TURNING_OFF_FLUSH, + SMUX_PWR_TURNING_OFF, + SMUX_PWR_OFF_FLUSH, +}; + +/** + * Logical Channel Structure. One instance per channel. + * + * Locking Hierarchy + * Each lock has a postfix that describes the locking level. If multiple locks + * are required, only increasing lock hierarchy numbers may be locked which + * ensures avoiding a deadlock. + * + * Locking Example + * If state_lock_lhb1 is currently held and the TX list needs to be + * manipulated, then tx_lock_lhb2 may be locked since it's locking hierarchy + * is greater. However, if tx_lock_lhb2 is held, then state_lock_lhb1 may + * not be acquired since it would result in a deadlock. + * + * Note that the Line Discipline locks (*_lha) should always be acquired + * before the logical channel locks. + */ +struct smux_lch_t { + /* channel state */ + spinlock_t state_lock_lhb1; + uint8_t lcid; + unsigned local_state; + unsigned local_mode; + uint8_t local_tiocm; + + unsigned remote_state; + unsigned remote_mode; + uint8_t remote_tiocm; + + int tx_flow_control; + + /* client callbacks and private data */ + void *priv; + void (*notify)(void *priv, int event_type, const void *metadata); + int (*get_rx_buffer)(void *priv, void **pkt_priv, void **buffer, + int size); + + /* TX Info */ + spinlock_t tx_lock_lhb2; + struct list_head tx_queue; + struct list_head tx_ready_list; + unsigned tx_pending_data_cnt; + unsigned notify_lwm; +}; + +union notifier_metadata { + struct smux_meta_disconnected disconnected; + struct smux_meta_read read; + struct smux_meta_write write; + struct smux_meta_tiocm tiocm; +}; + +struct smux_notify_handle { + void (*notify)(void *priv, int event_type, const void *metadata); + void *priv; + int event_type; + union notifier_metadata *metadata; +}; + +/** + * Line discipline and module structure. + * + * Only one instance since multiple instances of line discipline are not + * allowed. + */ +struct smux_ldisc_t { + spinlock_t lock_lha0; + + int is_initialized; + int in_reset; + int ld_open_count; + struct tty_struct *tty; + + /* RX State Machine */ + spinlock_t rx_lock_lha1; + unsigned char recv_buf[SMUX_MAX_PKT_SIZE]; + unsigned int recv_len; + unsigned int pkt_remain; + unsigned rx_state; + unsigned rx_activity_flag; + + /* TX / Power */ + spinlock_t tx_lock_lha2; + struct list_head lch_tx_ready_list; + unsigned power_state; + unsigned pwr_wakeup_delay_us; + unsigned tx_activity_flag; + unsigned powerdown_enabled; +}; + + +/* data structures */ +static struct smux_lch_t smux_lch[SMUX_NUM_LOGICAL_CHANNELS]; +static struct smux_ldisc_t smux; +static const char *tty_error_type[] = { + [TTY_NORMAL] = "normal", + [TTY_OVERRUN] = "overrun", + [TTY_BREAK] = "break", + [TTY_PARITY] = "parity", + [TTY_FRAME] = "framing", +}; + +static const char *smux_cmds[] = { + [SMUX_CMD_DATA] = "DATA", + [SMUX_CMD_OPEN_LCH] = "OPEN", + [SMUX_CMD_CLOSE_LCH] = "CLOSE", + [SMUX_CMD_STATUS] = "STATUS", + [SMUX_CMD_PWR_CTL] = "PWR", + [SMUX_CMD_BYTE] = "Raw Byte", +}; + +static void smux_notify_local_fn(struct work_struct *work); +static DECLARE_WORK(smux_notify_local, smux_notify_local_fn); + +static struct workqueue_struct *smux_notify_wq; +static size_t handle_size; +static struct kfifo smux_notify_fifo; +static int queued_fifo_notifications; +static DEFINE_SPINLOCK(notify_lock_lhc1); + +static struct workqueue_struct *smux_tx_wq; +static void smux_tx_worker(struct work_struct *work); +static DECLARE_WORK(smux_tx_work, smux_tx_worker); + +static void smux_wakeup_worker(struct work_struct *work); +static DECLARE_WORK(smux_wakeup_work, smux_wakeup_worker); +static DECLARE_DELAYED_WORK(smux_wakeup_delayed_work, smux_wakeup_worker); + +static void smux_inactivity_worker(struct work_struct *work); +static DECLARE_WORK(smux_inactivity_work, smux_inactivity_worker); +static DECLARE_DELAYED_WORK(smux_delayed_inactivity_work, + smux_inactivity_worker); + +static long msm_smux_tiocm_get_atomic(struct smux_lch_t *ch); +static void list_channel(struct smux_lch_t *ch); +static int smux_send_status_cmd(struct smux_lch_t *ch); +static int smux_dispatch_rx_pkt(struct smux_pkt_t *pkt); + +/** + * Convert TTY Error Flags to string for logging purposes. + * + * @flag TTY_* flag + * @returns String description or NULL if unknown + */ +static const char *tty_flag_to_str(unsigned flag) +{ + if (flag < ARRAY_SIZE(tty_error_type)) + return tty_error_type[flag]; + return NULL; +} + +/** + * Convert SMUX Command to string for logging purposes. + * + * @cmd SMUX command + * @returns String description or NULL if unknown + */ +static const char *cmd_to_str(unsigned cmd) +{ + if (cmd < ARRAY_SIZE(smux_cmds)) + return smux_cmds[cmd]; + return NULL; +} + +/** + * Set the reset state due to an unrecoverable failure. + */ +static void smux_enter_reset(void) +{ + pr_err("%s: unrecoverable failure, waiting for ssr\n", __func__); + smux.in_reset = 1; +} + +static int lch_init(void) +{ + unsigned int id; + struct smux_lch_t *ch; + int i = 0; + + handle_size = sizeof(struct smux_notify_handle *); + + smux_notify_wq = create_singlethread_workqueue("smux_notify_wq"); + smux_tx_wq = create_singlethread_workqueue("smux_tx_wq"); + + if (IS_ERR(smux_notify_wq) || IS_ERR(smux_tx_wq)) { + SMUX_DBG("%s: create_singlethread_workqueue ENOMEM\n", + __func__); + return -ENOMEM; + } + + i |= kfifo_alloc(&smux_notify_fifo, + SMUX_NOTIFY_FIFO_SIZE * handle_size, + GFP_KERNEL); + i |= smux_loopback_init(); + + if (i) { + pr_err("%s: out of memory error\n", __func__); + return -ENOMEM; + } + + for (id = 0 ; id < SMUX_NUM_LOGICAL_CHANNELS; id++) { + ch = &smux_lch[id]; + + spin_lock_init(&ch->state_lock_lhb1); + ch->lcid = id; + ch->local_state = SMUX_LCH_LOCAL_CLOSED; + ch->local_mode = SMUX_LCH_MODE_NORMAL; + ch->local_tiocm = 0x0; + ch->remote_state = SMUX_LCH_REMOTE_CLOSED; + ch->remote_mode = SMUX_LCH_MODE_NORMAL; + ch->remote_tiocm = 0x0; + ch->tx_flow_control = 0; + ch->priv = 0; + ch->notify = 0; + ch->get_rx_buffer = 0; + + spin_lock_init(&ch->tx_lock_lhb2); + INIT_LIST_HEAD(&ch->tx_queue); + INIT_LIST_HEAD(&ch->tx_ready_list); + ch->tx_pending_data_cnt = 0; + ch->notify_lwm = 0; + } + + return 0; +} + +int smux_assert_lch_id(uint32_t lcid) +{ + if (lcid >= SMUX_NUM_LOGICAL_CHANNELS) + return -ENXIO; + else + return 0; +} + +/** + * Log packet information for debug purposes. + * + * @pkt Packet to log + * @is_recv 1 = RX packet; 0 = TX Packet + * + * [DIR][LCID] [LOCAL_STATE][LOCAL_MODE]:[REMOTE_STATE][REMOTE_MODE] PKT Info + * + * PKT Info: + * [CMD] flags [flags] len [PAYLOAD_LEN]:[PAD_LEN] [Payload hex bytes] + * + * Direction: R = Receive, S = Send + * Local State: C = Closed; c = closing; o = opening; O = Opened + * Local Mode: L = Local loopback; R = Remote loopback; N = Normal + * Remote State: C = Closed; O = Opened + * Remote Mode: R = Remote loopback; N = Normal + */ +static void smux_log_pkt(struct smux_pkt_t *pkt, int is_recv) +{ + char logbuf[SMUX_PKT_LOG_SIZE]; + char cmd_extra[16]; + int i = 0; + int count; + int len; + char local_state; + char local_mode; + char remote_state; + char remote_mode; + struct smux_lch_t *ch; + unsigned char *data; + + ch = &smux_lch[pkt->hdr.lcid]; + + switch (ch->local_state) { + case SMUX_LCH_LOCAL_CLOSED: + local_state = 'C'; + break; + case SMUX_LCH_LOCAL_OPENING: + local_state = 'o'; + break; + case SMUX_LCH_LOCAL_OPENED: + local_state = 'O'; + break; + case SMUX_LCH_LOCAL_CLOSING: + local_state = 'c'; + break; + default: + local_state = 'U'; + break; + } + + switch (ch->local_mode) { + case SMUX_LCH_MODE_LOCAL_LOOPBACK: + local_mode = 'L'; + break; + case SMUX_LCH_MODE_REMOTE_LOOPBACK: + local_mode = 'R'; + break; + case SMUX_LCH_MODE_NORMAL: + local_mode = 'N'; + break; + default: + local_mode = 'U'; + break; + } + + switch (ch->remote_state) { + case SMUX_LCH_REMOTE_CLOSED: + remote_state = 'C'; + break; + case SMUX_LCH_REMOTE_OPENED: + remote_state = 'O'; + break; + + default: + remote_state = 'U'; + break; + } + + switch (ch->remote_mode) { + case SMUX_LCH_MODE_REMOTE_LOOPBACK: + remote_mode = 'R'; + break; + case SMUX_LCH_MODE_NORMAL: + remote_mode = 'N'; + break; + default: + remote_mode = 'U'; + break; + } + + /* determine command type (ACK, etc) */ + cmd_extra[0] = '\0'; + switch (pkt->hdr.cmd) { + case SMUX_CMD_OPEN_LCH: + if (pkt->hdr.flags & SMUX_CMD_OPEN_ACK) + snprintf(cmd_extra, sizeof(cmd_extra), " ACK"); + break; + case SMUX_CMD_CLOSE_LCH: + if (pkt->hdr.flags & SMUX_CMD_CLOSE_ACK) + snprintf(cmd_extra, sizeof(cmd_extra), " ACK"); + break; + }; + + i += snprintf(logbuf + i, SMUX_PKT_LOG_SIZE - i, + "smux: %c%d %c%c:%c%c %s%s flags %x len %d:%d ", + is_recv ? 'R' : 'S', pkt->hdr.lcid, + local_state, local_mode, + remote_state, remote_mode, + cmd_to_str(pkt->hdr.cmd), cmd_extra, pkt->hdr.flags, + pkt->hdr.payload_len, pkt->hdr.pad_len); + + len = (pkt->hdr.payload_len > 16) ? 16 : pkt->hdr.payload_len; + data = (unsigned char *)pkt->payload; + for (count = 0; count < len; count++) + i += snprintf(logbuf + i, SMUX_PKT_LOG_SIZE - i, + "%02x ", (unsigned)data[count]); + + pr_info("%s\n", logbuf); +} + +static void smux_notify_local_fn(struct work_struct *work) +{ + struct smux_notify_handle *notify_handle = NULL; + union notifier_metadata *metadata = NULL; + unsigned long flags; + int i; + + for (;;) { + /* retrieve notification */ + spin_lock_irqsave(¬ify_lock_lhc1, flags); + if (kfifo_len(&smux_notify_fifo) >= handle_size) { + i = kfifo_out(&smux_notify_fifo, + ¬ify_handle, + handle_size); + if (i != handle_size) { + pr_err("%s: unable to retrieve handle %d expected %d\n", + __func__, i, handle_size); + spin_unlock_irqrestore(¬ify_lock_lhc1, flags); + break; + } + } else { + spin_unlock_irqrestore(¬ify_lock_lhc1, flags); + break; + } + --queued_fifo_notifications; + spin_unlock_irqrestore(¬ify_lock_lhc1, flags); + + /* notify client */ + metadata = notify_handle->metadata; + notify_handle->notify(notify_handle->priv, + notify_handle->event_type, + metadata); + + kfree(metadata); + kfree(notify_handle); + } +} + +/** + * Initialize existing packet. + */ +void smux_init_pkt(struct smux_pkt_t *pkt) +{ + memset(pkt, 0x0, sizeof(*pkt)); + pkt->hdr.magic = SMUX_MAGIC; + INIT_LIST_HEAD(&pkt->list); +} + +/** + * Allocate and initialize packet. + * + * If a payload is needed, either set it directly and ensure that it's freed or + * use smd_alloc_pkt_payload() to allocate a packet and it will be freed + * automatically when smd_free_pkt() is called. + */ +struct smux_pkt_t *smux_alloc_pkt(void) +{ + struct smux_pkt_t *pkt; + + /* Consider a free list implementation instead of kmalloc */ + pkt = kmalloc(sizeof(struct smux_pkt_t), GFP_ATOMIC); + if (!pkt) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + smux_init_pkt(pkt); + pkt->allocated = 1; + + return pkt; +} + +/** + * Free packet. + * + * @pkt Packet to free (may be NULL) + * + * If payload was allocated using smux_alloc_pkt_payload(), then it is freed as + * well. Otherwise, the caller is responsible for freeing the payload. + */ +void smux_free_pkt(struct smux_pkt_t *pkt) +{ + if (pkt) { + if (pkt->free_payload) + kfree(pkt->payload); + if (pkt->allocated) + kfree(pkt); + } +} + +/** + * Allocate packet payload. + * + * @pkt Packet to add payload to + * + * @returns 0 on success, <0 upon error + * + * A flag is set to signal smux_free_pkt() to free the payload. + */ +int smux_alloc_pkt_payload(struct smux_pkt_t *pkt) +{ + if (!pkt) + return -EINVAL; + + pkt->payload = kmalloc(pkt->hdr.payload_len, GFP_ATOMIC); + pkt->free_payload = 1; + if (!pkt->payload) { + pr_err("%s: unable to malloc %d bytes for payload\n", + __func__, pkt->hdr.payload_len); + return -ENOMEM; + } + + return 0; +} + +static int schedule_notify(uint8_t lcid, int event, + const union notifier_metadata *metadata) +{ + struct smux_notify_handle *notify_handle = 0; + union notifier_metadata *meta_copy = 0; + struct smux_lch_t *ch; + int i; + unsigned long flags; + int ret = 0; + + ch = &smux_lch[lcid]; + notify_handle = kzalloc(sizeof(struct smux_notify_handle), + GFP_ATOMIC); + if (!notify_handle) { + pr_err("%s: out of memory\n", __func__); + ret = -ENOMEM; + goto free_out; + } + + notify_handle->notify = ch->notify; + notify_handle->priv = ch->priv; + notify_handle->event_type = event; + if (metadata) { + meta_copy = kzalloc(sizeof(union notifier_metadata), + GFP_ATOMIC); + if (!meta_copy) { + pr_err("%s: out of memory\n", __func__); + ret = -ENOMEM; + goto free_out; + } + *meta_copy = *metadata; + notify_handle->metadata = meta_copy; + } else { + notify_handle->metadata = NULL; + } + + spin_lock_irqsave(¬ify_lock_lhc1, flags); + i = kfifo_avail(&smux_notify_fifo); + if (i < handle_size) { + pr_err("%s: fifo full error %d expected %d\n", + __func__, i, handle_size); + ret = -ENOMEM; + goto unlock_out; + } + + i = kfifo_in(&smux_notify_fifo, ¬ify_handle, handle_size); + if (i < 0 || i != handle_size) { + pr_err("%s: fifo not available error %d (expected %d)\n", + __func__, i, handle_size); + ret = -ENOSPC; + goto unlock_out; + } + ++queued_fifo_notifications; + +unlock_out: + spin_unlock_irqrestore(¬ify_lock_lhc1, flags); + +free_out: + queue_work(smux_notify_wq, &smux_notify_local); + if (ret < 0 && notify_handle) { + kfree(notify_handle->metadata); + kfree(notify_handle); + } + return ret; +} + +/** + * Returns the serialized size of a packet. + * + * @pkt Packet to serialize + * + * @returns Serialized length of packet + */ +static unsigned int smux_serialize_size(struct smux_pkt_t *pkt) +{ + unsigned int size; + + size = sizeof(struct smux_hdr_t); + size += pkt->hdr.payload_len; + size += pkt->hdr.pad_len; + + return size; +} + +/** + * Serialize packet @pkt into output buffer @data. + * + * @pkt Packet to serialize + * @out Destination buffer pointer + * @out_len Size of serialized packet + * + * @returns 0 for success + */ +int smux_serialize(struct smux_pkt_t *pkt, char *out, + unsigned int *out_len) +{ + char *data_start = out; + + if (smux_serialize_size(pkt) > SMUX_MAX_PKT_SIZE) { + pr_err("%s: packet size %d too big\n", + __func__, smux_serialize_size(pkt)); + return -E2BIG; + } + + memcpy(out, &pkt->hdr, sizeof(struct smux_hdr_t)); + out += sizeof(struct smux_hdr_t); + if (pkt->payload) { + memcpy(out, pkt->payload, pkt->hdr.payload_len); + out += pkt->hdr.payload_len; + } + if (pkt->hdr.pad_len) { + memset(out, 0x0, pkt->hdr.pad_len); + out += pkt->hdr.pad_len; + } + *out_len = out - data_start; + return 0; +} + +/** + * Serialize header and provide pointer to the data. + * + * @pkt Packet + * @out[out] Pointer to the serialized header data + * @out_len[out] Pointer to the serialized header length + */ +static void smux_serialize_hdr(struct smux_pkt_t *pkt, char **out, + unsigned int *out_len) +{ + *out = (char *)&pkt->hdr; + *out_len = sizeof(struct smux_hdr_t); +} + +/** + * Serialize payload and provide pointer to the data. + * + * @pkt Packet + * @out[out] Pointer to the serialized payload data + * @out_len[out] Pointer to the serialized payload length + */ +static void smux_serialize_payload(struct smux_pkt_t *pkt, char **out, + unsigned int *out_len) +{ + *out = pkt->payload; + *out_len = pkt->hdr.payload_len; +} + +/** + * Serialize padding and provide pointer to the data. + * + * @pkt Packet + * @out[out] Pointer to the serialized padding (always NULL) + * @out_len[out] Pointer to the serialized payload length + * + * Since the padding field value is undefined, only the size of the patting + * (@out_len) is set and the buffer pointer (@out) will always be NULL. + */ +static void smux_serialize_padding(struct smux_pkt_t *pkt, char **out, + unsigned int *out_len) +{ + *out = NULL; + *out_len = pkt->hdr.pad_len; +} + +/** + * Write data to TTY framework and handle breaking the writes up if needed. + * + * @data Data to write + * @len Length of data + * + * @returns 0 for success, < 0 for failure + */ +static int write_to_tty(char *data, unsigned len) +{ + int data_written; + + if (!data) + return 0; + + while (len > 0) { + data_written = smux.tty->ops->write(smux.tty, data, len); + if (data_written >= 0) { + len -= data_written; + data += data_written; + } else { + pr_err("%s: TTY write returned error %d\n", + __func__, data_written); + return data_written; + } + + if (len) + tty_wait_until_sent(smux.tty, + msecs_to_jiffies(TTY_BUFFER_FULL_WAIT_MS)); + + /* FUTURE - add SSR logic */ + } + return 0; +} + +/** + * Write packet to TTY. + * + * @pkt packet to write + * + * @returns 0 on success + */ +static int smux_tx_tty(struct smux_pkt_t *pkt) +{ + char *data; + unsigned int len; + int ret; + + if (!smux.tty) { + pr_err("%s: TTY not initialized", __func__); + return -ENOTTY; + } + + if (pkt->hdr.cmd == SMUX_CMD_BYTE) { + SMUX_DBG("%s: tty send single byte\n", __func__); + ret = write_to_tty(&pkt->hdr.flags, 1); + return ret; + } + + smux_serialize_hdr(pkt, &data, &len); + ret = write_to_tty(data, len); + if (ret) { + pr_err("%s: failed %d to write header %d\n", + __func__, ret, len); + return ret; + } + + smux_serialize_payload(pkt, &data, &len); + ret = write_to_tty(data, len); + if (ret) { + pr_err("%s: failed %d to write payload %d\n", + __func__, ret, len); + return ret; + } + + smux_serialize_padding(pkt, &data, &len); + while (len > 0) { + char zero = 0x0; + ret = write_to_tty(&zero, 1); + if (ret) { + pr_err("%s: failed %d to write padding %d\n", + __func__, ret, len); + return ret; + } + --len; + } + return 0; +} + +/** + * Send a single character. + * + * @ch Character to send + */ +static void smux_send_byte(char ch) +{ + struct smux_pkt_t pkt; + + smux_init_pkt(&pkt); + + pkt.hdr.cmd = SMUX_CMD_BYTE; + pkt.hdr.flags = ch; + pkt.hdr.lcid = 0; + pkt.hdr.flags = ch; + SMUX_LOG_PKT_TX(&pkt); + if (!smux_byte_loopback) + smux_tx_tty(&pkt); + else + smux_tx_loopback(&pkt); +} + +/** + * Receive a single-character packet (used for internal testing). + * + * @ch Character to receive + * @lcid Logical channel ID for packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 locked. + */ +static int smux_receive_byte(char ch, int lcid) +{ + struct smux_pkt_t pkt; + + smux_init_pkt(&pkt); + pkt.hdr.lcid = lcid; + pkt.hdr.cmd = SMUX_CMD_BYTE; + pkt.hdr.flags = ch; + + return smux_dispatch_rx_pkt(&pkt); +} + +/** + * Queue packet for transmit. + * + * @pkt_ptr Packet to queue + * @ch Channel to queue packet on + * @queue Queue channel on ready list + */ +static void smux_tx_queue(struct smux_pkt_t *pkt_ptr, struct smux_lch_t *ch, + int queue) +{ + unsigned long flags; + + SMUX_DBG("%s: queuing pkt %p\n", __func__, pkt_ptr); + + spin_lock_irqsave(&ch->tx_lock_lhb2, flags); + list_add_tail(&pkt_ptr->list, &ch->tx_queue); + spin_unlock_irqrestore(&ch->tx_lock_lhb2, flags); + + if (queue) + list_channel(ch); +} + +/** + * Handle receive OPEN ACK command. + * + * @pkt Received packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_handle_rx_open_ack(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + struct smux_lch_t *ch; + int enable_powerdown = 0; + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + + spin_lock(&ch->state_lock_lhb1); + if (ch->local_state == SMUX_LCH_LOCAL_OPENING) { + SMUX_DBG("lcid %d local state 0x%x -> 0x%x\n", lcid, + ch->local_state, + SMUX_LCH_LOCAL_OPENED); + + if (pkt->hdr.flags & SMUX_CMD_OPEN_POWER_COLLAPSE) + enable_powerdown = 1; + + ch->local_state = SMUX_LCH_LOCAL_OPENED; + if (ch->remote_state == SMUX_LCH_REMOTE_OPENED) + schedule_notify(lcid, SMUX_CONNECTED, NULL); + ret = 0; + } else if (ch->remote_mode == SMUX_LCH_MODE_REMOTE_LOOPBACK) { + SMUX_DBG("Remote loopback OPEN ACK received\n"); + ret = 0; + } else { + pr_err("%s: lcid %d state 0x%x open ack invalid\n", + __func__, lcid, ch->local_state); + ret = -EINVAL; + } + spin_unlock(&ch->state_lock_lhb1); + + if (enable_powerdown) { + spin_lock(&smux.tx_lock_lha2); + if (!smux.powerdown_enabled) { + smux.powerdown_enabled = 1; + SMUX_DBG("%s: enabling power-collapse support\n", + __func__); + } + spin_unlock(&smux.tx_lock_lha2); + } + + return ret; +} + +static int smux_handle_close_ack(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + struct smux_lch_t *ch; + union notifier_metadata meta_disconnected; + unsigned long flags; + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + meta_disconnected.disconnected.is_ssr = 0; + + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + + if (ch->local_state == SMUX_LCH_LOCAL_CLOSING) { + SMUX_DBG("lcid %d local state 0x%x -> 0x%x\n", lcid, + SMUX_LCH_LOCAL_CLOSING, + SMUX_LCH_LOCAL_CLOSED); + ch->local_state = SMUX_LCH_LOCAL_CLOSED; + if (ch->remote_state == SMUX_LCH_REMOTE_CLOSED) + schedule_notify(lcid, SMUX_DISCONNECTED, + &meta_disconnected); + ret = 0; + } else if (ch->remote_mode == SMUX_LCH_MODE_REMOTE_LOOPBACK) { + SMUX_DBG("Remote loopback CLOSE ACK received\n"); + ret = 0; + } else { + pr_err("%s: lcid %d state 0x%x close ack invalid\n", + __func__, lcid, ch->local_state); + ret = -EINVAL; + } + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + return ret; +} + +/** + * Handle receive OPEN command. + * + * @pkt Received packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_handle_rx_open_cmd(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + struct smux_lch_t *ch; + struct smux_pkt_t *ack_pkt; + int tx_ready = 0; + int enable_powerdown = 0; + + if (pkt->hdr.flags & SMUX_CMD_OPEN_ACK) + return smux_handle_rx_open_ack(pkt); + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + + spin_lock(&ch->state_lock_lhb1); + + if (ch->remote_state == SMUX_LCH_REMOTE_CLOSED) { + SMUX_DBG("lcid %d remote state 0x%x -> 0x%x\n", lcid, + SMUX_LCH_REMOTE_CLOSED, + SMUX_LCH_REMOTE_OPENED); + + ch->remote_state = SMUX_LCH_REMOTE_OPENED; + if (pkt->hdr.flags & SMUX_CMD_OPEN_POWER_COLLAPSE) + enable_powerdown = 1; + + /* Send Open ACK */ + ack_pkt = smux_alloc_pkt(); + if (!ack_pkt) { + /* exit out to allow retrying this later */ + ret = -ENOMEM; + goto out; + } + ack_pkt->hdr.cmd = SMUX_CMD_OPEN_LCH; + ack_pkt->hdr.flags = SMUX_CMD_OPEN_ACK + | SMUX_CMD_OPEN_POWER_COLLAPSE; + ack_pkt->hdr.lcid = lcid; + ack_pkt->hdr.payload_len = 0; + ack_pkt->hdr.pad_len = 0; + if (pkt->hdr.flags & SMUX_CMD_OPEN_REMOTE_LOOPBACK) { + ch->remote_mode = SMUX_LCH_MODE_REMOTE_LOOPBACK; + ack_pkt->hdr.flags |= SMUX_CMD_OPEN_REMOTE_LOOPBACK; + } + smux_tx_queue(ack_pkt, ch, 0); + tx_ready = 1; + + if (ch->remote_mode == SMUX_LCH_MODE_REMOTE_LOOPBACK) { + /* + * Send an Open command to the remote side to + * simulate our local client doing it. + */ + ack_pkt = smux_alloc_pkt(); + if (ack_pkt) { + ack_pkt->hdr.lcid = lcid; + ack_pkt->hdr.cmd = SMUX_CMD_OPEN_LCH; + ack_pkt->hdr.flags = + SMUX_CMD_OPEN_POWER_COLLAPSE; + ack_pkt->hdr.payload_len = 0; + ack_pkt->hdr.pad_len = 0; + smux_tx_queue(ack_pkt, ch, 0); + tx_ready = 1; + } else { + pr_err("%s: Remote loopack allocation failure\n", + __func__); + } + } else if (ch->local_state == SMUX_LCH_LOCAL_OPENED) { + schedule_notify(lcid, SMUX_CONNECTED, NULL); + } + ret = 0; + } else { + pr_err("%s: lcid %d remote state 0x%x open invalid\n", + __func__, lcid, ch->remote_state); + ret = -EINVAL; + } + +out: + spin_unlock(&ch->state_lock_lhb1); + + if (enable_powerdown) { + spin_lock(&smux.tx_lock_lha2); + smux.powerdown_enabled = 1; + SMUX_DBG("%s: enabling power-collapse support\n", __func__); + spin_unlock(&smux.tx_lock_lha2); + } + + if (tx_ready) + list_channel(ch); + + return ret; +} + +/** + * Handle receive CLOSE command. + * + * @pkt Received packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_handle_rx_close_cmd(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + struct smux_lch_t *ch; + struct smux_pkt_t *ack_pkt; + union notifier_metadata meta_disconnected; + int tx_ready = 0; + + if (pkt->hdr.flags & SMUX_CMD_CLOSE_ACK) + return smux_handle_close_ack(pkt); + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + meta_disconnected.disconnected.is_ssr = 0; + + spin_lock(&ch->state_lock_lhb1); + if (ch->remote_state == SMUX_LCH_REMOTE_OPENED) { + SMUX_DBG("lcid %d remote state 0x%x -> 0x%x\n", lcid, + SMUX_LCH_REMOTE_OPENED, + SMUX_LCH_REMOTE_CLOSED); + + ack_pkt = smux_alloc_pkt(); + if (!ack_pkt) { + /* exit out to allow retrying this later */ + ret = -ENOMEM; + goto out; + } + ch->remote_state = SMUX_LCH_REMOTE_CLOSED; + ack_pkt->hdr.cmd = SMUX_CMD_CLOSE_LCH; + ack_pkt->hdr.flags = SMUX_CMD_CLOSE_ACK; + ack_pkt->hdr.lcid = lcid; + ack_pkt->hdr.payload_len = 0; + ack_pkt->hdr.pad_len = 0; + smux_tx_queue(ack_pkt, ch, 0); + tx_ready = 1; + + if (ch->remote_mode == SMUX_LCH_MODE_REMOTE_LOOPBACK) { + /* + * Send a Close command to the remote side to simulate + * our local client doing it. + */ + ack_pkt = smux_alloc_pkt(); + if (ack_pkt) { + ack_pkt->hdr.lcid = lcid; + ack_pkt->hdr.cmd = SMUX_CMD_CLOSE_LCH; + ack_pkt->hdr.flags = 0; + ack_pkt->hdr.payload_len = 0; + ack_pkt->hdr.pad_len = 0; + smux_tx_queue(ack_pkt, ch, 0); + tx_ready = 1; + } else { + pr_err("%s: Remote loopack allocation failure\n", + __func__); + } + } + + if (ch->local_state == SMUX_LCH_LOCAL_CLOSED) + schedule_notify(lcid, SMUX_DISCONNECTED, + &meta_disconnected); + ret = 0; + } else { + pr_err("%s: lcid %d remote state 0x%x close invalid\n", + __func__, lcid, ch->remote_state); + ret = -EINVAL; + } +out: + spin_unlock(&ch->state_lock_lhb1); + if (tx_ready) + list_channel(ch); + + return ret; +} + +/* + * Handle receive DATA command. + * + * @pkt Received packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_handle_rx_data_cmd(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + int i; + int tmp; + int rx_len; + struct smux_lch_t *ch; + union notifier_metadata metadata; + int remote_loopback; + int tx_ready = 0; + struct smux_pkt_t *ack_pkt; + unsigned long flags; + + if (!pkt || smux_assert_lch_id(pkt->hdr.lcid)) + return -ENXIO; + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + remote_loopback = ch->remote_mode == SMUX_LCH_MODE_REMOTE_LOOPBACK; + + if (ch->local_state != SMUX_LCH_LOCAL_OPENED + && !remote_loopback) { + pr_err("smux: ch %d error data on local state 0x%x", + lcid, ch->local_state); + ret = -EIO; + goto out; + } + + if (ch->remote_state != SMUX_LCH_REMOTE_OPENED) { + pr_err("smux: ch %d error data on remote state 0x%x", + lcid, ch->remote_state); + ret = -EIO; + goto out; + } + + rx_len = pkt->hdr.payload_len; + if (rx_len == 0) { + ret = -EINVAL; + goto out; + } + + for (i = 0; i < SMUX_GET_RX_BUFF_MAX_RETRY_CNT; ++i) { + metadata.read.pkt_priv = 0; + metadata.read.buffer = 0; + + if (!remote_loopback) { + tmp = ch->get_rx_buffer(ch->priv, + (void **)&metadata.read.pkt_priv, + (void **)&metadata.read.buffer, + rx_len); + if (tmp == 0 && metadata.read.buffer) { + /* place data into RX buffer */ + memcpy(metadata.read.buffer, pkt->payload, + rx_len); + metadata.read.len = rx_len; + schedule_notify(lcid, SMUX_READ_DONE, + &metadata); + ret = 0; + break; + } else if (tmp == -EAGAIN) { + ret = -ENOMEM; + } else if (tmp < 0) { + schedule_notify(lcid, SMUX_READ_FAIL, NULL); + ret = -ENOMEM; + break; + } else if (!metadata.read.buffer) { + pr_err("%s: get_rx_buffer() buffer is NULL\n", + __func__); + ret = -ENOMEM; + } + } else { + /* Echo the data back to the remote client. */ + ack_pkt = smux_alloc_pkt(); + if (ack_pkt) { + ack_pkt->hdr.lcid = lcid; + ack_pkt->hdr.cmd = SMUX_CMD_DATA; + ack_pkt->hdr.flags = 0; + ack_pkt->hdr.payload_len = pkt->hdr.payload_len; + ack_pkt->payload = pkt->payload; + ack_pkt->hdr.pad_len = pkt->hdr.pad_len; + smux_tx_queue(ack_pkt, ch, 0); + tx_ready = 1; + } else { + pr_err("%s: Remote loopack allocation failure\n", + __func__); + } + } + } + +out: + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + + if (tx_ready) + list_channel(ch); + + return ret; +} + +/** + * Handle receive byte command for testing purposes. + * + * @pkt Received packet + * + * @returns 0 for success + */ +static int smux_handle_rx_byte_cmd(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + struct smux_lch_t *ch; + union notifier_metadata metadata; + unsigned long flags; + + if (!pkt || smux_assert_lch_id(pkt->hdr.lcid)) + return -ENXIO; + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + + if (ch->local_state != SMUX_LCH_LOCAL_OPENED) { + pr_err("smux: ch %d error data on local state 0x%x", + lcid, ch->local_state); + ret = -EIO; + goto out; + } + + if (ch->remote_state != SMUX_LCH_REMOTE_OPENED) { + pr_err("smux: ch %d error data on remote state 0x%x", + lcid, ch->remote_state); + ret = -EIO; + goto out; + } + + metadata.read.pkt_priv = (void *)(int)pkt->hdr.flags; + metadata.read.buffer = 0; + schedule_notify(lcid, SMUX_READ_DONE, &metadata); + ret = 0; + +out: + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + return ret; +} + +/** + * Handle receive status command. + * + * @pkt Received packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_handle_rx_status_cmd(struct smux_pkt_t *pkt) +{ + uint8_t lcid; + int ret; + struct smux_lch_t *ch; + union notifier_metadata meta; + unsigned long flags; + int tx_ready = 0; + + lcid = pkt->hdr.lcid; + ch = &smux_lch[lcid]; + + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + meta.tiocm.tiocm_old = ch->remote_tiocm; + meta.tiocm.tiocm_new = pkt->hdr.flags; + + /* update logical channel flow control */ + if ((meta.tiocm.tiocm_old & SMUX_CMD_STATUS_FLOW_CNTL) ^ + (meta.tiocm.tiocm_new & SMUX_CMD_STATUS_FLOW_CNTL)) { + /* logical channel flow control changed */ + if (pkt->hdr.flags & SMUX_CMD_STATUS_FLOW_CNTL) { + /* disabled TX */ + SMUX_DBG("TX Flow control enabled\n"); + ch->tx_flow_control = 1; + } else { + /* re-enable channel */ + SMUX_DBG("TX Flow control disabled\n"); + ch->tx_flow_control = 0; + tx_ready = 1; + } + } + meta.tiocm.tiocm_old = msm_smux_tiocm_get_atomic(ch); + ch->remote_tiocm = pkt->hdr.flags; + meta.tiocm.tiocm_new = msm_smux_tiocm_get_atomic(ch); + + /* client notification for status change */ + if (IS_FULLY_OPENED(ch)) { + if (meta.tiocm.tiocm_old != meta.tiocm.tiocm_new) + schedule_notify(lcid, SMUX_TIOCM_UPDATE, &meta); + ret = 0; + } + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + if (tx_ready) + list_channel(ch); + + return ret; +} + +/** + * Handle receive power command. + * + * @pkt Received packet + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_handle_rx_power_cmd(struct smux_pkt_t *pkt) +{ + int tx_ready = 0; + struct smux_pkt_t *ack_pkt = NULL; + + spin_lock(&smux.tx_lock_lha2); + if (pkt->hdr.flags & SMUX_CMD_PWR_CTL_ACK) { + /* local sleep request ack */ + if (smux.power_state == SMUX_PWR_TURNING_OFF) { + /* Power-down complete, turn off UART */ + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, SMUX_PWR_OFF_FLUSH); + smux.power_state = SMUX_PWR_OFF_FLUSH; + queue_work(smux_tx_wq, &smux_inactivity_work); + } else { + pr_err("%s: sleep request ack invalid in state %d\n", + __func__, smux.power_state); + } + } else { + /* remote sleep request */ + if (smux.power_state == SMUX_PWR_ON + || smux.power_state == SMUX_PWR_TURNING_OFF) { + ack_pkt = smux_alloc_pkt(); + if (ack_pkt) { + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, + SMUX_PWR_TURNING_OFF_FLUSH); + + /* send power-down request */ + ack_pkt->hdr.cmd = SMUX_CMD_PWR_CTL; + ack_pkt->hdr.flags = SMUX_CMD_PWR_CTL_ACK; + ack_pkt->hdr.lcid = pkt->hdr.lcid; + smux_tx_queue(ack_pkt, + &smux_lch[ack_pkt->hdr.lcid], 0); + tx_ready = 1; + smux.power_state = SMUX_PWR_TURNING_OFF_FLUSH; + queue_delayed_work(smux_tx_wq, + &smux_delayed_inactivity_work, + msecs_to_jiffies( + SMUX_INACTIVITY_TIMEOUT_MS)); + } + } else { + pr_err("%s: sleep request invalid in state %d\n", + __func__, smux.power_state); + } + } + spin_unlock(&smux.tx_lock_lha2); + + if (tx_ready) + list_channel(&smux_lch[ack_pkt->hdr.lcid]); + + return 0; +} + +/** + * Handle dispatching a completed packet for receive processing. + * + * @pkt Packet to process + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_dispatch_rx_pkt(struct smux_pkt_t *pkt) +{ + int ret; + + SMUX_LOG_PKT_RX(pkt); + + switch (pkt->hdr.cmd) { + case SMUX_CMD_OPEN_LCH: + ret = smux_handle_rx_open_cmd(pkt); + break; + + case SMUX_CMD_DATA: + ret = smux_handle_rx_data_cmd(pkt); + break; + + case SMUX_CMD_CLOSE_LCH: + ret = smux_handle_rx_close_cmd(pkt); + break; + + case SMUX_CMD_STATUS: + ret = smux_handle_rx_status_cmd(pkt); + break; + + case SMUX_CMD_PWR_CTL: + ret = smux_handle_rx_power_cmd(pkt); + break; + + case SMUX_CMD_BYTE: + ret = smux_handle_rx_byte_cmd(pkt); + break; + + default: + pr_err("%s: command %d unknown\n", __func__, pkt->hdr.cmd); + ret = -EINVAL; + } + return ret; +} + +/** + * Deserializes a packet and dispatches it to the packet receive logic. + * + * @data Raw data for one packet + * @len Length of the data + * + * @returns 0 for success + * + * Called with rx_lock_lha1 already locked. + */ +static int smux_deserialize(unsigned char *data, int len) +{ + struct smux_pkt_t recv; + uint8_t lcid; + + smux_init_pkt(&recv); + + /* + * It may be possible to optimize this to not use the + * temporary buffer. + */ + memcpy(&recv.hdr, data, sizeof(struct smux_hdr_t)); + + if (recv.hdr.magic != SMUX_MAGIC) { + pr_err("%s: invalid header magic\n", __func__); + return -EINVAL; + } + + lcid = recv.hdr.lcid; + if (smux_assert_lch_id(lcid)) { + pr_err("%s: invalid channel id %d\n", __func__, lcid); + return -ENXIO; + } + + if (recv.hdr.payload_len) + recv.payload = data + sizeof(struct smux_hdr_t); + + return smux_dispatch_rx_pkt(&recv); +} + +/** + * Handle wakeup request byte. + * + * Called with rx_lock_lha1 already locked. + */ +static void smux_handle_wakeup_req(void) +{ + spin_lock(&smux.tx_lock_lha2); + if (smux.power_state == SMUX_PWR_OFF + || smux.power_state == SMUX_PWR_TURNING_ON) { + /* wakeup system */ + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, SMUX_PWR_ON); + smux.power_state = SMUX_PWR_ON; + queue_work(smux_tx_wq, &smux_wakeup_work); + queue_work(smux_tx_wq, &smux_tx_work); + queue_delayed_work(smux_tx_wq, &smux_delayed_inactivity_work, + msecs_to_jiffies(SMUX_INACTIVITY_TIMEOUT_MS)); + smux_send_byte(SMUX_WAKEUP_ACK); + } else { + smux_send_byte(SMUX_WAKEUP_ACK); + } + spin_unlock(&smux.tx_lock_lha2); +} + +/** + * Handle wakeup request ack. + * + * Called with rx_lock_lha1 already locked. + */ +static void smux_handle_wakeup_ack(void) +{ + spin_lock(&smux.tx_lock_lha2); + if (smux.power_state == SMUX_PWR_TURNING_ON) { + /* received response to wakeup request */ + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, SMUX_PWR_ON); + smux.power_state = SMUX_PWR_ON; + queue_work(smux_tx_wq, &smux_tx_work); + queue_delayed_work(smux_tx_wq, &smux_delayed_inactivity_work, + msecs_to_jiffies(SMUX_INACTIVITY_TIMEOUT_MS)); + + } else if (smux.power_state != SMUX_PWR_ON) { + /* invalid message */ + pr_err("%s: wakeup request ack invalid in state %d\n", + __func__, smux.power_state); + } + spin_unlock(&smux.tx_lock_lha2); +} + +/** + * RX State machine - IDLE state processing. + * + * @data New RX data to process + * @len Length of the data + * @used Return value of length processed + * @flag Error flag - TTY_NORMAL 0 for no failure + * + * Called with rx_lock_lha1 locked. + */ +static void smux_rx_handle_idle(const unsigned char *data, + int len, int *used, int flag) +{ + int i; + + if (flag) { + if (smux_byte_loopback) + smux_receive_byte(SMUX_UT_ECHO_ACK_FAIL, + smux_byte_loopback); + pr_err("%s: TTY error 0x%x - ignoring\n", __func__, flag); + ++*used; + return; + } + + for (i = *used; i < len && smux.rx_state == SMUX_RX_IDLE; i++) { + switch (data[i]) { + case SMUX_MAGIC_WORD1: + smux.rx_state = SMUX_RX_MAGIC; + break; + case SMUX_WAKEUP_REQ: + smux_handle_wakeup_req(); + break; + case SMUX_WAKEUP_ACK: + smux_handle_wakeup_ack(); + break; + default: + /* unexpected character */ + if (smux_byte_loopback && data[i] == SMUX_UT_ECHO_REQ) + smux_receive_byte(SMUX_UT_ECHO_ACK_OK, + smux_byte_loopback); + pr_err("%s: parse error 0x%02x - ignoring\n", __func__, + (unsigned)data[i]); + break; + } + } + + *used = i; +} + +/** + * RX State machine - Header Magic state processing. + * + * @data New RX data to process + * @len Length of the data + * @used Return value of length processed + * @flag Error flag - TTY_NORMAL 0 for no failure + * + * Called with rx_lock_lha1 locked. + */ +static void smux_rx_handle_magic(const unsigned char *data, + int len, int *used, int flag) +{ + int i; + + if (flag) { + pr_err("%s: TTY RX error %d\n", __func__, flag); + smux_enter_reset(); + smux.rx_state = SMUX_RX_FAILURE; + ++*used; + return; + } + + for (i = *used; i < len && smux.rx_state == SMUX_RX_MAGIC; i++) { + /* wait for completion of the magic */ + if (data[i] == SMUX_MAGIC_WORD2) { + smux.recv_len = 0; + smux.recv_buf[smux.recv_len++] = SMUX_MAGIC_WORD1; + smux.recv_buf[smux.recv_len++] = SMUX_MAGIC_WORD2; + smux.rx_state = SMUX_RX_HDR; + } else { + /* unexpected / trash character */ + pr_err("%s: rx parse error for char %c; *used=%d, len=%d\n", + __func__, data[i], *used, len); + smux.rx_state = SMUX_RX_IDLE; + } + } + + *used = i; +} + +/** + * RX State machine - Packet Header state processing. + * + * @data New RX data to process + * @len Length of the data + * @used Return value of length processed + * @flag Error flag - TTY_NORMAL 0 for no failure + * + * Called with rx_lock_lha1 locked. + */ +static void smux_rx_handle_hdr(const unsigned char *data, + int len, int *used, int flag) +{ + int i; + struct smux_hdr_t *hdr; + + if (flag) { + pr_err("%s: TTY RX error %d\n", __func__, flag); + smux_enter_reset(); + smux.rx_state = SMUX_RX_FAILURE; + ++*used; + return; + } + + for (i = *used; i < len && smux.rx_state == SMUX_RX_HDR; i++) { + smux.recv_buf[smux.recv_len++] = data[i]; + + if (smux.recv_len == sizeof(struct smux_hdr_t)) { + /* complete header received */ + hdr = (struct smux_hdr_t *)smux.recv_buf; + smux.pkt_remain = hdr->payload_len + hdr->pad_len; + smux.rx_state = SMUX_RX_PAYLOAD; + } + } + *used = i; +} + +/** + * RX State machine - Packet Payload state processing. + * + * @data New RX data to process + * @len Length of the data + * @used Return value of length processed + * @flag Error flag - TTY_NORMAL 0 for no failure + * + * Called with rx_lock_lha1 locked. + */ +static void smux_rx_handle_pkt_payload(const unsigned char *data, + int len, int *used, int flag) +{ + int remaining; + + if (flag) { + pr_err("%s: TTY RX error %d\n", __func__, flag); + smux_enter_reset(); + smux.rx_state = SMUX_RX_FAILURE; + ++*used; + return; + } + + /* copy data into rx buffer */ + if (smux.pkt_remain < (len - *used)) + remaining = smux.pkt_remain; + else + remaining = len - *used; + + memcpy(&smux.recv_buf[smux.recv_len], &data[*used], remaining); + smux.recv_len += remaining; + smux.pkt_remain -= remaining; + *used += remaining; + + if (smux.pkt_remain == 0) { + /* complete packet received */ + smux_deserialize(smux.recv_buf, smux.recv_len); + smux.rx_state = SMUX_RX_IDLE; + } +} + +/** + * Feed data to the receive state machine. + * + * @data Pointer to data block + * @len Length of data + * @flag TTY_NORMAL (0) for no error, otherwise TTY Error Flag + * + * Called with rx_lock_lha1 locked. + */ +void smux_rx_state_machine(const unsigned char *data, + int len, int flag) +{ + unsigned long flags; + int used; + int initial_rx_state; + + + SMUX_DBG("%s: %p, len=%d, flag=%d\n", __func__, data, len, flag); + spin_lock_irqsave(&smux.rx_lock_lha1, flags); + used = 0; + smux.rx_activity_flag = 1; + do { + SMUX_DBG("%s: state %d; %d of %d\n", + __func__, smux.rx_state, used, len); + initial_rx_state = smux.rx_state; + + switch (smux.rx_state) { + case SMUX_RX_IDLE: + smux_rx_handle_idle(data, len, &used, flag); + break; + case SMUX_RX_MAGIC: + smux_rx_handle_magic(data, len, &used, flag); + break; + case SMUX_RX_HDR: + smux_rx_handle_hdr(data, len, &used, flag); + break; + case SMUX_RX_PAYLOAD: + smux_rx_handle_pkt_payload(data, len, &used, flag); + break; + default: + SMUX_DBG("%s: invalid state %d\n", + __func__, smux.rx_state); + smux.rx_state = SMUX_RX_IDLE; + break; + } + } while (used < len || smux.rx_state != initial_rx_state); + spin_unlock_irqrestore(&smux.rx_lock_lha1, flags); +} + +/** + * Add channel to transmit-ready list and trigger transmit worker. + * + * @ch Channel to add + */ +static void list_channel(struct smux_lch_t *ch) +{ + unsigned long flags; + + SMUX_DBG("%s: listing channel %d\n", + __func__, ch->lcid); + + spin_lock_irqsave(&smux.tx_lock_lha2, flags); + spin_lock(&ch->tx_lock_lhb2); + smux.tx_activity_flag = 1; + if (list_empty(&ch->tx_ready_list)) + list_add_tail(&ch->tx_ready_list, &smux.lch_tx_ready_list); + spin_unlock(&ch->tx_lock_lhb2); + spin_unlock_irqrestore(&smux.tx_lock_lha2, flags); + + queue_work(smux_tx_wq, &smux_tx_work); +} + +/** + * Transmit packet on correct transport and then perform client + * notification. + * + * @ch Channel to transmit on + * @pkt Packet to transmit + */ +static void smux_tx_pkt(struct smux_lch_t *ch, struct smux_pkt_t *pkt) +{ + union notifier_metadata meta_write; + int ret; + + if (ch && pkt) { + SMUX_LOG_PKT_TX(pkt); + if (ch->local_mode == SMUX_LCH_MODE_LOCAL_LOOPBACK) + ret = smux_tx_loopback(pkt); + else + ret = smux_tx_tty(pkt); + + if (pkt->hdr.cmd == SMUX_CMD_DATA) { + /* notify write-done */ + meta_write.write.pkt_priv = pkt->priv; + meta_write.write.buffer = pkt->payload; + meta_write.write.len = pkt->hdr.payload_len; + if (ret >= 0) { + SMUX_DBG("%s: PKT write done", __func__); + schedule_notify(ch->lcid, SMUX_WRITE_DONE, + &meta_write); + } else { + pr_err("%s: failed to write pkt %d\n", + __func__, ret); + schedule_notify(ch->lcid, SMUX_WRITE_FAIL, + &meta_write); + } + } + } +} + +/** + * Power-up the UART. + */ +static void smux_uart_power_on(void) +{ + struct uart_state *state; + + if (!smux.tty || !smux.tty->driver_data) { + pr_err("%s: unable to find UART port for tty %p\n", + __func__, smux.tty); + return; + } + state = smux.tty->driver_data; + msm_hs_request_clock_on(state->uart_port); +} + +/** + * Power down the UART. + */ +static void smux_uart_power_off(void) +{ + struct uart_state *state; + + if (!smux.tty || !smux.tty->driver_data) { + pr_err("%s: unable to find UART port for tty %p\n", + __func__, smux.tty); + return; + } + state = smux.tty->driver_data; + msm_hs_request_clock_off(state->uart_port); +} + +/** + * TX Wakeup Worker + * + * @work Not used + * + * Do an exponential back-off wakeup sequence with a maximum period + * of approximately 1 second (1 << 20 microseconds). + */ +static void smux_wakeup_worker(struct work_struct *work) +{ + unsigned long flags; + unsigned wakeup_delay; + int complete = 0; + + for (;;) { + spin_lock_irqsave(&smux.tx_lock_lha2, flags); + if (smux.power_state == SMUX_PWR_ON) { + /* wakeup complete */ + complete = 1; + spin_unlock_irqrestore(&smux.tx_lock_lha2, flags); + break; + } else { + /* retry */ + wakeup_delay = smux.pwr_wakeup_delay_us; + smux.pwr_wakeup_delay_us <<= 1; + if (smux.pwr_wakeup_delay_us > SMUX_WAKEUP_DELAY_MAX) + smux.pwr_wakeup_delay_us = + SMUX_WAKEUP_DELAY_MAX; + } + spin_unlock_irqrestore(&smux.tx_lock_lha2, flags); + SMUX_DBG("%s: triggering wakeup\n", __func__); + smux_send_byte(SMUX_WAKEUP_REQ); + + if (wakeup_delay < SMUX_WAKEUP_DELAY_MIN) { + SMUX_DBG("%s: sleeping for %u us\n", __func__, + wakeup_delay); + usleep_range(wakeup_delay, 2*wakeup_delay); + } else { + /* schedule delayed work */ + SMUX_DBG("%s: scheduling delayed wakeup in %u ms\n", + __func__, wakeup_delay / 1000); + queue_delayed_work(smux_tx_wq, + &smux_wakeup_delayed_work, + msecs_to_jiffies(wakeup_delay / 1000)); + break; + } + } + + if (complete) { + SMUX_DBG("%s: wakeup complete\n", __func__); + /* + * Cancel any pending retry. This avoids a race condition with + * a new power-up request because: + * 1) this worker doesn't modify the state + * 2) this worker is processed on the same single-threaded + * workqueue as new TX wakeup requests + */ + cancel_delayed_work(&smux_wakeup_delayed_work); + } +} + + +/** + * Inactivity timeout worker. Periodically scheduled when link is active. + * When it detects inactivity, it will power-down the UART link. + * + * @work Work structure (not used) + */ +static void smux_inactivity_worker(struct work_struct *work) +{ + int tx_ready = 0; + struct smux_pkt_t *pkt; + unsigned long flags; + + spin_lock_irqsave(&smux.rx_lock_lha1, flags); + spin_lock(&smux.tx_lock_lha2); + + if (!smux.tx_activity_flag && !smux.rx_activity_flag) { + /* no activity */ + if (smux.powerdown_enabled) { + if (smux.power_state == SMUX_PWR_ON) { + /* start power-down sequence */ + pkt = smux_alloc_pkt(); + if (pkt) { + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, + SMUX_PWR_TURNING_OFF); + smux.power_state = SMUX_PWR_TURNING_OFF; + + /* send power-down request */ + pkt->hdr.cmd = SMUX_CMD_PWR_CTL; + pkt->hdr.flags = 0; + pkt->hdr.lcid = 0; + smux_tx_queue(pkt, + &smux_lch[SMUX_TEST_LCID], + 0); + tx_ready = 1; + } + } + } else { + SMUX_DBG("%s: link inactive, but powerdown disabled\n", + __func__); + } + } + smux.tx_activity_flag = 0; + smux.rx_activity_flag = 0; + + spin_unlock(&smux.tx_lock_lha2); + spin_unlock_irqrestore(&smux.rx_lock_lha1, flags); + + if (tx_ready) + list_channel(&smux_lch[SMUX_TEST_LCID]); + + if ((smux.power_state == SMUX_PWR_OFF_FLUSH) || + (smux.power_state == SMUX_PWR_TURNING_OFF_FLUSH)) { + /* ready to power-down the UART */ + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, SMUX_PWR_OFF); + smux_uart_power_off(); + spin_lock_irqsave(&smux.tx_lock_lha2, flags); + smux.power_state = SMUX_PWR_OFF; + spin_unlock_irqrestore(&smux.tx_lock_lha2, flags); + } + + /* reschedule inactivity worker */ + if (smux.power_state != SMUX_PWR_OFF) + queue_delayed_work(smux_tx_wq, &smux_delayed_inactivity_work, + msecs_to_jiffies(SMUX_INACTIVITY_TIMEOUT_MS)); +} + +/** + * Transmit worker handles serializing and transmitting packets onto the + * underlying transport. + * + * @work Work structure (not used) + */ +static void smux_tx_worker(struct work_struct *work) +{ + struct smux_pkt_t *pkt; + struct smux_lch_t *ch; + unsigned low_wm_notif; + unsigned lcid; + unsigned long flags; + + + /* + * Transmit packets in round-robin fashion based upon ready + * channels. + * + * To eliminate the need to hold a lock for the entire + * iteration through the channel ready list, the head of the + * ready-channel list is always the next channel to be + * processed. To send a packet, the first valid packet in + * the head channel is removed and the head channel is then + * rescheduled at the end of the queue by removing it and + * inserting after the tail. The locks can then be released + * while the packet is processed. + */ + for (;;) { + pkt = NULL; + low_wm_notif = 0; + + /* get the next ready channel */ + spin_lock_irqsave(&smux.tx_lock_lha2, flags); + if (list_empty(&smux.lch_tx_ready_list)) { + /* no ready channels */ + SMUX_DBG("%s: no more ready channels, exiting\n", + __func__); + spin_unlock_irqrestore(&smux.tx_lock_lha2, flags); + break; + } + smux.tx_activity_flag = 1; + + if (smux.power_state != SMUX_PWR_ON + && smux.power_state != SMUX_PWR_TURNING_OFF + && smux.power_state != SMUX_PWR_TURNING_OFF_FLUSH) { + /* Link isn't ready to transmit */ + if (smux.power_state == SMUX_PWR_OFF) { + /* link is off, trigger wakeup */ + smux.pwr_wakeup_delay_us = 1; + SMUX_DBG("%s: Power %d->%d\n", __func__, + smux.power_state, + SMUX_PWR_TURNING_ON); + smux.power_state = SMUX_PWR_TURNING_ON; + spin_unlock_irqrestore(&smux.tx_lock_lha2, + flags); + smux_uart_power_on(); + queue_work(smux_tx_wq, &smux_wakeup_work); + } else { + SMUX_DBG("%s: can not tx with power state %d\n", + __func__, + smux.power_state); + spin_unlock_irqrestore(&smux.tx_lock_lha2, + flags); + } + break; + } + + /* get the next packet to send and rotate channel list */ + ch = list_first_entry(&smux.lch_tx_ready_list, + struct smux_lch_t, + tx_ready_list); + + spin_lock(&ch->state_lock_lhb1); + spin_lock(&ch->tx_lock_lhb2); + if (!list_empty(&ch->tx_queue)) { + /* + * If remote TX flow control is enabled or + * the channel is not fully opened, then only + * send command packets. + */ + if (ch->tx_flow_control || !IS_FULLY_OPENED(ch)) { + struct smux_pkt_t *curr; + list_for_each_entry(curr, &ch->tx_queue, list) { + if (curr->hdr.cmd != SMUX_CMD_DATA) { + pkt = curr; + break; + } + } + } else { + /* get next cmd/data packet to send */ + pkt = list_first_entry(&ch->tx_queue, + struct smux_pkt_t, list); + } + } + + if (pkt) { + list_del(&pkt->list); + + /* update packet stats */ + if (pkt->hdr.cmd == SMUX_CMD_DATA) { + --ch->tx_pending_data_cnt; + if (ch->notify_lwm && + ch->tx_pending_data_cnt + <= SMUX_WM_LOW) { + ch->notify_lwm = 0; + low_wm_notif = 1; + } + } + + /* advance to the next ready channel */ + list_rotate_left(&smux.lch_tx_ready_list); + } else { + /* no data in channel to send, remove from ready list */ + list_del(&ch->tx_ready_list); + INIT_LIST_HEAD(&ch->tx_ready_list); + } + lcid = ch->lcid; + spin_unlock(&ch->tx_lock_lhb2); + spin_unlock(&ch->state_lock_lhb1); + spin_unlock_irqrestore(&smux.tx_lock_lha2, flags); + + if (low_wm_notif) + schedule_notify(lcid, SMUX_LOW_WM_HIT, NULL); + + /* send the packet */ + smux_tx_pkt(ch, pkt); + smux_free_pkt(pkt); + } +} + + +/**********************************************************************/ +/* Kernel API */ +/**********************************************************************/ + +/** + * Set or clear channel option using the SMUX_CH_OPTION_* channel + * flags. + * + * @lcid Logical channel ID + * @set Options to set + * @clear Options to clear + * + * @returns 0 for success, < 0 for failure + */ +int msm_smux_set_ch_option(uint8_t lcid, uint32_t set, uint32_t clear) +{ + unsigned long flags; + struct smux_lch_t *ch; + int tx_ready = 0; + int ret = 0; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + + /* Local loopback mode */ + if (set & SMUX_CH_OPTION_LOCAL_LOOPBACK) + ch->local_mode = SMUX_LCH_MODE_LOCAL_LOOPBACK; + + if (clear & SMUX_CH_OPTION_LOCAL_LOOPBACK) + ch->local_mode = SMUX_LCH_MODE_NORMAL; + + /* Remote loopback mode */ + if (set & SMUX_CH_OPTION_REMOTE_LOOPBACK) + ch->local_mode = SMUX_LCH_MODE_REMOTE_LOOPBACK; + + if (clear & SMUX_CH_OPTION_REMOTE_LOOPBACK) + ch->local_mode = SMUX_LCH_MODE_NORMAL; + + /* Flow control */ + if (set & SMUX_CH_OPTION_REMOTE_TX_STOP) { + ch->local_tiocm |= SMUX_CMD_STATUS_FLOW_CNTL; + ret = smux_send_status_cmd(ch); + tx_ready = 1; + } + + if (clear & SMUX_CH_OPTION_REMOTE_TX_STOP) { + ch->local_tiocm &= ~SMUX_CMD_STATUS_FLOW_CNTL; + ret = smux_send_status_cmd(ch); + tx_ready = 1; + } + + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + + if (tx_ready) + list_channel(ch); + + return ret; +} + +/** + * Starts the opening sequence for a logical channel. + * + * @lcid Logical channel ID + * @priv Free for client usage + * @notify Event notification function + * @get_rx_buffer Function used to provide a receive buffer to SMUX + * + * @returns 0 for success, <0 otherwise + * + * A channel must be fully closed (either not previously opened or + * msm_smux_close() has been called and the SMUX_DISCONNECTED has been + * received. + * + * One the remote side is opened, the client will receive a SMUX_CONNECTED + * event. + */ +int msm_smux_open(uint8_t lcid, void *priv, + void (*notify)(void *priv, int event_type, const void *metadata), + int (*get_rx_buffer)(void *priv, void **pkt_priv, void **buffer, + int size)) +{ + int ret; + struct smux_lch_t *ch; + struct smux_pkt_t *pkt; + int tx_ready = 0; + unsigned long flags; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + + if (ch->local_state == SMUX_LCH_LOCAL_CLOSING) { + ret = -EAGAIN; + goto out; + } + + if (ch->local_state != SMUX_LCH_LOCAL_CLOSED) { + pr_err("%s: open lcid %d local state %x invalid\n", + __func__, lcid, ch->local_state); + ret = -EINVAL; + goto out; + } + + SMUX_DBG("lcid %d local state 0x%x -> 0x%x\n", lcid, + ch->local_state, + SMUX_LCH_LOCAL_OPENING); + + ch->local_state = SMUX_LCH_LOCAL_OPENING; + + ch->priv = priv; + ch->notify = notify; + ch->get_rx_buffer = get_rx_buffer; + ret = 0; + + /* Send Open Command */ + pkt = smux_alloc_pkt(); + if (!pkt) { + ret = -ENOMEM; + goto out; + } + pkt->hdr.magic = SMUX_MAGIC; + pkt->hdr.cmd = SMUX_CMD_OPEN_LCH; + pkt->hdr.flags = SMUX_CMD_OPEN_POWER_COLLAPSE; + if (ch->local_mode == SMUX_LCH_MODE_REMOTE_LOOPBACK) + pkt->hdr.flags |= SMUX_CMD_OPEN_REMOTE_LOOPBACK; + pkt->hdr.lcid = lcid; + pkt->hdr.payload_len = 0; + pkt->hdr.pad_len = 0; + smux_tx_queue(pkt, ch, 0); + tx_ready = 1; + +out: + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + if (tx_ready) + list_channel(ch); + return ret; +} + +/** + * Starts the closing sequence for a logical channel. + * + * @lcid Logical channel ID + * + * @returns 0 for success, <0 otherwise + * + * Once the close event has been acknowledge by the remote side, the client + * will receive a SMUX_DISCONNECTED notification. + */ +int msm_smux_close(uint8_t lcid) +{ + int ret = 0; + struct smux_lch_t *ch; + struct smux_pkt_t *pkt; + int tx_ready = 0; + unsigned long flags; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + ch->local_tiocm = 0x0; + ch->remote_tiocm = 0x0; + ch->tx_pending_data_cnt = 0; + ch->notify_lwm = 0; + + /* Purge TX queue */ + spin_lock(&ch->tx_lock_lhb2); + while (!list_empty(&ch->tx_queue)) { + pkt = list_first_entry(&ch->tx_queue, struct smux_pkt_t, + list); + list_del(&pkt->list); + + if (pkt->hdr.cmd == SMUX_CMD_OPEN_LCH) { + /* Open was never sent, just force to closed state */ + union notifier_metadata meta_disconnected; + + ch->local_state = SMUX_LCH_LOCAL_CLOSED; + meta_disconnected.disconnected.is_ssr = 0; + schedule_notify(lcid, SMUX_DISCONNECTED, + &meta_disconnected); + } else if (pkt->hdr.cmd == SMUX_CMD_DATA) { + /* Notify client of failed write */ + union notifier_metadata meta_write; + + meta_write.write.pkt_priv = pkt->priv; + meta_write.write.buffer = pkt->payload; + meta_write.write.len = pkt->hdr.payload_len; + schedule_notify(ch->lcid, SMUX_WRITE_FAIL, &meta_write); + } + smux_free_pkt(pkt); + } + spin_unlock(&ch->tx_lock_lhb2); + + /* Send Close Command */ + if (ch->local_state == SMUX_LCH_LOCAL_OPENED || + ch->local_state == SMUX_LCH_LOCAL_OPENING) { + SMUX_DBG("lcid %d local state 0x%x -> 0x%x\n", lcid, + ch->local_state, + SMUX_LCH_LOCAL_CLOSING); + + ch->local_state = SMUX_LCH_LOCAL_CLOSING; + pkt = smux_alloc_pkt(); + if (pkt) { + pkt->hdr.cmd = SMUX_CMD_CLOSE_LCH; + pkt->hdr.flags = 0; + pkt->hdr.lcid = lcid; + pkt->hdr.payload_len = 0; + pkt->hdr.pad_len = 0; + smux_tx_queue(pkt, ch, 0); + tx_ready = 1; + } else { + pr_err("%s: pkt allocation failed\n", __func__); + ret = -ENOMEM; + } + } + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + + if (tx_ready) + list_channel(ch); + + return ret; +} + +/** + * Write data to a logical channel. + * + * @lcid Logical channel ID + * @pkt_priv Client data that will be returned with the SMUX_WRITE_DONE or + * SMUX_WRITE_FAIL notification. + * @data Data to write + * @len Length of @data + * + * @returns 0 for success, <0 otherwise + * + * Data may be written immediately after msm_smux_open() is called, + * but the data will wait in the transmit queue until the channel has + * been fully opened. + * + * Once the data has been written, the client will receive either a completion + * (SMUX_WRITE_DONE) or a failure notice (SMUX_WRITE_FAIL). + */ +int msm_smux_write(uint8_t lcid, void *pkt_priv, const void *data, int len) +{ + struct smux_lch_t *ch; + struct smux_pkt_t *pkt; + int tx_ready = 0; + unsigned long flags; + int ret; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + + if (ch->local_state != SMUX_LCH_LOCAL_OPENED && + ch->local_state != SMUX_LCH_LOCAL_OPENING) { + pr_err("%s: hdr.invalid local state %d channel %d\n", + __func__, ch->local_state, lcid); + ret = -EINVAL; + goto out; + } + + if (len > SMUX_MAX_PKT_SIZE - sizeof(struct smux_hdr_t)) { + pr_err("%s: payload %d too large\n", + __func__, len); + ret = -E2BIG; + goto out; + } + + pkt = smux_alloc_pkt(); + if (!pkt) { + ret = -ENOMEM; + goto out; + } + + pkt->hdr.cmd = SMUX_CMD_DATA; + pkt->hdr.lcid = lcid; + pkt->hdr.flags = 0; + pkt->hdr.payload_len = len; + pkt->payload = (void *)data; + pkt->priv = pkt_priv; + pkt->hdr.pad_len = 0; + + spin_lock(&ch->tx_lock_lhb2); + /* verify high watermark */ + SMUX_DBG("%s: pending %d", __func__, ch->tx_pending_data_cnt); + + if (ch->tx_pending_data_cnt >= SMUX_WM_HIGH) { + pr_err("%s: ch %d high watermark %d exceeded %d\n", + __func__, lcid, SMUX_WM_HIGH, + ch->tx_pending_data_cnt); + ret = -EAGAIN; + goto out_inner; + } + + /* queue packet for transmit */ + if (++ch->tx_pending_data_cnt == SMUX_WM_HIGH) { + ch->notify_lwm = 1; + pr_err("%s: high watermark hit\n", __func__); + schedule_notify(lcid, SMUX_HIGH_WM_HIT, NULL); + } + list_add_tail(&pkt->list, &ch->tx_queue); + + /* add to ready list */ + if (IS_FULLY_OPENED(ch)) + tx_ready = 1; + + ret = 0; + +out_inner: + spin_unlock(&ch->tx_lock_lhb2); + +out: + if (ret) + smux_free_pkt(pkt); + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + + if (tx_ready) + list_channel(ch); + + return ret; +} + +/** + * Returns true if the TX queue is currently full (high water mark). + * + * @lcid Logical channel ID + * @returns 0 if channel is not full + * 1 if it is full + * < 0 for error + */ +int msm_smux_is_ch_full(uint8_t lcid) +{ + struct smux_lch_t *ch; + unsigned long flags; + int is_full = 0; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + + spin_lock_irqsave(&ch->tx_lock_lhb2, flags); + if (ch->tx_pending_data_cnt >= SMUX_WM_HIGH) + is_full = 1; + spin_unlock_irqrestore(&ch->tx_lock_lhb2, flags); + + return is_full; +} + +/** + * Returns true if the TX queue has space for more packets it is at or + * below the low water mark). + * + * @lcid Logical channel ID + * @returns 0 if channel is above low watermark + * 1 if it's at or below the low watermark + * < 0 for error + */ +int msm_smux_is_ch_low(uint8_t lcid) +{ + struct smux_lch_t *ch; + unsigned long flags; + int is_low = 0; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + + spin_lock_irqsave(&ch->tx_lock_lhb2, flags); + if (ch->tx_pending_data_cnt <= SMUX_WM_LOW) + is_low = 1; + spin_unlock_irqrestore(&ch->tx_lock_lhb2, flags); + + return is_low; +} + +/** + * Send TIOCM status update. + * + * @ch Channel for update + * + * @returns 0 for success, <0 for failure + * + * Channel lock must be held before calling. + */ +static int smux_send_status_cmd(struct smux_lch_t *ch) +{ + struct smux_pkt_t *pkt; + + if (!ch) + return -EINVAL; + + pkt = smux_alloc_pkt(); + if (!pkt) + return -ENOMEM; + + pkt->hdr.lcid = ch->lcid; + pkt->hdr.cmd = SMUX_CMD_STATUS; + pkt->hdr.flags = ch->local_tiocm; + pkt->hdr.payload_len = 0; + pkt->hdr.pad_len = 0; + smux_tx_queue(pkt, ch, 0); + + return 0; +} + +/** + * Internal helper function for getting the TIOCM status with + * state_lock_lhb1 already locked. + * + * @ch Channel pointer + * + * @returns TIOCM status + */ +static long msm_smux_tiocm_get_atomic(struct smux_lch_t *ch) +{ + long status = 0x0; + + status |= (ch->remote_tiocm & SMUX_CMD_STATUS_RTC) ? TIOCM_DSR : 0; + status |= (ch->remote_tiocm & SMUX_CMD_STATUS_RTR) ? TIOCM_CTS : 0; + status |= (ch->remote_tiocm & SMUX_CMD_STATUS_RI) ? TIOCM_RI : 0; + status |= (ch->remote_tiocm & SMUX_CMD_STATUS_DCD) ? TIOCM_CD : 0; + + status |= (ch->local_tiocm & SMUX_CMD_STATUS_RTC) ? TIOCM_DTR : 0; + status |= (ch->local_tiocm & SMUX_CMD_STATUS_RTR) ? TIOCM_RTS : 0; + + return status; +} + +/** + * Get the TIOCM status bits. + * + * @lcid Logical channel ID + * + * @returns >= 0 TIOCM status bits + * < 0 Error condition + */ +long msm_smux_tiocm_get(uint8_t lcid) +{ + struct smux_lch_t *ch; + unsigned long flags; + long status = 0x0; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + status = msm_smux_tiocm_get_atomic(ch); + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + + return status; +} + +/** + * Set/clear the TIOCM status bits. + * + * @lcid Logical channel ID + * @set Bits to set + * @clear Bits to clear + * + * @returns 0 for success; < 0 for failure + * + * If a bit is specified in both the @set and @clear masks, then the clear bit + * definition will dominate and the bit will be cleared. + */ +int msm_smux_tiocm_set(uint8_t lcid, uint32_t set, uint32_t clear) +{ + struct smux_lch_t *ch; + unsigned long flags; + uint8_t old_status; + uint8_t status_set = 0x0; + uint8_t status_clear = 0x0; + int tx_ready = 0; + int ret = 0; + + if (smux_assert_lch_id(lcid)) + return -ENXIO; + + ch = &smux_lch[lcid]; + spin_lock_irqsave(&ch->state_lock_lhb1, flags); + + status_set |= (set & TIOCM_DTR) ? SMUX_CMD_STATUS_RTC : 0; + status_set |= (set & TIOCM_RTS) ? SMUX_CMD_STATUS_RTR : 0; + status_set |= (set & TIOCM_RI) ? SMUX_CMD_STATUS_RI : 0; + status_set |= (set & TIOCM_CD) ? SMUX_CMD_STATUS_DCD : 0; + + status_clear |= (clear & TIOCM_DTR) ? SMUX_CMD_STATUS_RTC : 0; + status_clear |= (clear & TIOCM_RTS) ? SMUX_CMD_STATUS_RTR : 0; + status_clear |= (clear & TIOCM_RI) ? SMUX_CMD_STATUS_RI : 0; + status_clear |= (clear & TIOCM_CD) ? SMUX_CMD_STATUS_DCD : 0; + + old_status = ch->local_tiocm; + ch->local_tiocm |= status_set; + ch->local_tiocm &= ~status_clear; + + if (ch->local_tiocm != old_status) { + ret = smux_send_status_cmd(ch); + tx_ready = 1; + } + spin_unlock_irqrestore(&ch->state_lock_lhb1, flags); + + if (tx_ready) + list_channel(ch); + + return ret; +} + +/**********************************************************************/ +/* Line Discipline Interface */ +/**********************************************************************/ +static int smuxld_open(struct tty_struct *tty) +{ + int i; + int tmp; + unsigned long flags; + + if (!smux.is_initialized) + return -ENODEV; + + spin_lock_irqsave(&smux.lock_lha0, flags); + if (smux.ld_open_count) { + pr_err("%s: %p multiple instances not supported\n", + __func__, tty); + spin_unlock_irqrestore(&smux.lock_lha0, flags); + return -EEXIST; + } + + ++smux.ld_open_count; + if (tty->ops->write == NULL) { + spin_unlock_irqrestore(&smux.lock_lha0, flags); + return -EINVAL; + } + + /* connect to TTY */ + smux.tty = tty; + tty->disc_data = &smux; + tty->receive_room = TTY_RECEIVE_ROOM; + tty_driver_flush_buffer(tty); + + /* power-down the UART if we are idle */ + spin_lock(&smux.tx_lock_lha2); + if (smux.power_state == SMUX_PWR_OFF) { + SMUX_DBG("%s: powering off uart\n", __func__); + smux.power_state = SMUX_PWR_OFF_FLUSH; + spin_unlock(&smux.tx_lock_lha2); + queue_work(smux_tx_wq, &smux_inactivity_work); + } else { + spin_unlock(&smux.tx_lock_lha2); + } + spin_unlock_irqrestore(&smux.lock_lha0, flags); + + /* register platform devices */ + for (i = 0; i < ARRAY_SIZE(smux_devs); ++i) { + tmp = platform_device_register(&smux_devs[i]); + if (tmp) + pr_err("%s: error %d registering device %s\n", + __func__, tmp, smux_devs[i].name); + } + return 0; +} + +static void smuxld_close(struct tty_struct *tty) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&smux.lock_lha0, flags); + if (smux.ld_open_count <= 0) { + pr_err("%s: invalid ld count %d\n", __func__, + smux.ld_open_count); + spin_unlock_irqrestore(&smux.lock_lha0, flags); + return; + } + spin_unlock_irqrestore(&smux.lock_lha0, flags); + + for (i = 0; i < ARRAY_SIZE(smux_devs); ++i) + platform_device_unregister(&smux_devs[i]); + + --smux.ld_open_count; +} + +/** + * Receive data from TTY Line Discipline. + * + * @tty TTY structure + * @cp Character data + * @fp Flag data + * @count Size of character and flag data + */ +void smuxld_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + int i; + int last_idx = 0; + const char *tty_name = NULL; + char *f; + + if (smux_debug_mask & MSM_SMUX_DEBUG) + print_hex_dump(KERN_INFO, "smux tty rx: ", DUMP_PREFIX_OFFSET, + 16, 1, cp, count, true); + + /* verify error flags */ + for (i = 0, f = fp; i < count; ++i, ++f) { + if (*f != TTY_NORMAL) { + if (tty) + tty_name = tty->name; + pr_err("%s: TTY %s Error %d (%s)\n", __func__, + tty_name, *f, tty_flag_to_str(*f)); + + /* feed all previous valid data to the parser */ + smux_rx_state_machine(cp + last_idx, i - last_idx, + TTY_NORMAL); + + /* feed bad data to parser */ + smux_rx_state_machine(cp + i, 1, *f); + last_idx = i + 1; + } + } + + /* feed data to RX state machine */ + smux_rx_state_machine(cp + last_idx, count - last_idx, TTY_NORMAL); +} + +static void smuxld_flush_buffer(struct tty_struct *tty) +{ + pr_err("%s: not supported\n", __func__); +} + +static ssize_t smuxld_chars_in_buffer(struct tty_struct *tty) +{ + pr_err("%s: not supported\n", __func__); + return -ENODEV; +} + +static ssize_t smuxld_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + pr_err("%s: not supported\n", __func__); + return -ENODEV; +} + +static ssize_t smuxld_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr) +{ + pr_err("%s: not supported\n", __func__); + return -ENODEV; +} + +static int smuxld_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + pr_err("%s: not supported\n", __func__); + return -ENODEV; +} + +static unsigned int smuxld_poll(struct tty_struct *tty, struct file *file, + struct poll_table_struct *tbl) +{ + pr_err("%s: not supported\n", __func__); + return -ENODEV; +} + +static void smuxld_write_wakeup(struct tty_struct *tty) +{ + pr_err("%s: not supported\n", __func__); +} + +static struct tty_ldisc_ops smux_ldisc_ops = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "n_smux", + .open = smuxld_open, + .close = smuxld_close, + .flush_buffer = smuxld_flush_buffer, + .chars_in_buffer = smuxld_chars_in_buffer, + .read = smuxld_read, + .write = smuxld_write, + .ioctl = smuxld_ioctl, + .poll = smuxld_poll, + .receive_buf = smuxld_receive_buf, + .write_wakeup = smuxld_write_wakeup +}; + +static int __init smux_init(void) +{ + int ret; + + spin_lock_init(&smux.lock_lha0); + + spin_lock_init(&smux.rx_lock_lha1); + smux.rx_state = SMUX_RX_IDLE; + smux.power_state = SMUX_PWR_OFF; + smux.pwr_wakeup_delay_us = 1; + smux.powerdown_enabled = 0; + smux.rx_activity_flag = 0; + smux.tx_activity_flag = 0; + smux.recv_len = 0; + smux.tty = NULL; + smux.ld_open_count = 0; + smux.in_reset = 0; + smux.is_initialized = 1; + smux_byte_loopback = 0; + + spin_lock_init(&smux.tx_lock_lha2); + INIT_LIST_HEAD(&smux.lch_tx_ready_list); + + ret = tty_register_ldisc(N_SMUX, &smux_ldisc_ops); + if (ret != 0) { + pr_err("%s: error %d registering line discipline\n", + __func__, ret); + return ret; + } + + ret = lch_init(); + if (ret != 0) { + pr_err("%s: lch_init failed\n", __func__); + return ret; + } + + return 0; +} + +static void __exit smux_exit(void) +{ + int ret; + + ret = tty_unregister_ldisc(N_SMUX); + if (ret != 0) { + pr_err("%s error %d unregistering line discipline\n", + __func__, ret); + return; + } +} + +module_init(smux_init); +module_exit(smux_exit); + +MODULE_DESCRIPTION("Serial Mux TTY Line Discipline"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS_LDISC(N_SMUX); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 070b442c1f81abb08ffee6cd69e3ffe0ab87b209..9274c175c8feb002355f9a320fd36b3edef5b475 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -988,6 +988,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 7257c5d898ae2d36da427c3f6a9e225f1a0741ae..4376e10edd8542114a4dd0bba735160b824d49df 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -56,6 +56,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 8131e2c28015432ea3be3fc41b6206f3102fd98b..72bc8deac77087cc1e263324b9705eb2e1789d84 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-2012, 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 @@ -19,44 +19,85 @@ # 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 #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) { - if (!(msm_read(port, UART_SR) & UART_SR_TX_EMPTY)) - while ((msm_read(port, UART_ISR) & bits) != bits) - cpu_relax(); + __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); +} + +#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); @@ -89,60 +130,122 @@ static void msm_enable_ms(struct uart_port *port) msm_write(port, msm_port->imr, UART_IMR); } -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) +{ + unsigned long flags; + 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_irqsave(&port->lock, flags); - 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; + } + + msm_serial_clock_on(port, 0); - /* TODO: handle sysrq */ - tty_insert_flip_string(tty, (char *) &c, - (count > 4) ? 4 : count); - count -= 4; + /* 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_irqrestore(&port->lock, flags); + return IRQ_HANDLED; } +#endif static void handle_rx(struct uart_port *port) { @@ -192,12 +295,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; @@ -205,18 +302,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 */ @@ -224,17 +314,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); } @@ -248,34 +343,34 @@ static void handle_delta_cts(struct uart_port *port) static irqreturn_t msm_irq(int irq, void *dev_id) { + unsigned long flags; struct uart_port *port = dev_id; struct msm_port *msm_port = UART_TO_MSM(port); unsigned int misr; - spin_lock(&port->lock); + spin_lock_irqsave(&port->lock, flags); 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 */ - spin_unlock(&port->lock); + spin_unlock_irqrestore(&port->lock, flags); return IRQ_HANDLED; } 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; + + ret = (msm_read(port, UART_SR) & UART_SR_TX_EMPTY) ? TIOCSER_TEMT : 0; + return ret; } static unsigned int msm_get_mctrl(struct uart_port *port) @@ -283,21 +378,10 @@ 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; + mr = msm_read(port, UART_MR1); if (!(mctrl & TIOCM_RTS)) { @@ -318,10 +402,9 @@ static void msm_break_ctl(struct uart_port *port, int break_ctl) msm_write(port, UART_CR_CMD_STOP_BREAK, UART_CR); } -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: @@ -371,14 +454,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 */ @@ -393,27 +472,62 @@ 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) { + int ret; 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); + ret = clk_prepare_enable(msm_port->clk); + if (ret) { + pr_err("%s(): Can't enable uartclk. ret:%d\n", __func__, ret); + return; + } + +#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_unprepare(msm_port->clk); +#endif + +} static int msm_startup(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); @@ -428,7 +542,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; @@ -451,31 +573,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; } @@ -486,9 +606,18 @@ static void msm_shutdown(struct uart_port *port) 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, @@ -497,13 +626,14 @@ static void msm_set_termios(struct uart_port *port, struct ktermios *termios, unsigned long flags; unsigned int baud, mr; + if (!termios->c_cflag) + return; + spin_lock_irqsave(&port->lock, flags); /* 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); @@ -562,7 +692,6 @@ static void msm_set_termios(struct uart_port *port, struct ktermios *termios, port->read_status_mask |= UART_SR_RX_BREAK; uart_update_timeout(port, termios->c_cflag, baud); - spin_unlock_irqrestore(&port->lock, flags); } @@ -574,102 +703,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(pdev, - IORESOURCE_MEM, 1); - - 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(pdev, IORESOURCE_MEM, 0); - 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(pdev, IORESOURCE_MEM, 1); - /* 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,21 +759,22 @@ 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) { + int ret; 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); + ret = clk_prepare_enable(msm_port->clk); + if (ret) + pr_err("msm_serial: %s(): Can't enable uartclk.\n", + __func__); break; case 3: - clk_disable(msm_port->clk); - if (!IS_ERR(msm_port->pclk)) - clk_disable(msm_port->pclk); + clk_disable_unprepare(msm_port->clk); break; default: - printk(KERN_ERR "msm_serial: Unknown PM state %d\n", state); + pr_err("msm_serial: %s(): Unknown PM state %d\n", + __func__, state); } } @@ -728,7 +804,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 +813,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 +830,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; - if (msm_port->is_uartdm) - reset_dm_count(port); + /* Wait up to 10ms for the character(s) to be sent. */ + do { + status = msm_read(port, UART_SR); - while (!(msm_read(port, UART_SR) & UART_SR_TX_READY)) - ; - msm_write(port, c, msm_port->is_uartdm ? UARTDM_TF : UART_TF); + if (--tmout == 0) + break; + udelay(1); + } while ((status & bits) != bits); + + 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,33 +887,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; + int baud = 0, 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); @@ -821,11 +945,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); @@ -843,7 +962,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 @@ -857,17 +976,15 @@ static struct uart_driver msm_uart_driver = { .cons = MSM_CONSOLE, }; -static atomic_t msm_uart_next_id = ATOMIC_INIT(0); - static int __init msm_serial_probe(struct platform_device *pdev) { struct msm_port *msm_port; struct resource *resource; struct uart_port *port; int irq; - - if (pdev->id == -1) - pdev->id = atomic_inc_return(&msm_uart_next_id) - 1; +#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; @@ -878,29 +995,12 @@ static int __init msm_serial_probe(struct platform_device *pdev) port->dev = &pdev->dev; msm_port = UART_TO_MSM(port); - if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) - 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, "core_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(pdev, IORESOURCE_MEM, 0); if (unlikely(!resource)) @@ -914,6 +1014,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); } @@ -921,14 +1044,76 @@ 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; } -static struct of_device_id msm_match_table[] = { - { .compatible = "qcom,msm-uart" }, - {} +#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 = { @@ -936,7 +1121,7 @@ static struct platform_driver msm_platform_driver = { .driver = { .name = "msm_serial", .owner = THIS_MODULE, - .of_match_table = msm_match_table, + .pm = &msm_serial_dev_pm_ops, }, }; @@ -971,4 +1156,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 e4acef5de77ef82f01d45add56d8646d8ba2f50d..65d0e30cd79a98a9aeb1e803503dc4e68246e3ef 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 0000000000000000000000000000000000000000..88b67840852434cdeb89d83fe6f7c1bc69d3125b --- /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 fca13dc73e2302e19aee0e8773b6fa93a004a7c8..21b96698d9eec894b2f3cf5fee57ca4da186cb1b 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-2012, 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. @@ -30,8 +34,6 @@ #include #include -#include -#include #include #include #include @@ -45,163 +47,26 @@ #include #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, @@ -211,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 */ @@ -228,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; @@ -256,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; @@ -285,127 +115,281 @@ 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 */ + + struct dentry *loopback_dir; + struct work_struct clock_off_w; /* work for actual clock off */ + struct workqueue_struct *hsuart_wq; /* hsuart workqueue */ + struct mutex clk_mutex; /* mutex to guard against clock off/clock on */ }; #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 snprintf(buf, PAGE_SIZE, "%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 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 unsigned int msm_hs_read(struct uart_port *uport, +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"); + if (unlikely(!gsbi_resource)) + return; + + size = resource_size(gsbi_resource); + 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 = resource_size(gsbi_resource); + 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_prepare_enable(msm_uport->clk); + if (msm_uport->pclk) + clk_prepare_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_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(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_prepare_enable(msm_uport->clk); + if (msm_uport->pclk) + clk_prepare_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_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(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 __devinit 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); + msm_uport->loopback_dir = debugfs_create_file(node_name, + S_IRUGO | S_IWUSR, + debug_base, + msm_uport, + &loopback_enable_fops); + + if (IS_ERR_OR_NULL(msm_uport->loopback_dir)) + pr_err("%s(): Cannot create loopback.%d debug entry", + __func__, id); +} + 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); @@ -415,6 +399,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(msm_uport->loopback_dir); + 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, @@ -428,8 +419,15 @@ 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); + destroy_workqueue(msm_uport->hsuart_wq); + mutex_destroy(&msm_uport->clk_mutex); + uart_remove_one_port(&msm_hs_driver, &msm_uport->uport); clk_put(msm_uport->clk); + if (msm_uport->pclk) + clk_put(msm_uport->pclk); /* Free the tx resources */ kfree(msm_uport->tx.command_ptr); @@ -444,64 +442,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); + /* 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_prepare_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_prepare_enable(msm_uport->pclk); + if (ret) { + clk_disable_unprepare(msm_uport->clk); + 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; @@ -509,63 +491,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: @@ -578,21 +560,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; @@ -601,6 +590,63 @@ static void msm_hs_set_bps_locked(struct uart_port *uport, data = rxstale & UARTDM_IPR_STALE_LSB_BMSK; data |= UARTDM_IPR_STALE_TIMEOUT_MSB_BMSK & (rxstale << 2); + msm_hs_write(uport, UARTDM_IPR_ADDR, data); + /* + * It is suggested to do reset of transmitter and receiver after + * changing any protocol configuration. Here Baud rate and stale + * timeout are getting updated. Hence reset transmitter and receiver. + */ + msm_hs_write(uport, UARTDM_CR_ADDR, RESET_TX); + msm_hs_write(uport, UARTDM_CR_ADDR, RESET_RX); +} + + +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); } @@ -611,8 +657,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; @@ -621,7 +667,19 @@ static void msm_hs_set_termios(struct uart_port *uport, struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); spin_lock_irqsave(&uport->lock, flags); - clk_enable(msm_uport->clk); + + /* + * Disable Rx channel of UARTDM + * DMA Rx Stall happens if enqueue and flush of Rx command happens + * concurrently. Hence before changing the baud rate/protocol + * configuration and sending flush command to ADM, disable the Rx + * channel of UARTDM. + * Note: should not reset the receiver here immediately as it is not + * suggested to do disable/reset or reset/disable at the same time. + */ + data = msm_hs_read(uport, UARTDM_DMEN_ADDR); + data &= ~UARTDM_RX_DM_EN_BMSK; + msm_hs_write(uport, UARTDM_DMEN_ADDR, data); /* 300 is the minimum baud support by the driver */ bps = uart_get_baud_rate(uport, termios, oldtermios, 200, 4000000); @@ -630,18 +688,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 */ @@ -697,13 +760,20 @@ 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_flush(msm_uport->dma_rx_channel, 0); } msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); - - clk_disable(msm_uport->clk); + mb(); spin_unlock_irqrestore(&uport->lock, flags); } @@ -711,22 +781,18 @@ 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; - struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - - clk_enable(msm_uport->clk); data = msm_hs_read(uport, UARTDM_SR_ADDR); if (data & UARTDM_SR_TXEMT_BMSK) ret = TIOCSER_TEMT; - clk_disable(msm_uport->clk); - return ret; } +EXPORT_SYMBOL(msm_hs_tx_empty); /* * Standard API, Stop transmitter. @@ -753,21 +819,21 @@ static void msm_hs_stop_rx_locked(struct uart_port *uport) struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); unsigned int data; - clk_enable(msm_uport->clk); - /* disable dlink */ data = msm_hs_read(uport, UARTDM_DMEN_ADDR); 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_flush(msm_uport->dma_rx_channel, 0); + } if (msm_uport->rx.flush != FLUSH_SHUTDOWN) msm_uport->rx.flush = FLUSH_STOP; - - clk_disable(msm_uport->clk); } /* Transmit the next chunk of data */ @@ -775,7 +841,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; @@ -798,8 +866,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); @@ -810,9 +883,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); @@ -820,6 +890,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); } @@ -827,106 +903,119 @@ 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; + unsigned int data; + + 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; + + /* + * Enable UARTDM Rx Interface as previously it has been + * disable in set_termios before configuring baud rate. + */ + data = msm_hs_read(uport, UARTDM_DMEN_ADDR); + data |= UARTDM_RX_DM_EN_BMSK; + msm_hs_write(uport, UARTDM_DMEN_ADDR, data); 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); 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; } @@ -938,50 +1027,133 @@ 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) + msm_hs_write(uport, UARTDM_CR_ADDR, RESET_ERROR_STATUS); + + 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) + 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); + if (retval != rx_count) { + msm_uport->rx.buffer_pending |= CHARS_NORMAL | + retval << 5 | (rx_count - retval) << 16; + } + } + + /* 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)); + } + /* 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) + tty_flip_buffer_push(tty); +} + +/* 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); + + 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); } +} - if (error_f) - msm_hs_write(uport, UARTDM_CR_ADDR, RESET_ERROR_STATUS); - - if (msm_uport->clk_req_off_state == CLK_REQ_OFF_FLUSH_ISSUED) - msm_uport->clk_req_off_state = CLK_REQ_OFF_RXSTALE_FLUSHED; +/* + * 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; - flush = msm_uport->rx.flush; - if (flush == FLUSH_IGNORE) - msm_hs_start_rx_locked(uport); - if (flush == FLUSH_STOP) - msm_uport->rx.flush = FLUSH_SHUTDOWN; - if (flush >= FLUSH_DATA_INVALID) - goto out; + WARN_ON(result != 0x80000002); /* DMA did not finish properly */ - rx_count = msm_hs_read(uport, UARTDM_RX_TOTAL_SNAP_ADDR); + msm_uport = container_of(cmd_ptr, struct msm_hs_port, tx.xfer); - if (0 != (uport->read_status_mask & CREAD)) { - retval = tty_insert_flip_string(tty, msm_uport->rx.buffer, - rx_count); - BUG_ON(retval != rx_count); - } + tasklet_schedule(&msm_uport->tx.tlet); +} - msm_hs_start_rx_locked(uport); +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); -out: - clk_disable(msm_uport->clk); + spin_lock_irqsave(&(msm_uport->uport.lock), flags); - spin_unlock_irqrestore(&uport->lock, flags); + 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(); - if (flush < FLUSH_DATA_INVALID) - queue_work(msm_hs_workqueue, &msm_uport->rx.tty_work); + spin_unlock_irqrestore(&(msm_uport->uport.lock), flags); } -static void msm_hs_tty_flip_buffer_work(struct work_struct *work) +/* + * 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 = - 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; - tty_flip_buffer_push(tty); + msm_uport = container_of(cmd_ptr, struct msm_hs_port, rx.xfer); + + tasklet_schedule(&msm_uport->rx.tlet); } /* @@ -1003,58 +1175,57 @@ 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; - data = msm_hs_read(uport, UARTDM_MR1_ADDR); + /* RTS is active low */ + set_rts = TIOCM_RTS & mctrl ? 0 : 1; - 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 */ + 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); } + mb(); } -/* - * 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) { struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - clk_enable(msm_uport->clk); - /* Enable DELTA_CTS Interrupt */ msm_uport->imr_reg |= UARTDM_ISR_DELTA_CTS_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); - - clk_disable(msm_uport->clk); + mb(); } @@ -1066,38 +1237,45 @@ static void msm_hs_enable_ms_locked(struct uart_port *uport) */ static void msm_hs_break_ctl(struct uart_port *uport, int ctl) { - struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); + unsigned long flags; - clk_enable(msm_uport->clk); + spin_lock_irqsave(&uport->lock, flags); msm_hs_write(uport, UARTDM_CR_ADDR, ctl ? START_BREAK : STOP_BREAK); - clk_disable(msm_uport->clk); + mb(); + 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); } - spin_unlock_irqrestore(&uport->lock, flags); + + if (is_gsbi_uart(msm_uport)) { + if (msm_uport->pclk) + clk_prepare_enable(msm_uport->pclk); + spin_lock_irqsave(&uport->lock, flags); + iowrite32(GSBI_PROTOCOL_UART, msm_uport->mapped_gsbi + + GSBI_CONTROL_ADDR); + spin_unlock_irqrestore(&uport->lock, flags); + if (msm_uport->pclk) + clk_disable_unprepare(msm_uport->pclk); + } } /* Handle CTS changes (Called from interrupt handler) */ static void msm_hs_handle_delta_cts_locked(struct uart_port *uport) { - struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - - 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); - /* clear the IOCTL TIOCMIWAIT if called */ wake_up_interruptible(&uport->state->port.delta_msr_wait); } @@ -1107,34 +1285,51 @@ static void msm_hs_handle_delta_cts_locked(struct uart_port *uport) * -1 did not clock off, do not retry * 1 if we clocked off */ -static int msm_hs_check_clock_off_locked(struct uart_port *uport) +static int msm_hs_check_clock_off(struct uart_port *uport) { unsigned long sr_status; + unsigned long flags; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); struct circ_buf *tx_buf = &uport->state->xmit; + mutex_lock(&msm_uport->clk_mutex); + spin_lock_irqsave(&uport->lock, flags); + /* 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) { + spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); return -1; } /* Make sure the uart is finished with the last byte */ sr_status = msm_hs_read(uport, UARTDM_SR_ADDR); - if (!(sr_status & UARTDM_SR_TXEMT_BMSK)) + if (!(sr_status & UARTDM_SR_TXEMT_BMSK)) { + spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); return 0; /* retry */ + } /* Make sure forced RXSTALE flush complete */ switch (msm_uport->clk_req_off_state) { 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(); + spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); return 0; /* RXSTALE flush not complete - retry */ case CLK_REQ_OFF_RXSTALE_ISSUED: case CLK_REQ_OFF_FLUSH_ISSUED: + spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); return 0; /* RXSTALE flush not complete - retry */ case CLK_REQ_OFF_RXSTALE_FLUSHED: break; /* continue */ @@ -1143,45 +1338,60 @@ static int msm_hs_check_clock_off_locked(struct uart_port *uport) if (msm_uport->rx.flush != FLUSH_SHUTDOWN) { if (msm_uport->rx.flush == FLUSH_NONE) msm_hs_stop_rx_locked(uport); + + spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); return 0; /* come back later to really clock off */ } + spin_unlock_irqrestore(&uport->lock, flags); + /* we really want to clock off */ - clk_disable(msm_uport->clk); + clk_disable_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(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); + spin_lock_irqsave(&uport->lock, flags); + 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); + + spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); return 1; } -static enum hrtimer_restart msm_hs_clk_off_retry(struct hrtimer *timer) +static void hsuart_clock_off_work(struct work_struct *w) { - unsigned long flags; - int ret = HRTIMER_NORESTART; - struct msm_hs_port *msm_uport = container_of(timer, struct msm_hs_port, - clk_off_timer); + struct msm_hs_port *msm_uport = container_of(w, struct msm_hs_port, + clock_off_w); struct uart_port *uport = &msm_uport->uport; - spin_lock_irqsave(&uport->lock, flags); - - if (!msm_hs_check_clock_off_locked(uport)) { - hrtimer_forward_now(timer, msm_uport->clk_off_delay); - ret = HRTIMER_RESTART; + if (!msm_hs_check_clock_off(uport)) { + hrtimer_start(&msm_uport->clk_off_timer, + msm_uport->clk_off_delay, + HRTIMER_MODE_REL); } +} - spin_unlock_irqrestore(&uport->lock, flags); +static enum hrtimer_restart msm_hs_clk_off_retry(struct hrtimer *timer) +{ + struct msm_hs_port *msm_uport = container_of(timer, struct msm_hs_port, + clk_off_timer); - return ret; + queue_work(msm_uport->hsuart_wq, &msm_uport->clock_off_w); + return HRTIMER_NORESTART; } 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; @@ -1193,20 +1403,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, 1); } } /* tx ready interrupt */ @@ -1219,7 +1438,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; @@ -1236,10 +1459,12 @@ 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); - if (!msm_hs_check_clock_off_locked(uport)) - hrtimer_start(&msm_uport->clk_off_timer, - msm_uport->clk_off_delay, - HRTIMER_MODE_REL); + /* + * Complete device write before starting clock_off request. + * Hence mb() requires here. + */ + mb(); + queue_work(msm_uport->hsuart_wq, &msm_uport->clock_off_w); } /* Change in CTS interrupt */ @@ -1251,51 +1476,60 @@ static irqreturn_t msm_hs_isr(int irq, void *dev) 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) +void msm_hs_request_clock_on(struct uart_port *uport) { struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); + unsigned long flags; unsigned int data; + int ret = 0; + + mutex_lock(&msm_uport->clk_mutex); + spin_lock_irqsave(&uport->lock, flags); switch (msm_uport->clk_state) { case MSM_HS_CLK_OFF: - clk_enable(msm_uport->clk); - disable_irq_nosync(msm_uport->rx_wakeup.irq); - /* fall-through */ + wake_lock(&msm_uport->dma_wake_lock); + disable_irq_nosync(msm_uport->wakeup.irq); + spin_unlock_irqrestore(&uport->lock, flags); + ret = clk_prepare_enable(msm_uport->clk); + if (ret) { + dev_err(uport->dev, "Clock ON Failure" + "For UART CLK Stalling HSUART\n"); + break; + } + + if (msm_uport->pclk) { + ret = clk_prepare_enable(msm_uport->pclk); + if (unlikely(ret)) { + clk_disable_unprepare(msm_uport->clk); + dev_err(uport->dev, "Clock ON Failure" + "For UART Pclk Stalling HSUART\n"); + break; + } + } + spin_lock_irqsave(&uport->lock, flags); + /* else fall-through */ case MSM_HS_CLK_REQUEST_OFF: if (msm_uport->rx.flush == FLUSH_STOP || msm_uport->rx.flush == FLUSH_SHUTDOWN) { @@ -1303,12 +1537,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; @@ -1318,40 +1552,26 @@ void msm_hs_request_clock_on_locked(struct uart_port *uport) case MSM_HS_CLK_PORT_OFF: break; } -} - -/** - * 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) -{ - unsigned long flags; - spin_lock_irqsave(&uport->lock, flags); - msm_hs_request_clock_on_locked(uport); spin_unlock_irqrestore(&uport->lock, flags); + mutex_unlock(&msm_uport->clk_mutex); } +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; } @@ -1359,24 +1579,27 @@ static irqreturn_t msm_hs_rx_wakeup_isr(int irq, void *dev) if (wakeup) { /* 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) { + spin_unlock_irqrestore(&uport->lock, flags); + msm_hs_request_clock_on(uport); + spin_lock_irqsave(&uport->lock, flags); + 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 */ @@ -1389,7 +1612,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) @@ -1398,15 +1620,13 @@ 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; - + wake_lock(&msm_uport->dma_wake_lock); /* turn on uart clk */ - ret = msm_hs_init_clk_locked(uport); + ret = msm_hs_init_clk(uport); if (unlikely(ret)) { - printk(KERN_ERR "Turning uartclk failed!\n"); - goto err_msm_hs_init_clk; + pr_err("Turning ON uartclk error\n"); + wake_unlock(&msm_uport->dma_wake_lock); + return ret; } /* Set auto RFR Level */ @@ -1447,7 +1667,6 @@ 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->command_ptr->cmd = CMD_LC | CMD_DST_CRCI(msm_uport->dma_tx_crci) | CMD_MODE_BOX; @@ -1460,49 +1679,47 @@ 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)) { + pr_err("%s():Err setting wakeup irq\n", __func__); + goto deinit_uart_clk; + } + } 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, - IRQF_TRIGGER_FALLING, - "msm_hs_rx_wakeup", msm_uport); + pr_err("%s():Error getting uart irq\n", __func__); + goto free_wake_irq; + } + if (use_low_power_wakeup(msm_uport)) { + + ret = request_threaded_irq(msm_uport->wakeup.irq, NULL, + msm_hs_wakeup_isr, + IRQF_TRIGGER_FALLING, + "msm_hs_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; + pr_err("%s():Err getting uart wakeup_irq\n", __func__); + goto free_uart_irq; } - disable_irq(msm_uport->rx_wakeup.irq); + 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,15 +1730,21 @@ static int msm_hs_startup(struct uart_port *uport) return 0; -err_request_irq: -err_msm_hs_init_clk: - dma_unmap_single(uport->dev, tx->dma_base, - UART_XMIT_SIZE, DMA_TO_DEVICE); +free_uart_irq: + free_irq(uport->irq, msm_uport); +free_wake_irq: + irq_set_irq_wake(msm_uport->wakeup.irq, 0); +deinit_uart_clk: + clk_disable_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(msm_uport->pclk); + wake_unlock(&msm_uport->dma_wake_lock); + return ret; } /* 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); @@ -1536,7 +1759,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, @@ -1547,20 +1770,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 */ @@ -1568,14 +1799,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) | @@ -1583,6 +1814,19 @@ 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->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); @@ -1592,24 +1836,32 @@ 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; } @@ -1620,8 +1872,7 @@ static int __devinit msm_hs_probe(struct platform_device *pdev) 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); @@ -1636,40 +1887,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->mapbase = resource->start; - uport->irq = platform_get_irq(pdev, 0); - if (unlikely(uport->irq < 0)) - return -ENXIO; + uport->membase = ioremap(uport->mapbase, PAGE_SIZE); + if (unlikely(!uport->membase)) + return -ENOMEM; - if (unlikely(irq_set_irq_wake(uport->irq, 1))) + uport->irq = platform_get_irq(pdev, 0); + if (unlikely((int)uport->irq < 0)) 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; @@ -1677,23 +1925,70 @@ 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"); + + msm_uport->clk = clk_get(&pdev->dev, "core_clk"); if (IS_ERR(msm_uport->clk)) return PTR_ERR(msm_uport->clk); + msm_uport->pclk = clk_get(&pdev->dev, "iface_clk"); + /* + * 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; + } + + msm_uport->hsuart_wq = alloc_workqueue("k_hsuart", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!msm_uport->hsuart_wq) { + pr_err("%s(): Unable to create workqueue hsuart_wq\n", + __func__); + return -ENOMEM; + } + + INIT_WORK(&msm_uport->clock_off_w, hsuart_clock_off_work); + mutex_init(&msm_uport->clk_mutex); + + clk_prepare_enable(msm_uport->clk); + if (msm_uport->pclk) + clk_prepare_enable(msm_uport->pclk); + ret = uartdm_init_port(uport); - if (unlikely(ret)) + if (unlikely(ret)) { + clk_disable_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(msm_uport->pclk); return ret; + } + + /* configure the CR Protection to Enable */ + msm_hs_write(uport, UARTDM_CR_ADDR, CR_PROTECTION_EN); + + clk_disable_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(msm_uport->pclk); + + /* + * 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, @@ -1701,43 +1996,45 @@ static int __devinit msm_hs_probe(struct platform_device *pdev) 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); 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. @@ -1746,56 +2043,63 @@ module_init(msm_serial_hs_init); */ static void msm_hs_shutdown(struct uart_port *uport) { - unsigned long flags; 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); + flush_workqueue(msm_uport->hsuart_wq); + pm_runtime_disable(uport->dev); + pm_runtime_set_suspended(uport->dev); + mutex_lock(&msm_uport->clk_mutex); /* 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); - - clk_disable(msm_uport->clk); /* to balance local clk_enable() */ - if (msm_uport->clk_state != MSM_HS_CLK_OFF) - clk_disable(msm_uport->clk); /* to balance clk_state */ + /* + * Complete all device write before actually disabling uartclk. + * Hence mb() requires here. + */ + mb(); + + if (msm_uport->clk_state != MSM_HS_CLK_OFF) { + /* to balance clk_state */ + clk_disable_unprepare(msm_uport->clk); + if (msm_uport->pclk) + clk_disable_unprepare(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, UART_XMIT_SIZE, DMA_TO_DEVICE); - spin_unlock_irqrestore(&uport->lock, flags); + if (use_low_power_wakeup(msm_uport)) + irq_set_irq_wake(msm_uport->wakeup.irq, 0); - if (cancel_work_sync(&msm_uport->rx.tty_work)) - msm_hs_tty_flip_buffer_work(&msm_uport->rx.tty_work); + /* Free the interrupt */ + free_irq(uport->irq, msm_uport); + if (use_low_power_wakeup(msm_uport)) + free_irq(msm_uport->wakeup.irq, msm_uport); + mutex_unlock(&msm_uport->clk_mutex); + mutex_destroy(&msm_uport->clk_mutex); } 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"); + debugfs_remove_recursive(debug_base); 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) { /* @@ -1810,7 +2114,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; } @@ -1820,15 +2123,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, @@ -1837,11 +2134,10 @@ static const struct dev_pm_ops msm_hs_dev_pm_ops = { }; static struct platform_driver msm_serial_hs_platform_driver = { - .probe = msm_hs_probe, + .probe = msm_hs_probe, .remove = __devexit_p(msm_hs_remove), .driver = { .name = "msm_serial_hs", - .owner = THIS_MODULE, .pm = &msm_hs_dev_pm_ops, }, }; @@ -1866,13 +2162,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 0000000000000000000000000000000000000000..81f3d540647ab51bdc86bdc325866d49c9f6b49b --- /dev/null +++ b/drivers/tty/serial/msm_serial_hs_hwreg.h @@ -0,0 +1,207 @@ +/* drivers/serial/msm_serial_hs_hwreg.h + * + * Copyright (c) 2007-2009, 2012, 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 + +enum msm_hsl_regs { + UARTDM_MR1, + UARTDM_MR2, + UARTDM_IMR, + UARTDM_SR, + UARTDM_CR, + UARTDM_CSR, + UARTDM_IPR, + UARTDM_ISR, + UARTDM_RX_TOTAL_SNAP, + UARTDM_RFWR, + UARTDM_TFWR, + UARTDM_RF, + UARTDM_TF, + UARTDM_MISR, + UARTDM_DMRX, + UARTDM_NCF_TX, + UARTDM_DMEN, + UARTDM_BCR, + UARTDM_TXFS, + UARTDM_RXFS, + UARTDM_LAST, +}; + +#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_TXFS_ADDR 0x4C +#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_RX_ZERO_CHAR_OFF 0x100 +#define UARTDM_MR2_RX_ERROR_CHAR_OFF 0x200 +#define UARTDM_MR2_RX_BREAK_ZERO_CHAR_OFF 0x100 + +#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 0000000000000000000000000000000000000000..59104ed28ad684a68b4c7a7be60767399d8c6999 --- /dev/null +++ b/drivers/tty/serial/msm_serial_hs_lite.c @@ -0,0 +1,1572 @@ +/* + * drivers/serial/msm_serial.c - driver for msm7k serial device and console + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2010-2012, 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 +#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; + struct dentry *loopback_dir; + unsigned int imr; + unsigned int *uart_csr_code; + unsigned int *gsbi_mapbase; + unsigned int *mapped_gsbi; + int is_uartdm; + unsigned int old_snap_state; + unsigned int ver_id; + int tx_timeout; +}; + +#define UARTDM_VERSION_11_13 0 +#define UARTDM_VERSION_14 1 + +#define UART_TO_MSM(uart_port) ((struct msm_hsl_port *) uart_port) +#define is_console(port) ((port)->cons && \ + (port)->cons->index == (port)->line) + +static const unsigned int regmap[][UARTDM_LAST] = { + [UARTDM_VERSION_11_13] = { + [UARTDM_MR1] = UARTDM_MR1_ADDR, + [UARTDM_MR2] = UARTDM_MR2_ADDR, + [UARTDM_IMR] = UARTDM_IMR_ADDR, + [UARTDM_SR] = UARTDM_SR_ADDR, + [UARTDM_CR] = UARTDM_CR_ADDR, + [UARTDM_CSR] = UARTDM_CSR_ADDR, + [UARTDM_IPR] = UARTDM_IPR_ADDR, + [UARTDM_ISR] = UARTDM_ISR_ADDR, + [UARTDM_RX_TOTAL_SNAP] = UARTDM_RX_TOTAL_SNAP_ADDR, + [UARTDM_TFWR] = UARTDM_TFWR_ADDR, + [UARTDM_RFWR] = UARTDM_RFWR_ADDR, + [UARTDM_RF] = UARTDM_RF_ADDR, + [UARTDM_TF] = UARTDM_TF_ADDR, + [UARTDM_MISR] = UARTDM_MISR_ADDR, + [UARTDM_DMRX] = UARTDM_DMRX_ADDR, + [UARTDM_NCF_TX] = UARTDM_NCF_TX_ADDR, + [UARTDM_DMEN] = UARTDM_DMEN_ADDR, + [UARTDM_TXFS] = UARTDM_TXFS_ADDR, + [UARTDM_RXFS] = UARTDM_RXFS_ADDR, + }, + [UARTDM_VERSION_14] = { + [UARTDM_MR1] = 0x0, + [UARTDM_MR2] = 0x4, + [UARTDM_IMR] = 0xb0, + [UARTDM_SR] = 0xa4, + [UARTDM_CR] = 0xa8, + [UARTDM_CSR] = 0xa0, + [UARTDM_IPR] = 0x18, + [UARTDM_ISR] = 0xb4, + [UARTDM_RX_TOTAL_SNAP] = 0xbc, + [UARTDM_TFWR] = 0x1c, + [UARTDM_RFWR] = 0x20, + [UARTDM_RF] = 0x140, + [UARTDM_TF] = 0x100, + [UARTDM_MISR] = 0xac, + [UARTDM_DMRX] = 0x34, + [UARTDM_NCF_TX] = 0x40, + [UARTDM_DMEN] = 0x3c, + [UARTDM_TXFS] = 0x4c, + [UARTDM_RXFS] = 0x50, + }, +}; + +static struct of_device_id msm_hsl_match_table[] = { + { .compatible = "qcom,msm-lsuart-v14", + .data = (void *)UARTDM_VERSION_14 + }, + {} +}; +static struct dentry *debug_base; +static inline void wait_for_xmitr(struct uart_port *port); +static int get_console_state(struct uart_port *port); +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 get_line(struct platform_device *pdev) +{ + const struct msm_serial_hslite_platform_data *pdata = + pdev->dev.platform_data; + if (pdata) + return pdata->line; + + return pdev->id; +} + +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_prepare_enable(msm_hsl_port->clk); + if (ret) + goto err; + if (msm_hsl_port->pclk) { + ret = clk_prepare_enable(msm_hsl_port->pclk); + if (ret) { + clk_disable_unprepare(msm_hsl_port->clk); + goto err; + } + } + } else { + + clk_disable_unprepare(msm_hsl_port->clk); + if (msm_hsl_port->pclk) + clk_disable_unprepare(msm_hsl_port->pclk); + } +err: + return ret; +} +static int msm_hsl_loopback_enable_set(void *data, u64 val) +{ + struct msm_hsl_port *msm_hsl_port = data; + struct uart_port *port = &(msm_hsl_port->uart); + unsigned int vid; + unsigned long flags; + int ret = 0; + + ret = clk_set_rate(msm_hsl_port->clk, 7372800); + if (!ret) + clk_en(port, 1); + else { + pr_err("%s(): Error: Setting the clock rate\n", __func__); + return -EINVAL; + } + + vid = msm_hsl_port->ver_id; + if (val) { + spin_lock_irqsave(&port->lock, flags); + ret = msm_hsl_read(port, regmap[vid][UARTDM_MR2]); + ret |= UARTDM_MR2_LOOP_MODE_BMSK; + msm_hsl_write(port, ret, regmap[vid][UARTDM_MR2]); + spin_unlock_irqrestore(&port->lock, flags); + } else { + spin_lock_irqsave(&port->lock, flags); + ret = msm_hsl_read(port, regmap[vid][UARTDM_MR2]); + ret &= ~UARTDM_MR2_LOOP_MODE_BMSK; + msm_hsl_write(port, ret, regmap[vid][UARTDM_MR2]); + spin_unlock_irqrestore(&port->lock, flags); + } + + clk_en(port, 0); + return 0; +} +static int msm_hsl_loopback_enable_get(void *data, u64 *val) +{ + struct msm_hsl_port *msm_hsl_port = data; + struct uart_port *port = &(msm_hsl_port->uart); + unsigned long flags; + int ret = 0; + + ret = clk_set_rate(msm_hsl_port->clk, 7372800); + if (!ret) + clk_en(port, 1); + else { + pr_err("%s(): Error setting clk rate\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&port->lock, flags); + ret = msm_hsl_read(port, regmap[msm_hsl_port->ver_id][UARTDM_MR2]); + spin_unlock_irqrestore(&port->lock, flags); + clk_en(port, 0); + + *val = (ret & UARTDM_MR2_LOOP_MODE_BMSK) ? 1 : 0; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(loopback_enable_fops, msm_hsl_loopback_enable_get, + msm_hsl_loopback_enable_set, "%llu\n"); +/* + * msm_serial_hsl debugfs node: /msm_serial_hsl/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 msm_hsl_debugfs_init(struct msm_hsl_port *msm_uport, + int id) +{ + char node_name[15]; + + snprintf(node_name, sizeof(node_name), "loopback.%d", id); + msm_uport->loopback_dir = debugfs_create_file(node_name, + S_IRUGO | S_IWUSR, + debug_base, + msm_uport, + &loopback_enable_fops); + + if (IS_ERR_OR_NULL(msm_uport->loopback_dir)) + pr_err("%s(): Cannot create loopback.%d debug entry", + __func__, id); +} +static void msm_hsl_stop_tx(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + msm_hsl_port->imr &= ~UARTDM_ISR_TXLEV_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, + regmap[msm_hsl_port->ver_id][UARTDM_IMR]); +} + +static void msm_hsl_start_tx(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + msm_hsl_port->imr |= UARTDM_ISR_TXLEV_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, + regmap[msm_hsl_port->ver_id][UARTDM_IMR]); +} + +static void msm_hsl_stop_rx(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + msm_hsl_port->imr &= ~(UARTDM_ISR_RXLEV_BMSK | + UARTDM_ISR_RXSTALE_BMSK); + msm_hsl_write(port, msm_hsl_port->imr, + regmap[msm_hsl_port->ver_id][UARTDM_IMR]); +} + +static void msm_hsl_enable_ms(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + msm_hsl_port->imr |= UARTDM_ISR_DELTA_CTS_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, + regmap[msm_hsl_port->ver_id][UARTDM_IMR]); +} + +static void handle_rx(struct uart_port *port, unsigned int misr) +{ + struct tty_struct *tty = port->state->port.tty; + unsigned int vid; + unsigned int sr; + int count = 0; + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + vid = msm_hsl_port->ver_id; + /* + * 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, regmap[vid][UARTDM_SR]) & + UARTDM_SR_OVERRUN_BMSK)) { + port->icount.overrun++; + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + msm_hsl_write(port, RESET_ERROR_STATUS, + regmap[vid][UARTDM_CR]); + } + + if (misr & UARTDM_ISR_RXSTALE_BMSK) { + count = msm_hsl_read(port, + regmap[vid][UARTDM_RX_TOTAL_SNAP]) - + msm_hsl_port->old_snap_state; + msm_hsl_port->old_snap_state = 0; + } else { + count = 4 * (msm_hsl_read(port, regmap[vid][UARTDM_RFWR])); + 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, regmap[vid][UARTDM_SR]); + if ((sr & UARTDM_SR_RXRDY_BMSK) == 0) { + msm_hsl_port->old_snap_state -= count; + break; + } + c = msm_hsl_read(port, regmap[vid][UARTDM_RF]); + 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; + unsigned int vid; + + vid = UART_TO_MSM(port)->ver_id; + 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); + msm_hsl_write(port, tx_count + 1, regmap[vid][UARTDM_NCF_TX]); + msm_hsl_read(port, regmap[vid][UARTDM_NCF_TX]); + msm_hsl_write(port, port->x_char, regmap[vid][UARTDM_TF]); + port->icount.tx++; + port->x_char = 0; + } else if (tx_count) { + wait_for_xmitr(port); + msm_hsl_write(port, tx_count, regmap[vid][UARTDM_NCF_TX]); + msm_hsl_read(port, regmap[vid][UARTDM_NCF_TX]); + } + if (!tx_count) { + msm_hsl_stop_tx(port); + return; + } + + while (tf_pointer < tx_count) { + if (unlikely(!(msm_hsl_read(port, regmap[vid][UARTDM_SR]) & + 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, regmap[vid][UARTDM_TF]); + 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) +{ + unsigned int vid = UART_TO_MSM(port)->ver_id; + + msm_hsl_write(port, RESET_CTS, regmap[vid][UARTDM_CR]); + 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 vid; + unsigned int misr; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + vid = msm_hsl_port->ver_id; + misr = msm_hsl_read(port, regmap[vid][UARTDM_MISR]); + /* disable interrupt */ + msm_hsl_write(port, 0, regmap[vid][UARTDM_IMR]); + + 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, + regmap[vid][UARTDM_CR]); + msm_hsl_write(port, 6500, regmap[vid][UARTDM_DMRX]); + msm_hsl_write(port, STALE_EVENT_ENABLE, regmap[vid][UARTDM_CR]); + } + 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, regmap[vid][UARTDM_IMR]); + spin_unlock_irqrestore(&port->lock, flags); + + return IRQ_HANDLED; +} + +static unsigned int msm_hsl_tx_empty(struct uart_port *port) +{ + unsigned int ret; + unsigned int vid = UART_TO_MSM(port)->ver_id; + + ret = (msm_hsl_read(port, regmap[vid][UARTDM_SR]) & + UARTDM_SR_TXEMT_BMSK) ? TIOCSER_TEMT : 0; + return ret; +} + +static void msm_hsl_reset(struct uart_port *port) +{ + unsigned int vid = UART_TO_MSM(port)->ver_id; + + /* reset everything */ + msm_hsl_write(port, RESET_RX, regmap[vid][UARTDM_CR]); + msm_hsl_write(port, RESET_TX, regmap[vid][UARTDM_CR]); + msm_hsl_write(port, RESET_ERROR_STATUS, regmap[vid][UARTDM_CR]); + msm_hsl_write(port, RESET_BREAK_INT, regmap[vid][UARTDM_CR]); + msm_hsl_write(port, RESET_CTS, regmap[vid][UARTDM_CR]); + msm_hsl_write(port, RFR_LOW, regmap[vid][UARTDM_CR]); +} + +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 vid = UART_TO_MSM(port)->ver_id; + unsigned int mr; + unsigned int loop_mode; + + mr = msm_hsl_read(port, regmap[vid][UARTDM_MR1]); + + if (!(mctrl & TIOCM_RTS)) { + mr &= ~UARTDM_MR1_RX_RDY_CTL_BMSK; + msm_hsl_write(port, mr, regmap[vid][UARTDM_MR1]); + msm_hsl_write(port, RFR_HIGH, regmap[vid][UARTDM_CR]); + } else { + mr |= UARTDM_MR1_RX_RDY_CTL_BMSK; + msm_hsl_write(port, mr, regmap[vid][UARTDM_MR1]); + } + + loop_mode = TIOCM_LOOP & mctrl; + if (loop_mode) { + mr = msm_hsl_read(port, regmap[vid][UARTDM_MR2]); + mr |= UARTDM_MR2_LOOP_MODE_BMSK; + msm_hsl_write(port, mr, regmap[vid][UARTDM_MR2]); + + /* 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, regmap[vid][UARTDM_CR]); + } +} + +static void msm_hsl_break_ctl(struct uart_port *port, int break_ctl) +{ + unsigned int vid = UART_TO_MSM(port)->ver_id; + + if (break_ctl) + msm_hsl_write(port, START_BREAK, regmap[vid][UARTDM_CR]); + else + msm_hsl_write(port, STOP_BREAK, regmap[vid][UARTDM_CR]); +} + +static void msm_hsl_set_baud_rate(struct uart_port *port, unsigned int baud) +{ + unsigned int baud_code, rxstale, watermark; + unsigned int data; + unsigned int vid; + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + switch (baud) { + case 300: + baud_code = UARTDM_CSR_75; + rxstale = 1; + break; + case 600: + baud_code = UARTDM_CSR_150; + rxstale = 1; + break; + case 1200: + baud_code = UARTDM_CSR_300; + rxstale = 1; + break; + case 2400: + baud_code = UARTDM_CSR_600; + rxstale = 1; + break; + case 4800: + baud_code = UARTDM_CSR_1200; + rxstale = 1; + break; + case 9600: + baud_code = UARTDM_CSR_2400; + rxstale = 2; + break; + case 14400: + baud_code = UARTDM_CSR_3600; + rxstale = 3; + break; + case 19200: + baud_code = UARTDM_CSR_4800; + rxstale = 4; + break; + case 28800: + baud_code = UARTDM_CSR_7200; + rxstale = 6; + break; + case 38400: + baud_code = UARTDM_CSR_9600; + rxstale = 8; + break; + case 57600: + baud_code = UARTDM_CSR_14400; + rxstale = 16; + break; + case 115200: + baud_code = UARTDM_CSR_28800; + rxstale = 31; + break; + case 230400: + baud_code = UARTDM_CSR_57600; + rxstale = 31; + break; + case 460800: + baud_code = UARTDM_CSR_115200; + rxstale = 31; + break; + default: /* 115200 baud rate */ + baud_code = UARTDM_CSR_28800; + rxstale = 31; + break; + } + + /* Set timeout to be ~100x the character transmit time */ + msm_hsl_port->tx_timeout = 1000000000 / baud; + + vid = msm_hsl_port->ver_id; + msm_hsl_write(port, baud_code, regmap[vid][UARTDM_CSR]); + + /* RX stale watermark */ + watermark = UARTDM_IPR_STALE_LSB_BMSK & rxstale; + watermark |= UARTDM_IPR_STALE_TIMEOUT_MSB_BMSK & (rxstale << 2); + msm_hsl_write(port, watermark, regmap[vid][UARTDM_IPR]); + + /* Set RX watermark + * Configure Rx Watermark as 3/4 size of Rx FIFO. + * RFWR register takes value in Words for UARTDM Core + * whereas it is consider to be in Bytes for UART Core. + * Hence configuring Rx Watermark as 12 Words. + */ + watermark = (port->fifosize * 3) / (4*4); + msm_hsl_write(port, watermark, regmap[vid][UARTDM_RFWR]); + + /* set TX watermark */ + msm_hsl_write(port, 0, regmap[vid][UARTDM_TFWR]); + + msm_hsl_write(port, CR_PROTECTION_EN, regmap[vid][UARTDM_CR]); + msm_hsl_reset(port); + + data = UARTDM_CR_TX_EN_BMSK; + data |= UARTDM_CR_RX_EN_BMSK; + /* enable TX & RX */ + msm_hsl_write(port, data, regmap[vid][UARTDM_CR]); + + msm_hsl_write(port, RESET_STALE_INT, regmap[vid][UARTDM_CR]); + /* turn on RX and CTS interrupts */ + msm_hsl_port->imr = UARTDM_ISR_RXSTALE_BMSK + | UARTDM_ISR_DELTA_CTS_BMSK | UARTDM_ISR_RXLEV_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, regmap[vid][UARTDM_IMR]); + msm_hsl_write(port, 6500, regmap[vid][UARTDM_DMRX]); + msm_hsl_write(port, STALE_EVENT_ENABLE, regmap[vid][UARTDM_CR]); +} + +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; + unsigned int vid; + 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); + + /* Set RFR Level as 3/4 of UARTDM FIFO Size */ + if (likely(port->fifosize > 48)) + rfr_level = port->fifosize - 16; + else + rfr_level = port->fifosize; + + /* + * Use rfr_level value in Words to program + * MR1 register for UARTDM Core. + */ + rfr_level = (rfr_level / 4); + + spin_lock_irqsave(&port->lock, flags); + + vid = msm_hsl_port->ver_id; + /* set automatic RFR level */ + data = msm_hsl_read(port, regmap[vid][UARTDM_MR1]); + 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, regmap[vid][UARTDM_MR1]); + 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; + } + 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; + + msm_hsl_port->imr = 0; + /* disable interrupts */ + msm_hsl_write(port, 0, regmap[msm_hsl_port->ver_id][UARTDM_IMR]); + + 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; + unsigned int vid; + + if (!termios->c_cflag) + return; + + spin_lock_irqsave(&port->lock, flags); + + /* calculate and set baud rate */ + baud = uart_get_baud_rate(port, termios, old, 300, 460800); + + msm_hsl_set_baud_rate(port, baud); + + vid = UART_TO_MSM(port)->ver_id; + /* calculate parity */ + mr = msm_hsl_read(port, regmap[vid][UARTDM_MR2]); + 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, regmap[vid][UARTDM_MR2]); + + /* calculate and set hardware flow control */ + mr = msm_hsl_read(port, regmap[vid][UARTDM_MR1]); + 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, regmap[vid][UARTDM_MR1]); + + /* 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); + + 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; + resource_size_t size; + + uart_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uartdm_resource"); + if (!uart_resource) + uart_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + 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); + 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 (!uart_resource) + uart_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + 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 (!gsbi_resource) + gsbi_resource = platform_get_resource(pdev, + IORESOURCE_MEM, 1); + 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 (msm_hsl_port->pclk) + clk_prepare_enable(msm_hsl_port->pclk); + 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 (msm_hsl_port->pclk) + clk_disable_unprepare(msm_hsl_port->pclk); + } +} + +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, 7372800); + if (ret) + pr_err("%s(): Error setting UART clock rate\n", + __func__); + clk_en(port, 1); + break; + case 3: + clk_en(port, 0); + ret = clk_set_rate(msm_hsl_port->clk, 0); + if (ret) + pr_err("%s(): Error setting UART clock rate to zero.\n", + __func__); + 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; +} + +static unsigned int msm_hsl_console_state[8]; + +static void dump_hsl_regs(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + unsigned int vid = msm_hsl_port->ver_id; + unsigned int sr, isr, mr1, mr2, ncf, txfs, rxfs, con_state; + + sr = msm_hsl_read(port, regmap[vid][UARTDM_SR]); + isr = msm_hsl_read(port, regmap[vid][UARTDM_ISR]); + mr1 = msm_hsl_read(port, regmap[vid][UARTDM_MR1]); + mr2 = msm_hsl_read(port, regmap[vid][UARTDM_MR2]); + ncf = msm_hsl_read(port, regmap[vid][UARTDM_NCF_TX]); + txfs = msm_hsl_read(port, regmap[vid][UARTDM_TXFS]); + rxfs = msm_hsl_read(port, regmap[vid][UARTDM_RXFS]); + con_state = get_console_state(port); + + msm_hsl_console_state[0] = sr; + msm_hsl_console_state[1] = isr; + msm_hsl_console_state[2] = mr1; + msm_hsl_console_state[3] = mr2; + msm_hsl_console_state[4] = ncf; + msm_hsl_console_state[5] = txfs; + msm_hsl_console_state[6] = rxfs; + msm_hsl_console_state[7] = con_state; + + pr_info("%s(): Timeout: %d uS\n", __func__, msm_hsl_port->tx_timeout); + pr_info("%s(): SR: %08x\n", __func__, sr); + pr_info("%s(): ISR: %08x\n", __func__, isr); + pr_info("%s(): MR1: %08x\n", __func__, mr1); + pr_info("%s(): MR2: %08x\n", __func__, mr2); + pr_info("%s(): NCF: %08x\n", __func__, ncf); + pr_info("%s(): TXFS: %08x\n", __func__, txfs); + pr_info("%s(): RXFS: %08x\n", __func__, rxfs); + pr_info("%s(): Console state: %d\n", __func__, con_state); +} + +/* + * Wait for transmitter & holding register to empty + * Derived from wait_for_xmitr in 8250 serial driver by Russell King */ +static void wait_for_xmitr(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + unsigned int vid = msm_hsl_port->ver_id; + int count = 0; + + if (!(msm_hsl_read(port, regmap[vid][UARTDM_SR]) & + UARTDM_SR_TXEMT_BMSK)) { + while (!(msm_hsl_read(port, regmap[vid][UARTDM_ISR]) & + UARTDM_ISR_TX_READY_BMSK)) { + udelay(1); + touch_nmi_watchdog(); + cpu_relax(); + if (++count == msm_hsl_port->tx_timeout) { + dump_hsl_regs(port); + panic("MSM HSL wait_for_xmitr is stuck!"); + } + } + msm_hsl_write(port, CLEAR_TX_READY, regmap[vid][UARTDM_CR]); + } +} + +#ifdef CONFIG_SERIAL_MSM_HSL_CONSOLE +static void msm_hsl_console_putchar(struct uart_port *port, int ch) +{ + unsigned int vid = UART_TO_MSM(port)->ver_id; + + wait_for_xmitr(port); + msm_hsl_write(port, 1, regmap[vid][UARTDM_NCF_TX]); + /* + * Dummy read to add 1 AHB clock delay to fix UART hardware bug. + * Bug: Delay required on TX-transfer-init. after writing to + * NO_CHARS_FOR_TX register. + */ + msm_hsl_read(port, regmap[vid][UARTDM_SR]); + msm_hsl_write(port, ch, regmap[vid][UARTDM_TF]); +} + +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; + unsigned int vid; + 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); + vid = msm_hsl_port->ver_id; + + /* 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, regmap[vid][UARTDM_IMR]); + uart_console_write(port, s, count, msm_hsl_console_putchar); + msm_hsl_write(port, msm_hsl_port->imr, regmap[vid][UARTDM_IMR]); + if (locked == 1) + spin_unlock(&port->lock); +} + +static int msm_hsl_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + unsigned int vid; + int baud = 0, flow, bits, parity, mr2; + int ret; + + if (unlikely(co->index >= UART_NR || co->index < 0)) + return -ENXIO; + + port = get_port_from_line(co->index); + vid = UART_TO_MSM(port)->ver_id; + + 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, + regmap[vid][UARTDM_MR2]); /* 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); + + mr2 = msm_hsl_read(port, regmap[vid][UARTDM_MR2]); + mr2 |= UARTDM_MR2_RX_ERROR_CHAR_OFF; + mr2 |= UARTDM_MR2_RX_BREAK_ZERO_CHAR_OFF; + msm_hsl_write(port, mr2, regmap[vid][UARTDM_MR2]); + + msm_hsl_reset(port); + /* Enable transmitter */ + msm_hsl_write(port, CR_PROTECTION_EN, regmap[vid][UARTDM_CR]); + msm_hsl_write(port, UARTDM_CR_TX_EN_BMSK, regmap[vid][UARTDM_CR]); + + 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) +/* + * get_console_state - check the per-port serial console state. + * @port: uart_port structure describing the port + * + * Return the state of serial console availability on port. + * return 1: If serial console is enabled on particular UART port. + * return 0: If serial console is disabled on particular UART port. + */ +static int get_console_state(struct uart_port *port) +{ + if (is_console(port) && (port->cons->flags & CON_ENABLED)) + return 1; + else + return 0; +} + +/* show_msm_console - provide per-port serial console state. */ +static ssize_t show_msm_console(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int enable; + struct uart_port *port; + + struct platform_device *pdev = to_platform_device(dev); + port = get_port_from_line(get_line(pdev)); + + enable = get_console_state(port); + + return snprintf(buf, sizeof(enable), "%d\n", enable); +} + +/* + * set_msm_console - allow to enable/disable serial console on port. + * + * writing 1 enables serial console on UART port. + * writing 0 disables serial console on UART port. + */ +static ssize_t set_msm_console(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int enable, cur_state; + struct uart_port *port; + + struct platform_device *pdev = to_platform_device(dev); + port = get_port_from_line(get_line(pdev)); + + cur_state = get_console_state(port); + enable = buf[0] - '0'; + + if (enable == cur_state) + return count; + + switch (enable) { + case 0: + pr_debug("%s(): Calling stop_console\n", __func__); + console_stop(port->cons); + pr_debug("%s(): Calling unregister_console\n", __func__); + unregister_console(port->cons); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + /* + * Disable UART Core clk + * 3 - to disable the UART clock + * Thid parameter is not used here, but used in serial core. + */ + msm_hsl_power(port, 3, 1); + break; + case 1: + pr_debug("%s(): Calling register_console\n", __func__); + /* + * Disable UART Core clk + * 0 - to enable the UART clock + * Thid parameter is not used here, but used in serial core. + */ + msm_hsl_power(port, 0, 1); + pm_runtime_enable(&pdev->dev); + register_console(port->cons); + break; + default: + return -EINVAL; + } + + return count; +} +static DEVICE_ATTR(console, S_IWUSR | S_IRUGO, show_msm_console, + set_msm_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 atomic_t msm_serial_hsl_next_id = ATOMIC_INIT(0); + +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; + const struct of_device_id *match; + int ret; + + if (pdev->id == -1) + pdev->id = atomic_inc_return(&msm_serial_hsl_next_id) - 1; + + if (unlikely(get_line(pdev) < 0 || get_line(pdev) >= UART_NR)) + return -ENXIO; + + printk(KERN_INFO "msm_serial_hsl: detected port #%d\n", pdev->id); + + port = get_port_from_line(get_line(pdev)); + port->dev = &pdev->dev; + msm_hsl_port = UART_TO_MSM(port); + + match = of_match_device(msm_hsl_match_table, &pdev->dev); + if (!match) + msm_hsl_port->ver_id = UARTDM_VERSION_11_13; + else + msm_hsl_port->ver_id = (unsigned int)match->data; + + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + if (!gsbi_resource) + gsbi_resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); + msm_hsl_port->clk = clk_get(&pdev->dev, "core_clk"); + if (gsbi_resource) { + msm_hsl_port->is_uartdm = 1; + msm_hsl_port->pclk = clk_get(&pdev->dev, "iface_clk"); + } else { + msm_hsl_port->is_uartdm = 0; + 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 (!uart_resource) + uart_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + 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((int)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); +#ifdef CONFIG_SERIAL_MSM_HSL_CONSOLE + ret = device_create_file(&pdev->dev, &dev_attr_console); + if (unlikely(ret)) + pr_err("%s():Can't create console attribute\n", __func__); +#endif + msm_hsl_debugfs_init(msm_hsl_port, get_line(pdev)); + + /* Temporarily increase the refcount on the GSBI clock to avoid a race + * condition with the earlyprintk handover mechanism. + */ + if (msm_hsl_port->pclk) + clk_prepare_enable(msm_hsl_port->pclk); + ret = uart_add_one_port(&msm_hsl_uart_driver, port); + if (msm_hsl_port->pclk) + clk_disable_unprepare(msm_hsl_port->pclk); + 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(get_line(pdev)); +#ifdef CONFIG_SERIAL_MSM_HSL_CONSOLE + device_remove_file(&pdev->dev, &dev_attr_console); +#endif + 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); + debugfs_remove(msm_hsl_port->loopback_dir); + + 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(get_line(pdev)); + + if (port) { + + if (is_console(port)) + msm_hsl_deinit_clock(port); + + uart_suspend_port(&msm_hsl_uart_driver, port); + if (device_may_wakeup(dev)) + enable_irq_wake(port->irq); + } + + 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(get_line(pdev)); + + if (port) { + + uart_resume_port(&msm_hsl_uart_driver, port); + if (device_may_wakeup(dev)) + disable_irq_wake(port->irq); + + if (is_console(port)) + msm_hsl_init_clock(port); + } + + 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(get_line(pdev)); + + 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(get_line(pdev)); + + 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, + .of_match_table = msm_hsl_match_table, + }, +}; + +static int __init msm_serial_hsl_init(void) +{ + int ret; + + ret = uart_register_driver(&msm_hsl_uart_driver); + if (unlikely(ret)) + return ret; + + debug_base = debugfs_create_dir("msm_serial_hsl", NULL); + if (IS_ERR_OR_NULL(debug_base)) + pr_err("%s():Cannot create debugfs dir\n", __func__); + + 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) +{ + debugfs_remove_recursive(debug_base); +#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/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 7d9fbb82012e4145f61030c0c883e3c031735745..9ca64ac0033468e206e734a96295a11eca4bb45e 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1965,7 +1965,11 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport) */ if (port->tty && port->tty->termios && termios.c_cflag == 0) termios = *(port->tty->termios); - + /* + * As we need to set the uart clock rate back to 7.3 MHz. + * We need this change. + * + */ if (console_suspend_enabled) uart_change_pm(state, 0); uport->ops->set_termios(uport, &termios, NULL); diff --git a/drivers/tty/smux_ctl.c b/drivers/tty/smux_ctl.c new file mode 100644 index 0000000000000000000000000000000000000000..69adbf3df0a7b6b2625ab68f69a4783aa11f9de3 --- /dev/null +++ b/drivers/tty/smux_ctl.c @@ -0,0 +1,937 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Serial Mux Control Driver -- Provides a binary serial muxed control + * port interface. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MAX_WRITE_RETRY 5 +#define MAGIC_NO_V1 0x33FC +#define DEVICE_NAME "smuxctl" +#define SMUX_CTL_MAX_BUF_SIZE 2048 +#define SMUX_CTL_MODULE_NAME "smux_ctl" +#define DEBUG + +static int msm_smux_ctl_debug_mask; +module_param_named(debug_mask, msm_smux_ctl_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +static uint32_t smux_ctl_ch_id[] = { + SMUX_DATA_CTL_0, + SMUX_DATA_CTL_1, + SMUX_DATA_CTL_2, + SMUX_DATA_CTL_3, + SMUX_DATA_CTL_4, + SMUX_DATA_CTL_5, + SMUX_DATA_CTL_6, + SMUX_DATA_CTL_7, + SMUX_USB_RMNET_CTL_0, + SMUX_CSVT_CTL_0 +}; + +#define SMUX_CTL_NUM_CHANNELS ARRAY_SIZE(smux_ctl_ch_id) + +struct smux_ctl_dev { + int id; + char name[10]; + struct cdev cdev; + struct device *devicep; + struct mutex dev_lock; + atomic_t ref_count; + int state; + int is_channel_reset; + int is_high_wm; + int write_pending; + + 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; + + struct { + uint32_t bytes_tx; + uint32_t bytes_rx; + uint32_t pkts_tx; + uint32_t pkts_rx; + uint32_t cnt_ssr; + uint32_t cnt_read_fail; + uint32_t cnt_write_fail; + uint32_t cnt_high_wm_hit; + } stats; + +} *smux_ctl_devp[SMUX_CTL_NUM_CHANNELS]; + +struct smux_ctl_pkt { + int data_size; + void *data; +}; + +struct smux_ctl_list_elem { + struct list_head list; + struct smux_ctl_pkt ctl_pkt; +}; + +struct class *smux_ctl_classp; +static dev_t smux_ctl_number; +static uint32_t smux_ctl_inited; + +enum { + MSM_SMUX_CTL_DEBUG = 1U << 0, + MSM_SMUX_CTL_DUMP_BUFFER = 1U << 1, +}; + +#if defined(DEBUG) + +static const char *smux_ctl_event_str[] = { + "SMUX_CONNECTED", + "SMUX_DISCONNECTED", + "SMUX_READ_DONE", + "SMUX_READ_FAIL", + "SMUX_WRITE_DONE", + "SMUX_WRITE_FAIL", + "SMUX_TIOCM_UPDATE", + "SMUX_LOW_WM_HIT", + "SMUX_HIGH_WM_HIT", +}; + +#define SMUXCTL_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (msm_smux_ctl_debug_mask & MSM_SMUX_CTL_DUMP_BUFFER) { \ + int i; \ + pr_err("%s", prestr); \ + for (i = 0; i < cnt; i++) \ + pr_err("%.2x", buf[i]); \ + pr_err("\n"); \ + } \ +} while (0) + +#define SMUXCTL_DBG(x...) \ +do { \ + if (msm_smux_ctl_debug_mask & MSM_SMUX_CTL_DEBUG) \ + pr_err(x); \ +} while (0) + + +#else +#define SMUXCTL_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) +#define SMUXCTL_DBG(x...) do {} while (0) +#endif + +#if defined(DEBUG_LOOPBACK) +#define SMUXCTL_SET_LOOPBACK(lcid) \ + msm_smux_set_ch_option(lcid, SMUX_CH_OPTION_LOCAL_LOOPBACK, 0) +#else +#define SMUXCTL_SET_LOOPBACK(lcid) do {} while (0) +#endif + +static int get_ctl_dev_index(int id) +{ + int dev_index; + for (dev_index = 0; dev_index < SMUX_CTL_NUM_CHANNELS; dev_index++) { + if (smux_ctl_ch_id[dev_index] == id) + return dev_index; + } + return -ENODEV; +} + +static int smux_ctl_get_rx_buf_cb(void *priv, void **pkt_priv, + void **buffer, int size) +{ + void *buf = NULL; + int id = ((struct smux_ctl_dev *)(priv))->id; + int dev_index; + + if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1]) + return -ENODEV; + + if (!buffer || 0 >= size) + return -EINVAL; + + dev_index = get_ctl_dev_index(id); + if (dev_index < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: Ch%d is not " + "exported to user-space\n", + __func__, id); + return -ENODEV; + } + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: Allocating Rx buf size %d " + "for ch%d\n", + __func__, size, smux_ctl_devp[dev_index]->id); + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: buffer allocation failed: " + "Ch%d, size %d ", __func__, id, size); + return -ENOMEM; + } + + *buffer = buf; + *pkt_priv = NULL; + return 0; + +} + +void smux_ctl_notify_cb(void *priv, int event_type, const void *metadata) +{ + int id = ((struct smux_ctl_dev *)(priv))->id; + struct smux_ctl_list_elem *list_elem = NULL; + int dev_index; + void *data; + int len; + + if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1]) + return; + + dev_index = get_ctl_dev_index(id); + if (dev_index < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: Ch%d is not exported " + "to user-space\n", __func__, id); + return; + } + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: Ch%d, Event %d (%s)\n", + __func__, smux_ctl_devp[dev_index]->id, + event_type, smux_ctl_event_str[event_type]); + + + switch (event_type) { + case SMUX_CONNECTED: + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->state = SMUX_CONNECTED; + smux_ctl_devp[dev_index]->is_high_wm = 0; + smux_ctl_devp[dev_index]->is_channel_reset = 0; + smux_ctl_devp[dev_index]->read_avail = 0; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + wake_up(&smux_ctl_devp[dev_index]->write_wait_queue); + break; + + case SMUX_DISCONNECTED: + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->state = SMUX_DISCONNECTED; + smux_ctl_devp[dev_index]->is_channel_reset = + ((struct smux_meta_disconnected *)metadata)->is_ssr; + if (smux_ctl_devp[dev_index]->is_channel_reset) + smux_ctl_devp[dev_index]->stats.cnt_ssr++; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + wake_up(&smux_ctl_devp[dev_index]->write_wait_queue); + wake_up(&smux_ctl_devp[dev_index]->read_wait_queue); + break; + + case SMUX_READ_FAIL: + data = ((struct smux_meta_read *)metadata)->buffer; + kfree(data); + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->stats.cnt_read_fail++; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + wake_up(&smux_ctl_devp[dev_index]->read_wait_queue); + break; + + case SMUX_READ_DONE: + data = ((struct smux_meta_read *)metadata)->buffer; + len = ((struct smux_meta_read *)metadata)->len; + + if (data && len > 0) { + list_elem = kmalloc(sizeof(struct smux_ctl_list_elem), + GFP_KERNEL); + if (list_elem) { + list_elem->ctl_pkt.data = data; + list_elem->ctl_pkt.data_size = len; + + mutex_lock(&smux_ctl_devp[dev_index]->rx_lock); + list_add_tail(&list_elem->list, + &smux_ctl_devp[dev_index]->rx_list); + smux_ctl_devp[dev_index]->read_avail += len; + mutex_unlock( + &smux_ctl_devp[dev_index]->rx_lock); + } else { + kfree(data); + } + } + + wake_up(&smux_ctl_devp[dev_index]->read_wait_queue); + break; + + case SMUX_WRITE_DONE: + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->write_pending = 0; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + data = ((struct smux_meta_write *)metadata)->buffer; + kfree(data); + wake_up(&smux_ctl_devp[dev_index]->write_wait_queue); + break; + + case SMUX_WRITE_FAIL: + data = ((struct smux_meta_write *)metadata)->buffer; + kfree(data); + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->stats.cnt_write_fail++; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + wake_up(&smux_ctl_devp[dev_index]->write_wait_queue); + break; + + case SMUX_LOW_WM_HIT: + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->is_high_wm = 0; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + wake_up(&smux_ctl_devp[dev_index]->write_wait_queue); + break; + + case SMUX_HIGH_WM_HIT: + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + smux_ctl_devp[dev_index]->is_high_wm = 1; + smux_ctl_devp[dev_index]->stats.cnt_high_wm_hit++; + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + break; + + case SMUX_TIOCM_UPDATE: + default: + pr_err(SMUX_CTL_MODULE_NAME ": %s: Event %d not supported\n", + __func__, event_type); + break; + + } + +} + +int smux_ctl_open(struct inode *inode, struct file *file) +{ + int r = 0; + struct smux_ctl_dev *devp; + + if (!smux_ctl_inited) + return -EIO; + + devp = container_of(inode->i_cdev, struct smux_ctl_dev, cdev); + if (!devp) + return -ENODEV; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d device\n", + __func__, devp->id); + + if (1 == atomic_add_return(1, &devp->ref_count)) { + + SMUXCTL_SET_LOOPBACK(devp->id); + r = msm_smux_open(devp->id, + devp, + smux_ctl_notify_cb, + smux_ctl_get_rx_buf_cb); + if (r < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: smux_open failed " + "for smuxctl%d with rc %d\n", + __func__, devp->id, r); + atomic_dec(&devp->ref_count); + return r; + } + + r = wait_event_interruptible_timeout( + devp->write_wait_queue, + (devp->state == SMUX_CONNECTED), + (5 * HZ)); + if (r == 0) + r = -ETIMEDOUT; + + if (r < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "SMUX open timed out: %d, LCID %d\n", + __func__, r, devp->id); + atomic_dec(&devp->ref_count); + msm_smux_close(devp->id); + return r; + + } else if (devp->state != SMUX_CONNECTED) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "Invalid open notification\n", __func__); + r = -ENODEV; + atomic_dec(&devp->ref_count); + msm_smux_close(devp->id); + return r; + } + } + + file->private_data = devp; + return 0; +} + +int smux_ctl_release(struct inode *inode, struct file *file) +{ + struct smux_ctl_dev *devp; + struct smux_ctl_list_elem *list_elem = NULL; + + devp = file->private_data; + if (!devp) + return -EINVAL; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d device\n", + __func__, devp->id); + + mutex_lock(&devp->dev_lock); + if (atomic_dec_and_test(&devp->ref_count)) { + mutex_lock(&devp->rx_lock); + while (!list_empty(&devp->rx_list)) { + list_elem = list_first_entry( + &devp->rx_list, + struct smux_ctl_list_elem, + list); + list_del(&list_elem->list); + kfree(list_elem->ctl_pkt.data); + kfree(list_elem); + } + devp->read_avail = 0; + mutex_unlock(&devp->rx_lock); + msm_smux_close(devp->id); + } + mutex_unlock(&devp->dev_lock); + file->private_data = NULL; + + return 0; +} + +static int smux_ctl_readable(int id) +{ + int r; + int dev_index; + + if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1]) + return -ENODEV; + + dev_index = get_ctl_dev_index(id); + if (dev_index < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: Ch%d " + "is not exported to user-space\n", + __func__, id); + return -ENODEV; + } + + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + + if (signal_pending(current)) + r = -ERESTARTSYS; + + if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED && + smux_ctl_devp[dev_index]->is_channel_reset != 0) + r = -ENETRESET; + + else if (smux_ctl_devp[dev_index]->state != SMUX_CONNECTED) + r = -ENODEV; + + else + r = smux_ctl_devp[dev_index]->read_avail; + + + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + + return r; + +} + +ssize_t smux_ctl_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + int r = 0, id, bytes_to_read, read_err; + struct smux_ctl_dev *devp; + struct smux_ctl_list_elem *list_elem = NULL; + + devp = file->private_data; + + if (!devp) + return -ENODEV; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: read from ch%d\n", + __func__, devp->id); + + id = devp->id; + mutex_lock(&devp->rx_lock); + while (devp->read_avail <= 0) { + mutex_unlock(&devp->rx_lock); + r = wait_event_interruptible(devp->read_wait_queue, + 0 != (read_err = smux_ctl_readable(id))); + + if (r < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s:" + "wait_event_interruptible " + "ret %i\n", __func__, r); + return r; + } + + if (read_err < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s:" + " Read block failed for Ch%d, err %d\n", + __func__, devp->id, read_err); + return read_err; + } + + mutex_lock(&devp->rx_lock); + } + + if (list_empty(&devp->rx_list)) { + mutex_unlock(&devp->rx_lock); + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: " + "Nothing in ch%d's rx_list\n", __func__, + devp->id); + return -EAGAIN; + } + + list_elem = list_first_entry(&devp->rx_list, + struct smux_ctl_list_elem, list); + bytes_to_read = (uint32_t)(list_elem->ctl_pkt.data_size); + if (bytes_to_read > count) { + mutex_unlock(&devp->rx_lock); + pr_err(SMUX_CTL_MODULE_NAME ": %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(&devp->rx_lock); + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "copy_to_user failed for ch%d\n", __func__, + devp->id); + return -EFAULT; + } + + devp->read_avail -= bytes_to_read; + list_del(&list_elem->list); + kfree(list_elem->ctl_pkt.data); + kfree(list_elem); + devp->stats.pkts_rx++; + devp->stats.bytes_rx += bytes_to_read; + mutex_unlock(&devp->rx_lock); + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: " + "Returning %d bytes to ch%d\n", __func__, + bytes_to_read, devp->id); + return bytes_to_read; +} + +static int smux_ctl_writeable(int id) +{ + int r; + int dev_index; + + if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1]) + return -ENODEV; + + dev_index = get_ctl_dev_index(id); + if (dev_index < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "Ch%d is not exported to user-space\n", + __func__, id); + return -ENODEV; + } + + mutex_lock(&smux_ctl_devp[dev_index]->dev_lock); + + if (signal_pending(current)) + r = -ERESTARTSYS; + else if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED && + smux_ctl_devp[dev_index]->is_channel_reset != 0) + r = -ENETRESET; + else if (smux_ctl_devp[dev_index]->state != SMUX_CONNECTED) + r = -ENODEV; + else if (smux_ctl_devp[dev_index]->is_high_wm || + smux_ctl_devp[dev_index]->write_pending) + r = 0; + else + r = SMUX_CTL_MAX_BUF_SIZE; + + mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock); + + return r; + +} + +ssize_t smux_ctl_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int r = 0, id, write_err; + char *temp_buf; + struct smux_ctl_dev *devp; + + if (count <= 0) + return -EINVAL; + + devp = file->private_data; + if (!devp) + return -ENODEV; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: writing %i bytes on ch%d\n", + __func__, count, devp->id); + + id = devp->id; + r = wait_event_interruptible(devp->write_wait_queue, + 0 != (write_err = smux_ctl_writeable(id))); + + if (r < 0) { + pr_err(SMUX_CTL_MODULE_NAME + ": %s: wait_event_interruptible " + "ret %i\n", __func__, r); + return r; + } + + if (write_err < 0) { + pr_err(SMUX_CTL_MODULE_NAME ": %s:" + "Write block failed for Ch%d, err %d\n", + __func__, devp->id, write_err); + return write_err; + } + + temp_buf = kmalloc(count, GFP_KERNEL); + if (!temp_buf) { + pr_err(SMUX_CTL_MODULE_NAME + ": %s: temp_buf alloc failed\n", __func__); + return -ENOMEM; + } + + if (copy_from_user(temp_buf, buf, count)) { + pr_err(SMUX_CTL_MODULE_NAME + ": %s: copy_from_user failed\n", __func__); + kfree(temp_buf); + return -EFAULT; + } + + mutex_lock(&devp->dev_lock); + devp->write_pending = 1; + mutex_unlock(&devp->dev_lock); + + r = msm_smux_write(id, NULL, (void *)temp_buf, count); + if (r < 0) { + pr_err(SMUX_CTL_MODULE_NAME + ": %s: smux_write on Ch%dfailed, err %d\n", + __func__, id, r); + mutex_lock(&devp->dev_lock); + devp->write_pending = 0; + mutex_unlock(&devp->dev_lock); + return r; + } + + r = wait_event_interruptible(devp->write_wait_queue, + 0 != (write_err = smux_ctl_writeable(id))); + if (r < 0) { + pr_err(SMUX_CTL_MODULE_NAME " :%s: wait_event_interruptible " + "ret %i\n", __func__, r); + mutex_lock(&devp->dev_lock); + devp->write_pending = 0; + mutex_unlock(&devp->dev_lock); + return r; + } + + mutex_lock(&devp->dev_lock); + devp->write_pending = 0; + devp->stats.pkts_tx++; + devp->stats.bytes_tx += count; + mutex_unlock(&devp->dev_lock); + return count; +} + +static long smux_ctl_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct smux_ctl_dev *devp; + + devp = file->private_data; + if (!devp) + return -ENODEV; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d device\n", + __func__, devp->id); + + switch (cmd) { + case TIOCMGET: + ret = msm_smux_tiocm_get(devp->id); + break; + case TIOCMSET: + ret = msm_smux_tiocm_set(devp->id, arg, ~arg); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct file_operations smux_ctl_fops = { + .owner = THIS_MODULE, + .open = smux_ctl_open, + .release = smux_ctl_release, + .read = smux_ctl_read, + .write = smux_ctl_write, + .unlocked_ioctl = smux_ctl_ioctl, +}; + +static int smux_ctl_probe(struct platform_device *pdev) +{ + int i; + int r; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__); + + for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) { + smux_ctl_devp[i] = kzalloc(sizeof(struct smux_ctl_dev), + GFP_KERNEL); + if (IS_ERR(smux_ctl_devp[i])) { + pr_err(SMUX_CTL_MODULE_NAME + ": %s kmalloc() ENOMEM\n", __func__); + r = -ENOMEM; + goto error0; + } + + smux_ctl_devp[i]->id = smux_ctl_ch_id[i]; + atomic_set(&smux_ctl_devp[i]->ref_count, 0); + smux_ctl_devp[i]->is_high_wm = 0; + smux_ctl_devp[i]->write_pending = 0; + smux_ctl_devp[i]->is_channel_reset = 0; + smux_ctl_devp[i]->state = SMUX_DISCONNECTED; + smux_ctl_devp[i]->read_avail = 0; + + smux_ctl_devp[i]->stats.bytes_tx = 0; + smux_ctl_devp[i]->stats.bytes_rx = 0; + smux_ctl_devp[i]->stats.pkts_tx = 0; + smux_ctl_devp[i]->stats.pkts_rx = 0; + smux_ctl_devp[i]->stats.cnt_ssr = 0; + smux_ctl_devp[i]->stats.cnt_read_fail = 0; + smux_ctl_devp[i]->stats.cnt_write_fail = 0; + smux_ctl_devp[i]->stats.cnt_high_wm_hit = 0; + + mutex_init(&smux_ctl_devp[i]->dev_lock); + init_waitqueue_head(&smux_ctl_devp[i]->read_wait_queue); + init_waitqueue_head(&smux_ctl_devp[i]->write_wait_queue); + mutex_init(&smux_ctl_devp[i]->rx_lock); + INIT_LIST_HEAD(&smux_ctl_devp[i]->rx_list); + } + + r = alloc_chrdev_region(&smux_ctl_number, 0, SMUX_CTL_NUM_CHANNELS, + DEVICE_NAME); + if (IS_ERR_VALUE(r)) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "alloc_chrdev_region() ret %i.\n", + __func__, r); + goto error0; + } + + smux_ctl_classp = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(smux_ctl_classp)) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "class_create() ENOMEM\n", __func__); + r = -ENOMEM; + goto error1; + } + + for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) { + cdev_init(&smux_ctl_devp[i]->cdev, &smux_ctl_fops); + smux_ctl_devp[i]->cdev.owner = THIS_MODULE; + + r = cdev_add(&smux_ctl_devp[i]->cdev, (smux_ctl_number + i), 1); + + if (IS_ERR_VALUE(r)) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "cdev_add() ret %i\n", __func__, r); + kfree(smux_ctl_devp[i]); + goto error2; + } + + smux_ctl_devp[i]->devicep = + device_create(smux_ctl_classp, NULL, + (smux_ctl_number + i), NULL, + DEVICE_NAME "%d", smux_ctl_ch_id[i]); + + if (IS_ERR(smux_ctl_devp[i]->devicep)) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: " + "device_create() ENOMEM\n", __func__); + r = -ENOMEM; + cdev_del(&smux_ctl_devp[i]->cdev); + kfree(smux_ctl_devp[i]); + goto error2; + } + } + + smux_ctl_inited = 1; + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: " + "SMUX Control Port Driver Initialized.\n", __func__); + return 0; + +error2: + while (--i >= 0) { + cdev_del(&smux_ctl_devp[i]->cdev); + device_destroy(smux_ctl_classp, + MKDEV(MAJOR(smux_ctl_number), i)); + } + + class_destroy(smux_ctl_classp); + i = SMUX_CTL_NUM_CHANNELS; + +error1: + unregister_chrdev_region(MAJOR(smux_ctl_number), + SMUX_CTL_NUM_CHANNELS); + +error0: + while (--i >= 0) + kfree(smux_ctl_devp[i]); + + return r; +} + +static int smux_ctl_remove(struct platform_device *pdev) +{ + int i; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__); + + for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) { + cdev_del(&smux_ctl_devp[i]->cdev); + kfree(smux_ctl_devp[i]); + device_destroy(smux_ctl_classp, + MKDEV(MAJOR(smux_ctl_number), i)); + } + class_destroy(smux_ctl_classp); + unregister_chrdev_region(MAJOR(smux_ctl_number), + SMUX_CTL_NUM_CHANNELS); + + return 0; +} + +static struct platform_driver smux_ctl_driver = { + .probe = smux_ctl_probe, + .remove = smux_ctl_remove, + .driver = { + .name = "SMUX_CTL", + .owner = THIS_MODULE, + }, +}; + +static int __init smux_ctl_init(void) +{ + msm_smux_ctl_debug_mask = MSM_SMUX_CTL_DEBUG | MSM_SMUX_CTL_DUMP_BUFFER; + + SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__); + return platform_driver_register(&smux_ctl_driver); +} + + +#if defined(CONFIG_DEBUG_FS) + +#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 bsize = 0; + int i; + if (!smux_ctl_inited) { + pr_err(SMUX_CTL_MODULE_NAME ": %s: SMUX_CTL not yet inited\n", + __func__); + return -EIO; + } + + bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize, + "SMUX_CTL Channel States:\n"); + + for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) { + bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize, + "Ch%02d %s RefCnt=%01d State=%02d " + "SSR=%02d HighWM=%02d ReadAvail=%04d WritePending=%02d\n", + smux_ctl_devp[i]->id, + smux_ctl_devp[i]->name, + atomic_read(&smux_ctl_devp[i]->ref_count), + smux_ctl_devp[i]->state, + smux_ctl_devp[i]->is_channel_reset, + smux_ctl_devp[i]->is_high_wm, + smux_ctl_devp[i]->read_avail, + smux_ctl_devp[i]->write_pending); + } + + bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize, + "\nSMUX_CTL Channel Statistics:\n"); + for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) { + bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize, + "Ch%02d %s BytesTX=%08d " + "BytesRx=%08d PktsTx=%04d PktsRx=%04d" + "CntSSR=%02d CntHighWM=%02d " + "CntReadFail%02d CntWriteFailed=%02d\n", + smux_ctl_devp[i]->id, + smux_ctl_devp[i]->name, + smux_ctl_devp[i]->stats.bytes_tx, + smux_ctl_devp[i]->stats.bytes_rx, + smux_ctl_devp[i]->stats.pkts_tx, + smux_ctl_devp[i]->stats.pkts_rx, + smux_ctl_devp[i]->stats.cnt_ssr, + smux_ctl_devp[i]->stats.cnt_high_wm_hit, + smux_ctl_devp[i]->stats.cnt_read_fail, + smux_ctl_devp[i]->stats.cnt_write_fail); + } + + 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 int __init smux_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("smux_ctl", 0); + if (!IS_ERR(dent)) + debugfs_create_file("smux_ctl_state", 0444, dent, + NULL, &debug_ops); + + return 0; +} + +late_initcall(smux_debugfs_init); +#endif + +module_init(smux_ctl_init); +MODULE_DESCRIPTION("MSM SMUX Control Port"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/tty/smux_loopback.c b/drivers/tty/smux_loopback.c new file mode 100644 index 0000000000000000000000000000000000000000..52ce17f9e919c4c9d35efe05a5d5f04d845c4d3c --- /dev/null +++ b/drivers/tty/smux_loopback.c @@ -0,0 +1,289 @@ +/* drivers/tty/smux_loopback.c + * + * Copyright (c) 2012, 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 "smux_private.h" + +#define SMUX_LOOP_FIFO_SIZE 128 + +static void smux_loopback_rx_worker(struct work_struct *work); +static struct workqueue_struct *smux_loopback_wq; +static DECLARE_WORK(smux_loopback_work, smux_loopback_rx_worker); +static struct kfifo smux_loop_pkt_fifo; +static DEFINE_SPINLOCK(hw_fn_lock); + +/** + * Initialize loopback framework (called by n_smux.c). + */ +int smux_loopback_init(void) +{ + int ret = 0; + + spin_lock_init(&hw_fn_lock); + smux_loopback_wq = create_singlethread_workqueue("smux_loopback_wq"); + if (IS_ERR(smux_loopback_wq)) { + pr_err("%s: failed to create workqueue\n", __func__); + return -ENOMEM; + } + + ret |= kfifo_alloc(&smux_loop_pkt_fifo, + SMUX_LOOP_FIFO_SIZE * sizeof(struct smux_pkt_t *), + GFP_KERNEL); + + return ret; +} + +/** + * Simulate a write to the TTY hardware by duplicating + * the TX packet and putting it into the RX queue. + * + * @pkt Packet to write + * + * @returns 0 on success + */ +int smux_tx_loopback(struct smux_pkt_t *pkt_ptr) +{ + struct smux_pkt_t *send_pkt; + unsigned long flags; + int i; + int ret; + + /* duplicate packet */ + send_pkt = smux_alloc_pkt(); + send_pkt->hdr = pkt_ptr->hdr; + if (pkt_ptr->hdr.payload_len) { + ret = smux_alloc_pkt_payload(send_pkt); + if (ret) { + ret = -ENOMEM; + goto out; + } + memcpy(send_pkt->payload, pkt_ptr->payload, + pkt_ptr->hdr.payload_len); + } + + /* queue duplicate as pseudo-RX data */ + spin_lock_irqsave(&hw_fn_lock, flags); + i = kfifo_avail(&smux_loop_pkt_fifo); + if (i < sizeof(struct smux_pkt_t *)) { + pr_err("%s: no space in fifo\n", __func__); + ret = -ENOMEM; + goto unlock; + } + + i = kfifo_in(&smux_loop_pkt_fifo, + &send_pkt, + sizeof(struct smux_pkt_t *)); + if (i < 0) { + pr_err("%s: fifo error\n", __func__); + ret = -ENOMEM; + goto unlock; + } + queue_work(smux_loopback_wq, &smux_loopback_work); + ret = 0; + +unlock: + spin_unlock_irqrestore(&hw_fn_lock, flags); +out: + return ret; +} + +/** + * Receive loopback byte processor. + * + * @pkt Incoming packet + */ +static void smux_loopback_rx_byte(struct smux_pkt_t *pkt) +{ + static int simulated_retry_cnt; + const char ack = SMUX_WAKEUP_ACK; + + switch (pkt->hdr.flags) { + case SMUX_WAKEUP_REQ: + /* reply with ACK after appropriate delays */ + ++simulated_retry_cnt; + if (simulated_retry_cnt >= smux_simulate_wakeup_delay) { + pr_err("%s: completed %d of %d\n", + __func__, simulated_retry_cnt, + smux_simulate_wakeup_delay); + pr_err("%s: simulated wakeup\n", __func__); + simulated_retry_cnt = 0; + smux_rx_state_machine(&ack, 1, 0); + } else { + /* force retry */ + pr_err("%s: dropping wakeup request %d of %d\n", + __func__, simulated_retry_cnt, + smux_simulate_wakeup_delay); + } + break; + case SMUX_WAKEUP_ACK: + /* this shouldn't happen since we don't send requests */ + pr_err("%s: wakeup ACK unexpected\n", __func__); + break; + + default: + /* invalid character */ + pr_err("%s: invalid character 0x%x\n", + __func__, (unsigned)pkt->hdr.flags); + break; + } +} + +/** + * Simulated remote hardware used for local loopback testing. + * + * @work Not used + */ +static void smux_loopback_rx_worker(struct work_struct *work) +{ + struct smux_pkt_t *pkt; + struct smux_pkt_t reply_pkt; + char *data; + int len; + int lcid; + int i; + unsigned long flags; + + data = kzalloc(SMUX_MAX_PKT_SIZE, GFP_ATOMIC); + + spin_lock_irqsave(&hw_fn_lock, flags); + while (kfifo_len(&smux_loop_pkt_fifo) >= sizeof(struct smux_pkt_t *)) { + i = kfifo_out(&smux_loop_pkt_fifo, &pkt, + sizeof(struct smux_pkt_t *)); + spin_unlock_irqrestore(&hw_fn_lock, flags); + + if (pkt->hdr.magic != SMUX_MAGIC) { + pr_err("%s: invalid magic %x\n", __func__, + pkt->hdr.magic); + return; + } + + lcid = pkt->hdr.lcid; + if (smux_assert_lch_id(lcid)) { + pr_err("%s: invalid channel id %d\n", __func__, lcid); + return; + } + + switch (pkt->hdr.cmd) { + case SMUX_CMD_OPEN_LCH: + if (pkt->hdr.flags & SMUX_CMD_OPEN_ACK) + break; + + /* Reply with Open ACK */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_OPEN_LCH; + reply_pkt.hdr.flags = SMUX_CMD_OPEN_ACK + | SMUX_CMD_OPEN_POWER_COLLAPSE; + reply_pkt.hdr.payload_len = 0; + reply_pkt.hdr.pad_len = 0; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + + /* Send Remote Open */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_OPEN_LCH; + reply_pkt.hdr.flags = SMUX_CMD_OPEN_POWER_COLLAPSE; + reply_pkt.hdr.payload_len = 0; + reply_pkt.hdr.pad_len = 0; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + break; + + case SMUX_CMD_CLOSE_LCH: + if (pkt->hdr.flags == SMUX_CMD_CLOSE_ACK) + break; + + /* Reply with Close ACK */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_CLOSE_LCH; + reply_pkt.hdr.flags = SMUX_CMD_CLOSE_ACK; + reply_pkt.hdr.payload_len = 0; + reply_pkt.hdr.pad_len = 0; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + + /* Send Remote Close */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_CLOSE_LCH; + reply_pkt.hdr.flags = 0; + reply_pkt.hdr.payload_len = 0; + reply_pkt.hdr.pad_len = 0; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + break; + + case SMUX_CMD_DATA: + /* Echo back received data */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_DATA; + reply_pkt.hdr.flags = 0; + reply_pkt.hdr.payload_len = pkt->hdr.payload_len; + reply_pkt.payload = pkt->payload; + reply_pkt.hdr.pad_len = pkt->hdr.pad_len; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + break; + + case SMUX_CMD_STATUS: + /* Echo back received status */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_STATUS; + reply_pkt.hdr.flags = pkt->hdr.flags; + reply_pkt.hdr.payload_len = 0; + reply_pkt.payload = NULL; + reply_pkt.hdr.pad_len = pkt->hdr.pad_len; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + break; + + case SMUX_CMD_PWR_CTL: + /* reply with ack */ + smux_init_pkt(&reply_pkt); + reply_pkt.hdr.lcid = lcid; + reply_pkt.hdr.cmd = SMUX_CMD_PWR_CTL; + reply_pkt.hdr.flags = SMUX_CMD_PWR_CTL_SLEEP_REQ + | SMUX_CMD_PWR_CTL_ACK; + reply_pkt.hdr.payload_len = 0; + reply_pkt.payload = NULL; + reply_pkt.hdr.pad_len = pkt->hdr.pad_len; + smux_serialize(&reply_pkt, data, &len); + smux_rx_state_machine(data, len, 0); + break; + + case SMUX_CMD_BYTE: + smux_loopback_rx_byte(pkt); + break; + + default: + pr_err("%s: unknown command %d\n", + __func__, pkt->hdr.cmd); + break; + }; + + smux_free_pkt(pkt); + spin_lock_irqsave(&hw_fn_lock, flags); + } + spin_unlock_irqrestore(&hw_fn_lock, flags); + kfree(data); +} diff --git a/drivers/tty/smux_loopback.h b/drivers/tty/smux_loopback.h new file mode 100644 index 0000000000000000000000000000000000000000..85c6c23e62e418ede6238116b279f2336c5ac4bc --- /dev/null +++ b/drivers/tty/smux_loopback.h @@ -0,0 +1,39 @@ +/* drivers/tty/smux_loopback.h + * + * Copyright (c) 2012, 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 SMUX_LOOPBACK_H +#define SMUX_LOOPBACK_H + +#include "smux_private.h" + +#ifdef CONFIG_N_SMUX_LOOPBACK + +int smux_loopback_init(void); +int smux_tx_loopback(struct smux_pkt_t *pkt_ptr); + +#else +static inline int smux_loopback_init(void) +{ + return 0; +} + +static inline int smux_tx_loopback(struct smux_pkt_t *pkt_ptr) +{ + return -ENODEV; +} + + +#endif /* CONFIG_N_SMUX_LOOPBACK */ +#endif /* SMUX_LOOPBACK_H */ + diff --git a/drivers/tty/smux_private.h b/drivers/tty/smux_private.h new file mode 100644 index 0000000000000000000000000000000000000000..5ce8fb8ae0cb70dd6bf1f3f5070f2883e163ae2b --- /dev/null +++ b/drivers/tty/smux_private.h @@ -0,0 +1,115 @@ +/* drivers/tty/smux_private.h + * + * Copyright (c) 2012, 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 SMUX_PRIVATE_H +#define SMUX_PRIVATE_H + +#define SMUX_MAX_PKT_SIZE 8192 + +/* SMUX Protocol Characters */ +#define SMUX_MAGIC 0x33FC +#define SMUX_MAGIC_WORD1 0xFC +#define SMUX_MAGIC_WORD2 0x33 +#define SMUX_WAKEUP_REQ 0xFD +#define SMUX_WAKEUP_ACK 0xFE + +/* Unit testing characters */ +#define SMUX_UT_ECHO_REQ 0xF0 +#define SMUX_UT_ECHO_ACK_OK 0xF1 +#define SMUX_UT_ECHO_ACK_FAIL 0xF2 + +struct tty_struct; + +/* Packet header. */ +struct smux_hdr_t { + uint16_t magic; + uint8_t flags; + uint8_t cmd; + uint8_t pad_len; + uint8_t lcid; + uint16_t payload_len; +}; + +/* Internal packet structure. */ +struct smux_pkt_t { + struct smux_hdr_t hdr; + int allocated; + unsigned char *payload; + int free_payload; + struct list_head list; + void *priv; +}; + +/* SMUX Packet Commands */ +enum { + SMUX_CMD_DATA = 0x0, + SMUX_CMD_OPEN_LCH = 0x1, + SMUX_CMD_CLOSE_LCH = 0x2, + SMUX_CMD_STATUS = 0x3, + SMUX_CMD_PWR_CTL = 0x4, + + SMUX_CMD_BYTE, /* for internal usage */ + SMUX_NUM_COMMANDS +}; + +/* Open command flags */ +enum { + SMUX_CMD_OPEN_ACK = 1 << 0, + SMUX_CMD_OPEN_POWER_COLLAPSE = 1 << 1, + SMUX_CMD_OPEN_REMOTE_LOOPBACK = 1 << 2, +}; + +/* Close command flags */ +enum { + SMUX_CMD_CLOSE_ACK = 1 << 0, +}; + +/* Power command flags */ +enum { + SMUX_CMD_PWR_CTL_ACK = 1 << 0, + SMUX_CMD_PWR_CTL_SLEEP_REQ = 1 << 1, +}; + +/* Local logical channel states */ +enum { + SMUX_LCH_LOCAL_CLOSED, + SMUX_LCH_LOCAL_OPENING, + SMUX_LCH_LOCAL_OPENED, + SMUX_LCH_LOCAL_CLOSING, +}; + +/* Remote logical channel states */ +enum { + SMUX_LCH_REMOTE_CLOSED, + SMUX_LCH_REMOTE_OPENED, +}; + + +int smux_assert_lch_id(uint32_t lcid); +void smux_init_pkt(struct smux_pkt_t *pkt); +struct smux_pkt_t *smux_alloc_pkt(void); +int smux_alloc_pkt_payload(struct smux_pkt_t *pkt); +void smux_free_pkt(struct smux_pkt_t *pkt); +int smux_serialize(struct smux_pkt_t *pkt, char *out, + unsigned int *out_len); + +void smux_rx_state_machine(const unsigned char *data, int len, int flag); +void smuxld_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count); + +/* testing parameters */ +extern int smux_byte_loopback; +extern int smux_simulate_wakeup_delay; + +#endif /* SMUX_PRIVATE_H */ diff --git a/drivers/tty/smux_test.c b/drivers/tty/smux_test.c new file mode 100644 index 0000000000000000000000000000000000000000..242c66e6e90e0fd7c7a1624a37991b9f4650cb45 --- /dev/null +++ b/drivers/tty/smux_test.c @@ -0,0 +1,1222 @@ +/* drivers/tty/smux_test.c + * + * Copyright (c) 2012, 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 "smux_private.h" + +#define DEBUG_BUFMAX 4096 + +/** + * Unit test assertion for logging test cases. + * + * @a lval + * @b rval + * @cmp comparison operator + * + * Assertion fails if (@a cmp @b) is not true which then + * logs the function and line number where the error occurred + * along with the values of @a and @b. + * + * Assumes that the following local variables exist: + * @buf - buffer to write failure message to + * @i - number of bytes written to buffer + * @max - maximum size of the buffer + * @failed - set to true if test fails + */ +#define UT_ASSERT_INT(a, cmp, b) \ + if (!((a)cmp(b))) { \ + i += scnprintf(buf + i, max - i, \ + "%s:%d Fail: " #a "(%d) " #cmp " " #b "(%d)\n", \ + __func__, __LINE__, \ + a, b); \ + failed = 1; \ + break; \ + } \ + do {} while (0) + +#define UT_ASSERT_PTR(a, cmp, b) \ + if (!((a)cmp(b))) { \ + i += scnprintf(buf + i, max - i, \ + "%s:%d Fail: " #a "(%p) " #cmp " " #b "(%p)\n", \ + __func__, __LINE__, \ + a, b); \ + failed = 1; \ + break; \ + } \ + do {} while (0) + +#define UT_ASSERT_UINT(a, cmp, b) \ + if (!((a)cmp(b))) { \ + i += scnprintf(buf + i, max - i, \ + "%s:%d Fail: " #a "(%u) " #cmp " " #b "(%u)\n", \ + __func__, __LINE__, \ + a, b); \ + failed = 1; \ + break; \ + } \ + do {} while (0) + +static unsigned char test_array[] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, + 89, 144, 233}; + +/* Used for mapping local to remote TIOCM signals */ +struct tiocm_test_vector { + uint32_t input; + uint32_t set_old; + uint32_t set_new; + uint32_t clr_old; +}; + +/** + * Allocates a new buffer for SMUX for every call. + */ +int get_rx_buffer(void *priv, void **pkt_priv, void **buffer, int size) +{ + void *rx_buf; + + rx_buf = kmalloc(size, GFP_ATOMIC); + *pkt_priv = (void *)0x1234; + *buffer = rx_buf; + + return 0; +} + +/* Test vector for packet tests. */ +struct test_vector { + const char *data; + const unsigned len; +}; + +/* Mock object metadata for SMUX_READ_DONE event */ +struct mock_read_event { + struct list_head list; + struct smux_meta_read meta; +}; + +/* Mock object metadata for SMUX_WRITE_DONE event */ +struct mock_write_event { + struct list_head list; + struct smux_meta_write meta; +}; + +/* Mock object for all SMUX callback events */ +struct smux_mock_callback { + int cb_count; + struct completion cb_completion; + spinlock_t lock; + + /* status changes */ + int event_connected; + int event_disconnected; + int event_disconnected_ssr; + int event_low_wm; + int event_high_wm; + + /* TIOCM changes */ + int event_tiocm; + struct smux_meta_tiocm tiocm_meta; + + /* read event data */ + int event_read_done; + int event_read_failed; + struct list_head read_events; + + /* write event data */ + int event_write_done; + int event_write_failed; + struct list_head write_events; +}; + +/** + * Initialize mock callback data. Only call once. + * + * @cb Mock callback data + */ +void mock_cb_data_init(struct smux_mock_callback *cb) +{ + init_completion(&cb->cb_completion); + spin_lock_init(&cb->lock); + INIT_LIST_HEAD(&cb->read_events); + INIT_LIST_HEAD(&cb->write_events); +} + +/** + * Reset mock callback data to default values. + * + * @cb Mock callback data + * + * All packets are freed and counters reset to zero. + */ +void mock_cb_data_reset(struct smux_mock_callback *cb) +{ + cb->cb_count = 0; + INIT_COMPLETION(cb->cb_completion); + cb->event_connected = 0; + cb->event_disconnected = 0; + cb->event_disconnected_ssr = 0; + cb->event_low_wm = 0; + cb->event_high_wm = 0; + cb->event_tiocm = 0; + cb->tiocm_meta.tiocm_old = 0; + cb->tiocm_meta.tiocm_new = 0; + + cb->event_read_done = 0; + cb->event_read_failed = 0; + while (!list_empty(&cb->read_events)) { + struct mock_read_event *meta; + meta = list_first_entry(&cb->read_events, + struct mock_read_event, + list); + kfree(meta->meta.buffer); + list_del(&meta->list); + kfree(meta); + } + + cb->event_write_done = 0; + cb->event_write_failed = 0; + while (!list_empty(&cb->write_events)) { + struct mock_write_event *meta; + meta = list_first_entry(&cb->write_events, + struct mock_write_event, + list); + list_del(&meta->list); + kfree(meta); + } +} + +/** + * Dump the values of the mock callback data for debug purposes. + * + * @cb Mock callback data + * @buf Print buffer + * @max Maximum number of characters to print + * + * @returns Number of characters added to buffer + */ +static int mock_cb_data_print(const struct smux_mock_callback *cb, + char *buf, int max) +{ + int i = 0; + + i += scnprintf(buf + i, max - i, + "\tcb_count=%d\n" + "\tcb_completion.done=%d\n" + "\tevent_connected=%d\n" + "\tevent_disconnected=%d\n" + "\tevent_disconnected_ssr=%d\n" + "\tevent_low_wm=%d\n" + "\tevent_high_wm=%d\n" + "\tevent_tiocm=%d\n" + "\tevent_read_done=%d\n" + "\tevent_read_failed=%d\n" + "\tread_events=%d\n" + "\tevent_write_done=%d\n" + "\tevent_write_failed=%d\n" + "\twrite_events=%d\n", + cb->cb_count, + cb->cb_completion.done, + cb->event_connected, + cb->event_disconnected, + cb->event_disconnected_ssr, + cb->event_low_wm, + cb->event_high_wm, + cb->event_tiocm, + cb->event_read_done, + cb->event_read_failed, + !list_empty(&cb->read_events), + cb->event_write_done, + cb->event_write_failed, + list_empty(&cb->write_events) + ); + + return i; +} + +/** + * Mock object event callback. Used to logs events for analysis in the unit + * tests. + */ +void smux_mock_cb(void *priv, int event, const void *metadata) +{ + struct smux_mock_callback *cb_data_ptr; + struct mock_write_event *write_event_meta; + struct mock_read_event *read_event_meta; + unsigned long flags; + + cb_data_ptr = (struct smux_mock_callback *)priv; + if (cb_data_ptr == NULL) { + pr_err("%s: invalid private data\n", __func__); + return; + } + + spin_lock_irqsave(&cb_data_ptr->lock, flags); + switch (event) { + case SMUX_CONNECTED: + ++cb_data_ptr->event_connected; + break; + + case SMUX_DISCONNECTED: + ++cb_data_ptr->event_disconnected; + cb_data_ptr->event_disconnected_ssr = + ((struct smux_meta_disconnected *)metadata)->is_ssr; + break; + + case SMUX_READ_DONE: + ++cb_data_ptr->event_read_done; + read_event_meta = kmalloc(sizeof(struct mock_read_event), + GFP_ATOMIC); + if (read_event_meta) { + read_event_meta->meta = + *(struct smux_meta_read *)metadata; + list_add_tail(&read_event_meta->list, + &cb_data_ptr->read_events); + } + break; + + case SMUX_READ_FAIL: + ++cb_data_ptr->event_read_failed; + read_event_meta = kmalloc(sizeof(struct mock_read_event), + GFP_ATOMIC); + if (read_event_meta) { + read_event_meta->meta = + *(struct smux_meta_read *)metadata; + list_add_tail(&read_event_meta->list, + &cb_data_ptr->read_events); + } + break; + + case SMUX_WRITE_DONE: + ++cb_data_ptr->event_write_done; + write_event_meta = kmalloc(sizeof(struct mock_write_event), + GFP_ATOMIC); + if (write_event_meta) { + write_event_meta->meta = + *(struct smux_meta_write *)metadata; + list_add_tail(&write_event_meta->list, + &cb_data_ptr->write_events); + } + break; + + case SMUX_WRITE_FAIL: + ++cb_data_ptr->event_write_failed; + write_event_meta = kmalloc(sizeof(struct mock_write_event), + GFP_ATOMIC); + if (write_event_meta) { + write_event_meta->meta = + *(struct smux_meta_write *)metadata; + list_add_tail(&write_event_meta->list, + &cb_data_ptr->write_events); + } + break; + + case SMUX_LOW_WM_HIT: + ++cb_data_ptr->event_low_wm; + break; + + case SMUX_HIGH_WM_HIT: + ++cb_data_ptr->event_high_wm; + break; + + case SMUX_TIOCM_UPDATE: + ++cb_data_ptr->event_tiocm; + cb_data_ptr->tiocm_meta = *(struct smux_meta_tiocm *)metadata; + break; + + default: + pr_err("%s: unknown event %d\n", __func__, event); + }; + + ++cb_data_ptr->cb_count; + complete(&cb_data_ptr->cb_completion); + spin_unlock_irqrestore(&cb_data_ptr->lock, flags); +} + +/** + * Test Read/write usage. + * + * @buf Output buffer for failure/status messages + * @max Size of @buf + * @vectors Test vector data (must end with NULL item) + * @name Name of the test case for failure messages + * + * Perform a sanity test consisting of opening a port, writing test packet(s), + * reading the response(s), and closing the port. + * + * The port should already be configured to use either local or remote + * loopback. + */ +static int smux_ut_basic_core(char *buf, int max, + const struct test_vector *vectors, + const char *name) +{ + int i = 0; + int failed = 0; + static struct smux_mock_callback cb_data; + static int cb_initialized; + int ret; + + if (!cb_initialized) + mock_cb_data_init(&cb_data); + + mock_cb_data_reset(&cb_data); + while (!failed) { + struct mock_write_event *write_event; + struct mock_read_event *read_event; + + /* open port */ + ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, + get_rx_buffer); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_connected, ==, 1); + mock_cb_data_reset(&cb_data); + + /* write, read, and verify the test vector data */ + for (; vectors->data != NULL; ++vectors) { + const char *test_data = vectors->data; + const unsigned test_len = vectors->len; + + i += scnprintf(buf + i, max - i, + "Writing vector %p len %d\n", + test_data, test_len); + + /* write data */ + msm_smux_write(SMUX_TEST_LCID, (void *)0xCAFEFACE, + test_data, test_len); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + + /* wait for write and echo'd read to complete */ + INIT_COMPLETION(cb_data.cb_completion); + if (cb_data.cb_count < 2) + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + + UT_ASSERT_INT(cb_data.cb_count, >=, 1); + UT_ASSERT_INT(cb_data.event_write_done, ==, 1); + UT_ASSERT_INT(list_empty(&cb_data.write_events), ==, 0); + + write_event = list_first_entry(&cb_data.write_events, + struct mock_write_event, list); + UT_ASSERT_PTR(write_event->meta.pkt_priv, ==, + (void *)0xCAFEFACE); + UT_ASSERT_PTR(write_event->meta.buffer, ==, + (void *)test_data); + UT_ASSERT_INT(write_event->meta.len, ==, test_len); + + /* verify read event */ + UT_ASSERT_INT(cb_data.event_read_done, ==, 1); + UT_ASSERT_INT(list_empty(&cb_data.read_events), ==, 0); + read_event = list_first_entry(&cb_data.read_events, + struct mock_read_event, list); + UT_ASSERT_PTR(read_event->meta.pkt_priv, ==, + (void *)0x1234); + UT_ASSERT_PTR(read_event->meta.buffer, !=, NULL); + + if (read_event->meta.len != test_len || + memcmp(read_event->meta.buffer, + test_data, test_len)) { + /* data mismatch */ + char linebuff[80]; + + hex_dump_to_buffer(test_data, test_len, + 16, 1, linebuff, sizeof(linebuff), 1); + i += scnprintf(buf + i, max - i, + "Expected:\n%s\n\n", linebuff); + + hex_dump_to_buffer(read_event->meta.buffer, + read_event->meta.len, + 16, 1, linebuff, sizeof(linebuff), 1); + i += scnprintf(buf + i, max - i, + "Actual:\n%s\n", linebuff); + failed = 1; + break; + } + mock_cb_data_reset(&cb_data); + } + + /* close port */ + ret = msm_smux_close(SMUX_TEST_LCID); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); + break; + } + + if (!failed) { + i += scnprintf(buf + i, max - i, "\tOK\n"); + } else { + pr_err("%s: Failed\n", name); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + i += mock_cb_data_print(&cb_data, buf + i, max - i); + msm_smux_close(SMUX_TEST_LCID); + } + + mock_cb_data_reset(&cb_data); + return i; +} + +/** + * Verify Basic Local Loopback Support + * + * Perform a sanity test consisting of opening a port in local loopback + * mode and writing a packet and reading the echo'd packet back. + */ +static int smux_ut_basic(char *buf, int max) +{ + const struct test_vector test_data[] = { + {"hello\0world\n", sizeof("hello\0world\n")}, + {0, 0}, + }; + int i = 0; + int failed = 0; + int ret; + + i += scnprintf(buf + i, max - i, "Running %s\n", __func__); + while (!failed) { + /* enable loopback mode */ + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); + UT_ASSERT_INT(ret, ==, 0); + + i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); + break; + } + + if (failed) { + pr_err("%s: Failed\n", __func__); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + } + return i; +} + +/** + * Verify Basic Remote Loopback Support + * + * Perform a sanity test consisting of opening a port in remote loopback + * mode and writing a packet and reading the echo'd packet back. + */ +static int smux_ut_remote_basic(char *buf, int max) +{ + const struct test_vector test_data[] = { + {"hello\0world\n", sizeof("hello\0world\n")}, + {0, 0}, + }; + int i = 0; + int failed = 0; + int ret; + + i += scnprintf(buf + i, max - i, "Running %s\n", __func__); + while (!failed) { + /* enable remote mode */ + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); + UT_ASSERT_INT(ret, ==, 0); + + i += smux_ut_basic_core(buf + i, max - i, test_data, __func__); + break; + } + + if (failed) { + pr_err("%s: Failed\n", __func__); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + } + return i; +} + +/** + * Fill test pattern into provided buffer including an optional + * redzone 16 bytes before and 16 bytes after the buffer. + * + * buf --------- + * redzone + * --------- <- returned pointer + * data + * --------- <- returned pointer + len + * redzone + * --------- + * + * @buf Pointer to the buffer of size len or len+32 (redzone) + * @len Length of the *data* buffer (excluding 32-byte redzone) + * @redzone If true, adds redzone data + * + * @returns pointer to buffer (buf + 16 if redzone enabled) + */ +uint8_t *test_pattern_fill(char *buf, int len, int redzone) +{ + void *ret; + uint8_t ch; + + ret = buf; + if (redzone) { + memset((char *)buf, 0xAB, 16); + memset((char *)buf + len, 0xBA, 16); + ret += 16; + } + + /* fill with test pattern */ + for (ch = 0; len > 0; --len, ++ch) + *buf++ = (char)ch; + + return ret; +} + +/** + * Verify test pattern generated by test_pattern_fill. + * + * @buf_ptr Pointer to buffer pointer + * @len Length of the *data* buffer (excluding 32-byte redzone) + * @redzone If true, verifies redzone and adjusts *buf_ptr + * @errmsg Buffer for error message + * @errmsg_max Size of error message buffer + * + * @returns 0 for success; length of error message otherwise + */ +unsigned test_pattern_verify(char **buf_ptr, int len, int redzone, + char *errmsg, int errmsg_max) +{ + int n; + int i = 0; + char linebuff[80]; + + if (redzone) { + *buf_ptr -= 16; + + /* verify prefix redzone */ + for (n = 0; n < 16; ++n) { + if (*buf_ptr[n] != 0xAB) { + hex_dump_to_buffer(*buf_ptr, 16, + 16, 1, linebuff, sizeof(linebuff), 1); + i += scnprintf(errmsg + i, errmsg_max - i, + "Redzone violation: %s\n", linebuff); + break; + } + } + + /* verify postfix redzone */ + for (n = 0; n < 16; ++n) { + if (*buf_ptr[len + n] != 0xBA) { + hex_dump_to_buffer(&(*buf_ptr)[len], 16, + 16, 1, linebuff, sizeof(linebuff), 1); + i += scnprintf(errmsg + i, errmsg_max - i, + "Redzone violation: %s\n", linebuff); + break; + } + } + } + return i; +} + +/** + * Write a multiple packets in ascending size and verify packet is received + * correctly. + * + * @buf Buffer for status message + * @max Size of buffer + * @name Name of the test for error reporting + * + * @returns Number of bytes written to @buf + * + * Requires that the port already be opened and loopback mode is + * configured correctly (if required). + */ +static int smux_ut_loopback_big_pkt(char *buf, int max, const char *name) +{ + struct test_vector test_data[] = { + {0, 64}, + {0, 128}, + {0, 256}, + {0, 512}, + {0, 1024}, + {0, 2048}, + {0, 4096}, + {0, 0}, + }; + int i = 0; + int failed = 0; + struct test_vector *tv; + + /* generate test data */ + for (tv = test_data; tv->len > 0; ++tv) { + tv->data = kmalloc(tv->len + 32, GFP_KERNEL); + pr_err("%s: allocating %p len %d\n", + __func__, tv->data, tv->len); + if (!tv->data) { + i += scnprintf(buf + i, max - i, + "%s: Unable to allocate %d bytes\n", + __func__, tv->len); + failed = 1; + goto out; + } + test_pattern_fill((uint8_t *)tv->data, tv->len, 1); + } + + /* run test */ + i += scnprintf(buf + i, max - i, "Running %s\n", name); + while (!failed) { + i += smux_ut_basic_core(buf + i, max - i, test_data, name); + break; + } + +out: + if (failed) { + pr_err("%s: Failed\n", name); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + } + + for (tv = test_data; tv->len > 0; ++tv) { + if (!tv->data) { + i += test_pattern_verify((char **)&tv->data, + tv->len, 1, buf + i, max - i); + pr_err("%s: freeing %p len %d\n", __func__, + tv->data, tv->len); + kfree(tv->data); + } + } + + return i; +} + +/** + * Verify Large-packet Local Loopback Support. + * + * @buf Buffer for status message + * @max Size of buffer + * + * @returns Number of bytes written to @buf + * + * Open port in local loopback mode and write a multiple packets in ascending + * size and verify packet is received correctly. + */ +static int smux_ut_local_big_pkt(char *buf, int max) +{ + int i = 0; + int ret; + + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); + + if (ret == 0) { + smux_byte_loopback = SMUX_TEST_LCID; + i += smux_ut_loopback_big_pkt(buf, max, __func__); + smux_byte_loopback = 0; + } else { + i += scnprintf(buf + i, max - i, + "%s: Unable to set loopback mode\n", + __func__); + } + + return i; +} + +/** + * Verify Large-packet Remote Loopback Support. + * + * @buf Buffer for status message + * @max Size of buffer + * + * @returns Number of bytes written to @buf + * + * Open port in remote loopback mode and write a multiple packets in ascending + * size and verify packet is received correctly. + */ +static int smux_ut_remote_big_pkt(char *buf, int max) +{ + int i = 0; + int ret; + + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); + if (ret == 0) { + i += smux_ut_loopback_big_pkt(buf, max, __func__); + } else { + i += scnprintf(buf + i, max - i, + "%s: Unable to set loopback mode\n", + __func__); + } + + return i; +} + +/** + * Verify set and get operations for each TIOCM bit. + * + * @buf Buffer for status message + * @max Size of buffer + * @name Name of the test for error reporting + * + * @returns Number of bytes written to @buf + */ +static int smux_ut_tiocm(char *buf, int max, const char *name) +{ + static struct smux_mock_callback cb_data; + static int cb_initialized; + static const struct tiocm_test_vector tiocm_vectors[] = { + /* bit to set, set old, set new, clear old */ + {TIOCM_DTR, TIOCM_DTR, TIOCM_DTR | TIOCM_DSR, TIOCM_DSR}, + {TIOCM_RTS, TIOCM_RTS, TIOCM_RTS | TIOCM_CTS, TIOCM_CTS}, + {TIOCM_RI, 0x0, TIOCM_RI, TIOCM_RI}, + {TIOCM_CD, 0x0, TIOCM_CD, TIOCM_CD}, + }; + int i = 0; + int failed = 0; + int n; + int ret; + + i += scnprintf(buf + i, max - i, "Running %s\n", name); + + if (!cb_initialized) + mock_cb_data_init(&cb_data); + + mock_cb_data_reset(&cb_data); + while (!failed) { + /* open port */ + ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, + get_rx_buffer); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_connected, ==, 1); + mock_cb_data_reset(&cb_data); + + /* set and clear each TIOCM bit */ + for (n = 0; n < ARRAY_SIZE(tiocm_vectors) && !failed; ++n) { + /* set signal and verify */ + ret = msm_smux_tiocm_set(SMUX_TEST_LCID, + tiocm_vectors[n].input, 0x0); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_tiocm, ==, 1); + UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_old, ==, + tiocm_vectors[n].set_old); + UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_new, ==, + tiocm_vectors[n].set_new); + mock_cb_data_reset(&cb_data); + + /* clear signal and verify */ + ret = msm_smux_tiocm_set(SMUX_TEST_LCID, 0x0, + tiocm_vectors[n].input); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_tiocm, ==, 1); + UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_old, ==, + tiocm_vectors[n].clr_old); + UT_ASSERT_INT(cb_data.tiocm_meta.tiocm_new, ==, 0x0); + mock_cb_data_reset(&cb_data); + } + if (failed) + break; + + /* close port */ + ret = msm_smux_close(SMUX_TEST_LCID); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); + break; + } + + if (!failed) { + i += scnprintf(buf + i, max - i, "\tOK\n"); + } else { + pr_err("%s: Failed\n", name); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + i += mock_cb_data_print(&cb_data, buf + i, max - i); + msm_smux_close(SMUX_TEST_LCID); + } + + mock_cb_data_reset(&cb_data); + return i; +} + +/** + * Verify TIOCM Status Bits for local loopback. + * + * @buf Buffer for status message + * @max Size of buffer + * + * @returns Number of bytes written to @buf + */ +static int smux_ut_local_tiocm(char *buf, int max) +{ + int i = 0; + int ret; + + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); + + if (ret == 0) { + smux_byte_loopback = SMUX_TEST_LCID; + i += smux_ut_tiocm(buf, max, __func__); + smux_byte_loopback = 0; + } else { + i += scnprintf(buf + i, max - i, + "%s: Unable to set loopback mode\n", + __func__); + } + + return i; +} + +/** + * Verify TIOCM Status Bits for remote loopback. + * + * @buf Buffer for status message + * @max Size of buffer + * + * @returns Number of bytes written to @buf + */ +static int smux_ut_remote_tiocm(char *buf, int max) +{ + int i = 0; + int ret; + + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_REMOTE_LOOPBACK, 0); + if (ret == 0) { + i += smux_ut_tiocm(buf, max, __func__); + } else { + i += scnprintf(buf + i, max - i, + "%s: Unable to set loopback mode\n", + __func__); + } + + return i; +} + +/** + * Verify High/Low Watermark notifications. + * + * @buf Buffer for status message + * @max Size of buffer + * + * @returns Number of bytes written to @buf + */ +static int smux_ut_local_wm(char *buf, int max) +{ + static struct smux_mock_callback cb_data; + static int cb_initialized; + int i = 0; + int failed = 0; + int ret; + + i += scnprintf(buf + i, max - i, "Running %s\n", __func__); + pr_err("%s", buf); + + if (!cb_initialized) + mock_cb_data_init(&cb_data); + + mock_cb_data_reset(&cb_data); + smux_byte_loopback = SMUX_TEST_LCID; + while (!failed) { + /* open port for loopback with TX disabled */ + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_LOCAL_LOOPBACK + | SMUX_CH_OPTION_REMOTE_TX_STOP, + 0); + UT_ASSERT_INT(ret, ==, 0); + + ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, + get_rx_buffer); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_connected, ==, 1); + mock_cb_data_reset(&cb_data); + + /* transmit 4 packets and verify high-watermark notification */ + ret = 0; + ret |= msm_smux_write(SMUX_TEST_LCID, (void *)1, + test_array, sizeof(test_array)); + ret |= msm_smux_write(SMUX_TEST_LCID, (void *)2, + test_array, sizeof(test_array)); + ret |= msm_smux_write(SMUX_TEST_LCID, (void *)3, + test_array, sizeof(test_array)); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 0); + UT_ASSERT_INT(cb_data.event_high_wm, ==, 0); + + ret = msm_smux_write(SMUX_TEST_LCID, (void *)4, + test_array, sizeof(test_array)); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + UT_ASSERT_INT(cb_data.event_high_wm, ==, 1); + UT_ASSERT_INT(cb_data.event_low_wm, ==, 0); + mock_cb_data_reset(&cb_data); + + /* exceed watermark and verify failure return value */ + ret = msm_smux_write(SMUX_TEST_LCID, (void *)5, + test_array, sizeof(test_array)); + UT_ASSERT_INT(ret, ==, -EAGAIN); + + /* re-enable TX and verify low-watermark notification */ + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + 0, SMUX_CH_OPTION_REMOTE_TX_STOP); + UT_ASSERT_INT(ret, ==, 0); + while (cb_data.cb_count < 9) { + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + INIT_COMPLETION(cb_data.cb_completion); + } + if (failed) + break; + + UT_ASSERT_INT(cb_data.event_high_wm, ==, 0); + UT_ASSERT_INT(cb_data.event_low_wm, ==, 1); + UT_ASSERT_INT(cb_data.event_write_done, ==, 4); + mock_cb_data_reset(&cb_data); + + /* close port */ + ret = msm_smux_close(SMUX_TEST_LCID); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); + break; + } + + if (!failed) { + i += scnprintf(buf + i, max - i, "\tOK\n"); + } else { + pr_err("%s: Failed\n", __func__); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + i += mock_cb_data_print(&cb_data, buf + i, max - i); + msm_smux_close(SMUX_TEST_LCID); + } + smux_byte_loopback = 0; + mock_cb_data_reset(&cb_data); + return i; +} + +/** + * Verify smuxld_receive_buf regular and error processing. + * + * @buf Buffer for status message + * @max Size of buffer + * + * @returns Number of bytes written to @buf + */ +static int smux_ut_local_smuxld_receive_buf(char *buf, int max) +{ + static struct smux_mock_callback cb_data; + static int cb_initialized; + struct mock_read_event *meta; + int i = 0; + int failed = 0; + int ret; + char data[] = {SMUX_UT_ECHO_REQ, + SMUX_UT_ECHO_REQ, SMUX_UT_ECHO_REQ, + }; + char flags[] = {0x0, 0x1, 0x0,}; + + + i += scnprintf(buf + i, max - i, "Running %s\n", __func__); + + if (!cb_initialized) + mock_cb_data_init(&cb_data); + + mock_cb_data_reset(&cb_data); + smux_byte_loopback = SMUX_TEST_LCID; + while (!failed) { + /* open port for loopback */ + ret = msm_smux_set_ch_option(SMUX_TEST_LCID, + SMUX_CH_OPTION_LOCAL_LOOPBACK, 0); + UT_ASSERT_INT(ret, ==, 0); + + ret = msm_smux_open(SMUX_TEST_LCID, &cb_data, smux_mock_cb, + get_rx_buffer); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_connected, ==, 1); + mock_cb_data_reset(&cb_data); + + /* + * Verify RX error processing by sending 3 echo requests: + * one OK, one fail, and a final OK + * + * The parsing framework should process the requests + * and send us three BYTE command packets with + * ECHO ACK FAIL and ECHO ACK OK characters. + */ + smuxld_receive_buf(0, data, flags, sizeof(data)); + + /* verify response characters */ + do { + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), >, 0); + INIT_COMPLETION(cb_data.cb_completion); + } while (cb_data.cb_count < 3); + UT_ASSERT_INT(cb_data.cb_count, ==, 3); + UT_ASSERT_INT(cb_data.event_read_done, ==, 3); + + meta = list_first_entry(&cb_data.read_events, + struct mock_read_event, list); + UT_ASSERT_INT((int)meta->meta.pkt_priv, ==, + SMUX_UT_ECHO_ACK_OK); + list_del(&meta->list); + + meta = list_first_entry(&cb_data.read_events, + struct mock_read_event, list); + UT_ASSERT_INT((int)meta->meta.pkt_priv, ==, + SMUX_UT_ECHO_ACK_FAIL); + list_del(&meta->list); + + meta = list_first_entry(&cb_data.read_events, + struct mock_read_event, list); + UT_ASSERT_INT((int)meta->meta.pkt_priv, ==, + SMUX_UT_ECHO_ACK_OK); + list_del(&meta->list); + mock_cb_data_reset(&cb_data); + + /* close port */ + ret = msm_smux_close(SMUX_TEST_LCID); + UT_ASSERT_INT(ret, ==, 0); + UT_ASSERT_INT( + (int)wait_for_completion_timeout( + &cb_data.cb_completion, HZ), + >, 0); + UT_ASSERT_INT(cb_data.cb_count, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected, ==, 1); + UT_ASSERT_INT(cb_data.event_disconnected_ssr, ==, 0); + break; + } + + if (!failed) { + i += scnprintf(buf + i, max - i, "\tOK\n"); + } else { + pr_err("%s: Failed\n", __func__); + i += scnprintf(buf + i, max - i, "\tFailed\n"); + i += mock_cb_data_print(&cb_data, buf + i, max - i); + msm_smux_close(SMUX_TEST_LCID); + } + smux_byte_loopback = 0; + mock_cb_data_reset(&cb_data); + return i; +} + +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; + + if (*ppos != 0) + return 0; + + 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 int __init smux_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("n_smux", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + /* + * Add Unit Test entries. + * + * The idea with unit tests is that you can run all of them + * from ADB shell by doing: + * adb shell + * cat ut* + * + * And if particular tests fail, you can then repeatedly run the failing + * tests as you debug and resolve the failing test. + */ + debug_create("ut_local_basic", 0444, dent, smux_ut_basic); + debug_create("ut_remote_basic", 0444, dent, smux_ut_remote_basic); + debug_create("ut_local_big_pkt", 0444, dent, smux_ut_local_big_pkt); + debug_create("ut_remote_big_pkt", 0444, dent, smux_ut_remote_big_pkt); + debug_create("ut_local_tiocm", 0444, dent, smux_ut_local_tiocm); + debug_create("ut_remote_tiocm", 0444, dent, smux_ut_remote_tiocm); + debug_create("ut_local_wm", 0444, dent, smux_ut_local_wm); + debug_create("ut_local_smuxld_receive_buf", 0444, dent, + smux_ut_local_smuxld_receive_buf); + + return 0; +} + +late_initcall(smux_debugfs_init); + diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index d939bd705c71d82e6cc83af0ca05d0d3421beeda..a20347237170b8f87692776e9db4f978ded3f8d3 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -2576,8 +2576,11 @@ static int tty_tiocmset(struct tty_struct *tty, unsigned int cmd, clear = ~val; break; } - set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP; - clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP; + + 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, set, clear); } diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 76316a33061b91b48a62a60f6be1b921227c6ed8..ceefb23f40b6444298cfafb46339f44ed1acfa4c 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -133,8 +133,6 @@ source "drivers/usb/host/Kconfig" source "drivers/usb/musb/Kconfig" -source "drivers/usb/renesas_usbhs/Kconfig" - source "drivers/usb/class/Kconfig" source "drivers/usb/storage/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 53a7bc07dd8d7cd92d8ed940aa09cfb597b8f6d7..b1ec3fcdc5592a339f571e1664acfe2df6c47100 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -27,6 +27,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/ diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 9a56635dc19ceb980ea71986dd97bb9d9ea725e8..19e124463a13f44341ba92dc3fb9b03e04bdef7e 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1274,6 +1274,57 @@ 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 = NULL; + + /* + * The OTG-B device must hand back the host role to OTG PET + * within 100 msec irrespective of host_request flag. + */ + if (bus->quick_hnp) { + bus->quick_hnp = 0; + goto start_hnp; + } + + status = kmalloc(sizeof(*status), GFP_KERNEL); + if (!status) + goto reschedule; + + 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; + } + + if (!(*status & (1 << HOST_REQUEST_FLAG))) + goto reschedule; + +start_hnp: + 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"); + goto out; + +reschedule: + 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; @@ -1342,6 +1393,7 @@ int usb_resume(struct device *dev, pm_message_t msg) * (This can't be done in usb_resume_interface() * above because it doesn't own the right set of locks.) */ + pm_runtime_get_sync(dev->parent); status = usb_resume_both(udev, msg); if (status == 0) { pm_runtime_disable(dev); @@ -1349,6 +1401,7 @@ int usb_resume(struct device *dev, pm_message_t msg) pm_runtime_enable(dev); unbind_no_reset_resume_drivers_interfaces(udev); } + pm_runtime_put_sync(dev->parent); /* Avoid PM error messages for devices disconnected while suspended * as we'll display regular disconnect messages just a bit later. diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 140d3e11f2124291f7051fb6e743a10f74c8f541..7c37affffb82fe7c0f88b5c2c9ff1fc78615e58e 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -897,6 +897,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 } /*-------------------------------------------------------------------------*/ @@ -926,6 +929,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 ec6c97dadbe4dba5e3cf8a47980e23256a44bff5..5dceb41ead3675c9e93833958731c297aac04bde 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 @@ -358,8 +384,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); @@ -765,6 +794,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)); @@ -906,6 +939,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 */ @@ -1335,6 +1373,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 @@ -1676,6 +1715,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_sync(&udev->bus->hnp_polling); + udev->bus->hnp_support = 0; + } +#endif + usb_lock_device(udev); /* Free up all the children before we remove this device */ @@ -1757,6 +1803,7 @@ static int usb_enumerate_device_otg(struct usb_device *udev) int err = 0; #ifdef CONFIG_USB_OTG + bool old_otg = false; /* * OTG-aware devices on OTG-capable root hubs may be able to use SRP, * to wake us after we've powered off VBUS; and HNP, switching roles @@ -1780,15 +1827,34 @@ 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->bLength) == + USB_DT_OTG_SIZE) && + 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. + */ + old_otg = true; + /* 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, @@ -1797,26 +1863,47 @@ 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 ((udev->quirks & USB_QUIRK_OTG_PET)) { + if (le16_to_cpu(udev->descriptor.bcdDevice) & + OTG_TTST_VBUS_OFF) + udev->bus->otg_vbus_off = 1; + if (udev->bus->is_b_host || old_otg) + udev->bus->quick_hnp = 1; + } 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. + */ + if (udev->bus->quick_hnp) + schedule_delayed_work(&udev->bus->hnp_polling, + msecs_to_jiffies(OTG_TTST_SUSP)); + else + schedule_delayed_work(&udev->bus->hnp_polling, + msecs_to_jiffies(THOST_REQ_POLL)); } -fail: #endif return err; } @@ -2475,6 +2562,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 /* disable USB2 hardware LPM */ if (udev->usb2_hw_lpm_enabled == 1) @@ -3091,14 +3194,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, false); - 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, false); + 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, @@ -3366,6 +3477,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); @@ -3504,7 +3618,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: @@ -3581,6 +3720,11 @@ static void hub_events(void) u16 portstatus; u16 portchange; int i, ret; +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + int j; + int otgport = 0; + struct usb_port_status port_status; +#endif int connect_change, wakeup_change; /* @@ -3657,6 +3801,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); @@ -3761,9 +4070,19 @@ static void hub_events(void) HUB_BH_RESET_TIME, true); } - 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 */ @@ -3795,7 +4114,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 ca717da3be95ddbab8d4b5211e78af699fcb1399..b2da64ca8c98c750c26316a311a02e6551ae5954 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1770,6 +1770,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); @@ -1777,11 +1780,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 7728c91dfa2e61eb50b21d75f558a96342db60a2..617a364631ea153e0e0d2c68d84c202fc19e7a56 100644 --- a/drivers/usb/core/notify.c +++ b/drivers/usb/core/notify.c @@ -58,6 +58,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 e8cdce571bb1cb3ae4b1748638bf817b86839160..7bb6747b4f114573bad2ddc9bd97f655b198d720 100644 --- a/drivers/usb/core/otg_whitelist.h +++ b/drivers/usb/core/otg_whitelist.h @@ -59,6 +59,11 @@ static int is_targeted(struct usb_device *dev) le16_to_cpu(dev->descriptor.idProduct) == 0xbadd)) return 0; + /* OTG PET device is always targeted (see OTG 2.0 ECN 6.4.2) */ + if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a && + le16_to_cpu(dev->descriptor.idProduct) == 0x0200)) + return 1; + /* NOTE: can't use usb_match_id() since interface caches * aren't set up yet. this is cut/paste from that code. */ @@ -92,7 +97,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/quirks.c b/drivers/usb/core/quirks.c index 4c65eb6a867a1974d8596caaccc9d3d3f15152ef..200dc9bfeec848c8fdc2a8f84f220666fa1498b6 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -150,6 +150,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, + /* Protocol and OTG Electrical Test Device */ + { USB_DEVICE(0x1a0a, 0x0200), .driver_info = USB_QUIRK_OTG_PET }, + { } /* terminating entry must be last */ }; diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 71648dcbe4386a98cf5e571516b3186d3b3ba749..4f40547d6df83d938bf4d0f46fc02aba62295a7b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -104,6 +104,10 @@ static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) } #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; @@ -153,6 +157,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/dwc3/Makefile b/drivers/usb/dwc3/Makefile index d441fe4c180b9f9d15c1c34f8428a82a5f20d638..322750886c4a626bdd06afbed88cde7eb66111fb 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -26,7 +26,9 @@ endif # PCI doesn't provide nops if CONFIG_PCI isn't enabled. ## -obj-$(CONFIG_USB_DWC3) += dwc3-omap.o + +obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o +obj-$(CONFIG_USB_DWC3_MSM) += dwc3-msm.o ## # REVISIT Samsung Exynos platform needs the clk API which isn't diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c new file mode 100644 index 0000000000000000000000000000000000000000..4e6091e8a55a1b7a95cd61e20133303575fbbb22 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-msm.c @@ -0,0 +1,879 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "core.h" +#include "gadget.h" + +/** + * USB DBM Hardware registers. + * + */ +#define DBM_EP_CFG(n) (0x00 + 4 * (n)) +#define DBM_DATA_FIFO(n) (0x10 + 4 * (n)) +#define DBM_DATA_FIFO_SIZE(n) (0x20 + 4 * (n)) +#define DBM_DATA_FIFO_EN (0x30) +#define DBM_GEVNTADR (0x34) +#define DBM_GEVNTSIZ (0x38) +#define DBM_DBG_CNFG (0x3C) +#define DBM_HW_TRB0_EP(n) (0x40 + 4 * (n)) +#define DBM_HW_TRB1_EP(n) (0x50 + 4 * (n)) +#define DBM_HW_TRB2_EP(n) (0x60 + 4 * (n)) +#define DBM_HW_TRB3_EP(n) (0x70 + 4 * (n)) +#define DBM_PIPE_CFG (0x80) +#define DBM_SOFT_RESET (0x84) + +/** + * USB DBM Hardware registers bitmask. + * + */ +/* DBM_EP_CFG */ +#define DBM_EN_EP 0x00000000 +#define DBM_USB3_EP_NUM 0x0000003E +#define DBM_BAM_PIPE_NUM 0x000000C0 +#define DBM_PRODUCER 0x00000100 +#define DBM_DISABLE_WB 0x00000200 +#define DBM_INT_RAM_ACC 0x00000400 + +/* DBM_DATA_FIFO_SIZE */ +#define DBM_DATA_FIFO_SIZE_MASK 0x0000ffff + +/* DBM_GEVNTSIZ */ +#define DBM_GEVNTSIZ_MASK 0x0000ffff + +/* DBM_DBG_CNFG */ +#define DBM_ENABLE_IOC_MASK 0x0000000f + +/* DBM_SOFT_RESET */ +#define DBM_SFT_RST_EP0 0x00000001 +#define DBM_SFT_RST_EP1 0x00000002 +#define DBM_SFT_RST_EP2 0x00000004 +#define DBM_SFT_RST_EP3 0x00000008 +#define DBM_SFT_RST_EPS 0x0000000F +#define DBM_SFT_RST 0x80000000 + +#define DBM_MAX_EPS 4 + +struct dwc3_msm_req_complete { + struct list_head list_item; + struct usb_request *req; + void (*orig_complete)(struct usb_ep *ep, + struct usb_request *req); +}; + +struct dwc3_msm { + struct platform_device *dwc3; + struct device *dev; + void __iomem *base; + u32 resource_size; + int dbm_num_eps; + u8 ep_num_mapping[DBM_MAX_EPS]; + const struct usb_ep_ops *original_ep_ops[DWC3_ENDPOINTS_NUM]; + struct list_head req_complete_list; +}; + +static struct dwc3_msm *context; + +/** + * + * Read register with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * + * @return u32 + */ +static inline u32 dwc3_msm_read_reg(void *base, u32 offset) +{ + u32 val = ioread32(base + offset); + return val; +} + +/** + * Read register masked field with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * + * @return u32 + */ +static inline u32 dwc3_msm_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; + return val; +} + +/** + * + * Write register with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @val - value to write. + * + */ +static inline void dwc3_msm_write_reg(void *base, u32 offset, u32 val) +{ + iowrite32(val, base + offset); +} + +/** + * Write register masked field with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * @val - value to write. + * + */ +static inline void dwc3_msm_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); +} + +/** + * Return DBM EP number which is not already configured. + * + */ +static int dwc3_msm_find_avail_dbm_ep(void) +{ + int i; + + for (i = 0; i < context->dbm_num_eps; i++) + if (!context->ep_num_mapping[i]) + return i; + + return -ENODEV; /* Not found */ +} + +/** + * Return DBM EP number according to usb endpoint number. + * + */ +static int dwc3_msm_find_matching_dbm_ep(u8 usb_ep) +{ + int i; + + for (i = 0; i < context->dbm_num_eps; i++) + if (context->ep_num_mapping[i] == usb_ep) + return i; + + return -ENODEV; /* Not found */ +} + +/** + * Return number of configured DBM endpoints. + * + */ +static int dwc3_msm_configured_dbm_ep_num(void) +{ + int i; + int count = 0; + + for (i = 0; i < context->dbm_num_eps; i++) + if (context->ep_num_mapping[i]) + count++; + + return count; +} + +/** + * Configure the DBM with the USB3 core event buffer. + * This function is called by the SNPS UDC upon initialization. + * + * @addr - address of the event buffer. + * @size - size of the event buffer. + * + */ +static int dwc3_msm_event_buffer_config(u32 addr, u16 size) +{ + dev_dbg(context->dev, "%s\n", __func__); + + dwc3_msm_write_reg(context->base, DBM_GEVNTADR, addr); + dwc3_msm_write_reg_field(context->base, DBM_GEVNTSIZ, + DBM_GEVNTSIZ_MASK, size); + + return 0; +} + +/** + * Reset the DBM registers upon initialization. + * + */ +static int dwc3_msm_dbm_soft_reset(void) +{ + dev_dbg(context->dev, "%s\n", __func__); + + dwc3_msm_write_reg_field(context->base, DBM_SOFT_RESET, + DBM_SFT_RST, 1); + + return 0; +} + +/** + * Soft reset specific DBM ep. + * This function is called by the function driver upon events + * such as transfer aborting, USB re-enumeration and USB + * disconnection. + * + * @dbm_ep - DBM ep number. + * @enter_reset - should we enter a reset state or get out of it. + * + */ +static int dwc3_msm_dbm_ep_soft_reset(u8 dbm_ep, bool enter_reset) +{ + dev_dbg(context->dev, "%s\n", __func__); + + if (dbm_ep >= context->dbm_num_eps) { + dev_err(context->dev, + "%s: Invalid DBM ep index\n", __func__); + return -ENODEV; + } + + if (enter_reset) { + dwc3_msm_write_reg_field(context->base, DBM_SOFT_RESET, + DBM_SFT_RST_EPS, 1 << dbm_ep); + } else { + dwc3_msm_write_reg_field(context->base, DBM_SOFT_RESET, + DBM_SFT_RST_EPS, 0); + } + + return 0; +} + +/** + * Configure a USB DBM ep to work in BAM mode. + * + * + * @usb_ep - USB physical EP number. + * @producer - producer/consumer. + * @disable_wb - disable write back to system memory. + * @internal_mem - use internal USB memory for data fifo. + * @ioc - enable interrupt on completion. + * + * @return int - DBM ep number. + */ +static int dwc3_msm_dbm_ep_config(u8 usb_ep, u8 bam_pipe, + bool producer, bool disable_wb, + bool internal_mem, bool ioc) +{ + u8 dbm_ep; + u8 ioc_mask; + + dev_dbg(context->dev, "%s\n", __func__); + + dbm_ep = dwc3_msm_find_avail_dbm_ep(); + if (dbm_ep < 0) { + dev_err(context->dev, "%s: No more DBM eps\n", __func__); + return -ENODEV; + } + + context->ep_num_mapping[dbm_ep] = usb_ep; + + /* First, reset the dbm endpoint */ + dwc3_msm_dbm_ep_soft_reset(dbm_ep, false); + + ioc_mask = dwc3_msm_read_reg_field(context->base, DBM_DBG_CNFG, + DBM_ENABLE_IOC_MASK); + ioc_mask &= ~(ioc << dbm_ep); /* Clear ioc bit for dbm_ep */ + /* Set ioc bit for dbm_ep if needed */ + dwc3_msm_write_reg_field(context->base, DBM_DBG_CNFG, + DBM_ENABLE_IOC_MASK, ioc_mask | (ioc << dbm_ep)); + + dwc3_msm_write_reg(context->base, DBM_EP_CFG(dbm_ep), + producer | disable_wb | internal_mem); + dwc3_msm_write_reg_field(context->base, DBM_EP_CFG(dbm_ep), + DBM_USB3_EP_NUM, usb_ep); + dwc3_msm_write_reg_field(context->base, DBM_EP_CFG(dbm_ep), + DBM_BAM_PIPE_NUM, bam_pipe); + dwc3_msm_write_reg_field(context->base, DBM_EP_CFG(dbm_ep), + DBM_EN_EP, 1); + + return dbm_ep; +} + +/** + * Configure a USB DBM ep to work in normal mode. + * + * @usb_ep - USB ep number. + * + */ +static int dwc3_msm_dbm_ep_unconfig(u8 usb_ep) +{ + u8 dbm_ep; + + dev_dbg(context->dev, "%s\n", __func__); + + dbm_ep = dwc3_msm_find_matching_dbm_ep(usb_ep); + + if (dbm_ep < 0) { + dev_err(context->dev, + "%s: Invalid usb ep index\n", __func__); + return -ENODEV; + } + + context->ep_num_mapping[dbm_ep] = 0; + + dwc3_msm_write_reg(context->base, DBM_EP_CFG(dbm_ep), 0); + + /* Reset the dbm endpoint */ + dwc3_msm_dbm_ep_soft_reset(dbm_ep, true); + + return 0; +} + +/** + * Configure the DBM with the BAM's data fifo. + * This function is called by the USB BAM Driver + * upon initialization. + * + * @ep - pointer to usb endpoint. + * @addr - address of data fifo. + * @size - size of data fifo. + * + */ +int msm_data_fifo_config(struct usb_ep *ep, u32 addr, u32 size) +{ + u8 dbm_ep; + struct dwc3_ep *dep = to_dwc3_ep(ep); + + dev_dbg(context->dev, "%s\n", __func__); + + dbm_ep = dwc3_msm_find_matching_dbm_ep(dep->number); + + if (dbm_ep >= context->dbm_num_eps) { + dev_err(context->dev, + "%s: Invalid DBM ep index\n", __func__); + return -ENODEV; + } + + dwc3_msm_write_reg(context->base, DBM_DATA_FIFO(dbm_ep), addr); + dwc3_msm_write_reg_field(context->base, DBM_DATA_FIFO_SIZE(dbm_ep), + DBM_DATA_FIFO_SIZE_MASK, size); + + return 0; +} + +/** +* Cleanups for msm endpoint on request complete. +* +* Also call original request complete. +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to usb_request instance. +* +* @return int - 0 on success, negetive on error. +*/ +static void dwc3_msm_req_complete_func(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3_msm_req_complete *req_complete = NULL; + + /* Find original request complete function and remove it from list */ + list_for_each_entry(req_complete, + &context->req_complete_list, + list_item) { + if (req_complete->req == request) + break; + } + if (!req_complete || req_complete->req != request) { + dev_err(dep->dwc->dev, "%s: could not find the request\n", + __func__); + return; + } + list_del(&req_complete->list_item); + + /* + * Release another one TRB to the pool since DBM queue took 2 TRBs + * (normal and link), and the dwc3/gadget.c :: dwc3_gadget_giveback + * released only one. + */ + if (req->queued) + dep->busy_slot++; + + /* Unconfigure dbm ep */ + dwc3_msm_dbm_ep_unconfig(dep->number); + + /* + * If this is the last endpoint we unconfigured, than reset also + * the event buffers. + */ + if (0 == dwc3_msm_configured_dbm_ep_num()) + dwc3_msm_event_buffer_config(0, 0); + + /* + * Call original complete function, notice that dwc->lock is already + * taken by the caller of this function (dwc3_gadget_giveback()). + */ + request->complete = req_complete->orig_complete; + request->complete(ep, request); + + kfree(req_complete); +} + +/** +* Helper function. +* See the header of the dwc3_msm_ep_queue function. +* +* @dwc3_ep - pointer to dwc3_ep instance. +* @req - pointer to dwc3_request instance. +* +* @return int - 0 on success, negetive on error. +*/ +static int __dwc3_msm_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3_trb_hw *trb_hw; + struct dwc3_trb_hw *trb_link_hw; + struct dwc3_trb trb; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret = 0; + + if ((req->request.udc_priv & MSM_IS_FINITE_TRANSFER) && + (req->request.length > 0)) { + /* Map the request to a DMA. */ + dwc3_map_buffer_to_dma(req); + } + + /* We push the request to the dep->req_queued list to indicate that + * this request is issued with start transfer. The request will be out + * from this list in 2 cases. The first is that the transfer will be + * completed (not if the transfer is endless using a circular TRBs with + * with link TRB). The second case is an option to do stop stransfer, + * this can be initiated by the function driver when calling dequeue. + */ + req->queued = true; + list_add_tail(&req->list, &dep->req_queued); + + /* First, prepare a normal TRB, point to the fake buffer */ + trb_hw = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; + dep->free_slot++; + memset(&trb, 0, sizeof(trb)); + + req->trb = trb_hw; + + trb.bplh = req->request.dma; + trb.lst = 0; + trb.trbctl = DWC3_TRBCTL_NORMAL; + trb.length = req->request.length; + trb.hwo = true; + + dwc3_trb_to_hw(&trb, trb_hw); + req->trb_dma = dep->trb_pool_dma; + + /* Second, prepare a Link TRB that points to the first TRB*/ + trb_link_hw = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; + dep->free_slot++; + memset(&trb, 0, sizeof(trb)); + + trb.bplh = dep->trb_pool_dma; + trb.trbctl = DWC3_TRBCTL_LINK_TRB; + trb.hwo = true; + + dwc3_trb_to_hw(&trb, trb_link_hw); + + /* + * Now start the transfer + */ + memset(¶ms, 0, sizeof(params)); + params.param0 = upper_32_bits(req->trb_dma); + params.param1 = lower_32_bits(req->trb_dma); + cmd = DWC3_DEPCMD_STARTTRANSFER; + ret = dwc3_send_gadget_ep_cmd(dep->dwc, dep->number, cmd, ¶ms); + if (ret < 0) { + dev_dbg(dep->dwc->dev, + "%s: failed to send STARTTRANSFER command\n", + __func__); + + dwc3_unmap_buffer_from_dma(req); + list_del(&req->list); + return ret; + } + + return ret; +} + +/** +* Queue a usb request to the DBM endpoint. +* This function should be called after the endpoint +* was enabled by the ep_enable. +* +* This function prepares special structure of TRBs which +* is familier with the DBM HW, so it will possible to use +* this endpoint in DBM mode. +* +* The TRBs prepared by this function, is one normal TRB +* which point to a fake buffer, followed by a link TRB +* that points to the first TRB. +* +* The API of this function follow the regular API of +* usb_ep_queue (see usb_ep_ops in include/linuk/usb/gadget.h). +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to usb_request instance. +* @gfp_flags - possible flags. +* +* @return int - 0 on success, negetive on error. +*/ +static int dwc3_msm_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm_req_complete *req_complete; + unsigned long flags; + int ret = 0; + u8 bam_pipe; + bool producer; + bool disable_wb; + bool internal_mem; + bool ioc; + + if (!(request->udc_priv & MSM_SPS_MODE)) { + /* Not SPS mode, call original queue */ + dev_vdbg(dwc->dev, "%s: not sps mode, use regular queue\n", + __func__); + + return (context->original_ep_ops[dep->number])->queue(ep, + request, + gfp_flags); + } + + if (!dep->endpoint.desc) { + dev_err(dwc->dev, + "%s: trying to queue request %p to disabled ep %s\n", + __func__, request, ep->name); + return -EPERM; + } + + if (dep->number == 0 || dep->number == 1) { + dev_err(dwc->dev, + "%s: trying to queue dbm request %p to control ep %s\n", + __func__, request, ep->name); + return -EPERM; + } + + if (dep->free_slot > 0 || dep->busy_slot > 0 || + !list_empty(&dep->request_list) || + !list_empty(&dep->req_queued)) { + + dev_err(dwc->dev, + "%s: trying to queue dbm request %p tp ep %s\n", + __func__, request, ep->name); + return -EPERM; + } + + /* + * Override req->complete function, but before doing that, + * store it's original pointer in the req_complete_list. + */ + req_complete = kzalloc(sizeof(*req_complete), GFP_KERNEL); + if (!req_complete) { + dev_err(dep->dwc->dev, "%s: not enough memory\n", __func__); + return -ENOMEM; + } + req_complete->req = request; + req_complete->orig_complete = request->complete; + list_add_tail(&req_complete->list_item, &context->req_complete_list); + request->complete = dwc3_msm_req_complete_func; + + /* + * Configure dbm event buffers if this is the first + * dbm endpoint we about to configure. + */ + if (0 == dwc3_msm_configured_dbm_ep_num()) + dwc3_msm_event_buffer_config(dwc->ev_buffs[0]->dma, + dwc->ev_buffs[0]->length); + + /* + * Configure the DBM endpoint + */ + bam_pipe = (request->udc_priv & MSM_PIPE_ID_MASK); + producer = ((request->udc_priv & MSM_PRODUCER) ? true : false); + disable_wb = ((request->udc_priv & MSM_DISABLE_WB) ? true : false); + internal_mem = ((request->udc_priv & MSM_INTERNAL_MEM) ? true : false); + ioc = ((request->udc_priv & MSM_ETD_IOC) ? true : false); + + ret = dwc3_msm_dbm_ep_config(dep->number, + bam_pipe, producer, + disable_wb, internal_mem, ioc); + if (ret < 0) { + dev_err(context->dev, + "error %d after calling dwc3_msm_dbm_ep_config\n", + ret); + return ret; + } + + dev_vdbg(dwc->dev, "%s: queing request %p to ep %s length %d\n", + __func__, request, ep->name, request->length); + + /* + * We must obtain the lock of the dwc3 core driver, + * including disabling interrupts, so we will be sure + * that we are the only ones that configure the HW device + * core and ensure that we queuing the request will finish + * as soon as possible so we will release back the lock. + */ + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_msm_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + if (ret < 0) { + dev_err(context->dev, + "error %d after calling __dwc3_msm_ep_queue\n", ret); + return ret; + } + + return 0; +} + +/** + * Configure MSM endpoint. + * This function do specific configurations + * to an endpoint which need specific implementaion + * in the MSM architecture. + * + * This function should be called by usb function/class + * layer which need a support from the specific MSM HW + * which wrap the USB3 core. (like DBM specific endpoints) + * + * @ep - a pointer to some usb_ep instance + * + * @return int - 0 on success, negetive on error. + */ +int msm_ep_config(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct usb_ep_ops *new_ep_ops; + + /* Save original ep ops for future restore*/ + if (context->original_ep_ops[dep->number]) { + dev_err(context->dev, + "ep [%s,%d] already configured as msm endpoint\n", + ep->name, dep->number); + return -EPERM; + } + context->original_ep_ops[dep->number] = ep->ops; + + /* Set new usb ops as we like */ + new_ep_ops = kzalloc(sizeof(struct usb_ep_ops), GFP_KERNEL); + if (!new_ep_ops) { + dev_err(context->dev, + "%s: unable to allocate mem for new usb ep ops\n", + __func__); + return -ENOMEM; + } + (*new_ep_ops) = (*ep->ops); + new_ep_ops->queue = dwc3_msm_ep_queue; + ep->ops = new_ep_ops; + + /* + * Do HERE more usb endpoint configurations + * which are specific to MSM. + */ + + return 0; +} +EXPORT_SYMBOL(msm_ep_config); + +/** + * Un-configure MSM endpoint. + * Tear down configurations done in the + * dwc3_msm_ep_config function. + * + * @ep - a pointer to some usb_ep instance + * + * @return int - 0 on success, negetive on error. + */ +int msm_ep_unconfig(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct usb_ep_ops *old_ep_ops; + + /* Restore original ep ops */ + if (!context->original_ep_ops[dep->number]) { + dev_err(context->dev, + "ep [%s,%d] was not configured as msm endpoint\n", + ep->name, dep->number); + return -EINVAL; + } + old_ep_ops = (struct usb_ep_ops *)ep->ops; + ep->ops = context->original_ep_ops[dep->number]; + context->original_ep_ops[dep->number] = NULL; + kfree(old_ep_ops); + + /* + * Do HERE more usb endpoint un-configurations + * which are specific to MSM. + */ + + return 0; +} +EXPORT_SYMBOL(msm_ep_unconfig); + +static int __devinit dwc3_msm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct platform_device *dwc3; + struct dwc3_msm *msm; + struct resource *res; + int ret = 0; + + msm = devm_kzalloc(&pdev->dev, sizeof(*msm), GFP_KERNEL); + if (!msm) { + dev_err(&pdev->dev, "not enough memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, msm); + context = msm; + + INIT_LIST_HEAD(&msm->req_complete_list); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory base resource\n"); + return -ENODEV; + } + + msm->base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!msm->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENODEV; + } + + dwc3 = platform_device_alloc("dwc3-msm", -1); + if (!dwc3) { + dev_err(&pdev->dev, "couldn't allocate dwc3 device\n"); + return -ENOMEM; + } + + dma_set_coherent_mask(&dwc3->dev, pdev->dev.coherent_dma_mask); + + dwc3->dev.parent = &pdev->dev; + dwc3->dev.dma_mask = pdev->dev.dma_mask; + dwc3->dev.dma_parms = pdev->dev.dma_parms; + msm->resource_size = resource_size(res); + msm->dev = &pdev->dev; + msm->dwc3 = dwc3; + + if (of_property_read_u32(node, "qcom,dwc-usb3-msm-dbm-eps", + &msm->dbm_num_eps)) { + dev_err(&pdev->dev, + "unable to read platform data num of dbm eps\n"); + msm->dbm_num_eps = DBM_MAX_EPS; + } + + if (msm->dbm_num_eps > DBM_MAX_EPS) { + dev_err(&pdev->dev, + "Driver doesn't support number of DBM EPs. " + "max: %d, dbm_num_eps: %d\n", + DBM_MAX_EPS, msm->dbm_num_eps); + ret = -ENODEV; + goto err1; + } + + ret = platform_device_add_resources(dwc3, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(&pdev->dev, "couldn't add resources to dwc3 device\n"); + goto err1; + } + + ret = platform_device_add(dwc3); + if (ret) { + dev_err(&pdev->dev, "failed to register dwc3 device\n"); + goto err1; + } + + /* Reset the DBM */ + dwc3_msm_dbm_soft_reset(); + + return 0; + +err1: + platform_device_put(dwc3); + + return ret; +} + +static int __devexit dwc3_msm_remove(struct platform_device *pdev) +{ + struct dwc3_msm *msm = platform_get_drvdata(pdev); + + platform_device_unregister(msm->dwc3); + + return 0; +} + +static const struct of_device_id of_dwc3_matach[] = { + { + .compatible = "qcom,dwc-usb3-msm", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_matach); + +static struct platform_driver dwc3_msm_driver = { + .probe = dwc3_msm_probe, + .remove = __devexit_p(dwc3_msm_remove), + .driver = { + .name = "msm-dwc3", + .of_match_table = of_dwc3_matach, + }, +}; + +MODULE_LICENSE("GPLV2"); +MODULE_DESCRIPTION("DesignWare USB3 MSM Glue Layer"); + +static int __devinit dwc3_msm_init(void) +{ + return platform_driver_register(&dwc3_msm_driver); +} +module_init(dwc3_msm_init); + +static void __exit dwc3_msm_exit(void) +{ + platform_driver_unregister(&dwc3_msm_driver); +} +module_exit(dwc3_msm_exit); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index a04b3f57814e3adf6b7715db3c0f42d7a25ecc79..14b07e50076fc6c337f10d9898ebb6977ae7a066 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -83,7 +83,7 @@ config USB_GADGET_DEBUG_FS config USB_GADGET_VBUS_DRAW int "Maximum VBUS Power usage (2-500 mA)" range 2 500 - default 2 + default 500 help Some devices need to draw power from USB when they are configured, perhaps to operate circuitry or to recharge @@ -494,10 +494,33 @@ config USB_CI13XXX_MSM dynamically linked module called "ci13xxx_msm" and force all gadget drivers to also be dynamically linked. +config USB_CI13XXX_MSM_HSIC + tristate "MIPS HSIC CI13xxx for MSM" + depends on ARCH_MSM + select USB_GADGET_DUALSPEED + help + MSM SoC has chipidea USB controller. This driver uses + ci13xxx_udc core. Support USB-HSIC core. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "ci13xxx_msm_hsic" and force all + gadget drivers to also be dynamically linked. + # # LAST -- dummy/emulated controller # +config USB_MSM_72K + tristate "MSM 72K Device Controller" + depends on ARCH_MSM + 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_DUMMY_HCD tristate "Dummy HCD (DEVELOPMENT)" depends on USB=y || (USB=m && USB_GADGET=m) @@ -986,4 +1009,90 @@ 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 USB_MSC_PROFILING + bool "USB MSC performance profiling" + help + If you say Y here, support will be added for collecting + Mass-storage performance numbers at the VFS level. + +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 b91866a65326d0d34c854af164b8a62ec561b9ca..c646c9fa0a9d9e6c9a4f84ca45d8de700997ec2e 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -29,8 +29,10 @@ obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o obj-$(CONFIG_USB_EG20T) += pch_udc.o obj-$(CONFIG_USB_MV_UDC) += mv_udc.o mv_udc-y := mv_udc_core.o +obj-$(CONFIG_USB_CI13XXX_MSM_HSIC) += ci13xxx_msm_hsic.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 7a2af5844113281774cbab826a6de379c361ec77..eefe95fd15e3f99ff5e42c4746fb879995733f41 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -23,10 +23,12 @@ #include #include #include +#include #include #include #include +#include #include "gadget_chips.h" @@ -42,16 +44,33 @@ #include "epautoconf.c" #include "composite.c" +#include "f_diag.c" +#include "f_rmnet_smd.c" +#include "f_rmnet_sdio.c" +#include "f_rmnet_smd_sdio.c" +#include "f_rmnet.c" #include "f_mass_storage.c" #include "u_serial.c" +#include "u_sdio.c" +#include "u_smd.c" +#include "u_bam.c" +#include "u_rmnet_ctrl_smd.c" +#include "u_ctrl_hsic.c" +#include "u_data_hsic.c" +#include "u_ctrl_hsuart.c" +#include "u_data_hsuart.c" +#include "f_serial.c" #include "f_acm.c" #include "f_adb.c" +#include "f_ccid.c" #include "f_mtp.c" #include "f_accessory.c" #define USB_ETH_RNDIS y #include "f_rndis.c" #include "rndis.c" #include "u_ether.c" +#include "u_bam_data.c" +#include "f_mbim.c" MODULE_AUTHOR("Mike Lockwood"); MODULE_DESCRIPTION("Android Composite USB Driver"); @@ -106,8 +125,12 @@ struct android_dev { bool enabled; int disable_depth; struct mutex mutex; + struct android_usb_platform_data *pdata; + bool connected; bool sw_connected; + char pm_qos[5]; + struct pm_qos_request pm_qos_req_dma; struct work_struct work; }; @@ -154,14 +177,50 @@ static struct usb_device_descriptor device_desc = { .bNumConfigurations = 1, }; +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, + .bcdOTG = __constant_cpu_to_le16(0x0200), +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + static struct usb_configuration android_config_driver = { .label = "android", .unbind = android_unbind_config, .bConfigurationValue = 1, - .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, - .bMaxPower = 0xFA, /* 500ma */ }; +enum android_device_state { + USB_DISCONNECTED, + USB_CONNECTED, + USB_CONFIGURED, +}; + +static void android_pm_qos_update_latency(struct android_dev *dev, int vote) +{ + struct android_usb_platform_data *pdata = dev->pdata; + u32 swfi_latency = 0; + static int last_vote = -1; + + if (!pdata || vote == last_vote + || !pdata->swfi_latency) + return; + + swfi_latency = pdata->swfi_latency + 1; + if (vote) + pm_qos_update_request(&dev->pm_qos_req_dma, + swfi_latency); + else + pm_qos_update_request(&dev->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + last_vote = vote; +} + static void android_work(struct work_struct *data) { struct android_dev *dev = container_of(data, struct android_dev, work); @@ -170,18 +229,53 @@ static void android_work(struct work_struct *data) char *connected[2] = { "USB_STATE=CONNECTED", NULL }; char *configured[2] = { "USB_STATE=CONFIGURED", NULL }; char **uevent_envp = NULL; + static enum android_device_state last_uevent, next_state; unsigned long flags; + int pm_qos_vote = -1; spin_lock_irqsave(&cdev->lock, flags); - if (cdev->config) + if (cdev->config) { uevent_envp = configured; - else if (dev->connected != dev->sw_connected) + next_state = USB_CONFIGURED; + } else if (dev->connected != dev->sw_connected) { uevent_envp = dev->connected ? connected : disconnected; + next_state = dev->connected ? USB_CONNECTED : USB_DISCONNECTED; + if (dev->connected && strncmp(dev->pm_qos, "low", 3)) + pm_qos_vote = 1; + else if (!dev->connected || !strncmp(dev->pm_qos, "low", 3)) + pm_qos_vote = 0; + } dev->sw_connected = dev->connected; spin_unlock_irqrestore(&cdev->lock, flags); + if (pm_qos_vote != -1) + android_pm_qos_update_latency(dev, pm_qos_vote); + if (uevent_envp) { + /* + * Some userspace modules, e.g. MTP, work correctly only if + * CONFIGURED uevent is preceded by DISCONNECT uevent. + * Check if we missed sending out a DISCONNECT uevent. This can + * happen if host PC resets and configures device really quick. + */ + if (((uevent_envp == connected) && + (last_uevent != USB_DISCONNECTED)) || + ((uevent_envp == configured) && + (last_uevent == USB_CONFIGURED))) { + pr_info("%s: sent missed DISCONNECT event\n", __func__); + kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, + disconnected); + msleep(20); + } + /* + * Before sending out CONFIGURED uevent give function drivers + * a chance to wakeup userspace threads and notify disconnect + */ + if (uevent_envp == configured) + msleep(50); + kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); + last_uevent = next_state; pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]); } else { pr_info("%s: did not send uevent (%d %d %p)\n", __func__, @@ -311,88 +405,410 @@ static void adb_closed_callback(void) } -#define MAX_ACM_INSTANCES 4 -struct acm_function_config { - int instances; +/*-------------------------------------------------------------------------*/ +/* Supported functions initialization */ + +/* RMNET_SMD */ +static int rmnet_smd_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return rmnet_smd_bind_config(c); +} + +static struct android_usb_function rmnet_smd_function = { + .name = "rmnet_smd", + .bind_config = rmnet_smd_function_bind_config, }; -static int -acm_function_init(struct android_usb_function *f, - struct usb_composite_dev *cdev) +/* RMNET_SDIO */ +static int rmnet_sdio_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) { - f->config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL); - if (!f->config) - return -ENOMEM; + return rmnet_sdio_function_add(c); +} + +static struct android_usb_function rmnet_sdio_function = { + .name = "rmnet_sdio", + .bind_config = rmnet_sdio_function_bind_config, +}; - return gserial_setup(cdev->gadget, MAX_ACM_INSTANCES); +/* RMNET_SMD_SDIO */ +static int rmnet_smd_sdio_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return rmnet_smd_sdio_init(); } -static void acm_function_cleanup(struct android_usb_function *f) +static void rmnet_smd_sdio_function_cleanup(struct android_usb_function *f) { - gserial_cleanup(); - kfree(f->config); - f->config = NULL; + rmnet_smd_sdio_cleanup(); } -static int -acm_function_bind_config(struct android_usb_function *f, - struct usb_configuration *c) +static int rmnet_smd_sdio_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return rmnet_smd_sdio_function_add(c); +} + +static struct device_attribute *rmnet_smd_sdio_attributes[] = { + &dev_attr_transport, NULL }; + +static struct android_usb_function rmnet_smd_sdio_function = { + .name = "rmnet_smd_sdio", + .init = rmnet_smd_sdio_function_init, + .cleanup = rmnet_smd_sdio_function_cleanup, + .bind_config = rmnet_smd_sdio_bind_config, + .attributes = rmnet_smd_sdio_attributes, +}; + +/*rmnet transport string format(per port):"ctrl0,data0,ctrl1,data1..." */ +#define MAX_XPORT_STR_LEN 50 +static char rmnet_transports[MAX_XPORT_STR_LEN]; + +static void rmnet_function_cleanup(struct android_usb_function *f) +{ + frmnet_cleanup(); +} + +static int rmnet_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) { int i; - int ret = 0; - struct acm_function_config *config = f->config; + int err = 0; + char *ctrl_name; + char *data_name; + char buf[MAX_XPORT_STR_LEN], *b; + static int rmnet_initialized, ports; + + if (!rmnet_initialized) { + rmnet_initialized = 1; + strlcpy(buf, rmnet_transports, sizeof(buf)); + b = strim(buf); + while (b) { + ctrl_name = strsep(&b, ","); + data_name = strsep(&b, ","); + if (ctrl_name && data_name) { + err = frmnet_init_port(ctrl_name, data_name); + if (err) { + pr_err("rmnet: Cannot open ctrl port:" + "'%s' data port:'%s'\n", + ctrl_name, data_name); + goto out; + } + ports++; + } + } - for (i = 0; i < config->instances; i++) { - ret = acm_bind_config(c, i); - if (ret) { - pr_err("Could not bind acm%u config\n", i); - break; + err = rmnet_gport_setup(); + if (err) { + pr_err("rmnet: Cannot setup transports"); + goto out; } } - return ret; + for (i = 0; i < ports; i++) { + err = frmnet_bind_config(c, i); + if (err) { + pr_err("Could not bind rmnet%u config\n", i); + break; + } + } +out: + return err; } -static ssize_t acm_instances_show(struct device *dev, +static ssize_t rmnet_transports_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct android_usb_function *f = dev_get_drvdata(dev); - struct acm_function_config *config = f->config; - return sprintf(buf, "%d\n", config->instances); + return snprintf(buf, PAGE_SIZE, "%s\n", rmnet_transports); } -static ssize_t acm_instances_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) +static ssize_t rmnet_transports_store( + struct device *device, struct device_attribute *attr, + const char *buff, size_t size) { - struct android_usb_function *f = dev_get_drvdata(dev); - struct acm_function_config *config = f->config; - int value; + strlcpy(rmnet_transports, buff, sizeof(rmnet_transports)); - sscanf(buf, "%d", &value); - if (value > MAX_ACM_INSTANCES) - value = MAX_ACM_INSTANCES; - config->instances = value; return size; } -static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, - acm_instances_store); -static struct device_attribute *acm_function_attributes[] = { - &dev_attr_instances, - NULL +static struct device_attribute dev_attr_rmnet_transports = + __ATTR(transports, S_IRUGO | S_IWUSR, + rmnet_transports_show, + rmnet_transports_store); +static struct device_attribute *rmnet_function_attributes[] = { + &dev_attr_rmnet_transports, + NULL }; + +static struct android_usb_function rmnet_function = { + .name = "rmnet", + .cleanup = rmnet_function_cleanup, + .bind_config = rmnet_function_bind_config, + .attributes = rmnet_function_attributes, +}; + + +/* MBIM - used with BAM */ +#define MAX_MBIM_INSTANCES 1 + +static int mbim_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return mbim_init(MAX_MBIM_INSTANCES); +} + +static void mbim_function_cleanup(struct android_usb_function *f) +{ + fmbim_cleanup(); +} + +static int mbim_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return mbim_bind_config(c, 0); +} + +static struct android_usb_function mbim_function = { + .name = "usb_mbim", + .cleanup = mbim_function_cleanup, + .bind_config = mbim_function_bind_config, + .init = mbim_function_init, }; + +/* DIAG */ +static 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) +{ + strlcpy(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 *); + + strlcpy(buf, diag_clients, sizeof(buf)); + b = strim(buf); + + while (b) { + notify = NULL; + name = strsep(&b, ","); + /* Allow only first diag channel to update pid and serial no */ + if (_android_dev->pdata && !once++) + notify = _android_dev->pdata->update_pid_and_serial_num; + + 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, +}; + +/* SERIAL */ +static char serial_transports[32]; /*enabled FSERIAL ports - "tty[,sdio]"*/ +static ssize_t serial_transports_store( + struct device *device, struct device_attribute *attr, + const char *buff, size_t size) +{ + strlcpy(serial_transports, buff, sizeof(serial_transports)); + + return size; +} + +static DEVICE_ATTR(transports, S_IWUSR, NULL, serial_transports_store); +static struct device_attribute *serial_function_attributes[] = + { &dev_attr_transports, NULL }; + +static void serial_function_cleanup(struct android_usb_function *f) +{ + gserial_cleanup(); +} + +static int serial_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + char *name; + char buf[32], *b; + int err = -1, i; + static int serial_initialized = 0, ports = 0; + + if (serial_initialized) + goto bind_config; + + serial_initialized = 1; + strlcpy(buf, serial_transports, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + + if (name) { + err = gserial_init_port(ports, name); + if (err) { + pr_err("serial: Cannot open port '%s'", name); + goto out; + } + ports++; + } + } + err = gport_setup(c); + if (err) { + pr_err("serial: Cannot setup transports"); + goto out; + } + +bind_config: + for (i = 0; i < ports; i++) { + err = gser_bind_config(c, i); + if (err) { + pr_err("serial: bind_config failed for port %d", i); + goto out; + } + } + +out: + return err; +} + +static struct android_usb_function serial_function = { + .name = "serial", + .cleanup = serial_function_cleanup, + .bind_config = serial_function_bind_config, + .attributes = serial_function_attributes, +}; + +/* ACM */ +static char acm_transports[32]; /*enabled ACM ports - "tty[,sdio]"*/ +static ssize_t acm_transports_store( + struct device *device, struct device_attribute *attr, + const char *buff, size_t size) +{ + strlcpy(acm_transports, buff, sizeof(acm_transports)); + + return size; +} + +static DEVICE_ATTR(acm_transports, S_IWUSR, NULL, acm_transports_store); +static struct device_attribute *acm_function_attributes[] = { + &dev_attr_acm_transports, NULL }; + +static void acm_function_cleanup(struct android_usb_function *f) +{ + gserial_cleanup(); +} + +static int acm_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + char *name; + char buf[32], *b; + int err = -1, i; + static int acm_initialized, ports; + + if (acm_initialized) + goto bind_config; + + acm_initialized = 1; + strlcpy(buf, acm_transports, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + + if (name) { + err = acm_init_port(ports, name); + if (err) { + pr_err("acm: Cannot open port '%s'", name); + goto out; + } + ports++; + } + } + err = acm_port_setup(c); + if (err) { + pr_err("acm: Cannot setup transports"); + goto out; + } + +bind_config: + for (i = 0; i < ports; i++) { + err = acm_bind_config(c, i); + if (err) { + pr_err("acm: bind_config failed for port %d", i); + goto out; + } + } + +out: + return err; +} static struct android_usb_function acm_function = { .name = "acm", - .init = acm_function_init, .cleanup = acm_function_cleanup, .bind_config = acm_function_bind_config, .attributes = acm_function_attributes, }; +/* CCID */ +static int ccid_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return ccid_setup(); +} -static int -mtp_function_init(struct android_usb_function *f, +static void ccid_function_cleanup(struct android_usb_function *f) +{ + ccid_cleanup(); +} + +static int ccid_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return ccid_bind_config(c); +} + +static struct android_usb_function ccid_function = { + .name = "ccid", + .init = ccid_function_init, + .cleanup = ccid_function_cleanup, + .bind_config = ccid_function_bind_config, +}; + +static int mtp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) { return mtp_setup(); @@ -403,16 +819,13 @@ static void mtp_function_cleanup(struct android_usb_function *f) mtp_cleanup(); } -static int -mtp_function_bind_config(struct android_usb_function *f, +static int mtp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) { return mtp_bind_config(c, false); } -static int -ptp_function_init(struct android_usb_function *f, - struct usb_composite_dev *cdev) +static int ptp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) { /* nothing to do - initialization is handled by mtp_function_init */ return 0; @@ -423,9 +836,7 @@ static void ptp_function_cleanup(struct android_usb_function *f) /* nothing to do - cleanup is handled by mtp_function_cleanup */ } -static int -ptp_function_bind_config(struct android_usb_function *f, - struct usb_configuration *c) +static int ptp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) { return mtp_bind_config(c, true); } @@ -527,7 +938,8 @@ static ssize_t rndis_manufacturer_show(struct device *dev, { struct android_usb_function *f = dev_get_drvdata(dev); struct rndis_function_config *config = f->config; - return sprintf(buf, "%s\n", config->manufacturer); + + return snprintf(buf, PAGE_SIZE, "%s\n", config->manufacturer); } static ssize_t rndis_manufacturer_store(struct device *dev, @@ -538,7 +950,8 @@ static ssize_t rndis_manufacturer_store(struct device *dev, if (size >= sizeof(config->manufacturer)) return -EINVAL; - if (sscanf(buf, "%s", config->manufacturer) == 1) + + if (sscanf(buf, "%255s", config->manufacturer) == 1) return size; return -1; } @@ -551,7 +964,8 @@ static ssize_t rndis_wceis_show(struct device *dev, { struct android_usb_function *f = dev_get_drvdata(dev); struct rndis_function_config *config = f->config; - return sprintf(buf, "%d\n", config->wceis); + + return snprintf(buf, PAGE_SIZE, "%d\n", config->wceis); } static ssize_t rndis_wceis_store(struct device *dev, @@ -576,7 +990,8 @@ static ssize_t rndis_ethaddr_show(struct device *dev, { struct android_usb_function *f = dev_get_drvdata(dev); struct rndis_function_config *rndis = f->config; - return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + + return snprintf(buf, PAGE_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x\n", rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); } @@ -603,7 +1018,8 @@ static ssize_t rndis_vendorID_show(struct device *dev, { struct android_usb_function *f = dev_get_drvdata(dev); struct rndis_function_config *config = f->config; - return sprintf(buf, "%04x\n", config->vendorID); + + return snprintf(buf, PAGE_SIZE, "%04x\n", config->vendorID); } static ssize_t rndis_vendorID_store(struct device *dev, @@ -671,6 +1087,7 @@ static int mass_storage_function_init(struct android_usb_function *f, &common->luns[0].dev.kobj, "lun"); if (err) { + fsg_common_release(&common->ref); kfree(config); return err; } @@ -698,7 +1115,7 @@ static ssize_t mass_storage_inquiry_show(struct device *dev, { struct android_usb_function *f = dev_get_drvdata(dev); struct mass_storage_function_config *config = f->config; - return sprintf(buf, "%s\n", config->common->inquiry_string); + return snprintf(buf, PAGE_SIZE, "%s\n", config->common->inquiry_string); } static ssize_t mass_storage_inquiry_store(struct device *dev, @@ -708,7 +1125,7 @@ static ssize_t mass_storage_inquiry_store(struct device *dev, struct mass_storage_function_config *config = f->config; if (size >= sizeof(config->common->inquiry_string)) return -EINVAL; - if (sscanf(buf, "%s", config->common->inquiry_string) != 1) + if (sscanf(buf, "%28s", config->common->inquiry_string) != 1) return -EINVAL; return size; } @@ -765,7 +1182,15 @@ static struct android_usb_function accessory_function = { static struct android_usb_function *supported_functions[] = { + &mbim_function, + &rmnet_smd_function, + &rmnet_sdio_function, + &rmnet_smd_sdio_function, + &rmnet_function, + &diag_function, + &serial_function, &adb_function, + &ccid_function, &acm_function, &mtp_function, &ptp_function, @@ -775,6 +1200,31 @@ static struct android_usb_function *supported_functions[] = { NULL }; +static void android_cleanup_functions(struct android_usb_function **functions) +{ + struct android_usb_function *f; + struct device_attribute **attrs; + struct device_attribute *attr; + + while (*functions) { + f = *functions++; + + if (f->dev) { + device_destroy(android_class, f->dev->devt); + kfree(f->dev_name); + } else + continue; + + if (f->cleanup) + f->cleanup(f); + + attrs = f->attributes; + if (attrs) { + while ((attr = *attrs++)) + device_remove_file(f->dev, attr); + } + } +} static int android_init_functions(struct android_usb_function **functions, struct usb_composite_dev *cdev) @@ -783,17 +1233,22 @@ static int android_init_functions(struct android_usb_function **functions, struct android_usb_function *f; struct device_attribute **attrs; struct device_attribute *attr; - int err; - int index = 0; + int err = 0; + int index = 1; /* index 0 is for android0 device */ for (; (f = *functions++); index++) { f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name); + if (!f->dev_name) { + err = -ENOMEM; + goto err_out; + } f->dev = device_create(android_class, dev->dev, MKDEV(0, index), f, f->dev_name); if (IS_ERR(f->dev)) { pr_err("%s: Failed to create dev %s", __func__, f->dev_name); err = PTR_ERR(f->dev); + f->dev = NULL; goto err_create; } @@ -802,7 +1257,7 @@ static int android_init_functions(struct android_usb_function **functions, if (err) { pr_err("%s: Failed to init %s", __func__, f->name); - goto err_out; + goto err_init; } } @@ -814,35 +1269,26 @@ static int android_init_functions(struct android_usb_function **functions, if (err) { pr_err("%s: Failed to create function %s attributes", __func__, f->name); - goto err_out; + goto err_attrs; } } return 0; -err_out: +err_attrs: + for (attr = *(attrs -= 2); attrs != f->attributes; attr = *(attrs--)) + device_remove_file(f->dev, attr); + if (f->cleanup) + f->cleanup(f); +err_init: device_destroy(android_class, f->dev->devt); err_create: + f->dev = NULL; kfree(f->dev_name); +err_out: + android_cleanup_functions(dev->functions); return err; } -static void android_cleanup_functions(struct android_usb_function **functions) -{ - struct android_usb_function *f; - - while (*functions) { - f = *functions++; - - if (f->dev) { - device_destroy(android_class, f->dev->devt); - kfree(f->dev_name); - } - - if (f->cleanup) - f->cleanup(f); - } -} - static int android_bind_enabled_functions(struct android_dev *dev, struct usb_configuration *c) @@ -889,6 +1335,32 @@ static int android_enable_function(struct android_dev *dev, char *name) /*-------------------------------------------------------------------------*/ /* /sys/class/android_usb/android%d/ interface */ +static ssize_t remote_wakeup_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", + !!(android_config_driver.bmAttributes & + USB_CONFIG_ATT_WAKEUP)); +} + +static ssize_t remote_wakeup_store(struct device *pdev, + struct device_attribute *attr, const char *buff, size_t size) +{ + int enable = 0; + + sscanf(buff, "%d", &enable); + + pr_debug("android_usb: %s remote wakeup\n", + enable ? "enabling" : "disabling"); + + if (enable) + android_config_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + else + android_config_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; + + return size; +} + static ssize_t functions_show(struct device *pdev, struct device_attribute *attr, char *buf) { @@ -899,7 +1371,7 @@ functions_show(struct device *pdev, struct device_attribute *attr, char *buf) mutex_lock(&dev->mutex); list_for_each_entry(f, &dev->enabled_functions, enabled_list) - buff += sprintf(buff, "%s,", f->name); + buff += snprintf(buff, PAGE_SIZE, "%s,", f->name); mutex_unlock(&dev->mutex); @@ -926,7 +1398,7 @@ functions_store(struct device *pdev, struct device_attribute *attr, INIT_LIST_HEAD(&dev->enabled_functions); - strncpy(buf, buff, sizeof(buf)); + strlcpy(buf, buff, sizeof(buf)); b = strim(buf); while (b) { @@ -947,7 +1419,8 @@ static ssize_t enable_show(struct device *pdev, struct device_attribute *attr, char *buf) { struct android_dev *dev = dev_get_drvdata(pdev); - return sprintf(buf, "%d\n", dev->enabled); + + return snprintf(buf, PAGE_SIZE, "%d\n", dev->enabled); } static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, @@ -958,7 +1431,6 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, struct android_usb_function *f; int enabled = 0; - if (!cdev) return -ENODEV; @@ -996,6 +1468,26 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, } mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t pm_qos_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + + return snprintf(buf, PAGE_SIZE, "%s\n", dev->pm_qos); +} + +static ssize_t pm_qos_store(struct device *pdev, + struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + + strlcpy(dev->pm_qos, buff, sizeof(dev->pm_qos)); + return size; } @@ -1017,7 +1509,7 @@ static ssize_t state_show(struct device *pdev, struct device_attribute *attr, state = "CONNECTED"; spin_unlock_irqrestore(&cdev->lock, flags); out: - return sprintf(buf, "%s\n", state); + return snprintf(buf, PAGE_SIZE, "%s\n", state); } #define DESCRIPTOR_ATTR(field, format_string) \ @@ -1025,7 +1517,8 @@ static ssize_t \ field ## _show(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ - return sprintf(buf, format_string, device_desc.field); \ + return snprintf(buf, PAGE_SIZE, \ + format_string, device_desc.field); \ } \ static ssize_t \ field ## _store(struct device *dev, struct device_attribute *attr, \ @@ -1045,7 +1538,7 @@ static ssize_t \ field ## _show(struct device *dev, struct device_attribute *attr, \ char *buf) \ { \ - return sprintf(buf, "%s", buffer); \ + return snprintf(buf, PAGE_SIZE, "%s", buffer); \ } \ static ssize_t \ field ## _store(struct device *dev, struct device_attribute *attr, \ @@ -1071,7 +1564,11 @@ DESCRIPTOR_STRING_ATTR(iSerial, serial_string) static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show, functions_store); static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); +static DEVICE_ATTR(pm_qos, S_IRUGO | S_IWUSR, + pm_qos_show, pm_qos_store); static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); +static DEVICE_ATTR(remote_wakeup, S_IRUGO | S_IWUSR, + remote_wakeup_show, remote_wakeup_store); static struct device_attribute *android_usb_attributes[] = { &dev_attr_idVendor, @@ -1085,7 +1582,9 @@ static struct device_attribute *android_usb_attributes[] = { &dev_attr_iSerial, &dev_attr_functions, &dev_attr_enable, + &dev_attr_pm_qos, &dev_attr_state, + &dev_attr_remote_wakeup, NULL }; @@ -1143,9 +1642,10 @@ static int android_bind(struct usb_composite_dev *cdev) device_desc.iProduct = id; /* Default strings - should be updated by userspace */ - strncpy(manufacturer_string, "Android", sizeof(manufacturer_string)-1); - strncpy(product_string, "Android", sizeof(product_string) - 1); - strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1); + strlcpy(manufacturer_string, "Android", + sizeof(manufacturer_string) - 1); + strlcpy(product_string, "Android", sizeof(product_string) - 1); + strlcpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1); id = usb_string_id(cdev); if (id < 0) @@ -1153,6 +1653,9 @@ static int android_bind(struct usb_composite_dev *cdev) strings_dev[STRING_SERIAL_IDX].id = id; device_desc.iSerialNumber = id; + if (gadget_is_otg(cdev->gadget)) + android_config_driver.descriptors = otg_desc; + gcnum = usb_gadget_controller_number(gadget); if (gcnum >= 0) device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum); @@ -1162,7 +1665,6 @@ static int android_bind(struct usb_composite_dev *cdev) device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); } - usb_gadget_set_selfpowered(gadget); dev->cdev = cdev; return 0; @@ -1172,6 +1674,9 @@ static int android_usb_unbind(struct usb_composite_dev *cdev) { struct android_dev *dev = _android_dev; + manufacturer_string[0] = '\0'; + product_string[0] = '\0'; + serial_string[0] = '0'; cancel_work_sync(&dev->work); android_cleanup_functions(dev->functions); return 0; @@ -1182,7 +1687,7 @@ static struct usb_composite_driver android_usb_driver = { .dev = &device_desc, .strings = dev_strings, .unbind = android_usb_unbind, - .max_speed = USB_SPEED_HIGH, + .max_speed = USB_SPEED_SUPER }; static int @@ -1267,19 +1772,86 @@ static int android_create_device(struct android_dev *dev) return 0; } +static void android_destroy_device(struct android_dev *dev) +{ + struct device_attribute **attrs = android_usb_attributes; + struct device_attribute *attr; -static int __init init(void) + while ((attr = *attrs++)) + device_remove_file(dev->dev, attr); + device_destroy(android_class, dev->dev->devt); +} + +static int __devinit android_probe(struct platform_device *pdev) { - struct android_dev *dev; - int err; + struct android_usb_platform_data *pdata = pdev->dev.platform_data; + struct android_dev *dev = _android_dev; + int ret = 0; + + dev->pdata = pdata; android_class = class_create(THIS_MODULE, "android_usb"); if (IS_ERR(android_class)) return PTR_ERR(android_class); + ret = android_create_device(dev); + if (ret) { + pr_err("%s(): android_create_device failed\n", __func__); + goto err_dev; + } + + ret = usb_composite_probe(&android_usb_driver, android_bind); + if (ret) { + pr_err("%s(): Failed to register android " + "composite driver\n", __func__); + goto err_probe; + } + + /* pm qos request to prevent apps idle power collapse */ + if (pdata && pdata->swfi_latency) + pm_qos_add_request(&dev->pm_qos_req_dma, + PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + strlcpy(dev->pm_qos, "high", sizeof(dev->pm_qos)); + + return ret; +err_probe: + android_destroy_device(dev); +err_dev: + class_destroy(android_class); + return ret; +} + +static int android_remove(struct platform_device *pdev) +{ + struct android_dev *dev = _android_dev; + struct android_usb_platform_data *pdata = pdev->dev.platform_data; + + android_destroy_device(dev); + class_destroy(android_class); + usb_composite_unregister(&android_usb_driver); + if (pdata && pdata->swfi_latency) + pm_qos_remove_request(&dev->pm_qos_req_dma); + + return 0; +} + +static struct platform_driver android_platform_driver = { + .driver = { .name = "android_usb"}, + .probe = android_probe, + .remove = android_remove, +}; + +static int __init init(void) +{ + struct android_dev *dev; + int ret; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) + if (!dev) { + pr_err("%s(): Failed to alloc memory for android_dev\n", + __func__); return -ENOMEM; + } dev->disable_depth = 1; dev->functions = supported_functions; @@ -1287,27 +1859,26 @@ static int __init init(void) INIT_WORK(&dev->work, android_work); mutex_init(&dev->mutex); - err = android_create_device(dev); - if (err) { - class_destroy(android_class); - kfree(dev); - return err; - } - _android_dev = dev; /* Override composite driver functions */ composite_driver.setup = android_setup; composite_driver.disconnect = android_disconnect; - return usb_composite_probe(&android_usb_driver, android_bind); + ret = platform_driver_register(&android_platform_driver); + if (ret) { + pr_err("%s(): Failed to register android" + "platform driver\n", __func__); + kfree(dev); + } + + return ret; } module_init(init); static void __exit cleanup(void) { - usb_composite_unregister(&android_usb_driver); - class_destroy(android_class); + platform_driver_unregister(&android_platform_driver); kfree(_android_dev); _android_dev = NULL; } diff --git a/drivers/usb/gadget/ci13xxx_msm.c b/drivers/usb/gadget/ci13xxx_msm.c index d07e44c05e9b3c035ca6a254c13bcdc2a51aa24f..1cbaa8e7703ac81251e7e8080135084c260c908c 100644 --- a/drivers/usb/gadget/ci13xxx_msm.c +++ b/drivers/usb/gadget/ci13xxx_msm.c @@ -1,8 +1,14 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * */ #include @@ -10,59 +16,153 @@ #include #include #include +#include #include "ci13xxx_udc.c" #define MSM_USB_BASE (udc->regs) +struct ci13xxx_udc_context { + int irq; + void __iomem *regs; + int wake_gpio; + int wake_irq; + bool wake_irq_state; +}; + +static struct ci13xxx_udc_context _udc_ctxt; + static irqreturn_t msm_udc_irq(int irq, void *data) { return udc_irq(); } +static void ci13xxx_msm_suspend(void) +{ + struct device *dev = _udc->gadget.dev.parent; + dev_dbg(dev, "ci13xxx_msm_suspend\n"); + + if (_udc_ctxt.wake_irq && !_udc_ctxt.wake_irq_state) { + enable_irq_wake(_udc_ctxt.wake_irq); + enable_irq(_udc_ctxt.wake_irq); + _udc_ctxt.wake_irq_state = true; + } +} + +static void ci13xxx_msm_resume(void) +{ + struct device *dev = _udc->gadget.dev.parent; + dev_dbg(dev, "ci13xxx_msm_resume\n"); + + if (_udc_ctxt.wake_irq && _udc_ctxt.wake_irq_state) { + disable_irq_wake(_udc_ctxt.wake_irq); + disable_irq(_udc_ctxt.wake_irq); + _udc_ctxt.wake_irq_state = false; + } +} + static void ci13xxx_msm_notify_event(struct ci13xxx *udc, unsigned event) { struct device *dev = udc->gadget.dev.parent; - int val; switch (event) { case CI13XXX_CONTROLLER_RESET_EVENT: - dev_dbg(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n"); + dev_info(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n"); writel(0, USB_AHBBURST); - writel(0, USB_AHBMODE); + writel_relaxed(0x08, USB_AHBMODE); + break; + case CI13XXX_CONTROLLER_DISCONNECT_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_DISCONNECT_EVENT received\n"); + ci13xxx_msm_resume(); + break; + case CI13XXX_CONTROLLER_SUSPEND_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_SUSPEND_EVENT received\n"); + ci13xxx_msm_suspend(); break; - case CI13XXX_CONTROLLER_STOPPED_EVENT: - dev_dbg(dev, "CI13XXX_CONTROLLER_STOPPED_EVENT received\n"); - /* - * Put the transceiver in non-driving mode. Otherwise host - * may not detect soft-disconnection. - */ - val = usb_phy_io_read(udc->transceiver, ULPI_FUNC_CTRL); - val &= ~ULPI_FUNC_CTRL_OPMODE_MASK; - val |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; - usb_phy_io_write(udc->transceiver, val, ULPI_FUNC_CTRL); + case CI13XXX_CONTROLLER_RESUME_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_RESUME_EVENT received\n"); + ci13xxx_msm_resume(); break; + default: dev_dbg(dev, "unknown ci13xxx_udc event\n"); break; } } +static irqreturn_t ci13xxx_msm_resume_irq(int irq, void *data) +{ + struct ci13xxx *udc = _udc; + + if (udc->transceiver && udc->vbus_active && udc->suspended) + usb_phy_set_suspend(udc->transceiver, 0); + else if (!udc->suspended) + ci13xxx_msm_resume(); + + return IRQ_HANDLED; +} + static struct ci13xxx_udc_driver ci13xxx_msm_udc_driver = { .name = "ci13xxx_msm", .flags = CI13XXX_REGS_SHARED | CI13XXX_REQUIRE_TRANSCEIVER | CI13XXX_PULLUP_ON_VBUS | - CI13XXX_DISABLE_STREAMING, + CI13XXX_ZERO_ITC | + CI13XXX_DISABLE_STREAMING | + CI13XXX_IS_OTG, .notify_event = ci13xxx_msm_notify_event, }; +static int ci13xxx_msm_install_wake_gpio(struct platform_device *pdev, + struct resource *res) +{ + int wake_irq; + int ret; + + dev_dbg(&pdev->dev, "ci13xxx_msm_install_wake_gpio\n"); + + _udc_ctxt.wake_gpio = res->start; + gpio_request(_udc_ctxt.wake_gpio, "USB_RESUME"); + gpio_direction_input(_udc_ctxt.wake_gpio); + wake_irq = gpio_to_irq(_udc_ctxt.wake_gpio); + if (wake_irq < 0) { + dev_err(&pdev->dev, "could not register USB_RESUME GPIO.\n"); + return -ENXIO; + } + + dev_dbg(&pdev->dev, "_udc_ctxt.gpio_irq = %d and irq = %d\n", + _udc_ctxt.wake_gpio, wake_irq); + ret = request_irq(wake_irq, ci13xxx_msm_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "usb resume", NULL); + if (ret < 0) { + dev_err(&pdev->dev, "could not register USB_RESUME IRQ.\n"); + goto gpio_free; + } + disable_irq(wake_irq); + _udc_ctxt.wake_irq = wake_irq; + + return 0; + +gpio_free: + gpio_free(_udc_ctxt.wake_gpio); + _udc_ctxt.wake_gpio = 0; + return ret; +} + +static void ci13xxx_msm_uninstall_wake_gpio(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "ci13xxx_msm_uninstall_wake_gpio\n"); + + if (_udc_ctxt.wake_gpio) { + gpio_free(_udc_ctxt.wake_gpio); + _udc_ctxt.wake_gpio = 0; + } +} + static int ci13xxx_msm_probe(struct platform_device *pdev) { struct resource *res; - void __iomem *regs; - int irq; int ret; dev_dbg(&pdev->dev, "ci13xxx_msm_probe\n"); @@ -73,29 +173,39 @@ static int ci13xxx_msm_probe(struct platform_device *pdev) return -ENXIO; } - regs = ioremap(res->start, resource_size(res)); - if (!regs) { + _udc_ctxt.regs = ioremap(res->start, resource_size(res)); + if (!_udc_ctxt.regs) { dev_err(&pdev->dev, "ioremap failed\n"); return -ENOMEM; } - ret = udc_probe(&ci13xxx_msm_udc_driver, &pdev->dev, regs); + ret = udc_probe(&ci13xxx_msm_udc_driver, &pdev->dev, _udc_ctxt.regs); if (ret < 0) { dev_err(&pdev->dev, "udc_probe failed\n"); goto iounmap; } - irq = platform_get_irq(pdev, 0); - if (irq < 0) { + _udc_ctxt.irq = platform_get_irq(pdev, 0); + if (_udc_ctxt.irq < 0) { dev_err(&pdev->dev, "IRQ not found\n"); ret = -ENXIO; goto udc_remove; } - ret = request_irq(irq, msm_udc_irq, IRQF_SHARED, pdev->name, pdev); + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "USB_RESUME"); + if (res) { + ret = ci13xxx_msm_install_wake_gpio(pdev, res); + if (ret < 0) { + dev_err(&pdev->dev, "gpio irq install failed\n"); + goto udc_remove; + } + } + + ret = request_irq(_udc_ctxt.irq, msm_udc_irq, IRQF_SHARED, pdev->name, + pdev); if (ret < 0) { dev_err(&pdev->dev, "request_irq failed\n"); - goto udc_remove; + goto gpio_uninstall; } pm_runtime_no_callbacks(&pdev->dev); @@ -103,17 +213,30 @@ static int ci13xxx_msm_probe(struct platform_device *pdev) return 0; +gpio_uninstall: + ci13xxx_msm_uninstall_wake_gpio(pdev); udc_remove: udc_remove(); iounmap: - iounmap(regs); + iounmap(_udc_ctxt.regs); return ret; } +int ci13xxx_msm_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + free_irq(_udc_ctxt.irq, pdev); + ci13xxx_msm_uninstall_wake_gpio(pdev); + udc_remove(); + iounmap(_udc_ctxt.regs); + return 0; +} + static struct platform_driver ci13xxx_msm_driver = { .probe = ci13xxx_msm_probe, .driver = { .name = "msm_hsusb", }, + .remove = ci13xxx_msm_remove, }; MODULE_ALIAS("platform:msm_hsusb"); @@ -123,4 +246,10 @@ static int __init ci13xxx_msm_init(void) } module_init(ci13xxx_msm_init); +static void __exit ci13xxx_msm_exit(void) +{ + platform_driver_unregister(&ci13xxx_msm_driver); +} +module_exit(ci13xxx_msm_exit); + MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/ci13xxx_msm_hsic.c b/drivers/usb/gadget/ci13xxx_msm_hsic.c new file mode 100644 index 0000000000000000000000000000000000000000..39d47208363a268dbdf65fae25557f3389b8f117 --- /dev/null +++ b/drivers/usb/gadget/ci13xxx_msm_hsic.c @@ -0,0 +1,794 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "ci13xxx_udc.c" + +#define MSM_USB_BASE (mhsic->regs) + +#define ULPI_IO_TIMEOUT_USEC (10 * 1000) +#define USB_PHY_VDD_DIG_VOL_SUSP_MIN 500000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ +#define USB_PHY_VDD_DIG_LOAD 49360 /* uA */ +#define LINK_RESET_TIMEOUT_USEC (250 * 1000) +#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000) +#define PHY_RESUME_TIMEOUT_USEC (100 * 1000) +#define HSIC_CFG_REG 0x30 +#define HSIC_IO_CAL_PER_REG 0x33 +#define HSIC_DBG1_REG 0x38 + +struct msm_hsic_per *the_mhsic; + +struct msm_hsic_per { + struct device *dev; + struct clk *iface_clk; + struct clk *core_clk; + struct clk *alt_core_clk; + struct clk *phy_clk; + struct clk *cal_clk; + struct regulator *hsic_vddcx; + bool async_int; + void __iomem *regs; + int irq; + atomic_t in_lpm; + struct wake_lock wlock; + struct msm_xo_voter *xo_handle; + struct workqueue_struct *wq; + struct work_struct suspend_w; + struct msm_hsic_peripheral_platform_data *pdata; +}; + +static int msm_hsic_init_vddcx(struct msm_hsic_per *mhsic, int init) +{ + int ret = 0; + + if (!init) + goto disable_reg; + + mhsic->hsic_vddcx = regulator_get(mhsic->dev, "HSIC_VDDCX"); + if (IS_ERR(mhsic->hsic_vddcx)) { + dev_err(mhsic->dev, "unable to get hsic vddcx\n"); + return PTR_ERR(mhsic->hsic_vddcx); + } + + ret = regulator_set_voltage(mhsic->hsic_vddcx, + USB_PHY_VDD_DIG_VOL_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret) { + dev_err(mhsic->dev, "unable to set the voltage" + "for hsic vddcx\n"); + goto reg_set_voltage_err; + } + + ret = regulator_set_optimum_mode(mhsic->hsic_vddcx, + USB_PHY_VDD_DIG_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set optimum mode of the regulator:" + "VDDCX\n", __func__); + goto reg_optimum_mode_err; + } + + ret = regulator_enable(mhsic->hsic_vddcx); + if (ret) { + dev_err(mhsic->dev, "unable to enable hsic vddcx\n"); + goto reg_enable_err; + } + + return 0; + +disable_reg: + regulator_disable(mhsic->hsic_vddcx); +reg_enable_err: + regulator_set_optimum_mode(mhsic->hsic_vddcx, 0); +reg_optimum_mode_err: + regulator_set_voltage(mhsic->hsic_vddcx, 0, + USB_PHY_VDD_DIG_VOL_MIN); +reg_set_voltage_err: + regulator_put(mhsic->hsic_vddcx); + + return ret; + +} + +static int ulpi_write(struct msm_hsic_per *mhsic, u32 val, u32 reg) +{ + int cnt = 0; + + /* initiate write operation */ + writel_relaxed(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(mhsic->dev, "ulpi_write: timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int msm_hsic_phy_clk_reset(struct msm_hsic_per *mhsic) +{ + int ret; + + ret = clk_reset(mhsic->core_clk, CLK_RESET_ASSERT); + if (ret) { + clk_disable(mhsic->alt_core_clk); + dev_err(mhsic->dev, "usb phy clk assert failed\n"); + return ret; + } + usleep_range(10000, 12000); + clk_disable(mhsic->alt_core_clk); + + ret = clk_reset(mhsic->core_clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(mhsic->dev, "usb phy clk deassert failed\n"); + + return ret; +} + +static int msm_hsic_phy_reset(struct msm_hsic_per *mhsic) +{ + u32 val; + int ret; + + ret = msm_hsic_phy_clk_reset(mhsic); + if (ret) + return ret; + + val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK; + writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC); + + /* + * Ensure that RESET operation is completed before + * turning off clock. + */ + mb(); + dev_dbg(mhsic->dev, "phy_reset: success\n"); + + return 0; +} + +static int msm_hsic_enable_clocks(struct platform_device *pdev, + struct msm_hsic_per *mhsic, bool enable) +{ + int ret = 0; + + if (!enable) + goto put_clocks; + + mhsic->iface_clk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(mhsic->iface_clk)) { + dev_err(mhsic->dev, "failed to get iface_clk\n"); + ret = PTR_ERR(mhsic->iface_clk); + goto put_iface_clk; + } + + mhsic->core_clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(mhsic->core_clk)) { + dev_err(mhsic->dev, "failed to get core_clk\n"); + ret = PTR_ERR(mhsic->core_clk); + goto put_core_clk; + } + + mhsic->phy_clk = clk_get(&pdev->dev, "phy_clk"); + if (IS_ERR(mhsic->phy_clk)) { + dev_err(mhsic->dev, "failed to get phy_clk\n"); + ret = PTR_ERR(mhsic->phy_clk); + goto put_phy_clk; + } + + mhsic->alt_core_clk = clk_get(&pdev->dev, "alt_core_clk"); + if (IS_ERR(mhsic->alt_core_clk)) { + dev_err(mhsic->dev, "failed to get alt_core_clk\n"); + ret = PTR_ERR(mhsic->alt_core_clk); + goto put_alt_core_clk; + } + + mhsic->cal_clk = clk_get(&pdev->dev, "cal_clk"); + if (IS_ERR(mhsic->cal_clk)) { + dev_err(mhsic->dev, "failed to get cal_clk\n"); + ret = PTR_ERR(mhsic->cal_clk); + goto put_cal_clk; + } + + clk_enable(mhsic->iface_clk); + clk_enable(mhsic->core_clk); + clk_enable(mhsic->phy_clk); + clk_enable(mhsic->alt_core_clk); + clk_enable(mhsic->cal_clk); + + return 0; + +put_clocks: + clk_disable(mhsic->iface_clk); + clk_disable(mhsic->core_clk); + clk_disable(mhsic->phy_clk); + clk_disable(mhsic->alt_core_clk); + clk_disable(mhsic->cal_clk); +put_cal_clk: + clk_put(mhsic->cal_clk); +put_alt_core_clk: + clk_put(mhsic->alt_core_clk); +put_phy_clk: + clk_put(mhsic->phy_clk); +put_core_clk: + clk_put(mhsic->core_clk); +put_iface_clk: + clk_put(mhsic->iface_clk); + + return ret; +} + +static int msm_hsic_reset(struct msm_hsic_per *mhsic) +{ + int cnt = 0; + int ret; + + ret = msm_hsic_phy_reset(mhsic); + if (ret) { + dev_err(mhsic->dev, "phy_reset failed\n"); + return ret; + } + + writel_relaxed(USBCMD_RESET, USB_USBCMD); + while (cnt < LINK_RESET_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET)) + break; + udelay(1); + cnt++; + } + if (cnt >= LINK_RESET_TIMEOUT_USEC) + return -ETIMEDOUT; + + /* Reset PORTSC and select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + return 0; +} + +static void msm_hsic_wakeup(void) +{ + if (atomic_read(&the_mhsic->in_lpm)) + pm_runtime_resume(the_mhsic->dev); +} + +static void msm_hsic_start(void) +{ + int ret; + + /* programmable length of connect signaling (33.2ns) */ + ret = ulpi_write(the_mhsic, 3, HSIC_DBG1_REG); + if (ret) { + pr_err("%s: Unable to program length of connect signaling\n", + __func__); + } + + /*set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */ + ret = ulpi_write(the_mhsic, 0xFF, HSIC_IO_CAL_PER_REG); + + if (ret) { + pr_err("%s: Unable to set periodic calibration interval\n", + __func__); + } + + /* Enable periodic IO calibration in HSIC_CFG register */ + ret = ulpi_write(the_mhsic, 0xE9, HSIC_CFG_REG); + if (ret) { + pr_err("%s: Unable to enable periodic IO calibration\n", + __func__); + } +} + + +#ifdef CONFIG_PM_SLEEP +static int msm_hsic_suspend(struct msm_hsic_per *mhsic) +{ + int cnt = 0, ret; + u32 val; + + if (atomic_read(&mhsic->in_lpm)) { + dev_dbg(mhsic->dev, "%s called while in lpm\n", __func__); + return 0; + } + disable_irq(mhsic->irq); + + /* + * 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 + * in failure case. + */ + val = readl_relaxed(USB_PORTSC) | PORTSC_PHCD; + writel_relaxed(val, USB_PORTSC); + + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { + dev_err(mhsic->dev, "Unable to suspend PHY\n"); + msm_hsic_reset(mhsic); + } + + /* + * PHY has capability to generate interrupt asynchronously in low + * power mode (LPM). This interrupt is level triggered. So USB IRQ + * line must be disabled till async interrupt enable bit is cleared + * in USBCMD register. Assert STP (ULPI interface STOP signal) to + * block data communication from PHY. + */ + writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL | + ULPI_STP_CTRL, USB_USBCMD); + + /* + * Ensure that hardware is put in low power mode before + * clocks are turned OFF and VDD is allowed to minimize. + */ + mb(); + + if (!mhsic->pdata->keep_core_clk_on_suspend_workaround) { + clk_disable(mhsic->iface_clk); + clk_disable(mhsic->core_clk); + } + clk_disable(mhsic->phy_clk); + clk_disable(mhsic->cal_clk); + + ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_OFF); + if (ret) + dev_err(mhsic->dev, "%s failed to devote for TCXO %d\n" + , __func__, ret); + + ret = regulator_set_voltage(mhsic->hsic_vddcx, + USB_PHY_VDD_DIG_VOL_SUSP_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret < 0) + dev_err(mhsic->dev, "unable to set vddcx voltage: min:0.5v max:1.32v\n"); + + if (device_may_wakeup(mhsic->dev)) + enable_irq_wake(mhsic->irq); + + atomic_set(&mhsic->in_lpm, 1); + enable_irq(mhsic->irq); + wake_unlock(&mhsic->wlock); + + dev_info(mhsic->dev, "HSIC-USB in low power mode\n"); + + return 0; +} + +static int msm_hsic_resume(struct msm_hsic_per *mhsic) +{ + int cnt = 0, ret; + unsigned temp; + + if (!atomic_read(&mhsic->in_lpm)) { + dev_dbg(mhsic->dev, "%s called while not in lpm\n", __func__); + return 0; + } + + wake_lock(&mhsic->wlock); + ret = regulator_set_voltage(mhsic->hsic_vddcx, + USB_PHY_VDD_DIG_VOL_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret < 0) + dev_err(mhsic->dev, + "unable to set vddcx voltage: min:1.045v max:1.32v\n"); + + ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_ON); + if (ret) + dev_err(mhsic->dev, "%s failed to vote for TCXO %d\n", + __func__, ret); + + if (!mhsic->pdata->keep_core_clk_on_suspend_workaround) { + clk_enable(mhsic->iface_clk); + clk_enable(mhsic->core_clk); + } + clk_enable(mhsic->phy_clk); + clk_enable(mhsic->cal_clk); + + temp = readl_relaxed(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel_relaxed(temp, USB_USBCMD); + + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) + goto skip_phy_resume; + + temp = readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD; + writel_relaxed(temp, USB_PORTSC); + while (cnt < PHY_RESUME_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD) && + (readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE)) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_RESUME_TIMEOUT_USEC) { + /* + * This is a fatal error. Reset the link and + * PHY to make hsic working. + */ + dev_err(mhsic->dev, "Unable to resume USB. Reset the hsic\n"); + msm_hsic_reset(mhsic); + } +skip_phy_resume: + if (device_may_wakeup(mhsic->dev)) + disable_irq_wake(mhsic->irq); + + atomic_set(&mhsic->in_lpm, 0); + + if (mhsic->async_int) { + mhsic->async_int = false; + enable_irq(mhsic->irq); + } + + dev_info(mhsic->dev, "HSIC-USB exited from low power mode\n"); + + return 0; +} + +static int msm_hsic_pm_suspend(struct device *dev) +{ + struct msm_hsic_per *mhsic = dev_get_drvdata(dev); + + dev_dbg(dev, "MSM HSIC Peripheral PM suspend\n"); + + return msm_hsic_suspend(mhsic); +} + +#ifdef CONFIG_PM_RUNTIME +static int msm_hsic_pm_resume(struct device *dev) +{ + dev_dbg(dev, "MSM HSIC Peripheral PM resume\n"); + + /* + * Do not resume hardware as part of system resume, + * rather, wait for the ASYNC INT from the h/w + */ + return 0; +} +#else +static int msm_hsic_pm_resume(struct device *dev) +{ + struct msm_hsic_per *mhsic = dev_get_drvdata(dev); + + dev_dbg(dev, "MSM HSIC Peripheral PM resume\n"); + + return msm_hsic_resume(mhsic); +} +#endif + +static void msm_hsic_pm_suspend_work(struct work_struct *w) +{ + pm_runtime_put_noidle(the_mhsic->dev); + pm_runtime_suspend(the_mhsic->dev); +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int msm_hsic_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "MSM HSIC Peripheral runtime idle\n"); + + return 0; +} + +static int msm_hsic_runtime_suspend(struct device *dev) +{ + struct msm_hsic_per *mhsic = dev_get_drvdata(dev); + + dev_dbg(dev, "MSM HSIC Peripheral runtime suspend\n"); + + return msm_hsic_suspend(mhsic); +} + +static int msm_hsic_runtime_resume(struct device *dev) +{ + struct msm_hsic_per *mhsic = dev_get_drvdata(dev); + + dev_dbg(dev, "MSM HSIC Peripheral runtime resume\n"); + pm_runtime_get_noresume(mhsic->dev); + + return msm_hsic_resume(mhsic); +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops msm_hsic_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(msm_hsic_pm_suspend, msm_hsic_pm_resume) + SET_RUNTIME_PM_OPS(msm_hsic_runtime_suspend, msm_hsic_runtime_resume, + msm_hsic_runtime_idle) +}; +#endif + +/** + * Dummy match function - will be called only for HSIC msm + * device (msm_device_gadget_hsic_peripheral). + */ +static inline int __match(struct device *dev, void *data) { return 1; } + +static void msm_hsic_connect_peripheral(struct device *msm_udc_dev) +{ + struct device *dev; + struct usb_gadget *gadget; + + dev = device_find_child(msm_udc_dev, NULL, __match); + gadget = dev_to_usb_gadget(dev); + usb_gadget_vbus_connect(gadget); +} + +static irqreturn_t msm_udc_hsic_irq(int irq, void *data) +{ + struct msm_hsic_per *mhsic = data; + + if (atomic_read(&mhsic->in_lpm)) { + disable_irq_nosync(mhsic->irq); + mhsic->async_int = true; + pm_request_resume(mhsic->dev); + return IRQ_HANDLED; + } + + return udc_irq(); +} + +static void ci13xxx_msm_hsic_notify_event(struct ci13xxx *udc, unsigned event) +{ + struct device *dev = udc->gadget.dev.parent; + struct msm_hsic_per *mhsic = the_mhsic; + + switch (event) { + case CI13XXX_CONTROLLER_RESET_EVENT: + dev_dbg(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n"); + writel_relaxed(0, USB_AHBBURST); + writel_relaxed(0x08, USB_AHBMODE); + break; + case CI13XXX_CONTROLLER_CONNECT_EVENT: + dev_dbg(dev, "CI13XXX_CONTROLLER_CONNECT_EVENT received\n"); + msm_hsic_start(); + break; + case CI13XXX_CONTROLLER_SUSPEND_EVENT: + dev_dbg(dev, "CI13XXX_CONTROLLER_SUSPEND_EVENT received\n"); + queue_work(mhsic->wq, &mhsic->suspend_w); + break; + case CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT: + dev_dbg(dev, "CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT received\n"); + msm_hsic_wakeup(); + break; + default: + dev_dbg(dev, "unknown ci13xxx_udc event\n"); + break; + } +} + +static struct ci13xxx_udc_driver ci13xxx_msm_udc_hsic_driver = { + .name = "ci13xxx_msm_hsic", + .flags = CI13XXX_REGS_SHARED | + CI13XXX_PULLUP_ON_VBUS | + CI13XXX_DISABLE_STREAMING | + CI13XXX_ZERO_ITC, + + .notify_event = ci13xxx_msm_hsic_notify_event, +}; + +static int __devinit msm_hsic_probe(struct platform_device *pdev) +{ + struct resource *res; + struct msm_hsic_per *mhsic; + int ret = 0; + struct msm_hsic_peripheral_platform_data *pdata; + + dev_dbg(&pdev->dev, "msm-hsic probe\n"); + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data given. Bailing out\n"); + return -ENODEV; + } else { + pdata = pdev->dev.platform_data; + } + + mhsic = kzalloc(sizeof(struct msm_hsic_per), GFP_KERNEL); + if (!mhsic) { + dev_err(&pdev->dev, "unable to allocate msm_hsic\n"); + return -ENOMEM; + } + the_mhsic = mhsic; + platform_set_drvdata(pdev, mhsic); + mhsic->dev = &pdev->dev; + mhsic->pdata = pdata; + + mhsic->irq = platform_get_irq(pdev, 0); + if (mhsic->irq < 0) { + dev_err(&pdev->dev, "Unable to get IRQ resource\n"); + ret = mhsic->irq; + goto error; + } + + mhsic->wq = alloc_workqueue("mhsic_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!mhsic->wq) { + pr_err("%s: Unable to create workqueue mhsic wq\n", + __func__); + ret = -ENOMEM; + goto error; + } + INIT_WORK(&mhsic->suspend_w, msm_hsic_pm_suspend_work); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get memory resource\n"); + ret = -ENODEV; + goto error; + } + mhsic->regs = ioremap(res->start, resource_size(res)); + if (!mhsic->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto unmap; + } + dev_info(&pdev->dev, "HSIC Peripheral regs = %p\n", mhsic->regs); + + mhsic->xo_handle = msm_xo_get(MSM_XO_TCXO_D0, "hsic_peripheral"); + if (IS_ERR(mhsic->xo_handle)) { + dev_err(&pdev->dev, "%s not able to get the handle " + "to vote for TCXO\n", __func__); + ret = PTR_ERR(mhsic->xo_handle); + goto unmap; + } + + ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_ON); + if (ret) { + dev_err(&pdev->dev, "%s failed to vote for TCXO %d\n", + __func__, ret); + goto free_xo_handle; + } + + ret = msm_hsic_enable_clocks(pdev, mhsic, true); + + if (ret) { + dev_err(&pdev->dev, "msm_hsic_enable_clocks failed\n"); + ret = -ENODEV; + goto deinit_clocks; + } + ret = msm_hsic_init_vddcx(mhsic, 1); + if (ret) { + dev_err(&pdev->dev, "unable to initialize VDDCX\n"); + ret = -ENODEV; + goto deinit_vddcx; + } + + ret = msm_hsic_reset(mhsic); + if (ret) { + dev_err(&pdev->dev, "msm_hsic_reset failed\n"); + ret = -ENODEV; + goto deinit_vddcx; + } + + ret = udc_probe(&ci13xxx_msm_udc_hsic_driver, &pdev->dev, mhsic->regs); + if (ret < 0) { + dev_err(&pdev->dev, "udc_probe failed\n"); + ret = -ENODEV; + goto deinit_vddcx; + } + + msm_hsic_connect_peripheral(&pdev->dev); + + device_init_wakeup(&pdev->dev, 1); + wake_lock_init(&mhsic->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev)); + wake_lock(&mhsic->wlock); + + ret = request_irq(mhsic->irq, msm_udc_hsic_irq, + IRQF_SHARED, pdev->name, mhsic); + if (ret < 0) { + dev_err(&pdev->dev, "request_irq failed\n"); + ret = -ENODEV; + goto udc_remove; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + return 0; +udc_remove: + udc_remove(); +deinit_vddcx: + msm_hsic_init_vddcx(mhsic, 0); +deinit_clocks: + msm_hsic_enable_clocks(pdev, mhsic, 0); + msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_OFF); +free_xo_handle: + msm_xo_put(mhsic->xo_handle); +unmap: + iounmap(mhsic->regs); +error: + destroy_workqueue(mhsic->wq); + kfree(mhsic); + return ret; +} + +static int __devexit hsic_msm_remove(struct platform_device *pdev) +{ + struct msm_hsic_per *mhsic = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + msm_hsic_init_vddcx(mhsic, 0); + msm_hsic_enable_clocks(pdev, mhsic, 0); + msm_xo_put(mhsic->xo_handle); + wake_lock_destroy(&mhsic->wlock); + destroy_workqueue(mhsic->wq); + udc_remove(); + iounmap(mhsic->regs); + kfree(mhsic); + + return 0; +} + +static struct platform_driver msm_hsic_peripheral_driver = { + .probe = msm_hsic_probe, + .remove = __devexit_p(hsic_msm_remove), + .driver = { + .name = "msm_hsic_peripheral", +#ifdef CONFIG_PM + .pm = &msm_hsic_dev_pm_ops, +#endif + }, +}; + +static int __init msm_hsic_peripheral_init(void) +{ + return platform_driver_probe(&msm_hsic_peripheral_driver, + msm_hsic_probe); +} + +static void __exit msm_hsic_peripheral_exit(void) +{ + platform_driver_unregister(&msm_hsic_peripheral_driver); +} + +module_init(msm_hsic_peripheral_init); +module_exit(msm_hsic_peripheral_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c index 243ef1adf96914614842c9de08de544af8aa51d4..466fa150e276ac56fbb9b03a9401c46d3998a246 100644 --- a/drivers/usb/gadget/ci13xxx_udc.c +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -60,10 +60,12 @@ #include #include #include +#include #include #include #include #include +#include #include "ci13xxx_udc.h" @@ -158,6 +160,7 @@ static struct { #define CAP_ENDPTLISTADDR (0x018UL) #define CAP_PORTSC (0x044UL) #define CAP_DEVLC (0x084UL) +#define CAP_ENDPTPIPEID (0x0BCUL) #define CAP_USBMODE (hw_bank.lpm ? 0x0C8UL : 0x068UL) #define CAP_ENDPTSETUPSTAT (hw_bank.lpm ? 0x0D8UL : 0x06CUL) #define CAP_ENDPTPRIME (hw_bank.lpm ? 0x0DCUL : 0x070UL) @@ -331,6 +334,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); @@ -430,6 +444,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; } @@ -497,13 +515,18 @@ static int hw_ep_prime(int num, int dir, int is_ctrl) */ static int hw_ep_set_halt(int num, int dir, int value) { + u32 addr, mask_xs, mask_xr; + if (value != 0 && value != 1) return -EINVAL; do { - u32 addr = CAP_ENDPTCTRL + num * sizeof(u32); - u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; - u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; + if (hw_cread(CAP_ENDPTSETUPSTAT, BIT(num))) + return 0; + + addr = CAP_ENDPTCTRL + num * sizeof(u32); + mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; /* data toggle - reserved for EP0 but it's in ESS */ hw_cwrite(addr, mask_xs|mask_xr, value ? mask_xs : mask_xr); @@ -855,6 +878,32 @@ static void dbg_inc(unsigned *idx) *idx = (*idx + 1) & (DBG_DATA_MAX-1); } + +static unsigned int ep_addr_txdbg_mask; +module_param(ep_addr_txdbg_mask, uint, S_IRUGO | S_IWUSR); +static unsigned int ep_addr_rxdbg_mask; +module_param(ep_addr_rxdbg_mask, uint, S_IRUGO | S_IWUSR); + +static int allow_dbg_print(u8 addr) +{ + int dir, num; + + /* allow bus wide events */ + if (addr == 0xff) + return 1; + + dir = addr & USB_ENDPOINT_DIR_MASK ? TX : RX; + num = addr & ~USB_ENDPOINT_DIR_MASK; + num = 1 << num; + + if ((dir == TX) && (num & ep_addr_txdbg_mask)) + return 1; + if ((dir == RX) && (num & ep_addr_rxdbg_mask)) + return 1; + + return 0; +} + /** * dbg_print: prints the common part of the event * @addr: endpoint address @@ -868,6 +917,9 @@ static void dbg_print(u8 addr, const char *name, int status, const char *extra) unsigned int stamp; unsigned long flags; + if (!allow_dbg_print(addr)) + return; + write_lock_irqsave(&dbg_data.lck, flags); do_gettimeofday(&tval); @@ -1246,6 +1298,9 @@ 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; dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL); if (!dump) { @@ -1343,6 +1398,142 @@ static ssize_t show_requests(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); +/* EP# and Direction */ +static ssize_t prime_ept(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct ci13xxx_ep *mEp; + unsigned int ep_num, dir; + int n; + struct ci13xxx_req *mReq = NULL; + + if (sscanf(buf, "%u %u", &ep_num, &dir) != 2) { + dev_err(dev, " : prime the ep"); + goto done; + } + + if (dir) + mEp = &udc->ci13xxx_ep[ep_num + hw_ep_max/2]; + else + mEp = &udc->ci13xxx_ep[ep_num]; + + n = hw_ep_bit(mEp->num, mEp->dir); + mReq = list_entry(mEp->qh.queue.next, struct ci13xxx_req, queue); + mEp->qh.ptr->td.next = mReq->dma; + mEp->qh.ptr->td.token &= ~TD_STATUS; + + wmb(); + + hw_cwrite(CAP_ENDPTPRIME, BIT(n), BIT(n)); + while (hw_cread(CAP_ENDPTPRIME, BIT(n))) + cpu_relax(); + + pr_info("%s: prime:%08x stat:%08x ep#%d dir:%s\n", __func__, + hw_cread(CAP_ENDPTPRIME, ~0), + hw_cread(CAP_ENDPTSTAT, ~0), + mEp->num, mEp->dir ? "IN" : "OUT"); +done: + return count; + +} +static DEVICE_ATTR(prime, S_IWUSR, NULL, prime_ept); + +/* EP# and Direction */ +static ssize_t print_dtds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct ci13xxx_ep *mEp; + unsigned int ep_num, dir; + int n; + struct list_head *ptr = NULL; + struct ci13xxx_req *req = NULL; + + if (sscanf(buf, "%u %u", &ep_num, &dir) != 2) { + dev_err(dev, " : to print dtds"); + goto done; + } + + if (dir) + mEp = &udc->ci13xxx_ep[ep_num + hw_ep_max/2]; + else + mEp = &udc->ci13xxx_ep[ep_num]; + + n = hw_ep_bit(mEp->num, mEp->dir); + pr_info("%s: prime:%08x stat:%08x ep#%d dir:%s" + "dTD_update_fail_count: %lu " + "mEp->dTD_update_fail_count: %lu\n", __func__, + hw_cread(CAP_ENDPTPRIME, ~0), + hw_cread(CAP_ENDPTSTAT, ~0), + mEp->num, mEp->dir ? "IN" : "OUT", + udc->dTD_update_fail_count, + mEp->dTD_update_fail_count); + + pr_info("QH: cap:%08x cur:%08x next:%08x token:%08x\n", + mEp->qh.ptr->cap, mEp->qh.ptr->curr, + mEp->qh.ptr->td.next, mEp->qh.ptr->td.token); + + list_for_each(ptr, &mEp->qh.queue) { + req = list_entry(ptr, struct ci13xxx_req, queue); + + pr_info("\treq:%08x next:%08x token:%08x page0:%08x status:%d\n", + req->dma, req->ptr->next, req->ptr->token, + req->ptr->page[0], req->req.status); + } +done: + return count; + +} +static DEVICE_ATTR(dtds, S_IWUSR, NULL, print_dtds); + +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; + } + spin_unlock_irqrestore(udc->lock, flags); + + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT); + + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 0); + + spin_lock_irqsave(udc->lock, flags); + 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 @@ -1379,8 +1570,24 @@ __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; + retval = device_create_file(dev, &dev_attr_prime); + if (retval) + goto rm_prime; + retval = device_create_file(dev, &dev_attr_dtds); + if (retval) + goto rm_dtds; + return 0; +rm_dtds: + device_remove_file(dev, &dev_attr_dtds); +rm_prime: + device_remove_file(dev, &dev_attr_prime); +rm_remote_wakeup: + device_remove_file(dev, &dev_attr_wakeup); rm_registers: device_remove_file(dev, &dev_attr_registers); rm_qheads: @@ -1417,6 +1624,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; } @@ -1497,6 +1705,23 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) if (!mReq->req.no_interrupt) mReq->ptr->token |= TD_IOC; } + + /* MSM Specific: updating the request as required for + * SPS mode. Enable MSM proprietary DMA engine acording + * to the UDC private data in the request. + */ + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) { + if (mReq->req.udc_priv & MSM_SPS_MODE) { + mReq->ptr->token = TD_STATUS_ACTIVE; + if (mReq->req.udc_priv & MSM_IS_FINITE_TRANSFER) + mReq->ptr->next = TD_TERMINATE; + else + mReq->ptr->next = MSM_ETD_TYPE | mReq->dma; + if (!mReq->req.no_interrupt) + mReq->ptr->token |= MSM_ETD_IOC; + } + } + mReq->ptr->page[0] = mReq->req.dma; for (i = 1; i < 5; i++) mReq->ptr->page[i] = @@ -1526,10 +1751,56 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) } /* QH configuration */ + if (!list_empty(&mEp->qh.queue)) { + struct ci13xxx_req *mReq = \ + list_entry(mEp->qh.queue.next, + struct ci13xxx_req, queue); + + if (TD_STATUS_ACTIVE & mReq->ptr->token) { + mEp->qh.ptr->td.next = mReq->dma; + mEp->qh.ptr->td.token &= ~TD_STATUS; + goto prime; + } + } + mEp->qh.ptr->td.next = mReq->dma; /* TERMINATE = 0 */ + + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) { + if (mReq->req.udc_priv & MSM_SPS_MODE) { + mEp->qh.ptr->td.next |= MSM_ETD_TYPE; + i = hw_cread(CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), ~0); + /* Read current value of this EPs pipe id */ + i = (mEp->dir == TX) ? + ((i >> MSM_TX_PIPE_ID_OFS) & MSM_PIPE_ID_MASK) : + (i & MSM_PIPE_ID_MASK); + /* If requested pipe id is different from current, + then write it */ + if (i != (mReq->req.udc_priv & MSM_PIPE_ID_MASK)) { + if (mEp->dir == TX) + hw_cwrite( + CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + MSM_PIPE_ID_MASK << + MSM_TX_PIPE_ID_OFS, + (mReq->req.udc_priv & + MSM_PIPE_ID_MASK) + << MSM_TX_PIPE_ID_OFS); + else + hw_cwrite( + CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + MSM_PIPE_ID_MASK, + mReq->req.udc_priv & + MSM_PIPE_ID_MASK); + } + } + } + mEp->qh.ptr->td.token &= ~TD_STATUS; /* clear status */ mEp->qh.ptr->cap |= QH_ZLT; +prime: wmb(); /* synchronize before ep prime */ ret = hw_ep_prime(mEp->num, mEp->dir, @@ -1552,9 +1823,16 @@ static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) if (mReq->req.status != -EALREADY) return -EINVAL; + /* clean speculative fetches on req->ptr->token */ + mb(); + if ((TD_STATUS_ACTIVE & mReq->ptr->token) != 0) return -EBUSY; + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) + if ((mReq->req.udc_priv & MSM_SPS_MODE) && + (mReq->req.udc_priv & MSM_IS_FINITE_TRANSFER)) + return -EBUSY; if (mReq->zptr) { if ((TD_STATUS_ACTIVE & mReq->zptr->token) != 0) return -EBUSY; @@ -1598,6 +1876,9 @@ static int _ep_nuke(struct ci13xxx_ep *mEp) __releases(mEp->lock) __acquires(mEp->lock) { + struct ci13xxx_ep *mEpTemp = mEp; + unsigned val; + trace("%p", mEp); if (mEp == NULL) @@ -1612,11 +1893,37 @@ __acquires(mEp->lock) list_entry(mEp->qh.queue.next, struct ci13xxx_req, queue); list_del_init(&mReq->queue); + + /* MSM Specific: Clear end point proprietary register */ + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) { + if (mReq->req.udc_priv & MSM_SPS_MODE) { + val = hw_cread(CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + ~0); + + if (val != MSM_EP_PIPE_ID_RESET_VAL) + hw_cwrite( + CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + ~0, MSM_EP_PIPE_ID_RESET_VAL); + } + } mReq->req.status = -ESHUTDOWN; + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, + mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = 0; + mReq->map = 0; + } + if (mReq->req.complete != NULL) { spin_unlock(mEp->lock); - mReq->req.complete(&mEp->ep, &mReq->req); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = &_udc->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); spin_lock(mEp->lock); } } @@ -1644,8 +1951,14 @@ 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); + gadget->b_hnp_enable = 0; + gadget->a_hnp_support = 0; + gadget->host_request = 0; + gadget->otg_srp_reqd = 0; + /* flush all endpoints */ gadget_for_each_ep(ep, gadget) { usb_ep_fifo_flush(ep); @@ -1693,6 +2006,11 @@ __acquires(udc->lock) dbg_event(0xFF, "BUS RST", 0); spin_unlock(udc->lock); + + /*stop charging upon reset */ + if (udc->transceiver) + usb_phy_set_power(udc->transceiver, 0); + retval = _gadget_stop_activity(&udc->gadget); if (retval) goto done; @@ -1712,6 +2030,51 @@ __acquires(udc->lock) err("error: %i", retval); } +/** + * isr_resume_handler: USB PCI interrupt handler + * @udc: UDC device + * + */ +static void isr_resume_handler(struct ci13xxx *udc) +{ + udc->gadget.speed = hw_port_is_high_speed() ? + USB_SPEED_HIGH : USB_SPEED_FULL; + if (udc->suspended) { + spin_unlock(udc->lock); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_RESUME_EVENT); + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 0); + udc->driver->resume(&udc->gadget); + spin_lock(udc->lock); + udc->suspended = 0; + } +} + +/** + * isr_resume_handler: USB SLI interrupt handler + * @udc: UDC device + * + */ +static void isr_suspend_handler(struct ci13xxx *udc) +{ + if (udc->gadget.speed != USB_SPEED_UNKNOWN && + udc->vbus_active) { + if (udc->suspended == 0) { + spin_unlock(udc->lock); + udc->driver->suspend(&udc->gadget); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_SUSPEND_EVENT); + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 1); + spin_lock(udc->lock); + udc->suspended = 1; + } + } +} + /** * isr_get_status_complete: get_status request complete function * @ep: endpoint @@ -1769,8 +2132,15 @@ __acquires(mEp->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - /* Assume that device is bus powered for now. */ - *((u16 *)req->buf) = _udc->remote_wakeup << 1; + if (setup->wIndex == OTG_STATUS_SELECTOR) { + *((u8 *)req->buf) = _udc->gadget.host_request << + HOST_REQUEST_FLAG; + req->length = 1; + } else { + /* Assume that device is bus powered for now. */ + *((u16 *)req->buf) = _udc->remote_wakeup << 1; + } + /* TODO: D1 - Remote Wakeup; D0 - Self Powered */ retval = 0; } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { @@ -1860,17 +2230,35 @@ __acquires(mEp->lock) struct ci13xxx_req *mReq, *mReqTemp; struct ci13xxx_ep *mEpTemp = mEp; int uninitialized_var(retval); + int req_dequeue = 1; + struct ci13xxx *udc = _udc; trace("%p", mEp); if (list_empty(&mEp->qh.queue)) - return -EINVAL; + return 0; list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue, queue) { +dequeue: retval = _hardware_dequeue(mEp, mReq); - if (retval < 0) + if (retval < 0) { + /* + * FIXME: don't know exact delay + * required for HW to update dTD status + * bits. This is a temporary workaround till + * HW designers come back on this. + */ + if (retval == -EBUSY && req_dequeue && mEp->dir == 0) { + req_dequeue = 0; + udc->dTD_update_fail_count++; + mEp->dTD_update_fail_count++; + udelay(10); + goto dequeue; + } break; + } + req_dequeue = 0; list_del_init(&mReq->queue); dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); if (mReq->req.complete != NULL) { @@ -1955,6 +2343,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; @@ -2000,8 +2390,7 @@ __acquires(udc->lock) type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && type != (USB_DIR_IN|USB_RECIP_INTERFACE)) goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) + if (le16_to_cpu(req.wValue) != 0) break; err = isr_get_status_response(udc, &req); break; @@ -2016,6 +2405,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) == @@ -2041,6 +2434,16 @@ __acquires(udc->lock) udc->remote_wakeup = 1; err = isr_setup_status_phase(udc); break; + case USB_DEVICE_B_HNP_ENABLE: + udc->gadget.b_hnp_enable = 1; + err = isr_setup_status_phase(udc); + break; + case USB_DEVICE_A_HNP_SUPPORT: + udc->gadget.a_hnp_support = 1; + err = isr_setup_status_phase(udc); + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + break; case USB_DEVICE_TEST_MODE: tmode = le16_to_cpu(req.wIndex) >> 8; switch (tmode) { @@ -2053,11 +2456,21 @@ __acquires(udc->lock) err = isr_setup_status_phase( udc); break; + case TEST_OTG_SRP_REQD: + udc->gadget.otg_srp_reqd = 1; + err = isr_setup_status_phase( + udc); + break; + case TEST_OTG_HNP_REQD: + udc->gadget.host_request = 1; + err = isr_setup_status_phase( + udc); + break; default: break; } default: - goto delegate; + break; } } else { goto delegate; @@ -2129,12 +2542,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 @@ -2266,6 +2682,7 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); int retval = 0; unsigned long flags; + struct ci13xxx *udc = _udc; trace("%p, %p, %X", ep, req, gfp_flags); @@ -2274,6 +2691,15 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, spin_lock_irqsave(mEp->lock, flags); + if (!udc->configured && mEp->type != + USB_ENDPOINT_XFER_CONTROL) { + spin_unlock_irqrestore(mEp->lock, flags); + trace("usb is not configured" + "ept #%d, ept name#%s\n", + mEp->num, mEp->ep.name); + return -ESHUTDOWN; + } + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { if (req->length) mEp = (_udc->ep0_dir == RX) ? @@ -2326,6 +2752,7 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) { struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_ep *mEpTemp = mEp; struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); unsigned long flags; @@ -2354,7 +2781,10 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) if (mReq->req.complete != NULL) { spin_unlock(mEp->lock); - mReq->req.complete(&mEp->ep, &mReq->req); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = &_udc->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); spin_lock(mEp->lock); } @@ -2362,6 +2792,12 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) return 0; } +static int is_sps_req(struct ci13xxx_req *mReq) +{ + return (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID && + mReq->req.udc_priv & MSM_SPS_MODE); +} + /** * ep_set_halt: sets the endpoint halt feature * @@ -2383,7 +2819,9 @@ static int ep_set_halt(struct usb_ep *ep, int value) #ifndef STALL_IN /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && - !list_empty(&mEp->qh.queue)) { + !list_empty(&mEp->qh.queue) && + !is_sps_req(list_entry(mEp->qh.queue.next, struct ci13xxx_req, + queue))){ spin_unlock_irqrestore(mEp->lock, flags); return -EAGAIN; } @@ -2494,13 +2932,14 @@ 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); + _gadget_stop_activity(&udc->gadget); if (udc->udc_driver->notify_event) udc->udc_driver->notify_event(udc, - CI13XXX_CONTROLLER_STOPPED_EVENT); - _gadget_stop_activity(&udc->gadget); + CI13XXX_CONTROLLER_DISCONNECT_EVENT); pm_runtime_put_sync(&_gadget->dev); } } @@ -2508,38 +2947,39 @@ 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(); + if (udc->transceiver) + return usb_phy_set_power(udc->transceiver, mA); + return -ENOTSUPP; +} + +static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; spin_lock_irqsave(udc->lock, flags); - if (!udc->remote_wakeup) { - ret = -EOPNOTSUPP; - trace("remote wakeup feature is not enabled\n"); - goto out; - } - if (!hw_cread(CAP_PORTSC, PORTSC_SUSP)) { - ret = -EINVAL; - trace("port is not suspended\n"); - goto out; + 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; } - hw_cwrite(CAP_PORTSC, PORTSC_FPR, PORTSC_FPR); -out: spin_unlock_irqrestore(udc->lock, flags); - return ret; -} -static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) -{ - struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + if (is_active) { + hw_device_state(udc->ep0out.qh.dma); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_CONNECT_EVENT); + } + else + hw_device_state(0); - if (udc->transceiver) - return usb_phy_set_power(udc->transceiver, mA); - return -ENOTSUPP; + return 0; } static int ci13xxx_start(struct usb_gadget_driver *driver, @@ -2554,6 +2994,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, .start = ci13xxx_start, .stop = ci13xxx_stop, }; @@ -2573,6 +3014,7 @@ static int ci13xxx_start(struct usb_gadget_driver *driver, unsigned long flags; int i, j; int retval = -ENOMEM; + bool put = false; trace("%p", driver); @@ -2660,8 +3102,10 @@ static int ci13xxx_start(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); + pm_runtime_get_sync(&udc->gadget.dev); retval = bind(&udc->gadget); /* MAY SLEEP */ spin_lock_irqsave(udc->lock, flags); @@ -2671,23 +3115,27 @@ static int ci13xxx_start(struct usb_gadget_driver *driver, } udc->driver = driver; - pm_runtime_get_sync(&udc->gadget.dev); if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) { if (udc->vbus_active) { if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) hw_device_reset(udc); } else { - pm_runtime_put_sync(&udc->gadget.dev); + put = true; goto done; } } + if (!udc->softconnect) { + put = true; + goto done; + } + retval = hw_device_state(udc->ep0out.qh.dma); - if (retval) - pm_runtime_put_sync(&udc->gadget.dev); done: spin_unlock_irqrestore(udc->lock, flags); + if (retval || put) + pm_runtime_put_sync(&udc->gadget.dev); return retval; } @@ -2715,9 +3163,6 @@ static int ci13xxx_stop(struct usb_gadget_driver *driver) if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) || udc->vbus_active) { hw_device_state(0); - if (udc->udc_driver->notify_event) - udc->udc_driver->notify_event(udc, - CI13XXX_CONTROLLER_STOPPED_EVENT); spin_unlock_irqrestore(udc->lock, flags); _gadget_stop_activity(&udc->gadget); spin_lock_irqsave(udc->lock, flags); @@ -2803,14 +3248,7 @@ static irqreturn_t udc_irq(void) } if (USBi_PCI & intr) { isr_statistics.pci++; - udc->gadget.speed = hw_port_is_high_speed() ? - USB_SPEED_HIGH : USB_SPEED_FULL; - if (udc->suspended && udc->driver->resume) { - spin_unlock(udc->lock); - udc->driver->resume(&udc->gadget); - spin_lock(udc->lock); - udc->suspended = 0; - } + isr_resume_handler(udc); } if (USBi_UEI & intr) isr_statistics.uei++; @@ -2819,13 +3257,7 @@ static irqreturn_t udc_irq(void) isr_tr_complete_handler(udc); } if (USBi_SLI & intr) { - if (udc->gadget.speed != USB_SPEED_UNKNOWN && - udc->driver->suspend) { - udc->suspended = 1; - spin_unlock(udc->lock); - udc->driver->suspend(&udc->gadget); - spin_lock(udc->lock); - } + isr_suspend_handler(udc); isr_statistics.sli++; } retval = IRQ_HANDLED; @@ -2866,7 +3298,7 @@ static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, void __iomem *regs) { struct ci13xxx *udc; - int retval = 0; + int retval = 0, i; trace("%p, %p, %p", dev, regs, driver->name); @@ -2885,7 +3317,10 @@ static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, udc->gadget.ops = &usb_gadget_ops; udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.max_speed = USB_SPEED_HIGH; - udc->gadget.is_otg = 0; + if (udc->udc_driver->flags & CI13XXX_IS_OTG) + udc->gadget.is_otg = 1; + else + udc->gadget.is_otg = 0; udc->gadget.name = driver->name; INIT_LIST_HEAD(&udc->gadget.ep_list); @@ -2897,19 +3332,23 @@ static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, udc->gadget.dev.parent = dev; udc->gadget.dev.release = udc_release; - retval = hw_device_init(regs); - if (retval < 0) - goto free_udc; - - udc->transceiver = usb_get_transceiver(); - if (udc->udc_driver->flags & CI13XXX_REQUIRE_TRANSCEIVER) { + udc->transceiver = usb_get_transceiver(); if (udc->transceiver == NULL) { retval = -ENODEV; goto free_udc; } } + retval = hw_device_init(regs); + if (retval < 0) + goto put_transceiver; + + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + INIT_LIST_HEAD(&mEp->ep.ep_list); + } + if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { retval = hw_device_reset(udc); if (retval) diff --git a/drivers/usb/gadget/ci13xxx_udc.h b/drivers/usb/gadget/ci13xxx_udc.h index 0d31af56c989b2817b1c9593b7f5b6d5821c80ab..43768047035ee53fe7edfa45587fe63974cb3b0d 100644 --- a/drivers/usb/gadget/ci13xxx_udc.h +++ b/drivers/usb/gadget/ci13xxx_udc.h @@ -25,6 +25,14 @@ #define RX (0) /* similar to USB_DIR_OUT but can be used as an index */ #define TX (1) /* similar to USB_DIR_IN but can be used as an index */ +/* UDC private data: + * 16MSb - Vendor ID | 16 LSb Vendor private data + */ +#define CI13XX_REQ_VENDOR_ID(id) (id & 0xFFFF0000UL) + +#define MSM_ETD_TYPE BIT(1) +#define MSM_EP_PIPE_ID_RESET_VAL 0x1F001F + /****************************************************************************** * STRUCTURES *****************************************************************************/ @@ -98,6 +106,7 @@ struct ci13xxx_ep { spinlock_t *lock; struct device *device; struct dma_pool *td_pool; + unsigned long dTD_update_fail_count; }; struct ci13xxx; @@ -108,9 +117,15 @@ 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_CONTROLLER_RESET_EVENT 0 -#define CI13XXX_CONTROLLER_STOPPED_EVENT 1 +#define CI13XXX_ZERO_ITC BIT(4) +#define CI13XXX_IS_OTG BIT(5) + +#define CI13XXX_CONTROLLER_RESET_EVENT 0 +#define CI13XXX_CONTROLLER_CONNECT_EVENT 1 +#define CI13XXX_CONTROLLER_SUSPEND_EVENT 2 +#define CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT 3 +#define CI13XXX_CONTROLLER_RESUME_EVENT 4 +#define CI13XXX_CONTROLLER_DISCONNECT_EVENT 5 void (*notify_event) (struct ci13xxx *udc, unsigned event); }; @@ -131,11 +146,14 @@ 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 */ + unsigned long dTD_update_fail_count; struct usb_phy *transceiver; /* Transceiver struct */ }; @@ -189,6 +207,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) @@ -212,7 +232,10 @@ do { \ "[%s] " format "\n", __func__, ## args); \ } while (0) +#ifndef err #define err(format, args...) ci13xxx_printk(KERN_ERR, format, ## args) +#endif + #define warn(format, args...) ci13xxx_printk(KERN_WARNING, format, ## args) #define info(format, args...) ci13xxx_printk(KERN_INFO, format, ## args) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 8f82fc0eb86516262708b08a2b8c96119711d568..d35d8616270a8f17b522fb60d49453e03b9c212b 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -29,7 +29,7 @@ */ /* big enough to hold our biggest descriptor */ -#define USB_BUFSIZ 1024 +#define USB_BUFSIZ 4096 static struct usb_composite_driver *composite; static int (*composite_gadget_bind)(struct usb_composite_dev *cdev); @@ -175,13 +175,14 @@ int config_ep_by_speed(struct usb_gadget *g, _ep->comp_desc = comp_desc; if (g->speed == USB_SPEED_SUPER) { switch (usb_endpoint_type(_ep->desc)) { - case USB_ENDPOINT_XFER_ISOC: - /* mult: bits 1:0 of bmAttributes */ - _ep->mult = comp_desc->bmAttributes & 0x3; case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: _ep->maxburst = comp_desc->bMaxBurst; break; + case USB_ENDPOINT_XFER_ISOC: + /* mult: bits 1:0 of bmAttributes */ + _ep->mult = comp_desc->bmAttributes & 0x3; + break; default: /* Do nothing for control endpoints */ break; @@ -687,6 +688,7 @@ static int set_config(struct usb_composite_dev *cdev, power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW; done: usb_gadget_vbus_draw(gadget, power); + if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; return result; @@ -1080,6 +1082,11 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) u16 w_length = le16_to_cpu(ctrl->wLength); struct usb_function *f = NULL; u8 endp; + struct usb_configuration *c; + + + if (w_length > USB_BUFSIZ) + return value; /* partial re-init of the response message; the function or the * gadget might need to intercept e.g. a control-OUT completion @@ -1133,6 +1140,16 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) if (value >= 0) value = min(w_length, (u16) value); break; + case USB_DT_OTG: + if (!gadget_is_otg(gadget)) + break; + c = list_first_entry(&cdev->configs, + struct usb_configuration, list); + if (c && c->descriptors) + value = usb_find_descriptor_fillbuf(req->buf, + USB_BUFSIZ, c->descriptors, + USB_DT_OTG); + break; case USB_DT_STRING: value = get_string(cdev, req->buf, w_index, w_value & 0xff); @@ -1606,6 +1623,8 @@ static struct usb_gadget_driver composite_driver = { int usb_composite_probe(struct usb_composite_driver *driver, int (*bind)(struct usb_composite_dev *cdev)) { + int retval; + if (!driver || !driver->dev || !bind || composite) return -EINVAL; @@ -1620,7 +1639,10 @@ int usb_composite_probe(struct usb_composite_driver *driver, composite = driver; composite_gadget_bind = bind; - return usb_gadget_probe_driver(&composite_driver, composite_bind); + retval = usb_gadget_probe_driver(&composite_driver, composite_bind); + if (retval) + composite = NULL; + return retval; } /** diff --git a/drivers/usb/gadget/config.c b/drivers/usb/gadget/config.c index 7542a72ce51ae51a0cbf801650ea22a2f2cdd56b..c52d8b62a0312442ee9fb94e4d0dcf98fc952ee6 100644 --- a/drivers/usb/gadget/config.c +++ b/drivers/usb/gadget/config.c @@ -19,6 +19,40 @@ #include #include +/** + * usb_find_descriptor_fillbuf - fill buffer with the requested descriptor + * @buf: Buffer to be filled + * @buflen: Size of buf + * @src: Array of descriptor pointers, terminated by null pointer. + * @desc_type: bDescriptorType field of the requested descriptor. + * + * Copies the requested descriptor into the buffer, returning the length + * or a negative error code if it is not found or can't be copied. Useful + * when DT_OTG descriptor is requested. + */ +int +usb_find_descriptor_fillbuf(void *buf, unsigned buflen, + const struct usb_descriptor_header **src, u8 desc_type) +{ + if (!src) + return -EINVAL; + + for (; NULL != *src; src++) { + unsigned len; + + if ((*src)->bDescriptorType != desc_type) + continue; + + len = (*src)->bLength; + if (len > buflen) + return -EINVAL; + + memcpy(buf, *src, len); + return len; + } + + return -ENOENT; +} /** * usb_descriptor_fillbuf - fill buffer with descriptors diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c index a5818227611a4bdb6eabde2e0cbbb3bc7942ddde..108caf9826846cf1b04f86b905d88ec326a8331f 100644 --- a/drivers/usb/gadget/f_accessory.c +++ b/drivers/usb/gadget/f_accessory.c @@ -296,7 +296,7 @@ static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) } } -static int __init create_bulk_endpoints(struct acc_dev *dev, +static int create_bulk_endpoints(struct acc_dev *dev, struct usb_endpoint_descriptor *in_desc, struct usb_endpoint_descriptor *out_desc) { @@ -688,19 +688,31 @@ static int acc_function_set_alt(struct usb_function *f, DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt); ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); - if (ret) - return ret; - + if (ret) { + dev->ep_in->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->ep_in->name, ret); + return ret; + } ret = usb_ep_enable(dev->ep_in); - if (ret) + if (ret) { + ERROR(cdev, "failed to enable ep %s, result %d\n", + dev->ep_in->name, ret); return ret; + } ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); - if (ret) + if (ret) { + dev->ep_out->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->ep_out->name, ret); + usb_ep_disable(dev->ep_in); return ret; - + } ret = usb_ep_enable(dev->ep_out); if (ret) { + ERROR(cdev, "failed to enable ep %s, result %d\n", + dev->ep_out->name, ret); usb_ep_disable(dev->ep_in); return ret; } diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index d672250a61fa60253e4507c16e7ec199038dae81..cc151cbb763632814f6c56fbe403dbbf979c9717 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 (mina86@mina86.com) * * This software is distributed under the terms of the GNU General @@ -17,6 +18,8 @@ #include #include #include +#include +#include #include "u_serial.h" #include "gadget_chips.h" @@ -43,6 +46,7 @@ struct f_acm { struct gserial port; u8 ctrl_id, data_id; u8 port_num; + enum transport_type transport; u8 pending; @@ -73,6 +77,17 @@ struct f_acm { #define ACM_CTRL_DCD (1 << 0) }; +static unsigned int no_acm_tty_ports; +static unsigned int no_acm_sdio_ports; +static unsigned int no_acm_smd_ports; +static unsigned int nr_acm_ports; + +static struct acm_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); @@ -83,6 +98,82 @@ static inline struct f_acm *port_to_acm(struct gserial *p) return container_of(p, struct f_acm, port); } +static int acm_port_setup(struct usb_configuration *c) +{ + int ret = 0; + + pr_debug("%s: no_acm_tty_ports:%u no_acm_sdio_ports: %u nr_acm_ports:%u\n", + __func__, no_acm_tty_ports, no_acm_sdio_ports, + nr_acm_ports); + + if (no_acm_tty_ports) + ret = gserial_setup(c->cdev->gadget, no_acm_tty_ports); + if (no_acm_sdio_ports) + ret = gsdio_setup(c->cdev->gadget, no_acm_sdio_ports); + if (no_acm_smd_ports) + ret = gsmd_setup(c->cdev->gadget, no_acm_smd_ports); + + return ret; +} + +static int acm_port_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__, xport_to_str(acm->transport), + acm, &acm->port, acm->port_num, port_num); + + switch (acm->transport) { + case USB_GADGET_XPORT_TTY: + gserial_connect(&acm->port, port_num); + break; + case USB_GADGET_XPORT_SDIO: + gsdio_connect(&acm->port, port_num); + break; + case USB_GADGET_XPORT_SMD: + gsmd_connect(&acm->port, port_num); + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(acm->transport)); + return -ENODEV; + } + + return 0; +} + +static int acm_port_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__, xport_to_str(acm->transport), + acm, &acm->port, acm->port_num, port_num); + + switch (acm->transport) { + case USB_GADGET_XPORT_TTY: + gserial_disconnect(&acm->port); + break; + case USB_GADGET_XPORT_SDIO: + gsdio_disconnect(&acm->port, port_num); + break; + case USB_GADGET_XPORT_SMD: + gsmd_disconnect(&acm->port, port_num); + break; + default: + pr_err("%s: Un-supported transport:%s\n", __func__, + xport_to_str(acm->transport)); + return -ENODEV; + } + + return 0; +} /*-------------------------------------------------------------------------*/ /* notification endpoint uses smallish and infrequent fixed-size messages */ @@ -359,8 +450,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; @@ -371,8 +461,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)); @@ -382,9 +470,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 @@ -392,6 +477,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: @@ -431,16 +522,17 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(acm->notify); } else { VDBG(cdev, "init acm ctrl interface %d\n", intf); - if (config_ep_by_speed(cdev->gadget, f, acm->notify)) - return -EINVAL; } + if (config_ep_by_speed(cdev->gadget, f, acm->notify)) + return -EINVAL; + usb_ep_enable(acm->notify); acm->notify->driver_data = acm; } 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); + acm_port_disconnect(acm); } if (!acm->port.in->desc || !acm->port.out->desc) { DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); @@ -453,7 +545,16 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) return -EINVAL; } } - gserial_connect(&acm->port, acm->port_num); + if (config_ep_by_speed(cdev->gadget, f, + acm->port.in) || + config_ep_by_speed(cdev->gadget, f, + acm->port.out)) { + acm->port.in->desc = NULL; + acm->port.out->desc = NULL; + return -EINVAL; + } + + acm_port_connect(acm); } else return -EINVAL; @@ -467,7 +568,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); + acm_port_disconnect(acm); usb_ep_disable(acm->notify); acm->notify->driver_data = NULL; } @@ -598,6 +699,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 */ @@ -678,6 +788,8 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) /* copy descriptors */ f->hs_descriptors = usb_copy_descriptors(acm_hs_function); + if (!f->hs_descriptors) + goto fail; } if (gadget_is_superspeed(c->cdev->gadget)) { acm_ss_in_desc.bEndpointAddress = @@ -700,6 +812,11 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) return 0; fail: + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->descriptors) + usb_free_descriptors(f->descriptors); + if (acm->notify_req) gs_free_req(acm->notify, acm->notify_req); @@ -727,6 +844,7 @@ acm_unbind(struct usb_configuration *c, struct usb_function *f) usb_free_descriptors(f->ss_descriptors); usb_free_descriptors(f->descriptors); gs_free_req(acm->notify, acm->notify_req); + kfree(acm->port.func.name); kfree(acm); } @@ -793,12 +911,18 @@ 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 = "acm"; + acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num + 1); + if (!acm->port.func.name) { + kfree(acm); + return -ENOMEM; + } acm->port.func.strings = acm_strings; /* descriptors are per-instance copies */ acm->port.func.bind = acm_bind; @@ -812,3 +936,44 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num) kfree(acm); return status; } + +/** + * acm_init_port - bind a acm_port to its transport + */ +static int acm_init_port(int port_num, const char *name) +{ + enum transport_type transport; + + if (port_num >= GSERIAL_NO_PORTS) + return -ENODEV; + + transport = str_to_xport(name); + pr_debug("%s, port:%d, transport:%s\n", __func__, + port_num, xport_to_str(transport)); + + gacm_ports[port_num].transport = transport; + gacm_ports[port_num].port_num = port_num; + + switch (transport) { + case USB_GADGET_XPORT_TTY: + gacm_ports[port_num].client_port_num = no_acm_tty_ports; + no_acm_tty_ports++; + break; + case USB_GADGET_XPORT_SDIO: + gacm_ports[port_num].client_port_num = no_acm_sdio_ports; + no_acm_sdio_ports++; + break; + case USB_GADGET_XPORT_SMD: + gacm_ports[port_num].client_port_num = no_acm_smd_ports; + no_acm_smd_ports++; + break; + default: + pr_err("%s: Un-supported transport transport: %u\n", + __func__, gacm_ports[port_num].transport); + return -ENODEV; + } + + nr_acm_ports++; + + return 0; +} diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c index 1629ffb5b9799e41a3db2a26c6d9c235098e7424..9778673f4f4f41b0fc84805b2949db8c14f8eff3 100644 --- a/drivers/usb/gadget/f_adb.c +++ b/drivers/usb/gadget/f_adb.c @@ -42,8 +42,8 @@ struct adb_dev { struct usb_ep *ep_in; struct usb_ep *ep_out; - int online; - int error; + atomic_t online; + atomic_t error; atomic_t read_excl; atomic_t write_excl; @@ -195,7 +195,7 @@ static void adb_complete_in(struct usb_ep *ep, struct usb_request *req) struct adb_dev *dev = _adb_dev; if (req->status != 0) - dev->error = 1; + atomic_set(&dev->error, 1); adb_req_put(dev, &dev->tx_idle, req); @@ -208,7 +208,7 @@ static void adb_complete_out(struct usb_ep *ep, struct usb_request *req) dev->rx_done = 1; if (req->status != 0 && req->status != -ECONNRESET) - dev->error = 1; + atomic_set(&dev->error, 1); wake_up(&dev->read_wq); } @@ -283,16 +283,17 @@ static ssize_t adb_read(struct file *fp, char __user *buf, return -EBUSY; /* we will block until we're online */ - while (!(dev->online || dev->error)) { + while (!(atomic_read(&dev->online) || atomic_read(&dev->error))) { pr_debug("adb_read: waiting for online state\n"); ret = wait_event_interruptible(dev->read_wq, - (dev->online || dev->error)); + (atomic_read(&dev->online) || + atomic_read(&dev->error))); if (ret < 0) { adb_unlock(&dev->read_excl); return ret; } } - if (dev->error) { + if (atomic_read(&dev->error)) { r = -EIO; goto done; } @@ -306,7 +307,7 @@ static ssize_t adb_read(struct file *fp, char __user *buf, if (ret < 0) { pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret); r = -EIO; - dev->error = 1; + atomic_set(&dev->error, 1); goto done; } else { pr_debug("rx %p queue\n", req); @@ -316,12 +317,12 @@ static ssize_t adb_read(struct file *fp, char __user *buf, ret = wait_event_interruptible(dev->read_wq, dev->rx_done); if (ret < 0) { if (ret != -ERESTARTSYS) - dev->error = 1; + atomic_set(&dev->error, 1); r = ret; usb_ep_dequeue(dev->ep_out, req); goto done; } - if (!dev->error) { + if (!atomic_read(&dev->error)) { /* If we got a 0-len packet, throw it back and try again. */ if (req->actual == 0) goto requeue_req; @@ -356,7 +357,7 @@ static ssize_t adb_write(struct file *fp, const char __user *buf, return -EBUSY; while (count > 0) { - if (dev->error) { + if (atomic_read(&dev->error)) { pr_debug("adb_write dev->error\n"); r = -EIO; break; @@ -365,7 +366,8 @@ static ssize_t adb_write(struct file *fp, const char __user *buf, /* get an idle tx request to use */ req = 0; ret = wait_event_interruptible(dev->write_wq, - (req = adb_req_get(dev, &dev->tx_idle)) || dev->error); + ((req = adb_req_get(dev, &dev->tx_idle)) || + atomic_read(&dev->error))); if (ret < 0) { r = ret; @@ -386,7 +388,7 @@ static ssize_t adb_write(struct file *fp, const char __user *buf, ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); if (ret < 0) { pr_debug("adb_write: xfer error %d\n", ret); - dev->error = 1; + atomic_set(&dev->error, 1); r = -EIO; break; } @@ -419,7 +421,7 @@ static int adb_open(struct inode *ip, struct file *fp) fp->private_data = _adb_dev; /* clear the error latch */ - _adb_dev->error = 0; + atomic_set(&_adb_dev->error, 0); adb_ready_callback(); @@ -498,8 +500,8 @@ adb_function_unbind(struct usb_configuration *c, struct usb_function *f) struct usb_request *req; - dev->online = 0; - dev->error = 1; + atomic_set(&dev->online, 0); + atomic_set(&dev->error, 1); wake_up(&dev->read_wq); @@ -518,23 +520,35 @@ static int adb_function_set_alt(struct usb_function *f, DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt); ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); - if (ret) + if (ret) { + dev->ep_in->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->ep_in->name, ret); return ret; - + } ret = usb_ep_enable(dev->ep_in); - if (ret) + if (ret) { + ERROR(cdev, "failed to enable ep %s, result %d\n", + dev->ep_in->name, ret); return ret; + } ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); - if (ret) + if (ret) { + dev->ep_out->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->ep_out->name, ret); + usb_ep_disable(dev->ep_in); return ret; - + } ret = usb_ep_enable(dev->ep_out); if (ret) { + ERROR(cdev, "failed to enable ep %s, result %d\n", + dev->ep_out->name, ret); usb_ep_disable(dev->ep_in); return ret; } - dev->online = 1; + atomic_set(&dev->online, 1); /* readers may be blocked waiting for us to go online */ wake_up(&dev->read_wq); @@ -547,8 +561,8 @@ static void adb_function_disable(struct usb_function *f) struct usb_composite_dev *cdev = dev->cdev; DBG(cdev, "adb_function_disable cdev %p\n", cdev); - dev->online = 0; - dev->error = 1; + atomic_set(&dev->online, 0); + atomic_set(&dev->error, 1); usb_ep_disable(dev->ep_in); usb_ep_disable(dev->ep_out); diff --git a/drivers/usb/gadget/f_ccid.c b/drivers/usb/gadget/f_ccid.c new file mode 100644 index 0000000000000000000000000000000000000000..c8f144abaf9a1f02697b87bef71aa0c0e92c2f09 --- /dev/null +++ b/drivers/usb/gadget/f_ccid.c @@ -0,0 +1,999 @@ +/* + * f_ccid.c -- CCID function 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 "f_ccid.h" + +#define BULK_IN_BUFFER_SIZE sizeof(struct ccid_bulk_in_header) +#define BULK_OUT_BUFFER_SIZE sizeof(struct ccid_bulk_out_header) +#define CTRL_BUF_SIZE 4 +#define FUNCTION_NAME "ccid" +#define CCID_NOTIFY_INTERVAL 5 +#define CCID_NOTIFY_MAXPACKET 4 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +struct ccid_ctrl_dev { + atomic_t opened; + struct list_head tx_q; + wait_queue_head_t tx_wait_q; + unsigned char buf[CTRL_BUF_SIZE]; + int tx_ctrl_done; +}; + +struct ccid_bulk_dev { + atomic_t error; + atomic_t opened; + atomic_t rx_req_busy; + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; + struct list_head tx_idle; +}; + +struct f_ccid { + struct usb_function function; + struct usb_composite_dev *cdev; + int ifc_id; + spinlock_t lock; + atomic_t online; + /* usb eps*/ + struct usb_ep *notify; + struct usb_ep *in; + struct usb_ep *out; + struct usb_request *notify_req; + struct ccid_ctrl_dev ctrl_dev; + struct ccid_bulk_dev bulk_dev; + int dtr_state; +}; + +static struct f_ccid *_ccid_dev; +static struct miscdevice ccid_bulk_device; +static struct miscdevice ccid_ctrl_device; + +/* Interface Descriptor: */ +static struct usb_interface_descriptor ccid_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_CSCID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, +}; +/* CCID Class Descriptor */ +static struct usb_ccid_class_descriptor ccid_class_desc = { + .bLength = sizeof(ccid_class_desc), + .bDescriptorType = CCID_DECRIPTOR_TYPE, + .bcdCCID = CCID1_10, + .bMaxSlotIndex = 0, + /* This value indicates what voltages the CCID can supply to slots */ + .bVoltageSupport = VOLTS_3_0, + .dwProtocols = PROTOCOL_TO, + /* Default ICC clock frequency in KHz */ + .dwDefaultClock = 3580, + /* Maximum supported ICC clock frequency in KHz */ + .dwMaximumClock = 3580, + .bNumClockSupported = 0, + /* Default ICC I/O data rate in bps */ + .dwDataRate = 9600, + /* Maximum supported ICC I/O data rate in bps */ + .dwMaxDataRate = 9600, + .bNumDataRatesSupported = 0, + .dwMaxIFSD = 0, + .dwSynchProtocols = 0, + .dwMechanical = 0, + /* This value indicates what intelligent features the CCID has */ + .dwFeatures = CCID_FEATURES_EXC_SAPDU | + CCID_FEATURES_AUTO_PNEGO | + CCID_FEATURES_AUTO_BAUD | + CCID_FEATURES_AUTO_CLOCK | + CCID_FEATURES_AUTO_VOLT | + CCID_FEATURES_AUTO_ACTIV | + CCID_FEATURES_AUTO_PCONF, + /* extended APDU level Message Length */ + .dwMaxCCIDMessageLength = 0x200, + .bClassGetResponse = 0x0, + .bClassEnvelope = 0x0, + .wLcdLayout = 0, + .bPINSupport = 0, + .bMaxCCIDBusySlots = 1 +}; +/* Full speed support: */ +static struct usb_endpoint_descriptor ccid_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(CCID_NOTIFY_MAXPACKET), + .bInterval = 1 << CCID_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor ccid_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 ccid_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 *ccid_fs_descs[] = { + (struct usb_descriptor_header *) &ccid_interface_desc, + (struct usb_descriptor_header *) &ccid_class_desc, + (struct usb_descriptor_header *) &ccid_fs_notify_desc, + (struct usb_descriptor_header *) &ccid_fs_in_desc, + (struct usb_descriptor_header *) &ccid_fs_out_desc, + NULL, +}; + +/* High speed support: */ +static struct usb_endpoint_descriptor ccid_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(CCID_NOTIFY_MAXPACKET), + .bInterval = CCID_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor ccid_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 ccid_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 *ccid_hs_descs[] = { + (struct usb_descriptor_header *) &ccid_interface_desc, + (struct usb_descriptor_header *) &ccid_class_desc, + (struct usb_descriptor_header *) &ccid_hs_notify_desc, + (struct usb_descriptor_header *) &ccid_hs_in_desc, + (struct usb_descriptor_header *) &ccid_hs_out_desc, + NULL, +}; + +static inline struct f_ccid *func_to_ccid(struct usb_function *f) +{ + return container_of(f, struct f_ccid, function); +} + +static void ccid_req_put(struct f_ccid *ccid_dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&ccid_dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&ccid_dev->lock, flags); +} + +static struct usb_request *ccid_req_get(struct f_ccid *ccid_dev, + struct list_head *head) +{ + unsigned long flags; + struct usb_request *req = NULL; + + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!list_empty(head)) { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&ccid_dev->lock, flags); + return req; +} + +static void ccid_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + switch (req->status) { + case -ECONNRESET: + case -ESHUTDOWN: + case 0: + break; + default: + pr_err("CCID notify ep error %d\n", req->status); + } +} + +static void ccid_bulk_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ccid *ccid_dev = _ccid_dev; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + + if (req->status != 0) + atomic_set(&bulk_dev->error, 1); + + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + wake_up(&bulk_dev->write_wq); +} + +static void ccid_bulk_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ccid *ccid_dev = _ccid_dev; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + if (req->status != 0) + atomic_set(&bulk_dev->error, 1); + + bulk_dev->rx_done = 1; + wake_up(&bulk_dev->read_wq); +} + +static struct usb_request * +ccid_request_alloc(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); +} + +static void ccid_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static int +ccid_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_ccid *ccid_dev = container_of(f, struct f_ccid, function); + struct ccid_ctrl_dev *ctrl_dev = &ccid_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); + + if (!atomic_read(&ccid_dev->online)) + return -ENOTCONN; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | CCIDGENERICREQ_ABORT: + if (w_length != 0) + goto invalid; + ctrl_dev->buf[0] = CCIDGENERICREQ_ABORT; + ctrl_dev->buf[1] = w_value & 0xFF; + ctrl_dev->buf[2] = (w_value >> 8) & 0xFF; + ctrl_dev->buf[3] = 0x00; + ctrl_dev->tx_ctrl_done = 1; + wake_up(&ctrl_dev->tx_wait_q); + return 0; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | CCIDGENERICREQ_GET_CLOCK_FREQUENCIES: + if (w_length > req->length) + goto invalid; + *(u32 *) req->buf = + cpu_to_le32(ccid_class_desc.dwDefaultClock); + ret = min_t(u32, w_length, + sizeof(ccid_class_desc.dwDefaultClock)); + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | CCIDGENERICREQ_GET_DATA_RATES: + if (w_length > req->length) + goto invalid; + *(u32 *) req->buf = cpu_to_le32(ccid_class_desc.dwDataRate); + ret = min_t(u32, w_length, sizeof(ccid_class_desc.dwDataRate)); + break; + + default: +invalid: + pr_debug("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) { + pr_debug("ccid req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + pr_err("ccid ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static void ccid_function_disable(struct usb_function *f) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + struct usb_request *req; + + /* Disable endpoints */ + usb_ep_disable(ccid_dev->notify); + usb_ep_disable(ccid_dev->in); + usb_ep_disable(ccid_dev->out); + /* Free endpoint related requests */ + ccid_request_free(ccid_dev->notify_req, ccid_dev->notify); + if (!atomic_read(&bulk_dev->rx_req_busy)) + ccid_request_free(bulk_dev->rx_req, ccid_dev->out); + while ((req = ccid_req_get(ccid_dev, &bulk_dev->tx_idle))) + ccid_request_free(req, ccid_dev->in); + + ccid_dev->dtr_state = 0; + atomic_set(&ccid_dev->online, 0); + /* Wake up threads */ + wake_up(&bulk_dev->write_wq); + wake_up(&bulk_dev->read_wq); + wake_up(&ctrl_dev->tx_wait_q); + +} + +static int +ccid_function_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct usb_composite_dev *cdev = ccid_dev->cdev; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req; + int ret = 0; + int i; + + ccid_dev->notify_req = ccid_request_alloc(ccid_dev->notify, + sizeof(struct usb_ccid_notification), GFP_ATOMIC); + if (IS_ERR(ccid_dev->notify_req)) { + pr_err("%s: unable to allocate memory for notify req\n", + __func__); + return PTR_ERR(ccid_dev->notify_req); + } + ccid_dev->notify_req->complete = ccid_notify_complete; + ccid_dev->notify_req->context = ccid_dev; + + /* now allocate requests for our endpoints */ + req = ccid_request_alloc(ccid_dev->out, BULK_OUT_BUFFER_SIZE, + GFP_ATOMIC); + if (IS_ERR(req)) { + pr_err("%s: unable to allocate memory for out req\n", + __func__); + ret = PTR_ERR(req); + goto free_notify; + } + req->complete = ccid_bulk_complete_out; + req->context = ccid_dev; + bulk_dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = ccid_request_alloc(ccid_dev->in, BULK_IN_BUFFER_SIZE, + GFP_ATOMIC); + if (IS_ERR(req)) { + pr_err("%s: unable to allocate memory for in req\n", + __func__); + ret = PTR_ERR(req); + goto free_bulk_out; + } + req->complete = ccid_bulk_complete_in; + req->context = ccid_dev; + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + } + + /* choose the descriptors and enable endpoints */ + ret = config_ep_by_speed(cdev->gadget, f, ccid_dev->notify); + if (ret) { + ccid_dev->notify->desc = NULL; + pr_err("%s: config_ep_by_speed failed for ep#%s, err#%d\n", + __func__, ccid_dev->notify->name, ret); + goto free_bulk_in; + } + ret = usb_ep_enable(ccid_dev->notify); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, ccid_dev->notify->name, ret); + goto free_bulk_in; + } + ccid_dev->notify->driver_data = ccid_dev; + + ret = config_ep_by_speed(cdev->gadget, f, ccid_dev->in); + if (ret) { + ccid_dev->in->desc = NULL; + pr_err("%s: config_ep_by_speed failed for ep#%s, err#%d\n", + __func__, ccid_dev->in->name, ret); + goto disable_ep_notify; + } + ret = usb_ep_enable(ccid_dev->in); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, ccid_dev->in->name, ret); + goto disable_ep_notify; + } + + ret = config_ep_by_speed(cdev->gadget, f, ccid_dev->out); + if (ret) { + ccid_dev->out->desc = NULL; + pr_err("%s: config_ep_by_speed failed for ep#%s, err#%d\n", + __func__, ccid_dev->out->name, ret); + goto disable_ep_in; + } + ret = usb_ep_enable(ccid_dev->out); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, ccid_dev->out->name, ret); + goto disable_ep_in; + } + ccid_dev->dtr_state = 1; + atomic_set(&ccid_dev->online, 1); + return ret; + +disable_ep_in: + usb_ep_disable(ccid_dev->in); +disable_ep_notify: + usb_ep_disable(ccid_dev->notify); + ccid_dev->notify->driver_data = NULL; +free_bulk_in: + while ((req = ccid_req_get(ccid_dev, &bulk_dev->tx_idle))) + ccid_request_free(req, ccid_dev->in); +free_bulk_out: + ccid_request_free(bulk_dev->rx_req, ccid_dev->out); +free_notify: + ccid_request_free(ccid_dev->notify_req, ccid_dev->notify); + return ret; +} + +static void ccid_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + +} + +static int ccid_function_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct usb_ep *ep; + struct usb_composite_dev *cdev = c->cdev; + int ret = -ENODEV; + + ccid_dev->ifc_id = usb_interface_id(c, f); + if (ccid_dev->ifc_id < 0) { + pr_err("%s: unable to allocate ifc id, err:%d", + __func__, ccid_dev->ifc_id); + return ccid_dev->ifc_id; + } + ccid_interface_desc.bInterfaceNumber = ccid_dev->ifc_id; + + ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_notify_desc); + if (!ep) { + pr_err("%s: usb epnotify autoconfig failed\n", __func__); + return -ENODEV; + } + ccid_dev->notify = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_in_desc); + if (!ep) { + pr_err("%s: usb epin autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_in_fail; + } + ccid_dev->in = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_out_desc); + if (!ep) { + pr_err("%s: usb epout autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_out_fail; + } + ccid_dev->out = ep; + ep->driver_data = cdev; + + f->descriptors = usb_copy_descriptors(ccid_fs_descs); + if (!f->descriptors) + goto ep_auto_out_fail; + + if (gadget_is_dualspeed(cdev->gadget)) { + ccid_hs_in_desc.bEndpointAddress = + ccid_fs_in_desc.bEndpointAddress; + ccid_hs_out_desc.bEndpointAddress = + ccid_fs_out_desc.bEndpointAddress; + ccid_hs_notify_desc.bEndpointAddress = + ccid_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(ccid_hs_descs); + if (!f->hs_descriptors) + goto ep_auto_out_fail; + } + + pr_debug("%s: CCID %s Speed, IN:%s OUT:%s\n", __func__, + gadget_is_dualspeed(cdev->gadget) ? "dual" : "full", + ccid_dev->in->name, ccid_dev->out->name); + + return 0; + +ep_auto_out_fail: + ccid_dev->out->driver_data = NULL; + ccid_dev->out = NULL; +ep_auto_in_fail: + ccid_dev->in->driver_data = NULL; + ccid_dev->in = NULL; + + return ret; +} + +static int ccid_bulk_open(struct inode *ip, struct file *fp) +{ + struct f_ccid *ccid_dev = _ccid_dev; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + unsigned long flags; + + pr_debug("ccid_bulk_open\n"); + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + + if (atomic_read(&bulk_dev->opened)) { + pr_debug("%s: bulk device is already opened\n", __func__); + return -EBUSY; + } + atomic_set(&bulk_dev->opened, 1); + /* clear the error latch */ + atomic_set(&bulk_dev->error, 0); + spin_lock_irqsave(&ccid_dev->lock, flags); + fp->private_data = ccid_dev; + spin_unlock_irqrestore(&ccid_dev->lock, flags); + + return 0; +} + +static int ccid_bulk_release(struct inode *ip, struct file *fp) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + + pr_debug("ccid_bulk_release\n"); + atomic_set(&bulk_dev->opened, 0); + return 0; +} + +static ssize_t ccid_bulk_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req; + int r = count, xfer; + int ret; + unsigned long flags; + + pr_debug("ccid_bulk_read(%d)\n", count); + + if (count > BULK_OUT_BUFFER_SIZE) { + pr_err("%s: max_buffer_size:%d given_pkt_size:%d\n", + __func__, BULK_OUT_BUFFER_SIZE, count); + return -ENOMEM; + } + + if (atomic_read(&bulk_dev->error)) { + r = -EIO; + pr_err("%s bulk_dev_error\n", __func__); + goto done; + } + +requeue_req: + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + /* queue a request */ + req = bulk_dev->rx_req; + req->length = count; + bulk_dev->rx_done = 0; + spin_unlock_irqrestore(&ccid_dev->lock, flags); + ret = usb_ep_queue(ccid_dev->out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + pr_err("%s usb ep queue failed\n", __func__); + atomic_set(&bulk_dev->error, 1); + goto done; + } + /* wait for a request to complete */ + ret = wait_event_interruptible(bulk_dev->read_wq, bulk_dev->rx_done || + atomic_read(&bulk_dev->error) || + !atomic_read(&ccid_dev->online)); + if (ret < 0) { + atomic_set(&bulk_dev->error, 1); + r = ret; + usb_ep_dequeue(ccid_dev->out, req); + goto done; + } + if (!atomic_read(&bulk_dev->error)) { + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!atomic_read(&ccid_dev->online)) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", __func__); + r = -ENODEV; + goto done; + } + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + goto requeue_req; + } + xfer = (req->actual < count) ? req->actual : count; + atomic_set(&bulk_dev->rx_req_busy, 1); + spin_unlock_irqrestore(&ccid_dev->lock, flags); + + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + spin_lock_irqsave(&ccid_dev->lock, flags); + atomic_set(&bulk_dev->rx_req_busy, 0); + if (!atomic_read(&ccid_dev->online)) { + ccid_request_free(bulk_dev->rx_req, ccid_dev->out); + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", __func__); + r = -ENODEV; + goto done; + } + spin_unlock_irqrestore(&ccid_dev->lock, flags); + } else { + r = -EIO; + } +done: + pr_debug("ccid_bulk_read returning %d\n", r); + return r; +} + +static ssize_t ccid_bulk_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req = 0; + int r = count; + int ret; + unsigned long flags; + + pr_debug("ccid_bulk_write(%d)\n", count); + + if (!atomic_read(&ccid_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 > BULK_IN_BUFFER_SIZE) { + pr_err("%s: max_buffer_size:%d given_pkt_size:%d\n", + __func__, BULK_IN_BUFFER_SIZE, count); + return -ENOMEM; + } + + + /* get an idle tx request to use */ + ret = wait_event_interruptible(bulk_dev->write_wq, + ((req = ccid_req_get(ccid_dev, &bulk_dev->tx_idle)) || + atomic_read(&bulk_dev->error))); + + if (ret < 0) { + r = ret; + goto done; + } + + if (atomic_read(&bulk_dev->error)) { + pr_err(" %s dev->error\n", __func__); + r = -EIO; + goto done; + } + if (copy_from_user(req->buf, buf, count)) { + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", + __func__); + ccid_request_free(req, ccid_dev->in); + r = -ENODEV; + } else { + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + r = -EFAULT; + } + goto done; + } + req->length = count; + ret = usb_ep_queue(ccid_dev->in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("ccid_bulk_write: xfer error %d\n", ret); + atomic_set(&bulk_dev->error, 1); + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + r = -EIO; + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!atomic_read(&ccid_dev->online)) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", + __func__); + while ((req = ccid_req_get(ccid_dev, + &bulk_dev->tx_idle))) + ccid_request_free(req, ccid_dev->in); + r = -ENODEV; + } + spin_unlock_irqrestore(&ccid_dev->lock, flags); + goto done; + } +done: + pr_debug("ccid_bulk_write returning %d\n", r); + return r; +} + +static const struct file_operations ccid_bulk_fops = { + .owner = THIS_MODULE, + .read = ccid_bulk_read, + .write = ccid_bulk_write, + .open = ccid_bulk_open, + .release = ccid_bulk_release, +}; + +static struct miscdevice ccid_bulk_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ccid_bulk", + .fops = &ccid_bulk_fops, +}; + +static int ccid_bulk_device_init(struct f_ccid *dev) +{ + int ret; + struct ccid_bulk_dev *bulk_dev = &dev->bulk_dev; + + init_waitqueue_head(&bulk_dev->read_wq); + init_waitqueue_head(&bulk_dev->write_wq); + INIT_LIST_HEAD(&bulk_dev->tx_idle); + + ret = misc_register(&ccid_bulk_device); + if (ret) { + pr_err("%s: failed to register misc device\n", __func__); + return ret; + } + + return 0; +} + +static int ccid_ctrl_open(struct inode *inode, struct file *fp) +{ + struct f_ccid *ccid_dev = _ccid_dev; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + unsigned long flags; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + if (atomic_read(&ctrl_dev->opened)) { + pr_debug("%s: ctrl device is already opened\n", __func__); + return -EBUSY; + } + atomic_set(&ctrl_dev->opened, 1); + spin_lock_irqsave(&ccid_dev->lock, flags); + fp->private_data = ccid_dev; + spin_unlock_irqrestore(&ccid_dev->lock, flags); + + return 0; +} + + +static int ccid_ctrl_release(struct inode *inode, struct file *fp) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + + atomic_set(&ctrl_dev->opened, 0); + + return 0; +} + +static ssize_t ccid_ctrl_read(struct file *fp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + int ret = 0; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + if (count > CTRL_BUF_SIZE) + count = CTRL_BUF_SIZE; + + ret = wait_event_interruptible(ctrl_dev->tx_wait_q, + ctrl_dev->tx_ctrl_done); + if (ret < 0) + return ret; + ctrl_dev->tx_ctrl_done = 0; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + ret = copy_to_user(buf, ctrl_dev->buf, count); + if (ret) + return -EFAULT; + + return count; +} + +static long +ccid_ctrl_ioctl(struct file *fp, unsigned cmd, u_long arg) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct usb_request *req = ccid_dev->notify_req; + struct usb_ccid_notification *ccid_notify = req->buf; + void __user *argp = (void __user *)arg; + int ret = 0; + + switch (cmd) { + case CCID_NOTIFY_CARD: + if (copy_from_user(ccid_notify, argp, + sizeof(struct usb_ccid_notification))) + return -EFAULT; + req->length = 2; + break; + case CCID_NOTIFY_HWERROR: + if (copy_from_user(ccid_notify, argp, + sizeof(struct usb_ccid_notification))) + return -EFAULT; + req->length = 4; + break; + case CCID_READ_DTR: + if (copy_to_user((int *)arg, &ccid_dev->dtr_state, sizeof(int))) + return -EFAULT; + return 0; + } + ret = usb_ep_queue(ccid_dev->notify, ccid_dev->notify_req, GFP_KERNEL); + if (ret < 0) { + pr_err("ccid notify ep enqueue error %d\n", ret); + return ret; + } + return 0; +} + +static const struct file_operations ccid_ctrl_fops = { + .owner = THIS_MODULE, + .open = ccid_ctrl_open, + .release = ccid_ctrl_release, + .read = ccid_ctrl_read, + .unlocked_ioctl = ccid_ctrl_ioctl, +}; + +static struct miscdevice ccid_ctrl_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ccid_ctrl", + .fops = &ccid_ctrl_fops, +}; + +static int ccid_ctrl_device_init(struct f_ccid *dev) +{ + int ret; + struct ccid_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + + INIT_LIST_HEAD(&ctrl_dev->tx_q); + init_waitqueue_head(&ctrl_dev->tx_wait_q); + + ret = misc_register(&ccid_ctrl_device); + if (ret) { + pr_err("%s: failed to register misc device\n", __func__); + return ret; + } + + return 0; +} + +static int ccid_bind_config(struct usb_configuration *c) +{ + struct f_ccid *ccid_dev = _ccid_dev; + + pr_debug("ccid_bind_config\n"); + ccid_dev->cdev = c->cdev; + ccid_dev->function.name = FUNCTION_NAME; + ccid_dev->function.descriptors = ccid_fs_descs; + ccid_dev->function.hs_descriptors = ccid_hs_descs; + ccid_dev->function.bind = ccid_function_bind; + ccid_dev->function.unbind = ccid_function_unbind; + ccid_dev->function.set_alt = ccid_function_set_alt; + ccid_dev->function.setup = ccid_function_setup; + ccid_dev->function.disable = ccid_function_disable; + + return usb_add_function(c, &ccid_dev->function); + +} + +static int ccid_setup(void) +{ + struct f_ccid *ccid_dev; + int ret; + + ccid_dev = kzalloc(sizeof(*ccid_dev), GFP_KERNEL); + if (!ccid_dev) + return -ENOMEM; + + _ccid_dev = ccid_dev; + spin_lock_init(&ccid_dev->lock); + + ret = ccid_ctrl_device_init(ccid_dev); + if (ret) { + pr_err("%s: ccid_ctrl_device_init failed, err:%d\n", + __func__, ret); + goto err_ctrl_init; + } + ret = ccid_bulk_device_init(ccid_dev); + if (ret) { + pr_err("%s: ccid_bulk_device_init failed, err:%d\n", + __func__, ret); + goto err_bulk_init; + } + + return 0; +err_bulk_init: + misc_deregister(&ccid_ctrl_device); +err_ctrl_init: + kfree(ccid_dev); + pr_err("ccid gadget driver failed to initialize\n"); + return ret; +} + +static void ccid_cleanup(void) +{ + misc_deregister(&ccid_bulk_device); + misc_deregister(&ccid_ctrl_device); + kfree(_ccid_dev); +} diff --git a/drivers/usb/gadget/f_ccid.h b/drivers/usb/gadget/f_ccid.h new file mode 100644 index 0000000000000000000000000000000000000000..4d6a0eac6ad0bce20620cbc0fe8191a88e2208ae --- /dev/null +++ b/drivers/usb/gadget/f_ccid.h @@ -0,0 +1,83 @@ +/* + * 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 __F_CCID_H +#define __F_CCID_H + +#define PROTOCOL_TO 0x01 +#define PROTOCOL_T1 0x02 +#define ABDATA_SIZE 512 + +/* define for dwFeatures for Smart Card Device Class Descriptors */ +/* No special characteristics */ +#define CCID_FEATURES_NADA 0x00000000 +/* Automatic parameter configuration based on ATR data */ +#define CCID_FEATURES_AUTO_PCONF 0x00000002 +/* Automatic activation of ICC on inserting */ +#define CCID_FEATURES_AUTO_ACTIV 0x00000004 +/* Automatic ICC voltage selection */ +#define CCID_FEATURES_AUTO_VOLT 0x00000008 +/* Automatic ICC clock frequency change */ +#define CCID_FEATURES_AUTO_CLOCK 0x00000010 +/* Automatic baud rate change */ +#define CCID_FEATURES_AUTO_BAUD 0x00000020 +/*Automatic parameters negotiation made by the CCID */ +#define CCID_FEATURES_AUTO_PNEGO 0x00000040 +/* Automatic PPS made by the CCID according to the active parameters */ +#define CCID_FEATURES_AUTO_PPS 0x00000080 +/* CCID can set ICC in clock stop mode */ +#define CCID_FEATURES_ICCSTOP 0x00000100 +/* NAD value other than 00 accepted (T=1 protocol in use) */ +#define CCID_FEATURES_NAD 0x00000200 +/* Automatic IFSD exchange as first exchange (T=1 protocol in use) */ +#define CCID_FEATURES_AUTO_IFSD 0x00000400 +/* TPDU level exchanges with CCID */ +#define CCID_FEATURES_EXC_TPDU 0x00010000 +/* Short APDU level exchange with CCID */ +#define CCID_FEATURES_EXC_SAPDU 0x00020000 +/* Short and Extended APDU level exchange with CCID */ +#define CCID_FEATURES_EXC_APDU 0x00040000 +/* USB Wake up signaling supported on card insertion and removal */ +#define CCID_FEATURES_WAKEUP 0x00100000 + +#define CCID_NOTIFY_CARD _IOW('C', 1, struct usb_ccid_notification) +#define CCID_NOTIFY_HWERROR _IOW('C', 2, struct usb_ccid_notification) +#define CCID_READ_DTR _IOR('C', 3, int) + +struct usb_ccid_notification { + unsigned char buf[4]; +} __packed; + +struct ccid_bulk_in_header { + unsigned char bMessageType; + unsigned long wLength; + unsigned char bSlot; + unsigned char bSeq; + unsigned char bStatus; + unsigned char bError; + unsigned char bSpecific; + unsigned char abData[ABDATA_SIZE]; + unsigned char bSizeToSend; +} __packed; + +struct ccid_bulk_out_header { + unsigned char bMessageType; + unsigned long wLength; + unsigned char bSlot; + unsigned char bSeq; + unsigned char bSpecific_0; + unsigned char bSpecific_1; + unsigned char bSpecific_2; + unsigned char APDU[ABDATA_SIZE]; +} __packed; +#endif diff --git a/drivers/usb/gadget/f_diag.c b/drivers/usb/gadget/f_diag.c new file mode 100644 index 0000000000000000000000000000000000000000..72bff49028f6ee2305db1611ceafc493682ade2e --- /dev/null +++ b/drivers/usb/gadget/f_diag.c @@ -0,0 +1,759 @@ +/* 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-2012, 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 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; + + if (!ctxt) + return -ENODEV; + + 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; + + if (!ctxt) + return -ENODEV; + + 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; + int rc = 0; + + if (config_ep_by_speed(cdev->gadget, f, dev->in) || + config_ep_by_speed(cdev->gadget, f, dev->out)) { + dev->in->desc = NULL; + dev->out->desc = NULL; + return -EINVAL; + } + + dev->in->driver_data = dev; + rc = usb_ep_enable(dev->in); + 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); + 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); + + dev->dpkts_tolaptop = 0; + dev->dpkts_tomodem = 0; + dev->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); + if (!ep) + goto fail; + ctxt->in = ep; + ep->driver_data = ctxt; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_out_desc); + if (!ep) + goto fail; + 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 = ch->priv_usb; + + if (ctxt) + temp += scnprintf(buf + temp, PAGE_SIZE - temp, + "---Name: %s---\n" + "endpoints: %s, %s\n" + "dpkts_tolaptop: %lu\n" + "dpkts_tomodem: %lu\n" + "pkts_tolaptop_pending: %u\n", + ch->name, + ctxt->in->name, ctxt->out->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 = ch->priv_usb; + + if (ctxt) { + 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, +}; + +struct dentry *dent_diag; +static void fdiag_debugfs_init(void) +{ + dent_diag = debugfs_create_dir("usb_diag", 0); + if (IS_ERR(dent_diag)) + return; + + debugfs_create_file("status", 0444, dent_diag, 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; + + debugfs_remove_recursive(dent_diag); + + 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 0000000000000000000000000000000000000000..82d9a25245f0e8e6ead772fe4805f72bbefec688 --- /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 cb8c162cae5af059c5c8cce2c07679b9b3611177..278e04e202d12a21566f67f5ac5a25f736ef5720 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -310,7 +310,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; @@ -467,6 +470,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) { @@ -746,6 +750,9 @@ static int do_read(struct fsg_common *common) loff_t file_offset, file_offset_tmp; unsigned int amount; ssize_t nread; +#ifdef CONFIG_USB_MSC_PROFILING + ktime_t start, diff; +#endif /* * Get the starting Logical Block Address and check that it's @@ -813,11 +820,20 @@ static int do_read(struct fsg_common *common) /* Perform the read */ file_offset_tmp = file_offset; + +#ifdef CONFIG_USB_MSC_PROFILING + start = ktime_get(); +#endif 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); + (unsigned long long) file_offset, (int) nread); +#ifdef CONFIG_USB_MSC_PROFILING + diff = ktime_sub(ktime_get(), start); + curlun->perf.rbytes += nread; + curlun->perf.rtime = ktime_add(curlun->perf.rtime, diff); +#endif if (signal_pending(current)) return -EINTR; @@ -879,6 +895,13 @@ static int do_write(struct fsg_common *common) ssize_t nwritten; int rc; +#ifdef CONFIG_USB_CSW_HACK + int i; +#endif + +#ifdef CONFIG_USB_MSC_PROFILING + ktime_t start, diff; +#endif if (curlun->ro) { curlun->sense_data = SS_WRITE_PROTECTED; return -EINVAL; @@ -971,7 +994,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; @@ -1006,11 +1039,20 @@ static int do_write(struct fsg_common *common) /* Perform the write */ file_offset_tmp = file_offset; +#ifdef CONFIG_USB_MSC_PROFILING + start = ktime_get(); +#endif 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); +#ifdef CONFIG_USB_MSC_PROFILING + diff = ktime_sub(ktime_get(), start); + curlun->perf.wbytes += nwritten; + curlun->perf.wtime = + ktime_add(curlun->perf.wtime, diff); +#endif if (signal_pending(current)) return -EINTR; /* Interrupted! */ @@ -1033,9 +1075,37 @@ static int do_write(struct fsg_common *common) curlun->sense_data_info = file_offset >> curlun->blkbits; 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 + empty_write: /* Did the host decide to stop early? */ if (bh->outreq->actual < bh->bulk_out_intended_length) { @@ -1497,8 +1567,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; @@ -1778,6 +1847,19 @@ static int send_status(struct fsg_common *common) csw->Signature = cpu_to_le32(US_BULK_CS_SIGN); 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 = US_BULK_CS_WRAP_LEN; @@ -2366,15 +2448,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); @@ -2387,28 +2460,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 */ - rc = config_ep_by_speed(common->gadget, &(fsg->function), fsg->bulk_in); - if (rc) - goto reset; - rc = usb_ep_enable(fsg->bulk_in); - if (rc) - goto reset; - fsg->bulk_in->driver_data = common; - fsg->bulk_in_enabled = 1; - - rc = config_ep_by_speed(common->gadget, &(fsg->function), - fsg->bulk_out); - if (rc) - goto reset; - rc = usb_ep_enable(fsg->bulk_out); - if (rc) - goto reset; - fsg->bulk_out->driver_data = common; - fsg->bulk_out_enabled = 1; - common->bulk_out_maxpacket = usb_endpoint_maxp(fsg->bulk_out->desc); - clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); - /* Allocate the requests */ for (i = 0; i < fsg_num_buffers; ++i) { struct fsg_buffhd *bh = &common->buffhds[i]; @@ -2437,14 +2488,55 @@ 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; + int rc; + + /* Enable the endpoints */ + rc = config_ep_by_speed(common->gadget, &(fsg->function), fsg->bulk_in); + if (rc) + return rc; + rc = usb_ep_enable(fsg->bulk_in); + if (rc) + return rc; + fsg->bulk_in->driver_data = common; + fsg->bulk_in_enabled = 1; + + rc = config_ep_by_speed(common->gadget, &(fsg->function), + fsg->bulk_out); + if (rc) + goto reset_bulk_int; + rc = usb_ep_enable(fsg->bulk_out); + if (rc) + goto reset_bulk_int; + fsg->bulk_out->driver_data = common; + fsg->bulk_out_enabled = 1; + common->bulk_out_maxpacket = le16_to_cpu(fsg->bulk_in->desc->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; + +reset_bulk_int: + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + return rc; } 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); } @@ -2651,6 +2743,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; @@ -2691,7 +2793,9 @@ static int fsg_main_thread(void *common_) static DEVICE_ATTR(ro, 0644, fsg_show_ro, fsg_store_ro); static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, fsg_store_nofua); static DEVICE_ATTR(file, 0644, fsg_show_file, fsg_store_file); - +#ifdef CONFIG_USB_MSC_PROFILING +static DEVICE_ATTR(perf, 0644, fsg_show_perf, fsg_store_perf); +#endif /****************************** FSG COMMON ******************************/ @@ -2788,6 +2892,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 */ @@ -2815,7 +2920,12 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, rc = device_create_file(&curlun->dev, &dev_attr_nofua); if (rc) goto error_luns; - +#ifdef CONFIG_USB_MSC_PROFILING + rc = device_create_file(&curlun->dev, &dev_attr_perf); + if (rc) + dev_err(&gadget->dev, "failed to create sysfs entry:" + "(dev_attr_perf) error: %d\n", rc); +#endif if (lcfg->filename) { rc = fsg_lun_open(curlun, lcfg->filename); if (rc) @@ -2944,6 +3054,9 @@ static void fsg_common_release(struct kref *ref) /* In error recovery common->nluns may be zero. */ for (; i; --i, ++lun) { +#ifdef CONFIG_USB_MSC_PROFILING + device_remove_file(&lun->dev, &dev_attr_perf); +#endif device_remove_file(&lun->dev, &dev_attr_nofua); device_remove_file(&lun->dev, &dev_attr_ro); device_remove_file(&lun->dev, &dev_attr_file); @@ -3085,7 +3198,7 @@ static int fsg_bind_config(struct usb_composite_dev *cdev, if (unlikely(!fsg)) return -ENOMEM; - fsg->function.name = FSG_DRIVER_DESC; + fsg->function.name = "mass_storage"; fsg->function.strings = fsg_strings_array; fsg->function.bind = fsg_bind; fsg->function.unbind = fsg_unbind; diff --git a/drivers/usb/gadget/f_mbim.c b/drivers/usb/gadget/f_mbim.c new file mode 100644 index 0000000000000000000000000000000000000000..41a1777ed38df6306bb5ddd7b63dafd449f93991 --- /dev/null +++ b/drivers/usb/gadget/f_mbim.c @@ -0,0 +1,1804 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 + +/* + * This function is a "Mobile Broadband Interface Model" (MBIM) link. + * MBIM is intended to be used with high-speed network attachments. + * + * Note that MBIM requires the use of "alternate settings" for its data + * interface. This means that the set_alt() method has real work to do, + * and also means that a get_alt() method is required. + */ + +#define MBIM_BULK_BUFFER_SIZE 4096 + +#define MBIM_IOCTL_MAGIC 'o' +#define MBIM_GET_NTB_SIZE _IOR(MBIM_IOCTL_MAGIC, 2, u32) +#define MBIM_GET_DATAGRAM_COUNT _IOR(MBIM_IOCTL_MAGIC, 3, u16) + +#define NR_MBIM_PORTS 1 + +struct ctrl_pkt { + void *buf; + int len; + struct list_head list; +}; + +struct mbim_ep_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + struct usb_endpoint_descriptor *notify; +}; + +struct mbim_notify_port { + struct usb_ep *notify; + struct usb_request *notify_req; + u8 notify_state; + atomic_t notify_count; +}; + +enum mbim_notify_state { + NCM_NOTIFY_NONE, + NCM_NOTIFY_CONNECT, + NCM_NOTIFY_SPEED, +}; + +struct f_mbim { + struct usb_function function; + struct usb_composite_dev *cdev; + + atomic_t online; + bool is_open; + + atomic_t open_excl; + atomic_t ioctl_excl; + atomic_t read_excl; + atomic_t write_excl; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + + u8 port_num; + struct data_port bam_port; + struct mbim_notify_port not_port; + + struct mbim_ep_descs fs; + struct mbim_ep_descs hs; + + u8 ctrl_id, data_id; + + struct ndp_parser_opts *parser_opts; + + spinlock_t lock; + + struct list_head cpkt_req_q; + struct list_head cpkt_resp_q; + + u32 ntb_input_size; + u16 ntb_max_datagrams; + + atomic_t error; +}; + +struct mbim_ntb_input_size { + u32 ntb_input_size; + u16 ntb_max_datagrams; + u16 reserved; +}; + +/* temporary variable used between mbim_open() and mbim_gadget_bind() */ +static struct f_mbim *_mbim_dev; + +static unsigned int nr_mbim_ports; + +static struct mbim_ports { + struct f_mbim *port; + unsigned port_num; +} mbim_ports[NR_MBIM_PORTS]; + +static inline struct f_mbim *func_to_mbim(struct usb_function *f) +{ + return container_of(f, struct f_mbim, function); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned mbim_bitrate(struct usb_gadget *g) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +#define NTB_DEFAULT_IN_SIZE (0x4000) +#define NTB_OUT_SIZE (0x1000) +#define NDP_IN_DIVISOR (0x4) + +#define FORMATS_SUPPORTED USB_CDC_NCM_NTB16_SUPPORTED + +static struct usb_cdc_ncm_ntb_parameters ntb_parameters = { + .wLength = sizeof ntb_parameters, + .bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED), + .dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE), + .wNdpInDivisor = cpu_to_le16(NDP_IN_DIVISOR), + .wNdpInPayloadRemainder = cpu_to_le16(0), + .wNdpInAlignment = cpu_to_le16(4), + + .dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE), + .wNdpOutDivisor = cpu_to_le16(4), + .wNdpOutPayloadRemainder = cpu_to_le16(0), + .wNdpOutAlignment = cpu_to_le16(4), + .wNtbOutMaxDatagrams = 0, +}; + +/* + * 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. + */ + +#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +static struct usb_interface_assoc_descriptor mbim_iad_desc = { + .bLength = sizeof mbim_iad_desc, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = 2, + .bFunctionSubClass = 0x0e, + .bFunctionProtocol = 0, + /* .iFunction = DYNAMIC */ +}; + +/* interface descriptor: */ +static struct usb_interface_descriptor mbim_control_intf = { + .bLength = sizeof mbim_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = 0x02, + .bInterfaceSubClass = 0x0e, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc mbim_header_desc = { + .bLength = sizeof mbim_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc mbim_union_desc = { + .bLength = sizeof(mbim_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_mbb_desc mbb_desc = { + .bLength = sizeof mbb_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_MBB_TYPE, + + .bcdMbbVersion = cpu_to_le16(0x0100), + + .wMaxControlMessage = cpu_to_le16(0x1000), + .bNumberFilters = 0x10, + .bMaxFilterSize = 0x80, + .wMaxSegmentSize = cpu_to_le16(0xfe0), + .bmNetworkCapabilities = 0x20, +}; + +/* the default data interface has no endpoints ... */ +static struct usb_interface_descriptor mbim_data_nop_intf = { + .bLength = sizeof mbim_data_nop_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = 0x0a, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0x02, + /* .iInterface = DYNAMIC */ +}; + +/* ... but the "real" data interface has two bulk endpoints */ +static struct usb_interface_descriptor mbim_data_intf = { + .bLength = sizeof mbim_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = 0x0a, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0x02, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_mbim_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 4*cpu_to_le16(NCM_STATUS_BYTECOUNT), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor fs_mbim_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_mbim_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *mbim_fs_function[] = { + (struct usb_descriptor_header *) &mbim_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_control_intf, + (struct usb_descriptor_header *) &mbim_header_desc, + (struct usb_descriptor_header *) &mbb_desc, + (struct usb_descriptor_header *) &fs_mbim_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_data_nop_intf, + (struct usb_descriptor_header *) &mbim_data_intf, + (struct usb_descriptor_header *) &fs_mbim_in_desc, + (struct usb_descriptor_header *) &fs_mbim_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_mbim_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 4*cpu_to_le16(NCM_STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor hs_mbim_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_mbim_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *mbim_hs_function[] = { + (struct usb_descriptor_header *) &mbim_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_control_intf, + (struct usb_descriptor_header *) &mbim_header_desc, + (struct usb_descriptor_header *) &mbb_desc, + (struct usb_descriptor_header *) &hs_mbim_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_data_nop_intf, + (struct usb_descriptor_header *) &mbim_data_intf, + (struct usb_descriptor_header *) &hs_mbim_in_desc, + (struct usb_descriptor_header *) &hs_mbim_out_desc, + NULL, +}; + +/* string descriptors: */ + +#define STRING_CTRL_IDX 0 +#define STRING_DATA_IDX 1 + +static struct usb_string mbim_string_defs[] = { + [STRING_CTRL_IDX].s = "MBIM Control", + [STRING_DATA_IDX].s = "MBIM Data", + { } /* end of list */ +}; + +static struct usb_gadget_strings mbim_string_table = { + .language = 0x0409, /* en-us */ + .strings = mbim_string_defs, +}; + +static struct usb_gadget_strings *mbim_strings[] = { + &mbim_string_table, + NULL, +}; + +/* + * Here are options for the Datagram Pointer table (NDP) parser. + * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3), + * in NDP16 offsets and sizes fields are 1 16bit word wide, + * in NDP32 -- 2 16bit words wide. Also signatures are different. + * To make the parser code the same, put the differences in the structure, + * and switch pointers to the structures when the format is changed. + */ + +struct ndp_parser_opts { + u32 nth_sign; + u32 ndp_sign; + unsigned nth_size; + unsigned ndp_size; + unsigned ndplen_align; + /* sizes in u16 units */ + unsigned dgram_item_len; /* index or length */ + unsigned block_length; + unsigned fp_index; + unsigned reserved1; + unsigned reserved2; + unsigned next_fp_index; +}; + +#define INIT_NDP16_OPTS { \ + .nth_sign = USB_CDC_NCM_NTH16_SIGN, \ + .ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \ + .nth_size = sizeof(struct usb_cdc_ncm_nth16), \ + .ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \ + .ndplen_align = 4, \ + .dgram_item_len = 1, \ + .block_length = 1, \ + .fp_index = 1, \ + .reserved1 = 0, \ + .reserved2 = 0, \ + .next_fp_index = 1, \ +} + +#define INIT_NDP32_OPTS { \ + .nth_sign = USB_CDC_NCM_NTH32_SIGN, \ + .ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \ + .nth_size = sizeof(struct usb_cdc_ncm_nth32), \ + .ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \ + .ndplen_align = 8, \ + .dgram_item_len = 2, \ + .block_length = 2, \ + .fp_index = 2, \ + .reserved1 = 1, \ + .reserved2 = 2, \ + .next_fp_index = 2, \ +} + +static struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS; +static struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS; + +static inline int mbim_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -EBUSY; + } +} + +static inline void mbim_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +static struct ctrl_pkt *mbim_alloc_ctrl_pkt(unsigned len, gfp_t flags) +{ + struct ctrl_pkt *pkt; + + pkt = kzalloc(sizeof(struct 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 mbim_free_ctrl_pkt(struct ctrl_pkt *pkt) +{ + if (pkt) { + kfree(pkt->buf); + kfree(pkt); + } +} + +static struct usb_request *mbim_alloc_req(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + req->length = buffer_size; + return req; +} + +void fmbim_free_req(struct usb_ep *ep, struct usb_request *req) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static void fmbim_ctrl_response_available(struct f_mbim *dev) +{ + struct usb_request *req = dev->not_port.notify_req; + struct usb_cdc_notification *event = NULL; + unsigned long flags; + int ret; + + int notif_c = 0; + + pr_info("dev:%p portno#%d\n", dev, dev->port_num); + + spin_lock_irqsave(&dev->lock, flags); + + if (!atomic_read(&dev->online)) { + pr_info("dev:%p is not online\n", dev); + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + if (!req) { + pr_info("dev:%p req is NULL\n", dev); + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + if (!req->buf) { + pr_info("dev:%p req->buf is NULL\n", dev); + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + notif_c = atomic_inc_return(&dev->not_port.notify_count); + pr_info("atomic_inc_return[notif_c] = %d", notif_c); + + 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->ctrl_id); + event->wLength = cpu_to_le16(0); + spin_unlock_irqrestore(&dev->lock, flags); + + pr_info("Call usb_ep_queue"); + + ret = usb_ep_queue(dev->not_port.notify, + dev->not_port.notify_req, GFP_ATOMIC); + if (ret) { + atomic_dec(&dev->not_port.notify_count); + pr_err("ep enqueue error %d\n", ret); + } + + pr_info("Succcessfull Exit"); +} + +static int +fmbim_send_cpkt_response(struct f_mbim *gr, struct ctrl_pkt *cpkt) +{ + struct f_mbim *dev = gr; + unsigned long flags; + + if (!gr || !cpkt) { + pr_err("Invalid cpkt, dev:%p cpkt:%p\n", + gr, cpkt); + return -ENODEV; + } + + pr_info("dev:%p port_num#%d\n", dev, dev->port_num); + + if (!atomic_read(&dev->online)) { + pr_info("dev:%p is not connected\n", dev); + mbim_free_ctrl_pkt(cpkt); + return 0; + } + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&cpkt->list, &dev->cpkt_resp_q); + spin_unlock_irqrestore(&dev->lock, flags); + + fmbim_ctrl_response_available(dev); + + return 0; +} + +/* ---------------------------- BAM INTERFACE ----------------------------- */ + +static int mbim_bam_setup(int no_ports) +{ + int ret; + + pr_info("no_ports:%d\n", no_ports); + + ret = bam_data_setup(no_ports); + if (ret) { + pr_err("bam_data_setup failed err: %d\n", ret); + return ret; + } + + pr_info("Initialized %d ports\n", no_ports); + return 0; +} + +static int mbim_bam_connect(struct f_mbim *dev) +{ + int ret; + + pr_info("dev:%p portno:%d\n", dev, dev->port_num); + + ret = bam_data_connect(&dev->bam_port, dev->port_num, dev->port_num); + if (ret) { + pr_err("bam_data_setup failed: err:%d\n", + ret); + return ret; + } else { + pr_info("mbim bam connected\n"); + } + + return 0; +} + +static int mbim_bam_disconnect(struct f_mbim *dev) +{ + pr_info("dev:%p port:%d. Do nothing.\n", + dev, dev->port_num); + + /* bam_data_disconnect(&dev->bam_port, dev->port_num); */ + + return 0; +} + +/* -------------------------------------------------------------------------*/ + +static inline void mbim_reset_values(struct f_mbim *mbim) +{ + mbim->parser_opts = &ndp16_opts; + + mbim->ntb_input_size = NTB_DEFAULT_IN_SIZE; + + atomic_set(&mbim->not_port.notify_count, 0); + atomic_set(&mbim->online, 0); +} + +static void mbim_reset_function_queue(struct f_mbim *dev) +{ + struct ctrl_pkt *cpkt = NULL; + + pr_debug("Queue empty packet for QBI"); + + spin_lock(&dev->lock); + if (!dev->is_open) { + pr_err("%s: mbim file handler %p is not open", __func__, dev); + spin_unlock(&dev->lock); + return; + } + + cpkt = mbim_alloc_ctrl_pkt(0, GFP_ATOMIC); + if (!cpkt) { + pr_err("%s: Unable to allocate reset function pkt\n", __func__); + spin_unlock(&dev->lock); + return; + } + + list_add_tail(&cpkt->list, &dev->cpkt_req_q); + spin_unlock(&dev->lock); + + pr_debug("%s: Wake up read queue", __func__); + wake_up(&dev->read_wq); +} + +static void fmbim_reset_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_mbim *dev = req->context; + + mbim_reset_function_queue(dev); +} + +static void mbim_clear_queues(struct f_mbim *mbim) +{ + struct ctrl_pkt *cpkt = NULL; + struct list_head *act, *tmp; + + spin_lock(&mbim->lock); + list_for_each_safe(act, tmp, &mbim->cpkt_req_q) { + cpkt = list_entry(act, struct ctrl_pkt, list); + list_del(&cpkt->list); + mbim_free_ctrl_pkt(cpkt); + } + list_for_each_safe(act, tmp, &mbim->cpkt_resp_q) { + cpkt = list_entry(act, struct ctrl_pkt, list); + list_del(&cpkt->list); + mbim_free_ctrl_pkt(cpkt); + } + spin_unlock(&mbim->lock); +} + +/* + * Context: mbim->lock held + */ +static void mbim_do_notify(struct f_mbim *mbim) +{ + struct usb_request *req = mbim->not_port.notify_req; + struct usb_cdc_notification *event; + struct usb_composite_dev *cdev = mbim->cdev; + __le32 *data; + int status; + + pr_info("notify_state: %d", mbim->not_port.notify_state); + + if (!req) + return; + + event = req->buf; + + switch (mbim->not_port.notify_state) { + + case NCM_NOTIFY_NONE: + return; + + case NCM_NOTIFY_CONNECT: + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + if (mbim->is_open) + event->wValue = cpu_to_le16(1); + else + event->wValue = cpu_to_le16(0); + event->wLength = 0; + req->length = sizeof *event; + + pr_info("notify connect %s\n", + mbim->is_open ? "true" : "false"); + mbim->not_port.notify_state = NCM_NOTIFY_NONE; + break; + + case NCM_NOTIFY_SPEED: + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(8); + req->length = NCM_STATUS_BYTECOUNT; + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data = req->buf + sizeof *event; + data[0] = cpu_to_le32(mbim_bitrate(cdev->gadget)); + data[1] = data[0]; + + pr_info("notify speed %d\n", + mbim_bitrate(cdev->gadget)); + mbim->not_port.notify_state = NCM_NOTIFY_CONNECT; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(mbim->ctrl_id); + + mbim->not_port.notify_req = NULL; + /* + * In double buffering if there is a space in FIFO, + * completion callback can be called right after the call, + * so unlocking + */ + spin_unlock(&mbim->lock); + status = usb_ep_queue(mbim->not_port.notify, req, GFP_ATOMIC); + spin_lock(&mbim->lock); + if (status < 0) { + mbim->not_port.notify_req = req; + atomic_dec(&mbim->not_port.notify_count); + pr_err("usb_ep_queue failed, err: %d", status); + } +} + +/* + * Context: mbim->lock held + */ +static void mbim_notify(struct f_mbim *mbim) +{ + /* + * If mbim_notify() is called before the second (CONNECT) + * notification is sent, then it will reset to send the SPEED + * notificaion again (and again, and again), but it's not a problem + */ + pr_info("dev:%p\n", mbim); + + mbim->not_port.notify_state = NCM_NOTIFY_SPEED; + mbim_do_notify(mbim); +} + +static void mbim_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_mbim *mbim = req->context; + struct usb_cdc_notification *event = req->buf; + + int notif_c = 0; + + pr_info("dev:%p\n", mbim); + + spin_lock(&mbim->lock); + switch (req->status) { + case 0: + pr_info("Notification %02x sent\n", + event->bNotificationType); + + notif_c = atomic_dec_return(&mbim->not_port.notify_count); + + if (notif_c != 0) { + pr_info("Continue to mbim_do_notify()"); + break; + } else { + pr_info("notify_count decreased to 0. Do not notify"); + spin_unlock(&mbim->lock); + return; + } + + break; + + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + mbim->not_port.notify_state = NCM_NOTIFY_NONE; + atomic_set(&mbim->not_port.notify_count, 0); + pr_info("ESHUTDOWN/ECONNRESET, connection gone"); + spin_unlock(&mbim->lock); + mbim_clear_queues(mbim); + mbim_reset_function_queue(mbim); + break; + default: + pr_err("Unknown event %02x --> %d\n", + event->bNotificationType, req->status); + break; + } + + mbim->not_port.notify_req = req; + mbim_do_notify(mbim); + + spin_unlock(&mbim->lock); + + pr_info("dev:%p Exit\n", mbim); +} + +static void mbim_ep0out_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* now for SET_NTB_INPUT_SIZE only */ + unsigned in_size = 0; + struct usb_function *f = req->context; + struct f_mbim *mbim = func_to_mbim(f); + struct mbim_ntb_input_size *ntb = NULL; + + pr_info("dev:%p\n", mbim); + + req->context = NULL; + if (req->status || req->actual != req->length) { + pr_err("Bad control-OUT transfer\n"); + goto invalid; + } + + if (req->length == 4) { + in_size = get_unaligned_le32(req->buf); + if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || + in_size > le32_to_cpu(ntb_parameters.dwNtbInMaxSize)) { + pr_err("Illegal INPUT SIZE (%d) from host\n", in_size); + goto invalid; + } + } else if (req->length == 8) { + ntb = (struct mbim_ntb_input_size *)req->buf; + in_size = get_unaligned_le32(&(ntb->ntb_input_size)); + if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || + in_size > le32_to_cpu(ntb_parameters.dwNtbInMaxSize)) { + pr_err("Illegal INPUT SIZE (%d) from host\n", in_size); + goto invalid; + } + mbim->ntb_max_datagrams = + get_unaligned_le16(&(ntb->ntb_max_datagrams)); + } else { + pr_err("Illegal NTB length %d\n", in_size); + goto invalid; + } + + pr_info("Set NTB INPUT SIZE %d\n", in_size); + + mbim->ntb_input_size = in_size; + return; + +invalid: + usb_ep_set_halt(ep); + + pr_err("dev:%p Failed\n", mbim); + + return; +} + +static void +fmbim_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_mbim *dev = req->context; + struct ctrl_pkt *cpkt = NULL; + int len = req->actual; + + if (!dev) { + pr_err("mbim dev is null\n"); + return; + } + + if (req->status < 0) { + pr_err("mbim command error %d\n", req->status); + return; + } + + pr_info("dev:%p port#%d\n", dev, dev->port_num); + + spin_lock(&dev->lock); + if (!dev->is_open) { + pr_err("mbim file handler %p is not open", dev); + spin_unlock(&dev->lock); + return; + } + + cpkt = mbim_alloc_ctrl_pkt(len, GFP_ATOMIC); + if (!cpkt) { + pr_err("Unable to allocate ctrl pkt\n"); + spin_unlock(&dev->lock); + return; + } + + pr_info("Add to cpkt_req_q packet with len = %d\n", len); + memcpy(cpkt->buf, req->buf, len); + list_add_tail(&cpkt->list, &dev->cpkt_req_q); + spin_unlock(&dev->lock); + + /* wakeup read thread */ + pr_info("Wake up read queue"); + wake_up(&dev->read_wq); + + return; +} + +static int +mbim_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_mbim *mbim = func_to_mbim(f); + struct usb_composite_dev *cdev = mbim->cdev; + struct usb_request *req = cdev->req; + struct ctrl_pkt *cpkt = NULL; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* + * composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + + if (!atomic_read(&mbim->online)) { + pr_info("usb cable is not connected\n"); + return -ENOTCONN; + } + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_RESET_FUNCTION: + + pr_info("USB_CDC_RESET_FUNCTION"); + value = 0; + req->complete = fmbim_reset_cmd_complete; + req->context = mbim; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + + pr_info("USB_CDC_SEND_ENCAPSULATED_COMMAND"); + + if (w_length > req->length) { + pr_err("w_length > req->length: %d > %d", + w_length, req->length); + } + value = w_length; + req->complete = fmbim_cmd_complete; + req->context = mbim; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + + pr_info("USB_CDC_GET_ENCAPSULATED_RESPONSE"); + + if (w_value) { + pr_err("w_length > 0: %d", w_length); + break; + } + + pr_info("req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + spin_lock(&mbim->lock); + if (list_empty(&mbim->cpkt_resp_q)) { + pr_err("ctrl resp queue empty\n"); + spin_unlock(&mbim->lock); + break; + } + + cpkt = list_first_entry(&mbim->cpkt_resp_q, + struct ctrl_pkt, list); + list_del(&cpkt->list); + spin_unlock(&mbim->lock); + + value = min_t(unsigned, w_length, cpkt->len); + memcpy(req->buf, cpkt->buf, value); + mbim_free_ctrl_pkt(cpkt); + + pr_info("copied encapsulated_response %d bytes", + value); + + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_PARAMETERS: + + pr_info("USB_CDC_GET_NTB_PARAMETERS"); + + if (w_length == 0 || w_value != 0 || w_index != mbim->ctrl_id) + break; + + value = w_length > sizeof ntb_parameters ? + sizeof ntb_parameters : w_length; + memcpy(req->buf, &ntb_parameters, value); + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_INPUT_SIZE: + + pr_info("USB_CDC_GET_NTB_INPUT_SIZE"); + + if (w_length < 4 || w_value != 0 || w_index != mbim->ctrl_id) + break; + + put_unaligned_le32(mbim->ntb_input_size, req->buf); + value = 4; + pr_info("Reply to host INPUT SIZE %d\n", + mbim->ntb_input_size); + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_INPUT_SIZE: + + pr_info("USB_CDC_SET_NTB_INPUT_SIZE"); + + if (w_length != 4 && w_length != 8) { + pr_err("wrong NTB length %d", w_length); + break; + } + + if (w_value != 0 || w_index != mbim->ctrl_id) + break; + + req->complete = mbim_ep0out_complete; + req->length = w_length; + req->context = f; + + value = req->length; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_FORMAT: + { + uint16_t format; + + pr_info("USB_CDC_GET_NTB_FORMAT"); + + if (w_length < 2 || w_value != 0 || w_index != mbim->ctrl_id) + break; + + format = (mbim->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001; + put_unaligned_le16(format, req->buf); + value = 2; + pr_info("NTB FORMAT: sending %d\n", format); + break; + } + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_FORMAT: + { + pr_info("USB_CDC_SET_NTB_FORMAT"); + + if (w_length != 0 || w_index != mbim->ctrl_id) + break; + switch (w_value) { + case 0x0000: + mbim->parser_opts = &ndp16_opts; + pr_info("NCM16 selected\n"); + break; + case 0x0001: + mbim->parser_opts = &ndp32_opts; + pr_info("NCM32 selected\n"); + break; + default: + break; + } + value = 0; + break; + } + + /* optional in mbim descriptor: */ + /* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */ + /* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */ + + default: + pr_err("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) { + pr_info("control request: %02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (value < w_length); + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + pr_err("queueing req failed: %02x.%02x, err %d\n", + ctrl->bRequestType, + ctrl->bRequest, value); + } + } else { + pr_err("ctrl req err %d: %02x.%02x v%04x i%04x l%d\n", + value, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int mbim_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_mbim *mbim = func_to_mbim(f); + struct usb_composite_dev *cdev = mbim->cdev; + int ret = 0; + + /* Control interface has only altsetting 0 */ + if (intf == mbim->ctrl_id) { + + pr_info("CONTROL_INTERFACE"); + + if (alt != 0) + goto fail; + + if (mbim->not_port.notify->driver_data) { + pr_info("reset mbim control %d\n", intf); + usb_ep_disable(mbim->not_port.notify); + } + + ret = config_ep_by_speed(cdev->gadget, f, + mbim->not_port.notify); + if (ret) { + mbim->not_port.notify->desc = NULL; + pr_err("Failed configuring notify ep %s: err %d\n", + mbim->not_port.notify->name, ret); + return ret; + } + + ret = usb_ep_enable(mbim->not_port.notify); + if (ret) { + pr_err("usb ep#%s enable failed, err#%d\n", + mbim->not_port.notify->name, ret); + return ret; + } + mbim->not_port.notify->driver_data = mbim; + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == mbim->data_id) { + + pr_info("DATA_INTERFACE"); + + if (alt > 1) + goto fail; + + if (mbim->bam_port.in->driver_data) { + pr_info("reset mbim\n"); + mbim_reset_values(mbim); + mbim_bam_disconnect(mbim); + } + + /* + * CDC Network only sends data in non-default altsettings. + * Changing altsettings resets filters, statistics, etc. + */ + if (alt == 1) { + pr_info("Alt set 1, initialize ports"); + + if (!mbim->bam_port.in->desc) { + + pr_info("Choose endpoints"); + + ret = config_ep_by_speed(cdev->gadget, f, + mbim->bam_port.in); + if (ret) { + mbim->bam_port.in->desc = NULL; + pr_err("IN ep %s failed: %d\n", + mbim->bam_port.in->name, ret); + return ret; + } + + pr_info("Set mbim port in_desc = 0x%p", + mbim->bam_port.in->desc); + + ret = config_ep_by_speed(cdev->gadget, f, + mbim->bam_port.out); + if (ret) { + mbim->bam_port.out->desc = NULL; + pr_err("OUT ep %s failed: %d\n", + mbim->bam_port.out->name, ret); + return ret; + } + + pr_info("Set mbim port out_desc = 0x%p", + mbim->bam_port.out->desc); + } else { + pr_info("PORTS already SET"); + } + + pr_info("Activate mbim\n"); + mbim_bam_connect(mbim); + } + + spin_lock(&mbim->lock); + mbim_notify(mbim); + spin_unlock(&mbim->lock); + } else { + goto fail; + } + + atomic_set(&mbim->online, 1); + + pr_info("SET DEVICE ONLINE"); + + /* wakeup file threads */ + wake_up(&mbim->read_wq); + wake_up(&mbim->write_wq); + + return 0; + +fail: + pr_err("ERROR: Illegal Interface"); + return -EINVAL; +} + +/* + * Because the data interface supports multiple altsettings, + * this MBIM function *MUST* implement a get_alt() method. + */ +static int mbim_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_mbim *mbim = func_to_mbim(f); + + if (intf == mbim->ctrl_id) + return 0; + return mbim->bam_port.in->driver_data ? 1 : 0; +} + +static void mbim_disable(struct usb_function *f) +{ + struct f_mbim *mbim = func_to_mbim(f); + + pr_info("SET DEVICE OFFLINE"); + atomic_set(&mbim->online, 0); + + mbim_clear_queues(mbim); + mbim_reset_function_queue(mbim); + + mbim_bam_disconnect(mbim); + + if (mbim->not_port.notify->driver_data) { + usb_ep_disable(mbim->not_port.notify); + mbim->not_port.notify->driver_data = NULL; + } + + pr_info("mbim deactivated\n"); +} + +/*---------------------- function driver setup/binding ---------------------*/ + +static int +mbim_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_mbim *mbim = func_to_mbim(f); + int status; + struct usb_ep *ep; + + pr_info("Enter"); + + mbim->cdev = cdev; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + mbim->ctrl_id = status; + mbim_iad_desc.bFirstInterface = status; + + mbim_control_intf.bInterfaceNumber = status; + mbim_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + mbim->data_id = status; + + mbim_data_nop_intf.bInterfaceNumber = status; + mbim_data_intf.bInterfaceNumber = status; + mbim_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_in_desc); + if (!ep) { + pr_err("usb epin autoconfig failed\n"); + goto fail; + } + pr_info("usb epin autoconfig succeeded\n"); + ep->driver_data = cdev; /* claim */ + mbim->bam_port.in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_out_desc); + if (!ep) { + pr_err("usb epout autoconfig failed\n"); + goto fail; + } + pr_info("usb epout autoconfig succeeded\n"); + ep->driver_data = cdev; /* claim */ + mbim->bam_port.out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_notify_desc); + if (!ep) { + pr_err("usb notify ep autoconfig failed\n"); + goto fail; + } + pr_info("usb notify ep autoconfig succeeded\n"); + mbim->not_port.notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + mbim->not_port.notify_req = mbim_alloc_req(ep, NCM_STATUS_BYTECOUNT); + if (!mbim->not_port.notify_req) { + pr_info("failed to allocate notify request\n"); + goto fail; + } + pr_info("allocated notify ep request & request buffer\n"); + + mbim->not_port.notify_req->context = mbim; + mbim->not_port.notify_req->complete = mbim_notify_complete; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(mbim_fs_function); + if (!f->descriptors) + goto fail; + + /* + * 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)) { + hs_mbim_in_desc.bEndpointAddress = + fs_mbim_in_desc.bEndpointAddress; + hs_mbim_out_desc.bEndpointAddress = + fs_mbim_out_desc.bEndpointAddress; + hs_mbim_notify_desc.bEndpointAddress = + fs_mbim_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(mbim_hs_function); + if (!f->hs_descriptors) + goto fail; + } + + pr_info("mbim(%d): %s speed IN/%s OUT/%s NOTIFY/%s\n", + mbim->port_num, + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + mbim->bam_port.in->name, mbim->bam_port.out->name, + mbim->not_port.notify->name); + + return 0; + +fail: + pr_err("%s failed to bind, err %d\n", f->name, status); + + if (f->descriptors) + usb_free_descriptors(f->descriptors); + + if (mbim->not_port.notify_req) { + kfree(mbim->not_port.notify_req->buf); + usb_ep_free_request(mbim->not_port.notify, + mbim->not_port.notify_req); + } + + /* we might as well release our claims on endpoints */ + if (mbim->not_port.notify) + mbim->not_port.notify->driver_data = NULL; + if (mbim->bam_port.out) + mbim->bam_port.out->driver_data = NULL; + if (mbim->bam_port.in) + mbim->bam_port.in->driver_data = NULL; + + return status; +} + +static void mbim_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_mbim *mbim = func_to_mbim(f); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + kfree(mbim->not_port.notify_req->buf); + usb_ep_free_request(mbim->not_port.notify, mbim->not_port.notify_req); +} + +/** + * mbim_bind_config - add MBIM link to a configuration + * @c: the configuration to support the network link + * Context: single threaded during gadget setup + * Returns zero on success, else negative errno. + */ +int mbim_bind_config(struct usb_configuration *c, unsigned portno) +{ + struct f_mbim *mbim = NULL; + int status = 0; + + pr_info("port number %u", portno); + + if (portno >= nr_mbim_ports) { + pr_err("Can not add port %u. Max ports = %d", + portno, nr_mbim_ports); + return -ENODEV; + } + + status = mbim_bam_setup(nr_mbim_ports); + if (status) { + pr_err("bam setup failed"); + return status; + } + + /* maybe allocate device-global string IDs */ + if (mbim_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + mbim_string_defs[STRING_CTRL_IDX].id = status; + mbim_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + mbim_string_defs[STRING_DATA_IDX].id = status; + mbim_data_nop_intf.iInterface = status; + mbim_data_intf.iInterface = status; + } + + /* allocate and initialize one new instance */ + mbim = mbim_ports[0].port; + if (!mbim) { + pr_info("mbim struct not allocated"); + return -ENOMEM; + } + + mbim->cdev = c->cdev; + + spin_lock_init(&mbim->lock); + + mbim_reset_values(mbim); + + mbim->function.name = "usb_mbim"; + mbim->function.strings = mbim_strings; + mbim->function.bind = mbim_bind; + mbim->function.unbind = mbim_unbind; + mbim->function.set_alt = mbim_set_alt; + mbim->function.get_alt = mbim_get_alt; + mbim->function.setup = mbim_setup; + mbim->function.disable = mbim_disable; + + INIT_LIST_HEAD(&mbim->cpkt_req_q); + INIT_LIST_HEAD(&mbim->cpkt_resp_q); + + status = usb_add_function(c, &mbim->function); + + pr_info("Exit status %d", status); + + return status; +} + +/* ------------ MBIM DRIVER File Operations API for USER SPACE ------------ */ + +static ssize_t +mbim_read(struct file *fp, char __user *buf, size_t count, loff_t *pos) +{ + struct f_mbim *dev = fp->private_data; + struct ctrl_pkt *cpkt = NULL; + int ret = 0; + + pr_debug("Enter(%d)\n", count); + + if (!dev) { + pr_err("Received NULL mbim pointer\n"); + return -ENODEV; + } + + if (count > MBIM_BULK_BUFFER_SIZE) { + pr_err("Buffer size is too big %d, should be at most %d\n", + count, MBIM_BULK_BUFFER_SIZE); + return -EINVAL; + } + + if (mbim_lock(&dev->read_excl)) { + pr_err("Previous reading is not finished yet\n"); + return -EBUSY; + } + + /* block until mbim online */ + while (!(atomic_read(&dev->online) || atomic_read(&dev->error))) { + pr_err("USB cable not connected. Wait.\n"); + ret = wait_event_interruptible(dev->read_wq, + (atomic_read(&dev->online) || + atomic_read(&dev->error))); + if (ret < 0) { + mbim_unlock(&dev->read_excl); + return 0; + } + } + + if (atomic_read(&dev->error)) { + mbim_unlock(&dev->read_excl); + return -EIO; + } + + while (list_empty(&dev->cpkt_req_q)) { + pr_err("Requests list is empty. Wait.\n"); + ret = wait_event_interruptible(dev->read_wq, + !list_empty(&dev->cpkt_req_q)); + if (ret < 0) { + pr_err("Waiting failed\n"); + mbim_unlock(&dev->read_excl); + return 0; + } + pr_debug("Received request packet\n"); + } + + cpkt = list_first_entry(&dev->cpkt_req_q, struct ctrl_pkt, + list); + if (cpkt->len > count) { + mbim_unlock(&dev->read_excl); + pr_err("cpkt size too big:%d > buf size:%d\n", + cpkt->len, count); + return -ENOMEM; + } + + pr_debug("cpkt size:%d\n", cpkt->len); + + list_del(&cpkt->list); + mbim_unlock(&dev->read_excl); + + ret = copy_to_user(buf, cpkt->buf, cpkt->len); + if (ret) { + pr_err("copy_to_user failed: err %d\n", ret); + ret = 0; + } else { + pr_debug("copied %d bytes to user\n", cpkt->len); + ret = cpkt->len; + } + + mbim_free_ctrl_pkt(cpkt); + + return ret; +} + +static ssize_t +mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos) +{ + struct f_mbim *dev = fp->private_data; + struct ctrl_pkt *cpkt = NULL; + int ret = 0; + + pr_debug("Enter(%d)", count); + + if (!dev) { + pr_err("Received NULL mbim pointer\n"); + return -ENODEV; + } + + if (!count) { + pr_err("zero length ctrl pkt\n"); + return -ENODEV; + } + + if (count > MAX_CTRL_PKT_SIZE) { + pr_err("given pkt size too big:%d > max_pkt_size:%d\n", + count, MAX_CTRL_PKT_SIZE); + return -ENOMEM; + } + + if (mbim_lock(&dev->write_excl)) { + pr_err("Previous writing not finished yet\n"); + return -EBUSY; + } + + if (!atomic_read(&dev->online)) { + pr_err("USB cable not connected\n"); + mbim_unlock(&dev->write_excl); + return -EPIPE; + } + + cpkt = mbim_alloc_ctrl_pkt(count, GFP_KERNEL); + if (!cpkt) { + pr_err("failed to allocate ctrl pkt\n"); + mbim_unlock(&dev->write_excl); + return -ENOMEM; + } + + ret = copy_from_user(cpkt->buf, buf, count); + if (ret) { + pr_err("copy_from_user failed err:%d\n", ret); + mbim_free_ctrl_pkt(cpkt); + mbim_unlock(&dev->write_excl); + return 0; + } + + fmbim_send_cpkt_response(dev, cpkt); + + mbim_unlock(&dev->write_excl); + + pr_debug("Exit(%d)", count); + + return count; +} + +static int mbim_open(struct inode *ip, struct file *fp) +{ + pr_info("Open mbim driver\n"); + + while (!_mbim_dev) { + pr_err("mbim_dev not created yet\n"); + return -ENODEV; + } + + if (mbim_lock(&_mbim_dev->open_excl)) { + pr_err("Already opened\n"); + return -EBUSY; + } + + pr_info("Lock mbim_dev->open_excl for open\n"); + + if (!atomic_read(&_mbim_dev->online)) + pr_err("USB cable not connected\n"); + + fp->private_data = _mbim_dev; + + atomic_set(&_mbim_dev->error, 0); + + spin_lock(&_mbim_dev->lock); + _mbim_dev->is_open = true; + mbim_notify(_mbim_dev); + spin_unlock(&_mbim_dev->lock); + + pr_info("Exit, mbim file opened\n"); + + return 0; +} + +static int mbim_release(struct inode *ip, struct file *fp) +{ + struct f_mbim *mbim = fp->private_data; + + pr_info("Close mbim file"); + + spin_lock(&mbim->lock); + mbim->is_open = false; + mbim_notify(mbim); + spin_unlock(&mbim->lock); + + mbim_unlock(&_mbim_dev->open_excl); + + return 0; +} + +static long mbim_ioctl(struct file *fp, unsigned cmd, unsigned long arg) +{ + struct f_mbim *mbim = fp->private_data; + int ret = 0; + + pr_info("Received command %d", cmd); + + if (mbim_lock(&mbim->ioctl_excl)) + return -EBUSY; + + switch (cmd) { + case MBIM_GET_NTB_SIZE: + ret = copy_to_user((void __user *)arg, + &mbim->ntb_input_size, sizeof(mbim->ntb_input_size)); + if (ret) { + pr_err("copying to user space failed"); + ret = -EFAULT; + } + pr_info("Sent NTB size %d", mbim->ntb_input_size); + break; + case MBIM_GET_DATAGRAM_COUNT: + ret = copy_to_user((void __user *)arg, + &mbim->ntb_max_datagrams, + sizeof(mbim->ntb_max_datagrams)); + if (ret) { + pr_err("copying to user space failed"); + ret = -EFAULT; + } + pr_info("Sent NTB datagrams count %d", + mbim->ntb_max_datagrams); + break; + default: + pr_err("wrong parameter"); + ret = -EINVAL; + } + + mbim_unlock(&mbim->ioctl_excl); + + return ret; +} + +/* file operations for MBIM device /dev/android_mbim */ +static const struct file_operations mbim_fops = { + .owner = THIS_MODULE, + .open = mbim_open, + .release = mbim_release, + .read = mbim_read, + .write = mbim_write, + .unlocked_ioctl = mbim_ioctl, +}; + +static struct miscdevice mbim_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_mbim", + .fops = &mbim_fops, +}; + +static int mbim_init(int instances) +{ + int i; + struct f_mbim *dev = NULL; + int ret; + + pr_info("initialize %d instances\n", instances); + + if (instances > NR_MBIM_PORTS) { + pr_err("Max-%d instances supported\n", NR_MBIM_PORTS); + return -EINVAL; + } + + for (i = 0; i < instances; i++) { + dev = kzalloc(sizeof(struct f_mbim), GFP_KERNEL); + if (!dev) { + pr_err("Failed to allocate mbim dev\n"); + ret = -ENOMEM; + goto fail_probe; + } + + dev->port_num = i; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->cpkt_req_q); + INIT_LIST_HEAD(&dev->cpkt_resp_q); + + mbim_ports[i].port = dev; + mbim_ports[i].port_num = i; + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + nr_mbim_ports++; + + } + + _mbim_dev = dev; + ret = misc_register(&mbim_device); + if (ret) { + pr_err("mbim driver failed to register"); + goto fail_probe; + } + + pr_info("Initialized %d ports\n", nr_mbim_ports); + + return ret; + +fail_probe: + pr_err("Failed"); + for (i = 0; i < nr_mbim_ports; i++) { + kfree(mbim_ports[i].port); + mbim_ports[i].port = NULL; + } + + return ret; +} + +static void fmbim_cleanup(void) +{ + int i = 0; + + pr_info("Enter"); + + for (i = 0; i < nr_mbim_ports; i++) { + kfree(mbim_ports[i].port); + mbim_ports[i].port = NULL; + } + nr_mbim_ports = 0; + + misc_deregister(&mbim_device); + + _mbim_dev = NULL; +} + diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c index 1638977a54105cea6783509d4812b03d0e9f90d5..0394b0b1a923565d09a8e127c7b457d3c55686d7 100644 --- a/drivers/usb/gadget/f_mtp.c +++ b/drivers/usb/gadget/f_mtp.c @@ -410,15 +410,6 @@ static int mtp_create_bulk_endpoints(struct mtp_dev *dev, ep->driver_data = dev; /* claim the endpoint */ dev->ep_out = ep; - ep = usb_ep_autoconfig(cdev->gadget, out_desc); - if (!ep) { - DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); - return -ENODEV; - } - DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); - ep->driver_data = dev; /* claim the endpoint */ - dev->ep_out = ep; - ep = usb_ep_autoconfig(cdev->gadget, intr_desc); if (!ep) { DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); @@ -504,7 +495,17 @@ static ssize_t mtp_read(struct file *fp, char __user *buf, } /* wait for a request to complete */ - ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, req); + spin_lock_irq(&dev->lock); + dev->state = STATE_CANCELED; + spin_unlock_irq(&dev->lock); + goto done; + } if (ret < 0) { r = ret; usb_ep_dequeue(dev->ep_out, req); @@ -709,7 +710,8 @@ static void send_file_work(struct work_struct *data) ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); if (ret < 0) { DBG(cdev, "send_file_work: xfer error %d\n", ret); - dev->state = STATE_ERROR; + if (dev->state != STATE_OFFLINE) + dev->state = STATE_ERROR; r = -EIO; break; } @@ -762,7 +764,8 @@ static void receive_file_work(struct work_struct *data) ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); if (ret < 0) { r = -EIO; - dev->state = STATE_ERROR; + if (dev->state != STATE_OFFLINE) + dev->state = STATE_ERROR; break; } } @@ -774,7 +777,8 @@ static void receive_file_work(struct work_struct *data) DBG(cdev, "vfs_write %d\n", ret); if (ret != write_req->actual) { r = -EIO; - dev->state = STATE_ERROR; + if (dev->state != STATE_OFFLINE) + dev->state = STATE_ERROR; break; } write_req = NULL; @@ -1141,27 +1145,35 @@ static int mtp_function_set_alt(struct usb_function *f, DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); - if (ret) + if (ret) { + dev->ep_in->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->ep_in->name, ret); return ret; - + } ret = usb_ep_enable(dev->ep_in); - if (ret) + if (ret) { + ERROR(cdev, "failed to enable ep %s, result %d\n", + dev->ep_in->name, ret); return ret; + } ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); - if (ret) + if (ret) { + dev->ep_out->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->ep_out->name, ret); + usb_ep_disable(dev->ep_in); return ret; - + } ret = usb_ep_enable(dev->ep_out); if (ret) { + ERROR(cdev, "failed to enable ep %s, result %d\n", + dev->ep_out->name, ret); usb_ep_disable(dev->ep_in); return ret; } - - ret = config_ep_by_speed(cdev->gadget, f, dev->ep_intr); - if (ret) - return ret; - + dev->ep_intr->desc = &mtp_intr_desc; ret = usb_ep_enable(dev->ep_intr); if (ret) { usb_ep_disable(dev->ep_out); diff --git a/drivers/usb/gadget/f_rmnet.c b/drivers/usb/gadget/f_rmnet.c new file mode 100644 index 0000000000000000000000000000000000000000..414a7b9790fd2fa0d83fd8aafa0ac0c7ebbb6da6 --- /dev/null +++ b/drivers/usb/gadget/f_rmnet.c @@ -0,0 +1,1162 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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) + + +#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; + atomic_t ctrl_online; + struct usb_composite_dev *cdev; + + spinlock_t lock; + + /* usb eps*/ + struct usb_ep *notify; + struct usb_request *notify_req; + + /* control info */ + struct list_head cpkt_resp_q; + atomic_t notify_count; + unsigned long cpkts_len; +}; + +#define NR_RMNET_PORTS 3 +static unsigned int nr_rmnet_ports; +static unsigned int no_ctrl_smd_ports; +static unsigned int no_ctrl_hsic_ports; +static unsigned int no_ctrl_hsuart_ports; +static unsigned int no_data_bam_ports; +static unsigned int no_data_bam2bam_ports; +static unsigned int no_data_hsic_ports; +static unsigned int no_data_hsuart_ports; +static struct rmnet_ports { + enum transport_type data_xport; + enum transport_type ctrl_xport; + unsigned data_xport_num; + unsigned ctrl_xport_num; + unsigned port_num; + struct f_rmnet *port; +} rmnet_ports[NR_RMNET_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, +}; + +static void frmnet_ctrl_response_available(struct f_rmnet *dev); + +/* ------- 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 rmnet_gport_setup(void) +{ + int ret; + int port_idx; + int i; + + pr_debug("%s: bam ports: %u bam2bam ports: %u data hsic ports: %u data hsuart ports: %u" + " smd ports: %u ctrl hsic ports: %u ctrl hsuart ports: %u" + " nr_rmnet_ports: %u\n", + __func__, no_data_bam_ports, no_data_bam2bam_ports, + no_data_hsic_ports, no_data_hsuart_ports, no_ctrl_smd_ports, + no_ctrl_hsic_ports, no_ctrl_hsuart_ports, nr_rmnet_ports); + + if (no_data_bam_ports || no_data_bam2bam_ports) { + ret = gbam_setup(no_data_bam_ports, + no_data_bam2bam_ports); + if (ret) + return ret; + } + + if (no_ctrl_smd_ports) { + ret = gsmd_ctrl_setup(no_ctrl_smd_ports); + if (ret) + return ret; + } + + if (no_data_hsic_ports) { + port_idx = ghsic_data_setup(no_data_hsic_ports, + USB_GADGET_RMNET); + if (port_idx < 0) + return port_idx; + for (i = 0; i < nr_rmnet_ports; i++) { + if (rmnet_ports[i].data_xport == + USB_GADGET_XPORT_HSIC) { + rmnet_ports[i].data_xport_num = port_idx; + port_idx++; + } + } + } + + if (no_ctrl_hsic_ports) { + port_idx = ghsic_ctrl_setup(no_ctrl_hsic_ports, + USB_GADGET_RMNET); + if (port_idx < 0) + return port_idx; + for (i = 0; i < nr_rmnet_ports; i++) { + if (rmnet_ports[i].ctrl_xport == + USB_GADGET_XPORT_HSIC) { + rmnet_ports[i].ctrl_xport_num = port_idx; + port_idx++; + } + } + } + + if (no_data_hsuart_ports) { + port_idx = ghsuart_data_setup(no_data_hsuart_ports, + USB_GADGET_RMNET); + if (port_idx < 0) + return port_idx; + for (i = 0; i < nr_rmnet_ports; i++) { + if (rmnet_ports[i].data_xport == + USB_GADGET_XPORT_HSUART) { + rmnet_ports[i].data_xport_num = port_idx; + port_idx++; + } + } + } + + if (no_ctrl_hsuart_ports) { + port_idx = ghsuart_ctrl_setup(no_ctrl_hsuart_ports, + USB_GADGET_RMNET); + if (port_idx < 0) + return port_idx; + for (i = 0; i < nr_rmnet_ports; i++) { + if (rmnet_ports[i].ctrl_xport == + USB_GADGET_XPORT_HSUART) { + rmnet_ports[i].ctrl_xport_num = port_idx; + port_idx++; + } + } + } + + return 0; +} + +static int gport_rmnet_connect(struct f_rmnet *dev) +{ + int ret; + unsigned port_num; + enum transport_type cxport = rmnet_ports[dev->port_num].ctrl_xport; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + + pr_debug("%s: ctrl xport: %s data xport: %s dev: %p portno: %d\n", + __func__, xport_to_str(cxport), xport_to_str(dxport), + dev, dev->port_num); + + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + switch (cxport) { + case USB_GADGET_XPORT_SMD: + ret = gsmd_ctrl_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: gsmd_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + case USB_GADGET_XPORT_HSIC: + ret = ghsic_ctrl_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: ghsic_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + case USB_GADGET_XPORT_HSUART: + ret = ghsuart_ctrl_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: ghsuart_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(cxport)); + return -ENODEV; + } + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM: + case USB_GADGET_XPORT_BAM2BAM: + ret = gbam_connect(&dev->port, port_num, + dxport, port_num); + if (ret) { + pr_err("%s: gbam_connect failed: err:%d\n", + __func__, ret); + gsmd_ctrl_disconnect(&dev->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_HSIC: + ret = ghsic_data_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: ghsic_data_connect failed: err:%d\n", + __func__, ret); + ghsic_ctrl_disconnect(&dev->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_HSUART: + ret = ghsuart_data_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: ghsuart_data_connect failed: err:%d\n", + __func__, ret); + ghsuart_ctrl_disconnect(&dev->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + return -ENODEV; + } + + return 0; +} + +static int gport_rmnet_disconnect(struct f_rmnet *dev) +{ + unsigned port_num; + enum transport_type cxport = rmnet_ports[dev->port_num].ctrl_xport; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + + pr_debug("%s: ctrl xport: %s data xport: %s dev: %p portno: %d\n", + __func__, xport_to_str(cxport), xport_to_str(dxport), + dev, dev->port_num); + + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + switch (cxport) { + case USB_GADGET_XPORT_SMD: + gsmd_ctrl_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_HSIC: + ghsic_ctrl_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_HSUART: + ghsuart_ctrl_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(cxport)); + return -ENODEV; + } + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM: + case USB_GADGET_XPORT_BAM2BAM: + gbam_disconnect(&dev->port, port_num, dxport); + break; + case USB_GADGET_XPORT_HSIC: + ghsic_data_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_HSUART: + ghsuart_data_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + return -ENODEV; + } + + 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(f->name); +} + +static void frmnet_suspend(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + unsigned port_num; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + + pr_debug("%s: data xport: %s dev: %p portno: %d\n", + __func__, xport_to_str(dxport), + dev, dev->port_num); + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM: + break; + case USB_GADGET_XPORT_BAM2BAM: + gbam_suspend(&dev->port, port_num, dxport); + break; + case USB_GADGET_XPORT_HSIC: + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + } +} + +static void frmnet_resume(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + unsigned port_num; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + + pr_debug("%s: data xport: %s dev: %p portno: %d\n", + __func__, xport_to_str(dxport), + dev, dev->port_num); + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM: + break; + case USB_GADGET_XPORT_BAM2BAM: + gbam_resume(&dev->port, port_num, dxport); + break; + case USB_GADGET_XPORT_HSIC: + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + } +} + +static void frmnet_disable(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + unsigned long flags; + struct rmnet_ctrl_pkt *cpkt; + + pr_debug("%s: port#%d\n", __func__, dev->port_num); + + usb_ep_disable(dev->notify); + dev->notify->driver_data = NULL; + + atomic_set(&dev->online, 0); + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(&dev->cpkt_resp_q)) { + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + } + atomic_set(&dev->notify_count, 0); + spin_unlock_irqrestore(&dev->lock, flags); + + gport_rmnet_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; + struct list_head *cpkt; + + 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); + } + + ret = config_ep_by_speed(cdev->gadget, f, dev->notify); + if (ret) { + dev->notify->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->notify->name, ret); + return ret; + } + ret = usb_ep_enable(dev->notify); + + 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->desc || !dev->port.out->desc) { + if (config_ep_by_speed(cdev->gadget, f, dev->port.in) || + config_ep_by_speed(cdev->gadget, f, dev->port.out)) { + dev->port.in->desc = NULL; + dev->port.out->desc = NULL; + return -EINVAL; + } + ret = gport_rmnet_connect(dev); + } + + atomic_set(&dev->online, 1); + + /* In case notifications were aborted, but there are pending control + packets in the response queue, re-add the notifications */ + list_for_each(cpkt, &dev->cpkt_resp_q) + frmnet_ctrl_response_available(dev); + + 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 void frmnet_connect(struct grmnet *gr) +{ + struct f_rmnet *dev; + + if (!gr) { + pr_err("%s: Invalid grmnet:%p\n", __func__, gr); + return; + } + + dev = port_to_rmnet(gr); + + atomic_set(&dev->ctrl_online, 1); +} + +static void frmnet_disconnect(struct grmnet *gr) +{ + struct f_rmnet *dev; + unsigned long flags; + struct usb_cdc_notification *event; + int status; + struct rmnet_ctrl_pkt *cpkt; + + if (!gr) { + pr_err("%s: Invalid grmnet:%p\n", __func__, gr); + return; + } + + dev = port_to_rmnet(gr); + + atomic_set(&dev->ctrl_online, 0); + + if (!atomic_read(&dev->online)) { + pr_debug("%s: nothing to do\n", __func__); + return; + } + + usb_ep_fifo_flush(dev->notify); + + event = dev->notify_req->buf; + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + 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->notify, dev->notify_req, GFP_ATOMIC); + if (status < 0) { + if (!atomic_read(&dev->online)) + return; + pr_err("%s: rmnet notify ep enqueue error %d\n", + __func__, status); + } + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(&dev->cpkt_resp_q)) { + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + } + atomic_set(&dev->notify_count, 0); + spin_unlock_irqrestore(&dev->lock, flags); + +} + +static int +frmnet_send_cpkt_response(void *gr, void *buf, size_t len) +{ + struct f_rmnet *dev; + struct rmnet_ctrl_pkt *cpkt; + unsigned long flags; + + if (!gr || !buf) { + pr_err("%s: Invalid grmnet/buf, grmnet:%p buf:%p\n", + __func__, gr, buf); + return -ENODEV; + } + cpkt = rmnet_alloc_ctrl_pkt(len, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + pr_err("%s: Unable to allocate ctrl pkt\n", __func__); + return -ENOMEM; + } + memcpy(cpkt->buf, buf, len); + cpkt->len = len; + + dev = port_to_rmnet(gr); + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (!atomic_read(&dev->online) || !atomic_read(&dev->ctrl_online)) { + rmnet_free_ctrl_pkt(cpkt); + return 0; + } + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&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; + unsigned port_num; + + 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; + + if (dev->port.send_encap_cmd) { + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + dev->port.send_encap_cmd(port_num, req->buf, req->actual); + } +} + +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_read(&dev->ctrl_online)) + break; + + 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; + unsigned port_num; + 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: + 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.notify_modem) { + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + dev->port.notify_modem(&dev->port, 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), + 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); + + if (!f->descriptors) + goto fail; + + 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); + + if (!f->hs_descriptors) + goto fail; + } + + 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; + +fail: + if (f->descriptors) + usb_free_descriptors(f->descriptors); +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; +} + +static int frmnet_bind_config(struct usb_configuration *c, 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_rmnet_ports) { + pr_err("%s: supporting ports#%u port_id:%u", __func__, + nr_rmnet_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 = rmnet_ports[portno].port; + + spin_lock_irqsave(&dev->lock, flags); + dev->cdev = c->cdev; + f = &dev->port.func; + f->name = kasprintf(GFP_ATOMIC, "rmnet%d", portno); + spin_unlock_irqrestore(&dev->lock, flags); + if (!f->name) { + pr_err("%s: cannot allocate memory for name\n", __func__); + return -ENOMEM; + } + + 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; + f->suspend = frmnet_suspend; + f->resume = frmnet_resume; + dev->port.send_cpkt_response = frmnet_send_cpkt_response; + dev->port.disconnect = frmnet_disconnect; + dev->port.connect = frmnet_connect; + + status = usb_add_function(c, f); + if (status) { + pr_err("%s: usb add function failed: %d\n", + __func__, status); + kfree(f->name); + return status; + } + + pr_debug("%s: complete\n", __func__); + + return status; +} + +static void frmnet_cleanup(void) +{ + int i; + + for (i = 0; i < nr_rmnet_ports; i++) + kfree(rmnet_ports[i].port); + + nr_rmnet_ports = 0; + no_ctrl_smd_ports = 0; + no_data_bam_ports = 0; + no_data_bam2bam_ports = 0; + no_ctrl_hsic_ports = 0; + no_data_hsic_ports = 0; + no_ctrl_hsuart_ports = 0; + no_data_hsuart_ports = 0; +} + +static int frmnet_init_port(const char *ctrl_name, const char *data_name) +{ + struct f_rmnet *dev; + struct rmnet_ports *rmnet_port; + int ret; + int i; + + if (nr_rmnet_ports >= NR_RMNET_PORTS) { + pr_err("%s: Max-%d instances supported\n", + __func__, NR_RMNET_PORTS); + return -EINVAL; + } + + pr_debug("%s: port#:%d, ctrl port: %s data port: %s\n", + __func__, nr_rmnet_ports, ctrl_name, data_name); + + dev = kzalloc(sizeof(struct f_rmnet), GFP_KERNEL); + if (!dev) { + pr_err("%s: Unable to allocate rmnet device\n", __func__); + return -ENOMEM; + } + + dev->port_num = nr_rmnet_ports; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->cpkt_resp_q); + + rmnet_port = &rmnet_ports[nr_rmnet_ports]; + rmnet_port->port = dev; + rmnet_port->port_num = nr_rmnet_ports; + rmnet_port->ctrl_xport = str_to_xport(ctrl_name); + rmnet_port->data_xport = str_to_xport(data_name); + + switch (rmnet_port->ctrl_xport) { + case USB_GADGET_XPORT_SMD: + rmnet_port->ctrl_xport_num = no_ctrl_smd_ports; + no_ctrl_smd_ports++; + break; + case USB_GADGET_XPORT_HSIC: + rmnet_port->ctrl_xport_num = no_ctrl_hsic_ports; + no_ctrl_hsic_ports++; + break; + case USB_GADGET_XPORT_HSUART: + rmnet_port->ctrl_xport_num = no_ctrl_hsuart_ports; + no_ctrl_hsuart_ports++; + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %u\n", __func__, + rmnet_port->ctrl_xport); + ret = -ENODEV; + goto fail_probe; + } + + switch (rmnet_port->data_xport) { + case USB_GADGET_XPORT_BAM: + rmnet_port->data_xport_num = no_data_bam_ports; + no_data_bam_ports++; + break; + case USB_GADGET_XPORT_BAM2BAM: + rmnet_port->data_xport_num = no_data_bam2bam_ports; + no_data_bam2bam_ports++; + break; + case USB_GADGET_XPORT_HSIC: + rmnet_port->data_xport_num = no_data_hsic_ports; + no_data_hsic_ports++; + break; + case USB_GADGET_XPORT_HSUART: + rmnet_port->data_xport_num = no_data_hsuart_ports; + no_data_hsuart_ports++; + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %u\n", __func__, + rmnet_port->data_xport); + ret = -ENODEV; + goto fail_probe; + } + nr_rmnet_ports++; + + return 0; + +fail_probe: + for (i = 0; i < nr_rmnet_ports; i++) + kfree(rmnet_ports[i].port); + + nr_rmnet_ports = 0; + no_ctrl_smd_ports = 0; + no_data_bam_ports = 0; + no_ctrl_hsic_ports = 0; + no_data_hsic_ports = 0; + no_ctrl_hsuart_ports = 0; + no_data_hsuart_ports = 0; + + return ret; +} diff --git a/drivers/usb/gadget/f_rmnet.h b/drivers/usb/gadget/f_rmnet.h new file mode 100644 index 0000000000000000000000000000000000000000..2d816c653f14eced9242a2cad6d8b9d17c9baaa7 --- /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 0000000000000000000000000000000000000000..80193563dd418fe0e86d299b252d4b11e8c2022f --- /dev/null +++ b/drivers/usb/gadget/f_rmnet_sdio.c @@ -0,0 +1,1576 @@ +/* + * 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 + +#ifdef CONFIG_RMNET_SDIO_CTL_CHANNEL +static uint32_t rmnet_sdio_ctl_ch = CONFIG_RMNET_SDIO_CTL_CHANNEL; +#else +static uint32_t rmnet_sdio_ctl_ch; +#endif +module_param(rmnet_sdio_ctl_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_sdio_ctl_ch, "RmNet control SDIO channel ID"); + +#ifdef CONFIG_RMNET_SDIO_DATA_CHANNEL +static uint32_t rmnet_sdio_data_ch = CONFIG_RMNET_SDIO_DATA_CHANNEL; +#else +static uint32_t rmnet_sdio_data_ch; +#endif +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 sdio_tx_pkt_drop_thld = TX_PKT_DROP_THRESHOLD; +module_param(sdio_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int sdio_rx_fctrl_en_thld = RX_PKT_FLOW_CTRL_EN_THRESHOLD; +module_param(sdio_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int sdio_rx_fctrl_dis_thld = RX_PKT_FLOW_CTRL_DISABLE; +module_param(sdio_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_sdio_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; + unsigned int qreq_q_len; + struct list_head qmi_resp_q; + unsigned int qresp_q_len; + /* Tx/Rx lists */ + struct list_head tx_idle; + unsigned int tx_idle_len; + struct sk_buff_head tx_skb_queue; + struct list_head rx_idle; + unsigned int rx_idle_len; + 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; + struct work_struct sdio_close_work; +#define RMNET_SDIO_CH_OPEN 1 + unsigned long data_ch_status; + unsigned long ctrl_ch_status; + + 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*/ + struct dentry *dent; + 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_sdio_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_sdio_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_sdio_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_sdio_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_sdio_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_sdio_interface_desc, + (struct usb_descriptor_header *) &rmnet_sdio_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_sdio_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_sdio_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_sdio_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_sdio_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_sdio_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_sdio_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_sdio_interface_desc, + (struct usb_descriptor_header *) &rmnet_sdio_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_sdio_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_sdio_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_sdio_string_defs[] = { + [0].s = "QMI RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_sdio_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_sdio_string_defs, +}; + +static struct usb_gadget_strings *rmnet_sdio_strings[] = { + &rmnet_sdio_string_table, + NULL, +}; + +static struct rmnet_sdio_qmi_buf * +rmnet_sdio_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_sdio_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_sdio_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_sdio_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void rmnet_sdio_notify_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_sdio_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 (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) + return; + + /* 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 rmnet_sdio_qmi_resp_available(struct rmnet_sdio_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_cdc_notification *event; + int status; + unsigned long flags; + + /* Response will be sent later */ + if (atomic_inc_return(&dev->notify_count) != 1) + return; + + spin_lock_irqsave(&dev->lock, flags); + + if (!atomic_read(&dev->online)) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + event = dev->notify_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); + + status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC); + if (status < 0) { + if (atomic_read(&dev->online)) + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enqueue error %d\n", status); + } +} + +#define SDIO_MAX_CTRL_PKT_SIZE 4096 +static void rmnet_sdio_ctl_receive_cb(void *data, int size, void *priv) +{ + struct rmnet_sdio_dev *dev = priv; + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_sdio_qmi_buf *qmi_resp; + unsigned long flags; + + if (!data) { + pr_info("%s: cmux_ch close event\n", __func__); + if (test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status) && + test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { + clear_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); + clear_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); + queue_work(dev->wq, &dev->sdio_close_work); + } + return; + } + + if (!size || !test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) + return; + + + if (size > SDIO_MAX_CTRL_PKT_SIZE) { + ERROR(cdev, "ctrl pkt size:%d exceeds max pkt size:%d\n", + size, SDIO_MAX_CTRL_PKT_SIZE); + return; + } + + if (!atomic_read(&dev->online)) { + DBG(cdev, "USB disconnected\n"); + return; + } + + qmi_resp = rmnet_sdio_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); + dev->qresp_q_len++; + spin_unlock_irqrestore(&dev->lock, flags); + + rmnet_sdio_qmi_resp_available(dev); +} + +static void rmnet_sdio_ctl_write_done(void *data, int size, void *priv) +{ + struct rmnet_sdio_dev *dev = priv; + struct usb_composite_dev *cdev = dev->cdev; + + VDBG(cdev, "rmnet control write done = %d bytes\n", size); +} + +static void rmnet_sdio_sts_callback(int id, void *priv) +{ + struct rmnet_sdio_dev *dev = priv; + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "rmnet_sdio_sts_callback: id: %d\n", id); +} + +static void rmnet_sdio_control_rx_work(struct work_struct *w) +{ + struct rmnet_sdio_dev *dev = container_of(w, struct rmnet_sdio_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); + dev->qreq_q_len--; + 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_sdio_free_qmi(qmi_req); + } +unlock: + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_sdio_response_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_sdio_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_sdio_command_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_sdio_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 (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) + return; + + qmi_req = rmnet_sdio_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); + dev->qreq_q_len++; + spin_unlock(&dev->lock); + queue_work(dev->wq, &dev->ctl_rx_work); +} + +static int +rmnet_sdio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_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: + ret = w_length; + req->complete = rmnet_sdio_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); + dev->qresp_q_len--; + 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_sdio_response_complete; + rmnet_sdio_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_sdio_rx_submit(struct rmnet_sdio_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_sdio_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + int status; + struct usb_request *req; + unsigned long flags; + + if (!atomic_read(&dev->online)) { + pr_err("%s: USB not connected\n", __func__); + return; + } + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(&dev->rx_idle)) { + req = list_first_entry(&dev->rx_idle, struct usb_request, list); + list_del(&req->list); + dev->rx_idle_len--; + + spin_unlock_irqrestore(&dev->lock, flags); + status = rmnet_sdio_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); + dev->rx_idle_len++; + break; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_sdio_start_tx(struct rmnet_sdio_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; + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) + 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); + dev->tx_idle_len--; + 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); + dev->tx_idle_len++; + __skb_queue_head(&dev->tx_skb_queue, skb); + } else { + req->buf = 0; + rmnet_sdio_free_req(dev->epin, req); + dev_kfree_skb_any(skb); + } + break; + } + dev->dpkt_tolaptop++; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_sdio_data_receive_cb(void *priv, struct sk_buff *skb) +{ + struct rmnet_sdio_dev *dev = priv; + unsigned long flags; + + /* SDIO mux sends NULL SKB when link state changes */ + if (!skb) { + pr_info("%s: dmux_ch close event\n", __func__); + if (test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status) && + test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { + clear_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); + clear_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); + queue_work(dev->wq, &dev->sdio_close_work); + } + return; + } + + if (!atomic_read(&dev->online)) { + dev_kfree_skb_any(skb); + return; + } + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->tx_skb_queue.qlen > sdio_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); + + rmnet_sdio_start_tx(dev); +} + +static void rmnet_sdio_data_write_done(void *priv, struct sk_buff *skb) +{ + struct rmnet_sdio_dev *dev = priv; + + /* SDIO mux sends NULL SKB when link state changes */ + if (!skb) { + pr_info("%s: dmux_ch open event\n", __func__); + queue_delayed_work(dev->wq, &dev->sdio_open_work, 0); + 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 (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status) || + 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_sdio_dev *dev = container_of(w, struct rmnet_sdio_dev, + data_rx_work); + struct usb_composite_dev *cdev = dev->cdev; + struct sk_buff *skb; + int ret; + unsigned long flags; + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { + pr_info("%s: sdio data ch not open\n", __func__); + return; + } + + 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); + break; + } else { + dev->dpkt_tomodem++; + 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_sdio_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_sdio_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; + } + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { + pr_info("%s: sdio data ch not open\n", __func__); + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_sdio_free_req(ep, req); + return; + } + + 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 >= sdio_rx_fctrl_en_thld) { + list_add_tail(&req->list, &dev->rx_idle); + dev->rx_idle_len++; + 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, &dev->rx_idle); + dev->rx_idle_len++; + } +} + +static void rmnet_sdio_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_sdio_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); + dev->tx_idle_len++; + spin_unlock(&dev->lock); + dev_kfree_skb_any(skb); + + rmnet_sdio_start_tx(dev); +} + +static void rmnet_sdio_free_buf(struct rmnet_sdio_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); + dev->tx_idle_len--; + req->buf = NULL; + rmnet_sdio_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); + dev->rx_idle_len--; + req->buf = NULL; + rmnet_sdio_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); + dev->qreq_q_len--; + rmnet_sdio_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); + dev->qresp_q_len--; + rmnet_sdio_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_sdio_free_req(dev->epnotify, dev->notify_req); + + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_sdio_set_modem_cbits_w(struct work_struct *w) +{ + struct rmnet_sdio_dev *dev; + + dev = container_of(w, struct rmnet_sdio_dev, set_modem_ctl_bits_work); + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) + 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_sdio_disconnect_work(struct work_struct *w) +{ + /* REVISIT: Push all the data to sdio if anythign is pending */ +} +static void rmnet_sdio_suspend(struct usb_function *f) +{ + struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_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_sdio_disable(struct usb_function *f) +{ + struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_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_sdio_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); +} + +static void rmnet_close_sdio_work(struct work_struct *w) +{ + struct rmnet_sdio_dev *dev; + unsigned long flags; + struct usb_cdc_notification *event; + int status; + struct rmnet_sdio_qmi_buf *qmi; + struct usb_request *req; + struct sk_buff *skb; + + pr_debug("%s:\n", __func__); + + dev = container_of(w, struct rmnet_sdio_dev, sdio_close_work); + + if (!atomic_read(&dev->online)) + return; + + usb_ep_fifo_flush(dev->epnotify); + + spin_lock_irqsave(&dev->lock, flags); + event = dev->notify_req->buf; + + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + 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); + + status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_KERNEL); + if (status < 0) { + if (!atomic_read(&dev->online)) + return; + pr_err("%s: rmnet notify ep enqueue error %d\n", + __func__, status); + } + + usb_ep_fifo_flush(dev->epout); + usb_ep_fifo_flush(dev->epin); + cancel_work_sync(&dev->data_rx_work); + + spin_lock_irqsave(&dev->lock, flags); + + if (!atomic_read(&dev->online)) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + /* free all usb requests in tx pool */ + while (!list_empty(&dev->tx_idle)) { + req = list_first_entry(&dev->tx_idle, struct usb_request, list); + list_del(&req->list); + dev->tx_idle_len--; + req->buf = NULL; + rmnet_sdio_free_req(dev->epout, req); + } + + /* free all usb requests in rx pool */ + while (!list_empty(&dev->rx_idle)) { + req = list_first_entry(&dev->rx_idle, struct usb_request, list); + list_del(&req->list); + dev->rx_idle_len--; + req->buf = NULL; + rmnet_sdio_free_req(dev->epin, req); + } + + /* free all buffers in qmi request pool */ + while (!list_empty(&dev->qmi_req_q)) { + qmi = list_first_entry(&dev->qmi_req_q, + struct rmnet_sdio_qmi_buf, list); + list_del(&qmi->list); + dev->qreq_q_len--; + rmnet_sdio_free_qmi(qmi); + } + + /* free all buffers in qmi response pool */ + while (!list_empty(&dev->qmi_resp_q)) { + qmi = list_first_entry(&dev->qmi_resp_q, + struct rmnet_sdio_qmi_buf, list); + list_del(&qmi->list); + dev->qresp_q_len--; + rmnet_sdio_free_qmi(qmi); + } + atomic_set(&dev->notify_count, 0); + + pr_info("%s: setting notify count to zero\n", __func__); + + + 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); + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int rmnet_sdio_start_io(struct rmnet_sdio_dev *dev) +{ + struct usb_request *req; + int ret, i; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (!atomic_read(&dev->online)) { + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status) || + !test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + + for (i = 0; i < RMNET_SDIO_RX_REQ_MAX; i++) { + req = rmnet_sdio_alloc_req(dev->epout, 0, GFP_ATOMIC); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + spin_unlock_irqrestore(&dev->lock, flags); + goto free_buf; + } + req->complete = rmnet_sdio_complete_epout; + list_add_tail(&req->list, &dev->rx_idle); + dev->rx_idle_len++; + } + for (i = 0; i < RMNET_SDIO_TX_REQ_MAX; i++) { + req = rmnet_sdio_alloc_req(dev->epin, 0, GFP_ATOMIC); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + spin_unlock_irqrestore(&dev->lock, flags); + goto free_buf; + } + req->complete = rmnet_sdio_complete_epin; + list_add_tail(&req->list, &dev->tx_idle); + dev->tx_idle_len++; + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* Queue Rx data requests */ + rmnet_sdio_start_rx(dev); + + return 0; + +free_buf: + rmnet_sdio_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + return ret; +} + + +#define RMNET_SDIO_OPEN_RETRY_DELAY msecs_to_jiffies(2000) +#define SDIO_SDIO_OPEN_MAX_RETRY 90 +static void rmnet_open_sdio_work(struct work_struct *w) +{ + struct rmnet_sdio_dev *dev = + container_of(w, struct rmnet_sdio_dev, + sdio_open_work.work); + struct usb_composite_dev *cdev = dev->cdev; + int ret; + static int retry_cnt; + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { + /* Control channel for QMI messages */ + ret = sdio_cmux_open(rmnet_sdio_ctl_ch, + rmnet_sdio_ctl_receive_cb, + rmnet_sdio_ctl_write_done, + rmnet_sdio_sts_callback, dev); + if (!ret) + set_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); + } + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { + /* 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) + set_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); + } + + if (test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status) && + test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { + + rmnet_sdio_start_io(dev); + + /* 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); + retry_cnt = 0; + return; + } + + retry_cnt++; + pr_debug("%s: usb rmnet sdio open retry_cnt:%d\n", + __func__, retry_cnt); + + if (retry_cnt > SDIO_SDIO_OPEN_MAX_RETRY) { + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) + ERROR(cdev, "Unable to open control SDIO channel\n"); + + if (!test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) + ERROR(cdev, "Unable to open DATA SDIO channel\n"); + + } else { + queue_delayed_work(dev->wq, &dev->sdio_open_work, + RMNET_SDIO_OPEN_RETRY_DELAY); + } +} + +static int rmnet_sdio_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, + function); + struct usb_composite_dev *cdev = dev->cdev; + int ret = 0; + + /* Enable epin */ + dev->epin->driver_data = dev; + ret = config_ep_by_speed(cdev->gadget, f, dev->epin); + if (ret) { + dev->epin->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->epin->name, ret); + return ret; + } + ret = usb_ep_enable(dev->epin); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epin->name, ret); + return ret; + } + + /* Enable epout */ + dev->epout->driver_data = dev; + ret = config_ep_by_speed(cdev->gadget, f, dev->epout); + if (ret) { + dev->epout->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + ret = usb_ep_enable(dev->epout); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + + /* Enable epnotify */ + ret = config_ep_by_speed(cdev->gadget, f, dev->epnotify); + if (ret) { + dev->epnotify->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->epnotify->name, ret); + usb_ep_disable(dev->epin); + usb_ep_disable(dev->epout); + return ret; + } + ret = usb_ep_enable(dev->epnotify); + 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; + } + + /* allocate notification */ + dev->notify_req = rmnet_sdio_alloc_req(dev->epnotify, + RMNET_SDIO_MAX_NFY_SZE, GFP_ATOMIC); + + if (IS_ERR(dev->notify_req)) { + ret = PTR_ERR(dev->notify_req); + pr_err("%s: unable to allocate memory for notify ep\n", + __func__); + return ret; + } + dev->notify_req->complete = rmnet_sdio_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_SDIO_MAX_NFY_SZE; + + atomic_set(&dev->online, 1); + + ret = rmnet_sdio_start_io(dev); + + return ret; + +} + +static int rmnet_sdio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_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_sdio_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_sdio_fs_in_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epin = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_sdio_fs_out_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epout = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_sdio_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_sdio_hs_in_desc.bEndpointAddress = + rmnet_sdio_fs_in_desc.bEndpointAddress; + rmnet_sdio_hs_out_desc.bEndpointAddress = + rmnet_sdio_fs_out_desc.bEndpointAddress; + rmnet_sdio_hs_notify_desc.bEndpointAddress = + rmnet_sdio_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_sdio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct rmnet_sdio_dev *dev = container_of(f, struct rmnet_sdio_dev, + function); + + cancel_delayed_work_sync(&dev->sdio_open_work); + destroy_workqueue(dev->wq); + + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + + if (test_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status)) { + msm_sdio_dmux_close(rmnet_sdio_data_ch); + clear_bit(RMNET_SDIO_CH_OPEN, &dev->data_ch_status); + } + + if (test_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status)) { + sdio_cmux_close(rmnet_sdio_ctl_ch); + clear_bit(RMNET_SDIO_CH_OPEN, &dev->ctrl_ch_status); + } + + debugfs_remove_recursive(dev->dent); + + kfree(dev); +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t rmnet_sdio_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_sdio_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, + "-*-DATA-*-\n" + "dpkts_tohost:%lu epInPool:%u tx_size:%u drp_cnt:%lu\n" + "dpkts_tomodem:%lu epOutPool:%u rx_size:%u pending:%u\n" + "-*-QMI-*-\n" + "cpkts_tomodem:%lu qmi_req_q:%u cbits:%d\n" + "cpkts_tolaptop:%lu qmi_resp_q:%u notify_cnt:%d\n" + "-*-MISC-*-\n" + "data_ch_status: %lu ctrl_ch_status: %lu\n", + /* data */ + dev->dpkt_tolaptop, dev->tx_idle_len, + dev->tx_skb_queue.qlen, dev->tx_drp_cnt, + dev->dpkt_tomodem, dev->rx_idle_len, + dev->rx_skb_queue.qlen, dev->dpkts_pending_atdmux, + /* qmi */ + dev->cpkt_tomodem, dev->qreq_q_len, + dev->cbits_to_modem, + dev->cpkt_tolaptop, dev->qresp_q_len, + atomic_read(&dev->notify_count), + /* misc */ + dev->data_ch_status, dev->ctrl_ch_status); + + spin_unlock_irqrestore(&dev->lock, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + + kfree(buf); + + return ret; +} + +static ssize_t rmnet_sdio_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_sdio_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_rmnet_sdio_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +const struct file_operations debug_rmnet_sdio_stats_ops = { + .open = debug_rmnet_sdio_open, + .read = rmnet_sdio_read_stats, + .write = rmnet_sdio_reset_stats, +}; + +static void rmnet_sdio_debugfs_init(struct rmnet_sdio_dev *dev) +{ + dev->dent = debugfs_create_dir("usb_rmnet_sdio", 0); + if (IS_ERR(dev->dent)) + return; + + debugfs_create_file("status", 0444, dev->dent, dev, + &debug_rmnet_sdio_stats_ops); +} +#else +static void rmnet_sdio_debugfs_init(struct rmnet_sdio_dev *dev) +{ + return; +} +#endif + +int rmnet_sdio_function_add(struct usb_configuration *c) +{ + struct rmnet_sdio_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_sdio_disconnect_work); + INIT_WORK(&dev->set_modem_ctl_bits_work, rmnet_sdio_set_modem_cbits_w); + + INIT_WORK(&dev->ctl_rx_work, rmnet_sdio_control_rx_work); + INIT_WORK(&dev->data_rx_work, rmnet_sdio_data_rx_work); + + INIT_DELAYED_WORK(&dev->sdio_open_work, rmnet_open_sdio_work); + INIT_WORK(&dev->sdio_close_work, rmnet_close_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_sdio_strings; + dev->function.descriptors = rmnet_sdio_fs_function; + dev->function.hs_descriptors = rmnet_sdio_hs_function; + dev->function.bind = rmnet_sdio_bind; + dev->function.unbind = rmnet_sdio_unbind; + dev->function.setup = rmnet_sdio_setup; + dev->function.set_alt = rmnet_sdio_set_alt; + dev->function.disable = rmnet_sdio_disable; + dev->function.suspend = rmnet_sdio_suspend; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto free_wq; + + rmnet_sdio_debugfs_init(dev); + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} diff --git a/drivers/usb/gadget/f_rmnet_smd.c b/drivers/usb/gadget/f_rmnet_smd.c new file mode 100644 index 0000000000000000000000000000000000000000..b71f64686781481236eee733dd77a6fcb4f3b784 --- /dev/null +++ b/drivers/usb/gadget/f_rmnet_smd.c @@ -0,0 +1,1391 @@ +/* + * 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-2012, Code Aurora Forum. All 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 "gadget_chips.h" + +#ifndef CONFIG_MSM_SMD +#define CONFIG_RMNET_SMD_CTL_CHANNEL "" +#define CONFIG_RMNET_SMD_DATA_CHANNEL "" +#endif + +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_SMD_ACM_CTRL_DTR (1 << 0) + +#define RMNET_SMD_NOTIFY_INTERVAL 5 +#define RMNET_SMD_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 RMNET_RX_REQ_MAX 8 +#define RMNET_RX_REQ_SIZE 2048 +#define RMNET_TX_REQ_MAX 8 +#define RMNET_TX_REQ_SIZE 2048 + +#define RMNET_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_ch_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 { + 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 platform_driver pdrv; + u8 is_pdrv_used; + struct rmnet_smd_ch_info smd_ctl; + struct rmnet_smd_ch_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 rmnet_smd_dev *rmnet_smd; + +static struct usb_interface_descriptor rmnet_smd_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_smd_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_SMD_MAX_NOTIFY_SIZE), + .bInterval = 1 << RMNET_SMD_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_smd_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_smd_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_smd_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_smd_interface_desc, + (struct usb_descriptor_header *) &rmnet_smd_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_smd_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_smd_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_smd_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_SMD_MAX_NOTIFY_SIZE), + .bInterval = RMNET_SMD_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_smd_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_smd_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_smd_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_smd_interface_desc, + (struct usb_descriptor_header *) &rmnet_smd_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_smd_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_smd_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_smd_string_defs[] = { + [0].s = "QMI RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_smd_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_smd_string_defs, +}; + +static struct usb_gadget_strings *rmnet_smd_strings[] = { + &rmnet_smd_string_table, + NULL, +}; + +static struct qmi_buf * +rmnet_smd_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_smd_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_smd_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_smd_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void rmnet_smd_notify_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_smd_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_smd_response_available(struct rmnet_smd_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_event_notify(void *priv, unsigned event) +{ + struct rmnet_smd_ch_info *smd_info = priv; + int len = atomic_read(&smd_info->rx_pkt); + struct rmnet_smd_dev *dev = + (struct rmnet_smd_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_smd_dev *dev = (struct rmnet_smd_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_smd_response_available(dev); + } + +} + +static void rmnet_control_rx_tlet(unsigned long arg) +{ + struct rmnet_smd_dev *dev = (struct rmnet_smd_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_smd_command_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_smd_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_smd_dev *dev = req->context; + + spin_lock(&dev->lock); + dev->cpkts_to_host++; + spin_unlock(&dev->lock); +} + +static int +rmnet_smd_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct rmnet_smd_dev *dev = container_of(f, struct rmnet_smd_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: + ret = w_length; + req->complete = rmnet_smd_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 & RMNET_SMD_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_smd_start_rx(struct rmnet_smd_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_smd_dev *dev = (struct rmnet_smd_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_smd_dev *dev = (struct rmnet_smd_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_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_smd_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_smd_start_rx(dev); + return; + } +queue_req: + list_add_tail(&req->list, &dev->rx_queue); + spin_unlock(&dev->lock); +} + +static void rmnet_smd_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_smd_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_smd_disconnect_work(struct work_struct *w) +{ + struct qmi_buf *qmi; + struct usb_request *req; + struct list_head *act, *tmp; + struct rmnet_smd_dev *dev = container_of(w, struct rmnet_smd_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); + } + + if (dev->is_pdrv_used) { + platform_driver_unregister(&dev->pdrv); + dev->is_pdrv_used = 0; + } +} + +/* SMD close may sleep + * schedule a work to close smd channels + */ +static void rmnet_smd_disable(struct usb_function *f) +{ + struct rmnet_smd_dev *dev = container_of(f, struct rmnet_smd_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_smd_connect_work(struct work_struct *w) +{ + struct rmnet_smd_dev *dev = container_of(w, struct rmnet_smd_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_event_notify); + if (ret) { + ERROR(cdev, "Unable to open control smd channel: %d\n", ret); + /* + * Register platform driver to be notified in case SMD channels + * later becomes ready to be opened. + */ + ret = platform_driver_register(&dev->pdrv); + if (ret) + ERROR(cdev, "Platform driver %s register failed %d\n", + dev->pdrv.driver.name, ret); + else + dev->is_pdrv_used = 1; + + 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_event_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_smd_start_rx(dev); +} + +static int rmnet_smd_ch_probe(struct platform_device *pdev) +{ + DBG(rmnet_smd->cdev, "Probe called for device: %s\n", pdev->name); + + queue_work(rmnet_smd->wq, &rmnet_smd->connect_work); + + return 0; +} + +/* SMD open may sleep. + * Schedule a work to open smd channels and enable + * endpoints if smd channels are opened successfully. + */ +static int rmnet_smd_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct rmnet_smd_dev *dev = container_of(f, struct rmnet_smd_dev, + function); + struct usb_composite_dev *cdev = dev->cdev; + int ret = 0; + + /* Enable epin endpoint */ + ret = config_ep_by_speed(cdev->gadget, f, dev->epin); + if (ret) { + dev->epin->desc = NULL; + ERROR(cdev, "config_ep_by_speed failed for ep %s, result %d\n", + dev->epin->name, ret); + return ret; + } + ret = usb_ep_enable(dev->epin); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epin->name, ret); + return ret; + } + + /* Enable epout endpoint */ + ret = config_ep_by_speed(cdev->gadget, f, dev->epout); + if (ret) { + dev->epout->desc = NULL; + ERROR(cdev, "config_ep_by_speed failed for ep %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + ret = usb_ep_enable(dev->epout); + + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + + /* Enable epnotify endpoint */ + ret = config_ep_by_speed(cdev->gadget, f, dev->epnotify); + if (ret) { + dev->epnotify->desc = NULL; + ERROR(cdev, "config_ep_by_speed failed for ep %s, result %d\n", + dev->epnotify->name, ret); + usb_ep_disable(dev->epin); + usb_ep_disable(dev->epout); + return ret; + } + ret = usb_ep_enable(dev->epnotify); + 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_smd_free_buf(struct rmnet_smd_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_smd_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_smd_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_smd_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_smd_free_qmi(qmi); + } + + rmnet_smd_free_req(dev->epnotify, dev->notify_req); +} +static int rmnet_smd_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct rmnet_smd_dev *dev = container_of(f, struct rmnet_smd_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_smd_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_smd_fs_in_desc); + if (!ep) + return -ENODEV; + ep->driver_data = cdev; /* claim endpoint */ + dev->epin = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_smd_fs_out_desc); + if (!ep) + return -ENODEV; + ep->driver_data = cdev; /* claim endpoint */ + dev->epout = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_smd_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_smd_hs_in_desc.bEndpointAddress = + rmnet_smd_fs_in_desc.bEndpointAddress; + rmnet_smd_hs_out_desc.bEndpointAddress = + rmnet_smd_fs_out_desc.bEndpointAddress; + rmnet_smd_hs_notify_desc.bEndpointAddress = + rmnet_smd_fs_notify_desc.bEndpointAddress; + + } + + /* allocate notification */ + dev->notify_req = rmnet_smd_alloc_req(dev->epnotify, + RMNET_SMD_MAX_NOTIFY_SIZE, GFP_KERNEL); + if (IS_ERR(dev->notify_req)) + return PTR_ERR(dev->notify_req); + + dev->notify_req->complete = rmnet_smd_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_SMD_MAX_NOTIFY_SIZE; + + /* Allocate the qmi request and response buffers */ + for (i = 0; i < QMI_REQ_MAX; i++) { + qmi = rmnet_smd_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_smd_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 < RMNET_RX_REQ_MAX; i++) { + req = rmnet_smd_alloc_req(dev->epout, RMNET_RX_REQ_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->length = RMNET_TXN_MAX; + req->context = dev; + req->complete = rmnet_smd_complete_epout; + list_add_tail(&req->list, &dev->rx_idle); + } + + for (i = 0; i < RMNET_TX_REQ_MAX; i++) { + req = rmnet_smd_alloc_req(dev->epin, RMNET_TX_REQ_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->context = dev; + req->complete = rmnet_smd_complete_epin; + list_add_tail(&req->list, &dev->tx_idle); + } + + return 0; + +free_buf: + rmnet_smd_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + return ret; +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t rmnet_smd_debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_smd_dev *dev = file->private_data; + struct rmnet_smd_ch_info smd_ctl_info = dev->smd_ctl; + struct rmnet_smd_ch_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 rmnet_smd_debug_reset_stats(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_smd_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 rmnet_smd_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +const struct file_operations rmnet_smd_debug_stats_ops = { + .open = rmnet_smd_debug_open, + .read = rmnet_smd_debug_read_stats, + .write = rmnet_smd_debug_reset_stats, +}; + +struct dentry *dent_smd; +struct dentry *dent_smd_status; + +static void rmnet_smd_debugfs_init(struct rmnet_smd_dev *dev) +{ + + dent_smd = debugfs_create_dir("usb_rmnet_smd", 0); + if (IS_ERR(dent_smd)) + return; + + dent_smd_status = debugfs_create_file("status", 0444, dent_smd, dev, + &rmnet_smd_debug_stats_ops); + + if (!dent_smd_status) { + debugfs_remove(dent_smd); + dent_smd = NULL; + return; + } + + return; +} +#else +static void rmnet_smd_debugfs_init(struct rmnet_smd_dev *dev) {} +#endif + +static void +rmnet_smd_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct rmnet_smd_dev *dev = container_of(f, struct rmnet_smd_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_smd_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + + destroy_workqueue(dev->wq); + debugfs_remove_recursive(dent_smd); + kfree(dev); + +} + +int rmnet_smd_bind_config(struct usb_configuration *c) +{ + struct rmnet_smd_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + rmnet_smd = 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); + atomic_set(&dev->smd_ctl.rx_pkt, 0); + atomic_set(&dev->smd_data.rx_pkt, 0); + + INIT_WORK(&dev->connect_work, rmnet_smd_connect_work); + INIT_WORK(&dev->disconnect_work, rmnet_smd_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); + + dev->pdrv.probe = rmnet_smd_ch_probe; + dev->pdrv.driver.name = CONFIG_RMNET_SMD_CTL_CHANNEL; + dev->pdrv.driver.owner = THIS_MODULE; + + 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_smd_strings; + dev->function.descriptors = rmnet_smd_fs_function; + dev->function.hs_descriptors = rmnet_smd_hs_function; + dev->function.bind = rmnet_smd_bind; + dev->function.unbind = rmnet_smd_unbind; + dev->function.setup = rmnet_smd_setup; + dev->function.set_alt = rmnet_smd_set_alt; + dev->function.disable = rmnet_smd_disable; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto free_wq; + + rmnet_smd_debugfs_init(dev); + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} 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 0000000000000000000000000000000000000000..175afe324d932fc5ecafb5e0629ba38f4515ef4f --- /dev/null +++ b/drivers/usb/gadget/f_rmnet_smd_sdio.c @@ -0,0 +1,2045 @@ +/* + * 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 +#include + +#ifdef CONFIG_RMNET_SMD_SDIO_CTL_CHANNEL +static uint32_t rmnet_mux_sdio_ctl_ch = CONFIG_RMNET_SMD_SDIO_CTL_CHANNEL; +#else +static uint32_t rmnet_mux_sdio_ctl_ch; +#endif +module_param(rmnet_mux_sdio_ctl_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_mux_sdio_ctl_ch, "RmNetMUX control SDIO channel ID"); + +#ifdef CONFIG_RMNET_SMD_SDIO_DATA_CHANNEL +static uint32_t rmnet_mux_sdio_data_ch = CONFIG_RMNET_SMD_SDIO_DATA_CHANNEL; +#else +static uint32_t rmnet_mux_sdio_data_ch; +#endif +module_param(rmnet_mux_sdio_data_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_mux_sdio_data_ch, "RmNetMUX data SDIO channel ID"); + +#ifdef CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL +static char *rmnet_mux_smd_data_ch = CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL; +#else +static char *rmnet_mux_smd_data_ch; +#endif +module_param(rmnet_mux_smd_data_ch, charp, S_IRUGO); +MODULE_PARM_DESC(rmnet_mux_smd_data_ch, "RmNetMUX data SMD channel"); + +#define RMNET_MUX_ACM_CTRL_DTR (1 << 0) + +#define RMNET_MUX_SDIO_HDR 8 +#define RMNET_MUX_SDIO_NOTIFY_INTERVAL 5 +#define RMNET_MUX_SDIO_MAX_NFY_SZE sizeof(struct usb_cdc_notification) + +#define RMNET_MUX_SDIO_RX_REQ_MAX 16 +#define RMNET_MUX_SDIO_RX_REQ_SIZE 2048 +#define RMNET_MUX_SDIO_TX_REQ_MAX 100 + +#define RMNET_MUX_SDIO_TX_LIMIT 1000 +#define RMNET_MUX_SDIO_RX_ENABLE_LIMIT 1000 +#define RMNET_MUX_SDIO_RX_DISABLE_LIMIT 500 + +static uint32_t mux_sdio_tx_pkt_drop_thld = RMNET_MUX_SDIO_TX_LIMIT; +module_param(mux_sdio_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +static uint32_t mux_sdio_rx_fctrl_en_thld = + RMNET_MUX_SDIO_RX_ENABLE_LIMIT; +module_param(mux_sdio_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static uint32_t mux_sdio_rx_fctrl_dis_thld = RMNET_MUX_SDIO_RX_DISABLE_LIMIT; +module_param(mux_sdio_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + + +#define RMNET_MUX_SMD_RX_REQ_MAX 8 +#define RMNET_MUX_SMD_RX_REQ_SIZE 2048 +#define RMNET_MUX_SMD_TX_REQ_MAX 8 +#define RMNET_MUX_SMD_TX_REQ_SIZE 2048 +#define RMNET_MUX_SMD_TXN_MAX 2048 + +struct rmnet_mux_ctrl_pkt { + void *buf; + int len; + struct list_head list; +}; + +struct rmnet_mux_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_mux_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_mux_smd_info { + struct smd_channel *ch; + struct tasklet_struct tx_tlet; + struct tasklet_struct rx_tlet; +#define RMNET_MUX_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_mux_smd_dev { + /* Tx/Rx lists */ + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_queue; + + struct rmnet_mux_smd_info smd_data; +}; + +struct rmnet_mux_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_mux_smd_dev smd_dev; + struct rmnet_mux_sdio_dev sdio_dev; + struct rmnet_mux_ctrl_dev ctrl_dev; + + u8 ifc_id; + enum transport_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_mux_dev *rmux_dev; + +static struct usb_interface_descriptor rmnet_mux_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_mux_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_MUX_SDIO_MAX_NFY_SZE), + .bInterval = 1 << RMNET_MUX_SDIO_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_mux_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_mux_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_mux_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_mux_interface_desc, + (struct usb_descriptor_header *) &rmnet_mux_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_mux_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_mux_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_mux_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_MUX_SDIO_MAX_NFY_SZE), + .bInterval = RMNET_MUX_SDIO_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_mux_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_mux_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_mux_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_mux_interface_desc, + (struct usb_descriptor_header *) &rmnet_mux_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_mux_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_mux_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_mux_string_defs[] = { + [0].s = "RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_mux_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_mux_string_defs, +}; + +static struct usb_gadget_strings *rmnet_mux_strings[] = { + &rmnet_mux_string_table, + NULL, +}; + +static struct rmnet_mux_ctrl_pkt *rmnet_mux_alloc_ctrl_pkt(unsigned len, + gfp_t flags) +{ + struct rmnet_mux_ctrl_pkt *cpkt; + + cpkt = kzalloc(sizeof(struct rmnet_mux_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_mux_free_ctrl_pkt(struct rmnet_mux_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_mux_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_mux_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static int rmnet_mux_sdio_rx_submit(struct rmnet_mux_dev *dev, + struct usb_request *req, gfp_t gfp_flags) +{ + struct sk_buff *skb; + int retval; + + skb = alloc_skb(RMNET_MUX_SDIO_RX_REQ_SIZE + RMNET_MUX_SDIO_HDR, + gfp_flags); + if (skb == NULL) + return -ENOMEM; + skb_reserve(skb, RMNET_MUX_SDIO_HDR); + + req->buf = skb->data; + req->length = RMNET_MUX_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_mux_sdio_start_rx(struct rmnet_mux_dev *dev) +{ + struct rmnet_mux_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_mux_sdio_rx_submit(dev, req, GFP_KERNEL); + spin_lock_irqsave(&dev->lock, flags); + + if (status) { + ERROR(cdev, "rmnet_mux 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_mux_sdio_start_tx(struct rmnet_mux_dev *dev) +{ + unsigned long flags; + int status; + struct sk_buff *skb; + struct usb_request *req; + struct rmnet_mux_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_mux_free_req(dev->epin, req); + dev_kfree_skb_any(skb); + } + break; + } + dev->dpkts_tolaptop++; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_mux_sdio_data_receive_cb(void *priv, struct sk_buff *skb) +{ + struct rmnet_mux_dev *dev = priv; + struct rmnet_mux_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 > mux_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_mux_sdio_start_tx(dev); +} + +static void rmnet_mux_sdio_data_write_done(void *priv, struct sk_buff *skb) +{ + struct rmnet_mux_dev *dev = priv; + struct rmnet_mux_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 >= mux_sdio_rx_fctrl_dis_thld) { + spin_unlock(&dev->lock); + return; + } + spin_unlock(&dev->lock); + + rmnet_mux_sdio_start_rx(dev); +} + +static void rmnet_mux_sdio_data_rx_work(struct work_struct *w) +{ + struct rmnet_mux_dev *dev = container_of(w, struct rmnet_mux_dev, + sdio_dev.data_rx_work); + struct rmnet_mux_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_mux_sdio_data_ch, skb); + spin_lock_irqsave(&dev->lock, flags); + if (ret < 0) { + ERROR(cdev, "rmnet_mux 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_mux_sdio_complete_epout(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_mux_dev *dev = ep->driver_data; + struct rmnet_mux_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_GADGET_XPORT_UNDEF) { + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_mux_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_mux_free_req(ep, req); + return; + default: + /* unexpected failure */ + ERROR(cdev, "RMNET_MUX %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 >= mux_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_mux_sdio_rx_submit(dev, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "rmnet_mux data rx enqueue err %d\n", status); + list_add_tail(&req->list, &sdio_dev->rx_idle); + } +} + +static void +rmnet_mux_sdio_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_mux_dev *dev = ep->driver_data; + struct rmnet_mux_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_GADGET_XPORT_UNDEF) { + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_mux_free_req(ep, req); + return; + } + + switch (status) { + case 0: + /* successful completion */ + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + break; + default: + ERROR(cdev, "rmnet_mux 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_mux_sdio_start_tx(dev); +} + +static int rmnet_mux_sdio_enable(struct rmnet_mux_dev *dev) +{ + struct rmnet_mux_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_MUX_SDIO_RX_REQ_MAX; i++) { + req = rmnet_mux_alloc_req(dev->epout, 0, GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->complete = rmnet_mux_sdio_complete_epout; + list_add_tail(&req->list, &sdio_dev->rx_idle); + } + for (i = 0; i < RMNET_MUX_SDIO_TX_REQ_MAX; i++) { + req = rmnet_mux_alloc_req(dev->epin, 0, GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->complete = rmnet_mux_sdio_complete_epin; + list_add_tail(&req->list, &sdio_dev->tx_idle); + } + + rmnet_mux_sdio_start_rx(dev); + return 0; +} + +static void rmnet_mux_smd_start_rx(struct rmnet_mux_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_mux_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_mux_smd_data_tx_tlet(unsigned long arg) +{ + struct rmnet_mux_dev *dev = (struct rmnet_mux_dev *) arg; + struct rmnet_mux_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_mux 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_mux_smd_data_rx_tlet(unsigned long arg) +{ + struct rmnet_mux_dev *dev = (struct rmnet_mux_dev *) arg; + struct rmnet_mux_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_mux 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_mux 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_mux_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_mux_smd_complete_epout(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_mux_dev *dev = req->context; + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + int ret; + + if (dev->xport == USB_GADGET_XPORT_UNDEF) { + rmnet_mux_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_MUX %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_mux 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_mux_smd_start_rx(dev); + return; + } +queue_req: + list_add_tail(&req->list, &smd_dev->rx_queue); + spin_unlock(&dev->lock); +} + +static void rmnet_mux_smd_complete_epin(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_mux_dev *dev = req->context; + struct rmnet_mux_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_GADGET_XPORT_UNDEF) { + rmnet_mux_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_mux 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_mux_smd_notify(void *priv, unsigned event) +{ + struct rmnet_mux_dev *dev = priv; + struct rmnet_mux_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(RMNET_MUX_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(RMNET_MUX_CH_OPENED, &smd_info->flags); + break; + } +} + +static int rmnet_mux_smd_enable(struct rmnet_mux_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + int i, ret; + struct usb_request *req; + + if (test_bit(RMNET_MUX_CH_OPENED, &smd_dev->smd_data.flags)) + goto smd_alloc_req; + + ret = smd_open(rmnet_mux_smd_data_ch, &smd_dev->smd_data.ch, + dev, rmnet_mux_smd_notify); + if (ret) { + ERROR(cdev, "Unable to open data smd channel\n"); + return ret; + } + + wait_event(smd_dev->smd_data.wait, test_bit(RMNET_MUX_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_MUX_SMD_RX_REQ_MAX; i++) { + req = rmnet_mux_alloc_req(dev->epout, RMNET_MUX_SMD_RX_REQ_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->length = RMNET_MUX_SMD_TXN_MAX; + req->context = dev; + req->complete = rmnet_mux_smd_complete_epout; + list_add_tail(&req->list, &smd_dev->rx_idle); + } + + for (i = 0; i < RMNET_MUX_SMD_TX_REQ_MAX; i++) { + req = rmnet_mux_alloc_req(dev->epin, RMNET_MUX_SMD_TX_REQ_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->context = dev; + req->complete = rmnet_mux_smd_complete_epin; + list_add_tail(&req->list, &smd_dev->tx_idle); + } + + rmnet_mux_smd_start_rx(dev); + return 0; +} + +static void rmnet_mux_notify_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_mux_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_mux 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_mux_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_mux notify ep enqueue error %d\n", status); + } +} + +#define MAX_CTRL_PKT_SIZE 4096 + +static void rmnet_mux_response_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_mux_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_mux %s response error %d, %d/%d\n", + ep->name, req->status, + req->actual, req->length); + } +} + +static void rmnet_mux_command_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct rmnet_mux_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_mux_ctrl_pkt *cpkt; + int len = req->actual; + + if (req->status < 0) { + ERROR(cdev, "rmnet_mux command error %d\n", req->status); + return; + } + + cpkt = rmnet_mux_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); + rmnet_mux_free_ctrl_pkt(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_mux_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, + function); + struct rmnet_mux_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_mux_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: + ret = w_length; + req->complete = rmnet_mux_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_mux_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_mux_response_complete; + req->context = dev; + rmnet_mux_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 & RMNET_MUX_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_mux 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_mux ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static void rmnet_mux_free_buf(struct rmnet_mux_dev *dev) +{ + struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; + struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + struct rmnet_mux_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_mux_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_mux_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_mux_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_mux_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_mux_free_req(dev->epin, req); + } + + pool = &ctrl_dev->tx_q; + while (!list_empty(pool)) { + cpkt = list_first_entry(pool, struct rmnet_mux_ctrl_pkt, list); + list_del(&cpkt->list); + rmnet_mux_free_ctrl_pkt(cpkt); + ctrl_dev->tx_len--; + } + + pool = &ctrl_dev->rx_q; + while (!list_empty(pool)) { + cpkt = list_first_entry(pool, struct rmnet_mux_ctrl_pkt, list); + list_del(&cpkt->list); + rmnet_mux_free_ctrl_pkt(cpkt); + ctrl_dev->rx_len--; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_mux_disconnect_work(struct work_struct *w) +{ + struct rmnet_mux_dev *dev = container_of(w, struct rmnet_mux_dev, + disconnect_work); + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + + if (dev->xport == USB_GADGET_XPORT_SMD) { + tasklet_kill(&smd_dev->smd_data.rx_tlet); + tasklet_kill(&smd_dev->smd_data.tx_tlet); + } + + rmnet_mux_free_buf(dev); + dev->xport = 0; + + /* wakeup read thread */ + wake_up(&ctrl_dev->tx_wait_q); +} + +static void rmnet_mux_suspend(struct usb_function *f) +{ + struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, + function); + struct rmnet_mux_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_mux_disable(struct usb_function *f) +{ + struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, + function); + struct rmnet_mux_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_mux_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_mux_open_sdio_work(struct work_struct *w) +{ + struct rmnet_mux_dev *dev = + container_of(w, struct rmnet_mux_dev, sdio_dev.open_work.work); + struct rmnet_mux_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_mux_sdio_data_ch, dev, + rmnet_mux_sdio_data_receive_cb, + rmnet_mux_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_mux sdio channels are open retry_cnt:%d\n", + __func__, retry_cnt); + retry_cnt = 0; + return; +} + +static int rmnet_mux_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, + function); + struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + int ret = 0; + + /* allocate notification */ + dev->notify_req = rmnet_mux_alloc_req(dev->epnotify, + RMNET_MUX_SDIO_MAX_NFY_SZE, GFP_ATOMIC); + + if (IS_ERR(dev->notify_req)) + return PTR_ERR(dev->notify_req); + + dev->notify_req->complete = rmnet_mux_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_MUX_SDIO_MAX_NFY_SZE; + + /* Enable epin */ + dev->epin->driver_data = dev; + ret = config_ep_by_speed(cdev->gadget, f, dev->epin); + if (ret) { + dev->epin->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->epin->name, ret); + return ret; + } + ret = usb_ep_enable(dev->epin); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epin->name, ret); + return ret; + } + + /* Enable epout */ + dev->epout->driver_data = dev; + ret = config_ep_by_speed(cdev->gadget, f, dev->epout); + if (ret) { + dev->epout->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + ret = usb_ep_enable(dev->epout); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + + /* Enable epnotify */ + ret = config_ep_by_speed(cdev->gadget, f, dev->epnotify); + if (ret) { + dev->epnotify->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->epnotify->name, ret); + usb_ep_disable(dev->epin); + usb_ep_disable(dev->epout); + return ret; + } + ret = usb_ep_enable(dev->epnotify); + 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; + } + + 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 rmnet_mux_dev *dev = rmux_dev; + int value; + enum transport_type given_xport; + enum transport_type t; + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + struct rmnet_mux_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_GADGET_XPORT_SDIO; + else + given_xport = USB_GADGET_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_mux: TransportRequested: %s\n", + xport_to_str(given_xport)); + + /* prevent any other pkts to/from usb */ + t = dev->xport; + dev->xport = USB_GADGET_XPORT_UNDEF; + if (t != USB_GADGET_XPORT_UNDEF) { + usb_ep_fifo_flush(dev->epin); + usb_ep_fifo_flush(dev->epout); + } + + switch (t) { + case USB_GADGET_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_mux_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_mux_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_GADGET_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_mux_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_mux_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_mux_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_GADGET_XPORT_SDIO: + rmnet_mux_sdio_enable(dev); + break; + case USB_GADGET_XPORT_SMD: + rmnet_mux_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_mux_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, + function); + struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; + 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_mux_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_mux_fs_in_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epin = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_mux_fs_out_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epout = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_mux_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_mux_hs_in_desc.bEndpointAddress = + rmnet_mux_fs_in_desc.bEndpointAddress; + rmnet_mux_hs_out_desc.bEndpointAddress = + rmnet_mux_fs_out_desc.bEndpointAddress; + rmnet_mux_hs_notify_desc.bEndpointAddress = + rmnet_mux_fs_notify_desc.bEndpointAddress; + } + + 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_mux_smd_init(struct rmnet_mux_smd_dev *smd_dev) +{ + struct rmnet_mux_dev *dev = container_of(smd_dev, + struct rmnet_mux_dev, smd_dev); + + atomic_set(&smd_dev->smd_data.rx_pkt, 0); + tasklet_init(&smd_dev->smd_data.rx_tlet, rmnet_mux_smd_data_rx_tlet, + (unsigned long) dev); + tasklet_init(&smd_dev->smd_data.tx_tlet, rmnet_mux_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_mux_sdio_init(struct rmnet_mux_sdio_dev *sdio_dev) +{ + INIT_WORK(&sdio_dev->data_rx_work, rmnet_mux_sdio_data_rx_work); + + INIT_DELAYED_WORK(&sdio_dev->open_work, rmnet_mux_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_mux_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct rmnet_mux_dev *dev = container_of(f, struct rmnet_mux_dev, + function); + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + + smd_dev->smd_data.flags = 0; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t rmnet_mux_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_mux_dev *dev = file->private_data; + struct rmnet_mux_sdio_dev *sdio_dev = &dev->sdio_dev; + struct rmnet_mux_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 rmnet_mux_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_mux_dev *dev = file->private_data; + struct rmnet_mux_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 dbg_rmnet_mux_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +const struct file_operations rmnet_mux_svlte_debug_stats_ops = { + .open = dbg_rmnet_mux_open, + .read = rmnet_mux_read_stats, + .write = rmnet_mux_reset_stats, +}; + +struct dentry *dent_rmnet_mux; + +static void rmnet_mux_debugfs_init(struct rmnet_mux_dev *dev) +{ + + dent_rmnet_mux = debugfs_create_dir("usb_rmnet_mux", 0); + if (IS_ERR(dent_rmnet_mux)) + return; + + debugfs_create_file("status", 0444, dent_rmnet_mux, dev, + &rmnet_mux_svlte_debug_stats_ops); +} +#else +static void rmnet_mux_debugfs_init(struct rmnet_mux_dev *dev) {} +#endif + +int usb_rmnet_mux_ctrl_open(struct inode *inode, struct file *fp) +{ + struct rmnet_mux_dev *dev = rmux_dev; + struct rmnet_mux_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_mux_ctrl_release(struct inode *inode, struct file *fp) +{ + struct rmnet_mux_dev *dev = fp->private_data; + struct rmnet_mux_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_mux_ctrl_read(struct file *fp, + char __user *buf, + size_t count, + loff_t *ppos) +{ + struct rmnet_mux_dev *dev = fp->private_data; + struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_mux_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_mux_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_mux_free_ctrl_pkt(cpkt); + + if (ret) + return ret; + + return count; +} + +ssize_t usb_rmnet_mux_ctrl_write(struct file *fp, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + struct rmnet_mux_dev *dev = fp->private_data; + struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_mux_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_mux_alloc_ctrl_pkt(count, GFP_KERNEL); + if (!cpkt) { + pr_err("%s: cannot allocate rmnet_mux 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_mux_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_MUX_CTRL_GET_DTR _IOR(0xFE, 0, int) +static long +usb_rmnet_mux_ctrl_ioctl(struct file *fp, unsigned c, unsigned long value) +{ + struct rmnet_mux_dev *dev = fp->private_data; + struct rmnet_mux_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + unsigned long *temp = (unsigned long *)value; + int ret = 0; + + if (c != RMNET_MUX_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_mux_ctrl_fops = { + .owner = THIS_MODULE, + .open = usb_rmnet_mux_ctrl_open, + .release = usb_rmnet_mux_ctrl_release, + .read = usb_rmnet_mux_ctrl_read, + .write = usb_rmnet_mux_ctrl_write, + .unlocked_ioctl = usb_rmnet_mux_ctrl_ioctl, +}; + +static struct miscdevice rmnet_mux_ctrl_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "rmnet_mux_ctrl", + .fops = &rmnet_mux_ctrl_fops, +}; + +static int rmnet_mux_ctrl_device_init(struct rmnet_mux_dev *dev) +{ + int ret; + struct rmnet_mux_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_mux_ctrl_dev); + if (ret) { + pr_err("%s: failed to register misc device\n", __func__); + return ret; + } + + return 0; +} + +static int rmnet_smd_sdio_function_add(struct usb_configuration *c) +{ + struct rmnet_mux_dev *dev = rmux_dev; + + if (!dev) + return -ENODEV; + + pr_debug("rmnet_smd_sdio_function_add\n"); + + dev->function.name = "rmnet_smd_sdio"; + dev->function.strings = rmnet_mux_strings; + dev->function.descriptors = rmnet_mux_fs_function; + dev->function.hs_descriptors = rmnet_mux_hs_function; + dev->function.bind = rmnet_mux_bind; + dev->function.unbind = rmnet_mux_unbind; + dev->function.setup = rmnet_mux_setup; + dev->function.set_alt = rmnet_mux_set_alt; + dev->function.disable = rmnet_mux_disable; + dev->function.suspend = rmnet_mux_suspend; + + return usb_add_function(c, &dev->function); +} + +static int rmnet_smd_sdio_init(void) +{ + struct rmnet_mux_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + rmux_dev = dev; + + dev->wq = create_singlethread_workqueue("k_rmnet_mux_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_mux_disconnect_work); + rmnet_mux_smd_init(&dev->smd_dev); + rmnet_mux_sdio_init(&dev->sdio_dev); + + ret = rmnet_mux_ctrl_device_init(dev); + if (ret) { + pr_debug("%s: rmnet_mux_ctrl_device_init failed, err:%d\n", + __func__, ret); + goto free_wq; + } + + rmnet_mux_debugfs_init(dev); + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} + +static void rmnet_smd_sdio_cleanup(void) +{ + struct rmnet_mux_dev *dev = rmux_dev; + struct rmnet_mux_smd_dev *smd_dev = &dev->smd_dev; + + debugfs_remove_recursive(dent_rmnet_mux); + misc_deregister(&rmnet_mux_ctrl_dev); + smd_close(smd_dev->smd_data.ch); + destroy_workqueue(dev->wq); + kfree(dev); +} diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 1413df9b0a29588a42ece8d4b6ce86f787a16c1f..6e807cb3033d19505160787196417f7d00128847 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -869,14 +869,14 @@ rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], if (!can_support_rndis(c) || !ethaddr) return -EINVAL; + /* setup RNDIS itself */ + status = rndis_init(); + if (status < 0) + return status; + /* maybe allocate device-global string IDs */ if (rndis_string_defs[0].id == 0) { - /* ... and setup RNDIS itself */ - status = rndis_init(); - if (status < 0) - return status; - /* control interface label */ status = usb_string_id(c->cdev); if (status < 0) diff --git a/drivers/usb/gadget/f_serial.c b/drivers/usb/gadget/f_serial.c index 07197d63d9b1d056e75c79cd85261059279715fa..7b6acc622e5ac8b49924e5f73a146358fd2bb38a 100644 --- a/drivers/usb/gadget/f_serial.c +++ b/drivers/usb/gadget/f_serial.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "u_serial.h" #include "gadget_chips.h" @@ -26,74 +27,199 @@ * CDC ACM driver. However, for many purposes it's just as functional * if you can arrange appropriate host side drivers. */ +#define GSERIAL_NO_PORTS 3 struct f_gser { struct gserial port; u8 data_id; u8 port_num; + + u8 online; + enum transport_type transport; + +#ifdef CONFIG_MODEM_SUPPORT + u8 pending; + spinlock_t lock; + struct usb_ep *notify; + 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 }; +static unsigned int no_tty_ports; +static unsigned int no_sdio_ports; +static unsigned int no_smd_ports; +static unsigned int no_hsic_sports; +static unsigned int no_hsuart_sports; +static unsigned int nr_ports; + +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_XPORT_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 = { +static struct usb_endpoint_descriptor gser_fs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor gser_fs_out_desc __initdata = { +static struct usb_endpoint_descriptor gser_fs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *gser_fs_function[] __initdata = { +static struct usb_descriptor_header *gser_fs_function[] = { (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 = { +static struct usb_endpoint_descriptor gser_hs_in_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_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 = { +static struct usb_descriptor_header *gser_hs_function[] = { (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, @@ -144,18 +270,279 @@ static struct usb_gadget_strings *gser_strings[] = { NULL, }; +static int gport_setup(struct usb_configuration *c) +{ + int ret = 0; + int port_idx; + int i; + + pr_debug("%s: no_tty_ports: %u no_sdio_ports: %u" + " no_smd_ports: %u no_hsic_sports: %u no_hsuart_ports: %u nr_ports: %u\n", + __func__, no_tty_ports, no_sdio_ports, no_smd_ports, + no_hsic_sports, no_hsuart_sports, 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); + if (no_hsic_sports) { + port_idx = ghsic_data_setup(no_hsic_sports, USB_GADGET_SERIAL); + if (port_idx < 0) + return port_idx; + + for (i = 0; i < nr_ports; i++) { + if (gserial_ports[i].transport == + USB_GADGET_XPORT_HSIC) { + gserial_ports[i].client_port_num = port_idx; + port_idx++; + } + } + + /*clinet port num is same for data setup and ctrl setup*/ + ret = ghsic_ctrl_setup(no_hsic_sports, USB_GADGET_SERIAL); + if (ret < 0) + return ret; + return 0; + } + if (no_hsuart_sports) { + port_idx = ghsuart_data_setup(no_hsuart_sports, + USB_GADGET_SERIAL); + if (port_idx < 0) + return port_idx; + + for (i = 0; i < nr_ports; i++) { + if (gserial_ports[i].transport == + USB_GADGET_XPORT_HSUART) { + gserial_ports[i].client_port_num = port_idx; + port_idx++; + } + } + + return 0; + } + return ret; +} + +static int gport_connect(struct f_gser *gser) +{ + unsigned port_num; + int ret; + + pr_debug("%s: transport: %s f_gser: %p gserial: %p port_num: %d\n", + __func__, xport_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_XPORT_TTY: + gserial_connect(&gser->port, port_num); + break; + case USB_GADGET_XPORT_SDIO: + gsdio_connect(&gser->port, port_num); + break; + case USB_GADGET_XPORT_SMD: + gsmd_connect(&gser->port, port_num); + break; + case USB_GADGET_XPORT_HSIC: + ret = ghsic_ctrl_connect(&gser->port, port_num); + if (ret) { + pr_err("%s: ghsic_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + ret = ghsic_data_connect(&gser->port, port_num); + if (ret) { + pr_err("%s: ghsic_data_connect failed: err:%d\n", + __func__, ret); + ghsic_ctrl_disconnect(&gser->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_HSUART: + ret = ghsuart_data_connect(&gser->port, port_num); + if (ret) { + pr_err("%s: ghsuart_data_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_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__, xport_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_XPORT_TTY: + gserial_disconnect(&gser->port); + break; + case USB_GADGET_XPORT_SDIO: + gsdio_disconnect(&gser->port, port_num); + break; + case USB_GADGET_XPORT_SMD: + gsmd_disconnect(&gser->port, port_num); + break; + case USB_GADGET_XPORT_HSIC: + ghsic_ctrl_disconnect(&gser->port, port_num); + ghsic_data_disconnect(&gser->port, port_num); + break; + case USB_GADGET_XPORT_HSUART: + ghsuart_data_disconnect(&gser->port, port_num); + break; + default: + pr_err("%s: Un-supported transport:%s\n", __func__, + xport_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); + } + + if (!gser->notify->desc) { + if (config_ep_by_speed(cdev->gadget, f, gser->notify)) { + gser->notify->desc = NULL; + return -EINVAL; + } + } + rc = usb_ep_enable(gser->notify); + + 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); } if (!gser->port.in->desc || !gser->port.out->desc) { DBG(cdev, "activate generic ttyGS%d\n", gser->port_num); @@ -166,8 +553,11 @@ static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) return -EINVAL; } } - gserial_connect(&gser->port, gser->port_num); - return 0; + + gport_connect(gser); + + gser->online = 1; + return rc; } static void gser_disable(struct usb_function *f) @@ -176,14 +566,185 @@ 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 */ -static int __init +static int gser_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; @@ -213,9 +774,29 @@ 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); + if (!f->descriptors) + goto fail; + /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at * both speeds @@ -225,9 +806,17 @@ 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); + + if (!f->hs_descriptors) + goto fail; + } if (gadget_is_superspeed(c->cdev->gadget)) { gser_ss_in_desc.bEndpointAddress = @@ -249,6 +838,16 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) return 0; fail: + if (f->descriptors) + usb_free_descriptors(f->descriptors); +#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; @@ -263,11 +862,17 @@ 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); if (gadget_is_superspeed(c->cdev->gadget)) usb_free_descriptors(f->ss_descriptors); usb_free_descriptors(f->descriptors); +#ifdef CONFIG_MODEM_SUPPORT + gs_free_req(gser->notify, gser->notify_req); +#endif kfree(func_to_gser(f)); } @@ -283,7 +888,7 @@ gser_unbind(struct usb_configuration *c, struct usb_function *f) * handle all the ones it binds. Caller is also responsible * for calling @gserial_cleanup() before module unload. */ -int __init gser_bind_config(struct usb_configuration *c, u8 port_num) +int gser_bind_config(struct usb_configuration *c, u8 port_num) { struct f_gser *gser; int status; @@ -305,6 +910,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"; @@ -313,9 +921,77 @@ 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 three ports for now */ + if (port_num == 0) + gser->port.func.name = "modem"; + else if (port_num == 1) + gser->port.func.name = "nmea"; + else + gser->port.func.name = "modem2"; + 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; } + +/** + * gserial_init_port - bind a gserial_port to its transport + */ +static int gserial_init_port(int port_num, const char *name) +{ + enum transport_type transport; + + if (port_num >= GSERIAL_NO_PORTS) + return -ENODEV; + + transport = str_to_xport(name); + pr_debug("%s, port:%d, transport:%s\n", __func__, + port_num, xport_to_str(transport)); + + gserial_ports[port_num].transport = transport; + gserial_ports[port_num].port_num = port_num; + + switch (transport) { + case USB_GADGET_XPORT_TTY: + gserial_ports[port_num].client_port_num = no_tty_ports; + no_tty_ports++; + break; + case USB_GADGET_XPORT_SDIO: + gserial_ports[port_num].client_port_num = no_sdio_ports; + no_sdio_ports++; + break; + case USB_GADGET_XPORT_SMD: + gserial_ports[port_num].client_port_num = no_smd_ports; + no_smd_ports++; + break; + case USB_GADGET_XPORT_HSIC: + /*client port number will be updated in gport_setup*/ + no_hsic_sports++; + break; + case USB_GADGET_XPORT_HSUART: + /*client port number will be updated in gport_setup*/ + no_hsuart_sports++; + break; + default: + pr_err("%s: Un-supported transport transport: %u\n", + __func__, gserial_ports[port_num].transport); + return -ENODEV; + } + + nr_ports++; + + return 0; +} diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index a8855d0b7f3bdf19fbdc76eab8a25992f07884b0..14873a0be165bf7b7c0686b859c923f5663e9817 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -29,6 +29,7 @@ #define gadget_is_at91(g) (!strcmp("at91_udc", (g)->name)) #define gadget_is_atmel_usba(g) (!strcmp("atmel_usba_udc", (g)->name)) #define gadget_is_ci13xxx_msm(g) (!strcmp("ci13xxx_msm", (g)->name)) +#define gadget_is_ci13xxx_msm_hsic(g) (!strcmp("ci13xxx_msm_hsic", (g)->name)) #define gadget_is_ci13xxx_pci(g) (!strcmp("ci13xxx_pci", (g)->name)) #define gadget_is_dummy(g) (!strcmp("dummy_udc", (g)->name)) #define gadget_is_dwc3(g) (!strcmp("dwc3-gadget", (g)->name)) @@ -38,6 +39,7 @@ #define gadget_is_imx(g) (!strcmp("imx_udc", (g)->name)) #define gadget_is_langwell(g) (!strcmp("langwell_udc", (g)->name)) #define gadget_is_m66592(g) (!strcmp("m66592_udc", (g)->name)) +#define gadget_is_msm72k(g) (!strcmp("msm72k_udc", (g)->name)) #define gadget_is_musbhdrc(g) (!strcmp("musb-hdrc", (g)->name)) #define gadget_is_net2272(g) (!strcmp("net2272", (g)->name)) #define gadget_is_net2280(g) (!strcmp("net2280", (g)->name)) @@ -118,6 +120,10 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x31; else if (gadget_is_dwc3(gadget)) return 0x32; + else if (gadget_is_msm72k(gadget)) + return 0x33; + else if (gadget_is_ci13xxx_msm_hsic(gadget)) + return 0x34; return -ENOENT; } diff --git a/drivers/usb/gadget/msm72k_udc.c b/drivers/usb/gadget/msm72k_udc.c new file mode 100644 index 0000000000000000000000000000000000000000..a025d95eccf13b384da9b5a8beb6f8f83861f6ac --- /dev/null +++ b/drivers/usb/gadget/msm72k_udc.c @@ -0,0 +1,2834 @@ +/* + * Driver for HighSpeed USB Client Controller in MSM7K + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2012, 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, phy) +#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 long dTD_update_fail_count; + unsigned long false_prime_fail_count; + unsigned actual_prime_fail_count; + + unsigned wedged:1; + /* pointers to DMA transfer list area */ + /* these are allocated from the usb_info dma space */ + struct ept_queue_head *head; + struct timer_list prime_timer; +}; + +/* PHY status check timer to monitor phy stuck up on reset */ +static struct timer_list phy_status_timer; + +static void ept_prime_timer_func(unsigned long data); +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)) +#define EPT_PRIME_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; + unsigned prime_fail_count; + unsigned long dTD_update_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 usb_phy *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 snprintf(buf, PAGE_SIZE, "%s\n", DRIVER_NAME); +} + +static ssize_t print_switch_state(struct switch_dev *sdev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%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 (usb_phy_io_write(ui->xceiv, 0xAA, 0x16) == -1) { + dev_dbg(&ui->pdev->dev, + "%s(): ulpi write timeout\n", __func__); + return -EIO; + } + + if (usb_phy_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) + usb_phy_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) + usb_phy_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 usb_phy_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; + } + setup_timer(&ept->prime_timer, ept_prime_timer_func, + (unsigned long) ept); + + } +} + +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 ept_prime_timer_func(unsigned long data) +{ + struct msm_endpoint *ept = (struct msm_endpoint *)data; + struct usb_info *ui = ept->ui; + unsigned n = 1 << ept->bit; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + + ept->false_prime_fail_count++; + if ((readl_relaxed(USB_ENDPTPRIME) & n)) { + /* + * ---- UNLIKELY --- + * May be hardware is taking long time to process the + * prime request. Or could be intermittent priming and + * previous dTD is not fired yet. + */ + mod_timer(&ept->prime_timer, EPT_PRIME_CHECK_DELAY); + goto out; + } + if (readl_relaxed(USB_ENDPTSTAT) & n) + goto out; + + /* clear speculative loads on item->info */ + rmb(); + if (ept->req && (ept->req->item->info & INFO_ACTIVE)) { + ui->prime_fail_count++; + ept->actual_prime_fail_count++; + pr_err("%s(): ept%d%s prime failed. ept: config: %x" + "active: %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); + writel_relaxed(n, USB_ENDPTPRIME); + mod_timer(&ept->prime_timer, EPT_PRIME_CHECK_DELAY); + } +out: + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void usb_ept_start(struct msm_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req = ept->req; + unsigned n = 1 << ept->bit; + + 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; + + /* flush buffers before priming ept */ + mb(); + /* during high throughput testing it is observed that + * ept stat bit is not set even though all the data + * structures are updated properly and ept prime bit + * is set. To workaround the issue, kick a timer and + * make decision on re-prime. We can do a busy loop here + * but it leads to high cpu usage. + */ + writel_relaxed(n, USB_ENDPTPRIME); + mod_timer(&ept->prime_timer, EPT_PRIME_CHECK_DELAY); +} + +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); + usb_phy_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 + /* USB hardware sometimes generate interrupt before + * 8 bytes of SETUP packet are written to system memory. + * This results in fetching wrong setup_data sometimes. + * TODO: Remove below workaround of adding 1us delay once + * it gets fixed in hardware. + */ + udelay(10); + + 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; + int req_dequeue = 1; + 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; + } + +dequeue: + /* 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) { + if (req_dequeue) { + req_dequeue = 0; + ui->dTD_update_fail_count++; + ept->dTD_update_fail_count++; + udelay(10); + goto dequeue; + } else { + break; + } + } + req_dequeue = 0; + + del_timer(&ept->prime_timer); + /* 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) +{ + del_timer(&ept->prime_timer); + 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) + usb_phy_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) + usb_phy_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 + */ + 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) + usb_phy_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) +{ + if (ret) + dev_dbg(&ui->pdev->dev, "usb_free(%d)\n", ret); + + usb_del_gadget_udc(&ui->gadget); + + if (ui->xceiv) + usb_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 + */ + usb_phy_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; + + usb_phy_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; + usb_phy_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 ssize_t debug_reprime_ep(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct usb_info *ui = file->private_data; + struct msm_endpoint *ept; + char kbuf[10]; + unsigned int ep_num, dir; + unsigned long flags; + unsigned n, i; + + memset(kbuf, 0, 10); + + if (copy_from_user(kbuf, ubuf, count > 10 ? 10 : count)) + return -EFAULT; + + if (sscanf(kbuf, "%u %u", &ep_num, &dir) != 2) + return -EINVAL; + + if (dir) + i = ep_num + 16; + else + i = ep_num; + + spin_lock_irqsave(&ui->lock, flags); + ept = ui->ept + i; + n = 1 << ept->bit; + + if ((readl_relaxed(USB_ENDPTPRIME) & n)) + goto out; + + if (readl_relaxed(USB_ENDPTSTAT) & n) + goto out; + + /* clear speculative loads on item->info */ + rmb(); + if (ept->req && (ept->req->item->info & INFO_ACTIVE)) { + pr_err("%s(): ept%d%s prime failed. ept: config: %x" + "active: %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); + writel_relaxed(n, USB_ENDPTPRIME); + } +out: + spin_unlock_irqrestore(&ui->lock, flags); + + return count; +} + +static char buffer[512]; +static ssize_t debug_prime_fail_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct usb_info *ui = file->private_data; + char *buf = buffer; + unsigned long flags; + struct msm_endpoint *ept; + int n; + int i = 0; + + spin_lock_irqsave(&ui->lock, flags); + 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 false_prime_count=%lu prime_fail_count=%d dtd_fail_count=%lu\n", + ept->num, (ept->flags & EPT_FLAG_IN) ? "in " : "out", + ept->false_prime_fail_count, + ept->actual_prime_fail_count, + ept->dTD_update_fail_count); + } + + i += scnprintf(buf + i, PAGE_SIZE - i, + "dTD_update_fail count: %lu\n", + ui->dTD_update_fail_count); + + i += scnprintf(buf + i, PAGE_SIZE - i, + "prime_fail count: %d\n", ui->prime_fail_count); + + spin_unlock_irqrestore(&ui->lock, flags); + + return simple_read_from_buffer(ubuf, count, ppos, buf, i); +} + +static int debug_prime_fail_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +const struct file_operations prime_fail_ops = { + .open = debug_prime_fail_open, + .read = debug_prime_fail_read, + .write = debug_reprime_ep, +}; + +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); + debugfs_create_file("prime_fail_countt", 0666, dent, ui, + &prime_fail_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; + unsigned char ep_type; + + if (_ep == NULL || desc == NULL) + return -EINVAL; + + ept = to_msm_endpoint(_ep); + 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); + /* + * Clear descriptors here. Otherwise previous descriptors + * will be used by function drivers upon next enumeration. + */ + _ep->desc = NULL; + 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; + } + del_timer(&ep->prime_timer); + /* 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 */ + usb_phy_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); + struct msm_otg *otg = to_msm_otg(ui->xceiv); + 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 || + atomic_read(&otg->chg_type) == USB_CHG_TYPE__WALLCHARGER) { + 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; + } + usb_phy_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 int msm72k_gadget_start(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)); +static int msm72k_gadget_stop(struct usb_gadget_driver *driver); + + +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, + .start = msm72k_gadget_start, + .stop = msm72k_gadget_stop +}; + +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; + usb_phy_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 = snprintf(buf, PAGE_SIZE, "%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 = snprintf(buf, PAGE_SIZE, "%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 = snprintf(buf, PAGE_SIZE, "%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 = usb_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.max_speed = USB_SPEED_HIGH; + 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 + + retval = usb_add_gadget_udc(&pdev->dev, &ui->gadget); + if (retval) + return usb_free(ui, retval); + + 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->otg, &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; +} + +static int msm72k_gadget_start(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct usb_info *ui = the_usb_info; + int retval, n; + + if (!driver + || driver->max_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; +} + +static int msm72k_gadget_stop(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; +} + + +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 int __exit msm72k_remove(struct platform_device *pdev) +{ + struct usb_info *ui = container_of(&pdev, struct usb_info, pdev); + + return usb_free(ui, 0); +} + +static struct platform_driver usb_driver = { + .probe = msm72k_probe, + .remove = msm72k_remove, + .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 0000000000000000000000000000000000000000..39686c4e05c0f48a908cc186ce9a7125f433f8be --- /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/rndis.c b/drivers/usb/gadget/rndis.c index 73a934a170d1a8ae891f873fb24eab383697407a..0cb21218a17fafea923fd43c587e23eab378b844 100644 --- a/drivers/usb/gadget/rndis.c +++ b/drivers/usb/gadget/rndis.c @@ -1146,11 +1146,15 @@ static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; #endif /* CONFIG_USB_GADGET_DEBUG_FILES */ +static bool rndis_initialized; int rndis_init(void) { u8 i; + if (rndis_initialized) + return 0; + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { #ifdef CONFIG_USB_GADGET_DEBUG_FILES char name [20]; @@ -1177,6 +1181,7 @@ int rndis_init(void) INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue)); } + rndis_initialized = true; return 0; } @@ -1185,7 +1190,13 @@ void rndis_exit(void) #ifdef CONFIG_USB_GADGET_DEBUG_FILES u8 i; char name[20]; +#endif + if (!rndis_initialized) + return; + rndis_initialized = false; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { sprintf(name, NAME_TEMPLATE, i); remove_proc_entry(name, NULL); diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 8081ca3a70a231272532f75de1101bdebaebccd0..3c57df4e18a22d6140045df3efc553c6cad1896f 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -207,6 +207,17 @@ struct fsg_lun { unsigned int blkbits; /* Bits of logical block size of bound block device */ unsigned int blksize; /* logical block size of bound block device */ struct device dev; +#ifdef CONFIG_USB_MSC_PROFILING + spinlock_t lock; + struct { + + unsigned long rbytes; + unsigned long wbytes; + ktime_t rtime; + ktime_t wtime; + } perf; + +#endif }; #define fsg_lun_is_open(curlun) ((curlun)->filp != NULL) @@ -221,6 +232,9 @@ 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 */ +#ifdef CONFIG_USB_CSW_HACK +#define fsg_num_buffers 4 +#else #ifdef CONFIG_USB_GADGET_DEBUG_FILES static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; @@ -236,6 +250,7 @@ MODULE_PARM_DESC(num_buffers, "Number of pipeline buffers"); #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS #endif /* CONFIG_USB_DEBUG */ +#endif /* CONFIG_USB_CSW_HACK */ /* check if fsg_num_buffers is within a valid range */ static inline int fsg_num_buffers_validate(void) @@ -786,6 +801,43 @@ static ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%u\n", curlun->nofua); } +#ifdef CONFIG_USB_MSC_PROFILING +static ssize_t fsg_show_perf(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + unsigned long rbytes, wbytes; + int64_t rtime, wtime; + + spin_lock(&curlun->lock); + rbytes = curlun->perf.rbytes; + wbytes = curlun->perf.wbytes; + rtime = ktime_to_us(curlun->perf.rtime); + wtime = ktime_to_us(curlun->perf.wtime); + spin_unlock(&curlun->lock); + + return snprintf(buf, PAGE_SIZE, "Write performance :" + "%lu bytes in %lld microseconds\n" + "Read performance :" + "%lu bytes in %lld microseconds\n", + wbytes, wtime, rbytes, rtime); +} +static ssize_t fsg_store_perf(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + int value; + + sscanf(buf, "%d", &value); + if (!value) { + spin_lock(&curlun->lock); + memset(&curlun->perf, 0, sizeof(curlun->perf)); + spin_unlock(&curlun->lock); + } + + return count; +} +#endif static ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, char *buf) { @@ -872,10 +924,16 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, struct rw_semaphore *filesem = dev_get_drvdata(dev); int rc = 0; + +#ifndef CONFIG_USB_ANDROID_MASS_STORAGE + /* disabled in android because we need to allow closing the backing file + * if the media was removed + */ if (curlun->prevent_medium_removal && fsg_lun_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') diff --git a/drivers/usb/gadget/u_bam.c b/drivers/usb/gadget/u_bam.c new file mode 100644 index 0000000000000000000000000000000000000000..d379c6643d65db2fd47fc6aa6a380527d19db5cb --- /dev/null +++ b/drivers/usb/gadget/u_bam.c @@ -0,0 +1,1262 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "u_rmnet.h" + +#define BAM_N_PORTS 1 +#define BAM2BAM_N_PORTS 3 + +static struct workqueue_struct *gbam_wq; +static int n_bam_ports; +static int n_bam2bam_ports; +static unsigned n_tx_req_queued; +static unsigned bam_ch_ids[] = { 8 }; + +static const char *bam_ch_names[] = { "bam_dmux_ch_8" }; + +#define BAM_PENDING_LIMIT 220 +#define BAM_MUX_TX_PKT_DROP_THRESHOLD 1000 +#define BAM_MUX_RX_PKT_FCTRL_EN_TSHOLD 500 +#define BAM_MUX_RX_PKT_FCTRL_DIS_TSHOLD 300 +#define BAM_MUX_RX_PKT_FLOW_CTRL_SUPPORT 1 + +#define BAM_MUX_HDR 8 + +#define BAM_MUX_RX_Q_SIZE 16 +#define BAM_MUX_TX_Q_SIZE 200 +#define BAM_MUX_RX_REQ_SIZE (2048 - BAM_MUX_HDR) + +#define DL_INTR_THRESHOLD 20 + +unsigned int bam_mux_tx_pkt_drop_thld = BAM_MUX_TX_PKT_DROP_THRESHOLD; +module_param(bam_mux_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int bam_mux_rx_fctrl_en_thld = BAM_MUX_RX_PKT_FCTRL_EN_TSHOLD; +module_param(bam_mux_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int bam_mux_rx_fctrl_support = BAM_MUX_RX_PKT_FLOW_CTRL_SUPPORT; +module_param(bam_mux_rx_fctrl_support, uint, S_IRUGO | S_IWUSR); + +unsigned int bam_mux_rx_fctrl_dis_thld = BAM_MUX_RX_PKT_FCTRL_DIS_TSHOLD; +module_param(bam_mux_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int bam_mux_tx_q_size = BAM_MUX_TX_Q_SIZE; +module_param(bam_mux_tx_q_size, uint, S_IRUGO | S_IWUSR); + +unsigned int bam_mux_rx_q_size = BAM_MUX_RX_Q_SIZE; +module_param(bam_mux_rx_q_size, uint, S_IRUGO | S_IWUSR); + +unsigned int bam_mux_rx_req_size = BAM_MUX_RX_REQ_SIZE; +module_param(bam_mux_rx_req_size, uint, S_IRUGO | S_IWUSR); + +unsigned int dl_intr_threshold = DL_INTR_THRESHOLD; +module_param(dl_intr_threshold, uint, S_IRUGO | S_IWUSR); + +#define BAM_CH_OPENED BIT(0) +#define BAM_CH_READY BIT(1) + +struct bam_ch_info { + unsigned long flags; + 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; + struct work_struct write_tohost_w; + + struct usb_request *rx_req; + struct usb_request *tx_req; + + u8 src_pipe_idx; + u8 dst_pipe_idx; + u8 connection_idx; + + /* 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_ul; + spinlock_t port_lock_dl; + + struct grmnet *port_usb; + struct grmnet *gr; + + struct bam_ch_info data_ch; + + struct work_struct connect_w; + struct work_struct disconnect_w; +}; + +static struct bam_portmaster { + struct gbam_port *port; + struct platform_driver pdrv; +} bam_ports[BAM_N_PORTS]; + +struct gbam_port *bam2bam_ports[BAM2BAM_N_PORTS]; +static void gbam_start_rx(struct gbam_port *port); +static void gbam_start_endless_rx(struct gbam_port *port); +static void gbam_start_endless_tx(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_dl, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_dl, 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_dl, flags); + return; + } + req = list_first_entry(&d->tx_idle, + struct usb_request, + list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + n_tx_req_queued++; + if (n_tx_req_queued == dl_intr_threshold) { + req->no_interrupt = 0; + n_tx_req_queued = 0; + } else { + req->no_interrupt = 1; + } + + list_del(&req->list); + + spin_unlock(&port->port_lock_dl); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&port->port_lock_dl); + 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_dl, flags); +} + +static void gbam_write_data_tohost_w(struct work_struct *w) +{ + struct bam_ch_info *d; + struct gbam_port *port; + + d = container_of(w, struct bam_ch_info, write_tohost_w); + port = d->port; + + gbam_write_data_tohost(port); +} + +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_dl, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_dl, flags); + dev_kfree_skb_any(skb); + return; + } + + if (d->tx_skb_q.qlen > bam_mux_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_dl, flags); + dev_kfree_skb_any(skb); + return; + } + + __skb_queue_tail(&d->tx_skb_q, skb); + spin_unlock_irqrestore(&port->port_lock_dl, 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_ul, 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); + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + queue_work(gbam_wq, &d->write_tobam_w); +} + +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; + int qlen; + + d = container_of(w, struct bam_ch_info, write_tobam_w); + port = d->port; + + spin_lock_irqsave(&port->port_lock_ul, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + + while (d->pending_with_bam < BAM_PENDING_LIMIT) { + skb = __skb_dequeue(&d->rx_skb_q); + if (!skb) + break; + + 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_ul, flags); + ret = msm_bam_dmux_write(d->id, skb); + spin_lock_irqsave(&port->port_lock_ul, 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; + } + } + + qlen = d->rx_skb_q.qlen; + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (qlen < BAM_MUX_RX_PKT_FCTRL_DIS_TSHOLD) + gbam_start_rx(port); +} +/*-------------------------------------------------------------*/ + +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_dl); + d = &port->data_ch; + list_add_tail(&req->list, &d->tx_idle); + spin_unlock(&port->port_lock_dl); + + queue_work(gbam_wq, &d->write_tohost_w); +} + +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_ul); + 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 (bam_mux_rx_fctrl_support && + d->rx_skb_q.qlen >= bam_mux_rx_fctrl_en_thld) { + + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + return; + } + spin_unlock(&port->port_lock_ul); + + skb = alloc_skb(bam_mux_rx_req_size + BAM_MUX_HDR, GFP_ATOMIC); + if (!skb) { + spin_lock(&port->port_lock_ul); + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + return; + } + skb_reserve(skb, BAM_MUX_HDR); + + req->buf = skb->data; + req->length = bam_mux_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_ul); + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + } +} + +static void gbam_endless_rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + int status = req->status; + + pr_debug("%s status: %d\n", __func__, status); +} + +static void gbam_endless_tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + int status = req->status; + + pr_debug("%s status: %d\n", __func__, status); +} + +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_ul, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + + d = &port->data_ch; + ep = port->port_usb->out; + + while (port->port_usb && !list_empty(&d->rx_idle)) { + + if (bam_mux_rx_fctrl_support && + d->rx_skb_q.qlen >= bam_mux_rx_fctrl_en_thld) + break; + + req = list_first_entry(&d->rx_idle, struct usb_request, list); + + skb = alloc_skb(bam_mux_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 = bam_mux_rx_req_size; + req->context = skb; + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock_irqsave(&port->port_lock_ul, 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_ul, flags); +} + +static void gbam_start_endless_rx(struct gbam_port *port) +{ + struct bam_ch_info *d = &port->data_ch; + int status; + + status = usb_ep_queue(port->port_usb->out, d->rx_req, GFP_ATOMIC); + if (status) + pr_err("%s: error enqueuing transfer, %d\n", __func__, status); +} + +static void gbam_start_endless_tx(struct gbam_port *port) +{ + struct bam_ch_info *d = &port->data_ch; + int status; + + status = usb_ep_queue(port->port_usb->in, d->tx_req, GFP_ATOMIC); + if (status) + pr_err("%s: error enqueuing transfer, %d\n", __func__, status); +} + +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_ul, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + + d = &port->data_ch; + ep = port->port_usb->out; + ret = gbam_alloc_requests(ep, &d->rx_idle, bam_mux_rx_q_size, + gbam_epout_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: rx req allocation failed\n", __func__); + return; + } + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + spin_lock_irqsave(&port->port_lock_dl, flags); + ep = port->port_usb->in; + ret = gbam_alloc_requests(ep, &d->tx_idle, bam_mux_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_dl, flags); + + /* queue out requests */ + gbam_start_rx(port); +} + +static void gbam_notify(void *p, int event, unsigned long data) +{ + switch (event) { + case BAM_DMUX_RECEIVE: + gbam_data_recv_cb(p, (struct sk_buff *)(data)); + break; + case BAM_DMUX_WRITE_DONE: + gbam_data_write_done(p, (struct sk_buff *)(data)); + break; + } +} + +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_ul, flags); + spin_lock(&port->port_lock_dl); + + 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(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); +} + +static void gbam_disconnect_work(struct work_struct *w) +{ + struct gbam_port *port = + container_of(w, struct gbam_port, disconnect_w); + struct bam_ch_info *d = &port->data_ch; + + if (!test_bit(BAM_CH_OPENED, &d->flags)) + return; + + msm_bam_dmux_close(d->id); + clear_bit(BAM_CH_OPENED, &d->flags); +} + +static void gbam2bam_disconnect_work(struct work_struct *w) +{ + struct gbam_port *port = + container_of(w, struct gbam_port, disconnect_w); + unsigned long flags; + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + port->port_usb = 0; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + /* disable endpoints */ + usb_ep_disable(port->gr->out); + usb_ep_disable(port->gr->in); + + port->gr->in->driver_data = NULL; + port->gr->out->driver_data = NULL; +} + +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; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + if (!port->port_usb) { + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (!test_bit(BAM_CH_READY, &d->flags)) + return; + + ret = msm_bam_dmux_open(d->id, port, gbam_notify); + if (ret) { + pr_err("%s: unable open bam ch:%d err:%d\n", + __func__, d->id, ret); + return; + } + set_bit(BAM_CH_OPENED, &d->flags); + + gbam_start_io(port); + + pr_debug("%s: done\n", __func__); +} + +static void gbam2bam_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; + u32 sps_params; + int ret; + unsigned long flags; + + ret = usb_ep_enable(port->gr->in); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:IN ep:%p", + __func__, port->gr->in); + return; + } + port->gr->in->driver_data = port; + + ret = usb_ep_enable(port->gr->out); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p", + __func__, port->gr->out); + port->gr->in->driver_data = 0; + return; + } + port->gr->out->driver_data = port; + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + port->port_usb = port->gr; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + ret = usb_bam_connect(d->connection_idx, &d->src_pipe_idx, + &d->dst_pipe_idx); + if (ret) { + pr_err("%s: usb_bam_connect failed: err:%d\n", + __func__, ret); + return; + } + + d->rx_req = usb_ep_alloc_request(port->port_usb->out, GFP_KERNEL); + if (!d->rx_req) + return; + + d->rx_req->context = port; + d->rx_req->complete = gbam_endless_rx_complete; + d->rx_req->length = 0; + sps_params = (MSM_SPS_MODE | d->src_pipe_idx | + MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER; + d->rx_req->udc_priv = sps_params; + d->tx_req = usb_ep_alloc_request(port->port_usb->in, GFP_KERNEL); + if (!d->tx_req) + return; + + d->tx_req->context = port; + d->tx_req->complete = gbam_endless_tx_complete; + d->tx_req->length = 0; + sps_params = (MSM_SPS_MODE | d->dst_pipe_idx | + MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER; + d->tx_req->udc_priv = sps_params; + + /* queue in & out requests */ + gbam_start_endless_rx(port); + gbam_start_endless_tx(port); + + pr_debug("%s: done\n", __func__); +} + +/* BAM data channel ready, allow attempt to open */ +static int gbam_data_ch_probe(struct platform_device *pdev) +{ + struct gbam_port *port; + struct bam_ch_info *d; + int i; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_bam_ports; i++) { + port = bam_ports[i].port; + d = &port->data_ch; + + if (!strncmp(bam_ch_names[i], pdev->name, + BAM_DMUX_CH_NAME_MAX_LEN)) { + set_bit(BAM_CH_READY, &d->flags); + + /* if usb is online, try opening bam_ch */ + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + if (port->port_usb) + queue_work(gbam_wq, &port->connect_w); + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + break; + } + } + + return 0; +} + +/* BAM data channel went inactive, so close it */ +static int gbam_data_ch_remove(struct platform_device *pdev) +{ + struct gbam_port *port; + struct bam_ch_info *d; + struct usb_ep *ep_in = NULL; + struct usb_ep *ep_out = NULL; + unsigned long flags; + int i; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_bam_ports; i++) { + if (!strncmp(bam_ch_names[i], pdev->name, + BAM_DMUX_CH_NAME_MAX_LEN)) { + port = bam_ports[i].port; + d = &port->data_ch; + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + if (port->port_usb) { + ep_in = port->port_usb->in; + ep_out = port->port_usb->out; + } + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (ep_in) + usb_ep_fifo_flush(ep_in); + if (ep_out) + usb_ep_fifo_flush(ep_out); + + gbam_free_buffers(port); + + msm_bam_dmux_close(d->id); + + /* bam dmux will free all pending skbs */ + d->pending_with_bam = 0; + + clear_bit(BAM_CH_READY, &d->flags); + clear_bit(BAM_CH_OPENED, &d->flags); + } + } + + return 0; +} + +static void gbam_port_free(int portno) +{ + struct gbam_port *port = bam_ports[portno].port; + struct platform_driver *pdrv = &bam_ports[portno].pdrv; + + if (port) { + kfree(port); + platform_driver_unregister(pdrv); + } +} + +static void gbam2bam_port_free(int portno) +{ + struct gbam_port *port = bam2bam_ports[portno]; + + kfree(port); +} + +static int gbam_port_alloc(int portno) +{ + struct gbam_port *port; + struct bam_ch_info *d; + struct platform_driver *pdrv; + + port = kzalloc(sizeof(struct gbam_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + + /* port initialization */ + spin_lock_init(&port->port_lock_ul); + spin_lock_init(&port->port_lock_dl); + INIT_WORK(&port->connect_w, gbam_connect_work); + INIT_WORK(&port->disconnect_w, gbam_disconnect_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); + INIT_WORK(&d->write_tohost_w, gbam_write_data_tohost_w); + 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; + + pdrv = &bam_ports[portno].pdrv; + pdrv->probe = gbam_data_ch_probe; + pdrv->remove = gbam_data_ch_remove; + pdrv->driver.name = bam_ch_names[portno]; + pdrv->driver.owner = THIS_MODULE; + + platform_driver_register(pdrv); + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +static int gbam2bam_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_ul); + spin_lock_init(&port->port_lock_dl); + + INIT_WORK(&port->connect_w, gbam2bam_connect_work); + INIT_WORK(&port->disconnect_w, gbam2bam_disconnect_work); + + /* data ch */ + d = &port->data_ch; + d->port = port; + bam2bam_ports[portno] = 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_ul, flags); + spin_lock(&port->port_lock_dl); + + 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" + "rx_buf_len: %u\n" + "data_ch_open: %d\n" + "data_ch_ready: %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, d->rx_skb_q.qlen, + test_bit(BAM_CH_OPENED, &d->flags), + test_bit(BAM_CH_READY, &d->flags)); + + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, 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_ul, flags); + spin_lock(&port->port_lock_dl); + + 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(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, 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 + +void gbam_disconnect(struct grmnet *gr, u8 port_num, enum transport_type trans) +{ + 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 (trans == USB_GADGET_XPORT_BAM && + port_num >= n_bam_ports) { + pr_err("%s: invalid bam portno#%d\n", + __func__, port_num); + return; + } + + if (trans == USB_GADGET_XPORT_BAM2BAM && + port_num >= n_bam2bam_ports) { + pr_err("%s: invalid bam2bam portno#%d\n", + __func__, port_num); + return; + } + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return; + } + if (trans == USB_GADGET_XPORT_BAM) + port = bam_ports[port_num].port; + else + port = bam2bam_ports[port_num]; + + d = &port->data_ch; + port->gr = gr; + + if (trans == USB_GADGET_XPORT_BAM) { + gbam_free_buffers(port); + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + port->port_usb = 0; + n_tx_req_queued = 0; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + /* disable endpoints */ + usb_ep_disable(gr->out); + usb_ep_disable(gr->in); + } + + queue_work(gbam_wq, &port->disconnect_w); +} + +int gbam_connect(struct grmnet *gr, u8 port_num, + enum transport_type trans, u8 connection_idx) +{ + 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 (trans == USB_GADGET_XPORT_BAM && port_num >= n_bam_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if (trans == USB_GADGET_XPORT_BAM2BAM && port_num >= n_bam2bam_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; + } + + if (trans == USB_GADGET_XPORT_BAM) + port = bam_ports[port_num].port; + else + port = bam2bam_ports[port_num]; + + d = &port->data_ch; + + if (trans == USB_GADGET_XPORT_BAM) { + ret = usb_ep_enable(gr->in); + 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); + 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_ul, flags); + spin_lock(&port->port_lock_dl); + 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(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + } + + if (trans == USB_GADGET_XPORT_BAM2BAM) { + port->gr = gr; + d->connection_idx = connection_idx; + } + + queue_work(gbam_wq, &port->connect_w); + + return 0; +} + +int gbam_setup(unsigned int no_bam_port, unsigned int no_bam2bam_port) +{ + int i; + int ret; + + pr_debug("%s: requested BAM ports:%d and BAM2BAM ports:%d\n", + __func__, no_bam_port, no_bam2bam_port); + + if ((!no_bam_port && !no_bam2bam_port) || no_bam_port > BAM_N_PORTS + || no_bam2bam_port > BAM2BAM_N_PORTS) { + pr_err("%s: Invalid num of ports count:%d,%d\n", + __func__, no_bam_port, no_bam2bam_port); + 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 < no_bam_port; i++) { + n_bam_ports++; + ret = gbam_port_alloc(i); + if (ret) { + n_bam_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_bam_ports; + } + } + + for (i = 0; i < no_bam2bam_port; i++) { + n_bam2bam_ports++; + ret = gbam2bam_port_alloc(i); + if (ret) { + n_bam2bam_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_bam_ports; + } + } + gbam_debugfs_init(); + return 0; + +free_bam_ports: + for (i = 0; i < n_bam_ports; i++) + gbam_port_free(i); + for (i = 0; i < n_bam2bam_ports; i++) + gbam2bam_port_free(i); + destroy_workqueue(gbam_wq); + + return ret; +} + +static int gbam_wake_cb(void *param) +{ + struct gbam_port *port = (struct gbam_port *)param; + struct bam_ch_info *d; + struct f_rmnet *dev; + + dev = port_to_rmnet(port->gr); + d = &port->data_ch; + + pr_debug("%s: woken up by peer\n", __func__); + + return usb_gadget_wakeup(dev->cdev->gadget); +} + +void gbam_suspend(struct grmnet *gr, u8 port_num, enum transport_type trans) +{ + struct gbam_port *port; + struct bam_ch_info *d; + + if (trans != USB_GADGET_XPORT_BAM2BAM) + return; + + port = bam2bam_ports[port_num]; + d = &port->data_ch; + + pr_debug("%s: suspended port %d\n", __func__, port_num); + + usb_bam_register_wake_cb(d->connection_idx, gbam_wake_cb, port); +} + +void gbam_resume(struct grmnet *gr, u8 port_num, enum transport_type trans) +{ + struct gbam_port *port; + struct bam_ch_info *d; + + if (trans != USB_GADGET_XPORT_BAM2BAM) + return; + + port = bam2bam_ports[port_num]; + d = &port->data_ch; + + pr_debug("%s: resumed port %d\n", __func__, port_num); + + usb_bam_register_wake_cb(d->connection_idx, NULL, NULL); +} diff --git a/drivers/usb/gadget/u_bam_data.c b/drivers/usb/gadget/u_bam_data.c new file mode 100644 index 0000000000000000000000000000000000000000..73b4e750f0200c28a9777a4f04174c579481c3ff --- /dev/null +++ b/drivers/usb/gadget/u_bam_data.c @@ -0,0 +1,328 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define BAM2BAM_DATA_N_PORTS 1 + +static struct workqueue_struct *bam_data_wq; +static int n_bam2bam_data_ports; + +#define SPS_PARAMS_SPS_MODE BIT(5) +#define SPS_PARAMS_TBE BIT(6) +#define MSM_VENDOR_ID BIT(16) + +struct data_port { + struct usb_function func; + struct usb_ep *in; + struct usb_ep *out; +}; + +struct bam_data_ch_info { + unsigned long flags; + unsigned id; + + struct bam_data_port *port; + struct work_struct write_tobam_w; + + struct usb_request *rx_req; + struct usb_request *tx_req; + + u8 src_pipe_idx; + u8 dst_pipe_idx; + u8 connection_idx; +}; + +struct bam_data_port { + unsigned port_num; + struct data_port *port_usb; + struct bam_data_ch_info data_ch; + + struct work_struct connect_w; + struct work_struct disconnect_w; +}; + +struct bam_data_port *bam2bam_data_ports[BAM2BAM_DATA_N_PORTS]; + +/*------------data_path----------------------------*/ + +static void bam_data_endless_rx_complete(struct usb_ep *ep, + struct usb_request *req) +{ + int status = req->status; + + pr_info("status: %d\n", status); +} + +static void bam_data_endless_tx_complete(struct usb_ep *ep, + struct usb_request *req) +{ + int status = req->status; + + pr_info("status: %d\n", status); +} + +static void bam_data_start_endless_rx(struct bam_data_port *port) +{ + struct bam_data_ch_info *d = &port->data_ch; + int status; + + status = usb_ep_queue(port->port_usb->out, d->rx_req, GFP_ATOMIC); + if (status) + pr_err("error enqueuing transfer, %d\n", status); +} + +static void bam_data_start_endless_tx(struct bam_data_port *port) +{ + struct bam_data_ch_info *d = &port->data_ch; + int status; + + status = usb_ep_queue(port->port_usb->in, d->tx_req, GFP_ATOMIC); + if (status) + pr_err("error enqueuing transfer, %d\n", status); +} + +static void bam2bam_data_disconnect_work(struct work_struct *w) +{ + struct bam_data_port *port = + container_of(w, struct bam_data_port, disconnect_w); + + pr_info("Enter"); + + /* disable endpoints */ + if (!port->port_usb || !port->port_usb->out || !port->port_usb->in) { + pr_err("port_usb->out/in == NULL. Exit"); + return; + } + usb_ep_disable(port->port_usb->out); + usb_ep_disable(port->port_usb->in); + + port->port_usb->in->driver_data = NULL; + port->port_usb->out->driver_data = NULL; + + port->port_usb = 0; + + pr_info("Exit"); +} + +static void bam2bam_data_connect_work(struct work_struct *w) +{ + struct bam_data_port *port = container_of(w, struct bam_data_port, + connect_w); + struct bam_data_ch_info *d = &port->data_ch; + u32 sps_params; + int ret; + + pr_info("Enter"); + + ret = usb_bam_connect(d->connection_idx, &d->src_pipe_idx, + &d->dst_pipe_idx); + d->src_pipe_idx = 11; + d->dst_pipe_idx = 10; + + if (ret) { + pr_err("usb_bam_connect failed: err:%d\n", ret); + return; + } + + if (!port->port_usb) { + pr_err("port_usb is NULL"); + return; + } + + if (!port->port_usb->out) { + pr_err("port_usb->out (bulk out ep) is NULL"); + return; + } + + d->rx_req = usb_ep_alloc_request(port->port_usb->out, GFP_KERNEL); + if (!d->rx_req) + return; + + d->rx_req->context = port; + d->rx_req->complete = bam_data_endless_rx_complete; + d->rx_req->length = 0; + sps_params = (SPS_PARAMS_SPS_MODE | d->src_pipe_idx | + MSM_VENDOR_ID) & ~SPS_PARAMS_TBE; + d->rx_req->udc_priv = sps_params; + d->tx_req = usb_ep_alloc_request(port->port_usb->in, GFP_KERNEL); + if (!d->tx_req) + return; + + d->tx_req->context = port; + d->tx_req->complete = bam_data_endless_tx_complete; + d->tx_req->length = 0; + sps_params = (SPS_PARAMS_SPS_MODE | d->dst_pipe_idx | + MSM_VENDOR_ID) & ~SPS_PARAMS_TBE; + d->tx_req->udc_priv = sps_params; + + /* queue in & out requests */ + bam_data_start_endless_rx(port); + bam_data_start_endless_tx(port); + + pr_info("Done\n"); +} + +static void bam2bam_data_port_free(int portno) +{ + kfree(bam2bam_data_ports[portno]); + bam2bam_data_ports[portno] = NULL; +} + +static int bam2bam_data_port_alloc(int portno) +{ + struct bam_data_port *port = NULL; + struct bam_data_ch_info *d = NULL; + + port = kzalloc(sizeof(struct bam_data_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + + INIT_WORK(&port->connect_w, bam2bam_data_connect_work); + INIT_WORK(&port->disconnect_w, bam2bam_data_disconnect_work); + + /* data ch */ + d = &port->data_ch; + d->port = port; + bam2bam_data_ports[portno] = port; + + pr_info("port:%p portno:%d\n", port, portno); + + return 0; +} + +void bam_data_disconnect(struct data_port *gr, u8 port_num) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + + pr_info("dev:%p port#%d\n", gr, port_num); + + if (port_num >= n_bam2bam_data_ports) { + pr_err("invalid bam2bam portno#%d\n", port_num); + return; + } + + if (!gr) { + pr_err("mbim data port is null\n"); + return; + } + + port = bam2bam_data_ports[port_num]; + + d = &port->data_ch; + port->port_usb = gr; + + queue_work(bam_data_wq, &port->disconnect_w); +} + +int bam_data_connect(struct data_port *gr, u8 port_num, + u8 connection_idx) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + int ret; + + pr_info("dev:%p port#%d\n", gr, port_num); + + if (port_num >= n_bam2bam_data_ports) { + pr_err("invalid portno#%d\n", port_num); + return -ENODEV; + } + + if (!gr) { + pr_err("mbim data port is null\n"); + return -ENODEV; + } + + port = bam2bam_data_ports[port_num]; + + d = &port->data_ch; + + ret = usb_ep_enable(gr->in); + if (ret) { + pr_err("usb_ep_enable failed eptype:IN ep:%p", gr->in); + return ret; + } + gr->in->driver_data = port; + + ret = usb_ep_enable(gr->out); + if (ret) { + pr_err("usb_ep_enable failed eptype:OUT ep:%p", gr->out); + gr->in->driver_data = 0; + return ret; + } + gr->out->driver_data = port; + + port->port_usb = gr; + + d->connection_idx = connection_idx; + + queue_work(bam_data_wq, &port->connect_w); + + return 0; +} + +int bam_data_setup(unsigned int no_bam2bam_port) +{ + int i; + int ret; + + pr_info("requested %d BAM2BAM ports", no_bam2bam_port); + + if (!no_bam2bam_port || no_bam2bam_port > BAM2BAM_DATA_N_PORTS) { + pr_err("Invalid num of ports count:%d\n", no_bam2bam_port); + return -EINVAL; + } + + bam_data_wq = alloc_workqueue("k_bam_data", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!bam_data_wq) { + pr_err("Failed to create workqueue\n"); + return -ENOMEM; + } + + for (i = 0; i < no_bam2bam_port; i++) { + n_bam2bam_data_ports++; + ret = bam2bam_data_port_alloc(i); + if (ret) { + n_bam2bam_data_ports--; + pr_err("Failed to alloc port:%d\n", i); + goto free_bam_ports; + } + } + + return 0; + +free_bam_ports: + for (i = 0; i < n_bam2bam_data_ports; i++) + bam2bam_data_port_free(i); + destroy_workqueue(bam_data_wq); + + return ret; +} + diff --git a/drivers/usb/gadget/u_ctrl_hsic.c b/drivers/usb/gadget/u_ctrl_hsic.c new file mode 100644 index 0000000000000000000000000000000000000000..fdfab968a699e6d2786e308f862154861d26807a --- /dev/null +++ b/drivers/usb/gadget/u_ctrl_hsic.c @@ -0,0 +1,617 @@ +/* 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 + +/* from cdc-acm.h */ +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ +#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) + + +static unsigned int no_ctrl_ports; + +static const char *ctrl_bridge_names[] = { + "dun_ctrl_hsic0", + "rmnet_ctrl_hsic0" +}; + +#define CTRL_BRIDGE_NAME_MAX_LEN 20 +#define READ_BUF_LEN 1024 + +#define CH_OPENED 0 +#define CH_READY 1 + +struct gctrl_port { + /* port */ + unsigned port_num; + + /* gadget */ + spinlock_t port_lock; + void *port_usb; + + /* work queue*/ + struct workqueue_struct *wq; + struct work_struct connect_w; + struct work_struct disconnect_w; + + enum gadget_type gtype; + + /*ctrl pkt response cb*/ + int (*send_cpkt_response)(void *g, void *buf, size_t len); + + struct bridge brdg; + + /* bridge status */ + unsigned long bridge_sts; + + /* control bits */ + unsigned cbits_tomodem; + unsigned cbits_tohost; + + /* counters */ + unsigned long to_modem; + unsigned long to_host; + unsigned long drp_cpkt_cnt; +}; + +static struct { + struct gctrl_port *port; + struct platform_driver pdrv; +} gctrl_ports[NUM_PORTS]; + +static int ghsic_ctrl_receive(void *dev, void *buf, size_t actual) +{ + struct gctrl_port *port = dev; + int retval = 0; + + pr_debug_ratelimited("%s: read complete bytes read: %d\n", + __func__, actual); + + /* send it to USB here */ + if (port && port->send_cpkt_response) { + retval = port->send_cpkt_response(port->port_usb, buf, actual); + port->to_host++; + } + + return retval; +} + +static int +ghsic_send_cpkt_tomodem(u8 portno, void *buf, size_t len) +{ + void *cbuf; + struct gctrl_port *port; + + if (portno >= no_ctrl_ports) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return -ENODEV; + } + + port = gctrl_ports[portno].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + + cbuf = kmalloc(len, GFP_ATOMIC); + if (!cbuf) + return -ENOMEM; + + memcpy(cbuf, buf, len); + + /* drop cpkt if ch is not open */ + if (!test_bit(CH_OPENED, &port->bridge_sts)) { + port->drp_cpkt_cnt++; + kfree(cbuf); + return 0; + } + + pr_debug("%s: ctrl_pkt:%d bytes\n", __func__, len); + + ctrl_bridge_write(port->brdg.ch_id, cbuf, len); + + port->to_modem++; + + return 0; +} + +static void +ghsic_send_cbits_tomodem(void *gptr, u8 portno, int cbits) +{ + struct gctrl_port *port; + + if (portno >= no_ctrl_ports || !gptr) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return; + } + + port = gctrl_ports[portno].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + if (cbits == port->cbits_tomodem) + return; + + port->cbits_tomodem = cbits; + + if (!test_bit(CH_OPENED, &port->bridge_sts)) + return; + + pr_debug("%s: ctrl_tomodem:%d\n", __func__, cbits); + + ctrl_bridge_set_cbits(port->brdg.ch_id, cbits); +} + +static void ghsic_ctrl_connect_w(struct work_struct *w) +{ + struct gserial *gser = NULL; + struct grmnet *gr = NULL; + struct gctrl_port *port = + container_of(w, struct gctrl_port, connect_w); + unsigned long flags; + int retval; + unsigned cbits; + + if (!port || !test_bit(CH_READY, &port->bridge_sts)) + return; + + pr_debug("%s: port:%p\n", __func__, port); + + retval = ctrl_bridge_open(&port->brdg); + if (retval) { + pr_err("%s: ctrl bridge open failed :%d\n", __func__, retval); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + ctrl_bridge_close(port->brdg.ch_id); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + set_bit(CH_OPENED, &port->bridge_sts); + spin_unlock_irqrestore(&port->port_lock, flags); + + cbits = ctrl_bridge_get_cbits_tohost(port->brdg.ch_id); + + if (port->gtype == USB_GADGET_SERIAL && (cbits & ACM_CTRL_DCD)) { + gser = port->port_usb; + if (gser && gser->connect) + gser->connect(gser); + return; + } + + if (port->gtype == USB_GADGET_RMNET) { + gr = port->port_usb; + if (gr && gr->connect) + gr->connect(gr); + } +} + +int ghsic_ctrl_connect(void *gptr, int port_num) +{ + struct gctrl_port *port; + struct gserial *gser; + struct grmnet *gr; + unsigned long flags; + + pr_debug("%s: port#%d\n", __func__, port_num); + + if (port_num > no_ctrl_ports || !gptr) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + port = gctrl_ports[port_num].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (port->gtype == USB_GADGET_SERIAL) { + gser = gptr; + gser->notify_modem = ghsic_send_cbits_tomodem; + } + + if (port->gtype == USB_GADGET_RMNET) { + gr = gptr; + port->send_cpkt_response = gr->send_cpkt_response; + gr->send_encap_cmd = ghsic_send_cpkt_tomodem; + gr->notify_modem = ghsic_send_cbits_tomodem; + } + + port->port_usb = gptr; + port->to_host = 0; + port->to_modem = 0; + port->drp_cpkt_cnt = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + queue_work(port->wq, &port->connect_w); + + return 0; +} + +static void gctrl_disconnect_w(struct work_struct *w) +{ + struct gctrl_port *port = + container_of(w, struct gctrl_port, disconnect_w); + + if (!test_bit(CH_OPENED, &port->bridge_sts)) + return; + + /* send the dtr zero */ + ctrl_bridge_close(port->brdg.ch_id); + clear_bit(CH_OPENED, &port->bridge_sts); +} + +void ghsic_ctrl_disconnect(void *gptr, int port_num) +{ + struct gctrl_port *port; + struct gserial *gser = NULL; + struct grmnet *gr = NULL; + unsigned long flags; + + pr_debug("%s: port#%d\n", __func__, port_num); + + port = gctrl_ports[port_num].port; + + if (port_num > no_ctrl_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return; + } + + if (!gptr || !port) { + pr_err("%s: grmnet port is null\n", __func__); + return; + } + + if (port->gtype == USB_GADGET_SERIAL) + gser = gptr; + else + gr = gptr; + + spin_lock_irqsave(&port->port_lock, flags); + if (gr) { + gr->send_encap_cmd = 0; + gr->notify_modem = 0; + } + + if (gser) + gser->notify_modem = 0; + port->cbits_tomodem = 0; + port->port_usb = 0; + port->send_cpkt_response = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + queue_work(port->wq, &port->disconnect_w); +} + +static void ghsic_ctrl_status(void *ctxt, unsigned int ctrl_bits) +{ + struct gctrl_port *port = ctxt; + struct gserial *gser; + + pr_debug("%s - input control lines: dcd%c dsr%c break%c " + "ring%c framing%c parity%c overrun%c\n", __func__, + ctrl_bits & ACM_CTRL_DCD ? '+' : '-', + ctrl_bits & ACM_CTRL_DSR ? '+' : '-', + ctrl_bits & ACM_CTRL_BRK ? '+' : '-', + ctrl_bits & ACM_CTRL_RI ? '+' : '-', + ctrl_bits & ACM_CTRL_FRAMING ? '+' : '-', + ctrl_bits & ACM_CTRL_PARITY ? '+' : '-', + ctrl_bits & ACM_CTRL_OVERRUN ? '+' : '-'); + + port->cbits_tohost = ctrl_bits; + gser = port->port_usb; + if (gser && gser->send_modem_ctrl_bits) + gser->send_modem_ctrl_bits(gser, ctrl_bits); +} + +static int ghsic_ctrl_probe(struct platform_device *pdev) +{ + struct gctrl_port *port; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + if (pdev->id >= no_ctrl_ports) { + pr_err("%s: invalid port: %d\n", __func__, pdev->id); + return -EINVAL; + } + + port = gctrl_ports[pdev->id].port; + set_bit(CH_READY, &port->bridge_sts); + + /* if usb is online, start read */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + queue_work(port->wq, &port->connect_w); + spin_unlock_irqrestore(&port->port_lock, flags); + + return 0; +} + +static int ghsic_ctrl_remove(struct platform_device *pdev) +{ + struct gctrl_port *port; + struct gserial *gser = NULL; + struct grmnet *gr = NULL; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + if (pdev->id >= no_ctrl_ports) { + pr_err("%s: invalid port: %d\n", __func__, pdev->id); + return -EINVAL; + } + + port = gctrl_ports[pdev->id].port; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + goto not_ready; + } + + if (port->gtype == USB_GADGET_SERIAL) + gser = port->port_usb; + else + gr = port->port_usb; + + port->cbits_tohost = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + if (gr && gr->disconnect) + gr->disconnect(gr); + + if (gser && gser->disconnect) + gser->disconnect(gser); + + ctrl_bridge_close(port->brdg.ch_id); + + clear_bit(CH_OPENED, &port->bridge_sts); +not_ready: + clear_bit(CH_READY, &port->bridge_sts); + + return 0; +} + +static void ghsic_ctrl_port_free(int portno) +{ + struct gctrl_port *port = gctrl_ports[portno].port; + struct platform_driver *pdrv = &gctrl_ports[portno].pdrv; + + destroy_workqueue(port->wq); + kfree(port); + + if (pdrv) + platform_driver_unregister(pdrv); +} + +static int gctrl_port_alloc(int portno, enum gadget_type gtype) +{ + struct gctrl_port *port; + struct platform_driver *pdrv; + + port = kzalloc(sizeof(struct gctrl_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->wq = create_singlethread_workqueue(ctrl_bridge_names[portno]); + if (!port->wq) { + pr_err("%s: Unable to create workqueue:%s\n", + __func__, ctrl_bridge_names[portno]); + return -ENOMEM; + } + + port->port_num = portno; + port->gtype = gtype; + + spin_lock_init(&port->port_lock); + + INIT_WORK(&port->connect_w, ghsic_ctrl_connect_w); + INIT_WORK(&port->disconnect_w, gctrl_disconnect_w); + + port->brdg.ch_id = portno; + port->brdg.ctx = port; + port->brdg.ops.send_pkt = ghsic_ctrl_receive; + if (port->gtype == USB_GADGET_SERIAL) + port->brdg.ops.send_cbits = ghsic_ctrl_status; + gctrl_ports[portno].port = port; + + pdrv = &gctrl_ports[portno].pdrv; + pdrv->probe = ghsic_ctrl_probe; + pdrv->remove = ghsic_ctrl_remove; + pdrv->driver.name = ctrl_bridge_names[portno]; + pdrv->driver.owner = THIS_MODULE; + + platform_driver_register(pdrv); + + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +int ghsic_ctrl_setup(unsigned int num_ports, enum gadget_type gtype) +{ + int first_port_id = no_ctrl_ports; + int total_num_ports = num_ports + no_ctrl_ports; + int i; + int ret = 0; + + if (!num_ports || total_num_ports > NUM_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, num_ports); + return -EINVAL; + } + + pr_debug("%s: requested ports:%d\n", __func__, num_ports); + + for (i = first_port_id; i < (first_port_id + num_ports); i++) { + + /*probe can be called while port_alloc,so update no_ctrl_ports*/ + no_ctrl_ports++; + ret = gctrl_port_alloc(i, gtype); + if (ret) { + no_ctrl_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_ports; + } + } + + return first_port_id; + +free_ports: + for (i = first_port_id; i < no_ctrl_ports; i++) + ghsic_ctrl_port_free(i); + no_ctrl_ports = first_port_id; + return ret; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t gctrl_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct gctrl_port *port; + struct platform_driver *pdrv; + 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 < no_ctrl_ports; i++) { + port = gctrl_ports[i].port; + if (!port) + continue; + pdrv = &gctrl_ports[i].pdrv; + spin_lock_irqsave(&port->port_lock, flags); + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName: %s\n" + "#PORT:%d port: %p\n" + "to_usbhost: %lu\n" + "to_modem: %lu\n" + "cpkt_drp_cnt: %lu\n" + "DTR: %s\n" + "ch_open: %d\n" + "ch_ready: %d\n", + pdrv->driver.name, + i, port, + port->to_host, port->to_modem, + port->drp_cpkt_cnt, + port->cbits_tomodem ? "HIGH" : "LOW", + test_bit(CH_OPENED, &port->bridge_sts), + test_bit(CH_READY, &port->bridge_sts)); + + spin_unlock_irqrestore(&port->port_lock, flags); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t gctrl_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct gctrl_port *port; + int i; + unsigned long flags; + + for (i = 0; i < no_ctrl_ports; i++) { + port = gctrl_ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->port_lock, flags); + port->to_host = 0; + port->to_modem = 0; + port->drp_cpkt_cnt = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + } + return count; +} + +const struct file_operations gctrl_stats_ops = { + .read = gctrl_read_stats, + .write = gctrl_reset_stats, +}; + +struct dentry *gctrl_dent; +struct dentry *gctrl_dfile; +static void gctrl_debugfs_init(void) +{ + gctrl_dent = debugfs_create_dir("ghsic_ctrl_xport", 0); + if (IS_ERR(gctrl_dent)) + return; + + gctrl_dfile = + debugfs_create_file("status", 0444, gctrl_dent, 0, + &gctrl_stats_ops); + if (!gctrl_dfile || IS_ERR(gctrl_dfile)) + debugfs_remove(gctrl_dent); +} + +static void gctrl_debugfs_exit(void) +{ + debugfs_remove(gctrl_dfile); + debugfs_remove(gctrl_dent); +} + +#else +static void gctrl_debugfs_init(void) { } +static void gctrl_debugfs_exit(void) { } +#endif + +static int __init gctrl_init(void) +{ + gctrl_debugfs_init(); + + return 0; +} +module_init(gctrl_init); + +static void __exit gctrl_exit(void) +{ + gctrl_debugfs_exit(); +} +module_exit(gctrl_exit); +MODULE_DESCRIPTION("hsic control xport driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/u_ctrl_hsuart.c b/drivers/usb/gadget/u_ctrl_hsuart.c new file mode 100644 index 0000000000000000000000000000000000000000..7102d815963fc15d1d506f92de9e60ebc939c6ba --- /dev/null +++ b/drivers/usb/gadget/u_ctrl_hsuart.c @@ -0,0 +1,576 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 CH_OPENED 0 +#define CH_READY 1 + +static unsigned int num_ctrl_ports; + +static const char *ghsuart_ctrl_names[] = { + "SMUX_RMNET_CTL_HSUART" +}; + +struct ghsuart_ctrl_port { + /* port */ + unsigned port_num; + /* gadget */ + enum gadget_type gtype; + spinlock_t port_lock; + void *port_usb; + /* work queue*/ + struct workqueue_struct *wq; + struct work_struct connect_w; + struct work_struct disconnect_w; + /*ctrl pkt response cb*/ + int (*send_cpkt_response)(void *g, void *buf, size_t len); + void *ctxt; + unsigned int ch_id; + /* flow control bits */ + unsigned long flags; + int (*send_pkt)(void *, void *, size_t actual); + /* Channel status */ + unsigned long channel_sts; + /* control bits */ + unsigned cbits_tomodem; + /* counters */ + unsigned long to_modem; + unsigned long to_host; + unsigned long drp_cpkt_cnt; +}; + +static struct { + struct ghsuart_ctrl_port *port; + struct platform_driver pdrv; +} ghsuart_ctrl_ports[NUM_HSUART_PORTS]; + +static int ghsuart_ctrl_receive(void *dev, void *buf, size_t actual); + +static void smux_control_event(void *priv, int event_type, const void *metadata) +{ + struct grmnet *gr = NULL; + struct ghsuart_ctrl_port *port = priv; + void *buf; + unsigned long flags; + size_t len; + + switch (event_type) { + case SMUX_CONNECTED: + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + spin_unlock_irqrestore(&port->port_lock, flags); + set_bit(CH_OPENED, &port->channel_sts); + if (port->gtype == USB_GADGET_RMNET) { + gr = port->port_usb; + if (gr && gr->connect) + gr->connect(gr); + } + break; + case SMUX_DISCONNECTED: + clear_bit(CH_OPENED, &port->channel_sts); + break; + case SMUX_READ_DONE: + len = ((struct smux_meta_read *)metadata)->len; + buf = ((struct smux_meta_read *)metadata)->buffer; + ghsuart_ctrl_receive(port, buf, len); + break; + case SMUX_READ_FAIL: + buf = ((struct smux_meta_read *)metadata)->buffer; + kfree(buf); + break; + case SMUX_WRITE_DONE: + case SMUX_WRITE_FAIL: + buf = ((struct smux_meta_write *)metadata)->buffer; + kfree(buf); + break; + case SMUX_LOW_WM_HIT: + case SMUX_HIGH_WM_HIT: + case SMUX_TIOCM_UPDATE: + break; + default: + pr_err("%s Event %d not supported\n", __func__, event_type); + }; +} + +static int rx_control_buffer(void *priv, void **pkt_priv, void **buffer, + int size) +{ + void *rx_buf; + + rx_buf = kmalloc(size, GFP_KERNEL); + if (!rx_buf) + return -EAGAIN; + *buffer = rx_buf; + *pkt_priv = NULL; + + return 0; +} + +static int ghsuart_ctrl_receive(void *dev, void *buf, size_t actual) +{ + struct ghsuart_ctrl_port *port = dev; + int retval = 0; + + pr_debug_ratelimited("%s: read complete bytes read: %d\n", + __func__, actual); + + /* send it to USB here */ + if (port && port->send_cpkt_response) { + retval = port->send_cpkt_response(port->port_usb, buf, actual); + port->to_host++; + } + kfree(buf); + return retval; +} + +static int +ghsuart_send_cpkt_tomodem(u8 portno, void *buf, size_t len) +{ + void *cbuf; + struct ghsuart_ctrl_port *port; + int ret; + + if (portno >= num_ctrl_ports) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return -ENODEV; + } + + port = ghsuart_ctrl_ports[portno].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + /* drop cpkt if ch is not open */ + if (!test_bit(CH_OPENED, &port->channel_sts)) { + port->drp_cpkt_cnt++; + return 0; + } + cbuf = kmalloc(len, GFP_ATOMIC); + if (!cbuf) + return -ENOMEM; + + memcpy(cbuf, buf, len); + + pr_debug("%s: ctrl_pkt:%d bytes\n", __func__, len); + + ret = msm_smux_write(port->ch_id, port, (void *)cbuf, len); + if (ret < 0) { + pr_err_ratelimited("%s: write error:%d\n", + __func__, ret); + port->drp_cpkt_cnt++; + kfree(cbuf); + return ret; + } + port->to_modem++; + + return 0; +} + +static void +ghsuart_send_cbits_tomodem(void *gptr, u8 portno, int cbits) +{ + struct ghsuart_ctrl_port *port; + + if (portno >= num_ctrl_ports || !gptr) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return; + } + + port = ghsuart_ctrl_ports[portno].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + if (cbits == port->cbits_tomodem) + return; + + port->cbits_tomodem = cbits; + + if (!test_bit(CH_OPENED, &port->channel_sts)) + return; + + pr_debug("%s: ctrl_tomodem:%d\n", __func__, cbits); + /* Send the control bits to the Modem */ + msm_smux_tiocm_set(port->ch_id, cbits, ~cbits); +} + +static void ghsuart_ctrl_connect_w(struct work_struct *w) +{ + struct ghsuart_ctrl_port *port = + container_of(w, struct ghsuart_ctrl_port, connect_w); + int retval; + + if (!port || !test_bit(CH_READY, &port->channel_sts)) + return; + + pr_debug("%s: port:%p\n", __func__, port); + + retval = msm_smux_open(port->ch_id, port->ctxt, smux_control_event, + rx_control_buffer); + if (retval < 0) { + pr_err(" %s smux_open failed\n", __func__); + return; + } + +} + +int ghsuart_ctrl_connect(void *gptr, int port_num) +{ + struct ghsuart_ctrl_port *port; + struct grmnet *gr; + unsigned long flags; + + pr_debug("%s: port#%d\n", __func__, port_num); + + if (port_num > num_ctrl_ports || !gptr) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + port = ghsuart_ctrl_ports[port_num].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&port->port_lock, flags); + + gr = gptr; + port->send_cpkt_response = gr->send_cpkt_response; + gr->send_encap_cmd = ghsuart_send_cpkt_tomodem; + gr->notify_modem = ghsuart_send_cbits_tomodem; + + port->port_usb = gptr; + port->to_host = 0; + port->to_modem = 0; + port->drp_cpkt_cnt = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + if (test_bit(CH_READY, &port->channel_sts)) + queue_work(port->wq, &port->connect_w); + + return 0; +} + +static void ghsuart_ctrl_disconnect_w(struct work_struct *w) +{ + struct ghsuart_ctrl_port *port = + container_of(w, struct ghsuart_ctrl_port, disconnect_w); + + if (!test_bit(CH_OPENED, &port->channel_sts)) + return; + + msm_smux_close(port->ch_id); + clear_bit(CH_OPENED, &port->channel_sts); +} + +void ghsuart_ctrl_disconnect(void *gptr, int port_num) +{ + struct gctrl_port *port; + struct grmnet *gr = NULL; + unsigned long flags; + + pr_debug("%s: port#%d\n", __func__, port_num); + + if (port_num > num_ctrl_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return; + } + + port = gctrl_ports[port_num].port; + + if (!gptr || !port) { + pr_err("%s: grmnet port is null\n", __func__); + return; + } + + gr = gptr; + + spin_lock_irqsave(&port->port_lock, flags); + gr->send_encap_cmd = 0; + gr->notify_modem = 0; + port->cbits_tomodem = 0; + port->port_usb = 0; + port->send_cpkt_response = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + queue_work(port->wq, &port->disconnect_w); +} + +static int ghsuart_ctrl_probe(struct platform_device *pdev) +{ + struct ghsuart_ctrl_port *port; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + port = ghsuart_ctrl_ports[pdev->id].port; + set_bit(CH_READY, &port->channel_sts); + + /* if usb is online, start read */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + queue_work(port->wq, &port->connect_w); + spin_unlock_irqrestore(&port->port_lock, flags); + + return 0; +} + +static int ghsuart_ctrl_remove(struct platform_device *pdev) +{ + struct ghsuart_ctrl_port *port; + struct grmnet *gr = NULL; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + port = ghsuart_ctrl_ports[pdev->id].port; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + goto not_ready; + } + + gr = port->port_usb; + + spin_unlock_irqrestore(&port->port_lock, flags); + + if (gr && gr->disconnect) + gr->disconnect(gr); + + clear_bit(CH_OPENED, &port->channel_sts); +not_ready: + clear_bit(CH_READY, &port->channel_sts); + + return 0; +} + +static void ghsuart_ctrl_port_free(int portno) +{ + struct ghsuart_ctrl_port *port = ghsuart_ctrl_ports[portno].port; + struct platform_driver *pdrv = &gctrl_ports[portno].pdrv; + + destroy_workqueue(port->wq); + if (pdrv) + platform_driver_unregister(pdrv); + kfree(port); +} + +static int ghsuart_ctrl_port_alloc(int portno, enum gadget_type gtype) +{ + struct ghsuart_ctrl_port *port; + struct platform_driver *pdrv; + int err; + + port = kzalloc(sizeof(struct ghsuart_ctrl_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->wq = create_singlethread_workqueue(ghsuart_ctrl_names[portno]); + if (!port->wq) { + pr_err("%s: Unable to create workqueue:%s\n", + __func__, ghsuart_ctrl_names[portno]); + kfree(port); + return -ENOMEM; + } + + port->port_num = portno; + port->gtype = gtype; + + spin_lock_init(&port->port_lock); + + INIT_WORK(&port->connect_w, ghsuart_ctrl_connect_w); + INIT_WORK(&port->disconnect_w, ghsuart_ctrl_disconnect_w); + + port->ch_id = SMUX_USB_RMNET_CTL_0; + port->ctxt = port; + port->send_pkt = ghsuart_ctrl_receive; + ghsuart_ctrl_ports[portno].port = port; + + pdrv = &ghsuart_ctrl_ports[portno].pdrv; + pdrv->probe = ghsuart_ctrl_probe; + pdrv->remove = ghsuart_ctrl_remove; + pdrv->driver.name = ghsuart_ctrl_names[portno]; + pdrv->driver.owner = THIS_MODULE; + + err = platform_driver_register(pdrv); + if (unlikely(err < 0)) + return err; + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +int ghsuart_ctrl_setup(unsigned int num_ports, enum gadget_type gtype) +{ + int first_port_id = num_ctrl_ports; + int total_num_ports = num_ports + num_ctrl_ports; + int i; + int ret = 0; + + if (!num_ports || total_num_ports > NUM_HSUART_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, num_ports); + return -EINVAL; + } + + pr_debug("%s: requested ports:%d\n", __func__, num_ports); + + for (i = first_port_id; i < (first_port_id + num_ports); i++) { + + num_ctrl_ports++; + ret = ghsuart_ctrl_port_alloc(i, gtype); + if (ret) { + num_ctrl_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_ports; + } + } + + return first_port_id; + +free_ports: + for (i = first_port_id; i < num_ctrl_ports; i++) + ghsuart_ctrl_port_free(i); + num_ctrl_ports = first_port_id; + return ret; +} + +#define DEBUG_BUF_SIZE 1024 +static ssize_t ghsuart_ctrl_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ghsuart_ctrl_port *port; + 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 < num_ctrl_ports; i++) { + port = ghsuart_ctrl_ports[i].port; + if (!port) + continue; + spin_lock_irqsave(&port->port_lock, flags); + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "#PORT:%d port: %p\n" + "to_usbhost: %lu\n" + "to_modem: %lu\n" + "cpkt_drp_cnt: %lu\n" + "DTR: %s\n", + i, port, + port->to_host, port->to_modem, + port->drp_cpkt_cnt, + port->cbits_tomodem ? "HIGH" : "LOW"); + + spin_unlock_irqrestore(&port->port_lock, flags); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t ghsuart_ctrl_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct ghsuart_ctrl_port *port; + int i; + unsigned long flags; + + for (i = 0; i < num_ctrl_ports; i++) { + port = ghsuart_ctrl_ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->port_lock, flags); + port->to_host = 0; + port->to_modem = 0; + port->drp_cpkt_cnt = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + } + return count; +} + +static const struct file_operations ghsuart_ctrl_stats_ops = { + .read = ghsuart_ctrl_read_stats, + .write = ghsuart_ctrl_reset_stats, +}; + +static struct dentry *ghsuart_ctrl_dent; +static int ghsuart_ctrl_debugfs_init(void) +{ + struct dentry *ghsuart_ctrl_dfile; + + ghsuart_ctrl_dent = debugfs_create_dir("ghsuart_ctrl_xport", 0); + if (!ghsuart_ctrl_dent || IS_ERR(ghsuart_ctrl_dent)) + return -ENODEV; + + ghsuart_ctrl_dfile = + debugfs_create_file("status", S_IRUGO | S_IWUSR, + ghsuart_ctrl_dent, 0, &gctrl_stats_ops); + if (!ghsuart_ctrl_dfile || IS_ERR(ghsuart_ctrl_dfile)) { + debugfs_remove(ghsuart_ctrl_dent); + ghsuart_ctrl_dent = NULL; + return -ENODEV; + } + return 0; +} + +static void ghsuart_ctrl_debugfs_exit(void) +{ + debugfs_remove_recursive(ghsuart_ctrl_dent); +} + +static int __init ghsuart_ctrl_init(void) +{ + int ret; + + ret = ghsuart_ctrl_debugfs_init(); + if (ret) { + pr_debug("mode debugfs file is not available\n"); + return ret; + } + return 0; +} +module_init(ghsuart_ctrl_init); + +static void __exit ghsuart_ctrl_exit(void) +{ + ghsuart_ctrl_debugfs_exit(); +} +module_exit(ghsuart_ctrl_exit); + +MODULE_DESCRIPTION("HSUART control xport for RmNet"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/u_data_hsic.c b/drivers/usb/gadget/u_data_hsic.c new file mode 100644 index 0000000000000000000000000000000000000000..89d2887a93b13e154d1d24e9680c7e467f2148ec --- /dev/null +++ b/drivers/usb/gadget/u_data_hsic.c @@ -0,0 +1,1143 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int no_data_ports; + +static const char *data_bridge_names[] = { + "dun_data_hsic0", + "rmnet_data_hsic0" +}; + +#define DATA_BRIDGE_NAME_MAX_LEN 20 + +#define GHSIC_DATA_RMNET_RX_Q_SIZE 50 +#define GHSIC_DATA_RMNET_TX_Q_SIZE 300 +#define GHSIC_DATA_SERIAL_RX_Q_SIZE 10 +#define GHSIC_DATA_SERIAL_TX_Q_SIZE 20 +#define GHSIC_DATA_RX_REQ_SIZE 2048 +#define GHSIC_DATA_TX_INTR_THRESHOLD 20 + +static unsigned int ghsic_data_rmnet_tx_q_size = GHSIC_DATA_RMNET_TX_Q_SIZE; +module_param(ghsic_data_rmnet_tx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_rmnet_rx_q_size = GHSIC_DATA_RMNET_RX_Q_SIZE; +module_param(ghsic_data_rmnet_rx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_serial_tx_q_size = GHSIC_DATA_SERIAL_TX_Q_SIZE; +module_param(ghsic_data_serial_tx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_serial_rx_q_size = GHSIC_DATA_SERIAL_RX_Q_SIZE; +module_param(ghsic_data_serial_rx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_rx_req_size = GHSIC_DATA_RX_REQ_SIZE; +module_param(ghsic_data_rx_req_size, uint, S_IRUGO | S_IWUSR); + +unsigned int ghsic_data_tx_intr_thld = GHSIC_DATA_TX_INTR_THRESHOLD; +module_param(ghsic_data_tx_intr_thld, uint, S_IRUGO | S_IWUSR); + +/*flow ctrl*/ +#define GHSIC_DATA_FLOW_CTRL_EN_THRESHOLD 500 +#define GHSIC_DATA_FLOW_CTRL_DISABLE 300 +#define GHSIC_DATA_FLOW_CTRL_SUPPORT 1 +#define GHSIC_DATA_PENDLIMIT_WITH_BRIDGE 500 + +static unsigned int ghsic_data_fctrl_support = GHSIC_DATA_FLOW_CTRL_SUPPORT; +module_param(ghsic_data_fctrl_support, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_fctrl_en_thld = + GHSIC_DATA_FLOW_CTRL_EN_THRESHOLD; +module_param(ghsic_data_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_fctrl_dis_thld = GHSIC_DATA_FLOW_CTRL_DISABLE; +module_param(ghsic_data_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsic_data_pend_limit_with_bridge = + GHSIC_DATA_PENDLIMIT_WITH_BRIDGE; +module_param(ghsic_data_pend_limit_with_bridge, uint, S_IRUGO | S_IWUSR); + +#define CH_OPENED 0 +#define CH_READY 1 + +struct gdata_port { + /* port */ + unsigned port_num; + + /* gadget */ + atomic_t connected; + struct usb_ep *in; + struct usb_ep *out; + + enum gadget_type gtype; + + /* data transfer queues */ + unsigned int tx_q_size; + struct list_head tx_idle; + struct sk_buff_head tx_skb_q; + spinlock_t tx_lock; + + unsigned int rx_q_size; + struct list_head rx_idle; + struct sk_buff_head rx_skb_q; + spinlock_t rx_lock; + + /* work */ + struct workqueue_struct *wq; + struct work_struct connect_w; + struct work_struct disconnect_w; + struct work_struct write_tomdm_w; + struct work_struct write_tohost_w; + + struct bridge brdg; + + /*bridge status*/ + unsigned long bridge_sts; + + unsigned int n_tx_req_queued; + + /*counters*/ + unsigned long to_modem; + unsigned long to_host; + unsigned int rx_throttled_cnt; + unsigned int rx_unthrottled_cnt; + unsigned int tx_throttled_cnt; + unsigned int tx_unthrottled_cnt; + unsigned int tomodem_drp_cnt; + unsigned int unthrottled_pnd_skbs; +}; + +static struct { + struct gdata_port *port; + struct platform_driver pdrv; +} gdata_ports[NUM_PORTS]; + +static unsigned int get_timestamp(void); +static void dbg_timestamp(char *, struct sk_buff *); +static void ghsic_data_start_rx(struct gdata_port *port); + +static void ghsic_data_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 ghsic_data_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:%s head:%p num:%d cb:%p", __func__, + ep->name, 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; +} + +static void ghsic_data_unthrottle_tx(void *ctx) +{ + struct gdata_port *port = ctx; + unsigned long flags; + + if (!port || !atomic_read(&port->connected)) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + port->tx_unthrottled_cnt++; + spin_unlock_irqrestore(&port->rx_lock, flags); + + queue_work(port->wq, &port->write_tomdm_w); + pr_debug("%s: port num =%d unthrottled\n", __func__, + port->port_num); +} + +static void ghsic_data_write_tohost(struct work_struct *w) +{ + unsigned long flags; + struct sk_buff *skb; + int ret; + struct usb_request *req; + struct usb_ep *ep; + struct gdata_port *port; + struct timestamp_info *info; + + port = container_of(w, struct gdata_port, write_tohost_w); + + if (!port) + return; + + spin_lock_irqsave(&port->tx_lock, flags); + ep = port->in; + if (!ep) { + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + + while (!list_empty(&port->tx_idle)) { + skb = __skb_dequeue(&port->tx_skb_q); + if (!skb) + break; + + req = list_first_entry(&port->tx_idle, struct usb_request, + list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + + port->n_tx_req_queued++; + if (port->n_tx_req_queued == ghsic_data_tx_intr_thld) { + req->no_interrupt = 0; + port->n_tx_req_queued = 0; + } else { + req->no_interrupt = 1; + } + + list_del(&req->list); + + info = (struct timestamp_info *)skb->cb; + info->tx_queued = get_timestamp(); + spin_unlock_irqrestore(&port->tx_lock, flags); + ret = usb_ep_queue(ep, req, GFP_KERNEL); + spin_lock_irqsave(&port->tx_lock, flags); + if (ret) { + pr_err("%s: usb epIn failed\n", __func__); + list_add(&req->list, &port->tx_idle); + dev_kfree_skb_any(skb); + break; + } + port->to_host++; + if (ghsic_data_fctrl_support && + port->tx_skb_q.qlen <= ghsic_data_fctrl_dis_thld && + test_and_clear_bit(RX_THROTTLED, &port->brdg.flags)) { + port->rx_unthrottled_cnt++; + port->unthrottled_pnd_skbs = port->tx_skb_q.qlen; + pr_debug_ratelimited("%s: disable flow ctrl:" + " tx skbq len: %u\n", + __func__, port->tx_skb_q.qlen); + data_bridge_unthrottle_rx(port->brdg.ch_id); + } + } + spin_unlock_irqrestore(&port->tx_lock, flags); +} + +static int ghsic_data_receive(void *p, void *data, size_t len) +{ + struct gdata_port *port = p; + unsigned long flags; + struct sk_buff *skb = data; + + if (!port || !atomic_read(&port->connected)) { + dev_kfree_skb_any(skb); + return -ENOTCONN; + } + + pr_debug("%s: p:%p#%d skb_len:%d\n", __func__, + port, port->port_num, skb->len); + + spin_lock_irqsave(&port->tx_lock, flags); + __skb_queue_tail(&port->tx_skb_q, skb); + + if (ghsic_data_fctrl_support && + port->tx_skb_q.qlen >= ghsic_data_fctrl_en_thld) { + set_bit(RX_THROTTLED, &port->brdg.flags); + port->rx_throttled_cnt++; + pr_debug_ratelimited("%s: flow ctrl enabled: tx skbq len: %u\n", + __func__, port->tx_skb_q.qlen); + spin_unlock_irqrestore(&port->tx_lock, flags); + queue_work(port->wq, &port->write_tohost_w); + return -EBUSY; + } + + spin_unlock_irqrestore(&port->tx_lock, flags); + + queue_work(port->wq, &port->write_tohost_w); + + return 0; +} + +static void ghsic_data_write_tomdm(struct work_struct *w) +{ + struct gdata_port *port; + struct sk_buff *skb; + struct timestamp_info *info; + unsigned long flags; + int ret; + + port = container_of(w, struct gdata_port, write_tomdm_w); + + if (!port || !atomic_read(&port->connected)) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + if (test_bit(TX_THROTTLED, &port->brdg.flags)) { + spin_unlock_irqrestore(&port->rx_lock, flags); + goto start_rx; + } + + while ((skb = __skb_dequeue(&port->rx_skb_q))) { + pr_debug("%s: port:%p tom:%lu pno:%d\n", __func__, + port, port->to_modem, port->port_num); + + info = (struct timestamp_info *)skb->cb; + info->rx_done_sent = get_timestamp(); + spin_unlock_irqrestore(&port->rx_lock, flags); + ret = data_bridge_write(port->brdg.ch_id, skb); + spin_lock_irqsave(&port->rx_lock, flags); + if (ret < 0) { + if (ret == -EBUSY) { + /*flow control*/ + port->tx_throttled_cnt++; + break; + } + pr_err_ratelimited("%s: write error:%d\n", + __func__, ret); + port->tomodem_drp_cnt++; + dev_kfree_skb_any(skb); + break; + } + port->to_modem++; + } + spin_unlock_irqrestore(&port->rx_lock, flags); +start_rx: + ghsic_data_start_rx(port); +} + +static void ghsic_data_epin_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gdata_port *port = ep->driver_data; + struct sk_buff *skb = req->context; + int status = req->status; + + switch (status) { + case 0: + /* successful completion */ + dbg_timestamp("DL", skb); + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + dev_kfree_skb_any(skb); + req->buf = 0; + usb_ep_free_request(ep, req); + return; + default: + pr_err("%s: data tx ep error %d\n", __func__, status); + break; + } + + dev_kfree_skb_any(skb); + + spin_lock(&port->tx_lock); + list_add_tail(&req->list, &port->tx_idle); + spin_unlock(&port->tx_lock); + + queue_work(port->wq, &port->write_tohost_w); +} + +static void +ghsic_data_epout_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gdata_port *port = ep->driver_data; + struct sk_buff *skb = req->context; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + 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: + pr_err_ratelimited("%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->rx_lock); + if (queue) { + info->rx_done = get_timestamp(); + __skb_queue_tail(&port->rx_skb_q, skb); + list_add_tail(&req->list, &port->rx_idle); + queue_work(port->wq, &port->write_tomdm_w); + } + spin_unlock(&port->rx_lock); +} + +static void ghsic_data_start_rx(struct gdata_port *port) +{ + struct usb_request *req; + struct usb_ep *ep; + unsigned long flags; + int ret; + struct sk_buff *skb; + struct timestamp_info *info; + unsigned int created; + + pr_debug("%s: port:%p\n", __func__, port); + if (!port) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + ep = port->out; + if (!ep) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + while (atomic_read(&port->connected) && !list_empty(&port->rx_idle)) { + if (port->rx_skb_q.qlen > ghsic_data_pend_limit_with_bridge) + break; + + req = list_first_entry(&port->rx_idle, + struct usb_request, list); + + created = get_timestamp(); + skb = alloc_skb(ghsic_data_rx_req_size, GFP_ATOMIC); + if (!skb) + break; + info = (struct timestamp_info *)skb->cb; + info->created = created; + list_del(&req->list); + req->buf = skb->data; + req->length = ghsic_data_rx_req_size; + req->context = skb; + + info->rx_queued = get_timestamp(); + spin_unlock_irqrestore(&port->rx_lock, flags); + ret = usb_ep_queue(ep, req, GFP_KERNEL); + spin_lock_irqsave(&port->rx_lock, flags); + if (ret) { + dev_kfree_skb_any(skb); + + pr_err_ratelimited("%s: rx queue failed\n", __func__); + + if (atomic_read(&port->connected)) + list_add(&req->list, &port->rx_idle); + else + usb_ep_free_request(ep, req); + break; + } + } + spin_unlock_irqrestore(&port->rx_lock, flags); +} + +static void ghsic_data_start_io(struct gdata_port *port) +{ + unsigned long flags; + struct usb_ep *ep; + int ret; + + pr_debug("%s: port:%p\n", __func__, port); + + if (!port) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + ep = port->out; + if (!ep) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + ret = ghsic_data_alloc_requests(ep, &port->rx_idle, + port->rx_q_size, ghsic_data_epout_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: rx req allocation failed\n", __func__); + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + spin_unlock_irqrestore(&port->rx_lock, flags); + + spin_lock_irqsave(&port->tx_lock, flags); + ep = port->in; + if (!ep) { + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + + ret = ghsic_data_alloc_requests(ep, &port->tx_idle, + port->tx_q_size, ghsic_data_epin_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: tx req allocation failed\n", __func__); + ghsic_data_free_requests(ep, &port->rx_idle); + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + spin_unlock_irqrestore(&port->tx_lock, flags); + + /* queue out requests */ + ghsic_data_start_rx(port); +} + +static void ghsic_data_connect_w(struct work_struct *w) +{ + struct gdata_port *port = + container_of(w, struct gdata_port, connect_w); + int ret; + + if (!port || !atomic_read(&port->connected) || + !test_bit(CH_READY, &port->bridge_sts)) + return; + + pr_debug("%s: port:%p\n", __func__, port); + + ret = data_bridge_open(&port->brdg); + if (ret) { + pr_err("%s: unable open bridge ch:%d err:%d\n", + __func__, port->brdg.ch_id, ret); + return; + } + + set_bit(CH_OPENED, &port->bridge_sts); + + ghsic_data_start_io(port); +} + +static void ghsic_data_disconnect_w(struct work_struct *w) +{ + struct gdata_port *port = + container_of(w, struct gdata_port, disconnect_w); + + if (!test_bit(CH_OPENED, &port->bridge_sts)) + return; + + data_bridge_close(port->brdg.ch_id); + clear_bit(CH_OPENED, &port->bridge_sts); +} + +static void ghsic_data_free_buffers(struct gdata_port *port) +{ + struct sk_buff *skb; + unsigned long flags; + + if (!port) + return; + + spin_lock_irqsave(&port->tx_lock, flags); + if (!port->in) { + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + + ghsic_data_free_requests(port->in, &port->tx_idle); + + while ((skb = __skb_dequeue(&port->tx_skb_q))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + if (!port->out) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + ghsic_data_free_requests(port->out, &port->rx_idle); + + while ((skb = __skb_dequeue(&port->rx_skb_q))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&port->rx_lock, flags); +} + +static int ghsic_data_probe(struct platform_device *pdev) +{ + struct gdata_port *port; + + pr_debug("%s: name:%s no_data_ports= %d\n", + __func__, pdev->name, no_data_ports); + + if (pdev->id >= no_data_ports) { + pr_err("%s: invalid port: %d\n", __func__, pdev->id); + return -EINVAL; + } + + port = gdata_ports[pdev->id].port; + set_bit(CH_READY, &port->bridge_sts); + + /* if usb is online, try opening bridge */ + if (atomic_read(&port->connected)) + queue_work(port->wq, &port->connect_w); + + return 0; +} + +/* mdm disconnect */ +static int ghsic_data_remove(struct platform_device *pdev) +{ + struct gdata_port *port; + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + if (pdev->id >= no_data_ports) { + pr_err("%s: invalid port: %d\n", __func__, pdev->id); + return -EINVAL; + } + + port = gdata_ports[pdev->id].port; + + ep_in = port->in; + if (ep_in) + usb_ep_fifo_flush(ep_in); + + ep_out = port->out; + if (ep_out) + usb_ep_fifo_flush(ep_out); + + ghsic_data_free_buffers(port); + + data_bridge_close(port->brdg.ch_id); + + clear_bit(CH_READY, &port->bridge_sts); + clear_bit(CH_OPENED, &port->bridge_sts); + + return 0; +} + +static void ghsic_data_port_free(int portno) +{ + struct gdata_port *port = gdata_ports[portno].port; + struct platform_driver *pdrv = &gdata_ports[portno].pdrv; + + destroy_workqueue(port->wq); + kfree(port); + + if (pdrv) + platform_driver_unregister(pdrv); +} + +static int ghsic_data_port_alloc(unsigned port_num, enum gadget_type gtype) +{ + struct gdata_port *port; + struct platform_driver *pdrv; + + port = kzalloc(sizeof(struct gdata_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->wq = create_singlethread_workqueue(data_bridge_names[port_num]); + if (!port->wq) { + pr_err("%s: Unable to create workqueue:%s\n", + __func__, data_bridge_names[port_num]); + kfree(port); + return -ENOMEM; + } + port->port_num = port_num; + + /* port initialization */ + spin_lock_init(&port->rx_lock); + spin_lock_init(&port->tx_lock); + + INIT_WORK(&port->connect_w, ghsic_data_connect_w); + INIT_WORK(&port->disconnect_w, ghsic_data_disconnect_w); + INIT_WORK(&port->write_tohost_w, ghsic_data_write_tohost); + INIT_WORK(&port->write_tomdm_w, ghsic_data_write_tomdm); + + INIT_LIST_HEAD(&port->tx_idle); + INIT_LIST_HEAD(&port->rx_idle); + + skb_queue_head_init(&port->tx_skb_q); + skb_queue_head_init(&port->rx_skb_q); + + port->gtype = gtype; + port->brdg.ch_id = port_num; + port->brdg.ctx = port; + port->brdg.ops.send_pkt = ghsic_data_receive; + port->brdg.ops.unthrottle_tx = ghsic_data_unthrottle_tx; + gdata_ports[port_num].port = port; + + pdrv = &gdata_ports[port_num].pdrv; + pdrv->probe = ghsic_data_probe; + pdrv->remove = ghsic_data_remove; + pdrv->driver.name = data_bridge_names[port_num]; + pdrv->driver.owner = THIS_MODULE; + + platform_driver_register(pdrv); + + pr_debug("%s: port:%p portno:%d\n", __func__, port, port_num); + + return 0; +} + +void ghsic_data_disconnect(void *gptr, int port_num) +{ + struct gdata_port *port; + unsigned long flags; + + pr_debug("%s: port#%d\n", __func__, port_num); + + port = gdata_ports[port_num].port; + + if (port_num > no_data_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return; + } + + if (!gptr || !port) { + pr_err("%s: port is null\n", __func__); + return; + } + + ghsic_data_free_buffers(port); + + /* disable endpoints */ + if (port->in) + usb_ep_disable(port->out); + + if (port->out) + usb_ep_disable(port->in); + + atomic_set(&port->connected, 0); + + spin_lock_irqsave(&port->tx_lock, flags); + port->in = NULL; + port->n_tx_req_queued = 0; + clear_bit(RX_THROTTLED, &port->brdg.flags); + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->out = NULL; + clear_bit(TX_THROTTLED, &port->brdg.flags); + spin_unlock_irqrestore(&port->rx_lock, flags); + + queue_work(port->wq, &port->disconnect_w); +} + +int ghsic_data_connect(void *gptr, int port_num) +{ + struct gdata_port *port; + struct gserial *gser; + struct grmnet *gr; + unsigned long flags; + int ret = 0; + + pr_debug("%s: port#%d\n", __func__, port_num); + + port = gdata_ports[port_num].port; + + if (port_num > no_data_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if (!gptr || !port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + + if (port->gtype == USB_GADGET_SERIAL) { + gser = gptr; + + spin_lock_irqsave(&port->tx_lock, flags); + port->in = gser->in; + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->out = gser->out; + spin_unlock_irqrestore(&port->rx_lock, flags); + + port->tx_q_size = ghsic_data_serial_tx_q_size; + port->rx_q_size = ghsic_data_serial_rx_q_size; + gser->in->driver_data = port; + gser->out->driver_data = port; + } else { + gr = gptr; + + spin_lock_irqsave(&port->tx_lock, flags); + port->in = gr->in; + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->out = gr->out; + spin_unlock_irqrestore(&port->rx_lock, flags); + + port->tx_q_size = ghsic_data_rmnet_tx_q_size; + port->rx_q_size = ghsic_data_rmnet_rx_q_size; + gr->in->driver_data = port; + gr->out->driver_data = port; + } + + ret = usb_ep_enable(port->in); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:IN ep:%p", + __func__, port->in); + goto fail; + } + + ret = usb_ep_enable(port->out); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p", + __func__, port->out); + usb_ep_disable(port->in); + goto fail; + } + + atomic_set(&port->connected, 1); + + spin_lock_irqsave(&port->tx_lock, flags); + port->to_host = 0; + port->rx_throttled_cnt = 0; + port->rx_unthrottled_cnt = 0; + port->unthrottled_pnd_skbs = 0; + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->to_modem = 0; + port->tomodem_drp_cnt = 0; + port->tx_throttled_cnt = 0; + port->tx_unthrottled_cnt = 0; + spin_unlock_irqrestore(&port->rx_lock, flags); + + queue_work(port->wq, &port->connect_w); +fail: + return ret; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 + +static unsigned int record_timestamp; +module_param(record_timestamp, uint, S_IRUGO | S_IWUSR); + +static struct timestamp_buf dbg_data = { + .idx = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/*get_timestamp - returns time of day in us */ +static unsigned int get_timestamp(void) +{ + struct timeval tval; + unsigned int stamp; + + if (!record_timestamp) + return 0; + + do_gettimeofday(&tval); + /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = tval.tv_sec & 0xFFF; + stamp = stamp * 1000000 + tval.tv_usec; + return stamp; +} + +static void dbg_inc(unsigned *idx) +{ + *idx = (*idx + 1) & (DBG_DATA_MAX-1); +} + +/** +* dbg_timestamp - Stores timestamp values of a SKB life cycle +* to debug buffer +* @event: "DL": Downlink Data +* @skb: SKB used to store timestamp values to debug buffer +*/ +static void dbg_timestamp(char *event, struct sk_buff * skb) +{ + unsigned long flags; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + + if (!record_timestamp) + return; + + write_lock_irqsave(&dbg_data.lck, flags); + + scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, + "%p %u[%s] %u %u %u %u %u %u\n", + skb, skb->len, event, info->created, info->rx_queued, + info->rx_done, info->rx_done_sent, info->tx_queued, + get_timestamp()); + + dbg_inc(&dbg_data.idx); + + write_unlock_irqrestore(&dbg_data.lck, flags); +} + +/* show_timestamp: displays the timestamp buffer */ +static ssize_t show_timestamp(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + unsigned long flags; + unsigned i; + unsigned j = 0; + char *buf; + int ret = 0; + + if (!record_timestamp) + return 0; + + buf = kzalloc(sizeof(char) * 4 * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + read_lock_irqsave(&dbg_data.lck, flags); + + i = dbg_data.idx; + for (dbg_inc(&i); i != dbg_data.idx; dbg_inc(&i)) { + if (!strnlen(dbg_data.buf[i], DBG_DATA_MSG)) + continue; + j += scnprintf(buf + j, (4 * DEBUG_BUF_SIZE) - j, + "%s\n", dbg_data.buf[i]); + } + + read_unlock_irqrestore(&dbg_data.lck, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, j); + + kfree(buf); + + return ret; +} + +const struct file_operations gdata_timestamp_ops = { + .read = show_timestamp, +}; + +static ssize_t ghsic_data_read_stats(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos) +{ + struct gdata_port *port; + struct platform_driver *pdrv; + 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 < no_data_ports; i++) { + port = gdata_ports[i].port; + if (!port) + continue; + pdrv = &gdata_ports[i].pdrv; + + spin_lock_irqsave(&port->rx_lock, flags); + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName: %s\n" + "#PORT:%d port#: %p\n" + "data_ch_open: %d\n" + "data_ch_ready: %d\n" + "\n******UL INFO*****\n\n" + "dpkts_to_modem: %lu\n" + "tomodem_drp_cnt: %u\n" + "rx_buf_len: %u\n" + "tx thld cnt %u\n" + "tx unthld cnt %u\n" + "TX_THROTTLED %d\n", + pdrv->driver.name, + i, port, + test_bit(CH_OPENED, &port->bridge_sts), + test_bit(CH_READY, &port->bridge_sts), + port->to_modem, + port->tomodem_drp_cnt, + port->rx_skb_q.qlen, + port->tx_throttled_cnt, + port->tx_unthrottled_cnt, + test_bit(TX_THROTTLED, &port->brdg.flags)); + spin_unlock_irqrestore(&port->rx_lock, flags); + + spin_lock_irqsave(&port->tx_lock, flags); + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\n******DL INFO******\n\n" + "dpkts_to_usbhost: %lu\n" + "tx_buf_len: %u\n" + "rx thld cnt %u\n" + "rx unthld cnt %u\n" + "uthld pnd skbs %u\n" + "RX_THROTTLED %d\n", + port->to_host, + port->tx_skb_q.qlen, + port->rx_throttled_cnt, + port->rx_unthrottled_cnt, + port->unthrottled_pnd_skbs, + test_bit(RX_THROTTLED, &port->brdg.flags)); + spin_unlock_irqrestore(&port->tx_lock, flags); + + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t ghsic_data_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct gdata_port *port; + int i; + unsigned long flags; + + for (i = 0; i < no_data_ports; i++) { + port = gdata_ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->rx_lock, flags); + port->to_modem = 0; + port->tomodem_drp_cnt = 0; + port->tx_throttled_cnt = 0; + port->tx_unthrottled_cnt = 0; + spin_unlock_irqrestore(&port->rx_lock, flags); + + spin_lock_irqsave(&port->tx_lock, flags); + port->to_host = 0; + port->rx_throttled_cnt = 0; + port->rx_unthrottled_cnt = 0; + port->unthrottled_pnd_skbs = 0; + spin_unlock_irqrestore(&port->tx_lock, flags); + } + return count; +} + +const struct file_operations ghsic_stats_ops = { + .read = ghsic_data_read_stats, + .write = ghsic_data_reset_stats, +}; + +static struct dentry *gdata_dent; +static struct dentry *gdata_dfile_stats; +static struct dentry *gdata_dfile_tstamp; + +static void ghsic_data_debugfs_init(void) +{ + gdata_dent = debugfs_create_dir("ghsic_data_xport", 0); + if (IS_ERR(gdata_dent)) + return; + + gdata_dfile_stats = debugfs_create_file("status", 0444, gdata_dent, 0, + &ghsic_stats_ops); + if (!gdata_dfile_stats || IS_ERR(gdata_dfile_stats)) { + debugfs_remove(gdata_dent); + return; + } + + gdata_dfile_tstamp = debugfs_create_file("timestamp", 0644, gdata_dent, + 0, &gdata_timestamp_ops); + if (!gdata_dfile_tstamp || IS_ERR(gdata_dfile_tstamp)) + debugfs_remove(gdata_dent); +} + +static void ghsic_data_debugfs_exit(void) +{ + debugfs_remove(gdata_dfile_stats); + debugfs_remove(gdata_dfile_tstamp); + debugfs_remove(gdata_dent); +} + +#else +static void ghsic_data_debugfs_init(void) { } +static void ghsic_data_debugfs_exit(void) { } +static void dbg_timestamp(char *event, struct sk_buff * skb) +{ + return; +} +static unsigned int get_timestamp(void) +{ + return 0; +} + +#endif + +int ghsic_data_setup(unsigned num_ports, enum gadget_type gtype) +{ + int first_port_id = no_data_ports; + int total_num_ports = num_ports + no_data_ports; + int ret = 0; + int i; + + if (!num_ports || total_num_ports > NUM_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, num_ports); + return -EINVAL; + } + pr_debug("%s: count: %d\n", __func__, num_ports); + + for (i = first_port_id; i < (num_ports + first_port_id); i++) { + + /*probe can be called while port_alloc,so update no_data_ports*/ + no_data_ports++; + ret = ghsic_data_port_alloc(i, gtype); + if (ret) { + no_data_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_ports; + } + } + + /*return the starting index*/ + return first_port_id; + +free_ports: + for (i = first_port_id; i < no_data_ports; i++) + ghsic_data_port_free(i); + no_data_ports = first_port_id; + + return ret; +} + +static int __init ghsic_data_init(void) +{ + ghsic_data_debugfs_init(); + + return 0; +} +module_init(ghsic_data_init); + +static void __exit ghsic_data_exit(void) +{ + ghsic_data_debugfs_exit(); +} +module_exit(ghsic_data_exit); +MODULE_DESCRIPTION("hsic data xport driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/u_data_hsuart.c b/drivers/usb/gadget/u_data_hsuart.c new file mode 100644 index 0000000000000000000000000000000000000000..b2c57c45e4179fbb25a021b4d93fa380ceb9bdd1 --- /dev/null +++ b/drivers/usb/gadget/u_data_hsuart.c @@ -0,0 +1,1142 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 unsigned int num_data_ports; + +static const char *ghsuart_data_names[] = { + "SMUX_DUN_DATA_HSUART", + "SMUX_RMNET_DATA_HSUART" +}; + +#define DATA_BRIDGE_NAME_MAX_LEN 20 + +#define GHSUART_DATA_RMNET_RX_Q_SIZE 10 +#define GHSUART_DATA_RMNET_TX_Q_SIZE 20 +#define GHSUART_DATA_SERIAL_RX_Q_SIZE 5 +#define GHSUART_DATA_SERIAL_TX_Q_SIZE 5 +#define GHSUART_DATA_RX_REQ_SIZE 2048 +#define GHSUART_DATA_TX_INTR_THRESHOLD 1 + +/* from cdc-acm.h */ +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ +#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) + +static unsigned int ghsuart_data_rmnet_tx_q_size = GHSUART_DATA_RMNET_TX_Q_SIZE; +module_param(ghsuart_data_rmnet_tx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsuart_data_rmnet_rx_q_size = GHSUART_DATA_RMNET_RX_Q_SIZE; +module_param(ghsuart_data_rmnet_rx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsuart_data_serial_tx_q_size = + GHSUART_DATA_SERIAL_TX_Q_SIZE; +module_param(ghsuart_data_serial_tx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsuart_data_serial_rx_q_size = + GHSUART_DATA_SERIAL_RX_Q_SIZE; +module_param(ghsuart_data_serial_rx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int ghsuart_data_rx_req_size = GHSUART_DATA_RX_REQ_SIZE; +module_param(ghsuart_data_rx_req_size, uint, S_IRUGO | S_IWUSR); + +unsigned int ghsuart_data_tx_intr_thld = GHSUART_DATA_TX_INTR_THRESHOLD; +module_param(ghsuart_data_tx_intr_thld, uint, S_IRUGO | S_IWUSR); + +#define CH_OPENED 0 +#define CH_READY 1 + +struct ghsuart_data_port { + /* port */ + unsigned port_num; + + /* gadget */ + atomic_t connected; + struct usb_ep *in; + struct usb_ep *out; + + enum gadget_type gtype; + spinlock_t port_lock; + void *port_usb; + + /* data transfer queues */ + unsigned int tx_q_size; + struct list_head tx_idle; + struct sk_buff_head tx_skb_q; + spinlock_t tx_lock; + + unsigned int rx_q_size; + struct list_head rx_idle; + struct sk_buff_head rx_skb_q; + spinlock_t rx_lock; + + /* work */ + struct workqueue_struct *wq; + struct work_struct connect_w; + struct work_struct disconnect_w; + struct work_struct write_tomdm_w; + struct work_struct write_tohost_w; + void *ctx; + unsigned int ch_id; + /* flow control bits */ + unsigned long flags; + /* channel status */ + unsigned long channel_sts; + + unsigned int n_tx_req_queued; + + /* control bits */ + unsigned cbits_tomodem; + unsigned cbits_tohost; + + /* counters */ + unsigned long to_modem; + unsigned long to_host; + unsigned int tomodem_drp_cnt; +}; + +static struct { + struct ghsuart_data_port *port; + struct platform_driver pdrv; +} ghsuart_data_ports[NUM_HSUART_PORTS]; + +static void ghsuart_data_start_rx(struct ghsuart_data_port *port); + +static void ghsuart_data_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 ghsuart_data_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:%s head:%p num:%d cb:%p", __func__, + ep->name, head, num, cb); + + for (i = 0; i < num; i++) { + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_err("%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 ghsuart_data_write_tohost(struct work_struct *w) +{ + unsigned long flags; + struct sk_buff *skb; + int ret; + struct usb_request *req; + struct usb_ep *ep; + struct ghsuart_data_port *port; + + port = container_of(w, struct ghsuart_data_port, write_tohost_w); + + if (!port || !atomic_read(&port->connected)) + return; + + spin_lock_irqsave(&port->tx_lock, flags); + ep = port->in; + if (!ep) { + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + + while (!list_empty(&port->tx_idle)) { + skb = __skb_dequeue(&port->tx_skb_q); + if (!skb) + break; + + req = list_first_entry(&port->tx_idle, struct usb_request, + list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + + port->n_tx_req_queued++; + if (port->n_tx_req_queued == ghsuart_data_tx_intr_thld) { + req->no_interrupt = 0; + port->n_tx_req_queued = 0; + } else { + req->no_interrupt = 1; + } + + list_del(&req->list); + + spin_unlock_irqrestore(&port->tx_lock, flags); + ret = usb_ep_queue(ep, req, GFP_KERNEL); + spin_lock_irqsave(&port->tx_lock, flags); + if (ret) { + pr_err("%s: usb epIn failed\n", __func__); + list_add(&req->list, &port->tx_idle); + dev_kfree_skb_any(skb); + break; + } + port->to_host++; + } + spin_unlock_irqrestore(&port->tx_lock, flags); +} + +static void ghsuart_data_write_tomdm(struct work_struct *w) +{ + struct ghsuart_data_port *port; + struct sk_buff *skb; + unsigned long flags; + int ret; + + port = container_of(w, struct ghsuart_data_port, write_tomdm_w); + + if (!port || !atomic_read(&port->connected)) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + if (test_bit(TX_THROTTLED, &port->flags)) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + while ((skb = __skb_dequeue(&port->rx_skb_q))) { + pr_debug("%s: port:%p tom:%lu pno:%d\n", __func__, + port, port->to_modem, port->port_num); + + spin_unlock_irqrestore(&port->rx_lock, flags); + ret = msm_smux_write(port->ch_id, skb, skb->data, skb->len); + spin_lock_irqsave(&port->rx_lock, flags); + if (ret < 0) { + if (ret == -EAGAIN) { + /*flow control*/ + set_bit(TX_THROTTLED, &port->flags); + __skb_queue_head(&port->rx_skb_q, skb); + break; + } + pr_err_ratelimited("%s: write error:%d\n", + __func__, ret); + port->tomodem_drp_cnt++; + dev_kfree_skb_any(skb); + break; + } + port->to_modem++; + } + spin_unlock_irqrestore(&port->rx_lock, flags); + ghsuart_data_start_rx(port); +} + +static void ghsuart_data_epin_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct ghsuart_data_port *port = ep->driver_data; + struct sk_buff *skb = req->context; + int status = req->status; + + switch (status) { + case 0: + /* successful completion */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + dev_kfree_skb_any(skb); + req->buf = 0; + usb_ep_free_request(ep, req); + return; + default: + pr_err("%s: data tx ep error %d\n", __func__, status); + break; + } + + dev_kfree_skb_any(skb); + + spin_lock(&port->tx_lock); + list_add_tail(&req->list, &port->tx_idle); + spin_unlock(&port->tx_lock); + + queue_work(port->wq, &port->write_tohost_w); +} + +static void +ghsuart_data_epout_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct ghsuart_data_port *port = ep->driver_data; + 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: + pr_err_ratelimited("%s: %s response error %d, %d/%d\n", + __func__, ep->name, status, + req->actual, req->length); + dev_kfree_skb_any(skb); + list_add_tail(&req->list, &port->rx_idle); + return; + } + + spin_lock(&port->rx_lock); + if (queue) { + __skb_queue_tail(&port->rx_skb_q, skb); + list_add_tail(&req->list, &port->rx_idle); + queue_work(port->wq, &port->write_tomdm_w); + } + spin_unlock(&port->rx_lock); +} + +static void ghsuart_data_start_rx(struct ghsuart_data_port *port) +{ + struct usb_request *req; + struct usb_ep *ep; + unsigned long flags; + int ret; + struct sk_buff *skb; + + pr_debug("%s: port:%p\n", __func__, port); + if (!port) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + ep = port->out; + if (!ep) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + if (test_bit(TX_THROTTLED, &port->flags)) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + while (atomic_read(&port->connected) && !list_empty(&port->rx_idle)) { + + req = list_first_entry(&port->rx_idle, + struct usb_request, list); + + skb = alloc_skb(ghsuart_data_rx_req_size, GFP_ATOMIC); + if (!skb) + break; + list_del(&req->list); + req->buf = skb->data; + req->length = ghsuart_data_rx_req_size; + req->context = skb; + + spin_unlock_irqrestore(&port->rx_lock, flags); + ret = usb_ep_queue(ep, req, GFP_KERNEL); + spin_lock_irqsave(&port->rx_lock, flags); + if (ret) { + dev_kfree_skb_any(skb); + + pr_err_ratelimited("%s: rx queue failed\n", __func__); + + if (atomic_read(&port->connected)) + list_add(&req->list, &port->rx_idle); + else + usb_ep_free_request(ep, req); + break; + } + } + spin_unlock_irqrestore(&port->rx_lock, flags); +} + +static void ghsuart_data_start_io(struct ghsuart_data_port *port) +{ + unsigned long flags; + struct usb_ep *ep; + int ret; + + pr_debug("%s: port:%p\n", __func__, port); + + if (!port) + return; + + spin_lock_irqsave(&port->rx_lock, flags); + ep = port->out; + if (!ep) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + ret = ghsuart_data_alloc_requests(ep, &port->rx_idle, + port->rx_q_size, ghsuart_data_epout_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: rx req allocation failed\n", __func__); + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + spin_unlock_irqrestore(&port->rx_lock, flags); + + spin_lock_irqsave(&port->tx_lock, flags); + ep = port->in; + if (!ep) { + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + + ret = ghsuart_data_alloc_requests(ep, &port->tx_idle, + port->tx_q_size, ghsuart_data_epin_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: tx req allocation failed\n", __func__); + ghsuart_data_free_requests(ep, &port->rx_idle); + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + spin_unlock_irqrestore(&port->tx_lock, flags); + + /* queue out requests */ + ghsuart_data_start_rx(port); +} + +static void ghsuart_dunctrl_status(void *ctxt, unsigned int ctrl_bits) +{ + struct ghsuart_data_port *port = ctxt; + struct gserial *gser; + unsigned long flags; + + pr_debug("%s - input control lines: dcd%c dsr%c break%c " + "ring%c framing%c parity%c overrun%c\n", __func__, + ctrl_bits & ACM_CTRL_DCD ? '+' : '-', + ctrl_bits & ACM_CTRL_DSR ? '+' : '-', + ctrl_bits & ACM_CTRL_BRK ? '+' : '-', + ctrl_bits & ACM_CTRL_RI ? '+' : '-', + ctrl_bits & ACM_CTRL_FRAMING ? '+' : '-', + ctrl_bits & ACM_CTRL_PARITY ? '+' : '-', + ctrl_bits & ACM_CTRL_OVERRUN ? '+' : '-'); + + spin_lock_irqsave(&port->port_lock, flags); + port->cbits_tohost = ctrl_bits; + gser = port->port_usb; + spin_unlock_irqrestore(&port->port_lock, flags); + if (gser && gser->send_modem_ctrl_bits) + gser->send_modem_ctrl_bits(gser, ctrl_bits); +} + +const char *event_string(int event_type) +{ + switch (event_type) { + case SMUX_CONNECTED: + return "SMUX_CONNECTED"; + case SMUX_DISCONNECTED: + return "SMUX_DISCONNECTED"; + case SMUX_READ_DONE: + return "SMUX_READ_DONE"; + case SMUX_READ_FAIL: + return "SMUX_READ_FAIL"; + case SMUX_WRITE_DONE: + return "SMUX_WRITE_DONE"; + case SMUX_WRITE_FAIL: + return "SMUX_WRITE_FAIL"; + case SMUX_HIGH_WM_HIT: + return "SMUX_HIGH_WM_HIT"; + case SMUX_LOW_WM_HIT: + return "SMUX_LOW_WM_HIT"; + case SMUX_TIOCM_UPDATE: + return "SMUX_TIOCM_UPDATE"; + default: + return "UNDEFINED"; + } +} + +static void ghsuart_notify_event(void *priv, int event_type, + const void *metadata) +{ + struct ghsuart_data_port *port = priv; + struct smux_meta_write *meta_write = + (struct smux_meta_write *) metadata; + struct smux_meta_read *meta_read = + (struct smux_meta_read *) metadata; + struct sk_buff *skb; + unsigned long flags; + unsigned int cbits; + struct gserial *gser; + + pr_debug("%s: event type: %s ", __func__, event_string(event_type)); + switch (event_type) { + case SMUX_CONNECTED: + set_bit(CH_OPENED, &port->channel_sts); + if (port->gtype == USB_GADGET_SERIAL) { + cbits = msm_smux_tiocm_get(port->ch_id); + if (cbits & ACM_CTRL_DCD) { + gser = port->port_usb; + if (gser && gser->connect) + gser->connect(gser); + } + } + ghsuart_data_start_io(port); + break; + case SMUX_DISCONNECTED: + clear_bit(CH_OPENED, &port->channel_sts); + break; + case SMUX_READ_DONE: + skb = meta_read->pkt_priv; + skb->data = meta_read->buffer; + skb->len = meta_read->len; + spin_lock_irqsave(&port->tx_lock, flags); + __skb_queue_tail(&port->tx_skb_q, skb); + spin_unlock_irqrestore(&port->tx_lock, flags); + queue_work(port->wq, &port->write_tohost_w); + break; + case SMUX_WRITE_DONE: + skb = meta_write->pkt_priv; + skb->data = meta_write->buffer; + dev_kfree_skb_any(skb); + queue_work(port->wq, &port->write_tomdm_w); + break; + case SMUX_READ_FAIL: + skb = meta_read->pkt_priv; + skb->data = meta_read->buffer; + dev_kfree_skb_any(skb); + break; + case SMUX_WRITE_FAIL: + skb = meta_write->pkt_priv; + skb->data = meta_write->buffer; + dev_kfree_skb_any(skb); + break; + case SMUX_HIGH_WM_HIT: + spin_lock_irqsave(&port->rx_lock, flags); + set_bit(TX_THROTTLED, &port->flags); + spin_unlock_irqrestore(&port->rx_lock, flags); + case SMUX_LOW_WM_HIT: + spin_lock_irqsave(&port->rx_lock, flags); + clear_bit(TX_THROTTLED, &port->flags); + spin_unlock_irqrestore(&port->rx_lock, flags); + queue_work(port->wq, &port->write_tomdm_w); + break; + case SMUX_TIOCM_UPDATE: + if (port->gtype == USB_GADGET_SERIAL) { + cbits = msm_smux_tiocm_get(port->ch_id); + ghsuart_dunctrl_status(port, cbits); + } + break; + default: + pr_err("%s:wrong event recieved\n", __func__); + } +} + +static int ghsuart_get_rx_buffer(void *priv, void **pkt_priv, + void **buffer, int size) +{ + struct sk_buff *skb; + + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + *pkt_priv = skb; + *buffer = skb->data; + + return 0; +} + +static void ghsuart_data_connect_w(struct work_struct *w) +{ + struct ghsuart_data_port *port = + container_of(w, struct ghsuart_data_port, connect_w); + int ret; + + if (!port || !atomic_read(&port->connected) || + !test_bit(CH_READY, &port->channel_sts)) + return; + + pr_debug("%s: port:%p\n", __func__, port); + + ret = msm_smux_open(port->ch_id, port, &ghsuart_notify_event, + &ghsuart_get_rx_buffer); + if (ret) { + pr_err("%s: unable to open smux ch:%d err:%d\n", + __func__, port->ch_id, ret); + return; + } +} + +static void ghsuart_data_disconnect_w(struct work_struct *w) +{ + struct ghsuart_data_port *port = + container_of(w, struct ghsuart_data_port, disconnect_w); + + if (!test_bit(CH_OPENED, &port->channel_sts)) + return; + + msm_smux_close(port->ch_id); + clear_bit(CH_OPENED, &port->channel_sts); +} + +static void ghsuart_data_free_buffers(struct ghsuart_data_port *port) +{ + struct sk_buff *skb; + unsigned long flags; + + if (!port) + return; + + spin_lock_irqsave(&port->tx_lock, flags); + if (!port->in) { + spin_unlock_irqrestore(&port->tx_lock, flags); + return; + } + + ghsuart_data_free_requests(port->in, &port->tx_idle); + + while ((skb = __skb_dequeue(&port->tx_skb_q))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + if (!port->out) { + spin_unlock_irqrestore(&port->rx_lock, flags); + return; + } + + ghsuart_data_free_requests(port->out, &port->rx_idle); + + while ((skb = __skb_dequeue(&port->rx_skb_q))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&port->rx_lock, flags); +} + +static int ghsuart_data_probe(struct platform_device *pdev) +{ + struct ghsuart_data_port *port; + + pr_debug("%s: name:%s num_data_ports= %d\n", + __func__, pdev->name, num_data_ports); + + if (pdev->id >= num_data_ports) { + pr_err("%s: invalid port: %d\n", __func__, pdev->id); + return -EINVAL; + } + + port = ghsuart_data_ports[pdev->id].port; + set_bit(CH_READY, &port->channel_sts); + + /* if usb is online, try opening bridge */ + if (atomic_read(&port->connected)) + queue_work(port->wq, &port->connect_w); + + return 0; +} + +/* mdm disconnect */ +static int ghsuart_data_remove(struct platform_device *pdev) +{ + struct ghsuart_data_port *port; + struct usb_ep *ep_in; + struct usb_ep *ep_out; + int ret; + struct gserial *gser = NULL; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + if (pdev->id >= num_data_ports) { + pr_err("%s: invalid port: %d\n", __func__, pdev->id); + return -EINVAL; + } + + port = ghsuart_data_ports[pdev->id].port; + + ep_in = port->in; + if (ep_in) + usb_ep_fifo_flush(ep_in); + + ep_out = port->out; + if (ep_out) + usb_ep_fifo_flush(ep_out); + + ghsuart_data_free_buffers(port); + + if (port->gtype == USB_GADGET_SERIAL) { + spin_lock_irqsave(&port->port_lock, flags); + gser = port->port_usb; + port->cbits_tohost = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + if (gser && gser->disconnect) + gser->disconnect(gser); + } + + ret = msm_smux_close(port->ch_id); + if (ret < 0) + pr_err("%s:Unable to close smux channel: %d\n", + __func__, port->ch_id); + + clear_bit(CH_READY, &port->channel_sts); + clear_bit(CH_OPENED, &port->channel_sts); + + return 0; +} + +static void ghsuart_data_port_free(int portno) +{ + struct ghsuart_data_port *port = ghsuart_data_ports[portno].port; + struct platform_driver *pdrv = &ghsuart_data_ports[portno].pdrv; + + destroy_workqueue(port->wq); + kfree(port); + + if (pdrv) + platform_driver_unregister(pdrv); +} + +static void +ghsuart_send_controlbits_tomodem(void *gptr, u8 portno, int cbits) +{ + struct ghsuart_data_port *port; + + if (portno >= num_ctrl_ports || !gptr) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return; + } + + port = ghsuart_data_ports[portno].port; + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + if (cbits == port->cbits_tomodem) + return; + + port->cbits_tomodem = cbits; + + if (!test_bit(CH_OPENED, &port->channel_sts)) + return; + + /* if DTR is high, update latest modem info to Host */ + if (port->cbits_tomodem & ACM_CTRL_DTR) { + unsigned int i; + + i = msm_smux_tiocm_get(port->ch_id); + ghsuart_dunctrl_status(port, i); + } + + pr_debug("%s: ctrl_tomodem:%d\n", __func__, cbits); + /* Send the control bits to the Modem */ + msm_smux_tiocm_set(port->ch_id, cbits, ~cbits); +} + +static int ghsuart_data_port_alloc(unsigned port_num, enum gadget_type gtype) +{ + struct ghsuart_data_port *port; + struct platform_driver *pdrv; + + port = kzalloc(sizeof(struct ghsuart_data_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->wq = create_singlethread_workqueue(ghsuart_data_names[port_num]); + if (!port->wq) { + pr_err("%s: Unable to create workqueue:%s\n", + __func__, ghsuart_data_names[port_num]); + kfree(port); + return -ENOMEM; + } + port->port_num = port_num; + + /* port initialization */ + spin_lock_init(&port->port_lock); + spin_lock_init(&port->rx_lock); + spin_lock_init(&port->tx_lock); + + INIT_WORK(&port->connect_w, ghsuart_data_connect_w); + INIT_WORK(&port->disconnect_w, ghsuart_data_disconnect_w); + INIT_WORK(&port->write_tohost_w, ghsuart_data_write_tohost); + INIT_WORK(&port->write_tomdm_w, ghsuart_data_write_tomdm); + + INIT_LIST_HEAD(&port->tx_idle); + INIT_LIST_HEAD(&port->rx_idle); + + skb_queue_head_init(&port->tx_skb_q); + skb_queue_head_init(&port->rx_skb_q); + + port->gtype = gtype; + if (port->gtype == USB_GADGET_SERIAL) + port->ch_id = SMUX_USB_DUN_0; + else + port->ch_id = SMUX_USB_RMNET_DATA_0; + port->ctx = port; + ghsuart_data_ports[port_num].port = port; + + pdrv = &ghsuart_data_ports[port_num].pdrv; + pdrv->probe = ghsuart_data_probe; + pdrv->remove = ghsuart_data_remove; + pdrv->driver.name = ghsuart_data_names[port_num]; + pdrv->driver.owner = THIS_MODULE; + + platform_driver_register(pdrv); + + pr_debug("%s: port:%p portno:%d\n", __func__, port, port_num); + + return 0; +} + +void ghsuart_data_disconnect(void *gptr, int port_num) +{ + struct ghsuart_data_port *port; + unsigned long flags; + struct gserial *gser = NULL; + + pr_debug("%s: port#%d\n", __func__, port_num); + + port = ghsuart_data_ports[port_num].port; + + if (port_num > num_data_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return; + } + + if (!gptr || !port) { + pr_err("%s: port is null\n", __func__); + return; + } + + ghsuart_data_free_buffers(port); + + /* disable endpoints */ + if (port->in) + usb_ep_disable(port->in); + + if (port->out) + usb_ep_disable(port->out); + + atomic_set(&port->connected, 0); + + if (port->gtype == USB_GADGET_SERIAL) { + gser = gptr; + spin_lock_irqsave(&port->port_lock, flags); + gser->notify_modem = 0; + port->cbits_tomodem = 0; + port->port_usb = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + } + + spin_lock_irqsave(&port->tx_lock, flags); + port->in = NULL; + port->n_tx_req_queued = 0; + clear_bit(RX_THROTTLED, &port->flags); + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->out = NULL; + clear_bit(TX_THROTTLED, &port->flags); + spin_unlock_irqrestore(&port->rx_lock, flags); + + queue_work(port->wq, &port->disconnect_w); +} + +int ghsuart_data_connect(void *gptr, int port_num) +{ + struct ghsuart_data_port *port; + struct gserial *gser; + struct grmnet *gr; + unsigned long flags; + int ret = 0; + + pr_debug("%s: port#%d\n", __func__, port_num); + + port = ghsuart_data_ports[port_num].port; + + if (port_num > num_data_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if (!gptr || !port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + + if (port->gtype == USB_GADGET_SERIAL) { + gser = gptr; + + spin_lock_irqsave(&port->tx_lock, flags); + port->in = gser->in; + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->out = gser->out; + spin_unlock_irqrestore(&port->rx_lock, flags); + + + port->tx_q_size = ghsuart_data_serial_tx_q_size; + port->rx_q_size = ghsuart_data_serial_rx_q_size; + gser->in->driver_data = port; + gser->out->driver_data = port; + + spin_lock_irqsave(&port->port_lock, flags); + gser->notify_modem = ghsuart_send_controlbits_tomodem; + port->port_usb = gptr; + spin_unlock_irqrestore(&port->port_lock, flags); + } else { + gr = gptr; + + spin_lock_irqsave(&port->tx_lock, flags); + port->in = gr->in; + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->out = gr->out; + spin_unlock_irqrestore(&port->rx_lock, flags); + + port->tx_q_size = ghsuart_data_rmnet_tx_q_size; + port->rx_q_size = ghsuart_data_rmnet_rx_q_size; + gr->in->driver_data = port; + gr->out->driver_data = port; + } + + ret = usb_ep_enable(port->in); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:IN ep:%p", + __func__, port->in); + goto fail; + } + + ret = usb_ep_enable(port->out); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p", + __func__, port->out); + usb_ep_disable(port->in); + goto fail; + } + + atomic_set(&port->connected, 1); + + spin_lock_irqsave(&port->tx_lock, flags); + port->to_host = 0; + spin_unlock_irqrestore(&port->tx_lock, flags); + + spin_lock_irqsave(&port->rx_lock, flags); + port->to_modem = 0; + port->tomodem_drp_cnt = 0; + spin_unlock_irqrestore(&port->rx_lock, flags); + + queue_work(port->wq, &port->connect_w); +fail: + return ret; +} + +#define DEBUG_BUF_SIZE 1024 +static ssize_t ghsuart_data_read_stats(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos) +{ + struct ghsuart_data_port *port; + struct platform_driver *pdrv; + 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 < num_data_ports; i++) { + port = ghsuart_data_ports[i].port; + if (!port) + continue; + pdrv = &ghsuart_data_ports[i].pdrv; + + spin_lock_irqsave(&port->rx_lock, flags); + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName: %s\n" + "#PORT:%d port#: %p\n" + "data_ch_open: %d\n" + "data_ch_ready: %d\n" + "\n******UL INFO*****\n\n" + "dpkts_to_modem: %lu\n" + "tomodem_drp_cnt: %u\n" + "rx_buf_len: %u\n" + "TX_THROTTLED %d\n", + pdrv->driver.name, + i, port, + test_bit(CH_OPENED, &port->channel_sts), + test_bit(CH_READY, &port->channel_sts), + port->to_modem, + port->tomodem_drp_cnt, + port->rx_skb_q.qlen, + test_bit(TX_THROTTLED, &port->flags)); + spin_unlock_irqrestore(&port->rx_lock, flags); + + spin_lock_irqsave(&port->tx_lock, flags); + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\n******DL INFO******\n\n" + "dpkts_to_usbhost: %lu\n" + "tx_buf_len: %u\n" + "RX_THROTTLED %d\n", + port->to_host, + port->tx_skb_q.qlen, + test_bit(RX_THROTTLED, &port->flags)); + spin_unlock_irqrestore(&port->tx_lock, flags); + + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t ghsuart_data_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct ghsuart_data_port *port; + int i; + unsigned long flags; + + for (i = 0; i < num_data_ports; i++) { + port = ghsuart_data_ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->rx_lock, flags); + port->to_modem = 0; + port->tomodem_drp_cnt = 0; + spin_unlock_irqrestore(&port->rx_lock, flags); + + spin_lock_irqsave(&port->tx_lock, flags); + port->to_host = 0; + spin_unlock_irqrestore(&port->tx_lock, flags); + } + return count; +} + +const struct file_operations ghsuart_data_stats_ops = { + .read = ghsuart_data_read_stats, + .write = ghsuart_data_reset_stats, +}; + +static struct dentry *ghsuart_data_dent; +static int ghsuart_data_debugfs_init(void) +{ + struct dentry *ghsuart_data_dfile; + + ghsuart_data_dent = debugfs_create_dir("ghsic_data_xport", 0); + if (!ghsuart_data_dent || IS_ERR(ghsuart_data_dent)) + return -ENODEV; + + ghsuart_data_dfile = debugfs_create_file("status", S_IRUGO | S_IWUSR, + ghsuart_data_dent, 0, &ghsuart_data_stats_ops); + if (!ghsuart_data_dfile || IS_ERR(ghsuart_data_dfile)) { + debugfs_remove(ghsuart_data_dent); + return -ENODEV; + } + + return 0; +} + +static void ghsuart_data_debugfs_exit(void) +{ + debugfs_remove_recursive(ghsuart_data_dent); +} + +int ghsuart_data_setup(unsigned num_ports, enum gadget_type gtype) +{ + int first_port_id = num_data_ports; + int total_num_ports = num_ports + num_data_ports; + int ret = 0; + int i; + + if (!num_ports || total_num_ports > NUM_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, num_ports); + return -EINVAL; + } + pr_debug("%s: count: %d\n", __func__, num_ports); + + for (i = first_port_id; i < total_num_ports; i++) { + + /*probe can be called while port_alloc,so update no_data_ports*/ + num_data_ports++; + ret = ghsuart_data_port_alloc(i, gtype); + if (ret) { + num_data_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_ports; + } + } + + /*return the starting index*/ + return first_port_id; + +free_ports: + for (i = first_port_id; i < num_data_ports; i++) + ghsuart_data_port_free(i); + num_data_ports = first_port_id; + + return ret; +} + +static int __init ghsuart_data_init(void) +{ + int ret; + + ret = ghsuart_data_debugfs_init(); + if (ret) { + pr_debug("mode debugfs file is not available"); + return ret; + } + + return 0; +} +module_init(ghsuart_data_init); + +static void __exit ghsuart_data_exit(void) +{ + ghsuart_data_debugfs_exit(); +} +module_exit(ghsuart_data_exit); + +MODULE_DESCRIPTION("hsuart data xport driver for DUN and RMNET"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index c8702c8eab55743db726aad747dfdda843fe8031..9e3301fb83e0e829255bd46f30a8b8eb4202c59f 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -58,7 +58,7 @@ struct eth_dev { spinlock_t req_lock; /* guard {rx,tx}_reqs */ struct list_head tx_reqs, rx_reqs; - atomic_t tx_qlen; + unsigned tx_qlen; struct sk_buff_head rx_frames; @@ -476,7 +476,6 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req) 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); } @@ -590,12 +589,19 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, req->length = length; - /* throttle high/super speed IRQ rate back slightly */ - if (gadget_is_dualspeed(dev->gadget)) - req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH || - dev->gadget->speed == USB_SPEED_SUPER) - ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) - : 0; + /* throttle highspeed IRQ rate back slightly */ + if (gadget_is_dualspeed(dev->gadget) && + (dev->gadget->speed == USB_SPEED_HIGH)) { + dev->tx_qlen++; + if (dev->tx_qlen == qmult) { + req->no_interrupt = 0; + dev->tx_qlen = 0; + } else { + req->no_interrupt = 1; + } + } else { + req->no_interrupt = 0; + } retval = usb_ep_queue(in, req, GFP_ATOMIC); switch (retval) { @@ -604,7 +610,6 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, break; case 0: net->trans_start = jiffies; - atomic_inc(&dev->tx_qlen); } if (retval) { @@ -630,7 +635,7 @@ static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) rx_fill(dev, gfp_flags); /* and open the tx floodgates */ - atomic_set(&dev->tx_qlen, 0); + dev->tx_qlen = 0; netif_wake_queue(dev->net); } @@ -956,7 +961,6 @@ void gether_disconnect(struct gether *link) struct eth_dev *dev = link->ioport; struct usb_request *req; - WARN_ON(!dev); if (!dev) return; diff --git a/drivers/usb/gadget/u_rmnet.h b/drivers/usb/gadget/u_rmnet.h new file mode 100644 index 0000000000000000000000000000000000000000..0f7c4fb1b66df58e7e7a0fb6dbf102fd8301c956 --- /dev/null +++ b/drivers/usb/gadget/u_rmnet.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; + + /* to usb host, aka laptop, windows pc etc. Will + * be filled by usb driver of rmnet functionality + */ + int (*send_cpkt_response)(void *g, void *buf, size_t len); + + /* to modem, and to be filled by driver implementing + * control function + */ + int (*send_encap_cmd)(u8 port_num, void *buf, size_t len); + + void (*notify_modem)(void *g, u8 port_num, int cbits); + + void (*disconnect)(struct grmnet *g); + void (*connect)(struct grmnet *g); +}; + +int gbam_setup(unsigned int no_bam_port, unsigned int no_bam2bam_port); +int gbam_connect(struct grmnet *gr, u8 port_num, + enum transport_type trans, u8 connection_idx); +void gbam_disconnect(struct grmnet *gr, u8 port_num, enum transport_type trans); +void gbam_suspend(struct grmnet *gr, u8 port_num, enum transport_type trans); +void gbam_resume(struct grmnet *gr, u8 port_num, enum transport_type trans); +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 0000000000000000000000000000000000000000..0256a75397ab21d791fded6bee334a3d66832ada --- /dev/null +++ b/drivers/usb/gadget/u_rmnet_ctrl_smd.c @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_CTRL_SMD_PORTS 3 +static int n_rmnet_ctrl_ports; +static char *rmnet_ctrl_names[] = {"DATA40_CNTL", "DATA39_CNTL", "DATA38_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 delayed_work connect_w; +}; + +static struct rmnet_ctrl_ports { + struct rmnet_ctrl_port *port; + struct platform_driver pdrv; +} ctrl_smd_ports[NR_CTRL_SMD_PORTS]; + + +/*---------------misc functions---------------- */ + +static struct rmnet_ctrl_pkt *alloc_rmnet_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 free_rmnet_ctrl_pkt(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; + size_t len; + void *buf; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + while (c->ch) { + sz = smd_cur_packet_size(c->ch); + if (sz <= 0) + break; + + if (smd_read_avail(c->ch) < sz) + break; + + spin_unlock_irqrestore(&port->port_lock, flags); + + buf = kmalloc(sz, GFP_KERNEL); + if (!buf) + return; + + len = smd_read(c->ch, 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, + buf, len); + c->to_host++; + } + kfree(buf); + } + 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 (c->ch) { + 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); + free_rmnet_ctrl_pkt(cpkt); + break; + } + free_rmnet_ctrl_pkt(cpkt); + c->to_modem++; + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int +grmnet_ctrl_smd_send_cpkt_tomodem(u8 portno, + void *buf, size_t len) +{ + unsigned long flags; + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + struct rmnet_ctrl_pkt *cpkt; + + if (portno >= n_rmnet_ctrl_ports) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return -ENODEV; + } + + port = ctrl_smd_ports[portno].port; + + cpkt = alloc_rmnet_ctrl_pkt(len, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + pr_err("%s: Unable to allocate ctrl pkt\n", __func__); + return -ENOMEM; + } + + memcpy(cpkt->buf, buf, len); + cpkt->len = len; + + 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)) { + free_rmnet_ctrl_pkt(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 RMNET_CTRL_DTR 0x01 +static void +gsmd_ctrl_send_cbits_tomodem(void *gptr, 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_rmnet_ctrl_ports) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return; + } + + if (!gptr) { + pr_err("%s: grmnet is null\n", __func__); + return; + } + + port = ctrl_smd_ports[portno].port; + cbits = cbits & RMNET_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 & RMNET_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; + struct rmnet_ctrl_pkt *cpkt; + unsigned long flags; + + 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); + + if (port && port->port_usb && port->port_usb->connect) + port->port_usb->connect(port->port_usb); + + break; + case SMD_EVENT_CLOSE: + clear_bit(CH_OPENED, &c->flags); + + if (port && port->port_usb && port->port_usb->disconnect) + port->port_usb->disconnect(port->port_usb); + + spin_lock_irqsave(&port->port_lock, flags); + while (!list_empty(&c->tx_q)) { + cpkt = list_first_entry(&c->tx_q, + struct rmnet_ctrl_pkt, list); + + list_del(&cpkt->list); + free_rmnet_ctrl_pkt(cpkt); + } + spin_unlock_irqrestore(&port->port_lock, 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.work); + 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) { + if (ret == -EAGAIN) { + /* port not ready - retry */ + pr_debug("%s: SMD port not ready - rescheduling:%s err:%d\n", + __func__, c->name, ret); + queue_delayed_work(grmnet_ctrl_wq, &port->connect_w, + msecs_to_jiffies(250)); + } else { + pr_err("%s: unable to open smd port:%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_rmnet_ctrl_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 = ctrl_smd_ports[port_num].port; + c = &port->ctrl_ch; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = gr; + gr->send_encap_cmd = grmnet_ctrl_smd_send_cpkt_tomodem; + gr->notify_modem = gsmd_ctrl_send_cbits_tomodem; + spin_unlock_irqrestore(&port->port_lock, flags); + + queue_delayed_work(grmnet_ctrl_wq, &port->connect_w, 0); + + 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; + struct rmnet_ctrl_pkt *cpkt; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (port_num >= n_rmnet_ctrl_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 = ctrl_smd_ports[port_num].port; + c = &port->ctrl_ch; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = 0; + gr->send_encap_cmd = 0; + gr->notify_modem = 0; + c->cbits_tomodem = 0; + + while (!list_empty(&c->tx_q)) { + cpkt = list_first_entry(&c->tx_q, struct rmnet_ctrl_pkt, list); + + list_del(&cpkt->list); + free_rmnet_ctrl_pkt(cpkt); + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + if (test_and_clear_bit(CH_OPENED, &c->flags)) + /* send dtr zero */ + smd_tiocmset(c->ch, c->cbits_tomodem, ~c->cbits_tomodem); + + if (c->ch) { + smd_close(c->ch); + c->ch = NULL; + } +} + +#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_rmnet_ctrl_ports; i++) { + port = ctrl_smd_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_delayed_work(grmnet_ctrl_wq, + &port->connect_w, 0); + 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_rmnet_ctrl_ports; i++) { + port = ctrl_smd_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); + if (c->ch) { + smd_close(c->ch); + c->ch = NULL; + } + break; + } + } + + return 0; +} + + +static void grmnet_ctrl_smd_port_free(int portno) +{ + struct rmnet_ctrl_port *port = ctrl_smd_ports[portno].port; + struct platform_driver *pdrv = &ctrl_smd_ports[portno].pdrv; + + if (port) { + kfree(port); + platform_driver_unregister(pdrv); + } +} + +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_DELAYED_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); + + ctrl_smd_ports[portno].port = port; + + pdrv = &ctrl_smd_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_CTRL_SMD_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++) { + n_rmnet_ctrl_ports++; + ret = grmnet_ctrl_smd_port_alloc(i); + if (ret) { + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + n_rmnet_ctrl_ports--; + goto free_ctrl_smd_ports; + } + } + + return 0; + +free_ctrl_smd_ports: + for (i = 0; i < n_rmnet_ctrl_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_rmnet_ctrl_ports; i++) { + port = ctrl_smd_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), + c->ch ? smd_read_avail(c->ch) : 0, + c->ch ? smd_write_avail(c->ch) : 0); + + 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_rmnet_ctrl_ports; i++) { + port = ctrl_smd_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 0000000000000000000000000000000000000000..8c4b4c7d0a990e7bf9f1bbd56fb7a4e2d86fd189 --- /dev/null +++ b/drivers/usb/gadget/u_sdio.c @@ -0,0 +1,1169 @@ +/* + * 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 sdio_probe; + 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; + } + + if (!port->sdio_open) { + pr_debug("%s: sdio is not open\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; + 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(void *gptr, u8 portno, int ctrl_bits) +{ + struct gsdio_port *port; + int temp; + struct gserial *gser = gptr; + + 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; + } + + port->ctrl_ch_err = 0; + 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_remove(struct platform_device *dev) +{ + struct gsdio_port *port; + struct sdio_port_info *pi; + int i; + unsigned long flags; + + 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; + + if (!strncmp(pi->data_ch_name, dev->name, + SDIO_CH_NAME_MAX_LEN)) { + struct gserial *gser = port->port_usb; + + port->sdio_open = 0; + port->sdio_probe = 0; + port->ctrl_ch_err = 1; + + /* check if usb cable is connected */ + if (!gser) + continue; + + /* indicated call status to usb host */ + gsdio_ctrl_modem_status(0, port); + + usb_ep_fifo_flush(gser->in); + usb_ep_fifo_flush(gser->out); + + cancel_work_sync(&port->push); + cancel_work_sync(&port->pull); + + 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); + + } + } + + return 0; +} + +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)) { + port->sdio_probe = 1; + 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->remove = gsdio_ch_remove; + 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); + 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); + 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; + unsigned long flags; + int i = 0; + int temp = 0; + int ret; + + buf = kzalloc(sizeof(char) * 1024, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + 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" + "sdio_open: %d\n" + "sdio_probe: %d\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, + port->sdio_open, port->sdio_probe); + spin_unlock_irqrestore(&port->port_lock, flags); + i++; + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +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; + + 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); + n_sdio_ports++; + if (ret) { + n_sdio_ports--; + pr_err("%s: sdio logical port allocation failed\n", + __func__); + goto free_sdio_ports; + } + +#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 380a87f6e56c326cb0b1fc74cf4b994af7901632..de93049c9506c6a9868ea51383007ac1bcf66ec6 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "u_serial.h" @@ -78,9 +79,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; @@ -110,7 +116,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; @@ -120,16 +126,22 @@ 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 */ -#define N_PORTS 4 +#define N_PORTS 8 static struct portmaster { struct mutex lock; /* protect open/close */ struct gs_port *port; } ports[N_PORTS]; static unsigned n_ports; +static struct workqueue_struct *gserial_wq; + #define GS_CLOSE_TIMEOUT 15 /* seconds */ @@ -362,18 +374,39 @@ __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 explicitly to make it + * work with UDCs which don't support req->zero flag + */ + 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; } @@ -381,7 +414,6 @@ __acquires(&port->port_lock) req->length = len; list_del(&req->list); - req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0); pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", port->port_num, len, *((u8 *)req->buf), @@ -397,19 +429,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) @@ -428,6 +466,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; @@ -439,12 +478,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) @@ -452,7 +491,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); @@ -461,9 +509,6 @@ __acquires(&port->port_lock) } port->read_started++; - /* abort immediately after disconnect */ - if (!port->port_usb) - break; } return port->read_started; } @@ -478,9 +523,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; @@ -533,6 +578,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) { @@ -556,19 +602,18 @@ static void gs_rx_push(unsigned long _port) if (tty && do_push) tty_flip_buffer_push(tty); - /* We want our data queue to become empty ASAP, keeping data * in the tty and ldisc (not here). If we couldn't push any * 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 +630,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 +658,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 +668,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 +686,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 +730,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 +746,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); @@ -798,6 +849,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; @@ -871,7 +929,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); @@ -887,6 +945,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); } @@ -985,7 +1059,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); @@ -1009,6 +1083,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, @@ -1019,6 +1164,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, }; /*-------------------------------------------------------------------------*/ @@ -1038,7 +1185,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); @@ -1052,6 +1199,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 @@ -1090,7 +1347,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 @@ -1109,6 +1367,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); @@ -1123,6 +1387,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; @@ -1138,6 +1403,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"); @@ -1145,6 +1413,8 @@ int gserial_setup(struct usb_gadget *g, unsigned count) fail: while (count--) kfree(ports[count].port); + if (gserial_wq) + destroy_workqueue(gserial_wq); put_tty_driver(gs_tty_driver); gs_tty_driver = NULL; return status; @@ -1191,7 +1461,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)); @@ -1202,6 +1472,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; @@ -1340,5 +1611,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 9b0fe6450fbfaf88d27d130918d2a73aff53ffd1..dadc507f2e5a92ad1c3dcc74156b07e826ff3707 100644 --- a/drivers/usb/gadget/u_serial.h +++ b/drivers/usb/gadget/u_serial.h @@ -38,11 +38,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)(void *gser, u8 portno, int ctrl_bits); }; /* utilities to allocate/free request and buffer */ @@ -57,6 +68,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 0000000000000000000000000000000000000000..a5ceaff5f8e2dfdcdba63dd1c4b7f99739b45e07 --- /dev/null +++ b/drivers/usb/gadget/u_smd.c @@ -0,0 +1,979 @@ +/* + * 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 +#define CH_READY 1 +struct smd_port_info { + struct smd_channel *ch; + char *name; + unsigned long flags; +}; + +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 delayed_work 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; + struct platform_driver pdrv; +} 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; + unsigned long flags; + int ret; + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + + 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 (test_bit(CH_OPENED, &port->pi->flags) && !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_irqrestore(&port->port_lock, flags); + ret = usb_ep_queue(out, req, GFP_KERNEL); + spin_lock_irqsave(&port->port_lock, flags); + 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_irqrestore(&port->port_lock, flags); +} + +static void gsmd_rx_push(struct work_struct *w) +{ + struct gsmd_port *port = container_of(w, struct gsmd_port, push); + struct smd_port_info *pi = port->pi; + 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 (pi->ch && !list_empty(q)) { + struct usb_request *req; + int avail; + + 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; + int 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; + struct smd_port_info *pi = port->pi; + struct usb_ep *in; + + pr_debug("%s: port:%p port#%d pool:%p\n", __func__, + port, port->port_num, pool); + + spin_lock_irq(&port->port_lock); + + if (!port->port_usb) { + pr_debug("%s: usb is disconnected\n", __func__); + spin_unlock_irq(&port->port_lock); + gsmd_read_pending(port); + return; + } + + in = port->port_usb->in; + while (pi->ch && !list_empty(pool)) { + struct usb_request *req; + 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; + + pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock(&port->port_lock); + if (!test_bit(CH_OPENED, &port->pi->flags) || + req->status == -ESHUTDOWN) { + spin_unlock(&port->port_lock); + gsmd_free_req(ep, req); + return; + } + + list_add_tail(&req->list, &port->read_queue); + queue_work(gsmd_wq, &port->push); + spin_unlock(&port->port_lock); + + return; +} + +static void gsmd_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gsmd_port *port = ep->driver_data; + + pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock(&port->port_lock); + if (!test_bit(CH_OPENED, &port->pi->flags) || + req->status == -ESHUTDOWN) { + spin_unlock(&port->port_lock); + gsmd_free_req(ep, req); + return; + } + + if (req->status) + pr_warning("%s: port:%p port#%d unexpected %s status %d\n", + __func__, port, port->port_num, + ep->name, req->status); + + list_add(&req->list, &port->write_pool); + queue_work(gsmd_wq, &port->pull); + spin_unlock(&port->port_lock); + + return; +} + +static void gsmd_start_io(struct gsmd_port *port) +{ + int ret = -ENODEV; + + pr_debug("%s: port: %p\n", __func__, port); + + spin_lock(&port->port_lock); + + if (!port->port_usb) + goto start_io_out; + + smd_tiocmset_from_cb(port->pi->ch, + port->cbits_to_modem, + ~port->cbits_to_modem); + + 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(&port->port_lock); + + 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_stop_io(struct gsmd_port *port) +{ + struct usb_ep *in; + struct usb_ep *out; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + in = port->port_usb->in; + out = port->port_usb->out; + spin_unlock_irqrestore(&port->port_lock, flags); + + usb_ep_fifo_flush(in); + usb_ep_fifo_flush(out); + + spin_lock(&port->port_lock); + if (port->port_usb) { + gsmd_free_requests(out, &port->read_pool); + gsmd_free_requests(out, &port->read_queue); + gsmd_free_requests(in, &port->write_pool); + port->n_read = 0; + port->cbits_to_laptop = 0; + } + + if (port->port_usb->send_modem_ctrl_bits) + port->port_usb->send_modem_ctrl_bits( + port->port_usb, + port->cbits_to_laptop); + spin_unlock(&port->port_lock); + +} + +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); + gsmd_start_io(port); + break; + case SMD_EVENT_CLOSE: + pr_debug("%s: Event Close\n", __func__); + clear_bit(CH_OPENED, &pi->flags); + gsmd_stop_io(port); + 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; + } +} + +static void gsmd_connect_work(struct work_struct *w) +{ + struct gsmd_port *port; + struct smd_port_info *pi; + int ret; + + port = container_of(w, struct gsmd_port, connect_work.work); + pi = port->pi; + + pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num); + + if (!test_bit(CH_READY, &pi->flags)) + return; + + ret = smd_named_open_on_edge(pi->name, SMD_APPS_MODEM, + &pi->ch, port, gsmd_notify); + if (ret) { + if (ret == -EAGAIN) { + /* port not ready - retry */ + pr_debug("%s: SMD port not ready - rescheduling:%s err:%d\n", + __func__, pi->name, ret); + queue_delayed_work(gsmd_wq, &port->connect_work, + msecs_to_jiffies(250)); + } else { + pr_err("%s: unable to open smd port:%s err:%d\n", + __func__, pi->name, ret); + } + } +} + +static void gsmd_notify_modem(void *gptr, u8 portno, int ctrl_bits) +{ + struct gsmd_port *port; + int temp; + struct gserial *gser = gptr; + + 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); + 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); + 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_delayed_work(gsmd_wq, &port->connect_work, msecs_to_jiffies(0)); + + 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_and_clear_bit(CH_OPENED, &port->pi->flags)) { + /* lower the dtr */ + port->cbits_to_modem = 0; + smd_tiocmset(port->pi->ch, + port->cbits_to_modem, + ~port->cbits_to_modem); + } + + if (port->pi->ch) { + smd_close(port->pi->ch); + port->pi->ch = NULL; + } +} + +#define SMD_CH_MAX_LEN 20 +static int gsmd_ch_probe(struct platform_device *pdev) +{ + struct gsmd_port *port; + struct smd_port_info *pi; + int i; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_smd_ports; i++) { + port = smd_ports[i].port; + pi = port->pi; + + if (!strncmp(pi->name, pdev->name, SMD_CH_MAX_LEN)) { + set_bit(CH_READY, &pi->flags); + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + queue_delayed_work(gsmd_wq, &port->connect_work, + msecs_to_jiffies(0)); + spin_unlock_irqrestore(&port->port_lock, flags); + break; + } + } + return 0; +} + +static int gsmd_ch_remove(struct platform_device *pdev) +{ + struct gsmd_port *port; + struct smd_port_info *pi; + int i; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_smd_ports; i++) { + port = smd_ports[i].port; + pi = port->pi; + + if (!strncmp(pi->name, pdev->name, SMD_CH_MAX_LEN)) { + clear_bit(CH_READY, &pi->flags); + clear_bit(CH_OPENED, &pi->flags); + if (pi->ch) { + smd_close(pi->ch); + pi->ch = NULL; + } + break; + } + } + return 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; + struct platform_driver *pdrv; + + 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_DELAYED_WORK(&port->connect_work, gsmd_connect_work); + + smd_ports[portno].port = port; + pdrv = &smd_ports[portno].pdrv; + pdrv->probe = gsmd_ch_probe; + pdrv->remove = gsmd_ch_remove; + pdrv->driver.name = port->pi->name; + pdrv->driver.owner = THIS_MODULE; + platform_driver_register(pdrv); + + 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; + struct smd_port_info *pi; + 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; + pi = port->pi; + 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" + "smd_read_avail: %d\n" + "smd_write_avail: %d\n" + "CH_OPENED: %d\n" + "CH_READY: %d\n", + i, port->nbytes_tolaptop, port->nbytes_tomodem, + port->cbits_to_modem, port->cbits_to_laptop, + port->n_read, + pi->ch ? smd_read_avail(pi->ch) : 0, + pi->ch ? smd_write_avail(pi->ch) : 0, + test_bit(CH_OPENED, &pi->flags), + test_bit(CH_READY, &pi->flags)); + 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); + n_smd_ports++; + ret = gsmd_port_alloc(i, &coding); + if (ret) { + n_smd_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_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 f788eb86707c0e29d44e455d18b86b0ef6bd203c..4357867098d6af14339f77a1747d0dfcffe3afd2 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -63,6 +63,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 @@ -169,6 +182,24 @@ config USB_EHCI_MSM This driver is not supported on boards like trout which has an external PHY. +config USB_EHCI_MSM_HSIC + bool "Support for HSIC based MSM on-chip EHCI USB controller" + depends on USB_EHCI_HCD && ARCH_MSM + ---help--- + Enables support for the HSIC (High Speed Inter-Chip) based + USB Host controller present on the Qualcomm chipsets. + + HSIC is a supplement to USB 2.0 specification and is preferred + for chip-to-chip interconnect (having maximum circuit length of + 10cm) as it removes the analog transceivers. + +config USB_EHCI_MSM_HOST4 + bool "Support for MSM on-chip EHCI USB controller# 4" + depends on USB_EHCI_HCD && ARCH_MSM && ARCH_APQ8064 + ---help--- + Enables support for the EHCI Compliant USB Host controller# 4 + present on the Qualcomm chipsets. + config USB_EHCI_TEGRA boolean "NVIDIA Tegra HCD support" depends on USB_EHCI_HCD && ARCH_TEGRA @@ -247,6 +278,21 @@ 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 @@ -588,6 +634,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 0982bcc140bd6b84dd46faa6bceca7fc6095238b..7d35f5b510f37bc9049d849df3ef317dce98fc54 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -20,6 +20,7 @@ ifneq ($(CONFIG_USB_XHCI_PLATFORM), ) endif 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 680e1a31fb87c2ae9538e8667c8dab0861a6410f..87bf3dfbc185019fb4851d8e7cc957772b307857 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 4a3bc5b7a06f82d395c9a6dae0a025ba9660cf02..bdde862eed129bb963e6c6f452f6df88c237dd20 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -503,7 +503,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; @@ -579,8 +579,21 @@ static void ehci_stop (struct usb_hcd *hcd) /* root hub is shut down separately (first, when possible) */ spin_lock_irq (&ehci->lock); - if (ehci->async) + if (ehci->async) { + /* + * TODO: Observed that ehci->async next ptr is not + * NULL sometimes which leads to crash in mem_cleanup. + * Root cause is not yet known why this messup is + * happenning. + * The follwing workaround fixes the crash caused + * by this temporarily. + * check if async next ptr is not NULL and unlink + * explictly. + */ + if (ehci->async->qh_next.ptr != NULL) + start_unlink_async(ehci, ehci->async->qh_next.qh); ehci_work (ehci); + } spin_unlock_irq (&ehci->lock); ehci_mem_cleanup (ehci); @@ -726,7 +739,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); u32 temp; @@ -931,6 +944,12 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) pstatus = ehci_readl(ehci, &ehci->regs->port_status[i]); + /*set RS bit in case of remote wakeup*/ + if (ehci_is_TDI(ehci) && !(cmd & CMD_RUN) && + (pstatus & PORT_SUSPEND)) + ehci_writel(ehci, cmd | CMD_RUN, + &ehci->regs->command); + if (pstatus & PORT_OWNER) continue; if (!(test_bit(i, &ehci->suspended_ports) && @@ -1200,7 +1219,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); @@ -1258,140 +1277,268 @@ MODULE_LICENSE ("GPL"); #define PCI_DRIVER ehci_pci_driver #endif +#ifdef CONFIG_PPC_PS3 +#include "ehci-ps3.c" +#define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver +#endif + +#ifdef CONFIG_USB_EHCI_HCD_PPC_OF +#include "ehci-ppc-of.c" +#define OF_PLATFORM_DRIVER ehci_hcd_ppc_of_driver +#endif + +#ifdef CONFIG_XPS_USB_HCD_XILINX +#include "ehci-xilinx-of.c" +#define XILINX_OF_PLATFORM_DRIVER ehci_hcd_xilinx_of_driver +#endif + #ifdef CONFIG_USB_EHCI_FSL #include "ehci-fsl.c" -#define PLATFORM_DRIVER ehci_fsl_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_MXC #include "ehci-mxc.c" -#define PLATFORM_DRIVER ehci_mxc_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_SH #include "ehci-sh.c" -#define PLATFORM_DRIVER ehci_hcd_sh_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_MIPS_ALCHEMY #include "ehci-au1xxx.c" -#define PLATFORM_DRIVER ehci_hcd_au1xxx_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_HCD_OMAP #include "ehci-omap.c" -#define PLATFORM_DRIVER ehci_hcd_omap_driver -#endif - -#ifdef CONFIG_PPC_PS3 -#include "ehci-ps3.c" -#define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver -#endif - -#ifdef CONFIG_USB_EHCI_HCD_PPC_OF -#include "ehci-ppc-of.c" -#define OF_PLATFORM_DRIVER ehci_hcd_ppc_of_driver -#endif - -#ifdef CONFIG_XPS_USB_HCD_XILINX -#include "ehci-xilinx-of.c" -#define XILINX_OF_PLATFORM_DRIVER ehci_hcd_xilinx_of_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_PLAT_ORION #include "ehci-orion.c" -#define PLATFORM_DRIVER ehci_orion_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_ARCH_IXP4XX #include "ehci-ixp4xx.c" -#define PLATFORM_DRIVER ixp4xx_ehci_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_W90X900_EHCI #include "ehci-w90x900.c" -#define PLATFORM_DRIVER ehci_hcd_w90x900_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_ARCH_AT91 #include "ehci-atmel.c" -#define PLATFORM_DRIVER ehci_atmel_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_OCTEON_EHCI #include "ehci-octeon.c" -#define PLATFORM_DRIVER ehci_octeon_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_CNS3XXX_EHCI #include "ehci-cns3xxx.c" -#define PLATFORM_DRIVER cns3xxx_ehci_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_ARCH_VT8500 #include "ehci-vt8500.c" -#define PLATFORM_DRIVER vt8500_ehci_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_PLAT_SPEAR #include "ehci-spear.c" -#define PLATFORM_DRIVER spear_ehci_hcd_driver +#define PLATFORM_DRIVER_PRESENT +#endif + +#ifdef CONFIG_USB_EHCI_MSM_72K +#include "ehci-msm72k.c" +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_MSM #include "ehci-msm.c" -#define PLATFORM_DRIVER ehci_msm_driver +#include "ehci-msm2.c" +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_HCD_PMC_MSP #include "ehci-pmcmsp.c" -#define PLATFORM_DRIVER ehci_hcd_msp_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_TEGRA #include "ehci-tegra.c" -#define PLATFORM_DRIVER tegra_ehci_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_S5P #include "ehci-s5p.c" -#define PLATFORM_DRIVER s5p_ehci_driver +#define PLATFORM_DRIVER_PRESENT +#endif + +#ifdef CONFIG_USB_EHCI_ATH79 +#include "ehci-ath79.c" +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_SPARC_LEON #include "ehci-grlib.c" -#define PLATFORM_DRIVER ehci_grlib_driver +#define PLATFORM_DRIVER_PRESENT +#endif + +#ifdef CONFIG_USB_EHCI_MSM_HSIC +#include "ehci-msm-hsic.c" +#define PLATFORM_DRIVER_PRESENT +#endif + +#ifdef CONFIG_USB_PXA168_EHCI +#include "ehci-pxa168.c" +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_CPU_XLR #include "ehci-xls.c" -#define PLATFORM_DRIVER ehci_xls_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_MV #include "ehci-mv.c" -#define PLATFORM_DRIVER ehci_mv_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_MACH_LOONGSON1 #include "ehci-ls1x.c" -#define PLATFORM_DRIVER ehci_ls1x_driver +#define PLATFORM_DRIVER_PRESENT #endif #ifdef CONFIG_USB_EHCI_HCD_PLATFORM #include "ehci-platform.c" -#define PLATFORM_DRIVER ehci_platform_driver +#define PLATFORM_DRIVER_PRESENT #endif -#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \ +#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER_PRESENT) && \ !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \ !defined(XILINX_OF_PLATFORM_DRIVER) #error "missing bus glue for ehci-hcd" #endif +static struct platform_driver *plat_drivers[] = { +#ifdef CONFIG_USB_EHCI_FSL + &ehci_fsl_driver, +#endif + +#ifdef CONFIG_USB_EHCI_MXC + &ehci_mxc_driver, +#endif + +#ifdef CONFIG_CPU_SUBTYPE_SH7786 + &ehci_hcd_sh_driver, +#endif + +#ifdef CONFIG_SOC_AU1200 + &ehci_hcd_au1xxx_driver, +#endif + +#ifdef CONFIG_USB_EHCI_HCD_OMAP + &ehci_hcd_omap_driver, +#endif + +#ifdef CONFIG_PLAT_ORION + &ehci_orion_driver, +#endif + +#ifdef CONFIG_ARCH_IXP4XX + &ixp4xx_ehci_driver, +#endif + +#ifdef CONFIG_USB_W90X900_EHCI + &ehci_hcd_w90x900_driver, +#endif + +#ifdef CONFIG_ARCH_AT91 + &ehci_atmel_driver, +#endif + +#ifdef CONFIG_USB_OCTEON_EHCI + &ehci_octeon_driver, +#endif + +#ifdef CONFIG_USB_CNS3XXX_EHCI + &cns3xxx_ehci_driver, +#endif + +#ifdef CONFIG_ARCH_VT8500 + &vt8500_ehci_driver, +#endif + +#ifdef CONFIG_PLAT_SPEAR + &spear_ehci_hcd_driver, +#endif + +#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP + &ehci_hcd_msp_driver +#endif + +#ifdef CONFIG_USB_EHCI_TEGRA + &tegra_ehci_driver +#endif + +#ifdef CONFIG_USB_EHCI_S5P + &s5p_ehci_driver +#endif + +#ifdef CONFIG_USB_EHCI_ATH79 + &ehci_ath79_driver +#endif + +#ifdef CONFIG_SPARC_LEON + &ehci_grlib_driver +#endif + +#if defined(CONFIG_USB_EHCI_MSM_72K) || defined(CONFIG_USB_EHCI_MSM) + &ehci_msm_driver, +#endif + +#ifdef CONFIG_USB_EHCI_MSM_HSIC + &ehci_msm_hsic_driver, +#endif + +#ifdef CONFIG_USB_EHCI_MSM + &ehci_msm2_driver, +#endif + +#ifdef CONFIG_USB_PXA168_EHCI + &ehci_pxa168_driver, +#endif + +#ifdef CONFIG_CPU_XLR + &ehci_xls_driver, +#endif + +#ifdef CONFIG_USB_EHCI_MV + &ehci_mv_driver, +#endif + +#ifdef CONFIG_MACH_LOONGSON1 + &ehci_ls1x_driver, +#endif + +#ifdef CONFIG_USB_EHCI_HCD_PLATFORM + &ehci_platform_driver, +#endif +}; + + static int __init ehci_hcd_init(void) { - int retval = 0; + int i, retval = 0; if (usb_disabled()) return -ENODEV; @@ -1416,11 +1563,14 @@ static int __init ehci_hcd_init(void) } #endif -#ifdef PLATFORM_DRIVER - retval = platform_driver_register(&PLATFORM_DRIVER); - if (retval < 0) - goto clean0; -#endif + for (i = 0; i < ARRAY_SIZE(plat_drivers); i++) { + retval = platform_driver_register(plat_drivers[i]); + if (retval) { + while (--i >= 0) + platform_driver_unregister(plat_drivers[i]); + goto clean0; + } + } #ifdef PCI_DRIVER retval = pci_register_driver(&PCI_DRIVER); @@ -1463,10 +1613,9 @@ static int __init ehci_hcd_init(void) pci_unregister_driver(&PCI_DRIVER); clean1: #endif -#ifdef PLATFORM_DRIVER - platform_driver_unregister(&PLATFORM_DRIVER); + for (i = 0; i < ARRAY_SIZE(plat_drivers); i++) + platform_driver_unregister(plat_drivers[i]); clean0: -#endif #ifdef DEBUG debugfs_remove(ehci_debug_root); ehci_debug_root = NULL; @@ -1479,15 +1628,17 @@ module_init(ehci_hcd_init); static void __exit ehci_hcd_cleanup(void) { + int i; #ifdef XILINX_OF_PLATFORM_DRIVER platform_driver_unregister(&XILINX_OF_PLATFORM_DRIVER); #endif #ifdef OF_PLATFORM_DRIVER platform_driver_unregister(&OF_PLATFORM_DRIVER); #endif -#ifdef PLATFORM_DRIVER - platform_driver_unregister(&PLATFORM_DRIVER); -#endif + + for (i = 0; i < ARRAY_SIZE(plat_drivers); i++) + platform_driver_unregister(plat_drivers[i]); + #ifdef PCI_DRIVER pci_unregister_driver(&PCI_DRIVER); #endif diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 38fe076231522875bec58c60ec74fc7b14932960..5cc70e06a7fd46239b42cdcba057c44b07a35680 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -254,6 +254,11 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (t1 & PORT_OWNER) set_bit(port, &ehci->owned_ports); else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) { + /*clear RS bit before setting SUSP bit + * and wait for HCH to get set*/ + if (ehci->susp_sof_bug) + ehci_halt(ehci); + t2 |= PORT_SUSPEND; set_bit(port, &ehci->bus_suspended); } @@ -304,9 +309,11 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (ehci->bus_suspended) udelay(150); - /* turn off now-idle HC */ - ehci_halt (ehci); - ehci->rh_state = EHCI_RH_SUSPENDED; + /*if this bit is set, controller is already haled*/ + if (!ehci->susp_sof_bug) + ehci_halt(ehci); /* turn off now-idle HC */ + + hcd->state = HC_STATE_SUSPENDED; if (ehci->reclaim) end_unlink_async(ehci); @@ -652,6 +659,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 ( @@ -978,14 +1130,29 @@ static int ehci_hub_control ( if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) goto error; - + /*port gets suspended as part of bus suspend routine*/ + if (!ehci->susp_sof_bug) + ehci_writel(ehci, temp | PORT_SUSPEND, + status_reg); +#ifdef CONFIG_USB_OTG + if (hcd->self.otg_port == (wIndex + 1) && + hcd->self.b_hnp_enable) { + set_bit(wIndex, &ehci->suspended_ports); + otg_start_hnp(ehci->transceiver->otg); + 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 */ temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; - ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + if (ehci->susp_sof_bug) + ehci_writel(ehci, temp, status_reg); + else + ehci_writel(ehci, temp | PORT_SUSPEND, + status_reg); if (hostpc_reg) { spin_unlock_irqrestore(&ehci->lock, flags); msleep(5);/* 5ms for HCD enter low pwr mode */ @@ -1041,26 +1208,38 @@ static int ehci_hub_control ( * about the EHCI-specific stuff. */ case USB_PORT_FEAT_TEST: - if (!selector || selector > 5) - goto error; - ehci_quiesce(ehci); + if (selector && selector <= 5) { + ehci_quiesce(ehci); /* Put all enabled ports into suspend */ - while (ports--) { - u32 __iomem *sreg = + while (ports--) { + u32 __iomem *sreg = &ehci->regs->port_status[ports]; - temp = ehci_readl(ehci, sreg) & ~PORT_RWC_BITS; - if (temp & PORT_PE) - ehci_writel(ehci, temp | PORT_SUSPEND, + temp = ehci_readl(ehci, sreg) + & ~PORT_RWC_BITS; + if (temp & PORT_PE) + ehci_writel(ehci, + temp | PORT_SUSPEND, sreg); + } + ehci_halt(ehci); + temp = ehci_readl(ehci, status_reg); + temp |= selector << 16; + ehci_writel(ehci, temp, status_reg); } - ehci_halt(ehci); - temp = ehci_readl(ehci, status_reg); - 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; break; - default: goto error; } diff --git a/drivers/usb/host/ehci-msm-hsic.c b/drivers/usb/host/ehci-msm-hsic.c new file mode 100644 index 0000000000000000000000000000000000000000..fd3d1ca398cd73721d357913b60c3be0b4fb247d --- /dev/null +++ b/drivers/usb/host/ehci-msm-hsic.c @@ -0,0 +1,1154 @@ +/* ehci-msm-hsic.c - HSUSB Host Controller Driver Implementation + * + * Copyright (c) 2011-2012, 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 +#include +#include + +#define MSM_USB_BASE (hcd->regs) + +struct msm_hsic_hcd { + struct ehci_hcd ehci; + struct device *dev; + struct clk *ahb_clk; + struct clk *core_clk; + struct clk *alt_core_clk; + struct clk *phy_clk; + struct clk *cal_clk; + struct regulator *hsic_vddcx; + bool async_int; + atomic_t in_lpm; + struct wake_lock wlock; + int peripheral_status_irq; + int wakeup_irq; + int wakeup_gpio; + bool wakeup_irq_enabled; + atomic_t pm_usage_cnt; + uint32_t bus_perf_client; + uint32_t wakeup_int_cnt; +}; + +static bool debug_bus_voting_enabled = true; +static inline struct msm_hsic_hcd *hcd_to_hsic(struct usb_hcd *hcd) +{ + return (struct msm_hsic_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *hsic_to_hcd(struct msm_hsic_hcd *mehci) +{ + return container_of((void *) mehci, struct usb_hcd, hcd_priv); +} + +#define ULPI_IO_TIMEOUT_USEC (10 * 1000) + +#define USB_PHY_VDD_DIG_VOL_SUSP_MIN 500000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ +#define USB_PHY_VDD_DIG_LOAD 49360 /* uA */ + +#define HSIC_DBG1_REG 0x38 + +static int msm_hsic_init_vddcx(struct msm_hsic_hcd *mehci, int init) +{ + int ret = 0; + + if (!init) + goto disable_reg; + + mehci->hsic_vddcx = devm_regulator_get(mehci->dev, "HSIC_VDDCX"); + if (IS_ERR(mehci->hsic_vddcx)) { + dev_err(mehci->dev, "unable to get hsic vddcx\n"); + return PTR_ERR(mehci->hsic_vddcx); + } + + ret = regulator_set_voltage(mehci->hsic_vddcx, + USB_PHY_VDD_DIG_VOL_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret) { + dev_err(mehci->dev, "unable to set the voltage" + "for hsic vddcx\n"); + return ret; + } + + ret = regulator_set_optimum_mode(mehci->hsic_vddcx, + USB_PHY_VDD_DIG_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set optimum mode of the regulator:" + "VDDCX\n", __func__); + goto reg_optimum_mode_err; + } + + ret = regulator_enable(mehci->hsic_vddcx); + if (ret) { + dev_err(mehci->dev, "unable to enable hsic vddcx\n"); + goto reg_enable_err; + } + + return 0; + +disable_reg: + regulator_disable(mehci->hsic_vddcx); +reg_enable_err: + regulator_set_optimum_mode(mehci->hsic_vddcx, 0); +reg_optimum_mode_err: + regulator_set_voltage(mehci->hsic_vddcx, 0, + USB_PHY_VDD_DIG_VOL_MIN); + return ret; + +} + +static int ulpi_write(struct msm_hsic_hcd *mehci, u32 val, u32 reg) +{ + struct usb_hcd *hcd = hsic_to_hcd(mehci); + int cnt = 0; + + /* initiate write operation */ + writel_relaxed(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(mehci->dev, "ulpi_write: timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int msm_hsic_config_gpios(struct msm_hsic_hcd *mehci, int gpio_en) +{ + int rc = 0; + struct msm_hsic_host_platform_data *pdata; + static int gpio_status; + + pdata = mehci->dev->platform_data; + + if (!pdata || !pdata->strobe || !pdata->data) + return rc; + + if (gpio_status == gpio_en) + return 0; + + gpio_status = gpio_en; + + if (!gpio_en) + goto free_gpio; + + rc = gpio_request(pdata->strobe, "HSIC_STROBE_GPIO"); + if (rc < 0) { + dev_err(mehci->dev, "gpio request failed for HSIC STROBE\n"); + return rc; + } + + rc = gpio_request(pdata->data, "HSIC_DATA_GPIO"); + if (rc < 0) { + dev_err(mehci->dev, "gpio request failed for HSIC DATA\n"); + goto free_strobe; + } + + if (mehci->wakeup_gpio) { + rc = gpio_request(mehci->wakeup_gpio, "HSIC_WAKEUP_GPIO"); + if (rc < 0) { + dev_err(mehci->dev, "gpio request failed for HSIC WAKEUP\n"); + goto free_data; + } + } + + return 0; + +free_gpio: + if (mehci->wakeup_gpio) + gpio_free(mehci->wakeup_gpio); +free_data: + gpio_free(pdata->data); +free_strobe: + gpio_free(pdata->strobe); + + return rc; +} + +static int msm_hsic_phy_clk_reset(struct msm_hsic_hcd *mehci) +{ + int ret; + + clk_prepare_enable(mehci->alt_core_clk); + + ret = clk_reset(mehci->core_clk, CLK_RESET_ASSERT); + if (ret) { + clk_disable_unprepare(mehci->alt_core_clk); + dev_err(mehci->dev, "usb phy clk assert failed\n"); + return ret; + } + usleep_range(10000, 12000); + clk_disable_unprepare(mehci->alt_core_clk); + + ret = clk_reset(mehci->core_clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(mehci->dev, "usb phy clk deassert failed\n"); + + return ret; +} + +static int msm_hsic_phy_reset(struct msm_hsic_hcd *mehci) +{ + struct usb_hcd *hcd = hsic_to_hcd(mehci); + u32 val; + int ret; + + ret = msm_hsic_phy_clk_reset(mehci); + if (ret) + return ret; + + val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK; + writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC); + + /* Ensure that RESET operation is completed before turning off clock */ + mb(); + dev_dbg(mehci->dev, "phy_reset: success\n"); + + return 0; +} + +#define HSIC_GPIO150_PAD_CTL (MSM_TLMM_BASE+0x20C0) +#define HSIC_GPIO151_PAD_CTL (MSM_TLMM_BASE+0x20C4) +#define HSIC_CAL_PAD_CTL (MSM_TLMM_BASE+0x20C8) +#define HSIC_LV_MODE 0x04 +#define HSIC_PAD_CALIBRATION 0xA8 +#define HSIC_GPIO_PAD_VAL 0x0A0AAA10 +#define LINK_RESET_TIMEOUT_USEC (250 * 1000) +static int msm_hsic_reset(struct msm_hsic_hcd *mehci) +{ + struct usb_hcd *hcd = hsic_to_hcd(mehci); + int cnt = 0; + int ret; + struct msm_hsic_host_platform_data *pdata = mehci->dev->platform_data; + + ret = msm_hsic_phy_reset(mehci); + if (ret) { + dev_err(mehci->dev, "phy_reset failed\n"); + return ret; + } + + writel_relaxed(USBCMD_RESET, USB_USBCMD); + while (cnt < LINK_RESET_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET)) + break; + udelay(1); + cnt++; + } + if (cnt >= LINK_RESET_TIMEOUT_USEC) + return -ETIMEDOUT; + + /* Reset PORTSC and select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + + /* TODO: Need to confirm if HSIC PHY also requires delay after RESET */ + msleep(100); + + /* HSIC PHY Initialization */ + + /* HSIC init sequence when HSIC signals (Strobe/Data) are + routed via GPIOs */ + if (pdata && pdata->strobe && pdata->data) { + + /* Enable LV_MODE in HSIC_CAL_PAD_CTL register */ + writel_relaxed(HSIC_LV_MODE, HSIC_CAL_PAD_CTL); + + /*set periodic calibration interval to ~2.048sec in + HSIC_IO_CAL_REG */ + ulpi_write(mehci, 0xFF, 0x33); + + /* Enable periodic IO calibration in HSIC_CFG register */ + ulpi_write(mehci, HSIC_PAD_CALIBRATION, 0x30); + + /* Configure GPIO 150/151 pins for HSIC functionality mode */ + ret = msm_hsic_config_gpios(mehci, 1); + if (ret) { + dev_err(mehci->dev, " gpio configuarion failed\n"); + return ret; + } + /* Set LV_MODE=0x1 and DCC=0x2 in HSIC_GPIO150/151_PAD_CTL + register */ + writel_relaxed(HSIC_GPIO_PAD_VAL, HSIC_GPIO150_PAD_CTL); + writel_relaxed(HSIC_GPIO_PAD_VAL, HSIC_GPIO151_PAD_CTL); + /* Enable HSIC mode in HSIC_CFG register */ + ulpi_write(mehci, 0x01, 0x31); + } else { + /* HSIC init sequence when HSIC signals (Strobe/Data) are routed + via dedicated I/O */ + + /* programmable length of connect signaling (33.2ns) */ + ret = ulpi_write(mehci, 3, HSIC_DBG1_REG); + if (ret) { + pr_err("%s: Unable to program length of connect " + "signaling\n", __func__); + } + + /*set periodic calibration interval to ~2.048sec in + HSIC_IO_CAL_REG */ + ulpi_write(mehci, 0xFF, 0x33); + + /* Enable HSIC mode in HSIC_CFG register */ + ulpi_write(mehci, 0xA9, 0x30); + } + + /*disable auto resume*/ + ulpi_write(mehci, ULPI_IFC_CTRL_AUTORESUME, ULPI_CLR(ULPI_IFC_CTRL)); + + return 0; +} + +#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000) +#define PHY_RESUME_TIMEOUT_USEC (100 * 1000) + +#ifdef CONFIG_PM_SLEEP +static int msm_hsic_suspend(struct msm_hsic_hcd *mehci) +{ + struct usb_hcd *hcd = hsic_to_hcd(mehci); + int cnt = 0, ret; + u32 val; + + if (atomic_read(&mehci->in_lpm)) { + dev_dbg(mehci->dev, "%s called in lpm\n", __func__); + return 0; + } + + if (!(readl_relaxed(USB_PORTSC) & PORT_PE)) { + dev_dbg(mehci->dev, "%s:port is not enabled skip suspend\n", + __func__); + return -EAGAIN; + } + + disable_irq(hcd->irq); + + /* make sure we don't race against a remote wakeup */ + if (test_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags) || + readl_relaxed(USB_PORTSC) & PORT_RESUME) { + dev_dbg(mehci->dev, "wakeup pending, aborting suspend\n"); + enable_irq(hcd->irq); + return -EBUSY; + } + + /* + * 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 + * in failure case. + */ + val = readl_relaxed(USB_PORTSC); + val &= ~PORT_RWC_BITS; + val |= PORTSC_PHCD; + writel_relaxed(val, USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { + dev_err(mehci->dev, "Unable to suspend PHY\n"); + msm_hsic_config_gpios(mehci, 0); + msm_hsic_reset(mehci); + } + + /* + * PHY has capability to generate interrupt asynchronously in low + * power mode (LPM). This interrupt is level triggered. So USB IRQ + * line must be disabled till async interrupt enable bit is cleared + * in USBCMD register. Assert STP (ULPI interface STOP signal) to + * block data communication from PHY. + */ + writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL | + ULPI_STP_CTRL, USB_USBCMD); + + /* + * Ensure that hardware is put in low power mode before + * clocks are turned OFF and VDD is allowed to minimize. + */ + mb(); + + clk_disable_unprepare(mehci->core_clk); + clk_disable_unprepare(mehci->phy_clk); + clk_disable_unprepare(mehci->cal_clk); + clk_disable_unprepare(mehci->ahb_clk); + + ret = regulator_set_voltage(mehci->hsic_vddcx, + USB_PHY_VDD_DIG_VOL_SUSP_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret < 0) + dev_err(mehci->dev, "unable to set vddcx voltage: min:0.5v max:1.3v\n"); + + if (mehci->bus_perf_client && debug_bus_voting_enabled) { + ret = msm_bus_scale_client_update_request( + mehci->bus_perf_client, 0); + if (ret) + dev_err(mehci->dev, "%s: Failed to dvote for " + "bus bandwidth %d\n", __func__, ret); + } + + atomic_set(&mehci->in_lpm, 1); + enable_irq(hcd->irq); + + mehci->wakeup_irq_enabled = 1; + enable_irq_wake(mehci->wakeup_irq); + enable_irq(mehci->wakeup_irq); + + wake_unlock(&mehci->wlock); + + dev_info(mehci->dev, "HSIC-USB in low power mode\n"); + + return 0; +} + +static int msm_hsic_resume(struct msm_hsic_hcd *mehci) +{ + struct usb_hcd *hcd = hsic_to_hcd(mehci); + int cnt = 0, ret; + unsigned temp; + + if (!atomic_read(&mehci->in_lpm)) { + dev_dbg(mehci->dev, "%s called in !in_lpm\n", __func__); + return 0; + } + + if (mehci->wakeup_irq_enabled) { + disable_irq_wake(mehci->wakeup_irq); + disable_irq_nosync(mehci->wakeup_irq); + mehci->wakeup_irq_enabled = 0; + } + + wake_lock(&mehci->wlock); + + if (mehci->bus_perf_client && debug_bus_voting_enabled) { + ret = msm_bus_scale_client_update_request( + mehci->bus_perf_client, 1); + if (ret) + dev_err(mehci->dev, "%s: Failed to vote for " + "bus bandwidth %d\n", __func__, ret); + } + + ret = regulator_set_voltage(mehci->hsic_vddcx, + USB_PHY_VDD_DIG_VOL_MIN, + USB_PHY_VDD_DIG_VOL_MAX); + if (ret < 0) + dev_err(mehci->dev, "unable to set vddcx voltage: min:1v max:1.3v\n"); + + clk_prepare_enable(mehci->core_clk); + clk_prepare_enable(mehci->phy_clk); + clk_prepare_enable(mehci->cal_clk); + clk_prepare_enable(mehci->ahb_clk); + + temp = readl_relaxed(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel_relaxed(temp, USB_USBCMD); + + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) + goto skip_phy_resume; + + temp = readl_relaxed(USB_PORTSC); + temp &= ~(PORT_RWC_BITS | PORTSC_PHCD); + writel_relaxed(temp, USB_PORTSC); + while (cnt < PHY_RESUME_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD) && + (readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE)) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_RESUME_TIMEOUT_USEC) { + /* + * This is a fatal error. Reset the link and + * PHY to make hsic working. + */ + dev_err(mehci->dev, "Unable to resume USB. Reset the hsic\n"); + msm_hsic_config_gpios(mehci, 0); + msm_hsic_reset(mehci); + } + +skip_phy_resume: + + usb_hcd_resume_root_hub(hcd); + + atomic_set(&mehci->in_lpm, 0); + + if (mehci->async_int) { + mehci->async_int = false; + pm_runtime_put_noidle(mehci->dev); + enable_irq(hcd->irq); + } + + if (atomic_read(&mehci->pm_usage_cnt)) { + atomic_set(&mehci->pm_usage_cnt, 0); + pm_runtime_put_noidle(mehci->dev); + } + + dev_info(mehci->dev, "HSIC-USB exited from low power mode\n"); + + return 0; +} +#endif + +static irqreturn_t msm_hsic_irq(struct usb_hcd *hcd) +{ + struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd); + + if (atomic_read(&mehci->in_lpm)) { + disable_irq_nosync(hcd->irq); + dev_dbg(mehci->dev, "phy async intr\n"); + mehci->async_int = true; + pm_runtime_get(mehci->dev); + return IRQ_HANDLED; + } + + return ehci_irq(hcd); +} + +static int ehci_hsic_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)); + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache the data to minimize the chip reads*/ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + hcd->has_tt = 1; + ehci->sbrn = HCD_USB2; + + retval = ehci_halt(ehci); + if (retval) + return retval; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + retval = ehci_reset(ehci); + if (retval) + return retval; + + /* bursts of unspecified length. */ + writel_relaxed(0, USB_AHBBURST); + /* Use the AHB transactor */ + writel_relaxed(0x08, USB_AHBMODE); + /* Disable streaming mode and select host mode */ + writel_relaxed(0x13, USB_USBMODE); + + ehci_port_power(ehci, 1); + return 0; +} + +static struct hc_driver msm_hsic_driver = { + .description = hcd_name, + .product_desc = "Qualcomm EHCI Host Controller using HSIC", + .hcd_priv_size = sizeof(struct msm_hsic_hcd), + + /* + * generic hardware linkage + */ + .irq = msm_hsic_irq, + .flags = HCD_USB2 | HCD_MEMORY, + + .reset = ehci_hsic_reset, + .start = ehci_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, + .endpoint_reset = ehci_endpoint_reset, + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + /* + * PM support + */ + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +}; + +static int msm_hsic_init_clocks(struct msm_hsic_hcd *mehci, u32 init) +{ + int ret = 0; + + if (!init) + goto put_clocks; + + /*core_clk is required for LINK protocol engine + *clock rate appropriately set by target specific clock driver */ + mehci->core_clk = clk_get(mehci->dev, "core_clk"); + if (IS_ERR(mehci->core_clk)) { + dev_err(mehci->dev, "failed to get core_clk\n"); + ret = PTR_ERR(mehci->core_clk); + return ret; + } + + /* alt_core_clk is for LINK to be used during PHY RESET + * clock rate appropriately set by target specific clock driver */ + mehci->alt_core_clk = clk_get(mehci->dev, "alt_core_clk"); + if (IS_ERR(mehci->alt_core_clk)) { + dev_err(mehci->dev, "failed to core_clk\n"); + ret = PTR_ERR(mehci->alt_core_clk); + goto put_core_clk; + } + + /* phy_clk is required for HSIC PHY operation + * clock rate appropriately set by target specific clock driver */ + mehci->phy_clk = clk_get(mehci->dev, "phy_clk"); + if (IS_ERR(mehci->phy_clk)) { + dev_err(mehci->dev, "failed to get phy_clk\n"); + ret = PTR_ERR(mehci->phy_clk); + goto put_alt_core_clk; + } + + /* 10MHz cal_clk is required for calibration of I/O pads */ + mehci->cal_clk = clk_get(mehci->dev, "cal_clk"); + if (IS_ERR(mehci->cal_clk)) { + dev_err(mehci->dev, "failed to get cal_clk\n"); + ret = PTR_ERR(mehci->cal_clk); + goto put_phy_clk; + } + clk_set_rate(mehci->cal_clk, 10000000); + + /* ahb_clk is required for data transfers */ + mehci->ahb_clk = clk_get(mehci->dev, "iface_clk"); + if (IS_ERR(mehci->ahb_clk)) { + dev_err(mehci->dev, "failed to get iface_clk\n"); + ret = PTR_ERR(mehci->ahb_clk); + goto put_cal_clk; + } + + clk_prepare_enable(mehci->core_clk); + clk_prepare_enable(mehci->phy_clk); + clk_prepare_enable(mehci->cal_clk); + clk_prepare_enable(mehci->ahb_clk); + + return 0; + +put_clocks: + if (!atomic_read(&mehci->in_lpm)) { + clk_disable_unprepare(mehci->core_clk); + clk_disable_unprepare(mehci->phy_clk); + clk_disable_unprepare(mehci->cal_clk); + clk_disable_unprepare(mehci->ahb_clk); + } + clk_put(mehci->ahb_clk); +put_cal_clk: + clk_put(mehci->cal_clk); +put_phy_clk: + clk_put(mehci->phy_clk); +put_alt_core_clk: + clk_put(mehci->alt_core_clk); +put_core_clk: + clk_put(mehci->core_clk); + + return ret; +} +static irqreturn_t hsic_peripheral_status_change(int irq, void *dev_id) +{ + struct msm_hsic_hcd *mehci = dev_id; + + pr_debug("%s: mehci:%p dev_id:%p\n", __func__, mehci, dev_id); + + if (mehci) + msm_hsic_config_gpios(mehci, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t msm_hsic_wakeup_irq(int irq, void *data) +{ + struct msm_hsic_hcd *mehci = data; + + mehci->wakeup_int_cnt++; + dev_dbg(mehci->dev, "%s: hsic remote wakeup interrupt cnt: %u\n", + __func__, mehci->wakeup_int_cnt); + + wake_lock(&mehci->wlock); + + if (mehci->wakeup_irq_enabled) { + mehci->wakeup_irq_enabled = 0; + disable_irq_wake(irq); + disable_irq_nosync(irq); + } + + if (!atomic_read(&mehci->pm_usage_cnt)) { + atomic_set(&mehci->pm_usage_cnt, 1); + pm_runtime_get(mehci->dev); + } + + return IRQ_HANDLED; +} + +static int ehci_hsic_msm_bus_show(struct seq_file *s, void *unused) +{ + if (debug_bus_voting_enabled) + seq_printf(s, "enabled\n"); + else + seq_printf(s, "disabled\n"); + + return 0; +} + +static int ehci_hsic_msm_bus_open(struct inode *inode, struct file *file) +{ + return single_open(file, ehci_hsic_msm_bus_show, inode->i_private); +} + +static ssize_t ehci_hsic_msm_bus_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char buf[8]; + int ret; + struct seq_file *s = file->private_data; + struct msm_hsic_hcd *mehci = s->private; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "enable", 6)) { + /* Do not vote here. Let hsic driver decide when to vote */ + debug_bus_voting_enabled = true; + } else { + debug_bus_voting_enabled = false; + if (mehci->bus_perf_client) { + ret = msm_bus_scale_client_update_request( + mehci->bus_perf_client, 0); + if (ret) + dev_err(mehci->dev, "%s: Failed to devote " + "for bus bw %d\n", __func__, ret); + } + } + + return count; +} + +const struct file_operations ehci_hsic_msm_bus_fops = { + .open = ehci_hsic_msm_bus_open, + .read = seq_read, + .write = ehci_hsic_msm_bus_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int ehci_hsic_msm_wakeup_cnt_show(struct seq_file *s, void *unused) +{ + struct msm_hsic_hcd *mehci = s->private; + + seq_printf(s, "%u\n", mehci->wakeup_int_cnt); + + return 0; +} + +static int ehci_hsic_msm_wakeup_cnt_open(struct inode *inode, struct file *f) +{ + return single_open(f, ehci_hsic_msm_wakeup_cnt_show, inode->i_private); +} + +const struct file_operations ehci_hsic_msm_wakeup_cnt_fops = { + .open = ehci_hsic_msm_wakeup_cnt_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *ehci_hsic_msm_dbg_root; +static int ehci_hsic_msm_debugfs_init(struct msm_hsic_hcd *mehci) +{ + struct dentry *ehci_hsic_msm_dentry; + + ehci_hsic_msm_dbg_root = debugfs_create_dir("ehci_hsic_msm_dbg", NULL); + + if (!ehci_hsic_msm_dbg_root || IS_ERR(ehci_hsic_msm_dbg_root)) + return -ENODEV; + + ehci_hsic_msm_dentry = debugfs_create_file("bus_voting", + S_IRUGO | S_IWUSR, + ehci_hsic_msm_dbg_root, mehci, + &ehci_hsic_msm_bus_fops); + + if (!ehci_hsic_msm_dentry) { + debugfs_remove_recursive(ehci_hsic_msm_dbg_root); + return -ENODEV; + } + + ehci_hsic_msm_dentry = debugfs_create_file("wakeup_cnt", + S_IRUGO, + ehci_hsic_msm_dbg_root, mehci, + &ehci_hsic_msm_wakeup_cnt_fops); + + if (!ehci_hsic_msm_dentry) { + debugfs_remove_recursive(ehci_hsic_msm_dbg_root); + return -ENODEV; + } + + return 0; +} + +static void ehci_hsic_msm_debugfs_cleanup(void) +{ + debugfs_remove_recursive(ehci_hsic_msm_dbg_root); +} + +static int __devinit ehci_hsic_msm_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + struct msm_hsic_hcd *mehci; + struct msm_hsic_host_platform_data *pdata; + int ret; + + dev_dbg(&pdev->dev, "ehci_msm-hsic probe\n"); + + /* After parent device's probe is executed, it will be put in suspend + * mode. When child device's probe is called, driver core is not + * resuming parent device due to which parent will be in suspend even + * though child is active. Hence resume the parent device explicitly. + */ + if (pdev->dev.parent) + pm_runtime_get_sync(pdev->dev.parent); + + hcd = usb_create_hcd(&msm_hsic_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + + hcd->irq = platform_get_irq(pdev, 0); + if (hcd->irq < 0) { + dev_err(&pdev->dev, "Unable to get IRQ resource\n"); + ret = hcd->irq; + goto put_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get memory resource\n"); + ret = -ENODEV; + goto put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto put_hcd; + } + + mehci = hcd_to_hsic(hcd); + mehci->dev = &pdev->dev; + + mehci->ehci.susp_sof_bug = 1; + + res = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, + "peripheral_status_irq"); + if (res) + mehci->peripheral_status_irq = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "wakeup"); + if (res) { + mehci->wakeup_gpio = res->start; + mehci->wakeup_irq = MSM_GPIO_TO_INT(res->start); + dev_dbg(mehci->dev, "wakeup_irq: %d\n", mehci->wakeup_irq); + } + + ret = msm_hsic_init_clocks(mehci, 1); + if (ret) { + dev_err(&pdev->dev, "unable to initialize clocks\n"); + ret = -ENODEV; + goto unmap; + } + + ret = msm_hsic_init_vddcx(mehci, 1); + if (ret) { + dev_err(&pdev->dev, "unable to initialize VDDCX\n"); + ret = -ENODEV; + goto deinit_clocks; + } + + ret = msm_hsic_reset(mehci); + if (ret) { + dev_err(&pdev->dev, "unable to initialize PHY\n"); + goto deinit_vddcx; + } + + ret = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + if (ret) { + dev_err(&pdev->dev, "unable to register HCD\n"); + goto unconfig_gpio; + } + + device_init_wakeup(&pdev->dev, 1); + wake_lock_init(&mehci->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev)); + wake_lock(&mehci->wlock); + + if (mehci->peripheral_status_irq) { + ret = request_threaded_irq(mehci->peripheral_status_irq, + NULL, hsic_peripheral_status_change, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_SHARED, + "hsic_peripheral_status", mehci); + if (ret) + dev_err(&pdev->dev, "%s:request_irq:%d failed:%d", + __func__, mehci->peripheral_status_irq, ret); + } + + /* configure wakeup irq */ + if (mehci->wakeup_irq) { + ret = request_irq(mehci->wakeup_irq, msm_hsic_wakeup_irq, + IRQF_TRIGGER_HIGH, + "msm_hsic_wakeup", mehci); + if (!ret) { + disable_irq_nosync(mehci->wakeup_irq); + } else { + dev_err(&pdev->dev, "request_irq(%d) failed: %d\n", + mehci->wakeup_irq, ret); + mehci->wakeup_irq = 0; + } + } + + ret = ehci_hsic_msm_debugfs_init(mehci); + if (ret) + dev_dbg(&pdev->dev, "mode debugfs file is" + "not available\n"); + + pdata = mehci->dev->platform_data; + if (pdata && pdata->bus_scale_table) { + mehci->bus_perf_client = + msm_bus_scale_register_client(pdata->bus_scale_table); + /* Configure BUS performance parameters for MAX bandwidth */ + if (mehci->bus_perf_client) { + ret = msm_bus_scale_client_update_request( + mehci->bus_perf_client, 1); + if (ret) + dev_err(&pdev->dev, "%s: Failed to vote for " + "bus bandwidth %d\n", __func__, ret); + } else { + dev_err(&pdev->dev, "%s: Failed to register BUS " + "scaling client!!\n", __func__); + } + } + + /* + * This pdev->dev is assigned parent of root-hub by USB core, + * hence, runtime framework automatically calls this driver's + * runtime APIs based on root-hub's state. + */ + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + /* Decrement the parent device's counter after probe. + * As child is active, parent will not be put into + * suspend mode. + */ + if (pdev->dev.parent) + pm_runtime_put_sync(pdev->dev.parent); + + return 0; + +unconfig_gpio: + msm_hsic_config_gpios(mehci, 0); +deinit_vddcx: + msm_hsic_init_vddcx(mehci, 0); +deinit_clocks: + msm_hsic_init_clocks(mehci, 0); +unmap: + iounmap(hcd->regs); +put_hcd: + usb_put_hcd(hcd); + + return ret; +} + +static int __devexit ehci_hsic_msm_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd); + + if (mehci->peripheral_status_irq) + free_irq(mehci->peripheral_status_irq, mehci); + + if (mehci->wakeup_irq) { + if (mehci->wakeup_irq_enabled) + disable_irq_wake(mehci->wakeup_irq); + free_irq(mehci->wakeup_irq, mehci); + } + + if (mehci->bus_perf_client) + msm_bus_scale_unregister_client(mehci->bus_perf_client); + + ehci_hsic_msm_debugfs_cleanup(); + device_init_wakeup(&pdev->dev, 0); + pm_runtime_set_suspended(&pdev->dev); + + usb_remove_hcd(hcd); + msm_hsic_config_gpios(mehci, 0); + msm_hsic_init_vddcx(mehci, 0); + + msm_hsic_init_clocks(mehci, 0); + wake_lock_destroy(&mehci->wlock); + iounmap(hcd->regs); + usb_put_hcd(hcd); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int msm_hsic_pm_suspend(struct device *dev) +{ + int ret; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd); + + dev_dbg(dev, "ehci-msm-hsic PM suspend\n"); + + if (device_may_wakeup(dev)) + enable_irq_wake(hcd->irq); + + ret = msm_hsic_suspend(mehci); + + if (ret && device_may_wakeup(dev)) + disable_irq_wake(hcd->irq); + + return ret; +} + +static int msm_hsic_pm_resume(struct device *dev) +{ + int ret; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd); + + if (device_may_wakeup(dev)) + disable_irq_wake(hcd->irq); + + ret = msm_hsic_resume(mehci); + if (ret) + return ret; + + /* Bring the device to full powered state upon system resume */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int msm_hsic_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "EHCI runtime idle\n"); + return 0; +} + +static int msm_hsic_runtime_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd); + + dev_dbg(dev, "EHCI runtime suspend\n"); + return msm_hsic_suspend(mehci); +} + +static int msm_hsic_runtime_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd); + + dev_dbg(dev, "EHCI runtime resume\n"); + return msm_hsic_resume(mehci); +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops msm_hsic_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(msm_hsic_pm_suspend, msm_hsic_pm_resume) + SET_RUNTIME_PM_OPS(msm_hsic_runtime_suspend, msm_hsic_runtime_resume, + msm_hsic_runtime_idle) +}; +#endif + +static struct platform_driver ehci_msm_hsic_driver = { + .probe = ehci_hsic_msm_probe, + .remove = __devexit_p(ehci_hsic_msm_remove), + .driver = { + .name = "msm_hsic_host", +#ifdef CONFIG_PM + .pm = &msm_hsic_dev_pm_ops, +#endif + }, +}; diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c index 9803a55fd5f464b600d47b02ae3c2f923060473d..e8e4e10a6b3e55fdf61cd5d97add5dd33c8c69ae 100644 --- a/drivers/usb/host/ehci-msm.c +++ b/drivers/usb/host/ehci-msm.c @@ -1,6 +1,6 @@ /* ehci-msm.c - HSUSB Host Controller Driver Implementation * - * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. * * Partly derived from ehci-fsl.c and ehci-hcd.c * Copyright (c) 2000-2004 by David Brownell @@ -49,7 +49,7 @@ static int ehci_msm_reset(struct usb_hcd *hcd) /* bursts of unspecified length. */ writel(0, USB_AHBBURST); /* Use the AHB transactor */ - writel(0, USB_AHBMODE); + writel_relaxed(0x08, USB_AHBMODE); /* Disable streaming mode and select host mode */ writel(0x13, USB_USBMODE); @@ -158,12 +158,8 @@ static int ehci_msm_probe(struct platform_device *pdev) goto put_transceiver; } + hcd_to_ehci(hcd)->transceiver = phy; device_init_wakeup(&pdev->dev, 1); - /* - * OTG device parent of HCD takes care of putting - * hardware into low power mode. - */ - pm_runtime_no_callbacks(&pdev->dev); pm_runtime_enable(&pdev->dev); return 0; @@ -186,6 +182,7 @@ static int __devexit ehci_msm_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); + hcd_to_ehci(hcd)->transceiver = NULL; otg_set_host(phy->otg, NULL); usb_put_transceiver(phy); @@ -194,7 +191,31 @@ static int __devexit ehci_msm_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_RUNTIME +static int ehci_msm_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "ehci runtime idle\n"); + return 0; +} + +static int ehci_msm_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "ehci runtime suspend\n"); + /* + * Notify OTG about suspend. It takes care of + * putting the hardware in LPM. + */ + return usb_phy_set_suspend(phy, 1); +} + +static int ehci_msm_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "ehci runtime resume\n"); + return usb_phy_set_suspend(phy, 0); +} +#endif + +#ifdef CONFIG_PM_SLEEP static int ehci_msm_pm_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); @@ -202,6 +223,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 @@ -215,7 +239,7 @@ static int ehci_msm_pm_suspend(struct device *dev) wakeup); } - return 0; + return usb_phy_set_suspend(phy, 1); } static int ehci_msm_pm_resume(struct device *dev) @@ -223,18 +247,20 @@ 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; + return usb_phy_set_suspend(phy, 0); } -#else -#define ehci_msm_pm_suspend NULL -#define ehci_msm_pm_resume NULL #endif static const struct dev_pm_ops ehci_msm_dev_pm_ops = { - .suspend = ehci_msm_pm_suspend, - .resume = ehci_msm_pm_resume, + SET_SYSTEM_SLEEP_PM_OPS(ehci_msm_pm_suspend, ehci_msm_pm_resume) + SET_RUNTIME_PM_OPS(ehci_msm_runtime_suspend, ehci_msm_runtime_resume, + ehci_msm_runtime_idle) }; static struct platform_driver ehci_msm_driver = { diff --git a/drivers/usb/host/ehci-msm2.c b/drivers/usb/host/ehci-msm2.c new file mode 100644 index 0000000000000000000000000000000000000000..465728302b12d118d411fd86d553b8fa801d8d3e --- /dev/null +++ b/drivers/usb/host/ehci-msm2.c @@ -0,0 +1,1090 @@ +/* ehci-msm2.c - HSUSB Host Controller Driver Implementation + * + * Copyright (c) 2008-2012, 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 + +#define MSM_USB_BASE (hcd->regs) + +#define PDEV_NAME_LEN 20 + +struct msm_hcd { + struct ehci_hcd ehci; + struct device *dev; + struct clk *iface_clk; + struct clk *core_clk; + struct clk *alt_core_clk; + struct regulator *hsusb_vddcx; + struct regulator *hsusb_3p3; + struct regulator *hsusb_1p8; + struct regulator *vbus; + struct msm_xo_voter *xo_handle; + bool async_int; + bool vbus_on; + atomic_t in_lpm; + struct wake_lock wlock; +}; + +static inline struct msm_hcd *hcd_to_mhcd(struct usb_hcd *hcd) +{ + return (struct msm_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *mhcd_to_hcd(struct msm_hcd *mhcd) +{ + return container_of((void *) mhcd, struct usb_hcd, hcd_priv); +} + +#define HSUSB_PHY_3P3_VOL_MIN 3050000 /* uV */ +#define HSUSB_PHY_3P3_VOL_MAX 3300000 /* uV */ +#define HSUSB_PHY_3P3_HPM_LOAD 50000 /* uA */ + +#define HSUSB_PHY_1P8_VOL_MIN 1800000 /* uV */ +#define HSUSB_PHY_1P8_VOL_MAX 1800000 /* uV */ +#define HSUSB_PHY_1P8_HPM_LOAD 50000 /* uA */ + +#define HSUSB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */ +#define HSUSB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ +#define HSUSB_PHY_VDD_DIG_LOAD 49360 /* uA */ + +static int msm_ehci_init_vddcx(struct msm_hcd *mhcd, int init) +{ + int ret = 0; + + if (!init) + goto disable_reg; + + mhcd->hsusb_vddcx = devm_regulator_get(mhcd->dev, "HSUSB_VDDCX"); + if (IS_ERR(mhcd->hsusb_vddcx)) { + dev_err(mhcd->dev, "unable to get ehci vddcx\n"); + return PTR_ERR(mhcd->hsusb_vddcx); + } + + ret = regulator_set_voltage(mhcd->hsusb_vddcx, + HSUSB_PHY_VDD_DIG_VOL_MIN, + HSUSB_PHY_VDD_DIG_VOL_MAX); + if (ret) { + dev_err(mhcd->dev, "unable to set the voltage" + "for ehci vddcx\n"); + return ret; + } + + ret = regulator_set_optimum_mode(mhcd->hsusb_vddcx, + HSUSB_PHY_VDD_DIG_LOAD); + if (ret < 0) { + dev_err(mhcd->dev, "%s: Unable to set optimum mode of the" + " regulator: VDDCX\n", __func__); + goto reg_optimum_mode_err; + } + + ret = regulator_enable(mhcd->hsusb_vddcx); + if (ret) { + dev_err(mhcd->dev, "unable to enable ehci vddcx\n"); + goto reg_enable_err; + } + + return 0; + +disable_reg: + regulator_disable(mhcd->hsusb_vddcx); +reg_enable_err: + regulator_set_optimum_mode(mhcd->hsusb_vddcx, 0); +reg_optimum_mode_err: + regulator_set_voltage(mhcd->hsusb_vddcx, 0, + HSUSB_PHY_VDD_DIG_VOL_MIN); + return ret; + +} + +static int msm_ehci_ldo_init(struct msm_hcd *mhcd, int init) +{ + int rc = 0; + + if (!init) + goto put_1p8; + + mhcd->hsusb_3p3 = devm_regulator_get(mhcd->dev, "HSUSB_3p3"); + if (IS_ERR(mhcd->hsusb_3p3)) { + dev_err(mhcd->dev, "unable to get hsusb 3p3\n"); + return PTR_ERR(mhcd->hsusb_3p3); + } + + rc = regulator_set_voltage(mhcd->hsusb_3p3, + HSUSB_PHY_3P3_VOL_MIN, HSUSB_PHY_3P3_VOL_MAX); + if (rc) { + dev_err(mhcd->dev, "unable to set voltage level for" + "hsusb 3p3\n"); + return rc; + } + mhcd->hsusb_1p8 = devm_regulator_get(mhcd->dev, "HSUSB_1p8"); + if (IS_ERR(mhcd->hsusb_1p8)) { + dev_err(mhcd->dev, "unable to get hsusb 1p8\n"); + rc = PTR_ERR(mhcd->hsusb_1p8); + goto put_3p3_lpm; + } + rc = regulator_set_voltage(mhcd->hsusb_1p8, + HSUSB_PHY_1P8_VOL_MIN, HSUSB_PHY_1P8_VOL_MAX); + if (rc) { + dev_err(mhcd->dev, "unable to set voltage level for" + "hsusb 1p8\n"); + goto put_1p8; + } + + return 0; + +put_1p8: + regulator_set_voltage(mhcd->hsusb_1p8, 0, HSUSB_PHY_1P8_VOL_MAX); +put_3p3_lpm: + regulator_set_voltage(mhcd->hsusb_3p3, 0, HSUSB_PHY_3P3_VOL_MAX); + + return rc; +} + +#ifdef CONFIG_PM_SLEEP +#define HSUSB_PHY_SUSP_DIG_VOL_P50 500000 +#define HSUSB_PHY_SUSP_DIG_VOL_P75 750000 +static int msm_ehci_config_vddcx(struct msm_hcd *mhcd, int high) +{ + struct msm_usb_host_platform_data *pdata; + int max_vol = HSUSB_PHY_VDD_DIG_VOL_MAX; + int min_vol; + int ret; + + pdata = mhcd->dev->platform_data; + + if (high) + min_vol = HSUSB_PHY_VDD_DIG_VOL_MIN; + else if (pdata && pdata->dock_connect_irq && + !irq_read_line(pdata->dock_connect_irq)) + min_vol = HSUSB_PHY_SUSP_DIG_VOL_P75; + else + min_vol = HSUSB_PHY_SUSP_DIG_VOL_P50; + + ret = regulator_set_voltage(mhcd->hsusb_vddcx, min_vol, max_vol); + if (ret) { + dev_err(mhcd->dev, "%s: unable to set the voltage of regulator" + " HSUSB_VDDCX\n", __func__); + return ret; + } + + dev_dbg(mhcd->dev, "%s: min_vol:%d max_vol:%d\n", __func__, min_vol, + max_vol); + + return ret; +} +#else +static int msm_ehci_config_vddcx(struct msm_hcd *mhcd, int high) +{ + return 0; +} +#endif + +static void msm_ehci_vbus_power(struct msm_hcd *mhcd, bool on) +{ + int ret; + + if (!mhcd->vbus) { + pr_err("vbus is NULL."); + return; + } + + if (mhcd->vbus_on == on) + return; + + if (on) { + ret = regulator_enable(mhcd->vbus); + if (ret) { + pr_err("unable to enable vbus\n"); + return; + } + mhcd->vbus_on = true; + } else { + ret = regulator_disable(mhcd->vbus); + if (ret) { + pr_err("unable to disable vbus\n"); + return; + } + mhcd->vbus_on = false; + } +} + +static irqreturn_t msm_ehci_dock_connect_irq(int irq, void *data) +{ + const struct msm_usb_host_platform_data *pdata; + struct msm_hcd *mhcd = data; + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + + pdata = mhcd->dev->platform_data; + + if (atomic_read(&mhcd->in_lpm)) + usb_hcd_resume_root_hub(hcd); + + if (irq_read_line(pdata->dock_connect_irq)) { + dev_dbg(mhcd->dev, "%s:Dock removed disable vbus\n", __func__); + msm_ehci_vbus_power(mhcd, 0); + } else { + dev_dbg(mhcd->dev, "%s:Dock connected enable vbus\n", __func__); + msm_ehci_vbus_power(mhcd, 1); + } + + return IRQ_HANDLED; +} + +static int msm_ehci_init_vbus(struct msm_hcd *mhcd, int init) +{ + int rc = 0; + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + const struct msm_usb_host_platform_data *pdata; + + pdata = mhcd->dev->platform_data; + + if (!init) { + if (pdata && pdata->dock_connect_irq) + free_irq(pdata->dock_connect_irq, mhcd); + return rc; + } + + mhcd->vbus = devm_regulator_get(mhcd->dev, "vbus"); + if (IS_ERR(mhcd->vbus)) { + pr_err("Unable to get vbus\n"); + return -ENODEV; + } + + if (pdata) { + hcd->power_budget = pdata->power_budget; + + if (pdata->dock_connect_irq) { + rc = request_threaded_irq(pdata->dock_connect_irq, NULL, + msm_ehci_dock_connect_irq, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "msm_ehci_host", mhcd); + if (!rc) + enable_irq_wake(pdata->dock_connect_irq); + } + } + return rc; +} + +static int msm_ehci_ldo_enable(struct msm_hcd *mhcd, int on) +{ + int ret = 0; + + if (IS_ERR(mhcd->hsusb_1p8)) { + dev_err(mhcd->dev, "%s: HSUSB_1p8 is not initialized\n", + __func__); + return -ENODEV; + } + + if (IS_ERR(mhcd->hsusb_3p3)) { + dev_err(mhcd->dev, "%s: HSUSB_3p3 is not initialized\n", + __func__); + return -ENODEV; + } + + if (on) { + ret = regulator_set_optimum_mode(mhcd->hsusb_1p8, + HSUSB_PHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(mhcd->dev, "%s: Unable to set HPM of the" + " regulator: HSUSB_1p8\n", __func__); + return ret; + } + + ret = regulator_enable(mhcd->hsusb_1p8); + if (ret) { + dev_err(mhcd->dev, "%s: unable to enable the hsusb" + " 1p8\n", __func__); + regulator_set_optimum_mode(mhcd->hsusb_1p8, 0); + return ret; + } + + ret = regulator_set_optimum_mode(mhcd->hsusb_3p3, + HSUSB_PHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(mhcd->dev, "%s: Unable to set HPM of the " + "regulator: HSUSB_3p3\n", __func__); + regulator_set_optimum_mode(mhcd->hsusb_1p8, 0); + regulator_disable(mhcd->hsusb_1p8); + return ret; + } + + ret = regulator_enable(mhcd->hsusb_3p3); + if (ret) { + dev_err(mhcd->dev, "%s: unable to enable the " + "hsusb 3p3\n", __func__); + regulator_set_optimum_mode(mhcd->hsusb_3p3, 0); + regulator_set_optimum_mode(mhcd->hsusb_1p8, 0); + regulator_disable(mhcd->hsusb_1p8); + return ret; + } + + } else { + ret = regulator_disable(mhcd->hsusb_1p8); + if (ret) { + dev_err(mhcd->dev, "%s: unable to disable the " + "hsusb 1p8\n", __func__); + return ret; + } + + ret = regulator_set_optimum_mode(mhcd->hsusb_1p8, 0); + if (ret < 0) + dev_err(mhcd->dev, "%s: Unable to set LPM of the " + "regulator: HSUSB_1p8\n", __func__); + + ret = regulator_disable(mhcd->hsusb_3p3); + if (ret) { + dev_err(mhcd->dev, "%s: unable to disable the " + "hsusb 3p3\n", __func__); + return ret; + } + ret = regulator_set_optimum_mode(mhcd->hsusb_3p3, 0); + if (ret < 0) + dev_err(mhcd->dev, "%s: Unable to set LPM of the " + "regulator: HSUSB_3p3\n", __func__); + } + + dev_dbg(mhcd->dev, "reg (%s)\n", on ? "HPM" : "LPM"); + + return ret < 0 ? ret : 0; +} + + +#define ULPI_IO_TIMEOUT_USECS (10 * 1000) +static int msm_ulpi_read(struct msm_hcd *mhcd, u32 reg) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + unsigned long timeout; + + /* initiate read operation */ + writel_relaxed(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + timeout = jiffies + usecs_to_jiffies(ULPI_IO_TIMEOUT_USECS); + while (readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN) { + if (time_after(jiffies, timeout)) { + dev_err(mhcd->dev, "msm_ulpi_read: timeout %08x\n", + readl_relaxed(USB_ULPI_VIEWPORT)); + return -ETIMEDOUT; + } + udelay(1); + } + + return ULPI_DATA_READ(readl_relaxed(USB_ULPI_VIEWPORT)); +} + + +static int msm_ulpi_write(struct msm_hcd *mhcd, u32 val, u32 reg) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + unsigned long timeout; + + /* initiate write operation */ + writel_relaxed(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + timeout = jiffies + usecs_to_jiffies(ULPI_IO_TIMEOUT_USECS); + while (readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN) { + if (time_after(jiffies, timeout)) { + dev_err(mhcd->dev, "msm_ulpi_write: timeout\n"); + return -ETIMEDOUT; + } + udelay(1); + } + + return 0; +} + +static int msm_ehci_link_clk_reset(struct msm_hcd *mhcd, bool assert) +{ + int ret; + + if (assert) { + ret = clk_reset(mhcd->alt_core_clk, CLK_RESET_ASSERT); + if (ret) + dev_err(mhcd->dev, "usb alt_core_clk assert failed\n"); + } else { + ret = clk_reset(mhcd->alt_core_clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(mhcd->dev, "usb alt_core_clk deassert failed\n"); + } + + return ret; +} + +static int msm_ehci_phy_reset(struct msm_hcd *mhcd) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + u32 val; + int ret; + int retries; + + ret = msm_ehci_link_clk_reset(mhcd, 1); + if (ret) + return ret; + + udelay(1); + + ret = msm_ehci_link_clk_reset(mhcd, 0); + if (ret) + return ret; + + val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK; + writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC); + + for (retries = 3; retries > 0; retries--) { + ret = msm_ulpi_write(mhcd, ULPI_FUNC_CTRL_SUSPENDM, + ULPI_CLR(ULPI_FUNC_CTRL)); + if (!ret) + break; + } + if (!retries) + return -ETIMEDOUT; + + /* Wakeup the PHY with a reg-access for calibration */ + for (retries = 3; retries > 0; retries--) { + ret = msm_ulpi_read(mhcd, ULPI_DEBUG); + if (ret != -ETIMEDOUT) + break; + } + if (!retries) + return -ETIMEDOUT; + + dev_info(mhcd->dev, "phy_reset: success\n"); + + return 0; +} + +#define LINK_RESET_TIMEOUT_USEC (250 * 1000) +static int msm_hsusb_reset(struct msm_hcd *mhcd) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + unsigned long timeout; + int ret; + + clk_prepare_enable(mhcd->alt_core_clk); + ret = msm_ehci_phy_reset(mhcd); + if (ret) { + dev_err(mhcd->dev, "phy_reset failed\n"); + return ret; + } + + writel_relaxed(USBCMD_RESET, USB_USBCMD); + + timeout = jiffies + usecs_to_jiffies(LINK_RESET_TIMEOUT_USEC); + while (readl_relaxed(USB_USBCMD) & USBCMD_RESET) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + udelay(1); + } + + /* select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + + msleep(100); + + writel_relaxed(0x0, USB_AHBBURST); + writel_relaxed(0x08, USB_AHBMODE); + + /* Ensure that RESET operation is completed before turning off clock */ + mb(); + clk_disable_unprepare(mhcd->alt_core_clk); + + /*rising edge interrupts with Dp rise and fall enabled*/ + msm_ulpi_write(mhcd, ULPI_INT_DP, ULPI_USB_INT_EN_RISE); + msm_ulpi_write(mhcd, ULPI_INT_DP, ULPI_USB_INT_EN_FALL); + + /*Clear the PHY interrupts by reading the PHY interrupt latch register*/ + msm_ulpi_read(mhcd, ULPI_USB_INT_LATCH); + + return 0; +} + +#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000) +#define PHY_RESUME_TIMEOUT_USEC (100 * 1000) + +#ifdef CONFIG_PM_SLEEP +static int msm_ehci_suspend(struct msm_hcd *mhcd) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + unsigned long timeout; + int ret; + u32 portsc; + + if (atomic_read(&mhcd->in_lpm)) { + dev_dbg(mhcd->dev, "%s called in lpm\n", __func__); + return 0; + } + + disable_irq(hcd->irq); + + /* Set the PHCD bit, only if it is not set by the controller. + * 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 + * in failure case. + */ + portsc = readl_relaxed(USB_PORTSC); + if (!(portsc & PORTSC_PHCD)) { + writel_relaxed(portsc | PORTSC_PHCD, + USB_PORTSC); + + timeout = jiffies + usecs_to_jiffies(PHY_SUSPEND_TIMEOUT_USEC); + while (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) { + if (time_after(jiffies, timeout)) { + dev_err(mhcd->dev, "Unable to suspend PHY\n"); + msm_hsusb_reset(mhcd); + break; + } + udelay(1); + } + } + + /* + * PHY has capability to generate interrupt asynchronously in low + * power mode (LPM). This interrupt is level triggered. So USB IRQ + * line must be disabled till async interrupt enable bit is cleared + * in USBCMD register. Assert STP (ULPI interface STOP signal) to + * block data communication from PHY. + */ + writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL | + ULPI_STP_CTRL, USB_USBCMD); + + /* + * Ensure that hardware is put in low power mode before + * clocks are turned OFF and VDD is allowed to minimize. + */ + mb(); + + clk_disable_unprepare(mhcd->iface_clk); + clk_disable_unprepare(mhcd->core_clk); + + /* usb phy does not require TCXO clock, hence vote for TCXO disable */ + ret = msm_xo_mode_vote(mhcd->xo_handle, MSM_XO_MODE_OFF); + if (ret) + dev_err(mhcd->dev, "%s failed to devote for " + "TCXO D0 buffer%d\n", __func__, ret); + + msm_ehci_config_vddcx(mhcd, 0); + + atomic_set(&mhcd->in_lpm, 1); + enable_irq(hcd->irq); + wake_unlock(&mhcd->wlock); + + dev_info(mhcd->dev, "EHCI USB in low power mode\n"); + + return 0; +} + +static int msm_ehci_resume(struct msm_hcd *mhcd) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + unsigned long timeout; + unsigned temp; + int ret; + + if (!atomic_read(&mhcd->in_lpm)) { + dev_dbg(mhcd->dev, "%s called in !in_lpm\n", __func__); + return 0; + } + + wake_lock(&mhcd->wlock); + + /* Vote for TCXO when waking up the phy */ + ret = msm_xo_mode_vote(mhcd->xo_handle, MSM_XO_MODE_ON); + if (ret) + dev_err(mhcd->dev, "%s failed to vote for " + "TCXO D0 buffer%d\n", __func__, ret); + + clk_prepare_enable(mhcd->core_clk); + clk_prepare_enable(mhcd->iface_clk); + + msm_ehci_config_vddcx(mhcd, 1); + + temp = readl_relaxed(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel_relaxed(temp, USB_USBCMD); + + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) + goto skip_phy_resume; + + temp = readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD; + writel_relaxed(temp, USB_PORTSC); + + timeout = jiffies + usecs_to_jiffies(PHY_RESUME_TIMEOUT_USEC); + while ((readl_relaxed(USB_PORTSC) & PORTSC_PHCD) || + !(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE)) { + if (time_after(jiffies, timeout)) { + /*This is a fatal error. Reset the link and PHY*/ + dev_err(mhcd->dev, "Unable to resume USB. Resetting the h/w\n"); + msm_hsusb_reset(mhcd); + break; + } + udelay(1); + } + +skip_phy_resume: + + atomic_set(&mhcd->in_lpm, 0); + + if (mhcd->async_int) { + mhcd->async_int = false; + pm_runtime_put_noidle(mhcd->dev); + enable_irq(hcd->irq); + } + + dev_info(mhcd->dev, "EHCI USB exited from low power mode\n"); + + return 0; +} +#endif + +static irqreturn_t msm_ehci_irq(struct usb_hcd *hcd) +{ + struct msm_hcd *mhcd = hcd_to_mhcd(hcd); + + if (atomic_read(&mhcd->in_lpm)) { + disable_irq_nosync(hcd->irq); + mhcd->async_int = true; + pm_runtime_get(mhcd->dev); + return IRQ_HANDLED; + } + + return ehci_irq(hcd); +} + +static int msm_ehci_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)); + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache the data to minimize the chip reads*/ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + hcd->has_tt = 1; + ehci->sbrn = HCD_USB2; + + retval = ehci_halt(ehci); + if (retval) + return retval; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + retval = ehci_reset(ehci); + if (retval) + return retval; + + /* bursts of unspecified length. */ + writel_relaxed(0, USB_AHBBURST); + /* Use the AHB transactor */ + writel_relaxed(0x08, USB_AHBMODE); + /* Disable streaming mode and select host mode */ + writel_relaxed(0x13, USB_USBMODE); + + ehci_port_power(ehci, 1); + return 0; +} + +static struct hc_driver msm_hc2_driver = { + .description = hcd_name, + .product_desc = "Qualcomm EHCI Host Controller", + .hcd_priv_size = sizeof(struct msm_hcd), + + /* + * generic hardware linkage + */ + .irq = msm_ehci_irq, + .flags = HCD_USB2 | HCD_MEMORY, + + .reset = msm_ehci_reset, + .start = ehci_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, + .endpoint_reset = ehci_endpoint_reset, + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + /* + * PM support + */ + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +}; + +static int msm_ehci_init_clocks(struct msm_hcd *mhcd, u32 init) +{ + int ret = 0; + + if (!init) + goto put_clocks; + + /* 60MHz alt_core_clk is for LINK to be used during PHY RESET */ + mhcd->alt_core_clk = clk_get(mhcd->dev, "alt_core_clk"); + if (IS_ERR(mhcd->alt_core_clk)) { + dev_err(mhcd->dev, "failed to get alt_core_clk\n"); + ret = PTR_ERR(mhcd->alt_core_clk); + return ret; + } + clk_set_rate(mhcd->alt_core_clk, 60000000); + + /* iface_clk is required for data transfers */ + mhcd->iface_clk = clk_get(mhcd->dev, "iface_clk"); + if (IS_ERR(mhcd->iface_clk)) { + dev_err(mhcd->dev, "failed to get iface_clk\n"); + ret = PTR_ERR(mhcd->iface_clk); + goto put_alt_core_clk; + } + + /* Link's protocol engine is based on pclk which must + * be running >55Mhz and frequency should also not change. + * Hence, vote for maximum clk frequency on its source + */ + mhcd->core_clk = clk_get(mhcd->dev, "core_clk"); + if (IS_ERR(mhcd->core_clk)) { + dev_err(mhcd->dev, "failed to get core_clk\n"); + ret = PTR_ERR(mhcd->core_clk); + goto put_iface_clk; + } + clk_set_rate(mhcd->core_clk, INT_MAX); + + clk_prepare_enable(mhcd->core_clk); + clk_prepare_enable(mhcd->iface_clk); + + return 0; + +put_clocks: + clk_disable_unprepare(mhcd->iface_clk); + clk_disable_unprepare(mhcd->core_clk); + clk_put(mhcd->core_clk); +put_iface_clk: + clk_put(mhcd->iface_clk); +put_alt_core_clk: + clk_put(mhcd->alt_core_clk); + + return ret; +} + +static int __devinit ehci_msm2_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + struct msm_hcd *mhcd; + const struct msm_usb_host_platform_data *pdata; + char pdev_name[PDEV_NAME_LEN]; + int ret; + + dev_dbg(&pdev->dev, "ehci_msm2 probe\n"); + + hcd = usb_create_hcd(&msm_hc2_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + + hcd->irq = platform_get_irq(pdev, 0); + if (hcd->irq < 0) { + dev_err(&pdev->dev, "Unable to get IRQ resource\n"); + ret = hcd->irq; + goto put_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get memory resource\n"); + ret = -ENODEV; + goto put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto put_hcd; + } + + mhcd = hcd_to_mhcd(hcd); + mhcd->dev = &pdev->dev; + + snprintf(pdev_name, PDEV_NAME_LEN, "%s.%d", pdev->name, pdev->id); + mhcd->xo_handle = msm_xo_get(MSM_XO_TCXO_D0, pdev_name); + if (IS_ERR(mhcd->xo_handle)) { + dev_err(&pdev->dev, "%s not able to get the handle " + "to vote for TCXO D0 buffer\n", __func__); + ret = PTR_ERR(mhcd->xo_handle); + goto unmap; + } + + ret = msm_xo_mode_vote(mhcd->xo_handle, MSM_XO_MODE_ON); + if (ret) { + dev_err(&pdev->dev, "%s failed to vote for TCXO " + "D0 buffer%d\n", __func__, ret); + goto free_xo_handle; + } + + ret = msm_ehci_init_clocks(mhcd, 1); + if (ret) { + dev_err(&pdev->dev, "unable to initialize clocks\n"); + ret = -ENODEV; + goto devote_xo_handle; + } + + ret = msm_ehci_init_vddcx(mhcd, 1); + if (ret) { + dev_err(&pdev->dev, "unable to initialize VDDCX\n"); + ret = -ENODEV; + goto deinit_clocks; + } + + ret = msm_ehci_config_vddcx(mhcd, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); + goto deinit_vddcx; + } + + ret = msm_ehci_ldo_init(mhcd, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); + goto deinit_vddcx; + } + + ret = msm_ehci_ldo_enable(mhcd, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg enable failed\n"); + goto deinit_ldo; + } + + ret = msm_ehci_init_vbus(mhcd, 1); + if (ret) { + dev_err(&pdev->dev, "unable to get vbus\n"); + goto disable_ldo; + } + + ret = msm_hsusb_reset(mhcd); + if (ret) { + dev_err(&pdev->dev, "hsusb PHY initialization failed\n"); + goto vbus_deinit; + } + + ret = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + if (ret) { + dev_err(&pdev->dev, "unable to register HCD\n"); + goto vbus_deinit; + } + + pdata = mhcd->dev->platform_data; + if (pdata && (!pdata->dock_connect_irq || + !irq_read_line(pdata->dock_connect_irq))) + msm_ehci_vbus_power(mhcd, 1); + + device_init_wakeup(&pdev->dev, 1); + wake_lock_init(&mhcd->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev)); + wake_lock(&mhcd->wlock); + /* + * This pdev->dev is assigned parent of root-hub by USB core, + * hence, runtime framework automatically calls this driver's + * runtime APIs based on root-hub's state. + */ + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +vbus_deinit: + msm_ehci_init_vbus(mhcd, 0); +disable_ldo: + msm_ehci_ldo_enable(mhcd, 0); +deinit_ldo: + msm_ehci_ldo_init(mhcd, 0); +deinit_vddcx: + msm_ehci_init_vddcx(mhcd, 0); +deinit_clocks: + msm_ehci_init_clocks(mhcd, 0); +devote_xo_handle: + msm_xo_mode_vote(mhcd->xo_handle, MSM_XO_MODE_OFF); +free_xo_handle: + msm_xo_put(mhcd->xo_handle); +unmap: + iounmap(hcd->regs); +put_hcd: + usb_put_hcd(hcd); + + return ret; +} + +static int __devexit ehci_msm2_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct msm_hcd *mhcd = hcd_to_mhcd(hcd); + + device_init_wakeup(&pdev->dev, 0); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + usb_remove_hcd(hcd); + + msm_xo_put(mhcd->xo_handle); + msm_ehci_vbus_power(mhcd, 0); + msm_ehci_init_vbus(mhcd, 0); + msm_ehci_ldo_enable(mhcd, 0); + msm_ehci_ldo_init(mhcd, 0); + msm_ehci_init_vddcx(mhcd, 0); + + msm_ehci_init_clocks(mhcd, 0); + wake_lock_destroy(&mhcd->wlock); + iounmap(hcd->regs); + usb_put_hcd(hcd); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ehci_msm2_pm_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hcd *mhcd = hcd_to_mhcd(hcd); + + dev_dbg(dev, "ehci-msm2 PM suspend\n"); + + if (device_may_wakeup(dev)) + enable_irq_wake(hcd->irq); + + return msm_ehci_suspend(mhcd); + +} + +static int ehci_msm2_pm_resume(struct device *dev) +{ + int ret; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hcd *mhcd = hcd_to_mhcd(hcd); + + dev_dbg(dev, "ehci-msm2 PM resume\n"); + + if (device_may_wakeup(dev)) + disable_irq_wake(hcd->irq); + + ret = msm_ehci_resume(mhcd); + if (ret) + return ret; + + /* Bring the device to full powered state upon system resume */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int ehci_msm2_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "EHCI runtime idle\n"); + + return 0; +} + +static int ehci_msm2_runtime_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hcd *mhcd = hcd_to_mhcd(hcd); + + dev_dbg(dev, "EHCI runtime suspend\n"); + return msm_ehci_suspend(mhcd); +} + +static int ehci_msm2_runtime_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct msm_hcd *mhcd = hcd_to_mhcd(hcd); + + dev_dbg(dev, "EHCI runtime resume\n"); + return msm_ehci_resume(mhcd); +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops ehci_msm2_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ehci_msm2_pm_suspend, ehci_msm2_pm_resume) + SET_RUNTIME_PM_OPS(ehci_msm2_runtime_suspend, ehci_msm2_runtime_resume, + ehci_msm2_runtime_idle) +}; +#endif + +static struct platform_driver ehci_msm2_driver = { + .probe = ehci_msm2_probe, + .remove = __devexit_p(ehci_msm2_remove), + .driver = { + .name = "msm_ehci_host", +#ifdef CONFIG_PM + .pm = &ehci_msm2_dev_pm_ops, +#endif + }, +}; diff --git a/drivers/usb/host/ehci-msm72k.c b/drivers/usb/host/ehci-msm72k.c new file mode 100644 index 0000000000000000000000000000000000000000..816e408957195bc0391b313b90d85ad88f250e27 --- /dev/null +++ b/drivers/usb/host/ehci-msm72k.c @@ -0,0 +1,812 @@ +/* ehci-msm.c - HSUSB Host Controller Driver Implementation + * + * Copyright (c) 2008-2012, 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 *alt_core_clk; + struct clk *iface_clk; + 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 usb_phy *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_prepare_enable(pdata->ebi1_clk); + else + clk_disable_unprepare(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_prepare_enable(mhcd->alt_core_clk); + clk_prepare_enable(mhcd->iface_clk); + 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_unprepare(mhcd->alt_core_clk); + clk_disable_unprepare(mhcd->iface_clk); + 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, phy); + + /* + * 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 = usb_phy_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) { + usb_phy_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, phy); +#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; +} + +static int msm_xusb_init_host(struct platform_device *pdev, + 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 = usb_get_transceiver(); + if (!mhcd->xceiv) + return -ENODEV; + otg = container_of(mhcd->xceiv, struct msm_otg, phy); + hcd->regs = otg->regs; + otg->start_host = msm_hsusb_start_host; + + ret = otg_set_host(mhcd->xceiv->otg, &hcd->self); + ehci->transceiver = mhcd->xceiv; + break; + case USB_PHY_SERIAL_PMIC: + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + + if (!hcd->regs) + return -EFAULT; + /* get usb clocks */ + mhcd->alt_core_clk = clk_get(&pdev->dev, "alt_core_clk"); + if (IS_ERR(mhcd->alt_core_clk)) { + iounmap(hcd->regs); + return PTR_ERR(mhcd->alt_core_clk); + } + + mhcd->iface_clk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(mhcd->iface_clk)) { + iounmap(hcd->regs); + clk_put(mhcd->alt_core_clk); + return PTR_ERR(mhcd->iface_clk); + } + 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->alt_core_clk); + clk_put(mhcd->iface_clk); + } + 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(&pdev->dev, "core_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(pdev, 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); + hcd_to_ehci(hcd)->transceiver = NULL; + otg_set_host(mhcd->xceiv->otg, NULL); + usb_put_transceiver(mhcd->xceiv); + cancel_work_sync(&mhcd->otg_work); + break; + case USB_PHY_SERIAL_PMIC: + iounmap(hcd->regs); + clk_put(mhcd->alt_core_clk); + clk_put(mhcd->iface_clk); + 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 36ca5077cdf79df7898880024470f788b7bac45f..4c59eabc93b829f525e9a8157ddecb9839db2856 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1154,6 +1154,111 @@ submit_async ( 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 2694ed6558d2d954c03a4ba3b77eabdc64f9c7f6..c69104d5d532e447062bd612501e4450da85bc6e 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -149,6 +149,7 @@ struct ehci_hcd { /* one per controller */ unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ unsigned has_synopsys_hc_bug:1; /* Synopsys HC */ unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */ + unsigned susp_sof_bug:1; /*Chip Idea HC*/ /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) @@ -748,6 +749,23 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) #endif +/* + * Writing to dma coherent memory on ARM may be delayed via L2 + * writing buffer, so introduce the helper which can flush L2 writing + * buffer into memory immediately, especially used to flush ehci + * descriptor to memory. + * */ +#ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE +static inline void ehci_sync_mem(void) +{ + mb(); +} +#else +static inline void ehci_sync_mem(void) +{ +} +#endif + /*-------------------------------------------------------------------------*/ #ifdef CONFIG_PCI diff --git a/drivers/usb/host/pehci/Makefile b/drivers/usb/host/pehci/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..8c0d17fedf075e882f2164a74d5dba8266391511 --- /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 0000000000000000000000000000000000000000..91408e5393d753a507797f79c0de0ca04387ea3c --- /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 0000000000000000000000000000000000000000..2d66e5765bb298a31d885d1b503e3e68329818d2 --- /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 0000000000000000000000000000000000000000..35c0203307a71033dcf3b112413e2c0f16b4993e --- /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 0000000000000000000000000000000000000000..a7a65b7ef2ae260bf77f2e7b7c8d6ad5cff39bcc --- /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 0000000000000000000000000000000000000000..7355185bd548976c8ce31920015cc0207f69131c --- /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 0000000000000000000000000000000000000000..0c8552efb0de2faf1faa37c953c2c8c2bb023726 --- /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 0000000000000000000000000000000000000000..6699c3af33edcf42728d005d33af85abe91348a3 --- /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 0000000000000000000000000000000000000000..dbf28a92e140f290da624c939592ccd03220ec5a --- /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 100644 index 0000000000000000000000000000000000000000..546d9e9b82da70a0e792ce7a2f4b78be6e5fe2ff --- /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 0000000000000000000000000000000000000000..19e9441722e6f42515bd7777ccf948579d8e7d3e --- /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 0000000000000000000000000000000000000000..cc6a06bee63e843a27eb3ee03707edbb9ccdbcf1 --- /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 0000000000000000000000000000000000000000..093800ee3017244959f68cb6bd37d74fde134d33 --- /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 1bfcd02ebeb560314aa29e6e8fbe85278591e659..13828e0d4012b6b0d7034ba50c907981ca5fcb16 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 @@ -244,3 +259,37 @@ config USB_YUREX To compile this driver as a module, choose M here: the module will be called yurex. +config USB_QCOM_DIAG_BRIDGE + tristate "USB Qualcomm diagnostic bridge driver" + depends on USB + help + Say Y here if you have a Qualcomm modem device connected via USB that + will be bridged in kernel space. This driver communicates with the + diagnostic interface and allows for bridging with the diag forwarding + driver. + + To compile this driver as a module, choose M here: the + module will be called diag_bridge. If unsure, choose N. + +config USB_QCOM_DIAG_BRIDGE_TEST + tristate "USB Qualcomm diagnostic bridge driver test" + depends on USB && USB_QCOM_DIAG_BRIDGE + help + Say Y here if you want to enable the test hook for the + Qualcomm diag bridge driver. When enabled, this will create + a debugfs file entry named "diag_bridge_test" which can be used + to send a ping command to the diag port of the modem over USB. + + To compile this driver as a module, choose M here: the + module will be called diag_bridge_test. If unsure, choose N. + +config USB_QCOM_MDM_BRIDGE + tristate "USB Qualcomm modem bridge driver for DUN and RMNET" + depends on USB + help + Say Y here if you have a Qualcomm modem device connected via USB that + will be bridged in kernel space. This driver works as a bridge to pass + control and data packets between the modem and peripheral usb gadget + driver for dial up network and RMNET. + To compile this driver as a module, choose M here: the module + will be called mdm_bridge. If unsure, choose N. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 796ce7ebccc8dcdc9b9740a744b9f8f8125a22a2..b4aee65806ce6dd951dbe4552e2d726270990384 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -21,9 +21,15 @@ 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 obj-$(CONFIG_USB_YUREX) += yurex.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ + +obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_bridge.o +obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE_TEST) += diag_bridge_test.o +mdm_bridge-y := mdm_ctrl_bridge.o mdm_data_bridge.o +obj-$(CONFIG_USB_QCOM_MDM_BRIDGE) += mdm_bridge.o diff --git a/drivers/usb/misc/diag_bridge.c b/drivers/usb/misc/diag_bridge.c new file mode 100644 index 0000000000000000000000000000000000000000..96e5a900794d8993ecd0245e3c705baf22fa6cf2 --- /dev/null +++ b/drivers/usb/misc/diag_bridge.c @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_DESC "USB host diag bridge driver" +#define DRIVER_VERSION "1.0" + +struct diag_bridge { + struct usb_device *udev; + struct usb_interface *ifc; + struct usb_anchor submitted; + __u8 in_epAddr; + __u8 out_epAddr; + int err; + struct kref kref; + struct diag_bridge_ops *ops; + struct platform_device *pdev; + + /* debugging counters */ + unsigned long bytes_to_host; + unsigned long bytes_to_mdm; + unsigned pending_reads; + unsigned pending_writes; +}; +struct diag_bridge *__dev; + +int diag_bridge_open(struct diag_bridge_ops *ops) +{ + struct diag_bridge *dev = __dev; + + if (!dev) { + err("dev is null"); + return -ENODEV; + } + + dev->ops = ops; + dev->err = 0; + + return 0; +} +EXPORT_SYMBOL(diag_bridge_open); + +void diag_bridge_close(void) +{ + struct diag_bridge *dev = __dev; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + usb_kill_anchored_urbs(&dev->submitted); + + dev->ops = 0; +} +EXPORT_SYMBOL(diag_bridge_close); + +static void diag_bridge_read_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + dev_dbg(&dev->udev->dev, "%s: status:%d actual:%d\n", __func__, + urb->status, urb->actual_length); + + if (urb->status == -EPROTO) { + dev_err(&dev->udev->dev, "%s: proto error\n", __func__); + /* save error so that subsequent read/write returns ESHUTDOWN */ + dev->err = urb->status; + return; + } + + if (cbs && cbs->read_complete_cb) + cbs->read_complete_cb(cbs->ctxt, + urb->transfer_buffer, + urb->transfer_buffer_length, + urb->status < 0 ? urb->status : urb->actual_length); + + dev->bytes_to_host += urb->actual_length; + dev->pending_reads--; +} + +int diag_bridge_read(char *data, int size) +{ + struct urb *urb = NULL; + unsigned int pipe; + struct diag_bridge *dev = __dev; + int ret; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + if (!size) { + dev_err(&dev->udev->dev, "invalid size:%d\n", size); + return -EINVAL; + } + + if (!dev->ifc) { + dev_err(&dev->udev->dev, "device is disconnected\n"); + return -ENODEV; + } + + /* if there was a previous unrecoverable error, just quit */ + if (dev->err) + return -ESHUTDOWN; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&dev->udev->dev, "unable to allocate urb\n"); + return -ENOMEM; + } + + ret = usb_autopm_get_interface(dev->ifc); + if (ret < 0) { + dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret); + usb_free_urb(urb); + return ret; + } + + pipe = usb_rcvbulkpipe(dev->udev, dev->in_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, + diag_bridge_read_cb, dev); + usb_anchor_urb(urb, &dev->submitted); + dev->pending_reads++; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret); + dev->pending_reads--; + usb_unanchor_urb(urb); + usb_free_urb(urb); + usb_autopm_put_interface(dev->ifc); + return ret; + } + + usb_autopm_put_interface(dev->ifc); + usb_free_urb(urb); + + return 0; +} +EXPORT_SYMBOL(diag_bridge_read); + +static void diag_bridge_write_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + usb_autopm_put_interface_async(dev->ifc); + + if (urb->status == -EPROTO) { + dev_err(&dev->udev->dev, "%s: proto error\n", __func__); + /* save error so that subsequent read/write returns ESHUTDOWN */ + dev->err = urb->status; + return; + } + + if (cbs && cbs->write_complete_cb) + cbs->write_complete_cb(cbs->ctxt, + urb->transfer_buffer, + urb->transfer_buffer_length, + urb->status < 0 ? urb->status : urb->actual_length); + + dev->bytes_to_mdm += urb->actual_length; + dev->pending_writes--; +} + +int diag_bridge_write(char *data, int size) +{ + struct urb *urb = NULL; + unsigned int pipe; + struct diag_bridge *dev = __dev; + int ret; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + if (!size) { + dev_err(&dev->udev->dev, "invalid size:%d\n", size); + return -EINVAL; + } + + if (!dev->ifc) { + dev_err(&dev->udev->dev, "device is disconnected\n"); + return -ENODEV; + } + + /* if there was a previous unrecoverable error, just quit */ + if (dev->err) + return -ESHUTDOWN; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + err("unable to allocate urb"); + return -ENOMEM; + } + + ret = usb_autopm_get_interface(dev->ifc); + if (ret < 0) { + dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret); + usb_free_urb(urb); + return ret; + } + + pipe = usb_sndbulkpipe(dev->udev, dev->out_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, + diag_bridge_write_cb, dev); + usb_anchor_urb(urb, &dev->submitted); + dev->pending_writes++; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret); + dev->pending_writes--; + usb_unanchor_urb(urb); + usb_free_urb(urb); + usb_autopm_put_interface(dev->ifc); + return ret; + } + + usb_free_urb(urb); + + return 0; +} +EXPORT_SYMBOL(diag_bridge_write); + +static void diag_bridge_delete(struct kref *kref) +{ + struct diag_bridge *dev = + container_of(kref, struct diag_bridge, kref); + + usb_put_dev(dev->udev); + __dev = 0; + kfree(dev); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 512 +static ssize_t diag_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct diag_bridge *dev = __dev; + char *buf; + int ret; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, DEBUG_BUF_SIZE, + "epin:%d, epout:%d\n" + "bytes to host: %lu\n" + "bytes to mdm: %lu\n" + "pending reads: %u\n" + "pending writes: %u\n" + "last error: %d\n", + dev->in_epAddr, dev->out_epAddr, + dev->bytes_to_host, dev->bytes_to_mdm, + dev->pending_reads, dev->pending_writes, + dev->err); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t diag_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct diag_bridge *dev = __dev; + + dev->bytes_to_host = dev->bytes_to_mdm = 0; + dev->pending_reads = dev->pending_writes = 0; + + return count; +} + +const struct file_operations diag_stats_ops = { + .read = diag_read_stats, + .write = diag_reset_stats, +}; + +static struct dentry *dent; + +static void diag_bridge_debugfs_init(void) +{ + struct dentry *dfile; + + dent = debugfs_create_dir("diag_bridge", 0); + if (IS_ERR(dent)) + return; + + dfile = debugfs_create_file("status", 0444, dent, 0, &diag_stats_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} + +static void diag_bridge_debugfs_cleanup(void) +{ + if (dent) { + debugfs_remove_recursive(dent); + dent = NULL; + } +} +#else +static inline void diag_bridge_debugfs_init(void) { } +static inline void diag_bridge_debugfs_cleanup(void) { } +#endif + +static int +diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) +{ + struct diag_bridge *dev; + struct usb_host_interface *ifc_desc; + struct usb_endpoint_descriptor *ep_desc; + int i; + int ret = -ENOMEM; + __u8 ifc_num; + + dbg("%s: id:%lu", __func__, id->driver_info); + + ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber; + + /* is this interface supported ? */ + if (ifc_num != id->driver_info) + return -ENODEV; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + pr_err("%s: unable to allocate dev\n", __func__); + return -ENOMEM; + } + dev->pdev = platform_device_alloc("diag_bridge", -1); + if (!dev->pdev) { + pr_err("%s: unable to allocate platform device\n", __func__); + kfree(dev); + return -ENOMEM; + } + __dev = dev; + + dev->udev = usb_get_dev(interface_to_usbdev(ifc)); + dev->ifc = ifc; + kref_init(&dev->kref); + init_usb_anchor(&dev->submitted); + + ifc_desc = ifc->cur_altsetting; + for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) { + ep_desc = &ifc_desc->endpoint[i].desc; + + if (!dev->in_epAddr && usb_endpoint_is_bulk_in(ep_desc)) + dev->in_epAddr = ep_desc->bEndpointAddress; + + if (!dev->out_epAddr && usb_endpoint_is_bulk_out(ep_desc)) + dev->out_epAddr = ep_desc->bEndpointAddress; + } + + if (!(dev->in_epAddr && dev->out_epAddr)) { + err("could not find bulk in and bulk out endpoints"); + ret = -ENODEV; + goto error; + } + + usb_set_intfdata(ifc, dev); + diag_bridge_debugfs_init(); + platform_device_add(dev->pdev); + + dev_dbg(&dev->udev->dev, "%s: complete\n", __func__); + + return 0; + +error: + if (dev) + kref_put(&dev->kref, diag_bridge_delete); + + return ret; +} + +static void diag_bridge_disconnect(struct usb_interface *ifc) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + platform_device_del(dev->pdev); + diag_bridge_debugfs_cleanup(); + kref_put(&dev->kref, diag_bridge_delete); + usb_set_intfdata(ifc, NULL); +} + +static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + struct diag_bridge_ops *cbs = dev->ops; + int ret = 0; + + if (cbs && cbs->suspend) { + ret = cbs->suspend(cbs->ctxt); + if (ret) { + dev_dbg(&dev->udev->dev, + "%s: diag veto'd suspend\n", __func__); + return ret; + } + + usb_kill_anchored_urbs(&dev->submitted); + } + + return ret; +} + +static int diag_bridge_resume(struct usb_interface *ifc) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + struct diag_bridge_ops *cbs = dev->ops; + + + if (cbs && cbs->resume) + cbs->resume(cbs->ctxt); + + return 0; +} + +#define VALID_INTERFACE_NUM 0 +static const struct usb_device_id diag_bridge_ids[] = { + { USB_DEVICE(0x5c6, 0x9001), + .driver_info = VALID_INTERFACE_NUM, }, + { USB_DEVICE(0x5c6, 0x9034), + .driver_info = VALID_INTERFACE_NUM, }, + { USB_DEVICE(0x5c6, 0x9048), + .driver_info = VALID_INTERFACE_NUM, }, + { USB_DEVICE(0x5c6, 0x904C), + .driver_info = VALID_INTERFACE_NUM, }, + + {} /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, diag_bridge_ids); + +static struct usb_driver diag_bridge_driver = { + .name = "diag_bridge", + .probe = diag_bridge_probe, + .disconnect = diag_bridge_disconnect, + .suspend = diag_bridge_suspend, + .resume = diag_bridge_resume, + .id_table = diag_bridge_ids, + .supports_autosuspend = 1, +}; + +static int __init diag_bridge_init(void) +{ + int ret; + + ret = usb_register(&diag_bridge_driver); + if (ret) { + err("%s: unable to register diag driver", __func__); + return ret; + } + + return 0; +} + +static void __exit diag_bridge_exit(void) +{ + usb_deregister(&diag_bridge_driver); +} + +module_init(diag_bridge_init); +module_exit(diag_bridge_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/diag_bridge_test.c b/drivers/usb/misc/diag_bridge_test.c new file mode 100644 index 0000000000000000000000000000000000000000..5bc0828b4f421df312a6255cfe7f2e22558e9bac --- /dev/null +++ b/drivers/usb/misc/diag_bridge_test.c @@ -0,0 +1,207 @@ +/* + * 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 + +#define DRIVER_DESC "USB host diag bridge driver test" +#define DRIVER_VERSION "1.0" + +#define RD_BUF_SIZE 2048 +#define DIAG_TEST_CONNECTED 0 + +struct diag_test_dev { + char *read_buf; + struct work_struct read_w; + unsigned long flags; + + struct diag_bridge_ops ops; +}; +static struct diag_test_dev *__dev; +static struct dentry *dent; + +static void +diag_test_read_complete_cb(void *d, char *buf, size_t size, size_t actual) +{ + if (actual < 0) { + pr_err("%s: read complete err\n", __func__); + return; + } + + print_hex_dump(KERN_INFO, "to_host:", 0, 1, 1, buf, actual, false); +} +static void diag_test_read_work(struct work_struct *w) +{ + struct diag_test_dev *dev = + container_of(w, struct diag_test_dev, read_w); + + memset(dev->read_buf, 0, RD_BUF_SIZE); + diag_bridge_read(dev->read_buf, RD_BUF_SIZE); +} + +static void +diag_test_write_complete_cb(void *d, char *buf, size_t size, size_t actual) +{ + struct diag_test_dev *dev = d; + + if (actual > 0) + schedule_work(&dev->read_w); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t send_ping_cmd(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct diag_test_dev *dev = __dev; + unsigned char *buf; + int temp = sizeof(unsigned char) * 4; + + if (!dev) + return -ENODEV; + + buf = kmalloc(temp, GFP_KERNEL); + if (!buf) { + pr_err("%s: unable to allocate mem for ping cmd\n", + __func__); + return -ENOMEM; + } + + /* hdlc encoded ping command */ + buf[0] = 0x0C; + buf[1] = 0x14; + buf[2] = 0x3A; + buf[3] = 0x7E; + + diag_bridge_write(buf, temp); + + return count; +} + +const struct file_operations diag_test_ping_ops = { + .write = send_ping_cmd, +}; + +static void diag_test_debug_init(void) +{ + struct dentry *dfile; + + dent = debugfs_create_dir("diag_test", 0); + if (IS_ERR(dent)) + return; + + dfile = debugfs_create_file("send_ping", 0444, dent, + 0, &diag_test_ping_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} +#else +static void diag_test_debug_init(void) { } +#endif + +static int diag_test_remove(struct platform_device *pdev) +{ + diag_bridge_close(); + + if (dent) { + debugfs_remove_recursive(dent); + dent = NULL; + } + + return 0; +} + +static int diag_test_probe(struct platform_device *pdev) +{ + struct diag_test_dev *dev = __dev; + int ret = 0; + + pr_info("%s:\n", __func__); + + ret = diag_bridge_open(&dev->ops); + if (ret) + pr_err("diag open failed: %d", ret); + + + diag_test_debug_init(); + + return ret; +} + +static struct platform_driver diag_test = { + .remove = diag_test_remove, + .probe = diag_test_probe, + .driver = { + .name = "diag_bridge", + .owner = THIS_MODULE, + }, +}; + +static int __init diag_test_init(void) +{ + struct diag_test_dev *dev; + int ret = 0; + + pr_info("%s\n", __func__); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + __dev = dev; + + dev->ops.read_complete_cb = diag_test_read_complete_cb; + dev->ops.write_complete_cb = diag_test_write_complete_cb; + dev->read_buf = kmalloc(RD_BUF_SIZE, GFP_KERNEL); + if (!dev->read_buf) { + pr_err("%s: unable to allocate read buffer\n", __func__); + kfree(dev); + return -ENOMEM; + } + + dev->ops.ctxt = dev; + INIT_WORK(&dev->read_w, diag_test_read_work); + + ret = platform_driver_register(&diag_test); + if (ret) + pr_err("%s: platform driver %s register failed %d\n", + __func__, diag_test.driver.name, ret); + + return ret; +} + +static void __exit diag_test_exit(void) +{ + struct diag_test_dev *dev = __dev; + + pr_info("%s:\n", __func__); + + if (test_bit(DIAG_TEST_CONNECTED, &dev->flags)) + diag_bridge_close(); + + kfree(dev->read_buf); + kfree(dev); + +} + +module_init(diag_test_init); +module_exit(diag_test_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c new file mode 100644 index 0000000000000000000000000000000000000000..30879e01b8d49851500304821614678c5d73d4a2 --- /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/misc/mdm_ctrl_bridge.c b/drivers/usb/misc/mdm_ctrl_bridge.c new file mode 100644 index 0000000000000000000000000000000000000000..49591cdb6874b2ec71937b678adbf2dfe680dd50 --- /dev/null +++ b/drivers/usb/misc/mdm_ctrl_bridge.c @@ -0,0 +1,757 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +static const char *ctrl_bridge_names[] = { + "dun_ctrl_hsic0", + "rmnet_ctrl_hsic0" +}; + +/* polling interval for Interrupt ep */ +#define HS_INTERVAL 7 +#define FS_LS_INTERVAL 3 + +#define ACM_CTRL_DTR (1 << 0) +#define DEFAULT_READ_URB_LENGTH 4096 + +#define SUSPENDED BIT(0) + +struct ctrl_bridge { + struct usb_device *udev; + struct usb_interface *intf; + + unsigned int int_pipe; + struct urb *inturb; + void *intbuf; + + struct urb *readurb; + void *readbuf; + + struct usb_anchor tx_submitted; + struct usb_anchor tx_deferred; + struct usb_ctrlrequest *in_ctlreq; + + struct bridge *brdg; + struct platform_device *pdev; + + unsigned long flags; + + /* input control lines (DSR, CTS, CD, RI) */ + unsigned int cbits_tohost; + + /* output control lines (DTR, RTS) */ + unsigned int cbits_tomdm; + + /* counters */ + unsigned int snd_encap_cmd; + unsigned int get_encap_res; + unsigned int resp_avail; + unsigned int set_ctrl_line_sts; + unsigned int notify_ser_state; +}; + +static struct ctrl_bridge *__dev[MAX_BRIDGE_DEVICES]; + +/* counter used for indexing ctrl bridge devices */ +static int ch_id; + +unsigned int ctrl_bridge_get_cbits_tohost(unsigned int id) +{ + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + return dev->cbits_tohost; +} +EXPORT_SYMBOL(ctrl_bridge_get_cbits_tohost); + +int ctrl_bridge_set_cbits(unsigned int id, unsigned int cbits) +{ + struct ctrl_bridge *dev; + struct bridge *brdg; + int retval; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + pr_debug("%s: dev[id] =%u cbits : %u\n", __func__, id, cbits); + + brdg = dev->brdg; + if (!brdg) + return -ENODEV; + + dev->cbits_tomdm = cbits; + + retval = ctrl_bridge_write(id, NULL, 0); + + /* if DTR is high, update latest modem info to host */ + if (brdg && (cbits & ACM_CTRL_DTR) && brdg->ops.send_cbits) + brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost); + + return retval; +} +EXPORT_SYMBOL(ctrl_bridge_set_cbits); + +static void resp_avail_cb(struct urb *urb) +{ + struct ctrl_bridge *dev = urb->context; + struct usb_device *udev; + int status = 0; + int resubmit_urb = 1; + struct bridge *brdg = dev->brdg; + + udev = interface_to_usbdev(dev->intf); + switch (urb->status) { + case 0: + /*success*/ + dev->get_encap_res++; + if (brdg && brdg->ops.send_pkt) + brdg->ops.send_pkt(brdg->ctx, urb->transfer_buffer, + urb->actual_length); + break; + + /*do not resubmit*/ + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + /* unplug */ + case -EPROTO: + /*babble error*/ + resubmit_urb = 0; + /*resubmit*/ + case -EOVERFLOW: + default: + dev_dbg(&udev->dev, "%s: non zero urb status = %d\n", + __func__, urb->status); + } + + if (resubmit_urb) { + /*re- submit int urb to check response available*/ + usb_anchor_urb(dev->inturb, &dev->tx_submitted); + status = usb_submit_urb(dev->inturb, GFP_ATOMIC); + if (status) { + dev_err(&udev->dev, + "%s: Error re-submitting Int URB %d\n", + __func__, status); + usb_unanchor_urb(dev->inturb); + } + } +} + +static void notification_available_cb(struct urb *urb) +{ + int status; + struct usb_cdc_notification *ctrl; + struct usb_device *udev; + struct ctrl_bridge *dev = urb->context; + struct bridge *brdg = dev->brdg; + unsigned int ctrl_bits; + unsigned char *data; + + udev = interface_to_usbdev(dev->intf); + + switch (urb->status) { + case 0: + /*success*/ + break; + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + case -EPROTO: + /* unplug */ + return; + case -EPIPE: + dev_err(&udev->dev, "%s: stall on int endpoint\n", __func__); + /* TBD : halt to be cleared in work */ + case -EOVERFLOW: + default: + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + goto resubmit_int_urb; + } + + ctrl = (struct usb_cdc_notification *)urb->transfer_buffer; + data = (unsigned char *)(ctrl + 1); + + switch (ctrl->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: + dev->resp_avail++; + usb_fill_control_urb(dev->readurb, udev, + usb_rcvctrlpipe(udev, 0), + (unsigned char *)dev->in_ctlreq, + dev->readbuf, + DEFAULT_READ_URB_LENGTH, + resp_avail_cb, dev); + + usb_anchor_urb(dev->readurb, &dev->tx_submitted); + status = usb_submit_urb(dev->readurb, GFP_ATOMIC); + if (status) { + dev_err(&udev->dev, + "%s: Error submitting Read URB %d\n", + __func__, status); + usb_unanchor_urb(dev->readurb); + goto resubmit_int_urb; + } + return; + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&udev->dev, "%s network\n", ctrl->wValue ? + "connected to" : "disconnected from"); + break; + case USB_CDC_NOTIFY_SERIAL_STATE: + dev->notify_ser_state++; + ctrl_bits = get_unaligned_le16(data); + dev_dbg(&udev->dev, "serial state: %d\n", ctrl_bits); + dev->cbits_tohost = ctrl_bits; + if (brdg && brdg->ops.send_cbits) + brdg->ops.send_cbits(brdg->ctx, ctrl_bits); + break; + default: + dev_err(&udev->dev, "%s: unknown notification %d received:" + "index %d len %d data0 %d data1 %d", + __func__, ctrl->bNotificationType, ctrl->wIndex, + ctrl->wLength, data[0], data[1]); + } + +resubmit_int_urb: + usb_anchor_urb(urb, &dev->tx_submitted); + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n", + __func__, status); + usb_unanchor_urb(urb); + } +} + +static int ctrl_bridge_start_read(struct ctrl_bridge *dev) +{ + int retval = 0; + + if (!dev->inturb) { + dev_err(&dev->udev->dev, "%s: inturb is NULL\n", __func__); + return -ENODEV; + } + + if (!dev->inturb->anchor) { + usb_anchor_urb(dev->inturb, &dev->tx_submitted); + retval = usb_submit_urb(dev->inturb, GFP_KERNEL); + if (retval < 0) { + dev_err(&dev->udev->dev, + "%s error submitting int urb %d\n", + __func__, retval); + usb_unanchor_urb(dev->inturb); + } + } + + return retval; +} + +int ctrl_bridge_open(struct bridge *brdg) +{ + struct ctrl_bridge *dev; + + if (!brdg) { + err("bridge is null\n"); + return -EINVAL; + } + + if (brdg->ch_id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[brdg->ch_id]; + if (!dev) { + err("dev is null\n"); + return -ENODEV; + } + + dev->brdg = brdg; + dev->snd_encap_cmd = 0; + dev->get_encap_res = 0; + dev->resp_avail = 0; + dev->set_ctrl_line_sts = 0; + dev->notify_ser_state = 0; + + if (brdg->ops.send_cbits) + brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost); + + return 0; +} +EXPORT_SYMBOL(ctrl_bridge_open); + +void ctrl_bridge_close(unsigned int id) +{ + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return; + + dev = __dev[id]; + if (!dev || !dev->brdg) + return; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + ctrl_bridge_set_cbits(dev->brdg->ch_id, 0); + usb_unlink_anchored_urbs(&dev->tx_submitted); + + dev->brdg = NULL; +} +EXPORT_SYMBOL(ctrl_bridge_close); + +static void ctrl_write_callback(struct urb *urb) +{ + struct ctrl_bridge *dev = urb->context; + + if (urb->status) { + pr_debug("Write status/size %d/%d\n", + urb->status, urb->actual_length); + } + + kfree(urb->transfer_buffer); + kfree(urb->setup_packet); + usb_free_urb(urb); + usb_autopm_put_interface_async(dev->intf); +} + +int ctrl_bridge_write(unsigned int id, char *data, size_t size) +{ + int result; + struct urb *writeurb; + struct usb_ctrlrequest *out_ctlreq; + struct usb_device *udev; + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) { + result = -EINVAL; + goto free_data; + } + + dev = __dev[id]; + + if (!dev) { + result = -ENODEV; + goto free_data; + } + + udev = interface_to_usbdev(dev->intf); + + dev_dbg(&udev->dev, "%s:[id]:%u: write (%d bytes)\n", + __func__, id, size); + + writeurb = usb_alloc_urb(0, GFP_ATOMIC); + if (!writeurb) { + dev_err(&udev->dev, "%s: error allocating read urb\n", + __func__); + result = -ENOMEM; + goto free_data; + } + + out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_ATOMIC); + if (!out_ctlreq) { + dev_err(&udev->dev, + "%s: error allocating setup packet buffer\n", + __func__); + result = -ENOMEM; + goto free_urb; + } + + /* CDC Send Encapsulated Request packet */ + out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE); + if (!data && !size) { + out_ctlreq->bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE; + out_ctlreq->wValue = dev->cbits_tomdm; + dev->set_ctrl_line_sts++; + } else { + out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; + out_ctlreq->wValue = 0; + dev->snd_encap_cmd++; + } + out_ctlreq->wIndex = + dev->intf->cur_altsetting->desc.bInterfaceNumber; + out_ctlreq->wLength = cpu_to_le16(size); + + usb_fill_control_urb(writeurb, udev, + usb_sndctrlpipe(udev, 0), + (unsigned char *)out_ctlreq, + (void *)data, size, + ctrl_write_callback, dev); + + result = usb_autopm_get_interface_async(dev->intf); + if (result < 0) { + dev_err(&udev->dev, "%s: unable to resume interface: %d\n", + __func__, result); + + /* + * Revisit: if (result == -EPERM) + * bridge_suspend(dev->intf, PMSG_SUSPEND); + */ + + goto free_ctrlreq; + } + + if (test_bit(SUSPENDED, &dev->flags)) { + usb_anchor_urb(writeurb, &dev->tx_deferred); + goto deferred; + } + + usb_anchor_urb(writeurb, &dev->tx_submitted); + result = usb_submit_urb(writeurb, GFP_ATOMIC); + if (result < 0) { + dev_err(&udev->dev, "%s: submit URB error %d\n", + __func__, result); + usb_autopm_put_interface_async(dev->intf); + goto unanchor_urb; + } +deferred: + return size; + +unanchor_urb: + usb_unanchor_urb(writeurb); +free_ctrlreq: + kfree(out_ctlreq); +free_urb: + usb_free_urb(writeurb); +free_data: + kfree(data); + + return result; +} +EXPORT_SYMBOL(ctrl_bridge_write); + +int ctrl_bridge_suspend(unsigned int id) +{ + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + set_bit(SUSPENDED, &dev->flags); + usb_kill_anchored_urbs(&dev->tx_submitted); + + return 0; +} + +int ctrl_bridge_resume(unsigned int id) +{ + struct ctrl_bridge *dev; + struct urb *urb; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + if (!test_and_clear_bit(SUSPENDED, &dev->flags)) + return 0; + + /* submit pending write requests */ + while ((urb = usb_get_from_anchor(&dev->tx_deferred))) { + int ret; + usb_anchor_urb(urb, &dev->tx_submitted); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + usb_unanchor_urb(urb); + kfree(urb->setup_packet); + kfree(urb->transfer_buffer); + usb_free_urb(urb); + usb_autopm_put_interface_async(dev->intf); + } + } + + return ctrl_bridge_start_read(dev); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t ctrl_bridge_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ctrl_bridge *dev; + char *buf; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName#%s dev %p\n" + "snd encap cmd cnt: %u\n" + "get encap res cnt: %u\n" + "res available cnt: %u\n" + "set ctrlline sts cnt: %u\n" + "notify ser state cnt: %u\n" + "cbits_tomdm: %d\n" + "cbits_tohost: %d\n" + "suspended: %d\n", + dev->pdev->name, dev, + dev->snd_encap_cmd, + dev->get_encap_res, + dev->resp_avail, + dev->set_ctrl_line_sts, + dev->notify_ser_state, + dev->cbits_tomdm, + dev->cbits_tohost, + test_bit(SUSPENDED, &dev->flags)); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t ctrl_bridge_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct ctrl_bridge *dev; + int i; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + dev->snd_encap_cmd = 0; + dev->get_encap_res = 0; + dev->resp_avail = 0; + dev->set_ctrl_line_sts = 0; + dev->notify_ser_state = 0; + } + return count; +} + +const struct file_operations ctrl_stats_ops = { + .read = ctrl_bridge_read_stats, + .write = ctrl_bridge_reset_stats, +}; + +struct dentry *ctrl_dent; +struct dentry *ctrl_dfile; +static void ctrl_bridge_debugfs_init(void) +{ + ctrl_dent = debugfs_create_dir("ctrl_hsic_bridge", 0); + if (IS_ERR(ctrl_dent)) + return; + + ctrl_dfile = + debugfs_create_file("status", 0644, ctrl_dent, 0, + &ctrl_stats_ops); + if (!ctrl_dfile || IS_ERR(ctrl_dfile)) + debugfs_remove(ctrl_dent); +} + +static void ctrl_bridge_debugfs_exit(void) +{ + debugfs_remove(ctrl_dfile); + debugfs_remove(ctrl_dent); +} + +#else +static void ctrl_bridge_debugfs_init(void) { } +static void ctrl_bridge_debugfs_exit(void) { } +#endif + +int +ctrl_bridge_probe(struct usb_interface *ifc, struct usb_host_endpoint *int_in, + int id) +{ + struct ctrl_bridge *dev; + struct usb_device *udev; + struct usb_endpoint_descriptor *ep; + u16 wMaxPacketSize; + int retval = 0; + int interval; + + udev = interface_to_usbdev(ifc); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(&udev->dev, "%s: unable to allocate dev\n", + __func__); + return -ENOMEM; + } + dev->pdev = platform_device_alloc(ctrl_bridge_names[id], id); + if (!dev->pdev) { + dev_err(&dev->udev->dev, + "%s: unable to allocate platform device\n", __func__); + retval = -ENOMEM; + goto nomem; + } + + dev->udev = udev; + dev->int_pipe = usb_rcvintpipe(udev, + int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->intf = ifc; + + init_usb_anchor(&dev->tx_submitted); + init_usb_anchor(&dev->tx_deferred); + + /*use max pkt size from ep desc*/ + ep = &dev->intf->cur_altsetting->endpoint[0].desc; + + dev->inturb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->inturb) { + dev_err(&udev->dev, "%s: error allocating int urb\n", __func__); + retval = -ENOMEM; + goto pdev_del; + } + + wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize); + + dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL); + if (!dev->intbuf) { + dev_err(&udev->dev, "%s: error allocating int buffer\n", + __func__); + retval = -ENOMEM; + goto free_inturb; + } + + interval = + (udev->speed == USB_SPEED_HIGH) ? HS_INTERVAL : FS_LS_INTERVAL; + + usb_fill_int_urb(dev->inturb, udev, dev->int_pipe, + dev->intbuf, wMaxPacketSize, + notification_available_cb, dev, interval); + + dev->readurb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->readurb) { + dev_err(&udev->dev, "%s: error allocating read urb\n", + __func__); + retval = -ENOMEM; + goto free_intbuf; + } + + dev->readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL); + if (!dev->readbuf) { + dev_err(&udev->dev, "%s: error allocating read buffer\n", + __func__); + retval = -ENOMEM; + goto free_rurb; + } + + dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL); + if (!dev->in_ctlreq) { + dev_err(&udev->dev, + "%s:error allocating setup packet buffer\n", + __func__); + retval = -ENOMEM; + goto free_rbuf; + } + + dev->in_ctlreq->bRequestType = + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); + dev->in_ctlreq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + dev->in_ctlreq->wValue = 0; + dev->in_ctlreq->wIndex = + dev->intf->cur_altsetting->desc.bInterfaceNumber; + dev->in_ctlreq->wLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH); + + __dev[id] = dev; + + platform_device_add(dev->pdev); + + ch_id++; + + return ctrl_bridge_start_read(dev); + +free_rbuf: + kfree(dev->readbuf); +free_rurb: + usb_free_urb(dev->readurb); +free_intbuf: + kfree(dev->intbuf); +free_inturb: + usb_free_urb(dev->inturb); +pdev_del: + platform_device_del(dev->pdev); +nomem: + kfree(dev); + + return retval; +} + +void ctrl_bridge_disconnect(unsigned int id) +{ + struct ctrl_bridge *dev = __dev[id]; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + platform_device_del(dev->pdev); + + kfree(dev->in_ctlreq); + kfree(dev->readbuf); + kfree(dev->intbuf); + + usb_free_urb(dev->readurb); + usb_free_urb(dev->inturb); + + __dev[id] = NULL; + ch_id--; + + kfree(dev); +} + +static int __init ctrl_bridge_init(void) +{ + ctrl_bridge_debugfs_init(); + + return 0; +} +module_init(ctrl_bridge_init); + +static void __exit ctrl_bridge_exit(void) +{ + ctrl_bridge_debugfs_exit(); +} +module_exit(ctrl_bridge_exit); + +MODULE_DESCRIPTION("Qualcomm modem control bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/mdm_data_bridge.c b/drivers/usb/misc/mdm_data_bridge.c new file mode 100644 index 0000000000000000000000000000000000000000..bf8e5f401efeabddd1df6c479d8b5deeb0a73c70 --- /dev/null +++ b/drivers/usb/misc/mdm_data_bridge.c @@ -0,0 +1,1080 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 MAX_RX_URBS 50 +#define RMNET_RX_BUFSIZE 2048 + +#define STOP_SUBMIT_URB_LIMIT 500 +#define FLOW_CTRL_EN_THRESHOLD 500 +#define FLOW_CTRL_DISABLE 300 +#define FLOW_CTRL_SUPPORT 1 + +static const char *data_bridge_names[] = { + "dun_data_hsic0", + "rmnet_data_hsic0" +}; + +static struct workqueue_struct *bridge_wq; + +static unsigned int fctrl_support = FLOW_CTRL_SUPPORT; +module_param(fctrl_support, uint, S_IRUGO | S_IWUSR); + +static unsigned int fctrl_en_thld = FLOW_CTRL_EN_THRESHOLD; +module_param(fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int fctrl_dis_thld = FLOW_CTRL_DISABLE; +module_param(fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int max_rx_urbs = MAX_RX_URBS; +module_param(max_rx_urbs, uint, S_IRUGO | S_IWUSR); + +unsigned int stop_submit_urb_limit = STOP_SUBMIT_URB_LIMIT; +module_param(stop_submit_urb_limit, uint, S_IRUGO | S_IWUSR); + +static unsigned tx_urb_mult = 20; +module_param(tx_urb_mult, uint, S_IRUGO|S_IWUSR); + +#define TX_HALT BIT(0) +#define RX_HALT BIT(1) +#define SUSPENDED BIT(2) + +struct data_bridge { + struct usb_interface *intf; + struct usb_device *udev; + int id; + + unsigned int bulk_in; + unsigned int bulk_out; + int err; + + /* keep track of in-flight URBs */ + struct usb_anchor tx_active; + struct usb_anchor rx_active; + + /* keep track of outgoing URBs during suspend */ + struct usb_anchor delayed; + + struct list_head rx_idle; + struct sk_buff_head rx_done; + + struct workqueue_struct *wq; + struct work_struct process_rx_w; + + struct bridge *brdg; + + /* work queue function for handling halt conditions */ + struct work_struct kevent; + + unsigned long flags; + + struct platform_device *pdev; + + /* counters */ + atomic_t pending_txurbs; + unsigned int txurb_drp_cnt; + unsigned long to_host; + unsigned long to_modem; + unsigned int tx_throttled_cnt; + unsigned int tx_unthrottled_cnt; + unsigned int rx_throttled_cnt; + unsigned int rx_unthrottled_cnt; +}; + +static struct data_bridge *__dev[MAX_BRIDGE_DEVICES]; + +/* counter used for indexing data bridge devices */ +static int ch_id; + +static unsigned int get_timestamp(void); +static void dbg_timestamp(char *, struct sk_buff *); +static int submit_rx_urb(struct data_bridge *dev, struct urb *urb, + gfp_t flags); + +static inline bool rx_halted(struct data_bridge *dev) +{ + return test_bit(RX_HALT, &dev->flags); +} + +static inline bool rx_throttled(struct bridge *brdg) +{ + return test_bit(RX_THROTTLED, &brdg->flags); +} + +int data_bridge_unthrottle_rx(unsigned int id) +{ + struct data_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev || !dev->brdg) + return -ENODEV; + + dev->rx_unthrottled_cnt++; + queue_work(dev->wq, &dev->process_rx_w); + + return 0; +} +EXPORT_SYMBOL(data_bridge_unthrottle_rx); + +static void data_bridge_process_rx(struct work_struct *work) +{ + int retval; + unsigned long flags; + struct urb *rx_idle; + struct sk_buff *skb; + struct timestamp_info *info; + struct data_bridge *dev = + container_of(work, struct data_bridge, process_rx_w); + + struct bridge *brdg = dev->brdg; + + if (!brdg || !brdg->ops.send_pkt || rx_halted(dev)) + return; + + while (!rx_throttled(brdg) && (skb = skb_dequeue(&dev->rx_done))) { + dev->to_host++; + info = (struct timestamp_info *)skb->cb; + info->rx_done_sent = get_timestamp(); + /* hand off sk_buff to client,they'll need to free it */ + retval = brdg->ops.send_pkt(brdg->ctx, skb, skb->len); + if (retval == -ENOTCONN || retval == -EINVAL) { + return; + } else if (retval == -EBUSY) { + dev->rx_throttled_cnt++; + break; + } + } + + spin_lock_irqsave(&dev->rx_done.lock, flags); + while (!list_empty(&dev->rx_idle)) { + if (dev->rx_done.qlen > stop_submit_urb_limit) + break; + + rx_idle = list_first_entry(&dev->rx_idle, struct urb, urb_list); + list_del(&rx_idle->urb_list); + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + retval = submit_rx_urb(dev, rx_idle, GFP_KERNEL); + spin_lock_irqsave(&dev->rx_done.lock, flags); + if (retval) { + list_add_tail(&rx_idle->urb_list, &dev->rx_idle); + break; + } + } + spin_unlock_irqrestore(&dev->rx_done.lock, flags); +} + +static void data_bridge_read_cb(struct urb *urb) +{ + struct bridge *brdg; + struct sk_buff *skb = urb->context; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + struct data_bridge *dev = info->dev; + bool queue = 0; + + brdg = dev->brdg; + skb_put(skb, urb->actual_length); + + switch (urb->status) { + case 0: /* success */ + queue = 1; + info->rx_done = get_timestamp(); + spin_lock(&dev->rx_done.lock); + __skb_queue_tail(&dev->rx_done, skb); + spin_unlock(&dev->rx_done.lock); + break; + + /*do not resubmit*/ + case -EPIPE: + set_bit(RX_HALT, &dev->flags); + dev_err(&dev->udev->dev, "%s: epout halted\n", __func__); + schedule_work(&dev->kevent); + /* FALLTHROUGH */ + case -ESHUTDOWN: + case -ENOENT: /* suspended */ + case -ECONNRESET: /* unplug */ + case -EPROTO: + dev_kfree_skb_any(skb); + break; + + /*resubmit */ + case -EOVERFLOW: /*babble error*/ + default: + queue = 1; + dev_kfree_skb_any(skb); + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + break; + } + + spin_lock(&dev->rx_done.lock); + list_add_tail(&urb->urb_list, &dev->rx_idle); + spin_unlock(&dev->rx_done.lock); + + if (queue) + queue_work(dev->wq, &dev->process_rx_w); +} + +static int submit_rx_urb(struct data_bridge *dev, struct urb *rx_urb, + gfp_t flags) +{ + struct sk_buff *skb; + struct timestamp_info *info; + int retval = -EINVAL; + unsigned int created; + + created = get_timestamp(); + skb = alloc_skb(RMNET_RX_BUFSIZE, flags); + if (!skb) + return -ENOMEM; + + info = (struct timestamp_info *)skb->cb; + info->dev = dev; + info->created = created; + + usb_fill_bulk_urb(rx_urb, dev->udev, dev->bulk_in, + skb->data, RMNET_RX_BUFSIZE, + data_bridge_read_cb, skb); + + if (test_bit(SUSPENDED, &dev->flags)) + goto suspended; + + usb_anchor_urb(rx_urb, &dev->rx_active); + info->rx_queued = get_timestamp(); + retval = usb_submit_urb(rx_urb, flags); + if (retval) + goto fail; + + usb_mark_last_busy(dev->udev); + return 0; +fail: + usb_unanchor_urb(rx_urb); +suspended: + dev_kfree_skb_any(skb); + + return retval; +} + +static int data_bridge_prepare_rx(struct data_bridge *dev) +{ + int i; + struct urb *rx_urb; + + for (i = 0; i < max_rx_urbs; i++) { + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) + return -ENOMEM; + + list_add_tail(&rx_urb->urb_list, &dev->rx_idle); + } + return 0; +} + +int data_bridge_open(struct bridge *brdg) +{ + struct data_bridge *dev; + + if (!brdg) { + err("bridge is null\n"); + return -EINVAL; + } + + if (brdg->ch_id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[brdg->ch_id]; + if (!dev) { + err("dev is null\n"); + return -ENODEV; + } + + dev_dbg(&dev->udev->dev, "%s: dev:%p\n", __func__, dev); + + dev->brdg = brdg; + dev->err = 0; + atomic_set(&dev->pending_txurbs, 0); + dev->to_host = 0; + dev->to_modem = 0; + dev->txurb_drp_cnt = 0; + dev->tx_throttled_cnt = 0; + dev->tx_unthrottled_cnt = 0; + dev->rx_throttled_cnt = 0; + dev->rx_unthrottled_cnt = 0; + + queue_work(dev->wq, &dev->process_rx_w); + + return 0; +} +EXPORT_SYMBOL(data_bridge_open); + +void data_bridge_close(unsigned int id) +{ + struct data_bridge *dev; + struct sk_buff *skb; + unsigned long flags; + + if (id >= MAX_BRIDGE_DEVICES) + return; + + dev = __dev[id]; + if (!dev || !dev->brdg) + return; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + usb_unlink_anchored_urbs(&dev->tx_active); + usb_unlink_anchored_urbs(&dev->rx_active); + usb_unlink_anchored_urbs(&dev->delayed); + + spin_lock_irqsave(&dev->rx_done.lock, flags); + while ((skb = __skb_dequeue(&dev->rx_done))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + + dev->brdg = NULL; +} +EXPORT_SYMBOL(data_bridge_close); + +static void defer_kevent(struct work_struct *work) +{ + int status; + struct data_bridge *dev = + container_of(work, struct data_bridge, kevent); + + if (!dev) + return; + + if (test_bit(TX_HALT, &dev->flags)) { + usb_unlink_anchored_urbs(&dev->tx_active); + + status = usb_autopm_get_interface(dev->intf); + if (status < 0) { + dev_err(&dev->udev->dev, + "can't acquire interface, status %d\n", status); + return; + } + + status = usb_clear_halt(dev->udev, dev->bulk_out); + usb_autopm_put_interface(dev->intf); + if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) + dev_err(&dev->udev->dev, + "can't clear tx halt, status %d\n", status); + else + clear_bit(TX_HALT, &dev->flags); + } + + if (test_bit(RX_HALT, &dev->flags)) { + usb_unlink_anchored_urbs(&dev->rx_active); + + status = usb_autopm_get_interface(dev->intf); + if (status < 0) { + dev_err(&dev->udev->dev, + "can't acquire interface, status %d\n", status); + return; + } + + status = usb_clear_halt(dev->udev, dev->bulk_in); + usb_autopm_put_interface(dev->intf); + if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) + dev_err(&dev->udev->dev, + "can't clear rx halt, status %d\n", status); + else { + clear_bit(RX_HALT, &dev->flags); + if (dev->brdg) + queue_work(dev->wq, &dev->process_rx_w); + } + } +} + +static void data_bridge_write_cb(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + struct data_bridge *dev = info->dev; + struct bridge *brdg = dev->brdg; + int pending; + + pr_debug("%s: dev:%p\n", __func__, dev); + + switch (urb->status) { + case 0: /*success*/ + dbg_timestamp("UL", skb); + break; + case -EPROTO: + dev->err = -EPROTO; + break; + case -EPIPE: + set_bit(TX_HALT, &dev->flags); + dev_err(&dev->udev->dev, "%s: epout halted\n", __func__); + schedule_work(&dev->kevent); + /* FALLTHROUGH */ + case -ESHUTDOWN: + case -ENOENT: /* suspended */ + case -ECONNRESET: /* unplug */ + case -EOVERFLOW: /*babble error*/ + /* FALLTHROUGH */ + default: + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + } + + usb_free_urb(urb); + dev_kfree_skb_any(skb); + + pending = atomic_dec_return(&dev->pending_txurbs); + + /*flow ctrl*/ + if (brdg && fctrl_support && pending <= fctrl_dis_thld && + test_and_clear_bit(TX_THROTTLED, &brdg->flags)) { + pr_debug_ratelimited("%s: disable flow ctrl: pend urbs:%u\n", + __func__, pending); + dev->tx_unthrottled_cnt++; + if (brdg->ops.unthrottle_tx) + brdg->ops.unthrottle_tx(brdg->ctx); + } + + usb_autopm_put_interface_async(dev->intf); +} + +int data_bridge_write(unsigned int id, struct sk_buff *skb) +{ + int result; + int size = skb->len; + int pending; + struct urb *txurb; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + struct data_bridge *dev = __dev[id]; + struct bridge *brdg; + + if (!dev || !dev->brdg || dev->err || !usb_get_intfdata(dev->intf)) + return -ENODEV; + + brdg = dev->brdg; + if (!brdg) + return -ENODEV; + + dev_dbg(&dev->udev->dev, "%s: write (%d bytes)\n", __func__, skb->len); + + result = usb_autopm_get_interface(dev->intf); + if (result < 0) { + dev_err(&dev->udev->dev, "%s: resume failure\n", __func__); + goto error; + } + + txurb = usb_alloc_urb(0, GFP_KERNEL); + if (!txurb) { + dev_err(&dev->udev->dev, "%s: error allocating read urb\n", + __func__); + result = -ENOMEM; + goto error; + } + + /* store dev pointer in skb */ + info->dev = dev; + info->tx_queued = get_timestamp(); + + usb_fill_bulk_urb(txurb, dev->udev, dev->bulk_out, + skb->data, skb->len, data_bridge_write_cb, skb); + + if (test_bit(SUSPENDED, &dev->flags)) { + usb_anchor_urb(txurb, &dev->delayed); + goto free_urb; + } + + pending = atomic_inc_return(&dev->pending_txurbs); + usb_anchor_urb(txurb, &dev->tx_active); + + if (atomic_read(&dev->pending_txurbs) % tx_urb_mult) + txurb->transfer_flags |= URB_NO_INTERRUPT; + + result = usb_submit_urb(txurb, GFP_KERNEL); + if (result < 0) { + usb_unanchor_urb(txurb); + atomic_dec(&dev->pending_txurbs); + dev_err(&dev->udev->dev, "%s: submit URB error %d\n", + __func__, result); + goto free_urb; + } + + dev->to_modem++; + dev_dbg(&dev->udev->dev, "%s: pending_txurbs: %u\n", __func__, pending); + + /* flow control: last urb submitted but return -EBUSY */ + if (fctrl_support && pending > fctrl_en_thld) { + set_bit(TX_THROTTLED, &brdg->flags); + dev->tx_throttled_cnt++; + pr_debug_ratelimited("%s: enable flow ctrl pend txurbs:%u\n", + __func__, pending); + return -EBUSY; + } + + return size; + +free_urb: + usb_free_urb(txurb); +error: + dev->txurb_drp_cnt++; + usb_autopm_put_interface(dev->intf); + + return result; +} +EXPORT_SYMBOL(data_bridge_write); + +static int data_bridge_resume(struct data_bridge *dev) +{ + struct urb *urb; + int retval; + + if (!test_and_clear_bit(SUSPENDED, &dev->flags)) + return 0; + + while ((urb = usb_get_from_anchor(&dev->delayed))) { + usb_anchor_urb(urb, &dev->tx_active); + atomic_inc(&dev->pending_txurbs); + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval < 0) { + atomic_dec(&dev->pending_txurbs); + usb_unanchor_urb(urb); + + /* TODO: need to free urb data */ + usb_scuttle_anchored_urbs(&dev->delayed); + break; + } + dev->to_modem++; + dev->txurb_drp_cnt--; + } + + if (dev->brdg) + queue_work(dev->wq, &dev->process_rx_w); + + return 0; +} + +static int bridge_resume(struct usb_interface *iface) +{ + int retval = 0; + int oldstate; + struct data_bridge *dev = usb_get_intfdata(iface); + + oldstate = iface->dev.power.power_state.event; + iface->dev.power.power_state.event = PM_EVENT_ON; + + if (oldstate & PM_EVENT_SUSPEND) { + retval = data_bridge_resume(dev); + if (!retval) + retval = ctrl_bridge_resume(dev->id); + } + + return retval; +} + +static int data_bridge_suspend(struct data_bridge *dev, pm_message_t message) +{ + if (atomic_read(&dev->pending_txurbs) && + (message.event & PM_EVENT_AUTO)) + return -EBUSY; + + set_bit(SUSPENDED, &dev->flags); + + usb_kill_anchored_urbs(&dev->tx_active); + usb_kill_anchored_urbs(&dev->rx_active); + + return 0; +} + +static int bridge_suspend(struct usb_interface *intf, pm_message_t message) +{ + int retval; + struct data_bridge *dev = usb_get_intfdata(intf); + + retval = data_bridge_suspend(dev, message); + if (!retval) { + retval = ctrl_bridge_suspend(dev->id); + intf->dev.power.power_state.event = message.event; + } + + return retval; +} + +static int data_bridge_probe(struct usb_interface *iface, + struct usb_host_endpoint *bulk_in, + struct usb_host_endpoint *bulk_out, int id) +{ + struct data_bridge *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + err("%s: unable to allocate dev\n", __func__); + return -ENOMEM; + } + + dev->pdev = platform_device_alloc(data_bridge_names[id], id); + if (!dev->pdev) { + err("%s: unable to allocate platform device\n", __func__); + kfree(dev); + return -ENOMEM; + } + + init_usb_anchor(&dev->tx_active); + init_usb_anchor(&dev->rx_active); + init_usb_anchor(&dev->delayed); + + INIT_LIST_HEAD(&dev->rx_idle); + skb_queue_head_init(&dev->rx_done); + + dev->wq = bridge_wq; + dev->id = id; + dev->udev = interface_to_usbdev(iface); + dev->intf = iface; + + dev->bulk_in = usb_rcvbulkpipe(dev->udev, + bulk_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + dev->bulk_out = usb_sndbulkpipe(dev->udev, + bulk_out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + usb_set_intfdata(iface, dev); + + INIT_WORK(&dev->kevent, defer_kevent); + INIT_WORK(&dev->process_rx_w, data_bridge_process_rx); + + __dev[id] = dev; + + /*allocate list of rx urbs*/ + data_bridge_prepare_rx(dev); + + platform_device_add(dev->pdev); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 + +static unsigned int record_timestamp; +module_param(record_timestamp, uint, S_IRUGO | S_IWUSR); + +static struct timestamp_buf dbg_data = { + .idx = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/*get_timestamp - returns time of day in us */ +static unsigned int get_timestamp(void) +{ + struct timeval tval; + unsigned int stamp; + + if (!record_timestamp) + return 0; + + do_gettimeofday(&tval); + /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = tval.tv_sec & 0xFFF; + stamp = stamp * 1000000 + tval.tv_usec; + return stamp; +} + +static void dbg_inc(unsigned *idx) +{ + *idx = (*idx + 1) & (DBG_DATA_MAX-1); +} + +/** +* dbg_timestamp - Stores timestamp values of a SKB life cycle +* to debug buffer +* @event: "UL": Uplink Data +* @skb: SKB used to store timestamp values to debug buffer +*/ +static void dbg_timestamp(char *event, struct sk_buff * skb) +{ + unsigned long flags; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + + if (!record_timestamp) + return; + + write_lock_irqsave(&dbg_data.lck, flags); + + scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, + "%p %u[%s] %u %u %u %u %u %u\n", + skb, skb->len, event, info->created, info->rx_queued, + info->rx_done, info->rx_done_sent, info->tx_queued, + get_timestamp()); + + dbg_inc(&dbg_data.idx); + + write_unlock_irqrestore(&dbg_data.lck, flags); +} + +/* show_timestamp: displays the timestamp buffer */ +static ssize_t show_timestamp(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + unsigned long flags; + unsigned i; + unsigned j = 0; + char *buf; + int ret = 0; + + if (!record_timestamp) + return 0; + + buf = kzalloc(sizeof(char) * 4 * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + read_lock_irqsave(&dbg_data.lck, flags); + + i = dbg_data.idx; + for (dbg_inc(&i); i != dbg_data.idx; dbg_inc(&i)) { + if (!strnlen(dbg_data.buf[i], DBG_DATA_MSG)) + continue; + j += scnprintf(buf + j, (4 * DEBUG_BUF_SIZE) - j, + "%s\n", dbg_data.buf[i]); + } + + read_unlock_irqrestore(&dbg_data.lck, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, j); + + kfree(buf); + + return ret; +} + +const struct file_operations data_timestamp_ops = { + .read = show_timestamp, +}; + +static ssize_t data_bridge_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct data_bridge *dev; + char *buf; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName#%s dev %p\n" + "pending tx urbs: %u\n" + "tx urb drp cnt: %u\n" + "to host: %lu\n" + "to mdm: %lu\n" + "tx throttled cnt: %u\n" + "tx unthrottled cnt: %u\n" + "rx throttled cnt: %u\n" + "rx unthrottled cnt: %u\n" + "rx done skb qlen: %u\n" + "dev err: %d\n" + "suspended: %d\n" + "TX_HALT: %d\n" + "RX_HALT: %d\n", + dev->pdev->name, dev, + atomic_read(&dev->pending_txurbs), + dev->txurb_drp_cnt, + dev->to_host, + dev->to_modem, + dev->tx_throttled_cnt, + dev->tx_unthrottled_cnt, + dev->rx_throttled_cnt, + dev->rx_unthrottled_cnt, + dev->rx_done.qlen, + dev->err, + test_bit(SUSPENDED, &dev->flags), + test_bit(TX_HALT, &dev->flags), + test_bit(RX_HALT, &dev->flags)); + + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t data_bridge_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct data_bridge *dev; + int i; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + dev->to_host = 0; + dev->to_modem = 0; + dev->txurb_drp_cnt = 0; + dev->tx_throttled_cnt = 0; + dev->tx_unthrottled_cnt = 0; + dev->rx_throttled_cnt = 0; + dev->rx_unthrottled_cnt = 0; + } + return count; +} + +const struct file_operations data_stats_ops = { + .read = data_bridge_read_stats, + .write = data_bridge_reset_stats, +}; + +static struct dentry *data_dent; +static struct dentry *data_dfile_stats; +static struct dentry *data_dfile_tstamp; + +static void data_bridge_debugfs_init(void) +{ + data_dent = debugfs_create_dir("data_hsic_bridge", 0); + if (IS_ERR(data_dent)) + return; + + data_dfile_stats = debugfs_create_file("status", 0644, data_dent, 0, + &data_stats_ops); + if (!data_dfile_stats || IS_ERR(data_dfile_stats)) { + debugfs_remove(data_dent); + return; + } + + data_dfile_tstamp = debugfs_create_file("timestamp", 0644, data_dent, + 0, &data_timestamp_ops); + if (!data_dfile_tstamp || IS_ERR(data_dfile_tstamp)) + debugfs_remove(data_dent); +} + +static void data_bridge_debugfs_exit(void) +{ + debugfs_remove(data_dfile_stats); + debugfs_remove(data_dfile_tstamp); + debugfs_remove(data_dent); +} + +#else +static void data_bridge_debugfs_init(void) { } +static void data_bridge_debugfs_exit(void) { } +static void dbg_timestamp(char *event, struct sk_buff * skb) +{ + return; +} + +static unsigned int get_timestamp(void) +{ + return 0; +} + +#endif + +static int __devinit +bridge_probe(struct usb_interface *iface, const struct usb_device_id *id) +{ + struct usb_host_endpoint *endpoint = NULL; + struct usb_host_endpoint *bulk_in = NULL; + struct usb_host_endpoint *bulk_out = NULL; + struct usb_host_endpoint *int_in = NULL; + struct usb_device *udev; + int i; + int status = 0; + int numends; + unsigned int iface_num; + + iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + + if (iface->num_altsetting != 1) { + err("%s invalid num_altsetting %u\n", + __func__, iface->num_altsetting); + return -EINVAL; + } + + udev = interface_to_usbdev(iface); + usb_get_dev(udev); + + if (!test_bit(iface_num, &id->driver_info)) + return -ENODEV; + + numends = iface->cur_altsetting->desc.bNumEndpoints; + for (i = 0; i < numends; i++) { + endpoint = iface->cur_altsetting->endpoint + i; + if (!endpoint) { + dev_err(&udev->dev, "%s: invalid endpoint %u\n", + __func__, i); + status = -EINVAL; + goto out; + } + + if (usb_endpoint_is_bulk_in(&endpoint->desc)) + bulk_in = endpoint; + else if (usb_endpoint_is_bulk_out(&endpoint->desc)) + bulk_out = endpoint; + else if (usb_endpoint_is_int_in(&endpoint->desc)) + int_in = endpoint; + } + + if (!bulk_in || !bulk_out || !int_in) { + dev_err(&udev->dev, "%s: invalid endpoints\n", __func__); + status = -EINVAL; + goto out; + } + + status = data_bridge_probe(iface, bulk_in, bulk_out, ch_id); + if (status < 0) { + dev_err(&udev->dev, "data_bridge_probe failed %d\n", status); + goto out; + } + + status = ctrl_bridge_probe(iface, int_in, ch_id); + if (status < 0) { + dev_err(&udev->dev, "ctrl_bridge_probe failed %d\n", status); + goto free_data_bridge; + } + + ch_id++; + + return 0; + +free_data_bridge: + platform_device_del(__dev[ch_id]->pdev); + usb_set_intfdata(iface, NULL); + kfree(__dev[ch_id]); + __dev[ch_id] = NULL; +out: + usb_put_dev(udev); + + return status; +} + +static void bridge_disconnect(struct usb_interface *intf) +{ + struct data_bridge *dev = usb_get_intfdata(intf); + struct list_head *head; + struct urb *rx_urb; + unsigned long flags; + + if (!dev) { + err("%s: data device not found\n", __func__); + return; + } + + ch_id--; + ctrl_bridge_disconnect(ch_id); + platform_device_del(dev->pdev); + usb_set_intfdata(intf, NULL); + __dev[ch_id] = NULL; + + cancel_work_sync(&dev->process_rx_w); + cancel_work_sync(&dev->kevent); + + /*free rx urbs*/ + head = &dev->rx_idle; + spin_lock_irqsave(&dev->rx_done.lock, flags); + while (!list_empty(head)) { + rx_urb = list_entry(head->next, struct urb, urb_list); + list_del(&rx_urb->urb_list); + usb_free_urb(rx_urb); + } + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + + usb_put_dev(dev->udev); + kfree(dev); +} + +/*bit position represents interface number*/ +#define PID9001_IFACE_MASK 0xC +#define PID9034_IFACE_MASK 0xC +#define PID9048_IFACE_MASK 0x18 +#define PID904C_IFACE_MASK 0x28 + +static const struct usb_device_id bridge_ids[] = { + { USB_DEVICE(0x5c6, 0x9001), + .driver_info = PID9001_IFACE_MASK, + }, + { USB_DEVICE(0x5c6, 0x9034), + .driver_info = PID9034_IFACE_MASK, + }, + { USB_DEVICE(0x5c6, 0x9048), + .driver_info = PID9048_IFACE_MASK, + }, + { USB_DEVICE(0x5c6, 0x904c), + .driver_info = PID904C_IFACE_MASK, + }, + + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, bridge_ids); + +static struct usb_driver bridge_driver = { + .name = "mdm_bridge", + .probe = bridge_probe, + .disconnect = bridge_disconnect, + .id_table = bridge_ids, + .suspend = bridge_suspend, + .resume = bridge_resume, + .supports_autosuspend = 1, +}; + +static int __init bridge_init(void) +{ + int ret; + + ret = usb_register(&bridge_driver); + if (ret) { + err("%s: unable to register mdm_bridge driver", __func__); + return ret; + } + + bridge_wq = create_singlethread_workqueue("mdm_bridge"); + if (!bridge_wq) { + usb_deregister(&bridge_driver); + pr_err("%s: Unable to create workqueue:bridge\n", __func__); + return -ENOMEM; + } + + data_bridge_debugfs_init(); + + return 0; +} + +static void __exit bridge_exit(void) +{ + data_bridge_debugfs_exit(); + destroy_workqueue(bridge_wq); + usb_deregister(&bridge_driver); +} + +module_init(bridge_init); +module_exit(bridge_exit); + +MODULE_DESCRIPTION("Qualcomm modem data bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index c2902a865b3a18a8bdd114c0e8e6ba4c227bc3e9..e6c823d5a27bf4635e49f60d7bc3ea2fb13c1615 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 @@ -107,6 +135,25 @@ 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 USB_MSM_STANDARD_ACA + bool "Support for Standard ACA" + depends on USB_MSM_ACA + default USB_MSM_OTG_72K + help + A Standard ACA has a Standard-A receptacle on the Accessory Port, + and can only be attached to a B-device. RID_A and RID_GND states + are only possible with Standard ACA. Select this feature if the + board is intended to support only Standard ACA. + 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 638d040c92d0ffbb438633af96ff02c7c00aa7f1..5afb02eca85d95ab05375315900f01876a190b67 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_TWL6030_USB) += twl6030-usb.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 0000000000000000000000000000000000000000..f62ae7679b24089cd5218f57ea2e10fc3c4a19e1 --- /dev/null +++ b/drivers/usb/otg/msm72k_otg.c @@ -0,0 +1,3036 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 usb_phy *phy, int phy_reset); +static void msm_otg_set_vbus_state(int online); +#ifdef CONFIG_USB_EHCI_MSM_72K +static void msm_otg_set_id_state(int id); +#else +static void msm_otg_set_id_state(int id) +{ +} +#endif + +struct msm_otg *the_msm_otg; + +static int is_host(void) +{ + struct msm_otg *dev = the_msm_otg; + + 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 usb_phy *xceiv, u32 val, u32 reg) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy); + + return ulpi_write(dev, val, reg); +} + +static int usb_ulpi_read(struct usb_phy *xceiv, u32 reg) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy); + + return ulpi_read(dev, reg); +} + +#ifdef CONFIG_USB_EHCI_MSM_72K +static void enable_idgnd(struct msm_otg *dev) +{ + unsigned temp; + + /* 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); + ulpi_write(dev, (1<<0), 0x0B); + temp = OTGSC_IDIE | OTGSC_IDPU; + writel_relaxed(readl_relaxed(USB_OTGSC) | temp, USB_OTGSC); +} + +static void disable_idgnd(struct msm_otg *dev) +{ + unsigned temp; + + /* Do nothing if instead of ID pin, USER controls mode switch */ + if (dev->pdata->otg_mode == OTG_USER_CONTROL) + return; + temp = OTGSC_IDIE | OTGSC_IDPU; + writel_relaxed(readl_relaxed(USB_OTGSC) & ~temp, USB_OTGSC); + ulpi_write(dev, (1<<4), 0x0F); + ulpi_write(dev, (1<<4), 0x12); + ulpi_write(dev, (1<<0), 0x0C); +} +#else +static void enable_idgnd(struct msm_otg *dev) +{ +} +static void disable_idgnd(struct msm_otg *dev) +{ +} +#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 +static void set_aca_id_inputs(struct msm_otg *dev) +{ +} +#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); +} + +/* 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 usb_otg *otg, + 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(&otg->phy->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 usb_otg *otg) +{ + struct msm_otg *dev = container_of(otg->phy, struct msm_otg, phy); + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->phy.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 usb_otg *otg) +{ + struct msm_otg *dev = container_of(otg->phy, struct msm_otg, phy); + u32 val; + int ret = 0; + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->phy.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 usb_phy *xceiv, unsigned mA) +{ + static enum chg_type curr_chg = USB_CHG_TYPE__INVALID; + struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy); + 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 usb_phy *xceiv, int on) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy); + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + if (on) + /* enable clocks */ + clk_prepare_enable(dev->alt_core_clk); + else + clk_disable_unprepare(dev->alt_core_clk); + + return 0; +} +static void msm_otg_start_peripheral(struct usb_otg *otg, int on) +{ + struct msm_otg *dev = container_of(otg->phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = dev->pdata; + + if (!otg->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_prepare_enable(dev->iface_clk); + + usb_gadget_vbus_connect(otg->gadget); + } else { + atomic_set(&dev->chg_type, USB_CHG_TYPE__INVALID); + usb_gadget_vbus_disconnect(otg->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_unprepare(dev->iface_clk); + + otg_pm_qos_update_latency(dev, 0); + if (pdata->setup_gpio) + pdata->setup_gpio(USB_SWITCH_DISABLE); + } +} + +static void msm_otg_start_host(struct usb_otg *otg, int on) +{ + struct msm_otg *dev = container_of(otg->phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = dev->pdata; + + if (!otg->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_prepare_enable(dev->iface_clk); + else + clk_disable_unprepare(dev->iface_clk); + } + + dev->start_host(otg->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->phy.otg->host && is_host(); + + /* + * Configure the PMIC ID only in case of cable disconnect. + * PMIC doesn't generate interrupt for ID_GND to ID_A + * transistion. hence use the PHY ID cricuit. + */ + if (dev->pdata->pmic_id_notif_init && !host_bus_suspend && + !test_bit(ID_A, &dev->inputs)) { + disable_idgnd(dev); + ret = dev->pdata->pmic_id_notif_init( + &msm_otg_set_id_state, 1); + if (!ret) { + dev->pmic_id_notif_supp = 1; + if (dev->pdata->pmic_id_irq) + dev->id_irq = dev->pdata->pmic_id_irq; + } else if (ret == -ENOTSUPP) { + pr_debug("%s:USB ID is not routed to pmic", + __func__); + enable_idgnd(dev); + } else { + pr_err("%s: pmic_id_ notif_init failed err:%d", + __func__, ret); + } + } + + if ((dev->phy.otg->gadget && chg_type == USB_CHG_TYPE__WALLCHARGER) || + host_bus_suspend || + (dev->phy.otg->host && !dev->pmic_id_notif_supp) || + (dev->phy.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->phy.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->iface_clk) + clk_disable_unprepare(dev->iface_clk); + + clk_disable_unprepare(dev->core_clk); + /* 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->phy.dev)) { + enable_irq_wake(dev->irq); + if (dev->vbus_on_irq) + enable_irq_wake(dev->vbus_on_irq); + if (dev->id_irq) + enable_irq_wake(dev->id_irq); + } + + atomic_set(&dev->in_lpm, 1); + + /* + * TODO: put regulators in low power mode by assuming that + * regulators are brought back to active state before PHY + * becomes active. But this assumption becomes wrong in case of + * ACA charger where PHY itself will generate the wakeup + * interrupt. This creates a small window where PHY regulators + * are in LPM but PHY is in active state and this patch assumes + * that there is no harm with this. Till hw folks confirms this + * put regulators in lpm. + */ + if (!host_bus_suspend && dev->pmic_vbus_notif_supp && + !test_bit(ID_A, &dev->inputs)) { + 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); + + clk_prepare_enable(dev->core_clk); + + if (dev->iface_clk) + clk_prepare_enable(dev->iface_clk); + + temp = readl(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel(temp, USB_USBCMD); + + if (device_may_wakeup(dev->phy.dev)) { + disable_irq_wake(dev->irq); + if (dev->vbus_on_irq) + disable_irq_wake(dev->vbus_on_irq); + if (dev->id_irq) + disable_irq_wake(dev->id_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->phy.dev); + pm_runtime_resume(dev->phy.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->phy.dev); + if (!atomic_read(&dev->in_lpm)) + pm_runtime_get_sync(dev->phy.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; + + if (can_phy_power_collapse(dev) && dev->pdata->ldo_enable) + dev->pdata->ldo_enable(1); + + if (pm_runtime_enabled(dev->phy.dev)) { + msm_otg_get_resume(dev); + } else { + pm_runtime_get_noresume(dev->phy.dev); + msm_otg_resume(dev); + pm_runtime_set_active(dev->phy.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->phy, 1); + break; + } + udelay(10); + } + +phy_resumed: + /* + * It is observed that BSVIS may get set immediatly + * after PHY becomes active upon micro-B cable connect. + * But BSVIS might get cleared by below enable_idgnd + * function which causes hw to not generate the BSV interrupt. + * Hence check for BSV interrupt explictly and schedule the + * work. + */ + if (readl_relaxed(USB_OTGSC) & OTGSC_BSVIS) { + set_bit(B_SESS_VLD, &dev->inputs); + queue_work(dev->wq, &dev->sm_work); + } + if (dev->pmic_id_notif_supp) { + dev->pdata->pmic_id_notif_init(&msm_otg_set_id_state, 0); + dev->pmic_id_notif_supp = 0; + enable_idgnd(dev); + } + + /* Enable Idabc interrupts as these were disabled before entering LPM */ + enable_idabc(dev); + + /* + * There is corner case where host won't be resumed + * while transitioning from ID_GND to ID_A. In that + * IDGND might have cleared and ID_A might not have updated + * yet. Hence update the ACA states explicitly. + */ + set_aca_id_inputs(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->phy.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 usb_phy *xceiv, int suspend) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy); + enum usb_otg_state state; + unsigned long flags; + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->phy.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->otg->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->phy.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->phy, 1); + break; + } + udelay(10); + } + if (dev->pmic_id_notif_supp) { + dev->pdata->pmic_id_notif_init( + &msm_otg_set_id_state, 0); + dev->pmic_id_notif_supp = 0; + enable_idgnd(dev); + } +out: + enable_idabc(dev); + enable_irq(dev->irq); + + } + + return 0; +} + +static int msm_otg_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct msm_otg *dev = container_of(otg->phy, struct msm_otg, phy); + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + if (!gadget) { + msm_otg_start_peripheral(otg, 0); + otg->gadget = 0; + disable_sess_valid(dev); + if (!otg->host) + disable_idabc(dev); + return 0; + } + 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->phy.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 usb_otg *otg, struct usb_bus *host) +{ + struct msm_otg *dev = container_of(otg->phy, struct msm_otg, phy); + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + if (!dev->start_host) + return -ENODEV; + + if (!host) { + msm_otg_start_host(otg, REQUEST_STOP); + usb_unregister_notify(&dev->usbdev_nb); + otg->host = 0; + dev->start_host = 0; + disable_idgnd(dev); + if (!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); + 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; +} + +static void msm_otg_set_id_state(int id) +{ + struct msm_otg *dev = the_msm_otg; + unsigned long flags; + + if (!atomic_read(&dev->in_lpm)) + return; + + if (id) { + set_bit(ID, &dev->inputs); + } else { + clear_bit(ID, &dev->inputs); + set_bit(A_BUS_REQ, &dev->inputs); + } + spin_lock_irqsave(&dev->lock, flags); + if (dev->phy.state != OTG_STATE_UNDEFINED) { + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + spin_unlock_irqrestore(&dev->lock, flags); +} +#endif + +void msm_otg_set_vbus_state(int online) +{ + struct msm_otg *dev = the_msm_otg; + + /* + * Process disconnect only for wallcharger + * during fast plug-out plug-in at the + * AC source side. + */ + if (online) + set_bit(B_SESS_VLD, &dev->inputs); + else + clear_bit(B_SESS_VLD, &dev->inputs); + + wake_lock(&dev->wlock); + 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->phy.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->phy.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->alt_core_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->alt_core_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 usb_phy *xceiv, int phy_reset) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, phy); + unsigned long timeout; + u32 mode, work = 0; + + clk_prepare_enable(dev->alt_core_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); + if (dev->pdata->bam_disable) { + writel_relaxed((readl_relaxed(USB_GEN_CONFIG) | + USB_BAM_DISABLE), USB_GEN_CONFIG); + pr_debug("%s(): USB_GEN_CONFIG = %x\n", + __func__, readl_relaxed(USB_GEN_CONFIG)); + } + /* Ensure that RESET operation is completed before turning off clock */ + mb(); + + clk_disable_unprepare(dev->alt_core_clk); + + if ((xceiv->otg->gadget && xceiv->otg->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->phy.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->phy.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->phy, 0); + + spin_lock_irqsave(&dev->lock, flags); + state = dev->phy.state; + spin_unlock_irqrestore(&dev->lock, flags); + + switch (state) { + case OTG_STATE_UNDEFINED: + + /* + * We can come here when LPM fails with wall charger + * connected. 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) { + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_B_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + work = 1; + break; + } + + /* Reset both phy and link */ + otg_reset(&dev->phy, 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->phy.otg->host) { + set_bit(ID, &dev->inputs); + set_bit(B_SESS_VLD, &dev->inputs); + } + } else { + if (!dev->phy.otg->host || !is_host()) + set_bit(ID, &dev->inputs); + + if (dev->phy.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->phy.state = OTG_STATE_B_IDLE; + } else { + set_bit(A_BUS_REQ, &dev->inputs); + dev->phy.state = OTG_STATE_A_IDLE; + } + spin_unlock_irqrestore(&dev->lock, flags); + + work = 1; + break; + case OTG_STATE_B_IDLE: + dev->phy.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->phy, 0); + + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_A_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_set_power(&dev->phy, 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->phy.state = OTG_STATE_B_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_set_power(&dev->phy, 0); + msm_otg_start_peripheral(dev->phy.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->phy.otg); + if (ret < 0) { + /* notify user space */ + clear_bit(B_BUS_REQ, &dev->inputs); + work = 1; + break; + } + spin_lock_irqsave(&dev->lock, flags); + dev->phy.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->phy, USB_IDCHG_MAX); + } else { + msm_otg_set_power(&dev->phy, 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->phy.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->phy.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->phy.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->phy.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(dev->phy.otg, 0); + dev->b_last_se0_sess = jiffies; + + /* Workaround: Reset phy after session */ + otg_reset(&dev->phy, 1); + work = 1; + } else if (test_bit(B_BUS_REQ, &dev->inputs) && + dev->phy.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->phy.otg, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->phy.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->phy.otg->host->is_b_host = 1; + msm_otg_start_host(dev->phy.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->phy, 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->phy, 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->phy.otg, REQUEST_STOP); + dev->phy.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->phy.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + + /* Workaround: Reset phy after session */ + otg_reset(&dev->phy, 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->phy.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->phy, 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->phy.otg, + OTG_EVENT_HNP_FAILED); + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + dev->phy.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->phy.state = OTG_STATE_B_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(dev->phy.otg, 1); + } else if (test_bit(ID_C, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->phy, 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->phy.otg, REQUEST_STOP); + dev->phy.otg->host->is_b_host = 0; + + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + /* Workaround: Reset phy after session */ + otg_reset(&dev->phy, 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->phy, USB_IDCHG_MAX); + } + break; + case OTG_STATE_A_IDLE: + dev->phy.otg->default_a = 1; + if (test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) { + pr_debug("id && !id_a\n"); + dev->phy.otg->default_a = 0; + otg_reset(&dev->phy, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_set_power(&dev->phy, 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->phy.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->phy, 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->phy.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->phy.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->phy.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->phy.otg, + OTG_EVENT_DEV_CONN_TMOUT); + msm_otg_del_timer(dev); + clear_bit(A_BUS_REQ, &dev->inputs); + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->phy, 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->phy, USB_IDCHG_MAX); + else + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->phy.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->phy.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->phy, + 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->phy.otg, REQUEST_STOP); + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + /* Reset both phy and link */ + otg_reset(&dev->phy, 1); + } else if (test_bit(ID_A, &dev->inputs)) { + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + } else if (!test_bit(ID, &dev->inputs)) { + msm_otg_set_power(&dev->phy, 0); + 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->phy.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->phy, 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->phy, 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->phy.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->phy, 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->phy.state = OTG_STATE_A_SUSPEND; + spin_unlock_irqrestore(&dev->lock, flags); + if (dev->phy.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->phy, + 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->phy.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->phy, + 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->phy, 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->phy.otg, + OTG_EVENT_HNP_FAILED); + msm_otg_del_timer(dev); + clear_bit(B_CONN, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + /* Reset both phy and link */ + otg_reset(&dev->phy, 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->phy, 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->phy.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->phy, 1); + } else if (!test_bit(B_CONN, &dev->inputs) && + dev->phy.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->phy.state = OTG_STATE_A_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + + msm_otg_start_host(dev->phy.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->phy.otg->gadget->is_a_peripheral = 1; + msm_otg_start_peripheral(dev->phy.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->phy, + USB_IDCHG_MIN - USB_IB_UNCFG); + } + } else if (!test_bit(B_CONN, &dev->inputs) && + !dev->phy.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->phy.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->phy, 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->phy, + USB_IDCHG_MIN - USB_IB_UNCFG); + } else if (!test_bit(ID, &dev->inputs)) { + msm_otg_set_power(&dev->phy, 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->phy.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(dev->phy.otg, 0); + dev->phy.otg->gadget->is_a_peripheral = 0; + /* HCD was suspended before. Stop it now */ + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + + /* Reset both phy and link */ + otg_reset(&dev->phy, 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->phy, 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->phy.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(dev->phy.otg, 0); + dev->phy.otg->gadget->is_a_peripheral = 0; + /* HCD was suspended before. Stop it now */ + msm_otg_start_host(dev->phy.otg, REQUEST_STOP); + } else if (test_bit(A_BIDL_ADIS, &dev->tmouts)) { + pr_debug("a_bidl_adis_tmout\n"); + msm_otg_start_peripheral(dev->phy.otg, 0); + dev->phy.otg->gadget->is_a_peripheral = 0; + + spin_lock_irqsave(&dev->lock, flags); + dev->phy.state = OTG_STATE_A_WAIT_BCON; + spin_unlock_irqrestore(&dev->lock, flags); + set_bit(A_BUS_REQ, &dev->inputs); + msm_otg_start_host(dev->phy.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->phy, 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->phy, + USB_IDCHG_MIN - USB_IB_UNCFG); + } else if (!test_bit(ID, &dev->inputs)) { + msm_otg_set_power(&dev->phy, 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->phy.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->phy.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->phy, 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->phy, 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->phy.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->phy.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->phy.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", 4)) { + clear_bit(B_SESS_VLD, &dev->inputs); + set_bit(ID, &dev->inputs); + work = 1; + } else if (!memcmp(buf, "peripheral", 10)) { + set_bit(B_SESS_VLD, &dev->inputs); + set_bit(ID, &dev->inputs); + work = 1; + } else if (!memcmp(buf, "host", 4)) { + 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" + "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", + state_string(dev->phy.state), + dev->pdata->otg_mode, + dev->inputs, + atomic_read(&dev->chg_type), + dev->pmic_vbus_notif_supp, + dev->pmic_id_notif_supp, + 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); + + 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 usb_phy_io_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; + + dev->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); + if (!dev->phy.otg) { + kfree(dev); + return -ENOMEM; + } + + the_msm_otg = dev; + dev->phy.dev = &pdev->dev; + dev->phy.otg->phy = &dev->phy; + 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->alt_core_clk = clk_get(&pdev->dev, "alt_core_clk"); + if (IS_ERR(dev->alt_core_clk)) { + pr_err("%s: failed to get alt_core_clk\n", __func__); + ret = PTR_ERR(dev->alt_core_clk); + goto rpc_fail; + } + clk_set_rate(dev->alt_core_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); + + dev->core_clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(dev->core_clk)) { + pr_err("%s: failed to get core_clk\n", __func__); + ret = PTR_ERR(dev->core_clk); + goto put_alt_core_clk; + } + /* CORE clk must be running at >60Mhz for correct HSUSB operation + * and USB core cannot tolerate frequency changes on CORE CLK. + * Vote for maximum clk frequency for CORE clock. + */ + clk_set_rate(dev->core_clk, INT_MAX); + + clk_prepare_enable(dev->core_clk); + + if (!dev->pdata->pclk_is_hw_gated) { + dev->iface_clk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(dev->iface_clk)) { + pr_err("%s: failed to get abh_clk\n", __func__); + ret = PTR_ERR(dev->iface_clk); + goto put_core_clk; + } + clk_prepare_enable(dev->iface_clk); + } + + if (!dev->pdata->phy_reset) { + dev->phy_reset_clk = clk_get(&pdev->dev, "phy_clk"); + if (IS_ERR(dev->phy_reset_clk)) { + pr_err("%s: failed to get phy_clk\n", __func__); + ret = PTR_ERR(dev->phy_reset_clk); + goto put_iface_clk; + } + } + + 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->phy_id_setup_init) { + ret = dev->pdata->phy_id_setup_init(1); + if (ret) { + pr_err("%s: phy_id_setup_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_phy_id_setup; + } + } + + 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->phy.set_suspend = msm_otg_set_suspend; + dev->phy.set_power = msm_otg_set_power; + + dev->phy.otg->set_peripheral = msm_otg_set_peripheral; +#ifdef CONFIG_USB_EHCI_MSM_72K + dev->phy.otg->set_host = msm_otg_set_host; +#endif + dev->phy.otg->start_hnp = msm_otg_start_hnp; + dev->phy.otg->send_event = msm_otg_send_event; + dev->set_clk = msm_otg_set_clk; + dev->reset = otg_reset; + dev->phy.io_ops = &msm_otg_io_ops; + if (usb_set_transceiver(&dev->phy)) { + 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_phy_id_setup: + if (dev->pdata->phy_id_setup_init) + dev->pdata->phy_id_setup_init(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_iface_clk: + if (dev->iface_clk) { + clk_disable_unprepare(dev->iface_clk); + clk_put(dev->iface_clk); + } +put_core_clk: + clk_disable_unprepare(dev->core_clk); + clk_put(dev->core_clk); +put_alt_core_clk: + clk_put(dev->alt_core_clk); +rpc_fail: + if (dev->pdata->rpc_connect) + dev->pdata->rpc_connect(0); +free_dev: + kfree(dev->phy.otg); + 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->pdata->phy_id_setup_init) + dev->pdata->phy_id_setup_init(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); + clk_disable_unprepare(dev->core_clk); + clk_put(dev->core_clk); + if (dev->iface_clk) { + clk_disable_unprepare(dev->iface_clk); + clk_put(dev->iface_clk); + } + if (dev->alt_core_clk) + clk_put(dev->alt_core_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_qos_remove_request(&dev->pdata->pm_qos_req_dma); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(dev->phy.otg); + kfree(dev); + 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 1d0347c247d184c42548b30d3fc8c3496296b819..59f01f651c1f8555a4d46255ca22e11d60ce0c3e 100644 --- a/drivers/usb/otg/msm_otg.c +++ b/drivers/usb/otg/msm_otg.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. * * This 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 @@ -30,23 +25,32 @@ #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 (motg->regs) #define DRIVER_NAME "msm_otg" +#define ID_TIMER_FREQ (jiffies + msecs_to_jiffies(500)) #define ULPI_IO_TIMEOUT_USEC (10 * 1000) - #define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ #define USB_PHY_3P3_VOL_MAX 3300000 /* uV */ #define USB_PHY_3P3_HPM_LOAD 50000 /* uA */ @@ -57,61 +61,58 @@ #define USB_PHY_1P8_HPM_LOAD 50000 /* uA */ #define USB_PHY_1P8_LPM_LOAD 4000 /* uA */ -#define USB_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_NONE 0 /*uV */ +#define USB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */ #define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ +static DECLARE_COMPLETION(pmic_vbus_init); +static struct msm_otg *the_msm_otg; +static bool debug_aca_enabled; +static bool debug_bus_voting_enabled; + static struct regulator *hsusb_3p3; static struct regulator *hsusb_1p8; static struct regulator *hsusb_vddcx; +static struct regulator *vbus_otg; +static struct regulator *mhl_analog_switch; +static struct power_supply *psy; -static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) +static bool aca_id_turned_on; +static inline bool aca_enabled(void) { - int ret = 0; - - if (init) { - hsusb_vddcx = regulator_get(motg->phy.dev, "HSUSB_VDDCX"); - if (IS_ERR(hsusb_vddcx)) { - dev_err(motg->phy.dev, "unable to get hsusb vddcx\n"); - return PTR_ERR(hsusb_vddcx); - } - - ret = regulator_set_voltage(hsusb_vddcx, - USB_PHY_VDD_DIG_VOL_MIN, - USB_PHY_VDD_DIG_VOL_MAX); - if (ret) { - dev_err(motg->phy.dev, "unable to set the voltage " - "for hsusb vddcx\n"); - regulator_put(hsusb_vddcx); - return ret; - } - - ret = regulator_enable(hsusb_vddcx); - if (ret) { - dev_err(motg->phy.dev, "unable to enable hsusb vddcx\n"); - regulator_put(hsusb_vddcx); - } - } else { - ret = regulator_set_voltage(hsusb_vddcx, 0, - USB_PHY_VDD_DIG_VOL_MAX); - if (ret) - dev_err(motg->phy.dev, "unable to set the voltage " - "for hsusb vddcx\n"); - ret = regulator_disable(hsusb_vddcx); - if (ret) - dev_err(motg->phy.dev, "unable to disable hsusb vddcx\n"); +#ifdef CONFIG_USB_MSM_ACA + return true; +#else + return debug_aca_enabled; +#endif +} - regulator_put(hsusb_vddcx); - } +enum usb_vdd_value { + VDD_NONE = 0, + VDD_MIN, + VDD_MAX, + VDD_VAL_MAX, +}; - return ret; -} +static const int vdd_val[VDD_TYPE_MAX][VDD_VAL_MAX] = { + { /* VDD_CX CORNER Voting */ + [VDD_NONE] = RPM_VREG_CORNER_NONE, + [VDD_MIN] = RPM_VREG_CORNER_NOMINAL, + [VDD_MAX] = RPM_VREG_CORNER_HIGH, + }, + { /* VDD_CX Voltage Voting */ + [VDD_NONE] = USB_PHY_VDD_DIG_VOL_NONE, + [VDD_MIN] = USB_PHY_VDD_DIG_VOL_MIN, + [VDD_MAX] = USB_PHY_VDD_DIG_VOL_MAX, + }, +}; static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) { int rc = 0; if (init) { - hsusb_3p3 = regulator_get(motg->phy.dev, "HSUSB_3p3"); + hsusb_3p3 = devm_regulator_get(motg->phy.dev, "HSUSB_3p3"); if (IS_ERR(hsusb_3p3)) { dev_err(motg->phy.dev, "unable to get hsusb 3p3\n"); return PTR_ERR(hsusb_3p3); @@ -120,60 +121,43 @@ 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->phy.dev, "unable to set voltage level " - "for hsusb 3p3\n"); - goto put_3p3; - } - rc = regulator_enable(hsusb_3p3); - if (rc) { - dev_err(motg->phy.dev, "unable to enable the hsusb 3p3\n"); - goto put_3p3; + dev_err(motg->phy.dev, "unable to set voltage level for" + "hsusb 3p3\n"); + return rc; } - hsusb_1p8 = regulator_get(motg->phy.dev, "HSUSB_1p8"); + hsusb_1p8 = devm_regulator_get(motg->phy.dev, "HSUSB_1p8"); if (IS_ERR(hsusb_1p8)) { dev_err(motg->phy.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->phy.dev, "unable to set voltage level " - "for hsusb 1p8\n"); - goto put_1p8; - } - rc = regulator_enable(hsusb_1p8); - if (rc) { - dev_err(motg->phy.dev, "unable to enable the hsusb 1p8\n"); + dev_err(motg->phy.dev, "unable to set voltage level for" + "hsusb 1p8\n"); goto put_1p8; } return 0; } - regulator_disable(hsusb_1p8); put_1p8: - regulator_put(hsusb_1p8); -disable_3p3: - regulator_disable(hsusb_3p3); -put_3p3: - regulator_put(hsusb_3p3); + regulator_set_voltage(hsusb_1p8, 0, USB_PHY_1P8_VOL_MAX); +put_3p3_lpm: + regulator_set_voltage(hsusb_3p3, 0, USB_PHY_3P3_VOL_MAX); return rc; } -#ifdef CONFIG_PM_SLEEP -#define USB_PHY_SUSP_DIG_VOL 500000 static int msm_hsusb_config_vddcx(int high) { - int max_vol = USB_PHY_VDD_DIG_VOL_MAX; + struct msm_otg *motg = the_msm_otg; + enum usb_vdd_type vdd_type = motg->vdd_type; + int max_vol = vdd_val[vdd_type][VDD_MAX]; int min_vol; int ret; - if (high) - min_vol = USB_PHY_VDD_DIG_VOL_MIN; - else - min_vol = USB_PHY_SUSP_DIG_VOL; - + min_vol = vdd_val[vdd_type][!!high]; ret = regulator_set_voltage(hsusb_vddcx, min_vol, max_vol); if (ret) { pr_err("%s: unable to set the voltage for regulator " @@ -185,18 +169,17 @@ static int msm_hsusb_config_vddcx(int high) return ret; } -#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; - if (!hsusb_1p8 || IS_ERR(hsusb_1p8)) { + if (IS_ERR(hsusb_1p8)) { pr_err("%s: HSUSB_1p8 is not initialized\n", __func__); return -ENODEV; } - if (!hsusb_3p3 || IS_ERR(hsusb_3p3)) { + if (IS_ERR(hsusb_3p3)) { pr_err("%s: HSUSB_3p3 is not initialized\n", __func__); return -ENODEV; } @@ -205,29 +188,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->phy.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->phy.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->phy.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->phy.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__); } @@ -235,6 +250,26 @@ static int msm_hsusb_ldo_set_mode(int on) return ret < 0 ? ret : 0; } +static void msm_hsusb_mhl_switch_enable(struct msm_otg *motg, bool on) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + + if (!pdata->mhl_enable) + return; + + if (!mhl_analog_switch) { + pr_err("%s: mhl_analog_switch is NULL.\n", __func__); + return; + } + + if (on) { + if (regulator_enable(mhl_analog_switch)) + pr_err("unable to enable mhl_analog_switch\n"); + } else { + regulator_disable(mhl_analog_switch); + } +} + static int ulpi_read(struct usb_phy *phy, u32 reg) { struct msm_otg *motg = container_of(phy, struct msm_otg, phy); @@ -310,6 +345,9 @@ static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) { int ret; + if (IS_ERR(motg->clk)) + return 0; + if (assert) { ret = clk_reset(motg->clk, CLK_RESET_ASSERT); if (ret) @@ -326,6 +364,9 @@ static int msm_otg_phy_clk_reset(struct msm_otg *motg) { int ret; + if (IS_ERR(motg->phy_reset_clk)) + return 0; + ret = clk_reset(motg->phy_reset_clk, CLK_RESET_ASSERT); if (ret) { dev_err(motg->phy.dev, "usb phy clk assert failed\n"); @@ -390,43 +431,74 @@ static int msm_otg_phy_reset(struct msm_otg *motg) } #define LINK_RESET_TIMEOUT_USEC (250 * 1000) +static int msm_otg_link_reset(struct msm_otg *motg) +{ + int cnt = 0; + + writel_relaxed(USBCMD_RESET, USB_USBCMD); + while (cnt < LINK_RESET_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET)) + break; + udelay(1); + cnt++; + } + if (cnt >= LINK_RESET_TIMEOUT_USEC) + return -ETIMEDOUT; + + /* select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + writel_relaxed(0x0, USB_AHBBURST); + writel_relaxed(0x08, USB_AHBMODE); + + return 0; +} + static int msm_otg_reset(struct usb_phy *phy) { struct msm_otg *motg = container_of(phy, struct msm_otg, phy); struct msm_otg_platform_data *pdata = motg->pdata; - int cnt = 0; int ret; u32 val = 0; u32 ulpi_val = 0; + /* + * USB PHY and Link reset also reset the USB BAM. + * Thus perform reset operation only once to avoid + * USB BAM reset on other cases e.g. USB cable disconnections. + */ + if (pdata->disable_reset_on_disconnect) { + if (motg->reset_counter) + return 0; + else + motg->reset_counter++; + } + + if (!IS_ERR(motg->clk)) + clk_prepare_enable(motg->clk); ret = msm_otg_phy_reset(motg); if (ret) { dev_err(phy->dev, "phy_reset failed\n"); return ret; } - ulpi_init(motg); - - writel(USBCMD_RESET, USB_USBCMD); - while (cnt < LINK_RESET_TIMEOUT_USEC) { - if (!(readl(USB_USBCMD) & USBCMD_RESET)) - break; - udelay(1); - cnt++; + aca_id_turned_on = false; + ret = msm_otg_link_reset(motg); + if (ret) { + dev_err(phy->dev, "link reset failed\n"); + return ret; } - if (cnt >= LINK_RESET_TIMEOUT_USEC) - return -ETIMEDOUT; + msleep(100); - /* select ULPI phy */ - writel(0x80000000, USB_PORTSC); + ulpi_init(motg); - msleep(100); + /* Ensure that RESET operation is completed before turning off clock */ + mb(); - writel(0x0, USB_AHBBURST); - writel(0x00, USB_AHBMODE); + if (!IS_ERR(motg->clk)) + clk_disable_unprepare(motg->clk); if (pdata->otg_control == OTG_PHY_CONTROL) { - val = readl(USB_OTGSC); + val = readl_relaxed(USB_OTGSC); if (pdata->mode == USB_OTG) { ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; val |= OTGSC_IDIE | OTGSC_BSVIE; @@ -434,11 +506,220 @@ static int msm_otg_reset(struct usb_phy *phy) ulpi_val = ULPI_INT_SESS_VALID; val |= OTGSC_BSVIE; } - writel(val, USB_OTGSC); + writel_relaxed(val, USB_OTGSC); ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE); ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); + } else if (pdata->otg_control == OTG_PMIC_CONTROL) { + ulpi_write(phy, OTG_COMP_DISABLE, + ULPI_SET(ULPI_PWR_CLK_MNG_REG)); + /* Enable PMIC pull-up */ + pm8xxx_usb_id_pullup(1); + } + + return 0; +} + +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"; + case A_TST_MAINT: return "a_tst_maint"; + case B_TST_SRP: return "b_tst_srp"; + case B_TST_CONFIG: return "b_tst_config"; + default: return "UNDEFINED"; + } +} + +static enum hrtimer_restart msm_otg_timer_func(struct hrtimer *hrtimer) +{ + struct msm_otg *motg = container_of(hrtimer, struct msm_otg, timer); + + switch (motg->active_tmout) { + case A_WAIT_VRISE: + /* TODO: use vbus_vld interrupt */ + set_bit(A_VBUS_VLD, &motg->inputs); + break; + case A_TST_MAINT: + /* OTG PET: End session after TA_TST_MAINT */ + set_bit(A_BUS_DROP, &motg->inputs); + break; + case B_TST_SRP: + /* + * OTG PET: Initiate SRP after TB_TST_SRP of + * previous session end. + */ + set_bit(B_BUS_REQ, &motg->inputs); + break; + case B_TST_CONFIG: + clear_bit(A_CONN, &motg->inputs); + break; + default: + set_bit(motg->active_tmout, &motg->tmouts); + } + + pr_debug("expired %s timer\n", timer_string(motg->active_tmout)); + queue_work(system_nrt_wq, &motg->sm_work); + return HRTIMER_NORESTART; +} + +static void msm_otg_del_timer(struct msm_otg *motg) +{ + int bit = motg->active_tmout; + + pr_debug("deleting %s timer. remaining %lld msec\n", timer_string(bit), + div_s64(ktime_to_us(hrtimer_get_remaining( + &motg->timer)), 1000)); + hrtimer_cancel(&motg->timer); + clear_bit(bit, &motg->tmouts); +} + +static void msm_otg_start_timer(struct msm_otg *motg, int time, int bit) +{ + clear_bit(bit, &motg->tmouts); + motg->active_tmout = bit; + pr_debug("starting %s timer\n", timer_string(bit)); + hrtimer_start(&motg->timer, + ktime_set(time / 1000, (time % 1000) * 1000000), + HRTIMER_MODE_REL); +} + +static void msm_otg_init_timer(struct msm_otg *motg) +{ + hrtimer_init(&motg->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + motg->timer.function = msm_otg_timer_func; +} + +static int msm_otg_start_hnp(struct usb_otg *otg) +{ + struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); + + if (otg->phy->state != OTG_STATE_A_HOST) { + pr_err("HNP can not be initiated in %s state\n", + otg_state_string(otg->phy->state)); + return -EINVAL; + } + + pr_debug("A-Host: HNP initiated\n"); + clear_bit(A_BUS_REQ, &motg->inputs); + queue_work(system_nrt_wq, &motg->sm_work); + return 0; +} + +static int msm_otg_start_srp(struct usb_otg *otg) +{ + struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); + u32 val; + int ret = 0; + + if (otg->phy->state != OTG_STATE_B_IDLE) { + pr_err("SRP can not be initiated in %s state\n", + otg_state_string(otg->phy->state)); + ret = -EINVAL; + goto out; + } + + if ((jiffies - motg->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; + } + + pr_debug("B-Device SRP started\n"); + + /* + * PHY won't pull D+ high unless it detects Vbus valid. + * Since by definition, SRP is only done when Vbus is not valid, + * software work-around needs to be used to spoof the PHY into + * thinking it is valid. This can be done using the VBUSVLDEXTSEL and + * VBUSVLDEXT register bits. + */ + ulpi_write(otg->phy, 0x03, 0x97); + /* + * Harware auto assist data pulsing: Data pulse is given + * for 7msec; wait for vbus + */ + val = readl_relaxed(USB_OTGSC); + writel_relaxed((val & ~OTGSC_INTSTS_MASK) | OTGSC_HADP, USB_OTGSC); + + /* VBUS plusing is obsoleted in OTG 2.0 supplement */ +out: + return ret; +} + +static void msm_otg_host_hnp_enable(struct usb_otg *otg, bool enable) +{ + struct usb_hcd *hcd = bus_to_hcd(otg->host); + struct usb_device *rhub = otg->host->root_hub; + + if (enable) { + pm_runtime_disable(&rhub->dev); + rhub->state = USB_STATE_NOTATTACHED; + hcd->driver->bus_suspend(hcd); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + } else { + usb_remove_hcd(hcd); + msm_otg_reset(otg->phy); + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); } +} + +static int msm_otg_set_suspend(struct usb_phy *phy, int suspend) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + + if (aca_enabled()) + return 0; + + if (suspend) { + switch (phy->state) { + case OTG_STATE_A_WAIT_BCON: + if (TA_WAIT_BCON > 0) + break; + /* fall through */ + case OTG_STATE_A_HOST: + pr_debug("host bus suspend\n"); + clear_bit(A_BUS_REQ, &motg->inputs); + queue_work(system_nrt_wq, &motg->sm_work); + break; + case OTG_STATE_B_PERIPHERAL: + pr_debug("peripheral bus suspend\n"); + if (!(motg->caps & ALLOW_LPM_ON_DEV_SUSPEND)) + break; + set_bit(A_BUS_SUSPEND, &motg->inputs); + queue_work(system_nrt_wq, &motg->sm_work); + break; + default: + break; + } + } else { + switch (phy->state) { + case OTG_STATE_A_SUSPEND: + /* Remote wakeup or resume */ + set_bit(A_BUS_REQ, &motg->inputs); + phy->state = OTG_STATE_A_HOST; + + /* ensure hardware is not in low power mode */ + pm_runtime_resume(phy->dev); + break; + case OTG_STATE_B_PERIPHERAL: + pr_debug("peripheral bus resume\n"); + if (!(motg->caps & ALLOW_LPM_ON_DEV_SUSPEND)) + break; + clear_bit(A_BUS_SUSPEND, &motg->inputs); + queue_work(system_nrt_wq, &motg->sm_work); + break; + default: + break; + } + } return 0; } @@ -452,11 +733,20 @@ static int msm_otg_suspend(struct msm_otg *motg) struct usb_bus *bus = phy->otg->host; struct msm_otg_platform_data *pdata = motg->pdata; int cnt = 0; + bool host_bus_suspend, device_bus_suspend, dcp; + u32 phy_ctrl_val = 0, cmd_val; + unsigned ret; + u32 portsc; if (atomic_read(&motg->in_lpm)) return 0; disable_irq(motg->irq); + host_bus_suspend = phy->otg->host && !test_bit(ID, &motg->inputs); + device_bus_suspend = phy->otg->gadget && test_bit(ID, &motg->inputs) && + test_bit(A_BUS_SUSPEND, &motg->inputs) && + motg->caps & ALLOW_LPM_ON_DEV_SUSPEND; + dcp = motg->chg_type == USB_DCP_CHARGER; /* * Chipidea 45-nm PHY suspend sequence: * @@ -481,17 +771,22 @@ static int msm_otg_suspend(struct msm_otg *motg) ulpi_write(phy, 0x08, 0x09); } - /* + + /* Set the PHCD bit, only if it is not set by the controller. * 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 * in failure case. */ - writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); - while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { - if (readl(USB_PORTSC) & PORTSC_PHCD) - break; - udelay(1); - cnt++; + portsc = readl_relaxed(USB_PORTSC); + if (!(portsc & PORTSC_PHCD)) { + writel_relaxed(portsc | PORTSC_PHCD, + USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } } if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { @@ -507,34 +802,69 @@ static int msm_otg_suspend(struct msm_otg *motg) * line must be disabled till async interrupt enable bit is cleared * in USBCMD register. Assert STP (ULPI interface STOP signal) to * block data communication from PHY. + * + * PHY retention mode is disallowed while entering to LPM with wall + * charger connected. But PHY is put into suspend mode. Hence + * enable asynchronous interrupt to detect charger disconnection when + * PMIC notifications are unavailable. + */ + cmd_val = readl_relaxed(USB_USBCMD); + if (host_bus_suspend || device_bus_suspend || + (motg->pdata->otg_control == OTG_PHY_CONTROL && dcp)) + cmd_val |= ASYNC_INTR_CTRL | ULPI_STP_CTRL; + else + cmd_val |= ULPI_STP_CTRL; + writel_relaxed(cmd_val, USB_USBCMD); + + /* + * BC1.2 spec mandates PD to enable VDP_SRC when charging from DCP. + * PHY retention and collapse can not happen with VDP_SRC enabled. */ - writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); + if (motg->caps & ALLOW_PHY_RETENTION && !host_bus_suspend && + !device_bus_suspend && !dcp) { + phy_ctrl_val = readl_relaxed(USB_PHY_CTRL); + if (motg->pdata->otg_control == OTG_PHY_CONTROL) + /* Enable PHY HV interrupts to wake MPM/Link */ + phy_ctrl_val |= + (PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN); + + writel_relaxed(phy_ctrl_val & ~PHY_RETEN, USB_PHY_CTRL); + motg->lpm_flags |= PHY_RETENTIONED; + } - 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); + /* Ensure that above operation is completed before turning off clocks */ + mb(); + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); - clk_disable(motg->pclk); - clk_disable(motg->clk); - if (motg->core_clk) - clk_disable(motg->core_clk); + /* usb phy no more require TCXO clock, hence vote for TCXO disable */ + ret = msm_xo_mode_vote(motg->xo_handle, MSM_XO_MODE_OFF); + if (ret) + dev_err(phy->dev, "%s failed to devote for " + "TCXO D0 buffer%d\n", __func__, ret); - if (!IS_ERR(motg->pclk_src)) - clk_disable(motg->pclk_src); + if (motg->caps & ALLOW_PHY_POWER_COLLAPSE && + !host_bus_suspend && !dcp) { + msm_hsusb_ldo_enable(motg, 0); + motg->lpm_flags |= PHY_PWR_COLLAPSED; + } - if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && - motg->pdata->otg_control == OTG_PMIC_CONTROL) { - msm_hsusb_ldo_set_mode(0); + if (motg->lpm_flags & PHY_RETENTIONED) { msm_hsusb_config_vddcx(0); + msm_hsusb_mhl_switch_enable(motg, 0); } - if (device_may_wakeup(phy->dev)) + if (device_may_wakeup(phy->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(phy->dev, "USB in low power mode\n"); @@ -547,23 +877,40 @@ static int msm_otg_resume(struct msm_otg *motg) struct usb_bus *bus = phy->otg->host; int cnt = 0; unsigned temp; + u32 phy_ctrl_val = 0; + unsigned ret; if (!atomic_read(&motg->in_lpm)) return 0; - if (!IS_ERR(motg->pclk_src)) - clk_enable(motg->pclk_src); + wake_lock(&motg->wlock); + + /* Vote for TCXO when waking up the phy */ + ret = msm_xo_mode_vote(motg->xo_handle, MSM_XO_MODE_ON); + if (ret) + dev_err(phy->dev, "%s failed to vote for " + "TCXO D0 buffer%d\n", __func__, ret); + + clk_prepare_enable(motg->core_clk); - clk_enable(motg->pclk); - clk_enable(motg->clk); - if (motg->core_clk) - clk_enable(motg->core_clk); + clk_prepare_enable(motg->pclk); + + if (motg->lpm_flags & PHY_PWR_COLLAPSED) { + msm_hsusb_ldo_enable(motg, 1); + motg->lpm_flags &= ~PHY_PWR_COLLAPSED; + } - 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_RETENTIONED) { + msm_hsusb_mhl_switch_enable(motg, 1); msm_hsusb_config_vddcx(1); - writel(readl(USB_PHY_CTRL) & ~PHY_RETEN, USB_PHY_CTRL); + phy_ctrl_val = readl_relaxed(USB_PHY_CTRL); + phy_ctrl_val |= PHY_RETEN; + if (motg->pdata->otg_control == OTG_PHY_CONTROL) + /* Disable PHY HV interrupts */ + phy_ctrl_val &= + ~(PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN); + writel_relaxed(phy_ctrl_val, USB_PHY_CTRL); + motg->lpm_flags &= ~PHY_RETENTIONED; } temp = readl(USB_USBCMD); @@ -598,8 +945,11 @@ static int msm_otg_resume(struct msm_otg *motg) } skip_phy_resume: - if (device_may_wakeup(phy->dev)) + if (device_may_wakeup(phy->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); @@ -607,7 +957,6 @@ static int msm_otg_resume(struct msm_otg *motg) if (motg->async_int) { motg->async_int = 0; - pm_runtime_put(phy->dev); enable_irq(motg->irq); } @@ -617,13 +966,91 @@ static int msm_otg_resume(struct msm_otg *motg) } #endif +static int msm_otg_notify_chg_type(struct msm_otg *motg) +{ + static int charger_type; + /* + * TODO + * Unify OTG driver charger types and power supply charger types + */ + if (charger_type == motg->chg_type) + return 0; + + if (motg->chg_type == USB_SDP_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB; + else if (motg->chg_type == USB_CDP_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB_CDP; + else if (motg->chg_type == USB_DCP_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB_DCP; + else if ((motg->chg_type == USB_ACA_DOCK_CHARGER || + motg->chg_type == USB_ACA_A_CHARGER || + motg->chg_type == USB_ACA_B_CHARGER || + motg->chg_type == USB_ACA_C_CHARGER)) + charger_type = POWER_SUPPLY_TYPE_USB_ACA; + else + charger_type = POWER_SUPPLY_TYPE_BATTERY; + + return pm8921_set_usb_power_supply_type(charger_type); +} + +static int msm_otg_notify_power_supply(struct msm_otg *motg, unsigned mA) +{ + + if (!psy) + goto psy_not_supported; + + if (motg->cur_power == 0 && mA > 0) { + /* Enable charging */ + if (power_supply_set_online(psy, true)) + goto psy_not_supported; + } else if (motg->cur_power > 0 && mA == 0) { + /* Disable charging */ + if (power_supply_set_online(psy, false)) + goto psy_not_supported; + return 0; + } + /* Set max current limit */ + if (power_supply_set_current_limit(psy, 1000*mA)) + goto psy_not_supported; + + return 0; + +psy_not_supported: + dev_dbg(motg->phy.dev, "Power Supply doesn't support USB charger\n"); + return -ENXIO; +} + static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA) { + struct usb_gadget *g = motg->phy.otg->gadget; + + if (g && g->is_a_peripheral) + return; + + if ((motg->chg_type == USB_ACA_DOCK_CHARGER || + motg->chg_type == USB_ACA_A_CHARGER || + motg->chg_type == USB_ACA_B_CHARGER || + motg->chg_type == USB_ACA_C_CHARGER) && + mA > IDEV_ACA_CHG_LIMIT) + mA = IDEV_ACA_CHG_LIMIT; + + if (msm_otg_notify_chg_type(motg)) + dev_err(motg->phy.dev, + "Failed notifying %d charger type to PMIC\n", + motg->chg_type); + if (motg->cur_power == mA) return; - /* TODO: Notify PMIC about available current */ dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA); + + /* + * Use Power Supply API if supported, otherwise fallback + * to legacy pm8921 API. + */ + if (msm_otg_notify_power_supply(motg, mA)) + pm8921_charger_vbus_draw(mA); + motg->cur_power = mA; } @@ -644,42 +1071,164 @@ static int msm_otg_set_power(struct usb_phy *phy, unsigned mA) return 0; } -static void msm_otg_start_host(struct usb_phy *phy, int on) +static void msm_otg_start_host(struct usb_otg *otg, int on) { - struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); struct msm_otg_platform_data *pdata = motg->pdata; struct usb_hcd *hcd; - if (!phy->otg->host) + if (!otg->host) return; - hcd = bus_to_hcd(phy->otg->host); + hcd = bus_to_hcd(otg->host); if (on) { - dev_dbg(phy->dev, "host on\n"); + dev_dbg(otg->phy->dev, "host on\n"); - if (pdata->vbus_power) - pdata->vbus_power(1); - /* + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(otg->phy, OTG_COMP_DISABLE, + ULPI_SET(ULPI_PWR_CLK_MNG_REG)); + + /* * Some boards have a switch cotrolled by gpio * to enable/disable internal HUB. Enable internal * HUB before kicking the host. */ 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(phy->dev, "host off\n"); + dev_dbg(otg->phy->dev, "host off\n"); -#ifdef CONFIG_USB usb_remove_hcd(hcd); -#endif + /* HCD core reset all bits of PORTSC. select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_UNDEFINED); - if (pdata->vbus_power) - pdata->vbus_power(0); + + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(otg->phy, OTG_COMP_DISABLE, + ULPI_CLR(ULPI_PWR_CLK_MNG_REG)); + } +} + +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_otg *otg = motg->phy.otg; + struct usb_device *udev = priv; + + if (action == USB_BUS_ADD || action == USB_BUS_REMOVE) + goto out; + + if (udev->bus != otg->host) + goto out; + /* + * 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) + goto out; + + switch (action) { + case USB_DEVICE_ADD: + if (aca_enabled()) + usb_disable_autosuspend(udev); + if (otg->phy->state == OTG_STATE_A_WAIT_BCON) { + pr_debug("B_CONN set\n"); + set_bit(B_CONN, &motg->inputs); + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_HOST; + /* + * OTG PET: A-device must end session within + * 10 sec after PET enumeration. + */ + if (udev->quirks & USB_QUIRK_OTG_PET) + msm_otg_start_timer(motg, TA_TST_MAINT, + A_TST_MAINT); + } + /* fall through */ + case USB_DEVICE_CONFIG: + if (udev->actconfig) + motg->mA_port = udev->actconfig->desc.bMaxPower * 2; + else + motg->mA_port = IUNIT; + if (otg->phy->state == OTG_STATE_B_HOST) + msm_otg_del_timer(motg); + break; + case USB_DEVICE_REMOVE: + if ((otg->phy->state == OTG_STATE_A_HOST) || + (otg->phy->state == OTG_STATE_A_SUSPEND)) { + pr_debug("B_CONN clear\n"); + clear_bit(B_CONN, &motg->inputs); + /* + * OTG PET: A-device must end session after + * PET disconnection if it is enumerated + * with bcdDevice[0] = 1. USB core sets + * bus->otg_vbus_off for us. clear it here. + */ + if (udev->bus->otg_vbus_off) { + udev->bus->otg_vbus_off = 0; + set_bit(A_BUS_DROP, &motg->inputs); + } + queue_work(system_nrt_wq, &motg->sm_work); + } + default: + break; + } + if (test_bit(ID_A, &motg->inputs)) + msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX - + motg->mA_port); +out: + return NOTIFY_OK; +} + +static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on) +{ + int ret; + static bool vbus_is_on; + + if (vbus_is_on == on) + return; + + if (motg->pdata->vbus_power) { + ret = motg->pdata->vbus_power(on); + if (!ret) + vbus_is_on = on; + return; + } + + if (!vbus_otg) { + pr_err("vbus_otg is NULL."); + return; + } + + /* + * if entering host mode tell the charger to not draw any current + * from usb before turning on the boost. + * if exiting host mode disable the boost before enabling to draw + * current from the source. + */ + if (on) { + pm8921_disable_source_current(on); + ret = regulator_enable(vbus_otg); + if (ret) { + pr_err("unable to enable vbus_otg\n"); + return; + } + vbus_is_on = true; + } else { + ret = regulator_disable(vbus_otg); + if (ret) { + pr_err("unable to disable vbus_otg\n"); + return; + } + pm8921_disable_source_current(on); + vbus_is_on = false; } } @@ -697,13 +1246,23 @@ static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) return -ENODEV; } + if (!motg->pdata->vbus_power && host) { + vbus_otg = devm_regulator_get(motg->phy.dev, "vbus_otg"); + if (IS_ERR(vbus_otg)) { + pr_err("Unable to get vbus_otg\n"); + return -ENODEV; + } + } + if (!host) { if (otg->phy->state == OTG_STATE_A_HOST) { pm_runtime_get_sync(otg->phy->dev); - msm_otg_start_host(otg->phy, 0); + usb_unregister_notify(&motg->usbdev_nb); + msm_otg_start_host(otg, 0); + msm_hsusb_vbus_power(motg, 0); otg->host = NULL; otg->phy->state = OTG_STATE_UNDEFINED; - schedule_work(&motg->sm_work); + queue_work(system_nrt_wq, &motg->sm_work); } else { otg->host = NULL; } @@ -714,6 +1273,11 @@ static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) hcd = bus_to_hcd(host); hcd->power_budget = motg->pdata->power_budget; +#ifdef CONFIG_USB_OTG + host->otg_port = 1; +#endif + motg->usbdev_nb.notifier_call = msm_otg_usbdev_notify; + usb_register_notify(&motg->usbdev_nb); otg->host = host; dev_dbg(otg->phy->dev, "host driver registered w/ tranceiver\n"); @@ -723,22 +1287,23 @@ static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) */ if (motg->pdata->mode == USB_HOST || otg->gadget) { pm_runtime_get_sync(otg->phy->dev); - schedule_work(&motg->sm_work); + queue_work(system_nrt_wq, &motg->sm_work); } return 0; } -static void msm_otg_start_peripheral(struct usb_phy *phy, int on) +static void msm_otg_start_peripheral(struct usb_otg *otg, int on) { - struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + int ret; + struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); struct msm_otg_platform_data *pdata = motg->pdata; - if (!phy->otg->gadget) + if (!otg->gadget) return; if (on) { - dev_dbg(phy->dev, "gadget on\n"); + dev_dbg(otg->phy->dev, "gadget on\n"); /* * Some boards have a switch cotrolled by gpio * to enable/disable internal HUB. Disable internal @@ -746,10 +1311,27 @@ static void msm_otg_start_peripheral(struct usb_phy *phy, int on) */ if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_B_PERIPHERAL); - usb_gadget_vbus_connect(phy->otg->gadget); + + /* Configure BUS performance parameters for MAX bandwidth */ + if (motg->bus_perf_client && debug_bus_voting_enabled) { + ret = msm_bus_scale_client_update_request( + motg->bus_perf_client, 1); + if (ret) + dev_err(motg->phy.dev, "%s: Failed to vote for " + "bus bandwidth %d\n", __func__, ret); + } + usb_gadget_vbus_connect(otg->gadget); } else { - dev_dbg(phy->dev, "gadget off\n"); - usb_gadget_vbus_disconnect(phy->otg->gadget); + dev_dbg(otg->phy->dev, "gadget off\n"); + usb_gadget_vbus_disconnect(otg->gadget); + /* Configure BUS performance parameters to default */ + if (motg->bus_perf_client) { + ret = msm_bus_scale_client_update_request( + motg->bus_perf_client, 0); + if (ret) + dev_err(motg->phy.dev, "%s: Failed to devote " + "for bus bw %d\n", __func__, ret); + } if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_UNDEFINED); } @@ -757,7 +1339,7 @@ static void msm_otg_start_peripheral(struct usb_phy *phy, int on) } static int msm_otg_set_peripheral(struct usb_otg *otg, - struct usb_gadget *gadget) + struct usb_gadget *gadget) { struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); @@ -773,10 +1355,10 @@ static int msm_otg_set_peripheral(struct usb_otg *otg, if (!gadget) { if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { pm_runtime_get_sync(otg->phy->dev); - msm_otg_start_peripheral(otg->phy, 0); + msm_otg_start_peripheral(otg, 0); otg->gadget = NULL; otg->phy->state = OTG_STATE_UNDEFINED; - schedule_work(&motg->sm_work); + queue_work(system_nrt_wq, &motg->sm_work); } else { otg->gadget = NULL; } @@ -792,12 +1374,190 @@ static int msm_otg_set_peripheral(struct usb_otg *otg, */ if (motg->pdata->mode == USB_PERIPHERAL || otg->host) { pm_runtime_get_sync(otg->phy->dev); - schedule_work(&motg->sm_work); + queue_work(system_nrt_wq, &motg->sm_work); } return 0; } +static bool msm_chg_aca_detect(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 int_sts; + bool ret = false; + + if (!aca_enabled()) + goto out; + + if (motg->pdata->phy_type == CI_45NM_INTEGRATED_PHY) + goto out; + + int_sts = ulpi_read(phy, 0x87); + switch (int_sts & 0x1C) { + case 0x08: + if (!test_and_set_bit(ID_A, &motg->inputs)) { + dev_dbg(phy->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); + set_bit(ID, &motg->inputs); + ret = true; + } + break; + case 0x0C: + if (!test_and_set_bit(ID_B, &motg->inputs)) { + dev_dbg(phy->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); + set_bit(ID, &motg->inputs); + ret = true; + } + break; + case 0x10: + if (!test_and_set_bit(ID_C, &motg->inputs)) { + dev_dbg(phy->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); + set_bit(ID, &motg->inputs); + ret = true; + } + break; + case 0x04: + if (test_and_clear_bit(ID, &motg->inputs)) { + dev_dbg(phy->dev, "ID_GND\n"); + motg->chg_type = USB_INVALID_CHARGER; + motg->chg_state = USB_CHG_STATE_UNDEFINED; + clear_bit(ID_A, &motg->inputs); + clear_bit(ID_B, &motg->inputs); + clear_bit(ID_C, &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) | + !test_and_set_bit(ID, &motg->inputs); + if (ret) { + dev_dbg(phy->dev, "ID A/B/C/GND 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 usb_phy *phy = &motg->phy; + + if (!aca_enabled()) + return; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + /* Disable ID_GND in link and PHY */ + writel_relaxed(readl_relaxed(USB_OTGSC) & ~(OTGSC_IDPU | + OTGSC_IDIE), USB_OTGSC); + ulpi_write(phy, 0x01, 0x0C); + ulpi_write(phy, 0x10, 0x0F); + ulpi_write(phy, 0x10, 0x12); + /* Disable PMIC ID pull-up */ + pm8xxx_usb_id_pullup(0); + /* Enable ACA ID detection */ + ulpi_write(phy, 0x20, 0x85); + aca_id_turned_on = true; + break; + default: + break; + } +} + +static void msm_chg_enable_aca_intr(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + + if (!aca_enabled()) + return; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + /* Enable ACA Detection interrupt (on any RID change) */ + ulpi_write(phy, 0x01, 0x94); + break; + default: + break; + } +} + +static void msm_chg_disable_aca_intr(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + + if (!aca_enabled()) + return; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + ulpi_write(phy, 0x01, 0x95); + break; + default: + break; + } +} + +static bool msm_chg_check_aca_intr(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + bool ret = false; + + if (!aca_enabled()) + return ret; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + if (ulpi_read(phy, 0x91) & 1) { + dev_dbg(phy->dev, "RID change\n"); + ulpi_write(phy, 0x01, 0x92); + ret = msm_chg_aca_detect(motg); + } + default: + break; + } + return ret; +} + +static void msm_otg_id_timer_func(unsigned long data) +{ + struct msm_otg *motg = (struct msm_otg *) data; + + if (!aca_enabled()) + return; + + if (atomic_read(&motg->in_lpm)) { + dev_dbg(motg->phy.dev, "timer: in lpm\n"); + return; + } + + if (motg->phy.state == OTG_STATE_A_SUSPEND) + goto out; + + if (msm_chg_check_aca_intr(motg)) { + dev_dbg(motg->phy.dev, "timer: aca work\n"); + queue_work(system_nrt_wq, &motg->sm_work); + } + +out: + if (!test_bit(ID, &motg->inputs) || test_bit(ID_A, &motg->inputs)) + mod_timer(&motg->id_timer, ID_TIMER_FREQ); +} + static bool msm_chg_check_secondary_det(struct msm_otg *motg) { struct usb_phy *phy = &motg->phy; @@ -846,6 +1606,9 @@ static void msm_chg_enable_secondary_det(struct msm_otg *motg) ulpi_write(phy, chg_det, 0x34); break; case SNPS_28NM_INTEGRATED_PHY: + /* Turn off VDP_SRC */ + ulpi_write(phy, 0x3, 0x86); + msleep(20); /* * Configure DM as current source, DP as current sink * and enable battery charging comparators. @@ -990,7 +1753,7 @@ static void msm_chg_block_on(struct msm_otg *motg) break; case SNPS_28NM_INTEGRATED_PHY: /* Clear charger detecting control bits */ - ulpi_write(phy, 0x3F, 0x86); + ulpi_write(phy, 0x1F, 0x86); /* Clear alt interrupt latch and enable bits */ ulpi_write(phy, 0x1F, 0x92); ulpi_write(phy, 0x1F, 0x95); @@ -1031,32 +1794,62 @@ static void msm_chg_block_off(struct msm_otg *motg) ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); } +static const char *chg_to_string(enum usb_chg_type chg_type) +{ + switch (chg_type) { + case USB_SDP_CHARGER: return "USB_SDP_CHARGER"; + case USB_DCP_CHARGER: return "USB_DCP_CHARGER"; + case USB_CDP_CHARGER: return "USB_CDP_CHARGER"; + case USB_ACA_A_CHARGER: return "USB_ACA_A_CHARGER"; + case USB_ACA_B_CHARGER: return "USB_ACA_B_CHARGER"; + case USB_ACA_C_CHARGER: return "USB_ACA_C_CHARGER"; + case USB_ACA_DOCK_CHARGER: return "USB_ACA_DOCK_CHARGER"; + default: return "INVALID_CHARGER"; + } +} + #define MSM_CHG_DCD_POLL_TIME (100 * HZ/1000) /* 100 msec */ #define MSM_CHG_DCD_MAX_RETRIES 6 /* Tdcd_tmout = 6 * 100 msec */ -#define MSM_CHG_PRIMARY_DET_TIME (40 * HZ/1000) /* TVDPSRC_ON */ -#define MSM_CHG_SECONDARY_DET_TIME (40 * HZ/1000) /* TVDMSRC_ON */ +#define MSM_CHG_PRIMARY_DET_TIME (50 * HZ/1000) /* TVDPSRC_ON */ +#define MSM_CHG_SECONDARY_DET_TIME (50 * HZ/1000) /* TVDMSRC_ON */ static void msm_chg_detect_work(struct work_struct *w) { struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); struct usb_phy *phy = &motg->phy; - bool is_dcd, tmout, vout; + bool is_dcd = false, tmout, vout, is_aca; unsigned long delay; dev_dbg(phy->dev, "chg detection work\n"); switch (motg->chg_state) { case USB_CHG_STATE_UNDEFINED: - pm_runtime_get_sync(phy->dev); msm_chg_block_on(motg); - msm_chg_enable_dcd(motg); + if (motg->pdata->enable_dcd) + 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_dcd = msm_chg_check_dcd(motg); + 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; + } + } + if (motg->pdata->enable_dcd) + is_dcd = msm_chg_check_dcd(motg); tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES; if (is_dcd || tmout) { - msm_chg_disable_dcd(motg); + if (motg->pdata->enable_dcd) + msm_chg_disable_dcd(motg); msm_chg_enable_primary_det(motg); delay = MSM_CHG_PRIMARY_DET_TIME; motg->chg_state = USB_CHG_STATE_DCD_DONE; @@ -1067,10 +1860,22 @@ static void msm_chg_detect_work(struct work_struct *w) case USB_CHG_STATE_DCD_DONE: vout = msm_chg_check_primary_det(motg); if (vout) { + if (test_bit(ID_A, &motg->inputs)) { + motg->chg_type = USB_ACA_DOCK_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + break; + } msm_chg_enable_secondary_det(motg); delay = MSM_CHG_SECONDARY_DET_TIME; motg->chg_state = USB_CHG_STATE_PRIMARY_DONE; } else { + if (test_bit(ID_A, &motg->inputs)) { + motg->chg_type = USB_ACA_A_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + break; + } motg->chg_type = USB_SDP_CHARGER; motg->chg_state = USB_CHG_STATE_DETECTED; delay = 0; @@ -1088,14 +1893,23 @@ 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); - dev_dbg(phy->dev, "charger = %d\n", motg->chg_type); - schedule_work(&motg->sm_work); + msm_chg_enable_aca_det(motg); + /* + * Spurious interrupt is seen after enabling ACA detection + * due to which charger detection fails in case of PET. + * Add delay of 100 microsec to avoid that. + */ + udelay(100); + msm_chg_enable_aca_intr(motg); + dev_dbg(phy->dev, "chg_type = %s\n", + chg_to_string(motg->chg_type)); + queue_work(system_nrt_wq, &motg->sm_work); return; default: return; } - schedule_delayed_work(&motg->chg_work, delay); + queue_delayed_work(system_nrt_wq, &motg->chg_work, delay); } /* @@ -1112,17 +1926,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 +1936,32 @@ static void msm_otg_init_sm(struct msm_otg *motg) set_bit(ID, &motg->inputs); clear_bit(B_SESS_VLD, &motg->inputs); } + } else if (pdata->otg_control == OTG_PHY_CONTROL) { + if (otgsc & OTGSC_ID) { + set_bit(ID, &motg->inputs); + } else { + clear_bit(ID, &motg->inputs); + set_bit(A_BUS_REQ, &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_PMIC_CONTROL) { + if (pdata->pmic_id_irq) { + unsigned long flags; + local_irq_save(flags); + if (irq_read_line(pdata->pmic_id_irq)) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + local_irq_restore(flags); + } + /* + * VBUS initial state is reported after PMIC + * driver initialization. Wait for it. + */ + wait_for_completion(&pmic_vbus_init); } break; case USB_HOST: @@ -1139,10 +1969,18 @@ static void msm_otg_init_sm(struct msm_otg *motg) break; case USB_PERIPHERAL: set_bit(ID, &motg->inputs); - if (otgsc & OTGSC_BSV) - set_bit(B_SESS_VLD, &motg->inputs); - else - clear_bit(B_SESS_VLD, &motg->inputs); + if (pdata->otg_control == OTG_PHY_CONTROL) { + 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_PMIC_CONTROL) { + /* + * VBUS initial state is reported after PMIC + * driver initialization. Wait for it. + */ + wait_for_completion(&pmic_vbus_init); + } break; default: break; @@ -1153,22 +1991,35 @@ static void msm_otg_sm_work(struct work_struct *w) { struct msm_otg *motg = container_of(w, struct msm_otg, sm_work); struct usb_otg *otg = motg->phy.otg; + bool work = 0, srp_reqd; + pm_runtime_resume(otg->phy->dev); + pr_debug("%s work\n", otg_state_string(otg->phy->state)); switch (otg->phy->state) { case OTG_STATE_UNDEFINED: - dev_dbg(otg->phy->dev, "OTG_STATE_UNDEFINED state\n"); msm_otg_reset(otg->phy); msm_otg_init_sm(motg); + psy = power_supply_get_by_name("usb"); + if (!psy) + pr_err("couldn't get usb power supply\n"); otg->phy->state = OTG_STATE_B_IDLE; + if (!test_bit(B_SESS_VLD, &motg->inputs) && + test_bit(ID, &motg->inputs)) { + pm_runtime_put_noidle(otg->phy->dev); + pm_runtime_suspend(otg->phy->dev); + break; + } /* FALL THROUGH */ case OTG_STATE_B_IDLE: - dev_dbg(otg->phy->dev, "OTG_STATE_B_IDLE state\n"); - if (!test_bit(ID, &motg->inputs) && otg->host) { - /* disable BSV bit */ - writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); - msm_otg_start_host(otg->phy, 1); - otg->phy->state = OTG_STATE_A_HOST; + if ((!test_bit(ID, &motg->inputs) || + test_bit(ID_A, &motg->inputs)) && otg->host) { + pr_debug("!id || id_A\n"); + clear_bit(B_BUS_REQ, &motg->inputs); + set_bit(A_BUS_REQ, &motg->inputs); + otg->phy->state = OTG_STATE_A_IDLE; + work = 1; } else if (test_bit(B_SESS_VLD, &motg->inputs)) { + pr_debug("b_sess_vld\n"); switch (motg->chg_state) { case USB_CHG_STATE_UNDEFINED: msm_chg_detect_work(&motg->chg_work.work); @@ -1176,21 +2027,39 @@ static void msm_otg_sm_work(struct work_struct *w) case USB_CHG_STATE_DETECTED: switch (motg->chg_type) { case USB_DCP_CHARGER: + /* Enable VDP_SRC */ + ulpi_write(otg->phy, 0x2, 0x85); msm_otg_notify_charger(motg, IDEV_CHG_MAX); + pm_runtime_put_noidle(otg->phy->dev); + pm_runtime_suspend(otg->phy->dev); + break; + case USB_ACA_B_CHARGER: + msm_otg_notify_charger(motg, + IDEV_ACA_CHG_MAX); + /* + * (ID_B --> ID_C) PHY_ALT interrupt can + * not be detected in LPM. + */ break; case USB_CDP_CHARGER: msm_otg_notify_charger(motg, IDEV_CHG_MAX); - msm_otg_start_peripheral(otg->phy, 1); - otg->phy->state - = OTG_STATE_B_PERIPHERAL; + msm_otg_start_peripheral(otg, 1); + otg->phy->state = + OTG_STATE_B_PERIPHERAL; + break; + case USB_ACA_C_CHARGER: + msm_otg_notify_charger(motg, + IDEV_ACA_CHG_MAX); + msm_otg_start_peripheral(otg, 1); + otg->phy->state = + OTG_STATE_B_PERIPHERAL; break; case USB_SDP_CHARGER: - msm_otg_notify_charger(motg, IUNIT); - msm_otg_start_peripheral(otg->phy, 1); - otg->phy->state - = OTG_STATE_B_PERIPHERAL; + msm_otg_start_peripheral(otg, 1); + otg->phy->state = + OTG_STATE_B_PERIPHERAL; break; default: break; @@ -1199,93 +2068,692 @@ static void msm_otg_sm_work(struct work_struct *w) default: break; } - } else { - /* - * If charger detection work is pending, decrement - * the pm usage counter to balance with the one that - * is incremented in charger detection work. - */ - if (cancel_delayed_work_sync(&motg->chg_work)) { - pm_runtime_put_sync(otg->phy->dev); - msm_otg_reset(otg->phy); + } else if (test_bit(B_BUS_REQ, &motg->inputs)) { + pr_debug("b_sess_end && b_bus_req\n"); + if (msm_otg_start_srp(otg) < 0) { + clear_bit(B_BUS_REQ, &motg->inputs); + work = 1; + break; } - msm_otg_notify_charger(motg, 0); + otg->phy->state = OTG_STATE_B_SRP_INIT; + msm_otg_start_timer(motg, TB_SRP_FAIL, B_SRP_FAIL); + break; + } else { + pr_debug("chg_work cancel"); + cancel_delayed_work_sync(&motg->chg_work); motg->chg_state = USB_CHG_STATE_UNDEFINED; motg->chg_type = USB_INVALID_CHARGER; + msm_otg_notify_charger(motg, 0); + msm_otg_reset(otg->phy); + pm_runtime_put_noidle(otg->phy->dev); + pm_runtime_suspend(otg->phy->dev); + } + break; + case OTG_STATE_B_SRP_INIT: + if (!test_bit(ID, &motg->inputs) || + test_bit(ID_A, &motg->inputs) || + test_bit(ID_C, &motg->inputs) || + (test_bit(B_SESS_VLD, &motg->inputs) && + !test_bit(ID_B, &motg->inputs))) { + pr_debug("!id || id_a/c || b_sess_vld+!id_b\n"); + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_B_IDLE; + /* + * clear VBUSVLDEXTSEL and VBUSVLDEXT register + * bits after SRP initiation. + */ + ulpi_write(otg->phy, 0x0, 0x98); + work = 1; + } else if (test_bit(B_SRP_FAIL, &motg->tmouts)) { + pr_debug("b_srp_fail\n"); + pr_info("A-device did not respond to SRP\n"); + clear_bit(B_BUS_REQ, &motg->inputs); + clear_bit(B_SRP_FAIL, &motg->tmouts); + otg_send_event(OTG_EVENT_NO_RESP_FOR_SRP); + ulpi_write(otg->phy, 0x0, 0x98); + otg->phy->state = OTG_STATE_B_IDLE; + motg->b_last_se0_sess = jiffies; + work = 1; } - pm_runtime_put_sync(otg->phy->dev); break; case OTG_STATE_B_PERIPHERAL: - dev_dbg(otg->phy->dev, "OTG_STATE_B_PERIPHERAL state\n"); - if (!test_bit(B_SESS_VLD, &motg->inputs) || - !test_bit(ID, &motg->inputs)) { - msm_otg_notify_charger(motg, 0); - msm_otg_start_peripheral(otg->phy, 0); + if (!test_bit(ID, &motg->inputs) || + test_bit(ID_A, &motg->inputs) || + test_bit(ID_B, &motg->inputs) || + !test_bit(B_SESS_VLD, &motg->inputs)) { + pr_debug("!id || id_a/b || !b_sess_vld\n"); motg->chg_state = USB_CHG_STATE_UNDEFINED; motg->chg_type = USB_INVALID_CHARGER; + msm_otg_notify_charger(motg, 0); + srp_reqd = otg->gadget->otg_srp_reqd; + msm_otg_start_peripheral(otg, 0); + if (test_bit(ID_B, &motg->inputs)) + clear_bit(ID_B, &motg->inputs); + clear_bit(B_BUS_REQ, &motg->inputs); otg->phy->state = OTG_STATE_B_IDLE; - msm_otg_reset(otg->phy); - schedule_work(w); + motg->b_last_se0_sess = jiffies; + if (srp_reqd) + msm_otg_start_timer(motg, + TB_TST_SRP, B_TST_SRP); + else + work = 1; + } else if (test_bit(B_BUS_REQ, &motg->inputs) && + otg->gadget->b_hnp_enable && + test_bit(A_BUS_SUSPEND, &motg->inputs)) { + pr_debug("b_bus_req && b_hnp_en && a_bus_suspend\n"); + msm_otg_start_timer(motg, TB_ASE0_BRST, B_ASE0_BRST); + /* D+ pullup should not be disconnected within 4msec + * after A device suspends the bus. Otherwise PET will + * fail the compliance test. + */ + udelay(1000); + msm_otg_start_peripheral(otg, 0); + otg->phy->state = OTG_STATE_B_WAIT_ACON; + /* + * start HCD even before A-device enable + * pull-up to meet HNP timings. + */ + otg->host->is_b_host = 1; + msm_otg_start_host(otg, 1); + } else if (test_bit(A_BUS_SUSPEND, &motg->inputs) && + test_bit(B_SESS_VLD, &motg->inputs)) { + pr_debug("a_bus_suspend && b_sess_vld\n"); + if (motg->caps & ALLOW_LPM_ON_DEV_SUSPEND) { + pm_runtime_put_noidle(otg->phy->dev); + pm_runtime_suspend(otg->phy->dev); + } + } else if (test_bit(ID_C, &motg->inputs)) { + msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX); } break; - case OTG_STATE_A_HOST: - dev_dbg(otg->phy->dev, "OTG_STATE_A_HOST state\n"); - if (test_bit(ID, &motg->inputs)) { - msm_otg_start_host(otg->phy, 0); + case OTG_STATE_B_WAIT_ACON: + if (!test_bit(ID, &motg->inputs) || + test_bit(ID_A, &motg->inputs) || + test_bit(ID_B, &motg->inputs) || + !test_bit(B_SESS_VLD, &motg->inputs)) { + pr_debug("!id || id_a/b || !b_sess_vld\n"); + msm_otg_del_timer(motg); + /* + * A-device is physically disconnected during + * HNP. Remove HCD. + */ + msm_otg_start_host(otg, 0); + otg->host->is_b_host = 0; + + clear_bit(B_BUS_REQ, &motg->inputs); + clear_bit(A_BUS_SUSPEND, &motg->inputs); + motg->b_last_se0_sess = jiffies; otg->phy->state = OTG_STATE_B_IDLE; msm_otg_reset(otg->phy); - schedule_work(w); - } + work = 1; + } else if (test_bit(A_CONN, &motg->inputs)) { + pr_debug("a_conn\n"); + clear_bit(A_BUS_SUSPEND, &motg->inputs); + otg->phy->state = OTG_STATE_B_HOST; + /* + * PET disconnects D+ pullup after reset is generated + * by B device in B_HOST role which is not detected by + * B device. As workaorund , start timer of 300msec + * and stop timer if A device is enumerated else clear + * A_CONN. + */ + msm_otg_start_timer(motg, TB_TST_CONFIG, + B_TST_CONFIG); + } else if (test_bit(B_ASE0_BRST, &motg->tmouts)) { + pr_debug("b_ase0_brst_tmout\n"); + pr_info("B HNP fail:No response from A device\n"); + msm_otg_start_host(otg, 0); + msm_otg_reset(otg->phy); + otg->host->is_b_host = 0; + clear_bit(B_ASE0_BRST, &motg->tmouts); + clear_bit(A_BUS_SUSPEND, &motg->inputs); + clear_bit(B_BUS_REQ, &motg->inputs); + otg_send_event(OTG_EVENT_HNP_FAILED); + otg->phy->state = OTG_STATE_B_IDLE; + work = 1; + } else if (test_bit(ID_C, &motg->inputs)) { + msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX); + } + break; + case OTG_STATE_B_HOST: + if (!test_bit(B_BUS_REQ, &motg->inputs) || + !test_bit(A_CONN, &motg->inputs) || + !test_bit(B_SESS_VLD, &motg->inputs)) { + pr_debug("!b_bus_req || !a_conn || !b_sess_vld\n"); + clear_bit(A_CONN, &motg->inputs); + clear_bit(B_BUS_REQ, &motg->inputs); + msm_otg_start_host(otg, 0); + otg->host->is_b_host = 0; + otg->phy->state = OTG_STATE_B_IDLE; + msm_otg_reset(otg->phy); + work = 1; + } else if (test_bit(ID_C, &motg->inputs)) { + msm_otg_notify_charger(motg, IDEV_ACA_CHG_MAX); + } + break; + case OTG_STATE_A_IDLE: + otg->default_a = 1; + if (test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) { + pr_debug("id && !id_a\n"); + otg->default_a = 0; + clear_bit(A_BUS_DROP, &motg->inputs); + otg->phy->state = OTG_STATE_B_IDLE; + del_timer_sync(&motg->id_timer); + msm_otg_link_reset(motg); + msm_chg_enable_aca_intr(motg); + msm_otg_notify_charger(motg, 0); + work = 1; + } else if (!test_bit(A_BUS_DROP, &motg->inputs) && + (test_bit(A_SRP_DET, &motg->inputs) || + test_bit(A_BUS_REQ, &motg->inputs))) { + pr_debug("!a_bus_drop && (a_srp_det || a_bus_req)\n"); + + clear_bit(A_SRP_DET, &motg->inputs); + /* Disable SRP detection */ + writel_relaxed((readl_relaxed(USB_OTGSC) & + ~OTGSC_INTSTS_MASK) & + ~OTGSC_DPIE, USB_OTGSC); + + otg->phy->state = OTG_STATE_A_WAIT_VRISE; + /* VBUS should not be supplied before end of SRP pulse + * generated by PET, if not complaince test fail. + */ + usleep_range(10000, 12000); + /* ACA: ID_A: Stop charging untill enumeration */ + if (test_bit(ID_A, &motg->inputs)) + msm_otg_notify_charger(motg, 0); + else + msm_hsusb_vbus_power(motg, 1); + msm_otg_start_timer(motg, TA_WAIT_VRISE, A_WAIT_VRISE); + } else { + pr_debug("No session requested\n"); + clear_bit(A_BUS_DROP, &motg->inputs); + if (test_bit(ID_A, &motg->inputs)) { + msm_otg_notify_charger(motg, + IDEV_ACA_CHG_MAX); + } else if (!test_bit(ID, &motg->inputs)) { + msm_otg_notify_charger(motg, 0); + /* + * A-device is not providing power on VBUS. + * Enable SRP detection. + */ + writel_relaxed(0x13, USB_USBMODE); + writel_relaxed((readl_relaxed(USB_OTGSC) & + ~OTGSC_INTSTS_MASK) | + OTGSC_DPIE, USB_OTGSC); + mb(); + } + } + break; + case OTG_STATE_A_WAIT_VRISE: + if ((test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) || + test_bit(A_BUS_DROP, &motg->inputs) || + test_bit(A_WAIT_VRISE, &motg->tmouts)) { + pr_debug("id || a_bus_drop || a_wait_vrise_tmout\n"); + clear_bit(A_BUS_REQ, &motg->inputs); + msm_otg_del_timer(motg); + msm_hsusb_vbus_power(motg, 0); + otg->phy->state = OTG_STATE_A_WAIT_VFALL; + msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (test_bit(A_VBUS_VLD, &motg->inputs)) { + pr_debug("a_vbus_vld\n"); + otg->phy->state = OTG_STATE_A_WAIT_BCON; + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(motg, TA_WAIT_BCON, + A_WAIT_BCON); + msm_otg_start_host(otg, 1); + msm_chg_enable_aca_det(motg); + msm_chg_disable_aca_intr(motg); + mod_timer(&motg->id_timer, ID_TIMER_FREQ); + if (msm_chg_check_aca_intr(motg)) + work = 1; + } + break; + case OTG_STATE_A_WAIT_BCON: + if ((test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) || + test_bit(A_BUS_DROP, &motg->inputs) || + test_bit(A_WAIT_BCON, &motg->tmouts)) { + pr_debug("(id && id_a/b/c) || a_bus_drop ||" + "a_wait_bcon_tmout\n"); + if (test_bit(A_WAIT_BCON, &motg->tmouts)) { + pr_info("Device No Response\n"); + otg_send_event(OTG_EVENT_DEV_CONN_TMOUT); + } + msm_otg_del_timer(motg); + clear_bit(A_BUS_REQ, &motg->inputs); + clear_bit(B_CONN, &motg->inputs); + msm_otg_start_host(otg, 0); + /* + * ACA: ID_A with NO accessory, just the A plug is + * attached to ACA: Use IDCHG_MAX for charging + */ + if (test_bit(ID_A, &motg->inputs)) + msm_otg_notify_charger(motg, IDEV_CHG_MIN); + else + msm_hsusb_vbus_power(motg, 0); + otg->phy->state = OTG_STATE_A_WAIT_VFALL; + msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (!test_bit(A_VBUS_VLD, &motg->inputs)) { + pr_debug("!a_vbus_vld\n"); + clear_bit(B_CONN, &motg->inputs); + msm_otg_del_timer(motg); + msm_otg_start_host(otg, 0); + otg->phy->state = OTG_STATE_A_VBUS_ERR; + msm_otg_reset(otg->phy); + } else if (test_bit(ID_A, &motg->inputs)) { + msm_hsusb_vbus_power(motg, 0); + } else if (!test_bit(A_BUS_REQ, &motg->inputs)) { + /* + * If TA_WAIT_BCON is infinite, we don;t + * turn off VBUS. Enter low power mode. + */ + if (TA_WAIT_BCON < 0) + pm_runtime_put_sync(otg->phy->dev); + } else if (!test_bit(ID, &motg->inputs)) { + msm_hsusb_vbus_power(motg, 1); + } + break; + case OTG_STATE_A_HOST: + if ((test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) || + test_bit(A_BUS_DROP, &motg->inputs)) { + pr_debug("id_a/b/c || a_bus_drop\n"); + clear_bit(B_CONN, &motg->inputs); + clear_bit(A_BUS_REQ, &motg->inputs); + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_WAIT_VFALL; + msm_otg_start_host(otg, 0); + if (!test_bit(ID_A, &motg->inputs)) + msm_hsusb_vbus_power(motg, 0); + msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (!test_bit(A_VBUS_VLD, &motg->inputs)) { + pr_debug("!a_vbus_vld\n"); + clear_bit(B_CONN, &motg->inputs); + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_VBUS_ERR; + msm_otg_start_host(otg, 0); + msm_otg_reset(otg->phy); + } else if (!test_bit(A_BUS_REQ, &motg->inputs)) { + /* + * a_bus_req is de-asserted when root hub is + * suspended or HNP is in progress. + */ + pr_debug("!a_bus_req\n"); + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_SUSPEND; + if (otg->host->b_hnp_enable) + msm_otg_start_timer(motg, TA_AIDL_BDIS, + A_AIDL_BDIS); + else + pm_runtime_put_sync(otg->phy->dev); + } else if (!test_bit(B_CONN, &motg->inputs)) { + pr_debug("!b_conn\n"); + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_WAIT_BCON; + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(motg, TA_WAIT_BCON, + A_WAIT_BCON); + if (msm_chg_check_aca_intr(motg)) + work = 1; + } else if (test_bit(ID_A, &motg->inputs)) { + msm_otg_del_timer(motg); + msm_hsusb_vbus_power(motg, 0); + if (motg->chg_type == USB_ACA_DOCK_CHARGER) + msm_otg_notify_charger(motg, + IDEV_ACA_CHG_MAX); + else + msm_otg_notify_charger(motg, + IDEV_CHG_MIN - motg->mA_port); + } 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); + msm_hsusb_vbus_power(motg, 1); + } + break; + case OTG_STATE_A_SUSPEND: + if ((test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) || + test_bit(A_BUS_DROP, &motg->inputs) || + test_bit(A_AIDL_BDIS, &motg->tmouts)) { + pr_debug("id_a/b/c || a_bus_drop ||" + "a_aidl_bdis_tmout\n"); + msm_otg_del_timer(motg); + clear_bit(B_CONN, &motg->inputs); + otg->phy->state = OTG_STATE_A_WAIT_VFALL; + msm_otg_start_host(otg, 0); + msm_otg_reset(otg->phy); + if (!test_bit(ID_A, &motg->inputs)) + msm_hsusb_vbus_power(motg, 0); + msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (!test_bit(A_VBUS_VLD, &motg->inputs)) { + pr_debug("!a_vbus_vld\n"); + msm_otg_del_timer(motg); + clear_bit(B_CONN, &motg->inputs); + otg->phy->state = OTG_STATE_A_VBUS_ERR; + msm_otg_start_host(otg, 0); + msm_otg_reset(otg->phy); + } else if (!test_bit(B_CONN, &motg->inputs) && + otg->host->b_hnp_enable) { + pr_debug("!b_conn && b_hnp_enable"); + otg->phy->state = OTG_STATE_A_PERIPHERAL; + msm_otg_host_hnp_enable(otg, 1); + otg->gadget->is_a_peripheral = 1; + msm_otg_start_peripheral(otg, 1); + } else if (!test_bit(B_CONN, &motg->inputs) && + !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, &motg->inputs); + otg->phy->state = OTG_STATE_A_WAIT_BCON; + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(motg, TA_WAIT_BCON, + A_WAIT_BCON); + } else if (test_bit(ID_A, &motg->inputs)) { + msm_hsusb_vbus_power(motg, 0); + msm_otg_notify_charger(motg, + IDEV_CHG_MIN - motg->mA_port); + } else if (!test_bit(ID, &motg->inputs)) { + msm_otg_notify_charger(motg, 0); + msm_hsusb_vbus_power(motg, 1); + } + break; + case OTG_STATE_A_PERIPHERAL: + if ((test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) || + test_bit(A_BUS_DROP, &motg->inputs)) { + pr_debug("id _f/b/c || a_bus_drop\n"); + /* Clear BIDL_ADIS timer */ + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_WAIT_VFALL; + msm_otg_start_peripheral(otg, 0); + otg->gadget->is_a_peripheral = 0; + msm_otg_start_host(otg, 0); + msm_otg_reset(otg->phy); + if (!test_bit(ID_A, &motg->inputs)) + msm_hsusb_vbus_power(motg, 0); + msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (!test_bit(A_VBUS_VLD, &motg->inputs)) { + pr_debug("!a_vbus_vld\n"); + /* Clear BIDL_ADIS timer */ + msm_otg_del_timer(motg); + otg->phy->state = OTG_STATE_A_VBUS_ERR; + msm_otg_start_peripheral(otg, 0); + otg->gadget->is_a_peripheral = 0; + msm_otg_start_host(otg, 0); + } else if (test_bit(A_BIDL_ADIS, &motg->tmouts)) { + pr_debug("a_bidl_adis_tmout\n"); + msm_otg_start_peripheral(otg, 0); + otg->gadget->is_a_peripheral = 0; + otg->phy->state = OTG_STATE_A_WAIT_BCON; + set_bit(A_BUS_REQ, &motg->inputs); + msm_otg_host_hnp_enable(otg, 0); + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(motg, TA_WAIT_BCON, + A_WAIT_BCON); + } else if (test_bit(ID_A, &motg->inputs)) { + msm_hsusb_vbus_power(motg, 0); + msm_otg_notify_charger(motg, + IDEV_CHG_MIN - motg->mA_port); + } else if (!test_bit(ID, &motg->inputs)) { + msm_otg_notify_charger(motg, 0); + msm_hsusb_vbus_power(motg, 1); + } + break; + case OTG_STATE_A_WAIT_VFALL: + if (test_bit(A_WAIT_VFALL, &motg->tmouts)) { + clear_bit(A_VBUS_VLD, &motg->inputs); + otg->phy->state = OTG_STATE_A_IDLE; + work = 1; + } + break; + case OTG_STATE_A_VBUS_ERR: + if ((test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) || + test_bit(A_BUS_DROP, &motg->inputs) || + test_bit(A_CLR_ERR, &motg->inputs)) { + otg->phy->state = OTG_STATE_A_WAIT_VFALL; + if (!test_bit(ID_A, &motg->inputs)) + msm_hsusb_vbus_power(motg, 0); + msm_otg_start_timer(motg, TA_WAIT_VFALL, A_WAIT_VFALL); + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; + msm_otg_notify_charger(motg, 0); + } break; default: break; } + if (work) + queue_work(system_nrt_wq, &motg->sm_work); } static irqreturn_t msm_otg_irq(int irq, void *data) { struct msm_otg *motg = data; - struct usb_phy *phy = &motg->phy; - u32 otgsc = 0; + struct usb_otg *otg = motg->phy.otg; + u32 otgsc = 0, usbsts, pc; + bool work = 0; + irqreturn_t ret = IRQ_HANDLED; if (atomic_read(&motg->in_lpm)) { + pr_debug("OTG IRQ: in LPM\n"); disable_irq_nosync(irq); motg->async_int = 1; - pm_runtime_get(phy->dev); + if (atomic_read(&motg->pm_suspended)) + motg->sm_work_pending = true; + else + pm_request_resume(otg->phy->dev); return IRQ_HANDLED; } + usbsts = readl(USB_USBSTS); otgsc = readl(USB_OTGSC); - if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) + + if (!(otgsc & OTG_OTGSTS_MASK) && !(usbsts & OTG_USBSTS_MASK)) return IRQ_NONE; if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { - if (otgsc & OTGSC_ID) + if (otgsc & OTGSC_ID) { + pr_debug("Id set\n"); set_bit(ID, &motg->inputs); - else + } 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, &motg->inputs); clear_bit(ID, &motg->inputs); - dev_dbg(phy->dev, "ID set/clear\n"); - pm_runtime_get_noresume(phy->dev); - } else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { - if (otgsc & OTGSC_BSV) + msm_chg_enable_aca_det(motg); + } + writel_relaxed(otgsc, USB_OTGSC); + work = 1; + } else if (otgsc & OTGSC_DPIS) { + pr_debug("DPIS detected\n"); + writel_relaxed(otgsc, USB_OTGSC); + set_bit(A_SRP_DET, &motg->inputs); + set_bit(A_BUS_REQ, &motg->inputs); + work = 1; + } else if (otgsc & OTGSC_BSVIS) { + writel_relaxed(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 ((otg->phy->state >= OTG_STATE_A_IDLE) && + !test_bit(ID_A, &motg->inputs)) + return IRQ_HANDLED; + if (otgsc & OTGSC_BSV) { + pr_debug("BSV set\n"); set_bit(B_SESS_VLD, &motg->inputs); - else + } else { + pr_debug("BSV clear\n"); clear_bit(B_SESS_VLD, &motg->inputs); - dev_dbg(phy->dev, "BSV set/clear\n"); - pm_runtime_get_noresume(phy->dev); + clear_bit(A_BUS_SUSPEND, &motg->inputs); + + msm_chg_check_aca_intr(motg); + } + work = 1; + } else if (usbsts & STS_PCI) { + pc = readl_relaxed(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 (otg->phy->state) { + case OTG_STATE_A_SUSPEND: + if (otg->host->b_hnp_enable && (pc & PORTSC_CSC) && + !(pc & PORTSC_CCS)) { + pr_debug("B_CONN clear\n"); + clear_bit(B_CONN, &motg->inputs); + msm_otg_del_timer(motg); + } + break; + case OTG_STATE_A_PERIPHERAL: + /* + * A-peripheral observed activity on bus. + * clear A_BIDL_ADIS timer. + */ + msm_otg_del_timer(motg); + work = 0; + break; + case OTG_STATE_B_WAIT_ACON: + if ((pc & PORTSC_CSC) && (pc & PORTSC_CCS)) { + pr_debug("A_CONN set\n"); + set_bit(A_CONN, &motg->inputs); + /* Clear ASE0_BRST timer */ + msm_otg_del_timer(motg); + } + break; + case OTG_STATE_B_HOST: + if ((pc & PORTSC_CSC) && !(pc & PORTSC_CCS)) { + pr_debug("A_CONN clear\n"); + clear_bit(A_CONN, &motg->inputs); + msm_otg_del_timer(motg); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (TA_WAIT_BCON < 0) + set_bit(A_BUS_REQ, &motg->inputs); + default: + work = 0; + break; + } + } else if (usbsts & STS_URI) { + ret = IRQ_NONE; + switch (otg->phy->state) { + case OTG_STATE_A_PERIPHERAL: + /* + * A-peripheral observed activity on bus. + * clear A_BIDL_ADIS timer. + */ + msm_otg_del_timer(motg); + work = 0; + break; + default: + work = 0; + break; + } + } else if (usbsts & STS_SLI) { + ret = IRQ_NONE; + work = 0; + switch (otg->phy->state) { + case OTG_STATE_B_PERIPHERAL: + if (otg->gadget->b_hnp_enable) { + set_bit(A_BUS_SUSPEND, &motg->inputs); + set_bit(B_BUS_REQ, &motg->inputs); + work = 1; + } + break; + case OTG_STATE_A_PERIPHERAL: + msm_otg_start_timer(motg, TA_BIDL_ADIS, + A_BIDL_ADIS); + break; + default: + break; + } + } else if ((usbsts & PHY_ALT_INT)) { + writel_relaxed(PHY_ALT_INT, USB_USBSTS); + if (msm_chg_check_aca_intr(motg)) + work = 1; + ret = IRQ_HANDLED; + } + if (work) + queue_work(system_nrt_wq, &motg->sm_work); + + return ret; +} + +static void msm_otg_set_vbus_state(int online) +{ + static bool init; + struct msm_otg *motg = the_msm_otg; + + if (online) { + pr_debug("PMIC: BSV set\n"); + set_bit(B_SESS_VLD, &motg->inputs); + } else { + pr_debug("PMIC: BSV clear\n"); + clear_bit(B_SESS_VLD, &motg->inputs); + } + + if (!init) { + init = true; + complete(&pmic_vbus_init); + pr_debug("PMIC: BSV init complete\n"); + return; + } + + if (atomic_read(&motg->pm_suspended)) + motg->sm_work_pending = true; + else + queue_work(system_nrt_wq, &motg->sm_work); +} + +static irqreturn_t msm_pmic_id_irq(int irq, void *data) +{ + struct msm_otg *motg = data; + + if (aca_id_turned_on) + return IRQ_HANDLED; + + if (irq_read_line(motg->pdata->pmic_id_irq)) { + pr_debug("PMIC: ID set\n"); + set_bit(ID, &motg->inputs); + } else { + pr_debug("PMIC: ID clear\n"); + clear_bit(ID, &motg->inputs); + set_bit(A_BUS_REQ, &motg->inputs); + } + + if (motg->phy.state != OTG_STATE_UNDEFINED) { + if (atomic_read(&motg->pm_suspended)) + motg->sm_work_pending = true; + else + queue_work(system_nrt_wq, &motg->sm_work); } - writel(otgsc, USB_OTGSC); - schedule_work(&motg->sm_work); return IRQ_HANDLED; } static int msm_otg_mode_show(struct seq_file *s, void *unused) { struct msm_otg *motg = s->private; - struct usb_otg *otg = motg->phy.otg; + struct usb_phy *phy = &motg->phy; - switch (otg->phy->state) { + switch (phy->state) { case OTG_STATE_A_HOST: seq_printf(s, "host\n"); break; @@ -1311,7 +2779,7 @@ static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, struct seq_file *s = file->private_data; struct msm_otg *motg = s->private; char buf[16]; - struct usb_otg *otg = motg->phy.otg; + struct usb_phy *phy = &motg->phy; int status = count; enum usb_mode_type req_mode; @@ -1335,7 +2803,7 @@ static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, switch (req_mode) { case USB_NONE: - switch (otg->phy->state) { + switch (phy->state) { case OTG_STATE_A_HOST: case OTG_STATE_B_PERIPHERAL: set_bit(ID, &motg->inputs); @@ -1346,7 +2814,7 @@ static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, } break; case USB_PERIPHERAL: - switch (otg->phy->state) { + switch (phy->state) { case OTG_STATE_B_IDLE: case OTG_STATE_A_HOST: set_bit(ID, &motg->inputs); @@ -1357,7 +2825,7 @@ static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, } break; case USB_HOST: - switch (otg->phy->state) { + switch (phy->state) { case OTG_STATE_B_IDLE: case OTG_STATE_B_PERIPHERAL: clear_bit(ID, &motg->inputs); @@ -1370,8 +2838,8 @@ static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, goto out; } - pm_runtime_get_sync(otg->phy->dev); - schedule_work(&motg->sm_work); + pm_runtime_resume(phy->dev); + queue_work(system_nrt_wq, &motg->sm_work); out: return status; } @@ -1384,31 +2852,320 @@ const struct file_operations msm_otg_mode_fops = { .release = single_release, }; +static int msm_otg_show_otg_state(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + struct usb_phy *phy = &motg->phy; + + seq_printf(s, "%s\n", otg_state_string(phy->state)); + return 0; +} + +static int msm_otg_otg_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_show_otg_state, inode->i_private); +} + +const struct file_operations msm_otg_state_fops = { + .open = msm_otg_otg_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int msm_otg_show_chg_type(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + + seq_printf(s, "%s\n", chg_to_string(motg->chg_type)); + return 0; +} + +static int msm_otg_chg_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_show_chg_type, inode->i_private); +} + +const struct file_operations msm_otg_chg_fops = { + .open = msm_otg_chg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int msm_otg_aca_show(struct seq_file *s, void *unused) +{ + if (debug_aca_enabled) + seq_printf(s, "enabled\n"); + else + seq_printf(s, "disabled\n"); + + return 0; +} + +static int msm_otg_aca_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_aca_show, inode->i_private); +} + +static ssize_t msm_otg_aca_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + char buf[8]; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "enable", 6)) + debug_aca_enabled = true; + else + debug_aca_enabled = false; + + return count; +} + +const struct file_operations msm_otg_aca_fops = { + .open = msm_otg_aca_open, + .read = seq_read, + .write = msm_otg_aca_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int msm_otg_bus_show(struct seq_file *s, void *unused) +{ + if (debug_bus_voting_enabled) + seq_printf(s, "enabled\n"); + else + seq_printf(s, "disabled\n"); + + return 0; +} + +static int msm_otg_bus_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_bus_show, inode->i_private); +} + +static ssize_t msm_otg_bus_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + char buf[8]; + int ret; + struct seq_file *s = file->private_data; + struct msm_otg *motg = s->private; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "enable", 6)) { + /* Do not vote here. Let OTG statemachine decide when to vote */ + debug_bus_voting_enabled = true; + } else { + debug_bus_voting_enabled = false; + if (motg->bus_perf_client) { + ret = msm_bus_scale_client_update_request( + motg->bus_perf_client, 0); + if (ret) + dev_err(motg->phy.dev, "%s: Failed to devote " + "for bus bw %d\n", __func__, ret); + } + } + + return count; +} + +const struct file_operations msm_otg_bus_fops = { + .open = msm_otg_bus_open, + .read = seq_read, + .write = msm_otg_bus_write, + .llseek = seq_lseek, + .release = single_release, +}; + static struct dentry *msm_otg_dbg_root; -static struct dentry *msm_otg_dbg_mode; static int msm_otg_debugfs_init(struct msm_otg *motg) { + struct dentry *msm_otg_dentry; + msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) return -ENODEV; - msm_otg_dbg_mode = debugfs_create_file("mode", S_IRUGO | S_IWUSR, - msm_otg_dbg_root, motg, &msm_otg_mode_fops); - if (!msm_otg_dbg_mode) { - debugfs_remove(msm_otg_dbg_root); - msm_otg_dbg_root = NULL; + if (motg->pdata->mode == USB_OTG && + motg->pdata->otg_control == OTG_USER_CONTROL) { + + msm_otg_dentry = debugfs_create_file("mode", S_IRUGO | + S_IWUSR, msm_otg_dbg_root, motg, + &msm_otg_mode_fops); + + if (!msm_otg_dentry) { + debugfs_remove(msm_otg_dbg_root); + msm_otg_dbg_root = NULL; + return -ENODEV; + } + } + + msm_otg_dentry = debugfs_create_file("chg_type", S_IRUGO, + msm_otg_dbg_root, motg, + &msm_otg_chg_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); + return -ENODEV; + } + + msm_otg_dentry = debugfs_create_file("aca", S_IRUGO | S_IWUSR, + msm_otg_dbg_root, motg, + &msm_otg_aca_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); + return -ENODEV; + } + + msm_otg_dentry = debugfs_create_file("bus_voting", S_IRUGO | S_IWUSR, + msm_otg_dbg_root, motg, + &msm_otg_bus_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); return -ENODEV; } + msm_otg_dentry = debugfs_create_file("otg_state", S_IRUGO, + msm_otg_dbg_root, motg, &msm_otg_state_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); + return -ENODEV; + } return 0; } static void msm_otg_debugfs_cleanup(void) { - debugfs_remove(msm_otg_dbg_mode); - debugfs_remove(msm_otg_dbg_root); + debugfs_remove_recursive(msm_otg_dbg_root); +} + +static u64 msm_otg_dma_mask = DMA_BIT_MASK(64); +static struct platform_device *msm_otg_add_pdev( + struct platform_device *ofdev, const char *name) +{ + struct platform_device *pdev; + const struct resource *res = ofdev->resource; + unsigned int num = ofdev->num_resources; + int retval; + + pdev = platform_device_alloc(name, -1); + if (!pdev) { + retval = -ENOMEM; + goto error; + } + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &msm_otg_dma_mask; + + if (num) { + retval = platform_device_add_resources(pdev, res, num); + if (retval) + goto error; + } + + retval = platform_device_add(pdev); + if (retval) + goto error; + + return pdev; + +error: + platform_device_put(pdev); + return ERR_PTR(retval); +} + +static int msm_otg_setup_devices(struct platform_device *ofdev, + enum usb_mode_type mode, bool init) +{ + const char *gadget_name = "msm_hsusb"; + const char *host_name = "msm_hsusb_host"; + static struct platform_device *gadget_pdev; + static struct platform_device *host_pdev; + int retval = 0; + + if (!init) { + if (gadget_pdev) + platform_device_unregister(gadget_pdev); + if (host_pdev) + platform_device_unregister(host_pdev); + return 0; + } + + switch (mode) { + case USB_OTG: + /* fall through */ + case USB_PERIPHERAL: + gadget_pdev = msm_otg_add_pdev(ofdev, gadget_name); + if (IS_ERR(gadget_pdev)) { + retval = PTR_ERR(gadget_pdev); + break; + } + if (mode == USB_PERIPHERAL) + break; + /* fall through */ + case USB_HOST: + host_pdev = msm_otg_add_pdev(ofdev, host_name); + if (IS_ERR(host_pdev)) { + retval = PTR_ERR(host_pdev); + if (mode == USB_OTG) + platform_device_unregister(gadget_pdev); + } + break; + default: + break; + } + + return retval; +} + +struct msm_otg_platform_data *msm_otg_dt_to_pdata(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct msm_otg_platform_data *pdata; + int len = 0; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + pr_err("unable to allocate platform data\n"); + return NULL; + } + of_get_property(node, "qcom,hsusb-otg-phy-init-seq", &len); + if (len) { + pdata->phy_init_seq = devm_kzalloc(&pdev->dev, len, GFP_KERNEL); + if (!pdata->phy_init_seq) + return NULL; + of_property_read_u32_array(node, "qcom,hsusb-otg-phy-init-seq", + pdata->phy_init_seq, + len/sizeof(*pdata->phy_init_seq)); + } + of_property_read_u32(node, "qcom,hsusb-otg-power-budget", + &pdata->power_budget); + of_property_read_u32(node, "qcom,hsusb-otg-mode", + &pdata->mode); + of_property_read_u32(node, "qcom,hsusb-otg-otg-control", + &pdata->otg_control); + of_property_read_u32(node, "qcom,hsusb-otg-default-mode", + &pdata->default_mode); + of_property_read_u32(node, "qcom,hsusb-otg-phy-type", + &pdata->phy_type); + of_property_read_u32(node, "qcom,hsusb-otg-pmic-id-irq", + &pdata->pmic_id_irq); + return pdata; } static int __init msm_otg_probe(struct platform_device *pdev) @@ -1417,11 +3174,25 @@ static int __init msm_otg_probe(struct platform_device *pdev) struct resource *res; struct msm_otg *motg; struct usb_phy *phy; + struct msm_otg_platform_data *pdata; dev_info(&pdev->dev, "msm_otg probe\n"); - if (!pdev->dev.platform_data) { + + if (pdev->dev.of_node) { + dev_dbg(&pdev->dev, "device tree enabled\n"); + pdata = msm_otg_dt_to_pdata(pdev); + if (!pdata) + return -ENOMEM; + ret = msm_otg_setup_devices(pdev, pdata->mode, true); + if (ret) { + dev_err(&pdev->dev, "devices setup failed\n"); + return ret; + } + } else if (!pdev->dev.platform_data) { dev_err(&pdev->dev, "No platform data given. Bailing out\n"); return -ENODEV; + } else { + pdata = pdev->dev.platform_data; } motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); @@ -1432,75 +3203,80 @@ static int __init msm_otg_probe(struct platform_device *pdev) motg->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); if (!motg->phy.otg) { - dev_err(&pdev->dev, "unable to allocate msm_otg\n"); - return -ENOMEM; + dev_err(&pdev->dev, "unable to allocate usb_otg\n"); + ret = -ENOMEM; + goto free_motg; } - motg->pdata = pdev->dev.platform_data; + the_msm_otg = motg; + motg->pdata = pdata; phy = &motg->phy; phy->dev = &pdev->dev; - motg->phy_reset_clk = clk_get(&pdev->dev, "usb_phy_clk"); - if (IS_ERR(motg->phy_reset_clk)) { - dev_err(&pdev->dev, "failed to get usb_phy_clk\n"); - ret = PTR_ERR(motg->phy_reset_clk); - goto free_motg; + /* + * ACA ID_GND threshold range is overlapped with OTG ID_FLOAT. Hence + * PHY treat ACA ID_GND as float and no interrupt is generated. But + * PMIC can detect ACA ID_GND and generate an interrupt. + */ + if (aca_enabled() && motg->pdata->otg_control != OTG_PMIC_CONTROL) { + dev_err(&pdev->dev, "ACA can not be enabled without PMIC\n"); + ret = -EINVAL; + goto free_otg; } - motg->clk = clk_get(&pdev->dev, "usb_hs_clk"); - if (IS_ERR(motg->clk)) { - dev_err(&pdev->dev, "failed to get usb_hs_clk\n"); - ret = PTR_ERR(motg->clk); - goto put_phy_reset_clk; - } - clk_set_rate(motg->clk, 60000000); + /* initialize reset counter */ + motg->reset_counter = 0; + + /* Some targets don't support PHY clock. */ + motg->phy_reset_clk = clk_get(&pdev->dev, "phy_clk"); + if (IS_ERR(motg->phy_reset_clk)) + dev_err(&pdev->dev, "failed to get phy_clk\n"); + + /* + * Targets on which link uses asynchronous reset methodology, + * free running clock is not required during the reset. + */ + motg->clk = clk_get(&pdev->dev, "alt_core_clk"); + if (IS_ERR(motg->clk)) + dev_dbg(&pdev->dev, "alt_core_clk is not present\n"); + else + clk_set_rate(motg->clk, 60000000); /* - * If USB Core is running its protocol engine based on CORE CLK, + * USB Core is running its protocol engine based on CORE CLK, * CORE CLK must be running at >55Mhz for correct HSUSB * operation and USB core cannot tolerate frequency changes on * CORE CLK. For such USB cores, vote for maximum clk frequency * on pclk source */ - if (motg->pdata->pclk_src_name) { - motg->pclk_src = clk_get(&pdev->dev, - motg->pdata->pclk_src_name); - if (IS_ERR(motg->pclk_src)) - goto put_clk; - clk_set_rate(motg->pclk_src, INT_MAX); - clk_enable(motg->pclk_src); - } else - motg->pclk_src = ERR_PTR(-ENOENT); - - - motg->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); + motg->core_clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(motg->core_clk)) { + motg->core_clk = NULL; + dev_err(&pdev->dev, "failed to get core_clk\n"); + ret = PTR_ERR(motg->core_clk); + goto put_clk; + } + clk_set_rate(motg->core_clk, INT_MAX); + + motg->pclk = clk_get(&pdev->dev, "iface_clk"); if (IS_ERR(motg->pclk)) { - dev_err(&pdev->dev, "failed to get usb_hs_pclk\n"); + dev_err(&pdev->dev, "failed to get iface_clk\n"); ret = PTR_ERR(motg->pclk); - goto put_pclk_src; + goto put_core_clk; } - /* - * USB core clock is not present on all MSM chips. This - * clock is introduced to remove the dependency on AXI - * bus frequency. - */ - motg->core_clk = clk_get(&pdev->dev, "usb_hs_core_clk"); - if (IS_ERR(motg->core_clk)) - motg->core_clk = NULL; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "failed to get platform resource mem\n"); ret = -ENODEV; - goto put_core_clk; + goto put_pclk; } motg->regs = ioremap(res->start, resource_size(res)); if (!motg->regs) { dev_err(&pdev->dev, "ioremap failed\n"); ret = -ENOMEM; - goto put_core_clk; + goto put_pclk; } dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs); @@ -1511,49 +3287,97 @@ static int __init msm_otg_probe(struct platform_device *pdev) goto free_regs; } - clk_enable(motg->clk); - clk_enable(motg->pclk); + motg->xo_handle = msm_xo_get(MSM_XO_TCXO_D0, "usb"); + if (IS_ERR(motg->xo_handle)) { + dev_err(&pdev->dev, "%s not able to get the handle " + "to vote for TCXO D0 buffer\n", __func__); + ret = PTR_ERR(motg->xo_handle); + goto free_regs; + } + + ret = msm_xo_mode_vote(motg->xo_handle, MSM_XO_MODE_ON); + if (ret) { + dev_err(&pdev->dev, "%s failed to vote for TCXO " + "D0 buffer%d\n", __func__, ret); + goto free_xo_handle; + } + + clk_prepare_enable(motg->pclk); - ret = msm_hsusb_init_vddcx(motg, 1); + motg->vdd_type = VDDCX_CORNER; + hsusb_vddcx = devm_regulator_get(motg->phy.dev, "hsusb_vdd_dig"); + if (IS_ERR(hsusb_vddcx)) { + hsusb_vddcx = devm_regulator_get(motg->phy.dev, "HSUSB_VDDCX"); + if (IS_ERR(hsusb_vddcx)) { + dev_err(motg->phy.dev, "unable to get hsusb vddcx\n"); + goto devote_xo_handle; + } + motg->vdd_type = VDDCX; + } + + ret = msm_hsusb_config_vddcx(1); if (ret) { dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); - goto free_regs; + goto devote_xo_handle; + } + + ret = regulator_enable(hsusb_vddcx); + if (ret) { + dev_err(&pdev->dev, "unable to enable the hsusb vddcx\n"); + goto free_config_vddcx; } ret = msm_hsusb_ldo_init(motg, 1); if (ret) { dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); - goto vddcx_exit; + goto free_hsusb_vddcx; + } + + if (pdata->mhl_enable) { + mhl_analog_switch = devm_regulator_get(motg->phy.dev, + "mhl_ext_3p3v"); + if (IS_ERR(mhl_analog_switch)) { + dev_err(&pdev->dev, "Unable to get mhl_analog_switch\n"); + goto free_ldo_init; + } } - 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) - clk_enable(motg->core_clk); + clk_prepare_enable(motg->core_clk); 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"); + msm_otg_init_timer(motg); INIT_WORK(&motg->sm_work, msm_otg_sm_work); INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); + setup_timer(&motg->id_timer, msm_otg_id_timer_func, + (unsigned long) motg); 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; } phy->init = msm_otg_reset; phy->set_power = msm_otg_set_power; + phy->set_suspend = msm_otg_set_suspend; phy->io_ops = &msm_otg_io_ops; phy->otg->phy = &motg->phy; phy->otg->set_host = msm_otg_set_host; phy->otg->set_peripheral = msm_otg_set_peripheral; + phy->otg->start_hnp = msm_otg_start_hnp; + phy->otg->start_srp = msm_otg_start_srp; ret = usb_set_transceiver(&motg->phy); if (ret) { @@ -1561,47 +3385,104 @@ static int __init msm_otg_probe(struct platform_device *pdev) goto free_irq; } + if (motg->pdata->mode == USB_OTG && + 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_phy; + } + } else { + ret = -ENODEV; + dev_err(&pdev->dev, "PMIC IRQ for ID notifications doesn't exist\n"); + goto remove_phy; + } + } + + msm_hsusb_mhl_switch_enable(motg, 1); + 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) { - ret = msm_otg_debugfs_init(motg); - if (ret) - dev_dbg(&pdev->dev, "mode debugfs file is" - "not available\n"); + ret = msm_otg_debugfs_init(motg); + if (ret) + dev_dbg(&pdev->dev, "mode debugfs file is" + "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) { + if (motg->pdata->otg_control == OTG_PMIC_CONTROL && + (!(motg->pdata->mode == USB_OTG) || + motg->pdata->pmic_id_irq)) + motg->caps = ALLOW_PHY_POWER_COLLAPSE | + ALLOW_PHY_RETENTION; + + if (motg->pdata->otg_control == OTG_PHY_CONTROL) + motg->caps = ALLOW_PHY_RETENTION; } + if (motg->pdata->enable_lpm_on_dev_suspend) + motg->caps |= ALLOW_LPM_ON_DEV_SUSPEND; + + wake_lock(&motg->wlock); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); + if (motg->pdata->bus_scale_table) { + motg->bus_perf_client = + msm_bus_scale_register_client(motg->pdata->bus_scale_table); + if (!motg->bus_perf_client) + dev_err(motg->phy.dev, "%s: Failed to register BUS " + "scaling client!!\n", __func__); + else + debug_bus_voting_enabled = true; + } + return 0; + +remove_phy: + usb_set_transceiver(NULL); free_irq: free_irq(motg->irq, motg); -disable_clks: - clk_disable(motg->pclk); - clk_disable(motg->clk); -ldo_exit: +destroy_wlock: + wake_lock_destroy(&motg->wlock); + clk_disable_unprepare(motg->core_clk); + msm_hsusb_ldo_enable(motg, 0); +free_ldo_init: msm_hsusb_ldo_init(motg, 0); -vddcx_exit: - msm_hsusb_init_vddcx(motg, 0); +free_hsusb_vddcx: + regulator_disable(hsusb_vddcx); +free_config_vddcx: + regulator_set_voltage(hsusb_vddcx, + vdd_val[motg->vdd_type][VDD_NONE], + vdd_val[motg->vdd_type][VDD_MAX]); +devote_xo_handle: + clk_disable_unprepare(motg->pclk); + msm_xo_mode_vote(motg->xo_handle, MSM_XO_MODE_OFF); +free_xo_handle: + msm_xo_put(motg->xo_handle); free_regs: iounmap(motg->regs); -put_core_clk: - if (motg->core_clk) - clk_put(motg->core_clk); +put_pclk: clk_put(motg->pclk); -put_pclk_src: - if (!IS_ERR(motg->pclk_src)) { - clk_disable(motg->pclk_src); - clk_put(motg->pclk_src); - } +put_core_clk: + clk_put(motg->core_clk); put_clk: - clk_put(motg->clk); -put_phy_reset_clk: - clk_put(motg->phy_reset_clk); -free_motg: + if (!IS_ERR(motg->clk)) + clk_put(motg->clk); + if (!IS_ERR(motg->phy_reset_clk)) + clk_put(motg->phy_reset_clk); +free_otg: kfree(motg->phy.otg); +free_motg: kfree(motg); return ret; } @@ -1609,12 +3490,16 @@ static int __init msm_otg_probe(struct platform_device *pdev) static int __devexit msm_otg_remove(struct platform_device *pdev) { struct msm_otg *motg = platform_get_drvdata(pdev); - struct usb_phy *phy = &motg->phy; + struct usb_otg *otg = motg->phy.otg; int cnt = 0; - if (phy->otg->host || phy->otg->gadget) + if (otg->host || otg->gadget) return -EBUSY; + if (pdev->dev.of_node) + msm_otg_setup_devices(pdev, motg->pdata->mode, false); + 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); @@ -1623,15 +3508,19 @@ 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); + msm_hsusb_mhl_switch_enable(motg, 0); + if (motg->pdata->pmic_id_irq) + free_irq(motg->pdata->pmic_id_irq, motg); usb_set_transceiver(NULL); free_irq(motg->irq, motg); /* * Put PHY in low power mode. */ - ulpi_read(phy, 0x14); - ulpi_write(phy, 0x08, 0x09); + ulpi_read(otg->phy, 0x14); + ulpi_write(otg->phy, 0x08, 0x09); writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { @@ -1641,30 +3530,33 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) cnt++; } if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) - dev_err(phy->dev, "Unable to suspend PHY\n"); + dev_err(otg->phy->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); - } + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); + msm_xo_put(motg->xo_handle); + msm_hsusb_ldo_enable(motg, 0); msm_hsusb_ldo_init(motg, 0); + regulator_disable(hsusb_vddcx); + regulator_set_voltage(hsusb_vddcx, + vdd_val[motg->vdd_type][VDD_NONE], + vdd_val[motg->vdd_type][VDD_MAX]); iounmap(motg->regs); pm_runtime_set_suspended(&pdev->dev); - clk_put(motg->phy_reset_clk); + if (!IS_ERR(motg->phy_reset_clk)) + clk_put(motg->phy_reset_clk); clk_put(motg->pclk); - clk_put(motg->clk); - if (motg->core_clk) - clk_put(motg->core_clk); + if (!IS_ERR(motg->clk)) + clk_put(motg->clk); + clk_put(motg->core_clk); + + if (motg->bus_perf_client) + msm_bus_scale_unregister_client(motg->bus_perf_client); kfree(motg->phy.otg); kfree(motg); - return 0; } @@ -1672,20 +3564,14 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) static int msm_otg_runtime_idle(struct device *dev) { struct msm_otg *motg = dev_get_drvdata(dev); - struct usb_otg *otg = motg->phy.otg; + struct usb_phy *phy = &motg->phy; dev_dbg(dev, "OTG runtime idle\n"); - /* - * It is observed some times that a spurious interrupt - * comes when PHY is put into LPM immediately after PHY reset. - * This 1 sec delay also prevents entering into LPM immediately - * after asynchronous interrupt. - */ - if (otg->phy->state != OTG_STATE_UNDEFINED) - pm_schedule_suspend(dev, 1000); - - return -EAGAIN; + if (phy->state == OTG_STATE_UNDEFINED) + return -EAGAIN; + else + return 0; } static int msm_otg_runtime_suspend(struct device *dev) @@ -1701,6 +3587,7 @@ static int msm_otg_runtime_resume(struct device *dev) struct msm_otg *motg = dev_get_drvdata(dev); dev_dbg(dev, "OTG runtime resume\n"); + pm_runtime_get_noresume(dev); return msm_otg_resume(motg); } #endif @@ -1708,32 +3595,42 @@ static int msm_otg_runtime_resume(struct device *dev) #ifdef CONFIG_PM_SLEEP static int msm_otg_pm_suspend(struct device *dev) { + int ret = 0; struct msm_otg *motg = dev_get_drvdata(dev); dev_dbg(dev, "OTG PM suspend\n"); - return msm_otg_suspend(motg); + + atomic_set(&motg->pm_suspended, 1); + ret = msm_otg_suspend(motg); + if (ret) + atomic_set(&motg->pm_suspended, 0); + + return ret; } static int msm_otg_pm_resume(struct device *dev) { + int ret = 0; struct msm_otg *motg = dev_get_drvdata(dev); - int ret; dev_dbg(dev, "OTG PM resume\n"); - ret = msm_otg_resume(motg); - if (ret) - return ret; + atomic_set(&motg->pm_suspended, 0); + if (motg->sm_work_pending) { + motg->sm_work_pending = false; - /* - * Runtime PM Documentation recommends bringing the - * device to full powered state upon resume. - */ - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); + pm_runtime_get_noresume(dev); + ret = msm_otg_resume(motg); - return 0; + /* Update runtime PM status */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + queue_work(system_nrt_wq, &motg->sm_work); + } + + return ret; } #endif @@ -1745,6 +3642,12 @@ static const struct dev_pm_ops msm_otg_dev_pm_ops = { }; #endif +static struct of_device_id msm_otg_dt_match[] = { + { .compatible = "qcom,hsusb-otg", + }, + {} +}; + static struct platform_driver msm_otg_driver = { .remove = __devexit_p(msm_otg_remove), .driver = { @@ -1753,6 +3656,7 @@ static struct platform_driver msm_otg_driver = { #ifdef CONFIG_PM .pm = &msm_otg_dev_pm_ops, #endif + .of_match_table = msm_otg_dt_match, }, }; diff --git a/drivers/usb/otg/otg.c b/drivers/usb/otg/otg.c index 801e597a15413ece58964805e494c959315b26a1..cf8676f325ff81a9b380a567a6e9b6676a85a535 100644 --- a/drivers/usb/otg/otg.c +++ b/drivers/usb/otg/otg.c @@ -100,3 +100,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 usb_phy *phy = usb_get_transceiver(); + int ret = -ENOTSUPP; + + if (phy && phy->otg && phy->otg->send_event) + ret = phy->otg->send_event(phy->otg, event); + + if (phy) + usb_put_transceiver(phy); + + return ret; +} +EXPORT_SYMBOL(otg_send_event); diff --git a/drivers/usb/otg/otg_id.c b/drivers/usb/otg/otg_id.c new file mode 100644 index 0000000000000000000000000000000000000000..7c38390ffe94168214d8ed5b1c0d27cf5260f5b0 --- /dev/null +++ b/drivers/usb/otg/otg_id.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * Author: + * Colin Cross + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +static DEFINE_MUTEX(otg_id_lock); +static struct plist_head otg_id_plist = + PLIST_HEAD_INIT(otg_id_plist); +static struct otg_id_notifier_block *otg_id_active; +static bool otg_id_cancelling; +static bool otg_id_inited; +static int otg_id_suspended; +static bool otg_id_pending; + +static void otg_id_cancel(void) +{ + if (otg_id_active) { + otg_id_cancelling = true; + mutex_unlock(&otg_id_lock); + + otg_id_active->cancel(otg_id_active); + + mutex_lock(&otg_id_lock); + otg_id_cancelling = false; + } +} + +static void __otg_id_notify(void) +{ + int ret = 0; + struct otg_id_notifier_block *otg_id_nb; + bool proxy_wait = false; + if (plist_head_empty(&otg_id_plist)) + return; + + plist_for_each_entry(otg_id_nb, &otg_id_plist, p) { + if (proxy_wait) { + if (otg_id_nb->proxy_wait) + ret = otg_id_nb->proxy_wait(otg_id_nb); + } else { + ret = otg_id_nb->detect(otg_id_nb); + } + if (ret == OTG_ID_HANDLED) { + otg_id_active = otg_id_nb; + return; + } + if (ret == OTG_ID_PROXY_WAIT) + proxy_wait = true; + + } + + WARN(1, "otg id event not handled"); + otg_id_active = NULL; +} + +int otg_id_init(void) +{ + mutex_lock(&otg_id_lock); + + otg_id_inited = true; + __otg_id_notify(); + + mutex_unlock(&otg_id_lock); + return 0; +} +late_initcall(otg_id_init); + +/** + * otg_id_register_notifier + * @otg_id_nb: notifier block containing priority and callback function + * + * Register a notifier that will be called on any USB cable state change. + * The priority determines the order the callback will be called in, a higher + * number will be called first. A callback function needs to determine the + * type of USB cable that is connected. If it can determine the type, it + * should notify the appropriate drivers (for example, call an otg notifier + * with USB_EVENT_VBUS), and return OTG_ID_HANDLED. Once a callback has + * returned OTG_ID_HANDLED, it is responsible for calling otg_id_notify() when + * the detected USB cable is disconnected. + */ +int otg_id_register_notifier(struct otg_id_notifier_block *otg_id_nb) +{ + plist_node_init(&otg_id_nb->p, otg_id_nb->priority); + + mutex_lock(&otg_id_lock); + plist_add(&otg_id_nb->p, &otg_id_plist); + + if (otg_id_inited) { + otg_id_cancel(); + __otg_id_notify(); + } + + mutex_unlock(&otg_id_lock); + + return 0; +} + +void otg_id_unregister_notifier(struct otg_id_notifier_block *otg_id_nb) +{ + mutex_lock(&otg_id_lock); + + plist_del(&otg_id_nb->p, &otg_id_plist); + + if (otg_id_inited && (otg_id_active == otg_id_nb)) { + otg_id_cancel(); + __otg_id_notify(); + } + + mutex_unlock(&otg_id_lock); +} + +/** + * otg_id_notify + * + * Notify listeners on any USB cable state change. + * + * A driver may only call otg_id_notify if it returned OTG_ID_HANDLED the last + * time it's notifier was called, and it's cancel function has not been called. + */ +void otg_id_notify(void) +{ + mutex_lock(&otg_id_lock); + + if (otg_id_cancelling) + goto out; + + if (otg_id_suspended != 0) { + otg_id_pending = true; + goto out; + } + + __otg_id_notify(); +out: + mutex_unlock(&otg_id_lock); +} + +/** + * otg_id_suspend + * + * Mark the otg_id subsystem as going into suspend. From here on out, + * any notifications will be deferred until the last otg_id client resumes. + * If there is a pending notification when calling this function, it will + * return a negative errno and expects that the caller will abort suspend. + * Returs 0 on success. + */ +int otg_id_suspend(void) +{ + int ret = 0; + + mutex_lock(&otg_id_lock); + + /* + * if there's a pending notification, tell the caller to abort suspend + */ + if (otg_id_suspended != 0 && otg_id_pending) { + pr_info("otg_id: pending notification, should abort suspend\n"); + ret = -EBUSY; + goto out; + } + + otg_id_suspended++; +out: + mutex_unlock(&otg_id_lock); + return ret; +} + +/** + * otg_id_resume + * + * Inform the otg_id subsystem that a client is resuming. If this is the + * last client to be resumed and there's a pending notification, + * otg_id_notify() is called. + */ +void otg_id_resume(void) +{ + mutex_lock(&otg_id_lock); + if (WARN(!otg_id_suspended, "unbalanced otg_id_resume\n")) + goto out; + if (--otg_id_suspended == 0) { + if (otg_id_pending) { + pr_info("otg_id: had pending notification\n"); + otg_id_pending = false; + __otg_id_notify(); + } + } +out: + mutex_unlock(&otg_id_lock); +} diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 7141d6599060c3a6fa6bd428b589b20527d0f211..90b68d19babdc7e2dbe57dd5cc40e7b41b49037c 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -669,6 +669,15 @@ config USB_SERIAL_SSU100 To compile this driver as a module, choose M here: the module will be called ssu100. +config USB_SERIAL_CSVT + tristate "USB serial driver for Circuit-Switched Video Telephony" + help + Say Y here if you want to use usb serial driver for Circuit-Switched + Video Telephony + + To compile this driver as a module, choose M here: the + module will be called csvt. + config USB_SERIAL_DEBUG tristate "USB Debugging Device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 07f198ee0486f503778a966471b0cfb5bfb92050..708bdbbcde89fcd458f9495b2683a44a748b6d6d 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -62,3 +62,4 @@ obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o obj-$(CONFIG_USB_SERIAL_VIVOPAY_SERIAL) += vivopay-serial.o obj-$(CONFIG_USB_SERIAL_ZIO) += zio.o +obj-$(CONFIG_USB_SERIAL_CSVT) += csvt.o diff --git a/drivers/usb/serial/csvt.c b/drivers/usb/serial/csvt.c new file mode 100644 index 0000000000000000000000000000000000000000..3efdd77c75d7e53711e7749a571cd76012df57d5 --- /dev/null +++ b/drivers/usb/serial/csvt.c @@ -0,0 +1,443 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + + +/* output control lines*/ +#define CSVT_CTRL_DTR 0x01 +#define CSVT_CTRL_RTS 0x02 + +/* input control lines*/ +#define CSVT_CTRL_CTS 0x01 +#define CSVT_CTRL_DSR 0x02 +#define CSVT_CTRL_RI 0x04 +#define CSVT_CTRL_CD 0x08 + +static int debug; +module_param(debug, int, S_IRUGO | S_IWUSR); + +struct csvt_ctrl_dev { + struct mutex dev_lock; + + /* input control lines (DSR, CTS, CD, RI) */ + unsigned int cbits_tolocal; + + /* output control lines (DTR, RTS) */ + unsigned int cbits_tomdm; +}; + +static const struct usb_device_id id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(0x05c6 , 0x904c, 0xff, 0xfe, 0xff)}, + {}, /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver csvt_driver = { + .name = "qc_csvt", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, + .suspend = usb_serial_suspend, + .resume = usb_serial_resume, + .supports_autosuspend = true, +}; + +#define CSVT_IFC_NUM 4 + +static int csvt_probe(struct usb_serial *serial, const struct usb_device_id *id) +{ + struct usb_host_interface *intf = + serial->interface->cur_altsetting; + + pr_debug("%s:\n", __func__); + + if (intf->desc.bInterfaceNumber != CSVT_IFC_NUM) + return -ENODEV; + + usb_enable_autosuspend(serial->dev); + + return 0; +} + +static int csvt_ctrl_write_cmd(struct csvt_ctrl_dev *dev, + struct usb_serial_port *port) +{ + struct usb_device *udev = port->serial->dev; + struct usb_interface *iface = port->serial->interface; + unsigned int iface_num; + int retval = 0; + + retval = usb_autopm_get_interface(iface); + if (retval < 0) { + dev_err(&port->dev, "%s: Unable to resume interface: %d\n", + __func__, retval); + return retval; + } + + dev_dbg(&port->dev, "%s: cbits to mdm 0x%x\n", __func__, + dev->cbits_tomdm); + + iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE), + dev->cbits_tomdm, + iface_num, + NULL, 0, USB_CTRL_SET_TIMEOUT); + usb_autopm_put_interface(iface); + + return retval; +} + +static void csvt_ctrl_dtr_rts(struct usb_serial_port *port, int on) +{ + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + if (!dev) + return; + + dev_dbg(&port->dev, "%s", __func__); + + mutex_lock(&dev->dev_lock); + if (on) { + dev->cbits_tomdm |= CSVT_CTRL_DTR; + dev->cbits_tomdm |= CSVT_CTRL_RTS; + } else { + dev->cbits_tomdm &= ~CSVT_CTRL_DTR; + dev->cbits_tomdm &= ~CSVT_CTRL_RTS; + } + mutex_unlock(&dev->dev_lock); + + csvt_ctrl_write_cmd(dev, port); +} + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + tmp.line = port->serial->minor; + tmp.port = port->number; + tmp.baud_base = tty_get_baud_rate(port->port.tty); + tmp.close_delay = port->port.close_delay / 10; + tmp.closing_wait = + port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + port->port.closing_wait / 10; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct usb_serial_port *port, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait; + unsigned int close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&port->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != port->port.close_delay) || + (closing_wait != port->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + port->port.close_delay = close_delay; + port->port.closing_wait = closing_wait; + } + + mutex_unlock(&port->port.mutex); + return retval; +} + +static int csvt_ctrl_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s cmd 0x%04x", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *) arg); + case TIOCSSERIAL: + return set_serial_info(port, + (struct serial_struct __user *) arg); + default: + break; + } + + dev_err(&port->dev, "%s arg not supported", __func__); + + return -ENOIOCTLCMD; +} + +static int csvt_ctrl_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + unsigned int control_state = 0; + + if (!dev) + return -ENODEV; + + mutex_lock(&dev->dev_lock); + control_state = (dev->cbits_tomdm & CSVT_CTRL_DTR ? TIOCM_DTR : 0) | + (dev->cbits_tomdm & CSVT_CTRL_RTS ? TIOCM_RTS : 0) | + (dev->cbits_tolocal & CSVT_CTRL_DSR ? TIOCM_DSR : 0) | + (dev->cbits_tolocal & CSVT_CTRL_RI ? TIOCM_RI : 0) | + (dev->cbits_tolocal & CSVT_CTRL_CD ? TIOCM_CD : 0) | + (dev->cbits_tolocal & CSVT_CTRL_CTS ? TIOCM_CTS : 0); + mutex_unlock(&dev->dev_lock); + + dev_dbg(&port->dev, "%s -- %x", __func__, control_state); + + return control_state; +} + +static int csvt_ctrl_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + if (!dev) + return -ENODEV; + + dev_dbg(&port->dev, "%s\n", __func__); + + mutex_lock(&dev->dev_lock); + if (set & CSVT_CTRL_DTR) + dev->cbits_tomdm |= TIOCM_DTR; + if (set & CSVT_CTRL_RTS) + dev->cbits_tomdm |= TIOCM_RTS; + + if (clear & CSVT_CTRL_DTR) + dev->cbits_tomdm &= ~TIOCM_DTR; + if (clear & CSVT_CTRL_RTS) + dev->cbits_tomdm &= ~TIOCM_RTS; + mutex_unlock(&dev->dev_lock); + + return csvt_ctrl_write_cmd(dev, port); +} + +static void csvt_ctrl_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + if (!dev) + return; + + dev_dbg(&port->dev, "%s", __func__); + + /* Doesn't support option setting */ + tty_termios_copy_hw(tty->termios, old_termios); + + csvt_ctrl_write_cmd(dev, port); +} + +static void csvt_ctrl_int_cb(struct urb *urb) +{ + int status; + struct usb_cdc_notification *ctrl; + struct usb_serial_port *port = urb->context; + struct csvt_ctrl_dev *dev; + unsigned int ctrl_bits; + unsigned char *data; + + switch (urb->status) { + case 0: + /*success*/ + break; + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + case -EPROTO: + /* unplug */ + return; + case -EPIPE: + dev_err(&port->dev, "%s: stall on int endpoint\n", __func__); + /* TBD : halt to be cleared in work */ + case -EOVERFLOW: + default: + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + goto resubmit_int_urb; + } + + dev = usb_get_serial_port_data(port); + if (!dev) + return; + + ctrl = urb->transfer_buffer; + data = (unsigned char *)(ctrl + 1); + + usb_serial_debug_data(debug, &port->dev, __func__, + urb->actual_length, data); + + switch (ctrl->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&port->dev, "%s network\n", ctrl->wValue ? + "connected to" : "disconnected from"); + break; + case USB_CDC_NOTIFY_SERIAL_STATE: + ctrl_bits = get_unaligned_le16(data); + dev_dbg(&port->dev, "serial state: %d\n", ctrl_bits); + dev->cbits_tolocal = ctrl_bits; + break; + default: + dev_err(&port->dev, "%s: unknown notification %d received:" + "index %d len %d data0 %d data1 %d", + __func__, ctrl->bNotificationType, ctrl->wIndex, + ctrl->wLength, data[0], data[1]); + } + +resubmit_int_urb: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(&port->dev, "%s: Error re-submitting Int URB %d\n", + __func__, status); + +} + +static int csvt_ctrl_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + int retval; + + dev_dbg(&port->dev, "%s port %d", __func__, port->number); + + retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&port->dev, "usb_submit_urb(read int) failed\n"); + return retval; + } + + retval = usb_serial_generic_open(tty, port); + if (retval) + usb_kill_urb(port->interrupt_in_urb); + + return retval; +} + +static void csvt_ctrl_close(struct usb_serial_port *port) +{ + dev_dbg(&port->dev, "%s port %d", __func__, port->number); + + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); +} + +static int csvt_ctrl_attach(struct usb_serial *serial) +{ + struct csvt_ctrl_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->dev_lock); + usb_set_serial_port_data(serial->port[0], dev); + + return 0; +} + +static void csvt_ctrl_release(struct usb_serial *serial) +{ + struct usb_serial_port *port = serial->port[0]; + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s", __func__); + + kfree(dev); + usb_set_serial_port_data(port, NULL); +} + +static struct usb_serial_driver csvt_device = { + .driver = { + .owner = THIS_MODULE, + .name = "qc_csvt", + }, + .description = "qc_csvt", + .id_table = id_table, + .num_ports = 1, + .open = csvt_ctrl_open, + .close = csvt_ctrl_close, + .probe = csvt_probe, + .dtr_rts = csvt_ctrl_dtr_rts, + .tiocmget = csvt_ctrl_tiocmget, + .tiocmset = csvt_ctrl_tiocmset, + .ioctl = csvt_ctrl_ioctl, + .set_termios = csvt_ctrl_set_termios, + .read_int_callback = csvt_ctrl_int_cb, + .attach = csvt_ctrl_attach, + .release = csvt_ctrl_release, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &csvt_device, + NULL, +}; + +static int __init csvt_init(void) +{ + int retval; + + retval = usb_serial_register_drivers(&csvt_driver, serial_drivers); + if (retval) { + err("%s: usb serial register failed\n", __func__); + return retval; + } + + return 0; +} + +static void __exit csvt_exit(void) +{ + usb_serial_deregister_drivers(&csvt_driver, serial_drivers); +} + +module_init(csvt_init); +module_exit(csvt_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index 0206b10c9e6e174506b9b05431ee2a3519549025..1f6d9151ecb48b2800f45861e970674e1dcbf0e8 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -1,7 +1,7 @@ /* * Qualcomm Serial USB driver * - * Copyright (c) 2008 QUALCOMM Incorporated. + * Copyright (c) 2008, 2012 Code Aurora Forum. All rights reserved. * Copyright (c) 2009 Greg Kroah-Hartman * Copyright (c) 2009 Novell Inc. * @@ -108,10 +108,14 @@ static const struct usb_device_id id_table[] = { {USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */ {USB_DEVICE(0x12D1, 0x14F0)}, /* Sony Gobi 3000 QDL */ {USB_DEVICE(0x12D1, 0x14F1)}, /* Sony Gobi 3000 Composite */ + {USB_DEVICE(0x05c6, 0x9048)}, /* MDM9x15 device */ + {USB_DEVICE(0x05c6, 0x904C)}, /* MDM9x15 device */ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, id_table); +#define EFS_SYNC_IFC_NUM 2 + static struct usb_driver qcdriver = { .name = "qcserial", .probe = usb_serial_probe, @@ -233,6 +237,14 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) } break; + case 9: + if (ifnum != EFS_SYNC_IFC_NUM) { + kfree(data); + break; + } + + retval = 0; + break; default: dev_err(&serial->dev->dev, "unknown number of interfaces: %d\n", nintf); diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h index c47b6ec030634f9af420987e06c33727042b4f05..de8d49052631f871f41c062b6e2cff7238185b57 100644 --- a/drivers/usb/serial/usb-wwan.h +++ b/drivers/usb/serial/usb-wwan.h @@ -31,10 +31,10 @@ extern int usb_wwan_resume(struct usb_serial *serial); /* per port private data */ -#define N_IN_URB 4 -#define N_OUT_URB 4 -#define IN_BUFLEN 4096 -#define OUT_BUFLEN 4096 +#define N_IN_URB 5 +#define N_OUT_URB 5 +#define IN_BUFLEN 65536 +#define OUT_BUFLEN 65536 struct usb_wwan_intf_private { spinlock_t susp_lock; diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index c88657dd31c88495a65a49f0aff2aca047f89481..519af39473dda8ccd7e00b81e5b80f936017ee7d 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -406,6 +406,11 @@ int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port) portdata = usb_get_serial_port_data(port); intfdata = serial->private; + /* explicitly set the driver mode to raw */ + tty->raw = 1; + tty->real_raw = 1; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); dbg("%s", __func__); /* Start reading from the IN endpoint */ @@ -548,7 +553,7 @@ int usb_wwan_startup(struct usb_serial *serial) init_usb_anchor(&portdata->delayed); for (j = 0; j < N_IN_URB; j++) { - buffer = (u8 *) __get_free_page(GFP_KERNEL); + buffer = kmalloc(IN_BUFLEN, GFP_KERNEL); if (!buffer) goto bail_out_error; portdata->in_buffer[j] = buffer; @@ -577,8 +582,7 @@ int usb_wwan_startup(struct usb_serial *serial) kfree(portdata->out_buffer[j]); bail_out_error: for (j = 0; j < N_IN_URB; j++) - if (portdata->in_buffer[j]) - free_page((unsigned long)portdata->in_buffer[j]); + kfree(portdata->in_buffer[j]); kfree(portdata); return 1; } @@ -624,8 +628,7 @@ void usb_wwan_release(struct usb_serial *serial) for (j = 0; j < N_IN_URB; j++) { usb_free_urb(portdata->in_urbs[j]); - free_page((unsigned long) - portdata->in_buffer[j]); + kfree(portdata->in_buffer[j]); portdata->in_urbs[j] = NULL; } for (j = 0; j < N_OUT_URB; j++) { @@ -731,6 +734,10 @@ int usb_wwan_resume(struct usb_serial *serial) } } + spin_lock_irq(&intfdata->susp_lock); + intfdata->suspended = 0; + spin_unlock_irq(&intfdata->susp_lock); + for (i = 0; i < serial->num_ports; i++) { /* walk all ports */ port = serial->port[i]; @@ -756,9 +763,6 @@ int usb_wwan_resume(struct usb_serial *serial) play_delayed(port); spin_unlock_irq(&intfdata->susp_lock); } - spin_lock_irq(&intfdata->susp_lock); - intfdata->suspended = 0; - spin_unlock_irq(&intfdata->susp_lock); err_out: return err; } diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 65b07f4d4e59a6df93422bbba3fb767d4e0388bb..903d421252ae76d944d7bac702f2ec1fedd91d79 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 @@ -2351,13 +2353,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 @@ -2413,6 +2408,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" source "drivers/video/exynos/Kconfig" diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c index c6ce416ab587776f72f629a1ffecbfcc3ecce693..d6a664a168b75b366cc2a0e4e53f1215b9e82d49 100644 --- a/drivers/video/fbmem.c +++ b/drivers/video/fbmem.c @@ -1183,14 +1183,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 0000000000000000000000000000000000000000..b8d1df8d29bfdd61063d1989c15915af9a49a833 --- /dev/null +++ b/drivers/video/msm/Kconfig @@ -0,0 +1,924 @@ + +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 + +config FB_MSM_MDP_HW + bool + default n + +choice + prompt "MDP HW version" + default FB_MSM_MDP22 + +config FB_MSM_MDP22 + select FB_MSM_MDP_HW + 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 + select FB_MSM_MDP_HW + 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 + select FB_MSM_MDP_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 + select FB_MSM_MDP_HW + bool "MDP HW ver4.0" + ---help--- + Support for MSM MDP HW revision 4.0 + Say Y here if this is msm7x30 variant platform. + +config FB_MSM_MDP_NONE + bool "MDP HW None" + ---help--- + Say Y here if this is mdm 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_LVDS + 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_TRULY + bool + select FB_MSM_MIPI_DSI + +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_MIPI_DSI_NT35510 + bool + select FB_MSM_MIPI_DSI + +config FB_MSM_MIPI_DSI_ORISE + bool + select FB_MSM_MIPI_DSI + default n + +config FB_MSM_MIPI_DSI_NT35516 + bool + select FB_MSM_MIPI_DSI + +config FB_MSM_MIPI_DSI_TC358764_DSI2LVDS + bool + select FB_MSM_MIPI_DSI + ---help--- + Support for Toshiba MIPI DSI-to-LVDS bridge. + The chip supports 1366x768 24-bit + using a single LVDS link + and up to WUXGA 1920x1200 18-bit + using a dual LVDS link. + +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_TRULY_HVGA_IPS3P2335 + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_TRULY_HVGA_IPS3P2335_PT_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Truly HVGA PT Panel" + select FB_MSM_LCDC_TRULY_HVGA_IPS3P2335 + default n + ---help--- + Support for LCDC Truly HVGA PT panel + + +config FB_MSM_LCDC_SAMSUNG_OLED_PT + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_NT35582_WVGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_WXGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LVDS_CHIMEI_WXGA + bool + select FB_MSM_LVDS + 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_TOSHIBA_VIDEO_WUXGA + 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_ORISE_VIDEO_720P_PT + bool + select FB_MSM_MIPI_DSI_ORISE + default n + +config FB_MSM_MIPI_ORISE_CMD_720P_PT + bool + select FB_MSM_MIPI_DSI_ORISE + 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_NT35510_VIDEO_WVGA_PT + bool + select FB_MSM_MIPI_DSI_NT35510 + default n + +config FB_MSM_MIPI_NT35510_CMD_WVGA_PT + bool + select FB_MSM_MIPI_DSI_NT35510 + default n + +config FB_MSM_MIPI_NT35516_VIDEO_QHD_PT + bool + select FB_MSM_MIPI_DSI_NT35516 + default n + +config FB_MSM_MIPI_NT35516_CMD_QHD_PT + bool + select FB_MSM_MIPI_DSI_NT35516 + default n + + +config FB_MSM_MIPI_CHIMEI_WXGA + bool "LVDS Chimei WXGA Panel using Toshiba MIPI DSI-to-LVDS bridge." + select FB_MSM_MIPI_DSI_TC358764_DSI2LVDS + ---help--- + Support for Chimei WXGA (1366x768) panel. + The panel is using a serial LVDS input. + The panel is connected to the host + via Toshiba DSI-to-LVDS bridge. + +config FB_MSM_MIPI_CHIMEI_WUXGA + bool "LVDS Chimei WUXGA Panel using Toshiba MIPI DSI-to-LVDS bridge." + select FB_MSM_MIPI_DSI_TC358764_DSI2LVDS + ---help--- + Support for Chimei WUXGA (1920x1200) panel. + The panel is using a serial LVDS input. + The panel is connected to the host + via Toshiba DSI-to-LVDS bridge. + +config FB_MSM_MIPI_TRULY_VIDEO_WVGA_PT + bool + select FB_MSM_MIPI_DSI_TRULY + +config FB_MSM_MIPI_SIMULATOR_VIDEO + bool + select FB_MSM_MIPI_DSI_SIMULATOR + default n + +config FB_MSM_OVERLAY0_WRITEBACK + depends on FB_MSM_OVERLAY + bool "MDP overlay0 write back mode enable" + ---help--- + Support for MDP4 OVERLAY0 write back mode + + +config FB_MSM_OVERLAY1_WRITEBACK + depends on FB_MSM_OVERLAY + bool "MDP overlay1 write back mode enable" + ---help--- + Support for MDP4 OVERLAY1 write back mode + +config FB_MSM_WRITEBACK_MSM_PANEL + depends on FB_MSM_OVERLAY + bool "MDP overlay write back panel 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_NT35582_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC NT35582 WVGA Panel" + select FB_MSM_LCDC_NT35582_WVGA + ---help--- + Support for LCDC NT35582 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_LVDS_CHIMEI_WXGA_PANEL + bool "LVDS Chimei WXGA Panel" + select FB_MSM_LVDS_CHIMEI_WXGA + ---help--- + Support for LVDS Chimei WXGA(1366x768) 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 + bool "MIPI Panel Detect" + select FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WUXGA + select FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT + select FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT + select FB_MSM_MIPI_TRULY_VIDEO_WVGA_PT + select FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT + select FB_MSM_MIPI_NOVATEK_CMD_QHD_PT + select FB_MSM_MIPI_NT35510_VIDEO_WVGA_PT + select FB_MSM_MIPI_NT35510_CMD_WVGA_PT + select FB_MSM_MIPI_ORISE_VIDEO_720P_PT + select FB_MSM_MIPI_ORISE_CMD_720P_PT + select FB_MSM_MIPI_NT35516_VIDEO_QHD_PT + select FB_MSM_MIPI_NT35516_CMD_QHD_PT + select FB_MSM_MIPI_SIMULATOR_VIDEO + select FB_MSM_MIPI_CHIMEI_WXGA + select FB_MSM_MIPI_CHIMEI_WUXGA + ---help--- + Support for MIPI panel auto detect + +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_NT35582_WVGA + select FB_MSM_LCDC_SAMSUNG_OLED_PT + ---help--- + Support for LCDC panel auto detect + +config FB_MSM_LCDC_MIPI_PANEL_AUTO_DETECT + bool "LCDC + MIPI 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 + select FB_MSM_LCDC_NT35582_WVGA + select FB_MSM_LCDC_TOSHIBA_FWVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT + select FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT + select FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT + select FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT + select FB_MSM_MIPI_NOVATEK_CMD_QHD_PT + select FB_MSM_MIPI_NT35510_VIDEO_WVGA_PT + select FB_MSM_MIPI_NT35510_CMD_WVGA_PT + select FB_MSM_MIPI_NT35516_VIDEO_QHD_PT + select FM_MSM_MIPI_NT35516_CMD_QHD_PT + select FB_MSM_MIPI_SIMULATOR_VIDEO + ---help--- + Support for LCDC + MIPI panel auto detect + +config FB_MSM_LVDS_MIPI_PANEL_DETECT + bool "LVDS + MIPI Panel Auto Detect" + select FB_MSM_LVDS_CHIMEI_WXGA + select FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WUXGA + select FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT + select FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT + select FB_MSM_MIPI_TRULY_VIDEO_WVGA_PT + select FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT + select FB_MSM_MIPI_NOVATEK_CMD_QHD_PT + select FB_MSM_MIPI_NT35510_VIDEO_WVGA_PT + select FB_MSM_MIPI_NT35510_CMD_WVGA_PT + select FB_MSM_MIPI_ORISE_VIDEO_720P_PT + select FB_MSM_MIPI_ORISE_CMD_720P_PT + select FB_MSM_MIPI_SIMULATOR_VIDEO + select FB_MSM_MIPI_CHIMEI_WXGA + select FB_MSM_MIPI_CHIMEI_WUXGA + ---help--- + Support for LVDS + MIPI 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_TOSHIBA_VIDEO_WUXGA_PANEL + bool "MIPI Toshiba WUXGA (1920x1200) Panel" + select FB_MSM_MIPI_TOSHIBA_VIDEO_WUXGA + +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_ORISE_VIDEO_720P_PT_PANEL + bool "MIPI ORISE VIDEO 720P PT Panel" + select FB_MSM_MIPI_ORISE_VIDEO_720P_PT + +config FB_MSM_MIPI_ORISE_CMD_720P_PT_PANEL + bool "MIPI ORISE CMD 720P PT Panel" + select FB_MSM_MIPI_ORISE_CMD_720P_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_CHIMEI_WXGA_PANEL + bool "MIPI Chimei WXGA PT Panel" + select FB_MSM_MIPI_CHIMEI_WXGA + +config FB_MSM_MIPI_CHIMEI_WUXGA_PANEL + bool "MIPI Chimei WUXGA Panel" + select FB_MSM_MIPI_CHIMEI_WUXGA + +config FB_MSM_MIPI_TRULY_VIDEO_WVGA_PT_PANEL + bool "MIPI Truly Video WVGA PT Panel" + select FB_MSM_MIPI_TRULY_VIDEO_WVGA_PT + +config FB_MSM_MIPI_NT35510_VIDEO_WVGA_PT_PANEL + bool "MIPI NT35510 Video WVGA PT Panel" + select FB_MSM_MIPI_NT35510_VIDEO_WVGA_PT + +config FB_MSM_MIPI_NT35510_CMD_WVGA_PT_PANEL + bool "MIPI NT35510 Command WVGA PT Panel" + select FB_MSM_MIPI_NT35510_CMD_WVGA_PT + +config FB_MSM_MIPI_NT35516_VIDEO_QHD_PT_PANEL + bool "MIPI NT35516 Video qHD PT Panel" + select FB_MSM_MIPI_NT35516_VIDEO_QHD_PT + +config FB_MSM_MIPI_NT35516_CMD_QHD_PT_PANEL + bool "MIPI NT35516 Command qHD PT Panel" + select FB_MSM_MIPI_NT35516_CMD_QHD_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_HDMI_AS_PRIMARY + depends on FB_MSM_HDMI_COMMON + bool "Use HDMI as primary panel" + ---help--- + Support for using HDMI as primary + +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 + +config FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + depends on FB_MSM_HDMI_MSM_PANEL + bool "Enable CEC" + default n + ---help--- + Support for HDMI CEC Feature + Choose to enable CEC + +config FB_MSM_HDMI_MHL_9244 + depends on FB_MSM_HDMI_MSM_PANEL + bool 'SI_MHL 9244 support' + default n + ---help--- + Support the HDMI to MHL conversion. + MHL (Mobile High-Definition Link) technology + uses USB connector to output HDMI content + +config FB_MSM_HDMI_MHL_8334 + depends on FB_MSM_HDMI_MSM_PANEL + bool 'SI_MHL 8334 support ' + default n + ---help--- + Support the HDMI to MHL conversion. + MHL (Mobile High-Definition Link) technology + uses USB connector to output HDMI content + +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 || FB_MSM_MDP303 + 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 + +config FB_MSM_EBI2_EPSON_S1D_QVGA_PANEL + bool "EBI2 Epson QVGA Panel" + select FB_MSM_EBI2 + default n + ---help--- + Support for EBI2 Epson QVGA (240x320) panel + +config FB_MSM_EBI2_PANEL_DETECT + bool "EBI2 Panel Detect" + select FB_MSM_EBI2_EPSON_S1D_QVGA_PANEL + default n + ---help--- + Support for EBI2 panel auto detect +endif diff --git a/drivers/video/msm/Makefile b/drivers/video/msm/Makefile index 802d6ae523fb36523f5c4621853fb7a23547865f..b2ecb083b9f254df15bab0ffccf55e370d147b9c 100644 --- a/drivers/video/msm/Makefile +++ b/drivers/video/msm/Makefile @@ -1,19 +1,195 @@ - -# 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 + +ifeq ($(CONFIG_FB_MSM_MDP_HW),y) + +# MDP +obj-y += mdp.o + +obj-$(CONFIG_DEBUG_FS) += mdp_debugfs.o + +ifeq ($(CONFIG_FB_MSM_MDP40),y) +obj-y += mdp4_util.o +obj-y += mdp4_hsic.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 + +# LVDS +obj-$(CONFIG_FB_MSM_LVDS) += lvds.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_ORISE) += mipi_orise.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_RENESAS) += mipi_renesas.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_TRULY) += mipi_truly.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_NT35510) += mipi_NT35510.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_NT35516) += mipi_truly_tft540960_1_e.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_SIMULATOR) += mipi_simulator.o + +# MIPI Bridge +obj-$(CONFIG_FB_MSM_MIPI_DSI_TC358764_DSI2LVDS) += mipi_tc358764_dsi2lvds.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 mipi_toshiba_video_wuxga.o +obj-y += mipi_novatek_video_qhd_pt.o mipi_novatek_cmd_qhd_pt.o +obj-y += mipi_orise_video_720p_pt.o mipi_orise_cmd_720p_pt.o +obj-y += mipi_renesas_video_fwvga_pt.o mipi_renesas_cmd_fwvga_pt.o +obj-y += mipi_NT35510_video_wvga_pt.o mipi_NT35510_cmd_wvga_pt.o +obj-y += mipi_truly_tft540960_1_e_video_qhd_pt.o mipi_truly_tft540960_1_e_cmd_qhd_pt.o +obj-y += mipi_chimei_wxga_pt.o +obj-y += mipi_chimei_wuxga.o +obj-y += mipi_truly_video_wvga_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_TOSHIBA_VIDEO_WUXGA) += mipi_toshiba_video_wuxga.o +obj-$(CONFIG_FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT) += mipi_novatek_video_qhd_pt.o +obj-$(CONFIG_FB_MSM_MIPI_ORISE_VIDEO_720P_PT) += mipi_orise_video_720p_pt.o +obj-$(CONFIG_FB_MSM_MIPI_ORISE_CMD_720P_PT) += mipi_orise_cmd_720p_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_RENESAS_VIDEO_FWVGA_PT) += mipi_renesas_video_fwvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_TRULY_VIDEO_WVGA_PT) += mipi_truly_video_wvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_NT35510_CMD_WVGA_PT) += mipi_NT35510_cmd_wvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_NT35510_VIDEO_WVGA_PT) += mipi_NT35510_video_wvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_NT35516_CMD_QHD_PT) += mipi_truly_tft540960_1_e_cmd_qhd_pt.o +obj-$(CONFIG_FB_MSM_MIPI_NT35516_VIDEO_QHD_PT) += mipi_truly_tft540960_1_e_video_qhd_pt.o +obj-$(CONFIG_FB_MSM_MIPI_SIMULATOR_VIDEO) += mipi_simulator_video.o +obj-$(CONFIG_FB_MSM_MIPI_CHIMEI_WXGA) += mipi_chimei_wxga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_CHIMEI_WUXGA) += mipi_chimei_wuxga.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_NT35582_WVGA) += lcdc_nt35582_wvga.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_LVDS_CHIMEI_WXGA) += lvds_chimei_wxga.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_LCDC_TRULY_HVGA_IPS3P2335) += lcdc_truly_ips3p2335.o + +obj-$(CONFIG_FB_MSM_TVOUT) += tvout_msm.o + +ccflags-y := -I$(src)/mhl +obj-$(CONFIG_FB_MSM_HDMI_MHL_8334) += mhl-8334.o +mhl-8334-objs += mhl/mhl_8334.o +mhl-8334-objs += mhl/mhl_i2c_utils.o + +obj-$(CONFIG_FB_MSM_EXTMDDI_SVGA) += mddi_ext_lcd.o -# MDDI interface -# -obj-y += mddi.o +obj-$(CONFIG_FB_MSM_WRITEBACK_MSM_PANEL) += mdp4_wfd_writeback_panel.o +obj-$(CONFIG_FB_MSM_WRITEBACK_MSM_PANEL) += mdp4_wfd_writeback.o +obj-$(CONFIG_FB_MSM_WRITEBACK_MSM_PANEL) += mdp4_overlay_writeback.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/ +else +obj-$(CONFIG_FB_MSM_EBI2) += ebi2_host.o +obj-$(CONFIG_FB_MSM_EBI2) += ebi2_lcd.o +obj-y += msm_fb_panel.o +obj-$(CONFIG_FB_MSM_EBI2_EPSON_S1D_QVGA_PANEL) += ebi2_epson_s1d_qvga.o +endif +clean: + rm *.o .*cmd diff --git a/drivers/video/msm/adv7520.c b/drivers/video/msm/adv7520.c new file mode 100644 index 0000000000000000000000000000000000000000..c0fb370253e1a69906198fcb597aa65421a50c89 --- /dev/null +++ b/drivers/video/msm/adv7520.c @@ -0,0 +1,1029 @@ +/* Copyright (c) 2010,2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_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 struct clk *tv_enc_clk; + +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); + switch_set_state(&external_common_state->sdev, 1); + } else { + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_OFFLINE); + switch_set_state(&external_common_state->sdev, 0); + } + 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); + + clk_prepare_enable(tv_enc_clk); + 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); + clk_disable_unprepare(tv_enc_clk); + 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"); + + if (hdmi_prim_display) + external_common_state->sdev.name = "hdmi_as_primary"; + else + external_common_state->sdev.name = "hdmi"; + + if (switch_dev_register(&external_common_state->sdev) < 0) + DEV_ERR("Hdmi switch registration failed\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; + } + switch_dev_unregister(&external_common_state->sdev); + 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; + + tv_enc_clk = clk_get(NULL, "tv_enc_clk"); + if (IS_ERR(tv_enc_clk)) { + printk(KERN_ERR "error: can't get tv_enc_clk!\n"); + return IS_ERR(tv_enc_clk); + } + + 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: + if (tv_enc_clk) + clk_put(tv_enc_clk); + 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_epson_s1d_qvga.c b/drivers/video/msm/ebi2_epson_s1d_qvga.c new file mode 100644 index 0000000000000000000000000000000000000000..8821eabbcb20d475c00ee615d58b80d57f34e8c9 --- /dev/null +++ b/drivers/video/msm/ebi2_epson_s1d_qvga.c @@ -0,0 +1,374 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 CMD_NOP_C 0x00 +#define CMD_SOFT_RESET_C 0x99 +#define CMD_DISPLAY_ON_C 0xAF +#define CMD_DISPLAY_OFF_C 0xAE +#define CMD_SET_DISPLAY_C 0xCA +#define CMD_SET_DISPLAY_TIMING_C 0xA1 +#define CMD_SET_DATA_C 0xBC +#define CMD_SET_START_ADDRESS_C 0x15 +#define CMD_SET_END_ADDRESS_C 0x75 +#define CMD_RAM_WRITE_C 0x5C +#define CMD_RAM_READ_C 0x5D +#define CMD_SET_AREA_SCROLLING_C 0xAA +#define CMD_SET_DISPLAY_START_LINE_C 0xAB +#define CMD_PARTIAL_DISPLAY_IN_C 0xA8 +#define CMD_PARTIAL_DISPLAY_OUT_C 0xA9 +#define CMD_SET_DISPLAY_DATA_INTERFACE_C 0x31 +#define CMD_SET_DISPLAY_COLOR_MODE_C 0x8B +#define CMD_SELECT_MTP_ROM_MODE_C 0x65 +#define CMD_MTP_ROM_MODE_IN_C 0x67 +#define CMD_MTP_ROM_MODE_OUT_C 0x68 +#define CMD_MTP_ROM_OPERATION_IN_C 0x69 +#define CMD_MTP_ROM_OPERATION_OUT_C 0x70 +#define CMD_GATE_LINE_SCAN_MODE_C 0x6F +#define CMD_SET_AC_OPERATION_DRIVE_C 0x8C +#define CMD_SET_ELECTRONIC_CONTROL_C 0x20 +#define CMD_SET_POSITIVE_CORRECTION_CHARS_C 0x22 +#define CMD_SET_NEGATIVE_CORRECTION_CHARS_C 0x25 +#define CMD_SET_POWER_CONTROL_C 0x21 +#define CMD_SET_PARTIAL_POWER_CONTROL_C 0x23 +#define CMD_SET_8_COLOR_CONTROL_C 0x24 +#define CMD_SLEEP_IN_C 0x95 +#define CMD_SLEEP_OUT_C 0x94 +#define CMD_VDD_OFF_C 0x97 +#define CMD_VDD_ON_C 0x96 +#define CMD_STOP_OSCILLATION_C 0x93 +#define CMD_START_OSCILLATION_C 0x92 +#define CMD_TEST_SOURCE_C 0xFD +#define CMD_TEST_FUSE_C 0xFE +#define CMD_TEST_C 0xFF +#define CMD_STATUS_READ_C 0xE8 +#define CMD_REVISION_READ_C 0xE9 + +#define PANEL_WIDTH 240 +#define PANEL_HEIGHT 320 +#define ACTIVE_WIN_WIDTH PANEL_WIDTH +#define ACTIVE_WIN_HEIGHT PANEL_HEIGHT + +#define ACTIVE_WIN_H_START 0 +#define ACTIVE_WIN_V_START 0 + +#define DISP_CMD_OUT(cmd) outpw(DISP_CMD_PORT, (cmd << 1)); +#define DISP_DATA_OUT(data) outpw(DISP_DATA_PORT, (data << 1)); +#define DISP_DATA_IN() inpw(DISP_DATA_PORT); + +static void *DISP_CMD_PORT; +static void *DISP_DATA_PORT; +static boolean disp_initialized; +static boolean display_on; +static struct msm_panel_common_pdata *ebi2_epson_pdata; + +static void epson_s1d_disp_init(struct platform_device *pdev); +static int epson_s1d_disp_off(struct platform_device *pdev); +static int epson_s1d_disp_on(struct platform_device *pdev); +static void epson_s1d_disp_set_rect(int x, int y, int xres, int yres); + +static void epson_s1d_disp_set_rect(int x, int y, int xres, int yres) +{ + int right, bottom; + + if (!disp_initialized) + return; + + right = x + xres - 1; + bottom = y + yres - 1; + + x += ACTIVE_WIN_H_START; + y += ACTIVE_WIN_V_START; + right += ACTIVE_WIN_H_START; + bottom += ACTIVE_WIN_V_START; + + if ((PANEL_WIDTH > x) && + (PANEL_HEIGHT > y) && + (PANEL_WIDTH > right) && + (PANEL_HEIGHT > bottom)) { + DISP_CMD_OUT(CMD_SET_START_ADDRESS_C); + DISP_DATA_OUT((uint8)x); + DISP_DATA_OUT((uint8)(y>>8)); + DISP_DATA_OUT((uint8)y); + + DISP_CMD_OUT(CMD_SET_END_ADDRESS_C); + DISP_DATA_OUT((uint8)right); + DISP_DATA_OUT((uint8)(bottom>>8)); + DISP_DATA_OUT((uint8)bottom); + DISP_CMD_OUT(CMD_RAM_WRITE_C); + } +} + +static void epson_s1d_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; + + disp_initialized = TRUE; +} + +static int epson_s1d_disp_off(struct platform_device *pdev) +{ + if (!disp_initialized) + epson_s1d_disp_init(pdev); + + if (display_on) { + DISP_CMD_OUT(CMD_SOFT_RESET_C); + DISP_CMD_OUT(CMD_VDD_OFF_C); + display_on = FALSE; + } + + return 0; +} + +static int epson_s1d_disp_on(struct platform_device *pdev) +{ + int i; + if (!disp_initialized) + epson_s1d_disp_init(pdev); + + if (!display_on) { + /* Enable Vdd regulator */ + DISP_CMD_OUT(CMD_VDD_ON_C); + msleep(20); + + /* Soft Reset before configuring display */ + DISP_CMD_OUT(CMD_SOFT_RESET_C); + msleep(20); + + /* Set display attributes */ + + /* GATESCAN */ + DISP_CMD_OUT(CMD_GATE_LINE_SCAN_MODE_C); + DISP_DATA_OUT(0x0); + + /* DISSET */ + DISP_CMD_OUT(CMD_SET_DISPLAY_C); + DISP_DATA_OUT(0x31); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT((uint8)((PANEL_HEIGHT - 1)>>8)); + DISP_DATA_OUT((uint8)(PANEL_HEIGHT - 1)); + DISP_DATA_OUT(0x03); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x08); + + /* VOLSET */ + DISP_CMD_OUT( + CMD_SET_ELECTRONIC_CONTROL_C); + DISP_DATA_OUT(0x10); + DISP_DATA_OUT(0x80); + DISP_DATA_OUT(0x11); + DISP_DATA_OUT(0x1B); + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x0D); + DISP_DATA_OUT(0x00); + + /* PWRCTL */ + DISP_CMD_OUT(CMD_SET_POWER_CONTROL_C); + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x24); + DISP_DATA_OUT(0x0F); + DISP_DATA_OUT(0xFE); + DISP_DATA_OUT(0x33); + DISP_DATA_OUT(0x31); + DISP_DATA_OUT(0xFF); + DISP_DATA_OUT(0x03); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x77); + DISP_DATA_OUT(0x33); + DISP_DATA_OUT(0x11); + DISP_DATA_OUT(0x44); + DISP_DATA_OUT(0x00); + + /* PPWRCTL */ + DISP_CMD_OUT(CMD_SET_PARTIAL_POWER_CONTROL_C); + DISP_DATA_OUT(0x33); + DISP_DATA_OUT(0xFF); + DISP_DATA_OUT(0x03); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x44); + DISP_DATA_OUT(0x00); + + /* SPLOUT */ + DISP_CMD_OUT(CMD_SLEEP_OUT_C); + msleep(100); + + /* DATSET */ + DISP_CMD_OUT(CMD_SET_DATA_C); + DISP_DATA_OUT(0x00); + + /* DISTMEMSET */ + DISP_CMD_OUT(CMD_SET_DISPLAY_TIMING_C); + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x2E); + DISP_DATA_OUT(0x0A); + DISP_DATA_OUT(0x2C); + DISP_DATA_OUT(0x23); + DISP_DATA_OUT(0x2F); + DISP_DATA_OUT(0x00); + + /* GAMSETP */ + DISP_CMD_OUT(CMD_SET_POSITIVE_CORRECTION_CHARS_C); + DISP_DATA_OUT(0x37); + DISP_DATA_OUT(0xFF); + DISP_DATA_OUT(0x7F); + DISP_DATA_OUT(0x15); + DISP_DATA_OUT(0x37); + DISP_DATA_OUT(0x05); + + /* GAMSETN */ + DISP_CMD_OUT(CMD_SET_NEGATIVE_CORRECTION_CHARS_C); + DISP_DATA_OUT(0x37); + DISP_DATA_OUT(0xFF); + DISP_DATA_OUT(0x7F); + DISP_DATA_OUT(0x15); + DISP_DATA_OUT(0x37); + DISP_DATA_OUT(0x05); + + /* ACDRIVE */ + DISP_CMD_OUT(CMD_SET_AC_OPERATION_DRIVE_C); + DISP_DATA_OUT(0x00); + + /* TEST */ + DISP_CMD_OUT(CMD_TEST_C); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x01); + + /* COLMOD */ + DISP_CMD_OUT(CMD_SET_DISPLAY_COLOR_MODE_C); + DISP_DATA_OUT(0x00); + + /* STADDSET */ + DISP_CMD_OUT(CMD_SET_START_ADDRESS_C); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x00); + + /* EDADDSET */ + DISP_CMD_OUT(CMD_SET_END_ADDRESS_C); + DISP_DATA_OUT(0xEF); + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x3F); + + /* Set Display Start Line */ + DISP_CMD_OUT(CMD_SET_DISPLAY_START_LINE_C); + DISP_DATA_OUT(0x00); + + /* Set Display Data Interface */ + DISP_CMD_OUT(CMD_SET_DISPLAY_DATA_INTERFACE_C); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x04); + + epson_s1d_disp_set_rect(0, + 0, + ACTIVE_WIN_WIDTH, + ACTIVE_WIN_HEIGHT); + + for (i = 0; i < (ACTIVE_WIN_WIDTH * ACTIVE_WIN_HEIGHT); i++) + outpdw(DISP_DATA_PORT, 0); + + /* DISON */ + DISP_CMD_OUT(CMD_DISPLAY_ON_C); + msleep(60); + + display_on = TRUE; + } + + return 0; +} + +static int epson_s1d_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + ebi2_epson_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + return 0; +} + +static struct platform_driver this_driver = { + .probe = epson_s1d_probe, + .driver = { + .name = "ebi2_epson_s1d_qvga", + }, +}; + +static struct msm_fb_panel_data epson_s1d_panel_data = { + .on = epson_s1d_disp_on, + .off = epson_s1d_disp_off, + .set_rect = epson_s1d_disp_set_rect, +}; + +static struct platform_device this_device = { + .name = "ebi2_epson_s1d_qvga", + .id = 1, + .dev = { + .platform_data = &epson_s1d_panel_data, + } +}; + +static int __init epson_s1d_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + ret = platform_driver_register(&this_driver); + if (!ret) { + pinfo = &epson_s1d_panel_data.panel_info; + pinfo->xres = PANEL_WIDTH; + pinfo->yres = PANEL_HEIGHT; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = EBI2_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0x048423E8; + pinfo->bpp = 18; + 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(epson_s1d_init); diff --git a/drivers/video/msm/ebi2_host.c b/drivers/video/msm/ebi2_host.c new file mode 100644 index 0000000000000000000000000000000000000000..8ba5506fa6f9b58da8053d7e731309dd36920e89 --- /dev/null +++ b/drivers/video/msm/ebi2_host.c @@ -0,0 +1,307 @@ +/* Copyright (c) 2012, 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 "msm_fb.h" + +struct mdp_ccs mdp_ccs_rgb2yuv; +struct mdp_ccs mdp_ccs_yuv2rgb; + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; +static int ebi2_host_resource_initialized; +static struct msm_panel_common_pdata *ebi2_host_pdata; + +static int ebi2_host_probe(struct platform_device *pdev); +static int ebi2_host_remove(struct platform_device *pdev); + +static int ebi2_host_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int ebi2_host_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops ebi2_host_dev_pm_ops = { + .runtime_suspend = ebi2_host_runtime_suspend, + .runtime_resume = ebi2_host_runtime_resume, +}; + + +static struct platform_driver ebi2_host_driver = { + .probe = ebi2_host_probe, + .remove = ebi2_host_remove, + .shutdown = NULL, + .driver = { + /* + * Simulate mdp hw + */ + .name = "mdp", + .pm = &ebi2_host_dev_pm_ops, + }, +}; + +void mdp_pipe_ctrl(MDP_BLOCK_TYPE block, MDP_BLOCK_POWER_STATE state, + boolean isr) +{ + return; +} +int mdp_ppp_blit(struct fb_info *info, struct mdp_blit_req *req) +{ + return 0; +} +int mdp_start_histogram(struct fb_info *info) +{ + return 0; +} +int mdp_stop_histogram(struct fb_info *info) +{ + return 0; +} +void mdp_refresh_screen(unsigned long data) +{ + return; +} + +static int ebi2_host_off(struct platform_device *pdev) +{ + int ret; + ret = panel_next_off(pdev); + return ret; +} + +static int ebi2_host_on(struct platform_device *pdev) +{ + int ret; + ret = panel_next_on(pdev); + return ret; +} + + +static int ebi2_host_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; + + if ((pdev->id == 0) && (pdev->num_resources > 0)) { + + ebi2_host_pdata = pdev->dev.platform_data; + + ebi2_host_resource_initialized = 1; + return 0; + } + + ebi2_host_resource_initialized = 1; + if (!ebi2_host_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; + + /* link to the latest pdev */ + mfd->pdev = msm_fb_dev; + + if (ebi2_host_pdata) { + mfd->mdp_rev = ebi2_host_pdata->mdp_rev; + mfd->mem_hid = ebi2_host_pdata->mem_hid; + } + + /* add panel data */ + if (platform_device_add_data + (msm_fb_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + pr_err("ebi2_host_probe: platform_device_add_data failed!\n"); + rc = -ENOMEM; + goto ebi2_host_probe_err; + } + /* data chain */ + pdata = msm_fb_dev->dev.platform_data; + pdata->on = ebi2_host_on; + pdata->off = ebi2_host_off; + pdata->next = pdev; + + /* set driver data */ + platform_set_drvdata(msm_fb_dev, mfd); + + rc = platform_device_add(msm_fb_dev); + if (rc) + goto ebi2_host_probe_err; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + pdev_list[pdev_list_cnt++] = pdev; + return 0; + +ebi2_host_probe_err: + platform_device_put(msm_fb_dev); + return rc; +} + +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; + struct fb_info *fbi = mfd->fbi; + struct msm_panel_info *panel_info = &mfd->panel_info; + MDPIBUF *iBuf; + int bpp = info->var.bits_per_pixel / 8; + int yres, remainder; + + if (panel_info->mode2_yres != 0) { + yres = panel_info->mode2_yres; + remainder = (fbi->fix.line_length*yres)%PAGE_SIZE; + } else { + yres = panel_info->yres; + remainder = (fbi->fix.line_length*yres)%PAGE_SIZE; + } + + if (!remainder) + remainder = PAGE_SIZE; + + down(&mfd->sem); + + iBuf = &mfd->ibuf; + /* use virtual address */ + iBuf->buf = (uint8 *) fbi->screen_base; + + if (fbi->var.yoffset < yres) { + iBuf->buf += fbi->var.xoffset * bpp; + } else if (fbi->var.yoffset >= yres && fbi->var.yoffset < 2 * yres) { + iBuf->buf += fbi->var.xoffset * bpp + yres * + fbi->fix.line_length + PAGE_SIZE - remainder; + } else { + iBuf->buf += fbi->var.xoffset * bpp + 2 * yres * + fbi->fix.line_length + 2 * (PAGE_SIZE - remainder); + } + + 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; + int i, j; + uint32 data; + uint8 *src; + struct msm_fb_panel_data *pdata = + (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + struct fb_info *fbi = mfd->fbi; + + iBuf = &mfd->ibuf; + + invalidate_caches((unsigned long)fbi->screen_base, + (unsigned long)info->fix.smem_len, + (unsigned long)info->fix.smem_start); + + pdata->set_rect(iBuf->dma_x, iBuf->dma_y, iBuf->dma_w, + iBuf->dma_h); + for (i = 0; i < iBuf->dma_h; i++) { + src = iBuf->buf + (fbi->fix.line_length * (iBuf->dma_y + i)) + + (iBuf->dma_x * iBuf->bpp); + for (j = 0; j < iBuf->dma_w; j++) { + data = (uint32)(*src++ >> 2) << 12; + data |= (uint32)(*src++ >> 2) << 6; + data |= (uint32)(*src++ >> 2); + data = ((data&0x1FF)<<16) | ((data&0x3FE00)>>9); + outpdw(mfd->data_port, data); + } + } +} + +static int ebi2_host_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int ebi2_host_register_driver(void) +{ + return platform_driver_register(&ebi2_host_driver); +} + +static int __init ebi2_host_driver_init(void) +{ + int ret; + + ret = ebi2_host_register_driver(); + if (ret) { + pr_err("ebi2_host_register_driver() failed!\n"); + return ret; + } + + return 0; +} + +module_init(ebi2_host_driver_init); diff --git a/drivers/video/msm/ebi2_l2f.c b/drivers/video/msm/ebi2_l2f.c new file mode 100644 index 0000000000000000000000000000000000000000..767b802cf6aae7510704012943f2007d353eefd2 --- /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 0000000000000000000000000000000000000000..966f974291cc028d63f33202744cabc307f5f56f --- /dev/null +++ b/drivers/video/msm/ebi2_lcd.c @@ -0,0 +1,308 @@ +/* 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, + .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 lcd01_base_phys; +static int ebi2_lcd_resource_initialized; + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; +static struct lcdc_platform_data *ebi2_pdata; + +static int ebi2_lcd_on(struct platform_device *pdev) +{ + int ret; + + if (ebi2_pdata && ebi2_pdata->lcdc_power_save) + ebi2_pdata->lcdc_power_save(1); + + ret = panel_next_on(pdev); + return ret; +} + +static int ebi2_lcd_off(struct platform_device *pdev) +{ + int ret; + + ret = panel_next_off(pdev); + + if (ebi2_pdata && ebi2_pdata->lcdc_power_save) + ebi2_pdata->lcdc_power_save(0); + + return ret; +} + +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, hw_version; + + 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_phys = pdev->resource[i].start; + 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_pdata = pdev->dev.platform_data; + 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 = ebi2_lcd_on; + pdata->off = ebi2_lcd_off; + pdata->next = pdev; + + /* get/set panel specific fb info */ + mfd->panel_info = pdata->panel_info; + + hw_version = inp32((int)ebi2_base + 8); + + if (mfd->panel_info.bpp == 24) + mfd->fb_imgType = MDP_RGB_888; + else if (mfd->panel_info.bpp == 18) + 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 (hw_version < 0x2020) { + 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; + if (hw_version >= 0x2020) { + mfd->data_port = + (void *)((uint32) mfd->cmd_port + 0x80); + mfd->data_port_phys = + (void *)(lcd01_base_phys + 0x80); + } else { + 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 0000000000000000000000000000000000000000..280373fcd5080c2be6fe3a38f6959c3a6bd05c13 --- /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 0000000000000000000000000000000000000000..b6bf47d04e03f1dd6fab00efcc5deb7115ead37e --- /dev/null +++ b/drivers/video/msm/external_common.c @@ -0,0 +1,2103 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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: " + +/* The start of the data block collection within the CEA Extension Version 3 */ +#define DBC_START_OFFSET 4 + +#include "msm_fb.h" +#include "hdmi_msm.h" +#include "external_common.h" +#include "mhl_api.h" + +#include "mdp.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; + } + } +} + +#ifdef DEBUG_EDID +/* + * Block 0 - 1920x1080p, 1360x768p + * Block 1 - 1280x720p, 1920x540i, 720x480p + */ +const char edid_blk0[0x100] = { +0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x4C, 0x2D, 0x03, 0x05, 0x00, +0x00, 0x00, 0x00, 0x30, 0x12, 0x01, 0x03, 0x80, 0x10, 0x09, 0x78, 0x0A, 0xEE, +0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54, 0xBD, 0xEF, 0x80, 0x71, +0x4F, 0x81, 0x00, 0x81, 0x40, 0x81, 0x80, 0x95, 0x00, 0x95, 0x0F, 0xB3, 0x00, +0xA9, 0x40, 0x02, 0x3A, 0x80, 0x18, 0x71, 0x38, 0x2D, 0x40, 0x58, 0x2C, 0x45, +0x00, 0xA0, 0x5A, 0x00, 0x00, 0x00, 0x1E, 0x66, 0x21, 0x50, 0xB0, 0x51, 0x00, +0x1B, 0x30, 0x40, 0x70, 0x36, 0x00, 0xA0, 0x5A, 0x00, 0x00, 0x00, 0x1E, 0x00, +0x00, 0x00, 0xFD, 0x00, 0x18, 0x4B, 0x1A, 0x51, 0x17, 0x00, 0x0A, 0x20, 0x20, +0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x53, 0x41, 0x4D, 0x53, +0x55, 0x4E, 0x47, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x8F}; + +const char edid_blk1[0x100] = { +0x02, 0x03, 0x1E, 0xF1, 0x46, 0x90, 0x04, 0x05, 0x03, 0x20, 0x22, 0x23, 0x09, +0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0xE2, 0x00, 0x0F, 0x67, 0x03, 0x0C, 0x00, +0x10, 0x00, 0xB8, 0x2D, 0x01, 0x1D, 0x00, 0x72, 0x51, 0xD0, 0x1E, 0x20, 0x6E, +0x28, 0x55, 0x00, 0xA0, 0x5A, 0x00, 0x00, 0x00, 0x1E, 0x01, 0x1D, 0x80, 0x18, +0x71, 0x1C, 0x16, 0x20, 0x58, 0x2C, 0x25, 0x00, 0xA0, 0x5A, 0x00, 0x00, 0x00, +0x9E, 0x8C, 0x0A, 0xD0, 0x8A, 0x20, 0xE0, 0x2D, 0x10, 0x10, 0x3E, 0x96, 0x00, +0xA0, 0x5A, 0x00, 0x00, 0x00, 0x18, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF}; +#endif /* DEBUG_EDID */ + +#define DMA_E_BASE 0xB0000 +void mdp_vid_quant_set(void) +{ + if ((external_common_state->video_resolution == \ + HDMI_VFRMT_720x480p60_4_3) || \ + (external_common_state->video_resolution == \ + HDMI_VFRMT_720x480p60_16_9)) { + MDP_OUTP(MDP_BASE + DMA_E_BASE + 0x70, 0x00EB0010); + MDP_OUTP(MDP_BASE + DMA_E_BASE + 0x74, 0x00EB0010); + MDP_OUTP(MDP_BASE + DMA_E_BASE + 0x78, 0x00EB0010); + } else { + MDP_OUTP(MDP_BASE + DMA_E_BASE + 0x70, 0x00FF0000); + MDP_OUTP(MDP_BASE + DMA_E_BASE + 0x74, 0x00FF0000); + MDP_OUTP(MDP_BASE + DMA_E_BASE + 0x78, 0x00FF0000); + } +} + +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); + +struct hdmi_disp_mode_timing_type + hdmi_mhl_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), + HDMI_SETTINGS_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), + HDMI_SETTINGS_1920x1080p24_16_9, + HDMI_SETTINGS_1920x1080p25_16_9, + HDMI_SETTINGS_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_mhl_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_edid_physical_address(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->physical_address); + + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->physical_address); + return ret; +} + + +static ssize_t hdmi_common_rda_edid_scan_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d, %d, %d\n", + external_common_state->pt_scan_info, + external_common_state->it_scan_info, + external_common_state->ce_scan_info); + DEV_DBG("%s: '%s'\n", __func__, buf); + return ret; +} + +static ssize_t hdmi_common_wta_vendor_name(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + uint8 *s = (uint8 *) buf; + uint8 *d = external_common_state->spd_vendor_name; + ssize_t ret = strnlen(buf, PAGE_SIZE); + ret = (ret > 8) ? 8 : ret; + + memset(external_common_state->spd_vendor_name, 0, 8); + while (*s) { + if (*s & 0x60 && *s ^ 0x7f) { + *d = *s; + } else { + /* stop copying if control character found */ + break; + } + + if (++s > (uint8 *) (buf + ret)) + break; + + d++; + } + + DEV_DBG("%s: '%s'\n", __func__, + external_common_state->spd_vendor_name); + + return ret; +} + +static ssize_t hdmi_common_rda_vendor_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%s\n", + external_common_state->spd_vendor_name); + DEV_DBG("%s: '%s'\n", __func__, + external_common_state->spd_vendor_name); + + return ret; +} + +static ssize_t hdmi_common_wta_product_description(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + uint8 *s = (uint8 *) buf; + uint8 *d = external_common_state->spd_product_description; + ssize_t ret = strnlen(buf, PAGE_SIZE); + ret = (ret > 16) ? 16 : ret; + + memset(external_common_state->spd_product_description, 0, 16); + while (*s) { + if (*s & 0x60 && *s ^ 0x7f) { + *d = *s; + } else { + /* stop copying if control character found */ + break; + } + + if (++s > (uint8 *) (buf + ret)) + break; + + d++; + } + + DEV_DBG("%s: '%s'\n", __func__, + external_common_state->spd_product_description); + + return ret; +} + +static ssize_t hdmi_common_rda_product_description(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%s\n", + external_common_state->spd_product_description); + DEV_DBG("%s: '%s'\n", __func__, + external_common_state->spd_product_description); + + return ret; +} + +static ssize_t hdmi_common_rda_edid_3d_modes(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + int i; + char buff_3d[128]; + + 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; + uint32 *video_3d_mode = external_common_state->disp_mode_list + .disp_3d_mode_list; + for (i = 0; i < external_common_state->disp_mode_list + .num_of_elements; ++i) { + video_3d_format_2string(*video_3d_mode++, buff_3d); + if (ret > 0) + ret += snprintf(buf+ret, PAGE_SIZE-ret, + ",%d=%s", + *video_mode++ + 1, buff_3d); + else + ret += snprintf(buf+ret, PAGE_SIZE-ret, + "%d=%s", + *video_mode++ + 1, buff_3d); + } + } 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; + if (hdmi_prim_display) + hpd = 1; + else + 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; +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT +/* + * This interface for CEC feature is defined to suit + * the current requirements. However, the actual functionality is + * added to accommodate different interfaces + */ +static ssize_t hdmi_msm_rda_cec(struct device *dev, + struct device_attribute *attr, char *buf) +{ + /* 0x028C CEC_CTRL */ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + (HDMI_INP(0x028C) & BIT(0))); + return ret; +} + +static ssize_t hdmi_msm_wta_cec(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int cec = atoi(buf); + + if (cec != 0) { + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->cec_enabled = true; + hdmi_msm_state->cec_logical_addr = 4; + + /* flush CEC queue */ + hdmi_msm_state->cec_queue_wr = hdmi_msm_state->cec_queue_start; + hdmi_msm_state->cec_queue_rd = hdmi_msm_state->cec_queue_start; + hdmi_msm_state->cec_queue_full = false; + memset(hdmi_msm_state->cec_queue_rd, 0, + sizeof(struct hdmi_msm_cec_msg)*CEC_QUEUE_SIZE); + + mutex_unlock(&hdmi_msm_state_mutex); + hdmi_msm_cec_init(); + hdmi_msm_cec_write_logical_addr( + hdmi_msm_state->cec_logical_addr); + DEV_DBG("CEC enabled\n"); + } else { + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->cec_enabled = false; + hdmi_msm_state->cec_logical_addr = 15; + mutex_unlock(&hdmi_msm_state_mutex); + hdmi_msm_cec_write_logical_addr( + hdmi_msm_state->cec_logical_addr); + /* 0x028C CEC_CTRL */ + HDMI_OUTP(0x028C, 0); + DEV_DBG("CEC disabled\n"); + } + return ret; +} + +static ssize_t hdmi_msm_rda_cec_logical_addr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + + mutex_lock(&hdmi_msm_state_mutex); + ret = snprintf(buf, PAGE_SIZE, "%d\n", + hdmi_msm_state->cec_logical_addr); + mutex_unlock(&hdmi_msm_state_mutex); + return ret; +} + +static ssize_t hdmi_msm_wta_cec_logical_addr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + +#ifdef DRVR_ONLY_CECT_NO_DAEMON + /* + * Only for testing + */ + hdmi_msm_cec_one_touch_play(); + return 0; +#else + ssize_t ret = strnlen(buf, PAGE_SIZE); + int logical_addr = atoi(buf); + + if (logical_addr < 0 || logical_addr > 15) + return -EINVAL; + + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->cec_logical_addr = logical_addr; + mutex_unlock(&hdmi_msm_state_mutex); + + hdmi_msm_cec_write_logical_addr(logical_addr); + + return ret; +#endif +} + +static ssize_t hdmi_msm_rda_cec_frame(struct device *dev, + struct device_attribute *attr, char *buf) +{ + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->cec_queue_rd == hdmi_msm_state->cec_queue_wr + && !hdmi_msm_state->cec_queue_full) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_ERR("CEC message queue is empty\n"); + return -EBUSY; + } + memcpy(buf, hdmi_msm_state->cec_queue_rd++, + sizeof(struct hdmi_msm_cec_msg)); + hdmi_msm_state->cec_queue_full = false; + if (hdmi_msm_state->cec_queue_rd == CEC_QUEUE_END) + hdmi_msm_state->cec_queue_rd = hdmi_msm_state->cec_queue_start; + mutex_unlock(&hdmi_msm_state_mutex); + + return sizeof(struct hdmi_msm_cec_msg); +} + +static ssize_t hdmi_msm_wta_cec_frame(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int i; + int retry = ((struct hdmi_msm_cec_msg *) buf)->retransmit; + + for (i = 0; i < RETRANSMIT_MAX_NUM; i++) { + hdmi_msm_cec_msg_send((struct hdmi_msm_cec_msg *) buf); + if (hdmi_msm_state->cec_frame_wr_status + & CEC_STATUS_WR_ERROR && retry--) { + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->fsm_reset_done) + retry++; + mutex_unlock(&hdmi_msm_state_mutex); + msleep(20); + } else + break; + } + + if (hdmi_msm_state->cec_frame_wr_status & CEC_STATUS_WR_DONE) + return sizeof(struct hdmi_msm_cec_msg); + else + return -EINVAL; +} +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT */ + +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 + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT +static DEVICE_ATTR(cec, S_IRUGO | S_IWUSR, + hdmi_msm_rda_cec, + hdmi_msm_wta_cec); + +static DEVICE_ATTR(cec_logical_addr, S_IRUGO | S_IWUSR, + hdmi_msm_rda_cec_logical_addr, + hdmi_msm_wta_cec_logical_addr); + +static DEVICE_ATTR(cec_rd_frame, S_IRUGO, + hdmi_msm_rda_cec_frame, NULL); + +static DEVICE_ATTR(cec_wr_frame, S_IWUSR, + NULL, hdmi_msm_wta_cec_frame); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT */ + + +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; + DEV_INFO("%s: video_mode is %d\n", __func__, video_mode); + 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 ssize_t external_common_rda_hdmi_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->hdmi_sink); + + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->hdmi_sink); + + return ret; +} + +static ssize_t hdmi_common_rda_hdmi_primary(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + hdmi_prim_display); + DEV_DBG("%s: '%d'\n", __func__, hdmi_prim_display); + 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); +static DEVICE_ATTR(hdmi_mode, S_IRUGO, external_common_rda_hdmi_mode, 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(pa, S_IRUGO, + hdmi_common_rda_edid_physical_address, NULL); +static DEVICE_ATTR(scan_info, S_IRUGO, + hdmi_common_rda_edid_scan_info, NULL); +static DEVICE_ATTR(vendor_name, S_IRUGO | S_IWUSR, hdmi_common_rda_vendor_name, + hdmi_common_wta_vendor_name); +static DEVICE_ATTR(product_description, S_IRUGO | S_IWUSR, + hdmi_common_rda_product_description, + hdmi_common_wta_product_description); +static DEVICE_ATTR(edid_3d_modes, S_IRUGO, + hdmi_common_rda_edid_3d_modes, 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 DEVICE_ATTR(hdmi_primary, S_IRUGO, hdmi_common_rda_hdmi_primary, NULL); + +static struct attribute *external_common_fs_attrs[] = { + &dev_attr_video_mode.attr, + &dev_attr_video_mode_str.attr, + &dev_attr_connected.attr, + &dev_attr_hdmi_mode.attr, +#ifdef CONFIG_FB_MSM_HDMI_COMMON + &dev_attr_edid_modes.attr, + &dev_attr_hdcp.attr, + &dev_attr_hpd.attr, + &dev_attr_pa.attr, + &dev_attr_scan_info.attr, + &dev_attr_vendor_name.attr, + &dev_attr_product_description.attr, + &dev_attr_edid_3d_modes.attr, + &dev_attr_3d_present.attr, + &dev_attr_hdcp_present.attr, +#endif +#ifdef CONFIG_FB_MSM_HDMI_3D + &dev_attr_format_3d.attr, +#endif +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + &dev_attr_cec.attr, + &dev_attr_cec_logical_addr.attr, + &dev_attr_cec_rd_frame.attr, + &dev_attr_cec_wr_frame.attr, +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT */ + &dev_attr_hdmi_primary.attr, + 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, + uint32 start_offset, uint8 type, uint8 *len) +{ + /* the start of data block collection, start of Video Data Block */ + uint32 offset = start_offset; + uint32 end_dbc_offset = in_buf[2]; + + *len = 0; + + /*edid buffer 1, byte 2 being 4 means no non-DTD/Data block collection + present. + edid buffer 1, byte 2 being 0 menas no non-DTD/DATA block collection + present and no DTD data present.*/ + if ((end_dbc_offset == 0) || (end_dbc_offset == 4)) { + DEV_WARN("EDID: no DTD or non-DTD data present\n"); + return NULL; + } + while (offset < end_dbc_offset) { + 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: type=%d block 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, DBC_START_OFFSET, 3, + &len); + + if (vsd == NULL) + return 0; + + DEV_DBG("EDID: VSD PhyAddr=%04x, MaxTMDS=%dMHz\n", + ((uint32)vsd[4] << 8) + (uint32)vsd[5], (uint32)vsd[7] * 5); + external_common_state->physical_address = + ((uint16)vsd[4] << 8) + (uint16)vsd[5]; + return ((uint32)vsd[3] << 16) + ((uint32)vsd[2] << 8) + (uint32)vsd[1]; +} + +#define HDMI_VSDB_3D_DATA_OFFSET(vsd) \ + (!((vsd)[8] & BIT(7)) ? 9 : (!((vsd)[8] & BIT(6)) ? 11 : 13)) + +static void hdmi_edid_extract_3d_present(const uint8 *in_buf) +{ + uint8 len, offset; + const uint8 *vsd = hdmi_edid_find_block(in_buf, DBC_START_OFFSET, 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 = HDMI_VSDB_3D_DATA_OFFSET(vsd); + 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, DBC_START_OFFSET, 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, DBC_START_OFFSET, 4, + &len); + + if (sad == NULL) + return; + + external_common_state->speaker_allocation_block = sad[1]; + DEV_DBG("EDID: speaker allocation data SP byte = %08x %s%s%s%s%s%s%s\n", + sad[1], + (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)) ? "RLC/RRC," : ""); +} + +static void hdmi_edid_extract_audio_data_blocks(const uint8 *in_buf) +{ + uint8 len; + const uint8 *sad = hdmi_edid_find_block(in_buf, DBC_START_OFFSET, 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_extract_extended_data_blocks(const uint8 *in_buf) +{ + uint8 len = 0; + uint32 start_offset = DBC_START_OFFSET; + + /* A Tage code of 7 identifies extended data blocks */ + uint8 const *etag = hdmi_edid_find_block(in_buf, start_offset, 7, &len); + + while (etag != NULL) { + /* The extended data block should at least be 2 bytes long */ + if (len < 2) { + DEV_DBG("EDID: Found an extended data block of length" + "less than 2 bytes. Ignoring ...\n"); + } else { + /* + * The second byte of the extended data block has the + * extended tag code + */ + switch (etag[1]) { + case 0: + /* Video Capability Data Block */ + DEV_DBG("EDID: VCDB=%02X %02X\n", etag[1], + etag[2]); + + /* + * Check if the sink specifies underscan + * support for: + * BIT 5: preferred video format + * BIT 3: IT video format + * BIT 1: CE video format + */ + external_common_state->pt_scan_info = (etag[2] & + (BIT(4) | BIT(5))) >> 4; + external_common_state->it_scan_info = (etag[2] & + (BIT(3) | BIT(2))) >> 2; + external_common_state->ce_scan_info = etag[2] & + (BIT(1) | BIT(0)); + DEV_DBG("EDID: Scan Information (pt|it|ce): " + "(%d|%d|%d)", + external_common_state->pt_scan_info, + external_common_state->it_scan_info, + external_common_state->ce_scan_info); + break; + default: + DEV_DBG("EDID: Extend Tag Code %d not" + "supported\n", etag[1]); + break; + } + } + + /* There could be more that one extended data block */ + start_offset = etag - in_buf + len + 1; + etag = hdmi_edid_find_block(in_buf, start_offset, 7, &len); + } +} + +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 as 4:3 if within specificed range , rathaer than being + * absolute value + */ + aspect_ratio_4_3 = (abs(img_size_h * 3 - img_size_v * 4) < 5) ? 1 : 0; + + max_num_of_elements = sizeof(hdmi_edid_disp_mode_lut) + / sizeof(*hdmi_edid_disp_mode_lut); + + /* EDID_TIMING_DESC_INTERLACE[0x11:7]: Relative Offset to the EDID + * detailed timing descriptors - Interlace flag */ + DEV_DBG("Interlaced mode byte data_buf[0x11]=[%x]\n", data_buf[0x11]); + /* + * CEA 861-D: interlaced bit is bit[7] of byte[0x11] + */ + interlaced = (data_buf[0x11] & 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) { + if (mhl_is_connected()) { + const struct hdmi_disp_mode_timing_type *mhl_timing = + hdmi_mhl_get_supported_mode(video_format); + boolean mhl_supported = mhl_timing != NULL; + DEV_DBG("EDID: format: %d [%s], %s by MHL\n", + video_format, video_format_2string(video_format), + mhl_supported ? "Supported" : "Not-Supported"); + if (mhl_supported) + disp_mode_list->disp_mode_list[ + disp_mode_list->num_of_elements++] = video_format; + } else + disp_mode_list->disp_mode_list[ + disp_mode_list->num_of_elements++] = video_format; + } +} + +const char *single_video_3d_format_2string(uint32 format) +{ + switch (format) { + case TOP_AND_BOTTOM: return "TAB"; + case FRAME_PACKING: return "FP"; + case SIDE_BY_SIDE_HALF: return "SSH"; + } + return ""; +} + +ssize_t video_3d_format_2string(uint32 format, char *buf) +{ + ssize_t ret, len = 0; + ret = snprintf(buf, PAGE_SIZE, "%s", + single_video_3d_format_2string(format & FRAME_PACKING)); + len += ret; + + if (len && (format & TOP_AND_BOTTOM)) + ret = snprintf(buf + len, PAGE_SIZE, ":%s", + single_video_3d_format_2string( + format & TOP_AND_BOTTOM)); + else + ret = snprintf(buf + len, PAGE_SIZE, "%s", + single_video_3d_format_2string( + format & TOP_AND_BOTTOM)); + len += ret; + + if (len && (format & SIDE_BY_SIDE_HALF)) + ret = snprintf(buf + len, PAGE_SIZE, ":%s", + single_video_3d_format_2string( + format & SIDE_BY_SIDE_HALF)); + else + ret = snprintf(buf + len, PAGE_SIZE, "%s", + single_video_3d_format_2string( + format & SIDE_BY_SIDE_HALF)); + len += ret; + + return len; +} + +static void add_supported_3d_format( + struct hdmi_disp_mode_list_type *disp_mode_list, + uint32 video_format, + uint32 video_3d_format) +{ + char string[128]; + boolean added = FALSE; + int i; + for (i = 0; i < disp_mode_list->num_of_elements; ++i) { + if (disp_mode_list->disp_mode_list[i] == video_format) { + disp_mode_list->disp_3d_mode_list[i] |= + video_3d_format; + added = TRUE; + break; + } + } + video_3d_format_2string(video_3d_format, string); + DEV_DBG("EDID[3D]: format: %d [%s], %s %s\n", + video_format, video_format_2string(video_format), + string, added ? "added" : "NOT added"); +} + +static void hdmi_edid_get_display_vsd_3d_mode(const uint8 *data_buf, + struct hdmi_disp_mode_list_type *disp_mode_list, + uint32 num_og_cea_blocks) +{ + uint8 len, offset, present_multi_3d, hdmi_vic_len, hdmi_3d_len; + uint16 structure_all, structure_mask; + const uint8 *vsd = num_og_cea_blocks ? + hdmi_edid_find_block(data_buf+0x80, DBC_START_OFFSET, + 3, &len) : NULL; + int i; + + offset = HDMI_VSDB_3D_DATA_OFFSET(vsd); + present_multi_3d = (vsd[offset] & 0x60) >> 5; + + offset += 1; + hdmi_vic_len = (vsd[offset] >> 5) & 0x7; + hdmi_3d_len = vsd[offset] & 0x1F; + DEV_DBG("EDID[3D]: HDMI_VIC_LEN = %d, HDMI_3D_LEN = %d\n", + hdmi_vic_len, hdmi_3d_len); + + offset += (hdmi_vic_len + 1); + if (present_multi_3d == 1 || present_multi_3d == 2) { + DEV_DBG("EDID[3D]: multi 3D present (%d)\n", present_multi_3d); + /* 3d_structure_all */ + structure_all = (vsd[offset] << 8) | vsd[offset + 1]; + offset += 2; + hdmi_3d_len -= 2; + if (present_multi_3d == 2) { + /* 3d_structure_mask */ + structure_mask = (vsd[offset] << 8) | vsd[offset + 1]; + offset += 2; + hdmi_3d_len -= 2; + } else + structure_mask = 0xffff; + + i = 0; + while (i < 16) { + if (i >= disp_mode_list->disp_multi_3d_mode_list_cnt) + break; + + if (!(structure_mask & BIT(i))) { + ++i; + continue; + } + + /* BIT0: FRAME PACKING */ + if (structure_all & BIT(0)) + add_supported_3d_format(disp_mode_list, + disp_mode_list-> + disp_multi_3d_mode_list[i], + FRAME_PACKING); + + /* BIT6: TOP AND BOTTOM */ + if (structure_all & BIT(6)) + add_supported_3d_format(disp_mode_list, + disp_mode_list-> + disp_multi_3d_mode_list[i], + TOP_AND_BOTTOM); + + /* BIT8: SIDE BY SIDE HALF */ + if (structure_all & BIT(8)) + add_supported_3d_format(disp_mode_list, + disp_mode_list-> + disp_multi_3d_mode_list[i], + SIDE_BY_SIDE_HALF); + + ++i; + } + } + + i = 0; + while (hdmi_3d_len > 0) { + DEV_DBG("EDID[3D]: 3D_Structure_%d @ %d: %02x\n", + i + 1, offset, vsd[offset]); + + if ((vsd[offset] >> 4) >= + disp_mode_list->disp_multi_3d_mode_list_cnt) { + if ((vsd[offset] & 0x0F) >= 8) { + offset += 1; + hdmi_3d_len -= 1; + DEV_DBG("EDID[3D]: 3D_Detail_%d @ %d: %02x\n", + i + 1, offset, vsd[offset]); + } + i += 1; + offset += 1; + hdmi_3d_len -= 1; + continue; + } + + switch (vsd[offset] & 0x0F) { + case 0: + /* 0000b: FRAME PACKING */ + add_supported_3d_format(disp_mode_list, + disp_mode_list->disp_multi_3d_mode_list + [vsd[offset] >> 4], + FRAME_PACKING); + break; + case 6: + /* 0110b: TOP AND BOTTOM */ + add_supported_3d_format(disp_mode_list, + disp_mode_list->disp_multi_3d_mode_list + [vsd[offset] >> 4], + TOP_AND_BOTTOM); + break; + case 8: + /* 1000b: SIDE BY SIDE HALF */ + add_supported_3d_format(disp_mode_list, + disp_mode_list->disp_multi_3d_mode_list + [vsd[offset] >> 4], + SIDE_BY_SIDE_HALF); + break; + } + if ((vsd[offset] & 0x0F) >= 8) { + offset += 1; + hdmi_3d_len -= 1; + DEV_DBG("EDID[3D]: 3D_Detail_%d @ %d: %02x\n", + i + 1, offset, vsd[offset]); + } + i += 1; + offset += 1; + hdmi_3d_len -= 1; + } +} + +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 *edid_blk0 = &data_buf[0x0]; + const uint8 *edid_blk1 = &data_buf[0x80]; + const uint8 *svd = num_og_cea_blocks ? + hdmi_edid_find_block(data_buf+0x80, DBC_START_OFFSET, + 2, &len) : NULL; + boolean has60hz_mode = FALSE; + boolean has50hz_mode = FALSE; + + + disp_mode_list->num_of_elements = 0; + disp_mode_list->disp_multi_3d_mode_list_cnt = 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); + /* Make a note of the preferred video format */ + if (i == 0) { + external_common_state->preferred_video_format = + video_format; + } + if (i < 16) { + disp_mode_list->disp_multi_3d_mode_list[i] + = video_format; + disp_mode_list->disp_multi_3d_mode_list_cnt++; + } + + if (video_format <= HDMI_VFRMT_1920x1080p60_16_9 || + video_format == HDMI_VFRMT_2880x480p60_4_3 || + video_format == HDMI_VFRMT_2880x480p60_16_9) + has60hz_mode = TRUE; + + if ((video_format >= HDMI_VFRMT_720x576p50_4_3 && + video_format <= HDMI_VFRMT_1920x1080p50_16_9) || + video_format == HDMI_VFRMT_2880x576p50_4_3 || + video_format == HDMI_VFRMT_2880x576p50_16_9 || + video_format == HDMI_VFRMT_1920x1250i50_16_9) + has50hz_mode = TRUE; + 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 != edid_blk0[0x36+desc_offset]) { + hdmi_edid_detail_desc(edid_blk0+0x36+desc_offset, + &video_format); + DEV_DBG("[%s:%d] Block-0 Adding vid fmt = [%s]\n", + __func__, __LINE__, + video_format_2string(video_format)); + add_supported_video_format(disp_mode_list, + video_format); + if (video_format == HDMI_VFRMT_640x480p60_4_3) + has480p = TRUE; + /* Make a note of the preferred video format */ + if (i == 0) { + external_common_state->preferred_video_format = + video_format; + } + desc_offset += 0x12; + ++i; + } + } else if (1 == num_og_cea_blocks) { + uint32 desc_offset = 0; + + /* + * Read from both block 0 and block 1 + * Read EDID block[0] as above + */ + while (4 > i && 0 != edid_blk0[0x36+desc_offset]) { + hdmi_edid_detail_desc(edid_blk0+0x36+desc_offset, + &video_format); + DEV_DBG("[%s:%d] Block-0 Adding vid fmt = [%s]\n", + __func__, __LINE__, + video_format_2string(video_format)); + add_supported_video_format(disp_mode_list, + video_format); + if (video_format == HDMI_VFRMT_640x480p60_4_3) + has480p = TRUE; + /* Make a note of the preferred video format */ + if (i == 0) { + external_common_state->preferred_video_format = + video_format; + } + desc_offset += 0x12; + ++i; + } + + /* 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 = edid_blk1[0x02]; + while (0 != edid_blk1[desc_offset]) { + hdmi_edid_detail_desc(edid_blk1+desc_offset, + &video_format); + DEV_DBG("[%s:%d] Block-1 Adding vid fmt = [%s]\n", + __func__, __LINE__, + video_format_2string(video_format)); + add_supported_video_format(disp_mode_list, + video_format); + if (video_format == HDMI_VFRMT_640x480p60_4_3) + has480p = TRUE; + /* Make a note of the preferred video format */ + if (i == 0) { + external_common_state->preferred_video_format = + video_format; + } + desc_offset += 0x12; + ++i; + } + } + + /* mandaroty 3d format */ + if (external_common_state->present_3d) { + if (has60hz_mode) { + add_supported_3d_format(disp_mode_list, + HDMI_VFRMT_1920x1080p24_16_9, + FRAME_PACKING | TOP_AND_BOTTOM); + add_supported_3d_format(disp_mode_list, + HDMI_VFRMT_1280x720p60_16_9, + FRAME_PACKING | TOP_AND_BOTTOM); + add_supported_3d_format(disp_mode_list, + HDMI_VFRMT_1920x1080i60_16_9, + SIDE_BY_SIDE_HALF); + } + if (has50hz_mode) { + add_supported_3d_format(disp_mode_list, + HDMI_VFRMT_1920x1080p24_16_9, + FRAME_PACKING | TOP_AND_BOTTOM); + add_supported_3d_format(disp_mode_list, + HDMI_VFRMT_1280x720p50_16_9, + FRAME_PACKING | TOP_AND_BOTTOM); + add_supported_3d_format(disp_mode_list, + HDMI_VFRMT_1920x1080i50_16_9, + SIDE_BY_SIDE_HALF); + } + + /* 3d format described in Vendor Specific Data */ + hdmi_edid_get_display_vsd_3d_mode(data_buf, disp_mode_list, + num_og_cea_blocks); + } + + 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, print_len; +#ifdef DEBUG + const u8 *b = edid_buf; +#endif + int status = external_common_state->read_edid_block(block, edid_buf); + if (status) + goto error; + + /* Calculate checksum */ + check_sum = 0; + for (ndx = 0; ndx < 0x80; ++ndx) + check_sum += edid_buf[ndx]; + + if (check_sum & 0xFF) { + 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; + } + print_len = 0x80; + for (ndx = 0; ndx < print_len; 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]); + + +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->preferred_video_format = 0; + 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); + hdmi_edid_extract_extended_data_blocks(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[2]) { + format = var->reserved[2]-1; + DEV_DBG("reserved format is %d\n", format); + } 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); + +const struct hdmi_disp_mode_timing_type *hdmi_mhl_get_mode(uint32 mode) +{ + if (mode >= HDMI_VFRMT_MAX) + return NULL; + + return &hdmi_mhl_supported_video_mode_lut[mode]; +} +EXPORT_SYMBOL(hdmi_mhl_get_mode); + +const struct hdmi_disp_mode_timing_type *hdmi_mhl_get_supported_mode( + uint32 mode) +{ + const struct hdmi_disp_mode_timing_type *ret + = hdmi_mhl_get_mode(mode); + + if (ret == NULL || !ret->supported) + return NULL; + return ret; +} +EXPORT_SYMBOL(hdmi_mhl_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->frame_rate = 60; + + 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; + if (hdmi_prim_display) + pinfo->fb_num = 2; + else + 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 0000000000000000000000000000000000000000..57c0804b0955a832254bbfbfdc1f7511ac0066e8 --- /dev/null +++ b/drivers/video/msm/external_common.h @@ -0,0 +1,272 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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__ +#include + +#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]; +#define TOP_AND_BOTTOM 0x10 +#define FRAME_PACKING 0x20 +#define SIDE_BY_SIDE_HALF 0x40 + uint32 disp_3d_mode_list[HDMI_VFRMT_MAX]; + uint32 disp_multi_3d_mode_list[16]; + uint32 disp_multi_3d_mode_list_cnt; + uint32 num_of_elements; +}; +#endif + +struct external_common_state_type { + boolean hpd_state; + struct kobject *uevent_kobj; + uint32 video_resolution; + struct device *dev; + struct switch_dev sdev; +#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; + uint16 physical_address; + uint32 preferred_video_format; + uint8 pt_scan_info; + uint8 it_scan_info; + uint8 ce_scan_info; + uint8 spd_vendor_name[8]; + uint8 spd_product_description[16]; + 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; +extern struct mutex hdmi_msm_state_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); +const struct hdmi_disp_mode_timing_type *hdmi_mhl_get_mode(uint32 mode); +const struct hdmi_disp_mode_timing_type *hdmi_mhl_get_supported_mode( + uint32 mode); +void hdmi_common_init_panel_info(struct msm_panel_info *pinfo); + +ssize_t video_3d_format_2string(uint32 format, char *buf); +#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 0000000000000000000000000000000000000000..a3720169307796fe12ba3a7c1615fc98301993b3 --- /dev/null +++ b/drivers/video/msm/hdmi_msm.c @@ -0,0 +1,4817 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 */ + +#define CEC_MSG_PRINT +#define TOGGLE_CEC_HARDWARE_FSM + +#include +#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 + +static int msm_hdmi_sample_rate = MSM_HDMI_SAMPLE_RATE_48KHZ; + +/* HDMI/HDCP Registers */ +#define HDCP_DDC_STATUS 0x0128 +#define HDCP_DDC_CTRL_0 0x0120 +#define HDCP_DDC_CTRL_1 0x0124 +#define HDMI_DDC_CTRL 0x020C + +struct workqueue_struct *hdmi_work_queue; +struct hdmi_msm_state_type *hdmi_msm_state; + +DEFINE_MUTEX(hdmi_msm_state_mutex); +EXPORT_SYMBOL(hdmi_msm_state_mutex); +static DEFINE_MUTEX(hdcp_auth_state_mutex); + +static void hdmi_msm_dump_regs(const char *prefix); + +#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 + +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); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + +static void hdmi_msm_cec_line_latch_detect(void); + +#ifdef TOGGLE_CEC_HARDWARE_FSM +static boolean msg_send_complete = TRUE; +static boolean msg_recv_complete = TRUE; +#endif + +#define HDMI_MSM_CEC_REFTIMER_REFTIMER_ENABLE BIT(16) +#define HDMI_MSM_CEC_REFTIMER_REFTIMER(___t) (((___t)&0xFFFF) << 0) + +#define HDMI_MSM_CEC_TIME_SIGNAL_FREE_TIME(___t) (((___t)&0x1FF) << 7) +#define HDMI_MSM_CEC_TIME_ENABLE BIT(0) + +#define HDMI_MSM_CEC_ADDR_LOGICAL_ADDR(___la) (((___la)&0xFF) << 0) + +#define HDMI_MSM_CEC_CTRL_LINE_OE BIT(9) +#define HDMI_MSM_CEC_CTRL_FRAME_SIZE(___sz) (((___sz)&0x1F) << 4) +#define HDMI_MSM_CEC_CTRL_SOFT_RESET BIT(2) +#define HDMI_MSM_CEC_CTRL_SEND_TRIG BIT(1) +#define HDMI_MSM_CEC_CTRL_ENABLE BIT(0) + +#define HDMI_MSM_CEC_INT_FRAME_RD_DONE_MASK BIT(7) +#define HDMI_MSM_CEC_INT_FRAME_RD_DONE_ACK BIT(6) +#define HDMI_MSM_CEC_INT_FRAME_RD_DONE_INT BIT(6) +#define HDMI_MSM_CEC_INT_MONITOR_MASK BIT(5) +#define HDMI_MSM_CEC_INT_MONITOR_ACK BIT(4) +#define HDMI_MSM_CEC_INT_MONITOR_INT BIT(4) +#define HDMI_MSM_CEC_INT_FRAME_ERROR_MASK BIT(3) +#define HDMI_MSM_CEC_INT_FRAME_ERROR_ACK BIT(2) +#define HDMI_MSM_CEC_INT_FRAME_ERROR_INT BIT(2) +#define HDMI_MSM_CEC_INT_FRAME_WR_DONE_MASK BIT(1) +#define HDMI_MSM_CEC_INT_FRAME_WR_DONE_ACK BIT(0) +#define HDMI_MSM_CEC_INT_FRAME_WR_DONE_INT BIT(0) + +#define HDMI_MSM_CEC_FRAME_WR_SUCCESS(___st) (((___st)&0xB) ==\ + (HDMI_MSM_CEC_INT_FRAME_WR_DONE_INT |\ + HDMI_MSM_CEC_INT_FRAME_WR_DONE_MASK |\ + HDMI_MSM_CEC_INT_FRAME_ERROR_MASK)) + +#define HDMI_MSM_CEC_RETRANSMIT_NUM(___num) (((___num)&0xF) << 4) +#define HDMI_MSM_CEC_RETRANSMIT_ENABLE BIT(0) + +#define HDMI_MSM_CEC_WR_DATA_DATA(___d) (((___d)&0xFF) << 8) + + +void hdmi_msm_cec_init(void) +{ + /* 0x02A8 CEC_REFTIMER */ + HDMI_OUTP(0x02A8, + HDMI_MSM_CEC_REFTIMER_REFTIMER_ENABLE + | HDMI_MSM_CEC_REFTIMER_REFTIMER(27 * 50) + ); + + /* + * 0x02A0 CEC_ADDR + * Starting with a default address of 4 + */ + HDMI_OUTP(0x02A0, HDMI_MSM_CEC_ADDR_LOGICAL_ADDR(4)); + + hdmi_msm_state->first_monitor = 0; + hdmi_msm_state->fsm_reset_done = false; + + /* 0x029C CEC_INT */ + /* Enable CEC interrupts */ + HDMI_OUTP(0x029C, \ + HDMI_MSM_CEC_INT_FRAME_WR_DONE_MASK \ + | HDMI_MSM_CEC_INT_FRAME_ERROR_MASK \ + | HDMI_MSM_CEC_INT_MONITOR_MASK \ + | HDMI_MSM_CEC_INT_FRAME_RD_DONE_MASK); + + HDMI_OUTP(0x02B0, 0x7FF << 4 | 1); + + /* + * Slight adjustment to logic 1 low periods on read, + * CEC Test 8.2-3 was failing, 8 for the + * BIT_1_ERR_RANGE_HI = 8 => 750us, the test used 775us, + * so increased this to 9 which => 800us. + */ + /* + * CEC latch up issue - To fire monitor interrupt + * for every start of message + */ + HDMI_OUTP(0x02E0, 0x880000); + + /* + * Slight adjustment to logic 0 low period on write + */ + HDMI_OUTP(0x02DC, 0x8888A888); + + /* + * Enable Signal Free Time counter and set to 7 bit periods + */ + HDMI_OUTP(0x02A4, 0x1 | (7 * 0x30) << 7); + + /* 0x028C CEC_CTRL */ + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); +} + +void hdmi_msm_cec_write_logical_addr(int addr) +{ + /* 0x02A0 CEC_ADDR + * LOGICAL_ADDR 7:0 NUM + */ + HDMI_OUTP(0x02A0, addr & 0xFF); +} + +void hdmi_msm_dump_cec_msg(struct hdmi_msm_cec_msg *msg) +{ +#ifdef CEC_MSG_PRINT + int i; + DEV_DBG("sender_id : %d", msg->sender_id); + DEV_DBG("recvr_id : %d", msg->recvr_id); + if (msg->frame_size < 2) { + DEV_DBG("polling message"); + return; + } + DEV_DBG("opcode : %02x", msg->opcode); + for (i = 0; i < msg->frame_size - 2; i++) + DEV_DBG("operand(%2d) : %02x", i + 1, msg->operand[i]); +#endif /* CEC_MSG_PRINT */ +} + +void hdmi_msm_cec_msg_send(struct hdmi_msm_cec_msg *msg) +{ + int i; + uint32 timeout_count = 1; + int retry = 10; + + boolean frameType = (msg->recvr_id == 15 ? BIT(0) : 0); + + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->fsm_reset_done = false; + mutex_unlock(&hdmi_msm_state_mutex); +#ifdef TOGGLE_CEC_HARDWARE_FSM + msg_send_complete = FALSE; +#endif + + INIT_COMPLETION(hdmi_msm_state->cec_frame_wr_done); + hdmi_msm_state->cec_frame_wr_status = 0; + + /* 0x0294 HDMI_MSM_CEC_RETRANSMIT */ + HDMI_OUTP(0x0294, +#ifdef DRVR_ONLY_CECT_NO_DAEMON + HDMI_MSM_CEC_RETRANSMIT_NUM(msg->retransmit) + | (msg->retransmit > 0) ? HDMI_MSM_CEC_RETRANSMIT_ENABLE : 0); +#else + HDMI_MSM_CEC_RETRANSMIT_NUM(0) | + HDMI_MSM_CEC_RETRANSMIT_ENABLE); +#endif + + /* 0x028C CEC_CTRL */ + HDMI_OUTP(0x028C, 0x1 | msg->frame_size << 4); + + /* 0x0290 CEC_WR_DATA */ + + /* header block */ + HDMI_OUTP(0x0290, + HDMI_MSM_CEC_WR_DATA_DATA(msg->sender_id << 4 | msg->recvr_id) + | frameType); + + /* data block 0 : opcode */ + HDMI_OUTP(0x0290, + HDMI_MSM_CEC_WR_DATA_DATA(msg->frame_size < 2 ? 0 : msg->opcode) + | frameType); + + /* data block 1-14 : operand 0-13 */ + for (i = 0; i < msg->frame_size - 1; i++) + HDMI_OUTP(0x0290, + HDMI_MSM_CEC_WR_DATA_DATA(msg->operand[i]) + | (msg->recvr_id == 15 ? BIT(0) : 0)); + + for (; i < 14; i++) + HDMI_OUTP(0x0290, + HDMI_MSM_CEC_WR_DATA_DATA(0) + | (msg->recvr_id == 15 ? BIT(0) : 0)); + + while ((HDMI_INP(0x0298) & 1) && retry--) { + DEV_DBG("CEC line is busy(%d)\n", retry); + schedule(); + } + + /* 0x028C CEC_CTRL */ + HDMI_OUTP(0x028C, + HDMI_MSM_CEC_CTRL_LINE_OE + | HDMI_MSM_CEC_CTRL_FRAME_SIZE(msg->frame_size) + | HDMI_MSM_CEC_CTRL_SEND_TRIG + | HDMI_MSM_CEC_CTRL_ENABLE); + + timeout_count = wait_for_completion_interruptible_timeout( + &hdmi_msm_state->cec_frame_wr_done, HZ); + + if (!timeout_count) { + hdmi_msm_state->cec_frame_wr_status |= CEC_STATUS_WR_TMOUT; + DEV_ERR("%s: timedout", __func__); + hdmi_msm_dump_cec_msg(msg); + } else { + DEV_DBG("CEC write frame done (frame len=%d)", + msg->frame_size); + hdmi_msm_dump_cec_msg(msg); + } + +#ifdef TOGGLE_CEC_HARDWARE_FSM + if (!msg_recv_complete) { + /* Toggle CEC hardware FSM */ + HDMI_OUTP(0x028C, 0x0); + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); + msg_recv_complete = TRUE; + } + msg_send_complete = TRUE; +#else + HDMI_OUTP(0x028C, 0x0); + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); +#endif +} + +void hdmi_msm_cec_line_latch_detect(void) +{ + /* + * CECT 9-5-1 + * The timer period needs to be changed to appropriate value + */ + /* + * Timedout without RD_DONE, WR_DONE or ERR_INT + * Toggle CEC hardware FSM + */ + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->first_monitor == 1) { + DEV_WARN("CEC line is probably latched up - CECT 9-5-1"); + if (!msg_recv_complete) + hdmi_msm_state->fsm_reset_done = true; + HDMI_OUTP(0x028C, 0x0); + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); + hdmi_msm_state->first_monitor = 0; + } + mutex_unlock(&hdmi_msm_state_mutex); +} + +void hdmi_msm_cec_msg_recv(void) +{ + uint32 data; + int i; +#ifdef DRVR_ONLY_CECT_NO_DAEMON + struct hdmi_msm_cec_msg temp_msg; +#endif + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->cec_queue_wr == hdmi_msm_state->cec_queue_rd + && hdmi_msm_state->cec_queue_full) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_ERR("CEC message queue is overflowing\n"); +#ifdef DRVR_ONLY_CECT_NO_DAEMON + /* + * Without CEC daemon: + * Compliance tests fail once the queue gets filled up. + * so reset the pointers to the start of the queue. + */ + hdmi_msm_state->cec_queue_wr = hdmi_msm_state->cec_queue_start; + hdmi_msm_state->cec_queue_rd = hdmi_msm_state->cec_queue_start; + hdmi_msm_state->cec_queue_full = false; +#else + return; +#endif + } + if (hdmi_msm_state->cec_queue_wr == NULL) { + DEV_ERR("%s: wp is NULL\n", __func__); + return; + } + mutex_unlock(&hdmi_msm_state_mutex); + + /* 0x02AC CEC_RD_DATA */ + data = HDMI_INP(0x02AC); + + hdmi_msm_state->cec_queue_wr->sender_id = (data & 0xF0) >> 4; + hdmi_msm_state->cec_queue_wr->recvr_id = (data & 0x0F); + hdmi_msm_state->cec_queue_wr->frame_size = (data & 0x1F00) >> 8; + DEV_DBG("Recvd init=[%u] dest=[%u] size=[%u]\n", + hdmi_msm_state->cec_queue_wr->sender_id, + hdmi_msm_state->cec_queue_wr->recvr_id, + hdmi_msm_state->cec_queue_wr->frame_size); + + if (hdmi_msm_state->cec_queue_wr->frame_size < 1) { + DEV_ERR("%s: invalid message (frame length = %d)", + __func__, hdmi_msm_state->cec_queue_wr->frame_size); + return; + } else if (hdmi_msm_state->cec_queue_wr->frame_size == 1) { + DEV_DBG("%s: polling message (dest[%x] <- init[%x])", + __func__, + hdmi_msm_state->cec_queue_wr->recvr_id, + hdmi_msm_state->cec_queue_wr->sender_id); + return; + } + + /* data block 0 : opcode */ + data = HDMI_INP(0x02AC); + hdmi_msm_state->cec_queue_wr->opcode = data & 0xFF; + + /* data block 1-14 : operand 0-13 */ + for (i = 0; i < hdmi_msm_state->cec_queue_wr->frame_size - 2; i++) { + data = HDMI_INP(0x02AC); + hdmi_msm_state->cec_queue_wr->operand[i] = data & 0xFF; + } + + for (; i < 14; i++) + hdmi_msm_state->cec_queue_wr->operand[i] = 0; + + DEV_DBG("CEC read frame done\n"); + DEV_DBG("=======================================\n"); + hdmi_msm_dump_cec_msg(hdmi_msm_state->cec_queue_wr); + DEV_DBG("=======================================\n"); + +#ifdef DRVR_ONLY_CECT_NO_DAEMON + switch (hdmi_msm_state->cec_queue_wr->opcode) { + case 0x64: + /* Set OSD String */ + DEV_INFO("Recvd OSD Str=[%x]\n",\ + hdmi_msm_state->cec_queue_wr->operand[3]); + break; + case 0x83: + /* Give Phy Addr */ + DEV_INFO("Recvd a Give Phy Addr cmd\n"); + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + /* Setup a frame for sending out phy addr */ + temp_msg.sender_id = 0x4; + + /* Broadcast */ + temp_msg.recvr_id = 0xf; + temp_msg.opcode = 0x84; + i = 0; + temp_msg.operand[i++] = 0x10; + temp_msg.operand[i++] = 0x00; + temp_msg.operand[i++] = 0x04; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + break; + case 0xFF: + /* Abort */ + DEV_INFO("Recvd an abort cmd 0xFF\n"); + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + + /*feature abort */ + temp_msg.opcode = 0x00; + temp_msg.operand[i++] = + hdmi_msm_state->cec_queue_wr->opcode; + + /*reason for abort = "Refused" */ + temp_msg.operand[i++] = 0x04; + temp_msg.frame_size = i + 2; + hdmi_msm_dump_cec_msg(&temp_msg); + hdmi_msm_cec_msg_send(&temp_msg); + break; + case 0x046: + /* Give OSD name */ + DEV_INFO("Recvd cmd 0x046\n"); + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + + /* OSD Name */ + temp_msg.opcode = 0x47; + + /* Display control byte */ + temp_msg.operand[i++] = 0x00; + temp_msg.operand[i++] = 'H'; + temp_msg.operand[i++] = 'e'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'o'; + temp_msg.operand[i++] = ' '; + temp_msg.operand[i++] = 'W'; + temp_msg.operand[i++] = 'o'; + temp_msg.operand[i++] = 'r'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'd'; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + break; + case 0x08F: + /* Give Device Power status */ + DEV_INFO("Recvd a Power status message\n"); + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + + /* OSD String */ + temp_msg.opcode = 0x90; + temp_msg.operand[i++] = 'H'; + temp_msg.operand[i++] = 'e'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'o'; + temp_msg.operand[i++] = ' '; + temp_msg.operand[i++] = 'W'; + temp_msg.operand[i++] = 'o'; + temp_msg.operand[i++] = 'r'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'd'; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + break; + case 0x080: + /* Routing Change cmd */ + case 0x086: + /* Set Stream Path */ + DEV_INFO("Recvd Set Stream\n"); + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + + /*Broadcast this message*/ + temp_msg.recvr_id = 0xf; + i = 0; + temp_msg.opcode = 0x82; /* Active Source */ + temp_msg.operand[i++] = 0x10; + temp_msg.operand[i++] = 0x00; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + + /* + * sending message + */ + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + /* opcode for Image View On */ + temp_msg.opcode = 0x04; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + break; + case 0x44: + /* User Control Pressed */ + DEV_INFO("User Control Pressed\n"); + break; + case 0x45: + /* User Control Released */ + DEV_INFO("User Control Released\n"); + break; + default: + DEV_INFO("Recvd an unknown cmd = [%u]\n", + hdmi_msm_state->cec_queue_wr->opcode); +#ifdef __SEND_ABORT__ + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + /* opcode for feature abort */ + temp_msg.opcode = 0x00; + temp_msg.operand[i++] = + hdmi_msm_state->cec_queue_wr->opcode; + /*reason for abort = "Unrecognized opcode" */ + temp_msg.operand[i++] = 0x00; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + break; +#else + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + /* OSD String */ + temp_msg.opcode = 0x64; + temp_msg.operand[i++] = 0x0; + temp_msg.operand[i++] = 'H'; + temp_msg.operand[i++] = 'e'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'o'; + temp_msg.operand[i++] = ' '; + temp_msg.operand[i++] = 'W'; + temp_msg.operand[i++] = 'o'; + temp_msg.operand[i++] = 'r'; + temp_msg.operand[i++] = 'l'; + temp_msg.operand[i++] = 'd'; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + break; +#endif /* __SEND_ABORT__ */ + } + +#endif /* DRVR_ONLY_CECT_NO_DAEMON */ + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->cec_queue_wr++; + if (hdmi_msm_state->cec_queue_wr == CEC_QUEUE_END) + hdmi_msm_state->cec_queue_wr = hdmi_msm_state->cec_queue_start; + if (hdmi_msm_state->cec_queue_wr == hdmi_msm_state->cec_queue_rd) + hdmi_msm_state->cec_queue_full = true; + mutex_unlock(&hdmi_msm_state_mutex); + DEV_DBG("Exiting %s()\n", __func__); +} + +void hdmi_msm_cec_one_touch_play(void) +{ + struct hdmi_msm_cec_msg temp_msg; + uint32 i = 0; + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + /* + * Broadcast this message + */ + temp_msg.recvr_id = 0xf; + i = 0; + /* Active Source */ + temp_msg.opcode = 0x82; + temp_msg.operand[i++] = 0x10; + temp_msg.operand[i++] = 0x00; + /*temp_msg.operand[i++] = 0x04;*/ + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + /* + * sending message + */ + memset(&temp_msg, 0x00, sizeof(struct hdmi_msm_cec_msg)); + temp_msg.sender_id = 0x4; + temp_msg.recvr_id = hdmi_msm_state->cec_queue_wr->sender_id; + i = 0; + /* Image View On */ + temp_msg.opcode = 0x04; + temp_msg.frame_size = i + 2; + hdmi_msm_cec_msg_send(&temp_msg); + +} +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT */ + +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; + } + + DEV_DBG("%s:Got interrupt\n", __func__); + /* 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); + /* QDSP OFF preceding the HPD event notification */ + 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); + switch_set_state(&external_common_state->sdev, 0); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); + 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 */ + 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(); +#ifndef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + /* Send Audio for HDMI Compliance Cases*/ + envp[0] = "HDCP_STATE=PASS"; + envp[1] = NULL; + DEV_INFO("HDMI HPD: sense : send HDCP_PASS\n"); + kobject_uevent_env(external_common_state->uevent_kobj, + KOBJ_CHANGE, envp); + switch_set_state(&external_common_state->sdev, 1); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); +#endif + } else { + DEV_INFO("HDMI HPD: sense DISCONNECTED: send OFFLINE\n" + ); + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_OFFLINE); + switch_set_state(&external_common_state->sdev, 0); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); + } + } + + /* 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_CEC_SUPPORT +static void hdmi_msm_cec_latch_work(struct work_struct *work) +{ + hdmi_msm_cec_line_latch_detect(); +} +#endif + +#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) +{ + + /* 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) +{ + + /* 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; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + uint32 cec_intr_status; +#endif + 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; + } + + /* 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 + * Leaving the bit[2] on, else core goes off + * on getting HPD during power off + */ + HDMI_OUTP(0x0254, (1 << 2) | (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); + switch_set_state(&external_common_state->sdev, 0); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); + 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__); + + /* Clear AUTH_FAIL_INFO as well */ + HDMI_OUTP(0x0118, (hdcp_int_val | (1 << 7))); + 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 */ + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + /* Process CEC Interrupt */ + /* HDMI_MSM_CEC_INT[0x029C] */ + cec_intr_status = HDMI_INP_ND(0x029C); + + DEV_DBG("cec interrupt status is [%u]\n", cec_intr_status); + + if (HDMI_MSM_CEC_FRAME_WR_SUCCESS(cec_intr_status)) { + DEV_DBG("CEC_IRQ_FRAME_WR_DONE\n"); + HDMI_OUTP(0x029C, cec_intr_status | + HDMI_MSM_CEC_INT_FRAME_WR_DONE_ACK); + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->cec_frame_wr_status |= CEC_STATUS_WR_DONE; + hdmi_msm_state->first_monitor = 0; + del_timer(&hdmi_msm_state->cec_read_timer); + mutex_unlock(&hdmi_msm_state_mutex); + complete(&hdmi_msm_state->cec_frame_wr_done); + return IRQ_HANDLED; + } + if ((cec_intr_status & (1 << 2)) && (cec_intr_status & (1 << 3))) { + DEV_DBG("CEC_IRQ_FRAME_ERROR\n"); +#ifdef TOGGLE_CEC_HARDWARE_FSM + /* Toggle CEC hardware FSM */ + HDMI_OUTP(0x028C, 0x0); + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); +#endif + HDMI_OUTP(0x029C, cec_intr_status); + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->first_monitor = 0; + del_timer(&hdmi_msm_state->cec_read_timer); + hdmi_msm_state->cec_frame_wr_status |= CEC_STATUS_WR_ERROR; + mutex_unlock(&hdmi_msm_state_mutex); + complete(&hdmi_msm_state->cec_frame_wr_done); + return IRQ_HANDLED; + } + + if ((cec_intr_status & (1 << 4)) && (cec_intr_status & (1 << 5))) { + DEV_DBG("CEC_IRQ_MONITOR\n"); + HDMI_OUTP(0x029C, cec_intr_status | + HDMI_MSM_CEC_INT_MONITOR_ACK); + + /* + * CECT 9-5-1 + * On the first occassion start a timer + * for few hundred ms, if it expires then + * reset the CEC block else go on with + * frame transactions as usual. + * Below adds hdmi_msm_cec_msg_recv() as an + * item into the work queue instead of running in + * interrupt context + */ + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->first_monitor == 0) { + /* This timer might have to be changed + * worst case theoritical = + * 16 bytes * 8 * 2.7msec = 346 msec + */ + mod_timer(&hdmi_msm_state->cec_read_timer, + jiffies + HZ/2); + hdmi_msm_state->first_monitor = 1; + } + mutex_unlock(&hdmi_msm_state_mutex); + return IRQ_HANDLED; + } + + if ((cec_intr_status & (1 << 6)) && (cec_intr_status & (1 << 7))) { + DEV_DBG("CEC_IRQ_FRAME_RD_DONE\n"); + + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->first_monitor = 0; + del_timer(&hdmi_msm_state->cec_read_timer); + mutex_unlock(&hdmi_msm_state_mutex); + HDMI_OUTP(0x029C, cec_intr_status | + HDMI_MSM_CEC_INT_FRAME_RD_DONE_ACK); + hdmi_msm_cec_msg_recv(); + +#ifdef TOGGLE_CEC_HARDWARE_FSM + if (!msg_send_complete) + msg_recv_complete = FALSE; + else { + /* Toggle CEC hardware FSM */ + HDMI_OUTP(0x028C, 0x0); + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); + } +#else + HDMI_OUTP(0x028C, 0x0); + HDMI_OUTP(0x028C, HDMI_MSM_CEC_CTRL_ENABLE); +#endif + + return IRQ_HANDLED; + } +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT */ + + DEV_DBG("%s: HPD, ddc_int_ctrl=%04x, " + "aud_int=%04x, cec_intr_status=%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; +} + +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; + if (external_common_state->present_hdcp) + /* HDMI Encryption */ + reg_val |= 0x00000004; + /* HDMI_CTRL */ + HDMI_OUTP(0x0000, reg_val); + /* HDMI_DVI_SEL */ + reg_val &= ~0x00000002; + } else { + if (external_common_state->present_hdcp) + /* HDMI_Encryption_ON */ + reg_val |= 0x00000006; + 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 + * Setting 31:24 bits : Time units to wait before timeout + * when clock is being stalled by external sink device + */ + HDMI_OUTP_ND(0x0224, 0xff000000); + + /* 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 = 0x5 (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 | (5 << 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); + + /* Disable HDCP interrupts */ + HDMI_OUTP(0x0118, 0x0); + + 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 void check_and_clear_HDCP_DDC_Failure(void) +{ + int hdcp_ddc_ctrl1_reg; + int hdcp_ddc_status; + int failure; + int nack0; + + /* + * Check for any DDC transfer failures + * 0x0128 HDCP_DDC_STATUS + * [16] FAILED Indicates that the last HDCP HW DDC transer + * failed. This occurs when a transfer is + * attempted with HDCP DDC disabled + * (HDCP_DDC_DISABLE=1) or the number of retries + * match HDCP_DDC_RETRY_CNT + * + * [14] NACK0 Indicates that the last HDCP HW DDC transfer + * was aborted due to a NACK on the first + * transaction - cleared by writing 0 to GO bit + */ + hdcp_ddc_status = HDMI_INP(HDCP_DDC_STATUS); + failure = (hdcp_ddc_status >> 16) & 0x1; + nack0 = (hdcp_ddc_status >> 14) & 0x1; + DEV_DBG("%s: On Entry: HDCP_DDC_STATUS = 0x%x, FAILURE = %d," + "NACK0 = %d\n", __func__ , hdcp_ddc_status, failure, nack0); + + if (failure == 0x1) { + /* + * Indicates that the last HDCP HW DDC transfer failed. + * This occurs when a transfer is attempted with HDCP DDC + * disabled (HDCP_DDC_DISABLE=1) or the number of retries + * matches HDCP_DDC_RETRY_CNT. + * Failure occured, let's clear it. + */ + DEV_INFO("%s: DDC failure detected. HDCP_DDC_STATUS=0x%08x\n", + __func__, hdcp_ddc_status); + /* + * First, Disable DDC + * 0x0120 HDCP_DDC_CTRL_0 + * [0] DDC_DISABLE Determines whether HDCP Ri and Pj reads + * are done unassisted by hardware or by + * software via HDMI_DDC (HDCP provides + * interrupts to request software + * transfers) + * 0 : Use Hardware DDC + * 1 : Use Software DDC + */ + HDMI_OUTP(HDCP_DDC_CTRL_0, 0x1); + + /* + * ACK the Failure to Clear it + * 0x0124 HDCP_DDC_CTRL_1 + * [0] DDC_FAILED_ACK Write 1 to clear + * HDCP_STATUS.HDCP_DDC_FAILED + */ + hdcp_ddc_ctrl1_reg = HDMI_INP(HDCP_DDC_CTRL_1); + HDMI_OUTP(HDCP_DDC_CTRL_1, hdcp_ddc_ctrl1_reg | 0x1); + + /* Check if the FAILURE got Cleared */ + hdcp_ddc_status = HDMI_INP(HDCP_DDC_STATUS); + hdcp_ddc_status = (hdcp_ddc_status >> 16) & 0x1; + if (hdcp_ddc_status == 0x0) { + DEV_INFO("%s: HDCP DDC Failure has been cleared\n", + __func__); + } else { + DEV_WARN("%s: Error: HDCP DDC Failure DID NOT get" + "cleared\n", __func__); + } + + /* Re-Enable HDCP DDC */ + HDMI_OUTP(HDCP_DDC_CTRL_0, 0x0); + } + + if (nack0 == 0x1) { + /* + * 0x020C HDMI_DDC_CTRL + * [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 + */ + HDMI_OUTP_ND(HDMI_DDC_CTRL, + HDMI_INP(HDMI_DDC_CTRL) | (0x1 << 3)); + msleep(20); + HDMI_OUTP_ND(HDMI_DDC_CTRL, + HDMI_INP(HDMI_DDC_CTRL) & ~(0x1 << 3)); + } + + hdcp_ddc_status = HDMI_INP(HDCP_DDC_STATUS); + + failure = (hdcp_ddc_status >> 16) & 0x1; + nack0 = (hdcp_ddc_status >> 14) & 0x1; + DEV_DBG("%s: On Exit: HDCP_DDC_STATUS = 0x%x, FAILURE = %d," + "NACK0 = %d\n", __func__ , hdcp_ddc_status, failure, nack0); +} + + +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 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, 0x0); + + /* 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)); + + /* + * Check to see if a HDCP DDC Failure is indicated in + * HDCP_DDC_STATUS. If yes, clear it. + */ + check_and_clear_HDCP_DDC_Failure(); + + /* 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 */ + + mutex_lock(&hdcp_auth_state_mutex); + 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); + mutex_unlock(&hdcp_auth_state_mutex); + 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); + mutex_unlock(&hdcp_auth_state_mutex); + + /* 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); + + /* 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); + + /* 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; + } + + DEV_DBG("HDCP: R0'=%02x%02x\n", buf[1], buf[0]); + INIT_COMPLETION(hdmi_msm_state->hdcp_success_done); + /* 0x013C HDCP_RCVPORT_DATA2_0 + [15:0] LINK0_RI */ + HDMI_OUTP(0x013C, (((uint32)buf[1]) << 8) | buf[0]); + + 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); + + /* Once 64 bytes have been written, we need to poll for + * HDCP_SHA_BLOCK_DONE before writing any further + */ + if (i && !((i+1)%64)) { + timeout_count = 100; + while (!(HDMI_INP_ND(0x0240) & 0x1) + && (--timeout_count)) { + DEV_DBG("HDCP Auth Part II: Waiting for the " + "computation of the current 64 byte to " + "complete. HDCP_SHA_STATUS=%08x. " + "timeout_count=%d\n", + HDMI_INP_ND(0x0240), timeout_count); + msleep(20); + } + if (!timeout_count) { + ret = -ETIMEDOUT; + DEV_ERR("%s(%d): timedout", __func__, __LINE__); + goto error; + } + } + + } + + /* 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) & 0xFFFFFF10)) && --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()) { + switch_set_state(&external_common_state->sdev, 1); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); + 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); + } + switch_set_state(&external_common_state->sdev, 1); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); + 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); + } + switch_set_state(&external_common_state->sdev, 0); + DEV_INFO("Hdmi state switch to %d: %s\n", + external_common_state->sdev.state, __func__); +} +#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.027MHz */ + HDMI_MSM_AUDIO_ARCS(27030, { + {4096, 27027}, {6272, 30030}, {6144, 27027}, {12544, 30030}, + {12288, 27027}, {25088, 30030}, {24576, 27027} }), + /* 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); + + mb(); + pr_info("%s :HDMI_AUDIO_CFG 0x%08x\n", __func__, + HDMI_INP(HDMI_AUDIO_CFG)); + + return 0; +} +EXPORT_SYMBOL(hdmi_audio_enable); + +#define HDMI_AUDIO_PKT_CTRL 0x0020 +#define HDMI_AUDIO_SAMPLE_SEND_ENABLE 1 + +int hdmi_audio_packet_enable(bool on) +{ + u32 hdmi_audio_pkt_ctrl; + hdmi_audio_pkt_ctrl = HDMI_INP(HDMI_AUDIO_PKT_CTRL); + + if (on) + hdmi_audio_pkt_ctrl |= HDMI_AUDIO_SAMPLE_SEND_ENABLE; + else + hdmi_audio_pkt_ctrl &= ~(HDMI_AUDIO_SAMPLE_SEND_ENABLE); + + HDMI_OUTP(HDMI_AUDIO_PKT_CTRL, hdmi_audio_pkt_ctrl); + + mb(); + pr_info("%s : HDMI_AUDIO_PKT_CTRL 0x%08x\n", __func__, + HDMI_INP(HDMI_AUDIO_PKT_CTRL)); + return 0; +} +EXPORT_SYMBOL(hdmi_audio_packet_enable); + + +/* TO-DO: return -EINVAL when num_of_channels and channel_allocation + * does not match CEA 861-D spec. +*/ +int hdmi_msm_audio_info_setup(bool enabled, u32 num_of_channels, + u32 channel_allocation, u32 level_shift, bool down_mix) +{ + 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; + u32 aud_pck_ctrl_2_reg; + u32 layout; + + layout = (MSM_HDMI_AUDIO_CHANNEL_2 == num_of_channels) ? 0 : 1; + aud_pck_ctrl_2_reg = 1 | (layout << 1); + HDMI_OUTP(0x00044, aud_pck_ctrl_2_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: + channel_allocation = 0; /* Default to FR,FL */ + 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: + pr_err("%s(): Unsupported num_of_channels = %u\n", + __func__, num_of_channels); + return -EINVAL; + 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); + + + hdmi_msm_dump_regs("HDMI-AUDIO-ON: "); + + return 0; + +} +EXPORT_SYMBOL(hdmi_msm_audio_info_setup); + +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); +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_ISRC_ACP_SUPPORT +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); +} +#else +static void hdmi_msm_en_isrc_packet(boolean isrc_is_continued) +{ + /* + * Until end-to-end support for various audio packets + */ +} +#endif + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_ISRC_ACP_SUPPORT +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); +} +#else +static void hdmi_msm_en_acp_packet(uint32 byte1) +{ + /* + * Until end-to-end support for various audio packets + */ +} +#endif + +int hdmi_msm_audio_get_sample_rate(void) +{ + return msm_hdmi_sample_rate; +} +EXPORT_SYMBOL(hdmi_msm_audio_get_sample_rate); + +void hdmi_msm_audio_sample_rate_reset(int rate) +{ + msm_hdmi_sample_rate = rate; + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + if (hdmi_msm_has_hdcp()) + hdcp_deauthenticate(); + else +#endif + hdmi_msm_turn_on(); +} +EXPORT_SYMBOL(hdmi_msm_audio_sample_rate_reset); + +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); + DEV_DBG("Not setting ACP, ISRC1, ISRC2 packets\n"); + hdmi_msm_audio_acr_setup(TRUE, + external_common_state->video_resolution, + msm_hdmi_sample_rate, channels); + hdmi_msm_audio_info_setup(TRUE, channels, 0, 0, FALSE); + + /* 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, 0, FALSE); + hdmi_msm_audio_acr_setup(FALSE, 0, 0, 0); + DEV_INFO("HDMI Audio: Disabled\n"); + return 0; +} + + +static uint8 hdmi_msm_avi_iframe_lut[][16] = { +/* 480p60 480i60 576p50 576i50 720p60 720p50 1080p60 1080i60 1080p50 + 1080i50 1080p24 1080p30 1080p25 640x480p 480p60_16_9 576p50_4_3 */ + {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, /*00*/ + {0x18, 0x18, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x18, 0x28, 0x18}, /*01*/ + {0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x88, 0x00, 0x04}, /*02*/ + {0x02, 0x06, 0x11, 0x15, 0x04, 0x13, 0x10, 0x05, 0x1F, + 0x14, 0x20, 0x22, 0x21, 0x01, 0x03, 0x11}, /*03*/ + {0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*04*/ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*05*/ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*06*/ + {0xE1, 0xE1, 0x41, 0x41, 0xD1, 0xd1, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0xe1, 0xE1, 0x41}, /*07*/ + {0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x01, 0x01, 0x02}, /*08*/ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*09*/ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*10*/ + {0xD1, 0xD1, 0xD1, 0xD1, 0x01, 0x01, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0xD1, 0xD1}, /*11*/ + {0x02, 0x02, 0x02, 0x02, 0x05, 0x05, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x02, 0x02, 0x02} /*12*/ +}; + +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; + boolean use_ce_scan_info = TRUE; + + switch (external_common_state->video_resolution) { + case HDMI_VFRMT_720x480p60_4_3: + 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; + case HDMI_VFRMT_720x480p60_16_9: + mode = 14; + break; + case HDMI_VFRMT_720x576p50_4_3: + mode = 15; + 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]; + + /* + * If the sink specified support for both underscan/overscan + * then, by default, set the underscan bit. + * Only checking underscan support for preferred format and cea formats + */ + if ((external_common_state->video_resolution == + external_common_state->preferred_video_format)) { + use_ce_scan_info = FALSE; + switch (external_common_state->pt_scan_info) { + case 0: + /* + * Need to use the info specified for the corresponding + * IT or CE format + */ + DEV_DBG("%s: No underscan information specified for the" + " preferred video format\n", __func__); + use_ce_scan_info = TRUE; + break; + case 3: + DEV_DBG("%s: Setting underscan bit for the preferred" + " video format\n", __func__); + aviInfoFrame[3] |= 0x02; + break; + default: + DEV_DBG("%s: Underscan information not set for the" + " preferred video format\n", __func__); + break; + } + } + + if (use_ce_scan_info) { + if (3 == external_common_state->ce_scan_info) { + DEV_DBG("%s: Setting underscan bit for the CE video" + " format\n", __func__); + aviInfoFrame[3] |= 0x02; + } else { + DEV_DBG("%s: Not setting underscan bit for the CE video" + " format\n", __func__); + } + } + + /* 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); +} + +#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 + +#define IFRAME_CHECKSUM_32(d) \ + ((d & 0xff) + ((d >> 8) & 0xff) + \ + ((d >> 16) & 0xff) + ((d >> 24) & 0xff)) + +static void hdmi_msm_spd_infoframe_packetsetup(void) +{ + uint32 packet_header = 0; + uint32 check_sum = 0; + uint32 packet_payload = 0; + uint32 packet_control = 0; + + uint8 *vendor_name = external_common_state->spd_vendor_name; + uint8 *product_description = + external_common_state->spd_product_description; + + /* 0x00A4 GENERIC1_HDR + * HB0 7:0 NUM + * HB1 15:8 NUM + * HB2 23:16 NUM */ + /* Setup Packet header and payload */ + /* 0x83 InfoFrame Type Code + 0x01 InfoFrame Version Number + 0x19 Length of Source Product Description InfoFrame + */ + packet_header = 0x83 | (0x01 << 8) | (0x19 << 16); + HDMI_OUTP(0x00A4, packet_header); + check_sum += IFRAME_CHECKSUM_32(packet_header); + + /* Vendor Name (7bit ASCII code) */ + /* 0x00A8 GENERIC1_0 + * BYTE0 7:0 CheckSum + * BYTE1 15:8 VENDOR_NAME[0] + * BYTE2 23:16 VENDOR_NAME[1] + * BYTE3 31:24 VENDOR_NAME[2] */ + packet_payload = ((vendor_name[0] & 0x7f) << 8) + | ((vendor_name[1] & 0x7f) << 16) + | ((vendor_name[2] & 0x7f) << 24); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + packet_payload |= ((0x100 - (0xff & check_sum)) & 0xff); + HDMI_OUTP(0x00A8, packet_payload); + + /* 0x00AC GENERIC1_1 + * BYTE4 7:0 VENDOR_NAME[3] + * BYTE5 15:8 VENDOR_NAME[4] + * BYTE6 23:16 VENDOR_NAME[5] + * BYTE7 31:24 VENDOR_NAME[6] */ + packet_payload = (vendor_name[3] & 0x7f) + | ((vendor_name[4] & 0x7f) << 8) + | ((vendor_name[5] & 0x7f) << 16) + | ((vendor_name[6] & 0x7f) << 24); + HDMI_OUTP(0x00AC, packet_payload); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + + /* Product Description (7-bit ASCII code) */ + /* 0x00B0 GENERIC1_2 + * BYTE8 7:0 VENDOR_NAME[7] + * BYTE9 15:8 PRODUCT_NAME[ 0] + * BYTE10 23:16 PRODUCT_NAME[ 1] + * BYTE11 31:24 PRODUCT_NAME[ 2] */ + packet_payload = (vendor_name[7] & 0x7f) + | ((product_description[0] & 0x7f) << 8) + | ((product_description[1] & 0x7f) << 16) + | ((product_description[2] & 0x7f) << 24); + HDMI_OUTP(0x00B0, packet_payload); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + + /* 0x00B4 GENERIC1_3 + * BYTE12 7:0 PRODUCT_NAME[ 3] + * BYTE13 15:8 PRODUCT_NAME[ 4] + * BYTE14 23:16 PRODUCT_NAME[ 5] + * BYTE15 31:24 PRODUCT_NAME[ 6] */ + packet_payload = (product_description[3] & 0x7f) + | ((product_description[4] & 0x7f) << 8) + | ((product_description[5] & 0x7f) << 16) + | ((product_description[6] & 0x7f) << 24); + HDMI_OUTP(0x00B4, packet_payload); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + + /* 0x00B8 GENERIC1_4 + * BYTE16 7:0 PRODUCT_NAME[ 7] + * BYTE17 15:8 PRODUCT_NAME[ 8] + * BYTE18 23:16 PRODUCT_NAME[ 9] + * BYTE19 31:24 PRODUCT_NAME[10] */ + packet_payload = (product_description[7] & 0x7f) + | ((product_description[8] & 0x7f) << 8) + | ((product_description[9] & 0x7f) << 16) + | ((product_description[10] & 0x7f) << 24); + HDMI_OUTP(0x00B8, packet_payload); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + + /* 0x00BC GENERIC1_5 + * BYTE20 7:0 PRODUCT_NAME[11] + * BYTE21 15:8 PRODUCT_NAME[12] + * BYTE22 23:16 PRODUCT_NAME[13] + * BYTE23 31:24 PRODUCT_NAME[14] */ + packet_payload = (product_description[11] & 0x7f) + | ((product_description[12] & 0x7f) << 8) + | ((product_description[13] & 0x7f) << 16) + | ((product_description[14] & 0x7f) << 24); + HDMI_OUTP(0x00BC, packet_payload); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + + /* 0x00C0 GENERIC1_6 + * BYTE24 7:0 PRODUCT_NAME[15] + * BYTE25 15:8 Source Device Information + * BYTE26 23:16 NUM + * BYTE27 31:24 NUM */ + /* Source Device Information + * 00h unknown + * 01h Digital STB + * 02h DVD + * 03h D-VHS + * 04h HDD Video + * 05h DVC + * 06h DSC + * 07h Video CD + * 08h Game + * 09h PC general */ + packet_payload = (product_description[15] & 0x7f) | 0x00 << 8; + HDMI_OUTP(0x00C0, packet_payload); + check_sum += IFRAME_CHECKSUM_32(packet_payload); + + /* GENERIC1_LINE | GENERIC1_CONT | GENERIC1_SEND + * Setup HDMI TX generic packet control + * Enable this packet to transmit every frame + * Enable HDMI TX engine to transmit Generic packet 1 */ + packet_control = HDMI_INP_ND(0x0034); + packet_control |= ((0x1 << 24) | (1 << 5) | (1 << 4)); + HDMI_OUTP(0x0034, packet_control); +} + +int hdmi_msm_clk(int on) +{ + int rc; + + DEV_DBG("HDMI Clk: %s\n", on ? "Enable" : "Disable"); + if (on) { + rc = clk_prepare_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_prepare_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_prepare_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_unprepare(hdmi_msm_state->hdmi_app_clk); + clk_disable_unprepare(hdmi_msm_state->hdmi_m_pclk); + clk_disable_unprepare(hdmi_msm_state->hdmi_s_pclk); + } + + return 0; +} + +static void hdmi_msm_turn_on(void) +{ + uint32 hpd_ctrl; + uint32 audio_pkt_ctrl, audio_cfg; + /* + * Number of wait iterations for QDSP to disable Audio Engine + * before resetting HDMI core + */ + 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(20); + } + + mutex_lock(&hdcp_auth_state_mutex); + hdmi_msm_reset_core(); + mutex_unlock(&hdcp_auth_state_mutex); + + hdmi_msm_init_phy(external_common_state->video_resolution); + /* HDMI_USEC_REFTIMER[0x0208] */ + HDMI_OUTP(0x0208, 0x0001001B); + + hdmi_msm_set_mode(TRUE); + + hdmi_msm_video_setup(external_common_state->video_resolution); + if (!hdmi_msm_is_dvi_mode()) + hdmi_msm_audio_setup(); + hdmi_msm_avi_info_frame(); +#ifdef CONFIG_FB_MSM_HDMI_3D + hdmi_msm_vendor_infoframe_packetsetup(); +#endif + hdmi_msm_spd_infoframe_packetsetup(); + + /* 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); + + /* 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 */ + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + /* re-initialize CEC if enabled */ + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->cec_enabled == true) { + hdmi_msm_cec_init(); + hdmi_msm_cec_write_logical_addr( + hdmi_msm_state->cec_logical_addr); + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_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 + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT +static void hdmi_msm_cec_read_timer_func(unsigned long data) +{ + queue_work(hdmi_work_queue, &hdmi_msm_state->cec_latch_detect_work); +} +#endif + +static void hdmi_msm_hpd_read_work(struct work_struct *work) +{ + uint32 hpd_ctrl; + + clk_prepare_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_unprepare(hdmi_msm_state->hdmi_app_clk); +} + +static void hdmi_msm_hpd_off(void) +{ + if (!hdmi_msm_state->hpd_initialized) { + DEV_DBG("%s: HPD is already OFF, returning\n", __func__); + return; + } + + 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_msm_state->hpd_initialized = FALSE; + hdmi_msm_powerdown_phy(); + hdmi_msm_state->pd->cec_power(0); + 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 *prefix) +{ +#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; + uint32 hpd_ctrl; + + if (hdmi_msm_state->hpd_initialized) { + DEV_DBG("%s: HPD is already ON, returning\n", __func__); + return 0; + } + + hdmi_msm_clk(1); + hdmi_msm_state->pd->core_power(1, 1); + hdmi_msm_state->pd->enable_5v(1); + hdmi_msm_state->pd->cec_power(1); + hdmi_msm_dump_regs("HDMI-INIT: "); + hdmi_msm_set_mode(FALSE); + + if (!phy_reset_done) { + hdmi_phy_reset(); + phy_reset_done = 1; + } + + /* HDMI_USEC_REFTIMER[0x0208] */ + HDMI_OUTP(0x0208, 0x0001001B); + + /* Check HPD State */ + 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_ctrl(boolean enable) +{ + if (!external_common_state->hpd_feature_on) + return 0; + + if (enable) + hdmi_msm_hpd_on(true); + else + hdmi_msm_hpd_off(); + + 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; + + 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 || mfd->ref_cnt) { + 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, 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) +{ + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + + if (!hdmi_msm_state->hdmi_app_clk) + return -ENODEV; + +#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(true); + + mutex_lock(&external_common_state_hpd_mutex); + if (!external_common_state->hpd_feature_on || mfd->ref_cnt) + 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(&pdev->dev, "core_clk"); + if (IS_ERR(hdmi_msm_state->hdmi_app_clk)) { + DEV_ERR("'core_clk' clk not found\n"); + rc = IS_ERR(hdmi_msm_state->hdmi_app_clk); + goto error; + } + + hdmi_msm_state->hdmi_m_pclk = clk_get(&pdev->dev, "master_iface_clk"); + if (IS_ERR(hdmi_msm_state->hdmi_m_pclk)) { + DEV_ERR("'master_iface_clk' clk not found\n"); + rc = IS_ERR(hdmi_msm_state->hdmi_m_pclk); + goto error; + } + + hdmi_msm_state->hdmi_s_pclk = clk_get(&pdev->dev, "slave_iface_clk"); + if (IS_ERR(hdmi_msm_state->hdmi_s_pclk)) { + DEV_ERR("'slave_iface_clk' 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; + } + + if (!hdmi_msm_state->pd->cec_power) { + DEV_ERR("Init FAILED: cec_power 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; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + init_timer(&hdmi_msm_state->cec_read_timer); + hdmi_msm_state->cec_read_timer.function = + hdmi_msm_cec_read_timer_func; + hdmi_msm_state->cec_read_timer.data = (uint32)NULL; + + hdmi_msm_state->cec_read_timer.expires = 0xffffffffL; + #endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_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()) { + /* Don't Set Encryption in case of non HDCP builds */ + external_common_state->present_hdcp = FALSE; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + external_common_state->present_hdcp = TRUE; +#endif + } 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); + + /* Initialize hdmi node and register with switch driver */ + if (hdmi_prim_display) + external_common_state->sdev.name = "hdmi_as_primary"; + else + external_common_state->sdev.name = "hdmi"; + if (switch_dev_register(&external_common_state->sdev) < 0) + DEV_ERR("Hdmi switch registration failed\n"); + + 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"); + + /* Unregister hdmi node from switch driver */ + switch_dev_unregister(&external_common_state->sdev); + + 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(); + /* Set HDMI switch node to 0 on HPD feature disable */ + switch_set_state(&external_common_state->sdev, 0); + } + + return rc; +} + +static struct platform_driver this_driver = { + .probe = hdmi_msm_probe, + .remove = hdmi_msm_remove, + .driver.name = "hdmi_msm", +}; + +static struct msm_fb_panel_data hdmi_msm_panel_data = { + .on = hdmi_msm_power_on, + .off = hdmi_msm_power_off, + .power_ctrl = hdmi_msm_power_ctrl, +}; + +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; + + if (msm_fb_detect_client("hdmi_msm")) + return 0; + +#ifdef CONFIG_FB_MSM_HDMI_AS_PRIMARY + hdmi_prim_display = 1; +#endif + + 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 + memset(external_common_state->spd_vendor_name, 0, + sizeof(external_common_state->spd_vendor_name)); + memset(external_common_state->spd_product_description, 0, + sizeof(external_common_state->spd_product_description)); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + hdmi_msm_state->cec_queue_start = + kzalloc(sizeof(struct hdmi_msm_cec_msg)*CEC_QUEUE_SIZE, + GFP_KERNEL); + if (!hdmi_msm_state->cec_queue_start) { + pr_err("hdmi_msm_init FAILED: CEC queue out of memory\n"); + rc = -ENOMEM; + goto init_exit; + } + + hdmi_msm_state->cec_queue_wr = hdmi_msm_state->cec_queue_start; + hdmi_msm_state->cec_queue_rd = hdmi_msm_state->cec_queue_start; + hdmi_msm_state->cec_queue_full = false; +#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 */ + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + INIT_WORK(&hdmi_msm_state->cec_latch_detect_work, + hdmi_msm_cec_latch_work); + init_completion(&hdmi_msm_state->cec_frame_wr_done); + init_completion(&hdmi_msm_state->cec_line_latch_wait); +#endif + + 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 0000000000000000000000000000000000000000..5195f2cb29aa5e96d23f2958914acd0a78be308d --- /dev/null +++ b/drivers/video/msm/hdmi_msm.h @@ -0,0 +1,138 @@ +/* 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 + + +/* + * Ref. HDMI 1.4a + * Supplement-1 CEC Section 6, 7 + */ +struct hdmi_msm_cec_msg { + uint8 sender_id; + uint8 recvr_id; + uint8 opcode; + uint8 operand[15]; + uint8 frame_size; + uint8 retransmit; +}; + +#define QFPROM_BASE ((uint32)hdmi_msm_state->qfprom_io) +#define HDMI_BASE ((uint32)hdmi_msm_state->hdmi_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 */ + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT + boolean cec_enabled; + unsigned int first_monitor; + int cec_logical_addr; + struct completion cec_frame_wr_done; + struct timer_list cec_read_timer; +#define CEC_STATUS_WR_ERROR 0x0001 +#define CEC_STATUS_WR_DONE 0x0002 +#define CEC_STATUS_WR_TMOUT 0x0004 + uint32 cec_frame_wr_status; + + struct hdmi_msm_cec_msg *cec_queue_start; + struct hdmi_msm_cec_msg *cec_queue_wr; + struct hdmi_msm_cec_msg *cec_queue_rd; + boolean cec_queue_full; + boolean fsm_reset_done; + + /* + * CECT 9-5-1 + */ + struct completion cec_line_latch_wait; + struct work_struct cec_latch_detect_work; + +#define CEC_QUEUE_SIZE 16 +#define CEC_QUEUE_END (hdmi_msm_state->cec_queue_start + CEC_QUEUE_SIZE) +#define RETRANSMIT_MAX_NUM 5 +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_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_msm_set_mode(boolean power_on); +int hdmi_msm_clk(int on); +void hdmi_phy_reset(void); +void hdmi_msm_reset_core(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 + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT +void hdmi_msm_cec_init(void); +void hdmi_msm_cec_write_logical_addr(int addr); +void hdmi_msm_cec_msg_recv(void); +void hdmi_msm_cec_one_touch_play(void); +void hdmi_msm_cec_msg_send(struct hdmi_msm_cec_msg *msg); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_CEC_SUPPORT */ + +#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 0000000000000000000000000000000000000000..3d274888e881c0b98000a7204690e7652033ed1b --- /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 0000000000000000000000000000000000000000..863d59de5f8292d9af807260f13475beff66e164 --- /dev/null +++ b/drivers/video/msm/lcdc.c @@ -0,0 +1,289 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_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_unprepare(pixel_mdp_clk); + clk_disable_unprepare(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 (mfd->ebi1_clk) { + if (mdp_rev == MDP_REV_303) { + if (clk_set_rate(mfd->ebi1_clk, 0)) + pr_err("%s: ebi1_lcdc_clk set rate failed\n", + __func__); + } + clk_disable_unprepare(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 + if (panel_pixclock_freq > 65000000) + /* pm_qos_rate should be in Khz */ + pm_qos_rate = panel_pixclock_freq / 1000 ; + else + pm_qos_rate = 65000; + + if (mfd->ebi1_clk) { + if (mdp_rev == MDP_REV_303) { + if (clk_set_rate(mfd->ebi1_clk, 65000000)) + pr_err("%s: ebi1_lcdc_clk set rate failed\n", + __func__); + } else { + clk_set_rate(mfd->ebi1_clk, pm_qos_rate * 1000); + } + clk_prepare_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_prepare_enable(pixel_mdp_clk); + clk_prepare_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; + struct clk *ebi1_clk = NULL; + + if (pdev->id == 0) { + lcdc_pdata = pdev->dev.platform_data; + pixel_mdp_clk = clk_get(&pdev->dev, "mdp_clk"); + if (IS_ERR(pixel_mdp_clk)) { + pr_err("Couldnt find pixel_mdp_clk\n"); + return -EINVAL; + } + + pixel_lcdc_clk = clk_get(&pdev->dev, "lcdc_clk"); + if (IS_ERR(pixel_lcdc_clk)) { + pr_err("Couldnt find pixel_lcdc_clk\n"); + return -EINVAL; + } + +#ifndef CONFIG_MSM_BUS_SCALING + ebi1_clk = clk_get(&pdev->dev, "mem_clk"); + if (IS_ERR(ebi1_clk)) + return PTR_ERR(ebi1_clk); +#endif + + return 0; + } + + mfd = platform_get_drvdata(pdev); + mfd->ebi1_clk = ebi1_clk; + + 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("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; + + /* + * 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) +{ + + 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 0000000000000000000000000000000000000000..6b0733f2f81beb51ccb81a05d547a034406f546e --- /dev/null +++ b/drivers/video/msm/lcdc_auo_wvga.c @@ -0,0 +1,411 @@ +/* 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; + + if (msm_fb_detect_client(LCDC_AUO_PANEL_NAME)) { + pr_err("%s: detect failed\n", __func__); + return 0; + } + + 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 0000000000000000000000000000000000000000..7453ecbc2a76be2a28185b0a07f9458ff2cc340d --- /dev/null +++ b/drivers/video/msm/lcdc_chimei_wxga.c @@ -0,0 +1,231 @@ +/* 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; + + if (msm_fb_detect_client("lcdc_chimei_wxga")) + return 0; + + 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 0000000000000000000000000000000000000000..ca82def6aef36e13e2347fa7d8e690030fd8cf0c --- /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 0000000000000000000000000000000000000000..b675787f2e52f455d858d047c568a7650c2d3818 --- /dev/null +++ b/drivers/video/msm/lcdc_gordon.c @@ -0,0 +1,457 @@ +/* Copyright (c) 2009-2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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 int bl_level; +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(); + 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); + } + 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) +{ + bl_level = mfd->bl_level; + + if (gordon_state.disp_initialized) { + 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_nt35582_wvga.c b/drivers/video/msm/lcdc_nt35582_wvga.c new file mode 100644 index 0000000000000000000000000000000000000000..9ecf4b98787c80cf53031a8c35f2e4d945625d60 --- /dev/null +++ b/drivers/video/msm/lcdc_nt35582_wvga.c @@ -0,0 +1,472 @@ +/* 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 +#ifdef CONFIG_SPI_QUP +#include +#endif +#include +#include +#include "msm_fb.h" + +#define LCDC_NT35582_PANEL_NAME "lcdc_nt35582_wvga" + +#define WRITE_FIRST_TRANS 0x20 +#define WRITE_SECOND_TRANS 0x00 +#define WRITE_THIRD_TRANS 0x40 +#define READ_FIRST_TRANS 0x20 +#define READ_SECOND_TRANS 0x00 +#define READ_THIRD_TRANS 0xC0 + +#ifdef CONFIG_SPI_QUP +#define LCDC_NT35582_SPI_DEVICE_NAME "lcdc_nt35582_spi" +static struct spi_device *spi_client; +#endif + +struct nt35582_state_type { + boolean display_on; + int bl_level; +}; + +static struct nt35582_state_type nt35582_state = { 0 }; +static int gpio_backlight_en; +static struct msm_panel_common_pdata *lcdc_nt35582_pdata; + +static int spi_write_2bytes(struct spi_device *spi, + unsigned char reg_high_addr, unsigned char reg_low_addr) +{ + char tx_buf[4]; + int rc; + struct spi_message m; + struct spi_transfer t; + + memset(&t, 0, sizeof t); + t.tx_buf = tx_buf; + + spi_setup(spi); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = WRITE_FIRST_TRANS; + tx_buf[1] = reg_high_addr; + tx_buf[2] = WRITE_SECOND_TRANS; + tx_buf[3] = reg_low_addr; + t.rx_buf = NULL; + t.len = 4; + t.bits_per_word = 16; + rc = spi_sync(spi, &m); + if (rc) + pr_err("write spi command failed!\n"); + + return rc; +} + +static int spi_write_3bytes(struct spi_device *spi, unsigned char reg_high_addr, + unsigned char reg_low_addr, unsigned char write_data) +{ + char tx_buf[6]; + int rc; + struct spi_message m; + struct spi_transfer t; + + memset(&t, 0, sizeof t); + t.tx_buf = tx_buf; + + spi_setup(spi); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = WRITE_FIRST_TRANS; + tx_buf[1] = reg_high_addr; + tx_buf[2] = WRITE_SECOND_TRANS; + tx_buf[3] = reg_low_addr; + tx_buf[4] = WRITE_THIRD_TRANS; + tx_buf[5] = write_data; + t.rx_buf = NULL; + t.len = 6; + t.bits_per_word = 16; + rc = spi_sync(spi, &m); + + if (rc) + pr_err("write spi command failed!\n"); + + return rc; +} + +static int spi_read_bytes(struct spi_device *spi, unsigned char reg_high_addr, + unsigned char reg_low_addr, unsigned char *read_value) +{ + char tx_buf[6]; + char rx_buf[6]; + int rc; + struct spi_message m; + struct spi_transfer t; + + memset(&t, 0, sizeof t); + t.tx_buf = tx_buf; + + spi_setup(spi); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = READ_FIRST_TRANS; + tx_buf[1] = reg_high_addr; + tx_buf[2] = READ_SECOND_TRANS; + tx_buf[3] = reg_low_addr; + tx_buf[4] = READ_THIRD_TRANS; + tx_buf[5] = 0x00; + + t.rx_buf = rx_buf; + t.len = 6; + t.bits_per_word = 16; + rc = spi_sync(spi, &m); + + if (rc) + pr_err("write spi command failed!\n"); + else + *read_value = rx_buf[5]; + + return rc; +} + +static void nt35582_disp_on(void) +{ + uint32 panel_id1 = 0, panel_id2 = 0; + + if (!nt35582_state.display_on) { + + /* GVDD setting */ + spi_write_3bytes(spi_client, 0xC0, 0x00, 0xC0); + spi_write_3bytes(spi_client, 0xC0, 0x01, 0x00); + spi_write_3bytes(spi_client, 0xC0, 0x02, 0xC0); + spi_write_3bytes(spi_client, 0xC0, 0x03, 0x00); + /* Power setting */ + spi_write_3bytes(spi_client, 0xC1, 0x00, 0x40); + spi_write_3bytes(spi_client, 0xC2, 0x00, 0x21); + spi_write_3bytes(spi_client, 0xC2, 0x02, 0x02); + + /* Gamma setting */ + spi_write_3bytes(spi_client, 0xE0, 0x00, 0x0E); + spi_write_3bytes(spi_client, 0xE0, 0x01, 0x54); + spi_write_3bytes(spi_client, 0xE0, 0x02, 0x63); + spi_write_3bytes(spi_client, 0xE0, 0x03, 0x76); + spi_write_3bytes(spi_client, 0xE0, 0x04, 0x1F); + spi_write_3bytes(spi_client, 0xE0, 0x05, 0x31); + spi_write_3bytes(spi_client, 0xE0, 0x06, 0x62); + spi_write_3bytes(spi_client, 0xE0, 0x07, 0x78); + spi_write_3bytes(spi_client, 0xE0, 0x08, 0x1F); + spi_write_3bytes(spi_client, 0xE0, 0x09, 0x25); + spi_write_3bytes(spi_client, 0xE0, 0x0A, 0xB3); + spi_write_3bytes(spi_client, 0xE0, 0x0B, 0x17); + spi_write_3bytes(spi_client, 0xE0, 0x0C, 0x38); + spi_write_3bytes(spi_client, 0xE0, 0x0D, 0x5A); + spi_write_3bytes(spi_client, 0xE0, 0x0E, 0xA2); + spi_write_3bytes(spi_client, 0xE0, 0x0F, 0xA2); + spi_write_3bytes(spi_client, 0xE0, 0x10, 0x24); + spi_write_3bytes(spi_client, 0xE0, 0x11, 0x57); + + spi_write_3bytes(spi_client, 0xE1, 0x00, 0x0E); + spi_write_3bytes(spi_client, 0xE1, 0x01, 0x54); + spi_write_3bytes(spi_client, 0xE1, 0x02, 0x63); + spi_write_3bytes(spi_client, 0xE1, 0x03, 0x76); + spi_write_3bytes(spi_client, 0xE1, 0x04, 0x1F); + spi_write_3bytes(spi_client, 0xE1, 0x05, 0x31); + spi_write_3bytes(spi_client, 0xE1, 0x06, 0X62); + spi_write_3bytes(spi_client, 0xE1, 0x07, 0x78); + spi_write_3bytes(spi_client, 0xE1, 0x08, 0x1F); + spi_write_3bytes(spi_client, 0xE1, 0x09, 0x25); + spi_write_3bytes(spi_client, 0xE1, 0x0A, 0xB3); + spi_write_3bytes(spi_client, 0xE1, 0x0B, 0x17); + spi_write_3bytes(spi_client, 0xE1, 0x0C, 0x38); + spi_write_3bytes(spi_client, 0xE1, 0x0D, 0x5A); + spi_write_3bytes(spi_client, 0xE1, 0x0E, 0xA2); + spi_write_3bytes(spi_client, 0xE1, 0x0F, 0xA2); + spi_write_3bytes(spi_client, 0xE1, 0x10, 0x24); + spi_write_3bytes(spi_client, 0xE1, 0x11, 0x57); + + spi_write_3bytes(spi_client, 0xE2, 0x00, 0x0E); + spi_write_3bytes(spi_client, 0xE2, 0x01, 0x54); + spi_write_3bytes(spi_client, 0xE2, 0x02, 0x63); + spi_write_3bytes(spi_client, 0xE2, 0x03, 0x76); + spi_write_3bytes(spi_client, 0xE2, 0x04, 0x1F); + spi_write_3bytes(spi_client, 0xE2, 0x05, 0x31); + spi_write_3bytes(spi_client, 0xE2, 0x06, 0x62); + spi_write_3bytes(spi_client, 0xE2, 0x07, 0x78); + spi_write_3bytes(spi_client, 0xE2, 0x08, 0x1F); + spi_write_3bytes(spi_client, 0xE2, 0x09, 0x25); + spi_write_3bytes(spi_client, 0xE2, 0x0A, 0xB3); + spi_write_3bytes(spi_client, 0xE2, 0x0B, 0x17); + spi_write_3bytes(spi_client, 0xE2, 0x0C, 0x38); + spi_write_3bytes(spi_client, 0xE2, 0x0D, 0x5A); + spi_write_3bytes(spi_client, 0xE2, 0x0E, 0xA2); + spi_write_3bytes(spi_client, 0xE2, 0x0F, 0xA2); + spi_write_3bytes(spi_client, 0xE2, 0x10, 0x24); + spi_write_3bytes(spi_client, 0xE2, 0x11, 0x57); + + spi_write_3bytes(spi_client, 0xE3, 0x00, 0x0E); + spi_write_3bytes(spi_client, 0xE3, 0x01, 0x54); + spi_write_3bytes(spi_client, 0xE3, 0x02, 0x63); + spi_write_3bytes(spi_client, 0xE3, 0x03, 0x76); + spi_write_3bytes(spi_client, 0xE3, 0x04, 0x1F); + spi_write_3bytes(spi_client, 0xE3, 0x05, 0x31); + spi_write_3bytes(spi_client, 0xE3, 0x06, 0x62); + spi_write_3bytes(spi_client, 0xE3, 0x07, 0x78); + spi_write_3bytes(spi_client, 0xE3, 0x08, 0x1F); + spi_write_3bytes(spi_client, 0xE3, 0x09, 0x25); + spi_write_3bytes(spi_client, 0xE3, 0x0A, 0xB3); + spi_write_3bytes(spi_client, 0xE3, 0x0B, 0x17); + spi_write_3bytes(spi_client, 0xE3, 0x0C, 0x38); + spi_write_3bytes(spi_client, 0xE3, 0x0D, 0x5A); + spi_write_3bytes(spi_client, 0xE3, 0x0E, 0xA2); + spi_write_3bytes(spi_client, 0xE3, 0x0F, 0xA2); + spi_write_3bytes(spi_client, 0xE3, 0x10, 0x24); + spi_write_3bytes(spi_client, 0xE3, 0x11, 0x57); + + spi_write_3bytes(spi_client, 0xE4, 0x00, 0x48); + spi_write_3bytes(spi_client, 0xE4, 0x01, 0x6B); + spi_write_3bytes(spi_client, 0xE4, 0x02, 0x84); + spi_write_3bytes(spi_client, 0xE4, 0x03, 0x9B); + spi_write_3bytes(spi_client, 0xE4, 0x04, 0x1F); + spi_write_3bytes(spi_client, 0xE4, 0x05, 0x31); + spi_write_3bytes(spi_client, 0xE4, 0x06, 0x62); + spi_write_3bytes(spi_client, 0xE4, 0x07, 0x78); + spi_write_3bytes(spi_client, 0xE4, 0x08, 0x1F); + spi_write_3bytes(spi_client, 0xE4, 0x09, 0x25); + spi_write_3bytes(spi_client, 0xE4, 0x0A, 0xB3); + spi_write_3bytes(spi_client, 0xE4, 0x0B, 0x17); + spi_write_3bytes(spi_client, 0xE4, 0x0C, 0x38); + spi_write_3bytes(spi_client, 0xE4, 0x0D, 0x5A); + spi_write_3bytes(spi_client, 0xE4, 0x0E, 0xA2); + spi_write_3bytes(spi_client, 0xE4, 0x0F, 0xA2); + spi_write_3bytes(spi_client, 0xE4, 0x10, 0x24); + spi_write_3bytes(spi_client, 0xE4, 0x11, 0x57); + + spi_write_3bytes(spi_client, 0xE5, 0x00, 0x48); + spi_write_3bytes(spi_client, 0xE5, 0x01, 0x6B); + spi_write_3bytes(spi_client, 0xE5, 0x02, 0x84); + spi_write_3bytes(spi_client, 0xE5, 0x03, 0x9B); + spi_write_3bytes(spi_client, 0xE5, 0x04, 0x1F); + spi_write_3bytes(spi_client, 0xE5, 0x05, 0x31); + spi_write_3bytes(spi_client, 0xE5, 0x06, 0x62); + spi_write_3bytes(spi_client, 0xE5, 0x07, 0x78); + spi_write_3bytes(spi_client, 0xE5, 0x08, 0x1F); + spi_write_3bytes(spi_client, 0xE5, 0x09, 0x25); + spi_write_3bytes(spi_client, 0xE5, 0x0A, 0xB3); + spi_write_3bytes(spi_client, 0xE5, 0x0B, 0x17); + spi_write_3bytes(spi_client, 0xE5, 0x0C, 0x38); + spi_write_3bytes(spi_client, 0xE5, 0x0D, 0x5A); + spi_write_3bytes(spi_client, 0xE5, 0x0E, 0xA2); + spi_write_3bytes(spi_client, 0xE5, 0x0F, 0xA2); + spi_write_3bytes(spi_client, 0xE5, 0x10, 0x24); + spi_write_3bytes(spi_client, 0xE5, 0x11, 0x57); + + /* Data format setting */ + spi_write_3bytes(spi_client, 0x3A, 0x00, 0x70); + + /* Reverse PCLK signal of LCM to meet Qualcomm's platform */ + spi_write_3bytes(spi_client, 0x3B, 0x00, 0x2B); + + /* Scan direstion setting */ + spi_write_3bytes(spi_client, 0x36, 0x00, 0x00); + + /* Sleep out */ + spi_write_2bytes(spi_client, 0x11, 0x00); + + msleep(120); + + /* Display on */ + spi_write_2bytes(spi_client, 0x29, 0x00); + + pr_info("%s: LCM SPI display on CMD finished...\n", __func__); + + msleep(200); + + nt35582_state.display_on = TRUE; + } + + /* Test to read RDDID. It should be 0x0055h and 0x0082h */ + spi_read_bytes(spi_client, 0x10, 0x80, (unsigned char *)&panel_id1); + spi_read_bytes(spi_client, 0x11, 0x80, (unsigned char *)&panel_id2); + + pr_info(KERN_INFO "nt35582_disp_on: LCM_ID=[0x%x, 0x%x]\n", + panel_id1, panel_id2); +} + +static int lcdc_nt35582_panel_on(struct platform_device *pdev) +{ + nt35582_disp_on(); + return 0; +} + +static int lcdc_nt35582_panel_off(struct platform_device *pdev) +{ + nt35582_state.display_on = FALSE; + return 0; +} + +static void lcdc_nt35582_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + int i = 0, step = 0; + + bl_level = mfd->bl_level; + if (bl_level == nt35582_state.bl_level) + return; + else + nt35582_state.bl_level = bl_level; + + if (bl_level == 0) { + gpio_set_value_cansleep(gpio_backlight_en, 0); + return; + } + + /* Level:0~31 mapping to step 32~1 */ + step = 32 - bl_level; + for (i = 0; i < step; i++) { + gpio_set_value_cansleep(gpio_backlight_en, 0); + ndelay(5); + gpio_set_value_cansleep(gpio_backlight_en, 1); + ndelay(5); + } +} + +static int __devinit nt35582_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + lcdc_nt35582_pdata = pdev->dev.platform_data; + return 0; + } + + gpio_backlight_en = *(lcdc_nt35582_pdata->gpio_num); + + msm_fb_add_device(pdev); + return 0; +} + +#ifdef CONFIG_SPI_QUP +static int __devinit lcdc_nt35582_spi_probe(struct spi_device *spi) +{ + spi_client = spi; + spi_client->bits_per_word = 16; + spi_client->chip_select = 0; + spi_client->max_speed_hz = 1100000; + spi_client->mode = SPI_MODE_0; + spi_setup(spi_client); + + return 0; +} + +static int __devexit lcdc_nt35582_spi_remove(struct spi_device *spi) +{ + spi_client = NULL; + return 0; +} + +static struct spi_driver lcdc_nt35582_spi_driver = { + .driver = { + .name = LCDC_NT35582_SPI_DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = lcdc_nt35582_spi_probe, + .remove = __devexit_p(lcdc_nt35582_spi_remove), +}; +#endif +static struct platform_driver this_driver = { + .probe = nt35582_probe, + .driver = { + .name = LCDC_NT35582_PANEL_NAME, + }, +}; + +static struct msm_fb_panel_data nt35582_panel_data = { + .on = lcdc_nt35582_panel_on, + .off = lcdc_nt35582_panel_off, + .set_backlight = lcdc_nt35582_set_backlight, +}; + +static struct platform_device this_device = { + .name = LCDC_NT35582_PANEL_NAME, + .id = 1, + .dev = { + .platform_data = &nt35582_panel_data, + } +}; + +static int __init lcdc_nt35582_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT + if (msm_fb_detect_client(LCDC_NT35582_PANEL_NAME)) { + pr_err("detect failed\n"); + return 0; + } +#endif + ret = platform_driver_register(&this_driver); + if (ret) { + pr_err("Fails to platform_driver_register...\n"); + return ret; + } + + pinfo = &nt35582_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 = 24; + pinfo->fb_num = 2; + pinfo->clk_rate = 25600000; + pinfo->bl_max = 31; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 10; /* hsw = 8 + hbp=184 */ + pinfo->lcdc.h_front_porch = 10; + pinfo->lcdc.h_pulse_width = 2; + pinfo->lcdc.v_back_porch = 4; /* vsw=1 + vbp = 2 */ + pinfo->lcdc.v_front_porch = 10; + 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) { + pr_err("not able to register the device\n"); + goto fail_driver; + } +#ifdef CONFIG_SPI_QUP + ret = spi_register_driver(&lcdc_nt35582_spi_driver); + + if (ret) { + pr_err("not able to register spi\n"); + goto fail_device; + } +#endif + return ret; + +#ifdef CONFIG_SPI_QUP +fail_device: + platform_device_unregister(&this_device); +#endif +fail_driver: + platform_driver_unregister(&this_driver); + return ret; +} + +device_initcall(lcdc_nt35582_panel_init); diff --git a/drivers/video/msm/lcdc_panel.c b/drivers/video/msm/lcdc_panel.c new file mode 100644 index 0000000000000000000000000000000000000000..57053255579157c1b9119400a0f3e6cff69b0e35 --- /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 0000000000000000000000000000000000000000..d127f63b912ffecc0d073eea8c940033cf91680e --- /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 0000000000000000000000000000000000000000..16790f304e02ed8812aa0cfe9b5ca7191dbdec05 --- /dev/null +++ b/drivers/video/msm/lcdc_samsung_oled_pt.c @@ -0,0 +1,588 @@ +/* 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; + + if (msm_fb_detect_client("lcdc_samsung_oled")) { + pr_err("%s: detect failed\n", __func__); + return 0; + } + + 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 0000000000000000000000000000000000000000..6b35e520b4d6d3d19f0e7c840b12d16936b53a30 --- /dev/null +++ b/drivers/video/msm/lcdc_samsung_wsvga.c @@ -0,0 +1,270 @@ +/* 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) +{ +#ifdef CONFIG_PMIC8058_PWM + int bl_level; + int ret; + + bl_level = mfd->bl_level; + + 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; + + if (msm_fb_detect_client("lcdc_samsung_wsvga")) + return 0; + + 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 0000000000000000000000000000000000000000..2ba2618b971782af76d0ddf2727e7e96d579aabf --- /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 0000000000000000000000000000000000000000..cdae3589b7d040693f0d09a855d336d777cbcb97 --- /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 0000000000000000000000000000000000000000..77606cf6bbda538429ec20ec37cf7ad14bc3dca1 --- /dev/null +++ b/drivers/video/msm/lcdc_toshiba_fwvga_pt.c @@ -0,0 +1,471 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied 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() || cpu_is_msm7x25ab()) { + 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 0000000000000000000000000000000000000000..f0aa8f55cffc28eb9f9284ea4b651369e83ec6d6 --- /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_truly_ips3p2335.c b/drivers/video/msm/lcdc_truly_ips3p2335.c new file mode 100644 index 0000000000000000000000000000000000000000..a4a370e066bd9c13a517167c5cfc38d3262ac1e7 --- /dev/null +++ b/drivers/video/msm/lcdc_truly_ips3p2335.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the 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_fb.h" + +static int prev_bl = 17; + +static int spi_cs; +static int spi_sclk; +static int spi_mosi; +static int gpio_backlight_en; +static int gpio_display_reset; + +struct truly_state_type { + boolean disp_initialized; + boolean display_on; + boolean disp_powered_up; +}; + +static struct truly_state_type truly_state = { 0 }; +static struct msm_panel_common_pdata *lcdc_truly_pdata; + +static char init_item_v1[] = { 0xff, 0x83, 0x57, }; +static char init_item_v2[] = { 0x03, }; +static char init_item_v3[] = { 0x00, 0x13, 0x1C, 0x1C, 0x83, 0x48, }; +static char init_item_v4[] = { 0x43, 0x06, 0x06, 0x06, }; +static char init_item_v5[] = { 0x53, }; +static char init_item_v6[] = { 0x02, 0x40, 0x00, 0x2a, 0x2a, 0x0d, 0x3f, }; +static char init_item_v7[] = { 0x70, 0x50, 0x01, 0x3c, 0xe8, 0x08, }; +static char init_item_v8[] = { 0x17, 0x0f, }; +static char init_item_v9[] = { 0x60}; +static char init_item_v10[] = { 0x00, 0x13, 0x1a, 0x29, 0x2d, 0x41, 0x49, + 0x52, 0x48, 0x41, 0x3c, 0x33, 0x30, 0x1c, + 0x19, 0x03, 0x00, 0x13, 0x1a, 0x29, 0x2d, + 0x41, 0x49, 0x52, 0x48, 0x41, 0x3c, 0x33, + 0x31, 0x1c, 0x19, 0x03, 0x00, 0x01, + }; +static char init_item_v11[] = { 0x40, }; + +static inline void truly_spi_write_byte(char dc, uint8 data) +{ + uint32 bit; + int bnum; + + gpio_set_value_cansleep(spi_sclk, 0); /* clk low */ + /* dc: 0 for command, 1 for parameter */ + gpio_set_value_cansleep(spi_mosi, dc); + udelay(1); /* at least 20 ns */ + gpio_set_value_cansleep(spi_sclk, 1); /* clk high */ + udelay(1); /* at least 20 ns */ + bnum = 8; /* 8 data bits */ + bit = 0x80; + while (bnum) { + gpio_set_value_cansleep(spi_sclk, 0); /* clk low */ + if (data & bit) + 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); + bit >>= 1; + bnum--; + } +} + +static inline int truly_spi_write(char cmd, char *data, int num) +{ + int i; + + gpio_set_value_cansleep(spi_cs, 0); /* cs low */ + /* command byte first */ + truly_spi_write_byte(0, cmd); + /* followed by parameter bytes */ + for (i = 0; i < num; i++) { + if (data) + truly_spi_write_byte(1, data[i]); + } + gpio_set_value_cansleep(spi_mosi, 1); /* mosi high */ + gpio_set_value_cansleep(spi_cs, 1); /* cs high */ + udelay(10); + return 0; +} + +static void spi_pin_assign(void) +{ + /* Setting the Default GPIO's */ + spi_mosi = *(lcdc_truly_pdata->gpio_num); + spi_sclk = *(lcdc_truly_pdata->gpio_num + 1); + spi_cs = *(lcdc_truly_pdata->gpio_num + 2); + gpio_backlight_en = *(lcdc_truly_pdata->gpio_num + 3); + gpio_display_reset = *(lcdc_truly_pdata->gpio_num + 4); + pr_debug("spi_mosi:%d spi_sclk:%d spi_cs:%d backlight:%d reset:%d\n", + spi_mosi, spi_sclk, spi_cs, gpio_backlight_en, + gpio_display_reset); + +} + +static void truly_disp_powerup(void) +{ + /* Reset the hardware first */ + /* Include DAC power up implementation here */ + if (!truly_state.disp_powered_up && !truly_state.display_on) + truly_state.disp_powered_up = TRUE; +} + +static void truly_disp_reginit(void) +{ + pr_debug("%s disp_powered_up:%d display_on:%d\n", __func__, + truly_state.disp_powered_up, truly_state.display_on); + if (truly_state.disp_powered_up && !truly_state.display_on) { + gpio_set_value_cansleep(spi_cs, 1); /* cs high */ + + truly_spi_write(0xb9, init_item_v1, sizeof(init_item_v1)); + msleep(20); + truly_spi_write(0xcc, init_item_v2, sizeof(init_item_v2)); + truly_spi_write(0xb1, init_item_v3, sizeof(init_item_v3)); + truly_spi_write(0xb3, init_item_v4, sizeof(init_item_v4)); + truly_spi_write(0xb6, init_item_v5, sizeof(init_item_v5)); + truly_spi_write(0xb4, init_item_v6, sizeof(init_item_v6)); + truly_spi_write(0xc0, init_item_v7, sizeof(init_item_v7)); + truly_spi_write(0xe3, init_item_v8, sizeof(init_item_v8)); + truly_spi_write(0x3a, init_item_v9, sizeof(init_item_v9)); + truly_spi_write(0xe0, init_item_v10, sizeof(init_item_v10)); + truly_spi_write(0x36, init_item_v11, sizeof(init_item_v11)); + truly_spi_write(0x11, NULL, 0); + msleep(150); + truly_spi_write(0x29, NULL, 0); + msleep(25); + + truly_state.display_on = TRUE; + } +} + +static int lcdc_truly_panel_on(struct platform_device *pdev) +{ + /* Configure reset GPIO that drives DAC */ + if (lcdc_truly_pdata->panel_config_gpio) + lcdc_truly_pdata->panel_config_gpio(1); + gpio_set_value_cansleep(gpio_display_reset, 1); + truly_disp_powerup(); + truly_disp_reginit(); + truly_state.disp_initialized = TRUE; + return 0; +} + +static int lcdc_truly_panel_off(struct platform_device *pdev) +{ + if (truly_state.disp_powered_up && truly_state.display_on) { + /* Main panel power off (Pull down reset) */ + gpio_set_value_cansleep(gpio_display_reset, 0); + truly_state.display_on = FALSE; + truly_state.disp_initialized = FALSE; + } + return 0; +} + +static void lcdc_truly_set_backlight(struct msm_fb_data_type *mfd) +{ + int step = 0, i = 0; + unsigned long flags; + int bl_level = mfd->bl_level; + + /* real backlight level, 1 - max, 16 - min, 17 - off */ + bl_level = 17 - bl_level; + + if (bl_level > prev_bl) { + step = bl_level - prev_bl; + if (bl_level == 17) + step--; + } else if (bl_level < prev_bl) { + step = bl_level + 16 - prev_bl; + } else { + pr_info("%s: no change\n", __func__); + return; + } + + if (bl_level == 17) { + /* turn off backlight */ + gpio_set_value(gpio_backlight_en, 0); + } else { + local_irq_save(flags); + + if (prev_bl == 17) { + /* turn on backlight */ + gpio_set_value(gpio_backlight_en, 1); + udelay(30); + } + + /* adjust backlight level */ + for (i = 0; i < step; i++) { + gpio_set_value(gpio_backlight_en, 0); + udelay(1); + gpio_set_value(gpio_backlight_en, 1); + udelay(1); + } + + local_irq_restore(flags); + } + msleep(20); + prev_bl = bl_level; + + return; +} + +static int __devinit truly_probe(struct platform_device *pdev) +{ + + if (pdev->id == 0) { + lcdc_truly_pdata = pdev->dev.platform_data; + + if (!lcdc_truly_pdata) + pr_err("%s pdata is null\n", __func__); + + spi_pin_assign(); + return 0; + } + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = truly_probe, + .driver = { + .name = "lcdc_truly_hvga_ips3p2335_pt", + }, +}; + +static struct msm_fb_panel_data truly_panel_data = { + .on = lcdc_truly_panel_on, + .off = lcdc_truly_panel_off, + .set_backlight = lcdc_truly_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_truly_hvga_ips3p2335_pt", + .id = 1, + .dev = { + .platform_data = &truly_panel_data, + } +}; + +static int __init lcdc_truly_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + ret = msm_fb_detect_client("lcdc_truly_hvga_ips3p2335_pt"); + if (ret) + return 0; + + ret = platform_driver_register(&this_driver); + if (ret) { + pr_err("%s() driver registration failed", __func__); + return ret; + } + + pinfo = &truly_panel_data.panel_info; + pinfo->xres = 320; + pinfo->yres = 480; + 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; + /* 10Mhz mdp_lcdc_pclk and mdp_lcdc_pad_pcl */ + pinfo->clk_rate = 10240000; + pinfo->bl_max = 16; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 16; /* hsw = 8 + hbp=16 */ + pinfo->lcdc.h_front_porch = 4; + pinfo->lcdc.h_pulse_width = 8; + pinfo->lcdc.v_back_porch = 7; /* vsw=1 + vbp = 7 */ + 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) { + pr_err("%s not able to register the device\n", __func__); + platform_driver_unregister(&this_driver); + } + return ret; +} + +device_initcall(lcdc_truly_panel_init); diff --git a/drivers/video/msm/lcdc_wxga.c b/drivers/video/msm/lcdc_wxga.c new file mode 100644 index 0000000000000000000000000000000000000000..3204704791887acaa3cde303eae3e89582cd0f23 --- /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 0000000000000000000000000000000000000000..57d754e4d81aa522935b541e1ccb2dd71a37af60 --- /dev/null +++ b/drivers/video/msm/logo.c @@ -0,0 +1,103 @@ +/* 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, bool bf_supported) +{ + 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; + if (bf_supported && (info->node == 1 || info->node == 2)) { + err = -EPERM; + pr_err("%s:%d no info->creen_base on fb%d!\n", + __func__, __LINE__, info->node); + goto err_logo_free_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/lvds.c b/drivers/video/msm/lvds.c new file mode 100644 index 0000000000000000000000000000000000000000..63234232bd96ccbe1a222cd3a3cbf7555e1bcd20 --- /dev/null +++ b/drivers/video/msm/lvds.c @@ -0,0 +1,373 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 lvds_probe(struct platform_device *pdev); +static int lvds_remove(struct platform_device *pdev); + +static int lvds_off(struct platform_device *pdev); +static int lvds_on(struct platform_device *pdev); + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +static struct clk *lvds_clk; + +static struct platform_driver lvds_driver = { + .probe = lvds_probe, + .remove = lvds_remove, + .suspend = NULL, + .resume = NULL, + .shutdown = NULL, + .driver = { + .name = "lvds", + }, +}; + +static struct lcdc_platform_data *lvds_pdata; + +static void lvds_init(struct msm_fb_data_type *mfd) +{ + unsigned int lvds_intf = 0, lvds_phy_cfg0 = 0; + + MDP_OUTP(MDP_BASE + 0xc2034, 0x33); + usleep(1000); + + /* LVDS PHY PLL configuration */ + MDP_OUTP(MDP_BASE + 0xc3004, 0x62); + MDP_OUTP(MDP_BASE + 0xc3008, 0x30); + MDP_OUTP(MDP_BASE + 0xc300c, 0xc4); + MDP_OUTP(MDP_BASE + 0xc3014, 0x10); + MDP_OUTP(MDP_BASE + 0xc3018, 0x05); + MDP_OUTP(MDP_BASE + 0xc301c, 0x62); + MDP_OUTP(MDP_BASE + 0xc3020, 0x41); + MDP_OUTP(MDP_BASE + 0xc3024, 0x0d); + + MDP_OUTP(MDP_BASE + 0xc3000, 0x01); + /* Wait until LVDS PLL is locked and ready */ + while (!readl_relaxed(MDP_BASE + 0xc3080)) + cpu_relax(); + + writel_relaxed(0x00, mmss_cc_base + 0x0264); + writel_relaxed(0x00, mmss_cc_base + 0x0094); + + writel_relaxed(0x02, mmss_cc_base + 0x00E4); + + writel_relaxed((0x80 | readl_relaxed(mmss_cc_base + 0x00E4)), + mmss_cc_base + 0x00E4); + usleep(1000); + writel_relaxed((~0x80 & readl_relaxed(mmss_cc_base + 0x00E4)), + mmss_cc_base + 0x00E4); + + writel_relaxed(0x05, mmss_cc_base + 0x0094); + writel_relaxed(0x02, mmss_cc_base + 0x0264); + /* Wait until LVDS pixel clock output is enabled */ + mb(); + + if (mfd->panel_info.bpp == 24) { + if (lvds_pdata && + lvds_pdata->lvds_pixel_remap && + lvds_pdata->lvds_pixel_remap()) { + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D0_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc2014, 0x05080001); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D0_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2018, 0x00020304); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D1_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc201c, 0x1011090a); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D1_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2020, 0x000b0c0d); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D2_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc2024, 0x191a1213); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D2_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2028, 0x00141518); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D3_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc202c, 0x171b0607); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D3_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2030, 0x000e0f16); + } else { + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D0_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc2014, 0x03040508); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D0_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2018, 0x00000102); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D1_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc201c, 0x0c0d1011); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D1_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2020, 0x00090a0b); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D2_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc2024, 0x151a191a); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D2_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2028, 0x00121314); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D3_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc202c, 0x0f16171b); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D3_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2030, 0x0006070e); + } + if (mfd->panel_info.lvds.channel_mode == + LVDS_DUAL_CHANNEL_MODE) { + lvds_intf = 0x0001ff80; + lvds_phy_cfg0 = BIT(6) | BIT(7); + if (mfd->panel_info.lvds.channel_swap) + lvds_intf |= BIT(4); + } else { + lvds_intf = 0x00010f84; + lvds_phy_cfg0 = BIT(6); + } + } else if (mfd->panel_info.bpp == 18) { + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D0_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc2014, 0x03040508); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D0_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2018, 0x00000102); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D1_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc201c, 0x0c0d1011); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D1_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2020, 0x00090a0b); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D2_3_TO_0 */ + MDP_OUTP(MDP_BASE + 0xc2024, 0x1518191a); + /* MDP_LCDC_LVDS_MUX_CTL_FOR_D2_6_TO_4 */ + MDP_OUTP(MDP_BASE + 0xc2028, 0x00121314); + + if (mfd->panel_info.lvds.channel_mode == + LVDS_DUAL_CHANNEL_MODE) { + lvds_intf = 0x00017788; + lvds_phy_cfg0 = BIT(6) | BIT(7); + if (mfd->panel_info.lvds.channel_swap) + lvds_intf |= BIT(4); + } else { + lvds_intf = 0x0001078c; + lvds_phy_cfg0 = BIT(6); + } + } else { + BUG(); + } + + /* MDP_LVDSPHY_CFG0 */ + MDP_OUTP(MDP_BASE + 0xc3100, lvds_phy_cfg0); + /* MDP_LCDC_LVDS_INTF_CTL */ + MDP_OUTP(MDP_BASE + 0xc2000, lvds_intf); + MDP_OUTP(MDP_BASE + 0xc3108, 0x30); + lvds_phy_cfg0 |= BIT(4); + + /* Wait until LVDS PHY registers are configured */ + mb(); + usleep(1); + /* MDP_LVDSPHY_CFG0, enable serialization */ + MDP_OUTP(MDP_BASE + 0xc3100, lvds_phy_cfg0); +} + +static int lvds_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 (lvds_clk) + clk_disable_unprepare(lvds_clk); + + MDP_OUTP(MDP_BASE + 0xc3100, 0x0); + MDP_OUTP(MDP_BASE + 0xc3000, 0x0); + usleep(10); + + if (lvds_pdata && lvds_pdata->lcdc_power_save) + lvds_pdata->lcdc_power_save(0); + + if (lvds_pdata && lvds_pdata->lcdc_gpio_config) + ret = lvds_pdata->lcdc_gpio_config(0); + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(0); +#endif + + return ret; +} + +static int lvds_on(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_data_type *mfd; + unsigned long panel_pixclock_freq = 0; + mfd = platform_get_drvdata(pdev); + + if (lvds_pdata && lvds_pdata->lcdc_get_clk) + panel_pixclock_freq = lvds_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); +#endif + mfd = platform_get_drvdata(pdev); + + if (lvds_clk) { + mfd->fbi->var.pixclock = clk_round_rate(lvds_clk, + mfd->fbi->var.pixclock); + ret = clk_set_rate(lvds_clk, mfd->fbi->var.pixclock); + if (ret) { + pr_err("%s: Can't set lvds clock to rate %u\n", + __func__, mfd->fbi->var.pixclock); + goto out; + } + clk_prepare_enable(lvds_clk); + } + + if (lvds_pdata && lvds_pdata->lcdc_power_save) + lvds_pdata->lcdc_power_save(1); + if (lvds_pdata && lvds_pdata->lcdc_gpio_config) + ret = lvds_pdata->lcdc_gpio_config(1); + + lvds_init(mfd); + ret = panel_next_on(pdev); + +out: + return ret; +} + +static int lvds_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) { + lvds_pdata = pdev->dev.platform_data; + + lvds_clk = clk_get(&pdev->dev, "lvds_clk"); + if (IS_ERR_OR_NULL(lvds_clk)) { + pr_err("Couldnt find lvds_clk\n"); + lvds_clk = NULL; + } + 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("lvds_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 = lvds_on; + pdata->off = lvds_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; + if (lvds_clk) { + fbi->var.pixclock = clk_round_rate(lvds_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; + + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto lvds_probe_err; + + pdev_list[pdev_list_cnt++] = pdev; + + return 0; + +lvds_probe_err: + platform_device_put(mdp_dev); + return rc; +} + +static int lvds_remove(struct platform_device *pdev) +{ + return 0; +} + +static int lvds_register_driver(void) +{ + return platform_driver_register(&lvds_driver); +} + +static int __init lvds_driver_init(void) +{ + return lvds_register_driver(); +} + +module_init(lvds_driver_init); diff --git a/drivers/video/msm/lvds_chimei_wxga.c b/drivers/video/msm/lvds_chimei_wxga.c new file mode 100644 index 0000000000000000000000000000000000000000..9a385b9235a7cdb96a3fd2c7279c0e0945a00e12 --- /dev/null +++ b/drivers/video/msm/lvds_chimei_wxga.c @@ -0,0 +1,167 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#define LVDS_CHIMEI_PWM_FREQ_HZ 300 +#define LVDS_CHIMEI_PWM_PERIOD_USEC (USEC_PER_SEC / LVDS_CHIMEI_PWM_FREQ_HZ) +#define LVDS_CHIMEI_PWM_LEVEL 255 +#define LVDS_CHIMEI_PWM_DUTY_LEVEL \ + (LVDS_CHIMEI_PWM_PERIOD_USEC / LVDS_CHIMEI_PWM_LEVEL) + + +static struct lvds_panel_platform_data *cm_pdata; +static struct platform_device *cm_fbpdev; +static struct pwm_device *bl_lpm; + +static int lvds_chimei_panel_on(struct platform_device *pdev) +{ + return 0; +} + +static int lvds_chimei_panel_off(struct platform_device *pdev) +{ + return 0; +} + +static void lvds_chimei_set_backlight(struct msm_fb_data_type *mfd) +{ + int ret; + + pr_debug("%s: back light level %d\n", __func__, mfd->bl_level); + + if (bl_lpm) { + ret = pwm_config(bl_lpm, LVDS_CHIMEI_PWM_DUTY_LEVEL * + mfd->bl_level, LVDS_CHIMEI_PWM_PERIOD_USEC); + if (ret) { + pr_err("pwm_config on lpm failed %d\n", ret); + return; + } + if (mfd->bl_level) { + ret = pwm_enable(bl_lpm); + if (ret) + pr_err("pwm enable/disable on lpm failed" + "for bl %d\n", mfd->bl_level); + } else { + pwm_disable(bl_lpm); + } + } +} + +static int __devinit lvds_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; + } + + if (cm_pdata != NULL) + bl_lpm = pwm_request(cm_pdata->gpio[0], + "backlight"); + + if (bl_lpm == NULL || IS_ERR(bl_lpm)) { + pr_err("%s pwm_request() failed\n", __func__); + bl_lpm = NULL; + } + pr_debug("bl_lpm = %p lpm = %d\n", bl_lpm, + cm_pdata->gpio[0]); + + 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 = lvds_chimei_probe, + .driver = { + .name = "lvds_chimei_wxga", + }, +}; + +static struct msm_fb_panel_data lvds_chimei_panel_data = { + .on = lvds_chimei_panel_on, + .off = lvds_chimei_panel_off, + .set_backlight = lvds_chimei_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lvds_chimei_wxga", + .id = 1, + .dev = { + .platform_data = &lvds_chimei_panel_data, + } +}; + +static int __init lvds_chimei_wxga_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + if (msm_fb_detect_client("lvds_chimei_wxga")) + return 0; + + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &lvds_chimei_panel_data.panel_info; + pinfo->xres = 1366; + pinfo->yres = 768; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LVDS_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 24; + pinfo->fb_num = 2; + pinfo->clk_rate = 75000000; + pinfo->bl_max = 255; + pinfo->bl_min = 1; + + /* + * this panel is operated by de, + * vsycn and hsync are ignored + */ + pinfo->lcdc.h_back_porch = 0; + pinfo->lcdc.h_front_porch = 194; + pinfo->lcdc.h_pulse_width = 40; + pinfo->lcdc.v_back_porch = 0; + pinfo->lcdc.v_front_porch = 38; + pinfo->lcdc.v_pulse_width = 20; + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; + pinfo->lvds.channel_mode = LVDS_SINGLE_CHANNEL_MODE; + + /* Set border color, padding only for reducing active display region */ + pinfo->lcdc.border_clr = 0x0; + pinfo->lcdc.xres_pad = 0; + pinfo->lcdc.yres_pad = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + + return ret; +} + +module_init(lvds_chimei_wxga_init); diff --git a/drivers/video/msm/mddi.c b/drivers/video/msm/mddi.c index b061d709bc44ce78e114908f3e17e2eff54583d4..115491387f2f86412507fce233e61e36b81ad4e7 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-2012, 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,581 @@ * * This program is distributed in the hope 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 "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; -}; - -struct reg_read_info { - struct completion done; - uint32_t reg; - uint32_t status; - uint32_t result; -}; - -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]; - - struct platform_device client_pdev; -}; - -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)) +#include +#include +#include -void mddi_activate_link(struct msm_mddi_client_data *cdata) +#include +#include +#include +#include +#include +#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) { - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - - mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; } -static void mddi_handle_link_list_done(struct mddi_info *mddi) +static int mddi_runtime_resume(struct device *dev) { + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; } -static void mddi_reset_rev_encap_ptr(struct mddi_info *mddi) +static int mddi_runtime_idle(struct device *dev) { - 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); + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; } -static void mddi_handle_rev_data(struct mddi_info *mddi, union mddi_rev *rev) -{ - 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); - } -} +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_wait_interrupt(struct mddi_info *mddi, uint32_t intmask); +static int pmdh_clk_status; +int irq_enabled; +unsigned char mddi_timer_shutdown_flag; -static void mddi_handle_rev_data_avail(struct mddi_info *mddi) +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, + }, +}; + +extern int int_mddi_pri_flag; +DEFINE_MUTEX(pmdh_clk_lock); + +int pmdh_clk_func(int value) { - 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); - } + 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; +} - if (rev_data_count == 0) - 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); +static void pmdh_clk_disable() +{ + mutex_lock(&pmdh_clk_lock); + if (pmdh_clk_status == 0) { + mutex_unlock(&pmdh_clk_lock); return; } - 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 (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; } - 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_clk) { + clk_disable_unprepare(mddi_clk); + pmdh_clk_status = 0; } + if (mddi_pclk) + clk_disable_unprepare(mddi_pclk); + mutex_unlock(&pmdh_clk_lock); } -static irqreturn_t mddi_isr(int irq, void *data) +static void pmdh_clk_enable() { - struct msm_mddi_client_data *cdata = data; - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - uint32_t active, status; - - spin_lock(&mddi->int_lock); - - active = mddi_readl(INT); - status = mddi_readl(STAT); - - mddi_writel(active, INT); - - /* ignore any interrupts we have disabled */ - active &= mddi->int_enable; - - mddi->got_int |= active; - wake_up(&mddi->int_wait); - - if (active & MDDI_INT_PRI_LINK_LIST_DONE) { - mddi->int_enable &= (~MDDI_INT_PRI_LINK_LIST_DONE); - mddi_handle_link_list_done(mddi); + mutex_lock(&pmdh_clk_lock); + if (pmdh_clk_status == 1) { + mutex_unlock(&pmdh_clk_lock); + return; } - if (active & MDDI_INT_REV_DATA_AVAIL) - mddi_handle_rev_data_avail(mddi); - if (active & ~MDDI_INT_NEED_CLEAR) - mddi->int_enable &= ~(active & ~MDDI_INT_NEED_CLEAR); - - if (active & MDDI_INT_LINK_ACTIVE) { - mddi->int_enable &= (~MDDI_INT_LINK_ACTIVE); - mddi->int_enable |= MDDI_INT_IN_HIBERNATION; + if (mddi_clk) { + clk_prepare_enable(mddi_clk); + pmdh_clk_status = 1; } + if (mddi_pclk) + clk_prepare_enable(mddi_pclk); - if (active & MDDI_INT_IN_HIBERNATION) { - mddi->int_enable &= (~MDDI_INT_IN_HIBERNATION); - mddi->int_enable |= MDDI_INT_LINK_ACTIVE; + if (int_mddi_pri_flag && !irq_enabled) { + enable_irq(INT_MDDI_PRI); + irq_enabled = 1; } - mddi_writel(mddi->int_enable, INTEN); - spin_unlock(&mddi->int_lock); + if (mddi_host_timer.function) + mddi_host_timer_service(0); - return IRQ_HANDLED; + mutex_unlock(&pmdh_clk_lock); } -static long mddi_wait_interrupt_timeout(struct mddi_info *mddi, - uint32_t intmask, int timeout) +static int mddi_off(struct platform_device *pdev) { - 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); -} + struct msm_fb_data_type *mfd; + boolean dma_pending, dma_update_flag; + int ret, i; -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); -} + mfd = platform_get_drvdata(pdev); -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); + 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_unprepare(mfd->ebi1_clk); +#endif + pm_runtime_put(&pdev->dev); + return ret; } -void mddi_set_auto_hibernate(struct msm_mddi_client_data *cdata, int on) +static int mddi_on(struct platform_device *pdev) { - 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); + 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 */ + clk_rate = clk_round_rate(mddi_clk, 49000000); + if (!clk_set_rate(mddi_clk, clk_rate)) { + 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_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); + + clk_rate = clk_round_rate(mddi_clk, clk_rate); + if (clk_set_rate(mddi_clk, clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_rate failed\n", + __func__); + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(2); +#else + if (mfd->ebi1_clk) + clk_prepare_enable(mfd->ebi1_clk); +#endif + ret = panel_next_on(pdev); + + return ret; } +static int mddi_resource_initialized; -static uint16_t mddi_init_registers(struct mddi_info *mddi) +static int mddi_probe(struct platform_device *pdev) { - 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); + 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; + unsigned long rate; + int ret; + struct clk *ebi1_clk = NULL; + + if ((pdev->id == 0) && (pdev->num_resources >= 0)) { + mddi_pdata = pdev->dev.platform_data; + pmdh_clk_status = 0; + + mddi_clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(mddi_clk)) { + pr_err("can't find mddi_clk\n"); + return PTR_ERR(mddi_clk); + } + rate = clk_round_rate(mddi_clk, 49000000); + ret = clk_set_rate(mddi_clk, rate); + if (ret) + pr_err("Can't set mddi_clk min rate to %lu\n", + rate); + + pr_info("mddi_clk init rate is %lu\n", + clk_get_rate(mddi_clk)); + mddi_pclk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(mddi_pclk)) + mddi_pclk = NULL; + pmdh_clk_enable(); + +#ifndef CONFIG_MSM_BUS_SCALING + ebi1_clk = clk_get(&pdev->dev, "mem_clk"); + if (IS_ERR(ebi1_clk)) + return PTR_ERR(ebi1_clk); + clk_set_rate(ebi1_clk, 65000000); +#endif + + size = resource_size(&pdev->resource[0]); + msm_pmdh_base = ioremap(pdev->resource[0].start, size); + + MSM_FB_INFO("primary mddi base phy_addr = 0x%x virt = 0x%x\n", + pdev->resource[0].start, (int) msm_pmdh_base); + + if (unlikely(!msm_pmdh_base)) + return -ENOMEM; + + if (mddi_pdata && mddi_pdata->mddi_power_save) + mddi_pdata->mddi_power_save(1); + + mddi_resource_initialized = 1; + return 0; } - /* Recommendation from PAD hw team */ - mddi_writel(0xa850f, PAD_CTL); + if (!mddi_resource_initialized) + return -EPERM; + mfd = platform_get_drvdata(pdev); + mfd->ebi1_clk = ebi1_clk; - /* Need an even number for counts */ - mddi_writel(0x60006, DRIVER_START_CNT); + if (!mfd) + return -ENODEV; - mddi_set_auto_hibernate(&mddi->client_data, 0); + if (mfd->key != MFD_KEY) + return -EINVAL; - mddi_writel(MDDI_CMD_DISP_IGNORE, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; - mddi_init_rev_encap(mddi); - return mddi_readl(CORE_VER) & 0xffff; -} + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; -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); + /* + * 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))) { + 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); + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto mddi_probe_err; + + pdev_list[pdev_list_cnt++] = pdev; + +#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 + + return 0; + +mddi_probe_err: + platform_device_put(mdp_dev); + return rc; } -static void mddi_resume(struct msm_mddi_client_data *cdata) +static int mddi_pad_ctrl; +static int mddi_power_locked; + +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 __devinit 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); + + 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; - - mutex_lock(&mddi->reg_write_lock); + mddi_host_type host_idx = MDDI_HOST_PRIM; + if (mddi_is_in_suspend) + return 0; - ll = mddi->reg_write_data; + mddi_is_in_suspend = 1; - 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; + if (mddi_power_locked) + return 0; - ra->register_address = reg; - ra->register_data_list = val; + pmdh_clk_enable(); - 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; + mddi_pad_ctrl = mddi_host_reg_in(PAD_CTL); + mddi_host_reg_out(PAD_CTL, 0x0); - 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 __devinit 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); -static int __init mddi_rev_data_setup(struct mddi_info *mddi) -{ - void *dma; - dma_addr_t dma_addr; - /* 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; } +#endif -static int __devinit mddi_probe(struct platform_device *pdev) +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mddi_early_suspend(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; + pm_message_t state; + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + mddi_early_suspend); - 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; - } - 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; + state.event = PM_EVENT_SUSPEND; + mddi_suspend(mfd->pdev, state); +} - mutex_init(&mddi->reg_write_lock); - mutex_init(&mddi->reg_read_lock); - spin_lock_init(&mddi->int_lock); - init_waitqueue_head(&mddi->int_wait); +static void mddi_early_resume(struct early_suspend *h) +{ + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + mddi_early_suspend); + mddi_resume(mfd->pdev); +} +#endif - ret = mddi_clk_setup(pdev, mddi, pdata->clk_rate); - if (ret) { - printk(KERN_ERR "mddi: failed to setup clock!\n"); - goto error_clk_setup; +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); } - ret = mddi_rev_data_setup(mddi); - if (ret) { - printk(KERN_ERR "mddi: failed to setup rev data!\n"); - goto error_rev_data; - } + iounmap(msm_pmdh_base); - mddi->int_enable = 0; - mddi_writel(mddi->int_enable, INTEN); - ret = request_irq(mddi->irq, mddi_isr, 0, "mddi", - &mddi->client_data); - if (ret) { - printk(KERN_ERR "mddi: failed to request enable irq!\n"); - goto error_request_irq; - } + return 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; - } +static int mddi_register_driver(void) +{ + return platform_driver_register(&mddi_driver); +} - /* 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; - } - } +static int __init mddi_driver_init(void) +{ + int ret; - 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 d2a091cebe2c31f42edabb2524d7053acc5956e3..ebbae87885b69f1ff5e09ba46363970e82fc25f2 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 7fcd67e132bf19114dff0ad9b05bb007b1ee5efc..eb8c701fed68418516c59c079315f61c0bb85969 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 053eb6877330ba5644edb9d82748a99baffe58c3..8868781090984dc4c67bc310c744234870995a52 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,42 +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_one(gpio, GPIOF_IN, "vsync"); - if (ret) - goto err_request_gpio_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: - gpio_free(gpio); -err_request_gpio_failed: - return ret; -} - static int mddi_toshiba_probe(struct platform_device *pdev) { int ret; @@ -227,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; @@ -253,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 0000000000000000000000000000000000000000..dc79fed45bfd22b88016a9cce1dab345e8c82bf8 --- /dev/null +++ b/drivers/video/msm/mddi_ext.c @@ -0,0 +1,354 @@ +/* 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); + + clk_rate = clk_round_rate(mddi_ext_clk, clk_rate); + if (clk_set_rate(mddi_ext_clk, clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_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; + mddi_ext_clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(mddi_ext_clk)) { + pr_err("can't find emdh_clk\n"); + return PTR_ERR(mddi_ext_clk); + } + clk_prepare_enable(mddi_ext_clk); + + mddi_ext_pclk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(mddi_ext_pclk)) + mddi_ext_pclk = NULL; + else + clk_prepare_enable(mddi_ext_pclk); + + 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; + + clk_disable_unprepare(mddi_ext_clk); + if (mddi_ext_pclk) + clk_disable_unprepare(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_prepare_enable(mddi_ext_clk); + if (mddi_ext_pclk) + clk_prepare_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; + + ret = mddi_ext_register_driver(); + if (ret) { + 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 0000000000000000000000000000000000000000..da79513289ff82767b95dee00b0c0aaafc92bf0d --- /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 45cc01fc1e7fc349aabd2377789e4556b6b4c4be..47bb4494c5f71cbd2b0f95e7ee066121ff12ca56 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 0000000000000000000000000000000000000000..fa48c751eabcf964fd33dc8331a5d729c9cbbab2 --- /dev/null +++ b/drivers/video/msm/mddi_orise.c @@ -0,0 +1,128 @@ +/* Copyright (c) 2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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.is_type1 = TRUE; + 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 0000000000000000000000000000000000000000..ec2bf57a9f35deef898843dcf39847357f79f145 --- /dev/null +++ b/drivers/video/msm/mddi_prism.c @@ -0,0 +1,112 @@ +/* Copyright (c) 2008-2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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->mddi.is_type1 = TRUE; + 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 0000000000000000000000000000000000000000..95e7d41d1217df33044821e0db0cac695c94219c --- /dev/null +++ b/drivers/video/msm/mddi_quickvx.c @@ -0,0 +1,719 @@ +/* Copyright (c) 2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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->mddi.is_type1 = TRUE; + 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 0000000000000000000000000000000000000000..6a9008fa0179024af62425d45883fbb4c51ad763 --- /dev/null +++ b/drivers/video/msm/mddi_sharp.c @@ -0,0 +1,901 @@ +/* Copyright (c) 2008-2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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->mddi.is_type1 = 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 0000000000000000000000000000000000000000..9727453218f73dd5aeab1ac3b122d21122912526 --- /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 0000000000000000000000000000000000000000..646f5e9e156a11c1a9452c9bf765f6f6b5de0184 --- /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 0000000000000000000000000000000000000000..73749f969b0fa49707b51a7e1049461776a30216 --- /dev/null +++ b/drivers/video/msm/mddi_toshiba_vga.c @@ -0,0 +1,134 @@ +/* Copyright (c) 2009-2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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.mddi.is_type1 = 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 0000000000000000000000000000000000000000..c1925e16718df87ee366f6df912428a0d6c22f3b --- /dev/null +++ b/drivers/video/msm/mddi_toshiba_wvga.c @@ -0,0 +1,61 @@ +/* Copyright (c) 2009-2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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.mddi.is_type1 = 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 0000000000000000000000000000000000000000..8da14854510d6f41fbbd6c41ffcc800adc9c401e --- /dev/null +++ b/drivers/video/msm/mddi_toshiba_wvga_pt.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 = 6102; /* adjust refx100 to prevent tearing */ + pinfo.mddi.is_type1 = TRUE; + pinfo.lcd.v_back_porch = 8; /* vsw=10 + vbp = 8 */ + pinfo.lcd.v_front_porch = 2; + pinfo.lcd.v_pulse_width = 10; + 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 0000000000000000000000000000000000000000..c6acf9f28074a3045111401ca7dbccb72c753f82 --- /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 0000000000000000000000000000000000000000..52bc67c815004f0917611f98c81cc60f14fe5965 --- /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 0000000000000000000000000000000000000000..d53aa6fa7a5a31cd553452f2d2652de4836dc79e --- /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 0000000000000000000000000000000000000000..1a5a3fdca66aca895acee1fb7aafde17ed16ef14 --- /dev/null +++ b/drivers/video/msm/mddihosti.c @@ -0,0 +1,2268 @@ +/* Copyright (c) 2008-2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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); + } + +#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 0000000000000000000000000000000000000000..166d15c89db2ed94cdf42639f63e1fe4216ff423 --- /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 cb2ddf164c98cfb527181d46de66e489102c4a1f..7fd2d91195c6adf0681a1a315cbfda76600733fa 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-2012, Code Aurora Forum. All rights reserved. * Copyright (C) 2007 Google Incorporated * * This software is licensed under the terms of the GNU General Public @@ -15,509 +15,2700 @@ * 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_lut_clk; +int mdp_rev; + +static struct platform_device *mdp_init_pdev; +static struct regulator *footswitch; +static unsigned int mdp_footswitch_on; + +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; -#include -#include -#include -#include +#ifdef CONFIG_FB_MSM_MDP40 +uint32 mdp_intr_mask = MDP4_ANY_INTR_MASK; +#else +uint32 mdp_intr_mask = MDP_ANY_INTR_MASK; +#endif -#include "mdp_hw.h" +MDP_BLOCK_TYPE mdp_debug[MDP_MAX_BLOCK]; -struct class *mdp_class; +atomic_t mdp_block_power_cnt[MDP_MAX_BLOCK]; -#define MDP_CMD_DEBUG_ACCESS_BASE (0x10000) +spinlock_t mdp_spin_lock; +struct workqueue_struct *mdp_dma_wq; /*mdp dma wq */ +struct workqueue_struct *mdp_vsync_wq; /*mdp vsync wq */ -static uint16_t mdp_default_ccs[] = { - 0x254, 0x000, 0x331, 0x254, 0xF38, 0xE61, 0x254, 0x409, 0x000, - 0x010, 0x080, 0x080 -}; +struct workqueue_struct *mdp_hist_wq; /*mdp histogram wq */ -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); +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; +static 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 + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL +struct mdp_dma_data dma_wb_data; +#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; + +extern int first_pixel_start_x; +extern int first_pixel_start_y; + +#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 -static int enable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +struct list_head mdp_hist_lut_list; +DEFINE_MUTEX(mdp_hist_lut_list_mutex); + +uint32_t mdp_block2base(uint32_t block) { - unsigned long irq_flags; - int ret = 0; + uint32_t base = 0x0; + switch (block) { + case MDP_BLOCK_DMA_P: + base = 0x90000; + break; + case MDP_BLOCK_DMA_S: + base = 0xA0000; + break; + case MDP_BLOCK_VG_1: + base = 0x20000; + break; + case MDP_BLOCK_VG_2: + base = 0x30000; + break; + case MDP_BLOCK_RGB_1: + base = 0x40000; + break; + case MDP_BLOCK_RGB_2: + base = 0x50000; + break; + case MDP_BLOCK_OVERLAY_0: + base = 0x10000; + break; + case MDP_BLOCK_OVERLAY_1: + base = 0x18000; + break; + case MDP_BLOCK_OVERLAY_2: + base = (mdp_rev >= MDP_REV_44) ? 0x88000 : 0; + break; + default: + break; + } + return base; +} + +static uint32_t mdp_pp_block2hist_lut(uint32_t block) +{ + uint32_t valid = 0; + switch (block) { + case MDP_BLOCK_DMA_P: + valid = (mdp_rev >= MDP_REV_40) ? 1 : 0; + break; + case MDP_BLOCK_DMA_S: + valid = (mdp_rev >= MDP_REV_40) ? 1 : 0; + break; + case MDP_BLOCK_VG_1: + valid = (mdp_rev >= MDP_REV_40) ? 1 : 0; + break; + case MDP_BLOCK_VG_2: + valid = (mdp_rev >= MDP_REV_40) ? 1 : 0; + break; + default: + break; + } + return valid; +} - BUG_ON(!mask); +static void mdp_hist_lut_init_mgmt(struct mdp_hist_lut_mgmt *mgmt, + uint32_t block) +{ + mutex_init(&mgmt->lock); + mgmt->block = block; - 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; + mutex_lock(&mdp_hist_lut_list_mutex); + list_add(&mgmt->list, &mdp_hist_lut_list); + mutex_unlock(&mdp_hist_lut_list_mutex); +} + +static int mdp_hist_lut_init(void) +{ + struct mdp_hist_lut_mgmt *temp; + struct list_head *pos, *q; + INIT_LIST_HEAD(&mdp_hist_lut_list); + + if (mdp_rev >= MDP_REV_30) { + temp = kmalloc(sizeof(struct mdp_hist_lut_mgmt), GFP_KERNEL); + if (!temp) + goto exit; + mdp_hist_lut_init_mgmt(temp, MDP_BLOCK_DMA_P); + } + + if (mdp_rev >= MDP_REV_40) { + temp = kmalloc(sizeof(struct mdp_hist_lut_mgmt), GFP_KERNEL); + if (!temp) + goto exit_list; + mdp_hist_lut_init_mgmt(temp, MDP_BLOCK_VG_1); + + temp = kmalloc(sizeof(struct mdp_hist_lut_mgmt), GFP_KERNEL); + if (!temp) + goto exit_list; + mdp_hist_lut_init_mgmt(temp, MDP_BLOCK_VG_2); + } + + if (mdp_rev > MDP_REV_42) { + temp = kmalloc(sizeof(struct mdp_hist_lut_mgmt), GFP_KERNEL); + if (!temp) + goto exit_list; + mdp_hist_lut_init_mgmt(temp, MDP_BLOCK_DMA_S); } - /* if the mdp irq is not already enabled enable it */ - if (!mdp_irq_mask) { - if (clk) - clk_enable(clk); - enable_irq(mdp->irq); + return 0; + +exit_list: + mutex_lock(&mdp_hist_lut_list_mutex); + list_for_each_safe(pos, q, &mdp_hist_lut_list) { + temp = list_entry(pos, struct mdp_hist_lut_mgmt, list); + list_del(pos); + kfree(temp); } + mutex_unlock(&mdp_hist_lut_list_mutex); +exit: + pr_err("Failed initializing histogram LUT memory\n"); + return -ENOMEM; +} + +static int mdp_hist_lut_block2mgmt(uint32_t block, + struct mdp_hist_lut_mgmt **mgmt) +{ + struct mdp_hist_lut_mgmt *temp, *output; + int ret = 0; + + output = NULL; + + mutex_lock(&mdp_hist_lut_list_mutex); + list_for_each_entry(temp, &mdp_hist_lut_list, list) { + if (temp->block == block) + output = temp; + } + mutex_unlock(&mdp_hist_lut_list_mutex); + + if (output == NULL) + ret = -EINVAL; + else + *mgmt = output; - /* 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; } -static int locked_disable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +#define MDP_HIST_LUT_SIZE (256) +static int mdp_hist_lut_write_off(struct mdp_hist_lut_data *data, + struct mdp_hist_lut_info *info, uint32_t offset) { - /* 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; + int i; + uint32_t element[MDP_HIST_LUT_SIZE]; + uint32_t base = mdp_block2base(info->block); + uint32_t sel = info->bank_sel; + + + if (data->len != MDP_HIST_LUT_SIZE) { + pr_err("%s: data->len != %d", __func__, MDP_HIST_LUT_SIZE); + return -EINVAL; } - /* 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); + + if (copy_from_user(&element, data->data, + MDP_HIST_LUT_SIZE * sizeof(uint32_t))) { + pr_err("%s: Error copying histogram data", __func__); + return -ENOMEM; } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < MDP_HIST_LUT_SIZE; i++) + MDP_OUTP(MDP_BASE + base + offset + (0x400*(sel)) + (4*i), + element[i]); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return 0; } -static int disable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +static int mdp_hist_lut_write(struct mdp_hist_lut_data *data, + struct mdp_hist_lut_info *info) { - unsigned long irq_flags; - int ret; + int ret = 0; - spin_lock_irqsave(&mdp_lock, irq_flags); - ret = locked_disable_mdp_irq(mdp, mask); - spin_unlock_irqrestore(&mdp_lock, irq_flags); + if (data->block != info->block) { + ret = -1; + pr_err("%s, data/info mdp_block mismatch! %d != %d\n", + __func__, data->block, info->block); + goto error; + } + + switch (data->block) { + case MDP_BLOCK_VG_1: + case MDP_BLOCK_VG_2: + ret = mdp_hist_lut_write_off(data, info, 0x3400); + break; + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + ret = mdp_hist_lut_write_off(data, info, 0x4800); + break; + default: + ret = -EINVAL; + goto error; + } + +error: return ret; } -static irqreturn_t mdp_isr(int irq, void *data) +#define MDP_HIST_LUT_VG_EN_MASK (0x20000) +#define MDP_HIST_LUT_VG_EN_SHIFT (17) +#define MDP_HIST_LUT_VG_EN_OFFSET (0x0058) +#define MDP_HIST_LUT_VG_SEL_OFFSET (0x0064) +static void mdp_hist_lut_commit_vg(struct mdp_hist_lut_info *info) { - uint32_t status; - unsigned long irq_flags; - struct mdp_info *mdp = data; + uint32_t out_en, temp_en; + uint32_t base = mdp_block2base(info->block); + temp_en = (info->is_enabled) ? (1 << MDP_HIST_LUT_VG_EN_SHIFT) : 0x0; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + out_en = inpdw(MDP_BASE + base + MDP_HIST_LUT_VG_EN_OFFSET) & + ~MDP_HIST_LUT_VG_EN_MASK; + MDP_OUTP(MDP_BASE + base + MDP_HIST_LUT_VG_EN_OFFSET, out_en | temp_en); + + if (info->has_sel_update) + MDP_OUTP(MDP_BASE + base + MDP_HIST_LUT_VG_SEL_OFFSET, + info->bank_sel); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} - spin_lock_irqsave(&mdp_lock, irq_flags); +#define MDP_HIST_LUT_DMA_EN_MASK (0x7) +#define MDP_HIST_LUT_DMA_SEL_MASK (0x400) +#define MDP_HIST_LUT_DMA_SEL_SHIFT (10) +#define MDP_HIST_LUT_DMA_P_OFFSET (0x0070) +#define MDP_HIST_LUT_DMA_S_OFFSET (0x0028) +static void mdp_hist_lut_commit_dma(struct mdp_hist_lut_info *info) +{ + uint32_t out, temp, mask; + uint32_t base = mdp_block2base(info->block); + uint32_t offset = (info->block == MDP_BLOCK_DMA_P) ? + MDP_HIST_LUT_DMA_P_OFFSET : MDP_HIST_LUT_DMA_S_OFFSET; - status = mdp_readl(mdp, MDP_INTR_STATUS); - mdp_writel(mdp, status, MDP_INTR_CLEAR); + mask = MDP_HIST_LUT_DMA_EN_MASK; + temp = (info->is_enabled) ? 0x7 : 0x0; - 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); + if (info->has_sel_update) { + mask |= MDP_HIST_LUT_DMA_SEL_MASK; + temp |= ((info->bank_sel & 0x1) << MDP_HIST_LUT_DMA_SEL_SHIFT); } - if (status & DL0_ROI_DONE) - wake_up(&mdp_ppp_waitqueue); + out = inpdw(MDP_BASE + base + offset) & ~mask; + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + base + offset, out | temp); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} - if (status) - locked_disable_mdp_irq(mdp, status); +static void mdp_hist_lut_commit_info(struct mdp_hist_lut_info *info) +{ + switch (info->block) { + case MDP_BLOCK_VG_1: + case MDP_BLOCK_VG_2: + mdp_hist_lut_commit_vg(info); + break; + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + mdp_hist_lut_commit_dma(info); + break; + default: + goto error; + } - spin_unlock_irqrestore(&mdp_lock, irq_flags); - return IRQ_HANDLED; +error: + return; } -static uint32_t mdp_check_mask(uint32_t mask) +static void mdp_hist_lut_update_info(struct mdp_hist_lut_info *info, int ops) { - uint32_t ret; - unsigned long irq_flags; - - spin_lock_irqsave(&mdp_lock, irq_flags); - ret = mdp_irq_mask & mask; - spin_unlock_irqrestore(&mdp_lock, irq_flags); - return ret; + info->bank_sel = (ops & 0x8) >> 3; + info->is_enabled = (ops & 0x1) ? TRUE : FALSE; + info->has_sel_update = (ops & 0x10) ? TRUE : FALSE; } -static int mdp_wait(struct mdp_info *mdp, uint32_t mask, wait_queue_head_t *wq) +int mdp_hist_lut_config(struct mdp_hist_lut_data *data) { + struct mdp_hist_lut_mgmt *mgmt = NULL; + struct mdp_hist_lut_info info; int ret = 0; - unsigned long irq_flags; - wait_event_timeout(*wq, !mdp_check_mask(mask), HZ); + if (!mdp_pp_block2hist_lut(data->block)) { + ret = -ENOTTY; + goto error; + } - 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; + ret = mdp_hist_lut_block2mgmt(data->block, &mgmt); + if (ret) + goto error; + + mutex_lock(&mgmt->lock); + + info.block = mgmt->block; + + mdp_hist_lut_update_info(&info, data->ops); + + switch ((data->ops & 0x6) >> 1) { + case 0x1: + pr_info("%s: histogram LUT read not supported\n", __func__); + break; + case 0x2: + ret = mdp_hist_lut_write(data, &info); + if (ret) + goto error_lock; + break; + default: + break; } - spin_unlock_irqrestore(&mdp_lock, irq_flags); + mdp_hist_lut_commit_info(&info); + +error_lock: + mutex_unlock(&mgmt->lock); +error: return ret; } -void mdp_dma_wait(struct mdp_device *mdp_dev) +DEFINE_MUTEX(mdp_lut_push_sem); +static int mdp_lut_i; +static int mdp_lut_hw_update(struct fb_cmap *cmap) { -#define MDP_MAX_TIMEOUTS 20 - static int timeout_count; - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + 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))); + } - if (mdp_wait(mdp, DL0_DMA2_TERM_DONE, &mdp_dma2_waitqueue) == -ETIMEDOUT) - timeout_count++; - else - timeout_count = 0; + return 0; +} - if (timeout_count > MDP_MAX_TIMEOUTS) { - printk(KERN_ERR "mdp: dma failed %d times, somethings wrong!\n", - MDP_MAX_TIMEOUTS); - BUG(); - } +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; + + 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); + + if (ret) + return ret; + + mutex_lock(&mdp_lut_push_sem); + mdp_lut_push = 1; + mdp_lut_push_i = mdp_lut_i; + mutex_unlock(&mdp_lut_push_sem); + + mdp_lut_i = (mdp_lut_i + 1)%2; + + return 0; } -static int mdp_ppp_wait(struct mdp_info *mdp) +static int mdp_lut_update_lcdc(struct fb_info *info, struct fb_cmap *cmap) { - return mdp_wait(mdp, DL0_ROI_DONE, &mdp_ppp_waitqueue); + int ret; + uint32_t out; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + ret = mdp_lut_hw_update(cmap); + + if (ret) { + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return ret; + } + + /*mask off non LUT select bits*/ + out = inpdw(MDP_BASE + 0x90070) & ~((0x1 << 10) | 0x7); + MDP_OUTP(MDP_BASE + 0x90070, (mdp_lut_i << 10) | 0x7 | out); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_lut_i = (mdp_lut_i + 1)%2; + + return 0; } -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_lut_enable(void) { - uint32_t dma2_cfg; - uint16_t ld_param = 0; /* 0=PRIM, 1=SECD, 2=EXT */ + uint32_t out; + if (mdp_lut_push) { + mutex_lock(&mdp_lut_push_sem); + mdp_lut_push = 0; + out = inpdw(MDP_BASE + 0x90070) & ~((0x1 << 10) | 0x7); + MDP_OUTP(MDP_BASE + 0x90070, + (mdp_lut_push_i << 10) | 0x7 | out); + mutex_unlock(&mdp_lut_push_sem); + } +} - if (enable_mdp_irq(mdp, DL0_DMA2_TERM_DONE)) { - printk(KERN_ERR "mdp_dma_to_mddi: busy\n"); - return; +#define MDP_REV42_HIST_MAX_BIN 128 +#define MDP_REV41_HIST_MAX_BIN 32 + +#define MDP_HIST_DATA32_R_OFF 0x0100 +#define MDP_HIST_DATA32_G_OFF 0x0200 +#define MDP_HIST_DATA32_B_OFF 0x0300 + +#define MDP_HIST_DATA128_R_OFF 0x0400 +#define MDP_HIST_DATA128_G_OFF 0x0800 +#define MDP_HIST_DATA128_B_OFF 0x0C00 + +#define MDP_HIST_DATA_LUMA_OFF 0x0200 + +#define MDP_HIST_EXTRA_DATA0_OFF 0x0028 +#define MDP_HIST_EXTRA_DATA1_OFF 0x002C + +struct mdp_hist_mgmt *mdp_hist_mgmt_array[MDP_HIST_MGMT_MAX]; + +void __mdp_histogram_kickoff(struct mdp_hist_mgmt *mgmt) +{ + char *mdp_hist_base = MDP_BASE + mgmt->base; + if (mgmt->mdp_is_hist_data == TRUE) { + MDP_OUTP(mdp_hist_base + 0x0004, mgmt->frame_cnt); + MDP_OUTP(mdp_hist_base, 1); } +} - dma_callback = callback; +void __mdp_histogram_reset(struct mdp_hist_mgmt *mgmt) +{ + char *mdp_hist_base = MDP_BASE + mgmt->base; + MDP_OUTP(mdp_hist_base + 0x000C, 1); +} - dma2_cfg = DMA_PACK_TIGHT | - DMA_PACK_ALIGN_LSB | - DMA_PACK_PATTERN_RGB | - DMA_OUT_SEL_AHB | - DMA_IBUF_NONCONTIGUOUS; +static void mdp_hist_read_work(struct work_struct *data); - dma2_cfg |= DMA_IBUF_FORMAT_RGB565; +static int mdp_hist_init_mgmt(struct mdp_hist_mgmt *mgmt, uint32_t block) +{ + uint32_t bins, extra, index, term = 0; + init_completion(&mgmt->mdp_hist_comp); + mutex_init(&mgmt->mdp_hist_mutex); + mutex_init(&mgmt->mdp_do_hist_mutex); + mgmt->block = block; + mgmt->base = mdp_block2base(block); + mgmt->mdp_is_hist_start = FALSE; + mgmt->mdp_is_hist_data = FALSE; + mgmt->mdp_is_hist_valid = FALSE; + mgmt->mdp_is_hist_init = FALSE; + mgmt->frame_cnt = 0; + mgmt->bit_mask = 0; + mgmt->num_bins = 0; + switch (block) { + case MDP_BLOCK_DMA_P: + term = MDP_HISTOGRAM_TERM_DMA_P; + bins = (mdp_rev >= MDP_REV_42) ? MDP_REV42_HIST_MAX_BIN : + MDP_REV41_HIST_MAX_BIN; + extra = 2; + mgmt->base += (mdp_rev >= MDP_REV_40) ? 0x5000 : 0x4000; + index = MDP_HIST_MGMT_DMA_P; + break; + case MDP_BLOCK_DMA_S: + term = MDP_HISTOGRAM_TERM_DMA_S; + bins = MDP_REV42_HIST_MAX_BIN; + extra = 2; + mgmt->base += 0x5000; + index = MDP_HIST_MGMT_DMA_S; + break; + case MDP_BLOCK_VG_1: + term = MDP_HISTOGRAM_TERM_VG_1; + bins = MDP_REV42_HIST_MAX_BIN; + extra = 1; + mgmt->base += 0x6000; + index = MDP_HIST_MGMT_VG_1; + break; + case MDP_BLOCK_VG_2: + term = MDP_HISTOGRAM_TERM_VG_2; + bins = MDP_REV42_HIST_MAX_BIN; + extra = 1; + mgmt->base += 0x6000; + index = MDP_HIST_MGMT_VG_2; + break; + default: + term = MDP_HISTOGRAM_TERM_DMA_P; + bins = (mdp_rev >= MDP_REV_42) ? MDP_REV42_HIST_MAX_BIN : + MDP_REV41_HIST_MAX_BIN; + extra = 2; + mgmt->base += (mdp_rev >= MDP_REV_40) ? 0x5000 : 0x4000; + index = MDP_HIST_MGMT_DMA_P; + } + mgmt->irq_term = term; - dma2_cfg |= DMA_OUT_SEL_MDDI; + mgmt->c0 = kmalloc(bins * sizeof(uint32_t), GFP_KERNEL); + if (mgmt->c0 == NULL) + goto error; - dma2_cfg |= DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY; + mgmt->c1 = kmalloc(bins * sizeof(uint32_t), GFP_KERNEL); + if (mgmt->c1 == NULL) + goto error_1; - dma2_cfg |= DMA_DITHER_EN; + mgmt->c2 = kmalloc(bins * sizeof(uint32_t), GFP_KERNEL); + if (mgmt->c2 == NULL) + goto error_2; - /* 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); + mgmt->extra_info = kmalloc(extra * sizeof(uint32_t), GFP_KERNEL); + if (mgmt->extra_info == NULL) + goto error_extra; - /* 666 18BPP */ - dma2_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + INIT_WORK(&mgmt->mdp_histogram_worker, mdp_hist_read_work); + mgmt->hist = NULL; - /* 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); + mdp_hist_mgmt_array[index] = mgmt; + return 0; - mdp_writel(mdp, dma2_cfg, MDP_CMD_DEBUG_ACCESS_BASE + 0x0180); +error_extra: + kfree(mgmt->c2); +error_2: + kfree(mgmt->c1); +error_1: + kfree(mgmt->c0); +error: + return -ENOMEM; +} - /* start DMA2 */ - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0044); +static void mdp_hist_del_mgmt(struct mdp_hist_mgmt *mgmt) +{ + kfree(mgmt->extra_info); + kfree(mgmt->c2); + kfree(mgmt->c1); + kfree(mgmt->c0); } -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 int mdp_histogram_init(void) { - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + struct mdp_hist_mgmt *temp; + int i, ret; + mdp_hist_wq = alloc_workqueue("mdp_hist_wq", + WQ_NON_REENTRANT | WQ_UNBOUND, 0); + + for (i = 0; i < MDP_HIST_MGMT_MAX; i++) + mdp_hist_mgmt_array[i] = NULL; + + if (mdp_rev >= MDP_REV_30) { + temp = kmalloc(sizeof(struct mdp_hist_mgmt), GFP_KERNEL); + if (!temp) + goto exit; + ret = mdp_hist_init_mgmt(temp, MDP_BLOCK_DMA_P); + if (ret) { + kfree(temp); + goto exit; + } + } + + if (mdp_rev >= MDP_REV_40) { + temp = kmalloc(sizeof(struct mdp_hist_mgmt), GFP_KERNEL); + if (!temp) + goto exit_list; + ret = mdp_hist_init_mgmt(temp, MDP_BLOCK_VG_1); + if (ret) + goto exit_list; + + temp = kmalloc(sizeof(struct mdp_hist_mgmt), GFP_KERNEL); + if (!temp) + goto exit_list; + ret = mdp_hist_init_mgmt(temp, MDP_BLOCK_VG_2); + if (ret) + goto exit_list; + } + + if (mdp_rev >= MDP_REV_42) { + temp = kmalloc(sizeof(struct mdp_hist_mgmt), GFP_KERNEL); + if (!temp) + goto exit_list; + ret = mdp_hist_init_mgmt(temp, MDP_BLOCK_DMA_S); + if (ret) + goto exit_list; + } + + return 0; - if (interface == MSM_MDDI_PMDH_INTERFACE) { - mdp_dma_to_mddi(mdp, addr, stride, width, height, x, y, - callback); +exit_list: + for (i = 0; i < MDP_HIST_MGMT_MAX; i++) { + temp = mdp_hist_mgmt_array[i]; + if (!temp) + continue; + mdp_hist_del_mgmt(temp); + kfree(temp); + mdp_hist_mgmt_array[i] = NULL; } +exit: + return -ENOMEM; } -int get_img(struct mdp_img *img, struct fb_info *info, - unsigned long *start, unsigned long *len, - struct file **filep) +int mdp_histogram_block2mgmt(uint32_t block, struct mdp_hist_mgmt **mgmt) { - int put_needed, ret = 0; - struct file *file; + struct mdp_hist_mgmt *temp, *output; + int i, ret = 0; - file = fget_light(img->memory_id, &put_needed); - if (file == NULL) - return -1; + output = NULL; - 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); + for (i = 0; i < MDP_HIST_MGMT_MAX; i++) { + temp = mdp_hist_mgmt_array[i]; + if (!temp) + continue; + + if (temp->block == block) { + output = temp; + break; + } + } + + if (output == NULL) + ret = -EINVAL; + else + *mgmt = output; return ret; } -void put_img(struct file *src_file, struct file *dst_file) +static int mdp_histogram_enable(struct mdp_hist_mgmt *mgmt) { + uint32_t base; + if (mgmt->mdp_is_hist_data == TRUE) { + pr_err("%s histogram already started\n", __func__); + return -EINVAL; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_enable_irq(mgmt->irq_term); + INIT_COMPLETION(mgmt->mdp_hist_comp); + + base = (uint32_t) (MDP_BASE + mgmt->base); + MDP_OUTP(base + 0x0018, INTR_HIST_DONE | INTR_HIST_RESET_SEQ_DONE); + MDP_OUTP(base + 0x0010, 1); + MDP_OUTP(base + 0x001C, INTR_HIST_DONE | INTR_HIST_RESET_SEQ_DONE); + + MDP_OUTP(base + 0x0004, mgmt->frame_cnt); + if (mgmt->block != MDP_BLOCK_VG_1 && mgmt->block != MDP_BLOCK_VG_2) + MDP_OUTP(base + 0x0008, mgmt->bit_mask); + mgmt->mdp_is_hist_data = TRUE; + mgmt->mdp_is_hist_valid = TRUE; + mgmt->mdp_is_hist_init = FALSE; + __mdp_histogram_reset(mgmt); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return 0; } -int mdp_blit(struct mdp_device *mdp_dev, struct fb_info *fb, - struct mdp_blit_req *req) +static int mdp_histogram_disable(struct mdp_hist_mgmt *mgmt) { - 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"); + uint32_t base, status; + if (mgmt->mdp_is_hist_data == FALSE) { + pr_err("%s histogram already stopped\n", __func__); return -EINVAL; } - if (unlikely(req->dst_rect.h == 0 || - req->dst_rect.w == 0)) - return -EINVAL; - /* 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"); - return -EINVAL; - } + mgmt->mdp_is_hist_data = FALSE; + mgmt->mdp_is_hist_valid = FALSE; + mgmt->mdp_is_hist_init = FALSE; - 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"); - 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); + base = (uint32_t) (MDP_BASE + mgmt->base); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (mdp_rev >= MDP_REV_42) + MDP_OUTP(base + 0x0020, 1); + status = inpdw(base + 0x001C); + status &= ~(INTR_HIST_DONE | INTR_HIST_RESET_SEQ_DONE); + MDP_OUTP(base + 0x001C, status); + + MDP_OUTP(base + 0x0018, INTR_HIST_DONE | INTR_HIST_RESET_SEQ_DONE); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + complete(&mgmt->mdp_hist_comp); + mdp_disable_irq(mgmt->irq_term); 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); +} + +/*call when spanning mgmt_array only*/ +int _mdp_histogram_ctrl(boolean en, struct mdp_hist_mgmt *mgmt) +{ + int ret = 0; + + mutex_lock(&mgmt->mdp_hist_mutex); + if (mgmt->mdp_is_hist_start == TRUE) { + if (en) + ret = mdp_histogram_enable(mgmt); + else + ret = mdp_histogram_disable(mgmt); + } + mutex_unlock(&mgmt->mdp_hist_mutex); + + if (en == false) + cancel_work_sync(&mgmt->mdp_histogram_worker); + return ret; } -void mdp_set_grp_disp(struct mdp_device *mdp_dev, unsigned disp_id) +int mdp_histogram_ctrl(boolean en, uint32_t block) { - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + struct mdp_hist_mgmt *mgmt = NULL; + int ret = 0; + + ret = mdp_histogram_block2mgmt(block, &mgmt); + if (ret) + goto error; - disp_id &= 0xf; - mdp_writel(mdp, disp_id, MDP_FULL_BYPASS_WORD43); + ret = _mdp_histogram_ctrl(en, mgmt); +error: + return ret; } -int register_mdp_client(struct class_interface *cint) +int mdp_histogram_ctrl_all(boolean en) { - if (!mdp_class) { - pr_err("mdp: no mdp_class when registering mdp client\n"); - return -ENODEV; + struct mdp_hist_mgmt *temp; + int i, ret = 0, ret_temp = 0; + + for (i = 0; i < MDP_HIST_MGMT_MAX; i++) { + temp = mdp_hist_mgmt_array[i]; + if (!temp) + continue; + + ret_temp = _mdp_histogram_ctrl(en, temp); + if (ret_temp) + ret = ret_temp; } - cint->class = mdp_class; - return class_interface_register(cint); + return ret; } -#include "mdp_csc_table.h" -#include "mdp_scale_tables.h" +int mdp_histogram_start(struct mdp_histogram_start_req *req) +{ + struct mdp_hist_mgmt *mgmt = NULL; + int ret; + + ret = mdp_histogram_block2mgmt(req->block, &mgmt); + if (ret) { + ret = -ENOTTY; + goto error; + } + + mutex_lock(&mgmt->mdp_hist_mutex); + if (mgmt->mdp_is_hist_start == TRUE) { + pr_err("%s histogram already started\n", __func__); + ret = -EPERM; + goto error_lock; + } + + mgmt->block = req->block; + mgmt->frame_cnt = req->frame_cnt; + mgmt->bit_mask = req->bit_mask; + mgmt->num_bins = req->num_bins; + mgmt->hist = NULL; + + ret = mdp_histogram_enable(mgmt); + + mgmt->mdp_is_hist_start = TRUE; + +error_lock: + mutex_unlock(&mgmt->mdp_hist_mutex); +error: + return ret; +} -int mdp_probe(struct platform_device *pdev) +int mdp_histogram_stop(struct fb_info *info, uint32_t block) { - struct resource *resource; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *) info->par; + struct mdp_hist_mgmt *mgmt = NULL; 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; + ret = mdp_histogram_block2mgmt(block, &mgmt); + if (ret) { + ret = -ENOTTY; + goto error; } - mdp = kzalloc(sizeof(struct mdp_info), GFP_KERNEL); - if (!mdp) - return -ENOMEM; + mutex_lock(&mgmt->mdp_hist_mutex); + if (mgmt->mdp_is_hist_start == FALSE) { + pr_err("%s histogram already stopped\n", __func__); + ret = -EPERM; + goto error_lock; + } + + mgmt->mdp_is_hist_start = FALSE; + + if (!mfd->panel_power_on) { + mgmt->mdp_is_hist_data = FALSE; + complete(&mgmt->mdp_hist_comp); + ret = -EINVAL; + goto error_lock; + } - 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; + ret = mdp_histogram_disable(mgmt); + + mutex_unlock(&mgmt->mdp_hist_mutex); + cancel_work_sync(&mgmt->mdp_histogram_worker); + return ret; + +error_lock: + mutex_unlock(&mgmt->mdp_hist_mutex); +error: + return ret; +} + +/*call from within mdp_hist_mutex context*/ +static int _mdp_histogram_read_dma_data(struct mdp_hist_mgmt *mgmt) +{ + char *mdp_hist_base; + uint32_t r_data_offset, g_data_offset, b_data_offset; + int i, ret = 0; + + mdp_hist_base = MDP_BASE + mgmt->base; + + r_data_offset = (32 == mgmt->num_bins) ? MDP_HIST_DATA32_R_OFF : + MDP_HIST_DATA128_R_OFF; + g_data_offset = (32 == mgmt->num_bins) ? MDP_HIST_DATA32_G_OFF : + MDP_HIST_DATA128_G_OFF; + b_data_offset = (32 == mgmt->num_bins) ? MDP_HIST_DATA32_B_OFF : + MDP_HIST_DATA128_B_OFF; + + if (mgmt->c0 == NULL || mgmt->c1 == NULL || mgmt->c2 == NULL) { + ret = -ENOMEM; + goto hist_err; + } + + if (!mgmt->hist) { + pr_err("%s: mgmt->hist not set, mgmt->hist = 0x%08x", + __func__, (uint32_t) mgmt->hist); + return -EINVAL; + } + + if (mgmt->hist->bin_cnt != mgmt->num_bins) { + pr_err("%s, bins config = %d, bin requested = %d", __func__, + mgmt->num_bins, mgmt->hist->bin_cnt); + return -EINVAL; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < mgmt->num_bins; i++) { + mgmt->c0[i] = inpdw(mdp_hist_base + r_data_offset + (4*i)); + mgmt->c1[i] = inpdw(mdp_hist_base + g_data_offset + (4*i)); + mgmt->c2[i] = inpdw(mdp_hist_base + b_data_offset + (4*i)); + } + + if (mdp_rev >= MDP_REV_42) { + if (mgmt->extra_info) { + mgmt->extra_info[0] = inpdw(mdp_hist_base + + MDP_HIST_EXTRA_DATA0_OFF); + mgmt->extra_info[1] = inpdw(mdp_hist_base + + MDP_HIST_EXTRA_DATA0_OFF + 4); + } else + ret = -ENOMEM; } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + if (!ret) + return ret; + +hist_err: + pr_err("%s: invalid hist buffer\n", __func__); + return ret; +} + +/*call from within mdp_hist_mutex context*/ +static int _mdp_histogram_read_vg_data(struct mdp_hist_mgmt *mgmt) +{ + char *mdp_hist_base; + int i, ret = 0; + + mdp_hist_base = MDP_BASE + mgmt->base; - mdp->base = ioremap(resource->start, resource_size(resource)); - if (mdp->base == 0) { - printk(KERN_ERR "msmfb: cannot allocate mdp regs!\n"); + if (mgmt->c0 == NULL) { ret = -ENOMEM; - goto error_ioremap; + goto hist_err; } - 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; + if (!mgmt->hist) { + pr_err("%s: mgmt->hist not set", __func__); + return -EINVAL; + } - clk = clk_get(&pdev->dev, "mdp_clk"); - if (IS_ERR(clk)) { - printk(KERN_INFO "mdp: failed to get mdp clk"); - ret = PTR_ERR(clk); - goto error_get_clk; + if (mgmt->hist->bin_cnt != mgmt->num_bins) { + pr_err("%s, bins config = %d, bin requested = %d", __func__, + mgmt->num_bins, mgmt->hist->bin_cnt); + return -EINVAL; } - ret = request_irq(mdp->irq, mdp_isr, 0, "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; + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < mgmt->num_bins; i++) + mgmt->c0[i] = inpdw(mdp_hist_base + MDP_HIST_DATA_LUMA_OFF + + (4*i)); + + if (mdp_rev >= MDP_REV_42) { + if (mgmt->extra_info) { + mgmt->extra_info[0] = inpdw(mdp_hist_base + + MDP_HIST_EXTRA_DATA0_OFF); + } else + ret = -ENOMEM; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + if (!ret) + return ret; -error_device_register: - free_irq(mdp->irq, mdp); -error_request_irq: -error_get_clk: - iounmap(mdp->base); -error_get_irq: -error_ioremap: - kfree(mdp); +hist_err: + pr_err("%s: invalid hist buffer\n", __func__); return ret; } -static struct platform_driver msm_mdp_driver = { - .probe = mdp_probe, - .driver = {.name = "msm_mdp"}, -}; +static void mdp_hist_read_work(struct work_struct *data) +{ + struct mdp_hist_mgmt *mgmt = container_of(data, struct mdp_hist_mgmt, + mdp_histogram_worker); + int ret = 0; + mutex_lock(&mgmt->mdp_hist_mutex); + if (mgmt->mdp_is_hist_data == FALSE) { + pr_debug("%s, Histogram disabled before read.\n", __func__); + ret = -EINVAL; + goto error; + } + + switch (mgmt->block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + ret = _mdp_histogram_read_dma_data(mgmt); + break; + case MDP_BLOCK_VG_1: + case MDP_BLOCK_VG_2: + ret = _mdp_histogram_read_vg_data(mgmt); + break; + default: + pr_err("%s, invalid MDP block = %d\n", __func__, mgmt->block); + ret = -EINVAL; + goto error; + } + + /* + * if read was triggered by an underrun or failed copying, + * don't wake up readers + */ + if (!ret && mgmt->mdp_is_hist_valid && mgmt->mdp_is_hist_init) { + mgmt->hist = NULL; + complete(&mgmt->mdp_hist_comp); + } + + if (mgmt->mdp_is_hist_valid == FALSE) + mgmt->mdp_is_hist_valid = TRUE; + if (mgmt->mdp_is_hist_init == FALSE) + mgmt->mdp_is_hist_init = TRUE; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (!ret) + __mdp_histogram_kickoff(mgmt); + else + __mdp_histogram_reset(mgmt); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); -static int __init mdp_init(void) +error: + mutex_unlock(&mgmt->mdp_hist_mutex); +} + +/*call from within mdp_hist_mutex*/ +static int _mdp_copy_hist_data(struct mdp_histogram_data *hist, + struct mdp_hist_mgmt *mgmt) +{ + int ret; + + if (hist->c0) { + ret = copy_to_user(hist->c0, mgmt->c0, + sizeof(uint32_t) * (hist->bin_cnt)); + if (ret) + goto err; + } + if (hist->c1) { + ret = copy_to_user(hist->c1, mgmt->c1, + sizeof(uint32_t) * (hist->bin_cnt)); + if (ret) + goto err; + } + if (hist->c2) { + ret = copy_to_user(hist->c2, mgmt->c2, + sizeof(uint32_t) * (hist->bin_cnt)); + if (ret) + goto err; + } + if (hist->extra_info) { + ret = copy_to_user(hist->extra_info, mgmt->extra_info, + sizeof(uint32_t) * ((hist->block > MDP_BLOCK_VG_2) ? 2 : 1)); + if (ret) + goto err; + } +err: + return ret; +} + +static int mdp_do_histogram(struct fb_info *info, + struct mdp_histogram_data *hist) { - 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); + struct mdp_hist_mgmt *mgmt = NULL; + int ret = 0; + + ret = mdp_histogram_block2mgmt(hist->block, &mgmt); + if (ret) { + pr_info("%s - %d", __func__, __LINE__); + ret = -EINVAL; + return ret; } - return platform_driver_register(&msm_mdp_driver); + + mutex_lock(&mgmt->mdp_do_hist_mutex); + if (!mgmt->frame_cnt || (mgmt->num_bins == 0)) { + pr_info("%s - frame_cnt = %d, num_bins = %d", __func__, + mgmt->frame_cnt, mgmt->num_bins); + ret = -EINVAL; + goto error; +} + if ((mdp_rev <= MDP_REV_41 && hist->bin_cnt > MDP_REV41_HIST_MAX_BIN) + || (mdp_rev == MDP_REV_42 && + hist->bin_cnt > MDP_REV42_HIST_MAX_BIN)) { + pr_info("%s - mdp_rev = %d, num_bins = %d", __func__, mdp_rev, + hist->bin_cnt); + ret = -EINVAL; + goto error; +} + mutex_lock(&mgmt->mdp_hist_mutex); + if (!mgmt->mdp_is_hist_data) { + pr_info("%s - hist_data = false!", __func__); + ret = -EINVAL; + goto error_lock; + } + + if (!mgmt->mdp_is_hist_start) { + pr_err("%s histogram not started\n", __func__); + ret = -EPERM; + goto error_lock; + } + + if (mgmt->hist != NULL) { + pr_err("%s; histogram attempted to be read twice\n", __func__); + ret = -EPERM; + goto error_lock; + } + mgmt->hist = hist; + mutex_unlock(&mgmt->mdp_hist_mutex); + + if (wait_for_completion_killable(&mgmt->mdp_hist_comp)) { + pr_err("%s(): histogram bin collection killed", __func__); + ret = -EINVAL; + goto error; + } + + mutex_lock(&mgmt->mdp_hist_mutex); + if (mgmt->mdp_is_hist_data && mgmt->mdp_is_hist_init) + ret = _mdp_copy_hist_data(hist, mgmt); + else + ret = -ENODATA; +error_lock: + mutex_unlock(&mgmt->mdp_hist_mutex); +error: + mutex_unlock(&mgmt->mdp_do_hist_mutex); + return ret; +} +#endif + +/* Returns < 0 on error, 0 on timeout, or > 0 on successful wait */ + +int mdp_ppp_pipe_wait(void) +{ + int ret = 1; + boolean wait; + unsigned long flag; + + /* wait 5 seconds for the operation to complete before declaring + the MDP hung */ + spin_lock_irqsave(&mdp_spin_lock, flag); + wait = mdp_ppp_waiting; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (wait == 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 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) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&mdp_lock, irq_flags); + 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); +} + +/* + * mdp_disable_irq: can not be called from isr + */ +void mdp_disable_irq(uint32 term) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&mdp_lock, irq_flags); + 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); +} + +void mdp_disable_irq_nosync(uint32 term) +{ + 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); +} + +void mdp_pipe_kickoff(uint32 term, struct msm_fb_data_type *mfd) +{ + unsigned long flag; + /* 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); + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_ppp_waiting = TRUE; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + 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 */ +#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_start(); +#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 if (term == MDP_OVERLAY2_TERM) { + mdp_pipe_ctrl(MDP_OVERLAY2_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_lut_enable(); + outpdw(MDP_BASE + 0x00D0, 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; + +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) +{ + 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 (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_unprepare(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_unprepare(mdp_pclk); + MSM_FB_DEBUG("MDP PCLK OFF\n"); + } + if (mdp_lut_clk != NULL) + clk_disable_unprepare(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_prepare_enable(mdp_clk); + MSM_FB_DEBUG("MDP CLK ON\n"); + } + if (mdp_pclk != NULL) { + clk_prepare_enable(mdp_pclk); + MSM_FB_DEBUG("MDP PCLK ON\n"); + } + if (mdp_lut_clk != NULL) + clk_prepare_enable(mdp_lut_clk); + mdp_vsync_clk_enable(); + } + up(&mdp_pipe_ctrl_mutex); + } +} + +void mdp_histogram_handle_isr(struct mdp_hist_mgmt *mgmt) +{ + uint32 isr, mask; + char *base_addr = MDP_BASE + mgmt->base; + isr = inpdw(base_addr + MDP_HIST_INTR_STATUS_OFF); + mask = inpdw(base_addr + MDP_HIST_INTR_ENABLE_OFF); + outpdw(base_addr + MDP_HIST_INTR_CLEAR_OFF, isr); + mb(); + isr &= mask; + if (isr & INTR_HIST_RESET_SEQ_DONE) + __mdp_histogram_kickoff(mgmt); + + if (isr & INTR_HIST_DONE) { + if ((waitqueue_active(&mgmt->mdp_hist_comp.wait)) + && (mgmt->hist != NULL)) { + if (!queue_work(mdp_hist_wq, + &mgmt->mdp_histogram_worker)) { + pr_err("%s %d- can't queue hist_read\n", + __func__, mgmt->block); + } + } else { + __mdp_histogram_reset(mgmt); + } + } +} + +#ifndef CONFIG_FB_MSM_MDP40 +irqreturn_t mdp_isr(int irq, void *ptr) +{ + uint32 mdp_interrupt = 0; + struct mdp_dma_data *dma; + unsigned long flag; + struct mdp_hist_mgmt *mgmt = NULL; + char *base_addr; + int i, ret; + /* Ensure all the register write are complete */ + mb(); + + mdp_is_in_isr = TRUE; + + mdp_interrupt = inp32(MDP_INTR_STATUS); + outp32(MDP_INTR_CLEAR, mdp_interrupt); + + mdp_interrupt &= mdp_intr_mask; + + if (mdp_interrupt & TV_ENC_UNDERRUN) { + mdp_interrupt &= ~(TV_ENC_UNDERRUN); + mdp_tv_underflow_cnt++; + } + + if (!mdp_interrupt) + goto out; + + /* 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); + + dma = &dma3_data; + if (dma->waiting) { + dma->waiting = FALSE; + complete(&dma->comp); + } + } + + if (mdp_rev >= MDP_REV_30) { + /* Only DMA_P histogram exists for this MDP rev*/ + if (mdp_interrupt & MDP_HIST_DONE) { + ret = mdp_histogram_block2mgmt(MDP_BLOCK_DMA_P, &mgmt); + if (!ret) + mdp_histogram_handle_isr(mgmt); + outp32(MDP_INTR_CLEAR, MDP_HIST_DONE); + } + + /* 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.*/ + for (i = 0; i < MDP_HIST_MGMT_MAX; i++) { + mgmt = mdp_hist_mgmt_array[i]; + if (!mgmt) + continue; + + base_addr = MDP_BASE + mgmt->base; + outpdw(base_addr + 0x010, 1); + outpdw(base_addr + 0x01C, INTR_HIST_DONE | + INTR_HIST_RESET_SEQ_DONE); + mgmt->mdp_is_hist_valid = FALSE; + __mdp_histogram_reset(mgmt); + } + } + + /* LCDC Frame Start */ + if (mdp_interrupt & LCDC_FRAME_START) { + dma = &dma2_data; + spin_lock_irqsave(&mdp_spin_lock, flag); + /* let's disable LCDC interrupt */ + mdp_intr_mask &= ~LCDC_FRAME_START; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + if (dma->waiting) { + dma->waiting = FALSE; + complete(&dma->comp); + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + } + + /* 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); + } + } + + /* 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; + spin_lock_irqsave(&mdp_spin_lock, flag); + dma->busy = FALSE; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + 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; + spin_lock_irqsave(&mdp_spin_lock, flag); + dma->busy = FALSE; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + 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); + spin_lock_irqsave(&mdp_spin_lock, flag); + if (mdp_ppp_waiting) { + mdp_ppp_waiting = FALSE; + complete(&mdp_ppp_comp); + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + } + +out: +mdp_is_in_isr = FALSE; + + return IRQ_HANDLED; +} +#endif + +static void mdp_drv_init(void) +{ + int i; + + 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 +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL + dma_wb_data.busy = FALSE; + dma_wb_data.waiting = FALSE; + init_completion(&dma_wb_data.comp); + mutex_init(&dma_wb_data.ov_mutex); +#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 +} + +static int mdp_probe(struct platform_device *pdev); +static int mdp_remove(struct platform_device *pdev); + +static int mdp_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int mdp_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +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; + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + + mdp_histogram_ctrl_all(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); + + if (mdp_rev >= MDP_REV_41 && mfd->panel.type == MIPI_CMD_PANEL) + mdp_dsi_cmd_overlay_suspend(); + return ret; +} + +static int mdp_on(struct platform_device *pdev) +{ + int ret = 0; + +#ifdef CONFIG_FB_MSM_MDP40 + struct msm_fb_data_type *mfd; + mdp4_overlay_ctrl_db_reset(); + + mfd = platform_get_drvdata(pdev); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (is_mdp4_hw_reset()) { + mdp_vsync_cfg_regs(mfd, FALSE); + mdp4_hw_init(); + outpdw(MDP_BASE + 0x0038, mdp4_display_intf); + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +#endif + + 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); + +#ifdef CONFIG_FB_MSM_MDP40 + if (mfd->panel.type == MIPI_CMD_PANEL) + mdp4_dsi_cmd_overlay_restore(); + else if (mfd->panel.type == MDDI_PANEL) + mdp4_mddi_overlay_restore(); +#endif + + mdp_histogram_ctrl_all(TRUE); + + return ret; +} + +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) +{ + 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) { + min_clk_rate = clk_round_rate(mdp_clk, min_clk_rate); + if (clk_set_rate(mdp_clk, min_clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_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++; + } + } + } +} +#endif + +#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 (mdp_bus_scale_handle < 1) { + pr_debug("%s invalid bus handle\n", __func__); + return -EINVAL; + } + 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); + 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; +} + +unsigned long mdp_get_core_clk(void) +{ + 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); + } + + return clk_rate; +} + +unsigned long mdp_perf_level2clk_rate(uint32 perf_level) +{ + 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 { + clk_rate = mdp_pdata-> + mdp_core_clk_table[mdp_pdata->num_mdp_clk + - perf_level]; + } + } else + clk_rate = mdp_get_core_clk(); + + return clk_rate; +} + +static int mdp_irq_clk_setup(struct platform_device *pdev, + char cont_splashScreen) +{ + int ret; + +#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(&pdev->dev, "vdd"); + if (IS_ERR(footswitch)) + footswitch = NULL; + else { + regulator_enable(footswitch); + mdp_footswitch_on = 1; + + if (mdp_rev == MDP_REV_42 && !cont_splashScreen) { + regulator_disable(footswitch); + msleep(20); + regulator_enable(footswitch); + } + } + + mdp_clk = clk_get(&pdev->dev, "core_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(&pdev->dev, "iface_clk"); + if (IS_ERR(mdp_pclk)) + mdp_pclk = NULL; + + if (mdp_rev >= MDP_REV_42) { + mdp_lut_clk = clk_get(&pdev->dev, "lut_clk"); + 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); + free_irq(mdp_irq, 0); + return ret; + } + } else { + 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) { + if (cont_splashScreen) + mdp_clk_rate = clk_get_rate(mdp_clk); + else + mdp_clk_rate = mdp_pdata->mdp_core_clk_rate; + + mutex_lock(&mdp_clk_lock); + clk_set_rate(mdp_clk, mdp_clk_rate); + if (mdp_lut_clk != NULL) + clk_set_rate(mdp_lut_clk, mdp_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 ; + unsigned long flag; +#ifdef CONFIG_FB_MSM_MDP40 + int intf, if_no; +#endif +#if defined(CONFIG_FB_MSM_MIPI_DSI) && defined(CONFIG_FB_MSM_MDP40) + struct mipi_panel_info *mipi; +#endif + static int contSplash_update_done; + + if ((pdev->id == 0) && (pdev->num_resources > 0)) { + mdp_init_pdev = pdev; + 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(pdev, mdp_pdata->cont_splash_enabled); + + if (rc) + return rc; + + mdp_hw_version(); + + /* initializing mdp hw */ +#ifdef CONFIG_FB_MSM_MDP40 + if (!(mdp_pdata->cont_splash_enabled)) + mdp4_hw_init(); +#else + mdp_hw_init(); +#endif + +#ifdef CONFIG_FB_MSM_OVERLAY + mdp_hw_cursor_init(); +#endif + mdp_resource_initialized = 1; + return 0; + } + + 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; + + /* link to the latest pdev */ + mfd->pdev = msm_fb_dev; + mfd->mdp_rev = mdp_rev; + + if (mdp_pdata) { + if (mdp_pdata->cont_splash_enabled) { + mfd->cont_splash_done = 0; + if (!contSplash_update_done) { + mdp_pipe_ctrl(MDP_CMD_BLOCK, + MDP_BLOCK_POWER_ON, FALSE); + contSplash_update_done = 1; + } + } else + mfd->cont_splash_done = 1; + } + + mfd->ov0_wb_buf = MDP_ALLOC(sizeof(struct mdp_buf_type)); + mfd->ov1_wb_buf = MDP_ALLOC(sizeof(struct mdp_buf_type)); + memset((void *)mfd->ov0_wb_buf, 0, sizeof(struct mdp_buf_type)); + memset((void *)mfd->ov1_wb_buf, 0, sizeof(struct mdp_buf_type)); + + if (mdp_pdata) { + mfd->ov0_wb_buf->size = mdp_pdata->ov0_wb_size; + mfd->ov1_wb_buf->size = mdp_pdata->ov1_wb_size; + mfd->mem_hid = mdp_pdata->mem_hid; + } else { + mfd->ov0_wb_buf->size = 0; + mfd->ov1_wb_buf->size = 0; + mfd->mem_hid = 0; + } + mfd->ov0_blt_state = 0; + mfd->use_ov0_blt = 0 ; + + /* initialize Post Processing data*/ + mdp_hist_lut_init(); + mdp_histogram_init(); + + /* 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); + } + +#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; + mfd->start_histogram = mdp_histogram_start; + mfd->stop_histogram = mdp_histogram_stop; + } 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 + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + if (mdp_hw_revision < MDP4_REVISION_V2_1) { + /* dmas dmap switch */ + mdp_intr_mask |= INTR_DMA_S_DONE; + } + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + 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(mdp_init_pdev, 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; + mfd->lut_update = mdp_lut_update_lcdc; + mfd->do_histogram = mdp_do_histogram; + mfd->start_histogram = mdp_histogram_start; + mfd->stop_histogram = mdp_histogram_stop; + 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; + mfd->start_histogram = mdp_histogram_start; + mfd->stop_histogram = mdp_histogram_stop; + 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 + if (mdp_rev >= MDP_REV_40) + mfd->cursor_update = mdp_hw_cursor_sync_update; + else + mfd->cursor_update = mdp_hw_cursor_update; + break; + + case MIPI_CMD_PANEL: +#ifndef CONFIG_FB_MSM_MDP303 + mfd->dma_fnc = mdp4_dsi_cmd_overlay; + mipi = &mfd->panel_info.mipi; + configure_mdp_core_clk_table((mipi->dsi_pclk_rate) * 3 / 2); + 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; + } + mfd->lut_update = mdp_lut_update_nonlcdc; + mfd->do_histogram = mdp_do_histogram; + mfd->start_histogram = mdp_histogram_start; + mfd->stop_histogram = mdp_histogram_stop; + mdp4_display_intf_sel(if_no, DSI_CMD_INTF); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +#else + mfd->dma_fnc = mdp_dma2_update; + mfd->do_histogram = mdp_do_histogram; + mfd->start_histogram = mdp_histogram_start; + mfd->stop_histogram = mdp_histogram_stop; + 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; + } + INIT_WORK(&mfd->dma_update_worker, + mdp_lcd_update_workqueue_handler); +#endif + mdp_config_vsync(mdp_init_pdev, 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: + case LVDS_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; + mfd->start_histogram = mdp_histogram_start; + mfd->stop_histogram = mdp_histogram_stop; +#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; + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL + case WRITEBACK_PANEL: + { + unsigned int mdp_version; + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, + FALSE); + mdp_version = inpdw(MDP_BASE + 0x0); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, + FALSE); + if (mdp_version < 0x04030303) { + pr_err("%s: writeback panel not supprted\n", + __func__); + platform_device_put(msm_fb_dev); + return -ENODEV; + } + pdata->on = mdp4_overlay_writeback_on; + pdata->off = mdp4_overlay_writeback_off; + mfd->dma_fnc = mdp4_writeback_overlay; + mfd->dma = &dma_wb_data; + mdp4_display_intf_sel(EXTERNAL_INTF_SEL, DTV_INTF); + } + break; +#endif + 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; + } + } + + /* req bus bandwidth immediately */ + if (!(mfd->cont_splash_done)) + mdp_bus_scale_update_request(5); + +#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; +} + +unsigned int mdp_check_suspended(void) +{ + unsigned int ret; + + mutex_lock(&mdp_suspend_mutex); + ret = mdp_suspended; + mutex_unlock(&mdp_suspend_mutex); + + return ret; +} + +void mdp_footswitch_ctrl(boolean on) +{ + mutex_lock(&mdp_suspend_mutex); + if (!mdp_suspended || mdp4_extn_disp || !footswitch || + mdp_rev <= MDP_REV_41) { + mutex_unlock(&mdp_suspend_mutex); + return; + } + + if (on && !mdp_footswitch_on) { + pr_debug("Enable MDP FS\n"); + regulator_enable(footswitch); + mdp_footswitch_on = 1; + } else if (!on && mdp_footswitch_on) { + pr_debug("Disable MDP FS\n"); + regulator_disable(footswitch); + mdp_footswitch_on = 0; + } + + mutex_unlock(&mdp_suspend_mutex); +} + +#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); + + 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; + } + } + + return 0; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mdp_early_suspend(struct early_suspend *h) +{ + mdp_suspend_sub(); +#ifdef CONFIG_FB_MSM_DTV + mdp4_dtv_set_black_screen(); +#endif + mdp4_iommu_detach(); + mdp_footswitch_ctrl(FALSE); +} + +static void mdp_early_resume(struct early_suspend *h) +{ + mdp_footswitch_ctrl(TRUE); + 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_driver_init(void) +{ + int ret; + + mdp_drv_init(); + + ret = mdp_register_driver(); + if (ret) { + printk(KERN_ERR "mdp_register_driver() failed!\n"); + return ret; + } + +#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 0000000000000000000000000000000000000000..b104b33ce6869abde58ccf48e4f9450351afbb73 --- /dev/null +++ b/drivers/video/msm/mdp.h @@ -0,0 +1,845 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#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; +extern struct mdp_csc_cfg mdp_csc_convert[4]; + +extern struct workqueue_struct *mdp_hist_wq; + +#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) +#define MDP_ALLOC(x) kmalloc(x, GFP_KERNEL) + +struct mdp_buf_type { + struct ion_handle *ihdl; + u32 phys_addr; + u32 size; +}; + +struct mdp_table_entry { + uint32_t reg; + uint32_t val; +}; + +extern struct mdp_ccs mdp_ccs_yuv2rgb ; +extern struct mdp_ccs mdp_ccs_rgb2yuv ; +extern unsigned char hdmi_prim_display; + +/* + * 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_OVERLAY2_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; +}; + +extern struct list_head mdp_hist_lut_list; +extern struct mutex mdp_hist_lut_list_mutex; +struct mdp_hist_lut_mgmt { + uint32_t block; + struct mutex lock; + struct list_head list; +}; + +struct mdp_hist_lut_info { + uint32_t block; + boolean is_enabled, has_sel_update; + int bank_sel; +}; + +struct mdp_hist_mgmt { + uint32_t block; + uint32_t irq_term; + uint32_t base; + struct completion mdp_hist_comp; + struct mutex mdp_hist_mutex; + struct mutex mdp_do_hist_mutex; + boolean mdp_is_hist_start, mdp_is_hist_data; + boolean mdp_is_hist_valid, mdp_is_hist_init; + uint8_t frame_cnt, bit_mask, num_bins; + struct work_struct mdp_histogram_worker; + struct mdp_histogram_data *hist; + uint32_t *c0, *c1, *c2; + uint32_t *extra_info; +}; + +enum { + MDP_HIST_MGMT_DMA_P = 0, + MDP_HIST_MGMT_DMA_S, + MDP_HIST_MGMT_VG_1, + MDP_HIST_MGMT_VG_2, + MDP_HIST_MGMT_MAX, +}; + +extern struct mdp_hist_mgmt *mdp_hist_mgmt_array[]; + +#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_OVERLAY2_TERM 0x80 +#define MDP_HISTOGRAM_TERM_DMA_P 0x100 +#define MDP_HISTOGRAM_TERM_DMA_S 0x200 +#define MDP_HISTOGRAM_TERM_VG_1 0x400 +#define MDP_HISTOGRAM_TERM_VG_2 0x800 + +#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) + +/* histogram interrupts */ +#define INTR_HIST_DONE BIT(1) +#define INTR_HIST_RESET_SEQ_DONE BIT(0) + +#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)<= 0x0402030b); +} + +int mdp4_overlay_dtv_set(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +int mdp4_overlay_dtv_unset(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(struct mdp_dma_data *dma); +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); +int mdp4_overlay_dsi_state_get(void); +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); +void mdp4_mixer_pipe_cleanup(int mixer); +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_wait(struct fb_info *info, + struct msmfb_overlay_data *req); +int mdp4_overlay_play(struct fb_info *info, struct msmfb_overlay_data *req); +struct mdp4_overlay_pipe *mdp4_overlay_pipe_alloc(int ptype, int mixer); +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(struct mdp_dma_data *dma); +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_dma_p_done_dsi_video(struct mdp_dma_data *dma); +void mdp4_dma_p_done_lcdc(void); +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_overlay_lcdc_wait4vsync(struct msm_fb_data_type *mfd); +void mdp4_overlay_lcdc_start(void); +void mdp4_overlay_lcdc_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_update_perf_level(u32 perf_level); +void mdp4_set_perf_level(void); +void mdp4_mddi_overlay_dmas_restore(void); + +#ifndef CONFIG_FB_MSM_MIPI_DSI +void mdp4_mddi_dma_busy_wait(struct msm_fb_data_type *mfd); +void mdp4_mddi_overlay_restore(void); +#else +static inline void mdp4_mddi_dma_busy_wait(struct msm_fb_data_type *mfd) +{ + /* empty */ +} +static inline void mdp4_mddi_overlay_restore(void) +{ + /* empty */ +} +#endif + +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); + + +#ifdef CONFIG_FB_MSM_MIPI_DSI +int mdp4_dsi_overlay_blt_start(struct msm_fb_data_type *mfd); +int mdp4_dsi_overlay_blt_stop(struct msm_fb_data_type *mfd); +void mdp4_dsi_video_blt_start(struct msm_fb_data_type *mfd); +void mdp4_dsi_video_blt_stop(struct msm_fb_data_type *mfd); +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 int mdp4_dsi_overlay_blt_start(struct msm_fb_data_type *mfd) +{ + return -ENODEV; +} +static inline int mdp4_dsi_overlay_blt_stop(struct msm_fb_data_type *mfd) +{ + return -ENODEV; +} +static inline void mdp4_dsi_video_blt_start(struct msm_fb_data_type *mfd) +{ +} +static inline void mdp4_dsi_video_blt_stop(struct msm_fb_data_type *mfd) +{ +} +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); +void mdp4_lcdc_overlay_blt_start(struct msm_fb_data_type *mfd); +void mdp4_lcdc_overlay_blt_stop(struct msm_fb_data_type *mfd); +void mdp4_dtv_overlay_blt_start(struct msm_fb_data_type *mfd); +void mdp4_dtv_overlay_blt_stop(struct msm_fb_data_type *mfd); + +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); +void mdp4_overlay_panel_mode_unset(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); + +#ifdef CONFIG_FB_MSM_MIPI_DSI +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_overlay_dsi_video_start(void); +void mdp4_overlay_dsi_video_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_dsi_cmd_overlay_restore(void); +void mdp_dsi_cmd_overlay_suspend(void); +#else +static inline void mdp4_dsi_cmd_dma_busy_wait(struct msm_fb_data_type *mfd) +{ + /* empty */ +} +static inline void mdp4_dsi_blt_dmap_busy_wait(struct msm_fb_data_type *mfd) +{ + /* empty */ +} +static inline void mdp4_overlay_dsi_video_start(void) +{ + /* empty */ +} +static inline void mdp4_overlay_dsi_video_vsync_push( + struct msm_fb_data_type *mfd, struct mdp4_overlay_pipe *pipe) +{ + /* empty */ +} +static inline void mdp4_dsi_cmd_overlay_restore(void) +{ + /* empty */ +} +#ifdef CONFIG_FB_MSM_MDP40 +static inline void mdp_dsi_cmd_overlay_suspend(void) +{ + /* empty */ +} +#endif +#endif /* MIPI_DSI */ + +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_overlay_panel_3d(int mixer_num, uint32 panel_3d); +int mdp4_overlay_3d_sbys(struct fb_info *info, struct msmfb_overlay_3d *req); +void mdp4_dsi_cmd_3d_sbys(struct msm_fb_data_type *mfd, + struct msmfb_overlay_3d *r3d); +void mdp4_dsi_video_3d_sbys(struct msm_fb_data_type *mfd, + struct msmfb_overlay_3d *r3d); + +int mdp4_mixer_info(int mixer_num, struct mdp_mixer_info *info); + +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_primary_vsync_dsi_video(void); +uint32_t mdp4_ss_table_value(int8_t param, int8_t index); +void mdp4_overlay_ctrl_db_reset(void); + +int mdp4_overlay_writeback_on(struct platform_device *pdev); +int mdp4_overlay_writeback_off(struct platform_device *pdev); +void mdp4_writeback_overlay(struct msm_fb_data_type *mfd); +void mdp4_writeback_kickoff_video(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_writeback_dma_busy_wait(struct msm_fb_data_type *mfd); +void mdp4_overlay1_done_writeback(struct mdp_dma_data *dma); + +int mdp4_writeback_start(struct fb_info *info); +int mdp4_writeback_stop(struct fb_info *info); +int mdp4_writeback_dequeue_buffer(struct fb_info *info, + struct msmfb_data *data); +int mdp4_writeback_queue_buffer(struct fb_info *info, + struct msmfb_data *data); +void mdp4_writeback_dma_stop(struct msm_fb_data_type *mfd); +int mdp4_writeback_init(struct fb_info *info); +int mdp4_writeback_terminate(struct fb_info *info); + +uint32_t mdp_block2base(uint32_t block); +int mdp_hist_lut_config(struct mdp_hist_lut_data *data); + +void mdp4_hsic_set(struct mdp4_overlay_pipe *pipe, struct dpp_ctrl *ctrl); +void mdp4_hsic_update(struct mdp4_overlay_pipe *pipe); +int mdp4_csc_config(struct mdp_csc_cfg_data *config); +void mdp4_csc_write(struct mdp_csc_cfg *data, uint32_t base); +int mdp4_csc_enable(struct mdp_csc_cfg_data *config); +int mdp4_pcc_cfg(struct mdp_pcc_cfg_data *cfg_ptr); +int mdp4_argc_cfg(struct mdp_pgc_lut_data *pgc_ptr); +int mdp4_qseed_cfg(struct mdp_qseed_cfg_data *cfg); +u32 mdp4_allocate_writeback_buf(struct msm_fb_data_type *mfd, u32 mix_num); +void mdp4_init_writeback_buf(struct msm_fb_data_type *mfd, u32 mix_num); +void mdp4_free_writeback_buf(struct msm_fb_data_type *mfd, u32 mix_num); + +int mdp4_igc_lut_config(struct mdp_igc_lut_data *cfg); +void mdp4_iommu_unmap(struct mdp4_overlay_pipe *pipe); +void mdp4_iommu_attach(void); +void mdp4_iommu_detach(void); +int mdp4_v4l2_overlay_set(struct fb_info *info, struct mdp_overlay *req, + struct mdp4_overlay_pipe **ppipe); +void mdp4_v4l2_overlay_clear(struct mdp4_overlay_pipe *pipe); +int mdp4_v4l2_overlay_play(struct fb_info *info, struct mdp4_overlay_pipe *pipe, + unsigned long srcp0_addr, unsigned long srcp1_addr, + unsigned long srcp2_addr); + +#endif /* MDP_H */ diff --git a/drivers/video/msm/mdp4_dtv.c b/drivers/video/msm/mdp4_dtv.c new file mode 100644 index 0000000000000000000000000000000000000000..f0353bd6b8a12d5995136ca8b23541c8e954c62a --- /dev/null +++ b/drivers/video/msm/mdp4_dtv.c @@ -0,0 +1,326 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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_unprepare(hdmi_clk); + if (mdp_tv_clk) + clk_disable_unprepare(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_unprepare(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; + + if (panel_pixclock_freq > 58000000) + /* pm_qos_rate should be in Khz */ + pm_qos_rate = panel_pixclock_freq / 1000 ; + else + pm_qos_rate = 58000; + 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_prepare_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); + mfd->panel_info.clk_rate = mfd->fbi->var.pixclock; + clk_prepare_enable(hdmi_clk); + clk_reset(hdmi_clk, CLK_RESET_ASSERT); + udelay(20); + clk_reset(hdmi_clk, CLK_RESET_DEASSERT); + + if (mdp_tv_clk) + clk_prepare_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; +#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(&pdev->dev, "mem_clk"); + if (IS_ERR(ebi1_clk)) { + ebi1_clk = NULL; + pr_warning("%s: Couldn't get ebi1 clock\n", __func__); + } +#endif + tv_src_clk = clk_get(&pdev->dev, "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(&pdev->dev, "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(&pdev->dev, "mdp_clk"); + if (IS_ERR(mdp_tv_clk)) + mdp_tv_clk = NULL; + + 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; + if (hdmi_prim_display) + 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; + + /* + * 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) +{ + return dtv_register_driver(); +} + +module_init(dtv_driver_init); diff --git a/drivers/video/msm/mdp4_hsic.c b/drivers/video/msm/mdp4_hsic.c new file mode 100644 index 0000000000000000000000000000000000000000..5735f4599b736d7d7acbd456ec85e71a72737994 --- /dev/null +++ b/drivers/video/msm/mdp4_hsic.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 "mdp.h" +#include "mdp4.h" + +/* Definitions */ +#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 +#define MDP_VG1_BASE (MDP_BASE + MDP4_VIDEO_BASE) + +#define MDP_VG1_CSC_MVn(n) (MDP_VG1_BASE + MDP4_CSC_MV_OFF + 4 * (n)) +#define MDP_VG1_CSC_PRE_LVn(n) (MDP_VG1_BASE + MDP4_CSC_PRE_LV_OFF + 4 * (n)) +#define MDP_VG1_CSC_POST_LVn(n) (MDP_VG1_BASE + MDP4_CSC_POST_LV_OFF + 4 * (n)) +#define MDP_VG1_CSC_PRE_BVn(n) (MDP_VG1_BASE + MDP4_CSC_PRE_BV_OFF + 4 * (n)) +#define MDP_VG1_CSC_POST_BVn(n) (MDP_VG1_BASE + MDP4_CSC_POST_BV_OFF + 4 * (n)) + +#define Q16 (16) +#define Q16_ONE (1 << Q16) + +#define Q16_VALUE(x) ((int32_t)((uint32_t)x << Q16)) +#define Q16_PERCENT_VALUE(x, n) ((int32_t)( \ + div_s64(((int64_t)x * (int64_t)Q16_ONE), n))) + +#define Q16_WHOLE(x) ((int32_t)(x >> 16)) +#define Q16_FRAC(x) ((int32_t)(x & 0xFFFF)) +#define Q16_S1Q16_MUL(x, y) (((x >> 1) * (y >> 1)) >> 14) + +#define Q16_MUL(x, y) ((int32_t)((((int64_t)x) * ((int64_t)y)) >> Q16)) +#define Q16_NEGATE(x) (0 - (x)) + +/* + * HSIC Control min/max values + * These settings are based on the maximum/minimum allowed modifications to + * HSIC controls for layer and display color. Allowing too much variation in + * the CSC block will result in color clipping resulting in unwanted color + * shifts. + */ +#define TRIG_MAX Q16_VALUE(128) +#define CON_SAT_MAX Q16_VALUE(128) +#define INTENSITY_MAX (Q16_VALUE(2047) >> 12) + +#define HUE_MAX Q16_VALUE(100) +#define HUE_MIN Q16_VALUE(-100) +#define HUE_DEF Q16_VALUE(0) + +#define SAT_MAX Q16_VALUE(100) +#define SAT_MIN Q16_VALUE(-100) +#define SAT_DEF CON_SAT_MAX + +#define CON_MAX Q16_VALUE(100) +#define CON_MIN Q16_VALUE(-100) +#define CON_DEF CON_SAT_MAX + +#define INTEN_MAX Q16_VALUE(100) +#define INTEN_MIN Q16_VALUE(-100) +#define INTEN_DEF Q16_VALUE(0) + +enum { + DIRTY, + GENERATED, + CLEAN +}; + +/* local vars*/ +static int32_t csc_matrix_tab[3][3] = { + {0x00012a00, 0x00000000, 0x00019880}, + {0x00012a00, 0xffff9b80, 0xffff3000}, + {0x00012a00, 0x00020480, 0x00000000} +}; + +static int32_t csc_yuv2rgb_conv_tab[3][3] = { + {0x00010000, 0x00000000, 0x000123cb}, + {0x00010000, 0xffff9af9, 0xffff6b5e}, + {0x00010000, 0x00020838, 0x00000000} +}; + +static int32_t csc_rgb2yuv_conv_tab[3][3] = { + {0x00004c8b, 0x00009645, 0x00001d2f}, + {0xffffda56, 0xffffb60e, 0x00006f9d}, + {0x00009d70, 0xffff7c2a, 0xffffe666} +}; + +static uint32_t csc_pre_bv_tab[3] = {0xfffff800, 0xffffc000, 0xffffc000}; +static uint32_t csc_post_bv_tab[3] = {0x00000000, 0x00000000, 0x00000000}; + +static uint32_t csc_pre_lv_tab[6] = {0x00000000, 0x00007f80, 0x00000000, + 0x00007f80, 0x00000000, 0x00007f80}; +static uint32_t csc_post_lv_tab[6] = {0x00000000, 0x00007f80, 0x00000000, + 0x00007f80, 0x00000000, 0x00007f80}; + +/* Lookup table for Sin/Cos lookup - Q16*/ +static const int32_t trig_lut[65] = { + 0x00000000, /* sin((2*M_PI/256) * 0x00);*/ + 0x00000648, /* sin((2*M_PI/256) * 0x01);*/ + 0x00000C90, /* sin((2*M_PI/256) * 0x02);*/ + 0x000012D5, + 0x00001918, + 0x00001F56, + 0x00002590, + 0x00002BC4, + 0x000031F1, + 0x00003817, + 0x00003E34, + 0x00004447, + 0x00004A50, + 0x0000504D, + 0x0000563E, + 0x00005C22, + 0x000061F8, + 0x000067BE, + 0x00006D74, + 0x0000731A, + 0x000078AD, + 0x00007E2F, + 0x0000839C, + 0x000088F6, + 0x00008E3A, + 0x00009368, + 0x00009880, + 0x00009D80, + 0x0000A268, + 0x0000A736, + 0x0000ABEB, + 0x0000B086, + 0x0000B505, + 0x0000B968, + 0x0000BDAF, + 0x0000C1D8, + 0x0000C5E4, + 0x0000C9D1, + 0x0000CD9F, + 0x0000D14D, + 0x0000D4DB, + 0x0000D848, + 0x0000DB94, + 0x0000DEBE, + 0x0000E1C6, + 0x0000E4AA, + 0x0000E768, + 0x0000EA0A, + 0x0000EC83, + 0x0000EED9, + 0x0000F109, + 0x0000F314, + 0x0000F4FA, + 0x0000F6BA, + 0x0000F854, + 0x0000F9C8, + 0x0000FB15, + 0x0000FC3B, + 0x0000FD3B, + 0x0000FE13, + 0x0000FEC4, + 0x0000FF4E, + 0x0000FFB1, + 0x0000FFEC, + 0x00010000, /* sin((2*M_PI/256) * 0x40);*/ +}; + +void trig_values_q16(int32_t deg, int32_t *cos, int32_t *sin) +{ + int32_t angle; + int32_t quad, anglei, anglef; + int32_t v0 = 0, v1 = 0; + int32_t t1, t2; + + /* + * Scale the angle so that 256 is one complete revolution and mask it + * to this domain + * NOTE: 0xB60B == 256/360 + */ + angle = Q16_MUL(deg, 0xB60B) & 0x00FFFFFF; + + /* Obtain a quadrant number, integer, and fractional part */ + quad = angle >> 22; + anglei = (angle >> 16) & 0x3F; + anglef = angle & 0xFFFF; + + /* + * Using the integer part, obtain the lookup table entry and its + * complement. Using the quadrant, swap and negate these as + * necessary. + * (The values and all derivatives of sine and cosine functions + * can be derived from these values) + */ + switch (quad) { + case 0x0: + v0 += trig_lut[anglei]; + v1 += trig_lut[0x40-anglei]; + break; + + case 0x1: + v0 += trig_lut[0x40-anglei]; + v1 -= trig_lut[anglei]; + break; + + case 0x2: + v0 -= trig_lut[anglei]; + v1 -= trig_lut[0x40-anglei]; + break; + + case 0x3: + v0 -= trig_lut[0x40-anglei]; + v1 += trig_lut[anglei]; + break; + } + + /* + * Multiply the fractional part by 2*PI/256 to move it from lookup + * table units to radians, giving us the coefficient for first + * derivatives. + */ + t1 = Q16_S1Q16_MUL(anglef, 0x0648); + + /* + * Square this and divide by 2 to get the coefficient for second + * derivatives + */ + t2 = Q16_S1Q16_MUL(t1, t1) >> 1; + + *sin = v0 + Q16_S1Q16_MUL(v1, t1) - Q16_S1Q16_MUL(v0, t2); + + *cos = v1 - Q16_S1Q16_MUL(v0, t1) - Q16_S1Q16_MUL(v1, t2); +} + +/* Convert input Q16 value to s4.9 */ +int16_t convert_q16_s49(int32_t q16Value) +{ /* Top half is the whole number, Bottom half is fractional portion*/ + int16_t whole = Q16_WHOLE(q16Value); + int32_t fraction = Q16_FRAC(q16Value); + + /* Clamp whole to 3 bits */ + if (whole > 7) + whole = 7; + else if (whole < -7) + whole = -7; + + /* Reduce fraction to 9 bits. */ + fraction = (fraction<<9)>>Q16; + + return (int16_t) ((int16_t)whole<<9) | ((int16_t)fraction); +} + +/* Convert input Q16 value to uint16 */ +int16_t convert_q16_int16(int32_t val) +{ + int32_t rounded; + + if (val >= 0) { + /* Add 0.5 */ + rounded = val + (Q16_ONE>>1); + } else { + /* Subtract 0.5 */ + rounded = val - (Q16_ONE>>1); + } + + /* Truncate rounded value */ + return (int16_t)(rounded>>Q16); +} + +/* + * norm_q16 + * Return a Q16 value represeting a normalized value + * + * value -100% 0% +100% + * |-----------------|----------------| + * ^ ^ ^ + * q16MinValue q16DefaultValue q16MaxValue + * + */ +int32_t norm_q16(int32_t value, int32_t min, int32_t default_val, int32_t max, + int32_t range) +{ + int32_t diff, perc, mul, result; + + if (0 == value) { + result = default_val; + } else if (value > 0) { + /* value is between 0% and +100% represent 1.0 -> QRange Max */ + diff = range; + perc = Q16_PERCENT_VALUE(value, max); + mul = Q16_MUL(perc, diff); + result = default_val + mul; + } else { + /* if (value <= 0) */ + diff = -range; + perc = Q16_PERCENT_VALUE(-value, -min); + mul = Q16_MUL(perc, diff); + result = default_val + mul; + } + return result; +} + +void matrix_mul_3x3(int32_t dest[][3], int32_t a[][3], int32_t b[][3]) +{ + int32_t i, j, k; + int32_t tmp[3][3]; + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + tmp[i][j] = 0; + for (k = 0; k < 3; k++) + tmp[i][j] += Q16_MUL(a[i][k], b[k][j]); + } + } + + /* in case dest = a or b*/ + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) + dest[i][j] = tmp[i][j]; + } +} + +#define CONVERT(x) (x)/*convert_q16_s49((x))*/ +void pr_params(struct mdp4_hsic_regs *regs) +{ + int i; + if (regs) { + for (i = 0; i < NUM_HSIC_PARAM; i++) { + pr_info("\t: hsic->params[%d] = 0x%08x [raw = 0x%08x]\n", + i, CONVERT(regs->params[i]), regs->params[i]); + } + } +} + +void pr_3x3_matrix(int32_t in[][3]) +{ + pr_info("\t[0x%08x\t0x%08x\t0x%08x]\n", CONVERT(in[0][0]), + CONVERT(in[0][1]), CONVERT(in[0][2])); + pr_info("\t[0x%08x\t0x%08x\t0x%08x]\n", CONVERT(in[1][0]), + CONVERT(in[1][1]), CONVERT(in[1][2])); + pr_info("\t[0x%08x\t0x%08x\t0x%08x]\n", CONVERT(in[2][0]), + CONVERT(in[2][1]), CONVERT(in[2][2])); +} + +void _hsic_get(struct mdp4_hsic_regs *regs, int32_t type, int8_t *val) +{ + if (type < 0 || type >= NUM_HSIC_PARAM) + BUG_ON(-EINVAL); + *val = regs->params[type]; + pr_info("%s: getting params[%d] = %d\n", __func__, type, *val); +} + +void _hsic_set(struct mdp4_hsic_regs *regs, int32_t type, int8_t val) +{ + if (type < 0 || type >= NUM_HSIC_PARAM) + BUG_ON(-EINVAL); + + if (regs->params[type] != Q16_VALUE(val)) { + regs->params[type] = Q16_VALUE(val); + regs->dirty = DIRTY; + } +} + +void _hsic_generate_csc_matrix(struct mdp4_overlay_pipe *pipe) +{ + int i, j; + int32_t sin, cos; + + int32_t hue_matrix[3][3]; + int32_t con_sat_matrix[3][3]; + struct mdp4_hsic_regs *regs = &(pipe->hsic_regs); + + memset(con_sat_matrix, 0x0, sizeof(con_sat_matrix)); + memset(hue_matrix, 0x0, sizeof(hue_matrix)); + + /* + * HSIC control require matrix multiplication of these two tables + * [T 0 0][1 0 0] T = Contrast C=Cos(Hue) + * [0 S 0][0 C -N] S = Saturation N=Sin(Hue) + * [0 0 S][0 N C] + */ + + con_sat_matrix[0][0] = norm_q16(regs->params[HSIC_CON], CON_MIN, + CON_DEF, CON_MAX, CON_SAT_MAX); + con_sat_matrix[1][1] = norm_q16(regs->params[HSIC_SAT], SAT_MIN, + SAT_DEF, SAT_MAX, CON_SAT_MAX); + con_sat_matrix[2][2] = con_sat_matrix[1][1]; + + hue_matrix[0][0] = TRIG_MAX; + + trig_values_q16(norm_q16(regs->params[HSIC_HUE], HUE_MIN, HUE_DEF, + HUE_MAX, TRIG_MAX), &cos, &sin); + + cos = Q16_MUL(cos, TRIG_MAX); + sin = Q16_MUL(sin, TRIG_MAX); + + hue_matrix[1][1] = cos; + hue_matrix[2][2] = cos; + hue_matrix[2][1] = sin; + hue_matrix[1][2] = Q16_NEGATE(sin); + + /* Generate YUV CSC matrix */ + matrix_mul_3x3(regs->conv_matrix, con_sat_matrix, hue_matrix); + + if (!(pipe->op_mode & MDP4_OP_SRC_DATA_YCBCR)) { + /* Convert input RGB to YUV then apply CSC matrix */ + pr_info("Pipe %d, has RGB input\n", pipe->pipe_num); + matrix_mul_3x3(regs->conv_matrix, regs->conv_matrix, + csc_rgb2yuv_conv_tab); + } + + /* Normalize the matrix */ + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) + regs->conv_matrix[i][j] = (regs->conv_matrix[i][j]>>14); + } + + /* Multiply above result by current csc table */ + matrix_mul_3x3(regs->conv_matrix, regs->conv_matrix, csc_matrix_tab); + + if (!(pipe->op_mode & MDP4_OP_SRC_DATA_YCBCR)) { + /*HACK:only "works"for src side*/ + /* Convert back to RGB */ + pr_info("Pipe %d, has RGB output\n", pipe->pipe_num); + matrix_mul_3x3(regs->conv_matrix, csc_yuv2rgb_conv_tab, + regs->conv_matrix); + } + + /* Update clamps pre and post. */ + /* TODO: different tables for different color formats? */ + for (i = 0; i < 6; i++) { + regs->pre_limit[i] = csc_pre_lv_tab[i]; + regs->post_limit[i] = csc_post_lv_tab[i]; + } + + /* update bias values, pre and post */ + for (i = 0; i < 3; i++) { + regs->pre_bias[i] = csc_pre_bv_tab[i]; + regs->post_bias[i] = csc_post_bv_tab[i] + + norm_q16(regs->params[HSIC_INT], + INTEN_MIN, INTEN_DEF, INTEN_MAX, INTENSITY_MAX); + } + + regs->dirty = GENERATED; +} + +void _hsic_update_mdp(struct mdp4_overlay_pipe *pipe) +{ + struct mdp4_hsic_regs *regs = &(pipe->hsic_regs); + int i, j, k; + + uint32_t *csc_mv; + uint32_t *pre_lv; + uint32_t *post_lv; + uint32_t *pre_bv; + uint32_t *post_bv; + + switch (pipe->pipe_num) { + case OVERLAY_PIPE_VG2: + csc_mv = (uint32_t *) (MDP_VG1_CSC_MVn(0) + + MDP4_VIDEO_OFF); + pre_lv = (uint32_t *) (MDP_VG1_CSC_PRE_LVn(0) + + MDP4_VIDEO_OFF); + post_lv = (uint32_t *) (MDP_VG1_CSC_POST_LVn(0) + + MDP4_VIDEO_OFF); + pre_bv = (uint32_t *) (MDP_VG1_CSC_PRE_BVn(0) + + MDP4_VIDEO_OFF); + post_bv = (uint32_t *) (MDP_VG1_CSC_POST_BVn(0) + + MDP4_VIDEO_OFF); + break; + case OVERLAY_PIPE_VG1: + default: + csc_mv = (uint32_t *) MDP_VG1_CSC_MVn(0); + pre_lv = (uint32_t *) MDP_VG1_CSC_PRE_LVn(0); + post_lv = (uint32_t *) MDP_VG1_CSC_POST_LVn(0); + pre_bv = (uint32_t *) MDP_VG1_CSC_PRE_BVn(0); + post_bv = (uint32_t *) MDP_VG1_CSC_POST_BVn(0); + break; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + k = (3*i) + j; + MDP_OUTP(csc_mv + k, convert_q16_s49( + regs->conv_matrix[i][j])); + } + } + + for (i = 0; i < 6; i++) { + MDP_OUTP(pre_lv + i, convert_q16_s49(regs->pre_limit[i])); + MDP_OUTP(post_lv + i, convert_q16_s49(regs->post_limit[i])); + } + + for (i = 0; i < 3; i++) { + MDP_OUTP(pre_bv + i, convert_q16_s49(regs->pre_bias[i])); + MDP_OUTP(post_bv + i, convert_q16_s49(regs->post_bias[i])); + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + regs->dirty = CLEAN; +} + +void mdp4_hsic_get(struct mdp4_overlay_pipe *pipe, struct dpp_ctrl *ctrl) +{ + int i; + for (i = 0; i < NUM_HSIC_PARAM; i++) + _hsic_get(&(pipe->hsic_regs), i, &(ctrl->hsic_params[i])); +} + +void mdp4_hsic_set(struct mdp4_overlay_pipe *pipe, struct dpp_ctrl *ctrl) +{ + int i; + for (i = 0; i < NUM_HSIC_PARAM; i++) + _hsic_set(&(pipe->hsic_regs), i, ctrl->hsic_params[i]); + + if (pipe->hsic_regs.dirty == DIRTY) + _hsic_generate_csc_matrix(pipe); +} + +void mdp4_hsic_update(struct mdp4_overlay_pipe *pipe) +{ + if (pipe->hsic_regs.dirty == GENERATED) + _hsic_update_mdp(pipe); +} diff --git a/drivers/video/msm/mdp4_overlay.c b/drivers/video/msm/mdp4_overlay.c new file mode 100644 index 0000000000000000000000000000000000000000..6d4e44b398a8041491526833c9411577cf08c3df --- /dev/null +++ b/drivers/video/msm/mdp4_overlay.c @@ -0,0 +1,3187 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#define VERSION_KEY_MASK 0xFFFFFF00 + +struct mdp4_overlay_ctrl { + struct mdp4_overlay_pipe plist[OVERLAY_PIPE_MAX]; + struct mdp4_overlay_pipe *stage[MDP4_MIXER_MAX][MDP4_MIXER_STAGE_MAX]; + uint32 mixer_cfg[MDP4_MIXER_MAX]; + uint32 cs_controller; + uint32 panel_3d; + uint32 panel_mode; + uint32 mixer0_played; + uint32 mixer1_played; + uint32 mixer2_played; +} mdp4_overlay_db = { + .cs_controller = CS_CONTROLLER_0, + .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_VIDEO, + .pipe_num = OVERLAY_PIPE_VG1, + .pipe_ndx = 3, + }, + { + .pipe_type = OVERLAY_TYPE_VIDEO, + .pipe_num = OVERLAY_PIPE_VG2, + .pipe_ndx = 4, + }, + { + .pipe_type = OVERLAY_TYPE_BF, + .pipe_num = OVERLAY_PIPE_RGB3, + .pipe_ndx = 5, + .mixer_num = MDP4_MIXER0, + }, + { + .pipe_type = OVERLAY_TYPE_BF, + .pipe_num = OVERLAY_PIPE_VG3, + .pipe_ndx = 6, + .mixer_num = MDP4_MIXER1, + }, + { + .pipe_type = OVERLAY_TYPE_BF, + .pipe_num = OVERLAY_PIPE_VG4, + .pipe_ndx = 7, + .mixer_num = MDP4_MIXER2, + }, + }, +}; + +static struct mdp4_overlay_ctrl *ctrl = &mdp4_overlay_db; +static int new_perf_level; +static struct ion_client *display_iclient; +static struct mdp4_iommu_pipe_info mdp_iommu[MDP4_MIXER_MAX][OVERLAY_PIPE_MAX]; + +int mdp4_overlay_iommu_map_buf(int mem_id, + struct mdp4_overlay_pipe *pipe, unsigned int plane, + unsigned long *start, unsigned long *len, + struct ion_handle **srcp_ihdl) +{ + struct mdp4_iommu_pipe_info *iom_pipe_info; + + if (!display_iclient) + return -EINVAL; + + *srcp_ihdl = ion_import_fd(display_iclient, mem_id); + if (IS_ERR_OR_NULL(*srcp_ihdl)) { + pr_err("ion_import_fd() failed\n"); + return PTR_ERR(*srcp_ihdl); + } + pr_debug("%s(): ion_hdl %p, ion_buf %p\n", __func__, *srcp_ihdl, + ion_share(display_iclient, *srcp_ihdl)); + pr_debug("mixer %u, pipe %u, plane %u\n", pipe->mixer_num, + pipe->pipe_ndx, plane); + if (ion_map_iommu(display_iclient, *srcp_ihdl, + DISPLAY_DOMAIN, GEN_POOL, SZ_4K, 0, start, + len, 0, ION_IOMMU_UNMAP_DELAYED)) { + ion_free(display_iclient, *srcp_ihdl); + pr_err("ion_map_iommu() failed\n"); + return -EINVAL; + } + + iom_pipe_info = &mdp_iommu[pipe->mixer_num][pipe->pipe_ndx - 1]; + if (!iom_pipe_info->ihdl[plane]) { + iom_pipe_info->ihdl[plane] = *srcp_ihdl; + } else { + if (iom_pipe_info->prev_ihdl[plane]) { + ion_unmap_iommu(display_iclient, + iom_pipe_info->prev_ihdl[plane], + DISPLAY_DOMAIN, GEN_POOL); + ion_free(display_iclient, + iom_pipe_info->prev_ihdl[plane]); + pr_debug("Previous: mixer %u, pipe %u, plane %u, " + "prev_ihdl %p\n", pipe->mixer_num, + pipe->pipe_ndx, plane, + iom_pipe_info->prev_ihdl[plane]); + } + + iom_pipe_info->prev_ihdl[plane] = iom_pipe_info->ihdl[plane]; + iom_pipe_info->ihdl[plane] = *srcp_ihdl; + } + pr_debug("mem_id %d, start 0x%lx, len 0x%lx\n", + mem_id, *start, *len); + return 0; +} + +void mdp4_iommu_unmap(struct mdp4_overlay_pipe *pipe) +{ + struct mdp4_iommu_pipe_info *iom_pipe_info; + unsigned char i, j; + + if (!display_iclient) + return; + + for (j = 0; j < OVERLAY_PIPE_MAX; j++) { + iom_pipe_info = &mdp_iommu[pipe->mixer_num][j]; + for (i = 0; i < MDP4_MAX_PLANE; i++) { + if (iom_pipe_info->prev_ihdl[i]) { + pr_debug("%s(): mixer %u, pipe %u, plane %u, " + "prev_ihdl %p\n", __func__, + pipe->mixer_num, j + 1, i, + iom_pipe_info->prev_ihdl[i]); + ion_unmap_iommu(display_iclient, + iom_pipe_info->prev_ihdl[i], + DISPLAY_DOMAIN, GEN_POOL); + ion_free(display_iclient, + iom_pipe_info->prev_ihdl[i]); + iom_pipe_info->prev_ihdl[i] = NULL; + } + + if (iom_pipe_info->mark_unmap) { + if (iom_pipe_info->ihdl[i]) { + if (pipe->mixer_num == MDP4_MIXER1) + mdp4_overlay_dtv_wait4vsync(); + pr_debug("%s(): mixer %u, pipe %u, plane %u, " + "ihdl %p\n", __func__, + pipe->mixer_num, j + 1, i, + iom_pipe_info->ihdl[i]); + ion_unmap_iommu(display_iclient, + iom_pipe_info->ihdl[i], + DISPLAY_DOMAIN, GEN_POOL); + ion_free(display_iclient, + iom_pipe_info->ihdl[i]); + iom_pipe_info->ihdl[i] = NULL; + } + } + } + iom_pipe_info->mark_unmap = 0; + } +} + +void mdp4_overlay_ctrl_db_reset(void) +{ + int i; + + for (i = MDP4_MIXER0; i < MDP4_MIXER_MAX; i++) + ctrl->mixer_cfg[i] = 0; +} + +int mdp4_overlay_mixer_play(int mixer_num) +{ + if (mixer_num == MDP4_MIXER2) + return ctrl->mixer2_played; + else 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; +} + +void mdp4_overlay_panel_mode_unset(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_vid_quant_set(); + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +#ifdef CONFIG_FB_MSM_HDMI_3D +void unfill_black_screen(void) { return; } +#else +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); + return; +} +#endif + +#ifdef CONFIG_FB_MSM_HDMI_3D +void fill_black_screen(void) { return; } +#else +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); + return; +} +#endif + +void mdp4_overlay_dmae_xy(struct mdp4_overlay_pipe *pipe) +{ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + MDP_OUTP(MDP_BASE + 0xb0004, + (pipe->src_height << 16 | pipe->src_width)); + if (pipe->blt_addr) { + uint32 off, bpp; +#ifdef BLT_RGB565 + bpp = 2; /* overlay ouput is RGB565 */ +#else + bpp = 3; /* overlay ouput is RGB888 */ +#endif + off = 0; + if (pipe->ov_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + MDP_OUTP(MDP_BASE + 0xb0008, pipe->blt_addr + off); + /* RGB888, output of overlay blending */ + MDP_OUTP(MDP_BASE + 0xb000c, pipe->src_width * bpp); + } else { + /* dma_e source */ + MDP_OUTP(MDP_BASE + 0xb0008, pipe->srcp0_addr); + MDP_OUTP(MDP_BASE + 0xb000c, pipe->srcp0_ystride); + } + /* dma_e 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; + uint32 mask, curr; + + 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 */ + curr = inpdw(MDP_BASE + 0x90000); + mask = 0x0FFFFFFF; + dma2_cfg_reg = (dma2_cfg_reg & mask) | (curr & ~mask); + 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, s; + int n; + + n = mdp4_leading_0(src); + if (n > f_num) + n = f_num; + s = src << n; /* maximum to reduce lose of resolution */ + val = s / dst; + if (n < f_num) { + n = f_num - n; + val <<= n; + val |= ((s % dst) << n) / dst; + } + + return val; +} + +static void mdp4_scale_setup(struct mdp4_overlay_pipe *pipe) +{ + pipe->phasex_step = MDP4_VG_PHASE_STEP_DEFAULT; + pipe->phasey_step = MDP4_VG_PHASE_STEP_DEFAULT; + + if (pipe->dst_h && pipe->src_h != pipe->dst_h) { + u32 upscale_max; + upscale_max = (mdp_rev >= MDP_REV_41) ? + MDP4_REV41_OR_LATER_UP_SCALING_MAX : + MDP4_REV40_UP_SCALING_MAX; + if (pipe->dst_h > pipe->src_h * upscale_max) + return; + + pipe->op_mode |= MDP4_OP_SCALEY_EN; + + if (pipe->pipe_type == OVERLAY_TYPE_VIDEO) { + if (pipe->flags & MDP_BACKEND_COMPOSITION && + pipe->alpha_enable && pipe->dst_h > pipe->src_h) + pipe->op_mode |= MDP4_OP_SCALEY_PIXEL_RPT; + else if (pipe->dst_h <= (pipe->src_h / 4)) + pipe->op_mode |= MDP4_OP_SCALEY_MN_PHASE; + else + pipe->op_mode |= MDP4_OP_SCALEY_FIR; + } else { /* RGB pipe */ + pipe->op_mode |= MDP4_OP_SCALE_RGB_ENHANCED | + MDP4_OP_SCALE_RGB_BILINEAR | + MDP4_OP_SCALE_ALPHA_BILINEAR; + } + + pipe->phasey_step = mdp4_scale_phase_step(29, + pipe->src_h, pipe->dst_h); + } + + if (pipe->dst_w && pipe->src_w != pipe->dst_w) { + u32 upscale_max; + upscale_max = (mdp_rev >= MDP_REV_41) ? + MDP4_REV41_OR_LATER_UP_SCALING_MAX : + MDP4_REV40_UP_SCALING_MAX; + + if (pipe->dst_w > pipe->src_w * upscale_max) + return; + pipe->op_mode |= MDP4_OP_SCALEX_EN; + if (pipe->pipe_type == OVERLAY_TYPE_VIDEO) { + if (pipe->flags & MDP_BACKEND_COMPOSITION && + pipe->alpha_enable && pipe->dst_w > pipe->src_w) + pipe->op_mode |= MDP4_OP_SCALEX_PIXEL_RPT; + else if (pipe->dst_w <= (pipe->src_w / 4)) + pipe->op_mode |= MDP4_OP_SCALEX_MN_PHASE; + else + pipe->op_mode |= MDP4_OP_SCALEX_FIR; + } else { /* RGB pipe */ + pipe->op_mode |= MDP4_OP_SCALE_RGB_ENHANCED | + MDP4_OP_SCALE_RGB_BILINEAR | + MDP4_OP_SCALE_ALPHA_BILINEAR; + } + + 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; + uint32 curr, mask; + uint32 offset = 0; + int pnum; + + pnum = pipe->pipe_num - OVERLAY_PIPE_RGB1; /* start from 0 */ + rgb_base = MDP_BASE + MDP4_RGB_BASE; + rgb_base += (MDP4_RGB_OFF * pnum); + + 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); + + if ((pipe->src_x + pipe->src_w) > 0x7FF) { + offset += pipe->src_x * pipe->bpp; + src_xy &= 0xFFFF0000; + } + + if ((pipe->src_y + pipe->src_h) > 0x7FF) { + offset += pipe->src_y * pipe->src_width * pipe->bpp; + src_xy &= 0x0000FFFF; + } + + 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); + + /* Ensure proper covert matrix loaded when color space swaps */ + curr = inpdw(rgb_base + 0x0058); + /* Don't touch bits you don't want to configure*/ + mask = 0xFFFEFFFF; + pipe->op_mode = (pipe->op_mode & mask) | (curr & ~mask); + + 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 + offset); + 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 */ + if (format & MDP4_FORMAT_SOLID_FILL) { + u32 op_mode = pipe->op_mode; + op_mode &= ~(MDP4_OP_FLIP_LR + MDP4_OP_SCALEX_EN); + op_mode &= ~(MDP4_OP_FLIP_UD + MDP4_OP_SCALEY_EN); + outpdw(rgb_base + 0x0058, op_mode);/* MDP_RGB_OP_MODE */ + } else + 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]++; +} + + +static void mdp4_overlay_vg_get_src_offset(struct mdp4_overlay_pipe *pipe, + char *vg_base, uint32 *luma_off, uint32 *chroma_off) +{ + uint32 src_xy; + *luma_off = 0; + *chroma_off = 0; + + if (pipe->src_x && (pipe->frame_format == + MDP4_FRAME_FORMAT_LINEAR)) { + src_xy = (pipe->src_y << 16) | pipe->src_x; + src_xy &= 0xffff0000; + outpdw(vg_base + 0x0004, src_xy); /* MDP_RGB_SRC_XY */ + + switch (pipe->src_format) { + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CR_CB_GH2V2: + case MDP_Y_CB_CR_H2V2: + *luma_off = pipe->src_x; + *chroma_off = pipe->src_x/2; + break; + + case MDP_Y_CBCR_H2V2_TILE: + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CRCB_H1V1: + case MDP_Y_CBCR_H1V1: + case MDP_Y_CRCB_H2V1: + case MDP_Y_CBCR_H2V1: + *luma_off = pipe->src_x; + *chroma_off = pipe->src_x; + break; + + case MDP_YCRYCB_H2V1: + if (pipe->src_x & 0x1) + pipe->src_x += 1; + *luma_off += pipe->src_x * 2; + break; + + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_BGRA_8888: + case MDP_RGBX_8888: + case MDP_RGB_565: + case MDP_BGR_565: + case MDP_XRGB_8888: + case MDP_RGB_888: + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + *luma_off = pipe->src_x * pipe->bpp; + break; + + default: + pr_err("%s: fmt %u not supported for adjustment\n", + __func__, pipe->src_format); + break; + } + } +} + +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, luma_offset, chroma_offset; + uint32 mask, curr, addr; + int pnum, ptype; + + 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); + + ptype = mdp4_overlay_format2type(pipe->src_format); + format = mdp4_overlay_format(pipe); + pattern = mdp4_overlay_unpack_pattern(pipe); + + /* not RGB use VG pipe, pure VG pipe */ + pipe->op_mode |= MDP4_OP_CSC_EN; + if (ptype != OVERLAY_TYPE_RGB) + pipe->op_mode |= MDP4_OP_SRC_DATA_YCBCR; + +#ifdef MDP4_IGC_LUT_ENABLE + pipe->op_mode |= MDP4_OP_IGC_LUT_EN; +#endif + + mdp4_scale_setup(pipe); + + luma_offset = 0; + chroma_offset = 0; + + if (ptype == OVERLAY_TYPE_RGB) { + if ((pipe->src_y + pipe->src_h) > 0x7FF) { + luma_offset = pipe->src_y * pipe->src_width * pipe->bpp; + src_xy &= 0x0000FFFF; + } + + if ((pipe->src_x + pipe->src_w) > 0x7FF) { + luma_offset += pipe->src_x * pipe->bpp; + src_xy &= 0xFFFF0000; + } + } + + 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 */ + + if (pipe->frame_format != MDP4_FRAME_FORMAT_LINEAR) + outpdw(vg_base + 0x0048, frame_size); /* TILE frame size */ + + /* + * Adjust src X offset to avoid MDP from overfetching pixels + * present before the offset. This is required for video + * frames coming with unused green pixels along the left margin + */ + /* not RGB use VG pipe, pure VG pipe */ + if (ptype != OVERLAY_TYPE_RGB) { + mdp4_overlay_vg_get_src_offset(pipe, vg_base, &luma_offset, + &chroma_offset); + } + + /* Ensure proper covert matrix loaded when color space swaps */ + curr = inpdw(vg_base + 0x0058); + mask = 0x600; + + if ((curr & mask) != (pipe->op_mode & mask)) { + addr = ((uint32_t)vg_base) + 0x4000; + if (ptype != OVERLAY_TYPE_RGB) + mdp4_csc_write(&(mdp_csc_convert[1]), addr); + else + mdp4_csc_write(&(mdp_csc_convert[0]), addr); + + mask = 0xFFFCFFFF; + } else { + /* Don't touch bits you don't want to configure*/ + mask = 0xFFFCF1FF; + } + pipe->op_mode = (pipe->op_mode & mask) | (curr & ~mask); + + /* luma component plane */ + outpdw(vg_base + 0x0010, pipe->srcp0_addr + luma_offset); + + /* chroma component plane or planar color 1 */ + outpdw(vg_base + 0x0014, pipe->srcp1_addr + chroma_offset); + + /* planar color 2 */ + outpdw(vg_base + 0x0018, pipe->srcp2_addr + chroma_offset); + + 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 */ + if (format & MDP4_FORMAT_SOLID_FILL) { + u32 op_mode = pipe->op_mode; + op_mode &= ~(MDP4_OP_FLIP_LR + MDP4_OP_SCALEX_EN); + op_mode &= ~(MDP4_OP_FLIP_UD + MDP4_OP_SCALEY_EN); + outpdw(vg_base + 0x0058, op_mode);/* MDP_RGB_OP_MODE */ + } else + 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)); + } + + if (mdp_rev > MDP_REV_41) { + /* mdp chip select controller */ + mask = 0; + if (pipe->pipe_num == OVERLAY_PIPE_VG1) + mask = 0x020; /* bit 5 */ + else if (pipe->pipe_num == OVERLAY_PIPE_VG2) + mask = 0x02000; /* bit 13 */ + if (mask) { + if (pipe->op_mode & MDP4_OP_SCALEY_MN_PHASE) + ctrl->cs_controller &= ~mask; + else + ctrl->cs_controller |= mask; + /* NOT double buffered */ + outpdw(MDP_BASE + 0x00c0, ctrl->cs_controller); + } + } + + + 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_CR_CB_GH2V2: + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CRCB_H1V1: + case MDP_Y_CBCR_H1V1: + case MDP_YCRCB_H1V1: + case MDP_YCBCR_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 = C1_B_Cb; /* B */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C2_R_Cr; /* R */ + 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 = C1_B_Cb; /* B */ + pipe->element2 = C0_G_Y; /* G */ + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C3_ALPHA; /* alpha */ + 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 = C1_B_Cb; /* B */ + pipe->element2 = C0_G_Y; /* G */ + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C3_ALPHA; /* alpha */ + 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 */ + if (pipe->src_format == MDP_Y_CRCB_H2V1) { + pipe->element1 = C1_B_Cb; + pipe->element0 = C2_R_Cr; + pipe->chroma_sample = MDP4_CHROMA_H2V1; + } else if (pipe->src_format == MDP_Y_CRCB_H1V1) { + pipe->element1 = C1_B_Cb; + pipe->element0 = C2_R_Cr; + 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 = C2_R_Cr; + pipe->element0 = C1_B_Cb; + pipe->chroma_sample = MDP4_CHROMA_H2V1; + } else if (pipe->src_format == MDP_Y_CBCR_H1V1) { + pipe->element1 = C2_R_Cr; + pipe->element0 = C1_B_Cb; + 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 = C1_B_Cb; + pipe->element0 = C2_R_Cr; + pipe->chroma_sample = MDP4_CHROMA_420; + } else if (pipe->src_format == MDP_Y_CBCR_H2V2) { + pipe->element1 = C2_R_Cr; + pipe->element0 = C1_B_Cb; + 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 */ + if (pipe->src_format == MDP_Y_CRCB_H2V2_TILE) { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->chroma_sample = MDP4_CHROMA_420; + } else if (pipe->src_format == MDP_Y_CBCR_H2V2_TILE) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->chroma_sample = MDP4_CHROMA_420; + } + pipe->bpp = 2; /* 2 bpp */ + break; + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CR_CB_GH2V2: + 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->chroma_sample = MDP4_CHROMA_420; + pipe->bpp = 2; /* 2 bpp */ + break; + case MDP_YCBCR_H1V1: + case MDP_YCRCB_H1V1: + 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->element0 = C0_G_Y; /* G */ + if (pipe->src_format == MDP_YCRCB_H1V1) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element2 = C1_B_Cb; /* B */ + } else { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element2 = C2_R_Cr; /* R */ + } + pipe->bpp = 3; /* 3 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: + case MDP_YCBCR_H1V1: + 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_CR_CB_GH2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CRCB_H2V1: + case MDP_Y_CRCB_H1V1: + case MDP_Y_CBCR_H1V1: + case MDP_YCRCB_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; + uint32 curr; + + intf = 0; + if (pipe->mixer_num == MDP4_MIXER2) + overlay_base = MDP_BASE + MDP4_OVERLAYPROC2_BASE; + else 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 support both primary and external external + */ + if (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 (pipe->mixer_num == MDP4_MIXER0 || + pipe->mixer_num == MDP4_MIXER1) { + 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); + + curr = inpdw(overlay_base + 0x0014); + curr &= 0x4; +#ifdef BLT_RGB565 + outpdw(overlay_base + 0x0014, curr | 0x1); /* RGB565 */ +#else + outpdw(overlay_base + 0x0014, curr | 0x0); /* RGB888 */ +#endif + } else if (pipe->mixer_num == MDP4_MIXER2) { + if (ctrl->panel_mode & MDP4_PANEL_WRITEBACK) { + off = 0; + bpp = 1; + 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 << 16) | + pipe->src_width)); + outpdw(overlay_base + 0x001c, + pipe->blt_addr + off); + off = pipe->src_height * pipe->src_width; + /* align chroma to 2k address */ + off = (off + 2047) & ~2047; + /* UV plane adress */ + outpdw(overlay_base + 0x0020, + pipe->blt_addr + off); + /* MDDI - BLT + on demand */ + outpdw(overlay_base + 0x0004, 0x08); + /* pseudo planar + writeback */ + curr = inpdw(overlay_base + 0x0014); + curr &= 0x4; + outpdw(overlay_base + 0x0014, curr | 0x012); + /* rgb->yuv */ + outpdw(overlay_base + 0x0200, 0x05); + } + } + } 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) { + curr = inpdw(overlay_base + 0x0014); + curr &= 0x4; + outpdw(overlay_base + 0x0014, 0x02); /* yuv422 */ + /* overlay1 CSC config */ + outpdw(overlay_base + 0x0200, 0x05); /* rgb->yuv */ + } + } + +#ifdef MDP4_IGC_LUT_ENABLE + curr = inpdw(overlay_base + 0x0014); + curr &= ~0x4; + outpdw(overlay_base + 0x0014, curr | 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, off; + int p1, p2; + + if (mixer == MDP4_MIXER2) + off = 0x100F0; + else + off = 0x10100; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + data = inpdw(MDP_BASE + off); + 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; +} + +int mdp4_mixer_info(int mixer_num, struct mdp_mixer_info *info) +{ + + int ndx, cnt; + struct mdp4_overlay_pipe *pipe; + + if (mixer_num > MDP4_MIXER_MAX) + return -ENODEV; + + cnt = 0; + ndx = 1; /* ndx 0 if not used */ + + for ( ; ndx < MDP4_MIXER_STAGE_MAX; ndx++) { + pipe = ctrl->stage[mixer_num][ndx]; + if (pipe == NULL) + continue; + info->z_order = pipe->mixer_stage - MDP4_MIXER_STAGE0; + info->ptype = pipe->pipe_type; + info->pnum = pipe->pipe_num; + info->pndx = pipe->pipe_ndx; + info->mixer_num = pipe->mixer_num; + info++; + cnt++; + } + return cnt; +} + +static void mdp4_overlay_bg_solidfill_clear(uint32 mixer_num) +{ + struct mdp4_overlay_pipe *bg_pipe; + unsigned char *rgb_base; + uint32 rgb_src_format; + int pnum; + + bg_pipe = mdp4_overlay_stage_pipe(mixer_num, + MDP4_MIXER_STAGE_BASE); + if (bg_pipe && bg_pipe->pipe_type == OVERLAY_TYPE_BF) { + bg_pipe = mdp4_overlay_stage_pipe(mixer_num, + MDP4_MIXER_STAGE0); + } + + if (bg_pipe && bg_pipe->pipe_type == OVERLAY_TYPE_RGB) { + rgb_src_format = mdp4_overlay_format(bg_pipe); + if (!(rgb_src_format & MDP4_FORMAT_SOLID_FILL)) { + pnum = bg_pipe->pipe_num - OVERLAY_PIPE_RGB1; + rgb_base = MDP_BASE + MDP4_RGB_BASE; + rgb_base += MDP4_RGB_OFF * pnum; + outpdw(rgb_base + 0x50, rgb_src_format); + outpdw(rgb_base + 0x0058, bg_pipe->op_mode); + mdp4_overlay_reg_flush(bg_pipe, 0); + } + } +} + +void mdp4_mixer_pipe_cleanup(int mixer) +{ + struct mdp4_overlay_pipe *pipe; + int j; + + for (j = MDP4_MIXER_STAGE_MAX - 1; j > MDP4_MIXER_STAGE_BASE; j--) { + pipe = ctrl->stage[mixer][j]; + if (pipe == NULL) + continue; + pr_debug("%s(): pipe %u\n", __func__, pipe->pipe_ndx); + mdp4_mixer_stage_down(pipe); + mdp4_overlay_pipe_free(pipe); + } +} + +static void mdp4_mixer_stage_commit(int mixer) +{ + struct mdp4_overlay_pipe *pipe; + int i, j, off; + u32 data = 0, stage, flush_bits = 0, pipe_cnt = 0, pull_mode = 0; + u32 cfg[MDP4_MIXER_MAX]; + + if (mixer == MDP4_MIXER0) + flush_bits |= 0x1; + else if (mixer == MDP4_MIXER1) + flush_bits |= 0x2; + + for (i = MDP4_MIXER0; i < MDP4_MIXER_MAX; i++) { + cfg[i] = 0; + for (j = MDP4_MIXER_STAGE_BASE; j < MDP4_MIXER_STAGE_MAX; j++) { + pipe = ctrl->stage[i][j]; + if (pipe == NULL) + break; + stage = pipe->mixer_stage; + if (i >= MDP4_MIXER1) + stage += 8; + stage <<= (4 * pipe->pipe_num); + cfg[i] |= stage; + pipe_cnt++; + + mdp4_mixer_blend_setup(pipe); + } + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (ctrl->mixer_cfg[mixer] != cfg[mixer]) { + if ((ctrl->mixer_cfg[MDP4_MIXER0] != cfg[MDP4_MIXER0]) || + (ctrl->mixer_cfg[MDP4_MIXER1] != cfg[MDP4_MIXER1])) { + off = 0x10100; + if (ctrl->mixer_cfg[MDP4_MIXER0] != cfg[MDP4_MIXER0]) { + flush_bits |= 0x1; + ctrl->mixer_cfg[MDP4_MIXER0] = cfg[MDP4_MIXER0]; + + if ((ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) || + (ctrl->panel_mode & MDP4_PANEL_LCDC)) + pull_mode = 1; + } + if (ctrl->mixer_cfg[MDP4_MIXER1] != cfg[MDP4_MIXER1]) { + flush_bits |= 0x2; + ctrl->mixer_cfg[MDP4_MIXER1] = cfg[MDP4_MIXER1]; + + pull_mode = 1; + } + + data = cfg[MDP4_MIXER0] | cfg[MDP4_MIXER1]; + + pr_debug("%s: mixer=%d data=%x flush=%x\n", __func__, + mixer, data, flush_bits); + + outpdw(MDP_BASE + off, data); /* LAYERMIXER_IN_CFG */ + if (pull_mode) { + outpdw(MDP_BASE + 0x18000, flush_bits); + /* wait for vsync on both pull mode interfaces */ + msleep(20); + } + } + + if (ctrl->mixer_cfg[MDP4_MIXER2] != cfg[MDP4_MIXER2]) { + off = 0x100F0; + ctrl->mixer_cfg[MDP4_MIXER2] = cfg[MDP4_MIXER2]; + data = cfg[MDP4_MIXER2]; + + pr_debug("%s: mixer=%d data=%x\n", __func__, + mixer, data); + + outpdw(MDP_BASE + off, data); /* LAYERMIXER_IN_CFG */ + } + } else { + if (mixer == MDP4_MIXER0) { + if ((ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) || + (ctrl->panel_mode & MDP4_PANEL_LCDC)) + pull_mode = 1; + } else if (mixer == MDP4_MIXER1) { + pull_mode = 1; + } + + if (pull_mode) + outpdw(MDP_BASE + 0x18000, flush_bits); + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + if (data && pipe_cnt == 1) + mdp4_update_perf_level(OVERLAY_PERF_LEVEL4); +} + +void mdp4_mixer_stage_up(struct mdp4_overlay_pipe *pipe) +{ + struct mdp4_overlay_pipe *spipe; + int mixer; + + mixer = pipe->mixer_num; + + spipe = ctrl->stage[mixer][pipe->mixer_stage]; + if ((spipe != NULL) && (spipe->pipe_num != pipe->pipe_num)) { + mdp4_stat.err_stage++; + return; + } + + ctrl->stage[mixer][pipe->mixer_stage] = pipe; /* keep it */ + + if (!(pipe->flags & MDP_OV_PLAY_NOWAIT)) + mdp4_mixer_stage_commit(mixer); +} + +void mdp4_mixer_stage_down(struct mdp4_overlay_pipe *pipe) +{ + struct mdp4_overlay_pipe *spipe; + + spipe = ctrl->stage[pipe->mixer_num][pipe->mixer_stage]; + if (spipe == NULL) /* not running */ + return; + + ctrl->stage[pipe->mixer_num][pipe->mixer_stage] = NULL; /* clear it */ + + mdp4_mixer_stage_commit(pipe->mixer_num); +} + +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; + uint32 fg_color3_out, fg_alpha = 0, bg_alpha = 0; + int off, pnum; + + if (pipe->mixer_stage == MDP4_MIXER_STAGE_BASE) + return; + + /* mixer numer, /dev/fb0, /dev/fb1, /dev/fb2 */ + if (pipe->mixer_num == MDP4_MIXER2) + overlay_base = MDP_BASE + MDP4_OVERLAYPROC2_BASE;/* 0x88000 */ + else if (pipe->mixer_num == MDP4_MIXER1) + 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; + } + + if (bg_pipe->pipe_type == OVERLAY_TYPE_BF && + pipe->mixer_stage > MDP4_MIXER_STAGE0) { + bg_pipe = mdp4_overlay_stage_pipe(pipe->mixer_num, + MDP4_MIXER_STAGE0); + } + + if (pipe->alpha_enable) { + /* alpha channel is lost on VG pipe when downscaling */ + if (pipe->pipe_type == OVERLAY_TYPE_VIDEO && + (pipe->dst_w < pipe->src_w || pipe->dst_h < pipe->src_h)) + fg_alpha = 0; + else + fg_alpha = 1; + } + + if (!fg_alpha && bg_pipe && bg_pipe->alpha_enable) { + struct mdp4_overlay_pipe *tmp; + int stage; + + bg_alpha = 1; + /* check all bg layers are opaque to propagate bg alpha */ + stage = bg_pipe->mixer_stage + 1; + for (; stage < pipe->mixer_stage; stage++) { + tmp = mdp4_overlay_stage_pipe(pipe->mixer_num, stage); + if (!tmp || tmp->alpha_enable || tmp->is_fg) { + bg_alpha = 0; + break; + } + } + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + 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); + fg_color3_out = 1; /* keep fg alpha by default */ + + if (pipe->is_fg) { + if (pipe->alpha == 0xff && + bg_pipe->pipe_type == OVERLAY_TYPE_RGB) { + u32 op_mode; + pnum = bg_pipe->pipe_num - OVERLAY_PIPE_RGB1; + rgb_base = MDP_BASE + MDP4_RGB_BASE; + rgb_base += MDP4_RGB_OFF * pnum; + rgb_src_format = inpdw(rgb_base + 0x50); + rgb_src_format |= MDP4_FORMAT_SOLID_FILL; + /* + * If solid fill is enabled, flip and scale + * have to be disabled. otherwise, h/w + * underruns. Also flush the pipe inorder + * to take solid fill into effect. + */ + op_mode = inpdw(rgb_base + 0x0058); + op_mode &= ~(MDP4_OP_FLIP_LR + MDP4_OP_SCALEX_EN); + op_mode &= ~(MDP4_OP_FLIP_UD + MDP4_OP_SCALEY_EN); + outpdw(rgb_base + 0x0058, op_mode); + outpdw(rgb_base + 0x50, rgb_src_format); + outpdw(rgb_base + 0x1008, constant_color); + mdp4_overlay_reg_flush(bg_pipe, 0); + } + } else if (fg_alpha) { + blend_op = (MDP4_BLEND_BG_ALPHA_FG_PIXEL | + MDP4_BLEND_BG_INV_ALPHA); + fg_color3_out = 1; /* keep fg alpha */ + } else if (bg_alpha) { + blend_op = (MDP4_BLEND_BG_ALPHA_BG_PIXEL | + MDP4_BLEND_FG_ALPHA_BG_PIXEL | + MDP4_BLEND_FG_INV_ALPHA); + fg_color3_out = 0; /* keep bg 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 if (bg_pipe) { + 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); + outpdw(overlay_base + (off << 5) + 0x1004, fg_color3_out); + + 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 (all) { + if (pipe->mixer_num == MDP4_MIXER1) + bits |= 0x02; + else + bits |= 0x01; + } + + if (pipe->pipe_num <= OVERLAY_PIPE_RGB2) + bits |= 1 << (2 + pipe->pipe_num); + if (pipe->is_fg && pipe->alpha == 0xFF) { + bg_pipe = mdp4_overlay_stage_pipe(pipe->mixer_num, + MDP4_MIXER_STAGE_BASE); + bits |= 1 << (2 + bg_pipe->pipe_num); + } + + 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 > OVERLAY_PIPE_MAX) + 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 i; + struct mdp4_overlay_pipe *pipe; + + for (i = 0; i < OVERLAY_PIPE_MAX; i++) { + pipe = &ctrl->plist[i]; + if ((pipe->pipe_used == 0) && ((pipe->pipe_type == ptype) || + (ptype == OVERLAY_TYPE_RGB && + pipe->pipe_type == OVERLAY_TYPE_VIDEO))) { + if (ptype == OVERLAY_TYPE_BF && + mixer != pipe->mixer_num) + continue; + init_completion(&pipe->comp); + init_completion(&pipe->dmas_comp); + pr_debug("%s: pipe=%x ndx=%d num=%d\n", __func__, + (int)pipe, pipe->pipe_ndx, pipe->pipe_num); + return pipe; + } + } + + pr_err("%s: ptype=%d FAILED\n", __func__, ptype); + + return NULL; +} + + +void mdp4_overlay_pipe_free(struct mdp4_overlay_pipe *pipe) +{ + uint32 ptype, num, ndx, mixer; + struct mdp4_iommu_pipe_info *iom_pipe_info; + + pr_debug("%s: pipe=%x ndx=%d\n", __func__, (int)pipe, pipe->pipe_ndx); + + ptype = pipe->pipe_type; + num = pipe->pipe_num; + ndx = pipe->pipe_ndx; + mixer = pipe->mixer_num; + iom_pipe_info = &mdp_iommu[pipe->mixer_num][pipe->pipe_ndx - 1]; + iom_pipe_info->mark_unmap = 1; + + memset(pipe, 0, sizeof(*pipe)); + + pipe->pipe_type = ptype; + pipe->pipe_num = num; + pipe->pipe_ndx = ndx; + pipe->mixer_num = mixer; +} + +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 || !req->dst_rect.w || !req->dst_rect.h) { + pr_debug("mdp_perf_level2clk_rate returned 0," + "or dst_rect height/width is 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) { + mdp4_stat.err_underflow++; + 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_iommu_pipe_info *iom_pipe_info; + int ret, ptype; + + u32 upscale_max; + upscale_max = (mdp_rev >= MDP_REV_41) ? + MDP4_REV41_OR_LATER_UP_SCALING_MAX : + MDP4_REV40_UP_SCALING_MAX; + + if (mfd == NULL) { + pr_err("%s: mfd == NULL, -ENODEV\n", __func__); + return -ENODEV; + } + + if (mixer >= MDP4_MIXER_MAX) { + 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 * upscale_max)) { + 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 * upscale_max)) { + 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; + } + + if (req->flags & MDP_OV_PIPE_SHARE) + ptype = OVERLAY_TYPE_VIDEO; /* VG pipe supports both RGB+YUV */ + + if (req->id == MSMFB_NEW_REQUEST) /* new request */ + pipe = mdp4_overlay_pipe_alloc(ptype, mixer); + else + pipe = mdp4_overlay_ndx2pipe(req->id); + + if (pipe == NULL) { + pr_err("%s: pipe == NULL!\n", __func__); + return -ENOMEM; + } + + if (!display_iclient && !IS_ERR_OR_NULL(mfd->iclient)) { + display_iclient = mfd->iclient; + pr_debug("%s(): display_iclient %p\n", __func__, + display_iclient); + } + + iom_pipe_info = &mdp_iommu[pipe->mixer_num][pipe->pipe_ndx - 1]; + iom_pipe_info->mark_unmap = 0; + + 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 */ + 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 & 0x1fff; /* source img width */ + pipe->src_height = req->src.height & 0x1fff; /* 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, + struct mdp4_overlay_pipe *pipe, unsigned int plane, + unsigned long *start, unsigned long *len, struct file **srcp_file, + int *p_need, struct ion_handle **srcp_ihdl) +{ + struct file *file; + int put_needed, ret = 0, fb_num; +#ifdef CONFIG_ANDROID_PMEM + unsigned long vstart; +#endif + *p_need = 0; + + if (img->flags & MDP_BLIT_SRC_GEM) { + *srcp_file = NULL; + return kgsl_gem_obj_addr(img->memory_id, (int) img->priv, + start, len); + } + + if (img->flags & MDP_MEMORY_ID_TYPE_FB) { + file = fget_light(img->memory_id, &put_needed); + if (file == NULL) + return -EINVAL; + + 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, + DISPLAY_SUBSYSTEM_ID)) { + ret = -1; + } else { + *srcp_file = file; + *p_need = put_needed; + } + } else + ret = -1; + if (ret) + fput_light(file, put_needed); + return ret; + } + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + return mdp4_overlay_iommu_map_buf(img->memory_id, pipe, plane, + start, len, srcp_ihdl); +#endif +#ifdef CONFIG_ANDROID_PMEM + if (!get_pmem_file(img->memory_id, start, &vstart, + len, srcp_file)) + return 0; + else + return -EINVAL; +#endif +} + +#ifdef CONFIG_FB_MSM_MIPI_DSI +int mdp4_overlay_3d_sbys(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; + + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + mdp4_dsi_cmd_3d_sbys(mfd, req); + ret = 0; + } else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) { + mdp4_dsi_video_3d_sbys(mfd, req); + ret = 0; + } + mutex_unlock(&mfd->dma->ov_mutex); + + return ret; +} +#else +int mdp4_overlay_3d_sbys(struct fb_info *info, struct msmfb_overlay_3d *req) +{ + /* do nothing */ + return -EPERM; +} +#endif + +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; +} + +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; + + if (mdp4_overlay_borderfill_supported()) + req->flags |= MDP_BORDERFILL_SUPPORTED; + + 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_BUS_SCALE_TABLE_BASE 6 + +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, + struct msm_fb_data_type *mfd) +{ + int is_fg = 0, i, cnt; + + if (req->is_fg && ((req->alpha & 0x0ff) == 0xff)) + is_fg = 1; + + if (mdp4_extn_disp) + return OVERLAY_PERF_LEVEL1; + + if (req->flags & (MDP_DEINTERLACE | MDP_BACKEND_COMPOSITION)) + return OVERLAY_PERF_LEVEL1; + + for (i = 0, cnt = 0; i < OVERLAY_PIPE_MAX; i++) { + if (ctrl->plist[i].pipe_used && ++cnt > 2) + 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 (req->src.width*req->src.height <= OVERLAY_VGA_SIZE) { + if (mfd->mdp_rev >= MDP_REV_42) + return OVERLAY_PERF_LEVEL4; + else + return OVERLAY_PERF_LEVEL3; + + } else if (req->src.width*req->src.height <= OVERLAY_720P_TILE_SIZE) { + u32 max, min; + max = (req->dst_rect.h > req->dst_rect.w) ? + req->dst_rect.h : req->dst_rect.w; + min = (mfd->panel_info.yres > mfd->panel_info.xres) ? + mfd->panel_info.xres : mfd->panel_info.yres; + if (max > min) /* landscape mode */ + return OVERLAY_PERF_LEVEL3; + else /* potrait mode */ + return OVERLAY_PERF_LEVEL2; + } + else + return OVERLAY_PERF_LEVEL1; +} + +void mdp4_update_perf_level(u32 perf_level) +{ + new_perf_level = perf_level; +} + +void mdp4_set_perf_level(void) +{ + static int old_perf_level; + int cur_perf_level; + + if (mdp4_extn_disp) + cur_perf_level = OVERLAY_PERF_LEVEL1; + else + cur_perf_level = new_perf_level; + + if (old_perf_level != cur_perf_level) { + mdp_set_core_clk(cur_perf_level); + old_perf_level = cur_perf_level; + mdp_bus_scale_update_request(OVERLAY_BUS_SCALE_TABLE_BASE + - cur_perf_level); + } +} + +static void mdp4_overlay_update_blt_mode(struct msm_fb_data_type *mfd) +{ + if (mfd->use_ov0_blt == mfd->ov0_blt_state) + return; + + if (mfd->use_ov0_blt) { + if (mfd->panel_info.type == LCDC_PANEL || + mfd->panel_info.type == LVDS_PANEL) + mdp4_lcdc_overlay_blt_start(mfd); + else if (mfd->panel_info.type == MIPI_VIDEO_PANEL) + mdp4_dsi_video_blt_start(mfd); + else if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) + mdp4_dsi_overlay_blt_start(mfd); + } else { + if (mfd->panel_info.type == LCDC_PANEL || + mfd->panel_info.type == LVDS_PANEL) + mdp4_lcdc_overlay_blt_stop(mfd); + else if (mfd->panel_info.type == MIPI_VIDEO_PANEL) + mdp4_dsi_video_blt_stop(mfd); + else if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) + mdp4_dsi_overlay_blt_stop(mfd); + } + mfd->ov0_blt_state = mfd->use_ov0_blt; +} + +static void mdp4_overlay1_update_blt_mode(struct msm_fb_data_type *mfd) +{ + if (mfd->ov1_blt_state == mfd->use_ov1_blt) + return; + if (mfd->use_ov1_blt) { + mdp4_allocate_writeback_buf(mfd, MDP4_MIXER1); + mdp4_dtv_overlay_blt_start(mfd); + pr_debug("%s overlay1 writeback is enabled\n", __func__); + } else { + mdp4_dtv_overlay_blt_stop(mfd); + pr_debug("%s overlay1 writeback is disabled\n", __func__); + } + mfd->ov1_blt_state = mfd->use_ov1_blt; +} + +static u32 mdp4_overlay_blt_enable(struct mdp_overlay *req, + struct msm_fb_data_type *mfd, uint32 perf_level) +{ + u32 clk_rate = mfd->panel_info.clk_rate; + u32 pull_mode = 0, use_blt = 0; + + if (mfd->panel_info.type == MIPI_VIDEO_PANEL) + clk_rate = (&mfd->panel_info.mipi)->dsi_pclk_rate; + + if ((mfd->panel_info.type == LCDC_PANEL) || + (mfd->panel_info.type == MIPI_VIDEO_PANEL) || + (mfd->panel_info.type == DTV_PANEL)) + pull_mode = 1; + + if (pull_mode && (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, + clk_rate)) + use_blt = 1; + } + + if (mfd->mdp_rev == MDP_REV_41) { + /* + * writeback (blt) mode to provide work around for + * dsi cmd mode interface hardware bug. + */ + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + if (req->dst_rect.x != 0) + use_blt = 1; + } + if ((mfd->panel_info.xres > 1280) && + (mfd->panel_info.type != DTV_PANEL)) + use_blt = 1; + } + return use_blt; +} + +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, perf_level; + struct mdp4_overlay_pipe *pipe; + + if (mfd == NULL) { + pr_err("%s: mfd == NULL, -ENODEV\n", __func__); + return -ENODEV; + } + + if (info->node != 0 || mfd->cont_splash_done) /* primary */ + 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; + } + + 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; + } + + perf_level = mdp4_overlay_get_perf_level(req, mfd); + + if (mixer == MDP4_MIXER0) { + u32 use_blt = mdp4_overlay_blt_enable(req, mfd, perf_level); + mfd->use_ov0_blt &= ~(1 << (pipe->pipe_ndx-1)); + mfd->use_ov0_blt |= (use_blt << (pipe->pipe_ndx-1)); + } + + /* 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 (!IS_ERR_OR_NULL(mfd->iclient)) { + pr_debug("pipe->flags 0x%x\n", pipe->flags); + if (pipe->flags & MDP_SECURE_OVERLAY_SESSION) { + mfd->mem_hid &= ~BIT(ION_IOMMU_HEAP_ID); + mfd->mem_hid |= ION_SECURE; + } else { + mfd->mem_hid |= BIT(ION_IOMMU_HEAP_ID); + mfd->mem_hid &= ~ION_SECURE; + } + } + + 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_debug("%s: No sharpening while downscaling.\n", + __func__); + pipe->flags &= ~MDP_SHARPENING; + } + } + + /* precompute HSIC matrices */ + if (req->flags & MDP_DPP_HSIC) + mdp4_hsic_set(pipe, &(req->dpp)); + + mdp4_stat.overlay_set[pipe->mixer_num]++; + + if (ctrl->panel_mode & MDP4_PANEL_DTV && + pipe->mixer_num == MDP4_MIXER1) { + u32 use_blt = mdp4_overlay_blt_enable(req, mfd, perf_level); + + if (hdmi_prim_display) { + if (!mdp4_overlay_is_rgb_type(req->src.format) && + pipe->pipe_type == OVERLAY_TYPE_VIDEO && + (req->src_rect.h > req->dst_rect.h || + req->src_rect.w > req->dst_rect.w)) + use_blt = 1; + } + + mdp4_overlay_dtv_set(mfd, pipe); + mfd->use_ov1_blt &= ~(1 << (pipe->pipe_ndx-1)); + mfd->use_ov1_blt |= (use_blt << (pipe->pipe_ndx-1)); + } + + if (new_perf_level != perf_level) { + u32 old_level = new_perf_level; + mdp4_update_perf_level(perf_level); + + /* change clck base on perf level */ + if (pipe->mixer_num == MDP4_MIXER0) { + if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) { + if (old_level > perf_level) + mdp4_set_perf_level(); + } else if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + mdp4_dsi_cmd_dma_busy_wait(mfd); + mdp4_dsi_blt_dmap_busy_wait(mfd); + mdp4_set_perf_level(); + } else if (ctrl->panel_mode & MDP4_PANEL_LCDC) { + if (old_level > perf_level) + mdp4_set_perf_level(); + } else if (ctrl->panel_mode & MDP4_PANEL_MDDI) { + mdp4_mddi_dma_busy_wait(mfd); + mdp4_set_perf_level(); + } + } else { + if (ctrl->panel_mode & MDP4_PANEL_DTV) { + mdp4_overlay_reg_flush(pipe, 0); + mdp4_overlay_dtv_ov_done_push(mfd, pipe); + } + } + } + mutex_unlock(&mfd->dma->ov_mutex); + + return 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; + struct dpp_ctrl dpp; + int i; + + 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_MIXER2) + ctrl->mixer2_played = 0; + else 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 + } + + if (mfd->mdp_rev >= MDP_REV_41 && + mdp4_overlay_is_rgb_type(pipe->src_format) && + !mfd->use_ov0_blt && (pipe->mixer_num == MDP4_MIXER0 || + hdmi_prim_display)) { + ctrl->stage[pipe->mixer_num][pipe->mixer_stage] = NULL; + } else { + if (pipe->is_fg && + !mdp4_overlay_is_rgb_type(pipe->src_format)) { + mdp4_overlay_bg_solidfill_clear(pipe->mixer_num); + pipe->is_fg = 0; + } + + 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) + mdp4_dsi_cmd_overlay_restore(); + } else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) { + pipe->flags &= ~MDP_OV_PLAY_NOWAIT; + if (mfd->panel_power_on) + mdp4_overlay_dsi_video_vsync_push(mfd, + pipe); + } +#else + if (ctrl->panel_mode & MDP4_PANEL_MDDI) { + if (mfd->panel_power_on) + mdp4_mddi_overlay_restore(); + } +#endif + else if (ctrl->panel_mode & MDP4_PANEL_LCDC) { + pipe->flags &= ~MDP_OV_PLAY_NOWAIT; + if (mfd->panel_power_on) + mdp4_overlay_lcdc_vsync_push(mfd, pipe); + } + mfd->use_ov0_blt &= ~(1 << (pipe->pipe_ndx-1)); + mdp4_overlay_update_blt_mode(mfd); + if (!mfd->use_ov0_blt) + mdp4_free_writeback_buf(mfd, MDP4_MIXER0); + } else { /* mixer1, DTV, ATV */ + if (ctrl->panel_mode & MDP4_PANEL_DTV) { + mdp4_overlay_dtv_unset(mfd, pipe); + mfd->use_ov1_blt &= ~(1 << (pipe->pipe_ndx-1)); + mdp4_overlay1_update_blt_mode(mfd); + if (!mfd->use_ov1_blt) + mdp4_free_writeback_buf(mfd, + MDP4_MIXER1); + } + } + } + + /* Reset any HSIC settings to default */ + if (pipe->flags & MDP_DPP_HSIC) { + for (i = 0; i < NUM_HSIC_PARAM; i++) + dpp.hsic_params[i] = 0; + + mdp4_hsic_set(pipe, &dpp); + mdp4_hsic_update(pipe); + } + + mdp4_stat.overlay_unset[pipe->mixer_num]++; + + mdp4_overlay_pipe_free(pipe); + + mutex_unlock(&mfd->dma->ov_mutex); + + 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_wait(struct fb_info *info, struct msmfb_overlay_data *req) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct mdp4_overlay_pipe *pipe; + + if (mfd == NULL) + return -ENODEV; + + if (!mfd->panel_power_on) /* suspended */ + return -EPERM; + + pipe = mdp4_overlay_ndx2pipe(req->id); + + if (!pipe) { + mdp4_stat.err_play++; + return -ENODEV; + } + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + mdp4_mixer_stage_commit(pipe->mixer_num); + + if (mfd->use_ov1_blt) + mdp4_overlay1_update_blt_mode(mfd); + + mdp4_overlay_dtv_wait4vsync(); + mdp4_iommu_unmap(pipe); + + mutex_unlock(&mfd->dma->ov_mutex); + return 0; +} + +int mdp4_overlay_play(struct fb_info *info, struct msmfb_overlay_data *req) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct msmfb_data *img; + struct mdp4_overlay_pipe *pipe; + ulong start, addr; + ulong len = 0; + struct file *srcp0_file = NULL; + struct file *srcp1_file = NULL, *srcp2_file = NULL; + struct ion_handle *srcp0_ihdl = NULL; + struct ion_handle *srcp1_ihdl = NULL, *srcp2_ihdl = NULL; + int ps0_need, p_need; + uint32_t overlay_version = 0; + int ret = 0; + + if (mfd == NULL) + return -ENODEV; + + if (!mfd->panel_power_on) /* suspended */ + return -EPERM; + + pipe = mdp4_overlay_ndx2pipe(req->id); + if (pipe == NULL) { + mdp4_stat.err_play++; + return -ENODEV; + } + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + img = &req->data; + get_img(img, info, pipe, 0, &start, &len, &srcp0_file, + &ps0_need, &srcp0_ihdl); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: pmem Error\n", __func__); + ret = -1; + goto end; + } + + 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, pipe, 1, &start, &len, &srcp1_file, + &p_need, &srcp1_ihdl); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: Error to get plane1\n", __func__); + ret = -EINVAL; + goto end; + } + pipe->srcp1_addr = start + img->offset; + } 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, pipe, 1, &start, &len, &srcp1_file, + &p_need, &srcp1_ihdl); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: Error to get plane1\n", __func__); + ret = -EINVAL; + goto end; + } + pipe->srcp1_addr = start + img->offset; + + img = &req->plane2_data; + get_img(img, info, pipe, 2, &start, &len, &srcp2_file, + &p_need, &srcp2_ihdl); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: Error to get plane2\n", __func__); + ret = -EINVAL; + goto end; + } + pipe->srcp2_addr = start + img->offset; + } else { + if (pipe->src_format == MDP_Y_CR_CB_GH2V2) { + addr += (ALIGN(pipe->src_width, 16) * + pipe->src_height); + pipe->srcp1_addr = addr; + addr += ((ALIGN((pipe->src_width / 2), 16)) * + (pipe->src_height / 2)); + pipe->srcp2_addr = addr; + } 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; + } + } + /* mdp planar format expects Cb in srcp1 and Cr in p2 */ + if ((pipe->src_format == MDP_Y_CR_CB_H2V2) || + (pipe->src_format == MDP_Y_CR_CB_GH2V2)) + swap(pipe->srcp1_addr, pipe->srcp2_addr); + + if (pipe->src_format == MDP_Y_CR_CB_GH2V2) { + pipe->srcp0_ystride = ALIGN(pipe->src_width, 16); + pipe->srcp1_ystride = ALIGN(pipe->src_width / 2, 16); + pipe->srcp2_ystride = ALIGN(pipe->src_width / 2, 16); + } else { + pipe->srcp0_ystride = pipe->src_width; + pipe->srcp1_ystride = pipe->src_width / 2; + pipe->srcp2_ystride = pipe->src_width / 2; + } + } + + if (mfd->use_ov0_blt) + mdp4_overlay_update_blt_mode(mfd); + + if (mfd->use_ov1_blt) + mdp4_overlay1_update_blt_mode(mfd); + + if (pipe->pipe_type == OVERLAY_TYPE_VIDEO) { + mdp4_overlay_vg_setup(pipe); /* video/graphic pipe */ + } else { + if (pipe->flags & MDP_SHARPENING) { + pr_debug( + "%s: Sharpening/Smoothing not supported on RGB pipe\n", + __func__); + pipe->flags &= ~MDP_SHARPENING; + } + mdp4_overlay_rgb_setup(pipe); /* rgb pipe */ + } + + if ((ctrl->panel_mode & MDP4_PANEL_DTV) || + (ctrl->panel_mode & MDP4_PANEL_LCDC) || + (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO)) + mdp4_overlay_reg_flush(pipe, 0); + + mdp4_mixer_stage_up(pipe); + + if (pipe->mixer_num == MDP4_MIXER2) { + ctrl->mixer2_played++; +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL + if (ctrl->panel_mode & MDP4_PANEL_WRITEBACK) { + mdp4_writeback_dma_busy_wait(mfd); + mdp4_writeback_kickoff_video(mfd, pipe); + } +#endif + } else if (pipe->mixer_num == MDP4_MIXER1) { + ctrl->mixer1_played++; + /* enternal interface */ + if (ctrl->panel_mode & MDP4_PANEL_DTV) { + mdp4_overlay_dtv_start(); + mdp4_overlay_dtv_ov_done_push(mfd, pipe); + if (!mfd->use_ov1_blt) + mdp4_overlay1_update_blt_mode(mfd); + } + } else { + + /* primary interface */ + ctrl->mixer0_played++; + if (ctrl->panel_mode & MDP4_PANEL_LCDC) { + mdp4_overlay_reg_flush(pipe, 0); + mdp4_overlay_lcdc_start(); + mdp4_overlay_lcdc_vsync_push(mfd, pipe); + if (!mfd->use_ov0_blt && + !(pipe->flags & MDP_OV_PLAY_NOWAIT)) + mdp4_overlay_update_blt_mode(mfd); + } +#ifdef CONFIG_FB_MSM_MIPI_DSI + else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) { + mdp4_overlay_reg_flush(pipe, 0); + mdp4_overlay_dsi_video_start(); + mdp4_overlay_dsi_video_vsync_push(mfd, pipe); + if (!mfd->use_ov0_blt && + !(pipe->flags & MDP_OV_PLAY_NOWAIT)) + mdp4_overlay_update_blt_mode(mfd); + } +#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); + goto end; + } +#ifdef CONFIG_FB_MSM_MIPI_DSI + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + mdp4_iommu_attach(); + 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 + } + } + + /* write out DPP HSIC registers */ + if (pipe->flags & MDP_DPP_HSIC) + mdp4_hsic_update(pipe); + if (!(pipe->flags & MDP_OV_PLAY_NOWAIT)) + mdp4_iommu_unmap(pipe); + mdp4_stat.overlay_play[pipe->mixer_num]++; + mutex_unlock(&mfd->dma->ov_mutex); +end: +#ifdef CONFIG_ANDROID_PMEM + if (srcp0_file) + put_pmem_file(srcp0_file); + if (srcp1_file) + put_pmem_file(srcp1_file); + if (srcp2_file) + put_pmem_file(srcp2_file); +#endif + /* only source may use frame buffer */ + if (img->flags & MDP_MEMORY_ID_TYPE_FB) + fput_light(srcp0_file, ps0_need); + return ret; +} + +static struct { + char *name; + int domain; +} msm_iommu_ctx_names[] = { + /* Display */ + { + .name = "mdp_port0_cb0", + .domain = DISPLAY_DOMAIN, + }, + /* Display */ + { + .name = "mdp_port0_cb1", + .domain = DISPLAY_DOMAIN, + }, + /* Display */ + { + .name = "mdp_port1_cb0", + .domain = DISPLAY_DOMAIN, + }, + /* Display */ + { + .name = "mdp_port1_cb1", + .domain = DISPLAY_DOMAIN, + }, +}; + +static int iommu_enabled; +static int mdp_iommu_fault_handler(struct iommu_domain *domain, + struct device *dev, unsigned long iova, int flags) +{ + pr_err("MDP IOMMU page fault: iova 0x%lx", iova); + return 0; +} + +void mdp4_iommu_attach(void) +{ + struct iommu_domain *domain; + int i; + + if (!iommu_enabled) { + 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; + + domain = msm_get_iommu_domain(domain_idx); + if (!domain) + continue; + + iommu_set_fault_handler(domain, + mdp_iommu_fault_handler); + if (iommu_attach_device(domain, ctx)) { + WARN(1, "%s: could not attach domain %d to context %s." + " iommu programming will not occur.\n", + __func__, domain_idx, + msm_iommu_ctx_names[i].name); + continue; + } + } + pr_debug("Attached MDP IOMMU device\n"); + iommu_enabled = 1; + } +} + +void mdp4_iommu_detach(void) +{ + struct iommu_domain *domain; + int i; + + if (!mdp_check_suspended() || mdp4_extn_disp) + return; + + if (iommu_enabled) { + 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; + + domain = msm_get_iommu_domain(domain_idx); + if (!domain) + continue; + + iommu_detach_device(domain, ctx); + } + pr_debug("Detached MDP IOMMU device\n"); + iommu_enabled = 0; + } +} + +int mdp4_v4l2_overlay_set(struct fb_info *info, struct mdp_overlay *req, + struct mdp4_overlay_pipe **ppipe) +{ + struct mdp4_overlay_pipe *pipe; + int err; + struct msm_fb_data_type *mfb = info->par; + + req->z_order = 0; + req->id = MSMFB_NEW_REQUEST; + req->is_fg = false; + req->alpha = 0xff; + err = mdp4_overlay_req2pipe(req, MDP4_MIXER0, &pipe, mfb); + if (err < 0) { + pr_err("%s:Could not allocate MDP overlay pipe\n", __func__); + return err; + } + + mdp4_mixer_blend_setup(pipe); + *ppipe = pipe; + + return 0; +} + +void mdp4_v4l2_overlay_clear(struct mdp4_overlay_pipe *pipe) +{ + mdp4_mixer_stage_down(pipe); + mdp4_overlay_pipe_free(pipe); +} + +int mdp4_v4l2_overlay_play(struct fb_info *info, struct mdp4_overlay_pipe *pipe, + unsigned long srcp0_addr, unsigned long srcp1_addr, + unsigned long srcp2_addr) +{ + struct msm_fb_data_type *mfd = info->par; + int err; + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + switch (pipe->src_format) { + case MDP_Y_CR_CB_H2V2: + /* YUV420 */ + pipe->srcp0_addr = srcp0_addr; + pipe->srcp0_ystride = pipe->src_width; + /* + * For YUV420, the luma plane is 1 byte per pixel times + * num of pixels in the image Also, the planes are + * switched in MDP, srcp2 is actually first chroma plane + */ + pipe->srcp2_addr = srcp1_addr ? srcp1_addr : + pipe->srcp0_addr + (pipe->src_width * pipe->src_height); + pipe->srcp2_ystride = pipe->src_width/2; + /* + * The chroma planes are half the size of the luma + * planes + */ + pipe->srcp1_addr = srcp2_addr ? srcp2_addr : + pipe->srcp2_addr + + (pipe->src_width * pipe->src_height / 4); + pipe->srcp1_ystride = pipe->src_width/2; + break; + case MDP_Y_CRCB_H2V2: + /* NV12 */ + pipe->srcp0_addr = srcp0_addr; + pipe->srcp0_ystride = pipe->src_width; + pipe->srcp1_addr = srcp1_addr ? srcp1_addr : + pipe->srcp0_addr + + (pipe->src_width * pipe->src_height); + pipe->srcp1_ystride = pipe->src_width; + break; + default: + pr_err("%s: format (%u) is not supported\n", __func__, + pipe->src_format); + err = -EINVAL; + goto done; + } + + pr_debug("%s: pipe ndx=%d stage=%d format=%x\n", __func__, + pipe->pipe_ndx, pipe->mixer_stage, pipe->src_format); + + if (pipe->pipe_type == OVERLAY_TYPE_VIDEO) + mdp4_overlay_vg_setup(pipe); + else + mdp4_overlay_rgb_setup(pipe); + + if (ctrl->panel_mode & MDP4_PANEL_LCDC) + mdp4_overlay_reg_flush(pipe, 1); + + mdp4_mixer_stage_up(pipe); + + if (ctrl->panel_mode & MDP4_PANEL_LCDC) { + mdp4_overlay_lcdc_vsync_push(mfd, pipe); + } else { +#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 + } +done: + mutex_unlock(&mfd->dma->ov_mutex); + return err; +} + diff --git a/drivers/video/msm/mdp4_overlay_atv.c b/drivers/video/msm/mdp4_overlay_atv.c new file mode 100644 index 0000000000000000000000000000000000000000..753ff23be09ed5b20289ffe9043d8d0740925f22 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_atv.c @@ -0,0 +1,210 @@ +/* Copyright (c) 2010, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; + unsigned int buf_offset; + 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_offset = calc_fb_offset(mfd, fbi, bpp); + + if (atv_pipe == NULL) { + ptype = mdp4_overlay_format2type(mfd->fb_imgType); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER1); + 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; + if (mfd->map_buffer) { + pipe->srcp0_addr = (unsigned int)mfd->map_buffer->iova[0] + \ + buf_offset; + pr_debug("start 0x%lx srcp0_addr 0x%x\n", mfd-> + map_buffer->iova[0], pipe->srcp0_addr); + } else { + pipe->srcp0_addr = (uint32)(buf + buf_offset); + } + + 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_overlayproc_cfg(pipe); + + mdp4_overlay_reg_flush(pipe, 1); + mdp4_mixer_stage_up(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); + mdp4_iommu_unmap(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; + unsigned int buf_offset; + 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_offset = calc_fb_offset(mfd, fbi, bpp); + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = atv_pipe; + if (mfd->map_buffer) { + pipe->srcp0_addr = (unsigned int)mfd->map_buffer->iova[0] + \ + buf_offset; + pr_debug("start 0x%lx srcp0_addr 0x%x\n", mfd-> + map_buffer->iova[0], pipe->srcp0_addr); + } else { + pipe->srcp0_addr = (uint32)(buf + buf_offset); + } + mdp4_overlay_rgb_setup(pipe); + mdp4_overlay_reg_flush(pipe, 0); + mdp4_mixer_stage_up(pipe); + + 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); + + /* change mdp clk while mdp is idle` */ + mdp4_set_perf_level(); + + mdp4_stat.kickoff_atv++; + 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 0000000000000000000000000000000000000000..a5b4b3eccdb4b4bd981e186fd2d3a6ae54d4ba1f --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_dsi_cmd.c @@ -0,0 +1,685 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; + +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); +} + +int mdp4_overlay_dsi_state_get(void) +{ + return dsi_state; +} + +static void dsi_clock_tout(unsigned long data) +{ + if (mipi_dsi_clk_on) { + if (dsi_state == ST_DSI_PLAYING) { + mipi_dsi_turn_off_clks(); + 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; + 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); + 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 */ + + mdp4_init_writeback_buf(mfd, MDP4_MIXER0); + pipe->blt_addr = 0; + + } else { + pipe = dsi_pipe; + } + /* + * configure dsi stream id + * dma_p = 0, dma_s = 1 + */ + MDP_OUTP(MDP_BASE + 0x000a0, 0x10); + /* disable dsi trigger */ + MDP_OUTP(MDP_BASE + 0x000a4, 0x00); + /* 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(); +} + +/* 3D side by side */ +void mdp4_dsi_cmd_3d_sbys(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); +} + +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); + + mdp4_allocate_writeback_buf(mfd, MDP4_MIXER0); + + if (mfd->ov0_wb_buf->phys_addr == 0) { + pr_info("%s: no blt_base assigned\n", __func__); + return -EBUSY; + } + + 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 = mfd->ov0_wb_buf->phys_addr; + mdp4_stat.blt_dsi_cmd++; + 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 = 0; + 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); + +} + +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); + mdp4_stat.kickoff_dmap++; + /* trigger dsi cmd engine */ + mipi_dsi_cmd_mdp_start(); + + 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); + mdp4_stat.kickoff_dmap++; + /* trigger dsi cmd engine */ + mipi_dsi_cmd_mdp_start(); + 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); + mipi_dsi_mdp_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_turn_on_clks(); + 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) +{ + /* + * a video kickoff may happen before UI kickoff after + * blt enabled. mdp4_overlay_update_dsi_cmd() need + * to be called before kickoff. + * vice versa for blt disabled. + */ + if (dsi_pipe->blt_addr && dsi_pipe->blt_cnt == 0) + mdp4_overlay_update_dsi_cmd(mfd); /* first time */ + else if (dsi_pipe->blt_addr == 0 && dsi_pipe->blt_cnt) { + mdp4_overlay_update_dsi_cmd(mfd); /* last time */ + dsi_pipe->blt_cnt = 0; + } + + pr_debug("%s: blt_addr=%d blt_cnt=%d\n", + __func__, (int)dsi_pipe->blt_addr, dsi_pipe->blt_cnt); + + 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; + + + /* change mdp clk */ + mdp4_set_perf_level(); + + mipi_dsi_mdp_busy_wait(mfd); + + if (dsi_pipe->blt_addr == 0) + mipi_dsi_cmd_mdp_start(); + + 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); + mdp4_stat.kickoff_ov0++; +} + +void mdp_dsi_cmd_overlay_suspend(void) +{ + /* dis-engage rgb0 from mixer0 */ + if (dsi_pipe) { + mdp4_mixer_stage_down(dsi_pipe); + mdp4_iommu_unmap(dsi_pipe); + } +} + +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_iommu_attach(); + mdp4_dsi_cmd_kickoff_ui(mfd, dsi_pipe); + mdp4_iommu_unmap(dsi_pipe); + /* signal if pan function is waiting for the update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + 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 0000000000000000000000000000000000000000..fb71cc164d5aa8b947ce899fa0f461c3b413b5b0 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_dsi_video.c @@ -0,0 +1,709 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +#include "mipi_dsi.h" + +#include + +#define DSI_VIDEO_BASE 0xE0000 + +static int first_pixel_start_x; +static int first_pixel_start_y; +static int dsi_video_enabled; + +static struct mdp4_overlay_pipe *dsi_pipe; +static struct completion dsi_video_comp; +static int blt_cfg_changed; + +static cmd_fxn_t display_on; + +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_dsi_video_fxn_register(cmd_fxn_t fxn) +{ + display_on = fxn; +} + +static void mdp4_overlay_dsi_video_wait4event(struct msm_fb_data_type *mfd, + int intr_done); + +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; + unsigned int buf_offset; + 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; + + mdp4_overlay_ctrl_db_reset(); + + fbi = mfd->fbi; + var = &fbi->var; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf_offset = calc_fb_offset(mfd, fbi, bpp); + + 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); + 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 */ + init_completion(&dsi_video_comp); + + mdp4_init_writeback_buf(mfd, MDP4_MIXER0); + pipe->blt_addr = 0; + + } else { + pipe = dsi_pipe; + } + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (!(mfd->cont_splash_done)) { + mfd->cont_splash_done = 1; + mdp_pipe_ctrl(MDP_CMD_BLOCK, + MDP_BLOCK_POWER_OFF, FALSE); + mdp4_overlay_dsi_video_wait4event(mfd, INTR_DMA_P_DONE); + /* disable timing generator */ + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 0); + mipi_dsi_controller_cfg(0); + } + + 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_ystride = fbi->fix.line_length; + pipe->bpp = bpp; + + if (mfd->display_iova) + pipe->srcp0_addr = mfd->display_iova + buf_offset; + else + pipe->srcp0_addr = (uint32)(buf + buf_offset); + + 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_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.lcdc.xres_pad; + dsi_height = mfd->panel_info.yres + + mfd->panel_info.lcdc.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); + mdp4_mixer_stage_up(pipe); + + mdp_histogram_ctrl_all(TRUE); + + ret = panel_next_on(pdev); + if (ret == 0) { + 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); + mdp4_mixer_pipe_cleanup(dsi_pipe->mixer_num); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 0); + dsi_video_enabled = 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_all(FALSE); + ret = panel_next_off(pdev); + + /* delay to make sure the last frame finishes */ + msleep(20); + + /* dis-engage rgb0 from mixer0 */ + if (dsi_pipe) { + mdp4_mixer_stage_down(dsi_pipe); + mdp4_iommu_unmap(dsi_pipe); + } + + return ret; +} + +/* 3D side by side */ +void mdp4_dsi_video_3d_sbys(struct msm_fb_data_type *mfd, + struct msmfb_overlay_3d *r3d) +{ + struct fb_info *fbi; + struct mdp4_overlay_pipe *pipe; + unsigned int buf_offset; + int bpp; + uint8 *buf = 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); + + fbi = mfd->fbi; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf_offset = calc_fb_offset(mfd, fbi, bpp); + + if (pipe->is_3d) { + 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; + + if (mfd->display_iova) + pipe->srcp0_addr = mfd->display_iova + buf_offset; + else + pipe->srcp0_addr = (uint32)(buf + buf_offset); + + mdp4_overlay_rgb_setup(pipe); + + mdp4_overlayproc_cfg(pipe); + + mdp4_overlay_dmap_xy(pipe); + + mdp4_overlay_dmap_cfg(mfd, 1); + + mdp4_overlay_reg_flush(pipe, 1); + + mdp4_mixer_stage_up(pipe); + + mb(); + + /* wait for vsycn */ + mdp4_overlay_dsi_video_vsync_push(mfd, pipe); +} + +static void mdp4_dsi_video_blt_ov_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->ov_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + addr = pipe->blt_addr + off; + + /* overlay 0 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + outpdw(overlay_base + 0x000c, addr); + outpdw(overlay_base + 0x001c, addr); +} + +static void mdp4_dsi_video_blt_dmap_update(struct mdp4_overlay_pipe *pipe) +{ + uint32 off, addr; + int bpp; + + 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); +} + +/* + * mdp4_overlay_dsi_video_wait4event: + * INTR_DMA_P_DONE and INTR_PRIMARY_VSYNC event only + * no INTR_OVERLAY0_DONE event allowed. + */ +static void mdp4_overlay_dsi_video_wait4event(struct msm_fb_data_type *mfd, + int intr_done) +{ + unsigned long flag; + unsigned int data; + + data = inpdw(MDP_BASE + DSI_VIDEO_BASE); + data &= 0x01; + if (data == 0) /* timing generator disabled */ + return; + + spin_lock_irqsave(&mdp_spin_lock, flag); + INIT_COMPLETION(dsi_video_comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, intr_done); + mdp_intr_mask |= intr_done; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp_enable_irq(MDP_DMA2_TERM); /* enable intr */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion(&dsi_video_comp); + mdp_disable_irq(MDP_DMA2_TERM); +} + +static void mdp4_overlay_dsi_video_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) { + INIT_COMPLETION(mfd->dma->comp); + 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_overlay_dsi_video_start(void) +{ + if (!dsi_video_enabled) { + /* enable DSI block */ + mdp4_iommu_attach(); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 1); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + dsi_video_enabled = 1; + } +} + +void mdp4_overlay_dsi_video_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + unsigned long flag; + + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + if (dsi_pipe->blt_addr) { + mdp4_overlay_dsi_video_dma_busy_wait(mfd); + + mdp4_dsi_video_blt_ov_update(dsi_pipe); + dsi_pipe->ov_cnt++; + + spin_lock_irqsave(&mdp_spin_lock, flag); + outp32(MDP_INTR_CLEAR, INTR_OVERLAY0_DONE); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp_enable_irq(MDP_OVERLAY0_TERM); + mfd->dma->busy = TRUE; + mb(); /* make sure all registers updated */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + outpdw(MDP_BASE + 0x0004, 0); /* kickoff overlay engine */ + mdp4_stat.kickoff_ov0++; + mb(); + mdp4_overlay_dsi_video_wait4event(mfd, INTR_DMA_P_DONE); + } else { + mdp4_overlay_dsi_video_wait4event(mfd, INTR_PRIMARY_VSYNC); + } + + mdp4_set_perf_level(); +} + +/* + * mdp4_primary_vsync_dsi_video: called from isr + */ +void mdp4_primary_vsync_dsi_video(void) +{ + complete_all(&dsi_video_comp); +} + + /* + * mdp4_dma_p_done_dsi_video: called from isr + */ +void mdp4_dma_p_done_dsi_video(struct mdp_dma_data *dma) +{ + if (blt_cfg_changed) { + mdp_is_in_isr = TRUE; + mdp4_overlayproc_cfg(dsi_pipe); + mdp4_overlay_dmap_xy(dsi_pipe); + mdp_is_in_isr = FALSE; + if (dsi_pipe->blt_addr) { + mdp4_dsi_video_blt_ov_update(dsi_pipe); + dsi_pipe->ov_cnt++; + outp32(MDP_INTR_CLEAR, INTR_OVERLAY0_DONE); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->busy = TRUE; + mdp_enable_irq(MDP_OVERLAY0_TERM); + /* kickoff overlay engine */ + outpdw(MDP_BASE + 0x0004, 0); + } + blt_cfg_changed = 0; + } + complete_all(&dsi_video_comp); +} + +/* + * mdp4_overlay1_done_dsi: called from isr + */ +void mdp4_overlay0_done_dsi_video(struct mdp_dma_data *dma) +{ + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + if (dsi_pipe->blt_addr == 0) { + spin_unlock(&mdp_spin_lock); + return; + } + mdp4_dsi_video_blt_dmap_update(dsi_pipe); + dsi_pipe->dmap_cnt++; + mdp_disable_irq_nosync(MDP_OVERLAY0_TERM); + spin_unlock(&mdp_spin_lock); + complete(&dma->comp); +} + +/* + * make sure the MIPI_DSI_WRITEBACK_SIZE defined at boardfile + * has enough space h * w * 3 * 2 + */ +static void mdp4_dsi_video_do_blt(struct msm_fb_data_type *mfd, int enable) +{ + unsigned long flag; + int data; + int change = 0; + + mdp4_allocate_writeback_buf(mfd, MDP4_MIXER0); + + if (mfd->ov0_wb_buf->phys_addr == 0) { + pr_info("%s: no blt_base assigned\n", __func__); + return; + } + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (enable && dsi_pipe->blt_addr == 0) { + dsi_pipe->blt_addr = mfd->ov0_wb_buf->phys_addr; + dsi_pipe->blt_cnt = 0; + dsi_pipe->ov_cnt = 0; + dsi_pipe->dmap_cnt = 0; + mdp4_stat.blt_dsi_video++; + change++; + } else if (enable == 0 && dsi_pipe->blt_addr) { + dsi_pipe->blt_addr = 0; + change++; + } + + if (!change) { + spin_unlock_irqrestore(&mdp_spin_lock, flag); + return; + } + + pr_debug("%s: enable=%d blt_addr=%x\n", __func__, + enable, (int)dsi_pipe->blt_addr); + blt_cfg_changed = 1; + + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + + /* + * may need mutex here to sync with whom dsiable + * timing generator + */ + data = inpdw(MDP_BASE + DSI_VIDEO_BASE); + data &= 0x01; + if (data) { /* timing generator enabled */ + mdp4_overlay_dsi_video_wait4event(mfd, INTR_DMA_P_DONE); + mdp4_overlay_dsi_video_wait4event(mfd, INTR_PRIMARY_VSYNC); + } + + +} + +int mdp4_dsi_video_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + req->offset = 0; + 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) +{ + mdp4_dsi_video_do_blt(mfd, req->enable); +} + +void mdp4_dsi_video_blt_start(struct msm_fb_data_type *mfd) +{ + mdp4_dsi_video_do_blt(mfd, 1); +} + +void mdp4_dsi_video_blt_stop(struct msm_fb_data_type *mfd) +{ + mdp4_dsi_video_do_blt(mfd, 0); +} + +void mdp4_dsi_video_overlay(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + unsigned int buf_offset; + 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_offset = calc_fb_offset(mfd, fbi, bpp); + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = dsi_pipe; + + if (mfd->display_iova) + pipe->srcp0_addr = mfd->display_iova + buf_offset; + else + pipe->srcp0_addr = (uint32)(buf + buf_offset); + + mdp4_overlay_rgb_setup(pipe); + mdp4_overlay_reg_flush(pipe, 0); + mdp4_mixer_stage_up(pipe); + mdp4_overlay_dsi_video_start(); + mdp4_overlay_dsi_video_vsync_push(mfd, pipe); + mdp4_iommu_unmap(pipe); + mutex_unlock(&mfd->dma->ov_mutex); +} diff --git a/drivers/video/msm/mdp4_overlay_dtv.c b/drivers/video/msm/mdp4_overlay_dtv.c new file mode 100644 index 0000000000000000000000000000000000000000..dd96439936bdfba23ba0004961298f08cc4acb3a --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_dtv.c @@ -0,0 +1,709 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 int dtv_enabled; + +static struct mdp4_overlay_pipe *dtv_pipe; +static DECLARE_COMPLETION(dtv_comp); + +static int mdp4_dtv_start(struct msm_fb_data_type *mfd) +{ + 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; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (dtv_pipe == NULL) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (hdmi_prim_display) { + if (is_mdp4_hw_reset()) { + mdp4_hw_init(); + outpdw(MDP_BASE + 0x0038, mdp4_display_intf); + } + } + mdp4_overlay_dmae_cfg(mfd, 0); + + /* + * 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); */ + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return 0; +} + +static int mdp4_dtv_stop(struct msm_fb_data_type *mfd) +{ + if (dtv_pipe == NULL) + return -EINVAL; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp4_mixer_pipe_cleanup(dtv_pipe->mixer_num); + msleep(20); + MDP_OUTP(MDP_BASE + DTV_BASE, 0); + dtv_enabled = 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); + + return 0; +} + +int mdp4_dtv_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + int ret = 0; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + mdp_footswitch_ctrl(TRUE); + mdp4_overlay_panel_mode(MDP4_MIXER1, MDP4_PANEL_DTV); + + /* Allocate dtv_pipe at dtv_on*/ + if (dtv_pipe == NULL) { + if (mdp4_overlay_dtv_set(mfd, NULL)) { + pr_warn("%s: dtv_pipe is NULL, dtv_set failed\n", + __func__); + return -EINVAL; + } + } + + ret = panel_next_on(pdev); + if (ret != 0) + dev_warn(&pdev->dev, "mdp4_overlay_dtv: panel_next_on failed"); + + dev_info(&pdev->dev, "mdp4_overlay_dtv: on"); + + return ret; +} + +int mdp4_dtv_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + int ret = 0; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (dtv_pipe != NULL) { + mdp4_dtv_stop(mfd); + + /* delay to make sure the last frame finishes */ + msleep(20); + + mdp4_mixer_stage_down(dtv_pipe); + mdp4_overlay_pipe_free(dtv_pipe); + mdp4_iommu_unmap(dtv_pipe); + dtv_pipe = NULL; + } + mdp4_overlay_panel_mode_unset(MDP4_MIXER1, MDP4_PANEL_DTV); + + ret = panel_next_off(pdev); + mdp4_iommu_detach(); + mdp_footswitch_ctrl(FALSE); + + dev_info(&pdev->dev, "mdp4_overlay_dtv: off"); + return ret; +} + +static void mdp4_overlay_dtv_alloc_pipe(struct msm_fb_data_type *mfd, + int32 ptype) +{ + int ret = 0; + struct fb_info *fbi = mfd->fbi; + struct mdp4_overlay_pipe *pipe; + + if (dtv_pipe != NULL) + return; + + pr_debug("%s: ptype=%d\n", __func__, ptype); + + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER1); + if (pipe == NULL) { + pr_err("%s: pipe_alloc failed\n", __func__); + return; + } + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER1; + + if (ptype == OVERLAY_TYPE_BF) { + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* LSP_BORDER_COLOR */ + MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5004, + ((0x0 & 0xFFF) << 16) | /* 12-bit B */ + (0x0 & 0xFFF)); /* 12-bit G */ + /* MSP_BORDER_COLOR */ + MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5008, + (0x0 & 0xFFF)); /* 12-bit R */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + } else { + switch (mfd->ibuf.bpp) { + case 2: + pipe->src_format = MDP_RGB_565; + break; + case 3: + pipe->src_format = MDP_RGB_888; + break; + case 4: + default: + if (hdmi_prim_display) + pipe->src_format = MSMFB_DEFAULT_TYPE; + else + pipe->src_format = MDP_ARGB_8888; + break; + } + } + + 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_ystride = fbi->fix.line_length; + + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + pr_warn("%s: format2type failed\n", __func__); + + mdp4_overlay_dmae_xy(pipe); /* dma_e */ + mdp4_overlayproc_cfg(pipe); + + if (pipe->pipe_type == OVERLAY_TYPE_RGB) { + pipe->srcp0_addr = (uint32) mfd->ibuf.buf; + mdp4_overlay_rgb_setup(pipe); + } + + mdp4_overlay_reg_flush(pipe, 1); + mdp4_mixer_stage_up(pipe); + + dtv_pipe = pipe; /* keep it */ +} + +int mdp4_overlay_dtv_set(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + if (dtv_pipe != NULL) + return 0; + + if (pipe != NULL && pipe->mixer_stage == MDP4_MIXER_STAGE_BASE && + pipe->pipe_type == OVERLAY_TYPE_RGB) + dtv_pipe = pipe; /* keep it */ + else if (!hdmi_prim_display && mdp4_overlay_borderfill_supported()) + mdp4_overlay_dtv_alloc_pipe(mfd, OVERLAY_TYPE_BF); + else + mdp4_overlay_dtv_alloc_pipe(mfd, OVERLAY_TYPE_RGB); + if (dtv_pipe == NULL) + return -ENODEV; + + mdp4_init_writeback_buf(mfd, MDP4_MIXER1); + dtv_pipe->blt_addr = 0; + + return mdp4_dtv_start(mfd); +} + +int mdp4_overlay_dtv_unset(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + int result = 0; + + if (dtv_pipe == NULL) + return result; + + pipe->flags &= ~MDP_OV_PLAY_NOWAIT; + mdp4_overlay_reg_flush(pipe, 0); + mdp4_overlay_dtv_ov_done_push(mfd, pipe); + + if (pipe->mixer_stage == MDP4_MIXER_STAGE_BASE && + pipe->pipe_type == OVERLAY_TYPE_RGB) { + result = mdp4_dtv_stop(mfd); + dtv_pipe = NULL; + } + return result; +} + +static void mdp4_dtv_blt_ov_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 = (pipe->ov_cnt & 0x01) ? + pipe->src_height * pipe->src_width * bpp : 0; + + addr = pipe->blt_addr + off; + pr_debug("%s overlay addr 0x%x\n", __func__, addr); + /* overlay 1 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC1_BASE;/* 0x18000 */ + outpdw(overlay_base + 0x000c, addr); + outpdw(overlay_base + 0x001c, addr); +} + +static inline void mdp4_dtv_blt_dmae_update(struct mdp4_overlay_pipe *pipe) +{ + uint32 off, addr; + int bpp; + + if (pipe->blt_addr == 0) + return; + +#ifdef BLT_RGB565 + bpp = 2; /* overlay ouput is RGB565 */ +#else + bpp = 3; /* overlay ouput is RGB888 */ +#endif + off = (pipe->dmae_cnt & 0x01) ? + pipe->src_height * pipe->src_width * bpp : 0; + addr = pipe->blt_addr + off; + MDP_OUTP(MDP_BASE + 0xb0008, addr); +} + +static inline void mdp4_overlay_dtv_ov_kick_start(void) +{ + outpdw(MDP_BASE + 0x0008, 0); +} + +static void mdp4_overlay_dtv_ov_start(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + /* enable irq */ + if (mfd->ov_start) + return; + + if (!dtv_pipe) { + pr_debug("%s: no mixer1 base layer pipe allocated!\n", + __func__); + return; + } + + if (dtv_pipe->blt_addr) { + mdp4_dtv_blt_ov_update(dtv_pipe); + dtv_pipe->ov_cnt++; + mdp4_overlay_dtv_ov_kick_start(); + } + + 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); + mfd->ov_start = true; +} + +static void mdp4_overlay_dtv_wait4dmae(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + if (!dtv_pipe) { + pr_debug("%s: no mixer1 base layer pipe allocated!\n", + __func__); + return; + } + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_DMA_E_TERM); + INIT_COMPLETION(dtv_pipe->comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_DMA_E_DONE); + mdp_intr_mask |= INTR_DMA_E_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_DMA_E_TERM); +} + +static void mdp4_overlay_dtv_wait4_ov_done(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + u32 data = inpdw(MDP_BASE + DTV_BASE); + + if (mfd->ov_start) + mfd->ov_start = false; + else + return; + if (!(data & 0x1) || (pipe == NULL)) + return; + if (!dtv_pipe) { + pr_debug("%s: no mixer1 base layer pipe allocated!\n", + __func__); + return; + } + + wait_for_completion_timeout(&dtv_pipe->comp, + msecs_to_jiffies(VSYNC_PERIOD*2)); + mdp_disable_irq(MDP_OVERLAY1_TERM); + + if (dtv_pipe->blt_addr) + mdp4_overlay_dtv_wait4dmae(mfd); +} + +void mdp4_overlay_dtv_start(void) +{ + if (!dtv_enabled) { + mdp4_iommu_attach(); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* enable DTV block */ + MDP_OUTP(MDP_BASE + DTV_BASE, 1); + mdp_pipe_ctrl(MDP_OVERLAY1_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + dtv_enabled = 1; + } +} + +void mdp4_overlay_dtv_ov_done_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + mdp4_overlay_dtv_ov_start(mfd); + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + mdp4_overlay_dtv_wait4_ov_done(mfd, pipe); + + /* change mdp clk while mdp is idle` */ + mdp4_set_perf_level(); +} + +void mdp4_overlay_dtv_wait_for_ov(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + mdp4_overlay_dtv_wait4_ov_done(mfd, pipe); + mdp4_set_perf_level(); +} + +void mdp4_dma_e_done_dtv() +{ + if (!dtv_pipe) + return; + + complete(&dtv_pipe->comp); +} + +void mdp4_external_vsync_dtv() +{ + + complete_all(&dtv_comp); +} + +/* + * mdp4_overlay1_done_dtv: called from isr + */ +void mdp4_overlay1_done_dtv() +{ + if (!dtv_pipe) + return; + if (dtv_pipe->blt_addr) { + mdp4_dtv_blt_dmae_update(dtv_pipe); + dtv_pipe->dmae_cnt++; + } + complete_all(&dtv_pipe->comp); +} + +void mdp4_dtv_set_black_screen(void) +{ + char *rgb_base; + /*Black color*/ + uint32 color = 0x00000000; + uint32 temp_src_format; + + if (!dtv_pipe || !hdmi_prim_display) { + pr_err("dtv_pipe/hdmi as primary are not" + " configured yet\n"); + return; + } + rgb_base = MDP_BASE + MDP4_RGB_BASE; + rgb_base += (MDP4_RGB_OFF * dtv_pipe->pipe_num); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* + * RGB Constant Color + */ + MDP_OUTP(rgb_base + 0x1008, color); + /* + * MDP_RGB_SRC_FORMAT + */ + temp_src_format = inpdw(rgb_base + 0x0050); + MDP_OUTP(rgb_base + 0x0050, temp_src_format | BIT(22)); + mdp4_overlay_reg_flush(dtv_pipe, 0); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_overlay_dtv_wait4vsync(void) +{ + unsigned long flag; + + if (!dtv_enabled) + return; + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_DMA_E_TERM); + INIT_COMPLETION(dtv_comp); + 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_comp); + mdp_disable_irq(MDP_DMA_E_TERM); +} + +static void mdp4_dtv_do_blt(struct msm_fb_data_type *mfd, int enable) +{ + unsigned long flag; + int change = 0; + + if (!mfd->ov1_wb_buf->phys_addr) { + pr_debug("%s: no writeback buf assigned\n", __func__); + return; + } + + if (!dtv_pipe) { + pr_debug("%s: no mixer1 base layer pipe allocated!\n", + __func__); + return; + } + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (enable && dtv_pipe->blt_addr == 0) { + dtv_pipe->blt_addr = mfd->ov1_wb_buf->phys_addr; + change++; + dtv_pipe->ov_cnt = 0; + dtv_pipe->dmae_cnt = 0; + } else if (enable == 0 && dtv_pipe->blt_addr) { + dtv_pipe->blt_addr = 0; + change++; + } + pr_debug("%s: blt_addr=%x\n", __func__, (int)dtv_pipe->blt_addr); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (!change) + return; + + mdp4_overlay_dtv_wait4dmae(mfd); + + MDP_OUTP(MDP_BASE + DTV_BASE, 0); /* stop dtv */ + msleep(20); + mdp4_overlayproc_cfg(dtv_pipe); + mdp4_overlay_dmae_xy(dtv_pipe); + MDP_OUTP(MDP_BASE + DTV_BASE, 1); /* start dtv */ +} + +void mdp4_dtv_overlay_blt_start(struct msm_fb_data_type *mfd) +{ + mdp4_dtv_do_blt(mfd, 1); +} + +void mdp4_dtv_overlay_blt_stop(struct msm_fb_data_type *mfd) +{ + mdp4_dtv_do_blt(mfd, 0); +} + +void mdp4_dtv_overlay(struct msm_fb_data_type *mfd) +{ + struct mdp4_overlay_pipe *pipe; + if (!mfd->panel_power_on) + return; + if (!dtv_pipe) { + pr_debug("%s: no mixer1 base layer pipe allocated!\n", + __func__); + return; + } + mutex_lock(&mfd->dma->ov_mutex); + pipe = dtv_pipe; + if (pipe->pipe_type == OVERLAY_TYPE_RGB) { + pipe->srcp0_addr = (uint32) mfd->ibuf.buf; + mdp4_overlay_rgb_setup(pipe); + } + mdp4_overlay_reg_flush(pipe, 0); + mdp4_mixer_stage_up(pipe); + mdp4_overlay_dtv_start(); + mdp4_overlay_dtv_ov_done_push(mfd, pipe); + mdp4_iommu_unmap(pipe); + 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 0000000000000000000000000000000000000000..a1fecb6498b74a4f2100771f3a813c68f1b55faf --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_lcdc.c @@ -0,0 +1,601 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 lcdc_enabled; + +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; + unsigned int buf_offset; + 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; + + mdp4_overlay_ctrl_db_reset(); + + 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_offset = calc_fb_offset(mfd, fbi, bpp); + + 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); + 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); + + mdp4_init_writeback_buf(mfd, MDP4_MIXER0); + pipe->blt_addr = 0; + + } 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; + + if (mfd->display_iova) + pipe->srcp0_addr = mfd->display_iova + buf_offset; + else + pipe->srcp0_addr = (uint32)(buf + buf_offset); + + 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_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 + mfd->panel_info.lcdc.xres_pad; + lcdc_height = var->yres + mfd->panel_info.lcdc.yres_pad; + 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); + mdp4_mixer_stage_up(pipe); + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(2); +#endif + mdp_histogram_ctrl_all(TRUE); + + ret = panel_next_on(pdev); + /* 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; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + mutex_lock(&mfd->dma->ov_mutex); + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp4_mixer_pipe_cleanup(lcdc_pipe->mixer_num); + MDP_OUTP(MDP_BASE + LCDC_BASE, 0); + lcdc_enabled = 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_all(FALSE); + ret = panel_next_off(pdev); + + mutex_unlock(&mfd->dma->ov_mutex); + + /* delay to make sure the last frame finishes */ + msleep(20); + + /* dis-engage rgb0 from mixer0 */ + if (lcdc_pipe) { + mdp4_mixer_stage_down(lcdc_pipe); + mdp4_iommu_unmap(lcdc_pipe); + } + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(0); +#endif + + return ret; +} + +static void mdp4_lcdc_blt_ov_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->ov_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + addr = pipe->blt_addr + off; + + /* overlay 0 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + outpdw(overlay_base + 0x000c, addr); + outpdw(overlay_base + 0x001c, addr); +} + +static void mdp4_lcdc_blt_dmap_update(struct mdp4_overlay_pipe *pipe) +{ + uint32 off, addr; + int bpp; + + 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); +} + +/* + * mdp4_overlay_lcdc_wait4event: + * INTR_DMA_P_DONE and INTR_PRIMARY_VSYNC event only + * no INTR_OVERLAY0_DONE event allowed. + */ +static void mdp4_overlay_lcdc_wait4event(struct msm_fb_data_type *mfd, + int intr_done) +{ + unsigned long flag; + unsigned int data; + + data = inpdw(MDP_BASE + LCDC_BASE); + data &= 0x01; + if (data == 0) /* timing generator disabled */ + return; + + spin_lock_irqsave(&mdp_spin_lock, flag); + INIT_COMPLETION(lcdc_comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, intr_done); + mdp_intr_mask |= intr_done; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp_enable_irq(MDP_DMA2_TERM); /* enable intr */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion(&lcdc_comp); + mdp_disable_irq(MDP_DMA2_TERM); +} + +static void mdp4_overlay_lcdc_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) { + INIT_COMPLETION(mfd->dma->comp); + 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_overlay_lcdc_start(void) +{ + if (!lcdc_enabled) { + /* enable LCDC block */ + mdp4_iommu_attach(); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + LCDC_BASE, 1); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + lcdc_enabled = 1; + } +} + +void mdp4_overlay_lcdc_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + unsigned long flag; + + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + if (lcdc_pipe->blt_addr) { + mdp4_overlay_lcdc_dma_busy_wait(mfd); + + mdp4_lcdc_blt_ov_update(lcdc_pipe); + lcdc_pipe->ov_cnt++; + + spin_lock_irqsave(&mdp_spin_lock, flag); + outp32(MDP_INTR_CLEAR, INTR_OVERLAY0_DONE); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp_enable_irq(MDP_OVERLAY0_TERM); + mfd->dma->busy = TRUE; + mb(); /* make sure all registers updated */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + outpdw(MDP_BASE + 0x0004, 0); /* kickoff overlay engine */ + mdp4_stat.kickoff_ov0++; + mb(); + mdp4_overlay_lcdc_wait4event(mfd, INTR_DMA_P_DONE); + } else { + mdp4_overlay_lcdc_wait4event(mfd, INTR_PRIMARY_VSYNC); + } + mdp4_set_perf_level(); +} + +/* + * mdp4_primary_vsync_lcdc: called from isr + */ +void mdp4_primary_vsync_lcdc(void) +{ + complete_all(&lcdc_comp); +} + +/* + * mdp4_dma_p_done_lcdc: called from isr + */ +void mdp4_dma_p_done_lcdc(void) +{ + complete_all(&lcdc_comp); +} + +/* + * mdp4_overlay0_done_lcdc: called from isr + */ +void mdp4_overlay0_done_lcdc(struct mdp_dma_data *dma) +{ + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + if (lcdc_pipe->blt_addr == 0) { + spin_unlock(&mdp_spin_lock); + return; + } + mdp4_lcdc_blt_dmap_update(lcdc_pipe); + lcdc_pipe->dmap_cnt++; + mdp_disable_irq_nosync(MDP_OVERLAY0_TERM); + spin_unlock(&mdp_spin_lock); + complete(&dma->comp); +} + +static void mdp4_overlay_lcdc_prefill(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + if (lcdc_pipe->blt_addr) { + mdp4_overlay_lcdc_dma_busy_wait(mfd); + + mdp4_lcdc_blt_ov_update(lcdc_pipe); + lcdc_pipe->ov_cnt++; + + spin_lock_irqsave(&mdp_spin_lock, flag); + outp32(MDP_INTR_CLEAR, INTR_OVERLAY0_DONE); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp_enable_irq(MDP_OVERLAY0_TERM); + mfd->dma->busy = TRUE; + mb(); /* make sure all registers updated */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + outpdw(MDP_BASE + 0x0004, 0); /* kickoff overlay engine */ + mdp4_stat.kickoff_ov0++; + mb(); + } +} +/* + * make sure the MIPI_DSI_WRITEBACK_SIZE defined at boardfile + * has enough space h * w * 3 * 2 + */ +static void mdp4_lcdc_do_blt(struct msm_fb_data_type *mfd, int enable) +{ + unsigned long flag; + int change = 0; + + mdp4_allocate_writeback_buf(mfd, MDP4_MIXER0); + + if (!mfd->ov0_wb_buf->phys_addr) { + pr_debug("%s: no blt_base assigned\n", __func__); + return; + } + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (enable && lcdc_pipe->blt_addr == 0) { + lcdc_pipe->blt_addr = mfd->ov0_wb_buf->phys_addr; + change++; + lcdc_pipe->blt_cnt = 0; + lcdc_pipe->ov_cnt = 0; + lcdc_pipe->dmap_cnt = 0; + mdp4_stat.blt_lcdc++; + } else if (enable == 0 && lcdc_pipe->blt_addr) { + lcdc_pipe->blt_addr = 0; + change++; + } + pr_info("%s: blt_addr=%x\n", __func__, (int)lcdc_pipe->blt_addr); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (!change) + return; + + mdp4_overlay_lcdc_wait4event(mfd, INTR_DMA_P_DONE); + MDP_OUTP(MDP_BASE + LCDC_BASE, 0); /* stop lcdc */ + msleep(20); + mdp4_overlayproc_cfg(lcdc_pipe); + mdp4_overlay_dmap_xy(lcdc_pipe); + if (lcdc_pipe->blt_addr) { + mdp4_overlay_lcdc_prefill(mfd); + mdp4_overlay_lcdc_prefill(mfd); + } + MDP_OUTP(MDP_BASE + LCDC_BASE, 1); /* start lcdc */ +} + +int mdp4_lcdc_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + req->offset = 0; + 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) +{ + mdp4_lcdc_do_blt(mfd, req->enable); +} + +void mdp4_lcdc_overlay_blt_start(struct msm_fb_data_type *mfd) +{ + mdp4_lcdc_do_blt(mfd, 1); +} + +void mdp4_lcdc_overlay_blt_stop(struct msm_fb_data_type *mfd) +{ + mdp4_lcdc_do_blt(mfd, 0); +} + +void mdp4_lcdc_overlay(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + unsigned int buf_offset; + 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_offset = calc_fb_offset(mfd, fbi, bpp); + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = lcdc_pipe; + + if (mfd->display_iova) + pipe->srcp0_addr = mfd->display_iova + buf_offset; + else + pipe->srcp0_addr = (uint32)(buf + buf_offset); + + mdp4_overlay_rgb_setup(pipe); + mdp4_overlay_reg_flush(pipe, 0); + mdp4_mixer_stage_up(pipe); + mdp4_overlay_lcdc_start(); + mdp4_overlay_lcdc_vsync_push(mfd, pipe); + mdp4_iommu_unmap(pipe); + mutex_unlock(&mfd->dma->ov_mutex); +} diff --git a/drivers/video/msm/mdp4_overlay_mddi.c b/drivers/video/msm/mdp4_overlay_mddi.c new file mode 100644 index 0000000000000000000000000000000000000000..5aa5965ac3993ef1a08dbf49546d3a6059af9d52 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_mddi.c @@ -0,0 +1,596 @@ +/* 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); + 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) +{ + /* change mdp clk while mdp is idle` */ + mdp4_set_perf_level(); + + mdp_enable_irq(MDP_OVERLAY0_TERM); + mfd->dma->busy = TRUE; + /* start OVERLAY pipe */ + mdp_pipe_kickoff(MDP_OVERLAY0_TERM, mfd); + mdp4_stat.kickoff_ov0++; +} + +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) +{ + /* change mdp clk while mdp is idle` */ + mdp4_set_perf_level(); + + 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); + mdp4_stat.kickoff_dmas++; + + /* 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); + + /* signal if pan function is waiting for the update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + 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_overlay_writeback.c b/drivers/video/msm/mdp4_overlay_writeback.c new file mode 100644 index 0000000000000000000000000000000000000000..f1a2ada7a755f8e2a8f240d0e0f6b018c486d43c --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_writeback.c @@ -0,0 +1,596 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" +enum { + WB_OPEN, + WB_START, + WB_STOPING, + WB_STOP +}; +enum { + REGISTERED, + IN_FREE_QUEUE, + IN_BUSY_QUEUE, + WITH_CLIENT +}; + +static struct mdp4_overlay_pipe *writeback_pipe; +static struct msm_fb_data_type *writeback_mfd; +static int busy_wait_cnt; + +int mdp4_overlay_writeback_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct fb_info *fbi; + uint8 *buf; + struct mdp4_overlay_pipe *pipe; + int bpp; + int ret; + uint32 data; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + writeback_mfd = mfd; /* keep it */ + + fbi = mfd->fbi; + + 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 cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (writeback_pipe == NULL) { + pipe = mdp4_overlay_pipe_alloc(OVERLAY_TYPE_BF, MDP4_MIXER2); + if (pipe == NULL) + pr_info("%s: pipe_alloc failed\n", __func__); + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER2; + pipe->src_format = MDP_ARGB_8888; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_WRITEBACK); + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + pr_info("%s: format2type failed\n", __func__); + + writeback_pipe = pipe; /* keep it */ + + } else { + pipe = writeback_pipe; + } + ret = panel_next_on(pdev); + + /* MDP_LAYERMIXER_WB_MUX_SEL to use mixer1 axi for mixer2 writeback */ + if (hdmi_prim_display) + data = 0x01; + else + data = 0x02; + outpdw(MDP_BASE + 0x100F4, data); + + MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5004, + ((0x0 & 0xFFF) << 16) | /* 12-bit B */ + (0x0 & 0xFFF)); /* 12-bit G */ + /* MSP_BORDER_COLOR */ + MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5008, + (0x0 & 0xFFF)); /* 12-bit R */ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return ret; +} + +int mdp4_overlay_writeback_off(struct platform_device *pdev) +{ + int ret; + struct msm_fb_data_type *mfd = + (struct msm_fb_data_type *)platform_get_drvdata(pdev); + if (mfd && writeback_pipe) { + mdp4_writeback_dma_busy_wait(mfd); + mdp4_overlay_pipe_free(writeback_pipe); + mdp4_overlay_panel_mode_unset(writeback_pipe->mixer_num, + MDP4_PANEL_WRITEBACK); + writeback_pipe = NULL; + } + ret = panel_next_off(pdev); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* MDP_LAYERMIXER_WB_MUX_SEL to restore to default cfg*/ + outpdw(MDP_BASE + 0x100F4, 0x0); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return ret; +} +int mdp4_overlay_writeback_update(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi; + uint8 *buf; + unsigned int buf_offset; + struct mdp4_overlay_pipe *pipe; + int bpp; + + if (mfd->key != MFD_KEY) + return -ENODEV; + + if (!writeback_pipe) + return -EINVAL; + + fbi = mfd->fbi; + + pipe = writeback_pipe; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf_offset = fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + /* 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->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; + + if (mfd->display_iova) + pipe->srcp0_addr = mfd->display_iova + buf_offset; + else + pipe->srcp0_addr = (uint32)(buf + buf_offset); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + wmb(); + return 0; +} +void mdp4_writeback_dma_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->busy == TRUE) { + if (busy_wait_cnt == 0) + INIT_COMPLETION(mfd->dma->comp); + busy_wait_cnt = 1; + 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); + } +} + +void mdp4_overlay1_done_writeback(struct mdp_dma_data *dma) +{ + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + if (busy_wait_cnt) + busy_wait_cnt = 0; + mdp_disable_irq_nosync(MDP_OVERLAY2_TERM); + spin_unlock(&mdp_spin_lock); + complete_all(&dma->comp); + pr_debug("%s ovdone interrupt\n", __func__); + +} +void mdp4_writeback_overlay_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + unsigned long flag; + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_OVERLAY2_TERM); + + mfd->dma->busy = TRUE; + outp32(MDP_INTR_CLEAR, INTR_OVERLAY2_DONE); + mdp_intr_mask |= INTR_OVERLAY2_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + + wmb(); /* make sure all registers updated */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + /* start OVERLAY pipe */ + mdp_pipe_kickoff(MDP_OVERLAY2_TERM, mfd); + wmb(); + pr_debug("%s: before ov done interrupt\n", __func__); +} +void mdp4_writeback_dma_stop(struct msm_fb_data_type *mfd) +{ + /* mutex holded by caller */ + if (mfd && writeback_pipe) { + mdp4_writeback_dma_busy_wait(mfd); + mdp4_overlay_writeback_update(mfd); + + mdp4_writeback_overlay_kickoff(mfd, writeback_pipe); + } +} + +void mdp4_writeback_kickoff_video(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + struct msmfb_writeback_data_list *node = NULL; + mutex_lock(&mfd->unregister_mutex); + mutex_lock(&mfd->writeback_mutex); + if (!list_empty(&mfd->writeback_free_queue) + && mfd->writeback_state != WB_STOPING + && mfd->writeback_state != WB_STOP) { + node = list_first_entry(&mfd->writeback_free_queue, + struct msmfb_writeback_data_list, active_entry); + } + if (node) { + list_del(&(node->active_entry)); + node->state = IN_BUSY_QUEUE; + mfd->writeback_active_cnt++; + } + mutex_unlock(&mfd->writeback_mutex); + + writeback_pipe->blt_addr = (ulong) (node ? node->addr : NULL); + + if (!writeback_pipe->blt_addr) { + pr_err("%s: no writeback buffer 0x%x, %p\n", __func__, + (unsigned int)writeback_pipe->blt_addr, node); + mutex_unlock(&mfd->unregister_mutex); + return; + } + + if (writeback_pipe->blt_cnt == 0) + mdp4_overlay_writeback_update(mfd); + + pr_debug("%s: pid=%d\n", __func__, current->pid); + + mdp4_writeback_overlay_kickoff(mfd, pipe); + + mutex_lock(&mfd->writeback_mutex); + list_add_tail(&node->active_entry, &mfd->writeback_busy_queue); + mutex_unlock(&mfd->writeback_mutex); + mfd->writeback_active_cnt--; + mutex_unlock(&mfd->unregister_mutex); + wake_up(&mfd->wait_q); +} + +void mdp4_writeback_kickoff_ui(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + + pr_debug("%s: pid=%d\n", __func__, current->pid); + mdp4_writeback_overlay_kickoff(mfd, pipe); +} + +void mdp4_writeback_overlay(struct msm_fb_data_type *mfd) +{ + int ret = 0; + struct msmfb_writeback_data_list *node = NULL; + + mutex_lock(&mfd->unregister_mutex); + mutex_lock(&mfd->writeback_mutex); + if (!list_empty(&mfd->writeback_free_queue) + && mfd->writeback_state != WB_STOPING + && mfd->writeback_state != WB_STOP) { + node = list_first_entry(&mfd->writeback_free_queue, + struct msmfb_writeback_data_list, active_entry); + } + if (node) { + list_del(&(node->active_entry)); + node->state = IN_BUSY_QUEUE; + mfd->writeback_active_cnt++; + } + mutex_unlock(&mfd->writeback_mutex); + + writeback_pipe->blt_addr = (ulong) (node ? node->addr : NULL); + + mutex_lock(&mfd->dma->ov_mutex); + pr_debug("%s in writeback\n", __func__); + if (writeback_pipe && !writeback_pipe->blt_addr) { + pr_err("%s: no writeback buffer 0x%x\n", __func__, + (unsigned int)writeback_pipe->blt_addr); + ret = mdp4_overlay_writeback_update(mfd); + if (ret) + pr_err("%s: update failed writeback pipe NULL\n", + __func__); + goto fail_no_blt_addr; + } + + if (mfd && mfd->panel_power_on) { + pr_debug("%s in before busy wait\n", __func__); + mdp4_writeback_dma_busy_wait(mfd); + + pr_debug("%s in before update\n", __func__); + ret = mdp4_overlay_writeback_update(mfd); + if (ret) { + pr_err("%s: update failed writeback pipe NULL\n", + __func__); + goto fail_no_blt_addr; + } + + pr_debug("%s: in writeback pan display 0x%x\n", __func__, + (unsigned int)writeback_pipe->blt_addr); + mdp4_writeback_kickoff_ui(mfd, writeback_pipe); + mdp4_iommu_unmap(writeback_pipe); + + /* signal if pan function is waiting for the + * update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + + mutex_lock(&mfd->writeback_mutex); + list_add_tail(&node->active_entry, &mfd->writeback_busy_queue); + mfd->writeback_active_cnt--; + mutex_unlock(&mfd->writeback_mutex); + wake_up(&mfd->wait_q); +fail_no_blt_addr: + /*NOTE: This api was removed + mdp4_overlay_resource_release();*/ + mutex_unlock(&mfd->dma->ov_mutex); + mutex_unlock(&mfd->unregister_mutex); +} +static int mdp4_overlay_writeback_register_buffer( + struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list *node) +{ + if (!node) { + pr_err("Cannot register a NULL node\n"); + return -EINVAL; + } + node->state = REGISTERED; + list_add_tail(&node->registered_entry, &mfd->writeback_register_queue); + return 0; +} +static struct msmfb_writeback_data_list *get_if_registered( + struct msm_fb_data_type *mfd, struct msmfb_data *data) +{ + struct msmfb_writeback_data_list *temp; + bool found = false; + if (!list_empty(&mfd->writeback_register_queue)) { + list_for_each_entry(temp, + &mfd->writeback_register_queue, + registered_entry) { + if (temp && temp->buf_info.iova == data->iova) { + found = true; + break; + } + } + } + if (!found) { + temp = kzalloc(sizeof(struct msmfb_writeback_data_list), + GFP_KERNEL); + if (temp == NULL) { + pr_err("%s: out of memory\n", __func__); + goto register_alloc_fail; + } + + if (data->iova) + temp->addr = (void *)(data->iova + data->offset); +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + else { + struct ion_handle *srcp_ihdl; + ulong len; + srcp_ihdl = ion_import_fd(mfd->iclient, + data->memory_id); + if (IS_ERR_OR_NULL(srcp_ihdl)) { + pr_err("%s: ion import fd failed\n", __func__); + goto register_ion_fail; + } + if (ion_phys(mfd->iclient, + srcp_ihdl, + (ulong *)&temp->addr, + (size_t *)&len)) { + pr_err("%s: unable to get ion phys\n", + __func__); + goto register_ion_fail; + } + temp->addr += data->offset; + } +#else + else { + pr_err("%s: only support ion memory\n", __func__); + goto register_ion_fail; + } +#endif + memcpy(&temp->buf_info, data, sizeof(struct msmfb_data)); + if (mdp4_overlay_writeback_register_buffer(mfd, temp)) { + pr_err("%s: error registering node\n", __func__); + goto register_ion_fail; + } + } + return temp; + register_ion_fail: + kfree(temp); + register_alloc_fail: + return NULL; +} +int mdp4_writeback_start( + struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + mutex_lock(&mfd->writeback_mutex); + mfd->writeback_state = WB_START; + mutex_unlock(&mfd->writeback_mutex); + wake_up(&mfd->wait_q); + return 0; +} + +int mdp4_writeback_queue_buffer(struct fb_info *info, struct msmfb_data *data) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct msmfb_writeback_data_list *node = NULL; + int rv = 0; + + mutex_lock(&mfd->writeback_mutex); + node = get_if_registered(mfd, data); + if (!node || node->state == IN_BUSY_QUEUE || + node->state == IN_FREE_QUEUE) { + pr_err("memory not registered or Buffer already with us\n"); + rv = -EINVAL; + goto exit; + } + + list_add_tail(&node->active_entry, &mfd->writeback_free_queue); + node->state = IN_FREE_QUEUE; + +exit: + mutex_unlock(&mfd->writeback_mutex); + return rv; +} +static int is_buffer_ready(struct msm_fb_data_type *mfd) +{ + int rc; + mutex_lock(&mfd->writeback_mutex); + rc = !list_empty(&mfd->writeback_busy_queue) || + (mfd->writeback_state == WB_STOPING); + mutex_unlock(&mfd->writeback_mutex); + return rc; +} +int mdp4_writeback_dequeue_buffer(struct fb_info *info, struct msmfb_data *data) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct msmfb_writeback_data_list *node = NULL; + int rc = 0; + + rc = wait_event_interruptible(mfd->wait_q, is_buffer_ready(mfd)); + if (rc) { + pr_err("failed to get dequeued buffer\n"); + return -ENOBUFS; + } + mutex_lock(&mfd->writeback_mutex); + if (mfd->writeback_state == WB_STOPING) { + mfd->writeback_state = WB_STOP; + mutex_unlock(&mfd->writeback_mutex); + return -ENOBUFS; + } else if (!list_empty(&mfd->writeback_busy_queue)) { + node = list_first_entry(&mfd->writeback_busy_queue, + struct msmfb_writeback_data_list, active_entry); + } + if (node) { + list_del(&node->active_entry); + node->state = WITH_CLIENT; + memcpy(data, &node->buf_info, sizeof(struct msmfb_data)); + } else { + pr_err("node is NULL. Somebody else dequeued?\n"); + rc = -ENOBUFS; + } + mutex_unlock(&mfd->writeback_mutex); + return rc; +} + +static bool is_writeback_inactive(struct msm_fb_data_type *mfd) +{ + bool active; + mutex_lock(&mfd->writeback_mutex); + active = !mfd->writeback_active_cnt; + mutex_unlock(&mfd->writeback_mutex); + return active; +} +int mdp4_writeback_stop(struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + mutex_lock(&mfd->writeback_mutex); + mfd->writeback_state = WB_STOPING; + mutex_unlock(&mfd->writeback_mutex); + /* Wait for all pending writebacks to finish */ + wait_event_interruptible(mfd->wait_q, is_writeback_inactive(mfd)); + + /* Wake up dequeue thread in case of no UI update*/ + wake_up(&mfd->wait_q); + + return 0; +} +int mdp4_writeback_init(struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + mutex_init(&mfd->writeback_mutex); + mutex_init(&mfd->unregister_mutex); + INIT_LIST_HEAD(&mfd->writeback_free_queue); + INIT_LIST_HEAD(&mfd->writeback_busy_queue); + INIT_LIST_HEAD(&mfd->writeback_register_queue); + mfd->writeback_state = WB_OPEN; + init_waitqueue_head(&mfd->wait_q); + return 0; +} +int mdp4_writeback_terminate(struct fb_info *info) +{ + struct list_head *ptr, *next; + struct msmfb_writeback_data_list *temp; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + int rc = 0; + + mutex_lock(&mfd->unregister_mutex); + mutex_lock(&mfd->writeback_mutex); + + if (mfd->writeback_state != WB_STOPING && + mfd->writeback_state != WB_STOP) { + pr_err("%s called without stopping\n", __func__); + rc = -EPERM; + goto terminate_err; + + } + + if (!list_empty(&mfd->writeback_register_queue)) { + list_for_each_safe(ptr, next, + &mfd->writeback_register_queue) { + temp = list_entry(ptr, + struct msmfb_writeback_data_list, + registered_entry); + list_del(&temp->registered_entry); + kfree(temp); + } + } + INIT_LIST_HEAD(&mfd->writeback_register_queue); + INIT_LIST_HEAD(&mfd->writeback_busy_queue); + INIT_LIST_HEAD(&mfd->writeback_free_queue); + + +terminate_err: + mutex_unlock(&mfd->writeback_mutex); + mutex_unlock(&mfd->unregister_mutex); + return rc; +} diff --git a/drivers/video/msm/mdp4_util.c b/drivers/video/msm/mdp4_util.c new file mode 100644 index 0000000000000000000000000000000000000000..b2657cf304b906b32d90b96e0ac924d1d944a671 --- /dev/null +++ b/drivers/video/msm/mdp4_util.c @@ -0,0 +1,3301 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 if (overlayer == MDP4_MIXER1) + 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 (mdp_rev >= MDP_REV_41 || 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; + uint32 clk_rate; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + mdp4_update_perf_level(OVERLAY_PERF_LEVEL4); + +#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 + + if (mdp_rev > MDP_REV_41) { + /* mdp chip select controller */ + outpdw(MDP_BASE + 0x00c0, CS_CONTROLLER_0); + outpdw(MDP_BASE + 0x00c4, CS_CONTROLLER_1); + } + + mdp4_clear_lcdc(); + + mdp4_mixer_blend_init(0); + mdp4_mixer_blend_init(1); + mdp4_vg_qseed_init(0); + mdp4_vg_qseed_init(1); + + mdp4_vg_csc_setup(0); + mdp4_vg_csc_setup(1); + mdp4_mixer_csc_setup(1); + mdp4_mixer_csc_setup(2); + mdp4_dmap_csc_setup(); + + if (mdp_rev <= MDP_REV_41) { + mdp4_mixer_gc_lut_setup(0); + mdp4_mixer_gc_lut_setup(1); + } + + 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 */ + + /* 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 + + clk_rate = mdp_get_core_clk(); + mdp4_fetch_cfg(clk_rate); + + /* 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); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + + +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; + struct mdp_hist_mgmt *mgmt = NULL; + char *base_addr; + int i, ret; + + 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.*/ + for (i = 0; i < MDP_HIST_MGMT_MAX; i++) { + mgmt = mdp_hist_mgmt_array[i]; + if (!mgmt) + continue; + base_addr = MDP_BASE + mgmt->base; + MDP_OUTP(base_addr + 0x010, 1); + outpdw(base_addr + 0x01c, INTR_HIST_DONE | + INTR_HIST_RESET_SEQ_DONE); + mgmt->mdp_is_hist_valid = FALSE; + __mdp_histogram_reset(mgmt); + } + } + + 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) { + mdp4_stat.intr_vsync_p++; + dma = &dma2_data; + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_PRIMARY_VSYNC; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + 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 + spin_unlock(&mdp_spin_lock); + } +#ifdef CONFIG_FB_MSM_DTV + if (isr & INTR_EXTERNAL_VSYNC) { + mdp4_stat.intr_vsync_e++; + 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; + if (panel & MDP4_PANEL_DTV) + mdp4_external_vsync_dtv(); + spin_unlock(&mdp_spin_lock); + } +#endif + +#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(dma); +#ifdef CONFIG_FB_MSM_MIPI_DSI + else if (panel & MDP4_PANEL_DSI_VIDEO) + mdp4_overlay0_done_dsi_video(dma); +#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 + } +#if defined(CONFIG_FB_MSM_WRITEBACK_MSM_PANEL) + if (isr & INTR_OVERLAY2_DONE) { + mdp4_stat.intr_overlay2++; + /* disable DTV interrupt */ + dma = &dma_wb_data; + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_OVERLAY2_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + spin_unlock(&mdp_spin_lock); + if (panel & MDP4_PANEL_WRITEBACK) + mdp4_overlay1_done_writeback(dma); + } +#endif +#endif /* OVERLAY */ + + 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; + mdp4_dma_p_done_lcdc(); + spin_unlock(&mdp_spin_lock); + } +#ifdef CONFIG_FB_MSM_OVERLAY +#ifdef CONFIG_FB_MSM_MIPI_DSI + else if (panel & MDP4_PANEL_DSI_VIDEO) { + /* 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; + mdp4_dma_p_done_dsi_video(dma); + spin_unlock(&mdp_spin_lock); + } else if (panel & MDP4_PANEL_DSI_CMD) { + mdp4_dma_p_done_dsi(dma); + } +#else + else { /* MDDI */ + mdp4_dma_p_done_mddi(); + mdp_pipe_ctrl(MDP_DMA2_BLOCK, + MDP_BLOCK_POWER_OFF, TRUE); + complete(&dma->comp); + } +#endif +#else + else { + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + spin_unlock(&mdp_spin_lock); + 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; + mdp4_dma_e_done_dtv(); + if (dma->waiting) { + dma->waiting = FALSE; + complete(&dma->comp); + } + spin_unlock(&mdp_spin_lock); + } + if (isr & INTR_DMA_P_HISTOGRAM) { + mdp4_stat.intr_histogram++; + ret = mdp_histogram_block2mgmt(MDP_BLOCK_DMA_P, &mgmt); + if (!ret) + mdp_histogram_handle_isr(mgmt); + } + if (isr & INTR_DMA_S_HISTOGRAM) { + mdp4_stat.intr_histogram++; + ret = mdp_histogram_block2mgmt(MDP_BLOCK_DMA_S, &mgmt); + if (!ret) + mdp_histogram_handle_isr(mgmt); + } + if (isr & INTR_VG1_HISTOGRAM) { + mdp4_stat.intr_histogram++; + ret = mdp_histogram_block2mgmt(MDP_BLOCK_VG_1, &mgmt); + if (!ret) + mdp_histogram_handle_isr(mgmt); + } + if (isr & INTR_VG2_HISTOGRAM) { + mdp4_stat.intr_histogram++; + ret = mdp_histogram_block2mgmt(MDP_BLOCK_VG_2, &mgmt); + if (!ret) + mdp_histogram_handle_isr(mgmt); + } + +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[] = { + 0x00000000, 0x20000000, +}; + +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); +} + +struct mdp_csc_cfg mdp_csc_convert[4] = { + { /*RGB2RGB*/ + 0, + { + 0x0200, 0x0000, 0x0000, + 0x0000, 0x0200, 0x0000, + 0x0000, 0x0000, 0x0200, + }, + { 0x0, 0x0, 0x0, }, + { 0x0, 0x0, 0x0, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + }, + { /*YUV2RGB*/ + 0, + { + 0x0254, 0x0000, 0x0331, + 0x0254, 0xff37, 0xfe60, + 0x0254, 0x0409, 0x0000, + }, + { 0xfff0, 0xff80, 0xff80, }, + { 0x0, 0x0, 0x0, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + }, + { /*RGB2YUV*/ + 0, + { + 0x0083, 0x0102, 0x0032, + 0x1fb5, 0x1f6c, 0x00e1, + 0x00e1, 0x1f45, 0x1fdc + }, + { 0x0, 0x0, 0x0, }, + { 0x0010, 0x0080, 0x0080, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + { 0x0010, 0x00eb, 0x0010, 0x00f0, 0x0010, 0x00f0, }, + }, + { /*YUV2YUV ???*/ + 0, + { + 0x0200, 0x0000, 0x0000, + 0x0000, 0x0200, 0x0000, + 0x0000, 0x0000, 0x0200, + }, + { 0x0, 0x0, 0x0, }, + { 0x0, 0x0, 0x0, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, }, + }, +}; + +struct mdp_csc_cfg csc_matrix[3] = { + { + (MDP_CSC_FLAG_YUV_OUT), + { + 0x0254, 0x0000, 0x0331, + 0x0254, 0xff37, 0xfe60, + 0x0254, 0x0409, 0x0000, + }, + { + 0xfff0, 0xff80, 0xff80, + }, + { + 0, 0, 0, + }, + { + 0, 0xff, 0, 0xff, 0, 0xff, + }, + { + 0, 0xff, 0, 0xff, 0, 0xff, + }, + }, + { + (MDP_CSC_FLAG_YUV_OUT), + { + 0x0254, 0x0000, 0x0331, + 0x0254, 0xff37, 0xfe60, + 0x0254, 0x0409, 0x0000, + }, + { + 0xfff0, 0xff80, 0xff80, + }, + { + 0, 0, 0, + }, + { + 0, 0xff, 0, 0xff, 0, 0xff, + }, + { + 0, 0xff, 0, 0xff, 0, 0xff, + }, + }, + { + (0), + { + 0x0200, 0x0000, 0x0000, + 0x0000, 0x0200, 0x0000, + 0x0000, 0x0000, 0x0200, + }, + { + 0x0, 0x0, 0x0, + }, + { + 0, 0, 0, + }, + { + 0, 0xff, 0, 0xff, 0, 0xff, + }, + { + 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[vp_num].csc_mv[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_matrix[vp_num].csc_pre_bv[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_matrix[vp_num].csc_post_bv[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_matrix[vp_num].csc_pre_lv[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_matrix[vp_num].csc_post_lv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_vg_csc_convert_setup(int vp_num) +{ + struct mdp_csc_cfg_data cfg; + + switch (vp_num) { + case 0: + cfg.block = MDP_BLOCK_VG_1; + break; + case 1: + cfg.block = MDP_BLOCK_VG_2; + break; + default: + pr_err("%s - invalid vp_num = %d", __func__, vp_num); + return; + } + cfg.csc_data = csc_matrix[vp_num]; + mdp4_csc_enable(&cfg); +} + +void mdp4_vg_csc_setup(int vp_num) +{ + /* yuv2rgb */ + mdp4_vg_csc_mv_setup(vp_num); + mdp4_vg_csc_pre_bv_setup(vp_num); + mdp4_vg_csc_post_bv_setup(vp_num); + mdp4_vg_csc_pre_lv_setup(vp_num); + mdp4_vg_csc_post_lv_setup(vp_num); + mdp4_vg_csc_convert_setup(vp_num); +} +void mdp4_vg_csc_update(struct mdp_csc *p) +{ + struct mdp4_overlay_pipe *pipe; + int vp_num; + + pipe = mdp4_overlay_ndx2pipe(p->id); + if (pipe == NULL) { + pr_err("%s: p->id = %d Error\n", __func__, p->id); + return; + } + + vp_num = pipe->pipe_num - OVERLAY_PIPE_VG1; + + if (vp_num == 0 || vp_num == 1) { + memcpy(csc_matrix[vp_num].csc_mv, p->csc_mv, sizeof(p->csc_mv)); + memcpy(csc_matrix[vp_num].csc_pre_bv, p->csc_pre_bv, + sizeof(p->csc_pre_bv)); + memcpy(csc_matrix[vp_num].csc_post_bv, p->csc_post_bv, + sizeof(p->csc_post_bv)); + memcpy(csc_matrix[vp_num].csc_pre_lv, p->csc_pre_lv, + sizeof(p->csc_pre_lv)); + memcpy(csc_matrix[vp_num].csc_post_lv, p->csc_post_lv, + sizeof(p->csc_post_lv)); + mdp4_vg_csc_setup(vp_num); + } +} +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_mixer_csc_mv_setup(uint32 mixer) +{ + uint32 *off; + int i; + + if (mixer == MDP4_MIXER1) + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2400); + else + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC2_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_mixer_csc_pre_bv_setup(uint32 mixer) +{ + uint32 *off; + int i; + + if (mixer == MDP4_MIXER1) + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2500); + else + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC2_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_mixer_csc_post_bv_setup(uint32 mixer) +{ + uint32 *off; + int i; + + if (mixer == MDP4_MIXER1) + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2580); + else + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC2_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_mixer_csc_pre_lv_setup(uint32 mixer) +{ + uint32 *off; + int i; + + if (mixer == MDP4_MIXER1) + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2600); + else + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC2_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_mixer_csc_post_lv_setup(uint32 mixer) +{ + uint32 *off; + int i; + + if (mixer == MDP4_MIXER1) + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2680); + else + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC2_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); +} + +void mdp4_mixer_csc_setup(uint32 mixer) +{ + if (mixer >= MDP4_MIXER1) { + /* rgb2yuv */ + mdp4_mixer_csc_mv_setup(mixer); + mdp4_mixer_csc_pre_bv_setup(mixer); + mdp4_mixer_csc_post_bv_setup(mixer); + mdp4_mixer_csc_pre_lv_setup(mixer); + mdp4_mixer_csc_post_lv_setup(mixer); + } +} + +#define DMA_P_BASE 0x90000 +void mdp4_dmap_csc_mv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + DMA_P_BASE + 0x3400); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 9; i++) { + outpdw(off, csc_matrix[2].csc_mv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_dmap_csc_pre_bv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + DMA_P_BASE + 0x3500); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 3; i++) { + outpdw(off, csc_matrix[2].csc_pre_bv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_dmap_csc_post_bv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + DMA_P_BASE + 0x3580); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 3; i++) { + outpdw(off, csc_matrix[2].csc_post_bv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_dmap_csc_pre_lv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + DMA_P_BASE + 0x3600); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 6; i++) { + outpdw(off, csc_matrix[2].csc_pre_lv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_dmap_csc_post_lv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + DMA_P_BASE + 0x3680); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 6; i++) { + outpdw(off, csc_matrix[2].csc_post_lv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_dmap_csc_setup(void) +{ + mdp4_dmap_csc_mv_setup(); + mdp4_dmap_csc_pre_bv_setup(); + mdp4_dmap_csc_post_bv_setup(); + mdp4_dmap_csc_pre_lv_setup(); + mdp4_dmap_csc_post_lv_setup(); +} + +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; +} + +static uint32_t mdp4_csc_block2base(uint32_t block) +{ + uint32_t base = 0x0; + switch (block) { + case MDP_BLOCK_OVERLAY_1: + base = 0x1A000; + break; + case MDP_BLOCK_OVERLAY_2: + base = (mdp_rev >= MDP_REV_44) ? 0x8A000 : 0x0; + break; + case MDP_BLOCK_VG_1: + base = 0x24000; + break; + case MDP_BLOCK_VG_2: + base = 0x34000; + break; + case MDP_BLOCK_DMA_P: + base = 0x93000; + break; + case MDP_BLOCK_DMA_S: + base = (mdp_rev >= MDP_REV_42) ? 0xA3000 : 0x0; + default: + break; + } + return base; +} + +int mdp4_csc_enable(struct mdp_csc_cfg_data *config) +{ + uint32_t output, base, temp, mask; + + switch (config->block) { + case MDP_BLOCK_DMA_P: + base = 0x90070; + output = (config->csc_data.flags << 3) & (0x08); + temp = (config->csc_data.flags << 10) & (0x1800); + output |= temp; + mask = 0x08 | 0x1800; + break; + case MDP_BLOCK_DMA_S: + base = 0xA0028; + output = (config->csc_data.flags << 3) & (0x08); + temp = (config->csc_data.flags << 10) & (0x1800); + output |= temp; + mask = 0x08 | 0x1800; + break; + case MDP_BLOCK_VG_1: + base = 0x20058; + output = (config->csc_data.flags << 11) & (0x800); + temp = (config->csc_data.flags << 8) & (0x600); + output |= temp; + mask = 0x800 | 0x600; + break; + case MDP_BLOCK_VG_2: + base = 0x30058; + output = (config->csc_data.flags << 11) & (0x800); + temp = (config->csc_data.flags << 8) & (0x600); + output |= temp; + mask = 0x800 | 0x600; + break; + case MDP_BLOCK_OVERLAY_1: + base = 0x18200; + output = config->csc_data.flags; + mask = 0x07; + break; + case MDP_BLOCK_OVERLAY_2: + base = 0x88200; + output = config->csc_data.flags; + mask = 0x07; + break; + default: + pr_err("%s - CSC block does not exist on MDP_BLOCK = %d\n", + __func__, config->block); + return -EINVAL; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + temp = inpdw(MDP_BASE + base) & ~mask; + output |= temp; + outpdw(MDP_BASE + base, output); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return 0; +} + +#define CSC_MV_OFF 0x400 +#define CSC_BV_OFF 0x500 +#define CSC_LV_OFF 0x600 +#define CSC_POST_OFF 0x80 + +void mdp4_csc_write(struct mdp_csc_cfg *data, uint32_t base) +{ + int i; + uint32_t *off; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + off = (uint32_t *) ((uint32_t) base + CSC_MV_OFF); + for (i = 0; i < 9; i++) { + outpdw(off, data->csc_mv[i]); + off++; + } + + off = (uint32_t *) ((uint32_t) base + CSC_BV_OFF); + for (i = 0; i < 3; i++) { + outpdw(off, data->csc_pre_bv[i]); + outpdw((uint32_t *)((uint32_t)off + CSC_POST_OFF), + data->csc_post_bv[i]); + off++; + } + + off = (uint32_t *) ((uint32_t) base + CSC_LV_OFF); + for (i = 0; i < 6; i++) { + outpdw(off, data->csc_pre_lv[i]); + outpdw((uint32_t *)((uint32_t)off + CSC_POST_OFF), + data->csc_post_lv[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +int mdp4_csc_config(struct mdp_csc_cfg_data *config) +{ + int ret = 0; + uint32_t base; + + base = mdp4_csc_block2base(config->block); + if (!base) { + pr_warn("%s: Block type %d isn't supported by CSC.\n", + __func__, config->block); + return -EINVAL; + } + + mdp4_csc_write(&config->csc_data, (uint32_t) (MDP_BASE + base)); + + ret = mdp4_csc_enable(config); + + return ret; +} + +void mdp4_init_writeback_buf(struct msm_fb_data_type *mfd, u32 mix_num) +{ + struct mdp_buf_type *buf; + + if (mix_num == MDP4_MIXER0) + buf = mfd->ov0_wb_buf; + else + buf = mfd->ov1_wb_buf; + + buf->ihdl = NULL; + buf->phys_addr = 0; +} + +u32 mdp4_allocate_writeback_buf(struct msm_fb_data_type *mfd, u32 mix_num) +{ + struct mdp_buf_type *buf; + ion_phys_addr_t addr; + size_t buffer_size; + unsigned long len; + + if (mix_num == MDP4_MIXER0) + buf = mfd->ov0_wb_buf; + else + buf = mfd->ov1_wb_buf; + + if (buf->phys_addr || !IS_ERR_OR_NULL(buf->ihdl)) + return 0; + + if (!buf->size) { + pr_err("%s:%d In valid size\n", __func__, __LINE__); + return -EINVAL; + } + + buffer_size = roundup(mfd->panel_info.xres * \ + mfd->panel_info.yres * 3 * 2, SZ_4K); + + if (!IS_ERR_OR_NULL(mfd->iclient)) { + pr_info("%s:%d ion based allocation mfd->mem_hid 0x%x\n", + __func__, __LINE__, mfd->mem_hid); + buf->ihdl = ion_alloc(mfd->iclient, buffer_size, SZ_4K, + mfd->mem_hid); + if (!IS_ERR_OR_NULL(buf->ihdl)) { + if (ion_map_iommu(mfd->iclient, buf->ihdl, + DISPLAY_DOMAIN, GEN_POOL, SZ_4K, 0, &addr, + &len, 0, 0)) { + pr_err("ion_map_iommu() failed\n"); + return -ENOMEM; + } + } else { + pr_err("%s:%d: ion_alloc failed\n", __func__, + __LINE__); + return -ENOMEM; + } + } else { + addr = allocate_contiguous_memory_nomap(buffer_size, + mfd->mem_hid, 4); + } + if (addr) { + pr_info("allocating %d bytes at %x for mdp writeback\n", + buffer_size, (u32) addr); + buf->phys_addr = addr; + return 0; + } else { + pr_err("%s cannot allocate memory for mdp writeback!\n", + __func__); + return -ENOMEM; + } +} + +void mdp4_free_writeback_buf(struct msm_fb_data_type *mfd, u32 mix_num) +{ + struct mdp_buf_type *buf; + + if (mix_num == MDP4_MIXER0) + buf = mfd->ov0_wb_buf; + else + buf = mfd->ov1_wb_buf; + + if (!IS_ERR_OR_NULL(mfd->iclient)) { + if (!IS_ERR_OR_NULL(buf->ihdl)) { + ion_unmap_iommu(mfd->iclient, buf->ihdl, + DISPLAY_DOMAIN, GEN_POOL); + ion_free(mfd->iclient, buf->ihdl); + pr_debug("%s:%d free writeback imem\n", __func__, + __LINE__); + buf->ihdl = NULL; + } + } else { + if (buf->phys_addr) { + free_contiguous_memory_by_paddr(buf->phys_addr); + pr_debug("%s:%d free writeback pmem\n", __func__, + __LINE__); + } + } + buf->phys_addr = 0; +} + +static int mdp4_update_pcc_regs(uint32_t offset, + struct mdp_pcc_cfg_data *cfg_ptr) +{ + int ret = -1; + + if (offset && cfg_ptr) { + + outpdw(offset, cfg_ptr->r.c); + outpdw(offset + 0x30, cfg_ptr->g.c); + outpdw(offset + 0x60, cfg_ptr->b.c); + offset += 4; + + outpdw(offset, cfg_ptr->r.r); + outpdw(offset + 0x30, cfg_ptr->g.r); + outpdw(offset + 0x60, cfg_ptr->b.r); + offset += 4; + + outpdw(offset, cfg_ptr->r.g); + outpdw(offset + 0x30, cfg_ptr->g.g); + outpdw(offset + 0x60, cfg_ptr->b.g); + offset += 4; + + outpdw(offset, cfg_ptr->r.b); + outpdw(offset + 0x30, cfg_ptr->g.b); + outpdw(offset + 0x60, cfg_ptr->b.b); + offset += 4; + + outpdw(offset, cfg_ptr->r.rr); + outpdw(offset + 0x30, cfg_ptr->g.rr); + outpdw(offset + 0x60, cfg_ptr->b.rr); + offset += 4; + + outpdw(offset, cfg_ptr->r.gg); + outpdw(offset + 0x30, cfg_ptr->g.gg); + outpdw(offset + 0x60, cfg_ptr->b.gg); + offset += 4; + + outpdw(offset, cfg_ptr->r.bb); + outpdw(offset + 0x30, cfg_ptr->g.bb); + outpdw(offset + 0x60, cfg_ptr->b.bb); + offset += 4; + + outpdw(offset, cfg_ptr->r.rg); + outpdw(offset + 0x30, cfg_ptr->g.rg); + outpdw(offset + 0x60, cfg_ptr->b.rg); + offset += 4; + + outpdw(offset, cfg_ptr->r.gb); + outpdw(offset + 0x30, cfg_ptr->g.gb); + outpdw(offset + 0x60, cfg_ptr->b.gb); + offset += 4; + + outpdw(offset, cfg_ptr->r.rb); + outpdw(offset + 0x30, cfg_ptr->g.rb); + outpdw(offset + 0x60, cfg_ptr->b.rb); + offset += 4; + + outpdw(offset, cfg_ptr->r.rgb_0); + outpdw(offset + 0x30, cfg_ptr->g.rgb_0); + outpdw(offset + 0x60, cfg_ptr->b.rgb_0); + offset += 4; + + outpdw(offset, cfg_ptr->r.rgb_1); + outpdw(offset + 0x30, cfg_ptr->g.rgb_1); + outpdw(offset + 0x60, cfg_ptr->b.rgb_1); + + ret = 0; + } + + return ret; +} + +static int mdp4_read_pcc_regs(uint32_t offset, + struct mdp_pcc_cfg_data *cfg_ptr) +{ + int ret = -1; + + if (offset && cfg_ptr) { + cfg_ptr->r.c = inpdw(offset); + cfg_ptr->g.c = inpdw(offset + 0x30); + cfg_ptr->b.c = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.r = inpdw(offset); + cfg_ptr->g.r = inpdw(offset + 0x30); + cfg_ptr->b.r = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.g = inpdw(offset); + cfg_ptr->g.g = inpdw(offset + 0x30); + cfg_ptr->b.g = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.b = inpdw(offset); + cfg_ptr->g.b = inpdw(offset + 0x30); + cfg_ptr->b.b = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.rr = inpdw(offset); + cfg_ptr->g.rr = inpdw(offset + 0x30); + cfg_ptr->b.rr = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.gg = inpdw(offset); + cfg_ptr->g.gg = inpdw(offset + 0x30); + cfg_ptr->b.gg = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.bb = inpdw(offset); + cfg_ptr->g.bb = inpdw(offset + 0x30); + cfg_ptr->b.bb = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.rg = inpdw(offset); + cfg_ptr->g.rg = inpdw(offset + 0x30); + cfg_ptr->b.rg = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.gb = inpdw(offset); + cfg_ptr->g.gb = inpdw(offset + 0x30); + cfg_ptr->b.gb = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.rb = inpdw(offset); + cfg_ptr->g.rb = inpdw(offset + 0x30); + cfg_ptr->b.rb = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.rgb_0 = inpdw(offset); + cfg_ptr->g.rgb_0 = inpdw(offset + 0x30); + cfg_ptr->b.rgb_0 = inpdw(offset + 0x60); + offset += 4; + + cfg_ptr->r.rgb_1 = inpdw(offset); + cfg_ptr->g.rgb_1 = inpdw(offset + 0x30); + cfg_ptr->b.rgb_1 = inpdw(offset + 0x60); + + ret = 0; + } + + return ret; +} + + +#define MDP_PCC_OFFSET 0xA000 +#define MDP_DMA_GC_OFFSET 0x8800 +#define MDP_LM_GC_OFFSET 0x4800 + +#define MDP_DMA_P_OP_MODE_OFFSET 0x70 +#define MDP_DMA_S_OP_MODE_OFFSET 0x28 +#define MDP_LM_OP_MODE_OFFSET 0x14 + +#define DMA_PCC_R2_OFFSET 0x100 + +#define MDP_GC_COLOR_OFFSET 0x100 +#define MDP_GC_PARMS_OFFSET 0x80 + +#define MDP_AR_GC_MAX_STAGES 16 + +static uint32_t mdp_pp_block2pcc(uint32_t block) +{ + uint32_t valid = 0; + + switch (block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + valid = (mdp_rev >= MDP_REV_42) ? 1 : 0; + break; + + default: + break; + } + + return valid; +} + +int mdp4_pcc_cfg(struct mdp_pcc_cfg_data *cfg_ptr) +{ + int ret = -1; + uint32_t pcc_offset = 0, mdp_cfg_offset = 0; + uint32_t mdp_dma_op_mode = 0; + uint32_t blockbase; + + if (!mdp_pp_block2pcc(cfg_ptr->block)) + return ret; + + blockbase = mdp_block2base(cfg_ptr->block); + if (!blockbase) + return ret; + + blockbase += (uint32_t) MDP_BASE; + + switch (cfg_ptr->block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + pcc_offset = blockbase + MDP_PCC_OFFSET; + mdp_cfg_offset = blockbase; + mdp_dma_op_mode = blockbase + + (MDP_BLOCK_DMA_P == cfg_ptr->block ? + MDP_DMA_P_OP_MODE_OFFSET + : MDP_DMA_S_OP_MODE_OFFSET); + break; + + default: + break; + } + + if (0x8 & cfg_ptr->ops) + pcc_offset += DMA_PCC_R2_OFFSET; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + switch ((0x6 & cfg_ptr->ops)>>1) { + case 0x1: + ret = mdp4_read_pcc_regs(pcc_offset, cfg_ptr); + break; + + case 0x2: + ret = mdp4_update_pcc_regs(pcc_offset, cfg_ptr); + break; + + default: + break; + } + + if (0x8 & cfg_ptr->ops) + outpdw(mdp_dma_op_mode, + ((inpdw(mdp_dma_op_mode) & ~(0x1<<10)) | + ((0x8 & cfg_ptr->ops)<<10))); + + outpdw(mdp_cfg_offset, + ((inpdw(mdp_cfg_offset) & ~(0x1<<29)) | + ((cfg_ptr->ops & 0x1)<<29))); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +static uint32_t mdp_pp_block2argc(uint32_t block) +{ + uint32_t valid = 0; + + switch (block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + case MDP_BLOCK_OVERLAY_0: + case MDP_BLOCK_OVERLAY_1: + valid = (mdp_rev >= MDP_REV_42) ? 1 : 0; + break; + + case MDP_BLOCK_OVERLAY_2: + valid = (mdp_rev >= MDP_REV_44) ? 1 : 0; + break; + + default: + break; + } + + return valid; +} + +static int update_ar_gc_lut(uint32_t *offset, struct mdp_pgc_lut_data *lut_data) +{ + int count = 0; + + uint32_t *c0_offset = offset; + uint32_t *c0_params_offset = (uint32_t *)((uint32_t)c0_offset + + MDP_GC_PARMS_OFFSET); + + uint32_t *c1_offset = (uint32_t *)((uint32_t)offset + + MDP_GC_COLOR_OFFSET); + + uint32_t *c1_params_offset = (uint32_t *)((uint32_t)c1_offset + + MDP_GC_PARMS_OFFSET); + + uint32_t *c2_offset = (uint32_t *)((uint32_t)offset + + 2*MDP_GC_COLOR_OFFSET); + + uint32_t *c2_params_offset = (uint32_t *)((uint32_t)c2_offset + +MDP_GC_PARMS_OFFSET); + + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (count = 0; count < MDP_AR_GC_MAX_STAGES; count++) { + if (count < lut_data->num_r_stages) { + outpdw(c0_offset+count, + ((0xfff & lut_data->r_data[count].x_start) + | 0x10000)); + + outpdw(c0_params_offset+count, + ((0x7fff & lut_data->r_data[count].slope) + | ((0xffff + & lut_data->r_data[count].offset) + << 16))); + } else + outpdw(c0_offset+count, 0); + + if (count < lut_data->num_b_stages) { + outpdw(c1_offset+count, + ((0xfff & lut_data->b_data[count].x_start) + | 0x10000)); + + outpdw(c1_params_offset+count, + ((0x7fff & lut_data->b_data[count].slope) + | ((0xffff + & lut_data->b_data[count].offset) + << 16))); + } else + outpdw(c1_offset+count, 0); + + if (count < lut_data->num_g_stages) { + outpdw(c2_offset+count, + ((0xfff & lut_data->g_data[count].x_start) + | 0x10000)); + + outpdw(c2_params_offset+count, + ((0x7fff & lut_data->g_data[count].slope) + | ((0xffff + & lut_data->g_data[count].offset) + << 16))); + } else + outpdw(c2_offset+count, 0); + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return 0; +} + +static int mdp4_argc_process_write_req(uint32_t *offset, + struct mdp_pgc_lut_data *pgc_ptr) +{ + int ret = -1; + struct mdp_ar_gc_lut_data r[MDP_AR_GC_MAX_STAGES]; + struct mdp_ar_gc_lut_data g[MDP_AR_GC_MAX_STAGES]; + struct mdp_ar_gc_lut_data b[MDP_AR_GC_MAX_STAGES]; + + ret = copy_from_user(&r[0], pgc_ptr->r_data, + pgc_ptr->num_r_stages * sizeof(struct mdp_ar_gc_lut_data)); + + if (!ret) { + ret = copy_from_user(&g[0], + pgc_ptr->g_data, + pgc_ptr->num_g_stages + * sizeof(struct mdp_ar_gc_lut_data)); + if (!ret) + ret = copy_from_user(&b[0], + pgc_ptr->b_data, + pgc_ptr->num_b_stages + * sizeof(struct mdp_ar_gc_lut_data)); + } + + if (ret) + return ret; + + pgc_ptr->r_data = &r[0]; + pgc_ptr->g_data = &g[0]; + pgc_ptr->b_data = &b[0]; + + ret = update_ar_gc_lut(offset, pgc_ptr); + return ret; +} + +int mdp4_argc_cfg(struct mdp_pgc_lut_data *pgc_ptr) +{ + int ret = -1; + uint32_t *offset = 0, *pgc_enable_offset = 0, lshift_bits = 0; + uint32_t blockbase; + + if (!mdp_pp_block2argc(pgc_ptr->block)) + return ret; + + blockbase = mdp_block2base(pgc_ptr->block); + if (!blockbase) + return ret; + + blockbase += (uint32_t) MDP_BASE; + ret = 0; + + switch (pgc_ptr->block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + offset = (uint32_t *)(blockbase + MDP_DMA_GC_OFFSET); + pgc_enable_offset = (uint32_t *) blockbase; + lshift_bits = 28; + break; + + case MDP_BLOCK_OVERLAY_0: + case MDP_BLOCK_OVERLAY_1: + case MDP_BLOCK_OVERLAY_2: + offset = (uint32_t *)(blockbase + MDP_LM_GC_OFFSET); + pgc_enable_offset = (uint32_t *)(blockbase + + MDP_LM_OP_MODE_OFFSET); + lshift_bits = 2; + break; + + default: + ret = -1; + break; + } + + if (!ret) { + + switch ((0x6 & pgc_ptr->flags)>>1) { + case 0x1: + ret = -ENOTTY; + break; + + case 0x2: + ret = mdp4_argc_process_write_req(offset, pgc_ptr); + break; + + default: + break; + } + + if (!ret) { + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(pgc_enable_offset, (inpdw(pgc_enable_offset) & + ~(0x1<flags) << lshift_bits)); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, + FALSE); + } + } + + return ret; +} + +static uint32_t mdp4_pp_block2igc(uint32_t block) +{ + uint32_t valid = 0; + switch (block) { + case MDP_BLOCK_VG_1: + valid = 0x1; + break; + case MDP_BLOCK_VG_2: + valid = 0x1; + break; + case MDP_BLOCK_RGB_1: + valid = 0x1; + break; + case MDP_BLOCK_RGB_2: + valid = 0x1; + break; + case MDP_BLOCK_DMA_P: + valid = (mdp_rev >= MDP_REV_40) ? 1 : 0; + break; + case MDP_BLOCK_DMA_S: + valid = (mdp_rev >= MDP_REV_40) ? 1 : 0; + break; + default: + break; + } + return valid; +} + +static int mdp4_igc_lut_write(struct mdp_igc_lut_data *cfg, uint32_t en_off, + uint32_t lut_off) +{ + int i; + uint32_t base, *off_low, *off_high; + uint32_t low[cfg->len]; + uint32_t high[cfg->len]; + + base = mdp_block2base(cfg->block); + + if (cfg->len != 256) + return -EINVAL; + + off_low = (uint32_t *)(MDP_BASE + base + lut_off); + off_high = (uint32_t *)(MDP_BASE + base + lut_off + 0x800); + if (copy_from_user(&low, cfg->c0_c1_data, cfg->len * sizeof(uint32_t))) + return -EFAULT; + if (copy_from_user(&high, cfg->c2_data, cfg->len * sizeof(uint32_t))) + return -EFAULT; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < cfg->len; i++) { + MDP_OUTP(off_low++, low[i]); + /*low address write should occur before high address write*/ + wmb(); + MDP_OUTP(off_high++, high[i]); + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return 0; +} + +static int mdp4_igc_lut_ctrl(struct mdp_igc_lut_data *cfg) +{ + uint32_t mask, out; + uint32_t base = mdp_block2base(cfg->block); + int8_t shift = 0; + + switch (cfg->block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + base = base; + shift = 30; + break; + case MDP_BLOCK_VG_1: + case MDP_BLOCK_VG_2: + case MDP_BLOCK_RGB_1: + case MDP_BLOCK_RGB_2: + base += 0x58; + shift = 16; + break; + default: + return -EINVAL; + + } + out = 1<ops & 0x1)<block) { + case MDP_BLOCK_DMA_P: + case MDP_BLOCK_DMA_S: + ret = mdp4_igc_lut_write(cfg, 0x00, 0x9000); + break; + case MDP_BLOCK_VG_1: + case MDP_BLOCK_VG_2: + case MDP_BLOCK_RGB_1: + case MDP_BLOCK_RGB_2: + ret = mdp4_igc_lut_write(cfg, 0x58, 0x5000); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +int mdp4_igc_lut_config(struct mdp_igc_lut_data *cfg) +{ + int ret = 0; + + if (!mdp4_pp_block2igc(cfg->block)) { + ret = -ENOTTY; + goto error; + } + + switch ((cfg->ops & 0x6) >> 1) { + case 0x1: + pr_info("%s: IGC LUT read not supported\n", __func__); + break; + case 0x2: + ret = mdp4_igc_lut_write_cfg(cfg); + if (ret) + goto error; + break; + default: + break; + } + + ret = mdp4_igc_lut_ctrl(cfg); + +error: + return ret; +} + +#define QSEED_TABLE_1_COUNT 2 +#define QSEED_TABLE_2_COUNT 1024 + +static uint32_t mdp4_pp_block2qseed(uint32_t block) +{ + uint32_t valid = 0; + switch (block) { + case MDP_BLOCK_VG_1: + case MDP_BLOCK_VG_2: + valid = 0x1; + break; + default: + break; + } + return valid; +} + +static int mdp4_qseed_write_cfg(struct mdp_qseed_cfg_data *cfg) +{ + int i, ret = 0; + uint32_t base = (uint32_t) (MDP_BASE + mdp_block2base(cfg->block)); + uint32_t *values; + + if ((cfg->table_num != 1) && (cfg->table_num != 2)) { + ret = -ENOTTY; + goto error; + } + + if (((cfg->table_num == 1) && (cfg->len != QSEED_TABLE_1_COUNT)) || + ((cfg->table_num == 2) && (cfg->len != QSEED_TABLE_2_COUNT))) { + ret = -EINVAL; + goto error; + } + + values = kmalloc(cfg->len * sizeof(uint32_t), GFP_KERNEL); + if (!values) { + ret = -ENOMEM; + goto error; + } + + ret = copy_from_user(values, cfg->data, sizeof(uint32_t) * cfg->len); + + base += (cfg->table_num == 1) ? MDP4_QSEED_TABLE1_OFF : + MDP4_QSEED_TABLE2_OFF; + for (i = 0; i < cfg->len; i++) { + MDP_OUTP(base , values[i]); + base += sizeof(uint32_t); + } + + kfree(values); +error: + return ret; +} + +int mdp4_qseed_cfg(struct mdp_qseed_cfg_data *cfg) +{ + int ret = 0; + + if (!mdp4_pp_block2qseed(cfg->block)) { + ret = -ENOTTY; + goto error; + } + + if (cfg->table_num != 1) { + ret = -ENOTTY; + pr_info("%s: Only QSEED table1 supported.\n", __func__); + goto error; + } + + switch ((cfg->ops & 0x6) >> 1) { + case 0x1: + pr_info("%s: QSEED read not supported\n", __func__); + ret = -ENOTTY; + break; + case 0x2: + ret = mdp4_qseed_write_cfg(cfg); + if (ret) + goto error; + break; + default: + break; + } + +error: + return ret; +} diff --git a/drivers/video/msm/mdp4_wfd_writeback.c b/drivers/video/msm/mdp4_wfd_writeback.c new file mode 100644 index 0000000000000000000000000000000000000000..a8fdcc035ee24f75ce4584daeb71ac56e547420c --- /dev/null +++ b/drivers/video/msm/mdp4_wfd_writeback.c @@ -0,0 +1,102 @@ +/* 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 "mdp4_wfd_writeback_util.h" +#include "msm_fb.h" + +static int writeback_on(struct platform_device *pdev) +{ + return 0; +} +static int writeback_off(struct platform_device *pdev) +{ + return 0; +} +static int writeback_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 = 0; + + WRITEBACK_MSG_ERR("Inside writeback_probe\n"); + mfd = platform_get_drvdata(pdev); + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + 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; + + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + pr_err("writeback_probe: " + "platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + pdata = (struct msm_fb_panel_data *)mdp_dev->dev.platform_data; + pdata->on = writeback_on; + pdata->off = writeback_off; + pdata->next = pdev; + + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; + + mfd->fb_imgType = MDP_RGB_565; + + platform_set_drvdata(mdp_dev, mfd); + + rc = platform_device_add(mdp_dev); + if (rc) { + WRITEBACK_MSG_ERR("failed to add device"); + platform_device_put(mdp_dev); + return rc; + } + return rc; +} + +static struct platform_driver writeback_driver = { + .probe = writeback_probe, + .driver = { + .name = "writeback", + }, +}; + +static int __init writeback_driver_init(void) +{ + int rc = 0; + WRITEBACK_MSG_ERR("Inside writeback_driver_init\n"); + rc = platform_driver_register(&writeback_driver); + return rc; +} + +module_init(writeback_driver_init); diff --git a/drivers/video/msm/mdp4_wfd_writeback_panel.c b/drivers/video/msm/mdp4_wfd_writeback_panel.c new file mode 100644 index 0000000000000000000000000000000000000000..40ffb65c9a7dd8200b61abef032f0da9a48beef4 --- /dev/null +++ b/drivers/video/msm/mdp4_wfd_writeback_panel.c @@ -0,0 +1,83 @@ +/* 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 "mdp4_wfd_writeback_util.h" +#include "msm_fb.h" + +static int __devinit writeback_panel_probe(struct platform_device *pdev) +{ + int rc = 0; + if (pdev->id == 0) + return 0; + + if (!msm_fb_add_device(pdev)) { + WRITEBACK_MSG_ERR("Failed to add fd device\n"); + rc = -ENOMEM; + } + return rc; +} +static struct msm_fb_panel_data writeback_msm_panel_data = { + .panel_info = { + .type = WRITEBACK_PANEL, + .xres = 1280, + .yres = 720, + .pdest = DISPLAY_3, + .wait_cycle = 0, + .bpp = 24, + .fb_num = 1, + .clk_rate = 74250000, + }, +}; + +static struct platform_device writeback_panel_device = { + .name = "writeback_panel", + .id = 1, + .dev.platform_data = &writeback_msm_panel_data, +}; +static struct platform_driver writeback_panel_driver = { + .probe = writeback_panel_probe, + .driver = { + .name = "writeback_panel" + } +}; + +static int __init writeback_panel_init(void) +{ + int rc = 0; + rc = platform_driver_register(&writeback_panel_driver); + if (rc) { + WRITEBACK_MSG_ERR("Failed to register platform driver\n"); + goto fail_driver_registration; + } + rc = platform_device_register(&writeback_panel_device); + if (rc) { + WRITEBACK_MSG_ERR("Failed to register " + "writeback_panel_device\n"); + goto fail_device_registration; + } + return rc; +fail_device_registration: + platform_driver_unregister(&writeback_panel_driver); +fail_driver_registration: + return rc; +} + +module_init(writeback_panel_init); diff --git a/drivers/video/msm/mdp4_wfd_writeback_util.h b/drivers/video/msm/mdp4_wfd_writeback_util.h new file mode 100644 index 0000000000000000000000000000000000000000..2d627133e9740d8fae75a462e64b9836a9dfb4ee --- /dev/null +++ b/drivers/video/msm/mdp4_wfd_writeback_util.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 _WRITEBACK_UTIL_H_ +#define _WRITEBACK_UTIL_H_ + +#define DEBUG + +#ifdef DEBUG + #define WRITEBACK_MSG_INFO(fmt...) pr_info(fmt) + #define WRITEBACK_MSG_WARN(fmt...) pr_warning(fmt) +#else + #define WRITEBACK_MSG_INFO(fmt...) + #define WRITEBACK_MSG_WARN(fmt...) +#endif + #define WRITEBACK_MSG_ERR(fmt...) pr_err(fmt) + #define WRITEBACK_MSG_CRIT(fmt...) pr_crit(fmt) +#endif diff --git a/drivers/video/msm/mdp_csc_table.h b/drivers/video/msm/mdp_csc_table.h index d1cde30ead52f9291c42df0e1cf21a638e74924a..a0f72c0ebd4f7cfbc8e7a9f132d8bd7e7a793cee 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 0000000000000000000000000000000000000000..f8c08e36066e651f5da5fa8e00bb41f3cc2cc558 --- /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 0000000000000000000000000000000000000000..7defd8207d6e87c6dc17da4609b849b850832a8e --- /dev/null +++ b/drivers/video/msm/mdp_debugfs.c @@ -0,0 +1,1392 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; + + + if (*ppos) + return 0; /* the end */ + + bp = debug_buf; + dlen = sizeof(debug_buf); + + len = snprintf(bp, dlen, "\nmdp:\n"); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "int_total: %08lu\t", + mdp4_stat.intr_tot); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "int_overlay0: %08lu\t", + mdp4_stat.intr_overlay0); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_overlay1: %08lu\n", + mdp4_stat.intr_overlay1); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_overlay1: %08lu\n", + mdp4_stat.intr_overlay2); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "int_dmap: %08lu\t", + mdp4_stat.intr_dma_p); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_dmas: %08lu\t", + mdp4_stat.intr_dma_s); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_dmae: %08lu\n", + mdp4_stat.intr_dma_e); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "primary: vsync: %08lu\t", + mdp4_stat.intr_vsync_p); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "underrun: %08lu\n", + mdp4_stat.intr_underrun_p); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "external: vsync: %08lu\t", + mdp4_stat.intr_vsync_e); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "underrun: %08lu\n", + mdp4_stat.intr_underrun_e); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "histogram: %08lu\t", + mdp4_stat.intr_histogram); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "read_ptr: %08lu\n\n", + mdp4_stat.intr_rd_ptr); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "dsi:\n"); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_total: %08lu\tmdp_start: %08lu\n", + mdp4_stat.intr_dsi, mdp4_stat.dsi_mdp_start); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_cmd: %08lu\t", + mdp4_stat.intr_dsi_cmd); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "int_mdp: %08lu\t", + mdp4_stat.intr_dsi_mdp); + + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "int_err: %08lu\n", + mdp4_stat.intr_dsi_err); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "clk_on : %08lu\t", + mdp4_stat.dsi_clk_on); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "clk_off: %08lu\n\n", + mdp4_stat.dsi_clk_off); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "kickoff:\n"); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "overlay0: %08lu\t", + mdp4_stat.kickoff_ov0); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "dmap: %08lu\t", + mdp4_stat.kickoff_dmap); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "dmas: %08lu\n", + mdp4_stat.kickoff_dmas); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay1: %08lu\t", + mdp4_stat.kickoff_ov1); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "dmae: %08lu\n\n", + mdp4_stat.kickoff_dmae); + + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "overlay0_play:\n"); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "set: %08lu\t", + mdp4_stat.overlay_set[0]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "unset: %08lu\t", + mdp4_stat.overlay_unset[0]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "play: %08lu\n", + mdp4_stat.overlay_play[0]); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "overlay1_play:\n"); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "set: %08lu\t", + mdp4_stat.overlay_set[1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "unset: %08lu\t", + mdp4_stat.overlay_unset[1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "play: %08lu\n\n", + mdp4_stat.overlay_play[1]); + + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "frame_push:\n"); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "rgb1: %08lu\t\t", + mdp4_stat.pipe[OVERLAY_PIPE_RGB1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "rgb2: %08lu\n", + mdp4_stat.pipe[OVERLAY_PIPE_RGB2]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "vg1: %08lu\t\t", + mdp4_stat.pipe[OVERLAY_PIPE_VG1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "vg2: %08lu\n", + mdp4_stat.pipe[OVERLAY_PIPE_VG2]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_mixer: %08lu\t", 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\t", mdp4_stat.err_scale); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_format: %08lu\n", mdp4_stat.err_format); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_play: %08lu\t", mdp4_stat.err_play); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_stage: %08lu\n", mdp4_stat.err_stage); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_underflow: %08lu\n\n", + mdp4_stat.err_underflow); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "writeback:\n"); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "dsi_cmd: %08lu\t", + mdp4_stat.blt_dsi_cmd); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "dsi_video: %08lu\n", + mdp4_stat.blt_dsi_video); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "lcdc: %08lu\t", + mdp4_stat.blt_lcdc); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "dtv: %08lu\t", + mdp4_stat.blt_dtv); + bp += len; + dlen -= len; + + len = snprintf(bp, dlen, "mddi: %08lu\n\n", + mdp4_stat.blt_mddi); + 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 0000000000000000000000000000000000000000..2ba2c859cfd6910f4f2f342e8c694fc9e009c68d --- /dev/null +++ b/drivers/video/msm/mdp_dma.c @@ -0,0 +1,612 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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_turn_on_clks(); +#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 +{ + unsigned long flag; + + 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); + + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_DMA2_TERM); + mfd->dma->busy = TRUE; + INIT_COMPLETION(mfd->dma->comp); + + spin_unlock_irqrestore(&mdp_spin_lock, flag); + /* 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; + struct fb_info *fbi = mfd->fbi; + MDPIBUF *iBuf; + int bpp = info->var.bits_per_pixel / 8; + + down(&mfd->sem); + + iBuf = &mfd->ibuf; + + if (mfd->display_iova) + iBuf->buf = (uint8 *)mfd->display_iova; + else + iBuf->buf = (uint8 *) info->fix.smem_start; + + iBuf->buf += calc_fb_offset(mfd, fbi, bpp); + + 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 0000000000000000000000000000000000000000..1ba5b8d816e83ef76e625e78fd4d9a4da8fbd942 --- /dev/null +++ b/drivers/video/msm/mdp_dma_dsi_video.c @@ -0,0 +1,275 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; + uint32_t mask, curr; + + 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 += calc_fb_offset(mfd, fbi, bpp); + + 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 */ + curr = inpdw(MDP_BASE + DMA_P_BASE); + mask = 0x0FFFFFFF; + dma2_cfg_reg = (dma2_cfg_reg & mask) | (curr & ~mask); + 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 += calc_fb_offset(mfd, fbi, bpp); + + /* 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 0000000000000000000000000000000000000000..c418e9c7489ad96c1cbdbcd196b434ae7abc453f --- /dev/null +++ b/drivers/video/msm/mdp_dma_lcdc.c @@ -0,0 +1,377 @@ +/* Copyright (c) 2008-2009, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; + uint32_t mask, curr; + + 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 += calc_fb_offset(mfd, fbi, bpp); + + 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 */ + curr = inpdw(MDP_BASE + DMA_P_BASE); + mask = 0x0FFFFFFF; + dma2_cfg_reg = (dma2_cfg_reg & mask) | (curr & ~mask); + 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 + + down(&mfd->dma->mutex); + /* 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); + up(&mfd->dma->mutex); + + /* 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; + + down(&mfd->dma->mutex); + /* 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 += calc_fb_offset(mfd, fbi, bpp); + + 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); + up(&mfd->dma->mutex); +} diff --git a/drivers/video/msm/mdp_dma_s.c b/drivers/video/msm/mdp_dma_s.c new file mode 100644 index 0000000000000000000000000000000000000000..22d79be61a7c675bb371946dee7515a6b6af415d --- /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 0000000000000000000000000000000000000000..b578ba24b7c7363866dcaf157b60c605cc80a3b1 --- /dev/null +++ b/drivers/video/msm/mdp_dma_tv.c @@ -0,0 +1,140 @@ +/* Copyright (c) 2008-2009, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 += calc_fb_offset(mfd, fbi, bpp); + + /* 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 += calc_fb_offset(mfd, fbi, bpp); + + 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 d80477415caaa3bdd401814c511a3359bb98bd97..f35a757d4731eacc6c6e101dbfb395cbd0ab73a0 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 0000000000000000000000000000000000000000..a642c9bcf71889bfce1b66a344fda4e80ec946f5 --- /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 */ + clk_set_rate(mdp->clk, 122880000); /* 122.88 Mhz */ + 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 0000000000000000000000000000000000000000..ff3ad41f5f4c00f847dd1d390d0646fbdd859696 --- /dev/null +++ b/drivers/video/msm/mdp_hw_init.c @@ -0,0 +1,714 @@ +/* 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); +#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 0000000000000000000000000000000000000000..62b0975c113d21c1faf96f57d88533c6a56bfddb --- /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_unprepare(lcdc->pad_pclk); + clk_disable_unprepare(lcdc->pclk); + clk_disable_unprepare(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_prepare_enable(lcdc->mdp_clk); + clk_prepare_enable(lcdc->pclk); + clk_prepare_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_prepare_enable(lcdc->mdp_clk); + clk_prepare_enable(lcdc->pclk); + clk_prepare_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 2b6564e8bfeaa37e7f785904af2604ac847b4493..4009ffc7844ff98100b0124e1fa35ba7f7409582 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, 2012 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, @@ -72,660 +52,1635 @@ static uint32_t bytes_per_pixel[] = { [MDP_RGBX_8888] = 4, [MDP_Y_CBCR_H2V1] = 1, [MDP_Y_CBCR_H2V2] = 1, + [MDP_Y_CBCR_H2V2_ADRENO] = 1, [MDP_Y_CRCB_H2V1] = 1, [MDP_Y_CRCB_H2V2] = 1, - [MDP_YCRYCB_H2V1] = 2 + [MDP_YCRYCB_H2V1] = 2, + [MDP_BGR_565] = 2 }; -static uint32_t dst_op_chroma[] = { - PPP_ARRAY1(CHROMA_SAMP, DST) -}; +extern uint32 mdp_plv[]; +extern struct semaphore mdp_ppp_mutex; -static uint32_t src_op_chroma[] = { - PPP_ARRAY1(CHROMA_SAMP, SRC) -}; - -static uint32_t bg_op_chroma[] = { - PPP_ARRAY1(CHROMA_SAMP, BG) -}; +int mdp_get_bytes_per_pixel(uint32_t format, + struct msm_fb_data_type *mfd) +{ + 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 rotate_dst_addr_x(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) { - 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; + 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 (_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; + } + /* + * Color Conversion + * reorder input colors + */ + temp = comp_C2; + comp_C2 = comp_C1; + comp_C1 = comp_C0; + comp_C0 = temp; + + /* 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; + + /* post bias (+) */ + comp_C0 += bias_vector[0]; + comp_C1 += bias_vector[1]; + comp_C2 += bias_vector[2]; + + /* 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; + + /* 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; + + output = (comp_C2 << 16) | (comp_C1 << 8) | comp_C0; + return output; } -static void rotate_dst_addr_y(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) { - 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; + 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; + + /* 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; + } + + output = (comp_C2 << 16) | (comp_C1 << 8) | comp_C0; + return output; } -static void blit_rotate(struct mdp_blit_req *req, - struct mdp_regs *regs) +static uint32 mdp_calc_tpval(MDPIMG *mdpImg) { - if (req->flags == MDP_ROT_NOP) - return; + 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 */ - 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); + tpVal = (mdpImg->tpVal & 0xFF00) >> 8; + tpVal |= (mdpImg->tpVal & 0xFF) << 8; + tpVal |= (mdpImg->tpVal & 0xFF0000); } - if (req->flags & MDP_FLIP_LR) - regs->op |= PPP_OP_FLIP_LR; + + 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_convert(struct mdp_blit_req *req, struct mdp_regs *regs) +static void mdp_ppp_setbg(MDPIBUF *iBuf) { - if (req->src.format == req->dst.format) + 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 (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; } + + /* 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_CBCR_H2V2_ADRENO) | \ + (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_CBCR_H2V2_ADRENO || \ + format == MDP_Y_CRCB_H2V2) ? 2 : (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) +{ + *len0 = IMG_LEN(rect->h, img->width, rect->w, bpp); + if (IS_PSEUDOPLNR(img->format)) + *len1 = *len0/Y_TO_CRCB_RATIO(img->format); + else + *len1 = 0; } -#define GET_BIT_RANGE(value, high, low) \ - (((1 << (high - low + 1)) - 1) & (value >> low)) -static uint32_t transp_convert(struct mdp_blit_req *req) +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) { - 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); + 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); } - return transp; + } -#undef GET_BIT_RANGE +#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 blit_blend(struct mdp_blit_req *req, struct mdp_regs *regs) +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) { - /* 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; + uint8 *src0, *src1; + uint8 *dest0, *dest1; + uint16 inpBpp; + uint32 dest0_ystride; + uint32 src_width; + uint32 src_height; + uint32 src0_ystride; + uint32 src0_y1stride; + 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; + } + + /* 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_CBCR_H2V2_ADRENO: + 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; + } + } + /* 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; } } - 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]; -} + 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 (iBuf->mdpImg.imgType == MDP_Y_CBCR_H2V2_ADRENO) + src0_ystride = ALIGN(src_width, 32) * inpBpp; + else + src0_ystride = src_width * inpBpp; + + if (iBuf->mdpImg.imgType == MDP_Y_CBCR_H2V2_ADRENO) + src0_y1stride = 2 * ALIGN(src_width/2, 32); + else + src0_y1stride = src0_ystride; + + 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); + } + } + } -#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) + /* + * 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_y1stride << 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 scale_params(uint32_t dim_in, uint32_t dim_out, uint32_t origin, - uint32_t *phase_init, uint32_t *phase_step) +static int mdp_ppp_verify_req(struct mdp_blit_req *req) { - /* 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) + 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; - do_div(n, d); - k3 = (n + 1) >> 1; - if ((k3 >> 4) < (1LL << 27) || (k3 >> 4) > (1LL << 31)) { - DLOG("crap bad scale\n"); + } + + 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; } - n = ((uint64_t)dim_in) << 34; - d = (uint64_t)dim_out; - if (!d) + + 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; - 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 (((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 (rpa) { - tmp = (dim_in * od_p) + ONE_HALF; - if (!dim_in) - return -1; - do_div(tmp, dim_in); - os_p = tmp - ONE_HALF; + 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 { - os_p = ((k1 * (od_p >> 33)) + k2); + dst_width = req->dst_rect.w; + dst_height = req->dst_rect.h; } - oreq = (os_p & INT_MASK) - ONE; + 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; + } - 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); + 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 load_scale_table(const struct mdp_info *mdp, - struct mdp_table_entry *table, int len) +int get_gem_img(struct mdp_img *img, unsigned long *start, unsigned long *len) { - int i; - for (i = 0; i < len; i++) - mdp_writel(mdp, table[i].val, table[i].reg); + /* 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); } -enum { -IMG_LEFT, -IMG_RIGHT, -IMG_TOP, -IMG_BOTTOM, -}; +int get_img(struct mdp_img *img, struct fb_info *info, unsigned long *start, + unsigned long *len, struct file **pp_file) +{ + 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; -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; + 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 { - *interp1 = -2; - *interp2 = src + 1; - *repeat1 = 2; - *repeat2 = 2; + ret = -1; + fput_light(file, put_needed); } - *interp1 += src_coord; - *interp2 += src_coord; + return ret; } -static int get_edge_cond(struct mdp_blit_req *req, struct mdp_regs *regs) + +void put_img(struct file *p_src_file) { - 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; +#ifdef CONFIG_ANDROID_PMEM + if (p_src_file) + put_pmem_file(p_src_file); +#endif +} - 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) +static int mdp_ppp_blit_addr(struct fb_info *info, struct mdp_blit_req *req, + unsigned long srcp0_start, unsigned long srcp0_len, + unsigned long srcp1_start, unsigned long srcp1_len, + unsigned long dst_start, unsigned long dst_len, + struct file *p_src_file, struct file *p_dst_file) +{ + MDPIBUF iBuf; + u32 dst_width, dst_height; + struct msm_fb_data_type *mfd = 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 (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; + } - 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; -} + iBuf.ibuf_width = req->dst.width; + iBuf.ibuf_height = req->dst.height; + iBuf.bpp = bytes_per_pixel[req->dst.format]; -static int blit_scale(const struct mdp_info *mdp, struct mdp_blit_req *req, - struct mdp_regs *regs) -{ - 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; + iBuf.ibuf_type = req->dst.format; + iBuf.buf = (uint8 *) dst_start; + iBuf.buf += req->dst.offset; - 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; - } + 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; - 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; + 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; - scale_factor_x = (dst_w * 10) / req->src_rect.w; - scale_factor_y = (dst_h * 10) / req->src_rect.h; + iBuf.mdpImg.width = req->src.width; + iBuf.mdpImg.imgType = req->src.format; - 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; + + iBuf.mdpImg.bmy_addr = (uint32 *) (srcp0_start + req->src.offset); + if (iBuf.mdpImg.imgType == MDP_Y_CBCR_H2V2_ADRENO) + iBuf.mdpImg.cbcr_addr = + (uint32 *) ((uint32) iBuf.mdpImg.bmy_addr + + ALIGN((ALIGN(req->src.width, 32) * + ALIGN(req->src.height, 32)), 4096)); else - downscale = MDP_DOWNSCALE_PT2TOPT4; - if (downscale != downscale_y_table) { - load_scale_table(mdp, mdp_downscale_y_table[downscale], 64); - downscale_y_table = downscale; - } + iBuf.mdpImg.cbcr_addr = srcp1_start ? (uint32 *)srcp1_start : + (uint32 *) ((uint32) iBuf.mdpImg.bmy_addr + + req->src.width * req->src.height); - 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; + iBuf.mdpImg.mdpOp = MDPOP_NOP; -} - -static void blit_blur(const struct mdp_info *mdp, struct mdp_blit_req *req, - struct mdp_regs *regs) -{ - if (!(req->flags & MDP_BLUR)) - return; + /* 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; + } - 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; + req->alpha &= 0xff; + if (req->alpha < MDP_ALPHA_NOP) { + iBuf.mdpImg.mdpOp |= MDPOP_ALPHAB; + iBuf.mdpImg.alpha = req->alpha; + } else { + iBuf.mdpImg.alpha = 0xff; } - regs->op |= (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); -} + /* 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) + iBuf.mdpImg.mdpOp |= MDPOP_DITHER; + + if (req->flags & MDP_BLEND_FG_PREMULT) { +#if defined(CONFIG_FB_MSM_MDP31) || defined(CONFIG_FB_MSM_MDP303) + iBuf.mdpImg.mdpOp |= MDPOP_FG_PM_ALPHA; +#else + put_img(p_src_file); + put_img(p_dst_file); + return -EINVAL; +#endif + } + 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 + } -#define IMG_LEN(rect_h, w, rect_w, bpp) (((rect_h) * w) * bpp) + /* 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; + } -#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) + if ((iBuf.roi.width != dst_width) || (iBuf.roi.height != dst_height)) + iBuf.mdpImg.mdpOp |= MDPOP_ASCALE; -static void get_len(struct mdp_img *img, struct mdp_rect *rect, uint32_t bpp, - uint32_t *len0, uint32_t *len1) -{ - *len0 = IMG_LEN(rect->h, img->width, rect->w, bpp); - if (IS_PSEUDOPLNR(img->format)) - *len1 = *len0/Y_TO_CRCB_RATIO(img->format); - else - *len1 = 0; -} + 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; -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) -{ - 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; +#endif } - 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; + + 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 } - if (regs->dst0 < dst_min_ok || regs->dst0 > dst_max_ok || - regs->dst0 + dst0_len > dst_max_ok) { - DLOG("invalid_dst"); - return 0; - } - 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; + + 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); } - return 1; -} +#endif + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + up(&mdp_ppp_mutex); -static void flush_imgs(struct mdp_blit_req *req, struct mdp_regs *regs, - struct file *src_file, struct file *dst_file) -{ + put_img(p_src_file); + put_img(p_dst_file); + return 0; } -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 mdp_ppp_blit(struct fb_info *info, struct mdp_blit_req *req) { - 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; - } else { - *addr = 0; + unsigned long src_start, dst_start; + unsigned long src_len = 0; + unsigned long dst_len = 0; + struct file *p_src_file = 0 , *p_dst_file = 0; + + 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 -EINVAL; } + 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 -EINVAL; + } + + return mdp_ppp_blit_addr(info, req, src_start, src_len, 0, 0, dst_start, + dst_len, p_src_file, p_dst_file); } -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) +static struct mdp_blit_req overlay_req; +static bool mdp_overlay_req_set; + +int mdp_ppp_v4l2_overlay_set(struct fb_info *info, struct mdp_overlay *req) { - 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); + memset(&overlay_req, 0, sizeof(struct mdp_blit_req)); + + overlay_req.src.width = req->src.width; + overlay_req.src.height = req->src.height; + overlay_req.src.format = req->src.format; + + + overlay_req.dst.width = req->dst_rect.w; + overlay_req.dst.height = req->dst_rect.h; + overlay_req.dst.format = MDP_FB_FORMAT; + overlay_req.transp_mask = req->transp_mask; + overlay_req.flags = req->flags; + overlay_req.alpha = req->alpha; + + overlay_req.src_rect.x = req->src_rect.x; + overlay_req.src_rect.y = req->src_rect.y; + overlay_req.src_rect.w = req->src_rect.w; + overlay_req.src_rect.h = req->src_rect.h; + overlay_req.dst_rect.x = req->dst_rect.x; + overlay_req.dst_rect.y = req->dst_rect.y; + overlay_req.dst_rect.w = req->dst_rect.w; + overlay_req.dst_rect.h = req->dst_rect.h; + mdp_overlay_req_set = true; + + pr_debug("%s: Overlay parameters:", __func__); + pr_debug("Src_Image (%u %u)\n", overlay_req.src.width, + overlay_req.src.height); + + if (overlay_req.src.format == MDP_Y_CRCB_H2V2) + pr_debug("Overlay format MDP_Y_CRCB_H2V2\n"); + else if (overlay_req.src.format == MDP_RGB_565) + pr_debug("Overlay format MDP_RGB_565\n"); + else + pr_debug("Overlay format(%u) unknown\n", + overlay_req.src.format); + + pr_debug("Dst_Image (%u %u)\n", overlay_req.dst.width, + overlay_req.dst.height); + pr_debug("Src rect: (%u,%u,%u,%u), Dst rect: (%u,%u,%u,%u)\n", + overlay_req.src_rect.x, overlay_req.src_rect.y, + overlay_req.src_rect.w, overlay_req.src_rect.h, + overlay_req.dst_rect.x, overlay_req.dst_rect.y, + overlay_req.dst_rect.w, overlay_req.dst_rect.h); return 0; } -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) +int mdp_ppp_v4l2_overlay_clear(void) { - struct mdp_regs regs = {0}; + memset(&overlay_req, 0, sizeof(struct mdp_overlay)); + mdp_overlay_req_set = false; + return 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_v4l2_overlay_play(struct fb_info *info, + unsigned long srcp0_addr, unsigned long srcp0_size, + unsigned long srcp1_addr, unsigned long srcp1_size) +{ + int ret; - 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"); + if (!mdp_overlay_req_set) { + pr_err("mdp_ppp:v4l2:No overlay set, ignore play req\n"); return -EINVAL; } - /* 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; - } + overlay_req.dst.width = info->var.xres; + overlay_req.dst.height = info->var.yres; - /* set up operation register */ - regs.op = 0; - blit_rotate(req, ®s); - blit_convert(req, ®s); - 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"); - return -EINVAL; - } - blit_blur(mdp, req, ®s); - regs.op |= dst_op_chroma[req->dst.format] | - src_op_chroma[req->src.format]; + ret = mdp_ppp_blit_addr(info, &overlay_req, + srcp0_addr, srcp0_size, srcp1_addr, srcp1_size, + info->fix.smem_start, info->fix.smem_len, NULL, NULL); - /* 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 (get_edge_cond(req, ®s)) - return -EINVAL; + if (ret) + pr_err("%s:Blitting overlay failed(%d)\n", __func__, ret); - send_blit(mdp, req, ®s, src_file, dst_file); - return 0; + return ret; } diff --git a/drivers/video/msm/mdp_ppp.h b/drivers/video/msm/mdp_ppp.h new file mode 100644 index 0000000000000000000000000000000000000000..e04564347bdfe16c450c138bf6dfe8be2f118d2d --- /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 0000000000000000000000000000000000000000..9016f0ab952444562a9e1030438d09e14c1ce2ba --- /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 0000000000000000000000000000000000000000..91764fe27a44f8a87c20131e91a43ff6976cf0f4 --- /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 0000000000000000000000000000000000000000..d271b85eb1c93a8195ae1c077949ebf80095fa33 --- /dev/null +++ b/drivers/video/msm/mdp_ppp_v20.c @@ -0,0 +1,2533 @@ +/* Copyright (c) 2008-2009, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * 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_CBCR_H2V2_ADRENO: + 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_CBCR_H2V2_ADRENO: + 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 */ + 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); + 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 (mdp_rev == MDP_REV_303) { + 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; + outpdw(MDP_BASE + 0x70010, 0); + } 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; + outpdw(MDP_BASE + 0x70010, 0); + } + } 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 | + 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 0000000000000000000000000000000000000000..ee6af5348771ca7362617bddbe41752cd4312b87 --- /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 0000000000000000000000000000000000000000..87e74d92f1db1ef9c263db4a63eccf3ae0d9e232 --- /dev/null +++ b/drivers/video/msm/mdp_vsync.c @@ -0,0 +1,491 @@ +/* Copyright (c) 2008-2009, 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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); +static DEFINE_SPINLOCK(vsync_timer_lock); + +static struct clk *mdp_vsync_clk; +static struct msm_fb_data_type *vsync_mfd; +static unsigned char timer_shutdown_flag; +static uint32 vsync_cnt_cfg; + +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_prepare_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_unprepare(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) { + spin_lock(&vsync_timer_lock); + timer_shutdown_flag = 1; + spin_unlock(&vsync_timer_lock); + del_timer_sync(&vsync_mfd->vsync_resync_timer); + spin_lock(&vsync_timer_lock); + timer_shutdown_flag = 0; + spin_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); + } + + spin_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); + } + spin_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 + +void mdp_vsync_cfg_regs(struct msm_fb_data_type *mfd, + boolean first_time) +{ + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, + FALSE); + if (first_time) + 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)); + + if (first_time) + mdp_hw_vsync_clk_disable(mfd); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} +#endif + +void mdp_config_vsync(struct platform_device *pdev, + 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(&pdev->dev, "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_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_vsync_cfg_regs(mfd, TRUE); + } + } +#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/mhl/mhl_8334.c b/drivers/video/msm/mhl/mhl_8334.c new file mode 100644 index 0000000000000000000000000000000000000000..43280a505645758f3ac533b643d5d28b7381c88a --- /dev/null +++ b/drivers/video/msm/mhl/mhl_8334.c @@ -0,0 +1,849 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "external_common.h" +#include "mhl_8334.h" +#include "mhl_i2c_utils.h" + +#define DEBUG + + +static struct i2c_device_id mhl_sii_i2c_id[] = { + { MHL_DRIVER_NAME, 0 }, + { } +}; + +struct mhl_msm_state_t *mhl_msm_state; +spinlock_t mhl_state_lock; + +static int mhl_i2c_probe(struct i2c_client *client,\ + const struct i2c_device_id *id); +static int mhl_i2c_remove(struct i2c_client *client); +static void force_usb_switch_open(void); +static void release_usb_switch_open(void); +static void switch_mode(enum mhl_st_type to_mode); +static irqreturn_t mhl_tx_isr(int irq, void *dev_id); + +static struct i2c_driver mhl_sii_i2c_driver = { + .driver = { + .name = MHL_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = mhl_i2c_probe, + /*.remove = __exit_p(mhl_i2c_remove),*/ + .remove = mhl_i2c_remove, + .id_table = mhl_sii_i2c_id, +}; + +bool mhl_is_connected(void) +{ + return true; +} + +static void cbus_reset(void) +{ + uint8_t i; + + /* + * REG_SRST + */ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0000, BIT3, BIT3); + msleep(20); + mhl_i2c_reg_modify(TX_PAGE_3, 0x0000, BIT3, 0x00); + /* + * REG_INTR1 and REG_INTR4 + */ + mhl_i2c_reg_write(TX_PAGE_L0, 0x0075, BIT6 | BIT5); + mhl_i2c_reg_write(TX_PAGE_3, 0x0022, + BIT0 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6); + /* REG5 */ + if (mhl_msm_state->chip_rev_id < 1) + mhl_i2c_reg_write(TX_PAGE_3, 0x0024, BIT3 | BIT4); + else + /*REG5 Mask disabled due to auto FIFO reset ??*/ + mhl_i2c_reg_write(TX_PAGE_3, 0x0024, 0x00); + + /* Unmask CBUS1 Intrs */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0009, + BIT2 | BIT3 | BIT4 | BIT5 | BIT6); + + /* Unmask CBUS2 Intrs */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x001F, BIT2 | BIT3); + + for (i = 0; i < 4; i++) { + /* + * Enable WRITE_STAT interrupt for writes to + * all 4 MSC Status registers. + */ + mhl_i2c_reg_write(TX_PAGE_CBUS, (0xE0 + i), 0xFF); + + /* + * Enable SET_INT interrupt for writes to + * all 4 MSC Interrupt registers. + */ + mhl_i2c_reg_write(TX_PAGE_CBUS, (0xF0 + i), 0xFF); + } +} + +static void init_cbus_regs(void) +{ + uint8_t regval; + + /* Increase DDC translation layer timer*/ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0007, 0xF2); + /* Drive High Time */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0036, 0x03); + /* Use programmed timing */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0039, 0x30); + /* CBUS Drive Strength */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0040, 0x03); + /* + * Write initial default settings + * to devcap regs: default settings + */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_DEV_STATE, + DEVCAP_VAL_DEV_STATE); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_MHL_VERSION, + DEVCAP_VAL_MHL_VERSION); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_DEV_CAT, + DEVCAP_VAL_DEV_CAT); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_ADOPTER_ID_H, + DEVCAP_VAL_ADOPTER_ID_H); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_ADOPTER_ID_L, + DEVCAP_VAL_ADOPTER_ID_L); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_VID_LINK_MODE, + DEVCAP_VAL_VID_LINK_MODE); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_AUD_LINK_MODE, + DEVCAP_VAL_AUD_LINK_MODE); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_VIDEO_TYPE, + DEVCAP_VAL_VIDEO_TYPE); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_LOG_DEV_MAP, + DEVCAP_VAL_LOG_DEV_MAP); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_BANDWIDTH, + DEVCAP_VAL_BANDWIDTH); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_FEATURE_FLAG, + DEVCAP_VAL_FEATURE_FLAG); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_DEVICE_ID_H, + DEVCAP_VAL_DEVICE_ID_H); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_DEVICE_ID_L, + DEVCAP_VAL_DEVICE_ID_L); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_SCRATCHPAD_SIZE, + DEVCAP_VAL_SCRATCHPAD_SIZE); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_INT_STAT_SIZE, + DEVCAP_VAL_INT_STAT_SIZE); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0080 | DEVCAP_OFFSET_RESERVED, + DEVCAP_VAL_RESERVED); + + /* Make bits 2,3 (initiator timeout) to 1,1 + * for register CBUS_LINK_CONTROL_2 + * REG_CBUS_LINK_CONTROL_2 + */ + regval = mhl_i2c_reg_read(TX_PAGE_CBUS, 0x0031); + regval = (regval | 0x0C); + /* REG_CBUS_LINK_CONTROL_2 */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0031, regval); + /* REG_MSC_TIMEOUT_LIMIT */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0022, 0x0F); + /* REG_CBUS_LINK_CONTROL_1 */ + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x0030, 0x01); + /* disallow vendor specific commands */ + mhl_i2c_reg_modify(TX_PAGE_CBUS, 0x002E, BIT4, BIT4); +} + +/* + * Configure the initial reg settings + */ +static void mhl_init_reg_settings(void) +{ + + /* + * ============================================ + * POWER UP + * ============================================ + */ + + /* Power up 1.2V core */ + mhl_i2c_reg_write(TX_PAGE_L1, 0x003D, 0x3F); + /* Enable Tx PLL Clock */ + mhl_i2c_reg_write(TX_PAGE_2, 0x0011, 0x01); + /* Enable Tx Clock Path and Equalizer */ + mhl_i2c_reg_write(TX_PAGE_2, 0x0012, 0x11); + /* Tx Source Termination ON */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0030, 0x10); + /* Enable 1X MHL Clock output */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0035, 0xAC); + /* Tx Differential Driver Config */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0031, 0x3C); + mhl_i2c_reg_write(TX_PAGE_3, 0x0033, 0xD9); + /* PLL Bandwidth Control */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0037, 0x02); + /* + * ============================================ + * Analog PLL Control + * ============================================ + */ + /* Enable Rx PLL clock */ + mhl_i2c_reg_write(TX_PAGE_L0, 0x0080, 0x00); + mhl_i2c_reg_write(TX_PAGE_L0, 0x00F8, 0x0C); + mhl_i2c_reg_write(TX_PAGE_L0, 0x0085, 0x02); + mhl_i2c_reg_write(TX_PAGE_2, 0x0000, 0x00); + mhl_i2c_reg_write(TX_PAGE_2, 0x0013, 0x60); + /* PLL Cal ref sel */ + mhl_i2c_reg_write(TX_PAGE_2, 0x0017, 0x03); + /* VCO Cal */ + mhl_i2c_reg_write(TX_PAGE_2, 0x001A, 0x20); + /* Auto EQ */ + mhl_i2c_reg_write(TX_PAGE_2, 0x0022, 0xE0); + mhl_i2c_reg_write(TX_PAGE_2, 0x0023, 0xC0); + mhl_i2c_reg_write(TX_PAGE_2, 0x0024, 0xA0); + mhl_i2c_reg_write(TX_PAGE_2, 0x0025, 0x80); + mhl_i2c_reg_write(TX_PAGE_2, 0x0026, 0x60); + mhl_i2c_reg_write(TX_PAGE_2, 0x0027, 0x40); + mhl_i2c_reg_write(TX_PAGE_2, 0x0028, 0x20); + mhl_i2c_reg_write(TX_PAGE_2, 0x0029, 0x00); + /* Rx PLL Bandwidth 4MHz */ + mhl_i2c_reg_write(TX_PAGE_2, 0x0031, 0x0A); + /* Rx PLL Bandwidth value from I2C */ + mhl_i2c_reg_write(TX_PAGE_2, 0x0045, 0x06); + mhl_i2c_reg_write(TX_PAGE_2, 0x004B, 0x06); + /* Manual zone control */ + mhl_i2c_reg_write(TX_PAGE_2, 0x004C, 0xE0); + /* PLL Mode value */ + mhl_i2c_reg_write(TX_PAGE_2, 0x004D, 0x00); + mhl_i2c_reg_write(TX_PAGE_L0, 0x0008, 0x35); + /* + * Discovery Control and Status regs + * Setting De-glitch time to 50 ms (default) + * Switch Control Disabled + */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0011, 0xAD); + /* 1.8V CBUS VTH */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0014, 0x55); + /* RGND and single Discovery attempt */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0015, 0x11); + /* Ignore VBUS */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0017, 0x82); + mhl_i2c_reg_write(TX_PAGE_3, 0x0018, 0x24); + /* Pull-up resistance off for IDLE state */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0013, 0x84); + /* Enable CBUS Discovery */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0010, 0x27); + mhl_i2c_reg_write(TX_PAGE_3, 0x0016, 0x20); + /* MHL CBUS Discovery - immediate comm. */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0012, 0x86); + /* Do not force HPD to 0 during wake-up from D3 */ + if (mhl_msm_state->cur_state != POWER_STATE_D3) { + mhl_i2c_reg_modify(TX_PAGE_3, 0x0020, + BIT5 | BIT4, BIT4); + } + /* Enable Auto Soft RESET */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0000, 0x084); + /* HDMI Transcode mode enable */ + mhl_i2c_reg_write(TX_PAGE_L0, 0x000D, 0x1C); + + cbus_reset(); + init_cbus_regs(); +} + +static int mhl_chip_init(void) +{ + /* Read the chip rev ID */ + mhl_msm_state->chip_rev_id = mhl_i2c_reg_read(TX_PAGE_L0, 0x04); + pr_debug("MHL: chip rev ID read=[%x]\n", mhl_msm_state->chip_rev_id); + + /* Reset the TX chip */ + mhl_msm_state->mhl_data->reset_pin(0); + msleep(20); + mhl_msm_state->mhl_data->reset_pin(1); + /* MHL spec requires a 100 ms wait here. */ + msleep(100); + + mhl_init_reg_settings(); + + /* + * Power down the chip to the + * D3 - a low power standby mode + * cable impedance measurement logic is operational + */ + switch_mode(POWER_STATE_D3); + return 0; +} + +/* + * I2C probe + */ +static int mhl_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + mhl_msm_state->mhl_data = kzalloc(sizeof(struct msm_mhl_platform_data), + GFP_KERNEL); + if (!(mhl_msm_state->mhl_data)) { + ret = -ENOMEM; + goto probe_exit; + } + pr_debug("Inside probe\n"); + mhl_msm_state->i2c_client = client; + + spin_lock_init(&mhl_state_lock); + + i2c_set_clientdata(client, mhl_msm_state); + mhl_msm_state->mhl_data = client->dev.platform_data; + + /* Init GPIO stuff here */ + ret = mhl_msm_state->mhl_data->gpio_setup(1); + if (ret == -1) { + pr_err("MHL: mhl_gpio_init has failed\n"); + ret = -ENODEV; + goto probe_exit; + } + return 0; + +probe_exit: + if (mhl_msm_state->mhl_data) { + /* free the gpios */ + mhl_msm_state->mhl_data->gpio_setup(0); + kfree(mhl_msm_state->mhl_data); + mhl_msm_state->mhl_data = NULL; + } + return ret; +} + +static int mhl_i2c_remove(struct i2c_client *client) +{ + pr_debug("inside i2c remove\n"); + mhl_msm_state->mhl_data->gpio_setup(0); + kfree(mhl_msm_state->mhl_data); + return 0; +} + +static int __init mhl_msm_init(void) +{ + int32_t ret; + + mhl_msm_state = kzalloc(sizeof(struct mhl_msm_state_t), GFP_KERNEL); + if (!mhl_msm_state) { + pr_err("mhl_msm_init FAILED: out of memory\n"); + ret = -ENOMEM; + goto init_exit; + } + + mhl_msm_state->i2c_client = NULL; + ret = i2c_add_driver(&mhl_sii_i2c_driver); + if (ret) { + pr_err("MHL: I2C driver add failed: %d\n", ret); + ret = -ENODEV; + goto init_exit; + } else { + if (mhl_msm_state->i2c_client == NULL) { + pr_err("JSR: I2C driver add failed\n"); + ret = -ENODEV; + goto init_exit; + } + pr_debug("MHL: I2C driver added\n"); + } + + /* Request IRQ stuff here */ + pr_debug("MHL: mhl_msm_state->mhl_data->irq=[%d]\n", + mhl_msm_state->mhl_data->irq); + ret = request_threaded_irq(mhl_msm_state->mhl_data->irq, NULL, + &mhl_tx_isr, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "mhl_tx_isr", mhl_msm_state); + if (ret != 0) { + pr_err("request_threaded_irq failed, status: %d\n", + ret); + ret = -EACCES; /* Error code???? */ + goto init_exit; + } + + mhl_msm_state->cur_state = POWER_STATE_D0_MHL; + + /* MHL SII 8334 chip specific init */ + mhl_chip_init(); + return 0; + +init_exit: + pr_err("Exiting from the init with err\n"); + i2c_del_driver(&mhl_sii_i2c_driver); + if (!mhl_msm_state) { + kfree(mhl_msm_state); + mhl_msm_state = NULL; + } + return ret; +} + +static void switch_mode(enum mhl_st_type to_mode) +{ + unsigned long flags; + + switch (to_mode) { + case POWER_STATE_D0_NO_MHL: + break; + case POWER_STATE_D0_MHL: + mhl_init_reg_settings(); + + /* REG_DISC_CTRL1 */ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0010, BIT1, 0); + + /* + * TPI_DEVICE_POWER_STATE_CTRL_REG + * TX_POWER_STATE_MASK = BIT1 | BIT0 + */ + mhl_i2c_reg_modify(TX_PAGE_TPI, 0x001E, BIT1 | BIT0, 0x00); + break; + case POWER_STATE_D3: + if (mhl_msm_state->cur_state != POWER_STATE_D3) { + /* Force HPD to 0 when not in MHL mode. */ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0020, + BIT5 | BIT4, BIT4); + + /* + * Change TMDS termination to high impedance + * on disconnection. + */ + mhl_i2c_reg_write(TX_PAGE_3, 0x0030, 0xD0); + mhl_i2c_reg_modify(TX_PAGE_L1, 0x003D, + BIT1 | BIT0, BIT0); + spin_lock_irqsave(&mhl_state_lock, flags); + mhl_msm_state->cur_state = POWER_STATE_D3; + spin_unlock_irqrestore(&mhl_state_lock, flags); + } + break; + default: + break; + } +} + +static void mhl_drive_hpd(uint8_t to_state) +{ + pr_debug("%s: To state=[0x%x]\n", __func__, to_state); + if (to_state == HPD_UP) { + /* + * Drive HPD to UP state + * + * The below two reg configs combined + * enable TMDS output. + */ + + /* Enable TMDS on TMDS_CCTRL */ + mhl_i2c_reg_modify(TX_PAGE_L0, 0x0080, BIT4, BIT4); + + /* + * Set HPD_OUT_OVR_EN = HPD State + * EDID read and Un-force HPD (from low) + * propogate to src let HPD float by clearing + * HPD OUT OVRRD EN + */ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0020, BIT4, 0x00); + } else { + /* + * Drive HPD to DOWN state + * Disable TMDS Output on REG_TMDS_CCTRL + * Enable/Disable TMDS output (MHL TMDS output only) + */ + mhl_i2c_reg_modify(TX_PAGE_L0, 0x0080, BIT4, 0x00); + } + return; +} + +static void mhl_msm_connection(void) +{ + uint8_t val; + unsigned long flags; + + pr_err("%s: cur state = [0x%x]\n", __func__, mhl_msm_state->cur_state); + + if (mhl_msm_state->cur_state == POWER_STATE_D0_MHL) { + /* Already in D0 - MHL power state */ + return; + } + spin_lock_irqsave(&mhl_state_lock, flags); + mhl_msm_state->cur_state = POWER_STATE_D0_MHL; + spin_unlock_irqrestore(&mhl_state_lock, flags); + + mhl_i2c_reg_write(TX_PAGE_3, 0x30, 0x10); + + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x07, 0xF2); + + /* + * Keep the discovery enabled. Need RGND interrupt + * Possibly chip disables discovery after MHL_EST?? + * Need to re-enable here + */ + val = mhl_i2c_reg_read(TX_PAGE_3, 0x10); + mhl_i2c_reg_write(TX_PAGE_3, 0x10, val | BIT(0)); + + return; +} + +static void mhl_msm_disconnection(void) +{ + uint8_t reg; + + /* Clear interrupts - REG INTR4 */ + reg = mhl_i2c_reg_read(TX_PAGE_3, 0x0021); + mhl_i2c_reg_write(TX_PAGE_3, 0x0021, reg); + /* + * MHL TX CTL1 + * Disabling Tx termination + */ + mhl_i2c_reg_write(TX_PAGE_3, 0x30, 0xD0); + /* + * MSC REQUESTOR ABORT REASON + * Clear CBUS_HPD status + */ + mhl_i2c_reg_modify(TX_PAGE_CBUS, 0x000D, BIT6, 0x00); + /* Change HPD line to drive it low */ + mhl_drive_hpd(HPD_DOWN); + /* switch power state to D3 */ + switch_mode(POWER_STATE_D3); + return; +} + +/* + * If hardware detected a change in impedence and raised an INTR + * We check the range of this impedence to infer if the connected + * device is MHL or USB and take appropriate actions. + */ +static void mhl_msm_read_rgnd_int(void) +{ + uint8_t rgnd_imp; + + /* + * DISC STATUS REG 2 + * 1:0 RGND + * 00 - open (USB) + * 01 - 2 kOHM (USB) + * 10 - 1 kOHM ***(MHL)**** It's range 800 - 1200 OHM from MHL spec + * 11 - short (USB) + */ + rgnd_imp = mhl_i2c_reg_read(TX_PAGE_3, 0x001C); + pr_debug("Imp Range read = %02X\n", (int)rgnd_imp); + + + if (0x02 == rgnd_imp) { + pr_debug("MHL: MHL DEVICE!!!\n"); + /* + * Handling the MHL event in driver + */ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0018, BIT0, BIT0); + } else { + pr_debug("MHL: NON-MHL DEVICE!!!\n"); + mhl_i2c_reg_modify(TX_PAGE_3, 0x0018, BIT3, BIT3); + } +} + +static void force_usb_switch_open(void) +{ + /*DISABLE_DISCOVERY*/ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0010, BIT0, 0); + /* Force USB ID switch to open*/ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0015, BIT6, BIT6); + mhl_i2c_reg_write(TX_PAGE_3, 0x0012, 0x86); + /* Force HPD to 0 when not in Mobile HD mode. */ + mhl_i2c_reg_modify(TX_PAGE_3, 0x0020, BIT5 | BIT4, BIT4); +} + +static void release_usb_switch_open(void) +{ + msleep(50); + mhl_i2c_reg_modify(TX_PAGE_3, 0x0015, BIT6, 0x00); + mhl_i2c_reg_modify(TX_PAGE_3, 0x0010, BIT0, BIT0); +} + +static void int_4_isr(void) +{ + uint8_t status; + + /* INTR_STATUS4 */ + status = mhl_i2c_reg_read(TX_PAGE_3, 0x0021); + + /* + * When I2C is inoperational (D3) and + * a previous interrupt brought us here, + * do nothing. + */ + pr_debug("MHL: MRR Interrupt status is = %02X\n", (int) status); + if (0xFF != status) { + if ((status & BIT0) && (mhl_msm_state->chip_rev_id < 1)) { + uint8_t tmds_cstat; + uint8_t mhl_fifo_status; + + /* TMDS CSTAT */ + tmds_cstat = mhl_i2c_reg_read(TX_PAGE_3, 0x0040); + + pr_debug("TMDS CSTAT: 0x%02x\n", tmds_cstat); + + if (tmds_cstat & 0x02) { + mhl_fifo_status = mhl_i2c_reg_read(TX_PAGE_3, + 0x0023); + pr_debug("MHL FIFO status: 0x%02x\n", + mhl_fifo_status); + if (mhl_fifo_status & 0x0C) { + mhl_i2c_reg_write(TX_PAGE_3, 0x0023, + 0x0C); + + pr_debug("Apply MHL FIFO Reset\n"); + mhl_i2c_reg_write(TX_PAGE_3, 0x0000, + 0x94); + mhl_i2c_reg_write(TX_PAGE_3, 0x0000, + 0x84); + } + } + } + + if (status & BIT1) + pr_err("MHL: INT4 BIT1 is set\n"); + + /* MHL_EST interrupt */ + if (status & BIT2) { + pr_err("MHL: Calling mhl_msm_connection() from ISR\n"); + mhl_msm_connection(); + pr_err("MHL Connect Drv: INT4 Status = %02X\n", + (int) status); + } else if (status & BIT3) { + pr_err("MHL: uUSB-A type device detected.\n"); + mhl_i2c_reg_write(TX_PAGE_3, 0x001C, 0x80); + switch_mode(POWER_STATE_D3); + } + + if (status & BIT5) { + mhl_msm_disconnection(); + pr_err("MHL Disconnect Drv: INT4 Status = %02X\n", + (int)status); + } + + if ((mhl_msm_state->cur_state != POWER_STATE_D0_MHL) &&\ + (status & BIT6)) { + /* RGND READY Intr */ + switch_mode(POWER_STATE_D0_MHL); + mhl_msm_read_rgnd_int(); + } + + /* Can't succeed at these in D3 */ + if (mhl_msm_state->cur_state != POWER_STATE_D3) { + /* CBUS Lockout interrupt? */ + /* + * Hardware detection mechanism figures that + * CBUS line is latched and raises this intr + * where we force usb switch open and release + */ + if (status & BIT4) { + force_usb_switch_open(); + release_usb_switch_open(); + } + } + } + pr_debug("MHL END Drv: INT4 Status = %02X\n", (int) status); + mhl_i2c_reg_write(TX_PAGE_3, 0x0021, status); + + return; +} + +static void int_5_isr(void) +{ + uint8_t intr_5_stat; + + /* + * Clear INT 5 ?? + * Probably need to revisit this later + * INTR5 is related to FIFO underflow/overflow reset + * which is handled in 8334 by auto FIFO reset + */ + intr_5_stat = mhl_i2c_reg_read(TX_PAGE_3, 0x0023); + mhl_i2c_reg_write(TX_PAGE_3, 0x0023, intr_5_stat); +} + + +static void int_1_isr(void) +{ + /* This ISR mainly handles the HPD status changes */ + uint8_t intr_1_stat; + uint8_t cbus_stat; + + /* INTR STATUS 1 */ + intr_1_stat = mhl_i2c_reg_read(TX_PAGE_L0, 0x0071); + + if (intr_1_stat) { + /* Clear interrupts */ + mhl_i2c_reg_write(TX_PAGE_L0, 0x0071, intr_1_stat); + if (BIT6 & intr_1_stat) { + /* + * HPD status change event is pending + * Read CBUS HPD status for this info + */ + + /* MSC REQ ABRT REASON */ + cbus_stat = mhl_i2c_reg_read(TX_PAGE_CBUS, 0x0D); + if (BIT6 & cbus_stat) + mhl_drive_hpd(HPD_UP); + } + } + return; +} + +/* + * RCP, RAP messages - mandatory for compliance + * + */ +static void mhl_cbus_isr(void) +{ + uint8_t regval; + int req_done = FALSE; + uint8_t sub_cmd; + uint8_t cmd_data; + int msc_msg_recved = FALSE; + int rc = -1; + + regval = mhl_i2c_reg_read(TX_PAGE_CBUS, 0x08); + if (regval == 0xff) + return; + + /* clear all interrupts that were raised even if we did not process */ + if (regval) + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x08, regval); + + pr_err("%s: CBUS_INT = %02x\n", __func__, regval); + + /* MSC_MSG (RCP/RAP) */ + if (regval & BIT(3)) { + sub_cmd = mhl_i2c_reg_read(TX_PAGE_CBUS, 0x18); + cmd_data = mhl_i2c_reg_read(TX_PAGE_CBUS, 0x19); + msc_msg_recved = TRUE; + } + + /* MSC_REQ_DONE */ + if (regval & BIT(4)) + req_done = TRUE; + + /* Now look for interrupts on CBUS_MSC_INT2 */ + regval = mhl_i2c_reg_read(TX_PAGE_CBUS, 0x1E); + + /* clear all interrupts that were raised */ + /* even if we did not process */ + if (regval) + mhl_i2c_reg_write(TX_PAGE_CBUS, 0x1E, regval); + + pr_err("%s: CBUS_MSC_INT2 = %02x\n", __func__, regval); + + /* received SET_INT */ + if (regval & BIT(2)) { + uint8_t intr; + intr = mhl_i2c_reg_read(TX_PAGE_CBUS, 0xA0); + pr_debug("%s: MHL_INT_0 = %02x\n", __func__, intr); + intr = mhl_i2c_reg_read(TX_PAGE_CBUS, 0xA1); + pr_debug("%s: MHL_INT_1 = %02x\n", __func__, intr); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xA0, 0xFF); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xA1, 0xFF); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xA2, 0xFF); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xA3, 0xFF); + } + + /* received WRITE_STAT */ + if (regval & BIT(3)) { + uint8_t stat; + stat = mhl_i2c_reg_read(TX_PAGE_CBUS, 0xB0); + pr_err("%s: MHL_STATUS_0 = %02x\n", __func__, stat); + stat = mhl_i2c_reg_read(TX_PAGE_CBUS, 0xB1); + pr_err("%s: MHL_STATUS_1 = %02x\n", __func__, stat); + + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xB0, 0xFF); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xB1, 0xFF); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xB2, 0xFF); + mhl_i2c_reg_write(TX_PAGE_CBUS, 0xB3, 0xFF); + } + + /* received MSC_MSG */ + if (msc_msg_recved) { + /*mhl msc recv msc msg*/ + if (rc) + pr_err("MHL: mhl msc recv msc msg failed(%d)!\n", rc); + } + + return; +} + +static irqreturn_t mhl_tx_isr(int irq, void *dev_id) +{ + /* + * Check discovery interrupt + * if not yet connected + */ + pr_debug("MHL: Current POWER state is [0x%x]\n", + mhl_msm_state->cur_state); + /* + * Check RGND, MHL_EST, CBUS_LOCKOUT, SCDT + * interrupts. In D3, we get only RGND + */ + int_4_isr(); + + pr_debug("MHL: Current POWER state is [0x%x]\n", + mhl_msm_state->cur_state); + if (mhl_msm_state->cur_state == POWER_STATE_D0_MHL) { + /* + * If int_4_isr() didn't move the tx to D3 + * on disconnect, continue to check other + * interrupt sources. + */ + int_5_isr(); + + /* + * Check for any peer messages for DCAP_CHG etc + * Dispatch to have the CBUS module working only + * once connected. + */ + mhl_cbus_isr(); + int_1_isr(); + } + return IRQ_HANDLED; +} + +static void __exit mhl_msm_exit(void) +{ + pr_warn("MHL: Exiting, Bye\n"); + /* + * Delete driver if i2c client structure is NULL + */ + i2c_del_driver(&mhl_sii_i2c_driver); + if (!mhl_msm_state) { + kfree(mhl_msm_state); + mhl_msm_state = NULL; + } +} + +module_init(mhl_msm_init); +module_exit(mhl_msm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MHL SII 8334 TX driver"); diff --git a/drivers/video/msm/mhl/mhl_8334.h b/drivers/video/msm/mhl/mhl_8334.h new file mode 100644 index 0000000000000000000000000000000000000000..c1d9030650a25fc148e110ea89146e662dfffc15 --- /dev/null +++ b/drivers/video/msm/mhl/mhl_8334.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MHL_MSM_H__ +#define __MHL_MSM_H__ + +#include +#include +#include + +#include "mhl_devcap.h" +#include "mhl_defs.h" + +#define GPIO_MHL_RESET 15 +#define GPIO_MHL_INT 4 + +#define MHL_DEVICE_NAME "sii8334" +#define MHL_DRIVER_NAME "sii8334" + +#define HPD_UP 1 +#define HPD_DOWN 0 + +struct mhl_msm_state_t { + struct i2c_client *i2c_client; + struct i2c_driver *i2c_driver; + uint8_t cur_state; + uint8_t chip_rev_id; + struct msm_mhl_platform_data *mhl_data; +}; + +enum { + TX_PAGE_TPI = 0x00, + TX_PAGE_L0 = 0x01, + TX_PAGE_L1 = 0x02, + TX_PAGE_2 = 0x03, + TX_PAGE_3 = 0x04, + TX_PAGE_CBUS = 0x05, + TX_PAGE_DDC_EDID = 0x06, + TX_PAGE_DDC_SEGM = 0x07, +}; + +enum mhl_st_type { + POWER_STATE_D0_NO_MHL = 0, + POWER_STATE_D0_MHL = 2, + POWER_STATE_D3 = 3, +}; + +enum { + DEV_PAGE_TPI_0 = (0x72), + DEV_PAGE_TX_L0_0 = (0x72), + DEV_PAGE_TPI_1 = (0x76), + DEV_PAGE_TX_L0_1 = (0x76), + DEV_PAGE_TX_L1_0 = (0x7A), + DEV_PAGE_TX_L1_1 = (0x7E), + DEV_PAGE_TX_2_0 = (0x92), + DEV_PAGE_TX_2_1 = (0x96), + DEV_PAGE_TX_3_0 = (0x9A), + DEV_PAGE_TX_3_1 = (0x9E), + DEV_PAGE_CBUS = (0xC8), + DEV_PAGE_DDC_EDID = (0xA0), + DEV_PAGE_DDC_SEGM = (0x60), +}; + +#endif /* __MHL_MSM_H__ */ diff --git a/drivers/video/msm/mhl/mhl_defs.h b/drivers/video/msm/mhl/mhl_defs.h new file mode 100644 index 0000000000000000000000000000000000000000..094874e37ec033c4f9973dd54ead28f945cfdaeb --- /dev/null +++ b/drivers/video/msm/mhl/mhl_defs.h @@ -0,0 +1,222 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MHL_SPEC_DEFS_H__ +#define __MHL_SPEC_DEFS_H__ + +enum DevCapOffset_e { + DEVCAP_OFFSET_DEV_STATE = 0x00, + DEVCAP_OFFSET_MHL_VERSION = 0x01, + DEVCAP_OFFSET_DEV_CAT = 0x02, + DEVCAP_OFFSET_ADOPTER_ID_H = 0x03, + DEVCAP_OFFSET_ADOPTER_ID_L = 0x04, + DEVCAP_OFFSET_VID_LINK_MODE = 0x05, + DEVCAP_OFFSET_AUD_LINK_MODE = 0x06, + DEVCAP_OFFSET_VIDEO_TYPE = 0x07, + DEVCAP_OFFSET_LOG_DEV_MAP = 0x08, + DEVCAP_OFFSET_BANDWIDTH = 0x09, + DEVCAP_OFFSET_FEATURE_FLAG = 0x0A, + DEVCAP_OFFSET_DEVICE_ID_H = 0x0B, + DEVCAP_OFFSET_DEVICE_ID_L = 0x0C, + DEVCAP_OFFSET_SCRATCHPAD_SIZE = 0x0D, + DEVCAP_OFFSET_INT_STAT_SIZE = 0x0E, + DEVCAP_OFFSET_RESERVED = 0x0F, + /* this one must be last */ + DEVCAP_SIZE +}; + +#ifndef __MHL_MSM_8334_REGS_H__ +#define __MHL_MSM_8334_REGS_H__ + +#define BIT0 0x01 +#define BIT1 0x02 +#define BIT2 0x04 +#define BIT3 0x08 +#define BIT4 0x10 +#define BIT5 0x20 +#define BIT6 0x40 +#define BIT7 0x80 + +#define LOW 0 +#define HIGH 1 + +#define MAX_PAGES 8 +#endif + + +/* Version that this chip supports*/ +/* bits 4..7 */ +#define MHL_VER_MAJOR (0x01 << 4) +/* bits 0..3 */ +#define MHL_VER_MINOR 0x01 +#define MHL_VERSION (MHL_VER_MAJOR | MHL_VER_MINOR) + +/*Device Category*/ +#define MHL_DEV_CATEGORY_OFFSET DEVCAP_OFFSET_DEV_CAT +#define MHL_DEV_CATEGORY_POW_BIT (BIT4) + +#define MHL_DEV_CAT_SOURCE 0x02 + +/*Video Link Mode*/ +#define MHL_DEV_VID_LINK_SUPPRGB444 0x01 +#define MHL_DEV_VID_LINK_SUPPYCBCR444 0x02 +#define MHL_DEV_VID_LINK_SUPPYCBCR422 0x04 +#define MHL_DEV_VID_LINK_SUPP_PPIXEL 0x08 +#define MHL_DEV_VID_LINK_SUPP_ISLANDS 0x10 + +/*Audio Link Mode Support*/ +#define MHL_DEV_AUD_LINK_2CH 0x01 +#define MHL_DEV_AUD_LINK_8CH 0x02 + + +/*Feature Flag in the devcap*/ +#define MHL_DEV_FEATURE_FLAG_OFFSET DEVCAP_OFFSET_FEATURE_FLAG +/* Dongles have freedom to not support RCP */ +#define MHL_FEATURE_RCP_SUPPORT BIT0 +/* Dongles have freedom to not support RAP */ +#define MHL_FEATURE_RAP_SUPPORT BIT1 +/* Dongles have freedom to not support SCRATCHPAD */ +#define MHL_FEATURE_SP_SUPPORT BIT2 + +/*Logical Dev Map*/ +#define MHL_DEV_LD_DISPLAY (0x01 << 0) +#define MHL_DEV_LD_VIDEO (0x01 << 1) +#define MHL_DEV_LD_AUDIO (0x01 << 2) +#define MHL_DEV_LD_MEDIA (0x01 << 3) +#define MHL_DEV_LD_TUNER (0x01 << 4) +#define MHL_DEV_LD_RECORD (0x01 << 5) +#define MHL_DEV_LD_SPEAKER (0x01 << 6) +#define MHL_DEV_LD_GUI (0x01 << 7) + +/*Bandwidth*/ +/* 225 MHz */ +#define MHL_BANDWIDTH_LIMIT 22 + + +#define MHL_STATUS_REG_CONNECTED_RDY 0x30 +#define MHL_STATUS_REG_LINK_MODE 0x31 + +#define MHL_STATUS_DCAP_RDY BIT0 + +#define MHL_STATUS_CLK_MODE_MASK 0x07 +#define MHL_STATUS_CLK_MODE_PACKED_PIXEL 0x02 +#define MHL_STATUS_CLK_MODE_NORMAL 0x03 +#define MHL_STATUS_PATH_EN_MASK 0x08 +#define MHL_STATUS_PATH_ENABLED 0x08 +#define MHL_STATUS_PATH_DISABLED 0x00 +#define MHL_STATUS_MUTED_MASK 0x10 + +#define MHL_RCHANGE_INT 0x20 +#define MHL_DCHANGE_INT 0x21 + +#define MHL_INT_DCAP_CHG BIT0 +#define MHL_INT_DSCR_CHG BIT1 +#define MHL_INT_REQ_WRT BIT2 +#define MHL_INT_GRT_WRT BIT3 + +/* On INTR_1 the EDID_CHG is located at BIT 0*/ +#define MHL_INT_EDID_CHG BIT1 + +/* This contains one nibble each - max offset */ +#define MHL_INT_AND_STATUS_SIZE 0x33 +#define MHL_SCRATCHPAD_SIZE 16 +/* manually define highest number */ +#define MHL_MAX_BUFFER_SIZE MHL_SCRATCHPAD_SIZE + + + +enum { + /* RCP sub-command */ + MHL_MSC_MSG_RCP = 0x10, + /* RCP Acknowledge sub-command */ + MHL_MSC_MSG_RCPK = 0x11, + /* RCP Error sub-command */ + MHL_MSC_MSG_RCPE = 0x12, + /* Mode Change Warning sub-command */ + MHL_MSC_MSG_RAP = 0x20, + /* MCW Acknowledge sub-command */ + MHL_MSC_MSG_RAPK = 0x21, +}; + +#define RCPE_NO_ERROR 0x00 +#define RCPE_INEEFECTIVE_KEY_CODE 0x01 +#define RCPE_BUSY 0x02 +/* MHL spec related defines*/ +enum { + /* Command or Data byte acknowledge */ + MHL_ACK = 0x33, + /* Command or Data byte not acknowledge */ + MHL_NACK = 0x34, + /* Transaction abort */ + MHL_ABORT = 0x35, + /* 0xE0 - Write one status register strip top bit */ + MHL_WRITE_STAT = 0x60 | 0x80, + /* Write one interrupt register */ + MHL_SET_INT = 0x60, + /* Read one register */ + MHL_READ_DEVCAP = 0x61, + /* Read CBUS revision level from follower */ + MHL_GET_STATE = 0x62, + /* Read vendor ID value from follower. */ + MHL_GET_VENDOR_ID = 0x63, + /* Set Hot Plug Detect in follower */ + MHL_SET_HPD = 0x64, + /* Clear Hot Plug Detect in follower */ + MHL_CLR_HPD = 0x65, + /* Set Capture ID for downstream device. */ + MHL_SET_CAP_ID = 0x66, + /* Get Capture ID from downstream device. */ + MHL_GET_CAP_ID = 0x67, + /* VS command to send RCP sub-commands */ + MHL_MSC_MSG = 0x68, + /* Get Vendor-Specific command error code. */ + MHL_GET_SC1_ERRORCODE = 0x69, + /* Get DDC channel command error code. */ + MHL_GET_DDC_ERRORCODE = 0x6A, + /* Get MSC command error code. */ + MHL_GET_MSC_ERRORCODE = 0x6B, + /* Write 1-16 bytes to responder's scratchpad. */ + MHL_WRITE_BURST = 0x6C, + /* Get channel 3 command error code. */ + MHL_GET_SC3_ERRORCODE = 0x6D, +}; + +/* Turn content streaming ON. */ +#define MHL_RAP_CONTENT_ON 0x10 +/* Turn content streaming OFF. */ +#define MHL_RAP_CONTENT_OFF 0x11 + +/* + * + * MHL Timings applicable to this driver. + * + */ +/* 100 - 1000 milliseconds. Per MHL 1.0 Specs */ +#define T_SRC_VBUS_CBUS_TO_STABLE (200) +/* 20 milliseconds. Per MHL 1.0 Specs */ +#define T_SRC_WAKE_PULSE_WIDTH_1 (20) +/* 60 milliseconds. Per MHL 1.0 Specs */ +#define T_SRC_WAKE_PULSE_WIDTH_2 (60) + +/* 100 - 1000 milliseconds. Per MHL 1.0 Specs */ +#define T_SRC_WAKE_TO_DISCOVER (500) + +#define T_SRC_VBUS_CBUS_T0_STABLE (500) + +/* Allow RSEN to stay low this much before reacting.*/ +#define T_SRC_RSEN_DEGLITCH (100) + +/* Wait this much after connection before reacting to RSEN (300-500ms)*/ +/* Per specs between 300 to 500 ms*/ +#define T_SRC_RXSENSE_CHK (400) + +#endif /* __MHL_SPEC_DEFS_H__ */ diff --git a/drivers/video/msm/mhl/mhl_devcap.h b/drivers/video/msm/mhl/mhl_devcap.h new file mode 100644 index 0000000000000000000000000000000000000000..6d01daf268be654f7bbc7091244f109ab81abb24 --- /dev/null +++ b/drivers/video/msm/mhl/mhl_devcap.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MHL_DEVCAP_H__ +#define __MHL_DEVCAP_H__ + +#define SILICON_IMAGE_ADOPTER_ID 322 +#define TRANSCODER_DEVICE_ID 0x8334 + +#define MHL_DEV_LD_AUDIO (0x01 << 2) +#define MHL_DEV_LD_VIDEO (0x01 << 1) +#define MHL_DEV_LD_MEDIA (0x01 << 3) +#define MHL_DEV_LD_GUI (0x01 << 7) +#define MHL_LOGICAL_DEVICE_MAP (MHL_DEV_LD_AUDIO |\ + MHL_DEV_LD_VIDEO | MHL_DEV_LD_MEDIA | MHL_DEV_LD_GUI) + +#define DEVCAP_VAL_DEV_STATE 0 +#define DEVCAP_VAL_MHL_VERSION MHL_VERSION +#define DEVCAP_VAL_DEV_CAT (MHL_DEV_CAT_SOURCE |\ + MHL_DEV_CATEGORY_POW_BIT) +#define DEVCAP_VAL_ADOPTER_ID_H (uint8_t)(SILICON_IMAGE_ADOPTER_ID >> 8) +#define DEVCAP_VAL_ADOPTER_ID_L (uint8_t)(SILICON_IMAGE_ADOPTER_ID & 0xFF) +#define DEVCAP_VAL_VID_LINK_MODE MHL_DEV_VID_LINK_SUPPRGB444 +#define DEVCAP_VAL_AUD_LINK_MODE MHL_DEV_AUD_LINK_2CH +#define DEVCAP_VAL_VIDEO_TYPE 0 +#define DEVCAP_VAL_LOG_DEV_MAP MHL_LOGICAL_DEVICE_MAP +#define DEVCAP_VAL_BANDWIDTH 0 +#define DEVCAP_VAL_FEATURE_FLAG (MHL_FEATURE_RCP_SUPPORT |\ + MHL_FEATURE_RAP_SUPPORT | MHL_FEATURE_SP_SUPPORT) +#define DEVCAP_VAL_DEVICE_ID_H (uint8_t)(TRANSCODER_DEVICE_ID >> 8) +#define DEVCAP_VAL_DEVICE_ID_L (uint8_t)(TRANSCODER_DEVICE_ID & 0xFF) +#define DEVCAP_VAL_SCRATCHPAD_SIZE MHL_SCRATCHPAD_SIZE +#define DEVCAP_VAL_INT_STAT_SIZE MHL_INT_AND_STATUS_SIZE +#define DEVCAP_VAL_RESERVED 0 + +#endif /* __MHL_DEVCAP_H__ */ diff --git a/drivers/video/msm/mhl/mhl_i2c_utils.c b/drivers/video/msm/mhl/mhl_i2c_utils.c new file mode 100644 index 0000000000000000000000000000000000000000..596af2e0d197f8d6c8437463836cc79f4d106f97 --- /dev/null +++ b/drivers/video/msm/mhl/mhl_i2c_utils.c @@ -0,0 +1,107 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include + +#include "mhl_i2c_utils.h" +#include "mhl_8334.h" + +#define DEBUG + +uint8_t slave_addrs[MAX_PAGES] = { + DEV_PAGE_TPI_0 , + DEV_PAGE_TX_L0_0 , + DEV_PAGE_TX_L1_0 , + DEV_PAGE_TX_2_0 , + DEV_PAGE_TX_3_0 , + DEV_PAGE_CBUS , + DEV_PAGE_DDC_EDID , + DEV_PAGE_DDC_SEGM , +}; + +int mhl_i2c_reg_read(uint8_t slave_addr_index, uint8_t reg_offset) +{ + struct i2c_msg msgs[2]; + uint8_t buffer = 0; + int ret = -1; + + pr_debug("MRR: Reading from slave_addr_index=[%x] and offset=[%x]\n", + slave_addr_index, reg_offset); + pr_debug("MRR: Addr slave_addr_index=[%x]\n", + slave_addrs[slave_addr_index]); + + /* Slave addr */ + msgs[0].addr = slave_addrs[slave_addr_index] >> 1; + msgs[1].addr = slave_addrs[slave_addr_index] >> 1; + + /* Write Command */ + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + + /* Register offset for the next transaction */ + msgs[0].buf = ®_offset; + msgs[1].buf = &buffer; + + /* Offset is 1 Byte long */ + msgs[0].len = 1; + msgs[1].len = 1; + + ret = i2c_transfer(mhl_msm_state->i2c_client->adapter, msgs, 2); + if (ret < 1) { + pr_err("I2C READ FAILED=[%d]\n", ret); + return -EACCES; + } + pr_err("Buffer is [%x]\n", buffer); + return buffer; +} + + +int mhl_i2c_reg_write(uint8_t slave_addr_index, uint8_t reg_offset, + uint8_t value) +{ + return mhl_i2c_reg_write_cmds(slave_addr_index, reg_offset, &value, 1); +} + +int mhl_i2c_reg_write_cmds(uint8_t slave_addr_index, uint8_t reg_offset, + uint8_t *value, uint16_t count) +{ + struct i2c_msg msgs[1]; + uint8_t data[2]; + int status = -EACCES; + + msgs[0].addr = slave_addrs[slave_addr_index] >> 1; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = data; + data[0] = reg_offset; + data[1] = *value; + + status = i2c_transfer(mhl_msm_state->i2c_client->adapter, msgs, 1); + if (status < 1) { + pr_err("I2C WRITE FAILED=[%d]\n", status); + return -EACCES; + } + + return status; +} + +void mhl_i2c_reg_modify(uint8_t slave_addr_index, uint8_t reg_offset, + uint8_t mask, uint8_t val) +{ + uint8_t temp; + + temp = mhl_i2c_reg_read(slave_addr_index, reg_offset); + temp &= (~mask); + temp |= (mask & val); + mhl_i2c_reg_write(slave_addr_index, reg_offset, temp); +} + diff --git a/drivers/video/msm/mhl/mhl_i2c_utils.h b/drivers/video/msm/mhl/mhl_i2c_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..76498d4af297b030e4dc4df07e71e9d0e40ea1f9 --- /dev/null +++ b/drivers/video/msm/mhl/mhl_i2c_utils.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MHL_I2C_UTILS_H__ +#define __MHL_I2C_UTILS_H__ + +#include +#include + +#include "mhl_defs.h" + +/* + * I2C command to the adapter to append + * the buffer from next msg to this one. + */ +#define I2C_M_APPND_NXT_WR 0x0002 + +extern uint8_t slave_addrs[MAX_PAGES]; +extern struct mhl_msm_state_t *mhl_msm_state; + +int mhl_i2c_reg_read(uint8_t slave_addr_index, uint8_t reg_offset); +int mhl_i2c_reg_write(uint8_t slave_addr_index, uint8_t reg_offset, + uint8_t value); +int mhl_i2c_reg_write_cmds(uint8_t slave_addr_index, uint8_t reg_offset, + uint8_t *value, uint16_t count); +void mhl_i2c_reg_modify(uint8_t slave_addr_index, uint8_t reg_offset, + uint8_t mask, uint8_t val); + +#endif /* __MHL_I2C_UTILS_H__ */ diff --git a/drivers/video/msm/mhl_api.h b/drivers/video/msm/mhl_api.h new file mode 100644 index 0000000000000000000000000000000000000000..a4364ea4439226ac87918961c2591cd0ed11e2c8 --- /dev/null +++ b/drivers/video/msm/mhl_api.h @@ -0,0 +1,26 @@ + +/* 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 __MHL_API_H__ +#define __MHL_API_H__ + +#ifdef CONFIG_FB_MSM_HDMI_MHL_8334 +bool mhl_is_connected(void); +#else +static bool mhl_is_connected(void) +{ + return false; +} +#endif + +#endif /* __MHL_API_H__ */ diff --git a/drivers/video/msm/mipi_NT35510.c b/drivers/video/msm/mipi_NT35510.c new file mode 100644 index 0000000000000000000000000000000000000000..964df4e550e281ba718de072a99ec78746f80546 --- /dev/null +++ b/drivers/video/msm/mipi_NT35510.c @@ -0,0 +1,609 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_NT35510.h" + +static struct msm_panel_common_pdata *mipi_nt35510_pdata; +static struct dsi_buf nt35510_tx_buf; +static struct dsi_buf nt35510_rx_buf; + +#define NT35510_SLEEP_OFF_DELAY 150 +#define NT35510_DISPLAY_ON_DELAY 150 + +/* common setting */ +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}; +static char write_ram[2] = {0x2c, 0x00}; /* write ram */ + +static struct dsi_cmd_desc nt35510_display_off_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 150, sizeof(display_off), display_off}, + {DTYPE_DCS_WRITE, 1, 0, 0, 150, sizeof(enter_sleep), enter_sleep} +}; + +static char cmd0[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x01, +}; +static char cmd1[4] = { + 0xBC, 0x00, 0xA0, 0x00, +}; +static char cmd2[4] = { + 0xBD, 0x00, 0xA0, 0x00, +}; +static char cmd3[3] = { + 0xBE, 0x00, 0x79, +}; +static char cmd4[53] = { + 0xD1, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char cmd5[53] = { + 0xD2, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char cmd6[53] = { + 0xD3, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char cmd7[53] = { + 0xD4, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char cmd8[53] = { + 0xD5, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char cmd9[53] = { + 0xD6, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char cmd10[4] = { + 0xB0, 0x0A, 0x0A, 0x0A, +}; +static char cmd11[4] = { + 0xB1, 0x0A, 0x0A, 0x0A, +}; +static char cmd12[4] = { + 0xBA, 0x24, 0x24, 0x24, +}; +static char cmd13[4] = { + 0xB9, 0x24, 0x24, 0x24, +}; +static char cmd14[4] = { + 0xB8, 0x24, 0x24, 0x24, +}; +static char cmd15[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x00, +}; +static char cmd16[2] = { + 0xB3, 0x00, +}; +static char cmd17[2] = { + 0xB4, 0x10, +}; +static char cmd18[2] = { + 0xB6, 0x02, +}; +static char cmd19[3] = { + 0xB1, 0xEC, 0x00, +}; +static char cmd20[4] = { + 0xBC, 0x05, 0x05, 0x05, +}; +static char cmd21[3] = { + 0xB7, 0x20, 0x20, +}; +static char cmd22[5] = { + 0xB8, 0x01, 0x03, 0x03, + 0x03, +}; +static char cmd23[19] = { + 0xC8, 0x01, 0x00, 0x78, + 0x50, 0x78, 0x50, 0x78, + 0x50, 0x78, 0x50, 0xC8, + 0x3C, 0x3C, 0xC8, 0xC8, + 0x3C, 0x3C, 0xC8, +}; +static char cmd24[6] = { + 0xBD, 0x01, 0x84, 0x07, + 0x31, 0x00, +}; +static char cmd25[6] = { + 0xBE, 0x01, 0x84, 0x07, + 0x31, 0x00, +}; +static char cmd26[6] = { + 0xBF, 0x01, 0x84, 0x07, + 0x31, 0x00, +}; +static char cmd27[2] = { + 0x35, 0x00, +}; +static char config_MADCTL[2] = {0x36, 0x00}; +static struct dsi_cmd_desc nt35510_cmd_display_on_cmds[] = { + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd0), cmd0}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd1), cmd1}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd2), cmd2}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd3), cmd3}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd4), cmd4}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd5), cmd5}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd6), cmd6}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd7), cmd7}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd8), cmd8}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd9), cmd9}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd10), cmd10}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd11), cmd11}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd12), cmd12}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd13), cmd13}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd14), cmd14}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd15), cmd15}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd16), cmd16}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd17), cmd17}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd18), cmd18}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd19), cmd19}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd20), cmd20}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd21), cmd21}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd22), cmd22}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd23), cmd23}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd24), cmd24}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd25), cmd25}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd26), cmd26}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(cmd27), cmd27}, + + {DTYPE_DCS_WRITE, 1, 0, 0, 150, sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(display_on), display_on}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, 150, + sizeof(config_MADCTL), config_MADCTL}, + + {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(write_ram), write_ram}, +}; + +static char video0[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x01, +}; +static char video1[4] = { + 0xBC, 0x00, 0xA0, 0x00, +}; +static char video2[4] = { + 0xBD, 0x00, 0xA0, 0x00, +}; +static char video3[3] = { + 0xBE, 0x00, 0x79, +}; +static char video4[53] = { + 0xD1, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char video5[53] = { + 0xD2, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char video6[53] = { + 0xD3, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char video7[53] = { + 0xD4, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char video8[53] = { + 0xD5, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char video9[53] = { + 0xD6, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x32, 0x00, + 0x4F, 0x00, 0x65, 0x00, + 0x8B, 0x00, 0xA8, 0x00, + 0xD5, 0x00, 0xF7, 0x01, + 0x2B, 0x01, 0x54, 0x01, + 0x8E, 0x01, 0xBB, 0x01, + 0xBC, 0x01, 0xE3, 0x02, + 0x08, 0x02, 0x1C, 0x02, + 0x39, 0x02, 0x4F, 0x02, + 0x76, 0x02, 0xA3, 0x02, + 0xE3, 0x03, 0x12, 0x03, + 0x4C, 0x03, 0x66, 0x03, + 0x9A, +}; +static char video10[4] = { + 0xB0, 0x0A, 0x0A, 0x0A, +}; +static char video11[4] = { + 0xB1, 0x0A, 0x0A, 0x0A, +}; +static char video12[4] = { + 0xBA, 0x24, 0x24, 0x24, +}; +static char video13[4] = { + 0xB9, 0x24, 0x24, 0x24, +}; +static char video14[4] = { + 0xB8, 0x24, 0x24, 0x24, +}; +static char video15[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x00, +}; +static char video16[2] = { + 0xB3, 0x00, +}; +static char video17[2] = { + 0xB4, 0x10, +}; +static char video18[2] = { + 0xB6, 0x02, +}; +static char video19[3] = { + 0xB1, 0xFC, 0x00, +}; +static char video20[4] = { + 0xBC, 0x05, 0x05, 0x05, +}; +static char video21[3] = { + 0xB7, 0x20, 0x20, +}; +static char video22[5] = { + 0xB8, 0x01, 0x03, 0x03, + 0x03, +}; +static char video23[19] = { + 0xC8, 0x01, 0x00, 0x78, + 0x50, 0x78, 0x50, 0x78, + 0x50, 0x78, 0x50, 0xC8, + 0x3C, 0x3C, 0xC8, 0xC8, + 0x3C, 0x3C, 0xC8, +}; +static char video24[6] = { + 0xBD, 0x01, 0x84, 0x07, + 0x31, 0x00, +}; +static char video25[6] = { + 0xBE, 0x01, 0x84, 0x07, + 0x31, 0x00, +}; +static char video26[6] = { + 0xBF, 0x01, 0x84, 0x07, + 0x31, 0x00, +}; +static char video27[2] = { + 0x35, 0x00, +}; +static char config_video_MADCTL[2] = {0x36, 0xC0}; +static struct dsi_cmd_desc nt35510_video_display_on_cmds[] = { + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video0), video0}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video1), video1}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video2), video2}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video3), video3}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video4), video4}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video5), video5}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video6), video6}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video7), video7}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video8), video8}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video9), video9}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video10), video10}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video11), video11}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video12), video12}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video13), video13}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video14), video14}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video15), video15}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video16), video16}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video17), video17}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video18), video18}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video19), video19}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video20), video20}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video21), video21}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video22), video22}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video23), video23}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video24), video24}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video25), video25}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video26), video26}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 50, sizeof(video27), video27}, + {DTYPE_DCS_WRITE, 1, 0, 0, NT35510_SLEEP_OFF_DELAY, sizeof(exit_sleep), + exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, NT35510_DISPLAY_ON_DELAY, sizeof(display_on), + display_on}, +}; + +static struct dsi_cmd_desc nt35510_video_display_on_cmds_rotate[] = { + {DTYPE_DCS_WRITE1, 1, 0, 0, 150, + sizeof(config_video_MADCTL), config_video_MADCTL}, +}; +static int mipi_nt35510_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + static int rotate; + mfd = platform_get_drvdata(pdev); + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi = &mfd->panel_info.mipi; + + if (mipi_nt35510_pdata && mipi_nt35510_pdata->rotate_panel) + rotate = mipi_nt35510_pdata->rotate_panel(); + + if (mipi->mode == DSI_VIDEO_MODE) { + mipi_dsi_cmds_tx(mfd, &nt35510_tx_buf, + nt35510_video_display_on_cmds, + ARRAY_SIZE(nt35510_video_display_on_cmds)); + + if (rotate) { + mipi_dsi_cmds_tx(mfd, &nt35510_tx_buf, + nt35510_video_display_on_cmds_rotate, + ARRAY_SIZE(nt35510_video_display_on_cmds_rotate)); + } + } else if (mipi->mode == DSI_CMD_MODE) { + mipi_dsi_cmds_tx(mfd, &nt35510_tx_buf, + nt35510_cmd_display_on_cmds, + ARRAY_SIZE(nt35510_cmd_display_on_cmds)); + } + + return 0; +} + +static int mipi_nt35510_lcd_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + pr_debug("mipi_nt35510_lcd_off E\n"); + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi_dsi_cmds_tx(mfd, &nt35510_tx_buf, nt35510_display_off_cmds, + ARRAY_SIZE(nt35510_display_off_cmds)); + + pr_debug("mipi_nt35510_lcd_off X\n"); + return 0; +} + +static int __devinit mipi_nt35510_lcd_probe(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + + if (pdev->id == 0) { + mipi_nt35510_pdata = pdev->dev.platform_data; + if (mipi_nt35510_pdata->bl_lock) + spin_lock_init(&mipi_nt35510_pdata->bl_spinlock); + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mipi_nt35510_lcd_probe, + .driver = { + .name = "mipi_NT35510", + }, +}; + +static void mipi_nt35510_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + unsigned long flags; + bl_level = mfd->bl_level; + + if (mipi_nt35510_pdata->bl_lock) { + spin_lock_irqsave(&mipi_nt35510_pdata->bl_spinlock, flags); + mipi_nt35510_pdata->pmic_backlight(bl_level); + spin_unlock_irqrestore(&mipi_nt35510_pdata->bl_spinlock, flags); + } else + mipi_nt35510_pdata->pmic_backlight(bl_level); +} + +static struct msm_fb_panel_data nt35510_panel_data = { + .on = mipi_nt35510_lcd_on, + .off = mipi_nt35510_lcd_off, + .set_backlight = mipi_nt35510_set_backlight, +}; + +static int ch_used[3]; + +static int mipi_nt35510_lcd_init(void) +{ + mipi_dsi_buf_alloc(&nt35510_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&nt35510_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} +int mipi_nt35510_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; + + ret = mipi_nt35510_lcd_init(); + if (ret) { + pr_err("mipi_nt35510_lcd_init() failed with ret %u\n", ret); + return ret; + } + + pdev = platform_device_alloc("mipi_NT35510", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + nt35510_panel_data.panel_info = *pinfo; + ret = platform_device_add_data(pdev, &nt35510_panel_data, + sizeof(nt35510_panel_data)); + if (ret) { + pr_debug("%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + pr_debug("%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} diff --git a/drivers/video/msm/mipi_NT35510.h b/drivers/video/msm/mipi_NT35510.h new file mode 100644 index 0000000000000000000000000000000000000000..5c81875ea1e000db0c00f358cf87858aa3280f5a --- /dev/null +++ b/drivers/video/msm/mipi_NT35510.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_NT35510_H +#define MIPI_NT35510_H + +int mipi_nt35510_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_NT35510_H */ diff --git a/drivers/video/msm/mipi_NT35510_cmd_wvga_pt.c b/drivers/video/msm/mipi_NT35510_cmd_wvga_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..f052e9346fb3b4760b024b1ea6af3c00049368ba --- /dev/null +++ b/drivers/video/msm/mipi_NT35510_cmd_wvga_pt.c @@ -0,0 +1,98 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_NT35510.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_cmd_mode_phy_db = { + /* 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}, +}; + +static int mipi_cmd_nt35510_wvga_pt_init(void) +{ + int ret; + + if (msm_fb_detect_client("mipi_cmd_nt35510_wvga")) + return 0; + + pinfo.xres = 480; + pinfo.yres = 800; + pinfo.type = MIPI_CMD_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + 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.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 31; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.clk_rate = 499000000; + + 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_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_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.te_sel = 1; /* 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 = 0x0; + pinfo.mipi.dlane_swap = 0x01; + + ret = mipi_nt35510_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_nt35510_wvga_pt_init); diff --git a/drivers/video/msm/mipi_NT35510_video_wvga_pt.c b/drivers/video/msm/mipi_NT35510_video_wvga_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..4e97d994bb2e9e4b3d42acb823d4eb5893812381 --- /dev/null +++ b/drivers/video/msm/mipi_NT35510_video_wvga_pt.c @@ -0,0 +1,108 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_NT35510.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* 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}, +}; + +static int mipi_video_nt35510_wvga_pt_init(void) +{ + int ret; + + if (msm_fb_detect_client("mipi_video_nt35510_wvga")) + return 0; + + pinfo.xres = 480; + pinfo.yres = 800; + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + 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.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + /* number of dot_clk cycles HSYNC active edge is + delayed from VSYNC active edge */ + pinfo.lcdc.hsync_skew = 0; + pinfo.clk_rate = 499000000; + pinfo.bl_max = 31; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + /* send HSA and HE following VS/VE packet */ + pinfo.mipi.pulse_mode_hsa_he = TRUE; + pinfo.mipi.hfp_power_stop = TRUE; /* LP-11 during the HFP period */ + pinfo.mipi.hbp_power_stop = TRUE; /* LP-11 during the HBP period */ + pinfo.mipi.hsa_power_stop = TRUE; /* LP-11 during the HSA period */ + /* LP-11 or let Command Mode Engine send packets in + HS or LP mode for the BLLP of the last line of a frame */ + pinfo.mipi.eof_bllp_power_stop = TRUE; + /* LP-11 or let Command Mode Engine send packets in + HS or LP mode for packets sent during BLLP period */ + pinfo.mipi.bllp_power_stop = TRUE; + + 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; /* 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; /* FIXME */ + + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + pinfo.mipi.dlane_swap = 0x01; + /* append EOT at the end of data burst */ + pinfo.mipi.tx_eot_append = 0x01; + + ret = mipi_nt35510_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_nt35510_wvga_pt_init); diff --git a/drivers/video/msm/mipi_chimei_wuxga.c b/drivers/video/msm/mipi_chimei_wuxga.c new file mode 100644 index 0000000000000000000000000000000000000000..6ddf74d24545fa28297352a67a05553150a2d577 --- /dev/null +++ b/drivers/video/msm/mipi_chimei_wuxga.c @@ -0,0 +1,196 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Chimei WUXGA LVDS Panel driver. + * The panel model is N101JSF-L21. + * + * The panel interface includes: + * 1. LVDS input for video (clock & data). + * 2. few configuration pins to control 3D module: Enable, Mode (2D/3D). + * 3. Backlight LED control (PWM 200 HZ). + * + * This panel is controled via the Toshiba DSI-to-LVDS bridge. + * + */ + +/* #define DEBUG 1 */ + +#include "msm_fb.h" +#include "msm_fb_panel.h" +#include "mipi_dsi.h" +#include "mipi_tc358764_dsi2lvds.h" + +#define MHZ (1000*1000) + +/** + * Panel info parameters. + * The panel info is passed to the mipi framebuffer driver. + */ +static struct msm_panel_info chimei_wuxga_pinfo; + +/** + * The mipi_dsi_phy_ctrl is calculated according to the + * "dsi_timing_program.xlsm" excel sheet. + * Output is based on: 1200x1920, RGB565, 4 lanes , 58 frames + * per second. + */ +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* DSIPHY_REGULATOR_CTRL */ + .regulator = {0x03, 0x0a, 0x04, 0x00, 0x20}, /* common 8960 */ + /* DSIPHY_CTRL */ + .ctrl = {0x5f, 0x00, 0x00, 0x10}, /* common 8960 */ + /* DSIPHY_STRENGTH_CTRL */ + .strength = {0xff, 0x00, 0x06, 0x00}, /* common 8960 */ + /* DSIPHY_TIMING_CTRL */ + .timing = { 0xC9, 0x92, 0x29, /* panel specific */ + 0, /* DSIPHY_TIMING_CTRL_3 = 0 */ + 0x2D, 0x9B, 0x2B, 0x94, 0x2D, 0x03, 0x04}, /* panel specific */ + + /* DSIPHY_PLL_CTRL */ + .pll = { 0x00, /* common 8960 */ + /* VCO */ + 0x30, (0x01 | 0x30) , (0x19 | 0xC0), /* panel specific */ + 0x00, 0x50, 0x48, 0x63, + 0x77, 0x88, 0x99, /* Auto update by dsi-mipi driver */ + 0x00, 0x14, 0x03, 0x00, 0x02, /* common 8960 */ + 0x00, 0x20, 0x00, 0x01 }, /* common 8960 */ +}; + +/** + * Module init. + * + * Register the panel-info. + * + * Some parameters are from the panel datasheet + * and other are *calculated* by the "dsi_timing_program.xlsm" + * excel file + * + * @return int + */ +static int __init mipi_chimei_wuxga_init(void) +{ + int ret; + struct msm_panel_info *pinfo = &chimei_wuxga_pinfo; + + if (msm_fb_detect_client("mipi_video_chimei_wuxga")) + return 0; + + pr_info("mipi-dsi chimei wuxga (1200x1920) driver ver 1.0.\n"); + + /* Portrait */ + pinfo->xres = 1200; + pinfo->yres = 1920; + pinfo->type = MIPI_VIDEO_PANEL; + pinfo->pdest = DISPLAY_1; /* Primary Display */ + pinfo->wait_cycle = 0; + pinfo->bpp = 24; /* RGB565 requires 24 bits-per-pixel :-O */ + pinfo->fb_num = 2; /* using two frame buffers */ + + /* + * The CMI panel requires 80 MHZ LVDS-CLK. + * The D2L bridge drives the LVDS-CLK from the DSI-CLK. + * The DSI-CLK = bitclk/2, 640 MHZ/2= 320 MHZ. + * LVDS-CLK = DSI-CLK/4 , 320 MHZ/4= 80 MHZ. + */ + + pinfo->clk_rate = 635 * MHZ ; /* bitclk Calculated */ + + /* + * this panel is operated by DE, + * vsycn and hsync are ignored + */ + + pinfo->lcdc.h_front_porch = 160-48-32; /* thfp */ + pinfo->lcdc.h_back_porch = 48; /* thb */ + pinfo->lcdc.h_pulse_width = 32; /* thpw */ + + pinfo->lcdc.v_front_porch = 26-3-6; /* tvfp */ + pinfo->lcdc.v_back_porch = 3; /* tvb */ + pinfo->lcdc.v_pulse_width = 6; /* tvpw */ + + pinfo->lcdc.border_clr = 0; /* black */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + + pinfo->lcdc.hsync_skew = 0; + + /* Backlight levels - controled via PMIC pwm gpio */ + pinfo->bl_max = PWM_LEVEL; + pinfo->bl_min = 1; + + /* mipi - general */ + pinfo->mipi.vc = 0; /* virtual channel */ + pinfo->mipi.rgb_swap = DSI_RGB_SWAP_RGB; + pinfo->mipi.tx_eot_append = true; + pinfo->mipi.t_clk_post = 34; /* Calculated */ + pinfo->mipi.t_clk_pre = 69; /* Calculated */ + + pinfo->mipi.dsi_phy_db = &dsi_video_mode_phy_db; + + /* Four lanes are recomended for 1920x1200 at 60 frames per second */ + pinfo->mipi.frame_rate = 60; + pinfo->mipi.data_lane0 = true; + pinfo->mipi.data_lane1 = true; + pinfo->mipi.data_lane2 = true; + pinfo->mipi.data_lane3 = true; + + pinfo->mipi.mode = DSI_VIDEO_MODE; + /* + * Note: The CMI panel input is RGB888, + * thus the DSI-to-LVDS bridge output is RGB888. + * This parameter selects the DSI-Core output to the bridge. + */ + pinfo->mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB565; + + /* mipi - video mode */ + pinfo->mipi.traffic_mode = DSI_NON_BURST_SYNCH_EVENT; + pinfo->mipi.pulse_mode_hsa_he = false; /* sync mode */ + + 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; + + /* mipi - command mode */ + pinfo->mipi.te_sel = 1; /* TE from vsycn gpio */ + pinfo->mipi.interleave_max = 1; + /* The bridge supports only Generic Read/Write commands */ + pinfo->mipi.insert_dcs_cmd = false; + pinfo->mipi.wr_mem_continue = 0; + pinfo->mipi.wr_mem_start = 0; + pinfo->mipi.stream = false; /* dma_p */ + pinfo->mipi.mdp_trigger = DSI_CMD_TRIGGER_NONE; + pinfo->mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + /* + * toshiba d2l chip does not need max_pkt_size dcs cmd + * client reply len is directly configure through + * RDPKTLN register (0x0404) + */ + pinfo->mipi.no_max_pkt_size = 1; + pinfo->mipi.force_clk_lane_hs = 1; + + pinfo->is_3d_panel = FB_TYPE_3D_PANEL; + + ret = mipi_tc358764_dsi2lvds_register(pinfo, MIPI_DSI_PRIM, 1); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_chimei_wuxga_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Chimei WUXGA LVDS Panel driver"); +MODULE_AUTHOR("Amir Samuelov "); diff --git a/drivers/video/msm/mipi_chimei_wxga_pt.c b/drivers/video/msm/mipi_chimei_wxga_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..1ab50b7c1be5e8f2b0350c6ae29b7c0d1097c220 --- /dev/null +++ b/drivers/video/msm/mipi_chimei_wxga_pt.c @@ -0,0 +1,191 @@ +/* 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. + * + */ + +/* + * Chimei WXGA LVDS Panel driver. + * The panel model is N101BCG-L21. + * + * The panel interface includes: + * 1. LVDS input for video (clock & data). + * 2. few configuration pins: Up/Down scan, Left/Right scan etc. + * 3. Backlight LED control. + * 4. I2C interface for EEPROM access. + * + * The Panel is *internally* controlled by Novatek NT51009 controller. + * However, the "3-wire" SPI interface is not exposed on the panel interface. + * + * This panel is controled via the Toshiba DSI-to-LVDS bridge. + * + */ + +/* #define DEBUG 1 */ + +#include "msm_fb.h" +#include "msm_fb_panel.h" +#include "mipi_dsi.h" +#include "mipi_tc358764_dsi2lvds.h" + +#define MHZ (1000*1000) + +/** + * Panel info parameters. + * The panel info is passed to the mipi framebuffer driver. + */ +static struct msm_panel_info chimei_wxga_pinfo; + +/** + * The mipi_dsi_phy_ctrl is calculated according to the + * "DSI_panel_bring_up_guide_ver3.docm" using the excel sheet. + * Output is based on: 1366x768, RGB888, 4 lanes , 60 frames per second. + */ +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* DSIPHY_REGULATOR_CTRL */ + .regulator = {0x03, 0x0a, 0x04, 0x00, 0x20}, /* common 8960 */ + /* DSIPHY_CTRL */ + .ctrl = {0x5f, 0x00, 0x00, 0x10}, /* common 8960 */ + /* DSIPHY_STRENGTH_CTRL */ + .strength = {0xff, 0x00, 0x06, 0x00}, /* common 8960 */ + /* DSIPHY_TIMING_CTRL */ + .timing = { 0xB6, 0x8D, 0x1E, /* panel specific */ + 0, /* DSIPHY_TIMING_CTRL_3 = 0 */ + 0x21, 0x95, 0x21, 0x8F, 0x21, 0x03, 0x04}, /* panel specific */ + + /* DSIPHY_PLL_CTRL */ + .pll = { 0x00, /* common 8960 */ + /* VCO */ + 0xC6, 0x01, 0x19, /* panel specific */ + 0x00, 0x50, 0x48, 0x63, + 0x77, 0x88, 0x99, /* Auto update by dsi-mipi driver */ + 0x00, 0x14, 0x03, 0x00, 0x02, /* common 8960 */ + 0x00, 0x20, 0x00, 0x01 }, /* common 8960 */ +}; + +/** + * Module init. + * + * Register the panel-info. + * + * Some parameters are from the panel datasheet + * and other are *calculated* according to the + * "DSI_panel_bring_up_guide_ver3.docm". + * + * @return int + */ +static int __init mipi_chimei_wxga_init(void) +{ + int ret; + struct msm_panel_info *pinfo = &chimei_wxga_pinfo; + + if (msm_fb_detect_client("mipi_video_chimei_wxga")) + return 0; + + pr_debug("mipi-dsi chimei wxga (1366x768) driver ver 1.0.\n"); + /* Landscape */ + pinfo->xres = 1366; + pinfo->yres = 768; + pinfo->type = MIPI_VIDEO_PANEL; + pinfo->pdest = DISPLAY_1; /* Primary Display */ + pinfo->wait_cycle = 0; + pinfo->bpp = 24; /* RGB888 = 24 bits-per-pixel */ + pinfo->fb_num = 2; /* using two frame buffers */ + + /* bitclk */ + pinfo->clk_rate = 473400000; /* 473.4 MHZ Calculated */ + + /* + * this panel is operated by DE, + * vsycn and hsync are ignored + */ + + pinfo->lcdc.h_front_porch = 96+2;/* thfp */ + pinfo->lcdc.h_back_porch = 88; /* thb */ + pinfo->lcdc.h_pulse_width = 40; /* thpw */ + + pinfo->lcdc.v_front_porch = 15; /* tvfp */ + pinfo->lcdc.v_back_porch = 23; /* tvb */ + pinfo->lcdc.v_pulse_width = 20; /* tvpw */ + + pinfo->lcdc.border_clr = 0; /* black */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + + pinfo->lcdc.hsync_skew = 0; + + /* Backlight levels - controled via PMIC pwm gpio */ + pinfo->bl_max = PWM_LEVEL; + pinfo->bl_min = 1; + + /* mipi - general */ + pinfo->mipi.vc = 0; /* virtual channel */ + pinfo->mipi.rgb_swap = DSI_RGB_SWAP_RGB; + pinfo->mipi.tx_eot_append = true; + pinfo->mipi.t_clk_post = 34; /* Calculated */ + pinfo->mipi.t_clk_pre = 64; /* Calculated */ + + pinfo->mipi.dsi_phy_db = &dsi_video_mode_phy_db; + + /* Four lanes are recomended for 1366x768 at 60 frames per second */ + pinfo->mipi.frame_rate = 60; /* 60 frames per second */ + pinfo->mipi.data_lane0 = true; + pinfo->mipi.data_lane1 = true; + pinfo->mipi.data_lane2 = true; + pinfo->mipi.data_lane3 = true; + + pinfo->mipi.mode = DSI_VIDEO_MODE; + /* + * Note: The CMI panel input is RGB888, + * thus the DSI-to-LVDS bridge output is RGB888. + * This parameter selects the DSI-Core output to the bridge. + */ + pinfo->mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + + /* mipi - video mode */ + pinfo->mipi.traffic_mode = DSI_NON_BURST_SYNCH_EVENT; + pinfo->mipi.pulse_mode_hsa_he = false; /* sync mode */ + + 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; + + /* mipi - command mode */ + pinfo->mipi.te_sel = 1; /* TE from vsycn gpio */ + pinfo->mipi.interleave_max = 1; + /* The bridge supports only Generic Read/Write commands */ + pinfo->mipi.insert_dcs_cmd = false; + pinfo->mipi.wr_mem_continue = 0; + pinfo->mipi.wr_mem_start = 0; + pinfo->mipi.stream = false; /* dma_p */ + pinfo->mipi.mdp_trigger = DSI_CMD_TRIGGER_NONE; + pinfo->mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + /* + * toshiba d2l chip does not need max_pkt_szie dcs cmd + * client reply len is directly configure through + * RDPKTLN register (0x0404) + */ + pinfo->mipi.no_max_pkt_size = 1; + pinfo->mipi.force_clk_lane_hs = 1; + + ret = mipi_tc358764_dsi2lvds_register(pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_WXGA); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_chimei_wxga_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Chimei WXGA LVDS Panel driver"); +MODULE_AUTHOR("Amir Samuelov "); diff --git a/drivers/video/msm/mipi_dsi.c b/drivers/video/msm/mipi_dsi.c new file mode 100644 index 0000000000000000000000000000000000000000..75640162c954f65e8b918cbb56f5a32912543291 --- /dev/null +++ b/drivers/video/msm/mipi_dsi.c @@ -0,0 +1,637 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 boolean tlmm_settings = FALSE; + +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); + mipi_dsi_mdp_busy_wait(mfd); + } else { + mdp3_dsi_cmd_dma_busy_wait(mfd); + } + } else { + /* video mode, wait until fifo cleaned */ + mipi_dsi_controller_cfg(0); + } + + /* + * 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) { + if (MDP_REV_303 != mdp_rev) + 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 + + local_bh_disable(); + mipi_dsi_clk_disable(); + local_bh_enable(); + + /* disbale dsi engine */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, 0); + + mipi_dsi_phy_ctrl(0); + + + local_bh_disable(); + mipi_dsi_ahb_ctrl(0); + local_bh_enable(); + + mipi_dsi_unprepare_clocks(); + 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); + + cont_splash_clk_ctrl(0); + mipi_dsi_prepare_clocks(); + + local_bh_disable(); + mipi_dsi_ahb_ctrl(1); + local_bh_enable(); + + clk_rate = mfd->fbi->var.pixclock; + clk_rate = min(clk_rate, mfd->panel_info.clk_max); + + 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); + + local_bh_disable(); + mipi_dsi_clk_enable(); + local_bh_enable(); + + 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 = &mfd->panel_info.mipi; + if (mfd->panel_info.type == MIPI_VIDEO_PANEL) { + dummy_xres = mfd->panel_info.lcdc.xres_pad; + dummy_yres = mfd->panel_info.lcdc.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); + + if (mipi->force_clk_lane_hs) { + u32 tmp; + + tmp = MIPI_INP(MIPI_DSI_BASE + 0xA8); + tmp |= (1<<28); + MIPI_OUTP(MIPI_DSI_BASE + 0xA8, tmp); + wmb(); + } + + if (mdp_rev >= MDP_REV_41) + mutex_lock(&mfd->dma->ov_mutex); + else + down(&mfd->dma->mutex); + + 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 (mdp_rev >= MDP_REV_41) { + 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); + } else if (mdp_rev == MDP_REV_303) { + if (!tlmm_settings && gpio_request( + vsync_gpio, "MDP_VSYNC") == 0) { + ret = gpio_tlmm_config( + GPIO_CFG( + vsync_gpio, 1, + GPIO_CFG_INPUT, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + + if (ret) { + pr_err( + "%s: unable to config \ + tlmm = %d\n", + __func__, vsync_gpio); + } + tlmm_settings = TRUE; + + gpio_direction_input( + vsync_gpio); + } else { + if (!tlmm_settings) { + 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 + + mdp4_overlay_dsi_state_set(ST_DSI_RESUME); + + 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_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__); + } + } + + if (mipi_dsi_clk_init(pdev)) + return -EPERM; + + if (mipi_dsi_pdata->splash_is_enabled && + !mipi_dsi_pdata->splash_is_enabled()) { + mipi_dsi_ahb_ctrl(1); + MIPI_OUTP(MIPI_DSI_BASE + 0x118, 0); + MIPI_OUTP(MIPI_DSI_BASE + 0x0, 0); + MIPI_OUTP(MIPI_DSI_BASE + 0x200, 0); + mipi_dsi_ahb_ctrl(0); + } + mipi_dsi_resource_initialized = 1; + + return 0; + } + + 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; + + if (!mfd->cont_splash_done) + cont_splash_clk_ctrl(1); + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + + /* + * 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 (mfd->panel_info.type == MIPI_VIDEO_PANEL) + mfd->dest = DISPLAY_LCDC; + else + mfd->dest = DISPLAY_LCD; + + 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.lcdc.xres_pad; + v_period += mfd->panel_info.lcdc.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 0000000000000000000000000000000000000000..d54c5b502c3351550053eab143b4d60959312b19 --- /dev/null +++ b/drivers/video/msm/mipi_dsi.h @@ -0,0 +1,317 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 MIPI_DSI_PANEL_WSVGA_PT 4 +#define MIPI_DSI_PANEL_QHD_PT 5 +#define MIPI_DSI_PANEL_WXGA 6 +#define MIPI_DSI_PANEL_WUXGA 7 +#define MIPI_DSI_PANEL_720P_PT 8 +#define DSI_PANEL_MAX 8 + +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, +}; + +enum dsi_trigger_type { + DSI_CMD_MODE_DMA, + DSI_CMD_MODE_MDP, +}; + +#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 + +/* + * dcs response + */ +#define DTYPE_ACK_ERR_RESP 0x02 +#define DTYPE_EOT_RESP 0x08 /* end of tx */ +#define DTYPE_GEN_READ1_RESP 0x11 /* 1 parameter, short */ +#define DTYPE_GEN_READ2_RESP 0x12 /* 2 parameter, short */ +#define DTYPE_GEN_LREAD_RESP 0x1a +#define DTYPE_DCS_LREAD_RESP 0x1c +#define DTYPE_DCS_READ1_RESP 0x21 /* 1 parameter, short */ +#define DTYPE_DCS_READ2_RESP 0x22 /* 2 parameter, short */ + +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); +void mipi_dsi_lane_cfg(void); +void mipi_dsi_bist_ctrl(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_start(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); +void mipi_dsi_controller_cfg(int enable); +void mipi_dsi_sw_reset(void); +void mipi_dsi_mdp_busy_wait(struct msm_fb_data_type *mfd); + +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); +int mipi_dsi_clk_init(struct platform_device *pdev); +void mipi_dsi_clk_deinit(struct device *dev); +void mipi_dsi_prepare_clocks(void); +void mipi_dsi_unprepare_clocks(void); +void mipi_dsi_ahb_ctrl(u32 enable); +void cont_splash_clk_ctrl(int enable); +void mipi_dsi_turn_on_clks(void); +void mipi_dsi_turn_off_clks(void); + +#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 0000000000000000000000000000000000000000..dbf45b0da71981398249ebdbfbcf69a79583aec8 --- /dev/null +++ b/drivers/video/msm/mipi_dsi_host.c @@ -0,0 +1,1493 @@ + +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 completion dsi_mdp_comp; +static struct dsi_buf dsi_tx_buf; +static int dsi_irq_enabled; +static spinlock_t dsi_irq_lock; +static spinlock_t dsi_mdp_lock; +static int dsi_mdp_busy; + +static struct list_head pre_kickoff_list; +static struct list_head post_kickoff_list; + +enum { + STAT_DSI_START, + STAT_DSI_ERROR, + STAT_DSI_CMD, + STAT_DSI_MDP +}; + +#ifdef CONFIG_FB_MSM_MDP40 +void mipi_dsi_mdp_stat_inc(int which) +{ + switch (which) { + case STAT_DSI_START: + mdp4_stat.dsi_mdp_start++; + break; + case STAT_DSI_ERROR: + mdp4_stat.intr_dsi_err++; + break; + case STAT_DSI_CMD: + mdp4_stat.intr_dsi_cmd++; + break; + case STAT_DSI_MDP: + mdp4_stat.intr_dsi_mdp++; + break; + default: + break; + } +} +#else +void mipi_dsi_mdp_stat_inc(int which) +{ +} +#endif + +void mipi_dsi_init(void) +{ + init_completion(&dsi_dma_comp); + init_completion(&dsi_mdp_comp); + mipi_dsi_buf_alloc(&dsi_tx_buf, DSI_BUF_SIZE); + spin_lock_init(&dsi_irq_lock); + spin_lock_init(&dsi_mdp_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_irq_lock, flags); + if (dsi_irq_enabled) { + pr_debug("%s: IRQ aleady enabled\n", __func__); + spin_unlock_irqrestore(&dsi_irq_lock, flags); + return; + } + dsi_irq_enabled = 1; + enable_irq(dsi_irq); + spin_unlock_irqrestore(&dsi_irq_lock, flags); +} + +void mipi_dsi_disable_irq(void) +{ + unsigned long flags; + + spin_lock_irqsave(&dsi_irq_lock, flags); + if (dsi_irq_enabled == 0) { + pr_debug("%s: IRQ already disabled\n", __func__); + spin_unlock_irqrestore(&dsi_irq_lock, flags); + return; + } + + dsi_irq_enabled = 0; + disable_irq(dsi_irq); + spin_unlock_irqrestore(&dsi_irq_lock, flags); +} + +/* + * mipi_dsi_disale_irq_nosync() should be called + * from interrupt context + */ + void mipi_dsi_disable_irq_nosync(void) +{ + spin_lock(&dsi_irq_lock); + if (dsi_irq_enabled == 0) { + pr_debug("%s: IRQ cannot be disabled\n", __func__); + spin_unlock(&dsi_irq_lock); + return; + } + + dsi_irq_enabled = 0; + disable_irq_nosync(dsi_irq); + spin_unlock(&dsi_irq_lock); +} + +void mipi_dsi_turn_on_clks(void) +{ + local_bh_disable(); + mipi_dsi_ahb_ctrl(1); + mipi_dsi_clk_enable(); + local_bh_enable(); +} + +void mipi_dsi_turn_off_clks(void) +{ + local_bh_disable(); + mipi_dsi_clk_disable(); + mipi_dsi_ahb_ctrl(0); + local_bh_enable(); +} + +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; +} + +/* + * mipi_dsi_short_read1_resp: 1 parameter + */ +static int mipi_dsi_short_read1_resp(struct dsi_buf *rp) +{ + /* strip out dcs type */ + rp->data++; + rp->len = 1; + return rp->len; +} + +/* + * mipi_dsi_short_read2_resp: 2 parameter + */ +static int mipi_dsi_short_read2_resp(struct dsi_buf *rp) +{ + /* strip out dcs type */ + rp->data++; + rp->len = 2; + return rp->len; +} + +static int mipi_dsi_long_read_resp(struct dsi_buf *rp) +{ + short len; + + len = rp->data[2]; + len <<= 8; + len |= rp->data[1]; + /* strip out dcs header */ + rp->data += 4; + rp->len -= 4; + /* strip out 2 bytes of checksum */ + rp->len -= 2; + return len; +} + +void mipi_dsi_host_init(struct mipi_panel_info *pinfo) +{ + uint32 dsi_ctrl, intr_ctrl; + uint32 data; + + if (mdp_rev > MDP_REV_41 || mdp_rev == MDP_REV_303) + pinfo->rgb_swap = DSI_RGB_SWAP_RGB; + else + pinfo->rgb_swap = DSI_RGB_SWAP_BGR; + + 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_sw_reset(void) +{ + MIPI_OUTP(MIPI_DSI_BASE + 0x114, 0x01); + wmb(); + MIPI_OUTP(MIPI_DSI_BASE + 0x114, 0x00); + wmb(); +} + +void mipi_dsi_controller_cfg(int enable) +{ + + uint32 dsi_ctrl; + uint32 status; + int cnt; + + cnt = 16; + while (cnt--) { + status = MIPI_INP(MIPI_DSI_BASE + 0x0004); + status &= 0x02; /* CMD_MODE_DMA_BUSY */ + if (status == 0) + break; + usleep(1000); + } + if (cnt == 0) + pr_info("%s: DSI status=%x failed\n", __func__, status); + + cnt = 16; + while (cnt--) { + status = MIPI_INP(MIPI_DSI_BASE + 0x0008); + status &= 0x11111000; /* x_HS_FIFO_EMPTY */ + if (status == 0x11111000) /* all empty */ + break; + usleep(1000); + } + + if (cnt == 0) + pr_info("%s: FIFO status=%x failed\n", __func__, status); + + dsi_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0000); + if (enable) + dsi_ctrl |= 0x01; + else + dsi_ctrl &= ~0x01; + + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, dsi_ctrl); + wmb(); +} + +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_mdp_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(&dsi_mdp_lock, flag); + if (dsi_mdp_busy == TRUE) { + INIT_COMPLETION(dsi_mdp_comp); + need_wait++; + } + spin_unlock_irqrestore(&dsi_mdp_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(&dsi_mdp_comp); + } + pr_debug("%s: done pid=%d\n", + __func__, current->pid); +} + + +void mipi_dsi_cmd_mdp_start(void) +{ + unsigned long flag; + + + if (!in_interrupt()) + mipi_dsi_pre_kickoff_action(); + + mipi_dsi_mdp_stat_inc(STAT_DSI_START); + + spin_lock_irqsave(&dsi_mdp_lock, flag); + mipi_dsi_enable_irq(); + dsi_mdp_busy = TRUE; + spin_unlock_irqrestore(&dsi_mdp_lock, flag); +} + + +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; + unsigned long flag; + + /* 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 + } + } + + spin_lock_irqsave(&dsi_mdp_lock, flag); + mipi_dsi_enable_irq(); + dsi_mdp_busy = TRUE; + spin_unlock_irqrestore(&dsi_mdp_lock, flag); + + 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++; + } + + spin_lock_irqsave(&dsi_mdp_lock, flag); + dsi_mdp_busy = FALSE; + mipi_dsi_disable_irq(); + complete(&dsi_mdp_comp); + spin_unlock_irqrestore(&dsi_mdp_lock, flag); + + 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 rlen) +{ + int cnt, len, diff, pkt_size; + unsigned long flag; + char cmd; + + if (mfd->panel_info.mipi.no_max_pkt_size) { + /* Only support rlen = 4*n */ + rlen += 3; + rlen &= ~0x03; + } + + len = rlen; + diff = 0; + + if (len <= 2) + cnt = 4; /* short read */ + else { + if (len > MIPI_DSI_LEN) + len = MIPI_DSI_LEN; /* 8 bytes at most */ + + len = (len + 3) & ~0x03; /* len 4 bytes align */ + diff = len - rlen; + /* + * 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 + } + + spin_lock_irqsave(&dsi_mdp_lock, flag); + mipi_dsi_enable_irq(); + dsi_mdp_busy = TRUE; + spin_unlock_irqrestore(&dsi_mdp_lock, flag); + + if (!mfd->panel_info.mipi.no_max_pkt_size) { + /* packet size need to be set at every read */ + 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_buf_init(rp); + if (mfd->panel_info.mipi.no_max_pkt_size) { + /* + * expect rlen = n * 4 + * short alignement for start addr + */ + rp->data += 2; + } + + mipi_dsi_cmd_dma_rx(rp, cnt); + + spin_lock_irqsave(&dsi_mdp_lock, flag); + dsi_mdp_busy = FALSE; + mipi_dsi_disable_irq(); + complete(&dsi_mdp_comp); + spin_unlock_irqrestore(&dsi_mdp_lock, flag); + + if (mfd->panel_info.mipi.no_max_pkt_size) { + /* + * remove extra 2 bytes from previous + * rx transaction at shift register + * which was inserted during copy + * shift registers to rx buffer + * rx payload start from long alignment addr + */ + rp->data += 2; + } + + cmd = rp->data[0]; + switch (cmd) { + case DTYPE_ACK_ERR_RESP: + pr_debug("%s: rx ACK_ERR_PACLAGE\n", __func__); + break; + case DTYPE_GEN_READ1_RESP: + case DTYPE_DCS_READ1_RESP: + mipi_dsi_short_read1_resp(rp); + break; + case DTYPE_GEN_READ2_RESP: + case DTYPE_DCS_READ2_RESP: + mipi_dsi_short_read2_resp(rp); + break; + case DTYPE_GEN_LREAD_RESP: + case DTYPE_DCS_LREAD_RESP: + mipi_dsi_long_read_resp(rp); + rp->len -= 2; /* extra 2 bytes added */ + rp->len -= diff; /* align bytes */ + break; + default: + break; + } + + 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_mdp_stat_inc(STAT_DSI_ERROR); + mipi_dsi_error(); + } + + if (isr & DSI_INTR_VIDEO_DONE) { + /* + * do something here + */ + } + + if (isr & DSI_INTR_CMD_DMA_DONE) { + mipi_dsi_mdp_stat_inc(STAT_DSI_CMD); + complete(&dsi_dma_comp); + } + + if (isr & DSI_INTR_CMD_MDP_DONE) { + mipi_dsi_mdp_stat_inc(STAT_DSI_MDP); + spin_lock(&dsi_mdp_lock); + dsi_mdp_busy = FALSE; + mipi_dsi_disable_irq_nosync(); + spin_unlock(&dsi_mdp_lock); + complete(&dsi_mdp_comp); + 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 0000000000000000000000000000000000000000..007075728838228cd72f9e915ad6bdc4c9ad304c --- /dev/null +++ b/drivers/video/msm/mipi_novatek.c @@ -0,0 +1,658 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_SPI_QUP +#include +#endif +#include +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_novatek.h" +#include "mdp4.h" + + +static struct mipi_dsi_panel_platform_data *mipi_novatek_pdata; + +static struct dsi_buf novatek_tx_buf; +static struct dsi_buf novatek_rx_buf; +static int mipi_novatek_lcd_init(void); + +static int wled_trigger_initialized; + +#define MIPI_DSI_NOVATEK_SPI_DEVICE_NAME "dsi_novatek_3d_panel_spi" +#define HPCI_FPGA_READ_CMD 0x84 +#define HPCI_FPGA_WRITE_CMD 0x04 + +#ifdef CONFIG_SPI_QUP +static struct spi_device *panel_3d_spi_client; + +static void novatek_fpga_write(uint8 addr, uint16 value) +{ + char tx_buf[32]; + int rc; + struct spi_message m; + struct spi_transfer t; + u8 data[4] = {0x0, 0x0, 0x0, 0x0}; + + if (!panel_3d_spi_client) { + pr_err("%s panel_3d_spi_client is NULL\n", __func__); + return; + } + data[0] = HPCI_FPGA_WRITE_CMD; + data[1] = addr; + data[2] = ((value >> 8) & 0xFF); + data[3] = (value & 0xFF); + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + t.tx_buf = data; + t.len = 4; + spi_setup(panel_3d_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + rc = spi_sync(panel_3d_spi_client, &m); + if (rc) + pr_err("%s: SPI transfer failed\n", __func__); + + return; +} + +static void novatek_fpga_read(uint8 addr) +{ + char tx_buf[32]; + int rc; + struct spi_message m; + struct spi_transfer t; + struct spi_transfer rx; + char rx_value[2]; + u8 data[4] = {0x0, 0x0}; + + if (!panel_3d_spi_client) { + pr_err("%s panel_3d_spi_client is NULL\n", __func__); + return; + } + + data[0] = HPCI_FPGA_READ_CMD; + data[1] = addr; + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + memset(&rx, 0, sizeof rx); + memset(rx_value, 0, sizeof rx_value); + t.tx_buf = data; + t.len = 2; + rx.rx_buf = rx_value; + rx.len = 2; + spi_setup(panel_3d_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_message_add_tail(&rx, &m); + + rc = spi_sync(panel_3d_spi_client, &m); + if (rc) + pr_err("%s: SPI transfer failed\n", __func__); + else + pr_info("%s: rx_value = 0x%x, 0x%x\n", __func__, + rx_value[0], rx_value[1]); + + return; +} + +static int __devinit panel_3d_spi_probe(struct spi_device *spi) +{ + panel_3d_spi_client = spi; + return 0; +} +static int __devexit panel_3d_spi_remove(struct spi_device *spi) +{ + panel_3d_spi_client = NULL; + return 0; +} +static struct spi_driver panel_3d_spi_driver = { + .probe = panel_3d_spi_probe, + .remove = __devexit_p(panel_3d_spi_remove), + .driver = { + .name = "dsi_novatek_3d_panel_spi", + .owner = THIS_MODULE, + } +}; + +#else + +static void novatek_fpga_write(uint8 addr, uint16 value) +{ + return; +} + +static void novatek_fpga_read(uint8 addr) +{ + return; +} + +#endif + + +/* 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; + 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 int fpga_access_mode; +static bool support_3d; + +static void mipi_novatek_3d_init(int addr, int mode) +{ + fpga_addr = addr; + fpga_access_mode = mode; +} + +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; + } + + if (fpga_access_mode == FPGA_SPI_INTF) { + if (mode == LANDSCAPE) + novatek_fpga_write(fpga_addr, 1); + else if (mode == PORTRAIT) + novatek_fpga_write(fpga_addr, 3); + else + novatek_fpga_write(fpga_addr, 0); + + mb(); + novatek_fpga_read(fpga_addr); + } else if (fpga_access_mode == FPGA_EBI2_INTF) { + 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); + } else + pr_err("%s: 3D barrier not configured correctly\n", + __func__); +} + +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; +} + +DEFINE_LED_TRIGGER(bkl_led_trigger); + +static void mipi_novatek_set_backlight(struct msm_fb_data_type *mfd) +{ + struct mipi_panel_info *mipi; + + if ((mipi_novatek_pdata->enable_wled_bl_ctrl) + && (wled_trigger_initialized)) { + led_trigger_event(bkl_led_trigger, mfd->bl_level); + return; + } + mipi = &mfd->panel_info.mipi; + + mutex_lock(&mfd->dma->ov_mutex); + if (mdp4_overlay_dsi_state_get() <= ST_DSI_SUSPEND) { + mutex_unlock(&mfd->dma->ov_mutex); + return; + } + /* 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); + mipi_dsi_mdp_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)); + 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) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + struct platform_device *current_pdev; + static struct mipi_dsi_phy_ctrl *phy_settings; + static char dlane_swap; + + if (pdev->id == 0) { + mipi_novatek_pdata = pdev->dev.platform_data; + + if (mipi_novatek_pdata + && mipi_novatek_pdata->phy_ctrl_settings) { + phy_settings = (mipi_novatek_pdata->phy_ctrl_settings); + } + + if (mipi_novatek_pdata + && mipi_novatek_pdata->dlane_swap) { + dlane_swap = (mipi_novatek_pdata->dlane_swap); + } + + if (mipi_novatek_pdata + && mipi_novatek_pdata->fpga_3d_config_addr) + mipi_novatek_3d_init(mipi_novatek_pdata + ->fpga_3d_config_addr, mipi_novatek_pdata->fpga_ctrl_mode); + + /* 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; + } + + current_pdev = msm_fb_add_device(pdev); + + if (current_pdev) { + mfd = platform_get_drvdata(current_pdev); + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi = &mfd->panel_info.mipi; + + if (phy_settings != NULL) + mipi->dsi_phy_db = phy_settings; + + if (dlane_swap) + mipi->dlane_swap = dlane_swap; + } + 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, 0664, 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; + + ret = mipi_novatek_lcd_init(); + if (ret) { + pr_err("mipi_novatek_lcd_init() failed with ret %u\n", ret); + return ret; + } + + 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 mipi_novatek_lcd_init(void) +{ +#ifdef CONFIG_SPI_QUP + int ret; + ret = spi_register_driver(&panel_3d_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__); +#endif + + led_trigger_register_simple("bkl_trigger", &bkl_led_trigger); + pr_info("%s: SUCCESS (WLED TRIGGER)\n", __func__); + wled_trigger_initialized = 1; + + 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); +} diff --git a/drivers/video/msm/mipi_novatek.h b/drivers/video/msm/mipi_novatek.h new file mode 100644 index 0000000000000000000000000000000000000000..f84de9aa7b852c03c90b6caf52ef292bfc85e451 --- /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 0000000000000000000000000000000000000000..fbd2495645c3d8ebe943fae238df158ac5dd0947 --- /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; + + if (msm_fb_detect_client("mipi_cmd_novatek_qhd")) + return 0; + + 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.lcd.v_back_porch = 11; + pinfo.lcd.v_front_porch = 10; + pinfo.lcd.v_pulse_width = 5; + + pinfo.mipi.mode = DSI_CMD_MODE; + pinfo.mipi.dst_format = DSI_CMD_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + 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_NONE; + 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_QHD_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 0000000000000000000000000000000000000000..42ddfbe1dbee6d945092d0d13e886e40aebbcc04 --- /dev/null +++ b/drivers/video/msm/mipi_novatek_video_qhd_pt.c @@ -0,0 +1,96 @@ +/* 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; + + if (msm_fb_detect_client("mipi_video_novatek_qhd")) + return 0; + + 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_QHD_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_orise.c b/drivers/video/msm/mipi_orise.c new file mode 100644 index 0000000000000000000000000000000000000000..2afbb9b6821fe3b1e156b0dd9db24d632b8b1b6b --- /dev/null +++ b/drivers/video/msm/mipi_orise.c @@ -0,0 +1,194 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_orise.h" +#include "mdp4.h" + + +static struct mipi_dsi_panel_platform_data *mipi_orise_pdata; + +static struct dsi_buf orise_tx_buf; +static struct dsi_buf orise_rx_buf; + +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 struct dsi_cmd_desc orise_video_on_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(display_on), display_on}, +}; + +static struct dsi_cmd_desc orise_cmd_on_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(display_on), display_on}, +}; + +static struct dsi_cmd_desc orise_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 int mipi_orise_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; + mipi = &mfd->panel_info.mipi; + + if (mipi->mode == DSI_VIDEO_MODE) { + mipi_dsi_cmds_tx(mfd, &orise_tx_buf, orise_video_on_cmds, + ARRAY_SIZE(orise_video_on_cmds)); + } else { + mipi_dsi_cmds_tx(mfd, &orise_tx_buf, orise_cmd_on_cmds, + ARRAY_SIZE(orise_cmd_on_cmds)); + + mipi_dsi_cmd_bta_sw_trigger(); /* clean up ack_err_status */ + } + + return 0; +} + +static int mipi_orise_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, &orise_tx_buf, orise_display_off_cmds, + ARRAY_SIZE(orise_display_off_cmds)); + + return 0; +} + + + +static int __devinit mipi_orise_lcd_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + struct platform_device *current_pdev; + static struct mipi_dsi_phy_ctrl *phy_settings; + + if (pdev->id == 0) { + mipi_orise_pdata = pdev->dev.platform_data; + + if (mipi_orise_pdata + && mipi_orise_pdata->phy_ctrl_settings) { + phy_settings = (mipi_orise_pdata->phy_ctrl_settings); + } + + return 0; + } + + current_pdev = msm_fb_add_device(pdev); + + if (current_pdev) { + mfd = platform_get_drvdata(current_pdev); + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi = &mfd->panel_info.mipi; + + if (phy_settings != NULL) + mipi->dsi_phy_db = phy_settings; + } + return 0; +} + +static struct platform_driver this_driver = { + .probe = mipi_orise_lcd_probe, + .driver = { + .name = "mipi_orise", + }, +}; + +static struct msm_fb_panel_data orise_panel_data = { + .on = mipi_orise_lcd_on, + .off = mipi_orise_lcd_off, +}; + +static int ch_used[3]; + +int mipi_orise_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_orise", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + orise_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &orise_panel_data, + sizeof(orise_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_orise_lcd_init(void) +{ + mipi_dsi_buf_alloc(&orise_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&orise_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} + +module_init(mipi_orise_lcd_init); diff --git a/drivers/video/msm/mipi_orise.h b/drivers/video/msm/mipi_orise.h new file mode 100644 index 0000000000000000000000000000000000000000..1659479c9a1d5c86858825ffc4cfe2e74f32a49c --- /dev/null +++ b/drivers/video/msm/mipi_orise.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ORISE_H +#define MIPI_ORISE_H + +int mipi_orise_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_ORISE_H */ diff --git a/drivers/video/msm/mipi_orise_cmd_720p_pt.c b/drivers/video/msm/mipi_orise_cmd_720p_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..c2a158d7282852bbd227f71a74f2e6695401ad84 --- /dev/null +++ b/drivers/video/msm/mipi_orise_cmd_720p_pt.c @@ -0,0 +1,96 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_orise.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_cmd_mode_phy_db = { +/* DSI_BIT_CLK at 507MHz, 4 lane, RGB888 */ + {0x03, 0x0a, 0x04, 0x00, 0x20}, + /* timing */ + {0x8c, 0x34, 0x15, 0x00, 0x46, 0x50, 0x1a, 0x38, + 0x24, 0x03, 0x04, 0xa0}, + /* phy ctrl */ + {0x5f, 0x00, 0x00, 0x10}, + /* strength */ + {0xff, 0x00, 0x06, 0x00}, + /* pll control */ + {0x0, 0xf9, 0x30, 0xda, 0x00, 0x40, 0x03, 0x62, + 0x40, 0x07, 0x03, + 0x00, 0x1a, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x01 }, +}; + +static int __init mipi_cmd_orise_720p_pt_init(void) +{ + int ret; + + if (msm_fb_detect_client("mipi_cmd_orise_720p")) + return 0; + + pinfo.xres = 720; + pinfo.yres = 1280; + pinfo.type = MIPI_CMD_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.lcdc.h_back_porch = 160; + pinfo.lcdc.h_front_porch = 160; + pinfo.lcdc.h_pulse_width = 8; + pinfo.lcdc.v_back_porch = 32; + pinfo.lcdc.v_front_porch = 32; + 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 = 200; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + pinfo.clk_rate = 507000000; + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.hw_vsync_mode = TRUE; + pinfo.lcd.refx100 = 6000; /* adjust refx100 to prevent tearing */ + pinfo.lcd.v_back_porch = 32; + pinfo.lcd.v_front_porch = 32; + pinfo.lcd.v_pulse_width = 1; + + pinfo.mipi.mode = DSI_CMD_MODE; + pinfo.mipi.dst_format = DSI_CMD_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.data_lane0 = TRUE; + pinfo.mipi.data_lane1 = TRUE; + pinfo.mipi.data_lane2 = TRUE; + pinfo.mipi.data_lane3 = TRUE; + pinfo.mipi.t_clk_post = 0x04; + pinfo.mipi.t_clk_pre = 0x1e; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_NONE; + 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_orise_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_720P_PT); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_cmd_orise_720p_pt_init); diff --git a/drivers/video/msm/mipi_orise_video_720p_pt.c b/drivers/video/msm/mipi_orise_video_720p_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..629ff10adf93d18959ebc3bff5bf9e6c4ddf61ca --- /dev/null +++ b/drivers/video/msm/mipi_orise_video_720p_pt.c @@ -0,0 +1,97 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_orise.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* regulator */ + {0x03, 0x0a, 0x04, 0x00, 0x20}, + /* timing */ + {0x83, 0x31, 0x13, 0x00, 0x42, 0x4d, 0x18, 0x35, + 0x21, 0x03, 0x04, 0xa0}, + /* phy ctrl */ + {0x5f, 0x00, 0x00, 0x10}, + /* strength */ + {0xff, 0x00, 0x06, 0x00}, + /* pll control */ + {0x0, 0x0e, 0x30, 0xc0, 0x00, 0x40, 0x03, 0x62, + 0x40, 0x07, 0x07, + 0x00, 0x1a, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x01 }, +}; + +static int __init mipi_video_orise_720p_pt_init(void) +{ + int ret; + + if (msm_fb_detect_client("mipi_video_orise_720p")) + return 0; + + pinfo.xres = 720; + pinfo.yres = 1280; + pinfo.lcdc.xres_pad = 0; + pinfo.lcdc.yres_pad = 0; + + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.lcdc.h_back_porch = 160; + pinfo.lcdc.h_front_porch = 160; + pinfo.lcdc.h_pulse_width = 8; + pinfo.lcdc.v_back_porch = 32; + pinfo.lcdc.v_front_porch = 32; + 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 = 200; + 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 = FALSE; + pinfo.mipi.eof_bllp_power_stop = TRUE; + pinfo.mipi.bllp_power_stop = TRUE; + 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.data_lane3 = TRUE; + pinfo.mipi.t_clk_post = 0x04; + pinfo.mipi.t_clk_pre = 0x1c; + 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_orise_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_720P_PT); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_orise_720p_pt_init); diff --git a/drivers/video/msm/mipi_renesas.c b/drivers/video/msm/mipi_renesas.c new file mode 100644 index 0000000000000000000000000000000000000000..c9dc82559659a442a6c0fd4c5931d51f27dfa2bf --- /dev/null +++ b/drivers/video/msm/mipi_renesas.c @@ -0,0 +1,1262 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" +#include + +#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 int mipi_renesas_lcd_init(void); + +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_DSIRXCTL[2] = {0x41, 0x01}; +static char config_TEON[2] = {0x35, 0x00}; +static char config_TEOFF[1] = {0x34}; + +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}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_TEOFF), config_TEOFF}, +}; + +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_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DSIRXCTL), config_DSIRXCTL}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_TEON), config_TEON}, + {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 char config_WRTXHT2[7] = {0x92, 0x15, 0x05, 0x0F, 0x00, 0x01, 0xe0}; +static char config_WRTXVT2[7] = {0x8b, 0x14, 0x01, 0x14, 0x00, 0x03, 0x60}; + +static struct dsi_cmd_desc renesas_hvga_on_cmds[] = { + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_WRTXHT2), config_WRTXHT2}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_WRTXVT2), config_WRTXVT2}, +}; + +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 (cpu_is_msm7x25a() || cpu_is_msm7x25aa() || cpu_is_msm7x25ab()) { + mipi_dsi_cmds_tx(mfd, &renesas_tx_buf, renesas_hvga_on_cmds, + ARRAY_SIZE(renesas_hvga_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; + + ret = mipi_renesas_lcd_init(); + if (ret) { + pr_err("mipi_renesas_lcd_init() failed with ret %u\n", ret); + return ret; + } + + 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 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); +} diff --git a/drivers/video/msm/mipi_renesas.h b/drivers/video/msm/mipi_renesas.h new file mode 100644 index 0000000000000000000000000000000000000000..59ccfd04fcfb0be050933105fd6d0739b31f5408 --- /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 0000000000000000000000000000000000000000..7f5ac700dfc9950b3651d99a037259e9bb4c30ea --- /dev/null +++ b/drivers/video/msm/mipi_renesas_cmd_fwvga_pt.c @@ -0,0 +1,157 @@ +/* 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; + + if (msm_fb_detect_client("mipi_cmd_renesas_fwvga")) + return 0; + + 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 + 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.lcd.vsync_enable = TRUE; + pinfo.lcd.hw_vsync_mode = 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_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.te_sel = 1; /* 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 0000000000000000000000000000000000000000..e826773d9a713022b8ce6a473298b998e615127a --- /dev/null +++ b/drivers/video/msm/mipi_renesas_video_fwvga_pt.c @@ -0,0 +1,163 @@ +/* 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; + + if (msm_fb_detect_client("mipi_video_renesas_fwvga")) + return 0; + + 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 0000000000000000000000000000000000000000..c6bf5342e24300f5e78efcd4533fc2331fe7afe7 --- /dev/null +++ b/drivers/video/msm/mipi_simulator.c @@ -0,0 +1,167 @@ +/* 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 int mipi_simulator_lcd_init(void); + +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__); + ret = mipi_simulator_lcd_init(); + if (ret) { + pr_err("mipi_simulator_lcd_init() failed with ret %u\n", ret); + return ret; + } + + 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 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); +} diff --git a/drivers/video/msm/mipi_simulator.h b/drivers/video/msm/mipi_simulator.h new file mode 100644 index 0000000000000000000000000000000000000000..274ce8f1352c232f6bc3edb80c29902fb50d44b0 --- /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 0000000000000000000000000000000000000000..845df75ce225924d405f678f2ba7529254e8a3c9 --- /dev/null +++ b/drivers/video/msm/mipi_simulator_video.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 "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; + + if (msm_fb_detect_client("mipi_video_simulator_vga")) + return 0; + 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_VGA); + 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_tc358764_dsi2lvds.c b/drivers/video/msm/mipi_tc358764_dsi2lvds.c new file mode 100644 index 0000000000000000000000000000000000000000..f7f353f8ff6aaa6e1521ca0f037d2c531c8368d0 --- /dev/null +++ b/drivers/video/msm/mipi_tc358764_dsi2lvds.c @@ -0,0 +1,997 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Toshiba MIPI-DSI-to-LVDS Bridge driver. + * Device Model TC358764XBG/65XBG. + * Reference document: TC358764XBG_65XBG_V119.pdf + * + * The Host sends a DSI Generic Long Write packet (Data ID = 0x29) over the + * DSI link for each write access transaction to the chip configuration + * registers. + * Payload of this packet is 16-bit register address and 32-bit data. + * Multiple data values are allowed for sequential addresses. + * + * The Host sends a DSI Generic Read packet (Data ID = 0x24) over the DSI + * link for each read request transaction to the chip configuration + * registers. Payload of this packet is further defined as follows: + * 16-bit address followed by a 32-bit value (Generic Long Read Response + * packet). + * + * The bridge supports 5 GPIO lines controlled via the GPC register. + * + * The bridge support I2C Master/Slave. + * The I2C slave can be used for read/write to the bridge register instead of + * using the DSI interface. + * I2C slave address is 0x0F (read/write 0x1F/0x1E). + * The I2C Master can be used for communication with the panel if + * it has an I2C slave. + * + * NOTE: The I2C interface is not used in this driver. + * Only the DSI interface is used for read/write the bridge registers. + * + * Pixel data can be transmitted in non-burst or burst fashion. + * Non-burst refers to pixel data packet transmission time on DSI link + * being roughly the same (to account for packet overhead time) + * as active video line time on LVDS output (i.e. DE = 1). + * And burst refers to pixel data packet transmission time on DSI link + * being less than the active video line time on LVDS output. + * Video mode transmission is further differentiated by the types of + * timing events being transmitted. + * Video pulse mode refers to the case where both sync start and sync end + * events (for frame and line) are transmitted. + * Video event mode refers to the case where only sync start events + * are transmitted. + * This is configured via register bit VPCTRL.EVTMODE. + * + */ + +/* #define DEBUG 1 */ + +/** + * Use the I2C master to control the panel. + */ +/* #define TC358764_USE_I2C_MASTER */ + +#define DRV_NAME "mipi_tc358764" + +#include +#include +#include +#include +#include "msm_fb.h" +#include "mdp4.h" +#include "mipi_dsi.h" +#include "mipi_tc358764_dsi2lvds.h" + +/* Registers definition */ + +/* DSI D-PHY Layer Registers */ +#define D0W_DPHYCONTTX 0x0004 /* Data Lane 0 DPHY Tx Control */ +#define CLW_DPHYCONTRX 0x0020 /* Clock Lane DPHY Rx Control */ +#define D0W_DPHYCONTRX 0x0024 /* Data Lane 0 DPHY Rx Control */ +#define D1W_DPHYCONTRX 0x0028 /* Data Lane 1 DPHY Rx Control */ +#define D2W_DPHYCONTRX 0x002C /* Data Lane 2 DPHY Rx Control */ +#define D3W_DPHYCONTRX 0x0030 /* Data Lane 3 DPHY Rx Control */ +#define COM_DPHYCONTRX 0x0038 /* DPHY Rx Common Control */ +#define CLW_CNTRL 0x0040 /* Clock Lane Control */ +#define D0W_CNTRL 0x0044 /* Data Lane 0 Control */ +#define D1W_CNTRL 0x0048 /* Data Lane 1 Control */ +#define D2W_CNTRL 0x004C /* Data Lane 2 Control */ +#define D3W_CNTRL 0x0050 /* Data Lane 3 Control */ +#define DFTMODE_CNTRL 0x0054 /* DFT Mode Control */ + +/* DSI PPI Layer Registers */ +#define PPI_STARTPPI 0x0104 /* START control bit of PPI-TX function. */ +#define PPI_BUSYPPI 0x0108 +#define PPI_LINEINITCNT 0x0110 /* Line Initialization Wait Counter */ +#define PPI_LPTXTIMECNT 0x0114 +#define PPI_LANEENABLE 0x0134 /* Enables each lane at the PPI layer. */ +#define PPI_TX_RX_TA 0x013C /* DSI Bus Turn Around timing parameters */ + +/* Analog timer function enable */ +#define PPI_CLS_ATMR 0x0140 /* Delay for Clock Lane in LPRX */ +#define PPI_D0S_ATMR 0x0144 /* Delay for Data Lane 0 in LPRX */ +#define PPI_D1S_ATMR 0x0148 /* Delay for Data Lane 1 in LPRX */ +#define PPI_D2S_ATMR 0x014C /* Delay for Data Lane 2 in LPRX */ +#define PPI_D3S_ATMR 0x0150 /* Delay for Data Lane 3 in LPRX */ +#define PPI_D0S_CLRSIPOCOUNT 0x0164 + +#define PPI_D1S_CLRSIPOCOUNT 0x0168 /* For lane 1 */ +#define PPI_D2S_CLRSIPOCOUNT 0x016C /* For lane 2 */ +#define PPI_D3S_CLRSIPOCOUNT 0x0170 /* For lane 3 */ + +#define CLS_PRE 0x0180 /* Digital Counter inside of PHY IO */ +#define D0S_PRE 0x0184 /* Digital Counter inside of PHY IO */ +#define D1S_PRE 0x0188 /* Digital Counter inside of PHY IO */ +#define D2S_PRE 0x018C /* Digital Counter inside of PHY IO */ +#define D3S_PRE 0x0190 /* Digital Counter inside of PHY IO */ +#define CLS_PREP 0x01A0 /* Digital Counter inside of PHY IO */ +#define D0S_PREP 0x01A4 /* Digital Counter inside of PHY IO */ +#define D1S_PREP 0x01A8 /* Digital Counter inside of PHY IO */ +#define D2S_PREP 0x01AC /* Digital Counter inside of PHY IO */ +#define D3S_PREP 0x01B0 /* Digital Counter inside of PHY IO */ +#define CLS_ZERO 0x01C0 /* Digital Counter inside of PHY IO */ +#define D0S_ZERO 0x01C4 /* Digital Counter inside of PHY IO */ +#define D1S_ZERO 0x01C8 /* Digital Counter inside of PHY IO */ +#define D2S_ZERO 0x01CC /* Digital Counter inside of PHY IO */ +#define D3S_ZERO 0x01D0 /* Digital Counter inside of PHY IO */ + +#define PPI_CLRFLG 0x01E0 /* PRE Counters has reached set values */ +#define PPI_CLRSIPO 0x01E4 /* Clear SIPO values, Slave mode use only. */ +#define HSTIMEOUT 0x01F0 /* HS Rx Time Out Counter */ +#define HSTIMEOUTENABLE 0x01F4 /* Enable HS Rx Time Out Counter */ +#define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX function */ +#define DSI_BUSYDSI 0x0208 +#define DSI_LANEENABLE 0x0210 /* Enables each lane at the Protocol layer. */ +#define DSI_LANESTATUS0 0x0214 /* Displays lane is in HS RX mode. */ +#define DSI_LANESTATUS1 0x0218 /* Displays lane is in ULPS or STOP state */ + +#define DSI_INTSTATUS 0x0220 /* Interrupt Status */ +#define DSI_INTMASK 0x0224 /* Interrupt Mask */ +#define DSI_INTCLR 0x0228 /* Interrupt Clear */ +#define DSI_LPTXTO 0x0230 /* Low Power Tx Time Out Counter */ + +#define DSIERRCNT 0x0300 /* DSI Error Count */ +#define APLCTRL 0x0400 /* Application Layer Control */ +#define RDPKTLN 0x0404 /* Command Read Packet Length */ +#define VPCTRL 0x0450 /* Video Path Control */ +#define HTIM1 0x0454 /* Horizontal Timing Control 1 */ +#define HTIM2 0x0458 /* Horizontal Timing Control 2 */ +#define VTIM1 0x045C /* Vertical Timing Control 1 */ +#define VTIM2 0x0460 /* Vertical Timing Control 2 */ +#define VFUEN 0x0464 /* Video Frame Timing Update Enable */ + +/* Mux Input Select for LVDS LINK Input */ +#define LVMX0003 0x0480 /* Bit 0 to 3 */ +#define LVMX0407 0x0484 /* Bit 4 to 7 */ +#define LVMX0811 0x0488 /* Bit 8 to 11 */ +#define LVMX1215 0x048C /* Bit 12 to 15 */ +#define LVMX1619 0x0490 /* Bit 16 to 19 */ +#define LVMX2023 0x0494 /* Bit 20 to 23 */ +#define LVMX2427 0x0498 /* Bit 24 to 27 */ + +#define LVCFG 0x049C /* LVDS Configuration */ +#define LVPHY0 0x04A0 /* LVDS PHY 0 */ +#define LVPHY1 0x04A4 /* LVDS PHY 1 */ +#define SYSSTAT 0x0500 /* System Status */ +#define SYSRST 0x0504 /* System Reset */ + +/* GPIO Registers */ +#define GPIOC 0x0520 /* GPIO Control */ +#define GPIOO 0x0524 /* GPIO Output */ +#define GPIOI 0x0528 /* GPIO Input */ + +/* I2C Registers */ +#define I2CTIMCTRL 0x0540 /* I2C IF Timing and Enable Control */ +#define I2CMADDR 0x0544 /* I2C Master Addressing */ +#define WDATAQ 0x0548 /* Write Data Queue */ +#define RDATAQ 0x054C /* Read Data Queue */ + +/* Chip ID and Revision ID Register */ +#define IDREG 0x0580 + +#define TC358764XBG_ID 0x00006500 + +/* Debug Registers */ +#define DEBUG00 0x05A0 /* Debug */ +#define DEBUG01 0x05A4 /* LVDS Data */ + +/* PWM */ +static u32 d2l_pwm_freq_hz = (3.921*1000); + +#define PWM_FREQ_HZ (d2l_pwm_freq_hz) +#define PWM_PERIOD_USEC (USEC_PER_SEC / PWM_FREQ_HZ) +#define PWM_DUTY_LEVEL (PWM_PERIOD_USEC / PWM_LEVEL) + +#define CMD_DELAY 100 +#define DSI_MAX_LANES 4 +#define KHZ 1000 +#define MHZ (1000*1000) + +/** + * Command payload for DTYPE_GEN_LWRITE (0x29) / DTYPE_GEN_READ2 (0x24). + */ +struct wr_cmd_payload { + u16 addr; + u32 data; +} __packed; + +/* + * Driver state. + */ +static struct msm_panel_common_pdata *d2l_common_pdata; +struct msm_fb_data_type *d2l_mfd; +static struct dsi_buf d2l_tx_buf; +static struct dsi_buf d2l_rx_buf; +static int led_pwm; +static struct pwm_device *bl_pwm; +static struct pwm_device *tn_pwm; +static int bl_level; +static u32 d2l_gpio_out_mask; +static u32 d2l_gpio_out_val; +static u32 d2l_3d_gpio_enable; +static u32 d2l_3d_gpio_mode; +static int d2l_enable_3d; +static struct i2c_client *d2l_i2c_client; +static struct i2c_driver d2l_i2c_slave_driver; + +static int mipi_d2l_init(void); +static int mipi_d2l_enable_3d(struct msm_fb_data_type *mfd, + bool enable, bool mode); +static u32 d2l_i2c_read_reg(struct i2c_client *client, u16 reg); +static u32 d2l_i2c_write_reg(struct i2c_client *client, u16 reg, u32 val); + +/** + * Read a bridge register + * + * @param mfd + * + * @return register data value + */ +static u32 mipi_d2l_read_reg(struct msm_fb_data_type *mfd, u16 reg) +{ + u32 data; + int len = 4; + struct dsi_cmd_desc cmd_read_reg = { + DTYPE_GEN_READ2, 1, 0, 1, 0, /* cmd 0x24 */ + sizeof(reg), (char *) ®}; + + mipi_dsi_buf_init(&d2l_tx_buf); + mipi_dsi_buf_init(&d2l_rx_buf); + + /* mutex had been acquired at mipi_dsi_on */ + len = mipi_dsi_cmds_rx(mfd, &d2l_tx_buf, &d2l_rx_buf, + &cmd_read_reg, len); + + data = *(u32 *)d2l_rx_buf.data; + + if (len != 4) + pr_err("%s: invalid rlen=%d, expecting 4.\n", __func__, len); + + pr_debug("%s: reg=0x%x.data=0x%08x.\n", __func__, reg, data); + + return data; +} + +/** + * Write a bridge register + * + * @param mfd + * + * @return int + */ +static int mipi_d2l_write_reg(struct msm_fb_data_type *mfd, u16 reg, u32 data) +{ + struct wr_cmd_payload payload; + struct dsi_cmd_desc cmd_write_reg = { + DTYPE_GEN_LWRITE, 1, 0, 0, 0, + sizeof(payload), (char *)&payload}; + + payload.addr = reg; + payload.data = data; + + /* mutex had been acquired at mipi_dsi_on */ + mipi_dsi_cmds_tx(mfd, &d2l_tx_buf, &cmd_write_reg, 1); + + pr_debug("%s: reg=0x%x. data=0x%x.\n", __func__, reg, data); + + return 0; +} + +static void mipi_d2l_read_status(struct msm_fb_data_type *mfd) +{ + mipi_d2l_read_reg(mfd, DSI_LANESTATUS0); /* 0x214 */ + mipi_d2l_read_reg(mfd, DSI_LANESTATUS1); /* 0x218 */ + mipi_d2l_read_reg(mfd, DSI_INTSTATUS); /* 0x220 */ + mipi_d2l_read_reg(mfd, SYSSTAT); /* 0x500 */ +} + +static void mipi_d2l_read_status_via_i2c(struct i2c_client *client) +{ + u32 tmp = 0; + + tmp = d2l_i2c_read_reg(client, DSIERRCNT); + d2l_i2c_write_reg(client, DSIERRCNT, 0xFFFF0000); + + d2l_i2c_read_reg(client, DSI_LANESTATUS0); /* 0x214 */ + d2l_i2c_read_reg(client, DSI_LANESTATUS1); /* 0x218 */ + d2l_i2c_read_reg(client, DSI_INTSTATUS); /* 0x220 */ + d2l_i2c_read_reg(client, SYSSTAT); /* 0x500 */ + + d2l_i2c_write_reg(client, DSIERRCNT, tmp); +} +/** + * Init the D2L bridge via the DSI interface for Video. + * + * VPCTRL.EVTMODE (0x20) configuration bit is needed to determine whether + * video timing information is delivered in pulse mode or event mode. + * In pulse mode, both Sync Start and End packets are required. + * In event mode, only Sync Start packets are required. + * + * @param mfd + * + * @return int + */ +static int mipi_d2l_dsi_init_sequence(struct msm_fb_data_type *mfd) +{ + struct mipi_panel_info *mipi = &mfd->panel_info.mipi; + u32 lanes_enable; + u32 vpctrl; + u32 htime1; + u32 vtime1; + u32 htime2; + u32 vtime2; + u32 ppi_tx_rx_ta; /* BTA Bus-Turn-Around */ + u32 lvcfg; + u32 hbpr; /* Horizontal Back Porch */ + u32 hpw; /* Horizontal Pulse Width */ + u32 vbpr; /* Vertical Back Porch */ + u32 vpw; /* Vertical Pulse Width */ + + u32 hfpr; /* Horizontal Front Porch */ + u32 hsize; /* Horizontal Active size */ + u32 vfpr; /* Vertical Front Porch */ + u32 vsize; /* Vertical Active size */ + bool vesa_rgb888 = false; + + lanes_enable = 0x01; /* clock-lane enable */ + lanes_enable |= (mipi->data_lane0 << 1); + lanes_enable |= (mipi->data_lane1 << 2); + lanes_enable |= (mipi->data_lane2 << 3); + lanes_enable |= (mipi->data_lane3 << 4); + + if (mipi->traffic_mode == DSI_NON_BURST_SYNCH_EVENT) + vpctrl = 0x01000120; + else if (mipi->traffic_mode == DSI_NON_BURST_SYNCH_PULSE) + vpctrl = 0x01000100; + else { + pr_err("%s.unsupported traffic_mode %d.\n", + __func__, mipi->traffic_mode); + return -EINVAL; + } + + if (mfd->panel_info.clk_rate > 800*1000*1000) { + pr_err("%s.unsupported clk_rate %d.\n", + __func__, mfd->panel_info.clk_rate); + return -EINVAL; + } + + pr_debug("%s.xres=%d.yres=%d.fps=%d.dst_format=%d.\n", + __func__, + mfd->panel_info.xres, + mfd->panel_info.yres, + mfd->panel_info.mipi.frame_rate, + mfd->panel_info.mipi.dst_format); + + hbpr = mfd->panel_info.lcdc.h_back_porch; + hpw = mfd->panel_info.lcdc.h_pulse_width; + vbpr = mfd->panel_info.lcdc.v_back_porch; + vpw = mfd->panel_info.lcdc.v_pulse_width; + + htime1 = (hbpr << 16) + hpw; + vtime1 = (vbpr << 16) + vpw; + + hfpr = mfd->panel_info.lcdc.h_front_porch; + hsize = mfd->panel_info.xres; + vfpr = mfd->panel_info.lcdc.v_front_porch; + vsize = mfd->panel_info.yres; + + htime2 = (hfpr << 16) + hsize; + vtime2 = (vfpr << 16) + vsize; + + lvcfg = 0x0003; /* PCLK=DCLK/3, Dual Link, LVEN */ + vpctrl = 0x01000120; /* Output RGB888 , Event-Mode , */ + ppi_tx_rx_ta = 0x00040004; + + if (mfd->panel_info.xres == 1366) { + ppi_tx_rx_ta = 0x00040004; + lvcfg = 0x01; /* LVEN */ + vesa_rgb888 = true; + } + + if (mfd->panel_info.xres == 1200) { + lvcfg = 0x0103; /* PCLK=DCLK/4, Dual Link, LVEN */ + vesa_rgb888 = true; + } + + pr_debug("%s.htime1=0x%x.\n", __func__, htime1); + pr_debug("%s.vtime1=0x%x.\n", __func__, vtime1); + pr_debug("%s.vpctrl=0x%x.\n", __func__, vpctrl); + pr_debug("%s.lvcfg=0x%x.\n", __func__, lvcfg); + + mipi_d2l_write_reg(mfd, SYSRST, 0xFF); + msleep(30); + + if (vesa_rgb888) { + /* VESA format instead of JEIDA format for RGB888 */ + mipi_d2l_write_reg(mfd, LVMX0003, 0x03020100); + mipi_d2l_write_reg(mfd, LVMX0407, 0x08050704); + mipi_d2l_write_reg(mfd, LVMX0811, 0x0F0E0A09); + mipi_d2l_write_reg(mfd, LVMX1215, 0x100D0C0B); + mipi_d2l_write_reg(mfd, LVMX1619, 0x12111716); + mipi_d2l_write_reg(mfd, LVMX2023, 0x1B151413); + mipi_d2l_write_reg(mfd, LVMX2427, 0x061A1918); + } + + mipi_d2l_write_reg(mfd, PPI_TX_RX_TA, ppi_tx_rx_ta); /* BTA */ + mipi_d2l_write_reg(mfd, PPI_LPTXTIMECNT, 0x00000004); + mipi_d2l_write_reg(mfd, PPI_D0S_CLRSIPOCOUNT, 0x00000003); + mipi_d2l_write_reg(mfd, PPI_D1S_CLRSIPOCOUNT, 0x00000003); + mipi_d2l_write_reg(mfd, PPI_D2S_CLRSIPOCOUNT, 0x00000003); + mipi_d2l_write_reg(mfd, PPI_D3S_CLRSIPOCOUNT, 0x00000003); + mipi_d2l_write_reg(mfd, PPI_LANEENABLE, lanes_enable); + mipi_d2l_write_reg(mfd, DSI_LANEENABLE, lanes_enable); + mipi_d2l_write_reg(mfd, PPI_STARTPPI, 0x00000001); + mipi_d2l_write_reg(mfd, DSI_STARTDSI, 0x00000001); + + mipi_d2l_write_reg(mfd, VPCTRL, vpctrl); /* RGB888 + Event mode */ + mipi_d2l_write_reg(mfd, HTIM1, htime1); + mipi_d2l_write_reg(mfd, VTIM1, vtime1); + mipi_d2l_write_reg(mfd, HTIM2, htime2); + mipi_d2l_write_reg(mfd, VTIM2, vtime2); + mipi_d2l_write_reg(mfd, VFUEN, 0x00000001); + mipi_d2l_write_reg(mfd, LVCFG, lvcfg); /* Enables LVDS tx */ + + return 0; +} + +/** + * Set Backlight level. + * + * @param pwm + * @param level + * + * @return int + */ +static int mipi_d2l_set_backlight_level(struct pwm_device *pwm, int level) +{ + int ret = 0; + + pr_debug("%s: level=%d.\n", __func__, level); + + if ((pwm == NULL) || (level > PWM_LEVEL) || (level < 0)) { + pr_err("%s.pwm=NULL.\n", __func__); + return -EINVAL; + } + + ret = pwm_config(pwm, PWM_DUTY_LEVEL * level, PWM_PERIOD_USEC); + if (ret) { + pr_err("%s: pwm_config() failed err=%d.\n", __func__, ret); + return ret; + } + + ret = pwm_enable(pwm); + if (ret) { + pr_err("%s: pwm_enable() failed err=%d\n", + __func__, ret); + return ret; + } + + return 0; +} + +/** + * Set TN CLK. + * + * @param pwm + * @param level + * + * @return int + */ +static int mipi_d2l_set_tn_clk(struct pwm_device *pwm, u32 usec) +{ + int ret = 0; + + pr_debug("%s: usec=%d.\n", __func__, usec); + + ret = pwm_config(pwm, usec/2 , usec); + if (ret) { + pr_err("%s: pwm_config() failed err=%d.\n", __func__, ret); + return ret; + } + + ret = pwm_enable(pwm); + if (ret) { + pr_err("%s: pwm_enable() failed err=%d\n", + __func__, ret); + return ret; + } + + return 0; +} + +/** + * LCD ON. + * + * Set LCD On via MIPI interface or I2C-Slave interface. + * Set Backlight on. + * + * @param pdev + * + * @return int + */ +static int mipi_d2l_lcd_on(struct platform_device *pdev) +{ + int ret = 0; + u32 chip_id; + struct msm_fb_data_type *mfd; + + pr_info("%s.\n", __func__); + + /* wait for valid clock before sending data over DSI or I2C. */ + msleep(30); + + mfd = platform_get_drvdata(pdev); + d2l_mfd = mfd; + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + chip_id = mipi_d2l_read_reg(mfd, IDREG); + + + if (chip_id != TC358764XBG_ID) { + pr_err("%s: invalid chip_id=0x%x", __func__, chip_id); + return -ENODEV; + } + + ret = mipi_d2l_dsi_init_sequence(mfd); + if (ret) + return ret; + + mipi_d2l_write_reg(mfd, GPIOC, d2l_gpio_out_mask); + /* Set gpio#4=U/D=0, gpio#3=L/R=1 , gpio#2,1=CABC=0, gpio#0=NA. */ + mipi_d2l_write_reg(mfd, GPIOO, d2l_gpio_out_val); + + d2l_pwm_freq_hz = (3.921*1000); + + if (bl_level == 0) + bl_level = PWM_LEVEL * 2 / 3 ; /* Default ON value */ + + /* Set backlight via PWM */ + if (bl_pwm) { + ret = mipi_d2l_set_backlight_level(bl_pwm, bl_level); + if (ret) + pr_err("%s.mipi_d2l_set_backlight_level.ret=%d", + __func__, ret); + } + + mipi_d2l_read_status(mfd); + + mipi_d2l_enable_3d(mfd, false, false); + + /* Add I2C driver only after DSI-CLK is running */ + i2c_add_driver(&d2l_i2c_slave_driver); + + pr_info("%s.ret=%d.\n", __func__, ret); + + return ret; +} + +/** + * LCD OFF. + * + * @param pdev + * + * @return int + */ +static int mipi_d2l_lcd_off(struct platform_device *pdev) +{ + int ret; + struct msm_fb_data_type *mfd; + + pr_info("%s.\n", __func__); + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + ret = mipi_d2l_set_backlight_level(bl_pwm, 1); + + pr_info("%s.ret=%d.\n", __func__, ret); + + return ret; +} + +static void mipi_d2l_set_backlight(struct msm_fb_data_type *mfd) +{ + int level = mfd->bl_level; + + pr_debug("%s.lvl=%d.\n", __func__, level); + + mipi_d2l_set_backlight_level(bl_pwm, level); + + bl_level = level; +} + +static struct msm_fb_panel_data d2l_panel_data = { + .on = mipi_d2l_lcd_on, + .off = mipi_d2l_lcd_off, + .set_backlight = mipi_d2l_set_backlight, +}; + +static u32 d2l_i2c_read_reg(struct i2c_client *client, u16 reg) +{ + int rc; + u32 val = 0; + u8 buf[6]; + + if (client == NULL) { + pr_err("%s.invalid i2c client.\n", __func__); + return -EINVAL; + } + + buf[0] = reg >> 8; + buf[1] = reg & 0xFF; + + rc = i2c_master_send(client, buf, sizeof(reg)); + rc = i2c_master_recv(client, buf, 4); + + if (rc >= 0) { + val = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + pr_debug("%s.reg=0x%x.val=0x%x.\n", __func__, reg, val); + } else + pr_err("%s.fail.reg=0x%x.\n", __func__, reg); + + return val; +} + +static u32 d2l_i2c_write_reg(struct i2c_client *client, u16 reg, u32 val) +{ + int rc; + u8 buf[6]; + + if (client == NULL) { + pr_err("%s.invalid i2c client.\n", __func__); + return -EINVAL; + } + + buf[0] = reg >> 8; + buf[1] = reg & 0xFF; + + buf[2] = (val >> 0) & 0xFF; + buf[3] = (val >> 8) & 0xFF; + buf[4] = (val >> 16) & 0xFF; + buf[5] = (val >> 24) & 0xFF; + + rc = i2c_master_send(client, buf, sizeof(buf)); + + if (rc >= 0) + pr_debug("%s.reg=0x%x.val=0x%x.\n", __func__, reg, val); + else + pr_err("%s.fail.reg=0x%x.\n", __func__, reg); + + return val; +} + +static int __devinit d2l_i2c_slave_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static const u32 i2c_funcs = I2C_FUNC_I2C; + + d2l_i2c_client = client; + + if (!i2c_check_functionality(client->adapter, i2c_funcs)) { + pr_err("%s.i2c_check_functionality failed.\n", __func__); + return -ENOSYS; + } else { + pr_debug("%s.i2c_check_functionality OK.\n", __func__); + } + + d2l_i2c_read_reg(client, IDREG); + + mipi_d2l_read_status_via_i2c(d2l_i2c_client); + + return 0; +} + +static __devexit int d2l_i2c_slave_remove(struct i2c_client *client) +{ + d2l_i2c_client = NULL; + + return 0; +} + +static const struct i2c_device_id d2l_i2c_id[] = { + {"tc358764-i2c", 0}, + {} +}; + +static struct i2c_driver d2l_i2c_slave_driver = { + .driver = { + .name = "tc358764-i2c", + .owner = THIS_MODULE + }, + .probe = d2l_i2c_slave_probe, + .remove = __devexit_p(d2l_i2c_slave_remove), + .id_table = d2l_i2c_id, +}; + +static int mipi_d2l_enable_3d(struct msm_fb_data_type *mfd, + bool enable, bool mode) +{ + u32 tn_usec = 1000000 / 66; /* 66 HZ */ + + pr_debug("%s.enable=%d.mode=%d.\n", __func__, enable, mode); + + gpio_direction_output(d2l_3d_gpio_enable, enable); + gpio_direction_output(d2l_3d_gpio_mode, mode); + + mipi_d2l_set_tn_clk(tn_pwm, tn_usec); + + return 0; +} + +static ssize_t mipi_d2l_enable_3d_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf((char *)buf, sizeof(buf), "%u\n", d2l_enable_3d); +} + +static ssize_t mipi_d2l_enable_3d_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. Invalid input.\n", __func__); + ret = -EINVAL; + } else { + d2l_enable_3d = data; + if (data == 1) /* LANDSCAPE */ + mipi_d2l_enable_3d(d2l_mfd, true, true); + else if (data == 2) /* PORTRAIT */ + mipi_d2l_enable_3d(d2l_mfd, true, false); + else if (data == 0) + mipi_d2l_enable_3d(d2l_mfd, false, false); + else if (data == 9) + mipi_d2l_read_status_via_i2c(d2l_i2c_client); + else + pr_err("%s.Invalid value=%d.\n", __func__, data); + } + + return count; +} + +static struct device_attribute mipi_d2l_3d_barrier_attributes[] = { + __ATTR(enable_3d_barrier, 0666, + mipi_d2l_enable_3d_read, + mipi_d2l_enable_3d_write), +}; + +static int mipi_dsi_3d_barrier_sysfs_register(struct device *dev) +{ + int ret; + + pr_debug("%s.d2l_3d_gpio_enable=%d.\n", __func__, d2l_3d_gpio_enable); + pr_debug("%s.d2l_3d_gpio_mode=%d.\n", __func__, d2l_3d_gpio_mode); + + ret = device_create_file(dev, mipi_d2l_3d_barrier_attributes); + if (ret) { + pr_err("%s.failed to create 3D sysfs.\n", __func__); + goto err_device_create_file; + } + + ret = gpio_request(d2l_3d_gpio_enable, "d2l_3d_gpio_enable"); + if (ret) { + pr_err("%s.failed to get d2l_3d_gpio_enable=%d.\n", + __func__, d2l_3d_gpio_enable); + goto err_d2l_3d_gpio_enable; + } + + ret = gpio_request(d2l_3d_gpio_mode, "d2l_3d_gpio_mode"); + if (ret) { + pr_err("%s.failed to get d2l_3d_gpio_mode=%d.\n", + __func__, d2l_3d_gpio_mode); + goto err_d2l_3d_gpio_mode; + } + + return 0; + +err_d2l_3d_gpio_mode: + gpio_free(d2l_3d_gpio_enable); +err_d2l_3d_gpio_enable: + device_remove_file(dev, mipi_d2l_3d_barrier_attributes); +err_device_create_file: + + return ret; +} + +/** + * Probe for device. + * + * Both the "target" and "panel" device use the same probe function. + * "Target" device has id=0, "Panel" devic has non-zero id. + * Target device should register first, passing msm_panel_common_pdata. + * Panel device passing msm_panel_info. + * + * @param pdev + * + * @return int + */ +static int __devinit mipi_d2l_probe(struct platform_device *pdev) +{ + int ret = 0; + struct msm_panel_info *pinfo = NULL; + + pr_debug("%s.id=%d.\n", __func__, pdev->id); + + if (pdev->id == 0) { + d2l_common_pdata = pdev->dev.platform_data; + + if (d2l_common_pdata == NULL) { + pr_err("%s: no PWM gpio specified.\n", __func__); + return 0; + } + + led_pwm = d2l_common_pdata->gpio_num[0]; + d2l_gpio_out_mask = d2l_common_pdata->gpio_num[1] >> 8; + d2l_gpio_out_val = d2l_common_pdata->gpio_num[1] & 0xFF; + d2l_3d_gpio_enable = d2l_common_pdata->gpio_num[2]; + d2l_3d_gpio_mode = d2l_common_pdata->gpio_num[3]; + + mipi_dsi_buf_alloc(&d2l_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&d2l_rx_buf, DSI_BUF_SIZE); + + return 0; + } + + if (d2l_common_pdata == NULL) { + pr_err("%s: d2l_common_pdata is NULL.\n", __func__); + return -ENODEV; + } + + bl_pwm = NULL; + if (led_pwm >= 0) { + bl_pwm = pwm_request(led_pwm, "lcd-backlight"); + if (bl_pwm == NULL || IS_ERR(bl_pwm)) { + pr_err("%s pwm_request() failed.id=%d.bl_pwm=%d.\n", + __func__, led_pwm, (int) bl_pwm); + bl_pwm = NULL; + return -EIO; + } else { + pr_debug("%s.pwm_request() ok.pwm-id=%d.\n", + __func__, led_pwm); + + } + } else { + pr_err("%s. led_pwm is invalid.\n", __func__); + } + + tn_pwm = pwm_request(1, "3D_TN_clk"); + if (tn_pwm == NULL || IS_ERR(tn_pwm)) { + pr_err("%s pwm_request() failed.id=%d.tn_pwm=%d.\n", + __func__, 1, (int) tn_pwm); + tn_pwm = NULL; + return -EIO; + } else { + pr_debug("%s.pwm_request() ok.pwm-id=%d.\n", __func__, 1); + + } + + pinfo = pdev->dev.platform_data; + + if (pinfo == NULL) { + pr_err("%s: pinfo is NULL.\n", __func__); + return -ENODEV; + } + + d2l_panel_data.panel_info = *pinfo; + + pdev->dev.platform_data = &d2l_panel_data; + + msm_fb_add_device(pdev); + + if (pinfo->is_3d_panel) + mipi_dsi_3d_barrier_sysfs_register(&(pdev->dev)); + + return ret; +} + +/** + * Device removal notification handler. + * + * @param pdev + * + * @return int + */ +static int __devexit mipi_d2l_remove(struct platform_device *pdev) +{ + /* Note: There are no APIs to remove fb device and free DSI buf. */ + pr_debug("%s.\n", __func__); + + if (bl_pwm) { + pwm_free(bl_pwm); + bl_pwm = NULL; + } + + return 0; +} + +/** + * Register the panel device. + * + * @param pinfo + * @param channel_id + * @param panel_id + * + * @return int + */ +int mipi_tc358764_dsi2lvds_register(struct msm_panel_info *pinfo, + u32 channel_id, u32 panel_id) +{ + struct platform_device *pdev = NULL; + int ret; + /* Use DSI-to-LVDS bridge */ + const char driver_name[] = "mipi_tc358764"; + + pr_debug("%s.\n", __func__); + ret = mipi_d2l_init(); + if (ret) { + pr_err("mipi_d2l_init() failed with ret %u\n", ret); + return ret; + } + + /* Note: the device id should be non-zero */ + pdev = platform_device_alloc(driver_name, (panel_id << 8)|channel_id); + if (pdev == NULL) + return -ENOMEM; + + pdev->dev.platform_data = pinfo; + + 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 struct platform_driver d2l_driver = { + .probe = mipi_d2l_probe, + .remove = __devexit_p(mipi_d2l_remove), + .driver = { + .name = DRV_NAME, + }, +}; + +/** + * Module Init + * + * @return int + */ +static int mipi_d2l_init(void) +{ + pr_debug("%s.\n", __func__); + + return platform_driver_register(&d2l_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Toshiba MIPI-DSI-to-LVDS bridge driver"); +MODULE_AUTHOR("Amir Samuelov "); diff --git a/drivers/video/msm/mipi_tc358764_dsi2lvds.h b/drivers/video/msm/mipi_tc358764_dsi2lvds.h new file mode 100644 index 0000000000000000000000000000000000000000..1b949f01d99c21cf45a69a87f9d79de51fbaa469 --- /dev/null +++ b/drivers/video/msm/mipi_tc358764_dsi2lvds.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_TC358764_DSI2LVDS_H +#define MIPI_TC358764_DSI2LVDS_H + +#define PWM_LEVEL 255 + +int mipi_tc358764_dsi2lvds_register(struct msm_panel_info *pinfo, + u32 channel_id, u32 panel_id); +#endif /* MIPI_TC358764_DSI2LVDS_H */ diff --git a/drivers/video/msm/mipi_toshiba.c b/drivers/video/msm/mipi_toshiba.c new file mode 100644 index 0000000000000000000000000000000000000000..aeaa5aa079f031bfcec55530cf333cf1d98e4059 --- /dev/null +++ b/drivers/video/msm/mipi_toshiba.c @@ -0,0 +1,355 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 pwm_device *bl_lpm; +static struct mipi_dsi_panel_platform_data *mipi_toshiba_pdata; + +#define TM_GET_PID(id) (((id) & 0xff00)>>8) + +static struct dsi_buf toshiba_tx_buf; +static struct dsi_buf toshiba_rx_buf; +static int mipi_toshiba_lcd_init(void); + +#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}; + +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_wvga_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} +}; + +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_wsvga_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} +}; + +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; + + if (TM_GET_PID(mfd->panel.id) == MIPI_DSI_PANEL_WVGA_PT) + mipi_dsi_cmds_tx(mfd, &toshiba_tx_buf, + toshiba_wvga_display_on_cmds, + ARRAY_SIZE(toshiba_wvga_display_on_cmds)); + else if (TM_GET_PID(mfd->panel.id) == MIPI_DSI_PANEL_WSVGA_PT || + TM_GET_PID(mfd->panel.id) == MIPI_DSI_PANEL_WUXGA) + mipi_dsi_cmds_tx(mfd, &toshiba_tx_buf, + toshiba_wsvga_display_on_cmds, + ARRAY_SIZE(toshiba_wsvga_display_on_cmds)); + else + return -EINVAL; + + 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; +} + +void mipi_bklight_pwm_cfg(void) +{ + if (mipi_toshiba_pdata && mipi_toshiba_pdata->dsi_pwm_cfg) + mipi_toshiba_pdata->dsi_pwm_cfg(); +} + +static void mipi_toshiba_set_backlight(struct msm_fb_data_type *mfd) +{ + int ret; + static int bklight_pwm_cfg; + + if (bklight_pwm_cfg == 0) { + mipi_bklight_pwm_cfg(); + bklight_pwm_cfg++; + } + + if (bl_lpm) { + ret = pwm_config(bl_lpm, MIPI_TOSHIBA_PWM_DUTY_LEVEL * + mfd->bl_level, MIPI_TOSHIBA_PWM_PERIOD_USEC); + if (ret) { + pr_err("pwm_config on lpm failed %d\n", ret); + return; + } + if (mfd->bl_level) { + ret = pwm_enable(bl_lpm); + if (ret) + pr_err("pwm enable/disable on lpm failed" + "for bl %d\n", mfd->bl_level); + } else { + pwm_disable(bl_lpm); + } + } +} + +static int __devinit mipi_toshiba_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mipi_toshiba_pdata = pdev->dev.platform_data; + return 0; + } + + if (mipi_toshiba_pdata == NULL) { + pr_err("%s.invalid platform data.\n", __func__); + return -ENODEV; + } + + if (mipi_toshiba_pdata != NULL) + bl_lpm = pwm_request(mipi_toshiba_pdata->gpio[0], + "backlight"); + + if (bl_lpm == NULL || IS_ERR(bl_lpm)) { + pr_err("%s pwm_request() failed\n", __func__); + bl_lpm = NULL; + } + pr_debug("bl_lpm = %p lpm = %d\n", bl_lpm, + mipi_toshiba_pdata->gpio[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, + .set_backlight = mipi_toshiba_set_backlight, +}; + +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; + + ret = mipi_toshiba_lcd_init(); + if (ret) { + pr_err("mipi_toshiba_lcd_init() failed with ret %u\n", ret); + return ret; + } + + 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 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); +} diff --git a/drivers/video/msm/mipi_toshiba.h b/drivers/video/msm/mipi_toshiba.h new file mode 100644 index 0000000000000000000000000000000000000000..41071616ea845e03b74e42c71dd0fca1d47e02ff --- /dev/null +++ b/drivers/video/msm/mipi_toshiba.h @@ -0,0 +1,30 @@ + +/* 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_TOSHIBA_H +#define MIPI_TOSHIBA_H + +#include +#include + +int mipi_toshiba_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#define MIPI_TOSHIBA_PWM_FREQ_HZ 3921 +#define MIPI_TOSHIBA_PWM_PERIOD_USEC (USEC_PER_SEC / MIPI_TOSHIBA_PWM_FREQ_HZ) +#define MIPI_TOSHIBA_PWM_LEVEL 255 +#define MIPI_TOSHIBA_PWM_DUTY_LEVEL \ + (MIPI_TOSHIBA_PWM_PERIOD_USEC / MIPI_TOSHIBA_PWM_LEVEL) + +#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 0000000000000000000000000000000000000000..2a8610b64c72a83173bc12ddeabb3e247b3fa056 --- /dev/null +++ b/drivers/video/msm/mipi_toshiba_video_wsvga_pt.c @@ -0,0 +1,104 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 */ + {0x09, 0x08, 0x05, 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, 0x31, 0xda, 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; + + if (msm_fb_detect_client("mipi_video_toshiba_wsvga")) + return 0; + + 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.lcdc.xres_pad = 200; + pinfo.lcdc.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 = MIPI_TOSHIBA_PWM_LEVEL; + 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_WSVGA_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_wuxga.c b/drivers/video/msm/mipi_toshiba_video_wuxga.c new file mode 100644 index 0000000000000000000000000000000000000000..297248fa64b77b453ae2909008905ee21fb31c44 --- /dev/null +++ b/drivers/video/msm/mipi_toshiba_video_wuxga.c @@ -0,0 +1,97 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 = { + /* 1920*1200, RGB888, 4 Lane 60 fps video mode */ + /* regulator */ + {0x03, 0x0a, 0x04, 0x00, 0x20}, + /* timing */ + {0x66, 0x26, 0x1F, 0x00, 0x55, 0x9C, 0x16, 0x90, + 0x23, 0x03, 0x04, 0xa0}, + /* phy ctrl */ + {0x5f, 0x00, 0x00, 0x10}, + /* strength */ + {0xff, 0x00, 0x06, 0x00}, + /* pll control */ + {0x0, 0xD7, 0x1, 0x19, 0x00, 0x50, 0x48, 0x63, + 0x41, 0x0f, 0x01, + 0x00, 0x14, 0x03, 0x00, 0x02, 0x00, 0x20, 0x00, 0x01 }, +}; + +static int __init mipi_video_toshiba_wuxga_init(void) +{ + int ret; + + if (msm_fb_detect_client("mipi_video_toshiba_wuxga")) + return 0; + + pinfo.xres = 1920; + pinfo.yres = 1200; + + pinfo.type = MIPI_VIDEO_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 = 170; + pinfo.lcdc.v_back_porch = 7; + pinfo.lcdc.v_front_porch = 8; + pinfo.lcdc.v_pulse_width = 30; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = MIPI_TOSHIBA_PWM_LEVEL; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + pinfo.clk_rate = 981560000; + + 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.data_lane2 = TRUE; + pinfo.mipi.data_lane3 = TRUE; + 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_toshiba_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_WUXGA); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_toshiba_wuxga_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 0000000000000000000000000000000000000000..d6cabfcb8799e7838f03d2a1fdb4a28a1f42cbba --- /dev/null +++ b/drivers/video/msm/mipi_toshiba_video_wvga_pt.c @@ -0,0 +1,106 @@ +/* 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; + + if (msm_fb_detect_client("mipi_video_toshiba_wvga")) + return 0; + + 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/mipi_truly.c b/drivers/video/msm/mipi_truly.c new file mode 100644 index 0000000000000000000000000000000000000000..a2060f09dc4cd4ed10fbb677d861addbed71d293 --- /dev/null +++ b/drivers/video/msm/mipi_truly.c @@ -0,0 +1,259 @@ +/* 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_truly.h" + +static struct msm_panel_common_pdata *mipi_truly_pdata; +static struct dsi_buf truly_tx_buf; +static struct dsi_buf truly_rx_buf; + +#define TRULY_CMD_DELAY 0 +#define TRULY_SLEEP_OFF_DELAY 150 +#define TRULY_DISPLAY_ON_DELAY 150 +#define GPIO_TRULY_LCD_RESET 129 + +static int prev_bl = 17; + +static char extend_cmd_enable[4] = {0xB9, 0xFF, 0x83, 0x69}; +static char display_setting[16] = { + 0xB2, 0x00, 0x23, 0x62, + 0x62, 0x70, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0x03, 0x00, 0x01, +}; +static char wave_cycle_setting[6] = {0xB4, 0x00, 0x1D, 0x5F, 0x0E, 0x06}; +static char gip_setting[27] = { + 0xD5, 0x00, 0x04, 0x03, + 0x00, 0x01, 0x05, 0x1C, + 0x70, 0x01, 0x03, 0x00, + 0x00, 0x40, 0x06, 0x51, + 0x07, 0x00, 0x00, 0x41, + 0x06, 0x50, 0x07, 0x07, + 0x0F, 0x04, 0x00, +}; +static char power_setting[20] = { + 0xB1, 0x01, 0x00, 0x34, + 0x06, 0x00, 0x0F, 0x0F, + 0x2A, 0x32, 0x3F, 0x3F, + 0x07, 0x3A, 0x01, 0xE6, + 0xE6, 0xE6, 0xE6, 0xE6, +}; +static char vcom_setting[3] = {0xB6, 0x56, 0x56}; +static char pannel_setting[2] = {0xCC, 0x02}; +static char gamma_setting[35] = { + 0xE0, 0x00, 0x1D, 0x22, + 0x38, 0x3D, 0x3F, 0x2E, + 0x4A, 0x06, 0x0D, 0x0F, + 0x13, 0x15, 0x13, 0x16, + 0x10, 0x19, 0x00, 0x1D, + 0x22, 0x38, 0x3D, 0x3F, + 0x2E, 0x4A, 0x06, 0x0D, + 0x0F, 0x13, 0x15, 0x13, + 0x16, 0x10, 0x19, +}; +static char mipi_setting[14] = { + 0xBA, 0x00, 0xA0, 0xC6, + 0x00, 0x0A, 0x00, 0x10, + 0x30, 0x6F, 0x02, 0x11, + 0x18, 0x40, +}; +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}; + +static struct dsi_cmd_desc truly_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 struct dsi_cmd_desc truly_display_on_cmds[] = { + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(extend_cmd_enable), extend_cmd_enable}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(display_setting), display_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(wave_cycle_setting), wave_cycle_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(gip_setting), gip_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(power_setting), power_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(vcom_setting), vcom_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(pannel_setting), pannel_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(gamma_setting), gamma_setting}, + {DTYPE_GEN_LWRITE, 1, 0, 0, TRULY_CMD_DELAY, + sizeof(mipi_setting), mipi_setting}, + {DTYPE_DCS_WRITE, 1, 0, 0, TRULY_SLEEP_OFF_DELAY, + sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, TRULY_DISPLAY_ON_DELAY, + sizeof(display_on), display_on}, +}; + +static int mipi_truly_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; + + msleep(20); + mipi_dsi_cmds_tx(mfd, &truly_tx_buf, truly_display_on_cmds, + ARRAY_SIZE(truly_display_on_cmds)); + + return 0; +} + +static int mipi_truly_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, &truly_tx_buf, truly_display_off_cmds, + ARRAY_SIZE(truly_display_off_cmds)); + + return 0; +} + +#define BL_LEVEL 17 +static void mipi_truly_set_backlight(struct msm_fb_data_type *mfd) +{ + int step = 0, i = 0; + int bl_level = mfd->bl_level; + + /* real backlight level, 1 - max, 16 - min, 17 - off */ + bl_level = BL_LEVEL - bl_level; + + if (bl_level > prev_bl) { + step = bl_level - prev_bl; + if (bl_level == BL_LEVEL) + step--; + } else if (bl_level < prev_bl) { + step = bl_level + 16 - prev_bl; + } else { + pr_debug("%s: no change\n", __func__); + return; + } + + if (bl_level == BL_LEVEL) { + /* turn off backlight */ + mipi_truly_pdata->pmic_backlight(0); + } else { + if (prev_bl == BL_LEVEL) { + /* turn on backlight */ + mipi_truly_pdata->pmic_backlight(1); + udelay(30); + } + /* adjust backlight level */ + for (i = 0; i < step; i++) { + mipi_truly_pdata->pmic_backlight(0); + udelay(1); + mipi_truly_pdata->pmic_backlight(1); + udelay(1); + } + } + msleep(20); + prev_bl = bl_level; + + return; +} + +static int __devinit mipi_truly_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mipi_truly_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mipi_truly_lcd_probe, + .driver = { + .name = "mipi_truly", + }, +}; + +static struct msm_fb_panel_data truly_panel_data = { + .on = mipi_truly_lcd_on, + .off = mipi_truly_lcd_off, + .set_backlight = mipi_truly_set_backlight, +}; + +static int ch_used[3]; + +int mipi_truly_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_truly", (panel << 8)|channel); + + if (!pdev) + return -ENOMEM; + + truly_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &truly_panel_data, + sizeof(truly_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_truly_lcd_init(void) +{ + mipi_dsi_buf_alloc(&truly_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&truly_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} + +module_init(mipi_truly_lcd_init); diff --git a/drivers/video/msm/mipi_truly.h b/drivers/video/msm/mipi_truly.h new file mode 100644 index 0000000000000000000000000000000000000000..900e6f6beb7b8cacca44b77d9ede1c0c8951dc67 --- /dev/null +++ b/drivers/video/msm/mipi_truly.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_TRULY_H +#define MIPI_TRULY_H + +/* #define MIPI_TRULY_FAKE_PANEL */ /* FAKE PANEL for test */ + +int mipi_truly_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_TRULY_H */ diff --git a/drivers/video/msm/mipi_truly_tft540960_1_e.c b/drivers/video/msm/mipi_truly_tft540960_1_e.c new file mode 100644 index 0000000000000000000000000000000000000000..e465d469a3bb4cba9d239990ccd932178ab964d6 --- /dev/null +++ b/drivers/video/msm/mipi_truly_tft540960_1_e.c @@ -0,0 +1,817 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_truly_tft540960_1_e.h" + +static struct msm_panel_common_pdata *mipi_truly_pdata; +static struct dsi_buf truly_tx_buf; +static struct dsi_buf truly_rx_buf; + +#define TRULY_CMD_DELAY 0 +#define MIPI_SETTING_DELAY 10 +#define TRULY_SLEEP_OFF_DELAY 150 +#define TRULY_DISPLAY_ON_DELAY 150 + +/* common setting */ +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}; +static char write_ram[2] = {0x2c, 0x00}; /* write ram */ + +static struct dsi_cmd_desc truly_display_off_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 150, sizeof(display_off), display_off}, + {DTYPE_DCS_WRITE, 1, 0, 0, 150, sizeof(enter_sleep), enter_sleep} +}; + + +/* TFT540960_1_E CMD mode */ +static char cmd0[5] = { + 0xFF, 0xAA, 0x55, 0x25, + 0x01, +}; + +static char cmd2[5] = { + 0xF3, 0x02, 0x03, 0x07, + 0x45, +}; + +static char cmd3[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x00, +}; + +static char cmd4[2] = { + 0xB1, 0xeC, +}; + +/* add 0X BD command */ +static char cmd26_2[6] = { + 0xBD, 0x01, 0x48, 0x10, 0x38, 0x01 /* 59 HZ */ +}; + +static char cmd5[5] = { + 0xB8, 0x01, 0x02, 0x02, + 0x02, +}; + +static char cmd6[4] = { + 0xBC, 0x05, 0x05, 0x05, +}; + +static char cmd7[2] = { + 0x4C, 0x11, +}; + +static char cmd8[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x01, +}; + +static char cmd9[4] = { + 0xB0, 0x05, 0x05, 0x05, +}; + +static char cmd10[4] = { + 0xB6, 0x44, 0x44, 0x44, +}; +static char cmd11[4] = { + 0xB1, 0x05, 0x05, 0x05, +}; + +static char cmd12[4] = { + 0xB7, 0x34, 0x34, 0x34, +}; + +static char cmd13[4] = { + 0xB3, 0x10, 0x10, 0x10, +}; + +static char cmd14[4] = { + 0xB9, 0x34, 0x34, 0x34, +}; + +static char cmd15[4] = { + 0xB4, 0x0A, 0x0A, 0x0A, +}; + +static char cmd16[4] = { + 0xBA, 0x14, 0x14, 0x14, +}; +static char cmd17[4] = { + 0xBC, 0x00, 0xA0, 0x00, +}; + +static char cmd18[4] = { + 0xBD, 0x00, 0xA0, 0x00, +}; + +static char cmd19[2] = { + 0xBE, 0x45, +}; + +static char cmd20[17] = { + 0xD1, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char cmd21[17] = { + 0xD2, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char cmd22[17] = { + 0xD3, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char cmd23[5] = { + 0xD4, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char cmd24[17] = { + 0xD5, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; +static char cmd25[17] = { + 0xD6, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char cmd26[17] = { + 0xD7, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; +static char cmd27[5] = { + 0xD8, 0x03, 0xB0, 0x03, + 0xF4, +}; + + +static char cmd28[17] = { + 0xD9, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char cmd29[17] = { + 0xDD, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; +static char cmd30[17] = { + 0xDE, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char cmd31[5] = { + 0xDF, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char cmd32[17] = { + 0xE0, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char cmd33[17] = { + 0xE1, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char cmd34[17] = { + 0xE2, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char cmd35[5] = { + 0xE3, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char cmd36[17] = { + 0xE4, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; +static char cmd37[17] = { + 0xE5, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char cmd38[17] = { + 0xE6, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char cmd39[5] = { + 0xE7, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char cmd40[17] = { + 0xE8, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char cmd41[17] = { + 0xE9, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char cmd42[17] = { + 0xEA, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char cmd43[5] = { + 0xEB, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char cmd44[2] = { + 0x3A, 0x07, +}; + +static char cmd45[2] = { + 0x35, 0x00, +}; + + +static struct dsi_cmd_desc truly_cmd_display_on_cmds[] = { + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd0), cmd0}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd2), cmd2}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd3), cmd3}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd4), cmd4}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd26_2), cmd26_2}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd5), cmd5}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd6), cmd6}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd7), cmd7}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd8), cmd8}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd9), cmd9}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd10), cmd10}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd11), cmd11}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd12), cmd12}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd13), cmd13}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd14), cmd14}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd15), cmd15}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd16), cmd16}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd17), cmd17}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd18), cmd18}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd19), cmd19}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd20), cmd20}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd21), cmd21}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd22), cmd22}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd23), cmd23}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd24), cmd24}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd25), cmd25}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd26), cmd26}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd27), cmd27}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd28), cmd28}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd29), cmd29}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd30), cmd30}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd31), cmd31}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd32), cmd32}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd33), cmd33}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd34), cmd34}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd35), cmd35}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd36), cmd36}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd37), cmd37}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd38), cmd38}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd39), cmd39}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd40), cmd40}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd41), cmd41}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd42), cmd42}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd43), cmd43}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd44), cmd44}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(cmd45), cmd45}, + {DTYPE_DCS_WRITE, 1, 0, 0, TRULY_SLEEP_OFF_DELAY, sizeof(exit_sleep), + exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, TRULY_CMD_DELAY, sizeof(display_on), + display_on}, + {DTYPE_DCS_WRITE, 1, 0, 0, TRULY_CMD_DELAY, sizeof(write_ram), + write_ram}, + +}; + +/* TFT540960_1_E VIDEO mode */ +static char video0[5] = { + 0xFF, 0xAA, 0x55, 0x25, + 0x01, +}; + +static char video2[5] = { + 0xF3, 0x02, 0x03, 0x07, + 0x15, +}; + +static char video3[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x00, +}; + +static char video4[2] = { + 0xB1, 0xFC, +}; + +static char video5[5] = { + 0xB8, 0x01, 0x02, 0x02, + 0x02, +}; + +static char video6[4] = { + 0xBC, 0x05, 0x05, 0x05, +}; + +static char video7[2] = { + 0x4C, 0x11, +}; + +static char video8[6] = { + 0xF0, 0x55, 0xAA, 0x52, + 0x08, 0x01, +}; + +static char video9[4] = { + 0xB0, 0x05, 0x05, 0x05, +}; + +static char video10[4] = { + 0xB6, 0x44, 0x44, 0x44, +}; + +static char video11[4] = { + 0xB1, 0x05, 0x05, 0x05, +}; + +static char video12[4] = { + 0xB7, 0x34, 0x34, 0x34, +}; + +static char video13[4] = { + 0xB3, 0x10, 0x10, 0x10, +}; + +static char video14[4] = { + 0xB9, 0x34, 0x34, 0x34, +}; + +static char video15[4] = { + 0xB4, 0x0A, 0x0A, 0x0A, +}; + +static char video16[4] = { + 0xBA, 0x14, 0x14, 0x14, +}; + +static char video17[4] = { + 0xBC, 0x00, 0xA0, 0x00, +}; + +static char video18[4] = { + 0xBD, 0x00, 0xA0, 0x00, +}; + +static char video19[2] = { + 0xBE, 0x45, +}; + +static char video20[17] = { + 0xD1, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char video21[17] = { + 0xD2, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char video22[17] = { + 0xD3, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char video23[5] = { + 0xD4, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char video24[17] = { + 0xD5, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char video25[17] = { + 0xD6, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char video26[17] = { + 0xD7, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char video27[5] = { + 0xD8, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char video28[17] = { + 0xD9, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char video29[17] = { + 0xDD, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char video30[17] = { + 0xDE, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char video31[5] = { + 0xDF, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char video32[17] = { + 0xE0, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char video33[17] = { + 0xE1, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char video34[17] = { + 0xE2, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char video35[5] = { + 0xE3, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char video36[17] = { + 0xE4, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char video37[17] = { + 0xE5, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char video38[17] = { + 0xE6, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char video39[5] = { + 0xE7, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char video40[17] = { + 0xE8, 0x00, 0x32, 0x00, + 0x41, 0x00, 0x54, 0x00, + 0x67, 0x00, 0x7A, 0x00, + 0x98, 0x00, 0xB0, 0x00, + 0xDB, +}; + +static char video41[17] = { + 0xE9, 0x01, 0x01, 0x01, + 0x3F, 0x01, 0x70, 0x01, + 0xB4, 0x01, 0xEC, 0x01, + 0xED, 0x02, 0x1E, 0x02, + 0x51, +}; + +static char video42[17] = { + 0xEA, 0x02, 0x6C, 0x02, + 0x8D, 0x02, 0xA5, 0x02, + 0xC9, 0x02, 0xEA, 0x03, + 0x19, 0x03, 0x45, 0x03, + 0x7A, +}; + +static char video43[5] = { + 0xEB, 0x03, 0xB0, 0x03, + 0xF4, +}; + +static char video44[2] = { + 0x3A, 0x07, +}; + +static char video45[2] = { + 0x35, 0x00, +}; + +static struct dsi_cmd_desc truly_video_display_on_cmds[] = { + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video0), video0}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video2), video2}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video3), video3}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video4), video4}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video5), video5}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video6), video6}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video7), video7}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video8), video8}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video9), video9}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video10), video10}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video11), video11}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video12), video12}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video13), video13}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video14), video14}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video15), video15}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video16), video16}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video17), video17}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video18), video18}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video19), video19}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video20), video20}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video21), video21}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video22), video22}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video23), video23}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video24), video24}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video25), video25}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video26), video26}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video27), video27}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video28), video28}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video29), video29}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video30), video30}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video31), video31}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video32), video32}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video33), video33}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video34), video34}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video35), video35}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video36), video36}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video37), video37}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video38), video38}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video39), video39}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video40), video40}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video41), video41}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video42), video42}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video43), video43}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video44), video44}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(video45), video45}, + + {DTYPE_DCS_WRITE, 1, 0, 0, 150, sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 50, sizeof(display_on), display_on}, +}; + +static int mipi_truly_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi = &mfd->panel_info.mipi; + pr_info("%s: mode = %d\n", __func__, mipi->mode); + msleep(120); + + if (mipi->mode == DSI_VIDEO_MODE) { + mipi_dsi_cmds_tx(mfd, &truly_tx_buf, + truly_video_display_on_cmds, + ARRAY_SIZE(truly_video_display_on_cmds)); + } else if (mipi->mode == DSI_CMD_MODE) { + mipi_dsi_cmds_tx(mfd, &truly_tx_buf, + truly_cmd_display_on_cmds, + ARRAY_SIZE(truly_cmd_display_on_cmds)); + } + + return 0; +} + +static int mipi_truly_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, &truly_tx_buf, truly_display_off_cmds, + ARRAY_SIZE(truly_display_off_cmds)); + + return 0; +} + +static int __devinit mipi_truly_lcd_probe(struct platform_device *pdev) +{ + int rc = 0; + + if (pdev->id == 0) { + mipi_truly_pdata = pdev->dev.platform_data; + if (mipi_truly_pdata->bl_lock) + spin_lock_init(&mipi_truly_pdata->bl_spinlock); + return rc; + } + + msm_fb_add_device(pdev); + + return rc; +} + +static struct platform_driver this_driver = { + .probe = mipi_truly_lcd_probe, + .driver = { + .name = "mipi_truly_tft540960_1_e", + }, +}; + +static void mipi_truly_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + unsigned long flags; + bl_level = mfd->bl_level; + + if (mipi_truly_pdata->bl_lock) { + spin_lock_irqsave(&mipi_truly_pdata->bl_spinlock, flags); + mipi_truly_pdata->pmic_backlight(bl_level); + spin_unlock_irqrestore(&mipi_truly_pdata->bl_spinlock, flags); + } else + mipi_truly_pdata->pmic_backlight(bl_level); +} + +static struct msm_fb_panel_data truly_panel_data = { + .on = mipi_truly_lcd_on, + .off = mipi_truly_lcd_off, + .set_backlight = mipi_truly_set_backlight, +}; + +static int ch_used[3]; + +static int mipi_truly_tft540960_1_e_lcd_init(void) +{ + mipi_dsi_buf_alloc(&truly_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&truly_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} +int mipi_truly_tft540960_1_e_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; + + ret = mipi_truly_tft540960_1_e_lcd_init(); + if (ret) { + pr_err("%s: platform_device_register failed!\n", __func__); + return ret; + } + + pdev = platform_device_alloc("mipi_truly_tft540960_1_e", + (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + truly_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &truly_panel_data, + sizeof(truly_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; +} diff --git a/drivers/video/msm/mipi_truly_tft540960_1_e.h b/drivers/video/msm/mipi_truly_tft540960_1_e.h new file mode 100644 index 0000000000000000000000000000000000000000..8cbfb800911b03568a9105f07aa54db8fcf2b41f --- /dev/null +++ b/drivers/video/msm/mipi_truly_tft540960_1_e.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_TRULY_H +#define MIPI_TRULY_H + +int mipi_truly_tft540960_1_e_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_TRULY_H */ diff --git a/drivers/video/msm/mipi_truly_tft540960_1_e_cmd_qhd_pt.c b/drivers/video/msm/mipi_truly_tft540960_1_e_cmd_qhd_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..de98177fcda1b13c08f42d60cd822d37bccf757f --- /dev/null +++ b/drivers/video/msm/mipi_truly_tft540960_1_e_cmd_qhd_pt.c @@ -0,0 +1,98 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_truly_tft540960_1_e.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_cmd_mode_phy_db = { + /* 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}, +}; + +static int mipi_cmd_truly_qhd_pt_init(void) +{ + int ret; + if (msm_fb_detect_client("mipi_cmd_truly_qhd")) + return 0; + + 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 = 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.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 31; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.clk_rate = 499000000; + + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.hw_vsync_mode = TRUE; + pinfo.lcd.refx100 = 6100; /* 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; + 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_TE; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.te_sel = 1; /* 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 = 0x0; + pinfo.mipi.dlane_swap = 0x01; + + ret = mipi_truly_tft540960_1_e_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_truly_qhd_pt_init); diff --git a/drivers/video/msm/mipi_truly_tft540960_1_e_video_qhd_pt.c b/drivers/video/msm/mipi_truly_tft540960_1_e_video_qhd_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..ea2ff4718e5f000d34c641d6578d8972233ae58b --- /dev/null +++ b/drivers/video/msm/mipi_truly_tft540960_1_e_video_qhd_pt.c @@ -0,0 +1,107 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_truly_tft540960_1_e.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* 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}, +}; + +static int mipi_video_truly_qhd_pt_init(void) +{ + int ret; + if (msm_fb_detect_client("mipi_video_truly_qhd")) + return 0; + + 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 = 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.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + /* number of dot_clk cycles HSYNC active edge + is delayed from VSYNC active edge */ + pinfo.lcdc.hsync_skew = 0; + pinfo.clk_rate = 699000000; + pinfo.lcd.refx100 = 6000; /* FB driver calc FPS based on this value */ + pinfo.bl_max = 31; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + /* send HSA and HE following VS/VE packet */ + pinfo.mipi.pulse_mode_hsa_he = TRUE; + pinfo.mipi.hfp_power_stop = TRUE; /* LP-11 during the HFP period */ + pinfo.mipi.hbp_power_stop = TRUE; /* LP-11 during the HBP period */ + pinfo.mipi.hsa_power_stop = TRUE; /* LP-11 during the HSA period */ + /* LP-11 or let Command Mode Engine send packets in + HS or LP mode for the BLLP of the last line of a frame */ + pinfo.mipi.eof_bllp_power_stop = TRUE; + /* LP-11 or let Command Mode Engine send packets in + HS or LP mode for packets sent during BLLP period */ + pinfo.mipi.bllp_power_stop = TRUE; + + 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; /* 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; + /* append EOT at the end of data burst */ + pinfo.mipi.tx_eot_append = 0x01; + + ret = mipi_truly_tft540960_1_e_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_truly_qhd_pt_init); diff --git a/drivers/video/msm/mipi_truly_video_wvga_pt.c b/drivers/video/msm/mipi_truly_video_wvga_pt.c new file mode 100644 index 0000000000000000000000000000000000000000..03ef32bb5105d7c4e01821e32efeff2085d053bf --- /dev/null +++ b/drivers/video/msm/mipi_truly_video_wvga_pt.c @@ -0,0 +1,108 @@ +/* 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_truly.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* 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}, +}; + +static int __init mipi_video_truly_wvga_pt_init(void) +{ + int ret; + + if (msm_fb_detect_client("mipi_video_truly_wvga")) + return 0; + + pinfo.xres = 480; + pinfo.yres = 800; + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + 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.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + /* number of dot_clk cycles HSYNC active edge + is delayed from VSYNC active edge */ + pinfo.lcdc.hsync_skew = 0; + pinfo.clk_rate = 499000000; + 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; /* send HSA and HE following + VS/VE packet */ + pinfo.mipi.hfp_power_stop = TRUE; /* LP-11 during the HFP period */ + pinfo.mipi.hbp_power_stop = TRUE; /* LP-11 during the HBP period */ + pinfo.mipi.hsa_power_stop = TRUE; /* LP-11 during the HSA period */ + /* LP-11 or let Command Mode Engine send packets in + HS or LP mode for the BLLP of the last line of a frame */ + pinfo.mipi.eof_bllp_power_stop = TRUE; + /* LP-11 or let Command Mode Engine send packets in + HS or LP mode for packets sent during BLLP period */ + pinfo.mipi.bllp_power_stop = TRUE; + + 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; /* 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; /* FIXME */ + + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + pinfo.mipi.dlane_swap = 0x01; + pinfo.mipi.tx_eot_append = 0x01; /* append EOT at the end + of data burst */ + + ret = mipi_truly_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_truly_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 0000000000000000000000000000000000000000..f4f83750f54f9163d5b64a806a56fe3f4fda1be9 --- /dev/null +++ b/drivers/video/msm/msm_dss_io_7x27a.c @@ -0,0 +1,470 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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; + +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; +static struct clk *ebi1_dsi_clk; +int mipi_dsi_clk_on; + +int mipi_dsi_clk_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + dsi_esc_clk = clk_get(dev, "esc_clk"); + if (IS_ERR_OR_NULL(dsi_esc_clk)) { + printk(KERN_ERR "can't find dsi_esc_clk\n"); + dsi_esc_clk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_byte_div_clk = clk_get(dev, "byte_clk"); + if (IS_ERR_OR_NULL(dsi_byte_div_clk)) { + pr_err("can't find dsi_byte_div_clk\n"); + dsi_byte_div_clk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_pixel_clk = clk_get(dev, "pixel_clk"); + if (IS_ERR_OR_NULL(dsi_pixel_clk)) { + pr_err("can't find dsi_pixel_clk\n"); + dsi_pixel_clk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_clk = clk_get(dev, "core_clk"); + if (IS_ERR_OR_NULL(dsi_clk)) { + pr_err("can't find dsi_clk\n"); + dsi_clk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_ref_clk = clk_get(dev, "ref_clk"); + if (IS_ERR_OR_NULL(dsi_ref_clk)) { + pr_err("can't find dsi_ref_clk\n"); + dsi_ref_clk = NULL; + goto mipi_dsi_clk_err; + } + + mdp_dsi_pclk = clk_get(dev, "mdp_clk"); + if (IS_ERR_OR_NULL(mdp_dsi_pclk)) { + pr_err("can't find mdp_dsi_pclk\n"); + mdp_dsi_pclk = NULL; + goto mipi_dsi_clk_err; + } + + ahb_m_clk = clk_get(dev, "master_iface_clk"); + if (IS_ERR_OR_NULL(ahb_m_clk)) { + pr_err("can't find ahb_m_clk\n"); + ahb_m_clk = NULL; + goto mipi_dsi_clk_err; + } + + ahb_s_clk = clk_get(dev, "slave_iface_clk"); + if (IS_ERR_OR_NULL(ahb_s_clk)) { + pr_err("can't find ahb_s_clk\n"); + ahb_s_clk = NULL; + goto mipi_dsi_clk_err; + } + + ebi1_dsi_clk = clk_get(dev, "mem_clk"); + if (IS_ERR_OR_NULL(ebi1_dsi_clk)) { + pr_err("can't find ebi1_dsi_clk\n"); + ebi1_dsi_clk = NULL; + goto mipi_dsi_clk_err; + } + + return 0; + +mipi_dsi_clk_err: + mipi_dsi_clk_deinit(NULL); + return -EPERM; +} + +void mipi_dsi_clk_deinit(struct device *dev) +{ + if (mdp_dsi_pclk) + clk_put(mdp_dsi_pclk); + if (ahb_m_clk) + clk_put(ahb_m_clk); + if (ahb_s_clk) + clk_put(ahb_s_clk); + if (dsi_ref_clk) + clk_put(dsi_ref_clk); + if (dsi_byte_div_clk) + clk_put(dsi_byte_div_clk); + if (dsi_esc_clk) + clk_put(dsi_esc_clk); + if (ebi1_dsi_clk) + clk_put(ebi1_dsi_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 */ + wmb(); + usleep(10); + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ + wmb(); + usleep(10); + 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 + 0x0200, pd->pll[0]); + wmb(); +} + +void cont_splash_clk_ctrl(int enable) +{ +} + +void mipi_dsi_prepare_clocks(void) +{ + clk_prepare(dsi_ref_clk); + clk_prepare(ahb_m_clk); + clk_prepare(ahb_s_clk); + clk_prepare(ebi1_dsi_clk); + clk_prepare(mdp_dsi_pclk); + clk_prepare(dsi_byte_div_clk); + clk_prepare(dsi_esc_clk); + clk_prepare(dsi_clk); + clk_prepare(dsi_pixel_clk); +} + +void mipi_dsi_unprepare_clocks(void) +{ + clk_unprepare(dsi_esc_clk); + clk_unprepare(dsi_byte_div_clk); + clk_unprepare(mdp_dsi_pclk); + clk_unprepare(ebi1_dsi_clk); + clk_unprepare(ahb_m_clk); + clk_unprepare(ahb_s_clk); + clk_unprepare(dsi_ref_clk); + clk_unprepare(dsi_clk); + clk_unprepare(dsi_pixel_clk); +} + +void mipi_dsi_ahb_ctrl(u32 enable) +{ + static int ahb_ctrl_done; + if (enable) { + if (ahb_ctrl_done) { + pr_info("%s: ahb clks already ON\n", __func__); + return; + } + clk_enable(dsi_ref_clk); + clk_enable(ahb_m_clk); + clk_enable(ahb_s_clk); + ahb_ctrl_done = 1; + } else { + if (ahb_ctrl_done == 0) { + pr_info("%s: ahb clks already OFF\n", __func__); + return; + } + clk_disable(ahb_m_clk); + clk_disable(ahb_s_clk); + clk_disable(dsi_ref_clk); + ahb_ctrl_done = 0; + } +} + +void mipi_dsi_clk_enable(void) +{ + unsigned data = 0; + uint32 pll_ctrl; + + if (mipi_dsi_clk_on) { + pr_info("%s: mipi_dsi_clks already ON\n", __func__); + return; + } + if (clk_set_rate(ebi1_dsi_clk, 65000000)) /* 65 MHz */ + pr_err("%s: ebi1_dsi_clk set rate failed\n", __func__); + clk_enable(ebi1_dsi_clk); + + pll_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0200); + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, pll_ctrl | 0x01); + mb(); + + clk_set_rate(dsi_byte_div_clk, data); + clk_set_rate(dsi_esc_clk, data); + clk_enable(mdp_dsi_pclk); + 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_clk_on = 1; +} + +void mipi_dsi_clk_disable(void) +{ + if (mipi_dsi_clk_on == 0) { + pr_info("%s: mipi_dsi_clks already OFF\n", __func__); + return; + } + 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); + /* DSIPHY_PLL_CTRL_0, disable dsi pll */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x40); + if (clk_set_rate(ebi1_dsi_clk, 0)) + pr_err("%s: ebi1_dsi_clk set rate failed\n", __func__); + clk_disable(ebi1_dsi_clk); + mipi_dsi_clk_on = 0; +} + +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); + + /* disable 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 0000000000000000000000000000000000000000..b17c1955ffcaef310f8045d8120d00cf6729aca7 --- /dev/null +++ b/drivers/video/msm/msm_dss_io_8960.c @@ -0,0 +1,830 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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" +#include "mdp.h" +#include "mdp4.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; + +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; +int mipi_dsi_clk_on; + +int mipi_dsi_clk_init(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct device *dev = &pdev->dev; + + mfd = platform_get_drvdata(pdev); + + amp_pclk = clk_get(dev, "arb_clk"); + if (IS_ERR_OR_NULL(amp_pclk)) { + pr_err("can't find amp_pclk\n"); + amp_pclk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_m_pclk = clk_get(dev, "master_iface_clk"); + if (IS_ERR_OR_NULL(dsi_m_pclk)) { + pr_err("can't find dsi_m_pclk\n"); + dsi_m_pclk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_s_pclk = clk_get(dev, "slave_iface_clk"); + if (IS_ERR_OR_NULL(dsi_s_pclk)) { + pr_err("can't find dsi_s_pclk\n"); + dsi_s_pclk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_byte_div_clk = clk_get(dev, "byte_clk"); + if (IS_ERR(dsi_byte_div_clk)) { + pr_err("can't find dsi_byte_div_clk\n"); + dsi_byte_div_clk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_esc_clk = clk_get(dev, "esc_clk"); + if (IS_ERR(dsi_esc_clk)) { + printk(KERN_ERR "can't find dsi_esc_clk\n"); + dsi_esc_clk = NULL; + goto mipi_dsi_clk_err; + } + + return 0; + +mipi_dsi_clk_err: + mipi_dsi_clk_deinit(dev); + return -EPERM; +} + +void mipi_dsi_clk_deinit(struct device *dev) +{ + if (amp_pclk) + clk_put(amp_pclk); + if (amp_pclk) + clk_put(dsi_m_pclk); + if (dsi_s_pclk) + clk_put(dsi_s_pclk); + if (dsi_byte_div_clk) + clk_put(dsi_byte_div_clk); + if (dsi_esc_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)); +} + +void mipi_dsi_lane_cfg(void) +{ + int i, ln_offset; + + ln_offset = 0x300; + for (i = 0; i < 4; i++) { + /* DSI1_DSIPHY_LN_CFG0 */ + MIPI_OUTP(MIPI_DSI_BASE + ln_offset, 0x80); + /* DSI1_DSIPHY_LN_CFG1 */ + MIPI_OUTP(MIPI_DSI_BASE + ln_offset + 0x04, 0x45); + /* DSI1_DSIPHY_LN_CFG2 */ + MIPI_OUTP(MIPI_DSI_BASE + ln_offset + 0x08, 0x0); + /* DSI1_DSIPHY_LN_TEST_DATAPATH */ + MIPI_OUTP(MIPI_DSI_BASE + ln_offset + 0x0c, 0x0); + /* DSI1_DSIPHY_LN_TEST_STR0 */ + MIPI_OUTP(MIPI_DSI_BASE + ln_offset + 0x14, 0x1); + /* DSI1_DSIPHY_LN_TEST_STR1 */ + MIPI_OUTP(MIPI_DSI_BASE + ln_offset + 0x18, 0x66); + ln_offset += 0x40; + } + + MIPI_OUTP(MIPI_DSI_BASE + 0x0400, 0x40); /* DSI1_DSIPHY_LNCK_CFG0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0404, 0x67); /* DSI1_DSIPHY_LNCK_CFG1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0408, 0x0); /* DSI1_DSIPHY_LNCK_CFG2 */ + /* DSI1_DSIPHY_LNCK_TEST_DATAPATH */ + MIPI_OUTP(MIPI_DSI_BASE + 0x040c, 0x0); + MIPI_OUTP(MIPI_DSI_BASE + 0x0414, 0x1); /* DSI1_DSIPHY_LNCK_TEST_STR0 */ + /* DSI1_DSIPHY_LNCK_TEST_STR1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0418, 0x88); +} + +void mipi_dsi_bist_ctrl(void) +{ + MIPI_OUTP(MIPI_DSI_BASE + 0x049c, 0x0f); /* DSI1_DSIPHY_BIST_CTRL4 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0490, 0x03); /* DSI1_DSIPHY_BIST_CTRL1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x048c, 0x03); /* DSI1_DSIPHY_BIST_CTRL0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x049c, 0x0); /* DSI1_DSIPHY_BIST_CTRL4 */ +} + +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, 0x03); + + /* 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 < 600) { + 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 */ + wmb(); + usleep(1); + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ + wmb(); + usleep(1); + 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 */ + + MIPI_OUTP(MIPI_DSI_BASE + 0x4b0, 0x04);/* DSIPHY_LDO_CNTRL */ + + 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(); + mipi_dsi_lane_cfg(); /* lane cfgs */ + mipi_dsi_bist_ctrl(); /* bist ctrl */ + + 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(); + + off = 0x0440; /* phy timing 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 cont_splash_clk_ctrl(int enable) +{ + static int cont_splash_clks_enabled; + if (enable && !cont_splash_clks_enabled) { + clk_prepare_enable(dsi_byte_div_clk); + clk_prepare_enable(dsi_esc_clk); + cont_splash_clks_enabled = 1; + } else if (!enable && cont_splash_clks_enabled) { + clk_disable_unprepare(dsi_byte_div_clk); + clk_disable_unprepare(dsi_esc_clk); + cont_splash_clks_enabled = 0; + } +} + +void mipi_dsi_prepare_clocks(void) +{ + clk_prepare(amp_pclk); + clk_prepare(dsi_m_pclk); + clk_prepare(dsi_s_pclk); + clk_prepare(dsi_byte_div_clk); + clk_prepare(dsi_esc_clk); +} + +void mipi_dsi_unprepare_clocks(void) +{ + clk_unprepare(dsi_esc_clk); + clk_unprepare(dsi_byte_div_clk); + clk_unprepare(dsi_m_pclk); + clk_unprepare(dsi_s_pclk); + clk_unprepare(amp_pclk); +} + +void mipi_dsi_ahb_ctrl(u32 enable) +{ + static int ahb_ctrl_done; + if (enable) { + if (ahb_ctrl_done) { + pr_info("%s: ahb clks already ON\n", __func__); + return; + } + clk_enable(amp_pclk); /* clock for AHB-master to AXI */ + clk_enable(dsi_m_pclk); + clk_enable(dsi_s_pclk); + mipi_dsi_ahb_en(); + mipi_dsi_sfpb_cfg(); + ahb_ctrl_done = 1; + } else { + if (ahb_ctrl_done == 0) { + pr_info("%s: ahb clks already OFF\n", __func__); + return; + } + clk_disable(dsi_m_pclk); + clk_disable(dsi_s_pclk); + clk_disable(amp_pclk); /* clock for AHB-master to AXI */ + ahb_ctrl_done = 0; + } +} + +void mipi_dsi_clk_enable(void) +{ + u32 pll_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0200); + if (mipi_dsi_clk_on) { + pr_info("%s: mipi_dsi_clks already ON\n", __func__); + return; + } + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, pll_ctrl | 0x01); + mipi_dsi_phy_rdy_poll(); + + 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__); + mipi_dsi_pclk_ctrl(&dsi_pclk, 1); + mipi_dsi_clk_ctrl(&dsicore_clk, 1); + clk_enable(dsi_byte_div_clk); + clk_enable(dsi_esc_clk); + mipi_dsi_clk_on = 1; + mdp4_stat.dsi_clk_on++; +} + +void mipi_dsi_clk_disable(void) +{ + if (mipi_dsi_clk_on == 0) { + pr_info("%s: mipi_dsi_clks already OFF\n", __func__); + return; + } + clk_disable(dsi_esc_clk); + clk_disable(dsi_byte_div_clk); + mipi_dsi_pclk_ctrl(&dsi_pclk, 0); + mipi_dsi_clk_ctrl(&dsicore_clk, 0); + /* DSIPHY_PLL_CTRL_0, disable dsi pll */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x0); + mipi_dsi_clk_on = 0; + mdp4_stat.dsi_clk_off++; +} + +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); + + /* disable 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_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); + udelay(20); + clk_reset(hdmi_msm_state->hdmi_app_clk, CLK_RESET_DEASSERT); +} + +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); + + offset = HDMI_PHY_REG_4; + while (offset <= HDMI_PHY_REG_11) { + HDMI_OUTP(offset, 0x0); + offset += 0x4; + } + + HDMI_OUTP(HDMI_PHY_REG_3, 0x20); +} + +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 0000000000000000000000000000000000000000..a1897e3704ba2a2d80f852cbf6046111ead2cc32 --- /dev/null +++ b/drivers/video/msm/msm_dss_io_8x60.c @@ -0,0 +1,690 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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" +#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; + +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; +int mipi_dsi_clk_on; + +int mipi_dsi_clk_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + amp_pclk = clk_get(dev, "arb_clk"); + if (IS_ERR_OR_NULL(amp_pclk)) { + pr_err("can't find amp_pclk\n"); + amp_pclk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_m_pclk = clk_get(dev, "master_iface_clk"); + if (IS_ERR_OR_NULL(dsi_m_pclk)) { + pr_err("can't find dsi_m_pclk\n"); + dsi_m_pclk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_s_pclk = clk_get(dev, "slave_iface_clk"); + if (IS_ERR_OR_NULL(dsi_s_pclk)) { + pr_err("can't find dsi_s_pclk\n"); + dsi_s_pclk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_byte_div_clk = clk_get(dev, "byte_clk"); + if (IS_ERR_OR_NULL(dsi_byte_div_clk)) { + pr_err("can't find dsi_byte_div_clk\n"); + dsi_byte_div_clk = NULL; + goto mipi_dsi_clk_err; + } + + dsi_esc_clk = clk_get(dev, "esc_clk"); + if (IS_ERR_OR_NULL(dsi_esc_clk)) { + printk(KERN_ERR "can't find dsi_esc_clk\n"); + dsi_esc_clk = NULL; + goto mipi_dsi_clk_err; + } + + return 0; + +mipi_dsi_clk_err: + mipi_dsi_clk_deinit(NULL); + return -EPERM; +} + +void mipi_dsi_clk_deinit(struct device *dev) +{ + if (amp_pclk) + clk_put(amp_pclk); + if (dsi_m_pclk) + clk_put(dsi_m_pclk); + if (dsi_s_pclk) + clk_put(dsi_s_pclk); + if (dsi_byte_div_clk) + clk_put(dsi_byte_div_clk); + if (dsi_esc_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 */ + wmb(); + usleep(1); + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ + wmb(); + usleep(1); + 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(); +} + +void cont_splash_clk_ctrl(int enable) +{ +} + +void mipi_dsi_prepare_clocks(void) +{ + clk_prepare(amp_pclk); + clk_prepare(dsi_m_pclk); + clk_prepare(dsi_s_pclk); + clk_prepare(dsi_byte_div_clk); + clk_prepare(dsi_esc_clk); +} + +void mipi_dsi_unprepare_clocks(void) +{ + clk_unprepare(dsi_esc_clk); + clk_unprepare(dsi_byte_div_clk); + clk_unprepare(dsi_m_pclk); + clk_unprepare(dsi_s_pclk); + clk_unprepare(amp_pclk); +} + +void mipi_dsi_ahb_ctrl(u32 enable) +{ + static int ahb_ctrl_done; + if (enable) { + if (ahb_ctrl_done) { + pr_info("%s: ahb clks already ON\n", __func__); + return; + } + clk_enable(amp_pclk); /* clock for AHB-master to AXI */ + clk_enable(dsi_m_pclk); + clk_enable(dsi_s_pclk); + mipi_dsi_ahb_en(); + mipi_dsi_sfpb_cfg(); + ahb_ctrl_done = 1; + } else { + if (ahb_ctrl_done == 0) { + pr_info("%s: ahb clks already OFF\n", __func__); + return; + } + clk_disable(dsi_m_pclk); + clk_disable(dsi_s_pclk); + clk_disable(amp_pclk); /* clock for AHB-master to AXI */ + ahb_ctrl_done = 0; + } +} + +void mipi_dsi_clk_enable(void) +{ + u32 pll_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0200); + if (mipi_dsi_clk_on) { + pr_info("%s: mipi_dsi_clks already ON\n", __func__); + return; + } + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, pll_ctrl | 0x01); + mb(); + + if (clk_set_rate(dsi_byte_div_clk, 1) < 0) /* divided by 1 */ + pr_err("%s: clk_set_rate failed\n", __func__); + mipi_dsi_pclk_ctrl(&dsi_pclk, 1); + mipi_dsi_clk_ctrl(&dsicore_clk, 1); + clk_enable(dsi_byte_div_clk); + clk_enable(dsi_esc_clk); + mipi_dsi_clk_on = 1; +} + +void mipi_dsi_clk_disable(void) +{ + if (mipi_dsi_clk_on == 0) { + pr_info("%s: mipi_dsi_clks already OFF\n", __func__); + return; + } + clk_disable(dsi_esc_clk); + clk_disable(dsi_byte_div_clk); + + mipi_dsi_pclk_ctrl(&dsi_pclk, 0); + mipi_dsi_clk_ctrl(&dsicore_clk, 0); + /* DSIPHY_PLL_CTRL_0, disable dsi pll */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x40); + mipi_dsi_clk_on = 0; +} + +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); + + /* disable 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_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); +} + +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) +{ + /* Assert RESET PHY from controller */ + HDMI_OUTP_ND(0x02D4, 0x4); + udelay(10); + /* De-assert RESET PHY from controller */ + HDMI_OUTP_ND(0x02D4, 0x0); + /* Turn off Driver */ + HDMI_OUTP_ND(0x0308, 0x1F); + udelay(10); + /* 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 c6e3b4fcdd683b52eaebc7da3afd49bd24497590..3ed305b0aa83c3291871de60afe1a3daa67d0181 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-2012, 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,626 +15,3757 @@ * 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 - -#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_TRIPLE_BUFFER +#define MSM_FB_NUM 3 +#endif + +static unsigned char *fbram; +static unsigned char *fbram_phys; +static int fbram_size; +static boolean bf_supported; + +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) +static struct ion_client *iclient; + +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; +unsigned char hdmi_prim_display; + +int msm_fb_detect_client(const char *name) +{ + int ret = 0; + u32 len; +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 id; +#endif + if (!msm_fb_pdata) + return -EPERM; + + len = strnlen(name, PANEL_NAME_MAX_LEN); + if (strnlen(msm_fb_pdata->prim_panel_name, PANEL_NAME_MAX_LEN)) { + pr_err("\n name = %s, prim_display = %s", + name, msm_fb_pdata->prim_panel_name); + if (!strncmp((char *)msm_fb_pdata->prim_panel_name, + name, len)) { + if (!strncmp((char *)msm_fb_pdata->prim_panel_name, + "hdmi_msm", len)) + hdmi_prim_display = 1; + return 0; + } else { + ret = -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 (strnlen(msm_fb_pdata->ext_panel_name, PANEL_NAME_MAX_LEN)) { + pr_err("\n name = %s, ext_display = %s", + name, msm_fb_pdata->ext_panel_name); + if (!strncmp((char *)msm_fb_pdata->ext_panel_name, name, len)) + return 0; + else + ret = -EPERM; } - 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; + + if (ret) + return ret; + + ret = -EPERM; + 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 } - 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 LVDS_PANEL: + ret = snprintf(buf, PAGE_SIZE, "lvds panel\n"); + break; + case DTV_PANEL: + ret = snprintf(buf, PAGE_SIZE, "dtv panel\n"); + break; + case MIPI_VIDEO_PANEL: + ret = snprintf(buf, PAGE_SIZE, "mipi dsi video panel\n"); + break; + case MIPI_CMD_PANEL: + ret = snprintf(buf, PAGE_SIZE, "mipi dsi cmd panel\n"); + break; + case WRITEBACK_PANEL: + ret = snprintf(buf, PAGE_SIZE, "writeback 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 = __va(fbram_phys); + + 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); + + iclient = msm_ion_client_create(-1, pdev->name); + if (IS_ERR_OR_NULL(iclient)) { + pr_err("msm_ion_client_create() return" + " error, val %p\n", iclient); + iclient = NULL; + } - /* 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); - 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; - } - } - } + if (!mfd) + return -ENODEV; - /* 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); - } else { - if (!hrtimer_active(&msmfb->fake_vsync)) { - hrtimer_start(&msmfb->fake_vsync, - ktime_set(0, NSEC_PER_SEC/60), - HRTIMER_MODE_REL); - } + 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 + + bf_supported = mdp4_overlay_borderfill_supported(); + + 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; } -static void msmfb_update(struct fb_info *info, uint32_t left, uint32_t top, - uint32_t eright, uint32_t ebottom) +static int msm_fb_remove(struct platform_device *pdev) { - msmfb_pan_update(info, left, top, eright, ebottom, 0, 0); + 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; } -static void power_on_panel(struct work_struct *work) +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static int msm_fb_suspend(struct platform_device *pdev, pm_message_t state) { - struct msmfb_info *msmfb = - container_of(work, struct msmfb_info, resume_work); - struct msm_panel_data *panel = msmfb->panel; - unsigned long irq_flags; + struct msm_fb_data_type *mfd; + 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); + 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); + + 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 { + pdev->dev.power.power_state = state; } -error: - mutex_unlock(&msmfb->panel_init_lock); -} + console_unlock(); + return ret; +} +#else +#define msm_fb_suspend NULL +#endif -static int msmfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd) { - 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; + 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; } -int msmfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +#ifdef CONFIG_PM +static int msm_fb_resume_sub(struct msm_fb_data_type *mfd) { - struct msmfb_info *msmfb = info->par; - struct msm_panel_data *panel = msmfb->panel; + int ret = 0; + struct msm_fb_panel_data *pdata = NULL; - /* "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); + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; + + pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + + /* 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"); } else { - msmfb_pan_update(info, 0, 0, info->var.xres, info->var.yres, - var->yoffset, 1); + if (pdata->power_ctrl) + pdata->power_ctrl(TRUE); } - return 0; + + return ret; } +#endif -static void msmfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static int msm_fb_resume(struct platform_device *pdev) { - cfb_fillrect(p, rect); - msmfb_update(p, rect->dx, rect->dy, rect->dx + rect->width, - rect->dy + rect->height); + /* 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 void msmfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +static int msm_fb_runtime_suspend(struct device *dev) { - cfb_copyarea(p, area); - msmfb_update(p, area->dx, area->dy, area->dx + area->width, - area->dy + area->height); + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; } -static void msmfb_imageblit(struct fb_info *p, const struct fb_image *image) +static int msm_fb_runtime_resume(struct device *dev) { - cfb_imageblit(p, image); - msmfb_update(p, image->dx, image->dy, image->dx + image->width, - image->dy + image->height); + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; } +static int msm_fb_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} -static int msmfb_blit(struct fb_info *info, - void __user *p) +#if (defined(CONFIG_SUSPEND) && defined(CONFIG_FB_MSM_HDMI_MSM_PANEL)) +static int msm_fb_ext_suspend(struct device *dev) { - struct mdp_blit_req req; - struct mdp_blit_req_list req_list; - int i; - int ret; + struct msm_fb_data_type *mfd = dev_get_drvdata(dev); + int ret = 0; - if (copy_from_user(&req_list, p, sizeof(req_list))) - return -EFAULT; + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; - 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; - } - return 0; + if (mfd->panel_info.type == HDMI_PANEL || + mfd->panel_info.type == DTV_PANEL) + ret = msm_fb_suspend_sub(mfd); + + return ret; } +static int msm_fb_ext_resume(struct device *dev) +{ + struct msm_fb_data_type *mfd = dev_get_drvdata(dev); + int ret = 0; + + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; + + if (mfd->panel_info.type == HDMI_PANEL || + mfd->panel_info.type == DTV_PANEL) + ret = msm_fb_resume_sub(mfd); + + return ret; +} +#endif + +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, +#if (defined(CONFIG_SUSPEND) && defined(CONFIG_FB_MSM_HDMI_MSM_PANEL)) + .suspend = msm_fb_ext_suspend, + .resume = msm_fb_ext_resume, +#endif +}; -DEFINE_MUTEX(mdp_ppp_lock); +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, + }, +}; -static int msmfb_ioctl(struct fb_info *p, unsigned int cmd, unsigned long arg) +#if defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_FB_MSM_MDP303) +static void memset32_io(u32 __iomem *_ptr, u32 val, size_t count) { - void __user *argp = (void __user *)arg; - int ret; + count >>= 2; + while (count--) + writel(val, _ptr++); +} +#endif - 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; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void msmfb_early_suspend(struct early_suspend *h) +{ + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + early_suspend); +#if 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: - printk(KERN_INFO "msmfb unknown ioctl: %d\n", cmd); - return -EINVAL; + memset32_io((void *)fbi->screen_base, 0x00, fbi->fix.smem_len); + break; } - return 0; +#endif + msm_fb_suspend_sub(mfd); } -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, -}; +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 unsigned PP[16]; +static int unset_bl_level, bl_updated; +static int bl_level_old; +void msm_fb_set_backlight(struct msm_fb_data_type *mfd, __u32 bkl_lvl) +{ + struct msm_fb_panel_data *pdata; + + if (!mfd->panel_power_on || !bl_updated) { + unset_bl_level = bkl_lvl; + return; + } else { + unset_bl_level = 0; + } + pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; -#define BITS_PER_PIXEL 16 + if ((pdata) && (pdata->set_backlight)) { + down(&mfd->sem); + if (bl_level_old == bkl_lvl) { + up(&mfd->sem); + return; + } + mfd->bl_level = bkl_lvl; + pdata->set_backlight(mfd); + bl_level_old = mfd->bl_level; + up(&mfd->sem); + } +} -static void setup_fb_info(struct msmfb_info *msmfb) +static int msm_fb_blank_sub(int blank_mode, struct fb_info *info, + boolean op_enable) { - struct fb_info *fb_info = msmfb->fb; - int r; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct msm_fb_panel_data *pdata = NULL; + int ret = 0; - /* finish setting up the fb_info struct */ - strncpy(fb_info->fix.id, "msmfb", 16); - fb_info->fix.ypanstep = 1; + if (!op_enable) + return -EPERM; - fb_info->fbops = &msmfb_ops; - fb_info->flags = FBINFO_DEFAULT; + 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; + } - fb_info->fix.type = FB_TYPE_PACKED_PIXELS; - fb_info->fix.visual = FB_VISUAL_TRUECOLOR; - fb_info->fix.line_length = msmfb->xres * 2; + 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; - 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; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + case FB_BLANK_POWERDOWN: + default: + 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; + bl_updated = 0; + + msleep(16); + ret = pdata->off(mfd->pdev); + if (ret) + mfd->panel_power_on = curr_pwr_state; + + mfd->op_enable = TRUE; + } else { + if (pdata->power_ctrl) + pdata->power_ctrl(FALSE); + } + break; + } - fb_info->var.yoffset = 0; + return ret; +} - 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; +int calc_fb_offset(struct msm_fb_data_type *mfd, struct fb_info *fbi, int bpp) +{ + struct msm_panel_info *panel_info = &mfd->panel_info; + int remainder, yres, offset; - /* - * 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); + if (panel_info->mode2_yres != 0) { + yres = panel_info->mode2_yres; + remainder = (fbi->fix.line_length*yres) & (PAGE_SIZE - 1); + } else { + yres = panel_info->yres; + remainder = (fbi->fix.line_length*yres) & (PAGE_SIZE - 1); } - 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; + if (!remainder) + remainder = PAGE_SIZE; - r = fb_alloc_cmap(&fb_info->cmap, 16, 0); - fb_info->pseudo_palette = PP; - - PP[0] = 0; - for (r = 1; r < 16; r++) - PP[r] = 0xffffffff; + if (fbi->var.yoffset < yres) { + offset = (fbi->var.xoffset * bpp); + /* iBuf->buf += fbi->var.xoffset * bpp + 0 * + yres * fbi->fix.line_length; */ + } else if (fbi->var.yoffset >= yres && fbi->var.yoffset < 2 * yres) { + offset = (fbi->var.xoffset * bpp + yres * + fbi->fix.line_length + PAGE_SIZE - remainder); + } else { + offset = (fbi->var.xoffset * bpp + 2 * yres * + fbi->fix.line_length + 2 * (PAGE_SIZE - remainder)); + } + return offset; } -static int setup_fbmem(struct msmfb_info *msmfb, struct platform_device *pdev) +static void msm_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) { - struct fb_info *fb = msmfb->fb; - struct resource *resource; - unsigned long size = msmfb->xres * msmfb->yres * - (BITS_PER_PIXEL >> 3) * 2; - unsigned char *fbram; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; - /* board file might have attached a resource describing an fb */ - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!resource) - return -EINVAL; + cfb_fillrect(info, rect); + if (!mfd->hw_refresh && (info->var.yoffset == 0) && + !mfd->sw_currently_refreshing) { + struct fb_var_screeninfo var; - /* 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_size(resource); - fbram = ioremap(resource->start, resource_size(resource)); - if (fbram == NULL) { - printk(KERN_ERR "msmfb: cannot allocate fbram!\n"); - return -ENOMEM; + 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); + + msm_fb_pan_display(&var, info); } - fb->screen_base = fbram; - return 0; } -static int msmfb_probe(struct platform_device *pdev) +static void msm_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) { - struct fb_info *fb; - struct msmfb_info *msmfb; - struct msm_panel_data *panel = pdev->dev.platform_data; - int ret; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; - 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; + cfb_copyarea(info, area); + 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] = (area->dy << 16) | (area->dx); + var.reserved[2] = ((area->dy + area->height) << 16) | + (area->dx + area->width); + + msm_fb_pan_display(&var, info); } +} - 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; +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; - ret = setup_fbmem(msmfb, pdev); - if (ret) - goto error_setup_fbmem; + cfb_imageblit(info, image); + if (!mfd->hw_refresh && (info->var.yoffset == 0) && + !mfd->sw_currently_refreshing) { + struct fb_var_screeninfo var; - setup_fb_info(msmfb); + 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); - 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); + msm_fb_pan_display(&var, info); + } +} - printk(KERN_INFO "msmfb_probe() installing %d x %d panel\n", - msmfb->xres, msmfb->yres); +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); +} - 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); +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; + if (!mfd->lut_update) + return -ENODEV; - msmfb->fake_vsync.function = msmfb_fake_vsync; + mfd->lut_update(info, cmap); + return 0; +} - ret = register_framebuffer(fb); - if (ret) - goto error_register_framebuffer; +/* + * 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) +{ + /* 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); + } - msmfb->sleeping = WAKING; + /* 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; return 0; - -error_register_framebuffer: - iounmap(fb->screen_base); -error_setup_fbmem: - framebuffer_release(msmfb->fb); - return ret; } -static struct platform_driver msm_panel_driver = { - /* need to write remove */ - .probe = msmfb_probe, - .driver = {.name = "msm_panel"}, +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 int msmfb_add_mdp_device(struct device *dev, - struct class_interface *class_intf) +static __u32 msm_fb_line_length(__u32 fb_index, __u32 xres, int bpp) { - /* 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); + /* 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; } -static void msmfb_remove_mdp_device(struct device *dev, - struct class_interface *class_intf) +static int msm_fb_register(struct msm_fb_data_type *mfd) { - /* might need locking if mulitple mdp devices */ - if (dev != &mdp->dev) - return; - platform_driver_unregister(&msm_panel_driver); - mdp = NULL; -} + 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; + int remainder, remainder_mode2; + + /* + * 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; -static struct class_interface msm_fb_interface = { - .add_dev = &msmfb_add_mdp_device, - .remove_dev = &msmfb_remove_mdp_device, -}; + 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; + } + + fix->type = panel_info->is_3d_panel; + + fix->line_length = msm_fb_line_length(mfd->index, panel_info->xres, + bpp); + + /* Make sure all buffers can be addressed on a page boundary by an x + * and y offset */ + + remainder = (fix->line_length * panel_info->yres) & (PAGE_SIZE - 1); + /* PAGE_SIZE is a power of 2 */ + if (!remainder) + remainder = PAGE_SIZE; + remainder_mode2 = (fix->line_length * + panel_info->mode2_yres) & (PAGE_SIZE - 1); + if (!remainder_mode2) + remainder_mode2 = PAGE_SIZE; + + /* + * calculate smem_len based on max size of two supplied modes. + * Only fb0 has mem. fb1 and fb2 don't have mem. + */ + if (!bf_supported || mfd->index == 0) + fix->smem_len = MAX((msm_fb_line_length(mfd->index, + panel_info->xres, + bpp) * + panel_info->yres + PAGE_SIZE - + remainder) * mfd->fb_page, + (msm_fb_line_length(mfd->index, + panel_info->mode2_xres, + bpp) * + panel_info->mode2_yres + PAGE_SIZE - + remainder_mode2) * mfd->fb_page); + else if (mfd->index == 1 || mfd->index == 2) { + pr_debug("%s:%d no memory is allocated for fb%d!\n", + __func__, __LINE__, mfd->index); + fix->smem_len = 0; + } + + mfd->var_xres = panel_info->xres; + mfd->var_yres = panel_info->yres; + mfd->var_frame_rate = panel_info->frame_rate; + + 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 + + ((PAGE_SIZE - remainder)/fix->line_length) * mfd->fb_page; + var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */ + if (mfd->dest == DISPLAY_LCD) { + if (panel_info->type == MDDI_PANEL && panel_info->mddi.is_type1) + var->reserved[3] = panel_info->lcd.refx100 / (100 * 2); + else + var->reserved[3] = panel_info->lcd.refx100 / 100; + } else { + if (panel_info->type == MIPI_VIDEO_PANEL) { + var->reserved[3] = panel_info->mipi.frame_rate; + } else { + var->reserved[3] = 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)); + } + } + pr_debug("reserved[3] %u\n", var->reserved[3]); + + /* + * id field for fb app + */ + id = (int *)&mfd->panel; + + switch (mdp_rev) { + case MDP_REV_20: + snprintf(fix->id, sizeof(fix->id), "msmfb20_%x", (__u32) *id); + break; + case MDP_REV_22: + snprintf(fix->id, sizeof(fix->id), "msmfb22_%x", (__u32) *id); + break; + case MDP_REV_30: + snprintf(fix->id, sizeof(fix->id), "msmfb30_%x", (__u32) *id); + break; + case MDP_REV_303: + snprintf(fix->id, sizeof(fix->id), "msmfb303_%x", (__u32) *id); + break; + case MDP_REV_31: + snprintf(fix->id, sizeof(fix->id), "msmfb31_%x", (__u32) *id); + break; + case MDP_REV_40: + snprintf(fix->id, sizeof(fix->id), "msmfb40_%x", (__u32) *id); + break; + case MDP_REV_41: + snprintf(fix->id, sizeof(fix->id), "msmfb41_%x", (__u32) *id); + break; + case MDP_REV_42: + snprintf(fix->id, sizeof(fix->id), "msmfb42_%x", (__u32) *id); + break; + case MDP_REV_43: + snprintf(fix->id, sizeof(fix->id), "msmfb43_%x", (__u32) *id); + break; + case MDP_REV_44: + snprintf(fix->id, sizeof(fix->id), "msmfb44_%x", (__u32) *id); + break; + default: + snprintf(fix->id, sizeof(fix->id), "msmfb0_%x", (__u32) *id); + break; + } + + 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 (!bf_supported || mfd->index == 0) + if (fbram_size < fix->smem_len) { + pr_err("error: no more framebuffer memory!\n"); + return -ENOMEM; + } + + fbi->screen_base = fbram; + fbi->fix.smem_start = (unsigned long)fbram_phys; + + msm_iommu_map_contig_buffer(fbi->fix.smem_start, + DISPLAY_DOMAIN, + GEN_POOL, + fbi->fix.smem_len, + SZ_4K, + 1, + &(mfd->display_iova)); + + msm_iommu_map_contig_buffer(fbi->fix.smem_start, + ROTATOR_DOMAIN, + GEN_POOL, + fbi->fix.smem_len, + SZ_4K, + 1, + &(mfd->rotator_iova)); + + if (!bf_supported || mfd->index == 0) + memset(fbi->screen_base, 0x0, fix->smem_len); + + mfd->op_enable = TRUE; + mfd->panel_power_on = FALSE; + + /* 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; + } + + if (mfd->lut_update) { + ret = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (ret) + printk(KERN_ERR "%s: fb_alloc_cmap() failed!\n", + __func__); + } + + 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); + + 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 + /* Flip buffer */ + if (!load_565rle_image(INIT_IMAGE_FILE, bf_supported)) + ; +#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 int msm_fb_open(struct fb_info *info, int user) +{ + 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 (info->node == 0 && !(mfd->cont_splash_done)) { /* primary */ + mfd->ref_cnt++; + return 0; + } + + if (!mfd->ref_cnt) { + if (!bf_supported || + (info->node != 1 && info->node != 2)) + mdp_set_dma_pan_info(info, NULL, TRUE); + else + pr_debug("%s:%d no mdp_set_dma_pan_info %d\n", + __func__, __LINE__, info->node); + + 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; + struct msm_fb_panel_data *pdata; + + /* + * If framebuffer is 1 or 2, io pen display is not allowed. + */ + if (bf_supported && + (info->node == 1 || info->node == 2)) { + pr_err("%s: no pan display for fb%d!", + __func__, info->node); + return -EPERM; + } + + if (info->node != 0 || mfd->cont_splash_done) /* primary */ + 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); + + if (info->node == 0 && !(mfd->cont_splash_done)) { /* primary */ + mdp_set_dma_pan_info(info, NULL, TRUE); + if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) { + pr_err("%s: can't turn on display!\n", __func__); + return -EINVAL; + } + } + + mdp_set_dma_pan_info(info, dirtyPtr, + (var->activate == FB_ACTIVATE_VBL)); + mdp_dma_pan_update(info); + up(&msm_fb_pan_sem); + + if (unset_bl_level && !bl_updated) { + pdata = (struct msm_fb_panel_data *)mfd->pdev-> + dev.platform_data; + if ((pdata) && (pdata->set_backlight)) { + down(&mfd->sem); + mfd->bl_level = unset_bl_level; + pdata->set_backlight(mfd); + bl_level_old = unset_bl_level; + up(&mfd->sem); + bl_updated = 1; + } + } + + ++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 (!bf_supported || + (info->node != 1 && info->node != 2)) + 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; +} + +int msm_fb_check_frame_rate(struct msm_fb_data_type *mfd + , struct fb_info *info) +{ + int panel_height, panel_width, var_frame_rate, fps_mod; + struct fb_var_screeninfo *var = &info->var; + fps_mod = 0; + if ((mfd->panel_info.type == DTV_PANEL) || + (mfd->panel_info.type == HDMI_PANEL)) { + panel_height = var->yres + var->upper_margin + + var->vsync_len + var->lower_margin; + panel_width = var->xres + var->right_margin + + var->hsync_len + var->left_margin; + var_frame_rate = ((var->pixclock)/(panel_height * panel_width)); + if (mfd->var_frame_rate != var_frame_rate) { + fps_mod = 1; + mfd->var_frame_rate = var_frame_rate; + } + } + return fps_mod; +} + +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) || + (msm_fb_check_frame_rate(mfd, info))))) { + 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) == 0x05) || + ((splitreq.flags & 0x07) == 0x02) || + ((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) == 0x05) || + ((splitreq.flags & 0x07) == 0x02) || + ((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; + } + 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 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; + + 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; + if (bf_supported && + (info->node == 1 || info->node == 2)) { + pr_err("%s: no pan display for fb%d.", + __func__, info->node); + return -EPERM; + } + /* 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_wait(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; + + if (mfd->overlay_play_enable == 0) /* nothing to do */ + return 0; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("%s:msmfb_overlay_wait ioctl failed", __func__); + return ret; + } + + ret = mdp4_overlay_play_wait(info, &req); + + return ret; +} + +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 msm_fb_panel_data *pdata; + + 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); + + if (info->node == 0 && !(mfd->cont_splash_done)) { /* primary */ + mdp_set_dma_pan_info(info, NULL, TRUE); + if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) { + pr_err("%s: can't turn on display!\n", __func__); + return -EINVAL; + } + } + + ret = mdp4_overlay_play(info, &req); + + if (unset_bl_level && !bl_updated) { + pdata = (struct msm_fb_panel_data *)mfd->pdev-> + dev.platform_data; + if ((pdata) && (pdata->set_backlight)) { + down(&mfd->sem); + mfd->bl_level = unset_bl_level; + pdata->set_backlight(mfd); + bl_level_old = unset_bl_level; + up(&mfd->sem); + bl_updated = 1; + } + } + + 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; +} + +static int msmfb_overlay_blt(struct fb_info *info, unsigned long *argp) +{ + int ret; + struct msmfb_overlay_blt req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("%s: 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 = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("%s: failed\n", __func__); + return ret; + } + + 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; +} + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL +static int msmfb_overlay_ioctl_writeback_init(struct fb_info *info) +{ + return mdp4_writeback_init(info); +} +static int msmfb_overlay_ioctl_writeback_start( + struct fb_info *info) +{ + int ret = 0; + ret = mdp4_writeback_start(info); + if (ret) + goto error; +error: + if (ret) + pr_err("%s:msmfb_writeback_start " + " ioctl failed\n", __func__); + return ret; +} + +static int msmfb_overlay_ioctl_writeback_stop( + struct fb_info *info) +{ + int ret = 0; + ret = mdp4_writeback_stop(info); + if (ret) + goto error; + +error: + if (ret) + pr_err("%s:msmfb_writeback_stop ioctl failed\n", + __func__); + return ret; +} + +static int msmfb_overlay_ioctl_writeback_queue_buffer( + struct fb_info *info, unsigned long *argp) +{ + int ret = 0; + struct msmfb_data data; + + ret = copy_from_user(&data, argp, sizeof(data)); + if (ret) + goto error; + + ret = mdp4_writeback_queue_buffer(info, &data); + if (ret) + goto error; + +error: + if (ret) + pr_err("%s:msmfb_writeback_queue_buffer ioctl failed\n", + __func__); + return ret; +} + +static int msmfb_overlay_ioctl_writeback_dequeue_buffer( + struct fb_info *info, unsigned long *argp) +{ + int ret = 0; + struct msmfb_data data; + + ret = copy_from_user(&data, argp, sizeof(data)); + if (ret) + goto error; + + ret = mdp4_writeback_dequeue_buffer(info, &data); + if (ret) + goto error; + + ret = copy_to_user(argp, &data, sizeof(data)); + if (ret) + goto error; + +error: + if (ret) + pr_err("%s:msmfb_writeback_dequeue_buffer ioctl failed\n", + __func__); + return ret; +} +static int msmfb_overlay_ioctl_writeback_terminate(struct fb_info *info) +{ + return mdp4_writeback_terminate(info); +} + +#else +static int msmfb_overlay_ioctl_writeback_init(struct fb_info *info) +{ + return -ENOTSUPP; +} +static int msmfb_overlay_ioctl_writeback_start( + struct fb_info *info) +{ + return -ENOTSUPP; +} + +static int msmfb_overlay_ioctl_writeback_stop( + struct fb_info *info) +{ + return -ENOTSUPP; +} + +static int msmfb_overlay_ioctl_writeback_queue_buffer( + struct fb_info *info, unsigned long *argp) +{ + return -ENOTSUPP; +} + +static int msmfb_overlay_ioctl_writeback_dequeue_buffer( + struct fb_info *info, unsigned long *argp) +{ + return -ENOTSUPP; +} +static int msmfb_overlay_ioctl_writeback_terminate(struct fb_info *info) +{ + return -ENOTSUPP; +} +#endif + +static int msmfb_overlay_3d_sbys(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_sbys(info, &req); + + return ret; +} + +static int msmfb_mixer_info(struct fb_info *info, unsigned long *argp) +{ + int ret, cnt; + struct msmfb_mixer_info_req req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("%s: failed\n", __func__); + return ret; + } + + cnt = mdp4_mixer_info(req.mixer_num, req.info); + req.cnt = cnt; + ret = copy_to_user(argp, &req, sizeof(req)); + if (ret) + pr_err("%s:msmfb_overlay_blt_off ioctl failed\n", + __func__); + + return cnt; +} + +#endif + +DEFINE_SEMAPHORE(msm_fb_ioctl_ppp_sem); +DEFINE_MUTEX(msm_fb_ioctl_lut_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); + } +} +#else +static void msmfb_set_color_conv(struct mdp_csc *p) +{ + mdp4_vg_csc_update(p); +} +#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 msmfb_handle_pp_ioctl(struct msmfb_mdp_pp *pp_ptr) +{ + int ret = -1; + + if (!pp_ptr) + return ret; + + switch (pp_ptr->op) { +#ifdef CONFIG_FB_MSM_MDP40 + case mdp_op_csc_cfg: + ret = mdp4_csc_config(&(pp_ptr->data.csc_cfg_data)); + break; + + case mdp_op_pcc_cfg: + ret = mdp4_pcc_cfg(&(pp_ptr->data.pcc_cfg_data)); + break; + + case mdp_op_lut_cfg: + switch (pp_ptr->data.lut_cfg_data.lut_type) { + case mdp_lut_igc: + ret = mdp4_igc_lut_config( + (struct mdp_igc_lut_data *) + &pp_ptr->data.lut_cfg_data.data); + break; + + case mdp_lut_pgc: + ret = mdp4_argc_cfg( + &pp_ptr->data.lut_cfg_data.data.pgc_lut_data); + break; + + case mdp_lut_hist: + ret = mdp_hist_lut_config( + (struct mdp_hist_lut_data *) + &pp_ptr->data.lut_cfg_data.data); + break; + + default: + break; + } + break; + case mdp_op_qseed_cfg: + ret = mdp4_qseed_cfg((struct mdp_qseed_cfg_data *) + &pp_ptr->data.qseed_cfg_data); + break; +#endif + default: + pr_warn("Unsupported request to MDP_PP IOCTL.\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +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_data hist; + struct mdp_histogram_start_req hist_req; + uint32_t block; +#ifndef CONFIG_FB_MSM_MDP40 + struct mdp_ccs ccs_matrix; +#else + struct mdp_csc csc_matrix; +#endif + struct mdp_page_protection fb_page_protection; + struct msmfb_mdp_pp mdp_pp; + 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_PLAY_WAIT: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_play_wait(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_sbys(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_MIXER_INFO: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_mixer_info(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_WRITEBACK_INIT: + ret = msmfb_overlay_ioctl_writeback_init(info); + break; + case MSMFB_WRITEBACK_START: + ret = msmfb_overlay_ioctl_writeback_start( + info); + break; + case MSMFB_WRITEBACK_STOP: + ret = msmfb_overlay_ioctl_writeback_stop( + info); + break; + case MSMFB_WRITEBACK_QUEUE_BUFFER: + ret = msmfb_overlay_ioctl_writeback_queue_buffer( + info, argp); + break; + case MSMFB_WRITEBACK_DEQUEUE_BUFFER: + ret = msmfb_overlay_ioctl_writeback_dequeue_buffer( + info, argp); + break; + case MSMFB_WRITEBACK_TERMINATE: + ret = msmfb_overlay_ioctl_writeback_terminate(info); + 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 = copy_from_user(&csc_matrix, argp, sizeof(csc_matrix)); + if (ret) { + pr_err("%s:MSMFB_SET_CSC_MATRIX ioctl failed\n", + __func__); + return ret; + } + down(&msm_fb_ioctl_ppp_sem); + msmfb_set_color_conv(&csc_matrix); + up(&msm_fb_ioctl_ppp_sem); + +#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->panel_power_on) + return -EPERM; + + if (!mfd->do_histogram) + return -ENODEV; + + ret = copy_from_user(&hist, argp, sizeof(hist)); + if (ret) + return ret; + + ret = mfd->do_histogram(info, &hist); + break; + + case MSMFB_HISTOGRAM_START: + if (!mfd->panel_power_on) + return -EPERM; + + if (!mfd->start_histogram) + return -ENODEV; + + ret = copy_from_user(&hist_req, argp, sizeof(hist_req)); + if (ret) + return ret; + + ret = mfd->start_histogram(&hist_req); + break; + + case MSMFB_HISTOGRAM_STOP: + if (!mfd->stop_histogram) + return -ENODEV; + + ret = copy_from_user(&block, argp, sizeof(int)); + if (ret) + return ret; + + ret = mfd->stop_histogram(info, block); + 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; + + case MSMFB_MDP_PP: + ret = copy_from_user(&mdp_pp, argp, sizeof(mdp_pp)); + if (ret) + return ret; + + ret = msmfb_handle_pp_ioctl(&mdp_pp); + 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); +} + +#ifdef CONFIG_FB_MSM_WRITEBACK_MSM_PANEL +struct fb_info *msm_fb_get_writeback_fb(void) +{ + int c = 0; + for (c = 0; c < fbi_list_index; ++c) { + struct msm_fb_data_type *mfd; + mfd = (struct msm_fb_data_type *)fbi_list[c]->par; + if (mfd->panel.type == WRITEBACK_PANEL) + return fbi_list[c]; + } + + return NULL; +} +EXPORT_SYMBOL(msm_fb_get_writeback_fb); + +int msm_fb_writeback_start(struct fb_info *info) +{ + return mdp4_writeback_start(info); +} +EXPORT_SYMBOL(msm_fb_writeback_start); + +int msm_fb_writeback_queue_buffer(struct fb_info *info, + struct msmfb_data *data) +{ + return mdp4_writeback_queue_buffer(info, data); +} +EXPORT_SYMBOL(msm_fb_writeback_queue_buffer); + +int msm_fb_writeback_dequeue_buffer(struct fb_info *info, + struct msmfb_data *data) +{ + return mdp4_writeback_dequeue_buffer(info, data); +} +EXPORT_SYMBOL(msm_fb_writeback_dequeue_buffer); + +int msm_fb_writeback_stop(struct fb_info *info) +{ + return mdp4_writeback_stop(info); +} +EXPORT_SYMBOL(msm_fb_writeback_stop); +int msm_fb_writeback_init(struct fb_info *info) +{ + return mdp4_writeback_init(info); +} +EXPORT_SYMBOL(msm_fb_writeback_init); +int msm_fb_writeback_terminate(struct fb_info *info) +{ + return mdp4_writeback_terminate(info); +} +EXPORT_SYMBOL(msm_fb_writeback_terminate); +#endif + +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 || type == WRITEBACK_PANEL) { + if (hdmi_prim_display) + pdata->panel_info.fb_num = 2; + else + 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; + mfd->iclient = iclient; + /* 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, + int subsys_id) +{ + struct fb_info *info; + struct msm_fb_data_type *mfd; + + if (fb_num > MAX_FBI_LIST || + (subsys_id != DISPLAY_SUBSYSTEM_ID && + subsys_id != ROTATOR_SUBSYSTEM_ID)) { + pr_err("%s(): Invalid parameters\n", __func__); + return -1; + } + + info = fbi_list[fb_num]; + if (!info) { + pr_err("%s(): info is NULL\n", __func__); + return -1; + } + + mfd = (struct msm_fb_data_type *)info->par; + + if (subsys_id == DISPLAY_SUBSYSTEM_ID) { + if (mfd->display_iova) + *start = mfd->display_iova; + else + *start = info->fix.smem_start; + } else { + if (mfd->rotator_iova) + *start = mfd->rotator_iova; + else + *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; +} + +/* Called by v4l2 driver to enable/disable overlay pipe */ +int msm_fb_v4l2_enable(struct mdp_overlay *req, bool enable, void **par) +{ + int err = 0; +#ifdef CONFIG_FB_MSM_MDP40 + struct mdp4_overlay_pipe *pipe; + if (enable) { + + err = mdp4_v4l2_overlay_set(fbi_list[0], req, &pipe); + + *(struct mdp4_overlay_pipe **)par = pipe; + + } else { + pipe = *(struct mdp4_overlay_pipe **)par; + mdp4_v4l2_overlay_clear(pipe); + } +#else +#ifdef CONFIG_FB_MSM_MDP30 + if (enable) + err = mdp_ppp_v4l2_overlay_set(fbi_list[0], req); + else + err = mdp_ppp_v4l2_overlay_clear(); +#else + err = -EINVAL; +#endif +#endif + + return err; +} +EXPORT_SYMBOL(msm_fb_v4l2_enable); -static int __init msmfb_init(void) +/* Called by v4l2 driver to provide a frame for display */ +int msm_fb_v4l2_update(void *par, + unsigned long srcp0_addr, unsigned long srcp0_size, + unsigned long srcp1_addr, unsigned long srcp1_size, + unsigned long srcp2_addr, unsigned long srcp2_size) { - return register_mdp_client(&msm_fb_interface); +#ifdef CONFIG_FB_MSM_MDP40 + struct mdp4_overlay_pipe *pipe = (struct mdp4_overlay_pipe *)par; + return mdp4_v4l2_overlay_play(fbi_list[0], pipe, + srcp0_addr, srcp1_addr, + srcp2_addr); +#else +#ifdef CONFIG_FB_MSM_MDP30 + return mdp_ppp_v4l2_overlay_play(fbi_list[0], + srcp0_addr, srcp0_size, + srcp1_addr, srcp1_size); +#else + return -EINVAL; +#endif +#endif } +EXPORT_SYMBOL(msm_fb_v4l2_update); -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 0000000000000000000000000000000000000000..c9eb7dd2b2b51e3d0236b3d5bd5b52c0cababf7e --- /dev/null +++ b/drivers/video/msm/msm_fb.h @@ -0,0 +1,223 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#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 msmfb_writeback_data_list { + struct list_head registered_entry; + struct list_head active_entry; + void *addr; + struct file *pmem_file; + struct msmfb_data buf_info; + struct msmfb_img img; + int state; +}; + + +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_data *hist); + int (*start_histogram) (struct mdp_histogram_start_req *req); + int (*stop_histogram) (struct fb_info *info, uint32_t block); + 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; + __u32 var_frame_rate; + +#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 mutex writeback_mutex; + struct mutex unregister_mutex; + struct list_head writeback_busy_queue; + struct list_head writeback_free_queue; + struct list_head writeback_register_queue; + wait_queue_head_t wait_q; + struct ion_client *iclient; + unsigned long display_iova; + unsigned long rotator_iova; + struct mdp_buf_type *ov0_wb_buf; + struct mdp_buf_type *ov1_wb_buf; + u32 ov_start; + u32 mem_hid; + u32 mdp_rev; + u32 use_ov0_blt, ov0_blt_state; + u32 use_ov1_blt, ov1_blt_state; + u32 writeback_state; + bool writeback_active_cnt; + int cont_splash_done; +}; + +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); +struct fb_info *msm_fb_get_writeback_fb(void); +int msm_fb_writeback_init(struct fb_info *info); +int msm_fb_writeback_start(struct fb_info *info); +int msm_fb_writeback_queue_buffer(struct fb_info *info, + struct msmfb_data *data); +int msm_fb_writeback_dequeue_buffer(struct fb_info *info, + struct msmfb_data *data); +int msm_fb_writeback_stop(struct fb_info *info); +int msm_fb_writeback_terminate(struct fb_info *info); +int msm_fb_detect_client(const char *name); +int calc_fb_offset(struct msm_fb_data_type *mfd, struct fb_info *fbi, int bpp); + +#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); +int msm_fb_check_frame_rate(struct msm_fb_data_type *mfd, + struct fb_info *info); + +#ifdef CONFIG_FB_MSM_LOGO +#define INIT_IMAGE_FILE "/initlogo.rle" +int load_565rle_image(char *filename, bool bf_supported); +#endif + +#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 0000000000000000000000000000000000000000..9afbbf16a570f7bc887db08bc93162956ef0e272 --- /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 0000000000000000000000000000000000000000..1c1f3926723dd269aab956b185a6413926d1a9d1 --- /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 0000000000000000000000000000000000000000..8640116156f463d162a0149db02c9949ec3983f4 --- /dev/null +++ b/drivers/video/msm/msm_fb_panel.c @@ -0,0 +1,148 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 LVDS_PANEL: + snprintf(dev_name, sizeof(dev_name), "lvds"); + 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; + case WRITEBACK_PANEL: + snprintf(dev_name, sizeof(dev_name), "writeback"); + 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 0000000000000000000000000000000000000000..9d16b7b411eb46319863e3834fe00b1cbc9630d7 --- /dev/null +++ b/drivers/video/msm/msm_fb_panel.h @@ -0,0 +1,211 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 */ +#define WRITEBACK_PANEL 10 /* Wifi display */ +#define LVDS_PANEL 11 /* LVDS */ + +/* 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 */ + DISPLAY_3, /* attached on third writeback 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; + /* Pad width */ + uint32 xres_pad; + /* Pad height */ + uint32 yres_pad; +}; + +struct mddi_panel_info { + __u32 vdopkt; + boolean is_type1; +}; + +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; + /* The packet-size should not bet changed */ + char no_max_pkt_size; + /* Clock required during LP commands */ + char force_clk_lane_hs; +}; + +enum lvds_mode { + LVDS_SINGLE_CHANNEL_MODE, + LVDS_DUAL_CHANNEL_MODE, +}; + +struct lvds_panel_info { + enum lvds_mode channel_mode; + /* Channel swap in dual mode */ + char channel_swap; +}; + +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; + __u32 frame_rate; + + + struct mddi_panel_info mddi; + struct lcd_panel_info lcd; + struct lcdc_panel_info lcdc; + struct mipi_panel_info mipi; + struct lvds_panel_info lvds; +}; + +#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); + int (*power_ctrl) (boolean enable); + 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 0000000000000000000000000000000000000000..30dc85456ae00fd865b6766ae578e9909d3f53ed --- /dev/null +++ b/drivers/video/msm/tvenc.c @@ -0,0 +1,522 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 TVENC_C +#include "tvenc.h" +#include "msm_fb.h" +#include "mdp4.h" +/* AXI rate in KHz */ +#define MSM_SYSTEM_BUS_RATE 128000000 + +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_prepare_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_prepare_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_unprepare(tvenc_pclk); + clk_disable_unprepare(tvenc_clk); + return ret; + } +tvencp_err: + clk_disable_unprepare(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_prepare_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_prepare_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_unprepare(mdp_tv_clk); + clk_disable_unprepare(tvdac_clk); + if (tvenc_pdata->poll) + tvenc_set_encoder_clock(CLOCK_OFF); + return ret; + } + +mdptv_err: + clk_disable_unprepare(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_unprepare(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_prepare_enable(mfd->ebi1_clk); +#endif + 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, ret; + struct clk *ebi1_clk = NULL; + + 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_clk = clk_get(&pdev->dev, "enc_clk"); + tvdac_clk = clk_get(&pdev->dev, "dac_clk"); + tvenc_pclk = clk_get(&pdev->dev, "iface_clk"); + mdp_tv_clk = clk_get(&pdev->dev, "mdp_clk"); + +#ifndef CONFIG_MSM_BUS_SCALING + ebi1_clk = clk_get(&pdev->dev, "mem_clk"); + if (IS_ERR(ebi1_clk)) { + rc = PTR_ERR(ebi1_clk); + goto tvenc_probe_err; + } + clk_set_rate(ebi1_clk, MSM_SYSTEM_BUS_RATE); +#endif + +#ifdef CONFIG_FB_MSM_MDP40 + tv_src_clk = clk_get(&pdev->dev, "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; + } + } + + tvenc_pdata = pdev->dev.platform_data; + tvenc_resource_initialized = 1; + return 0; + } + + if (!tvenc_resource_initialized) + return -EPERM; + + mfd = platform_get_drvdata(pdev); + mfd->ebi1_clk = ebi1_clk; + + 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__); + } + } +#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) +{ + 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 0000000000000000000000000000000000000000..c64c16067d9ecd508360861d611b1d804ad54bfc --- /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 0000000000000000000000000000000000000000..9ee55ccb68d9686d965630113a070b206307aaea --- /dev/null +++ b/drivers/video/msm/tvout_msm.c @@ -0,0 +1,652 @@ +/* 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; + + if (msm_fb_detect_client("tvout_msm")) + return 0; + + 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 0000000000000000000000000000000000000000..81b143626addbee2be86e134d40307b46cfd63be --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.c @@ -0,0 +1,719 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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; + 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); + if (res_trk_get_enable_ion()) { + DDL_MSG_LOW("ddl_dev_init:ION framework enabled"); + ddl_context->video_ion_client = + res_trk_get_ion_client(); + if (!ddl_context->video_ion_client) { + DDL_MSG_ERROR("ION client create failed"); + return VCD_ERR_ILLEGAL_OP; + } + } + 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 (res_trk_get_firmware_addr(&ddl_context->dram_base_a)) { + DDL_MSG_ERROR("firmware allocation failed"); + ptr = NULL; + } else { + ptr = (void *)ddl_context->dram_base_a.virtual_base_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) { + ddl_context->metadata_shared_input.mem_type = DDL_FW_MEM; + 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->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_context->video_ion_client = NULL; + 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; + } + if (res_trk_check_for_sec_session()) + ddl->shared_mem[0].mem_type = DDL_CMD_MEM; + else + ddl->shared_mem[0].mem_type = DDL_FW_MEM; + ptr = ddl_pmem_alloc(&ddl->shared_mem[0], + DDL_FW_AUX_HOST_CMD_SPACE_SIZE, 0); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + if (!status && ddl_context->frame_channel_depth + == VCD_DUAL_FRAME_COMMAND_CHANNEL) { + if (res_trk_check_for_sec_session()) + ddl->shared_mem[1].mem_type = DDL_CMD_MEM; + else + ddl->shared_mem[1].mem_type = DDL_FW_MEM; + ptr = ddl_pmem_alloc(&ddl->shared_mem[1], + DDL_FW_AUX_HOST_CMD_SPACE_SIZE, 0); + 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 (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 + encoder->seq_header.mem_type = DDL_MM_MEM; + 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; + } + msm_ion_do_cache_op(ddl_context->video_ion_client, + encoder->seq_header.alloc_handle, + encoder->seq_header.virtual_base_addr, + encoder->seq_header.buffer_size, + ION_IOC_CLEAN_INV_CACHES); + if (encoder->slice_delivery_info.enable) { + DDL_MSG_LOW("%s: slice mode allocate memory for struct\n", + __func__); + ptr = ddl_pmem_alloc(&encoder->batch_frame.slice_batch_in, + DDL_ENC_SLICE_BATCH_INPSTRUCT_SIZE, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (ptr) { + ptr = ddl_pmem_alloc( + &encoder->batch_frame.slice_batch_out, + DDL_ENC_SLICE_BATCH_OUTSTRUCT_SIZE, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + } + if (!ptr) { + ddl_pmem_free(&encoder->batch_frame.slice_batch_in); + ddl_pmem_free(&encoder->batch_frame.slice_batch_out); + ddl_free_enc_hw_buffers(ddl); + ddl_pmem_free(&encoder->seq_header); + DDL_MSG_ERROR("ddlEncStart:SeqHdrAllocFailed"); + return VCD_ERR_ALLOC_FAIL; + } + } + 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"); + 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; + struct ddl_encoder_data *encoder = + &ddl->codec_data.encoder; + u32 vcd_status = VCD_S_SUCCESS; + struct vcd_transc *transc; + transc = (struct vcd_transc *)(ddl->client_data); + DDL_MSG_LOW("%s: transc = 0x%x, in_use = %u", + __func__, (u32)ddl->client_data, transc->in_use); + if (encoder->slice_delivery_info.enable) { + return ddl_encode_frame_batch(ddl_handle, + input_frame, + output_bit, + 1, + encoder->slice_delivery_info.num_slices, + client_data); + } + + 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_encode_frame_batch(u32 *ddl_handle, + struct ddl_frame_data_tag *input_frame, + struct ddl_frame_data_tag *output_bit, + u32 num_in_frames, u32 num_out_frames, + 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; + struct ddl_encoder_data *encoder; + + DDL_MSG_LOW("ddl_encode_frame_batch"); + 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; + encoder = &ddl->codec_data.encoder; + if (encoder->slice_delivery_info.enable) { + DDL_MEMCPY((void *)&(encoder->batch_frame.output_frame[0]), + (void *)output_bit, + sizeof(struct ddl_frame_data_tag) * num_out_frames); + encoder->batch_frame.num_output_frames = num_out_frames; + ddl->input_frame = *input_frame; + vcd_status = ddl_insert_input_frame_to_pool(ddl, input_frame); + if (!vcd_status) + ddl_vidc_encode_slice_batch_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"); + 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"); + 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 0000000000000000000000000000000000000000..ac1ff247e98270e8311429c52c63a3b3762fa644 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.h @@ -0,0 +1,492 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#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) + +#define MDP_MIN_TILE_HEIGHT 96 + +enum ddl_mem_area { + DDL_FW_MEM = 0x0, + DDL_MM_MEM = 0x1, + DDL_CMD_MEM = 0x2 +}; + +struct ddl_buf_addr{ + u8 *virtual_base_addr; + u8 *physical_base_addr; + u8 *align_physical_addr; + u8 *align_virtual_addr; + phys_addr_t alloced_phys_addr; + struct msm_mapped_buffer *mapped_buffer; + struct ion_handle *alloc_handle; + u32 buffer_size; + enum ddl_mem_area mem_type; + void *pil_cookie; +}; +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_ENCODE_CONTINUE = 0xB, + 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_WAIT_FOR_CONTINUE = 0xD, + 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_batch_frame_data { + struct ddl_buf_addr slice_batch_in; + struct ddl_buf_addr slice_batch_out; + struct ddl_frame_data_tag input_frame; + struct ddl_frame_data_tag output_frame + [DDL_MAX_NUM_BFRS_FOR_SLICE_BATCH]; + u32 num_output_frames; + u32 out_frm_next_frmindex; +}; +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 vcd_property_sps_pps_for_idr_enable sps_pps; + 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; + u32 closed_gop; + struct vcd_property_slice_delivery_info slice_delivery_info; + struct ddl_batch_frame_data batch_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; + u32 reconfig_detected; + u32 dmx_disable; + int avg_dec_time; + int dec_time_sum; +}; +union ddl_codec_data{ + struct ddl_codec_data_hdr hdr; + struct ddl_decoder_data decoder; + struct ddl_encoder_data encoder; +}; +struct ddl_context{ + 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; + struct ion_client *video_ion_client; + 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); + void(*vidc_encode_slice_batch_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_continue(struct ddl_client_context *); +void ddl_vidc_encode_frame_run(struct ddl_client_context *); +void ddl_vidc_encode_slice_batch_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_vidc_encode_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_get_stream_buf_from_batch_pool(struct ddl_client_context *ddl, + struct ddl_frame_data_tag *stream_buffer); +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); +void ddl_fill_dec_desc_buffer(struct ddl_client_context *ddl); +void ddl_set_vidc_timeout(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(struct ddl_buf_addr *); +int ddl_vidc_decode_get_avg_time(struct ddl_client_context *ddl); +void ddl_vidc_decode_reset_avg_time(struct ddl_client_context *ddl); +void ddl_calc_core_proc_time(const char *func_name, u32 index, + struct ddl_client_context *ddl); +#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 0000000000000000000000000000000000000000..5c1ee215f105319b701a2993b45cad27b855e985 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_api.h @@ -0,0 +1,120 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include "vidc.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 vcd_property_slice_delivery_info { + u32 enable; + u32 num_slices; + u32 num_slices_enc; +}; +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_frame_batch(u32 *ddl_handle, + struct ddl_frame_data_tag *input_frame, + struct ddl_frame_data_tag *output_bit, + u32 num_in_frames, u32 num_out_frames, + 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 0000000000000000000000000000000000000000..50c3696d1aa1e43699fc69f85beea6c82111336a --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_core.h @@ -0,0 +1,153 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VIDC_1080P_48MHZ (48000000) +#define DDL_VIDC_1080P_133MHZ (133330000) +#define DDL_VIDC_1080P_200MHZ (200000000) +#define DDL_VIDC_1080P_48MHZ_TIMEOUT_VALUE (0xCB8) +#define DDL_VIDC_1080P_133MHZ_TIMEOUT_VALUE (0x2355) +#define DDL_VIDC_1080P_200MHZ_TIMEOUT_VALUE (0x3500) + +#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(4)) +#define DDL_FW_INST_GLOBAL_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(800)) +#define DDL_FW_H264DEC_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(800)) +#define DDL_FW_H264ENC_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(20)) +#define DDL_FW_OTHER_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(20)) + +#define VCD_DEC_CPB_SIZE (DDL_KILO_BYTE(512)) +#define DDL_DBG_CORE_DUMP_SIZE (DDL_KILO_BYTE(10)) +#define DDL_VIDC_1080P_BASE_OFFSET_SHIFT 11 + +#define DDL_BUFEND_PAD 256 +#define DDL_ENC_SEQHEADER_SIZE (512+DDL_BUFEND_PAD) +#define DDL_ENC_SLICE_BATCH_FACTOR 5 +#define DDL_MAX_NUM_BFRS_FOR_SLICE_BATCH 8 +#define DDL_ENC_SLICE_BATCH_INPSTRUCT_SIZE (128 + \ + 32 * DDL_MAX_NUM_BFRS_FOR_SLICE_BATCH) +#define DDL_ENC_SLICE_BATCH_OUTSTRUCT_SIZE (64 + \ + 64 * DDL_MAX_NUM_BFRS_FOR_SLICE_BATCH) +#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 + +#define VIDC_SM_ERR_CONCEALMENT_ENABLE 1 +#define VIDC_SM_ERR_CONCEALMENT_INTER_SLICE_MB_COPY 2 +#define VIDC_SM_ERR_CONCEALMENT_INTRA_SLICE_COLOR_CONCEALMENT 1 + +#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 0000000000000000000000000000000000000000..a2327d537a5977289def75af13cb7d27f99909ba --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_errors.c @@ -0,0 +1,771 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_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_WARN_DEBLOCKING_NOT_DONE: + case VIDC_1080P_WARN_INCOMPLETE_FRAME: + 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: + case VIDC_1080P_WARN_NO_LONG_TERM_REFERENCE: + case VIDC_1080P_WARN_NO_SPACE_MPEG2_DATA_DUMP: + case VIDC_1080P_WARN_METADATA_NO_SPACE_MISSING_MB: + 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_WARN_DEBLOCKING_NOT_DONE: + string = "VIDC_1080P_WARN_DEBLOCKING_NOT_DONE"; + break; + case VIDC_1080P_WARN_INCOMPLETE_FRAME: + string = "VIDC_1080P_WARN_INCOMPLETE_FRAME"; + 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; + case VIDC_1080P_WARN_NO_LONG_TERM_REFERENCE: + string = "VIDC_1080P_WARN_NO_LONG_TERM_REFERENCE"; + break; + case VIDC_1080P_WARN_NO_SPACE_MPEG2_DATA_DUMP: + string = "VIDC_1080P_WARN_NO_SPACE_MPEG2_DATA_DUMP"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_MISSING_MB: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_MISSING_MB"; + 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_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 0000000000000000000000000000000000000000..64d2976ad5678efcfd88227fe57b91e6d5d60332 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_helper.c @@ -0,0 +1,1074 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "vcd_ddl.h" +#include "vcd_ddl_shared_mem.h" +#include "vcd_res_tracker_api.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 (!(res_trk_check_for_sec_session()) && + frame[i].vcd_frm.virtual) { + if (luma_size <= frame[i].vcd_frm.alloc_len) { + 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); + if (frame[i].vcd_frm.ion_flag == CACHED) { + msm_ion_do_cache_op( + ddl_context->video_ion_client, + frame[i].vcd_frm.buff_ion_handle, + (unsigned long *)frame[i]. + vcd_frm.virtual, + (unsigned long)frame[i]. + vcd_frm.alloc_len, + ION_IOC_CLEAN_INV_CACHES); + } + } else { + DDL_MSG_ERROR("luma size error"); + return VCD_ERR_FAIL; + } + } + + 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) +{ + ddl_pmem_free(&ddl_context->metadata_shared_input); + ddl_fw_release(&ddl_context->dram_base_a); +} + +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_pmem_free(&encoder->batch_frame.slice_batch_in); + ddl_pmem_free(&encoder->batch_frame.slice_batch_out); + ddl_vidc_encode_dynamic_property(ddl, false); + encoder->dynamic_prop_change = 0; + ddl_free_enc_hw_buffers(ddl); + } + ddl_pmem_free(&ddl->shared_mem[0]); + ddl_pmem_free(&ddl->shared_mem[1]); +} + +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; + struct ddl_context *ddl_context = ddl->ddl_context; + + 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) { + dec_bufs->context.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->context, buf_size.sz_context, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + else + msm_ion_do_cache_op(ddl_context->video_ion_client, + dec_bufs->context.alloc_handle, + dec_bufs->context.virtual_base_addr, + dec_bufs->context.buffer_size, + ION_IOC_CLEAN_INV_CACHES); + } + if (buf_size.sz_nb_ip > 0) { + dec_bufs->h264_nb_ip.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->h264_nb_ip, buf_size.sz_nb_ip, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_vert_nb_mv > 0) { + dec_bufs->h264_vert_nb_mv.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->h264_vert_nb_mv, + buf_size.sz_vert_nb_mv, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_nb_dcac > 0) { + dec_bufs->nb_dcac.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->nb_dcac, buf_size.sz_nb_dcac, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_upnb_mv > 0) { + dec_bufs->upnb_mv.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->upnb_mv, buf_size.sz_upnb_mv, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_sub_anchor_mv > 0) { + dec_bufs->sub_anchor_mv.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->sub_anchor_mv, + buf_size.sz_sub_anchor_mv, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_overlap_xform > 0) { + dec_bufs->overlay_xform.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->overlay_xform, + buf_size.sz_overlap_xform, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_bit_plane3 > 0) { + dec_bufs->bit_plane3.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->bit_plane3, + buf_size.sz_bit_plane3, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_bit_plane2 > 0) { + dec_bufs->bit_plane2.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->bit_plane2, + buf_size.sz_bit_plane2, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_bit_plane1 > 0) { + dec_bufs->bit_plane1.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->bit_plane1, + buf_size.sz_bit_plane1, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_stx_parser > 0) { + dec_bufs->stx_parser.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->stx_parser, + buf_size.sz_stx_parser, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + } + if (buf_size.sz_desc > 0) { + dec_bufs->desc.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&dec_bufs->desc, buf_size.sz_desc, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_free_exit; + else { + if (!res_trk_check_for_sec_session()) { + memset(dec_bufs->desc.align_virtual_addr, + 0, buf_size.sz_desc); + msm_ion_do_cache_op( + ddl_context->video_ion_client, + dec_bufs->desc.alloc_handle, + dec_bufs->desc.virtual_base_addr, + dec_bufs->desc.buffer_size, + ION_IOC_CLEAN_INV_CACHES); + } + } + } + return status; +fail_free_exit: + status = VCD_ERR_ALLOC_FAIL; + 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; + sz_context = DDL_FW_OTHER_CONTEXT_SPACE_SIZE; + 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 * mb_y * 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)); + sz_context = DDL_FW_H264ENC_CONTEXT_SPACE_SIZE; + 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)); + } + 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; + struct ddl_context *ddl_context = ddl->ddl_context; + + 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) { + enc_bufs->mv.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->mv, buf_size.sz_mv, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_col_zero > 0) { + enc_bufs->col_zero.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->col_zero, + buf_size.sz_col_zero, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_md > 0) { + enc_bufs->md.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->md, buf_size.sz_md, + DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_pred > 0) { + enc_bufs->pred.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->pred, + buf_size.sz_pred, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_nbor_info > 0) { + enc_bufs->nbor_info.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->nbor_info, + buf_size.sz_nbor_info, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_acdc_coef > 0) { + enc_bufs->acdc_coef.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->acdc_coef, + buf_size.sz_acdc_coef, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_mb_info > 0) { + enc_bufs->mb_info.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->mb_info, + buf_size.sz_mb_info, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + } + if (buf_size.sz_context > 0) { + enc_bufs->context.mem_type = DDL_MM_MEM; + ptr = ddl_pmem_alloc(&enc_bufs->context, + buf_size.sz_context, DDL_KILO_BYTE(2)); + if (!ptr) + goto fail_enc_free_exit; + else + msm_ion_do_cache_op( + ddl_context->video_ion_client, + enc_bufs->context.alloc_handle, + enc_bufs->context.virtual_base_addr, + enc_bufs->context.buffer_size, + ION_IOC_CLEAN_INV_CACHES); + } + } + return status; +fail_enc_free_exit: + status = VCD_ERR_ALLOC_FAIL; + 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) && + decoder->progressive_only) + need_reconfig = false; + } + return need_reconfig; +} + +void ddl_handle_reconfig(u32 res_change, struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + if ((decoder->cont_mode) && + (res_change == DDL_RESL_CHANGE_DECREASED)) { + DDL_MSG_LOW("%s Resolution decreased, continue decoding\n", + __func__); + vidc_sm_get_min_yc_dpb_sizes( + &ddl->shared_mem[ddl->command_channel], + &decoder->dpb_buf_size.size_y, + &decoder->dpb_buf_size.size_c); + DDL_MSG_LOW(" %s Resolution decreased, size_y = %u" + " size_c = %u\n", + __func__, + decoder->dpb_buf_size.size_y, + decoder->dpb_buf_size.size_c); + ddl_decoder_chroma_dpb_change(ddl); + vidc_sm_set_chroma_addr_change( + &ddl->shared_mem[ddl->command_channel], + true); + ddl_vidc_decode_frame_run(ddl); + } else { + DDL_MSG_LOW("%s Resolution change, start realloc\n", + __func__); + decoder->reconfig_detected = true; + ddl->client_state = DDL_CLIENT_WAIT_FOR_EOS_DONE; + ddl->cmd_state = DDL_CMD_EOS; + vidc_1080p_frame_start_realloc(ddl->instance_id); + } +} + +void ddl_fill_dec_desc_buffer(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct vcd_frame_data *ip_bitstream = &(ddl->input_frame.vcd_frm); + struct ddl_buf_addr *dec_desc_buf = &(decoder->hw_bufs.desc); + + if (ip_bitstream->desc_buf && + ip_bitstream->desc_size < DDL_KILO_BYTE(128)) + memcpy(dec_desc_buf->align_virtual_addr, + ip_bitstream->desc_buf, + ip_bitstream->desc_size); +} + +void ddl_set_vidc_timeout(struct ddl_client_context *ddl) +{ + u32 vidc_time_out = 0; + if (ddl->codec_data.decoder.idr_only_decoding) + vidc_time_out = 2 * DDL_VIDC_1080P_200MHZ_TIMEOUT_VALUE; + DDL_MSG_HIGH("%s Video core time out value = 0x%x", + __func__, vidc_time_out); + vidc_sm_set_video_core_timeout_value( + &ddl->shared_mem[ddl->command_channel], vidc_time_out); +} 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 0000000000000000000000000000000000000000..e2c0a2ae163fe32a4163bfff721652c383364c44 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_interrupt_handler.c @@ -0,0 +1,1975 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 "vcd_res_tracker_api.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, + u32 eos_present); +static void ddl_handle_slice_done_slice_batch(struct ddl_client_context *ddl); +static void ddl_handle_enc_frame_done_slice_mode( + struct ddl_client_context *ddl, u32 eos_present); +static void ddl_handle_enc_skipframe_slice_mode( + struct ddl_client_context *ddl, u32 eos_present); + +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; + u8 *fw_ver; + + 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); + fw_ver = (u8 *)&ddl_context->fw_version; + DDL_MSG_ERROR("fw_version %x:%x:20%x", + fw_ver[1]&0xFF, fw_ver[0]&0xFF, fw_ver[2]&0xFF); + 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) { + ddl_calc_core_proc_time(__func__, + DEC_OP_TIME, ddl); + 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_HIGH("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; + } + ddl_calc_core_proc_time(__func__, ENC_OP_TIME, ddl); + 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 { + ddl_calc_core_proc_time(__func__, DEC_OP_TIME, ddl); + 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 (res_trk_get_disable_fullhd() && + (seq_hdr_info.img_size_x * seq_hdr_info.img_size_y > + 1280 * 720)) { + DDL_MSG_ERROR("FATAL:Resolution greater than 720P HD"); + ddl_client_fatal_cb(ddl); + return process_further; + } + 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)); + ddl_calc_core_proc_time(__func__, DEC_OP_TIME, ddl); + 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 eos_present = false; + + DDL_MSG_MED("ddl_encoder_frame_run_callback\n"); + 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 { + ddl_calc_core_proc_time(__func__, ENC_OP_TIME, ddl); + DDL_MSG_LOW("ENC_FRM_RUN_DONE"); + ddl->cmd_state = DDL_CMD_INVALID; + vidc_1080p_get_encode_frame_info(&encoder->enc_frame_info); + + 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 (VCD_FRAME_FLAG_EOS & ddl->input_frame.vcd_frm.flags) { + DDL_MSG_LOW("%s EOS detected\n", __func__); + eos_present = true; + } + + 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)) { + if (encoder->slice_delivery_info.enable) { + if (encoder->enc_frame_info.enc_frame == + VIDC_1080P_ENCODE_FRAMETYPE_SKIPPED) + ddl_handle_enc_skipframe_slice_mode( + ddl, eos_present); + else + ddl_handle_enc_frame_done_slice_mode( + ddl, eos_present); + } else { + ddl_handle_enc_frame_done(ddl, eos_present); + } + 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 if (eos_present && + encoder->slice_delivery_info.enable) { + ddl_vidc_encode_eos_run(ddl); + } 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; + decoder->reconfig_detected = false; + 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 = + (decoder->reconfig_detected) ?\ + VIDC_1080P_DEC_TYPE_FRAME_DATA :\ + 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); + DDL_MSG_LOW("ddl_encoder_eos_done\n"); + if (ddl == NULL) { + DDL_MSG_ERROR("NO_DDL_CONTEXT"); + } else { + if (!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); + if (!encoder->slice_delivery_info.enable) { + ddl_handle_enc_frame_done(ddl, true); + ddl->cmd_state = DDL_CMD_INVALID; + } + DDL_MSG_LOW("encoder_eos_done"); + 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_slice_done_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id; + u32 return_status = true; + DDL_MSG_LOW("ddl_sliceDoneCallback"); + 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 == NULL) { + DDL_MSG_ERROR("NO_DDL_CONTEXT"); + return_status = false; + } else if (ddl->cmd_state == DDL_CMD_ENCODE_FRAME) { + ddl->cmd_state = DDL_CMD_INVALID; + if (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_FRAME_DONE)) { + ddl_handle_slice_done_slice_batch(ddl); + } else { + DDL_MSG_ERROR("STATE-CRITICAL-ENCFRMRUN : %s\n", + __func__); + ddl_client_fatal_cb(ddl); + } + } else { + DDL_MSG_ERROR("UNKNOWN_SLICEDONE : %s\n", __func__); + } + return return_status; +} + +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_SLICE_DONE_RET: + ddl_slice_done_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; + struct ddl_client_context *ddl; + struct ddl_encoder_data *encoder; + + 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); + DDL_MSG_LOW("%s vidc_1080p_get_risc2host_cmd cmd = %u" + "arg1 = %u arg2 = %u arg3 = %u" + "arg4 = %u\n", + __func__, ddl_hw_response->cmd, + ddl_hw_response->arg1, ddl_hw_response->arg2, + ddl_hw_response->arg3, + ddl_hw_response->arg4); + ddl = ddl_get_current_ddl_client_for_channel_id(ddl_context, + ddl_context->response_cmd_ch_id); + if (ddl) { + encoder = &(ddl->codec_data.encoder); + if (encoder && encoder->slice_delivery_info.enable + && + ((ddl_hw_response->cmd == + VIDC_1080P_RISC2HOST_CMD_SLICE_DONE_RET) + || (ddl_hw_response->cmd == + VIDC_1080P_RISC2HOST_CMD_FRAME_DONE_RET))) { + vidc_sm_set_encoder_slice_batch_int_ctrl( + &ddl->shared_mem[ddl->command_channel], + 1); + } + } + 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_MSG_LOW("%s cmd_err_status = %d\n", __func__, + ddl_context->cmd_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; + 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), + (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); + if (dec_disp_info->display_correct == + VIDC_1080P_DECODE_NOT_CORRECT || + dec_disp_info->display_correct == + VIDC_1080P_DECODE_APPROX_CORRECT) + output_vcd_frm->flags |= + VCD_FRAME_FLAG_DATACORRUPT; + } else { + if (dec_disp_info->decode_correct == + VIDC_1080P_DECODE_NOT_CORRECT || + dec_disp_info->decode_correct == + VIDC_1080P_DECODE_APPROX_CORRECT) + output_vcd_frm->flags |= + VCD_FRAME_FLAG_DATACORRUPT; + } + 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; + ddl_calc_core_proc_time(__func__, DEC_OP_TIME, ddl); + ddl_process_decoder_metadata(ddl); + vidc_sm_get_aspect_ratio_info( + &ddl->shared_mem[ddl->command_channel], + &output_vcd_frm->aspect_ratio_info); + 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, + u32 eos_present) +{ + 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 = eos_present ? + 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); +} + +static void ddl_handle_slice_done_slice_batch(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 = NULL; + u32 bottom_frame_tag; + struct vidc_1080p_enc_slice_batch_out_param *slice_output = NULL; + u32 num_slices_comp = 0; + u32 index = 0; + u32 start_bfr_idx = 0; + u32 actual_idx = 0; + + DDL_MSG_LOW("%s\n", __func__); + vidc_sm_get_num_slices_comp( + &ddl->shared_mem[ddl->command_channel], + &num_slices_comp); + slice_output = (struct vidc_1080p_enc_slice_batch_out_param *) + (encoder->batch_frame.slice_batch_out.align_virtual_addr); + DDL_MSG_LOW(" after get no of slices = %d\n", num_slices_comp); + if (slice_output == NULL) + DDL_MSG_ERROR(" slice_output is NULL\n"); + encoder->slice_delivery_info.num_slices_enc += num_slices_comp; + if (vidc_msg_timing) { + ddl_calc_core_proc_time_cnt(__func__, ENC_SLICE_OP_TIME, + num_slices_comp); + ddl_set_core_start_time(__func__, ENC_SLICE_OP_TIME); + } + DDL_MSG_LOW("%s : Slices Completed %d Total slices %d OutBfrInfo:" + "Cmd %d Size %d\n", + __func__, + num_slices_comp, + encoder->slice_delivery_info.num_slices_enc, + slice_output->cmd_type, + slice_output->output_size); + start_bfr_idx = encoder->batch_frame.out_frm_next_frmindex; + for (index = 0; index < num_slices_comp; index++) { + actual_idx = + slice_output->slice_info[start_bfr_idx+index].stream_buffer_idx; + DDL_MSG_LOW("Slice Info: OutBfrIndex %d SliceSize %d\n", + actual_idx, + slice_output->slice_info[start_bfr_idx+index]. + stream_buffer_size); + output_frame = &( + encoder->batch_frame.output_frame[actual_idx].vcd_frm); + DDL_MSG_LOW("OutBfr: vcd_frm 0x%x frmbfr(virtual) 0x%x" + "frmbfr(physical) 0x%x\n", + &output_frame, + output_frame.virtual_base_addr, + output_frame.physical_base_addr); + 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); + ddl_get_encoded_frame(output_frame, + encoder->codec.codec, + encoder->enc_frame_info.enc_frame); + output_frame->data_len = + slice_output->slice_info[actual_idx].stream_buffer_size; + ddl->output_frame = + encoder->batch_frame.output_frame[actual_idx]; + ddl->output_frame.frm_trans_end = false; + 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); + ddl->input_frame.frm_trans_end = false; + DDL_MSG_LOW("%s Set i/p o/p transactions to false\n", __func__); + } + encoder->batch_frame.out_frm_next_frmindex = start_bfr_idx + index; + ddl->cmd_state = DDL_CMD_ENCODE_FRAME; + vidc_sm_set_encoder_slice_batch_int_ctrl( + &ddl->shared_mem[ddl->command_channel], + 0); +} + +static void ddl_handle_enc_frame_done_slice_mode( + struct ddl_client_context *ddl, u32 eos_present) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct vcd_frame_data *output_frame = NULL; + u32 bottom_frame_tag; + u8 *input_buffer_address = NULL; + struct vidc_1080p_enc_slice_batch_out_param *slice_output = NULL; + u32 num_slices_comp = 0; + u32 index = 0; + u32 start_bfr_idx = 0; + u32 actual_idx = 0; + struct vcd_transc *transc; + + DDL_MSG_LOW("%s\n", __func__); + vidc_sm_get_num_slices_comp( + &ddl->shared_mem[ddl->command_channel], + &num_slices_comp); + slice_output = (struct vidc_1080p_enc_slice_batch_out_param *) + encoder->batch_frame.slice_batch_out.align_virtual_addr; + encoder->slice_delivery_info.num_slices_enc += num_slices_comp; + if (vidc_msg_timing) { + ddl_calc_core_proc_time_cnt(__func__, ENC_OP_TIME, + num_slices_comp); + } + DDL_MSG_LOW("%s Slices Completed %d Total slices done = %d" + " OutBfrInfo: Cmd %d Size %d", + __func__, + num_slices_comp, + encoder->slice_delivery_info.num_slices_enc, + slice_output->cmd_type, + slice_output->output_size); + start_bfr_idx = encoder->batch_frame.out_frm_next_frmindex; + if ((encoder->slice_delivery_info.num_slices_enc % + encoder->batch_frame.num_output_frames) != 0) { + DDL_MSG_ERROR("ERROR : %d %d\n", + encoder->slice_delivery_info.num_slices_enc, + encoder->batch_frame.num_output_frames); + } + for (index = 0; index < num_slices_comp; index++) { + actual_idx = + slice_output->slice_info[start_bfr_idx+index]. \ + stream_buffer_idx; + DDL_MSG_LOW("Slice Info: OutBfrIndex %d SliceSize %d", + actual_idx, + slice_output->slice_info[start_bfr_idx+index]. \ + stream_buffer_size, 0); + output_frame = + &(encoder->batch_frame.output_frame[actual_idx].vcd_frm); + DDL_MSG_LOW("OutBfr: vcd_frm 0x%x frmbfr(virtual) 0x%x" + "frmbfr(physical) 0x%x", + &output_frame, + output_frame.virtual_base_addr, + output_frame.physical_base_addr); + 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); + ddl_get_encoded_frame(output_frame, + encoder->codec.codec, + encoder->enc_frame_info.enc_frame); + output_frame->data_len = + slice_output->slice_info[actual_idx].stream_buffer_size; + ddl->output_frame = + encoder->batch_frame.output_frame[actual_idx]; + DDL_MSG_LOW(" %s actual_idx = %d" + "encoder->batch_frame.num_output_frames = %d\n", __func__, + actual_idx, encoder->batch_frame.num_output_frames); + if (encoder->batch_frame.num_output_frames == (actual_idx+1)) { + output_frame->flags |= VCD_FRAME_FLAG_ENDOFFRAME; + ddl_vidc_encode_dynamic_property(ddl, false); + ddl->input_frame.frm_trans_end = true; + DDL_MSG_LOW("%s End of frame detected\n", __func__); + 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); + transc = (struct vcd_transc *)(ddl->client_data); + if (eos_present) + ddl->output_frame.frm_trans_end = false; + else + ddl->output_frame.frm_trans_end = true; + } + DDL_MSG_LOW("%s Before output done cb\n", __func__); + transc = (struct vcd_transc *)(ddl->client_data); + ddl->output_frame.vcd_frm = *output_frame; + 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 (encoder->batch_frame.num_output_frames == (actual_idx+1)) { + DDL_MSG_LOW("%s sending input done\n", __func__); + 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 ddl_handle_enc_skipframe_slice_mode( + struct ddl_client_context *ddl, u32 eos_present) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct vcd_frame_data *output_frame = NULL; + u32 bottom_frame_tag; + u8 *input_buffer_address = NULL; + u32 index = 0; + DDL_MSG_HIGH("ddl_handle_enc_skipframe_slice_mode: frame skipped"); + vidc_sm_set_encoder_slice_batch_int_ctrl( + &ddl->shared_mem[ddl->command_channel], + 1); + for (index = 0; + index < encoder->batch_frame.num_output_frames; + index++) { + output_frame = + &(encoder->batch_frame.output_frame[index].vcd_frm); + DDL_MSG_MED("output buffer: vcd_frm = 0x%x " + "frmbfr(virtual) = 0x%x frmbfr(physical) = 0x%x", + (u32)output_frame, (u32)output_frame->virtual, + (u32)output_frame->physical); + vidc_sm_get_frame_tags( + &ddl->shared_mem[ddl->command_channel], + &output_frame->ip_frm_tag, + &bottom_frame_tag); + ddl_get_encoded_frame( + output_frame, + encoder->codec.codec, + encoder->enc_frame_info.enc_frame); + output_frame->data_len = 0; + ddl->output_frame.frm_trans_end = false; + if (encoder->batch_frame.num_output_frames == + (index + 1)) { + DDL_MSG_MED("last output bfr for skip frame, set EOF"); + output_frame->flags |= VCD_FRAME_FLAG_ENDOFFRAME; + ddl_vidc_encode_dynamic_property(ddl, false); + if (eos_present) + ddl->output_frame.frm_trans_end = false; + else + ddl->output_frame.frm_trans_end = true; + } + ddl->output_frame.vcd_frm = *output_frame; + 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 (encoder->batch_frame.num_output_frames == + (index + 1)) { + ddl->input_frame.frm_trans_end = false; + input_buffer_address = + ddl_context->dram_base_a.physical_base_addr + + (encoder->enc_frame_info.enc_luma_address); + ddl_get_input_frame_from_pool(ddl, + input_buffer_address); + DDL_MSG_MED("InpBfr: vcd_frm 0x%x frmbfr(virtual) 0x%x" + " frmbfr(physical) 0x%x", + (u32)&(ddl->input_frame.vcd_frm), + (u32)ddl->input_frame.vcd_frm.virtual, + (u32)ddl->input_frame.vcd_frm.physical); + 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); + } + } +} 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 0000000000000000000000000000000000000000..267e924ab92032ee7b81202aebd6da3fe5202dd5 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.c @@ -0,0 +1,508 @@ +/* Copyright (c) 2010, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; + encoder->output_buf_req.sz = + DDL_ALIGN(encoder->output_buf_req.sz, DDL_KILO_BYTE(4)); +} + +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; + DDL_MSG_LOW("processing metadata for encoder"); + 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 0000000000000000000000000000000000000000..c63b6a9edc0506391fa8d1c57d3e49f28f8ed0d8 --- /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 0000000000000000000000000000000000000000..3827bc1e471399833681d408ef27de193a1bc538 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_properties.c @@ -0,0 +1,2113 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_res_tracker_api.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); + } + DDL_MSG_LOW("set VCD_I_FRAME_SIZE width = %d" + " height = %d\n", + frame_size->width, frame_size->height); + 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->dev_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; + case VCD_I_DISABLE_DMX: + { + int disable_dmx_allowed = 0; + DDL_MSG_LOW("Set property VCD_I_DISABLE_DMX\n"); + if (res_trk_get_disable_dmx() && + ((decoder->codec.codec == VCD_CODEC_H264) || + (decoder->codec.codec == VCD_CODEC_VC1) || + (decoder->codec.codec == VCD_CODEC_VC1_RCV))) + disable_dmx_allowed = 1; + + if (sizeof(u32) == property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + disable_dmx_allowed) { + decoder->dmx_disable = *(u32 *)property_value; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_REQ_PERF_LEVEL: + 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; + DDL_MSG_HIGH("VCD_I_MULTI_SLICE eMSliceSel %d nMSliceSize %d" + "Tot#of MB %d encoder->frame_size.width = %d" + "encoder->frame_size.height = %d", + (int)multi_slice->m_slice_sel, + multi_slice->m_slice_size, + DDL_NO_OF_MB(encoder->frame_size.width, + encoder->frame_size.height), + encoder->frame_size.width, + encoder->frame_size.height); + 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)); + DDL_MSG_LOW("%s encoder->client_output_buf_req.sz" + " = %d\n", __func__, + encoder->client_output_buf_req.sz); + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_RECON_BUFFERS: + { + int index, index_hw_bufs = -1; + 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) { + index_hw_bufs = index; + break; + } else + continue; + } + if (index_hw_bufs == -1) { + DDL_MSG_HIGH("ERROR: value of index_hw_bufs"); + vcd_status = VCD_ERR_ILLEGAL_PARM; + } else { + if (property_hdr->sz == sizeof(struct + vcd_property_enc_recon_buffer)) { + encoder->hw_bufs.dpb_y[index_hw_bufs]. + align_physical_addr = + recon_buffers->dev_addr; + encoder->hw_bufs.dpb_y[index_hw_bufs]. + align_virtual_addr = + recon_buffers->kernel_virtual_addr; + encoder->hw_bufs.dpb_y[index_hw_bufs]. + buffer_size = recon_buffers->buffer_size; + encoder->hw_bufs.dpb_c[index_hw_bufs]. + align_physical_addr = + recon_buffers->dev_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_hw_bufs]. + 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_hw_bufs]. + align_virtual_addr, + encoder->hw_bufs.dpb_y[index_hw_bufs]. + align_physical_addr, + encoder->hw_bufs.dpb_c[index_hw_bufs]. + align_virtual_addr, + encoder->hw_bufs.dpb_c[index_hw_bufs]. + 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_LOW("Meta Data Interface is Requested"); + if (!res_trk_check_for_sec_session()) { + if (!encoder->slice_delivery_info.enable) { + vcd_status = ddl_set_metadata_params(ddl, + property_hdr, property_value); + } else { + DDL_MSG_ERROR("Ignoring meta data settting in " + "slice mode: %s\n", __func__); + vcd_status = VCD_S_SUCCESS; + } + } else { + DDL_MSG_ERROR("Meta Data Interface is not " + "supported in secure session"); + vcd_status = VCD_ERR_ILLEGAL_OP; + } + break; + case VCD_I_META_BUFFER_MODE: + vcd_status = VCD_S_SUCCESS; + break; + case VCD_I_ENABLE_SPS_PPS_FOR_IDR: + { + struct vcd_property_sps_pps_for_idr_enable *sps_pps = + (struct vcd_property_sps_pps_for_idr_enable *) property_value; + + if ((sizeof(struct vcd_property_sps_pps_for_idr_enable)) == + property_hdr->sz) { + DDL_MSG_LOW("SPS PPS generation for IDR Encode " + "is Requested"); + encoder->sps_pps.sps_pps_for_idr_enable_flag = + sps_pps->sps_pps_for_idr_enable_flag; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_SLICE_DELIVERY_MODE: + { + size_t output_buf_size; + u32 num_mb, num_slices; + struct vcd_property_hdr slice_property_hdr; + struct vcd_property_meta_data_enable slice_meta_data; + DDL_MSG_HIGH("Set property VCD_I_SLICE_DELIVERY_MODE\n"); + if (sizeof(u32) == property_hdr->sz && + encoder->codec.codec == VCD_CODEC_H264 && + encoder->multi_slice.m_slice_sel + == VCD_MSLICE_BY_MB_COUNT && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + encoder->slice_delivery_info.enable + = *(u32 *)property_value; + DDL_MSG_HIGH("set encoder->slice_delivery_mode = %u\n", + encoder->slice_delivery_info.enable); + output_buf_size = + encoder->client_output_buf_req.sz; + num_mb = DDL_NO_OF_MB(encoder->frame_size.width, + encoder->frame_size.height); + num_slices = num_mb/ + encoder->multi_slice.m_slice_size; + num_slices = ((num_mb - num_slices * + encoder->multi_slice.m_slice_size) > 0) + ? (num_slices + 1) : num_slices; + encoder->slice_delivery_info.num_slices = + num_slices; + if (num_slices <= DDL_MAX_NUM_BFRS_FOR_SLICE_BATCH) { + DDL_MSG_HIGH("%s: currently slice info " + "metadata is not supported when slice " + "delivery mode is enabled. hence " + "disabling slice info metadata.\n", + __func__); + slice_property_hdr.prop_id = + VCD_I_METADATA_ENABLE; + slice_property_hdr.sz = + sizeof(struct \ + vcd_property_meta_data_enable); + ddl_get_metadata_params(ddl, + &slice_property_hdr, + &slice_meta_data); + slice_meta_data.meta_data_enable_flag + &= ~VCD_METADATA_ENC_SLICE; + ddl_set_metadata_params(ddl, + &slice_property_hdr, + &slice_meta_data); + encoder->client_output_buf_req.min_count = + ((DDL_ENC_SLICE_BATCH_FACTOR * num_slices + 2) + > DDL_MAX_BUFFER_COUNT) + ? DDL_MAX_BUFFER_COUNT : + (DDL_ENC_SLICE_BATCH_FACTOR * num_slices + 2); + output_buf_size = + encoder->client_output_buf_req.sz/num_slices; + encoder->client_output_buf_req.sz = + DDL_ALIGN(output_buf_size, DDL_KILO_BYTE(4)); + encoder->output_buf_req = + encoder->client_output_buf_req; + DDL_MSG_HIGH("%s num_mb = %u num_slices = %u " + "output_buf_count = %u " + "output_buf_size = %u aligned size = %u\n", + __func__, num_mb, num_slices, + encoder->client_output_buf_req.min_count, + output_buf_size, + encoder->client_output_buf_req.sz); + vcd_status = VCD_S_SUCCESS; + } + } + break; + } + case VCD_REQ_PERF_LEVEL: + 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; + case VCD_I_DISABLE_DMX_SUPPORT: + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = res_trk_get_disable_dmx(); + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_DISABLE_DMX: + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = decoder->dmx_disable; + 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; + DDL_MSG_LOW("%s encoder->client_output_buf_req = %d\n", + __func__, + encoder->client_output_buf_req.sz); + 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; + case VCD_I_ENABLE_SPS_PPS_FOR_IDR: + if (sizeof(struct vcd_property_sps_pps_for_idr_enable) == + property_hdr->sz) { + *(struct vcd_property_sps_pps_for_idr_enable *) + property_value = encoder->sps_pps; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_SLICE_DELIVERY_MODE: + if (sizeof(struct vcd_property_slice_delivery_info) == + property_hdr->sz) { + *(struct vcd_property_slice_delivery_info *) + property_value = encoder->slice_delivery_info; + 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; + } + } + break; + 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; + decoder->reconfig_detected = false; + decoder->dmx_disable = false; + 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; + if (encoder->codec.codec == VCD_CODEC_MPEG4) + encoder->closed_gop = true; + ddl_set_default_metadata_flag(ddl); + ddl_set_default_encoder_buffer_req(encoder); + encoder->slice_delivery_info.enable = 0; + encoder->slice_delivery_info.num_slices = 0; + encoder->slice_delivery_info.num_slices_enc = 0; +} + +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; + DDL_MSG_LOW("%s encoder->client_output_buf_req.sz = %d\n", + __func__, encoder->client_output_buf_req.sz); +} + +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; + if (estimate) { + if (!decoder->cont_mode) + min_dpb = ddl_decoder_min_num_dpb(decoder); + else + min_dpb = res_trk_get_min_dpb_count(); + 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 { + 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; + if ((decoder->buf_format.buffer_format == + VCD_BUFFER_FORMAT_TILE_4x2) && + (frame_size->height < MDP_MIN_TILE_HEIGHT)) { + frame_size->height = MDP_MIN_TILE_HEIGHT; + ddl_calculate_stride(frame_size, + !decoder->progressive_only); + y_cb_cr_size = ddl_get_yuv_buffer_size( + frame_size, + &decoder->buf_format, + (!decoder->progressive_only), + decoder->hdr.decoding, NULL); + } else + y_cb_cr_size = decoder->y_cb_cr_size; + } + memset(output_buf_req, 0, + sizeof(struct vcd_buffer_requirement)); + if (!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 * 2); + 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"); + DDL_MSG_ERROR("codec_buf_req: min_cnt=%d, mx_cnt=%d, " + "align=%d, sz=%d\n", original_buf_req->min_count, + original_buf_req->max_count, original_buf_req->align, + original_buf_req->sz); + DDL_MSG_ERROR("client_buffs: actual_count=%d, align=%d, " + "sz=%d\n", req_buf_req->actual_count, + req_buf_req->align, req_buf_req->sz); + } + 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); + encoder->sps_pps.sps_pps_for_idr_enable_flag = false; + } +} 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 0000000000000000000000000000000000000000..878db622caebb7af2b53d9abe960ebafba55d7bf --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.c @@ -0,0 +1,876 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_H263_CPCFC_ENABLE_BMSK 0x80 +#define VIDC_SM_ENC_EXT_CTRL_H263_CPCFC_ENABLE_SHFT 7 +#define VIDC_SM_ENC_EXT_CTRL_SPS_PPS_CONTROL_BMSK 0X100 +#define VIDC_SM_ENC_EXT_CTRL_SPS_PPS_CONTROL_SHFT 8 +#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_BATCH_INPUT_ADDR 0x00a4 +#define VIDC_SM_BATCH_INPUT_ADDR_VALUE_BMSK 0xffffffff +#define VIDC_SM_BATCH_INPUT_ADDRL_VALUE_SHFT 0 +#define VIDC_SM_BATCH_OUTPUT_ADDR 0x00a8 +#define VIDC_SM_BATCH_OUTPUT_ADDR_VALUE_BMSK 0xffffffff +#define VIDC_SM_BATCH_OUTPUT_ADDR_VALUE_SHFT 0 +#define VIDC_SM_BATCH_OUTPUT_SIZE_ADDR 0x00ac +#define VIDC_SM_BATCH_OUTPUT_SIZE_VALUE_BMSK 0xffffffff +#define VIDC_SM_BATCH_OUTPUT_SIZE_VALUE_SHFT 0 +#define VIDC_SM_ENC_SLICE_BATCH_INT_CTRL_ADDR 0x01c8 +#define VIDC_SM_ENC_SLICE_BATCH_INT_CTRL_VALUE_BMSK 0x1 +#define VIDC_SM_ENC_SLICE_BATCH_INT_CTRL_VALUE_SHFT 0 +#define VIDC_SM_ENC_NUM_OF_SLICE_ADDR 0x01cc +#define VIDC_SM_ENC_NUM_OF_SLICE_VALUE_BMSK 0xffffffff +#define VIDC_SM_ENC_NUM_OF_SLICE_VALUE_SHFT 0 +#define VIDC_SM_ENC_NUM_OF_SLICE_COMP_ADDR 0x01d0 +#define VIDC_SM_ENC_NUM_OF_SLICE_COMP_VALUE_BMSK 0xffffffff +#define VIDC_SM_ENC_NUM_OF_SLICE_COMP_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_ASPECT_RATIO_INFO_ADDR 0x00c8 +#define VIDC_SM_MPEG4_ASPECT_RATIO_INFO_BMSK 0xf +#define VIDC_SM_MPEG4_ASPECT_RATIO_INFO_SHFT 0x0 +#define VIDC_SM_EXTENDED_PAR_ADDR 0x00cc +#define VIDC_SM_EXTENDED_PAR_WIDTH_BMSK 0xffff0000 +#define VIDC_SM_EXTENDED_PAR_WIDTH_SHFT 0xf +#define VIDC_SM_EXTENDED_PAR_HEIGHT_BMSK 0x0000ffff +#define VIDC_SM_EXTENDED_PAR_HEIGHT_SHFT 0x0 + +#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 VIDC_SM_ERROR_CONCEALMENT_CONFIG_ADDR 0x0154 + +#define VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTER_SLICE_BMSK 0x0c +#define VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTER_SLICE_SHFT 2 +#define VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTRA_SLICE_BMSK 0X02 +#define VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTRA_SLICE_SHFT 1 +#define VIDC_SM_ERROR_CONCEALMENT_CONFIG_CONCEAL_ENABLE_BMSK 0x01 +#define VIDC_SM_ERROR_CONCEALMENT_CONFIG_CONCEAL_ENABLE_SHFT 0 + +#define VIDC_SM_SEI_ENABLE_ADDR 0x0180 +#define VIDC_SM_SEI_ENABLE_RECOVERY_POINT_SEI_BMSK 0x00000001 +#define VIDC_SM_SEI_ENABLE_RECOVERY_POINT_SEI_SHFT 0 + +#define VIDC_SM_NUM_STUFF_BYTES_CONSUME_ADDR 0X01ac + +#define VIDC_SM_TIMEOUT_VALUE_ADDR 0x0158 +#define VIDC_SM_TIMEOUT_VALUE_BMSK 0xffffffff +#define VIDC_SM_TIMEOUT_VALUE_SHFT 0 + +#define VIDC_SM_ENC_EXT_CTRL_CLOSED_GOP_ENABLE_BMSK 0x40 +#define VIDC_SM_ENC_EXT_CTRL_CLOSED_GOP_ENABLE_SHFT 6 + +#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 cpcfc_enable, + u32 sps_pps_control, u32 closed_gop_enable) +{ + 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) | + VIDC_SETFIELD((cpcfc_enable) ? 1 : 0, + VIDC_SM_ENC_EXT_CTRL_H263_CPCFC_ENABLE_SHFT, + VIDC_SM_ENC_EXT_CTRL_H263_CPCFC_ENABLE_BMSK) | + VIDC_SETFIELD((sps_pps_control) ? 1 : 0, + VIDC_SM_ENC_EXT_CTRL_SPS_PPS_CONTROL_SHFT, + VIDC_SM_ENC_EXT_CTRL_SPS_PPS_CONTROL_BMSK) | + VIDC_SETFIELD(closed_gop_enable, + VIDC_SM_ENC_EXT_CTRL_CLOSED_GOP_ENABLE_SHFT, + VIDC_SM_ENC_EXT_CTRL_CLOSED_GOP_ENABLE_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); + +} + +void vidc_sm_set_mpeg4_profile_override(struct ddl_buf_addr *shared_mem, + enum vidc_sm_mpeg4_profileinfo profile_info) +{ + u32 profile_enforce = 0; + if (shared_mem != NULL) { + profile_enforce = 1; + switch (profile_info) { + case VIDC_SM_PROFILE_INFO_ASP: + profile_enforce |= 4; + break; + case VIDC_SM_PROFILE_INFO_SP: + profile_enforce |= 2; + break; + case VIDC_SM_PROFILE_INFO_DISABLE: + default: + profile_enforce = 0; + break; + } + DDL_MEM_WRITE_32(shared_mem, 0x15c, profile_enforce); + } +} +void vidc_sm_set_decoder_sei_enable(struct ddl_buf_addr *shared_mem, + u32 sei_enable) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_SEI_ENABLE_ADDR, sei_enable); +} + +void vidc_sm_get_decoder_sei_enable(struct ddl_buf_addr *shared_mem, + u32 *sei_enable) +{ + *sei_enable = DDL_MEM_READ_32(shared_mem, VIDC_SM_SEI_ENABLE_ADDR); +} + +void vidc_sm_set_error_concealment_config(struct ddl_buf_addr *shared_mem, + u32 inter_slice, u32 intra_slice, u32 conceal_config_enable) +{ + u32 error_conceal_config = 0; + + error_conceal_config = VIDC_SETFIELD(inter_slice, + VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTER_SLICE_SHFT, + VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTER_SLICE_BMSK); + + error_conceal_config |= VIDC_SETFIELD(intra_slice, + VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTRA_SLICE_SHFT, + VIDC_SM_ERROR_CONCEALMENT_CONFIG_INTRA_SLICE_BMSK); + + error_conceal_config |= VIDC_SETFIELD(conceal_config_enable, + VIDC_SM_ERROR_CONCEALMENT_CONFIG_CONCEAL_ENABLE_SHFT, + VIDC_SM_ERROR_CONCEALMENT_CONFIG_CONCEAL_ENABLE_BMSK); + + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ERROR_CONCEALMENT_CONFIG_ADDR, + error_conceal_config); +} + +void vidc_sm_set_decoder_stuff_bytes_consumption( + struct ddl_buf_addr *shared_mem, + enum vidc_sm_num_stuff_bytes_consume_info consume_info) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_NUM_STUFF_BYTES_CONSUME_ADDR, + consume_info); +} + +void vidc_sm_get_aspect_ratio_info(struct ddl_buf_addr *shared_mem, + struct vcd_aspect_ratio *aspect_ratio_info) +{ + u32 extended_par_info = 0; + aspect_ratio_info->aspect_ratio = DDL_MEM_READ_32(shared_mem, + VIDC_SM_ASPECT_RATIO_INFO_ADDR); + + if (aspect_ratio_info->aspect_ratio == 0x0f) { + extended_par_info = DDL_MEM_READ_32(shared_mem, + VIDC_SM_EXTENDED_PAR_ADDR); + aspect_ratio_info->extended_par_width = + VIDC_GETFIELD(extended_par_info, + VIDC_SM_EXTENDED_PAR_WIDTH_BMSK, + VIDC_SM_EXTENDED_PAR_WIDTH_SHFT); + aspect_ratio_info->extended_par_height = + VIDC_GETFIELD(extended_par_info, + VIDC_SM_EXTENDED_PAR_HEIGHT_BMSK, + VIDC_SM_EXTENDED_PAR_HEIGHT_SHFT); + } +} + +void vidc_sm_set_encoder_slice_batch_int_ctrl(struct ddl_buf_addr *shared_mem, + u32 slice_batch_int_enable) +{ + u32 slice_batch_int_ctrl = VIDC_SETFIELD((slice_batch_int_enable) ? + 1 : 0, + VIDC_SM_ENC_EXT_CTRL_HEC_ENABLE_SHFT, + VIDC_SM_ENC_EXT_CTRL_HEC_ENABLE_BMSK); + DDL_MEM_WRITE_32(shared_mem, + VIDC_SM_ENC_SLICE_BATCH_INT_CTRL_ADDR, + slice_batch_int_ctrl); +} + +void vidc_sm_get_num_slices_comp(struct ddl_buf_addr *shared_mem, + u32 *num_slices_comp) +{ + *num_slices_comp = DDL_MEM_READ_32(shared_mem, + VIDC_SM_ENC_NUM_OF_SLICE_COMP_ADDR); +} + +void vidc_sm_set_encoder_batch_config(struct ddl_buf_addr *shared_mem, + u32 num_slices, + u32 input_addr, u32 output_addr, + u32 output_buffer_size) +{ + DDL_MEM_WRITE_32(shared_mem, + VIDC_SM_ENC_NUM_OF_SLICE_ADDR, + num_slices); + DDL_MEM_WRITE_32(shared_mem, + VIDC_SM_BATCH_INPUT_ADDR, + input_addr); + DDL_MEM_WRITE_32(shared_mem, + VIDC_SM_BATCH_OUTPUT_ADDR, + output_addr); + DDL_MEM_WRITE_32(shared_mem, + VIDC_SM_BATCH_OUTPUT_SIZE_ADDR, + output_buffer_size); +} + +void vidc_sm_get_encoder_batch_output_size(struct ddl_buf_addr *shared_mem, + u32 *output_buffer_size) +{ + *output_buffer_size = DDL_MEM_READ_32(shared_mem, + VIDC_SM_BATCH_OUTPUT_SIZE_ADDR); +} + +void vidc_sm_set_video_core_timeout_value(struct ddl_buf_addr *shared_mem, + u32 timeout) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_TIMEOUT_VALUE_ADDR, + timeout); +} + 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 0000000000000000000000000000000000000000..6cd75595c56f536f2a7830787addba1d070d8e33 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.h @@ -0,0 +1,196 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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) + +#define VIDC_SM_RECOVERY_POINT_SEI (1) +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; +}; + +enum vidc_sm_mpeg4_profileinfo { + VIDC_SM_PROFILE_INFO_DISABLE = 0, + VIDC_SM_PROFILE_INFO_SP = 1, + VIDC_SM_PROFILE_INFO_ASP = 2, + VIDC_SM_PROFILE_INFO_MAX = 0x7fffffff +}; + +enum vidc_sm_num_stuff_bytes_consume_info { + VIDC_SM_NUM_STUFF_BYTES_CONSUME_ALL = 0x0, + VIDC_SM_NUM_STUFF_BYTES_CONSUME_NONE = 0xffffffff +}; + +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, u32 cpcfc_enable, u32 sps_pps_control, + u32 closed_gop_enable); +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); +void vidc_sm_set_mpeg4_profile_override(struct ddl_buf_addr *shared_mem, + enum vidc_sm_mpeg4_profileinfo profile_info); +void vidc_sm_set_decoder_sei_enable(struct ddl_buf_addr *shared_mem, + u32 sei_enable); +void vidc_sm_get_decoder_sei_enable(struct ddl_buf_addr *shared_mem, + u32 *sei_enable); +void vidc_sm_set_error_concealment_config(struct ddl_buf_addr *shared_mem, + u32 inter_slice, u32 intra_slice, u32 conceal_config_enable); +void vidc_sm_set_decoder_stuff_bytes_consumption( + struct ddl_buf_addr *shared_mem, + enum vidc_sm_num_stuff_bytes_consume_info consume_info); +void vidc_sm_get_aspect_ratio_info(struct ddl_buf_addr *shared_mem, + struct vcd_aspect_ratio *aspect_ratio_info); +void vidc_sm_set_encoder_slice_batch_int_ctrl(struct ddl_buf_addr *shared_mem, + u32 slice_batch_int_enable); +void vidc_sm_get_num_slices_comp(struct ddl_buf_addr *shared_mem, + u32 *num_slices_comp); +void vidc_sm_set_encoder_batch_config(struct ddl_buf_addr *shared_mem, + u32 num_slices, + u32 input_addr, u32 output_addr, + u32 output_buffer_size); +void vidc_sm_get_encoder_batch_output_size(struct ddl_buf_addr *shared_mem, + u32 *output_buffer_size); +void vidc_sm_set_video_core_timeout_value(struct ddl_buf_addr *shared_mem, + u32 timeout); +#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 0000000000000000000000000000000000000000..260cd728b80d2a68dbf5adffe425ae6728217ef6 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.c @@ -0,0 +1,518 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include "vcd_ddl_utils.h" +#include "vcd_ddl.h" +#include "vcd_res_tracker_api.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) +static unsigned int vidc_mmu_subsystem[] = { + MSM_SUBSYSTEM_VIDEO, MSM_SUBSYSTEM_VIDEO_FWARE}; + +#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 ; + u32 index = 0; + struct ddl_context *ddl_context; + struct msm_mapped_buffer *mapped_buffer = NULL; + unsigned long iova = 0; + unsigned long buffer_size = 0; + unsigned long *kernel_vaddr = NULL; + unsigned long ionflag = 0; + unsigned long flags = 0; + int ret = 0; + ion_phys_addr_t phyaddr = 0; + size_t len = 0; + int rc = 0; + DBG_PMEM("\n%s() IN: Requested alloc size(%u)", __func__, (u32)sz); + if (!addr) { + DDL_MSG_ERROR("\n%s() Invalid Parameters", __func__); + goto bail_out; + } + ddl_context = ddl_get_context(); + res_trk_set_mem_type(addr->mem_type); + alloc_size = (sz + alignment); + if (res_trk_get_enable_ion()) { + if (!ddl_context->video_ion_client) + ddl_context->video_ion_client = + res_trk_get_ion_client(); + if (!ddl_context->video_ion_client) { + DDL_MSG_ERROR("%s() :DDL ION Client Invalid handle\n", + __func__); + goto bail_out; + } + alloc_size = (alloc_size+4095) & ~4095; + addr->alloc_handle = ion_alloc( + ddl_context->video_ion_client, alloc_size, SZ_4K, + res_trk_get_mem_type()); + if (IS_ERR_OR_NULL(addr->alloc_handle)) { + DDL_MSG_ERROR("%s() :DDL ION alloc failed\n", + __func__); + goto bail_out; + } + if (res_trk_check_for_sec_session() || + addr->mem_type == DDL_FW_MEM) + ionflag = UNCACHED; + else + ionflag = CACHED; + kernel_vaddr = (unsigned long *) ion_map_kernel( + ddl_context->video_ion_client, + addr->alloc_handle, ionflag); + if (IS_ERR_OR_NULL(kernel_vaddr)) { + DDL_MSG_ERROR("%s() :DDL ION map failed\n", + __func__); + goto free_ion_alloc; + } + addr->virtual_base_addr = (u8 *) kernel_vaddr; + if (res_trk_check_for_sec_session()) { + rc = ion_phys(ddl_context->video_ion_client, + addr->alloc_handle, &phyaddr, + &len); + if (rc || !phyaddr) { + DDL_MSG_ERROR( + "%s():DDL ION client physical failed\n", + __func__); + goto unmap_ion_alloc; + } + addr->alloced_phys_addr = phyaddr; + } else { + ret = ion_map_iommu(ddl_context->video_ion_client, + addr->alloc_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL, + SZ_4K, + 0, + &iova, + &buffer_size, + UNCACHED, 0); + if (ret) { + DDL_MSG_ERROR( + "%s():DDL ION ion map iommu failed\n", + __func__); + goto unmap_ion_alloc; + } + addr->alloced_phys_addr = (phys_addr_t) iova; + } + if (!addr->alloced_phys_addr) { + DDL_MSG_ERROR("%s():DDL ION client physical failed\n", + __func__); + goto unmap_ion_alloc; + } + addr->mapped_buffer = NULL; + addr->physical_base_addr = (u8 *) addr->alloced_phys_addr; + 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 = alloc_size; + } else { + addr->alloced_phys_addr = (phys_addr_t) + allocate_contiguous_memory_nomap(alloc_size, + res_trk_get_mem_type(), SZ_4K); + if (!addr->alloced_phys_addr) { + DDL_MSG_ERROR("%s() : acm alloc failed (%d)\n", + __func__, alloc_size); + goto bail_out; + } + flags = MSM_SUBSYSTEM_MAP_IOVA | MSM_SUBSYSTEM_MAP_KADDR; + if (alignment == DDL_KILO_BYTE(128)) + index = 1; + else if (alignment > SZ_4K) + flags |= MSM_SUBSYSTEM_ALIGN_IOVA_8K; + + addr->mapped_buffer = + msm_subsystem_map_buffer((unsigned long)addr->alloced_phys_addr, + alloc_size, flags, &vidc_mmu_subsystem[index], + sizeof(vidc_mmu_subsystem[index])/sizeof(unsigned int)); + if (IS_ERR(addr->mapped_buffer)) { + pr_err(" %s() buffer map failed", __func__); + goto free_acm_alloc; + } + mapped_buffer = addr->mapped_buffer; + if (!mapped_buffer->vaddr || !mapped_buffer->iova[0]) { + pr_err("%s() map buffers failed\n", __func__); + goto free_map_buffers; + } + addr->physical_base_addr = (u8 *)mapped_buffer->iova[0]; + addr->virtual_base_addr = mapped_buffer->vaddr; + 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; + } + return addr->virtual_base_addr; +free_map_buffers: + msm_subsystem_unmap_buffer(addr->mapped_buffer); + addr->mapped_buffer = NULL; +free_acm_alloc: + free_contiguous_memory_by_paddr( + (unsigned long)addr->alloced_phys_addr); + addr->alloced_phys_addr = (phys_addr_t)NULL; + return NULL; +unmap_ion_alloc: + ion_unmap_kernel(ddl_context->video_ion_client, + addr->alloc_handle); + addr->virtual_base_addr = NULL; + addr->alloced_phys_addr = (phys_addr_t)NULL; +free_ion_alloc: + ion_free(ddl_context->video_ion_client, + addr->alloc_handle); + addr->alloc_handle = NULL; +bail_out: + return NULL; +} + +void ddl_pmem_free(struct ddl_buf_addr *addr) +{ + struct ddl_context *ddl_context; + ddl_context = ddl_get_context(); + if (!addr) { + pr_err("%s() invalid args\n", __func__); + return; + } + if (ddl_context->video_ion_client) { + if (!IS_ERR_OR_NULL(addr->alloc_handle)) { + ion_unmap_kernel(ddl_context->video_ion_client, + addr->alloc_handle); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu(ddl_context->video_ion_client, + addr->alloc_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + } + ion_free(ddl_context->video_ion_client, + addr->alloc_handle); + } + } else { + if (addr->mapped_buffer) + msm_subsystem_unmap_buffer(addr->mapped_buffer); + if (addr->alloced_phys_addr) + free_contiguous_memory_by_paddr( + (unsigned long)addr->alloced_phys_addr); + } + memset(addr, 0, sizeof(struct ddl_buf_addr)); +} + +#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 + +u32 ddl_fw_init(struct ddl_buf_addr *dram_base) +{ + u8 *dest_addr; + dest_addr = DDL_GET_ALIGNED_VITUAL(*dram_base); + DDL_MSG_LOW("FW Addr / FW Size : %x/%d", (u32)vidc_video_codec_fw, + vidc_video_codec_fw_size); + if (res_trk_check_for_sec_session() && res_trk_is_cp_enabled()) { + if (res_trk_enable_footswitch()) { + pr_err("Failed to enable footswitch"); + return false; + } + if (res_trk_enable_iommu_clocks()) { + res_trk_disable_footswitch(); + pr_err("Failed to enable iommu clocks\n"); + return false; + } + dram_base->pil_cookie = pil_get("vidc"); + if (res_trk_disable_iommu_clocks()) + pr_err("Failed to disable iommu clocks\n"); + if (IS_ERR_OR_NULL(dram_base->pil_cookie)) { + res_trk_disable_footswitch(); + pr_err("pil_get failed\n"); + return false; + } + } else { + if (vidc_video_codec_fw_size > dram_base->buffer_size || + !vidc_video_codec_fw) + return false; + memcpy(dest_addr, vidc_video_codec_fw, + vidc_video_codec_fw_size); + } + return true; +} + +void ddl_fw_release(struct ddl_buf_addr *dram_base) +{ + void *cookie = dram_base->pil_cookie; + if (res_trk_is_cp_enabled() && + res_trk_check_for_sec_session()) { + res_trk_close_secure_session(); + if (IS_ERR_OR_NULL(cookie)) { + pr_err("Invalid params"); + return; + } + if (res_trk_enable_footswitch()) { + pr_err("Failed to enable footswitch"); + return; + } + if (res_trk_enable_iommu_clocks()) { + res_trk_disable_footswitch(); + pr_err("Failed to enable iommu clocks\n"); + return; + } + pil_put(cookie); + if (res_trk_disable_iommu_clocks()) + pr_err("Failed to disable iommu clocks\n"); + if (res_trk_disable_footswitch()) + pr_err("Failed to disable footswitch\n"); + } else { + if (res_trk_check_for_sec_session()) + res_trk_close_secure_session(); + res_trk_release_fw_addr(); + } +} + +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 if (vidc_msg_timing) { + 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 ddl_client_context *ddl) +{ + struct time_data *time_data = &proc_time[index]; + struct ddl_decoder_data *decoder = NULL; + 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++; + if (vidc_msg_timing) { + 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); + } + if ((index == DEC_OP_TIME) && (time_data->ddl_count > 2) && + (time_data->ddl_count < 6)) { + decoder = &(ddl->codec_data.decoder); + decoder->dec_time_sum = decoder->dec_time_sum + + ddl_t2 - time_data->ddl_t1; + if (time_data->ddl_count == 5) + decoder->avg_dec_time = + decoder->dec_time_sum / 3; + } + time_data->ddl_t1 = 0; + } +} + +void ddl_calc_core_proc_time_cnt(const char *func_name, u32 index, u32 count) +{ + 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 += 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_update_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); + time_data->ddl_t1 = act_time; + DDL_MSG_LOW("\n%s(): Start time updated Act(%u)", + func_name, act_time); +} + +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; +} + +int ddl_get_core_decode_proc_time(u32 *ddl_handle) +{ + int avg_time = 0; + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + avg_time = ddl_vidc_decode_get_avg_time(ddl); + return avg_time; +} + +void ddl_reset_avg_dec_time(u32 *ddl_handle) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + ddl_vidc_decode_reset_avg_time(ddl); +} 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 0000000000000000000000000000000000000000..bbde7ae8a91eececdcaf1363ab324e7ed3b804a9 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +extern u32 vidc_msg_pmem; +extern u32 vidc_msg_timing; + +enum timing_data { + DEC_OP_TIME, + DEC_IP_TIME, + ENC_OP_TIME, + ENC_SLICE_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) +#define DDL_MEMCPY(dest, src, len) memcpy((dest), (src), (len)) +#define DDL_MEMSET(src, value, len) memset((src), (value), (len)) + +void ddl_set_core_start_time(const char *func_name, u32 index); +void ddl_reset_core_time_variables(u32 index); +void ddl_calc_core_proc_time_cnt(const char *func_name, u32 index, u32 count); +void ddl_update_core_start_time(const char *func_name, u32 index); +int ddl_get_core_decode_proc_time(u32 *ddl_handle); +void ddl_reset_avg_dec_time(u32 *ddl_handle); +#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 0000000000000000000000000000000000000000..d0cf4e838b018fbbb4fe1edd8831218e7fca48ad --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_vidc.c @@ -0,0 +1,1181 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +static unsigned int run_cnt; + +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; + ddl_context->vidc_encode_slice_batch_start[0] = + vidc_1080p_encode_slice_batch_start_ch0; + ddl_context->vidc_encode_slice_batch_start[1] = + vidc_1080p_encode_slice_batch_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) { + 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; + + 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); + + vidc_sm_set_error_concealment_config( + &ddl->shared_mem[ddl->command_channel], + VIDC_SM_ERR_CONCEALMENT_INTER_SLICE_MB_COPY, + VIDC_SM_ERR_CONCEALMENT_INTRA_SLICE_COLOR_CONCEALMENT, + VIDC_SM_ERR_CONCEALMENT_ENABLE); + + 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; + if ((decoder->codec.codec == VCD_CODEC_MPEG4) || + (decoder->codec.codec == VCD_CODEC_DIVX_4) || + (decoder->codec.codec == VCD_CODEC_DIVX_5) || + (decoder->codec.codec == VCD_CODEC_DIVX_6) || + (decoder->codec.codec == VCD_CODEC_XVID)) + vidc_sm_set_mpeg4_profile_override( + &ddl->shared_mem[ddl->command_channel], + VIDC_SM_PROFILE_INFO_ASP); + if (VCD_CODEC_H264 == decoder->codec.codec) + vidc_sm_set_decoder_sei_enable( + &ddl->shared_mem[ddl->command_channel], + VIDC_SM_RECOVERY_POINT_SEI); + ddl_context->vidc_decode_seq_start[ddl->command_channel]( + &seq_start_param); + + vidc_sm_set_decoder_stuff_bytes_consumption( + &ddl->shared_mem[ddl->command_channel], + VIDC_SM_NUM_STUFF_BYTES_CONSUME_NONE); +} + +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_GOB: + 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); +} + +static void ddl_vidc_encode_set_batch_slice_info( + struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + DDL_MSG_LOW("%s\n", __func__); + encoder->batch_frame.slice_batch_out.buffer_size = + encoder->output_buf_req.sz; + DDL_MSG_LOW("encoder->batch_frame.slice_batch_out.buffer_size = %d\n", + encoder->batch_frame.slice_batch_out.buffer_size); + vidc_sm_set_encoder_batch_config( + &ddl->shared_mem[ddl->command_channel], + 1, + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + encoder->batch_frame.slice_batch_in), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + encoder->batch_frame.slice_batch_out), + encoder->batch_frame.slice_batch_out.buffer_size); + vidc_sm_set_encoder_slice_batch_int_ctrl( + &ddl->shared_mem[ddl->command_channel], + 0); +} + +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; + u32 h263_cpfc_enable = false; + u32 scaled_frame_rate; + + 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; + scaled_frame_rate = DDL_FRAMERATE_SCALE(encoder->\ + frame_rate.fps_numerator) / + encoder->frame_rate.fps_denominator; + if ((encoder->codec.codec == VCD_CODEC_H263) && + (DDL_FRAMERATE_SCALE(DDL_INITIAL_FRAME_RATE) + != scaled_frame_rate)) + h263_cpfc_enable = true; + vidc_sm_set_extended_encoder_control(&ddl->shared_mem + [ddl->command_channel], hdr_ext_control, + r_cframe_skip, false, 0, + h263_cpfc_enable, encoder->sps_pps.sps_pps_for_idr_enable_flag, + encoder->closed_gop); + 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( + scaled_frame_rate, + 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_MSG_LOW("%s\n", __func__); + 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; + enc_param.slice_enable = 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); +} + +void ddl_vidc_encode_frame_continue(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct vcd_frame_data *input_vcd_frm = &(ddl->input_frame.vcd_frm); + u32 address_offset; + address_offset = (u32)(ddl->output_frame.vcd_frm.physical - + ddl_context->dram_base_a.align_physical_addr) >> + DDL_VIDC_1080P_BASE_OFFSET_SHIFT; + DDL_MSG_LOW("%s\n", __func__); + if (VCD_FRAME_FLAG_EOS & input_vcd_frm->flags) + ddl->client_state = DDL_CLIENT_WAIT_FOR_EOS_DONE; + else + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME_DONE; + ddl->cmd_state = DDL_CMD_ENCODE_CONTINUE; + vidc_1080p_set_host2risc_cmd(VIDC_1080P_HOST2RISC_CMD_CONTINUE_ENC, + address_offset, + 0, 0, 0); +} + +void ddl_vidc_encode_slice_batch_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 *input_vcd_frm = + &(ddl->input_frame.vcd_frm); + u32 dpb_addr_y[4], dpb_addr_c[4]; + u32 index, y_addr, c_addr; + u32 bitstream_size; + struct vidc_1080p_enc_slice_batch_in_param *slice_batch_in = + (struct vidc_1080p_enc_slice_batch_in_param *) + encoder->batch_frame.slice_batch_in.align_virtual_addr; + DDL_MSG_LOW("%s\n", __func__); + DDL_MEMSET(slice_batch_in, 0, + sizeof(struct vidc_1080p_enc_slice_batch_in_param)); + DDL_MEMSET(encoder->batch_frame.slice_batch_in.align_virtual_addr, 0, + sizeof(struct vidc_1080p_enc_slice_batch_in_param)); + encoder->batch_frame.out_frm_next_frmindex = 0; + bitstream_size = encoder->batch_frame.output_frame[0].vcd_frm.alloc_len; + encoder->output_buf_req.sz = bitstream_size; + 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); + enc_param.encode = VIDC_1080P_ENC_TYPE_SLICE_BATCH_START; + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_FRAME_DONE", + ddl_get_state_string(ddl->client_state)); + slice_batch_in->cmd_type = VIDC_1080P_ENC_TYPE_SLICE_BATCH_START; + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME_DONE; + ddl->cmd_state = DDL_CMD_ENCODE_FRAME; + 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_size = bitstream_size; + slice_batch_in->num_stream_buffer = + encoder->batch_frame.num_output_frames; + slice_batch_in->stream_buffer_size = bitstream_size; + DDL_MSG_LOW("%s slice_batch_in->num_stream_buffer = %u size = %u\n", + __func__, slice_batch_in->num_stream_buffer, + slice_batch_in->stream_buffer_size); + for (index = 0; index < encoder->batch_frame.num_output_frames; + index++) { + slice_batch_in->stream_buffer_addr_offset[index] = + ((DDL_OFFSET(ddl_context->dram_base_b.align_physical_addr, + encoder->batch_frame.output_frame[index].vcd_frm.physical)) >> + DDL_VIDC_1080P_BASE_OFFSET_SHIFT); + } + slice_batch_in->input_size = VIDC_1080P_SLICE_BATCH_IN_SIZE(index); + enc_param.intra_frame = encoder->intra_frame_insertion; + if (encoder->intra_frame_insertion) + encoder->intra_frame_insertion = false; + enc_param.input_flush = false; + enc_param.slice_enable = + encoder->slice_delivery_info.enable; + 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); + DDL_MSG_LOW("%sdpb_count = %d\n", __func__, enc_buffers->dpb_count); + 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(); + } + if ((!encoder->rc_level.frame_level_rc) && + (!encoder->rc_level.mb_level_rc)) { + encoder->session_qp.p_frame_qp++; + if (encoder->session_qp.p_frame_qp > encoder->qp_range.max_qp) + encoder->session_qp.p_frame_qp = + encoder->qp_range.min_qp; + 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 (vidc_msg_timing) { + if (run_cnt < 2) { + ddl_reset_core_time_variables(ENC_OP_TIME); + ddl_reset_core_time_variables(ENC_SLICE_OP_TIME); + run_cnt++; + } + ddl_update_core_start_time(__func__, ENC_SLICE_OP_TIME); + ddl_set_core_start_time(__func__, ENC_OP_TIME); + } + ddl_vidc_encode_set_batch_slice_info(ddl); + ddl_context->vidc_encode_slice_batch_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; + u32 size_y = 0; + u32 size_c = 0; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) { + DDL_MSG_ERROR("STATE-CRITICAL"); + return VCD_ERR_FAIL; + } + ddl_set_vidc_timeout(ddl); + 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 + ddl_set_core_start_time(__func__, DEC_OP_TIME); + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_INIT); + if (ddl_decoder_dpb_init(ddl) == VCD_ERR_FAIL) + return VCD_ERR_FAIL; + 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; + if (decoder->cont_mode) { + size_y = ddl_get_yuv_buf_size(decoder->client_frame_size.width, + decoder->client_frame_size.height, + DDL_YUV_BUF_TYPE_TILE); + size_c = ddl_get_yuv_buf_size(decoder->client_frame_size.width, + (decoder->client_frame_size.height >> 1), + DDL_YUV_BUF_TYPE_TILE); + vidc_sm_set_allocated_dpb_size( + &ddl->shared_mem[ddl->command_channel], + size_y, + size_c); + } else { + 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; + init_buf_param.dmx_disable = decoder->dmx_disable; + 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; + 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; + dec_param.dmx_disable = decoder->dmx_disable; + if (decoder->dmx_disable) + ddl_fill_dec_desc_buffer(ddl); + 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); +} + +void ddl_vidc_encode_eos_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); + DDL_MSG_LOW("%s\n", __func__); + ddl->client_state = DDL_CLIENT_WAIT_FOR_EOS_DONE; + ddl_vidc_encode_dynamic_property(ddl, true); + ddl->client_state = DDL_CMD_EOS; + DDL_MEMSET(&enc_param, 0, sizeof(enc_param)); + enc_param.encode = VIDC_1080P_ENC_TYPE_LAST_FRAME_DATA; + 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 = 0; + enc_param.current_c_addr_offset = 0; + enc_param.stream_buffer_size = 0; + enc_param.intra_frame = encoder->intra_frame_insertion; + vidc_sm_set_frame_tag(&ddl->shared_mem[ddl->command_channel], + ddl->input_frame.vcd_frm.ip_frm_tag); + ddl_context->vidc_encode_frame_start[ddl->command_channel]( + &enc_param); +} + +int ddl_vidc_decode_get_avg_time(struct ddl_client_context *ddl) +{ + int avg_time = 0; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + avg_time = decoder->avg_dec_time; + return avg_time; +} + +void ddl_vidc_decode_reset_avg_time(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + decoder->avg_dec_time = 0; + decoder->dec_time_sum = 0; +} 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 0000000000000000000000000000000000000000..d39984749e1abab41c855e3918907589688eb668 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc.c @@ -0,0 +1,1108 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_RG7_DISPLAY_CORRECT_MASK 0x00000180 +#define VIDC_1080P_SI_RG7_DISPLAY_CORRECT_SHIFT 7 +#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_SI_RG11_DECODE_CORRECT_MASK 0x00000600 +#define VIDC_1080P_SI_RG11_DECODE_CORRECT_SHIFT 9 +#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_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_BMSK 0x00004000 +#define VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_SHFT 14 +#define VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_BMSK 0x80000000 +#define VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_SHFT 31 +#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); + dec_disp_info->display_correct = VIDC_GETFIELD(display_result, + VIDC_1080P_SI_RG7_DISPLAY_CORRECT_MASK, + VIDC_1080P_SI_RG7_DISPLAY_CORRECT_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); + dec_disp_info->decode_correct = VIDC_GETFIELD(decode_result, + VIDC_1080P_SI_RG11_DECODE_CORRECT_MASK, + VIDC_1080P_SI_RG11_DECODE_CORRECT_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_404623, 0); + 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_404623, 0); + 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->dmx_disable, + VIDC_1080P_SI_RG10_DMX_DISABLE_SHFT, + VIDC_1080P_SI_RG10_DMX_DISABLE_BMSK) | + 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->dmx_disable, + VIDC_1080P_SI_RG10_DMX_DISABLE_SHFT, + VIDC_1080P_SI_RG10_DMX_DISABLE_BMSK) | + 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) +{ + u32 dpb_config; + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + dpb_config = VIDC_SETFIELD(param->dmx_disable, + VIDC_1080P_SI_RG10_DMX_DISABLE_SHFT, + VIDC_1080P_SI_RG10_DMX_DISABLE_BMSK) | + VIDC_SETFIELD(param->dpb_count, + VIDC_1080P_SI_RG10_NUM_DPB_SHFT, + VIDC_1080P_SI_RG10_NUM_DPB_BMSK); + 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, VIDC_1080P_DEC_TYPE_INIT_BUFFERS | + param->inst_id); +} + +void vidc_1080p_decode_init_buffers_ch1( + struct vidc_1080p_dec_init_buffers_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); + dpb_config = VIDC_SETFIELD(param->dmx_disable, + VIDC_1080P_SI_RG10_DMX_DISABLE_SHFT, + VIDC_1080P_SI_RG10_DMX_DISABLE_BMSK) | + VIDC_SETFIELD(param->dpb_count, + VIDC_1080P_SI_RG10_NUM_DPB_SHFT, + VIDC_1080P_SI_RG10_NUM_DPB_BMSK); + 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, 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) +{ + u32 input_flush; + 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); + input_flush = VIDC_SETFIELD(param->input_flush, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_SHFT, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_BMSK); + input_flush |= VIDC_SETFIELD(param->slice_enable, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_SHFT, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_BMSK); + VIDC_HWIO_OUT(REG_404623, 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) +{ + u32 input_flush; + 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); + input_flush = VIDC_SETFIELD(param->input_flush, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_SHFT, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_BMSK); + input_flush |= VIDC_SETFIELD(param->slice_enable, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_SHFT, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_BMSK); + + VIDC_HWIO_OUT(REG_404623, 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_encode_slice_batch_start_ch0( + struct vidc_1080p_enc_frame_start_param *param) +{ + u32 input_flush; + 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_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); + input_flush = VIDC_SETFIELD(param->input_flush, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_SHFT, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_BMSK); + input_flush |= VIDC_SETFIELD(param->slice_enable, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_SHFT, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_BMSK); + VIDC_HWIO_OUT(REG_404623, 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_slice_batch_start_ch1( + struct vidc_1080p_enc_frame_start_param *param) +{ + u32 input_flush; + 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_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); + input_flush = VIDC_SETFIELD(param->input_flush, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_SHFT, + VIDC_1080P_SI_RG10_ENCODE_INPUT_BUFFER_FLUSH_BMSK); + input_flush |= VIDC_SETFIELD(param->slice_enable, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_SHFT, + VIDC_1080P_SI_RG10_ENCODE_SLICE_IF_ENABLE_BMSK); + + VIDC_HWIO_OUT(REG_404623, 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 0000000000000000000000000000000000000000..7460ef31e91818fc0a8db50658d1c39cea7995a4 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc.h @@ -0,0 +1,586 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SLICE_DONE_RET 6 +#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_BATCH_ENC_RET 14 +#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_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_DEBLOCKING_NOT_DONE 178 +#define VIDC_1080P_WARN_INCOMPLETE_FRAME 179 +#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_WARN_NO_LONG_TERM_REFERENCE 183 +#define VIDC_1080P_WARN_NO_SPACE_MPEG2_DATA_DUMP 190 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_MISSING_MB 191 + +#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 +#define VIDC_1080P_SLICE_BATCH_MAX_STRM_BFR 8 +#define VIDC_1080P_SLICE_BATCH_IN_SIZE(idx) (4 * sizeof(u32) + \ + idx * sizeof(u32)) +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_decode_frame_correct_type { + VIDC_1080P_DECODE_NOT_CORRECT = 0, + VIDC_1080P_DECODE_CORRECT = 1, + VIDC_1080P_DECODE_APPROX_CORRECT = 2, + VIDC_1080P_DECODE_CORRECTTYPE_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_SLICE_BATCH_START = 0x00070000, + 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; + u32 dmx_disable; + 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; + u32 dmx_disable; +}; +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; + u32 slice_enable; + 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_enc_slice_batch_in_param { + u32 cmd_type; + u32 input_size; + u32 num_stream_buffer; + u32 stream_buffer_size; + u32 stream_buffer_addr_offset[VIDC_1080P_SLICE_BATCH_MAX_STRM_BFR]; +}; +struct vidc_1080p_enc_slice_info { + u32 stream_buffer_idx; + u32 stream_buffer_size; +}; +struct vidc_1080p_enc_slice_batch_out_param { + u32 cmd_type; + u32 output_size; + struct vidc_1080p_enc_slice_info slice_info + [VIDC_1080P_SLICE_BATCH_MAX_STRM_BFR]; +}; +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_correct_type display_correct; + enum vidc_1080P_decode_frame_correct_type decode_correct; + 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_encode_slice_batch_start_ch0( + struct vidc_1080p_enc_frame_start_param *param); +void vidc_1080p_encode_slice_batch_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 0000000000000000000000000000000000000000..a5a8e57b881692bdba1779fe35585cd8056eb397 --- /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) __raw_readl(port) +#define __outpdw(port, val) __raw_writel(val, port) + +#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 0000000000000000000000000000000000000000..819cd6c7847e88d330933e507d15c58321c794d8 --- /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 0000000000000000000000000000000000000000..6870525144ca5a3794a4b608dc6dd534e0f71ead --- /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 0000000000000000000000000000000000000000..e8a93a1e02631e0af1f70a50cf252e8907a0b28e --- /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 0000000000000000000000000000000000000000..d90b8ca4ad9b2afcdcf6ad193609ec677d25f83a --- /dev/null +++ b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.c @@ -0,0 +1,991 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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" + +#define PIL_FW_BASE_ADDR 0x9fe00000 +#define PIL_FW_SIZE 0x200000 + +static unsigned int vidc_clk_table[3] = { + 48000000, 133330000, 200000000 +}; +static unsigned int restrk_mmu_subsystem[] = { + MSM_SUBSYSTEM_VIDEO, MSM_SUBSYSTEM_VIDEO_FWARE}; +static struct res_trk_context resource_context; + +#define VIDC_FW "vidc_1080p.fw" +#define VIDC_FW_SIZE SZ_1M + +struct res_trk_vidc_mmu_clk { + char *mmu_clk_name; + struct clk *mmu_clk; +}; + +static struct res_trk_vidc_mmu_clk vidc_mmu_clks[] = { + {"mdp_iommu_clk"}, {"rot_iommu_clk"}, + {"vcodec_iommu0_clk"}, {"vcodec_iommu1_clk"}, + {"smmu_iface_clk"} +}; + +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 void *res_trk_pmem_map + (struct ddl_buf_addr *addr, size_t sz, u32 alignment) +{ + u32 offset = 0, flags = 0; + u32 index = 0; + struct ddl_context *ddl_context; + struct msm_mapped_buffer *mapped_buffer = NULL; + int ret = 0; + unsigned long iova = 0; + unsigned long buffer_size = 0; + unsigned long *kernel_vaddr = NULL; + + ddl_context = ddl_get_context(); + if (res_trk_get_enable_ion() && addr->alloc_handle) { + kernel_vaddr = (unsigned long *) ion_map_kernel( + ddl_context->video_ion_client, + addr->alloc_handle, UNCACHED); + if (IS_ERR_OR_NULL(kernel_vaddr)) { + DDL_MSG_ERROR("%s():DDL ION client map failed\n", + __func__); + goto ion_bail_out; + } + addr->virtual_base_addr = (u8 *) kernel_vaddr; + ret = ion_map_iommu(ddl_context->video_ion_client, + addr->alloc_handle, + VIDEO_DOMAIN, + VIDEO_FIRMWARE_POOL, + SZ_4K, + 0, + &iova, + &buffer_size, + UNCACHED, 0); + if (ret) { + DDL_MSG_ERROR("%s():DDL ION client iommu map failed\n", + __func__); + goto ion_unmap_bail_out; + } + addr->mapped_buffer = NULL; + addr->physical_base_addr = (u8 *)iova; + 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 = buffer_size; + } else { + if (!res_trk_check_for_sec_session()) { + if (!addr->alloced_phys_addr) { + pr_err(" %s() alloced addres NULL", __func__); + goto bail_out; + } + flags = MSM_SUBSYSTEM_MAP_IOVA | + MSM_SUBSYSTEM_MAP_KADDR; + if (alignment == DDL_KILO_BYTE(128)) + index = 1; + else if (alignment > SZ_4K) + flags |= MSM_SUBSYSTEM_ALIGN_IOVA_8K; + addr->mapped_buffer = + msm_subsystem_map_buffer( + (unsigned long)addr->alloced_phys_addr, + sz, flags, &restrk_mmu_subsystem[index], + sizeof(restrk_mmu_subsystem[index])/ + sizeof(unsigned int)); + if (IS_ERR(addr->mapped_buffer)) { + pr_err(" %s() buffer map failed", __func__); + goto bail_out; + } + mapped_buffer = addr->mapped_buffer; + if (!mapped_buffer->vaddr || !mapped_buffer->iova[0]) { + pr_err("%s() map buffers failed\n", __func__); + goto bail_out; + } + addr->physical_base_addr = + (u8 *)mapped_buffer->iova[0]; + addr->virtual_base_addr = + mapped_buffer->vaddr; + } else { + addr->physical_base_addr = + (u8 *) addr->alloced_phys_addr; + addr->virtual_base_addr = + (u8 *)addr->alloced_phys_addr; + } + 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; + } + return addr->virtual_base_addr; +bail_out: + if (IS_ERR(addr->mapped_buffer)) + msm_subsystem_unmap_buffer(addr->mapped_buffer); + return NULL; +ion_unmap_bail_out: + if (!IS_ERR_OR_NULL(addr->alloc_handle)) { + ion_unmap_kernel(resource_context. + res_ion_client, addr->alloc_handle); + } +ion_bail_out: + return NULL; +} + +static void res_trk_pmem_free(struct ddl_buf_addr *addr) +{ + struct ddl_context *ddl_context; + ddl_context = ddl_get_context(); + if (ddl_context->video_ion_client) { + if (addr && addr->alloc_handle) { + ion_free(ddl_context->video_ion_client, + addr->alloc_handle); + addr->alloc_handle = NULL; + } + } else { + if (addr->mapped_buffer) + msm_subsystem_unmap_buffer(addr->mapped_buffer); + if (addr->alloced_phys_addr) + free_contiguous_memory_by_paddr( + (unsigned long)addr->alloced_phys_addr); + } + memset(addr, 0 , sizeof(struct ddl_buf_addr)); +} +static int res_trk_pmem_alloc + (struct ddl_buf_addr *addr, size_t sz, u32 alignment) +{ + u32 alloc_size; + struct ddl_context *ddl_context; + int rc = 0; + DBG_PMEM("\n%s() IN: Requested alloc size(%u)", __func__, (u32)sz); + if (!addr) { + DDL_MSG_ERROR("\n%s() Invalid Parameters", __func__); + rc = -EINVAL; + goto bail_out; + } + ddl_context = ddl_get_context(); + res_trk_set_mem_type(addr->mem_type); + alloc_size = (sz + alignment); + if (res_trk_get_enable_ion()) { + if (!res_trk_is_cp_enabled() || + !res_trk_check_for_sec_session()) { + if (!ddl_context->video_ion_client) + ddl_context->video_ion_client = + res_trk_get_ion_client(); + if (!ddl_context->video_ion_client) { + DDL_MSG_ERROR( + "%s() :DDL ION Client Invalid handle\n", + __func__); + rc = -ENOMEM; + goto bail_out; + } + alloc_size = (alloc_size+4095) & ~4095; + addr->alloc_handle = ion_alloc( + ddl_context->video_ion_client, + alloc_size, SZ_4K, + res_trk_get_mem_type()); + if (IS_ERR_OR_NULL(addr->alloc_handle)) { + DDL_MSG_ERROR("%s() :DDL ION alloc failed\n", + __func__); + rc = -ENOMEM; + goto bail_out; + } + } else { + addr->alloc_handle = NULL; + addr->alloced_phys_addr = PIL_FW_BASE_ADDR; + addr->buffer_size = sz; + } + } else { + addr->alloced_phys_addr = (phys_addr_t) + allocate_contiguous_memory_nomap(alloc_size, + res_trk_get_mem_type(), SZ_4K); + if (!addr->alloced_phys_addr) { + DDL_MSG_ERROR("%s() : acm alloc failed (%d)\n", + __func__, alloc_size); + rc = -ENOMEM; + goto bail_out; + } + addr->buffer_size = sz; + return rc; + } +bail_out: + return rc; +} + +static void res_trk_pmem_unmap(struct ddl_buf_addr *addr) +{ + if (!addr) { + pr_err("%s() invalid args\n", __func__); + return; + } + if (!IS_ERR_OR_NULL(addr->alloc_handle)) { + if (addr->physical_base_addr) { + ion_unmap_kernel(resource_context.res_ion_client, + addr->alloc_handle); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu(resource_context.res_ion_client, + addr->alloc_handle, + VIDEO_DOMAIN, + VIDEO_FIRMWARE_POOL); + } + addr->virtual_base_addr = NULL; + addr->physical_base_addr = NULL; + } + } else if (addr->mapped_buffer) + msm_subsystem_unmap_buffer(addr->mapped_buffer); + addr->mapped_buffer = NULL; +} + +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, + "core_clk"); + if (IS_ERR(resource_context.vcodec_clk)) { + VCDRES_MSG_ERROR("%s(): core_clk get failed\n", + __func__); + goto bail_out; + } + resource_context.vcodec_pclk = clk_get(resource_context.device, + "iface_clk"); + if (IS_ERR(resource_context.vcodec_pclk)) { + VCDRES_MSG_ERROR("%s(): iface_clk get failed\n", + __func__); + goto release_vcodec_clk; + } + 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: + clk_put(resource_context.vcodec_pclk); + resource_context.vcodec_pclk = 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); + 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_prepare_enable(resource_context.vcodec_pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable fail\n"); + goto bail_out; + } + if (clk_prepare_enable(resource_context.vcodec_clk)) { + VCDRES_MSG_ERROR("vidc core clk Enable fail\n"); + goto vidc_disable_pclk; + } + + 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_unprepare(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; +} + +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_unprepare(resource_context.vcodec_clk); + if (resource_context.vcodec_pclk) + clk_disable_unprepare(resource_context.vcodec_pclk); + 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; + } + if (!resource_context.footswitch) + resource_context.footswitch = + regulator_get(resource_context.device, "vdd"); + 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; +} + +static struct ion_client *res_trk_create_ion_client(void){ + struct ion_client *video_client; + video_client = msm_ion_client_create(-1, "video_client"); + return video_client; +} + +int res_trk_enable_footswitch(void) +{ + int rc = 0; + mutex_lock(&resource_context.lock); + if (!resource_context.footswitch) + 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; + rc = -EINVAL; + } else + rc = regulator_enable(resource_context.footswitch); + mutex_unlock(&resource_context.lock); + return rc; +} + +int res_trk_disable_footswitch(void) +{ + mutex_lock(&resource_context.lock); + 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; + } + mutex_unlock(&resource_context.lock); + return 0; +} + +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"); + res_trk_pmem_unmap(&resource_context.firmware_addr); + res_trk_pmem_free(&resource_context.firmware_addr); +#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); + mutex_init(&resource_context.secure_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; + resource_context.fw_mem_type = + resource_context.vidc_platform_data->memtype; + resource_context.cmd_mem_type = + resource_context.vidc_platform_data->memtype; + if (resource_context.vidc_platform_data->enable_ion) { + resource_context.res_ion_client = + res_trk_create_ion_client(); + if (!(resource_context.res_ion_client)) { + VCDRES_MSG_ERROR("%s()ION createfail\n", + __func__); + return; + } + resource_context.fw_mem_type = + ION_MM_FIRMWARE_HEAP_ID; + resource_context.cmd_mem_type = + ION_CP_MFC_HEAP_ID; + } + resource_context.disable_dmx = + resource_context.vidc_platform_data->disable_dmx; + resource_context.disable_fullhd = + resource_context.vidc_platform_data->disable_fullhd; +#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.disable_dmx = 0; + } + resource_context.core_type = VCD_CORE_1080P; + resource_context.firmware_addr.mem_type = DDL_FW_MEM; + } +} + +u32 res_trk_get_core_type(void){ + return resource_context.core_type; +} + +u32 res_trk_get_firmware_addr(struct ddl_buf_addr *firm_addr) +{ + int rc = 0; + size_t size = 0; + if (!firm_addr || resource_context.firmware_addr.mapped_buffer) { + pr_err("%s() invalid params", __func__); + return -EINVAL; + } + if (res_trk_is_cp_enabled() && res_trk_check_for_sec_session()) + size = PIL_FW_SIZE; + else + size = VIDC_FW_SIZE; + + if (res_trk_pmem_alloc(&resource_context.firmware_addr, + size, DDL_KILO_BYTE(128))) { + pr_err("%s() Firmware buffer allocation failed", + __func__); + memset(&resource_context.firmware_addr, 0, + sizeof(resource_context.firmware_addr)); + rc = -ENOMEM; + goto fail_alloc; + } + if (!res_trk_pmem_map(&resource_context.firmware_addr, + resource_context.firmware_addr.buffer_size, + DDL_KILO_BYTE(128))) { + pr_err("%s() Firmware buffer mapping failed", + __func__); + rc = -ENOMEM; + goto fail_map; + } + memcpy(firm_addr, &resource_context.firmware_addr, + sizeof(struct ddl_buf_addr)); + return 0; +fail_map: + res_trk_pmem_free(&resource_context.firmware_addr); +fail_alloc: + return rc; +} + +void res_trk_release_fw_addr(void) +{ + res_trk_pmem_unmap(&resource_context.firmware_addr); + res_trk_pmem_free(&resource_context.firmware_addr); +} + +int res_trk_check_for_sec_session(void) +{ + int rc; + mutex_lock(&resource_context.secure_lock); + rc = resource_context.secure_session; + mutex_unlock(&resource_context.secure_lock); + return rc; +} + +int res_trk_get_mem_type(void) +{ + int mem_type = -1; + switch (resource_context.res_mem_type) { + case DDL_FW_MEM: + mem_type = ION_HEAP(resource_context.fw_mem_type); + return mem_type; + case DDL_MM_MEM: + mem_type = resource_context.memtype; + break; + case DDL_CMD_MEM: + if (res_trk_check_for_sec_session()) + mem_type = resource_context.cmd_mem_type; + else + mem_type = resource_context.memtype; + break; + default: + return mem_type; + } + if (resource_context.vidc_platform_data->enable_ion) { + if (res_trk_check_for_sec_session()) { + mem_type = ION_HEAP(mem_type); + if (resource_context.res_mem_type != DDL_FW_MEM) + mem_type |= ION_SECURE; + else if (res_trk_is_cp_enabled()) + mem_type |= ION_SECURE; + } else + mem_type = (ION_HEAP(mem_type) | + ION_HEAP(ION_IOMMU_HEAP_ID)); + } + return mem_type; +} + +u32 res_trk_is_cp_enabled(void) +{ + if (resource_context.vidc_platform_data->cp_enabled) + return 1; + else + return 0; +} + +u32 res_trk_get_enable_ion(void) +{ + if (resource_context.vidc_platform_data->enable_ion) + return 1; + else + return 0; +} + +struct ion_client *res_trk_get_ion_client(void) +{ + return resource_context.res_ion_client; +} + +u32 res_trk_get_disable_dmx(void){ + return resource_context.disable_dmx; +} + +u32 res_trk_get_min_dpb_count(void){ + return resource_context.vidc_platform_data->cont_mode_dpb_count; +} + +void res_trk_set_mem_type(enum ddl_mem_area mem_type) +{ + resource_context.res_mem_type = mem_type; + return; +} + +u32 res_trk_get_disable_fullhd(void) +{ + return resource_context.disable_fullhd; +} + +int res_trk_enable_iommu_clocks(void) +{ + int ret = 0, i; + if (resource_context.mmu_clks_on) { + pr_err(" %s: Clocks are already on", __func__); + return -EINVAL; + } + resource_context.mmu_clks_on = 1; + for (i = 0; i < ARRAY_SIZE(vidc_mmu_clks); i++) { + vidc_mmu_clks[i].mmu_clk = clk_get(resource_context.device, + vidc_mmu_clks[i].mmu_clk_name); + if (IS_ERR(vidc_mmu_clks[i].mmu_clk)) { + pr_err(" %s: Get failed for clk %s", __func__, + vidc_mmu_clks[i].mmu_clk_name); + ret = PTR_ERR(vidc_mmu_clks[i].mmu_clk); + } + if (!ret) { + ret = clk_prepare_enable(vidc_mmu_clks[i].mmu_clk); + if (ret) { + clk_put(vidc_mmu_clks[i].mmu_clk); + vidc_mmu_clks[i].mmu_clk = NULL; + } + } + if (ret) { + for (i--; i >= 0; i--) { + clk_disable_unprepare(vidc_mmu_clks[i].mmu_clk); + clk_put(vidc_mmu_clks[i].mmu_clk); + vidc_mmu_clks[i].mmu_clk = NULL; + } + resource_context.mmu_clks_on = 0; + pr_err("%s() clocks enable failed", __func__); + break; + } + } + return ret; +} + +int res_trk_disable_iommu_clocks(void) +{ + int i; + if (!resource_context.mmu_clks_on) { + pr_err(" %s: clks are already off", __func__); + return -EINVAL; + } + resource_context.mmu_clks_on = 0; + for (i = 0; i < ARRAY_SIZE(vidc_mmu_clks); i++) { + clk_disable_unprepare(vidc_mmu_clks[i].mmu_clk); + clk_put(vidc_mmu_clks[i].mmu_clk); + vidc_mmu_clks[i].mmu_clk = NULL; + } + return 0; +} + +void res_trk_secure_unset(void) +{ + mutex_lock(&resource_context.secure_lock); + resource_context.secure_session--; + mutex_unlock(&resource_context.secure_lock); +} + +void res_trk_secure_set(void) +{ + mutex_lock(&resource_context.secure_lock); + resource_context.secure_session++; + mutex_unlock(&resource_context.secure_lock); +} + +int res_trk_open_secure_session() +{ + int rc; + + if (res_trk_check_for_sec_session() == 1) { + mutex_lock(&resource_context.secure_lock); + pr_err("Securing...\n"); + rc = res_trk_enable_iommu_clocks(); + if (rc) { + pr_err("IOMMU clock enabled failed while open"); + goto error_open; + } + msm_ion_secure_heap(ION_HEAP(resource_context.memtype)); + msm_ion_secure_heap(ION_HEAP(resource_context.cmd_mem_type)); + res_trk_disable_iommu_clocks(); + mutex_unlock(&resource_context.secure_lock); + } + return 0; +error_open: + mutex_unlock(&resource_context.secure_lock); + return rc; +} + +int res_trk_close_secure_session() +{ + int rc; + if (res_trk_check_for_sec_session() == 1) { + pr_err("Unsecuring....\n"); + mutex_lock(&resource_context.secure_lock); + rc = res_trk_enable_iommu_clocks(); + if (rc) { + pr_err("IOMMU clock enabled failed while close"); + goto error_close; + } + msm_ion_unsecure_heap(ION_HEAP(resource_context.cmd_mem_type)); + msm_ion_unsecure_heap(ION_HEAP(resource_context.memtype)); + res_trk_disable_iommu_clocks(); + mutex_unlock(&resource_context.secure_lock); + } + return 0; +error_close: + mutex_unlock(&resource_context.secure_lock); + return rc; +} + +u32 get_res_trk_perf_level(enum vcd_perf_level perf_level) +{ + u32 res_trk_perf_level; + switch (perf_level) { + case VCD_PERF_LEVEL0: + res_trk_perf_level = RESTRK_1080P_VGA_PERF_LEVEL; + break; + case VCD_PERF_LEVEL1: + res_trk_perf_level = RESTRK_1080P_720P_PERF_LEVEL; + break; + case VCD_PERF_LEVEL2: + res_trk_perf_level = RESTRK_1080P_MAX_PERF_LEVEL; + break; + default: + VCD_MSG_ERROR("Invalid perf level: %d\n", perf_level); + res_trk_perf_level = -EINVAL; + } + return res_trk_perf_level; +} + +u32 res_trk_estimate_perf_level(u32 pn_perf_lvl) +{ + VCDRES_MSG_MED("%s(), req_perf_lvl = %d", __func__, pn_perf_lvl); + if ((pn_perf_lvl >= RESTRK_1080P_VGA_PERF_LEVEL) && + (pn_perf_lvl < RESTRK_1080P_720P_PERF_LEVEL)) { + return RESTRK_1080P_720P_PERF_LEVEL; + } else if ((pn_perf_lvl >= RESTRK_1080P_720P_PERF_LEVEL) && + (pn_perf_lvl < RESTRK_1080P_MAX_PERF_LEVEL)) { + return RESTRK_1080P_MAX_PERF_LEVEL; + } else { + return pn_perf_lvl; + } +} 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 0000000000000000000000000000000000000000..bf8607dd54cc27997ffca0e050362fcec0c2a115 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#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; + 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; + int fw_mem_type; + int cmd_mem_type; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *vidc_bus_client_pdata; + uint32_t pcl; +#endif + u32 core_type; + struct ddl_buf_addr firmware_addr; + struct ion_client *res_ion_client; + u32 disable_dmx; + u32 disable_fullhd; + enum ddl_mem_area res_mem_type; + u32 mmu_clks_on; + u32 secure_session; + struct mutex secure_lock; +}; + +#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 0000000000000000000000000000000000000000..2ae2512629cc821e3b6d87309eaa4d372e2791b2 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker_api.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" +#include "vcd_ddl.h" +#include "vcd_ddl_utils.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_firmware_addr(struct ddl_buf_addr *firm_addr); +int res_trk_get_mem_type(void); +u32 res_trk_get_enable_ion(void); +u32 res_trk_is_cp_enabled(void); +u32 res_trk_get_disable_fullhd(void); +struct ion_client *res_trk_get_ion_client(void); +u32 res_trk_get_disable_dmx(void); +u32 res_trk_get_min_dpb_count(void); +void res_trk_set_mem_type(enum ddl_mem_area mem_type); +int res_trk_enable_iommu_clocks(void); +int res_trk_disable_iommu_clocks(void); +int res_trk_check_for_sec_session(void); +int res_trk_open_secure_session(void); +int res_trk_close_secure_session(void); +void res_trk_secure_set(void); +void res_trk_secure_unset(void); +u32 get_res_trk_perf_level(enum vcd_perf_level); +int res_trk_enable_footswitch(void); +int res_trk_disable_footswitch(void); +void res_trk_release_fw_addr(void); +u32 res_trk_estimate_perf_level(u32 pn_perf_lvl); +u32 res_trk_get_clk_rate(unsigned long *phclk_rate); +#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 0000000000000000000000000000000000000000..02b2369b9249a0d877e4018522bb072cba52108b --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl.c @@ -0,0 +1,628 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_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 || + !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 0000000000000000000000000000000000000000..e1407c817283a0f142b2623ef703050223fe3b39 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl.h @@ -0,0 +1,292 @@ +/* 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 +#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 + +enum ddl_mem_area { + DDL_MM_MEM = 0x0 +}; + +struct ddl_buf_addr { + u32 *physical_base_addr; + u32 *virtual_base_addr; + u32 *align_physical_addr; + u32 *align_virtual_addr; + struct msm_mapped_buffer *mapped_buffer; + u32 buffer_size; + enum ddl_mem_area mem_type; +}; + +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; + u32 idr_only_decoding; +}; + +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); +void ddl_pmem_alloc(struct ddl_buf_addr *addr, size_t sz, u32 alignment); +void ddl_pmem_free(struct ddl_buf_addr *addr); +#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 0000000000000000000000000000000000000000..53cc93e526e73eba4d336e65c0eb1b20836fec0a --- /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 0000000000000000000000000000000000000000..9fdb66876411eb0817e5247c10ff52d3f5e4f797 --- /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 0000000000000000000000000000000000000000..ac5bce98b6c1bc6d56f55bcaeba90eb910787fbb --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_errors.c @@ -0,0 +1,609 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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" + +#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: + { + vcd_status = VCD_ERR_BITSTREAM_ERR; + break; + } + case ACTIVE_SPS_NOT_PRESENT: + case ACTIVE_PPS_NOT_PRESENT: + { + if (ddl->codec_data.decoder.idr_only_decoding) { + DBG("Consider warnings as errors in idr mode"); + ddl_client_fatal_cb(ddl_context); + return true; + } + vcd_status = VCD_ERR_BITSTREAM_ERR; + break; + } + case PROFILE_UNKOWN: + if (ddl->decoding) + 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: + case PROFILE_UNKOWN: + 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 0000000000000000000000000000000000000000..965c3aa78ee4e27008274a16e19020760641ed00 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.c @@ -0,0 +1,352 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_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 0000000000000000000000000000000000000000..a136de8c3150992bf2125ebf92b139fa7234f31a --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#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 0000000000000000000000000000000000000000..6a69955bd81bdd82a02d407f4a8c0ec6cbc7c94d --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_hal.c @@ -0,0 +1,971 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_metadata.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define DBG_INFO(x...) pr_info(x) + +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(); + + DBG_INFO("Loading CONTROL_FW of FW_SIZE %u\n", + fw_details.fw_size*4); + + 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); + + DBG_INFO("Loading firmware for CODEC:%u of FW_SIZE:%u\n", + fw_details.codec, fw_details.fw_size*4); + + 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; + struct ddl_context *ddl_ctxt = NULL; + + 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: + { + if (decoder->idr_only_decoding) + comv_buf_no = decoder->min_dpb_num; + else + 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, y_sz, frm_sz; + u32 i = 0; + sz = decoder->dp_buf.dec_pic_buffers[0].vcd_frm.alloc_len; + frm_sz = sz; + y_sz = decoder->client_frame_size.height * + decoder->client_frame_size.width; + 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; + } + memset((u8 *)decoder->ref_buffer.virtual_base_addr, + 0x80, sz); + for (i = 0; i < ref_buf_no; i++) + memset((u8 *)decoder->ref_buffer.align_virtual_addr + + i*frm_sz, 0x10, y_sz); + } + ddl_decode_set_metadata_output(decoder); + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_INIT); + ddl_ctxt = ddl_get_context(); + vidc_720p_set_deblock_line_buffer( + ddl_ctxt->db_line_buffer.align_physical_addr, + ddl_ctxt->db_line_buffer.buffer_size); + 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 0000000000000000000000000000000000000000..15adf218b41fac1b2806d2fbf022d36c37fd96dc --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_helper.c @@ -0,0 +1,297 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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" + +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; + u32 temp_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) { + temp_mask = (~(0x1 << loopc)); + if (decoder->idr_only_decoding) + temp_mask = ~(0xffffffff); + dpb_mask->hw_mask &= temp_mask; + *in_out_frame = *found_frame; + } else if (operation == + DDL_DPB_OP_MARK_FREE) { + temp_mask = (0x1 << loopc); + if (decoder->idr_only_decoding) + temp_mask = 0xffffffff; + dpb_mask->client_mask |= temp_mask; + *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, index, num_dpb; + dpb_size = (!decoder->meta_data_offset) ? + decoder->dp_buf.dec_pic_buffers[0].vcd_frm. + alloc_len : decoder->meta_data_offset; + if (decoder->idr_only_decoding) + num_dpb = decoder->min_dpb_num; + else + num_dpb = decoder->dp_buf.no_of_dec_pic_buf; + vidc_720p_decode_set_dpb_details( + num_dpb, + dpb_size, + decoder->ref_buffer. + align_physical_addr); + for (loopc = 0; loopc < num_dpb; ++loopc) { + if (decoder->idr_only_decoding) + index = 0; + else + index = loopc; + vidc_720p_decode_set_dpb_buffers(loopc, + (u32 *) + decoder-> + dp_buf. + dec_pic_buffers + [index]. + vcd_frm. + physical); + VIDC_LOG1("DEC_DPB_BUFn_SIZE=%d", + decoder->dp_buf. + dec_pic_buffers[index].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 0000000000000000000000000000000000000000..7e201cf85a4b1efea662e0f826b18da821a19e7c --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_internal_property.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#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 vcd_property_slice_delivery_info { + u32 enable; + u32 num_slices; + u32 num_slices_enc; +}; + +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 0000000000000000000000000000000000000000..5fa9b09bda1b7baa28c212722de6741bd0bde6f6 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_interrupt_handler.c @@ -0,0 +1,1130 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 "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->flags & VCD_FRAME_FLAG_EOS) + 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; + } + case VIDC_720P_IDRFRAME: + { + frame->flags |= VCD_FRAME_FLAG_SYNCFRAME; + frame->frame = VCD_FRAME_IDR; + 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 0000000000000000000000000000000000000000..2a74da81906a1b82c377297033c00b8bed0dd6d8 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.c @@ -0,0 +1,580 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_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 0000000000000000000000000000000000000000..ed43861c225ca03c55f017af2cfca0204bc9be23 --- /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 0000000000000000000000000000000000000000..3aebdaf5e85b32f1562d857e8c203394b55b49d6 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_properties.c @@ -0,0 +1,1943 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_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_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_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; + } + case VCD_I_META_BUFFER_MODE: + { + vcd_status = VCD_S_SUCCESS; + 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_MPEG2 && + decoder->codec.codec <= VCD_CODEC_XVID) + 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->idr_only_decoding = 0; + 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; + } + + if (decoder->idr_only_decoding) + min_dpb = 1; + + 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 (decoder->idr_only_decoding) { + output_buf_req->actual_count = output_buf_req->min_count; + } else { + 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 0000000000000000000000000000000000000000..aa0d4b825d471fcac8d2df138f26b0ba4fdc676e --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.c @@ -0,0 +1,242 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#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, flags = 0; + struct ddl_context *ddl_context; + struct msm_mapped_buffer *mapped_buffer = NULL; + + 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; + flags = MSM_SUBSYSTEM_MAP_KADDR; + buff_addr->mapped_buffer = + msm_subsystem_map_buffer((unsigned long)physical_addr, + alloc_size, flags, NULL, 0); + if (IS_ERR(buff_addr->mapped_buffer)) { + pr_err(" %s() buffer map failed", __func__); + goto free_acm_alloc; + } + mapped_buffer = buff_addr->mapped_buffer; + if (!mapped_buffer->vaddr) { + pr_err("%s() mapped virtual address is NULL", __func__); + goto free_map_buffers; + } + buff_addr->virtual_base_addr = mapped_buffer->vaddr; + 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; +free_map_buffers: + msm_subsystem_unmap_buffer(buff_addr->mapped_buffer); +free_acm_alloc: + free_contiguous_memory_by_paddr( + (unsigned long) physical_addr); +bailout: + buff_addr->physical_base_addr = NULL; + buff_addr->virtual_base_addr = NULL; + buff_addr->buffer_size = 0; + buff_addr->mapped_buffer = NULL; +} + +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->mapped_buffer) + msm_subsystem_unmap_buffer(buff_addr->mapped_buffer); + 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; + buff_addr->mapped_buffer = 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; +} +int ddl_get_core_decode_proc_time(u32 *ddl_handle) +{ + return 0; +} + +void ddl_reset_avg_dec_time(u32 *ddl_handle) +{ + return; +} 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 0000000000000000000000000000000000000000..59bb6200e152dd11c4298c192c5afedde2a54438 --- /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_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); + +int ddl_get_core_decode_proc_time(u32 *ddl_handle); + +void ddl_reset_avg_dec_time(u32 *ddl_handle); + +#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 0000000000000000000000000000000000000000..de6cbbb9709e3bc7f6c773bc99c5c6f29c85fa73 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vidc.c @@ -0,0 +1,804 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "vidc.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); + + if (disp_info->input_frame & 0x10) + disp_info->input_frame = VIDC_720P_IDRFRAME; + else + 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 0000000000000000000000000000000000000000..509482bb4b09d9fca4d93eda7723e1cd8a51a8a7 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vidc.h @@ -0,0 +1,2705 @@ +/* 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 +#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 0x1f +#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 0x17 +#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, + VIDC_720P_IDRFRAME = 4 +}; + +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 0000000000000000000000000000000000000000..e51bf452c774b09136735ddd11b84f21ce18fbd2 --- /dev/null +++ b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.c @@ -0,0 +1,771 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "vcd_res_tracker.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 +}; + +static unsigned int axi_clk_freq_table_enc[2] = { + 122880, 192000 +}; +static unsigned int axi_clk_freq_table_dec[2] = { + 122880, 192000 +}; + +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_prepare_enable(resource_context.pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable failed\n"); + goto bail_out; + } + + if (clk_prepare_enable(resource_context.hclk)) { + VCDRES_MSG_ERROR("vidc hclk Enable failed\n"); + goto disable_pclk; + } + + if (clk_prepare_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_unprepare(resource_context.pclk); + clk_disable_unprepare(resource_context.hclk); + clk_disable_unprepare(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_unprepare(resource_context.hclk); +disable_pclk: + clk_disable_unprepare(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_prepare_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_prepare_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_prepare_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_unprepare(resource_context.hclk); + clk_disable_unprepare(resource_context.hclk_div2); + clk_disable_unprepare(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, + "iface_clk"); + + if (IS_ERR(resource_context.pclk)) { + VCDRES_MSG_ERROR("%s(): iface_clk get failed\n" + , __func__); + goto disable_regulator; + } + + resource_context.hclk = clk_get(resource_context.device, + "core_clk"); + + if (IS_ERR(resource_context.hclk)) { + VCDRES_MSG_ERROR("%s(): core_clk get failed\n" + , __func__); + + goto release_pclk; + } + + resource_context.hclk_div2 = + clk_get(resource_context.device, "core_div2_clk"); + + if (IS_ERR(resource_context.hclk_div2)) { + VCDRES_MSG_ERROR("%s(): core_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_prepare_enable(resource_context.pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable failed\n"); + goto release_all_clks; + } + + if (clk_prepare_enable(resource_context.hclk)) { + VCDRES_MSG_ERROR("vidc hclk Enable failed\n"); + goto disable_pclk; + } + + if (clk_prepare_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_unprepare(resource_context.pclk); + clk_disable_unprepare(resource_context.hclk); + clk_disable_unprepare(resource_context.hclk_div2); + + } + resource_context.rail_enabled = 1; + mutex_unlock(&resource_context.lock); + return true; + +disable_and_release_all_clks: + clk_disable_unprepare(resource_context.hclk_div2); +disable_hclk_pclk: + clk_disable_unprepare(resource_context.hclk); +disable_pclk: + clk_disable_unprepare(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(resource_context.device, "mem_clk"); + if (IS_ERR(ebi1_clk)) { + VCDRES_MSG_ERROR("Request AXI bus QOS fails."); + return false; + } + clk_prepare_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_unprepare(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; +} + +u32 res_trk_get_enable_ion(void) +{ + return 0; +} + +struct ion_client *res_trk_get_ion_client(void) +{ + return NULL; +} + +void res_trk_set_mem_type(enum ddl_mem_area mem_type) +{ + return; +} + +u32 res_trk_get_disable_fullhd(void) +{ + return 0; +} + +int res_trk_check_for_sec_session() +{ + return 0; +} + +void res_trk_secure_unset(void) +{ + return; +} + +void res_trk_secure_set(void) +{ + return; +} + +int res_trk_open_secure_session() +{ + return -EINVAL; +} + +int res_trk_close_secure_session() +{ + return 0; +} +u32 get_res_trk_perf_level(enum vcd_perf_level perf_level) +{ + return -ENOTSUPP; +} +u32 res_trk_is_cp_enabled(void) +{ + if (resource_context.vidc_platform_data->cp_enabled) + return 1; + else + return 0; +} +u32 res_trk_estimate_perf_level(u32 pn_perf_lvl) +{ + return 0; +} + 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 0000000000000000000000000000000000000000..2b92a42a0859eb9567bb549cc1f14e48f6905c93 --- /dev/null +++ b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; + u32 secure_session; +}; + +#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 0000000000000000000000000000000000000000..75fdb3e1bf90675e29c5a10b07d1a8444101086a --- /dev/null +++ b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker_api.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" +#include "vcd_ddl.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); +u32 res_trk_get_disable_fullhd(void); +u32 res_trk_get_enable_ion(void); +u32 res_trk_is_cp_enabled(void); +struct ion_client *res_trk_get_ion_client(void); +void res_trk_set_mem_type(enum ddl_mem_area mem_type); +int res_trk_check_for_sec_session(void); +int res_trk_open_secure_session(void); +int res_trk_close_secure_session(void); +void res_trk_secure_set(void); +void res_trk_secure_unset(void); +u32 get_res_trk_perf_level(enum vcd_perf_level perf_level); +u32 res_trk_estimate_perf_level(u32 pn_perf_lvl); +#endif diff --git a/drivers/video/msm/vidc/Kconfig b/drivers/video/msm/vidc/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..9ffcb154af1d5046754d6e21933cdfe858b69ab5 --- /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 0000000000000000000000000000000000000000..af41f181878e489bc091287131842ba24f92cca5 --- /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 0000000000000000000000000000000000000000..11177b8c6e2ea63d150fee89369eefa0b9342f02 --- /dev/null +++ b/drivers/video/msm/vidc/common/dec/vdec.c @@ -0,0 +1,2380 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "vcd_res_tracker_api.h" +#include "vdec_internal.h" + + + +#define DBG(x...) pr_debug(x) +#define INFO(x...) pr_info(x) +#define ERR(x...) pr_err(x) + +#define VID_DEC_NAME "msm_vidc_dec" + +static char *node_name[2] = {"", "_sec"}; +static struct vid_dec_dev *vid_dec_device_p; +static dev_t vid_dec_dev_num; +static struct class *vid_dec_class; + +static unsigned int vidc_mmu_subsystem[] = { + MSM_SUBSYSTEM_VIDEO}; +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: + vdec_status = VDEC_S_INPUT_BITSTREAM_ERR; + break; + 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_handle_field_drop(struct video_client_ctx *client_ctx, + u32 event, u32 status, int64_t time_stamp) +{ + struct vid_dec_msg *vdec_msg; + + if (!client_ctx) { + ERR("%s() NULL 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); + if (event == VCD_EVT_IND_INFO_FIELD_DROPPED) { + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_EVT_INFO_FIELD_DROPPED; + vdec_msg->vdec_msg_info.msgdata.output_frame.time_stamp + = time_stamp; + DBG("Send FIELD_DROPPED 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.msgdatasize = + sizeof(struct vdec_output_frameinfo); + 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_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; + } + + kfree(vcd_frame_data->desc_buf); + vcd_frame_data->desc_buf = NULL; + vcd_frame_data->desc_size = 0; + + 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; + u32 ion_flag = 0; + struct ion_handle *buff_handle = NULL; + struct vdec_output_frameinfo *output_frame; + + 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; + case VCD_FRAME_IDR: + pic_type = PICTURE_TYPE_IDR; + break; + default: + pic_type = PICTURE_TYPE_UNKNOWN; + } + vdec_msg->vdec_msg_info.msgdata.output_frame.pic_type = + pic_type; + output_frame = &vdec_msg->vdec_msg_info.msgdata.output_frame; + output_frame->aspect_ratio_info.aspect_ratio = + vcd_frame_data->aspect_ratio_info.aspect_ratio; + output_frame->aspect_ratio_info.par_width = + vcd_frame_data->aspect_ratio_info.extended_par_width; + output_frame->aspect_ratio_info.par_height = + vcd_frame_data->aspect_ratio_info.extended_par_height; + 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; + } + if (vcd_frame_data->data_len > 0) { + ion_flag = vidc_get_fd_info(client_ctx, BUFFER_TYPE_OUTPUT, + pmem_fd, kernel_vaddr, buffer_index, + &buff_handle); + if (ion_flag == CACHED && buff_handle) { + msm_ion_do_cache_op(client_ctx->user_ion_client, + buff_handle, + (unsigned long *) kernel_vaddr, + (unsigned long)vcd_frame_data->data_len, + ION_IOC_INV_CACHES); + } + } + 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: + DBG("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: + DBG("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: + DBG("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: + DBG("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: + DBG("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: + DBG("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: + DBG("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: + DBG("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: + DBG("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_IND_INFO_FIELD_DROPPED: + if (info) + vid_dec_handle_field_drop(client_ctx, event, + status, *((int64_t *)info)); + else + pr_err("Wrong Payload for Field dropped\n"); + 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_4: + codec.codec = VCD_CODEC_DIVX_4; + break; + case VDEC_CODECTYPE_DIVX_5: + codec.codec = VCD_CODEC_DIVX_5; + break; + case VDEC_CODECTYPE_DIVX_6: + codec.codec = VCD_CODEC_DIVX_6; + 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_get_disable_dmx_support(struct video_client_ctx *client_ctx, + u32 *disable_dmx) +{ + + struct vcd_property_hdr vcd_property_hdr; + if (!client_ctx || !disable_dmx) + return false; + vcd_property_hdr.prop_id = VCD_I_DISABLE_DMX_SUPPORT; + vcd_property_hdr.sz = sizeof(u32); + if (vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + disable_dmx)) + return false; + else + return true; +} +static u32 vid_dec_get_disable_dmx(struct video_client_ctx *client_ctx, + u32 *disable_dmx) +{ + + struct vcd_property_hdr vcd_property_hdr; + if (!client_ctx || !disable_dmx) + return false; + vcd_property_hdr.prop_id = VCD_I_DISABLE_DMX; + vcd_property_hdr.sz = sizeof(u32); + if (vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + disable_dmx)) + return false; + else + return true; +} + +static u32 vid_dec_set_disable_dmx(struct video_client_ctx *client_ctx) +{ + + struct vcd_property_hdr vcd_property_hdr; + u32 vcd_disable_dmx; + if (!client_ctx) + return false; + vcd_property_hdr.prop_id = VCD_I_DISABLE_DMX; + vcd_property_hdr.sz = sizeof(u32); + vcd_disable_dmx = true; + DBG("%s() : Setting Disable DMX: %d\n", + __func__, vcd_disable_dmx); + + if (vcd_set_property(client_ctx->vcd_handle, &vcd_property_hdr, + &vcd_disable_dmx)) + 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 = NULL; + struct msm_mapped_buffer *mapped_buffer = NULL; + u32 vcd_status = VCD_ERR_FAIL; + u32 len = 0, flags = 0; + struct file *file; + int rc = 0; + unsigned long ionflag = 0; + unsigned long buffer_size = 0; + unsigned long iova = 0; + + 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); + vcd_h264_mv_buffer = &client_ctx->vcd_h264_mv_buffer; + + memset(&client_ctx->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 (!vcd_get_ion_status()) { + 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); + flags = MSM_SUBSYSTEM_MAP_IOVA; + mapped_buffer = msm_subsystem_map_buffer( + (unsigned long)vcd_h264_mv_buffer->physical_addr, len, + flags, vidc_mmu_subsystem, + sizeof(vidc_mmu_subsystem)/ + sizeof(unsigned int)); + if (IS_ERR(mapped_buffer)) { + pr_err("buffer map failed"); + return false; + } + vcd_h264_mv_buffer->client_data = (void *) mapped_buffer; + vcd_h264_mv_buffer->dev_addr = (u8 *)mapped_buffer->iova[0]; + } else { + client_ctx->h264_mv_ion_handle = ion_import_fd( + client_ctx->user_ion_client, + vcd_h264_mv_buffer->pmem_fd); + if (IS_ERR_OR_NULL(client_ctx->h264_mv_ion_handle)) { + ERR("%s(): get_ION_handle failed\n", __func__); + goto import_ion_error; + } + rc = ion_handle_get_flags(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle, + &ionflag); + if (rc) { + ERR("%s():get_ION_flags fail\n", + __func__); + goto import_ion_error; + } + vcd_h264_mv_buffer->kernel_virtual_addr = (u8 *) ion_map_kernel( + client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle, + ionflag); + if (!vcd_h264_mv_buffer->kernel_virtual_addr) { + ERR("%s(): get_ION_kernel virtual addr failed\n", + __func__); + goto import_ion_error; + } + if (res_trk_check_for_sec_session()) { + rc = ion_phys(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle, + (unsigned long *) (&(vcd_h264_mv_buffer-> + physical_addr)), &len); + if (rc) { + ERR("%s():get_ION_kernel physical addr fail\n", + __func__); + goto ion_map_error; + } + vcd_h264_mv_buffer->client_data = NULL; + vcd_h264_mv_buffer->dev_addr = (u8 *) + vcd_h264_mv_buffer->physical_addr; + } else { + rc = ion_map_iommu(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle, + VIDEO_DOMAIN, VIDEO_MAIN_POOL, + SZ_4K, 0, (unsigned long *)&iova, + (unsigned long *)&buffer_size, + UNCACHED, 0); + if (rc) { + ERR("%s():get_ION_kernel physical addr fail\n", + __func__); + goto ion_map_error; + } + vcd_h264_mv_buffer->physical_addr = (u8 *) iova; + vcd_h264_mv_buffer->client_data = NULL; + vcd_h264_mv_buffer->dev_addr = (u8 *) iova; + } + } + DBG("Virt: %p, Phys %p, fd: %d", vcd_h264_mv_buffer-> + kernel_virtual_addr, vcd_h264_mv_buffer->physical_addr, + vcd_h264_mv_buffer->pmem_fd); + DBG("Dev addr %p", vcd_h264_mv_buffer->dev_addr); + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, vcd_h264_mv_buffer); + + if (vcd_status) + return false; + else + return true; +ion_map_error: + if (vcd_h264_mv_buffer->kernel_virtual_addr) { + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle); + vcd_h264_mv_buffer->kernel_virtual_addr = NULL; + } + if (!IS_ERR_OR_NULL(client_ctx->h264_mv_ion_handle)) { + ion_free(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle); + client_ctx->h264_mv_ion_handle = NULL; + } +import_ion_error: + return false; +} + +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; + if (client_ctx->vcd_h264_mv_buffer.client_data) + msm_subsystem_unmap_buffer((struct msm_mapped_buffer *) + client_ctx->vcd_h264_mv_buffer.client_data); + + 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 (!IS_ERR_OR_NULL(client_ctx->h264_mv_ion_handle)) { + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + } + ion_free(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle); + client_ctx->h264_mv_ion_handle = NULL; + } + + 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, length; + + 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; + } + length = buffer_info->buffer.buffer_len; + /*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, length)) { + 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) { + DBG("msm_vidc_dec: PAUSE command from client = %p\n", + client_ctx); + vcd_status = vcd_pause(client_ctx->vcd_handle); + } else{ + DBG("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; + + DBG("msm_vidc_dec: Inside %s()", __func__); + if (!client_ctx) { + ERR("\n Invalid client_ctx"); + return false; + } + + if (start) { + if (client_ctx->seq_header_set) { + DBG("%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 { + DBG("%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 { + DBG("%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, + u8 *desc_buf, u32 desc_size) +{ + 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; + u32 ion_flag = 0; + struct ion_handle *buff_handle = NULL; + + 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_input_buffer.desc_buf = desc_buf; + vcd_input_buffer.desc_size = desc_size; + if (vcd_input_buffer.data_len > 0) { + ion_flag = vidc_get_fd_info(client_ctx, + BUFFER_TYPE_INPUT, + pmem_fd, + kernel_vaddr, + buffer_index, + &buff_handle); + if (ion_flag == CACHED && buff_handle) { + msm_ion_do_cache_op(client_ctx->user_ion_client, + buff_handle, + (unsigned long *)kernel_vaddr, + (unsigned long) vcd_input_buffer.data_len, + ION_IOC_CLEAN_CACHES); + } + } + 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 ion_handle *buff_handle = NULL; + + 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_frame.ion_flag = vidc_get_fd_info(client_ctx, + BUFFER_TYPE_OUTPUT, + pmem_fd, kernel_vaddr, + buffer_index, + &buff_handle); + vcd_frame.buff_ion_handle = buff_handle; + 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; + + DBG("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; + unsigned long ker_vaddr; + struct file *pmem_file; + u32 result = true; + void __user *arg = (void __user *)u_arg; + int rc = 0; + size_t ion_len; + + 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; + u8 *desc_buf = NULL; + u32 desc_size = 0; + 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; + if (client_ctx->dmx_disable) { + if (input_frame_info.desc_addr) { + desc_size = input_frame_info.desc_size; + desc_buf = kzalloc(desc_size, GFP_KERNEL); + if (desc_buf) { + if (copy_from_user(desc_buf, + input_frame_info.desc_addr, + desc_size)) { + kfree(desc_buf); + desc_buf = NULL; + return -EFAULT; + } + } + } else + return -EINVAL; + } + result = vid_dec_decode_frame(client_ctx, &input_frame_info, + desc_buf, desc_size); + + if (!result) { + kfree(desc_buf); + desc_buf = NULL; + 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; + unsigned long ionflag; + 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 (!vcd_get_ion_status()) { + 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); + } else { + client_ctx->seq_hdr_ion_handle = ion_import_fd( + client_ctx->user_ion_client, + seq_header.pmem_fd); + if (!client_ctx->seq_hdr_ion_handle) { + ERR("%s(): get_ION_handle failed\n", __func__); + return false; + } + rc = ion_handle_get_flags(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle, + &ionflag); + if (rc) { + ERR("%s():get_ION_flags fail\n", + __func__); + ion_free(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle); + return false; + } + ker_vaddr = (unsigned long) ion_map_kernel( + client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle, ionflag); + if (!ker_vaddr) { + ERR("%s():get_ION_kernel virtual addr fail\n", + __func__); + ion_free(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle); + return false; + } + kernel_vaddr = ker_vaddr; + rc = ion_phys(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle, + &phy_addr, &ion_len); + if (rc) { + ERR("%s():get_ION_kernel physical addr fail\n", + __func__); + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle); + ion_free(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle); + return false; + } + len = ion_len; + } + 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; + } + if (vcd_get_ion_status()) { + if (client_ctx->seq_hdr_ion_handle) { + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle); + ion_free(client_ctx->user_ion_client, + client_ctx->seq_hdr_ion_handle); + } + } + 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_GET_DISABLE_DMX_SUPPORT: + { + u32 disable_dmx; + DBG("VDEC_IOCTL_GET_DISABLE_DMX_SUPPORT\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + result = vid_dec_get_disable_dmx_support(client_ctx, + &disable_dmx); + if (result) { + if (copy_to_user(vdec_msg.out, &disable_dmx, + sizeof(u32))) + return -EFAULT; + } else + return -EIO; + break; + } + case VDEC_IOCTL_GET_DISABLE_DMX: + { + u32 disable_dmx; + DBG("VDEC_IOCTL_GET_DISABLE_DMX\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + result = vid_dec_get_disable_dmx(client_ctx, + &disable_dmx); + if (result) { + if (copy_to_user(vdec_msg.out, &disable_dmx, + sizeof(u32))) + return -EFAULT; + } else + return -EIO; + break; + } + case VDEC_IOCTL_SET_DISABLE_DMX: + { + DBG("VDEC_IOCTL_SET_DISABLE_DMX\n"); + result = vid_dec_set_disable_dmx(client_ctx); + if (!result) + return -EIO; + client_ctx->dmx_disable = 1; + 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; + + DBG("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; + } + client_ctx->user_ion_client = NULL; + 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; +} + +int vid_dec_open_client(struct video_client_ctx **vid_clnt_ctx, int flags) +{ + int rc = 0; + s32 client_index; + struct video_client_ctx *client_ctx = NULL; + u8 client_count; + + if (!vid_clnt_ctx) { + ERR("Invalid input\n"); + return -EINVAL; + } + *vid_clnt_ctx = NULL; + 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"); + rc = -ENOMEM; + goto client_failure; + } + + 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()) { + rc = -ENOMEM; + goto client_failure; + } + } + + client_index = vid_dec_get_empty_client_index(); + if (client_index == -1) { + ERR("%s() : No free clients client_index == -1\n", __func__); + rc = -ENOMEM; + goto client_failure; + } + 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); + mutex_init(&client_ctx->enrty_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; + client_ctx->dmx_disable = 0; + if (vcd_get_ion_status()) { + client_ctx->user_ion_client = vcd_get_ion_client(); + if (!client_ctx->user_ion_client) { + ERR("vcd_open ion client get failed"); + rc = -ENOMEM; + goto client_failure; + } + } + rc = vcd_open(vid_dec_device_p->device_handle, true, + vid_dec_vcd_cb, client_ctx, flags); + if (!rc) { + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) { + ERR("callback for vcd_open returned error: %u", + client_ctx->event_status); + rc = -ENODEV; + goto client_failure; + } + } else { + ERR("vcd_open returned error: %u", rc); + goto client_failure; + } + client_ctx->seq_header_set = false; + *vid_clnt_ctx = client_ctx; +client_failure: + return rc; +} + +static int vid_dec_open_secure(struct inode *inode, struct file *file) +{ + int rc = 0; + struct video_client_ctx *client_ctx; + mutex_lock(&vid_dec_device_p->lock); + rc = vid_dec_open_client(&client_ctx, VCD_CP_SESSION); + if (rc) + goto error; + if (!client_ctx) { + rc = -ENOMEM; + goto error; + } + + file->private_data = client_ctx; + if (res_trk_open_secure_session()) { + ERR("Secure session operation failure\n"); + rc = -EACCES; + goto error; + } + mutex_unlock(&vid_dec_device_p->lock); + return 0; +error: + mutex_unlock(&vid_dec_device_p->lock); + return rc; +} + +static int vid_dec_open(struct inode *inode, struct file *file) +{ + int rc = 0; + struct video_client_ctx *client_ctx; + INFO("msm_vidc_dec: Inside %s()", __func__); + mutex_lock(&vid_dec_device_p->lock); + rc = vid_dec_open_client(&client_ctx, 0); + if (rc) { + mutex_unlock(&vid_dec_device_p->lock); + return rc; + } + if (!client_ctx) { + mutex_unlock(&vid_dec_device_p->lock); + return -ENOMEM; + } + + file->private_data = client_ctx; + mutex_unlock(&vid_dec_device_p->lock); + return rc; +} + +static int vid_dec_release_secure(struct inode *inode, struct file *file) +{ + struct video_client_ctx *client_ctx = file->private_data; + + INFO("msm_vidc_dec: Inside %s()", __func__); + vidc_cleanup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT); + vidc_cleanup_addr_table(client_ctx, BUFFER_TYPE_INPUT); + 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 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__); + vidc_cleanup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT); + vidc_cleanup_addr_table(client_ctx, BUFFER_TYPE_INPUT); + 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[2] = { + { + .owner = THIS_MODULE, + .open = vid_dec_open, + .release = vid_dec_release, + .unlocked_ioctl = vid_dec_ioctl, + }, + { + .owner = THIS_MODULE, + .open = vid_dec_open_secure, + .release = vid_dec_release_secure, + .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); */ + DBG("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, i = 0, j = 0; + struct device *class_devp; + + DBG("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, NUM_OF_DRIVER_NODES, + 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; + } + for (i = 0; i < NUM_OF_DRIVER_NODES; i++) { + class_devp = device_create(vid_dec_class, NULL, + (vid_dec_dev_num + i), + NULL, VID_DEC_NAME "%s", + node_name[i]); + + if (IS_ERR(class_devp)) { + rc = PTR_ERR(class_devp); + ERR("%s: class device_create failed %d\n", + __func__, rc); + if (!i) + goto error_vid_dec_class_device_create; + else + goto error_vid_dec_cdev_add; + } + + vid_dec_device_p->device[i] = class_devp; + + cdev_init(&vid_dec_device_p->cdev[i], &vid_dec_fops[i]); + vid_dec_device_p->cdev[i].owner = THIS_MODULE; + rc = cdev_add(&(vid_dec_device_p->cdev[i]), + (vid_dec_dev_num+i), 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: + for (j = i-1; j >= 0; j--) + cdev_del(&(vid_dec_device_p->cdev[j])); + 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, NUM_OF_DRIVER_NODES); +error_vid_dec_alloc_chrdev_region: + kfree(vid_dec_device_p); + return rc; +} + +static void __exit vid_dec_exit(void) +{ + int i = 0; + INFO("msm_vidc_dec: Inside %s()", __func__); + for (i = 0; i < NUM_OF_DRIVER_NODES; i++) + cdev_del(&(vid_dec_device_p->cdev[i])); + device_destroy(vid_dec_class, vid_dec_dev_num); + class_destroy(vid_dec_class); + unregister_chrdev_region(vid_dec_dev_num, NUM_OF_DRIVER_NODES); + kfree(vid_dec_device_p); + DBG("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 0000000000000000000000000000000000000000..89da9a2b210268807590ba1e81a169af7b1c013e --- /dev/null +++ b/drivers/video/msm/vidc/common/dec/vdec_internal.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2010, 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#define NUM_OF_DRIVER_NODES 2 + +struct vid_dec_msg { + struct list_head list; + struct vdec_msginfo vdec_msg_info; +}; + +struct vid_dec_dev { + struct cdev cdev[NUM_OF_DRIVER_NODES]; + struct device *device[NUM_OF_DRIVER_NODES]; + 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 0000000000000000000000000000000000000000..1b77b671b382f752ee09358247a9f6ee63c7a9a9 --- /dev/null +++ b/drivers/video/msm/vidc/common/enc/venc.c @@ -0,0 +1,1648 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "venc_internal.h" +#include "vcd_res_tracker_api.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; + u32 ion_flag = 0; + struct ion_handle *buff_handle = NULL; + + 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; + } + if (venc_msg->venc_msg_info.buf.len > 0) { + ion_flag = vidc_get_fd_info(client_ctx, BUFFER_TYPE_OUTPUT, + pmem_fd, kernel_vaddr, buffer_index, + &buff_handle); + if (ion_flag == CACHED && buff_handle) { + msm_ion_do_cache_op(client_ctx->user_ion_client, + buff_handle, + (unsigned long *) kernel_vaddr, + (unsigned long)venc_msg->venc_msg_info.buf.len, + ION_IOC_CLEAN_INV_CACHES); + } + } + 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 int 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 -EIO; + + rc = wait_event_interruptible(client_ctx->msg_wait, + vid_enc_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("stopped stop_msg = %u\n", 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_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 0; +} + +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; + int rc = 0; + 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); + mutex_init(&client_ctx->enrty_queue_lock); + INIT_LIST_HEAD(&client_ctx->msg_queue); + init_waitqueue_head(&client_ctx->msg_wait); + if (vcd_get_ion_status()) { + client_ctx->user_ion_client = vcd_get_ion_client(); + if (!client_ctx->user_ion_client) { + ERR("vcd_open ion get client failed"); + return -EFAULT; + } + } + rc = vcd_open(vid_enc_device_p->device_handle, false, + vid_enc_vcd_cb, client_ctx, 0); + client_ctx->stop_msg = 0; + + if (!rc) { + 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", rc); + mutex_unlock(&vid_enc_device_p->lock); + return rc; + } + file->private_data = client_ctx; + mutex_unlock(&vid_enc_device_p->lock); + return rc; +} + +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; + int result_read = -1; + + 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_read = vid_enc_get_next_msg(client_ctx, &cb_msg); + if (result_read < 0) + return result_read; + 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: + { + struct venc_recon_addr venc_recon; + DBG("VEN_IOCTL_FREE_RECON_BUFFER\n"); + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + if (copy_from_user(&venc_recon, venc_msg.in, + sizeof(venc_recon))) + return -EFAULT; + result = vid_enc_free_recon_buffers(client_ctx, + &venc_recon); + 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; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + if (copy_from_user(&seq_header, venc_msg.in, + sizeof(seq_header))) + return -EFAULT; + + DBG("VEN_IOCTL_GET_SEQUENCE_HDR\n"); + result = vid_enc_get_sequence_header(client_ctx, + &seq_header); + if (!result) { + ERR("get sequence header failed\n"); + return -EIO; + } + DBG("seq_header: buf=%x, sz=%d, hdrlen=%d\n", + (int)seq_header.hdrbufptr, + (int)seq_header.bufsize, + (int)seq_header.hdrlen); + if (copy_to_user(venc_msg.out, &seq_header, + sizeof(seq_header))) + return -EFAULT; + 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_METABUFFER_MODE: + { + u32 metabuffer_mode, vcd_status; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_live live_mode; + + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + if (copy_from_user(&metabuffer_mode, venc_msg.in, + sizeof(metabuffer_mode))) + return -EFAULT; + vcd_property_hdr.prop_id = VCD_I_META_BUFFER_MODE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_live); + live_mode.live = metabuffer_mode; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &live_mode); + if (vcd_status) { + pr_err(" Setting metabuffer mode failed"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_EXTRADATA: + case VEN_IOCTL_GET_EXTRADATA: + { + u32 extradata_flag; + DBG("VEN_IOCTL_(G)SET_EXTRADATA\n"); + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + if (cmd == VEN_IOCTL_SET_EXTRADATA) { + if (copy_from_user(&extradata_flag, venc_msg.in, + sizeof(u32))) + return -EFAULT; + result = vid_enc_set_get_extradata(client_ctx, + &extradata_flag, true); + } else { + result = vid_enc_set_get_extradata(client_ctx, + &extradata_flag, false); + if (result) { + if (copy_to_user(venc_msg.out, &extradata_flag, + sizeof(u32))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_LIVE_MODE failed\n"); + } + break; + } + case VEN_IOCTL_SET_SLICE_DELIVERY_MODE: + { + struct vcd_property_hdr vcd_property_hdr; + u32 vcd_status = VCD_ERR_FAIL; + u32 enable = true; + vcd_property_hdr.prop_id = VCD_I_SLICE_DELIVERY_MODE; + vcd_property_hdr.sz = sizeof(u32); + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &enable); + if (vcd_status) { + pr_err(" Setting slice delivery mode failed"); + return -EIO; + } + 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 0000000000000000000000000000000000000000..bbbe0cf240259ec3276d3e8c47dcf01cf7c4b929 --- /dev/null +++ b/drivers/video/msm/vidc/common/enc/venc_internal.c @@ -0,0 +1,1990 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "vcd_res_tracker_api.h" +#include "venc_internal.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define ERR(x...) printk(KERN_ERR x) +static unsigned int vidc_mmu_subsystem[] = { + MSM_SUBSYSTEM_VIDEO}; + + +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_extradata(struct video_client_ctx *client_ctx, + u32 *extradata_flag, u32 set_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); + if (set_flag) { + DBG("vcd_set_property: VCD_I_METADATA_ENABLE = %d\n", + *extradata_flag); + 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) { + ERR("%s(): Set VCD_I_METADATA_ENABLE Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_meta_data); + if (vcd_status) { + ERR("%s(): Get VCD_I_METADATA_ENABLE Failed\n", + __func__); + return false; + } + *extradata_flag = vcd_meta_data.meta_data_enable_flag; + DBG("vcd_get_property: VCD_I_METADATA_ENABLE = %d\n", + *extradata_flag); + } + 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_3p2: + level.level = VCD_LEVEL_H264_3p2; + 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: + profile_level->level = VEN_LEVEL_H264_3p2; + 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 = seq_header->hdrbufptr; + hdr.sequence_header_len = seq_header->bufsize; + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &hdr); + seq_header->hdrlen = hdr.sequence_header_len; + + 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; + DBG("%s: actual_count=%d, align=%d, sz=%d, min_count=%d, " + "max_count=%d, buf_pool_id=%d\n", __func__, + buffer_req.actual_count, buffer_req.align, + buffer_req.sz, buffer_req.min_count, + buffer_req.max_count, buffer_req.buf_pool_id); + } + 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; + + DBG("%s: actual_count=%d, align=%d, sz=%d, min_count=%d, " + "max_count=%d, buf_pool_id=%d\n", __func__, + buffer_req.actual_count, buffer_req.align, buffer_req.sz, + buffer_req.min_count, buffer_req.max_count, + buffer_req.buf_pool_id); + 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, length = 0; + + if (!client_ctx || !buffer_info) + return false; + + if (buffer == VEN_BUFFER_TYPE_OUTPUT) { + dir_buffer = BUFFER_TYPE_OUTPUT; + vcd_buffer_t = VCD_BUFFER_OUTPUT; + } + length = buffer_info->sz; + /*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, length)) { + 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 ion_flag = 0; + struct ion_handle *buff_handle = NULL; + + 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; + + ion_flag = vidc_get_fd_info(client_ctx, BUFFER_TYPE_INPUT, + pmem_fd, kernel_vaddr, buffer_index, + &buff_handle); + + if (vcd_input_buffer.data_len > 0) { + if (ion_flag == CACHED && buff_handle) { + msm_ion_do_cache_op( + client_ctx->user_ion_client, + buff_handle, + (unsigned long *) vcd_input_buffer.virtual, + (unsigned long) vcd_input_buffer.data_len, + ION_IOC_CLEAN_CACHES); + } + } + + 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, i, flags = 0; + struct file *file; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_enc_recon_buffer *control = NULL; + struct msm_mapped_buffer *mapped_buffer = NULL; + int rc = -1; + unsigned long ionflag = 0; + unsigned long iova = 0; + unsigned long buffer_size = 0; + size_t ion_len = -1; + unsigned long phy_addr; + + if (!client_ctx || !venc_recon) { + pr_err("%s() Invalid params", __func__); + return false; + } + len = sizeof(client_ctx->recon_buffer)/ + sizeof(struct vcd_property_enc_recon_buffer); + for (i = 0; i < len; i++) { + if (!client_ctx->recon_buffer[i].kernel_virtual_addr) { + control = &client_ctx->recon_buffer[i]; + break; + } + } + if (!control) { + pr_err("Exceeded max recon buffer setting"); + return false; + } + 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; + control->user_virtual_addr = venc_recon->pbuffer; + + if (!vcd_get_ion_status()) { + 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); + flags = MSM_SUBSYSTEM_MAP_IOVA; + mapped_buffer = msm_subsystem_map_buffer( + (unsigned long)control->physical_addr, len, + flags, vidc_mmu_subsystem, + sizeof(vidc_mmu_subsystem)/sizeof(unsigned int)); + if (IS_ERR(mapped_buffer)) { + pr_err("buffer map failed"); + return false; + } + control->client_data = (void *) mapped_buffer; + control->dev_addr = (u8 *)mapped_buffer->iova[0]; + } else { + client_ctx->recon_buffer_ion_handle[i] = ion_import_fd( + client_ctx->user_ion_client, control->pmem_fd); + if (IS_ERR_OR_NULL(client_ctx->recon_buffer_ion_handle[i])) { + ERR("%s(): get_ION_handle failed\n", __func__); + goto import_ion_error; + } + rc = ion_handle_get_flags(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + &ionflag); + if (rc) { + ERR("%s():get_ION_flags fail\n", + __func__); + goto import_ion_error; + } + control->kernel_virtual_addr = (u8 *) ion_map_kernel( + client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + ionflag); + if (!control->kernel_virtual_addr) { + ERR("%s(): get_ION_kernel virtual addr fail\n", + __func__); + goto import_ion_error; + } + if (res_trk_check_for_sec_session()) { + rc = ion_phys(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + &phy_addr, &ion_len); + if (rc) { + ERR("%s():get_ION_kernel physical addr fail\n", + __func__); + goto map_ion_error; + } + control->physical_addr = (u8 *) phy_addr; + len = (unsigned long) ion_len; + control->client_data = NULL; + control->dev_addr = (u8 *)control->physical_addr; + } else { + rc = ion_map_iommu(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + VIDEO_DOMAIN, + VIDEO_MAIN_POOL, + SZ_4K, + 0, + (unsigned long *)&iova, + (unsigned long *)&buffer_size, + UNCACHED, 0); + if (rc) { + ERR("%s():ION map iommu addr fail\n", + __func__); + goto map_ion_error; + } + control->physical_addr = (u8 *) iova; + len = buffer_size; + control->client_data = NULL; + control->dev_addr = (u8 *)iova; + } + } + + 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; + } +map_ion_error: + if (control->kernel_virtual_addr) + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i]); + if (client_ctx->recon_buffer_ion_handle[i]) + ion_free(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i]); + client_ctx->recon_buffer_ion_handle[i] = NULL; +import_ion_error: + return false; +} + +u32 vid_enc_free_recon_buffers(struct video_client_ctx *client_ctx, + struct venc_recon_addr *venc_recon) +{ + u32 vcd_status = VCD_ERR_FAIL; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_enc_recon_buffer *control = NULL; + u32 len = 0, i; + + if (!client_ctx || !venc_recon) { + pr_err("%s() Invalid params", __func__); + return false; + } + len = sizeof(client_ctx->recon_buffer)/ + sizeof(struct vcd_property_enc_recon_buffer); + pr_err(" %s() address %p", __func__, + venc_recon->pbuffer); + for (i = 0; i < len; i++) { + if (client_ctx->recon_buffer[i].user_virtual_addr + == venc_recon->pbuffer) { + control = &client_ctx->recon_buffer[i]; + break; + } + } + if (!control) { + pr_err(" %s() address not found %p", __func__, + venc_recon->pbuffer); + return false; + } + if (control->client_data) + msm_subsystem_unmap_buffer((struct msm_mapped_buffer *) + control->client_data); + + 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); + if (vcd_get_ion_status()) { + if (client_ctx->recon_buffer_ion_handle[i]) { + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i]); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i], + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + } + ion_free(client_ctx->user_ion_client, + client_ctx->recon_buffer_ion_handle[i]); + client_ctx->recon_buffer_ion_handle[i] = NULL; + } + } + memset(control, 0, sizeof(struct vcd_property_enc_recon_buffer)); + 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 0000000000000000000000000000000000000000..8a07fdb82985b3d231cbe57cedb6bc0bcb531de8 --- /dev/null +++ b/drivers/video/msm/vidc/common/enc/venc_internal.h @@ -0,0 +1,154 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#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_extradata(struct video_client_ctx *client_ctx, + u32 *extradata_flag, 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, + struct venc_recon_addr *venc_recon); + +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 0000000000000000000000000000000000000000..23c990a3f436314287bd0af76503a0256cfd7e3e --- /dev/null +++ b/drivers/video/msm/vidc/common/init/vidc_init.c @@ -0,0 +1,929 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "vidc_init_internal.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 unsigned int vidc_mmu_subsystem[] = {MSM_SUBSYSTEM_VIDEO}; + +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, vidc_msg_register; + +#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_request_irq; + } + 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_create_workqueue; + } + 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); + vidc_debugfs_file_create(root, "vidc_msg_register", + (u32 *) &vidc_msg_register); + } +#endif + return 0; + +error_vidc_create_workqueue: + free_irq(vidc_device_p->irq, vidc_device_p->device); +error_vidc_request_irq: + platform_driver_unregister(&msm_vidc_720p_platform_driver); +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; + + if (!res_trk_check_for_sec_session()) { + 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) +{ + if (!res_trk_check_for_sec_session()) { + 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_get_fd_info(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, int pmem_fd, + unsigned long kvaddr, int index, + struct ion_handle **buff_handle) +{ + struct buf_addr_table *buf_addr_table; + u32 rc = 0; + if (!client_ctx) + return false; + if (buffer == BUFFER_TYPE_INPUT) + buf_addr_table = client_ctx->input_buf_addr_table; + else + buf_addr_table = client_ctx->output_buf_addr_table; + if (buf_addr_table[index].pmem_fd == pmem_fd) { + if (buf_addr_table[index].kernel_vaddr == kvaddr) { + rc = buf_addr_table[index].buff_ion_flag; + *buff_handle = buf_addr_table[index].buff_ion_handle; + } else + *buff_handle = NULL; + } else + *buff_handle = NULL; + return rc; +} +EXPORT_SYMBOL(vidc_get_fd_info); + +void vidc_cleanup_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer) +{ + u32 *num_of_buffers = NULL; + u32 i = 0; + struct buf_addr_table *buf_addr_table; + 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) + goto bail_out_cleanup; + if (!client_ctx->user_ion_client) + goto bail_out_cleanup; + for (i = 0; i < *num_of_buffers; ++i) { + if (buf_addr_table[i].client_data) { + msm_subsystem_unmap_buffer( + (struct msm_mapped_buffer *) + buf_addr_table[i].client_data); + buf_addr_table[i].client_data = NULL; + } + if (!IS_ERR_OR_NULL(buf_addr_table[i].buff_ion_handle)) { + if (!IS_ERR_OR_NULL(client_ctx->user_ion_client)) { + ion_unmap_kernel(client_ctx->user_ion_client, + buf_addr_table[i]. + buff_ion_handle); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu( + client_ctx->user_ion_client, + buf_addr_table[i]. + buff_ion_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + } + ion_free(client_ctx->user_ion_client, + buf_addr_table[i]. + buff_ion_handle); + buf_addr_table[i].buff_ion_handle = NULL; + } + } + } + if (client_ctx->vcd_h264_mv_buffer.client_data) { + msm_subsystem_unmap_buffer((struct msm_mapped_buffer *) + client_ctx->vcd_h264_mv_buffer.client_data); + client_ctx->vcd_h264_mv_buffer.client_data = NULL; + } + if (!IS_ERR_OR_NULL(client_ctx->h264_mv_ion_handle)) { + if (!IS_ERR_OR_NULL(client_ctx->user_ion_client)) { + ion_unmap_kernel(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + } + ion_free(client_ctx->user_ion_client, + client_ctx->h264_mv_ion_handle); + client_ctx->h264_mv_ion_handle = NULL; + } + } +bail_out_cleanup: + return; +} +EXPORT_SYMBOL(vidc_cleanup_addr_table); + +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; + mutex_lock(&client_ctx->enrty_queue_lock); + 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].dev_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); + mutex_unlock(&client_ctx->enrty_queue_lock); + 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); + mutex_unlock(&client_ctx->enrty_queue_lock); + 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 length) +{ + unsigned long len, phys_addr; + struct file *file = NULL; + u32 *num_of_buffers = NULL; + u32 i, flags; + struct buf_addr_table *buf_addr_table; + struct msm_mapped_buffer *mapped_buffer = NULL; + struct ion_handle *buff_ion_handle = NULL; + unsigned long ionflag = 0; + unsigned long iova = 0; + int ret = 0; + unsigned long buffer_size = 0; + size_t ion_len; + + if (!client_ctx || !length) + return false; + mutex_lock(&client_ctx->enrty_queue_lock); + 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); + length = length * 2; /* workaround for iommu video h/w bug */ + } + + if (*num_of_buffers == max_num_buffers) { + ERR("%s(): Num of buffers reached max value : %d", + __func__, max_num_buffers); + goto bail_out_add; + } + + 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); + goto bail_out_add; + } else { + if (!vcd_get_ion_status()) { + if (get_pmem_file(pmem_fd, &phys_addr, + kernel_vaddr, &len, &file)) { + ERR("%s(): get_pmem_file failed\n", __func__); + goto bail_out_add; + } + put_pmem_file(file); + flags = (buffer == BUFFER_TYPE_INPUT) + ? MSM_SUBSYSTEM_MAP_IOVA : + MSM_SUBSYSTEM_MAP_IOVA|MSM_SUBSYSTEM_ALIGN_IOVA_8K; + mapped_buffer = msm_subsystem_map_buffer(phys_addr, + length, flags, vidc_mmu_subsystem, + sizeof(vidc_mmu_subsystem)/sizeof(unsigned int)); + if (IS_ERR(mapped_buffer)) { + pr_err("buffer map failed"); + goto bail_out_add; + } + buf_addr_table[*num_of_buffers].client_data = (void *) + mapped_buffer; + buf_addr_table[*num_of_buffers].dev_addr = + mapped_buffer->iova[0]; + } else { + buff_ion_handle = ion_import_fd( + client_ctx->user_ion_client, pmem_fd); + if (IS_ERR_OR_NULL(buff_ion_handle)) { + ERR("%s(): get_ION_handle failed\n", + __func__); + goto bail_out_add; + } + if (ion_handle_get_flags(client_ctx->user_ion_client, + buff_ion_handle, + &ionflag)) { + ERR("%s():ION flags fail\n", + __func__); + goto bail_out_add; + } + *kernel_vaddr = (unsigned long) + ion_map_kernel( + client_ctx->user_ion_client, + buff_ion_handle, + ionflag); + if (IS_ERR_OR_NULL((void *)*kernel_vaddr)) { + ERR("%s():ION virtual addr fail\n", + __func__); + *kernel_vaddr = (unsigned long)NULL; + goto ion_free_error; + } + if (res_trk_check_for_sec_session()) { + if (ion_phys(client_ctx->user_ion_client, + buff_ion_handle, + &phys_addr, &ion_len)) { + ERR("%s():ION physical addr fail\n", + __func__); + goto ion_map_error; + } + len = (unsigned long) ion_len; + buf_addr_table[*num_of_buffers].client_data = + NULL; + buf_addr_table[*num_of_buffers].dev_addr = + phys_addr; + } else { + ret = ion_map_iommu(client_ctx->user_ion_client, + buff_ion_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL, + SZ_8K, + length, + (unsigned long *) &iova, + (unsigned long *) &buffer_size, + UNCACHED, + ION_IOMMU_UNMAP_DELAYED); + if (ret) { + ERR("%s():ION iommu map fail\n", + __func__); + goto ion_map_error; + } + phys_addr = iova; + buf_addr_table[*num_of_buffers].client_data = + NULL; + buf_addr_table[*num_of_buffers].dev_addr = + iova; + } + } + 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; + buf_addr_table[*num_of_buffers].buff_ion_handle = + buff_ion_handle; + buf_addr_table[*num_of_buffers].buff_ion_flag = + ionflag; + *num_of_buffers = *num_of_buffers + 1; + DBG("%s() : client_ctx = %p, user_virt_addr = 0x%08lx, " + "kernel_vaddr = 0x%08lx phys_addr=%lu inserted!", + __func__, client_ctx, user_vaddr, *kernel_vaddr, + phys_addr); + } + mutex_unlock(&client_ctx->enrty_queue_lock); + return true; +ion_map_error: + if (*kernel_vaddr && buff_ion_handle) + ion_unmap_kernel(client_ctx->user_ion_client, buff_ion_handle); +ion_free_error: + if (!IS_ERR_OR_NULL(buff_ion_handle)) + ion_free(client_ctx->user_ion_client, buff_ion_handle); +bail_out_add: + mutex_unlock(&client_ctx->enrty_queue_lock); + return false; +} +EXPORT_SYMBOL(vidc_insert_addr_table); + +/* + * Similar to vidc_insert_addr_table except intended for in-kernel + * use where buffers have already been alloced and mapped properly + */ +u32 vidc_insert_addr_table_kernel(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, unsigned long user_vaddr, + unsigned long kernel_vaddr, unsigned long phys_addr, + unsigned int max_num_buffers, + unsigned long length) +{ + u32 *num_of_buffers = NULL; + u32 i; + struct buf_addr_table *buf_addr_table; + struct msm_mapped_buffer *mapped_buffer = NULL; + + if (!client_ctx || !length || !kernel_vaddr || !phys_addr) + return false; + mutex_lock(&client_ctx->enrty_queue_lock); + 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); + goto bail_out_add; + } + + 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); + goto bail_out_add; + } else { + mapped_buffer = NULL; + buf_addr_table[*num_of_buffers].client_data = (void *) + mapped_buffer; + buf_addr_table[*num_of_buffers].dev_addr = phys_addr; + 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 = -1; + buf_addr_table[*num_of_buffers].file = NULL; + buf_addr_table[*num_of_buffers].phy_addr = phys_addr; + buf_addr_table[*num_of_buffers].buff_ion_handle = NULL; + *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); + } + mutex_unlock(&client_ctx->enrty_queue_lock); + return true; +bail_out_add: + mutex_unlock(&client_ctx->enrty_queue_lock); + return false; +} +EXPORT_SYMBOL(vidc_insert_addr_table_kernel); + +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; + mutex_lock(&client_ctx->enrty_queue_lock); + if (buffer == BUFFER_TYPE_INPUT) { + buf_addr_table = client_ctx->input_buf_addr_table; + num_of_buffers = &client_ctx->num_of_input_buffers; + + } else { + buf_addr_table = client_ctx->output_buf_addr_table; + num_of_buffers = &client_ctx->num_of_output_buffers; + } + + if (!*num_of_buffers) + goto bail_out_del; + + i = 0; + while (i < *num_of_buffers && + user_vaddr != buf_addr_table[i].user_vaddr) + i++; + if (i == *num_of_buffers) { + pr_err("%s() : client_ctx = %p." + " user_virt_addr = 0x%08lx NOT found", + __func__, client_ctx, user_vaddr); + goto bail_out_del; + } + if (buf_addr_table[i].client_data) { + msm_subsystem_unmap_buffer( + (struct msm_mapped_buffer *)buf_addr_table[i].client_data); + buf_addr_table[i].client_data = NULL; + } + *kernel_vaddr = buf_addr_table[i].kernel_vaddr; + if (buf_addr_table[i].buff_ion_handle) { + ion_unmap_kernel(client_ctx->user_ion_client, + buf_addr_table[i].buff_ion_handle); + if (!res_trk_check_for_sec_session()) { + ion_unmap_iommu(client_ctx->user_ion_client, + buf_addr_table[i].buff_ion_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + } + ion_free(client_ctx->user_ion_client, + buf_addr_table[i].buff_ion_handle); + buf_addr_table[i].buff_ion_handle = NULL; + } + if (i < (*num_of_buffers - 1)) { + buf_addr_table[i].client_data = + buf_addr_table[*num_of_buffers - 1].client_data; + buf_addr_table[i].dev_addr = + buf_addr_table[*num_of_buffers - 1].dev_addr; + 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; + buf_addr_table[i].buff_ion_handle = + buf_addr_table[*num_of_buffers - 1].buff_ion_handle; + } + *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); + mutex_unlock(&client_ctx->enrty_queue_lock); + return true; +bail_out_del: + mutex_unlock(&client_ctx->enrty_queue_lock); + return false; +} +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_internal.h b/drivers/video/msm/vidc/common/init/vidc_init_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..1d903ad24635c1402532b5e1e29a665791eaba4b --- /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 0000000000000000000000000000000000000000..3e02030cfb4bc3f757199461c05b7eae1d2e8d48 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd.h @@ -0,0 +1,400 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include "vcd_util.h" +#include "vcd_ddl_api.h" +#include "vcd_res_tracker_api.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); + +u32 vcd_req_perf_level(struct vcd_clnt_ctxt *cctxt, + struct vcd_property_perf_level *); + +u32 vcd_set_num_slices(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_update_decoder_perf_level(struct vcd_dev_ctxt *dev_ctxt, u32 perf_lvl); + +#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 0000000000000000000000000000000000000000..c66c2b77e7c184842b9925439cde10c82099148e --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_api.c @@ -0,0 +1,986 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#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); + +struct client_security_info { + int secure_enc; + int secure_dec; + int non_secure_enc; + int non_secure_dec; +}; + +static int vcd_get_clients_security_info(struct client_security_info *sec_info) +{ + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_clnt_ctxt *cctxt; + int count = 0; + if (!sec_info) { + VCD_MSG_ERROR("Invalid argument\n"); + return -EINVAL; + } + memset(sec_info, 0 , sizeof(*sec_info)); + drv_ctxt = vcd_get_drv_context(); + mutex_lock(&drv_ctxt->dev_mutex); + cctxt = drv_ctxt->dev_ctxt.cctxt_list_head; + while (cctxt) { + if (cctxt->secure && cctxt->decoding) + sec_info->secure_dec++; + else if (cctxt->secure && !cctxt->decoding) + sec_info->secure_enc++; + else if (!cctxt->secure && cctxt->decoding) + sec_info->non_secure_dec++; + else + sec_info->non_secure_enc++; + count++; + cctxt = cctxt->next; + } + mutex_unlock(&drv_ctxt->dev_mutex); + return count; +} + +static int is_session_invalid(u32 decoding, u32 flags) +{ + int is_secure; + struct client_security_info sec_info; + int client_count = 0; + int secure_session_running = 0; + is_secure = (flags & VCD_CP_SESSION) ? 1 : 0; + client_count = vcd_get_clients_security_info(&sec_info); + secure_session_running = (sec_info.secure_enc > 0) || + (sec_info.secure_dec > 0); + if (!decoding && is_secure) { + if ((sec_info.secure_dec == 1)) + VCD_MSG_LOW("SE-SD: SUCCESS\n"); + else { + VCD_MSG_LOW("SE is permitted only with SD: FAILURE\n"); + return -EACCES; + } + } else if (!decoding && !is_secure) { + if (secure_session_running) { + VCD_MSG_LOW("SD-NSE: FAILURE\n"); + VCD_MSG_LOW("SE-NSE: FAILURE\n"); + return -EACCES; + } + } else if (decoding && is_secure) { + if (client_count > 0) { + VCD_MSG_LOW("S/NS-SD: FAILURE\n"); + if (sec_info.secure_enc > 0 || + sec_info.non_secure_enc > 0) { + return -EAGAIN; + } + return -EACCES; + } + } else { + if (sec_info.secure_dec > 0) { + VCD_MSG_LOW("SD-NSD: FAILURE\n"); + return -EACCES; + } + } + return 0; +} + +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, int flags) +{ + u32 rc = 0; + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_clnt_ctxt *cctxt; + int is_secure = (flags & VCD_CP_SESSION) ? 1 : 0; + VCD_MSG_MED("vcd_open:"); + + if (!callback) { + VCD_MSG_ERROR("Bad parameters"); + return -EINVAL; + } + rc = is_session_invalid(decoding, flags); + if (rc) { + VCD_MSG_ERROR("Invalid Session: is_decoder: %d, secure: %d\n", + decoding, flags); + return rc; + } + if (is_secure) + res_trk_secure_set(); + 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); + if (rc) { + rc = -ENODEV; + } + } else { + VCD_MSG_ERROR("Unsupported API in device state %d", + drv_ctxt->dev_state.state); + rc = -EPERM; + } + if (!rc) { + cctxt = drv_ctxt->dev_ctxt.cctxt_list_head; + cctxt->secure = is_secure; + } else if (is_secure) + res_trk_secure_unset(); + + 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; + int is_secure = 0; + VCD_MSG_MED("vcd_close:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + is_secure = cctxt->secure; + 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); + if (is_secure) + res_trk_secure_unset(); + 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); + +u32 vcd_get_ion_status(void) +{ + return res_trk_get_enable_ion(); +} +EXPORT_SYMBOL(vcd_get_ion_status); + +struct ion_client *vcd_get_ion_client(void) +{ + return res_trk_get_ion_client(); +} +EXPORT_SYMBOL(vcd_get_ion_client); + + + + 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 0000000000000000000000000000000000000000..5019d31af8f117232e647cd507ab30b805f54713 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_client_sm.c @@ -0,0 +1,1884 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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.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->meta_mode && !cctxt->in_buf_pool.entries) || + !cctxt->out_buf_pool.entries || + (!cctxt->meta_mode && + cctxt->in_buf_pool.validated != cctxt->in_buf_pool.count) || + cctxt->out_buf_pool.validated != + cctxt->out_buf_pool.count) { + VCD_MSG_HIGH("%s: Buffer pool is not completely setup yet", + __func__); + } + + 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) +{ + u32 rc = VCD_S_SUCCESS; + 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); + if (!(cctxt->status.frame_submitted) && + (!cctxt->decoding)) { + 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_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); + if (rc) { + /* Some properties aren't known to ddl that we can handle */ + if (prop_hdr->prop_id != VCD_I_VOP_TIMING_CONSTANT_DELTA) + VCD_FAILED_RETURN(rc, "Failed: ddl_set_property"); + } + + switch (prop_hdr->prop_id) { + case VCD_I_META_BUFFER_MODE: + { + struct vcd_property_live *live = + (struct vcd_property_live *)prop_val; + cctxt->meta_mode = live->live; + break; + } + 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; + } + case VCD_REQ_PERF_LEVEL: + rc = vcd_req_perf_level(cctxt, + (struct vcd_property_perf_level *)prop_val); + break; + case VCD_I_VOP_TIMING_CONSTANT_DELTA: + { + struct vcd_property_vop_timing_constant_delta *delta = + prop_val; + + if (delta->constant_delta > 0) { + cctxt->time_frame_delta = delta->constant_delta; + rc = VCD_S_SUCCESS; + } else { + VCD_MSG_ERROR("Frame delta must be positive"); + rc = VCD_ERR_ILLEGAL_PARM; + } + 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) +{ + int rc; + 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; + } + rc = ddl_get_property(cctxt->ddl_handle, prop_hdr, prop_val); + if (rc) { + /* Some properties aren't known to ddl that we can handle */ + if (prop_hdr->prop_id != VCD_I_VOP_TIMING_CONSTANT_DELTA) + VCD_FAILED_RETURN(rc, "Failed: ddl_set_property"); + } + + switch (prop_hdr->prop_id) { + case VCD_I_VOP_TIMING_CONSTANT_DELTA: + { + struct vcd_property_vop_timing_constant_delta *delta = + (struct vcd_property_vop_timing_constant_delta *) + prop_val; + delta->constant_delta = cctxt->time_frame_delta; + rc = VCD_S_SUCCESS; + } + } + return rc; +} + +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 0000000000000000000000000000000000000000..9f2d63dbe326e71dda5910eeadb6aa916cfb2d2b --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_client_sm.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#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 0000000000000000000000000000000000000000..7ae4f452ba7ceabbfb37d1e471c07b76178b84a3 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_core.h @@ -0,0 +1,228 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#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 flags; + 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; + u32 num_slices; + + struct vcd_property_frame_rate frm_rate; + u32 frm_p_units; + u32 reqd_perf_lvl; + u32 time_resoln; + u32 time_frame_delta; + + 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 ion_client *vcd_ion_client; + u32 vcd_enable_ion; + struct vcd_clnt_ctxt *next; + u32 meta_mode; + int perf_set_by_client; + int secure; +}; + +#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 0000000000000000000000000000000000000000..d5170281ba9edee6b7f1d17dc7917a75e900dd62 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_device_sm.c @@ -0,0 +1,1218 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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.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, transc = 0x%x\n", + (u32)transc); + if (transc) { + VCD_MSG_ERROR("transc->in_use = %u, " + "transc->cctxt = 0x%x\n", + transc->in_use, + (u32)transc->cctxt); + } + } 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_HIGH("Device config mismatch. " + "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; + struct ddl_context *ddl_context; + + ddl_context = ddl_get_context(); + + 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; + if (tmp_client == dev_ctxt->cctxt_list_head) + fail_all_open = true; + + vcd_destroy_client_context(tmp_client); + } + } + + if (!client_inited || fail_all_open) { + VCD_MSG_ERROR("All client open requests failed"); + + DDL_IDLE(ddl_context); + + vcd_handle_device_init_failed(drv_ctxt, + DEVICE_STATE_EVENT_NUMBER(close)); + 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 0000000000000000000000000000000000000000..2443c33aa356473cc581e63fcd7c9ec84dc27a66 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_device_sm.h @@ -0,0 +1,96 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#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 0000000000000000000000000000000000000000..beaa87216438e152382baa71be43be04ca055104 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_power_sm.c @@ -0,0 +1,366 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_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_decoder_perf_level(struct vcd_dev_ctxt *dev_ctxt, u32 perf_lvl) +{ + u32 rc = VCD_S_SUCCESS; + + 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; + } + + 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 0000000000000000000000000000000000000000..26ce0196d488e76911d742098f19079985b8b91c --- /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_scheduler.c b/drivers/video/msm/vidc/common/vcd/vcd_scheduler.c new file mode 100644 index 0000000000000000000000000000000000000000..ab21bac49653430bacefccafe2ea5dc6485d3018 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_scheduler.c @@ -0,0 +1,287 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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.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"); + } + if (!cctxt->perf_set_by_client) + 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_sub.c b/drivers/video/msm/vidc/common/vcd/vcd_sub.c new file mode 100644 index 0000000000000000000000000000000000000000..6ca4dbe2a6653029111388d1f18bbf2e68231464 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_sub.c @@ -0,0 +1,3433 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include "vcd.h" +#include "vdec_internal.h" + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MAP_TABLE_SZ 64 +#define VCD_ENC_MAX_OUTBFRS_PER_FRAME 8 +#define MAX_DEC_TIME 33 + +struct vcd_msm_map_buffer { + phys_addr_t phy_addr; + struct msm_mapped_buffer *mapped_buffer; + struct ion_handle *alloc_handle; + u32 in_use; +}; +static struct vcd_msm_map_buffer msm_mapped_buffer_table[MAP_TABLE_SZ]; +static unsigned int vidc_mmu_subsystem[] = {MSM_SUBSYSTEM_VIDEO}; + +static int vcd_pmem_alloc(size_t sz, u8 **kernel_vaddr, u8 **phy_addr, + struct vcd_clnt_ctxt *cctxt) +{ + u32 memtype, i = 0, flags = 0; + struct vcd_msm_map_buffer *map_buffer = NULL; + struct msm_mapped_buffer *mapped_buffer = NULL; + unsigned long iova = 0; + unsigned long buffer_size = 0; + int ret = 0; + unsigned long ionflag = 0; + + if (!kernel_vaddr || !phy_addr || !cctxt) { + pr_err("\n%s: Invalid parameters", __func__); + goto bailout; + } + *phy_addr = NULL; + *kernel_vaddr = NULL; + for (i = 0; i < MAP_TABLE_SZ; i++) { + if (!msm_mapped_buffer_table[i].in_use) { + map_buffer = &msm_mapped_buffer_table[i]; + map_buffer->in_use = 1; + break; + } + } + if (!map_buffer) { + pr_err("%s() map table is full", __func__); + goto bailout; + } + res_trk_set_mem_type(DDL_MM_MEM); + memtype = res_trk_get_mem_type(); + if (!cctxt->vcd_enable_ion) { + map_buffer->phy_addr = (phys_addr_t) + allocate_contiguous_memory_nomap(sz, memtype, SZ_4K); + if (!map_buffer->phy_addr) { + pr_err("%s() acm alloc failed", __func__); + goto free_map_table; + } + flags = MSM_SUBSYSTEM_MAP_IOVA | MSM_SUBSYSTEM_MAP_KADDR; + map_buffer->mapped_buffer = + msm_subsystem_map_buffer((unsigned long)map_buffer->phy_addr, + sz, flags, vidc_mmu_subsystem, + sizeof(vidc_mmu_subsystem)/sizeof(unsigned int)); + if (IS_ERR(map_buffer->mapped_buffer)) { + pr_err(" %s() buffer map failed", __func__); + goto free_acm_alloc; + } + mapped_buffer = map_buffer->mapped_buffer; + if (!mapped_buffer->vaddr || !mapped_buffer->iova[0]) { + pr_err("%s() map buffers failed", __func__); + goto free_map_buffers; + } + *phy_addr = (u8 *) mapped_buffer->iova[0]; + *kernel_vaddr = (u8 *) mapped_buffer->vaddr; + } else { + map_buffer->alloc_handle = ion_alloc( + cctxt->vcd_ion_client, sz, SZ_4K, + memtype); + if (!map_buffer->alloc_handle) { + pr_err("%s() ION alloc failed", __func__); + goto bailout; + } + if (ion_handle_get_flags(cctxt->vcd_ion_client, + map_buffer->alloc_handle, + &ionflag)) { + pr_err("%s() ION get flag failed", __func__); + goto bailout; + } + *kernel_vaddr = (u8 *) ion_map_kernel( + cctxt->vcd_ion_client, + map_buffer->alloc_handle, + ionflag); + if (!(*kernel_vaddr)) { + pr_err("%s() ION map failed", __func__); + goto ion_free_bailout; + } + ret = ion_map_iommu(cctxt->vcd_ion_client, + map_buffer->alloc_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL, + SZ_4K, + 0, + (unsigned long *)&iova, + (unsigned long *)&buffer_size, + UNCACHED, 0); + if (ret) { + pr_err("%s() ION iommu map failed", __func__); + goto ion_map_bailout; + } + map_buffer->phy_addr = iova; + if (!map_buffer->phy_addr) { + pr_err("%s() acm alloc failed", __func__); + goto free_map_table; + } + *phy_addr = (u8 *)iova; + mapped_buffer = NULL; + map_buffer->mapped_buffer = NULL; + } + + return 0; + +free_map_buffers: + if (map_buffer->mapped_buffer) + msm_subsystem_unmap_buffer(map_buffer->mapped_buffer); +free_acm_alloc: + if (!cctxt->vcd_enable_ion) { + free_contiguous_memory_by_paddr( + (unsigned long)map_buffer->phy_addr); + } + return -ENOMEM; +ion_map_bailout: + ion_unmap_kernel(cctxt->vcd_ion_client, map_buffer->alloc_handle); +ion_free_bailout: + ion_free(cctxt->vcd_ion_client, map_buffer->alloc_handle); +free_map_table: + map_buffer->in_use = 0; +bailout: + return -ENOMEM; +} + +static int vcd_pmem_free(u8 *kernel_vaddr, u8 *phy_addr, + struct vcd_clnt_ctxt *cctxt) +{ + u32 i = 0; + struct vcd_msm_map_buffer *map_buffer = NULL; + + if (!kernel_vaddr || !phy_addr || !cctxt) { + pr_err("\n%s: Invalid parameters", __func__); + goto bailout; + } + for (i = 0; i < MAP_TABLE_SZ; i++) { + if (msm_mapped_buffer_table[i].in_use && + (msm_mapped_buffer_table[i] + .mapped_buffer->vaddr == kernel_vaddr)) { + map_buffer = &msm_mapped_buffer_table[i]; + map_buffer->in_use = 0; + break; + } + } + if (!map_buffer) { + pr_err("%s() Entry not found", __func__); + goto bailout; + } + if (map_buffer->mapped_buffer) + msm_subsystem_unmap_buffer(map_buffer->mapped_buffer); + if (cctxt->vcd_enable_ion) { + if (map_buffer->alloc_handle) { + ion_unmap_kernel(cctxt->vcd_ion_client, + map_buffer->alloc_handle); + ion_unmap_iommu(cctxt->vcd_ion_client, + map_buffer->alloc_handle, + VIDEO_DOMAIN, + VIDEO_MAIN_POOL); + ion_free(cctxt->vcd_ion_client, + map_buffer->alloc_handle); + } + } else { + free_contiguous_memory_by_paddr( + (unsigned long)map_buffer->phy_addr); + } +bailout: + 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; + } + +} + +u32 vcd_get_ion_flag(struct video_client_ctx *client_ctx, + unsigned long kernel_vaddr, + struct ion_handle **buff_ion_handle) +{ + unsigned long phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + u32 ion_flag = 0; + struct ion_handle *buff_handle = NULL; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_INPUT, + false, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + ion_flag = vidc_get_fd_info(client_ctx, BUFFER_TYPE_INPUT, + pmem_fd, kernel_vaddr, buffer_index, + &buff_handle); + *buff_ion_handle = buff_handle; + return ion_flag; + } else if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + false, &user_vaddr, &kernel_vaddr, &phy_addr, &pmem_fd, &file, + &buffer_index)) { + ion_flag = vidc_get_fd_info(client_ctx, BUFFER_TYPE_OUTPUT, + pmem_fd, kernel_vaddr, buffer_index, + &buff_handle); + *buff_ion_handle = buff_handle; + return ion_flag; + } else { + VCD_MSG_ERROR("Couldn't get ion flag"); + return 0; + } + +} + +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; + u32 ion_flag = 0; + struct ion_handle *buff_handle = NULL; + + 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); + + ion_flag = vcd_get_ion_flag(cctxt->client_data, + (unsigned long)buffer, + &buff_handle); + 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_entry->frame.ion_flag = ion_flag; + buf_entry->frame.buff_ion_handle = buff_handle; + + 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, cctxt); + + 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 && !cctxt->meta_mode) { + 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, cctxt); + 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, cctxt); + } + } + + } + + 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->vcd_enable_ion = res_trk_get_enable_ion(); + if (cctxt->vcd_enable_ion) { + cctxt->vcd_ion_client = res_trk_get_ion_client(); + if (!cctxt->vcd_ion_client) { + VCD_MSG_LOW("vcd_init_ion_get_client_failed:"); + return -EINVAL; + } + } + 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; + cctxt->num_slices = 1; + 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); + 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; + cctxt->vcd_ion_client = 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 { + vcd_set_num_slices(transc->cctxt); + 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; + u32 perf_level = 0; + int decodeTime = 0; + struct ddl_frame_data_tag ddl_ip_frm; + struct ddl_frame_data_tag *ddl_op_frm; + u32 out_buf_cnt = 0; + + 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; + transc->flags = ip_frm_entry->flags; + ip_frm_entry->ip_frm_tag = (u32) transc; + memset(&ddl_ip_frm, 0, sizeof(ddl_ip_frm)); + if (cctxt->decoding) { + decodeTime = ddl_get_core_decode_proc_time(cctxt->ddl_handle); + if (decodeTime > MAX_DEC_TIME) { + if (res_trk_get_curr_perf_level(&perf_level)) { + vcd_update_decoder_perf_level(dev_ctxt, + res_trk_estimate_perf_level(perf_level)); + ddl_reset_avg_dec_time(cctxt->ddl_handle); + } else + VCD_MSG_ERROR("%s(): retrieve curr_perf_level" + "returned FALSE\n", __func__); + } + 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 { + ddl_op_frm = (struct ddl_frame_data_tag *) + kmalloc((sizeof(struct ddl_frame_data_tag) * + VCD_ENC_MAX_OUTBFRS_PER_FRAME), GFP_KERNEL); + if (!ddl_op_frm) { + VCD_MSG_ERROR("Memory allocation failure"); + return VCD_ERR_ALLOC_FAIL; + } + memset(ddl_op_frm, 0, (sizeof(struct ddl_frame_data_tag) * + VCD_ENC_MAX_OUTBFRS_PER_FRAME)); + for (out_buf_cnt = 0; out_buf_cnt < cctxt->num_slices ; + out_buf_cnt++) { + 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; + break; + } + op_buf_entry->in_use = true; + cctxt->out_buf_pool.in_use++; + ddl_op_frm[out_buf_cnt].vcd_frm = op_buf_entry->frame; + VCD_MSG_LOW("%s : buffer_cnt = %d framebfr(virtual)" + " 0x%p", __func__, out_buf_cnt, + op_buf_entry->frame.virtual); + VCD_MSG_LOW("framebfr(physical) 0x%p bfrlength %d", + op_buf_entry->frame.physical, + op_buf_entry->frame.alloc_len); + } + ddl_ip_frm.vcd_frm = *ip_frm_entry; + ddl_ip_frm.frm_delta = + vcd_calculate_frame_delta(cctxt, + ip_frm_entry); + evcode = CLIENT_STATE_EVENT_NUMBER(encode_frame); + + if (!VCD_FAILED(rc)) { + rc = ddl_encode_frame(cctxt->ddl_handle, + &ddl_ip_frm, &ddl_op_frm[0], (void *) transc); + } + kfree(ddl_op_frm); + } + 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; + VCD_MSG_LOW("%s: Get transc = 0x%x, in_use = %u\n", + __func__, (u32)(&dev_ctxt->trans_tbl[i]), + dev_ctxt->trans_tbl[i].in_use); + 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; + VCD_MSG_LOW("%s: Free transc = 0x%x, in_use = %u\n", + __func__, (u32)trans_entry, trans_entry->in_use); + } +} + +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; + struct vcd_buffer_entry *orig_frame = NULL; + u32 rc; + VCD_MSG_LOW("%s\n", __func__); + + 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; + orig_frame = vcd_find_buffer_pool_entry(&cctxt->in_buf_pool, + transc->ip_buf_entry->virtual); + + 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); + + orig_frame->in_use--; + VCD_BUFFERPOOL_INUSE_DECREMENT(cctxt->in_buf_pool.in_use); + + if (cctxt->decoding && orig_frame->in_use) { + VCD_MSG_ERROR("When decoding same input buffer not " + "supposed to be queued multiple times"); + return VCD_ERR_FAIL; + } + + if (orig_frame != transc->ip_buf_entry) + kfree(transc->ip_buf_entry); + transc->ip_buf_entry = NULL; + transc->input_done = true; + + if (transc->input_done && transc->frame_done) { + VCD_MSG_LOW("%s Calling vcd_release_trans_tbl_entry\n", + __func__); + 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(); + VCD_MSG_LOW("%s\n", __func__); + 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)) || + ((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; + VCD_MSG_LOW("%s\n", __func__); + + 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; + VCD_MSG_LOW("%s\n", __func__); + + 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; + s64 time_stamp; + VCD_MSG_LOW("%s\n", __func__); + + 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 (transc->flags & VCD_FRAME_FLAG_EOSEQ) + op_frm->vcd_frm.flags |= VCD_FRAME_FLAG_EOSEQ; + else + op_frm->vcd_frm.flags &= ~VCD_FRAME_FLAG_EOSEQ; + + 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) { + time_stamp = transc->time_stamp; + 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(VCD_EVT_IND_INFO_FIELD_DROPPED, + VCD_S_SUCCESS, + &time_stamp, + sizeof(time_stamp), + cctxt, cctxt->client_data); + } + } + + 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("%s\n", __func__); + 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_sps_pps_for_idr_enable idr_enable; + 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) { + /* + * The seq. header is stored in a seperate internal + * buffer and is memcopied into the output buffer + * that we provide. In secure sessions, we aren't + * allowed to touch these buffers. In these cases + * seq. headers are returned to client as part of + * I-frames. So for secure session, just return + * empty buffer. + */ + if (!cctxt->secure) { + 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; + } else + VCD_MSG_ERROR("rc = 0x%x. Failed:" + "ddl_get_property: VCD_I_SEQ_HEADER", + rc); + } else { + /* + * First check that the proper props are enabled + * so client can get the proper info eventually + */ + prop_hdr.prop_id = VCD_I_ENABLE_SPS_PPS_FOR_IDR; + prop_hdr.sz = sizeof(idr_enable); + rc = ddl_get_property(cctxt->ddl_handle, + &prop_hdr, &idr_enable); + if (!VCD_FAILED(rc)) { + if (!idr_enable. + sps_pps_for_idr_enable_flag) { + VCD_MSG_ERROR("SPS/PPS per IDR " + "needs to be enabled"); + rc = VCD_ERR_BAD_STATE; + } else { + /* Send zero len frame */ + frm_entry->data_len = 0; + frm_entry->time_stamp = 0; + frm_entry->flags = 0; + } + } + + } + + if (!VCD_FAILED(rc)) + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, frm_entry, + sizeof(struct vcd_frame_data), + cctxt, + cctxt->client_data); + } 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, *orig_frame; + 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"); + + orig_frame = vcd_find_buffer_pool_entry(&cctxt->in_buf_pool, + input_frame->virtual); + if (!orig_frame) { + VCD_MSG_ERROR("Bad buffer addr: %p", input_frame->virtual); + return VCD_ERR_FAIL; + } + + if (orig_frame->in_use) { + /* + * This path only allowed for enc., dec. not allowed + * to queue same buffer multiple times + */ + if (cctxt->decoding) { + VCD_MSG_ERROR("An inuse input frame is being " + "re-queued to scheduler"); + return VCD_ERR_FAIL; + } + + buf_entry = kzalloc(sizeof(*buf_entry), GFP_KERNEL); + if (!buf_entry) { + VCD_MSG_ERROR("Unable to alloc memory"); + return VCD_ERR_FAIL; + } + + INIT_LIST_HEAD(&buf_entry->sched_list); + /* + * Pre-emptively poisoning this, as these dupe entries + * shouldn't get added to any list + */ + INIT_LIST_HEAD(&buf_entry->list); + buf_entry->list.next = LIST_POISON1; + buf_entry->list.prev = LIST_POISON2; + + buf_entry->valid = orig_frame->valid; + buf_entry->alloc = orig_frame->alloc; + buf_entry->virtual = orig_frame->virtual; + buf_entry->physical = orig_frame->physical; + buf_entry->sz = orig_frame->sz; + buf_entry->allocated = orig_frame->allocated; + buf_entry->in_use = 1; /* meaningless for the dupe buffers */ + buf_entry->frame = orig_frame->frame; + } else + buf_entry = orig_frame; + + 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"); + + orig_frame->in_use++; + 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); + 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), cctxt); + + 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_req_perf_level( + struct vcd_clnt_ctxt *cctxt, + struct vcd_property_perf_level *perf_level) +{ + u32 rc; + u32 res_trk_perf_level; + if (!perf_level) { + VCD_MSG_ERROR("Invalid parameters\n"); + return -EINVAL; + } + res_trk_perf_level = get_res_trk_perf_level(perf_level->level); + if (res_trk_perf_level < 0) { + rc = -ENOTSUPP; + goto perf_level_not_supp; + } + rc = vcd_set_perf_level(cctxt->dev_ctxt, res_trk_perf_level); + if (!rc) { + cctxt->reqd_perf_lvl = res_trk_perf_level; + cctxt->perf_set_by_client = 1; + } +perf_level_not_supp: + 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; + if (res_trk_get_disable_fullhd() && frm_size && + (frm_size->width * frm_size->height > 1280 * 720)) { + VCD_MSG_ERROR("Frame size = %dX%d greater than 1280X720 not" + "supported", frm_size->width, frm_size->height); + return VCD_ERR_FAIL; + } + 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 (cctxt->time_frame_delta) + temp = cctxt->time_frame_delta; + else 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\n", + 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); + } +} + +u32 vcd_set_num_slices(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_property_hdr prop_hdr; + struct vcd_property_slice_delivery_info slice_delivery_info; + u32 rc = VCD_S_SUCCESS; + prop_hdr.prop_id = VCD_I_SLICE_DELIVERY_MODE; + prop_hdr.sz = prop_hdr.sz = + sizeof(struct vcd_property_slice_delivery_info); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, + &slice_delivery_info); + VCD_FAILED_RETURN(rc, "Failed: Get VCD_I_SLICE_DELIVERY_MODE"); + if (slice_delivery_info.enable) { + cctxt->num_slices = slice_delivery_info.num_slices; + VCD_MSG_LOW("%s slice delivery mode num_slices = %u\n", + __func__, cctxt->num_slices); + } else { + cctxt->num_slices = 1; + } + return rc; +} 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 0000000000000000000000000000000000000000..ba991f1ccde320c2b0737e1b5c13451916bdc896 --- /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 0000000000000000000000000000000000000000..7164029cfa8f1226fa4f9f104fbb1351b8a21a8a --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_util.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include + +#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/vfb.c b/drivers/video/vfb.c index 501a922aa9dc425c0d45258c8fb19ac897d51da9..2428997c3a0e96847001726980a196a5ae4b4491 100644 --- a/drivers/video/vfb.c +++ b/drivers/video/vfb.c @@ -35,6 +35,13 @@ static void *videomemory; static u_long videomemorysize = VIDEOMEMSIZE; module_param(videomemorysize, ulong, 0); +static char *mode_option __devinitdata; +static int bpp __devinitdata = 8; + +module_param(mode_option, charp, 0); +MODULE_PARM_DESC(mode_option, "Initial video mode e.g. '648x480-8@60'"); +module_param(bpp, int, 0); + /********************************************************************** * @@ -469,6 +476,14 @@ static int __init vfb_setup(char *options) /* Test disable for backwards compatibility */ if (!strcmp(this_opt, "disable")) vfb_enable = 0; + else if (!strncmp(this_opt, "bpp=", 4)) { + if (kstrtoint(this_opt + 4, 0, &bpp) < 0) + bpp = 8; + } else if (!strncmp(this_opt, "memsize=", 8)) { + if (kstrtoul(this_opt + 8, 0, &videomemorysize) < 0) + videomemorysize = VIDEOMEMSIZE; + } else + mode_option = this_opt; } return 1; } @@ -504,8 +519,8 @@ static int __devinit vfb_probe(struct platform_device *dev) info->screen_base = (char __iomem *)videomemory; info->fbops = &vfb_ops; - retval = fb_find_mode(&info->var, info, NULL, - NULL, 0, NULL, 8); + retval = fb_find_mode(&info->var, info, mode_option, + NULL, 0, NULL, bpp); if (!retval || (retval == 4)) info->var = vfb_default; diff --git a/fs/compat_ioctl.c b/fs/compat_ioctl.c index debdfe0fc809edfd01ac4a72a0eaf2753efc993d..335952e9da74d4c98fc1a68268f801cc04d36e3f 100644 --- a/fs/compat_ioctl.c +++ b/fs/compat_ioctl.c @@ -1214,6 +1214,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 e06190322c1c8af1544034c1476f3c779b165318..3f3e5354f8e710f28494d9954e9e7ef28e186fe0 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/jbd2/journal.c b/fs/jbd2/journal.c index 1afb701622b0b17748b4cd7f7d171df139bcc9cc..e52effdcdc00fdfbb5caa5b3353a91798e163501 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -1413,6 +1413,14 @@ static int journal_get_superblock(journal_t *journal) goto out; } + if (be32_to_cpu(sb->s_first) == 0 || + be32_to_cpu(sb->s_first) >= journal->j_maxlen) { + printk(KERN_WARNING + "JBD2: Invalid start block of journal: %u\n", + be32_to_cpu(sb->s_first)); + goto out; + } + return 0; out: diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 74c00bc92b9af6b01e95e55c119b90d61fbf9d34..e35df2d93249426bf99520ab64fea9f94ac5e48d 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -1721,6 +1721,18 @@ static void write_cinfo(__be32 **p, struct nfsd4_change_info *c) \ save = resp->p; +static bool seqid_mutating_err(__be32 err) +{ + /* rfc 3530 section 8.1.5: */ + return err != nfserr_stale_clientid && + err != nfserr_stale_stateid && + err != nfserr_bad_stateid && + err != nfserr_bad_seqid && + err != nfserr_bad_xdr && + err != nfserr_resource && + err != nfserr_nofilehandle; +} + /* * Routine for encoding the result of a "seqid-mutating" NFSv4 operation. This * is where sequence id's are incremented, and the replay cache is filled. diff --git a/fs/yaffs2/yaffs_mtdif.c b/fs/yaffs2/yaffs_mtdif.c index 7cf53b3d91be5057a0ff9d2dfb6d3994d48b12ac..f6bf8e56d912d8b77d76d6d9bdcd20860870da4a 100644 --- a/fs/yaffs2/yaffs_mtdif.c +++ b/fs/yaffs2/yaffs_mtdif.c @@ -40,7 +40,7 @@ int nandmtd_erase_block(struct yaffs_dev *dev, int block_no) ei.callback = NULL; ei.priv = (u_long) dev; - retval = mtd->erase(mtd, &ei); + retval = mtd_erase(mtd, &ei); if (retval == 0) return YAFFS_OK; diff --git a/fs/yaffs2/yaffs_mtdif1.c b/fs/yaffs2/yaffs_mtdif1.c index 51083695eb3347eea20734317ac27d2b276338e8..c449babb74b9e1dbc13b42a3dc3d7744262bef73 100644 --- a/fs/yaffs2/yaffs_mtdif1.c +++ b/fs/yaffs2/yaffs_mtdif1.c @@ -103,13 +103,13 @@ int nandmtd1_write_chunk_tags(struct yaffs_dev *dev, #endif memset(&ops, 0, sizeof(ops)); - ops.mode = MTD_OOB_AUTO; + ops.mode = MTD_OPS_AUTO_OOB; ops.len = (data) ? chunk_bytes : 0; ops.ooblen = YTAG1_SIZE; ops.datbuf = (u8 *) data; ops.oobbuf = (u8 *) & pt1; - retval = mtd->write_oob(mtd, addr, &ops); + retval = mtd_write_oob(mtd, addr, &ops); if (retval) { yaffs_trace(YAFFS_TRACE_MTD, "write_oob failed, chunk %d, mtd error %d", @@ -156,7 +156,7 @@ int nandmtd1_read_chunk_tags(struct yaffs_dev *dev, int deleted; memset(&ops, 0, sizeof(ops)); - ops.mode = MTD_OOB_AUTO; + ops.mode = MTD_OPS_AUTO_OOB; ops.len = (data) ? chunk_bytes : 0; ops.ooblen = YTAG1_SIZE; ops.datbuf = data; @@ -165,7 +165,7 @@ int nandmtd1_read_chunk_tags(struct yaffs_dev *dev, /* Read page and oob using MTD. * Check status and determine ECC result. */ - retval = mtd->read_oob(mtd, addr, &ops); + retval = mtd_read_oob(mtd, addr, &ops); if (retval) { yaffs_trace(YAFFS_TRACE_MTD, "read_oob failed, chunk %d, mtd error %d", @@ -189,7 +189,7 @@ int nandmtd1_read_chunk_tags(struct yaffs_dev *dev, /* fall into... */ default: rettags(etags, YAFFS_ECC_RESULT_UNFIXED, 0); - etags->block_bad = (mtd->block_isbad) (mtd, addr); + etags->block_bad = mtd_block_isbad(mtd, addr); return YAFFS_FAIL; } @@ -257,7 +257,7 @@ int nandmtd1_mark_block_bad(struct yaffs_dev *dev, int block_no) yaffs_trace(YAFFS_TRACE_BAD_BLOCKS, "marking block %d bad", block_no); - retval = mtd->block_markbad(mtd, (loff_t) blocksize * block_no); + retval = mtd_block_markbad(mtd, (loff_t) blocksize * block_no); return (retval) ? YAFFS_FAIL : YAFFS_OK; } @@ -307,7 +307,7 @@ int nandmtd1_query_block(struct yaffs_dev *dev, int block_no, return YAFFS_FAIL; retval = nandmtd1_read_chunk_tags(dev, chunk_num, NULL, &etags); - etags.block_bad = (mtd->block_isbad) (mtd, addr); + etags.block_bad = mtd_block_isbad(mtd, addr); if (etags.block_bad) { yaffs_trace(YAFFS_TRACE_BAD_BLOCKS, "block %d is marked bad", block_no); diff --git a/fs/yaffs2/yaffs_mtdif2.c b/fs/yaffs2/yaffs_mtdif2.c index d1643df2c381b89e92b403a09e12732b39a91aac..df6928179d63d77d90b3ffbe2e5587bc3ab3c0a6 100644 --- a/fs/yaffs2/yaffs_mtdif2.c +++ b/fs/yaffs2/yaffs_mtdif2.c @@ -70,13 +70,13 @@ int nandmtd2_write_chunk_tags(struct yaffs_dev *dev, int nand_chunk, yaffs_pack_tags2(&pt, tags, !dev->param.no_tags_ecc); } - ops.mode = MTD_OOB_AUTO; + ops.mode = MTD_OPS_AUTO_OOB; ops.ooblen = (dev->param.inband_tags) ? 0 : packed_tags_size; ops.len = dev->param.total_bytes_per_chunk; ops.ooboffs = 0; ops.datbuf = (u8 *) data; ops.oobbuf = (dev->param.inband_tags) ? NULL : packed_tags_ptr; - retval = mtd->write_oob(mtd, addr, &ops); + retval = mtd_write_oob(mtd, addr, &ops); if (retval == 0) return YAFFS_OK; @@ -117,16 +117,16 @@ int nandmtd2_read_chunk_tags(struct yaffs_dev *dev, int nand_chunk, } if (dev->param.inband_tags || (data && !tags)) - retval = mtd->read(mtd, addr, dev->param.total_bytes_per_chunk, + retval = mtd_read(mtd, addr, dev->param.total_bytes_per_chunk, &dummy, data); else if (tags) { - ops.mode = MTD_OOB_AUTO; + ops.mode = MTD_OPS_AUTO_OOB; ops.ooblen = packed_tags_size; ops.len = data ? dev->data_bytes_per_chunk : packed_tags_size; ops.ooboffs = 0; ops.datbuf = data; ops.oobbuf = yaffs_dev_to_lc(dev)->spare_buffer; - retval = mtd->read_oob(mtd, addr, &ops); + retval = mtd_read_oob(mtd, addr, &ops); } if (dev->param.inband_tags) { @@ -173,7 +173,7 @@ int nandmtd2_mark_block_bad(struct yaffs_dev *dev, int block_no) "nandmtd2_mark_block_bad %d", block_no); retval = - mtd->block_markbad(mtd, + mtd_block_markbad(mtd, block_no * dev->param.chunks_per_block * dev->param.total_bytes_per_chunk); @@ -192,7 +192,7 @@ int nandmtd2_query_block(struct yaffs_dev *dev, int block_no, yaffs_trace(YAFFS_TRACE_MTD, "nandmtd2_query_block %d", block_no); retval = - mtd->block_isbad(mtd, + mtd_block_isbad(mtd, block_no * dev->param.chunks_per_block * dev->param.total_bytes_per_chunk); diff --git a/fs/yaffs2/yaffs_vfs.c b/fs/yaffs2/yaffs_vfs.c index d5b875314005a36afe115ba7abd656bf6b3eff39..8e8c55b605a104f567c183c457e10a49443286a0 100644 --- a/fs/yaffs2/yaffs_vfs.c +++ b/fs/yaffs2/yaffs_vfs.c @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ #include #include #include +#include #include @@ -66,7 +66,7 @@ #define YPROC_ROOT NULL -#define Y_INIT_TIMER(a) init_timer_on_stack(a) +#define Y_INIT_TIMER(a, b, c) setup_deferrable_timer_on_stack(a, b, c) #define WRITE_SIZE_STR "writesize" #define WRITE_SIZE(mtd) ((mtd)->writesize) @@ -202,7 +202,7 @@ struct inode *yaffs_get_inode(struct super_block *sb, int mode, int dev, return inode; } -static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, +static int yaffs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; @@ -282,12 +282,12 @@ static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, return error; } -static int yaffs_mkdir(struct inode *dir, struct dentry *dentry, int mode) +static int yaffs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { return yaffs_mknod(dir, dentry, mode | S_IFDIR, 0); } -static int yaffs_create(struct inode *dir, struct dentry *dentry, int mode, +static int yaffs_create(struct inode *dir, struct dentry *dentry, umode_t mode, struct nameidata *n) { return yaffs_mknod(dir, dentry, mode | S_IFREG, 0); @@ -314,7 +314,7 @@ static int yaffs_link(struct dentry *old_dentry, struct inode *dir, obj); if (link) { - old_dentry->d_inode->i_nlink = yaffs_get_obj_link_count(obj); + set_nlink(old_dentry->d_inode, yaffs_get_obj_link_count(obj)); d_instantiate(dentry, old_dentry->d_inode); atomic_inc(&old_dentry->d_inode->i_count); yaffs_trace(YAFFS_TRACE_OS, @@ -429,7 +429,7 @@ static int yaffs_unlink(struct inode *dir, struct dentry *dentry) ret_val = yaffs_unlinker(obj, dentry->d_name.name); if (ret_val == YAFFS_OK) { - dentry->d_inode->i_nlink--; + drop_nlink(dentry->d_inode); dir->i_version++; yaffs_gross_unlock(dev); mark_inode_dirty(dentry->d_inode); @@ -440,7 +440,7 @@ static int yaffs_unlink(struct inode *dir, struct dentry *dentry) return -ENOTEMPTY; } -static int yaffs_sync_object(struct file *file, int datasync) +static int yaffs_sync_object(struct file *file, loff_t a, loff_t b, int datasync) { struct yaffs_obj *obj; @@ -497,7 +497,7 @@ static int yaffs_rename(struct inode *old_dir, struct dentry *old_dentry, if (ret_val == YAFFS_OK) { if (target) { - new_dentry->d_inode->i_nlink--; + drop_nlink(new_dentry->d_inode); mark_inode_dirty(new_dentry->d_inode); } @@ -1140,6 +1140,10 @@ static int yaffs_readpage_nolock(struct file *f, struct page *pg) (unsigned)(pg->index << PAGE_CACHE_SHIFT), (unsigned)PAGE_CACHE_SIZE); + ret = cleancache_get_page(pg); + if (!ret) + goto cleancache_got; + obj = yaffs_dentry_to_obj(f->f_dentry); dev = obj->my_dev; @@ -1159,11 +1163,13 @@ static int yaffs_readpage_nolock(struct file *f, struct page *pg) if (ret >= 0) ret = 0; +cleancache_got: if (ret) { ClearPageUptodate(pg); SetPageError(pg); } else { SetPageUptodate(pg); + SetPageMappedToDisk(pg); ClearPageError(pg); } @@ -1675,10 +1681,9 @@ static int yaffs_bg_thread_fn(void *data) if (time_before(expires, now)) expires = now + HZ; - Y_INIT_TIMER(&timer); + Y_INIT_TIMER(&timer, yaffs_background_waker, + (unsigned long)current); timer.expires = expires + 1; - timer.data = (unsigned long)current; - timer.function = yaffs_background_waker; set_current_state(TASK_INTERRUPTIBLE); add_timer(&timer); @@ -1917,7 +1922,7 @@ static void yaffs_fill_inode_from_obj(struct inode *inode, inode->i_size = yaffs_get_obj_length(obj); inode->i_blocks = (inode->i_size + 511) >> 9; - inode->i_nlink = yaffs_get_obj_link_count(obj); + set_nlink(inode, yaffs_get_obj_link_count(obj)); yaffs_trace(YAFFS_TRACE_OS, "yaffs_fill_inode mode %x uid %d gid %d size %d count %d", @@ -1992,8 +1997,7 @@ static void yaffs_mtd_put_super(struct super_block *sb) { struct mtd_info *mtd = yaffs_dev_to_mtd(yaffs_super_to_dev(sb)); - if (mtd->sync) - mtd->sync(mtd); + mtd_sync(mtd); put_mtd_device(mtd); } @@ -2094,13 +2098,13 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version, return NULL; } - yaffs_trace(YAFFS_TRACE_OS, " erase %p", mtd->erase); - yaffs_trace(YAFFS_TRACE_OS, " read %p", mtd->read); - yaffs_trace(YAFFS_TRACE_OS, " write %p", mtd->write); - yaffs_trace(YAFFS_TRACE_OS, " readoob %p", mtd->read_oob); - yaffs_trace(YAFFS_TRACE_OS, " writeoob %p", mtd->write_oob); - yaffs_trace(YAFFS_TRACE_OS, " block_isbad %p", mtd->block_isbad); - yaffs_trace(YAFFS_TRACE_OS, " block_markbad %p", mtd->block_markbad); + yaffs_trace(YAFFS_TRACE_OS, " erase %p", mtd->_erase); + yaffs_trace(YAFFS_TRACE_OS, " read %p", mtd->_read); + yaffs_trace(YAFFS_TRACE_OS, " write %p", mtd->_write); + yaffs_trace(YAFFS_TRACE_OS, " readoob %p", mtd->_read_oob); + yaffs_trace(YAFFS_TRACE_OS, " writeoob %p", mtd->_write_oob); + yaffs_trace(YAFFS_TRACE_OS, " block_isbad %p", mtd->_block_isbad); + yaffs_trace(YAFFS_TRACE_OS, " block_markbad %p", mtd->_block_markbad); yaffs_trace(YAFFS_TRACE_OS, " %s %d", WRITE_SIZE_STR, WRITE_SIZE(mtd)); yaffs_trace(YAFFS_TRACE_OS, " oobsize %d", mtd->oobsize); yaffs_trace(YAFFS_TRACE_OS, " erasesize %d", mtd->erasesize); @@ -2123,11 +2127,11 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version, if (yaffs_version == 2) { /* Check for version 2 style functions */ - if (!mtd->erase || - !mtd->block_isbad || - !mtd->block_markbad || - !mtd->read || - !mtd->write || !mtd->read_oob || !mtd->write_oob) { + if (!mtd->_erase || + !mtd->_block_isbad || + !mtd->_block_markbad || + !mtd->_read || + !mtd->_write || !mtd->_read_oob || !mtd->_write_oob) { yaffs_trace(YAFFS_TRACE_ALWAYS, "MTD device does not support required functions"); return NULL; @@ -2142,9 +2146,9 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version, } } else { /* Check for V1 style functions */ - if (!mtd->erase || - !mtd->read || - !mtd->write || !mtd->read_oob || !mtd->write_oob) { + if (!mtd->_erase || + !mtd->_read || + !mtd->_write || !mtd->_read_oob || !mtd->_write_oob) { yaffs_trace(YAFFS_TRACE_ALWAYS, "MTD device does not support required functions"); return NULL; @@ -2351,9 +2355,9 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version, yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: got root inode"); - root = d_alloc_root(inode); + root = d_make_root(inode); - yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: d_alloc_root done"); + yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: d_make_root done"); if (!root) { iput(inode); @@ -2366,6 +2370,7 @@ static struct super_block *yaffs_internal_read_super(int yaffs_version, dev->is_checkpointed); yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: done"); + cleancache_init_fs(sb); return sb; } @@ -2375,19 +2380,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 +2403,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 8d226bfa2696a6f7a1dbefa4ccbf1bff4b391bf6..5f65ac2887a2c5aef6e16d186af6223ae4df0345 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 1e38a19d68f6220d3c89d9f3ffae84b8cbb012a2..bd36b4b2b2daf5fc9205728341f281c7aac8769e 100644 --- a/include/drm/Kbuild +++ b/include/drm/Kbuild @@ -13,3 +13,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 64ff02d5b73056f81d03ff2e54b784e3943ba658..ad72af8c9d25cedb33f270db29a9a7e386ba793d 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 0000000000000000000000000000000000000000..f1c7f4e22f3c986e89a9ebcfeb7324814094ba55 --- /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 c5a9cae514c8ec702d4235fe128edfbbc28595af..1e1e09ff0b9bdb88132005004f70f8f85723335a 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -19,6 +19,7 @@ header-y += netfilter_ipv4/ header-y += netfilter_ipv6/ header-y += usb/ header-y += wimax/ +header-y += mfd/ objhdr-y += version.h @@ -46,6 +47,7 @@ header-y += agpgart.h header-y += aio_abi.h header-y += apm_bios.h header-y += arcfb.h +header-y += ashmem.h header-y += atalk.h header-y += atm.h header-y += atm_eni.h @@ -97,6 +99,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 @@ -203,6 +206,7 @@ header-y += unix_diag.h header-y += inotify.h header-y += input.h header-y += ioctl.h +header-y += ion.h header-y += ip.h header-y += ip6_tunnel.h header-y += ip_vs.h @@ -234,6 +238,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 @@ -253,6 +258,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 @@ -371,6 +377,7 @@ header-y += tiocl.h header-y += tipc.h header-y += tipc_config.h header-y += toshiba.h +header-y += tspp.h header-y += tty.h header-y += types.h header-y += udf_fs_i.h @@ -408,3 +415,30 @@ 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_audio_qcp.h +header-y += msm_audio_amrnb.h +header-y += msm_audio_voicememo.h +header-y += msm_audio_sbc.h +header-y += msm_ipc.h +header-y += msm_charm.h +header-y += tzcom.h +header-y += qseecom.h +header-y += qcedev.h +header-y += idle_stats_device.h +header-y += genlock.h +header-y += msm_audio_amrwb.h diff --git a/include/linux/adv7520.h b/include/linux/adv7520.h new file mode 100644 index 0000000000000000000000000000000000000000..96db7b7b91c4ab53280144601e74d730257e7771 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..cbfeafce329eba72a130f3178c1328e3354fa865 --- /dev/null +++ b/include/linux/android_alarm.h @@ -0,0 +1,107 @@ +/* include/linux/android_alarm.h + * + * Copyright (C) 2006-2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_ANDROID_ALARM_H +#define _LINUX_ANDROID_ALARM_H + +#include +#include + +enum android_alarm_type { + /* return code bit numbers or set alarm arg */ + ANDROID_ALARM_RTC_WAKEUP, + ANDROID_ALARM_RTC, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + ANDROID_ALARM_ELAPSED_REALTIME, + ANDROID_ALARM_SYSTEMTIME, + + ANDROID_ALARM_TYPE_COUNT, + + /* return code bit numbers */ + /* ANDROID_ALARM_TIME_CHANGE = 16 */ +}; + +#ifdef __KERNEL__ + +#include +#include + +/* + * The alarm interface is similar to the hrtimer interface but adds support + * for wakeup from suspend. It also adds an elapsed realtime clock that can + * be used for periodic timers that need to keep runing while the system is + * suspended and not be disrupted when the wall time is set. + */ + +/** + * struct alarm - the basic alarm structure + * @node: red black tree node for time ordered insertion + * @type: alarm type. rtc/elapsed-realtime/systemtime, wakeup/non-wakeup. + * @softexpires: the absolute earliest expiry time of the alarm. + * @expires: the absolute expiry time. + * @function: alarm expiry callback function + * + * The alarm structure must be initialized by alarm_init() + * + */ + +struct alarm { + struct rb_node node; + enum android_alarm_type type; + ktime_t softexpires; + ktime_t expires; + void (*function)(struct alarm *); +}; + +void alarm_init(struct alarm *alarm, + enum android_alarm_type type, void (*function)(struct alarm *)); +void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end); +int alarm_try_to_cancel(struct alarm *alarm); +int alarm_cancel(struct alarm *alarm); +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 + +enum android_alarm_return_flags { + ANDROID_ALARM_RTC_WAKEUP_MASK = 1U << ANDROID_ALARM_RTC_WAKEUP, + ANDROID_ALARM_RTC_MASK = 1U << ANDROID_ALARM_RTC, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK = + 1U << ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + ANDROID_ALARM_ELAPSED_REALTIME_MASK = + 1U << ANDROID_ALARM_ELAPSED_REALTIME, + ANDROID_ALARM_SYSTEMTIME_MASK = 1U << ANDROID_ALARM_SYSTEMTIME, + ANDROID_ALARM_TIME_CHANGE_MASK = 1U << 16 +}; + +/* Disable alarm */ +#define ANDROID_ALARM_CLEAR(type) _IO('a', 0 | ((type) << 4)) + +/* Ack last alarm and wait for next */ +#define ANDROID_ALARM_WAIT _IO('a', 1) + +#define ALARM_IOW(c, type, size) _IOW('a', (c) | ((type) << 4), size) +/* Set alarm */ +#define ANDROID_ALARM_SET(type) ALARM_IOW(2, type, struct timespec) +#define ANDROID_ALARM_SET_AND_WAIT(type) ALARM_IOW(3, type, struct timespec) +#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec) +#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec) +#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0))) +#define ANDROID_ALARM_IOCTL_TO_TYPE(cmd) (_IOC_NR(cmd) >> 4) + +#endif diff --git a/include/linux/android_pmem.h b/include/linux/android_pmem.h new file mode 100644 index 0000000000000000000000000000000000000000..ab9637927815d5ca84f83584ad991fe144b76353 --- /dev/null +++ b/include/linux/android_pmem.h @@ -0,0 +1,189 @@ +/* include/linux/android_pmem.h + * + * 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 _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) +#define PMEM_GET_SIZE _IOW(PMEM_IOCTL_MAGIC, 3, unsigned int) +#define PMEM_UNMAP _IOW(PMEM_IOCTL_MAGIC, 4, unsigned int) +/* This ioctl will allocate pmem space, backing the file, it will fail + * if the file already has an allocation, pass it the len as the argument + * to the ioctl */ +#define PMEM_ALLOCATE _IOW(PMEM_IOCTL_MAGIC, 5, unsigned int) +/* This will connect a one pmem file to another, pass the file that is already + * backed in memory as the argument to the ioctl + */ +#define PMEM_CONNECT _IOW(PMEM_IOCTL_MAGIC, 6, unsigned int) +/* Returns the total size of the pmem region it is sent to as a pmem_region + * struct (with offset set to 0). + */ +#define PMEM_GET_TOTAL_SIZE _IOW(PMEM_IOCTL_MAGIC, 7, 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; + /* size of memory region */ + unsigned long size; + + 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; + /* which memory type (i.e. SMI, EBI1) this PMEM device is backed by */ + unsigned memory_type; + /* + * function to be called when the number of allocations goes from + * 0 -> 1 + */ + int (*request_region)(void *); + /* + * function to be called when the number of allocations goes from + * 1 -> 0 + */ + int (*release_region)(void *); + /* + * function to be called upon pmem registration + */ + void *(*setup_region)(void); + /* + * indicates that this region should be mapped/unmaped as needed + */ + int map_on_demand; + /* + * indicates this pmem may be reused via fmem + */ + int reusable; +}; + +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); +#endif /* __KERNEL__ */ + +#endif //_ANDROID_PPP_H_ + diff --git a/drivers/staging/android/ashmem.h b/include/linux/ashmem.h similarity index 83% rename from drivers/staging/android/ashmem.h rename to include/linux/ashmem.h index 1976b10ef93ebedf06b587ee8b6956b99d685e9c..25a190e12ab79a34e3afa9ca4ba6abc09c0f0094 100644 --- a/drivers/staging/android/ashmem.h +++ b/include/linux/ashmem.h @@ -44,5 +44,12 @@ 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) +#define ASHMEM_CACHE_CLEAN_RANGE _IO(__ASHMEMIOC, 12) +#define ASHMEM_CACHE_INV_RANGE _IO(__ASHMEMIOC, 13) + +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 0000000000000000000000000000000000000000..012e68bb3ce02da4bed57e1a0202e71e30f2e5e7 --- /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 maxtouch_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 maxtouch_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 7ad634501e48cd7d39b1e8b621f0affce26765d7..01ab4519d7cf7251b6dd030b9f8cf0c1e0e2c5ef 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/blk_types.h b/include/linux/blk_types.h index 4053cbd4490edb6530eee7bb332ab95138352d17..9c49d17cbe112ca58f5cddf3bf4637484667a98a 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -150,6 +150,7 @@ enum rq_flag_bits { __REQ_FLUSH_SEQ, /* request for flush sequence */ __REQ_IO_STAT, /* account I/O stat */ __REQ_MIXED_MERGE, /* merge of different types, fail separately */ + __REQ_SANITIZE, /* sanitize */ __REQ_NR_BITS, /* stops here */ }; @@ -161,13 +162,15 @@ enum rq_flag_bits { #define REQ_META (1 << __REQ_META) #define REQ_PRIO (1 << __REQ_PRIO) #define REQ_DISCARD (1 << __REQ_DISCARD) +#define REQ_SANITIZE (1 << __REQ_SANITIZE) #define REQ_NOIDLE (1 << __REQ_NOIDLE) #define REQ_FAILFAST_MASK \ (REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER) #define REQ_COMMON_MASK \ (REQ_WRITE | REQ_FAILFAST_MASK | REQ_SYNC | REQ_META | REQ_PRIO | \ - REQ_DISCARD | REQ_NOIDLE | REQ_FLUSH | REQ_FUA | REQ_SECURE) + REQ_DISCARD | REQ_NOIDLE | REQ_FLUSH | REQ_FUA | REQ_SECURE | \ + REQ_SANITIZE) #define REQ_CLONE_MASK REQ_COMMON_MASK #define REQ_RAHEAD (1 << __REQ_RAHEAD) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 4d4ac24a263ea956457d4ea4a63f1431408a6d90..a19d3745241d555a253db29554b5dff56ee3fea5 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -421,6 +421,7 @@ struct request_queue { #define QUEUE_FLAG_ADD_RANDOM 16 /* Contributes to random pool */ #define QUEUE_FLAG_SECDISCARD 17 /* supports SECDISCARD */ #define QUEUE_FLAG_SAME_FORCE 18 /* force complete on same CPU */ +#define QUEUE_FLAG_SANITIZE 19 /* supports SANITIZE */ #define QUEUE_FLAG_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \ (1 << QUEUE_FLAG_STACKABLE) | \ @@ -500,6 +501,7 @@ static inline void queue_flag_clear(unsigned int flag, struct request_queue *q) #define blk_queue_stackable(q) \ test_bit(QUEUE_FLAG_STACKABLE, &(q)->queue_flags) #define blk_queue_discard(q) test_bit(QUEUE_FLAG_DISCARD, &(q)->queue_flags) +#define blk_queue_sanitize(q) test_bit(QUEUE_FLAG_SANITIZE, &(q)->queue_flags) #define blk_queue_secdiscard(q) (blk_queue_discard(q) && \ test_bit(QUEUE_FLAG_SECDISCARD, &(q)->queue_flags)) @@ -953,6 +955,7 @@ static inline struct request *blk_map_queue_find_tag(struct blk_queue_tag *bqt, extern int blkdev_issue_flush(struct block_device *, gfp_t, sector_t *); extern int blkdev_issue_discard(struct block_device *bdev, sector_t sector, sector_t nr_sects, gfp_t gfp_mask, unsigned long flags); +extern int blkdev_issue_sanitize(struct block_device *bdev, gfp_t gfp_mask); extern int blkdev_issue_zeroout(struct block_device *bdev, sector_t sector, sector_t nr_sects, gfp_t gfp_mask); static inline int sb_issue_discard(struct super_block *sb, sector_t block, diff --git a/include/linux/bma150.h b/include/linux/bma150.h index 7911fda23bb439975d7e9b0b0941b94a2ea3c910..a3d1c4fde7ddac22c495ba8e22ea32df70276bde 100644 --- a/include/linux/bma150.h +++ b/include/linux/bma150.h @@ -1,46 +1,31 @@ -/* - * Copyright (c) 2011 Bosch Sensortec GmbH - * Copyright (c) 2011 Unixphere +/* 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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. */ +#ifndef LINUX_BMA150_MODULE_H +#define LINUX_BMA150_MODULE_H -#ifndef _BMA150_H_ -#define _BMA150_H_ - -#define BMA150_DRIVER "bma150" - -struct bma150_cfg { - bool any_motion_int; /* Set to enable any-motion interrupt */ - bool hg_int; /* Set to enable high-G interrupt */ - bool lg_int; /* Set to enable low-G interrupt */ - unsigned char any_motion_dur; /* Any-motion duration */ - unsigned char any_motion_thres; /* Any-motion threshold */ - unsigned char hg_hyst; /* High-G hysterisis */ - unsigned char hg_dur; /* High-G duration */ - unsigned char hg_thres; /* High-G threshold */ - unsigned char lg_hyst; /* Low-G hysterisis */ - unsigned char lg_dur; /* Low-G duration */ - unsigned char lg_thres; /* Low-G threshold */ - unsigned char range; /* BMA0150_RANGE_xxx (in G) */ - unsigned char bandwidth; /* BMA0150_BW_xxx (in Hz) */ -}; +/** + * 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 { - struct bma150_cfg cfg; - int (*irq_gpio_cfg)(void); + int (*setup)(struct device *); + void (*teardown)(struct device *); + int (*power_on)(void); + void (*power_off)(void); }; -#endif /* _BMA150_H_ */ +#endif /* LINUX_BMA150_MODULE_H */ diff --git a/include/linux/clk.h b/include/linux/clk.h index b0252726df6106991bdaa7fae7146bc6ea4b6633..bb5d6f878140ce5ffc22d4863147857ffae90c32 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -100,6 +100,16 @@ int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb); */ struct clk *clk_get(struct device *dev, const char *id); +/** + * devm_clk_get - Resource managed clk_get() + * @dev: device for clk "consumer" + * @id: clk ID. + * + * Managed clk_get(). Clocks returned from this function are + * automatically clk_put() on driver detach. + */ +struct clk *devm_clk_get(struct device *dev, const char *id); + /** * clk_prepare - prepare a clock source * @clk: clock source diff --git a/include/linux/completion.h b/include/linux/completion.h index 51494e6b55487f30496c8870165dd75f8ba4c7b1..a5b2e1ca5ceb19e49658eac810e96ef6fe922bad 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/cpuacct.h b/include/linux/cpuacct.h new file mode 100644 index 0000000000000000000000000000000000000000..8f68e733fe194c04a70106551862cec389c194ee --- /dev/null +++ b/include/linux/cpuacct.h @@ -0,0 +1,43 @@ +/* include/linux/cpuacct.h + * + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _CPUACCT_H_ +#define _CPUACCT_H_ + +#include + +#ifdef CONFIG_CGROUP_CPUACCT + +/* + * Platform specific CPU frequency hooks for cpuacct. These functions are + * called from the scheduler. + */ +struct cpuacct_charge_calls { + /* + * Platforms can take advantage of this data and use + * per-cpu allocations if necessary. + */ + void (*init) (void **cpuacct_data); + void (*charge) (void *cpuacct_data, u64 cputime, unsigned int cpu); + void (*cpufreq_show) (void *cpuacct_data, struct cgroup_map_cb *cb); + /* Returns power consumed in milliWatt seconds */ + u64 (*power_usage) (void *cpuacct_data); +}; + +int cpuacct_charge_register(struct cpuacct_charge_calls *fn); + +#endif /* CONFIG_CGROUP_CPUACCT */ + +#endif // _CPUACCT_H_ diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index d1c3bb0e1fca1b342be9f9d0bbf28c946beed37e..6723f48f2d4a0b7d39f5c3b5236b20e72332defd 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -57,6 +57,10 @@ static inline void disable_cpufreq(void) { } #define CPUFREQ_POLICY_POWERSAVE (1) #define CPUFREQ_POLICY_PERFORMANCE (2) +/* Minimum frequency cutoff to notify the userspace about cpu utilization + * changes */ +#define MIN_CPU_UTIL_NOTIFY 40 + /* Frequency values here are CPU kHz so that hardware which doesn't run * with some frequencies can complain without having to guess what per * cent / per mille means. @@ -97,6 +101,7 @@ struct cpufreq_policy { unsigned int max; /* in kHz */ unsigned int cur; /* in kHz, only needed if cpufreq * governors are used */ + unsigned int util; /* CPU utilization at max frequency */ unsigned int policy; /* see above */ struct cpufreq_governor *governor; /* see below */ @@ -200,6 +205,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 * @@ -254,7 +261,8 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver_data); void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state); - +void cpufreq_notify_utilization(struct cpufreq_policy *policy, + unsigned int load); static inline void cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min, unsigned int max) { diff --git a/include/linux/csdio.h b/include/linux/csdio.h new file mode 100644 index 0000000000000000000000000000000000000000..260c49d608207261bc202ada94432f3cb063d58d --- /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-qc.h b/include/linux/cyttsp-qc.h new file mode 100644 index 0000000000000000000000000000000000000000..0e5cac7cffb41e133e3ee6c275a2234c25185b57 --- /dev/null +++ b/include/linux/cyttsp-qc.h @@ -0,0 +1,666 @@ +/* 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_TMA300_SLEEP_CURR_UA 10 +#define CY_I2C_VTG_MAX_UV 1800000 +#define CY_I2C_VTG_MIN_UV 1800000 +#define CY_I2C_CURR_UA 9630 +#define CY_I2C_SLEEP_CURR_UA 10 + + +/* 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 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 */ + +#define CY_USE_DEEP_SLEEP_SEL 0x80 +#define CY_USE_LOW_POWER_SEL 0x01 + +#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 max_uV; + u32 min_uV; + u32 hpm_load_uA; + u32 lpm_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; + bool disable_ghost_det; +#ifdef CY_USE_I2C_DRIVER + s32 (*init)(struct i2c_client *client); + s32 (*resume)(struct i2c_client *client); + s32 (*suspend)(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/delay.h b/include/linux/delay.h index a6ecb34cf547da29ad16edf8b109de2511d10cd5..0e303d1aacd8b3fcb40407ca8765c6d2cde3aa0d 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/device.h b/include/linux/device.h index 5ad17cccdd715cbd95ea98f7b279f9fd3d106ec3..84be123d403bb1b9678c5eec1c60fd6ed0858e89 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -581,6 +581,10 @@ struct device_dma_parameters { * @mutex: Mutex to synchronize calls to its driver. * @bus: Type of bus device is on. * @driver: Which driver has allocated this + * @deferred_probe: entry in deferred_probe_list which is used to retry the + * binding of drivers which were unable to get all the resources + * needed by the device; typically because it depends on another + * driver getting probed first. * @platform_data: Platform data specific to the device. * Example: For devices on custom boards, as typical of embedded * and SOC based hardware, Linux often uses platform_data to point @@ -640,6 +644,7 @@ struct device { struct bus_type *bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ + struct list_head deferred_probe; void *platform_data; /* Platform specific data, device core doesn't touch it */ struct dev_pm_info power; diff --git a/include/linux/diagchar.h b/include/linux/diagchar.h new file mode 100644 index 0000000000000000000000000000000000000000..537960b8373fe3486e2ece0aa371b50b1a184693 --- /dev/null +++ b/include/linux/diagchar.h @@ -0,0 +1,705 @@ +/* Copyright (c) 2008-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 USER_SPACE_LOG_TYPE 32 +#define DCI_DATA_TYPE 64 +#define USB_MODE 1 +#define MEMORY_DEVICE_MODE 2 +#define NO_LOGGING_MODE 3 +#define UART_MODE 4 + +/* 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 +#define DIAG_IOCTL_DCI_INIT 20 +#define DIAG_IOCTL_DCI_DEINIT 21 +#define DIAG_IOCTL_DCI_SUPPORT 22 +#define DIAG_IOCTL_DCI_REG 23 + +/* PC Tools IDs */ +#define APQ8060_TOOLS_ID 4062 +#define AO8960_TOOLS_ID 4064 +#define APQ8064_TOOLS_ID 4072 +#define MSM8625_TOOLS_ID 4075 +#define MSM8930_TOOLS_ID 4076 +#define MSM8630_TOOLS_ID 4077 +#define MSM8230_TOOLS_ID 4078 +#define APQ8030_TOOLS_ID 4079 +#define MSM8627_TOOLS_ID 4080 +#define MSM8227_TOOLS_ID 4081 +#define MSM8974_TOOLS_ID 4083 + +#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 23 +#define EVENT_LAST_ID 0x083F + +#define MSG_SSID_0 0 +#define MSG_SSID_0_LAST 90 +#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 3014 +#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 4612 +#define MSG_SSID_8 5000 +#define MSG_SSID_8_LAST 5029 +#define MSG_SSID_9 5500 +#define MSG_SSID_9_LAST 5516 +#define MSG_SSID_10 6000 +#define MSG_SSID_10_LAST 6072 +#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 +#define MSG_SSID_19 10200 +#define MSG_SSID_19_LAST 10210 +#define MSG_SSID_20 10251 +#define MSG_SSID_20_LAST 10255 +#define MSG_SSID_21 10300 +#define MSG_SSID_21_LAST 10300 +#define MSG_SSID_22 10350 +#define MSG_SSID_22_LAST 10361 + +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, + MSG_LVL_LOW, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_HIGH, + 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_MED|MSG_LVL_HIGH|MSG_LVL_ERROR|MSG_LVL_FATAL, + MSG_LVL_MED, + MSG_LVL_LOW|MSG_LVL_MED|MSG_LVL_HIGH|MSG_LVL_ERROR|MSG_LVL_FATAL, + MSG_LVL_LOW, + MSG_LVL_MED, + 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, + MSG_LVL_LOW, + MSG_LVL_LOW +}; + +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|MSG_MASK_5|MSG_MASK_6|MSG_MASK_7| \ + MSG_MASK_8|MSG_MASK_9, + 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, + MSG_LVL_LOW +}; + +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, + 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, + 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, + MSG_LVL_LOW, + 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_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + 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 +}; + +static const uint32_t msg_bld_masks_19[] = { + 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_20[] = { + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW +}; + +static const uint32_t msg_bld_masks_21[] = { + MSG_LVL_HIGH +}; + +static const uint32_t msg_bld_masks_22[] = { + 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 +}; + +/* LOG CODES */ + +#define LOG_0 0x0 +#define LOG_1 0x15A7 +#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/dvb/dmx.h b/include/linux/dvb/dmx.h index f078f3ac82d4bc606c345926722fefce6fdb4abe..e0058d3c32e6eaddbec9fc9bce16e9c049ca9003 100644 --- a/include/linux/dvb/dmx.h +++ b/include/linux/dvb/dmx.h @@ -5,6 +5,8 @@ * & Ralph Metzler * for convergence integrated media GmbH * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 @@ -97,16 +99,40 @@ typedef struct dmx_filter } dmx_filter_t; +/* Filter flags */ +#define DMX_CHECK_CRC 0x01 +#define DMX_ONESHOT 0x02 +#define DMX_IMMEDIATE_START 0x04 +#define DMX_ENABLE_INDEXING 0x08 +#define DMX_KERNEL_CLIENT 0x8000 + struct dmx_sct_filter_params { __u16 pid; dmx_filter_t filter; __u32 timeout; __u32 flags; -#define DMX_CHECK_CRC 1 -#define DMX_ONESHOT 2 -#define DMX_IMMEDIATE_START 4 -#define DMX_KERNEL_CLIENT 0x8000 +}; + + +/* Indexing: supported video standards */ +enum dmx_indexing_video_standard { + DMX_INDEXING_MPEG2, + DMX_INDEXING_H264, + DMX_INDEXING_VC1 +}; + +/* Indexing: Supported video profiles */ +enum dmx_indexing_video_profile { + DMX_INDEXING_MPEG2_ANY, + DMX_INDEXING_H264_ANY, + DMX_INDEXING_VC1_ANY +}; + +/* Indexing: video configuration parameters */ +struct dmx_indexing_video_params { + enum dmx_indexing_video_standard standard; + enum dmx_indexing_video_profile profile; }; @@ -117,11 +143,89 @@ struct dmx_pes_filter_params dmx_output_t output; dmx_pes_type_t pes_type; __u32 flags; + + struct dmx_indexing_video_params video_params; +}; + +struct dmx_buffer_status { + /* size of buffer in bytes */ + unsigned int size; + + /* fullness of buffer in bytes */ + unsigned int fullness; + + /* + * How many bytes are free + * It's the same as: size-fullness-1 + */ + unsigned int free_bytes; + + /* read pointer offset in bytes */ + unsigned int read_offset; + + /* write pointer offset in bytes */ + unsigned int write_offset; + + /* non-zero if data error occured */ + int error; }; typedef struct dmx_caps { __u32 caps; + +/* Indicates whether demux support playback from memory in pull mode */ +#define DMX_CAP_PULL_MODE 0x01 + +/* Indicates whether demux support indexing of recorded video stream */ +#define DMX_CAP_VIDEO_INDEXING 0x02 + +/* Indicates whether demux support sending data directly to video decoder */ +#define DMX_CAP_VIDEO_DECODER_DATA 0x04 + +/* Indicates whether demux support sending data directly to audio decoder */ +#define DMX_CAP_AUDIO_DECODER_DATA 0x08 + +/* Indicates whether demux support sending data directly to subtitle decoder */ +#define DMX_CAP_SUBTITLE_DECODER_DATA 0x10 + + /* Number of decoders demux can output data to */ int num_decoders; + + /* Number of demux devices */ + int num_demux_devices; + + /* Max number of PID filters */ + int num_pid_filters; + + /* Max number of section filters */ + int num_section_filters; + + /* + * Max number of section filters using same PID, + * 0 if not supported + */ + int num_section_filters_per_pid; + + /* + * Length of section filter, not including section + * length field (2 bytes). + */ + int section_filter_length; + + /* Max number of demod based input */ + int num_demod_inputs; + + /* Max number of memory based input */ + int num_memory_inputs; + + /* Overall bitrate from all inputs concurrently. Mbit/sec */ + int max_bitrate; + + /* Max bitrate from single demod input. Mbit/sec */ + int demod_input_max_bitrate; + + /* Max bitrate from single memory input. Mbit/sec */ + int memory_input_max_bitrate; } dmx_caps_t; typedef enum { @@ -135,6 +239,34 @@ typedef enum { DMX_SOURCE_DVR3 } dmx_source_t; +enum dmx_tsp_format_t { + DMX_TSP_FORMAT_188 = 0, + DMX_TSP_FORMAT_192_TAIL, + DMX_TSP_FORMAT_192_HEAD, + DMX_TSP_FORMAT_204, +}; + +enum dmx_playback_mode_t { + /* + * In push mode, if one of output buffers + * is full, the buffer would overflow + * and demux continue processing incoming stream. + * This is the default mode. When playing from frontend, + * this is the only mode that is allowed. + */ + DMX_PB_MODE_PUSH = 0, + + /* + * In pull mode, if one of output buffers + * is full, demux stalls waiting for free space, + * this would cause DVR input buffer fullness + * to accumulate. + * This mode is possible only when playing + * from DVR. + */ + DMX_PB_MODE_PULL, +}; + struct dmx_stc { unsigned int num; /* input : which STC? 0..N */ unsigned int base; /* output: divisor for stc to get 90 kHz clock */ @@ -153,5 +285,12 @@ struct dmx_stc { #define DMX_GET_STC _IOWR('o', 50, struct dmx_stc) #define DMX_ADD_PID _IOW('o', 51, __u16) #define DMX_REMOVE_PID _IOW('o', 52, __u16) +#define DMX_SET_TS_PACKET_FORMAT _IOW('o', 53, enum dmx_tsp_format_t) +#define DMX_SET_TS_OUT_FORMAT _IOW('o', 54, enum dmx_tsp_format_t) +#define DMX_SET_DECODER_BUFFER_SIZE _IO('o', 55) +#define DMX_GET_BUFFER_STATUS _IOR('o', 56, struct dmx_buffer_status) +#define DMX_RELEASE_DATA _IO('o', 57) +#define DMX_FEED_DATA _IO('o', 58) +#define DMX_SET_PLAYBACK_MODE _IOW('o', 59, enum dmx_playback_mode_t) #endif /*_DVBDMX_H_*/ diff --git a/include/linux/earlysuspend.h b/include/linux/earlysuspend.h new file mode 100644 index 0000000000000000000000000000000000000000..8343b817af31b8d262df4e4eb45bd3ac4121ea0d --- /dev/null +++ b/include/linux/earlysuspend.h @@ -0,0 +1,56 @@ +/* include/linux/earlysuspend.h + * + * Copyright (C) 2007-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_EARLYSUSPEND_H +#define _LINUX_EARLYSUSPEND_H + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +/* The early_suspend structure defines suspend and resume hooks to be called + * when the user visible sleep state of the system changes, and a level to + * control the order. They can be used to turn off the screen and input + * devices that are not used for wakeup. + * Suspend handlers are called in low to high level order, resume handlers are + * called in the opposite order. If, when calling register_early_suspend, + * the suspend handlers have already been called without a matching call to the + * resume handlers, the suspend handler will be called directly from + * register_early_suspend. This direct call can violate the normal level order. + */ +enum { + EARLY_SUSPEND_LEVEL_BLANK_SCREEN = 50, + EARLY_SUSPEND_LEVEL_STOP_DRAWING = 100, + EARLY_SUSPEND_LEVEL_DISABLE_FB = 150, +}; +struct early_suspend { +#ifdef CONFIG_HAS_EARLYSUSPEND + struct list_head link; + int level; + void (*suspend)(struct early_suspend *h); + void (*resume)(struct early_suspend *h); +#endif +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +void register_early_suspend(struct early_suspend *handler); +void unregister_early_suspend(struct early_suspend *handler); +#else +#define register_early_suspend(handler) do { } while (0) +#define unregister_early_suspend(handler) do { } while (0) +#endif + +#endif + diff --git a/include/linux/epm_adc.h b/include/linux/epm_adc.h new file mode 100644 index 0000000000000000000000000000000000000000..1af97feb7824bf52753ae668606f3f6573bfc6d3 --- /dev/null +++ b/include/linux/epm_adc.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __EPM_ADC_H +#define __EPM_ADC_H + +#include + +struct epm_chan_request { + /* EPM ADC device index. 0 - ADC1, 1 - ADC2 */ + uint32_t device_idx; + /* Channel number within the EPM ADC device */ + uint32_t channel_idx; + /* The data meaningful for each individual channel whether it is + * voltage, current etc. */ + int32_t physical; +}; + +struct epm_chan_properties { + uint32_t resistorValue; + uint32_t gain; +}; + +struct epm_adc_platform_data { + struct epm_chan_properties *channel; + uint32_t num_channels; + uint32_t num_adc; + uint32_t chan_per_adc; + uint32_t chan_per_mux; + struct i2c_board_info epm_i2c_board_info; + uint32_t bus_id; + uint32_t gpio_expander_base_addr; +}; + +#define EPM_ADC_IOCTL_CODE 0x91 + +#define EPM_ADC_REQUEST _IOWR(EPM_ADC_IOCTL_CODE, 1, \ + struct epm_chan_request) + +#define EPM_ADC_INIT _IOR(EPM_ADC_IOCTL_CODE, 2, \ + uint32_t) + +#define EPM_ADC_DEINIT _IOR(EPM_ADC_IOCTL_CODE, 3, \ + uint32_t) +#endif /* __EPM_ADC_H */ diff --git a/include/linux/fmem.h b/include/linux/fmem.h new file mode 100644 index 0000000000000000000000000000000000000000..e4fa82cb5c98df29128b0d6018ba079ca1135a13 --- /dev/null +++ b/include/linux/fmem.h @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _FMEM_H_ +#define _FMEM_H_ + +#include + +struct fmem_platform_data { + unsigned long phys; + unsigned long size; + unsigned long reserved_size_low; + unsigned long reserved_size_high; + unsigned long align; +}; + +struct fmem_data { + unsigned long phys; + void *virt; + struct vm_struct *area; + unsigned long size; + unsigned long reserved_size_low; + unsigned long reserved_size_high; +}; + +enum fmem_state { + FMEM_UNINITIALIZED = 0, + FMEM_C_STATE, + FMEM_T_STATE, + FMEM_O_STATE, +}; + +#ifdef CONFIG_QCACHE +struct fmem_data *fmem_get_info(void); +int fmem_set_state(enum fmem_state); +void lock_fmem_state(void); +void unlock_fmem_state(void); +void *fmem_map_virtual_area(int cacheability); +void fmem_unmap_virtual_area(void); +#else +static inline struct fmem_data *fmem_get_info(void) { return NULL; } +static inline int fmem_set_state(enum fmem_state f) { return -ENODEV; } +static inline void lock_fmem_state(void) { return; } +static inline void unlock_fmem_state(void) { return; } +static inline void *fmem_map_virtual_area(int cacheability) { return NULL; } +static inline void fmem_unmap_virtual_area(void) { return; } +#endif + +int request_fmem_c_region(void *unused); +int release_fmem_c_region(void *unused); +#endif diff --git a/include/linux/fs.h b/include/linux/fs.h index 25c40b9f848afeac9313be994c6e8ad03683a0c5..03fc44b3c04ea18a639c7742438dfb49317e8209 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -17,8 +17,8 @@ * nr_file rlimit, so it's safe to set up a ridiculously high absolute * upper limit on files-per-process. * - * Some programs (notably those using select()) may have to be - * recompiled to take full advantage of the new limits.. + * Some programs (notably those using select()) may have to be + * recompiled to take full advantage of the new limits.. */ /* Fixed constants first: */ @@ -178,7 +178,7 @@ struct inodes_stat_t { #define SEL_EX 4 /* public flags for file_system_type */ -#define FS_REQUIRES_DEV 1 +#define FS_REQUIRES_DEV 1 #define FS_BINARY_MOUNTDATA 2 #define FS_HAS_SUBTYPE 4 #define FS_REVAL_DOT 16384 /* Check the paths ".", ".." for staleness */ @@ -324,6 +324,7 @@ struct inodes_stat_t { #define BLKDISCARDZEROES _IO(0x12,124) #define BLKSECDISCARD _IO(0x12,125) #define BLKROTATIONAL _IO(0x12,126) +#define BLKSANITIZE _IO(0x12, 127) #define BMAP_IOCTL 1 /* obsolete - kept for compatibility */ #define FIBMAP _IO(0x00,1) /* bmap access */ @@ -489,7 +490,7 @@ struct iattr { */ #include -/** +/** * enum positive_aop_returns - aop return codes with specific semantics * * @AOP_WRITEPAGE_ACTIVATE: Informs the caller that page writeback has @@ -499,7 +500,7 @@ struct iattr { * be a candidate for writeback again in the near * future. Other callers must be careful to unlock * the page if they get this return. Returned by - * writepage(); + * writepage(); * * @AOP_TRUNCATED_PAGE: The AOP method that was handed a locked page has * unlocked it and the page might have been truncated. @@ -1077,10 +1078,10 @@ static inline int file_check_writeable(struct file *filp) #define MAX_NON_LFS ((1UL<<31) - 1) -/* Page cache limit. The filesystems should put that into their s_maxbytes - limits, otherwise bad things can happen in VM. */ +/* Page cache limit. The filesystems should put that into their s_maxbytes + limits, otherwise bad things can happen in VM. */ #if BITS_PER_LONG==32 -#define MAX_LFS_FILESIZE (((u64)PAGE_CACHE_SIZE << (BITS_PER_LONG-1))-1) +#define MAX_LFS_FILESIZE (((u64)PAGE_CACHE_SIZE << (BITS_PER_LONG-1))-1) #elif BITS_PER_LONG==64 #define MAX_LFS_FILESIZE 0x7fffffffffffffffUL #endif @@ -2281,7 +2282,7 @@ extern void free_write_pipe(struct file *); extern int kernel_read(struct file *, loff_t, char *, unsigned long); extern struct file * open_exec(const char *); - + /* fs/dcache.c -- generic fs support functions */ extern int is_subdir(struct dentry *, struct dentry *); extern int path_is_under(struct path *, struct path *); diff --git a/include/linux/fsm_dfe_hh.h b/include/linux/fsm_dfe_hh.h new file mode 100644 index 0000000000000000000000000000000000000000..79385186ca2c03c080b59366634509d5b73fff97 --- /dev/null +++ b/include/linux/fsm_dfe_hh.h @@ -0,0 +1,82 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_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 0000000000000000000000000000000000000000..18b794711993375c33402728ca0dca3a9470fbe0 --- /dev/null +++ b/include/linux/fsm_rfic_ftr.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_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 5e98eeb2af3b970220c7743016f9f85b0213ae8b..a87246c9cfc677d952a883213a81c952c5cd7ebe 100644 --- a/include/linux/genalloc.h +++ b/include/linux/genalloc.h @@ -72,10 +72,28 @@ static inline int gen_pool_add(struct gen_pool *pool, unsigned long addr, 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); extern void gen_pool_for_each_chunk(struct gen_pool *, void (*)(struct gen_pool *, struct gen_pool_chunk *, void *), void *); extern size_t gen_pool_avail(struct gen_pool *); extern size_t gen_pool_size(struct gen_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. + */ +static inline unsigned long __must_check +gen_pool_alloc(struct gen_pool *pool, size_t size) +{ + return gen_pool_alloc_aligned(pool, size, 0); +} + #endif /* __GENALLOC_H__ */ diff --git a/include/linux/genlock.h b/include/linux/genlock.h new file mode 100644 index 0000000000000000000000000000000000000000..587c49df7444a7550a8c004e4f94753f68d84bc0 --- /dev/null +++ b/include/linux/genlock.h @@ -0,0 +1,52 @@ +#ifndef _GENLOCK_H_ +#define _GENLOCK_H_ + +#ifdef __KERNEL__ + +struct genlock; +struct genlock_handle; + +struct genlock_handle *genlock_get_handle(void); +struct genlock_handle *genlock_get_handle_fd(int fd); +void genlock_put_handle(struct genlock_handle *handle); +struct genlock *genlock_create_lock(struct genlock_handle *); +struct genlock *genlock_attach_lock(struct genlock_handle *, int fd); +int genlock_wait(struct genlock_handle *handle, u32 timeout); +/* genlock_release_lock was deprecated */ +int genlock_lock(struct genlock_handle *handle, int op, int flags, + u32 timeout); +#endif + +#define GENLOCK_UNLOCK 0 +#define GENLOCK_WRLOCK 1 +#define GENLOCK_RDLOCK 2 + +#define GENLOCK_NOBLOCK (1 << 0) +#define GENLOCK_WRITE_TO_READ (1 << 1) + +struct genlock_lock { + int fd; + int op; + int flags; + int timeout; +}; + +#define GENLOCK_IOC_MAGIC 'G' + +#define GENLOCK_IOC_NEW _IO(GENLOCK_IOC_MAGIC, 0) +#define GENLOCK_IOC_EXPORT _IOR(GENLOCK_IOC_MAGIC, 1, \ + struct genlock_lock) +#define GENLOCK_IOC_ATTACH _IOW(GENLOCK_IOC_MAGIC, 2, \ + struct genlock_lock) + +/* Deprecated */ +#define GENLOCK_IOC_LOCK _IOW(GENLOCK_IOC_MAGIC, 3, \ + struct genlock_lock) + +/* Deprecated */ +#define GENLOCK_IOC_RELEASE _IO(GENLOCK_IOC_MAGIC, 4) +#define GENLOCK_IOC_WAIT _IOW(GENLOCK_IOC_MAGIC, 5, \ + struct genlock_lock) +#define GENLOCK_IOC_DREADLOCK _IOW(GENLOCK_IOC_MAGIC, 6, \ + struct genlock_lock) +#endif diff --git a/include/linux/gpio-pm8xxx-rpc.h b/include/linux/gpio-pm8xxx-rpc.h new file mode 100644 index 0000000000000000000000000000000000000000..5b6f09787bf1b7c7f10da276924c8392dee12b6d --- /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/i2c/atmel_mxt_ts.h b/include/linux/i2c/atmel_mxt_ts.h index f027f7a63511bbdcd0daf459e5dce65434117dcf..b54fcb45b49b0f2bc87e9766c3e5aa60f07aa9d0 100644 --- a/include/linux/i2c/atmel_mxt_ts.h +++ b/include/linux/i2c/atmel_mxt_ts.h @@ -3,6 +3,7 @@ * * Copyright (C) 2010 Samsung Electronics Co.Ltd * Author: Joonyoung Shim + * Copyright (c) 2011-2012, Code Aurora Forum. All 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 @@ -25,20 +26,55 @@ #define MXT_ROTATED_180 0x6 #define MXT_DIAGONAL_COUNTER 0x7 -/* The platform data for the Atmel maXTouch touchscreen driver */ -struct mxt_platform_data { +/* MXT_TOUCH_KEYARRAY_T15 */ +#define MXT_KEYARRAY_MAX_KEYS 32 + +/* Bootoader IDs */ +#define MXT_BOOTLOADER_ID_224 0x0A +#define MXT_BOOTLOADER_ID_224E 0x06 +#define MXT_BOOTLOADER_ID_1386 0x01 +#define MXT_BOOTLOADER_ID_1386E 0x10 + +/* Config data for a given maXTouch controller with a specific firmware */ +struct mxt_config_info { const u8 *config; size_t config_length; + u8 family_id; + u8 variant_id; + u8 version; + u8 build; + u8 bootldr_id; + /* Points to the firmware name to be upgraded to */ + const char *fw_name; +}; + +/* The platform data for the Atmel maXTouch touchscreen driver */ +struct mxt_platform_data { + const struct mxt_config_info *config_array; + size_t config_array_size; + + /* touch panel's minimum and maximum coordinates */ + u32 panel_minx; + u32 panel_maxx; + u32 panel_miny; + u32 panel_maxy; + + /* display's minimum and maximum coordinates */ + u32 disp_minx; + u32 disp_maxx; + u32 disp_miny; + u32 disp_maxy; - unsigned int x_line; - unsigned int y_line; - unsigned int x_size; - unsigned int y_size; - unsigned int blen; - unsigned int threshold; - unsigned int voltage; - unsigned char orient; unsigned long irqflags; + bool i2c_pull_up; + bool digital_pwr_regulator; + int reset_gpio; + int irq_gpio; + int *key_codes; + + u8(*read_chg) (void); + int (*init_hw) (bool); + int (*power_on) (bool); }; #endif /* __LINUX_ATMEL_MXT_TS_H */ diff --git a/include/linux/i2c/bq27520.h b/include/linux/i2c/bq27520.h new file mode 100644 index 0000000000000000000000000000000000000000..70c5a4c2a1189cc24a3fa838cd9e4b118625abe1 --- /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 0000000000000000000000000000000000000000..9dab3eb210900a11ecf55de9e1a079ad13861b13 --- /dev/null +++ b/include/linux/i2c/isa1200.h @@ -0,0 +1,61 @@ +/* + * isa1200.h - ISA1200 Haptic Motor driver + * + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park + * Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * 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 + +#define ISA_I2C_VTG_MAX_UV 1800000 +#define ISA_I2C_VTG_MIN_UV 1800000 +#define ISA_I2C_CURR_UA 9630 + +struct isa1200_regulator { + const char *name; + u32 min_uV; + u32 max_uV; + u32 load_uA; +}; + +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; + unsigned int hap_len_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; + struct isa1200_regulator *regulator_info; + u8 num_regulators; + int (*power_on)(int on); + int (*dev_setup)(bool on); + int (*clk_enable)(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 0000000000000000000000000000000000000000..8c98bf7c6ef034b67aea005345fbd64de2a1e16a --- /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 0000000000000000000000000000000000000000..a72b8957a03af0fa01a0a086ec7452492f508837 --- /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/smb349.h b/include/linux/i2c/smb349.h new file mode 100644 index 0000000000000000000000000000000000000000..2adacb381d76fee6ec46675fd0f0b0df13e6595e --- /dev/null +++ b/include/linux/i2c/smb349.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful; + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __SMB349_H__ +#define __SMB349_H__ + +#define SMB349_NAME "smb349" + +/** + * struct smb349_platform_data + * structure to pass board specific information to the smb137b charger driver + * @chg_current_ma: maximum fast charge current in mA + * @en_n_gpio: gpio to enable or disable charging + * @chg_susp_gpio: put active low to allow chip to suspend and disable I2C + */ +struct smb349_platform_data { + int en_n_gpio; + int chg_susp_gpio; + int chg_current_ma; +}; + +#endif diff --git a/include/linux/i2c/sx150x.h b/include/linux/i2c/sx150x.h index 52baa79d69a763f7f94f728b115fe0337190addd..e73dfd930f682d4af4766eefe97a6280350f7de4 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 506a9f7af51e6a5dc4766f62d19dda5279d06e7e..761cbdc5d51f7f0ee4bc0f27172bd2ac33f65c68 100644 --- a/include/linux/i2c/tsc2007.h +++ b/include/linux/i2c/tsc2007.h @@ -13,12 +13,22 @@ struct tsc2007_platform_data { int fuzzx; /* fuzz factor for X, Y and pressure axes */ int fuzzy; int fuzzz; + u16 min_x; + u16 min_y; + u16 max_x; + u16 max_y; + 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/idle_stats_device.h b/include/linux/idle_stats_device.h new file mode 100644 index 0000000000000000000000000000000000000000..cd613af16aa2d9e0e7ee81dd5c7f7123c7908f47 --- /dev/null +++ b/include/linux/idle_stats_device.h @@ -0,0 +1,82 @@ +#ifndef __ARCH_ARM_MACH_MSM_IDLE_STATS_DEVICE_H +#define __ARCH_ARM_MACH_MSM_IDLE_STATS_DEVICE_H + +#include +#include + +#define MSM_IDLE_STATS_EVENT_NONE 0 +#define MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED 1 +#define MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED_RESET 2 +#define MSM_IDLE_STATS_EVENT_COLLECTION_NEARLY_FULL 4 +#define MSM_IDLE_STATS_EVENT_COLLECTION_FULL 8 +#define MSM_IDLE_STATS_EVENT_IDLE_TIMER_EXPIRED 16 + +/* + * All time, timer, and time interval values are in units of + * microseconds unless stated otherwise. + */ +#define MSM_IDLE_STATS_NR_MAX_INTERVALS 200 + +struct msm_idle_pulse { + __s64 busy_start_time; + __u32 busy_interval; + __u32 wait_interval; +}; + +struct msm_idle_read_stats { + __u32 event; + __s64 return_timestamp; + __u32 busy_timer_remaining; + __u32 nr_collected; + struct msm_idle_pulse pulse_chain[MSM_IDLE_STATS_NR_MAX_INTERVALS]; +}; + +struct msm_idle_write_stats { + __u32 busy_timer; + __u32 next_busy_timer; + __u32 max_samples; +}; + +#define MSM_IDLE_STATS_IOC_MAGIC 0xD8 +#define MSM_IDLE_STATS_IOC_READ_STATS \ + _IOWR(MSM_IDLE_STATS_IOC_MAGIC, 1, struct msm_idle_read_stats) +#define MSM_IDLE_STATS_IOC_WRITE_STATS \ + _IOWR(MSM_IDLE_STATS_IOC_MAGIC, 2, struct msm_idle_write_stats) + +#ifdef __KERNEL__ +#include +#include +#include + +struct msm_idle_stats_device { + const char *name; + void (*get_sample)(struct msm_idle_stats_device *device, + struct msm_idle_pulse *pulse); + + struct miscdevice miscdev; + spinlock_t lock; + wait_queue_head_t wait; + struct list_head list; + struct hrtimer busy_timer; + ktime_t busy_timer_interval; + ktime_t idle_start; + ktime_t remaining_time; + __u32 max_samples; + + struct msm_idle_read_stats *stats; + struct msm_idle_read_stats stats_vector[2]; +}; + +int msm_idle_stats_register_device(struct msm_idle_stats_device *device); +int msm_idle_stats_deregister_device(struct msm_idle_stats_device *device); +void msm_idle_stats_prepare_idle_start(struct msm_idle_stats_device *device); +void msm_idle_stats_abort_idle_start(struct msm_idle_stats_device *device); +void msm_idle_stats_idle_start(struct msm_idle_stats_device *device); +void msm_idle_stats_idle_end(struct msm_idle_stats_device *device, + struct msm_idle_pulse *pulse); +void msm_idle_stats_update_event(struct msm_idle_stats_device *device, + __u32 event); +#endif + +#endif /* __ARCH_ARM_MACH_MSM_IDLE_STATS_DEVICE_H */ + diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 210e2c3255342f7cf6546ca0e1718fe6dc0e2dd4..a2b59d795aef320b897b9400f32a0a3cafc28cec 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1408,6 +1408,7 @@ enum ieee80211_key_len { WLAN_KEY_LEN_CCMP = 16, WLAN_KEY_LEN_TKIP = 32, WLAN_KEY_LEN_AES_CMAC = 16, + WLAN_KEY_LEN_WAPI_SMS4 = 32, }; /* Public action codes */ @@ -1564,6 +1565,7 @@ enum ieee80211_sa_query_action { #define WLAN_CIPHER_SUITE_CCMP 0x000FAC04 #define WLAN_CIPHER_SUITE_WEP104 0x000FAC05 #define WLAN_CIPHER_SUITE_AES_CMAC 0x000FAC06 +#define WLAN_CIPHER_SUITE_SMS4 0x00147201 #define WLAN_CIPHER_SUITE_SMS4 0x00147201 diff --git a/include/linux/if_arp.h b/include/linux/if_arp.h index 6d722f41ee7c760010ae26f3deea4fea1c9ca4aa..2cc79aeddd8ed7d30e91bbfd200a3b85f8d380de 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 49fb20e35cff5b8f70e8563679eb1f3e3e916502..d4cdb02c9de0209600ac0075e95e82d9d5ca7b0f 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -691,7 +691,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 */ @@ -846,7 +846,10 @@ struct input_keymap_entry { #define SW_FRONT_PROXIMITY 0x0b /* set = front proximity sensor active */ #define SW_ROTATE_LOCK 0x0c /* set = rotate locked/disabled */ #define SW_LINEIN_INSERT 0x0d /* set = inserted */ -#define SW_MAX 0x0f +#define SW_HPHL_OVERCURRENT 0x0e /* set = over current on left hph */ +#define SW_HPHR_OVERCURRENT 0x0f /* set = over current on right hph */ +#define SW_UNSUPPORT_INSERT 0x10 /* set = unsupported device inserted */ +#define SW_MAX 0x20 #define SW_CNT (SW_MAX+1) /* diff --git a/include/linux/input/cy8c_ts.h b/include/linux/input/cy8c_ts.h new file mode 100644 index 0000000000000000000000000000000000000000..d25f31d66da53a42d4c779db4c7645061a0f05b4 --- /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/ft5x06_ts.h b/include/linux/input/ft5x06_ts.h new file mode 100644 index 0000000000000000000000000000000000000000..b2fb3c4a56aec1559712dd8260c2efd334809d65 --- /dev/null +++ b/include/linux/input/ft5x06_ts.h @@ -0,0 +1,31 @@ +/* + * + * FocalTech ft5x06 TouchScreen driver header file. + * + * Copyright (c) 2010 Focal tech Ltd. + * Copyright (c) 2012, 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 __LINUX_FT5X06_TS_H__ +#define __LINUX_FT5X06_TS_H__ + +struct ft5x06_ts_platform_data { + unsigned long irqflags; + u32 x_max; + u32 y_max; + u32 irq_gpio; + u32 reset_gpio; + int (*power_init) (bool); + int (*power_on) (bool); +}; + +#endif diff --git a/include/linux/input/kp_flip_switch.h b/include/linux/input/kp_flip_switch.h new file mode 100644 index 0000000000000000000000000000000000000000..31c0cc4a9e54ec6b83119c33ade12aa5ed3433b5 --- /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 6c07ced0af8105e2cc12e38417c757804a76c34b..c3bb13b47d2c73791f4177a44dd109acf3bc6b34 100644 --- a/include/linux/input/matrix_keypad.h +++ b/include/linux/input/matrix_keypad.h @@ -5,12 +5,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 0000000000000000000000000000000000000000..45df9f7bc3cc3a6e64fcac857455ed45db86852f --- /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/pmic8xxx-pwrkey.h b/include/linux/input/pmic8xxx-pwrkey.h index 6d2974e57109a9bb5afe8bc06dda0e0b6ecb4fde..a32eafd64dae187fab82bd2f83314017e35a9b47 100644 --- a/include/linux/input/pmic8xxx-pwrkey.h +++ b/include/linux/input/pmic8xxx-pwrkey.h @@ -24,6 +24,13 @@ */ struct pm8xxx_pwrkey_platform_data { bool pull_up; + /* Time delay for pwr-key state change interrupt triggering in micro- + * second. The actual delay can only be one of these eight levels: + * 2 sec, 1 sec, 1/2 sec, 1/4 sec, 1/8 sec, 1/16 sec, 1/32 sec, and + * 1/64 sec. The valid range of kpd_trigger_delay_us is 1/64 second to + * 2 seconds. A value within the valid range will be rounded down to the + * closest level. Any value outside the valid range will be rejected. + */ u32 kpd_trigger_delay_us; u32 wakeup; }; diff --git a/include/linux/input/qci_kbd.h b/include/linux/input/qci_kbd.h new file mode 100644 index 0000000000000000000000000000000000000000..5afda7da818f40a437db8e5ddf08b5286168d2c0 --- /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/rmi_i2c.h b/include/linux/input/rmi_i2c.h new file mode 100644 index 0000000000000000000000000000000000000000..65ebbfb7634795771f6ebccddc7d4d9823978887 --- /dev/null +++ b/include/linux/input/rmi_i2c.h @@ -0,0 +1,58 @@ +/** + * + * Synaptics RMI over I2C Physical Layer Driver Header File. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################# + * GPL + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + *############################################################################# + */ + +#ifndef _RMI_I2C_H +#define _RMI_I2C_H + +#include + +/* Sensor-specific configuration data, to be included as the platform data + * for the relevant i2c_board_info entry. + * + * This describes a single RMI4 sensor on an I2C bus, including: + * its I2C address, IRQ (if any), the type of IRQ (if applicable), and an + * optional list of any non-default settings (on a per function basis) + * to be applied at start up. + */ +struct rmi_i2c_platformdata { + /* The seven-bit i2c address of the sensor. */ + int i2c_address; + /* The number of the irq. Set to zero if polling is required. */ + int irq; + /* The type of the irq (e.g., IRQF_TRIGGER_FALLING). + * Only valid if irq != 0 */ + int irq_type; + + /* If >0, the driver will delay this many milliseconds before attempting + * I2C communications. This is necessary because some horribly broken + * development systems don't bring their I2C up very fast after system + * power on or reboot. In most cases, you can safely ignore this. + */ + int delay_ms; + + /* Use this to specify platformdata that is not I2C specific. */ + struct rmi_sensordata *sensordata; +}; + +#endif diff --git a/include/linux/input/rmi_platformdata.h b/include/linux/input/rmi_platformdata.h new file mode 100644 index 0000000000000000000000000000000000000000..8c44d4c71b647a98c1ac322dbfb139a3e2189143 --- /dev/null +++ b/include/linux/input/rmi_platformdata.h @@ -0,0 +1,125 @@ +/** + * + * Synaptics RMI platform data definitions for use in board files. + * Copyright (c) 2007 - 2011, Synaptics Incorporated + * + */ +/* + * This file is licensed under the GPL2 license. + * + *############################################################################ + * GPL + * + * 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. + * + *############################################################################ + */ + +#if !defined(_RMI_PLATFORMDATA_H) +#define _RMI_PLATFORMDATA_H + +#define RMI_F01_INDEX 0x01 +#define RMI_F11_INDEX 0x11 +#define RMI_F19_INDEX 0x19 +#define RMI_F34_INDEX 0x34 + + +/* A couple of structs that are useful for frequently occuring constructs,such + * as coordinate origin offsets or coordinate clipping values. + */ +struct rmi_XY_pair { + int x; + int y; +}; + +struct rmi_range { + int min; + int max; +}; + +/* This contains sensor specific data that is not specialized to I2C or SPI. + */ +struct rmi_sensordata { + /* This will be called from rmi_register_sensor(). You can use it + * to set up gpios, IRQs, and other platform specific infrastructure. + */ + int (*rmi_sensor_setup)(void); + + /* This will be called when the sensor is unloaded. Use this to + * release gpios, IRQs, and other platform specific infrastructure. + */ + void (*rmi_sensor_teardown)(void); + + /* Use this to specify non-default settings on a per function basis. + */ + struct rmi_functiondata_list *perfunctiondata; +}; + +/* This contains the per-function customization for a given function.We store + * the data this way in order to avoid allocating a large sparse array + * typically + * only a few functions are present on a sensor, and even fewer will be have + * custom settings. There is a very small penalty paid for doing a linear + * search through the list to find a given function's data, but since the list + * is typically very short and is searched only at system boot time, this is + * considered acceptable. + * + * When adding new fields to a functiondata struct, please follow these rules: + * - Where possible, use 0 to indicate that the value should be defaulted. + * This works pretty well for bools, ints, and chars. + * - Where this is not practical (for example, in coordinate offsets or + * range clipping), use a pointer. Set that pointer to null to indicate + * that the value should be defaulted. + */ +struct rmi_functiondata { + unsigned char function_index; + void *data; +}; + +/* This can be included in the platformdata for SPI or I2C RMI4 devices to + * customize the settings of the functions on a given sensor. + */ +struct rmi_functiondata_list { + unsigned char count; /* Number of elements in the array */ + struct rmi_functiondata *functiondata; +}; + +struct rmi_f01_functiondata { + /* What this does is product specific. For most, but not all, RMI4 + * devices, you can set this to true in order to request the device + * report data at half the usual rate. This can be useful on slow + * CPUs that don't have the resources to process data at the usual + * rate. However, the meaning of this field is product specific, and + * you should consult the product spec for your sensor to find out + * what this will do. + */ + bool nonstandard_report_rate; +}; + +struct rmi_f11_functiondata { + bool swap_axes; + bool flipX; + bool flipY; + int button_height; + struct rmi_XY_pair *offset; + struct rmi_range *clipX; + struct rmi_range *clipY; +}; + +struct rmi_button_map { + unsigned char nbuttons; + unsigned char *map; +}; + +struct rmi_f19_functiondata { + struct rmi_button_map *button_map; +}; + +#endif diff --git a/include/linux/input/tdisc_shinetsu.h b/include/linux/input/tdisc_shinetsu.h new file mode 100644 index 0000000000000000000000000000000000000000..88f84f2a84886fd2f4e8c3ecaf7c350e2a20dab7 --- /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 2aea5d22db07bc08a9bd0071ac49b7ab59ab592c..7d2b77ea90496a12bbde08c05aee64b8d17d9e6e 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -354,6 +354,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) { @@ -699,5 +700,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 d937580417ba668d343b30b1741d59139f7924b9..95b15d6ef6c35bea0939475b444fd143d91077a3 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -19,7 +19,9 @@ #ifndef __LINUX_IOMMU_H #define __LINUX_IOMMU_H +#include #include +#include #define IOMMU_READ (1) #define IOMMU_WRITE (2) @@ -62,7 +64,7 @@ struct iommu_domain { * @pgsize_bitmap: bitmap of supported page sizes */ struct iommu_ops { - int (*domain_init)(struct iommu_domain *domain); + int (*domain_init)(struct iommu_domain *domain, int flags); void (*domain_destroy)(struct iommu_domain *domain); int (*attach_dev)(struct iommu_domain *domain, struct device *dev); void (*detach_dev)(struct iommu_domain *domain, struct device *dev); @@ -70,17 +72,22 @@ struct iommu_ops { phys_addr_t paddr, size_t size, int prot); size_t (*unmap)(struct iommu_domain *domain, unsigned long iova, size_t size); + int (*map_range)(struct iommu_domain *domain, unsigned int iova, + struct scatterlist *sg, unsigned int len, int prot); + int (*unmap_range)(struct iommu_domain *domain, unsigned int iova, + unsigned int len); phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, unsigned long iova); int (*domain_has_cap)(struct iommu_domain *domain, unsigned long cap); + phys_addr_t (*get_pt_base_addr)(struct iommu_domain *domain); int (*device_group)(struct device *dev, unsigned int *groupid); unsigned long pgsize_bitmap; }; extern int bus_set_iommu(struct bus_type *bus, struct iommu_ops *ops); extern bool iommu_present(struct bus_type *bus); -extern struct iommu_domain *iommu_domain_alloc(struct bus_type *bus); +extern struct iommu_domain *iommu_domain_alloc(struct bus_type *bus, int flags); extern void iommu_domain_free(struct iommu_domain *domain); extern int iommu_attach_device(struct iommu_domain *domain, struct device *dev); @@ -90,10 +97,15 @@ extern int iommu_map(struct iommu_domain *domain, unsigned long iova, phys_addr_t paddr, size_t size, int prot); extern size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size); +extern int iommu_map_range(struct iommu_domain *domain, unsigned int iova, + struct scatterlist *sg, unsigned int len, int prot); +extern int iommu_unmap_range(struct iommu_domain *domain, unsigned int iova, + unsigned int len); extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, unsigned long iova); extern int iommu_domain_has_cap(struct iommu_domain *domain, unsigned long cap); +extern phys_addr_t iommu_get_pt_base_addr(struct iommu_domain *domain); extern void iommu_set_fault_handler(struct iommu_domain *domain, iommu_fault_handler_t handler); extern int iommu_device_group(struct device *dev, unsigned int *groupid); @@ -146,7 +158,7 @@ static inline bool iommu_present(struct bus_type *bus) return false; } -static inline struct iommu_domain *iommu_domain_alloc(struct bus_type *bus) +static inline struct iommu_domain *iommu_domain_alloc(struct bus_type *bus, int flags) { return NULL; } @@ -178,6 +190,19 @@ static inline int iommu_unmap(struct iommu_domain *domain, unsigned long iova, return -ENODEV; } +static inline int iommu_map_range(struct iommu_domain *domain, + unsigned int iova, struct scatterlist *sg, + unsigned int len, int prot) +{ + return -ENODEV; +} + +static inline int iommu_unmap_range(struct iommu_domain *domain, + unsigned int iova, unsigned int len) +{ + return -ENODEV; +} + static inline phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, unsigned long iova) { @@ -190,6 +215,11 @@ static inline int domain_has_cap(struct iommu_domain *domain, return 0; } +static inline phys_addr_t iommu_get_pt_base_addr(struct iommu_domain *domain) +{ + return 0; +} + static inline void iommu_set_fault_handler(struct iommu_domain *domain, iommu_fault_handler_t handler) { diff --git a/include/linux/ion.h b/include/linux/ion.h index d44ce69446dd389687b260792d86f25f766b7f8d..d9443ff4893deabbc71112a0b08be4180249deac 100644 --- a/include/linux/ion.h +++ b/include/linux/ion.h @@ -2,6 +2,7 @@ * include/linux/ion.h * * Copyright (C) 2011 Google, Inc. + * Copyright (c) 2011-2012, 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,8 +18,10 @@ #ifndef _LINUX_ION_H #define _LINUX_ION_H +#include #include + struct ion_handle; /** * enum ion_heap_types - list of all possible types of heaps @@ -27,12 +30,18 @@ struct ion_handle; * @ION_HEAP_TYPE_CARVEOUT: memory allocated from a prereserved * carveout heap, allocations are physically * contiguous - * @ION_HEAP_END: helper for iterating over heaps + * @ION_HEAP_TYPE_IOMMU: IOMMU memory + * @ION_HEAP_TYPE_CP: memory allocated from a prereserved + * carveout heap, allocations are physically + * contiguous. Used for content protection. + * @ION_HEAP_END: helper for iterating over heaps */ enum ion_heap_type { ION_HEAP_TYPE_SYSTEM, ION_HEAP_TYPE_SYSTEM_CONTIG, ION_HEAP_TYPE_CARVEOUT, + ION_HEAP_TYPE_IOMMU, + ION_HEAP_TYPE_CP, ION_HEAP_TYPE_CUSTOM, /* must be last so device specific heaps always are at the end of this enum */ ION_NUM_HEAPS, @@ -41,8 +50,82 @@ enum ion_heap_type { #define ION_HEAP_SYSTEM_MASK (1 << ION_HEAP_TYPE_SYSTEM) #define ION_HEAP_SYSTEM_CONTIG_MASK (1 << ION_HEAP_TYPE_SYSTEM_CONTIG) #define ION_HEAP_CARVEOUT_MASK (1 << ION_HEAP_TYPE_CARVEOUT) +#define ION_HEAP_CP_MASK (1 << ION_HEAP_TYPE_CP) + + +/** + * These are the only ids that should be used for Ion heap ids. + * The ids listed are the order in which allocation will be attempted + * if specified. Don't swap the order of heap ids unless you know what + * you are doing! + * Id's are spaced by purpose to allow new Id's to be inserted in-between (for + * possible fallbacks) + */ + +enum ion_heap_ids { + INVALID_HEAP_ID = -1, + ION_CP_MM_HEAP_ID = 8, + ION_CP_MFC_HEAP_ID = 12, + ION_CP_WB_HEAP_ID = 16, /* 8660 only */ + ION_CAMERA_HEAP_ID = 20, /* 8660 only */ + ION_SF_HEAP_ID = 24, + ION_IOMMU_HEAP_ID = 25, + ION_QSECOM_HEAP_ID = 27, + ION_AUDIO_HEAP_ID = 28, + + ION_MM_FIRMWARE_HEAP_ID = 29, + ION_SYSTEM_HEAP_ID = 30, + + ION_HEAP_ID_RESERVED = 31 /** Bit reserved for ION_SECURE flag */ +}; + +enum ion_fixed_position { + NOT_FIXED, + FIXED_LOW, + FIXED_MIDDLE, + FIXED_HIGH, +}; + +/** + * Flag to use when allocating to indicate that a heap is secure. + */ +#define ION_SECURE (1 << ION_HEAP_ID_RESERVED) + +/** + * Macro should be used with ion_heap_ids defined above. + */ +#define ION_HEAP(bit) (1 << (bit)) + +#define ION_VMALLOC_HEAP_NAME "vmalloc" +#define ION_AUDIO_HEAP_NAME "audio" +#define ION_SF_HEAP_NAME "sf" +#define ION_MM_HEAP_NAME "mm" +#define ION_CAMERA_HEAP_NAME "camera_preview" +#define ION_IOMMU_HEAP_NAME "iommu" +#define ION_MFC_HEAP_NAME "mfc" +#define ION_WB_HEAP_NAME "wb" +#define ION_MM_FIRMWARE_HEAP_NAME "mm_fw" +#define ION_QSECOM_HEAP_NAME "qsecom" +#define ION_FMEM_HEAP_NAME "fmem" + +#define CACHED 1 +#define UNCACHED 0 + +#define ION_CACHE_SHIFT 0 + +#define ION_SET_CACHE(__cache) ((__cache) << ION_CACHE_SHIFT) + +#define ION_IS_CACHED(__flags) ((__flags) & (1 << ION_CACHE_SHIFT)) + +/* + * This flag allows clients when mapping into the IOMMU to specify to + * defer un-mapping from the IOMMU until the buffer memory is freed. + */ +#define ION_IOMMU_UNMAP_DELAYED 1 #ifdef __KERNEL__ +#include +#include struct ion_device; struct ion_heap; struct ion_mapper; @@ -54,17 +137,19 @@ struct ion_buffer; be converted to phys_addr_t. For the time being many kernel interfaces do not accept phys_addr_t's that would have to */ #define ion_phys_addr_t unsigned long +#define ion_virt_addr_t unsigned long /** * struct ion_platform_heap - defines a heap in the given platform * @type: type of the heap from ion_heap_type enum - * @id: unique identifier for heap. When allocating (lower numbers + * @id: unique identifier for heap. When allocating (lower numbers * will be allocated from first) * @name: used for debug purposes * @base: base address of heap in physical memory if applicable * @size: size of the heap in bytes if applicable - * - * Provided by the board file. + * @memory_type:Memory type used for the heap + * @has_outer_cache: set to 1 if outer cache is used, 0 otherwise. + * @extra_data: Extra data specific to each heap type */ struct ion_platform_heap { enum ion_heap_type type; @@ -72,30 +157,100 @@ struct ion_platform_heap { const char *name; ion_phys_addr_t base; size_t size; + enum ion_memory_types memory_type; + unsigned int has_outer_cache; + void *extra_data; +}; + +/** + * struct ion_cp_heap_pdata - defines a content protection heap in the given + * platform + * @permission_type: Memory ID used to identify the memory to TZ + * @align: Alignment requirement for the memory + * @secure_base: Base address for securing the heap. + * Note: This might be different from actual base address + * of this heap in the case of a shared heap. + * @secure_size: Memory size for securing the heap. + * Note: This might be different from actual size + * of this heap in the case of a shared heap. + * @reusable Flag indicating whether this heap is reusable of not. + * (see FMEM) + * @mem_is_fmem Flag indicating whether this memory is coming from fmem + * or not. + * @fixed_position If nonzero, position in the fixed area. + * @virt_addr: Virtual address used when using fmem. + * @iommu_map_all: Indicates whether we should map whole heap into IOMMU. + * @iommu_2x_map_domain: Indicates the domain to use for overmapping. + * @request_region: function to be called when the number of allocations + * goes from 0 -> 1 + * @release_region: function to be called when the number of allocations + * goes from 1 -> 0 + * @setup_region: function to be called upon ion registration + * + */ +struct ion_cp_heap_pdata { + enum ion_permission_type permission_type; + unsigned int align; + ion_phys_addr_t secure_base; /* Base addr used when heap is shared */ + size_t secure_size; /* Size used for securing heap when heap is shared*/ + int reusable; + int mem_is_fmem; + enum ion_fixed_position fixed_position; + int iommu_map_all; + int iommu_2x_map_domain; + ion_virt_addr_t *virt_addr; + int (*request_region)(void *); + int (*release_region)(void *); + void *(*setup_region)(void); +}; + +/** + * struct ion_co_heap_pdata - defines a carveout heap in the given platform + * @adjacent_mem_id: Id of heap that this heap must be adjacent to. + * @align: Alignment requirement for the memory + * @mem_is_fmem Flag indicating whether this memory is coming from fmem + * or not. + * @fixed_position If nonzero, position in the fixed area. + * @request_region: function to be called when the number of allocations + * goes from 0 -> 1 + * @release_region: function to be called when the number of allocations + * goes from 1 -> 0 + * @setup_region: function to be called upon ion registration + * + */ +struct ion_co_heap_pdata { + int adjacent_mem_id; + unsigned int align; + int mem_is_fmem; + enum ion_fixed_position fixed_position; + int (*request_region)(void *); + int (*release_region)(void *); + void *(*setup_region)(void); }; /** * struct ion_platform_data - array of platform heaps passed from board file - * @nr: number of structures in the array - * @heaps: array of platform_heap structions + * @has_outer_cache: set to 1 if outer cache is used, 0 otherwise. + * @nr: number of structures in the array + * @request_region: function to be called when the number of allocations goes + * from 0 -> 1 + * @release_region: function to be called when the number of allocations goes + * from 1 -> 0 + * @setup_region: function to be called upon ion registration + * @heaps: array of platform_heap structions * * Provided by the board file in the form of platform data to a platform device. */ struct ion_platform_data { + unsigned int has_outer_cache; int nr; + int (*request_region)(void *); + int (*release_region)(void *); + void *(*setup_region)(void); struct ion_platform_heap heaps[]; }; -/** - * ion_reserve() - reserve memory for ion heaps if applicable - * @data: platform data specifying starting physical address and - * size - * - * Calls memblock reserve to set aside memory for heaps that are - * located at specific memory addresses or of specfic sizes not - * managed by the kernel - */ -void ion_reserve(struct ion_platform_data *data); +#ifdef CONFIG_ION /** * ion_client_create() - allocate a client and returns it @@ -106,6 +261,17 @@ void ion_reserve(struct ion_platform_data *data); struct ion_client *ion_client_create(struct ion_device *dev, unsigned int heap_mask, const char *name); +/** + * msm_ion_client_create - allocate a client using the ion_device specified in + * drivers/gpu/ion/msm/msm_ion.c + * + * heap_mask and name are the same as ion_client_create, return values + * are the same as ion_client_create. + */ + +struct ion_client *msm_ion_client_create(unsigned int heap_mask, + const char *name); + /** * ion_client_destroy() - free's a client and all it's handles * @client: the client @@ -149,7 +315,7 @@ void ion_free(struct ion_client *client, struct ion_handle *handle); * This function queries the heap for a particular handle to get the * handle's physical address. It't output is only correct if * a heap returns physically contiguous memory -- in other cases - * this api should not be implemented -- ion_sg_table should be used + * this api should not be implemented -- ion_map_dma should be used * instead. Returns -EINVAL if the handle is invalid. This has * no implications on the reference counting of the handle -- * the returned value may not be valid if the caller is not @@ -158,26 +324,18 @@ void ion_free(struct ion_client *client, struct ion_handle *handle); int ion_phys(struct ion_client *client, struct ion_handle *handle, ion_phys_addr_t *addr, size_t *len); -/** - * ion_map_dma - return an sg_table describing a handle - * @client: the client - * @handle: the handle - * - * This function returns the sg_table describing - * a particular ion handle. - */ -struct sg_table *ion_sg_table(struct ion_client *client, - struct ion_handle *handle); - /** * ion_map_kernel - create mapping for the given handle * @client: the client * @handle: handle to map + * @flags: flags for this mapping * * Map the given handle into the kernel and return a kernel address that - * can be used to access this address. + * can be used to access this address. If no flags are specified, this + * will return a non-secure uncached mapping. */ -void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle); +void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle, + unsigned long flags); /** * ion_unmap_kernel() - destroy a kernel mapping for a handle @@ -187,23 +345,318 @@ void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle); void ion_unmap_kernel(struct ion_client *client, struct ion_handle *handle); /** - * ion_share_dma_buf() - given an ion client, create a dma-buf fd + * ion_map_dma - create a dma mapping for a given handle * @client: the client - * @handle: the handle + * @handle: handle to map + * + * Return an sglist describing the given handle */ -int ion_share_dma_buf(struct ion_client *client, struct ion_handle *handle); +struct scatterlist *ion_map_dma(struct ion_client *client, + struct ion_handle *handle, + unsigned long flags); /** - * ion_import_dma_buf() - given an dma-buf fd from the ion exporter get handle + * ion_unmap_dma() - destroy a dma mapping for a handle * @client: the client - * @fd: the dma-buf fd + * @handle: handle to unmap + */ +void ion_unmap_dma(struct ion_client *client, struct ion_handle *handle); + +/** + * ion_share() - given a handle, obtain a buffer to pass to other clients + * @client: the client + * @handle: the handle to share + * + * Given a handle, return a buffer, which exists in a global name + * space, and can be passed to other clients. Should be passed into ion_import + * to obtain a new handle for this buffer. + * + * NOTE: This function does do not an extra reference. The burden is on the + * caller to make sure the buffer doesn't go away while it's being passed to + * another client. That is, ion_free should not be called on this handle until + * the buffer has been imported into the other client. + */ +struct ion_buffer *ion_share(struct ion_client *client, + struct ion_handle *handle); + +/** + * ion_import() - given an buffer in another client, import it + * @client: this blocks client + * @buffer: the buffer to import (as obtained from ion_share) + * + * Given a buffer, add it to the client and return the handle to use to refer + * to it further. This is called to share a handle from one kernel client to + * another. + */ +struct ion_handle *ion_import(struct ion_client *client, + struct ion_buffer *buffer); + +/** + * ion_import_fd() - given an fd obtained via ION_IOC_SHARE ioctl, import it + * @client: this blocks client + * @fd: the fd + * + * A helper function for drivers that will be recieving ion buffers shared + * with them from userspace. These buffers are represented by a file + * descriptor obtained as the return from the ION_IOC_SHARE ioctl. + * This function coverts that fd into the underlying buffer, and returns + * the handle to use to refer to it further. + */ +struct ion_handle *ion_import_fd(struct ion_client *client, int fd); + +/** + * ion_handle_get_flags - get the flags for a given handle + * + * @client - client who allocated the handle + * @handle - handle to get the flags + * @flags - pointer to store the flags + * + * Gets the current flags for a handle. These flags indicate various options + * of the buffer (caching, security, etc.) + */ +int ion_handle_get_flags(struct ion_client *client, struct ion_handle *handle, + unsigned long *flags); + + +/** + * ion_map_iommu - map the given handle into an iommu + * + * @client - client who allocated the handle + * @handle - handle to map + * @domain_num - domain number to map to + * @partition_num - partition number to allocate iova from + * @align - alignment for the iova + * @iova_length - length of iova to map. If the iova length is + * greater than the handle length, the remaining + * address space will be mapped to a dummy buffer. + * @iova - pointer to store the iova address + * @buffer_size - pointer to store the size of the buffer + * @flags - flags for options to map + * @iommu_flags - flags specific to the iommu. + * + * Maps the handle into the iova space specified via domain number. Iova + * will be allocated from the partition specified via partition_num. + * Returns 0 on success, negative value on error. + */ +int ion_map_iommu(struct ion_client *client, struct ion_handle *handle, + int domain_num, int partition_num, unsigned long align, + unsigned long iova_length, unsigned long *iova, + unsigned long *buffer_size, + unsigned long flags, unsigned long iommu_flags); + + +/** + * ion_handle_get_size - get the allocated size of a given handle + * + * @client - client who allocated the handle + * @handle - handle to get the size + * @size - pointer to store the size + * + * gives the allocated size of a handle. returns 0 on success, negative + * value on error + * + * NOTE: This is intended to be used only to get a size to pass to map_iommu. + * You should *NOT* rely on this for any other usage. + */ + +int ion_handle_get_size(struct ion_client *client, struct ion_handle *handle, + unsigned long *size); + +/** + * ion_unmap_iommu - unmap the handle from an iommu + * + * @client - client who allocated the handle + * @handle - handle to unmap + * @domain_num - domain to unmap from + * @partition_num - partition to unmap from * - * Given an dma-buf fd that was allocated through ion via ion_share_dma_buf, - * import that fd and return a handle representing it. If a dma-buf from - * another exporter is passed in this function will return ERR_PTR(-EINVAL) + * Decrement the reference count on the iommu mapping. If the count is + * 0, the mapping will be removed from the iommu. */ -struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd); +void ion_unmap_iommu(struct ion_client *client, struct ion_handle *handle, + int domain_num, int partition_num); + +/** + * ion_secure_heap - secure a heap + * + * @client - a client that has allocated from the heap heap_id + * @heap_id - heap id to secure. + * + * Secure a heap + * Returns 0 on success + */ +int ion_secure_heap(struct ion_device *dev, int heap_id); + +/** + * ion_unsecure_heap - un-secure a heap + * + * @client - a client that has allocated from the heap heap_id + * @heap_id - heap id to un-secure. + * + * Un-secure a heap + * Returns 0 on success + */ +int ion_unsecure_heap(struct ion_device *dev, int heap_id); + +/** + * msm_ion_secure_heap - secure a heap. Wrapper around ion_secure_heap. + * + * @heap_id - heap id to secure. + * + * Secure a heap + * Returns 0 on success + */ +int msm_ion_secure_heap(int heap_id); + +/** + * msm_ion_unsecure_heap - unsecure a heap. Wrapper around ion_unsecure_heap. + * + * @heap_id - heap id to secure. + * + * Un-secure a heap + * Returns 0 on success + */ +int msm_ion_unsecure_heap(int heap_id); + +/** + * msm_ion_do_cache_op - do cache operations. + * + * @client - pointer to ION client. + * @handle - pointer to buffer handle. + * @vaddr - virtual address to operate on. + * @len - Length of data to do cache operation on. + * @cmd - Cache operation to perform: + * ION_IOC_CLEAN_CACHES + * ION_IOC_INV_CACHES + * ION_IOC_CLEAN_INV_CACHES + * + * Returns 0 on success + */ +int msm_ion_do_cache_op(struct ion_client *client, struct ion_handle *handle, + void *vaddr, unsigned long len, unsigned int cmd); + +#else +static inline struct ion_client *ion_client_create(struct ion_device *dev, + unsigned int heap_mask, const char *name) +{ + return ERR_PTR(-ENODEV); +} + +static inline struct ion_client *msm_ion_client_create(unsigned int heap_mask, + const char *name) +{ + return ERR_PTR(-ENODEV); +} + +static inline void ion_client_destroy(struct ion_client *client) { } + +static inline struct ion_handle *ion_alloc(struct ion_client *client, + size_t len, size_t align, unsigned int flags) +{ + return ERR_PTR(-ENODEV); +} + +static inline void ion_free(struct ion_client *client, + struct ion_handle *handle) { } + + +static inline int ion_phys(struct ion_client *client, + struct ion_handle *handle, ion_phys_addr_t *addr, size_t *len) +{ + return -ENODEV; +} + +static inline void *ion_map_kernel(struct ion_client *client, + struct ion_handle *handle, unsigned long flags) +{ + return ERR_PTR(-ENODEV); +} + +static inline void ion_unmap_kernel(struct ion_client *client, + struct ion_handle *handle) { } + +static inline struct scatterlist *ion_map_dma(struct ion_client *client, + struct ion_handle *handle, unsigned long flags) +{ + return ERR_PTR(-ENODEV); +} + +static inline void ion_unmap_dma(struct ion_client *client, + struct ion_handle *handle) { } + +static inline struct ion_buffer *ion_share(struct ion_client *client, + struct ion_handle *handle) +{ + return ERR_PTR(-ENODEV); +} + +static inline struct ion_handle *ion_import(struct ion_client *client, + struct ion_buffer *buffer) +{ + return ERR_PTR(-ENODEV); +} + +static inline struct ion_handle *ion_import_fd(struct ion_client *client, + int fd) +{ + return ERR_PTR(-ENODEV); +} + +static inline int ion_handle_get_flags(struct ion_client *client, + struct ion_handle *handle, unsigned long *flags) +{ + return -ENODEV; +} + +static inline int ion_map_iommu(struct ion_client *client, + struct ion_handle *handle, int domain_num, + int partition_num, unsigned long align, + unsigned long iova_length, unsigned long *iova, + unsigned long *buffer_size, + unsigned long flags, + unsigned long iommu_flags) +{ + return -ENODEV; +} + +static inline void ion_unmap_iommu(struct ion_client *client, + struct ion_handle *handle, int domain_num, + int partition_num) +{ + return; +} + +static inline int ion_secure_heap(struct ion_device *dev, int heap_id) +{ + return -ENODEV; + +} + +static inline int ion_unsecure_heap(struct ion_device *dev, int heap_id) +{ + return -ENODEV; +} + +static inline int msm_ion_secure_heap(int heap_id) +{ + return -ENODEV; + +} + +static inline int msm_ion_unsecure_heap(int heap_id) +{ + return -ENODEV; +} + +static inline int msm_ion_do_cache_op(struct ion_client *client, + struct ion_handle *handle, void *vaddr, + unsigned long len, unsigned int cmd) +{ + return -ENODEV; +} + +#endif /* CONFIG_ION */ #endif /* __KERNEL__ */ /** @@ -267,6 +720,40 @@ struct ion_custom_data { unsigned long arg; }; + +/* struct ion_flush_data - data passed to ion for flushing caches + * + * @handle: handle with data to flush + * @fd: fd to flush + * @vaddr: userspace virtual address mapped with mmap + * @offset: offset into the handle to flush + * @length: length of handle to flush + * + * Performs cache operations on the handle. If p is the start address + * of the handle, p + offset through p + offset + length will have + * the cache operations performed + */ +struct ion_flush_data { + struct ion_handle *handle; + int fd; + void *vaddr; + unsigned int offset; + unsigned int length; +}; + +/* struct ion_flag_data - information about flags for this buffer + * + * @handle: handle to get flags from + * @flags: flags of this handle + * + * Takes handle as an input and outputs the flags from the handle + * in the flag field. + */ +struct ion_flag_data { + struct ion_handle *handle; + unsigned long flags; +}; + #define ION_IOC_MAGIC 'I' /** @@ -323,4 +810,35 @@ struct ion_custom_data { */ #define ION_IOC_CUSTOM _IOWR(ION_IOC_MAGIC, 6, struct ion_custom_data) + +/** + * DOC: ION_IOC_CLEAN_CACHES - clean the caches + * + * Clean the caches of the handle specified. + */ +#define ION_IOC_CLEAN_CACHES _IOWR(ION_IOC_MAGIC, 7, \ + struct ion_flush_data) +/** + * DOC: ION_MSM_IOC_INV_CACHES - invalidate the caches + * + * Invalidate the caches of the handle specified. + */ +#define ION_IOC_INV_CACHES _IOWR(ION_IOC_MAGIC, 8, \ + struct ion_flush_data) +/** + * DOC: ION_MSM_IOC_CLEAN_CACHES - clean and invalidate the caches + * + * Clean and invalidate the caches of the handle specified. + */ +#define ION_IOC_CLEAN_INV_CACHES _IOWR(ION_IOC_MAGIC, 9, \ + struct ion_flush_data) + +/** + * DOC: ION_IOC_GET_FLAGS - get the flags of the handle + * + * Gets the flags of the current handle which indicate cachability, + * secure state etc. + */ +#define ION_IOC_GET_FLAGS _IOWR(ION_IOC_MAGIC, 10, \ + struct ion_flag_data) #endif /* _LINUX_ION_H */ diff --git a/include/linux/iopoll.h b/include/linux/iopoll.h new file mode 100644 index 0000000000000000000000000000000000000000..7169870dcf66ac76897daa2a4af5e22a60ec8870 --- /dev/null +++ b/include/linux/iopoll.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_IOPOLL_H +#define _LINUX_IOPOLL_H + +#include +#include +#include +#include +#include +#include + +/** + * readl_poll_timeout - Periodically poll an address until a condition is met or a timeout occurs + * @addr: Address to poll + * @val: Variable to read the value into + * @cond: Break condition (usually involving @val) + * @sleep_us: Maximum time to sleep between reads in uS (0 tight-loops) + * @timeout_us: Timeout in uS, 0 means never timeout + * + * Returns 0 on success and -ETIMEDOUT upon a timeout. In either + * case, the last read value at @addr is stored in @val. Must not + * be called from atomic context if sleep_us or timeout_us are used. + */ +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ +({ \ + unsigned long timeout = jiffies + usecs_to_jiffies(timeout_us); \ + might_sleep_if(timeout_us); \ + for (;;) { \ + (val) = readl(addr); \ + if ((cond) || (timeout_us && time_after(jiffies, timeout))) \ + break; \ + if (sleep_us) \ + usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +/** + * readl_poll - Periodically poll an address until a condition is met + * @addr: Address to poll + * @val: Variable to read the value into + * @cond: Break condition (usually involving @val) + * @sleep_us: Maximum time to sleep between reads in uS (0 tight-loops) + * + * Must not be called from atomic context if sleep_us is used. + */ +#define readl_poll(addr, val, cond, sleep_us) \ + readl_poll_timeout(addr, val, cond, sleep_us, 0) + +/** + * readl_tight_poll_timeout - Tight-loop on an address until a condition is met or a timeout occurs + * @addr: Address to poll + * @val: Variable to read the value into + * @cond: Break condition (usually involving @val) + * @timeout_us: Timeout in uS, 0 means never timeout + * + * Returns 0 on success and -ETIMEDOUT upon a timeout. In either + * case, the last read value at @addr is stored in @val. Must not + * be called from atomic context if timeout_us is used. + */ +#define readl_tight_poll_timeout(addr, val, cond, timeout_us) \ + readl_poll_timeout(addr, val, cond, 0, timeout_us) + +/** + * readl_tight_poll - Tight-loop on an address until a condition is met + * @addr: Address to poll + * @val: Variable to read the value into + * @cond: Break condition (usually involving @val) + * + * May be called from atomic context. + */ +#define readl_tight_poll(addr, val, cond) \ + readl_poll_timeout(addr, val, cond, 0, 0) + +#endif /* _LINUX_IOPOLL_H */ diff --git a/include/linux/ioport.h b/include/linux/ioport.h index e885ba23de7017882457ff0dc2d6fbb6f6575b13..34ae6e6c65e125bae340ca401808282bbecec3ba 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -139,6 +139,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 b27cfcfd3a59c15319488580a14de42c18174744..2a67ab2c6666b9c13cbd564c193eb152668dc3d9 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -292,6 +292,7 @@ static inline irq_hw_number_t irqd_to_hwirq(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 @@ -320,6 +321,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); @@ -418,6 +420,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/irqdomain.h b/include/linux/irqdomain.h index c65740d76e663a35da56f3e680835d8349e8f9dd..ed6bb39121f79458918dcebc9b666534a5c6643e 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -9,178 +9,106 @@ * representation into a hardware irq number that can be mapped back to a * Linux irq number without any extra platform support code. * - * Interrupt controller "domain" data structure. This could be defined as a - * irq domain controller. That is, it handles the mapping between hardware - * and virtual interrupt numbers for a given interrupt domain. The domain - * structure is generally created by the PIC code for a given PIC instance - * (though a domain can cover more than one PIC if they have a flat number - * model). It's the domain callbacks that are responsible for setting the - * irq_chip on a given irq_desc after it's been mapped. - * - * The host code and data structures are agnostic to whether or not - * we use an open firmware device-tree. We do have references to struct - * device_node in two places: in irq_find_host() to find the host matching - * a given interrupt controller node, and of course as an argument to its - * counterpart domain->ops->match() callback. However, those are treated as - * generic pointers by the core and the fact that it's actually a device-node - * pointer is purely a convention between callers and implementation. This - * code could thus be used on other architectures by replacing those two - * by some sort of arch-specific void * "token" used to identify interrupt - * controllers. + * irq_domain is expected to be embedded in an interrupt controller's private + * data structure. */ - #ifndef _LINUX_IRQDOMAIN_H #define _LINUX_IRQDOMAIN_H -#include -#include +#include +#include +#ifdef CONFIG_IRQ_DOMAIN struct device_node; struct irq_domain; -struct of_device_id; - -/* Number of irqs reserved for a legacy isa controller */ -#define NUM_ISA_INTERRUPTS 16 /** * struct irq_domain_ops - Methods for irq_domain objects - * @match: Match an interrupt controller device node to a host, returns - * 1 on a match - * @map: Create or update a mapping between a virtual irq number and a hw - * irq number. This is called only once for a given mapping. - * @unmap: Dispose of such a mapping - * @xlate: Given a device tree node and interrupt specifier, decode - * the hardware irq number and linux irq type value. - * - * Functions below are provided by the driver and called whenever a new mapping - * is created or an old mapping is disposed. The driver can then proceed to - * whatever internal data structures management is required. It also needs - * to setup the irq_desc when returning from map(). + * @to_irq: (optional) given a local hardware irq number, return the linux + * irq number. If to_irq is not implemented, then the irq_domain + * will use this translation: irq = (domain->irq_base + hwirq) + * @dt_translate: Given a device tree node and interrupt specifier, decode + * the hardware irq number and linux irq type value. */ struct irq_domain_ops { - int (*match)(struct irq_domain *d, struct device_node *node); - int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); - void (*unmap)(struct irq_domain *d, unsigned int virq); - int (*xlate)(struct irq_domain *d, struct device_node *node, - const u32 *intspec, unsigned int intsize, - unsigned long *out_hwirq, unsigned int *out_type); + unsigned int (*to_irq)(struct irq_domain *d, unsigned long hwirq); + +#ifdef CONFIG_OF + int (*dt_translate)(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type); +#endif /* CONFIG_OF */ }; /** * struct irq_domain - Hardware interrupt number translation object - * @link: Element in global irq_domain list. - * @revmap_type: Method used for reverse mapping hwirq numbers to linux irq. This - * will be one of the IRQ_DOMAIN_MAP_* values. - * @revmap_data: Revmap method specific data. - * @ops: pointer to irq_domain methods - * @host_data: private data pointer for use by owner. Not touched by irq_domain - * core code. + * @list: Element in global irq_domain list. * @irq_base: Start of irq_desc range assigned to the irq_domain. The creator * of the irq_domain is responsible for allocating the array of * irq_desc structures. * @nr_irq: Number of irqs managed by the irq domain * @hwirq_base: Starting number for hwirqs managed by the irq domain + * @ops: pointer to irq_domain methods + * @priv: private data pointer for use by owner. Not touched by irq_domain + * core code. * @of_node: (optional) Pointer to device tree nodes associated with the * irq_domain. Used when decoding device tree interrupt specifiers. */ struct irq_domain { - struct list_head link; - - /* type of reverse mapping_technique */ - unsigned int revmap_type; - union { - struct { - unsigned int size; - unsigned int first_irq; - irq_hw_number_t first_hwirq; - } legacy; - struct { - unsigned int size; - unsigned int *revmap; - } linear; - struct { - unsigned int max_irq; - } nomap; - struct radix_tree_root tree; - } revmap_data; + struct list_head list; + unsigned int irq_base; + unsigned int nr_irq; + unsigned int hwirq_base; const struct irq_domain_ops *ops; - void *host_data; - irq_hw_number_t inval_irq; - - /* Optional device node pointer */ + void *priv; struct device_node *of_node; }; -#ifdef CONFIG_IRQ_DOMAIN -struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, - unsigned int size, - unsigned int first_irq, - irq_hw_number_t first_hwirq, - const struct irq_domain_ops *ops, - void *host_data); -struct irq_domain *irq_domain_add_linear(struct device_node *of_node, - unsigned int size, - const struct irq_domain_ops *ops, - void *host_data); -struct irq_domain *irq_domain_add_nomap(struct device_node *of_node, - unsigned int max_irq, - const struct irq_domain_ops *ops, - void *host_data); -struct irq_domain *irq_domain_add_tree(struct device_node *of_node, - const struct irq_domain_ops *ops, - void *host_data); - -extern struct irq_domain *irq_find_host(struct device_node *node); -extern void irq_set_default_host(struct irq_domain *host); - -static inline struct irq_domain *irq_domain_add_legacy_isa( - struct device_node *of_node, - const struct irq_domain_ops *ops, - void *host_data) +/** + * irq_domain_to_irq() - Translate from a hardware irq to a linux irq number + * + * Returns the linux irq number associated with a hardware irq. By default, + * the mapping is irq == domain->irq_base + hwirq, but this mapping can + * be overridden if the irq_domain implements a .to_irq() hook. + */ +static inline unsigned int irq_domain_to_irq(struct irq_domain *d, + unsigned long hwirq) { - return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops, - host_data); + if (d->ops->to_irq) + return d->ops->to_irq(d, hwirq); + if (WARN_ON(hwirq < d->hwirq_base)) + return 0; + return d->irq_base + hwirq - d->hwirq_base; } -extern struct irq_domain *irq_find_host(struct device_node *node); -extern void irq_set_default_host(struct irq_domain *host); +#define irq_domain_for_each_hwirq(d, hw) \ + for (hw = d->hwirq_base; hw < d->hwirq_base + d->nr_irq; hw++) + +#define irq_domain_for_each_irq(d, hw, irq) \ + for (hw = d->hwirq_base, irq = irq_domain_to_irq(d, hw); \ + hw < d->hwirq_base + d->nr_irq; \ + hw++, irq = irq_domain_to_irq(d, hw)) -extern unsigned int irq_create_mapping(struct irq_domain *host, - irq_hw_number_t hwirq); extern void irq_dispose_mapping(unsigned int virq); -extern unsigned int irq_find_mapping(struct irq_domain *host, - irq_hw_number_t hwirq); -extern unsigned int irq_create_direct_mapping(struct irq_domain *host); -extern void irq_radix_revmap_insert(struct irq_domain *host, unsigned int virq, - irq_hw_number_t hwirq); -extern unsigned int irq_radix_revmap_lookup(struct irq_domain *host, - irq_hw_number_t hwirq); -extern unsigned int irq_linear_revmap(struct irq_domain *host, - irq_hw_number_t hwirq); -extern const struct irq_domain_ops irq_domain_simple_ops; +extern int irq_domain_add(struct irq_domain *domain); +extern void irq_domain_del(struct irq_domain *domain); +extern void irq_domain_register(struct irq_domain *domain); +extern void irq_domain_register_irq(struct irq_domain *domain, int hwirq); +extern void irq_domain_unregister(struct irq_domain *domain); +extern void irq_domain_unregister_irq(struct irq_domain *domain, int hwirq); +extern int irq_domain_find_free_range(unsigned int from, unsigned int cnt); -/* stock xlate functions */ -int irq_domain_xlate_onecell(struct irq_domain *d, struct device_node *ctrlr, - const u32 *intspec, unsigned int intsize, - irq_hw_number_t *out_hwirq, unsigned int *out_type); -int irq_domain_xlate_twocell(struct irq_domain *d, struct device_node *ctrlr, - const u32 *intspec, unsigned int intsize, - irq_hw_number_t *out_hwirq, unsigned int *out_type); -int irq_domain_xlate_onetwocell(struct irq_domain *d, struct device_node *ctrlr, - const u32 *intspec, unsigned int intsize, - irq_hw_number_t *out_hwirq, unsigned int *out_type); +extern struct irq_domain_ops irq_domain_simple_ops; +#endif /* CONFIG_IRQ_DOMAIN */ -#if defined(CONFIG_OF_IRQ) +#if defined(CONFIG_IRQ_DOMAIN) && defined(CONFIG_OF_IRQ) +extern void irq_domain_add_simple(struct device_node *controller, int irq_base); extern void irq_domain_generate_simple(const struct of_device_id *match, u64 phys_base, unsigned int irq_start); -#else /* CONFIG_OF_IRQ */ +#else /* CONFIG_IRQ_DOMAIN && CONFIG_OF_IRQ */ static inline void irq_domain_generate_simple(const struct of_device_id *match, u64 phys_base, unsigned int irq_start) { } -#endif /* !CONFIG_OF_IRQ */ - -#else /* CONFIG_IRQ_DOMAIN */ -static inline void irq_dispose_mapping(unsigned int virq) { } -#endif /* !CONFIG_IRQ_DOMAIN */ +#endif /* CONFIG_IRQ_DOMAIN && CONFIG_OF_IRQ */ #endif /* _LINUX_IRQDOMAIN_H */ diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h index 387571959dd9a1219c90dc952c29f8382698f397..6883e197acb9e939156c4934d9cc7150b1b107f5 100644 --- a/include/linux/kallsyms.h +++ b/include/linux/kallsyms.h @@ -36,6 +36,7 @@ const char *kallsyms_lookup(unsigned long addr, /* Look up a kernel symbol and return it in a text buffer. */ extern int sprint_symbol(char *buffer, unsigned long address); +extern int sprint_symbol_no_offset(char *buffer, unsigned long address); extern int sprint_backtrace(char *buffer, unsigned long address); /* Look up a kernel symbol and print it to the kernel messages. */ @@ -80,6 +81,12 @@ static inline int sprint_symbol(char *buffer, unsigned long addr) return 0; } +static inline int sprint_symbol_no_offset(char *buffer, unsigned long addr) +{ + *buffer = '\0'; + return 0; +} + static inline int sprint_backtrace(char *buffer, unsigned long addr) { *buffer = '\0'; diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 747404ab4c9297fc8a20355f59f8eeaed0c9352e..f581c8f5ef8b3c6616b3961a3a6a390041a70685 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -710,4 +710,7 @@ extern char *mach_panic_string; #endif /* __KERNEL__ */ +/* To identify board information in panic logs, set this */ +extern char *mach_panic_string; + #endif diff --git a/include/linux/kexec.h b/include/linux/kexec.h index 0d7d6a1b172f29fde03d030168b253197b84f479..af84a25ef6b004711241e1a0800352f425f11c30 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -126,6 +126,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/ks8851.h b/include/linux/ks8851.h new file mode 100644 index 0000000000000000000000000000000000000000..6970f47ee95230b5092e20c4ce860bdab5186f29 --- /dev/null +++ b/include/linux/ks8851.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. + */ + +/* struct ks8851_pdata - platform data definition for a KS8851 device + * @irq_gpio - GPIO pin number for the KS8851 IRQ line + * @rst_gpio - GPIO pin number for the KS8851 Reset line + * + * Platform data may be omitted (or individual GPIO numbers set to -1) to + * avoid doing any GPIO configuration in the driver. + */ +struct ks8851_pdata { + int irq_gpio; + int rst_gpio; +}; diff --git a/include/linux/leds-pm8xxx.h b/include/linux/leds-pm8xxx.h new file mode 100644 index 0000000000000000000000000000000000000000..60755de3b8c369dacd61e313ce8070af24a389c5 --- /dev/null +++ b/include/linux/leds-pm8xxx.h @@ -0,0 +1,135 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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__ + +#include +#include + +#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, + PM8XXX_ID_WLED, + PM8XXX_ID_RGB_LED_RED, + PM8XXX_ID_RGB_LED_GREEN, + PM8XXX_ID_RGB_LED_BLUE, + PM8XXX_ID_MAX, +}; + +/** + * pm8xxx_led_modes - Operating modes of LEDs + */ +enum pm8xxx_led_modes { + PM8XXX_LED_MODE_MANUAL, + PM8XXX_LED_MODE_PWM1, + PM8XXX_LED_MODE_PWM2, + PM8XXX_LED_MODE_PWM3, + PM8XXX_LED_MODE_DTEST1, + PM8XXX_LED_MODE_DTEST2, + PM8XXX_LED_MODE_DTEST3, + PM8XXX_LED_MODE_DTEST4 +}; + +/* current boost limit */ +enum wled_current_bost_limit { + WLED_CURR_LIMIT_105mA, + WLED_CURR_LIMIT_385mA, + WLED_CURR_LIMIT_525mA, + WLED_CURR_LIMIT_805mA, + WLED_CURR_LIMIT_980mA, + WLED_CURR_LIMIT_1260mA, + WLED_CURR_LIMIT_1400mA, + WLED_CURR_LIMIT_1680mA, +}; + +/* over voltage protection threshold */ +enum wled_ovp_threshold { + WLED_OVP_35V, + WLED_OVP_32V, + WLED_OVP_29V, + WLED_OVP_37V, +}; + +/** + * wled_config_data - wled configuration data + * @num_strings - number of wled strings supported + * @ovp_val - over voltage protection threshold + * @boost_curr_lim - boot current limit + * @cp_select - high pole capacitance + * @ctrl_delay_us - delay in activation of led + * @dig_mod_gen_en - digital module generator + * @cs_out_en - current sink output enable + * @op_fdbck - selection of output as feedback for the boost + */ +struct wled_config_data { + u8 num_strings; + u8 ovp_val; + u8 boost_curr_lim; + u8 cp_select; + u8 ctrl_delay_us; + bool dig_mod_gen_en; + bool cs_out_en; + bool op_fdbck; +}; + +/** + * pm8xxx_led_config - led configuration parameters + * @id - LED id + * @mode - LED mode + * @max_current - maximum current that LED can sustain + * @pwm_channel - PWM channel ID the LED is driven to + * @pwm_period_us - PWM period value in micro seconds + * @default_state - default state of the led + * @pwm_duty_cycles - PWM duty cycle information + */ +struct pm8xxx_led_config { + u8 id; + u8 mode; + u16 max_current; + int pwm_channel; + u32 pwm_period_us; + bool default_state; + struct pm8xxx_pwm_duty_cycles *pwm_duty_cycles; + struct wled_config_data *wled_cfg; +}; + +/** + * pm8xxx_led_platform_data - platform data for LED + * @led_core - array of LEDs. Each datum in array contains + * core data for the LED + * @configs - array of platform configuration parameters + * for each LED. It maps one-to-one with + * array of LEDs + * @num_configs - count of members of configs array + */ +struct pm8xxx_led_platform_data { + struct led_platform_data *led_core; + struct pm8xxx_led_config *configs; + u32 num_configs; +}; +#endif /* __LEDS_PM8XXX_H__ */ diff --git a/include/linux/leds-pmic8058.h b/include/linux/leds-pmic8058.h new file mode 100644 index 0000000000000000000000000000000000000000..cbfde9f7b24c839e9069c965f4d1c18d71855b27 --- /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 0000000000000000000000000000000000000000..99b7d04d58ee14d6d2f87b4a021a683fd3c01b94 --- /dev/null +++ b/include/linux/libra_sdioif.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 *); +typedef void (notify_card_removal_t)(void); +typedef void (shutdown_handler_t)(void); + +int libra_enable_sdio_irq_in_chip(struct sdio_func *func, u8 enable); +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); +int libra_sdio_notify_card_removal( + notify_card_removal_t *libra_sdio_notify_card_removal_hdlr); +int libra_sdio_register_shutdown_hdlr( + shutdown_handler_t *libra_shutdown_hdlr); +#endif /* __LIBRA_SDIOIF_H__ */ diff --git a/include/linux/m_adcproc.h b/include/linux/m_adcproc.h new file mode 100644 index 0000000000000000000000000000000000000000..e36a90a335f1c8171ae02689fdf7c82e2e157c7e --- /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/memblock.h b/include/linux/memblock.h index a6bb102351486a15a8e1bce8a2da4fbfea4037c6..36bc2f5b6a19a3947d9ceec59f0a0a26fff7fcbf 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -162,6 +162,7 @@ phys_addr_t memblock_end_of_DRAM(void); void memblock_enforce_memory_limit(phys_addr_t memory_limit); int memblock_is_memory(phys_addr_t addr); int memblock_is_region_memory(phys_addr_t base, phys_addr_t size); +int memblock_overlaps_memory(phys_addr_t base, phys_addr_t size); int memblock_is_reserved(phys_addr_t addr); int memblock_is_region_reserved(phys_addr_t base, phys_addr_t size); diff --git a/include/linux/memory_alloc.h b/include/linux/memory_alloc.h new file mode 100644 index 0000000000000000000000000000000000000000..e7049f8a355ea149923101ad6c7f75997a738ea8 --- /dev/null +++ b/include/linux/memory_alloc.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 _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; + unsigned int id; +}; + +struct alloc { + struct rb_node rb_node; + void *vaddr; + unsigned long paddr; + struct mem_pool *mpool; + unsigned long len; + void *caller; +}; + +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 *caller); + +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 910550f3b70e485a58d07f542b610f15d88e5d44..fc7d1a45dff2957ea8a48c24199d1a24368a18c7 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h @@ -11,6 +11,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 /* @@ -240,4 +243,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 0000000000000000000000000000000000000000..bba647ceb42be465424e70dcb86d359434e89e18 --- /dev/null +++ b/include/linux/mfd/Kbuild @@ -0,0 +1,3 @@ +header-y += timpani-audio.h +header-y += msm-adie-codec.h +header-y += wcd9xxx/ diff --git a/include/linux/mfd/marimba-codec.h b/include/linux/mfd/marimba-codec.h new file mode 100644 index 0000000000000000000000000000000000000000..bfda146936104f9c6da40dedee7ef3016e8d2e07 --- /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 0000000000000000000000000000000000000000..6a05b43b939a378245a4cb658bb513dd65f3e08a --- /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 0000000000000000000000000000000000000000..32fe748a84e8e84cf28e1c4692db7e6aaae948a1 --- /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 0000000000000000000000000000000000000000..651d34a5c05062180d235ae0c43466cd129738c6 --- /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/pm8xxx/batt-alarm.h b/include/linux/mfd/pm8xxx/batt-alarm.h new file mode 100644 index 0000000000000000000000000000000000000000..f10715d17614acad8f6f7c4117d7d62ee8aac102 --- /dev/null +++ b/include/linux/mfd/pm8xxx/batt-alarm.h @@ -0,0 +1,201 @@ +/* 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 +#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/ccadc.h b/include/linux/mfd/pm8xxx/ccadc.h new file mode 100644 index 0000000000000000000000000000000000000000..23d0fb020a5dbd5f549cdc4dc6e068a2dc831b98 --- /dev/null +++ b/include/linux/mfd/pm8xxx/ccadc.h @@ -0,0 +1,100 @@ +/* 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 __PMIC8XXX_CCADC_H__ +#define __PMIC8XXX_CCADC_H__ + +#include + +#define PM8XXX_CCADC_DEV_NAME "pm8xxx-ccadc" + +/** + * struct pm8xxx_ccadc_platform_data - + * @r_sense: sense resistor value in (mOhms) + */ +struct pm8xxx_ccadc_platform_data { + int r_sense; +}; + +#define CCADC_READING_RESOLUTION_N_V1 1085069 +#define CCADC_READING_RESOLUTION_D_V1 100000 +#define CCADC_READING_RESOLUTION_N_V2 542535 +#define CCADC_READING_RESOLUTION_D_V2 100000 + +static s64 pm8xxx_ccadc_reading_to_microvolt_v1(s64 cc) +{ + return div_s64(cc * CCADC_READING_RESOLUTION_N_V1, + CCADC_READING_RESOLUTION_D_V1); +} + +static s64 pm8xxx_ccadc_reading_to_microvolt_v2(s64 cc) +{ + return div_s64(cc * CCADC_READING_RESOLUTION_N_V2, + CCADC_READING_RESOLUTION_D_V2); +} + +static inline s64 pm8xxx_ccadc_reading_to_microvolt(int revision, s64 cc) +{ + /* + * resolution (the value of a single bit) was changed after revision 2.0 + * for more accurate readings + */ + return (revision < PM8XXX_REVISION_8921_2p0) ? + pm8xxx_ccadc_reading_to_microvolt_v1((s64)cc) : + pm8xxx_ccadc_reading_to_microvolt_v2((s64)cc); +} + +#if defined(CONFIG_PM8XXX_CCADC) || defined(CONFIG_PM8XXX_CCADC_MODULE) +/** + * pm8xxx_cc_adjust_for_gain - the function to adjust the voltage read from + * ccadc for gain compensation + * @v: the voltage which needs to be gain compensated in microVolts + * + * + * RETURNS: gain compensated voltage + */ +s64 pm8xxx_cc_adjust_for_gain(s64 uv); + +/** + * pm8xxx_calib_ccadc - calibration for ccadc. This will calculate gain + * and offset and reprogram them in the appropriate + * registers + */ +void pm8xxx_calib_ccadc(void); + +/** + * pm8xxx_ccadc_get_battery_current - return the battery current based on vsense + * resitor in microamperes + * @result: The pointer where the voltage will be updated. A -ve + * result means that the current is flowing in + * the battery - during battery charging + * + * RETURNS: Error code if there was a problem reading vsense, Zero otherwise + * The result won't be updated in case of an error. + * + */ +int pm8xxx_ccadc_get_battery_current(int *bat_current); +#else +static inline s64 pm8xxx_cc_adjust_for_gain(s64 uv) +{ + return -ENXIO; +} +static inline void pm8xxx_calib_ccadc(void) +{ +} +static inline int pm8xxx_ccadc_get_battery_current(int *bat_current) +{ + return -ENXIO; +} +#endif + +#endif /* __PMIC8XXX_CCADC_H__ */ diff --git a/include/linux/mfd/pm8xxx/core.h b/include/linux/mfd/pm8xxx/core.h index bd2f4f64e931db4fc109b73fbece9b64c9fe8fd5..08e9014c7abfea99b6ddd6893977b8d38995be22 100644 --- a/include/linux/mfd/pm8xxx/core.h +++ b/include/linux/mfd/pm8xxx/core.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -20,15 +20,75 @@ #include +enum pm8xxx_version { + PM8XXX_VERSION_8058, + PM8XXX_VERSION_8901, + PM8XXX_VERSION_8921, + PM8XXX_VERSION_8821, + PM8XXX_VERSION_8018, + PM8XXX_VERSION_8922, + PM8XXX_VERSION_8038, + PM8XXX_VERSION_8917, +}; + +/* 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_8901_2p2 5 +#define PM8XXX_REVISION_8901_2p3 6 + +#define PM8XXX_REVISION_8921_TEST 0 +#define PM8XXX_REVISION_8921_1p0 1 +#define PM8XXX_REVISION_8921_1p1 2 +#define PM8XXX_REVISION_8921_2p0 3 +#define PM8XXX_REVISION_8921_3p0 4 +#define PM8XXX_REVISION_8921_3p1 5 + +#define PM8XXX_REVISION_8821_TEST 0 +#define PM8XXX_REVISION_8821_1p0 1 +#define PM8XXX_REVISION_8821_2p0 2 +#define PM8XXX_REVISION_8821_2p1 3 + +#define PM8XXX_REVISION_8018_TEST 0 +#define PM8XXX_REVISION_8018_1p0 1 +#define PM8XXX_REVISION_8018_2p0 2 +#define PM8XXX_REVISION_8018_2p1 3 + +#define PM8XXX_REVISION_8922_TEST 0 +#define PM8XXX_REVISION_8922_1p0 1 +#define PM8XXX_REVISION_8922_1p1 2 +#define PM8XXX_REVISION_8922_2p0 3 + +#define PM8XXX_REVISION_8038_TEST 0 +#define PM8XXX_REVISION_8038_1p0 1 +#define PM8XXX_REVISION_8038_2p0 2 +#define PM8XXX_REVISION_8038_2p1 3 + +#define PM8XXX_REVISION_8917_TEST 0 +#define PM8XXX_REVISION_8917_1p0 1 + 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 +138,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 0000000000000000000000000000000000000000..ccd9c102402976a73eeca7203f07f2698fe15d90 --- /dev/null +++ b/include/linux/mfd/pm8xxx/gpio.h @@ -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. + */ + +/* + * 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 + +/* vin_sel: Voltage Input select on PM8058 */ +#define PM8058_GPIO_VIN_VPH 0 +#define PM8058_GPIO_VIN_BB 1 +#define PM8058_GPIO_VIN_S3 2 +#define PM8058_GPIO_VIN_L3 3 +#define PM8058_GPIO_VIN_L7 4 +#define PM8058_GPIO_VIN_L6 5 +#define PM8058_GPIO_VIN_L5 6 +#define PM8058_GPIO_VIN_L2 7 + +/* vin_sel: Voltage Input Select on PM8038*/ +#define PM8038_GPIO_VIN_VPH 0 +#define PM8038_GPIO_VIN_BB 1 +#define PM8038_GPIO_VIN_L11 2 +#define PM8038_GPIO_VIN_L15 3 +#define PM8038_GPIO_VIN_L4 4 +#define PM8038_GPIO_VIN_L3 5 +#define PM8038_GPIO_VIN_L17 6 + +/* vin_sel: Voltage Input Select on PM8018*/ +#define PM8018_GPIO_VIN_L4 0 +#define PM8018_GPIO_VIN_L14 1 +#define PM8018_GPIO_VIN_S3 2 +#define PM8018_GPIO_VIN_L6 3 +#define PM8018_GPIO_VIN_L2 4 +#define PM8018_GPIO_VIN_L5 5 +#define PM8018_GPIO_VIN_L8 6 +#define PM8018_GPIO_VIN_VPH 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 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/irq.h b/include/linux/mfd/pm8xxx/irq.h index 4b21769f44835bfe652fe6537ee8103741c0b035..78fbed3c969de9010c559e569bfe166b32858de3 100644 --- a/include/linux/mfd/pm8xxx/irq.h +++ b/include/linux/mfd/pm8xxx/irq.h @@ -24,6 +24,7 @@ struct pm8xxx_irq_core_data { u32 rev; int nirqs; + unsigned int base_addr; }; struct pm8xxx_irq_platform_data { @@ -31,27 +32,27 @@ struct pm8xxx_irq_platform_data { struct pm8xxx_irq_core_data irq_cdata; int devirq; int irq_trigger_flag; + int dev_id; }; struct pm_irq_chip; #ifdef CONFIG_MFD_PM8XXX_IRQ int pm8xxx_get_irq_stat(struct pm_irq_chip *chip, int irq); -struct pm_irq_chip * __devinit pm8xxx_irq_init(struct device *dev, +struct pm_irq_chip *pm8xxx_irq_init(struct device *dev, const struct pm8xxx_irq_platform_data *pdata); -int __devexit pm8xxx_irq_exit(struct pm_irq_chip *chip); +int pm8xxx_irq_exit(struct pm_irq_chip *chip); #else static inline int pm8xxx_get_irq_stat(struct pm_irq_chip *chip, int irq) { return -ENXIO; } -static inline struct pm_irq_chip * __devinit pm8xxx_irq_init( - const struct device *dev, +static inline struct pm_irq_chip *pm8xxx_irq_init(const struct device *dev, const struct pm8xxx_irq_platform_data *pdata) { return ERR_PTR(-ENXIO); } -static inline int __devexit pm8xxx_irq_exit(struct pm_irq_chip *chip) +static inline int pm8xxx_irq_exit(struct pm_irq_chip *chip) { return -ENXIO; } diff --git a/include/linux/mfd/pm8xxx/misc.h b/include/linux/mfd/pm8xxx/misc.h new file mode 100644 index 0000000000000000000000000000000000000000..c4b0ea4595d2acc4d82fc314297a5899cd734c7d --- /dev/null +++ b/include/linux/mfd/pm8xxx/misc.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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; +}; + +enum pm8xxx_uart_path_sel { + UART_NONE, + UART_TX1_RX1, + UART_TX2_RX2, + UART_TX3_RX3, +}; + +enum pm8xxx_coincell_chg_voltage { + PM8XXX_COINCELL_VOLTAGE_3p2V = 1, + PM8XXX_COINCELL_VOLTAGE_3p1V, + PM8XXX_COINCELL_VOLTAGE_3p0V, + PM8XXX_COINCELL_VOLTAGE_2p5V = 16 +}; + +enum pm8xxx_coincell_chg_resistor { + PM8XXX_COINCELL_RESISTOR_2100_OHMS, + PM8XXX_COINCELL_RESISTOR_1700_OHMS, + PM8XXX_COINCELL_RESISTOR_1200_OHMS, + PM8XXX_COINCELL_RESISTOR_800_OHMS +}; + +enum pm8xxx_coincell_chg_state { + PM8XXX_COINCELL_CHG_DISABLE, + PM8XXX_COINCELL_CHG_ENABLE +}; + +struct pm8xxx_coincell_chg { + enum pm8xxx_coincell_chg_state state; + enum pm8xxx_coincell_chg_voltage voltage; + enum pm8xxx_coincell_chg_resistor resistor; +}; + +enum pm8xxx_smpl_delay { + PM8XXX_SMPL_DELAY_0p5, + PM8XXX_SMPL_DELAY_1p0, + PM8XXX_SMPL_DELAY_1p5, + PM8XXX_SMPL_DELAY_2p0, +}; + +enum pm8xxx_pon_config { + PM8XXX_DISABLE_HARD_RESET = 0, + PM8XXX_SHUTDOWN_ON_HARD_RESET, + PM8XXX_RESTART_ON_HARD_RESET, +}; + +enum pm8xxx_aux_clk_id { + CLK_MP3_1, + CLK_MP3_2, +}; + +enum pm8xxx_aux_clk_div { + XO_DIV_NONE, + XO_DIV_1, + XO_DIV_2, + XO_DIV_4, + XO_DIV_8, + XO_DIV_16, + XO_DIV_32, + XO_DIV_64, +}; + +enum pm8xxx_hsed_bias { + PM8XXX_HSED_BIAS0, + PM8XXX_HSED_BIAS1, + PM8XXX_HSED_BIAS2, +}; + +#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); + +int pm8xxx_uart_gpio_mux_ctrl(enum pm8xxx_uart_path_sel uart_path_sel); + +/** + * pm8xxx_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 pm8xxx_coincell_chg_config(struct pm8xxx_coincell_chg *chg_config); + +/** + * pm8xxx_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 pm8xxx_smpl_control(int enable); + +/** + * pm8xxx_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 pm8xxx_smpl_set_delay(enum pm8xxx_smpl_delay delay); + +/** + * pm8xxx_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 pm8xxx_watchdog_reset_control(int enable); + +/** + * pm8xxx_hard_reset_config - Allows different reset configurations + * + * 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 + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_hard_reset_config(enum pm8xxx_pon_config config); + +/** + * pm8xxx_stay_on - enables stay_on feature + * + * PMIC stay-on feature allows PMIC to ignore MSM PS_HOLD=low + * signal so that some special functions like debugging could be + * performed. + * + * This feature should not be used in any product release. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_stay_on(void); + +/** + * pm8xxx_preload_dVdd - preload the dVdd regulator during off state. + * + * This can help to reduce fluctuations in the dVdd voltage during startup + * at the cost of additional off state current draw. + * + * This API should only be called if dVdd startup issues are suspected. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_preload_dVdd(void); + +/** + * pm8xxx_usb_id_pullup - Control a pullup for USB ID + * + * @enable: enable (1) or disable (0) the pullup + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_usb_id_pullup(int enable); + +/** + * pm8xxx_aux_clk_control - Control an auxiliary clock + * @clk_id: ID of clock to be programmed, registers of XO_CNTRL2 + * @divider: divisor to use when configuring desired clock + * @enable: enable (1) the designated clock with the supplied division, + * or disable (0) the designated clock + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_aux_clk_control(enum pm8xxx_aux_clk_id clk_id, + enum pm8xxx_aux_clk_div divider, + bool enable); + +/** + * pm8xxx_hsed_bias_control - Control the HSED_BIAS signal + * @bias: the bias line to be controlled (of the 3) + * @enable: enable/disable the bias line + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_hsed_bias_control(enum pm8xxx_hsed_bias bias, bool enable); +#else + +static inline int pm8xxx_reset_pwr_off(int reset) +{ + return -ENODEV; +} +static inline int +pm8xxx_uart_gpio_mux_ctrl(enum pm8xxx_uart_path_sel uart_path_sel) +{ + return -ENODEV; +} +static inline int +pm8xxx_coincell_chg_config(struct pm8xxx_coincell_chg *chg_config) +{ + return -ENODEV; +} +static inline int pm8xxx_smpl_set_delay(enum pm8xxx_smpl_delay delay) +{ + return -ENODEV; +} +static inline int pm8xxx_smpl_control(int enable) +{ + return -ENODEV; +} +static inline int pm8xxx_watchdog_reset_control(int enable) +{ + return -ENODEV; +} +static inline int pm8xxx_hard_reset_config(enum pm8xxx_pon_config config) +{ + return -ENODEV; +} +static inline int pm8xxx_stay_on(void) +{ + return -ENODEV; +} +static inline int pm8xxx_preload_dVdd(void) +{ + return -ENODEV; +} +static inline int pm8xxx_usb_id_pullup(int enable) +{ + return -ENODEV; +} +static inline int pm8xxx_aux_clk_control(enum pm8xxx_aux_clk_id clk_id, + enum pm8xxx_aux_clk_div divider, bool enable) +{ + return -ENODEV; +} +static inline int pm8xxx_hsed_bias_control(enum pm8xxx_hsed_bias bias, + bool enable) +{ + 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 0000000000000000000000000000000000000000..802948b17b8ff074ed94da58bb0b92ead53b6127 --- /dev/null +++ b/include/linux/mfd/pm8xxx/mpp.h @@ -0,0 +1,263 @@ +/* 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/Output: level [PM8821] */ +#define PM8821_MPP_DIG_LEVEL_1P8 1 +#define PM8821_MPP_DIG_LEVEL_VPH 7 + +/* Digital Input/Output: level [PM8018] */ +#define PM8018_MPP_DIG_LEVEL_L4 0 +#define PM8018_MPP_DIG_LEVEL_L14 1 +#define PM8018_MPP_DIG_LEVEL_S3 2 +#define PM8018_MPP_DIG_LEVEL_L6 3 +#define PM8018_MPP_DIG_LEVEL_L2 4 +#define PM8018_MPP_DIG_LEVEL_L5 5 +#define PM8018_MPP_DIG_LEVEL_VPH 7 + +/* Digital Input/Output: level [PM8038] */ +#define PM8038_MPP_DIG_LEVEL_L20 0 +#define PM8038_MPP_DIG_LEVEL_L11 1 +#define PM8038_MPP_DIG_LEVEL_L5 2 +#define PM8038_MPP_DIG_LEVEL_L15 3 +#define PM8038_MPP_DIG_LEVEL_L17 4 +#define PM8038_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/nfc.h b/include/linux/mfd/pm8xxx/nfc.h new file mode 100644 index 0000000000000000000000000000000000000000..e58e0a90be4fdacbbb1d1d76056f1aff8f15c293 --- /dev/null +++ b/include/linux/mfd/pm8xxx/nfc.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 __PM8XXX_NFC_H__ +#define __PM8XXX_NFC_H__ + +struct pm8xxx_nfc_device; + +#define PM8XXX_NFC_DEV_NAME "pm8xxx-nfc" + +/* 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) + +/* + * pm8xxx_nfc_request - request a handle to access NFC device + */ +struct pm8xxx_nfc_device *pm8xxx_nfc_request(void); + +/* + * pm8xxx_nfc_config - configure NFC signals + * + * @nfcdev: the NFC device + * @mask: signal mask to configure + * @flags: control flags + */ +int pm8xxx_nfc_config(struct pm8xxx_nfc_device *nfcdev, u32 mask, u32 flags); + +/* + * pm8xxx_nfc_get_status - get NFC status + * + * @nfcdev: the NFC device + * @mask: of status mask to read + * @status: pointer to the status variable + */ +int pm8xxx_nfc_get_status(struct pm8xxx_nfc_device *nfcdev, + u32 mask, u32 *status); + +/* + * pm8xxx_nfc_free - free the NFC device + */ +void pm8xxx_nfc_free(struct pm8xxx_nfc_device *nfcdev); + +#endif /* __PM8XXX_NFC_H__ */ diff --git a/include/linux/mfd/pm8xxx/pm8018.h b/include/linux/mfd/pm8xxx/pm8018.h new file mode 100644 index 0000000000000000000000000000000000000000..daacdd407c7ac10eff3abc5cb0a621ba01c78f3e --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8018.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 8018 driver header file + * + */ + +#ifndef __MFD_PM8018_H +#define __MFD_PM8018_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PM8018_CORE_DEV_NAME "pm8018-core" + +#define PM8018_NR_IRQS 256 + +#define PM8018_NR_GPIOS 6 + +#define PM8018_NR_MPPS 6 + +#define PM8018_GPIO_BLOCK_START 24 +#define PM8018_MPP_BLOCK_START 16 +#define PM8018_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +/* GPIOs and MPPs [1,N] */ +#define PM8018_GPIO_IRQ(base, gpio) ((base) + \ + PM8018_IRQ_BLOCK_BIT(PM8018_GPIO_BLOCK_START, (gpio)-1)) +#define PM8018_MPP_IRQ(base, mpp) ((base) + \ + PM8018_IRQ_BLOCK_BIT(PM8018_MPP_BLOCK_START, (mpp)-1)) + +/* PMIC Interrupts */ +#define PM8018_RTC_ALARM_IRQ PM8018_IRQ_BLOCK_BIT(4, 7) + +#define PM8018_PWRKEY_REL_IRQ PM8018_IRQ_BLOCK_BIT(6, 2) +#define PM8018_PWRKEY_PRESS_IRQ PM8018_IRQ_BLOCK_BIT(6, 3) +#define PM8018_ADC_EOC_USR_IRQ PM8018_IRQ_BLOCK_BIT(9, 6) +#define PM8018_ADC_BATT_TEMP_WARM_IRQ PM8018_IRQ_BLOCK_BIT(9, 1) +#define PM8018_ADC_BATT_TEMP_COLD_IRQ PM8018_IRQ_BLOCK_BIT(9, 0) + +#define PM8018_OVERTEMP_IRQ PM8018_IRQ_BLOCK_BIT(4, 2) +#define PM8018_TEMPSTAT_IRQ PM8018_IRQ_BLOCK_BIT(6, 7) + +#define PM8018_LVS1_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 0) + +struct pm8018_platform_data { + 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_misc_platform_data *misc_pdata; + struct pm8xxx_regulator_platform_data *regulator_pdatas; + struct pm8xxx_adc_platform_data *adc_pdata; + int num_regulators; + struct pm8xxx_led_platform_data *leds_pdata; +}; + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8038.h b/include/linux/mfd/pm8xxx/pm8038.h new file mode 100644 index 0000000000000000000000000000000000000000..90557b97fa762b23e09713ee36ef2e7a53eff19e --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8038.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 8038 driver header file + * + */ + +#ifndef __MFD_PM8038_H +#define __MFD_PM8038_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PM8038_CORE_DEV_NAME "pm8038-core" + +#define PM8038_NR_IRQS 256 +#define PM8038_NR_GPIOS 12 +#define PM8038_NR_MPPS 6 + +#define PM8038_GPIO_BLOCK_START 24 +#define PM8038_MPP_BLOCK_START 16 + +#define PM8038_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +/* GPIO and MPPs [1,N] */ +#define PM8038_GPIO_IRQ(base, gpio) ((base) + \ + PM8038_IRQ_BLOCK_BIT(PM8038_GPIO_BLOCK_START, (gpio)-1)) +#define PM8038_MPP_IRQ(base, mpp) ((base) + \ + PM8038_IRQ_BLOCK_BIT(PM8038_MPP_BLOCK_START, (mpp)-1)) + +/* PMIC Interrupts */ +#define PM8038_RTC_ALARM_IRQ PM8038_IRQ_BLOCK_BIT(4, 7) +#define PM8038_PWRKEY_REL_IRQ PM8038_IRQ_BLOCK_BIT(6, 2) +#define PM8038_PWRKEY_PRESS_IRQ PM8038_IRQ_BLOCK_BIT(6, 3) +#define PM8038_KEYPAD_IRQ PM8038_IRQ_BLOCK_BIT(9, 2) +#define PM8038_KEYSTUCK_IRQ PM8038_IRQ_BLOCK_BIT(9, 3) +#define PM8038_ADC_EOC_USR_IRQ PM8038_IRQ_BLOCK_BIT(9, 6) +#define PM8038_ADC_BATT_TEMP_WARM_IRQ PM8038_IRQ_BLOCK_BIT(9, 1) +#define PM8038_ADC_BATT_TEMP_COLD_IRQ PM8038_IRQ_BLOCK_BIT(9, 0) +#define PM8038_USB_ID_IN_IRQ(base) (base + PM8921_IRQ_BLOCK_BIT(6, 1)) + +#define PM8038_RESOUT_IRQ PM8038_IRQ_BLOCK_BIT(6, 4) + +struct pm8038_platform_data { + int irq_base; + struct pm8xxx_gpio_platform_data *gpio_pdata; + struct pm8xxx_irq_platform_data *irq_pdata; + struct pm8xxx_mpp_platform_data *mpp_pdata; + struct pm8xxx_rtc_platform_data *rtc_pdata; + struct pm8xxx_pwrkey_platform_data *pwrkey_pdata; + struct pm8xxx_misc_platform_data *misc_pdata; + struct pm8xxx_regulator_platform_data *regulator_pdatas; + int num_regulators; + struct pm8921_charger_platform_data *charger_pdata; + struct pm8921_bms_platform_data *bms_pdata; + struct pm8xxx_adc_platform_data *adc_pdata; + struct pm8xxx_led_platform_data *leds_pdata; + struct pm8xxx_ccadc_platform_data *ccadc_pdata; + struct pm8xxx_spk_platform_data *spk_pdata; +}; + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8821.h b/include/linux/mfd/pm8xxx/pm8821.h new file mode 100644 index 0000000000000000000000000000000000000000..850e8c12dd218dd630158155e5f77b95dfa4ec0e --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8821.h @@ -0,0 +1,43 @@ +/* + * 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 8821 driver header file + * + */ + +#ifndef __MFD_PM8821_H +#define __MFD_PM8821_H + +#include +#include +#include + +#define PM8821_NR_IRQS (64) +#define PM8821_NR_MPPS (4) + +#define PM8821_MPP_BLOCK_START (16) +#define PM8821_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +/* MPPs [1,N] */ +#define PM8821_MPP_IRQ(base, mpp) ((base) + \ + PM8821_IRQ_BLOCK_BIT(PM8821_MPP_BLOCK_START, (mpp)-1)) + +/* PMIC Interrupts */ + +struct pm8821_platform_data { + int irq_base; + struct pm8xxx_irq_platform_data *irq_pdata; + struct pm8xxx_mpp_platform_data *mpp_pdata; +}; + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8921-bms.h b/include/linux/mfd/pm8xxx/pm8921-bms.h new file mode 100644 index 0000000000000000000000000000000000000000..537e0b5d9463947d185cbb1d85e59c195190a2e7 --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8921-bms.h @@ -0,0 +1,242 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 29 +#define PC_CC_COLS 13 + +#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 sf_lut - + * @rows: number of percent charge entries should be <= PC_CC_ROWS + * @cols: number of charge cycle entries should be <= PC_CC_COLS + * @row_entries: the charge cycles/temperature 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 sf_lut { + int rows; + int cols; + int row_entries[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 + * @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 + * @rbatt_sf_lut: table to get battery resistance scaling factor given + * temperature and percent charge + * @default_rbatt_mohm: the default value of battery resistance to use when + * readings from bms are not available. + * @delta_rbatt_mohm: the resistance to be added towards lower soc to + * compensate for battery capacitance. + */ +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 sf_lut *pc_sf_lut; + struct sf_lut *rbatt_sf_lut; + int default_rbatt_mohm; + int delta_rbatt_mohm; +}; + +struct pm8xxx_bms_core_data { + unsigned int batt_temp_channel; + unsigned int vbat_channel; + unsigned int ref625mv_channel; + unsigned int ref1p25v_channel; + unsigned int batt_id_channel; +}; + +enum battery_type { + BATT_UNKNOWN = 0, + BATT_PALLADIUM, + BATT_DESAY, +}; + +/** + * struct pm8921_bms_platform_data - + * @batt_type: allows to force chose battery calibration 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 + * @enable_fcc_learning: if set the driver will learn full charge + * capacity of the battery upon end of charge + */ +struct pm8921_bms_platform_data { + struct pm8xxx_bms_core_data bms_cdata; + enum battery_type battery_type; + unsigned int r_sense; + unsigned int i_test; + unsigned int v_failure; + unsigned int calib_delay_ms; + unsigned int max_voltage_uv; + unsigned int rconn_mohm; + int enable_fcc_learning; +}; + +#if defined(CONFIG_PM8921_BMS) || defined(CONFIG_PM8921_BMS_MODULE) +extern struct pm8921_bms_battery_data palladium_1500_data; +extern struct pm8921_bms_battery_data desay_5200_data; +/** + * pm8921_bms_get_vsense_avg - return the voltage across the sense + * resitor in microvolts + * @result: The pointer where the voltage will be updated. A -ve + * result means that the current is flowing in + * the battery - during battery charging + * + * RETURNS: Error code if there was a problem reading vsense, Zero otherwise + * The result won't be updated in case of an error. + * + * + */ +int pm8921_bms_get_vsense_avg(int *result); + +/** + * pm8921_bms_get_battery_current - return the battery current based on vsense + * resitor in microamperes + * @result: The pointer where the voltage will be updated. A -ve + * result means that the current is flowing in + * the battery - during battery charging + * + * RETURNS: Error code if there was a problem reading vsense, Zero otherwise + * The result won't be updated in case of an error. + * + */ +int pm8921_bms_get_battery_current(int *result); + +/** + * pm8921_bms_get_percent_charge - returns the current battery charge in percent + * + */ +int pm8921_bms_get_percent_charge(void); + +/** + * pm8921_bms_get_fcc - returns fcc in mAh of the battery depending on its age + * and temperature + * + */ +int pm8921_bms_get_fcc(void); + +/** + * pm8921_bms_charging_began - function to notify the bms driver that charging + * has started. Used by the bms driver to keep + * track of chargecycles + */ +void pm8921_bms_charging_began(void); +/** + * pm8921_bms_charging_end - function to notify the bms driver that charging + * has stopped. Used by the bms driver to keep + * track of chargecycles + */ +void pm8921_bms_charging_end(int is_battery_full); + +void pm8921_bms_calibrate_hkadc(void); +/** + * pm8921_bms_get_simultaneous_battery_voltage_and_current + * - function to take simultaneous vbat and vsense readings + * this puts the bms in override mode but keeps coulumb couting + * on. Useful when ir compensation needs to be implemented + */ +int pm8921_bms_get_simultaneous_battery_voltage_and_current(int *ibat_ua, + int *vbat_uv); +/** + * pm8921_bms_get_rbatt - function to get the battery resistance in mOhm. + */ +int pm8921_bms_get_rbatt(void); +#else +static inline int pm8921_bms_get_vsense_avg(int *result) +{ + return -ENXIO; +} +static inline int pm8921_bms_get_battery_current(int *result) +{ + return -ENXIO; +} +static inline int pm8921_bms_get_percent_charge(void) +{ + return -ENXIO; +} +static inline int pm8921_bms_get_fcc(void) +{ + return -ENXIO; +} +static inline void pm8921_bms_charging_began(void) +{ +} +static inline void pm8921_bms_charging_end(int is_battery_full) +{ +} +static inline void pm8921_bms_calibrate_hkadc(void) +{ +} +static inline int pm8921_bms_get_simultaneous_battery_voltage_and_current( + int *ibat_ua, int *vbat_uv) +{ + return -ENXIO; +} +static inline int pm8921_bms_get_rbatt(void) +{ + return -EINVAL; +} +#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 0000000000000000000000000000000000000000..21869036810ff5adfeac370cc2605e7b71e11f39 --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8921-charger.h @@ -0,0 +1,347 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include + +#define PM8921_CHARGER_DEV_NAME "pm8921-charger" + +struct pm8xxx_charger_core_data { + unsigned int vbat_channel; + unsigned int batt_temp_channel; + unsigned int batt_id_channel; +}; + +enum pm8921_chg_cold_thr { + PM_SMBC_BATT_TEMP_COLD_THR__LOW, + PM_SMBC_BATT_TEMP_COLD_THR__HIGH +}; + +enum pm8921_chg_hot_thr { + PM_SMBC_BATT_TEMP_HOT_THR__LOW, + PM_SMBC_BATT_TEMP_HOT_THR__HIGH +}; + +enum pm8921_usb_ov_threshold { + PM_USB_OV_5P5V, + PM_USB_OV_6V, + PM_USB_OV_6P5V, + PM_USB_OV_7V, +}; + +enum pm8921_usb_debounce_time { + PM_USB_BYPASS_DEBOUNCER, + PM_USB_DEBOUNCE_20P5MS, + PM_USB_DEBOUNCE_40P5MS, + PM_USB_DEBOUNCE_80P5MS, +}; + +enum pm8921_chg_led_src_config { + LED_SRC_GND, + LED_SRC_VPH_PWR, + LED_SRC_5V, + LED_SRC_MIN_VPH_5V, + LED_SRC_BYPASS, +}; + +/** + * struct pm8921_charger_platform_data - + * @safety_time: max charging time in minutes incl. fast and trkl + * valid range 4 to 512 min. PON default 120 min + * @ttrkl_time: max trckl charging time in minutes + * valid range 1 to 64 mins. PON default 15 min + * @update_time: how often the userland be updated of the charging (msec) + * @max_voltage: the max voltage (mV) the battery should be charged up to + * @min_voltage: the voltage (mV) where charging method switches from + * trickle to fast. This is also the minimum voltage the + * system operates at + * @resume_voltage_delta: the (mV) drop to wait for before resume charging + * after the battery has been fully charged + * @term_current: the charger current (mA) at which EOC happens + * @cool_temp: the temperature (degC) at which the battery is + * considered cool charging current and voltage is reduced. + * Use INT_MIN to indicate not valid. + * @warm_temp: the temperature (degC) at which the battery is + * considered warm charging current and voltage is reduced + * Use INT_MIN to indicate not valid. + * @temp_check_period: The polling interval in seconds to check battery + * temeperature if it has gone to cool or warm temperature + * area + * @max_bat_chg_current: Max charge current of the battery in mA + * Usually 70% of full charge capacity + * @cool_bat_chg_current: chg current (mA) when the battery is cool + * @warm_bat_chg_current: chg current (mA) when the battery is warm + * @cool_bat_voltage: chg voltage (mV) when the battery is cool + * @warm_bat_voltage: chg voltage (mV) when the battery is warm + * @get_batt_capacity_percent: + * a board specific function to return battery + * capacity. If null - a default one will be used + * @trkl_voltage: the trkl voltage in (mV) below which hw controlled + * trkl charging happens with linear charger + * @weak_voltage: the weak voltage (mV) below which hw controlled + * trkl charging happens with switching mode charger + * @trkl_current: the trkl current in (mA) to use for trkl charging phase + * @weak_current: the weak current in (mA) to use for weak charging phase + * @vin_min: the input voltage regulation point (mV) - if the + * voltage falls below this, the charger reduces charge + * current or stop charging temporarily + * @thermal_mitigation: the array of charge currents to use as temperature + * increases + * @thermal_levels: the number of thermal mitigation levels supported + * @cold_thr: if high battery will be cold when VBAT_THERM goes above + * 80% of VREF_THERM (typically 1.8volts), if low the + * battery will be considered cold if VBAT_THERM goes above + * 70% of VREF_THERM. Hardware defaults to low. + * @hot_thr: if high the battery will be considered hot when the + * VBAT_THERM goes below 35% of VREF_THERM, if low the + * battery will be considered hot when VBAT_THERM goes + * below 25% of VREF_THERM. Hardware defaults to low. + * @rconn_mohm: resistance in milliOhm from the vbat sense to ground + * with the battery terminals shorted. This indicates + * resistance of the pads, connectors, battery terminals + * and rsense. + * @led_src_config: Power source for anode of charger indicator LED. + */ +struct pm8921_charger_platform_data { + struct pm8xxx_charger_core_data charger_cdata; + unsigned int safety_time; + unsigned int ttrkl_time; + unsigned int update_time; + unsigned int max_voltage; + unsigned int min_voltage; + unsigned int resume_voltage_delta; + unsigned int term_current; + int cool_temp; + int warm_temp; + unsigned int temp_check_period; + unsigned int max_bat_chg_current; + unsigned int cool_bat_chg_current; + unsigned int warm_bat_chg_current; + unsigned int cool_bat_voltage; + unsigned int warm_bat_voltage; + unsigned int (*get_batt_capacity_percent) (void); + int64_t batt_id_min; + int64_t batt_id_max; + bool keep_btm_on_suspend; + int trkl_voltage; + int weak_voltage; + int trkl_current; + int weak_current; + int vin_min; + int *thermal_mitigation; + int thermal_levels; + enum pm8921_chg_cold_thr cold_thr; + enum pm8921_chg_hot_thr hot_thr; + int rconn_mohm; + enum pm8921_chg_led_src_config led_src_config; +}; + +enum pm8921_charger_source { + PM8921_CHG_SRC_NONE, + PM8921_CHG_SRC_USB, + PM8921_CHG_SRC_DC, +}; + +#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)); +/** + * pm8921_charger_enable - + * + * @enable: 1 means enable charging, 0 means disable + * + * Enable/Disable battery charging current, the device will still draw current + * from the charging source + */ +int pm8921_charger_enable(bool enable); + +/** + * pm8921_is_usb_chg_plugged_in - is usb plugged in + * + * if usb is under voltage or over voltage this will return false + */ +int pm8921_is_usb_chg_plugged_in(void); + +/** + * pm8921_is_dc_chg_plugged_in - is dc plugged in + * + * if dc is under voltage or over voltage this will return false + */ +int pm8921_is_dc_chg_plugged_in(void); + +/** + * pm8921_is_battery_present - + * + * returns if the pmic sees the battery present + */ +int pm8921_is_battery_present(void); + +/** + * pm8921_set_max_battery_charge_current - set max battery chg current + * + * @ma: max charge current in milliAmperes + */ +int pm8921_set_max_battery_charge_current(int ma); + +/** + * pm8921_disable_input_current_limt - disable input current limit + * + * @disable: disable input curren_limit limit + * + * Disabling the charge current limit causes current + * current limits to have no monitoring. An adequate charger + * capable of supplying high current while sustaining VIN_MIN + * is required if input current limiting is disabled. + */ +int pm8921_disable_input_current_limit(bool disable); + +/** + * pm8921_set_usb_power_supply_type - set USB supply type + * + * @type: power_supply_type enum + * + * This api lets one set a specific usb power_supply_type. + * USB drivers can distinguish between types of USB connections + * and set the appropriate type for the USB supply. + */ + +int pm8921_set_usb_power_supply_type(enum power_supply_type type); + +/** + * pm8921_disable_source_current - disable drawing current from source + * @disable: true to disable current drawing from source false otherwise + * + * This function will stop all charging activities and disable any current + * drawn from the charger. The battery provides the system current. + */ +int pm8921_disable_source_current(bool disable); + +/** + * pm8921_regulate_input_voltage - + * @voltage: voltage in millivolts to regulate + * allowable values are from 4300mV to 6500mV + */ +int pm8921_regulate_input_voltage(int voltage); +/** + * pm8921_is_battery_charging - + * @source: when the battery is charging the source is updated to reflect which + * charger, usb or dc, is charging the battery. + * + * RETURNS: bool, whether the battery is being charged or not + */ +bool pm8921_is_battery_charging(int *source); + +/** + * pm8921_batt_temperature - get battery temp in degC + * + */ +int pm8921_batt_temperature(void); +/** + * pm8921_usb_ovp_set_threshold - + * Set the usb threshold as defined in by + * enum usb_ov_threshold + */ +int pm8921_usb_ovp_set_threshold(enum pm8921_usb_ov_threshold ov); + +/** + * pm8921_usb_ovp_set_hystersis - + * @ms: the debounce time enum + * + * Sets the debounce time for usb insertion/removal detection + * + */ +int pm8921_usb_ovp_set_hystersis(enum pm8921_usb_debounce_time ms); + +/** + * pm8921_usb_ovp_disable - + * + * when disabled there is no over voltage protection. The usb voltage is + * fed to the pmic as is. This should be disabled only when there is + * over voltage protection circuitry present outside the pmic chip. + * + */ +int pm8921_usb_ovp_disable(int disable); +#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)) +{ +} +static inline int pm8921_charger_enable(bool enable) +{ + return -ENXIO; +} +static inline int pm8921_is_usb_chg_plugged_in(void) +{ + return -ENXIO; +} +static inline int pm8921_is_dc_chg_plugged_in(void) +{ + return -ENXIO; +} +static inline int pm8921_is_battery_present(void) +{ + return -ENXIO; +} +static inline int pm8921_disable_input_current_limit(bool disable) +{ + return -ENXIO; +} +static inline int pm8921_set_usb_power_supply_type(enum power_supply_type type) +{ + return -ENXIO; +} +static inline int pm8921_set_max_battery_charge_current(int ma) +{ + return -ENXIO; +} +static inline int pm8921_disable_source_current(bool disable) +{ + return -ENXIO; +} +static inline int pm8921_regulate_input_voltage(int voltage) +{ + return -ENXIO; +} +static inline bool pm8921_is_battery_charging(int *source) +{ + *source = PM8921_CHG_SRC_NONE; + return 0; +} +static inline int pm8921_batt_temperature(void) +{ + return -ENXIO; +} +static inline int pm8921_usb_ovp_set_threshold(enum pm8921_usb_ov_threshold ov) +{ + return -ENXIO; +} +static inline int pm8921_usb_ovp_set_hystersis(enum pm8921_usb_debounce_time ms) +{ + return -ENXIO; +} +static inline int pm8921_usb_ovp_disable(int disable) +{ + return -ENXIO; +} +#endif + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8921.h b/include/linux/mfd/pm8xxx/pm8921.h index 00fa3de7659dd3e1ea09472cb396bcb3d1ae2c45..92bb94b593ff9d4ef86f38b2da4358ec0c7633f2 100644 --- a/include/linux/mfd/pm8xxx/pm8921.h +++ b/include/linux/mfd/pm8xxx/pm8921.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -19,12 +19,129 @@ #define __MFD_PM8921_H #include +#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 PM8917_NR_GPIOS 38 + +#define PM8921_NR_MPPS 12 +#define PM8917_NR_MPPS 10 + +#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) +#define PM8921_RESOUT_IRQ PM8921_IRQ_BLOCK_BIT(6, 4) + +#define PM8921_USB_OTG_OCP_IRQ PM8921_IRQ_BLOCK_BIT(6, 0) +#define PM8921_LVS7_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 7) +#define PM8921_LVS6_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 6) +#define PM8921_LVS5_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 5) +#define PM8921_LVS4_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 4) +#define PM8921_LVS3_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 3) +#define PM8921_LVS2_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 2) +#define PM8921_LVS1_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 1) +#define PM8921_HDMI_MVS_OCP_IRQ PM8921_IRQ_BLOCK_BIT(13, 0) + +/* 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 pm8xxx_regulator_platform_data *regulator_pdatas; + int num_regulators; + struct pm8xxx_adc_platform_data *adc_pdata; + struct pm8xxx_led_platform_data *leds_pdata; + struct pm8xxx_vibrator_platform_data *vibrator_pdata; + struct pm8xxx_ccadc_platform_data *ccadc_pdata; + struct pm8xxx_pwm_platform_data *pwm_pdata; }; #endif diff --git a/include/linux/mfd/pm8xxx/pm8xxx-adc.h b/include/linux/mfd/pm8xxx/pm8xxx-adc.h new file mode 100644 index 0000000000000000000000000000000000000000..84f8e03899ea8eaad6f18712966cb357c298fd9c --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8xxx-adc.h @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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/8018 ADC driver header file + * + */ + +#ifndef __PM8XXX_ADC_H +#define __PM8XXX_ADC_H + +#include +#include + +/** + * enum pm8xxx_adc_channels - PM8XXX 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 pm8xxx_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, + /* PM8018 ADC Arbiter uses a single channel on AMUX8 + * to read either Batt_id or Batt_therm. + */ + CHANNEL_BATT_ID_THERM = CHANNEL_BATT_THERM, + CHANNEL_BATT_ID, + CHANNEL_USBIN, + CHANNEL_DIE_TEMP, + CHANNEL_625MV, + CHANNEL_125V, + CHANNEL_CHG_TEMP, + CHANNEL_MUXOFF, + CHANNEL_NONE, + ADC_MPP_1_ATEST_8 = 20, + ADC_MPP_1_USB_SNS_DIV20, + ADC_MPP_1_DCIN_SNS_DIV20, + ADC_MPP_1_AMUX3, + ADC_MPP_1_AMUX4, + ADC_MPP_1_AMUX5, + ADC_MPP_1_AMUX6, + ADC_MPP_1_AMUX7, + ADC_MPP_1_AMUX8, + ADC_MPP_1_ATEST_1, + ADC_MPP_1_ATEST_2, + ADC_MPP_1_ATEST_3, + ADC_MPP_1_ATEST_4, + ADC_MPP_1_ATEST_5, + ADC_MPP_1_ATEST_6, + ADC_MPP_1_ATEST_7, + ADC_MPP_2_ATEST_8 = 40, + ADC_MPP_2_USB_SNS_DIV20, + ADC_MPP_2_DCIN_SNS_DIV20, + ADC_MPP_2_AMUX3, + ADC_MPP_2_AMUX4, + ADC_MPP_2_AMUX5, + ADC_MPP_2_AMUX6, + ADC_MPP_2_AMUX7, + ADC_MPP_2_AMUX8, + ADC_MPP_2_ATEST_1, + ADC_MPP_2_ATEST_2, + ADC_MPP_2_ATEST_3, + ADC_MPP_2_ATEST_4, + ADC_MPP_2_ATEST_5, + ADC_MPP_2_ATEST_6, + ADC_MPP_2_ATEST_7, + ADC_CHANNEL_MAX_NUM, +}; + +#define PM8XXX_ADC_PMIC_0 0x0 + +#define PM8XXX_CHANNEL_ADC_625_UV 625000 +#define PM8XXX_CHANNEL_MPP_SCALE1_IDX 20 +#define PM8XXX_CHANNEL_MPP_SCALE3_IDX 40 + +#define PM8XXX_AMUX_MPP_3 0x3 +#define PM8XXX_AMUX_MPP_4 0x4 +#define PM8XXX_AMUX_MPP_5 0x5 +#define PM8XXX_AMUX_MPP_6 0x6 +#define PM8XXX_AMUX_MPP_7 0x7 +#define PM8XXX_AMUX_MPP_8 0x8 + +#define PM8XXX_ADC_DEV_NAME "pm8xxx-adc" + +/** + * enum pm8xxx_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 PM8XXX ADC arbiter. + */ +enum pm8xxx_adc_decimation_type { + ADC_DECIMATION_TYPE1 = 0, + ADC_DECIMATION_TYPE2, + ADC_DECIMATION_TYPE3, + ADC_DECIMATION_TYPE4, + ADC_DECIMATION_NONE, +}; + +/** + * enum pm8xxx_adc_calib_type - PM8XXX 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 PM8XXX ADC. + */ +enum pm8xxx_adc_calib_type { + ADC_CALIB_ABSOLUTE = 0, + ADC_CALIB_RATIOMETRIC, + ADC_CALIB_NONE, +}; + +/** + * enum pm8xxx_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 pm8xxx_adc_channel_scaling_param { + CHAN_PATH_SCALING1 = 0, + CHAN_PATH_SCALING2, + CHAN_PATH_SCALING3, + CHAN_PATH_SCALING4, + CHAN_PATH_SCALING_NONE, +}; + +/** + * enum pm8xxx_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 pm8xxx_adc_amux_input_rsv { + AMUX_RSV0 = 0, + AMUX_RSV1, + AMUX_RSV2, + AMUX_RSV3, + AMUX_RSV4, + AMUX_RSV5, + AMUX_NONE, +}; + +/** + * enum pm8xxx_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 pm8xxx_adc_premux_mpp_scale_type { + PREMUX_MPP_SCALE_0 = 0, + PREMUX_MPP_SCALE_1, + PREMUX_MPP_SCALE_1_DIV3, + PREMUX_MPP_NONE, +}; + +/** + * enum pm8xxx_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_XOTHERM: Returns XO thermistor voltage in degree's Centigrade + * %ADC_SCALE_NONE: Do not use this scaling type + */ +enum pm8xxx_adc_scale_fn_type { + ADC_SCALE_DEFAULT = 0, + ADC_SCALE_BATT_THERM, + ADC_SCALE_PA_THERM, + ADC_SCALE_PMIC_THERM, + ADC_SCALE_XOTHERM, + ADC_SCALE_NONE, +}; + +/** + * struct pm8xxx_adc_linear_graph - Represent ADC characteristics + * @dy: Numerator slope to calculate the gain + * @dx: Denominator slope to calculate the gain + * @adc_vref: A/D word of the voltage reference used for the channel + * @adc_gnd: A/D word of the ground reference used for the channel + * + * Each ADC device has different offset and gain parameters which are computed + * to calibrate the device. + */ +struct pm8xxx_adc_linear_graph { + int64_t dy; + int64_t dx; + int64_t adc_vref; + int64_t adc_gnd; +}; + +/** + * struct pm8xxx_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 pm8xxx_adc_map_pt { + int32_t x; + int32_t y; +}; + +/** + * struct pm8xxx_adc_scaling_ratio - Represent scaling ratio for adc input + * @num: Numerator scaling parameter + * @den: Denominator scaling parameter + */ +struct pm8xxx_adc_scaling_ratio { + int32_t num; + int32_t den; +}; + +/** + * struct pm8xxx_adc_properties - Represent the ADC properties + * @adc_reference: Reference voltage for PM8XXX ADC + * @bitresolution: ADC bit resolution for PM8XXX ADC + * @biploar: Polarity for PM8XXX ADC + */ +struct pm8xxx_adc_properties { + uint32_t adc_vdd_reference; + uint32_t bitresolution; + bool bipolar; +}; + +/** + * struct pm8xxx_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 pm8xxx_adc_linear_graph + */ +struct pm8xxx_adc_chan_properties { + uint32_t offset_gain_numerator; + uint32_t offset_gain_denominator; + struct pm8xxx_adc_linear_graph adc_graph[2]; +}; + +/** + * struct pm8xxx_adc_chan_result - Represent the result of the PM8XXX 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. + * All voltage units are represented in micro - volts. + * -Battery temperature units are represented as 0.1 DegC + * -PA Therm temperature units are represented as DegC + * -PMIC Die temperature units are represented as 0.001 DegC + */ +struct pm8xxx_adc_chan_result { + uint32_t chan; + int32_t adc_code; + int64_t measurement; + int64_t physical; +}; + +#if defined(CONFIG_SENSORS_PM8XXX_ADC) \ + || defined(CONFIG_SENSORS_PM8XXX_ADC_MODULE) +/** + * pm8xxx_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 pm8xxx 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 pm8xxx_adc_scale_default(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt); +/** + * pm8xxx_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 pm8xxx 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 pm8xxx_adc_tdkntcg_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt); +/** + * pm8xxx_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 pm8xxx 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 pm8xxx_adc_scale_batt_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt); +/** + * pm8xxx_adc_scale_pa_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 pm8xxx 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 pm8xxx_adc_scale_pa_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt); +/** + * pm8xxx_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 pm8xxx 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 pm8xxx_adc_scale_pmic_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt); +/** + * pm8xxx_adc_scale_batt_id() - 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 pm8xxx 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 pm8xxx_adc_scale_batt_id(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt); +#else +static inline int32_t pm8xxx_adc_scale_default(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8xxx_adc_tdkntcg_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8xxx_adc_scale_batt_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8xxx_adc_scale_pa_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8xxx_adc_scale_pmic_therm(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8xxx_adc_scale_batt_id(int32_t adc_code, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop, + struct pm8xxx_adc_chan_result *chan_rslt) +{ return -ENXIO; } +#endif + +/** + * struct pm8xxx_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 pm8xxx_adc_scale_fn { + int32_t (*chan) (int32_t, + const struct pm8xxx_adc_properties *, + const struct pm8xxx_adc_chan_properties *, + struct pm8xxx_adc_chan_result *); +}; + +/** + * struct pm8xxx_adc_amux - AMUX properties for individual channel + * @name: Channel name + * @channel_name: Channel in integer used from pm8xxx_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 pm8xxx_adc_amux { + char *name; + enum pm8xxx_adc_channels channel_name; + enum pm8xxx_adc_channel_scaling_param chan_path_prescaling; + enum pm8xxx_adc_amux_input_rsv adc_rsv; + enum pm8xxx_adc_decimation_type adc_decimation; + enum pm8xxx_adc_scale_fn_type adc_scale_fn; +}; + +/** + * struct pm8xxx_adc_arb_btm_param - PM8XXX 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_cool_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 pm8xxx_adc_arb_btm_param { + int32_t low_thr_temp; + int32_t high_thr_temp; + uint64_t low_thr_voltage; + uint64_t high_thr_voltage; + int32_t interval; + void (*btm_warm_fn) (bool); + void (*btm_cool_fn) (bool); +}; + +int32_t pm8xxx_adc_batt_scaler(struct pm8xxx_adc_arb_btm_param *, + const struct pm8xxx_adc_properties *adc_prop, + const struct pm8xxx_adc_chan_properties *chan_prop); +/** + * struct pm8xxx_adc_platform_data - PM8XXX ADC platform data + * @adc_prop: ADC specific parameters, voltage and channel setup + * @adc_channel: Channel properties of the ADC arbiter + * @adc_num_board_channel: Number of channels added in the board file + * @adc_mpp_base: PM8XXX MPP0 base passed from board file. This is used + * to offset the PM8XXX MPP passed to configure the + * the MPP to AMUX mapping. + */ +struct pm8xxx_adc_platform_data { + struct pm8xxx_adc_properties *adc_prop; + struct pm8xxx_adc_amux *adc_channel; + uint32_t adc_num_board_channel; + uint32_t adc_mpp_base; +}; + +/* Public API */ +#if defined(CONFIG_SENSORS_PM8XXX_ADC) \ + || defined(CONFIG_SENSORS_PM8XXX_ADC_MODULE) +/** + * pm8xxx_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 pm8xxx_adc_read(enum pm8xxx_adc_channels channel, + struct pm8xxx_adc_chan_result *result); +/** + * pm8xxx_adc_mpp_config_read() - Configure's the PM8XXX MPP + * to AMUX6 and performs an ADC read. + * + * On PM8921 ADC the MPP needs to first be configured + * as an analog input to the AMUX pre-mux channel before + * issuing a read request. PM8921 MPP 8 is mapped to AMUX8 + * and is common between remote processor's. + * + * On PM8018 ADC the MPP is directly connected to the AMUX + * pre-mux. Therefore clients of the PM8018 MPP do not need + * to configure the MPP as an analog input to the pre-mux. + * Clients can directly issue request on the pre-mux AMUX + * channel to read the ADC on the MPP. Clients can directly + * call the pm8xxx_adc_read(). + * @mpp_num PM8XXX MPP number to configure to AMUX6. + * @channel: Input channel to perform the ADC read. + * a) 'ADC_MPP_1_AMUX6' if the input voltage is less than 1.8V + * b) 'ADC_MPP_2_AMUX6' if the input voltage is greater then 1.8V + * the input voltage is pre-divided by 3 and passed to the ADC. + * The appropriate scaling function needs to be selected to let + * the driver know a post scaling is required before returning + * the result. + * @result: Structure pointer of type adc_chan_result + * in which the ADC read results are stored. + */ +uint32_t pm8xxx_adc_mpp_config_read(uint32_t mpp_num, + enum pm8xxx_adc_channels channel, + struct pm8xxx_adc_chan_result *result); +/** + * pm8xxx_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 + pm8xxx_btm_configure() api which sets + the temperature thresholds, interval + and functions to call when warm/cold + events are triggered. + * @param: none. + */ +uint32_t pm8xxx_adc_btm_start(void); + +/** + * pm8xxx_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 pm8xxx_adc_btm_end(void); + +/** + * pm8xxx_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 pm8xxx_adc_btm_configure(struct pm8xxx_adc_arb_btm_param *); +#else +static inline uint32_t pm8xxx_adc_read(uint32_t channel, + struct pm8xxx_adc_chan_result *result) +{ return -ENXIO; } +static inline uint32_t pm8xxx_adc_mpp_config_read(uint32_t mpp_num, + enum pm8xxx_adc_channels channel, + struct pm8xxx_adc_chan_result *result) +{ return -ENXIO; } +static inline uint32_t pm8xxx_adc_btm_start(void) +{ return -ENXIO; } +static inline uint32_t pm8xxx_adc_btm_end(void) +{ return -ENXIO; } +static inline uint32_t pm8xxx_adc_btm_configure( + struct pm8xxx_adc_arb_btm_param *param) +{ return -ENXIO; } +#endif + +#endif /* PM8XXX_ADC_H */ diff --git a/include/linux/mfd/pm8xxx/pwm.h b/include/linux/mfd/pm8xxx/pwm.h new file mode 100644 index 0000000000000000000000000000000000000000..09b165e4cce7a67c75c6728683474a1df768e3e4 --- /dev/null +++ b/include/linux/mfd/pm8xxx/pwm.h @@ -0,0 +1,169 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_MIN 7 /* usec: 19.2M, n=6, m=0, pre=2 */ +#define PM8XXX_PWM_PERIOD_MAX (384 * USEC_PER_SEC) /* 1K, n=9, m=7, pre=6 */ +#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 frequency/period control + * + * PWM Frequency = ClockFrequency / (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) + * + */ + +enum pm_pwm_size { + PM_PWM_SIZE_6BIT = 6, + PM_PWM_SIZE_9BIT = 9, +}; + +enum pm_pwm_clk { + PM_PWM_CLK_1KHZ, + PM_PWM_CLK_32KHZ, + PM_PWM_CLK_19P2MHZ, +}; + +enum pm_pwm_pre_div { + PM_PWM_PDIV_2, + PM_PWM_PDIV_3, + PM_PWM_PDIV_5, + PM_PWM_PDIV_6, +}; + +/** + * struct pm8xxx_pwm_period - PWM period structure + * @pwm_size: enum pm_pwm_size + * @clk: enum pm_pwm_clk + * @pre_div: enum pm_pwm_pre_div + * @pre_div_exp: exponent of 2 as part of pre-divider: 0..7 + */ +struct pm8xxx_pwm_period { + enum pm_pwm_size pwm_size; + enum pm_pwm_clk clk; + enum pm_pwm_pre_div pre_div; + int pre_div_exp; +}; + +/** + * struct pm8xxx_pwm_duty_cycles - PWM duty cycle info + * duty_pcts - pointer to an array of duty percentage for a pwm period + * num_duty_pcts - total entries in duty_pcts array + * duty_ms - duty cycle time in ms + * start_idx - index in the LUT + */ +struct pm8xxx_pwm_duty_cycles { + int *duty_pcts; + int num_duty_pcts; + int duty_ms; + int start_idx; +}; + +/** + * struct pm8xxx_pwm_platform_data - PWM platform data + * dtest_channel - Enable LPG DTEST mode for this LPG channel + */ +struct pm8xxx_pwm_platform_data { + int dtest_channel; +}; + +/** + * pm8xxx_pwm_config_period - change PWM period + * + * @pwm: the PWM device + * @pwm_p: period in struct pm8xxx_pwm_period + */ +int pm8xxx_pwm_config_period(struct pwm_device *pwm, + struct pm8xxx_pwm_period *pwm_p); + +/** + * pm8xxx_pwm_config_pwm_value - change a PWM device configuration + * @pwm: the PWM device + * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size) + */ +int pm8xxx_pwm_config_pwm_value(struct pwm_device *pwm, int pwm_value); + +/** + * 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/regulator.h b/include/linux/mfd/pm8xxx/regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..83492d271de1cfef022bfdb7071797b69a9a9f42 --- /dev/null +++ b/include/linux/mfd/pm8xxx/regulator.h @@ -0,0 +1,271 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_REGULATOR_H__ +#define __MFD_PM8XXX_REGULATOR_H__ + +#include +#include +#include +#include + +/** + * enum pm8xxx_regulator_type - possible PM8XXX voltage regulator types + * %PM8XXX_REGULATOR_TYPE_PLDO: PMOS low drop-out linear regulator + * %PM8XXX_REGULATOR_TYPE_NLDO: NMOS low drop-out linear regulator + * %PM8XXX_REGULATOR_TYPE_NLDO1200: NMOS low drop-out linear regulator + * capable of supplying up to 1200 mA + * %PM8XXX_REGULATOR_TYPE_SMPS: switched-mode power supply (buck) + * %PM8XXX_REGULATOR_TYPE_FTSMPS: fast transient switched-mode power + * supply (buck) + * %PM8XXX_REGULATOR_TYPE_VS: voltage switch capable of sourcing 100mA + * %PM8XXX_REGULATOR_TYPE_VS300: voltage switch capable of sourcing 300mA + * %PM8XXX_REGULATOR_TYPE_NCP: negative charge pump + * %PM8XXX_REGULATOR_TYPE_BOOST: boost regulator + * %PM8XXX_REGULATOR_TYPE_MAX: used internally for error checking; not + * a valid regulator type. + * + * Each of these has a different register control interface. + */ +enum pm8xxx_regulator_type { + PM8XXX_REGULATOR_TYPE_PLDO, + PM8XXX_REGULATOR_TYPE_NLDO, + PM8XXX_REGULATOR_TYPE_NLDO1200, + PM8XXX_REGULATOR_TYPE_SMPS, + PM8XXX_REGULATOR_TYPE_FTSMPS, + PM8XXX_REGULATOR_TYPE_VS, + PM8XXX_REGULATOR_TYPE_VS300, + PM8XXX_REGULATOR_TYPE_NCP, + PM8XXX_REGULATOR_TYPE_BOOST, + PM8XXX_REGULATOR_TYPE_MAX, +}; + +/** + * struct pm8xxx_vreg - regulator configuration and state data used by the + * pm8xxx-regulator driver + * @rdesc: regulator description + * @rdesc_pc: pin control regulator description. rdesc_pc.name == NULL + * implies that there is no pin control version of this + * regulator. + * @type: regulator type + * @hpm_min_load: minimum load in uA that will result in the regulator + * being set to high power mode + * @ctrl_addr: control register SSBI address + * @test_addr: test register SSBI address (not needed for all types) + * @clk_ctrl_addr: clock control register SSBI address (only used by SMPS + * type regulators) + * @sleep_ctrl_addr: sleep control register SSBI address (only used by SMPS + * type regulators) + * @pfm_ctrl_addr: pulse-frequency modulation control register SSBI address + * (only used by FTSMPS type regulators) + * @pwr_cnfg_addr: power configuration register SSBI address (only used by + * FTSMPS type regulators) + * @pdata: this platform data struct is filled based using the + * platform data pointed to in a core platform data struct + * @rdev: pointer to regulator device which is created with + * regulator_register + * @rdev_pc: pointer to pin controlled regulator device which is + * created with regulator_register + * @dev: pointer to pm8xxx-regulator device + * @dev_pc: pointer to pin control pm8xxx-regulator device + * @pc_lock: mutex lock to handle sharing between pin controlled and + * non-pin controlled versions of a given regulator. Note, + * this lock must be initialized in the PMIC core driver.) + * @save_uV: current regulator voltage in uV + * @mode: current mode of the regulator + * @write_count: number of SSBI writes that have taken place for this + * regulator. This is used for debug printing to determine + * if a given operation is redundant. + * @prev_write_count: number of SSBI writes that have taken place for this + * regulator at the start of an operation. This is used for + * debug printing to determine if a given operation is + * redundant. + * @is_enabled: true if the regulator is currently enabled, false if not + * @is_enabled_pc: true if the pin controlled version of the regulator is + * currently enabled (i.e. pin control is active), false if + * not + * @test_reg: last value read from or written to each of the banks of + * the test register + * @ctrl_reg: last value read from or written to the control register + * @clk_ctrl_reg: last value read from or written to the clock control + * register + * @sleep_ctrl_reg: last value read from or written to the sleep control + * register + * @pfm_ctrl_reg: last value read from or written to the PFM control + * register + * @pwr_cnfg_reg: last value read from or written to the power + * configuration register + * + * This data structure should only need to be instantiated in a PMIC core driver + * It is used to specify PMIC specific as opposed to board specific + * configuration data. It is also used to hold all state variables needed by + * the pm8xxx-regulator driver as these variables need to be shared between + * pin controlled and non-pin controlled versions of a given regulator, which + * are probed separately. + */ +struct pm8xxx_vreg { + /* Configuration data */ + struct regulator_desc rdesc; + struct regulator_desc rdesc_pc; + enum pm8xxx_regulator_type type; + 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; + /* State data */ + struct pm8xxx_regulator_platform_data pdata; + struct regulator_dev *rdev; + struct regulator_dev *rdev_pc; + struct device *dev; + struct device *dev_pc; + 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; +}; + +/** + * struct pm8xxx_regulator_core_platform_data - platform data specified in a + * PMIC core driver and utilized in the pm8xxx-regulator driver +* @vreg: pointer to pm8xxx_vreg data structure that may be shared +* between pin controlled and non-pin controlled versions +* of a given regulator. Note that this data must persist +* as long as the regulator device is in use. +* @pdata: pointer to platform data passed in from a board file +* @is_pin_controlled: true if the regulator driver represents the pin control +* portion of a regulator, false if not. +* +* This data structure should only be needed in a PMIC core driver. +*/ +struct pm8xxx_regulator_core_platform_data { + struct pm8xxx_vreg *vreg; + struct pm8xxx_regulator_platform_data *pdata; + bool is_pin_controlled; +}; + +/* Helper macros */ +#define PLDO(_name, _pc_name, _ctrl_addr, _test_addr, _hpm_min_load) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_PLDO, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .hpm_min_load = PM8XXX_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _pc_name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define NLDO(_name, _pc_name, _ctrl_addr, _test_addr, _hpm_min_load) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_NLDO, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .hpm_min_load = PM8XXX_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _pc_name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define NLDO1200(_name, _ctrl_addr, _test_addr, _hpm_min_load) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_NLDO1200, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .hpm_min_load = PM8XXX_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .rdesc.name = _name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define SMPS(_name, _pc_name, _ctrl_addr, _test_addr, _clk_ctrl_addr, \ + _sleep_ctrl_addr, _hpm_min_load) \ + { \ + .type = PM8XXX_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 = PM8XXX_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _pc_name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define FTSMPS(_name, _pwm_ctrl_addr, _fts_cnfg1_addr, _pfm_ctrl_addr, \ + _pwr_cnfg_addr, _hpm_min_load) \ + { \ + .type = PM8XXX_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 = PM8XXX_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .rdesc.name = _name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define VS(_name, _pc_name, _ctrl_addr, _test_addr) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_VS, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .rdesc.name = _name, \ + .rdesc_pc.name = _pc_name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define VS300(_name, _ctrl_addr, _test_addr) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_VS300, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .rdesc.name = _name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define NCP(_name, _ctrl_addr) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_NCP, \ + .ctrl_addr = _ctrl_addr, \ + .rdesc.name = _name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#define BOOST(_name, _ctrl_addr) \ + { \ + .type = PM8XXX_REGULATOR_TYPE_BOOST, \ + .ctrl_addr = _ctrl_addr, \ + .rdesc.name = _name, \ + .write_count = 0, \ + .prev_write_count = -1, \ + } + +#endif diff --git a/include/linux/mfd/pm8xxx/rtc.h b/include/linux/mfd/pm8xxx/rtc.h index 14f1983eaecc4c84c4d26d6760cde8f3323819a7..bb3f98a54c1dd8e09761c3fef80bbb12c71d190a 100644 --- a/include/linux/mfd/pm8xxx/rtc.h +++ b/include/linux/mfd/pm8xxx/rtc.h @@ -20,6 +20,7 @@ */ struct pm8xxx_rtc_platform_data { bool rtc_write_enable; + bool rtc_alarm_powerup; }; #endif /* __RTC_PM8XXX_H__ */ diff --git a/include/linux/mfd/pm8xxx/spk.h b/include/linux/mfd/pm8xxx/spk.h new file mode 100644 index 0000000000000000000000000000000000000000..1155d2f3cf24424cb330e4a73d9036a19ff578db --- /dev/null +++ b/include/linux/mfd/pm8xxx/spk.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SPK_PM8XXX_H__ +#define __SPK_PM8XXX_H__ + +#define PM8XXX_SPK_DEV_NAME "pm8xxx-spk" + +/** + * struct pm8xxx_spk_pdata - SPK driver platform data + * @spk_add_enable: variable stating SPK secondary input adding capability + */ +struct pm8xxx_spk_platform_data { + bool spk_add_enable; +}; + +/* + * pm8xxx_spk_mute - mute/unmute speaker pamp + * + * @mute: bool value for mute + */ +int pm8xxx_spk_mute(bool mute); + +/* + * pm8xxx_spk_gain - Set Speaker gain + * + * @gain: Speaker gain + */ +int pm8xxx_spk_gain(u8 gain); + +/* + * pm8xxx_spk_enable - Enable/Disable Speaker + * + * @enable: bool enable/disable Speaker + */ +int pm8xxx_spk_enable(int enable); + +#endif /* __SPK_PM8XXX_H__ */ diff --git a/include/linux/mfd/pm8xxx/tm.h b/include/linux/mfd/pm8xxx/tm.h new file mode 100644 index 0000000000000000000000000000000000000000..69747547988ca648d0a1151ad1801bb63b832d3a --- /dev/null +++ b/include/linux/mfd/pm8xxx/tm.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. + */ + +/* + * 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_PM8058_ADC, + PM8XXX_TM_ADC_PM8XXX_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/pm8xxx/upl.h b/include/linux/mfd/pm8xxx/upl.h new file mode 100644 index 0000000000000000000000000000000000000000..b0e94a9a49c580289910e28c73c0885efe892877 --- /dev/null +++ b/include/linux/mfd/pm8xxx/upl.h @@ -0,0 +1,65 @@ +/* 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 __PM8XXX_UPL_H__ +#define __PM8XXX_UPL_H__ + +struct pm8xxx_upl_device; + +#define PM8XXX_UPL_DEV_NAME "pm8xxx-upl" + +/* control masks and flags */ +#define PM8XXX_UPL_MOD_ENABLE_MASK (0x10) +#define PM8XXX_UPL_MOD_ENABLE (0x10) +#define PM8XXX_UPL_MOD_DISABLE (0x00) + +#define PM8XXX_UPL_OUT_DTEST_MASK (0xE0) +#define PM8XXX_UPL_OUT_GPIO_ONLY (0x00) +#define PM8XXX_UPL_OUT_DTEST_1 (0x80) +#define PM8XXX_UPL_OUT_DTEST_2 (0xA0) +#define PM8XXX_UPL_OUT_DTEST_3 (0xC0) +#define PM8XXX_UPL_OUT_DTEST_4 (0xE0) + +#define PM8XXX_UPL_IN_A_MASK (0x01) +#define PM8XXX_UPL_IN_A_GPIO (0x00) +#define PM8XXX_UPL_IN_A_DTEST (0x01) +#define PM8XXX_UPL_IN_B_MASK (0x02) +#define PM8XXX_UPL_IN_B_GPIO (0x00) +#define PM8XXX_UPL_IN_B_DTEST (0x02) +#define PM8XXX_UPL_IN_C_MASK (0x04) +#define PM8XXX_UPL_IN_C_GPIO (0x00) +#define PM8XXX_UPL_IN_C_DTEST (0x04) +#define PM8XXX_UPL_IN_D_MASK (0x08) +#define PM8XXX_UPL_IN_D_GPIO (0x00) +#define PM8XXX_UPL_IN_D_DTEST (0x08) + +/* + * pm8xxx_upl_request - request a handle to access UPL device + */ +struct pm8xxx_upl_device *pm8xxx_upl_request(void); + +int pm8xxx_upl_read_truthtable(struct pm8xxx_upl_device *upldev, + u16 *truthtable); + +int pm8xxx_upl_write_truthtable(struct pm8xxx_upl_device *upldev, + u16 truthtable); + +/* + * pm8xxx_upl_config - configure UPL I/O settings and UPL enable/disable + * + * @upldev: the UPL device + * @mask: setting mask to configure + * @flags: setting flags + */ +int pm8xxx_upl_config(struct pm8xxx_upl_device *upldev, u32 mask, u32 flags); + +#endif /* __PM8XXX_UPL_H__ */ diff --git a/include/linux/mfd/pm8xxx/vibrator.h b/include/linux/mfd/pm8xxx/vibrator.h new file mode 100644 index 0000000000000000000000000000000000000000..cfea1c9f9b817b924479d3f1d0a9129260da1d84 --- /dev/null +++ b/include/linux/mfd/pm8xxx/vibrator.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 __PMIC8XXX_VIBRATOR_H__ +#define __PMIC8XXX_VIBRATOR_H__ + +#define PM8XXX_VIBRATOR_DEV_NAME "pm8xxx-vib" + +enum pm8xxx_vib_en_mode { + PM8XXX_VIB_MANUAL, + PM8XXX_VIB_DTEST1, + PM8XXX_VIB_DTEST2, + PM8XXX_VIB_DTEST3 +}; + +struct pm8xxx_vib_config { + u16 drive_mV; + u8 active_low; + enum pm8xxx_vib_en_mode enable_mode; +}; + +struct pm8xxx_vibrator_platform_data { + int initial_vibrate_ms; + int max_timeout_ms; + int level_mV; +}; + +int pm8xxx_vibrator_config(struct pm8xxx_vib_config *vib_config); + +#endif /* __PMIC8XXX_VIBRATOR_H__ */ diff --git a/include/linux/mfd/pmic8058.h b/include/linux/mfd/pmic8058.h new file mode 100644 index 0000000000000000000000000000000000000000..ff7a3295cbf9c35a0910dec7aa24e9b428b27516 --- /dev/null +++ b/include/linux/mfd/pmic8058.h @@ -0,0 +1,133 @@ +/* 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 + * + */ + +#ifndef __MFD_PMIC8058_H__ +#define __MFD_PMIC8058_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PM8058_GPIOS 40 +#define PM8058_MPPS 12 + +#define PM8058_GPIO_BLOCK_START 24 +#define PM8058_MPP_BLOCK_START 16 + +#define PM8058_NR_IRQS 256 + +#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))) + +/* PM8058 IRQ's */ +#define PM8058_VCP_IRQ PM8058_IRQ_BLOCK_BIT(1, 0) +#define PM8058_CHGILIM_IRQ PM8058_IRQ_BLOCK_BIT(1, 3) +#define PM8058_VBATDET_LOW_IRQ PM8058_IRQ_BLOCK_BIT(1, 4) +#define PM8058_BATT_REPLACE_IRQ PM8058_IRQ_BLOCK_BIT(1, 5) +#define PM8058_CHGINVAL_IRQ PM8058_IRQ_BLOCK_BIT(1, 6) +#define PM8058_CHGVAL_IRQ PM8058_IRQ_BLOCK_BIT(1, 7) +#define PM8058_CHG_END_IRQ PM8058_IRQ_BLOCK_BIT(2, 0) +#define PM8058_FASTCHG_IRQ PM8058_IRQ_BLOCK_BIT(2, 1) +#define PM8058_CHGSTATE_IRQ PM8058_IRQ_BLOCK_BIT(2, 3) +#define PM8058_AUTO_CHGFAIL_IRQ PM8058_IRQ_BLOCK_BIT(2, 4) +#define PM8058_AUTO_CHGDONE_IRQ PM8058_IRQ_BLOCK_BIT(2, 5) +#define PM8058_ATCFAIL_IRQ PM8058_IRQ_BLOCK_BIT(2, 6) +#define PM8058_ATC_DONE_IRQ PM8058_IRQ_BLOCK_BIT(2, 7) +#define PM8058_OVP_OK_IRQ PM8058_IRQ_BLOCK_BIT(3, 0) +#define PM8058_COARSE_DET_OVP_IRQ PM8058_IRQ_BLOCK_BIT(3, 1) +#define PM8058_VCPMAJOR_IRQ PM8058_IRQ_BLOCK_BIT(3, 2) +#define PM8058_CHG_GONE_IRQ PM8058_IRQ_BLOCK_BIT(3, 3) +#define PM8058_CHGTLIMIT_IRQ PM8058_IRQ_BLOCK_BIT(3, 4) +#define PM8058_CHGHOT_IRQ PM8058_IRQ_BLOCK_BIT(3, 5) +#define PM8058_BATTTEMP_IRQ PM8058_IRQ_BLOCK_BIT(3, 6) +#define PM8058_BATTCONNECT_IRQ PM8058_IRQ_BLOCK_BIT(3, 7) +#define PM8058_BATFET_IRQ PM8058_IRQ_BLOCK_BIT(5, 4) +#define PM8058_VBATDET_IRQ PM8058_IRQ_BLOCK_BIT(5, 5) +#define PM8058_VBAT_IRQ PM8058_IRQ_BLOCK_BIT(5, 6) + +#define PM8058_RTC_IRQ PM8058_IRQ_BLOCK_BIT(6, 5) +#define PM8058_RTC_ALARM_IRQ PM8058_IRQ_BLOCK_BIT(4, 7) +#define PM8058_PWRKEY_REL_IRQ PM8058_IRQ_BLOCK_BIT(6, 2) +#define PM8058_PWRKEY_PRESS_IRQ PM8058_IRQ_BLOCK_BIT(6, 3) +#define PM8058_KEYPAD_IRQ PM8058_IRQ_BLOCK_BIT(9, 2) +#define PM8058_KEYSTUCK_IRQ PM8058_IRQ_BLOCK_BIT(9, 3) +#define PM8058_BATT_ALARM_IRQ PM8058_IRQ_BLOCK_BIT(5, 6) +#define PM8058_SW_0_IRQ PM8058_IRQ_BLOCK_BIT(7, 1) +#define PM8058_IR_0_IRQ PM8058_IRQ_BLOCK_BIT(7, 0) +#define PM8058_SW_1_IRQ PM8058_IRQ_BLOCK_BIT(7, 3) +#define PM8058_IR_1_IRQ PM8058_IRQ_BLOCK_BIT(7, 2) +#define PM8058_SW_2_IRQ PM8058_IRQ_BLOCK_BIT(7, 5) +#define PM8058_IR_2_IRQ PM8058_IRQ_BLOCK_BIT(7, 4) +#define PM8058_TEMPSTAT_IRQ PM8058_IRQ_BLOCK_BIT(6, 7) +#define PM8058_OVERTEMP_IRQ PM8058_IRQ_BLOCK_BIT(4, 2) +#define PM8058_ADC_IRQ PM8058_IRQ_BLOCK_BIT(9, 4) +#define PM8058_OSCHALT_IRQ PM8058_IRQ_BLOCK_BIT(4, 6) +#define PM8058_CBLPWR_IRQ PM8058_IRQ_BLOCK_BIT(4, 3) +#define PM8058_RESOUT_IRQ PM8058_IRQ_BLOCK_BIT(6, 4) + +struct pmic8058_charger_data { + unsigned int max_source_current; + int charger_type; + bool charger_data_valid; +}; + +struct pm8058_platform_data { + struct pm8xxx_mpp_platform_data *mpp_pdata; + struct pm8xxx_keypad_platform_data *keypad_pdata; + struct pm8xxx_gpio_platform_data *gpio_pdata; + struct pm8xxx_irq_platform_data *irq_pdata; + struct pm8xxx_rtc_platform_data *rtc_pdata; + struct pm8xxx_pwrkey_platform_data *pwrkey_pdata; + struct pm8xxx_vibrator_platform_data *vibrator_pdata; + struct pm8xxx_misc_platform_data *misc_pdata; + struct pmic8058_leds_platform_data *leds_pdata; + struct pmic8058_othc_config_pdata *othc0_pdata; + struct pmic8058_othc_config_pdata *othc1_pdata; + struct pmic8058_othc_config_pdata *othc2_pdata; + struct xoadc_platform_data *xoadc_pdata; + struct pm8058_pwm_pdata *pwm_pdata; + struct pm8058_vreg_pdata *regulator_pdatas; + int num_regulators; + struct pm8058_xo_pdata *xo_buffer_pdata; + int num_xo_buffers; + struct pmic8058_charger_data *charger_pdata; +}; + +#endif /* __MFD_PMIC8058_H__ */ diff --git a/include/linux/mfd/pmic8901.h b/include/linux/mfd/pmic8901.h new file mode 100644 index 0000000000000000000000000000000000000000..f5b34be4320839003a7fe7328397040bbf381bb4 --- /dev/null +++ b/include/linux/mfd/pmic8901.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 __PMIC8901_H__ +#define __PMIC8901_H__ +/* + * Qualcomm PMIC8901 driver header file + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define PM8901_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +#define PM8901_NR_IRQS 72 + +/* PM8901 MPP */ +#define PM8901_MPP_BLOCK_START 6 +#define PM8901_MPPS 4 + +/* PM8901 IRQs */ +#define PM8901_MPP_IRQ(mpp) PM8901_IRQ_BLOCK_BIT(6, (mpp)) +#define PM8901_TEMPSTAT_IRQ PM8901_IRQ_BLOCK_BIT(6, 4) +#define PM8901_OVERTEMP_IRQ PM8901_IRQ_BLOCK_BIT(6, 5) + +struct pm8901_platform_data { + struct pm8xxx_irq_platform_data *irq_pdata; + struct pm8xxx_mpp_platform_data *mpp_pdata; + struct pm8xxx_misc_platform_data *misc_pdata; + struct pm8901_vreg_pdata *regulator_pdatas; + int num_regulators; +}; + +#endif /* __PMIC8901_H__ */ diff --git a/include/linux/mfd/timpani-audio.h b/include/linux/mfd/timpani-audio.h new file mode 100644 index 0000000000000000000000000000000000000000..49fd49ba2155ba3e14fee1225ffab3402744d104 --- /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 0000000000000000000000000000000000000000..4cce091399750d69cb045e722149d8e9a2e0694a --- /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/wcd9xxx/Kbuild b/include/linux/mfd/wcd9xxx/Kbuild new file mode 100644 index 0000000000000000000000000000000000000000..acfab6efa82949f7cc8f8cecf38dda3ae2850442 --- /dev/null +++ b/include/linux/mfd/wcd9xxx/Kbuild @@ -0,0 +1,2 @@ +header-y += wcd9xxx_registers.h +header-y += wcd9310_registers.h diff --git a/include/linux/mfd/wcd9xxx/core.h b/include/linux/mfd/wcd9xxx/core.h new file mode 100644 index 0000000000000000000000000000000000000000..7917d243251677a8f742922928fe6705888f981a --- /dev/null +++ b/include/linux/mfd/wcd9xxx/core.h @@ -0,0 +1,191 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include + +#define WCD9XXX_NUM_IRQ_REGS 3 + +#define WCD9XXX_SLIM_NUM_PORT_REG 3 + +#define WCD9XXX_INTERFACE_TYPE_SLIMBUS 0x00 +#define WCD9XXX_INTERFACE_TYPE_I2C 0x01 + +#define TABLA_VERSION_1_0 0 +#define TABLA_VERSION_1_1 1 +#define TABLA_VERSION_2_0 2 +#define TABLA_IS_1_X(ver) \ + (((ver == TABLA_VERSION_1_0) || (ver == TABLA_VERSION_1_1)) ? 1 : 0) +#define TABLA_IS_2_0(ver) ((ver == TABLA_VERSION_2_0) ? 1 : 0) + +#define SITAR_VERSION_1P0 0 +#define SITAR_VERSION_1P1 1 +#define SITAR_IS_1P0(ver) \ + ((ver == SITAR_VERSION_1P0) ? 1 : 0) +#define SITAR_IS_1P1(ver) \ + ((ver == SITAR_VERSION_1P1) ? 1 : 0) + +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, +}; + +enum { + SITAR_IRQ_SLIMBUS = 0, + SITAR_IRQ_MBHC_REMOVAL, + SITAR_IRQ_MBHC_SHORT_TERM, + SITAR_IRQ_MBHC_PRESS, + SITAR_IRQ_MBHC_RELEASE, + SITAR_IRQ_MBHC_POTENTIAL, + SITAR_IRQ_MBHC_INSERTION, + SITAR_IRQ_BG_PRECHARGE, + SITAR_IRQ_PA1_STARTUP, + SITAR_IRQ_PA2_STARTUP, + SITAR_IRQ_PA3_STARTUP, + SITAR_IRQ_PA4_STARTUP, + SITAR_IRQ_PA5_STARTUP, + SITAR_IRQ_MICBIAS1_PRECHARGE, + SITAR_IRQ_MICBIAS2_PRECHARGE, + SITAR_IRQ_MICBIAS3_PRECHARGE, + SITAR_IRQ_HPH_PA_OCPL_FAULT, + SITAR_IRQ_HPH_PA_OCPR_FAULT, + SITAR_IRQ_EAR_PA_OCPL_FAULT, + SITAR_IRQ_HPH_L_PA_STARTUP, + SITAR_IRQ_HPH_R_PA_STARTUP, + SITAR_IRQ_EAR_PA_STARTUP, + SITAR_NUM_IRQS, +}; + + +enum wcd9xxx_pm_state { + WCD9XXX_PM_SLEEPABLE, + WCD9XXX_PM_AWAKE, + WCD9XXX_PM_ASLEEP, +}; + +struct wcd9xxx { + struct device *dev; + struct slim_device *slim; + struct slim_device *slim_slave; + struct mutex io_lock; + struct mutex xfer_lock; + struct mutex irq_lock; + u8 version; + + unsigned int irq_base; + unsigned int irq; + u8 irq_masks_cur[WCD9XXX_NUM_IRQ_REGS]; + u8 irq_masks_cache[WCD9XXX_NUM_IRQ_REGS]; + u8 irq_level[WCD9XXX_NUM_IRQ_REGS]; + + int reset_gpio; + + int (*read_dev)(struct wcd9xxx *wcd9xxx, unsigned short reg, + int bytes, void *dest, bool interface_reg); + int (*write_dev)(struct wcd9xxx *wcd9xxx, unsigned short reg, + int bytes, void *src, bool interface_reg); + + struct regulator_bulk_data *supplies; + + enum wcd9xxx_pm_state pm_state; + struct mutex pm_lock; + /* pm_wq notifies change of pm_state */ + wait_queue_head_t pm_wq; + struct wake_lock wlock; + int wlock_holders; + + int num_rx_port; + int num_tx_port; +}; + +int wcd9xxx_reg_read(struct wcd9xxx *wcd9xxx, unsigned short reg); +int wcd9xxx_reg_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + u8 val); +int wcd9xxx_interface_reg_read(struct wcd9xxx *wcd9xxx, unsigned short reg); +int wcd9xxx_interface_reg_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + u8 val); +int wcd9xxx_bulk_read(struct wcd9xxx *wcd9xxx, unsigned short reg, + int count, u8 *buf); +int wcd9xxx_bulk_write(struct wcd9xxx *wcd9xxx, unsigned short reg, + int count, u8 *buf); +int wcd9xxx_irq_init(struct wcd9xxx *wcd9xxx); +void wcd9xxx_irq_exit(struct wcd9xxx *wcd9xxx); +int wcd9xxx_get_logical_addresses(u8 *pgd_la, u8 *inf_la); +int wcd9xxx_get_intf_type(void); + +bool wcd9xxx_lock_sleep(struct wcd9xxx *wcd9xxx); +void wcd9xxx_unlock_sleep(struct wcd9xxx *wcd9xxx); +enum wcd9xxx_pm_state wcd9xxx_pm_cmpxchg(struct wcd9xxx *wcd9xxx, + enum wcd9xxx_pm_state o, + enum wcd9xxx_pm_state n); + +static inline int wcd9xxx_request_irq(struct wcd9xxx *wcd9xxx, int irq, + irq_handler_t handler, const char *name, + void *data) +{ + if (!wcd9xxx->irq_base) + return -EINVAL; + return request_threaded_irq(wcd9xxx->irq_base + irq, NULL, handler, + IRQF_TRIGGER_RISING, name, + data); +} +static inline void wcd9xxx_free_irq(struct wcd9xxx *wcd9xxx, + int irq, void *data) +{ + if (!wcd9xxx->irq_base) + return; + free_irq(wcd9xxx->irq_base + irq, data); +} +static inline void wcd9xxx_enable_irq(struct wcd9xxx *wcd9xxx, int irq) +{ + if (!wcd9xxx->irq_base) + return; + enable_irq(wcd9xxx->irq_base + irq); +} +static inline void wcd9xxx_disable_irq(struct wcd9xxx *wcd9xxx, int irq) +{ + if (!wcd9xxx->irq_base) + return; + disable_irq_nosync(wcd9xxx->irq_base + irq); +} +static inline void wcd9xxx_disable_irq_sync(struct wcd9xxx *wcd9xxx, int irq) +{ + if (!wcd9xxx->irq_base) + return; + disable_irq(wcd9xxx->irq_base + irq); +} + +#endif diff --git a/include/linux/mfd/wcd9xxx/pdata.h b/include/linux/mfd/wcd9xxx/pdata.h new file mode 100644 index 0000000000000000000000000000000000000000..db76294049280e848cd2c72e93dd99cfda0f6c97 --- /dev/null +++ b/include/linux/mfd/wcd9xxx/pdata.h @@ -0,0 +1,141 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +#define SITAR_LDOH_1P95_V 0x0 +#define SITAR_LDOH_2P35_V 0x1 +#define SITAR_LDOH_2P75_V 0x2 +#define SITAR_LDOH_2P85_V 0x3 + +#define SITAR_CFILT1_SEL 0x0 +#define SITAR_CFILT2_SEL 0x1 +#define SITAR_CFILT3_SEL 0x2 + +#define TABLA_LDOH_1P95_V 0x0 +#define TABLA_LDOH_2P35_V 0x1 +#define TABLA_LDOH_2P75_V 0x2 +#define TABLA_LDOH_2P85_V 0x3 + +#define TABLA_CFILT1_SEL 0x0 +#define TABLA_CFILT2_SEL 0x1 +#define TABLA_CFILT3_SEL 0x2 + +#define MAX_AMIC_CHANNEL 7 + +#define TABLA_OCP_300_MA 0x0 +#define TABLA_OCP_350_MA 0x2 +#define TABLA_OCP_365_MA 0x3 +#define TABLA_OCP_150_MA 0x4 +#define TABLA_OCP_190_MA 0x6 +#define TABLA_OCP_220_MA 0x7 + +#define TABLA_DCYCLE_255 0x0 +#define TABLA_DCYCLE_511 0x1 +#define TABLA_DCYCLE_767 0x2 +#define TABLA_DCYCLE_1023 0x3 +#define TABLA_DCYCLE_1279 0x4 +#define TABLA_DCYCLE_1535 0x5 +#define TABLA_DCYCLE_1791 0x6 +#define TABLA_DCYCLE_2047 0x7 +#define TABLA_DCYCLE_2303 0x8 +#define TABLA_DCYCLE_2559 0x9 +#define TABLA_DCYCLE_2815 0xA +#define TABLA_DCYCLE_3071 0xB +#define TABLA_DCYCLE_3327 0xC +#define TABLA_DCYCLE_3583 0xD +#define TABLA_DCYCLE_3839 0xE +#define TABLA_DCYCLE_4095 0xF + +struct wcd9xxx_amic { + /*legacy mode, txfe_enable and txfe_buff take 7 input + * each bit represent the channel / TXFE number + * and numbered as below + * bit 0 = channel 1 / TXFE1_ENABLE / TXFE1_BUFF + * bit 1 = channel 2 / TXFE2_ENABLE / TXFE2_BUFF + * ... + * bit 7 = channel 7 / TXFE7_ENABLE / TXFE7_BUFF + */ + u8 legacy_mode:MAX_AMIC_CHANNEL; + u8 txfe_enable:MAX_AMIC_CHANNEL; + u8 txfe_buff:MAX_AMIC_CHANNEL; + u8 use_pdata:MAX_AMIC_CHANNEL; +}; + +/* Each micbias can be assigned to one of three cfilters + * Vbatt_min >= .15V + ldoh_v + * ldoh_v >= .15v + cfiltx_mv + * If ldoh_v = 1.95 160 mv < cfiltx_mv < 1800 mv + * If ldoh_v = 2.35 200 mv < cfiltx_mv < 2200 mv + * If ldoh_v = 2.75 240 mv < cfiltx_mv < 2600 mv + * If ldoh_v = 2.85 250 mv < cfiltx_mv < 2700 mv + */ + +struct wcd9xxx_micbias_setting { + u8 ldoh_v; + u32 cfilt1_mv; /* in mv */ + u32 cfilt2_mv; /* in mv */ + u32 cfilt3_mv; /* in mv */ + u8 bias1_cfilt_sel; + u8 bias2_cfilt_sel; + u8 bias3_cfilt_sel; + u8 bias4_cfilt_sel; +}; + +struct wcd9xxx_ocp_setting { + unsigned int use_pdata:1; /* 0 - use sys default as recommended */ + unsigned int num_attempts:4; /* up to 15 attempts */ + unsigned int run_time:4; /* in duty cycle */ + unsigned int wait_time:4; /* in duty cycle */ + unsigned int hph_ocp_limit:3; /* Headphone OCP current limit */ +}; + +#define MAX_REGULATOR 6 +/* + * format : TABLA__CUR_MAX + * + * from Tabla objective spec +*/ + +#define WCD9XXX_CDC_VDDA_CP_CUR_MAX 500000 +#define WCD9XXX_CDC_VDDA_RX_CUR_MAX 20000 +#define WCD9XXX_CDC_VDDA_TX_CUR_MAX 20000 +#define WCD9XXX_VDDIO_CDC_CUR_MAX 5000 + +#define WCD9XXX_VDDD_CDC_D_CUR_MAX 5000 +#define WCD9XXX_VDDD_CDC_A_CUR_MAX 5000 + +struct wcd9xxx_regulator { + const char *name; + int min_uV; + int max_uV; + int optimum_uA; + struct regulator *regulator; +}; + +struct wcd9xxx_pdata { + int irq; + int irq_base; + int num_irqs; + int reset_gpio; + struct wcd9xxx_amic amic_settings; + struct slim_device slimbus_slave_device; + struct wcd9xxx_micbias_setting micbias; + struct wcd9xxx_ocp_setting ocp; + struct wcd9xxx_regulator regulator[MAX_REGULATOR]; +}; + +#endif diff --git a/include/linux/mfd/wcd9xxx/wcd9304_registers.h b/include/linux/mfd/wcd9xxx/wcd9304_registers.h new file mode 100644 index 0000000000000000000000000000000000000000..53ae67b279e752a5eac35eade39280af51a8febe --- /dev/null +++ b/include/linux/mfd/wcd9xxx/wcd9304_registers.h @@ -0,0 +1,777 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef SITAR_CODEC_DIGITAL_H +#define SITAR_CODEC_DIGITAL_H + +#define SITAR_A_PIN_CTL_OE0 (0x10) +#define SITAR_A_PIN_CTL_OE0__POR (0x00000000) +#define SITAR_A_PIN_CTL_OE1 (0x11) +#define SITAR_A_PIN_CTL_OE1__POR (0x00000000) +#define SITAR_A_PIN_CTL_DATA0 (0x12) +#define SITAR_A_PIN_CTL_DATA0__POR (0x00000000) +#define SITAR_A_PIN_CTL_DATA1 (0x13) +#define SITAR_A_PIN_CTL_DATA1__POR (0x00000000) +#define SITAR_A_HDRIVE_GENERIC (0x18) +#define SITAR_A_HDRIVE_GENERIC__POR (0x00000000) +#define SITAR_A_HDRIVE_OVERRIDE (0x19) +#define SITAR_A_HDRIVE_OVERRIDE__POR (0x00000008) +#define SITAR_A_ANA_CSR_WAIT_STATE (0x20) +#define SITAR_A_ANA_CSR_WAIT_STATE__POR (0x00000044) +#define SITAR_A_PROCESS_MONITOR_CTL0 (0x40) +#define SITAR_A_PROCESS_MONITOR_CTL0__POR (0x00000080) +#define SITAR_A_PROCESS_MONITOR_CTL1 (0x41) +#define SITAR_A_PROCESS_MONITOR_CTL1__POR (0x00000000) +#define SITAR_A_PROCESS_MONITOR_CTL2 (0x42) +#define SITAR_A_PROCESS_MONITOR_CTL2__POR (0x00000000) +#define SITAR_A_PROCESS_MONITOR_CTL3 (0x43) +#define SITAR_A_PROCESS_MONITOR_CTL3__POR (0x00000001) +#define SITAR_A_QFUSE_CTL (0x48) +#define SITAR_A_QFUSE_CTL__POR (0x00000000) +#define SITAR_A_QFUSE_STATUS (0x49) +#define SITAR_A_QFUSE_STATUS__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT0 (0x4A) +#define SITAR_A_QFUSE_DATA_OUT0__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT1 (0x4B) +#define SITAR_A_QFUSE_DATA_OUT1__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT2 (0x4C) +#define SITAR_A_QFUSE_DATA_OUT2__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT3 (0x4D) +#define SITAR_A_QFUSE_DATA_OUT3__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT4 (0x4E) +#define SITAR_A_QFUSE_DATA_OUT4__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT5 (0x4F) +#define SITAR_A_QFUSE_DATA_OUT5__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT6 (0x50) +#define SITAR_A_QFUSE_DATA_OUT6__POR (0x00000000) +#define SITAR_A_QFUSE_DATA_OUT7 (0x51) +#define SITAR_A_QFUSE_DATA_OUT7__POR (0x00000000) +#define SITAR_A_CDC_CTL (0x80) +#define SITAR_A_CDC_CTL__POR (0x00000000) +#define SITAR_A_LEAKAGE_CTL (0x88) +#define SITAR_A_LEAKAGE_CTL__POR (0x00000004) +#define SITAR_A_INTR_MODE (0x90) +#define SITAR_A_INTR_MODE__POR (0x00000000) +#define SITAR_A_INTR_MASK0 (0x94) +#define SITAR_A_INTR_MASK0__POR (0x000000ff) +#define SITAR_A_INTR_MASK1 (0x95) +#define SITAR_A_INTR_MASK1__POR (0x000000ff) +#define SITAR_A_INTR_MASK2 (0x96) +#define SITAR_A_INTR_MASK2__POR (0x000000ff) +#define SITAR_A_INTR_STATUS0 (0x98) +#define SITAR_A_INTR_STATUS0__POR (0x00000000) +#define SITAR_A_INTR_STATUS1 (0x99) +#define SITAR_A_INTR_STATUS1__POR (0x00000000) +#define SITAR_A_INTR_STATUS2 (0x9A) +#define SITAR_A_INTR_STATUS2__POR (0x00000000) +#define SITAR_A_INTR_CLEAR0 (0x9C) +#define SITAR_A_INTR_CLEAR0__POR (0x00000000) +#define SITAR_A_INTR_CLEAR1 (0x9D) +#define SITAR_A_INTR_CLEAR1__POR (0x00000000) +#define SITAR_A_INTR_CLEAR2 (0x9E) +#define SITAR_A_INTR_CLEAR2__POR (0x00000000) +#define SITAR_A_INTR_LEVEL0 (0xA0) +#define SITAR_A_INTR_LEVEL0__POR (0x00000001) +#define SITAR_A_INTR_LEVEL1 (0xA1) +#define SITAR_A_INTR_LEVEL1__POR (0x00000000) +#define SITAR_A_INTR_LEVEL2 (0xA2) +#define SITAR_A_INTR_LEVEL2__POR (0x00000000) +#define SITAR_A_INTR_TEST0 (0xA4) +#define SITAR_A_INTR_TEST0__POR (0x00000000) +#define SITAR_A_INTR_TEST1 (0xA5) +#define SITAR_A_INTR_TEST1__POR (0x00000000) +#define SITAR_A_INTR_TEST2 (0xA6) +#define SITAR_A_INTR_TEST2__POR (0x00000000) +#define SITAR_A_INTR_SET0 (0xA8) +#define SITAR_A_INTR_SET0__POR (0x00000000) +#define SITAR_A_INTR_SET1 (0xA9) +#define SITAR_A_INTR_SET1__POR (0x00000000) +#define SITAR_A_INTR_SET2 (0xAA) +#define SITAR_A_INTR_SET2__POR (0x00000000) +#define SITAR_A_CDC_TX_I2S_SCK_MODE (0xC0) +#define SITAR_A_CDC_TX_I2S_SCK_MODE__POR (0x00000000) +#define SITAR_A_CDC_TX_I2S_WS_MODE (0xC1) +#define SITAR_A_CDC_TX_I2S_WS_MODE__POR (0x00000000) +#define SITAR_A_CDC_DMIC_DATA0_MODE (0xC4) +#define SITAR_A_CDC_DMIC_DATA0_MODE__POR (0x00000000) +#define SITAR_A_CDC_DMIC_CLK0_MODE (0xC5) +#define SITAR_A_CDC_DMIC_CLK0_MODE__POR (0x00000000) +#define SITAR_A_CDC_DMIC_DATA1_MODE (0xC6) +#define SITAR_A_CDC_DMIC_DATA1_MODE__POR (0x00000000) +#define SITAR_A_CDC_DMIC_CLK1_MODE (0xC7) +#define SITAR_A_CDC_DMIC_CLK1_MODE__POR (0x00000000) +#define SITAR_A_CDC_TX_I2S_SD0_MODE (0xC8) +#define SITAR_A_CDC_TX_I2S_SD0_MODE__POR (0x00000000) +#define SITAR_A_CDC_INTR_MODE (0xC9) +#define SITAR_A_CDC_INTR_MODE__POR (0x00000000) +#define SITAR_A_CDC_RX_I2S_SD0_MODE (0xCA) +#define SITAR_A_CDC_RX_I2S_SD0_MODE__POR (0x00000000) +#define SITAR_A_CDC_RX_I2S_SD1_MODE (0xCB) +#define SITAR_A_CDC_RX_I2S_SD1_MODE__POR (0x00000000) +#define SITAR_A_BIAS_REF_CTL (0x100) +#define SITAR_A_BIAS_REF_CTL__POR (0x0000001c) +#define SITAR_A_BIAS_CENTRAL_BG_CTL (0x101) +#define SITAR_A_BIAS_CENTRAL_BG_CTL__POR (0x00000050) +#define SITAR_A_BIAS_PRECHRG_CTL (0x102) +#define SITAR_A_BIAS_PRECHRG_CTL__POR (0x00000007) +#define SITAR_A_BIAS_CURR_CTL_1 (0x103) +#define SITAR_A_BIAS_CURR_CTL_1__POR (0x00000052) +#define SITAR_A_BIAS_CURR_CTL_2 (0x104) +#define SITAR_A_BIAS_CURR_CTL_2__POR (0x00000000) +#define SITAR_A_BIAS_OSC_BG_CTL (0x105) +#define SITAR_A_BIAS_OSC_BG_CTL__POR (0x00000016) +#define SITAR_A_CLK_BUFF_EN1 (0x108) +#define SITAR_A_CLK_BUFF_EN1__POR (0x00000004) +#define SITAR_A_CLK_BUFF_EN2 (0x109) +#define SITAR_A_CLK_BUFF_EN2__POR (0x00000002) +#define SITAR_A_LDO_H_MODE_1 (0x110) +#define SITAR_A_LDO_H_MODE_1__POR (0x00000065) +#define SITAR_A_LDO_H_MODE_2 (0x111) +#define SITAR_A_LDO_H_MODE_2__POR (0x000000a8) +#define SITAR_A_LDO_H_LOOP_CTL (0x112) +#define SITAR_A_LDO_H_LOOP_CTL__POR (0x0000006b) +#define SITAR_A_LDO_H_COMP_1 (0x113) +#define SITAR_A_LDO_H_COMP_1__POR (0x00000084) +#define SITAR_A_LDO_H_COMP_2 (0x114) +#define SITAR_A_LDO_H_COMP_2__POR (0x000000e0) +#define SITAR_A_LDO_H_BIAS_1 (0x115) +#define SITAR_A_LDO_H_BIAS_1__POR (0x0000006d) +#define SITAR_A_LDO_H_BIAS_2 (0x116) +#define SITAR_A_LDO_H_BIAS_2__POR (0x000000a5) +#define SITAR_A_LDO_H_BIAS_3 (0x117) +#define SITAR_A_LDO_H_BIAS_3__POR (0x00000060) +#define SITAR_A_MICB_CFILT_1_CTL (0x128) +#define SITAR_A_MICB_CFILT_1_CTL__POR (0x00000040) +#define SITAR_A_MICB_CFILT_1_VAL (0x129) +#define SITAR_A_MICB_CFILT_1_VAL__POR (0x00000080) +#define SITAR_A_MICB_CFILT_1_PRECHRG (0x12A) +#define SITAR_A_MICB_CFILT_1_PRECHRG__POR (0x00000038) +#define SITAR_A_MICB_1_CTL (0x12B) +#define SITAR_A_MICB_1_CTL__POR (0x00000016) +#define SITAR_A_MICB_1_INT_RBIAS (0x12C) +#define SITAR_A_MICB_1_INT_RBIAS__POR (0x00000024) +#define SITAR_A_MICB_1_MBHC (0x12D) +#define SITAR_A_MICB_1_MBHC__POR (0x00000001) +#define SITAR_A_MICB_CFILT_2_CTL (0x12E) +#define SITAR_A_MICB_CFILT_2_CTL__POR (0x00000040) +#define SITAR_A_MICB_CFILT_2_VAL (0x12F) +#define SITAR_A_MICB_CFILT_2_VAL__POR (0x00000080) +#define SITAR_A_MICB_CFILT_2_PRECHRG (0x130) +#define SITAR_A_MICB_CFILT_2_PRECHRG__POR (0x00000038) +#define SITAR_A_MICB_2_CTL (0x131) +#define SITAR_A_MICB_2_CTL__POR (0x00000016) +#define SITAR_A_MICB_2_INT_RBIAS (0x132) +#define SITAR_A_MICB_2_INT_RBIAS__POR (0x00000024) +#define SITAR_A_MICB_2_MBHC (0x133) +#define SITAR_A_MICB_2_MBHC__POR (0x00000002) +#define SITAR_A_TX_COM_BIAS (0x14C) +#define SITAR_A_TX_COM_BIAS__POR (0x000000e0) +#define SITAR_A_MBHC_SCALING_MUX_1 (0x14E) +#define SITAR_A_MBHC_SCALING_MUX_1__POR (0x00000000) +#define SITAR_A_MBHC_SCALING_MUX_2 (0x14F) +#define SITAR_A_MBHC_SCALING_MUX_2__POR (0x00000080) +#define SITAR_A_TX_SUP_SWITCH_CTRL_1 (0x151) +#define SITAR_A_TX_SUP_SWITCH_CTRL_1__POR (0x00000000) +#define SITAR_A_TX_SUP_SWITCH_CTRL_2 (0x152) +#define SITAR_A_TX_SUP_SWITCH_CTRL_2__POR (0x00000080) +#define SITAR_A_TX_1_2_EN (0x153) +#define SITAR_A_TX_1_2_EN__POR (0x00000000) +#define SITAR_A_TX_1_2_TEST_EN (0x154) +#define SITAR_A_TX_1_2_TEST_EN__POR (0x000000cc) +#define SITAR_A_TX_1_2_ADC_CH1 (0x155) +#define SITAR_A_TX_1_2_ADC_CH1__POR (0x00000044) +#define SITAR_A_TX_1_2_ADC_CH2 (0x156) +#define SITAR_A_TX_1_2_ADC_CH2__POR (0x00000044) +#define SITAR_A_TX_1_2_ATEST_REFCTRL (0x157) +#define SITAR_A_TX_1_2_ATEST_REFCTRL__POR (0x00000000) +#define SITAR_A_TX_1_2_TEST_CTL (0x158) +#define SITAR_A_TX_1_2_TEST_CTL__POR (0x00000038) +#define SITAR_A_TX_1_2_TEST_BLOCK_EN (0x159) +#define SITAR_A_TX_1_2_TEST_BLOCK_EN__POR (0x000000fc) +#define SITAR_A_TX_1_2_TXFE_CLKDIV (0x15A) +#define SITAR_A_TX_1_2_TXFE_CLKDIV__POR (0x000000ee) +#define SITAR_A_TX_1_2_SAR_ERR_CH1 (0x15B) +#define SITAR_A_TX_1_2_SAR_ERR_CH1__POR (0x00000000) +#define SITAR_A_TX_1_2_SAR_ERR_CH2 (0x15C) +#define SITAR_A_TX_1_2_SAR_ERR_CH2__POR (0x00000000) +#define SITAR_A_TX_3_EN (0x15D) +#define SITAR_A_TX_3_EN__POR (0x00000000) +#define SITAR_A_TX_3_TEST_EN (0x15E) +#define SITAR_A_TX_3_TEST_EN__POR (0x000000cc) +#define SITAR_A_TX_3_ADC (0x15F) +#define SITAR_A_TX_3_ADC__POR (0x00000044) +#define SITAR_A_TX_3_MBHC_ATEST_REFCTRL (0x161) +#define SITAR_A_TX_3_MBHC_ATEST_REFCTRL__POR (0x00000000) +#define SITAR_A_TX_3_TEST_CTL (0x162) +#define SITAR_A_TX_3_TEST_CTL__POR (0x00000038) +#define SITAR_A_TX_3_TEST_BLOCK_EN (0x163) +#define SITAR_A_TX_3_TEST_BLOCK_EN__POR (0x000000fc) +#define SITAR_A_TX_3_TXFE_CKDIV (0x164) +#define SITAR_A_TX_3_TXFE_CKDIV__POR (0x000000ee) +#define SITAR_A_TX_3_SAR_ERR (0x165) +#define SITAR_A_TX_3_SAR_ERR__POR (0x00000000) +#define SITAR_A_TX_4_MBHC_EN (0x171) +#define SITAR_A_TX_4_MBHC_EN__POR (0x0000000c) +#define SITAR_A_TX_4_MBHC_ADC (0x173) +#define SITAR_A_TX_4_MBHC_ADC__POR (0x00000044) +#define SITAR_A_TX_4_MBHC_TEST_CTL (0x174) +#define SITAR_A_TX_4_MBHC_TEST_CTL__POR (0x00000038) +#define SITAR_A_TX_4_MBHC_SAR_ERR (0x175) +#define SITAR_A_TX_4_MBHC_SAR_ERR__POR (0x00000000) +#define SITAR_A_TX_4_TXFE_CLKDIV (0x176) +#define SITAR_A_TX_4_TXFE_CLKDIV__POR (0x0000001c) +#define SITAR_A_AUX_COM_CTL (0x180) +#define SITAR_A_AUX_COM_CTL__POR (0x00000034) +#define SITAR_A_AUX_COM_ATEST (0x181) +#define SITAR_A_AUX_COM_ATEST__POR (0x00000000) +#define SITAR_A_AUX_L_EN (0x182) +#define SITAR_A_AUX_L_EN__POR (0x00000000) +#define SITAR_A_AUX_L_GAIN (0x183) +#define SITAR_A_AUX_L_GAIN__POR (0x0000001f) +#define SITAR_A_AUX_L_PA_CONN (0x184) +#define SITAR_A_AUX_L_PA_CONN__POR (0x00000000) +#define SITAR_A_AUX_L_PA_CONN_INV (0x185) +#define SITAR_A_AUX_L_PA_CONN_INV__POR (0x00000000) +#define SITAR_A_AUX_R_EN (0x186) +#define SITAR_A_AUX_R_EN__POR (0x00000000) +#define SITAR_A_AUX_R_GAIN (0x187) +#define SITAR_A_AUX_R_GAIN__POR (0x0000001f) +#define SITAR_A_AUX_R_PA_CONN (0x188) +#define SITAR_A_AUX_R_PA_CONN__POR (0x00000000) +#define SITAR_A_AUX_R_PA_CONN_INV (0x189) +#define SITAR_A_AUX_R_PA_CONN_INV__POR (0x00000000) +#define SITAR_A_CP_EN (0x192) +#define SITAR_A_CP_EN__POR (0x000000e6) +#define SITAR_A_CP_CLK (0x193) +#define SITAR_A_CP_CLK__POR (0x00000029) +#define SITAR_A_CP_STATIC (0x194) +#define SITAR_A_CP_STATIC__POR (0x00000010) +#define SITAR_A_CP_DCC1 (0x195) +#define SITAR_A_CP_DCC1__POR (0x00000052) +#define SITAR_A_CP_DCC3 (0x196) +#define SITAR_A_CP_DCC3__POR (0x00000001) +#define SITAR_A_CP_ATEST (0x197) +#define SITAR_A_CP_ATEST__POR (0x00000000) +#define SITAR_A_CP_DTEST (0x198) +#define SITAR_A_CP_DTEST__POR (0x00000000) +#define SITAR_A_RX_COM_TIMER_DIV (0x19E) +#define SITAR_A_RX_COM_TIMER_DIV__POR (0x000000e8) +#define SITAR_A_RX_COM_OCP_CTL (0x19F) +#define SITAR_A_RX_COM_OCP_CTL__POR (0x0000001f) +#define SITAR_A_RX_COM_OCP_COUNT (0x1A0) +#define SITAR_A_RX_COM_OCP_COUNT__POR (0x00000077) +#define SITAR_A_RX_COM_DAC_CTL (0x1A1) +#define SITAR_A_RX_COM_DAC_CTL__POR (0x00000000) +#define SITAR_A_RX_COM_BIAS (0x1A2) +#define SITAR_A_RX_COM_BIAS__POR (0x00000000) +#define SITAR_A_RX_HPH_BIAS_PA (0x1A6) +#define SITAR_A_RX_HPH_BIAS_PA__POR (0x00000057) +#define SITAR_A_RX_HPH_BIAS_LDO (0x1A7) +#define SITAR_A_RX_HPH_BIAS_LDO__POR (0x00000056) +#define SITAR_A_RX_HPH_BIAS_CNP (0x1A8) +#define SITAR_A_RX_HPH_BIAS_CNP__POR (0x0000008a) +#define SITAR_A_RX_HPH_BIAS_WG (0x1A9) +#define SITAR_A_RX_HPH_BIAS_WG__POR (0x00000060) +#define SITAR_A_RX_HPH_OCP_CTL (0x1AA) +#define SITAR_A_RX_HPH_OCP_CTL__POR (0x000000e8) +#define SITAR_A_RX_HPH_CNP_EN (0x1AB) +#define SITAR_A_RX_HPH_CNP_EN__POR (0x00000080) +#define SITAR_A_RX_HPH_CNP_WG_CTL (0x1AC) +#define SITAR_A_RX_HPH_CNP_WG_CTL__POR (0x000000dc) +#define SITAR_A_RX_HPH_CNP_WG_TIME (0x1AD) +#define SITAR_A_RX_HPH_CNP_WG_TIME__POR (0x00000028) +#define SITAR_A_RX_HPH_L_GAIN (0x1AE) +#define SITAR_A_RX_HPH_L_GAIN__POR (0x00000000) +#define SITAR_A_RX_HPH_L_TEST (0x1AF) +#define SITAR_A_RX_HPH_L_TEST__POR (0x00000001) +#define SITAR_A_RX_HPH_L_PA_CTL (0x1B0) +#define SITAR_A_RX_HPH_L_PA_CTL__POR (0x00000040) +#define SITAR_A_RX_HPH_L_DAC_CTL (0x1B1) +#define SITAR_A_RX_HPH_L_DAC_CTL__POR (0x00000000) +#define SITAR_A_RX_HPH_L_ATEST (0x1B2) +#define SITAR_A_RX_HPH_L_ATEST__POR (0x00000000) +#define SITAR_A_RX_HPH_L_STATUS (0x1B3) +#define SITAR_A_RX_HPH_L_STATUS__POR (0x00000004) +#define SITAR_A_RX_HPH_R_GAIN (0x1B4) +#define SITAR_A_RX_HPH_R_GAIN__POR (0x00000000) +#define SITAR_A_RX_HPH_R_TEST (0x1B5) +#define SITAR_A_RX_HPH_R_TEST__POR (0x00000001) +#define SITAR_A_RX_HPH_R_PA_CTL (0x1B6) +#define SITAR_A_RX_HPH_R_PA_CTL__POR (0x00000040) +#define SITAR_A_RX_HPH_R_DAC_CTL (0x1B7) +#define SITAR_A_RX_HPH_R_DAC_CTL__POR (0x00000000) +#define SITAR_A_RX_HPH_R_ATEST (0x1B8) +#define SITAR_A_RX_HPH_R_ATEST__POR (0x00000000) +#define SITAR_A_RX_HPH_R_STATUS (0x1B9) +#define SITAR_A_RX_HPH_R_STATUS__POR (0x00000004) +#define SITAR_A_RX_EAR_BIAS_PA (0x1BA) +#define SITAR_A_RX_EAR_BIAS_PA__POR (0x000000a6) +#define SITAR_A_RX_EAR_BIAS_CMBUFF (0x1BB) +#define SITAR_A_RX_EAR_BIAS_CMBUFF__POR (0x000000a0) +#define SITAR_A_RX_EAR_EN (0x1BC) +#define SITAR_A_RX_EAR_EN__POR (0x00000000) +#define SITAR_A_RX_EAR_GAIN (0x1BD) +#define SITAR_A_RX_EAR_GAIN__POR (0x00000002) +#define SITAR_A_RX_EAR_CMBUFF (0x1BE) +#define SITAR_A_RX_EAR_CMBUFF__POR (0x00000004) +#define SITAR_A_RX_EAR_ICTL (0x1BF) +#define SITAR_A_RX_EAR_ICTL__POR (0x00000040) +#define SITAR_A_RX_EAR_CCOMP (0x1C0) +#define SITAR_A_RX_EAR_CCOMP__POR (0x00000008) +#define SITAR_A_RX_EAR_VCM (0x1C1) +#define SITAR_A_RX_EAR_VCM__POR (0x00000003) +#define SITAR_A_RX_EAR_CNP (0x1C2) +#define SITAR_A_RX_EAR_CNP__POR (0x000000f2) +#define SITAR_A_RX_EAR_ATEST (0x1C3) +#define SITAR_A_RX_EAR_ATEST__POR (0x00000000) +#define SITAR_A_RX_EAR_STATUS (0x1C5) +#define SITAR_A_RX_EAR_STATUS__POR (0x00000004) +#define SITAR_A_RX_LINE_BIAS_PA (0x1C6) +#define SITAR_A_RX_LINE_BIAS_PA__POR (0x000000aa) +#define SITAR_A_RX_LINE_BIAS_LDO (0x1C7) +#define SITAR_A_RX_LINE_BIAS_LDO__POR (0x00000086) +#define SITAR_A_RX_LINE_BIAS_CNP1 (0x1C8) +#define SITAR_A_RX_LINE_BIAS_CNP1__POR (0x00000060) +#define SITAR_A_RX_LINE_COM (0x1C9) +#define SITAR_A_RX_LINE_COM__POR (0x00000000) +#define SITAR_A_RX_LINE_CNP_EN (0x1CA) +#define SITAR_A_RX_LINE_CNP_EN__POR (0x00000080) +#define SITAR_A_RX_LINE_CNP_WG_CTL (0x1CB) +#define SITAR_A_RX_LINE_CNP_WG_CTL__POR (0x000000dc) +#define SITAR_A_RX_LINE_CNP_WG_TIME (0x1CC) +#define SITAR_A_RX_LINE_CNP_WG_TIME__POR (0x00000028) +#define SITAR_A_RX_LINE_1_GAIN (0x1CD) +#define SITAR_A_RX_LINE_1_GAIN__POR (0x00000000) +#define SITAR_A_RX_LINE_1_TEST (0x1CE) +#define SITAR_A_RX_LINE_1_TEST__POR (0x00000001) +#define SITAR_A_RX_LINE_1_DAC_CTL (0x1CF) +#define SITAR_A_RX_LINE_1_DAC_CTL__POR (0x00000000) +#define SITAR_A_RX_LINE_1_STATUS (0x1D0) +#define SITAR_A_RX_LINE_1_STATUS__POR (0x00000004) +#define SITAR_A_RX_LINE_2_GAIN (0x1D1) +#define SITAR_A_RX_LINE_2_GAIN__POR (0x00000000) +#define SITAR_A_RX_LINE_2_TEST (0x1D2) +#define SITAR_A_RX_LINE_2_TEST__POR (0x00000001) +#define SITAR_A_RX_LINE_2_DAC_CTL (0x1D3) +#define SITAR_A_RX_LINE_2_DAC_CTL__POR (0x00000000) +#define SITAR_A_RX_LINE_2_STATUS (0x1D4) +#define SITAR_A_RX_LINE_2_STATUS__POR (0x00000004) +#define SITAR_A_RX_LINE_BIAS_CNP2 (0x1E1) +#define SITAR_A_RX_LINE_BIAS_CNP2__POR (0x0000008a) +#define SITAR_A_RX_LINE_OCP_CTL (0x1E2) +#define SITAR_A_RX_LINE_OCP_CTL__POR (0x000000e8) +#define SITAR_A_RX_LINE_1_PA_CTL (0x1E3) +#define SITAR_A_RX_LINE_1_PA_CTL__POR (0x00000040) +#define SITAR_A_RX_LINE_2_PA_CTL (0x1E4) +#define SITAR_A_RX_LINE_2_PA_CTL__POR (0x00000040) +#define SITAR_A_RX_LINE_CNP_DBG (0x1EC) +#define SITAR_A_RX_LINE_CNP_DBG__POR (0x00000000) +#define SITAR_A_MBHC_HPH (0x1ED) +#define SITAR_A_MBHC_HPH__POR (0x00000048) +#define SITAR_A_RC_OSC_FREQ (0x1F7) +#define SITAR_A_RC_OSC_FREQ__POR (0x00000046) +#define SITAR_A_RC_OSC_TEST (0x1F8) +#define SITAR_A_RC_OSC_TEST__POR (0x0000000a) +#define SITAR_A_RC_OSC_STATUS (0x1F9) +#define SITAR_A_RC_OSC_STATUS__POR (0x0000001c) +#define SITAR_A_RC_OSC_TUNER (0x1FA) +#define SITAR_A_RC_OSC_TUNER__POR (0x00000000) +#define SITAR_A_CDC_ANC1_CTL (0x200) +#define SITAR_A_CDC_ANC1_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_SHIFT (0x201) +#define SITAR_A_CDC_ANC1_SHIFT__POR (0x00000000) +#define SITAR_A_CDC_ANC1_IIR_B1_CTL (0x202) +#define SITAR_A_CDC_ANC1_IIR_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_IIR_B2_CTL (0x203) +#define SITAR_A_CDC_ANC1_IIR_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_IIR_B3_CTL (0x204) +#define SITAR_A_CDC_ANC1_IIR_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_IIR_B4_CTL (0x205) +#define SITAR_A_CDC_ANC1_IIR_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_LPF_B1_CTL (0x206) +#define SITAR_A_CDC_ANC1_LPF_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_LPF_B2_CTL (0x207) +#define SITAR_A_CDC_ANC1_LPF_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_LPF_B3_CTL (0x208) +#define SITAR_A_CDC_ANC1_LPF_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_SPARE (0x209) +#define SITAR_A_CDC_ANC1_SPARE__POR (0x00000000) +#define SITAR_A_CDC_ANC1_SMLPF_CTL (0x20A) +#define SITAR_A_CDC_ANC1_SMLPF_CTL__POR (0x00000000) +#define SITAR_A_CDC_ANC1_DCFLT_CTL (0x20B) +#define SITAR_A_CDC_ANC1_DCFLT_CTL__POR (0x00000000) +#define SITAR_A_CDC_TX1_VOL_CTL_TIMER (0x220) +#define SITAR_A_CDC_TX1_VOL_CTL_TIMER__POR (0x00000000) +#define SITAR_A_CDC_TX1_VOL_CTL_GAIN (0x221) +#define SITAR_A_CDC_TX1_VOL_CTL_GAIN__POR (0x00000000) +#define SITAR_A_CDC_TX2_VOL_CTL_GAIN (0x229) +#define SITAR_A_CDC_TX2_VOL_CTL_GAIN__POR (0x00000000) +#define SITAR_A_CDC_TX3_VOL_CTL_GAIN (0x231) +#define SITAR_A_CDC_TX3_VOL_CTL_GAIN__POR (0x00000000) +#define SITAR_A_CDC_TX4_VOL_CTL_GAIN (0x239) +#define SITAR_A_CDC_TX4_VOL_CTL_GAIN__POR (0x00000000) +#define SITAR_A_CDC_TX5_VOL_CTL_GAIN (0x241) +#define SITAR_A_CDC_TX5_VOL_CTL_GAIN__POR (0x00000000) +#define SITAR_A_CDC_TX1_VOL_CTL_CFG (0x222) +#define SITAR_A_CDC_TX1_VOL_CTL_CFG__POR (0x00000000) +#define SITAR_A_CDC_TX2_VOL_CTL_CFG (0x22A) +#define SITAR_A_CDC_TX2_VOL_CTL_CFG__POR (0x00000000) +#define SITAR_A_CDC_TX3_VOL_CTL_CFG (0x232) +#define SITAR_A_CDC_TX3_VOL_CTL_CFG__POR (0x00000000) +#define SITAR_A_CDC_TX4_VOL_CTL_CFG (0x23A) +#define SITAR_A_CDC_TX4_VOL_CTL_CFG__POR (0x00000000) + +#define SITAR_A_CDC_TX1_MUX_CTL (0x223) +#define SITAR_A_CDC_TX1_MUX_CTL__POR (0x00000008) +#define SITAR_A_CDC_TX1_CLK_FS_CTL (0x00000224) +#define SITAR_A_CDC_TX1_CLK_FS_CTL__POR (0x00000003) +#define SITAR_A_CDC_TX2_CLK_FS_CTL (0x0000022C) +#define SITAR_A_CDC_TX2_CLK_FS_CTL__POR (0x00000003) +#define SITAR_A_CDC_TX3_CLK_FS_CTL (0x00000234) +#define SITAR_A_CDC_TX3_CLK_FS_CTL__POR (0x00000003) +#define SITAR_A_CDC_TX4_CLK_FS_CTL (0x0000023C) +#define SITAR_A_CDC_TX4_CLK_FS_CTL__POR (0x00000003) +#define SITAR_A_CDC_TX1_DMIC_CTL (0x225) +#define SITAR_A_CDC_TX1_DMIC_CTL__POR (0x00000000) +#define SITAR_A_CDC_TX2_MUX_CTL (0x22B) +#define SITAR_A_CDC_TX2_MUX_CTL__POR (0x00000008) +#define SITAR_A_CDC_TX3_MUX_CTL (0x233) +#define SITAR_A_CDC_TX3_MUX_CTL__POR (0x00000008) +#define SITAR_A_CDC_TX4_MUX_CTL (0x23B) +#define SITAR_A_CDC_TX4_MUX_CTL__POR (0x00000008) +#define SITAR_A_CDC_TX5_MUX_CTL (0x243) +#define SITAR_A_CDC_TX5_MUX_CTL__POR (0x00000008) + +#define SITAR_A_CDC_SRC1_PDA_CFG (0x2A0) +#define SITAR_A_CDC_SRC1_PDA_CFG__POR (0x00000000) +#define SITAR_A_CDC_SRC1_FS_CTL (0x2A1) +#define SITAR_A_CDC_SRC1_FS_CTL__POR (0x0000001b) + +#define SITAR_A_CDC_RX1_B1_CTL (0x000002B0) +#define SITAR_A_CDC_RX1_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX2_B1_CTL (0x000002B8) +#define SITAR_A_CDC_RX2_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX3_B1_CTL (0x000002C0) +#define SITAR_A_CDC_RX3_B1_CTL__POR (0x00000000) + +#define SITAR_A_CDC_RX1_B2_CTL (0x000002B1) +#define SITAR_A_CDC_RX1_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX2_B2_CTL (0x000002B9) +#define SITAR_A_CDC_RX2_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX3_B2_CTL (0x000002C1) +#define SITAR_A_CDC_RX3_B2_CTL__POR (0x00000000) + +#define SITAR_A_CDC_RX1_B3_CTL (0x000002B2) +#define SITAR_A_CDC_RX1_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX2_B3_CTL (0x000002BA) +#define SITAR_A_CDC_RX2_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX3_B3_CTL (0x000002C2) +#define SITAR_A_CDC_RX3_B3_CTL__POR (0x00000000) + +#define SITAR_A_CDC_RX1_B4_CTL (0x000002B3) +#define SITAR_A_CDC_RX1_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX2_B4_CTL (0x000002BB) +#define SITAR_A_CDC_RX2_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX3_B4_CTL (0x000002C3) +#define SITAR_A_CDC_RX3_B4_CTL__POR (0x00000000) + +#define SITAR_A_CDC_RX1_B5_CTL (0x000002B4) +#define SITAR_A_CDC_RX1_B5_CTL__POR (0x00000078) +#define SITAR_A_CDC_RX2_B5_CTL (0x000002BC) +#define SITAR_A_CDC_RX2_B5_CTL__POR (0x00000078) +#define SITAR_A_CDC_RX3_B5_CTL (0x000002C4) +#define SITAR_A_CDC_RX3_B5_CTL__POR (0x00000078) + +#define SITAR_A_CDC_RX1_B6_CTL (0x000002B5) +#define SITAR_A_CDC_RX1_B6_CTL__POR (0x00000080) +#define SITAR_A_CDC_RX2_B6_CTL (0x000002BD) +#define SITAR_A_CDC_RX2_B6_CTL__POR (0x00000080) +#define SITAR_A_CDC_RX3_B6_CTL (0x000002C5) +#define SITAR_A_CDC_RX3_B6_CTL__POR (0x00000080) + + +#define SITAR_A_CDC_RX1_VOL_CTL_B1_CTL (0x2B6) +#define SITAR_A_CDC_RX1_VOL_CTL_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX1_VOL_CTL_B2_CTL (0x2B7) +#define SITAR_A_CDC_RX1_VOL_CTL_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX2_VOL_CTL_B2_CTL (0x2BF) +#define SITAR_A_CDC_RX2_VOL_CTL_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_RX3_VOL_CTL_B2_CTL (0x2C7) +#define SITAR_A_CDC_RX3_VOL_CTL_B2_CTL__POR (0x00000000) + +#define SITAR_A_CDC_CLK_ANC_RESET_CTL (0x300) +#define SITAR_A_CDC_CLK_ANC_RESET_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_RX_RESET_CTL (0x301) +#define SITAR_A_CDC_CLK_RX_RESET_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_TX_RESET_B1_CTL (0x302) +#define SITAR_A_CDC_CLK_TX_RESET_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_TX_RESET_B2_CTL (0x303) +#define SITAR_A_CDC_CLK_TX_RESET_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_DMIC_CTL (0x304) +#define SITAR_A_CDC_CLK_DMIC_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_RX_I2S_CTL (0x305) +#define SITAR_A_CDC_CLK_RX_I2S_CTL__POR (0x00000003) +#define SITAR_A_CDC_CLK_TX_I2S_CTL (0x306) +#define SITAR_A_CDC_CLK_TX_I2S_CTL__POR (0x00000003) +#define SITAR_A_CDC_CLK_OTHR_RESET_CTL (0x307) +#define SITAR_A_CDC_CLK_OTHR_RESET_CTL__POR (0x00000010) +#define SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL (0x308) +#define SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_OTHR_CTL (0x30A) +#define SITAR_A_CDC_CLK_OTHR_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL (0x30B) +#define SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_ANC_CLK_EN_CTL (0x30C) +#define SITAR_A_CDC_CLK_ANC_CLK_EN_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_RX_B1_CTL (0x30D) +#define SITAR_A_CDC_CLK_RX_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_RX_B2_CTL (0x30E) +#define SITAR_A_CDC_CLK_RX_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_MCLK_CTL (0x30F) +#define SITAR_A_CDC_CLK_MCLK_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_PDM_CTL (0x310) +#define SITAR_A_CDC_CLK_PDM_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_SD_CTL (0x311) +#define SITAR_A_CDC_CLK_SD_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLK_LP_CTL (0x312) +#define SITAR_A_CDC_CLK_LP_CTL__POR (0x00000000) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL (0x320) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL__POR (0x00000007) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL (0x321) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL__POR (0x00000013) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL (0x322) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL__POR (0x0000001b) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL (0x323) +#define SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL__POR (0x0000007f) +#define SITAR_A_CDC_CLSG_GAIN_THRESH_CTL (0x324) +#define SITAR_A_CDC_CLSG_GAIN_THRESH_CTL__POR (0x00000026) +#define SITAR_A_CDC_CLSG_TIMER_B1_CFG (0x325) +#define SITAR_A_CDC_CLSG_TIMER_B1_CFG__POR (0x0000000a) +#define SITAR_A_CDC_CLSG_TIMER_B2_CFG (0x326) +#define SITAR_A_CDC_CLSG_TIMER_B2_CFG__POR (0x00000000) +#define SITAR_A_CDC_CLSG_CTL (0x327) +#define SITAR_A_CDC_CLSG_CTL__POR (0x00000013) +#define SITAR_A_CDC_IIR1_GAIN_B1_CTL (0x340) +#define SITAR_A_CDC_IIR1_GAIN_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B2_CTL (0x341) +#define SITAR_A_CDC_IIR1_GAIN_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B3_CTL (0x342) +#define SITAR_A_CDC_IIR1_GAIN_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B4_CTL (0x343) +#define SITAR_A_CDC_IIR1_GAIN_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B5_CTL (0x344) +#define SITAR_A_CDC_IIR1_GAIN_B5_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B6_CTL (0x345) +#define SITAR_A_CDC_IIR1_GAIN_B6_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B7_CTL (0x346) +#define SITAR_A_CDC_IIR1_GAIN_B7_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_GAIN_B8_CTL (0x347) +#define SITAR_A_CDC_IIR1_GAIN_B8_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_CTL (0x348) +#define SITAR_A_CDC_IIR1_CTL__POR (0x00000040) +#define SITAR_A_CDC_IIR1_GAIN_TIMER_CTL (0x349) +#define SITAR_A_CDC_IIR1_GAIN_TIMER_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_COEF_B1_CTL (0x34A) +#define SITAR_A_CDC_IIR1_COEF_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_COEF_B2_CTL (0x34B) +#define SITAR_A_CDC_IIR1_COEF_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_COEF_B3_CTL (0x34C) +#define SITAR_A_CDC_IIR1_COEF_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_COEF_B4_CTL (0x34D) +#define SITAR_A_CDC_IIR1_COEF_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_IIR1_COEF_B5_CTL (0x34E) +#define SITAR_A_CDC_IIR1_COEF_B5_CTL__POR (0x00000000) +#define SITAR_A_CDC_TOP_GAIN_UPDATE (0x360) +#define SITAR_A_CDC_TOP_GAIN_UPDATE__POR (0x00000000) +#define SITAR_A_CDC_TOP_RDAC_DOUT_CTL (0x361) +#define SITAR_A_CDC_TOP_RDAC_DOUT_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B1_CTL (0x368) +#define SITAR_A_CDC_DEBUG_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B2_CTL (0x369) +#define SITAR_A_CDC_DEBUG_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B3_CTL (0x36A) +#define SITAR_A_CDC_DEBUG_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B4_CTL (0x36B) +#define SITAR_A_CDC_DEBUG_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B5_CTL (0x36C) +#define SITAR_A_CDC_DEBUG_B5_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B6_CTL (0x36D) +#define SITAR_A_CDC_DEBUG_B6_CTL__POR (0x00000000) +#define SITAR_A_CDC_DEBUG_B7_CTL (0x36E) +#define SITAR_A_CDC_DEBUG_B7_CTL__POR (0x00000000) +#define SITAR_A_CDC_COMP1_B1_CTL (0x370) +#define SITAR_A_CDC_COMP1_B1_CTL__POR (0x00000030) +#define SITAR_A_CDC_COMP1_B2_CTL (0x371) +#define SITAR_A_CDC_COMP1_B2_CTL__POR (0x000000b5) +#define SITAR_A_CDC_COMP1_B3_CTL (0x372) +#define SITAR_A_CDC_COMP1_B3_CTL__POR (0x00000028) +#define SITAR_A_CDC_COMP1_B4_CTL (0x373) +#define SITAR_A_CDC_COMP1_B4_CTL__POR (0x0000003c) +#define SITAR_A_CDC_COMP1_B5_CTL (0x374) +#define SITAR_A_CDC_COMP1_B5_CTL__POR (0x0000001f) +#define SITAR_A_CDC_COMP1_B6_CTL (0x375) +#define SITAR_A_CDC_COMP1_B6_CTL__POR (0x00000000) +#define SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS (0x376) +#define SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS__POR (0x00000003) +#define SITAR_A_CDC_COMP1_FS_CFG (0x377) +#define SITAR_A_CDC_COMP1_FS_CFG__POR (0x0000001b) +#define SITAR_A_CDC_CONN_RX1_B1_CTL (0x380) +#define SITAR_A_CDC_CONN_RX1_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX1_B2_CTL (0x381) +#define SITAR_A_CDC_CONN_RX1_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX1_B3_CTL (0x382) +#define SITAR_A_CDC_CONN_RX1_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX2_B1_CTL (0x383) +#define SITAR_A_CDC_CONN_RX2_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX2_B2_CTL (0x384) +#define SITAR_A_CDC_CONN_RX2_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX2_B3_CTL (0x385) +#define SITAR_A_CDC_CONN_RX2_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX3_B1_CTL (0x386) +#define SITAR_A_CDC_CONN_RX3_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX3_B2_CTL (0x387) +#define SITAR_A_CDC_CONN_RX3_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX3_B3_CTL (0x388) +#define SITAR_A_CDC_CONN_RX3_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_ANC_B1_CTL (0x391) +#define SITAR_A_CDC_CONN_ANC_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_ANC_B2_CTL (0x392) +#define SITAR_A_CDC_CONN_ANC_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_B1_CTL (0x393) +#define SITAR_A_CDC_CONN_TX_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_B2_CTL (0x394) +#define SITAR_A_CDC_CONN_TX_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ1_B1_CTL (0x397) +#define SITAR_A_CDC_CONN_EQ1_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ1_B2_CTL (0x398) +#define SITAR_A_CDC_CONN_EQ1_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ1_B3_CTL (0x399) +#define SITAR_A_CDC_CONN_EQ1_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ1_B4_CTL (0x39A) +#define SITAR_A_CDC_CONN_EQ1_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ2_B1_CTL (0x39B) +#define SITAR_A_CDC_CONN_EQ2_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ2_B2_CTL (0x39C) +#define SITAR_A_CDC_CONN_EQ2_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ2_B3_CTL (0x39D) +#define SITAR_A_CDC_CONN_EQ2_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_EQ2_B4_CTL (0x39E) +#define SITAR_A_CDC_CONN_EQ2_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_SRC1_B1_CTL (0x39F) +#define SITAR_A_CDC_CONN_SRC1_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_SRC1_B2_CTL (0x3A0) +#define SITAR_A_CDC_CONN_SRC1_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_SRC2_B1_CTL (0x3A1) +#define SITAR_A_CDC_CONN_SRC2_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_SRC2_B2_CTL (0x3A2) +#define SITAR_A_CDC_CONN_SRC2_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_SB_B1_CTL (0x3A3) +#define SITAR_A_CDC_CONN_TX_SB_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_SB_B2_CTL (0x3A4) +#define SITAR_A_CDC_CONN_TX_SB_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_SB_B3_CTL (0x3A5) +#define SITAR_A_CDC_CONN_TX_SB_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_SB_B4_CTL (0x3A6) +#define SITAR_A_CDC_CONN_TX_SB_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_TX_SB_B5_CTL (0x3A7) +#define SITAR_A_CDC_CONN_TX_SB_B5_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX_SB_B1_CTL (0x3AE) +#define SITAR_A_CDC_CONN_RX_SB_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_RX_SB_B2_CTL (0x3AF) +#define SITAR_A_CDC_CONN_RX_SB_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_CLSG_CTL (0x3B0) +#define SITAR_A_CDC_CONN_CLSG_CTL__POR (0x00000000) +#define SITAR_A_CDC_CONN_SPARE (0x3B1) +#define SITAR_A_CDC_CONN_SPARE__POR (0x00000000) +#define SITAR_A_CDC_MBHC_EN_CTL (0x3C0) +#define SITAR_A_CDC_MBHC_EN_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_FIR_B1_CFG (0x3C1) +#define SITAR_A_CDC_MBHC_FIR_B1_CFG__POR (0x00000000) +#define SITAR_A_CDC_MBHC_FIR_B2_CFG (0x3C2) +#define SITAR_A_CDC_MBHC_FIR_B2_CFG__POR (0x00000006) +#define SITAR_A_CDC_MBHC_TIMER_B1_CTL (0x3C3) +#define SITAR_A_CDC_MBHC_TIMER_B1_CTL__POR (0x00000003) +#define SITAR_A_CDC_MBHC_TIMER_B2_CTL (0x3C4) +#define SITAR_A_CDC_MBHC_TIMER_B2_CTL__POR (0x00000009) +#define SITAR_A_CDC_MBHC_TIMER_B3_CTL (0x3C5) +#define SITAR_A_CDC_MBHC_TIMER_B3_CTL__POR (0x0000001e) +#define SITAR_A_CDC_MBHC_TIMER_B4_CTL (0x3C6) +#define SITAR_A_CDC_MBHC_TIMER_B4_CTL__POR (0x00000045) +#define SITAR_A_CDC_MBHC_TIMER_B5_CTL (0x3C7) +#define SITAR_A_CDC_MBHC_TIMER_B5_CTL__POR (0x00000004) +#define SITAR_A_CDC_MBHC_TIMER_B6_CTL (0x3C8) +#define SITAR_A_CDC_MBHC_TIMER_B6_CTL__POR (0x00000078) +#define SITAR_A_CDC_MBHC_B1_STATUS (0x3C9) +#define SITAR_A_CDC_MBHC_B1_STATUS__POR (0x00000000) +#define SITAR_A_CDC_MBHC_B2_STATUS (0x3CA) +#define SITAR_A_CDC_MBHC_B2_STATUS__POR (0x00000000) +#define SITAR_A_CDC_MBHC_B3_STATUS (0x3CB) +#define SITAR_A_CDC_MBHC_B3_STATUS__POR (0x00000000) +#define SITAR_A_CDC_MBHC_B4_STATUS (0x3CC) +#define SITAR_A_CDC_MBHC_B4_STATUS__POR (0x00000000) +#define SITAR_A_CDC_MBHC_B5_STATUS (0x3CD) +#define SITAR_A_CDC_MBHC_B5_STATUS__POR (0x00000000) +#define SITAR_A_CDC_MBHC_B1_CTL (0x3CE) +#define SITAR_A_CDC_MBHC_B1_CTL__POR (0x000000c0) +#define SITAR_A_CDC_MBHC_B2_CTL (0x3CF) +#define SITAR_A_CDC_MBHC_B2_CTL__POR (0x0000005d) +#define SITAR_A_CDC_MBHC_VOLT_B1_CTL (0x3D0) +#define SITAR_A_CDC_MBHC_VOLT_B1_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B2_CTL (0x3D1) +#define SITAR_A_CDC_MBHC_VOLT_B2_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B3_CTL (0x3D2) +#define SITAR_A_CDC_MBHC_VOLT_B3_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B4_CTL (0x3D3) +#define SITAR_A_CDC_MBHC_VOLT_B4_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B5_CTL (0x3D4) +#define SITAR_A_CDC_MBHC_VOLT_B5_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B6_CTL (0x3D5) +#define SITAR_A_CDC_MBHC_VOLT_B6_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B7_CTL (0x3D6) +#define SITAR_A_CDC_MBHC_VOLT_B7_CTL__POR (0x000000ff) +#define SITAR_A_CDC_MBHC_VOLT_B8_CTL (0x3D7) +#define SITAR_A_CDC_MBHC_VOLT_B8_CTL__POR (0x00000007) +#define SITAR_A_CDC_MBHC_VOLT_B9_CTL (0x3D8) +#define SITAR_A_CDC_MBHC_VOLT_B9_CTL__POR (0x000000ff) +#define SITAR_A_CDC_MBHC_VOLT_B10_CTL (0x3D9) +#define SITAR_A_CDC_MBHC_VOLT_B10_CTL__POR (0x0000007f) +#define SITAR_A_CDC_MBHC_VOLT_B11_CTL (0x3DA) +#define SITAR_A_CDC_MBHC_VOLT_B11_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_VOLT_B12_CTL (0x3DB) +#define SITAR_A_CDC_MBHC_VOLT_B12_CTL__POR (0x00000080) +#define SITAR_A_CDC_MBHC_CLK_CTL (0x3DC) +#define SITAR_A_CDC_MBHC_CLK_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_INT_CTL (0x3DD) +#define SITAR_A_CDC_MBHC_INT_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_DEBUG_CTL (0x3DE) +#define SITAR_A_CDC_MBHC_DEBUG_CTL__POR (0x00000000) +#define SITAR_A_CDC_MBHC_SPARE (0x3DF) +#define SITAR_A_CDC_MBHC_SPARE__POR (0x00000000) +/* SLIMBUS Slave Registers */ +#define SITAR_SLIM_PGD_PORT_INT_EN0 (0x30) +#define SITAR_SLIM_PGD_PORT_INT_STATUS0 (0x34) +#define SITAR_SLIM_PGD_PORT_INT_CLR0 (0x38) +#define SITAR_SLIM_PGD_PORT_INT_SOURCE0 (0x60) + +/* Macros for Packing Register Writes into a U32 */ +#define SITAR_PACKED_REG_SIZE sizeof(u32) + +#define SITAR_CODEC_PACK_ENTRY(reg, mask, val) ((val & 0xff)|\ + ((mask & 0xff) << 8)|((reg & 0xffff) << 16)) + +#define SITAR_CODEC_UNPACK_ENTRY(packed, reg, mask, val) \ + do { \ + ((reg) = ((packed >> 16) & (0xffff))); \ + ((mask) = ((packed >> 8) & (0xff))); \ + ((val) = ((packed) & (0xff))); \ + } while (0); +#endif diff --git a/include/linux/mfd/wcd9xxx/wcd9310_registers.h b/include/linux/mfd/wcd9xxx/wcd9310_registers.h new file mode 100644 index 0000000000000000000000000000000000000000..67c2a6ba33ee74e1b50e0b7a1fe2b79a99244c89 --- /dev/null +++ b/include/linux/mfd/wcd9xxx/wcd9310_registers.h @@ -0,0 +1,1117 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include + +#define TABLA_A_CHIP_CTL WCD9XXX_A_CHIP_CTL +#define TABLA_A_CHIP_CTL__POR WCD9XXX_A_CHIP_CTL__POR +#define TABLA_A_CHIP_STATUS WCD9XXX_A_CHIP_STATUS +#define TABLA_A_CHIP_STATUS__POR WCD9XXX_A_CHIP_STATUS__POR +#define TABLA_A_CHIP_ID_BYTE_0 WCD9XXX_A_CHIP_ID_BYTE_0 +#define TABLA_A_CHIP_ID_BYTE_0__POR WCD9XXX_A_CHIP_ID_BYTE_0__POR +#define TABLA_A_CHIP_ID_BYTE_1 WCD9XXX_A_CHIP_ID_BYTE_1 +#define TABLA_A_CHIP_ID_BYTE_1__POR WCD9XXX_A_CHIP_ID_BYTE_1__POR +#define TABLA_A_CHIP_ID_BYTE_2 WCD9XXX_A_CHIP_ID_BYTE_2 +#define TABLA_A_CHIP_ID_BYTE_2__POR WCD9XXX_A_CHIP_ID_BYTE_2__POR +#define TABLA_A_CHIP_ID_BYTE_3 WCD9XXX_A_CHIP_ID_BYTE_3 +#define TABLA_A_CHIP_ID_BYTE_3__POR WCD9XXX_A_CHIP_ID_BYTE_3__POR +#define TABLA_A_CHIP_VERSION WCD9XXX_A_CHIP_VERSION +#define TABLA_A_CHIP_VERSION__POR WCD9XXX_A_CHIP_VERSION__POR +#define TABLA_A_SB_VERSION WCD9XXX_A_SB_VERSION +#define TABLA_A_SB_VERSION__POR WCD9XXX_A_SB_VERSION__POR +#define TABLA_A_SLAVE_ID_1 WCD9XXX_A_SLAVE_ID_1 +#define TABLA_A_SLAVE_ID_1__POR WCD9XXX_A_SLAVE_ID_1__POR +#define TABLA_A_SLAVE_ID_2 WCD9XXX_A_SLAVE_ID_2 +#define TABLA_A_SLAVE_ID_2__POR WCD9XXX_A_SLAVE_ID_2__POR +#define TABLA_A_SLAVE_ID_3 WCD9XXX_A_SLAVE_ID_3 +#define TABLA_A_SLAVE_ID_3__POR WCD9XXX_A_SLAVE_ID_3__POR +#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 WCD9XXX_A_CDC_CTL +#define TABLA_A_CDC_CTL__POR WCD9XXX_A_CDC_CTL__POR +#define TABLA_A_LEAKAGE_CTL WCD9XXX_A_LEAKAGE_CTL +#define TABLA_A_LEAKAGE_CTL__POR WCD9XXX_A_LEAKAGE_CTL__POR +#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_1_A_MICB_4_CTL (0x013A) +#define TABLA_2_A_MICB_4_CTL (0x013D) +#define TABLA_A_MICB_4_CTL__POR (0x00000016) +#define TABLA_1_A_MICB_4_INT_RBIAS (0x013B) +#define TABLA_2_A_MICB_4_INT_RBIAS (0x013E) +#define TABLA_A_MICB_4_INT_RBIAS__POR (0x00000000) +#define TABLA_1_A_MICB_4_MBHC (0x013C) +#define TABLA_2_A_MICB_4_MBHC (0x013F) +#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_ANC1_CTL (0x00000200) +#define TABLA_A_CDC_ANC1_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_CTL (0x00000280) +#define TABLA_A_CDC_ANC2_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_SHIFT (0x00000201) +#define TABLA_A_CDC_ANC1_SHIFT__POR (0x00000000) +#define TABLA_A_CDC_ANC2_SHIFT (0x00000281) +#define TABLA_A_CDC_ANC2_SHIFT__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT1_B1_CTL (0x00000202) +#define TABLA_A_CDC_ANC1_FILT1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT1_B1_CTL (0x00000282) +#define TABLA_A_CDC_ANC2_FILT1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT1_B2_CTL (0x00000203) +#define TABLA_A_CDC_ANC1_FILT1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT1_B2_CTL (0x00000283) +#define TABLA_A_CDC_ANC2_FILT1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT1_B3_CTL (0x00000204) +#define TABLA_A_CDC_ANC1_FILT1_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT1_B3_CTL (0x00000284) +#define TABLA_A_CDC_ANC2_FILT1_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT1_B4_CTL (0x00000205) +#define TABLA_A_CDC_ANC1_FILT1_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT1_B4_CTL (0x00000285) +#define TABLA_A_CDC_ANC2_FILT1_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT2_B1_CTL (0x00000206) +#define TABLA_A_CDC_ANC1_FILT2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT2_B1_CTL (0x00000286) +#define TABLA_A_CDC_ANC2_FILT2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT2_B2_CTL (0x00000207) +#define TABLA_A_CDC_ANC1_FILT2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT2_B2_CTL (0x00000287) +#define TABLA_A_CDC_ANC2_FILT2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT2_B3_CTL (0x00000208) +#define TABLA_A_CDC_ANC1_FILT2_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT2_B3_CTL (0x00000288) +#define TABLA_A_CDC_ANC2_FILT2_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_SPARE (0x00000209) +#define TABLA_A_CDC_ANC1_SPARE__POR (0x00000000) +#define TABLA_A_CDC_ANC2_SPARE (0x00000289) +#define TABLA_A_CDC_ANC2_SPARE__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT3_CTL (0x0000020A) +#define TABLA_A_CDC_ANC1_FILT3_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT3_CTL (0x0000028A) +#define TABLA_A_CDC_ANC2_FILT3_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC1_FILT4_CTL (0x0000020B) +#define TABLA_A_CDC_ANC1_FILT4_CTL__POR (0x00000000) +#define TABLA_A_CDC_ANC2_FILT4_CTL (0x0000028B) +#define TABLA_A_CDC_ANC2_FILT4_CTL__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_ANC_RESET_CTL (0x00000300) +#define TABLA_A_CDC_CLK_ANC_RESET_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_ANC_CLK_EN_CTL (0x0000030C) +#define TABLA_A_CDC_CLK_ANC_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_COMP1_B1_CTL (0x00000370) +#define TABLA_A_CDC_COMP1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP1_B2_CTL (0x00000371) +#define TABLA_A_CDC_COMP1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP1_B3_CTL (0x00000372) +#define TABLA_A_CDC_COMP1_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP1_B4_CTL (0x00000373) +#define TABLA_A_CDC_COMP1_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP1_B5_CTL (0x00000374) +#define TABLA_A_CDC_COMP1_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP1_B6_CTL (0x00000375) +#define TABLA_A_CDC_COMP1_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS (0x00000376) +#define TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS__POR (0x00000000) +#define TABLA_A_CDC_COMP1_FS_CFG (0x00000377) +#define TABLA_A_CDC_COMP1_FS_CFG__POR (0x00000000) +#define TABLA_A_CDC_COMP2_B1_CTL (0x00000378) +#define TABLA_A_CDC_COMP2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP2_B2_CTL (0x00000379) +#define TABLA_A_CDC_COMP2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP2_B3_CTL (0x0000037A) +#define TABLA_A_CDC_COMP2_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP2_B4_CTL (0x0000037B) +#define TABLA_A_CDC_COMP2_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP2_B5_CTL (0x0000037C) +#define TABLA_A_CDC_COMP2_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP2_B6_CTL (0x0000037D) +#define TABLA_A_CDC_COMP2_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_COMP2_SHUT_DOWN_STATUS (0x0000037E) +#define TABLA_A_CDC_COMP2_SHUT_DOWN_STATUS__POR (0x00000000) +#define TABLA_A_CDC_COMP2_FS_CFG (0x0000037F) +#define TABLA_A_CDC_COMP2_FS_CFG__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_ANC_B1_CTL (0x00000391) +#define TABLA_A_CDC_CONN_ANC_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_ANC_B2_CTL (0x00000392) +#define TABLA_A_CDC_CONN_ANC_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) + +/* Macros for Packing Register Writes into a U32 */ +#define TABLA_PACKED_REG_SIZE sizeof(u32) + +#define TABLA_CODEC_PACK_ENTRY(reg, mask, val) ((val & 0xff)|\ + ((mask & 0xff) << 8)|((reg & 0xffff) << 16)) + +#define TABLA_CODEC_UNPACK_ENTRY(packed, reg, mask, val) \ + do { \ + ((reg) = ((packed >> 16) & (0xffff))); \ + ((mask) = ((packed >> 8) & (0xff))); \ + ((val) = ((packed) & (0xff))); \ + } while (0); + +#endif diff --git a/include/linux/mfd/wcd9xxx/wcd9xxx-slimslave.h b/include/linux/mfd/wcd9xxx/wcd9xxx-slimslave.h new file mode 100644 index 0000000000000000000000000000000000000000..fcd3bd399c73272a488484ceec0dd5b5ab13a643 --- /dev/null +++ b/include/linux/mfd/wcd9xxx/wcd9xxx-slimslave.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __WCD9310_SLIMSLAVE_H_ +#define __WCD9310_SLIMSLAVE_H_ + +#include +#include + +/* Local to the core only */ +#define SLIM_MAX_RX_PORTS 7 +#define SLIM_MAX_TX_PORTS 10 + +/* Channel numbers to be used for each port */ +enum { + SLIM_TX_1 = 128, + SLIM_TX_2 = 129, + SLIM_TX_3 = 130, + SLIM_TX_4 = 131, + SLIM_TX_5 = 132, + SLIM_TX_6 = 133, + SLIM_TX_7 = 134, + SLIM_TX_8 = 135, + SLIM_TX_9 = 136, + SLIM_TX_10 = 137, + SLIM_RX_1 = 138, + SLIM_RX_2 = 139, + SLIM_RX_3 = 140, + SLIM_RX_4 = 141, + SLIM_RX_5 = 142, + SLIM_RX_6 = 143, + SLIM_RX_7 = 144, + SLIM_MAX = 145 +}; + +/* + * client is expected to give port ids in the range of 1-10 for Tx ports and + * 1-7 for Rx ports, we need to add offset for getting the absolute slave + * port id before configuring the HW + */ +#define SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS 10 +#define SB_PGD_OFFSET_OF_TX_SLAVE_DEV_PORTS -1 +#define SB_PGD_MAX_NUMBER_OF_RX_SLAVE_DEV_PORTS 7 +#define SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS 9 + +/* below details are taken from SLIMBUS slave SWI */ +#define SB_PGD_PORT_BASE 0x000 + +#define SB_PGD_PORT_CFG_BYTE_ADDR(port_num) \ + (SB_PGD_PORT_BASE + 0x040 + 1*port_num) + +#define SB_PGD_TX_PORT_MULTI_CHANNEL_0(port_num) \ + (SB_PGD_PORT_BASE + 0x100 + 4*port_num) +#define SB_PGD_TX_PORT_MULTI_CHANNEL_0_START_PORT_ID 0 +#define SB_PGD_TX_PORT_MULTI_CHANNEL_0_END_PORT_ID 7 + +#define SB_PGD_TX_PORT_MULTI_CHANNEL_1(port_num) \ + (SB_PGD_PORT_BASE + 0x101 + 4*port_num) +#define SB_PGD_TX_PORT_MULTI_CHANNEL_1_START_PORT_ID 8 +#define SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID 9 + +#define SB_PGD_RX_PORT_MULTI_CHANNEL_0(port_num) \ + (SB_PGD_PORT_BASE + 0x180 + 4*port_num) +#define SB_PGD_RX_PORT_MULTI_CHANNEL_0_START_PORT_ID 10 +#define SB_PGD_RX_PORT_MULTI_CHANNEL_0_END_PORT_ID 16 + +/* slave port water mark level + * (0: 6bytes, 1: 9bytes, 2: 12 bytes, 3: 15 bytes) + */ +#define SLAVE_PORT_WATER_MARK_VALUE 2 +#define SLAVE_PORT_WATER_MARK_SHIFT 1 +#define SLAVE_PORT_ENABLE 1 +#define SLAVE_PORT_DISABLE 0 + +#define BASE_CH_NUM 128 + + +int wcd9xxx_init_slimslave(struct wcd9xxx *wcd9xxx, u8 wcd9xxx_pgd_la); + +int wcd9xxx_deinit_slimslave(struct wcd9xxx *wcd9xxx); + +int wcd9xxx_cfg_slim_sch_rx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int tot_ch, unsigned int rate); +int wcd9xxx_cfg_slim_sch_tx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int tot_ch, unsigned int rate); +int wcd9xxx_close_slim_sch_rx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int tot_ch); +int wcd9xxx_close_slim_sch_tx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, + unsigned int tot_ch); +int wcd9xxx_get_channel(struct wcd9xxx *wcd9xxx, + unsigned int *rx_ch, + unsigned int *tx_ch); +#endif /* __WCD9310_SLIMSLAVE_H_ */ diff --git a/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h b/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h new file mode 100644 index 0000000000000000000000000000000000000000..c66e953d29df5d7def1d81965cd70679eee8f8f2 --- /dev/null +++ b/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef WCD9XXX_CODEC_DIGITAL_H + +#define WCD9XXX_CODEC_DIGITAL_H + +#define WCD9XXX_A_CHIP_CTL (0x00) +#define WCD9XXX_A_CHIP_CTL__POR (0x00000000) +#define WCD9XXX_A_CHIP_STATUS (0x01) +#define WCD9XXX_A_CHIP_STATUS__POR (0x00000000) +#define WCD9XXX_A_CHIP_ID_BYTE_0 (0x04) +#define WCD9XXX_A_CHIP_ID_BYTE_0__POR (0x00000000) +#define WCD9XXX_A_CHIP_ID_BYTE_1 (0x05) +#define WCD9XXX_A_CHIP_ID_BYTE_1__POR (0x00000000) +#define WCD9XXX_A_CHIP_ID_BYTE_2 (0x06) +#define WCD9XXX_A_CHIP_ID_BYTE_2__POR (0x00000000) +#define WCD9XXX_A_CHIP_ID_BYTE_3 (0x07) +#define WCD9XXX_A_CHIP_ID_BYTE_3__POR (0x00000001) +#define WCD9XXX_A_CHIP_VERSION (0x08) +#define WCD9XXX_A_CHIP_VERSION__POR (0x00000020) +#define WCD9XXX_A_SB_VERSION (0x09) +#define WCD9XXX_A_SB_VERSION__POR (0x00000010) +#define WCD9XXX_A_SLAVE_ID_1 (0x0C) +#define WCD9XXX_A_SLAVE_ID_1__POR (0x00000077) +#define WCD9XXX_A_SLAVE_ID_2 (0x0D) +#define WCD9XXX_A_SLAVE_ID_2__POR (0x00000066) +#define WCD9XXX_A_SLAVE_ID_3 (0x0E) +#define WCD9XXX_A_SLAVE_ID_3__POR (0x00000055) +#define WCD9XXX_A_CDC_CTL (0x80) +#define WCD9XXX_A_CDC_CTL__POR (0x00000000) +#define WCD9XXX_A_LEAKAGE_CTL (0x88) +#define WCD9XXX_A_LEAKAGE_CTL__POR (0x00000004) +#endif diff --git a/include/linux/mm.h b/include/linux/mm.h index 3bf2f379bc88c8c88e8a6b1ee42a7276836b354f..ddfb7c5dd51573b080d8cebff355598028ad163b 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -31,6 +31,9 @@ extern unsigned long max_mapnr; extern unsigned long num_physpages; extern unsigned long totalram_pages; +#ifdef CONFIG_FIX_MOVABLE_ZONE +extern unsigned long total_unmovable_pages; +#endif extern void * high_memory; extern int page_cluster; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 629b823f88362b44001cf09dc1559d1e86780dad..1adfbe7c3dace6e448b1f8a6829b8d36b7cbc1c4 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -53,6 +53,9 @@ struct mmc_ext_csd { u8 part_config; u8 cache_ctrl; u8 rst_n_function; + u8 max_packed_writes; + u8 max_packed_reads; + u8 packed_event_en; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ unsigned int generic_cmd6_time; /* Units: 10ms */ @@ -76,6 +79,9 @@ struct mmc_ext_csd { unsigned int data_tag_unit_size; /* DATA TAG UNIT size */ unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; + bool bkops; /* background support bit */ + bool bkops_en; /* background enable bit */ + u8 raw_exception_status; /* 53 */ u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ @@ -89,6 +95,7 @@ struct mmc_ext_csd { u8 raw_sec_erase_mult; /* 230 */ u8 raw_sec_feature_support;/* 231 */ u8 raw_trim_mult; /* 232 */ + u8 raw_bkops_status; /* 246 */ u8 raw_sectors[4]; /* 212 - 4 bytes */ unsigned int feature_support; @@ -198,6 +205,25 @@ struct mmc_part { #define MMC_BLK_DATA_AREA_GP (1<<2) }; +enum mmc_packed_stop_reasons { + EXCEEDS_SEGMENTS = 0, + EXCEEDS_SECTORS, + WRONG_DATA_DIR, + FLUSH_OR_DISCARD, + EMPTY_QUEUE, + REL_WRITE, + THRESHOLD, + MAX_REASONS, +}; + +struct mmc_wr_pack_stats { + u32 *packing_events; + u32 pack_stop_reason[MAX_REASONS]; + spinlock_t lock; + bool enabled; + bool print_in_read; +}; + /* * MMC device */ @@ -221,6 +247,9 @@ struct mmc_card { #define MMC_CARD_REMOVED (1<<7) /* card has been removed */ #define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */ #define MMC_STATE_SLEEP (1<<9) /* card is in sleep state */ +#define MMC_STATE_NEED_BKOPS (1<<10) /* card need to do BKOPS */ +#define MMC_STATE_DOING_BKOPS (1<<11) /* card is doing BKOPS */ +#define MMC_STATE_CHECK_BKOPS (1<<12) /* card need to check BKOPS */ unsigned int quirks; /* card quirks */ #define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize */ @@ -235,6 +264,7 @@ struct mmc_card { #define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in */ #define MMC_QUIRK_LONG_READ_TIME (1<<9) /* Data read time > CSD says */ /* byte mode */ +#define MMC_QUIRK_INAND_DATA_TIMEOUT (1<<8) /* For incorrect data timeout */ unsigned int poweroff_notify_state; /* eMMC4.5 notify feature */ #define MMC_NO_POWER_NOTIFICATION 0 #define MMC_POWERED_ON 1 @@ -270,6 +300,8 @@ struct mmc_card { struct dentry *debugfs_root; struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ unsigned int nr_parts; + + struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ }; /* @@ -387,6 +419,9 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC) #define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED)) #define mmc_card_is_sleep(c) ((c)->state & MMC_STATE_SLEEP) +#define mmc_card_need_bkops(c) ((c)->state & MMC_STATE_NEED_BKOPS) +#define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) +#define mmc_card_check_bkops(c) ((c)->state & MMC_STATE_CHECK_BKOPS) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -399,7 +434,13 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC) #define mmc_card_set_removed(c) ((c)->state |= MMC_CARD_REMOVED) #define mmc_card_set_sleep(c) ((c)->state |= MMC_STATE_SLEEP) +#define mmc_card_set_need_bkops(c) ((c)->state |= MMC_STATE_NEED_BKOPS) +#define mmc_card_set_doing_bkops(c) ((c)->state |= MMC_STATE_DOING_BKOPS) +#define mmc_card_set_check_bkops(c) ((c)->state |= MMC_STATE_CHECK_BKOPS) +#define mmc_card_clr_need_bkops(c) ((c)->state &= ~MMC_STATE_NEED_BKOPS) +#define mmc_card_clr_doing_bkops(c) ((c)->state &= ~MMC_STATE_DOING_BKOPS) +#define mmc_card_clr_check_bkops(c) ((c)->state &= ~MMC_STATE_CHECK_BKOPS) #define mmc_card_clr_sleep(c) ((c)->state &= ~MMC_STATE_SLEEP) /* * Quirk add/remove for MMC products. @@ -491,4 +532,8 @@ extern void mmc_unregister_driver(struct mmc_driver *); extern void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table); +extern struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics( + struct mmc_card *card); +extern void mmc_blk_init_packed_statistics(struct mmc_card *card); + #endif /* LINUX_MMC_CARD_H */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 1b431c728b9a057a150dff532fb3a47a4897e048..6348b2ffecf02d6e1296a17f01c4d226a25cb695 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -18,6 +18,9 @@ struct mmc_request; struct mmc_command { u32 opcode; u32 arg; +#define MMC_CMD23_ARG_REL_WR (1 << 31) +#define MMC_CMD23_ARG_PACKED ((0 << 31) | (1 << 30)) +#define MMC_CMD23_ARG_TAG_REQ (1 << 29) u32 resp[4]; unsigned int flags; /* expected response type */ #define MMC_RSP_PRESENT (1 << 0) @@ -134,6 +137,9 @@ struct mmc_host; struct mmc_card; struct mmc_async_req; +extern int mmc_interrupt_bkops(struct mmc_card *); +extern int mmc_read_bkops_status(struct mmc_card *); +extern int mmc_is_exception_event(struct mmc_card *, unsigned int); extern struct mmc_async_req *mmc_start_req(struct mmc_host *, struct mmc_async_req *, int *); extern int mmc_interrupt_hpi(struct mmc_card *); @@ -143,6 +149,7 @@ extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *); extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, struct mmc_command *, int); extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); +extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd); #define MMC_ERASE_ARG 0x00000000 #define MMC_SECURE_ERASE_ARG 0x80000000 @@ -163,6 +170,7 @@ extern int mmc_can_sanitize(struct mmc_card *card); extern int mmc_can_secure_erase_trim(struct mmc_card *card); extern int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, unsigned int nr); +extern void mmc_start_bkops(struct mmc_card *card); extern unsigned int mmc_calc_max_discard(struct mmc_card *card); extern int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen); @@ -176,6 +184,9 @@ extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int); extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); extern void mmc_release_host(struct mmc_host *host); extern int mmc_try_claim_host(struct mmc_host *host); +extern void mmc_set_ios(struct mmc_host *host); +extern int mmc_detect_card_removed(struct mmc_host *host); +extern int mmc_flush_cache(struct mmc_card *); extern int mmc_flush_cache(struct mmc_card *); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index a1f7a4f6a2c7eb4ee7e59c61c1744b0b16b895e8..05d4d96fd57a67e1526ab61b7ff5906da0ebdbcd 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -60,6 +60,8 @@ struct mmc_ios { #define MMC_TIMING_UHS_DDR50 5 #define MMC_TIMING_MMC_HS200 6 + unsigned char ddr; /* dual data rate used */ + #define MMC_SDR_MODE 0 #define MMC_1_2V_DDR_MODE 1 #define MMC_1_8V_DDR_MODE 2 @@ -240,6 +242,14 @@ struct mmc_host { #define MMC_CAP2_DETECT_ON_ERR (1 << 8) /* On I/O err check card removal */ #define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */ +#define MMC_CAP2_PACKED_RD (1 << 10) /* Allow packed read */ +#define MMC_CAP2_PACKED_WR (1 << 11) /* Allow packed write */ +#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \ + MMC_CAP2_PACKED_WR) /* Allow packed commands */ +#define MMC_CAP2_PACKED_WR_CONTROL (1 << 12) /* Allow write packing control */ +#define MMC_CAP2_SANITIZE (1 << 13) /* Support Sanitize */ +#define MMC_CAP2_BKOPS (1 << 14) /* BKOPS supported */ +#define MMC_CAP2_INIT_BKOPS (1 << 15) /* Need to set BKOPS_EN */ mmc_pm_flag_t pm_caps; /* supported pm features */ unsigned int power_notify_type; #define MMC_HOST_PW_NOTIFY_NONE 0 @@ -287,6 +297,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; @@ -303,6 +314,7 @@ struct mmc_host { unsigned int sdio_irqs; struct task_struct *sdio_irq_thread; + bool sdio_irq_pending; atomic_t sdio_irq_thread_abort; mmc_pm_flag_t pm_flags; /* requested pm features */ @@ -334,6 +346,18 @@ struct mmc_host { } embedded_sdio_data; #endif +#ifdef CONFIG_MMC_PERF_PROFILING + struct { + + unsigned long rbytes_drv; /* Rd bytes MMC Host */ + unsigned long wbytes_drv; /* Wr bytes MMC Host */ + ktime_t rtime_drv; /* Rd time MMC Host */ + ktime_t wtime_drv; /* Wr time MMC Host */ + ktime_t start; + } perf; + bool perf_enable; +#endif + unsigned long private[0] ____cacheline_aligned; }; @@ -387,6 +411,7 @@ extern int mmc_cache_ctrl(struct mmc_host *, u8); static inline void mmc_signal_sdio_irq(struct mmc_host *host) { host->ops->enable_sdio_irq(host, 0); + host->sdio_irq_pending = true; wake_up_process(host->sdio_irq_thread); } @@ -415,6 +440,9 @@ int mmc_card_awake(struct mmc_host *host); int mmc_card_sleep(struct mmc_host *host); 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 *); /* Module parameter */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index b822a2cb6008d16d1dee1474c62d9a8301da754e..a5b4facdeaf493918ae32727f29d32850bf2a152 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -139,7 +139,9 @@ static inline bool mmc_op_multi(u32 opcode) #define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ #define R1_READY_FOR_DATA (1 << 8) /* sx, a */ #define R1_SWITCH_ERROR (1 << 7) /* sx, c */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sx, a */ #define R1_APP_CMD (1 << 5) /* sr, c */ +#define R1_EXP_EVENT (1 << 6) /* sr, a */ #define R1_STATE_IDLE 0 #define R1_STATE_READY 1 @@ -222,6 +224,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) @@ -274,12 +277,18 @@ struct _mmc_csd { #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ +#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */ +#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */ +#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */ +#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ #define EXT_CSD_HPI_MGMT 161 /* R/W */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_BKOPS_EN 163 /* R/W */ +#define EXT_CSD_BKOPS_START 164 /* W */ #define EXT_CSD_SANITIZE_START 165 /* W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ #define EXT_CSD_BOOT_WP 173 /* R/W */ @@ -313,11 +322,15 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_200_360 237 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_BKOPS_STATUS 246 /* RO */ #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ #define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ +#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */ +#define EXT_CSD_MAX_PACKED_READS 501 /* RO */ +#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */ #define EXT_CSD_HPI_FEATURES 503 /* RO */ /* @@ -333,6 +346,7 @@ struct _mmc_csd { #define EXT_CSD_PART_CONFIG_ACC_MASK (0x7) #define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1) +#define EXT_CSD_PART_CONFIG_ACC_BOOT1 (0x2) #define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4) #define EXT_CSD_PART_SUPPORT_PART_EN (0x1) @@ -433,10 +447,26 @@ struct _mmc_csd { #define EXT_CSD_POWER_OFF_SHORT 2 #define EXT_CSD_POWER_OFF_LONG 3 +#define EXT_CSD_RST_N_EN_MASK 0x3 +#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ + +#define EXT_CSD_NO_POWER_NOTIFICATION 0 +#define EXT_CSD_POWER_ON 1 +#define EXT_CSD_POWER_OFF_SHORT 2 +#define EXT_CSD_POWER_OFF_LONG 3 + #define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */ #define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ #define EXT_CSD_PWR_CL_8BIT_SHIFT 4 #define EXT_CSD_PWR_CL_4BIT_SHIFT 0 + +#define EXT_CSD_PACKED_EVENT_EN (1 << 3) + +#define EXT_CSD_PACKED_FAILURE (1 << 3) + +#define EXT_CSD_PACKED_GENERIC_ERROR (1 << 0) +#define EXT_CSD_PACKED_INDEXED_ERROR (1 << 1) + /* * MMC_SWITCH access modes */ @@ -446,4 +476,16 @@ struct _mmc_csd { #define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */ #define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ +/* + * BKOPS status level + */ +#define EXT_CSD_BKOPS_LEVEL_2 0x2 + +/* + * EXCEPTION_EVENT_STATUS field (eMMC4.5) + */ +#define EXT_CSD_URGENT_BKOPS BIT(0) +#define EXT_CSD_DYNCAP_NEEDED BIT(1) +#define EXT_CSD_SYSPOOL_EXHAUSTED BIT(2) + #endif /* LINUX_MMC_MMC_H */ diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index c9fe66c58f8fc718e68b2147733a0acab750d5aa..58e52d438837db3e0811f0104bafb5e265e88e72 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -100,6 +100,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/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h old mode 100755 new mode 100644 diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index dff711509661c8e5eb74fce083eef26e526a783e..64290b39e4b6751f2ce98a287a2c9d02bc7fde98 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -484,6 +484,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 @@ -1119,7 +1125,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 501da4cb8a6de02ecd0dbe8b6759c26b298d2c12..bc124da75e918b4415e64df38f604db211128b68 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -292,7 +292,7 @@ struct pcmcia_device_id { #define INPUT_DEVICE_ID_LED_MAX 0x0f #define INPUT_DEVICE_ID_SND_MAX 0x07 #define INPUT_DEVICE_ID_FF_MAX 0x7f -#define INPUT_DEVICE_ID_SW_MAX 0x0f +#define INPUT_DEVICE_ID_SW_MAX 0x20 #define INPUT_DEVICE_ID_MATCH_BUS 1 #define INPUT_DEVICE_ID_MATCH_VENDOR 2 @@ -445,6 +445,24 @@ 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)))); +}; + +#define SPMI_NAME_SIZE 32 +#define SPMI_MODULE_PREFIX "spmi:" + +struct spmi_device_id { + char name[SPMI_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 0000000000000000000000000000000000000000..14ffae311fd0480b0b18a0d2ac9e4bcbc3889eb0 --- /dev/null +++ b/include/linux/msm-charger.h @@ -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. + * + */ +#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 (*start_system_current) (struct msm_hardware_charger *hw_chg, + int chg_current); + void (*stop_system_current) (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 0000000000000000000000000000000000000000..c303e698d3877b8e191d1f91f2c8d817ba851840 --- /dev/null +++ b/include/linux/msm_adc.h @@ -0,0 +1,372 @@ +#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, + MSM_8x25, +}; + +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); +#ifdef CONFIG_SENSORS_MSM_ADC +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); +#else +static inline int32_t adc_channel_open(uint32_t channel, void **h) +{ + pr_err("%s.not supported.\n", __func__); + return -ENODEV; +} +static inline int32_t adc_channel_close(void *h) +{ + pr_err("%s.not supported.\n", __func__); + return -ENODEV; +} +static inline int32_t +adc_channel_request_conv(void *h, struct completion *conv_complete_evt) +{ + pr_err("%s.not supported.\n", __func__); + return -ENODEV; +} +static inline int32_t +adc_channel_read_result(void *h, struct adc_chan_result *chan_result) +{ + pr_err("%s.not supported.\n", __func__); + return -ENODEV; +} +#endif /* CONFIG_SENSORS_MSM_ADC */ +#endif +#endif /* __MSM_ADC_H */ diff --git a/include/linux/msm_adsp.h b/include/linux/msm_adsp.h new file mode 100644 index 0000000000000000000000000000000000000000..ca23ad8dd74f319a78a4817f9d2cf79e1bae7127 --- /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 0000000000000000000000000000000000000000..f2a39e46677626652d908e49cbc5aa6c11fdc49b --- /dev/null +++ b/include/linux/msm_audio.h @@ -0,0 +1,367 @@ +/* include/linux/msm_audio.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2012 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 __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) + +#define AUDIO_SET_SRS_TRUMEDIA_PARAM _IOW(AUDIO_IOCTL_MAGIC, 43, unsigned) + +/* 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_REGISTER_ION _IOW(AUDIO_IOCTL_MAGIC, 97, unsigned) +#define AUDIO_DEREGISTER_ION _IOW(AUDIO_IOCTL_MAGIC, 98, unsigned) + +#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 SRS_ENABLE 0x0020 +#define SRS_DISABLE 0x0040 + +#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_ion_info { + int fd; + void *vaddr; +}; + +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 0000000000000000000000000000000000000000..620e5abfc8ddcbbb0bf60c4ddd50e35a9c661e47 --- /dev/null +++ b/include/linux/msm_audio_aac.h @@ -0,0 +1,72 @@ +#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_FORMAT_ADIF 0x0003 + +#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 0000000000000000000000000000000000000000..e7f06b53ac84bb506ea36f20f5bb3db6c3e7940b --- /dev/null +++ b/include/linux/msm_audio_acdb.h @@ -0,0 +1,81 @@ +#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_RX_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+13), unsigned) +#define AUDIO_SET_ADM_TX_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+14), unsigned) +#define AUDIO_SET_ASM_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+15), unsigned) +#define AUDIO_SET_AFE_TX_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+16), unsigned) +#define AUDIO_SET_AFE_RX_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+17), unsigned) + + +#define AUDIO_MAX_ACDB_IOCTL (AUDIO_MAX_COMMON_IOCTL_NUM+30) + +/* 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_ADM_INFO _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+1), unsigned) +#define AUDIO_GET_RTAC_VOICE_INFO _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+2), unsigned) +#define AUDIO_GET_RTAC_ADM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+3), unsigned) +#define AUDIO_SET_RTAC_ADM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+4), unsigned) +#define AUDIO_GET_RTAC_ASM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+5), unsigned) +#define AUDIO_SET_RTAC_ASM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+6), unsigned) +#define AUDIO_GET_RTAC_CVS_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+7), unsigned) +#define AUDIO_SET_RTAC_CVS_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+8), unsigned) +#define AUDIO_GET_RTAC_CVP_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+9), unsigned) +#define AUDIO_SET_RTAC_CVP_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_ACDB_IOCTL+10), unsigned) + +#define AUDIO_MAX_RTAC_IOCTL (AUDIO_MAX_ACDB_IOCTL+20) + +#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 0000000000000000000000000000000000000000..77a1258f18b3739d08808cee7c25b82b59005a33 --- /dev/null +++ b/include/linux/msm_audio_amrnb.h @@ -0,0 +1,33 @@ +#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_amrwb.h b/include/linux/msm_audio_amrwb.h new file mode 100644 index 0000000000000000000000000000000000000000..23837433cfad65d37bf6c3cdf59ec29e1a7cc0f3 --- /dev/null +++ b/include/linux/msm_audio_amrwb.h @@ -0,0 +1,18 @@ +#ifndef __MSM_AUDIO_AMRWB_H +#define __MSM_AUDIO_AMRWB_H + +#include + +#define AUDIO_GET_AMRWB_ENC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), \ + struct msm_audio_amrwb_enc_config) +#define AUDIO_SET_AMRWB_ENC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), \ + struct msm_audio_amrwb_enc_config) + +struct msm_audio_amrwb_enc_config { + uint32_t band_mode; + uint32_t dtx_enable; + uint32_t frame_format; +}; +#endif /* __MSM_AUDIO_AMRWB_H */ diff --git a/include/linux/msm_audio_mvs.h b/include/linux/msm_audio_mvs.h new file mode 100644 index 0000000000000000000000000000000000000000..1807cb044f2be6f090a50ee7f20c1f8f949dcf52 --- /dev/null +++ b/include/linux/msm_audio_mvs.h @@ -0,0 +1,144 @@ +#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 0x1 +#define MVS_MODE_IS127 0x2 +#define MVS_MODE_4GV_NB 0x3 +#define MVS_MODE_4GV_WB 0x4 +#define MVS_MODE_AMR 0x5 +#define MVS_MODE_EFR 0x6 +#define MVS_MODE_FR 0x7 +#define MVS_MODE_HR 0x8 +#define MVS_MODE_LINEAR_PCM 0x9 +#define MVS_MODE_G711 0xA +#define MVS_MODE_PCM 0xC +#define MVS_MODE_AMR_WB 0xD +#define MVS_MODE_G729A 0xE +#define MVS_MODE_G711A 0xF +#define MVS_MODE_G722 0x10 +#define MVS_MODE_PCM_WB 0x12 + +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 min_max_rate { + uint32_t min_rate; + uint32_t max_rate; +}; + +struct msm_audio_mvs_config { + uint32_t mvs_mode; + uint32_t rate_type; + struct min_max_rate min_max_rate; + uint32_t dtx_mode; +}; + +#define MVS_MAX_VOC_PKT_SIZE 640 + +struct gsm_header { + uint8_t bfi; + uint8_t sid; + uint8_t taf; + uint8_t ufi; +}; + +struct q6_msm_audio_mvs_frame { + union { + uint32_t frame_type; + uint32_t packet_rate; + struct gsm_header gsm_frame_type; + } header; + uint32_t len; + uint8_t voc_pkt[MVS_MAX_VOC_PKT_SIZE]; + +}; + +struct msm_audio_mvs_frame { + uint32_t frame_type; + uint32_t len; + uint8_t voc_pkt[MVS_MAX_VOC_PKT_SIZE]; + +}; + +#define Q5V2_MVS_MAX_VOC_PKT_SIZE 320 + +struct q5v2_msm_audio_mvs_frame { + uint32_t frame_type; + uint32_t len; + uint8_t voc_pkt[Q5V2_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 0000000000000000000000000000000000000000..6e0c390986caf5c4cee3eceb572c2f72b3b70821 --- /dev/null +++ b/include/linux/msm_audio_qcp.h @@ -0,0 +1,37 @@ +#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 0000000000000000000000000000000000000000..c1de751f1c9249bed2fe5d62f4b6f0cbadce7feb --- /dev/null +++ b/include/linux/msm_audio_sbc.h @@ -0,0 +1,36 @@ +#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 0000000000000000000000000000000000000000..d616c2e86aee07aafc129af3e46783ff1d11d677 --- /dev/null +++ b/include/linux/msm_audio_voicememo.h @@ -0,0 +1,66 @@ +#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 0000000000000000000000000000000000000000..24ff2643c90c72571082d54668dcd5538e983c7b --- /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 0000000000000000000000000000000000000000..b680f419ecc6186f3ae216411ccf8c1b443fb2c8 --- /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 0000000000000000000000000000000000000000..c31e49375cf3fb6f58ba215a6b04c172b473bed6 --- /dev/null +++ b/include/linux/msm_charm.h @@ -0,0 +1,20 @@ +#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) +#define GET_DLOAD_STATUS _IOR(CHARM_CODE, 8, 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 0000000000000000000000000000000000000000..a5ac256127ebcf498593d954b21ec7cd50803cf3 --- /dev/null +++ b/include/linux/msm_dsps.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 _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*) + +#define DSPS_IOCTL_RESET _IO(DSPS_IOCTL_MAGIC, 5) + +#endif /* _DSPS_H_ */ diff --git a/include/linux/msm_ipc.h b/include/linux/msm_ipc.h new file mode 100644 index 0000000000000000000000000000000000000000..82f76a66eec94998a4e119b9ac2e818503130c7f --- /dev/null +++ b/include/linux/msm_ipc.h @@ -0,0 +1,73 @@ +#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; + uint32_t lookup_mask; + 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 0000000000000000000000000000000000000000..e67190f07d4b3647dfde3d158d76f52ce3f13db8 --- /dev/null +++ b/include/linux/msm_kgsl.h @@ -0,0 +1,519 @@ +#ifndef _MSM_KGSL_H +#define _MSM_KGSL_H + +#define KGSL_VERSION_MAJOR 3 +#define KGSL_VERSION_MINOR 11 + +/*context flags */ +#define KGSL_CONTEXT_SAVE_GMEM 0x00000001 +#define KGSL_CONTEXT_NO_GMEM_ALLOC 0x00000002 +#define KGSL_CONTEXT_SUBMIT_IB_LIST 0x00000004 +#define KGSL_CONTEXT_CTX_SWITCH 0x00000008 +#define KGSL_CONTEXT_PREAMBLE 0x00000010 +#define KGSL_CONTEXT_TRASH_STATE 0x00000020 +#define KGSL_CONTEXT_PER_CONTEXT_TS 0x00000040 + +#define KGSL_CONTEXT_INVALID 0xffffffff + +/* 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_FLAGS_PER_CONTEXT_TIMESTAMPS 0x00000200 + +/* Clock flags to show which clocks should be controled by a given platform */ +#define KGSL_CLK_SRC 0x00000001 +#define KGSL_CLK_CORE 0x00000002 +#define KGSL_CLK_IFACE 0x00000004 +#define KGSL_CLK_MEM 0x00000008 +#define KGSL_CLK_MEM_IFACE 0x00000010 +#define KGSL_CLK_AXI 0x00000020 + +/* + * Reset status values for context + */ +enum kgsl_ctx_reset_stat { + KGSL_CTX_STAT_NO_ERROR = 0x00000000, + KGSL_CTX_STAT_GUILTY_CONTEXT_RESET_EXT = 0x00000001, + KGSL_CTX_STAT_INNOCENT_CONTEXT_RESET_EXT = 0x00000002, + KGSL_CTX_STAT_UNKNOWN_CONTEXT_RESET_EXT = 0x00000003 +}; + +#define KGSL_MAX_PWRLEVELS 5 + +#define KGSL_CONVERT_TO_MBPS(val) \ + (val*1000*1000U) + +/* 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, + KGSL_USER_MEM_TYPE_ION = 0x00000003, + KGSL_USER_MEM_TYPE_MAX = 0x00000004, +}; + +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_MEMSTORE_OFFSET(ctxt_id, field) \ + ((ctxt_id)*sizeof(struct kgsl_devmemstore) + \ + 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_QUEUED = 0x00000003, +}; + +/* 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, + KGSL_PROP_GPU_RESET_STAT = 0x00000009, + KGSL_PROP_PWRCTRL = 0x0000000E, +}; + +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; + unsigned int io_fraction; +}; + +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" + +enum kgsl_iommu_context_id { + KGSL_IOMMU_CONTEXT_USER = 0, + KGSL_IOMMU_CONTEXT_PRIV = 1, +}; + +struct kgsl_iommu_ctx { + const char *iommu_ctx_name; + enum kgsl_iommu_context_id ctx_id; +}; + +struct kgsl_device_iommu_data { + const struct kgsl_iommu_ctx *iommu_ctxs; + int iommu_ctx_count; + unsigned int physstart; + unsigned int physend; +}; + +struct kgsl_device_platform_data { + struct kgsl_pwrlevel pwrlevel[KGSL_MAX_PWRLEVELS]; + int init_level; + int num_levels; + int (*set_grp_async)(void); + unsigned int idle_timeout; + bool strtstp_sleepwake; + unsigned int nap_allowed; + unsigned int clk_map; + unsigned int idle_needed; + struct msm_bus_scale_pdata *bus_scale_table; + struct kgsl_device_iommu_data *iommu_data; + int iommu_count; + struct msm_dcvs_core_info *core_info; +}; + +#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) + +/* IOCTL_KGSL_DEVICE_READ (0x3) - removed 03/2012 + */ + +/* 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) + +struct kgsl_device_waittimestamp_ctxtid { + unsigned int context_id; + unsigned int timestamp; + unsigned int timeout; +}; + +#define IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID \ + _IOW(KGSL_IOC_TYPE, 0x7, struct kgsl_device_waittimestamp_ctxtid) + +/* 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_OLD \ + _IOR(KGSL_IOC_TYPE, 0x11, struct kgsl_cmdstream_readtimestamp) + +#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP \ + _IOWR(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) + +struct kgsl_cmdstream_readtimestamp_ctxtid { + unsigned int context_id; + unsigned int type; + unsigned int timestamp; /*output param */ +}; + +#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID \ + _IOWR(KGSL_IOC_TYPE, 0x16, struct kgsl_cmdstream_readtimestamp_ctxtid) + +struct kgsl_cmdstream_freememontimestamp_ctxtid { + unsigned int context_id; + unsigned int gpuaddr; + unsigned int type; + unsigned int timestamp; +}; + +#define IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_CTXTID \ + _IOW(KGSL_IOC_TYPE, 0x17, \ + struct kgsl_cmdstream_freememontimestamp_ctxtid) + +/* 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_cff_user_event { + unsigned char cff_opcode; + unsigned int op1; + unsigned int op2; + unsigned int op3; + unsigned int op4; + unsigned int op5; + unsigned int __pad[2]; +}; + +#define IOCTL_KGSL_CFF_USER_EVENT \ + _IOW(KGSL_IOC_TYPE, 0x31, struct kgsl_cff_user_event) + +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) + +struct kgsl_cff_syncmem { + unsigned int gpuaddr; + unsigned int len; + unsigned int __pad[2]; /* For future binary compatibility */ +}; + +#define IOCTL_KGSL_CFF_SYNCMEM \ + _IOW(KGSL_IOC_TYPE, 0x30, struct kgsl_cff_syncmem) + +/* + * A timestamp event allows the user space to register an action following an + * expired timestamp. + */ + +struct kgsl_timestamp_event { + int type; /* Type of event (see list below) */ + unsigned int timestamp; /* Timestamp to trigger event on */ + unsigned int context_id; /* Context for the timestamp */ + void *priv; /* Pointer to the event specific blob */ + size_t len; /* Size of the event specific blob */ +}; + +#define IOCTL_KGSL_TIMESTAMP_EVENT \ + _IOW(KGSL_IOC_TYPE, 0x31, struct kgsl_timestamp_event) + +/* A genlock timestamp event releases an existing lock on timestamp expire */ + +#define KGSL_TIMESTAMP_EVENT_GENLOCK 1 + +struct kgsl_timestamp_event_genlock { + int handle; /* Handle of the genlock lock to release */ +}; + +/* + * Set a property within the kernel. Uses the same structure as + * IOCTL_KGSL_GETPROPERTY + */ + +#define IOCTL_KGSL_SETPROPERTY \ + _IOW(KGSL_IOC_TYPE, 0x32, struct kgsl_device_getproperty) + +#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 fe722c1fb61d552afa87d0d6e62c0da6bd8f82af..a0af4b5f1f5d304926e25467e26c56829a7bc454 100644 --- a/include/linux/msm_mdp.h +++ b/include/linux/msm_mdp.h @@ -1,6 +1,7 @@ /* include/linux/msm_mdp.h * * Copyright (C) 2007 Google Incorporated + * Copyright (c) 2012 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,25 +16,92 @@ #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_data) +/* 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 _IOR(MSMFB_IOCTL_MAGIC, 144, \ + struct mdp_histogram_start_req) +#define MSMFB_HISTOGRAM_STOP _IOR(MSMFB_IOCTL_MAGIC, 145, unsigned int) +#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 MSMFB_MIXER_INFO _IOWR(MSMFB_IOCTL_MAGIC, 148, \ + struct msmfb_mixer_info_req) +#define MSMFB_OVERLAY_PLAY_WAIT _IOWR(MSMFB_IOCTL_MAGIC, 149, \ + struct msmfb_overlay_data) +#define MSMFB_WRITEBACK_INIT _IO(MSMFB_IOCTL_MAGIC, 150) +#define MSMFB_WRITEBACK_START _IO(MSMFB_IOCTL_MAGIC, 151) +#define MSMFB_WRITEBACK_STOP _IO(MSMFB_IOCTL_MAGIC, 152) +#define MSMFB_WRITEBACK_QUEUE_BUFFER _IOW(MSMFB_IOCTL_MAGIC, 153, \ + struct msmfb_data) +#define MSMFB_WRITEBACK_DEQUEUE_BUFFER _IOW(MSMFB_IOCTL_MAGIC, 154, \ + struct msmfb_data) +#define MSMFB_WRITEBACK_TERMINATE _IO(MSMFB_IOCTL_MAGIC, 155) +#define MSMFB_MDP_PP _IOWR(MSMFB_IOCTL_MAGIC, 156, struct msmfb_mdp_pp) + +#define FB_TYPE_3D_PANEL 0x10101010 +#define MDP_IMGTYPE2_START 0x10000 +#define MSMFB_DRIVER_VERSION 0xF9E8D701 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 */ + NOTIFY_UPDATE_START, + NOTIFY_UPDATE_STOP, +}; + +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_Y_CBCR_H2V2_ADRENO, + 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_CR_CB_GH2V2, /* Y, Cr and Cb, planar aligned to Android YV12 */ + 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_YCRCB_H1V1, /* YCrCb interleave */ + MDP_YCBCR_H1V1, /* YCbCr interleave */ + 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 +109,402 @@ 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 +enum { + HSIC_HUE = 0, + HSIC_SAT, + HSIC_INT, + HSIC_CON, + NUM_HSIC_PARAM, +}; + +/* 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_DPP_HSIC 0x00080000 +#define MDP_BACKEND_COMPOSITION 0x00040000 +#define MDP_BORDERFILL_SUPPORTED 0x00010000 +#define MDP_SECURE_OVERLAY_SESSION 0x00008000 +#define MDP_MEMORY_ID_TYPE_FB 0x00001000 -#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 */ +}; + +struct mdp_csc { + int id; + uint32_t csc_mv[9]; + uint32_t csc_pre_bv[3]; + uint32_t csc_post_bv[3]; + uint32_t csc_pre_lv[6]; + uint32_t csc_post_lv[6]; +}; + +/* 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; + uint32_t iova; +}; + +#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; +}; + +#define MSMFB_WRITEBACK_DEQUEUE_BLOCKING 0x1 +struct msmfb_writeback_data { + struct msmfb_data buf_info; + struct msmfb_img img; +}; + +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; + int8_t hsic_params[NUM_HSIC_PARAM]; +}; + +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; +}; + + +/* + + mdp_block_type defines the identifiers for each of pipes in MDP 4.3 + + MDP_BLOCK_RESERVED is provided for backward compatibility and is + deprecated. It corresponds to DMA_P. So MDP_BLOCK_DMA_P should be used + instead. + +*/ + +enum { + MDP_BLOCK_RESERVED = 0, + MDP_BLOCK_OVERLAY_0, + MDP_BLOCK_OVERLAY_1, + MDP_BLOCK_VG_1, + MDP_BLOCK_VG_2, + MDP_BLOCK_RGB_1, + MDP_BLOCK_RGB_2, + MDP_BLOCK_DMA_P, + MDP_BLOCK_DMA_S, + MDP_BLOCK_DMA_E, + MDP_BLOCK_OVERLAY_2, + MDP_BLOCK_MAX, +}; + +/* + * mdp_histogram_start_req is used to provide the parameters for + * histogram start request + */ + +struct mdp_histogram_start_req { + uint32_t block; + uint8_t frame_cnt; + uint8_t bit_mask; + uint8_t num_bins; +}; + +/* + * mdp_histogram_data is used to return the histogram data, once + * the histogram is done/stopped/cance + */ + +struct mdp_histogram_data { + uint32_t block; + uint8_t bin_cnt; + uint32_t *c0; + uint32_t *c1; + uint32_t *c2; + uint32_t *extra_info; +}; + +struct mdp_pcc_coeff { + uint32_t c, r, g, b, rr, gg, bb, rg, gb, rb, rgb_0, rgb_1; +}; + +struct mdp_pcc_cfg_data { + uint32_t block; + uint32_t ops; + struct mdp_pcc_coeff r, g, b; +}; + +#define MDP_CSC_FLAG_ENABLE 0x1 +#define MDP_CSC_FLAG_YUV_IN 0x2 +#define MDP_CSC_FLAG_YUV_OUT 0x4 + +struct mdp_csc_cfg { + /* flags for enable CSC, toggling RGB,YUV input/output */ + uint32_t flags; + uint32_t csc_mv[9]; + uint32_t csc_pre_bv[3]; + uint32_t csc_post_bv[3]; + uint32_t csc_pre_lv[6]; + uint32_t csc_post_lv[6]; +}; + +struct mdp_csc_cfg_data { + uint32_t block; + struct mdp_csc_cfg csc_data; +}; + +enum { + mdp_lut_igc, + mdp_lut_pgc, + mdp_lut_hist, + mdp_lut_max, +}; + + +struct mdp_igc_lut_data { + uint32_t block; + uint32_t len, ops; + uint32_t *c0_c1_data; + uint32_t *c2_data; +}; + +struct mdp_ar_gc_lut_data { + uint32_t x_start; + uint32_t slope; + uint32_t offset; +}; + +struct mdp_pgc_lut_data { + uint32_t block; + uint32_t flags; + uint8_t num_r_stages; + uint8_t num_g_stages; + uint8_t num_b_stages; + struct mdp_ar_gc_lut_data *r_data; + struct mdp_ar_gc_lut_data *g_data; + struct mdp_ar_gc_lut_data *b_data; +}; + + +struct mdp_hist_lut_data { + uint32_t block; + uint32_t ops; + uint32_t len; + uint32_t *data; +}; + + +struct mdp_lut_cfg_data { + uint32_t lut_type; + union { + struct mdp_igc_lut_data igc_lut_data; + struct mdp_pgc_lut_data pgc_lut_data; + struct mdp_hist_lut_data hist_lut_data; + } data; +}; + +struct mdp_qseed_cfg_data { + uint32_t block; + uint32_t table_num; + uint32_t ops; + uint32_t len; + uint32_t *data; +}; + + +enum { + mdp_op_pcc_cfg, + mdp_op_csc_cfg, + mdp_op_lut_cfg, + mdp_op_qseed_cfg, + mdp_op_max, +}; + +struct msmfb_mdp_pp { + uint32_t op; + union { + struct mdp_pcc_cfg_data pcc_cfg_data; + struct mdp_csc_cfg_data csc_cfg_data; + struct mdp_lut_cfg_data lut_cfg_data; + struct mdp_qseed_cfg_data qseed_cfg_data; + } data; +}; + + +struct mdp_page_protection { + uint32_t page_protection; +}; + + +struct mdp_mixer_info { + int pndx; + int pnum; + int ptype; + int mixer_num; + int z_order; +}; + +#define MAX_PIPE_PER_MIXER 4 + +struct msmfb_mixer_info_req { + int mixer_num; + int cnt; + struct mdp_mixer_info info[MAX_PIPE_PER_MIXER]; +}; + +enum { + DISPLAY_SUBSYSTEM_ID, + ROTATOR_SUBSYSTEM_ID, +}; + +#ifdef __KERNEL__ + +/* get the framebuffer physical address information */ +int get_fb_phys_info(unsigned long *start, unsigned long *len, int fb_num, + int subsys_id); +struct fb_info *msm_fb_get_writeback_fb(void); +int msm_fb_writeback_init(struct fb_info *info); +int msm_fb_writeback_start(struct fb_info *info); +int msm_fb_writeback_queue_buffer(struct fb_info *info, + struct msmfb_data *data); +int msm_fb_writeback_dequeue_buffer(struct fb_info *info, + struct msmfb_data *data); +int msm_fb_writeback_stop(struct fb_info *info); +int msm_fb_writeback_terminate(struct fb_info *info); +#endif + +#endif /*_MSM_MDP_H_*/ diff --git a/include/linux/msm_q6vdec.h b/include/linux/msm_q6vdec.h new file mode 100644 index 0000000000000000000000000000000000000000..47b8163fe01a8b25df933a2a4a4f4490cf2fac93 --- /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 0000000000000000000000000000000000000000..c6bf20c7b1bc82a59f4510ba916f660b860b206f --- /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 0000000000000000000000000000000000000000..9f524649102126688eab51017e01f3776e24f544 --- /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 0000000000000000000000000000000000000000..0f15a8bee5ee2c1e8b0584c44fdb35c34b9d67b9 --- /dev/null +++ b/include/linux/msm_rotator.h @@ -0,0 +1,60 @@ +#ifndef __MSM_ROTATOR_H__ +#define __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_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; + unsigned int downscale_ratio; +}; + +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; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *bus_scale_table; +#endif +}; +#endif + diff --git a/include/linux/msm_rpcrouter.h b/include/linux/msm_rpcrouter.h new file mode 100644 index 0000000000000000000000000000000000000000..01d3809e574a5ff86b06ce1f5294a42dc970a4b9 --- /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 0000000000000000000000000000000000000000..dc7328f667268ea7b856ee15a84dd54ee7879e03 --- /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 0000000000000000000000000000000000000000..647bc066e3eb8ac340f3575c85bbc7452263ed09 --- /dev/null +++ b/include/linux/msm_ssbi.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2010 Google, Inc. + * Copyright (c) 2011-2012, 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 { + const char *rsl_id; + 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_tsens.h b/include/linux/msm_tsens.h new file mode 100644 index 0000000000000000000000000000000000000000..1b0d39973303e8a629216b4938fadb54d3f1117f --- /dev/null +++ b/include/linux/msm_tsens.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 Header file + * + */ + +#ifndef __MSM_TSENS_H +#define __MSM_TSENS_H + +enum platform_type { + MSM_8660 = 0, + MSM_8960, + MDM_9615, + APQ_8064, + MSM_TYPE +}; + +#define TSENS_MAX_SENSORS 11 + +struct tsens_platform_data { + int slope[TSENS_MAX_SENSORS]; + int tsens_factor; + uint32_t tsens_num_sensor; + enum platform_type hw_type; +}; + +struct tsens_device { + uint32_t sensor_num; +}; + +int32_t tsens_get_temp(struct tsens_device *dev, unsigned long *temp); +int msm_tsens_early_init(struct tsens_platform_data *pdata); + +#endif /*MSM_TSENS_H */ diff --git a/include/linux/msm_vidc_dec.h b/include/linux/msm_vidc_dec.h new file mode 100644 index 0000000000000000000000000000000000000000..0c03e136c3c9d86407b05022df834444e46c16a6 --- /dev/null +++ b/include/linux/msm_vidc_dec.h @@ -0,0 +1,568 @@ +#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_S_INPUT_BITSTREAM_ERR (VDEC_S_BASE + 14) + +#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) +#define VDEC_MSG_EVT_INFO_FIELD_DROPPED (VDEC_MSG_BASE + 16) + +/*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) + +#define VDEC_IOCTL_SET_DISABLE_DMX \ + _IOW(VDEC_IOCTL_MAGIC, 35, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_GET_DISABLE_DMX \ + _IOR(VDEC_IOCTL_MAGIC, 36, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_GET_DISABLE_DMX_SUPPORT \ + _IOR(VDEC_IOCTL_MAGIC, 37, struct vdec_ioctl_msg) + +enum vdec_picture { + PICTURE_TYPE_I, + PICTURE_TYPE_P, + PICTURE_TYPE_B, + PICTURE_TYPE_BI, + PICTURE_TYPE_SKIP, + PICTURE_TYPE_IDR, + 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; + void __user *desc_addr; + uint32_t desc_size; +}; + +struct vdec_framesize { + uint32_t left; + uint32_t top; + uint32_t right; + uint32_t bottom; +}; + +struct vdec_aspectratioinfo { + uint32_t aspect_ratio; + uint32_t par_width; + uint32_t par_height; +}; + +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; + struct vdec_aspectratioinfo aspect_ratio_info; +}; + +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 0000000000000000000000000000000000000000..519c537c722ab099e198992d19a7997c0a19ed62 --- /dev/null +++ b/include/linux/msm_vidc_enc.h @@ -0,0 +1,617 @@ +#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 + +/*Post processing flags bit masks*/ +#define VEN_EXTRADATA_NONE 0x001 +#define VEN_EXTRADATA_QCOMFILLER 0x002 +#define VEN_EXTRADATA_SLICEINFO 0x100 + +/*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_3p2 0x13/* H.264 Level 3.2 */ +#define VEN_LEVEL_H264_4 0x14/* H.264 Level 4 */ + +#define VEN_LEVEL_H263_10 0x15/* H.263 Level 10 */ +#define VEN_LEVEL_H263_20 0x16/* H.263 Level 20 */ +#define VEN_LEVEL_H263_30 0x17/* H.263 Level 30 */ +#define VEN_LEVEL_H263_40 0x18/* H.263 Level 40 */ +#define VEN_LEVEL_H263_45 0x19/* H.263 Level 45 */ +#define VEN_LEVEL_H263_50 0x1A/* H.263 Level 50 */ +#define VEN_LEVEL_H263_60 0x1B/* H.263 Level 60 */ +#define VEN_LEVEL_H263_70 0x1C/* 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) + +#define VEN_IOCTL_SET_METABUFFER_MODE \ + _IOW(VEN_IOCTLBASE_ENC, 47, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - unsigned int, OutputData - NULL.*/ +#define VEN_IOCTL_SET_EXTRADATA \ + _IOW(VEN_IOCTLBASE_ENC, 48, struct venc_ioctl_msg) +/*IOCTL params:GET: InputData - NULL, OutputData - unsigned int.*/ +#define VEN_IOCTL_GET_EXTRADATA \ + _IOR(VEN_IOCTLBASE_ENC, 49, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - NULL, OutputData - NULL.*/ +#define VEN_IOCTL_SET_SLICE_DELIVERY_MODE \ + _IO(VEN_IOCTLBASE_ENC, 50) + +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/mtd/partitions.h b/include/linux/mtd/partitions.h index 2475228c1158e6f54e4f7ce02b2607ee9f487922..d7245383b55ecb6819173092d63dac48b917c1e6 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -64,6 +64,8 @@ struct mtd_part_parser_data { }; +void part_fill_badblockstats(struct mtd_info *mtd); + /* * Functions dealing with the various ways of partitioning the space */ diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index e474f6e780cc7261731d8bb81f45361f8f858d1c..89a84219d4278a848cb5b0391ec9353634a0f424 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -1129,6 +1129,55 @@ enum nl80211_commands { * %NL80211_CMD_SET_BEACON to provide extra IEs (e.g., WPS/P2P IE) into * (Re)Association Response frames when the driver (or firmware) replies to * (Re)Association Request frames. + * @NL80211_ATTR_STA_WME: Nested attribute containing the wme configuration + * of the station, see &enum nl80211_sta_wme_attr. + * @NL80211_ATTR_SUPPORT_AP_UAPSD: the device supports uapsd when working + * as AP. + * + * @NL80211_ATTR_ROAM_SUPPORT: Indicates whether the firmware is capable of + * roaming to another AP in the same ESS if the signal lever is low. + * + * @NL80211_ATTR_PMKSA_CANDIDATE: Nested attribute containing the PMKSA caching + * candidate information, see &enum nl80211_pmksa_candidate_attr. + * + * @NL80211_ATTR_TX_NO_CCK_RATE: Indicates whether to use CCK rate or not + * for management frames transmission. In order to avoid p2p probe/action + * frames are being transmitted at CCK rate in 2GHz band, the user space + * applications use this attribute. + * This attribute is used with %NL80211_CMD_TRIGGER_SCAN and + * %NL80211_CMD_FRAME commands. + * + * @NL80211_ATTR_TDLS_ACTION: Low level TDLS action code (e.g. link setup + * request, link setup confirm, link teardown, etc.). Values are + * described in the TDLS (802.11z) specification. + * @NL80211_ATTR_TDLS_DIALOG_TOKEN: Non-zero token for uniquely identifying a + * TDLS conversation between two devices. + * @NL80211_ATTR_TDLS_OPERATION: High level TDLS operation; see + * &enum nl80211_tdls_operation, represented as a u8. + * @NL80211_ATTR_TDLS_SUPPORT: A flag indicating the device can operate + * as a TDLS peer sta. + * @NL80211_ATTR_TDLS_EXTERNAL_SETUP: The TDLS discovery/setup and teardown + * procedures should be performed by sending TDLS packets via + * %NL80211_CMD_TDLS_MGMT. Otherwise %NL80211_CMD_TDLS_OPER should be + * used for asking the driver to perform a TDLS operation. + * + * @NL80211_ATTR_DEVICE_AP_SME: This u32 attribute may be listed for devices + * that have AP support to indicate that they have the AP SME integrated + * with support for the features listed in this attribute, see + * &enum nl80211_ap_sme_features. + * + * @NL80211_ATTR_DONT_WAIT_FOR_ACK: Used with %NL80211_CMD_FRAME, this tells + * the driver to not wait for an acknowledgement. Note that due to this, + * it will also not give a status callback nor return a cookie. This is + * mostly useful for probe responses to save airtime. + * + * @NL80211_ATTR_FEATURE_FLAGS: This u32 attribute contains flags from + * &enum nl80211_feature_flags and is advertised in wiphy information. + * @NL80211_ATTR_PROBE_RESP_OFFLOAD: Indicates that the HW responds to probe + * + * requests while operating in AP-mode. + * This attribute holds a bitmap of the supported protocols for + * offloading (see &enum nl80211_probe_resp_offload_support_attr). * * @NL80211_ATTR_STA_WME: Nested attribute containing the wme configuration * of the station, see &enum nl80211_sta_wme_attr. diff --git a/include/linux/of_slimbus.h b/include/linux/of_slimbus.h new file mode 100644 index 0000000000000000000000000000000000000000..8e1dc657401ec006a60a905d68f78de54489c1d6 --- /dev/null +++ b/include/linux/of_slimbus.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_OF_SLIMBUS +/* + * of_slim_register_devices() - Register devices in the SLIMbus Device Tree + * @ctrl: slim_controller which devices should be registered to. + * + * This routine scans the SLIMbus Device Tree, allocating resources and + * creating slim_devices according to the SLIMbus Device Tree + * hierarchy. Details of this hierarchy can be found in + * Documentation/devicetree/bindings/slimbus. This routine is normally + * called from the probe routine of the driver registering as a + * slim_controller. + */ +extern int of_register_slim_devices(struct slim_controller *ctrl); +#else +static int of_register_slim_devices(struct slim_controller *ctrl) +{ + return 0; +} +#endif /* CONFIG_OF_SLIMBUS */ diff --git a/include/linux/of_spmi.h b/include/linux/of_spmi.h new file mode 100644 index 0000000000000000000000000000000000000000..fe09dec4538927fb119b76bd0c4c30eeaf0cc78c --- /dev/null +++ b/include/linux/of_spmi.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_OF_SPMI +/** + * of_spmi_register_devices() - Register devices in the SPMI Device Tree + * @ctrl: spmi_controller which devices should be registered to. + * + * This routine scans the SPMI Device Tree, allocating resources and + * creating spmi_devices according to the SPMI bus Device Tree + * hierarchy. Details of this hierarchy can be found in + * Documentation/devicetree/bindings/spmi. This routine is normally + * called from the probe routine of the driver registering as a + * spmi_controller. + */ +int of_spmi_register_devices(struct spmi_controller *ctrl); +#else +static int of_spmi_register_devices(struct spmi_controller *ctrl) +{ + return -ENXIO; +} +#endif /* CONFIG_OF_SPMI */ diff --git a/include/linux/ofn_atlab.h b/include/linux/ofn_atlab.h new file mode 100644 index 0000000000000000000000000000000000000000..16c34d7d97ce0380b9e557b4e5bc82d588f2b6f4 --- /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/pkt_sched.h b/include/linux/pkt_sched.h index 410b33d014d2dfc3fb0c8ad0c6bb0c20a68ddbc2..be68612de50bc250295194d0098421ca099ce807 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 0000000000000000000000000000000000000000..08aa784fa8c9599b166d42ec88943c3ed2408d96 --- /dev/null +++ b/include/linux/platform_data/qcom_crypto_device.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 __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; + void *bus_scale_table; +}; + +#endif /* __QCOM_CRYPTO_DEVICE__H */ diff --git a/include/linux/platform_data/qcom_wcnss_device.h b/include/linux/platform_data/qcom_wcnss_device.h new file mode 100644 index 0000000000000000000000000000000000000000..e904084a2d40607613c549ac5e9355b04f197116 --- /dev/null +++ b/include/linux/platform_data/qcom_wcnss_device.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_WCNSS_DEVICE__H +#define __QCOM_WCNSS_DEVICE__H + +struct qcom_wcnss_opts { + bool has_48mhz_xo; +}; + +#endif /* __QCOM_WCNSS_DEVICE__H */ diff --git a/include/linux/platform_data/ram_console.h b/include/linux/platform_data/ram_console.h new file mode 100644 index 0000000000000000000000000000000000000000..9f1125c110660b805a718271a19c128d71bfbde9 --- /dev/null +++ b/include/linux/platform_data/ram_console.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _INCLUDE_LINUX_PLATFORM_DATA_RAM_CONSOLE_H_ +#define _INCLUDE_LINUX_PLATFORM_DATA_RAM_CONSOLE_H_ + +struct ram_console_platform_data { + const char *bootinfo; +}; + +#endif /* _INCLUDE_LINUX_PLATFORM_DATA_RAM_CONSOLE_H_ */ diff --git a/include/linux/pmic8058-charger.h b/include/linux/pmic8058-charger.h new file mode 100644 index 0000000000000000000000000000000000000000..0fbc828164595ee40ed3cf391658eb9c045543c4 --- /dev/null +++ b/include/linux/pmic8058-charger.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 __PMIC8058_CHARGER_H__ +#define __PMIC8058_CHARGER_H__ +/** + * enum pmic8058_chg_state - pmic8058 charging states + * @PMIC8058_CHG_STATE_NONE: Initial off state + * @PMIC8058_CHG_STATE_PWR_CHG: Device powered from charger + * @PMIC8058_CHG_STATE_ATC: Device is Auto Tricke Charged (ATC) + * @PMIC8058_CHG_STATE_PWR_BAT: Device powered from Battery + * @PMIC8058_CHG_STATE_ATC_FAIL: ATC failed + * @PMIC8058_CHG_STATE_AUX_EN: Transient state + * @PMIC8058_CHG_STATE_PON_AFTER_ATC: Power on from battery and chg with limit + * of 90mA + * @PMIC8058_CHG_STATE_FAST_CHG: pmic is fast charging the battery + * @PMIC8058_CHG_STATE_TRKL_CHG: pmic is trck charging the battery + * @PMIC8058_CHG_STATE_CHG_FAIL: charging failed + * @PMIC8058_CHG_STATE_EOC: end of charging reached + * @PMIC8058_CHG_STATE_INRUSH_LIMIT: Brings up Vdd with 90mA max drawn from + * VBUS + * @PMIC8058_CHG_STATE_USB_SUSPENDED: USB supended, no current drawn from VBUS + * @PMIC8058_CHG_STATE_PAUSE_ATC: ATC paused + * @PMIC8058_CHG_STATE_PAUSE_FAST_CHG: FAST charging paused + * @PMIC8058_CHG_STATE_PAUSE_TRKL_CHG: TRLK charging paused + * + * The paused states happen when a unfavourable condition for charging is + * detected. The most common one being the battery gets too hot ot gets + * too cold for charging. + */ +enum pmic8058_chg_state { + PMIC8058_CHG_STATE_NONE, + PMIC8058_CHG_STATE_PWR_CHG, + PMIC8058_CHG_STATE_ATC, + PMIC8058_CHG_STATE_PWR_BAT, + PMIC8058_CHG_STATE_ATC_FAIL, + PMIC8058_CHG_STATE_AUX_EN, + PMIC8058_CHG_STATE_PON_AFTER_ATC, + PMIC8058_CHG_STATE_FAST_CHG, + PMIC8058_CHG_STATE_TRKL_CHG, + PMIC8058_CHG_STATE_CHG_FAIL, + PMIC8058_CHG_STATE_EOC, + PMIC8058_CHG_STATE_INRUSH_LIMIT, + PMIC8058_CHG_STATE_USB_SUSPENDED, + PMIC8058_CHG_STATE_PAUSE_ATC, + PMIC8058_CHG_STATE_PAUSE_FAST_CHG, + PMIC8058_CHG_STATE_PAUSE_TRKL_CHG +}; + +#if defined(CONFIG_BATTERY_MSM8X60) || defined(CONFIG_BATTERY_MSM8X60_MODULE) +int pmic8058_get_charge_batt(void); +int pmic8058_set_charge_batt(int); +/** + * pmic8058_get_fsm_state - + * + * CONTEXT: may sleep - should not be called from non-atomic context + * + * RETURNS: The pmic internal state, or error otherwise + */ +enum pmic8058_chg_state pmic8058_get_fsm_state(void); +#else +int pmic8058_get_charge_batt(void) +{ + return -ENXIO; +} +int pmic8058_set_charge_batt(int) +{ + return -ENXIO; +} +enum pmic8058_chg_state pmic8058_get_fsm_state(void) +{ + return -ENXIO; +} +#endif +#endif /* __PMIC8058_CHARGER_H__ */ diff --git a/include/linux/pmic8058-othc.h b/include/linux/pmic8058-othc.h new file mode 100644 index 0000000000000000000000000000000000000000..4c598452c86963dfa8a77be1bc3b9da4e0fab11a --- /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 0000000000000000000000000000000000000000..d380170ba5fd7e5287d1ee7874719b2441cc72c5 --- /dev/null +++ b/include/linux/pmic8058-pwm.h @@ -0,0 +1,148 @@ +/* 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 + +/** + * PWM frequency/period control + * + * PWM Frequency = ClockFrequency / (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) + * + */ + +enum pm_pwm_size { + PM_PWM_SIZE_6BIT = 6, + PM_PWM_SIZE_9BIT = 9, +}; + +enum pm_pwm_clk { + PM_PWM_CLK_1KHZ, + PM_PWM_CLK_32KHZ, + PM_PWM_CLK_19P2MHZ, +}; + +enum pm_pwm_pre_div { + PM_PWM_PDIV_2, + PM_PWM_PDIV_3, + PM_PWM_PDIV_5, + PM_PWM_PDIV_6, +}; + +/** + * struct pm8058_pwm_period - PWM period structure + * @pwm_size: enum pm_pwm_size + * @clk: enum pm_pwm_clk + * @pre_div: enum pm_pwm_pre_div + * @pre_div_exp: exponent of 2 as part of pre-divider: 0..7 + */ +struct pm8058_pwm_period { + enum pm_pwm_size pwm_size; + enum pm_pwm_clk clk; + enum pm_pwm_pre_div pre_div; + int pre_div_exp; +}; + +/** + * pm8058_pwm_config_period - change PWM period + * + * @pwm: the PWM device + * @pwm_p: period in struct pm8058_pwm_period + */ +int pm8058_pwm_config_period(struct pwm_device *pwm, + struct pm8058_pwm_period *pwm_p); + +/** + * pm8058_pwm_config_duty_cycle - change PWM duty cycle + * + * @pwm: the PWM device + * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size) + */ +int pm8058_pwm_config_duty_cycle(struct pwm_device *pwm, int pwm_value); + +/** + * 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-xoadc.h b/include/linux/pmic8058-xoadc.h new file mode 100644 index 0000000000000000000000000000000000000000..5163b6503603d0d4b5746d0194d3e7419e58dbac --- /dev/null +++ b/include/linux/pmic8058-xoadc.h @@ -0,0 +1,121 @@ +/* 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 _PMIC8058_XOADC_H_ +#define _PMIC8058_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; +}; + +#ifdef CONFIG_PMIC8058_XOADC +int32_t pm8058_xoadc_read_adc_code(uint32_t adc_instance, int32_t *data); + +int32_t pm8058_xoadc_select_chan_and_start_conv(uint32_t adc_instance, + struct adc_conv_slot *slot); + +void pm8058_xoadc_slot_request(uint32_t adc_instance, + struct adc_conv_slot **slot); + +void pm8058_xoadc_restore_slot(uint32_t adc_instance, + struct adc_conv_slot *slot); + +struct adc_properties *pm8058_xoadc_get_properties(uint32_t dev_instance); + +int32_t pm8058_xoadc_calibrate(uint32_t dev_instance, + struct adc_conv_slot *slot, int * calib_status); + +int32_t pm8058_xoadc_registered(void); + +int32_t pm8058_xoadc_calib_device(uint32_t adc_instance); + +#else + +static inline int32_t pm8058_xoadc_read_adc_code(uint32_t adc_instance, + int32_t *data) +{ return -ENXIO; } + +static inline int32_t pm8058_xoadc_select_chan_and_start_conv( + uint32_t adc_instance, struct adc_conv_slot *slot) +{ return -ENXIO; } + +static inline void pm8058_xoadc_slot_request(uint32_t adc_instance, + struct adc_conv_slot **slot) +{ return; } + +static inline void pm8058_xoadc_restore_slot(uint32_t adc_instance, + struct adc_conv_slot *slot) +{ return; } + +static inline struct adc_properties *pm8058_xoadc_get_properties( + uint32_t dev_instance) +{ return NULL; } + +static inline int32_t pm8058_xoadc_calibrate(uint32_t dev_instance, + struct adc_conv_slot *slot, int *calib_status) +{ return -ENXIO; } + +static inline int32_t pm8058_xoadc_registered(void) +{ return -ENXIO; } + +static inline int32_t pm8058_xoadc_calib_device(uint32_t adc_instance) +{ return -ENXIO; } +#endif +#endif diff --git a/include/linux/power/ltc4088-charger.h b/include/linux/power/ltc4088-charger.h new file mode 100644 index 0000000000000000000000000000000000000000..7a0bacfa0ded79d5a4756fdfa415bd5070714712 --- /dev/null +++ b/include/linux/power/ltc4088-charger.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 LTC4088_CHARGER_H_ +#define LTC4088_CHARGER_H_ + +#define LTC4088_CHARGER_DEV_NAME "ltc4088-charger" + +/** + * struct ltc4088_charger_platform_data - platform data for LTC4088 charger + * @gpio_mode_select_d0: GPIO #pin for D0 charger line + * @gpio_mode_select_d1: GPIO #pin for D1 charger line + * @gpio_mode_select_d2: GPIO #pin for D2 charger line + */ +struct ltc4088_charger_platform_data { + unsigned int gpio_mode_select_d0; + unsigned int gpio_mode_select_d1; + unsigned int gpio_mode_select_d2; +}; + +#endif /* LTC4088_CHARGER_H_ */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index e078bd784d53e469a4805eeba2b5d5e093f01df3..643c80e341eb7bb7b4feceef27217ceeaa9575ed 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -214,6 +214,9 @@ extern struct power_supply *power_supply_get_by_name(char *name); extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); extern int power_supply_set_battery_charged(struct power_supply *psy); +extern int power_supply_set_current_limit(struct power_supply *psy, int limit); +extern int power_supply_set_online(struct power_supply *psy, bool enable); +extern int power_supply_set_charge_type(struct power_supply *psy, int type); #if defined(CONFIG_POWER_SUPPLY) || defined(CONFIG_POWER_SUPPLY_MODULE) extern int power_supply_is_system_supplied(void); diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 7c775751392c58003a896a0c2741cb134343ce9d..e0c8c3fd5f49066379b555292c0883277a052b04 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/qcedev.h b/include/linux/qcedev.h new file mode 100644 index 0000000000000000000000000000000000000000..87040df06564ec1261cc5edc5b0e3cfc9b729f65 --- /dev/null +++ b/include/linux/qcedev.h @@ -0,0 +1,241 @@ +#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_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 +*/ +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; +}; + + +#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/include/linux/qcomwlan7x27a_pwrif.h b/include/linux/qcomwlan7x27a_pwrif.h new file mode 100644 index 0000000000000000000000000000000000000000..16e1783f39e00eab7b45c60e608f91f6807c7ac0 --- /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 0000000000000000000000000000000000000000..74d2a800629e211db56ff5ba89f6efbaed10d86e --- /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 0000000000000000000000000000000000000000..8c6e42532596c476e14ad033e26a0c7a7d6efbc5 --- /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 0000000000000000000000000000000000000000..afc6b7fbd154b27b251346f3f4fa871d11432918 --- /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 0000000000000000000000000000000000000000..8e3fd5e680ba2afb5757b166a855a426fa6e7e74 --- /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/qpnp/gpio.h b/include/linux/qpnp/gpio.h new file mode 100644 index 0000000000000000000000000000000000000000..e7fb53e93af9501d1ece4c8ec01e0f645a0b838a --- /dev/null +++ b/include/linux/qpnp/gpio.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 QPNP_GPIO_DIR_IN 0 +#define QPNP_GPIO_DIR_OUT 1 +#define QPNP_GPIO_DIR_BOTH 2 + +#define QPNP_GPIO_INVERT_DISABLE 0 +#define QPNP_GPIO_INVERT_ENABLE 1 + +#define QPNP_GPIO_OUT_BUF_CMOS 0 +#define QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS 1 +#define QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS 2 + +#define QPNP_GPIO_VIN0 0 +#define QPNP_GPIO_VIN1 1 +#define QPNP_GPIO_VIN2 2 +#define QPNP_GPIO_VIN3 3 +#define QPNP_GPIO_VIN4 4 +#define QPNP_GPIO_VIN5 5 +#define QPNP_GPIO_VIN6 6 +#define QPNP_GPIO_VIN7 7 + +#define QPNP_GPIO_PULL_UP_30 0 +#define QPNP_GPIO_PULL_UP_1P5 1 +#define QPNP_GPIO_PULL_UP_31P5 2 +#define QPNP_GPIO_PULL_UP_1P5_30 3 +#define QPNP_GPIO_PULL_DN 4 +#define QPNP_GPIO_PULL_NO 5 + +#define QPNP_GPIO_OUT_STRENGTH_LOW 1 +#define QPNP_GPIO_OUT_STRENGTH_MED 2 +#define QPNP_GPIO_OUT_STRENGTH_HIGH 3 + +#define QPNP_GPIO_SRC_FUNC_NORMAL 0 +#define QPNP_GPIO_SRC_FUNC_PAIRED 1 +#define QPNP_GPIO_SRC_FUNC_1 2 +#define QPNP_GPIO_SRC_FUNC_2 3 +#define QPNP_GPIO_SRC_DTEST1 4 +#define QPNP_GPIO_SRC_DTEST2 5 +#define QPNP_GPIO_SRC_DTEST3 6 +#define QPNP_GPIO_SRC_DTEST4 7 + +#define QPNP_GPIO_MASTER_DISABLE 0 +#define QPNP_GPIO_MASTER_ENABLE 1 + +/** + * struct qpnp_gpio_cfg - structure to specify gpio configurtion values + * @direction: indicates whether the gpio should be input, output, or + * both. Should be of the type QPNP_GPIO_DIR_* + * @output_type: indicates gpio should be configured as CMOS or open + * drain. Should be of the type QPNP_GPIO_OUT_BUF_* + * @invert: Invert the signal of the gpio line - + * QPNP_GPIO_INVERT_DISABLE or QPNP_GPIO_INVERT_ENABLE + * @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 QPNP_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 QPNP_GPIO_STRENGTH_* + * @source_sel: 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 QPNP_GPIO_SRC_*. + * @master_en: QPNP_GPIO_MASTER_ENABLE = Enable features within the + * GPIO block based on configurations. + * QPNP_GPIO_MASTER_DISABLE = Completely disable the GPIO + * block and let the pin float with high impedance + * regardless of other settings. + */ +struct qpnp_gpio_cfg { + unsigned int direction; + unsigned int output_type; + unsigned int invert; + unsigned int pull; + unsigned int vin_sel; + unsigned int out_strength; + unsigned int src_select; + unsigned int master_en; +}; + +/** + * qpnp_gpio_config - Apply gpio configuration for Linux gpio + * @gpio: Linux gpio number to configure. + * @param: parameters to configure. + * + * This routine takes a Linux gpio number that corresponds with a + * PMIC gpio and applies the configuration specified in 'param'. + * This gpio number can be ascertained by of_get_gpio_flags() or + * the qpnp_gpio_map_gpio() API. + */ +int qpnp_gpio_config(int gpio, struct qpnp_gpio_cfg *param); + +/** + * qpnp_gpio_map_gpio - Obtain Linux GPIO number from device spec + * @slave_id: slave_id of the spmi_device for the gpio in question. + * @pmic_gpio: PMIC gpio number to lookup. + * + * This routine is used in legacy configurations that do not support + * Device Tree. If you are using Device Tree, you should not use this. + * For such cases, use of_get_gpio() instead. + */ +int qpnp_gpio_map_gpio(uint16_t slave_id, uint32_t pmic_gpio); diff --git a/include/linux/qseecom.h b/include/linux/qseecom.h new file mode 100644 index 0000000000000000000000000000000000000000..62b5efef27ec3b32b2dec3b35975894595361a94 --- /dev/null +++ b/include/linux/qseecom.h @@ -0,0 +1,155 @@ +#ifndef __QSEECOM_H_ +#define __QSEECOM_H_ + +#include +#include + +#define MAX_ION_FD 4 +#define MAX_APP_NAME_SIZE 32 + +/* + * struct qseecom_register_listener_req - + * for register listener ioctl request + * @listener_id - service id (shared between userspace and QSE) + * @ifd_data_fd - ion handle + * @virt_sb_base - shared buffer base in user space + * @sb_size - shared buffer size + */ +struct qseecom_register_listener_req { + uint32_t listener_id; /* in */ + int32_t ifd_data_fd; /* in */ + uint32_t virt_sb_base; /* in */ + uint32_t sb_size; /* in */ +}; + +/* + * struct qseecom_send_cmd_req - for send command ioctl request + * @cmd_req_len - command buffer length + * @cmd_req_buf - command buffer + * @resp_len - response buffer length + * @resp_buf - response buffer + */ +struct qseecom_send_cmd_req { + void *cmd_req_buf; /* in */ + unsigned int cmd_req_len; /* in */ + void *resp_buf; /* in/out */ + unsigned int resp_len; /* in/out */ +}; + + +/* + * struct qseecom_ion_fd_info - ion fd handle data information + * @fd - ion handle to some memory allocated in user space + * @cmd_buf_offset - command buffer offset + */ +struct qseecom_ion_fd_info { + int32_t fd; + uint32_t cmd_buf_offset; +}; +/* + * struct qseecom_send_modfd_cmd_req - for send command ioctl request + * @cmd_req_len - command buffer length + * @cmd_req_buf - command buffer + * @resp_len - response buffer length + * @resp_buf - response buffer + * @ifd_data_fd - ion handle to memory allocated in user space + * @cmd_buf_offset - command buffer offset + */ +struct qseecom_send_modfd_cmd_req { + void *cmd_req_buf; /* in */ + unsigned int cmd_req_len; /* in */ + void *resp_buf; /* in/out */ + unsigned int resp_len; /* in/out */ + struct qseecom_ion_fd_info ifd_data[MAX_ION_FD]; +}; +/* + * struct qseecom_listener_send_resp_req - signal to continue the send_cmd req. + * Used as a trigger from HLOS service to notify QSEECOM that it's done with its + * operation and provide the response for QSEECOM can continue the incomplete + * command execution + * @resp_len - Length of the response + * @resp_buf - Response buffer where the response of the cmd should go. + */ +struct qseecom_send_resp_req { + void *resp_buf; /* in */ + unsigned int resp_len; /* in */ +}; + +/* + * struct qseecom_load_img_data - for sending image length information and + * ion file descriptor to the qseecom driver. ion file descriptor is used + * for retrieving the ion file handle and in turn the physical address of + * the image location. + * @mdt_len - Length of the .mdt file in bytes. + * @img_len - Length of the .mdt + .b00 +..+.bxx images files in bytes + * @ion_fd - Ion file descriptor used when allocating memory. + * @img_name - Name of the image. +*/ +struct qseecom_load_img_req { + uint32_t mdt_len; /* in */ + uint32_t img_len; /* in */ + int32_t ifd_data_fd; /* in */ + char img_name[MAX_APP_NAME_SIZE]; /* in */ + int app_id; /* out*/ +}; + +struct qseecom_set_sb_mem_param_req { + int32_t ifd_data_fd; /* in */ + uint32_t virt_sb_base; /* in */ + uint32_t sb_len; /* in */ +}; + +/* + * struct qseecom_qseos_version_req - get qseos version + * @qseos_version - version number + */ +struct qseecom_qseos_version_req { + unsigned int qseos_version; /* in */ +}; + +#define QSEECOM_IOC_MAGIC 0x97 + + +#define QSEECOM_IOCTL_REGISTER_LISTENER_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 1, struct qseecom_register_listener_req) + +#define QSEECOM_IOCTL_UNREGISTER_LISTENER_REQ \ + _IO(QSEECOM_IOC_MAGIC, 2) + +#define QSEECOM_IOCTL_SEND_CMD_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 3, struct qseecom_send_cmd_req) + +#define QSEECOM_IOCTL_SEND_MODFD_CMD_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 4, struct qseecom_send_modfd_cmd_req) + +#define QSEECOM_IOCTL_RECEIVE_REQ \ + _IO(QSEECOM_IOC_MAGIC, 5) + +#define QSEECOM_IOCTL_SEND_RESP_REQ \ + _IO(QSEECOM_IOC_MAGIC, 6) + +#define QSEECOM_IOCTL_LOAD_APP_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 7, struct qseecom_load_img_req) + +#define QSEECOM_IOCTL_SET_MEM_PARAM_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 8, struct qseecom_set_sb_mem_param_req) + +#define QSEECOM_IOCTL_UNLOAD_APP_REQ \ + _IO(QSEECOM_IOC_MAGIC, 9) + +#define QSEECOM_IOCTL_GET_QSEOS_VERSION_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 10, struct qseecom_qseos_version_req) + +#define QSEECOM_IOCTL_PERF_ENABLE_REQ \ + _IO(QSEECOM_IOC_MAGIC, 11) + +#define QSEECOM_IOCTL_PERF_DISABLE_REQ \ + _IO(QSEECOM_IOC_MAGIC, 12) + +#define QSEECOM_IOCTL_LOAD_EXTERNAL_ELF_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 13, struct qseecom_load_img_req) + +#define QSEECOM_IOCTL_UNLOAD_EXTERNAL_ELF_REQ \ + _IO(QSEECOM_IOC_MAGIC, 14) + +#endif /* __QSEECOM_H_ */ diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index 4ed1b30ac5fc9ff04cf2a50b1b4ba91e1718e2b7..23c72674d7f964a8c331566afa4812c9367deb26 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -35,6 +35,8 @@ #ifndef __LINUX_REGULATOR_CONSUMER_H_ #define __LINUX_REGULATOR_CONSUMER_H_ +#include + struct device; struct notifier_block; @@ -115,6 +117,10 @@ struct regulator; * using the bulk regulator APIs. * @consumer: The regulator consumer for the supply. This will be managed * by the bulk API. + * @min_uV: The minimum requested voltage for the regulator (in microvolts), + * or 0 to not set a voltage. + * @max_uV: The maximum requested voltage for the regulator (in microvolts), + * or 0 to use @min_uV. * * The regulator APIs provide a series of regulator_bulk_() API calls as * a convenience to consumers which require multiple supplies. This @@ -123,6 +129,8 @@ struct regulator; struct regulator_bulk_data { const char *supply; struct regulator *consumer; + int min_uV; + int max_uV; /* private: Internal use */ int ret; @@ -153,6 +161,8 @@ int devm_regulator_bulk_get(struct device *dev, int num_consumers, struct regulator_bulk_data *consumers); int regulator_bulk_enable(int num_consumers, struct regulator_bulk_data *consumers); +int regulator_bulk_set_voltage(int num_consumers, + struct regulator_bulk_data *consumers); int regulator_bulk_disable(int num_consumers, struct regulator_bulk_data *consumers); int regulator_bulk_force_disable(int num_consumers, @@ -283,6 +293,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/machine.h b/include/linux/regulator/machine.h index b02108446be756cc1c216271e21ac3cbf7a132d8..837423fdcdfc8247f4106ecb5a1f03922ebdc060 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -188,6 +188,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) { @@ -196,6 +197,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/msm-gpio-regulator.h b/include/linux/regulator/msm-gpio-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..9efda85d7225f1348b8545115b409606a1532058 --- /dev/null +++ b/include/linux/regulator/msm-gpio-regulator.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_GPIO_REGULATOR_H__ +#define __MSM_GPIO_REGULATOR_H__ + +#include + +#define GPIO_REGULATOR_DEV_NAME "msm-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/pm8058-xo.h b/include/linux/regulator/pm8058-xo.h new file mode 100644 index 0000000000000000000000000000000000000000..a2b8aeba205db44ad5cb8c18f3ed67f019ba1274 --- /dev/null +++ b/include/linux/regulator/pm8058-xo.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 __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; + int id; +}; + +#endif diff --git a/include/linux/regulator/pm8xxx-regulator.h b/include/linux/regulator/pm8xxx-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..21352e808f39321733c6f76fb89b5a33bd6aab9f --- /dev/null +++ b/include/linux/regulator/pm8xxx-regulator.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __REGULATOR_PM8XXX_REGULATOR_H__ +#define __REGULATOR_PM8XXX_REGULATOR_H__ + +#include +#include + +#define PM8XXX_REGULATOR_DEV_NAME "pm8xxx-regulator" + +/* Pin control input pins. */ +#define PM8XXX_VREG_PIN_CTRL_NONE 0x00 +#define PM8XXX_VREG_PIN_CTRL_EN0 0x01 +#define PM8XXX_VREG_PIN_CTRL_EN1 0x02 +#define PM8XXX_VREG_PIN_CTRL_EN2 0x04 +#define PM8XXX_VREG_PIN_CTRL_EN3 0x08 +#define PM8XXX_VREG_PIN_CTRL_ALL 0x0F + +#define PM8921_VREG_PIN_CTRL_NONE PM8XXX_VREG_PIN_CTRL_NONE +#define PM8921_VREG_PIN_CTRL_D1 PM8XXX_VREG_PIN_CTRL_EN0 +#define PM8921_VREG_PIN_CTRL_A0 PM8XXX_VREG_PIN_CTRL_EN1 +#define PM8921_VREG_PIN_CTRL_A1 PM8XXX_VREG_PIN_CTRL_EN2 +#define PM8921_VREG_PIN_CTRL_A2 PM8XXX_VREG_PIN_CTRL_EN3 + +/* Minimum high power mode loads in uA. */ +#define PM8XXX_VREG_LDO_50_HPM_MIN_LOAD 5000 +#define PM8XXX_VREG_LDO_150_HPM_MIN_LOAD 10000 +#define PM8XXX_VREG_LDO_300_HPM_MIN_LOAD 10000 +#define PM8XXX_VREG_LDO_600_HPM_MIN_LOAD 10000 +#define PM8XXX_VREG_LDO_1200_HPM_MIN_LOAD 10000 +#define PM8XXX_VREG_SMPS_1500_HPM_MIN_LOAD 100000 +#define PM8XXX_VREG_SMPS_2000_HPM_MIN_LOAD 100000 + +#define REGULATOR_TEST_BANKS_MAX 8 + +/** + * enum pm8xxx_vreg_pin_function - action to perform when pin control is active + * %PM8XXX_VREG_PIN_FN_ENABLE: pin control enables the regulator + * %PM8XXX_VREG_PIN_FN_MODE: pin control changes mode from LPM to HPM + */ +enum pm8xxx_vreg_pin_function { + PM8XXX_VREG_PIN_FN_ENABLE = 0, + PM8XXX_VREG_PIN_FN_MODE, +}; + +/** + * struct pm8xxx_regulator_platform_data - PMIC 8921 regulator platform data + * @init_data: regulator constraints + * @id: regulator id. Any value unique among pm8xxx_regulator + * devices is acceptable. + * @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 PM8XXX_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 + * @enable_time: time in us taken to enable a regulator to the maximum + * allowed voltage for the system. This is dependent upon + * the load and capacitance for a regulator on the board. + * @ocp_enable: enable over current protection logic (available for + * LVS and MVS type switches) + * @ocp_enable_time: time in us to delay between enabling the switch and then + * enabling OCP for it. This delay is needed to avoid + * false triggering due to inrush current. + */ +struct pm8xxx_regulator_platform_data { + struct regulator_init_data init_data; + int id; + unsigned pull_down_enable; + unsigned pin_ctrl; + enum pm8xxx_vreg_pin_function pin_fn; + int system_uA; + int enable_time; + unsigned ocp_enable; + int ocp_enable_time; +}; + +#endif diff --git a/include/linux/regulator/pmic8058-regulator.h b/include/linux/regulator/pmic8058-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..3eeaa61b81bc55d20a601b55960f3f38c1a9b1ec --- /dev/null +++ b/include/linux/regulator/pmic8058-regulator.h @@ -0,0 +1,89 @@ +/* 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; + int id; + 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 0000000000000000000000000000000000000000..ec842bc2b64fe2ad245551118b17e3b861a511b3 --- /dev/null +++ b/include/linux/regulator/pmic8901-regulator.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 __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 + +/* Low voltage switch regulator ids */ +#define PM8901_VREG_ID_LVS0 12 +#define PM8901_VREG_ID_LVS1 13 +#define PM8901_VREG_ID_LVS2 14 +#define PM8901_VREG_ID_LVS3 15 + +/* Medium voltage switch regulator ids */ +#define PM8901_VREG_ID_MVS0 16 + +/* USB OTG voltage switch regulator ids */ +#define PM8901_VREG_ID_USB_OTG 17 + +/* HDMI medium voltage switch regulator ids */ +#define PM8901_VREG_ID_HDMI_MVS 18 + +#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; + int id; + unsigned pull_down_enable; + unsigned pin_ctrl; + enum pm8901_vreg_pin_fn pin_fn; +}; + +#endif diff --git a/include/linux/regulator/qpnp-regulator.h b/include/linux/regulator/qpnp-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..ca8ccd7dab1bac80a621878848fe03638931c782 --- /dev/null +++ b/include/linux/regulator/qpnp-regulator.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __REGULATOR_QPNP_REGULATOR_H__ +#define __REGULATOR_QPNP_REGULATOR_H__ + +#include + +#define QPNP_REGULATOR_DRIVER_NAME "qcom,qpnp-regulator" + +/* Pin control enable input pins. */ +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_NONE 0x00 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN0 0x01 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN1 0x02 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN2 0x04 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN3 0x08 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT 0x10 + +/* Pin control high power mode input pins. */ +#define QPNP_REGULATOR_PIN_CTRL_HPM_NONE 0x00 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN0 0x01 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN1 0x02 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN2 0x04 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN3 0x08 +#define QPNP_REGULATOR_PIN_CTRL_HPM_SLEEP_B 0x10 +#define QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT 0x20 + +/* + * Used with enable parameters to specify that hardware default register values + * should be left unaltered. + */ +#define QPNP_REGULATOR_DISABLE 0 +#define QPNP_REGULATOR_ENABLE 1 +#define QPNP_REGULATOR_USE_HW_DEFAULT 2 + +/* Soft start strength of a voltage switch type regulator */ +enum qpnp_vs_soft_start_str { + QPNP_VS_SOFT_START_STR_0P05_UA, + QPNP_VS_SOFT_START_STR_0P25_UA, + QPNP_VS_SOFT_START_STR_0P55_UA, + QPNP_VS_SOFT_START_STR_0P75_UA, + QPNP_VS_SOFT_START_STR_HW_DEFAULT, +}; + +/* Current limit of a boost type regulator */ +enum qpnp_boost_current_limit { + QPNP_BOOST_CURRENT_LIMIT_300_MA, + QPNP_BOOST_CURRENT_LIMIT_600_MA, + QPNP_BOOST_CURRENT_LIMIT_900_MA, + QPNP_BOOST_CURRENT_LIMIT_1200_MA, + QPNP_BOOST_CURRENT_LIMIT_1500_MA, + QPNP_BOOST_CURRENT_LIMIT_1800_MA, + QPNP_BOOST_CURRENT_LIMIT_2100_MA, + QPNP_BOOST_CURRENT_LIMIT_2400_MA, + QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT, +}; + +/** + * struct qpnp_regulator_platform_data - qpnp-regulator initialization data + * @init_data: regulator constraints + * @pull_down_enable: 1 = Enable output pull down resistor when the + * regulator is disabled + * 0 = Disable pull down resistor + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * pull down state + * @pin_ctrl_enable: Bit mask specifying which hardware pins should be + * used to enable the regulator, if any + * Value should be an ORing of + * QPNP_REGULATOR_PIN_CTRL_ENABLE_* constants. If + * the bit specified by + * QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT is + * set, then pin control enable hardware registers + * will not be modified. + * @pin_ctrl_hpm: Bit mask specifying which hardware pins should be + * used to force the regulator into high power + * mode, if any + * Value should be an ORing of + * QPNP_REGULATOR_PIN_CTRL_HPM_* constants. If + * the bit specified by + * QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT is + * set, then pin control mode hardware registers + * will not be modified. + * @system_load: Load in uA present on regulator that is not captured + * by any consumer request + * @enable_time: Time in us to delay after enabling the regulator + * @ocp_enable: 1 = Enable over current protection (OCP) for voltage + * switch type regulators so that they latch off + * automatically when over current is detected + * 0 = Disable OCP + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * OCP state + * @boost_current_limit: This parameter sets the current limit of boost type + * regulators. Its value should be one of + * QPNP_BOOST_CURRENT_LIMIT_*. If its value is + * QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT, then the + * boost current limit will be left at its default + * hardware value. + * @soft_start_enable: 1 = Enable soft start for LDO and voltage switch + * type regulators so that output voltage slowly + * ramps up when the regulator is enabled + * 0 = Disable soft start + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * soft start state + * @vs_soft_start_strength: This parameter sets the soft start strength for + * voltage switch type regulators. Its value + * should be one of QPNP_VS_SOFT_START_STR_*. If + * its value is QPNP_VS_SOFT_START_STR_HW_DEFAULT, + * then the soft start strength will be left at its + * default hardware value. + * @ocp_enable_time: Time to delay in us between enabling a switch and + * subsequently enabling over current protection + * (OCP) for the switch + * @auto_mode_enable: 1 = Enable automatic hardware selection of regulator + * mode (HPM vs LPM). Auto mode is not available + * on boost type regulators + * 0 = Disable auto mode selection + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * auto mode state + * @bypass_mode_enable: 1 = Enable bypass mode for an LDO type regulator so + * that it acts like a switch and simply outputs + * its input voltage + * 0 = Do not enable bypass mode + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * bypass mode state + * @base_addr: SMPI base address for the regulator peripheral + */ +struct qpnp_regulator_platform_data { + struct regulator_init_data init_data; + int pull_down_enable; + unsigned pin_ctrl_enable; + unsigned pin_ctrl_hpm; + int system_load; + int enable_time; + int ocp_enable; + enum qpnp_boost_current_limit boost_current_limit; + int soft_start_enable; + enum qpnp_vs_soft_start_str vs_soft_start_strength; + int ocp_enable_time; + int auto_mode_enable; + int bypass_mode_enable; + u16 base_addr; +}; + +#ifdef CONFIG_REGULATOR_QPNP + +/** + * qpnp_regulator_init() - register spmi driver for qpnp-regulator + * + * This initialization function should be called in systems in which driver + * registration ordering must be controlled precisely. + */ +int __init qpnp_regulator_init(void); + +#else + +static inline int __init qpnp_regulator_init(void) +{ + return -ENODEV; +} + +#endif /* CONFIG_REGULATOR_QPNP */ + +#endif diff --git a/include/linux/regulator/stub-regulator.h b/include/linux/regulator/stub-regulator.h new file mode 100644 index 0000000000000000000000000000000000000000..e7f41106c093a6acab4e12248e1f01c94d9ff61b --- /dev/null +++ b/include/linux/regulator/stub-regulator.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __STUB_REGULATOR_H__ +#define __STUB_REGULATOR_H__ + +#include + +#define STUB_REGULATOR_DRIVER_NAME "stub-regulator" + +/** + * struct stub_regulator_pdata - stub regulator device data + * @init_data: regulator constraints + * @hpm_min_load: minimum load in uA that will result in the regulator + * being set to high power mode + * @system_uA: current drawn from regulator not accounted for by any + * regulator framework consumer + */ +struct stub_regulator_pdata { + struct regulator_init_data init_data; + int hpm_min_load; + int system_uA; +}; + +int __init regulator_stub_init(void); +#endif diff --git a/include/linux/remote_spinlock.h b/include/linux/remote_spinlock.h new file mode 100644 index 0000000000000000000000000000000000000000..8d7c7e760edd8e2d683c47646e343a01e5f3117f --- /dev/null +++ b/include/linux/remote_spinlock.h @@ -0,0 +1,121 @@ +/* Copyright (c) 2008-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_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; \ + }) + +#define remote_spin_release(lock, pid) \ + _remote_spin_release(&((lock)->remote), pid) + +#define remote_spin_release_all(pid) \ + _remote_spin_release_all(pid) + +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/rfkill.h b/include/linux/rfkill.h index 6fdf02737e9decd563ac8616fb510d4951c11515..7dec42cfa41956c4bf79c79f29c5fa7a8e83a9b8 100644 --- a/include/linux/rfkill.h +++ b/include/linux/rfkill.h @@ -199,8 +199,11 @@ void rfkill_pause_polling(struct rfkill *rfkill); * NOTE: not necessary for suspend/resume -- in that case the * core stops polling anyway */ +#ifdef CONFIG_RFKILL_PM void rfkill_resume_polling(struct rfkill *rfkill); - +#else +static inline void rfkill_resume_polling(struct rfkill *rfkill) { } +#endif /** * rfkill_unregister - Unregister a rfkill structure. diff --git a/include/linux/rmt_storage_client.h b/include/linux/rmt_storage_client.h new file mode 100644 index 0000000000000000000000000000000000000000..f56819ab7cb5296b81fa4cfdedfa8d1143223381 --- /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/rq_stats.h b/include/linux/rq_stats.h new file mode 100644 index 0000000000000000000000000000000000000000..e04063ffeb05189c88075ce844c91eb9a6a378b9 --- /dev/null +++ b/include/linux/rq_stats.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. + * + */ + +struct rq_data { + unsigned int rq_avg; + unsigned long rq_poll_jiffies; + unsigned long def_timer_jiffies; + unsigned long rq_poll_last_jiffy; + unsigned long rq_poll_total_jiffies; + unsigned long def_timer_last_jiffy; + unsigned int def_interval; + int64_t def_start_time; + struct attribute_group *attr_group; + struct kobject *kobj; + struct work_struct def_timer_work; + int init; +}; + +extern spinlock_t rq_lock; +extern struct rq_data rq_info; +extern struct workqueue_struct *rq_wq; diff --git a/include/linux/rtc-msm.h b/include/linux/rtc-msm.h new file mode 100644 index 0000000000000000000000000000000000000000..f8f6a165f12ee02b0d611effac315ec1b674bbd9 --- /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/slimbus/slimbus.h b/include/linux/slimbus/slimbus.h new file mode 100644 index 0000000000000000000000000000000000000000..75b132b2fde318fef0849b15209846da151af32d --- /dev/null +++ b/include/linux/slimbus/slimbus.h @@ -0,0 +1,1037 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_MIN_CLK_GEAR 1 +#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 + +/* + * Clock pause flag to indicate that the reconfig message + * corresponds to clock pause sequence + */ +#define SLIM_MSG_CLK_PAUSE_SEQ_FLG (1U << 8) + +/* 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 + +/* Clock pause values defined in Table 66 (slimbus spec 1.01.01) */ +#define SLIM_CLK_FAST 0 +#define SLIM_CLK_CONST_PHASE 1 +#define SLIM_CLK_UNSPECIFIED 2 + +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. LSB is message code for type mt. Framework will set MSB to + * SLIM_MSG_CLK_PAUSE_SEQ_FLG in case "mc" in the reconfiguration sequence + * is for pausing the clock. + * @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; + u16 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 port used by this channel. + * @sinkh: Sink ports used by this channel. + * @nsink: number of sink ports used by this channel. + * @chan: Channel number sent on hardware lines for this channel. May not be + * equal to array-index into chans if client requested to use number beyond + * channel-array for the controller. + * @ref: Reference number to keep track of how many clients (upto 2) are using + * this channel. + * @def: Used to keep track of how many times the channel definition is sent + * to hardware and this will decide if channel-remove can be sent for the + * channel. Channel definition may be sent upto twice (once per producer + * and once per consumer). Channel removal should be sent only once to + * avoid clients getting underflow/overflow errors. + */ +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; + u32 *sinkh; + int nsink; + u8 chan; + int ref; + int def; +}; + +/* + * 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; +}; + +/* + * enum slim_clk_state: Slimbus controller's clock state used internally for + * maintaining current clock state. + * @SLIM_CLK_ACTIVE: Slimbus clock is active + * @SLIM_CLK_PAUSE_FAILED: Slimbus controlled failed to go in clock pause. + * Hardware-wise, this state is same as active but controller will wait on + * completion before making transition to SLIM_CLK_ACTIVE in framework + * @SLIM_CLK_ENTERING_PAUSE: Slimbus clock pause sequence is being sent on the + * bus. If this succeeds, state changes to SLIM_CLK_PAUSED. If the + * transition fails, state changes to SLIM_CLK_PAUSE_FAILED + * @SLIM_CLK_PAUSED: Slimbus controller clock has paused. + */ +enum slim_clk_state { + SLIM_CLK_ACTIVE, + SLIM_CLK_ENTERING_PAUSE, + SLIM_CLK_PAUSE_FAILED, + SLIM_CLK_PAUSED, +}; +/* + * 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 + * @min_cg: Minimum clock gear supported by this controller (default value: 1) + * @max_cg: Maximum clock gear supported by this controller (default value: 10) + * @clk_state: Controller's clock state from enum slim_clk_state + * @pause_comp: Signals completion of clock pause sequence. This is useful when + * client tries to call slimbus transaction when controller may be entering + * clock pause. + * @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 + * @reserved: Reserved channels that controller wants to use internally + * Clients will be assigned channel numbers after this number + * @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. + * @wakeup: This function pointer implements controller-specific procedure + * to wake it up from clock-pause. Framework will call this to bring + * the controller out of clock pause. + * @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; + int min_cg; + int max_cg; + enum slim_clk_state clk_state; + struct completion pause_comp; + 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; + u8 reserved; + 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 (*wakeup)(struct slim_controller *ctrl); + 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, + u16 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_src: Connect source port to channel. + * @sb: client handle + * @srch: source 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 1 source port. + * Channel specified in chanh needs to be allocated first. + * Returns -EALREADY if source is already configured for this channel. + * Returns -ENOTCONN if channel is not allocated + */ +extern int slim_connect_src(struct slim_device *sb, u32 srch, u16 chanh); + +/* + * slim_connect_sink: Connect sink port(s) to channel. + * @sb: client handle + * @sinkh: sink handle(s) to be connected to this channel + * @nsink: number of sinks + * @chanh: Channel with which the ports need to be associated with. + * Per slimbus specification, a channel may have multiple sink-ports. + * Channel specified in chanh needs to be allocated first. + * Returns -EALREADY if sink is already configured for this channel. + * Returns -ENOTCONN if channel is not allocated + */ +extern int slim_connect_sink(struct slim_device *sb, u32 *sinkh, int nsink, + 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. + * -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_query_ch: Get reference-counted handle for a channel number. Every + * channel is reference counted by one as producer and the others as + * consumer) + * @sb: client handle + * @chan: slimbus channel number + * @chanh: return channel handle + * If request channel number is not in use, it is allocated, and reference + * count is set to one. If the channel was was already allocated, this API + * will return handle to that channel and reference count is incremented. + * -EXFULL is returned if all channels are in use + */ +extern int slim_query_ch(struct slim_device *sb, u8 chan, 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. + * -ENOTCONN is returned if deallocation is tried on a channel that's not + * allocated. + */ +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 channel is already used with different parameters. + * -ENXIO is returned if the channel is not yet allocated. + */ +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. + * -EINVAL is returned if individual control of a grouped-channel is attempted. + */ +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_ctrl_clk_pause: Called by slimbus controller to request clock to be + * paused or woken up out of clock pause + * @ctrl: controller requesting bus to be paused or woken up + * @wakeup: Wakeup this controller from clock pause. + * @restart: Restart time value per spec used for clock pause. This value + * isn't used when controller is to be woken up. + * This API executes clock pause reconfiguration sequence if wakeup is false. + * If wakeup is true, controller's wakeup is called + * Slimbus clock is idle and can be disabled by the controller later. + */ +extern int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, + u8 restart); + +/* + * 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/smsc3503.h b/include/linux/smsc3503.h new file mode 100644 index 0000000000000000000000000000000000000000..66ba003c04851abd873a0c29a3e72beb7fd34bf5 --- /dev/null +++ b/include/linux/smsc3503.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2012, 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 __LINUX_SMSC3503_H__ +#define __LINUX_SMSC3503_H__ + +/*Serial interface Registers*/ +#define SMSC3503_VENDORID 0x00 /*u16 read*/ +#define SMSC3503_PRODUCTID 0x02 /*u16 read*/ +#define SMSC3503_DEVICEID 0x04 /*u16 read*/ + +#define SMSC3503_CONFIG_BYTE_1 0x06 /*u8 read*/ +#define PORT_PWR (1<<0) +#define EOP_DISABLE (1<<3) +#define MTT_ENABLE (1<<4) +#define HS_DISABLE (1<<5) +#define SELF_BUS_PWR (1<<7) + +#define SMSC3503_CONFIG_BYTE_2 0x07 /*u8 read*/ +#define SMSC3503_LANGID 0x11 /*u16 read*/ +#define SMSC3503_MFRSL 0x13 /*u8 read*/ +#define SMSC3503_PRDSL 0x14 /*u8 read*/ +#define SMSC3503_SERSL 0x15 /*u8 read*/ +#define SMSC3503_MANSTR 0x16 /*0x16h-0x53h*/ +#define SMSC3503_PRDSTR 0x54 /*0x54h-0x91h*/ +#define SMSC3503_SERSTR 0x92 /*0x92h-0xCFh*/ + +#define SMSC3503_SP_ILOCK 0xE7 /*u8 read, set,clear*/ +#define CONFIG_N (1<<0) +#define CONNECT_N (1<<1) +#define PRTPWRPINSEL (1<<4) +#define OCSPINSEL (1<<5) + +struct smsc_hub_platform_data { + unsigned hub_reset; +}; + +#endif diff --git a/include/linux/smsc911x.h b/include/linux/smsc911x.h index 4dde70e74822be5d13198a15c0816978f3ff4b2a..0493fbdf776181a545766ea8a0d9fc3451605cb5 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/smux.h b/include/linux/smux.h new file mode 100644 index 0000000000000000000000000000000000000000..308f96904dc80eda6dc6daf0830f83ed6cc878d8 --- /dev/null +++ b/include/linux/smux.h @@ -0,0 +1,297 @@ +/* include/linux/smux.h + * + * Copyright (c) 2012, 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 SMUX_H +#define SMUX_H + +/** + * Logical Channel IDs + * + * This must be identical between local and remote clients. + */ +enum { + /* Data Ports */ + SMUX_DATA_0, + SMUX_DATA_1, + SMUX_DATA_2, + SMUX_DATA_3, + SMUX_DATA_4, + SMUX_DATA_5, + SMUX_DATA_6, + SMUX_DATA_7, + SMUX_DATA_8, + SMUX_DATA_9, + SMUX_USB_RMNET_DATA_0, + SMUX_USB_DUN_0, + SMUX_USB_DIAG_0, + SMUX_SYS_MONITOR_0, + SMUX_CSVT_0, + /* add new data ports here */ + + /* Control Ports */ + SMUX_DATA_CTL_0 = 32, + SMUX_DATA_CTL_1, + SMUX_DATA_CTL_2, + SMUX_DATA_CTL_3, + SMUX_DATA_CTL_4, + SMUX_DATA_CTL_5, + SMUX_DATA_CTL_6, + SMUX_DATA_CTL_7, + SMUX_DATA_CTL_8, + SMUX_DATA_CTL_9, + SMUX_USB_RMNET_CTL_0, + SMUX_USB_DUN_CTL_0_UNUSED, + SMUX_USB_DIAG_CTL_0, + SMUX_SYS_MONITOR_CTL_0, + SMUX_CSVT_CTL_0, + /* add new control ports here */ + + SMUX_TEST_LCID, + SMUX_NUM_LOGICAL_CHANNELS, +}; + +/** + * Notification events that are passed to the notify() function. + * + * If the @metadata argument in the notifier is non-null, then it will + * point to the associated struct smux_meta_* structure. + */ +enum { + SMUX_CONNECTED, /* @metadata is null */ + SMUX_DISCONNECTED, + SMUX_READ_DONE, + SMUX_READ_FAIL, + SMUX_WRITE_DONE, + SMUX_WRITE_FAIL, + SMUX_TIOCM_UPDATE, + SMUX_LOW_WM_HIT, /* @metadata is NULL */ + SMUX_HIGH_WM_HIT, /* @metadata is NULL */ +}; + +/** + * Channel options used to modify channel behavior. + */ +enum { + SMUX_CH_OPTION_LOCAL_LOOPBACK = 1 << 0, + SMUX_CH_OPTION_REMOTE_LOOPBACK = 1 << 1, + SMUX_CH_OPTION_REMOTE_TX_STOP = 1 << 2, +}; + +/** + * Metadata for SMUX_DISCONNECTED notification + * + * @is_ssr: Disconnect caused by subsystem restart + */ +struct smux_meta_disconnected { + int is_ssr; +}; + +/** + * Metadata for SMUX_READ_DONE/SMUX_READ_FAIL notification + * + * @pkt_priv: Packet-specific private data + * @buffer: Buffer pointer passed into msm_smux_write + * @len: Buffer length passed into msm_smux_write + */ +struct smux_meta_read { + void *pkt_priv; + void *buffer; + int len; +}; + +/** + * Metadata for SMUX_WRITE_DONE/SMUX_WRITE_FAIL notification + * + * @pkt_priv: Packet-specific private data + * @buffer: Buffer pointer returned by get_rx_buffer() + * @len: Buffer length returned by get_rx_buffer() + */ +struct smux_meta_write { + void *pkt_priv; + void *buffer; + int len; +}; + +/** + * Metadata for SMUX_TIOCM_UPDATE notification + * + * @tiocm_old: Previous TIOCM state + * @tiocm_new: Current TIOCM state + */ +struct smux_meta_tiocm { + uint32_t tiocm_old; + uint32_t tiocm_new; +}; + + +#ifdef CONFIG_N_SMUX +/** + * Starts the opening sequence for a logical channel. + * + * @lcid Logical channel ID + * @priv Free for client usage + * @notify Event notification function + * @get_rx_buffer Function used to provide a receive buffer to SMUX + * + * @returns 0 for success, <0 otherwise + * + * A channel must be fully closed (either not previously opened or + * msm_smux_close() has been called and the SMUX_DISCONNECTED has been + * recevied. + * + * One the remote side is opened, the client will receive a SMUX_CONNECTED + * event. + */ +int msm_smux_open(uint8_t lcid, void *priv, + void (*notify)(void *priv, int event_type, const void *metadata), + int (*get_rx_buffer)(void *priv, void **pkt_priv, + void **buffer, int size)); + +/** + * Starts the closing sequence for a logical channel. + * + * @lcid Logical channel ID + * @returns 0 for success, <0 otherwise + * + * Once the close event has been acknowledge by the remote side, the client + * will receive a SMUX_DISCONNECTED notification. + */ +int msm_smux_close(uint8_t lcid); + +/** + * Write data to a logical channel. + * + * @lcid Logical channel ID + * @pkt_priv Client data that will be returned with the SMUX_WRITE_DONE or + * SMUX_WRITE_FAIL notification. + * @data Data to write + * @len Length of @data + * + * @returns 0 for success, <0 otherwise + * + * Data may be written immediately after msm_smux_open() is called, but + * the data will wait in the transmit queue until the channel has been + * fully opened. + * + * Once the data has been written, the client will receive either a completion + * (SMUX_WRITE_DONE) or a failure notice (SMUX_WRITE_FAIL). + */ +int msm_smux_write(uint8_t lcid, void *pkt_priv, const void *data, int len); + +/** + * Returns true if the TX queue is currently full (high water mark). + * + * @lcid Logical channel ID + * + * @returns 0 if channel is not full; 1 if it is full; < 0 for error + */ +int msm_smux_is_ch_full(uint8_t lcid); + +/** + * Returns true if the TX queue has space for more packets it is at or + * below the low water mark). + * + * @lcid Logical channel ID + * + * @returns 0 if channel is above low watermark + * 1 if it's at or below the low watermark + * < 0 for error + */ +int msm_smux_is_ch_low(uint8_t lcid); + +/** + * Get the TIOCM status bits. + * + * @lcid Logical channel ID + * + * @returns >= 0 TIOCM status bits + * < 0 Error condition + */ +long msm_smux_tiocm_get(uint8_t lcid); + +/** + * Set/clear the TIOCM status bits. + * + * @lcid Logical channel ID + * @set Bits to set + * @clear Bits to clear + * + * @returns 0 for success; < 0 for failure + * + * If a bit is specified in both the @set and @clear masks, then the clear bit + * definition will dominate and the bit will be cleared. + */ +int msm_smux_tiocm_set(uint8_t lcid, uint32_t set, uint32_t clear); + +/** + * Set or clear channel option using the SMUX_CH_OPTION_* channel + * flags. + * + * @lcid Logical channel ID + * @set Options to set + * @clear Options to clear + * + * @returns 0 for success, < 0 for failure + */ +int msm_smux_set_ch_option(uint8_t lcid, uint32_t set, uint32_t clear); + +#else +static inline int msm_smux_open(uint8_t lcid, void *priv, + void (*notify)(void *priv, int event_type, const void *metadata), + int (*get_rx_buffer)(void *priv, void **pkt_priv, + void **buffer, int size)) +{ + return -ENODEV; +} + +static inline int msm_smux_close(uint8_t lcid) +{ + return -ENODEV; +} + +static inline int msm_smux_write(uint8_t lcid, void *pkt_priv, + const void *data, int len) +{ + return -ENODEV; +} + +static inline int msm_smux_is_ch_full(uint8_t lcid) +{ + return -ENODEV; +} + +static inline int msm_smux_is_ch_low(uint8_t lcid) +{ + return -ENODEV; +} + +static inline long msm_smux_tiocm_get(uint8_t lcid) +{ + return 0; +} + +static inline int msm_smux_tiocm_set(uint8_t lcid, uint32_t set, uint32_t clear) +{ + return -ENODEV; +} + +static inline int msm_smux_set_ch_option(uint8_t lcid, uint32_t set, + uint32_t clear) +{ + return -ENODEV; +} + +#endif /* CONFIG_N_SMUX */ + +#endif /* SMUX_H */ diff --git a/include/linux/spmi.h b/include/linux/spmi.h new file mode 100644 index 0000000000000000000000000000000000000000..927978af32b5efc0930cd1224a4695c6469a4a49 --- /dev/null +++ b/include/linux/spmi.h @@ -0,0 +1,420 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_SPMI_H +#define _LINUX_SPMI_H + +#include +#include +#include + +/* Maximum slave identifier */ +#define SPMI_MAX_SLAVE_ID 16 + +/* SPMI Commands */ +enum spmi_commands { + SPMI_CMD_EXT_WRITE = 0x00, + SPMI_CMD_RESET = 0x10, + SPMI_CMD_SLEEP = 0x11, + SPMI_CMD_SHUTDOWN = 0x12, + SPMI_CMD_WAKEUP = 0x13, + SPMI_CMD_AUTHENTICATE = 0x14, + SPMI_CMD_MSTR_READ = 0x15, + SPMI_CMD_MSTR_WRITE = 0x16, + SPMI_CMD_TRANSFER_BUS_OWNERSHIP = 0x1A, + SPMI_CMD_DDB_MASTER_READ = 0x1B, + SPMI_CMD_DDB_SLAVE_READ = 0x1C, + SPMI_CMD_EXT_READ = 0x20, + SPMI_CMD_EXT_WRITEL = 0x30, + SPMI_CMD_EXT_READL = 0x38, + SPMI_CMD_WRITE = 0x40, + SPMI_CMD_READ = 0x60, + SPMI_CMD_ZERO_WRITE = 0x80, +}; + +struct spmi_device; + +/** + * struct spmi_controller: interface to the SPMI master controller + * @nr: board-specific number identifier for this controller/bus + * @name: name for this controller + * @cmd: sends a non-data command sequence on the SPMI bus. + * @read_cmd: sends a register read command sequence on the SPMI bus. + * @write_cmd: sends a register write command sequence on the SPMI bus. + */ +struct spmi_controller { + struct device dev; + unsigned int nr; + struct list_head list; + int (*cmd)(struct spmi_controller *, u8 opcode, u8 sid); + int (*read_cmd)(struct spmi_controller *, + u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf); + int (*write_cmd)(struct spmi_controller *, + u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf); +}; +#define to_spmi_controller(d) container_of(d, struct spmi_controller, dev) + +/** + * struct spmi_driver: Manage SPMI generic/slave device driver + * @probe: binds this driver to a SPMI device. + * @remove: unbinds this driver from the SPMI 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: SPMI device drivers should initialize name and owner field of + * this structure + * @id_table: list of SPMI devices supported by this driver + */ +struct spmi_driver { + int (*probe)(struct spmi_device *dev); + int (*remove)(struct spmi_device *dev); + void (*shutdown)(struct spmi_device *dev); + int (*suspend)(struct spmi_device *dev, + pm_message_t pmesg); + int (*resume)(struct spmi_device *dev); + + struct device_driver driver; + const struct spmi_device_id *id_table; +}; +#define to_spmi_driver(d) container_of(d, struct spmi_driver, driver) + +/** + * struct spmi_resource: spmi_resource for one device_node + * @num_resources: number of resources for this device node + * @resources: array of resources for this device_node + * @of_node: device_node of the resource in question + */ +struct spmi_resource { + struct resource *resource; + u32 num_resources; + struct device_node *of_node; +}; + +/** + * Client/device handle (struct spmi_device): + * ------------------------------------------ + * This is the client/device handle returned when a SPMI device + * is registered with a controller. + * 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. + * @ctrl: SPMI controller managing the bus hosting this device. + * @dev_node: array of SPMI resources - one entry per device_node. + * @num_dev_node: number of device_node structures. + * @sid: Slave Identifier. + */ +struct spmi_device { + struct device dev; + const char *name; + struct spmi_controller *ctrl; + struct spmi_resource *dev_node; + u32 num_dev_node; + u8 sid; +}; +#define to_spmi_device(d) container_of(d, struct spmi_device, dev) + +/** + * struct spmi_boardinfo: Declare board info for SPMI device bringup. + * @slave_id: slave identifier. + * @spmi_device: device to be registered with the SPMI framework. + * @of_node: pointer to the OpenFirmware device node. + * @dev_node: one spmi_resource for each device_node. + * @num_dev_node: number of device_node structures. + * @platform_data: goes to spmi_device.dev.platform_data + */ +struct spmi_boardinfo { + char name[SPMI_NAME_SIZE]; + uint8_t slave_id; + struct device_node *of_node; + struct spmi_resource *dev_node; + u32 num_dev_node; + const void *platform_data; +}; + +/** + * spmi_driver_register: Client driver registration with SPMI framework. + * @drv: client driver to be associated with client-device. + * + * This API will register the client driver with the SPMI framework. + * It is called from the driver's module-init function. + */ +extern int spmi_driver_register(struct spmi_driver *drv); + +/** + * spmi_driver_unregister - reverse effect of spmi_driver_register + * @sdrv: the driver to unregister + * Context: can sleep + */ +static inline void spmi_driver_unregister(struct spmi_driver *sdrv) +{ + if (sdrv) + driver_unregister(&sdrv->driver); +} + +/** + * spmi_add_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 SPMI framework registers the controller. + * Function will return -EBUSY if the number is in use. + */ +extern int spmi_add_controller(struct spmi_controller *ctrl); + +/** + * spmi_del_controller: Controller tear-down. + * Controller added with the above API is teared down using this API. + */ +extern int spmi_del_controller(struct spmi_controller *ctrl); + +/** + * spmi_busnum_to_ctrl: Map bus number to controller + * @busnum: bus number + * + * Returns controller device representing this bus number + */ +extern struct spmi_controller *spmi_busnum_to_ctrl(u32 bus_num); + +/** + * spmi_alloc_device: Allocate a new SPMI devices. + * @ctrl: controller to which this device is to be added to. + * Context: can sleep + * + * Allows a driver to allocate and initialize a SPMI device without + * registering it immediately. This allows a driver to directly fill + * the spmi_device structure before calling spmi_add_device(). + * + * Caller is responsible to call spmi_add_device() on the returned + * spmi_device. If the caller needs to discard the spmi_device without + * adding it, then spmi_dev_put() should be called. + */ +extern struct spmi_device *spmi_alloc_device(struct spmi_controller *ctrl); + +/** + * spmi_add_device: Add spmi_device allocated with spmi_alloc_device(). + * @spmi_dev: spmi_device to be added (registered). + */ +extern int spmi_add_device(struct spmi_device *spmi_dev); + +/** + * spmi_new_device: Instantiates a new SPMI device + * @ctrl: controller to which this device is to be added to. + * @info: board information for this device. + * + * Returns the new device or NULL. + */ +extern struct spmi_device *spmi_new_device(struct spmi_controller *ctrl, + struct spmi_boardinfo const *info); + +/* spmi_remove_device: Remove the effect of spmi_add_device() */ +extern void spmi_remove_device(struct spmi_device *spmi_dev); + +#ifdef CONFIG_SPMI +/** + * spmi_register_board_info: Board-initialization routine. + * @bus_num: controller number (bus) on which this device will sit. + * @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. + */ +extern int spmi_register_board_info(int busnum, + struct spmi_boardinfo const *info, unsigned n); +#else +static inline int spmi_register_board_info(int busnum, + struct spmi_boardinfo const *info, unsigned n) +{ + return 0; +} +#endif + +static inline void *spmi_get_ctrldata(const struct spmi_controller *ctrl) +{ + return dev_get_drvdata(&ctrl->dev); +} + +static inline void spmi_set_ctrldata(struct spmi_controller *ctrl, void *data) +{ + dev_set_drvdata(&ctrl->dev, data); +} + +static inline void *spmi_get_devicedata(const struct spmi_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void spmi_set_devicedata(struct spmi_device *dev, void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +static inline void spmi_dev_put(struct spmi_device *spmidev) +{ + if (spmidev) + put_device(&spmidev->dev); +} + +/** + * spmi_register_read() - register read + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @ad: slave register address (5-bit address). + * @buf: buffer to be populated with data from the Slave. + * + * Reads 1 byte of data from a Slave device register. + */ +extern int spmi_register_read(struct spmi_controller *ctrl, + u8 sid, u8 ad, u8 *buf); + +/** + * spmi_ext_register_read() - extended register read + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @ad: slave register address (8-bit address). + * @len: the request number of bytes to read (up to 16 bytes). + * @buf: buffer to be populated with data from the Slave. + * + * Reads up to 16 bytes of data from the extended register space on a + * Slave device. + */ +extern int spmi_ext_register_read(struct spmi_controller *ctrl, + u8 sid, u8 ad, u8 *buf, int len); + +/** + * spmi_ext_register_readl() - extended register read long + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @ad: slave register address (16-bit address). + * @len: the request number of bytes to read (up to 8 bytes). + * @buf: buffer to be populated with data from the Slave. + * + * Reads up to 8 bytes of data from the extended register space on a + * Slave device using 16-bit address. + */ +extern int spmi_ext_register_readl(struct spmi_controller *ctrl, + u8 sid, u16 ad, u8 *buf, int len); + +/** + * spmi_register_write() - register write + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @ad: slave register address (5-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * + * Writes 1 byte of data to a Slave device register. + */ +extern int spmi_register_write(struct spmi_controller *ctrl, + u8 sid, u8 ad, u8 *buf); + +/** + * spmi_register_zero_write() - register zero write + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @data: the data to be written to register 0 (7-bits). + * + * Writes data to register 0 of the Slave device. + */ +extern int spmi_register_zero_write(struct spmi_controller *ctrl, + u8 sid, u8 data); + +/** + * spmi_ext_register_write() - extended register write + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @ad: slave register address (8-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * @len: the request number of bytes to read (up to 16 bytes). + * + * Writes up to 16 bytes of data to the extended register space of a + * Slave device. + */ +extern int spmi_ext_register_write(struct spmi_controller *ctrl, + u8 sid, u8 ad, u8 *buf, int len); + +/** + * spmi_ext_register_writel() - extended register write long + * @ctrl: SPMI controller. + * @sid: slave identifier. + * @ad: slave register address (16-bit address). + * @buf: buffer containing the data to be transferred to the Slave. + * @len: the request number of bytes to read (up to 8 bytes). + * + * Writes up to 8 bytes of data to the extended register space of a + * Slave device using 16-bit address. + */ +extern int spmi_ext_register_writel(struct spmi_controller *ctrl, + u8 sid, u16 ad, u8 *buf, int len); + +/** + * spmi_command_reset() - sends RESET command to the specified slave + * @ctrl: SPMI controller. + * @sid: slave identifier. + * + * The Reset command initializes the Slave and forces all registers to + * their reset values. The Slave shall enter the STARTUP state after + * receiving a Reset command. + * + * Returns + * -EINVAL for invalid slave identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +extern int spmi_command_reset(struct spmi_controller *ctrl, u8 sid); + +/** + * spmi_command_sleep() - sends SLEEP command to the specified slave + * @ctrl: SPMI controller. + * @sid: slave identifier. + * + * The Sleep command causes the Slave to enter the user defined SLEEP state. + * + * Returns + * -EINVAL for invalid slave identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +extern int spmi_command_sleep(struct spmi_controller *ctrl, u8 sid); + +/** + * spmi_command_wakeup() - sends WAKEUP command to the specified slave + * @ctrl: SPMI controller. + * @sid: slave identifier. + * + * The Wakeup command causes the Slave to move from the SLEEP state to + * the ACTIVE state. + * + * Returns + * -EINVAL for invalid slave identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +extern int spmi_command_wakeup(struct spmi_controller *ctrl, u8 sid); + +/** + * spmi_command_shutdown() - sends SHUTDOWN command to the specified slave + * @ctrl: SPMI controller. + * @sid: slave identifier. + * + * The Shutdown command causes the Slave to enter the SHUTDOWN state. + * + * Returns + * -EINVAL for invalid slave identifier. + * -EPERM if the SPMI transaction is denied due to permission issues. + * -EIO if the SPMI transaction fails (parity errors, etc). + * -ETIMEDOUT if the SPMI transaction times out. + */ +extern int spmi_command_shutdown(struct spmi_controller *ctrl, u8 sid); +#endif diff --git a/include/linux/stop_machine.h b/include/linux/stop_machine.h index 3b5e910d14ca47561ca8c2656b1f91cfb696ac45..f9547f4cb42b7dd39e294d8be9d042063a3dd419 100644 --- a/include/linux/stop_machine.h +++ b/include/linux/stop_machine.h @@ -27,6 +27,8 @@ struct cpu_stop_work { struct cpu_stop_done *done; }; +extern struct mutex stop_cpus_mutex; + int stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg); void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg, struct cpu_stop_work *work_buf); diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index c34b4c82b0dcd6f60937cf6507da1b9988fd9d6f..84c59dc43e460c576f5b228f5aaddd11b61ca619 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/sysdev.h b/include/linux/sysdev.h new file mode 100644 index 0000000000000000000000000000000000000000..d35e783a598c39065f66aae6d7497c7107a0a75e --- /dev/null +++ b/include/linux/sysdev.h @@ -0,0 +1,165 @@ +/** + * System devices follow a slightly different driver model. + * They don't need to do dynammic driver binding, can't be probed, + * and don't reside on any type of peripheral bus. + * So, we represent and treat them a little differently. + * + * We still have a notion of a driver for a system device, because we still + * want to perform basic operations on these devices. + * + * We also support auxiliary drivers binding to devices of a certain class. + * + * This allows configurable drivers to register themselves for devices of + * a certain type. And, it allows class definitions to reside in generic + * code while arch-specific code can register specific drivers. + * + * Auxiliary drivers registered with a NULL cls are registered as drivers + * for all system devices, and get notification calls for each device. + */ + + +#ifndef _SYSDEV_H_ +#define _SYSDEV_H_ + +#include +#include +#include + + +struct sys_device; +struct sysdev_class_attribute; + +struct sysdev_class { + const char *name; + struct list_head drivers; + struct sysdev_class_attribute **attrs; + struct kset kset; +}; + +struct sysdev_class_attribute { + struct attribute attr; + ssize_t (*show)(struct sysdev_class *, struct sysdev_class_attribute *, + char *); + ssize_t (*store)(struct sysdev_class *, struct sysdev_class_attribute *, + const char *, size_t); +}; + +#define _SYSDEV_CLASS_ATTR(_name,_mode,_show,_store) \ +{ \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +#define SYSDEV_CLASS_ATTR(_name,_mode,_show,_store) \ + struct sysdev_class_attribute attr_##_name = \ + _SYSDEV_CLASS_ATTR(_name,_mode,_show,_store) + + +extern int sysdev_class_register(struct sysdev_class *); +extern void sysdev_class_unregister(struct sysdev_class *); + +extern int sysdev_class_create_file(struct sysdev_class *, + struct sysdev_class_attribute *); +extern void sysdev_class_remove_file(struct sysdev_class *, + struct sysdev_class_attribute *); +/** + * Auxiliary system device drivers. + */ + +struct sysdev_driver { + struct list_head entry; + int (*add)(struct sys_device *); + int (*remove)(struct sys_device *); +}; + + +extern int sysdev_driver_register(struct sysdev_class *, struct sysdev_driver *); +extern void sysdev_driver_unregister(struct sysdev_class *, struct sysdev_driver *); + + +/** + * sys_devices can be simplified a lot from regular devices, because they're + * simply not as versatile. + */ + +struct sys_device { + u32 id; + struct sysdev_class * cls; + struct kobject kobj; +}; + +extern int sysdev_register(struct sys_device *); +extern void sysdev_unregister(struct sys_device *); + + +struct sysdev_attribute { + struct attribute attr; + ssize_t (*show)(struct sys_device *, struct sysdev_attribute *, char *); + ssize_t (*store)(struct sys_device *, struct sysdev_attribute *, + const char *, size_t); +}; + + +#define _SYSDEV_ATTR(_name, _mode, _show, _store) \ +{ \ + .attr = { .name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +#define SYSDEV_ATTR(_name, _mode, _show, _store) \ + struct sysdev_attribute attr_##_name = \ + _SYSDEV_ATTR(_name, _mode, _show, _store); + +extern int sysdev_create_file(struct sys_device *, struct sysdev_attribute *); +extern void sysdev_remove_file(struct sys_device *, struct sysdev_attribute *); + +/* Create/remove NULL terminated attribute list */ +static inline int +sysdev_create_files(struct sys_device *d, struct sysdev_attribute **a) +{ + return sysfs_create_files(&d->kobj, (const struct attribute **)a); +} + +static inline void +sysdev_remove_files(struct sys_device *d, struct sysdev_attribute **a) +{ + return sysfs_remove_files(&d->kobj, (const struct attribute **)a); +} + +struct sysdev_ext_attribute { + struct sysdev_attribute attr; + void *var; +}; + +/* + * Support for simple variable sysdev attributes. + * The pointer to the variable is stored in a sysdev_ext_attribute + */ + +/* Add more types as needed */ + +extern ssize_t sysdev_show_ulong(struct sys_device *, struct sysdev_attribute *, + char *); +extern ssize_t sysdev_store_ulong(struct sys_device *, + struct sysdev_attribute *, const char *, size_t); +extern ssize_t sysdev_show_int(struct sys_device *, struct sysdev_attribute *, + char *); +extern ssize_t sysdev_store_int(struct sys_device *, + struct sysdev_attribute *, const char *, size_t); + +#define _SYSDEV_ULONG_ATTR(_name, _mode, _var) \ + { _SYSDEV_ATTR(_name, _mode, sysdev_show_ulong, sysdev_store_ulong), \ + &(_var) } +#define SYSDEV_ULONG_ATTR(_name, _mode, _var) \ + struct sysdev_ext_attribute attr_##_name = \ + _SYSDEV_ULONG_ATTR(_name, _mode, _var); +#define _SYSDEV_INT_ATTR(_name, _mode, _var) \ + { _SYSDEV_ATTR(_name, _mode, sysdev_show_int, sysdev_store_int), \ + &(_var) } +#define SYSDEV_INT_ATTR(_name, _mode, _var) \ + struct sysdev_ext_attribute attr_##_name = \ + _SYSDEV_INT_ATTR(_name, _mode, _var); + +#endif /* _SYSDEV_H_ */ diff --git a/include/linux/tcp.h b/include/linux/tcp.h index b6c62d2943805c52411d7d0c881831dee5905577..2f95e9b06040f4df7d3df2ba6f2f22430311c113 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -68,18 +68,18 @@ union tcp_word_hdr { #define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words [3]) -enum { - TCP_FLAG_CWR = __cpu_to_be32(0x00800000), - TCP_FLAG_ECE = __cpu_to_be32(0x00400000), - TCP_FLAG_URG = __cpu_to_be32(0x00200000), - TCP_FLAG_ACK = __cpu_to_be32(0x00100000), - TCP_FLAG_PSH = __cpu_to_be32(0x00080000), - TCP_FLAG_RST = __cpu_to_be32(0x00040000), - TCP_FLAG_SYN = __cpu_to_be32(0x00020000), - TCP_FLAG_FIN = __cpu_to_be32(0x00010000), - TCP_RESERVED_BITS = __cpu_to_be32(0x0F000000), - TCP_DATA_OFFSET = __cpu_to_be32(0xF0000000) -}; +enum { + TCP_FLAG_CWR = __constant_htonl(0x00800000), + TCP_FLAG_ECE = __constant_htonl(0x00400000), + TCP_FLAG_URG = __constant_htonl(0x00200000), + TCP_FLAG_ACK = __constant_htonl(0x00100000), + TCP_FLAG_PSH = __constant_htonl(0x00080000), + TCP_FLAG_RST = __constant_htonl(0x00040000), + TCP_FLAG_SYN = __constant_htonl(0x00020000), + TCP_FLAG_FIN = __constant_htonl(0x00010000), + TCP_RESERVED_BITS = __constant_htonl(0x0F000000), + TCP_DATA_OFFSET = __constant_htonl(0xF0000000) +}; /* * TCP general constants @@ -135,6 +135,7 @@ struct tcp_info { __u8 tcpi_backoff; __u8 tcpi_options; __u8 tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4; + __u8 tcpi_count; __u32 tcpi_rto; __u32 tcpi_ato; diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 796f1ff0388c979138b81b7094bb6feda5bfd212..f740640b32d7ce59e74015c1c4544911b3ae7cbc 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 0000000000000000000000000000000000000000..fc4d20b1fa6bac520f9121c24d815e1092ca36db --- /dev/null +++ b/include/linux/tsif_api.h @@ -0,0 +1,214 @@ +/** + * 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_get_active - return active tsif hardware instance + * + * Return TSIF instance to use (selected by CONFIG_MSM_USE_TSIF1) + */ +int tsif_get_active(void); + +/** + * 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/tspp.h b/include/linux/tspp.h new file mode 100644 index 0000000000000000000000000000000000000000..d5a5ffc53406c0b4392fe4f4e718eaea683dc6d6 --- /dev/null +++ b/include/linux/tspp.h @@ -0,0 +1,84 @@ +#ifndef _TSPP_H_ +#define _TSPP_H_ + +#include + +#define TSPP_NUM_SYSTEM_KEYS 8 + +enum tspp_key_parity { + TSPP_KEY_PARITY_EVEN, + TSPP_KEY_PARITY_ODD +}; + +enum tspp_source { + TSPP_SOURCE_TSIF0, + TSPP_SOURCE_TSIF1, + TSPP_SOURCE_MEM, + TSPP_SOURCE_NONE = -1 +}; + +enum tspp_mode { + TSPP_MODE_DISABLED, + TSPP_MODE_PES, + TSPP_MODE_RAW, + TSPP_MODE_RAW_NO_SUFFIX +}; + +struct tspp_filter { + int pid; + int mask; + enum tspp_mode mode; + int priority; /* 0 - 15 */ + int decrypt; + enum tspp_source source; +}; + +struct tspp_select_source { + enum tspp_source source; +}; + +struct tspp_pid { + int pid; +}; + +struct tspp_key { + enum tspp_key_parity parity; + int lsb; + int msb; +}; + +struct tspp_iv { + int data[2]; +}; + +struct tspp_system_keys { + int data[TSPP_NUM_SYSTEM_KEYS]; +}; + +struct tspp_buffer { + int size; +}; + +/* defines for IOCTL functions */ +/* read Documentation/ioctl-number.txt */ +/* some random number to avoid coinciding with other ioctl numbers */ +#define TSPP_IOCTL_BASE 0xAA +#define TSPP_IOCTL_SELECT_SOURCE \ + _IOW(TSPP_IOCTL_BASE, 0, struct tspp_select_source) +#define TSPP_IOCTL_ADD_FILTER \ + _IOW(TSPP_IOCTL_BASE, 1, struct tspp_filter) +#define TSPP_IOCTL_REMOVE_FILTER \ + _IOW(TSPP_IOCTL_BASE, 2, struct tspp_pid) +#define TSPP_IOCTL_SET_KEY \ + _IOW(TSPP_IOCTL_BASE, 3, struct tspp_key) +#define TSPP_IOCTL_SET_IV \ + _IOW(TSPP_IOCTL_BASE, 4, struct tspp_iv) +#define TSPP_IOCTL_SET_SYSTEM_KEYS \ + _IOW(TSPP_IOCTL_BASE, 5, struct tspp_system_keys) +#define TSPP_IOCTL_BUFFER_SIZE \ + _IOW(TSPP_IOCTL_BASE, 6, struct tspp_buffer) +#define TSPP_IOCTL_LOOPBACK \ + _IOW(TSPP_IOCTL_BASE, 0xFF, int) + + +#endif /* _TSPP_H_ */ diff --git a/include/linux/tty.h b/include/linux/tty.h index 9f47ab540f65e997b79b0a16c52332c564354234..6a0259d94c9acb95e2569277ba57639ab023b304 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -34,6 +34,7 @@ #define N_TI_WL 22 /* for TI's WL BT, FM, GPS combo chips */ #define N_TRACESINK 23 /* Trace data routing for MIPI P1149.7 */ #define N_TRACEROUTER 24 /* Trace data routing for MIPI P1149.7 */ +#define N_SMUX 25 /* Serial MUX */ #ifdef __KERNEL__ #include diff --git a/include/linux/tzcom.h b/include/linux/tzcom.h new file mode 100644 index 0000000000000000000000000000000000000000..448ab2a12c5d5818e39ce985e7b653e6f7dae6dc --- /dev/null +++ b/include/linux/tzcom.h @@ -0,0 +1,136 @@ +/* Qualcomm TrustZone communicator API */ + +#ifndef __TZCOM_H_ +#define __TZCOM_H_ + +#include +#include + +#define MAX_ION_FD 4 +/** + * 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 + * @ifd_data_fd - ion handle to some memory allocated in user space + * @cmd_buf_offset - command buffer offset + * @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_ion_fd_info - ion fd handle data information + * @fd - ion handle to some memory allocated in user space + * @cmd_buf_offset - command buffer offset + */ +struct tzcom_ion_fd_info { + int32_t fd; + uint32_t cmd_buf_offset; +}; + +/** + * struct tzcom_send_cmd_op_req - for send command ioctl request + * @cmd_id - command to execute on TZBSP side + * @ifd_data_fd - ion handle to some memory allocated in user space + * @cmd_buf_offset - command buffer offset + * @cmd_len - command buffer length + * @cmd_buf - command buffer + * @resp_len - response buffer length + * @resp_buf - response buffer + */ +struct tzcom_send_cmd_fd_op_req { + uint32_t cmd_id; /* in */ + struct tzcom_ion_fd_info ifd_data[MAX_ION_FD]; + 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) + +#define TZCOM_IOCTL_ABORT_REQ _IO(TZCOM_IOC_MAGIC, 6) +/* For TZ service */ +#define TZCOM_IOCTL_SEND_CMD_FD_REQ \ + _IOWR(TZCOM_IOC_MAGIC, 7, struct tzcom_send_cmd_fd_op_req) +#endif /* __TZCOM_H_ */ diff --git a/include/linux/usb.h b/include/linux/usb.h index 73b68d1f2cb01a2da4fbb65bd5bb319b51ac690e..68a87bf9e05245cfb4f82e9d4eecd1a1292a5167 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -331,6 +331,15 @@ 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 */ + unsigned quick_hnp:1; /* OTG: Indiacates if hnp is required + irrespective of host_request flag + */ + unsigned otg_vbus_off:1; /* OTG: OTG test device feature bit that + * tells A-device to turn off VBUS after + * B-device is disconnected. + */ + 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 @@ -372,6 +381,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; @@ -500,6 +519,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; @@ -1638,8 +1669,15 @@ static inline int usb_translate_errors(int error_code) #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 0000000000000000000000000000000000000000..6d3c3ad2c1fd1f87a771bcd20b1cc9706f78c181 --- /dev/null +++ b/include/linux/usb/android.h @@ -0,0 +1,25 @@ +/* + * 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 *); + u32 swfi_latency; +}; + +#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 0000000000000000000000000000000000000000..438dfa4f7beeed93588ae237cc8250cea72afcc3 --- /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/ccid_desc.h b/include/linux/usb/ccid_desc.h new file mode 100644 index 0000000000000000000000000000000000000000..2d1ae7416418b6d70a7986ae0c57a61c013197e2 --- /dev/null +++ b/include/linux/usb/ccid_desc.h @@ -0,0 +1,112 @@ +/* + * 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_CCID_DESC_H +#define __LINUX_USB_CCID_DESC_H + +/*CCID specification version 1.10*/ +#define CCID1_10 0x0110 + +#define SMART_CARD_DEVICE_CLASS 0x0B +/* Smart Card Device Class Descriptor Type */ +#define CCID_DECRIPTOR_TYPE 0x21 + +/* Table 5.3-1 Summary of CCID Class Specific Request */ +#define CCIDGENERICREQ_ABORT 0x01 +#define CCIDGENERICREQ_GET_CLOCK_FREQUENCIES 0x02 +#define CCIDGENERICREQ_GET_DATA_RATES 0x03 + +/* 6.1 Command Pipe, Bulk-OUT Messages */ +#define PC_TO_RDR_ICCPOWERON 0x62 +#define PC_TO_RDR_ICCPOWEROFF 0x63 +#define PC_TO_RDR_GETSLOTSTATUS 0x65 +#define PC_TO_RDR_XFRBLOCK 0x6F +#define PC_TO_RDR_GETPARAMETERS 0x6C +#define PC_TO_RDR_RESETPARAMETERS 0x6D +#define PC_TO_RDR_SETPARAMETERS 0x61 +#define PC_TO_RDR_ESCAPE 0x6B +#define PC_TO_RDR_ICCCLOCK 0x6E +#define PC_TO_RDR_T0APDU 0x6A +#define PC_TO_RDR_SECURE 0x69 +#define PC_TO_RDR_MECHANICAL 0x71 +#define PC_TO_RDR_ABORT 0x72 +#define PC_TO_RDR_SETDATARATEANDCLOCKFREQUENCY 0x73 + +/* 6.2 Response Pipe, Bulk-IN Messages */ +#define RDR_TO_PC_DATABLOCK 0x80 +#define RDR_TO_PC_SLOTSTATUS 0x81 +#define RDR_TO_PC_PARAMETERS 0x82 +#define RDR_TO_PC_ESCAPE 0x83 +#define RDR_TO_PC_DATARATEANDCLOCKFREQUENCY 0x84 + +/* 6.3 Interrupt-IN Messages */ +#define RDR_TO_PC_NOTIFYSLOTCHANGE 0x50 +#define RDR_TO_PC_HARDWAREERROR 0x51 + +/* Table 6.2-2 Slot error register when bmCommandStatus = 1 */ +#define CMD_ABORTED 0xFF +#define ICC_MUTE 0xFE +#define XFR_PARITY_ERROR 0xFD +#define XFR_OVERRUN 0xFC +#define HW_ERROR 0xFB +#define BAD_ATR_TS 0xF8 +#define BAD_ATR_TCK 0xF7 +#define ICC_PROTOCOL_NOT_SUPPORTED 0xF6 +#define ICC_CLASS_NOT_SUPPORTED 0xF5 +#define PROCEDURE_BYTE_CONFLICT 0xF4 +#define DEACTIVATED_PROTOCOL 0xF3 +#define BUSY_WITH_AUTO_SEQUENCE 0xF2 +#define PIN_TIMEOUT 0xF0 +#define PIN_CANCELLED 0xEF +#define CMD_SLOT_BUSY 0xE0 + +/* CCID rev 1.1, p.27 */ +#define VOLTS_AUTO 0x00 +#define VOLTS_5_0 0x01 +#define VOLTS_3_0 0x02 +#define VOLTS_1_8 0x03 + +/* 6.3.1 RDR_to_PC_NotifySlotChange */ +#define ICC_NOT_PRESENT 0x00 +#define ICC_PRESENT 0x01 +#define ICC_CHANGE 0x02 +#define ICC_INSERTED_EVENT (ICC_PRESENT+ICC_CHANGE) + +/* Identifies the length of type of subordinate descriptors of a CCID device + * Table 5.1-1 Smart Card Device Class descriptors + */ +struct usb_ccid_class_descriptor { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned short bcdCCID; + unsigned char bMaxSlotIndex; + unsigned char bVoltageSupport; + unsigned long dwProtocols; + unsigned long dwDefaultClock; + unsigned long dwMaximumClock; + unsigned char bNumClockSupported; + unsigned long dwDataRate; + unsigned long dwMaxDataRate; + unsigned char bNumDataRatesSupported; + unsigned long dwMaxIFSD; + unsigned long dwSynchProtocols; + unsigned long dwMechanical; + unsigned long dwFeatures; + unsigned long dwMaxCCIDMessageLength; + unsigned char bClassGetResponse; + unsigned char bClassEnvelope; + unsigned short wLcdLayout; + unsigned char bPINSupport; + unsigned char bMaxCCIDBusySlots; +} __packed; +#endif diff --git a/include/linux/usb/cdc.h b/include/linux/usb/cdc.h index 81a927930bfd35a68f57bff638d662da9d6f6cf8..2b39f6953da8ce7b8a248679bca01be5f97af970 100644 --- a/include/linux/usb/cdc.h +++ b/include/linux/usb/cdc.h @@ -53,6 +53,7 @@ #define USB_CDC_DMM_TYPE 0x14 #define USB_CDC_OBEX_TYPE 0x15 #define USB_CDC_NCM_TYPE 0x1a +#define USB_CDC_MBB_TYPE 0x1b /* mbb_desc */ /* "Header Functional Descriptor" from CDC spec 5.2.3.1 */ struct usb_cdc_header_desc { @@ -187,6 +188,21 @@ struct usb_cdc_ncm_desc { __le16 bcdNcmVersion; __u8 bmNetworkCapabilities; } __attribute__ ((packed)); + +/* "MBIM Functional Descriptor" */ +struct usb_cdc_mbb_desc { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + + __le16 bcdMbbVersion; + __le16 wMaxControlMessage; + __u8 bNumberFilters; + __u8 bMaxFilterSize; + __le16 wMaxSegmentSize; + __u8 bmNetworkCapabilities; +} __packed; + /*-------------------------------------------------------------------------*/ /* @@ -201,6 +217,7 @@ struct usb_cdc_ncm_desc { #define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00 #define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01 +#define USB_CDC_RESET_FUNCTION 0x05 #define USB_CDC_REQ_SET_LINE_CODING 0x20 #define USB_CDC_REQ_GET_LINE_CODING 0x21 #define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22 diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index af21f311591943338672a13e538fb5dbf522cef7..c918b7465a86e683de25c728ad8118e3faa894bf 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -134,6 +134,12 @@ #define TEST_PACKET 4 #define TEST_FORCE_EN 5 +/* OTG test mode feature bits + * See ECN OTG2.0 spec Table 6-8 + */ +#define TEST_OTG_SRP_REQD 6 +#define TEST_OTG_HNP_REQD 7 + /* * New Feature Selectors as added by USB 3.0 * See USB 3.0 spec Table 9-6 @@ -152,6 +158,13 @@ #define USB_ENDPOINT_HALT 0 /* IN/OUT will STALL */ +#define OTG_STATUS_SELECTOR 0xF000 +#define HOST_REQUEST_FLAG 0 +#define THOST_REQ_POLL 1500 /* msec (1000 - 2000) */ +#define OTG_TTST_SUSP 70 /* msec (0 - 100) */ + +#define OTG_TTST_VBUS_OFF 1 + /* 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 */ @@ -653,8 +666,10 @@ struct usb_otg_descriptor { __u8 bDescriptorType; __u8 bmAttributes; /* support for HNP, SRP, etc */ + __le16 bcdOTG; } __attribute__ ((packed)); +#define USB_DT_OTG_SIZE 5 /* from usb_otg_descriptor.bmAttributes */ #define USB_OTG_SRP (1 << 0) #define USB_OTG_HNP (1 << 1) /* swap host/device roles */ diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 9517466ababbcb1b2ede5d106c39da30da5bf5d9..1a945e735fe3b72b36fbab36dfd9f68170bc7efb 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -68,6 +68,7 @@ struct usb_ep; * Note that for writes (IN transfers) some data bytes may still * reside in a device-side FIFO when the request is reported as * complete. + *@udc_priv: Vendor private data in usage by the UDC. * * These are allocated/freed through the endpoint they're used with. The * hardware's driver can add extra per-request data to the memory it returns, @@ -108,6 +109,7 @@ struct usb_request { int status; unsigned actual; + unsigned udc_priv; }; /*-------------------------------------------------------------------------*/ @@ -127,7 +129,6 @@ struct usb_ep_ops { struct usb_request *(*alloc_request) (struct usb_ep *ep, gfp_t gfp_flags); void (*free_request) (struct usb_ep *ep, struct usb_request *req); - int (*queue) (struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags); int (*dequeue) (struct usb_ep *ep, struct usb_request *req); @@ -499,6 +500,9 @@ struct usb_gadget_ops { * only supports HNP on a different root port. * @b_hnp_enable: OTG device feature flag, indicating that the A-Host * enabled HNP support. + * @host_request: A flag set by user when wishes to take up host role. + * @otg_srp_reqd: OTG test mode feature to initiate SRP after the end of + * current session. * @name: Identifies the controller hardware type. Used in diagnostics * and sometimes configuration. * @dev: Driver model state for this abstract device. @@ -534,6 +538,8 @@ struct usb_gadget { unsigned b_hnp_enable:1; unsigned a_hnp_support:1; unsigned a_alt_hnp_support:1; + unsigned host_request:1; + unsigned otg_srp_reqd:1; const char *name; struct device dev; }; @@ -927,6 +933,11 @@ int usb_gadget_get_string(struct usb_gadget_strings *table, int id, u8 *buf); /* utility to simplify managing config descriptors */ +/* Find and fill the requested descriptor into buffer */ +int +usb_find_descriptor_fillbuf(void *, unsigned, + const struct usb_descriptor_header **, u8); + /* write vector of descriptors into buffer */ int usb_descriptor_fillbuf(void *, unsigned, const struct usb_descriptor_header **); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index d28cc78a38e442e75e435c0dede140291c57ce2c..9cdbfddb8ee74e52bf80e6d3a08fe11f079b3f86 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -380,9 +380,18 @@ extern struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, extern struct usb_hcd *usb_get_hcd(struct usb_hcd *hcd); extern void usb_put_hcd(struct usb_hcd *hcd); extern int usb_hcd_is_primary_hcd(struct usb_hcd *hcd); +#ifdef CONFIG_USB extern int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags); extern void usb_remove_hcd(struct usb_hcd *hcd); +#else +static inline int +usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags) +{ + return 0; +} +static inline void usb_remove_hcd(struct usb_hcd *hcd) {} +#endif struct platform_device; extern void usb_hcd_platform_shutdown(struct platform_device *dev); diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h index 22a396c13f3a2976cce483670ba3f69a4d96a028..e2a0392d0fae2ad3db8a040e29baac32a0e1859d 100644 --- a/include/linux/usb/msm_hsusb.h +++ b/include/linux/usb/msm_hsusb.h @@ -2,7 +2,7 @@ * * Copyright (C) 2008 Google, Inc. * Author: Brian Swetland - * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2012, 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,7 +19,26 @@ #define __ASM_ARCH_MSM_HSUSB_H #include +#include +#include #include +#include +#include + +/* + * The following are bit fields describing the usb_request.udc_priv word. + * These bit fields are set by function drivers that wish to queue + * usb_requests with sps/bam parameters. + */ +#define MSM_PIPE_ID_MASK (0x1F) +#define MSM_TX_PIPE_ID_OFS (16) +#define MSM_SPS_MODE BIT(5) +#define MSM_IS_FINITE_TRANSFER BIT(6) +#define MSM_PRODUCER BIT(7) +#define MSM_DISABLE_WB BIT(8) +#define MSM_ETD_IOC BIT(9) +#define MSM_INTERNAL_MEM BIT(10) +#define MSM_VENDOR_ID BIT(16) /** * Supported USB modes @@ -68,8 +87,12 @@ enum msm_usb_phy_type { }; #define IDEV_CHG_MAX 1500 +#define IDEV_CHG_MIN 500 #define IUNIT 100 +#define IDEV_ACA_CHG_MAX 1500 +#define IDEV_ACA_CHG_LIMIT 500 + /** * Different states involved in USB charger detection. * @@ -102,13 +125,38 @@ 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, +}; + +/** + * Used different VDDCX voltage voting mechnism + * VDDCX_CORNER Vote for VDDCX Corner voltage + * VDDCX Vote for VDDCX Absolute voltage + */ +enum usb_vdd_type { + VDDCX_CORNER = 0, + VDDCX, + VDD_TYPE_MAX, }; /** @@ -116,37 +164,91 @@ enum usb_chg_type { * for msm_otg driver. * @phy_init_seq: PHY configuration sequence. val, reg pairs * terminated by -1. - * @vbus_power: VBUS power on/off routine. + * @vbus_power: VBUS power on/off routine.It should return result + * as success(zero value) or failure(non-zero value). * @power_budget: VBUS power budget in mA (0 will be treated as 500mA). * @mode: Supported mode (OTG/peripheral/host). * @otg_control: OTG switch controlled by user/Id pin * @default_mode: Default operational mode. Applicable only if * 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. + * @mhl_enable: indicates MHL connector or not. + * @disable_reset_on_disconnect: perform USB PHY and LINK reset + * on USB cable disconnection. + * @enable_dcd: Enable Data Contact Detection circuit. if not set + * wait for 600msec before proceeding to primary + * detection. + * @enable_lpm_on_suspend: Enable the USB core to go into Low + * Power Mode, when USB bus is suspended but cable + * is connected. + * @bus_scale_table: parameters for bus bandwidth requirements */ struct msm_otg_platform_data { int *phy_init_seq; - void (*vbus_power)(bool on); + int (*vbus_power)(bool on); unsigned power_budget; enum usb_mode_type mode; enum otg_control_type otg_control; enum usb_mode_type default_mode; enum msm_usb_phy_type phy_type; void (*setup_gpio)(enum usb_otg_state state); - char *pclk_src_name; + int pmic_id_irq; + bool mhl_enable; + bool disable_reset_on_disconnect; + bool enable_dcd; + bool enable_lpm_on_dev_suspend; + struct msm_bus_scale_pdata *bus_scale_table; }; +/* 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_USB_OTG +#define TA_WAIT_BCON 30000 /* (1100 - 30000) */ +#else +#define TA_WAIT_BCON -1 +#endif + +#define TA_AIDL_BDIS 500 /* (200 - ) */ +#define TA_BIDL_ADIS 155 /* (155 - 200) */ +#define TB_SRP_FAIL 6000 /* (5000 - 6000) */ +#define TB_ASE0_BRST 200 /* (155 - ) */ + +/* TB_SSEND_SRP and TB_SE0_SRP are combined */ +#define TB_SRP_INIT 2000 /* (1500 - ) */ + +#define TA_TST_MAINT 10100 /* (9900 - 10100) */ +#define TB_TST_SRP 3000 /* ( - 5000) */ +#define TB_TST_CONFIG 300 + +/* 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 +#define A_TST_MAINT 7 +#define B_TST_SRP 8 +#define B_TST_CONFIG 9 + /** * struct msm_otg: OTG driver data. Shared by HCD and DCD. * @otg: USB OTG Transceiver structure. * @pdata: otg device platform data. * @irq: IRQ number assigned for HSUSB controller. - * @clk: clock struct of usb_hs_clk. - * @pclk: clock struct of usb_hs_pclk. - * @pclk_src: pclk source for voting. - * @phy_reset_clk: clock struct of usb_phy_clk. - * @core_clk: clock struct of usb_hs_core_clk. + * @clk: clock struct of alt_core_clk. + * @pclk: clock struct of iface_clk. + * @phy_reset_clk: clock struct of phy_clk. + * @core_clk: clock struct of core_bus_clk. * @regs: ioremapped register base address. * @inputs: OTG state machine inputs(Id, SessValid etc). * @sm_work: OTG state machine work. @@ -158,6 +260,15 @@ 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. + * @id_timer: The timer used for polling ID line to detect ACA states. + * @xo_handle: TCXO buffer handle + * @bus_perf_client: Bus performance client handle to request BUS bandwidth */ struct msm_otg { struct usb_phy phy; @@ -165,14 +276,30 @@ struct msm_otg { int irq; struct clk *clk; struct clk *pclk; - struct clk *pclk_src; struct clk *phy_reset_clk; struct clk *core_clk; void __iomem *regs; #define ID 0 #define B_SESS_VLD 1 +#define ID_A 2 +#define ID_B 3 +#define ID_C 4 +#define A_BUS_DROP 5 +#define A_BUS_REQ 6 +#define A_SRP_DET 7 +#define A_VBUS_VLD 8 +#define B_CONN 9 +#define ADP_CHANGE 10 +#define POWER_UP 11 +#define A_CLR_ERR 12 +#define A_BUS_RESUME 13 +#define A_BUS_SUSPEND 14 +#define A_CONN 15 +#define B_BUS_REQ 16 unsigned long inputs; struct work_struct sm_work; + bool sm_work_pending; + atomic_t pm_suspended; atomic_t in_lpm; int async_int; unsigned cur_power; @@ -180,6 +307,81 @@ 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; + struct timer_list id_timer; + unsigned long caps; + struct msm_xo_voter *xo_handle; + uint32_t bus_perf_client; + /* + * 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) + /* + * Allow putting the core in Low Power mode, when + * USB bus is suspended but cable is connected. + */ +#define ALLOW_LPM_ON_DEV_SUSPEND BIT(2) + unsigned long lpm_flags; +#define PHY_PWR_COLLAPSED BIT(0) +#define PHY_RETENTIONED BIT(1) + int reset_counter; + unsigned long b_last_se0_sess; + unsigned long tmouts; + u8 active_tmout; + struct hrtimer timer; + enum usb_vdd_type vdd_type; +}; + +struct msm_hsic_host_platform_data { + unsigned strobe; + unsigned data; + struct msm_bus_scale_pdata *bus_scale_table; +}; + +struct msm_usb_host_platform_data { + unsigned int power_budget; + unsigned int dock_connect_irq; +}; + +struct msm_hsic_peripheral_platform_data { + bool keep_core_clk_on_suspend_workaround; }; +struct usb_bam_pipe_connect { + u32 src_phy_addr; + int src_pipe_index; + u32 dst_phy_addr; + int dst_pipe_index; + u32 data_fifo_base_offset; + u32 data_fifo_size; + u32 desc_fifo_base_offset; + u32 desc_fifo_size; +}; + +struct msm_usb_bam_platform_data { + struct usb_bam_pipe_connect *connections; + int usb_active_bam; + int usb_bam_num_pipes; +}; + +enum usb_bam { + HSUSB_BAM = 0, + HSIC_BAM, +}; + +int msm_ep_config(struct usb_ep *ep); +int msm_ep_unconfig(struct usb_ep *ep); +int msm_data_fifo_config(struct usb_ep *ep, u32 addr, u32 size); + #endif diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h index 6e97a2d3d39fbb0d7e2d70be26bcc5e225045c2d..8a05136f4ddfc848bd70cc06e1c7b85fe320a3da 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) @@ -32,26 +33,49 @@ #define PORTSC_PHCD (1 << 23) /* phy suspend mode */ #define PORTSC_PTS_MASK (3 << 30) #define PORTSC_PTS_ULPI (3 << 30) +#define PORTSC_CSC (1 << 1) +#define PORTSC_CCS (1 << 0) #define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170) #define ULPI_RUN (1 << 30) #define ULPI_WRITE (1 << 29) #define ULPI_READ (0 << 29) +#define ULPI_SYNC_STATE (1 << 27) #define ULPI_ADDR(n) (((n) & 255) << 16) #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 */ +#define PHY_IDHV_INTEN (1 << 8) /* PHY ID HV interrupt */ +#define PHY_OTGSESSVLDHV_INTEN (1 << 9) /* PHY Session Valid HV int. */ + +#define STS_PCI (1 << 2) /* R/WC - Port Change Detect */ +#define STS_URI (1 << 6) /* R/WC - RESET recv'd */ +#define STS_SLI (1 << 8) /* R/WC - suspend state entered */ /* OTG definitions */ #define OTGSC_INTSTS_MASK (0x7f << 16) +#define OTGSC_IDPU (1 << 5) +#define OTGSC_INTR_MASK (0x7f << 24) +#define OTGSC_HADP (1 << 6) #define OTGSC_ID (1 << 8) #define OTGSC_BSV (1 << 11) #define OTGSC_IDIS (1 << 16) #define OTGSC_BSVIS (1 << 19) #define OTGSC_IDIE (1 << 24) #define OTGSC_BSVIE (1 << 27) +#define OTGSC_DPIE (1 << 30) +#define OTGSC_DPIS (1 << 22) + +/* OTG interrupt status mask */ +#define OTG_USBSTS_MASK (STS_PCI | STS_URI | STS_SLI | PHY_ALT_INT) +#define OTG_OTGSTS_MASK (OTGSC_IDIS | OTGSC_BSVIS | OTGSC_DPIS) #endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */ diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index 38ab3f46346ff4852eabc4e0bbfc3fc2d5e2e793..ae3ffe4fc9cae8c054a227d5075b23bd50df9f50 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_phy_events { USB_EVENT_NONE, /* no events or cable disconnected */ USB_EVENT_VBUS, /* vbus valid event */ @@ -76,6 +104,10 @@ struct usb_otg { /* start or continue HNP role switch */ int (*start_hnp)(struct usb_otg *otg); + /* send events to user space */ + int (*send_event)(struct usb_otg *otg, + enum usb_otg_event event); + }; /* @@ -170,6 +202,10 @@ usb_phy_shutdown(struct usb_phy *x) x->shutdown(x); } +/* 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 usb_phy *usb_get_transceiver(void); diff --git a/include/linux/usb/otg_id.h b/include/linux/usb/otg_id.h new file mode 100644 index 0000000000000000000000000000000000000000..f9f5189a73b7f8b4e1f56081122fa7b0d878bc51 --- /dev/null +++ b/include/linux/usb/otg_id.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * Author: + * Colin Cross + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_USB_OTG_ID_H +#define __LINUX_USB_OTG_ID_H + +#include +#include + +/** + * otg_id_notifier_block + * + * @priority: Order the notifications will be called in. Higher numbers + * get called first. + * @detect: Called during otg_id_notify. Return OTG_ID_HANDLED if the USB cable + * has been identified + * @proxy_wait: Called during otg_id_notify if a previous handler returns + * OTG_ID_PROXY_WAIT. This should wait on ID change then call otg_id_notify. + * This is used when a handler knows what's connected but can't detect + * the change itself. + * @cancel: Called after detect has returned OTG_ID_HANDLED to ask it to + * release detection resources to allow a new identification to occur. + */ + +struct otg_id_notifier_block { + int priority; + int (*detect)(struct otg_id_notifier_block *otg_id_nb); + int (*proxy_wait)(struct otg_id_notifier_block *otg_id_nb); + void (*cancel)(struct otg_id_notifier_block *otg_id_nb); + struct plist_node p; +}; + +#define OTG_ID_PROXY_WAIT 2 +#define OTG_ID_HANDLED 1 +#define OTG_ID_UNHANDLED 0 + +int otg_id_register_notifier(struct otg_id_notifier_block *otg_id_nb); +void otg_id_unregister_notifier(struct otg_id_notifier_block *otg_id_nb); + +void otg_id_notify(void); +int otg_id_suspend(void); +void otg_id_resume(void); + +#endif /* __LINUX_USB_OTG_ID_H */ diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h index 3e93de7ecbc365c59e680f626990d8be80df0f43..fb1ca8c299d4cf4b913970373fe5b875bd79e460 100644 --- a/include/linux/usb/quirks.h +++ b/include/linux/usb/quirks.h @@ -30,4 +30,6 @@ descriptor */ #define USB_QUIRK_DELAY_INIT 0x00000040 +#define USB_QUIRK_OTG_PET 0x00000080 + #endif /* __LINUX_USB_QUIRKS_H */ diff --git a/include/linux/usb/ulpi.h b/include/linux/usb/ulpi.h index 6f033a415ecb85caa0291a58940922b5d6304419..627cf3fe90c89a4fe8e5b560a64e381e82359156 100644 --- a/include/linux/usb/ulpi.h +++ b/include/linux/usb/ulpi.h @@ -145,6 +145,7 @@ #define ULPI_INT_SESS_VALID (1 << 2) #define ULPI_INT_SESS_END (1 << 3) #define ULPI_INT_IDGRD (1 << 4) +#define ULPI_INT_DP (1 << 7) /* Debug */ #define ULPI_DEBUG_LINESTATE0 (1 << 0) diff --git a/include/linux/vcm.h b/include/linux/vcm.h new file mode 100644 index 0000000000000000000000000000000000000000..776b8b26af8f0cb799f0bebae2d30960d1de71f0 --- /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 0000000000000000000000000000000000000000..f0e4ea4a6ef90dc3f0780faf866157fd1143591e --- /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 0000000000000000000000000000000000000000..4cc53583738664f88669368a6dc755c205c0835e --- /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 0000000000000000000000000000000000000000..7ec20a6b63e6e3f9a4d9e379e8cbbf3a8d5ef303 --- /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/videodev2.h b/include/linux/videodev2.h index c9c9a4680cc5112c93b9fdb0f9b5ee6464a11663..0696b136bb052af532b0fdba34a5414d1c9ea725 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -399,6 +399,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_XVID v4l2_fourcc('X', 'V', 'I', 'D') /* Xvid */ #define V4L2_PIX_FMT_VC1_ANNEX_G v4l2_fourcc('V', 'C', '1', 'G') /* SMPTE 421M Annex G compliant stream */ #define V4L2_PIX_FMT_VC1_ANNEX_L v4l2_fourcc('V', 'C', '1', 'L') /* SMPTE 421M Annex L compliant stream */ +#define V4L2_PIX_FMT_DIVX_311 v4l2_fourcc('D', 'I', 'V', '3') /* DIVX */ /* Vendor-specific formats */ #define V4L2_PIX_FMT_CPIA1 v4l2_fourcc('C', 'P', 'I', 'A') /* cpia1 YUV */ @@ -671,6 +672,7 @@ struct v4l2_buffer { /* Cache handling flags */ #define V4L2_BUF_FLAG_NO_CACHE_INVALIDATE 0x0800 #define V4L2_BUF_FLAG_NO_CACHE_CLEAN 0x1000 +#define V4L2_BUF_FLAG_EOS 0x2000 /* * O V E R L A Y P R E V I E W @@ -731,6 +733,11 @@ struct v4l2_captureparm { /* Flags for 'capability' and 'capturemode' fields */ #define V4L2_MODE_HIGHQUALITY 0x0001 /* High quality imaging mode */ #define V4L2_CAP_TIMEPERFRAME 0x1000 /* timeperframe field is supported */ +#define V4L2_CAP_QCOM_FRAMESKIP 0x2000 /* frame skipping is supported */ + +struct v4l2_qcom_frameskip { + __u64 maxframeinterval; +}; struct v4l2_outputparm { __u32 capability; /* Supported modes */ @@ -1455,7 +1462,7 @@ enum v4l2_mpeg_video_bitrate_mode { enum v4l2_mpeg_video_header_mode { V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE = 0, V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME = 1, - + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_I_FRAME = 2, }; #define V4L2_CID_MPEG_VIDEO_MAX_REF_PIC (V4L2_CID_MPEG_BASE+217) #define V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE (V4L2_CID_MPEG_BASE+218) @@ -1566,6 +1573,7 @@ enum v4l2_mpeg_video_h264_vui_sar_idc { #define V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP (V4L2_CID_MPEG_BASE+403) #define V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP (V4L2_CID_MPEG_BASE+404) #define V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL (V4L2_CID_MPEG_BASE+405) + enum v4l2_mpeg_video_mpeg4_level { V4L2_MPEG_VIDEO_MPEG4_LEVEL_0 = 0, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B = 1, @@ -1629,6 +1637,9 @@ enum v4l2_mpeg_cx2341x_video_median_filter_type { /* MPEG-class control IDs specific to the Samsung MFC 5.1 driver as defined by V4L2 */ #define V4L2_CID_MPEG_MFC51_BASE (V4L2_CTRL_CLASS_MPEG | 0x1100) +#define V4L2_CID_MPEG_QCOM_BASE (V4L2_CTRL_CLASS_MPEG | 0x2100) + +#define V4L2_CID_MPEG_QCOM_SET_PERF_LEVEL (V4L2_CID_MPEG_QCOM_BASE + 0) #define V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY (V4L2_CID_MPEG_MFC51_BASE+0) #define V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY_ENABLE (V4L2_CID_MPEG_MFC51_BASE+1) @@ -1654,6 +1665,84 @@ enum v4l2_mpeg_mfc51_video_force_frame_type { #define V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_STATIC (V4L2_CID_MPEG_MFC51_BASE+53) #define V4L2_CID_MPEG_MFC51_VIDEO_H264_NUM_REF_PIC_FOR_P (V4L2_CID_MPEG_MFC51_BASE+54) +/* MPEG-class control IDs specific to the msm_vidc driver */ +#define V4L2_CID_MPEG_MSM_VIDC_BASE (V4L2_CTRL_CLASS_MPEG | 0x2000) + +#define V4L2_CID_MPEG_VIDC_VIDEO_ENABLE_PICTURE_TYPE \ + (V4L2_CID_MPEG_MSM_VIDC_BASE+0) +#define V4L2_CID_MPEG_VIDC_VIDEO_KEEP_ASPECT_RATIO \ + (V4L2_CID_MPEG_MSM_VIDC_BASE+1) +#define V4L2_CID_MPEG_VIDC_VIDEO_POST_LOOP_DEBLOCKER_MODE \ + (V4L2_CID_MPEG_MSM_VIDC_BASE+2) +#define V4L2_CID_MPEG_VIDC_VIDEO_DIVX_FORMAT \ + (V4L2_CID_MPEG_MSM_VIDC_BASE+3) +enum v4l2_mpeg_vidc_video_divx_format_type { + V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_4 = 0, + V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_5 = 1, + V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_6 = 2, +}; +#define V4L2_CID_MPEG_VIDC_VIDEO_MB_ERROR_MAP_REPORTING \ + (V4L2_CID_MPEG_MSM_VIDC_BASE+4) +#define V4L2_CID_MPEG_VIDC_VIDEO_CONTINUE_DATA_TRANSFER \ + (V4L2_CID_MPEG_MSM_VIDC_BASE+5) + +#define V4L2_CID_MPEG_VIDC_VIDEO_STREAM_FORMAT (V4L2_CID_MPEG_MSM_VIDC_BASE+6) +enum v4l2_mpeg_vidc_video_stream_format { + V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_STARTCODES = 0, + V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_ONE_NAL_PER_BUFFER = 1, + V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_ONE_BYTE_LENGTH = 2, + V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_TWO_BYTE_LENGTH = 3, + V4L2_MPEG_VIDC_VIDEO_NAL_FORMAT_FOUR_BYTE_LENGTH = 4, +}; + +#define V4L2_CID_MPEG_VIDC_VIDEO_OUTPUT_ORDER (V4L2_CID_MPEG_MSM_VIDC_BASE+7) +enum v4l2_mpeg_vidc_video_output_order { + V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DISPLAY = 0, + V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DECODE = 1, +}; + +#define V4L2_CID_MPEG_VIDC_VIDEO_FRAME_RATE (V4L2_CID_MPEG_MSM_VIDC_BASE+8) +#define V4L2_CID_MPEG_VIDC_VIDEO_IDR_PERIOD (V4L2_CID_MPEG_MSM_VIDC_BASE+9) +#define V4L2_CID_MPEG_VIDC_VIDEO_NUM_P_FRAMES (V4L2_CID_MPEG_MSM_VIDC_BASE+10) +#define V4L2_CID_MPEG_VIDC_VIDEO_NUM_B_FRAMES (V4L2_CID_MPEG_MSM_VIDC_BASE+11) +#define V4L2_CID_MPEG_VIDC_VIDEO_REQUEST_IFRAME (V4L2_CID_MPEG_MSM_VIDC_BASE+12) + +#define V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL (V4L2_CID_MPEG_MSM_VIDC_BASE+13) +enum v4l2_mpeg_vidc_video_rate_control { + V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_OFF = 0, + V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_VBR_VFR = 1, + V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_VBR_CFR = 2, + V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_CBR_VFR = 3, + V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_CBR_CFR = 4, +}; + +#define V4L2_CID_MPEG_VIDC_VIDEO_ROTATION (V4L2_CID_MPEG_MSM_VIDC_BASE+14) +enum v4l2_mpeg_vidc_video_rotation { + V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_NONE = 0, + V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_90 = 1, + V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_180 = 2, + V4L2_CID_MPEG_VIDC_VIDEO_ROTATION_270 = 3, +}; +#define MSM_VIDC_BASE V4L2_CID_MPEG_MSM_VIDC_BASE +#define V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL (MSM_VIDC_BASE+15) +enum v4l2_mpeg_vidc_h264_cabac_model { + V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0 = 0, + V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_1 = 1, + V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_2 = 2, +}; + +#define V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_MODE (MSM_VIDC_BASE+16) +enum v4l2_mpeg_vidc_video_intra_refresh_mode { + V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_NONE = 0, + V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_CYCLIC = 1, + V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_ADAPTIVE = 2, + V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_CYCLIC_ADAPTIVE = 3, + V4L2_CID_MPEG_VIDC_VIDEO_INTRA_REFRESH_RANDOM = 4, +}; +#define V4L2_CID_MPEG_VIDC_VIDEO_AIR_MBS (V4L2_CID_MPEG_MSM_VIDC_BASE+17) +#define V4L2_CID_MPEG_VIDC_VIDEO_AIR_REF (V4L2_CID_MPEG_MSM_VIDC_BASE+18) +#define V4L2_CID_MPEG_VIDC_VIDEO_CIR_MBS (V4L2_CID_MPEG_MSM_VIDC_BASE+19) + /* Camera class control IDs */ #define V4L2_CID_CAMERA_CLASS_BASE (V4L2_CTRL_CLASS_CAMERA | 0x900) #define V4L2_CID_CAMERA_CLASS (V4L2_CTRL_CLASS_CAMERA | 1) diff --git a/include/linux/wait.h b/include/linux/wait.h index 1dee81c41ff14578c840dcfdf8f449983937267a..e2094acf2b936b2cc0f4cacbf514fc20a782559d 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/wakelock.h b/include/linux/wakelock.h index f4a698a2288037e101669aae12bd4a779c11e80c..a096d24ada1dbaaa6587c133af7004f486bfbed1 100644 --- a/include/linux/wakelock.h +++ b/include/linux/wakelock.h @@ -1,6 +1,6 @@ /* include/linux/wakelock.h * - * Copyright (C) 2007-2012 Google, Inc. + * Copyright (C) 2007-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 @@ -16,52 +16,76 @@ #ifndef _LINUX_WAKELOCK_H #define _LINUX_WAKELOCK_H +#include #include -#include /* A wake_lock prevents the system from entering suspend or other low power * states when active. If the type is set to WAKE_LOCK_SUSPEND, the wake_lock - * prevents a full system suspend. + * prevents a full system suspend. If the type is WAKE_LOCK_IDLE, low power + * states that cause large interrupt latencies or that disable a set of + * interrupts will not entered from idle until the wake_locks are released. */ enum { WAKE_LOCK_SUSPEND, /* Prevent suspend */ + WAKE_LOCK_IDLE, /* Prevent low power idle */ WAKE_LOCK_TYPE_COUNT }; struct wake_lock { - struct wakeup_source ws; +#ifdef CONFIG_HAS_WAKELOCK + struct list_head link; + int flags; + const char *name; + unsigned long expires; +#ifdef CONFIG_WAKELOCK_STAT + struct { + int count; + int expire_count; + int wakeup_count; + ktime_t total_time; + ktime_t prevent_suspend_time; + ktime_t max_time; + ktime_t last_time; + } stat; +#endif +#endif }; -static inline void wake_lock_init(struct wake_lock *lock, int type, - const char *name) -{ - wakeup_source_init(&lock->ws, name); -} +#ifdef CONFIG_HAS_WAKELOCK + +void wake_lock_init(struct wake_lock *lock, int type, const char *name); +void wake_lock_destroy(struct wake_lock *lock); +void wake_lock(struct wake_lock *lock); +void wake_lock_timeout(struct wake_lock *lock, long timeout); +void wake_unlock(struct wake_lock *lock); + +/* wake_lock_active returns a non-zero value if the wake_lock is currently + * locked. If the wake_lock has a timeout, it does not check the timeout + * but if the timeout had aready been checked it will return 0. + */ +int wake_lock_active(struct wake_lock *lock); -static inline void wake_lock_destroy(struct wake_lock *lock) -{ - wakeup_source_trash(&lock->ws); -} +/* has_wake_lock returns 0 if no wake locks of the specified type are active, + * and non-zero if one or more wake locks are held. Specifically it returns + * -1 if one or more wake locks with no timeout are active or the + * number of jiffies until all active wake locks time out. + */ +long has_wake_lock(int type); -static inline void wake_lock(struct wake_lock *lock) -{ - __pm_stay_awake(&lock->ws); -} +#else -static inline void wake_lock_timeout(struct wake_lock *lock, long timeout) -{ - __pm_wakeup_event(&lock->ws, jiffies_to_msecs(timeout)); -} +static inline void wake_lock_init(struct wake_lock *lock, int type, + const char *name) {} +static inline void wake_lock_destroy(struct wake_lock *lock) {} +static inline void wake_lock(struct wake_lock *lock) {} +static inline void wake_lock_timeout(struct wake_lock *lock, long timeout) {} +static inline void wake_unlock(struct wake_lock *lock) {} -static inline void wake_unlock(struct wake_lock *lock) -{ - __pm_relax(&lock->ws); -} +static inline int wake_lock_active(struct wake_lock *lock) { return 0; } +static inline long has_wake_lock(int type) { return 0; } -static inline int wake_lock_active(struct wake_lock *lock) -{ - return lock->ws.active; -} +#endif #endif + diff --git a/include/linux/wcnss_wlan.h b/include/linux/wcnss_wlan.h new file mode 100644 index 0000000000000000000000000000000000000000..d7e65b051bcf4fdea4788eb42f18ad03d58521d6 --- /dev/null +++ b/include/linux/wcnss_wlan.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 + +enum wcnss_opcode { + WCNSS_WLAN_SWITCH_OFF = 0, + WCNSS_WLAN_SWITCH_ON, +}; + +struct wcnss_wlan_config { + int use_48mhz_xo; +}; + +#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); +void wcnss_wlan_unregister_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops); +void wcnss_register_thermal_mitigation(struct device *dev, + void (*tm_notify)(struct device *dev, int)); +void wcnss_unregister_thermal_mitigation( + void (*tm_notify)(struct device *dev, int)); +struct platform_device *wcnss_get_platform_device(void); +struct wcnss_wlan_config *wcnss_get_wlan_config(void); +int wcnss_wlan_power(struct device *dev, + struct wcnss_wlan_config *cfg, + enum wcnss_opcode opcode); +int req_riva_power_on_lock(char *driver_name); +int free_riva_power_on_lock(char *driver_name); +unsigned int wcnss_get_serial_number(void); +#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/wpce775x.h b/include/linux/wpce775x.h new file mode 100644 index 0000000000000000000000000000000000000000..1803122d1e71daf0cea1eba21ab33e4a7b0906a2 --- /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 0000000000000000000000000000000000000000..03951ce77482c901165ebfa9e40b5a780d902c5f --- /dev/null +++ b/include/media/Kbuild @@ -0,0 +1,7 @@ +header-y += tavarua.h + +header-y += msm_camera.h +header-y += vcap_fmt.h +header-y += msm_isp.h +header-y += msm_gemini.h +header-y += msm_gestures.h diff --git a/include/media/gpio-ir-recv.h b/include/media/gpio-ir-recv.h index 67797bf5d432f6a648961c742d70ed1d008d0199..3eab611d77fa86f6632d16dca3f18df90c69bb7b 100644 --- a/include/media/gpio-ir-recv.h +++ b/include/media/gpio-ir-recv.h @@ -14,8 +14,9 @@ #define __GPIO_IR_RECV_H__ struct gpio_ir_recv_platform_data { - int gpio_nr; + unsigned int gpio_nr; bool active_low; + bool can_wakeup; }; #endif /* __GPIO_IR_RECV_H__ */ diff --git a/include/media/msm/vcd_api.h b/include/media/msm/vcd_api.h new file mode 100644 index 0000000000000000000000000000000000000000..c93b6966094710a40e2f548cbd5681c808260bda --- /dev/null +++ b/include/media/msm/vcd_api.h @@ -0,0 +1,158 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_DATACORRUPT 0x00000008 +#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_FRAME_FLAG_EOSEQ 0x00200000 + +#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_IDR, + VCD_FRAME_32BIT = 0x7fffffff +}; + +enum vcd_power_state { + VCD_PWR_STATE_ON = 1, + VCD_PWR_STATE_SLEEP, +}; + +struct vcd_aspect_ratio { + u32 aspect_ratio; + u32 extended_par_width; + u32 extended_par_height; +}; + +struct vcd_frame_data { + u8 *virtual; + u8 *physical; + u32 ion_flag; + u32 alloc_len; + u32 data_len; + u32 offset; + s64 time_stamp; /* in usecs*/ + 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; + u8 *desc_buf; + u32 desc_size; + struct ion_handle *buff_ion_handle; + struct vcd_aspect_ratio aspect_ratio_info; +}; + +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); +}; + +/*Flags passed to vcd_open*/ +#define VCD_CP_SESSION 0x00000001 + +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, int flags); +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); +u32 vcd_get_ion_status(void); +struct ion_client *vcd_get_ion_client(void); +#endif diff --git a/include/media/msm/vcd_property.h b/include/media/msm/vcd_property.h new file mode 100644 index 0000000000000000000000000000000000000000..cd00800e3b734885122322ebf0c38d1f099ec654 --- /dev/null +++ b/include/media/msm/vcd_property.h @@ -0,0 +1,371 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_I_META_BUFFER_MODE (VCD_START_BASE + 0x22) +#define VCD_I_DISABLE_DMX (VCD_START_BASE + 0x23) +#define VCD_I_DISABLE_DMX_SUPPORT (VCD_START_BASE + 0x24) +#define VCD_I_ENABLE_SPS_PPS_FOR_IDR (VCD_START_BASE + 0x25) +#define VCD_REQ_PERF_LEVEL (VCD_START_BASE + 0x26) +#define VCD_I_SLICE_DELIVERY_MODE (VCD_START_BASE + 0x27) +#define VCD_I_VOP_TIMING_CONSTANT_DELTA (VCD_START_BASE + 0x28) + +#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; +}; + +enum vcd_perf_level { + VCD_PERF_LEVEL0, + VCD_PERF_LEVEL1, + VCD_PERF_LEVEL2, +}; + +#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; +}; + +struct vcd_property_perf_level { + enum vcd_perf_level level; +}; + +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_vop_timing_constant_delta { + u32 constant_delta; /*In usecs */ +}; + +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 *user_virtual_addr; + u8 *kernel_virtual_addr; + u8 *physical_addr; + u8 *dev_addr; + u32 buffer_size; + u32 ysize; + int pmem_fd; + u32 offset; + void *client_data; +}; + +struct vcd_property_h264_mv_buffer { + u8 *kernel_virtual_addr; + u8 *physical_addr; + u32 size; + u32 count; + int pmem_fd; + u32 offset; + u8 *dev_addr; + void *client_data; +}; + +struct vcd_property_buffer_size { + int width; + int height; + int size; + int alignment; +}; + +struct vcd_property_sps_pps_for_idr_enable { + u32 sps_pps_for_idr_enable_flag; +}; + +#endif diff --git a/include/media/msm/vcd_status.h b/include/media/msm/vcd_status.h new file mode 100644 index 0000000000000000000000000000000000000000..7e8ec0bcd5fe419e4a9914c659c8f4fcea50ecff --- /dev/null +++ b/include/media/msm/vcd_status.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_EVT_IND_INFO_FIELD_DROPPED (VCD_EVT_IND_BASE + 0x6) + +#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/include/media/msm/vidc_init.h b/include/media/msm/vidc_init.h new file mode 100644 index 0000000000000000000000000000000000000000..c6812130e1dfa19e152c6a235ac70b0c40de6f4d --- /dev/null +++ b/include/media/msm/vidc_init.h @@ -0,0 +1,100 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include +#include + +#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; + unsigned long buff_ion_flag; + struct ion_handle *buff_ion_handle; + int pmem_fd; + struct file *file; + unsigned long dev_addr; + void *client_data; +}; + +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; + struct mutex enrty_queue_lock; + wait_queue_head_t msg_wait; + struct completion event; + struct vcd_property_h264_mv_buffer vcd_h264_mv_buffer; + struct vcd_property_enc_recon_buffer recon_buffer[4]; + u32 event_status; + u32 seq_header_set; + u32 stop_msg; + u32 stop_called; + u32 stop_sync_cb; + struct ion_client *user_ion_client; + struct ion_handle *seq_hdr_ion_handle; + struct ion_handle *h264_mv_ion_handle; + struct ion_handle *recon_buffer_ion_handle[4]; + u32 dmx_disable; +}; + +void __iomem *vidc_get_ioaddr(void); +int vidc_load_firmware(void); +void vidc_release_firmware(void); +u32 vidc_get_fd_info(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, int pmem_fd, + unsigned long kvaddr, int index, + struct ion_handle **buff_handle); +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, unsigned long length); +u32 vidc_insert_addr_table_kernel(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, unsigned long user_vaddr, + unsigned long kernel_vaddr, unsigned long phys_addr, + unsigned int max_num_buffers, + unsigned long length); +u32 vidc_delete_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, unsigned long user_vaddr, + unsigned long *kernel_vaddr); +void vidc_cleanup_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer); + +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/include/media/msm/vidc_type.h b/include/media/msm/vidc_type.h new file mode 100644 index 0000000000000000000000000000000000000000..d4db0a0a09547f50c5145f46299f01fa911a6193 --- /dev/null +++ b/include/media/msm/vidc_type.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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/include/media/msm_camera.h b/include/media/msm_camera.h new file mode 100644 index 0000000000000000000000000000000000000000..d4cf1d2e5a915aa357f774c12479670d97a08c96 --- /dev/null +++ b/include/media/msm_camera.h @@ -0,0 +1,1443 @@ +/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_CAMERA_H +#define __LINUX_MSM_CAMERA_H + +#ifdef MSM_CAMERA_BIONIC +#include +#endif +#include +#include +#ifdef __KERNEL__ +#include +#endif +#ifdef MSM_CAMERA_GCC +#include +#else +#include +#endif + +#include + +#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_V4L2_EVT_NOTIFY \ + _IOR(MSM_CAM_IOCTL_MAGIC, 40, struct v4l2_event *) + +#define MSM_CAM_IOCTL_SET_MEM_MAP_INFO \ + _IOR(MSM_CAM_IOCTL_MAGIC, 41, struct msm_mem_map_info *) + +#define MSM_CAM_IOCTL_ACTUATOR_IO_CFG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 42, struct msm_actuator_cfg_data *) + +#define MSM_CAM_IOCTL_MCTL_POST_PROC \ + _IOW(MSM_CAM_IOCTL_MAGIC, 43, struct msm_mctl_post_proc_cmd *) + +#define MSM_CAM_IOCTL_RESERVE_FREE_FRAME \ + _IOW(MSM_CAM_IOCTL_MAGIC, 44, struct msm_cam_evt_divert_frame *) + +#define MSM_CAM_IOCTL_RELEASE_FREE_FRAME \ + _IOR(MSM_CAM_IOCTL_MAGIC, 45, struct msm_cam_evt_divert_frame *) + +#define MSM_CAM_IOCTL_PICT_PP_DIVERT_DONE \ + _IOR(MSM_CAM_IOCTL_MAGIC, 46, struct msm_pp_frame *) + +#define MSM_CAM_IOCTL_SENSOR_V4l2_S_CTRL \ + _IOR(MSM_CAM_IOCTL_MAGIC, 47, struct v4l2_control) + +#define MSM_CAM_IOCTL_SENSOR_V4l2_QUERY_CTRL \ + _IOR(MSM_CAM_IOCTL_MAGIC, 48, struct v4l2_queryctrl) + +#define MSM_CAM_IOCTL_GET_KERNEL_SYSTEM_TIME \ + _IOW(MSM_CAM_IOCTL_MAGIC, 49, struct timeval *) + +#define MSM_CAM_IOCTL_SET_VFE_OUTPUT_TYPE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 50, uint32_t *) + +#define MSM_CAM_IOCTL_MCTL_DIVERT_DONE \ + _IOR(MSM_CAM_IOCTL_MAGIC, 51, struct msm_cam_evt_divert_frame *) + +#define MSM_CAM_IOCTL_GET_ACTUATOR_INFO \ + _IOW(MSM_CAM_IOCTL_MAGIC, 52, struct msm_actuator_cfg_data *) + +#define MSM_CAM_IOCTL_EEPROM_IO_CFG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 53, struct msm_eeprom_cfg_data *) + +struct msm_mctl_pp_cmd { + int32_t id; + uint16_t length; + void *value; +}; + +struct msm_mctl_post_proc_cmd { + int32_t type; + struct msm_mctl_pp_cmd cmd; +}; + +#define MSM_CAMERA_LED_OFF 0 +#define MSM_CAMERA_LED_LOW 1 +#define MSM_CAMERA_LED_HIGH 2 +#define MSM_CAMERA_LED_INIT 3 +#define MSM_CAMERA_LED_RELEASE 4 + +#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 MAX_CAM_NAME_SIZE 32 +#define MAX_ACT_MOD_NAME_SIZE 32 +#define MAX_ACT_NAME_SIZE 32 +#define NUM_ACTUATOR_DIR 2 +#define MAX_ACTUATOR_SCENARIO 8 +#define MAX_ACTUATOR_REGION 5 +#define MAX_ACTUATOR_INIT_SET 12 +#define MAX_ACTUATOR_TYPE_SIZE 32 +#define MAX_ACTUATOR_REG_TBL_SIZE 8 + + +#define MSM_MAX_CAMERA_CONFIGS 2 + +#define PP_SNAP 0x01 +#define PP_RAW_SNAP ((0x01)<<1) +#define PP_PREV ((0x01)<<2) +#define PP_THUMB ((0x01)<<3) +#define PP_MASK (PP_SNAP|PP_RAW_SNAP|PP_PREV|PP_THUMB) + +#define MSM_CAM_CTRL_CMD_DONE 0 +#define MSM_CAM_SENSOR_VFE_CMD 1 + +/* Should be same as VIDEO_MAX_PLANES in videodev2.h */ +#define MAX_PLANES 8 + +/***************************************************** + * 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? */ + int queue_idx; + uint32_t evt_id; + uint32_t stream_type; /* used to pass value to qcamera server */ + int config_ident; /*used as identifier for config node*/ +}; + +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 timespec timestamp; +}; + +struct msm_pp_frame_sp { + /* phy addr of the buffer */ + unsigned long phy_addr; + uint32_t y_off; + uint32_t cbcr_off; + /* buffer length */ + uint32_t length; + int32_t fd; + uint32_t addr_offset; + /* mapped addr */ + unsigned long vaddr; +}; + +struct msm_pp_frame_mp { + /* phy addr of the plane */ + unsigned long phy_addr; + /* offset of plane data */ + uint32_t data_offset; + /* plane length */ + uint32_t length; + int32_t fd; + uint32_t addr_offset; + /* mapped addr */ + unsigned long vaddr; +}; + +struct msm_pp_frame { + uint32_t handle; /* stores vb cookie */ + uint32_t frame_id; + unsigned short buf_idx; + int path; + unsigned short image_type; + unsigned short num_planes; /* 1 for sp */ + struct timeval timestamp; + union { + struct msm_pp_frame_sp sp; + struct msm_pp_frame_mp mp[MAX_PLANES]; + }; + int node_type; +}; + +struct msm_cam_evt_divert_frame { + unsigned short image_mode; + unsigned short op_mode; + unsigned short inst_idx; + unsigned short node_idx; + struct msm_pp_frame frame; + int do_pp; +}; + +struct msm_mctl_pp_cmd_ack_event { + uint32_t cmd; /* VPE_CMD_ZOOM? */ + int status; /* 0 done, < 0 err */ + uint32_t cookie; /* daemon's cookie */ +}; + +struct msm_mctl_pp_event_info { + int32_t event; + union { + struct msm_mctl_pp_cmd_ack_event ack; + }; +}; + +struct msm_isp_event_ctrl { + unsigned short resptype; + union { + struct msm_cam_evt_msg isp_msg; + struct msm_ctrl_cmd ctrl; + struct msm_cam_evt_divert_frame div_frame; + struct msm_mctl_pp_event_info pp_event_info; + } 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_DIV_FRAME_EVT_MSG 5 +#define MSM_CAM_RESP_DONE_EVENT 6 +#define MSM_CAM_RESP_MCTL_PP_EVENT 7 +#define MSM_CAM_RESP_MAX 8 + +#define MSM_CAM_APP_NOTIFY_EVENT 0 +#define MSM_CAM_APP_NOTIFY_ERROR_EVENT 1 + +/* 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 +#define CMD_CONFIG_PING_ADDR 46 +#define CMD_CONFIG_PONG_ADDR 47 +#define CMD_CONFIG_FREE_BUF_ADDR 48 +#define CMD_AXI_CFG_ZSL_ALL_CHNLS 49 +#define CMD_AXI_CFG_VIDEO_ALL_CHNLS 50 +#define CMD_VFE_BUFFER_RELEASE 51 +#define CMD_VFE_PROCESS_IRQ 52 + +#define CMD_AXI_CFG_PRIM 0xF1 +#define CMD_AXI_CFG_PRIM_ALL_CHNLS 0xF2 +#define CMD_AXI_CFG_SEC 0xF4 +#define CMD_AXI_CFG_SEC_ALL_CHNLS 0xF8 + +/* 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; + uint32_t planar0_off; + uint32_t planar1_off; + uint32_t planar2_off; + uint8_t active; +}; + +struct outputCfg { + uint32_t height; + uint32_t width; + + uint32_t window_height_firstline; + uint32_t window_height_lastline; +}; + +#define VIDEO_NODE 0 +#define MCTL_NODE 1 + +#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 OUTPUT_ALL_CHNLS 8 +#define OUTPUT_VIDEO_ALL_CHNLS 9 +#define OUTPUT_ZSL_ALL_CHNLS 10 +#define LAST_AXI_OUTPUT_MODE_ENUM = OUTPUT_ZSL_ALL_CHNLS + +#define OUTPUT_PRIM 0xF1 +#define OUTPUT_PRIM_ALL_CHNLS 0xF2 +#define OUTPUT_SEC 0xF4 +#define OUTPUT_SEC_ALL_CHNLS 0xF8 + + +#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_mem_map_info { + uint32_t cookie; + uint32_t length; + uint32_t mem_type; +}; + +#define MSM_MEM_MMAP 0 +#define MSM_MEM_USERPTR 1 +#define MSM_PLANE_MAX 8 +#define MSM_PLANE_Y 0 +#define MSM_PLANE_UV 1 + +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; + uint32_t planar0_off; + uint32_t planar1_off; + uint32_t planar2_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; + + struct ion_allocation_data ion_alloc; + struct ion_fd_data fd_data; +}; + +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_p0_off; + uint32_t buf_p1_off; + uint32_t buf_p0_stride; + uint32_t buf_p1_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 { + uint8_t awb_ymin; + 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; + int length; + struct ion_handle *handle; + 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_MMAP_ENTRY (V4L2_CID_PRIVATE_BASE+16) +#define MSM_V4L2_PID_MMAP_INST (V4L2_CID_PRIVATE_BASE+17) +#define MSM_V4L2_PID_PP_PLANE_INFO (V4L2_CID_PRIVATE_BASE+18) +#define MSM_V4L2_PID_MAX MSM_V4L2_PID_PP_PLANE_INFO + +/* 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) +/* camera operation mode for jpeg snapshot - one frame output queue */ +#define MSM_V4L2_CAM_OP_JPEG_CAPTURE (MSM_V4L2_CAM_OP_DEFAULT+6) + + +#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_GET_OUTPUT_INFO 32 +#define CFG_GET_EEPROM_INFO 33 +#define CFG_GET_EEPROM_DATA 34 +#define CFG_SET_ACTUATOR_INFO 35 +#define CFG_GET_ACTUATOR_INFO 36 +/* TBD: QRD */ +#define CFG_SET_SATURATION 37 +#define CFG_SET_SHARPNESS 38 +#define CFG_SET_TOUCHAEC 39 +#define CFG_SET_AUTO_FOCUS 40 +#define CFG_SET_AUTOFLASH 41 +#define CFG_SET_EXPOSURE_COMPENSATION 42 +#define CFG_SET_ISO 43 +#define CFG_MAX 44 + + +#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_EMBOSS 9 +#define CAMERA_EFFECT_SKETCH 10 +#define CAMERA_EFFECT_NEON 11 +#define CAMERA_EFFECT_MAX 12 + +/* QRD */ +#define CAMERA_EFFECT_BW 10 +#define CAMERA_EFFECT_BLUISH 12 +#define CAMERA_EFFECT_REDDISH 13 +#define CAMERA_EFFECT_GREENISH 14 + +/* QRD */ +#define CAMERA_ANTIBANDING_OFF 0 +#define CAMERA_ANTIBANDING_50HZ 2 +#define CAMERA_ANTIBANDING_60HZ 1 +#define CAMERA_ANTIBANDING_AUTO 3 + +#define CAMERA_CONTRAST_LV0 0 +#define CAMERA_CONTRAST_LV1 1 +#define CAMERA_CONTRAST_LV2 2 +#define CAMERA_CONTRAST_LV3 3 +#define CAMERA_CONTRAST_LV4 4 +#define CAMERA_CONTRAST_LV5 5 +#define CAMERA_CONTRAST_LV6 6 +#define CAMERA_CONTRAST_LV7 7 +#define CAMERA_CONTRAST_LV8 8 +#define CAMERA_CONTRAST_LV9 9 + +#define CAMERA_BRIGHTNESS_LV0 0 +#define CAMERA_BRIGHTNESS_LV1 1 +#define CAMERA_BRIGHTNESS_LV2 2 +#define CAMERA_BRIGHTNESS_LV3 3 +#define CAMERA_BRIGHTNESS_LV4 4 +#define CAMERA_BRIGHTNESS_LV5 5 +#define CAMERA_BRIGHTNESS_LV6 6 +#define CAMERA_BRIGHTNESS_LV7 7 +#define CAMERA_BRIGHTNESS_LV8 8 + + +#define CAMERA_SATURATION_LV0 0 +#define CAMERA_SATURATION_LV1 1 +#define CAMERA_SATURATION_LV2 2 +#define CAMERA_SATURATION_LV3 3 +#define CAMERA_SATURATION_LV4 4 +#define CAMERA_SATURATION_LV5 5 +#define CAMERA_SATURATION_LV6 6 +#define CAMERA_SATURATION_LV7 7 +#define CAMERA_SATURATION_LV8 8 + +#define CAMERA_SHARPNESS_LV0 0 +#define CAMERA_SHARPNESS_LV1 3 +#define CAMERA_SHARPNESS_LV2 6 +#define CAMERA_SHARPNESS_LV3 9 +#define CAMERA_SHARPNESS_LV4 12 +#define CAMERA_SHARPNESS_LV5 15 +#define CAMERA_SHARPNESS_LV6 18 +#define CAMERA_SHARPNESS_LV7 21 +#define CAMERA_SHARPNESS_LV8 24 +#define CAMERA_SHARPNESS_LV9 27 +#define CAMERA_SHARPNESS_LV10 30 + +#define CAMERA_SETAE_AVERAGE 0 +#define CAMERA_SETAE_CENWEIGHT 1 + +#define CAMERA_WB_AUTO 1 /* This list must match aeecamera.h */ +#define CAMERA_WB_CUSTOM 2 +#define CAMERA_WB_INCANDESCENT 3 +#define CAMERA_WB_FLUORESCENT 4 +#define CAMERA_WB_DAYLIGHT 5 +#define CAMERA_WB_CLOUDY_DAYLIGHT 6 +#define CAMERA_WB_TWILIGHT 7 +#define CAMERA_WB_SHADE 8 + +#define CAMERA_EXPOSURE_COMPENSATION_LV0 12 +#define CAMERA_EXPOSURE_COMPENSATION_LV1 6 +#define CAMERA_EXPOSURE_COMPENSATION_LV2 0 +#define CAMERA_EXPOSURE_COMPENSATION_LV3 -6 +#define CAMERA_EXPOSURE_COMPENSATION_LV4 -12 + +enum msm_v4l2_saturation_level { + MSM_V4L2_SATURATION_L0, + MSM_V4L2_SATURATION_L1, + MSM_V4L2_SATURATION_L2, + MSM_V4L2_SATURATION_L3, + MSM_V4L2_SATURATION_L4, + MSM_V4L2_SATURATION_L5, + MSM_V4L2_SATURATION_L6, + MSM_V4L2_SATURATION_L7, + MSM_V4L2_SATURATION_L8, + MSM_V4L2_SATURATION_L9, + MSM_V4L2_SATURATION_L10, +}; + +enum msm_v4l2_exposure_level { + MSM_V4L2_EXPOSURE_N2, + MSM_V4L2_EXPOSURE_N1, + MSM_V4L2_EXPOSURE_D, + MSM_V4L2_EXPOSURE_P1, + MSM_V4L2_EXPOSURE_P2, +}; + +enum msm_v4l2_sharpness_level { + MSM_V4L2_SHARPNESS_L0, + MSM_V4L2_SHARPNESS_L1, + MSM_V4L2_SHARPNESS_L2, + MSM_V4L2_SHARPNESS_L3, + MSM_V4L2_SHARPNESS_L4, + MSM_V4L2_SHARPNESS_L5, + MSM_V4L2_SHARPNESS_L6, +}; + +enum msm_v4l2_expo_metering_mode { + MSM_V4L2_EXP_FRAME_AVERAGE, + MSM_V4L2_EXP_CENTER_WEIGHTED, + MSM_V4L2_EXP_SPOT_METERING, +}; + +enum msm_v4l2_iso_mode { + MSM_V4L2_ISO_AUTO = 0, + MSM_V4L2_ISO_DEBLUR, + MSM_V4L2_ISO_100, + MSM_V4L2_ISO_200, + MSM_V4L2_ISO_400, + MSM_V4L2_ISO_800, + MSM_V4L2_ISO_1600, +}; + +enum msm_v4l2_wb_mode { + MSM_V4L2_WB_MIN_MINUS_1, + MSM_V4L2_WB_AUTO = 1, + MSM_V4L2_WB_CUSTOM, + MSM_V4L2_WB_INCANDESCENT, + MSM_V4L2_WB_FLUORESCENT, + MSM_V4L2_WB_DAYLIGHT, + MSM_V4L2_WB_CLOUDY_DAYLIGHT, + MSM_V4L2_WB_TWILIGHT, + MSM_V4L2_WB_SHADE, + MSM_V4L2_WB_OFF, +}; + +enum msm_v4l2_power_line_frequency { + MSM_V4L2_POWER_LINE_OFF, + MSM_V4L2_POWER_LINE_60HZ, + MSM_V4L2_POWER_LINE_50HZ, + MSM_V4L2_POWER_LINE_AUTO, +}; + +#define CAMERA_ISO_TYPE_AUTO 0 +#define CAMEAR_ISO_TYPE_HJR 1 +#define CAMEAR_ISO_TYPE_100 2 +#define CAMERA_ISO_TYPE_200 3 +#define CAMERA_ISO_TYPE_400 4 +#define CAMEAR_ISO_TYPE_800 5 +#define CAMERA_ISO_TYPE_1600 6 + +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; +}; + +enum msm_sensor_resolution_t { + MSM_SENSOR_RES_FULL, + MSM_SENSOR_RES_QTR, + 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, +}; + +struct msm_sensor_output_info_t { + uint16_t x_output; + uint16_t y_output; + uint16_t line_length_pclk; + uint16_t frame_length_lines; + uint32_t vt_pixel_clk; + uint32_t op_pixel_clk; + uint16_t binning_factor; +}; + +struct sensor_output_info_t { + struct msm_sensor_output_info_t *output_info; + uint16_t num_info; +}; + +struct mirror_flip { + int32_t x_mirror; + int32_t y_flip; +}; + +struct cord { + uint32_t x; + uint32_t y; +}; + +struct msm_eeprom_data_t { + void *eeprom_data; + uint16_t index; +}; + +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; + uint8_t iso_type; + 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; + struct sensor_output_info_t output_info; + struct msm_eeprom_data_t eeprom_data; + /* QRD */ + uint16_t antibanding; + uint8_t contrast; + uint8_t saturation; + uint8_t sharpness; + int8_t brightness; + int ae_mode; + uint8_t wb_val; + int8_t exp_compensation; + struct cord aec_cord; + int is_autoflash; + struct mirror_flip mirror_flip; + } cfg; +}; + +struct damping_params_t { + uint32_t damping_step; + uint32_t damping_delay; + uint32_t hw_params; +}; + +enum actuator_type { + ACTUATOR_VCM, + ACTUATOR_PIEZO, +}; + +enum msm_actuator_data_type { + MSM_ACTUATOR_BYTE_DATA = 1, + MSM_ACTUATOR_WORD_DATA, +}; + +enum msm_actuator_addr_type { + MSM_ACTUATOR_BYTE_ADDR = 1, + MSM_ACTUATOR_WORD_ADDR, +}; + +enum msm_actuator_write_type { + MSM_ACTUATOR_WRITE_HW_DAMP, + MSM_ACTUATOR_WRITE_DAC, +}; + +struct msm_actuator_reg_params_t { + enum msm_actuator_write_type reg_write_type; + uint32_t hw_mask; + uint16_t reg_addr; + uint16_t hw_shift; + uint16_t data_shift; +}; + +struct reg_settings_t { + uint16_t reg_addr; + uint16_t reg_data; +}; + +struct region_params_t { + /* [0] = ForwardDirection Macro boundary + [1] = ReverseDirection Inf boundary + */ + uint16_t step_bound[2]; + uint16_t code_per_step; +}; + +struct msm_actuator_move_params_t { + int8_t dir; + int8_t sign_dir; + int16_t dest_step_pos; + int32_t num_steps; + struct damping_params_t *ringing_params; +}; + +struct msm_actuator_tuning_params_t { + int16_t initial_code; + uint16_t pwd_step; + uint16_t region_size; + uint32_t total_steps; + struct region_params_t *region_params; +}; + +struct msm_actuator_params_t { + enum actuator_type act_type; + uint8_t reg_tbl_size; + uint16_t data_size; + uint16_t init_setting_size; + uint32_t i2c_addr; + enum msm_actuator_addr_type i2c_addr_type; + enum msm_actuator_data_type i2c_data_type; + struct msm_actuator_reg_params_t *reg_tbl_params; + struct reg_settings_t *init_settings; +}; + +struct msm_actuator_set_info_t { + struct msm_actuator_params_t actuator_params; + struct msm_actuator_tuning_params_t af_tuning_params; +}; + +struct msm_actuator_get_info_t { + uint32_t focal_length_num; + uint32_t focal_length_den; + uint32_t f_number_num; + uint32_t f_number_den; + uint32_t f_pix_num; + uint32_t f_pix_den; + uint32_t total_f_dist_num; + uint32_t total_f_dist_den; + uint32_t hor_view_angle_num; + uint32_t hor_view_angle_den; + uint32_t ver_view_angle_num; + uint32_t ver_view_angle_den; +}; + +enum af_camera_name { + ACTUATOR_MAIN_CAM_0, + ACTUATOR_MAIN_CAM_1, + ACTUATOR_MAIN_CAM_2, + ACTUATOR_MAIN_CAM_3, + ACTUATOR_MAIN_CAM_4, + ACTUATOR_MAIN_CAM_5, + ACTUATOR_WEB_CAM_0, + ACTUATOR_WEB_CAM_1, + ACTUATOR_WEB_CAM_2, +}; + +struct msm_actuator_cfg_data { + int cfgtype; + uint8_t is_af_supported; + union { + struct msm_actuator_move_params_t move; + struct msm_actuator_set_info_t set_info; + struct msm_actuator_get_info_t get_info; + enum af_camera_name cam_name; + } cfg; +}; + +struct msm_eeprom_support { + uint16_t is_supported; + uint16_t size; + uint16_t index; + uint16_t qvalue; +}; + +struct msm_calib_wb { + uint16_t r_over_g; + uint16_t b_over_g; + uint16_t gr_over_gb; +}; + +struct msm_calib_af { + uint16_t macro_dac; + uint16_t inf_dac; + uint16_t start_dac; +}; + +struct msm_calib_lsc { + uint16_t r_gain[221]; + uint16_t b_gain[221]; + uint16_t gr_gain[221]; + uint16_t gb_gain[221]; +}; + +struct pixel_t { + int x; + int y; +}; + +struct msm_calib_dpc { + uint16_t validcount; + struct pixel_t snapshot_coord[128]; + struct pixel_t preview_coord[128]; + struct pixel_t video_coord[128]; +}; + +struct msm_camera_eeprom_info_t { + struct msm_eeprom_support af; + struct msm_eeprom_support wb; + struct msm_eeprom_support lsc; + struct msm_eeprom_support dpc; +}; + +struct msm_eeprom_cfg_data { + int cfgtype; + uint8_t is_eeprom_supported; + union { + struct msm_eeprom_data_t get_data; + struct msm_camera_eeprom_info_t get_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]; + int config_dev_id[MSM_MAX_CAMERA_CONFIGS]; +}; + +struct msm_mctl_node_info { + int num_mctl_nodes; + const char *mctl_node_name[MSM_MAX_CAMERA_SENSORS]; +}; + +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; + uint8_t strobe_flash_enabled; + uint8_t actuator_enabled; + int8_t total_steps; + uint8_t support_3d; + enum flash_type flashtype; + enum sensor_type_t sensor_type; + uint32_t pxlcode; /* enum v4l2_mbus_pixelcode */ + uint32_t camera_type; /* msm_camera_type */ + int mount_angle; + uint32_t max_width; + uint32_t max_height; +}; + +#define V4L2_SINGLE_PLANE 0 +#define V4L2_MULTI_PLANE_Y 0 +#define V4L2_MULTI_PLANE_CBCR 1 +#define V4L2_MULTI_PLANE_CB 1 +#define V4L2_MULTI_PLANE_CR 2 + +struct plane_data { + int plane_id; + uint32_t offset; + unsigned long size; +}; + +struct img_plane_info { + uint32_t width; + uint32_t height; + uint32_t pixelformat; + uint8_t buffer_type; /*Single/Multi planar*/ + uint8_t output_port; + uint32_t ext_mode; + uint8_t num_planes; + struct plane_data plane[MAX_PLANES]; + uint32_t sp_y_offset; + uint8_t vpe_can_use; +}; + +#define QCAMERA_NAME "qcamera" +#define QCAMERA_DEVICE_GROUP_ID 1 +#define QCAMERA_VNODE_GROUP_ID 2 + +#define MSM_CAM_V4L2_IOCTL_GET_CAMERA_INFO \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 1, struct msm_camera_v4l2_ioctl_t *) + +#define MSM_CAM_V4L2_IOCTL_GET_CONFIG_INFO \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 2, struct msm_camera_v4l2_ioctl_t *) + +#define MSM_CAM_V4L2_IOCTL_GET_MCTL_INFO \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 3, struct msm_camera_v4l2_ioctl_t *) + +#define MSM_CAM_V4L2_IOCTL_CTRL_CMD_DONE \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 4, struct msm_camera_v4l2_ioctl_t *) + +#define MSM_CAM_V4L2_IOCTL_GET_EVENT_PAYLOAD \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 5, struct msm_camera_v4l2_ioctl_t *) + +#define MSM_CAM_IOCTL_SEND_EVENT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 6, struct v4l2_event) + +struct msm_camera_v4l2_ioctl_t { + void __user *ioctl_ptr; +}; + +#endif /* __LINUX_MSM_CAMERA_H */ diff --git a/include/media/msm_gemini.h b/include/media/msm_gemini.h new file mode 100644 index 0000000000000000000000000000000000000000..0167335d961d933d90aeced87790c690f6651768 --- /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/msm_gestures.h b/include/media/msm_gestures.h new file mode 100644 index 0000000000000000000000000000000000000000..c9af0344bc89f2dfce6424180abc930d87b8f5b4 --- /dev/null +++ b/include/media/msm_gestures.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_GESTURES_H +#define __LINUX_MSM_GESTURES_H + +#include +#include +#include + +#define MSM_GES_IOCTL_CTRL_COMMAND \ + _IOW('V', BASE_VIDIOC_PRIVATE + 20, struct v4l2_control) + +#define VIDIOC_MSM_GESTURE_EVT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 21, struct v4l2_event) + +#define MSM_GES_GET_EVT_PAYLOAD \ + _IOW('V', BASE_VIDIOC_PRIVATE + 22, struct msm_ges_evt) + +#define VIDIOC_MSM_GESTURE_CAM_EVT \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 23, int) + +#define MSM_GES_RESP_V4L2 MSM_CAM_RESP_MAX +#define MSM_GES_RESP_MAX (MSM_GES_RESP_V4L2 + 1) + +#define MSM_SVR_RESP_MAX MSM_GES_RESP_MAX + + +#define MSM_V4L2_GES_BASE 100 +#define MSM_V4L2_GES_OPEN (MSM_V4L2_GES_BASE + 0) +#define MSM_V4L2_GES_CLOSE (MSM_V4L2_GES_BASE + 1) +#define MSM_V4L2_GES_CAM_OPEN (MSM_V4L2_GES_BASE + 2) +#define MSM_V4L2_GES_CAM_CLOSE (MSM_V4L2_GES_BASE + 3) + +#define MSM_GES_APP_EVT_MIN (V4L2_EVENT_PRIVATE_START + 0x14) +#define MSM_GES_APP_NOTIFY_EVENT (MSM_GES_APP_EVT_MIN + 0) +#define MSM_GES_APP_NOTIFY_ERROR_EVENT (MSM_GES_APP_EVT_MIN + 1) +#define MSM_GES_APP_EVT_MAX (MSM_GES_APP_EVT_MIN + 2) + +#define MSM_GESTURE_CID_CTRL_CMD V4L2_CID_BRIGHTNESS + +#define MAX_GES_EVENTS 25 + +struct msm_ges_ctrl_cmd { + int type; + void *value; + int len; + int fd; + uint32_t cookie; +}; + +struct msm_ges_evt { + void *evt_data; + int evt_len; +}; + +#endif /*__LINUX_MSM_GESTURES_H*/ diff --git a/include/media/msm_isp.h b/include/media/msm_isp.h new file mode 100644 index 0000000000000000000000000000000000000000..cb728a0c3167ef014c41ee42d8b08f37a517e7da --- /dev/null +++ b/include/media/msm_isp.h @@ -0,0 +1,331 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_ISP_H__ +#define __MSM_ISP_H__ + +#define BIT(nr) (1UL << (nr)) + +/* ISP message IDs */ +#define MSG_ID_RESET_ACK 0 +#define MSG_ID_START_ACK 1 +#define MSG_ID_STOP_ACK 2 +#define MSG_ID_UPDATE_ACK 3 +#define MSG_ID_OUTPUT_P 4 +#define MSG_ID_OUTPUT_T 5 +#define MSG_ID_OUTPUT_S 6 +#define MSG_ID_OUTPUT_V 7 +#define MSG_ID_SNAPSHOT_DONE 8 +#define MSG_ID_STATS_AEC 9 +#define MSG_ID_STATS_AF 10 +#define MSG_ID_STATS_AWB 11 +#define MSG_ID_STATS_RS 12 +#define MSG_ID_STATS_CS 13 +#define MSG_ID_STATS_IHIST 14 +#define MSG_ID_STATS_SKIN 15 +#define MSG_ID_EPOCH1 16 +#define MSG_ID_EPOCH2 17 +#define MSG_ID_SYNC_TIMER0_DONE 18 +#define MSG_ID_SYNC_TIMER1_DONE 19 +#define MSG_ID_SYNC_TIMER2_DONE 20 +#define MSG_ID_ASYNC_TIMER0_DONE 21 +#define MSG_ID_ASYNC_TIMER1_DONE 22 +#define MSG_ID_ASYNC_TIMER2_DONE 23 +#define MSG_ID_ASYNC_TIMER3_DONE 24 +#define MSG_ID_AE_OVERFLOW 25 +#define MSG_ID_AF_OVERFLOW 26 +#define MSG_ID_AWB_OVERFLOW 27 +#define MSG_ID_RS_OVERFLOW 28 +#define MSG_ID_CS_OVERFLOW 29 +#define MSG_ID_IHIST_OVERFLOW 30 +#define MSG_ID_SKIN_OVERFLOW 31 +#define MSG_ID_AXI_ERROR 32 +#define MSG_ID_CAMIF_OVERFLOW 33 +#define MSG_ID_VIOLATION 34 +#define MSG_ID_CAMIF_ERROR 35 +#define MSG_ID_BUS_OVERFLOW 36 +#define MSG_ID_SOF_ACK 37 +#define MSG_ID_STOP_REC_ACK 38 +#define MSG_ID_STATS_AWB_AEC 39 +#define MSG_ID_OUTPUT_PRIMARY 40 +#define MSG_ID_OUTPUT_SECONDARY 41 +#define MSG_ID_STATS_COMPOSITE 42 + +/* ISP command IDs */ +#define VFE_CMD_DUMMY_0 0 +#define VFE_CMD_SET_CLK 1 +#define VFE_CMD_RESET 2 +#define VFE_CMD_START 3 +#define VFE_CMD_TEST_GEN_START 4 +#define VFE_CMD_OPERATION_CFG 5 +#define VFE_CMD_AXI_OUT_CFG 6 +#define VFE_CMD_CAMIF_CFG 7 +#define VFE_CMD_AXI_INPUT_CFG 8 +#define VFE_CMD_BLACK_LEVEL_CFG 9 +#define VFE_CMD_MESH_ROLL_OFF_CFG 10 +#define VFE_CMD_DEMUX_CFG 11 +#define VFE_CMD_FOV_CFG 12 +#define VFE_CMD_MAIN_SCALER_CFG 13 +#define VFE_CMD_WB_CFG 14 +#define VFE_CMD_COLOR_COR_CFG 15 +#define VFE_CMD_RGB_G_CFG 16 +#define VFE_CMD_LA_CFG 17 +#define VFE_CMD_CHROMA_EN_CFG 18 +#define VFE_CMD_CHROMA_SUP_CFG 19 +#define VFE_CMD_MCE_CFG 20 +#define VFE_CMD_SK_ENHAN_CFG 21 +#define VFE_CMD_ASF_CFG 22 +#define VFE_CMD_S2Y_CFG 23 +#define VFE_CMD_S2CbCr_CFG 24 +#define VFE_CMD_CHROMA_SUBS_CFG 25 +#define VFE_CMD_OUT_CLAMP_CFG 26 +#define VFE_CMD_FRAME_SKIP_CFG 27 +#define VFE_CMD_DUMMY_1 28 +#define VFE_CMD_DUMMY_2 29 +#define VFE_CMD_DUMMY_3 30 +#define VFE_CMD_UPDATE 31 +#define VFE_CMD_BL_LVL_UPDATE 32 +#define VFE_CMD_DEMUX_UPDATE 33 +#define VFE_CMD_FOV_UPDATE 34 +#define VFE_CMD_MAIN_SCALER_UPDATE 35 +#define VFE_CMD_WB_UPDATE 36 +#define VFE_CMD_COLOR_COR_UPDATE 37 +#define VFE_CMD_RGB_G_UPDATE 38 +#define VFE_CMD_LA_UPDATE 39 +#define VFE_CMD_CHROMA_EN_UPDATE 40 +#define VFE_CMD_CHROMA_SUP_UPDATE 41 +#define VFE_CMD_MCE_UPDATE 42 +#define VFE_CMD_SK_ENHAN_UPDATE 43 +#define VFE_CMD_S2CbCr_UPDATE 44 +#define VFE_CMD_S2Y_UPDATE 45 +#define VFE_CMD_ASF_UPDATE 46 +#define VFE_CMD_FRAME_SKIP_UPDATE 47 +#define VFE_CMD_CAMIF_FRAME_UPDATE 48 +#define VFE_CMD_STATS_AF_UPDATE 49 +#define VFE_CMD_STATS_AE_UPDATE 50 +#define VFE_CMD_STATS_AWB_UPDATE 51 +#define VFE_CMD_STATS_RS_UPDATE 52 +#define VFE_CMD_STATS_CS_UPDATE 53 +#define VFE_CMD_STATS_SKIN_UPDATE 54 +#define VFE_CMD_STATS_IHIST_UPDATE 55 +#define VFE_CMD_DUMMY_4 56 +#define VFE_CMD_EPOCH1_ACK 57 +#define VFE_CMD_EPOCH2_ACK 58 +#define VFE_CMD_START_RECORDING 59 +#define VFE_CMD_STOP_RECORDING 60 +#define VFE_CMD_DUMMY_5 61 +#define VFE_CMD_DUMMY_6 62 +#define VFE_CMD_CAPTURE 63 +#define VFE_CMD_DUMMY_7 64 +#define VFE_CMD_STOP 65 +#define VFE_CMD_GET_HW_VERSION 66 +#define VFE_CMD_GET_FRAME_SKIP_COUNTS 67 +#define VFE_CMD_OUTPUT1_BUFFER_ENQ 68 +#define VFE_CMD_OUTPUT2_BUFFER_ENQ 69 +#define VFE_CMD_OUTPUT3_BUFFER_ENQ 70 +#define VFE_CMD_JPEG_OUT_BUF_ENQ 71 +#define VFE_CMD_RAW_OUT_BUF_ENQ 72 +#define VFE_CMD_RAW_IN_BUF_ENQ 73 +#define VFE_CMD_STATS_AF_ENQ 74 +#define VFE_CMD_STATS_AE_ENQ 75 +#define VFE_CMD_STATS_AWB_ENQ 76 +#define VFE_CMD_STATS_RS_ENQ 77 +#define VFE_CMD_STATS_CS_ENQ 78 +#define VFE_CMD_STATS_SKIN_ENQ 79 +#define VFE_CMD_STATS_IHIST_ENQ 80 +#define VFE_CMD_DUMMY_8 81 +#define VFE_CMD_JPEG_ENC_CFG 82 +#define VFE_CMD_DUMMY_9 83 +#define VFE_CMD_STATS_AF_START 84 +#define VFE_CMD_STATS_AF_STOP 85 +#define VFE_CMD_STATS_AE_START 86 +#define VFE_CMD_STATS_AE_STOP 87 +#define VFE_CMD_STATS_AWB_START 88 +#define VFE_CMD_STATS_AWB_STOP 89 +#define VFE_CMD_STATS_RS_START 90 +#define VFE_CMD_STATS_RS_STOP 91 +#define VFE_CMD_STATS_CS_START 92 +#define VFE_CMD_STATS_CS_STOP 93 +#define VFE_CMD_STATS_SKIN_START 94 +#define VFE_CMD_STATS_SKIN_STOP 95 +#define VFE_CMD_STATS_IHIST_START 96 +#define VFE_CMD_STATS_IHIST_STOP 97 +#define VFE_CMD_DUMMY_10 98 +#define VFE_CMD_SYNC_TIMER_SETTING 99 +#define VFE_CMD_ASYNC_TIMER_SETTING 100 +#define VFE_CMD_LIVESHOT 101 +#define VFE_CMD_LA_SETUP 102 +#define VFE_CMD_LINEARIZATION_CFG 103 +#define VFE_CMD_DEMOSAICV3 104 +#define VFE_CMD_DEMOSAICV3_ABCC_CFG 105 +#define VFE_CMD_DEMOSAICV3_DBCC_CFG 106 +#define VFE_CMD_DEMOSAICV3_DBPC_CFG 107 +#define VFE_CMD_DEMOSAICV3_ABF_CFG 108 +#define VFE_CMD_DEMOSAICV3_ABCC_UPDATE 109 +#define VFE_CMD_DEMOSAICV3_DBCC_UPDATE 110 +#define VFE_CMD_DEMOSAICV3_DBPC_UPDATE 111 +#define VFE_CMD_XBAR_CFG 112 +#define VFE_CMD_MODULE_CFG 113 +#define VFE_CMD_ZSL 114 +#define VFE_CMD_LINEARIZATION_UPDATE 115 +#define VFE_CMD_DEMOSAICV3_ABF_UPDATE 116 +#define VFE_CMD_CLF_CFG 117 +#define VFE_CMD_CLF_LUMA_UPDATE 118 +#define VFE_CMD_CLF_CHROMA_UPDATE 119 +#define VFE_CMD_PCA_ROLL_OFF_CFG 120 +#define VFE_CMD_PCA_ROLL_OFF_UPDATE 121 +#define VFE_CMD_GET_REG_DUMP 122 +#define VFE_CMD_GET_LINEARIZATON_TABLE 123 +#define VFE_CMD_GET_MESH_ROLLOFF_TABLE 124 +#define VFE_CMD_GET_PCA_ROLLOFF_TABLE 125 +#define VFE_CMD_GET_RGB_G_TABLE 126 +#define VFE_CMD_GET_LA_TABLE 127 +#define VFE_CMD_DEMOSAICV3_UPDATE 128 +#define VFE_CMD_ACTIVE_REGION_CFG 129 +#define VFE_CMD_COLOR_PROCESSING_CONFIG 130 +#define VFE_CMD_STATS_WB_AEC_CONFIG 131 +#define VFE_CMD_STATS_WB_AEC_UPDATE 132 +#define VFE_CMD_Y_GAMMA_CONFIG 133 +#define VFE_CMD_SCALE_OUTPUT1_CONFIG 134 +#define VFE_CMD_SCALE_OUTPUT2_CONFIG 135 +#define VFE_CMD_CAPTURE_RAW 136 +#define VFE_CMD_STOP_LIVESHOT 137 +#define VFE_CMD_RECONFIG_VFE 138 + +struct msm_isp_cmd { + int32_t id; + uint16_t length; + void *value; +}; + +#define VPE_CMD_DUMMY_0 0 +#define VPE_CMD_INIT 1 +#define VPE_CMD_DEINIT 2 +#define VPE_CMD_ENABLE 3 +#define VPE_CMD_DISABLE 4 +#define VPE_CMD_RESET 5 +#define VPE_CMD_FLUSH 6 +#define VPE_CMD_OPERATION_MODE_CFG 7 +#define VPE_CMD_INPUT_PLANE_CFG 8 +#define VPE_CMD_OUTPUT_PLANE_CFG 9 +#define VPE_CMD_INPUT_PLANE_UPDATE 10 +#define VPE_CMD_SCALE_CFG_TYPE 11 +#define VPE_CMD_ZOOM 13 +#define VPE_CMD_MAX 14 + +#define MSM_PP_CMD_TYPE_NOT_USED 0 /* not used */ +#define MSM_PP_CMD_TYPE_VPE 1 /* VPE cmd */ +#define MSM_PP_CMD_TYPE_MCTL 2 /* MCTL cmd */ + +#define MCTL_CMD_DUMMY_0 0 /* not used */ +#define MCTL_CMD_GET_FRAME_BUFFER 1 /* reserve a free frame buffer */ +#define MCTL_CMD_PUT_FRAME_BUFFER 2 /* return the free frame buffer */ +#define MCTL_CMD_DIVERT_FRAME_PP_PATH 3 /* divert frame for pp */ + +/* event typese sending to MCTL PP module */ +#define MCTL_PP_EVENT_NOTUSED 0 +#define MCTL_PP_EVENT_CMD_ACK 1 + +#define VPE_OPERATION_MODE_CFG_LEN 4 +#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 + + +#define CAPTURE_WIDTH 1280 +#define IMEM_Y_SIZE (CAPTURE_WIDTH*16) +#define IMEM_CBCR_SIZE (CAPTURE_WIDTH*8) + +#define IMEM_Y_PING_OFFSET 0x2E000000 +#define IMEM_CBCR_PING_OFFSET (IMEM_Y_PING_OFFSET + IMEM_Y_SIZE) + +#define IMEM_Y_PONG_OFFSET (IMEM_CBCR_PING_OFFSET + IMEM_CBCR_SIZE) +#define IMEM_CBCR_PONG_OFFSET (IMEM_Y_PONG_OFFSET + IMEM_Y_SIZE) + + +struct msm_vpe_op_mode_cfg { + uint8_t op_mode_cfg[VPE_OPERATION_MODE_CFG_LEN]; +}; + +struct msm_vpe_input_plane_cfg { + uint8_t input_plane_cfg[VPE_INPUT_PLANE_CFG_LEN]; +}; + +struct msm_vpe_output_plane_cfg { + uint8_t output_plane_cfg[VPE_OUTPUT_PLANE_CFG_LEN]; +}; + +struct msm_vpe_input_plane_update_cfg { + uint8_t input_plane_update_cfg[VPE_INPUT_PLANE_UPDATE_LEN]; +}; + +struct msm_vpe_scaler_cfg { + uint8_t scaler_cfg[VPE_SCALER_CONFIG_LEN]; +}; + +struct msm_vpe_flush_frame_buffer { + uint32_t src_buf_handle; + uint32_t dest_buf_handle; + int path; +}; + +struct msm_mctl_pp_frame_buffer { + uint32_t buf_handle; + int path; +}; +struct msm_mctl_pp_divert_pp { + int path; + int enable; +}; +struct msm_vpe_clock_rate { + uint32_t rate; +}; +struct msm_pp_crop { + uint32_t src_x; + uint32_t src_y; + uint32_t src_w; + uint32_t src_h; + uint32_t dst_x; + uint32_t dst_y; + uint32_t dst_w; + uint32_t dst_h; + uint8_t update_flag; +}; +#define MSM_MCTL_PP_VPE_FRAME_ACK (1<<0) +#define MSM_MCTL_PP_VPE_FRAME_TO_APP (1<<1) + +struct msm_mctl_pp_frame_cmd { + uint32_t cookie; + uint8_t vpe_output_action; + uint32_t src_buf_handle; + uint32_t dest_buf_handle; + struct msm_pp_crop crop; + int path; + /* TBD: 3D related */ +}; + +#define VFE_OUTPUTS_MAIN_AND_PREVIEW BIT(0) +#define VFE_OUTPUTS_MAIN_AND_VIDEO BIT(1) +#define VFE_OUTPUTS_MAIN_AND_THUMB BIT(2) +#define VFE_OUTPUTS_THUMB_AND_MAIN BIT(3) +#define VFE_OUTPUTS_PREVIEW_AND_VIDEO BIT(4) +#define VFE_OUTPUTS_VIDEO_AND_PREVIEW BIT(5) +#define VFE_OUTPUTS_PREVIEW BIT(6) +#define VFE_OUTPUTS_VIDEO BIT(7) +#define VFE_OUTPUTS_RAW BIT(8) +#define VFE_OUTPUTS_JPEG_AND_THUMB BIT(9) +#define VFE_OUTPUTS_THUMB_AND_JPEG BIT(10) + +#endif /*__MSM_ISP_H__*/ + diff --git a/include/media/msm_v4l2_overlay.h b/include/media/msm_v4l2_overlay.h new file mode 100644 index 0000000000000000000000000000000000000000..c83cfb7b19169b2924b244b9e0fffe9ac20faded --- /dev/null +++ b/include/media/msm_v4l2_overlay.h @@ -0,0 +1,9 @@ +#ifndef LINUX_MSM_V4L2_OVERLAY +#define LINUX_MSM_V4L2_OVERLAY + +#include + +#define VIDIOC_MSM_USERPTR_QBUF \ +_IOWR('V', BASE_VIDIOC_PRIVATE, struct v4l2_buffer) + +#endif diff --git a/include/media/msm_vidc.h b/include/media/msm_vidc.h new file mode 100644 index 0000000000000000000000000000000000000000..baa6a286eb08bbc79d1654c41a2a87970042ff2d --- /dev/null +++ b/include/media/msm_vidc.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_VIDC_H_ +#define _MSM_VIDC_H_ + +#include +#include + +enum core_id { + MSM_VIDC_CORE_0 = 0, + MSM_VIDC_CORES_MAX, +}; + +enum session_type { + MSM_VIDC_ENCODER = 0, + MSM_VIDC_DECODER, + MSM_VIDC_MAX_DEVICES, +}; + +int msm_vidc_open(void *vidc_inst, int core_id, int session_type); +int msm_vidc_close(void *instance); +int msm_vidc_querycap(void *instance, struct v4l2_capability *cap); +int msm_vidc_enum_fmt(void *instance, struct v4l2_fmtdesc *f); +int msm_vidc_s_fmt(void *instance, struct v4l2_format *f); +int msm_vidc_g_fmt(void *instance, struct v4l2_format *f); +int msm_vidc_s_ctrl(void *instance, struct v4l2_control *a); +int msm_vidc_g_ctrl(void *instance, struct v4l2_control *a); +int msm_vidc_reqbufs(void *instance, struct v4l2_requestbuffers *b); +int msm_vidc_prepare_buf(void *instance, struct v4l2_buffer *b); +int msm_vidc_release_buf(void *instance, struct v4l2_buffer *b); +int msm_vidc_qbuf(void *instance, struct v4l2_buffer *b); +int msm_vidc_dqbuf(void *instance, struct v4l2_buffer *b); +int msm_vidc_streamon(void *instance, enum v4l2_buf_type i); +int msm_vidc_streamoff(void *instance, enum v4l2_buf_type i); +int msm_vidc_decoder_cmd(void *instance, struct v4l2_decoder_cmd *dec); +int msm_vidc_poll(void *instance, struct file *filp, + struct poll_table_struct *pt); +#endif diff --git a/include/media/radio-iris.h b/include/media/radio-iris.h new file mode 100644 index 0000000000000000000000000000000000000000..b5e8f2e223a9d17db58b3d6f769a299415cb0cd5 --- /dev/null +++ b/include/media/radio-iris.h @@ -0,0 +1,784 @@ +/* + * + * Copyright (c) 2011-2012 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 reponce packets*/ +#define MAX_RIVA_PEEK_RSP_SIZE 251 +/* default data access */ +#define DEFAULT_DATA_OFFSET 2 +#define DEFAULT_DATA_SIZE 249 +/* Power levels are 0-7, but SOC will expect values from 0-255 + * So the each level step size will be 255/7 = 36 */ +#define FM_TX_PWR_LVL_STEP_SIZE 36 +#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 */ +#define FM_TX_PHY_CFG_MODE 0x3c +#define FM_TX_PHY_CFG_LEN 0x10 +#define FM_TX_PWR_GAIN_OFFSET 14 +/* 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 +#define HCI_OCF_FM_SET_EVENT_MASK 0x0016 +#define HCI_OCF_FM_SET_CH_DET_THRESHOLD 0x0017 +#define HCI_OCF_FM_GET_CH_DET_THRESHOLD 0x0018 +/* HCI trans control commans opcode*/ +#define HCI_OCF_FM_ENABLE_TRANS_REQ 0x0001 +#define HCI_OCF_FM_DISABLE_TRANS_REQ 0x0002 +#define HCI_OCF_FM_GET_TRANS_CONF_REQ 0x0003 +#define HCI_OCF_FM_SET_TRANS_CONF_REQ 0x0004 +#define HCI_OCF_FM_RDS_RT_REQ 0x0008 +#define HCI_OCF_FM_RDS_PS_REQ 0x0009 + + +/* 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 +#define HCI_OCF_FM_SET_CALIBRATION 0x0007 + +/*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 +#define HCI_FM_SET_INTERNAL_TONE_GENRATOR 0x0008 + +/* Opcode OGF */ +#define HCI_OGF_FM_RECV_CTRL_CMD_REQ 0x0013 +#define HCI_OGF_FM_TRANS_CTRL_CMD_REQ 0x0014 +#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_trans_ctrl_cmd_op_pack(ocf) \ + (__u16) hci_opcode_pack(HCI_OGF_FM_TRANS_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 +#define HCI_FM_ENABLE_TRANS_CMD 13 +#define HCI_FM_DISABLE_TRANS_CMD 14 +#define HCI_FM_GET_TX_CONFIG 15 +#define HCI_FM_GET_DET_CH_TH_CMD 16 + +/* Defines for FM TX*/ +#define TX_PS_DATA_LENGTH 96 +#define TX_RT_DATA_LENGTH 64 + +/* ----- 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; + +/* ----- HCI Command request ----- */ +struct hci_fm_trans_conf_req_struct { + __u8 emphasis; + __u8 rds_std; + __u32 band_low_limit; + __u32 band_high_limit; +} __packed; + + +/* ----- HCI Command request ----- */ +struct hci_fm_tx_ps { + __u8 ps_control; + __u16 pi; + __u8 pty; + __u8 ps_repeatcount; + __u8 ps_len; + __u8 ps_data[TX_PS_DATA_LENGTH]; +} __packed; + +struct hci_fm_tx_rt { + __u8 rt_control; + __u16 pi; + __u8 pty; + __u8 ps_len; + __u8 rt_data[TX_RT_DATA_LENGTH]; +} __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; + __u8 param_len; + __u8 param; +} __packed; + +struct hci_fm_def_data_wr_req { + __u8 mode; + __u8 length; + __u8 data[DEFAULT_DATA_SIZE]; +} __packed; + +struct hci_fm_riva_data { + __u8 subopcode; + __u32 start_addr; + __u8 length; +} __packed; + +struct hci_fm_riva_poke { + struct hci_fm_riva_data cmd_params; + __u8 data[MAX_RIVA_PEEK_RSP_SIZE]; +} __packed; + +struct hci_fm_ssbi_req { + __u16 start_addr; + __u8 data; +} __packed; +struct hci_fm_ssbi_peek { + __u16 start_address; +} __packed; + +struct hci_fm_ch_det_threshold { + char sinr; + __u8 sinr_samples; + __u8 low_th; + __u8 high_th; + +} __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; + char sinr; + __u8 intf_det_th; +} __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_rel_freq { + __u8 rel_freq_msb; + __u8 rel_freq_lsb; + +} __packed; +struct hci_ev_srch_list_compl { + __u8 num_stations_found; + struct hci_ev_rel_freq rel_freq[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_get_trans_conf_rsp { + __u8 status; + struct hci_fm_trans_conf_req_struct trans_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[DEFAULT_DATA_SIZE]; +} __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 RDS */ +#define RDS_PTYPE 2 +#define RDS_PID_LOWER 1 +#define RDS_PID_HIGHER 0 +#define RDS_OFFSET 5 +#define RDS_PS_LENGTH_OFFSET 7 +#define RDS_STRING 8 +#define RDS_PS_DATA_OFFSET 8 +#define RDS_CONFIG_OFFSET 3 +#define RDS_AF_JUMP_OFFSET 4 +#define PI_CODE_OFFSET 4 +#define AF_SIZE_OFFSET 6 +#define AF_LIST_OFFSET 7 +/*FM states*/ + +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_TRANS, + FM_RESET, + FM_CALIB +}; + +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, /*0x8000014*/ + + /*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, + V4L2_CID_PRIVATE_IRIS_MPX_DCC, + V4L2_CID_PRIVATE_IRIS_AF_JUMP, + V4L2_CID_PRIVATE_IRIS_RSSI_DELTA, + V4L2_CID_PRIVATE_IRIS_HLSI, /*0x800001d*/ + + /*Diagnostic commands*/ + V4L2_CID_PRIVATE_IRIS_SOFT_MUTE, + V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_ADDR, + V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_LEN, + V4L2_CID_PRIVATE_IRIS_RIVA_PEEK, + V4L2_CID_PRIVATE_IRIS_RIVA_POKE, + V4L2_CID_PRIVATE_IRIS_SSBI_ACCS_ADDR, + V4L2_CID_PRIVATE_IRIS_SSBI_PEEK, + V4L2_CID_PRIVATE_IRIS_SSBI_POKE, + V4L2_CID_PRIVATE_IRIS_TX_TONE, + V4L2_CID_PRIVATE_IRIS_RDS_GRP_COUNTERS, + V4L2_CID_PRIVATE_IRIS_SET_NOTCH_FILTER, /* 0x8000028 */ + V4L2_CID_PRIVATE_IRIS_SET_AUDIO_PATH, /* TAVARUA specific command */ + V4L2_CID_PRIVATE_IRIS_DO_CALIBRATION, + V4L2_CID_PRIVATE_IRIS_SRCH_ALGORITHM, /* TAVARUA specific command */ + V4L2_CID_PRIVATE_IRIS_GET_SINR, + V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD, + V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, + V4L2_CID_PRIVATE_SINR_THRESHOLD, + V4L2_CID_PRIVATE_SINR_SAMPLES, + + /*using private CIDs under userclass*/ + V4L2_CID_PRIVATE_IRIS_READ_DEFAULT = 0x00980928, + V4L2_CID_PRIVATE_IRIS_WRITE_DEFAULT, + V4L2_CID_PRIVATE_IRIS_SET_CALIBRATION, +}; + + +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, + IRIS_EVT_RADIO_DISABLED +}; +enum emphasis_type { + FM_RX_EMP75 = 0x0, + FM_RX_EMP50 = 0x1 +}; + +enum channel_space_type { + FM_RX_SPACE_200KHZ = 0x0, + FM_RX_SPACE_100KHZ = 0x1, + FM_RX_SPACE_50KHZ = 0x2 +}; + +enum high_low_injection { + AUTO_HI_LO_INJECTION = 0x0, + LOW_SIDE_INJECTION = 0x1, + HIGH_SIDE_INJECTION = 0x2 +}; + +enum fm_rds_type { + FM_RX_RDBS_SYSTEM = 0x0, + FM_RX_RDS_SYSTEM = 0x1 +}; + +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_PEEK, + IRIS_BUF_SSBI_PEEK, + IRIS_BUF_RDS_CNTRS, + IRIS_BUF_RD_DEFAULT, + IRIS_BUF_CAL_DATA, + 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, +}; + + +/* Band limits */ +#define REGION_US_EU_BAND_LOW 87500 +#define REGION_US_EU_BAND_HIGH 108000 +#define REGION_JAPAN_STANDARD_BAND_LOW 76000 +#define REGION_JAPAN_STANDARD_BAND_HIGH 90000 +#define REGION_JAPAN_WIDE_BAND_LOW 90000 +#define REGION_JAPAN_WIDE_BAND_HIGH 108000 + +#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 + +/* constants */ +#define RDS_BLOCKS_NUM (4) +#define BYTES_PER_BLOCK (3) +#define MAX_PS_LENGTH (96) +#define MAX_RT_LENGTH (64) +#define RDS_GRP_CNTR_LEN (36) +#define RX_RT_DATA_LENGTH (63) +/* Search direction */ +#define SRCH_DIR_UP (0) +#define SRCH_DIR_DOWN (1) + +/*Search RDS stations*/ +#define SEARCH_RDS_STNS_MODE_OFFSET 4 + +/*Search Station list */ +#define PARAMS_PER_STATION 0x08 +#define STN_NUM_OFFSET 0x01 +#define STN_FREQ_OFFSET 0x02 +#define KHZ_TO_MHZ 1000 +#define GET_MSB(x)((x >> 8) & 0xFF) +#define GET_LSB(x)((x) & 0xFF) + +/* control options */ +#define CTRL_ON (1) +#define CTRL_OFF (0) + +/*Diagnostic commands*/ + +#define RIVA_PEEK_OPCODE 0x0D +#define RIVA_POKE_OPCODE 0x0C + +#define PEEK_DATA_OFSET 0x1 +#define RIVA_PEEK_PARAM 0x6 +#define RIVA_PEEK_LEN_OFSET 0x6 +#define SSBI_PEEK_LEN 0x01 +/*Calibration data*/ +#define PROCS_CALIB_MODE 1 +#define PROCS_CALIB_SIZE 23 +#define DC_CALIB_MODE 2 +#define DC_CALIB_SIZE 48 +#define RSB_CALIB_MODE 3 +#define RSB_CALIB_SIZE 4 +#define CALIB_DATA_OFSET 2 +#define CALIB_MODE_OFSET 1 +#define MAX_CALIB_SIZE 75 +struct hci_fm_set_cal_req_proc { + __u8 mode; + /*Max process calibration data size*/ + __u8 data[PROCS_CALIB_SIZE]; +} __packed; + +struct hci_fm_set_cal_req_dc { + __u8 mode; + /*Max DC calibration data size*/ + __u8 data[DC_CALIB_SIZE]; +} __packed; + +struct hci_cc_do_calibration_rsp { + __u8 status; + __u8 mode; + __u8 data[MAX_CALIB_SIZE]; +} __packed; + +/* Low Power mode*/ +#define SIG_LEVEL_INTR (1 << 0) +#define RDS_SYNC_INTR (1 << 1) +#define AUDIO_CTRL_INTR (1 << 2) +#define AF_JUMP_ENABLE (1 << 4) +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); + +#endif /* __RADIO_HCI_CORE_H */ diff --git a/include/media/rc-map.h b/include/media/rc-map.h index 8db6741c12563541e99a47d4277306b0073bd27f..a51f84c7b73237171a6169b1738da53318dac690 100644 --- a/include/media/rc-map.h +++ b/include/media/rc-map.h @@ -122,6 +122,7 @@ void rc_map_init(void); #define RC_MAP_NORWOOD "rc-norwood" #define RC_MAP_NPGTECH "rc-npgtech" #define RC_MAP_PCTV_SEDNA "rc-pctv-sedna" +#define RC_MAP_RC6_PHILIPS "rc-philips" #define RC_MAP_PINNACLE_COLOR "rc-pinnacle-color" #define RC_MAP_PINNACLE_GREY "rc-pinnacle-grey" #define RC_MAP_PINNACLE_PCTV_HD "rc-pinnacle-pctv-hd" @@ -138,6 +139,7 @@ void rc_map_init(void); #define RC_MAP_RC6_MCE "rc-rc6-mce" #define RC_MAP_REAL_AUDIO_220_32_KEYS "rc-real-audio-220-32-keys" #define RC_MAP_SNAPSTREAM_FIREFLY "rc-snapstream-firefly" +#define RC_MAP_SAMSUNG_NECX "rc-samsung-necx" #define RC_MAP_STREAMZAP "rc-streamzap" #define RC_MAP_TBS_NEC "rc-tbs-nec" #define RC_MAP_TECHNISAT_USB2 "rc-technisat-usb2" @@ -151,6 +153,7 @@ void rc_map_init(void); #define RC_MAP_TT_1500 "rc-tt-1500" #define RC_MAP_TWINHAN_VP1027_DVBS "rc-twinhan1027" #define RC_MAP_VIDEOMATE_K100 "rc-videomate-k100" +#define RC_MAP_UE_RF4CE "rc-ue-rf4ce" #define RC_MAP_VIDEOMATE_S350 "rc-videomate-s350" #define RC_MAP_VIDEOMATE_TV_PVR "rc-videomate-tv-pvr" #define RC_MAP_WINFAST "rc-winfast" diff --git a/include/media/tavarua.h b/include/media/tavarua.h new file mode 100644 index 0000000000000000000000000000000000000000..52194f94d99f7fb9b29e3b34deb46101293aa1a2 --- /dev/null +++ b/include/media/tavarua.h @@ -0,0 +1,487 @@ +#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) + +#define ON_CHANNEL_TH_MSB (0x0B) +#define ON_CHANNEL_TH_LSB (0xA8) + +#define OFF_CHANNEL_TH_MSB (0x0B) +#define OFF_CHANNEL_TH_LSB (0xAC) + +#define ENF_200Khz (1) +#define SRCH200KHZ_OFFSET (7) +#define SRCH_MASK (1 << SRCH200KHZ_OFFSET) + +/* Standard buffer size */ +#define STD_BUF_SIZE (128) +/* 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, + V4L2_CID_PRIVATE_TAVARUA_AF_JUMP, + V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA, + V4L2_CID_PRIVATE_TAVARUA_HLSI, + + /* + * Here we have IOCTl's that are specific to IRIS + * (V4L2_CID_PRIVATE_BASE + 0x1E to V4L2_CID_PRIVATE_BASE + 0x28) + */ + V4L2_CID_PRIVATE_SOFT_MUTE,/* 0x800001E*/ + V4L2_CID_PRIVATE_RIVA_ACCS_ADDR, + V4L2_CID_PRIVATE_RIVA_ACCS_LEN, + V4L2_CID_PRIVATE_RIVA_PEEK, + V4L2_CID_PRIVATE_RIVA_POKE, + V4L2_CID_PRIVATE_SSBI_ACCS_ADDR, + V4L2_CID_PRIVATE_SSBI_PEEK, + V4L2_CID_PRIVATE_SSBI_POKE, + V4L2_CID_PRIVATE_TX_TONE, + V4L2_CID_PRIVATE_RDS_GRP_COUNTERS, + V4L2_CID_PRIVATE_SET_NOTCH_FILTER,/* 0x8000028 */ + + V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH,/* 0x8000029 */ + V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION,/* 0x800002A : IRIS */ + V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM,/* 0x800002B */ + V4L2_CID_PRIVATE_IRIS_GET_SINR, /* 0x800002C : IRIS */ + V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD, /* 0x800002D */ + V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, /* 0x800002E */ + V4L2_CID_PRIVATE_SINR_THRESHOLD, /* 0x800002F : IRIS */ + V4L2_CID_PRIVATE_SINR_SAMPLES, /* 0x8000030 : IRIS */ + +}; + +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 +}; + +enum channel_spacing { + FM_CH_SPACE_200KHZ, + FM_CH_SPACE_100KHZ, + FM_CH_SPACE_50KHZ +}; + +enum step_size { + NO_SRCH200khz, + ENF_SRCH200khz +}; + +enum emphasis { + EMP_75, + EMP_50 +}; + +enum rds_std { + RBDS_STD, + RDS_STD +}; + +/* 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) +#define RDSAF_OFFSET 6 +#define RDSAF_MASK (1 << RDSAF_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, +}; + +enum audio_path { + FM_DIGITAL_PATH, + FM_ANALOG_PATH +}; +#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) +#define RSH_DATA(val, offset) ((val) >> (offset)) +#define LSH_DATA(val, offset) ((val) << (offset)) +#define GET_ABS_VAL(val) ((val) & (0xFF)) + +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, + TAVARUA_EVT_RADIO_DISABLED +}; + +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/user-rc-input.h b/include/media/user-rc-input.h new file mode 100644 index 0000000000000000000000000000000000000000..e58e40fb5d8aeff59eb27525f59a2b3b55191b64 --- /dev/null +++ b/include/media/user-rc-input.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __USER_RC_INPUT_H__ +#define __USER_RC_INPUT_H__ + +#define USER_CONTROL_PRESSED 0x01 +#define USER_CONTROL_REPEATED 0x02 +#define USER_CONTROL_RELEASED 0x03 + +#endif /* __USER_RC_INPUT_H__ */ + diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h index 11e67562b3acb39a574ae895ae5e667a7aa9c24a..3a44b62ef3b4862ebe8093b232356709ee133193 100644 --- a/include/media/v4l2-ctrls.h +++ b/include/media/v4l2-ctrls.h @@ -33,6 +33,7 @@ struct v4l2_subdev; struct v4l2_subscribed_event; struct v4l2_fh; struct poll_table_struct; +struct file; /** struct v4l2_ctrl_ops - The control operations that the driver has to provide. * @g_volatile_ctrl: Get a new value for this control. Generally only relevant diff --git a/include/media/vcap_fmt.h b/include/media/vcap_fmt.h new file mode 100644 index 0000000000000000000000000000000000000000..51b45acbddd490d1208c968a5a0ee79859ddd90a --- /dev/null +++ b/include/media/vcap_fmt.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VCAP_FMT_H +#define VCAP_FMT_H + +#define V4L2_BUF_TYPE_INTERLACED_IN_DECODER (V4L2_BUF_TYPE_PRIVATE) +#define V4L2_BUF_TYPE_VP_OUT (V4L2_BUF_TYPE_PRIVATE + 1) + +enum hal_vcap_mode { + HAL_VCAP_MODE_PRO = 0, + HAL_VCAP_MODE_INT, +}; + +enum hal_vcap_polar { + HAL_VCAP_POLAR_NEG = 0, + HAL_VCAP_POLAR_POS, +}; + +enum hal_vcap_color { + HAL_VCAP_YUV = 0, + HAL_VCAP_RGB, +}; + +struct v4l2_format_vc_ext { + enum hal_vcap_mode mode; + enum hal_vcap_polar h_polar; + enum hal_vcap_polar v_polar; + enum hal_vcap_polar d_polar; + enum hal_vcap_color color_space; + + float clk_freq; + uint32_t vtotal; + uint32_t htotal; + uint32_t hactive_start; + uint32_t hactive_end; + uint32_t vactive_start; + uint32_t vactive_end; + uint32_t vsync_start; + uint32_t vsync_end; + uint32_t hsync_start; + uint32_t hsync_end; + uint32_t f2_vactive_start; + uint32_t f2_vactive_end; + uint32_t f2_vsync_h_start; + uint32_t f2_vsync_h_end; + uint32_t f2_vsync_v_start; + uint32_t f2_vsync_v_end; + uint32_t sizeimage; + uint32_t bytesperline; +}; + +enum vcap_type { + VC_TYPE, + VP_IN_TYPE, + VP_OUT_TYPE, +}; + +struct vcap_priv_fmt { + enum vcap_type type; + union { + struct v4l2_format_vc_ext timing; + struct v4l2_pix_format pix; + /* Once VP is created there will be another type in here */ + } u; +}; +#endif diff --git a/include/media/vcap_v4l2.h b/include/media/vcap_v4l2.h new file mode 100644 index 0000000000000000000000000000000000000000..374e681318fa6af8cd7f0119577cbc7a49eb4181 --- /dev/null +++ b/include/media/vcap_v4l2.h @@ -0,0 +1,222 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VCAP_V4L2_H +#define VCAP_V4L2_H + +#define TOP_FIELD_FIX +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define writel_iowmb(val, addr) \ + do { \ + __iowmb(); \ + writel_relaxed(val, addr); \ + } while (0) + +struct vcap_client_data; + +enum rdy_buf { + VC_NO_BUF = 0, + VC_BUF1 = 1 << 1, + VC_BUF2 = 1 << 2, + VC_BUF1N2 = 0x11 << 1, +}; + +enum vp_state { + VP_UNKNOWN = 0, + VP_FRAME1, + VP_FRAME2, + VP_FRAME3, + VP_NORMAL, +}; + +enum nr_buf_pos { + BUF_NOT_IN_USE = 0, + NRT2_BUF, + T1_BUF, + T0_BUF, + TM1_BUF, +}; + +struct vcap_buf_info { + unsigned long vaddr; + unsigned long size; +}; + +enum vcap_op_mode { + UNKNOWN_VCAP_OP = 0, + VC_VCAP_OP, + VP_VCAP_OP, + VC_AND_VP_VCAP_OP, +}; + +struct vcap_action { + struct list_head active; + + /* thread for generating video stream*/ + struct task_struct *kthread; + wait_queue_head_t wq; + + /* Buffer index */ + enum rdy_buf buf_ind; + + /* Buffers inside vc */ + struct vcap_buffer *buf1; + struct vcap_buffer *buf2; + + /* Counters to control fps rate */ + int frame; + int ini_jiffies; +}; + +struct nr_buffer { + void *vaddr; + unsigned long paddr; + enum nr_buf_pos nr_pos; +}; + +struct vp_action { + struct list_head in_active; + struct list_head out_active; + + /* Buffer index */ + enum vp_state vp_state; +#ifdef TOP_FIELD_FIX + bool top_field; +#endif + + /* Buffers inside vc */ + struct vcap_buffer *bufTm1; + struct vcap_buffer *bufT0; + struct vcap_buffer *bufT1; + struct vcap_buffer *bufT2; + struct vcap_buffer *bufNRT2; + + struct vcap_buffer *bufOut; + + void *bufMotion; + struct nr_buffer bufNR; + bool nr_enabled; +}; + +struct vp_work_t { + struct work_struct work; + struct vcap_client_data *cd; + uint32_t irq; +}; + +struct vcap_dev { + struct v4l2_device v4l2_dev; + + struct video_device *vfd; + struct ion_client *ion_client; + + struct resource *vcirq; + struct resource *vpirq; + + struct resource *vcapmem; + struct resource *vcapio; + void __iomem *vcapbase; + + struct vcap_platform_data *vcap_pdata; + + struct regulator *fs_vcap; + struct clk *vcap_clk; + struct clk *vcap_p_clk; + struct clk *vcap_npl_clk; + /*struct platform_device *pdev;*/ + + uint32_t bus_client_handle; + + struct vcap_client_data *vc_client; + struct vcap_client_data *vp_client; + + atomic_t vc_enabled; + atomic_t vp_enabled; + + atomic_t vc_resource; + atomic_t vp_resource; + + struct workqueue_struct *vcap_wq; + struct vp_work_t vp_work; + struct vp_work_t vc_to_vp_work; + struct vp_work_t vp_to_vc_work; +}; + +struct vp_format_data { + unsigned int width, height; + unsigned int pixfmt; +}; + +struct vcap_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_buffer vb; + struct list_head list; + unsigned long paddr; + struct ion_handle *ion_handle; +}; + +struct vcap_client_data { + bool set_cap, set_decode, set_vp_o; + struct vcap_dev *dev; + + struct vb2_queue vc_vidq; + struct vb2_queue vp_in_vidq; + struct vb2_queue vp_out_vidq; + + enum vcap_op_mode op_mode; + + struct v4l2_format_vc_ext vc_format; + + enum v4l2_buf_type vp_buf_type_field; + struct vp_format_data vp_in_fmt; + struct vp_format_data vp_out_fmt; + + struct vcap_action vid_vc_action; + struct vp_action vid_vp_action; + struct workqueue_struct *vcap_work_q; + struct ion_handle *vc_ion_handle; + + uint32_t hold_vc; + uint32_t hold_vp; + + spinlock_t cap_slock; + bool streaming; +}; + +struct vcap_hacked_vals { + uint32_t value; + uint32_t offset; +}; + +extern struct vcap_hacked_vals hacked_buf[]; + +#endif +int free_ion_handle(struct vcap_dev *dev, struct vb2_queue *q, + struct v4l2_buffer *b); + +int get_phys_addr(struct vcap_dev *dev, struct vb2_queue *q, + struct v4l2_buffer *b); +#endif diff --git a/include/media/videobuf-msm-mem.h b/include/media/videobuf-msm-mem.h new file mode 100644 index 0000000000000000000000000000000000000000..19dd93e4b7f816c12834666aa77e98d2bf6744f1 --- /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/media/videobuf2-msm-mem.h b/include/media/videobuf2-msm-mem.h new file mode 100644 index 0000000000000000000000000000000000000000..84e2bea093b16355c63a9c7fb68b88d4bc0deff2 --- /dev/null +++ b/include/media/videobuf2-msm-mem.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 _VIDEOBUF2_PMEM_CONTIG_H +#define _VIDEOBUF2_PMEM_CONTIG_H + +#include +#include +#include + +struct videobuf2_mapping { + unsigned int count; +}; + +enum videobuf2_buffer_type { + VIDEOBUF2_SINGLE_PLANE, + VIDEOBUF2_MULTIPLE_PLANES +}; + +struct videobuf2_sp_offset { + uint32_t y_off; + uint32_t cbcr_off; +}; + +struct videobuf2_msm_offset { + union { + struct videobuf2_sp_offset sp_off; + uint32_t data_offset; + }; +}; + +struct videobuf2_contig_pmem { + u32 magic; + void *vaddr; + int phyaddr; + unsigned long size; + int is_userptr; + /* Offset of the plane inside the buffer */ + struct videobuf2_msm_offset offset; + enum videobuf2_buffer_type buffer_type; + int path; + struct file *file; + /* Offset of the buffer */ + uint32_t addr_offset; + int dirty; + unsigned int count; + void *alloc_ctx; + unsigned long mapped_phyaddr; + struct ion_handle *ion_handle; + struct ion_client *client; +}; +void videobuf2_queue_pmem_contig_init(struct vb2_queue *q, + enum v4l2_buf_type type, + const struct vb2_ops *ops, + unsigned int size, + void *priv); +int videobuf2_pmem_contig_mmap_get(struct videobuf2_contig_pmem *mem, + struct videobuf2_msm_offset *offset, + enum videobuf2_buffer_type, int path); +int videobuf2_pmem_contig_user_get(struct videobuf2_contig_pmem *mem, + struct videobuf2_msm_offset *offset, + enum videobuf2_buffer_type, + uint32_t addr_offset, int path, + struct ion_client *client); +void videobuf2_pmem_contig_user_put(struct videobuf2_contig_pmem *mem, + struct ion_client *client); +unsigned long videobuf2_to_pmem_contig(struct vb2_buffer *buf, + unsigned int plane_no); + +#endif /* _VIDEOBUF2_PMEM_CONTIG_H */ diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h index 36eace03b2ac79229984b09254db9aed3e3b8d5f..a33bf3161725b8b54f8f311f67b539cbc688ea37 100644 --- a/include/mtd/mtd-abi.h +++ b/include/mtd/mtd-abi.h @@ -231,7 +231,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 0000000000000000000000000000000000000000..ec517b051a93c0a51c2bfb99f9d992574a492463 --- /dev/null +++ b/include/net/bluetooth/amp.h @@ -0,0 +1,293 @@ +/* + Copyright (c) 2010-2012 Code Aurora Forum. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY 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 (8000) /* 8 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; + +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 hci_conn *hcon, 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 hci_conn *hcon; + 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 a65910bda3811551ba0ae089abb47f428dee3e94..660a4a93d5c98c0418eaebbe628b77a393e78b63 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-2012 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 @@ -36,13 +36,9 @@ #define PF_BLUETOOTH AF_BLUETOOTH #endif -/* Bluetooth versions */ -#define BLUETOOTH_VER_1_1 1 -#define BLUETOOTH_VER_1_2 2 -#define BLUETOOTH_VER_2_0 3 - /* 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 @@ -69,54 +65,65 @@ struct bt_security { #define BT_SECURITY_HIGH 3 #define BT_DEFER_SETUP 7 - #define BT_FLUSHABLE 8 -#define BT_FLUSHABLE_OFF 0 -#define BT_FLUSHABLE_ON 1 - #define BT_POWER 9 struct bt_power { __u8 force_active; }; -#define BT_POWER_FORCE_ACTIVE_OFF 0 -#define BT_POWER_FORCE_ACTIVE_ON 1 -#define BT_CHANNEL_POLICY 10 +#define BT_AMP_POLICY 10 -/* BR/EDR only (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. +/* 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_CHANNEL_POLICY_BREDR_ONLY 0 +#define BT_AMP_POLICY_REQUIRE_BR_EDR 0 -/* BR/EDR Preferred - * 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. +/* 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_CHANNEL_POLICY_BREDR_PREFERRED 1 +#define BT_AMP_POLICY_PREFER_BR_EDR 1 -/* AMP Preferred +/* 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. + * 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. + * than BR/EDR */ -#define BT_CHANNEL_POLICY_AMP_PREFERRED 2 +#define BT_AMP_POLICY_PREFER_AMP 2 + +#define BT_LE_PARAMS 100 -__printf(1, 2) -int bt_info(const char *fmt, ...); -__printf(1, 2) -int bt_err(const char *fmt, ...); +#define BT_LE_SCAN_WINDOW_MIN 0x0004 +#define BT_LE_SCAN_WINDOW_MAX 0x4000 +#define BT_LE_SCAN_WINDOW_DEF 0x0004 -#define BT_INFO(fmt, ...) bt_info(fmt "\n", ##__VA_ARGS__) -#define BT_ERR(fmt, ...) bt_err(fmt "\n", ##__VA_ARGS__) -#define BT_DBG(fmt, ...) pr_debug(fmt "\n", ##__VA_ARGS__) +#define BT_LE_SCAN_INTERVAL_MIN 0x0004 +#define BT_LE_SCAN_INTERVAL_MAX 0x4000 +#define BT_LE_SCAN_INTERVAL_DEF 0x0008 + +#define BT_LE_CONN_INTERVAL_MIN 0x0006 +#define BT_LE_CONN_INTERVAL_MAX 0x0C80 +#define BT_LE_CONN_INTERVAL_MIN_DEF 0x0008 +#define BT_LE_CONN_INTERVAL_MAX_DEF 0x0100 + +#define BT_LE_LATENCY_MAX 0x01F4 +#define BT_LE_LATENCY_DEF 0x0000 + +#define BT_LE_SUP_TO_MIN 0x000A +#define BT_LE_SUP_TO_MAX 0x0C80 +#define BT_LE_SUP_TO_DEFAULT 0X03E8 + +#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 { @@ -131,40 +138,13 @@ enum { BT_CLOSED }; -/* If unused will be removed by compiler */ -static inline const char *state_to_string(int state) -{ - switch (state) { - case BT_CONNECTED: - return "BT_CONNECTED"; - case BT_OPEN: - return "BT_OPEN"; - case BT_BOUND: - return "BT_BOUND"; - case BT_LISTEN: - return "BT_LISTEN"; - case BT_CONNECT: - return "BT_CONNECT"; - case BT_CONNECT2: - return "BT_CONNECT2"; - case BT_CONFIG: - return "BT_CONFIG"; - case BT_DISCONN: - return "BT_DISCONN"; - case BT_CLOSED: - return "BT_CLOSED"; - } - - return "invalid state"; -} - /* BD Address */ 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) @@ -184,6 +164,20 @@ bdaddr_t *strtoba(char *str); #define bt_sk(__sk) ((struct bt_sock *) __sk) +struct bt_le_params { + __u8 prohibit_remote_chg; + __u8 filter_policy; + __u16 scan_interval; + __u16 scan_window; + __u16 interval_min; + __u16 interval_max; + __u16 latency; + __u16 supervision_timeout; + __u16 min_ce_len; + __u16 max_ce_len; + __u16 conn_timeout; +}; + struct bt_sock { struct sock sk; bdaddr_t src; @@ -191,7 +185,7 @@ struct bt_sock { struct list_head accept_q; struct sock *parent; u32 defer_setup; - bool suspended; + struct bt_le_params le_params; }; struct bt_sock_list { @@ -207,7 +201,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); @@ -216,14 +210,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; - __u16 tx_seq; __u8 retries; - __u8 sar; __u8 force_active; + unsigned short channel; + struct bt_l2cap_control control; }; #define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb)) @@ -231,7 +236,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; } @@ -244,7 +250,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; } @@ -269,7 +276,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); @@ -285,6 +292,4 @@ void l2cap_exit(void); int sco_init(void); void sco_exit(void); -void bt_sock_reclassify_lock(struct sock *sk, int proto); - #endif /* __BLUETOOTH_H */ diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 78132a8af027ab81ddf6b9d5ec150f5799a44950..eb89f4b1ea13a0cc3c70e94650833a350d4f95b3 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-2012 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 @@ -77,14 +79,6 @@ enum { HCI_RAW, - HCI_RESET, -}; - -/* - * BR/EDR and/or LE controller flags: the flags defined here should represent - * states from the controller. - */ -enum { HCI_SETUP, HCI_AUTO_OFF, HCI_MGMT, @@ -92,16 +86,8 @@ enum { HCI_SERVICE_CACHE, HCI_LINK_KEYS, HCI_DEBUG_KEYS, - HCI_UNREGISTER, - - HCI_LE_SCAN, - HCI_SSP_ENABLED, - HCI_HS_ENABLED, - HCI_LE_ENABLED, - HCI_CONNECTABLE, - HCI_DISCOVERABLE, - HCI_LINK_SECURITY, - HCI_PENDING_CLASS, + + HCI_RESET, }; /* HCI ioctl defines */ @@ -115,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) @@ -137,8 +124,7 @@ enum { #define HCI_PAIRING_TIMEOUT (60000) /* 60 seconds */ #define HCI_IDLE_TIMEOUT (6000) /* 6 seconds */ #define HCI_INIT_TIMEOUT (10000) /* 10 seconds */ -#define HCI_CMD_TIMEOUT (1000) /* 1 seconds */ -#define HCI_ACL_TX_TIMEOUT (45000) /* 45 seconds */ +#define HCI_CMD_TIMEOUT (5000) /* 5 seconds */ /* HCI data types */ #define HCI_COMMAND_PKT 0x01 @@ -174,18 +160,35 @@ enum { #define ESCO_2EV5 0x0100 #define ESCO_3EV5 0x0200 +#define ESCO_WBS (ESCO_EV3 | (EDR_ESCO_MASK ^ ESCO_2EV3)) + #define SCO_ESCO_MASK (ESCO_HV1 | ESCO_HV2 | ESCO_HV3) #define EDR_ESCO_MASK (ESCO_2EV3 | ESCO_3EV3 | ESCO_2EV5 | ESCO_3EV5) #define ALL_ESCO_MASK (SCO_ESCO_MASK | ESCO_EV3 | ESCO_EV4 | ESCO_EV5 | \ EDR_ESCO_MASK) +/* Air Coding Format */ +#define ACF_CVSD 0x0000; +#define ACF_ULAW 0x0001; +#define ACF_ALAW 0x0002; +#define ACF_TRANS 0x0003; + +/* Retransmission Effort */ +#define RE_NO_RETRANS 0x00; +#define RE_POWER_CONSUMP 0x01; +#define RE_LINK_QUALITY 0x02; +#define RE_DONT_CARE 0xFF; + /* ACL flags */ #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 @@ -221,7 +224,6 @@ enum { #define LMP_EV4 0x01 #define LMP_EV5 0x02 -#define LMP_NO_BREDR 0x20 #define LMP_LE 0x40 #define LMP_SNIFF_SUBR 0x02 @@ -231,18 +233,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_SSP 0x01 -#define LMP_HOST_LE 0x02 -#define LMP_HOST_LE_BREDR 0x04 /* Connection modes */ #define HCI_CM_ACTIVE 0x0000 @@ -273,46 +268,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, the _MASTER suffix is implied */ -#define HCI_SMP_STK 0x80 -#define HCI_SMP_STK_SLAVE 0x81 -#define HCI_SMP_LTK 0x82 -#define HCI_SMP_LTK_SLAVE 0x83 - -/* ---- HCI Error Codes ---- */ -#define HCI_ERROR_AUTH_FAILURE 0x05 -#define HCI_ERROR_REJ_BAD_ADDR 0x0f -#define HCI_ERROR_REMOTE_USER_TERM 0x13 -#define HCI_ERROR_LOCAL_HOST_TERM 0x16 -#define HCI_ERROR_PAIRING_NOT_ALLOWED 0x18 - /* Flow control modes */ -#define HCI_FLOW_CTL_MODE_PACKET_BASED 0x00 -#define HCI_FLOW_CTL_MODE_BLOCK_BASED 0x01 - -/* Extended Inquiry Response field types */ -#define EIR_FLAGS 0x01 /* flags */ -#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ -#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ -#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ -#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ -#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ -#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ -#define EIR_NAME_SHORT 0x08 /* shortened local name */ -#define EIR_NAME_COMPLETE 0x09 /* complete local name */ -#define EIR_TX_POWER 0x0A /* transmit power level */ -#define EIR_CLASS_OF_DEV 0x0D /* Class of Device */ -#define EIR_SSP_HASH_C 0x0E /* Simple Pairing Hash C */ -#define EIR_SSP_RAND_R 0x0F /* Simple Pairing Randomizer R */ -#define EIR_DEVICE_ID 0x10 /* device ID */ +#define HCI_PACKET_BASED_FLOW_CTL_MODE 0x00 +#define HCI_BLOCK_BASED_FLOW_CTL_MODE 0x01 /* ----- HCI Commands ---- */ #define HCI_OP_NOP 0x0000 @@ -373,6 +331,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; @@ -496,14 +459,6 @@ struct hci_rp_user_confirm_reply { #define HCI_OP_USER_CONFIRM_NEG_REPLY 0x042d -#define HCI_OP_USER_PASSKEY_REPLY 0x042e -struct hci_cp_user_passkey_reply { - bdaddr_t bdaddr; - __le32 passkey; -} __packed; - -#define HCI_OP_USER_PASSKEY_NEG_REPLY 0x042f - #define HCI_OP_REMOTE_OOB_DATA_REPLY 0x0430 struct hci_cp_remote_oob_data_reply { bdaddr_t bdaddr; @@ -522,6 +477,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; @@ -682,6 +700,12 @@ struct hci_cp_write_voice_setting { __le16 voice_setting; } __packed; +#define HCI_OP_WRITE_AUTOMATIC_FLUSH_TIMEOUT 0x0c28 +struct hci_cp_write_automatic_flush_timeout { + __le16 handle; + __le16 timeout; +} __packed; + #define HCI_OP_HOST_BUFFER_SIZE 0x0c33 struct hci_cp_host_buffer_size { __le16 acl_mtu; @@ -690,14 +714,20 @@ struct hci_cp_host_buffer_size { __le16 sco_max_pkt; } __packed; +#define HCI_OP_WRITE_CURRENT_IAC_LAP 0x0c3a +struct hci_cp_write_current_iac_lap { + __u8 num_current_iac; + __u8 lap[6]; +} __packed; + #define HCI_OP_WRITE_INQUIRY_MODE 0x0c45 #define HCI_MAX_EIR_LENGTH 240 #define HCI_OP_WRITE_EIR 0x0c52 struct hci_cp_write_eir { - __u8 fec; - __u8 data[HCI_MAX_EIR_LENGTH]; + uint8_t fec; + uint8_t data[HCI_MAX_EIR_LENGTH]; } __packed; #define HCI_OP_READ_SSP_MODE 0x0c55 @@ -720,16 +750,70 @@ struct hci_rp_read_local_oob_data { #define HCI_OP_READ_INQ_RSP_TX_POWER 0x0c58 +#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_LE_HOST_SUPPORTED 0x0c6d -struct hci_cp_write_le_host_supported { - __u8 le; - __u8 simul; +#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 @@ -755,9 +839,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; @@ -784,19 +865,20 @@ struct hci_rp_read_bd_addr { struct hci_rp_read_data_block_size { __u8 status; __le16 max_acl_len; - __le16 block_len; + __le16 data_block_len; __le16 num_blocks; } __packed; -#define HCI_OP_WRITE_PAGE_SCAN_ACTIVITY 0x0c1c -struct hci_cp_write_page_scan_activity { - __le16 interval; - __le16 window; +#define HCI_OP_READ_RSSI 0x1405 +struct hci_cp_read_rssi { + __le16 handle; } __packed; -#define HCI_OP_WRITE_PAGE_SCAN_TYPE 0x0c47 - #define PAGE_SCAN_TYPE_STANDARD 0x00 - #define PAGE_SCAN_TYPE_INTERLACED 0x01 +struct hci_rp_read_rssi { + __u8 status; + __le16 handle; + __s8 rssi; +} __packed; #define HCI_OP_READ_LOCAL_AMP_INFO 0x1409 struct hci_rp_read_local_amp_info { @@ -813,6 +895,33 @@ struct hci_rp_read_local_amp_info { __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]; @@ -825,22 +934,19 @@ struct hci_rp_le_read_buffer_size { __u8 le_max_pkt; } __packed; -#define HCI_OP_LE_SET_SCAN_PARAM 0x200b -struct hci_cp_le_set_scan_param { - __u8 type; - __le16 interval; - __le16 window; - __u8 own_address_type; - __u8 filter_policy; +#define HCI_OP_LE_SET_SCAN_PARAMETERS 0x200b +struct hci_cp_le_set_scan_parameters { + __u8 type; + __le16 interval; + __le16 window; + __u8 own_bdaddr_type; + __u8 filter; } __packed; -#define LE_SCANNING_DISABLED 0x00 -#define LE_SCANNING_ENABLED 0x01 - #define HCI_OP_LE_SET_SCAN_ENABLE 0x200c struct hci_cp_le_set_scan_enable { - __u8 enable; - __u8 filter_dup; + __u8 enable; + __u8 filter_dup; } __packed; #define HCI_OP_LE_CREATE_CONN 0x200d @@ -872,6 +978,16 @@ struct hci_cp_le_conn_update { __le16 max_ce_len; } __packed; +#define HCI_OP_LE_ENCRYPT 0x2017 +struct hci_cp_le_encrypt { + __u8 key[16]; + __u8 data[16]; +} __packed; +struct hci_cp_le_encrypt_reply { + __u8 status; + __u8 encrypted[16]; +} __packed; + #define HCI_OP_LE_START_ENC 0x2019 struct hci_cp_le_start_enc { __le16 handle; @@ -1012,14 +1128,9 @@ struct hci_ev_role_change { } __packed; #define HCI_EV_NUM_COMP_PKTS 0x13 -struct hci_comp_pkts_info { - __le16 handle; - __le16 count; -} __packed; - struct hci_ev_num_comp_pkts { __u8 num_hndl; - struct hci_comp_pkts_info handles[0]; + /* variable length part */ } __packed; #define HCI_EV_MODE_CHANGE 0x14 @@ -1159,8 +1270,8 @@ struct hci_ev_user_confirm_req { } __packed; #define HCI_EV_USER_PASSKEY_REQUEST 0x34 -struct hci_ev_user_passkey_req { - bdaddr_t bdaddr; +struct hci_ev_user_passkey_request { + bdaddr_t bdaddr; } __packed; #define HCI_EV_REMOTE_OOB_DATA_REQUEST 0x35 @@ -1174,6 +1285,12 @@ struct hci_ev_simple_pair_complete { bdaddr_t bdaddr; } __packed; +#define HCI_EV_USER_PASSKEY_NOTIFICATION 0x3b +struct hci_ev_user_passkey_notification { + bdaddr_t bdaddr; + __le32 passkey; +} __packed; + #define HCI_EV_REMOTE_HOST_FEATURES 0x3d struct hci_ev_remote_host_features { bdaddr_t bdaddr; @@ -1185,19 +1302,6 @@ struct hci_ev_le_meta { __u8 subevent; } __packed; -#define HCI_EV_NUM_COMP_BLOCKS 0x48 -struct hci_comp_blocks_info { - __le16 handle; - __le16 pkts; - __le16 blocks; -} __packed; - -struct hci_ev_num_comp_blocks { - __le16 num_blocks; - __u8 num_hndl; - struct hci_comp_blocks_info handles[0]; -} __packed; - /* Low energy meta events */ #define HCI_EV_LE_CONN_COMPLETE 0x01 struct hci_ev_le_conn_complete { @@ -1212,14 +1316,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 @@ -1238,6 +1334,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 { @@ -1285,6 +1447,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) { @@ -1300,6 +1463,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)) @@ -1330,8 +1494,7 @@ struct sockaddr_hci { #define HCI_DEV_NONE 0xffff #define HCI_CHANNEL_RAW 0 -#define HCI_CHANNEL_MONITOR 2 -#define HCI_CHANNEL_CONTROL 3 +#define HCI_CHANNEL_CONTROL 1 struct hci_filter { unsigned long type_mask; @@ -1397,6 +1560,8 @@ struct hci_conn_info { __u32 mtu; __u32 cnt; __u32 pkts; + __u8 pending_sec_level; + __u8 ssp_mode; }; struct hci_dev_req { @@ -1435,7 +1600,4 @@ struct hci_inquiry_req { }; #define IREQ_CACHE_FLUSH 0x0001 -extern bool enable_hs; -extern bool enable_le; - #endif /* __HCI_H */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 392b2cab0d0285ccdb348ccf5021391aededaa4f..47b856cf37e27a156355523e6d9761ae896078c0 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-2012, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -25,11 +25,11 @@ #ifndef __HCI_CORE_H #define __HCI_CORE_H -#include #include - -/* HCI priority */ -#define HCI_PRIO_MAX 7 +#include +/* HCI upper protocols */ +#define HCI_PROTO_L2CAP 0 +#define HCI_PROTO_SCO 1 /* HCI Core structures */ struct inquiry_data { @@ -44,40 +44,30 @@ struct inquiry_data { }; struct inquiry_entry { - struct list_head all; /* inq_cache.all */ - struct list_head list; /* unknown or resolve */ - enum { - NAME_NOT_KNOWN, - NAME_NEEDED, - NAME_PENDING, - NAME_KNOWN, - } name_state; + struct inquiry_entry *next; __u32 timestamp; struct inquiry_data data; }; -struct discovery_state { - int type; - enum { - DISCOVERY_STOPPED, - DISCOVERY_STARTING, - DISCOVERY_FINDING, - DISCOVERY_RESOLVING, - DISCOVERY_STOPPING, - } state; - struct list_head all; /* All devices found during inquiry */ - struct list_head unknown; /* Name state not known */ - struct list_head resolve; /* Name needs to be resolved */ +struct inquiry_cache { + spinlock_t lock; __u32 timestamp; + struct inquiry_entry *list; }; struct hci_conn_hash { struct list_head list; + spinlock_t lock; unsigned int acl_num; unsigned int sco_num; 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; @@ -89,24 +79,37 @@ struct bt_uuid { u8 svc_hint; }; -struct smp_ltk { - struct list_head list; - bdaddr_t bdaddr; - u8 bdaddr_type; - u8 authenticated; - u8 type; - u8 enc_size; +struct key_master_id { __le16 ediv; u8 rand[8]; +} __packed; + +#define KEY_TYPE_LE_BASE 0x11 +#define KEY_TYPE_LTK 0x11 +#define KEY_TYPE_IRK 0x12 +#define KEY_TYPE_CSRK 0x13 + +struct link_key_data { + bdaddr_t bdaddr; + u8 addr_type; + u8 key_type; u8 val[16]; + u8 pin_len; + u8 auth; + u8 dlen; + u8 data[0]; } __packed; struct link_key { struct list_head list; bdaddr_t bdaddr; - u8 type; + u8 addr_type; + u8 key_type; u8 val[16]; u8 pin_len; + u8 auth; + u8 dlen; + u8 data[0]; }; struct oob_data { @@ -120,21 +123,14 @@ struct adv_entry { struct list_head list; bdaddr_t bdaddr; u8 bdaddr_type; + u8 flags; }; -struct le_scan_params { - u8 type; - u16 interval; - u16 window; - int timeout; -}; - -#define HCI_MAX_SHORT_NAME_LENGTH 10 - #define NUM_REASSEMBLY 4 struct hci_dev { struct list_head list; - struct mutex lock; + spinlock_t lock; + atomic_t refcnt; char name[8]; unsigned long flags; @@ -143,14 +139,13 @@ struct hci_dev { __u8 dev_type; bdaddr_t bdaddr; __u8 dev_name[HCI_MAX_NAME_LENGTH]; - __u8 short_name[HCI_MAX_SHORT_NAME_LENGTH]; __u8 eir[HCI_MAX_EIR_LENGTH]; __u8 dev_class[3]; __u8 major_class; __u8 minor_class; __u8 features[8]; - __u8 host_features[8]; __u8 commands[64]; + __u8 ssp_mode; __u8 hci_ver; __u16 hci_rev; __u8 lmp_ver; @@ -179,9 +174,7 @@ struct hci_dev { __u32 amp_max_flush_to; __u32 amp_be_flush_to; - __u8 flow_ctl_mode; - - unsigned int auto_accept_delay; + __s8 is_wbs; unsigned long quirks; @@ -190,6 +183,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; @@ -197,10 +192,7 @@ struct hci_dev { unsigned int sco_pkts; unsigned int le_pkts; - __u16 block_len; - __u16 block_mtu; - __u16 num_blocks; - __u16 block_cnt; + unsigned int data_block_len; unsigned long acl_last_tx; unsigned long sco_last_tx; @@ -209,18 +201,13 @@ struct hci_dev { struct workqueue_struct *workqueue; struct work_struct power_on; - struct delayed_work power_off; - - __u16 discov_timeout; - struct delayed_work discov_off; - - struct delayed_work service_cache; + struct work_struct power_off; + struct timer_list off_timer; struct timer_list cmd_timer; - - struct work_struct rx_work; - struct work_struct cmd_work; - struct work_struct tx_work; + 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; @@ -236,27 +223,34 @@ struct hci_dev { __u16 init_last_cmd; - struct list_head mgmt_pending; + struct crypto_blkcipher *tfm; - struct discovery_state discovery; + 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; struct list_head link_keys; - struct list_head long_term_keys; - struct list_head remote_oob_data; struct list_head adv_entries; - struct delayed_work adv_work; + rwlock_t adv_entries_lock; + struct timer_list adv_timer; + + struct timer_list disco_timer; + struct timer_list disco_le_timer; + __u8 disco_state; + int disco_int_phase; + int disco_int_count; struct hci_dev_stats stat; struct sk_buff_head driver_init; + void *driver_data; void *core_data; atomic_t promisc; @@ -268,17 +262,13 @@ struct hci_dev { struct rfkill *rfkill; - unsigned long dev_flags; - - struct delayed_work le_scan_disable; - - struct work_struct le_scan; - struct le_scan_params le_scan_params; + struct module *owner; int (*open)(struct hci_dev *hdev); int (*close)(struct hci_dev *hdev); int (*flush)(struct hci_dev *hdev); int (*send)(struct sk_buff *skb); + void (*destruct)(struct hci_dev *hdev); void (*notify)(struct hci_dev *hdev, unsigned int evt); int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg); }; @@ -287,54 +277,85 @@ 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; __u8 type; - bool out; + __u8 out; __u8 attempt; __u8 dev_class[3]; __u8 features[8]; + __u8 ssp_mode; __u16 interval; __u16 pkt_type; __u16 link_policy; __u32 link_mode; - __u8 key_type; __u8 auth_type; __u8 sec_level; __u8 pending_sec_level; __u8 pin_length; __u8 enc_key_size; __u8 io_capability; + __u8 auth_initiator; + __u8 power_save; __u16 disc_timeout; - unsigned long flags; + __u16 conn_timeout; + unsigned long pend; __u8 remote_cap; + __u8 remote_oob; __u8 remote_auth; - bool flush_key; + + __s8 rssi_threshold; + __u16 rssi_update_interval; + __u8 rssi_update_thresh_exceed; unsigned int sent; struct sk_buff_head data_q; - struct list_head chan_list; - struct delayed_work disc_work; + struct timer_list disc_timer; struct timer_list idle_timer; - struct timer_list auto_accept_timer; + struct delayed_work rssi_update_work; + struct timer_list encrypt_pause_timer; + struct work_struct work_add; + struct work_struct work_del; + struct wake_lock idle_lock; struct device dev; atomic_t devref; struct hci_dev *hdev; void *l2cap_data; void *sco_data; - void *smp_conn; + void *priv; + + __u8 link_key[16]; + __u8 key_type; struct hci_conn *link; + /* Low Energy SMP pairing data */ + __u8 oob; /* OOB pairing supported */ + __u8 tk_valid; /* TK value is valid */ + __u8 cfm_pending; /* CONFIRM cmd may be sent */ + __u8 preq[7]; /* Pairing Request */ + __u8 prsp[7]; /* Pairing Response */ + __u8 prnd[16]; /* Pairing Random */ + __u8 pcnf[16]; /* Pairing Confirm */ + __u8 tk[16]; /* Temporary Key */ + __u8 smp_key_size; + __u8 sec_req; + __u8 auth; + void *smp_conn; + struct timer_list smp_timer; + + void (*connect_cfm_cb) (struct hci_conn *conn, u8 status); void (*security_cfm_cb) (struct hci_conn *conn, u8 status); void (*disconn_cfm_cb) (struct hci_conn *conn, u8 reason); @@ -342,54 +363,47 @@ struct hci_conn { struct hci_chan { struct list_head list; - - struct hci_conn *conn; - struct sk_buff_head data_q; - unsigned int sent; + 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; extern rwlock_t hci_dev_list_lock; extern rwlock_t hci_cb_list_lock; -/* ----- HCI interface to upper protocols ----- */ -extern int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr); -extern int l2cap_connect_cfm(struct hci_conn *hcon, u8 status); -extern int l2cap_disconn_ind(struct hci_conn *hcon); -extern int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason); -extern int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt); -extern int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags); - -extern int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr); -extern int sco_connect_cfm(struct hci_conn *hcon, __u8 status); -extern int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason); -extern int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb); - /* ----- Inquiry cache ----- */ #define INQUIRY_CACHE_AGE_MAX (HZ*30) /* 30 seconds */ #define INQUIRY_ENTRY_AGE_MAX (HZ*60) /* 60 seconds */ -static inline void discovery_init(struct hci_dev *hdev) +#define inquiry_cache_lock(c) spin_lock(&c->lock) +#define inquiry_cache_unlock(c) spin_unlock(&c->lock) +#define inquiry_cache_lock_bh(c) spin_lock_bh(&c->lock) +#define inquiry_cache_unlock_bh(c) spin_unlock_bh(&c->lock) + +static inline void inquiry_cache_init(struct hci_dev *hdev) { - hdev->discovery.state = DISCOVERY_STOPPED; - INIT_LIST_HEAD(&hdev->discovery.all); - INIT_LIST_HEAD(&hdev->discovery.unknown); - INIT_LIST_HEAD(&hdev->discovery.resolve); + struct inquiry_cache *c = &hdev->inq_cache; + spin_lock_init(&c->lock); + c->list = NULL; } -bool hci_discovery_active(struct hci_dev *hdev); - -void hci_discovery_set_state(struct hci_dev *hdev, int state); - static inline int inquiry_cache_empty(struct hci_dev *hdev) { - return list_empty(&hdev->discovery.all); + struct inquiry_cache *c = &hdev->inq_cache; + return c->list == NULL; } static inline long inquiry_cache_age(struct hci_dev *hdev) { - struct discovery_state *c = &hdev->discovery; + struct inquiry_cache *c = &hdev->inq_cache; return jiffies - c->timestamp; } @@ -398,53 +412,31 @@ 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_unknown(struct hci_dev *hdev, - bdaddr_t *bdaddr); -struct inquiry_entry *hci_inquiry_cache_lookup_resolve(struct hci_dev *hdev, - bdaddr_t *bdaddr, - int state); -void hci_inquiry_cache_update_resolve(struct hci_dev *hdev, - struct inquiry_entry *ie); -bool hci_inquiry_cache_update(struct hci_dev *hdev, struct inquiry_data *data, - bool name_known, bool *ssp); +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, HCI_CONN_SCO_SETUP_PEND, - HCI_CONN_LE_SMP_PEND, - HCI_CONN_MGMT_CONNECTED, - HCI_CONN_SSP_ENABLED, - HCI_CONN_POWER_SAVE, - HCI_CONN_REMOTE_OOB, }; -static inline bool hci_conn_ssp_enabled(struct hci_conn *conn) -{ - struct hci_dev *hdev = conn->hdev; - return (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags) && - test_bit(HCI_CONN_SSP_ENABLED, &conn->flags)); -} - static inline void hci_conn_hash_init(struct hci_dev *hdev) { struct hci_conn_hash *h = &hdev->conn_hash; INIT_LIST_HEAD(&h->list); + spin_lock_init(&h->lock); h->acl_num = 0; h->sco_num = 0; - h->le_num = 0; } static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c) { struct hci_conn_hash *h = &hdev->conn_hash; - list_add_rcu(&c->list, &h->list); + list_add(&c->list, &h->list); switch (c->type) { case ACL_LINK: h->acl_num++; @@ -462,10 +454,7 @@ static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c) static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c) { struct hci_conn_hash *h = &hdev->conn_hash; - - list_del_rcu(&c->list); - synchronize_rcu(); - + list_del(&c->list); switch (c->type) { case ACL_LINK: h->acl_num--; @@ -480,58 +469,55 @@ static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c) } } -static inline unsigned int hci_conn_num(struct hci_dev *hdev, __u8 type) -{ - struct hci_conn_hash *h = &hdev->conn_hash; - switch (type) { - case ACL_LINK: - return h->acl_num; - case LE_LINK: - return h->le_num; - case SCO_LINK: - case ESCO_LINK: - return h->sco_num; - default: - return 0; - } -} - static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev, __u16 handle) { struct hci_conn_hash *h = &hdev->conn_hash; + struct list_head *p; struct hci_conn *c; - rcu_read_lock(); - - list_for_each_entry_rcu(c, &h->list, list) { - if (c->handle == handle) { - rcu_read_unlock(); + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + if (c->handle == handle) return c; - } } - rcu_read_unlock(); - 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) { struct hci_conn_hash *h = &hdev->conn_hash; + struct list_head *p; struct hci_conn *c; - rcu_read_lock(); - - list_for_each_entry_rcu(c, &h->list, list) { - if (c->type == type && !bacmp(&c->dst, ba)) { - rcu_read_unlock(); + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + if (c->type == type && !bacmp(&c->dst, ba)) return c; - } } + return NULL; +} - rcu_read_unlock(); +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; } @@ -539,19 +525,44 @@ static inline struct hci_conn *hci_conn_hash_lookup_state(struct hci_dev *hdev, __u8 type, __u16 state) { struct hci_conn_hash *h = &hdev->conn_hash; + struct list_head *p; struct hci_conn *c; - rcu_read_lock(); + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + if (c->type == type && c->state == state) + return c; + } + 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_entry_rcu(c, &h->list, list) { - if (c->type == type && c->state == state) { - rcu_read_unlock(); + list_for_each(p, &l->list) { + c = list_entry(p, struct hci_chan, list); + if (c->ll_handle == handle) return c; - } } + return NULL; +} - rcu_read_unlock(); +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; } @@ -563,32 +574,55 @@ 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_hash_flush(struct hci_dev *hdev, u8 is_process); void hci_conn_check_pending(struct hci_dev *hdev); -struct hci_chan *hci_chan_create(struct hci_conn *conn); +struct hci_chan *hci_chan_add(struct hci_dev *hdev); int hci_chan_del(struct hci_chan *chan); -void hci_chan_list_flush(struct hci_conn *conn); +static inline void hci_chan_hold(struct hci_chan *chan) +{ + atomic_inc(&chan->refcnt); +} +int hci_chan_put(struct hci_chan *chan); + +struct hci_chan *hci_chan_create(struct hci_chan *chan, + 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); +struct hci_conn *hci_le_connect(struct hci_dev *hdev, __u16 pkt_type, + bdaddr_t *dst, __u8 sec_level, + __u8 auth_type, + struct bt_le_params *le_params); 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); void hci_conn_hold_device(struct hci_conn *conn); void hci_conn_put_device(struct hci_conn *conn); +void hci_conn_set_rssi_reporter(struct hci_conn *conn, + s8 rssi_threshold, u16 interval, u8 updateOnThreshExceed); +void hci_conn_unset_rssi_reporter(struct hci_conn *conn); + static inline void hci_conn_hold(struct hci_conn *conn) { atomic_inc(&conn->refcnt); - cancel_delayed_work(&conn->disc_work); + del_timer(&conn->disc_timer); } static inline void hci_conn_put(struct hci_conn *conn) @@ -600,54 +634,54 @@ static inline void hci_conn_put(struct hci_conn *conn) if (conn->state == BT_CONNECTED) { timeo = msecs_to_jiffies(conn->disc_timeout); if (!conn->out) - timeo *= 20; - } else { + timeo *= 4; + } else timeo = msecs_to_jiffies(10); - } - } else { + } else timeo = msecs_to_jiffies(10); - } - cancel_delayed_work(&conn->disc_work); - queue_delayed_work(conn->hdev->workqueue, - &conn->disc_work, timeo); + mod_timer(&conn->disc_timer, jiffies + timeo); } } /* ----- HCI Devices ----- */ -static inline void hci_dev_put(struct hci_dev *d) +static inline void __hci_dev_put(struct hci_dev *d) { - put_device(&d->dev); + if (atomic_dec_and_test(&d->refcnt)) + d->destruct(d); } -static inline struct hci_dev *hci_dev_hold(struct hci_dev *d) +static inline void hci_dev_put(struct hci_dev *d) { - get_device(&d->dev); - return d; + __hci_dev_put(d); + module_put(d->owner); } -#define hci_dev_lock(d) mutex_lock(&d->lock) -#define hci_dev_unlock(d) mutex_unlock(&d->lock) - -#define to_hci_dev(d) container_of(d, struct hci_dev, dev) -#define to_hci_conn(c) container_of(c, struct hci_conn, dev) - -static inline void *hci_get_drvdata(struct hci_dev *hdev) +static inline struct hci_dev *__hci_dev_hold(struct hci_dev *d) { - return dev_get_drvdata(&hdev->dev); + atomic_inc(&d->refcnt); + return d; } -static inline void hci_set_drvdata(struct hci_dev *hdev, void *data) +static inline struct hci_dev *hci_dev_hold(struct hci_dev *d) { - dev_set_drvdata(&hdev->dev, data); + if (try_module_get(d->owner)) + return __hci_dev_hold(d); + return NULL; } +#define hci_dev_lock(d) spin_lock(&d->lock) +#define hci_dev_unlock(d) spin_unlock(&d->lock) +#define hci_dev_lock_bh(d) spin_lock_bh(&d->lock) +#define hci_dev_unlock_bh(d) spin_unlock_bh(&d->lock) + 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); int hci_register_dev(struct hci_dev *hdev); -void hci_unregister_dev(struct hci_dev *hdev); +int hci_unregister_dev(struct hci_dev *hdev); int hci_suspend_dev(struct hci_dev *hdev); int hci_resume_dev(struct hci_dev *hdev); int hci_dev_open(__u16 dev); @@ -660,27 +694,23 @@ 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, u8 type); -int hci_blacklist_del(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type); 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); -struct smp_ltk *hci_find_ltk(struct hci_dev *hdev, __le16 ediv, u8 rand[8]); -int hci_add_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 addr_type, u8 type, - int new_key, u8 authenticated, u8 tk[16], u8 enc_size, u16 ediv, - u8 rand[8]); -struct smp_ltk *hci_find_ltk_by_addr(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 addr_type); -int hci_remove_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr); -int hci_smp_ltks_clear(struct hci_dev *hdev); +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); +int hci_add_ltk(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr, u8 type, + u8 auth, u8 key_size, __le16 ediv, u8 rand[8], u8 ltk[16]); int hci_remove_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr); int hci_remote_oob_data_clear(struct hci_dev *hdev); @@ -704,9 +734,8 @@ int hci_recv_frame(struct sk_buff *skb); int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count); int hci_recv_stream_fragment(struct hci_dev *hdev, void *data, int count); -void hci_init_sysfs(struct hci_dev *hdev); -int hci_add_sysfs(struct hci_dev *hdev); -void hci_del_sysfs(struct hci_dev *hdev); +int hci_register_sysfs(struct hci_dev *hdev); +void hci_unregister_sysfs(struct hci_dev *hdev); void hci_conn_init_sysfs(struct hci_conn *conn); void hci_conn_add_sysfs(struct hci_conn *conn); void hci_conn_del_sysfs(struct hci_conn *conn); @@ -722,46 +751,55 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define lmp_ssp_capable(dev) ((dev)->features[6] & LMP_SIMPLE_PAIR) #define lmp_no_flush_capable(dev) ((dev)->features[6] & LMP_NO_FLUSH) #define lmp_le_capable(dev) ((dev)->features[4] & LMP_LE) -#define lmp_bredr_capable(dev) (!((dev)->features[4] & LMP_NO_BREDR)) - -/* ----- Extended LMP capabilities ----- */ -#define lmp_host_le_capable(dev) ((dev)->host_features[0] & LMP_HOST_LE) /* ----- HCI protocols ----- */ -static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, - __u8 type) +struct hci_proto { + char *name; + unsigned int id; + unsigned long flags; + + void *priv; + + 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, + __u8 is_process); + 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 (*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) { - switch (type) { - case ACL_LINK: - return l2cap_connect_ind(hdev, bdaddr); + register struct hci_proto *hp; + int mask = 0; - case SCO_LINK: - case ESCO_LINK: - return sco_connect_ind(hdev, bdaddr); + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->connect_ind) + mask |= hp->connect_ind(hdev, bdaddr, type); - default: - BT_ERR("unknown link type %d", type); - return -EINVAL; - } + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->connect_ind) + mask |= hp->connect_ind(hdev, bdaddr, type); + + return mask; } static inline void hci_proto_connect_cfm(struct hci_conn *conn, __u8 status) { - switch (conn->type) { - case ACL_LINK: - case LE_LINK: - l2cap_connect_cfm(conn, status); - break; + register struct hci_proto *hp; - case SCO_LINK: - case ESCO_LINK: - sco_connect_cfm(conn, status); - break; + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->connect_cfm) + hp->connect_cfm(conn, status); - default: - BT_ERR("unknown link type %d", conn->type); - break; - } + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->connect_cfm) + hp->connect_cfm(conn, status); if (conn->connect_cfm_cb) conn->connect_cfm_cb(conn, status); @@ -769,29 +807,32 @@ static inline void hci_proto_connect_cfm(struct hci_conn *conn, __u8 status) static inline int hci_proto_disconn_ind(struct hci_conn *conn) { - if (conn->type != ACL_LINK && conn->type != LE_LINK) - return HCI_ERROR_REMOTE_USER_TERM; + register struct hci_proto *hp; + int reason = 0x13; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->disconn_ind) + reason = hp->disconn_ind(conn); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->disconn_ind) + reason = hp->disconn_ind(conn); - return l2cap_disconn_ind(conn); + return reason; } -static inline void hci_proto_disconn_cfm(struct hci_conn *conn, __u8 reason) +static inline void hci_proto_disconn_cfm(struct hci_conn *conn, __u8 reason, + __u8 is_process) { - switch (conn->type) { - case ACL_LINK: - case LE_LINK: - l2cap_disconn_cfm(conn, reason); - break; + register struct hci_proto *hp; - case SCO_LINK: - case ESCO_LINK: - sco_disconn_cfm(conn, reason); - break; + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->disconn_cfm) + hp->disconn_cfm(conn, reason, is_process); - default: - BT_ERR("unknown link type %d", conn->type); - break; - } + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->disconn_cfm) + hp->disconn_cfm(conn, reason, is_process); if (conn->disconn_cfm_cb) conn->disconn_cfm_cb(conn, reason); @@ -799,41 +840,79 @@ static inline void hci_proto_disconn_cfm(struct hci_conn *conn, __u8 reason) static inline void hci_proto_auth_cfm(struct hci_conn *conn, __u8 status) { + register struct hci_proto *hp; __u8 encrypt; - if (conn->type != ACL_LINK && conn->type != LE_LINK) - return; - - if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->flags)) + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) return; encrypt = (conn->link_mode & HCI_LM_ENCRYPT) ? 0x01 : 0x00; - l2cap_security_cfm(conn, status, encrypt); + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->security_cfm) + hp->security_cfm(conn, status, encrypt); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->security_cfm) + hp->security_cfm(conn, status, encrypt); if (conn->security_cfm_cb) 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) { - if (conn->type != ACL_LINK && conn->type != LE_LINK) - return; + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->security_cfm) + hp->security_cfm(conn, status, encrypt); - l2cap_security_cfm(conn, status, encrypt); + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->security_cfm) + hp->security_cfm(conn, status, encrypt); if (conn->security_cfm_cb) 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); + /* ----- HCI callbacks ----- */ struct hci_cb { struct list_head list; 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); }; @@ -845,195 +924,148 @@ static inline void hci_auth_cfm(struct hci_conn *conn, __u8 status) hci_proto_auth_cfm(conn, status); - if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->flags)) + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) return; encrypt = (conn->link_mode & HCI_LM_ENCRYPT) ? 0x01 : 0x00; - read_lock(&hci_cb_list_lock); + read_lock_bh(&hci_cb_list_lock); list_for_each(p, &hci_cb_list) { struct hci_cb *cb = list_entry(p, struct hci_cb, list); if (cb->security_cfm) cb->security_cfm(conn, status, encrypt); } - read_unlock(&hci_cb_list_lock); + 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; if (conn->sec_level == BT_SECURITY_SDP) conn->sec_level = BT_SECURITY_LOW; - if (conn->pending_sec_level > conn->sec_level) + if (!status && encrypt && conn->pending_sec_level > conn->sec_level) conn->sec_level = conn->pending_sec_level; hci_proto_encrypt_cfm(conn, status, encrypt); - read_lock(&hci_cb_list_lock); + read_lock_bh(&hci_cb_list_lock); list_for_each(p, &hci_cb_list) { struct hci_cb *cb = list_entry(p, struct hci_cb, list); if (cb->security_cfm) cb->security_cfm(conn, status, encrypt); } - read_unlock(&hci_cb_list_lock); + read_unlock_bh(&hci_cb_list_lock); } static inline void hci_key_change_cfm(struct hci_conn *conn, __u8 status) { struct list_head *p; - read_lock(&hci_cb_list_lock); + read_lock_bh(&hci_cb_list_lock); list_for_each(p, &hci_cb_list) { struct hci_cb *cb = list_entry(p, struct hci_cb, list); if (cb->key_change_cfm) cb->key_change_cfm(conn, status); } - read_unlock(&hci_cb_list_lock); + 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; - read_lock(&hci_cb_list_lock); + read_lock_bh(&hci_cb_list_lock); list_for_each(p, &hci_cb_list) { struct hci_cb *cb = list_entry(p, struct hci_cb, list); if (cb->role_switch_cfm) cb->role_switch_cfm(conn, status, role); } - read_unlock(&hci_cb_list_lock); + read_unlock_bh(&hci_cb_list_lock); } -static inline bool eir_has_data_type(u8 *data, size_t data_len, u8 type) -{ - size_t parsed = 0; - - if (data_len < 2) - return false; - - while (parsed < data_len - 1) { - u8 field_len = data[0]; - - if (field_len == 0) - break; - - parsed += field_len + 1; - - if (parsed > data_len) - break; +int hci_register_cb(struct hci_cb *hcb); +int hci_unregister_cb(struct hci_cb *hcb); - if (data[1] == type) - return true; +int hci_register_notifier(struct notifier_block *nb); +int hci_unregister_notifier(struct notifier_block *nb); - data += field_len + 1; - } - - return false; -} - -static inline u16 eir_append_data(u8 *eir, u16 eir_len, u8 type, u8 *data, - u8 data_len) -{ - eir[eir_len++] = sizeof(type) + data_len; - eir[eir_len++] = type; - memcpy(&eir[eir_len], data, data_len); - eir_len += data_len; +/* 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); +}; - return eir_len; -} +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_cb(struct hci_cb *hcb); -int hci_unregister_cb(struct hci_cb *hcb); +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_chan *chan, 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); -/* ----- HCI Sockets ----- */ -void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb); -void hci_send_to_control(struct sk_buff *skb, struct sock *skip_sk); -void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb); +void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data); -void hci_sock_dev_event(struct hci_dev *hdev, int event); +/* ----- HCI Sockets ----- */ +void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb, + struct sock *skip_sk); /* Management interface */ -#define MGMT_ADDR_BREDR 0x00 -#define MGMT_ADDR_LE_PUBLIC 0x01 -#define MGMT_ADDR_LE_RANDOM 0x02 -#define MGMT_ADDR_INVALID 0xff - -#define DISCOV_TYPE_BREDR (BIT(MGMT_ADDR_BREDR)) -#define DISCOV_TYPE_LE (BIT(MGMT_ADDR_LE_PUBLIC) | \ - BIT(MGMT_ADDR_LE_RANDOM)) -#define DISCOV_TYPE_INTERLEAVED (BIT(MGMT_ADDR_BREDR) | \ - BIT(MGMT_ADDR_LE_PUBLIC) | \ - BIT(MGMT_ADDR_LE_RANDOM)) - int mgmt_control(struct sock *sk, struct msghdr *msg, size_t len); -int mgmt_index_added(struct hci_dev *hdev); -int mgmt_index_removed(struct hci_dev *hdev); -int mgmt_powered(struct hci_dev *hdev, u8 powered); -int mgmt_discoverable(struct hci_dev *hdev, u8 discoverable); -int mgmt_connectable(struct hci_dev *hdev, u8 connectable); -int mgmt_write_scan_failed(struct hci_dev *hdev, u8 scan, u8 status); -int mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, - bool persistent); -int mgmt_device_connected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u32 flags, u8 *name, u8 name_len, - u8 *dev_class); -int mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type); -int mgmt_disconnect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status); -int mgmt_connect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 status); -int mgmt_pin_code_request(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 secure); -int mgmt_pin_code_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status); -int mgmt_pin_code_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status); -int mgmt_user_confirm_request(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, __le32 value, - u8 confirm_hint); -int mgmt_user_confirm_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status); -int mgmt_user_confirm_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status); -int mgmt_user_passkey_request(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type); -int mgmt_user_passkey_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status); -int mgmt_user_passkey_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status); -int mgmt_auth_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 status); -int mgmt_auth_enable_complete(struct hci_dev *hdev, u8 status); -int mgmt_ssp_enable_complete(struct hci_dev *hdev, u8 enable, u8 status); -int mgmt_set_class_of_dev_complete(struct hci_dev *hdev, u8 *dev_class, - u8 status); -int mgmt_set_local_name_complete(struct hci_dev *hdev, u8 *name, u8 status); -int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, - u8 *randomizer, u8 status); -int mgmt_le_enable_complete(struct hci_dev *hdev, u8 enable, u8 status); -int mgmt_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 *dev_class, s8 rssi, u8 cfm_name, - u8 ssp, u8 *eir, u16 eir_len); -int mgmt_remote_name(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, s8 rssi, u8 *name, u8 name_len); -int mgmt_start_discovery_failed(struct hci_dev *hdev, u8 status); -int mgmt_stop_discovery_failed(struct hci_dev *hdev, u8 status); -int mgmt_discovering(struct hci_dev *hdev, u8 discovering); -int mgmt_interleaved_discovery(struct hci_dev *hdev); -int mgmt_device_blocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type); -int mgmt_device_unblocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type); - -int mgmt_new_ltk(struct hci_dev *hdev, struct smp_ltk *key, u8 persistent); +int mgmt_index_added(u16 index); +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 bonded); +int mgmt_connected(u16 index, bdaddr_t *bdaddr, u8 le); +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); +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, u8 event, bdaddr_t *bdaddr, + __le32 value); +int mgmt_user_oob_request(u16 index, bdaddr_t *bdaddr); +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); +int mgmt_auth_failed(u16 index, bdaddr_t *bdaddr, u8 status); +int mgmt_set_local_name_complete(u16 index, u8 *name, u8 status); +int mgmt_read_local_oob_data_reply_complete(u16 index, u8 *hash, u8 *randomizer, + u8 status); +int mgmt_device_found(u16 index, bdaddr_t *bdaddr, u8 type, u8 le, + u8 *dev_class, s8 rssi, u8 eir_len, u8 *eir); +void mgmt_read_rssi_complete(u16 index, s8 rssi, bdaddr_t *bdaddr, + u16 handle, u8 status); +int mgmt_remote_name(u16 index, bdaddr_t *bdaddr, u8 status, u8 *name); +void mgmt_inquiry_started(u16 index); +void mgmt_inquiry_complete_evt(u16 index, u8 status); +void mgmt_disco_timeout(unsigned long data); +void mgmt_disco_le_timeout(unsigned long data); +int mgmt_encrypt_change(u16 index, bdaddr_t *bdaddr, u8 status); + +/* LE SMP Management interface */ +int le_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, void *cp); +int mgmt_remote_class(u16 index, bdaddr_t *bdaddr, u8 dev_class[3]); +int mgmt_remote_version(u16 index, bdaddr_t *bdaddr, u8 ver, u16 mnf, + u16 sub_ver); +int mgmt_remote_features(u16 index, bdaddr_t *bdaddr, u8 features[8]); /* HCI info for socket */ #define hci_pi(sk) ((struct hci_pinfo *) sk) @@ -1072,9 +1104,6 @@ void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __u8 rand[8], void hci_le_ltk_reply(struct hci_conn *conn, u8 ltk[16]); void hci_le_ltk_neg_reply(struct hci_conn *conn); -int hci_do_inquiry(struct hci_dev *hdev, u8 length); -int hci_cancel_inquiry(struct hci_dev *hdev); -int hci_le_scan(struct hci_dev *hdev, u8 type, u16 interval, u16 window, - int timeout); +void hci_read_rssi(struct hci_conn *conn); #endif /* __HCI_CORE_H */ diff --git a/include/net/bluetooth/hci_mon.h b/include/net/bluetooth/hci_mon.h deleted file mode 100644 index 77d1e5764185acc0aea30c3cc4fc7eb2a3d79509..0000000000000000000000000000000000000000 --- a/include/net/bluetooth/hci_mon.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - BlueZ - Bluetooth protocol stack for Linux - - Copyright (C) 2011-2012 Intel 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; - - 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 __HCI_MON_H -#define __HCI_MON_H - -struct hci_mon_hdr { - __le16 opcode; - __le16 index; - __le16 len; -} __packed; -#define HCI_MON_HDR_SIZE 6 - -#define HCI_MON_NEW_INDEX 0 -#define HCI_MON_DEL_INDEX 1 -#define HCI_MON_COMMAND_PKT 2 -#define HCI_MON_EVENT_PKT 3 -#define HCI_MON_ACL_TX_PKT 4 -#define HCI_MON_ACL_RX_PKT 5 -#define HCI_MON_SCO_TX_PKT 6 -#define HCI_MON_SCO_RX_PKT 7 - -struct hci_mon_new_index { - __u8 type; - __u8 bus; - bdaddr_t bdaddr; - char name[8]; -} __packed; -#define HCI_MON_NEW_INDEX_SIZE 16 - -#endif /* __HCI_MON_H */ diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 9b242c6bf55b2d9ecf8b0bebb725a45b1a7ee947..9a03a127ad06a8d3b2f17a2dae1649f43fd10bb1 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-2012 Code Aurora Forum. All rights reserved. Copyright (C) 2009-2010 Gustavo F. Padovan Copyright (C) 2010 Google Inc. @@ -27,29 +27,32 @@ #ifndef __L2CAP_H #define __L2CAP_H -#include - /* 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_MAX_FLUSH_TO 0x7ff #define L2CAP_DEFAULT_TX_WINDOW 63 -#define L2CAP_DEFAULT_EXT_WINDOW 0x3FFF #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_DEFAULT_MAX_SDU_SIZE 0xFFFF -#define L2CAP_DEFAULT_SDU_ITIME 0xFFFFFFFF -#define L2CAP_DEFAULT_ACC_LAT 0xFFFFFFFF -#define L2CAP_DISC_TIMEOUT msecs_to_jiffies(100) -#define L2CAP_DISC_REJ_TIMEOUT msecs_to_jiffies(5000) -#define L2CAP_ENC_TIMEOUT msecs_to_jiffies(5000) -#define L2CAP_CONN_TIMEOUT msecs_to_jiffies(40000) -#define L2CAP_INFO_TIMEOUT msecs_to_jiffies(4000) +#define L2CAP_CONN_TIMEOUT (40000) /* 40 seconds */ +#define L2CAP_INFO_TIMEOUT (4000) /* 4 seconds */ +#define L2CAP_MOVE_TIMEOUT (4*HZ) /* 4 seconds */ +#define L2CAP_MOVE_ERTX_TIMEOUT (60*HZ) /* 60 seconds */ /* L2CAP socket address */ struct sockaddr_l2 { @@ -84,36 +87,35 @@ 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_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 -/* L2CAP extended feature mask */ +/* L2CAP feature mask */ #define L2CAP_FEAT_FLOWCTL 0x00000001 #define L2CAP_FEAT_RETRANS 0x00000002 -#define L2CAP_FEAT_BIDIR_QOS 0x00000004 #define L2CAP_FEAT_ERTM 0x00000008 #define L2CAP_FEAT_STREAMING 0x00000010 #define L2CAP_FEAT_FCS 0x00000020 -#define L2CAP_FEAT_EXT_FLOW 0x00000040 #define L2CAP_FEAT_FIXED_CHAN 0x00000080 #define L2CAP_FEAT_EXT_WINDOW 0x00000100 #define L2CAP_FEAT_UCD 0x00000200 @@ -126,53 +128,52 @@ struct l2cap_conninfo { #define L2CAP_FC_L2CAP 0x02 #define L2CAP_FC_A2MP 0x08 -/* L2CAP Control Field bit masks */ -#define L2CAP_CTRL_SAR 0xC000 -#define L2CAP_CTRL_REQSEQ 0x3F00 -#define L2CAP_CTRL_TXSEQ 0x007E -#define L2CAP_CTRL_SUPERVISE 0x000C - -#define L2CAP_CTRL_RETRANS 0x0080 -#define L2CAP_CTRL_FINAL 0x0080 -#define L2CAP_CTRL_POLL 0x0010 -#define L2CAP_CTRL_FRAME_TYPE 0x0001 /* I- or S-Frame */ - -#define L2CAP_CTRL_TXSEQ_SHIFT 1 -#define L2CAP_CTRL_SUPER_SHIFT 2 -#define L2CAP_CTRL_REQSEQ_SHIFT 8 -#define L2CAP_CTRL_SAR_SHIFT 14 - -/* L2CAP Extended Control Field bit mask */ -#define L2CAP_EXT_CTRL_TXSEQ 0xFFFC0000 -#define L2CAP_EXT_CTRL_SAR 0x00030000 -#define L2CAP_EXT_CTRL_SUPERVISE 0x00030000 -#define L2CAP_EXT_CTRL_REQSEQ 0x0000FFFC - -#define L2CAP_EXT_CTRL_POLL 0x00040000 -#define L2CAP_EXT_CTRL_FINAL 0x00000002 -#define L2CAP_EXT_CTRL_FRAME_TYPE 0x00000001 /* I- or S-Frame */ - -#define L2CAP_EXT_CTRL_REQSEQ_SHIFT 2 -#define L2CAP_EXT_CTRL_SAR_SHIFT 16 -#define L2CAP_EXT_CTRL_SUPER_SHIFT 16 -#define L2CAP_EXT_CTRL_TXSEQ_SHIFT 18 - -/* L2CAP Supervisory Function */ -#define L2CAP_SUPER_RR 0x00 -#define L2CAP_SUPER_REJ 0x01 -#define L2CAP_SUPER_RNR 0x02 -#define L2CAP_SUPER_SREJ 0x03 +/* L2CAP Control Field */ +#define L2CAP_CTRL_SAR 0xC000 +#define L2CAP_CTRL_REQSEQ 0x3F00 +#define L2CAP_CTRL_TXSEQ 0x007E +#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 + +#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_SAR_UNSEGMENTED 0x00 -#define L2CAP_SAR_START 0x01 -#define L2CAP_SAR_END 0x02 -#define L2CAP_SAR_CONTINUE 0x03 +#define L2CAP_SAR_UNSEGMENTED 0x00 +#define L2CAP_SAR_START 0x01 +#define L2CAP_SAR_END 0x02 +#define L2CAP_SAR_CONTINUE 0x03 -/* L2CAP Command rej. reasons */ -#define L2CAP_REJ_NOT_UNDERSTOOD 0x0000 -#define L2CAP_REJ_MTU_EXCEEDED 0x0001 -#define L2CAP_REJ_INVALID_CID 0x0002 +/* L2CAP ERTM / Streaming extra field lengths */ +#define L2CAP_SDULEN_SIZE 2 +#define L2CAP_FCS_SIZE 2 /* L2CAP structures */ struct l2cap_hdr { @@ -180,12 +181,8 @@ struct l2cap_hdr { __le16 cid; } __packed; #define L2CAP_HDR_SIZE 4 -#define L2CAP_ENH_HDR_SIZE 6 -#define L2CAP_EXT_HDR_SIZE 8 - -#define L2CAP_FCS_SIZE 2 -#define L2CAP_SDULEN_SIZE 2 -#define L2CAP_PSMLEN_SIZE 2 +#define L2CAP_ENHANCED_HDR_SIZE 6 +#define L2CAP_EXTENDED_HDR_SIZE 8 struct l2cap_cmd_hdr { __u8 code; @@ -194,21 +191,10 @@ struct l2cap_cmd_hdr { } __packed; #define L2CAP_CMD_HDR_SIZE 4 -struct l2cap_cmd_rej_unk { +struct l2cap_cmd_rej { __le16 reason; } __packed; -struct l2cap_cmd_rej_mtu { - __le16 reason; - __le16 max_mtu; -} __packed; - -struct l2cap_cmd_rej_cid { - __le16 reason; - __le16 scid; - __le16 dcid; -} __packed; - struct l2cap_conn_req { __le16 psm; __le16 scid; @@ -224,21 +210,21 @@ 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 #define L2CAP_CID_DYN_START 0x0040 #define L2CAP_CID_DYN_END 0xffff -/* connect/create channel results */ +/* connect result */ #define L2CAP_CR_SUCCESS 0x0000 #define L2CAP_CR_PEND 0x0001 #define L2CAP_CR_BAD_PSM 0x0002 #define L2CAP_CR_SEC_BLOCK 0x0003 #define L2CAP_CR_NO_MEM 0x0004 -#define L2CAP_CR_BAD_AMP 0x0005 -/* connect/create channel status */ +/* connect status */ #define L2CAP_CS_NO_INFO 0x0000 #define L2CAP_CS_AUTHEN_PEND 0x0001 #define L2CAP_CS_AUTHOR_PEND 0x0002 @@ -261,7 +247,7 @@ struct l2cap_conf_rsp { #define L2CAP_CONF_REJECT 0x0002 #define L2CAP_CONF_UNKNOWN 0x0003 #define L2CAP_CONF_PENDING 0x0004 -#define L2CAP_CONF_EFS_REJECT 0x0005 +#define L2CAP_CONF_FLOW_SPEC_REJECT 0x0005 struct l2cap_conf_opt { __u8 type; @@ -278,8 +264,13 @@ struct l2cap_conf_opt { #define L2CAP_CONF_QOS 0x03 #define L2CAP_CONF_RFC 0x04 #define L2CAP_CONF_FCS 0x05 -#define L2CAP_CONF_EFS 0x06 -#define L2CAP_CONF_EWS 0x07 +#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 @@ -292,27 +283,26 @@ 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; + __le32 flush_to; +}; + #define L2CAP_MODE_BASIC 0x00 #define L2CAP_MODE_RETRANS 0x01 #define L2CAP_MODE_FLOWCTL 0x02 #define L2CAP_MODE_ERTM 0x03 #define L2CAP_MODE_STREAMING 0x04 -struct l2cap_conf_efs { - __u8 id; - __u8 stype; - __le16 msdu; - __le32 sdu_itime; - __le32 acc_lat; - __le32 flush_to; -} __packed; - -#define L2CAP_SERV_NOTRAFIC 0x00 -#define L2CAP_SERV_BESTEFFORT 0x01 -#define L2CAP_SERV_GUARANTEED 0x02 - -#define L2CAP_BESTEFFORT_ID 0x01 - struct l2cap_disconn_req { __le16 dcid; __le16 scid; @@ -337,53 +327,83 @@ struct l2cap_create_chan_req { __le16 psm; __le16 scid; __u8 amp_id; -} __packed; +} __attribute__ ((packed)); struct l2cap_create_chan_rsp { __le16 dcid; __le16 scid; __le16 result; __le16 status; -} __packed; +} __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; -} __packed; +} __attribute__ ((packed)); struct l2cap_move_chan_rsp { __le16 icid; __le16 result; -} __packed; +} __attribute__ ((packed)); -#define L2CAP_MR_SUCCESS 0x0000 -#define L2CAP_MR_PEND 0x0001 -#define L2CAP_MR_BAD_ID 0x0002 -#define L2CAP_MR_SAME_ID 0x0003 -#define L2CAP_MR_NOT_SUPP 0x0004 -#define L2CAP_MR_COLLISION 0x0005 -#define L2CAP_MR_NOT_ALLOWED 0x0006 +#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; -} __packed; +} __attribute__ ((packed)); -#define L2CAP_MC_CONFIRMED 0x0000 -#define L2CAP_MC_UNCONFIRMED 0x0001 +#define L2CAP_MOVE_CHAN_CONFIRMED (0x0000) +#define L2CAP_MOVE_CHAN_UNCONFIRMED (0x0001) struct l2cap_move_chan_cfm_rsp { __le16 icid; -} __packed; +} __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 -#define L2CAP_IT_FIXED_CHAN 0x0003 +#define L2CAP_IT_CL_MTU 0x0001 +#define L2CAP_IT_FEAT_MASK 0x0002 +#define L2CAP_IT_FIXED_CHAN 0x0003 /* info result */ -#define L2CAP_IR_SUCCESS 0x0000 -#define L2CAP_IR_NOTSUPP 0x0001 +#define L2CAP_IR_SUCCESS 0x0000 +#define L2CAP_IR_NOTSUPP 0x0001 struct l2cap_conn_param_update_req { __le16 min; @@ -400,21 +420,64 @@ 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 { - __u16 tx_seq; - struct list_head list; +/* ----- L2CAP connections ----- */ +struct l2cap_chan_list { + struct sock *head; + rwlock_t lock; }; -struct l2cap_chan { - struct sock *sk; +struct l2cap_conn { + struct hci_conn *hcon; - struct l2cap_conn *conn; + bdaddr_t *dst; + bdaddr_t *src; + + unsigned int mtu; + + __u32 feat_mask; + __u8 fc_mask; + struct amp_mgr *mgr; + + __u8 info_state; + __u8 info_ident; + + struct timer_list info_timer; + + spinlock_t lock; - __u8 state; + struct sk_buff *rx_skb; + __u32 rx_len; + __u8 tx_ident; - atomic_t refcnt; + __u8 disc_reason; + 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 + +/* ----- 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; __le16 psm; __u16 dcid; __u16 scid; @@ -423,443 +486,234 @@ struct l2cap_chan { __u16 omtu; __u16 flush_to; __u8 mode; - __u8 chan_type; - __u8 chan_policy; - - __le16 sport; - - __u8 sec_level; - - __u8 ident; - - __u8 conf_req[64]; - __u8 conf_len; + __u8 fixed_channel; __u8 num_conf_req; __u8 num_conf_rsp; + __u8 incoming; __u8 fcs; + __u8 sec_level; + __u8 role_switch; + __u8 force_reliable; + __u8 flushable; + __u8 force_active; - __u16 tx_win; - __u16 tx_win_max; - __u8 max_tx; - __u16 retrans_timeout; - __u16 monitor_timeout; - __u16 mps; - - unsigned long conf_state; - unsigned long conn_state; - unsigned long flags; + __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 buffer_seq_srej; __u16 srej_save_reqseq; - __u16 frames_sent; + __u16 last_acked_seq; + __u32 frames_sent; __u16 unacked_frames; __u8 retry_count; - __u8 num_acked; + __u16 srej_queue_next; __u16 sdu_len; struct sk_buff *sdu; struct sk_buff *sdu_last_frag; + atomic_t ertm_queued; + + __u8 ident; + __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; - __u8 local_id; - __u8 local_stype; - __u16 local_msdu; - __u32 local_sdu_itime; - __u32 local_acc_lat; - __u32 local_flush_to; - - __u8 remote_id; - __u8 remote_stype; - __u16 remote_msdu; - __u32 remote_sdu_itime; - __u32 remote_acc_lat; - __u32 remote_flush_to; - - struct delayed_work chan_timer; - struct delayed_work retrans_timer; - struct delayed_work monitor_timer; - struct delayed_work 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 mutex lock; -}; - -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); - struct sk_buff *(*alloc_skb) (struct l2cap_chan *chan, - unsigned long len, int nb, int *err); - -}; - -struct l2cap_conn { - struct hci_conn *hcon; - struct hci_chan *hchan; - - bdaddr_t *dst; - bdaddr_t *src; - - unsigned int mtu; - - __u32 feat_mask; - __u8 fixed_chan_mask; - - __u8 info_state; - __u8 info_ident; - - struct delayed_work info_timer; - - spinlock_t lock; - - struct sk_buff *rx_skb; - __u32 rx_len; - __u8 tx_ident; - - __u8 disc_reason; - - struct delayed_work security_timer; - struct smp_chan *smp_chan; - - struct list_head chan_l; - struct mutex chan_lock; -}; - -#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 ----- */ -#define l2cap_pi(sk) ((struct l2cap_pinfo *) sk) + __le16 sport; -struct l2cap_pinfo { - struct bt_sock bt; - struct l2cap_chan *chan; - struct sk_buff *rx_busy_skb; + 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; }; -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, - CONF_EWS_RECV, - CONF_LOC_CONF_PEND, - CONF_REM_CONF_PEND, -}; +#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 L2CAP_CONF_MAX_CONF_REQ 2 #define L2CAP_CONF_MAX_CONF_RSP 2 -enum { - 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, -}; - -/* Definitions for flags in l2cap_chan */ -enum { - FLAG_ROLE_SWITCH, - FLAG_FORCE_ACTIVE, - FLAG_FORCE_RELIABLE, - FLAG_FLUSHABLE, - FLAG_EXT_CTRL, - FLAG_EFS_ENABLE, -}; - -static inline void l2cap_chan_hold(struct l2cap_chan *c) -{ - atomic_inc(&c->refcnt); -} - -static inline void l2cap_chan_put(struct l2cap_chan *c) -{ - if (atomic_dec_and_test(&c->refcnt)) - kfree(c); -} - -static inline void l2cap_chan_lock(struct l2cap_chan *chan) -{ - mutex_lock(&chan->lock); -} - -static inline void l2cap_chan_unlock(struct l2cap_chan *chan) -{ - mutex_unlock(&chan->lock); -} - -static inline void l2cap_set_timer(struct l2cap_chan *chan, - struct delayed_work *work, long timeout) -{ - BT_DBG("chan %p state %s timeout %ld", chan, - state_to_string(chan->state), timeout); - - if (!cancel_delayed_work(work)) - l2cap_chan_hold(chan); - schedule_delayed_work(work, timeout); -} - -static inline bool l2cap_clear_timer(struct l2cap_chan *chan, - struct delayed_work *work) -{ - bool ret; - - ret = cancel_delayed_work(work); - if (ret) - l2cap_chan_put(chan); - - return ret; -} - -#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, \ - msecs_to_jiffies(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, \ - msecs_to_jiffies(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, \ - msecs_to_jiffies(L2CAP_DEFAULT_ACK_TO)); -#define __clear_ack_timer(c) l2cap_clear_timer(c, &c->ack_timer) - -static inline int __seq_offset(struct l2cap_chan *chan, __u16 seq1, __u16 seq2) -{ - int offset; - - offset = (seq1 - seq2) % (chan->tx_win_max + 1); - if (offset < 0) - offset += (chan->tx_win_max + 1); - - return offset; -} - -static inline __u16 __next_seq(struct l2cap_chan *chan, __u16 seq) -{ - return (seq + 1) % (chan->tx_win_max + 1); -} - -static inline int l2cap_tx_window_full(struct l2cap_chan *ch) -{ - int sub; - - sub = (ch->next_tx_seq - ch->expected_ack_seq) % 64; - - if (sub < 0) - sub += 64; - - return sub == ch->remote_tx_win; -} - -static inline __u16 __get_reqseq(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (ctrl & L2CAP_EXT_CTRL_REQSEQ) >> - L2CAP_EXT_CTRL_REQSEQ_SHIFT; - else - return (ctrl & L2CAP_CTRL_REQSEQ) >> L2CAP_CTRL_REQSEQ_SHIFT; -} - -static inline __u32 __set_reqseq(struct l2cap_chan *chan, __u32 reqseq) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (reqseq << L2CAP_EXT_CTRL_REQSEQ_SHIFT) & - L2CAP_EXT_CTRL_REQSEQ; - else - return (reqseq << L2CAP_CTRL_REQSEQ_SHIFT) & L2CAP_CTRL_REQSEQ; -} - -static inline __u16 __get_txseq(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (ctrl & L2CAP_EXT_CTRL_TXSEQ) >> - L2CAP_EXT_CTRL_TXSEQ_SHIFT; - else - return (ctrl & L2CAP_CTRL_TXSEQ) >> L2CAP_CTRL_TXSEQ_SHIFT; -} - -static inline __u32 __set_txseq(struct l2cap_chan *chan, __u32 txseq) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (txseq << L2CAP_EXT_CTRL_TXSEQ_SHIFT) & - L2CAP_EXT_CTRL_TXSEQ; - else - return (txseq << L2CAP_CTRL_TXSEQ_SHIFT) & L2CAP_CTRL_TXSEQ; -} - -static inline bool __is_sframe(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return ctrl & L2CAP_EXT_CTRL_FRAME_TYPE; - else - return ctrl & L2CAP_CTRL_FRAME_TYPE; -} - -static inline __u32 __set_sframe(struct l2cap_chan *chan) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return L2CAP_EXT_CTRL_FRAME_TYPE; - else - return L2CAP_CTRL_FRAME_TYPE; -} - -static inline __u8 __get_ctrl_sar(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (ctrl & L2CAP_EXT_CTRL_SAR) >> L2CAP_EXT_CTRL_SAR_SHIFT; - else - return (ctrl & L2CAP_CTRL_SAR) >> L2CAP_CTRL_SAR_SHIFT; -} - -static inline __u32 __set_ctrl_sar(struct l2cap_chan *chan, __u32 sar) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (sar << L2CAP_EXT_CTRL_SAR_SHIFT) & L2CAP_EXT_CTRL_SAR; - else - return (sar << L2CAP_CTRL_SAR_SHIFT) & L2CAP_CTRL_SAR; -} - -static inline bool __is_sar_start(struct l2cap_chan *chan, __u32 ctrl) -{ - return __get_ctrl_sar(chan, ctrl) == L2CAP_SAR_START; -} - -static inline __u32 __get_sar_mask(struct l2cap_chan *chan) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return L2CAP_EXT_CTRL_SAR; - else - return L2CAP_CTRL_SAR; -} - -static inline __u8 __get_ctrl_super(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (ctrl & L2CAP_EXT_CTRL_SUPERVISE) >> - L2CAP_EXT_CTRL_SUPER_SHIFT; - else - return (ctrl & L2CAP_CTRL_SUPERVISE) >> L2CAP_CTRL_SUPER_SHIFT; -} - -static inline __u32 __set_ctrl_super(struct l2cap_chan *chan, __u32 super) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return (super << L2CAP_EXT_CTRL_SUPER_SHIFT) & - L2CAP_EXT_CTRL_SUPERVISE; - else - return (super << L2CAP_CTRL_SUPER_SHIFT) & - L2CAP_CTRL_SUPERVISE; -} - -static inline __u32 __set_ctrl_final(struct l2cap_chan *chan) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return L2CAP_EXT_CTRL_FINAL; - else - return L2CAP_CTRL_FINAL; -} - -static inline bool __is_ctrl_final(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return ctrl & L2CAP_EXT_CTRL_FINAL; - else - return ctrl & L2CAP_CTRL_FINAL; -} - -static inline __u32 __set_ctrl_poll(struct l2cap_chan *chan) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return L2CAP_EXT_CTRL_POLL; - else - return L2CAP_CTRL_POLL; -} - -static inline bool __is_ctrl_poll(struct l2cap_chan *chan, __u32 ctrl) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return ctrl & L2CAP_EXT_CTRL_POLL; - else - return ctrl & L2CAP_CTRL_POLL; -} - -static inline __u32 __get_control(struct l2cap_chan *chan, void *p) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return get_unaligned_le32(p); - else - return get_unaligned_le16(p); -} - -static inline void __put_control(struct l2cap_chan *chan, __u32 control, - void *p) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return put_unaligned_le32(control, p); - else - return put_unaligned_le16(control, p); -} - -static inline __u8 __ctrl_size(struct l2cap_chan *chan) -{ - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - return L2CAP_EXT_HDR_SIZE - L2CAP_HDR_SIZE; - else - return L2CAP_ENH_HDR_SIZE - L2CAP_HDR_SIZE; -} +#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 L2CAP_ATT_ERROR 0x01 +#define L2CAP_ATT_MTU_REQ 0x02 +#define L2CAP_ATT_MTU_RSP 0x03 +#define L2CAP_ATT_RESPONSE_BIT 0x01 +#define L2CAP_ATT_INDICATE 0x1D +#define L2CAP_ATT_NOT_SUPPORTED 0x06 + +#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 bool 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, __le16 psm, u16 cid, - bdaddr_t *dst); -int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, - u32 priority); -void l2cap_chan_busy(struct l2cap_chan *chan, int busy); -int l2cap_chan_check_security(struct l2cap_chan *chan); +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); + +int l2cap_sock_le_params_valid(struct bt_le_params *le_params); +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); +struct sock *l2cap_find_sock_by_fixed_cid_and_dir(__le16 cid, bdaddr_t *src, + bdaddr_t *dst, int server); +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 ebfd91fc20f804437241614418e7505ac15c83a0..e34c4258316748b704da45b1565c4c50219c8737 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -2,7 +2,6 @@ BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2010 Nokia Corporation - Copyright (C) 2011-2012 Intel 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 @@ -24,439 +23,359 @@ #define MGMT_INDEX_NONE 0xFFFF -#define MGMT_STATUS_SUCCESS 0x00 -#define MGMT_STATUS_UNKNOWN_COMMAND 0x01 -#define MGMT_STATUS_NOT_CONNECTED 0x02 -#define MGMT_STATUS_FAILED 0x03 -#define MGMT_STATUS_CONNECT_FAILED 0x04 -#define MGMT_STATUS_AUTH_FAILED 0x05 -#define MGMT_STATUS_NOT_PAIRED 0x06 -#define MGMT_STATUS_NO_RESOURCES 0x07 -#define MGMT_STATUS_TIMEOUT 0x08 -#define MGMT_STATUS_ALREADY_CONNECTED 0x09 -#define MGMT_STATUS_BUSY 0x0a -#define MGMT_STATUS_REJECTED 0x0b -#define MGMT_STATUS_NOT_SUPPORTED 0x0c -#define MGMT_STATUS_INVALID_PARAMS 0x0d -#define MGMT_STATUS_DISCONNECTED 0x0e -#define MGMT_STATUS_NOT_POWERED 0x0f -#define MGMT_STATUS_CANCELLED 0x10 -#define MGMT_STATUS_INVALID_INDEX 0x11 - struct mgmt_hdr { - __le16 opcode; - __le16 index; - __le16 len; -} __packed; - -struct mgmt_addr_info { - bdaddr_t bdaddr; - __u8 type; + __le16 opcode; + __le16 index; + __le16 len; } __packed; -#define MGMT_ADDR_INFO_SIZE 7 #define MGMT_OP_READ_VERSION 0x0001 -#define MGMT_READ_VERSION_SIZE 0 struct mgmt_rp_read_version { - __u8 version; - __le16 revision; -} __packed; - -#define MGMT_OP_READ_COMMANDS 0x0002 -#define MGMT_READ_COMMANDS_SIZE 0 -struct mgmt_rp_read_commands { - __le16 num_commands; - __le16 num_events; - __le16 opcodes[0]; + __u8 version; + __le16 revision; } __packed; #define MGMT_OP_READ_INDEX_LIST 0x0003 -#define MGMT_READ_INDEX_LIST_SIZE 0 struct mgmt_rp_read_index_list { - __le16 num_controllers; - __le16 index[0]; + __le16 num_controllers; + __le16 index[0]; } __packed; /* Reserve one extra byte for names in management messages so that they * are always guaranteed to be nul-terminated */ #define MGMT_MAX_NAME_LENGTH (HCI_MAX_NAME_LENGTH + 1) -#define MGMT_MAX_SHORT_NAME_LENGTH (HCI_MAX_SHORT_NAME_LENGTH + 1) - -#define MGMT_SETTING_POWERED 0x00000001 -#define MGMT_SETTING_CONNECTABLE 0x00000002 -#define MGMT_SETTING_FAST_CONNECTABLE 0x00000004 -#define MGMT_SETTING_DISCOVERABLE 0x00000008 -#define MGMT_SETTING_PAIRABLE 0x00000010 -#define MGMT_SETTING_LINK_SECURITY 0x00000020 -#define MGMT_SETTING_SSP 0x00000040 -#define MGMT_SETTING_BREDR 0x00000080 -#define MGMT_SETTING_HS 0x00000100 -#define MGMT_SETTING_LE 0x00000200 #define MGMT_OP_READ_INFO 0x0004 -#define MGMT_READ_INFO_SIZE 0 struct mgmt_rp_read_info { - bdaddr_t bdaddr; - __u8 version; - __le16 manufacturer; - __le32 supported_settings; - __le32 current_settings; - __u8 dev_class[3]; - __u8 name[MGMT_MAX_NAME_LENGTH]; - __u8 short_name[MGMT_MAX_SHORT_NAME_LENGTH]; + __u8 type; + __u8 powered; + __u8 connectable; + __u8 discoverable; + __u8 pairable; + __u8 sec_mode; + bdaddr_t bdaddr; + __u8 dev_class[3]; + __u8 features[8]; + __u16 manufacturer; + __u8 hci_ver; + __u16 hci_rev; + __u8 name[MGMT_MAX_NAME_LENGTH]; } __packed; struct mgmt_mode { __u8 val; } __packed; -#define MGMT_SETTING_SIZE 1 - #define MGMT_OP_SET_POWERED 0x0005 #define MGMT_OP_SET_DISCOVERABLE 0x0006 -struct mgmt_cp_set_discoverable { - __u8 val; - __le16 timeout; -} __packed; -#define MGMT_SET_DISCOVERABLE_SIZE 3 #define MGMT_OP_SET_CONNECTABLE 0x0007 -#define MGMT_OP_SET_FAST_CONNECTABLE 0x0008 - -#define MGMT_OP_SET_PAIRABLE 0x0009 - -#define MGMT_OP_SET_LINK_SECURITY 0x000A - -#define MGMT_OP_SET_SSP 0x000B - -#define MGMT_OP_SET_HS 0x000C - -#define MGMT_OP_SET_LE 0x000D -#define MGMT_OP_SET_DEV_CLASS 0x000E -struct mgmt_cp_set_dev_class { - __u8 major; - __u8 minor; -} __packed; -#define MGMT_SET_DEV_CLASS_SIZE 2 - -#define MGMT_OP_SET_LOCAL_NAME 0x000F -struct mgmt_cp_set_local_name { - __u8 name[MGMT_MAX_NAME_LENGTH]; - __u8 short_name[MGMT_MAX_SHORT_NAME_LENGTH]; -} __packed; -#define MGMT_SET_LOCAL_NAME_SIZE 260 +#define MGMT_OP_SET_PAIRABLE 0x0008 -#define MGMT_OP_ADD_UUID 0x0010 +#define MGMT_OP_ADD_UUID 0x0009 struct mgmt_cp_add_uuid { - __u8 uuid[16]; - __u8 svc_hint; + __u8 uuid[16]; + __u8 svc_hint; } __packed; -#define MGMT_ADD_UUID_SIZE 17 -#define MGMT_OP_REMOVE_UUID 0x0011 +#define MGMT_OP_REMOVE_UUID 0x000A struct mgmt_cp_remove_uuid { - __u8 uuid[16]; + __u8 uuid[16]; } __packed; -#define MGMT_REMOVE_UUID_SIZE 16 -struct mgmt_link_key_info { - struct mgmt_addr_info addr; - __u8 type; - __u8 val[16]; - __u8 pin_len; +#define MGMT_OP_SET_DEV_CLASS 0x000B +struct mgmt_cp_set_dev_class { + __u8 major; + __u8 minor; +} __packed; +#define MGMT_MAJOR_CLASS_MASK 0x1F +#define MGMT_MAJOR_CLASS_LIMITED 0x20 + +#define MGMT_OP_SET_SERVICE_CACHE 0x000C +struct mgmt_cp_set_service_cache { + __u8 enable; } __packed; -#define MGMT_OP_LOAD_LINK_KEYS 0x0012 -struct mgmt_cp_load_link_keys { - __u8 debug_keys; - __le16 key_count; - struct mgmt_link_key_info keys[0]; +struct mgmt_key_info { + bdaddr_t bdaddr; + u8 addr_type; + u8 key_type; + u8 val[16]; + u8 pin_len; + u8 auth; + u8 dlen; + u8 data[10]; } __packed; -#define MGMT_LOAD_LINK_KEYS_SIZE 3 -struct mgmt_ltk_info { - struct mgmt_addr_info addr; - __u8 authenticated; - __u8 master; - __u8 enc_size; - __le16 ediv; - __u8 rand[8]; - __u8 val[16]; +#define MGMT_OP_LOAD_KEYS 0x000D +struct mgmt_cp_load_keys { + __u8 debug_keys; + __le16 key_count; + struct mgmt_key_info keys[0]; } __packed; -#define MGMT_OP_LOAD_LONG_TERM_KEYS 0x0013 -struct mgmt_cp_load_long_term_keys { - __le16 key_count; - struct mgmt_ltk_info keys[0]; +#define MGMT_OP_REMOVE_KEY 0x000E +struct mgmt_cp_remove_key { + bdaddr_t bdaddr; + __u8 disconnect; } __packed; -#define MGMT_LOAD_LONG_TERM_KEYS_SIZE 2 -#define MGMT_OP_DISCONNECT 0x0014 +#define MGMT_OP_DISCONNECT 0x000F struct mgmt_cp_disconnect { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; } __packed; -#define MGMT_DISCONNECT_SIZE MGMT_ADDR_INFO_SIZE struct mgmt_rp_disconnect { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; } __packed; -#define MGMT_OP_GET_CONNECTIONS 0x0015 -#define MGMT_GET_CONNECTIONS_SIZE 0 +#define MGMT_OP_GET_CONNECTIONS 0x0010 struct mgmt_rp_get_connections { __le16 conn_count; - struct mgmt_addr_info addr[0]; + bdaddr_t conn[0]; } __packed; -#define MGMT_OP_PIN_CODE_REPLY 0x0016 +#define MGMT_OP_PIN_CODE_REPLY 0x0011 struct mgmt_cp_pin_code_reply { - struct mgmt_addr_info addr; - __u8 pin_len; - __u8 pin_code[16]; + bdaddr_t bdaddr; + __u8 pin_len; + __u8 pin_code[16]; } __packed; -#define MGMT_PIN_CODE_REPLY_SIZE (MGMT_ADDR_INFO_SIZE + 17) struct mgmt_rp_pin_code_reply { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; + uint8_t status; } __packed; -#define MGMT_OP_PIN_CODE_NEG_REPLY 0x0017 +#define MGMT_OP_PIN_CODE_NEG_REPLY 0x0012 struct mgmt_cp_pin_code_neg_reply { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; } __packed; -#define MGMT_PIN_CODE_NEG_REPLY_SIZE MGMT_ADDR_INFO_SIZE -#define MGMT_OP_SET_IO_CAPABILITY 0x0018 +#define MGMT_OP_SET_IO_CAPABILITY 0x0013 struct mgmt_cp_set_io_capability { - __u8 io_capability; + __u8 io_capability; } __packed; -#define MGMT_SET_IO_CAPABILITY_SIZE 1 -#define MGMT_OP_PAIR_DEVICE 0x0019 +#define MGMT_OP_PAIR_DEVICE 0x0014 struct mgmt_cp_pair_device { - struct mgmt_addr_info addr; - __u8 io_cap; + bdaddr_t bdaddr; + __u8 io_cap; } __packed; -#define MGMT_PAIR_DEVICE_SIZE (MGMT_ADDR_INFO_SIZE + 1) struct mgmt_rp_pair_device { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; + __u8 status; } __packed; -#define MGMT_OP_CANCEL_PAIR_DEVICE 0x001A -#define MGMT_CANCEL_PAIR_DEVICE_SIZE MGMT_ADDR_INFO_SIZE - -#define MGMT_OP_UNPAIR_DEVICE 0x001B -struct mgmt_cp_unpair_device { - struct mgmt_addr_info addr; - __u8 disconnect; -} __packed; -#define MGMT_UNPAIR_DEVICE_SIZE (MGMT_ADDR_INFO_SIZE + 1) -struct mgmt_rp_unpair_device { - struct mgmt_addr_info addr; -}; - -#define MGMT_OP_USER_CONFIRM_REPLY 0x001C +#define MGMT_OP_USER_CONFIRM_REPLY 0x0015 struct mgmt_cp_user_confirm_reply { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; } __packed; -#define MGMT_USER_CONFIRM_REPLY_SIZE MGMT_ADDR_INFO_SIZE struct mgmt_rp_user_confirm_reply { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; + __u8 status; } __packed; -#define MGMT_OP_USER_CONFIRM_NEG_REPLY 0x001D -struct mgmt_cp_user_confirm_neg_reply { - struct mgmt_addr_info addr; -} __packed; -#define MGMT_USER_CONFIRM_NEG_REPLY_SIZE MGMT_ADDR_INFO_SIZE +#define MGMT_OP_USER_CONFIRM_NEG_REPLY 0x0016 -#define MGMT_OP_USER_PASSKEY_REPLY 0x001E -struct mgmt_cp_user_passkey_reply { - struct mgmt_addr_info addr; - __le32 passkey; -} __packed; -#define MGMT_USER_PASSKEY_REPLY_SIZE (MGMT_ADDR_INFO_SIZE + 4) -struct mgmt_rp_user_passkey_reply { - struct mgmt_addr_info addr; -} __packed; - -#define MGMT_OP_USER_PASSKEY_NEG_REPLY 0x001F -struct mgmt_cp_user_passkey_neg_reply { - struct mgmt_addr_info addr; +#define MGMT_OP_SET_LOCAL_NAME 0x0017 +struct mgmt_cp_set_local_name { + __u8 name[MGMT_MAX_NAME_LENGTH]; } __packed; -#define MGMT_USER_PASSKEY_NEG_REPLY_SIZE MGMT_ADDR_INFO_SIZE -#define MGMT_OP_READ_LOCAL_OOB_DATA 0x0020 -#define MGMT_READ_LOCAL_OOB_DATA_SIZE 0 +#define MGMT_OP_READ_LOCAL_OOB_DATA 0x0018 struct mgmt_rp_read_local_oob_data { - __u8 hash[16]; - __u8 randomizer[16]; + __u8 hash[16]; + __u8 randomizer[16]; } __packed; -#define MGMT_OP_ADD_REMOTE_OOB_DATA 0x0021 +#define MGMT_OP_ADD_REMOTE_OOB_DATA 0x0019 struct mgmt_cp_add_remote_oob_data { - struct mgmt_addr_info addr; - __u8 hash[16]; - __u8 randomizer[16]; + bdaddr_t bdaddr; + __u8 hash[16]; + __u8 randomizer[16]; } __packed; -#define MGMT_ADD_REMOTE_OOB_DATA_SIZE (MGMT_ADDR_INFO_SIZE + 32) -#define MGMT_OP_REMOVE_REMOTE_OOB_DATA 0x0022 +#define MGMT_OP_REMOVE_REMOTE_OOB_DATA 0x001A struct mgmt_cp_remove_remote_oob_data { - struct mgmt_addr_info addr; + bdaddr_t bdaddr; } __packed; -#define MGMT_REMOVE_REMOTE_OOB_DATA_SIZE MGMT_ADDR_INFO_SIZE -#define MGMT_OP_START_DISCOVERY 0x0023 -struct mgmt_cp_start_discovery { - __u8 type; +#define MGMT_OP_START_DISCOVERY 0x001B + +#define MGMT_OP_STOP_DISCOVERY 0x001C + +#define MGMT_OP_USER_PASSKEY_REPLY 0x001D +struct mgmt_cp_user_passkey_reply { + bdaddr_t bdaddr; + __le32 passkey; } __packed; -#define MGMT_START_DISCOVERY_SIZE 1 -#define MGMT_OP_STOP_DISCOVERY 0x0024 -struct mgmt_cp_stop_discovery { - __u8 type; +#define MGMT_OP_RESOLVE_NAME 0x001E +struct mgmt_cp_resolve_name { + bdaddr_t bdaddr; } __packed; -#define MGMT_STOP_DISCOVERY_SIZE 1 -#define MGMT_OP_CONFIRM_NAME 0x0025 -struct mgmt_cp_confirm_name { - struct mgmt_addr_info addr; - __u8 name_known; +#define MGMT_OP_SET_LIMIT_DISCOVERABLE 0x001F + +#define MGMT_OP_SET_CONNECTION_PARAMS 0x0020 +struct mgmt_cp_set_connection_params { + bdaddr_t bdaddr; + __le16 interval_min; + __le16 interval_max; + __le16 slave_latency; + __le16 timeout_multiplier; } __packed; -#define MGMT_CONFIRM_NAME_SIZE (MGMT_ADDR_INFO_SIZE + 1) -struct mgmt_rp_confirm_name { - struct mgmt_addr_info addr; + +#define MGMT_OP_ENCRYPT_LINK 0x0021 +struct mgmt_cp_encrypt_link { + bdaddr_t bdaddr; + __u8 enable; } __packed; -#define MGMT_OP_BLOCK_DEVICE 0x0026 -struct mgmt_cp_block_device { - struct mgmt_addr_info addr; +#define MGMT_OP_SET_RSSI_REPORTER 0x0022 +struct mgmt_cp_set_rssi_reporter { + bdaddr_t bdaddr; + __s8 rssi_threshold; + __le16 interval; + __u8 updateOnThreshExceed; } __packed; -#define MGMT_BLOCK_DEVICE_SIZE MGMT_ADDR_INFO_SIZE -#define MGMT_OP_UNBLOCK_DEVICE 0x0027 -struct mgmt_cp_unblock_device { - struct mgmt_addr_info addr; +#define MGMT_OP_UNSET_RSSI_REPORTER 0x0023 +struct mgmt_cp_unset_rssi_reporter { + bdaddr_t bdaddr; } __packed; -#define MGMT_UNBLOCK_DEVICE_SIZE MGMT_ADDR_INFO_SIZE #define MGMT_EV_CMD_COMPLETE 0x0001 struct mgmt_ev_cmd_complete { - __le16 opcode; - __u8 status; - __u8 data[0]; + __le16 opcode; + __u8 data[0]; } __packed; #define MGMT_EV_CMD_STATUS 0x0002 struct mgmt_ev_cmd_status { - __le16 opcode; - __u8 status; + __u8 status; + __le16 opcode; } __packed; #define MGMT_EV_CONTROLLER_ERROR 0x0003 struct mgmt_ev_controller_error { - __u8 error_code; + __u8 error_code; } __packed; #define MGMT_EV_INDEX_ADDED 0x0004 #define MGMT_EV_INDEX_REMOVED 0x0005 -#define MGMT_EV_NEW_SETTINGS 0x0006 +#define MGMT_EV_POWERED 0x0006 -#define MGMT_EV_CLASS_OF_DEV_CHANGED 0x0007 -struct mgmt_ev_class_of_dev_changed { - __u8 dev_class[3]; -}; +#define MGMT_EV_DISCOVERABLE 0x0007 -#define MGMT_EV_LOCAL_NAME_CHANGED 0x0008 -struct mgmt_ev_local_name_changed { - __u8 name[MGMT_MAX_NAME_LENGTH]; - __u8 short_name[MGMT_MAX_SHORT_NAME_LENGTH]; -} __packed; +#define MGMT_EV_CONNECTABLE 0x0008 -#define MGMT_EV_NEW_LINK_KEY 0x0009 -struct mgmt_ev_new_link_key { - __u8 store_hint; - struct mgmt_link_key_info key; -} __packed; +#define MGMT_EV_PAIRABLE 0x0009 -#define MGMT_EV_NEW_LONG_TERM_KEY 0x000A -struct mgmt_ev_new_long_term_key { - __u8 store_hint; - struct mgmt_ltk_info key; +#define MGMT_EV_NEW_KEY 0x000A +struct mgmt_ev_new_key { + __u8 store_hint; + struct mgmt_key_info key; } __packed; -#define MGMT_EV_DEVICE_CONNECTED 0x000B -struct mgmt_ev_device_connected { - struct mgmt_addr_info addr; - __le32 flags; - __le16 eir_len; - __u8 eir[0]; +#define MGMT_EV_CONNECTED 0x000B +struct mgmt_ev_connected { + bdaddr_t bdaddr; + __u8 le; } __packed; -#define MGMT_EV_DEVICE_DISCONNECTED 0x000C +#define MGMT_EV_DISCONNECTED 0x000C +struct mgmt_ev_disconnected { + bdaddr_t bdaddr; +} __packed; #define MGMT_EV_CONNECT_FAILED 0x000D struct mgmt_ev_connect_failed { - struct mgmt_addr_info addr; - __u8 status; + bdaddr_t bdaddr; + __u8 status; } __packed; #define MGMT_EV_PIN_CODE_REQUEST 0x000E struct mgmt_ev_pin_code_request { - struct mgmt_addr_info addr; - __u8 secure; + bdaddr_t bdaddr; + __u8 secure; } __packed; #define MGMT_EV_USER_CONFIRM_REQUEST 0x000F struct mgmt_ev_user_confirm_request { - struct mgmt_addr_info addr; - __u8 confirm_hint; - __le32 value; + bdaddr_t bdaddr; + __u8 auto_confirm; + __u8 event; + __le32 value; } __packed; -#define MGMT_EV_USER_PASSKEY_REQUEST 0x0010 -struct mgmt_ev_user_passkey_request { - struct mgmt_addr_info addr; -} __packed; - -#define MGMT_EV_AUTH_FAILED 0x0011 +#define MGMT_EV_AUTH_FAILED 0x0010 struct mgmt_ev_auth_failed { - struct mgmt_addr_info addr; - __u8 status; + bdaddr_t bdaddr; + __u8 status; } __packed; -#define MGMT_DEV_FOUND_CONFIRM_NAME 0x01 -#define MGMT_DEV_FOUND_LEGACY_PAIRING 0x02 +#define MGMT_EV_LOCAL_NAME_CHANGED 0x0011 +struct mgmt_ev_local_name_changed { + __u8 name[MGMT_MAX_NAME_LENGTH]; +} __packed; #define MGMT_EV_DEVICE_FOUND 0x0012 struct mgmt_ev_device_found { - struct mgmt_addr_info addr; - __s8 rssi; - __u8 flags[4]; - __le16 eir_len; - __u8 eir[0]; + bdaddr_t bdaddr; + __u8 dev_class[3]; + __s8 rssi; + __u8 le; + __u8 type; + __u8 eir[HCI_MAX_EIR_LENGTH]; } __packed; -#define MGMT_EV_DISCOVERING 0x0013 -struct mgmt_ev_discovering { - __u8 type; - __u8 discovering; +#define MGMT_EV_REMOTE_NAME 0x0013 +struct mgmt_ev_remote_name { + bdaddr_t bdaddr; + __u8 status; + __u8 name[MGMT_MAX_NAME_LENGTH]; } __packed; -#define MGMT_EV_DEVICE_BLOCKED 0x0014 -struct mgmt_ev_device_blocked { - struct mgmt_addr_info addr; +#define MGMT_EV_DISCOVERING 0x0014 + +#define MGMT_EV_USER_PASSKEY_REQUEST 0x0015 +struct mgmt_ev_user_passkey_request { + bdaddr_t bdaddr; } __packed; -#define MGMT_EV_DEVICE_UNBLOCKED 0x0015 -struct mgmt_ev_device_unblocked { - struct mgmt_addr_info addr; +#define MGMT_EV_ENCRYPT_CHANGE 0x0016 +struct mgmt_ev_encrypt_change { + bdaddr_t bdaddr; + __u8 status; } __packed; -#define MGMT_EV_DEVICE_UNPAIRED 0x0016 -struct mgmt_ev_device_unpaired { - struct mgmt_addr_info addr; + +#define MGMT_EV_REMOTE_CLASS 0x0017 +struct mgmt_ev_remote_class { + bdaddr_t bdaddr; + __u8 dev_class[3]; +} __packed; + +#define MGMT_EV_REMOTE_VERSION 0x0018 +struct mgmt_ev_remote_version { + bdaddr_t bdaddr; + __u8 lmp_ver; + __u16 manufacturer; + __u16 lmp_subver; +} __packed; + +#define MGMT_EV_REMOTE_FEATURES 0x0019 +struct mgmt_ev_remote_features { + bdaddr_t bdaddr; + uint8_t features[8]; +} __packed; + +#define MGMT_EV_RSSI_UPDATE 0x0020 +struct mgmt_ev_rssi_update { + bdaddr_t bdaddr; + __s8 rssi; } __packed; diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index e2e3ecad100830a6cb42765c65ca804304eb5fca..6eac4a760c3be903d1f7feb7bad94b08d2a408e5 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -211,7 +211,6 @@ struct rfcomm_dlc { #define RFCOMM_AUTH_ACCEPT 6 #define RFCOMM_AUTH_REJECT 7 #define RFCOMM_DEFER_SETUP 8 -#define RFCOMM_ENC_DROP 9 /* Scheduling flags and events */ #define RFCOMM_SCHED_WAKEUP 31 @@ -235,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); @@ -273,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) { @@ -315,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/net/bluetooth/sco.h b/include/net/bluetooth/sco.h index 6d1857ab8e5f710926885ebf579db91584b09360..160e3f0c71d04f7cd6a01c382bdaad668215e2e0 100644 --- a/include/net/bluetooth/sco.h +++ b/include/net/bluetooth/sco.h @@ -1,6 +1,7 @@ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2011, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -38,6 +39,7 @@ struct sockaddr_sco { sa_family_t sco_family; bdaddr_t sco_bdaddr; __u16 sco_pkt_type; + __s8 is_wbs; }; /* SCO socket options */ diff --git a/include/net/bluetooth/smp.h b/include/net/bluetooth/smp.h index 7b3acdd29134df26025d4a79460a4d113ad7dabe..71845ddc167aa34a0c8e97439962f9795981bd53 100644 --- a/include/net/bluetooth/smp.h +++ b/include/net/bluetooth/smp.h @@ -55,6 +55,13 @@ struct smp_cmd_pairing { #define SMP_AUTH_BONDING 0x01 #define SMP_AUTH_MITM 0x04 +#define SMP_JUST_WORKS 0x00 +#define SMP_JUST_CFM 0x01 +#define SMP_REQ_PASSKEY 0x02 +#define SMP_CFM_PASSKEY 0x03 +#define SMP_REQ_OOB 0x04 +#define SMP_OVERLAP 0xFF + #define SMP_CMD_PAIRING_CONFIRM 0x03 struct smp_cmd_pairing_confirm { __u8 confirm_val[16]; @@ -115,32 +122,10 @@ struct smp_cmd_security_req { #define SMP_MIN_ENC_KEY_SIZE 7 #define SMP_MAX_ENC_KEY_SIZE 16 -#define SMP_FLAG_TK_VALID 1 -#define SMP_FLAG_CFM_PENDING 2 -#define SMP_FLAG_MITM_AUTH 3 - -struct smp_chan { - struct l2cap_conn *conn; - u8 preq[7]; /* SMP Pairing Request */ - u8 prsp[7]; /* SMP Pairing Response */ - u8 prnd[16]; /* SMP Pairing Random (local) */ - u8 rrnd[16]; /* SMP Pairing Random (remote) */ - u8 pcnf[16]; /* SMP Pairing Confirm */ - u8 tk[16]; /* SMP Temporary Key */ - u8 enc_key_size; - unsigned long smp_flags; - struct crypto_blkcipher *tfm; - struct work_struct confirm; - struct work_struct random; - -}; - /* SMP Commands */ int smp_conn_security(struct l2cap_conn *conn, __u8 sec_level); int smp_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb); -int smp_distribute_keys(struct l2cap_conn *conn, __u8 force); -int smp_user_confirm_reply(struct hci_conn *conn, u16 mgmt_op, __le32 passkey); - -void smp_chan_destroy(struct l2cap_conn *conn); +int smp_link_encrypt_cmplt(struct l2cap_conn *conn, __u8 status, __u8 encrypt); +void smp_timeout(unsigned long l2cap_conn); #endif /* __SMP_H */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 83d800c31e3cf4b6a8829df75ff3d627ee826542..a57c9f99562bc3175967728439773fb37b5cc878 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -669,9 +669,7 @@ struct station_info { const u8 *assoc_req_ies; size_t assoc_req_ies_len; - u32 beacon_loss_count; - /* * Note: Add a new enum station_info_flags value for each new field and * use it to check which fields are initialized. diff --git a/include/net/transp_v6.h b/include/net/transp_v6.h index 498433dd067dd64eb6c728e566e61585093e960f..6f1470fadd05d7b84218ff128376f9ff2c0e8ef5 100644 --- a/include/net/transp_v6.h +++ b/include/net/transp_v6.h @@ -14,6 +14,8 @@ extern struct proto tcpv6_prot; struct flowi6; +extern void initialize_hashidentrnd(void); + /* extension headers */ extern int ipv6_exthdrs_init(void); extern void ipv6_exthdrs_exit(void); diff --git a/include/sound/Kbuild b/include/sound/Kbuild index 6df30ed1581c5b750f13335a3fd1b1743e05159e..739f289b99a350214b691156dc23063b33ed801b 100644 --- a/include/sound/Kbuild +++ b/include/sound/Kbuild @@ -8,3 +8,4 @@ header-y += sb16_csp.h header-y += sfnt_info.h header-y += compress_params.h header-y += compress_offload.h +header-y += tlv.h diff --git a/include/sound/apr_audio-v2.h b/include/sound/apr_audio-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..695fea9ad21da1838c3d0302d1f51faecd352311 --- /dev/null +++ b/include/sound/apr_audio-v2.h @@ -0,0 +1,6172 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY 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_V2_H_ +#define _APR_AUDIO_V2_H_ + +#include + +#define ADSP_ADM_VERSION 0x00070000 + +#define ADM_CMD_SHARED_MEM_MAP_REGIONS 0x00010322 +#define ADM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010323 +#define ADM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010324 + +#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325 + +/* Enumeration for an audio Rx matrix ID.*/ +#define ADM_MATRIX_ID_AUDIO_RX 0 + +#define ADM_MATRIX_ID_AUDIO_TX 1 + +/* Enumeration for an audio Tx matrix ID.*/ +#define ADM_MATRIX_ID_AUDIOX 1 + +#define ADM_MAX_COPPS 5 + + +/* Session map node structure. +* Immediately following this structure are num_copps +* entries of COPP IDs. The COPP IDs are 16 bits, so +* there might be a padding 16-bit field if num_copps +* is odd. +*/ +struct adm_session_map_node_v5 { + u16 session_id; +/* Handle of the ASM session to be routed. Supported values: 1 +* to 8. +*/ + + + u16 num_copps; + /* Number of COPPs to which this session is to be routed. + Supported values: 0 < num_copps <= ADM_MAX_COPPS. + */ +} __packed; + +/* Payload of the #ADM_CMD_MATRIX_MAP_ROUTINGS_V5 command. +* Immediately following this structure are num_sessions of the session map +* node payload (adm_session_map_node_v5). +*/ + +struct adm_cmd_matrix_map_routings_v5 { + struct apr_hdr hdr; + + u32 matrix_id; +/* Specifies whether the matrix ID is Audio Rx (0) or Audio Tx +* (1). Use the ADM_MATRIX_ID_AUDIO_RX or ADM_MATRIX_ID_AUDIOX +* macros to set this field. +*/ + u32 num_sessions; + /* Number of sessions being updated by this command (optional).*/ +} __packed; + +/* This command allows a client to open a COPP/Voice Proc. TX module +* and sets up the device session: Matrix -> COPP -> AFE on the RX +* and AFE -> COPP -> Matrix on the TX. This enables PCM data to +* be transferred to/from the endpoint (AFEPortID). +* +* @return +* #ADM_CMDRSP_DEVICE_OPEN_V5 with the resulting status and +* COPP ID. +*/ +#define ADM_CMD_DEVICE_OPEN_V5 0x00010326 + +/* Indicates that endpoint_id_2 is to be ignored.*/ +#define ADM_CMD_COPP_OPEN_END_POINT_ID_2_IGNORE 0xFFFF + +#define ADM_CMD_COPP_OPEN_MODE_OF_OPERATION_RX_PATH_COPP 1 + +#define ADM_CMD_COPP_OPEN_MODE_OF_OPERATIONX_PATH_LIVE_COPP 2 + +#define ADM_CMD_COPP_OPEN_MODE_OF_OPERATIONX_PATH_NON_LIVE_COPP 3 + +/* Indicates that an audio COPP is to send/receive a mono PCM + * stream to/from + * END_POINT_ID_1. + */ +#define ADM_CMD_COPP_OPEN_CHANNEL_CONFIG_MONO 1 + +/* Indicates that an audio COPP is to send/receive a + * stereo PCM stream to/from END_POINT_ID_1. + */ +#define ADM_CMD_COPP_OPEN_CHANNEL_CONFIG_STEREO 2 + +/* Sample rate is 8000 Hz.*/ +#define ADM_CMD_COPP_OPEN_SAMPLE_RATE_8K 8000 + +/* Sample rate is 16000 Hz.*/ +#define ADM_CMD_COPP_OPEN_SAMPLE_RATE_16K 16000 + +/* Sample rate is 48000 Hz.*/ +#define ADM_CMD_COPP_OPEN_SAMPLE_RATE_48K 48000 + +/* Definition for a COPP live input flag bitmask.*/ +#define ADM_BIT_MASK_COPP_LIVE_INPUT_FLAG (0x0001U) + +/* Definition for a COPP live shift value bitmask.*/ +#define ADM_SHIFT_COPP_LIVE_INPUT_FLAG 0 + +/* Definition for the COPP ID bitmask.*/ +#define ADM_BIT_MASK_COPP_ID (0x0000FFFFUL) + +/* Definition for the COPP ID shift value.*/ +#define ADM_SHIFT_COPP_ID 0 + +/* Definition for the service ID bitmask.*/ +#define ADM_BIT_MASK_SERVICE_ID (0x00FF0000UL) + +/* Definition for the service ID shift value.*/ +#define ADM_SHIFT_SERVICE_ID 16 + +/* Definition for the domain ID bitmask.*/ +#define ADM_BIT_MASK_DOMAIN_ID (0xFF000000UL) + +/* Definition for the domain ID shift value.*/ +#define ADM_SHIFT_DOMAIN_ID 24 + +/* ADM device open command payload of the + #ADM_CMD_DEVICE_OPEN_V5 command. +*/ +struct adm_cmd_device_open_v5 { + struct apr_hdr hdr; + u16 flags; +/* Reserved for future use. Clients must set this field + * to zero. + */ + + u16 mode_of_operation; +/* Specifies whether the COPP must be opened on the Tx or Rx + * path. Use the ADM_CMD_COPP_OPEN_MODE_OF_OPERATION_* macros for + * supported values and interpretation. + * Supported values: + * - 0x1 -- Rx path COPP + * - 0x2 -- Tx path live COPP + * - 0x3 -- Tx path nonlive COPP + * Live connections cause sample discarding in the Tx device + * matrix if the destination output ports do not pull them + * fast enough. Nonlive connections queue the samples + * indefinitely. + */ + + u16 endpoint_id_1; +/* Logical and physical endpoint ID of the audio path. + * If the ID is a voice processor Tx block, it receives near + * samples. Supported values: Any pseudoport, AFE Rx port, + * or AFE Tx port For a list of valid IDs, refer to + * @xhyperref{Q4,[Q4]}. + * Q4 = Hexagon Multimedia: AFE Interface Specification + */ + + u16 endpoint_id_2; +/* Logical and physical endpoint ID 2 for a voice processor + * Tx block. + * This is not applicable to audio COPP. + * Supported values: + * - AFE Rx port + * - 0xFFFF -- Endpoint 2 is unavailable and the voice + * processor Tx + * block ignores this endpoint + * When the voice processor Tx block is created on the audio + * record path, + * it can receive far-end samples from an AFE Rx port if the + * voice call + * is active. The ID of the AFE port is provided in this + * field. + * For a list of valid IDs, refer @xhyperref{Q4,[Q4]}. + */ + + u32 topology_id; + /* Audio COPP topology ID; 32-bit GUID. */ + + u16 dev_num_channel; +/* Number of channels the audio COPP sends to/receives from + * the endpoint. + * Supported values: 1 to 8. + * The value is ignored for the voice processor Tx block, + * where channel + * configuration is derived from the topology ID. + */ + + u16 bit_width; +/* Bit width (in bits) that the audio COPP sends to/receives + * from the + * endpoint. The value is ignored for the voice processing + * Tx block, + * where the PCM width is 16 bits. + */ + + u32 sample_rate; +/* Sampling rate at which the audio COPP/voice processor + * Tx block + * interfaces with the endpoint. + * Supported values for voice processor Tx: 8000, 16000, + * 48000 Hz + * Supported values for audio COPP: >0 and <=192 kHz + */ + + u8 dev_channel_mapping[8]; +/* Array of channel mapping of buffers that the audio COPP + * sends to the endpoint. Channel[i] mapping describes channel + * I inside the buffer, where 0 < i < dev_num_channel. + * This value is relevent only for an audio Rx COPP. + * For the voice processor block and Tx audio block, this field + * is set to zero and is ignored. + */ +} __packed; + +/* + * This command allows the client to close a COPP and disconnect + * the device session. + */ +#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327 + +/* Sets one or more parameters to a COPP. +*/ +#define ADM_CMD_SET_PP_PARAMS_V5 0x00010328 + +/* Payload of the #ADM_CMD_SET_PP_PARAMS_V5 command. + * If the data_payload_addr_lsw and data_payload_addr_msw element + * are NULL, a series of adm_param_datastructures immediately + * follows, whose total size is data_payload_size bytes. + */ +struct adm_cmd_set_pp_params_v5 { + struct apr_hdr hdr; + u32 data_payload_addr_lsw; + /* LSW of parameter data payload address.*/ + u32 data_payload_addr_msw; + /* MSW of parameter data payload address.*/ + + u32 mem_map_handle; +/* Memory map handle returned by ADM_CMD_SHARED_MEM_MAP_REGIONS + * command */ +/* If mem_map_handle is zero implies the message is in + * the payload */ + + u32 data_payload_size; +/* Size in bytes of the variable payload accompanying this + * message or + * in shared memory. This is used for parsing the parameter + * payload. + */ +} __packed; + +/* Payload format for COPP parameter data. + * Immediately following this structure are param_size bytes + * of parameter + * data. + */ +struct adm_param_data_v5 { + u32 module_id; + /* Unique ID of the module. */ + u32 param_id; + /* Unique ID of the parameter. */ + u16 param_size; + /* Data size of the param_id/module_id combination. + This value is a + multiple of 4 bytes. */ + u16 reserved; + /* Reserved for future enhancements. + * This field must be set to zero. + */ +} __packed; + +/* Returns the status and COPP ID to an #ADM_CMD_DEVICE_OPEN_V5 command. + */ +#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329 + +/* Payload of the #ADM_CMDRSP_DEVICE_OPEN_V5 message, + * which returns the + * status and COPP ID to an #ADM_CMD_DEVICE_OPEN_V5 command. + */ +struct adm_cmd_rsp_device_open_v5 { + u32 status; + /* Status message (error code).*/ + + u16 copp_id; + /* COPP ID: Supported values: 0 <= copp_id < ADM_MAX_COPPS*/ + + u16 reserved; + /* Reserved. This field must be set to zero.*/ +} __packed; + +/* This command allows a query of one COPP parameter. +*/ +#define ADM_CMD_GET_PP_PARAMS_V5 0x0001032A + +/* Payload an #ADM_CMD_GET_PP_PARAMS_V5 command. +*/ +struct adm_cmd_get_pp_params_v5 { + u32 data_payload_addr_lsw; + /* LSW of parameter data payload address.*/ + + u32 data_payload_addr_msw; + /* MSW of parameter data payload address.*/ + + /* If the mem_map_handle is non zero, + * on ACK, the ParamData payloads begin at + * the address specified (out-of-band). + */ + + u32 mem_map_handle; + /* Memory map handle returned + * by ADM_CMD_SHARED_MEM_MAP_REGIONS command. + * If the mem_map_handle is 0, it implies that + * the ACK's payload will contain the ParamData (in-band). + */ + + u32 module_id; + /* Unique ID of the module. */ + + u32 param_id; + /* Unique ID of the parameter. */ + + u16 param_max_size; + /* Maximum data size of the parameter + *ID/module ID combination. This + * field is a multiple of 4 bytes. + */ + u16 reserved; + /* Reserved for future enhancements. + * This field must be set to zero. + */ +} __packed; + +/* Returns parameter values + * in response to an #ADM_CMD_GET_PP_PARAMS_V5 command. + */ +#define ADM_CMDRSP_GET_PP_PARAMS_V5 0x0001032B + +/* Payload of the #ADM_CMDRSP_GET_PP_PARAMS_V5 message, + * which returns parameter values in response + * to an #ADM_CMD_GET_PP_PARAMS_V5 command. + * Immediately following this + * structure is the adm_param_data_v5 + * structure containing the pre/postprocessing + * parameter data. For an in-band + * scenario, the variable payload depends + * on the size of the parameter. +*/ +struct adm_cmd_rsp_get_pp_params_v5 { + u32 status; + /* Status message (error code).*/ +} __packed; + +/* Allows a client to control the gains on various session-to-COPP paths. + */ +#define ADM_CMD_MATRIX_RAMP_GAINS_V5 0x0001032C + +/* Indicates that the target gain in the + * current adm_session_copp_gain_v5 + * structure is to be applied to all + * the session-to-COPP paths that exist for + * the specified session. + */ +#define ADM_CMD_MATRIX_RAMP_GAINS_COPP_ID_ALL_CONNECTED_COPPS 0xFFFF + +/* Indicates that the target gain is + * to be immediately applied to the + * specified session-to-COPP path, + * without a ramping fashion. + */ +#define ADM_CMD_MATRIX_RAMP_GAINS_RAMP_DURATION_IMMEDIATE 0x0000 + +/* Enumeration for a linear ramping curve.*/ +#define ADM_CMD_MATRIX_RAMP_GAINS_RAMP_CURVE_LINEAR 0x0000 + +/* Payload of the #ADM_CMD_MATRIX_RAMP_GAINS_V5 command. + * Immediately following this structure are num_gains of the + * adm_session_copp_gain_v5structure. + */ +struct adm_cmd_matrix_ramp_gains_v5 { + u32 matrix_id; +/* Specifies whether the matrix ID is Audio Rx (0) or Audio Tx (1). + * Use the ADM_MATRIX_ID_AUDIO_RX or ADM_MATRIX_ID_AUDIOX + * macros to set this field. +*/ + + u16 num_gains; + /* Number of gains being applied. */ + + u16 reserved_for_align; + /* Reserved. This field must be set to zero.*/ +} __packed; + +/* Session-to-COPP path gain structure, used by the + * #ADM_CMD_MATRIX_RAMP_GAINS_V5 command. + * This structure specifies the target + * gain (per channel) that must be applied + * to a particular session-to-COPP path in + * the audio matrix. The structure can + * also be used to apply the gain globally + * to all session-to-COPP paths that + * exist for the given session. + * The aDSP uses device channel mapping to + * determine which channel gains to + * use from this command. For example, + * if the device is configured as stereo, + * the aDSP uses only target_gain_ch_1 and + * target_gain_ch_2, and it ignores + * the others. + */ +struct adm_session_copp_gain_v5 { + u16 session_id; +/* Handle of the ASM session. + * Supported values: 1 to 8. + */ + + u16 copp_id; +/* Handle of the COPP. Gain will be applied on the Session ID + * COPP ID path. + */ + + u16 ramp_duration; +/* Duration (in milliseconds) of the ramp over + * which target gains are + * to be applied. Use + * #ADM_CMD_MATRIX_RAMP_GAINS_RAMP_DURATION_IMMEDIATE + * to indicate that gain must be applied immediately. + */ + + u16 step_duration; +/* Duration (in milliseconds) of each step in the ramp. + * This parameter is ignored if ramp_duration is equal to + * #ADM_CMD_MATRIX_RAMP_GAINS_RAMP_DURATION_IMMEDIATE. + * Supported value: 1 + */ + + u16 ramp_curve; +/* Type of ramping curve. + * Supported value: #ADM_CMD_MATRIX_RAMP_GAINS_RAMP_CURVE_LINEAR + */ + + u16 reserved_for_align; + /* Reserved. This field must be set to zero. */ + + u16 target_gain_ch_1; + /* Target linear gain for channel 1 in Q13 format; */ + + u16 target_gain_ch_2; + /* Target linear gain for channel 2 in Q13 format; */ + + u16 target_gain_ch_3; + /* Target linear gain for channel 3 in Q13 format; */ + + u16 target_gain_ch_4; + /* Target linear gain for channel 4 in Q13 format; */ + + u16 target_gain_ch_5; + /* Target linear gain for channel 5 in Q13 format; */ + + u16 target_gain_ch_6; + /* Target linear gain for channel 6 in Q13 format; */ + + u16 target_gain_ch_7; + /* Target linear gain for channel 7 in Q13 format; */ + + u16 target_gain_ch_8; + /* Target linear gain for channel 8 in Q13 format; */ +} __packed; + +/* Allows to set mute/unmute on various session-to-COPP paths. + * For every session-to-COPP path (stream-device interconnection), + * mute/unmute can be set individually on the output channels. + */ +#define ADM_CMD_MATRIX_MUTE_V5 0x0001032D + +/* Indicates that mute/unmute in the + * current adm_session_copp_mute_v5structure + * is to be applied to all the session-to-COPP + * paths that exist for the specified session. + */ +#define ADM_CMD_MATRIX_MUTE_COPP_ID_ALL_CONNECTED_COPPS 0xFFFF + +/* Payload of the #ADM_CMD_MATRIX_MUTE_V5 command*/ +struct adm_cmd_matrix_mute_v5 { + u32 matrix_id; +/* Specifies whether the matrix ID is Audio Rx (0) or Audio Tx (1). + * Use the ADM_MATRIX_ID_AUDIO_RX or ADM_MATRIX_ID_AUDIOX + * macros to set this field. + */ + + u16 session_id; +/* Handle of the ASM session. + * Supported values: 1 to 8. + */ + + u16 copp_id; +/* Handle of the COPP. + * Use ADM_CMD_MATRIX_MUTE_COPP_ID_ALL_CONNECTED_COPPS + * to indicate that mute/unmute must be applied to + * all the COPPs connected to session_id. + * Supported values: + * - 0xFFFF -- Apply mute/unmute to all connected COPPs + * - Other values -- Valid COPP ID + */ + + u8 mute_flag_ch_1; + /* Mute flag for channel 1 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_2; + /* Mute flag for channel 2 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_3; + /* Mute flag for channel 3 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_4; + /* Mute flag for channel 4 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_5; + /* Mute flag for channel 5 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_6; + /* Mute flag for channel 6 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_7; + /* Mute flag for channel 7 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_8; + /* Mute flag for channel 8 is set to unmute (0) or mute (1). */ + + u16 ramp_duration; +/* Period (in milliseconds) over which the soft mute/unmute will be + * applied. + * Supported values: 0 (Default) to 0xFFFF + * The default of 0 means mute/unmute will be applied immediately. + */ + + u16 reserved_for_align; + /* Clients must set this field to zero.*/ +} __packed; + +/* Allows a client to connect the desired stream to + * the desired AFE port through the stream router + * + * This command allows the client to connect specified session to + * specified AFE port. This is used for compressed streams only + * opened using the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED or + * #ASM_STREAM_CMD_OPEN_READ_COMPRESSED command. + * + * @prerequisites + * Session ID and AFE Port ID must be valid. + * #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED or + * #ASM_STREAM_CMD_OPEN_READ_COMPRESSED + * must have been called on this session. + */ + +#define ADM_CMD_CONNECT_AFE_PORT_V5 0x0001032E +#define ADM_CMD_DISCONNECT_AFE_PORT_V5 0x0001032F +/* Enumeration for the Rx stream router ID.*/ +#define ADM_STRTR_ID_RX 0 +/* Enumeration for the Tx stream router ID.*/ +#define ADM_STRTR_IDX 1 + +/* Payload of the #ADM_CMD_CONNECT_AFE_PORT_V5 command.*/ +struct adm_cmd_connect_afe_port_v5 { + u8 mode; +/* ID of the stream router (RX/TX). Use the + * ADM_STRTR_ID_RX or ADM_STRTR_IDX macros + * to set this field. + */ + + u8 session_id; + /* Session ID of the stream to connect */ + + u16 afe_port_id; + /* Port ID of the AFE port to connect to.*/ + u32 num_channels; +/* Number of device channels + * Supported values: 2(Audio Sample Packet), + * 8 (HBR Audio Stream Sample Packet) + */ + + u32 sampling_rate; +/* Device sampling rate +* Supported values: Any +*/ +} __packed; + + +/* adsp_adm_api.h */ + + +/* 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 RT_PROXY_PORT_001_RX 0x2000 /* index = 30 */ +#define RT_PROXY_PORT_001_TX 0x2001 /* index = 31 */ + +#define AFE_PORT_INVALID 0xFFFF +#define SLIMBUS_INVALID AFE_PORT_INVALID + +#define AFE_PORT_CMD_START 0x000100ca + +#define AFE_EVENT_RTPORT_START 0 +#define AFE_EVENT_RTPORT_STOP 1 +#define AFE_EVENT_RTPORT_LOW_WM 2 +#define AFE_EVENT_RTPORT_HI_WM 3 + +#define ADSP_AFE_VERSION 0x00200000 + +/* Size of the range of port IDs for the audio interface. */ +#define AFE_PORT_ID_AUDIO_IF_PORT_RANGE_SIZE 0xF + +/* Size of the range of port IDs for internal BT-FM ports. */ +#define AFE_PORT_ID_INTERNAL_BT_FM_RANGE_SIZE 0x6 + +/* Size of the range of port IDs for SLIMbus® + * multichannel + * ports. + */ +#define AFE_PORT_ID_SLIMBUS_RANGE_SIZE 0xA + +/* Size of the range of port IDs for real-time proxy ports. */ +#define AFE_PORT_ID_RT_PROXY_PORT_RANGE_SIZE 0x2 + +/* Size of the range of port IDs for pseudoports. */ +#define AFE_PORT_ID_PSEUDOPORT_RANGE_SIZE 0x5 + +/* Start of the range of port IDs for the audio interface. */ +#define AFE_PORT_ID_AUDIO_IF_PORT_RANGE_START 0x1000 + +/* End of the range of port IDs for the audio interface. */ +#define AFE_PORT_ID_AUDIO_IF_PORT_RANGE_END \ + (AFE_PORT_ID_AUDIO_IF_PORT_RANGE_START +\ + AFE_PORT_ID_AUDIO_IF_PORT_RANGE_SIZE - 1) + +/* Start of the range of port IDs for real-time proxy ports. */ +#define AFE_PORT_ID_RT_PROXY_PORT_RANGE_START 0x2000 + +/* End of the range of port IDs for real-time proxy ports. */ +#define AFE_PORT_ID_RT_PROXY_PORT_RANGE_END \ + (AFE_PORT_ID_RT_PROXY_PORT_RANGE_START +\ + AFE_PORT_ID_RT_PROXY_PORT_RANGE_SIZE-1) + +/* Start of the range of port IDs for internal BT-FM devices. */ +#define AFE_PORT_ID_INTERNAL_BT_FM_RANGE_START 0x3000 + +/* End of the range of port IDs for internal BT-FM devices. */ +#define AFE_PORT_ID_INTERNAL_BT_FM_RANGE_END \ + (AFE_PORT_ID_INTERNAL_BT_FM_RANGE_START +\ + AFE_PORT_ID_INTERNAL_BT_FM_RANGE_SIZE-1) + +/* Start of the range of port IDs for SLIMbus devices. */ +#define AFE_PORT_ID_SLIMBUS_RANGE_START 0x4000 + +/* End of the range of port IDs for SLIMbus devices. */ +#define AFE_PORT_ID_SLIMBUS_RANGE_END \ + (AFE_PORT_ID_SLIMBUS_RANGE_START +\ + AFE_PORT_ID_SLIMBUS_RANGE_SIZE-1) + +/* Start of the range of port IDs for pseudoports. */ +#define AFE_PORT_ID_PSEUDOPORT_RANGE_START 0x8001 + +/* End of the range of port IDs for pseudoports. */ +#define AFE_PORT_ID_PSEUDOPORT_RANGE_END \ + (AFE_PORT_ID_PSEUDOPORT_RANGE_START +\ + AFE_PORT_ID_PSEUDOPORT_RANGE_SIZE-1) + +#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 +#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 +#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 +#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 +#define AFE_PORT_IDERTIARY_MI2S_RX 0x1004 +#define AFE_PORT_IDERTIARY_MI2S_TX 0x1005 +#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 +#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 +#define AUDIO_PORT_ID_I2S_RX 0x1008 +#define AFE_PORT_ID_DIGITAL_MIC_TX 0x1009 +#define AFE_PORT_ID_PRIMARY_PCM_RX 0x100A +#define AFE_PORT_ID_PRIMARY_PCM_TX 0x100B +#define AFE_PORT_ID_SECONDARY_PCM_RX 0x100C +#define AFE_PORT_ID_SECONDARY_PCM_TX 0x100D +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E +#define AFE_PORT_ID_RT_PROXY_PORT_001_RX 0x2000 +#define AFE_PORT_ID_RT_PROXY_PORT_001_TX 0x2001 +#define AFE_PORT_ID_INTERNAL_BT_SCO_RX 0x3000 +#define AFE_PORT_ID_INTERNAL_BT_SCO_TX 0x3001 +#define AFE_PORT_ID_INTERNAL_BT_A2DP_RX 0x3002 +#define AFE_PORT_ID_INTERNAL_FM_RX 0x3004 +#define AFE_PORT_ID_INTERNAL_FM_TX 0x3005 +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* Generic pseudoport 1. */ +#define AFE_PORT_ID_PSEUDOPORT_01 0x8001 +/* Generic pseudoport 2. */ +#define AFE_PORT_ID_PSEUDOPORT_02 0x8002 + +/* @xreflabel{hdr:AfePortIdPrimaryAuxPcmTx} + Primary Aux PCM Tx port ID. +*/ +#define AFE_PORT_ID_PRIMARY_PCM_TX 0x100B +/* Pseudoport that corresponds to the voice Rx path. + * For recording, the voice Rx path samples are written to this + * port and consumed by the audio path. + */ + +#define AFE_PORT_ID_VOICE_RECORD_RX 0x8003 + +/* Pseudoport that corresponds to the voice Tx path. + * For recording, the voice Tx path samples are written to this + * port and consumed by the audio path. + */ + +#define AFE_PORT_ID_VOICE_RECORD_TX 0x8004 +/* Pseudoport that corresponds to in-call voice delivery samples. + * During in-call audio delivery, the audio path delivers samples + * to this port from where the voice path delivers them on the + * Rx path. + */ +#define AFE_PORT_ID_VOICE_PLAYBACK_TX 0x8005 +#define AFE_PORT_ID_INVALID 0xFFFF + +#define AAC_ENC_MODE_AAC_LC 0x02 +#define AAC_ENC_MODE_AAC_P 0x05 +#define AAC_ENC_MODE_EAAC_P 0x1D + +#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, */ +} __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; +} __packed; + + +#define AFE_MODULE_SIDETONE_IIR_FILTER 0x00010202 +#define AFE_PARAM_ID_ENABLE 0x00010203 + +/* Payload of the #AFE_PARAM_ID_ENABLE + * parameter, which enables or + * disables any module. + * The fixed size of this structure is four bytes. + */ + +struct afe_mod_enable_param { + u16 enable; + /* Enables (1) or disables (0) the module. */ + + u16 reserved; + /* This field must be set to zero. + */ +} __packed; + +/* ID of the configuration parameter used by the + * #AFE_MODULE_SIDETONE_IIR_FILTER module. + */ +#define AFE_PARAM_ID_SIDETONE_IIR_FILTER_CONFIG 0x00010204 + +struct afe_sidetone_iir_filter_config_params { + u16 num_biquad_stages; +/* Number of stages. + * Supported values: Minimum of 5 and maximum of 10 + */ + + u16 pregain; +/* Pregain for the compensating filter response. + * Supported values: Any number in Q13 format + */ +} __packed; + +#define AFE_MODULE_LOOPBACK 0x00010205 +#define AFE_PARAM_ID_LOOPBACK_GAIN_PER_PATH 0x00010206 + +/* Payload of the #AFE_PARAM_ID_LOOPBACK_GAIN_PER_PATH parameter, + * which gets/sets loopback gain of a port to an Rx port. + * The Tx port ID of the loopback is part of the set_param command. + */ + +/* Payload of the #AFE_PORT_CMD_SET_PARAM_V2 command's + * configuration/calibration settings for the AFE port. + */ +struct afe_port_cmd_set_param_v2 { + u16 port_id; +/* Port interface and direction (Rx or Tx) to start. + */ + + u16 payload_size; +/* Actual size of the payload in bytes. + * This is used for parsing the parameter payload. + * Supported values: > 0 + */ + +u32 payload_address_lsw; +/* LSW of 64 bit Payload address. + * Address should be 32-byte, + * 4kbyte aligned and must be contiguous memory. + */ + +u32 payload_address_msw; +/* MSW of 64 bit Payload address. + * In case of 32-bit shared memory address, + * this field must be set to zero. + * In case of 36-bit shared memory address, + * bit-4 to bit-31 must be set to zero. + * Address should be 32-byte, 4kbyte aligned + * and must be contiguous memory. + */ + +u32 mem_map_handle; +/* Memory map handle returned by + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS commands. + * Supported Values: + * - NULL -- Message. The parameter data is in-band. + * - Non-NULL -- The parameter data is Out-band.Pointer to + * the physical address + * in shared memory of the payload data. + * An optional field is available if parameter + * data is in-band: + * afe_param_data_v2 param_data[...]. + * For detailed payload content, see the + * afe_port_param_data_v2 structure. + */ +} __packed; + +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF + +struct afe_port_param_data_v2 { + u32 module_id; +/* ID of the module to be configured. + * Supported values: Valid module ID + */ + +u32 param_id; +/* ID of the parameter corresponding to the supported parameters + * for the module ID. + * Supported values: Valid parameter ID + */ + +u16 param_size; +/* Actual size of the data for the + * module_id/param_id pair. The size is a + * multiple of four bytes. + * Supported values: > 0 + */ + +u16 reserved; +/* This field must be set to zero. + */ +} __packed; + +struct afe_loopback_gain_per_path_param { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + u16 rx_port_id; +/* Rx port of the loopback. */ + +u16 gain; +/* Loopback gain per path of the port. + * Supported values: Any number in Q13 format + */ +} __packed; + +/* Parameter ID used to configure and enable/disable the + * loopback path. The difference with respect to the existing + * API, AFE_PORT_CMD_LOOPBACK, is that it allows Rx port to be + * configured as source port in loopback path. Port-id in + * AFE_PORT_CMD_SET_PARAM cmd is the source port whcih can be + * Tx or Rx port. In addition, we can configure the type of + * routing mode to handle different use cases. + */ +#define AFE_PARAM_ID_LOOPBACK_CONFIG 0x0001020B +#define AFE_API_VERSION_LOOPBACK_CONFIG 0x1 + +enum afe_loopback_routing_mode { + LB_MODE_DEFAULT = 1, + /* Regular loopback from source to destination port */ + LB_MODE_SIDETONE, + /* Sidetone feed from Tx source to Rx destination port */ + LB_MODE_EC_REF_VOICE_AUDIO, + /* Echo canceller reference, voice + audio + DTMF */ + LB_MODE_EC_REF_VOICE + /* Echo canceller reference, voice alone */ +} __packed; + +/* Payload of the #AFE_PARAM_ID_LOOPBACK_CONFIG , + * which enables/disables one AFE loopback. + */ +struct afe_loopback_cfg_v1 { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + u32 loopback_cfg_minor_version; +/* Minor version used for tracking the version of the RMC module + * configuration interface. + * Supported values: #AFE_API_VERSION_LOOPBACK_CONFIG + */ + u16 dst_port_id; + /* Destination Port Id. */ + u16 routing_mode; +/* Specifies data path type from src to dest port. + * Supported values: + * #LB_MODE_DEFAULT + * #LB_MODE_SIDETONE + * #LB_MODE_EC_REF_VOICE_AUDIO + * #LB_MODE_EC_REF_VOICE_A + * #LB_MODE_EC_REF_VOICE + */ + + u16 enable; +/* Specifies whether to enable (1) or + * disable (0) an AFE loopback. + */ + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0. + */ + +} __packed; + +#define AFE_MODULE_SPEAKER_PROTECTION 0x00010209 +#define AFE_PARAM_ID_SPKR_PROT_CONFIG 0x0001020a +#define AFE_API_VERSION_SPKR_PROT_CONFIG 0x1 +#define AFE_SPKR_PROT_EXCURSIONF_LEN 512 +struct afe_spkr_prot_cfg_param_v1 { + u32 spkr_prot_minor_version; +/* + * Minor version used for tracking the version of the + * speaker protection module configuration interface. + * Supported values: #AFE_API_VERSION_SPKR_PROT_CONFIG + */ + +int16_t win_size; +/* Analysis and synthesis window size (nWinSize). + * Supported values: 1024, 512, 256 samples + */ + +int16_t margin; +/* Allowable margin for excursion prediction, + * in L16Q15 format. This is a + * control parameter to allow + * for overestimation of peak excursion. + */ + +int16_t spkr_exc_limit; +/* Speaker excursion limit, in L16Q15 format.*/ + +int16_t spkr_resonance_freq; +/* Resonance frequency of the speaker; used + * to define a frequency range + * for signal modification. + * + * Supported values: 0 to 2000 Hz */ + +int16_t limhresh; +/* Threshold of the hard limiter; used to + * prevent overshooting beyond a + * signal level that was set by the limiter + * prior to speaker protection. + * Supported values: 0 to 32767 + */ + +int16_t hpf_cut_off_freq; +/* High pass filter cutoff frequency. + * Supported values: 100, 200, 300 Hz + */ + +int16_t hpf_enable; +/* Specifies whether the high pass filter + * is enabled (0) or disabled (1). + */ + +int16_t reserved; +/* This field must be set to zero. */ + +int32_t amp_gain; +/* Amplifier gain in L32Q15 format. + * This is the RMS voltage at the + * loudspeaker when a 0dBFS tone + * is played in the digital domain. + */ + +int16_t excursionf[AFE_SPKR_PROT_EXCURSIONF_LEN]; +/* Array of the excursion transfer function. + * The peak excursion of the + * loudspeaker diaphragm is + * measured in millimeters for 1 Vrms Sine + * tone at all FFT bin frequencies. + * Supported values: Q15 format + */ +} __packed; + + +#define AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER 0x000100E0 + +/* Payload of the #AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER + * command, which registers a real-time port driver + * with the AFE service. + */ +struct afe_service_cmd_register_rt_port_driver { + struct apr_hdr hdr; + u16 port_id; +/* Port ID with which the real-time driver exchanges data + * (registers for events). + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +#define AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER 0x000100E1 + +/* Payload of the #AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER + * command, which unregisters a real-time port driver from + * the AFE service. + */ +struct afe_service_cmd_unregister_rt_port_driver { + struct apr_hdr hdr; + u16 port_id; +/* Port ID from which the real-time + * driver unregisters for events. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +#define AFE_EVENT_RT_PROXY_PORT_STATUS 0x00010105 +#define AFE_EVENTYPE_RT_PROXY_PORT_START 0 +#define AFE_EVENTYPE_RT_PROXY_PORT_STOP 1 +#define AFE_EVENTYPE_RT_PROXY_PORT_LOW_WATER_MARK 2 +#define AFE_EVENTYPE_RT_PROXY_PORT_HIGH_WATER_MARK 3 +#define AFE_EVENTYPE_RT_PROXY_PORT_INVALID 0xFFFF + +/* Payload of the #AFE_EVENT_RT_PROXY_PORT_STATUS + * message, which sends an event from the AFE service + * to a registered client. + */ +struct afe_event_rt_proxy_port_status { + u16 port_id; +/* Port ID to which the event is sent. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 eventype; +/* Type of event. + * Supported values: + * - #AFE_EVENTYPE_RT_PROXY_PORT_START + * - #AFE_EVENTYPE_RT_PROXY_PORT_STOP + * - #AFE_EVENTYPE_RT_PROXY_PORT_LOW_WATER_MARK + * - #AFE_EVENTYPE_RT_PROXY_PORT_HIGH_WATER_MARK + */ +} __packed; + +#define AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2 0x000100ED + +struct afe_port_data_cmd_rt_proxy_port_write_v2 { + struct apr_hdr hdr; + u16 port_id; +/* Tx (mic) proxy port ID with which the real-time + * driver exchanges data. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 reserved; + /* This field must be set to zero. */ + + u32 buffer_address_lsw; +/* LSW Address of the buffer containing the + * data from the real-time source + * device on a client. + */ + + u32 buffer_address_msw; +/* MSW Address of the buffer containing the + * data from the real-time source + * device on a client. + */ + + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory + * attributes is returned if + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS + * command is successful. + * Supported Values: + * - Any 32 bit value + */ + + u32 available_bytes; +/* Number of valid bytes available + * in the buffer (including all + * channels: number of bytes per + * channel = availableBytesumChannels). + * Supported values: > 0 + * + * This field must be equal to the frame + * size specified in the #AFE_PORT_AUDIO_IF_CONFIG + * command that was sent to configure this + * port. + */ +} __packed; + +#define AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2 0x000100EE + +/* Payload of the + * #AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2 command, which + * delivers an empty buffer to the AFE service. On + * acknowledgment, data is filled in the buffer. + */ +struct afe_port_data_cmd_rt_proxy_port_read_v2 { + struct apr_hdr hdr; + u16 port_id; +/* Rx proxy port ID with which the real-time + * driver exchanges data. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + * (This must be an Rx (speaker) port.) + */ + + u16 reserved; + /* This field must be set to zero. */ + + u32 buffer_address_lsw; +/* LSW Address of the buffer containing the data sent from the AFE + * service to a real-time sink device on the client. + */ + + + u32 buffer_address_msw; +/* MSW Address of the buffer containing the data sent from the AFE + * service to a real-time sink device on the client. + */ + + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory attributes is + * returned if AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS command is + * successful. + * Supported Values: + * - Any 32 bit value + */ + + u32 available_bytes; +/* Number of valid bytes available in the buffer (including all + * channels). + * Supported values: > 0 + * This field must be equal to the frame size specified in the + * #AFE_PORT_AUDIO_IF_CONFIG command that was sent to configure + * this port. + */ +} __packed; + +/* This module ID is related to device configuring like I2S,PCM, + * HDMI, SLIMBus etc. This module supports follwing parameter ids. + * - #AFE_PARAM_ID_I2S_CONFIG + * - #AFE_PARAM_ID_PCM_CONFIG + * - #AFE_PARAM_ID_DIGI_MIC_CONFIG + * - #AFE_PARAM_ID_HDMI_CONFIG + * - #AFE_PARAM_ID_INTERNAL_BT_FM_CONFIG + * - #AFE_PARAM_ID_SLIMBUS_CONFIG + * - #AFE_PARAM_ID_RT_PROXY_CONFIG + */ + +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C +#define AFE_PORT_SAMPLE_RATE_8K 8000 +#define AFE_PORT_SAMPLE_RATE_16K 16000 +#define AFE_PORT_SAMPLE_RATE_48K 48000 +#define AFE_PORT_SAMPLE_RATE_96K 96000 +#define AFE_PORT_SAMPLE_RATE_192K 192000 +#define AFE_LINEAR_PCM_DATA 0x0 +#define AFE_NON_LINEAR_DATA 0x1 +#define AFE_LINEAR_PCM_DATA_PACKED_60958 0x2 +#define AFE_NON_LINEAR_DATA_PACKED_60958 0x3 + +/* This param id is used to configure I2S interface */ +#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D +#define AFE_API_VERSION_I2S_CONFIG 0x1 +/* Enumeration for setting the I2S configuration + * channel_mode parameter to + * serial data wire number 1-3 (SD3). + */ +#define AFE_PORT_I2S_SD0 0x1 +#define AFE_PORT_I2S_SD1 0x2 +#define AFE_PORT_I2S_SD2 0x3 +#define AFE_PORT_I2S_SD3 0x4 +#define AFE_PORT_I2S_QUAD01 0x5 +#define AFE_PORT_I2S_QUAD23 0x6 +#define AFE_PORT_I2S_6CHS 0x7 +#define AFE_PORT_I2S_8CHS 0x8 +#define AFE_PORT_I2S_MONO 0x0 +#define AFE_PORT_I2S_STEREO 0x1 +#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0 +#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 + +/* Payload of the #AFE_PARAM_ID_I2S_CONFIG + * command's (I2S configuration + * parameter). + */ +struct afe_param_id_i2s_cfg { + u32 i2s_cfg_minor_version; +/* Minor version used for tracking the version of the I2S + * configuration interface. + * Supported values: #AFE_API_VERSION_I2S_CONFIG + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + + u16 channel_mode; +/* I2S lines and multichannel operation. + * Supported values: + * - #AFE_PORT_I2S_SD0 + * - #AFE_PORT_I2S_SD1 + * - #AFE_PORT_I2S_SD2 + * - #AFE_PORT_I2S_SD3 + * - #AFE_PORT_I2S_QUAD01 + * - #AFE_PORT_I2S_QUAD23 + * - #AFE_PORT_I2S_6CHS + * - #AFE_PORT_I2S_8CHS + */ + + u16 mono_stereo; +/* Specifies mono or stereo. This applies only when + * a single I2S line is used. + * Supported values: + * - #AFE_PORT_I2S_MONO + * - #AFE_PORT_I2S_STEREO + */ + + u16 ws_src; +/* Word select source: internal or external. + * Supported values: + * - #AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL + * - #AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ + + u16 data_format; +/* data format + * Supported values: + * - #LINEAR_PCM_DATA + * - #NON_LINEAR_DATA + * - #LINEAR_PCM_DATA_PACKED_IN_60958 + * - #NON_LINEAR_DATA_PACKED_IN_60958 + */ + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +/* + * This param id is used to configure PCM interface + */ +#define AFE_PARAM_ID_PCM_CONFIG 0x0001020E +#define AFE_API_VERSION_PCM_CONFIG 0x1 +/* Enumeration for the auxiliary PCM synchronization signal + * provided by an external source. + */ + +#define AFE_PORT_PCM_SYNC_SRC_EXTERNAL 0x0 +/* Enumeration for the auxiliary PCM synchronization signal + * provided by an internal source. + */ +#define AFE_PORT_PCM_SYNC_SRC_INTERNAL 0x1 +/* Enumeration for the PCM configuration aux_mode parameter, + * which configures the auxiliary PCM interface to use + * short synchronization. + */ +#define AFE_PORT_PCM_AUX_MODE_PCM 0x0 +/* + * Enumeration for the PCM configuration aux_mode parameter, + * which configures the auxiliary PCM interface to use long + * synchronization. + */ +#define AFE_PORT_PCM_AUX_MODE_AUX 0x1 +/* + * Enumeration for setting the PCM configuration frame to 8. + */ +#define AFE_PORT_PCM_BITS_PER_FRAME_8 0x0 +/* + * Enumeration for setting the PCM configuration frame to 16. + */ +#define AFE_PORT_PCM_BITS_PER_FRAME_16 0x1 + +/* Enumeration for setting the PCM configuration frame to 32.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_32 0x2 + +/* Enumeration for setting the PCM configuration frame to 64.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_64 0x3 + +/* Enumeration for setting the PCM configuration frame to 128.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_128 0x4 + +/* Enumeration for setting the PCM configuration frame to 256.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_256 0x5 + +/* Enumeration for setting the PCM configuration + * quantype parameter to A-law with no padding. + */ +#define AFE_PORT_PCM_ALAW_NOPADDING 0x0 + +/* Enumeration for setting the PCM configuration quantype + * parameter to mu-law with no padding. + */ +#define AFE_PORT_PCM_MULAW_NOPADDING 0x1 +/* Enumeration for setting the PCM configuration quantype + * parameter to linear with no padding. + */ +#define AFE_PORT_PCM_LINEAR_NOPADDING 0x2 +/* Enumeration for setting the PCM configuration quantype + * parameter to A-law with padding. + */ +#define AFE_PORT_PCM_ALAW_PADDING 0x3 +/* Enumeration for setting the PCM configuration quantype + * parameter to mu-law with padding. + */ +#define AFE_PORT_PCM_MULAW_PADDING 0x4 +/* Enumeration for setting the PCM configuration quantype + * parameter to linear with padding. + */ +#define AFE_PORT_PCM_LINEAR_PADDING 0x5 +/* Enumeration for disabling the PCM configuration + * ctrl_data_out_enable parameter. + * The PCM block is the only master. + */ +#define AFE_PORT_PCM_CTRL_DATA_OE_DISABLE 0x0 +/* + * Enumeration for enabling the PCM configuration + * ctrl_data_out_enable parameter. The PCM block shares + * the signal with other masters. + */ +#define AFE_PORT_PCM_CTRL_DATA_OE_ENABLE 0x1 + +/* Payload of the #AFE_PARAM_ID_PCM_CONFIG command's + * (PCM configuration parameter). + */ + +struct afe_param_id_pcm_cfg { + u32 pcm_cfg_minor_version; +/* Minor version used for tracking the version of the AUX PCM + * configuration interface. + * Supported values: #AFE_API_VERSION_PCM_CONFIG + */ + + u16 aux_mode; +/* PCM synchronization setting. + * Supported values: + * - #AFE_PORT_PCM_AUX_MODE_PCM + * - #AFE_PORT_PCM_AUX_MODE_AUX + */ + + u16 sync_src; +/* Synchronization source. + * Supported values: + * - #AFE_PORT_PCM_SYNC_SRC_EXTERNAL + * - #AFE_PORT_PCM_SYNC_SRC_INTERNAL + */ + + u16 frame_setting; +/* Number of bits per frame. + * Supported values: + * - #AFE_PORT_PCM_BITS_PER_FRAME_8 + * - #AFE_PORT_PCM_BITS_PER_FRAME_16 + * - #AFE_PORT_PCM_BITS_PER_FRAME_32 + * - #AFE_PORT_PCM_BITS_PER_FRAME_64 + * - #AFE_PORT_PCM_BITS_PER_FRAME_128 + * - #AFE_PORT_PCM_BITS_PER_FRAME_256 + */ + + u16 quantype; +/* PCM quantization type. + * Supported values: + * - #AFE_PORT_PCM_ALAW_NOPADDING + * - #AFE_PORT_PCM_MULAW_NOPADDING + * - #AFE_PORT_PCM_LINEAR_NOPADDING + * - #AFE_PORT_PCM_ALAW_PADDING + * - #AFE_PORT_PCM_MULAW_PADDING + * - #AFE_PORT_PCM_LINEAR_PADDING + */ + + u16 ctrl_data_out_enable; +/* Specifies whether the PCM block shares the data-out + * signal to the drive with other masters. + * Supported values: + * - #AFE_PORT_PCM_CTRL_DATA_OE_DISABLE + * - #AFE_PORT_PCM_CTRL_DATA_OE_ENABLE + */ + u16 reserved; + /* This field must be set to zero. */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to 4 + */ + + u16 slot_number_mapping[4]; +/* Specifies the slot number for the each channel in + * multi channel scenario. + * Supported values: 1 to 32 + */ +} __packed; + +/* + * This param id is used to configure DIGI MIC interface + */ +#define AFE_PARAM_ID_DIGI_MIC_CONFIG 0x0001020F +/* This version information is used to handle the new + * additions to the config interface in future in backward + * compatible manner. + */ +#define AFE_API_VERSION_DIGI_MIC_CONFIG 0x1 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to left 0. + */ + +#define AFE_PORT_DIGI_MIC_MODE_LEFT0 0x1 + +/*Enumeration for setting the digital mic configuration + * channel_mode parameter to right 0. + */ + + +#define AFE_PORT_DIGI_MIC_MODE_RIGHT0 0x2 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to left 1. + */ + +#define AFE_PORT_DIGI_MIC_MODE_LEFT1 0x3 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to right 1. + */ + +#define AFE_PORT_DIGI_MIC_MODE_RIGHT1 0x4 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to stereo 0. + */ +#define AFE_PORT_DIGI_MIC_MODE_STEREO0 0x5 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to stereo 1. + */ + + +#define AFE_PORT_DIGI_MIC_MODE_STEREO1 0x6 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to quad. + */ + +#define AFE_PORT_DIGI_MIC_MODE_QUAD 0x7 + +/* Payload of the #AFE_PARAM_ID_DIGI_MIC_CONFIG command's + * (DIGI MIC configuration + * parameter). + */ +struct afe_param_id_digi_mic_cfg { + u32 digi_mic_cfg_minor_version; +/* Minor version used for tracking the version of the DIGI Mic + * configuration interface. + * Supported values: #AFE_API_VERSION_DIGI_MIC_CONFIG + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u16 channel_mode; +/* Digital mic and multichannel operation. + * Supported values: + * - #AFE_PORT_DIGI_MIC_MODE_LEFT0 + * - #AFE_PORT_DIGI_MIC_MODE_RIGHT0 + * - #AFE_PORT_DIGI_MIC_MODE_LEFT1 + * - #AFE_PORT_DIGI_MIC_MODE_RIGHT1 + * - #AFE_PORT_DIGI_MIC_MODE_STEREO0 + * - #AFE_PORT_DIGI_MIC_MODE_STEREO1 + * - #AFE_PORT_DIGI_MIC_MODE_QUAD + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + */ +} __packed; + +/* +* This param id is used to configure HDMI interface +*/ +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_HDMI_CONFIG 0x1 + +/* Payload of the #AFE_PARAM_ID_HDMI_CONFIG command, + * which configures a multichannel HDMI audio interface. + */ +struct afe_param_id_hdmi_multi_chan_audio_cfg { + u32 hdmi_cfg_minor_version; +/* Minor version used for tracking the version of the HDMI + * configuration interface. + * Supported values: #AFE_API_VERSION_HDMI_CONFIG + */ + +u16 dataype; +/* data type + * Supported values: + * - #LINEAR_PCM_DATA + * - #NON_LINEAR_DATA + * - #LINEAR_PCM_DATA_PACKED_IN_60958 + * - #NON_LINEAR_DATA_PACKED_IN_60958 + */ + +u16 channel_allocation; +/* HDMI channel allocation information for programming an HDMI + * frame. The default is 0 (Stereo). + * + * This information is defined in the HDMI standard, CEA 861-D + * (refer to @xhyperref{S1,[S1]}). The number of channels is also + * inferred from this parameter. +*/ + + +u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - 22050, 44100, 176400 for compressed streams + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +/* +* This param id is used to configure BT or FM(RIVA) interface +*/ +#define AFE_PARAM_ID_INTERNAL_BT_FM_CONFIG 0x00010211 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_INTERNAL_BT_FM_CONFIG 0x1 + +/* Payload of the #AFE_PARAM_ID_INTERNAL_BT_FM_CONFIG + * command's BT voice/BT audio/FM configuration parameter. + */ +struct afe_param_id_internal_bt_fm_cfg { + u32 bt_fm_cfg_minor_version; +/* Minor version used for tracking the version of the BT and FM + * configuration interface. + * Supported values: #AFE_API_VERSION_INTERNAL_BT_FM_CONFIG + */ + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to 2 + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K (only for BTSCO) + * - #AFE_PORT_SAMPLE_RATE_16K (only for BTSCO) + * - #AFE_PORT_SAMPLE_RATE_48K (FM and A2DP) + */ +} __packed; + +/* This param id is used to configure SLIMBUS interface using + * shared channel approach. + */ + + +#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 + +/* Enumeration for setting SLIMbus device ID 1. +*/ +#define AFE_SLIMBUS_DEVICE_1 0x0 + +/* Enumeration for setting SLIMbus device ID 2. +*/ +#define AFE_SLIMBUS_DEVICE_2 0x1 + +/* Enumeration for setting the SLIMbus data formats. +*/ +#define AFE_SB_DATA_FORMAT_NOT_INDICATED 0x0 + +/* Enumeration for setting the maximum number of streams per + * device. + */ + +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8 + +/* Payload of the #AFE_PORT_CMD_SLIMBUS_CONFIG command's SLIMbus + * port configuration parameter. + */ + +struct afe_param_id_slimbus_cfg { + u32 sb_cfg_minor_version; +/* Minor version used for tracking the version of the SLIMBUS + * configuration interface. + * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG + */ + + u16 slimbus_dev_id; +/* SLIMbus hardware device ID, which is required to handle + * multiple SLIMbus hardware blocks. + * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2 + */ + + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + + u16 data_format; +/* Data format supported by the SLIMbus hardware. The default is + * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the + * hardware does not perform any format conversions before the data + * transfer. + */ + + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + + u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +/* Mapping of shared channel IDs (128 to 255) to which the + * master port is to be connected. + * Shared_channel_mapping[i] represents the shared channel assigned + * for audio channel i in multichannel audio data. + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ +} __packed; + +/* +* This param id is used to configure Real Time Proxy interface. +*/ +#define AFE_PARAM_ID_RT_PROXY_CONFIG 0x00010213 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_RT_PROXY_CONFIG 0x1 + +/* Payload of the #AFE_PARAM_ID_RT_PROXY_CONFIG + * command (real-time proxy port configuration parameter). + */ +struct afe_param_id_rt_proxy_port_cfg { + u32 rt_proxy_cfg_minor_version; +/* Minor version used for tracking the version of rt-proxy + * config interface. + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u16 interleaved; +/* Specifies whether the data exchanged between the AFE + * interface and real-time port is interleaved. + * Supported values: - 0 -- Non-interleaved (samples from each + * channel are contiguous in the buffer) - 1 -- Interleaved + * (corresponding samples from each input channel are interleaved + * within the buffer) + */ + + + u16 frame_size; + /* Size of the frames that are used for PCM exchanges with this + * port. + * Supported values: > 0, in bytes + * For example, 5 ms buffers of 16 bits and 16 kHz stereo samples + * is 5 ms * 16 samples/ms * 2 bytes/sample * 2 channels = 320 + * bytes. + */ + u16 jitter_allowance; +/* Configures the amount of jitter that the port will allow. + * Supported values: > 0 + * For example, if +/-10 ms of jitter is anticipated in the timing + * of sending frames to the port, and the configuration is 16 kHz + * mono with 16-bit samples, this field is 10 ms * 16 samples/ms * 2 + * bytes/sample = 320. + */ + + u16 low_water_mark; +/* Low watermark in bytes (including all channels). + * Supported values: + * - 0 -- Do not send any low watermark events + * - > 0 -- Low watermark for triggering an event + * If the number of bytes in an internal circular buffer is lower + * than this low_water_mark parameter, a LOW_WATER_MARK event is + * sent to applications (via the #AFE_EVENT_RT_PROXY_PORT_STATUS + * event). + * Use of watermark events is optional for debugging purposes. + */ + + u16 high_water_mark; +/* High watermark in bytes (including all channels). + * Supported values: + * - 0 -- Do not send any high watermark events + * - > 0 -- High watermark for triggering an event + * If the number of bytes in an internal circular buffer exceeds + * TOTAL_CIRC_BUF_SIZE minus high_water_mark, a high watermark event + * is sent to applications (via the #AFE_EVENT_RT_PROXY_PORT_STATUS + * event). + * The use of watermark events is optional and for debugging + * purposes. + */ + + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + */ + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + + u16 reserved; + /* For 32 bit alignment. */ +} __packed; + +union afe_port_config { + struct afe_param_id_pcm_cfg pcm; + struct afe_param_id_i2s_cfg i2s; + struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; + struct afe_param_id_slimbus_cfg slim_sch; + struct afe_param_id_rt_proxy_port_cfg rtproxy; +} __packed; + +struct afe_audioif_config_command { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + union afe_port_config port; +} __packed; + +#define AFE_PORT_CMD_DEVICE_START 0x000100E5 + +/* Payload of the #AFE_PORT_CMD_DEVICE_START.*/ +struct afe_port_cmd_device_start { + struct apr_hdr hdr; + u16 port_id; +/* Port interface and direction (Rx or Tx) to start. An even + * number represents the Rx direction, and an odd number represents + * the Tx direction. + */ + + + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ + +} __packed; + +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6 + +/* Payload of the #AFE_PORT_CMD_DEVICE_STOP. +*/ +struct afe_port_cmd_device_stop { + struct apr_hdr hdr; + u16 port_id; +/* Port interface and direction (Rx or Tx) to start. An even + * number represents the Rx direction, and an odd number represents + * the Tx direction. + */ + + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ +} __packed; + +#define AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS 0x000100EA + +/* Memory map regions command payload used by the + * #AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS . + * This structure allows clients to map multiple shared memory + * regions in a single command. Following this structure are + * num_regions of afe_service_shared_map_region_payload. + */ +struct afe_service_cmd_shared_mem_map_regions { + struct apr_hdr hdr; +u16 mem_pool_id; +/* Type of memory on which this memory region is mapped. + * Supported values: + * - #ADSP_MEMORY_MAP_EBI_POOL + * - #ADSP_MEMORY_MAP_SMI_POOL + * - #ADSP_MEMORY_MAP_SHMEM8_4K_POOL + * - Other values are reserved + * + * The memory pool ID implicitly defines the characteristics of the + * memory. Characteristics may include alignment type, permissions, + * etc. + * + * ADSP_MEMORY_MAP_EBI_POOL is External Buffer Interface type memory + * ADSP_MEMORY_MAP_SMI_POOL is Shared Memory Interface type memory + * ADSP_MEMORY_MAP_SHMEM8_4K_POOL is shared memory, byte + * addressable, and 4 KB aligned. + */ + + + u16 num_regions; +/* Number of regions to map. + * Supported values: + * - Any value greater than zero + */ + + u32 property_flag; +/* Configures one common property for all the regions in the + * payload. + * + * Supported values: - 0x00000000 to 0x00000001 + * + * b0 - bit 0 indicates physical or virtual mapping 0 Shared memory + * address provided in afe_service_shared_map_region_payloadis a + * physical address. The shared memory needs to be mapped( hardware + * TLB entry) and a software entry needs to be added for internal + * book keeping. + * + * 1 Shared memory address provided in + * afe_service_shared_map_region_payloadis a virtual address. The + * shared memory must not be mapped (since hardware TLB entry is + * already available) but a software entry needs to be added for + * internal book keeping. This can be useful if two services with in + * ADSP is communicating via APR. They can now directly communicate + * via the Virtual address instead of Physical address. The virtual + * regions must be contiguous. num_regions must be 1 in this case. + * + * b31-b1 - reserved bits. must be set to zero + */ + + +} __packed; +/* Map region payload used by the + * afe_service_shared_map_region_payloadstructure. + */ +struct afe_service_shared_map_region_payload { + u32 shm_addr_lsw; +/* least significant word of starting address in the memory + * region to map. It must be contiguous memory, and it must be 4 KB + * aligned. + * Supported values: - Any 32 bit value + */ + + + u32 shm_addr_msw; +/* most significant word of startng address in the memory region + * to map. For 32 bit shared memory address, this field must be set + * to zero. For 36 bit shared memory address, bit31 to bit 4 must be + * set to zero + * + * Supported values: - For 32 bit shared memory address, this field + * must be set to zero. - For 36 bit shared memory address, bit31 to + * bit 4 must be set to zero - For 64 bit shared memory address, any + * 32 bit value + */ + + + u32 mem_size_bytes; +/* Number of bytes in the region. The aDSP will always map the + * regions as virtual contiguous memory, but the memory size must be + * in multiples of 4 KB to avoid gaps in the virtually contiguous + * mapped memory. + * + * Supported values: - multiples of 4KB + */ + +} __packed; + +#define AFE_SERVICE_CMDRSP_SHARED_MEM_MAP_REGIONS 0x000100EB +struct afe_service_cmdrsp_shared_mem_map_regions { + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory attributes is + * returned iff AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS command is + * successful. In the case of failure , a generic APR error response + * is returned to the client. + * + * Supported Values: - Any 32 bit value + */ + +} __packed; +#define AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS 0x000100EC +/* Memory unmap regions command payload used by the + * #AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS + * + * This structure allows clients to unmap multiple shared memory + * regions in a single command. + */ + + +struct afe_service_cmd_shared_mem_unmap_regions { + struct apr_hdr hdr; +u32 mem_map_handle; +/* memory map handle returned by + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS commands + * + * Supported Values: + * - Any 32 bit value + */ +} __packed; + +#define AFE_PORT_CMD_GET_PARAM_V2 0x000100F0 + +/* Payload of the #AFE_PORT_CMD_GET_PARAM_V2 command, + * which queries for one post/preprocessing parameter of a + * stream. + */ +struct afe_port_cmd_get_param_v2 { + + struct apr_hdr hdr; +u16 port_id; +/* Port interface and direction (Rx or Tx) to start. */ + + u16 payload_size; +/* Maximum data size of the parameter ID/module ID combination. + * This is a multiple of four bytes + * Supported values: > 0 + */ + + u32 payload_address_lsw; +/* LSW of 64 bit Payload address. Address should be 32-byte, + * 4kbyte aligned and must be contig memory. + */ + + + u32 payload_address_msw; +/* MSW of 64 bit Payload address. In case of 32-bit shared + * memory address, this field must be set to zero. In case of 36-bit + * shared memory address, bit-4 to bit-31 must be set to zero. + * Address should be 32-byte, 4kbyte aligned and must be contiguous + * memory. + */ + + u32 mem_map_handle; +/* Memory map handle returned by + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS commands. + * Supported Values: - NULL -- Message. The parameter data is + * in-band. - Non-NULL -- The parameter data is Out-band.Pointer to + * - the physical address in shared memory of the payload data. + * For detailed payload content, see the afe_port_param_data_v2 + * structure + */ + + + u32 module_id; +/* ID of the module to be queried. + * Supported values: Valid module ID + */ + + u32 param_id; +/* ID of the parameter to be queried. + * Supported values: Valid parameter ID + */ +} __packed; + +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106 + +/* Payload of the #AFE_PORT_CMDRSP_GET_PARAM_V2 message, which + * responds to an #AFE_PORT_CMD_GET_PARAM_V2 command. + * + * Immediately following this structure is the parameters structure + * (afe_port_param_data) containing the response(acknowledgment) + * parameter payload. This payload is included for an in-band + * scenario. For an address/shared memory-based set parameter, this + * payload is not needed. + */ + + +struct afe_port_cmdrsp_get_param_v2 { + u32 status; +} __packed; + +/* adsp_afe_service_commands.h */ + +#define ADSP_MEMORY_MAP_EBI_POOL 0 + +#define ADSP_MEMORY_MAP_SMI_POOL 1 +#define ADSP_MEMORY_MAP_IMEM_POOL 2 +#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3 +/* +* Definition of virtual memory flag +*/ +#define ADSP_MEMORY_MAP_VIRTUAL_MEMORY 1 + +/* +* Definition of physical memory flag +*/ +#define ADSP_MEMORY_MAP_PHYSICAL_MEMORY 0 + + +#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 VPM_TX_QMIC_FLUENCE_COPP_TOPOLOGY 0x00010F75 + +/* Memory map regions command payload used by the + * #ASM_CMD_SHARED_MEM_MAP_REGIONS ,#ADM_CMD_SHARED_MEM_MAP_REGIONS + * commands. + * + * This structure allows clients to map multiple shared memory + * regions in a single command. Following this structure are + * num_regions of avs_shared_map_region_payload. + */ + + +struct avs_cmd_shared_mem_map_regions { + struct apr_hdr hdr; + u16 mem_pool_id; +/* Type of memory on which this memory region is mapped. + * + * Supported values: - #ADSP_MEMORY_MAP_EBI_POOL - + * #ADSP_MEMORY_MAP_SMI_POOL - #ADSP_MEMORY_MAP_IMEM_POOL + * (unsupported) - #ADSP_MEMORY_MAP_SHMEM8_4K_POOL - Other values + * are reserved + * + * The memory ID implicitly defines the characteristics of the + * memory. Characteristics may include alignment type, permissions, + * etc. + * + * SHMEM8_4K is shared memory, byte addressable, and 4 KB aligned. + */ + + + u16 num_regions; + /* Number of regions to map.*/ + + u32 property_flag; +/* Configures one common property for all the regions in the + * payload. No two regions in the same memory map regions cmd can + * have differnt property. Supported values: - 0x00000000 to + * 0x00000001 + * + * b0 - bit 0 indicates physical or virtual mapping 0 shared memory + * address provided in avs_shared_map_regions_payload is physical + * address. The shared memory needs to be mapped( hardware TLB + * entry) + * + * and a software entry needs to be added for internal book keeping. + * + * 1 Shared memory address provided in MayPayload[usRegions] is + * virtual address. The shared memory must not be mapped (since + * hardware TLB entry is already available) but a software entry + * needs to be added for internal book keeping. This can be useful + * if two services with in ADSP is communicating via APR. They can + * now directly communicate via the Virtual address instead of + * Physical address. The virtual regions must be contiguous. + * + * b31-b1 - reserved bits. must be set to zero + */ + +} __packed; + +struct avs_shared_map_region_payload { + u32 shm_addr_lsw; +/* least significant word of shared memory address of the memory + * region to map. It must be contiguous memory, and it must be 4 KB + * aligned. + */ + + u32 shm_addr_msw; +/* most significant word of shared memory address of the memory + * region to map. For 32 bit shared memory address, this field must + * tbe set to zero. For 36 bit shared memory address, bit31 to bit 4 + * must be set to zero + */ + + u32 mem_size_bytes; +/* Number of bytes in the region. + * + * The aDSP will always map the regions as virtual contiguous + * memory, but the memory size must be in multiples of 4 KB to avoid + * gaps in the virtually contiguous mapped memory. + */ + +} __packed; + +struct avs_cmd_shared_mem_unmap_regions { + struct apr_hdr hdr; + u32 mem_map_handle; +/* memory map handle returned by ASM_CMD_SHARED_MEM_MAP_REGIONS + * , ADM_CMD_SHARED_MEM_MAP_REGIONS, commands + */ + +} __packed; + +/* Memory map command response payload used by the + * #ASM_CMDRSP_SHARED_MEM_MAP_REGIONS + * ,#ADM_CMDRSP_SHARED_MEM_MAP_REGIONS + */ + + +struct avs_cmdrsp_shared_mem_map_regions { + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory attributes is + * returned + */ + +} __packed; + +/*adsp_audio_memmap_api.h*/ + +/* ASM related data structures */ +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; +} __packed; + +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; +} __packed; + +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; +} __packed; + +struct asm_softpause_params { + u32 enable; + u32 period; + u32 step; + u32 rampingcurve; +} __packed; + +struct asm_softvolume_params { + u32 period; + u32 step; + u32 rampingcurve; +} __packed; + +#define ASM_END_POINT_DEVICE_MATRIX 0 +/* Front left channel. */ +#define PCM_CHANNEL_FL 1 + +/* Front right channel. */ +#define PCM_CHANNEL_FR 2 + +/* Front center channel. */ +#define PCM_CHANNEL_FC 3 + +/* Left surround channel.*/ +#define PCM_CHANNEL_LS 4 + +/* Right surround channel.*/ +#define PCM_CHANNEL_RS 5 + +/* Low frequency effect channel. */ +#define PCM_CHANNEL_LFE 6 + +/* Center surround channel; Rear center channel. */ +#define PCM_CHANNEL_CS 7 + +/* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_LB 8 + +/* Right back channel; Rear right channel. */ +#define PCM_CHANNEL_RB 9 + +/* Top surround channel. */ +#define PCM_CHANNELS 10 + +/* Center vertical height channel.*/ +#define PCM_CHANNEL_CVH 11 + +/* Mono surround channel.*/ +#define PCM_CHANNEL_MS 12 + +/* Front left of center. */ +#define PCM_CHANNEL_FLC 13 + +/* Front right of center. */ +#define PCM_CHANNEL_FRC 14 + +/* Rear left of center. */ +#define PCM_CHANNEL_RLC 15 + +/* Rear right of center. */ +#define PCM_CHANNEL_RRC 16 + +#define PCM_FORMAT_MAX_NUM_CHANNEL 8 + +#define ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 0x00010DA5 + +#define ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT 0x00010BE4 + +#define ASM_MEDIA_FMT_EVRCB_FS 0x00010BEF + +#define ASM_MEDIA_FMT_EVRCWB_FS 0x00010BF0 + +#define ASM_MAX_EQ_BANDS 12 + +#define ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2 0x00010D98 + +struct asm_data_cmd_media_fmt_update_v2 { +u32 fmt_blk_size; + /* Media format block size in bytes.*/ +} __packed; + +struct asm_multi_channel_pcm_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + + u16 num_channels; + /* Number of channels. Supported values: 1 to 8 */ + u16 bits_per_sample; +/* Number of bits per sample per channel. * Supported values: + * 16, 24 * When used for playback, the client must send 24-bit + * samples packed in 32-bit words. The 24-bit samples must be placed + * in the most significant 24 bits of the 32-bit word. When used for + * recording, the aDSP sends 24-bit samples packed in 32-bit words. + * The 24-bit samples are placed in the most significant 24 bits of + * the 32-bit word. + */ + + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * Supported values: 2000 to 48000 + */ + + u16 is_signed; + /* Flag that indicates the samples are signed (1). */ + + u16 reserved; + /* reserved field for 32 bit alignment. must be set to zero. */ + + u8 channel_mapping[8]; +/* Channel array of size 8. + * Supported values: + * - #PCM_CHANNEL_L + * - #PCM_CHANNEL_R + * - #PCM_CHANNEL_C + * - #PCM_CHANNEL_LS + * - #PCM_CHANNEL_RS + * - #PCM_CHANNEL_LFE + * - #PCM_CHANNEL_CS + * - #PCM_CHANNEL_LB + * - #PCM_CHANNEL_RB + * - #PCM_CHANNELS + * - #PCM_CHANNEL_CVH + * - #PCM_CHANNEL_MS + * - #PCM_CHANNEL_FLC + * - #PCM_CHANNEL_FRC + * - #PCM_CHANNEL_RLC + * - #PCM_CHANNEL_RRC + * + * Channel[i] mapping describes channel I. Each element i of the + * array describes channel I inside the buffer where 0 @le I < + * num_channels. An unused channel is set to zero. + */ +} __packed; + +struct asm_stream_cmd_set_encdec_param { + u32 param_id; + /* ID of the parameter. */ + + u32 param_size; +/* Data size of this parameter, in bytes. The size is a multiple + * of 4 bytes. + */ + +} __packed; + +struct asm_enc_cfg_blk_param_v2 { + u32 frames_per_buf; +/* Number of encoded frames to pack into each buffer. + * + * @note1hang This is only guidance information for the aDSP. The + * number of encoded frames put into each buffer (specified by the + * client) is less than or equal to this number. + */ + + u32 enc_cfg_blk_size; +/* Size in bytes of the encoder configuration block that follows + * this member. + */ + +} __packed; + +/* @brief Multichannel PCM encoder configuration structure used + * in the #ASM_STREAM_CMD_OPEN_READ_V2 command. + */ + +struct asm_multi_channel_pcm_enc_cfg_v2 { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + uint16_t num_channels; +/*< Number of PCM channels. + * + * Supported values: - 0 -- Native mode - 1 -- 8 Native mode + * indicates that encoding must be performed with the number of + * channels at the input. + */ + + uint16_t bits_per_sample; +/*< Number of bits per sample per channel. + * Supported values: 16, 24 + */ + + uint32_t sample_rate; +/*< Number of samples per second (in Hertz). + * + * Supported values: 0, 8000 to 48000 A value of 0 indicates the + * native sampling rate. Encoding is performed at the input sampling + * rate. + */ + + uint16_t is_signed; +/*< Specifies whether the samples are signed (1). Currently, + * only signed samples are supported. + */ + + uint16_t reserved; +/*< reserved field for 32 bit alignment. must be set to zero.*/ + + + uint8_t channel_mapping[8]; +} __packed; + +#define ASM_MEDIA_FMT_MP3 0x00010BE9 +#define ASM_MEDIA_FMT_AAC_V2 0x00010DA6 + +/* @xreflabel + * {hdr:AsmMediaFmtDolbyAac} Media format ID for the + * Dolby AAC decoder. This format ID is be used if the client wants + * to use the Dolby AAC decoder to decode MPEG2 and MPEG4 AAC + * contents. + */ + +#define ASM_MEDIA_FMT_DOLBY_AAC 0x00010D86 + +/* Enumeration for the audio data transport stream AAC format. */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADTS 0 + +/* Enumeration for low overhead audio stream AAC format. */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_LOAS 1 + +/* Enumeration for the audio data interchange format + * AAC format. + */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADIF 2 + +/* Enumeration for the raw AAC format. */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_RAW 3 + +#define ASM_MEDIA_FMT_AAC_AOT_LC 2 +#define ASM_MEDIA_FMT_AAC_AOT_SBR 5 +#define ASM_MEDIA_FMT_AAC_AOT_PS 29 +#define ASM_MEDIA_FMT_AAC_AOT_BSAC 22 + +struct asm_aac_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + + u16 aac_fmt_flag; +/* Bitstream format option. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADTS + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_LOAS + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADIF + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_RAW + */ + + u16 audio_objype; +/* Audio Object Type (AOT) present in the AAC stream. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_AOT_LC + * - #ASM_MEDIA_FMT_AAC_AOT_SBR + * - #ASM_MEDIA_FMT_AAC_AOT_BSAC + * - #ASM_MEDIA_FMT_AAC_AOT_PS + * - Otherwise -- Not supported + */ + + u16 channel_config; +/* Number of channels present in the AAC stream. + * Supported values: + * - 1 -- Mono + * - 2 -- Stereo + * - 6 -- 5.1 content + */ + + u16 reserved; + /* Reserved. Clients must set this field to zero. */ + + u16 total_size_of_PCE_bits; +/* greater or equal to zero. * -In case of RAW formats and + * channel config = 0 (PCE), client can send * the bit stream + * containing PCE immediately following this structure * (in-band). + * -This number does not include bits included for 32 bit alignment. + * -If zero, then the PCE info is assumed to be available in the + * audio -bit stream & not in-band. + */ + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * + * Supported values: 8000, 11025, 12000, 16000, 22050, 24000, 32000, + * 44100, 48000 + * + * This field must be equal to the sample rate of the AAC-LC + * decoder's output. - For MP4 or 3GP containers, this is indicated + * by the samplingFrequencyIndex field in the AudioSpecificConfig + * element. - For ADTS format, this is indicated by the + * samplingFrequencyIndex in the ADTS fixed header. - For ADIF + * format, this is indicated by the samplingFrequencyIndex in the + * program_config_element present in the ADIF header. + */ + +} __packed; + +struct asm_aac_enc_cfg_v2 { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u32 bit_rate; + /* Encoding rate in bits per second. */ + u32 enc_mode; +/* Encoding mode. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_AOT_LC + * - #ASM_MEDIA_FMT_AAC_AOT_SBR + * - #ASM_MEDIA_FMT_AAC_AOT_PS + */ + u16 aac_fmt_flag; +/* AAC format flag. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADTS + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_RAW + */ + u16 channel_cfg; +/* Number of channels to encode. + * Supported values: + * - 0 -- Native mode + * - 1 -- Mono + * - 2 -- Stereo + * - Other values are not supported. + * @note1hang The eAAC+ encoder mode supports only stereo. + * Native mode indicates that encoding must be performed with the + * number of channels at the input. + * The number of channels must not change during encoding. + */ + + u32 sample_rate; +/* Number of samples per second. + * Supported values: - 0 -- Native mode - For other values, + * Native mode indicates that encoding must be performed with the + * sampling rate at the input. + * The sampling rate must not change during encoding. + */ + +} __packed; + +#define ASM_MEDIA_FMT_AMRNB_FS 0x00010BEB + +/* Enumeration for 4.75 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MR475 0 + +/* Enumeration for 5.15 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MR515 1 + +/* Enumeration for 5.90 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR59 2 + +/* Enumeration for 6.70 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR67 3 + +/* Enumeration for 7.40 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR74 4 + +/* Enumeration for 7.95 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR795 5 + +/* Enumeration for 10.20 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR102 6 + +/* Enumeration for 12.20 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR122 7 + +/* Enumeration for AMR-NB Discontinuous Transmission mode off. */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_OFF 0 + +/* Enumeration for AMR-NB DTX mode VAD1. */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD1 1 + +/* Enumeration for AMR-NB DTX mode VAD2. */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD2 2 + +/* Enumeration for AMR-NB DTX mode auto. + */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_AUTO 3 + +struct asm_amrnb_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u16 enc_mode; +/* AMR-NB encoding rate. + * Supported values: + * Use the ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_* + * macros + */ + + u16 dtx_mode; +/* Specifies whether DTX mode is disabled or enabled. + * Supported values: + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_OFF + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD1 + */ +} __packed; + +#define ASM_MEDIA_FMT_AMRWB_FS 0x00010BEC + +/* Enumeration for 6.6 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR66 0 + +/* Enumeration for 8.85 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR885 1 + +/* Enumeration for 12.65 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1265 2 + +/* Enumeration for 14.25 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1425 3 + +/* Enumeration for 15.85 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1585 4 + +/* Enumeration for 18.25 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1825 5 + +/* Enumeration for 19.85 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1985 6 + +/* Enumeration for 23.05 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR2305 7 + +/* Enumeration for 23.85 kbps AMR-WB Encoding mode. + */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR2385 8 + +struct asm_amrwb_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u16 enc_mode; +/* AMR-WB encoding rate. + * Suupported values: + * Use the ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_* + * macros + */ + + u16 dtx_mode; +/* Specifies whether DTX mode is disabled or enabled. + * Supported values: + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_OFF + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD1 + */ +} __packed; + +#define ASM_MEDIA_FMT_V13K_FS 0x00010BED + +/* Enumeration for 14.4 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1440 0 + +/* Enumeration for 12.2 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1220 1 + +/* Enumeration for 11.2 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1120 2 + +/* Enumeration for 9.0 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR90 3 + +/* Enumeration for 7.2 kbps V13K eEncoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR720 4 + +/* Enumeration for 1/8 vocoder rate.*/ +#define ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE 1 + +/* Enumeration for 1/4 vocoder rate. */ +#define ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE 2 + +/* Enumeration for 1/2 vocoder rate. */ +#define ASM_MEDIA_FMT_VOC_HALF_RATE 3 + +/* Enumeration for full vocoder rate. + */ +#define ASM_MEDIA_FMT_VOC_FULL_RATE 4 + +struct asm_v13k_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u16 max_rate; +/* Maximum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 min_rate; +/* Minimum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 reduced_rate_cmd; +/* Reduced rate command, used to change + * the average bitrate of the V13K + * vocoder. + * Supported values: + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1440 (Default) + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1220 + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1120 + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR90 + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR720 + */ + + u16 rate_mod_cmd; +/* Rate modulation command. Default = 0. + *- If bit 0=1, rate control is enabled. + *- If bit 1=1, the maximum number of consecutive full rate + * frames is limited with numbers supplied in + * bits 2 to 10. + *- If bit 1=0, the minimum number of non-full rate frames + * in between two full rate frames is forced to + * the number supplied in bits 2 to 10. In both cases, if necessary, + * half rate is used to substitute full rate. - Bits 15 to 10 are + * reserved and must all be set to zero. + */ + +} __packed; + +#define ASM_MEDIA_FMT_EVRC_FS 0x00010BEE + +/* EVRC encoder configuration structure used in the + * #ASM_STREAM_CMD_OPEN_READ_V2 command. + */ +struct asm_evrc_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u16 max_rate; +/* Maximum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 min_rate; +/* Minimum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 rate_mod_cmd; +/* Rate modulation command. Default: 0. + * - If bit 0=1, rate control is enabled. + * - If bit 1=1, the maximum number of consecutive full rate frames + * is limited with numbers supplied in bits 2 to 10. + * + * - If bit 1=0, the minimum number of non-full rate frames in + * between two full rate frames is forced to the number supplied in + * bits 2 to 10. In both cases, if necessary, half rate is used to + * substitute full rate. + * + * - Bits 15 to 10 are reserved and must all be set to zero. + */ + + u16 reserved; + /* Reserved. Clients must set this field to zero. */ +} __packed; + +#define ASM_MEDIA_FMT_WMA_V10PRO_V2 0x00010DA7 + +struct asm_wmaprov10_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmtblk; + + u16 fmtag; +/* WMA format type. + * Supported values: + * - 0x162 -- WMA 9 Pro + * - 0x163 -- WMA 9 Pro Lossless + * - 0x166 -- WMA 10 Pro + * - 0x167 -- WMA 10 Pro Lossless + */ + + u16 num_channels; +/* Number of channels encoded in the input stream. + * Supported values: 1 to 8 + */ + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * Supported values: 11025, 16000, 22050, 32000, 44100, 48000, + * 88200, 96000 + */ + + u32 avg_bytes_per_sec; +/* Bitrate expressed as the average bytes per second. + * Supported values: 2000 to 96000 + */ + + u16 blk_align; +/* Size of the bitstream packet size in bytes. WMA Pro files + * have a payload of one block per bitstream packet. + * Supported values: @le 13376 + */ + + u16 bits_per_sample; +/* Number of bits per sample in the encoded WMA stream. + * Supported values: 16, 24 + */ + + u32 channel_mask; +/* Bit-packed double word (32-bits) that indicates the + * recommended speaker positions for each source channel. + */ + + u16 enc_options; +/* Bit-packed word with values that indicate whether certain + * features of the bitstream are used. + * Supported values: - 0x0001 -- ENCOPT3_PURE_LOSSLESS - 0x0006 -- + * ENCOPT3_FRM_SIZE_MOD - 0x0038 -- ENCOPT3_SUBFRM_DIV - 0x0040 -- + * ENCOPT3_WRITE_FRAMESIZE_IN_HDR - 0x0080 -- + * ENCOPT3_GENERATE_DRC_PARAMS - 0x0100 -- ENCOPT3_RTMBITS + */ + + + u16 usAdvancedEncodeOpt; + /* Advanced encoding option. */ + + u32 advanced_enc_options2; + /* Advanced encoding option 2. */ + +} __packed; + +#define ASM_MEDIA_FMT_WMA_V9_V2 0x00010DA8 +struct asm_wmastdv9_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmtblk; + u16 fmtag; +/* WMA format tag. + * Supported values: 0x161 (WMA 9 standard) + */ + + u16 num_channels; +/* Number of channels in the stream. + * Supported values: 1, 2 + */ + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * Supported values: 48000 + */ + + u32 avg_bytes_per_sec; + /* Bitrate expressed as the average bytes per second. */ + + u16 blk_align; +/* Block align. All WMA files with a maximum packet size of + * 13376 are supported. + */ + + + u16 bits_per_sample; +/* Number of bits per sample in the output. + * Supported values: 16 + */ + + u32 channel_mask; +/* Channel mask. + * Supported values: + * - 3 -- Stereo (front left/front right) + * - 4 -- Mono (center) + */ + + u16 enc_options; + /* Options used during encoding. */ + +} __packed; + +#define ASM_MEDIA_FMT_WMA_V8 0x00010D91 + +struct asm_wmastdv8_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u32 bit_rate; + /* Encoding rate in bits per second. */ + + u32 sample_rate; +/* Number of samples per second. + * + * Supported values: + * - 0 -- Native mode + * - Other Supported values are 22050, 32000, 44100, and 48000. + * + * Native mode indicates that encoding must be performed with the + * sampling rate at the input. + * The sampling rate must not change during encoding. + */ + + u16 channel_cfg; +/* Number of channels to encode. + * Supported values: + * - 0 -- Native mode + * - 1 -- Mono + * - 2 -- Stereo + * - Other values are not supported. + * + * Native mode indicates that encoding must be performed with the + * number of channels at the input. + * The number of channels must not change during encoding. + */ + + u16 reserved; + /* Reserved. Clients must set this field to zero.*/ + } __packed; + +#define ASM_MEDIA_FMT_AMR_WB_PLUS_V2 0x00010DA9 + +struct asm_amrwbplus_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmtblk; + u32 amr_frame_fmt; +/* AMR frame format. + * Supported values: + * - 6 -- Transport Interface Format (TIF) + * - Any other value -- File storage format (FSF) + * + * TIF stream contains 2-byte header for each frame within the + * superframe. FSF stream contains one 2-byte header per superframe. + */ + +} __packed; + +#define ASM_MEDIA_FMT_AC3_DEC 0x00010BF6 +#define ASM_MEDIA_FMT_EAC3_DEC 0x00010C3C +#define ASM_MEDIA_FMT_DTS 0x00010D88 + +/* Media format ID for adaptive transform acoustic coding. This + * ID is used by the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED command + * only. + */ + +#define ASM_MEDIA_FMT_ATRAC 0x00010D89 + +/* Media format ID for metadata-enhanced audio transmission. + * This ID is used by the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED + * command only. + */ + +#define ASM_MEDIA_FMT_MAT 0x00010D8A + +/* adsp_media_fmt.h */ + +#define ASM_DATA_CMD_WRITE_V2 0x00010DAB + +struct asm_data_cmd_write_v2 { + struct apr_hdr hdr; + u32 buf_addr_lsw; +/* The 64 bit address msw-lsw should be a valid, mapped address. + * 64 bit address should be a multiple of 32 bytes + */ + + u32 buf_addr_msw; +/* The 64 bit address msw-lsw should be a valid, mapped address. + * 64 bit address should be a multiple of 32 bytes. + * -Address of the buffer containing the data to be decoded. + * The buffer should be aligned to a 32 byte boundary. + * -In the case of 32 bit Shared memory address, msw field must + * -be set to zero. + * -In the case of 36 bit shared memory address, bit 31 to bit 4 + * -of msw must be set to zero. + */ + u32 mem_map_handle; +/* memory map handle returned by DSP through + * ASM_CMD_SHARED_MEM_MAP_REGIONS command + */ + u32 buf_size; +/* Number of valid bytes available in the buffer for decoding. The + * first byte starts at buf_addr. + */ + + u32 seq_id; + /* Optional buffer sequence ID. */ + + u32 timestamp_lsw; +/* Lower 32 bits of the 64-bit session time in microseconds of the + * first buffer sample. + */ + + u32 timestamp_msw; +/* Upper 32 bits of the 64-bit session time in microseconds of the + * first buffer sample. + */ + + u32 flags; +/* Bitfield of flags. + * Supported values for bit 31: + * - 1 -- Valid timestamp. + * - 0 -- Invalid timestamp. + * - Use #ASM_BIT_MASKIMESTAMP_VALID_FLAG as the bitmask and + * #ASM_SHIFTIMESTAMP_VALID_FLAG as the shift value to set this bit. + * Supported values for bit 30: + * - 1 -- Last buffer. + * - 0 -- Not the last buffer. + * + * Supported values for bit 29: + * - 1 -- Continue the timestamp from the previous buffer. + * - 0 -- Timestamp of the current buffer is not related + * to the timestamp of the previous buffer. + * - Use #ASM_BIT_MASKS_CONTINUE_FLAG and #ASM_SHIFTS_CONTINUE_FLAG + * to set this bit. + * + * Supported values for bit 4: + * - 1 -- End of the frame. + * - 0 -- Not the end of frame, or this information is not known. + * - Use #ASM_BIT_MASK_EOF_FLAG as the bitmask and #ASM_SHIFT_EOF_FLAG + * as the shift value to set this bit. + * + * All other bits are reserved and must be set to 0. + * + * If bit 31=0 and bit 29=1: The timestamp of the first sample in + * this buffer continues from the timestamp of the last sample in + * the previous buffer. If there is no previous buffer (i.e., this + * is the first buffer sent after opening the stream or after a + * flush operation), or if the previous buffer does not have a valid + * timestamp, the samples in the current buffer also do not have a + * valid timestamp. They are played out as soon as possible. + * + * + * If bit 31=0 and bit 29=0: No timestamp is associated with the + * first sample in this buffer. The samples are played out as soon + * as possible. + * + * + * If bit 31=1 and bit 29 is ignored: The timestamp specified in + * this payload is honored. + * + * + * If bit 30=0: Not the last buffer in the stream. This is useful + * in removing trailing samples. + * + * + * For bit 4: The client can set this flag for every buffer sent in + * which the last byte is the end of a frame. If this flag is set, + * the buffer can contain data from multiple frames, but it should + * always end at a frame boundary. Restrictions allow the aDSP to + * detect an end of frame without requiring additional processing. + */ + +} __packed; + +#define ASM_DATA_CMD_READ_V2 0x00010DAC + +struct asm_data_cmd_read_v2 { + struct apr_hdr hdr; + u32 buf_addr_lsw; +/* the 64 bit address msw-lsw should be a valid mapped address + * and should be a multiple of 32 bytes + */ + + + u32 buf_addr_msw; +/* the 64 bit address msw-lsw should be a valid mapped address + * and should be a multiple of 32 bytes. +* - Address of the buffer where the DSP puts the encoded data, +* potentially, at an offset specified by the uOffset field in +* ASM_DATA_EVENT_READ_DONE structure. The buffer should be aligned +* to a 32 byte boundary. +*- In the case of 32 bit Shared memory address, msw field must +*- be set to zero. +*- In the case of 36 bit shared memory address, bit 31 to bit +*- 4 of msw must be set to zero. +*/ + u32 mem_map_handle; +/* memory map handle returned by DSP through + * ASM_CMD_SHARED_MEM_MAP_REGIONS command. + */ + + u32 buf_size; +/* Number of bytes available for the aDSP to write. The aDSP + * starts writing from buf_addr. + */ + + u32 seq_id; + /* Optional buffer sequence ID. + */ +} __packed; + +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DATA_EVENT_RENDERED_EOS 0x00010C1C +#define ASM_DATA_EVENT_EOS 0x00010BDD + +#define ASM_DATA_EVENT_WRITE_DONE_V2 0x00010D99 +struct asm_data_event_write_done_v2 { + u32 buf_addr_lsw; + /* lsw of the 64 bit address */ + u32 buf_addr_msw; + /* msw of the 64 bit address. address given by the client in + * ASM_DATA_CMD_WRITE_V2 command. + */ + u32 mem_map_handle; + /* memory map handle in the ASM_DATA_CMD_WRITE_V2 */ + + u32 status; +/* Status message (error code) that indicates whether the + * referenced buffer has been successfully consumed. + * Supported values: Refer to @xhyperref{Q3,[Q3]} + */ +} __packed; + +#define ASM_DATA_EVENT_READ_DONE_V2 0x00010D9A + +/* Definition of the frame metadata flag bitmask.*/ +#define ASM_BIT_MASK_FRAME_METADATA_FLAG (0x40000000UL) + +/* Definition of the frame metadata flag shift value. */ +#define ASM_SHIFT_FRAME_METADATA_FLAG 30 + +struct asm_data_event_read_done_v2 { + u32 status; +/* Status message (error code). + * Supported values: Refer to @xhyperref{Q3,[Q3]} + */ + +u32 buf_addr_lsw; +/* 64 bit address msw-lsw is a valid, mapped address. 64 bit + * address is a multiple of 32 bytes. + */ + +u32 buf_addr_msw; +/* 64 bit address msw-lsw is a valid, mapped address. 64 bit +* address is a multiple of 32 bytes. +* +* -Same address provided by the client in ASM_DATA_CMD_READ_V2 +* -In the case of 32 bit Shared memory address, msw field is set to +* zero. +* -In the case of 36 bit shared memory address, bit 31 to bit 4 +* -of msw is set to zero. +*/ + +u32 mem_map_handle; +/* memory map handle in the ASM_DATA_CMD_READ_V2 */ + +u32 enc_framesotal_size; +/* Total size of the encoded frames in bytes. + * Supported values: >0 + */ + +u32 offset; +/* Offset (from buf_addr) to the first byte of the first encoded + * frame. All encoded frames are consecutive, starting from this + * offset. + * Supported values: > 0 + */ + +u32 timestamp_lsw; +/* Lower 32 bits of the 64-bit session time in microseconds of + * the first sample in the buffer. If Bit 5 of mode_flags flag of + * ASM_STREAM_CMD_OPEN_READ_V2 is 1 then the 64 bit timestamp is + * absolute capture time otherwise it is relative session time. The + * absolute timestamp doesnt reset unless the system is reset. + */ + + +u32 timestamp_msw; +/* Upper 32 bits of the 64-bit session time in microseconds of + * the first sample in the buffer. + */ + + +u32 flags; +/* Bitfield of flags. Bit 30 indicates whether frame metadata is + * present. If frame metadata is present, num_frames consecutive + * instances of @xhyperref{hdr:FrameMetaData,Frame metadata} start + * at the buffer address. + * Supported values for bit 31: + * - 1 -- Timestamp is valid. + * - 0 -- Timestamp is invalid. + * - Use #ASM_BIT_MASKIMESTAMP_VALID_FLAG and + * #ASM_SHIFTIMESTAMP_VALID_FLAG to set this bit. + * + * Supported values for bit 30: + * - 1 -- Frame metadata is present. + * - 0 -- Frame metadata is absent. + * - Use #ASM_BIT_MASK_FRAME_METADATA_FLAG and + * #ASM_SHIFT_FRAME_METADATA_FLAG to set this bit. + * + * All other bits are reserved; the aDSP sets them to 0. + */ + +u32 num_frames; +/* Number of encoded frames in the buffer. */ + +u32 seq_id; +/* Optional buffer sequence ID. */ +} __packed; + +struct asm_data_read_buf_metadata_v2 { + u32 offset; +/* Offset from buf_addr in #ASM_DATA_EVENT_READ_DONE_PAYLOAD to + * the frame associated with this metadata. + * Supported values: > 0 + */ + +u32 frm_size; +/* Size of the encoded frame in bytes. + * Supported values: > 0 + */ + +u32 num_encoded_pcm_samples; +/* Number of encoded PCM samples (per channel) in the frame + * associated with this metadata. + * Supported values: > 0 + */ + +u32 timestamp_lsw; +/* Lower 32 bits of the 64-bit session time in microseconds of the + * first sample for this frame. + * If Bit 5 of mode_flags flag of ASM_STREAM_CMD_OPEN_READ_V2 is 1 + * then the 64 bit timestamp is absolute capture time otherwise it + * is relative session time. The absolute timestamp doesnt reset + * unless the system is reset. + */ + + +u32 timestamp_msw; +/* Lower 32 bits of the 64-bit session time in microseconds of the + * first sample for this frame. + */ + +u32 flags; +/* Frame flags. + * Supported values for bit 31: + * - 1 -- Time stamp is valid + * - 0 -- Time stamp is not valid + * - All other bits are reserved; the aDSP sets them to 0. +*/ +} __packed; + +/* Notifies the client of a change in the data sampling rate or + * Channel mode. This event is raised by the decoder service. The + * event is enabled through the mode flags of + * #ASM_STREAM_CMD_OPEN_WRITE_V2 or + * #ASM_STREAM_CMD_OPEN_READWRITE_V2. - The decoder detects a change + * in the output sampling frequency or the number/positioning of + * output channels, or if it is the first frame decoded.The new + * sampling frequency or the new channel configuration is + * communicated back to the client asynchronously. + */ + +#define ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY 0x00010C65 + +/* Payload of the #ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY event. + * This event is raised when the following conditions are both true: + * - The event is enabled through the mode_flags of + * #ASM_STREAM_CMD_OPEN_WRITE_V2 or + * #ASM_STREAM_CMD_OPEN_READWRITE_V2. - The decoder detects a change + * in either the output sampling frequency or the number/positioning + * of output channels, or if it is the first frame decoded. + * This event is not raised (even if enabled) if the decoder is + * MIDI, because + */ + + +struct asm_data_event_sr_cm_change_notify { + u32 sample_rate; +/* New sampling rate (in Hertz) after detecting a change in the + * bitstream. + * Supported values: 2000 to 48000 + */ + + u16 num_channels; +/* New number of channels after detecting a change in the + * bitstream. + * Supported values: 1 to 8 + */ + + + u16 reserved; + /* Reserved for future use. This field must be set to 0.*/ + + u8 channel_mapping[8]; + +} __packed; + +/* Notifies the client of a data sampling rate or channel mode + * change. This event is raised by the encoder service. + * This event is raised when : + * - Native mode encoding was requested in the encoder + * configuration (i.e., the channel number was 0), the sample rate + * was 0, or both were 0. + * + * - The input data frame at the encoder is the first one, or the + * sampling rate/channel mode is different from the previous input + * data frame. + * + */ +#define ASM_DATA_EVENT_ENC_SR_CM_CHANGE_NOTIFY 0x00010BDE + +struct asm_data_event_enc_sr_cm_change_notify { + u32 sample_rate; +/* New sampling rate (in Hertz) after detecting a change in the + * input data. + * Supported values: 2000 to 48000 + */ + + + u16 num_channels; +/* New number of channels after detecting a change in the input + * data. Supported values: 1 to 8 + */ + + + u16 bits_per_sample; +/* New bits per sample after detecting a change in the input + * data. + * Supported values: 16, 24 + */ + + + u8 channel_mapping[8]; + +} __packed; +#define ASM_DATA_CMD_IEC_60958_FRAME_RATE 0x00010D87 + + +/* Payload of the #ASM_DATA_CMD_IEC_60958_FRAME_RATE command, + * which is used to indicate the IEC 60958 frame rate of a given + * packetized audio stream. + */ + +struct asm_data_cmd_iec_60958_frame_rate { + u32 frame_rate; +/* IEC 60958 frame rate of the incoming IEC 61937 packetized stream. + * Supported values: Any valid frame rate + */ +} __packed; + +/* adsp_asm_data_commands.h*/ +#define ASM_SVC_CMD_GET_STREAM_HANDLES 0x00010C0B + +#define ASM_SVC_CMDRSP_GET_STREAM_HANDLES 0x00010C1B + +/* Definition of the stream ID bitmask.*/ +#define ASM_BIT_MASK_STREAM_ID (0x000000FFUL) + +/* Definition of the stream ID shift value.*/ +#define ASM_SHIFT_STREAM_ID 0 + +/* Definition of the session ID bitmask.*/ +#define ASM_BIT_MASK_SESSION_ID (0x0000FF00UL) + +/* Definition of the session ID shift value.*/ +#define ASM_SHIFT_SESSION_ID 8 + +/* Definition of the service ID bitmask.*/ +#define ASM_BIT_MASK_SERVICE_ID (0x00FF0000UL) + +/* Definition of the service ID shift value.*/ +#define ASM_SHIFT_SERVICE_ID 16 + +/* Definition of the domain ID bitmask.*/ +#define ASM_BIT_MASK_DOMAIN_ID (0xFF000000UL) + +/* Definition of the domain ID shift value.*/ +#define ASM_SHIFT_DOMAIN_ID 24 + +/* Payload of the #ASM_SVC_CMDRSP_GET_STREAM_HANDLES message, + * which returns a list of currently active stream handles. + * Immediately following this structure are num_handles of uint32 + * stream handles. + */ + + +struct asm_svc_cmdrsp_get_stream_handles { + u32 num_handles; + /* Number of active stream handles. */ +} __packed; + +#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92 +#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93 +#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94 + +/* adsp_asm_service_commands.h */ + +#define ASM_MAX_SESSION_ID (8) + +/* Maximum number of sessions.*/ +#define ASM_MAX_NUM_SESSIONS ASM_MAX_SESSION_ID + +/* Maximum number of streams per session.*/ +#define ASM_MAX_STREAMS_PER_SESSION (8) +#define ASM_SESSION_CMD_RUN_V2 0x00010DAA +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_IMMEDIATE 0 +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_ABSOLUTEIME 1 +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_RELATIVEIME 2 +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_WITH_DELAY 3 + +#define ASM_BIT_MASK_RUN_STARTIME (0x00000003UL) + +/* Bit shift value used to specify the start time for the + * ASM_SESSION_CMD_RUN_V2 command. + */ +#define ASM_SHIFT_RUN_STARTIME 0 +struct asm_session_cmd_run_v2 { + struct apr_hdr hdr; + u32 flags; +/* Specifies whether to run immediately or at a specific + * rendering time or with a specified delay. Run with delay is + * useful for delaying in case of ASM loopback opened through + * ASM_STREAM_CMD_OPEN_LOOPBACK_V2. Use #ASM_BIT_MASK_RUN_STARTIME + * and #ASM_SHIFT_RUN_STARTIME to set this 2-bit flag. + * + * + *Bits 0 and 1 can take one of four possible values: + * + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_IMMEDIATE + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_ABSOLUTEIME + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_RELATIVEIME + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_WITH_DELAY + * + *All other bits are reserved; clients must set them to zero. + */ + + u32 time_lsw; +/* Lower 32 bits of the time in microseconds used to align the + * session origin time. When bits 0-1 of flags is + * ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, time lsw is the lsw of + * the delay in us. For ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, + * maximum value of the 64 bit delay is 150 ms. + */ + + u32 time_msw; +/* Upper 32 bits of the time in microseconds used to align the + * session origin time. When bits 0-1 of flags is + * ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, time msw is the msw of + * the delay in us. For ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, + * maximum value of the 64 bit delay is 150 ms. + */ + +} __packed; + +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_SESSION_CMD_GET_SESSIONTIME_V3 0x00010D9D +#define ASM_SESSION_CMD_REGISTER_FOR_RX_UNDERFLOW_EVENTS 0x00010BD5 + +struct asm_session_cmd_rgstr_rx_underflow { + struct apr_hdr hdr; + u16 enable_flag; +/* Specifies whether a client is to receive events when an Rx + * session underflows. + * Supported values: + * - 0 -- Do not send underflow events + * - 1 -- Send underflow events + */ + u16 reserved; + /* Reserved. This field must be set to zero.*/ +} __packed; + +#define ASM_SESSION_CMD_REGISTER_FORX_OVERFLOW_EVENTS 0x00010BD6 + +struct asm_session_cmd_regx_overflow { + struct apr_hdr hdr; + u16 enable_flag; +/* Specifies whether a client is to receive events when a Tx +* session overflows. + * Supported values: + * - 0 -- Do not send overflow events + * - 1 -- Send overflow events + */ + + u16 reserved; + /* Reserved. This field must be set to zero.*/ +} __packed; + +#define ASM_SESSION_EVENT_RX_UNDERFLOW 0x00010C17 +#define ASM_SESSION_EVENTX_OVERFLOW 0x00010C18 +#define ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3 0x00010D9E + +struct asm_session_cmdrsp_get_sessiontime_v3 { + u32 status; + /* Status message (error code). + * Supported values: Refer to @xhyperref{Q3,[Q3]} + */ + + u32 sessiontime_lsw; + /* Lower 32 bits of the current session time in microseconds.*/ + + u32 sessiontime_msw; + /* Upper 32 bits of the current session time in microseconds.*/ + + u32 absolutetime_lsw; +/* Lower 32 bits in micro seconds of the absolute time at which + * the * sample corresponding to the above session time gets + * rendered * to hardware. This absolute time may be slightly in the + * future or past. + */ + + + u32 absolutetime_msw; +/* Upper 32 bits in micro seconds of the absolute time at which + * the * sample corresponding to the above session time gets + * rendered to * hardware. This absolute time may be slightly in the + * future or past. + */ + +} __packed; + +#define ASM_SESSION_CMD_ADJUST_SESSION_CLOCK_V2 0x00010D9F + +struct asm_session_cmd_adjust_session_clock_v2 { + struct apr_hdr hdr; +u32 adjustime_lsw; +/* Lower 32 bits of the signed 64-bit quantity that specifies the + * adjustment time in microseconds to the session clock. + * + * Positive values indicate advancement of the session clock. + * Negative values indicate delay of the session clock. + */ + + + u32 adjustime_msw; +/* Upper 32 bits of the signed 64-bit quantity that specifies + * the adjustment time in microseconds to the session clock. + * Positive values indicate advancement of the session clock. + * Negative values indicate delay of the session clock. + */ + +} __packed; + +#define ASM_SESSION_CMDRSP_ADJUST_SESSION_CLOCK_V2 0x00010DA0 + +struct asm_session_cmdrsp_adjust_session_clock_v2 { + u32 status; +/* Status message (error code). + * Supported values: Refer to @xhyperref{Q3,[Q3]} + * An error means the session clock is not adjusted. In this case, + * the next two fields are irrelevant. + */ + + + u32 actual_adjustime_lsw; +/* Lower 32 bits of the signed 64-bit quantity that specifies + * the actual adjustment in microseconds performed by the aDSP. + * A positive value indicates advancement of the session clock. A + * negative value indicates delay of the session clock. + */ + + + u32 actual_adjustime_msw; +/* Upper 32 bits of the signed 64-bit quantity that specifies + * the actual adjustment in microseconds performed by the aDSP. + * A positive value indicates advancement of the session clock. A + * negative value indicates delay of the session clock. + */ + + + u32 cmd_latency_lsw; +/* Lower 32 bits of the unsigned 64-bit quantity that specifies + * the amount of time in microseconds taken to perform the session + * clock adjustment. + */ + + + u32 cmd_latency_msw; +/* Upper 32 bits of the unsigned 64-bit quantity that specifies + * the amount of time in microseconds taken to perform the session + * clock adjustment. + */ + +} __packed; + +#define ASM_SESSION_CMD_GET_PATH_DELAY_V2 0x00010DAF +#define ASM_SESSION_CMDRSP_GET_PATH_DELAY_V2 0x00010DB0 + +struct asm_session_cmdrsp_get_path_delay_v2 { + u32 status; +/* Status message (error code). Whether this get delay operation + * is successful or not. Delay value is valid only if status is + * success. + * Supported values: Refer to @xhyperref{Q5,[Q5]} + */ + + u32 audio_delay_lsw; + /* Upper 32 bits of the aDSP delay in microseconds. */ + + u32 audio_delay_msw; + /* Lower 32 bits of the aDSP delay in microseconds. */ + +} __packed; + +/* adsp_asm_session_command.h*/ +#define ASM_STREAM_CMD_OPEN_WRITE_V2 0x00010D8F + +struct asm_stream_cmd_open_write_v2 { + struct apr_hdr hdr; + uint32_t mode_flags; +/* Mode flags that configure the stream to notify the client + * whenever it detects an SR/CM change at the input to its POPP. + * Supported values for bits 0 to 1: + * - Reserved; clients must set them to zero. + * Supported values for bit 2: + * - 0 -- SR/CM change notification event is disabled. + * - 1 -- SR/CM change notification event is enabled. + * - Use #ASM_BIT_MASK_SR_CM_CHANGE_NOTIFY_FLAG and + * #ASM_SHIFT_SR_CM_CHANGE_NOTIFY_FLAG to set or get this bit. + * + * Supported values for bit 31: + * - 0 -- Stream to be opened in on-Gapless mode. + * - 1 -- Stream to be opened in Gapless mode. In Gapless mode, + * successive streams must be opened with same session ID but + * different stream IDs. + * + * - Use #ASM_BIT_MASK_GAPLESS_MODE_FLAG and + * #ASM_SHIFT_GAPLESS_MODE_FLAG to set or get this bit. + * + * + * @note1hang MIDI and DTMF streams cannot be opened in Gapless mode. + */ + + uint16_t sink_endpointype; +/*< Sink point type. + * Supported values: + * - 0 -- Device matrix + * - Other values are reserved. + * + * The device matrix is the gateway to the hardware ports. + */ + + uint16_t bits_per_sample; +/*< Number of bits per sample processed by ASM modules. + * Supported values: 16 and 24 bits per sample + */ + + uint32_t postprocopo_id; +/*< Specifies the topology (order of processing) of + * postprocessing algorithms. None means no postprocessing. + * Supported values: + * - #ASM_STREAM_POSTPROCOPO_ID_DEFAULT + * - #ASM_STREAM_POSTPROCOPO_ID_MCH_PEAK_VOL + * - #ASM_STREAM_POSTPROCOPO_ID_NONE + * + * This field can also be enabled through SetParams flags. + */ + + uint32_t dec_fmt_id; +/*< Configuration ID of the decoder media format. + * + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_ADPCM + * - #ASM_MEDIA_FMT_MP3 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_DOLBY_AAC + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_AMR_WB_PLUS_V2 + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_WMA_V10PRO_V2 + * - #ASM_MEDIA_FMT_WMA_V9_V2 + * - #ASM_MEDIA_FMT_AC3_DEC + * - #ASM_MEDIA_FMT_EAC3_DEC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_FR_FS + * - #ASM_MEDIA_FMT_VORBIS + * - #ASM_MEDIA_FMT_FLAC + * - #ASM_MEDIA_FMT_EXAMPLE + */ +} __packed; + +#define ASM_STREAM_CMD_OPEN_READ_V2 0x00010D8C +/* Definition of the timestamp type flag bitmask */ +#define ASM_BIT_MASKIMESTAMPYPE_FLAG (0x00000020UL) + +/* Definition of the timestamp type flag shift value. */ +#define ASM_SHIFTIMESTAMPYPE_FLAG 5 + +/* Relative timestamp is identified by this value.*/ +#define ASM_RELATIVEIMESTAMP 0 + +/* Absolute timestamp is identified by this value.*/ +#define ASM_ABSOLUTEIMESTAMP 1 + + +struct asm_stream_cmd_open_read_v2 { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags that indicate whether meta information per encoded + * frame is to be provided. + * Supported values for bit 4: + * + * - 0 -- Return data buffer contains all encoded frames only; it + * does not contain frame metadata. + * + * - 1 -- Return data buffer contains an array of metadata and + * encoded frames. + * + * - Use #ASM_BIT_MASK_META_INFO_FLAG as the bitmask and + * #ASM_SHIFT_META_INFO_FLAG as the shift value for this bit. + * + * + * Supported values for bit 5: + * + * - ASM_RELATIVEIMESTAMP -- ASM_DATA_EVENT_READ_DONE_V2 will have + * - relative time-stamp. + * - ASM_ABSOLUTEIMESTAMP -- ASM_DATA_EVENT_READ_DONE_V2 will + * - have absolute time-stamp. + * + * - Use #ASM_BIT_MASKIMESTAMPYPE_FLAG as the bitmask and + * #ASM_SHIFTIMESTAMPYPE_FLAG as the shift value for this bit. + * + * All other bits are reserved; clients must set them to zero. + */ + + u32 src_endpointype; +/* Specifies the endpoint providing the input samples. + * Supported values: + * - 0 -- Device matrix + * - All other values are reserved; clients must set them to zero. + * Otherwise, an error is returned. + * The device matrix is the gateway from the tunneled Tx ports. + */ + + u32 preprocopo_id; +/* Specifies the topology (order of processing) of preprocessing + * algorithms. None means no preprocessing. + * Supported values: + * - #ASM_STREAM_PREPROCOPO_ID_DEFAULT + * - #ASM_STREAM_PREPROCOPO_ID_NONE + * + * This field can also be enabled through SetParams flags. + */ + + u32 enc_cfg_id; +/* Media configuration ID for encoded output. + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_EXAMPLE + * - #ASM_MEDIA_FMT_WMA_V8 + */ + + u16 bits_per_sample; +/* Number of bits per sample processed by ASM modules. + * Supported values: 16 and 24 bits per sample + */ + + u16 reserved; +/* Reserved for future use. This field must be set to zero.*/ +} __packed; + +#define ASM_POPP_OUTPUT_SR_NATIVE_RATE 0 + +/* Enumeration for the maximum sampling rate at the POPP output.*/ +#define ASM_POPP_OUTPUT_SR_MAX_RATE 48000 + +#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D +#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D +#define ASM_STREAM_CMD_OPEN_READ_V2 0x00010D8C + +struct asm_stream_cmd_open_readwrite_v2 { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags. + * Supported values for bit 2: + * - 0 -- SR/CM change notification event is disabled. + * - 1 -- SR/CM change notification event is enabled. Use + * #ASM_BIT_MASK_SR_CM_CHANGE_NOTIFY_FLAG and + * #ASM_SHIFT_SR_CM_CHANGE_NOTIFY_FLAG to set or + * getting this flag. + * + * Supported values for bit 4: + * - 0 -- Return read data buffer contains all encoded frames only; it + * does not contain frame metadata. + * - 1 -- Return read data buffer contains an array of metadata and + * encoded frames. + * + * All other bits are reserved; clients must set them to zero. + */ + + u32 postprocopo_id; +/* Specifies the topology (order of processing) of postprocessing + * algorithms. None means no postprocessing. + * + * Supported values: + * - #ASM_STREAM_POSTPROCOPO_ID_DEFAULT + * - #ASM_STREAM_POSTPROCOPO_ID_MCH_PEAK_VOL + * - #ASM_STREAM_POSTPROCOPO_ID_NONE + */ + + u32 dec_fmt_id; +/* Specifies the media type of the input data. PCM indicates that + * no decoding must be performed, e.g., this is an NT encoder + * session. + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_ADPCM + * - #ASM_MEDIA_FMT_MP3 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_DOLBY_AAC + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_WMA_V10PRO_V2 + * - #ASM_MEDIA_FMT_WMA_V9_V2 + * - #ASM_MEDIA_FMT_AMR_WB_PLUS_V2 + * - #ASM_MEDIA_FMT_AC3_DEC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_EXAMPLE + */ + + u32 enc_cfg_id; +/* Specifies the media type for the output of the stream. PCM + * indicates that no encoding must be performed, e.g., this is an NT + * decoder session. + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_EXAMPLE + * - #ASM_MEDIA_FMT_WMA_V8 + */ + + u16 bits_per_sample; +/* Number of bits per sample processed by ASM modules. + * Supported values: 16 and 24 bits per sample + */ + + u16 reserved; +/* Reserved for future use. This field must be set to zero.*/ + +} __packed; + +#define ASM_STREAM_CMD_OPEN_LOOPBACK_V2 0x00010D8E +struct asm_stream_cmd_open_loopback_v2 { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags. + * Bit 0-31: reserved; client should set these bits to 0 + */ + u16 src_endpointype; + /* Endpoint type. 0 = Tx Matrix */ + u16 sink_endpointype; + /* Endpoint type. 0 = Rx Matrix */ + u32 postprocopo_id; +/* Postprocessor topology ID. Specifies the topology of + * postprocessing algorithms. + */ + + u16 bits_per_sample; +/* The number of bits per sample processed by ASM modules + * Supported values: 16 and 24 bits per sample + */ + u16 reserved; +/* Reserved for future use. This field must be set to zero. */ +} __packed; + +#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE + + +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 +#define ASM_STREAM_CMD_SET_PP_PARAMS_V2 0x00010DA1 + +struct asm_stream_cmd_set_pp_params_v2 { + u32 data_payload_addr_lsw; +/* LSW of parameter data payload address. Supported values: any. */ + u32 data_payload_addr_msw; +/* MSW of Parameter data payload address. Supported values: any. + * - Must be set to zero for in-band data. + * - In the case of 32 bit Shared memory address, msw field must be + * - set to zero. + * - In the case of 36 bit shared memory address, bit 31 to bit 4 of + * msw + * + * - must be set to zero. + */ + u32 mem_map_handle; +/* Supported Values: Any. +* memory map handle returned by DSP through +* ASM_CMD_SHARED_MEM_MAP_REGIONS +* command. +* if mmhandle is NULL, the ParamData payloads are within the +* message payload (in-band). +* If mmhandle is non-NULL, the ParamData payloads begin at the +* address specified in the address msw and lsw (out-of-band). +*/ + + u32 data_payload_size; +/* Size in bytes of the variable payload accompanying the +message, or in shared memory. This field is used for parsing the +parameter payload. */ + +} __packed; + + +struct asm_stream_param_data_v2 { + u32 module_id; + /* Unique module ID. */ + + u32 param_id; + /* Unique parameter ID. */ + + u16 param_size; +/* Data size of the param_id/module_id combination. This is + * a multiple of 4 bytes. + */ + + u16 reserved; +/* Reserved for future enhancements. This field must be set to + * zero. + */ + +} __packed; + +#define ASM_STREAM_CMD_GET_PP_PARAMS_V2 0x00010DA2 + +struct asm_stream_cmd_get_pp_params_v2 { + u32 data_payload_addr_lsw; + /* LSW of the parameter data payload address. */ + u32 data_payload_addr_msw; +/* MSW of the parameter data payload address. + * - Size of the shared memory, if specified, shall be large enough + * to contain the whole ParamData payload, including Module ID, + * Param ID, Param Size, and Param Values + * - Must be set to zero for in-band data + * - In the case of 32 bit Shared memory address, msw field must be + * set to zero. + * - In the case of 36 bit shared memory address, bit 31 to bit 4 of + * msw must be set to zero. + */ + + u32 mem_map_handle; +/* Supported Values: Any. +* memory map handle returned by DSP through ASM_CMD_SHARED_MEM_MAP_REGIONS +* command. +* if mmhandle is NULL, the ParamData payloads in the ACK are within the +* message payload (in-band). +* If mmhandle is non-NULL, the ParamData payloads in the ACK begin at the +* address specified in the address msw and lsw. +* (out-of-band). +*/ + + u32 module_id; + /* Unique module ID. */ + + u32 param_id; + /* Unique parameter ID. */ + + u16 param_max_size; +/* Maximum data size of the module_id/param_id combination. This + * is a multiple of 4 bytes. + */ + + + u16 reserved; +/* Reserved for backward compatibility. Clients must set this +* field to zero. +*/ + +} __packed; + +#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10 + +#define ASM_PARAM_ID_ENCDEC_BITRATE 0x00010C13 + +struct asm_bitrate_param { + u32 bitrate; +/* Maximum supported bitrate. Only the AAC encoder is supported.*/ + +} __packed; + +#define ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2 0x00010DA3 +#define ASM_PARAM_ID_AAC_SBR_PS_FLAG 0x00010C63 + +/* Flag to turn off both SBR and PS processing, if they are + * present in the bitstream. + */ + +#define ASM_AAC_SBR_OFF_PS_OFF (2) + +/* Flag to turn on SBR but turn off PS processing,if they are + * present in the bitstream. + */ + +#define ASM_AAC_SBR_ON_PS_OFF (1) + +/* Flag to turn on both SBR and PS processing, if they are + * present in the bitstream (default behavior). + */ + + +#define ASM_AAC_SBR_ON_PS_ON (0) + +/* Structure for an AAC SBR PS processing flag. */ + +/* Payload of the #ASM_PARAM_ID_AAC_SBR_PS_FLAG parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +struct asm_aac_sbr_ps_flag_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u32 sbr_ps_flag; +/* Control parameter to enable or disable SBR/PS processing in + * the AAC bitstream. Use the following macros to set this field: + * - #ASM_AAC_SBR_OFF_PS_OFF -- Turn off both SBR and PS + * processing, if they are present in the bitstream. + * - #ASM_AAC_SBR_ON_PS_OFF -- Turn on SBR processing, but not PS + * processing, if they are present in the bitstream. + * - #ASM_AAC_SBR_ON_PS_ON -- Turn on both SBR and PS processing, + * if they are present in the bitstream (default behavior). + * - All other values are invalid. + * Changes are applied to the next decoded frame. + */ +} __packed; + +#define ASM_PARAM_ID_AAC_DUAL_MONO_MAPPING 0x00010C64 + +/* First single channel element in a dual mono bitstream.*/ +#define ASM_AAC_DUAL_MONO_MAP_SCE_1 (1) + +/* Second single channel element in a dual mono bitstream.*/ +#define ASM_AAC_DUAL_MONO_MAP_SCE_2 (2) + +/* Structure for AAC decoder dual mono channel mapping. */ + + +struct asm_aac_dual_mono_mapping_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u16 left_channel_sce; + u16 right_channel_sce; + +} __packed; + +#define ASM_STREAM_CMDRSP_GET_PP_PARAMS_V2 0x00010DA4 + +struct asm_stream_cmdrsp_get_pp_params_v2 { + u32 status; +} __packed; + +#define ASM_PARAM_ID_AC3_KARAOKE_MODE 0x00010D73 + +/* Enumeration for both vocals in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_NO_VOCAL (0) + +/* Enumeration for only the left vocal in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_LEFT_VOCAL (1) + +/* Enumeration for only the right vocal in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_RIGHT_VOCAL (2) + +/* Enumeration for both vocal channels in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_BOTH_VOCAL (3) +#define ASM_PARAM_ID_AC3_DRC_MODE 0x00010D74 +/* Enumeration for the Custom Analog mode.*/ +#define AC3_DRC_MODE_CUSTOM_ANALOG (0) + +/* Enumeration for the Custom Digital mode.*/ +#define AC3_DRC_MODE_CUSTOM_DIGITAL (1) +/* Enumeration for the Line Out mode (light compression).*/ +#define AC3_DRC_MODE_LINE_OUT (2) + +/* Enumeration for the RF remodulation mode (heavy compression).*/ +#define AC3_DRC_MODE_RF_REMOD (3) +#define ASM_PARAM_ID_AC3_DUAL_MONO_MODE 0x00010D75 + +/* Enumeration for playing dual mono in stereo mode.*/ +#define AC3_DUAL_MONO_MODE_STEREO (0) + +/* Enumeration for playing left mono.*/ +#define AC3_DUAL_MONO_MODE_LEFT_MONO (1) + +/* Enumeration for playing right mono.*/ +#define AC3_DUAL_MONO_MODE_RIGHT_MONO (2) + +/* Enumeration for mixing both dual mono channels and playing them.*/ +#define AC3_DUAL_MONO_MODE_MIXED_MONO (3) +#define ASM_PARAM_ID_AC3_STEREO_DOWNMIX_MODE 0x00010D76 + +/* Enumeration for using the Downmix mode indicated in the bitstream. */ + +#define AC3_STEREO_DOWNMIX_MODE_AUTO_DETECT (0) + +/* Enumeration for Surround Compatible mode (preserves the + * surround information). + */ + +#define AC3_STEREO_DOWNMIX_MODE_LT_RT (1) +/* Enumeration for Mono Compatible mode (if the output is to be + * further downmixed to mono). + */ + +#define AC3_STEREO_DOWNMIX_MODE_LO_RO (2) + +/* ID of the AC3 PCM scale factor parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +#define ASM_PARAM_ID_AC3_PCM_SCALEFACTOR 0x00010D78 + +/* ID of the AC3 DRC boost scale factor parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +#define ASM_PARAM_ID_AC3_DRC_BOOST_SCALEFACTOR 0x00010D79 + +/* ID of the AC3 DRC cut scale factor parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +#define ASM_PARAM_ID_AC3_DRC_CUT_SCALEFACTOR 0x00010D7A + +/* Structure for AC3 Generic Parameter. */ + +/* Payload of the AC3 parameters in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +struct asm_ac3_generic_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u32 generic_parameter; +/* AC3 generic parameter. Select from one of the following + * possible values. + * + * For #ASM_PARAM_ID_AC3_KARAOKE_MODE, supported values are: + * - AC3_KARAOKE_MODE_NO_VOCAL + * - AC3_KARAOKE_MODE_LEFT_VOCAL + * - AC3_KARAOKE_MODE_RIGHT_VOCAL + * - AC3_KARAOKE_MODE_BOTH_VOCAL + * + * For #ASM_PARAM_ID_AC3_DRC_MODE, supported values are: + * - AC3_DRC_MODE_CUSTOM_ANALOG + * - AC3_DRC_MODE_CUSTOM_DIGITAL + * - AC3_DRC_MODE_LINE_OUT + * - AC3_DRC_MODE_RF_REMOD + * + * For #ASM_PARAM_ID_AC3_DUAL_MONO_MODE, supported values are: + * - AC3_DUAL_MONO_MODE_STEREO + * - AC3_DUAL_MONO_MODE_LEFT_MONO + * - AC3_DUAL_MONO_MODE_RIGHT_MONO + * - AC3_DUAL_MONO_MODE_MIXED_MONO + * + * For #ASM_PARAM_ID_AC3_STEREO_DOWNMIX_MODE, supported values are: + * - AC3_STEREO_DOWNMIX_MODE_AUTO_DETECT + * - AC3_STEREO_DOWNMIX_MODE_LT_RT + * - AC3_STEREO_DOWNMIX_MODE_LO_RO + * + * For #ASM_PARAM_ID_AC3_PCM_SCALEFACTOR, supported values are + * 0 to 1 in Q31 format. + * + * For #ASM_PARAM_ID_AC3_DRC_BOOST_SCALEFACTOR, supported values are + * 0 to 1 in Q31 format. + * + * For #ASM_PARAM_ID_AC3_DRC_CUT_SCALEFACTOR, supported values are + * 0 to 1 in Q31 format. + */ +} __packed; + +/* Enumeration for Raw mode (no downmixing), which specifies + * that all channels in the bitstream are to be played out as is + * without any downmixing. (Default) + */ + +#define WMAPRO_CHANNEL_MASK_RAW (-1) + +/* Enumeration for setting the channel mask to 0. The 7.1 mode + * (Home Theater) is assigned. + */ + + +#define WMAPRO_CHANNEL_MASK_ZERO 0x0000 + +/* Speaker layout mask for one channel (Home Theater, mono). + * - Speaker front center + */ +#define WMAPRO_CHANNEL_MASK_1_C 0x0004 + +/* Speaker layout mask for two channels (Home Theater, stereo). + * - Speaker front left + * - Speaker front right + */ +#define WMAPRO_CHANNEL_MASK_2_L_R 0x0003 + +/* Speaker layout mask for three channels (Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + */ +#define WMAPRO_CHANNEL_MASK_3_L_C_R 0x0007 + +/* Speaker layout mask for two channels (stereo). + * - Speaker back left + * - Speaker back right + */ +#define WMAPRO_CHANNEL_MASK_2_Bl_Br 0x0030 + +/* Speaker layout mask for four channels. + * - Speaker front left + * - Speaker front right + * - Speaker back left + * - Speaker back right +*/ +#define WMAPRO_CHANNEL_MASK_4_L_R_Bl_Br 0x0033 + +/* Speaker layout mask for four channels (Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back center +*/ +#define WMAPRO_CHANNEL_MASK_4_L_R_C_Bc_HT 0x0107 +/* Speaker layout mask for five channels. + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + */ +#define WMAPRO_CHANNEL_MASK_5_L_C_R_Bl_Br 0x0037 + +/* Speaker layout mask for five channels (5 mode, Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker side left + * - Speaker side right + */ +#define WMAPRO_CHANNEL_MASK_5_L_C_R_Sl_Sr_HT 0x0607 +/* Speaker layout mask for six channels (5.1 mode). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker back left + * - Speaker back right + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Bl_Br_SLF 0x003F +/* Speaker layout mask for six channels (5.1 mode, Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker side left + * - Speaker side right + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Sl_Sr_SLF_HT 0x060F +/* Speaker layout mask for six channels (5.1 mode, no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + * - Speaker back center + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Bl_Br_Bc 0x0137 +/* Speaker layout mask for six channels (5.1 mode, Home Theater, + * no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back center + * - Speaker side left + * - Speaker side right + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Sl_Sr_Bc_HT 0x0707 + +/* Speaker layout mask for seven channels (6.1 mode). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker back left + * - Speaker back right + * - Speaker back center + */ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Bl_Br_Bc_SLF 0x013F + +/* Speaker layout mask for seven channels (6.1 mode, Home + * Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker back center + * - Speaker side left + * - Speaker side right +*/ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Sl_Sr_Bc_SLF_HT 0x070F + +/* Speaker layout mask for seven channels (6.1 mode, no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + * - Speaker front left of center + * - Speaker front right of center +*/ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Bl_Br_SFLOC_SFROC 0x00F7 + +/* Speaker layout mask for seven channels (6.1 mode, Home + * Theater, no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker side left + * - Speaker side right + * - Speaker front left of center + * - Speaker front right of center +*/ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Sl_Sr_SFLOC_SFROC_HT 0x0637 + +/* Speaker layout mask for eight channels (7.1 mode). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + * - Speaker low frequency + * - Speaker front left of center + * - Speaker front right of center + */ +#define WMAPRO_CHANNEL_MASK_7DOT1_L_C_R_Bl_Br_SLF_SFLOC_SFROC \ + 0x00FF + +/* Speaker layout mask for eight channels (7.1 mode, Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker side left + * - Speaker side right + * - Speaker low frequency + * - Speaker front left of center + * - Speaker front right of center + * +*/ +#define WMAPRO_CHANNEL_MASK_7DOT1_L_C_R_Sl_Sr_SLF_SFLOC_SFROC_HT \ + 0x063F + +#define ASM_PARAM_ID_DEC_OUTPUT_CHAN_MAP 0x00010D82 + +/* Maximum number of decoder output channels.*/ +#define MAX_CHAN_MAP_CHANNELS 16 + +/* Structure for decoder output channel mapping. */ + +/* Payload of the #ASM_PARAM_ID_DEC_OUTPUT_CHAN_MAP parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +struct asm_dec_out_chan_map_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u32 num_channels; +/* Number of decoder output channels. + * Supported values: 0 to #MAX_CHAN_MAP_CHANNELS + * + * A value of 0 indicates native channel mapping, which is valid + * only for NT mode. This means the output of the decoder is to be + * preserved as is. + */ + u8 channel_mapping[MAX_CHAN_MAP_CHANNELS]; +} __packed; + +#define ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED 0x00010D84 + +/* Bitmask for the IEC 61937 enable flag.*/ +#define ASM_BIT_MASK_IEC_61937_STREAM_FLAG (0x00000001UL) + +/* Shift value for the IEC 61937 enable flag.*/ +#define ASM_SHIFT_IEC_61937_STREAM_FLAG 0 + +/* Bitmask for the IEC 60958 enable flag.*/ +#define ASM_BIT_MASK_IEC_60958_STREAM_FLAG (0x00000002UL) + +/* Shift value for the IEC 60958 enable flag.*/ +#define ASM_SHIFT_IEC_60958_STREAM_FLAG 1 + +/* Payload format for open write compressed comand */ + +/* Payload format for the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED + * comand, which opens a stream for a given session ID and stream ID + * to be rendered in the compressed format. + */ + +struct asm_stream_cmd_open_write_compressed { + struct apr_hdr hdr; + u32 flags; +/* Mode flags that configure the stream for a specific format. + * Supported values: + * - Bit 0 -- IEC 61937 compatibility + * - 0 -- Stream is not in IEC 61937 format + * - 1 -- Stream is in IEC 61937 format + * - Bit 1 -- IEC 60958 compatibility + * - 0 -- Stream is not in IEC 60958 format + * - 1 -- Stream is in IEC 60958 format + * - Bits 2 to 31 -- 0 (Reserved) + * + * For the same stream, bit 0 cannot be set to 0 and bit 1 cannot + * be set to 1. A compressed stream connot have IEC 60958 + * packetization applied without IEC 61937 packetization. + * @note1hang Currently, IEC 60958 packetized input streams are not + * supported. + */ + + + u32 fmt_id; +/* Specifies the media type of the HDMI stream to be opened. + * Supported values: + * - #ASM_MEDIA_FMT_AC3_DEC + * - #ASM_MEDIA_FMT_EAC3_DEC + * - #ASM_MEDIA_FMT_DTS + * - #ASM_MEDIA_FMT_ATRAC + * - #ASM_MEDIA_FMT_MAT + * + * @note1hang This field must be set to a valid media type even if + * IEC 61937 packetization is not performed by the aDSP. + */ + +} __packed; + +#define ASM_STREAM_CMD_OPEN_READ_COMPRESSED 0x00010D95 + +struct asm_stream_cmd_open_read_compressed { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags that indicate whether meta information per encoded + * frame is to be provided. + * Supported values for bit 4: + * - 0 -- Return data buffer contains all encoded frames only; it does + * not contain frame metadata. + * - 1 -- Return data buffer contains an array of metadata and encoded + * frames. + * - Use #ASM_BIT_MASK_META_INFO_FLAG to set the bitmask and + * #ASM_SHIFT_META_INFO_FLAG to set the shift value for this bit. + * All other bits are reserved; clients must set them to zero. + */ + + u32 frames_per_buf; +/* Indicates the number of frames that need to be returned per + * read buffer + * Supported values: should be greater than 0 + */ + +} __packed; + +/* adsp_asm_stream_commands.h*/ + + +/* adsp_asm_api.h (no changes)*/ +#define ASM_STREAM_POSTPROCOPO_ID_DEFAULT \ + 0x00010BE4 +#define ASM_STREAM_POSTPROCOPO_ID_PEAKMETER \ + 0x00010D83 +#define ASM_STREAM_POSTPROCOPO_ID_NONE \ + 0x00010C68 +#define ASM_STREAM_POSTPROCOPO_ID_MCH_PEAK_VOL \ + 0x00010D8B +#define ASM_STREAM_PREPROCOPO_ID_DEFAULT \ + ASM_STREAM_POSTPROCOPO_ID_DEFAULT +#define ASM_STREAM_PREPROCOPO_ID_NONE \ + ASM_STREAM_POSTPROCOPO_ID_NONE +#define ADM_CMD_COPP_OPENOPOLOGY_ID_NONE_AUDIO_COPP \ + 0x00010312 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_MONO_AUDIO_COPP \ + 0x00010313 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_AUDIO_COPP \ + 0x00010314 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_IIR_AUDIO_COPP\ + 0x00010704 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_MONO_AUDIO_COPP_MBDRCV2\ + 0x0001070D +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_AUDIO_COPP_MBDRCV2\ + 0x0001070E +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_IIR_AUDIO_COPP_MBDRCV2\ + 0x0001070F +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_MCH_PEAK_VOL \ + 0x0001031B +#define ADM_CMD_COPP_OPENOPOLOGY_ID_MIC_MONO_AUDIO_COPP 0x00010315 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_MIC_STEREO_AUDIO_COPP 0x00010316 +#define AUDPROC_COPPOPOLOGY_ID_MCHAN_IIR_AUDIO 0x00010715 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_DEFAULT_AUDIO_COPP 0x00010BE3 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_PEAKMETER_AUDIO_COPP 0x00010317 +#define AUDPROC_MODULE_ID_AIG 0x00010716 +#define AUDPROC_PARAM_ID_AIG_ENABLE 0x00010717 +#define AUDPROC_PARAM_ID_AIG_CONFIG 0x00010718 + +struct Audio_AigParam { + uint16_t mode; +/*< Mode word for enabling AIG/SIG mode . + * Byte offset: 0 + */ + int16_t staticGainL16Q12; +/*< Static input gain when aigMode is set to 1. + * Byte offset: 2 + */ + int16_t initialGainDBL16Q7; +/* + +/* + * 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 RT_PROXY_PORT_001_RX 0x2000 /* index = 30 */ +#define RT_PROXY_PORT_001_TX 0x2001 /* index = 31 */ + +#define AFE_PORT_INVALID 0xFFFF +#define SLIMBUS_EXTPROC_RX AFE_PORT_INVALID + +#define AFE_PORT_CMD_START 0x000100ca + +#define AFE_EVENT_RTPORT_START 0 +#define AFE_EVENT_RTPORT_STOP 1 +#define AFE_EVENT_RTPORT_LOW_WM 2 +#define AFE_EVENT_RTPORT_HI_WM 3 + +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 format; /* don't touch this field if it is not for */ + /* AFE_PORT_CMD_I2S_CONFIG opcode */ +} __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_Linear = 1 */ +} __attribute__ ((packed)); + + +struct afe_port_hdmi_multi_ch_cfg { + u16 data_type; /* HDMI_Linear = 0 */ + /* HDMI_non_Linear = 1 */ + u16 channel_allocation; /* The default is 0 (Stereo) */ + u16 reserved; /* must be set to 0 */ +} __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; + +struct afe_port_slimbus_sch_cfg { + u16 slimbus_dev_id; /* SLIMBUS Device id.*/ + u16 bit_width; /** bit width of the samples, 16, 24.*/ + u16 data_format; /** data format.*/ + u16 num_channels; /** Number of channels.*/ + u16 reserved; + /** Slave channel mapping for respective channels.*/ + u8 slave_ch_mapping[8]; +} __packed; + +struct afe_port_rtproxy_cfg { + u16 bitwidth; /* 16,24,32 */ + u16 interleaved; /* interleaved = 1 */ + /* Noninterleaved = 0 */ + u16 frame_sz; /* 5ms buffers = 160bytes */ + u16 jitter; /* 10ms of jitter = 320 */ + u16 lw_mark; /* Low watermark in bytes for triggering event*/ + u16 hw_mark; /* High watermark bytes for triggering event*/ + u16 rsvd; + int num_ch; /* 1 to 8 */ +} __packed; + +#define AFE_PORT_AUDIO_IF_CONFIG 0x000100d3 +#define AFE_PORT_AUDIO_SLIM_SCH_CONFIG 0x000100e4 +#define AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG 0x000100D9 +#define AFE_PORT_CMD_I2S_CONFIG 0x000100E7 + +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_hdmi_multi_ch_cfg hdmi_multi_ch; + struct afe_port_slimbus_cfg slimbus; + struct afe_port_slimbus_sch_cfg slim_sch; + struct afe_port_rtproxy_cfg rtproxy; +} __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)); + +/* Parameter ID used to configure and enable/disable the loopback path. The + * difference with respect to the existing API, AFE_PORT_CMD_LOOPBACK, is that + * it allows Rx port to be configured as source port in loopback path. Port-id + * in AFE_PORT_CMD_SET_PARAM cmd is the source port whcih can be Tx or Rx port. + * In addition, we can configure the type of routing mode to handle different + * use cases. +*/ +enum { + /* Regular loopback from source to destination port */ + LB_MODE_DEFAULT = 1, + /* Sidetone feed from Tx source to Rx destination port */ + LB_MODE_SIDETONE, + /* Echo canceller reference, voice + audio + DTMF */ + LB_MODE_EC_REF_VOICE_AUDIO, + /* Echo canceller reference, voice alone */ + LB_MODE_EC_REF_VOICE +}; + +#define AFE_PARAM_ID_LOOPBACK_CONFIG 0x0001020B +#define AFE_API_VERSION_LOOPBACK_CONFIG 0x1 +struct afe_param_loopback_cfg { + /* Minor version used for tracking the version of the configuration + * interface. + */ + uint32_t loopback_cfg_minor_version; + + /* Destination Port Id. */ + uint16_t dst_port_id; + + /* Specifies data path type from src to dest port. Supported values: + * LB_MODE_DEFAULT + * LB_MODE_SIDETONE + * LB_MODE_EC_REF_VOICE_AUDIO + * LB_MODE_EC_REF_VOICE + */ + uint16_t routing_mode; + + /* Specifies whether to enable (1) or disable (0) an AFE loopback. */ + uint16_t enable; + + /* Reserved for 32-bit alignment. This field must be set to 0. */ + uint16_t reserved; +} __packed; + +#define AFE_MODULE_ID_PORT_INFO 0x00010200 +/* Module ID for the loopback-related parameters. */ +#define AFE_MODULE_LOOPBACK 0x00010205 +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; + struct afe_param_loopback_cfg loopback_cfg; + } __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)); + +struct afe_port_cmd_set_param_no_payload { + struct apr_hdr hdr; + u16 port_id; + u16 payload_size; + u32 payload_address; +} __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 AFE_SERVICE_CMD_MEMORY_MAP 0x000100DE +struct afe_cmd_memory_map { + struct apr_hdr hdr; + u32 phy_addr; + u32 mem_sz; + u16 mem_id; + u16 rsvd; +} __packed; + +#define AFE_SERVICE_CMD_MEMORY_UNMAP 0x000100DF +struct afe_cmd_memory_unmap { + struct apr_hdr hdr; + u32 phy_addr; +} __packed; + +#define AFE_SERVICE_CMD_REG_RTPORT 0x000100E0 +struct afe_cmd_reg_rtport { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; +} __packed; + +#define AFE_SERVICE_CMD_UNREG_RTPORT 0x000100E1 +struct afe_cmd_unreg_rtport { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; +} __packed; + +#define AFE_SERVICE_CMD_RTPORT_WR 0x000100E2 +struct afe_cmd_rtport_wr { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; + u32 buf_addr; + u32 bytes_avail; +} __packed; + +#define AFE_SERVICE_CMD_RTPORT_RD 0x000100E3 +struct afe_cmd_rtport_rd { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; + u32 buf_addr; + u32 bytes_avail; +} __packed; + +#define AFE_EVENT_RT_PROXY_PORT_STATUS 0x00010105 + +#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_MULTI_CHANNEL_COPP_OPEN 0x00010310 +struct adm_multi_ch_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; + u8 dev_channel_mapping[8]; +} __packed; + +#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 VPM_TX_QMIC_FLUENCE_COPP_TOPOLOGY 0x00010F75 + +/* SRS TRUMEDIA GUIDS */ +/* topology */ +#define SRS_TRUMEDIA_TOPOLOGY_ID 0x00010D90 +/* module */ +#define SRS_TRUMEDIA_MODULE_ID 0x10005010 +/* parameters */ +#define SRS_TRUMEDIA_PARAMS 0x10005011 +#define SRS_TRUMEDIA_PARAMS_WOWHD 0x10005012 +#define SRS_TRUMEDIA_PARAMS_CSHP 0x10005013 +#define SRS_TRUMEDIA_PARAMS_HPF 0x10005014 +#define SRS_TRUMEDIA_PARAMS_PEQ 0x10005015 +#define SRS_TRUMEDIA_PARAMS_HL 0x10005016 + +#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 SOFT_VOLUME_PARAM_ID 0x00010C29 + +#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 ADM_CMDRSP_MULTI_CHANNEL_COPP_OPEN 0x00010311 + + +#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; +}; + +#define PCM_CHANNEL_NULL 0 + +/* Front left channel. */ +#define PCM_CHANNEL_FL 1 + +/* Front right channel. */ +#define PCM_CHANNEL_FR 2 + +/* Front center channel. */ +#define PCM_CHANNEL_FC 3 + +/* Left surround channel.*/ +#define PCM_CHANNEL_LS 4 + +/* Right surround channel.*/ +#define PCM_CHANNEL_RS 5 + +/* Low frequency effect channel. */ +#define PCM_CHANNEL_LFE 6 + +/* Center surround channel; Rear center channel. */ +#define PCM_CHANNEL_CS 7 + +/* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_LB 8 + +/* Right back channel; Rear right channel. */ +#define PCM_CHANNEL_RB 9 + +/* Top surround channel. */ +#define PCM_CHANNEL_TS 10 + +/* Center vertical height channel.*/ +#define PCM_CHANNEL_CVH 11 + +/* Mono surround channel.*/ +#define PCM_CHANNEL_MS 12 + +/* Front left of center. */ +#define PCM_CHANNEL_FLC 13 + +/* Front right of center. */ +#define PCM_CHANNEL_FRC 14 + +/* Rear left of center. */ +#define PCM_CHANNEL_RLC 15 + +/* Rear right of center. */ +#define PCM_CHANNEL_RRC 16 + +#define PCM_FORMAT_MAX_NUM_CHANNEL 8 + +/* Maximum number of channels supported + * in ASM_ENCDEC_DEC_CHAN_MAP command + */ +#define MAX_CHAN_MAP_CHANNELS 16 +/* + * Multiple-channel PCM decoder format block structure used in the + * #ASM_STREAM_CMD_OPEN_WRITE command. + * The data must be in little-endian format. + */ +struct asm_multi_channel_pcm_fmt_blk { + + u16 num_channels; /* + * Number of channels. + * Supported values:1 to 8 + */ + + u16 bits_per_sample; /* + * Number of bits per sample per channel. + * Supported values: 16, 24 When used for + * playback, the client must send 24-bit + * samples packed in 32-bit words. The + * 24-bit samples must be placed in the most + * significant 24 bits of the 32-bit word. When + * used for recording, the aDSP sends 24-bit + * samples packed in 32-bit words. The 24-bit + * samples are placed in the most significant + * 24 bits of the 32-bit word. + */ + + u32 sample_rate; /* + * Number of samples per second + * (in Hertz). Supported values: + * 2000 to 48000 + */ + + u16 is_signed; /* + * Flag that indicates the samples + * are signed (1). + */ + + u16 is_interleaved; /* + * Flag that indicates whether the channels are + * de-interleaved (0) or interleaved (1). + * Interleaved format means corresponding + * samples from the left and right channels are + * interleaved within the buffer. + * De-interleaved format means samples from + * each channel are contiguous in the buffer. + * The samples from one channel immediately + * follow those of the previous channel. + */ + + u8 channel_mapping[8]; /* + * Supported values: + * PCM_CHANNEL_NULL, PCM_CHANNEL_FL, + * PCM_CHANNEL_FR, PCM_CHANNEL_FC, + * PCM_CHANNEL_LS, PCM_CHANNEL_RS, + * PCM_CHANNEL_LFE, PCM_CHANNEL_CS, + * PCM_CHANNEL_LB, PCM_CHANNEL_RB, + * PCM_CHANNEL_TS, PCM_CHANNEL_CVH, + * PCM_CHANNEL_MS, PCM_CHANNEL_FLC, + * PCM_CHANNEL_FRC, PCM_CHANNEL_RLC, + * PCM_CHANNEL_RRC. + * Channel[i] mapping describes channel I. Each + * element i of the array describes channel I + * inside the buffer where I < num_channels. + * An unused channel is set to zero. + */ +}; + +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_amrwb_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_dual_mono { + u16 sce_left; + u16 sce_right; +}; + +struct asm_dec_chan_map { + u32 num_channels; /* Number of decoder output + * channels. A value of 0 + * indicates native channel + * mapping, which is valid + * only for NT mode. This + * means the output of the + * decoder is to be preserved + * as is. + */ + + u8 channel_mapping[MAX_CHAN_MAP_CHANNELS];/* Channel array of size + * num_channels. It can grow + * till MAX_CHAN_MAP_CHANNELS. + * Channel[i] mapping + * describes channel I inside + * the decoder output buffer. + * Valid channel mapping + * values are to be present at + * the beginning of the array. + * All remaining elements of + * the array are to be filled + * with PCM_CHANNEL_NULL. + */ +}; + +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; + struct asm_amrwb_read_cfg amrwb; + struct asm_multi_channel_pcm_fmt_blk mpcm; + } __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 AMRWB_FS 0x00010BEC +#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 EAC3_DECODER 0x00010C3C +#define DTS 0x00010D88 +#define ATRAC 0x00010D89 +#define MAT 0x00010D8A +#define G711_ALAW_FS 0x00010BF7 +#define G711_MLAW_FS 0x00010BF8 +#define G711_PCM_FS 0x00010BF9 +#define MPEG4_MULTI_AAC 0x00010D86 +#define US_POINT_EPOS_FORMAT 0x00012310 +#define US_RAW_FORMAT 0x0001127C +#define MULTI_CHANNEL_PCM 0x00010C66 + +#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 IEC_61937_MASK 0x00000001 +#define IEC_60958_MASK 0x00000002 + +#define ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED 0x00010D84 +struct asm_stream_cmd_open_write_compressed { + struct apr_hdr hdr; + u32 flags; + u32 format; +} __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 ADM_CMD_CONNECT_AFE_PORT 0x00010320 + +struct adm_cmd_connect_afe_port { + struct apr_hdr hdr; + u8 mode; /*mode represent the interface is for RX or TX*/ + u8 session_id; /*ASM session ID*/ + u16 afe_port_id; +} __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 +#define ASM_CONFIGURE_DUAL_MONO 0x00010C64 +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)); + +struct asm_stream_cmd_encdec_dualmono { + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_dual_mono channel_map; +} __packed; + +#define ASM_ENCDEC_DEC_CHAN_MAP 0x00010D82 +struct asm_stream_cmd_encdec_channelmap { + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_dec_chan_map chan_map; +} __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_ENC_SR_CM_NOTIFY 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; + struct asm_multi_channel_pcm_fmt_blk multi_ch_pcm_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*/ + +/* SRS TRUMEDIA start */ +#define SRS_ID_GLOBAL 0x00000001 +#define SRS_ID_WOWHD 0x00000002 +#define SRS_ID_CSHP 0x00000003 +#define SRS_ID_HPF 0x00000004 +#define SRS_ID_PEQ 0x00000005 +#define SRS_ID_HL 0x00000006 + +#define SRS_CMD_UPLOAD 0x7FFF0000 +#define SRS_PARAM_INDEX_MASK 0x80000000 +#define SRS_PARAM_OFFSET_MASK 0x3FFF0000 +#define SRS_PARAM_VALUE_MASK 0x0000FFFF + +struct srs_trumedia_params_GLOBAL { + uint8_t v1; + uint8_t v2; + uint8_t v3; + uint8_t v4; + uint8_t v5; + uint8_t v6; + uint8_t v7; + uint8_t v8; +} __packed; + +struct srs_trumedia_params_WOWHD { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v4; + uint16_t v5; + uint16_t v6; + uint16_t v7; + uint16_t v8; + uint16_t v____A1; + uint32_t v9; + uint16_t v10; + uint16_t v11; + uint32_t v12[16]; +} __packed; + +struct srs_trumedia_params_CSHP { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v4; + uint16_t v5; + uint16_t v6; + uint16_t v____A1; + uint32_t v7; + uint16_t v8; + uint16_t v9; + uint32_t v10[16]; +} __packed; + +struct srs_trumedia_params_HPF { + uint32_t v1; + uint32_t v2[26]; +} __packed; + +struct srs_trumedia_params_PEQ { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v4; + uint16_t v____A1; + uint32_t v5[26]; + uint32_t v6[26]; +} __packed; + +struct srs_trumedia_params_HL { + uint16_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v____A1; + int32_t v4; + uint32_t v5; + uint16_t v6; + uint16_t v____A2; + uint32_t v7; +} __packed; + +struct srs_trumedia_params { + struct srs_trumedia_params_GLOBAL global; + struct srs_trumedia_params_WOWHD wowhd; + struct srs_trumedia_params_CSHP cshp; + struct srs_trumedia_params_HPF hpf; + struct srs_trumedia_params_PEQ peq; + struct srs_trumedia_params_HL hl; +} __packed; +int srs_trumedia_open(int port_id, int srs_tech_id, void *srs_params); +/* SRS TruMedia end */ + +#endif /*_APR_AUDIO_H_*/ diff --git a/include/sound/compress_offload.h b/include/sound/compress_offload.h index 05341a43fedf3d9eec2c30293a2b66032b817ca3..8d36c429eb0e93884a2f7e017b1b464a5ed9fb0d 100644 --- a/include/sound/compress_offload.h +++ b/include/sound/compress_offload.h @@ -70,6 +70,7 @@ struct snd_compr_tstamp { snd_pcm_uframes_t pcm_frames; snd_pcm_uframes_t pcm_io_frames; __u32 sampling_rate; + uint64_t timestamp; }; /** diff --git a/include/sound/compress_params.h b/include/sound/compress_params.h index da4a456de032fb24dd75132f033f8c104b1b710a..5aa7b0943424e1c4c1114b9013bf2ba90679d2a2 100644 --- a/include/sound/compress_params.h +++ b/include/sound/compress_params.h @@ -51,8 +51,6 @@ #ifndef __SND_COMPRESS_PARAMS_H #define __SND_COMPRESS_PARAMS_H -#include - /* AUDIO CODECS SUPPORTED */ #define MAX_NUM_CODECS 32 #define MAX_NUM_CODEC_DESCRIPTORS 32 @@ -72,7 +70,10 @@ #define SND_AUDIOCODEC_IEC61937 ((__u32) 0x0000000B) #define SND_AUDIOCODEC_G723_1 ((__u32) 0x0000000C) #define SND_AUDIOCODEC_G729 ((__u32) 0x0000000D) - +#define SND_AUDIOCODEC_AC3 ((__u32) 0x0000000E) +#define SND_AUDIOCODEC_DTS ((__u32) 0x0000000F) +#define SND_AUDIOCODEC_AC3_PASS_THROUGH ((__u32) 0x00000010) +#define SND_AUDIOCODEC_WMA_PRO ((__u32) 0x00000011) /* * Profile and modes are listed with bit masks. This allows for a * more compact representation of fields that will not evolve @@ -237,6 +238,9 @@ struct snd_enc_wma { __u32 super_block_align; /* WMA Type-specific data */ + __u32 bits_per_sample; + __u32 channelmask; + __u32 encodeopt; }; diff --git a/include/sound/control.h b/include/sound/control.h index 8332e865c7592c6a3eb48406bc319195c3efd193..1318164354f1b3c60fa6acbfacab885b15af6437 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -40,7 +40,7 @@ struct snd_kcontrol_new { snd_ctl_elem_iface_t iface; /* interface identifier */ unsigned int device; /* device/client number */ unsigned int subdevice; /* subdevice (substream) number */ - const unsigned char *name; /* ASCII name of item */ + unsigned char *name; /* ASCII name of item */ unsigned int index; /* index of item */ unsigned int access; /* access rights */ unsigned int count; /* count of same elements */ diff --git a/include/sound/cs8427.h b/include/sound/cs8427.h index f862cfff5f6a618daa1057836d30c9a73e0b5653..2004ec3378f64008f85f8c69d537d7845f9d1dd8 100644 --- a/include/sound/cs8427.h +++ b/include/sound/cs8427.h @@ -108,6 +108,7 @@ #define CS8427_SIDEL (1<<2) /* Delay of SDIN data relative to ILRCK for left-justified data formats, 0 = first ISCLK period, 1 = second ISCLK period */ #define CS8427_SISPOL (1<<1) /* ICLK clock polarity, 0 = rising edge of ISCLK, 1 = falling edge of ISCLK */ #define CS8427_SILRPOL (1<<0) /* ILRCK clock polarity, 0 = SDIN data left channel when ILRCK is high, 1 = SDIN right when ILRCK is high */ +#define CS8427_BITWIDTH_MASK 0xCF /* CS8427_REG_SERIALOUTPUT */ #define CS8427_SOMS (1<<7) /* 0 = slave, 1 = master mode */ @@ -186,6 +187,31 @@ #define CS8427_VERSHIFT 0 #define CS8427_VER8427A 0x71 +/* possible address cs8427 can take + * based on the below combinations the upper four bits of 7bit + * address will be fixed for 0010b, abd lower 3 bits will decide + * the address combination based on the AD0 and AD1 and EMPH(AD2) + * Hardware pin configuration to cs8427 chip + */ +#define CS8427_ADDR0 0x10 +#define CS8427_ADDR1 0x11 +#define CS8427_ADDR2 0x12 +#define CS8427_ADDR3 0x13 +#define CS8427_ADDR4 0x14 +#define CS8427_ADDR5 0x15 +#define CS8427_ADDR6 0x16 +#define CS8427_ADDR7 0x17 + +#define CHANNEL_STATUS_SIZE 24 + +struct cs8427_platform_data { + int irq; + int irq_base; + int num_irqs; + int reset_gpio; + int (*enable) (int enable); +}; + struct snd_pcm_substream; int snd_cs8427_create(struct snd_i2c_bus *bus, unsigned char addr, @@ -197,5 +223,4 @@ int snd_cs8427_iec958_build(struct snd_i2c_device *cs8427, struct snd_pcm_substream *capture_substream); int snd_cs8427_iec958_active(struct snd_i2c_device *cs8427, int active); int snd_cs8427_iec958_pcm(struct snd_i2c_device *cs8427, unsigned int rate); - #endif /* __SOUND_CS8427_H */ diff --git a/include/sound/dai.h b/include/sound/dai.h new file mode 100644 index 0000000000000000000000000000000000000000..4d3fb960a87f3c4a154c6e1275826405ca94c2f8 --- /dev/null +++ b/include/sound/dai.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 __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); +int wait_for_dma_cnt_stop(uint32_t dma_ch); +void dai_stop_hdmi(uint32_t dma_ch); + +#endif diff --git a/include/sound/jack.h b/include/sound/jack.h index 58916573db582eab1d19a6d751ebe516c194b834..1089ba41b1172395e522e1eae18e1961d3e28873 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -35,27 +35,29 @@ struct input_dev; * sound/core/jack.c. */ enum snd_jack_types { - SND_JACK_HEADPHONE = 0x0001, - SND_JACK_MICROPHONE = 0x0002, + SND_JACK_HEADPHONE = 0x0000001, + SND_JACK_MICROPHONE = 0x0000002, SND_JACK_HEADSET = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE, - SND_JACK_LINEOUT = 0x0004, - SND_JACK_MECHANICAL = 0x0008, /* If detected separately */ - SND_JACK_VIDEOOUT = 0x0010, + SND_JACK_LINEOUT = 0x0000004, + SND_JACK_MECHANICAL = 0x0000008, /* If detected separately */ + SND_JACK_VIDEOOUT = 0x0000010, SND_JACK_AVOUT = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT, - SND_JACK_LINEIN = 0x0020, - + /* */ + SND_JACK_LINEIN = 0x0000020, + SND_JACK_OC_HPHL = 0x0000040, + SND_JACK_OC_HPHR = 0x0000080, + SND_JACK_UNSUPPORTED = 0x0000100, /* Kept separate from switches to facilitate implementation */ - SND_JACK_BTN_0 = 0x4000, - SND_JACK_BTN_1 = 0x2000, - SND_JACK_BTN_2 = 0x1000, - SND_JACK_BTN_3 = 0x0800, - SND_JACK_BTN_4 = 0x0400, - SND_JACK_BTN_5 = 0x0200, + SND_JACK_BTN_0 = 0x4000000, + SND_JACK_BTN_1 = 0x2000000, + SND_JACK_BTN_2 = 0x1000000, + SND_JACK_BTN_3 = 0x0800000, + SND_JACK_BTN_4 = 0x0400000, + SND_JACK_BTN_5 = 0x0200000, + SND_JACK_BTN_6 = 0x0100000, + SND_JACK_BTN_7 = 0x0080000, }; -/* Keep in sync with definitions above */ -#define SND_JACK_SWITCH_TYPES 6 - struct snd_jack { struct input_dev *input_dev; int registered; diff --git a/include/sound/msm-dai-q6-v2.h b/include/sound/msm-dai-q6-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..3d5ffdd85446a44f50726020fbc3fd5cdaceda80 --- /dev/null +++ b/include/sound/msm-dai-q6-v2.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_DAI_Q6_PDATA_H__ + +#define __MSM_DAI_Q6_PDATA_H__ + +#define MSM_MI2S_SD0 (1 << 0) +#define MSM_MI2S_SD1 (1 << 1) +#define MSM_MI2S_SD2 (1 << 2) +#define MSM_MI2S_SD3 (1 << 3) +#define MSM_MI2S_CAP_RX 0 +#define MSM_MI2S_CAP_TX 1 + +struct msm_dai_auxpcm_pdata { + const char *clk; + u16 mode; + u16 sync; + u16 frame; + u16 quant; + /* modify slot to arr[4] to specify + * the slot number for each channel + * in multichannel scenario */ + u16 slot; + u16 data; + int pcm_clk_rate; +}; + +struct msm_i2s_data { + u32 capability; /* RX or TX */ + u16 sd_lines; +}; +#endif diff --git a/include/sound/msm-dai-q6.h b/include/sound/msm-dai-q6.h new file mode 100644 index 0000000000000000000000000000000000000000..042aa6f19f2483a319de7627f033e8bd79d1dfa5 --- /dev/null +++ b/include/sound/msm-dai-q6.h @@ -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. + */ + +#ifndef __MSM_DAI_Q6_PDATA_H__ + +#define __MSM_DAI_Q6_PDATA_H__ + +#define MSM_MI2S_SD0 (1 << 0) +#define MSM_MI2S_SD1 (1 << 1) +#define MSM_MI2S_SD2 (1 << 2) +#define MSM_MI2S_SD3 (1 << 3) +#define MSM_MI2S_CAP_RX 0 +#define MSM_MI2S_CAP_TX 1 + +struct msm_dai_auxpcm_config { + u16 mode; + u16 sync; + u16 frame; + u16 quant; + u16 slot; + u16 data; + int pcm_clk_rate; +}; + +struct msm_mi2s_pdata { + u16 rx_sd_lines; + u16 tx_sd_lines; +}; + +struct msm_dai_auxpcm_pdata { + const char *clk; + struct msm_dai_auxpcm_config mode_8k; + struct msm_dai_auxpcm_config mode_16k; +}; + +#endif diff --git a/include/sound/omap-abe-dsp.h b/include/sound/omap-abe-dsp.h new file mode 100644 index 0000000000000000000000000000000000000000..60c405d48c61f33b3fda83cf0190e49dd41b2b17 --- /dev/null +++ b/include/sound/omap-abe-dsp.h @@ -0,0 +1,19 @@ +/* + * omap-aess -- OMAP4 ABE DSP + * + * 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _OMAP4_ABE_DSP_H +#define _OMAP4_ABE_DSP_H + +struct omap4_abe_dsp_pdata { + /* Return context loss count due to PM states changing */ + int (*get_context_loss_count)(struct device *dev); +}; + +#endif diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 0d1112815be3be6ea96a5ec1768382bb70aea571..6cb456e8da111db4c3d38f9c73186bfe5d4bf636 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -264,7 +264,7 @@ struct snd_pcm_hw_constraint_ratdens { struct snd_pcm_hw_constraint_list { unsigned int count; - const unsigned int *list; + unsigned int *list; unsigned int mask; }; @@ -413,6 +413,7 @@ struct snd_pcm_substream { #endif /* misc flags */ unsigned int hw_opened: 1; + unsigned int hw_no_buffer: 1; /* substream may not have a buffer */ }; #define SUBSTREAM_BUSY(substream) ((substream)->ref_count > 0) @@ -454,7 +455,6 @@ struct snd_pcm { void *private_data; void (*private_free) (struct snd_pcm *pcm); struct device *dev; /* actual hw device this belongs to */ - bool internal; /* pcm is for internal use only */ #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) struct snd_pcm_oss oss; #endif @@ -476,9 +476,9 @@ extern const struct file_operations snd_pcm_f_ops[2]; int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm); -int snd_pcm_new_internal(struct snd_card *card, const char *id, int device, +int snd_pcm_new_soc_be(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, - struct snd_pcm **rpcm); + struct snd_pcm ** rpcm); int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count); int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree); @@ -785,8 +785,7 @@ void snd_interval_muldivk(const struct snd_interval *a, const struct snd_interva unsigned int k, struct snd_interval *c); void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k, const struct snd_interval *b, struct snd_interval *c); -int snd_interval_list(struct snd_interval *i, unsigned int count, - const unsigned int *list, unsigned int mask); +int snd_interval_list(struct snd_interval *i, unsigned int count, unsigned int *list, unsigned int mask); int snd_interval_ratnum(struct snd_interval *i, unsigned int rats_count, struct snd_ratnum *rats, unsigned int *nump, unsigned int *denp); diff --git a/include/sound/q6adm-v2.h b/include/sound/q6adm-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..cb2f3d7f1ff68b025bc57e22684520226a11266a --- /dev/null +++ b/include/sound/q6adm-v2.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_V2_H__ +#define __Q6_ADM_V2_H__ + + +#define ADM_PATH_PLAYBACK 0x1 +#define ADM_PATH_LIVE_REC 0x2 +#define ADM_PATH_NONLIVE_REC 0x3 +#include + +#define Q6_AFE_MAX_PORTS 32 + +/* multiple copp per stream. */ +struct route_payload { + unsigned int copp_ids[Q6_AFE_MAX_PORTS]; + unsigned short num_copps; + unsigned int session_id; +}; + +int adm_open(int port, int path, int rate, int mode, int topology); + +int adm_multi_ch_copp_open(int port, int path, int rate, int mode, + int topology); + +int adm_memory_map_regions(int port_id, uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt); + +int adm_memory_unmap_regions(int port_id, 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); + +int adm_connect_afe_port(int mode, int session_id, int port_id); + +int adm_get_copp_id(int port_id); + +#endif /* __Q6_ADM_V2_H__ */ diff --git a/include/sound/q6adm.h b/include/sound/q6adm.h new file mode 100644 index 0000000000000000000000000000000000000000..29fb6061427c703625976e9b4865ac9c2233f283 --- /dev/null +++ b/include/sound/q6adm.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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(int port, int path, int rate, int mode, int topology); + +int adm_multi_ch_copp_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); + +int adm_connect_afe_port(int mode, int session_id, int port_id); + +#ifdef CONFIG_RTAC +int adm_get_copp_id(int port_id); +#endif + +#endif /* __Q6_ADM_H__ */ diff --git a/include/sound/q6afe-v2.h b/include/sound/q6afe-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..1587d38e781cbb2c155bdb4a3d6e525ade6accec --- /dev/null +++ b/include/sound/q6afe-v2.h @@ -0,0 +1,107 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_V2_H__ +#define __Q6AFE_V2_H__ +#include + +#define MSM_AFE_MONO 0 +#define MSM_AFE_MONO_RIGHT 1 +#define MSM_AFE_MONO_LEFT 2 +#define MSM_AFE_STEREO 3 +#define MSM_AFE_4CHANNELS 4 +#define MSM_AFE_6CHANNELS 6 +#define MSM_AFE_8CHANNELS 8 + +#define MSM_AFE_I2S_FORMAT_LPCM 0 +#define MSM_AFE_I2S_FORMAT_COMPR 1 +#define MSM_AFE_I2S_FORMAT_IEC60958_LPCM 2 +#define MSM_AFE_I2S_FORMAT_IEC60958_COMPR 3 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 + +#define RT_PROXY_DAI_001_RX 0xE0 +#define RT_PROXY_DAI_001_TX 0xF0 +#define RT_PROXY_DAI_002_RX 0xF1 +#define RT_PROXY_DAI_002_TX 0xE1 +#define VIRTUAL_ID_TO_PORTID(val) ((val & 0xF) | 0x2000) + +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, + IDX_RT_PROXY_PORT_001_RX = 30, + IDX_RT_PROXY_PORT_001_TX = 31, + 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_start_pseudo_port(u16 port_id); +int afe_stop_pseudo_port(u16 port_id); +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_map_nowait(int port_id, u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_unmap(u32 dma_addr_p); +int afe_cmd_memory_unmap_nowait(u32 dma_addr_p); + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data); +int afe_unregister_get_events(u16 port_id); +int afe_rt_proxy_port_write(u32 buf_addr_p, u32 mem_map_handle, int bytes); +int afe_rt_proxy_port_read(u32 buf_addr_p, u32 mem_map_handle, int bytes); +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate); +int afe_port_stop_nowait(int port_id); +int afe_apply_gain(u16 port_id, u16 gain); +int afe_q6_interface_prepare(void); +int afe_get_port_type(u16 port_id); +/* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ +int afe_convert_virtual_to_portid(u16 port_id); + +int afe_pseudo_port_start_nowait(u16 port_id); +int afe_pseudo_port_stop_nowait(u16 port_id); +#endif /* __Q6AFE_V2_H__ */ diff --git a/include/sound/q6afe.h b/include/sound/q6afe.h new file mode 100644 index 0000000000000000000000000000000000000000..8cdcc18c69a6dc5c0d0fd75714942a3f53bb4283 --- /dev/null +++ b/include/sound/q6afe.h @@ -0,0 +1,109 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#define MSM_AFE_4CHANNELS 4 +#define MSM_AFE_6CHANNELS 6 +#define MSM_AFE_8CHANNELS 8 + +#define MSM_AFE_I2S_FORMAT_LPCM 0 +#define MSM_AFE_I2S_FORMAT_COMPR 1 +#define MSM_AFE_I2S_FORMAT_IEC60958_LPCM 2 +#define MSM_AFE_I2S_FORMAT_IEC60958_COMPR 3 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 + +#define RT_PROXY_DAI_001_RX 0xE0 +#define RT_PROXY_DAI_001_TX 0xF0 +#define RT_PROXY_DAI_002_RX 0xF1 +#define RT_PROXY_DAI_002_TX 0xE1 +#define VIRTUAL_ID_TO_PORTID(val) ((val & 0xF) | 0x2000) + +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, + IDX_RT_PROXY_PORT_001_RX = 30, + IDX_RT_PROXY_PORT_001_TX = 31, + 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_loopback_cfg(u16 enable, u16 dst_port, u16 src_port, u16 mode); +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_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_map_nowait(u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_unmap(u32 dma_addr_p); +int afe_cmd_memory_unmap_nowait(u32 dma_addr_p); + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data); +int afe_unregister_get_events(u16 port_id); +int afe_rt_proxy_port_write(u32 buf_addr_p, int bytes); +int afe_rt_proxy_port_read(u32 buf_addr_p, int bytes); +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate); +int afe_port_stop_nowait(int port_id); +int afe_apply_gain(u16 port_id, u16 gain); +int afe_q6_interface_prepare(void); +int afe_get_port_type(u16 port_id); +/* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ +int afe_convert_virtual_to_portid(u16 port_id); + +int afe_pseudo_port_start_nowait(u16 port_id); +int afe_pseudo_port_stop_nowait(u16 port_id); +#endif /* __Q6AFE_H__ */ diff --git a/include/sound/q6asm-v2.h b/include/sound/q6asm-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..7ef15ac029cc3c23714748cd9342f67aac3fc2f6 --- /dev/null +++ b/include/sound/q6asm-v2.h @@ -0,0 +1,303 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_V2_H__ +#define __Q6_ASM_V2_H__ + +#include +#include +#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 FORMAT_MPEG4_MULTI_AAC 0x0011 +#define FORMAT_MULTI_CHANNEL_LINEAR_PCM 0x0012 + +#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 + +#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, +}; + +#define SOFT_VOLUME_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_VOLUME_STEP 2000 /* Step value 2ms or 2000us */ +enum { + SOFT_VOLUME_CURVE_LINEAR = 0, + SOFT_VOLUME_CURVE_EXP, + SOFT_VOLUME_CURVE_LOG, +}; + +typedef void (*app_cb)(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t used; + uint32_t size;/* size of buffer */ + uint32_t actual_size; /* actual number of bytes read by DSP */ + struct ion_handle *handle; + struct ion_client *client; +}; + +struct audio_aio_write_param { + unsigned long paddr; + uint32_t len; + uint32_t uid; + uint32_t lsw_ts; + uint32_t msw_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; + struct list_head mem_map_handle; + uint32_t tmp_hdl; + /* read or write locks */ + struct mutex lock; + spinlock_t dsp_lock; +}; + +struct audio_client { + int session; + app_cb cb; + atomic_t cmd_state; + /* Relative or absolute TS */ + uint32_t time_flag; + void *priv; + uint32_t io_mode; + uint64_t time_stamp; + struct apr_svc *apr; + struct apr_svc *mmap_apr; + struct mutex cmd_lock; + /* idx:1 out port, 0: in port*/ + struct audio_port_data port[2]; + wait_queue_head_t cmd_wait; +}; + +void q6asm_audio_client_free(struct audio_client *ac); + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv); + +struct audio_client *q6asm_get_audio_client(int session_id); + +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 + /*, uint16_t bits_per_sample*/); + +int q6asm_open_write(struct audio_client *ac, uint32_t format + /*, uint16_t bits_per_sample*/); + +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); + +void *q6asm_is_cpu_buf_avail_nolock(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_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels); + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps); + +int q6asm_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right); + +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_enc_cfg_blk_amrwb(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_multi_ch_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_multi_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); + +/* Set Softvolume Params */ +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_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); + +/* Get Service ID for APR communication */ +int q6asm_get_apr_service_id(int session_id); + +/* Common format block without any payload +*/ +int q6asm_media_format_block(struct audio_client *ac, uint32_t format); + +#endif /* __Q6_ASM_H__ */ diff --git a/include/sound/q6asm.h b/include/sound/q6asm.h new file mode 100644 index 0000000000000000000000000000000000000000..54a91872514f91f481e193d8e96abfc415ad2880 --- /dev/null +++ b/include/sound/q6asm.h @@ -0,0 +1,318 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +#include +#endif + +#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 FORMAT_MPEG4_MULTI_AAC 0x0011 +#define FORMAT_MULTI_CHANNEL_LINEAR_PCM 0x0012 +#define FORMAT_AC3 0x0013 +#define FORMAT_DTS 0x0014 +#define FORMAT_EAC3 0x0015 +#define FORMAT_ATRAC 0x0016 +#define FORMAT_MAT 0x0017 +#define FORMAT_AAC 0x0018 + +#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 + +#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, +}; + +#define SOFT_VOLUME_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_VOLUME_STEP 2000 /* Step value 2ms or 2000us */ +enum { + SOFT_VOLUME_CURVE_LINEAR = 0, + SOFT_VOLUME_CURVE_EXP, + SOFT_VOLUME_CURVE_LOG, +}; + +typedef void (*app_cb)(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t used; + uint32_t size;/* size of buffer */ + uint32_t actual_size; /* actual number of bytes read by DSP */ +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + struct ion_handle *handle; + struct ion_client *client; +#else + void *mem_buffer; +#endif +}; + +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); + +struct audio_client *q6asm_get_audio_client(int session_id); + +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_write_compressed(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); + +void *q6asm_is_cpu_buf_avail_nolock(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_enc_cfg_blk_multi_ch_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_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right); + +int q6asm_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels); + +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_enc_cfg_blk_amrwb(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_multi_ch_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_multi_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); + +/* Set Softvolume Params */ +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_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_RTAC +/* Get Service ID for APR communication */ +int q6asm_get_apr_service_id(int session_id); +#endif + +/* Common format block without any payload +*/ +int q6asm_media_format_block(struct audio_client *ac, uint32_t format); + +#endif /* __Q6_ASM_H__ */ diff --git a/include/sound/q6audio-v2.h b/include/sound/q6audio-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..1a5dce18406a5ab2ae28036037593ff1a6518b79 --- /dev/null +++ b/include/sound/q6audio-v2.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_AUDIO_H_ +#define _Q6_AUDIO_H_ + +#include + +int q6audio_get_port_index(u16 port_id); + +int q6audio_convert_virtual_to_portid(u16 port_id); + +int q6audio_validate_port(u16 port_id); + +int q6audio_get_port_id(u16 port_id); + +#endif diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index c429f248cf4e995a6a8aa20eaa1ec8af71ddfe74..4676a02e28a7513ce33e424f439a11a5f1a0eeac 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -2,6 +2,7 @@ * linux/sound/soc-dai.h -- ALSA SoC Layer * * Copyright: 2005-2008 Wolfson Microelectronics. PLC. + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. * * 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 @@ -122,6 +123,10 @@ int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, unsigned int tx_num, unsigned int *tx_slot, unsigned int rx_num, unsigned int *rx_slot); +int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot); + int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate); /* Digital Audio Interface mute */ @@ -151,6 +156,9 @@ struct snd_soc_dai_ops { unsigned int rx_num, unsigned int *rx_slot); int (*set_tristate)(struct snd_soc_dai *dai, int tristate); + int (*get_channel_map)(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot); /* * DAI digital mute - optional. * Called by soc-core to minimise any pops. @@ -173,6 +181,8 @@ struct snd_soc_dai_ops { struct snd_soc_dai *); int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *); + int (*bespoke_trigger)(struct snd_pcm_substream *, int, + struct snd_soc_dai *); /* * For hardware based FIFO caused delay reporting. * Optional. @@ -257,6 +267,13 @@ struct snd_soc_dai { struct list_head list; struct list_head card_list; + + /* runtime AIF widget and channel mmap updates */ + u64 playback_channel_map; + u64 capture_channel_map; + struct snd_soc_dapm_widget *playback_aif; + struct snd_soc_dapm_widget *capture_aif; + bool channel_map_instanciated; }; static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai, @@ -287,4 +304,98 @@ static inline void *snd_soc_dai_get_drvdata(struct snd_soc_dai *dai) return dev_get_drvdata(dai->dev); } +/* Backend DAI PCM ops */ +static inline int snd_soc_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0; + + mutex_lock(&rtd->pcm_mutex); + + if (dai->driver->ops->startup) + ret = dai->driver->ops->startup(substream, dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_active++; + else + dai->capture_active++; + + dai->active++; + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static inline void snd_soc_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + mutex_lock(&rtd->pcm_mutex); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_active--; + else + dai->capture_active--; + + dai->active--; + + if (dai->driver->ops->shutdown) + dai->driver->ops->shutdown(substream, dai); + mutex_unlock(&rtd->pcm_mutex); +} + +static inline int snd_soc_dai_hw_params(struct snd_pcm_substream * substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0; + + mutex_lock(&rtd->pcm_mutex); + + if (dai->driver->ops->hw_params) + ret = dai->driver->ops->hw_params(substream, hw_params, dai); + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static inline int snd_soc_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0; + + mutex_lock(&rtd->pcm_mutex); + + if (dai->driver->ops->hw_free) + ret = dai->driver->ops->hw_free(substream, dai); + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static inline int snd_soc_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0; + + mutex_lock(&rtd->pcm_mutex); + + if (dai->driver->ops->prepare) + ret = dai->driver->ops->prepare(substream, dai); + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static inline int snd_soc_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + if (dai->driver->ops->trigger) + return dai->driver->ops->trigger(substream, cmd, dai); + return 0; +} #endif diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 8da3c2409060dc258cb5a87716fbb4fa7cdb527e..fed2e0a8900cbea327aa72a0457b8dac46f48c06 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -13,11 +13,10 @@ #ifndef __LINUX_SND_SOC_DAPM_H #define __LINUX_SND_SOC_DAPM_H +#include #include #include -struct device; - /* widget has no PM register bit */ #define SND_SOC_NOPM -1 @@ -244,10 +243,6 @@ struct device; { .id = snd_soc_dapm_supply, .name = wname, .reg = wreg, \ .shift = wshift, .invert = winvert, .event = wevent, \ .event_flags = wflags} -#define SND_SOC_DAPM_REGULATOR_SUPPLY(wname, wdelay) \ -{ .id = snd_soc_dapm_regulator_supply, .name = wname, \ - .reg = SND_SOC_NOPM, .shift = wdelay, .event = dapm_regulator_event, \ - .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD } /* dapm kcontrol types */ #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \ @@ -275,6 +270,12 @@ struct device; .put = snd_soc_dapm_put_enum_virt, \ .private_value = (unsigned long)&xenum } #define SOC_DAPM_ENUM_EXT(xname, xenum, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = xget, \ + .put = xput, \ + .private_value = (unsigned long)&xenum } +#define SOC_DAPM_ENUM_EXT(xname, xenum, xget, xput) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_enum_double, \ .get = xget, \ @@ -324,11 +325,10 @@ struct snd_soc_dapm_path; struct snd_soc_dapm_pin; struct snd_soc_dapm_route; struct snd_soc_dapm_context; +struct snd_soc_dapm_widget_list; int dapm_reg_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); -int dapm_regulator_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event); /* dapm controls */ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, @@ -353,12 +353,11 @@ int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uncontrol); int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uncontrol); +int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num); -int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, - struct snd_soc_dai *dai); -int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card); /* dapm path setup */ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm); @@ -369,15 +368,19 @@ int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_route *route, int num); /* dapm events */ -int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, - struct snd_soc_dai *dai, int event); +void snd_soc_dapm_codec_stream_event(struct snd_soc_codec *codec, + const char *stream, int event); +int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, + const char *stream, int event); +void snd_soc_dapm_rtd_stream_event(struct snd_soc_pcm_runtime *rtd, + int stream, int event); void snd_soc_dapm_shutdown(struct snd_soc_card *card); - /* external DAPM widget events */ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int connect); int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, - struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e); + struct snd_kcontrol *kcontrol, int change, + int mux, struct soc_enum *e); /* dapm sys fs - used by the core */ int snd_soc_dapm_sys_add(struct device *dev); @@ -402,6 +405,15 @@ void snd_soc_dapm_auto_nc_codec_pins(struct snd_soc_codec *codec); /* Mostly internal - should not normally be used */ void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason); +struct snd_soc_dapm_widget *snd_soc_get_codec_widget(struct snd_soc_card *card, + struct snd_soc_codec *codec, const char *name); +struct snd_soc_dapm_widget *snd_soc_get_platform_widget(struct snd_soc_card *card, + struct snd_soc_platform *platform, const char *name); + +/* dapm path query */ +int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, + struct snd_soc_dapm_widget_list **list); + /* dapm widget types */ enum snd_soc_dapm_type { snd_soc_dapm_input = 0, /* input pin */ @@ -425,11 +437,9 @@ enum snd_soc_dapm_type { snd_soc_dapm_pre, /* machine specific pre widget - exec first */ snd_soc_dapm_post, /* machine specific post widget - exec last */ snd_soc_dapm_supply, /* power/clock supply */ - snd_soc_dapm_regulator_supply, /* external regulator */ snd_soc_dapm_aif_in, /* audio interface input */ snd_soc_dapm_aif_out, /* audio interface output */ snd_soc_dapm_siggen, /* signal generator */ - snd_soc_dapm_dai, /* link to DAI structure */ }; /* @@ -450,8 +460,8 @@ struct snd_soc_dapm_route { /* dapm audio path between two widgets */ struct snd_soc_dapm_path { - const char *name; - const char *long_name; + char *name; + char *long_name; /* source (input) and sink (output) widgets */ struct snd_soc_dapm_widget *source; @@ -474,15 +484,14 @@ struct snd_soc_dapm_path { /* dapm widget */ struct snd_soc_dapm_widget { enum snd_soc_dapm_type id; - const char *name; /* widget name */ + char *name; /* widget name */ const char *sname; /* stream name */ struct snd_soc_codec *codec; struct snd_soc_platform *platform; + struct snd_soc_dai *dai; struct list_head list; struct snd_soc_dapm_context *dapm; - void *priv; /* widget specific data */ - /* dapm control */ short reg; /* negative reg = no direct dapm */ unsigned char shift; /* bits to shift */ @@ -502,6 +511,7 @@ struct snd_soc_dapm_widget { unsigned char new_power:1; /* power from this run */ unsigned char power_checked:1; /* power checked this run */ int subseq; /* sort within widget type */ + void *private_data; /* for widget specific data */ int (*power_check)(struct snd_soc_dapm_widget *w); @@ -549,6 +559,7 @@ struct snd_soc_dapm_context { struct device *dev; /* from parent - for debug */ struct snd_soc_codec *codec; /* parent codec */ struct snd_soc_platform *platform; /* parent platform */ + struct snd_soc_dai *dai; /* parent DAI */ struct snd_soc_card *card; /* parent card */ /* used during DAPM updates */ @@ -574,4 +585,16 @@ struct snd_soc_dapm_stats { int neighbour_checks; }; +/* Accessors for snd_soc_dapm_widget->private_data */ +static inline void *snd_soc_dapm_widget_get_pdata(struct snd_soc_dapm_widget *w) +{ + return w->private_data; +} + +static inline void snd_soc_dapm_widget_set_pdata(struct snd_soc_dapm_widget *w, + void *data) +{ + w->private_data = data; +} + #endif diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h new file mode 100644 index 0000000000000000000000000000000000000000..1f99cbad44c9dc2ef4f436645f691598f57f8556 --- /dev/null +++ b/include/sound/soc-dpcm.h @@ -0,0 +1,109 @@ +/* + * linux/sound/soc-dpcm.h -- ALSA SoC Dynamic PCM Support + * + * 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_SOC_DPCM_H +#define __LINUX_SND_SOC_DPCM_H + +#include + +/* + * Types of runtime_update to perform (e.g. originated from FE PCM ops + * or audio route changes triggered by muxes/mixers. + */ +#define SND_SOC_DPCM_UPDATE_NO 0 +#define SND_SOC_DPCM_UPDATE_BE 1 +#define SND_SOC_DPCM_UPDATE_FE 2 + +/* + * Dynamic PCM Frontend -> Backend link state. + */ +enum snd_soc_dpcm_link_state { + SND_SOC_DPCM_LINK_STATE_NEW = 0, /* newly created path */ + SND_SOC_DPCM_LINK_STATE_FREE, /* path to be dismantled */ +}; + +/* + * Dynamic PCM params link + * This links together a FE and BE DAI at runtime and stores the link + * state information and the hw_params configuration. + */ +struct snd_soc_dpcm_params { + /* FE and BE DAIs*/ + struct snd_soc_pcm_runtime *be; + struct snd_soc_pcm_runtime *fe; + + /* link state */ + enum snd_soc_dpcm_link_state state; + + struct list_head list_be; + struct list_head list_fe; + + /* hw params for this link - may be different for each link */ + struct snd_pcm_hw_params hw_params; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_state; +#endif +}; + +/* + * Bespoke Trigger() Helper API + */ + +/* is the PCM operation for this FE ? */ +static inline int snd_soc_dpcm_fe_can_update(struct snd_soc_pcm_runtime *fe, + int stream) +{ + return (fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE); +} + +/* is the PCM operation for this BE ? */ +static inline int snd_soc_dpcm_be_can_update(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + if ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) || + ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_BE) && + be->dpcm[stream].runtime_update)) + return 1; + else + return 0; +} + +/* trigger platform driver only */ +static inline int + snd_soc_dpcm_platform_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_platform *platform) +{ + if (platform->driver->ops->trigger) + return platform->driver->ops->trigger(substream, cmd); + return 0; +} + +int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream); + +static inline struct snd_pcm_substream * + snd_soc_dpcm_get_substream(struct snd_soc_pcm_runtime *be, int stream) +{ + return be->pcm->streams[stream].substream; +} + +static inline enum snd_soc_dpcm_state + snd_soc_dpcm_be_get_state(struct snd_soc_pcm_runtime *be, int stream) +{ + return be->dpcm[stream].state; +} + +static inline void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, + int stream, enum snd_soc_dpcm_state state) +{ + be->dpcm[stream].state = state; +} +#endif diff --git a/include/sound/soc.h b/include/sound/soc.h index 2ebf7877c148ae717727269a1bb91cab8124b2e9..7886e840bcebf078fdfb5fefebf1b58864724b75 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -55,6 +55,16 @@ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ .put = snd_soc_put_volsw, \ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } +#define SOC_SINGLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ + .put = snd_soc_put_volsw_s8, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .min = xmin, .max = xmax, \ + .platform_max = xmax} } #define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ @@ -131,6 +141,14 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = \ SOC_DOUBLE_VALUE(reg, shift_left, shift_right, max, invert) } + #define SOC_SINGLE_MULTI_EXT(xname, xreg, xshift, xmax, xinvert, xcount,\ + xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_multi_ext, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct soc_multi_mixer_control) \ + {.reg = xreg, .shift = xshift, .rshift = xshift, .max = xmax, \ + .count = xcount, .platform_max = xmax, .invert = xinvert} } #define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -185,20 +203,6 @@ .rreg = xreg_right, .shift = xshift, \ .min = xmin, .max = xmax} } -#define SND_SOC_BYTES(xname, xbase, xregs) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ - .put = snd_soc_bytes_put, .private_value = \ - ((unsigned long)&(struct soc_bytes) \ - {.base = xbase, .num_regs = xregs }) } - -#define SND_SOC_BYTES_MASK(xname, xbase, xregs, xmask) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ - .put = snd_soc_bytes_put, .private_value = \ - ((unsigned long)&(struct soc_bytes) \ - {.base = xbase, .num_regs = xregs, \ - .mask = xmask }) } /* * Simplified versions of above macros, declaring a struct and calculating @@ -217,6 +221,15 @@ #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 + +#define snd_soc_get_enum_text(soc_enum, idx) \ + (soc_enum->texts ? soc_enum->texts[idx] : soc_enum->dtexts[idx]) + + /* * Component probe and remove ordering levels for components with runtime * dependencies. @@ -263,6 +276,7 @@ struct snd_soc_jack; struct snd_soc_jack_zone; struct snd_soc_jack_pin; struct snd_soc_cache_ops; +struct snd_soc_dpcm_link; #include #ifdef CONFIG_GPIOLIB @@ -288,6 +302,35 @@ enum snd_soc_pcm_subclass { SND_SOC_PCM_CLASS_BE = 1, }; +/* + * Dynamic PCM DAI link states. + */ +enum snd_soc_dpcm_state { + SND_SOC_DPCM_STATE_NEW = 0, + SND_SOC_DPCM_STATE_OPEN, + SND_SOC_DPCM_STATE_HW_PARAMS, + SND_SOC_DPCM_STATE_PREPARE, + SND_SOC_DPCM_STATE_START, + SND_SOC_DPCM_STATE_STOP, + SND_SOC_DPCM_STATE_PAUSED, + SND_SOC_DPCM_STATE_SUSPEND, + SND_SOC_DPCM_STATE_HW_FREE, + SND_SOC_DPCM_STATE_CLOSE, +}; + +/* + * Dynamic PCM trigger ordering. Triggering flexibility is required as some + * DSPs require triggering before/after their clients/hosts. + * + * i.e. some clients may want to manually order this call in their PCM + * trigger() whilst others will just use the regular core ordering. + */ +enum snd_soc_dpcm_trigger { + SND_SOC_DPCM_TRIGGER_PRE = 0, + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_BESPOKE, +}; + int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id, int source, unsigned int freq, int dir); int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source, @@ -333,6 +376,11 @@ int snd_soc_platform_write(struct snd_soc_platform *platform, unsigned int reg, unsigned int val); int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num); +struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, + const char *dai_link, int stream); +struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, + const char *dai_link); + /* Utility functions to get clock rates from various things */ int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots); int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params); @@ -347,6 +395,8 @@ int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type, struct snd_soc_jack *jack); void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask); +void snd_soc_jack_report_no_dapm(struct snd_soc_jack *jack, int status, + int mask); int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, struct snd_soc_jack_pin *pins); void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, @@ -380,7 +430,7 @@ void snd_soc_free_ac97_codec(struct snd_soc_codec *codec); *Controls */ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, - void *data, const char *long_name, + void *data, char *long_name, const char *prefix); int snd_soc_add_codec_controls(struct snd_soc_codec *codec, const struct snd_kcontrol_new *controls, int num_controls); @@ -404,6 +454,8 @@ int snd_soc_put_value_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); +int snd_soc_info_multi_ext(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); #define snd_soc_info_bool_ext snd_ctl_boolean_mono_info @@ -427,13 +479,6 @@ int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); -int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo); -int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); -int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol); - /** * struct snd_soc_reg_access - Describes whether a given register is @@ -524,6 +569,7 @@ struct snd_soc_jack { /* SoC PCM stream information */ struct snd_soc_pcm_stream { const char *stream_name; + const char *aif_name; /* DAPM AIF widget name */ u64 formats; /* SNDRV_PCM_FMTBIT_* */ unsigned int rates; /* SNDRV_PCM_RATE_* */ unsigned int rate_min; /* min rate */ @@ -664,8 +710,6 @@ struct snd_soc_codec_driver { /* codec stream completion event */ int (*stream_event)(struct snd_soc_dapm_context *dapm, int event); - bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */ - /* probe ordering - for components with runtime dependencies */ int probe_order; int remove_order; @@ -711,6 +755,8 @@ struct snd_soc_platform_driver { /* platform IO - used for platform DAPM */ unsigned int (*read)(struct snd_soc_platform *, unsigned int); int (*write)(struct snd_soc_platform *, unsigned int, unsigned int); + + int (*bespoke_trigger)(struct snd_pcm_substream *, int); }; struct snd_soc_platform { @@ -718,7 +764,6 @@ struct snd_soc_platform { int id; struct device *dev; struct snd_soc_platform_driver *driver; - struct mutex mutex; unsigned int suspended:1; /* platform is suspended */ unsigned int probed:1; @@ -749,11 +794,21 @@ struct snd_soc_dai_link { unsigned int dai_fmt; /* format to set on init */ + enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */ + /* Keep DAI active over suspend */ unsigned int ignore_suspend:1; /* Symmetry requirements */ unsigned int symmetric_rates:1; + /* No PCM created for this DAI link */ + unsigned int no_pcm:1; + /* This DAI link can change CODEC and platform at runtime*/ + unsigned int dynamic:1; + /* This DAI has a Backend ID */ + unsigned int be_id; + /* This DAI can support no host IO (no pcm data is copied to from host) */ + unsigned int no_host_mode:2; /* pmdown_time is ignored at stop */ unsigned int ignore_pmdown_time:1; @@ -761,6 +816,10 @@ struct snd_soc_dai_link { /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_pcm_runtime *rtd); + /* hw_params re-writing for BE and FE sync */ + int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params); + /* machine stream operations */ struct snd_soc_ops *ops; }; @@ -800,6 +859,12 @@ struct snd_soc_card { struct list_head list; struct mutex mutex; + struct mutex dpcm_mutex; + + struct mutex dapm_mutex; + struct mutex dapm_power_mutex; + struct mutex dsp_mutex; + spinlock_t dsp_spinlock; bool instantiated; @@ -829,6 +894,8 @@ struct snd_soc_card { int num_links; struct snd_soc_pcm_runtime *rtd; int num_rtd; + int num_playback_channels; + int num_capture_channels; /* optional codec specific configuration */ struct snd_soc_codec_conf *codec_conf; @@ -880,6 +947,17 @@ struct snd_soc_card { void *drvdata; }; +/* DSP runtime data */ +struct snd_soc_dpcm_runtime { + struct list_head be_clients; + struct list_head fe_clients; + int users; + struct snd_pcm_runtime *runtime; + struct snd_pcm_hw_params hw_params; + int runtime_update; + enum snd_soc_dpcm_state state; +}; + /* SoC machine DAI configuration, glues a codec and cpu DAI together */ struct snd_soc_pcm_runtime { struct device *dev; @@ -892,6 +970,9 @@ struct snd_soc_pcm_runtime { unsigned int complete:1; unsigned int dev_registered:1; + /* Dynamic PCM BE runtime data */ + struct snd_soc_dpcm_runtime dpcm[2]; + long pmdown_time; /* runtime devices */ @@ -902,6 +983,11 @@ struct snd_soc_pcm_runtime { struct snd_soc_dai *cpu_dai; struct delayed_work delayed_work; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_dpcm_root; + struct dentry *debugfs_dpcm_state; +#endif }; /* mixer control */ @@ -909,13 +995,12 @@ struct soc_mixer_control { int min, max, platform_max; unsigned int reg, rreg, shift, rshift, invert; }; - -struct soc_bytes { - int base; - int num_regs; - u32 mask; +struct soc_multi_mixer_control { + int min, max, platform_max, count; + unsigned int reg, rreg, shift, rshift, invert; }; + /* enumerated kcontrol */ struct soc_enum { unsigned short reg; @@ -925,6 +1010,7 @@ struct soc_enum { unsigned int max; unsigned int mask; const char * const *texts; + char **dtexts; const unsigned int *values; void *dapm; }; diff --git a/init/Kconfig b/init/Kconfig index a7cffc8359649fd6faa3a8814ec3c2d92f1fe4f3..b2126bc7dbdab284b4767a380025fe2a2d93db89 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1132,6 +1132,15 @@ config SHMEM option replaces shmem and tmpfs with the much simpler ramfs code, which may be appropriate on small systems without swap. +config ASHMEM + bool "Enable the Anonymous Shared Memory Subsystem" + default n + depends on SHMEM || TINY_SHMEM + help + The ashmem subsystem is a new shared memory allocator, similar to + POSIX SHM but with different behavior and sporting a simpler + file-based API. + config AIO bool "Enable AIO support" if EXPERT default y @@ -1270,6 +1279,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/init/main.c b/init/main.c index 44b2433334c749ed92d7e42e5938f840efd843c0..737ab05f4add1ae02dca2f76eb2a2cf3e491e162 100644 --- a/init/main.c +++ b/init/main.c @@ -359,6 +359,7 @@ static __initdata DECLARE_COMPLETION(kthreadd_done); static noinline void __init_refok rest_init(void) { int pid; + const struct sched_param param = { .sched_priority = 1 }; rcu_scheduler_starting(); /* @@ -372,6 +373,7 @@ static noinline void __init_refok rest_init(void) rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); rcu_read_unlock(); + sched_setscheduler_nocheck(kthreadd_task, SCHED_FIFO, ¶m); complete(&kthreadd_done); /* diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 3914c1e03cfffb2bf5a3ed016ffcb7bf051c92d4..7a391df4bceee486683f2ad308768fdd198104c5 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -266,6 +266,7 @@ void handle_nested_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); struct irqaction *action; + int mask_this_irq = 0; irqreturn_t action_ret; might_sleep(); @@ -275,8 +276,10 @@ void handle_nested_irq(unsigned int irq) kstat_incr_irqs_this_cpu(irq, desc); action = desc->action; - if (unlikely(!action || irqd_irq_disabled(&desc->irq_data))) + if (unlikely(!action || irqd_irq_disabled(&desc->irq_data))) { + mask_this_irq = 1; goto out_unlock; + } irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock_irq(&desc->lock); @@ -290,6 +293,11 @@ void handle_nested_irq(unsigned int irq) out_unlock: raw_spin_unlock_irq(&desc->lock); + if (unlikely(mask_this_irq)) { + chip_bus_lock(desc); + mask_irq(desc); + chip_bus_sync_unlock(desc); + } } EXPORT_SYMBOL_GPL(handle_nested_irq); @@ -428,7 +436,8 @@ handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc) * then mask it and get out of here: */ if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { - desc->istate |= IRQS_PENDING; + if (!irq_settings_is_level(desc)) + desc->istate |= IRQS_PENDING; mask_irq(desc); goto out; } diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 0e0ba5f840b26c84affc8fe813ce4a89b51753bf..0aa96d348ab0c7d3610d99563b6c5dfd8d3e4790 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -1,780 +1,312 @@ -#include -#include -#include #include -#include #include #include #include #include #include -#include #include -#include -#include - -#define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs. - * ie. legacy 8259, gets irqs 1..15 */ -#define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */ -#define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */ -#define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */ static LIST_HEAD(irq_domain_list); static DEFINE_MUTEX(irq_domain_mutex); -static DEFINE_MUTEX(revmap_trees_mutex); -static struct irq_domain *irq_default_domain; - /** - * irq_domain_alloc() - Allocate a new irq_domain data structure - * @of_node: optional device-tree node of the interrupt controller - * @revmap_type: type of reverse mapping to use - * @ops: map/unmap domain callbacks - * @host_data: Controller private data pointer + * irq_domain_add() - Register an irq_domain + * @domain: ptr to initialized irq_domain structure * - * Allocates and initialize and irq_domain structure. Caller is expected to - * register allocated irq_domain with irq_domain_register(). Returns pointer - * to IRQ domain, or NULL on failure. + * Adds a irq_domain structure. The irq_domain must at a minimum be + * initialized with an ops structure pointer, and either a ->to_irq hook or + * a valid irq_base value. The irq range must be mutually exclusive with + * domains already registered. Everything else is optional. */ -static struct irq_domain *irq_domain_alloc(struct device_node *of_node, - unsigned int revmap_type, - const struct irq_domain_ops *ops, - void *host_data) +int irq_domain_add(struct irq_domain *domain) { - struct irq_domain *domain; - - domain = kzalloc(sizeof(*domain), GFP_KERNEL); - if (WARN_ON(!domain)) - return NULL; - - /* Fill structure */ - domain->revmap_type = revmap_type; - domain->ops = ops; - domain->host_data = host_data; - domain->of_node = of_node_get(of_node); + struct irq_domain *curr; + uint32_t d_highirq = domain->irq_base + domain->nr_irq - 1; - return domain; -} + if (!domain->nr_irq) + return -EINVAL; -static void irq_domain_add(struct irq_domain *domain) -{ mutex_lock(&irq_domain_mutex); - list_add(&domain->link, &irq_domain_list); + /* insert in ascending order of domain->irq_base */ + list_for_each_entry(curr, &irq_domain_list, list) { + uint32_t c_highirq = curr->irq_base + curr->nr_irq - 1; + if (domain->irq_base < curr->irq_base && + d_highirq < curr->irq_base) { + break; + } + if (d_highirq <= c_highirq) { + mutex_unlock(&irq_domain_mutex); + return -EINVAL; + } + } + list_add_tail(&domain->list, &curr->list); mutex_unlock(&irq_domain_mutex); - pr_debug("irq: Allocated domain of type %d @0x%p\n", - domain->revmap_type, domain); -} -static unsigned int irq_domain_legacy_revmap(struct irq_domain *domain, - irq_hw_number_t hwirq) -{ - irq_hw_number_t first_hwirq = domain->revmap_data.legacy.first_hwirq; - int size = domain->revmap_data.legacy.size; - - if (WARN_ON(hwirq < first_hwirq || hwirq >= first_hwirq + size)) - return 0; - return hwirq - first_hwirq + domain->revmap_data.legacy.first_irq; + return 0; } /** - * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain. - * @of_node: pointer to interrupt controller's device tree node. - * @size: total number of irqs in legacy mapping - * @first_irq: first number of irq block assigned to the domain - * @first_hwirq: first hwirq number to use for the translation. Should normally - * be '0', but a positive integer can be used if the effective - * hwirqs numbering does not begin at zero. - * @ops: map/unmap domain callbacks - * @host_data: Controller private data pointer + * irq_domain_register() - Register an entire irq_domain + * @domain: ptr to initialized irq_domain structure * - * Note: the map() callback will be called before this function returns - * for all legacy interrupts except 0 (which is always the invalid irq for - * a legacy controller). + * Registers the entire irq_domain. The irq_domain must at a minimum be + * initialized with an ops structure pointer, and either a ->to_irq hook or + * a valid irq_base value. Everything else is optional. */ -struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, - unsigned int size, - unsigned int first_irq, - irq_hw_number_t first_hwirq, - const struct irq_domain_ops *ops, - void *host_data) +void irq_domain_register(struct irq_domain *domain) { - struct irq_domain *domain; - unsigned int i; - - domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data); - if (!domain) - return NULL; - - domain->revmap_data.legacy.first_irq = first_irq; - domain->revmap_data.legacy.first_hwirq = first_hwirq; - domain->revmap_data.legacy.size = size; - - mutex_lock(&irq_domain_mutex); - /* Verify that all the irqs are available */ - for (i = 0; i < size; i++) { - int irq = first_irq + i; - struct irq_data *irq_data = irq_get_irq_data(irq); - - if (WARN_ON(!irq_data || irq_data->domain)) { - mutex_unlock(&irq_domain_mutex); - of_node_put(domain->of_node); - kfree(domain); - return NULL; + struct irq_data *d; + int hwirq, irq; + + irq_domain_for_each_irq(domain, hwirq, irq) { + d = irq_get_irq_data(irq); + if (!d) { + WARN(1, "error: assigning domain to non existant irq_desc"); + return; } + if (d->domain) { + /* things are broken; just report, don't clean up */ + WARN(1, "error: irq_desc already assigned to a domain"); + return; + } + d->domain = domain; + d->hwirq = hwirq; } - - /* Claim all of the irqs before registering a legacy domain */ - for (i = 0; i < size; i++) { - struct irq_data *irq_data = irq_get_irq_data(first_irq + i); - irq_data->hwirq = first_hwirq + i; - irq_data->domain = domain; - } - mutex_unlock(&irq_domain_mutex); - - for (i = 0; i < size; i++) { - int irq = first_irq + i; - int hwirq = first_hwirq + i; - - /* IRQ0 gets ignored */ - if (!irq) - continue; - - /* Legacy flags are left to default at this point, - * one can then use irq_create_mapping() to - * explicitly change them - */ - ops->map(domain, irq, hwirq); - - /* Clear norequest flags */ - irq_clear_status_flags(irq, IRQ_NOREQUEST); - } - - irq_domain_add(domain); - return domain; } /** - * irq_domain_add_linear() - Allocate and register a legacy revmap irq_domain. - * @of_node: pointer to interrupt controller's device tree node. - * @ops: map/unmap domain callbacks - * @host_data: Controller private data pointer + * irq_domain_register_irq() - Register an irq_domain + * @domain: ptr to initialized irq_domain structure + * @hwirq: irq_domain hwirq to register + * + * Registers a specific hwirq within the irq_domain. The irq_domain + * must at a minimum be initialized with an ops structure pointer, and + * either a ->to_irq hook or a valid irq_base value. Everything else is + * optional. */ -struct irq_domain *irq_domain_add_linear(struct device_node *of_node, - unsigned int size, - const struct irq_domain_ops *ops, - void *host_data) +void irq_domain_register_irq(struct irq_domain *domain, int hwirq) { - struct irq_domain *domain; - unsigned int *revmap; - - revmap = kzalloc(sizeof(*revmap) * size, GFP_KERNEL); - if (WARN_ON(!revmap)) - return NULL; + struct irq_data *d; - domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LINEAR, ops, host_data); - if (!domain) { - kfree(revmap); - return NULL; + d = irq_get_irq_data(irq_domain_to_irq(domain, hwirq)); + if (!d) { + WARN(1, "error: assigning domain to non existant irq_desc"); + return; } - domain->revmap_data.linear.size = size; - domain->revmap_data.linear.revmap = revmap; - irq_domain_add(domain); - return domain; -} - -struct irq_domain *irq_domain_add_nomap(struct device_node *of_node, - unsigned int max_irq, - const struct irq_domain_ops *ops, - void *host_data) -{ - struct irq_domain *domain = irq_domain_alloc(of_node, - IRQ_DOMAIN_MAP_NOMAP, ops, host_data); - if (domain) { - domain->revmap_data.nomap.max_irq = max_irq ? max_irq : ~0; - irq_domain_add(domain); + if (d->domain) { + /* things are broken; just report, don't clean up */ + WARN(1, "error: irq_desc already assigned to a domain"); + return; } - return domain; + d->domain = domain; + d->hwirq = hwirq; } /** - * irq_domain_add_tree() - * @of_node: pointer to interrupt controller's device tree node. - * @ops: map/unmap domain callbacks - * - * Note: The radix tree will be allocated later during boot automatically - * (the reverse mapping will use the slow path until that happens). + * irq_domain_del() - Removes a irq_domain from the system + * @domain: ptr to registered irq_domain. */ -struct irq_domain *irq_domain_add_tree(struct device_node *of_node, - const struct irq_domain_ops *ops, - void *host_data) +void irq_domain_del(struct irq_domain *domain) { - struct irq_domain *domain = irq_domain_alloc(of_node, - IRQ_DOMAIN_MAP_TREE, ops, host_data); - if (domain) { - INIT_RADIX_TREE(&domain->revmap_data.tree, GFP_KERNEL); - irq_domain_add(domain); - } - return domain; + mutex_lock(&irq_domain_mutex); + list_del(&domain->list); + mutex_unlock(&irq_domain_mutex); } /** - * irq_find_host() - Locates a domain for a given device node - * @node: device-tree node of the interrupt controller + * irq_domain_unregister() - Unregister an irq_domain + * @domain: ptr to registered irq_domain. */ -struct irq_domain *irq_find_host(struct device_node *node) +void irq_domain_unregister(struct irq_domain *domain) { - struct irq_domain *h, *found = NULL; - int rc; - - /* We might want to match the legacy controller last since - * it might potentially be set to match all interrupts in - * the absence of a device node. This isn't a problem so far - * yet though... - */ - mutex_lock(&irq_domain_mutex); - list_for_each_entry(h, &irq_domain_list, link) { - if (h->ops->match) - rc = h->ops->match(h, node); - else - rc = (h->of_node != NULL) && (h->of_node == node); + struct irq_data *d; + int hwirq, irq; - if (rc) { - found = h; - break; - } + /* Clear the irq_domain assignments */ + irq_domain_for_each_irq(domain, hwirq, irq) { + d = irq_get_irq_data(irq); + d->domain = NULL; } - mutex_unlock(&irq_domain_mutex); - return found; } -EXPORT_SYMBOL_GPL(irq_find_host); /** - * irq_set_default_host() - Set a "default" irq domain - * @domain: default domain pointer - * - * For convenience, it's possible to set a "default" domain that will be used - * whenever NULL is passed to irq_create_mapping(). It makes life easier for - * platforms that want to manipulate a few hard coded interrupt numbers that - * aren't properly represented in the device-tree. + * irq_domain_unregister_irq() - Unregister a hwirq within a irq_domain + * @domain: ptr to registered irq_domain. + * @hwirq: irq_domain hwirq to unregister. */ -void irq_set_default_host(struct irq_domain *domain) +void irq_domain_unregister_irq(struct irq_domain *domain, int hwirq) { - pr_debug("irq: Default domain set to @0x%p\n", domain); + struct irq_data *d; - irq_default_domain = domain; -} - -static int irq_setup_virq(struct irq_domain *domain, unsigned int virq, - irq_hw_number_t hwirq) -{ - struct irq_data *irq_data = irq_get_irq_data(virq); - - irq_data->hwirq = hwirq; - irq_data->domain = domain; - if (domain->ops->map(domain, virq, hwirq)) { - pr_debug("irq: -> mapping failed, freeing\n"); - irq_data->domain = NULL; - irq_data->hwirq = 0; - return -1; - } - - irq_clear_status_flags(virq, IRQ_NOREQUEST); - - return 0; + /* Clear the irq_domain assignment */ + d = irq_get_irq_data(irq_domain_to_irq(domain, hwirq)); + d->domain = NULL; } /** - * irq_create_direct_mapping() - Allocate an irq for direct mapping - * @domain: domain to allocate the irq for or NULL for default domain + * irq_domain_find_free_range() - Find an available irq range + * @from: lowest logical irq number to request from + * @cnt: number of interrupts to search for * - * This routine is used for irq controllers which can choose the hardware - * interrupt numbers they generate. In such a case it's simplest to use - * the linux irq as the hardware interrupt number. + * Finds an available logical irq range from the domains specified + * on the system. The from parameter can be used to allocate a range + * at least as great as the specified irq number. */ -unsigned int irq_create_direct_mapping(struct irq_domain *domain) +int irq_domain_find_free_range(unsigned int from, unsigned int cnt) { - unsigned int virq; - - if (domain == NULL) - domain = irq_default_domain; - - BUG_ON(domain == NULL); - WARN_ON(domain->revmap_type != IRQ_DOMAIN_MAP_NOMAP); - - virq = irq_alloc_desc_from(1, 0); - if (!virq) { - pr_debug("irq: create_direct virq allocation failed\n"); - return 0; - } - if (virq >= domain->revmap_data.nomap.max_irq) { - pr_err("ERROR: no free irqs available below %i maximum\n", - domain->revmap_data.nomap.max_irq); - irq_free_desc(virq); - return 0; - } - pr_debug("irq: create_direct obtained virq %d\n", virq); - - if (irq_setup_virq(domain, virq, virq)) { - irq_free_desc(virq); - return 0; + struct irq_domain *curr, *prev = NULL; + + if (list_empty(&irq_domain_list)) + return from; + + list_for_each_entry(curr, &irq_domain_list, list) { + if (prev == NULL) { + if ((from + cnt - 1) < curr->irq_base) + return from; + } else { + uint32_t p_next_irq = prev->irq_base + prev->nr_irq; + uint32_t start_irq; + if (from >= curr->irq_base) + continue; + if (from < p_next_irq) + start_irq = p_next_irq; + else + start_irq = from; + if ((curr->irq_base - start_irq) >= cnt) + return p_next_irq; + } + prev = curr; } + curr = list_entry(curr->list.prev, struct irq_domain, list); - return virq; + return from > curr->irq_base + curr->nr_irq ? + from : curr->irq_base + curr->nr_irq; } +#if defined(CONFIG_OF_IRQ) /** - * irq_create_mapping() - Map a hardware interrupt into linux irq space - * @domain: domain owning this hardware interrupt or NULL for default domain - * @hwirq: hardware irq number in that domain space + * irq_create_of_mapping() - Map a linux irq number from a DT interrupt spec + * + * Used by the device tree interrupt mapping code to translate a device tree + * interrupt specifier to a valid linux irq number. Returns either a valid + * linux IRQ number or 0. * - * Only one mapping per hardware interrupt is permitted. Returns a linux - * irq number. - * If the sense/trigger is to be specified, set_irq_type() should be called - * on the number returned from that call. + * When the caller no longer need the irq number returned by this function it + * should arrange to call irq_dispose_mapping(). */ -unsigned int irq_create_mapping(struct irq_domain *domain, - irq_hw_number_t hwirq) -{ - unsigned int hint; - int virq; - - pr_debug("irq: irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq); - - /* Look for default domain if nececssary */ - if (domain == NULL) - domain = irq_default_domain; - if (domain == NULL) { - printk(KERN_WARNING "irq_create_mapping called for" - " NULL domain, hwirq=%lx\n", hwirq); - WARN_ON(1); - return 0; - } - pr_debug("irq: -> using domain @%p\n", domain); - - /* Check if mapping already exists */ - virq = irq_find_mapping(domain, hwirq); - if (virq) { - pr_debug("irq: -> existing mapping on virq %d\n", virq); - return virq; - } - - /* Get a virtual interrupt number */ - if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) - return irq_domain_legacy_revmap(domain, hwirq); - - /* Allocate a virtual interrupt number */ - hint = hwirq % nr_irqs; - if (hint == 0) - hint++; - virq = irq_alloc_desc_from(hint, 0); - if (virq <= 0) - virq = irq_alloc_desc_from(1, 0); - if (virq <= 0) { - pr_debug("irq: -> virq allocation failed\n"); - return 0; - } - - if (irq_setup_virq(domain, virq, hwirq)) { - if (domain->revmap_type != IRQ_DOMAIN_MAP_LEGACY) - irq_free_desc(virq); - return 0; - } - - pr_debug("irq: irq %lu on domain %s mapped to virtual irq %u\n", - hwirq, domain->of_node ? domain->of_node->full_name : "null", virq); - - return virq; -} -EXPORT_SYMBOL_GPL(irq_create_mapping); - unsigned int irq_create_of_mapping(struct device_node *controller, const u32 *intspec, unsigned int intsize) { struct irq_domain *domain; - irq_hw_number_t hwirq; - unsigned int type = IRQ_TYPE_NONE; - unsigned int virq; - - domain = controller ? irq_find_host(controller) : irq_default_domain; - if (!domain) { -#ifdef CONFIG_MIPS - /* - * Workaround to avoid breaking interrupt controller drivers - * that don't yet register an irq_domain. This is temporary - * code. ~~~gcl, Feb 24, 2012 - * - * Scheduled for removal in Linux v3.6. That should be enough - * time. - */ - if (intsize > 0) - return intspec[0]; -#endif - printk(KERN_WARNING "irq: no irq domain found for %s !\n", - controller->full_name); - return 0; - } - - /* If domain has no translation, then we assume interrupt line */ - if (domain->ops->xlate == NULL) - hwirq = intspec[0]; - else { - if (domain->ops->xlate(domain, controller, intspec, intsize, - &hwirq, &type)) - return 0; - } + unsigned long hwirq; + unsigned int irq, type; + int rc = -EINVAL; - /* Create mapping */ - virq = irq_create_mapping(domain, hwirq); - if (!virq) - return virq; - - /* Set type if specified and different than the current one */ - if (type != IRQ_TYPE_NONE && - type != (irqd_get_trigger_type(irq_get_irq_data(virq)))) - irq_set_irq_type(virq, type); - return virq; -} -EXPORT_SYMBOL_GPL(irq_create_of_mapping); - -/** - * irq_dispose_mapping() - Unmap an interrupt - * @virq: linux irq number of the interrupt to unmap - */ -void irq_dispose_mapping(unsigned int virq) -{ - struct irq_data *irq_data = irq_get_irq_data(virq); - struct irq_domain *domain; - irq_hw_number_t hwirq; - - if (!virq || !irq_data) - return; - - domain = irq_data->domain; - if (WARN_ON(domain == NULL)) - return; - - /* Never unmap legacy interrupts */ - if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) - return; - - irq_set_status_flags(virq, IRQ_NOREQUEST); - - /* remove chip and handler */ - irq_set_chip_and_handler(virq, NULL, NULL); - - /* Make sure it's completed */ - synchronize_irq(virq); - - /* Tell the PIC about it */ - if (domain->ops->unmap) - domain->ops->unmap(domain, virq); - smp_mb(); + /* Find a domain which can translate the irq spec */ + mutex_lock(&irq_domain_mutex); + list_for_each_entry(domain, &irq_domain_list, list) { + if (!domain->ops->dt_translate) + continue; - /* Clear reverse map */ - hwirq = irq_data->hwirq; - switch(domain->revmap_type) { - case IRQ_DOMAIN_MAP_LINEAR: - if (hwirq < domain->revmap_data.linear.size) - domain->revmap_data.linear.revmap[hwirq] = 0; - break; - case IRQ_DOMAIN_MAP_TREE: - mutex_lock(&revmap_trees_mutex); - radix_tree_delete(&domain->revmap_data.tree, hwirq); - mutex_unlock(&revmap_trees_mutex); - break; + rc = domain->ops->dt_translate(domain, controller, + intspec, intsize, &hwirq, &type); + if (rc == 0) + break; } + mutex_unlock(&irq_domain_mutex); - irq_free_desc(virq); -} -EXPORT_SYMBOL_GPL(irq_dispose_mapping); - -/** - * irq_find_mapping() - Find a linux irq from an hw irq number. - * @domain: domain owning this hardware interrupt - * @hwirq: hardware irq number in that domain space - * - * This is a slow path, for use by generic code. It's expected that an - * irq controller implementation directly calls the appropriate low level - * mapping function. - */ -unsigned int irq_find_mapping(struct irq_domain *domain, - irq_hw_number_t hwirq) -{ - unsigned int i; - unsigned int hint = hwirq % nr_irqs; - - /* Look for default domain if nececssary */ - if (domain == NULL) - domain = irq_default_domain; - if (domain == NULL) + if (rc != 0) return 0; - /* legacy -> bail early */ - if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) - return irq_domain_legacy_revmap(domain, hwirq); - - /* Slow path does a linear search of the map */ - if (hint == 0) - hint = 1; - i = hint; - do { - struct irq_data *data = irq_get_irq_data(i); - if (data && (data->domain == domain) && (data->hwirq == hwirq)) - return i; - i++; - if (i >= nr_irqs) - i = 1; - } while(i != hint); - return 0; + irq = irq_domain_to_irq(domain, hwirq); + if (type != IRQ_TYPE_NONE) + irq_set_irq_type(irq, type); + pr_debug("%s: mapped hwirq=%i to irq=%i, flags=%x\n", + controller->full_name, (int)hwirq, irq, type); + return irq; } -EXPORT_SYMBOL_GPL(irq_find_mapping); +EXPORT_SYMBOL_GPL(irq_create_of_mapping); /** - * irq_radix_revmap_lookup() - Find a linux irq from a hw irq number. - * @domain: domain owning this hardware interrupt - * @hwirq: hardware irq number in that domain space + * irq_dispose_mapping() - Discard a mapping created by irq_create_of_mapping() + * @irq: linux irq number to be discarded * - * This is a fast path, for use by irq controller code that uses radix tree - * revmaps + * Calling this function indicates the caller no longer needs a reference to + * the linux irq number returned by a prior call to irq_create_of_mapping(). */ -unsigned int irq_radix_revmap_lookup(struct irq_domain *domain, - irq_hw_number_t hwirq) +void irq_dispose_mapping(unsigned int irq) { - struct irq_data *irq_data; - - if (WARN_ON_ONCE(domain->revmap_type != IRQ_DOMAIN_MAP_TREE)) - return irq_find_mapping(domain, hwirq); - - /* - * Freeing an irq can delete nodes along the path to - * do the lookup via call_rcu. - */ - rcu_read_lock(); - irq_data = radix_tree_lookup(&domain->revmap_data.tree, hwirq); - rcu_read_unlock(); - /* - * If found in radix tree, then fine. - * Else fallback to linear lookup - this should not happen in practice - * as it means that we failed to insert the node in the radix tree. + * nothing yet; will be filled when support for dynamic allocation of + * irq_descs is added to irq_domain */ - return irq_data ? irq_data->irq : irq_find_mapping(domain, hwirq); -} - -/** - * irq_radix_revmap_insert() - Insert a hw irq to linux irq number mapping. - * @domain: domain owning this hardware interrupt - * @virq: linux irq number - * @hwirq: hardware irq number in that domain space - * - * This is for use by irq controllers that use a radix tree reverse - * mapping for fast lookup. - */ -void irq_radix_revmap_insert(struct irq_domain *domain, unsigned int virq, - irq_hw_number_t hwirq) -{ - struct irq_data *irq_data = irq_get_irq_data(virq); - - if (WARN_ON(domain->revmap_type != IRQ_DOMAIN_MAP_TREE)) - return; - - if (virq) { - mutex_lock(&revmap_trees_mutex); - radix_tree_insert(&domain->revmap_data.tree, hwirq, irq_data); - mutex_unlock(&revmap_trees_mutex); - } -} - -/** - * irq_linear_revmap() - Find a linux irq from a hw irq number. - * @domain: domain owning this hardware interrupt - * @hwirq: hardware irq number in that domain space - * - * This is a fast path, for use by irq controller code that uses linear - * revmaps. It does fallback to the slow path if the revmap doesn't exist - * yet and will create the revmap entry with appropriate locking - */ -unsigned int irq_linear_revmap(struct irq_domain *domain, - irq_hw_number_t hwirq) -{ - unsigned int *revmap; - - if (WARN_ON_ONCE(domain->revmap_type != IRQ_DOMAIN_MAP_LINEAR)) - return irq_find_mapping(domain, hwirq); - - /* Check revmap bounds */ - if (unlikely(hwirq >= domain->revmap_data.linear.size)) - return irq_find_mapping(domain, hwirq); - - /* Check if revmap was allocated */ - revmap = domain->revmap_data.linear.revmap; - if (unlikely(revmap == NULL)) - return irq_find_mapping(domain, hwirq); - - /* Fill up revmap with slow path if no mapping found */ - if (unlikely(!revmap[hwirq])) - revmap[hwirq] = irq_find_mapping(domain, hwirq); - - return revmap[hwirq]; -} - -#ifdef CONFIG_IRQ_DOMAIN_DEBUG -static int virq_debug_show(struct seq_file *m, void *private) -{ - unsigned long flags; - struct irq_desc *desc; - const char *p; - static const char none[] = "none"; - void *data; - int i; - - seq_printf(m, "%-5s %-7s %-15s %-*s %s\n", "irq", "hwirq", - "chip name", (int)(2 * sizeof(void *) + 2), "chip data", - "domain name"); - - for (i = 1; i < nr_irqs; i++) { - desc = irq_to_desc(i); - if (!desc) - continue; - - raw_spin_lock_irqsave(&desc->lock, flags); - - if (desc->action && desc->action->handler) { - struct irq_chip *chip; - - seq_printf(m, "%5d ", i); - seq_printf(m, "0x%05lx ", desc->irq_data.hwirq); - - chip = irq_desc_get_chip(desc); - if (chip && chip->name) - p = chip->name; - else - p = none; - seq_printf(m, "%-15s ", p); - - data = irq_desc_get_chip_data(desc); - seq_printf(m, data ? "0x%p " : " %p ", data); - - if (desc->irq_data.domain && desc->irq_data.domain->of_node) - p = desc->irq_data.domain->of_node->full_name; - else - p = none; - seq_printf(m, "%s\n", p); - } - - raw_spin_unlock_irqrestore(&desc->lock, flags); - } - - return 0; -} - -static int virq_debug_open(struct inode *inode, struct file *file) -{ - return single_open(file, virq_debug_show, inode->i_private); -} - -static const struct file_operations virq_debug_fops = { - .open = virq_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int __init irq_debugfs_init(void) -{ - if (debugfs_create_file("irq_domain_mapping", S_IRUGO, NULL, - NULL, &virq_debug_fops) == NULL) - return -ENOMEM; - - return 0; -} -__initcall(irq_debugfs_init); -#endif /* CONFIG_IRQ_DOMAIN_DEBUG */ - -int irq_domain_simple_map(struct irq_domain *d, unsigned int irq, - irq_hw_number_t hwirq) -{ - return 0; } +EXPORT_SYMBOL_GPL(irq_dispose_mapping); -/** - * irq_domain_xlate_onecell() - Generic xlate for direct one cell bindings - * - * Device Tree IRQ specifier translation function which works with one cell - * bindings where the cell value maps directly to the hwirq number. - */ -int irq_domain_xlate_onecell(struct irq_domain *d, struct device_node *ctrlr, - const u32 *intspec, unsigned int intsize, - unsigned long *out_hwirq, unsigned int *out_type) +int irq_domain_simple_dt_translate(struct irq_domain *d, + struct device_node *controller, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type) { - if (WARN_ON(intsize < 1)) + if (d->of_node != controller) + return -EINVAL; + if (intsize < 1) + return -EINVAL; + if (d->nr_irq && ((intspec[0] < d->hwirq_base) || + (intspec[0] >= d->hwirq_base + d->nr_irq))) return -EINVAL; + *out_hwirq = intspec[0]; *out_type = IRQ_TYPE_NONE; + if (intsize > 1) + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; return 0; } -EXPORT_SYMBOL_GPL(irq_domain_xlate_onecell); /** - * irq_domain_xlate_twocell() - Generic xlate for direct two cell bindings - * - * Device Tree IRQ specifier translation function which works with two cell - * bindings where the cell values map directly to the hwirq number - * and linux irq flags. + * irq_domain_create_simple() - Set up a 'simple' translation range */ -int irq_domain_xlate_twocell(struct irq_domain *d, struct device_node *ctrlr, - const u32 *intspec, unsigned int intsize, - irq_hw_number_t *out_hwirq, unsigned int *out_type) +void irq_domain_add_simple(struct device_node *controller, int irq_base) { - if (WARN_ON(intsize < 2)) - return -EINVAL; - *out_hwirq = intspec[0]; - *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; - return 0; -} -EXPORT_SYMBOL_GPL(irq_domain_xlate_twocell); + struct irq_domain *domain; + int rc; -/** - * irq_domain_xlate_onetwocell() - Generic xlate for one or two cell bindings - * - * Device Tree IRQ specifier translation function which works with either one - * or two cell bindings where the cell values map directly to the hwirq number - * and linux irq flags. - * - * Note: don't use this function unless your interrupt controller explicitly - * supports both one and two cell bindings. For the majority of controllers - * the _onecell() or _twocell() variants above should be used. - */ -int irq_domain_xlate_onetwocell(struct irq_domain *d, - struct device_node *ctrlr, - const u32 *intspec, unsigned int intsize, - unsigned long *out_hwirq, unsigned int *out_type) -{ - if (WARN_ON(intsize < 1)) - return -EINVAL; - *out_hwirq = intspec[0]; - *out_type = (intsize > 1) ? intspec[1] : IRQ_TYPE_NONE; - return 0; -} -EXPORT_SYMBOL_GPL(irq_domain_xlate_onetwocell); + domain = kzalloc(sizeof(*domain), GFP_KERNEL); + if (!domain) { + WARN_ON(1); + return; + } -const struct irq_domain_ops irq_domain_simple_ops = { - .map = irq_domain_simple_map, - .xlate = irq_domain_xlate_onetwocell, -}; -EXPORT_SYMBOL_GPL(irq_domain_simple_ops); + domain->irq_base = irq_base; + domain->of_node = of_node_get(controller); + domain->ops = &irq_domain_simple_ops; + rc = irq_domain_add(domain); + if (rc) { + WARN(1, "Unable to create irq domain\n"); + return; + } + irq_domain_register(domain); +} +EXPORT_SYMBOL_GPL(irq_domain_add_simple); -#ifdef CONFIG_OF_IRQ void irq_domain_generate_simple(const struct of_device_id *match, u64 phys_base, unsigned int irq_start) { struct device_node *node; - pr_debug("looking for phys_base=%llx, irq_start=%i\n", + pr_info("looking for phys_base=%llx, irq_start=%i\n", (unsigned long long) phys_base, (int) irq_start); node = of_find_matching_node_by_address(NULL, match, phys_base); if (node) - irq_domain_add_legacy(node, 32, irq_start, 0, - &irq_domain_simple_ops, NULL); + irq_domain_add_simple(node, irq_start); + else + pr_info("no node found\n"); } EXPORT_SYMBOL_GPL(irq_domain_generate_simple); -#endif +#endif /* CONFIG_OF_IRQ */ + +struct irq_domain_ops irq_domain_simple_ops = { +#ifdef CONFIG_OF_IRQ + .dt_translate = irq_domain_simple_dt_translate, +#endif /* CONFIG_OF_IRQ */ +}; +EXPORT_SYMBOL_GPL(irq_domain_simple_ops); diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 89a3ea82569b00ac69da1bec696a90bce878790a..165d5dc53fdae97e64e05d4acb9abc335cc92aaf 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -531,6 +531,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 @@ -1211,9 +1237,16 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id) #endif /* If this was the last handler, shut down the IRQ line: */ - if (!desc->action) + if (!desc->action) { irq_shutdown(desc); + /* Explicitly mask the interrupt */ + if (desc->irq_data.chip->irq_mask) + desc->irq_data.chip->irq_mask(&desc->irq_data); + else if (desc->irq_data.chip->irq_mask_ack) + desc->irq_data.chip->irq_mask_ack(&desc->irq_data); + } + #ifdef CONFIG_SMP /* make sure affinity_hint is cleaned up */ if (WARN_ON_ONCE(desc->affinity_hint)) @@ -1451,6 +1484,19 @@ int request_any_context_irq(unsigned int irq, irq_handler_t handler, } 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); + void enable_percpu_irq(unsigned int irq, unsigned int type) { unsigned int cpu = smp_processor_id(); diff --git a/kernel/irq/pm.c b/kernel/irq/pm.c index fe4b09cf829ca2625559e972d6eb8671e3182cdd..0e3b91916c72c079f8045db519c34bea8673a191 100644 --- a/kernel/irq/pm.c +++ b/kernel/irq/pm.c @@ -45,7 +45,7 @@ static void resume_irqs(bool want_early) struct irq_desc *desc; int irq; - for_each_irq_desc(irq, desc) { + for_each_irq_desc_reverse(irq, desc) { unsigned long flags; bool is_early = desc->action && desc->action->flags & IRQF_EARLY_RESUME; diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 079f1d39a8b84a9105852864cbc8ceaf8875d5ac..2169feeba529be9843ea844e7281e1cdb31377a2 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -343,7 +343,7 @@ int lookup_symbol_attrs(unsigned long addr, unsigned long *size, /* Look up a kernel symbol and return it in a text buffer. */ static int __sprint_symbol(char *buffer, unsigned long address, - int symbol_offset) + int symbol_offset, int add_offset) { char *modname; const char *name; @@ -358,13 +358,13 @@ static int __sprint_symbol(char *buffer, unsigned long address, if (name != buffer) strcpy(buffer, name); len = strlen(buffer); - buffer += len; offset -= symbol_offset; + if (add_offset) + len += sprintf(buffer + len, "+%#lx/%#lx", offset, size); + if (modname) - len += sprintf(buffer, "+%#lx/%#lx [%s]", offset, size, modname); - else - len += sprintf(buffer, "+%#lx/%#lx", offset, size); + len += sprintf(buffer + len, " [%s]", modname); return len; } @@ -382,11 +382,27 @@ static int __sprint_symbol(char *buffer, unsigned long address, */ int sprint_symbol(char *buffer, unsigned long address) { - return __sprint_symbol(buffer, address, 0); + return __sprint_symbol(buffer, address, 0, 1); } - EXPORT_SYMBOL_GPL(sprint_symbol); +/** + * sprint_symbol_no_offset - Look up a kernel symbol and return it in a text buffer + * @buffer: buffer to be stored + * @address: address to lookup + * + * This function looks up a kernel symbol with @address and stores its name + * and module name to @buffer if possible. If no symbol was found, just saves + * its @address as is. + * + * This function returns the number of bytes stored in @buffer. + */ +int sprint_symbol_no_offset(char *buffer, unsigned long address) +{ + return __sprint_symbol(buffer, address, 0, 0); +} +EXPORT_SYMBOL_GPL(sprint_symbol_no_offset); + /** * sprint_backtrace - Look up a backtrace symbol and return it in a text buffer * @buffer: buffer to be stored @@ -403,7 +419,7 @@ EXPORT_SYMBOL_GPL(sprint_symbol); */ int sprint_backtrace(char *buffer, unsigned long address) { - return __sprint_symbol(buffer, address, -1); + return __sprint_symbol(buffer, address, -1, 1); } /* Look up a kernel symbol and print it to the kernel messages. */ diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 1e9acb47e9f145da4feb3daf58b472392fddc52b..4e059d576986c02fab90cf7fc182679ecf000c6c 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -20,11 +20,25 @@ config SUSPEND_FREEZER config HAS_WAKELOCK bool - default y -config WAKELOCK +config HAS_EARLYSUSPEND bool + +config WAKELOCK + bool "Wake lock" + depends on PM && RTC_CLASS + default n + select HAS_WAKELOCK + ---help--- + Enable wakelocks. When user space request a sleep state the + sleep request will be delayed until no wake locks are held. + +config WAKELOCK_STAT + bool "Wake lock stats" + depends on WAKELOCK default y + ---help--- + Report wake lock stats in /proc/wakelocks config USER_WAKELOCK bool "Userspace wake locks" @@ -36,6 +50,41 @@ config USER_WAKELOCK Write "lockname" to /sys/power/wake_unlock to unlock a user wake lock. +config EARLYSUSPEND + bool "Early suspend" + depends on WAKELOCK + default y + select HAS_EARLYSUSPEND + ---help--- + Call early suspend handlers when the user requested sleep state + changes. + +choice + prompt "User-space screen access" + default FB_EARLYSUSPEND if !FRAMEBUFFER_CONSOLE + default CONSOLE_EARLYSUSPEND + depends on HAS_EARLYSUSPEND + + config NO_USER_SPACE_SCREEN_ACCESS_CONTROL + bool "None" + + config CONSOLE_EARLYSUSPEND + bool "Console switch on early-suspend" + depends on HAS_EARLYSUSPEND && VT + ---help--- + Register early suspend handler to perform a console switch to + when user-space should stop drawing to the screen and a switch + back when it should resume. + + config FB_EARLYSUSPEND + bool "Sysfs interface" + depends on HAS_EARLYSUSPEND + ---help--- + Register early suspend handler that notifies and waits for + user-space through sysfs when user-space should stop drawing + to the screen and notifies user-space when it should resume. +endchoice + config HIBERNATE_CALLBACKS bool diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 7f51f84d530c75df8a4e4f211be3779a87e1ccd7..5ad8c75b827b369ebefcabfffce554f06493cf60 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -9,7 +9,11 @@ obj-$(CONFIG_SUSPEND) += suspend.o obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \ block_io.o +obj-$(CONFIG_WAKELOCK) += wakelock.o obj-$(CONFIG_USER_WAKELOCK) += userwakelock.o +obj-$(CONFIG_EARLYSUSPEND) += earlysuspend.o +obj-$(CONFIG_CONSOLE_EARLYSUSPEND) += consoleearlysuspend.o +obj-$(CONFIG_FB_EARLYSUSPEND) += fbearlysuspend.o obj-$(CONFIG_SUSPEND_TIME) += suspend_time.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o diff --git a/kernel/power/consoleearlysuspend.c b/kernel/power/consoleearlysuspend.c new file mode 100644 index 0000000000000000000000000000000000000000..a3edcb2673896d456de72c76c53228f8638de311 --- /dev/null +++ b/kernel/power/consoleearlysuspend.c @@ -0,0 +1,78 @@ +/* kernel/power/consoleearlysuspend.c + * + * Copyright (C) 2005-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 + +#define EARLY_SUSPEND_CONSOLE (MAX_NR_CONSOLES-1) + +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; + } + + if (vt_waitactive(orig_fgconsole + 1)) + pr_warning("console_late_resume: Can't switch VCs.\n"); +} + +static struct early_suspend console_early_suspend_desc = { + .level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, + .suspend = console_early_suspend, + .resume = console_late_resume, +}; + +static int __init console_early_suspend_init(void) +{ + register_early_suspend(&console_early_suspend_desc); + return 0; +} + +static void __exit console_early_suspend_exit(void) +{ + unregister_early_suspend(&console_early_suspend_desc); +} + +module_init(console_early_suspend_init); +module_exit(console_early_suspend_exit); + diff --git a/kernel/power/earlysuspend.c b/kernel/power/earlysuspend.c new file mode 100644 index 0000000000000000000000000000000000000000..5a6b2fa9f27b23756f857fabef19beba32b68758 --- /dev/null +++ b/kernel/power/earlysuspend.c @@ -0,0 +1,183 @@ +/* kernel/power/earlysuspend.c + * + * Copyright (C) 2005-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 "power.h" + +enum { + DEBUG_USER_STATE = 1U << 0, + DEBUG_SUSPEND = 1U << 2, + DEBUG_VERBOSE = 1U << 3, +}; +static int debug_mask = DEBUG_USER_STATE; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +static DEFINE_MUTEX(early_suspend_lock); +static LIST_HEAD(early_suspend_handlers); +static void early_suspend(struct work_struct *work); +static void late_resume(struct work_struct *work); +static DECLARE_WORK(early_suspend_work, early_suspend); +static DECLARE_WORK(late_resume_work, late_resume); +static DEFINE_SPINLOCK(state_lock); +enum { + SUSPEND_REQUESTED = 0x1, + SUSPENDED = 0x2, + SUSPEND_REQUESTED_AND_SUSPENDED = SUSPEND_REQUESTED | SUSPENDED, +}; +static int state; + +void register_early_suspend(struct early_suspend *handler) +{ + struct list_head *pos; + + mutex_lock(&early_suspend_lock); + list_for_each(pos, &early_suspend_handlers) { + struct early_suspend *e; + e = list_entry(pos, struct early_suspend, link); + if (e->level > handler->level) + break; + } + list_add_tail(&handler->link, pos); + if ((state & SUSPENDED) && handler->suspend) + handler->suspend(handler); + mutex_unlock(&early_suspend_lock); +} +EXPORT_SYMBOL(register_early_suspend); + +void unregister_early_suspend(struct early_suspend *handler) +{ + mutex_lock(&early_suspend_lock); + list_del(&handler->link); + mutex_unlock(&early_suspend_lock); +} +EXPORT_SYMBOL(unregister_early_suspend); + +static void early_suspend(struct work_struct *work) +{ + struct early_suspend *pos; + unsigned long irqflags; + int abort = 0; + + mutex_lock(&early_suspend_lock); + spin_lock_irqsave(&state_lock, irqflags); + if (state == SUSPEND_REQUESTED) + state |= SUSPENDED; + else + abort = 1; + spin_unlock_irqrestore(&state_lock, irqflags); + + if (abort) { + if (debug_mask & DEBUG_SUSPEND) + pr_info("early_suspend: abort, state %d\n", state); + mutex_unlock(&early_suspend_lock); + goto abort; + } + + if (debug_mask & DEBUG_SUSPEND) + pr_info("early_suspend: call handlers\n"); + list_for_each_entry(pos, &early_suspend_handlers, link) { + if (pos->suspend != NULL) { + if (debug_mask & DEBUG_VERBOSE) + pr_info("early_suspend: calling %pf\n", pos->suspend); + pos->suspend(pos); + } + } + mutex_unlock(&early_suspend_lock); + + suspend_sys_sync_queue(); +abort: + spin_lock_irqsave(&state_lock, irqflags); + if (state == SUSPEND_REQUESTED_AND_SUSPENDED) + wake_unlock(&main_wake_lock); + spin_unlock_irqrestore(&state_lock, irqflags); +} + +static void late_resume(struct work_struct *work) +{ + struct early_suspend *pos; + unsigned long irqflags; + int abort = 0; + + mutex_lock(&early_suspend_lock); + spin_lock_irqsave(&state_lock, irqflags); + if (state == SUSPENDED) + state &= ~SUSPENDED; + else + abort = 1; + spin_unlock_irqrestore(&state_lock, irqflags); + + if (abort) { + if (debug_mask & DEBUG_SUSPEND) + pr_info("late_resume: abort, state %d\n", state); + goto abort; + } + if (debug_mask & DEBUG_SUSPEND) + pr_info("late_resume: call handlers\n"); + list_for_each_entry_reverse(pos, &early_suspend_handlers, link) { + if (pos->resume != NULL) { + if (debug_mask & DEBUG_VERBOSE) + pr_info("late_resume: calling %pf\n", pos->resume); + + pos->resume(pos); + } + } + if (debug_mask & DEBUG_SUSPEND) + pr_info("late_resume: done\n"); +abort: + mutex_unlock(&early_suspend_lock); +} + +void request_suspend_state(suspend_state_t new_state) +{ + unsigned long irqflags; + int old_sleep; + + spin_lock_irqsave(&state_lock, irqflags); + old_sleep = state & SUSPEND_REQUESTED; + if (debug_mask & DEBUG_USER_STATE) { + struct timespec ts; + struct rtc_time tm; + getnstimeofday(&ts); + rtc_time_to_tm(ts.tv_sec, &tm); + pr_info("request_suspend_state: %s (%d->%d) at %lld " + "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", + new_state != PM_SUSPEND_ON ? "sleep" : "wakeup", + requested_suspend_state, new_state, + ktime_to_ns(ktime_get()), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); + } + if (!old_sleep && new_state != PM_SUSPEND_ON) { + state |= SUSPEND_REQUESTED; + queue_work(suspend_work_queue, &early_suspend_work); + } else if (old_sleep && new_state == PM_SUSPEND_ON) { + state &= ~SUSPEND_REQUESTED; + wake_lock(&main_wake_lock); + queue_work(suspend_work_queue, &late_resume_work); + } + requested_suspend_state = new_state; + spin_unlock_irqrestore(&state_lock, irqflags); +} + +suspend_state_t get_suspend_state(void) +{ + return requested_suspend_state; +} diff --git a/kernel/power/fbearlysuspend.c b/kernel/power/fbearlysuspend.c new file mode 100644 index 0000000000000000000000000000000000000000..48773725988f1c0c45878de4e90fd70cb106d977 --- /dev/null +++ b/kernel/power/fbearlysuspend.c @@ -0,0 +1,181 @@ +/* kernel/power/fbearlysuspend.c + * + * Copyright (C) 2005-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 "power.h" + +#define MAX_BUF 100 + +static wait_queue_head_t fb_state_wq; +static int display = 1; +static DEFINE_SPINLOCK(fb_state_lock); +static enum { + FB_STATE_STOPPED_DRAWING, + FB_STATE_REQUEST_STOP_DRAWING, + FB_STATE_DRAWING_OK, +} fb_state; + +/* tell userspace to stop drawing, wait for it to stop */ +static void stop_drawing_early_suspend(struct early_suspend *h) +{ + int ret; + unsigned long irq_flags; + + spin_lock_irqsave(&fb_state_lock, irq_flags); + fb_state = FB_STATE_REQUEST_STOP_DRAWING; + spin_unlock_irqrestore(&fb_state_lock, irq_flags); + + wake_up_all(&fb_state_wq); + ret = wait_event_timeout(fb_state_wq, + fb_state == FB_STATE_STOPPED_DRAWING, + HZ); + if (unlikely(fb_state != FB_STATE_STOPPED_DRAWING)) + pr_warning("stop_drawing_early_suspend: timeout waiting for " + "userspace to stop drawing\n"); +} + +/* tell userspace to start drawing */ +static void start_drawing_late_resume(struct early_suspend *h) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&fb_state_lock, irq_flags); + fb_state = FB_STATE_DRAWING_OK; + spin_unlock_irqrestore(&fb_state_lock, irq_flags); + wake_up(&fb_state_wq); +} + +static struct early_suspend stop_drawing_early_suspend_desc = { + .level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, + .suspend = stop_drawing_early_suspend, + .resume = start_drawing_late_resume, +}; + +static ssize_t wait_for_fb_sleep_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *s = buf; + int ret; + + ret = wait_event_interruptible(fb_state_wq, + fb_state != FB_STATE_DRAWING_OK); + if (ret && fb_state == FB_STATE_DRAWING_OK) { + return ret; + } else { + s += sprintf(buf, "sleeping"); + if (display == 1) { + display = 0; + sysfs_notify(power_kobj, NULL, "wait_for_fb_status"); + } + } + + return s - buf; +} + +static ssize_t wait_for_fb_wake_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *s = buf; + int ret; + unsigned long irq_flags; + + spin_lock_irqsave(&fb_state_lock, irq_flags); + if (fb_state == FB_STATE_REQUEST_STOP_DRAWING) { + fb_state = FB_STATE_STOPPED_DRAWING; + wake_up(&fb_state_wq); + } + spin_unlock_irqrestore(&fb_state_lock, irq_flags); + + ret = wait_event_interruptible(fb_state_wq, + fb_state == FB_STATE_DRAWING_OK); + if (ret && fb_state != FB_STATE_DRAWING_OK) + return ret; + else { + s += sprintf(buf, "awake"); + if (display == 0) { + display = 1; + sysfs_notify(power_kobj, NULL, "wait_for_fb_status"); + } + } + return s - buf; +} + +static ssize_t wait_for_fb_status_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret = 0; + + if (display == 1) + ret = snprintf(buf, strnlen("on", MAX_BUF) + 1, "on"); + else + ret = snprintf(buf, strnlen("off", MAX_BUF) + 1, "off"); + + return ret; +} + +#define power_ro_attr(_name) \ + static struct kobj_attribute _name##_attr = { \ + .attr = { \ + .name = __stringify(_name), \ + .mode = 0444, \ + }, \ + .show = _name##_show, \ + .store = NULL, \ + } + +power_ro_attr(wait_for_fb_sleep); +power_ro_attr(wait_for_fb_wake); +power_ro_attr(wait_for_fb_status); + +static struct attribute *g[] = { + &wait_for_fb_sleep_attr.attr, + &wait_for_fb_wake_attr.attr, + &wait_for_fb_status_attr.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = g, +}; + +static int __init android_power_init(void) +{ + int ret; + + init_waitqueue_head(&fb_state_wq); + fb_state = FB_STATE_DRAWING_OK; + + ret = sysfs_create_group(power_kobj, &attr_group); + if (ret) { + pr_err("android_power_init: sysfs_create_group failed\n"); + return ret; + } + + register_early_suspend(&stop_drawing_early_suspend_desc); + return 0; +} + +static void __exit android_power_exit(void) +{ + unregister_early_suspend(&stop_drawing_early_suspend_desc); + sysfs_remove_group(power_kobj, &attr_group); +} + +module_init(android_power_init); +module_exit(android_power_exit); + diff --git a/kernel/power/main.c b/kernel/power/main.c index 7f3c91a634cdc49b90c89088bd70b7c11b862a5c..a1d13f553e5248edd431d555be7d04c596138e51 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -15,9 +15,12 @@ #include #include #include +#include #include "power.h" +#define MAX_BUF 100 + DEFINE_MUTEX(pm_mutex); #ifdef CONFIG_PM_SLEEP @@ -26,6 +29,13 @@ DEFINE_MUTEX(pm_mutex); static BLOCKING_NOTIFIER_HEAD(pm_chain_head); +static void touch_event_fn(struct work_struct *work); +static DECLARE_WORK(touch_event_struct, touch_event_fn); + +static struct hrtimer tc_ev_timer; +static int tc_ev_processed; +static ktime_t touch_evt_timer_val; + int register_pm_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&pm_chain_head, nb); @@ -71,6 +81,81 @@ static ssize_t pm_async_store(struct kobject *kobj, struct kobj_attribute *attr, power_attr(pm_async); +static ssize_t +touch_event_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + if (tc_ev_processed == 0) + return snprintf(buf, strnlen("touch_event", MAX_BUF) + 1, + "touch_event"); + else + return snprintf(buf, strnlen("null", MAX_BUF) + 1, + "null"); +} + +static ssize_t +touch_event_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + + hrtimer_cancel(&tc_ev_timer); + tc_ev_processed = 0; + + /* set a timer to notify the userspace to stop processing + * touch event + */ + hrtimer_start(&tc_ev_timer, touch_evt_timer_val, HRTIMER_MODE_REL); + + /* wakeup the userspace poll */ + sysfs_notify(kobj, NULL, "touch_event"); + + return n; +} + +power_attr(touch_event); + +static ssize_t +touch_event_timer_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, MAX_BUF, "%lld", touch_evt_timer_val.tv64); +} + +static ssize_t +touch_event_timer_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + touch_evt_timer_val = ktime_set(0, val*1000); + + return n; +} + +power_attr(touch_event_timer); + +static void touch_event_fn(struct work_struct *work) +{ + /* wakeup the userspace poll */ + tc_ev_processed = 1; + sysfs_notify(power_kobj, NULL, "touch_event"); + + return; +} + +static enum hrtimer_restart tc_ev_stop(struct hrtimer *hrtimer) +{ + + schedule_work(&touch_event_struct); + + return HRTIMER_NORESTART; +} + #ifdef CONFIG_PM_DEBUG int pm_test_level = TEST_NONE; @@ -273,7 +358,11 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { #ifdef CONFIG_SUSPEND +#ifdef CONFIG_EARLYSUSPEND + suspend_state_t state = PM_SUSPEND_ON; +#else suspend_state_t state = PM_SUSPEND_STANDBY; +#endif const char * const *s; #endif char *p; @@ -292,8 +381,15 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, #ifdef CONFIG_SUSPEND for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) { if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) { +#ifdef CONFIG_EARLYSUSPEND + if (state == PM_SUSPEND_ON || valid_state(state)) { + error = 0; + request_suspend_state(state); + break; + } +#else error = pm_suspend(state); - break; +#endif } } #endif @@ -405,7 +501,9 @@ power_attr(wake_lock); power_attr(wake_unlock); #endif -static struct attribute * g[] = { +static struct attribute *g[] = { + &touch_event_attr.attr, + &touch_event_timer_attr.attr, &state_attr.attr, #ifdef CONFIG_PM_TRACE &pm_trace_attr.attr, @@ -450,6 +548,13 @@ static int __init pm_init(void) return error; hibernate_image_size_init(); hibernate_reserved_size_init(); + + touch_evt_timer_val = ktime_set(2, 0); + hrtimer_init(&tc_ev_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + tc_ev_timer.function = &tc_ev_stop; + tc_ev_processed = 1; + + power_kobj = kobject_create_and_add("power", NULL); if (!power_kobj) return -ENOMEM; diff --git a/kernel/power/power.h b/kernel/power/power.h index c4ecb81cb4fe970ce5c9c9095e018e1bc57531bd..32e08a1fe0c7d94ed323edcd4899594ba69c8f2d 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -265,6 +265,18 @@ static inline void suspend_thaw_processes(void) } #endif +#ifdef CONFIG_WAKELOCK +/* kernel/power/wakelock.c */ +extern struct workqueue_struct *suspend_work_queue; +extern struct wake_lock main_wake_lock; +extern suspend_state_t requested_suspend_state; +extern void suspend_sys_sync_queue(void); +extern int suspend_sys_sync_wait(void); +#else +static inline void suspend_sys_sync_queue(void) {} +static inline int suspend_sys_sync_wait(void) { return 0; } +#endif + #ifdef CONFIG_USER_WAKELOCK ssize_t wake_lock_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); @@ -275,3 +287,9 @@ ssize_t wake_unlock_show(struct kobject *kobj, struct kobj_attribute *attr, ssize_t wake_unlock_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n); #endif + +#ifdef CONFIG_EARLYSUSPEND +/* kernel/power/earlysuspend.c */ +void request_suspend_state(suspend_state_t state); +suspend_state_t get_suspend_state(void); +#endif diff --git a/kernel/power/process.c b/kernel/power/process.c index 19db29f67558fef712764d78d7feccc0318ed650..00259a85c854a14b8c52472b8b5bb0980d8b5594 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include "power.h" /* * Timeout for stopping processes @@ -69,6 +71,10 @@ static int try_to_freeze_tasks(bool user_only) todo += wq_busy; } + if (todo && has_wake_lock(WAKE_LOCK_SUSPEND)) { + wakeup = 1; + break; + } if (!todo || time_after(jiffies, end_time)) break; @@ -90,18 +96,31 @@ static int try_to_freeze_tasks(bool user_only) elapsed_csecs = elapsed_csecs64; if (todo) { - printk("\n"); - printk(KERN_ERR "Freezing of tasks %s after %d.%02d seconds " - "(%d tasks refusing to freeze, wq_busy=%d):\n", - wakeup ? "aborted" : "failed", - elapsed_csecs / 100, elapsed_csecs % 100, - todo - wq_busy, wq_busy); + /* This does not unfreeze processes that are already frozen + * (we have slightly ugly calling convention in that respect, + * and caller must call thaw_processes() if something fails), + * but it cleans up leftover PF_FREEZE requests. + */ + if(wakeup) { + printk("\n"); + printk(KERN_ERR "Freezing of %s aborted\n", + user_only ? "user space " : "tasks "); + } + else { + printk("\n"); + printk(KERN_ERR "Freezing of tasks %s after %d.%02d seconds " + "(%d tasks refusing to freeze, wq_busy=%d):\n", + wakeup ? "aborted" : "failed", + elapsed_csecs / 100, elapsed_csecs % 100, + todo - wq_busy, wq_busy); + } if (!wakeup) { read_lock(&tasklist_lock); do_each_thread(g, p) { if (p != current && !freezer_should_skip(p) - && freezing(p) && !frozen(p)) + && freezing(p) && !frozen(p) && + elapsed_csecs > 100) sched_show_task(p); } while_each_thread(g, p); read_unlock(&tasklist_lock); @@ -158,6 +177,10 @@ int freeze_kernel_threads(void) { int error; + error = suspend_sys_sync_wait(); + if (error) + return error; + printk("Freezing remaining freezable tasks ... "); pm_nosig_freezing = true; error = try_to_freeze_tasks(false); diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index 9bb2f077e3b31c920b82eac54da9ddbef2c1abfa..10d58d4370ba5d982b6d80795b4f6d8ebe2f186b 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -30,6 +30,9 @@ #include "power.h" const char *const pm_states[PM_SUSPEND_MAX] = { +#ifdef CONFIG_EARLYSUSPEND + [PM_SUSPEND_ON] = "on", +#endif [PM_SUSPEND_STANDBY] = "standby", [PM_SUSPEND_MEM] = "mem", }; @@ -276,10 +279,7 @@ static 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"); - + suspend_sys_sync_queue(); pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); error = suspend_prepare(); if (error) diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c new file mode 100644 index 0000000000000000000000000000000000000000..2583856fab315aadceacf4a822f3ca00a094628a --- /dev/null +++ b/kernel/power/wakelock.c @@ -0,0 +1,712 @@ +/* kernel/power/wakelock.c + * + * Copyright (C) 2005-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 /* sys_sync */ +#include +#ifdef CONFIG_WAKELOCK_STAT +#include +#endif +#include "power.h" + +enum { + DEBUG_EXIT_SUSPEND = 1U << 0, + DEBUG_WAKEUP = 1U << 1, + DEBUG_SUSPEND = 1U << 2, + DEBUG_EXPIRE = 1U << 3, + DEBUG_WAKE_LOCK = 1U << 4, +}; +static int debug_mask = DEBUG_EXIT_SUSPEND | DEBUG_WAKEUP; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define WAKE_LOCK_TYPE_MASK (0x0f) +#define WAKE_LOCK_INITIALIZED (1U << 8) +#define WAKE_LOCK_ACTIVE (1U << 9) +#define WAKE_LOCK_AUTO_EXPIRE (1U << 10) +#define WAKE_LOCK_PREVENTING_SUSPEND (1U << 11) + +static DEFINE_SPINLOCK(list_lock); +static LIST_HEAD(inactive_locks); +static struct list_head active_wake_locks[WAKE_LOCK_TYPE_COUNT]; +static int current_event_num; +static int suspend_sys_sync_count; +static DEFINE_SPINLOCK(suspend_sys_sync_lock); +static struct workqueue_struct *suspend_sys_sync_work_queue; +static DECLARE_COMPLETION(suspend_sys_sync_comp); +struct workqueue_struct *suspend_work_queue; +struct wake_lock main_wake_lock; +suspend_state_t requested_suspend_state = PM_SUSPEND_MEM; +static struct wake_lock unknown_wakeup; +static struct wake_lock suspend_backoff_lock; + +#define SUSPEND_BACKOFF_THRESHOLD 10 +#define SUSPEND_BACKOFF_INTERVAL 10000 + +static unsigned suspend_short_count; + +#ifdef CONFIG_WAKELOCK_STAT +static struct wake_lock deleted_wake_locks; +static ktime_t last_sleep_time_update; +static int wait_for_wakeup; + +int get_expired_time(struct wake_lock *lock, ktime_t *expire_time) +{ + struct timespec ts; + struct timespec kt; + struct timespec tomono; + struct timespec delta; + struct timespec sleep; + long timeout; + + if (!(lock->flags & WAKE_LOCK_AUTO_EXPIRE)) + return 0; + get_xtime_and_monotonic_and_sleep_offset(&kt, &tomono, &sleep); + timeout = lock->expires - jiffies; + if (timeout > 0) + return 0; + jiffies_to_timespec(-timeout, &delta); + set_normalized_timespec(&ts, kt.tv_sec + tomono.tv_sec - delta.tv_sec, + kt.tv_nsec + tomono.tv_nsec - delta.tv_nsec); + *expire_time = timespec_to_ktime(ts); + return 1; +} + + +static int print_lock_stat(struct seq_file *m, struct wake_lock *lock) +{ + int lock_count = lock->stat.count; + int expire_count = lock->stat.expire_count; + ktime_t active_time = ktime_set(0, 0); + ktime_t total_time = lock->stat.total_time; + ktime_t max_time = lock->stat.max_time; + + ktime_t prevent_suspend_time = lock->stat.prevent_suspend_time; + if (lock->flags & WAKE_LOCK_ACTIVE) { + ktime_t now, add_time; + int expired = get_expired_time(lock, &now); + if (!expired) + now = ktime_get(); + add_time = ktime_sub(now, lock->stat.last_time); + lock_count++; + if (!expired) + active_time = add_time; + else + expire_count++; + total_time = ktime_add(total_time, add_time); + if (lock->flags & WAKE_LOCK_PREVENTING_SUSPEND) + prevent_suspend_time = ktime_add(prevent_suspend_time, + ktime_sub(now, last_sleep_time_update)); + if (add_time.tv64 > max_time.tv64) + max_time = add_time; + } + + return seq_printf(m, + "\"%s\"\t%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t%lld\n", + lock->name, lock_count, expire_count, + lock->stat.wakeup_count, ktime_to_ns(active_time), + ktime_to_ns(total_time), + ktime_to_ns(prevent_suspend_time), ktime_to_ns(max_time), + ktime_to_ns(lock->stat.last_time)); +} + +static int wakelock_stats_show(struct seq_file *m, void *unused) +{ + unsigned long irqflags; + struct wake_lock *lock; + int ret; + int type; + + spin_lock_irqsave(&list_lock, irqflags); + + ret = seq_puts(m, "name\tcount\texpire_count\twake_count\tactive_since" + "\ttotal_time\tsleep_time\tmax_time\tlast_change\n"); + list_for_each_entry(lock, &inactive_locks, link) + ret = print_lock_stat(m, lock); + for (type = 0; type < WAKE_LOCK_TYPE_COUNT; type++) { + list_for_each_entry(lock, &active_wake_locks[type], link) + ret = print_lock_stat(m, lock); + } + spin_unlock_irqrestore(&list_lock, irqflags); + return 0; +} + +static void wake_unlock_stat_locked(struct wake_lock *lock, int expired) +{ + ktime_t duration; + ktime_t now; + if (!(lock->flags & WAKE_LOCK_ACTIVE)) + return; + if (get_expired_time(lock, &now)) + expired = 1; + else + now = ktime_get(); + lock->stat.count++; + if (expired) + lock->stat.expire_count++; + duration = ktime_sub(now, lock->stat.last_time); + lock->stat.total_time = ktime_add(lock->stat.total_time, duration); + if (ktime_to_ns(duration) > ktime_to_ns(lock->stat.max_time)) + lock->stat.max_time = duration; + lock->stat.last_time = ktime_get(); + if (lock->flags & WAKE_LOCK_PREVENTING_SUSPEND) { + duration = ktime_sub(now, last_sleep_time_update); + lock->stat.prevent_suspend_time = ktime_add( + lock->stat.prevent_suspend_time, duration); + lock->flags &= ~WAKE_LOCK_PREVENTING_SUSPEND; + } +} + +static void update_sleep_wait_stats_locked(int done) +{ + struct wake_lock *lock; + ktime_t now, etime, elapsed, add; + int expired; + + now = ktime_get(); + elapsed = ktime_sub(now, last_sleep_time_update); + list_for_each_entry(lock, &active_wake_locks[WAKE_LOCK_SUSPEND], link) { + expired = get_expired_time(lock, &etime); + if (lock->flags & WAKE_LOCK_PREVENTING_SUSPEND) { + if (expired) + add = ktime_sub(etime, last_sleep_time_update); + else + add = elapsed; + lock->stat.prevent_suspend_time = ktime_add( + lock->stat.prevent_suspend_time, add); + } + if (done || expired) + lock->flags &= ~WAKE_LOCK_PREVENTING_SUSPEND; + else + lock->flags |= WAKE_LOCK_PREVENTING_SUSPEND; + } + last_sleep_time_update = now; +} +#endif + + +static void expire_wake_lock(struct wake_lock *lock) +{ +#ifdef CONFIG_WAKELOCK_STAT + wake_unlock_stat_locked(lock, 1); +#endif + lock->flags &= ~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE); + list_del(&lock->link); + list_add(&lock->link, &inactive_locks); + if (debug_mask & (DEBUG_WAKE_LOCK | DEBUG_EXPIRE)) + pr_info("expired wake lock %s\n", lock->name); +} + +/* Caller must acquire the list_lock spinlock */ +static void print_active_locks(int type) +{ + struct wake_lock *lock; + bool print_expired = true; + + BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); + list_for_each_entry(lock, &active_wake_locks[type], link) { + if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) { + long timeout = lock->expires - jiffies; + if (timeout > 0) + pr_info("active wake lock %s, time left %ld\n", + lock->name, timeout); + else if (print_expired) + pr_info("wake lock %s, expired\n", lock->name); + } else { + pr_info("active wake lock %s\n", lock->name); + if (!(debug_mask & DEBUG_EXPIRE)) + print_expired = false; + } + } +} + +static long has_wake_lock_locked(int type) +{ + struct wake_lock *lock, *n; + long max_timeout = 0; + + BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); + list_for_each_entry_safe(lock, n, &active_wake_locks[type], link) { + if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) { + long timeout = lock->expires - jiffies; + if (timeout <= 0) + expire_wake_lock(lock); + else if (timeout > max_timeout) + max_timeout = timeout; + } else + return -1; + } + return max_timeout; +} + +long has_wake_lock(int type) +{ + long ret; + unsigned long irqflags; + spin_lock_irqsave(&list_lock, irqflags); + ret = has_wake_lock_locked(type); + if (ret && (debug_mask & DEBUG_WAKEUP) && type == WAKE_LOCK_SUSPEND) + print_active_locks(type); + spin_unlock_irqrestore(&list_lock, irqflags); + return ret; +} + +static void suspend_sys_sync(struct work_struct *work) +{ + if (debug_mask & DEBUG_SUSPEND) + pr_info("PM: Syncing filesystems...\n"); + + sys_sync(); + + if (debug_mask & DEBUG_SUSPEND) + pr_info("sync done.\n"); + + spin_lock(&suspend_sys_sync_lock); + suspend_sys_sync_count--; + spin_unlock(&suspend_sys_sync_lock); +} +static DECLARE_WORK(suspend_sys_sync_work, suspend_sys_sync); + +void suspend_sys_sync_queue(void) +{ + int ret; + + spin_lock(&suspend_sys_sync_lock); + ret = queue_work(suspend_sys_sync_work_queue, &suspend_sys_sync_work); + if (ret) + suspend_sys_sync_count++; + spin_unlock(&suspend_sys_sync_lock); +} + +static bool suspend_sys_sync_abort; +static void suspend_sys_sync_handler(unsigned long); +static DEFINE_TIMER(suspend_sys_sync_timer, suspend_sys_sync_handler, 0, 0); +/* value should be less then half of input event wake lock timeout value + * which is currently set to 5*HZ (see drivers/input/evdev.c) + */ +#define SUSPEND_SYS_SYNC_TIMEOUT (HZ/4) +static void suspend_sys_sync_handler(unsigned long arg) +{ + if (suspend_sys_sync_count == 0) { + complete(&suspend_sys_sync_comp); + } else if (has_wake_lock(WAKE_LOCK_SUSPEND)) { + suspend_sys_sync_abort = true; + complete(&suspend_sys_sync_comp); + } else { + mod_timer(&suspend_sys_sync_timer, jiffies + + SUSPEND_SYS_SYNC_TIMEOUT); + } +} + +int suspend_sys_sync_wait(void) +{ + suspend_sys_sync_abort = false; + + if (suspend_sys_sync_count != 0) { + mod_timer(&suspend_sys_sync_timer, jiffies + + SUSPEND_SYS_SYNC_TIMEOUT); + wait_for_completion(&suspend_sys_sync_comp); + } + if (suspend_sys_sync_abort) { + pr_info("suspend aborted....while waiting for sys_sync\n"); + return -EAGAIN; + } + + return 0; +} + +static void suspend_backoff(void) +{ + pr_info("suspend: too many immediate wakeups, back off\n"); + wake_lock_timeout(&suspend_backoff_lock, + msecs_to_jiffies(SUSPEND_BACKOFF_INTERVAL)); +} + +static void suspend(struct work_struct *work) +{ + int ret; + int entry_event_num; + struct timespec ts_entry, ts_exit; + + if (has_wake_lock(WAKE_LOCK_SUSPEND)) { + if (debug_mask & DEBUG_SUSPEND) + pr_info("suspend: abort suspend\n"); + return; + } + + entry_event_num = current_event_num; + suspend_sys_sync_queue(); + if (debug_mask & DEBUG_SUSPEND) + pr_info("suspend: enter suspend\n"); + getnstimeofday(&ts_entry); + ret = pm_suspend(requested_suspend_state); + getnstimeofday(&ts_exit); + + if (debug_mask & DEBUG_EXIT_SUSPEND) { + struct rtc_time tm; + rtc_time_to_tm(ts_exit.tv_sec, &tm); + pr_info("suspend: exit suspend, ret = %d " + "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", ret, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts_exit.tv_nsec); + } + + if (ts_exit.tv_sec - ts_entry.tv_sec <= 1) { + ++suspend_short_count; + + if (suspend_short_count == SUSPEND_BACKOFF_THRESHOLD) { + suspend_backoff(); + suspend_short_count = 0; + } + } else { + suspend_short_count = 0; + } + + if (current_event_num == entry_event_num) { + if (debug_mask & DEBUG_SUSPEND) + pr_info("suspend: pm_suspend returned with no event\n"); + wake_lock_timeout(&unknown_wakeup, HZ / 2); + } +} +static DECLARE_WORK(suspend_work, suspend); + +static void expire_wake_locks(unsigned long data) +{ + long has_lock; + unsigned long irqflags; + if (debug_mask & DEBUG_EXPIRE) + pr_info("expire_wake_locks: start\n"); + spin_lock_irqsave(&list_lock, irqflags); + if (debug_mask & DEBUG_SUSPEND) + print_active_locks(WAKE_LOCK_SUSPEND); + has_lock = has_wake_lock_locked(WAKE_LOCK_SUSPEND); + if (debug_mask & DEBUG_EXPIRE) + pr_info("expire_wake_locks: done, has_lock %ld\n", has_lock); + if (has_lock == 0) + queue_work(suspend_work_queue, &suspend_work); + spin_unlock_irqrestore(&list_lock, irqflags); +} +static DEFINE_TIMER(expire_timer, expire_wake_locks, 0, 0); + +static int power_suspend_late(struct device *dev) +{ + int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0; +#ifdef CONFIG_WAKELOCK_STAT + wait_for_wakeup = !ret; +#endif + if (debug_mask & DEBUG_SUSPEND) + pr_info("power_suspend_late return %d\n", ret); + return ret; +} + +static struct dev_pm_ops power_driver_pm_ops = { + .suspend_noirq = power_suspend_late, +}; + +static struct platform_driver power_driver = { + .driver.name = "power", + .driver.pm = &power_driver_pm_ops, +}; +static struct platform_device power_device = { + .name = "power", +}; + +void wake_lock_init(struct wake_lock *lock, int type, const char *name) +{ + unsigned long irqflags = 0; + + if (name) + lock->name = name; + BUG_ON(!lock->name); + + if (debug_mask & DEBUG_WAKE_LOCK) + pr_info("wake_lock_init name=%s\n", lock->name); +#ifdef CONFIG_WAKELOCK_STAT + lock->stat.count = 0; + lock->stat.expire_count = 0; + lock->stat.wakeup_count = 0; + lock->stat.total_time = ktime_set(0, 0); + lock->stat.prevent_suspend_time = ktime_set(0, 0); + lock->stat.max_time = ktime_set(0, 0); + lock->stat.last_time = ktime_set(0, 0); +#endif + lock->flags = (type & WAKE_LOCK_TYPE_MASK) | WAKE_LOCK_INITIALIZED; + + INIT_LIST_HEAD(&lock->link); + spin_lock_irqsave(&list_lock, irqflags); + list_add(&lock->link, &inactive_locks); + spin_unlock_irqrestore(&list_lock, irqflags); +} +EXPORT_SYMBOL(wake_lock_init); + +void wake_lock_destroy(struct wake_lock *lock) +{ + unsigned long irqflags; + if (debug_mask & DEBUG_WAKE_LOCK) + pr_info("wake_lock_destroy name=%s\n", lock->name); + spin_lock_irqsave(&list_lock, irqflags); + lock->flags &= ~WAKE_LOCK_INITIALIZED; +#ifdef CONFIG_WAKELOCK_STAT + if (lock->stat.count) { + deleted_wake_locks.stat.count += lock->stat.count; + deleted_wake_locks.stat.expire_count += lock->stat.expire_count; + deleted_wake_locks.stat.total_time = + ktime_add(deleted_wake_locks.stat.total_time, + lock->stat.total_time); + deleted_wake_locks.stat.prevent_suspend_time = + ktime_add(deleted_wake_locks.stat.prevent_suspend_time, + lock->stat.prevent_suspend_time); + deleted_wake_locks.stat.max_time = + ktime_add(deleted_wake_locks.stat.max_time, + lock->stat.max_time); + } +#endif + list_del(&lock->link); + spin_unlock_irqrestore(&list_lock, irqflags); +} +EXPORT_SYMBOL(wake_lock_destroy); + +static void wake_lock_internal( + struct wake_lock *lock, long timeout, int has_timeout) +{ + int type; + unsigned long irqflags; + long expire_in; + + spin_lock_irqsave(&list_lock, irqflags); + type = lock->flags & WAKE_LOCK_TYPE_MASK; + BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); + BUG_ON(!(lock->flags & WAKE_LOCK_INITIALIZED)); +#ifdef CONFIG_WAKELOCK_STAT + if (type == WAKE_LOCK_SUSPEND && wait_for_wakeup) { + if (debug_mask & DEBUG_WAKEUP) + pr_info("wakeup wake lock: %s\n", lock->name); + wait_for_wakeup = 0; + lock->stat.wakeup_count++; + } + if ((lock->flags & WAKE_LOCK_AUTO_EXPIRE) && + (long)(lock->expires - jiffies) <= 0) { + wake_unlock_stat_locked(lock, 0); + lock->stat.last_time = ktime_get(); + } +#endif + if (!(lock->flags & WAKE_LOCK_ACTIVE)) { + lock->flags |= WAKE_LOCK_ACTIVE; +#ifdef CONFIG_WAKELOCK_STAT + lock->stat.last_time = ktime_get(); +#endif + } + list_del(&lock->link); + if (has_timeout) { + if (debug_mask & DEBUG_WAKE_LOCK) + pr_info("wake_lock: %s, type %d, timeout %ld.%03lu\n", + lock->name, type, timeout / HZ, + (timeout % HZ) * MSEC_PER_SEC / HZ); + lock->expires = jiffies + timeout; + lock->flags |= WAKE_LOCK_AUTO_EXPIRE; + list_add_tail(&lock->link, &active_wake_locks[type]); + } else { + if (debug_mask & DEBUG_WAKE_LOCK) + pr_info("wake_lock: %s, type %d\n", lock->name, type); + lock->expires = LONG_MAX; + lock->flags &= ~WAKE_LOCK_AUTO_EXPIRE; + list_add(&lock->link, &active_wake_locks[type]); + } + if (type == WAKE_LOCK_SUSPEND) { + current_event_num++; +#ifdef CONFIG_WAKELOCK_STAT + if (lock == &main_wake_lock) + update_sleep_wait_stats_locked(1); + else if (!wake_lock_active(&main_wake_lock)) + update_sleep_wait_stats_locked(0); +#endif + if (has_timeout) + expire_in = has_wake_lock_locked(type); + else + expire_in = -1; + if (expire_in > 0) { + if (debug_mask & DEBUG_EXPIRE) + pr_info("wake_lock: %s, start expire timer, " + "%ld\n", lock->name, expire_in); + mod_timer(&expire_timer, jiffies + expire_in); + } else { + if (del_timer(&expire_timer)) + if (debug_mask & DEBUG_EXPIRE) + pr_info("wake_lock: %s, stop expire timer\n", + lock->name); + if (expire_in == 0) + queue_work(suspend_work_queue, &suspend_work); + } + } + spin_unlock_irqrestore(&list_lock, irqflags); +} + +void wake_lock(struct wake_lock *lock) +{ + wake_lock_internal(lock, 0, 0); +} +EXPORT_SYMBOL(wake_lock); + +void wake_lock_timeout(struct wake_lock *lock, long timeout) +{ + wake_lock_internal(lock, timeout, 1); +} +EXPORT_SYMBOL(wake_lock_timeout); + +void wake_unlock(struct wake_lock *lock) +{ + int type; + unsigned long irqflags; + spin_lock_irqsave(&list_lock, irqflags); + type = lock->flags & WAKE_LOCK_TYPE_MASK; +#ifdef CONFIG_WAKELOCK_STAT + wake_unlock_stat_locked(lock, 0); +#endif + if (debug_mask & DEBUG_WAKE_LOCK) + pr_info("wake_unlock: %s\n", lock->name); + lock->flags &= ~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE); + list_del(&lock->link); + list_add(&lock->link, &inactive_locks); + if (type == WAKE_LOCK_SUSPEND) { + long has_lock = has_wake_lock_locked(type); + if (has_lock > 0) { + if (debug_mask & DEBUG_EXPIRE) + pr_info("wake_unlock: %s, start expire timer, " + "%ld\n", lock->name, has_lock); + mod_timer(&expire_timer, jiffies + has_lock); + } else { + if (del_timer(&expire_timer)) + if (debug_mask & DEBUG_EXPIRE) + pr_info("wake_unlock: %s, stop expire " + "timer\n", lock->name); + if (has_lock == 0) + queue_work(suspend_work_queue, &suspend_work); + } + if (lock == &main_wake_lock) { + if (debug_mask & DEBUG_SUSPEND) + print_active_locks(WAKE_LOCK_SUSPEND); +#ifdef CONFIG_WAKELOCK_STAT + update_sleep_wait_stats_locked(0); +#endif + } + } + spin_unlock_irqrestore(&list_lock, irqflags); +} +EXPORT_SYMBOL(wake_unlock); + +int wake_lock_active(struct wake_lock *lock) +{ + return !!(lock->flags & WAKE_LOCK_ACTIVE); +} +EXPORT_SYMBOL(wake_lock_active); + +static int wakelock_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, wakelock_stats_show, NULL); +} + +static const struct file_operations wakelock_stats_fops = { + .owner = THIS_MODULE, + .open = wakelock_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init wakelocks_init(void) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(active_wake_locks); i++) + INIT_LIST_HEAD(&active_wake_locks[i]); + +#ifdef CONFIG_WAKELOCK_STAT + wake_lock_init(&deleted_wake_locks, WAKE_LOCK_SUSPEND, + "deleted_wake_locks"); +#endif + wake_lock_init(&main_wake_lock, WAKE_LOCK_SUSPEND, "main"); + wake_lock(&main_wake_lock); + wake_lock_init(&unknown_wakeup, WAKE_LOCK_SUSPEND, "unknown_wakeups"); + wake_lock_init(&suspend_backoff_lock, WAKE_LOCK_SUSPEND, + "suspend_backoff"); + + ret = platform_device_register(&power_device); + if (ret) { + pr_err("wakelocks_init: platform_device_register failed\n"); + goto err_platform_device_register; + } + ret = platform_driver_register(&power_driver); + if (ret) { + pr_err("wakelocks_init: platform_driver_register failed\n"); + goto err_platform_driver_register; + } + + INIT_COMPLETION(suspend_sys_sync_comp); + suspend_sys_sync_work_queue = + create_singlethread_workqueue("suspend_sys_sync"); + if (suspend_sys_sync_work_queue == NULL) { + ret = -ENOMEM; + goto err_suspend_sys_sync_work_queue; + } + + suspend_work_queue = create_singlethread_workqueue("suspend"); + if (suspend_work_queue == NULL) { + ret = -ENOMEM; + goto err_suspend_work_queue; + } + +#ifdef CONFIG_WAKELOCK_STAT + proc_create("wakelocks", S_IRUGO, NULL, &wakelock_stats_fops); +#endif + + return 0; + +err_suspend_work_queue: +err_suspend_sys_sync_work_queue: + platform_driver_unregister(&power_driver); +err_platform_driver_register: + platform_device_unregister(&power_device); +err_platform_device_register: + wake_lock_destroy(&suspend_backoff_lock); + wake_lock_destroy(&unknown_wakeup); + wake_lock_destroy(&main_wake_lock); +#ifdef CONFIG_WAKELOCK_STAT + wake_lock_destroy(&deleted_wake_locks); +#endif + return ret; +} + +static void __exit wakelocks_exit(void) +{ +#ifdef CONFIG_WAKELOCK_STAT + remove_proc_entry("wakelocks", NULL); +#endif + destroy_workqueue(suspend_work_queue); + destroy_workqueue(suspend_sys_sync_work_queue); + platform_driver_unregister(&power_driver); + platform_device_unregister(&power_device); + wake_lock_destroy(&suspend_backoff_lock); + wake_lock_destroy(&unknown_wakeup); + wake_lock_destroy(&main_wake_lock); +#ifdef CONFIG_WAKELOCK_STAT + wake_lock_destroy(&deleted_wake_locks); +#endif +} + +core_initcall(wakelocks_init); +module_exit(wakelocks_exit); diff --git a/kernel/printk.c b/kernel/printk.c index a4280499db0f57ae2f71c70a8ef8ad79b9606f77..4cf4670f234dc446e0985592cdebc2c87dec5f0e 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -44,6 +44,7 @@ #include +#include #define CREATE_TRACE_POINTS #include @@ -56,10 +57,6 @@ void asmlinkage __attribute__((weak)) early_printk(const char *fmt, ...) #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) -#ifdef CONFIG_DEBUG_LL -extern void printascii(char *); -#endif - /* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL CONFIG_DEFAULT_MESSAGE_LOGLEVEL @@ -799,6 +796,11 @@ asmlinkage int printk(const char *fmt, ...) { va_list args; int r; +#ifdef CONFIG_MSM_RTB + void *caller = __builtin_return_address(0); + + uncached_logk_pc(LOGK_LOGBUF, caller, (void *)log_end); +#endif #ifdef CONFIG_KGDB_KDB if (unlikely(kdb_trap_printk)) { @@ -935,9 +937,6 @@ asmlinkage int vprintk(const char *fmt, va_list args) printed_len += vscnprintf(printk_buf + printed_len, sizeof(printk_buf) - printed_len, fmt, args); -#ifdef CONFIG_DEBUG_LL - printascii(printk_buf); -#endif p = printk_buf; @@ -1199,6 +1198,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 @@ -1209,6 +1216,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) @@ -1220,6 +1230,13 @@ static int __cpuinit console_cpu_notify(struct notifier_block *self, case CPU_UP_CANCELED: console_lock(); console_unlock(); + break; + case CPU_DYING: + /* invoked with preemption disabled, so defer */ + 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 7e8ea66a8c016ffc934997256835f3fdc735f2f8..7203c06273ac7d3917c8616363b5d7f085195576 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 @@ -339,12 +357,18 @@ int walk_system_ram_range(unsigned long start_pfn, unsigned long nr_pages, while ((res.start < res.end) && (find_next_system_ram(&res, "System RAM") >= 0)) { pfn = (res.start + PAGE_SIZE - 1) >> PAGE_SHIFT; - end_pfn = (res.end + 1) >> PAGE_SHIFT; + if (res.end + 1 <= 0) + end_pfn = res.end >> PAGE_SHIFT; + else + end_pfn = (res.end + 1) >> PAGE_SHIFT; if (end_pfn > pfn) ret = (*func)(pfn, end_pfn - pfn, arg); if (ret) break; - res.start = res.end + 1; + if (res.end + 1 > res.start) + res.start = res.end + 1; + else + res.start = res.end; res.end = orig_end; } return ret; diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 9d35dfeda3ef04dbe99e3f26698a8db9b9df9c89..45a8d867c66ca091885cd49679138cd7c473439e 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -1215,7 +1215,7 @@ unsigned long wait_task_inactive(struct task_struct *p, long match_state) * yield - it could be a while. */ if (unlikely(on_rq)) { - ktime_t to = ktime_set(0, NSEC_PER_SEC/HZ); + ktime_t to = ktime_set(0, NSEC_PER_MSEC); set_current_state(TASK_UNINTERRUPTIBLE); schedule_hrtimeout(&to, HRTIMER_MODE_REL); @@ -3544,7 +3544,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); @@ -3557,7 +3557,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); @@ -3569,12 +3572,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; } @@ -3591,10 +3594,24 @@ 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 @@ -3610,7 +3627,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); @@ -3625,7 +3642,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; @@ -3647,7 +3665,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); @@ -3662,7 +3680,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; @@ -3685,7 +3703,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); diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c index 2f194e965715183786f2f8b640ddcbbf1b456dc4..b0f118e32ccba9d7d2140d6df35c428f1df92cfd 100644 --- a/kernel/stop_machine.c +++ b/kernel/stop_machine.c @@ -133,8 +133,8 @@ void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg, cpu_stop_queue_work(&per_cpu(cpu_stopper, cpu), work_buf); } +DEFINE_MUTEX(stop_cpus_mutex); /* static data for stop_cpus */ -static DEFINE_MUTEX(stop_cpus_mutex); static DEFINE_PER_CPU(struct cpu_stop_work, stop_cpus_work); static void queue_stop_cpus_work(const struct cpumask *cpumask, diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 49f472582722438669597dfa30948633d372ad46..b69314289aef8fb77c760a25a673f416cd78af70 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -995,6 +995,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 a650694883a180e93c5ec1d6414e45ba904fcff3..d42c279db29611f525544381d62e772f1d994cec 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/time/Makefile b/kernel/time/Makefile index e2fd74b8e8c250cb9de5ec5650eb32f5d4dcd93d..cae2ad7491b01d0a35b11e3293ecf6b25b808c5e 100644 --- a/kernel/time/Makefile +++ b/kernel/time/Makefile @@ -1,5 +1,5 @@ obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o -obj-y += timeconv.o posix-clock.o alarmtimer.o +obj-y += timeconv.o posix-clock.o #alarmtimer.o obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 6a3a5b9ff56176c2951256edf7ef9601a0f49106..08f52bb09e82f52717b43cb09b4f988d289c0155 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -20,11 +20,17 @@ #include #include #include +#include #include #include "tick-internal.h" + +struct rq_data rq_info; +struct workqueue_struct *rq_wq; +spinlock_t rq_lock; + /* * Per cpu nohz control structure */ @@ -760,6 +766,50 @@ void tick_check_idle(int cpu) * High resolution timer specific code */ #ifdef CONFIG_HIGH_RES_TIMERS +static void update_rq_stats(void) +{ + unsigned long jiffy_gap = 0; + unsigned int rq_avg = 0; + unsigned long flags = 0; + + jiffy_gap = jiffies - rq_info.rq_poll_last_jiffy; + + if (jiffy_gap >= rq_info.rq_poll_jiffies) { + + spin_lock_irqsave(&rq_lock, flags); + + if (!rq_info.rq_avg) + rq_info.rq_poll_total_jiffies = 0; + + rq_avg = nr_running() * 10; + + if (rq_info.rq_poll_total_jiffies) { + rq_avg = (rq_avg * jiffy_gap) + + (rq_info.rq_avg * + rq_info.rq_poll_total_jiffies); + do_div(rq_avg, + rq_info.rq_poll_total_jiffies + jiffy_gap); + } + + rq_info.rq_avg = rq_avg; + rq_info.rq_poll_total_jiffies += jiffy_gap; + rq_info.rq_poll_last_jiffy = jiffies; + + spin_unlock_irqrestore(&rq_lock, flags); + } +} + +static void wakeup_user(void) +{ + unsigned long jiffy_gap; + + jiffy_gap = jiffies - rq_info.def_timer_last_jiffy; + + if (jiffy_gap >= rq_info.def_timer_jiffies) { + rq_info.def_timer_last_jiffy = jiffies; + queue_work(rq_wq, &rq_info.def_timer_work); + } +} /* * We rearm the timer until we get disabled by the idle code. * Called with interrupts disabled and timer->base->cpu_base->lock held. @@ -807,6 +857,19 @@ static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer) } update_process_times(user_mode(regs)); profile_tick(CPU_PROFILING); + + if ((rq_info.init == 1) && (tick_do_timer_cpu == cpu)) { + + /* + * update run queue statistics + */ + update_rq_stats(); + + /* + * wakeup user if needed + */ + wakeup_user(); + } } hrtimer_forward(timer, now, tick_period); diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index c0bd0308741ca1a343a9cbc0b551b4a12438ddd7..06f794082d5d13d9b4781947e3a2ec9a5c863b8f 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -1788,6 +1788,8 @@ void blk_fill_rwbs(char *rwbs, u32 rw, int bytes) rwbs[i++] = 'W'; else if (rw & REQ_DISCARD) rwbs[i++] = 'D'; + else if (rw & REQ_SANITIZE) + rwbs[i++] = 'Z'; else if (bytes) rwbs[i++] = 'R'; else diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 2a22255c10101c7a55939955a04a9834bbbb940a..05970eafb81b81e06bc2147c04eb7f973db05fd0 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -3752,7 +3752,7 @@ tracing_mark_write(struct file *filp, const char __user *ubuf, int nr_pages = 1; ssize_t written; void *page1; - void *page2; + void *page2 = NULL; int offset; int size; int len; diff --git a/lib/Makefile b/lib/Makefile index 18515f0267c41591a198a1df9b3c7950a56a16cf..acd68695763741d1c6357b1a75fdd206a78ed889 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 md5.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 + is_single_threaded.o plist.o decompress.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 b5a8b6ad2454e32ec2444509fdb7aff4c80ec2d4..b121ae52793a54736384a1248d588bc34a2e72bd 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/genalloc.c b/lib/genalloc.c index 6bc04aab6ec717ea3144303e4a0294f528b83cf9..ef156403be5f3b66a84ea75b422da6d20319278a 100644 --- a/lib/genalloc.c +++ b/lib/genalloc.c @@ -250,20 +250,24 @@ void gen_pool_destroy(struct gen_pool *pool) EXPORT_SYMBOL(gen_pool_destroy); /** - * gen_pool_alloc - allocate special memory 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. Can not be used in NMI handler on * architectures without NMI-safe cmpxchg implementation. */ -unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size) +unsigned long gen_pool_alloc_aligned(struct gen_pool *pool, size_t size, + unsigned alignment_order) { struct gen_pool_chunk *chunk; - unsigned long addr = 0; + unsigned long addr = 0, align_mask = 0; int order = pool->min_alloc_order; - int nbits, start_bit = 0, end_bit, remain; + int nbits, start_bit = 0, remain; #ifndef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG BUG_ON(in_nmi()); @@ -272,17 +276,23 @@ unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size) if (size == 0) return 0; + if (alignment_order > order) + align_mask = (1 << (alignment_order - order)) - 1; + nbits = (size + (1UL << order) - 1) >> order; + rcu_read_lock(); list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) { + unsigned long chunk_size; if (size > atomic_read(&chunk->avail)) continue; + chunk_size = (chunk->end_addr - chunk->start_addr) >> order; - end_bit = (chunk->end_addr - chunk->start_addr) >> order; retry: - start_bit = bitmap_find_next_zero_area(chunk->bits, end_bit, - start_bit, nbits, 0); - if (start_bit >= end_bit) + start_bit = bitmap_find_next_zero_area_off(chunk->bits, chunk_size, + 0, nbits, align_mask, + chunk->start_addr); + if (start_bit >= chunk_size) continue; remain = bitmap_set_ll(chunk->bits, start_bit, nbits); if (remain) { @@ -293,14 +303,14 @@ unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size) } addr = chunk->start_addr + ((unsigned long)start_bit << order); - size = nbits << order; + size = nbits << pool->min_alloc_order; atomic_sub(size, &chunk->avail); break; } rcu_read_unlock(); return addr; } -EXPORT_SYMBOL(gen_pool_alloc); +EXPORT_SYMBOL(gen_pool_alloc_aligned); /** * gen_pool_free - free allocated special memory back to the pool diff --git a/lib/memory_alloc.c b/lib/memory_alloc.c new file mode 100644 index 0000000000000000000000000000000000000000..d931e148e5b05f3e7e07b7607753011896a5aa78 --- /dev/null +++ b/lib/memory_alloc.c @@ -0,0 +1,425 @@ +/* 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 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 void *s_start(struct seq_file *m, loff_t *pos) + __acquires(&alloc_mutex) +{ + loff_t n = *pos; + struct rb_node *r; + + mutex_lock(&alloc_mutex); + r = rb_first(&alloc_root); + + while (n > 0 && r) { + n--; + r = rb_next(r); + } + if (!n) + return r; + return NULL; +} + +static void *s_next(struct seq_file *m, void *p, loff_t *pos) +{ + struct rb_node *r = p; + ++*pos; + return rb_next(r); +} + +static void s_stop(struct seq_file *m, void *p) + __releases(&alloc_mutex) +{ + mutex_unlock(&alloc_mutex); +} + +static int s_show(struct seq_file *m, void *p) +{ + struct rb_node *r = p; + struct alloc *node = rb_entry(r, struct alloc, rb_node); + + seq_printf(m, "0x%lx 0x%p %ld %u %pS\n", node->paddr, node->vaddr, + node->len, node->mpool->id, node->caller); + return 0; +} + +static const struct seq_operations mempool_op = { + .start = s_start, + .next = s_next, + .stop = s_stop, + .show = s_show, +}; + +static int mempool_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &mempool_op); +} + +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, void *caller) +{ + 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; + node->caller = caller; + 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; + mpools[id].id = id; + 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, + __builtin_return_address(0)); + +} +EXPORT_SYMBOL_GPL(allocate_contiguous_memory); + +unsigned long _allocate_contiguous_memory_nomap(unsigned long size, + int mem_type, unsigned long align, void *caller) +{ + 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; + node->caller = caller; + 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); + +unsigned long allocate_contiguous_memory_nomap(unsigned long size, + int mem_type, unsigned long align) +{ + return _allocate_contiguous_memory_nomap(size, mem_type, align, + __builtin_return_address(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); + +static const struct file_operations mempool_operations = { + .owner = THIS_MODULE, + .open = mempool_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +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; +} + +static int __init debugfs_mempool_init(void) +{ + struct dentry *entry, *dir = debugfs_create_dir("mempool", NULL); + + if (!dir) { + pr_err("Cannot create /sys/kernel/debug/mempool"); + return -EINVAL; + } + + entry = debugfs_create_file("map", S_IRUSR, dir, + NULL, &mempool_operations); + + if (!entry) + pr_err("Cannot create /sys/kernel/debug/mempool/map"); + + return entry ? 0 : -EINVAL; +} + +module_init(debugfs_mempool_init); diff --git a/lib/spinlock_debug.c b/lib/spinlock_debug.c index 525d160d44f05c112acca5b2866b566ded334e19..c1906e41247d87a2e10253f9bd354d3d8418b312 100644 --- a/lib/spinlock_debug.c +++ b/lib/spinlock_debug.c @@ -58,7 +58,7 @@ static void spin_dump(raw_spinlock_t *lock, const char *msg) printk(KERN_EMERG "BUG: spinlock %s on CPU#%d, %s/%d\n", msg, raw_smp_processor_id(), current->comm, task_pid_nr(current)); - printk(KERN_EMERG " lock: %p, .magic: %08x, .owner: %s/%d, " + printk(KERN_EMERG " lock: %ps, .magic: %08x, .owner: %s/%d, " ".owner_cpu: %d\n", lock, lock->magic, owner ? owner->comm : "", @@ -118,6 +118,10 @@ static void __spin_lock_debug(raw_spinlock_t *lock) /* lockup suspected: */ if (print_once) { print_once = 0; + printk(KERN_EMERG "BUG: spinlock lockup on CPU#%d, " + "%s/%d, %ps\n", + raw_smp_processor_id(), current->comm, + task_pid_nr(current), lock); spin_dump(lock, "lockup"); #ifdef CONFIG_SMP trigger_all_cpu_backtrace(); diff --git a/lib/vsprintf.c b/lib/vsprintf.c index abbabec9720a1947ffa5364ae9aab9ec9696ecfe..f5dfe0ca34f60c25a1367da581dd0c842db1ff87 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -436,7 +436,7 @@ char *symbol_string(char *buf, char *end, void *ptr, else if (ext != 'f' && ext != 's') sprint_symbol(sym, value); else - kallsyms_lookup(value, NULL, NULL, NULL, sym); + sprint_symbol_no_offset(sym, value); return string(buf, end, sym, spec); #else diff --git a/mm/Kconfig b/mm/Kconfig index e338407f1225f0873a8eb20761c86fd82db1dfc0..cad244caa939419f1506ad52f792d4a5a1b18fe5 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -145,7 +145,7 @@ config MEMORY_HOTPLUG bool "Allow for memory hot-add" depends on SPARSEMEM || X86_64_ACPI_NUMA depends on HOTPLUG && ARCH_ENABLE_MEMORY_HOTPLUG - depends on (IA64 || X86 || PPC_BOOK3S_64 || SUPERH || S390) + depends on (IA64 || X86 || PPC_BOOK3S_64 || SUPERH || S390 || ARM) config MEMORY_HOTPLUG_SPARSE def_bool y diff --git a/mm/memblock.c b/mm/memblock.c index a44eab3157f8dc4b25e686b643ccacbc8449e041..b510ca5c5cb2d20b1e3d672c6547dc3b8bd87e42 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -867,6 +867,12 @@ int __init_memblock memblock_is_region_memory(phys_addr_t base, phys_addr_t size memblock.memory.regions[idx].size) >= end; } +int __init_memblock memblock_overlaps_memory(phys_addr_t base, phys_addr_t size) +{ + memblock_cap_size(base, &size); + return memblock_overlaps_region(&memblock.memory, base, size) >= 0; +} + int __init_memblock memblock_is_region_reserved(phys_addr_t base, phys_addr_t size) { memblock_cap_size(base, &size); diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c index 6629fafd6ce4a65eae9421dd9d68b25805bca993..b031f96f6ec94130c3437c8b4fa82213452099b7 100644 --- a/mm/memory_hotplug.c +++ b/mm/memory_hotplug.c @@ -123,9 +123,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; @@ -144,9 +145,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); @@ -410,6 +423,11 @@ void __online_page_set_limits(struct page *page) { unsigned long pfn = page_to_pfn(page); + totalram_pages++; +#ifdef CONFIG_FIX_MOVABLE_ZONE + if (zone_idx(page_zone(page)) != ZONE_MOVABLE) + total_unmovable_pages++; +#endif if (pfn >= num_physpages) num_physpages = pfn + 1; } @@ -511,6 +529,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages) zone->present_pages += onlined_pages; zone->zone_pgdat->node_present_pages += onlined_pages; + drain_all_pages(); if (need_zonelists_rebuild) build_all_zonelists(zone); else @@ -658,6 +677,54 @@ 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 0; + } + + 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_resource(res_old); + if (PageSlab(virt_to_head_page(res_old))) + kfree(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 @@ -754,7 +821,8 @@ static struct page * hotremove_migrate_alloc(struct page *page, unsigned long private, int **x) { /* This should be improooooved!! */ - return alloc_page(GFP_HIGHUSER_MOVABLE); + return alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NORETRY | __GFP_NOWARN | + __GFP_NOMEMALLOC); } #define NR_OFFLINE_AT_ONCE_PAGES (256) @@ -958,10 +1026,17 @@ static int __ref offline_pages(unsigned long start_pfn, /* reset pagetype flags and makes migrate type to be MOVABLE */ undo_isolate_page_range(start_pfn, end_pfn); /* removal success */ - zone->present_pages -= offlined_pages; + if (offlined_pages > zone->present_pages) + zone->present_pages = 0; + else + zone->present_pages -= offlined_pages; zone->zone_pgdat->node_present_pages -= offlined_pages; totalram_pages -= offlined_pages; +#ifdef CONFIG_FIX_MOVABLE_ZONE + if (zone_idx(zone) != ZONE_MOVABLE) + total_unmovable_pages -= offlined_pages; +#endif init_per_zone_wmark_min(); if (!node_present_pages(node)) { @@ -996,6 +1071,7 @@ int remove_memory(u64 start, u64 size) end_pfn = start_pfn + PFN_DOWN(size); return offline_pages(start_pfn, end_pfn, 120 * HZ); } + #else int remove_memory(u64 start, u64 size) { diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 716061f48fd36258c4380e3f9b9648059a7ae8b5..05e8b06e9f6511fbe1cbbc02ad726c0afe288d19 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -105,6 +105,9 @@ unsigned long totalreserve_pages __read_mostly; */ unsigned long dirty_balance_reserve __read_mostly; +#ifdef CONFIG_FIX_MOVABLE_ZONE +unsigned long total_unmovable_pages __read_mostly; +#endif int percpu_pagelist_fraction; gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK; @@ -176,6 +179,9 @@ int sysctl_lowmem_reserve_ratio[MAX_NR_ZONES-1] = { }; EXPORT_SYMBOL(totalram_pages); +#ifdef CONFIG_FIX_MOVABLE_ZONE +EXPORT_SYMBOL(total_unmovable_pages); +#endif static char * const zone_names[MAX_NR_ZONES] = { #ifdef CONFIG_ZONE_DMA @@ -1549,8 +1555,9 @@ static inline int should_fail_alloc_page(gfp_t gfp_mask, unsigned int order) static bool __zone_watermark_ok(struct zone *z, int order, unsigned long mark, int classzone_idx, int alloc_flags, long free_pages) { - /* free_pages my go negative - that's OK */ + /* free_pages may go negative - that's OK */ long min = mark; + long lowmem_reserve = z->lowmem_reserve[classzone_idx]; int o; free_pages -= (1 << order) - 1; @@ -1559,7 +1566,7 @@ static bool __zone_watermark_ok(struct zone *z, int order, unsigned long mark, if (alloc_flags & ALLOC_HARDER) min -= min / 4; - if (free_pages <= min + z->lowmem_reserve[classzone_idx]) + if (free_pages <= min + lowmem_reserve) return false; for (o = 0; o < order; o++) { /* At the next order, this order's pages become unavailable */ @@ -4575,6 +4582,9 @@ static void __init find_zone_movable_pfns_for_nodes(void) unsigned long totalpages = early_calculate_totalpages(); int usable_nodes = nodes_weight(node_states[N_HIGH_MEMORY]); +#ifdef CONFIG_FIX_MOVABLE_ZONE + required_movablecore = movable_reserved_size >> PAGE_SHIFT; +#endif /* * If movablecore was specified, calculate what size of * kernelcore that corresponds so that memory usable for diff --git a/mm/sparse.c b/mm/sparse.c index a8bc7d364deb0a764cbd28956f1853fbb3ce421c..55cda7a9dfdd83b282282ec3696782bbd38a029d 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/mm/vmalloc.c b/mm/vmalloc.c index 94dff883b449e8c1ed3aba28c1b7fd98f41c08db..7d43e49dc7a5e9e7d03dab0b46055d177adc19dc 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -1647,9 +1647,14 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align, struct vm_struct *area; void *addr; unsigned long real_size = size; +#ifdef CONFIG_FIX_MOVABLE_ZONE + unsigned long total_pages = total_unmovable_pages; +#else + unsigned long total_pages = totalram_pages; +#endif size = PAGE_ALIGN(size); - if (!size || (size >> PAGE_SHIFT) > totalram_pages) + if (!size || (size >> PAGE_SHIFT) > total_pages) goto fail; area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST, @@ -2210,6 +2215,14 @@ struct vm_struct *alloc_vm_area(size_t size, pte_t **ptes) return NULL; } + /* + * If the allocated address space is passed to a hypercall + * before being used then we cannot rely on a page fault to + * trigger an update of the page tables. So sync all the page + * tables here. + */ + vmalloc_sync_all(); + return area; } EXPORT_SYMBOL_GPL(alloc_vm_area); diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index 3537d385035e61fc090e9094b83850243268f992..fc4543ab9fd999087da062a1c65843d87aa03172 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -11,6 +11,8 @@ menuconfig BT select CRYPTO_BLKCIPHER select CRYPTO_AES select CRYPTO_ECB + select CRYPTO_HMAC + select CRYPTO_SHA256 help Bluetooth is low-cost, low-power, short-range wireless technology. It was designed as a replacement for cables and other short-range diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 2dc5a5700f533191558381d8bcc0b16016b185a6..828be1d7e7cdb5c92299d6cbb6eec3153460db3d 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -9,4 +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 l2cap_core.o l2cap_sock.o smp.o sco.o lib.o + hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o sco.o lib.o \ + amp.o diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index b9af14e8a9eecb32b01e326d098a226c7d9fc61c..219df5ca811ddc8a0b4e0d382912d22eee4aebf3 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -80,16 +80,19 @@ static const char *const bt_slock_key_strings[BT_MAX_PROTO] = { "slock-AF_BLUETOOTH-BTPROTO_AVDTP", }; -void bt_sock_reclassify_lock(struct sock *sk, int proto) +static inline void bt_sock_reclassify_lock(struct socket *sock, int proto) { - BUG_ON(!sk); + struct sock *sk = sock->sk; + + if (!sk) + return; + BUG_ON(sock_owned_by_user(sk)); sock_lock_init_class_and_name(sk, bt_slock_key_strings[proto], &bt_slock_key[proto], bt_key_strings[proto], &bt_lock_key[proto]); } -EXPORT_SYMBOL(bt_sock_reclassify_lock); int bt_sock_register(int proto, const struct net_proto_family *ops) { @@ -180,8 +183,7 @@ static int bt_sock_create(struct net *net, struct socket *sock, int proto, if (bt_proto[proto] && try_module_get(bt_proto[proto]->owner)) { err = bt_proto[proto]->create(net, sock, proto, kern); - if (!err) - bt_sock_reclassify_lock(sock->sk, proto); + bt_sock_reclassify_lock(sock, proto); module_put(bt_proto[proto]->owner); } @@ -192,17 +194,17 @@ static int bt_sock_create(struct net *net, struct socket *sock, int proto, void bt_sock_link(struct bt_sock_list *l, struct sock *sk) { - write_lock(&l->lock); + write_lock_bh(&l->lock); sk_add_node(sk, &l->head); - write_unlock(&l->lock); + write_unlock_bh(&l->lock); } EXPORT_SYMBOL(bt_sock_link); void bt_sock_unlink(struct bt_sock_list *l, struct sock *sk) { - write_lock(&l->lock); + write_lock_bh(&l->lock); sk_del_node_init(sk); - write_unlock(&l->lock); + write_unlock_bh(&l->lock); } EXPORT_SYMBOL(bt_sock_unlink); @@ -235,14 +237,15 @@ struct sock *bt_accept_dequeue(struct sock *parent, struct socket *newsock) BT_DBG("parent %p", parent); + local_bh_disable(); list_for_each_safe(p, n, &bt_sk(parent)->accept_q) { sk = (struct sock *) list_entry(p, struct bt_sock, accept_q); - lock_sock(sk); + bh_lock_sock(sk); /* FIXME: Is this check still needed */ if (sk->sk_state == BT_CLOSED) { - release_sock(sk); + bh_unlock_sock(sk); bt_accept_unlink(sk); continue; } @@ -253,12 +256,14 @@ struct sock *bt_accept_dequeue(struct sock *parent, struct socket *newsock) if (newsock) sock_graft(sk, newsock); - release_sock(sk); + bh_unlock_sock(sk); + local_bh_enable(); return sk; } - release_sock(sk); + bh_unlock_sock(sk); } + local_bh_enable(); return NULL; } @@ -488,7 +493,7 @@ unsigned int bt_sock_poll(struct file *file, struct socket *sock, poll_table *wa sk->sk_state == BT_CONFIG) return mask; - if (!bt_sk(sk)->suspended && sock_writeable(sk)) + if (sock_writeable(sk)) mask |= POLLOUT | POLLWRNORM | POLLWRBAND; else set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags); @@ -553,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; @@ -568,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 0000000000000000000000000000000000000000..ba638d166b0c7fd4669e8f0f1479f2d3cedd758e --- /dev/null +++ b/net/bluetooth/amp.c @@ -0,0 +1,2041 @@ +/* + Copyright (c) 2010-2012 Code Aurora Forum. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or 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 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(&_mgr_list_lock); + list_del(&mgr->list); + write_unlock(&_mgr_list_lock); + + read_lock(&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(&mgr->ctx_list_lock); + BT_DBG("kill ctx %p", ctx); + kill_ctx(ctx); + read_lock(&mgr->ctx_list_lock); + } + read_unlock(&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(&_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(&_mgr_list_lock); + return found; +} + +static struct amp_mgr *get_create_amp_mgr(struct hci_conn *hcon, + struct sk_buff *skb) +{ + struct amp_mgr *mgr; + + write_lock(&_mgr_list_lock); + list_for_each_entry(mgr, &_mgr_list, list) { + if (mgr->l2cap_conn == hcon->l2cap_data) { + BT_DBG("found %p", mgr); + write_unlock(&_mgr_list_lock); + goto gc_finished; + } + } + write_unlock(&_mgr_list_lock); + + mgr = kzalloc(sizeof(*mgr), GFP_ATOMIC); + if (!mgr) + return NULL; + + mgr->l2cap_conn = hcon->l2cap_data; + mgr->next_ident = 1; + INIT_LIST_HEAD(&mgr->ctx_list); + rwlock_init(&mgr->ctx_list_lock); + mgr->skb = skb; + BT_DBG("hcon %p mgr %p", hcon, mgr); + mgr->a2mp_sock = open_fixed_channel(&hcon->hdev->bdaddr, &hcon->dst); + if (!mgr->a2mp_sock) { + kfree(mgr); + return NULL; + } + write_lock(&_mgr_list_lock); + list_add(&(mgr->list), &_mgr_list); + write_unlock(&_mgr_list_lock); + +gc_finished: + 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(&mgr->ctx_list_lock); + list_add(&ctx->list, &mgr->ctx_list); + write_unlock(&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(&mgr->ctx_list_lock); + list_del(&ctx->list); + write_unlock(&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(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + if (ctx->type == type) { + fnd = ctx; + break; + } + } + read_unlock(&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(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + if ((ctx->type == type) && (ctx != cur)) { + fnd = ctx; + break; + } + } + read_unlock(&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(&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(&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(&_mgr_list_lock); + list_for_each_entry(mgr, &_mgr_list, list) { + struct amp_ctx *ctx; + read_lock(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + struct hci_dev *ctx_hdev; + ctx_hdev = hci_dev_get(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(&mgr->ctx_list_lock); + } + read_unlock(&_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 = 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; + + list_for_each_entry(mgr, &_mgr_list, list) { + if (mgr->discovered) + send_a2mp_cl(mgr, next_ident(mgr), + A2MP_CHANGE_NOTIFY, 0, NULL); + } +} + +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(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->hcon, 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); + hdev = hci_dev_get(id); + if (!hdev) + goto ap_finished; + BT_DBG("hdev %p", hdev); + mgr = get_create_amp_mgr(lcon->hcon, NULL); + if (!mgr) + goto ap_finished; + BT_DBG("mgr %p", mgr); + 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: + if (hdev) + hci_dev_put(hdev); + 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(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(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 = 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)); + skb_pull(skb, sizeof(*grsp)); + 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); + + 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->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); + skb_pull(skb, ctx->d.cpl.rem_len); + 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; + + /* 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(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(&mgr->ctx_list_lock); + if (!list_empty(&mgr->ctx_list)) + ctx = list_first_entry(&mgr->ctx_list, struct amp_ctx, list); + read_unlock(&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 */ + +static 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 hci_conn *hcon = work->hcon; + struct sk_buff *skb = work->skb; + struct amp_mgr *mgr; + + mgr = get_create_amp_mgr(hcon, skb); + BT_DBG("mgr %p", mgr); + hci_conn_put(hcon); + 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 hci_conn *hcon, struct sk_buff *skb) +{ + struct amp_work_conn_ind *work; + BT_DBG("hcon %p, skb %p", hcon, skb); + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, conn_ind_worker); + hci_conn_hold(hcon); + work->hcon = hcon; + work->skb = skb; + if (!queue_work(amp_workqueue, (struct work_struct *) work)) { + hci_conn_put(hcon); + 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/core.c b/net/bluetooth/bnep/core.c index a779ec703323ce7293522e3cdb7af1d41b3c58a2..f504921921972f96b3e86fe6782c59a75c843bbf 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -26,6 +26,7 @@ */ #include +#include #include #include @@ -56,8 +57,8 @@ #define VERSION "1.3" -static bool compress_src = true; -static bool compress_dst = true; +static bool compress_src = 1; +static bool compress_dst = 1; static LIST_HEAD(bnep_session_list); static DECLARE_RWSEM(bnep_session_sem); @@ -65,24 +66,31 @@ static DECLARE_RWSEM(bnep_session_sem); static struct bnep_session *__bnep_get_session(u8 *dst) { struct bnep_session *s; + struct list_head *p; BT_DBG(""); - list_for_each_entry(s, &bnep_session_list, list) + list_for_each(p, &bnep_session_list) { + s = list_entry(p, struct bnep_session, list); if (!compare_ether_addr(dst, s->eh.h_source)) return s; - + } return NULL; } static void __bnep_link_session(struct bnep_session *s) { + /* It's safe to call __module_get() here because sessions are added + by the socket layer which has to hold the reference to this module. + */ + __module_get(THIS_MODULE); list_add(&s->list, &bnep_session_list); } static void __bnep_unlink_session(struct bnep_session *s) { list_del(&s->list); + module_put(THIS_MODULE); } static int bnep_send(struct bnep_session *s, void *data, size_t len) @@ -502,7 +510,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 */ @@ -523,7 +531,6 @@ static int bnep_session(void *arg) up_write(&bnep_session_sem); free_netdev(dev); - module_put_and_exit(0); return 0; } @@ -610,11 +617,9 @@ int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock) __bnep_link_session(s); - __module_get(THIS_MODULE); s->task = kthread_run(bnep_session, s, "kbnepd %s", dev->name); if (IS_ERR(s->task)) { /* Session thread start failed, gotta cleanup. */ - module_put(THIS_MODULE); unregister_netdev(dev); __bnep_unlink_session(s); err = PTR_ERR(s->task); @@ -663,14 +668,17 @@ static void __bnep_copy_ci(struct bnep_conninfo *ci, struct bnep_session *s) int bnep_get_connlist(struct bnep_connlist_req *req) { - struct bnep_session *s; + struct list_head *p; int err = 0, n = 0; down_read(&bnep_session_sem); - list_for_each_entry(s, &bnep_session_list, list) { + list_for_each(p, &bnep_session_list) { + struct bnep_session *s; struct bnep_conninfo ci; + s = list_entry(p, struct bnep_session, list); + __bnep_copy_ci(&ci, s); if (copy_to_user(req->ci, &ci, sizeof(ci))) { diff --git a/net/bluetooth/bnep/netdev.c b/net/bluetooth/bnep/netdev.c index bc4086480d97fa7586c3f8b01e07cd1c8ebbc69d..155ff7406776f3790498f741972f48ec9bf1b5b9 100644 --- a/net/bluetooth/bnep/netdev.c +++ b/net/bluetooth/bnep/netdev.c @@ -26,6 +26,7 @@ */ #include +#include #include #include diff --git a/net/bluetooth/bnep/sock.c b/net/bluetooth/bnep/sock.c index 180bfc45810da0217e4639dbd6d154a91a63c128..17800b1d28ea33c4d3b36801cdc1092a2930b88f 100644 --- a/net/bluetooth/bnep/sock.c +++ b/net/bluetooth/bnep/sock.c @@ -42,6 +42,7 @@ #include #include +#include #include "bnep.h" @@ -142,10 +143,10 @@ static int bnep_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigne { if (cmd == BNEPGETCONNLIST) { struct bnep_connlist_req cl; - u32 uci; + uint32_t uci; int err; - if (get_user(cl.cnum, (u32 __user *) arg) || + if (get_user(cl.cnum, (uint32_t __user *) arg) || get_user(uci, (u32 __user *) (arg + 4))) return -EFAULT; @@ -156,7 +157,7 @@ static int bnep_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigne err = bnep_get_connlist(&cl); - if (!err && put_user(cl.cnum, (u32 __user *) arg)) + if (!err && put_user(cl.cnum, (uint32_t __user *) arg)) err = -EFAULT; return err; diff --git a/net/bluetooth/cmtp/capi.c b/net/bluetooth/cmtp/capi.c index 50f0d135eb8f201daf8156c433f08a973bfa338f..744233cba244bc8c1f89f71a0102e1d69dc1d856 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); @@ -386,8 +387,7 @@ static void cmtp_reset_ctr(struct capi_ctr *ctrl) capi_ctr_down(ctrl); - atomic_inc(&session->terminate); - wake_up_process(session->task); + kthread_stop(session->task); } static void cmtp_register_appl(struct capi_ctr *ctrl, __u16 appl, capi_register_params *rp) diff --git a/net/bluetooth/cmtp/cmtp.h b/net/bluetooth/cmtp/cmtp.h index c32638dddbf9409d685c3436eb1b1541d04d5a9e..db43b54ac9afb91cfc78f911f8a15c05104fe7ed 100644 --- a/net/bluetooth/cmtp/cmtp.h +++ b/net/bluetooth/cmtp/cmtp.h @@ -81,7 +81,6 @@ struct cmtp_session { char name[BTNAMSIZ]; - atomic_t terminate; struct task_struct *task; wait_queue_head_t wait; diff --git a/net/bluetooth/cmtp/core.c b/net/bluetooth/cmtp/core.c index 6c9c1fd601cac41e879c3e041f103ee645ebd458..bff02adbc90d46e23e93486534cee2c0817af6fb 100644 --- a/net/bluetooth/cmtp/core.c +++ b/net/bluetooth/cmtp/core.c @@ -53,24 +53,28 @@ static LIST_HEAD(cmtp_session_list); static struct cmtp_session *__cmtp_get_session(bdaddr_t *bdaddr) { struct cmtp_session *session; + struct list_head *p; BT_DBG(""); - list_for_each_entry(session, &cmtp_session_list, list) + list_for_each(p, &cmtp_session_list) { + session = list_entry(p, struct cmtp_session, list); if (!bacmp(bdaddr, &session->bdaddr)) return session; - + } return NULL; } static void __cmtp_link_session(struct cmtp_session *session) { + __module_get(THIS_MODULE); list_add(&session->list, &cmtp_session_list); } static void __cmtp_unlink_session(struct cmtp_session *session) { list_del(&session->list); + module_put(THIS_MODULE); } static void __cmtp_copy_session(struct cmtp_session *session, struct cmtp_conninfo *ci) @@ -288,11 +292,9 @@ static int cmtp_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(&session->terminate)) - break; if (sk->sk_state != BT_CONNECTED) break; @@ -308,7 +310,7 @@ static int cmtp_session(void *arg) schedule(); } - __set_current_state(TASK_RUNNING); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); down_write(&cmtp_session_sem); @@ -323,7 +325,6 @@ static int cmtp_session(void *arg) up_write(&cmtp_session_sem); kfree(session); - module_put_and_exit(0); return 0; } @@ -348,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); @@ -373,28 +373,25 @@ int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock) __cmtp_link_session(session); - __module_get(THIS_MODULE); session->task = kthread_run(cmtp_session, session, "kcmtpd_ctr_%d", session->num); if (IS_ERR(session->task)) { - module_put(THIS_MODULE); err = PTR_ERR(session->task); goto unlink; } if (!(session->flags & (1 << CMTP_LOOPBACK))) { err = cmtp_attach_device(session); - if (err < 0) { - atomic_inc(&session->terminate); - wake_up_process(session->task); - up_write(&cmtp_session_sem); - return err; - } + if (err < 0) + goto detach; } up_write(&cmtp_session_sem); return 0; +detach: + cmtp_detach_device(session); + unlink: __cmtp_unlink_session(session); @@ -419,8 +416,7 @@ int cmtp_del_connection(struct cmtp_conndel_req *req) skb_queue_purge(&session->transmit); /* Stop session thread */ - atomic_inc(&session->terminate); - wake_up_process(session->task); + kthread_stop(session->task); } else err = -ENOENT; @@ -430,16 +426,19 @@ int cmtp_del_connection(struct cmtp_conndel_req *req) int cmtp_get_connlist(struct cmtp_connlist_req *req) { - struct cmtp_session *session; + struct list_head *p; int err = 0, n = 0; BT_DBG(""); down_read(&cmtp_session_sem); - list_for_each_entry(session, &cmtp_session_list, list) { + list_for_each(p, &cmtp_session_list) { + struct cmtp_session *session; struct cmtp_conninfo ci; + session = list_entry(p, struct cmtp_session, list); + __cmtp_copy_session(session, &ci); if (copy_to_user(req->ci, &ci, sizeof(ci))) { diff --git a/net/bluetooth/cmtp/sock.c b/net/bluetooth/cmtp/sock.c index 311668d14571626dac778201a65ed21f05c72cbe..3f2dd5c25ae516f3d267f1f038cb151af21c04c8 100644 --- a/net/bluetooth/cmtp/sock.c +++ b/net/bluetooth/cmtp/sock.c @@ -39,6 +39,7 @@ #include +#include #include "cmtp.h" @@ -136,10 +137,10 @@ static int cmtp_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigne { if (cmd == CMTPGETCONNLIST) { struct cmtp_connlist_req cl; - u32 uci; + uint32_t uci; int err; - if (get_user(cl.cnum, (u32 __user *) arg) || + if (get_user(cl.cnum, (uint32_t __user *) arg) || get_user(uci, (u32 __user *) (arg + 4))) return -EFAULT; @@ -150,7 +151,7 @@ static int cmtp_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigne err = cmtp_get_connlist(&cl); - if (!err && put_user(cl.cnum, (u32 __user *) arg)) + if (!err && put_user(cl.cnum, (uint32_t __user *) arg)) err = -EFAULT; return err; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index a5f93a9a0133d0b981e36e40475fac3040df5655..02ea0820989b9ab56177becab615a982e26816d4 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-2012 Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -35,37 +35,86 @@ #include #include #include +#include #include +#include #include #include #include #include +#include -static void hci_le_connect(struct hci_conn *conn) +struct hci_conn *hci_le_connect(struct hci_dev *hdev, __u16 pkt_type, + bdaddr_t *dst, __u8 sec_level, __u8 auth_type, + struct bt_le_params *le_params) { - struct hci_dev *hdev = conn->hdev; + struct hci_conn *le; struct hci_cp_le_create_conn cp; + struct adv_entry *entry; + struct link_key *key; - conn->state = BT_CONNECT; - conn->out = true; - conn->link_mode |= HCI_LM_MASTER; - conn->sec_level = BT_SECURITY_LOW; + BT_DBG("%p", hdev); + + le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst); + if (le) { + hci_conn_hold(le); + return le; + } + + key = hci_find_link_key_type(hdev, dst, KEY_TYPE_LTK); + if (!key) { + entry = hci_find_adv_entry(hdev, dst); + if (entry) + le = hci_le_conn_add(hdev, dst, + entry->bdaddr_type); + else + le = hci_le_conn_add(hdev, dst, 0); + } else { + le = hci_le_conn_add(hdev, dst, key->addr_type); + } + + if (!le) + return ERR_PTR(-ENOMEM); + + hci_conn_hold(le); + + le->state = BT_CONNECT; + le->out = 1; + le->link_mode |= HCI_LM_MASTER; + le->sec_level = BT_SECURITY_LOW; + le->type = LE_LINK; memset(&cp, 0, sizeof(cp)); - cp.scan_interval = cpu_to_le16(0x0060); - cp.scan_window = cpu_to_le16(0x0030); - bacpy(&cp.peer_addr, &conn->dst); - cp.peer_addr_type = conn->dst_type; - cp.conn_interval_min = cpu_to_le16(0x0028); - cp.conn_interval_max = cpu_to_le16(0x0038); - cp.supervision_timeout = cpu_to_le16(0x002a); - cp.min_ce_len = cpu_to_le16(0x0000); - cp.max_ce_len = cpu_to_le16(0x0000); + if (l2cap_sock_le_params_valid(le_params)) { + cp.supervision_timeout = + cpu_to_le16(le_params->supervision_timeout); + cp.scan_interval = cpu_to_le16(le_params->scan_interval); + cp.scan_window = cpu_to_le16(le_params->scan_window); + cp.conn_interval_min = cpu_to_le16(le_params->interval_min); + cp.conn_interval_max = cpu_to_le16(le_params->interval_max); + cp.conn_latency = cpu_to_le16(le_params->latency); + cp.min_ce_len = cpu_to_le16(le_params->min_ce_len); + cp.max_ce_len = cpu_to_le16(le_params->max_ce_len); + le->conn_timeout = le_params->conn_timeout; + } else { + cp.supervision_timeout = cpu_to_le16(BT_LE_SUP_TO_DEFAULT); + cp.scan_interval = cpu_to_le16(BT_LE_SCAN_INTERVAL_DEF); + cp.scan_window = cpu_to_le16(BT_LE_SCAN_WINDOW_DEF); + cp.conn_interval_min = cpu_to_le16(BT_LE_CONN_INTERVAL_MIN_DEF); + cp.conn_interval_max = cpu_to_le16(BT_LE_CONN_INTERVAL_MAX_DEF); + cp.conn_latency = cpu_to_le16(BT_LE_LATENCY_DEF); + le->conn_timeout = 5; + } + bacpy(&cp.peer_addr, &le->dst); + cp.peer_addr_type = le->dst_type; hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp); + + return le; } +EXPORT_SYMBOL(hci_le_connect); static void hci_le_connect_cancel(struct hci_conn *conn) { @@ -78,10 +127,10 @@ void hci_acl_connect(struct hci_conn *conn) struct inquiry_entry *ie; struct hci_cp_create_conn cp; - BT_DBG("hcon %p", conn); + BT_DBG("%p", conn); conn->state = BT_CONNECT; - conn->out = true; + conn->out = 1; conn->link_mode = HCI_LM_MASTER; @@ -103,8 +152,7 @@ void hci_acl_connect(struct hci_conn *conn) } memcpy(conn->dev_class, ie->data.dev_class, 3); - if (ie->data.ssp_mode > 0) - set_bit(HCI_CONN_SSP_ENABLED, &conn->flags); + conn->ssp_mode = ie->data.ssp_mode; } cp.pkt_type = cpu_to_le16(conn->pkt_type); @@ -122,7 +170,7 @@ static void hci_acl_connect_cancel(struct hci_conn *conn) BT_DBG("%p", conn); - if (conn->hdev->hci_ver < BLUETOOTH_VER_1_2) + if (conn->hdev->hci_ver < 2) return; bacpy(&cp.bdaddr, &conn->dst); @@ -131,15 +179,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) @@ -150,7 +205,7 @@ void hci_add_sco(struct hci_conn *conn, __u16 handle) BT_DBG("%p", conn); conn->state = BT_CONNECT; - conn->out = true; + conn->out = 1; conn->attempt++; @@ -168,18 +223,28 @@ void hci_setup_sync(struct hci_conn *conn, __u16 handle) BT_DBG("%p", conn); conn->state = BT_CONNECT; - conn->out = true; + conn->out = 1; conn->attempt++; cp.handle = cpu_to_le16(handle); - cp.pkt_type = cpu_to_le16(conn->pkt_type); cp.tx_bandwidth = cpu_to_le32(0x00001f40); cp.rx_bandwidth = cpu_to_le32(0x00001f40); - cp.max_latency = cpu_to_le16(0xffff); - cp.voice_setting = cpu_to_le16(hdev->voice_setting); - cp.retrans_effort = 0xff; + if (conn->hdev->is_wbs) { + /* Transparent Data */ + uint16_t voice_setting = hdev->voice_setting | ACF_TRANS; + cp.max_latency = cpu_to_le16(0x000D); + cp.pkt_type = cpu_to_le16(ESCO_WBS); + cp.voice_setting = cpu_to_le16(voice_setting); + /* Retransmission Effort */ + cp.retrans_effort = RE_LINK_QUALITY; + } else { + cp.max_latency = cpu_to_le16(0x000A); + cp.pkt_type = cpu_to_le16(conn->pkt_type); + cp.voice_setting = cpu_to_le16(hdev->voice_setting); + cp.retrans_effort = RE_POWER_CONSUMP; + } hci_send_cmd(hdev, HCI_OP_SETUP_SYNC_CONN, sizeof(cp), &cp); } @@ -204,6 +269,18 @@ void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, } EXPORT_SYMBOL(hci_le_conn_update); +void hci_read_rssi(struct hci_conn *conn) +{ + struct hci_cp_read_rssi cp; + struct hci_dev *hdev = conn->hdev; + + memset(&cp, 0, sizeof(cp)); + cp.handle = cpu_to_le16(conn->handle); + + hci_send_cmd(hdev, HCI_OP_READ_RSSI, sizeof(cp), &cp); +} +EXPORT_SYMBOL(hci_read_rssi); + void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __u8 rand[8], __u8 ltk[16]) { @@ -274,16 +351,15 @@ void hci_sco_setup(struct hci_conn *conn, __u8 status) } } -static void hci_conn_timeout(struct work_struct *work) +static void hci_conn_timeout(unsigned long arg) { - struct hci_conn *conn = container_of(work, struct hci_conn, - disc_work.work); + struct hci_conn *conn = (void *) arg; + struct hci_dev *hdev = conn->hdev; __u8 reason; - BT_DBG("conn %p state %s", conn, state_to_string(conn->state)); + BT_DBG("conn %p state %d", conn, conn->state); - if (atomic_read(&conn->refcnt)) - return; + hci_dev_lock(hdev); switch (conn->state) { case BT_CONNECT: @@ -297,67 +373,57 @@ static void hci_conn_timeout(struct work_struct *work) break; case BT_CONFIG: case BT_CONNECTED: - reason = hci_proto_disconn_ind(conn); - hci_acl_disconn(conn, reason); + if (!atomic_read(&conn->refcnt)) { + reason = hci_proto_disconn_ind(conn); + hci_acl_disconn(conn, reason); + } break; default: - conn->state = BT_CLOSED; + if (!atomic_read(&conn->refcnt)) + conn->state = BT_CLOSED; break; } + + hci_dev_unlock(hdev); } -/* Enter sniff mode */ -static void hci_conn_enter_sniff_mode(struct hci_conn *conn) +static void hci_conn_idle(unsigned long arg) { - struct hci_dev *hdev = conn->hdev; + struct hci_conn *conn = (void *) arg; BT_DBG("conn %p mode %d", conn, conn->mode); - if (test_bit(HCI_RAW, &hdev->flags)) - return; - - if (!lmp_sniff_capable(hdev) || !lmp_sniff_capable(conn)) - return; - - if (conn->mode != HCI_CM_ACTIVE || !(conn->link_policy & HCI_LP_SNIFF)) - return; - - if (lmp_sniffsubr_capable(hdev) && lmp_sniffsubr_capable(conn)) { - struct hci_cp_sniff_subrate cp; - cp.handle = cpu_to_le16(conn->handle); - cp.max_latency = cpu_to_le16(0); - cp.min_remote_timeout = cpu_to_le16(0); - cp.min_local_timeout = cpu_to_le16(0); - hci_send_cmd(hdev, HCI_OP_SNIFF_SUBRATE, sizeof(cp), &cp); - } - - if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->flags)) { - struct hci_cp_sniff_mode cp; - cp.handle = cpu_to_le16(conn->handle); - cp.max_interval = cpu_to_le16(hdev->sniff_max_interval); - cp.min_interval = cpu_to_le16(hdev->sniff_min_interval); - cp.attempt = cpu_to_le16(4); - cp.timeout = cpu_to_le16(1); - hci_send_cmd(hdev, HCI_OP_SNIFF_MODE, sizeof(cp), &cp); - } + hci_conn_enter_sniff_mode(conn); } -static void hci_conn_idle(unsigned long arg) +static void hci_conn_rssi_update(struct work_struct *work) { - struct hci_conn *conn = (void *) arg; + struct delayed_work *delayed = + container_of(work, struct delayed_work, work); + struct hci_conn *conn = + container_of(delayed, struct hci_conn, rssi_update_work); BT_DBG("conn %p mode %d", conn, conn->mode); - hci_conn_enter_sniff_mode(conn); + hci_read_rssi(conn); } -static void hci_conn_auto_accept(unsigned long arg) +static void encryption_disabled_timeout(unsigned long userdata) { - struct hci_conn *conn = (void *) arg; - struct hci_dev *hdev = conn->hdev; + struct hci_conn *conn = (struct hci_conn *)userdata; + BT_INFO("conn %p Grace Prd Exp ", conn); + + hci_encrypt_cfm(conn, 0, 0); + + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { + struct hci_cp_set_conn_encrypt cp; + BT_INFO("HCI_CONN_ENCRYPT_PEND is set"); + cp.handle = cpu_to_le16(conn->handle); + cp.encrypt = 1; + hci_send_cmd(conn->hdev, HCI_OP_SET_CONN_ENCRYPT, + sizeof(cp), &cp); + } - hci_send_cmd(hdev, HCI_OP_USER_CONFIRM_REPLY, sizeof(conn->dst), - &conn->dst); } struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, @@ -367,7 +433,7 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, BT_DBG("%s dst %s", hdev->name, batostr(dst)); - conn = kzalloc(sizeof(struct hci_conn), GFP_KERNEL); + conn = kzalloc(sizeof(struct hci_conn), GFP_ATOMIC); if (!conn) return NULL; @@ -379,14 +445,15 @@ 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; - set_bit(HCI_CONN_POWER_SAVE, &conn->flags); + conn->power_save = 1; conn->disc_timeout = HCI_DISCONN_TIMEOUT; + wake_lock_init(&conn->idle_lock, WAKE_LOCK_SUSPEND, "bt_idle"); switch (type) { case ACL_LINK: conn->pkt_type = hdev->pkt_type & ACL_PTYPE_MASK; + conn->link_policy = hdev->link_policy; break; case SCO_LINK: if (!pkt_type) @@ -410,17 +477,18 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, skb_queue_head_init(&conn->data_q); - INIT_LIST_HEAD(&conn->chan_list); - - INIT_DELAYED_WORK(&conn->disc_work, hci_conn_timeout); + 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); + INIT_DELAYED_WORK(&conn->rssi_update_work, hci_conn_rssi_update); + setup_timer(&conn->encrypt_pause_timer, encryption_disabled_timeout, + (unsigned long)conn); atomic_set(&conn->refcnt, 0); hci_dev_hold(hdev); + tasklet_disable(&hdev->tx_task); + hci_conn_hash_add(hdev, conn); if (hdev->notify) hdev->notify(hdev, HCI_NOTIFY_CONN_ADD); @@ -429,6 +497,20 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, hci_conn_init_sysfs(conn); + tasklet_enable(&hdev->tx_task); + + 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; } @@ -438,11 +520,13 @@ int hci_conn_del(struct hci_conn *conn) BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle); + /* Make sure no timers are running */ del_timer(&conn->idle_timer); - - cancel_delayed_work_sync(&conn->disc_work); - - del_timer(&conn->auto_accept_timer); + wake_lock_destroy(&conn->idle_lock); + del_timer(&conn->disc_timer); + del_timer(&conn->smp_timer); + __cancel_delayed_work(&conn->rssi_update_work); + del_timer(&conn->encrypt_pause_timer); if (conn->type == ACL_LINK) { struct hci_conn *sco = conn->link; @@ -464,35 +548,102 @@ int hci_conn_del(struct hci_conn *conn) } } - - hci_chan_list_flush(conn); + tasklet_disable(&hdev->tx_task); hci_conn_hash_del(hdev, conn); if (hdev->notify) hdev->notify(hdev, HCI_NOTIFY_CONN_DEL); + tasklet_schedule(&hdev->tx_task); + + tasklet_enable(&hdev->tx_task); + skb_queue_purge(&conn->data_q); hci_conn_put_device(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; +} +EXPORT_SYMBOL(hci_chan_add); + +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; } +int hci_chan_put(struct hci_chan *chan) +{ + struct hci_cp_disconn_logical_link cp; + struct hci_conn *hcon; + u16 ll_handle; + + BT_DBG("chan %p refcnt %d", chan, atomic_read(&chan->refcnt)); + if (!atomic_dec_and_test(&chan->refcnt)) + return 0; + + hcon = chan->conn; + ll_handle = chan->ll_handle; + + hci_chan_del(chan); + + BT_DBG("chan->conn->state %d", hcon->state); + if (hcon->state == BT_CONNECTED) { + cp.log_handle = cpu_to_le16(ll_handle); + hci_send_cmd(hcon->hdev, HCI_OP_DISCONN_LOGICAL_LINK, + sizeof(cp), &cp); + } + + return 1; +} +EXPORT_SYMBOL(hci_chan_put); + struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) { int use_src = bacmp(src, BDADDR_ANY); - struct hci_dev *hdev = NULL, *d; + struct hci_dev *hdev = NULL; + struct list_head *p; BT_DBG("%s -> %s", batostr(src), batostr(dst)); - read_lock(&hci_dev_list_lock); + read_lock_bh(&hci_dev_list_lock); - list_for_each_entry(d, &hci_dev_list, list) { + 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; @@ -515,52 +666,83 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) if (hdev) hdev = hci_dev_hold(hdev); - read_unlock(&hci_dev_list_lock); + read_unlock_bh(&hci_dev_list_lock); return hdev; } EXPORT_SYMBOL(hci_get_route); -/* Create SCO, ACL or LE connection. - * Device _must_ be locked */ -struct hci_conn *hci_connect(struct hci_dev *hdev, int type, - __u16 pkt_type, bdaddr_t *dst, - __u8 sec_level, __u8 auth_type) +struct hci_dev *hci_dev_get_type(u8 amp_type) { - struct hci_conn *acl; - struct hci_conn *sco; - struct hci_conn *le; + struct hci_dev *hdev = NULL; + struct hci_dev *d; - BT_DBG("%s dst %s", hdev->name, batostr(dst)); + BT_DBG("amp_type %d", amp_type); - if (type == LE_LINK) { - struct adv_entry *entry; + read_lock_bh(&hci_dev_list_lock); - le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst); - if (le) - return ERR_PTR(-EBUSY); + list_for_each_entry(d, &hci_dev_list, list) { + if ((d->amp_type == amp_type) && test_bit(HCI_UP, &d->flags)) { + hdev = d; + break; + } + } - entry = hci_find_adv_entry(hdev, dst); - if (!entry) - return ERR_PTR(-EHOSTUNREACH); + if (hdev) + hdev = hci_dev_hold(hdev); - le = hci_conn_add(hdev, LE_LINK, 0, dst); - if (!le) - return ERR_PTR(-ENOMEM); + read_unlock_bh(&hci_dev_list_lock); + return hdev; +} +EXPORT_SYMBOL(hci_dev_get_type); - le->dst_type = entry->bdaddr_type; +struct hci_dev *hci_dev_get_amp(bdaddr_t *dst) +{ + struct hci_dev *d; + struct hci_dev *hdev = NULL; - hci_le_connect(le); + BT_DBG("%s dst %s", hdev->name, batostr(dst)); - hci_conn_hold(le); + read_lock_bh(&hci_dev_list_lock); - return le; + 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, + __u16 pkt_type, bdaddr_t *dst, + __u8 sec_level, __u8 auth_type) +{ + struct hci_conn *acl; + struct hci_conn *sco; + + BT_DBG("%s dst %s", hdev->name, batostr(dst)); + + if (type == LE_LINK) + return hci_le_connect(hdev, pkt_type, dst, sec_level, + auth_type, NULL); + acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); if (!acl) { acl = hci_conn_add(hdev, ACL_LINK, 0, dst); if (!acl) - return ERR_PTR(-ENOMEM); + return NULL; } hci_conn_hold(acl); @@ -580,7 +762,7 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, sco = hci_conn_add(hdev, type, pkt_type, dst); if (!sco) { hci_conn_put(acl); - return ERR_PTR(-ENOMEM); + return NULL; } } @@ -591,12 +773,12 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, if (acl->state == BT_CONNECTED && (sco->state == BT_OPEN || sco->state == BT_CLOSED)) { - set_bit(HCI_CONN_POWER_SAVE, &acl->flags); - hci_conn_enter_active_mode(acl, BT_POWER_FORCE_ACTIVE_ON); + acl->power_save = 1; + hci_conn_enter_active_mode(acl, 1); - if (test_bit(HCI_CONN_MODE_CHANGE_PEND, &acl->flags)) { + if (test_bit(HCI_CONN_MODE_CHANGE_PEND, &acl->pend)) { /* defer SCO setup until mode change completed */ - set_bit(HCI_CONN_SCO_SETUP_PEND, &acl->flags); + set_bit(HCI_CONN_SCO_SETUP_PEND, &acl->pend); return sco; } @@ -607,12 +789,43 @@ 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, 0); +} +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) { BT_DBG("conn %p", conn); - if (hci_conn_ssp_enabled(conn) && !(conn->link_mode & HCI_LM_ENCRYPT)) + if (conn->ssp_mode > 0 && conn->hdev->ssp_mode > 0 && + !(conn->link_mode & HCI_LM_ENCRYPT)) return 0; return 1; @@ -634,115 +847,71 @@ static int hci_conn_auth(struct hci_conn *conn, __u8 sec_level, __u8 auth_type) /* Make sure we preserve an existing MITM requirement*/ auth_type |= (conn->auth_type & 0x01); - conn->auth_type = auth_type; + conn->auth_initiator = 1; - if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->flags)) { + 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->flags); + 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->flags); } 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->flags)) { - 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); + BT_DBG("conn %p %d %d", conn, sec_level, auth_type); - /* 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 && !hci_conn_ssp_enabled(conn)) + 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->flags)) - return 0; + if (conn->type == LE_LINK) { + if (conn->pending_sec_level > sec_level) + sec_level = conn->pending_sec_level; - if (!hci_conn_auth(conn, sec_level, auth_type)) + if (sec_level > conn->sec_level) + conn->pending_sec_level = sec_level; + hci_proto_connect_cfm(conn, 0); + return 0; + } else if (conn->link_mode & HCI_LM_ENCRYPT) { + return hci_conn_auth(conn, sec_level, auth_type); + } else if (test_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; + if (timer_pending(&conn->encrypt_pause_timer)) { + BT_INFO("encrypt_pause_timer is pending"); + return 0; + } + 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) { BT_DBG("conn %p", conn); - if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->flags)) { + if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { struct hci_cp_change_conn_link_key cp; cp.handle = cpu_to_le16(conn->handle); hci_send_cmd(conn->hdev, HCI_OP_CHANGE_CONN_LINK_KEY, @@ -761,7 +930,7 @@ int hci_conn_switch_role(struct hci_conn *conn, __u8 role) if (!role && conn->link_mode & HCI_LM_MASTER) return 1; - if (!test_and_set_bit(HCI_CONN_RSWITCH_PEND, &conn->flags)) { + if (!test_and_set_bit(HCI_CONN_RSWITCH_PEND, &conn->pend)) { struct hci_cp_switch_role cp; bacpy(&cp.bdaddr, &conn->dst); cp.role = role; @@ -785,33 +954,174 @@ void hci_conn_enter_active_mode(struct hci_conn *conn, __u8 force_active) if (conn->mode != HCI_CM_SNIFF) goto timer; - if (!test_bit(HCI_CONN_POWER_SAVE, &conn->flags) && !force_active) + if (!conn->power_save && !force_active) goto timer; - if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->flags)) { + if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) { struct hci_cp_exit_sniff_mode cp; cp.handle = cpu_to_le16(conn->handle); hci_send_cmd(hdev, HCI_OP_EXIT_SNIFF_MODE, sizeof(cp), &cp); } timer: - if (hdev->idle_timeout > 0) + if (hdev->idle_timeout > 0) { mod_timer(&conn->idle_timer, jiffies + msecs_to_jiffies(hdev->idle_timeout)); + wake_lock(&conn->idle_lock); + } +} + +static inline void hci_conn_stop_rssi_timer(struct hci_conn *conn) +{ + BT_DBG("conn %p", conn); + cancel_delayed_work(&conn->rssi_update_work); +} + +static inline void hci_conn_start_rssi_timer(struct hci_conn *conn, + u16 interval) +{ + struct hci_dev *hdev = conn->hdev; + BT_DBG("conn %p, pending %d", conn, + delayed_work_pending(&conn->rssi_update_work)); + if (!delayed_work_pending(&conn->rssi_update_work)) { + queue_delayed_work(hdev->workqueue, &conn->rssi_update_work, + msecs_to_jiffies(interval)); + } +} + +void hci_conn_set_rssi_reporter(struct hci_conn *conn, + s8 rssi_threshold, u16 interval, u8 updateOnThreshExceed) +{ + if (conn) { + conn->rssi_threshold = rssi_threshold; + conn->rssi_update_interval = interval; + conn->rssi_update_thresh_exceed = updateOnThreshExceed; + hci_conn_start_rssi_timer(conn, interval); + } +} + +void hci_conn_unset_rssi_reporter(struct hci_conn *conn) +{ + if (conn) { + BT_DBG("Deleting the rssi_update_timer"); + hci_conn_stop_rssi_timer(conn); + } +} + +/* Enter sniff mode */ +void hci_conn_enter_sniff_mode(struct hci_conn *conn) +{ + struct hci_dev *hdev = conn->hdev; + + BT_DBG("conn %p mode %d", conn, conn->mode); + + if (test_bit(HCI_RAW, &hdev->flags)) + return; + + if (!lmp_sniff_capable(hdev) || !lmp_sniff_capable(conn)) + return; + + if (conn->mode != HCI_CM_ACTIVE || + !(conn->link_policy & HCI_LP_SNIFF) || + (hci_find_link_key(hdev, &conn->dst) == NULL)) + return; + + if (lmp_sniffsubr_capable(hdev) && lmp_sniffsubr_capable(conn)) { + struct hci_cp_sniff_subrate cp; + cp.handle = cpu_to_le16(conn->handle); + cp.max_latency = cpu_to_le16(0); + cp.min_remote_timeout = cpu_to_le16(0); + cp.min_local_timeout = cpu_to_le16(0); + hci_send_cmd(hdev, HCI_OP_SNIFF_SUBRATE, sizeof(cp), &cp); + } + + if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) { + struct hci_cp_sniff_mode cp; + cp.handle = cpu_to_le16(conn->handle); + cp.max_interval = cpu_to_le16(hdev->sniff_max_interval); + cp.min_interval = cpu_to_le16(hdev->sniff_min_interval); + cp.attempt = cpu_to_le16(4); + cp.timeout = cpu_to_le16(1); + hci_send_cmd(hdev, HCI_OP_SNIFF_MODE, sizeof(cp), &cp); + } +} + +struct hci_chan *hci_chan_create(struct hci_chan *chan, + struct hci_ext_fs *tx_fs, struct hci_ext_fs *rx_fs) +{ + struct hci_cp_create_logical_link cp; + + chan->state = BT_CONNECT; + 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); + if (chan->conn->out) + hci_send_cmd(chan->conn->hdev, HCI_OP_CREATE_LOGICAL_LINK, + sizeof(cp), &cp); + else + hci_send_cmd(chan->conn->hdev, HCI_OP_ACCEPT_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) +void hci_conn_hash_flush(struct hci_dev *hdev, u8 is_process) { struct hci_conn_hash *h = &hdev->conn_hash; - struct hci_conn *c, *n; + struct list_head *p; BT_DBG("hdev %s", hdev->name); - list_for_each_entry_safe(c, n, &h->list, list) { + p = h->list.next; + while (p != &h->list) { + struct hci_conn *c; + + c = list_entry(p, struct hci_conn, list); + p = p->next; + c->state = BT_CLOSED; - hci_proto_disconn_cfm(c, HCI_ERROR_LOCAL_HOST_TERM); + hci_proto_disconn_cfm(c, 0x16, is_process); hci_conn_del(c); } } @@ -847,10 +1157,10 @@ EXPORT_SYMBOL(hci_conn_put_device); int hci_get_conn_list(void __user *arg) { - register struct hci_conn *c; struct hci_conn_list_req req, *cl; struct hci_conn_info *ci; struct hci_dev *hdev; + struct list_head *p; int n = 0, size, err; if (copy_from_user(&req, arg, sizeof(req))) @@ -873,8 +1183,11 @@ int hci_get_conn_list(void __user *arg) ci = cl->conn_info; - hci_dev_lock(hdev); - list_for_each_entry(c, &hdev->conn_hash.list, list) { + hci_dev_lock_bh(hdev); + list_for_each(p, &hdev->conn_hash.list) { + register struct hci_conn *c; + c = list_entry(p, struct hci_conn, list); + bacpy(&(ci + n)->bdaddr, &c->dst); (ci + n)->handle = c->handle; (ci + n)->type = c->type; @@ -893,7 +1206,7 @@ int hci_get_conn_list(void __user *arg) if (++n >= req.conn_num) break; } - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); cl->dev_id = hdev->id; cl->conn_num = n; @@ -917,7 +1230,7 @@ int hci_get_conn_info(struct hci_dev *hdev, void __user *arg) if (copy_from_user(&req, arg, sizeof(req))) return -EFAULT; - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); conn = hci_conn_hash_lookup_ba(hdev, req.type, &req.bdaddr); if (conn) { bacpy(&ci.bdaddr, &conn->dst); @@ -935,8 +1248,10 @@ 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(hdev); + hci_dev_unlock_bh(hdev); if (!conn) return -ENOENT; @@ -952,11 +1267,11 @@ int hci_get_auth_info(struct hci_dev *hdev, void __user *arg) if (copy_from_user(&req, arg, sizeof(req))) return -EFAULT; - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &req.bdaddr); if (conn) req.type = conn->auth_type; - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); if (!conn) return -ENOENT; @@ -964,48 +1279,22 @@ int hci_get_auth_info(struct hci_dev *hdev, void __user *arg) return copy_to_user(arg, &req, sizeof(req)) ? -EFAULT : 0; } -struct hci_chan *hci_chan_create(struct hci_conn *conn) +int hci_set_auth_info(struct hci_dev *hdev, void __user *arg) { - struct hci_dev *hdev = conn->hdev; - struct hci_chan *chan; - - BT_DBG("%s conn %p", hdev->name, conn); - - chan = kzalloc(sizeof(struct hci_chan), GFP_KERNEL); - if (!chan) - return NULL; - - chan->conn = conn; - skb_queue_head_init(&chan->data_q); - - list_add_rcu(&chan->list, &conn->chan_list); - - return chan; -} - -int hci_chan_del(struct hci_chan *chan) -{ - struct hci_conn *conn = chan->conn; - struct hci_dev *hdev = conn->hdev; - - BT_DBG("%s conn %p chan %p", hdev->name, conn, chan); - - list_del_rcu(&chan->list); - - synchronize_rcu(); - - skb_queue_purge(&chan->data_q); - kfree(chan); + struct hci_auth_info_req req; + struct hci_conn *conn; - return 0; -} + if (copy_from_user(&req, arg, sizeof(req))) + return -EFAULT; -void hci_chan_list_flush(struct hci_conn *conn) -{ - struct hci_chan *chan, *n; + 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); - BT_DBG("conn %p", conn); + if (!conn) + return -ENOENT; - list_for_each_entry_safe(chan, n, &conn->chan_list, list) - hci_chan_del(chan); + 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 d6dc44cd15b0729a90bc8963ae90182353ed7e10..da8b2dc73144f5c80a5895e99d80767d6d023885 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1,7 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - Copyright (C) 2011 ProFUSION Embedded Systems + Copyright (c) 2000-2001, 2010-2012 Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -40,11 +39,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -53,9 +54,13 @@ #define AUTO_OFF_TIMEOUT 2000 -static void hci_rx_work(struct work_struct *work); -static void hci_cmd_work(struct work_struct *work); -static void hci_tx_work(struct work_struct *work); +static void hci_cmd_task(unsigned long arg); +static void hci_rx_task(unsigned long arg); +static void hci_tx_task(unsigned long arg); + +static DEFINE_RWLOCK(hci_task_lock); + +static bool enable_smp = 1; /* HCI device list */ LIST_HEAD(hci_dev_list); @@ -65,11 +70,32 @@ 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]; + +/* HCI notifiers list */ +static ATOMIC_NOTIFIER_HEAD(hci_notifier); + /* ---- HCI notifications ---- */ +int hci_register_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&hci_notifier, nb); +} + +int hci_unregister_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&hci_notifier, nb); +} + static void hci_notify(struct hci_dev *hdev, int event) { - hci_sock_dev_event(hdev, event); + atomic_notifier_call_chain(&hci_notifier, event, hdev); } /* ---- HCI requests ---- */ @@ -81,28 +107,8 @@ void hci_req_complete(struct hci_dev *hdev, __u16 cmd, int result) /* If this is the init phase check if the completed command matches * the last init command, and if not just return. */ - if (test_bit(HCI_INIT, &hdev->flags) && hdev->init_last_cmd != cmd) { - struct hci_command_hdr *sent = (void *) hdev->sent_cmd->data; - struct sk_buff *skb; - - /* Some CSR based controllers generate a spontaneous - * reset complete event during init and any pending - * command will never be completed. In such a case we - * need to resend whatever was the last sent - * command. - */ - - if (cmd != HCI_OP_RESET || sent->opcode == HCI_OP_RESET) - return; - - skb = skb_clone(hdev->sent_cmd, GFP_ATOMIC); - if (skb) { - skb_queue_head(&hdev->cmd_q, skb); - queue_work(hdev->workqueue, &hdev->cmd_work); - } - + if (test_bit(HCI_INIT, &hdev->flags) && hdev->init_last_cmd != cmd) return; - } if (hdev->req_status == HCI_REQ_PEND) { hdev->req_result = result; @@ -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: @@ -187,104 +193,110 @@ static void hci_reset_req(struct hci_dev *hdev, unsigned long opt) /* Reset device */ set_bit(HCI_RESET, &hdev->flags); + memset(&hdev->features, 0, sizeof(hdev->features)); hci_send_cmd(hdev, HCI_OP_RESET, 0, NULL); } -static void bredr_init(struct hci_dev *hdev) +static void hci_init_req(struct hci_dev *hdev, unsigned long opt) { struct hci_cp_delete_stored_link_key cp; + struct sk_buff *skb; __le16 param; __u8 flt_type; - hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_PACKET_BASED; + BT_DBG("%s %ld", hdev->name, opt); + + /* Driver initialization */ + + /* Special commands */ + while ((skb = skb_dequeue(&hdev->driver_init))) { + bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; + skb->dev = (void *) hdev; + + skb_queue_tail(&hdev->cmd_q, skb); + tasklet_schedule(&hdev->cmd_task); + } + skb_queue_purge(&hdev->driver_init); /* Mandatory initialization */ /* Reset */ if (!test_bit(HCI_QUIRK_NO_RESET, &hdev->quirks)) { - set_bit(HCI_RESET, &hdev->flags); - hci_send_cmd(hdev, HCI_OP_RESET, 0, NULL); + set_bit(HCI_RESET, &hdev->flags); + 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); - /* Read Buffer Size (ACL mtu, max pkt, etc.) */ - hci_send_cmd(hdev, HCI_OP_READ_BUFFER_SIZE, 0, NULL); - - /* Read BD Address */ - hci_send_cmd(hdev, HCI_OP_READ_BD_ADDR, 0, NULL); - - /* Read Class of Device */ - hci_send_cmd(hdev, HCI_OP_READ_CLASS_OF_DEV, 0, NULL); - /* Read Local Name */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_NAME, 0, NULL); - - /* Read Voice Setting */ - hci_send_cmd(hdev, HCI_OP_READ_VOICE_SETTING, 0, NULL); - - /* Optional initialization */ + /* 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; - /* Clear Event Filters */ - flt_type = HCI_FLT_CLEAR_ALL; - hci_send_cmd(hdev, HCI_OP_SET_EVENT_FLT, 1, &flt_type); + /* Read HCI Flow Control Mode */ + hci_send_cmd(hdev, HCI_OP_READ_FLOW_CONTROL_MODE, 0, NULL); - /* Connection accept timeout ~20 secs */ - param = cpu_to_le16(0x7d00); - hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); + /* Read Buffer Size (ACL mtu, max pkt, etc.) */ + hci_send_cmd(hdev, HCI_OP_READ_BUFFER_SIZE, 0, NULL); - bacpy(&cp.bdaddr, BDADDR_ANY); - cp.delete_all = 1; - hci_send_cmd(hdev, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp); -} + /* Read Data Block Size (ACL mtu, max pkt, etc.) */ + hci_send_cmd(hdev, HCI_OP_READ_DATA_BLOCK_SIZE, 0, NULL); -static void amp_init(struct hci_dev *hdev) -{ - hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_BLOCK_BASED; +#if 0 + /* Host buffer size */ + { + struct hci_cp_host_buffer_size cp; + cp.acl_mtu = cpu_to_le16(HCI_MAX_ACL_SIZE); + cp.sco_mtu = HCI_MAX_SCO_SIZE; + cp.acl_max_pkt = cpu_to_le16(0xffff); + cp.sco_max_pkt = cpu_to_le16(0xffff); + hci_send_cmd(hdev, HCI_OP_HOST_BUFFER_SIZE, sizeof(cp), &cp); + } +#endif - /* Reset */ - hci_send_cmd(hdev, HCI_OP_RESET, 0, NULL); + if (hdev->dev_type == HCI_BREDR) { + /* BR-EDR initialization */ - /* Read Local Version */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL); -} + /* Read Local Supported Features */ + hci_send_cmd(hdev, HCI_OP_READ_LOCAL_FEATURES, 0, NULL); -static void hci_init_req(struct hci_dev *hdev, unsigned long opt) -{ - struct sk_buff *skb; + /* Read BD Address */ + hci_send_cmd(hdev, HCI_OP_READ_BD_ADDR, 0, NULL); - BT_DBG("%s %ld", hdev->name, opt); + /* Read Class of Device */ + hci_send_cmd(hdev, HCI_OP_READ_CLASS_OF_DEV, 0, NULL); - /* Driver initialization */ + /* Read Local Name */ + hci_send_cmd(hdev, HCI_OP_READ_LOCAL_NAME, 0, NULL); - /* Special commands */ - while ((skb = skb_dequeue(&hdev->driver_init))) { - bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; - skb->dev = (void *) hdev; + /* Read Voice Setting */ + hci_send_cmd(hdev, HCI_OP_READ_VOICE_SETTING, 0, NULL); - skb_queue_tail(&hdev->cmd_q, skb); - queue_work(hdev->workqueue, &hdev->cmd_work); - } - skb_queue_purge(&hdev->driver_init); + /* Optional initialization */ + /* Clear Event Filters */ + flt_type = HCI_FLT_CLEAR_ALL; + hci_send_cmd(hdev, HCI_OP_SET_EVENT_FLT, 1, &flt_type); - switch (hdev->dev_type) { - case HCI_BREDR: - bredr_init(hdev); - break; + /* Connection accept timeout ~20 secs */ + param = cpu_to_le16(0x7d00); + hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); - case HCI_AMP: - amp_init(hdev); - break; + 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); - default: - BT_ERR("Unknown device type %d", hdev->dev_type); - break; + /* 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) @@ -339,7 +351,8 @@ static void hci_linkpol_req(struct hci_dev *hdev, unsigned long opt) * Device is held on return. */ struct hci_dev *hci_dev_get(int index) { - struct hci_dev *hdev = NULL, *d; + struct hci_dev *hdev = NULL; + struct list_head *p; BT_DBG("%d", index); @@ -347,7 +360,8 @@ struct hci_dev *hci_dev_get(int index) return NULL; read_lock(&hci_dev_list_lock); - list_for_each_entry(d, &hci_dev_list, list) { + list_for_each(p, &hci_dev_list) { + struct hci_dev *d = list_entry(p, struct hci_dev, list); if (d->id == index) { hdev = hci_dev_hold(d); break; @@ -356,211 +370,75 @@ struct hci_dev *hci_dev_get(int index) read_unlock(&hci_dev_list_lock); return hdev; } +EXPORT_SYMBOL(hci_dev_get); /* ---- Inquiry support ---- */ - -bool hci_discovery_active(struct hci_dev *hdev) -{ - struct discovery_state *discov = &hdev->discovery; - - switch (discov->state) { - case DISCOVERY_FINDING: - case DISCOVERY_RESOLVING: - return true; - - default: - return false; - } -} - -void hci_discovery_set_state(struct hci_dev *hdev, int state) -{ - BT_DBG("%s state %u -> %u", hdev->name, hdev->discovery.state, state); - - if (hdev->discovery.state == state) - return; - - switch (state) { - case DISCOVERY_STOPPED: - if (hdev->discovery.state != DISCOVERY_STARTING) - mgmt_discovering(hdev, 0); - hdev->discovery.type = 0; - break; - case DISCOVERY_STARTING: - break; - case DISCOVERY_FINDING: - mgmt_discovering(hdev, 1); - break; - case DISCOVERY_RESOLVING: - break; - case DISCOVERY_STOPPING: - break; - } - - hdev->discovery.state = state; -} - static void inquiry_cache_flush(struct hci_dev *hdev) { - struct discovery_state *cache = &hdev->discovery; - struct inquiry_entry *p, *n; - - list_for_each_entry_safe(p, n, &cache->all, all) { - list_del(&p->all); - kfree(p); - } - - INIT_LIST_HEAD(&cache->unknown); - INIT_LIST_HEAD(&cache->resolve); -} - -struct inquiry_entry *hci_inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr) -{ - struct discovery_state *cache = &hdev->discovery; - struct inquiry_entry *e; + struct inquiry_cache *cache = &hdev->inq_cache; + struct inquiry_entry *next = cache->list, *e; - BT_DBG("cache %p, %s", cache, batostr(bdaddr)); + BT_DBG("cache %p", cache); - list_for_each_entry(e, &cache->all, all) { - if (!bacmp(&e->data.bdaddr, bdaddr)) - return e; + cache->list = NULL; + while ((e = next)) { + next = e->next; + kfree(e); } - - return NULL; } -struct inquiry_entry *hci_inquiry_cache_lookup_unknown(struct hci_dev *hdev, - bdaddr_t *bdaddr) +struct inquiry_entry *hci_inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr) { - struct discovery_state *cache = &hdev->discovery; + struct inquiry_cache *cache = &hdev->inq_cache; struct inquiry_entry *e; BT_DBG("cache %p, %s", cache, batostr(bdaddr)); - list_for_each_entry(e, &cache->unknown, list) { - if (!bacmp(&e->data.bdaddr, bdaddr)) - return e; - } - - return NULL; -} - -struct inquiry_entry *hci_inquiry_cache_lookup_resolve(struct hci_dev *hdev, - bdaddr_t *bdaddr, - int state) -{ - struct discovery_state *cache = &hdev->discovery; - struct inquiry_entry *e; - - BT_DBG("cache %p bdaddr %s state %d", cache, batostr(bdaddr), state); - - list_for_each_entry(e, &cache->resolve, list) { - if (!bacmp(bdaddr, BDADDR_ANY) && e->name_state == state) - return e; + for (e = cache->list; e; e = e->next) if (!bacmp(&e->data.bdaddr, bdaddr)) - return e; - } - - return NULL; -} - -void hci_inquiry_cache_update_resolve(struct hci_dev *hdev, - struct inquiry_entry *ie) -{ - struct discovery_state *cache = &hdev->discovery; - struct list_head *pos = &cache->resolve; - struct inquiry_entry *p; - - list_del(&ie->list); - - list_for_each_entry(p, &cache->resolve, list) { - if (p->name_state != NAME_PENDING && - abs(p->data.rssi) >= abs(ie->data.rssi)) break; - pos = &p->list; - } - - list_add(&ie->list, pos); + return e; } -bool hci_inquiry_cache_update(struct hci_dev *hdev, struct inquiry_data *data, - bool name_known, bool *ssp) +void hci_inquiry_cache_update(struct hci_dev *hdev, struct inquiry_data *data) { - struct discovery_state *cache = &hdev->discovery; + struct inquiry_cache *cache = &hdev->inq_cache; struct inquiry_entry *ie; BT_DBG("cache %p, %s", cache, batostr(&data->bdaddr)); - if (ssp) - *ssp = data->ssp_mode; - ie = hci_inquiry_cache_lookup(hdev, &data->bdaddr); - if (ie) { - if (ie->data.ssp_mode && ssp) - *ssp = true; - - if (ie->name_state == NAME_NEEDED && - data->rssi != ie->data.rssi) { - ie->data.rssi = data->rssi; - hci_inquiry_cache_update_resolve(hdev, ie); - } - - goto update; - } - - /* Entry not in the cache. Add new one. */ - ie = kzalloc(sizeof(struct inquiry_entry), GFP_ATOMIC); - if (!ie) - return false; - - list_add(&ie->all, &cache->all); - - if (name_known) { - ie->name_state = NAME_KNOWN; - } else { - ie->name_state = NAME_NOT_KNOWN; - list_add(&ie->list, &cache->unknown); - } + if (!ie) { + /* Entry not in the cache. Add new one. */ + ie = kzalloc(sizeof(struct inquiry_entry), GFP_ATOMIC); + if (!ie) + return; -update: - if (name_known && ie->name_state != NAME_KNOWN && - ie->name_state != NAME_PENDING) { - ie->name_state = NAME_KNOWN; - list_del(&ie->list); + ie->next = cache->list; + cache->list = ie; } memcpy(&ie->data, data, sizeof(*data)); ie->timestamp = jiffies; cache->timestamp = jiffies; - - if (ie->name_state == NAME_NOT_KNOWN) - return false; - - return true; } static int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf) { - struct discovery_state *cache = &hdev->discovery; + struct inquiry_cache *cache = &hdev->inq_cache; struct inquiry_info *info = (struct inquiry_info *) buf; struct inquiry_entry *e; int copied = 0; - list_for_each_entry(e, &cache->all, all) { + for (e = cache->list; e && copied < num; e = e->next, copied++) { struct inquiry_data *data = &e->data; - - if (copied >= num) - break; - bacpy(&info->bdaddr, &data->bdaddr); info->pscan_rep_mode = data->pscan_rep_mode; info->pscan_period_mode = data->pscan_period_mode; info->pscan_mode = data->pscan_mode; memcpy(info->dev_class, data->dev_class, 3); info->clock_offset = data->clock_offset; - info++; - copied++; } BT_DBG("cache %p, copied %d", cache, copied); @@ -600,14 +478,14 @@ int hci_inquiry(void __user *arg) if (!hdev) return -ENODEV; - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX || inquiry_cache_empty(hdev) || ir.flags & IREQ_CACHE_FLUSH) { inquiry_cache_flush(hdev); do_inquiry = 1; } - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); timeo = ir.length * msecs_to_jiffies(2000); @@ -629,9 +507,9 @@ int hci_inquiry(void __user *arg) goto done; } - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf); - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); BT_DBG("num_rsp %d", ir.num_rsp); @@ -665,11 +543,6 @@ int hci_dev_open(__u16 dev) hci_req_lock(hdev); - if (test_bit(HCI_UNREGISTER, &hdev->dev_flags)) { - ret = -ENODEV; - goto done; - } - if (hdev->rfkill && rfkill_blocked(hdev->rfkill)) { ret = -ERFKILL; goto done; @@ -683,16 +556,24 @@ 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 if - enable_hs is not set */ - if (hdev->dev_type != HCI_BREDR && !enable_hs) - set_bit(HCI_RAW, &hdev->flags); - if (hdev->open(hdev)) { ret = -EIO; goto done; } + if (!skb_queue_empty(&hdev->cmd_q)) { + BT_ERR("command queue is not empty, purging"); + skb_queue_purge(&hdev->cmd_q); + } + if (!skb_queue_empty(&hdev->rx_q)) { + BT_ERR("rx queue is not empty, purging"); + skb_queue_purge(&hdev->rx_q); + } + if (!skb_queue_empty(&hdev->raw_q)) { + BT_ERR("raw queue is not empty, purging"); + skb_queue_purge(&hdev->raw_q); + } + if (!test_bit(HCI_RAW, &hdev->flags)) { atomic_set(&hdev->cmd_cnt, 1); set_bit(HCI_INIT, &hdev->flags); @@ -701,7 +582,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)); @@ -712,16 +593,17 @@ int hci_dev_open(__u16 dev) hci_dev_hold(hdev); set_bit(HCI_UP, &hdev->flags); hci_notify(hdev, HCI_DEV_UP); - if (!test_bit(HCI_SETUP, &hdev->dev_flags)) { - hci_dev_lock(hdev); - mgmt_powered(hdev, 1); - hci_dev_unlock(hdev); + if (!test_bit(HCI_SETUP, &hdev->flags) && + hdev->dev_type == HCI_BREDR) { + hci_dev_lock_bh(hdev); + mgmt_powered(hdev->id, 1); + hci_dev_unlock_bh(hdev); } } else { /* Init failed, cleanup */ - flush_work(&hdev->tx_work); - flush_work(&hdev->cmd_work); - flush_work(&hdev->rx_work); + tasklet_kill(&hdev->rx_task); + tasklet_kill(&hdev->tx_task); + tasklet_kill(&hdev->cmd_task); skb_queue_purge(&hdev->cmd_q); skb_queue_purge(&hdev->rx_q); @@ -744,11 +626,11 @@ int hci_dev_open(__u16 dev) return ret; } -static int hci_dev_do_close(struct hci_dev *hdev) +static int hci_dev_do_close(struct hci_dev *hdev, u8 is_process) { - BT_DBG("%s %p", hdev->name, hdev); + unsigned long keepflags = 0; - cancel_work_sync(&hdev->le_scan); + BT_DBG("%s %p", hdev->name, hdev); hci_req_cancel(hdev, ENODEV); hci_req_lock(hdev); @@ -759,44 +641,38 @@ static int hci_dev_do_close(struct hci_dev *hdev) return 0; } - /* Flush RX and TX works */ - flush_work(&hdev->tx_work); - flush_work(&hdev->rx_work); - - if (hdev->discov_timeout > 0) { - cancel_delayed_work(&hdev->discov_off); - hdev->discov_timeout = 0; - clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags); - } - - if (test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) - cancel_delayed_work(&hdev->service_cache); + /* Kill RX and TX tasks */ + tasklet_kill(&hdev->rx_task); + tasklet_kill(&hdev->tx_task); - cancel_delayed_work_sync(&hdev->le_scan_disable); - - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); inquiry_cache_flush(hdev); - hci_conn_hash_flush(hdev); - hci_dev_unlock(hdev); + hci_conn_hash_flush(hdev, is_process); + hci_dev_unlock_bh(hdev); hci_notify(hdev, HCI_DEV_DOWN); + if (hdev->dev_type == HCI_BREDR) { + hci_dev_lock_bh(hdev); + mgmt_powered(hdev->id, 0); + hci_dev_unlock_bh(hdev); + } + if (hdev->flush) hdev->flush(hdev); /* Reset device */ skb_queue_purge(&hdev->cmd_q); atomic_set(&hdev->cmd_cnt, 1); - if (!test_bit(HCI_RAW, &hdev->flags) && - test_bit(HCI_QUIRK_NO_RESET, &hdev->quirks)) { + if (!test_bit(HCI_RAW, &hdev->flags)) { set_bit(HCI_INIT, &hdev->flags); __hci_request(hdev, hci_reset_req, 0, msecs_to_jiffies(250)); clear_bit(HCI_INIT, &hdev->flags); } - /* flush cmd work */ - flush_work(&hdev->cmd_work); + /* Kill cmd task */ + tasklet_kill(&hdev->cmd_task); /* Drop queues */ skb_queue_purge(&hdev->rx_q); @@ -814,17 +690,15 @@ static int hci_dev_do_close(struct hci_dev *hdev) * and no tasks are scheduled. */ hdev->close(hdev); - if (!test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) { - hci_dev_lock(hdev); - mgmt_powered(hdev, 0); - hci_dev_unlock(hdev); - } - - /* Clear flags */ - hdev->flags = 0; + /* Clear only non-persistent flags */ + if (test_bit(HCI_MGMT, &hdev->flags)) + set_bit(HCI_MGMT, &keepflags); + if (test_bit(HCI_LINK_KEYS, &hdev->flags)) + set_bit(HCI_LINK_KEYS, &keepflags); + if (test_bit(HCI_DEBUG_KEYS, &hdev->flags)) + set_bit(HCI_DEBUG_KEYS, &keepflags); - memset(hdev->eir, 0, sizeof(hdev->eir)); - memset(hdev->dev_class, 0, sizeof(hdev->dev_class)); + hdev->flags = keepflags; hci_req_unlock(hdev); @@ -840,12 +714,7 @@ int hci_dev_close(__u16 dev) hdev = hci_dev_get(dev); if (!hdev) return -ENODEV; - - if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) - cancel_delayed_work(&hdev->power_off); - - err = hci_dev_do_close(hdev); - + err = hci_dev_do_close(hdev, 1); hci_dev_put(hdev); return err; } @@ -860,6 +729,7 @@ int hci_dev_reset(__u16 dev) return -ENODEV; hci_req_lock(hdev); + tasklet_disable(&hdev->tx_task); if (!test_bit(HCI_UP, &hdev->flags)) goto done; @@ -868,10 +738,10 @@ int hci_dev_reset(__u16 dev) skb_queue_purge(&hdev->rx_q); skb_queue_purge(&hdev->cmd_q); - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); inquiry_cache_flush(hdev); - hci_conn_hash_flush(hdev); - hci_dev_unlock(hdev); + hci_conn_hash_flush(hdev, 0); + hci_dev_unlock_bh(hdev); if (hdev->flush) hdev->flush(hdev); @@ -884,6 +754,7 @@ int hci_dev_reset(__u16 dev) msecs_to_jiffies(HCI_INIT_TIMEOUT)); done: + tasklet_enable(&hdev->tx_task); hci_req_unlock(hdev); hci_dev_put(hdev); return ret; @@ -982,9 +853,9 @@ int hci_dev_cmd(unsigned int cmd, void __user *arg) int hci_get_dev_list(void __user *arg) { - struct hci_dev *hdev; struct hci_dev_list_req *dl; struct hci_dev_req *dr; + struct list_head *p; int n = 0, size, err; __u16 dev_num; @@ -1002,13 +873,16 @@ int hci_get_dev_list(void __user *arg) dr = dl->dev_req; - read_lock(&hci_dev_list_lock); - list_for_each_entry(hdev, &hci_dev_list, list) { - if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) - cancel_delayed_work(&hdev->power_off); + read_lock_bh(&hci_dev_list_lock); + list_for_each(p, &hci_dev_list) { + struct hci_dev *hdev; + + hdev = list_entry(p, struct hci_dev, list); - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) - set_bit(HCI_PAIRABLE, &hdev->dev_flags); + hci_del_off_timer(hdev); + + if (!test_bit(HCI_MGMT, &hdev->flags)) + set_bit(HCI_PAIRABLE, &hdev->flags); (dr + n)->dev_id = hdev->id; (dr + n)->dev_opt = hdev->flags; @@ -1016,7 +890,7 @@ int hci_get_dev_list(void __user *arg) if (++n >= dev_num) break; } - read_unlock(&hci_dev_list_lock); + read_unlock_bh(&hci_dev_list_lock); dl->dev_num = n; size = sizeof(*dl) + n * sizeof(*dr); @@ -1040,11 +914,10 @@ int hci_get_dev_info(void __user *arg) if (!hdev) return -ENODEV; - if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) - cancel_delayed_work_sync(&hdev->power_off); + hci_del_off_timer(hdev); - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) - set_bit(HCI_PAIRABLE, &hdev->dev_flags); + if (!test_bit(HCI_MGMT, &hdev->flags)) + set_bit(HCI_PAIRABLE, &hdev->flags); strcpy(di.name, hdev->name); di.bdaddr = hdev->bdaddr; @@ -1080,7 +953,7 @@ static int hci_rfkill_set_block(void *data, bool blocked) if (!blocked) return 0; - hci_dev_do_close(hdev); + hci_dev_do_close(hdev, 0); return 0; } @@ -1098,7 +971,6 @@ struct hci_dev *hci_alloc_dev(void) if (!hdev) return NULL; - hci_init_sysfs(hdev); skb_queue_head_init(&hdev->driver_init); return hdev; @@ -1118,46 +990,50 @@ EXPORT_SYMBOL(hci_free_dev); static void hci_power_on(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, power_on); + int err; BT_DBG("%s", hdev->name); - if (hci_dev_open(hdev->id) < 0) + err = hci_dev_open(hdev->id); + if (err && err != -EALREADY) return; - if (test_bit(HCI_AUTO_OFF, &hdev->dev_flags)) - schedule_delayed_work(&hdev->power_off, - msecs_to_jiffies(AUTO_OFF_TIMEOUT)); + if (test_bit(HCI_AUTO_OFF, &hdev->flags) && + hdev->dev_type == HCI_BREDR) + mod_timer(&hdev->off_timer, + jiffies + msecs_to_jiffies(AUTO_OFF_TIMEOUT)); - if (test_and_clear_bit(HCI_SETUP, &hdev->dev_flags)) - mgmt_index_added(hdev); + if (test_and_clear_bit(HCI_SETUP, &hdev->flags) && + hdev->dev_type == HCI_BREDR) + mgmt_index_added(hdev->id); } static void hci_power_off(struct work_struct *work) { - struct hci_dev *hdev = container_of(work, struct hci_dev, - power_off.work); + struct hci_dev *hdev = container_of(work, struct hci_dev, power_off); BT_DBG("%s", hdev->name); - hci_dev_do_close(hdev); + hci_dev_close(hdev->id); } -static void hci_discov_off(struct work_struct *work) +static void hci_auto_off(unsigned long data) { - struct hci_dev *hdev; - u8 scan = SCAN_PAGE; - - hdev = container_of(work, struct hci_dev, discov_off.work); + struct hci_dev *hdev = (struct hci_dev *) data; BT_DBG("%s", hdev->name); - hci_dev_lock(hdev); + clear_bit(HCI_AUTO_OFF, &hdev->flags); - hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan); + queue_work(hdev->workqueue, &hdev->power_off); +} - hdev->discov_timeout = 0; +void hci_del_off_timer(struct hci_dev *hdev) +{ + BT_DBG("%s", hdev->name); - hci_dev_unlock(hdev); + clear_bit(HCI_AUTO_OFF, &hdev->flags); + del_timer(&hdev->off_timer); } int hci_uuids_clear(struct hci_dev *hdev) @@ -1192,108 +1068,80 @@ int hci_link_keys_clear(struct hci_dev *hdev) return 0; } -int hci_smp_ltks_clear(struct hci_dev *hdev) +struct link_key *hci_find_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr) { - struct smp_ltk *k, *tmp; - - list_for_each_entry_safe(k, tmp, &hdev->long_term_keys, list) { - list_del(&k->list); - kfree(k); - } + struct list_head *p; - return 0; -} + list_for_each(p, &hdev->link_keys) { + struct link_key *k; -struct link_key *hci_find_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr) -{ - struct link_key *k; + k = list_entry(p, struct link_key, list); - list_for_each_entry(k, &hdev->link_keys, list) if (bacmp(bdaddr, &k->bdaddr) == 0) return k; + } return NULL; } -static bool hci_persistent_key(struct hci_dev *hdev, struct hci_conn *conn, - u8 key_type, u8 old_key_type) +struct link_key *hci_find_ltk(struct hci_dev *hdev, __le16 ediv, u8 rand[8]) { - /* Legacy key */ - if (key_type < 0x03) - return true; - - /* Debug keys are insecure so don't store them persistently */ - if (key_type == HCI_LK_DEBUG_COMBINATION) - return false; - - /* Changed combination key and there's no previous one */ - if (key_type == HCI_LK_CHANGED_COMBINATION && old_key_type == 0xff) - return false; + struct list_head *p; - /* Security mode 3 case */ - if (!conn) - return true; + list_for_each(p, &hdev->link_keys) { + struct link_key *k; + struct key_master_id *id; - /* Neither local nor remote side had no-bonding as requirement */ - if (conn->auth_type > 0x01 && conn->remote_auth > 0x01) - return true; - - /* Local side had dedicated bonding as requirement */ - if (conn->auth_type == 0x02 || conn->auth_type == 0x03) - return true; - - /* Remote side had dedicated bonding as requirement */ - if (conn->remote_auth == 0x02 || conn->remote_auth == 0x03) - return true; - - /* If none of the above criteria match, then don't store the key - * persistently */ - return false; -} + k = list_entry(p, struct link_key, list); -struct smp_ltk *hci_find_ltk(struct hci_dev *hdev, __le16 ediv, u8 rand[8]) -{ - struct smp_ltk *k; + if (k->key_type != KEY_TYPE_LTK) + continue; - list_for_each_entry(k, &hdev->long_term_keys, list) { - if (k->ediv != ediv || - memcmp(rand, k->rand, sizeof(k->rand))) + if (k->dlen != sizeof(*id)) continue; - return k; + id = (void *) &k->data; + if (id->ediv == ediv && + (memcmp(rand, id->rand, sizeof(id->rand)) == 0)) + return k; } return NULL; } EXPORT_SYMBOL(hci_find_ltk); -struct smp_ltk *hci_find_ltk_by_addr(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 addr_type) +struct link_key *hci_find_link_key_type(struct hci_dev *hdev, + bdaddr_t *bdaddr, u8 type) { - struct smp_ltk *k; + struct list_head *p; + + list_for_each(p, &hdev->link_keys) { + struct link_key *k; - list_for_each_entry(k, &hdev->long_term_keys, list) - if (addr_type == k->bdaddr_type && - bacmp(bdaddr, &k->bdaddr) == 0) + k = list_entry(p, struct link_key, list); + + if ((k->key_type == type) && (bacmp(bdaddr, &k->bdaddr) == 0)) return k; + } return NULL; } -EXPORT_SYMBOL(hci_find_ltk_by_addr); +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; + struct hci_conn *conn; u8 old_key_type; - bool persistent; + u8 bonded = 0; old_key = hci_find_link_key(hdev, bdaddr); if (old_key) { - old_key_type = old_key->type; + old_key_type = old_key->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; @@ -1302,72 +1150,76 @@ 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->auth = 0x01; + key->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; + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, bdaddr); + /* Store the link key persistently if one of the following is true: + * 1. the remote side is using dedicated bonding since in that case + * also the local requirements are set to dedicated bonding + * 2. the local side had dedicated bonding as a requirement + * 3. this is a legacy link key + * 4. this is a changed combination key and there was a previously + * stored one + * If none of the above match only keep the link key around for + * this connection and set the temporary flag for the device. + */ - persistent = hci_persistent_key(hdev, conn, type, old_key_type); + if (conn) { + if ((conn->remote_auth > 0x01) || + (conn->auth_initiator && conn->auth_type > 0x01) || + (key->key_type < 0x03) || + (key->key_type == 0x06 && old_key_type != 0xff)) + bonded = 1; + } - mgmt_new_link_key(hdev, key, persistent); + if (new_key) + mgmt_new_key(hdev->id, key, bonded); - if (conn) - conn->flush_key = !persistent; + if (type == 0x06) + key->key_type = old_key_type; return 0; } -int hci_add_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 addr_type, u8 type, - int new_key, u8 authenticated, u8 tk[16], u8 enc_size, u16 - ediv, u8 rand[8]) +int hci_add_ltk(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr, + u8 addr_type, u8 key_size, u8 auth, + __le16 ediv, u8 rand[8], u8 ltk[16]) { - struct smp_ltk *key, *old_key; + struct link_key *key, *old_key; + struct key_master_id *id; - if (!(type & HCI_SMP_STK) && !(type & HCI_SMP_LTK)) - return 0; + BT_DBG("%s Auth: %2.2X addr %s type: %d", hdev->name, auth, + batostr(bdaddr), addr_type); - old_key = hci_find_ltk_by_addr(hdev, bdaddr, addr_type); - if (old_key) + old_key = hci_find_link_key_type(hdev, bdaddr, KEY_TYPE_LTK); + if (old_key) { key = old_key; - else { - key = kzalloc(sizeof(*key), GFP_ATOMIC); + } else { + key = kzalloc(sizeof(*key) + sizeof(*id), GFP_ATOMIC); if (!key) return -ENOMEM; - list_add(&key->list, &hdev->long_term_keys); + list_add(&key->list, &hdev->link_keys); } + key->dlen = sizeof(*id); + bacpy(&key->bdaddr, bdaddr); - key->bdaddr_type = addr_type; - memcpy(key->val, tk, sizeof(key->val)); - key->authenticated = authenticated; - key->ediv = ediv; - key->enc_size = enc_size; - key->type = type; - memcpy(key->rand, rand, sizeof(key->rand)); - - if (!new_key) - return 0; + key->addr_type = addr_type; + memcpy(key->val, ltk, sizeof(key->val)); + key->key_type = KEY_TYPE_LTK; + key->pin_len = key_size; + key->auth = auth; - if (type & HCI_SMP_LTK) - mgmt_new_ltk(hdev, key, 1); + id = (void *) &key->data; + id->ediv = ediv; + memcpy(id->rand, rand, sizeof(id->rand)); + + if (new_key) + mgmt_new_key(hdev->id, key, auth & 0x01); return 0; } @@ -1388,23 +1240,6 @@ int hci_remove_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr) return 0; } -int hci_remove_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr) -{ - struct smp_ltk *k, *tmp; - - list_for_each_entry_safe(k, tmp, &hdev->long_term_keys, list) { - if (bacmp(bdaddr, &k->bdaddr)) - continue; - - BT_DBG("%s removing %s", hdev->name, batostr(bdaddr)); - - list_del(&k->list); - kfree(k); - } - - return 0; -} - /* HCI command timer function */ static void hci_cmd_timer(unsigned long arg) { @@ -1412,11 +1247,12 @@ static void hci_cmd_timer(unsigned long arg) BT_ERR("%s command tx timeout", hdev->name); atomic_set(&hdev->cmd_cnt, 1); - queue_work(hdev->workqueue, &hdev->cmd_work); + clear_bit(HCI_RESET, &hdev->flags); + tasklet_schedule(&hdev->cmd_task); } struct oob_data *hci_find_remote_oob_data(struct hci_dev *hdev, - bdaddr_t *bdaddr) + bdaddr_t *bdaddr) { struct oob_data *data; @@ -1455,282 +1291,158 @@ 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) +static void hci_adv_clear(unsigned long arg) { - 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)); + struct hci_dev *hdev = (void *) arg; - return 0; + hci_adv_entries_clear(hdev); } -struct bdaddr_list *hci_blacklist_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr) +int hci_adv_entries_clear(struct hci_dev *hdev) { - struct bdaddr_list *b; + struct list_head *p, *n; - list_for_each_entry(b, &hdev->blacklist, list) - if (bacmp(bdaddr, &b->bdaddr) == 0) - return b; + BT_DBG(""); + write_lock_bh(&hdev->adv_entries_lock); - return NULL; -} - -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; + list_for_each_safe(p, n, &hdev->adv_entries) { + struct adv_entry *entry; - b = list_entry(p, struct bdaddr_list, list); + 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, u8 type) +struct adv_entry *hci_find_adv_entry(struct hci_dev *hdev, bdaddr_t *bdaddr) { - struct bdaddr_list *entry; + struct list_head *p; + struct adv_entry *res = NULL; - if (bacmp(bdaddr, BDADDR_ANY) == 0) - return -EBADF; + BT_DBG(""); + read_lock_bh(&hdev->adv_entries_lock); - if (hci_blacklist_lookup(hdev, bdaddr)) - return -EEXIST; - - entry = kzalloc(sizeof(struct bdaddr_list), GFP_KERNEL); - if (!entry) - return -ENOMEM; + list_for_each(p, &hdev->adv_entries) { + struct adv_entry *entry; - bacpy(&entry->bdaddr, bdaddr); + entry = list_entry(p, struct adv_entry, list); - list_add(&entry->list, &hdev->blacklist); - - return mgmt_device_blocked(hdev, bdaddr, type); + if (bacmp(bdaddr, &entry->bdaddr) == 0) { + res = entry; + goto out; + } + } +out: + read_unlock_bh(&hdev->adv_entries_lock); + return res; } -int hci_blacklist_del(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) +static inline int is_connectable_adv(u8 evt_type) { - struct bdaddr_list *entry; - - 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); + if (evt_type == ADV_IND || evt_type == ADV_DIRECT_IND) + return 1; - return mgmt_device_unblocked(hdev, bdaddr, type); + return 0; } -static void hci_clear_adv_cache(struct work_struct *work) +int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *hash, + u8 *randomizer) { - struct hci_dev *hdev = container_of(work, struct hci_dev, - adv_work.work); - - 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); + 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 adv_entry *hci_find_adv_entry(struct hci_dev *hdev, bdaddr_t *bdaddr) +int hci_add_adv_entry(struct hci_dev *hdev, + struct hci_ev_le_advertising_info *ev) { struct adv_entry *entry; + u8 flags = 0; + int i; - list_for_each_entry(entry, &hdev->adv_entries, list) - if (bacmp(bdaddr, &entry->bdaddr) == 0) - return entry; - - return NULL; -} - -static inline int is_connectable_adv(u8 evt_type) -{ - if (evt_type == ADV_IND || evt_type == ADV_DIRECT_IND) - return 1; + BT_DBG(""); - return 0; -} - -int hci_add_adv_entry(struct hci_dev *hdev, - struct hci_ev_le_advertising_info *ev) { struct adv_entry *entry; if (!is_connectable_adv(ev->evt_type)) + if (!is_connectable_adv(ev->evt_type)) return -EINVAL; + if (ev->data && ev->length) { + for (i = 0; (i + 2) < ev->length; i++) + if (ev->data[i+1] == 0x01) { + flags = ev->data[i+2]; + BT_DBG("flags: %2.2x", flags); + break; + } else { + i += ev->data[i]; + } + } + + 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) { + entry->flags = flags; return 0; + } - entry = kzalloc(sizeof(*entry), GFP_KERNEL); + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); if (!entry) return -ENOMEM; bacpy(&entry->bdaddr, &ev->bdaddr); entry->bdaddr_type = ev->bdaddr_type; + entry->flags = flags; + 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 void le_scan_param_req(struct hci_dev *hdev, unsigned long opt) -{ - struct le_scan_params *param = (struct le_scan_params *) opt; - struct hci_cp_le_set_scan_param cp; - - memset(&cp, 0, sizeof(cp)); - cp.type = param->type; - cp.interval = cpu_to_le16(param->interval); - cp.window = cpu_to_le16(param->window); - - hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_PARAM, sizeof(cp), &cp); -} - -static void le_scan_enable_req(struct hci_dev *hdev, unsigned long opt) +static struct crypto_blkcipher *alloc_cypher(void) { - struct hci_cp_le_set_scan_enable cp; + if (enable_smp) + return crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC); - memset(&cp, 0, sizeof(cp)); - cp.enable = 1; - - hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(cp), &cp); -} - -static int hci_do_le_scan(struct hci_dev *hdev, u8 type, u16 interval, - u16 window, int timeout) -{ - long timeo = msecs_to_jiffies(3000); - struct le_scan_params param; - int err; - - BT_DBG("%s", hdev->name); - - if (test_bit(HCI_LE_SCAN, &hdev->dev_flags)) - return -EINPROGRESS; - - param.type = type; - param.interval = interval; - param.window = window; - - hci_req_lock(hdev); - - err = __hci_request(hdev, le_scan_param_req, (unsigned long) ¶m, - timeo); - if (!err) - err = __hci_request(hdev, le_scan_enable_req, 0, timeo); - - hci_req_unlock(hdev); - - if (err < 0) - return err; - - schedule_delayed_work(&hdev->le_scan_disable, - msecs_to_jiffies(timeout)); - - return 0; -} - -static void le_scan_disable_work(struct work_struct *work) -{ - struct hci_dev *hdev = container_of(work, struct hci_dev, - le_scan_disable.work); - struct hci_cp_le_set_scan_enable cp; - - BT_DBG("%s", hdev->name); - - memset(&cp, 0, sizeof(cp)); - - hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(cp), &cp); -} - -static void le_scan_work(struct work_struct *work) -{ - struct hci_dev *hdev = container_of(work, struct hci_dev, le_scan); - struct le_scan_params *param = &hdev->le_scan_params; - - BT_DBG("%s", hdev->name); - - hci_do_le_scan(hdev, param->type, param->interval, param->window, - param->timeout); -} - -int hci_le_scan(struct hci_dev *hdev, u8 type, u16 interval, u16 window, - int timeout) -{ - struct le_scan_params *param = &hdev->le_scan_params; - - BT_DBG("%s", hdev->name); - - if (work_busy(&hdev->le_scan)) - return -EINPROGRESS; - - param->type = type; - param->interval = interval; - param->window = window; - param->timeout = timeout; - - queue_work(system_long_wq, &hdev->le_scan); - - return 0; + return ERR_PTR(-ENOTSUPP); } /* Register HCI device */ int hci_register_dev(struct hci_dev *hdev) { struct list_head *head = &hci_dev_list, *p; - int i, id, error; + int i, id; - BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus); + BT_DBG("%p name %s bus %d owner %p", hdev, hdev->name, + hdev->bus, hdev->owner); - if (!hdev->open || !hdev->close) + if (!hdev->open || !hdev->close || !hdev->destruct) return -EINVAL; - /* Do not allow HCI_AMP devices to register at index 0, - * so the index can be used as the AMP controller ID. - */ id = (hdev->dev_type == HCI_BREDR) ? 0 : 1; - write_lock(&hci_dev_list_lock); + write_lock_bh(&hci_dev_list_lock); /* Find first available device id */ list_for_each(p, &hci_dev_list) { @@ -1741,12 +1453,12 @@ int hci_register_dev(struct hci_dev *hdev) sprintf(hdev->name, "hci%d", id); hdev->id = id; - list_add_tail(&hdev->list, head); + list_add(&hdev->list, head); - mutex_init(&hdev->lock); + atomic_set(&hdev->refcnt, 1); + spin_lock_init(&hdev->lock); hdev->flags = 0; - hdev->dev_flags = 0; hdev->pkt_type = (HCI_DM1 | HCI_DH1 | HCI_HV1); hdev->esco_type = (ESCO_HV1); hdev->link_mode = (HCI_LM_ACCEPT); @@ -1756,16 +1468,19 @@ int hci_register_dev(struct hci_dev *hdev) hdev->sniff_max_interval = 800; hdev->sniff_min_interval = 80; - INIT_WORK(&hdev->rx_work, hci_rx_work); - INIT_WORK(&hdev->cmd_work, hci_cmd_work); - INIT_WORK(&hdev->tx_work, hci_tx_work); - + tasklet_init(&hdev->cmd_task, hci_cmd_task, (unsigned long) hdev); + tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev); + tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev); skb_queue_head_init(&hdev->rx_q); skb_queue_head_init(&hdev->cmd_q); skb_queue_head_init(&hdev->raw_q); setup_timer(&hdev->cmd_timer, hci_cmd_timer, (unsigned long) hdev); + setup_timer(&hdev->disco_timer, mgmt_disco_timeout, + (unsigned long) hdev); + setup_timer(&hdev->disco_le_timer, mgmt_disco_le_timeout, + (unsigned long) hdev); for (i = 0; i < NUM_REASSEMBLY; i++) hdev->reassembly[i] = NULL; @@ -1773,49 +1488,43 @@ int hci_register_dev(struct hci_dev *hdev) init_waitqueue_head(&hdev->req_wait_q); mutex_init(&hdev->req_lock); - discovery_init(hdev); + inquiry_cache_init(hdev); hci_conn_hash_init(hdev); - - INIT_LIST_HEAD(&hdev->mgmt_pending); + hci_chan_list_init(hdev); INIT_LIST_HEAD(&hdev->blacklist); INIT_LIST_HEAD(&hdev->uuids); INIT_LIST_HEAD(&hdev->link_keys); - INIT_LIST_HEAD(&hdev->long_term_keys); INIT_LIST_HEAD(&hdev->remote_oob_data); INIT_LIST_HEAD(&hdev->adv_entries); + rwlock_init(&hdev->adv_entries_lock); + setup_timer(&hdev->adv_timer, hci_adv_clear, (unsigned long) hdev); - INIT_DELAYED_WORK(&hdev->adv_work, hci_clear_adv_cache); INIT_WORK(&hdev->power_on, hci_power_on); - INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); - - INIT_DELAYED_WORK(&hdev->discov_off, hci_discov_off); + INIT_WORK(&hdev->power_off, hci_power_off); + setup_timer(&hdev->off_timer, hci_auto_off, (unsigned long) hdev); memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); atomic_set(&hdev->promisc, 0); - INIT_WORK(&hdev->le_scan, le_scan_work); + write_unlock_bh(&hci_dev_list_lock); - INIT_DELAYED_WORK(&hdev->le_scan_disable, le_scan_disable_work); + hdev->workqueue = create_singlethread_workqueue(hdev->name); + if (!hdev->workqueue) + goto nomem; - write_unlock(&hci_dev_list_lock); + hdev->tfm = alloc_cypher(); + if (IS_ERR(hdev->tfm)) + BT_INFO("Failed to load transform for ecb(aes): %ld", + PTR_ERR(hdev->tfm)); - hdev->workqueue = alloc_workqueue(hdev->name, WQ_HIGHPRI | WQ_UNBOUND | - WQ_MEM_RECLAIM, 1); - if (!hdev->workqueue) { - error = -ENOMEM; - goto err; - } - - error = hci_add_sysfs(hdev); - if (error < 0) - goto err_wqueue; + hci_register_sysfs(hdev); hdev->rfkill = rfkill_alloc(hdev->name, &hdev->dev, RFKILL_TYPE_BLUETOOTH, &hci_rfkill_ops, hdev); @@ -1826,54 +1535,49 @@ int hci_register_dev(struct hci_dev *hdev) } } - set_bit(HCI_AUTO_OFF, &hdev->dev_flags); - set_bit(HCI_SETUP, &hdev->dev_flags); - schedule_work(&hdev->power_on); + set_bit(HCI_AUTO_OFF, &hdev->flags); + set_bit(HCI_SETUP, &hdev->flags); + queue_work(hdev->workqueue, &hdev->power_on); hci_notify(hdev, HCI_DEV_REG); - hci_dev_hold(hdev); return id; -err_wqueue: - destroy_workqueue(hdev->workqueue); -err: - write_lock(&hci_dev_list_lock); +nomem: + write_lock_bh(&hci_dev_list_lock); list_del(&hdev->list); - write_unlock(&hci_dev_list_lock); + write_unlock_bh(&hci_dev_list_lock); - return error; + return -ENOMEM; } EXPORT_SYMBOL(hci_register_dev); /* Unregister HCI device */ -void hci_unregister_dev(struct hci_dev *hdev) +int hci_unregister_dev(struct hci_dev *hdev) { int i; BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus); - set_bit(HCI_UNREGISTER, &hdev->dev_flags); - - write_lock(&hci_dev_list_lock); + write_lock_bh(&hci_dev_list_lock); list_del(&hdev->list); - write_unlock(&hci_dev_list_lock); + write_unlock_bh(&hci_dev_list_lock); - hci_dev_do_close(hdev); + hci_dev_do_close(hdev, hdev->bus == HCI_SMD); for (i = 0; i < NUM_REASSEMBLY; i++) kfree_skb(hdev->reassembly[i]); if (!test_bit(HCI_INIT, &hdev->flags) && - !test_bit(HCI_SETUP, &hdev->dev_flags)) { - hci_dev_lock(hdev); - mgmt_index_removed(hdev); - hci_dev_unlock(hdev); + !test_bit(HCI_SETUP, &hdev->flags) && + hdev->dev_type == HCI_BREDR) { + hci_dev_lock_bh(hdev); + mgmt_index_removed(hdev->id); + hci_dev_unlock_bh(hdev); } - /* mgmt_index_removed should take care of emptying the - * pending list */ - BUG_ON(!list_empty(&hdev->mgmt_pending)); + if (!IS_ERR(hdev->tfm)) + crypto_free_blkcipher(hdev->tfm); hci_notify(hdev, HCI_DEV_UNREG); @@ -1882,22 +1586,28 @@ void hci_unregister_dev(struct hci_dev *hdev) rfkill_destroy(hdev->rfkill); } - hci_del_sysfs(hdev); + hci_unregister_sysfs(hdev); - cancel_delayed_work_sync(&hdev->adv_work); + /* Disable all timers */ + hci_del_off_timer(hdev); + del_timer(&hdev->adv_timer); + del_timer(&hdev->cmd_timer); + del_timer(&hdev->disco_timer); + del_timer(&hdev->disco_le_timer); destroy_workqueue(hdev->workqueue); - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); hci_blacklist_clear(hdev); hci_uuids_clear(hdev); hci_link_keys_clear(hdev); - hci_smp_ltks_clear(hdev); hci_remote_oob_data_clear(hdev); hci_adv_entries_clear(hdev); - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); - hci_dev_put(hdev); + __hci_dev_put(hdev); + + return 0; } EXPORT_SYMBOL(hci_unregister_dev); @@ -1933,8 +1643,9 @@ int hci_recv_frame(struct sk_buff *skb) /* Time stamp */ __net_timestamp(skb); + /* Queue frame for rx task */ skb_queue_tail(&hdev->rx_q, skb); - queue_work(hdev->workqueue, &hdev->rx_work); + tasklet_schedule(&hdev->rx_task); return 0; } @@ -1985,7 +1696,7 @@ static int hci_reassembly(struct hci_dev *hdev, int type, void *data, while (count) { scb = (void *) skb->cb; - len = min_t(uint, scb->expect, count); + len = min(scb->expect, (__u16)count); memcpy(skb_put(skb, len), data, len); @@ -2063,7 +1774,7 @@ int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count) data += (count - rem); count = rem; - } + }; return rem; } @@ -2098,7 +1809,7 @@ int hci_recv_stream_fragment(struct hci_dev *hdev, void *data, int count) data += (count - rem); count = rem; - } + }; return rem; } @@ -2106,13 +1817,59 @@ EXPORT_SYMBOL(hci_recv_stream_fragment); /* ---- Interface to upper protocols ---- */ +/* Register/Unregister protocols. + * hci_task_lock is used to ensure that no tasks are running. */ +int hci_register_proto(struct hci_proto *hp) +{ + int err = 0; + + BT_DBG("%p name %s id %d", hp, hp->name, hp->id); + + if (hp->id >= HCI_MAX_PROTO) + return -EINVAL; + + write_lock_bh(&hci_task_lock); + + if (!hci_proto[hp->id]) + hci_proto[hp->id] = hp; + else + err = -EEXIST; + + write_unlock_bh(&hci_task_lock); + + return err; +} +EXPORT_SYMBOL(hci_register_proto); + +int hci_unregister_proto(struct hci_proto *hp) +{ + int err = 0; + + BT_DBG("%p name %s id %d", hp, hp->name, hp->id); + + if (hp->id >= HCI_MAX_PROTO) + return -EINVAL; + + write_lock_bh(&hci_task_lock); + + if (hci_proto[hp->id]) + hci_proto[hp->id] = NULL; + else + err = -ENOENT; + + write_unlock_bh(&hci_task_lock); + + return err; +} +EXPORT_SYMBOL(hci_unregister_proto); + int hci_register_cb(struct hci_cb *cb) { BT_DBG("%p name %s", cb, cb->name); - write_lock(&hci_cb_list_lock); + write_lock_bh(&hci_cb_list_lock); list_add(&cb->list, &hci_cb_list); - write_unlock(&hci_cb_list_lock); + write_unlock_bh(&hci_cb_list_lock); return 0; } @@ -2122,14 +1879,82 @@ int hci_unregister_cb(struct hci_cb *cb) { BT_DBG("%p name %s", cb, cb->name); - write_lock(&hci_cb_list_lock); + write_lock_bh(&hci_cb_list_lock); list_del(&cb->list); - write_unlock(&hci_cb_list_lock); + write_unlock_bh(&hci_cb_list_lock); return 0; } 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; @@ -2141,20 +1966,17 @@ static int hci_send_frame(struct sk_buff *skb) BT_DBG("%s type %d len %d", hdev->name, bt_cb(skb)->pkt_type, skb->len); - /* Time stamp */ - __net_timestamp(skb); - - /* Send copy to monitor */ - hci_send_to_monitor(hdev, skb); - if (atomic_read(&hdev->promisc)) { - /* Send copy to the sockets */ - hci_send_to_sock(hdev, skb); + /* Time stamp */ + __net_timestamp(skb); + + hci_send_to_sock(hdev, skb, NULL); } /* Get rid of skb owner, prior to sending to the driver. */ skb_orphan(skb); + hci_notify(hdev, HCI_DEV_WRITE); return hdev->send(skb); } @@ -2189,10 +2011,11 @@ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) hdev->init_last_cmd = opcode; skb_queue_tail(&hdev->cmd_q, skb); - queue_work(hdev->workqueue, &hdev->cmd_work); + tasklet_schedule(&hdev->cmd_task); 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) @@ -2225,18 +2048,27 @@ static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags) hdr->dlen = cpu_to_le16(len); } -static void hci_queue_acl(struct hci_conn *conn, struct sk_buff_head *queue, - 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 chan %p flags 0x%x", hdev->name, conn, chan, flags); + + skb->dev = (void *) hdev; + bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; + 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) { /* Non fragmented */ BT_DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len); - skb_queue_tail(queue, skb); + skb_queue_tail(&conn->data_q, skb); } else { /* Fragmented */ BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); @@ -2244,11 +2076,10 @@ static void hci_queue_acl(struct hci_conn *conn, struct sk_buff_head *queue, skb_shinfo(skb)->frag_list = NULL; /* Queue all fragments atomically */ - spin_lock(&queue->lock); - - __skb_queue_tail(queue, skb); + spin_lock_bh(&conn->data_q.lock); - flags &= ~ACL_START; + __skb_queue_tail(&conn->data_q, skb); + flags &= ~ACL_PB_MASK; flags |= ACL_CONT; do { skb = list; list = list->next; @@ -2259,27 +2090,13 @@ static void hci_queue_acl(struct hci_conn *conn, struct sk_buff_head *queue, BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); - __skb_queue_tail(queue, skb); + __skb_queue_tail(&conn->data_q, skb); } while (list); - spin_unlock(&queue->lock); + spin_unlock_bh(&conn->data_q.lock); } -} - -void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) -{ - struct hci_conn *conn = chan->conn; - struct hci_dev *hdev = conn->hdev; - - BT_DBG("%s chan %p flags 0x%x", hdev->name, chan, flags); - - skb->dev = (void *) hdev; - bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; - hci_add_acl_hdr(skb, conn->handle, flags); - - hci_queue_acl(conn, &chan->data_q, skb, flags); - queue_work(hdev->workqueue, &hdev->tx_work); + tasklet_schedule(&hdev->tx_task); } EXPORT_SYMBOL(hci_send_acl); @@ -2302,7 +2119,7 @@ void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) bt_cb(skb)->pkt_type = HCI_SCODATA_PKT; skb_queue_tail(&conn->data_q, skb); - queue_work(hdev->workqueue, &hdev->tx_work); + tasklet_schedule(&hdev->tx_task); } EXPORT_SYMBOL(hci_send_sco); @@ -2312,15 +2129,16 @@ EXPORT_SYMBOL(hci_send_sco); static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote) { struct hci_conn_hash *h = &hdev->conn_hash; - struct hci_conn *conn = NULL, *c; + struct hci_conn *conn = NULL; int num = 0, min = ~0; + struct list_head *p; /* We don't have to lock device here. Connections are always * added and removed with TX task disabled. */ + list_for_each(p, &h->list) { + struct hci_conn *c; + c = list_entry(p, struct hci_conn, list); - rcu_read_lock(); - - list_for_each_entry_rcu(c, &h->list, list) { if (c->type != type || skb_queue_empty(&c->data_q)) continue; @@ -2333,13 +2151,8 @@ static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int min = c->sent; conn = c; } - - if (hci_conn_num(hdev, type) == num) - break; } - rcu_read_unlock(); - if (conn) { int cnt, q; @@ -2371,270 +2184,67 @@ static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int static inline void hci_link_tx_to(struct hci_dev *hdev, __u8 type) { struct hci_conn_hash *h = &hdev->conn_hash; - struct hci_conn *c; + struct list_head *p; + struct hci_conn *c; BT_ERR("%s link tx timeout", hdev->name); - rcu_read_lock(); - /* Kill stalled connections */ - list_for_each_entry_rcu(c, &h->list, list) { + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); if (c->type == type && c->sent) { BT_ERR("%s killing stalled connection %s", hdev->name, batostr(&c->dst)); hci_acl_disconn(c, 0x13); } } - - rcu_read_unlock(); -} - -static inline struct hci_chan *hci_chan_sent(struct hci_dev *hdev, __u8 type, - int *quote) -{ - struct hci_conn_hash *h = &hdev->conn_hash; - struct hci_chan *chan = NULL; - int num = 0, min = ~0, cur_prio = 0; - struct hci_conn *conn; - int cnt, q, conn_num = 0; - - BT_DBG("%s", hdev->name); - - rcu_read_lock(); - - list_for_each_entry_rcu(conn, &h->list, list) { - struct hci_chan *tmp; - - if (conn->type != type) - continue; - - if (conn->state != BT_CONNECTED && conn->state != BT_CONFIG) - continue; - - conn_num++; - - list_for_each_entry_rcu(tmp, &conn->chan_list, list) { - struct sk_buff *skb; - - if (skb_queue_empty(&tmp->data_q)) - continue; - - skb = skb_peek(&tmp->data_q); - if (skb->priority < cur_prio) - continue; - - if (skb->priority > cur_prio) { - num = 0; - min = ~0; - cur_prio = skb->priority; - } - - num++; - - if (conn->sent < min) { - min = conn->sent; - chan = tmp; - } - } - - if (hci_conn_num(hdev, type) == conn_num) - break; - } - - rcu_read_unlock(); - - if (!chan) - return NULL; - - switch (chan->conn->type) { - case ACL_LINK: - cnt = hdev->acl_cnt; - break; - case SCO_LINK: - case ESCO_LINK: - cnt = hdev->sco_cnt; - break; - case LE_LINK: - cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt; - break; - default: - cnt = 0; - BT_ERR("Unknown link type"); - } - - q = cnt / num; - *quote = q ? q : 1; - BT_DBG("chan %p quote %d", chan, *quote); - return chan; } -static void hci_prio_recalculate(struct hci_dev *hdev, __u8 type) +static inline void hci_sched_acl(struct hci_dev *hdev) { - struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *conn; - int num = 0; + struct sk_buff *skb; + int quote; BT_DBG("%s", hdev->name); - rcu_read_lock(); - - list_for_each_entry_rcu(conn, &h->list, list) { - struct hci_chan *chan; - - if (conn->type != type) - continue; - - if (conn->state != BT_CONNECTED && conn->state != BT_CONFIG) - continue; - - num++; - - list_for_each_entry_rcu(chan, &conn->chan_list, list) { - struct sk_buff *skb; - - if (chan->sent) { - chan->sent = 0; - continue; - } - - if (skb_queue_empty(&chan->data_q)) - continue; - - skb = skb_peek(&chan->data_q); - if (skb->priority >= HCI_PRIO_MAX - 1) - continue; - - skb->priority = HCI_PRIO_MAX - 1; - - BT_DBG("chan %p skb %p promoted to %d", chan, skb, - skb->priority); - } - - if (hci_conn_num(hdev, type) == num) - break; - } - - rcu_read_unlock(); - -} - -static inline int __get_blocks(struct hci_dev *hdev, struct sk_buff *skb) -{ - /* Calculate count of blocks used by this packet */ - return DIV_ROUND_UP(skb->len - HCI_ACL_HDR_SIZE, hdev->block_len); -} - -static inline void __check_timeout(struct hci_dev *hdev, unsigned int cnt) -{ if (!test_bit(HCI_RAW, &hdev->flags)) { /* ACL tx timeout must be longer than maximum * link supervision timeout (40.9 seconds) */ - if (!cnt && time_after(jiffies, hdev->acl_last_tx + - msecs_to_jiffies(HCI_ACL_TX_TIMEOUT))) + if (hdev->acl_cnt <= 0 && + time_after(jiffies, hdev->acl_last_tx + HZ * 45)) hci_link_tx_to(hdev, ACL_LINK); } -} - -static inline void hci_sched_acl_pkt(struct hci_dev *hdev) -{ - unsigned int cnt = hdev->acl_cnt; - struct hci_chan *chan; - struct sk_buff *skb; - int quote; - - __check_timeout(hdev, cnt); - - while (hdev->acl_cnt && - (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { - u32 priority = (skb_peek(&chan->data_q))->priority; - while (quote-- && (skb = skb_peek(&chan->data_q))) { - BT_DBG("chan %p skb %p len %d priority %u", chan, skb, - skb->len, skb->priority); - - /* Stop if priority has changed */ - if (skb->priority < priority) - break; - - skb = skb_dequeue(&chan->data_q); - - hci_conn_enter_active_mode(chan->conn, - bt_cb(skb)->force_active); - - hci_send_frame(skb); - hdev->acl_last_tx = jiffies; - - hdev->acl_cnt--; - chan->sent++; - chan->conn->sent++; - } - } - - if (cnt != hdev->acl_cnt) - hci_prio_recalculate(hdev, ACL_LINK); -} - -static inline void hci_sched_acl_blk(struct hci_dev *hdev) -{ - unsigned int cnt = hdev->block_cnt; - struct hci_chan *chan; - struct sk_buff *skb; - int quote; - - __check_timeout(hdev, cnt); - while (hdev->block_cnt > 0 && - (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { - u32 priority = (skb_peek(&chan->data_q))->priority; - while (quote > 0 && (skb = skb_peek(&chan->data_q))) { - int blocks; + 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("chan %p skb %p len %d priority %u", chan, skb, - skb->len, skb->priority); - - /* Stop if priority has changed */ - if (skb->priority < priority) - break; + BT_DBG("skb %p len %d", skb, skb->len); - skb = skb_dequeue(&chan->data_q); + 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; - blocks = __get_blocks(hdev, skb); - if (blocks > hdev->block_cnt) + if (count > hdev->acl_cnt) return; - hci_conn_enter_active_mode(chan->conn, - bt_cb(skb)->force_active); + hci_conn_enter_active_mode(conn, bt_cb(skb)->force_active); hci_send_frame(skb); hdev->acl_last_tx = jiffies; - hdev->block_cnt -= blocks; - quote -= blocks; + hdev->acl_cnt -= count; + quote -= count; - chan->sent += blocks; - chan->conn->sent += blocks; + conn->sent += count; } } - - if (cnt != hdev->block_cnt) - hci_prio_recalculate(hdev, ACL_LINK); -} - -static inline void hci_sched_acl(struct hci_dev *hdev) -{ - BT_DBG("%s", hdev->name); - - if (!hci_conn_num(hdev, ACL_LINK)) - return; - - switch (hdev->flow_ctl_mode) { - case HCI_FLOW_CTL_MODE_PACKET_BASED: - hci_sched_acl_pkt(hdev); - break; - - case HCI_FLOW_CTL_MODE_BLOCK_BASED: - hci_sched_acl_blk(hdev); - break; - } } /* Schedule SCO */ @@ -2646,9 +2256,6 @@ static inline void hci_sched_sco(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - if (!hci_conn_num(hdev, SCO_LINK)) - return; - while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) { while (quote-- && (skb = skb_dequeue(&conn->data_q))) { BT_DBG("skb %p len %d", skb, skb->len); @@ -2669,9 +2276,6 @@ static inline void hci_sched_esco(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - if (!hci_conn_num(hdev, ESCO_LINK)) - return; - while (hdev->sco_cnt && (conn = hci_low_sent(hdev, ESCO_LINK, "e))) { while (quote-- && (skb = skb_dequeue(&conn->data_q))) { BT_DBG("skb %p len %d", skb, skb->len); @@ -2686,15 +2290,12 @@ static inline void hci_sched_esco(struct hci_dev *hdev) static inline void hci_sched_le(struct hci_dev *hdev) { - struct hci_chan *chan; + struct hci_conn *conn; struct sk_buff *skb; - int quote, cnt, tmp; + int quote, cnt; BT_DBG("%s", hdev->name); - if (!hci_conn_num(hdev, LE_LINK)) - return; - if (!test_bit(HCI_RAW, &hdev->flags)) { /* LE tx timeout must be longer than maximum * link supervision timeout (40.9 seconds) */ @@ -2704,42 +2305,30 @@ static inline void hci_sched_le(struct hci_dev *hdev) } cnt = hdev->le_pkts ? hdev->le_cnt : hdev->acl_cnt; - tmp = cnt; - while (cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { - u32 priority = (skb_peek(&chan->data_q))->priority; - while (quote-- && (skb = skb_peek(&chan->data_q))) { - BT_DBG("chan %p skb %p len %d priority %u", chan, skb, - skb->len, skb->priority); - - /* Stop if priority has changed */ - if (skb->priority < priority) - break; - - skb = skb_dequeue(&chan->data_q); + while (cnt && (conn = hci_low_sent(hdev, LE_LINK, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); hci_send_frame(skb); hdev->le_last_tx = jiffies; cnt--; - chan->sent++; - chan->conn->sent++; + conn->sent++; } } - if (hdev->le_pkts) hdev->le_cnt = cnt; else hdev->acl_cnt = cnt; - - if (cnt != tmp) - hci_prio_recalculate(hdev, LE_LINK); } -static void hci_tx_work(struct work_struct *work) +static void hci_tx_task(unsigned long arg) { - struct hci_dev *hdev = container_of(work, struct hci_dev, tx_work); + struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; + read_lock(&hci_task_lock); + BT_DBG("%s acl %d sco %d le %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt, hdev->le_cnt); @@ -2756,9 +2345,11 @@ static void hci_tx_work(struct work_struct *work) /* Send next queued raw (unknown type) packet */ while ((skb = skb_dequeue(&hdev->raw_q))) hci_send_frame(skb); + + 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) @@ -2782,19 +2373,16 @@ static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) hci_dev_unlock(hdev); if (conn) { - hci_conn_enter_active_mode(conn, BT_POWER_FORCE_ACTIVE_OFF); + register struct hci_proto *hp; - hci_dev_lock(hdev); - if (test_bit(HCI_MGMT, &hdev->dev_flags) && - !test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) - mgmt_device_connected(hdev, &conn->dst, conn->type, - conn->dst_type, 0, NULL, 0, - conn->dev_class); - hci_dev_unlock(hdev); + hci_conn_enter_active_mode(conn, bt_cb(skb)->force_active); /* Send to upper protocol */ - l2cap_recv_acldata(conn, skb, flags); - return; + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->recv_acldata) { + hp->recv_acldata(conn, skb, flags); + return; + } } else { BT_ERR("%s ACL packet for unknown connection handle %d", hdev->name, handle); @@ -2823,9 +2411,14 @@ static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb) hci_dev_unlock(hdev); if (conn) { + register struct hci_proto *hp; + /* Send to upper protocol */ - sco_recv_scodata(conn, skb); - return; + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->recv_scodata) { + hp->recv_scodata(conn, skb); + return; + } } else { BT_ERR("%s SCO packet for unknown connection handle %d", hdev->name, handle); @@ -2834,20 +2427,19 @@ static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb) kfree_skb(skb); } -static void hci_rx_work(struct work_struct *work) +static void hci_rx_task(unsigned long arg) { - struct hci_dev *hdev = container_of(work, struct hci_dev, rx_work); + struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; BT_DBG("%s", hdev->name); - while ((skb = skb_dequeue(&hdev->rx_q))) { - /* Send copy to monitor */ - hci_send_to_monitor(hdev, skb); + read_lock(&hci_task_lock); + while ((skb = skb_dequeue(&hdev->rx_q))) { if (atomic_read(&hdev->promisc)) { /* Send copy to the sockets */ - hci_send_to_sock(hdev, skb); + hci_send_to_sock(hdev, skb, NULL); } if (test_bit(HCI_RAW, &hdev->flags)) { @@ -2868,7 +2460,6 @@ static void hci_rx_work(struct work_struct *work) /* Process frame */ switch (bt_cb(skb)->pkt_type) { case HCI_EVENT_PKT: - BT_DBG("%s Event packet", hdev->name); hci_event_packet(hdev, skb); break; @@ -2887,11 +2478,13 @@ static void hci_rx_work(struct work_struct *work) break; } } + + read_unlock(&hci_task_lock); } -static void hci_cmd_work(struct work_struct *work) +static void hci_cmd_task(unsigned long arg) { - struct hci_dev *hdev = container_of(work, struct hci_dev, cmd_work); + struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; BT_DBG("%s cmd %d", hdev->name, atomic_read(&hdev->cmd_cnt)); @@ -2908,44 +2501,14 @@ static void hci_cmd_work(struct work_struct *work) 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); - queue_work(hdev->workqueue, &hdev->cmd_work); + tasklet_schedule(&hdev->cmd_task); } } } -int hci_do_inquiry(struct hci_dev *hdev, u8 length) -{ - /* General inquiry access code (GIAC) */ - u8 lap[3] = { 0x33, 0x8b, 0x9e }; - struct hci_cp_inquiry cp; - - BT_DBG("%s", hdev->name); - - if (test_bit(HCI_INQUIRY, &hdev->flags)) - return -EINPROGRESS; - - inquiry_cache_flush(hdev); - - memset(&cp, 0, sizeof(cp)); - memcpy(&cp.lap, lap, sizeof(cp.lap)); - cp.length = length; - - return hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp); -} - -int hci_cancel_inquiry(struct hci_dev *hdev) -{ - BT_DBG("%s", hdev->name); - - if (!test_bit(HCI_INQUIRY, &hdev->flags)) - return -EPERM; - - return hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, NULL); -} +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 old mode 100755 new mode 100644 index 626318c122f4a7b9141267faf61562c2600cb1da..2b14423d1ab3556e11bdc5ac33a8dbe8726bf027 --- 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-2012, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -35,8 +35,10 @@ #include #include #include +#include #include +#include #include #include @@ -51,19 +53,11 @@ static void hci_cc_inquiry_cancel(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%x", hdev->name, status); - if (status) { - hci_dev_lock(hdev); - mgmt_stop_discovery_failed(hdev, status); - hci_dev_unlock(hdev); + if (status) return; - } clear_bit(HCI_INQUIRY, &hdev->flags); - hci_dev_lock(hdev); - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); - hci_dev_unlock(hdev); - hci_req_complete(hdev, HCI_OP_INQUIRY_CANCEL, status); hci_conn_check_pending(hdev); @@ -78,9 +72,36 @@ static void hci_cc_exit_periodic_inq(struct hci_dev *hdev, struct sk_buff *skb) if (status) return; + 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); @@ -190,11 +211,6 @@ static void hci_cc_reset(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_RESET, &hdev->flags); hci_req_complete(hdev, HCI_OP_RESET, status); - - /* Reset all non-persistent flags */ - hdev->dev_flags &= ~(BIT(HCI_LE_SCAN) | BIT(HCI_PENDING_CLASS)); - - hdev->discovery.state = DISCOVERY_STOPPED; } static void hci_cc_write_local_name(struct hci_dev *hdev, struct sk_buff *skb) @@ -207,17 +223,13 @@ static void hci_cc_write_local_name(struct hci_dev *hdev, struct sk_buff *skb) sent = hci_sent_cmd_data(hdev, HCI_OP_WRITE_LOCAL_NAME); if (!sent) return; - hci_dev_lock(hdev); - - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_set_local_name_complete(hdev, sent, status); - else if (!status) + if (!status) memcpy(hdev->dev_name, sent, HCI_MAX_NAME_LENGTH); + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_set_local_name_complete(hdev->id, sent, status); hci_dev_unlock(hdev); - - hci_req_complete(hdev, HCI_OP_WRITE_LOCAL_NAME, status); } static void hci_cc_read_local_name(struct hci_dev *hdev, struct sk_buff *skb) @@ -229,8 +241,7 @@ static void hci_cc_read_local_name(struct hci_dev *hdev, struct sk_buff *skb) if (rp->status) return; - if (test_bit(HCI_SETUP, &hdev->dev_flags)) - memcpy(hdev->dev_name, rp->name, HCI_MAX_NAME_LENGTH); + memcpy(hdev->dev_name, rp->name, HCI_MAX_NAME_LENGTH); } static void hci_cc_write_auth_enable(struct hci_dev *hdev, struct sk_buff *skb) @@ -253,9 +264,6 @@ static void hci_cc_write_auth_enable(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_AUTH, &hdev->flags); } - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_auth_enable_complete(hdev, status); - hci_req_complete(hdev, HCI_OP_WRITE_AUTH_ENABLE, status); } @@ -284,8 +292,7 @@ static void hci_cc_write_encrypt_mode(struct hci_dev *hdev, struct sk_buff *skb) static void hci_cc_write_scan_enable(struct hci_dev *hdev, struct sk_buff *skb) { - __u8 param, status = *((__u8 *) skb->data); - int old_pscan, old_iscan; + __u8 status = *((__u8 *) skb->data); void *sent; BT_DBG("%s status 0x%x", hdev->name, status); @@ -294,40 +301,30 @@ static void hci_cc_write_scan_enable(struct hci_dev *hdev, struct sk_buff *skb) if (!sent) return; - param = *((__u8 *) sent); - - hci_dev_lock(hdev); + if (!status) { + __u8 param = *((__u8 *) sent); + int old_pscan, old_iscan; + hci_dev_lock(hdev); - if (status != 0) { - mgmt_write_scan_failed(hdev, param, status); - hdev->discov_timeout = 0; - goto done; + old_pscan = test_and_clear_bit(HCI_PSCAN, &hdev->flags); + old_iscan = test_and_clear_bit(HCI_ISCAN, &hdev->flags); + + if (param & SCAN_INQUIRY) { + set_bit(HCI_ISCAN, &hdev->flags); + if (!old_iscan) + mgmt_discoverable(hdev->id, 1); + } else if (old_iscan) + mgmt_discoverable(hdev->id, 0); + + if (param & SCAN_PAGE) { + set_bit(HCI_PSCAN, &hdev->flags); + if (!old_pscan) + mgmt_connectable(hdev->id, 1); + } else if (old_pscan) + mgmt_connectable(hdev->id, 0); + hci_dev_unlock(hdev); } - old_pscan = test_and_clear_bit(HCI_PSCAN, &hdev->flags); - old_iscan = test_and_clear_bit(HCI_ISCAN, &hdev->flags); - - if (param & SCAN_INQUIRY) { - set_bit(HCI_ISCAN, &hdev->flags); - if (!old_iscan) - mgmt_discoverable(hdev, 1); - if (hdev->discov_timeout > 0) { - int to = msecs_to_jiffies(hdev->discov_timeout * 1000); - queue_delayed_work(hdev->workqueue, &hdev->discov_off, - to); - } - } else if (old_iscan) - mgmt_discoverable(hdev, 0); - - if (param & SCAN_PAGE) { - set_bit(HCI_PSCAN, &hdev->flags); - if (!old_pscan) - mgmt_connectable(hdev, 1); - } else if (old_pscan) - mgmt_connectable(hdev, 0); - -done: - hci_dev_unlock(hdev); hci_req_complete(hdev, HCI_OP_WRITE_SCAN_ENABLE, status); } @@ -353,19 +350,14 @@ static void hci_cc_write_class_of_dev(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%x", hdev->name, status); + if (status) + return; + sent = hci_sent_cmd_data(hdev, HCI_OP_WRITE_CLASS_OF_DEV); if (!sent) return; - hci_dev_lock(hdev); - - if (status == 0) - memcpy(hdev->dev_class, sent, 3); - - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_set_class_of_dev_complete(hdev, sent, status); - - hci_dev_unlock(hdev); + memcpy(hdev->dev_class, sent, 3); } static void hci_cc_read_voice_setting(struct hci_dev *hdev, struct sk_buff *skb) @@ -387,8 +379,11 @@ static void hci_cc_read_voice_setting(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s voice setting 0x%04x", hdev->name, setting); - if (hdev->notify) + if (hdev->notify) { + tasklet_disable(&hdev->tx_task); hdev->notify(hdev, HCI_NOTIFY_VOICE_SETTING); + tasklet_enable(&hdev->tx_task); + } } static void hci_cc_write_voice_setting(struct hci_dev *hdev, struct sk_buff *skb) @@ -415,8 +410,11 @@ static void hci_cc_write_voice_setting(struct hci_dev *hdev, struct sk_buff *skb BT_DBG("%s voice setting 0x%04x", hdev->name, setting); - if (hdev->notify) + if (hdev->notify) { + tasklet_disable(&hdev->tx_task); hdev->notify(hdev, HCI_NOTIFY_VOICE_SETTING); + tasklet_enable(&hdev->tx_task); + } } static void hci_cc_host_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) @@ -428,6 +426,18 @@ static void hci_cc_host_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) hci_req_complete(hdev, HCI_OP_HOST_BUFFER_SIZE, status); } +static void hci_cc_read_ssp_mode(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_rp_read_ssp_mode *rp = (void *) skb->data; + + BT_DBG("%s status 0x%x", hdev->name, rp->status); + + if (rp->status) + return; + + hdev->ssp_mode = rp->mode; +} + static void hci_cc_write_ssp_mode(struct hci_dev *hdev, struct sk_buff *skb) { __u8 status = *((__u8 *) skb->data); @@ -435,18 +445,14 @@ static void hci_cc_write_ssp_mode(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%x", hdev->name, status); + if (status) + return; + sent = hci_sent_cmd_data(hdev, HCI_OP_WRITE_SSP_MODE); if (!sent) return; - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_ssp_enable_complete(hdev, *((u8 *) sent), status); - else if (!status) { - if (*((u8 *) sent)) - set_bit(HCI_SSP_ENABLED, &hdev->dev_flags); - else - clear_bit(HCI_SSP_ENABLED, &hdev->dev_flags); - } + hdev->ssp_mode = *((__u8 *) sent); } static u8 hci_get_inquiry_mode(struct hci_dev *hdev) @@ -493,16 +499,16 @@ 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->hci_ver < BLUETOOTH_VER_1_2) - return; + BT_DBG(""); - 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 */ @@ -543,27 +549,12 @@ static void hci_setup_event_mask(struct hci_dev *hdev) static void hci_setup(struct hci_dev *hdev) { - if (hdev->dev_type != HCI_BREDR) - return; - - hci_setup_event_mask(hdev); - - if (hdev->hci_ver > BLUETOOTH_VER_1_1) + if (hdev->lmp_ver > 1) hci_send_cmd(hdev, HCI_OP_READ_LOCAL_COMMANDS, 0, NULL); if (hdev->features[6] & LMP_SIMPLE_PAIR) { - if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) { - u8 mode = 0x01; - hci_send_cmd(hdev, HCI_OP_WRITE_SSP_MODE, - sizeof(mode), &mode); - } else { - struct hci_cp_write_eir cp; - - memset(hdev->eir, 0, sizeof(hdev->eir)); - memset(&cp, 0, sizeof(cp)); - - hci_send_cmd(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp); - } + u8 mode = 0x01; + hci_send_cmd(hdev, HCI_OP_WRITE_SSP_MODE, sizeof(mode), &mode); } if (hdev->features[3] & LMP_RSSI_INQ) @@ -571,20 +562,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 (test_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) { - u8 enable = 1; - hci_send_cmd(hdev, HCI_OP_WRITE_AUTH_ENABLE, sizeof(enable), - &enable); - } } static void hci_cc_read_local_version(struct hci_dev *hdev, struct sk_buff *skb) @@ -594,7 +571,7 @@ static void hci_cc_read_local_version(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%x", hdev->name, rp->status); if (rp->status) - goto done; + return; hdev->hci_ver = rp->hci_ver; hdev->hci_rev = __le16_to_cpu(rp->hci_rev); @@ -606,11 +583,8 @@ 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); - -done: - hci_req_complete(hdev, HCI_OP_READ_LOCAL_VERSION, rp->status); } static void hci_setup_link_policy(struct hci_dev *hdev) @@ -627,8 +601,8 @@ static void hci_setup_link_policy(struct hci_dev *hdev) link_policy |= HCI_LP_PARK; link_policy = cpu_to_le16(link_policy); - hci_send_cmd(hdev, HCI_OP_WRITE_DEF_LINK_POLICY, sizeof(link_policy), - &link_policy); + hci_send_cmd(hdev, HCI_OP_WRITE_DEF_LINK_POLICY, + sizeof(link_policy), &link_policy); } static void hci_cc_read_local_commands(struct hci_dev *hdev, struct sk_buff *skb) @@ -660,6 +634,23 @@ static void hci_cc_read_local_features(struct hci_dev *hdev, struct sk_buff *skb memcpy(hdev->features, rp->features, 8); + if (hdev->dev_type == HCI_BREDR && test_bit(HCI_INIT, &hdev->flags)) { + if (hdev->features[6] & LMP_SIMPLE_PAIR) { + u8 mode = 0x01; + hci_send_cmd(hdev, HCI_OP_WRITE_SSP_MODE, + sizeof(mode), &mode); + } + + if (hdev->features[3] & LMP_RSSI_INQ) + hci_setup_inquiry_mode(hdev); + + if (hdev->features[7] & LMP_INQ_TX_PWR) + hci_send_cmd(hdev, HCI_OP_READ_INQ_RSP_TX_POWER, + 0, NULL); + + hci_setup_event_mask(hdev); + } + /* Adjust default settings according to features * supported by device. */ @@ -704,50 +695,8 @@ static void hci_cc_read_local_features(struct hci_dev *hdev, struct sk_buff *skb hdev->features[6], hdev->features[7]); } -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 && test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) { - cp.le = 1; - cp.simul = !!(hdev->features[6] & LMP_SIMUL_LE_BR); - } - - if (cp.le != !!(hdev->host_features[0] & LMP_HOST_LE)) - hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(cp), - &cp); -} - -static void hci_cc_read_local_ext_features(struct hci_dev *hdev, - struct sk_buff *skb) -{ - struct hci_rp_read_local_ext_features *rp = (void *) skb->data; - - BT_DBG("%s status 0x%x", hdev->name, rp->status); - - if (rp->status) - goto done; - - switch (rp->page) { - case 0: - memcpy(hdev->features, rp->features, 8); - break; - case 1: - memcpy(hdev->host_features, rp->features, 8); - break; - } - - if (test_bit(HCI_INIT, &hdev->flags) && hdev->features[4] & LMP_LE) - hci_set_le_support(hdev); - -done: - hci_req_complete(hdev, HCI_OP_READ_LOCAL_EXT_FEATURES, rp->status); -} - static void hci_cc_read_flow_control_mode(struct hci_dev *hdev, - struct sk_buff *skb) + struct sk_buff *skb) { struct hci_rp_read_flow_control_mode *rp = (void *) skb->data; @@ -757,8 +706,6 @@ static void hci_cc_read_flow_control_mode(struct hci_dev *hdev, return; hdev->flow_ctl_mode = rp->mode; - - hci_req_complete(hdev, HCI_OP_READ_FLOW_CONTROL_MODE, rp->status); } static void hci_cc_read_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) @@ -770,18 +717,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, @@ -800,8 +749,17 @@ static void hci_cc_read_bd_addr(struct hci_dev *hdev, struct sk_buff *skb) hci_req_complete(hdev, HCI_OP_READ_BD_ADDR, rp->status); } +static void hci_cc_write_ca_timeout(struct hci_dev *hdev, struct sk_buff *skb) +{ + __u8 status = *((__u8 *) skb->data); + + BT_DBG("%s status 0x%x", hdev->name, status); + + 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 sk_buff *skb) { struct hci_rp_read_data_block_size *rp = (void *) skb->data; @@ -810,29 +768,23 @@ static void hci_cc_read_data_block_size(struct hci_dev *hdev, if (rp->status) return; - hdev->block_mtu = __le16_to_cpu(rp->max_acl_len); - hdev->block_len = __le16_to_cpu(rp->block_len); - hdev->num_blocks = __le16_to_cpu(rp->num_blocks); - - hdev->block_cnt = hdev->num_blocks; - - BT_DBG("%s blk mtu %d cnt %d len %d", hdev->name, hdev->block_mtu, - hdev->block_cnt, hdev->block_len); - - hci_req_complete(hdev, HCI_OP_READ_DATA_BLOCK_SIZE, rp->status); -} - -static void hci_cc_write_ca_timeout(struct hci_dev *hdev, struct sk_buff *skb) -{ - __u8 status = *((__u8 *) skb->data); - - BT_DBG("%s status 0x%x", hdev->name, status); + 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; + } - hci_req_complete(hdev, HCI_OP_WRITE_CA_TIMEOUT, status); + 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 sk_buff *skb) { struct hci_rp_read_local_amp_info *rp = (void *) skb->data; @@ -910,11 +862,10 @@ static void hci_cc_pin_code_reply(struct hci_dev *hdev, struct sk_buff *skb) struct hci_conn *conn; BT_DBG("%s status 0x%x", hdev->name, rp->status); - hci_dev_lock(hdev); - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_pin_code_reply_complete(hdev, &rp->bdaddr, rp->status); + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_pin_code_reply_complete(hdev->id, &rp->bdaddr, rp->status); if (rp->status != 0) goto unlock; @@ -926,7 +877,6 @@ static void hci_cc_pin_code_reply(struct hci_dev *hdev, struct sk_buff *skb) conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); if (conn) conn->pin_length = cp->pin_len; - unlock: hci_dev_unlock(hdev); } @@ -936,16 +886,13 @@ static void hci_cc_pin_code_neg_reply(struct hci_dev *hdev, struct sk_buff *skb) struct hci_rp_pin_code_neg_reply *rp = (void *) skb->data; BT_DBG("%s status 0x%x", hdev->name, rp->status); - hci_dev_lock(hdev); - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_pin_code_neg_reply_complete(hdev, &rp->bdaddr, + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_pin_code_neg_reply_complete(hdev->id, &rp->bdaddr, rp->status); - hci_dev_unlock(hdev); } - static void hci_cc_le_read_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) { @@ -971,13 +918,11 @@ static void hci_cc_user_confirm_reply(struct hci_dev *hdev, struct sk_buff *skb) struct hci_rp_user_confirm_reply *rp = (void *) skb->data; BT_DBG("%s status 0x%x", hdev->name, rp->status); - hci_dev_lock(hdev); - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_user_confirm_reply_complete(hdev, &rp->bdaddr, ACL_LINK, 0, - rp->status); - + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_user_confirm_reply_complete(hdev->id, &rp->bdaddr, + rp->status); hci_dev_unlock(hdev); } @@ -987,45 +932,27 @@ static void hci_cc_user_confirm_neg_reply(struct hci_dev *hdev, struct hci_rp_user_confirm_reply *rp = (void *) skb->data; BT_DBG("%s status 0x%x", hdev->name, rp->status); - - hci_dev_lock(hdev); - - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_user_confirm_neg_reply_complete(hdev, &rp->bdaddr, - ACL_LINK, 0, rp->status); - - hci_dev_unlock(hdev); -} - -static void hci_cc_user_passkey_reply(struct hci_dev *hdev, struct sk_buff *skb) -{ - struct hci_rp_user_confirm_reply *rp = (void *) skb->data; - - BT_DBG("%s status 0x%x", hdev->name, rp->status); - hci_dev_lock(hdev); - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_user_passkey_reply_complete(hdev, &rp->bdaddr, ACL_LINK, - 0, rp->status); - + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_user_confirm_neg_reply_complete(hdev->id, &rp->bdaddr, + rp->status); hci_dev_unlock(hdev); } -static void hci_cc_user_passkey_neg_reply(struct hci_dev *hdev, - struct sk_buff *skb) +static void hci_cc_read_rssi(struct hci_dev *hdev, struct sk_buff *skb) { - struct hci_rp_user_confirm_reply *rp = (void *) skb->data; + struct hci_conn *conn; + struct hci_rp_read_rssi *rp = (void *) skb->data; BT_DBG("%s status 0x%x", hdev->name, rp->status); - hci_dev_lock(hdev); - - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_user_passkey_neg_reply_complete(hdev, &rp->bdaddr, - ACL_LINK, 0, rp->status); + BT_DBG("%s rssi : %d handle : %d", hdev->name, rp->rssi, rp->handle); - hci_dev_unlock(hdev); + conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(rp->handle)); + if (conn) + mgmt_read_rssi_complete(hdev->id, rp->rssi, &conn->dst, + __le16_to_cpu(rp->handle), rp->status); } static void hci_cc_read_local_oob_data_reply(struct hci_dev *hdev, @@ -1034,86 +961,13 @@ static void hci_cc_read_local_oob_data_reply(struct hci_dev *hdev, struct hci_rp_read_local_oob_data *rp = (void *) skb->data; BT_DBG("%s status 0x%x", hdev->name, rp->status); - hci_dev_lock(hdev); - mgmt_read_local_oob_data_reply_complete(hdev, rp->hash, + + mgmt_read_local_oob_data_reply_complete(hdev->id, rp->hash, rp->randomizer, rp->status); hci_dev_unlock(hdev); } -static void hci_cc_le_set_scan_param(struct hci_dev *hdev, struct sk_buff *skb) -{ - __u8 status = *((__u8 *) skb->data); - - BT_DBG("%s status 0x%x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_LE_SET_SCAN_PARAM, status); - - if (status) { - hci_dev_lock(hdev); - mgmt_start_discovery_failed(hdev, status); - hci_dev_unlock(hdev); - return; - } -} - -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); - - cp = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_SCAN_ENABLE); - if (!cp) - return; - - switch (cp->enable) { - case LE_SCANNING_ENABLED: - hci_req_complete(hdev, HCI_OP_LE_SET_SCAN_ENABLE, status); - - if (status) { - hci_dev_lock(hdev); - mgmt_start_discovery_failed(hdev, status); - hci_dev_unlock(hdev); - return; - } - - set_bit(HCI_LE_SCAN, &hdev->dev_flags); - - cancel_delayed_work_sync(&hdev->adv_work); - - hci_dev_lock(hdev); - hci_adv_entries_clear(hdev); - hci_discovery_set_state(hdev, DISCOVERY_FINDING); - hci_dev_unlock(hdev); - break; - - case LE_SCANNING_DISABLED: - if (status) - return; - - clear_bit(HCI_LE_SCAN, &hdev->dev_flags); - - schedule_delayed_work(&hdev->adv_work, ADV_CLEAR_TIMEOUT); - - if (hdev->discovery.type == DISCOV_TYPE_INTERLEAVED) { - mgmt_interleaved_discovery(hdev); - } else { - hci_dev_lock(hdev); - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); - hci_dev_unlock(hdev); - } - - break; - - default: - BT_ERR("Used reserved LE_Scan_Enable param %d", cp->enable); - break; - } -} - 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; @@ -1138,30 +992,26 @@ 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_write_le_host_supported *sent; + void *sent; + __u8 param_scan_enable; __u8 status = *((__u8 *) skb->data); - BT_DBG("%s status 0x%x", hdev->name, status); + if (status) + return; - sent = hci_sent_cmd_data(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED); + sent = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_SCAN_ENABLE); if (!sent) return; - if (!status) { - if (sent->le) - hdev->host_features[0] |= LMP_HOST_LE; - else - hdev->host_features[0] &= ~LMP_HOST_LE; + param_scan_enable = *((__u8 *) sent); + if (param_scan_enable == 0x01) { + del_timer(&hdev->adv_timer); + } else if (param_scan_enable == 0x00) { + mod_timer(&hdev->adv_timer, jiffies + ADV_CLEAR_TIMEOUT); } - - if (test_bit(HCI_MGMT, &hdev->dev_flags) && - !test_bit(HCI_INIT, &hdev->flags)) - mgmt_le_enable_complete(hdev, sent->le, status); - - hci_req_complete(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, status); } static inline void hci_cs_inquiry(struct hci_dev *hdev, __u8 status) @@ -1170,19 +1020,15 @@ 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); + } else { + set_bit(HCI_INQUIRY, &hdev->flags); hci_dev_lock(hdev); - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_start_discovery_failed(hdev, status); + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_inquiry_started(hdev->id); hci_dev_unlock(hdev); - return; } - - set_bit(HCI_INQUIRY, &hdev->flags); - - hci_dev_lock(hdev); - hci_discovery_set_state(hdev, DISCOVERY_FINDING); - hci_dev_unlock(hdev); } static inline void hci_cs_create_conn(struct hci_dev *hdev, __u8 status) @@ -1215,7 +1061,7 @@ static inline void hci_cs_create_conn(struct hci_dev *hdev, __u8 status) if (!conn) { conn = hci_conn_add(hdev, ACL_LINK, 0, &cp->bdaddr); if (conn) { - conn->out = true; + conn->out = 1; conn->link_mode |= HCI_LM_MASTER; } else BT_ERR("No memory for new connection"); @@ -1267,9 +1113,6 @@ static void hci_cs_auth_requested(struct hci_dev *hdev, __u8 status) BT_DBG("%s status 0x%x", hdev->name, status); - if (!status) - return; - cp = hci_sent_cmd_data(hdev, HCI_OP_AUTH_REQUESTED); if (!cp) return; @@ -1278,10 +1121,27 @@ static void hci_cs_auth_requested(struct hci_dev *hdev, __u8 status) conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle)); if (conn) { - if (conn->state == BT_CONFIG) { - hci_proto_connect_cfm(conn, status); - hci_conn_put(conn); + if (status) { + mgmt_auth_failed(hdev->id, &conn->dst, status); + clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); + + if (conn->state == BT_CONFIG) { + conn->state = BT_CONNECTED; + hci_proto_connect_cfm(conn, status); + hci_conn_put(conn); + } else { + hci_auth_cfm(conn, status); + hci_conn_hold(conn); + conn->disc_timeout = HCI_DISCONN_TIMEOUT; + hci_conn_put(conn); + } + + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { + clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); + hci_encrypt_cfm(conn, status, 0x00); + } } + conn->auth_initiator = 1; } hci_dev_unlock(hdev); @@ -1324,82 +1184,15 @@ static int hci_outgoing_auth_needed(struct hci_dev *hdev, return 0; /* Only request authentication for SSP connections or non-SSP - * devices with sec_level HIGH or if MITM protection is requested */ - if (!hci_conn_ssp_enabled(conn) && - conn->pending_sec_level != BT_SECURITY_HIGH && - !(conn->auth_type & 0x01)) + * devices with sec_level >= BT_SECURITY_MEDIUM*/ + BT_DBG("Pending sec level is %d", conn->pending_sec_level); + if (!(hdev->ssp_mode > 0 && conn->ssp_mode > 0) && + conn->pending_sec_level < BT_SECURITY_MEDIUM) return 0; return 1; } -static inline int hci_resolve_name(struct hci_dev *hdev, - struct inquiry_entry *e) -{ - struct hci_cp_remote_name_req cp; - - memset(&cp, 0, sizeof(cp)); - - bacpy(&cp.bdaddr, &e->data.bdaddr); - cp.pscan_rep_mode = e->data.pscan_rep_mode; - cp.pscan_mode = e->data.pscan_mode; - cp.clock_offset = e->data.clock_offset; - - return hci_send_cmd(hdev, HCI_OP_REMOTE_NAME_REQ, sizeof(cp), &cp); -} - -static bool hci_resolve_next_name(struct hci_dev *hdev) -{ - struct discovery_state *discov = &hdev->discovery; - struct inquiry_entry *e; - - if (list_empty(&discov->resolve)) - return false; - - e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, NAME_NEEDED); - if (hci_resolve_name(hdev, e) == 0) { - e->name_state = NAME_PENDING; - return true; - } - - return false; -} - -static void hci_check_pending_name(struct hci_dev *hdev, struct hci_conn *conn, - bdaddr_t *bdaddr, u8 *name, u8 name_len) -{ - struct discovery_state *discov = &hdev->discovery; - struct inquiry_entry *e; - - if (conn && !test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) - mgmt_device_connected(hdev, bdaddr, ACL_LINK, 0x00, 0, name, - name_len, conn->dev_class); - - if (discov->state == DISCOVERY_STOPPED) - return; - - if (discov->state == DISCOVERY_STOPPING) - goto discov_complete; - - if (discov->state != DISCOVERY_RESOLVING) - return; - - e = hci_inquiry_cache_lookup_resolve(hdev, bdaddr, NAME_PENDING); - if (e) { - e->name_state = NAME_KNOWN; - list_del(&e->list); - if (name) - mgmt_remote_name(hdev, bdaddr, ACL_LINK, 0x00, - e->data.rssi, name, name_len); - } - - if (hci_resolve_next_name(hdev)) - return; - -discov_complete: - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); -} - static void hci_cs_remote_name_req(struct hci_dev *hdev, __u8 status) { struct hci_cp_remote_name_req *cp; @@ -1419,23 +1212,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 (test_bit(HCI_MGMT, &hdev->dev_flags)) - hci_check_pending_name(hdev, conn, &cp->bdaddr, NULL, 0); - - if (!conn) - goto unlock; - - if (!hci_outgoing_auth_needed(hdev, conn)) - goto unlock; - - if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->flags)) { + 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); } @@ -1546,9 +1328,9 @@ static void hci_cs_sniff_mode(struct hci_dev *hdev, __u8 status) conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle)); if (conn) { - clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->flags); + clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend); - if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->flags)) + if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->pend)) hci_sco_setup(conn, status); } @@ -1573,41 +1355,20 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status) conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle)); if (conn) { - clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->flags); + clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend); - if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->flags)) + if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->pend)) hci_sco_setup(conn, status); } hci_dev_unlock(hdev); } -static void hci_cs_disconnect(struct hci_dev *hdev, u8 status) -{ - struct hci_cp_disconnect *cp; - struct hci_conn *conn; - - if (!status) - return; - - cp = hci_sent_cmd_data(hdev, HCI_OP_DISCONNECT); - if (!cp) - return; - - hci_dev_lock(hdev); - - conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle)); - if (conn) - mgmt_disconnect_failed(hdev, &conn->dst, conn->type, - conn->dst_type, status); - - hci_dev_unlock(hdev); -} - static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status) { struct hci_cp_le_create_conn *cp; struct hci_conn *conn; + unsigned long exp = msecs_to_jiffies(5000); BT_DBG("%s status 0x%x", hdev->name, status); @@ -1630,64 +1391,178 @@ 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->out = true; - } else { + conn = hci_le_conn_add(hdev, &cp->peer_addr, + cp->peer_addr_type); + if (conn) + conn->out = 1; + else BT_ERR("No memory for new connection"); - } + } else + exp = msecs_to_jiffies(conn->conn_timeout * 1000); + + if (conn && exp) + mod_timer(&conn->disc_timer, jiffies + exp); + } + + 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); } + } else if (chan) { + chan->state = BT_CONNECT2; } hci_dev_unlock(hdev); } -static void hci_cs_le_start_enc(struct hci_dev *hdev, u8 status) +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); + } + } else if (chan) + chan->state = BT_CONNECT2; + + hci_dev_unlock(hdev); } -static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +static void hci_cs_flow_spec_modify(struct hci_dev *hdev, __u8 status) { - __u8 status = *((__u8 *) skb->data); - struct discovery_state *discov = &hdev->discovery; - struct inquiry_entry *e; + struct hci_cp_flow_spec_modify *cp; + struct hci_chan *chan; - BT_DBG("%s status %d", hdev->name, status); + BT_DBG("%s status 0x%x", hdev->name, status); - hci_req_complete(hdev, HCI_OP_INQUIRY, status); + cp = hci_sent_cmd_data(hdev, HCI_OP_FLOW_SPEC_MODIFY); + if (!cp) + return; - hci_conn_check_pending(hdev); + 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 (!test_and_clear_bit(HCI_INQUIRY, &hdev->flags)) + if (!status) return; - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) + 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); - if (discov->state != DISCOVERY_FINDING) - goto unlock; + chan = hci_chan_list_lookup_handle(hdev, cp->log_handle); + if (chan) + hci_chan_del(chan); - if (list_empty(&discov->resolve)) { - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); - goto unlock; - } + hci_dev_unlock(hdev); +} - e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, NAME_NEEDED); - if (e && hci_resolve_name(hdev, e) == 0) { - e->name_state = NAME_PENDING; - hci_discovery_set_state(hdev, DISCOVERY_RESOLVING); - } else { - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); +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); } -unlock: 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); +} + +static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + __u8 status = *((__u8 *) skb->data); + + BT_DBG("%s status %d", hdev->name, status); + + if (!hdev->disco_state) + clear_bit(HCI_INQUIRY, &hdev->flags); + + hci_req_complete(hdev, HCI_OP_INQUIRY, status); + hci_dev_lock(hdev); + + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_inquiry_complete_evt(hdev->id, status); + hci_dev_unlock(hdev); + + if (!lmp_le_capable(hdev)) + hci_conn_check_pending(hdev); +} + static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct inquiry_data data; @@ -1702,8 +1577,6 @@ static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff * hci_dev_lock(hdev); for (; num_rsp; num_rsp--, info++) { - bool name_known, ssp; - bacpy(&data.bdaddr, &info->bdaddr); data.pscan_rep_mode = info->pscan_rep_mode; data.pscan_period_mode = info->pscan_period_mode; @@ -1712,11 +1585,9 @@ static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff * data.clock_offset = info->clock_offset; data.rssi = 0x00; data.ssp_mode = 0x00; - - name_known = hci_inquiry_cache_update(hdev, &data, false, &ssp); - mgmt_device_found(hdev, &info->bdaddr, ACL_LINK, 0x00, - info->dev_class, 0, !name_known, ssp, NULL, - 0); + hci_inquiry_cache_update(hdev, &data); + mgmt_device_found(hdev->id, &info->bdaddr, 0, 0, + info->dev_class, 0, 0, NULL); } hci_dev_unlock(hdev); @@ -1750,6 +1621,11 @@ static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *s conn->state = BT_CONFIG; hci_conn_hold(conn); conn->disc_timeout = HCI_DISCONN_TIMEOUT; + mgmt_connected(hdev->id, &ev->bdaddr, 0); + } else if (conn->type == LE_LINK) { + conn->state = BT_CONNECTED; + conn->disc_timeout = HCI_DISCONN_TIMEOUT; + mgmt_connected(hdev->id, &ev->bdaddr, 1); } else conn->state = BT_CONNECTED; @@ -1762,27 +1638,26 @@ static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *s if (test_bit(HCI_ENCRYPT, &hdev->flags)) conn->link_mode |= HCI_LM_ENCRYPT; - /* Get remote features */ + /* Get remote version */ if (conn->type == ACL_LINK) { - struct hci_cp_read_remote_features cp; + struct hci_cp_read_remote_version cp; cp.handle = ev->handle; - hci_send_cmd(hdev, HCI_OP_READ_REMOTE_FEATURES, - sizeof(cp), &cp); + hci_send_cmd(hdev, HCI_OP_READ_REMOTE_VERSION, + sizeof(cp), &cp); } /* Set packet type for incoming connection */ - if (!conn->out && hdev->hci_ver < BLUETOOTH_VER_2_0) { + if (!conn->out && hdev->hci_ver < 3) { struct hci_cp_change_conn_ptype cp; cp.handle = ev->handle; cp.pkt_type = cpu_to_le16(conn->pkt_type); - hci_send_cmd(hdev, HCI_OP_CHANGE_CONN_PTYPE, sizeof(cp), - &cp); + hci_send_cmd(hdev, HCI_OP_CHANGE_CONN_PTYPE, + sizeof(cp), &cp); } } else { conn->state = BT_CLOSED; - if (conn->type == ACL_LINK) - mgmt_connect_failed(hdev, &ev->bdaddr, conn->type, - conn->dst_type, ev->status); + if (conn->type == ACL_LINK || conn->type == LE_LINK) + mgmt_connect_failed(hdev->id, &ev->bdaddr, ev->status); } if (conn->type == ACL_LINK) @@ -1843,6 +1718,8 @@ static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *sk } memcpy(conn->dev_class, ev->dev_class, 3); + /* For incoming connection update remote class to userspace */ + mgmt_remote_class(hdev->id, &ev->bdaddr, ev->dev_class); conn->state = BT_CONNECT; hci_dev_unlock(hdev); @@ -1858,8 +1735,8 @@ static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *sk else cp.role = 0x01; /* Remain slave */ - hci_send_cmd(hdev, HCI_OP_ACCEPT_CONN_REQ, sizeof(cp), - &cp); + hci_send_cmd(hdev, HCI_OP_ACCEPT_CONN_REQ, + sizeof(cp), &cp); } else { struct hci_cp_accept_sync_conn_req cp; @@ -1868,19 +1745,19 @@ 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); + sizeof(cp), &cp); } } else { /* Connection rejected */ struct hci_cp_reject_conn_req cp; bacpy(&cp.bdaddr, &ev->bdaddr); - cp.reason = HCI_ERROR_REJ_BAD_ADDR; + cp.reason = 0x0f; hci_send_cmd(hdev, HCI_OP_REJECT_CONN_REQ, sizeof(cp), &cp); } } @@ -1892,31 +1769,29 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff BT_DBG("%s status %d", hdev->name, ev->status); + if (ev->status) { + hci_dev_lock(hdev); + mgmt_disconnect_failed(hdev->id); + hci_dev_unlock(hdev); + return; + } + hci_dev_lock(hdev); conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle)); if (!conn) goto unlock; - if (ev->status == 0) - conn->state = BT_CLOSED; + conn->state = BT_CLOSED; - if (test_and_clear_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags) && - (conn->type == ACL_LINK || conn->type == LE_LINK)) { - if (ev->status != 0) - mgmt_disconnect_failed(hdev, &conn->dst, conn->type, - conn->dst_type, ev->status); - else - mgmt_device_disconnected(hdev, &conn->dst, conn->type, - conn->dst_type); - } + if (conn->type == ACL_LINK || conn->type == LE_LINK) + mgmt_disconnected(hdev->id, &conn->dst); - if (ev->status == 0) { - if (conn->type == ACL_LINK && conn->flush_key) - hci_remove_link_key(hdev, &conn->dst); - hci_proto_disconn_cfm(conn, ev->reason); - hci_conn_del(conn); - } + if (conn->type == LE_LINK) + del_timer(&conn->smp_timer); + + hci_proto_disconn_cfm(conn, ev->reason, 0); + hci_conn_del(conn); unlock: hci_dev_unlock(hdev); @@ -1932,59 +1807,91 @@ 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; + hci_remove_link_key(hdev, &conn->dst); + cp.handle = cpu_to_le16(conn->handle); + /*Initiates dedicated bonding as pin or key is missing + on remote device*/ + /*In case if remote device is ssp supported, + reduce the security level to MEDIUM if it is HIGH*/ + if (conn->ssp_mode && conn->auth_initiator && + conn->io_capability != 0x03) { + conn->pending_sec_level = BT_SECURITY_HIGH; + conn->auth_type = HCI_AT_DEDICATED_BONDING_MITM; + } + 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 (!hci_conn_ssp_enabled(conn) && - test_bit(HCI_CONN_REAUTH_PEND, &conn->flags)) { - 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, &conn->dst, conn->type, conn->dst_type, - ev->status); - } - clear_bit(HCI_CONN_AUTH_PEND, &conn->flags); - clear_bit(HCI_CONN_REAUTH_PEND, &conn->flags); + clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); - if (conn->state == BT_CONFIG) { - if (!ev->status && hci_conn_ssp_enabled(conn)) { - 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->flags)) { - 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->flags); - hci_encrypt_cfm(conn, ev->status, 0x00); + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { + if (!ev->status) { + if (conn->link_mode & HCI_LM_ENCRYPT) { + /* Encryption implies authentication */ + conn->link_mode |= HCI_LM_AUTH; + conn->link_mode |= HCI_LM_ENCRYPT; + conn->sec_level = + conn->pending_sec_level; + clear_bit(HCI_CONN_ENCRYPT_PEND, + &conn->pend); + hci_encrypt_cfm(conn, ev->status, 1); + + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_encrypt_change(hdev->id, + &conn->dst, + ev->status); + + } else { + 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); } @@ -1999,31 +1906,16 @@ static inline void hci_remote_name_evt(struct hci_dev *hdev, struct sk_buff *skb hci_dev_lock(hdev); - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &ev->bdaddr); - - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) - goto check_auth; - - if (ev->status == 0) - hci_check_pending_name(hdev, conn, &ev->bdaddr, ev->name, - strnlen(ev->name, HCI_MAX_NAME_LENGTH)); - else - hci_check_pending_name(hdev, conn, &ev->bdaddr, NULL, 0); + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_remote_name(hdev->id, &ev->bdaddr, ev->status, ev->name); -check_auth: - if (!conn) - goto unlock; - - if (!hci_outgoing_auth_needed(hdev, conn)) - goto unlock; - - if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->flags)) { + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &ev->bdaddr); + 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); } @@ -2048,13 +1940,7 @@ static inline void hci_encrypt_change_evt(struct hci_dev *hdev, struct sk_buff * conn->link_mode &= ~HCI_LM_ENCRYPT; } - clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->flags); - - if (ev->status && conn->state == BT_CONNECTED) { - hci_acl_disconn(conn, 0x13); - hci_conn_put(conn); - goto unlock; - } + clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); if (conn->state == BT_CONFIG) { if (!ev->status) @@ -2062,11 +1948,30 @@ static inline void hci_encrypt_change_evt(struct hci_dev *hdev, struct sk_buff * hci_proto_connect_cfm(conn, ev->status); hci_conn_put(conn); - } else - hci_encrypt_cfm(conn, ev->status, ev->encrypt); + } else { + /* + * If the remote device does not support + * Pause Encryption, usually during the + * roleSwitch we see Encryption disable + * for short duration. Allow remote device + * to disable encryption + * for short duration in this case. + */ + if ((ev->encrypt == 0) && (ev->status == 0) && + ((conn->features[5] & LMP_PAUSE_ENC) == 0)) { + mod_timer(&conn->encrypt_pause_timer, + jiffies + msecs_to_jiffies(500)); + BT_INFO("enc pause timer, enc_pend_flag set"); + } else { + del_timer(&conn->encrypt_pause_timer); + hci_encrypt_cfm(conn, ev->status, ev->encrypt); + } + } + + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_encrypt_change(hdev->id, &conn->dst, ev->status); } -unlock: hci_dev_unlock(hdev); } @@ -2084,7 +1989,7 @@ static inline void hci_change_link_key_complete_evt(struct hci_dev *hdev, struct if (!ev->status) conn->link_mode |= HCI_LM_SECURE; - clear_bit(HCI_CONN_AUTH_PEND, &conn->flags); + clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); hci_key_change_cfm(conn, ev->status); } @@ -2105,8 +2010,10 @@ static inline void hci_remote_features_evt(struct hci_dev *hdev, struct sk_buff if (!conn) goto unlock; - if (!ev->status) + if (!ev->status) { memcpy(conn->features, ev->features, 8); + mgmt_remote_features(hdev->id, &conn->dst, ev->features); + } if (conn->state != BT_CONFIG) goto unlock; @@ -2118,18 +2025,18 @@ static inline void hci_remote_features_evt(struct hci_dev *hdev, struct sk_buff hci_send_cmd(hdev, HCI_OP_READ_REMOTE_EXT_FEATURES, sizeof(cp), &cp); goto unlock; + } else if (!(lmp_ssp_capable(conn)) && conn->auth_initiator && + (conn->pending_sec_level == BT_SECURITY_HIGH)) { + conn->pending_sec_level = BT_SECURITY_MEDIUM; } - if (!ev->status && !test_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) { + if (!ev->status) { struct hci_cp_remote_name_req cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.bdaddr, &conn->dst); cp.pscan_rep_mode = 0x02; hci_send_cmd(hdev, HCI_OP_REMOTE_NAME_REQ, sizeof(cp), &cp); - } else if (!test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) - mgmt_device_connected(hdev, &conn->dst, conn->type, - conn->dst_type, 0, NULL, 0, - conn->dev_class); + } if (!hci_outgoing_auth_needed(hdev, conn)) { conn->state = BT_CONNECTED; @@ -2143,7 +2050,24 @@ static inline void hci_remote_features_evt(struct hci_dev *hdev, struct sk_buff static inline void hci_remote_version_evt(struct hci_dev *hdev, struct sk_buff *skb) { - BT_DBG("%s", hdev->name); + struct hci_ev_remote_version *ev = (void *) skb->data; + struct hci_cp_read_remote_features cp; + struct hci_conn *conn; + BT_DBG("%s status %d", hdev->name, ev->status); + + hci_dev_lock(hdev); + cp.handle = ev->handle; + hci_send_cmd(hdev, HCI_OP_READ_REMOTE_FEATURES, + sizeof(cp), &cp); + + conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle)); + if (!conn) + goto unlock; + if (!ev->status) + mgmt_remote_version(hdev->id, &conn->dst, ev->lmp_ver, + ev->manufacturer, ev->lmp_subver); +unlock: + hci_dev_unlock(hdev); } static inline void hci_qos_setup_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) @@ -2169,6 +2093,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; @@ -2237,6 +2165,10 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_host_buffer_size(hdev, skb); break; + case HCI_OP_READ_SSP_MODE: + hci_cc_read_ssp_mode(hdev, skb); + break; + case HCI_OP_WRITE_SSP_MODE: hci_cc_write_ssp_mode(hdev, skb); break; @@ -2253,10 +2185,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; @@ -2265,10 +2193,6 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_read_bd_addr(hdev, skb); break; - case HCI_OP_READ_DATA_BLOCK_SIZE: - hci_cc_read_data_block_size(hdev, skb); - break; - case HCI_OP_WRITE_CA_TIMEOUT: hci_cc_write_ca_timeout(hdev, skb); break; @@ -2277,10 +2201,19 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk 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; @@ -2317,6 +2250,10 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_le_read_buffer_size(hdev, skb); break; + case HCI_OP_READ_RSSI: + hci_cc_read_rssi(hdev, skb); + break; + case HCI_OP_USER_CONFIRM_REPLY: hci_cc_user_confirm_reply(hdev, skb); break; @@ -2325,22 +2262,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_USER_PASSKEY_REPLY: - hci_cc_user_passkey_reply(hdev, skb); - break; - - case HCI_OP_USER_PASSKEY_NEG_REPLY: - hci_cc_user_passkey_neg_reply(hdev, skb); - break; - - case HCI_OP_LE_SET_SCAN_PARAM: - hci_cc_le_set_scan_param(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; @@ -2349,8 +2270,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: @@ -2364,7 +2285,7 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk if (ev->ncmd) { atomic_set(&hdev->cmd_cnt, 1); if (!skb_queue_empty(&hdev->cmd_q)) - queue_work(hdev->workqueue, &hdev->cmd_work); + tasklet_schedule(&hdev->cmd_task); } } @@ -2410,20 +2331,45 @@ static inline void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_cs_read_remote_ext_features(hdev, ev->status); break; - case HCI_OP_SETUP_SYNC_CONN: - hci_cs_setup_sync_conn(hdev, ev->status); + case HCI_OP_SETUP_SYNC_CONN: + hci_cs_setup_sync_conn(hdev, ev->status); + break; + + case HCI_OP_SNIFF_MODE: + hci_cs_sniff_mode(hdev, ev->status); + break; + + case HCI_OP_EXIT_SNIFF_MODE: + 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_SNIFF_MODE: - hci_cs_sniff_mode(hdev, ev->status); + case HCI_OP_FLOW_SPEC_MODIFY: + hci_cs_flow_spec_modify(hdev, ev->status); break; - case HCI_OP_EXIT_SNIFF_MODE: - hci_cs_exit_sniff_mode(hdev, ev->status); + 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: - hci_cs_disconnect(hdev, ev->status); + if (ev->status != 0) + mgmt_disconnect_failed(hdev->id); break; case HCI_OP_LE_CREATE_CONN: @@ -2445,7 +2391,7 @@ static inline void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb) if (ev->ncmd && !test_bit(HCI_RESET, &hdev->flags)) { atomic_set(&hdev->cmd_cnt, 1); if (!skb_queue_empty(&hdev->cmd_q)) - queue_work(hdev->workqueue, &hdev->cmd_work); + tasklet_schedule(&hdev->cmd_task); } } @@ -2467,7 +2413,7 @@ static inline void hci_role_change_evt(struct hci_dev *hdev, struct sk_buff *skb conn->link_mode |= HCI_LM_MASTER; } - clear_bit(HCI_CONN_RSWITCH_PEND, &conn->flags); + clear_bit(HCI_CONN_RSWITCH_PEND, &conn->pend); hci_role_switch_cfm(conn, ev->status, ev->role); } @@ -2478,117 +2424,125 @@ static inline void hci_role_change_evt(struct hci_dev *hdev, struct sk_buff *skb static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_num_comp_pkts *ev = (void *) skb->data; + __le16 *ptr; int i; - if (hdev->flow_ctl_mode != HCI_FLOW_CTL_MODE_PACKET_BASED) { - BT_ERR("Wrong event for mode %d", hdev->flow_ctl_mode); - return; - } + skb_pull(skb, sizeof(*ev)); + + BT_DBG("%s num_hndl %d", hdev->name, ev->num_hndl); - if (skb->len < sizeof(*ev) || skb->len < sizeof(*ev) + - ev->num_hndl * sizeof(struct hci_comp_pkts_info)) { + if (skb->len < ev->num_hndl * 4) { BT_DBG("%s bad parameters", hdev->name); return; } - BT_DBG("%s num_hndl %d", hdev->name, ev->num_hndl); + tasklet_disable(&hdev->tx_task); - for (i = 0; i < ev->num_hndl; i++) { - struct hci_comp_pkts_info *info = &ev->handles[i]; - struct hci_conn *conn; + for (i = 0, ptr = (__le16 *) skb->data; i < ev->num_hndl; i++) { + struct hci_conn *conn = NULL; + struct hci_chan *chan; __u16 handle, count; - handle = __le16_to_cpu(info->handle); - count = __le16_to_cpu(info->count); + handle = get_unaligned_le16(ptr++); + count = get_unaligned_le16(ptr++); - conn = hci_conn_hash_lookup_handle(hdev, handle); - if (!conn) - continue; - - conn->sent -= count; - - switch (conn->type) { - case ACL_LINK: - hdev->acl_cnt += count; - if (hdev->acl_cnt > hdev->acl_pkts) - hdev->acl_cnt = hdev->acl_pkts; - break; - - case LE_LINK: - if (hdev->le_pkts) { - hdev->le_cnt += count; - if (hdev->le_cnt > hdev->le_pkts) - hdev->le_cnt = hdev->le_pkts; - } else { + 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; + + if (conn->type == ACL_LINK) { hdev->acl_cnt += count; if (hdev->acl_cnt > hdev->acl_pkts) hdev->acl_cnt = hdev->acl_pkts; + } else if (conn->type == LE_LINK) { + if (hdev->le_pkts) { + hdev->le_cnt += count; + if (hdev->le_cnt > hdev->le_pkts) + hdev->le_cnt = hdev->le_pkts; + } else { + hdev->acl_cnt += count; + if (hdev->acl_cnt > hdev->acl_pkts) + hdev->acl_cnt = hdev->acl_pkts; + } + } else { + hdev->sco_cnt += count; + if (hdev->sco_cnt > hdev->sco_pkts) + hdev->sco_cnt = hdev->sco_pkts; } - break; - - case SCO_LINK: - hdev->sco_cnt += count; - if (hdev->sco_cnt > hdev->sco_pkts) - hdev->sco_cnt = hdev->sco_pkts; - break; - - default: - BT_ERR("Unknown type %d conn %p", conn->type, conn); - break; } } - queue_work(hdev->workqueue, &hdev->tx_work); + tasklet_schedule(&hdev->tx_task); + + tasklet_enable(&hdev->tx_task); } static inline void hci_num_comp_blocks_evt(struct hci_dev *hdev, - struct sk_buff *skb) + struct sk_buff *skb) { struct hci_ev_num_comp_blocks *ev = (void *) skb->data; + __le16 *ptr; int i; - if (hdev->flow_ctl_mode != HCI_FLOW_CTL_MODE_BLOCK_BASED) { - BT_ERR("Wrong event for mode %d", hdev->flow_ctl_mode); - return; - } + 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 < sizeof(*ev) || skb->len < sizeof(*ev) + - ev->num_hndl * sizeof(struct hci_comp_blocks_info)) { + if (skb->len < ev->num_hndl * 6) { BT_DBG("%s bad parameters", hdev->name); return; } - BT_DBG("%s num_blocks %d num_hndl %d", hdev->name, ev->num_blocks, - ev->num_hndl); + tasklet_disable(&hdev->tx_task); - for (i = 0; i < ev->num_hndl; i++) { - struct hci_comp_blocks_info *info = &ev->handles[i]; - struct hci_conn *conn; + 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 = __le16_to_cpu(info->handle); - block_count = __le16_to_cpu(info->blocks); + handle = get_unaligned_le16(ptr++); - conn = hci_conn_hash_lookup_handle(hdev, handle); - if (!conn) - continue; + /* Skip packet count */ + ptr++; + block_count = get_unaligned_le16(ptr++); - conn->sent -= block_count; + 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); - switch (conn->type) { - case ACL_LINK: - hdev->block_cnt += block_count; - if (hdev->block_cnt > hdev->num_blocks) - hdev->block_cnt = hdev->num_blocks; - break; + conn->sent -= block_count; - default: - BT_ERR("Unknown type %d conn %p", conn->type, conn); - break; + 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"); + } } } - queue_work(hdev->workqueue, &hdev->tx_work); + 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) @@ -2605,14 +2559,17 @@ static inline void hci_mode_change_evt(struct hci_dev *hdev, struct sk_buff *skb conn->mode = ev->mode; conn->interval = __le16_to_cpu(ev->interval); - if (!test_and_clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->flags)) { + if (!test_and_clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) { if (conn->mode == HCI_CM_ACTIVE) - set_bit(HCI_CONN_POWER_SAVE, &conn->flags); + conn->power_save = 1; else - clear_bit(HCI_CONN_POWER_SAVE, &conn->flags); + conn->power_save = 0; } + if (conn->mode == HCI_CM_SNIFF) + if (wake_lock_active(&conn->idle_lock)) + wake_unlock(&conn->idle_lock); - if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->flags)) + if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->pend)) hci_sco_setup(conn, ev->status); } @@ -2629,30 +2586,20 @@ static inline void hci_pin_code_request_evt(struct hci_dev *hdev, struct sk_buff hci_dev_lock(hdev); conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &ev->bdaddr); - if (!conn) - goto unlock; - - if (conn->state == BT_CONNECTED) { + if (conn && conn->state == BT_CONNECTED) { hci_conn_hold(conn); conn->disc_timeout = HCI_PAIRING_TIMEOUT; hci_conn_put(conn); + hci_conn_enter_active_mode(conn, 0); } - if (!test_bit(HCI_PAIRABLE, &hdev->dev_flags)) + 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->dev_flags)) { - u8 secure; - if (conn->pending_sec_level == BT_SECURITY_HIGH) - secure = 1; - else - secure = 0; - - mgmt_pin_code_request(hdev, &ev->bdaddr, secure); - } + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_pin_code_request(hdev->id, &ev->bdaddr); -unlock: hci_dev_unlock(hdev); } @@ -2665,7 +2612,7 @@ static inline void hci_link_key_request_evt(struct hci_dev *hdev, struct sk_buff BT_DBG("%s", hdev->name); - if (!test_bit(HCI_LINK_KEYS, &hdev->dev_flags)) + if (!test_bit(HCI_LINK_KEYS, &hdev->flags)) return; hci_dev_lock(hdev); @@ -2677,33 +2624,31 @@ static inline void hci_link_key_request_evt(struct hci_dev *hdev, struct sk_buff goto not_found; } - BT_DBG("%s found key type %u for %s", hdev->name, key->type, + BT_DBG("%s found key type %u for %s", hdev->name, key->key_type, batostr(&ev->bdaddr)); - if (!test_bit(HCI_DEBUG_KEYS, &hdev->dev_flags) && - key->type == HCI_LK_DEBUG_COMBINATION) { + if (!test_bit(HCI_DEBUG_KEYS, &hdev->flags) && key->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; - } + if (conn) { + BT_DBG("Conn pending sec level is %d, ssp is %d, key len is %d", + conn->pending_sec_level, conn->ssp_mode, key->pin_len); + } + if (conn && (conn->ssp_mode == 0) && + (conn->pending_sec_level == BT_SECURITY_HIGH) && + (key->pin_len != 16)) { + BT_DBG("Security is high ignoring this key"); + goto not_found; + } - conn->key_type = key->type; - conn->pin_length = key->pin_len; + if (key->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); @@ -2726,7 +2671,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); @@ -2734,16 +2679,19 @@ 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); + conn->link_mode &= ~HCI_LM_ENCRYPT; + pin_len = conn->pin_length; hci_conn_put(conn); + hci_conn_enter_active_mode(conn, 0); } - if (test_bit(HCI_LINK_KEYS, &hdev->dev_flags)) - hci_add_link_key(hdev, conn, 1, &ev->bdaddr, ev->link_key, + if (test_bit(HCI_LINK_KEYS, &hdev->flags)) + hci_add_link_key(hdev, 1, &ev->bdaddr, ev->link_key, ev->key_type, pin_len); hci_dev_unlock(hdev); @@ -2810,7 +2758,6 @@ static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct { struct inquiry_data data; int num_rsp = *((__u8 *) skb->data); - bool name_known, ssp; BT_DBG("%s num_rsp %d", hdev->name, num_rsp); @@ -2832,12 +2779,10 @@ static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct data.clock_offset = info->clock_offset; data.rssi = info->rssi; data.ssp_mode = 0x00; - - name_known = hci_inquiry_cache_update(hdev, &data, - false, &ssp); - mgmt_device_found(hdev, &info->bdaddr, ACL_LINK, 0x00, - info->dev_class, info->rssi, - !name_known, ssp, NULL, 0); + hci_inquiry_cache_update(hdev, &data); + mgmt_device_found(hdev->id, &info->bdaddr, 0, 0, + info->dev_class, info->rssi, + 0, NULL); } } else { struct inquiry_info_with_rssi *info = (void *) (skb->data + 1); @@ -2851,11 +2796,10 @@ static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct data.clock_offset = info->clock_offset; data.rssi = info->rssi; data.ssp_mode = 0x00; - name_known = hci_inquiry_cache_update(hdev, &data, - false, &ssp); - mgmt_device_found(hdev, &info->bdaddr, ACL_LINK, 0x00, - info->dev_class, info->rssi, - !name_known, ssp, NULL, 0); + hci_inquiry_cache_update(hdev, &data); + mgmt_device_found(hdev->id, &info->bdaddr, 0, 0, + info->dev_class, info->rssi, + 0, NULL); } } @@ -2880,25 +2824,32 @@ static inline void hci_remote_ext_features_evt(struct hci_dev *hdev, struct sk_b ie = hci_inquiry_cache_lookup(hdev, &conn->dst); if (ie) - ie->data.ssp_mode = (ev->features[0] & LMP_HOST_SSP); - - if (ev->features[0] & LMP_HOST_SSP) - set_bit(HCI_CONN_SSP_ENABLED, &conn->flags); + ie->data.ssp_mode = (ev->features[0] & 0x01); + + conn->ssp_mode = (ev->features[0] & 0x01); + /*In case if remote device ssp supported/2.0 device + reduce the security level to MEDIUM if it is HIGH*/ + if (!conn->ssp_mode && conn->auth_initiator && + (conn->pending_sec_level == BT_SECURITY_HIGH)) + conn->pending_sec_level = BT_SECURITY_MEDIUM; + + if (conn->ssp_mode && conn->auth_initiator && + conn->io_capability != 0x03) { + conn->pending_sec_level = BT_SECURITY_HIGH; + conn->auth_type = HCI_AT_DEDICATED_BONDING_MITM; + } } if (conn->state != BT_CONFIG) goto unlock; - if (!ev->status && !test_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) { + if (!ev->status) { struct hci_cp_remote_name_req cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.bdaddr, &conn->dst); cp.pscan_rep_mode = 0x02; hci_send_cmd(hdev, HCI_OP_REMOTE_NAME_REQ, sizeof(cp), &cp); - } else if (!test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) - mgmt_device_connected(hdev, &conn->dst, conn->type, - conn->dst_type, 0, NULL, 0, - conn->dev_class); + } if (!hci_outgoing_auth_needed(hdev, conn)) { conn->state = BT_CONNECTED; @@ -2940,13 +2891,14 @@ 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 */ case 0x1f: /* Unspecified error */ if (conn->out && conn->attempt < 2) { - conn->pkt_type = (hdev->esco_type & SCO_ESCO_MASK) | + if (!conn->hdev->is_wbs) + conn->pkt_type = + (hdev->esco_type & SCO_ESCO_MASK) | (hdev->esco_type & EDR_ESCO_MASK); hci_setup_sync(conn, conn->link->handle); goto unlock; @@ -2992,8 +2944,6 @@ static inline void hci_extended_inquiry_result_evt(struct hci_dev *hdev, struct hci_dev_lock(hdev); for (; num_rsp; num_rsp--, info++) { - bool name_known, ssp; - bacpy(&data.bdaddr, &info->bdaddr); data.pscan_rep_mode = info->pscan_rep_mode; data.pscan_period_mode = info->pscan_period_mode; @@ -3002,19 +2952,10 @@ static inline void hci_extended_inquiry_result_evt(struct hci_dev *hdev, struct data.clock_offset = info->clock_offset; data.rssi = info->rssi; data.ssp_mode = 0x01; - - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - name_known = eir_has_data_type(info->data, - sizeof(info->data), - EIR_NAME_COMPLETE); - else - name_known = true; - - name_known = hci_inquiry_cache_update(hdev, &data, name_known, - &ssp); - mgmt_device_found(hdev, &info->bdaddr, ACL_LINK, 0x00, - info->dev_class, info->rssi, !name_known, - ssp, info->data, sizeof(info->data)); + hci_inquiry_cache_update(hdev, &data); + mgmt_device_found(hdev->id, &info->bdaddr, 0, 0, + info->dev_class, info->rssi, + HCI_MAX_EIR_LENGTH, info->data); } hci_dev_unlock(hdev); @@ -3022,19 +2963,23 @@ static inline void hci_extended_inquiry_result_evt(struct hci_dev *hdev, struct static inline u8 hci_get_auth_req(struct hci_conn *conn) { + BT_DBG("%p", conn); + /* If remote requests dedicated bonding follow that lead */ if (conn->remote_auth == 0x02 || conn->remote_auth == 0x03) { /* If both remote and local IO capabilities allow MITM * protection then require it, otherwise don't */ - if (conn->remote_cap == 0x03 || conn->io_capability == 0x03) + if (conn->remote_cap == 0x03 || conn->io_capability == 0x03) { return 0x02; - else + } else { + conn->auth_type |= 0x01; return 0x03; + } } /* 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); + if (conn->remote_auth <= 0x01) + return 0x00; return conn->auth_type; } @@ -3054,22 +2999,23 @@ static inline void hci_io_capa_request_evt(struct hci_dev *hdev, struct sk_buff hci_conn_hold(conn); - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) + if (!test_bit(HCI_MGMT, &hdev->flags)) goto unlock; - if (test_bit(HCI_PAIRABLE, &hdev->dev_flags) || + if (test_bit(HCI_PAIRABLE, &hdev->flags) || (conn->remote_auth & ~0x01) == HCI_AT_NO_BONDING) { struct hci_cp_io_capability_reply cp; + u8 io_cap = conn->io_capability; + /* ACL-SSP does not support IO CAP 0x04 */ + cp.capability = (io_cap == 0x04) ? 0x01 : io_cap; bacpy(&cp.bdaddr, &ev->bdaddr); - /* Change the IO capability from KeyboardDisplay - * to DisplayYesNo as it is not supported by BT spec. */ - cp.capability = (conn->io_capability == 0x04) ? - 0x01 : conn->io_capability; - conn->auth_type = hci_get_auth_req(conn); - cp.authentication = conn->auth_type; - - if ((conn->out || test_bit(HCI_CONN_REMOTE_OOB, &conn->flags)) && + if (conn->auth_initiator) + cp.authentication = conn->auth_type; + else + cp.authentication = hci_get_auth_req(conn); + + if ((conn->out == 0x01 || conn->remote_oob == 0x01) && hci_find_remote_oob_data(hdev, &conn->dst)) cp.oob_data = 0x01; else @@ -3081,7 +3027,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 = HCI_ERROR_PAIRING_NOT_ALLOWED; + cp.reason = 0x16; /* Pairing not allowed */ hci_send_cmd(hdev, HCI_OP_IO_CAPABILITY_NEG_REPLY, sizeof(cp), &cp); @@ -3105,94 +3051,31 @@ static inline void hci_io_capa_reply_evt(struct hci_dev *hdev, struct sk_buff *s goto unlock; conn->remote_cap = ev->capability; + conn->remote_oob = ev->oob_data; conn->remote_auth = ev->authentication; - if (ev->oob_data) - set_bit(HCI_CONN_REMOTE_OOB, &conn->flags); unlock: hci_dev_unlock(hdev); } -static inline void hci_user_confirm_request_evt(struct hci_dev *hdev, - struct sk_buff *skb) +static inline void hci_user_ssp_confirmation_evt(struct hci_dev *hdev, + u8 event, 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->dev_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->flags)) { - 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; + if (test_bit(HCI_MGMT, &hdev->flags)) { + if (event == HCI_EV_USER_PASSKEY_REQUEST) + mgmt_user_confirm_request(hdev->id, event, + &ev->bdaddr, 0); + else + mgmt_user_confirm_request(hdev->id, event, + &ev->bdaddr, ev->passkey); } -confirm: - mgmt_user_confirm_request(hdev, &ev->bdaddr, ACL_LINK, 0, ev->passkey, - confirm_hint); - -unlock: - hci_dev_unlock(hdev); -} - -static inline void hci_user_passkey_request_evt(struct hci_dev *hdev, - struct sk_buff *skb) -{ - struct hci_ev_user_passkey_req *ev = (void *) skb->data; - - BT_DBG("%s", hdev->name); - - hci_dev_lock(hdev); - - if (test_bit(HCI_MGMT, &hdev->dev_flags)) - mgmt_user_passkey_request(hdev, &ev->bdaddr, ACL_LINK, 0); - hci_dev_unlock(hdev); } @@ -3214,9 +3097,8 @@ static inline void hci_simple_pair_complete_evt(struct hci_dev *hdev, struct sk_ * initiated the authentication. A traditional auth_complete * event gets always produced as initiator and is also mapped to * the mgmt_auth_failed event */ - if (!test_bit(HCI_CONN_AUTH_PEND, &conn->flags) && ev->status != 0) - mgmt_auth_failed(hdev, &conn->dst, conn->type, conn->dst_type, - ev->status); + if (!test_bit(HCI_CONN_AUTH_PEND, &conn->pend) && ev->status != 0) + mgmt_auth_failed(hdev->id, &conn->dst, ev->status); hci_conn_put(conn); @@ -3235,13 +3117,13 @@ static inline void hci_remote_host_features_evt(struct hci_dev *hdev, struct sk_ ie = hci_inquiry_cache_lookup(hdev, &ev->bdaddr); if (ie) - ie->data.ssp_mode = (ev->features[0] & LMP_HOST_SSP); + ie->data.ssp_mode = (ev->features[0] & 0x01); hci_dev_unlock(hdev); } static inline void hci_remote_oob_data_request_evt(struct hci_dev *hdev, - struct sk_buff *skb) + struct sk_buff *skb) { struct hci_ev_remote_oob_data_request *ev = (void *) skb->data; struct oob_data *data; @@ -3250,7 +3132,7 @@ static inline void hci_remote_oob_data_request_evt(struct hci_dev *hdev, hci_dev_lock(hdev); - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) + if (!test_bit(HCI_MGMT, &hdev->flags)) goto unlock; data = hci_find_remote_oob_data(hdev, &ev->bdaddr); @@ -3286,33 +3168,28 @@ 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, &ev->bdaddr, conn->type, - conn->dst_type, ev->status); hci_proto_connect_cfm(conn, ev->status); conn->state = BT_CLOSED; hci_conn_del(conn); goto unlock; } - if (!test_and_set_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) - mgmt_device_connected(hdev, &ev->bdaddr, conn->type, - conn->dst_type, 0, NULL, 0, NULL); - conn->sec_level = BT_SECURITY_LOW; conn->handle = __le16_to_cpu(ev->handle); conn->state = BT_CONNECTED; + conn->disc_timeout = HCI_DISCONN_TIMEOUT; + mgmt_connected(hdev->id, &ev->bdaddr, 1); + hci_conn_hold(conn); hci_conn_hold_device(conn); hci_conn_add_sysfs(conn); @@ -3322,30 +3199,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) -{ - u8 num_reports = skb->data[0]; - void *ptr = &skb->data[1]; - s8 rssi; - - hci_dev_lock(hdev); - - while (num_reports--) { - struct hci_ev_le_advertising_info *ev = ptr; - - hci_add_adv_entry(hdev, ev); - - rssi = ev->data[ev->length]; - mgmt_device_found(hdev, &ev->bdaddr, LE_LINK, ev->bdaddr_type, - NULL, rssi, 0, 1, ev->data, ev->length); - - ptr += sizeof(*ev) + ev->length + 1; - } - - hci_dev_unlock(hdev); -} - static inline void hci_le_ltk_request_evt(struct hci_dev *hdev, struct sk_buff *skb) { @@ -3353,7 +3206,7 @@ static inline void hci_le_ltk_request_evt(struct hci_dev *hdev, struct hci_cp_le_ltk_reply cp; struct hci_cp_le_ltk_neg_reply neg; struct hci_conn *conn; - struct smp_ltk *ltk; + struct link_key *ltk; BT_DBG("%s handle %d", hdev->name, cpu_to_le16(ev->handle)); @@ -3369,17 +3222,10 @@ static inline void hci_le_ltk_request_evt(struct hci_dev *hdev, memcpy(cp.ltk, ltk->val, sizeof(ltk->val)); cp.handle = cpu_to_le16(conn->handle); - - if (ltk->authenticated) - conn->sec_level = BT_SECURITY_HIGH; + conn->pin_length = ltk->pin_len; hci_send_cmd(hdev, HCI_OP_LE_LTK_REPLY, sizeof(cp), &cp); - if (ltk->type & HCI_SMP_STK) { - list_del(<k->list); - kfree(ltk); - } - hci_dev_unlock(hdev); return; @@ -3390,6 +3236,27 @@ 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_dev_lock(hdev); + + while (num_reports--) { + mgmt_device_found(hdev->id, &ev->bdaddr, ev->bdaddr_type, + 1, NULL, 0, ev->length, ev->data); + hci_add_adv_entry(hdev, ev); + ev = (void *) (ev->data + ev->length + 1); + } + + hci_dev_unlock(hdev); +} + 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; @@ -3401,24 +3268,146 @@ 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 (chan) { + if (ev->status == 0) { + chan->ll_handle = __le16_to_cpu(ev->log_handle); + chan->state = BT_CONNECTED; + } else { + chan->state = BT_CLOSED; + } + + hci_proto_create_cfm(chan, ev->status); + } + + 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_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, 0); + 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; __u8 event = hdr->evt; + BT_DBG(""); + skb_pull(skb, HCI_EVENT_HDR_SIZE); switch (event) { @@ -3546,12 +3535,10 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) hci_io_capa_reply_evt(hdev, skb); break; - case HCI_EV_USER_CONFIRM_REQUEST: - hci_user_confirm_request_evt(hdev, skb); - break; - case HCI_EV_USER_PASSKEY_REQUEST: - hci_user_passkey_request_evt(hdev, skb); + case HCI_EV_USER_PASSKEY_NOTIFICATION: + case HCI_EV_USER_CONFIRM_REQUEST: + hci_user_ssp_confirmation_evt(hdev, event, skb); break; case HCI_EV_SIMPLE_PAIR_COMPLETE: @@ -3570,10 +3557,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; @@ -3582,3 +3599,31 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) kfree_skb(skb); hdev->stat.evt_rx++; } + +/* Generate internal stack event */ +void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data) +{ + struct hci_event_hdr *hdr; + struct hci_ev_stack_internal *ev; + struct sk_buff *skb; + + skb = bt_skb_alloc(HCI_EVENT_HDR_SIZE + sizeof(*ev) + dlen, GFP_ATOMIC); + if (!skb) + return; + + hdr = (void *) skb_put(skb, HCI_EVENT_HDR_SIZE); + hdr->evt = HCI_EV_STACK_INTERNAL; + hdr->plen = sizeof(*ev) + dlen; + + ev = (void *) skb_put(skb, sizeof(*ev) + dlen); + ev->type = type; + memcpy(ev->data, data, dlen); + + bt_cb(skb)->incoming = 1; + __net_timestamp(skb); + + bt_cb(skb)->pkt_type = HCI_EVENT_PKT; + skb->dev = (void *) hdev; + hci_send_to_sock(hdev, skb, NULL); + kfree_skb(skb); +} diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 5914623f426aa835aadee48c6ec8eb8400ab7728..61c2ca12e1ae5680fbab86db23c3b2a78a4a5f33 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 @@ -42,14 +42,14 @@ #include #include +#include #include #include #include #include -#include -static atomic_t monitor_promisc = ATOMIC_INIT(0); +static bool enable_mgmt = 1; /* ----- HCI socket interface ----- */ @@ -85,20 +85,22 @@ static struct bt_sock_list hci_sk_list = { }; /* Send frame to RAW socket */ -void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb) +void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb, + struct sock *skip_sk) { struct sock *sk; struct hlist_node *node; - struct sk_buff *skb_copy = NULL; BT_DBG("hdev %p len %d", hdev, skb->len); read_lock(&hci_sk_list.lock); - sk_for_each(sk, node, &hci_sk_list.head) { struct hci_filter *flt; struct sk_buff *nskb; + if (sk == skip_sk) + continue; + if (sk->sk_state != BT_BOUND || hci_pi(sk)->hdev != hdev) continue; @@ -106,9 +108,12 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb) if (skb->sk == sk) continue; - if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) + if (bt_cb(skb)->channel != hci_pi(sk)->channel) continue; + if (bt_cb(skb)->channel == HCI_CHANNEL_CONTROL) + goto clone; + /* Apply filter */ flt = &hci_pi(sk)->filter; @@ -132,303 +137,21 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb) continue; } - if (!skb_copy) { - /* Create a private copy with headroom */ - skb_copy = __pskb_copy(skb, 1, GFP_ATOMIC); - if (!skb_copy) - continue; - - /* Put type byte before the data */ - memcpy(skb_push(skb_copy, 1), &bt_cb(skb)->pkt_type, 1); - } - - nskb = skb_clone(skb_copy, GFP_ATOMIC); - if (!nskb) - continue; - - if (sock_queue_rcv_skb(sk, nskb)) - kfree_skb(nskb); - } - - read_unlock(&hci_sk_list.lock); - - kfree_skb(skb_copy); -} - -/* Send frame to control socket */ -void hci_send_to_control(struct sk_buff *skb, struct sock *skip_sk) -{ - struct sock *sk; - struct hlist_node *node; - - BT_DBG("len %d", skb->len); - - read_lock(&hci_sk_list.lock); - - sk_for_each(sk, node, &hci_sk_list.head) { - struct sk_buff *nskb; - - /* Skip the original socket */ - if (sk == skip_sk) - continue; - - if (sk->sk_state != BT_BOUND) - continue; - - if (hci_pi(sk)->channel != HCI_CHANNEL_CONTROL) - continue; - +clone: nskb = skb_clone(skb, GFP_ATOMIC); if (!nskb) continue; - if (sock_queue_rcv_skb(sk, nskb)) - kfree_skb(nskb); - } - - read_unlock(&hci_sk_list.lock); -} - -/* Send frame to monitor socket */ -void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb) -{ - struct sock *sk; - struct hlist_node *node; - struct sk_buff *skb_copy = NULL; - __le16 opcode; - - if (!atomic_read(&monitor_promisc)) - return; - - BT_DBG("hdev %p len %d", hdev, skb->len); - - switch (bt_cb(skb)->pkt_type) { - case HCI_COMMAND_PKT: - opcode = __constant_cpu_to_le16(HCI_MON_COMMAND_PKT); - break; - case HCI_EVENT_PKT: - opcode = __constant_cpu_to_le16(HCI_MON_EVENT_PKT); - break; - case HCI_ACLDATA_PKT: - if (bt_cb(skb)->incoming) - opcode = __constant_cpu_to_le16(HCI_MON_ACL_RX_PKT); - else - opcode = __constant_cpu_to_le16(HCI_MON_ACL_TX_PKT); - break; - case HCI_SCODATA_PKT: - if (bt_cb(skb)->incoming) - opcode = __constant_cpu_to_le16(HCI_MON_SCO_RX_PKT); - else - opcode = __constant_cpu_to_le16(HCI_MON_SCO_TX_PKT); - break; - default: - return; - } - - read_lock(&hci_sk_list.lock); - - sk_for_each(sk, node, &hci_sk_list.head) { - struct sk_buff *nskb; - - if (sk->sk_state != BT_BOUND) - continue; - - if (hci_pi(sk)->channel != HCI_CHANNEL_MONITOR) - continue; - - if (!skb_copy) { - struct hci_mon_hdr *hdr; - - /* Create a private copy with headroom */ - skb_copy = __pskb_copy(skb, HCI_MON_HDR_SIZE, GFP_ATOMIC); - if (!skb_copy) - continue; - - /* Put header before the data */ - hdr = (void *) skb_push(skb_copy, HCI_MON_HDR_SIZE); - hdr->opcode = opcode; - hdr->index = cpu_to_le16(hdev->id); - hdr->len = cpu_to_le16(skb->len); - } - - nskb = skb_clone(skb_copy, GFP_ATOMIC); - if (!nskb) - continue; - - if (sock_queue_rcv_skb(sk, nskb)) - kfree_skb(nskb); - } - - read_unlock(&hci_sk_list.lock); - - kfree_skb(skb_copy); -} - -static void send_monitor_event(struct sk_buff *skb) -{ - struct sock *sk; - struct hlist_node *node; - - BT_DBG("len %d", skb->len); - - read_lock(&hci_sk_list.lock); - - sk_for_each(sk, node, &hci_sk_list.head) { - struct sk_buff *nskb; - - if (sk->sk_state != BT_BOUND) - continue; - - if (hci_pi(sk)->channel != HCI_CHANNEL_MONITOR) - continue; - - nskb = skb_clone(skb, GFP_ATOMIC); - if (!nskb) - continue; + /* Put type byte before the data */ + if (bt_cb(skb)->channel == HCI_CHANNEL_RAW) + memcpy(skb_push(nskb, 1), &bt_cb(nskb)->pkt_type, 1); if (sock_queue_rcv_skb(sk, nskb)) kfree_skb(nskb); } - read_unlock(&hci_sk_list.lock); } -static struct sk_buff *create_monitor_event(struct hci_dev *hdev, int event) -{ - struct hci_mon_hdr *hdr; - struct hci_mon_new_index *ni; - struct sk_buff *skb; - __le16 opcode; - - switch (event) { - case HCI_DEV_REG: - skb = bt_skb_alloc(HCI_MON_NEW_INDEX_SIZE, GFP_ATOMIC); - if (!skb) - return NULL; - - ni = (void *) skb_put(skb, HCI_MON_NEW_INDEX_SIZE); - ni->type = hdev->dev_type; - ni->bus = hdev->bus; - bacpy(&ni->bdaddr, &hdev->bdaddr); - memcpy(ni->name, hdev->name, 8); - - opcode = __constant_cpu_to_le16(HCI_MON_NEW_INDEX); - break; - - case HCI_DEV_UNREG: - skb = bt_skb_alloc(0, GFP_ATOMIC); - if (!skb) - return NULL; - - opcode = __constant_cpu_to_le16(HCI_MON_DEL_INDEX); - break; - - default: - return NULL; - } - - __net_timestamp(skb); - - hdr = (void *) skb_push(skb, HCI_MON_HDR_SIZE); - hdr->opcode = opcode; - hdr->index = cpu_to_le16(hdev->id); - hdr->len = cpu_to_le16(skb->len - HCI_MON_HDR_SIZE); - - return skb; -} - -static void send_monitor_replay(struct sock *sk) -{ - struct hci_dev *hdev; - - read_lock(&hci_dev_list_lock); - - list_for_each_entry(hdev, &hci_dev_list, list) { - struct sk_buff *skb; - - skb = create_monitor_event(hdev, HCI_DEV_REG); - if (!skb) - continue; - - if (sock_queue_rcv_skb(sk, skb)) - kfree_skb(skb); - } - - read_unlock(&hci_dev_list_lock); -} - -/* Generate internal stack event */ -static void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data) -{ - struct hci_event_hdr *hdr; - struct hci_ev_stack_internal *ev; - struct sk_buff *skb; - - skb = bt_skb_alloc(HCI_EVENT_HDR_SIZE + sizeof(*ev) + dlen, GFP_ATOMIC); - if (!skb) - return; - - hdr = (void *) skb_put(skb, HCI_EVENT_HDR_SIZE); - hdr->evt = HCI_EV_STACK_INTERNAL; - hdr->plen = sizeof(*ev) + dlen; - - ev = (void *) skb_put(skb, sizeof(*ev) + dlen); - ev->type = type; - memcpy(ev->data, data, dlen); - - bt_cb(skb)->incoming = 1; - __net_timestamp(skb); - - bt_cb(skb)->pkt_type = HCI_EVENT_PKT; - skb->dev = (void *) hdev; - hci_send_to_sock(hdev, skb); - kfree_skb(skb); -} - -void hci_sock_dev_event(struct hci_dev *hdev, int event) -{ - struct hci_ev_si_device ev; - - BT_DBG("hdev %s event %d", hdev->name, event); - - /* Send event to monitor */ - if (atomic_read(&monitor_promisc)) { - struct sk_buff *skb; - - skb = create_monitor_event(hdev, event); - if (skb) { - send_monitor_event(skb); - kfree_skb(skb); - } - } - - /* Send event to sockets */ - ev.event = event; - ev.dev_id = hdev->id; - hci_si_event(NULL, HCI_EV_SI_DEVICE, sizeof(ev), &ev); - - if (event == HCI_DEV_UNREG) { - struct sock *sk; - struct hlist_node *node; - - /* Detach sockets from device */ - read_lock(&hci_sk_list.lock); - sk_for_each(sk, node, &hci_sk_list.head) { - bh_lock_sock_nested(sk); - if (hci_pi(sk)->hdev == hdev) { - hci_pi(sk)->hdev = NULL; - sk->sk_err = EPIPE; - sk->sk_state = BT_OPEN; - sk->sk_state_change(sk); - - hci_dev_put(hdev); - } - bh_unlock_sock(sk); - } - read_unlock(&hci_sk_list.lock); - } -} - static int hci_sock_release(struct socket *sock) { struct sock *sk = sock->sk; @@ -441,9 +164,6 @@ static int hci_sock_release(struct socket *sock) hdev = hci_pi(sk)->hdev; - if (hci_pi(sk)->channel == HCI_CHANNEL_MONITOR) - atomic_dec(&monitor_promisc); - bt_sock_unlink(&hci_sk_list, sk); if (hdev) { @@ -460,38 +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; - int err; + struct bdaddr_list *entry; if (copy_from_user(&bdaddr, arg, sizeof(bdaddr))) return -EFAULT; - hci_dev_lock(hdev); + if (bacmp(&bdaddr, BDADDR_ANY) == 0) + return -EBADF; - err = hci_blacklist_add(hdev, &bdaddr, 0); + if (hci_blacklist_lookup(hdev, &bdaddr)) + return -EEXIST; - hci_dev_unlock(hdev); + entry = kzalloc(sizeof(struct bdaddr_list), GFP_KERNEL); + if (!entry) + return -ENOMEM; - return err; + bacpy(&entry->bdaddr, &bdaddr); + + list_add(&entry->list, &hdev->blacklist); + + return 0; +} + +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_sock_blacklist_del(struct hci_dev *hdev, void __user *arg) +static int hci_blacklist_del(struct hci_dev *hdev, void __user *arg) { bdaddr_t bdaddr; - int err; + struct bdaddr_list *entry; if (copy_from_user(&bdaddr, arg, sizeof(bdaddr))) return -EFAULT; - hci_dev_lock(hdev); + if (bacmp(&bdaddr, BDADDR_ANY) == 0) + return hci_blacklist_clear(hdev); - err = hci_blacklist_del(hdev, &bdaddr, 0); + entry = hci_blacklist_lookup(hdev, &bdaddr); + if (!entry) + return -ENOENT; - hci_dev_unlock(hdev); + list_del(&entry->list); + kfree(entry); - return err; + return 0; } /* Ioctls that require bound socket */ @@ -526,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) @@ -561,7 +328,12 @@ static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long a case HCIDEVUP: if (!capable(CAP_NET_ADMIN)) return -EACCES; - return hci_dev_open(arg); + + err = hci_dev_open(arg); + if (!err || err == -EALREADY) + return 0; + else + return err; case HCIDEVDOWN: if (!capable(CAP_NET_ADMIN)) @@ -620,69 +392,31 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_le if (haddr.hci_family != AF_BLUETOOTH) return -EINVAL; + if (haddr.hci_channel > HCI_CHANNEL_CONTROL) + return -EINVAL; + + if (haddr.hci_channel == HCI_CHANNEL_CONTROL && !enable_mgmt) + return -EINVAL; + lock_sock(sk); - if (sk->sk_state == BT_BOUND) { + if (sk->sk_state == BT_BOUND || hci_pi(sk)->hdev) { err = -EALREADY; goto done; } - switch (haddr.hci_channel) { - case HCI_CHANNEL_RAW: - if (hci_pi(sk)->hdev) { - err = -EALREADY; + if (haddr.hci_dev != HCI_DEV_NONE) { + hdev = hci_dev_get(haddr.hci_dev); + if (!hdev) { + err = -ENODEV; goto done; } - if (haddr.hci_dev != HCI_DEV_NONE) { - hdev = hci_dev_get(haddr.hci_dev); - if (!hdev) { - err = -ENODEV; - goto done; - } - - atomic_inc(&hdev->promisc); - } - - hci_pi(sk)->hdev = hdev; - break; - - case HCI_CHANNEL_CONTROL: - if (haddr.hci_dev != HCI_DEV_NONE) { - err = -EINVAL; - goto done; - } - - if (!capable(CAP_NET_ADMIN)) { - err = -EPERM; - goto done; - } - - break; - - case HCI_CHANNEL_MONITOR: - if (haddr.hci_dev != HCI_DEV_NONE) { - err = -EINVAL; - goto done; - } - - if (!capable(CAP_NET_RAW)) { - err = -EPERM; - goto done; - } - - send_monitor_replay(sk); - - atomic_inc(&monitor_promisc); - break; - - default: - err = -EINVAL; - goto done; + atomic_inc(&hdev->promisc); } - hci_pi(sk)->channel = haddr.hci_channel; + hci_pi(sk)->hdev = hdev; sk->sk_state = BT_BOUND; done: @@ -733,8 +467,7 @@ static inline void hci_sock_cmsg(struct sock *sk, struct msghdr *msg, struct sk_ data = &tv; len = sizeof(tv); #ifdef CONFIG_COMPAT - if (!COMPAT_USE_64BIT_TIME && - (msg->msg_flags & MSG_CMSG_COMPAT)) { + if (msg->msg_flags & MSG_CMSG_COMPAT) { ctv.tv_sec = tv.tv_sec; ctv.tv_usec = tv.tv_usec; data = &ctv; @@ -777,15 +510,7 @@ static int hci_sock_recvmsg(struct kiocb *iocb, struct socket *sock, skb_reset_transport_header(skb); err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); - switch (hci_pi(sk)->channel) { - case HCI_CHANNEL_RAW: - hci_sock_cmsg(sk, msg, skb); - break; - case HCI_CHANNEL_CONTROL: - case HCI_CHANNEL_MONITOR: - sock_recv_timestamp(msg, sk, skb); - break; - } + hci_sock_cmsg(sk, msg, skb); skb_free_datagram(sk, skb); @@ -798,6 +523,7 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct sock *sk = sock->sk; struct hci_dev *hdev; struct sk_buff *skb; + int reserve = 0; int err; BT_DBG("sock %p sk %p", sock, sk); @@ -819,9 +545,6 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, case HCI_CHANNEL_CONTROL: err = mgmt_control(sk, msg, len); goto done; - case HCI_CHANNEL_MONITOR: - err = -EOPNOTSUPP; - goto done; default: err = -EINVAL; goto done; @@ -838,10 +561,18 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, goto done; } - skb = bt_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err); + /* Allocate extra headroom for Qualcomm PAL */ + if (hdev->dev_type == HCI_AMP && hdev->manufacturer == 0x001d) + reserve = BT_SKB_RESERVE_80211; + + skb = bt_skb_send_alloc(sk, len + reserve, + msg->msg_flags & MSG_DONTWAIT, &err); if (!skb) goto done; + if (reserve) + skb_reserve(skb, reserve); + if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) { err = -EFAULT; goto drop; @@ -865,10 +596,10 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, if (test_bit(HCI_RAW, &hdev->flags) || (ogf == 0x3f)) { skb_queue_tail(&hdev->raw_q, skb); - queue_work(hdev->workqueue, &hdev->tx_work); + tasklet_schedule(&hdev->tx_task); } else { skb_queue_tail(&hdev->cmd_q, skb); - queue_work(hdev->workqueue, &hdev->cmd_work); + tasklet_schedule(&hdev->cmd_task); } } else { if (!capable(CAP_NET_RAW)) { @@ -877,7 +608,7 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, } skb_queue_tail(&hdev->raw_q, skb); - queue_work(hdev->workqueue, &hdev->tx_work); + tasklet_schedule(&hdev->tx_task); } err = len; @@ -901,11 +632,6 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char lock_sock(sk); - if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) { - err = -EINVAL; - goto done; - } - switch (optname) { case HCI_DATA_DIR: if (get_user(opt, (int __user *)optval)) { @@ -968,7 +694,6 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char break; } -done: release_sock(sk); return err; } @@ -977,20 +702,11 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname, char { struct hci_ufilter uf; struct sock *sk = sock->sk; - int len, opt, err = 0; - - BT_DBG("sk %p, opt %d", sk, optname); + int len, opt; if (get_user(len, optlen)) return -EFAULT; - lock_sock(sk); - - if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) { - err = -EINVAL; - goto done; - } - switch (optname) { case HCI_DATA_DIR: if (hci_pi(sk)->cmsg_mask & HCI_CMSG_DIR) @@ -999,7 +715,7 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname, char opt = 0; if (put_user(opt, optval)) - err = -EFAULT; + return -EFAULT; break; case HCI_TIME_STAMP: @@ -1009,7 +725,7 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname, char opt = 0; if (put_user(opt, optval)) - err = -EFAULT; + return -EFAULT; break; case HCI_FILTER: @@ -1024,17 +740,15 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname, char len = min_t(unsigned int, len, sizeof(uf)); if (copy_to_user(optval, &uf, len)) - err = -EFAULT; + return -EFAULT; break; default: - err = -ENOPROTOOPT; + return -ENOPROTOOPT; break; } -done: - release_sock(sk); - return err; + return 0; } static const struct proto_ops hci_sock_ops = { @@ -1092,12 +806,54 @@ static int hci_sock_create(struct net *net, struct socket *sock, int protocol, return 0; } +static int hci_sock_dev_event(struct notifier_block *this, unsigned long event, void *ptr) +{ + struct hci_dev *hdev = (struct hci_dev *) ptr; + struct hci_ev_si_device ev; + + BT_DBG("hdev %s event %ld", hdev->name, event); + + /* Send event to sockets */ + ev.event = event; + ev.dev_id = hdev->id; + hci_si_event(NULL, HCI_EV_SI_DEVICE, sizeof(ev), &ev); + + if (event == HCI_DEV_UNREG) { + struct sock *sk; + struct hlist_node *node; + + /* Detach sockets from device */ + read_lock(&hci_sk_list.lock); + sk_for_each(sk, node, &hci_sk_list.head) { + local_bh_disable(); + bh_lock_sock_nested(sk); + if (hci_pi(sk)->hdev == hdev) { + hci_pi(sk)->hdev = NULL; + sk->sk_err = EPIPE; + sk->sk_state = BT_OPEN; + sk->sk_state_change(sk); + + hci_dev_put(hdev); + } + bh_unlock_sock(sk); + local_bh_enable(); + } + read_unlock(&hci_sk_list.lock); + } + + return NOTIFY_DONE; +} + static const struct net_proto_family hci_sock_family_ops = { .family = PF_BLUETOOTH, .owner = THIS_MODULE, .create = hci_sock_create, }; +static struct notifier_block hci_sock_nblock = { + .notifier_call = hci_sock_dev_event +}; + int __init hci_sock_init(void) { int err; @@ -1110,6 +866,8 @@ int __init hci_sock_init(void) if (err < 0) goto error; + hci_register_notifier(&hci_sock_nblock); + BT_INFO("HCI socket layer initialized"); return 0; @@ -1125,5 +883,10 @@ void hci_sock_cleanup(void) if (bt_sock_unregister(BTPROTO_HCI) < 0) BT_ERR("HCI socket unregistration failed"); + hci_unregister_notifier(&hci_sock_nblock); + proto_unregister(&hci_sk_proto); } + +module_param(enable_mgmt, bool, 0644); +MODULE_PARM_DESC(enable_mgmt, "Enable Management interface"); diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index bc154298979a8278efa4611309e025d663f056bc..0a4c7d6022245bd1de9451bd4b446974436f1e54 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -24,8 +25,6 @@ static inline char *link_typetostr(int type) return "SCO"; case ESCO_LINK: return "eSCO"; - case LE_LINK: - return "LE"; default: return "UNKNOWN"; } @@ -33,19 +32,19 @@ static inline char *link_typetostr(int type) static ssize_t show_link_type(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_conn *conn = to_hci_conn(dev); + struct hci_conn *conn = dev_get_drvdata(dev); return sprintf(buf, "%s\n", link_typetostr(conn->type)); } static ssize_t show_link_address(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_conn *conn = to_hci_conn(dev); + struct hci_conn *conn = dev_get_drvdata(dev); return sprintf(buf, "%s\n", batostr(&conn->dst)); } static ssize_t show_link_features(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_conn *conn = to_hci_conn(dev); + struct hci_conn *conn = dev_get_drvdata(dev); return sprintf(buf, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n", conn->features[0], conn->features[1], @@ -79,8 +78,8 @@ static const struct attribute_group *bt_link_groups[] = { static void bt_link_release(struct device *dev) { - struct hci_conn *conn = to_hci_conn(dev); - kfree(conn); + void *data = dev_get_drvdata(dev); + kfree(data); } static struct device_type bt_link = { @@ -89,37 +88,15 @@ static struct device_type bt_link = { .release = bt_link_release, }; -/* - * The rfcomm tty device will possibly retain even when conn - * is down, and sysfs doesn't support move zombie device, - * so we should move the device before conn device is destroyed. - */ -static int __match_tty(struct device *dev, void *data) -{ - return !strncmp(dev_name(dev), "rfcomm", 6); -} - -void hci_conn_init_sysfs(struct hci_conn *conn) +static void add_conn(struct work_struct *work) { + struct hci_conn *conn = container_of(work, struct hci_conn, work_add); struct hci_dev *hdev = conn->hdev; - BT_DBG("conn %p", conn); - - conn->dev.type = &bt_link; - conn->dev.class = bt_class; - conn->dev.parent = &hdev->dev; - - device_initialize(&conn->dev); -} - -void hci_conn_add_sysfs(struct hci_conn *conn) -{ - struct hci_dev *hdev = conn->hdev; - - BT_DBG("conn %p", conn); - dev_set_name(&conn->dev, "%s:%d", hdev->name, conn->handle); + dev_set_drvdata(&conn->dev, conn); + if (device_add(&conn->dev) < 0) { BT_ERR("Failed to register connection device"); return; @@ -128,8 +105,19 @@ void hci_conn_add_sysfs(struct hci_conn *conn) hci_dev_hold(hdev); } -void hci_conn_del_sysfs(struct hci_conn *conn) +/* + * The rfcomm tty device will possibly retain even when conn + * is down, and sysfs doesn't support move zombie device, + * so we should move the device before conn device is destroyed. + */ +static int __match_tty(struct device *dev, void *data) +{ + return !strncmp(dev_name(dev), "rfcomm", 6); +} + +static void del_conn(struct work_struct *work) { + struct hci_conn *conn = container_of(work, struct hci_conn, work_del); struct hci_dev *hdev = conn->hdev; if (!device_is_registered(&conn->dev)) @@ -151,6 +139,36 @@ void hci_conn_del_sysfs(struct hci_conn *conn) hci_dev_put(hdev); } +void hci_conn_init_sysfs(struct hci_conn *conn) +{ + struct hci_dev *hdev = conn->hdev; + + BT_DBG("conn %p", conn); + + conn->dev.type = &bt_link; + conn->dev.class = bt_class; + conn->dev.parent = &hdev->dev; + + device_initialize(&conn->dev); + + INIT_WORK(&conn->work_add, add_conn); + INIT_WORK(&conn->work_del, del_conn); +} + +void hci_conn_add_sysfs(struct hci_conn *conn) +{ + BT_DBG("conn %p", conn); + + queue_work(conn->hdev->workqueue, &conn->work_add); +} + +void hci_conn_del_sysfs(struct hci_conn *conn) +{ + BT_DBG("conn %p", conn); + + queue_work(conn->hdev->workqueue, &conn->work_del); +} + static inline char *host_bustostr(int bus) { switch (bus) { @@ -187,19 +205,19 @@ static inline char *host_typetostr(int type) static ssize_t show_bus(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%s\n", host_bustostr(hdev->bus)); } static ssize_t show_type(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%s\n", host_typetostr(hdev->dev_type)); } static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); char name[HCI_MAX_NAME_LENGTH + 1]; int i; @@ -212,20 +230,20 @@ static ssize_t show_name(struct device *dev, struct device_attribute *attr, char static ssize_t show_class(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "0x%.2x%.2x%.2x\n", hdev->dev_class[2], hdev->dev_class[1], hdev->dev_class[0]); } static ssize_t show_address(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%s\n", batostr(&hdev->bdaddr)); } static ssize_t show_features(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n", hdev->features[0], hdev->features[1], @@ -236,31 +254,31 @@ static ssize_t show_features(struct device *dev, struct device_attribute *attr, static ssize_t show_manufacturer(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%d\n", hdev->manufacturer); } static ssize_t show_hci_version(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%d\n", hdev->hci_ver); } static ssize_t show_hci_revision(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%d\n", hdev->hci_rev); } static ssize_t show_idle_timeout(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%d\n", hdev->idle_timeout); } static ssize_t store_idle_timeout(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); unsigned int val; int rv; @@ -278,13 +296,13 @@ static ssize_t store_idle_timeout(struct device *dev, struct device_attribute *a static ssize_t show_sniff_max_interval(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%d\n", hdev->sniff_max_interval); } static ssize_t store_sniff_max_interval(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); u16 val; int rv; @@ -302,13 +320,13 @@ static ssize_t store_sniff_max_interval(struct device *dev, struct device_attrib static ssize_t show_sniff_min_interval(struct device *dev, struct device_attribute *attr, char *buf) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); return sprintf(buf, "%d\n", hdev->sniff_min_interval); } static ssize_t store_sniff_min_interval(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct hci_dev *hdev = to_hci_dev(dev); + struct hci_dev *hdev = dev_get_drvdata(dev); u16 val; int rv; @@ -368,9 +386,8 @@ static const struct attribute_group *bt_host_groups[] = { static void bt_host_release(struct device *dev) { - struct hci_dev *hdev = to_hci_dev(dev); - kfree(hdev); - module_put(THIS_MODULE); + void *data = dev_get_drvdata(dev); + kfree(data); } static struct device_type bt_host = { @@ -382,12 +399,12 @@ static struct device_type bt_host = { static int inquiry_cache_show(struct seq_file *f, void *p) { struct hci_dev *hdev = f->private; - struct discovery_state *cache = &hdev->discovery; + struct inquiry_cache *cache = &hdev->inq_cache; struct inquiry_entry *e; - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); - list_for_each_entry(e, &cache->all, all) { + for (e = cache->list; e; e = e->next) { struct inquiry_data *data = &e->data; seq_printf(f, "%s %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", batostr(&data->bdaddr), @@ -398,7 +415,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) data->rssi, data->ssp_mode, e->timestamp); } - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); return 0; } @@ -418,14 +435,19 @@ static const struct file_operations inquiry_cache_fops = { static int blacklist_show(struct seq_file *f, void *p) { struct hci_dev *hdev = f->private; - struct bdaddr_list *b; + struct list_head *l; + + hci_dev_lock_bh(hdev); + + list_for_each(l, &hdev->blacklist) { + struct bdaddr_list *b; - hci_dev_lock(hdev); + b = list_entry(l, struct bdaddr_list, list); - list_for_each_entry(b, &hdev->blacklist, list) seq_printf(f, "%s\n", batostr(&b->bdaddr)); + } - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); return 0; } @@ -462,14 +484,19 @@ static void print_bt_uuid(struct seq_file *f, u8 *uuid) static int uuids_show(struct seq_file *f, void *p) { struct hci_dev *hdev = f->private; - struct bt_uuid *uuid; + struct list_head *l; + + hci_dev_lock_bh(hdev); + + list_for_each(l, &hdev->uuids) { + struct bt_uuid *uuid; - hci_dev_lock(hdev); + uuid = list_entry(l, struct bt_uuid, list); - list_for_each_entry(uuid, &hdev->uuids, list) print_bt_uuid(f, uuid->uuid); + } - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); return 0; } @@ -486,57 +513,22 @@ 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(hdev); - - hdev->auto_accept_delay = val; - - hci_dev_unlock(hdev); - - return 0; -} - -static int auto_accept_delay_get(void *data, u64 *val) -{ - struct hci_dev *hdev = data; - - hci_dev_lock(hdev); - - *val = hdev->auto_accept_delay; - - hci_dev_unlock(hdev); - - return 0; -} - -DEFINE_SIMPLE_ATTRIBUTE(auto_accept_delay_fops, auto_accept_delay_get, - auto_accept_delay_set, "%llu\n"); - -void hci_init_sysfs(struct hci_dev *hdev) -{ - struct device *dev = &hdev->dev; - - dev->type = &bt_host; - dev->class = bt_class; - - __module_get(THIS_MODULE); - device_initialize(dev); -} - -int hci_add_sysfs(struct hci_dev *hdev) +int hci_register_sysfs(struct hci_dev *hdev) { struct device *dev = &hdev->dev; int err; BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus); + dev->type = &bt_host; + dev->class = bt_class; dev->parent = hdev->parent; + dev_set_name(dev, "%s", hdev->name); - err = device_add(dev); + dev_set_drvdata(dev, hdev); + + err = device_register(dev); if (err < 0) return err; @@ -555,12 +547,10 @@ int hci_add_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; } -void hci_del_sysfs(struct hci_dev *hdev) +void hci_unregister_sysfs(struct hci_dev *hdev) { BT_DBG("%p name %s bus %d", hdev, hdev->name, hdev->bus); diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index d478be11d562995ae08c65bc8198c187ee686d61..a91c97c4c021e84355d49d90aca1114fd96c06b0 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -21,6 +21,7 @@ */ #include +#include #include #include @@ -36,8 +37,6 @@ #include #include #include -#include -#include #include #include @@ -81,20 +80,24 @@ static unsigned char hidp_mkeyspat[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; static struct hidp_session *__hidp_get_session(bdaddr_t *bdaddr) { struct hidp_session *session; + struct list_head *p; BT_DBG(""); - list_for_each_entry(session, &hidp_session_list, list) { + list_for_each(p, &hidp_session_list) { + session = list_entry(p, struct hidp_session, list); if (!bacmp(bdaddr, &session->bdaddr)) return session; } - return NULL; } static void __hidp_link_session(struct hidp_session *session) { + __module_get(THIS_MODULE); list_add(&session->list, &hidp_session_list); + + hci_conn_hold_device(session->conn); } static void __hidp_unlink_session(struct hidp_session *session) @@ -102,6 +105,7 @@ static void __hidp_unlink_session(struct hidp_session *session) hci_conn_put_device(session->conn); list_del(&session->list); + module_put(THIS_MODULE); } static void __hidp_copy_session(struct hidp_session *session, struct hidp_conninfo *ci) @@ -250,9 +254,6 @@ static int __hidp_send_ctrl_message(struct hidp_session *session, BT_DBG("session %p data %p size %d", session, data, size); - if (atomic_read(&session->terminate)) - return -EIO; - skb = alloc_skb(size + 1, GFP_ATOMIC); if (!skb) { BT_ERR("Can't allocate memory for new frame"); @@ -318,143 +319,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; - int ret; - - 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; - ret = hidp_send_ctrl_message(hid->driver_data, report_type, data, 1); - if (ret) - goto err; - - /* 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 */ - ret = -EIO; - goto err; - } - if (res < 0) { - /* signal */ - ret = -ERESTARTSYS; - goto err; - } - } - - 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: - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - mutex_unlock(&session->report_mutex); - return ret; -} - 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); - ret = hidp_send_ctrl_message(hid->driver_data, report_type, data, - count); - if (ret) - 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; + if (hidp_send_ctrl_message(hid->driver_data, report_type, + data, count)) + return -ENOMEM; + return count; } static void hidp_idle_timeout(unsigned long arg) @@ -462,7 +344,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) @@ -481,21 +363,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_and_clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) - wake_up_interruptible(&session->report_queue); - /* FIXME: Call into SET_ GET_ handlers here */ break; @@ -514,10 +391,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_and_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, @@ -530,16 +403,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) { @@ -551,6 +423,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: @@ -562,27 +435,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); @@ -602,7 +460,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: @@ -611,8 +469,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, @@ -657,32 +514,25 @@ static int hidp_send_frame(struct socket *sock, unsigned char *data, int len) return kernel_sendmsg(sock, &msg, &iv, 1, len); } -static void hidp_process_intr_transmit(struct hidp_session *session) +static void hidp_process_transmit(struct hidp_session *session) { struct sk_buff *skb; BT_DBG("session %p", session); - while ((skb = skb_dequeue(&session->intr_transmit))) { - if (hidp_send_frame(session->intr_sock, skb->data, skb->len) < 0) { - skb_queue_head(&session->intr_transmit, skb); + while ((skb = skb_dequeue(&session->ctrl_transmit))) { + if (hidp_send_frame(session->ctrl_sock, skb->data, skb->len) < 0) { + skb_queue_head(&session->ctrl_transmit, skb); break; } hidp_set_timer(session); kfree_skb(skb); } -} - -static void hidp_process_ctrl_transmit(struct hidp_session *session) -{ - struct sk_buff *skb; - BT_DBG("session %p", session); - - while ((skb = skb_dequeue(&session->ctrl_transmit))) { - if (hidp_send_frame(session->ctrl_sock, skb->data, skb->len) < 0) { - skb_queue_head(&session->ctrl_transmit, skb); + while ((skb = skb_dequeue(&session->intr_transmit))) { + if (hidp_send_frame(session->intr_sock, skb->data, skb->len) < 0) { + skb_queue_head(&session->intr_transmit, skb); break; } @@ -697,56 +547,59 @@ 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); - __module_get(THIS_MODULE); + 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(&intr_sk->sk_receive_queue))) { + while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) { skb_orphan(skb); if (!skb_linearize(skb)) - hidp_recv_intr_frame(session, skb); + hidp_recv_ctrl_frame(session, skb); else kfree_skb(skb); } - hidp_process_intr_transmit(session); - - while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) { + while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) { skb_orphan(skb); if (!skb_linearize(skb)) - hidp_recv_ctrl_frame(session, skb); + hidp_recv_intr_frame(session, skb); else kfree_skb(skb); } - hidp_process_ctrl_transmit(session); + hidp_process_transmit(session); schedule(); - set_current_state(TASK_INTERRUPTIBLE); } set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(intr_sk), &intr_wait); remove_wait_queue(sk_sleep(ctrl_sk), &ctrl_wait); - clear_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags); - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - wake_up_interruptible(&session->report_queue); - down_write(&hidp_session_sem); hidp_del_timer(session); @@ -778,39 +631,35 @@ static int hidp_session(void *arg) up_write(&hidp_session_sem); - kfree(session->rd_data); kfree(session); - module_put_and_exit(0); return 0; } -static struct hci_conn *hidp_get_connection(struct hidp_session *session) +static struct device *hidp_get_device(struct hidp_session *session) { bdaddr_t *src = &bt_sk(session->ctrl_sock->sk)->src; bdaddr_t *dst = &bt_sk(session->ctrl_sock->sk)->dst; - struct hci_conn *conn; + struct device *device = NULL; struct hci_dev *hdev; hdev = hci_get_route(dst, src); if (!hdev) return NULL; - hci_dev_lock(hdev); - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); - if (conn) - hci_conn_hold_device(conn); - hci_dev_unlock(hdev); + session->conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); + if (session->conn) + device = &session->conn->dev; hci_dev_put(hdev); - return conn; + return device; } static int hidp_setup_input(struct hidp_session *session, struct hidp_connadd_req *req) { struct input_dev *input; - int i; + int err, i; input = input_allocate_device(); if (!input) @@ -853,10 +702,16 @@ static int hidp_setup_input(struct hidp_session *session, input->relbit[0] |= BIT_MASK(REL_WHEEL); } - input->dev.parent = &session->conn->dev; + input->dev.parent = hidp_get_device(session); input->event = hidp_input_event; + err = input_register_device(input); + if (err < 0) { + hci_conn_put_device(session->conn); + return err; + } + return 0; } @@ -882,9 +737,6 @@ static int hidp_start(struct hid_device *hid) struct hidp_session *session = hid->driver_data; struct hid_report *report; - if (hid->quirks & HID_QUIRK_NO_INIT_REPORTS) - return 0; - list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT]. report_list, list) hidp_send_report(session, report); @@ -915,8 +767,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) { @@ -953,14 +803,21 @@ static int hidp_setup_hid(struct hidp_session *session, strncpy(hid->phys, batostr(&bt_sk(session->ctrl_sock->sk)->src), 64); strncpy(hid->uniq, batostr(&bt_sk(session->ctrl_sock->sk)->dst), 64); - hid->dev.parent = &session->conn->dev; + 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; @@ -971,7 +828,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(""); @@ -980,28 +836,24 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, bacmp(&bt_sk(ctrl_sock->sk)->dst, &bt_sk(intr_sock->sk)->dst)) return -ENOTUNIQ; + session = kzalloc(sizeof(struct hidp_session), GFP_KERNEL); + if (!session) + return -ENOMEM; + BT_DBG("rd_data %p rd_size %d", req->rd_data, req->rd_size); down_write(&hidp_session_sem); s = __hidp_get_session(&bt_sk(ctrl_sock->sk)->dst); if (s && s->state == BT_CONNECTED) { - up_write(&hidp_session_sem); - return -EEXIST; - } - - session = kzalloc(sizeof(struct hidp_session), GFP_KERNEL); - if (!session) { - up_write(&hidp_session_sem); - return -ENOMEM; + err = -EEXIST; + goto failed; } 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); @@ -1009,29 +861,17 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, session->intr_sock = intr_sock; session->state = BT_CONNECTED; - session->conn = hidp_get_connection(session); - if (!session->conn) { - err = -ENOTCONN; - goto failed; - } - setup_timer(&session->timer, hidp_idle_timeout, (unsigned long)session); 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; - __hidp_link_session(session); - if (req->rd_size > 0) { err = hidp_setup_hid(session, req); - if (err) + if (err && err != -ENODEV) goto purge; } @@ -1041,42 +881,13 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, goto purge; } - hidp_set_timer(session); + __hidp_link_session(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; - } + hidp_set_timer(session); - 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); - } - - if (session->hid) - err = hid_add_device(session->hid); - else - err = input_register_device(session->input); - - 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, @@ -1093,6 +904,8 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, unlink: hidp_del_timer(session); + __hidp_unlink_session(session); + if (session->input) { input_unregister_device(session->input); session->input = NULL; @@ -1107,14 +920,13 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, session->rd_data = NULL; purge: - __hidp_unlink_session(session); - skb_queue_purge(&session->ctrl_transmit); skb_queue_purge(&session->intr_transmit); failed: up_write(&hidp_session_sem); + input_free_device(session->input); kfree(session); return err; } @@ -1138,8 +950,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; @@ -1150,16 +967,19 @@ int hidp_del_connection(struct hidp_conndel_req *req) int hidp_get_connlist(struct hidp_connlist_req *req) { - struct hidp_session *session; + struct list_head *p; int err = 0, n = 0; BT_DBG(""); down_read(&hidp_session_sem); - list_for_each_entry(session, &hidp_session_list, list) { + list_for_each(p, &hidp_session_list) { + struct hidp_session *session; struct hidp_conninfo ci; + session = list_entry(p, struct hidp_session, list); + __hidp_copy_session(session, &ci); if (copy_to_user(req->ci, &ci, sizeof(ci))) { diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h index af1bcc823f26d8196587be3624b8f091f29f1d8f..28bb9ce40bf21c9bd05915d5ada3732c5c766d66 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/hidp/sock.c b/net/bluetooth/hidp/sock.c index 73a32d705c1fc14f423c5178c367ee4064cd9284..178ac7f127adcace1c672eddff2a55e10f0be1f1 100644 --- a/net/bluetooth/hidp/sock.c +++ b/net/bluetooth/hidp/sock.c @@ -160,10 +160,10 @@ static int hidp_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigne { if (cmd == HIDPGETCONNLIST) { struct hidp_connlist_req cl; - u32 uci; + uint32_t uci; int err; - if (get_user(cl.cnum, (u32 __user *) arg) || + if (get_user(cl.cnum, (uint32_t __user *) arg) || get_user(uci, (u32 __user *) (arg + 4))) return -EFAULT; @@ -174,7 +174,7 @@ static int hidp_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsigne err = hidp_get_connlist(&cl); - if (!err && put_user(cl.cnum, (u32 __user *) arg)) + if (!err && put_user(cl.cnum, (uint32_t __user *) arg)) err = -EFAULT; return err; diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 0939c7295a643d05897fce70ace1dacabad09d12..7719b82fe4c8b4c3106320594faf5e883fcc034e 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1,9 +1,8 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2010-2012 Code Aurora Forum. All rights reserved. Copyright (C) 2009-2010 Gustavo F. Padovan Copyright (C) 2010 Google Inc. - Copyright (C) 2011 ProFUSION Embedded Systems Written 2000,2001 by Maxim Krasnyansky @@ -47,340 +46,536 @@ #include #include #include +#include #include +#include #include #include #include #include #include +#include bool disable_ertm; +bool enable_reconfig; static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN; -static u8 l2cap_fixed_chan[8] = { L2CAP_FC_L2CAP, }; +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_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, u8 is_process); +static u16 l2cap_get_smallest_flushto(struct l2cap_chan_list *l); +static void l2cap_set_acl_flushto(struct hci_conn *hcon, u16 flush_to); /* ---- L2CAP channels ---- */ - -static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn, u16 cid) +static struct sock *__l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, u16 cid) { - struct l2cap_chan *c; - - list_for_each_entry(c, &conn->chan_l, list) { - if (c->dcid == cid) - return c; + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->dcid == cid) + break; } - return NULL; + return s; } -static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid) +/* Find channel with given DCID. + * Returns locked socket */ +static inline struct sock *l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, + u16 cid) { - struct l2cap_chan *c; + 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; +} - list_for_each_entry(c, &conn->chan_l, list) { - if (c->scid == cid) - return c; +static struct sock *__l2cap_get_chan_by_scid(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)->scid == cid) + break; } - return NULL; + return s; } /* Find channel with given SCID. * Returns locked socket */ -static struct l2cap_chan *l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid) +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; +} - mutex_lock(&conn->chan_lock); - c = __l2cap_get_chan_by_scid(conn, cid); - mutex_unlock(&conn->chan_lock); +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 s; +} - return c; +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_ident(struct l2cap_conn *conn, u8 ident) +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->ident == ident) - return c; + skb_queue_walk(head, skb) { + if (bt_cb(skb)->control.txseq == seq) + return skb; } + return NULL; } -static inline struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn, u8 ident) +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; - mutex_lock(&conn->chan_lock); - c = __l2cap_get_chan_by_ident(conn, ident); - mutex_unlock(&conn->chan_lock); + seq_list->list = kzalloc(sizeof(u16) * allocSize, GFP_ATOMIC); + if (!seq_list->list) + return -ENOMEM; - 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_global_chan_by_addr(__le16 psm, bdaddr_t *src) +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, &chan_list, global_l) { - if (c->sport == psm && !bacmp(&bt_sk(c->sk)->src, src)) - 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; } -int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm) +static u16 l2cap_seq_list_remove(struct l2cap_seq_list *seq_list, u16 seq) { - int err; + u16 mask = seq_list->mask; - write_lock(&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->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 (psm) { - chan->psm = psm; - chan->sport = psm; - err = 0; + if (seq_list->head == L2CAP_SEQ_LIST_TAIL) { + seq_list->head = L2CAP_SEQ_LIST_CLEAR; + seq_list->tail = L2CAP_SEQ_LIST_CLEAR; + } } 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; + /* 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; +} -done: - write_unlock(&chan_list_lock); - return err; +static inline u16 l2cap_seq_list_pop(struct l2cap_seq_list *seq_list) +{ + return l2cap_seq_list_remove(seq_list, seq_list->head); +} + +static void l2cap_seq_list_clear(struct l2cap_seq_list *seq_list) +{ + 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; + + seq_list->head = L2CAP_SEQ_LIST_CLEAR; + seq_list->tail = L2CAP_SEQ_LIST_CLEAR; + } } -int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid) +static void l2cap_seq_list_append(struct l2cap_seq_list *seq_list, u16 seq) { - write_lock(&chan_list_lock); + u16 mask = seq_list->mask; - chan->scid = scid; + BT_DBG("seq_list %p, seq %d", seq_list, (int) seq); - write_unlock(&chan_list_lock); + 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; - return 0; + seq_list->tail = seq; + seq_list->list[seq & mask] = L2CAP_SEQ_LIST_TAIL; + } } -static u16 l2cap_alloc_cid(struct l2cap_conn *conn) +static u16 __pack_enhanced_control(struct bt_l2cap_control *control) { - u16 cid = L2CAP_CID_DYN_START; + u16 packed; - for (; cid < L2CAP_CID_DYN_END; cid++) { - if (!__l2cap_get_chan_by_scid(conn, cid)) - return cid; + 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 { + packed |= (control->sar << L2CAP_CTRL_SAR_SHIFT) & + L2CAP_CTRL_SAR; + packed |= (control->txseq << L2CAP_CTRL_TXSEQ_SHIFT) & + L2CAP_CTRL_TXSEQ; } - return 0; + return packed; } -static void __l2cap_state_change(struct l2cap_chan *chan, int state) +static void __get_enhanced_control(u16 enhanced, + struct bt_l2cap_control *control) { - BT_DBG("chan %p %s -> %s", chan, state_to_string(chan->state), - state_to_string(state)); + control->reqseq = (enhanced & L2CAP_CTRL_REQSEQ) >> + L2CAP_CTRL_REQSEQ_SHIFT; + control->final = (enhanced & L2CAP_CTRL_FINAL) >> + L2CAP_CTRL_FINAL_SHIFT; - chan->state = state; - chan->ops->state_change(chan->data, state); -} + 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; -static void l2cap_state_change(struct l2cap_chan *chan, int state) -{ - struct sock *sk = chan->sk; + 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; - lock_sock(sk); - __l2cap_state_change(chan, state); - release_sock(sk); + control->poll = 0; + control->super = 0; + } } -static inline void __l2cap_chan_set_err(struct l2cap_chan *chan, int err) +static u32 __pack_extended_control(struct bt_l2cap_control *control) { - struct sock *sk = chan->sk; + u32 packed; - sk->sk_err = err; -} + packed = (control->reqseq << L2CAP_EXT_CTRL_REQSEQ_SHIFT) & + L2CAP_EXT_CTRL_REQSEQ; + packed |= (control->final << L2CAP_EXT_CTRL_FINAL_SHIFT) & + L2CAP_EXT_CTRL_FINAL; -static inline void l2cap_chan_set_err(struct l2cap_chan *chan, int err) -{ - struct sock *sk = chan->sk; + 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; + } - lock_sock(sk); - __l2cap_chan_set_err(chan, err); - release_sock(sk); + return packed; } -static void l2cap_chan_timeout(struct work_struct *work) +static void __get_extended_control(u32 extended, + struct bt_l2cap_control *control) { - struct l2cap_chan *chan = container_of(work, struct l2cap_chan, - chan_timer.work); - struct l2cap_conn *conn = chan->conn; - int reason; - - BT_DBG("chan %p state %s", chan, state_to_string(chan->state)); + control->reqseq = (extended & L2CAP_EXT_CTRL_REQSEQ) >> + L2CAP_EXT_CTRL_REQSEQ_SHIFT; + control->final = (extended & L2CAP_EXT_CTRL_FINAL) >> + L2CAP_EXT_CTRL_FINAL_SHIFT; - mutex_lock(&conn->chan_lock); - l2cap_chan_lock(chan); - - 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; + 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; - l2cap_chan_close(chan, reason); + 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; - l2cap_chan_unlock(chan); + control->poll = 0; + control->super = 0; + } +} - chan->ops->close(chan->data); - mutex_unlock(&conn->chan_lock); +static inline void l2cap_ertm_stop_ack_timer(struct l2cap_pinfo *pi) +{ + BT_DBG("pi %p", pi); + __cancel_delayed_work(&pi->ack_work); +} - l2cap_chan_put(chan); +static inline void l2cap_ertm_start_ack_timer(struct l2cap_pinfo *pi) +{ + 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)); + } } -struct l2cap_chan *l2cap_chan_create(struct sock *sk) +static inline void l2cap_ertm_stop_retrans_timer(struct l2cap_pinfo *pi) { - struct l2cap_chan *chan; + BT_DBG("pi %p", pi); + __cancel_delayed_work(&pi->retrans_work); +} - chan = kzalloc(sizeof(*chan), GFP_ATOMIC); - if (!chan) - return NULL; +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)); + } +} - mutex_init(&chan->lock); +static inline void l2cap_ertm_stop_monitor_timer(struct l2cap_pinfo *pi) +{ + BT_DBG("pi %p", pi); + __cancel_delayed_work(&pi->monitor_work); +} - chan->sk = sk; +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)); + } +} - write_lock(&chan_list_lock); - list_add(&chan->global_l, &chan_list); - write_unlock(&chan_list_lock); +static u16 l2cap_alloc_cid(struct l2cap_chan_list *l) +{ + u16 cid = L2CAP_CID_DYN_START; - INIT_DELAYED_WORK(&chan->chan_timer, l2cap_chan_timeout); + for (; cid < L2CAP_CID_DYN_END; cid++) { + if (!__l2cap_get_chan_by_scid(l, cid)) + return cid; + } - chan->state = BT_OPEN; + return 0; +} - atomic_set(&chan->refcnt, 1); +static inline void __l2cap_chan_link(struct l2cap_chan_list *l, struct sock *sk) +{ + sock_hold(sk); - BT_DBG("sk %p chan %p", sk, chan); + if (l->head) + l2cap_pi(l->head)->prev_c = sk; - return chan; + l2cap_pi(sk)->next_c = l->head; + l2cap_pi(sk)->prev_c = NULL; + l->head = sk; } -void l2cap_chan_destroy(struct l2cap_chan *chan) +static inline void l2cap_chan_unlink(struct l2cap_chan_list *l, struct sock *sk) { - write_lock(&chan_list_lock); - list_del(&chan->global_l); - write_unlock(&chan_list_lock); + struct sock *next = l2cap_pi(sk)->next_c, *prev = l2cap_pi(sk)->prev_c; + + write_lock_bh(&l->lock); + if (sk == l->head) + l->head = next; + + if (next) + l2cap_pi(next)->prev_c = prev; + if (prev) + l2cap_pi(prev)->next_c = next; + write_unlock_bh(&l->lock); - l2cap_chan_put(chan); + __sock_put(sk); } -void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) +static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk) { + struct l2cap_chan_list *l = &conn->chan_list; + 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 = HCI_ERROR_REMOTE_USER_TERM; + conn->disc_reason = 0x13; - chan->conn = conn; + l2cap_pi(sk)->conn = conn; - switch (chan->chan_type) { - case 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; } - break; - - case 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; - break; - - default: + 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; } - chan->local_id = L2CAP_BESTEFFORT_ID; - chan->local_stype = L2CAP_SERV_BESTEFFORT; - chan->local_msdu = L2CAP_DEFAULT_MAX_SDU_SIZE; - chan->local_sdu_itime = L2CAP_DEFAULT_SDU_ITIME; - chan->local_acc_lat = L2CAP_DEFAULT_ACC_LAT; - chan->local_flush_to = L2CAP_DEFAULT_FLUSH_TO; - - l2cap_chan_hold(chan); - - list_add(&chan->list, &conn->chan_l); -} + if (l2cap_get_smallest_flushto(l) > l2cap_pi(sk)->flush_to) { + /*if flush timeout of the channel is lesser than existing */ + l2cap_set_acl_flushto(conn->hcon, l2cap_pi(sk)->flush_to); + } + /* Otherwise, do not set scid/dcid/omtu. These will be set up + * by l2cap_fixed_channel_config() + */ -void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) -{ - mutex_lock(&conn->chan_lock); - __l2cap_chan_add(conn, chan); - mutex_unlock(&conn->chan_lock); + __l2cap_chan_link(l, sk); } -static void l2cap_chan_del(struct l2cap_chan *chan, int err) +/* Delete channel. + * Must be called on the locked socket. */ +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 */ - list_del(&chan->list); - - l2cap_chan_put(chan); - - chan->conn = NULL; - hci_conn_put(conn->hcon); + struct l2cap_chan_list *l = &conn->chan_list; + /* Unlink from channel list */ + l2cap_chan_unlink(l, sk); + l2cap_pi(sk)->conn = NULL; + if (!l2cap_pi(sk)->fixed_channel) + hci_conn_put(conn->hcon); + + read_lock(&l->lock); + if (l2cap_pi(sk)->flush_to < l2cap_get_smallest_flushto(l)) + l2cap_set_acl_flushto(conn->hcon, + l2cap_get_smallest_flushto(l)); + read_unlock(&l->lock); + } + + if (l2cap_pi(sk)->ampchan) { + struct hci_chan *ampchan = l2cap_pi(sk)->ampchan; + struct hci_conn *ampcon = l2cap_pi(sk)->ampcon; + l2cap_pi(sk)->ampchan = NULL; + l2cap_pi(sk)->ampcon = NULL; + l2cap_pi(sk)->amp_id = 0; + if (hci_chan_put(ampchan)) + ampcon->l2cap_data = NULL; + else + l2cap_deaggregate(ampchan, l2cap_pi(sk)); } - lock_sock(sk); - - __l2cap_state_change(chan, BT_CLOSED); + sk->sk_state = BT_CLOSED; sock_set_flag(sk, SOCK_ZAPPED); if (err) - __l2cap_chan_set_err(chan, err); + sk->sk_err = err; if (parent) { bt_accept_unlink(sk); @@ -388,118 +583,25 @@ static void l2cap_chan_del(struct l2cap_chan *chan, int err) } else sk->sk_state_change(sk); - release_sock(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); + sk->sk_send_head = NULL; + skb_queue_purge(TX_QUEUE(sk)); - skb_queue_purge(&chan->srej_q); + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM) { + if (l2cap_pi(sk)->sdu) + kfree_skb(l2cap_pi(sk)->sdu); - 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; - - l2cap_chan_lock(chan); - __clear_chan_timer(chan); - l2cap_chan_close(chan, ECONNRESET); - l2cap_chan_unlock(chan); - - 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 %s sk %p", chan, - state_to_string(chan->state), sk); - - switch (chan->state) { - case BT_LISTEN: - lock_sock(sk); - l2cap_chan_cleanup_listen(sk); - - __l2cap_state_change(chan, BT_CLOSED); - sock_set_flag(sk, SOCK_ZAPPED); - release_sock(sk); - 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); - } - - l2cap_chan_del(chan, reason); - break; - - case BT_CONNECT: - case BT_DISCONN: - l2cap_chan_del(chan, reason); - break; + skb_queue_purge(SREJ_QUEUE(sk)); - default: - lock_sock(sk); - sock_set_flag(sk, SOCK_ZAPPED); - release_sock(sk); - 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: @@ -507,16 +609,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: @@ -528,17 +630,18 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan) } /* Service level security */ -int l2cap_chan_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; @@ -548,150 +651,114 @@ static u8 l2cap_get_ident(struct l2cap_conn *conn) * 200 - 254 are used by utilities like l2ping, etc. */ - spin_lock(&conn->lock); + spin_lock_bh(&conn->lock); if (++conn->tx_ident > 128) conn->tx_ident = 1; id = conn->tx_ident; - spin_unlock(&conn->lock); + spin_unlock_bh(&conn->lock); 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; - - BT_DBG("code 0x%2.2x", code); + size_t len; + u16 partial_crc; + struct sk_buff *iter; + struct sk_buff *final_frag = skb; - if (!skb) - return; - - if (lmp_no_flush_capable(conn->hcon->hdev)) - flags = ACL_START_NO_FLUSH; + if (skb_has_frag_list(skb)) + len = skb_headlen(skb); else - flags = ACL_START; - - bt_cb(skb)->force_active = BT_POWER_FORCE_ACTIVE_ON; - skb->priority = HCI_PRIO_MAX; - - hci_send_acl(conn->hchan, skb, flags); -} + len = skb->len - L2CAP_FCS_SIZE; -static void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb) -{ - struct hci_conn *hcon = chan->conn->hcon; - u16 flags; + partial_crc = crc16(0, (u8 *) skb->data, len); - BT_DBG("chan %p, skb %p len %d priority %u", chan, skb, skb->len, - skb->priority); + skb_walk_frags(skb, iter) { + len = iter->len; + if (!iter->next) + len -= L2CAP_FCS_SIZE; - if (!test_bit(FLAG_FLUSHABLE, &chan->flags) && - lmp_no_flush_capable(hcon->hdev)) - flags = ACL_START_NO_FLUSH; - else - flags = ACL_START; + partial_crc = crc16(partial_crc, iter->data, len); + final_frag = iter; + } - bt_cb(skb)->force_active = test_bit(FLAG_FORCE_ACTIVE, &chan->flags); - hci_send_acl(chan->conn->hchan, 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, u32 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; - - if (chan->state != BT_CONNECTED) - return; - - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - hlen = L2CAP_EXT_HDR_SIZE; - else - hlen = L2CAP_ENH_HDR_SIZE; - - if (chan->fcs == L2CAP_FCS_CRC16) - hlen += L2CAP_FCS_SIZE; - - BT_DBG("chan %p, control 0x%8.8x", chan, control); - - count = min_t(unsigned int, conn->mtu, hlen); - - control |= __set_sframe(chan); - - if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state)) - control |= __set_ctrl_final(chan); + struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); + u8 flags; - if (test_and_clear_bit(CONN_SEND_PBIT, &chan->conn_state)) - control |= __set_ctrl_poll(chan); + 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_control(chan, control, skb_put(skb, __ctrl_size(chan))); - - if (chan->fcs == L2CAP_FCS_CRC16) { - u16 fcs = crc16(0, (u8 *)lh, count - L2CAP_FCS_SIZE); - put_unaligned_le16(fcs, skb_put(skb, L2CAP_FCS_SIZE)); - } - - skb->priority = HCI_PRIO_MAX; - l2cap_do_send(chan, skb); -} - -static inline void l2cap_send_rr_or_rnr(struct l2cap_chan *chan, u32 control) -{ - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR); - set_bit(CONN_RNR_SENT, &chan->conn_state); - } else - control |= __set_ctrl_super(chan, L2CAP_SUPER_RR); + if (lmp_no_flush_capable(conn->hcon->hdev)) + flags = ACL_START_NO_FLUSH; + else + flags = ACL_START; - control |= __set_reqseq(chan, chan->buffer_seq); + bt_cb(skb)->force_active = 1; - l2cap_send_sframe(chan, control); + hci_send_acl(conn->hcon, NULL, skb, flags); } -static inline int __l2cap_no_conn_pending(struct l2cap_chan *chan) +static inline int __l2cap_no_conn_pending(struct sock *sk) { - return !test_bit(CONF_CONNECT_PEND, &chan->conf_state); + return !(l2cap_pi(sk)->conf_state & L2CAP_CONF_CONNECT_PEND); } -static void l2cap_send_conn_req(struct l2cap_chan *chan) +static void l2cap_send_conn_req(struct sock *sk) { - struct l2cap_conn *conn = chan->conn; 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); - req.scid = cpu_to_le16(chan->scid); - req.psm = chan->psm; + l2cap_send_cmd(l2cap_pi(sk)->conn, l2cap_pi(sk)->ident, + L2CAP_CONN_REQ, sizeof(req), &req); +} - chan->ident = l2cap_get_ident(conn); +static void l2cap_send_create_chan_req(struct sock *sk, u8 amp_id) +{ + 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; - set_bit(CONF_CONNECT_PEND, &chan->conf_state); + l2cap_pi(sk)->conf_state |= L2CAP_CONF_LOCKSTEP; + l2cap_pi(sk)->ident = l2cap_get_ident(l2cap_pi(sk)->conn); - l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, sizeof(req), &req); + 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_chan_check_security(chan) && - __l2cap_no_conn_pending(chan)) - l2cap_send_conn_req(chan); + if (l2cap_check_security(sk) && __l2cap_no_conn_pending(sk)) { + l2cap_pi(sk)->conf_state |= L2CAP_CONF_CONNECT_PEND; + + if (l2cap_pi(sk)->amp_pref == + BT_AMP_POLICY_PREFER_AMP && + conn->fc_mask & L2CAP_FC_A2MP) + amp_create_physical(conn, sk); + else + l2cap_send_conn_req(sk); + } } else { struct l2cap_info_req req; req.type = cpu_to_le16(L2CAP_IT_FEAT_MASK); @@ -699,7 +766,8 @@ static void l2cap_do_start(struct l2cap_chan *chan) conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_SENT; conn->info_ident = l2cap_get_ident(conn); - schedule_delayed_work(&conn->info_timer, L2CAP_INFO_TIMEOUT); + mod_timer(&conn->info_timer, jiffies + + msecs_to_jiffies(L2CAP_INFO_TIMEOUT)); l2cap_send_cmd(conn, conn->info_ident, L2CAP_INFO_REQ, sizeof(req), &req); @@ -722,75 +790,90 @@ 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 = chan->sk; struct l2cap_disconn_req req; if (!conn) return; - if (chan->mode == L2CAP_MODE_ERTM) { - __clear_retrans_timer(chan); - __clear_monitor_timer(chan); - __clear_ack_timer(chan); + sk->sk_send_head = NULL; + 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); } - 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); - lock_sock(sk); - __l2cap_state_change(chan, BT_DISCONN); - __l2cap_chan_set_err(chan, err); - release_sock(sk); + 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); - mutex_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); - l2cap_chan_lock(chan); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + bh_lock_sock(sk); - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { - l2cap_chan_unlock(chan); + if (sk->sk_type != SOCK_SEQPACKET && + sk->sk_type != SOCK_STREAM) { + bh_unlock_sock(sk); continue; } - if (chan->state == BT_CONNECT) { - if (!l2cap_chan_check_security(chan) || - !__l2cap_no_conn_pending(chan)) { - l2cap_chan_unlock(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(chan, ECONNRESET); - l2cap_chan_unlock(chan); + 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; } - l2cap_send_conn_req(chan); + l2cap_pi(sk)->conf_state |= L2CAP_CONF_CONNECT_PEND; + + if (l2cap_pi(sk)->amp_pref == + BT_AMP_POLICY_PREFER_AMP && + conn->fc_mask & L2CAP_FC_A2MP) + amp_create_physical(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_chan_check_security(chan)) { - lock_sock(sk); + 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); @@ -799,86 +882,133 @@ 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); } - release_sock(sk); } else { rsp.result = cpu_to_le16(L2CAP_CR_PEND); 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) { - l2cap_chan_unlock(chan); + 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++; } - l2cap_chan_unlock(chan); + bh_unlock_sock(sk); } - mutex_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. +/* Find socket with fixed cid with given source and destination bdaddrs. + * Direction of the req/rsp must match. */ -static struct l2cap_chan *l2cap_global_chan_by_scid(int state, __le16 cid, bdaddr_t *src) +struct sock *l2cap_find_sock_by_fixed_cid_and_dir(__le16 cid, bdaddr_t *src, + bdaddr_t *dst, int incoming) { - struct l2cap_chan *c, *c1 = NULL; + struct sock *sk = NULL, *sk1 = NULL; + struct hlist_node *node; + + BT_DBG(" %d", incoming); - read_lock(&chan_list_lock); + read_lock(&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) { + + if (incoming && !l2cap_pi(sk)->incoming) + continue; - if (state && c->state != state) + if (!incoming && l2cap_pi(sk)->incoming) continue; - if (c->scid == cid) { + if (l2cap_pi(sk)->scid == cid && !bacmp(&bt_sk(sk)->dst, dst)) { /* 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) +/* Find socket with cid and source bdaddr. + * Returns closest match, locked. + */ +static struct sock *l2cap_get_sock_by_scid(int state, __le16 cid, bdaddr_t *src) { - struct sock *parent, *sk; - struct l2cap_chan *chan, *pchan; + struct sock *sk = NULL, *sk1 = NULL; + struct hlist_node *node; - BT_DBG(""); + read_lock(&l2cap_sk_list.lock); - /* Check if we have socket listening on cid */ - pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA, + sk_for_each(sk, node, &l2cap_sk_list.head) { + if (state && sk->sk_state != state) + continue; + + if (l2cap_pi(sk)->scid == cid) { + /* Exact match. */ + if (!bacmp(&bt_sk(sk)->src, src)) + break; + + /* Closest match */ + if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) + sk1 = sk; + } + } + + read_unlock(&l2cap_sk_list.lock); + + return node ? sk : sk1; +} + +static void l2cap_le_conn_ready(struct l2cap_conn *conn) +{ + struct l2cap_chan_list *list = &conn->chan_list; + struct sock *parent, *uninitialized_var(sk); + + BT_DBG(""); + + /* Check if we have socket listening on cid */ + parent = l2cap_get_sock_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA, conn->src); - if (!pchan) + if (!parent) return; - parent = pchan->sk; - - lock_sock(parent); + bh_lock_sock(parent); /* Check for backlog size */ if (sk_acceptq_is_full(parent)) { @@ -886,113 +1016,101 @@ 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(&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); + l2cap_pi(sk)->incoming = 1; bt_accept_enqueue(parent, sk); - l2cap_chan_add(conn, chan); + __l2cap_chan_add(conn, sk); - __set_chan_timer(chan, sk->sk_sndtimeo); - - __l2cap_state_change(chan, BT_CONNECTED); + sk->sk_state = BT_CONNECTED; parent->sk_data_ready(parent, 0); -clean: - release_sock(parent); -} - -static void l2cap_chan_ready(struct l2cap_chan *chan) -{ - struct sock *sk = chan->sk; - struct sock *parent; - - lock_sock(sk); - - 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); + write_unlock_bh(&list->lock); - if (parent) - parent->sk_data_ready(parent, 0); - - release_sock(sk); +clean: + bh_unlock_sock(parent); } 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); - if (conn->hcon->out && conn->hcon->type == LE_LINK) - smp_conn_security(conn, conn->hcon->pending_sec_level); + read_lock(&l->lock); - mutex_lock(&conn->chan_lock); + if (l->head) { + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + bh_lock_sock(sk); - list_for_each_entry(chan, &conn->chan_l, list) { + if (conn->hcon->type == LE_LINK) { + u8 sec_level = l2cap_pi(sk)->sec_level; + u8 pending_sec = conn->hcon->pending_sec_level; - l2cap_chan_lock(chan); + if (pending_sec > sec_level) + sec_level = pending_sec; - if (conn->hcon->type == LE_LINK) { - if (smp_conn_security(conn, chan->sec_level)) - l2cap_chan_ready(chan); + if (smp_conn_security(conn, sec_level)) + l2cap_chan_ready(sk); - } else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { - struct sock *sk = chan->sk; - __clear_chan_timer(chan); - lock_sock(sk); - __l2cap_state_change(chan, BT_CONNECTED); - sk->sk_state_change(sk); - release_sock(sk); + hci_conn_put(conn->hcon); - } else if (chan->state == BT_CONNECT) - l2cap_do_start(chan); + } 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 (sk->sk_state == BT_CONNECT) + l2cap_do_start(sk); - l2cap_chan_unlock(chan); + bh_unlock_sock(sk); + } + } else if (conn->hcon->type == LE_LINK) { + smp_conn_security(conn, BT_SECURITY_HIGH); } - mutex_unlock(&conn->chan_lock); + read_unlock(&l->lock); + + if (conn->hcon->out && conn->hcon->type == LE_LINK) + l2cap_le_conn_ready(conn); } /* 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); - mutex_lock(&conn->chan_lock); + read_lock(&l->lock); - list_for_each_entry(chan, &conn->chan_l, list) { - if (test_bit(FLAG_FORCE_RELIABLE, &chan->flags)) - __l2cap_chan_set_err(chan, err); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + if (l2cap_pi(sk)->force_reliable) + sk->sk_err = err; } - mutex_unlock(&conn->chan_lock); + read_unlock(&l->lock); } -static void l2cap_info_timeout(struct work_struct *work) +static void l2cap_info_timeout(unsigned long arg) { - struct l2cap_conn *conn = container_of(work, struct l2cap_conn, - info_timer.work); + struct l2cap_conn *conn = (void *) arg; conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; conn->info_ident = 0; @@ -1000,78 +1118,21 @@ static void l2cap_info_timeout(struct work_struct *work) 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; - - if (!conn) - return; - - BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); - - kfree_skb(conn->rx_skb); - - mutex_lock(&conn->chan_lock); - - /* Kill channels */ - list_for_each_entry_safe(chan, l, &conn->chan_l, list) { - l2cap_chan_lock(chan); - - l2cap_chan_del(chan, err); - - l2cap_chan_unlock(chan); - - chan->ops->close(chan->data); - } - - mutex_unlock(&conn->chan_lock); - - hci_chan_del(conn->hchan); - - if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) - cancel_delayed_work_sync(&conn->info_timer); - - if (test_and_clear_bit(HCI_CONN_LE_SMP_PEND, &hcon->flags)) { - cancel_delayed_work_sync(&conn->security_timer); - smp_chan_destroy(conn); - } - - hcon->l2cap_data = NULL; - kfree(conn); -} - -static void security_timeout(struct work_struct *work) -{ - struct l2cap_conn *conn = container_of(work, struct l2cap_conn, - security_timer.work); - - l2cap_conn_del(conn->hcon, ETIMEDOUT); -} - static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status) { struct l2cap_conn *conn = hcon->l2cap_data; - struct hci_chan *hchan; if (conn || status) return conn; - hchan = hci_chan_create(hcon); - if (!hchan) - return NULL; - conn = kzalloc(sizeof(struct l2cap_conn), GFP_ATOMIC); - if (!conn) { - hci_chan_del(hchan); + if (!conn) return NULL; - } hcon->l2cap_data = conn; conn->hcon = hcon; - conn->hchan = hchan; - BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan); + BT_DBG("hcon %p conn %p", hcon, conn); if (hcon->hdev->le_mtu && hcon->type == LE_LINK) conn->mtu = hcon->hdev->le_mtu; @@ -1084,59 +1145,110 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status) conn->feat_mask = 0; spin_lock_init(&conn->lock); - mutex_init(&conn->chan_lock); - - INIT_LIST_HEAD(&conn->chan_l); + rwlock_init(&conn->chan_list.lock); if (hcon->type == LE_LINK) - INIT_DELAYED_WORK(&conn->security_timer, security_timeout); + setup_timer(&hcon->smp_timer, smp_timeout, + (unsigned long) conn); else - INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout); + setup_timer(&conn->info_timer, l2cap_info_timeout, + (unsigned long) conn); - conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; + conn->disc_reason = 0x13; return conn; } +static void l2cap_conn_del(struct hci_conn *hcon, int err, u8 is_process) +{ + struct l2cap_conn *conn = hcon->l2cap_data; + struct sock *sk; + struct sock *next; + + if (!conn) + return; + + BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + + if ((conn->hcon == hcon) && (conn->rx_skb)) + kfree_skb(conn->rx_skb); + + BT_DBG("conn->hcon %p", conn->hcon); + + /* 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; + if (is_process) + lock_sock(sk); + else + bh_lock_sock(sk); + l2cap_chan_del(sk, err); + if (is_process) + release_sock(sk); + else + 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 l2cap_chan *l2cap_global_chan_by_psm(int state, __le16 psm, bdaddr_t *src) +static struct sock *l2cap_get_sock_by_psm(int state, __le16 psm, 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->psm == psm) { + 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, __le16 psm, u16 cid, bdaddr_t *dst) +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; struct hci_conn *hcon; struct hci_dev *hdev; @@ -1144,136 +1256,104 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, bdaddr_t *d 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) return -EHOSTUNREACH; - hci_dev_lock(hdev); - - l2cap_chan_lock(chan); - - /* PSM must be odd and lsb of upper byte must be 0 */ - if ((__le16_to_cpu(psm) & 0x0101) != 0x0001 && !cid && - chan->chan_type != L2CAP_CHAN_RAW) { - err = -EINVAL; - goto done; - } - - if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED && !(psm || cid)) { - err = -EINVAL; - goto done; - } - - switch (chan->mode) { - case L2CAP_MODE_BASIC: - break; - case L2CAP_MODE_ERTM: - case L2CAP_MODE_STREAMING: - if (!disable_ertm) - break; - /* fall through */ - default: - err = -ENOTSUPP; - goto done; - } - - lock_sock(sk); - - switch (sk->sk_state) { - case BT_CONNECT: - case BT_CONNECT2: - case BT_CONFIG: - /* Already connecting */ - err = 0; - release_sock(sk); - goto done; - - case BT_CONNECTED: - /* Already connected */ - err = -EISCONN; - release_sock(sk); - goto done; - - case BT_OPEN: - case BT_BOUND: - /* Can connect */ - break; - - default: - err = -EBADFD; - release_sock(sk); - goto done; - } - - /* Set destination address and psm */ - bacpy(&bt_sk(sk)->dst, dst); - - release_sock(sk); + hci_dev_lock_bh(hdev); - chan->psm = psm; - chan->dcid = cid; + auth_type = l2cap_get_auth_type(sk); - auth_type = l2cap_get_auth_type(chan); + 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 (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); + conn = hcon->l2cap_data; + } else { + if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA) + hcon = hci_le_connect(hdev, 0, dst, + l2cap_pi(sk)->sec_level, auth_type, + &bt_sk(sk)->le_params); + else + hcon = hci_connect(hdev, ACL_LINK, 0, dst, + l2cap_pi(sk)->sec_level, auth_type); - if (IS_ERR(hcon)) { - err = PTR_ERR(hcon); - 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; + 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_unlock(chan); - l2cap_chan_add(conn, chan); - l2cap_chan_lock(chan); - - l2cap_state_change(chan, BT_CONNECT); - __set_chan_timer(chan, sk->sk_sndtimeo); + l2cap_chan_add(conn, sk); - if (hcon->state == BT_CONNECTED) { - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { - __clear_chan_timer(chan); - if (l2cap_chan_check_security(chan)) - l2cap_state_change(chan, BT_CONNECTED); + if ((l2cap_pi(sk)->fixed_channel) || + (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA && + hcon->state == BT_CONNECTED)) { + sk->sk_state = BT_CONNECTED; + sk->sk_state_change(sk); + } else { + sk->sk_state = BT_CONNECT; + /* If we have valid LE Params, let timeout override default */ + if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA && + l2cap_sock_le_params_valid(&bt_sk(sk)->le_params)) { + u16 timeout = bt_sk(sk)->le_params.conn_timeout; + + if (timeout) + l2cap_sock_set_timer(sk, + msecs_to_jiffies(timeout*1000)); } else - l2cap_do_start(chan); + 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; done: - l2cap_chan_unlock(chan); - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); hci_dev_put(hdev); return err; } 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; @@ -1285,7 +1365,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) @@ -1296,325 +1375,349 @@ int __l2cap_wait_ack(struct sock *sk) return err; } -static void l2cap_monitor_timeout(struct work_struct *work) +static void l2cap_ertm_tx_worker(struct work_struct *work) { - struct l2cap_chan *chan = container_of(work, struct l2cap_chan, - monitor_timer.work); - - BT_DBG("chan %p", chan); + struct l2cap_pinfo *pi = + container_of(work, struct l2cap_pinfo, tx_work); + struct sock *sk = (struct sock *)pi; + BT_DBG("%p", pi); - l2cap_chan_lock(chan); + lock_sock(sk); + l2cap_ertm_send(sk); + release_sock(sk); + sock_put(sk); +} - if (chan->retry_count >= chan->remote_max_tx) { - l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED); - l2cap_chan_unlock(chan); - l2cap_chan_put(chan); - return; - } +static void l2cap_skb_destructor(struct sk_buff *skb) +{ + struct sock *sk = skb->sk; + int queued; + int keep_sk = 0; - chan->retry_count++; - __set_monitor_timer(chan); + queued = atomic_sub_return(1, &l2cap_pi(sk)->ertm_queued); + if (queued < L2CAP_MIN_ERTM_QUEUED) + keep_sk = queue_work(_l2cap_wq, &l2cap_pi(sk)->tx_work); - l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_POLL); - l2cap_chan_unlock(chan); - l2cap_chan_put(chan); + if (!keep_sk) + sock_put(sk); } -static void l2cap_retrans_timeout(struct work_struct *work) +void l2cap_do_send(struct sock *sk, struct sk_buff *skb) { - struct l2cap_chan *chan = container_of(work, struct l2cap_chan, - retrans_timer.work); - - BT_DBG("chan %p", chan); + struct l2cap_pinfo *pi = l2cap_pi(sk); - l2cap_chan_lock(chan); + BT_DBG("sk %p, skb %p len %d", sk, skb, skb->len); - chan->retry_count = 1; - __set_monitor_timer(chan); + 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; - set_bit(CONN_WAIT_F, &chan->conn_state); + bt_cb(skb)->force_active = pi->force_active; + BT_DBG("Sending on BR/EDR connection %p", pi->conn->hcon); - l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_POLL); + if (lmp_no_flush_capable(pi->conn->hcon->hdev) && + !l2cap_pi(sk)->flushable) + flags = ACL_START_NO_FLUSH; + else + flags = ACL_START; - l2cap_chan_unlock(chan); - l2cap_chan_put(chan); + hci_send_acl(pi->conn->hcon, NULL, skb, flags); + } } -static void l2cap_drop_acked_frames(struct l2cap_chan *chan) +int l2cap_ertm_send(struct sock *sk) { - struct sk_buff *skb; - - while ((skb = skb_peek(&chan->tx_q)) && - chan->unacked_frames) { - if (bt_cb(skb)->tx_seq == chan->expected_ack_seq) - break; - - skb = skb_dequeue(&chan->tx_q); - kfree_skb(skb); - - chan->unacked_frames--; - } + struct sk_buff *skb, *tx_skb; + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct bt_l2cap_control *control; + int sent = 0; - if (!chan->unacked_frames) - __clear_retrans_timer(chan); -} + BT_DBG("sk %p", sk); -static void l2cap_streaming_send(struct l2cap_chan *chan) -{ - struct sk_buff *skb; - u32 control; - u16 fcs; + if (sk->sk_state != BT_CONNECTED) + return -ENOTCONN; - while ((skb = skb_dequeue(&chan->tx_q))) { - control = __get_control(chan, skb->data + L2CAP_HDR_SIZE); - control |= __set_txseq(chan, chan->next_tx_seq); - __put_control(chan, 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 - L2CAP_FCS_SIZE); - put_unaligned_le16(fcs, - skb->data + skb->len - L2CAP_FCS_SIZE); - } + 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 = __next_seq(chan, chan->next_tx_seq); - } -} + skb = sk->sk_send_head; -static void l2cap_retransmit_one_frame(struct l2cap_chan *chan, u16 tx_seq) -{ - struct sk_buff *skb, *tx_skb; - u16 fcs; - u32 control; + 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; - while (bt_cb(skb)->tx_seq != tx_seq) { - if (skb_queue_is_last(&chan->tx_q, skb)) - return; + 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); + } - skb = skb_queue_next(&chan->tx_q, skb); - } + if (pi->fcs == L2CAP_FCS_CRC16) + apply_fcs(skb); - if (chan->remote_max_tx && - bt_cb(skb)->retries == chan->remote_max_tx) { - l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED); - return; - } + /* 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); - tx_skb = skb_clone(skb, GFP_ATOMIC); - bt_cb(skb)->retries++; + if (!tx_skb) + break; - control = __get_control(chan, tx_skb->data + L2CAP_HDR_SIZE); - control &= __get_sar_mask(chan); + sock_hold(sk); + tx_skb->sk = sk; + tx_skb->destructor = l2cap_skb_destructor; + atomic_inc(&pi->ertm_queued); - if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state)) - control |= __set_ctrl_final(chan); + l2cap_ertm_start_retrans_timer(pi); - control |= __set_reqseq(chan, chan->buffer_seq); - control |= __set_txseq(chan, tx_seq); + pi->next_tx_seq = __next_seq(pi->next_tx_seq, pi); + pi->unacked_frames += 1; + pi->frames_sent += 1; + sent += 1; - __put_control(chan, control, tx_skb->data + L2CAP_HDR_SIZE); + 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); - if (chan->fcs == L2CAP_FCS_CRC16) { - fcs = crc16(0, (u8 *)tx_skb->data, - tx_skb->len - L2CAP_FCS_SIZE); - put_unaligned_le16(fcs, - tx_skb->data + tx_skb->len - L2CAP_FCS_SIZE); + l2cap_do_send(sk, tx_skb); + BT_DBG("Sent txseq %d", (int)control->txseq); } - 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; } -static 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 fcs; - u32 control; - 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_control(chan, tx_skb->data + L2CAP_HDR_SIZE); - control &= __get_sar_mask(chan); + BT_DBG("skb %p", skb); - if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state)) - control |= __set_ctrl_final(chan); + bt_cb(skb)->retries = 1; + control = &bt_cb(skb)->control; - control |= __set_reqseq(chan, chan->buffer_seq); - control |= __set_txseq(chan, chan->next_tx_seq); + BT_DBG("control %p", control); - __put_control(chan, control, tx_skb->data + L2CAP_HDR_SIZE); + control->reqseq = 0; + control->txseq = pi->next_tx_seq; - if (chan->fcs == L2CAP_FCS_CRC16) { - fcs = crc16(0, (u8 *)skb->data, - tx_skb->len - L2CAP_FCS_SIZE); - put_unaligned_le16(fcs, skb->data + - tx_skb->len - L2CAP_FCS_SIZE); + 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); - - __set_retrans_timer(chan); - - bt_cb(skb)->tx_seq = chan->next_tx_seq; + if (pi->fcs == L2CAP_FCS_CRC16) + apply_fcs(skb); - chan->next_tx_seq = __next_seq(chan, chan->next_tx_seq); + l2cap_do_send(sk, skb); - if (bt_cb(skb)->retries == 1) { - chan->unacked_frames++; + BT_DBG("Sent txseq %d", (int)control->txseq); - if (!nsent++) - __clear_ack_timer(chan); - } - - 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); + 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) { - u32 control = 0; - - control |= __set_reqseq(chan, chan->buffer_seq); - - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR); - 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 |= __set_ctrl_super(chan, L2CAP_SUPER_RR); - l2cap_send_sframe(chan, control); + return 0; } -static void l2cap_send_ack(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) { - __clear_ack_timer(chan); - __l2cap_send_ack(chan); -} + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + struct sk_buff **frag; + struct sk_buff *final; + int err, sent = 0; -static void l2cap_send_srejtail(struct l2cap_chan *chan) -{ - struct srej_list *tail; - u32 control; + BT_DBG("sk %p, msg %p, len %d, count %d, skb %p", sk, + msg, (int)len, (int)count, skb); - control = __set_ctrl_super(chan, L2CAP_SUPER_SREJ); - control |= __set_ctrl_final(chan); + if (!conn) + return -ENOTCONN; - tail = list_entry((&chan->srej_l)->prev, struct srej_list, list); - control |= __set_reqseq(chan, tail->tx_seq); + /* 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); + } - l2cap_send_sframe(chan, control); -} - -static inline int l2cap_skbuff_fromiovec(struct l2cap_chan *chan, - struct msghdr *msg, int len, - int count, struct sk_buff *skb) -{ - struct l2cap_conn *conn = chan->conn; - struct sk_buff **frag; - int err, sent = 0; - - if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) - return -EFAULT; + 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 = chan->ops->alloc_skb(chan, 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; - (*frag)->priority = skb->priority; + /* 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; } -static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, - struct msghdr *msg, size_t len, - u32 priority) +struct sk_buff *l2cap_create_connless_pdu(struct sock *sk, struct msghdr *msg, size_t len) { - 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 + L2CAP_PSMLEN_SIZE; + int err, count, hlen = L2CAP_HDR_SIZE + 2; struct l2cap_hdr *lh; - BT_DBG("chan %p len %d priority %u", chan, (int)len, priority); + BT_DBG("sk %p len %d", sk, (int)len); count = min_t(unsigned int, (conn->mtu - hlen), len); - - skb = chan->ops->alloc_skb(chan, count + hlen, - msg->msg_flags & MSG_DONTWAIT, &err); - + skb = bt_skb_send_alloc(sk, count + hlen, + msg->msg_flags & MSG_DONTWAIT, &err); if (!skb) return ERR_PTR(err); - skb->priority = priority; - /* 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(chan, 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); @@ -1622,33 +1725,27 @@ static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, return skb; } -static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, - struct msghdr *msg, size_t len, - u32 priority) +struct sk_buff *l2cap_create_basic_pdu(struct sock *sk, struct msghdr *msg, size_t len) { - 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; - BT_DBG("chan %p len %d", chan, (int)len); + BT_DBG("sk %p len %d", sk, (int)len); count = min_t(unsigned int, (conn->mtu - hlen), len); - - skb = chan->ops->alloc_skb(chan, count + hlen, - msg->msg_flags & MSG_DONTWAIT, &err); - + skb = bt_skb_send_alloc(sk, count + hlen, + msg->msg_flags & MSG_DONTWAIT, &err); if (!skb) return ERR_PTR(err); - skb->priority = priority; - /* 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(chan, 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); @@ -1656,1748 +1753,3902 @@ static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, return skb; } -static struct sk_buff *l2cap_create_iframe_pdu(struct l2cap_chan *chan, - struct msghdr *msg, size_t len, - u32 control, u16 sdulen) +struct sk_buff *l2cap_create_iframe_pdu(struct sock *sk, + struct msghdr *msg, size_t len, + u16 sdulen, int reseg) { - struct l2cap_conn *conn = chan->conn; struct sk_buff *skb; int err, count, hlen; + int reserve = 0; struct l2cap_hdr *lh; + u8 fcs = l2cap_pi(sk)->fcs; - BT_DBG("chan %p len %d", chan, (int)len); - - if (!conn) - return ERR_PTR(-ENOTCONN); - - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - hlen = L2CAP_EXT_HDR_SIZE; + if (l2cap_pi(sk)->extended_control) + hlen = L2CAP_EXTENDED_HDR_SIZE; else - hlen = L2CAP_ENH_HDR_SIZE; + hlen = L2CAP_ENHANCED_HDR_SIZE; if (sdulen) hlen += L2CAP_SDULEN_SIZE; - if (chan->fcs == L2CAP_FCS_CRC16) + if (fcs == L2CAP_FCS_CRC16) hlen += L2CAP_FCS_SIZE; - count = min_t(unsigned int, (conn->mtu - hlen), len); + BT_DBG("sk %p, msg %p, len %d, sdulen %d, hlen %d", + sk, msg, (int)len, (int)sdulen, hlen); - skb = chan->ops->alloc_skb(chan, count + hlen, - msg->msg_flags & MSG_DONTWAIT, &err); + 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)); + lh->cid = cpu_to_le16(l2cap_pi(sk)->dcid); + lh->len = cpu_to_le16(len + hlen - L2CAP_HDR_SIZE); - __put_control(chan, control, skb_put(skb, __ctrl_size(chan))); + /* 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, L2CAP_SDULEN_SIZE)); - err = l2cap_skbuff_fromiovec(chan, 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, L2CAP_FCS_SIZE)); - bt_cb(skb)->retries = 0; return skb; } -static 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; - u32 control; - size_t size = 0; + struct l2cap_pinfo *pi; + struct sk_buff *acked_skb; + u16 ackseq; - skb_queue_head_init(&sar_queue); - control = __set_ctrl_sar(chan, L2CAP_SAR_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 = __set_ctrl_sar(chan, L2CAP_SAR_CONTINUE); - buflen = chan->remote_mps; - } else { - control = __set_ctrl_sar(chan, L2CAP_SAR_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)) { - __skb_queue_tail(&sar_queue, skb); - len -= buflen; - size += buflen; + 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--; + } } - skb_queue_splice_tail(&sar_queue, &chan->tx_q); - if (chan->tx_send_head == NULL) - chan->tx_send_head = sar_queue.next; - return size; + 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); } -int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, - u32 priority) +static struct sk_buff *l2cap_create_sframe_pdu(struct sock *sk, u32 control) { struct sk_buff *skb; - u32 control; - int err; + int len; + int reserve = 0; + struct l2cap_hdr *lh; - /* Connectionless channel */ - if (chan->chan_type == L2CAP_CHAN_CONN_LESS) { - skb = l2cap_create_connless_pdu(chan, msg, len, priority); - if (IS_ERR(skb)) - return PTR_ERR(skb); + if (l2cap_pi(sk)->extended_control) + len = L2CAP_EXTENDED_HDR_SIZE; + else + len = L2CAP_ENHANCED_HDR_SIZE; - l2cap_do_send(chan, skb); - return len; - } + if (l2cap_pi(sk)->fcs == L2CAP_FCS_CRC16) + len += L2CAP_FCS_SIZE; - switch (chan->mode) { - case L2CAP_MODE_BASIC: - /* Check outgoing MTU */ - if (len > chan->omtu) - return -EMSGSIZE; + /* Allocate extra headroom for Qualcomm PAL */ + if (l2cap_pi(sk)->ampcon && + l2cap_pi(sk)->ampcon->hdev->manufacturer == 0x001d) + reserve = BT_SKB_RESERVE_80211; - /* Create a basic PDU */ - skb = l2cap_create_basic_pdu(chan, msg, len, priority); - if (IS_ERR(skb)) - return PTR_ERR(skb); + skb = bt_skb_alloc(len + reserve, GFP_ATOMIC); - l2cap_do_send(chan, skb); - err = len; - break; + if (!skb) + return ERR_PTR(-ENOMEM); - case L2CAP_MODE_ERTM: - case L2CAP_MODE_STREAMING: - /* Entire SDU fits into one PDU */ - if (len <= chan->remote_mps) { - control = __set_ctrl_sar(chan, L2CAP_SAR_UNSEGMENTED); - skb = l2cap_create_iframe_pdu(chan, msg, len, control, - 0); - if (IS_ERR(skb)) - return PTR_ERR(skb); + if (reserve) + skb_reserve(skb, reserve); - __skb_queue_tail(&chan->tx_q, skb); + 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 (chan->tx_send_head == NULL) - chan->tx_send_head = skb; + if (l2cap_pi(sk)->extended_control) + put_unaligned_le32(control, skb_put(skb, 4)); + else + put_unaligned_le16(control, skb_put(skb, 2)); - } else { - /* Segment SDU into multiples PDUs */ - err = l2cap_sar_segment_sdu(chan, msg, len); - if (err < 0) - return err; - } + 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)); + } - if (chan->mode == L2CAP_MODE_STREAMING) { - l2cap_streaming_send(chan); - err = len; - break; - } + return skb; +} - if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) && - test_bit(CONN_WAIT_F, &chan->conn_state)) { - err = len; - break; - } +static void l2cap_ertm_send_sframe(struct sock *sk, + struct bt_l2cap_control *control) +{ + struct l2cap_pinfo *pi; + struct sk_buff *skb; + u32 control_field; - err = l2cap_ertm_send(chan); - if (err >= 0) - err = len; + BT_DBG("sk %p, control %p", sk, control); - break; + if (control->frame_type != 's') + return; - default: - BT_DBG("bad state %1.1x", chan->mode); - err = -EBADFD; + pi = l2cap_pi(sk); + + 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; } - return err; + if ((pi->conn_state & L2CAP_CONN_SEND_FBIT) && !control->poll) { + control->final = 1; + pi->conn_state &= ~L2CAP_CONN_SEND_FBIT; + } + + 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; + + if (control->super != L2CAP_SFRAME_SREJ) { + pi->last_acked_seq = control->reqseq; + l2cap_ertm_stop_ack_timer(pi); + } + + BT_DBG("reqseq %d, final %d, poll %d, super %d", (int) control->reqseq, + (int) control->final, (int) control->poll, + (int) control->super); + + if (pi->extended_control) + control_field = __pack_extended_control(control); + else + control_field = __pack_enhanced_control(control); + + skb = l2cap_create_sframe_pdu(sk, control_field); + if (!IS_ERR(skb)) + l2cap_do_send(sk, skb); } -/* 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_ack(struct sock *sk) { - struct sk_buff *nskb; - struct l2cap_chan *chan; + 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("conn %p", conn); + BT_DBG("sk %p", sk); + BT_DBG("last_acked_seq %d, buffer_seq %d", (int)pi->last_acked_seq, + (int)pi->buffer_seq); - mutex_lock(&conn->chan_lock); + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; - list_for_each_entry(chan, &conn->chan_l, list) { - struct sock *sk = chan->sk; - if (chan->chan_type != L2CAP_CHAN_RAW) - continue; + 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; + } - /* Don't send frame to the socket it came from */ - if (skb->sk == sk) - continue; - nskb = skb_clone(skb, GFP_ATOMIC); - if (!nskb) - continue; + /* 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 (chan->ops->recv(chan->data, nskb)) - kfree_skb(nskb); + if (frames_to_ack) + l2cap_ertm_start_ack_timer(pi); } - - mutex_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_rr_or_rnr(struct sock *sk, bool poll) { - struct sk_buff *skb, **frag; - struct l2cap_cmd_hdr *cmd; - struct l2cap_hdr *lh; - int len, count; + struct l2cap_pinfo *pi; + struct bt_l2cap_control control; - 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, conn->mtu, len); + BT_DBG("sk %p, poll %d", sk, (int) poll); - skb = bt_skb_alloc(count, GFP_ATOMIC); - if (!skb) - return NULL; + pi = l2cap_pi(sk); - lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->len = cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen); + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.poll = poll; - if (conn->hcon->type == LE_LINK) - lh->cid = cpu_to_le16(L2CAP_CID_LE_SIGNALING); + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) + control.super = L2CAP_SFRAME_RNR; else - lh->cid = cpu_to_le16(L2CAP_CID_SIGNALING); + control.super = L2CAP_SFRAME_RR; - cmd = (struct l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE); - cmd->code = code; - cmd->ident = ident; - cmd->len = cpu_to_le16(dlen); + control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &control); +} - if (dlen) { - count -= L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE; - memcpy(skb_put(skb, count), data, count); - data += count; +static void l2cap_ertm_send_i_or_rr_or_rnr(struct sock *sk) +{ + struct l2cap_pinfo *pi; + struct bt_l2cap_control control; + + BT_DBG("sk %p", sk); + + pi = l2cap_pi(sk); + + 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 (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + control.super = L2CAP_SFRAME_RNR; + l2cap_ertm_send_sframe(sk, &control); } - len -= skb->len; + if ((pi->conn_state & L2CAP_CONN_REMOTE_BUSY) && + (pi->unacked_frames > 0)) + l2cap_ertm_start_retrans_timer(pi); - /* Continuation fragments (no L2CAP header) */ - frag = &skb_shinfo(skb)->frag_list; - while (len) { - count = min_t(unsigned int, conn->mtu, len); + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; - *frag = bt_skb_alloc(count, GFP_ATOMIC); - if (!*frag) - goto fail; + /* Send pending iframes */ + l2cap_ertm_send(sk); - memcpy(skb_put(*frag, count), data, count); + 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); + } +} - len -= count; - data += count; +static void l2cap_ertm_send_srej(struct sock *sk, u16 txseq) +{ + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; + u16 seq; - frag = &(*frag)->next; + 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); + } } - return skb; + pi->expected_tx_seq = __next_seq(txseq, pi); +} -fail: - kfree_skb(skb); - return NULL; +static void l2cap_ertm_send_srej_tail(struct sock *sk) +{ + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; + + BT_DBG("sk %p", sk); + + pi = l2cap_pi(sk); + + if (pi->srej_list.tail == L2CAP_SEQ_LIST_CLEAR) + return; + + 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); } -static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val) +static void l2cap_ertm_send_srej_list(struct sock *sk, u16 txseq) { - struct l2cap_conf_opt *opt = *ptr; - int len; + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; + u16 initial_head; + u16 seq; - len = L2CAP_CONF_OPT_SIZE + opt->len; - *ptr += len; + BT_DBG("sk %p, txseq %d", sk, (int) txseq); - *type = opt->type; - *olen = opt->len; + pi = l2cap_pi(sk); + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.super = L2CAP_SFRAME_SREJ; - switch (opt->len) { - case 1: - *val = *((u8 *) opt->val); - break; + /* Capture initial list head to allow only one pass through the list. */ + initial_head = pi->srej_list.head; - case 2: - *val = get_unaligned_le16(opt->val); - break; + do { + seq = l2cap_seq_list_pop(&pi->srej_list); + if ((seq == txseq) || (seq == L2CAP_SEQ_LIST_CLEAR)) + break; - case 4: - *val = get_unaligned_le32(opt->val); - break; + control.reqseq = seq; + l2cap_ertm_send_sframe(sk, &control); + l2cap_seq_list_append(&pi->srej_list, seq); + } while (pi->srej_list.head != initial_head); +} - default: - *val = (unsigned long) opt->val; - break; - } +static void l2cap_ertm_abort_rx_srej_sent(struct sock *sk) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + BT_DBG("sk %p", sk); - BT_DBG("type 0x%2.2x len %d val 0x%lx", *type, opt->len, *val); - return len; + 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 void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, 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; + struct l2cap_pinfo *pi; + int err = 0; - BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val); + BT_DBG("sk %p, control %p, skbs %p, event %d", sk, control, skbs, + (int)event); + pi = l2cap_pi(sk); - opt->type = type; - opt->len = len; + switch (event) { + case L2CAP_ERTM_EVENT_DATA_REQUEST: + if (sk->sk_send_head == NULL) + sk->sk_send_head = skb_peek(skbs); - switch (len) { - case 1: - *((u8 *) opt->val) = 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: - 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->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: - memcpy(opt->val, (void *) val, len); break; } - *ptr += L2CAP_CONF_OPT_SIZE + len; + return err; } -static void l2cap_add_opt_efs(void **ptr, struct l2cap_chan *chan) +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_efs efs; + struct l2cap_pinfo *pi; + int err = 0; - switch (chan->mode) { - case L2CAP_MODE_ERTM: - efs.id = chan->local_id; - efs.stype = chan->local_stype; - efs.msdu = cpu_to_le16(chan->local_msdu); - efs.sdu_itime = cpu_to_le32(chan->local_sdu_itime); - efs.acc_lat = cpu_to_le32(L2CAP_DEFAULT_ACC_LAT); - efs.flush_to = cpu_to_le32(L2CAP_DEFAULT_FLUSH_TO); - break; + BT_DBG("sk %p, control %p, skbs %p, event %d", sk, control, skbs, + (int)event); + pi = l2cap_pi(sk); - case L2CAP_MODE_STREAMING: - efs.id = 1; - efs.stype = L2CAP_SERV_BESTEFFORT; - efs.msdu = cpu_to_le16(chan->local_msdu); - efs.sdu_itime = cpu_to_le32(chan->local_sdu_itime); - efs.acc_lat = 0; - efs.flush_to = 0; + 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); + } + + l2cap_ertm_send_ack(sk); + 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: - return; + break; } - l2cap_add_conf_opt(ptr, L2CAP_CONF_EFS, sizeof(efs), - (unsigned long) &efs); + return err; } -static void l2cap_ack_timeout(struct work_struct *work) +int l2cap_ertm_tx(struct sock *sk, struct bt_l2cap_control *control, + struct sk_buff_head *skbs, u8 event) { - struct l2cap_chan *chan = container_of(work, struct l2cap_chan, - ack_timer.work); - - BT_DBG("chan %p", chan); + struct l2cap_pinfo *pi; + int err = 0; - l2cap_chan_lock(chan); + BT_DBG("sk %p, control %p, skbs %p, event %d, state %d", + sk, control, skbs, (int)event, l2cap_pi(sk)->tx_state); - __l2cap_send_ack(chan); + pi = l2cap_pi(sk); - l2cap_chan_unlock(chan); + 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; + } - l2cap_chan_put(chan); + 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) { - chan->expected_ack_seq = 0; - chan->unacked_frames = 0; - chan->buffer_seq = 0; - chan->num_acked = 0; - chan->frames_sent = 0; + struct sk_buff *skb; + u16 sdu_len; + size_t pdu_len; + int err = 0; + u8 sar; - INIT_DELAYED_WORK(&chan->retrans_timer, l2cap_retrans_timeout); - INIT_DELAYED_WORK(&chan->monitor_timer, l2cap_monitor_timeout); - INIT_DELAYED_WORK(&chan->ack_timer, l2cap_ack_timeout); + BT_DBG("sk %p, msg %p, len %d", sk, msg, (int)len); - skb_queue_head_init(&chan->srej_q); + /* 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. + */ - INIT_LIST_HEAD(&chan->srej_l); -} + /* PDU size is derived from the HCI MTU */ + pdu_len = l2cap_pi(sk)->conn->mtu; -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; - } -} + /* 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); -static inline bool __l2cap_ews_supported(struct l2cap_chan *chan) -{ - return enable_hs && chan->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW; -} + /* Adjust for largest possible L2CAP overhead. */ + pdu_len -= L2CAP_EXTENDED_HDR_SIZE + L2CAP_FCS_SIZE; -static inline bool __l2cap_efs_supported(struct l2cap_chan *chan) -{ - return enable_hs && chan->conn->feat_mask & L2CAP_FEAT_EXT_FLOW; -} + /* Remote device may have requested smaller PDUs */ + pdu_len = min_t(size_t, pdu_len, l2cap_pi(sk)->remote_mps); -static inline void l2cap_txwin_setup(struct l2cap_chan *chan) -{ - if (chan->tx_win > L2CAP_DEFAULT_TX_WINDOW && - __l2cap_ews_supported(chan)) { - /* use extended control field */ - set_bit(FLAG_EXT_CTRL, &chan->flags); - chan->tx_win_max = L2CAP_DEFAULT_EXT_WINDOW; + if (len <= pdu_len) { + sar = L2CAP_SAR_UNSEGMENTED; + sdu_len = 0; + pdu_len = len; } else { - chan->tx_win = min_t(u16, chan->tx_win, - L2CAP_DEFAULT_TX_WINDOW); - chan->tx_win_max = L2CAP_DEFAULT_TX_WINDOW; + 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; - u16 size; + 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); - if (__l2cap_efs_supported(chan)) - set_bit(FLAG_EFS_ENABLE, &chan->flags); + len -= pdu_len; + if (sdu_len) { + sdu_len = 0; + pdu_len += L2CAP_SDULEN_SIZE; + } - /* fall through */ - default: - chan->mode = l2cap_select_mode(rfc.mode, chan->conn->feat_mask); - break; + 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.max_transmit = chan->max_tx; - rfc.retrans_timeout = 0; - rfc.monitor_timeout = 0; + if (iv->iov_len + skb->len > veclen) + return -ENOMEM; - size = min_t(u16, L2CAP_DEFAULT_MAX_PDU_SIZE, chan->conn->mtu - - L2CAP_EXT_HDR_SIZE - - L2CAP_SDULEN_SIZE - - L2CAP_FCS_SIZE); - rfc.max_pdu_size = cpu_to_le16(size); + memcpy(iv->iov_base + iv->iov_len, skb->data, skb->len); + iv->iov_len += skb->len; - l2cap_txwin_setup(chan); + skb_walk_frags(skb, frag_iter) { + if (iv->iov_len + skb->len > veclen) + return -ENOMEM; - rfc.txwin_size = min_t(u16, chan->tx_win, - L2CAP_DEFAULT_TX_WINDOW); + 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; + } - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), - (unsigned long) &rfc); + return 0; +} - if (test_bit(FLAG_EFS_ENABLE, &chan->flags)) - l2cap_add_opt_efs(&ptr, chan); +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); - } + if (skb_queue_empty(queue)) + return 0; - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - l2cap_add_conf_opt(&ptr, L2CAP_CONF_EWS, 2, - chan->tx_win); - break; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = (struct iovec *) &iv; - 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; + buflen = pi->omtu + L2CAP_FCS_SIZE; + buf = kzalloc(buflen, GFP_TEMPORARY); - size = min_t(u16, L2CAP_DEFAULT_MAX_PDU_SIZE, chan->conn->mtu - - L2CAP_EXT_HDR_SIZE - - L2CAP_SDULEN_SIZE - - L2CAP_FCS_SIZE); - rfc.max_pdu_size = cpu_to_le16(size); + if (!buf) { + BT_DBG("Could not allocate resegmentation buffer"); + return -ENOMEM; + } - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), - (unsigned long) &rfc); + /* Move current frames off the original queue */ + __skb_queue_head_init(&old_frames); + skb_queue_splice_tail_init(queue, &old_frames); - if (test_bit(FLAG_EFS_ENABLE, &chan->flags)) - l2cap_add_opt_efs(&ptr, chan); + while (!skb_queue_empty(&old_frames)) { + struct sk_buff_head current_sdu; + u8 original_sar; - if (!(chan->conn->feat_mask & L2CAP_FEAT_FCS)) - break; + /* Reassemble each SDU from one or more PDUs */ - 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; - } + iv.iov_base = buf; + iv.iov_len = 0; - req->dcid = cpu_to_le16(chan->dcid); - req->flags = cpu_to_le16(0); + skb = skb_peek(&old_frames); + original_sar = bt_cb(skb)->control.sar; - return ptr - data; -} + __skb_unlink(skb, &old_frames); -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 }; - struct l2cap_conf_efs efs; - u8 remote_efs = 0; - u16 mtu = L2CAP_DEFAULT_MTU; - u16 result = L2CAP_CONF_SUCCESS; - u16 size; + /* Append data to SDU */ + if (pi->extended_control) + skb_pull(skb, L2CAP_EXTENDED_HDR_SIZE); + else + skb_pull(skb, L2CAP_ENHANCED_HDR_SIZE); - BT_DBG("chan %p", chan); + if (original_sar == L2CAP_SAR_START) + skb_pull(skb, L2CAP_SDULEN_SIZE); - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&req, &type, &olen, &val); + err = l2cap_skbuff_to_kvec(skb, &iv, buflen); - hint = type & L2CAP_CONF_HINT; - type &= L2CAP_CONF_MASK; + if (bt_cb(skb)->control.fcs == L2CAP_FCS_CRC16) + iv.iov_len -= L2CAP_FCS_SIZE; - switch (type) { - case L2CAP_CONF_MTU: - mtu = val; - break; + /* Free skb */ + kfree_skb(skb); - case L2CAP_CONF_FLUSH_TO: - chan->flush_to = val; + if (err) break; - case L2CAP_CONF_QOS: - break; + while (!skb_queue_empty(&old_frames) && !err) { + /* Check next frame */ + skb = skb_peek(&old_frames); - case L2CAP_CONF_RFC: - if (olen == sizeof(rfc)) - memcpy(&rfc, (void *) val, olen); - break; + if (is_initial_frame(bt_cb(skb)->control.sar)) + break; - case L2CAP_CONF_FCS: - if (val == L2CAP_FCS_NONE) - set_bit(CONF_NO_FCS_RECV, &chan->conf_state); - break; + __skb_unlink(skb, &old_frames); - case L2CAP_CONF_EFS: - remote_efs = 1; - if (olen == sizeof(efs)) - memcpy(&efs, (void *) val, olen); - 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_EWS: - if (!enable_hs) - return -ECONNREFUSED; + if (bt_cb(skb)->control.sar == L2CAP_SAR_START) + skb_pull(skb, L2CAP_SDULEN_SIZE); - set_bit(FLAG_EXT_CTRL, &chan->flags); - set_bit(CONF_EWS_RECV, &chan->conf_state); - chan->tx_win_max = L2CAP_DEFAULT_EXT_WINDOW; - chan->remote_tx_win = val; - break; + err = l2cap_skbuff_to_kvec(skb, &iv, buflen); - default: - if (hint) - break; + if (bt_cb(skb)->control.fcs == L2CAP_FCS_CRC16) + iv.iov_len -= L2CAP_FCS_SIZE; - result = L2CAP_CONF_UNKNOWN; - *((u8 *) ptr++) = type; - break; + /* Free skb */ + kfree_skb(skb); } - } - - if (chan->num_conf_rsp || chan->num_conf_req > 1) - 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); + if (err) break; - } - if (remote_efs) { - if (__l2cap_efs_supported(chan)) - set_bit(FLAG_EFS_ENABLE, &chan->flags); - else - return -ECONNREFUSED; + /* 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; } - if (chan->mode != rfc.mode) - return -ECONNREFUSED; - - break; - } - -done: - if (chan->mode != rfc.mode) { - result = L2CAP_CONF_UNACCEPT; - rfc.mode = chan->mode; + /* 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 (chan->num_conf_rsp == 1) - return -ECONNREFUSED; + 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); + } + } - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); + /* Add to queue */ + skb_queue_splice_tail(¤t_sdu, queue); } - 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; - else { - chan->omtu = mtu; - set_bit(CONF_MTU_DONE, &chan->conf_state); - } - l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->omtu); + __skb_queue_purge(&old_frames); + if (err) + __skb_queue_purge(queue); - if (remote_efs) { - if (chan->local_stype != L2CAP_SERV_NOTRAFIC && - efs.stype != L2CAP_SERV_NOTRAFIC && - efs.stype != chan->local_stype) { + kfree(buf); - result = L2CAP_CONF_UNACCEPT; + BT_DBG("Queue resegmented, err=%d", err); + return err; +} - if (chan->num_conf_req >= 1) - return -ECONNREFUSED; +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; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS, - sizeof(efs), - (unsigned long) &efs); - } else { - /* Send PENDING Conf Rsp */ - result = L2CAP_CONF_PENDING; - set_bit(CONF_LOC_CONF_PEND, &chan->conf_state); - } - } + kfree(seg_work); - switch (rfc.mode) { - case L2CAP_MODE_BASIC: - chan->fcs = L2CAP_FCS_NONE; - set_bit(CONF_MODE_DONE, &chan->conf_state); - break; + BT_DBG("sk %p", sk); + lock_sock(sk); - case L2CAP_MODE_ERTM: - if (!test_bit(CONF_EWS_RECV, &chan->conf_state)) - chan->remote_tx_win = rfc.txwin_size; - else - rfc.txwin_size = L2CAP_DEFAULT_TX_WINDOW; + if (l2cap_pi(sk)->amp_move_state != L2CAP_AMP_STATE_RESEGMENT) { + release_sock(sk); + sock_put(sk); + return; + } - chan->remote_max_tx = rfc.max_transmit; + err = l2cap_resegment_queue(sk, TX_QUEUE(sk)); - size = min_t(u16, le16_to_cpu(rfc.max_pdu_size), - chan->conn->mtu - - L2CAP_EXT_HDR_SIZE - - L2CAP_SDULEN_SIZE - - L2CAP_FCS_SIZE); - rfc.max_pdu_size = cpu_to_le16(size); - chan->remote_mps = size; + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_STABLE; - rfc.retrans_timeout = - le16_to_cpu(L2CAP_DEFAULT_RETRANS_TO); - rfc.monitor_timeout = - le16_to_cpu(L2CAP_DEFAULT_MONITOR_TO); + if (skb_queue_empty(TX_QUEUE(sk))) + sk->sk_send_head = NULL; + else + sk->sk_send_head = skb_peek(TX_QUEUE(sk)); - set_bit(CONF_MODE_DONE, &chan->conf_state); + if (err) + l2cap_send_disconn_req(l2cap_pi(sk)->conn, sk, ECONNRESET); + else + l2cap_ertm_send(sk); - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); + release_sock(sk); + sock_put(sk); +} - if (test_bit(FLAG_EFS_ENABLE, &chan->flags)) { - chan->remote_id = efs.id; - chan->remote_stype = efs.stype; - chan->remote_msdu = le16_to_cpu(efs.msdu); - chan->remote_flush_to = - le32_to_cpu(efs.flush_to); - chan->remote_acc_lat = - le32_to_cpu(efs.acc_lat); - chan->remote_sdu_itime = - le32_to_cpu(efs.sdu_itime); - l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS, - sizeof(efs), (unsigned long) &efs); - } - break; +static int l2cap_setup_resegment(struct sock *sk) +{ + struct l2cap_resegment_work *seg_work; - case L2CAP_MODE_STREAMING: - size = min_t(u16, le16_to_cpu(rfc.max_pdu_size), - chan->conn->mtu - - L2CAP_EXT_HDR_SIZE - - L2CAP_SDULEN_SIZE - - L2CAP_FCS_SIZE); - rfc.max_pdu_size = cpu_to_le16(size); - chan->remote_mps = size; + BT_DBG("sk %p", sk); - set_bit(CONF_MODE_DONE, &chan->conf_state); + if (skb_queue_empty(TX_QUEUE(sk))) + return 0; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); + seg_work = kzalloc(sizeof(*seg_work), GFP_ATOMIC); + if (!seg_work) + return -ENOMEM; - break; + INIT_WORK(&seg_work->work, l2cap_resegment_worker); + sock_hold(sk); + seg_work->sk = sk; - default: - result = L2CAP_CONF_UNACCEPT; + if (!queue_work(_l2cap_wq, &seg_work->work)) { + kfree(seg_work); + sock_put(sk); + return -ENOMEM; + } - memset(&rfc, 0, sizeof(rfc)); - rfc.mode = chan->mode; - } + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_RESEGMENT; - if (result == L2CAP_CONF_SUCCESS) - set_bit(CONF_OUTPUT_DONE, &chan->conf_state); - } - rsp->scid = cpu_to_le16(chan->dcid); - rsp->result = cpu_to_le16(result); - rsp->flags = cpu_to_le16(0x0000); + return 0; +} - return ptr - data; +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 int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, void *data, u16 *result) +static inline int l2cap_rmem_full(struct sock *sk) { - struct l2cap_conf_req *req = data; - void *ptr = req->data; - int type, olen; - unsigned long val; - struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC }; - struct l2cap_conf_efs efs; + 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; +} - BT_DBG("chan %p, rsp %p, len %d, req %p", chan, rsp, len, data); +void l2cap_amp_move_init(struct sock *sk) +{ + BT_DBG("sk %p", sk); - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + if (!l2cap_pi(sk)->conn) + return; - 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; + if (!(l2cap_pi(sk)->conn->fc_mask & L2CAP_FC_A2MP)) + return; - case L2CAP_CONF_FLUSH_TO: - chan->flush_to = val; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, - 2, chan->flush_to); - break; + 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); + } +} - case L2CAP_CONF_RFC: - if (olen == sizeof(rfc)) - memcpy(&rfc, (void *)val, olen); +static void l2cap_chan_ready(struct sock *sk) +{ + struct sock *parent = bt_sk(sk)->parent; - if (test_bit(CONF_STATE2_DEVICE, &chan->conf_state) && - rfc.mode != chan->mode) - return -ECONNREFUSED; + BT_DBG("sk %p, parent %p", sk, parent); - chan->fcs = 0; + l2cap_pi(sk)->conf_state = 0; + l2cap_sock_clear_timer(sk); - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); - break; + 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); + } +} - case L2CAP_CONF_EWS: - chan->tx_win = min_t(u16, val, - L2CAP_DEFAULT_EXT_WINDOW); - l2cap_add_conf_opt(&ptr, L2CAP_CONF_EWS, 2, - chan->tx_win); - break; +/* 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; - case L2CAP_CONF_EFS: - if (olen == sizeof(efs)) - memcpy(&efs, (void *)val, olen); + BT_DBG("conn %p", conn); - if (chan->local_stype != L2CAP_SERV_NOTRAFIC && - efs.stype != L2CAP_SERV_NOTRAFIC && - efs.stype != chan->local_stype) - return -ECONNREFUSED; + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + if (sk->sk_type != SOCK_RAW) + continue; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS, - sizeof(efs), (unsigned long) &efs); - break; - } + /* 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); +} - if (chan->mode == L2CAP_MODE_BASIC && chan->mode != rfc.mode) - return -ECONNREFUSED; +/* ---- 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; - chan->mode = rfc.mode; + BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d", + conn, code, ident, dlen); - if (*result == L2CAP_CONF_SUCCESS || *result == L2CAP_CONF_PENDING) { - 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); - - if (test_bit(FLAG_EFS_ENABLE, &chan->flags)) { - chan->local_msdu = le16_to_cpu(efs.msdu); - chan->local_sdu_itime = - le32_to_cpu(efs.sdu_itime); - chan->local_acc_lat = le32_to_cpu(efs.acc_lat); - chan->local_flush_to = - le32_to_cpu(efs.flush_to); - } - break; + len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen; + count = min_t(unsigned int, mtu, len); - case L2CAP_MODE_STREAMING: - chan->mps = le16_to_cpu(rfc.max_pdu_size); - } + 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; } - req->dcid = cpu_to_le16(chan->dcid); - req->flags = cpu_to_le16(0x0000); + len -= skb->len; - return ptr - data; + /* 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 int l2cap_build_conf_rsp(struct l2cap_chan *chan, void *data, u16 result, u16 flags) +static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val) { - struct l2cap_conf_rsp *rsp = data; - void *ptr = rsp->data; + struct l2cap_conf_opt *opt = *ptr; + int len; - BT_DBG("chan %p", chan); + len = L2CAP_CONF_OPT_SIZE + opt->len; + *ptr += len; - rsp->scid = cpu_to_le16(chan->dcid); - rsp->result = cpu_to_le16(result); - rsp->flags = cpu_to_le16(flags); + *type = opt->type; + *olen = opt->len; - return ptr - data; + 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; } -void __l2cap_connect_rsp_defer(struct l2cap_chan *chan) +static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val) { - struct l2cap_conn_rsp rsp; - struct l2cap_conn *conn = chan->conn; - u8 buf[128]; + 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; - 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); + BT_DBG("sk %p", sk); - if (test_and_set_bit(CONF_REQ_SENT, &chan->conf_state)) + if (!sk) return; - l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, buf), buf); - chan->num_conf_req++; + 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_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len) +static void l2cap_ertm_retrans_timeout(struct work_struct *work) { - int type, olen; - unsigned long val; - struct l2cap_conf_rfc rfc; + 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("chan %p, rsp %p, len %d", chan, rsp, len); + BT_DBG("sk %p", sk); - if ((chan->mode != L2CAP_MODE_ERTM) && (chan->mode != L2CAP_MODE_STREAMING)) + if (!sk) return; - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + lock_sock(sk); - switch (type) { - case L2CAP_CONF_RFC: - if (olen == sizeof(rfc)) - memcpy(&rfc, (void *)val, olen); - goto done; - } + if (!l2cap_pi(sk)->conn) { + release_sock(sk); + return; } - /* Use sane default values in case a misbehaving remote device - * did not send an RFC option. - */ - rfc.mode = chan->mode; - 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(chan->imtu); + l2cap_ertm_tx(sk, 0, 0, L2CAP_ERTM_EVENT_RETRANS_TIMER_EXPIRES); + release_sock(sk); +} - BT_ERR("Expected RFC option was not found, using defaults"); +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; -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; + 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 || + sk->sk_state != BT_CONNECTED) { + 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: - chan->mps = le16_to_cpu(rfc.max_pdu_size); + case L2CAP_MODE_ERTM: + if (l2cap_mode_supported(mode, remote_feat_mask)) + return mode; + /* fall through */ + default: + return L2CAP_MODE_BASIC; } } -static inline int l2cap_command_rej(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +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; + if (cur_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 l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data; + struct hci_ext_fs tx_fs; + struct hci_ext_fs rx_fs; + + BT_DBG("chan %p", chan); - if (rej->reason != L2CAP_REJ_NOT_UNDERSTOOD) + 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; - if ((conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) && - cmd->ident == conn->info_ident) { - cancel_delayed_work(&conn->info_timer); + 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; +} - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; +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; + if (cur_rate) + agg->sdu_arr_time = div64_u64( + agg->max_sdu * 1000000ULL, cur_rate); + } +} - l2cap_conn_start(conn); +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 sock *sk) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct hci_dev *hdev; + struct hci_conn *hcon; + struct hci_chan *chan; + + hdev = hci_dev_get(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) { + chan = NULL; + goto done; + } + + chan = hci_chan_list_lookup_id(hdev, hcon->handle); + if (chan) { + l2cap_aggregate(chan, pi); + sock_hold(sk); + chan->l2cap_sk = sk; + hci_chan_hold(chan); + pi->ampchan = chan; + goto done; + } + + chan = hci_chan_add(hdev); + if (chan) { + chan->conn = hcon; + sock_hold(sk); + chan->l2cap_sk = sk; + hci_chan_hold(chan); + pi->ampchan = chan; + hci_chan_create(chan, + (struct hci_ext_fs *) &pi->local_fs, + (struct hci_ext_fs *) &pi->remote_fs); + } +done: + hci_dev_put(hdev); + return chan; +} + +static void l2cap_get_ertm_timeouts(struct l2cap_conf_rfc *rfc, + struct l2cap_pinfo *pi) +{ + if (pi->amp_id && pi->ampcon) { + u64 ertm_to = pi->ampcon->hdev->amp_be_flush_to; + + /* Class 1 devices have must have ERTM timeouts + * exceeding the Link Supervision Timeout. The + * default Link Supervision Timeout for AMP + * controllers is 10 seconds. + * + * Class 1 devices use 0xffffffff for their + * best-effort flush timeout, so the clamping logic + * will result in a timeout that meets the above + * requirement. ERTM timeouts are 16-bit values, so + * the maximum timeout is 65.535 seconds. + */ + + /* Convert timeout to milliseconds and round */ + ertm_to = div_u64(ertm_to + 999, 1000); + + /* This is the recommended formula for class 2 devices + * that start ERTM timers when packets are sent to the + * controller. + */ + ertm_to = 3 * ertm_to + 500; + + if (ertm_to > 0xffff) + ertm_to = 0xffff; + + rfc->retrans_timeout = cpu_to_le16((u16) ertm_to); + rfc->monitor_timeout = rfc->retrans_timeout; + } else { + rfc->retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO); + rfc->monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO); + } +} + +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.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); + l2cap_get_ertm_timeouts(&rfc, pi); + + 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: + l2cap_setup_txwin(pi); + 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; + + BT_DBG("sk %p", sk); + + switch (pi->mode) { + case L2CAP_MODE_ERTM: + rfc.mode = L2CAP_MODE_ERTM; + rfc.txwin_size = pi->tx_win; + rfc.max_transmit = pi->max_tx; + rfc.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); + l2cap_get_ertm_timeouts(&rfc, pi); + 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 */ + /* FCS is not used with AMP because it is redundant - lower + * layers already include a checksum. */ + if (pi->amp_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); + l2cap_get_ertm_timeouts(&rfc, pi); + + 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, sk); + if (!chan) + return -ECONNREFUSED; + + 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); + 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 (pi->mode != rfc.mode || rfc.mode == L2CAP_MODE_BASIC) + result = L2CAP_CONF_UNACCEPT; + + 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); + } + + pi->remote_mps = rfc.max_pdu_size; + + if (rfc.mode == L2CAP_MODE_ERTM) { + l2cap_get_ertm_timeouts(&rfc, pi); + } else { + rfc.retrans_timeout = 0; + rfc.monitor_timeout = 0; + } + + 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; + +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); + + /* Initialize rfc in case no rfc option is received */ + rfc.mode = pi->mode; + 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); + + 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); + + /* Initialize rfc in case no rfc option is received */ + rfc.mode = pi->mode; + 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 ((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) { + l2cap_send_disconn_req(pi->conn, sk, + ECONNRESET); + return -ECONNRESET; + } + + goto done; + } + } + } + + BT_ERR("Expected RFC option was missing, using existing values"); + + rfc.mode = pi->mode; + rfc.retrans_timeout = cpu_to_le16(pi->retrans_timeout); + rfc.monitor_timeout = cpu_to_le16(pi->monitor_timeout); + +done: + l2cap_ertm_stop_ack_timer(pi); + l2cap_ertm_stop_retrans_timer(pi); + l2cap_ertm_stop_monitor_timer(pi); + + pi->mps = le16_to_cpu(rfc.max_pdu_size); + if (pi->mode == L2CAP_MODE_ERTM) { + pi->retrans_timeout = le16_to_cpu(rfc.retrans_timeout); + pi->monitor_timeout = le16_to_cpu(rfc.monitor_timeout); + } + + 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 (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++; + } + +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 (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, sk); + if (!chan) { + l2cap_send_disconn_req(pi->conn, sk, + ECONNRESET); + goto done; + } + + 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; + } + + default: + sk->sk_err = ECONNRESET; + l2cap_sock_set_timer(sk, HZ * 5); + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto done; + } + + 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: + bh_unlock_sock(sk); + return 0; +} + +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; + + scid = __le16_to_cpu(req->scid); + dcid = __le16_to_cpu(req->dcid); + + BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid); + + sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid); + if (!sk) + return 0; + + 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; + + sk->sk_send_head = NULL; + 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); + } + } + + /* 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); } return 0; } -static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static inline int l2cap_information_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; + struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) data; + u16 type, result; - u16 dcid = 0, scid = __le16_to_cpu(req->scid); - __le16 psm = req->psm; + type = __le16_to_cpu(rsp->type); + result = __le16_to_cpu(rsp->result); - BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid); + BT_DBG("type 0x%4.4x result 0x%2.2x", type, result); - /* 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; - } + /* 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; - parent = pchan->sk; + del_timer(&conn->info_timer); - mutex_lock(&conn->chan_lock); - lock_sock(parent); + if (result != L2CAP_IR_SUCCESS) { + conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; + conn->info_ident = 0; - /* 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 = HCI_ERROR_AUTH_FAILURE; - result = L2CAP_CR_SEC_BLOCK; - goto response; + l2cap_conn_start(conn); + + return 0; } - result = L2CAP_CR_NO_MEM; + if (type == L2CAP_IT_FEAT_MASK) { + conn->feat_mask = get_unaligned_le32(rsp->data); - /* Check for backlog size */ - if (sk_acceptq_is_full(parent)) { - BT_DBG("backlog full %d", parent->sk_ack_backlog); - goto response; - } + if (conn->feat_mask & L2CAP_FEAT_FIXED_CHAN) { + struct l2cap_info_req req; + req.type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); - chan = pchan->ops->new_connection(pchan->data); - if (!chan) - goto response; + conn->info_ident = l2cap_get_ident(conn); - sk = chan->sk; + 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; - /* Check if we already have channel with that dcid */ - if (__l2cap_get_chan_by_dcid(conn, scid)) { - sock_set_flag(sk, SOCK_ZAPPED); - chan->ops->close(chan->data); - goto response; - } + 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; - hci_conn_hold(conn->hcon); + l2cap_conn_start(conn); + } - bacpy(&bt_sk(sk)->src, conn->src); - bacpy(&bt_sk(sk)->dst, conn->dst); - chan->psm = psm; - chan->dcid = scid; + return 0; +} - bt_accept_enqueue(parent, sk); +static void l2cap_send_move_chan_req(struct l2cap_conn *conn, + struct l2cap_pinfo *pi, u16 icid, u8 dest_amp_id) +{ + struct l2cap_move_chan_req req; + u8 ident; - __l2cap_chan_add(conn, chan); + BT_DBG("pi %p, icid %d, dest_amp_id %d", pi, (int) icid, + (int) dest_amp_id); - dcid = chan->scid; + ident = l2cap_get_ident(conn); + if (pi) + pi->ident = ident; - __set_chan_timer(chan, sk->sk_sndtimeo); + req.icid = cpu_to_le16(icid); + req.dest_amp_id = dest_amp_id; - chan->ident = cmd->ident; + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_REQ, sizeof(req), &req); +} - if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE) { - if (l2cap_chan_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; - } - } else { - __l2cap_state_change(chan, BT_CONNECT2); - result = L2CAP_CR_PEND; - status = L2CAP_CS_NO_INFO; - } +static void l2cap_send_move_chan_rsp(struct l2cap_conn *conn, u8 ident, + u16 icid, u16 result) +{ + struct l2cap_move_chan_rsp rsp; -response: - release_sock(parent); - mutex_unlock(&conn->chan_lock); + BT_DBG("icid %d, result %d", (int) icid, (int) result); -sendresp: - rsp.scid = cpu_to_le16(scid); - rsp.dcid = cpu_to_le16(dcid); + rsp.icid = cpu_to_le16(icid); 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); + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_RSP, sizeof(rsp), &rsp); +} - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_SENT; - conn->info_ident = l2cap_get_ident(conn); +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; - schedule_delayed_work(&conn->info_timer, L2CAP_INFO_TIMEOUT); + BT_DBG("icid %d, result %d", (int) icid, (int) result); - l2cap_send_cmd(conn, conn->info_ident, - L2CAP_INFO_REQ, sizeof(info), &info); - } + ident = l2cap_get_ident(conn); + if (pi) + pi->ident = ident; - 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++; - } + cfm.icid = cpu_to_le16(icid); + cfm.result = cpu_to_le16(result); - return 0; + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM, sizeof(cfm), &cfm); } -static inline int l2cap_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident, + u16 icid) { - struct l2cap_conn_rsp *rsp = (struct l2cap_conn_rsp *) data; - u16 scid, dcid, result, status; - struct l2cap_chan *chan; - u8 req[128]; - int err; + struct l2cap_move_chan_cfm_rsp rsp; - 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("icid %d", (int) icid); - BT_DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", - dcid, scid, result, status); + rsp.icid = cpu_to_le16(icid); + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp); +} - mutex_lock(&conn->chan_lock); +static inline int l2cap_create_channel_req(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_create_chan_req *req = + (struct l2cap_create_chan_req *) data; + struct sock *sk; + u16 psm, scid; - if (scid) { - chan = __l2cap_get_chan_by_scid(conn, scid); - if (!chan) { - err = -EFAULT; - goto unlock; - } - } else { - chan = __l2cap_get_chan_by_ident(conn, cmd->ident); - if (!chan) { - err = -EFAULT; - goto unlock; - } - } + psm = le16_to_cpu(req->psm); + scid = le16_to_cpu(req->scid); - err = 0; + BT_DBG("psm %d, scid %d, amp_id %d", (int) psm, (int) scid, + (int) req->amp_id); - l2cap_chan_lock(chan); + if (req->amp_id) { + struct hci_dev *hdev; - 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); + /* Validate AMP controller id */ + hdev = hci_dev_get(req->amp_id); + if (!hdev || !test_bit(HCI_UP, &hdev->flags)) { + struct l2cap_create_chan_rsp rsp; - if (test_and_set_bit(CONF_REQ_SENT, &chan->conf_state)) - break; + rsp.dcid = 0; + rsp.scid = cpu_to_le16(scid); + rsp.result = L2CAP_CREATE_CHAN_REFUSED_CONTROLLER; + rsp.status = L2CAP_CREATE_CHAN_STATUS_NONE; - l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, req), req); - chan->num_conf_req++; - break; + l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP, + sizeof(rsp), &rsp); - case L2CAP_CR_PEND: - set_bit(CONF_CONNECT_PEND, &chan->conf_state); - break; + if (hdev) + hci_dev_put(hdev); - default: - l2cap_chan_del(chan, ECONNREFUSED); - break; + return 0; + } + + hci_dev_put(hdev); } - l2cap_chan_unlock(chan); + sk = l2cap_create_connect(conn, cmd, data, L2CAP_CREATE_CHAN_RSP, + req->amp_id); -unlock: - mutex_unlock(&conn->chan_lock); + if (sk) + l2cap_pi(sk)->conf_state |= L2CAP_CONF_LOCKSTEP; - return err; + if (sk && req->amp_id && + (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE)) + amp_accept_physical(conn, req->amp_id, sk); + + return 0; } -static inline void set_default_fcs(struct l2cap_chan *chan) +static inline int l2cap_create_channel_rsp(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) { - /* 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; + BT_DBG("conn %p", conn); + + return l2cap_connect_rsp(conn, cmd, data); } -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_req(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; - int len; - - dcid = __le16_to_cpu(req->dcid); - flags = __le16_to_cpu(req->flags); + 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; - BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags); + icid = le16_to_cpu(req->icid); - chan = l2cap_get_chan_by_scid(conn, dcid); - if (!chan) - return -ENOENT; + BT_DBG("icid %d, dest_amp_id %d", (int) icid, (int) req->dest_amp_id); - l2cap_chan_lock(chan); + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_dcid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); - if (chan->state != BT_CONFIG && chan->state != BT_CONNECT2) { - struct l2cap_cmd_rej_cid rej; + if (!sk) + goto send_move_response; - rej.reason = cpu_to_le16(L2CAP_REJ_INVALID_CID); - rej.scid = cpu_to_le16(chan->scid); - rej.dcid = cpu_to_le16(chan->dcid); + lock_sock(sk); + pi = l2cap_pi(sk); - l2cap_send_cmd(conn, cmd->ident, L2CAP_COMMAND_REJ, - sizeof(rej), &rej); - goto unlock; + if (pi->scid < L2CAP_CID_DYN_START || + (pi->mode != L2CAP_MODE_ERTM && + pi->mode != L2CAP_MODE_STREAMING)) { + goto send_move_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; + if (pi->amp_id == req->dest_amp_id) { + result = L2CAP_MOVE_CHAN_REFUSED_SAME_ID; + goto send_move_response; } - /* Store config. */ - memcpy(chan->conf_req + chan->conf_len, req->data, len); - chan->conf_len += len; - - 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 (req->dest_amp_id) { + struct hci_dev *hdev; + hdev = hci_dev_get(req->dest_amp_id); + if (!hdev || !test_bit(HCI_UP, &hdev->flags)) { + if (hdev) + hci_dev_put(hdev); - /* Complete config. */ - len = l2cap_parse_conf_req(chan, rsp); - if (len < 0) { - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto unlock; + result = L2CAP_MOVE_CHAN_REFUSED_CONTROLLER; + goto send_move_response; + } + hci_dev_put(hdev); } - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, len, rsp); - chan->num_conf_rsp++; - - /* Reset config buffer. */ - chan->conf_len = 0; - - if (!test_bit(CONF_OUTPUT_DONE, &chan->conf_state)) - goto unlock; - - if (test_bit(CONF_INPUT_DONE, &chan->conf_state)) { - set_default_fcs(chan); - - 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); - - l2cap_chan_ready(chan); - goto unlock; + 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 (!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 (pi->amp_pref == BT_AMP_POLICY_REQUIRE_BR_EDR) { + result = L2CAP_MOVE_CHAN_REFUSED_NOT_ALLOWED; + goto send_move_response; } - /* Got Conf Rsp PENDING from remote side and asume we sent - Conf Rsp PENDING in the code above */ - if (test_bit(CONF_REM_CONF_PEND, &chan->conf_state) && - test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) { + 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; - /* check compatibility */ + 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; + } - clear_bit(CONF_LOC_CONF_PEND, &chan->conf_state); - set_bit(CONF_OUTPUT_DONE, &chan->conf_state); +send_move_response: + l2cap_send_move_chan_rsp(conn, cmd->ident, icid, result); - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, - l2cap_build_conf_rsp(chan, rsp, - L2CAP_CONF_SUCCESS, 0x0000), rsp); - } + if (sk) + release_sock(sk); -unlock: - l2cap_chan_unlock(chan); return 0; } -static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static inline int l2cap_move_channel_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 l2cap_chan *chan; - 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); - - chan = l2cap_get_chan_by_scid(conn, scid); - if (!chan) - return 0; - - l2cap_chan_lock(chan); - - switch (result) { - case L2CAP_CONF_SUCCESS: - l2cap_conf_rfc_get(chan, rsp->data, len); - clear_bit(CONF_REM_CONF_PEND, &chan->conf_state); - break; - - case L2CAP_CONF_PENDING: - set_bit(CONF_REM_CONF_PEND, &chan->conf_state); - - if (test_bit(CONF_LOC_CONF_PEND, &chan->conf_state)) { - char buf[64]; - - len = l2cap_parse_conf_rsp(chan, rsp->data, len, - buf, &result); - if (len < 0) { - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto done; - } + struct l2cap_move_chan_rsp *rsp = (struct l2cap_move_chan_rsp *) data; + struct sock *sk; + struct l2cap_pinfo *pi; + u16 icid, result; - /* check compatibility */ + icid = le16_to_cpu(rsp->icid); + result = le16_to_cpu(rsp->result); - clear_bit(CONF_LOC_CONF_PEND, &chan->conf_state); - set_bit(CONF_OUTPUT_DONE, &chan->conf_state); + BT_DBG("icid %d, result %d", (int) icid, (int) result); - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, - l2cap_build_conf_rsp(chan, buf, - L2CAP_CONF_SUCCESS, 0x0000), buf); + 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; } - goto done; - - case L2CAP_CONF_UNACCEPT: - if (chan->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, chan, ECONNRESET); - goto done; + 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 (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS) { + if (result == L2CAP_MOVE_CHAN_PENDING) { + break; + } else if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOCAL_BUSY; + } else { + /* Logical link is up or moving to BR/EDR, + * proceed with move */ + 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); } - - /* 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; + } 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, sk); + if (!chan) { + /* Logical link not available */ + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + break; } - l2cap_send_cmd(conn, l2cap_get_ident(conn), - L2CAP_CONF_REQ, len, req); - chan->num_conf_req++; - if (result != L2CAP_CONF_SUCCESS) - goto done; + if (chan->state == BT_CONNECTED) { + /* Logical link is already ready to go */ + 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; + } + + l2cap_create_cfm(chan, 0); + } + } else { + /* Any other amp move state means the move failed. */ + 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; + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } + break; + default: + /* 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; } - default: - l2cap_chan_set_err(chan, ECONNRESET); + 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; + } + } - __set_chan_timer(chan, L2CAP_DISC_REJ_TIMEOUT); - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto done; + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + break; } - if (flags & 0x01) - goto done; - - set_bit(CONF_INPUT_DONE, &chan->conf_state); - - if (test_bit(CONF_OUTPUT_DONE, &chan->conf_state)) { - set_default_fcs(chan); - - 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); - - l2cap_chan_ready(chan); - } + if (sk) + release_sock(sk); -done: - l2cap_chan_unlock(chan); return 0; } -static inline int l2cap_disconnect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static inline int l2cap_move_channel_confirm(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 l2cap_chan *chan; + struct l2cap_move_chan_cfm *cfm = (struct l2cap_move_chan_cfm *) data; struct sock *sk; + struct l2cap_pinfo *pi; + u16 icid, result; - scid = __le16_to_cpu(req->scid); - dcid = __le16_to_cpu(req->dcid); + icid = le16_to_cpu(cfm->icid); + result = le16_to_cpu(cfm->result); - BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid); + BT_DBG("icid %d, result %d", (int) icid, (int) result); - mutex_lock(&conn->chan_lock); + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_dcid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); - chan = __l2cap_get_chan_by_scid(conn, dcid); - if (!chan) { - mutex_unlock(&conn->chan_lock); - return 0; + if (!sk) { + BT_DBG("Bad channel (%d)", (int) icid); + goto send_move_confirm_response; } - l2cap_chan_lock(chan); - - sk = chan->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); - lock_sock(sk); - sk->sk_shutdown = SHUTDOWN_MASK; - release_sock(sk); - - l2cap_chan_del(chan, ECONNRESET); - - l2cap_chan_unlock(chan); + pi = l2cap_pi(sk); + + if (pi->amp_move_state == L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM) { + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + if (result == L2CAP_MOVE_CHAN_CONFIRMED) { + pi->amp_id = pi->amp_move_id; + if (!pi->amp_id && pi->ampchan) { + struct hci_chan *ampchan = pi->ampchan; + struct hci_conn *ampcon = pi->ampcon; + /* Have moved off of AMP, free the channel */ + pi->ampchan = NULL; + pi->ampcon = NULL; + if (hci_chan_put(ampchan)) + ampcon->l2cap_data = NULL; + else + l2cap_deaggregate(ampchan, pi); + } + l2cap_amp_move_success(sk); + } else { + pi->amp_move_id = pi->amp_id; + l2cap_amp_move_revert(sk); + } + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + } else if (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM) { + BT_DBG("Bad AMP_MOVE_STATE (%d)", pi->amp_move_state); + } - chan->ops->close(chan->data); +send_move_confirm_response: + l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid); - mutex_unlock(&conn->chan_lock); + if (sk) + release_sock(sk); return 0; } -static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static inline int l2cap_move_channel_confirm_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 l2cap_move_chan_cfm_rsp *rsp = + (struct l2cap_move_chan_cfm_rsp *) data; + struct sock *sk; + struct l2cap_pinfo *pi; - scid = __le16_to_cpu(rsp->scid); - dcid = __le16_to_cpu(rsp->dcid); + u16 icid; - BT_DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid); + icid = le16_to_cpu(rsp->icid); - mutex_lock(&conn->chan_lock); + BT_DBG("icid %d", (int) icid); - chan = __l2cap_get_chan_by_scid(conn, scid); - if (!chan) { - mutex_unlock(&conn->chan_lock); - return 0; - } + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_scid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); - l2cap_chan_lock(chan); + if (!sk) + return 0; - l2cap_chan_del(chan, 0); + lock_sock(sk); + pi = l2cap_pi(sk); + + l2cap_sock_clear_timer(sk); + + if (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP) { + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + pi->amp_id = pi->amp_move_id; + + if (!pi->amp_id && pi->ampchan) { + struct hci_chan *ampchan = pi->ampchan; + struct hci_conn *ampcon = pi->ampcon; + /* Have moved off of AMP, free the channel */ + pi->ampchan = NULL; + pi->ampcon = NULL; + if (hci_chan_put(ampchan)) + ampcon->l2cap_data = NULL; + else + l2cap_deaggregate(ampchan, pi); + } - l2cap_chan_unlock(chan); + l2cap_amp_move_success(sk); - chan->ops->close(chan->data); + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + } - mutex_unlock(&conn->chan_lock); + 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_amp_signal_worker(struct work_struct *work) { - struct l2cap_info_req *req = (struct l2cap_info_req *) data; - u16 type; + int err = 0; + struct l2cap_amp_signal_work *ampwork = + container_of(work, struct l2cap_amp_signal_work, work); - type = __le16_to_cpu(req->type); + switch (ampwork->cmd.code) { + case L2CAP_MOVE_CHAN_REQ: + err = l2cap_move_channel_req(ampwork->conn, &work->cmd, + ampwork->data); + break; - BT_DBG("type 0x%4.4x", type); + case L2CAP_MOVE_CHAN_RSP: + err = l2cap_move_channel_rsp(ampwork->conn, &work->cmd, + ampwork->data); + break; - 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; - if (enable_hs) - feat_mask |= L2CAP_FEAT_EXT_FLOW - | L2CAP_FEAT_EXT_WINDOW; + case L2CAP_MOVE_CHAN_CFM: + err = l2cap_move_channel_confirm(ampwork->conn, &work->cmd, + ampwork->data); + break; - 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; + case L2CAP_MOVE_CHAN_CFM_RSP: + err = l2cap_move_channel_confirm_rsp(ampwork->conn, + &work->cmd, ampwork->data); + break; - if (enable_hs) - l2cap_fixed_chan[0] |= L2CAP_FC_A2MP; - else - l2cap_fixed_chan[0] &= ~L2CAP_FC_A2MP; + default: + BT_ERR("Unknown signaling command 0x%2.2x", ampwork->cmd.code); + err = -EINVAL; + break; + } - rsp->type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); - rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS); - memcpy(rsp->data, l2cap_fixed_chan, sizeof(l2cap_fixed_chan)); - 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); + 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); } - return 0; + kfree_skb(ampwork->skb); + kfree(ampwork); } -static inline int l2cap_information_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +void l2cap_amp_physical_complete(int result, u8 local_id, u8 remote_id, + struct sock *sk) { - 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); - - BT_DBG("type 0x%4.4x result 0x%2.2x", type, result); - - /* 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; + struct l2cap_pinfo *pi; - cancel_delayed_work(&conn->info_timer); + BT_DBG("result %d, local_id %d, remote_id %d, sk %p", result, + (int) local_id, (int) remote_id, sk); - if (result != L2CAP_IR_SUCCESS) { - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; - - l2cap_conn_start(conn); + lock_sock(sk); - return 0; + if (sk->sk_state == BT_DISCONN || sk->sk_state == BT_CLOSED) { + release_sock(sk); + return; } - switch (type) { - case L2CAP_IT_FEAT_MASK: - conn->feat_mask = get_unaligned_le32(rsp->data); - - if (conn->feat_mask & L2CAP_FEAT_FIXED_CHAN) { - struct l2cap_info_req req; - req.type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); + pi = l2cap_pi(sk); - conn->info_ident = l2cap_get_ident(conn); + 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); + } - l2cap_send_cmd(conn, conn->info_ident, - L2CAP_INFO_REQ, sizeof(req), &req); + 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 { - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; - - l2cap_conn_start(conn); + /* 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, sk); + if (chan) { + if (chan->state == BT_CONNECTED) { + /* Logical link is ready to go */ + 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); + + l2cap_create_cfm(chan, 0); + } else { + /* Wait for logical link to be ready */ + 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); } - break; - case L2CAP_IT_FIXED_CHAN: - conn->fixed_chan_mask = rsp->data[0]; - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; - l2cap_conn_start(conn); - break; + 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); + + /* Restart data transmission */ + l2cap_ertm_send(sk); } - return 0; + release_sock(sk); } -static inline int l2cap_create_channel_req(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, u16 cmd_len, - void *data) +static void l2cap_logical_link_complete(struct hci_chan *chan, u8 status) { - struct l2cap_create_chan_req *req = data; - struct l2cap_create_chan_rsp rsp; - u16 psm, scid; - - if (cmd_len != sizeof(*req)) - return -EPROTO; - - if (!enable_hs) - return -EINVAL; + struct l2cap_pinfo *pi; + struct sock *sk; + struct hci_chan *ampchan; + struct hci_conn *ampcon; - psm = le16_to_cpu(req->psm); - scid = le16_to_cpu(req->scid); + BT_DBG("status %d, chan %p, conn %p", (int) status, chan, chan->conn); - BT_DBG("psm %d, scid %d, amp_id %d", psm, scid, req->amp_id); + sk = chan->l2cap_sk; + chan->l2cap_sk = NULL; - /* Placeholder: Always reject */ - rsp.dcid = 0; - rsp.scid = cpu_to_le16(scid); - rsp.result = L2CAP_CR_NO_MEM; - rsp.status = L2CAP_CS_NO_INFO; + BT_DBG("sk %p", sk); - l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP, - sizeof(rsp), &rsp); + lock_sock(sk); - return 0; -} + if (sk->sk_state != BT_CONNECTED && !l2cap_pi(sk)->amp_id) { + release_sock(sk); + return; + } -static inline int l2cap_create_channel_rsp(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, void *data) -{ - BT_DBG("conn %p", conn); + pi = l2cap_pi(sk); - return l2cap_connect_rsp(conn, cmd, data); -} + if ((!status) && (chan != NULL)) { + pi->ampcon = chan->conn; + pi->ampcon->l2cap_data = pi->conn; -static void l2cap_send_move_chan_rsp(struct l2cap_conn *conn, u8 ident, - u16 icid, u16 result) -{ - struct l2cap_move_chan_rsp rsp; + BT_DBG("amp_move_state %d", pi->amp_move_state); - BT_DBG("icid %d, result %d", icid, result); + if (sk->sk_state != BT_CONNECTED) { + struct l2cap_conf_rsp rsp; - rsp.icid = cpu_to_le16(icid); - rsp.result = cpu_to_le16(result); + /* 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; - l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_RSP, sizeof(rsp), &rsp); -} + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) { + set_default_fcs(l2cap_pi(sk)); -static void l2cap_send_move_chan_cfm(struct l2cap_conn *conn, - struct l2cap_chan *chan, u16 icid, u16 result) -{ - struct l2cap_move_chan_cfm cfm; - u8 ident; + sk->sk_state = BT_CONNECTED; - BT_DBG("icid %d, result %d", icid, result); + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM || + l2cap_pi(sk)->mode == L2CAP_MODE_STREAMING) + l2cap_ertm_init(sk); - ident = l2cap_get_ident(conn); - if (chan) - chan->ident = ident; + 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 if ((pi->amp_move_state != + L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS) && + (pi->amp_move_state != + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM) && + (pi->amp_move_state != + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP)) { + /* Move was not in expected state, free the channel */ + ampchan = pi->ampchan; + ampcon = pi->ampcon; + pi->ampchan = NULL; + pi->ampcon = NULL; + if (ampchan) { + if (hci_chan_put(ampchan)) + ampcon->l2cap_data = NULL; + else + l2cap_deaggregate(ampchan, pi); + } + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + } + } 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; + } - cfm.icid = cpu_to_le16(icid); - cfm.result = cpu_to_le16(result); + /* 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); + } + ampchan = pi->ampchan; + ampcon = pi->ampcon; + pi->ampchan = NULL; + pi->ampcon = NULL; + if (ampchan) { + if (hci_chan_put(ampchan)) + ampcon->l2cap_data = NULL; + else + l2cap_deaggregate(ampchan, pi); + } + } - l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM, sizeof(cfm), &cfm); + release_sock(sk); } -static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident, - u16 icid) +static void l2cap_logical_link_worker(struct work_struct *work) { - struct l2cap_move_chan_cfm_rsp rsp; - - BT_DBG("icid %d", icid); + struct l2cap_logical_link_work *log_link_work = + container_of(work, struct l2cap_logical_link_work, work); + struct sock *sk = log_link_work->chan->l2cap_sk; - rsp.icid = cpu_to_le16(icid); - l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp); + if (sk) { + l2cap_logical_link_complete(log_link_work->chan, + log_link_work->status); + sock_put(sk); + } + hci_chan_put(log_link_work->chan); + kfree(log_link_work); } -static inline int l2cap_move_channel_req(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data) +static int l2cap_create_cfm(struct hci_chan *chan, u8 status) { - struct l2cap_move_chan_req *req = data; - u16 icid = 0; - u16 result = L2CAP_MR_NOT_ALLOWED; + struct l2cap_logical_link_work *amp_work; - if (cmd_len != sizeof(*req)) - return -EPROTO; + if (!chan->l2cap_sk) { + BT_ERR("Expected l2cap_sk to point to connecting socket"); + return -EFAULT; + } - icid = le16_to_cpu(req->icid); + amp_work = kzalloc(sizeof(*amp_work), GFP_ATOMIC); + if (!amp_work) { + sock_put(chan->l2cap_sk); + return -ENOMEM; + } - BT_DBG("icid %d, dest_amp_id %d", icid, req->dest_amp_id); + INIT_WORK(&_work->work, l2cap_logical_link_worker); + amp_work->chan = chan; + amp_work->status = status; - if (!enable_hs) - return -EINVAL; + hci_chan_hold(chan); - /* Placeholder: Always refuse */ - l2cap_send_move_chan_rsp(conn, cmd->ident, icid, result); + if (!queue_work(_l2cap_wq, &_work->work)) { + kfree(amp_work); + sock_put(chan->l2cap_sk); + hci_chan_put(chan); + return -ENOMEM; + } return 0; } -static inline int l2cap_move_channel_rsp(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data) +int l2cap_modify_cfm(struct hci_chan *chan, u8 status) { - struct l2cap_move_chan_rsp *rsp = data; - u16 icid, result; - - if (cmd_len != sizeof(*rsp)) - return -EPROTO; - - icid = le16_to_cpu(rsp->icid); - result = le16_to_cpu(rsp->result); + struct l2cap_conn *conn = chan->conn->l2cap_data; - BT_DBG("icid %d, result %d", icid, result); - - /* Placeholder: Always unconfirmed */ - l2cap_send_move_chan_cfm(conn, NULL, icid, L2CAP_MC_UNCONFIRMED); + BT_DBG("chan %p conn %p status %d", chan, conn, status); + /* TODO: if failed status restore previous fs */ return 0; } -static inline int l2cap_move_channel_confirm(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data) +int l2cap_destroy_cfm(struct hci_chan *chan, u8 reason) { - struct l2cap_move_chan_cfm *cfm = data; - u16 icid, result; + struct l2cap_chan_list *l; + struct l2cap_conn *conn = chan->conn->l2cap_data; + struct sock *sk; - if (cmd_len != sizeof(*cfm)) - return -EPROTO; + BT_DBG("chan %p conn %p", chan, conn); - icid = le16_to_cpu(cfm->icid); - result = le16_to_cpu(cfm->result); + if (!conn) + return 0; - BT_DBG("icid %d, result %d", icid, result); + l = &conn->chan_list; - l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid); + read_lock(&l->lock); + + 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) { + struct hci_conn *ampcon = l2cap_pi(sk)->ampcon; + l2cap_pi(sk)->ampchan = NULL; + l2cap_pi(sk)->ampcon = NULL; + if (hci_chan_put(chan)) + ampcon->l2cap_data = NULL; + else + l2cap_deaggregate(chan, l2cap_pi(sk)); + + l2cap_amp_move_init(sk); + } + bh_unlock_sock(sk); + } + + read_unlock(&l->lock); return 0; + + } -static inline int l2cap_move_channel_confirm_rsp(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data) +static int l2cap_sig_amp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, + u8 *data, struct sk_buff *skb) { - struct l2cap_move_chan_cfm_rsp *rsp = data; - u16 icid; + struct l2cap_amp_signal_work *amp_work; - if (cmd_len != sizeof(*rsp)) - return -EPROTO; + amp_work = kzalloc(sizeof(*amp_work), GFP_ATOMIC); + if (!amp_work) + return -ENOMEM; - icid = le16_to_cpu(rsp->icid); + 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; + } - BT_DBG("icid %d", icid); + if (!queue_work(_l2cap_wq, &_work->work)) { + kfree_skb(amp_work->skb); + kfree(amp_work); + return -ENOMEM; + } return 0; } @@ -3429,7 +5680,8 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn, struct hci_conn *hcon = conn->hcon; struct l2cap_conn_param_update_req *req; struct l2cap_conn_param_update_rsp rsp; - u16 min, max, latency, to_multiplier, cmd_len; + struct sock *sk; + u16 min, max, latency, timeout, cmd_len; int err; if (!(hcon->link_mode & HCI_LM_MASTER)) @@ -3439,34 +5691,39 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn, if (cmd_len != sizeof(struct l2cap_conn_param_update_req)) return -EPROTO; - req = (struct l2cap_conn_param_update_req *) data; - min = __le16_to_cpu(req->min); - max = __le16_to_cpu(req->max); - latency = __le16_to_cpu(req->latency); - to_multiplier = __le16_to_cpu(req->to_multiplier); + memset(&rsp, 0, sizeof(rsp)); + rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED); - BT_DBG("min 0x%4.4x max 0x%4.4x latency: 0x%4.4x Timeout: 0x%4.4x", - min, max, latency, to_multiplier); + sk = l2cap_find_sock_by_fixed_cid_and_dir(4, conn->src, conn->dst, 0); - memset(&rsp, 0, sizeof(rsp)); + if (sk && !bt_sk(sk)->le_params.prohibit_remote_chg) { + req = (struct l2cap_conn_param_update_req *) data; + min = __le16_to_cpu(req->min); + max = __le16_to_cpu(req->max); + latency = __le16_to_cpu(req->latency); + timeout = __le16_to_cpu(req->to_multiplier); - err = l2cap_check_conn_param(min, max, latency, to_multiplier); - if (err) - rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED); - else - rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED); + err = l2cap_check_conn_param(min, max, latency, timeout); + if (!err) { + rsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED); + hci_le_conn_update(hcon, min, max, latency, timeout); + bt_sk(sk)->le_params.interval_min = min; + bt_sk(sk)->le_params.interval_max = max; + bt_sk(sk)->le_params.latency = latency; + bt_sk(sk)->le_params.supervision_timeout = timeout; + } + } l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP, sizeof(rsp), &rsp); - if (!err) - hci_le_conn_update(hcon, min, max, latency, to_multiplier); return 0; } 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; @@ -3515,7 +5772,7 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, break; case L2CAP_CREATE_CHAN_REQ: - err = l2cap_create_channel_req(conn, cmd, cmd_len, data); + err = l2cap_create_channel_req(conn, cmd, data); break; case L2CAP_CREATE_CHAN_RSP: @@ -3523,21 +5780,11 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, break; case L2CAP_MOVE_CHAN_REQ: - err = l2cap_move_channel_req(conn, cmd, cmd_len, data); - break; - case L2CAP_MOVE_CHAN_RSP: - err = l2cap_move_channel_rsp(conn, cmd, cmd_len, data); - break; - case L2CAP_MOVE_CHAN_CFM: - err = l2cap_move_channel_confirm(conn, cmd, cmd_len, data); - break; - case L2CAP_MOVE_CHAN_CFM_RSP: - err = l2cap_move_channel_confirm_rsp(conn, cmd, cmd_len, data); + err = l2cap_sig_amp(conn, cmd, data, skb); break; - default: BT_ERR("Unknown BR/EDR signaling command 0x%2.2x", cmd->code); err = -EINVAL; @@ -3594,15 +5841,16 @@ 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_unk rej; + struct l2cap_cmd_rej rej; BT_ERR("Wrong link type (%d)", err); /* FIXME: Map err to a valid reason */ - rej.reason = cpu_to_le16(L2CAP_REJ_NOT_UNDERSTOOD); + rej.reason = cpu_to_le16(0); l2cap_send_cmd(conn, cmd.ident, L2CAP_COMMAND_REJ, sizeof(rej), &rej); } @@ -3613,94 +5861,183 @@ 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; - if (test_bit(FLAG_EXT_CTRL, &chan->flags)) - hdr_size = L2CAP_EXT_HDR_SIZE; + if (pi->extended_control) + hdr_size = L2CAP_EXTENDED_HDR_SIZE; else - hdr_size = L2CAP_ENH_HDR_SIZE; + hdr_size = L2CAP_ENHANCED_HDR_SIZE; - if (chan->fcs == L2CAP_FCS_CRC16) { + 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) { - u32 control = 0; + BT_DBG("sk %p, control %p", sk, control); + l2cap_ertm_tx(sk, control, 0, L2CAP_ERTM_EVENT_RECV_REQSEQ_AND_FBIT); +} + +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); +} - chan->frames_sent = 0; +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; - control |= __set_reqseq(chan, chan->buffer_seq); + BT_DBG("sk %p", sk); - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - control |= __set_ctrl_super(chan, L2CAP_SUPER_RNR); - l2cap_send_sframe(chan, control); - set_bit(CONN_RNR_SENT, &chan->conn_state); - } + pi = l2cap_pi(sk); + + if (pi->conn_state & L2CAP_CONN_REMOTE_BUSY) + return; + + if (pi->amp_move_state != L2CAP_AMP_STATE_STABLE && + pi->amp_move_state != L2CAP_AMP_STATE_WAIT_PREPARE) + return; + + while (pi->retrans_list.head != L2CAP_SEQ_LIST_CLEAR) { + seq = l2cap_seq_list_pop(&pi->retrans_list); + + 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; + } + + bt_cb(skb)->retries += 1; + control = bt_cb(skb)->control; - if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state)) - l2cap_retransmit_frames(chan); + 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; + } + + 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; + } + + 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 (!tx_skb) { + l2cap_seq_list_clear(&pi->retrans_list); + break; + } + + /* 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 (pi->fcs == L2CAP_FCS_CRC16) + apply_fcs(tx_skb); + + sock_hold(sk); + tx_skb->sk = sk; + tx_skb->destructor = l2cap_skb_destructor; + atomic_inc(&pi->ertm_queued); - l2cap_ertm_send(chan); + l2cap_do_send(sk, tx_skb); - if (!test_bit(CONN_LOCAL_BUSY, &chan->conn_state) && - chan->frames_sent == 0) { - control |= __set_ctrl_super(chan, L2CAP_SUPER_RR); - l2cap_send_sframe(chan, control); + BT_DBG("Resent txseq %d", (int)control.txseq); + + pi->last_acked_seq = pi->buffer_seq; } } -static int l2cap_add_to_srej_queue(struct l2cap_chan *chan, struct sk_buff *skb, u16 tx_seq, u8 sar) +static inline void l2cap_ertm_retransmit(struct sock *sk, + struct bt_l2cap_control *control) +{ + BT_DBG("sk %p, control %p", sk, control); + + l2cap_seq_list_append(&l2cap_pi(sk)->retrans_list, control->reqseq); + l2cap_ertm_resend(sk); +} + +static void l2cap_ertm_retransmit_all(struct sock *sk, + struct bt_l2cap_control *control) { - struct sk_buff *next_skb; - int tx_seq_offset, next_tx_seq_offset; + struct l2cap_pinfo *pi; + struct sk_buff *skb; - bt_cb(skb)->tx_seq = tx_seq; - bt_cb(skb)->sar = sar; + BT_DBG("sk %p, control %p", sk, control); - next_skb = skb_peek(&chan->srej_q); + pi = l2cap_pi(sk); - tx_seq_offset = __seq_offset(chan, tx_seq, chan->buffer_seq); + if (control->poll) + pi->conn_state |= L2CAP_CONN_SEND_FBIT; - while (next_skb) { - if (bt_cb(next_skb)->tx_seq == tx_seq) - return -EINVAL; + l2cap_seq_list_clear(&pi->retrans_list); - next_tx_seq_offset = __seq_offset(chan, - bt_cb(next_skb)->tx_seq, chan->buffer_seq); + if (pi->conn_state & L2CAP_CONN_REMOTE_BUSY) + return; - if (next_tx_seq_offset > tx_seq_offset) { - __skb_queue_before(&chan->srej_q, next_skb, skb); - return 0; + 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; } - if (skb_queue_is_last(&chan->srej_q, next_skb)) - next_skb = NULL; - else - next_skb = skb_queue_next(&chan->srej_q, next_skb); - } + skb_queue_walk_from(TX_QUEUE(sk), skb) { + if (skb == sk->sk_send_head) + break; - __skb_queue_tail(&chan->srej_q, skb); + l2cap_seq_list_append(&pi->retrans_list, + bt_cb(skb)->control.txseq); + } - return 0; + l2cap_ertm_resend(sk); + } } -static void append_skb_frag(struct sk_buff *skb, +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 + skb->data_len reflects only data in fragments */ + BT_DBG("skb %p, new_frag %p, *last_frag %p", skb, new_frag, *last_frag); + if (!skb_has_frag_list(skb)) skb_shinfo(skb)->frag_list = new_frag; @@ -3714,651 +6051,1147 @@ static void append_skb_frag(struct sk_buff *skb, skb->truesize += new_frag->truesize; } -static int l2cap_reassemble_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u32 control) +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; - switch (__get_ctrl_sar(chan, control)) { + BT_DBG("sk %p, control %p, skb %p len %d truesize %d", sk, control, + skb, skb->len, skb->truesize); + + if (!control) + return err; + + pi = l2cap_pi(sk); + + BT_DBG("type %c, sar %d, txseq %d, reqseq %d, final %d", + control->frame_type, control->sar, control->txseq, + control->reqseq, control->final); + + switch (control->sar) { case L2CAP_SAR_UNSEGMENTED: - if (chan->sdu) - break; + 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; + } - err = chan->ops->recv(chan->data, skb); + BT_DBG("Unsegmented"); + err = sock_queue_rcv_skb(sk, skb); break; case L2CAP_SAR_START: - if (chan->sdu) - break; + if (pi->sdu) { + BT_DBG("Unexpected start PDU during reassembly"); + kfree_skb(pi->sdu); + } - chan->sdu_len = get_unaligned_le16(skb->data); - skb_pull(skb, L2CAP_SDULEN_SIZE); + pi->sdu_len = get_unaligned_le16(skb->data); + skb_pull(skb, 2); - if (chan->sdu_len > chan->imtu) { + if (pi->sdu_len > pi->imtu) { err = -EMSGSIZE; break; } - if (skb->len >= chan->sdu_len) + if (skb->len >= pi->sdu_len) break; - chan->sdu = skb; - chan->sdu_last_frag = skb; + pi->sdu = skb; + pi->sdu_last_frag = skb; + + BT_DBG("Start"); skb = NULL; err = 0; break; case L2CAP_SAR_CONTINUE: - if (!chan->sdu) + if (!pi->sdu) break; - append_skb_frag(chan->sdu, skb, - &chan->sdu_last_frag); + append_skb_frag(pi->sdu, skb, + &pi->sdu_last_frag); skb = NULL; - if (chan->sdu->len >= chan->sdu_len) + if (pi->sdu->len >= pi->sdu_len) break; + BT_DBG("Continue, reassembled %d", pi->sdu->len); + err = 0; break; case L2CAP_SAR_END: - if (!chan->sdu) + if (!pi->sdu) break; - append_skb_frag(chan->sdu, skb, - &chan->sdu_last_frag); + append_skb_frag(pi->sdu, skb, + &pi->sdu_last_frag); skb = NULL; - if (chan->sdu->len != chan->sdu_len) + if (pi->sdu->len != pi->sdu_len) break; - err = chan->ops->recv(chan->data, chan->sdu); + 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 */ - chan->sdu = NULL; - chan->sdu_last_frag = NULL; - chan->sdu_len = 0; + pi->sdu = NULL; + pi->sdu_last_frag = NULL; + pi->sdu_len = 0; } break; + + default: + BT_DBG("Bad SAR value"); + break; } if (err) { - kfree_skb(skb); - kfree_skb(chan->sdu); - chan->sdu = NULL; - chan->sdu_last_frag = NULL; - chan->sdu_len = 0; + 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); } + /* 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); + return err; } -static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan) +static int l2cap_ertm_rx_queued_iframes(struct sock *sk) { - BT_DBG("chan %p, Enter local busy", chan); + int err = 0; + /* Pass sequential frames to l2cap_ertm_rx_expected_iframe() + * until a gap is encountered. + */ + + struct l2cap_pinfo *pi; + + BT_DBG("sk %p", sk); + pi = l2cap_pi(sk); + + 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))); + + skb = l2cap_ertm_seq_in_queue(SREJ_QUEUE(sk), pi->buffer_seq); - set_bit(CONN_LOCAL_BUSY, &chan->conn_state); + if (!skb) + break; + + 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; + } + + if (skb_queue_empty(SREJ_QUEUE(sk))) { + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; + l2cap_ertm_send_ack(sk); + } - __set_ack_timer(chan); + return err; } -static void l2cap_ertm_exit_local_busy(struct l2cap_chan *chan) +static void l2cap_ertm_handle_srej(struct sock *sk, + struct bt_l2cap_control *control) { - u32 control; + struct l2cap_pinfo *pi; + struct sk_buff *skb; - if (!test_bit(CONN_RNR_SENT, &chan->conn_state)) - goto done; + BT_DBG("sk %p, control %p", sk, control); - control = __set_reqseq(chan, chan->buffer_seq); - control |= __set_ctrl_poll(chan); - control |= __set_ctrl_super(chan, L2CAP_SUPER_RR); - l2cap_send_sframe(chan, control); - chan->retry_count = 1; + pi = l2cap_pi(sk); + + 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; + } - __clear_retrans_timer(chan); - __set_monitor_timer(chan); + skb = l2cap_ertm_seq_in_queue(TX_QUEUE(sk), control->reqseq); - set_bit(CONN_WAIT_F, &chan->conn_state); + if (skb == NULL) { + BT_DBG("Seq %d not available for retransmission", + (int) control->reqseq); + return; + } -done: - clear_bit(CONN_LOCAL_BUSY, &chan->conn_state); - clear_bit(CONN_RNR_SENT, &chan->conn_state); + 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; + } + + 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); - BT_DBG("chan %p, Exit local busy", chan); + 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; + } + } + } } -void l2cap_chan_busy(struct l2cap_chan *chan, int busy) +static void l2cap_ertm_handle_rej(struct sock *sk, + struct bt_l2cap_control *control) { - if (chan->mode == L2CAP_MODE_ERTM) { - if (busy) - l2cap_ertm_enter_local_busy(chan); + struct l2cap_pinfo *pi; + struct sk_buff *skb; + + BT_DBG("sk %p, control %p", sk, control); + + pi = l2cap_pi(sk); + + 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; + } + + skb = l2cap_ertm_seq_in_queue(TX_QUEUE(sk), control->reqseq); + + 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; + } + + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + + 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_exit_local_busy(chan); + 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; } } -static void l2cap_check_srej_gap(struct l2cap_chan *chan, u16 tx_seq) +static u8 l2cap_ertm_classify_txseq(struct sock *sk, u16 txseq) { - struct sk_buff *skb; - u32 control; + struct l2cap_pinfo *pi; - while ((skb = skb_peek(&chan->srej_q)) && - !test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - int err; + BT_DBG("sk %p, txseq %d", sk, (int)txseq); + pi = l2cap_pi(sk); - if (bt_cb(skb)->tx_seq != tx_seq) - break; + 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; + } + } - skb = skb_dequeue(&chan->srej_q); - control = __set_ctrl_sar(chan, bt_cb(skb)->sar); - err = l2cap_reassemble_sdu(chan, skb, control); + if (pi->srej_list.head == txseq) { + BT_DBG("Expected SREJ"); + return L2CAP_ERTM_TXSEQ_EXPECTED_SREJ; + } - if (err < 0) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - break; + if (l2cap_ertm_seq_in_queue(SREJ_QUEUE(sk), txseq)) { + BT_DBG("Duplicate SREJ - txseq already stored"); + return L2CAP_ERTM_TXSEQ_DUPLICATE_SREJ; } - chan->buffer_seq_srej = __next_seq(chan, chan->buffer_seq_srej); - tx_seq = __next_seq(chan, tx_seq); + if (l2cap_seq_list_contains(&pi->srej_list, txseq)) { + BT_DBG("Unexpected SREJ - txseq not requested " + "with SREJ"); + return L2CAP_ERTM_TXSEQ_UNEXPECTED_SREJ; + } } -} -static void l2cap_resend_srejframe(struct l2cap_chan *chan, u16 tx_seq) -{ - struct srej_list *l, *tmp; - u32 control; + 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; + } + } - list_for_each_entry_safe(l, tmp, &chan->srej_l, list) { - if (l->tx_seq == tx_seq) { - list_del(&l->list); - kfree(l); - return; + 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; } - control = __set_ctrl_super(chan, L2CAP_SUPER_SREJ); - control |= __set_reqseq(chan, l->tx_seq); - l2cap_send_sframe(chan, control); - list_del(&l->list); - list_add_tail(&l->list, &chan->srej_l); + } else { + BT_DBG("Unexpected - txseq indicates missing frames"); + return L2CAP_ERTM_TXSEQ_UNEXPECTED; } } -static int l2cap_send_srejframe(struct l2cap_chan *chan, u16 tx_seq) +static int l2cap_ertm_rx_state_recv(struct sock *sk, + struct bt_l2cap_control *control, + struct sk_buff *skb, u8 event) { - struct srej_list *new; - u32 control; + struct l2cap_pinfo *pi; + int err = 0; + bool skb_in_use = 0; - while (tx_seq != chan->expected_tx_seq) { - control = __set_ctrl_super(chan, L2CAP_SUPER_SREJ); - control |= __set_reqseq(chan, chan->expected_tx_seq); - l2cap_send_sframe(chan, control); + BT_DBG("sk %p, control %p, skb %p, event %d", sk, control, skb, + (int)event); + pi = l2cap_pi(sk); - new = kzalloc(sizeof(struct srej_list), GFP_ATOMIC); - if (!new) - return -ENOMEM; + 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); + + 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); + } + } - new->tx_seq = chan->expected_tx_seq; + 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; + } + + /* 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))); - chan->expected_tx_seq = __next_seq(chan, chan->expected_tx_seq); + pi->conn_state &= ~L2CAP_CONN_SREJ_ACT; + l2cap_seq_list_clear(&pi->srej_list); + l2cap_ertm_send_srej(sk, control->txseq); + + 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); + } - list_add_tail(&new->list, &chan->srej_l); + 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); + } + 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; } - chan->expected_tx_seq = __next_seq(chan, chan->expected_tx_seq); + if (skb && !skb_in_use) { + BT_DBG("Freeing %p", skb); + kfree_skb(skb); + } - return 0; + return err; } -static inline int l2cap_data_channel_iframe(struct l2cap_chan *chan, u32 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) { - u16 tx_seq = __get_txseq(chan, rx_control); - u16 req_seq = __get_reqseq(chan, rx_control); - u8 sar = __get_ctrl_sar(chan, rx_control); - 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%8.8x", 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 (__is_ctrl_final(chan, 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; - chan->expected_ack_seq = req_seq; - l2cap_drop_acked_frames(chan); + 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); + } - tx_seq_offset = __seq_offset(chan, tx_seq, chan->buffer_seq); + 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); + } - /* invalid tx_seq */ - if (tx_seq_offset >= chan->tx_win) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; + 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; } - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - if (!test_bit(CONN_RNR_SENT, &chan->conn_state)) - l2cap_send_ack(chan); - goto drop; + if (skb && !skb_in_use) { + BT_DBG("Freeing %p", skb); + kfree_skb(skb); } - if (tx_seq == chan->expected_tx_seq) - goto expected; + return err; +} + +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; - err = l2cap_send_srejframe(chan, tx_seq); - if (err < 0) { - l2cap_send_disconn_req(chan->conn, chan, -err); - return err; + if (control->final) { + if (pi->conn_state & L2CAP_CONN_REJ_ACT) + pi->conn_state &= ~L2CAP_CONN_REJ_ACT; + else + control->final = 0; } } - } else { - expected_tx_seq_offset = __seq_offset(chan, - chan->expected_tx_seq, chan->buffer_seq); - - /* 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 P-bit only if there are some I-frames to ack. */ - if (__clear_ack_timer(chan)) - set_bit(CONN_SEND_PBIT, &chan->conn_state); + pi = l2cap_pi(sk); - err = l2cap_send_srejframe(chan, tx_seq); - if (err < 0) { - l2cap_send_disconn_req(chan->conn, chan, -err); - return err; - } - } - return 0; + l2cap_ertm_process_reqseq(sk, pi->amp_move_reqseq); -expected: - chan->expected_tx_seq = __next_seq(chan, chan->expected_tx_seq); + if (!skb_queue_empty(TX_QUEUE(sk))) + sk->sk_send_head = skb_peek(TX_QUEUE(sk)); + else + sk->sk_send_head = NULL; - 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; - } + /* Rewind next_tx_seq to the point expected + * by the receiver. + */ + pi->next_tx_seq = pi->amp_move_reqseq; + pi->unacked_frames = 0; - err = l2cap_reassemble_sdu(chan, skb, rx_control); - chan->buffer_seq = __next_seq(chan, chan->buffer_seq); + err = l2cap_finish_amp_move(sk); - if (err < 0) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); + if (err) return err; - } - if (__is_ctrl_final(chan, rx_control)) { - if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state)) - l2cap_retransmit_frames(chan); - } + pi->conn_state |= L2CAP_CONN_SEND_FBIT; + l2cap_ertm_send_i_or_rr_or_rnr(sk); + memset(&control, 0, sizeof(control)); + control.reqseq = pi->amp_move_reqseq; - chan->num_acked = (chan->num_acked + 1) % num_to_ack; - if (chan->num_acked == num_to_ack - 1) - l2cap_send_ack(chan); + if (pi->amp_move_event == L2CAP_ERTM_EVENT_RECV_IFRAME) + err = -EPROTO; else - __set_ack_timer(chan); - - return 0; + 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, u32 rx_control) +static void l2cap_amp_move_setup(struct sock *sk) { - BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", chan, - __get_reqseq(chan, rx_control), rx_control); - - chan->expected_ack_seq = __get_reqseq(chan, rx_control); - l2cap_drop_acked_frames(chan); - - if (__is_ctrl_poll(chan, rx_control)) { - 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 (__is_ctrl_final(chan, rx_control)) { - 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, u32 rx_control) -{ - u16 tx_seq = __get_reqseq(chan, rx_control); - BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", 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 (__is_ctrl_final(chan, rx_control)) { - 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, u32 rx_control) -{ - u16 tx_seq = __get_reqseq(chan, rx_control); - - BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", chan, tx_seq, rx_control); - - clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); - if (__is_ctrl_poll(chan, rx_control)) { - 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 (__is_ctrl_final(chan, rx_control)) { - 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, u32 rx_control) +static int l2cap_amp_move_reconf(struct sock *sk) { - u16 tx_seq = __get_reqseq(chan, rx_control); - - BT_DBG("chan %p, req_seq %d ctrl 0x%8.8x", 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 (__is_ctrl_poll(chan, rx_control)) - 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 (__is_ctrl_poll(chan, rx_control)) - l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_FINAL); - return; - } + pi = l2cap_pi(sk); - if (__is_ctrl_poll(chan, rx_control)) { - l2cap_send_srejtail(chan); - } else { - rx_control = __set_ctrl_super(chan, L2CAP_SUPER_RR); - l2cap_send_sframe(chan, rx_control); - } + 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, u32 rx_control, struct sk_buff *skb) +static void l2cap_amp_move_success(struct sock *sk) { - BT_DBG("chan %p rx_control 0x%8.8x len %d", chan, rx_control, skb->len); - - if (__is_ctrl_final(chan, 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 (__get_ctrl_super(chan, rx_control)) { - case L2CAP_SUPER_RR: - l2cap_data_channel_rrframe(chan, rx_control); - break; + BT_DBG("sk %p", sk); - case L2CAP_SUPER_REJ: - l2cap_data_channel_rejframe(chan, rx_control); - break; + pi = l2cap_pi(sk); - case L2CAP_SUPER_SREJ: - 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_RNR: - 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 l2cap_chan *chan, struct sk_buff *skb) +static int l2cap_strm_rx(struct sock *sk, struct bt_l2cap_control *control, + struct sk_buff *skb) { - u32 control; - u16 req_seq; - int len, next_tx_seq_offset, req_seq_offset; + struct l2cap_pinfo *pi; + int err = 0; - control = __get_control(chan, skb->data); - skb_pull(skb, __ctrl_size(chan)); - 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(chan, control) && !__is_sframe(chan, control)) - len -= L2CAP_SDULEN_SIZE; + 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 -= L2CAP_FCS_SIZE; + 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(chan, control); + pi->last_acked_seq = control->txseq; + pi->expected_tx_seq = __next_seq(control->txseq, pi); - req_seq_offset = __seq_offset(chan, req_seq, chan->expected_ack_seq); + return err; +} - next_tx_seq_offset = __seq_offset(chan, chan->next_tx_seq, - chan->expected_ack_seq); +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; - /* check for invalid req-seq */ - if (req_seq_offset > next_tx_seq_offset) { - 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); - if (!__is_sframe(chan, control)) { - if (len < 0) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; - } + pi = l2cap_pi(sk); - 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; + 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; } - - l2cap_data_channel_sframe(chan, control, skb); + } 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; - u32 control; - u16 tx_seq; - int len; + lock_sock(sk); - chan = l2cap_get_chan_by_scid(conn, cid); - if (!chan) { - BT_DBG("unknown cid 0x%4.4x", cid); - /* Drop packet and return */ - kfree_skb(skb); - return 0; - } + 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); - l2cap_chan_lock(chan); + return; +} + +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 +}; + +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: - l2cap_ertm_data_rcv(chan, skb); - - goto done; - case L2CAP_MODE_STREAMING: - control = __get_control(chan, skb->data); - skb_pull(skb, __ctrl_size(chan)); + control = &bt_cb(skb)->control; + if (pi->extended_control) { + __get_extended_control(get_unaligned_le32(skb->data), + control); + skb_pull(skb, 4); + } else { + __get_enhanced_control(get_unaligned_le16(skb->data), + control); + 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(chan, control)) - len -= L2CAP_SDULEN_SIZE; + if ((control->frame_type == 'i') && + (control->sar == L2CAP_SAR_START)) + len -= 2; - if (chan->fcs == L2CAP_FCS_CRC16) - len -= L2CAP_FCS_SIZE; + if (pi->fcs == L2CAP_FCS_CRC16) + len -= 2; - if (len > chan->mps || len < 0 || __is_sframe(chan, 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(chan, control); + if (control->frame_type == 'i') { - if (chan->expected_tx_seq != tx_seq) { - /* Frame(s) missing - must discard partial SDU */ - kfree_skb(chan->sdu); - chan->sdu = NULL; - chan->sdu_last_frag = NULL; - chan->sdu_len = 0; + int err; - /* TODO: Notify userland of missing data */ - } + 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; + } - chan->expected_tx_seq = __next_seq(chan, tx_seq); + /* Validate F and P bits */ + if (control->final && + ((pi->tx_state != L2CAP_ERTM_TX_STATE_WAIT_F) + || control->poll)) + goto drop; - if (l2cap_reassemble_sdu(chan, skb, control) == -EMSGSIZE) - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); + 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; } @@ -4366,64 +7199,128 @@ static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk kfree_skb(skb); done: - l2cap_chan_unlock(chan); - 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 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; - BT_DBG("chan %p, len %d", chan, skb->len); + 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)) - return 0; + if (!sock_queue_rcv_skb(sk, skb)) + goto done; drop: kfree_skb(skb); +done: + if (sk) + bh_unlock_sock(sk); return 0; } -static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, struct sk_buff *skb) +static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, + struct sk_buff *skb) { - struct l2cap_chan *chan; + struct sock *sk; + struct sk_buff *skb_rsp; + struct l2cap_hdr *lh; + int dir; + u8 mtu_rsp[] = {L2CAP_ATT_MTU_RSP, 23, 0}; + u8 err_rsp[] = {L2CAP_ATT_ERROR, 0x00, 0x00, 0x00, + L2CAP_ATT_NOT_SUPPORTED}; - chan = l2cap_global_chan_by_scid(0, cid, conn->src); - if (!chan) + dir = (skb->data[0] & L2CAP_ATT_RESPONSE_BIT) ? 0 : 1; + + sk = l2cap_find_sock_by_fixed_cid_and_dir(cid, conn->src, + conn->dst, dir); + + BT_DBG("sk %p, dir:%d", sk, dir); + + if (!sk) goto drop; - BT_DBG("chan %p, len %d", chan, skb->len); + 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)) - return 0; + if (skb->data[0] == L2CAP_ATT_MTU_REQ) { + skb_rsp = bt_skb_alloc(sizeof(mtu_rsp) + L2CAP_HDR_SIZE, + GFP_ATOMIC); + if (!skb_rsp) + goto drop; + + lh = (struct l2cap_hdr *) skb_put(skb_rsp, L2CAP_HDR_SIZE); + lh->len = cpu_to_le16(sizeof(mtu_rsp)); + lh->cid = cpu_to_le16(L2CAP_CID_LE_DATA); + memcpy(skb_put(skb_rsp, sizeof(mtu_rsp)), mtu_rsp, + sizeof(mtu_rsp)); + hci_send_acl(conn->hcon, NULL, skb_rsp, 0); + + goto free_skb; + } + + if (!sock_queue_rcv_skb(sk, skb)) + goto done; drop: + if (skb->data[0] & L2CAP_ATT_RESPONSE_BIT && + skb->data[0] != L2CAP_ATT_INDICATE) + goto free_skb; + + /* If this is an incoming PDU that requires a response, respond with + * a generic error so remote device doesn't hang */ + + skb_rsp = bt_skb_alloc(sizeof(err_rsp) + L2CAP_HDR_SIZE, GFP_ATOMIC); + if (!skb_rsp) + goto free_skb; + + lh = (struct l2cap_hdr *) skb_put(skb_rsp, L2CAP_HDR_SIZE); + lh->len = cpu_to_le16(sizeof(err_rsp)); + lh->cid = cpu_to_le16(L2CAP_CID_LE_DATA); + err_rsp[1] = skb->data[0]; + memcpy(skb_put(skb_rsp, sizeof(err_rsp)), err_rsp, sizeof(err_rsp)); + hci_send_acl(conn->hcon, NULL, skb_rsp, 0); + +free_skb: kfree_skb(skb); +done: + if (sk) + bh_unlock_sock(sk); return 0; } 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; @@ -4456,204 +7353,244 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) case L2CAP_CID_SMP: if (smp_sig_channel(conn, skb)) - l2cap_conn_del(conn->hcon, EACCES); + l2cap_conn_del(conn->hcon, EACCES, 0); 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->hcon, skb); + } else { + BT_DBG("unknown cid 0x%4.4x", cid); + kfree_skb(skb); + } + break; } } /* ---- L2CAP interface with lower layer (HCI) ---- */ -int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr) +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 0; 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 (test_bit(FLAG_ROLE_SWITCH, &c->flags)) + 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 (test_bit(FLAG_ROLE_SWITCH, &c->flags)) + 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; } -int l2cap_connect_cfm(struct hci_conn *hcon, u8 status) +static int l2cap_connect_cfm(struct hci_conn *hcon, u8 status) { struct l2cap_conn *conn; BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status); + if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK)) + return -EINVAL; + if (!status) { conn = l2cap_conn_add(hcon, status); if (conn) l2cap_conn_ready(conn); } else - l2cap_conn_del(hcon, bt_to_errno(status)); + l2cap_conn_del(hcon, bt_err(status), 0); return 0; } -int l2cap_disconn_ind(struct hci_conn *hcon) +static int l2cap_disconn_ind(struct hci_conn *hcon) { struct l2cap_conn *conn = hcon->l2cap_data; BT_DBG("hcon %p", hcon); - if (!conn) - return HCI_ERROR_REMOTE_USER_TERM; + if (hcon->type != ACL_LINK || !conn) + return 0x13; + return conn->disc_reason; } -int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason) +static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason, u8 is_process) { BT_DBG("hcon %p reason %d", hcon, reason); - l2cap_conn_del(hcon, bt_to_errno(reason)); + if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK)) + return -EINVAL; + + l2cap_conn_del(hcon, bt_err(reason), is_process); + 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, L2CAP_ENC_TIMEOUT); - } 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); } } -int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) +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; + int smp = 0; if (!conn) return 0; - BT_DBG("conn %p", conn); + l = &conn->chan_list; - if (hcon->type == LE_LINK) { - smp_distribute_keys(conn, 0); - cancel_delayed_work(&conn->security_timer); - } + BT_DBG("conn %p", conn); - mutex_lock(&conn->chan_lock); + read_lock(&l->lock); - list_for_each_entry(chan, &conn->chan_l, list) { - l2cap_chan_lock(chan); + 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_chan_ready(chan); + l2cap_pi(sk)->sec_level = hcon->sec_level; + l2cap_chan_ready(sk); } - l2cap_chan_unlock(chan); + smp = 1; + bh_unlock_sock(sk); continue; } - if (test_bit(CONF_CONNECT_PEND, &chan->conf_state)) { - l2cap_chan_unlock(chan); + 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)) { - struct sock *sk = chan->sk; - - bt_sk(sk)->suspended = false; - sk->sk_state_change(sk); - - l2cap_check_encryption(chan, encrypt); - l2cap_chan_unlock(chan); + 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) { - l2cap_send_conn_req(chan); + 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, L2CAP_DISC_TIMEOUT); + l2cap_sock_clear_timer(sk); + l2cap_sock_set_timer(sk, HZ / 10); } - } else if (chan->state == BT_CONNECT2) { - struct sock *sk = chan->sk; + } else if (sk->sk_state == BT_CONNECT2) { struct l2cap_conn_rsp rsp; - __u16 res, stat; - - lock_sock(sk); + __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, L2CAP_DISC_TIMEOUT); - 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; } - release_sock(sk); + 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); - 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); + if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT) && + result == L2CAP_CR_SUCCESS) { + char 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++; + } } - l2cap_chan_unlock(chan); + bh_unlock_sock(sk); } - mutex_unlock(&conn->chan_lock); + read_unlock(&l->lock); + + if (smp) { + del_timer(&hcon->smp_timer); + smp_link_encrypt_cmplt(conn, status, encrypt); + } return 0; } -int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) +static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) { 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); @@ -4662,10 +7599,8 @@ int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) 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; - u16 cid; int len; if (conn->rx_len) { @@ -4685,7 +7620,6 @@ int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) hdr = (struct l2cap_hdr *) skb->data; len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE; - cid = __le16_to_cpu(hdr->cid); if (len == skb->len) { /* Complete frame received */ @@ -4693,6 +7627,14 @@ int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) 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) { @@ -4702,23 +7644,6 @@ int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) goto drop; } - chan = l2cap_get_chan_by_scid(conn, cid); - - if (chan && chan->sk) { - struct sock *sk = chan->sk; - lock_sock(sk); - - if (chan->imtu < len - L2CAP_HDR_SIZE) { - BT_ERR("Frame exceeding recv MTU (len %d, " - "MTU %d)", len, - chan->imtu); - release_sock(sk); - l2cap_conn_unreliable(conn, ECOMM); - goto drop; - } - release_sock(sk); - } - /* Allocate skb for the complete frame (with header) */ conn->rx_skb = bt_skb_alloc(len, GFP_ATOMIC); if (!conn->rx_skb) @@ -4762,24 +7687,53 @@ int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags) return 0; } +static void l2cap_set_acl_flushto(struct hci_conn *hcon, u16 flush_to) +{ + struct hci_cp_write_automatic_flush_timeout flush_tm; + if (hcon && hcon->hdev) { + flush_tm.handle = hcon->handle; + if (flush_to == L2CAP_DEFAULT_FLUSH_TO) + flush_to = 0; + flush_tm.timeout = (flush_to < L2CAP_MAX_FLUSH_TO) ? + flush_to : L2CAP_MAX_FLUSH_TO; + hci_send_cmd(hcon->hdev, + HCI_OP_WRITE_AUTOMATIC_FLUSH_TIMEOUT, + 4, &(flush_tm)); + } +} + +static u16 l2cap_get_smallest_flushto(struct l2cap_chan_list *l) +{ + int ret_flush_to = L2CAP_DEFAULT_FLUSH_TO; + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->flush_to > 0 && + l2cap_pi(s)->flush_to < ret_flush_to) + ret_flush_to = l2cap_pi(s)->flush_to; + } + return ret_flush_to; +} + static int l2cap_debugfs_show(struct seq_file *f, void *p) { - struct l2cap_chan *c; + struct sock *sk; + struct hlist_node *node; - read_lock(&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(&chan_list_lock); + read_unlock_bh(&l2cap_sk_list.lock); return 0; } @@ -4798,6 +7752,20 @@ static const struct file_operations l2cap_debugfs_fops = { static struct dentry *l2cap_debugfs; +static struct hci_proto l2cap_hci_proto = { + .name = "L2CAP", + .id = HCI_PROTO_L2CAP, + .connect_ind = l2cap_connect_ind, + .connect_cfm = l2cap_connect_cfm, + .disconn_ind = l2cap_disconn_ind, + .disconn_cfm = l2cap_disconn_cfm, + .security_cfm = l2cap_security_cfm, + .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) { int err; @@ -4806,6 +7774,19 @@ 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"); + bt_sock_unregister(BTPROTO_L2CAP); + goto error; + } + if (bt_debugfs) { l2cap_debugfs = debugfs_create_file("l2cap", 0444, bt_debugfs, NULL, &l2cap_debugfs_fops); @@ -4813,14 +7794,36 @@ 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"); + l2cap_cleanup_sockets(); } 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 04e7c172d49c9e0ce421b7f378f693e28bda888f..0ad1633005ade22f30ff840394af099851a415a6 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1,9 +1,8 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2011-2012 Code Aurora Forum. All rights reserved. Copyright (C) 2009-2010 Gustavo F. Padovan Copyright (C) 2010 Google Inc. - Copyright (C) 2011 ProFUSION Embedded Systems Written 2000,2001 by Maxim Krasnyansky @@ -27,22 +26,93 @@ /* Bluetooth L2CAP sockets. */ -#include -#include +#include +#include #include #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); +} + +int l2cap_sock_le_params_valid(struct bt_le_params *le_params) +{ + if (!le_params || le_params->latency > BT_LE_LATENCY_MAX || + le_params->scan_window > BT_LE_SCAN_WINDOW_MAX || + le_params->scan_interval < BT_LE_SCAN_INTERVAL_MIN || + le_params->scan_window > le_params->scan_interval || + le_params->interval_min < BT_LE_CONN_INTERVAL_MIN || + le_params->interval_max > BT_LE_CONN_INTERVAL_MAX || + le_params->interval_min > le_params->interval_max || + le_params->supervision_timeout < BT_LE_SUP_TO_MIN || + le_params->supervision_timeout > BT_LE_SUP_TO_MAX) { + return 0; + } + + return 1; +} + +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; @@ -81,22 +151,26 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) } } - if (la.l2_cid) - err = l2cap_add_scid(chan, __le16_to_cpu(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); @@ -106,11 +180,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) @@ -123,25 +197,80 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al if (la.l2_cid && la.l2_psm) return -EINVAL; - err = l2cap_chan_connect(chan, la.l2_psm, __le16_to_cpu(la.l2_cid), - &la.l2_bdaddr); - if (err) - return err; - lock_sock(sk); + 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 (l2cap_pi(sk)->mode) { + case L2CAP_MODE_BASIC: + break; + case L2CAP_MODE_ERTM: + case L2CAP_MODE_STREAMING: + if (!disable_ertm) + break; + /* fall through */ + default: + err = -ENOTSUPP; + goto done; + } + + switch (sk->sk_state) { + case BT_CONNECT: + case BT_CONNECT2: + case BT_CONFIG: + /* Already connecting */ + goto wait; + + case BT_CONNECTED: + /* Already connected */ + err = -EISCONN; + goto done; + + case BT_OPEN: + case BT_BOUND: + /* Can connect */ + break; + + default: + err = -EBADFD; + goto done; + } + + /* PSM must be odd and lsb of upper byte must be 0 */ + 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); + l2cap_pi(sk)->psm = la.l2_psm; + l2cap_pi(sk)->dcid = la.l2_cid; + + err = l2cap_do_connect(sk); + if (err) + goto done; + +wait: 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; } 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); @@ -154,7 +283,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: @@ -167,10 +296,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: @@ -187,26 +336,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; } @@ -214,12 +367,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) @@ -238,7 +387,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); @@ -246,13 +394,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; @@ -261,7 +409,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; @@ -277,13 +424,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 = 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)) @@ -292,7 +439,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; @@ -308,12 +455,15 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __us break; } - if (test_bit(FLAG_ROLE_SWITCH, &chan->flags)) + if (l2cap_pi(sk)->role_switch) opt |= L2CAP_LM_MASTER; - if (test_bit(FLAG_FORCE_RELIABLE, &chan->flags)) + 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; @@ -326,9 +476,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)) @@ -348,7 +497,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; @@ -368,17 +516,19 @@ 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; + if (sk->sk_state == BT_CONNECTED) { + sec.key_size = l2cap_pi(sk)->conn->hcon->enc_key_size; + sec.level = l2cap_pi(sk)->conn->hcon->sec_level; + } len = min_t(unsigned int, len, sizeof(sec)); if (copy_to_user(optval, (char *) &sec, len)) @@ -397,13 +547,6 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch break; - case BT_FLUSHABLE: - if (put_user(test_bit(FLAG_FLUSHABLE, &chan->flags), - (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) { @@ -411,7 +554,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch break; } - pwr.force_active = test_bit(FLAG_FORCE_ACTIVE, &chan->flags); + pwr.force_active = l2cap_pi(sk)->force_active; len = min_t(unsigned int, len, sizeof(pwr)); if (copy_to_user(optval, (char *) &pwr, len)) @@ -419,13 +562,19 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch break; - case BT_CHANNEL_POLICY: - if (!enable_hs) { - err = -ENOPROTOOPT; + case BT_AMP_POLICY: + if (put_user(l2cap_pi(sk)->amp_pref, (u32 __user *) optval)) + err = -EFAULT; + break; + + case BT_LE_PARAMS: + if (l2cap_pi(sk)->scid != L2CAP_CID_LE_DATA) { + err = -EINVAL; break; } - if (put_user(chan->chan_policy, (u32 __user *) optval)) + if (copy_to_user(optval, (char *) &bt_sk(sk)->le_params, + sizeof(bt_sk(sk)->le_params))) err = -EFAULT; break; @@ -441,29 +590,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 = 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)) { @@ -471,18 +621,39 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __us break; } - if (opts.txwin_size > L2CAP_DEFAULT_EXT_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: + if (!disable_ertm) { + /* No fallback to ERTM or Basic mode */ + l2cap_pi(sk)->conf_state |= + L2CAP_CONF_STATE2_DEVICE; + break; + } + err = -EINVAL; + break; + case L2CAP_MODE_ERTM: if (!disable_ertm) break; /* fall through */ @@ -491,11 +662,12 @@ 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 = 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; + l2cap_pi(sk)->flush_to = opts.flush_to; break; case L2CAP_LM: @@ -505,21 +677,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; - if (opt & L2CAP_LM_MASTER) - set_bit(FLAG_ROLE_SWITCH, &chan->flags); - else - clear_bit(FLAG_ROLE_SWITCH, &chan->flags); - - if (opt & L2CAP_LM_RELIABLE) - set_bit(FLAG_FORCE_RELIABLE, &chan->flags); - else - clear_bit(FLAG_FORCE_RELIABLE, &chan->flags); + 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: @@ -534,9 +700,9 @@ 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 bt_le_params le_params; struct l2cap_conn *conn; int len, err = 0; u32 opt; @@ -553,8 +719,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; } @@ -573,15 +739,10 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch break; } - chan->sec_level = sec.level; - - if (!chan->conn) - break; - - conn = chan->conn; + l2cap_pi(sk)->sec_level = sec.level; - /*change security for LE channels */ - if (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; @@ -589,19 +750,9 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch if (smp_conn_security(conn, sec.level)) break; + + err = 0; sk->sk_state = BT_CONFIG; - chan->state = BT_CONFIG; - - /* or for ACL link */ - } else if ((sk->sk_state == BT_CONNECT2 && - bt_sk(sk)->defer_setup) || - sk->sk_state == BT_CONNECTED) { - if (!l2cap_chan_check_security(chan)) - bt_sk(sk)->suspended = true; - else - sk->sk_state_change(sk); - } else { - err = -EINVAL; } break; @@ -619,77 +770,87 @@ 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; } - if (opt) - set_bit(FLAG_FLUSHABLE, &chan->flags); - else - clear_bit(FLAG_FLUSHABLE, &chan->flags); - break; - - case BT_POWER: - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED && - chan->chan_type != L2CAP_CHAN_RAW) { + if ((opt > BT_AMP_POLICY_PREFER_AMP) || + ((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); - len = min_t(unsigned int, sizeof(pwr), optlen); - if (copy_from_user((char *) &pwr, optval, len)) { + if ((sk->sk_state == BT_CONNECTED) && + (l2cap_pi(sk)->amp_move_role == L2CAP_AMP_MOVE_NONE)) + l2cap_amp_move_init(sk); + + break; + + case BT_FLUSHABLE: + if (get_user(opt, (u32 __user *) optval)) { err = -EFAULT; break; } + l2cap_pi(sk)->flushable = opt; - if (pwr.force_active) - set_bit(FLAG_FORCE_ACTIVE, &chan->flags); - else - clear_bit(FLAG_FORCE_ACTIVE, &chan->flags); break; - case BT_CHANNEL_POLICY: - if (!enable_hs) { - err = -ENOPROTOOPT; + case BT_LE_PARAMS: + if (l2cap_pi(sk)->scid != L2CAP_CID_LE_DATA) { + err = -EINVAL; break; } - if (get_user(opt, (u32 __user *) optval)) { + if (copy_from_user((char *) &le_params, optval, + sizeof(struct bt_le_params))) { err = -EFAULT; break; } - if (opt > BT_CHANNEL_POLICY_AMP_PREFERRED) { - err = -EINVAL; + conn = l2cap_pi(sk)->conn; + if (!conn || !conn->hcon || + l2cap_pi(sk)->scid != L2CAP_CID_LE_DATA) { + memcpy(&bt_sk(sk)->le_params, &le_params, + sizeof(le_params)); break; } - if (chan->mode != L2CAP_MODE_ERTM && - chan->mode != L2CAP_MODE_STREAMING) { - err = -EOPNOTSUPP; + if (!conn->hcon->out || + !l2cap_sock_le_params_valid(&le_params)) { + err = -EINVAL; break; } - chan->chan_policy = (u8) opt; + memcpy(&bt_sk(sk)->le_params, &le_params, sizeof(le_params)); + + hci_le_conn_update(conn->hcon, + le_params.interval_min, + le_params.interval_max, + le_params.latency, + le_params.supervision_timeout); break; default: @@ -704,8 +865,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); @@ -719,12 +883,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; + } + + /* 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; } - err = l2cap_chan_send(chan, msg, len, sk->sk_priority); + 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; } @@ -732,16 +986,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; - pi->chan->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; } @@ -753,57 +1034,112 @@ 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); - - 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); + if (err >= 0) + l2cap_ertm_recv_done(sk); -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; - BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state)); + 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; - struct l2cap_conn *conn; int err = 0; BT_DBG("sock %p, sk %p", sock, sk); @@ -811,24 +1147,17 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) if (!sk) return 0; - chan = l2cap_pi(sk)->chan; - conn = chan->conn; - - if (conn) - mutex_lock(&conn->chan_lock); - - l2cap_chan_lock(chan); 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; - - release_sock(sk); - l2cap_chan_close(chan, 0); - lock_sock(sk); + 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, @@ -839,17 +1168,13 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) err = -sk->sk_err; release_sock(sk); - l2cap_chan_unlock(chan); - - if (conn) - mutex_unlock(&conn->chan_lock); - return err; } static int l2cap_sock_release(struct socket *sock) { struct sock *sk = sock->sk; + struct sock *srv_sk = NULL; int err; BT_DBG("sock %p, sk %p", sock, sk); @@ -857,6 +1182,16 @@ static int l2cap_sock_release(struct socket *sock) if (!sk) return 0; + /* If this is an ATT Client socket, find the matching Server */ + if (l2cap_pi(sk)->scid == L2CAP_CID_LE_DATA && !l2cap_pi(sk)->incoming) + srv_sk = l2cap_find_sock_by_fixed_cid_and_dir(L2CAP_CID_LE_DATA, + &bt_sk(sk)->src, &bt_sk(sk)->dst, 1); + + /* If server socket found, request tear down */ + BT_DBG("client:%p server:%p", sk, srv_sk); + if (srv_sk) + l2cap_sock_set_timer(srv_sk, 1); + err = l2cap_sock_shutdown(sock, 2); sock_orphan(sk); @@ -864,166 +1199,86 @@ 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; - - bt_sock_reclassify_lock(sk, BTPROTO_L2CAP); - - 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); - - lock_sock(sk); - - if (pi->rx_busy_skb) { - err = -ENOMEM; - goto done; - } - - 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; - } - -done: - release_sock(sk); - - 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 sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan, - unsigned long len, int nb, - int *err) -{ - struct sock *sk = chan->sk; - - return bt_skb_send_alloc(sk, len, nb, err); -} - -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, - .alloc_skb = l2cap_sock_alloc_skb_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 l2cap_sock_init(struct sock *sk, struct sock *parent) +static void set_default_config(struct l2cap_conf_prm *conf_prm) +{ + conf_prm->fcs = L2CAP_FCS_CRC16; + conf_prm->flush_to = L2CAP_DEFAULT_FLUSH_TO; +} + +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->tx_win_max = pchan->tx_win_max; - chan->sec_level = pchan->sec_level; - chan->flags = pchan->flags; - - security_sk_clone(parent, sk); + 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->tx_win_max = L2CAP_DEFAULT_TX_WINDOW; - chan->sec_level = BT_SECURITY_LOW; - chan->flags = 0; - set_bit(FLAG_FORCE_ACTIVE, &chan->flags); + 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; - - chan->data = sk; - chan->ops = &l2cap_chan_ops; + 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; + pi->local_conf.flush_to = pi->flush_to; + + 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 = { @@ -1032,10 +1287,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) @@ -1045,21 +1299,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; } @@ -1089,7 +1338,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 506628876f3604dcdf9530852e6a27095dbf1f21..b826d1bf10df5ce53f9674a061a65e940af73913 100644 --- a/net/bluetooth/lib.c +++ b/net/bluetooth/lib.c @@ -24,8 +24,6 @@ /* Bluetooth kernel library. */ -#define pr_fmt(fmt) "Bluetooth: " fmt - #include #include @@ -61,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: @@ -151,42 +149,4 @@ int bt_to_errno(__u16 code) return ENOSYS; } } -EXPORT_SYMBOL(bt_to_errno); - -int bt_info(const char *format, ...) -{ - struct va_format vaf; - va_list args; - int r; - - va_start(args, format); - - vaf.fmt = format; - vaf.va = &args; - - r = pr_info("%pV", &vaf); - - va_end(args); - - return r; -} -EXPORT_SYMBOL(bt_info); - -int bt_err(const char *format, ...) -{ - struct va_format vaf; - va_list args; - int r; - - va_start(args, format); - - vaf.fmt = format; - vaf.va = &args; - - r = pr_err("%pV", &vaf); - - va_end(args); - - return r; -} EXPORT_SYMBOL(bt_err); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index c05c3a69c3722c2db04e70608061b177ccc57d02..ac8542353a5f9db0abdd281b64a7964d4c77b739 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1,8 +1,7 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2010 Nokia Corporation - Copyright (C) 2011-2012 Intel Corporation + Copyright (c) 2011-2012 Code Aurora Forum. All rights reserved. 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 @@ -24,191 +23,45 @@ /* Bluetooth HCI Management interface */ -#include #include +#include #include #include #include #include +#include #include #include -bool enable_hs; -bool enable_le; - -#define MGMT_VERSION 1 -#define MGMT_REVISION 0 - -static const u16 mgmt_commands[] = { - MGMT_OP_READ_INDEX_LIST, - MGMT_OP_READ_INFO, - MGMT_OP_SET_POWERED, - MGMT_OP_SET_DISCOVERABLE, - MGMT_OP_SET_CONNECTABLE, - MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_OP_SET_PAIRABLE, - MGMT_OP_SET_LINK_SECURITY, - MGMT_OP_SET_SSP, - MGMT_OP_SET_HS, - MGMT_OP_SET_LE, - MGMT_OP_SET_DEV_CLASS, - MGMT_OP_SET_LOCAL_NAME, - MGMT_OP_ADD_UUID, - MGMT_OP_REMOVE_UUID, - MGMT_OP_LOAD_LINK_KEYS, - MGMT_OP_LOAD_LONG_TERM_KEYS, - MGMT_OP_DISCONNECT, - MGMT_OP_GET_CONNECTIONS, - MGMT_OP_PIN_CODE_REPLY, - MGMT_OP_PIN_CODE_NEG_REPLY, - MGMT_OP_SET_IO_CAPABILITY, - MGMT_OP_PAIR_DEVICE, - MGMT_OP_CANCEL_PAIR_DEVICE, - MGMT_OP_UNPAIR_DEVICE, - MGMT_OP_USER_CONFIRM_REPLY, - MGMT_OP_USER_CONFIRM_NEG_REPLY, - MGMT_OP_USER_PASSKEY_REPLY, - MGMT_OP_USER_PASSKEY_NEG_REPLY, - MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_OP_ADD_REMOTE_OOB_DATA, - MGMT_OP_REMOVE_REMOTE_OOB_DATA, - MGMT_OP_START_DISCOVERY, - MGMT_OP_STOP_DISCOVERY, - MGMT_OP_CONFIRM_NAME, - MGMT_OP_BLOCK_DEVICE, - MGMT_OP_UNBLOCK_DEVICE, -}; - -static const u16 mgmt_events[] = { - MGMT_EV_CONTROLLER_ERROR, - MGMT_EV_INDEX_ADDED, - MGMT_EV_INDEX_REMOVED, - MGMT_EV_NEW_SETTINGS, - MGMT_EV_CLASS_OF_DEV_CHANGED, - MGMT_EV_LOCAL_NAME_CHANGED, - MGMT_EV_NEW_LINK_KEY, - MGMT_EV_NEW_LONG_TERM_KEY, - MGMT_EV_DEVICE_CONNECTED, - MGMT_EV_DEVICE_DISCONNECTED, - MGMT_EV_CONNECT_FAILED, - MGMT_EV_PIN_CODE_REQUEST, - MGMT_EV_USER_CONFIRM_REQUEST, - MGMT_EV_USER_PASSKEY_REQUEST, - MGMT_EV_AUTH_FAILED, - MGMT_EV_DEVICE_FOUND, - MGMT_EV_DISCOVERING, - MGMT_EV_DEVICE_BLOCKED, - MGMT_EV_DEVICE_UNBLOCKED, - MGMT_EV_DEVICE_UNPAIRED, -}; - -/* - * These LE scan and inquiry parameters were chosen according to LE General - * Discovery Procedure specification. - */ -#define LE_SCAN_TYPE 0x01 -#define LE_SCAN_WIN 0x12 -#define LE_SCAN_INT 0x12 -#define LE_SCAN_TIMEOUT_LE_ONLY 10240 /* TGAP(gen_disc_scan_min) */ -#define LE_SCAN_TIMEOUT_BREDR_LE 5120 /* TGAP(100)/2 */ - -#define INQUIRY_LEN_BREDR 0x08 /* TGAP(100) */ -#define INQUIRY_LEN_BREDR_LE 0x04 /* TGAP(100)/2 */ - -#define CACHE_TIMEOUT msecs_to_jiffies(2 * 1000) +#define MGMT_VERSION 0 +#define MGMT_REVISION 1 -#define hdev_is_powered(hdev) (test_bit(HCI_UP, &hdev->flags) && \ - !test_bit(HCI_AUTO_OFF, &hdev->dev_flags)) +#define SCAN_IDLE 0x00 +#define SCAN_LE 0x01 +#define SCAN_BR 0x02 struct pending_cmd { struct list_head list; - u16 opcode; + __u16 opcode; int index; void *param; struct sock *sk; void *user_data; }; -/* HCI to MGMT error code conversion table */ -static u8 mgmt_status_table[] = { - MGMT_STATUS_SUCCESS, - MGMT_STATUS_UNKNOWN_COMMAND, /* Unknown Command */ - MGMT_STATUS_NOT_CONNECTED, /* No Connection */ - MGMT_STATUS_FAILED, /* Hardware Failure */ - MGMT_STATUS_CONNECT_FAILED, /* Page Timeout */ - MGMT_STATUS_AUTH_FAILED, /* Authentication Failed */ - MGMT_STATUS_NOT_PAIRED, /* PIN or Key Missing */ - MGMT_STATUS_NO_RESOURCES, /* Memory Full */ - MGMT_STATUS_TIMEOUT, /* Connection Timeout */ - MGMT_STATUS_NO_RESOURCES, /* Max Number of Connections */ - MGMT_STATUS_NO_RESOURCES, /* Max Number of SCO Connections */ - MGMT_STATUS_ALREADY_CONNECTED, /* ACL Connection Exists */ - MGMT_STATUS_BUSY, /* Command Disallowed */ - MGMT_STATUS_NO_RESOURCES, /* Rejected Limited Resources */ - MGMT_STATUS_REJECTED, /* Rejected Security */ - MGMT_STATUS_REJECTED, /* Rejected Personal */ - MGMT_STATUS_TIMEOUT, /* Host Timeout */ - MGMT_STATUS_NOT_SUPPORTED, /* Unsupported Feature */ - MGMT_STATUS_INVALID_PARAMS, /* Invalid Parameters */ - MGMT_STATUS_DISCONNECTED, /* OE User Ended Connection */ - MGMT_STATUS_NO_RESOURCES, /* OE Low Resources */ - MGMT_STATUS_DISCONNECTED, /* OE Power Off */ - MGMT_STATUS_DISCONNECTED, /* Connection Terminated */ - MGMT_STATUS_BUSY, /* Repeated Attempts */ - MGMT_STATUS_REJECTED, /* Pairing Not Allowed */ - MGMT_STATUS_FAILED, /* Unknown LMP PDU */ - MGMT_STATUS_NOT_SUPPORTED, /* Unsupported Remote Feature */ - MGMT_STATUS_REJECTED, /* SCO Offset Rejected */ - MGMT_STATUS_REJECTED, /* SCO Interval Rejected */ - MGMT_STATUS_REJECTED, /* Air Mode Rejected */ - MGMT_STATUS_INVALID_PARAMS, /* Invalid LMP Parameters */ - MGMT_STATUS_FAILED, /* Unspecified Error */ - MGMT_STATUS_NOT_SUPPORTED, /* Unsupported LMP Parameter Value */ - MGMT_STATUS_FAILED, /* Role Change Not Allowed */ - MGMT_STATUS_TIMEOUT, /* LMP Response Timeout */ - MGMT_STATUS_FAILED, /* LMP Error Transaction Collision */ - MGMT_STATUS_FAILED, /* LMP PDU Not Allowed */ - MGMT_STATUS_REJECTED, /* Encryption Mode Not Accepted */ - MGMT_STATUS_FAILED, /* Unit Link Key Used */ - MGMT_STATUS_NOT_SUPPORTED, /* QoS Not Supported */ - MGMT_STATUS_TIMEOUT, /* Instant Passed */ - MGMT_STATUS_NOT_SUPPORTED, /* Pairing Not Supported */ - MGMT_STATUS_FAILED, /* Transaction Collision */ - MGMT_STATUS_INVALID_PARAMS, /* Unacceptable Parameter */ - MGMT_STATUS_REJECTED, /* QoS Rejected */ - MGMT_STATUS_NOT_SUPPORTED, /* Classification Not Supported */ - MGMT_STATUS_REJECTED, /* Insufficient Security */ - MGMT_STATUS_INVALID_PARAMS, /* Parameter Out Of Range */ - MGMT_STATUS_BUSY, /* Role Switch Pending */ - MGMT_STATUS_FAILED, /* Slot Violation */ - MGMT_STATUS_FAILED, /* Role Switch Failed */ - MGMT_STATUS_INVALID_PARAMS, /* EIR Too Large */ - MGMT_STATUS_NOT_SUPPORTED, /* Simple Pairing Not Supported */ - MGMT_STATUS_BUSY, /* Host Busy Pairing */ - MGMT_STATUS_REJECTED, /* Rejected, No Suitable Channel */ - MGMT_STATUS_BUSY, /* Controller Busy */ - MGMT_STATUS_INVALID_PARAMS, /* Unsuitable Connection Interval */ - MGMT_STATUS_TIMEOUT, /* Directed Advertising Timeout */ - MGMT_STATUS_AUTH_FAILED, /* Terminated Due to MIC Failure */ - MGMT_STATUS_CONNECT_FAILED, /* Connection Establishment Failed */ - MGMT_STATUS_CONNECT_FAILED, /* MAC Connection Failed */ +struct mgmt_pending_free_work { + struct work_struct work; + struct sock *sk; }; -static u8 mgmt_status(u8 hci_status) -{ - if (hci_status < ARRAY_SIZE(mgmt_status_table)) - return mgmt_status_table[hci_status]; - - return MGMT_STATUS_FAILED; -} +LIST_HEAD(cmd_list); static int cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status) { struct sk_buff *skb; struct mgmt_hdr *hdr; struct mgmt_ev_cmd_status *ev; - int err; BT_DBG("sock %p, index %u, cmd %u, status %u", sk, index, cmd, status); @@ -226,20 +79,18 @@ static int cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status) ev->status = status; put_unaligned_le16(cmd, &ev->opcode); - err = sock_queue_rcv_skb(sk, skb); - if (err < 0) + if (sock_queue_rcv_skb(sk, skb) < 0) kfree_skb(skb); - return err; + return 0; } -static int cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status, - void *rp, size_t rp_len) +static int cmd_complete(struct sock *sk, u16 index, u16 cmd, void *rp, + size_t rp_len) { struct sk_buff *skb; struct mgmt_hdr *hdr; struct mgmt_ev_cmd_complete *ev; - int err; BT_DBG("sock %p", sk); @@ -255,20 +106,17 @@ static int cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status, ev = (void *) skb_put(skb, sizeof(*ev) + rp_len); put_unaligned_le16(cmd, &ev->opcode); - ev->status = status; if (rp) memcpy(ev->data, rp, rp_len); - err = sock_queue_rcv_skb(sk, skb); - if (err < 0) + if (sock_queue_rcv_skb(sk, skb) < 0) kfree_skb(skb); - return err; + return 0; } -static int read_version(struct sock *sk, struct hci_dev *hdev, void *data, - u16 data_len) +static int read_version(struct sock *sk) { struct mgmt_rp_read_version rp; @@ -277,50 +125,14 @@ static int read_version(struct sock *sk, struct hci_dev *hdev, void *data, rp.version = MGMT_VERSION; put_unaligned_le16(MGMT_REVISION, &rp.revision); - return cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_VERSION, 0, &rp, - sizeof(rp)); -} - -static int read_commands(struct sock *sk, struct hci_dev *hdev, void *data, - u16 data_len) -{ - struct mgmt_rp_read_commands *rp; - u16 num_commands = ARRAY_SIZE(mgmt_commands); - u16 num_events = ARRAY_SIZE(mgmt_events); - u16 *opcode; - size_t rp_size; - int i, err; - - BT_DBG("sock %p", sk); - - rp_size = sizeof(*rp) + ((num_commands + num_events) * sizeof(u16)); - - rp = kmalloc(rp_size, GFP_KERNEL); - if (!rp) - return -ENOMEM; - - put_unaligned_le16(num_commands, &rp->num_commands); - put_unaligned_le16(num_events, &rp->num_events); - - for (i = 0, opcode = rp->opcodes; i < num_commands; i++, opcode++) - put_unaligned_le16(mgmt_commands[i], opcode); - - for (i = 0; i < num_events; i++, opcode++) - put_unaligned_le16(mgmt_events[i], opcode); - - err = cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_COMMANDS, 0, rp, - rp_size); - kfree(rp); - - return err; + return cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_VERSION, &rp, + sizeof(rp)); } -static int read_index_list(struct sock *sk, struct hci_dev *hdev, void *data, - u16 data_len) +static int read_index_list(struct sock *sk) { struct mgmt_rp_read_index_list *rp; struct list_head *p; - struct hci_dev *d; size_t rp_len; u16 count; int i, err; @@ -331,6 +143,9 @@ static int read_index_list(struct sock *sk, struct hci_dev *hdev, void *data, count = 0; 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; count++; } @@ -341,231 +156,261 @@ static int read_index_list(struct sock *sk, struct hci_dev *hdev, void *data, return -ENOMEM; } - put_unaligned_le16(count, &rp->num_controllers); + put_unaligned_le16(0, &rp->num_controllers); i = 0; - list_for_each_entry(d, &hci_dev_list, list) { - if (test_bit(HCI_SETUP, &d->dev_flags)) + list_for_each(p, &hci_dev_list) { + struct hci_dev *d = list_entry(p, struct hci_dev, list); + + hci_del_off_timer(d); + + if (d->dev_type != HCI_BREDR) + continue; + + set_bit(HCI_MGMT, &d->flags); + + if (test_bit(HCI_SETUP, &d->flags)) continue; put_unaligned_le16(d->id, &rp->index[i++]); + put_unaligned_le16((u16)i, &rp->num_controllers); BT_DBG("Added hci%u", d->id); } read_unlock(&hci_dev_list_lock); - err = cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_INDEX_LIST, 0, rp, - rp_len); + err = cmd_complete(sk, MGMT_INDEX_NONE, MGMT_OP_READ_INDEX_LIST, rp, + rp_len); kfree(rp); return err; } -static u32 get_supported_settings(struct hci_dev *hdev) +static int read_controller_info(struct sock *sk, u16 index) { - u32 settings = 0; - - settings |= MGMT_SETTING_POWERED; - settings |= MGMT_SETTING_CONNECTABLE; - settings |= MGMT_SETTING_FAST_CONNECTABLE; - settings |= MGMT_SETTING_DISCOVERABLE; - settings |= MGMT_SETTING_PAIRABLE; + struct mgmt_rp_read_info rp; + struct hci_dev *hdev; - if (hdev->features[6] & LMP_SIMPLE_PAIR) - settings |= MGMT_SETTING_SSP; + BT_DBG("sock %p hci%u", sk, index); - if (!(hdev->features[4] & LMP_NO_BREDR)) { - settings |= MGMT_SETTING_BREDR; - settings |= MGMT_SETTING_LINK_SECURITY; - } + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_READ_INFO, ENODEV); - if (enable_hs) - settings |= MGMT_SETTING_HS; + hci_del_off_timer(hdev); - if (enable_le) { - if (hdev->features[4] & LMP_LE) - settings |= MGMT_SETTING_LE; - } + hci_dev_lock_bh(hdev); - return settings; -} + set_bit(HCI_MGMT, &hdev->flags); -static u32 get_current_settings(struct hci_dev *hdev) -{ - u32 settings = 0; + memset(&rp, 0, sizeof(rp)); - if (hdev_is_powered(hdev)) - settings |= MGMT_SETTING_POWERED; + rp.type = hdev->dev_type; - if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) - settings |= MGMT_SETTING_CONNECTABLE; + rp.powered = test_bit(HCI_UP, &hdev->flags); + rp.connectable = test_bit(HCI_PSCAN, &hdev->flags); + rp.discoverable = test_bit(HCI_ISCAN, &hdev->flags); + rp.pairable = test_bit(HCI_PSCAN, &hdev->flags); - if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) - settings |= MGMT_SETTING_DISCOVERABLE; + if (test_bit(HCI_AUTH, &hdev->flags)) + rp.sec_mode = 3; + else if (hdev->ssp_mode > 0) + rp.sec_mode = 4; + else + rp.sec_mode = 2; - if (test_bit(HCI_PAIRABLE, &hdev->dev_flags)) - settings |= MGMT_SETTING_PAIRABLE; + bacpy(&rp.bdaddr, &hdev->bdaddr); + memcpy(rp.features, hdev->features, 8); + memcpy(rp.dev_class, hdev->dev_class, 3); + put_unaligned_le16(hdev->manufacturer, &rp.manufacturer); + rp.hci_ver = hdev->hci_ver; + put_unaligned_le16(hdev->hci_rev, &rp.hci_rev); - if (!(hdev->features[4] & LMP_NO_BREDR)) - settings |= MGMT_SETTING_BREDR; + memcpy(rp.name, hdev->dev_name, sizeof(hdev->dev_name)); - if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) - settings |= MGMT_SETTING_LE; + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - if (test_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) - settings |= MGMT_SETTING_LINK_SECURITY; + return cmd_complete(sk, index, MGMT_OP_READ_INFO, &rp, sizeof(rp)); +} - if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) - settings |= MGMT_SETTING_SSP; +static void mgmt_pending_free_worker(struct work_struct *work) +{ + struct mgmt_pending_free_work *free_work = + container_of(work, struct mgmt_pending_free_work, work); - if (test_bit(HCI_HS_ENABLED, &hdev->dev_flags)) - settings |= MGMT_SETTING_HS; + BT_DBG("sk %p", free_work->sk); - return settings; + sock_put(free_work->sk); + kfree(free_work); } -#define PNP_INFO_SVCLASS_ID 0x1200 - -static u8 bluetooth_base_uuid[] = { - 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, - 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -static u16 get_uuid16(u8 *uuid128) +static void mgmt_pending_free(struct pending_cmd *cmd) { - u32 val; - int i; + struct mgmt_pending_free_work *free_work; + struct sock *sk = cmd->sk; - for (i = 0; i < 12; i++) { - if (bluetooth_base_uuid[i] != uuid128[i]) - return 0; - } + BT_DBG("opcode %d, sk %p", cmd->opcode, sk); - memcpy(&val, &uuid128[12], 4); + kfree(cmd->param); + kfree(cmd); - val = le32_to_cpu(val); - if (val > 0xffff) - return 0; + free_work = kzalloc(sizeof(*free_work), GFP_ATOMIC); + if (free_work) { + INIT_WORK(&free_work->work, mgmt_pending_free_worker); + free_work->sk = sk; - return (u16) val; + if (!schedule_work(&free_work->work)) + kfree(free_work); + } } -static void create_eir(struct hci_dev *hdev, u8 *data) +static struct pending_cmd *mgmt_pending_add(struct sock *sk, u16 opcode, + u16 index, void *data, u16 len) { - u8 *ptr = data; - u16 eir_len = 0; - u16 uuid16_list[HCI_MAX_EIR_LENGTH / sizeof(u16)]; - int i, truncated = 0; - struct bt_uuid *uuid; - size_t name_len; - - name_len = strlen(hdev->dev_name); + struct pending_cmd *cmd; - if (name_len > 0) { - /* EIR Data type */ - if (name_len > 48) { - name_len = 48; - ptr[1] = EIR_NAME_SHORT; - } else - ptr[1] = EIR_NAME_COMPLETE; + BT_DBG("%d", opcode); - /* EIR Data length */ - ptr[0] = name_len + 1; + cmd = kmalloc(sizeof(*cmd), GFP_ATOMIC); + if (!cmd) + return NULL; - memcpy(ptr + 2, hdev->dev_name, name_len); + cmd->opcode = opcode; + cmd->index = index; - eir_len += (name_len + 2); - ptr += (name_len + 2); + cmd->param = kmalloc(len, GFP_ATOMIC); + if (!cmd->param) { + kfree(cmd); + return NULL; } - memset(uuid16_list, 0, sizeof(uuid16_list)); + if (data) + memcpy(cmd->param, data, len); - /* Group all UUID16 types */ - list_for_each_entry(uuid, &hdev->uuids, list) { - u16 uuid16; + cmd->sk = sk; + sock_hold(sk); - uuid16 = get_uuid16(uuid->uuid); - if (uuid16 == 0) - return; + list_add(&cmd->list, &cmd_list); - if (uuid16 < 0x1100) + return cmd; +} + +static void mgmt_pending_foreach(u16 opcode, int index, + void (*cb)(struct pending_cmd *cmd, void *data), + void *data) +{ + struct list_head *p, *n; + + BT_DBG(" %d", opcode); + + list_for_each_safe(p, n, &cmd_list) { + struct pending_cmd *cmd; + + cmd = list_entry(p, struct pending_cmd, list); + + if (opcode > 0 && cmd->opcode != opcode) continue; - if (uuid16 == PNP_INFO_SVCLASS_ID) + if (index >= 0 && cmd->index != index) continue; - /* Stop if not enough space to put next UUID */ - if (eir_len + 2 + sizeof(u16) > HCI_MAX_EIR_LENGTH) { - truncated = 1; - break; - } + cb(cmd, data); + } +} - /* Check for duplicates */ - for (i = 0; uuid16_list[i] != 0; i++) - if (uuid16_list[i] == uuid16) - break; +static struct pending_cmd *mgmt_pending_find(u16 opcode, int index) +{ + struct list_head *p; - if (uuid16_list[i] == 0) { - uuid16_list[i] = uuid16; - eir_len += sizeof(u16); - } - } + BT_DBG(" %d", opcode); - if (uuid16_list[0] != 0) { - u8 *length = ptr; + list_for_each(p, &cmd_list) { + struct pending_cmd *cmd; - /* EIR Data type */ - ptr[1] = truncated ? EIR_UUID16_SOME : EIR_UUID16_ALL; + cmd = list_entry(p, struct pending_cmd, list); - ptr += 2; - eir_len += 2; + if (cmd->opcode != opcode) + continue; - for (i = 0; uuid16_list[i] != 0; i++) { - *ptr++ = (uuid16_list[i] & 0x00ff); - *ptr++ = (uuid16_list[i] & 0xff00) >> 8; - } + if (index >= 0 && cmd->index != index) + continue; - /* EIR Data length */ - *length = (i * sizeof(u16)) + 1; + return cmd; } + + return NULL; } -static int update_eir(struct hci_dev *hdev) +static void mgmt_pending_remove(struct pending_cmd *cmd) { - struct hci_cp_write_eir cp; + BT_DBG(" %d", cmd->opcode); - if (!hdev_is_powered(hdev)) - return 0; + list_del(&cmd->list); + mgmt_pending_free(cmd); +} - if (!(hdev->features[6] & LMP_EXT_INQ)) - return 0; +static int set_powered(struct sock *sk, u16 index, unsigned char *data, u16 len) +{ + struct mgmt_mode *cp; + struct hci_dev *hdev; + struct pending_cmd *cmd; + int err, up; - if (!test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) - return 0; + cp = (void *) data; - if (test_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) - return 0; + BT_DBG("request for hci%u", index); - memset(&cp, 0, sizeof(cp)); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_POWERED, EINVAL); - create_eir(hdev, cp.data); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_POWERED, ENODEV); - if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0) - return 0; + hci_dev_lock_bh(hdev); - memcpy(hdev->eir, cp.data, sizeof(cp.data)); + up = test_bit(HCI_UP, &hdev->flags); + if ((cp->val && up) || (!cp->val && !up)) { + err = cmd_status(sk, index, MGMT_OP_SET_POWERED, EALREADY); + goto failed; + } - return hci_send_cmd(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp); + if (mgmt_pending_find(MGMT_OP_SET_POWERED, index)) { + err = cmd_status(sk, index, MGMT_OP_SET_POWERED, EBUSY); + goto failed; + } + + cmd = mgmt_pending_add(sk, MGMT_OP_SET_POWERED, index, data, len); + if (!cmd) { + err = -ENOMEM; + goto failed; + } + + if (cp->val) + queue_work(hdev->workqueue, &hdev->power_on); + else + queue_work(hdev->workqueue, &hdev->power_off); + + err = 0; + +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } static u8 get_service_classes(struct hci_dev *hdev) { - struct bt_uuid *uuid; + struct list_head *p; u8 val = 0; - list_for_each_entry(uuid, &hdev->uuids, list) + list_for_each(p, &hdev->uuids) { + struct bt_uuid *uuid = list_entry(p, struct bt_uuid, list); + val |= uuid->svc_hint; + } return val; } @@ -573,14 +418,10 @@ static u8 get_service_classes(struct hci_dev *hdev) static int update_class(struct hci_dev *hdev) { u8 cod[3]; - int err; BT_DBG("%s", hdev->name); - if (!hdev_is_powered(hdev)) - return 0; - - if (test_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) + if (test_bit(HCI_SERVICE_CACHE, &hdev->flags)) return 0; cod[0] = hdev->minor_class; @@ -590,317 +431,134 @@ static int update_class(struct hci_dev *hdev) if (memcmp(cod, hdev->dev_class, 3) == 0) return 0; - err = hci_send_cmd(hdev, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); - if (err == 0) - set_bit(HCI_PENDING_CLASS, &hdev->dev_flags); - - return err; + return hci_send_cmd(hdev, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); } -static void service_cache_off(struct work_struct *work) +static int set_limited_discoverable(struct sock *sk, u16 index, + unsigned char *data, u16 len) { - struct hci_dev *hdev = container_of(work, struct hci_dev, - service_cache.work); + struct mgmt_mode *cp; + struct hci_dev *hdev; + struct pending_cmd *cmd; + struct hci_cp_write_current_iac_lap dcp; + int update_cod; + int err = 0; + /* General Inquiry LAP: 0x9E8B33, Limited Inquiry LAP: 0x9E8B00 */ + u8 lap[] = { 0x33, 0x8b, 0x9e, 0x00, 0x8b, 0x9e }; - if (!test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) - return; + cp = (void *) data; - hci_dev_lock(hdev); + BT_DBG("hci%u discoverable: %d", index, cp->val); - update_eir(hdev); - update_class(hdev); + if (!cp || len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_LIMIT_DISCOVERABLE, + EINVAL); - hci_dev_unlock(hdev); -} + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_LIMIT_DISCOVERABLE, + ENODEV); -static void mgmt_init_hdev(struct sock *sk, struct hci_dev *hdev) -{ - if (test_and_set_bit(HCI_MGMT, &hdev->dev_flags)) - return; + hci_dev_lock_bh(hdev); - INIT_DELAYED_WORK(&hdev->service_cache, service_cache_off); + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_SET_LIMIT_DISCOVERABLE, + ENETDOWN); + goto failed; + } - /* Non-mgmt controlled devices get this bit set - * implicitly so that pairing works for them, however - * for mgmt we require user-space to explicitly enable - * it - */ - clear_bit(HCI_PAIRABLE, &hdev->dev_flags); -} + if (mgmt_pending_find(MGMT_OP_SET_LIMIT_DISCOVERABLE, index)) { + err = cmd_status(sk, index, MGMT_OP_SET_LIMIT_DISCOVERABLE, + EBUSY); + goto failed; + } -static int read_controller_info(struct sock *sk, struct hci_dev *hdev, - void *data, u16 data_len) -{ - struct mgmt_rp_read_info rp; + if (cp->val == test_bit(HCI_ISCAN, &hdev->flags) && + test_bit(HCI_PSCAN, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_SET_LIMIT_DISCOVERABLE, + EALREADY); + goto failed; + } - BT_DBG("sock %p %s", sk, hdev->name); + cmd = mgmt_pending_add(sk, MGMT_OP_SET_LIMIT_DISCOVERABLE, index, data, + len); + if (!cmd) { + err = -ENOMEM; + goto failed; + } - hci_dev_lock(hdev); + memset(&dcp, 0, sizeof(dcp)); + dcp.num_current_iac = cp->val ? 2 : 1; + memcpy(&dcp.lap, lap, dcp.num_current_iac * 3); + update_cod = 1; - memset(&rp, 0, sizeof(rp)); + if (cp->val) { + if (hdev->major_class & MGMT_MAJOR_CLASS_LIMITED) + update_cod = 0; + hdev->major_class |= MGMT_MAJOR_CLASS_LIMITED; + } else { + if (!(hdev->major_class & MGMT_MAJOR_CLASS_LIMITED)) + update_cod = 0; + hdev->major_class &= ~MGMT_MAJOR_CLASS_LIMITED; + } - bacpy(&rp.bdaddr, &hdev->bdaddr); + if (update_cod) + err = update_class(hdev); - rp.version = hdev->hci_ver; + if (err >= 0) + err = hci_send_cmd(hdev, HCI_OP_WRITE_CURRENT_IAC_LAP, + sizeof(dcp), &dcp); - put_unaligned_le16(hdev->manufacturer, &rp.manufacturer); + if (err < 0) + mgmt_pending_remove(cmd); - rp.supported_settings = cpu_to_le32(get_supported_settings(hdev)); - rp.current_settings = cpu_to_le32(get_current_settings(hdev)); +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - memcpy(rp.dev_class, hdev->dev_class, 3); + return err; +} - memcpy(rp.name, hdev->dev_name, sizeof(hdev->dev_name)); - memcpy(rp.short_name, hdev->short_name, sizeof(hdev->short_name)); - - hci_dev_unlock(hdev); - - return cmd_complete(sk, hdev->id, MGMT_OP_READ_INFO, 0, &rp, - sizeof(rp)); -} - -static void mgmt_pending_free(struct pending_cmd *cmd) -{ - sock_put(cmd->sk); - kfree(cmd->param); - kfree(cmd); -} - -static struct pending_cmd *mgmt_pending_add(struct sock *sk, u16 opcode, - struct hci_dev *hdev, void *data, - u16 len) -{ - struct pending_cmd *cmd; - - cmd = kmalloc(sizeof(*cmd), GFP_ATOMIC); - if (!cmd) - return NULL; - - cmd->opcode = opcode; - cmd->index = hdev->id; - - cmd->param = kmalloc(len, GFP_ATOMIC); - if (!cmd->param) { - kfree(cmd); - return NULL; - } - - if (data) - memcpy(cmd->param, data, len); - - cmd->sk = sk; - sock_hold(sk); - - list_add(&cmd->list, &hdev->mgmt_pending); - - return cmd; -} - -static void mgmt_pending_foreach(u16 opcode, struct hci_dev *hdev, - void (*cb)(struct pending_cmd *cmd, void *data), - void *data) -{ - struct list_head *p, *n; - - list_for_each_safe(p, n, &hdev->mgmt_pending) { - struct pending_cmd *cmd; - - cmd = list_entry(p, struct pending_cmd, list); - - if (opcode > 0 && cmd->opcode != opcode) - continue; - - cb(cmd, data); - } -} - -static struct pending_cmd *mgmt_pending_find(u16 opcode, struct hci_dev *hdev) -{ - struct pending_cmd *cmd; - - list_for_each_entry(cmd, &hdev->mgmt_pending, list) { - if (cmd->opcode == opcode) - return cmd; - } - - return NULL; -} - -static void mgmt_pending_remove(struct pending_cmd *cmd) -{ - list_del(&cmd->list); - mgmt_pending_free(cmd); -} - -static int send_settings_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev) +static int set_discoverable(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - __le32 settings = cpu_to_le32(get_current_settings(hdev)); - - return cmd_complete(sk, hdev->id, opcode, 0, &settings, - sizeof(settings)); -} - -static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) -{ - struct mgmt_mode *cp = data; - struct pending_cmd *cmd; - int err; - - BT_DBG("request for %s", hdev->name); - - hci_dev_lock(hdev); - - if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) { - cancel_delayed_work(&hdev->power_off); - - if (cp->val) { - err = send_settings_rsp(sk, MGMT_OP_SET_POWERED, hdev); - mgmt_powered(hdev, 1); - goto failed; - } - } - - if (!!cp->val == hdev_is_powered(hdev)) { - err = send_settings_rsp(sk, MGMT_OP_SET_POWERED, hdev); - goto failed; - } - - if (mgmt_pending_find(MGMT_OP_SET_POWERED, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED, - MGMT_STATUS_BUSY); - goto failed; - } - - cmd = mgmt_pending_add(sk, MGMT_OP_SET_POWERED, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto failed; - } - - if (cp->val) - schedule_work(&hdev->power_on); - else - schedule_work(&hdev->power_off.work); - - err = 0; - -failed: - hci_dev_unlock(hdev); - return err; -} - -static int mgmt_event(u16 event, struct hci_dev *hdev, void *data, u16 data_len, - struct sock *skip_sk) -{ - struct sk_buff *skb; - struct mgmt_hdr *hdr; - - skb = alloc_skb(sizeof(*hdr) + data_len, GFP_ATOMIC); - if (!skb) - return -ENOMEM; - - hdr = (void *) skb_put(skb, sizeof(*hdr)); - hdr->opcode = cpu_to_le16(event); - if (hdev) - hdr->index = cpu_to_le16(hdev->id); - else - hdr->index = cpu_to_le16(MGMT_INDEX_NONE); - hdr->len = cpu_to_le16(data_len); - - if (data) - memcpy(skb_put(skb, data_len), data, data_len); - - /* Time stamp */ - __net_timestamp(skb); - - hci_send_to_control(skb, skip_sk); - kfree_skb(skb); - - return 0; -} - -static int new_settings(struct hci_dev *hdev, struct sock *skip) -{ - __le32 ev; - - ev = cpu_to_le32(get_current_settings(hdev)); - - return mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), skip); -} - -static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) -{ - struct mgmt_cp_set_discoverable *cp = data; + struct mgmt_mode *cp; + struct hci_dev *hdev; struct pending_cmd *cmd; - u16 timeout; u8 scan; int err; - BT_DBG("request for %s", hdev->name); + cp = (void *) data; - timeout = get_unaligned_le16(&cp->timeout); - if (!cp->val && timeout > 0) - return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_INVALID_PARAMS); + BT_DBG("request for hci%u", index); - hci_dev_lock(hdev); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, EINVAL); - if (!hdev_is_powered(hdev) && timeout > 0) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_NOT_POWERED); - goto failed; - } + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, ENODEV); - if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev) || - mgmt_pending_find(MGMT_OP_SET_CONNECTABLE, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_BUSY); - goto failed; - } + hci_dev_lock_bh(hdev); - if (!test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, - MGMT_STATUS_REJECTED); + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, ENETDOWN); goto failed; } - if (!hdev_is_powered(hdev)) { - bool changed = false; - - if (!!cp->val != test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) { - change_bit(HCI_DISCOVERABLE, &hdev->dev_flags); - changed = true; - } - - err = send_settings_rsp(sk, MGMT_OP_SET_DISCOVERABLE, hdev); - if (err < 0) - goto failed; - - if (changed) - err = new_settings(hdev, sk); - + if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, index) || + mgmt_pending_find(MGMT_OP_SET_CONNECTABLE, index)) { + err = cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, EBUSY); goto failed; } - if (!!cp->val == test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) { - if (hdev->discov_timeout > 0) { - cancel_delayed_work(&hdev->discov_off); - hdev->discov_timeout = 0; - } - - if (cp->val && timeout > 0) { - hdev->discov_timeout = timeout; - queue_delayed_work(hdev->workqueue, &hdev->discov_off, - msecs_to_jiffies(hdev->discov_timeout * 1000)); - } - - err = send_settings_rsp(sk, MGMT_OP_SET_DISCOVERABLE, hdev); + if (cp->val == test_bit(HCI_ISCAN, &hdev->flags) && + test_bit(HCI_PSCAN, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, EALREADY); goto failed; } - cmd = mgmt_pending_add(sk, MGMT_OP_SET_DISCOVERABLE, hdev, data, len); + cmd = mgmt_pending_add(sk, MGMT_OP_SET_DISCOVERABLE, index, data, len); if (!cmd) { err = -ENOMEM; goto failed; @@ -910,349 +568,318 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, if (cp->val) scan |= SCAN_INQUIRY; - else - cancel_delayed_work(&hdev->discov_off); err = hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); if (err < 0) mgmt_pending_remove(cmd); - if (cp->val) - hdev->discov_timeout = timeout; - failed: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int set_connectable(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_mode *cp = data; + struct mgmt_mode *cp; + struct hci_dev *hdev; struct pending_cmd *cmd; u8 scan; int err; - BT_DBG("request for %s", hdev->name); - - hci_dev_lock(hdev); - - if (!hdev_is_powered(hdev)) { - bool changed = false; + cp = (void *) data; - if (!!cp->val != test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) - changed = true; + BT_DBG("request for hci%u", index); - if (cp->val) { - set_bit(HCI_CONNECTABLE, &hdev->dev_flags); - } else { - clear_bit(HCI_CONNECTABLE, &hdev->dev_flags); - clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags); - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, EINVAL); - err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev); - if (err < 0) - goto failed; + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, ENODEV); - if (changed) - err = new_settings(hdev, sk); + hci_dev_lock_bh(hdev); + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, ENETDOWN); goto failed; } - if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev) || - mgmt_pending_find(MGMT_OP_SET_CONNECTABLE, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE, - MGMT_STATUS_BUSY); + if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, index) || + mgmt_pending_find(MGMT_OP_SET_CONNECTABLE, index)) { + err = cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, EBUSY); goto failed; } - if (!!cp->val == test_bit(HCI_PSCAN, &hdev->flags)) { - err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev); + if (cp->val == test_bit(HCI_PSCAN, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, EALREADY); goto failed; } - cmd = mgmt_pending_add(sk, MGMT_OP_SET_CONNECTABLE, hdev, data, len); + cmd = mgmt_pending_add(sk, MGMT_OP_SET_CONNECTABLE, index, data, len); if (!cmd) { err = -ENOMEM; goto failed; } - if (cp->val) { + if (cp->val) scan = SCAN_PAGE; - } else { + else scan = 0; - if (test_bit(HCI_ISCAN, &hdev->flags) && - hdev->discov_timeout > 0) - cancel_delayed_work(&hdev->discov_off); - } - err = hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); if (err < 0) mgmt_pending_remove(cmd); failed: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int set_pairable(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int mgmt_event(u16 event, u16 index, void *data, u16 data_len, + struct sock *skip_sk) { - struct mgmt_mode *cp = data; - int err; + struct sk_buff *skb; + struct mgmt_hdr *hdr; + + BT_DBG("hci%d %d", index, event); - BT_DBG("request for %s", hdev->name); + skb = alloc_skb(sizeof(*hdr) + data_len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; - hci_dev_lock(hdev); + bt_cb(skb)->channel = HCI_CHANNEL_CONTROL; - if (cp->val) - set_bit(HCI_PAIRABLE, &hdev->dev_flags); - else - clear_bit(HCI_PAIRABLE, &hdev->dev_flags); + hdr = (void *) skb_put(skb, sizeof(*hdr)); + hdr->opcode = cpu_to_le16(event); + hdr->index = cpu_to_le16(index); + hdr->len = cpu_to_le16(data_len); - err = send_settings_rsp(sk, MGMT_OP_SET_PAIRABLE, hdev); - if (err < 0) - goto failed; + if (data) + memcpy(skb_put(skb, data_len), data, data_len); - err = new_settings(hdev, sk); + hci_send_to_sock(NULL, skb, skip_sk); + kfree_skb(skb); -failed: - hci_dev_unlock(hdev); - return err; + return 0; } -static int set_link_security(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int send_mode_rsp(struct sock *sk, u16 opcode, u16 index, u8 val) { - struct mgmt_mode *cp = data; - struct pending_cmd *cmd; - u8 val; - int err; + struct mgmt_mode rp; - BT_DBG("request for %s", hdev->name); + rp.val = val; - hci_dev_lock(hdev); + return cmd_complete(sk, index, opcode, &rp, sizeof(rp)); +} - if (!hdev_is_powered(hdev)) { - bool changed = false; +static int set_pairable(struct sock *sk, u16 index, unsigned char *data, + u16 len) +{ + struct mgmt_mode *cp, ev; + struct hci_dev *hdev; + int err; - if (!!cp->val != test_bit(HCI_LINK_SECURITY, - &hdev->dev_flags)) { - change_bit(HCI_LINK_SECURITY, &hdev->dev_flags); - changed = true; - } + cp = (void *) data; - err = send_settings_rsp(sk, MGMT_OP_SET_LINK_SECURITY, hdev); - if (err < 0) - goto failed; + BT_DBG("request for hci%u", index); - if (changed) - err = new_settings(hdev, sk); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_PAIRABLE, EINVAL); - goto failed; - } + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_PAIRABLE, ENODEV); - if (mgmt_pending_find(MGMT_OP_SET_LINK_SECURITY, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY, - MGMT_STATUS_BUSY); - goto failed; - } + hci_dev_lock_bh(hdev); - val = !!cp->val; + if (cp->val) + set_bit(HCI_PAIRABLE, &hdev->flags); + else + clear_bit(HCI_PAIRABLE, &hdev->flags); - if (test_bit(HCI_AUTH, &hdev->flags) == val) { - err = send_settings_rsp(sk, MGMT_OP_SET_LINK_SECURITY, hdev); + err = send_mode_rsp(sk, MGMT_OP_SET_PAIRABLE, index, cp->val); + if (err < 0) goto failed; - } - cmd = mgmt_pending_add(sk, MGMT_OP_SET_LINK_SECURITY, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto failed; - } + ev.val = cp->val; - err = hci_send_cmd(hdev, HCI_OP_WRITE_AUTH_ENABLE, sizeof(val), &val); - if (err < 0) { - mgmt_pending_remove(cmd); - goto failed; - } + err = mgmt_event(MGMT_EV_PAIRABLE, index, &ev, sizeof(ev), sk); failed: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int set_ssp(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) -{ - struct mgmt_mode *cp = data; - struct pending_cmd *cmd; - u8 val; - int err; +#define EIR_FLAGS 0x01 /* flags */ +#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define EIR_NAME_SHORT 0x08 /* shortened local name */ +#define EIR_NAME_COMPLETE 0x09 /* complete local name */ +#define EIR_TX_POWER 0x0A /* transmit power level */ +#define EIR_DEVICE_ID 0x10 /* device ID */ - BT_DBG("request for %s", hdev->name); +#define PNP_INFO_SVCLASS_ID 0x1200 - hci_dev_lock(hdev); +static u8 bluetooth_base_uuid[] = { + 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; - if (!(hdev->features[6] & LMP_SIMPLE_PAIR)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_SSP, - MGMT_STATUS_NOT_SUPPORTED); - goto failed; - } +static u16 get_uuid16(u8 *uuid128) +{ + u32 val; + int i; - val = !!cp->val; + for (i = 0; i < 12; i++) { + if (bluetooth_base_uuid[i] != uuid128[i]) + return 0; + } - if (!hdev_is_powered(hdev)) { - bool changed = false; + memcpy(&val, &uuid128[12], 4); - if (val != test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) { - change_bit(HCI_SSP_ENABLED, &hdev->dev_flags); - changed = true; - } + val = le32_to_cpu(val); + if (val > 0xffff) + return 0; - err = send_settings_rsp(sk, MGMT_OP_SET_SSP, hdev); - if (err < 0) - goto failed; + return (u16) val; +} - if (changed) - err = new_settings(hdev, sk); +static void create_eir(struct hci_dev *hdev, u8 *data) +{ + u8 *ptr = data; + u16 eir_len = 0; + u16 uuid16_list[HCI_MAX_EIR_LENGTH / sizeof(u16)]; + int i, truncated = 0; + struct list_head *p; + size_t name_len; - goto failed; - } + name_len = strnlen(hdev->dev_name, HCI_MAX_EIR_LENGTH); - if (mgmt_pending_find(MGMT_OP_SET_SSP, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_SSP, - MGMT_STATUS_BUSY); - goto failed; - } + if (name_len > 0) { + /* EIR Data type */ + if (name_len > 48) { + name_len = 48; + ptr[1] = EIR_NAME_SHORT; + } else + ptr[1] = EIR_NAME_COMPLETE; - if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags) == val) { - err = send_settings_rsp(sk, MGMT_OP_SET_SSP, hdev); - goto failed; - } + /* EIR Data length */ + ptr[0] = name_len + 1; - cmd = mgmt_pending_add(sk, MGMT_OP_SET_SSP, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto failed; - } + memcpy(ptr + 2, hdev->dev_name, name_len); - err = hci_send_cmd(hdev, HCI_OP_WRITE_SSP_MODE, sizeof(val), &val); - if (err < 0) { - mgmt_pending_remove(cmd); - goto failed; + eir_len += (name_len + 2); + ptr += (name_len + 2); } -failed: - hci_dev_unlock(hdev); - return err; -} - -static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) -{ - struct mgmt_mode *cp = data; - - BT_DBG("request for %s", hdev->name); + memset(uuid16_list, 0, sizeof(uuid16_list)); - if (!enable_hs) - return cmd_status(sk, hdev->id, MGMT_OP_SET_HS, - MGMT_STATUS_NOT_SUPPORTED); + /* Group all UUID16 types */ + list_for_each(p, &hdev->uuids) { + struct bt_uuid *uuid = list_entry(p, struct bt_uuid, list); + u16 uuid16; - if (cp->val) - set_bit(HCI_HS_ENABLED, &hdev->dev_flags); - else - clear_bit(HCI_HS_ENABLED, &hdev->dev_flags); + uuid16 = get_uuid16(uuid->uuid); + if (uuid16 == 0) + return; - return send_settings_rsp(sk, MGMT_OP_SET_HS, hdev); -} + if (uuid16 < 0x1100) + continue; -static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) -{ - struct mgmt_mode *cp = data; - struct hci_cp_write_le_host_supported hci_cp; - struct pending_cmd *cmd; - int err; - u8 val, enabled; + if (uuid16 == PNP_INFO_SVCLASS_ID) + continue; - BT_DBG("request for %s", hdev->name); + /* Stop if not enough space to put next UUID */ + if (eir_len + 2 + sizeof(u16) > HCI_MAX_EIR_LENGTH) { + truncated = 1; + break; + } - hci_dev_lock(hdev); + /* Check for duplicates */ + for (i = 0; uuid16_list[i] != 0; i++) + if (uuid16_list[i] == uuid16) + break; - if (!enable_le || !(hdev->features[4] & LMP_LE)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_LE, - MGMT_STATUS_NOT_SUPPORTED); - goto unlock; + if (uuid16_list[i] == 0) { + uuid16_list[i] = uuid16; + eir_len += sizeof(u16); + } } - val = !!cp->val; - enabled = !!(hdev->host_features[0] & LMP_HOST_LE); + if (uuid16_list[0] != 0) { + u8 *length = ptr; + + /* EIR Data type */ + ptr[1] = truncated ? EIR_UUID16_SOME : EIR_UUID16_ALL; - if (!hdev_is_powered(hdev) || val == enabled) { - bool changed = false; + ptr += 2; + eir_len += 2; - if (val != test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) { - change_bit(HCI_LE_ENABLED, &hdev->dev_flags); - changed = true; + for (i = 0; uuid16_list[i] != 0; i++) { + *ptr++ = (uuid16_list[i] & 0x00ff); + *ptr++ = (uuid16_list[i] & 0xff00) >> 8; } - err = send_settings_rsp(sk, MGMT_OP_SET_LE, hdev); - if (err < 0) - goto unlock; + /* EIR Data length */ + *length = (i * sizeof(u16)) + 1; + } +} - if (changed) - err = new_settings(hdev, sk); +static int update_eir(struct hci_dev *hdev) +{ + struct hci_cp_write_eir cp; - goto unlock; - } + if (!(hdev->features[6] & LMP_EXT_INQ)) + return 0; - if (mgmt_pending_find(MGMT_OP_SET_LE, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_LE, - MGMT_STATUS_BUSY); - goto unlock; - } + if (hdev->ssp_mode == 0) + return 0; - cmd = mgmt_pending_add(sk, MGMT_OP_SET_LE, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto unlock; - } + if (test_bit(HCI_SERVICE_CACHE, &hdev->flags)) + return 0; - memset(&hci_cp, 0, sizeof(hci_cp)); + memset(&cp, 0, sizeof(cp)); - if (val) { - hci_cp.le = val; - hci_cp.simul = !!(hdev->features[6] & LMP_SIMUL_LE_BR); - } + create_eir(hdev, cp.data); - err = hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp), - &hci_cp); - if (err < 0) { - mgmt_pending_remove(cmd); - goto unlock; - } + if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0) + return 0; -unlock: - hci_dev_unlock(hdev); - return err; + memcpy(hdev->eir, cp.data, sizeof(cp.data)); + + return hci_send_cmd(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp); } -static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) +static int add_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) { - struct mgmt_cp_add_uuid *cp = data; - struct pending_cmd *cmd; + struct mgmt_cp_add_uuid *cp; + struct hci_dev *hdev; struct bt_uuid *uuid; int err; - BT_DBG("request for %s", hdev->name); + cp = (void *) data; - hci_dev_lock(hdev); + BT_DBG("request for hci%u", index); - if (test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { - err = cmd_status(sk, hdev->id, MGMT_OP_ADD_UUID, - MGMT_STATUS_BUSY); - goto failed; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_ADD_UUID, EINVAL); + + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_ADD_UUID, ENODEV); + + hci_dev_lock_bh(hdev); uuid = kmalloc(sizeof(*uuid), GFP_ATOMIC); if (!uuid) { @@ -1265,73 +892,51 @@ static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) list_add(&uuid->list, &hdev->uuids); - err = update_class(hdev); - if (err < 0) - goto failed; + if (test_bit(HCI_UP, &hdev->flags)) { - err = update_eir(hdev); - if (err < 0) - goto failed; + err = update_class(hdev); + if (err < 0) + goto failed; - if (!test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_ADD_UUID, 0, - hdev->dev_class, 3); - goto failed; - } + err = update_eir(hdev); + if (err < 0) + goto failed; + } else + err = 0; - cmd = mgmt_pending_add(sk, MGMT_OP_ADD_UUID, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto failed; - } + err = cmd_complete(sk, index, MGMT_OP_ADD_UUID, NULL, 0); failed: - hci_dev_unlock(hdev); - return err; -} + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); -static bool enable_service_cache(struct hci_dev *hdev) -{ - if (!hdev_is_powered(hdev)) - return false; - - if (!test_and_set_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) { - schedule_delayed_work(&hdev->service_cache, CACHE_TIMEOUT); - return true; - } - - return false; + return err; } -static int remove_uuid(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int remove_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) { - struct mgmt_cp_remove_uuid *cp = data; - struct pending_cmd *cmd; struct list_head *p, *n; + struct mgmt_cp_remove_uuid *cp; + struct hci_dev *hdev; u8 bt_uuid_any[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int err, found; - BT_DBG("request for %s", hdev->name); + cp = (void *) data; - hci_dev_lock(hdev); + BT_DBG("request for hci%u", index); - if (test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { - err = cmd_status(sk, hdev->id, MGMT_OP_REMOVE_UUID, - MGMT_STATUS_BUSY); - goto unlock; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_REMOVE_UUID, EINVAL); - if (memcmp(cp->uuid, bt_uuid_any, 16) == 0) { - err = hci_uuids_clear(hdev); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_REMOVE_UUID, ENODEV); - if (enable_service_cache(hdev)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_UUID, - 0, hdev->dev_class, 3); - goto unlock; - } + hci_dev_lock_bh(hdev); - goto update_class; + if (memcmp(cp->uuid, bt_uuid_any, 16) == 0) { + err = hci_uuids_clear(hdev); + goto unlock; } found = 0; @@ -1347,480 +952,544 @@ static int remove_uuid(struct sock *sk, struct hci_dev *hdev, void *data, } if (found == 0) { - err = cmd_status(sk, hdev->id, MGMT_OP_REMOVE_UUID, - MGMT_STATUS_INVALID_PARAMS); + err = cmd_status(sk, index, MGMT_OP_REMOVE_UUID, ENOENT); goto unlock; } -update_class: - err = update_class(hdev); - if (err < 0) - goto unlock; - - err = update_eir(hdev); - if (err < 0) - goto unlock; + if (test_bit(HCI_UP, &hdev->flags)) { + err = update_class(hdev); + if (err < 0) + goto unlock; - if (!test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_UUID, 0, - hdev->dev_class, 3); - goto unlock; - } + err = update_eir(hdev); + if (err < 0) + goto unlock; + } else + err = 0; - cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_UUID, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto unlock; - } + err = cmd_complete(sk, index, MGMT_OP_REMOVE_UUID, NULL, 0); unlock: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int set_dev_class(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int set_dev_class(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_cp_set_dev_class *cp = data; - struct pending_cmd *cmd; + struct hci_dev *hdev; + struct mgmt_cp_set_dev_class *cp; int err; - BT_DBG("request for %s", hdev->name); + cp = (void *) data; - hci_dev_lock(hdev); + BT_DBG("request for hci%u", index); - if (test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_DEV_CLASS, - MGMT_STATUS_BUSY); - goto unlock; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_DEV_CLASS, EINVAL); - hdev->major_class = cp->major; + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_DEV_CLASS, ENODEV); + + hci_dev_lock_bh(hdev); + + hdev->major_class &= ~MGMT_MAJOR_CLASS_MASK; + hdev->major_class |= cp->major & MGMT_MAJOR_CLASS_MASK; hdev->minor_class = cp->minor; - if (!hdev_is_powered(hdev)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_SET_DEV_CLASS, 0, - hdev->dev_class, 3); - goto unlock; - } + if (test_bit(HCI_UP, &hdev->flags)) + err = update_class(hdev); + else + err = 0; - if (test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) { - hci_dev_unlock(hdev); - cancel_delayed_work_sync(&hdev->service_cache); - hci_dev_lock(hdev); - update_eir(hdev); - } + if (err == 0) + err = cmd_complete(sk, index, MGMT_OP_SET_DEV_CLASS, NULL, 0); - err = update_class(hdev); - if (err < 0) - goto unlock; + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - if (!test_bit(HCI_PENDING_CLASS, &hdev->dev_flags)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_SET_DEV_CLASS, 0, - hdev->dev_class, 3); - goto unlock; - } + return err; +} - cmd = mgmt_pending_add(sk, MGMT_OP_SET_DEV_CLASS, hdev, data, len); - if (!cmd) { - err = -ENOMEM; - goto unlock; +static int set_service_cache(struct sock *sk, u16 index, unsigned char *data, + u16 len) +{ + struct hci_dev *hdev; + struct mgmt_cp_set_service_cache *cp; + int err; + + cp = (void *) data; + + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_SERVICE_CACHE, EINVAL); + + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_SERVICE_CACHE, ENODEV); + + hci_dev_lock_bh(hdev); + + BT_DBG("hci%u enable %d", index, cp->enable); + + if (cp->enable) { + set_bit(HCI_SERVICE_CACHE, &hdev->flags); + err = 0; + } else { + clear_bit(HCI_SERVICE_CACHE, &hdev->flags); + if (test_bit(HCI_UP, &hdev->flags)) { + err = update_class(hdev); + if (err == 0) + err = update_eir(hdev); + } else + err = 0; } -unlock: - hci_dev_unlock(hdev); + if (err == 0) + err = cmd_complete(sk, index, MGMT_OP_SET_SERVICE_CACHE, NULL, + 0); + + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int load_link_keys(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int load_keys(struct sock *sk, u16 index, unsigned char *data, u16 len) { - struct mgmt_cp_load_link_keys *cp = data; + struct hci_dev *hdev; + struct mgmt_cp_load_keys *cp; u16 key_count, expected_len; - int i; + int i, err; + + cp = (void *) data; + + if (len < sizeof(*cp)) + return -EINVAL; key_count = get_unaligned_le16(&cp->key_count); - expected_len = sizeof(*cp) + key_count * - sizeof(struct mgmt_link_key_info); - if (expected_len != len) { - BT_ERR("load_link_keys: expected %u bytes, got %u bytes", - len, expected_len); - return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS, - MGMT_STATUS_INVALID_PARAMS); + expected_len = sizeof(*cp) + key_count * sizeof(struct mgmt_key_info); + if (expected_len > len) { + BT_ERR("load_keys: expected at least %u bytes, got %u bytes", + expected_len, len); + return -EINVAL; } - BT_DBG("%s debug_keys %u key_count %u", hdev->name, cp->debug_keys, + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_LOAD_KEYS, ENODEV); + + BT_DBG("hci%u debug_keys %u key_count %u", index, cp->debug_keys, key_count); - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); hci_link_keys_clear(hdev); - set_bit(HCI_LINK_KEYS, &hdev->dev_flags); + set_bit(HCI_LINK_KEYS, &hdev->flags); if (cp->debug_keys) - set_bit(HCI_DEBUG_KEYS, &hdev->dev_flags); + set_bit(HCI_DEBUG_KEYS, &hdev->flags); else - clear_bit(HCI_DEBUG_KEYS, &hdev->dev_flags); + clear_bit(HCI_DEBUG_KEYS, &hdev->flags); + + len -= sizeof(*cp); + i = 0; + + while (i < len) { + struct mgmt_key_info *key = (void *) cp->keys + i; + + i += sizeof(*key); + + if (key->key_type == KEY_TYPE_LTK) { + struct key_master_id *id = (void *) key->data; + + if (key->dlen != sizeof(struct key_master_id)) + continue; - for (i = 0; i < key_count; i++) { - struct mgmt_link_key_info *key = &cp->keys[i]; + hci_add_ltk(hdev, 0, &key->bdaddr, key->addr_type, + key->pin_len, key->auth, id->ediv, + id->rand, key->val); - hci_add_link_key(hdev, NULL, 0, &key->addr.bdaddr, key->val, - key->type, key->pin_len); + continue; + } + + hci_add_link_key(hdev, 0, &key->bdaddr, key->val, key->key_type, + key->pin_len); } - cmd_complete(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS, 0, NULL, 0); + err = cmd_complete(sk, index, MGMT_OP_LOAD_KEYS, NULL, 0); - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - return 0; + return err; } -static int device_unpaired(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 addr_type, struct sock *skip_sk) +static int remove_key(struct sock *sk, u16 index, unsigned char *data, u16 len) { - struct mgmt_ev_device_unpaired ev; + struct hci_dev *hdev; + struct mgmt_cp_remove_key *cp; + struct hci_conn *conn; + int err; + + cp = (void *) data; + + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_REMOVE_KEY, EINVAL); + + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_REMOVE_KEY, ENODEV); + + hci_dev_lock_bh(hdev); + + err = hci_remove_link_key(hdev, &cp->bdaddr); + if (err < 0) { + err = cmd_status(sk, index, MGMT_OP_REMOVE_KEY, -err); + goto unlock; + } + + err = 0; + + if (!test_bit(HCI_UP, &hdev->flags) || !cp->disconnect) + goto unlock; + + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); + if (conn) { + struct hci_cp_disconnect dc; + + put_unaligned_le16(conn->handle, &dc.handle); + dc.reason = 0x13; /* Remote User Terminated Connection */ + err = hci_send_cmd(hdev, HCI_OP_DISCONNECT, 0, NULL); + } - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = addr_type; +unlock: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - return mgmt_event(MGMT_EV_DEVICE_UNPAIRED, hdev, &ev, sizeof(ev), - skip_sk); + return err; } -static int unpair_device(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) { - struct mgmt_cp_unpair_device *cp = data; - struct mgmt_rp_unpair_device rp; + struct hci_dev *hdev; + struct mgmt_cp_disconnect *cp; struct hci_cp_disconnect dc; struct pending_cmd *cmd; struct hci_conn *conn; int err; - hci_dev_lock(hdev); + BT_DBG(""); - memset(&rp, 0, sizeof(rp)); - bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); - rp.addr.type = cp->addr.type; + cp = (void *) data; - if (!hdev_is_powered(hdev)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE, - MGMT_STATUS_NOT_POWERED, &rp, sizeof(rp)); - goto unlock; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_DISCONNECT, EINVAL); - if (cp->addr.type == MGMT_ADDR_BREDR) - err = hci_remove_link_key(hdev, &cp->addr.bdaddr); - else - err = hci_remove_ltk(hdev, &cp->addr.bdaddr); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_DISCONNECT, ENODEV); - if (err < 0) { - err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE, - MGMT_STATUS_NOT_PAIRED, &rp, sizeof(rp)); - goto unlock; + hci_dev_lock_bh(hdev); + + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_DISCONNECT, ENETDOWN); + goto failed; } - if (cp->disconnect) { - if (cp->addr.type == MGMT_ADDR_BREDR) - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, - &cp->addr.bdaddr); - else - conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, - &cp->addr.bdaddr); - } else { - conn = NULL; + if (mgmt_pending_find(MGMT_OP_DISCONNECT, index)) { + err = cmd_status(sk, index, MGMT_OP_DISCONNECT, EBUSY); + goto failed; } + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); if (!conn) { - err = cmd_complete(sk, hdev->id, MGMT_OP_UNPAIR_DEVICE, 0, - &rp, sizeof(rp)); - device_unpaired(hdev, &cp->addr.bdaddr, cp->addr.type, sk); - goto unlock; + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + if (!conn) { + err = cmd_status(sk, index, MGMT_OP_DISCONNECT, + ENOTCONN); + goto failed; + } } - cmd = mgmt_pending_add(sk, MGMT_OP_UNPAIR_DEVICE, hdev, cp, - sizeof(*cp)); + cmd = mgmt_pending_add(sk, MGMT_OP_DISCONNECT, index, data, len); if (!cmd) { err = -ENOMEM; - goto unlock; + goto failed; } 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); if (err < 0) mgmt_pending_remove(cmd); +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + + return err; +} + +static int get_connections(struct sock *sk, u16 index) +{ + struct mgmt_rp_get_connections *rp; + struct hci_dev *hdev; + struct list_head *p; + size_t rp_len; + u16 count; + int i, err; + + BT_DBG(""); + + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_GET_CONNECTIONS, ENODEV); + + hci_dev_lock_bh(hdev); + + count = 0; + list_for_each(p, &hdev->conn_hash.list) { + count++; + } + + rp_len = sizeof(*rp) + (count * sizeof(bdaddr_t)); + rp = kmalloc(rp_len, GFP_ATOMIC); + if (!rp) { + err = -ENOMEM; + goto unlock; + } + + 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); + + 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: - hci_dev_unlock(hdev); + kfree(rp); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); return err; } -static int disconnect(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_cp_disconnect *cp = data; - struct hci_cp_disconnect dc; + struct hci_dev *hdev; + struct mgmt_cp_pin_code_reply *cp; + struct hci_cp_pin_code_reply reply; struct pending_cmd *cmd; - struct hci_conn *conn; int err; BT_DBG(""); - hci_dev_lock(hdev); + cp = (void *) data; - if (!test_bit(HCI_UP, &hdev->flags)) { - err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT, - MGMT_STATUS_NOT_POWERED); - goto failed; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, EINVAL); - if (mgmt_pending_find(MGMT_OP_DISCONNECT, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT, - MGMT_STATUS_BUSY); - goto failed; - } + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, ENODEV); - if (cp->addr.type == MGMT_ADDR_BREDR) - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); - else - conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr); + hci_dev_lock_bh(hdev); - if (!conn) { - err = cmd_status(sk, hdev->id, MGMT_OP_DISCONNECT, - MGMT_STATUS_NOT_CONNECTED); + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, ENETDOWN); goto failed; } - cmd = mgmt_pending_add(sk, MGMT_OP_DISCONNECT, hdev, data, len); + cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_REPLY, index, data, len); if (!cmd) { err = -ENOMEM; goto failed; } - put_unaligned_le16(conn->handle, &dc.handle); - dc.reason = 0x13; /* Remote User Terminated Connection */ + bacpy(&reply.bdaddr, &cp->bdaddr); + reply.pin_len = cp->pin_len; + memcpy(reply.pin_code, cp->pin_code, 16); - err = hci_send_cmd(hdev, HCI_OP_DISCONNECT, sizeof(dc), &dc); + err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_REPLY, sizeof(reply), &reply); if (err < 0) mgmt_pending_remove(cmd); failed: - hci_dev_unlock(hdev); - return err; -} + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); -static u8 link_to_mgmt(u8 link_type, u8 addr_type) -{ - switch (link_type) { - case LE_LINK: - switch (addr_type) { - case ADDR_LE_DEV_PUBLIC: - return MGMT_ADDR_LE_PUBLIC; - case ADDR_LE_DEV_RANDOM: - return MGMT_ADDR_LE_RANDOM; - default: - return MGMT_ADDR_INVALID; - } - case ACL_LINK: - return MGMT_ADDR_BREDR; - default: - return MGMT_ADDR_INVALID; - } + return err; } -static int get_connections(struct sock *sk, struct hci_dev *hdev, void *data, - u16 data_len) +static int encrypt_link(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_rp_get_connections *rp; - struct hci_conn *c; - size_t rp_len; - int err; - u16 i; + struct hci_dev *hdev; + struct mgmt_cp_encrypt_link *cp; + struct hci_cp_set_conn_encrypt enc; + struct hci_conn *conn; + int err = 0; BT_DBG(""); - hci_dev_lock(hdev); + cp = (void *) data; - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_GET_CONNECTIONS, - MGMT_STATUS_NOT_POWERED); - goto unlock; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_ENCRYPT_LINK, EINVAL); - i = 0; - list_for_each_entry(c, &hdev->conn_hash.list, list) { - if (test_bit(HCI_CONN_MGMT_CONNECTED, &c->flags)) - i++; - } + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_ENCRYPT_LINK, ENODEV); - rp_len = sizeof(*rp) + (i * sizeof(struct mgmt_addr_info)); - rp = kmalloc(rp_len, GFP_ATOMIC); - if (!rp) { - err = -ENOMEM; - goto unlock; - } + hci_dev_lock_bh(hdev); - i = 0; - list_for_each_entry(c, &hdev->conn_hash.list, list) { - if (!test_bit(HCI_CONN_MGMT_CONNECTED, &c->flags)) - continue; - bacpy(&rp->addr[i].bdaddr, &c->dst); - rp->addr[i].type = link_to_mgmt(c->type, c->dst_type); - if (rp->addr[i].type == MGMT_ADDR_INVALID) - continue; - i++; + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_ENCRYPT_LINK, ENETDOWN); + goto done; } - put_unaligned_le16(i, &rp->conn_count); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); + if (!conn) { + err = cmd_status(sk, index, MGMT_OP_ENCRYPT_LINK, ENOTCONN); + goto done; + } - /* Recalculate length in case of filtered SCO connections, etc */ - rp_len = sizeof(*rp) + (i * sizeof(struct mgmt_addr_info)); + if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { + err = cmd_status(sk, index, MGMT_OP_ENCRYPT_LINK, EINPROGRESS); + goto done; + } - err = cmd_complete(sk, hdev->id, MGMT_OP_GET_CONNECTIONS, 0, rp, - rp_len); + if (conn->link_mode & HCI_LM_AUTH) { + enc.handle = cpu_to_le16(conn->handle); + enc.encrypt = cp->enable; + err = hci_send_cmd(hdev, + HCI_OP_SET_CONN_ENCRYPT, sizeof(enc), &enc); + } else { + conn->auth_initiator = 1; + if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { + struct hci_cp_auth_requested cp; + cp.handle = cpu_to_le16(conn->handle); + err = hci_send_cmd(conn->hdev, + HCI_OP_AUTH_REQUESTED, sizeof(cp), &cp); + } + } - kfree(rp); +done: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); -unlock: - hci_dev_unlock(hdev); return err; } -static int send_pin_code_neg_reply(struct sock *sk, 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, hdev, cp, - sizeof(*cp)); - if (!cmd) - return -ENOMEM; - - err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_NEG_REPLY, - sizeof(cp->addr.bdaddr), &cp->addr.bdaddr); - if (err < 0) - mgmt_pending_remove(cmd); - - return err; -} -static int pin_code_reply(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int pin_code_neg_reply(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct hci_conn *conn; - struct mgmt_cp_pin_code_reply *cp = data; - struct hci_cp_pin_code_reply reply; + struct hci_dev *hdev; + struct mgmt_cp_pin_code_neg_reply *cp; struct pending_cmd *cmd; int err; BT_DBG(""); - hci_dev_lock(hdev); - - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_NOT_POWERED); - goto failed; - } - - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); - if (!conn) { - err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_NOT_CONNECTED); - goto failed; - } - - if (conn->pending_sec_level == BT_SECURITY_HIGH && cp->pin_len != 16) { - struct mgmt_cp_pin_code_neg_reply ncp; + cp = (void *) data; - memcpy(&ncp.addr, &cp->addr, sizeof(ncp.addr)); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, + EINVAL); - BT_ERR("PIN code is not 16 bytes long"); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, + ENODEV); - err = send_pin_code_neg_reply(sk, hdev, &ncp); - if (err >= 0) - err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, - MGMT_STATUS_INVALID_PARAMS); + hci_dev_lock_bh(hdev); + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, + ENETDOWN); goto failed; } - cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_REPLY, hdev, data, len); + cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_NEG_REPLY, index, + data, len); if (!cmd) { err = -ENOMEM; goto failed; } - bacpy(&reply.bdaddr, &cp->addr.bdaddr); - reply.pin_len = cp->pin_len; - memcpy(reply.pin_code, cp->pin_code, sizeof(reply.pin_code)); - - err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_REPLY, sizeof(reply), &reply); + 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(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int pin_code_neg_reply(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) +static int set_io_capability(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_cp_pin_code_neg_reply *cp = data; - int err; + struct hci_dev *hdev; + struct mgmt_cp_set_io_capability *cp; BT_DBG(""); - hci_dev_lock(hdev); - - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_PIN_CODE_NEG_REPLY, - MGMT_STATUS_NOT_POWERED); - goto failed; - } - - err = send_pin_code_neg_reply(sk, hdev, cp); - -failed: - hci_dev_unlock(hdev); - return err; -} + cp = (void *) data; -static int set_io_capability(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) -{ - struct mgmt_cp_set_io_capability *cp = data; + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_IO_CAPABILITY, EINVAL); - BT_DBG(""); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_IO_CAPABILITY, ENODEV); - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); hdev->io_capability = cp->io_capability; BT_DBG("%s IO capability set to 0x%02x", hdev->name, hdev->io_capability); - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - return cmd_complete(sk, hdev->id, MGMT_OP_SET_IO_CAPABILITY, 0, NULL, - 0); + return cmd_complete(sk, index, MGMT_OP_SET_IO_CAPABILITY, NULL, 0); } static inline struct pending_cmd *find_pairing(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; - struct pending_cmd *cmd; + struct list_head *p; + + list_for_each(p, &cmd_list) { + struct pending_cmd *cmd; + + cmd = list_entry(p, struct pending_cmd, list); - list_for_each_entry(cmd, &hdev->mgmt_pending, list) { if (cmd->opcode != MGMT_OP_PAIR_DEVICE) continue; + if (cmd->index != hdev->id) + continue; + if (cmd->user_data != conn) continue; @@ -1835,19 +1504,18 @@ static void pairing_complete(struct pending_cmd *cmd, u8 status) struct mgmt_rp_pair_device rp; struct hci_conn *conn = cmd->user_data; - bacpy(&rp.addr.bdaddr, &conn->dst); - rp.addr.type = link_to_mgmt(conn->type, conn->dst_type); + BT_DBG(" %u", status); - cmd_complete(cmd->sk, cmd->index, MGMT_OP_PAIR_DEVICE, status, - &rp, sizeof(rp)); + bacpy(&rp.bdaddr, &conn->dst); + rp.status = status; + + cmd_complete(cmd->sk, cmd->index, MGMT_OP_PAIR_DEVICE, &rp, sizeof(rp)); /* So we don't get further callbacks for this connection */ conn->connect_cfm_cb = NULL; conn->security_cfm_cb = NULL; conn->disconn_cfm_cb = NULL; - hci_conn_put(conn); - mgmt_pending_remove(cmd); } @@ -1855,80 +1523,141 @@ static void pairing_complete_cb(struct hci_conn *conn, u8 status) { struct pending_cmd *cmd; - BT_DBG("status %u", status); + BT_DBG(" %u", status); cmd = find_pairing(conn); - if (!cmd) + if (!cmd) { + BT_DBG("Unable to find a pending command"); + return; + } + + pairing_complete(cmd, status); + hci_conn_put(conn); +} + +static void pairing_security_complete_cb(struct hci_conn *conn, u8 status) +{ + struct pending_cmd *cmd; + + BT_DBG(" %u", status); + + cmd = find_pairing(conn); + if (!cmd) { BT_DBG("Unable to find a pending command"); + return; + } + + if (conn->type == LE_LINK) + smp_link_encrypt_cmplt(conn->l2cap_data, status, + status ? 0 : 1); else - pairing_complete(cmd, mgmt_status(status)); + pairing_complete(cmd, status); } -static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static void pairing_connect_complete_cb(struct hci_conn *conn, u8 status) { - struct mgmt_cp_pair_device *cp = data; - struct mgmt_rp_pair_device rp; struct pending_cmd *cmd; - u8 sec_level, auth_type; + + BT_DBG("conn: %p %u", conn, status); + + cmd = find_pairing(conn); + if (!cmd) { + BT_DBG("Unable to find a pending command"); + return; + } + + if (status) + pairing_complete(cmd, status); + + hci_conn_put(conn); +} + +static void discovery_terminated(struct pending_cmd *cmd, void *data) +{ + struct hci_dev *hdev; + struct mgmt_mode ev = {0}; + + BT_DBG(""); + hdev = hci_dev_get(cmd->index); + if (!hdev) + goto not_found; + + del_timer(&hdev->disco_le_timer); + del_timer(&hdev->disco_timer); + hci_dev_put(hdev); + +not_found: + mgmt_event(MGMT_EV_DISCOVERING, cmd->index, &ev, sizeof(ev), NULL); + + list_del(&cmd->list); + + mgmt_pending_free(cmd); +} + +static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) +{ + struct hci_dev *hdev; + struct mgmt_cp_pair_device *cp; + struct pending_cmd *cmd; + u8 sec_level, auth_type, io_cap; struct hci_conn *conn; + struct adv_entry *entry; int err; BT_DBG(""); - hci_dev_lock(hdev); + cp = (void *) data; - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_PAIR_DEVICE, - MGMT_STATUS_NOT_POWERED); - goto unlock; - } + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_PAIR_DEVICE, EINVAL); - sec_level = BT_SECURITY_MEDIUM; - if (cp->io_cap == 0x03) - auth_type = HCI_AT_DEDICATED_BONDING; - else - auth_type = HCI_AT_DEDICATED_BONDING_MITM; + hdev = hci_dev_get(index); - if (cp->addr.type == MGMT_ADDR_BREDR) - conn = hci_connect(hdev, ACL_LINK, 0, &cp->addr.bdaddr, - sec_level, auth_type); - else - conn = hci_connect(hdev, LE_LINK, 0, &cp->addr.bdaddr, - sec_level, auth_type); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_PAIR_DEVICE, ENODEV); - memset(&rp, 0, sizeof(rp)); - bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); - rp.addr.type = cp->addr.type; + hci_dev_lock_bh(hdev); + + io_cap = cp->io_cap; + + sec_level = BT_SECURITY_MEDIUM; + auth_type = HCI_AT_DEDICATED_BONDING; + + entry = hci_find_adv_entry(hdev, &cp->bdaddr); + if (entry && entry->flags & 0x04) { + conn = hci_le_connect(hdev, 0, &cp->bdaddr, sec_level, + auth_type, NULL); + } else { + /* ACL-SSP does not support io_cap 0x04 (KeyboadDisplay) */ + if (io_cap == 0x04) + io_cap = 0x01; + conn = hci_connect(hdev, ACL_LINK, 0, &cp->bdaddr, sec_level, + auth_type); + conn->auth_initiator = 1; + } if (IS_ERR(conn)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_PAIR_DEVICE, - MGMT_STATUS_CONNECT_FAILED, &rp, - sizeof(rp)); + err = PTR_ERR(conn); goto unlock; } if (conn->connect_cfm_cb) { hci_conn_put(conn); - err = cmd_complete(sk, hdev->id, MGMT_OP_PAIR_DEVICE, - MGMT_STATUS_BUSY, &rp, sizeof(rp)); + err = cmd_status(sk, index, MGMT_OP_PAIR_DEVICE, EBUSY); goto unlock; } - cmd = mgmt_pending_add(sk, MGMT_OP_PAIR_DEVICE, hdev, data, len); + cmd = mgmt_pending_add(sk, MGMT_OP_PAIR_DEVICE, index, data, len); if (!cmd) { err = -ENOMEM; hci_conn_put(conn); goto unlock; } - /* For LE, just connecting isn't a proof that the pairing finished */ - if (cp->addr.type == MGMT_ADDR_BREDR) - conn->connect_cfm_cb = pairing_complete_cb; - - conn->security_cfm_cb = pairing_complete_cb; + conn->connect_cfm_cb = pairing_connect_complete_cb; + conn->security_cfm_cb = pairing_security_complete_cb; conn->disconn_cfm_cb = pairing_complete_cb; - conn->io_capability = cp->io_cap; + conn->io_capability = io_cap; cmd->user_data = conn; if (conn->state == BT_CONNECTED && @@ -1938,726 +1667,670 @@ static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data, err = 0; unlock: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int user_confirm_reply(struct sock *sk, u16 index, unsigned char *data, + u16 len, u16 opcode) { - struct mgmt_addr_info *addr = data; + struct mgmt_cp_user_confirm_reply *cp = (void *) data; + u16 mgmt_op = opcode, hci_op; struct pending_cmd *cmd; - struct hci_conn *conn; + struct hci_dev *hdev; + struct hci_conn *le_conn; int err; - BT_DBG(""); + BT_DBG("%d", mgmt_op); - hci_dev_lock(hdev); + if (mgmt_op == MGMT_OP_USER_CONFIRM_NEG_REPLY) + hci_op = HCI_OP_USER_CONFIRM_NEG_REPLY; + else + hci_op = HCI_OP_USER_CONFIRM_REPLY; - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, - MGMT_STATUS_NOT_POWERED); - goto unlock; - } + if (len < sizeof(*cp)) + return cmd_status(sk, index, mgmt_op, EINVAL); - cmd = mgmt_pending_find(MGMT_OP_PAIR_DEVICE, hdev); - if (!cmd) { - err = cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, - MGMT_STATUS_INVALID_PARAMS); - goto unlock; + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, mgmt_op, ENODEV); + + hci_dev_lock_bh(hdev); + + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, mgmt_op, ENETDOWN); + goto done; } - conn = cmd->user_data; + le_conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + if (le_conn) { + err = le_user_confirm_reply(le_conn, mgmt_op, (void *) cp); + goto done; + } + BT_DBG("BR/EDR: %s", mgmt_op == MGMT_OP_USER_CONFIRM_NEG_REPLY ? + "Reject" : "Accept"); - if (bacmp(&addr->bdaddr, &conn->dst) != 0) { - err = cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, - MGMT_STATUS_INVALID_PARAMS); - goto unlock; + cmd = mgmt_pending_add(sk, mgmt_op, index, data, len); + if (!cmd) { + err = -ENOMEM; + goto done; } - pairing_complete(cmd, MGMT_STATUS_CANCELLED); + err = hci_send_cmd(hdev, hci_op, sizeof(cp->bdaddr), &cp->bdaddr); + if (err < 0) + mgmt_pending_remove(cmd); + +done: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - err = cmd_complete(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, 0, - addr, sizeof(*addr)); -unlock: - hci_dev_unlock(hdev); return err; } -static int user_pairing_resp(struct sock *sk, struct hci_dev *hdev, - bdaddr_t *bdaddr, u8 type, u16 mgmt_op, - u16 hci_op, __le32 passkey) +static int resolve_name(struct sock *sk, u16 index, unsigned char *data, + u16 len) { + struct mgmt_cp_resolve_name *mgmt_cp = (void *) data; + struct hci_cp_remote_name_req hci_cp; + struct hci_dev *hdev; struct pending_cmd *cmd; - struct hci_conn *conn; int err; - hci_dev_lock(hdev); - - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, mgmt_op, - MGMT_STATUS_NOT_POWERED); - goto done; - } - - if (type == MGMT_ADDR_BREDR) - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, bdaddr); - else - conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, bdaddr); - - if (!conn) { - err = cmd_status(sk, hdev->id, mgmt_op, - MGMT_STATUS_NOT_CONNECTED); - goto done; - } + BT_DBG(""); - if (type == MGMT_ADDR_LE_PUBLIC || type == MGMT_ADDR_LE_RANDOM) { - /* Continue with pairing via SMP */ - err = smp_user_confirm_reply(conn, mgmt_op, passkey); + if (len != sizeof(*mgmt_cp)) + return cmd_status(sk, index, MGMT_OP_RESOLVE_NAME, EINVAL); - if (!err) - err = cmd_status(sk, hdev->id, mgmt_op, - MGMT_STATUS_SUCCESS); - else - err = cmd_status(sk, hdev->id, mgmt_op, - MGMT_STATUS_FAILED); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_RESOLVE_NAME, ENODEV); - goto done; - } + hci_dev_lock_bh(hdev); - cmd = mgmt_pending_add(sk, mgmt_op, hdev, bdaddr, sizeof(*bdaddr)); + cmd = mgmt_pending_add(sk, MGMT_OP_RESOLVE_NAME, index, data, len); if (!cmd) { err = -ENOMEM; - goto done; + goto failed; } - /* Continue with pairing via HCI */ - if (hci_op == HCI_OP_USER_PASSKEY_REPLY) { - struct hci_cp_user_passkey_reply cp; - - bacpy(&cp.bdaddr, bdaddr); - cp.passkey = passkey; - err = hci_send_cmd(hdev, hci_op, sizeof(cp), &cp); - } else - err = hci_send_cmd(hdev, hci_op, sizeof(*bdaddr), bdaddr); - + memset(&hci_cp, 0, sizeof(hci_cp)); + bacpy(&hci_cp.bdaddr, &mgmt_cp->bdaddr); + err = hci_send_cmd(hdev, HCI_OP_REMOTE_NAME_REQ, sizeof(hci_cp), + &hci_cp); if (err < 0) mgmt_pending_remove(cmd); -done: - hci_dev_unlock(hdev); +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int user_confirm_reply(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int set_connection_params(struct sock *sk, u16 index, + unsigned char *data, u16 len) { - struct mgmt_cp_user_confirm_reply *cp = data; + struct mgmt_cp_set_connection_params *cp = (void *) data; + struct hci_dev *hdev; + struct hci_conn *conn; + int err; BT_DBG(""); if (len != sizeof(*cp)) - return cmd_status(sk, hdev->id, MGMT_OP_USER_CONFIRM_REPLY, - MGMT_STATUS_INVALID_PARAMS); + return cmd_status(sk, index, MGMT_OP_SET_CONNECTION_PARAMS, + EINVAL); - return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, - MGMT_OP_USER_CONFIRM_REPLY, - HCI_OP_USER_CONFIRM_REPLY, 0); -} + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_CONNECTION_PARAMS, + ENODEV); -static int user_confirm_neg_reply(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) -{ - struct mgmt_cp_user_confirm_neg_reply *cp = data; + hci_dev_lock_bh(hdev); - BT_DBG(""); + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + if (!conn) { + err = cmd_status(sk, index, MGMT_OP_SET_CONNECTION_PARAMS, + ENOTCONN); + goto failed; + } + + hci_le_conn_update(conn, le16_to_cpu(cp->interval_min), + le16_to_cpu(cp->interval_max), + le16_to_cpu(cp->slave_latency), + le16_to_cpu(cp->timeout_multiplier)); + + err = cmd_status(sk, index, MGMT_OP_SET_CONNECTION_PARAMS, 0); + +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, - MGMT_OP_USER_CONFIRM_NEG_REPLY, - HCI_OP_USER_CONFIRM_NEG_REPLY, 0); + return err; } -static int user_passkey_reply(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int set_rssi_reporter(struct sock *sk, u16 index, + unsigned char *data, u16 len) { - struct mgmt_cp_user_passkey_reply *cp = data; + struct mgmt_cp_set_rssi_reporter *cp = (void *) data; + struct hci_dev *hdev; + struct hci_conn *conn; + int err = 0; - BT_DBG(""); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_SET_RSSI_REPORTER, + EINVAL); + + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_RSSI_REPORTER, + ENODEV); + + hci_dev_lock_bh(hdev); + + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + + if (!conn) { + err = cmd_status(sk, index, MGMT_OP_SET_RSSI_REPORTER, + ENOTCONN); + goto failed; + } + + BT_DBG("updateOnThreshExceed %d ", cp->updateOnThreshExceed); + hci_conn_set_rssi_reporter(conn, cp->rssi_threshold, + __le16_to_cpu(cp->interval), cp->updateOnThreshExceed); + +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, - MGMT_OP_USER_PASSKEY_REPLY, - HCI_OP_USER_PASSKEY_REPLY, cp->passkey); + return err; } -static int user_passkey_neg_reply(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) +static int unset_rssi_reporter(struct sock *sk, u16 index, + unsigned char *data, u16 len) { - struct mgmt_cp_user_passkey_neg_reply *cp = data; + struct mgmt_cp_unset_rssi_reporter *cp = (void *) data; + struct hci_dev *hdev; + struct hci_conn *conn; + int err = 0; - BT_DBG(""); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_UNSET_RSSI_REPORTER, + EINVAL); - return user_pairing_resp(sk, hdev, &cp->addr.bdaddr, cp->addr.type, - MGMT_OP_USER_PASSKEY_NEG_REPLY, - HCI_OP_USER_PASSKEY_NEG_REPLY, 0); -} + hdev = hci_dev_get(index); + + if (!hdev) + return cmd_status(sk, index, MGMT_OP_UNSET_RSSI_REPORTER, + ENODEV); + + hci_dev_lock_bh(hdev); + + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + + if (!conn) { + err = cmd_status(sk, index, MGMT_OP_UNSET_RSSI_REPORTER, + ENOTCONN); + goto failed; + } -static int update_name(struct hci_dev *hdev, const char *name) -{ - struct hci_cp_write_local_name cp; + hci_conn_unset_rssi_reporter(conn); - memcpy(cp.name, name, sizeof(cp.name)); +failed: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - return hci_send_cmd(hdev, HCI_OP_WRITE_LOCAL_NAME, sizeof(cp), &cp); + return err; } -static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int set_local_name(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_cp_set_local_name *cp = data; + struct mgmt_cp_set_local_name *mgmt_cp = (void *) data; + struct hci_cp_write_local_name hci_cp; + struct hci_dev *hdev; struct pending_cmd *cmd; int err; BT_DBG(""); - hci_dev_lock(hdev); + if (len != sizeof(*mgmt_cp)) + return cmd_status(sk, index, MGMT_OP_SET_LOCAL_NAME, EINVAL); - memcpy(hdev->short_name, cp->short_name, sizeof(hdev->short_name)); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_SET_LOCAL_NAME, ENODEV); - if (!hdev_is_powered(hdev)) { - memcpy(hdev->dev_name, cp->name, sizeof(hdev->dev_name)); + hci_dev_lock_bh(hdev); - err = cmd_complete(sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, 0, - data, len); - if (err < 0) - goto failed; - - err = mgmt_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, data, len, - sk); - - goto failed; - } - - cmd = mgmt_pending_add(sk, MGMT_OP_SET_LOCAL_NAME, hdev, data, len); + cmd = mgmt_pending_add(sk, MGMT_OP_SET_LOCAL_NAME, index, data, len); if (!cmd) { err = -ENOMEM; goto failed; } - err = update_name(hdev, cp->name); + memcpy(hci_cp.name, mgmt_cp->name, sizeof(hci_cp.name)); + err = hci_send_cmd(hdev, HCI_OP_WRITE_LOCAL_NAME, sizeof(hci_cp), + &hci_cp); if (err < 0) mgmt_pending_remove(cmd); failed: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; } -static int read_local_oob_data(struct sock *sk, struct hci_dev *hdev, - void *data, u16 data_len) +static void discovery_rsp(struct pending_cmd *cmd, void *data) { - struct pending_cmd *cmd; - int err; - - BT_DBG("%s", hdev->name); - - hci_dev_lock(hdev); - - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_NOT_POWERED); - goto unlock; - } + struct mgmt_mode ev; - if (!(hdev->features[6] & LMP_SIMPLE_PAIR)) { - err = cmd_status(sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_NOT_SUPPORTED); - goto unlock; + BT_DBG(""); + if (cmd->opcode == MGMT_OP_START_DISCOVERY) { + ev.val = 1; + cmd_status(cmd->sk, cmd->index, MGMT_OP_START_DISCOVERY, 0); + } else { + ev.val = 0; + cmd_complete(cmd->sk, cmd->index, MGMT_OP_STOP_DISCOVERY, + NULL, 0); + if (cmd->opcode == MGMT_OP_STOP_DISCOVERY) { + struct hci_dev *hdev = hci_dev_get(cmd->index); + if (hdev) { + del_timer(&hdev->disco_le_timer); + del_timer(&hdev->disco_timer); + hci_dev_put(hdev); + } + } } - if (mgmt_pending_find(MGMT_OP_READ_LOCAL_OOB_DATA, hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, - MGMT_STATUS_BUSY); - goto unlock; - } + mgmt_event(MGMT_EV_DISCOVERING, cmd->index, &ev, sizeof(ev), NULL); - cmd = mgmt_pending_add(sk, MGMT_OP_READ_LOCAL_OOB_DATA, hdev, NULL, 0); - if (!cmd) { - err = -ENOMEM; - goto unlock; - } + list_del(&cmd->list); - err = hci_send_cmd(hdev, HCI_OP_READ_LOCAL_OOB_DATA, 0, NULL); - if (err < 0) - mgmt_pending_remove(cmd); + mgmt_pending_free(cmd); +} -unlock: - hci_dev_unlock(hdev); - return err; +void mgmt_inquiry_started(u16 index) +{ + BT_DBG(""); + mgmt_pending_foreach(MGMT_OP_START_DISCOVERY, index, + discovery_rsp, NULL); } -static int add_remote_oob_data(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) +void mgmt_inquiry_complete_evt(u16 index, u8 status) { - struct mgmt_cp_add_remote_oob_data *cp = data; - u8 status; - int err; + struct hci_dev *hdev; + struct hci_cp_le_set_scan_enable le_cp = {1, 0}; + struct mgmt_mode cp = {0}; + int err = -1; + + BT_DBG(""); - BT_DBG("%s ", hdev->name); + hdev = hci_dev_get(index); - hci_dev_lock(hdev); + if (!hdev || !lmp_le_capable(hdev)) { - if (!hdev_is_powered(hdev)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_ADD_REMOTE_OOB_DATA, - MGMT_STATUS_NOT_POWERED, &cp->addr, - sizeof(cp->addr)); - goto unlock; + mgmt_pending_foreach(MGMT_OP_STOP_DISCOVERY, index, + discovery_terminated, NULL); + + mgmt_event(MGMT_EV_DISCOVERING, index, &cp, sizeof(cp), NULL); + + if (hdev) + goto done; + else + return; } - err = hci_add_remote_oob_data(hdev, &cp->addr.bdaddr, cp->hash, - cp->randomizer); - if (err < 0) - status = MGMT_STATUS_FAILED; - else - status = 0; + if (hdev->disco_state != SCAN_IDLE) { + err = hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(le_cp), &le_cp); + if (err >= 0) { + mod_timer(&hdev->disco_le_timer, jiffies + + msecs_to_jiffies(hdev->disco_int_phase * 1000)); + hdev->disco_state = SCAN_LE; + } else + hdev->disco_state = SCAN_IDLE; + } - err = cmd_complete(sk, hdev->id, MGMT_OP_ADD_REMOTE_OOB_DATA, status, - &cp->addr, sizeof(cp->addr)); + if (hdev->disco_state == SCAN_IDLE) + mgmt_event(MGMT_EV_DISCOVERING, index, &cp, sizeof(cp), NULL); -unlock: - hci_dev_unlock(hdev); - return err; + if (err < 0) + mgmt_pending_foreach(MGMT_OP_STOP_DISCOVERY, index, + discovery_terminated, NULL); + +done: + hci_dev_put(hdev); } -static int remove_remote_oob_data(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) +void mgmt_disco_timeout(unsigned long data) { - struct mgmt_cp_remove_remote_oob_data *cp = data; - u8 status; - int err; + struct hci_dev *hdev = (void *) data; + struct pending_cmd *cmd; + struct mgmt_mode cp = {0}; - BT_DBG("%s", hdev->name); + BT_DBG("hci%d", hdev->id); - hci_dev_lock(hdev); + hdev = hci_dev_get(hdev->id); - if (!hdev_is_powered(hdev)) { - err = cmd_complete(sk, hdev->id, - MGMT_OP_REMOVE_REMOTE_OOB_DATA, - MGMT_STATUS_NOT_POWERED, &cp->addr, - sizeof(cp->addr)); - goto unlock; + if (!hdev) + return; + + hci_dev_lock_bh(hdev); + del_timer(&hdev->disco_le_timer); + + if (hdev->disco_state != SCAN_IDLE) { + struct hci_cp_le_set_scan_enable le_cp = {0, 0}; + + if (test_bit(HCI_UP, &hdev->flags)) { + if (hdev->disco_state == SCAN_LE) + hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(le_cp), &le_cp); + else + hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, + NULL); + } + hdev->disco_state = SCAN_IDLE; } - err = hci_remove_remote_oob_data(hdev, &cp->addr.bdaddr); - if (err < 0) - status = MGMT_STATUS_INVALID_PARAMS; - else - status = 0; + mgmt_event(MGMT_EV_DISCOVERING, hdev->id, &cp, sizeof(cp), NULL); - err = cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_REMOTE_OOB_DATA, - status, &cp->addr, sizeof(cp->addr)); + cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, hdev->id); + if (cmd) + mgmt_pending_remove(cmd); -unlock: - hci_dev_unlock(hdev); - return err; + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); } -int mgmt_interleaved_discovery(struct hci_dev *hdev) +void mgmt_disco_le_timeout(unsigned long data) { - int err; + struct hci_dev *hdev = (void *)data; + struct hci_cp_le_set_scan_enable le_cp = {0, 0}; - BT_DBG("%s", hdev->name); + BT_DBG("hci%d", hdev->id); - hci_dev_lock(hdev); + hdev = hci_dev_get(hdev->id); - err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR_LE); - if (err < 0) - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); + if (!hdev) + return; - hci_dev_unlock(hdev); + hci_dev_lock_bh(hdev); + + if (test_bit(HCI_UP, &hdev->flags)) { + if (hdev->disco_state == SCAN_LE) + hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(le_cp), &le_cp); + + /* re-start BR scan */ + if (hdev->disco_state != SCAN_IDLE) { + struct hci_cp_inquiry cp = {{0x33, 0x8b, 0x9e}, 4, 0}; + hdev->disco_int_phase *= 2; + hdev->disco_int_count = 0; + cp.num_rsp = (u8) hdev->disco_int_phase; + hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp); + hdev->disco_state = SCAN_BR; + } + } - return err; + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); } -static int start_discovery(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) +static int start_discovery(struct sock *sk, u16 index) { - struct mgmt_cp_start_discovery *cp = data; + struct hci_cp_inquiry cp = {{0x33, 0x8b, 0x9e}, 8, 0}; + struct hci_dev *hdev; struct pending_cmd *cmd; int err; - BT_DBG("%s", hdev->name); + BT_DBG(""); - hci_dev_lock(hdev); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_START_DISCOVERY, ENODEV); - if (!hdev_is_powered(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY, - MGMT_STATUS_NOT_POWERED); - goto failed; - } + hci_dev_lock_bh(hdev); - if (hdev->discovery.state != DISCOVERY_STOPPED) { - err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY, - MGMT_STATUS_BUSY); + if (hdev->disco_state && timer_pending(&hdev->disco_timer)) { + err = -EBUSY; goto failed; } - cmd = mgmt_pending_add(sk, MGMT_OP_START_DISCOVERY, hdev, NULL, 0); + cmd = mgmt_pending_add(sk, MGMT_OP_START_DISCOVERY, index, NULL, 0); if (!cmd) { err = -ENOMEM; goto failed; } - hdev->discovery.type = cp->type; - - switch (hdev->discovery.type) { - case DISCOV_TYPE_BREDR: - if (lmp_bredr_capable(hdev)) - err = hci_do_inquiry(hdev, INQUIRY_LEN_BREDR); - else - err = -ENOTSUPP; - break; + /* If LE Capable, we will alternate between BR/EDR and LE */ + if (lmp_le_capable(hdev)) { + struct hci_cp_le_set_scan_parameters le_cp; - case DISCOV_TYPE_LE: - if (lmp_host_le_capable(hdev)) - err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT, - LE_SCAN_WIN, LE_SCAN_TIMEOUT_LE_ONLY); - else - err = -ENOTSUPP; - break; + /* Shorten BR scan params */ + cp.num_rsp = 1; + cp.length /= 2; - case DISCOV_TYPE_INTERLEAVED: - if (lmp_host_le_capable(hdev) && lmp_bredr_capable(hdev)) - err = hci_le_scan(hdev, LE_SCAN_TYPE, LE_SCAN_INT, - LE_SCAN_WIN, - LE_SCAN_TIMEOUT_BREDR_LE); - else - err = -ENOTSUPP; - break; + /* Setup LE scan params */ + memset(&le_cp, 0, sizeof(le_cp)); + le_cp.type = 0x01; /* Active scanning */ + /* The recommended value for scan interval and window is + * 11.25 msec. It is calculated by: time = n * 0.625 msec */ + le_cp.interval = cpu_to_le16(0x0012); + le_cp.window = cpu_to_le16(0x0012); + le_cp.own_bdaddr_type = 0; /* Public address */ + le_cp.filter = 0; /* Accept all adv packets */ - default: - err = -EINVAL; + hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_PARAMETERS, + sizeof(le_cp), &le_cp); } + err = hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp); + if (err < 0) mgmt_pending_remove(cmd); - else - hci_discovery_set_state(hdev, DISCOVERY_STARTING); + else if (lmp_le_capable(hdev)) { + cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index); + if (!cmd) + mgmt_pending_add(sk, MGMT_OP_STOP_DISCOVERY, index, + NULL, 0); + hdev->disco_int_phase = 1; + hdev->disco_int_count = 0; + hdev->disco_state = SCAN_BR; + del_timer(&hdev->disco_le_timer); + del_timer(&hdev->disco_timer); + mod_timer(&hdev->disco_timer, + jiffies + msecs_to_jiffies(20000)); + } failed: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + + if (err < 0) + return cmd_status(sk, index, MGMT_OP_START_DISCOVERY, -err); + return err; } -static int stop_discovery(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int stop_discovery(struct sock *sk, u16 index) { - struct mgmt_cp_stop_discovery *mgmt_cp = data; - struct pending_cmd *cmd; - struct hci_cp_remote_name_req_cancel cp; - struct inquiry_entry *e; - int err; + struct hci_cp_le_set_scan_enable le_cp = {0, 0}; + struct mgmt_mode mode_cp = {0}; + struct hci_dev *hdev; + struct pending_cmd *cmd = NULL; + int err = -EPERM; + u8 state; - BT_DBG("%s", hdev->name); + BT_DBG(""); - hci_dev_lock(hdev); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_STOP_DISCOVERY, ENODEV); - if (!hci_discovery_active(hdev)) { - err = cmd_complete(sk, hdev->id, MGMT_OP_STOP_DISCOVERY, - MGMT_STATUS_REJECTED, &mgmt_cp->type, - sizeof(mgmt_cp->type)); - goto unlock; - } + hci_dev_lock_bh(hdev); - if (hdev->discovery.type != mgmt_cp->type) { - err = cmd_complete(sk, hdev->id, MGMT_OP_STOP_DISCOVERY, - MGMT_STATUS_INVALID_PARAMS, &mgmt_cp->type, - sizeof(mgmt_cp->type)); - goto unlock; - } + state = hdev->disco_state; + hdev->disco_state = SCAN_IDLE; + del_timer(&hdev->disco_le_timer); + del_timer(&hdev->disco_timer); - cmd = mgmt_pending_add(sk, MGMT_OP_STOP_DISCOVERY, hdev, NULL, 0); - if (!cmd) { - err = -ENOMEM; - goto unlock; - } + if (state == SCAN_LE) { + err = hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(le_cp), &le_cp); + if (err >= 0) { + mgmt_pending_foreach(MGMT_OP_STOP_DISCOVERY, index, + discovery_terminated, NULL); - if (hdev->discovery.state == DISCOVERY_FINDING) { - err = hci_cancel_inquiry(hdev); - if (err < 0) - mgmt_pending_remove(cmd); - else - hci_discovery_set_state(hdev, DISCOVERY_STOPPING); - goto unlock; + err = cmd_complete(sk, index, MGMT_OP_STOP_DISCOVERY, + NULL, 0); + } } - e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, NAME_PENDING); - if (!e) { + if (err < 0) + err = hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, NULL); + + cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index); + if (err < 0 && cmd) mgmt_pending_remove(cmd); - err = cmd_complete(sk, hdev->id, MGMT_OP_STOP_DISCOVERY, 0, - &mgmt_cp->type, sizeof(mgmt_cp->type)); - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); - goto unlock; - } - bacpy(&cp.bdaddr, &e->data.bdaddr); - err = hci_send_cmd(hdev, HCI_OP_REMOTE_NAME_REQ_CANCEL, sizeof(cp), - &cp); + mgmt_event(MGMT_EV_DISCOVERING, index, &mode_cp, sizeof(mode_cp), NULL); + + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + if (err < 0) - mgmt_pending_remove(cmd); + return cmd_status(sk, index, MGMT_OP_STOP_DISCOVERY, -err); else - hci_discovery_set_state(hdev, DISCOVERY_STOPPING); - -unlock: - hci_dev_unlock(hdev); - return err; + return err; } -static int confirm_name(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int read_local_oob_data(struct sock *sk, u16 index) { - struct mgmt_cp_confirm_name *cp = data; - struct inquiry_entry *e; + struct hci_dev *hdev; + struct pending_cmd *cmd; int err; - BT_DBG("%s", hdev->name); + BT_DBG("hci%u", index); - hci_dev_lock(hdev); + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, + ENODEV); - if (!hci_discovery_active(hdev)) { - err = cmd_status(sk, hdev->id, MGMT_OP_CONFIRM_NAME, - MGMT_STATUS_FAILED); - goto failed; - } + hci_dev_lock_bh(hdev); - e = hci_inquiry_cache_lookup_unknown(hdev, &cp->addr.bdaddr); - if (!e) { - err = cmd_status(sk, hdev->id, MGMT_OP_CONFIRM_NAME, - MGMT_STATUS_INVALID_PARAMS); - goto failed; + if (!test_bit(HCI_UP, &hdev->flags)) { + err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, + ENETDOWN); + goto unlock; } - if (cp->name_known) { - e->name_state = NAME_KNOWN; - list_del(&e->list); - } else { - e->name_state = NAME_NEEDED; - hci_inquiry_cache_update_resolve(hdev, e); + if (!(hdev->features[6] & LMP_SIMPLE_PAIR)) { + err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, + EOPNOTSUPP); + goto unlock; } - err = 0; - -failed: - hci_dev_unlock(hdev); - return err; -} - -static int block_device(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) -{ - struct mgmt_cp_block_device *cp = data; - u8 status; - int err; - - BT_DBG("%s", hdev->name); + if (mgmt_pending_find(MGMT_OP_READ_LOCAL_OOB_DATA, index)) { + err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, EBUSY); + goto unlock; + } - hci_dev_lock(hdev); + cmd = mgmt_pending_add(sk, MGMT_OP_READ_LOCAL_OOB_DATA, index, NULL, 0); + if (!cmd) { + err = -ENOMEM; + goto unlock; + } - err = hci_blacklist_add(hdev, &cp->addr.bdaddr, cp->addr.type); + err = hci_send_cmd(hdev, HCI_OP_READ_LOCAL_OOB_DATA, 0, NULL); if (err < 0) - status = MGMT_STATUS_FAILED; - else - status = 0; - - err = cmd_complete(sk, hdev->id, MGMT_OP_BLOCK_DEVICE, status, - &cp->addr, sizeof(cp->addr)); + mgmt_pending_remove(cmd); - hci_dev_unlock(hdev); +unlock: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); return err; } -static int unblock_device(struct sock *sk, struct hci_dev *hdev, void *data, - u16 len) +static int add_remote_oob_data(struct sock *sk, u16 index, unsigned char *data, + u16 len) { - struct mgmt_cp_unblock_device *cp = data; - u8 status; + struct hci_dev *hdev; + struct mgmt_cp_add_remote_oob_data *cp = (void *) data; int err; - BT_DBG("%s", hdev->name); + BT_DBG("hci%u ", index); - hci_dev_lock(hdev); + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, + EINVAL); + + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, + ENODEV); - err = hci_blacklist_del(hdev, &cp->addr.bdaddr, cp->addr.type); + hci_dev_lock_bh(hdev); + + err = hci_add_remote_oob_data(hdev, &cp->bdaddr, cp->hash, + cp->randomizer); if (err < 0) - status = MGMT_STATUS_INVALID_PARAMS; + err = cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, -err); else - status = 0; - - err = cmd_complete(sk, hdev->id, MGMT_OP_UNBLOCK_DEVICE, status, - &cp->addr, sizeof(cp->addr)); + err = cmd_complete(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, NULL, + 0); - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); return err; } -static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev, - void *data, u16 len) +static int remove_remote_oob_data(struct sock *sk, u16 index, + unsigned char *data, u16 len) { - struct mgmt_mode *cp = data; - struct hci_cp_write_page_scan_activity acp; - u8 type; + struct hci_dev *hdev; + struct mgmt_cp_remove_remote_oob_data *cp = (void *) data; int err; - BT_DBG("%s", hdev->name); - - if (!hdev_is_powered(hdev)) - return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_NOT_POWERED); - - if (!test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) - return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_REJECTED); + BT_DBG("hci%u ", index); - hci_dev_lock(hdev); - - if (cp->val) { - type = PAGE_SCAN_TYPE_INTERLACED; - - /* 22.5 msec page scan interval */ - acp.interval = __constant_cpu_to_le16(0x0024); - } else { - type = PAGE_SCAN_TYPE_STANDARD; /* default */ + if (len != sizeof(*cp)) + return cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, + EINVAL); - /* default 1.28 sec page scan */ - acp.interval = __constant_cpu_to_le16(0x0800); - } + hdev = hci_dev_get(index); + if (!hdev) + return cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, + ENODEV); - /* default 11.25 msec page scan window */ - acp.window = __constant_cpu_to_le16(0x0012); + hci_dev_lock_bh(hdev); - err = hci_send_cmd(hdev, HCI_OP_WRITE_PAGE_SCAN_ACTIVITY, sizeof(acp), - &acp); - if (err < 0) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_FAILED); - goto done; - } + err = hci_remove_remote_oob_data(hdev, &cp->bdaddr); + if (err < 0) + err = cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, + -err); + else + err = cmd_complete(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, + NULL, 0); - err = hci_send_cmd(hdev, HCI_OP_WRITE_PAGE_SCAN_TYPE, 1, &type); - if (err < 0) { - err = cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, - MGMT_STATUS_FAILED); - goto done; - } + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); - err = cmd_complete(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, 0, - NULL, 0); -done: - hci_dev_unlock(hdev); return err; } -static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev, - void *cp_data, u16 len) -{ - struct mgmt_cp_load_long_term_keys *cp = cp_data; - u16 key_count, expected_len; - int i; - - key_count = get_unaligned_le16(&cp->key_count); - - expected_len = sizeof(*cp) + key_count * - sizeof(struct mgmt_ltk_info); - if (expected_len != len) { - BT_ERR("load_keys: expected %u bytes, got %u bytes", - len, expected_len); - return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS, - EINVAL); - } - - BT_DBG("%s key_count %u", hdev->name, key_count); - - hci_dev_lock(hdev); - - hci_smp_ltks_clear(hdev); - - for (i = 0; i < key_count; i++) { - struct mgmt_ltk_info *key = &cp->keys[i]; - u8 type; - - if (key->master) - type = HCI_SMP_LTK; - else - type = HCI_SMP_LTK_SLAVE; - - hci_add_ltk(hdev, &key->addr.bdaddr, key->addr.type, - type, 0, key->authenticated, key->val, - key->enc_size, key->ediv, key->rand); - } - - hci_dev_unlock(hdev); - - return 0; -} - -struct mgmt_handler { - int (*func) (struct sock *sk, struct hci_dev *hdev, void *data, - u16 data_len); - bool var_len; - size_t data_len; -} mgmt_handlers[] = { - { NULL }, /* 0x0000 (no command) */ - { read_version, false, MGMT_READ_VERSION_SIZE }, - { read_commands, false, MGMT_READ_COMMANDS_SIZE }, - { read_index_list, false, MGMT_READ_INDEX_LIST_SIZE }, - { read_controller_info, false, MGMT_READ_INFO_SIZE }, - { set_powered, false, MGMT_SETTING_SIZE }, - { set_discoverable, false, MGMT_SET_DISCOVERABLE_SIZE }, - { set_connectable, false, MGMT_SETTING_SIZE }, - { set_fast_connectable, false, MGMT_SETTING_SIZE }, - { set_pairable, false, MGMT_SETTING_SIZE }, - { set_link_security, false, MGMT_SETTING_SIZE }, - { set_ssp, false, MGMT_SETTING_SIZE }, - { set_hs, false, MGMT_SETTING_SIZE }, - { set_le, false, MGMT_SETTING_SIZE }, - { set_dev_class, false, MGMT_SET_DEV_CLASS_SIZE }, - { set_local_name, false, MGMT_SET_LOCAL_NAME_SIZE }, - { add_uuid, false, MGMT_ADD_UUID_SIZE }, - { remove_uuid, false, MGMT_REMOVE_UUID_SIZE }, - { load_link_keys, true, MGMT_LOAD_LINK_KEYS_SIZE }, - { load_long_term_keys, true, MGMT_LOAD_LONG_TERM_KEYS_SIZE }, - { disconnect, false, MGMT_DISCONNECT_SIZE }, - { get_connections, false, MGMT_GET_CONNECTIONS_SIZE }, - { pin_code_reply, false, MGMT_PIN_CODE_REPLY_SIZE }, - { pin_code_neg_reply, false, MGMT_PIN_CODE_NEG_REPLY_SIZE }, - { set_io_capability, false, MGMT_SET_IO_CAPABILITY_SIZE }, - { pair_device, false, MGMT_PAIR_DEVICE_SIZE }, - { cancel_pair_device, false, MGMT_CANCEL_PAIR_DEVICE_SIZE }, - { unpair_device, false, MGMT_UNPAIR_DEVICE_SIZE }, - { user_confirm_reply, false, MGMT_USER_CONFIRM_REPLY_SIZE }, - { user_confirm_neg_reply, false, MGMT_USER_CONFIRM_NEG_REPLY_SIZE }, - { user_passkey_reply, false, MGMT_USER_PASSKEY_REPLY_SIZE }, - { user_passkey_neg_reply, false, MGMT_USER_PASSKEY_NEG_REPLY_SIZE }, - { read_local_oob_data, false, MGMT_READ_LOCAL_OOB_DATA_SIZE }, - { add_remote_oob_data, false, MGMT_ADD_REMOTE_OOB_DATA_SIZE }, - { remove_remote_oob_data, false, MGMT_REMOVE_REMOTE_OOB_DATA_SIZE }, - { start_discovery, false, MGMT_START_DISCOVERY_SIZE }, - { stop_discovery, false, MGMT_STOP_DISCOVERY_SIZE }, - { confirm_name, false, MGMT_CONFIRM_NAME_SIZE }, - { block_device, false, MGMT_BLOCK_DEVICE_SIZE }, - { unblock_device, false, MGMT_UNBLOCK_DEVICE_SIZE }, -}; - - int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) { - void *buf; - u8 *cp; + unsigned char *buf; struct mgmt_hdr *hdr; u16 opcode, index, len; - struct hci_dev *hdev = NULL; - struct mgmt_handler *handler; int err; BT_DBG("got %zu bytes", msglen); @@ -2674,7 +2347,7 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) goto done; } - hdr = buf; + hdr = (struct mgmt_hdr *) buf; opcode = get_unaligned_le16(&hdr->opcode); index = get_unaligned_le16(&hdr->index); len = get_unaligned_le16(&hdr->len); @@ -2684,54 +2357,122 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) goto done; } - if (index != MGMT_INDEX_NONE) { - hdev = hci_dev_get(index); - if (!hdev) { - err = cmd_status(sk, index, opcode, - MGMT_STATUS_INVALID_INDEX); - goto done; - } - } - - if (opcode >= ARRAY_SIZE(mgmt_handlers) || - mgmt_handlers[opcode].func == NULL) { - BT_DBG("Unknown op %u", opcode); - err = cmd_status(sk, index, opcode, - MGMT_STATUS_UNKNOWN_COMMAND); - goto done; - } - - if ((hdev && opcode < MGMT_OP_READ_INFO) || - (!hdev && opcode >= MGMT_OP_READ_INFO)) { - err = cmd_status(sk, index, opcode, - MGMT_STATUS_INVALID_INDEX); - goto done; - } - - handler = &mgmt_handlers[opcode]; + BT_DBG("got opcode %x", opcode); + switch (opcode) { + case MGMT_OP_READ_VERSION: + err = read_version(sk); + break; + case MGMT_OP_READ_INDEX_LIST: + err = read_index_list(sk); + break; + case MGMT_OP_READ_INFO: + err = read_controller_info(sk, index); + break; + case MGMT_OP_SET_POWERED: + err = set_powered(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_DISCOVERABLE: + err = set_discoverable(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_LIMIT_DISCOVERABLE: + err = set_limited_discoverable(sk, index, buf + sizeof(*hdr), + len); + break; + case MGMT_OP_SET_CONNECTABLE: + err = set_connectable(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_PAIRABLE: + err = set_pairable(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_ADD_UUID: + err = add_uuid(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_REMOVE_UUID: + err = remove_uuid(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_DEV_CLASS: + err = set_dev_class(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_SERVICE_CACHE: + err = set_service_cache(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_LOAD_KEYS: + err = load_keys(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_REMOVE_KEY: + err = remove_key(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_DISCONNECT: + err = disconnect(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_GET_CONNECTIONS: + err = get_connections(sk, index); + break; + case MGMT_OP_PIN_CODE_REPLY: + err = pin_code_reply(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_PIN_CODE_NEG_REPLY: + err = pin_code_neg_reply(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_IO_CAPABILITY: + err = set_io_capability(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_PAIR_DEVICE: + err = pair_device(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_USER_CONFIRM_REPLY: + case MGMT_OP_USER_PASSKEY_REPLY: + case MGMT_OP_USER_CONFIRM_NEG_REPLY: + err = user_confirm_reply(sk, index, buf + sizeof(*hdr), + len, opcode); + break; + case MGMT_OP_SET_LOCAL_NAME: + err = set_local_name(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_RESOLVE_NAME: + err = resolve_name(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_CONNECTION_PARAMS: + err = set_connection_params(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_SET_RSSI_REPORTER: + err = set_rssi_reporter(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_UNSET_RSSI_REPORTER: + err = unset_rssi_reporter(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_READ_LOCAL_OOB_DATA: + err = read_local_oob_data(sk, index); + break; + case MGMT_OP_ADD_REMOTE_OOB_DATA: + err = add_remote_oob_data(sk, index, buf + sizeof(*hdr), len); + break; + case MGMT_OP_REMOVE_REMOTE_OOB_DATA: + err = remove_remote_oob_data(sk, index, buf + sizeof(*hdr), + len); + break; + case MGMT_OP_ENCRYPT_LINK: + err = encrypt_link(sk, index, buf + sizeof(*hdr), len); + break; - if ((handler->var_len && len < handler->data_len) || - (!handler->var_len && len != handler->data_len)) { - err = cmd_status(sk, index, opcode, - MGMT_STATUS_INVALID_PARAMS); - goto done; + default: + BT_DBG("Unknown op %u", opcode); + err = cmd_status(sk, index, opcode, 0x01); + break; } - if (hdev) - mgmt_init_hdev(sk, hdev); - - cp = buf + sizeof(*hdr); - - err = handler->func(sk, hdev, cp, len); if (err < 0) goto done; err = msglen; done: - if (hdev) - hci_dev_put(hdev); - kfree(buf); return err; } @@ -2744,31 +2485,37 @@ static void cmd_status_rsp(struct pending_cmd *cmd, void *data) mgmt_pending_remove(cmd); } -int mgmt_index_added(struct hci_dev *hdev) +int mgmt_index_added(u16 index) { - return mgmt_event(MGMT_EV_INDEX_ADDED, hdev, NULL, 0, NULL); + BT_DBG("%d", index); + return mgmt_event(MGMT_EV_INDEX_ADDED, index, NULL, 0, NULL); } -int mgmt_index_removed(struct hci_dev *hdev) +int mgmt_index_removed(u16 index) { - u8 status = MGMT_STATUS_INVALID_INDEX; + u8 status = ENODEV; + + BT_DBG("%d", index); - mgmt_pending_foreach(0, hdev, cmd_status_rsp, &status); + mgmt_pending_foreach(0, index, cmd_status_rsp, &status); - return mgmt_event(MGMT_EV_INDEX_REMOVED, hdev, NULL, 0, NULL); + return mgmt_event(MGMT_EV_INDEX_REMOVED, index, NULL, 0, NULL); } struct cmd_lookup { + u8 val; struct sock *sk; - struct hci_dev *hdev; - u8 mgmt_status; }; -static void settings_rsp(struct pending_cmd *cmd, void *data) +static void mode_rsp(struct pending_cmd *cmd, void *data) { + struct mgmt_mode *cp = cmd->param; struct cmd_lookup *match = data; - send_settings_rsp(cmd->sk, cmd->opcode, match->hdev); + if (cp->val != match->val) + return; + + send_mode_rsp(cmd->sk, cmd->opcode, cmd->index, cp->val); list_del(&cmd->list); @@ -2780,174 +2527,104 @@ static void settings_rsp(struct pending_cmd *cmd, void *data) mgmt_pending_free(cmd); } -int mgmt_powered(struct hci_dev *hdev, u8 powered) +int mgmt_powered(u16 index, u8 powered) { - struct cmd_lookup match = { NULL, hdev }; - int err; - - if (!test_bit(HCI_MGMT, &hdev->dev_flags)) - return 0; - - mgmt_pending_foreach(MGMT_OP_SET_POWERED, hdev, settings_rsp, &match); + struct mgmt_mode ev; + struct cmd_lookup match = { powered, NULL }; + int ret; - if (powered) { - u8 scan = 0; + BT_DBG("hci%u %d", index, powered); - if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) - scan |= SCAN_PAGE; - if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) - scan |= SCAN_INQUIRY; + mgmt_pending_foreach(MGMT_OP_SET_POWERED, index, mode_rsp, &match); - if (scan) - hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); - - update_class(hdev); - update_name(hdev, hdev->dev_name); - update_eir(hdev); - } else { - u8 status = MGMT_STATUS_NOT_POWERED; - mgmt_pending_foreach(0, hdev, cmd_status_rsp, &status); + if (!powered) { + u8 status = ENETDOWN; + mgmt_pending_foreach(0, index, cmd_status_rsp, &status); } - err = new_settings(hdev, match.sk); + ev.val = powered; + + ret = mgmt_event(MGMT_EV_POWERED, index, &ev, sizeof(ev), match.sk); if (match.sk) sock_put(match.sk); - return err; + return ret; } -int mgmt_discoverable(struct hci_dev *hdev, u8 discoverable) +int mgmt_discoverable(u16 index, u8 discoverable) { - struct cmd_lookup match = { NULL, hdev }; - bool changed = false; - int err = 0; + struct mgmt_mode ev; + struct cmd_lookup match = { discoverable, NULL }; + int ret; - if (discoverable) { - if (!test_and_set_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) - changed = true; - } else { - if (test_and_clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) - changed = true; - } + mgmt_pending_foreach(MGMT_OP_SET_DISCOVERABLE, index, mode_rsp, &match); - mgmt_pending_foreach(MGMT_OP_SET_DISCOVERABLE, hdev, settings_rsp, - &match); + ev.val = discoverable; - if (changed) - err = new_settings(hdev, match.sk); + ret = mgmt_event(MGMT_EV_DISCOVERABLE, index, &ev, sizeof(ev), + match.sk); if (match.sk) sock_put(match.sk); - return err; + return ret; } -int mgmt_connectable(struct hci_dev *hdev, u8 connectable) +int mgmt_connectable(u16 index, u8 connectable) { - struct cmd_lookup match = { NULL, hdev }; - bool changed = false; - int err = 0; + struct mgmt_mode ev; + struct cmd_lookup match = { connectable, NULL }; + int ret; - if (connectable) { - if (!test_and_set_bit(HCI_CONNECTABLE, &hdev->dev_flags)) - changed = true; - } else { - if (test_and_clear_bit(HCI_CONNECTABLE, &hdev->dev_flags)) - changed = true; - } + mgmt_pending_foreach(MGMT_OP_SET_CONNECTABLE, index, mode_rsp, &match); - mgmt_pending_foreach(MGMT_OP_SET_CONNECTABLE, hdev, settings_rsp, - &match); + ev.val = connectable; - if (changed) - err = new_settings(hdev, match.sk); + ret = mgmt_event(MGMT_EV_CONNECTABLE, index, &ev, sizeof(ev), match.sk); if (match.sk) sock_put(match.sk); - return err; -} - -int mgmt_write_scan_failed(struct hci_dev *hdev, u8 scan, u8 status) -{ - u8 mgmt_err = mgmt_status(status); - - if (scan & SCAN_PAGE) - mgmt_pending_foreach(MGMT_OP_SET_CONNECTABLE, hdev, - cmd_status_rsp, &mgmt_err); - - if (scan & SCAN_INQUIRY) - mgmt_pending_foreach(MGMT_OP_SET_DISCOVERABLE, hdev, - cmd_status_rsp, &mgmt_err); - - return 0; + return ret; } -int mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent) +int mgmt_new_key(u16 index, struct link_key *key, u8 bonded) { - struct mgmt_ev_new_link_key ev; - - memset(&ev, 0, sizeof(ev)); + struct mgmt_ev_new_key *ev; + int err, total; - ev.store_hint = persistent; - bacpy(&ev.key.addr.bdaddr, &key->bdaddr); - ev.key.addr.type = MGMT_ADDR_BREDR; - ev.key.type = key->type; - memcpy(ev.key.val, key->val, 16); - ev.key.pin_len = key->pin_len; - - return mgmt_event(MGMT_EV_NEW_LINK_KEY, hdev, &ev, sizeof(ev), NULL); -} - -int mgmt_new_ltk(struct hci_dev *hdev, struct smp_ltk *key, u8 persistent) -{ - struct mgmt_ev_new_long_term_key ev; + total = sizeof(struct mgmt_ev_new_key) + key->dlen; + ev = kzalloc(total, GFP_ATOMIC); + if (!ev) + return -ENOMEM; - memset(&ev, 0, sizeof(ev)); + bacpy(&ev->key.bdaddr, &key->bdaddr); + ev->key.addr_type = key->addr_type; + ev->key.key_type = key->key_type; + memcpy(ev->key.val, key->val, 16); + ev->key.pin_len = key->pin_len; + ev->key.auth = key->auth; + ev->store_hint = bonded; + ev->key.dlen = key->dlen; - ev.store_hint = persistent; - bacpy(&ev.key.addr.bdaddr, &key->bdaddr); - ev.key.addr.type = key->bdaddr_type; - ev.key.authenticated = key->authenticated; - ev.key.enc_size = key->enc_size; - ev.key.ediv = key->ediv; + memcpy(ev->key.data, key->data, key->dlen); - if (key->type == HCI_SMP_LTK) - ev.key.master = 1; + err = mgmt_event(MGMT_EV_NEW_KEY, index, ev, total, NULL); - memcpy(ev.key.rand, key->rand, sizeof(key->rand)); - memcpy(ev.key.val, key->val, sizeof(key->val)); + kfree(ev); - return mgmt_event(MGMT_EV_NEW_LONG_TERM_KEY, hdev, &ev, sizeof(ev), - NULL); + return err; } -int mgmt_device_connected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u32 flags, u8 *name, u8 name_len, - u8 *dev_class) +int mgmt_connected(u16 index, bdaddr_t *bdaddr, u8 le) { - char buf[512]; - struct mgmt_ev_device_connected *ev = (void *) buf; - u16 eir_len = 0; + struct mgmt_ev_connected ev; - bacpy(&ev->addr.bdaddr, bdaddr); - ev->addr.type = link_to_mgmt(link_type, addr_type); - - ev->flags = __cpu_to_le32(flags); - - if (name_len > 0) - eir_len = eir_append_data(ev->eir, 0, EIR_NAME_COMPLETE, - name, name_len); - - if (dev_class && memcmp(dev_class, "\0\0\0", 3) != 0) - eir_len = eir_append_data(ev->eir, eir_len, - EIR_CLASS_OF_DEV, dev_class, 3); - - put_unaligned_le16(eir_len, &ev->eir_len); + bacpy(&ev.bdaddr, bdaddr); + ev.le = le; - return mgmt_event(MGMT_EV_DEVICE_CONNECTED, hdev, buf, - sizeof(*ev) + eir_len, NULL); + return mgmt_event(MGMT_EV_CONNECTED, index, &ev, sizeof(ev), NULL); } static void disconnect_rsp(struct pending_cmd *cmd, void *data) @@ -2956,11 +2633,9 @@ static void disconnect_rsp(struct pending_cmd *cmd, void *data) struct sock **sk = data; struct mgmt_rp_disconnect rp; - bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); - rp.addr.type = cp->addr.type; + bacpy(&rp.bdaddr, &cp->bdaddr); - cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, 0, &rp, - sizeof(rp)); + cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, &rp, sizeof(rp)); *sk = cmd->sk; sock_hold(*sk); @@ -2968,402 +2643,243 @@ static void disconnect_rsp(struct pending_cmd *cmd, void *data) mgmt_pending_remove(cmd); } -static void unpair_device_rsp(struct pending_cmd *cmd, void *data) -{ - struct hci_dev *hdev = data; - struct mgmt_cp_unpair_device *cp = cmd->param; - struct mgmt_rp_unpair_device rp; - - memset(&rp, 0, sizeof(rp)); - bacpy(&rp.addr.bdaddr, &cp->addr.bdaddr); - rp.addr.type = cp->addr.type; - - device_unpaired(hdev, &cp->addr.bdaddr, cp->addr.type, cmd->sk); - - cmd_complete(cmd->sk, cmd->index, cmd->opcode, 0, &rp, sizeof(rp)); - - mgmt_pending_remove(cmd); -} - -int mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type) +int mgmt_disconnected(u16 index, bdaddr_t *bdaddr) { - struct mgmt_addr_info ev; + struct mgmt_ev_disconnected ev; struct sock *sk = NULL; int err; - mgmt_pending_foreach(MGMT_OP_DISCONNECT, hdev, disconnect_rsp, &sk); + mgmt_pending_foreach(MGMT_OP_DISCONNECT, index, disconnect_rsp, &sk); bacpy(&ev.bdaddr, bdaddr); - ev.type = link_to_mgmt(link_type, addr_type); - err = mgmt_event(MGMT_EV_DEVICE_DISCONNECTED, hdev, &ev, sizeof(ev), - sk); + err = mgmt_event(MGMT_EV_DISCONNECTED, index, &ev, sizeof(ev), sk); if (sk) - sock_put(sk); - - mgmt_pending_foreach(MGMT_OP_UNPAIR_DEVICE, hdev, unpair_device_rsp, - hdev); + sock_put(sk); return err; } -int mgmt_disconnect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status) +int mgmt_disconnect_failed(u16 index) { - struct mgmt_rp_disconnect rp; struct pending_cmd *cmd; int err; - cmd = mgmt_pending_find(MGMT_OP_DISCONNECT, hdev); + cmd = mgmt_pending_find(MGMT_OP_DISCONNECT, index); if (!cmd) return -ENOENT; - bacpy(&rp.addr.bdaddr, bdaddr); - rp.addr.type = link_to_mgmt(link_type, addr_type); - - err = cmd_complete(cmd->sk, cmd->index, MGMT_OP_DISCONNECT, - mgmt_status(status), &rp, sizeof(rp)); + err = cmd_status(cmd->sk, index, MGMT_OP_DISCONNECT, EIO); mgmt_pending_remove(cmd); - mgmt_pending_foreach(MGMT_OP_UNPAIR_DEVICE, hdev, unpair_device_rsp, - hdev); return err; } -int mgmt_connect_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 status) +int mgmt_connect_failed(u16 index, bdaddr_t *bdaddr, u8 status) { struct mgmt_ev_connect_failed ev; - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = link_to_mgmt(link_type, addr_type); - ev.status = mgmt_status(status); + bacpy(&ev.bdaddr, bdaddr); + ev.status = status; - return mgmt_event(MGMT_EV_CONNECT_FAILED, hdev, &ev, sizeof(ev), NULL); + return mgmt_event(MGMT_EV_CONNECT_FAILED, index, &ev, sizeof(ev), NULL); } -int mgmt_pin_code_request(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 secure) +int mgmt_pin_code_request(u16 index, bdaddr_t *bdaddr) { struct mgmt_ev_pin_code_request ev; - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = MGMT_ADDR_BREDR; - ev.secure = secure; + BT_DBG("hci%u", index); + + bacpy(&ev.bdaddr, bdaddr); + ev.secure = 0; - return mgmt_event(MGMT_EV_PIN_CODE_REQUEST, hdev, &ev, sizeof(ev), - NULL); + return mgmt_event(MGMT_EV_PIN_CODE_REQUEST, index, &ev, sizeof(ev), + NULL); } -int mgmt_pin_code_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status) +int mgmt_pin_code_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status) { struct pending_cmd *cmd; struct mgmt_rp_pin_code_reply rp; int err; - cmd = mgmt_pending_find(MGMT_OP_PIN_CODE_REPLY, hdev); + cmd = mgmt_pending_find(MGMT_OP_PIN_CODE_REPLY, index); if (!cmd) return -ENOENT; - bacpy(&rp.addr.bdaddr, bdaddr); - rp.addr.type = MGMT_ADDR_BREDR; + bacpy(&rp.bdaddr, bdaddr); + rp.status = status; - err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_PIN_CODE_REPLY, - mgmt_status(status), &rp, sizeof(rp)); + err = cmd_complete(cmd->sk, index, MGMT_OP_PIN_CODE_REPLY, &rp, + sizeof(rp)); mgmt_pending_remove(cmd); return err; } -int mgmt_pin_code_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 status) +int mgmt_pin_code_neg_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status) { struct pending_cmd *cmd; struct mgmt_rp_pin_code_reply rp; int err; - cmd = mgmt_pending_find(MGMT_OP_PIN_CODE_NEG_REPLY, hdev); + cmd = mgmt_pending_find(MGMT_OP_PIN_CODE_NEG_REPLY, index); if (!cmd) return -ENOENT; - bacpy(&rp.addr.bdaddr, bdaddr); - rp.addr.type = MGMT_ADDR_BREDR; + bacpy(&rp.bdaddr, bdaddr); + rp.status = status; - err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_PIN_CODE_NEG_REPLY, - mgmt_status(status), &rp, sizeof(rp)); + err = cmd_complete(cmd->sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, &rp, + sizeof(rp)); mgmt_pending_remove(cmd); return err; } -int mgmt_user_confirm_request(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, __le32 value, - u8 confirm_hint) +int mgmt_user_confirm_request(u16 index, u8 event, + bdaddr_t *bdaddr, __le32 value) { struct mgmt_ev_user_confirm_request ev; + struct hci_conn *conn = NULL; + struct hci_dev *hdev; + u8 loc_cap, rem_cap, loc_mitm, rem_mitm; - BT_DBG("%s", hdev->name); - - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = link_to_mgmt(link_type, addr_type); - ev.confirm_hint = confirm_hint; - put_unaligned_le32(value, &ev.value); - - return mgmt_event(MGMT_EV_USER_CONFIRM_REQUEST, hdev, &ev, sizeof(ev), - NULL); -} + BT_DBG("hci%u", index); -int mgmt_user_passkey_request(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type) -{ - struct mgmt_ev_user_passkey_request ev; + hdev = hci_dev_get(index); - BT_DBG("%s", hdev->name); + if (!hdev) + return -ENODEV; - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = link_to_mgmt(link_type, addr_type); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, bdaddr); - return mgmt_event(MGMT_EV_USER_PASSKEY_REQUEST, hdev, &ev, sizeof(ev), - NULL); -} + ev.auto_confirm = 0; -static int user_pairing_resp_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status, - u8 opcode) -{ - struct pending_cmd *cmd; - struct mgmt_rp_user_confirm_reply rp; - int err; + if (!conn || event != HCI_EV_USER_CONFIRM_REQUEST) + goto no_auto_confirm; - cmd = mgmt_pending_find(opcode, hdev); - if (!cmd) - return -ENOENT; + loc_cap = (conn->io_capability == 0x04) ? 0x01 : conn->io_capability; + rem_cap = conn->remote_cap; + loc_mitm = conn->auth_type & 0x01; + rem_mitm = conn->remote_auth & 0x01; - bacpy(&rp.addr.bdaddr, bdaddr); - rp.addr.type = link_to_mgmt(link_type, addr_type); - err = cmd_complete(cmd->sk, hdev->id, opcode, mgmt_status(status), - &rp, sizeof(rp)); + if ((conn->auth_type & HCI_AT_DEDICATED_BONDING) && + conn->auth_initiator && rem_cap == 0x03) + ev.auto_confirm = 1; + else if (loc_cap == 0x01 && (rem_cap == 0x00 || rem_cap == 0x03)) { + if (!loc_mitm && !rem_mitm) + value = 0; + goto no_auto_confirm; + } - mgmt_pending_remove(cmd); - return err; -} + if ((!loc_mitm || rem_cap == 0x03) && (!rem_mitm || loc_cap == 0x03)) + ev.auto_confirm = 1; -int mgmt_user_confirm_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status) -{ - return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, - status, MGMT_OP_USER_CONFIRM_REPLY); -} +no_auto_confirm: + bacpy(&ev.bdaddr, bdaddr); + ev.event = event; + put_unaligned_le32(value, &ev.value); -int mgmt_user_confirm_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status) -{ - return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, - status, MGMT_OP_USER_CONFIRM_NEG_REPLY); -} + hci_dev_put(hdev); -int mgmt_user_passkey_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status) -{ - return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, - status, MGMT_OP_USER_PASSKEY_REPLY); + return mgmt_event(MGMT_EV_USER_CONFIRM_REQUEST, index, &ev, sizeof(ev), + NULL); } -int mgmt_user_passkey_neg_reply_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, - u8 link_type, u8 addr_type, u8 status) +int mgmt_user_passkey_request(u16 index, bdaddr_t *bdaddr) { - return user_pairing_resp_complete(hdev, bdaddr, link_type, addr_type, - status, MGMT_OP_USER_PASSKEY_NEG_REPLY); -} + struct mgmt_ev_user_passkey_request ev; -int mgmt_auth_failed(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 status) -{ - struct mgmt_ev_auth_failed ev; + BT_DBG("hci%u", index); - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = link_to_mgmt(link_type, addr_type); - ev.status = mgmt_status(status); + bacpy(&ev.bdaddr, bdaddr); - return mgmt_event(MGMT_EV_AUTH_FAILED, hdev, &ev, sizeof(ev), NULL); + return mgmt_event(MGMT_EV_USER_PASSKEY_REQUEST, index, &ev, sizeof(ev), + NULL); } -int mgmt_auth_enable_complete(struct hci_dev *hdev, u8 status) +static int confirm_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status, + u8 opcode) { - struct cmd_lookup match = { NULL, hdev }; - bool changed = false; - int err = 0; - - if (status) { - u8 mgmt_err = mgmt_status(status); - mgmt_pending_foreach(MGMT_OP_SET_LINK_SECURITY, hdev, - cmd_status_rsp, &mgmt_err); - return 0; - } - - if (test_bit(HCI_AUTH, &hdev->flags)) { - if (!test_and_set_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) - changed = true; - } else { - if (test_and_clear_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) - changed = true; - } + struct pending_cmd *cmd; + struct mgmt_rp_user_confirm_reply rp; + int err; - mgmt_pending_foreach(MGMT_OP_SET_LINK_SECURITY, hdev, settings_rsp, - &match); + cmd = mgmt_pending_find(opcode, index); + if (!cmd) + return -ENOENT; - if (changed) - err = new_settings(hdev, match.sk); + bacpy(&rp.bdaddr, bdaddr); + rp.status = status; + err = cmd_complete(cmd->sk, index, opcode, &rp, sizeof(rp)); - if (match.sk) - sock_put(match.sk); + mgmt_pending_remove(cmd); return err; } -static int clear_eir(struct hci_dev *hdev) -{ - struct hci_cp_write_eir cp; - - if (!(hdev->features[6] & LMP_EXT_INQ)) - return 0; - - memset(hdev->eir, 0, sizeof(hdev->eir)); - - memset(&cp, 0, sizeof(cp)); - - return hci_send_cmd(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp); -} - -int mgmt_ssp_enable_complete(struct hci_dev *hdev, u8 enable, u8 status) +int mgmt_user_confirm_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status) { - struct cmd_lookup match = { NULL, hdev }; - bool changed = false; - int err = 0; - - if (status) { - u8 mgmt_err = mgmt_status(status); - - if (enable && test_and_clear_bit(HCI_SSP_ENABLED, - &hdev->dev_flags)) - err = new_settings(hdev, NULL); - - mgmt_pending_foreach(MGMT_OP_SET_SSP, hdev, cmd_status_rsp, - &mgmt_err); - - return err; - } - - if (enable) { - if (!test_and_set_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) - changed = true; - } else { - if (test_and_clear_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) - changed = true; - } - - mgmt_pending_foreach(MGMT_OP_SET_SSP, hdev, settings_rsp, &match); - - if (changed) - err = new_settings(hdev, match.sk); - - if (match.sk) - sock_put(match.sk); - - if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) - update_eir(hdev); - else - clear_eir(hdev); - - return err; + return confirm_reply_complete(index, bdaddr, status, + MGMT_OP_USER_CONFIRM_REPLY); } -static void class_rsp(struct pending_cmd *cmd, void *data) +int mgmt_user_confirm_neg_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status) { - struct cmd_lookup *match = data; - - cmd_complete(cmd->sk, cmd->index, cmd->opcode, match->mgmt_status, - match->hdev->dev_class, 3); - - list_del(&cmd->list); - - if (match->sk == NULL) { - match->sk = cmd->sk; - sock_hold(match->sk); - } - - mgmt_pending_free(cmd); + return confirm_reply_complete(index, bdaddr, status, + MGMT_OP_USER_CONFIRM_NEG_REPLY); } -int mgmt_set_class_of_dev_complete(struct hci_dev *hdev, u8 *dev_class, - u8 status) +int mgmt_auth_failed(u16 index, bdaddr_t *bdaddr, u8 status) { - struct cmd_lookup match = { NULL, hdev, mgmt_status(status) }; - int err = 0; - - clear_bit(HCI_PENDING_CLASS, &hdev->dev_flags); - - mgmt_pending_foreach(MGMT_OP_SET_DEV_CLASS, hdev, class_rsp, &match); - mgmt_pending_foreach(MGMT_OP_ADD_UUID, hdev, class_rsp, &match); - mgmt_pending_foreach(MGMT_OP_REMOVE_UUID, hdev, class_rsp, &match); - - if (!status) - err = mgmt_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev, dev_class, - 3, NULL); + struct mgmt_ev_auth_failed ev; - if (match.sk) - sock_put(match.sk); + bacpy(&ev.bdaddr, bdaddr); + ev.status = status; - return err; + return mgmt_event(MGMT_EV_AUTH_FAILED, index, &ev, sizeof(ev), NULL); } -int mgmt_set_local_name_complete(struct hci_dev *hdev, u8 *name, u8 status) +int mgmt_set_local_name_complete(u16 index, u8 *name, u8 status) { struct pending_cmd *cmd; + struct hci_dev *hdev; struct mgmt_cp_set_local_name ev; - bool changed = false; - int err = 0; - - if (memcmp(name, hdev->dev_name, sizeof(hdev->dev_name)) != 0) { - memcpy(hdev->dev_name, name, sizeof(hdev->dev_name)); - changed = true; - } + int err; memset(&ev, 0, sizeof(ev)); memcpy(ev.name, name, HCI_MAX_NAME_LENGTH); - memcpy(ev.short_name, hdev->short_name, HCI_MAX_SHORT_NAME_LENGTH); - cmd = mgmt_pending_find(MGMT_OP_SET_LOCAL_NAME, hdev); + cmd = mgmt_pending_find(MGMT_OP_SET_LOCAL_NAME, index); if (!cmd) goto send_event; - /* Always assume that either the short or the complete name has - * changed if there was a pending mgmt command */ - changed = true; - if (status) { - err = cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, - mgmt_status(status)); + err = cmd_status(cmd->sk, index, MGMT_OP_SET_LOCAL_NAME, EIO); goto failed; } - err = cmd_complete(cmd->sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, 0, &ev, - sizeof(ev)); + hdev = hci_dev_get(index); + if (hdev) { + update_eir(hdev); + hci_dev_put(hdev); + } + + err = cmd_complete(cmd->sk, index, MGMT_OP_SET_LOCAL_NAME, &ev, + sizeof(ev)); if (err < 0) goto failed; send_event: - if (changed) - err = mgmt_event(MGMT_EV_LOCAL_NAME_CHANGED, hdev, &ev, - sizeof(ev), cmd ? cmd->sk : NULL); - - update_eir(hdev); + err = mgmt_event(MGMT_EV_LOCAL_NAME_CHANGED, index, &ev, sizeof(ev), + cmd ? cmd->sk : NULL); failed: if (cmd) @@ -3371,30 +2887,29 @@ int mgmt_set_local_name_complete(struct hci_dev *hdev, u8 *name, u8 status) return err; } -int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, - u8 *randomizer, u8 status) +int mgmt_read_local_oob_data_reply_complete(u16 index, u8 *hash, u8 *randomizer, + u8 status) { struct pending_cmd *cmd; int err; - BT_DBG("%s status %u", hdev->name, status); + BT_DBG("hci%u status %u", index, status); - cmd = mgmt_pending_find(MGMT_OP_READ_LOCAL_OOB_DATA, hdev); + cmd = mgmt_pending_find(MGMT_OP_READ_LOCAL_OOB_DATA, index); if (!cmd) return -ENOENT; if (status) { - err = cmd_status(cmd->sk, hdev->id, MGMT_OP_READ_LOCAL_OOB_DATA, - mgmt_status(status)); + err = cmd_status(cmd->sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, + EIO); } else { struct mgmt_rp_read_local_oob_data rp; memcpy(rp.hash, hash, sizeof(rp.hash)); memcpy(rp.randomizer, randomizer, sizeof(rp.randomizer)); - err = cmd_complete(cmd->sk, hdev->id, - MGMT_OP_READ_LOCAL_OOB_DATA, 0, &rp, - sizeof(rp)); + err = cmd_complete(cmd->sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, + &rp, sizeof(rp)); } mgmt_pending_remove(cmd); @@ -3402,198 +2917,182 @@ int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash, return err; } -int mgmt_le_enable_complete(struct hci_dev *hdev, u8 enable, u8 status) +void mgmt_read_rssi_complete(u16 index, s8 rssi, bdaddr_t *bdaddr, + u16 handle, u8 status) { - struct cmd_lookup match = { NULL, hdev }; - bool changed = false; - int err = 0; - - if (status) { - u8 mgmt_err = mgmt_status(status); + struct mgmt_ev_rssi_update ev; + struct hci_conn *conn; + struct hci_dev *hdev; - if (enable && test_and_clear_bit(HCI_LE_ENABLED, - &hdev->dev_flags)) - err = new_settings(hdev, NULL); + if (status) + return; - mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, - cmd_status_rsp, &mgmt_err); + hdev = hci_dev_get(index); + conn = hci_conn_hash_lookup_handle(hdev, handle); - return err; - } + if (!conn) + return; - if (enable) { - if (!test_and_set_bit(HCI_LE_ENABLED, &hdev->dev_flags)) - changed = true; + BT_DBG("rssi_update_thresh_exceed : %d ", + conn->rssi_update_thresh_exceed); + BT_DBG("RSSI Threshold : %d , recvd RSSI : %d ", + conn->rssi_threshold, rssi); + + if (conn->rssi_update_thresh_exceed == 1) { + BT_DBG("rssi_update_thresh_exceed == 1"); + if (rssi > conn->rssi_threshold) { + memset(&ev, 0, sizeof(ev)); + bacpy(&ev.bdaddr, bdaddr); + ev.rssi = rssi; + mgmt_event(MGMT_EV_RSSI_UPDATE, index, &ev, + sizeof(ev), NULL); + } else { + hci_conn_set_rssi_reporter(conn, conn->rssi_threshold, + conn->rssi_update_interval, + conn->rssi_update_thresh_exceed); + } } else { - if (test_and_clear_bit(HCI_LE_ENABLED, &hdev->dev_flags)) - changed = true; + BT_DBG("rssi_update_thresh_exceed == 0"); + if (rssi < conn->rssi_threshold) { + memset(&ev, 0, sizeof(ev)); + bacpy(&ev.bdaddr, bdaddr); + ev.rssi = rssi; + mgmt_event(MGMT_EV_RSSI_UPDATE, index, &ev, + sizeof(ev), NULL); + } else { + hci_conn_set_rssi_reporter(conn, conn->rssi_threshold, + conn->rssi_update_interval, + conn->rssi_update_thresh_exceed); + } } - - mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, settings_rsp, &match); - - if (changed) - err = new_settings(hdev, match.sk); - - if (match.sk) - sock_put(match.sk); - - return err; } -int mgmt_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, u8 *dev_class, s8 rssi, u8 cfm_name, u8 - ssp, u8 *eir, u16 eir_len) -{ - char buf[512]; - struct mgmt_ev_device_found *ev = (void *) buf; - size_t ev_size; - - /* Leave 5 bytes for a potential CoD field */ - if (sizeof(*ev) + eir_len + 5 > sizeof(buf)) - return -EINVAL; - - memset(buf, 0, sizeof(buf)); - bacpy(&ev->addr.bdaddr, bdaddr); - ev->addr.type = link_to_mgmt(link_type, addr_type); - ev->rssi = rssi; - if (cfm_name) - ev->flags[0] |= MGMT_DEV_FOUND_CONFIRM_NAME; - if (!ssp) - ev->flags[0] |= MGMT_DEV_FOUND_LEGACY_PAIRING; +int mgmt_device_found(u16 index, bdaddr_t *bdaddr, u8 type, u8 le, + u8 *dev_class, s8 rssi, u8 eir_len, u8 *eir) +{ + struct mgmt_ev_device_found ev; + struct hci_dev *hdev; + int err; - if (eir_len > 0) - memcpy(ev->eir, eir, eir_len); + BT_DBG("le: %d", le); - if (dev_class && !eir_has_data_type(ev->eir, eir_len, EIR_CLASS_OF_DEV)) - eir_len = eir_append_data(ev->eir, eir_len, EIR_CLASS_OF_DEV, - dev_class, 3); + memset(&ev, 0, sizeof(ev)); - put_unaligned_le16(eir_len, &ev->eir_len); + bacpy(&ev.bdaddr, bdaddr); + ev.rssi = rssi; + ev.type = type; + ev.le = le; - ev_size = sizeof(*ev) + eir_len; + if (dev_class) + memcpy(ev.dev_class, dev_class, sizeof(ev.dev_class)); - return mgmt_event(MGMT_EV_DEVICE_FOUND, hdev, ev, ev_size, NULL); -} + if (eir && eir_len) + memcpy(ev.eir, eir, eir_len); -int mgmt_remote_name(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, - u8 addr_type, s8 rssi, u8 *name, u8 name_len) -{ - struct mgmt_ev_device_found *ev; - char buf[sizeof(*ev) + HCI_MAX_NAME_LENGTH + 2]; - u16 eir_len; + err = mgmt_event(MGMT_EV_DEVICE_FOUND, index, &ev, sizeof(ev), NULL); - ev = (struct mgmt_ev_device_found *) buf; + if (err < 0) + return err; - memset(buf, 0, sizeof(buf)); + hdev = hci_dev_get(index); - bacpy(&ev->addr.bdaddr, bdaddr); - ev->addr.type = link_to_mgmt(link_type, addr_type); - ev->rssi = rssi; + if (!hdev) + return 0; - eir_len = eir_append_data(ev->eir, 0, EIR_NAME_COMPLETE, name, - name_len); + if (hdev->disco_state == SCAN_IDLE) + goto done; - put_unaligned_le16(eir_len, &ev->eir_len); + hdev->disco_int_count++; + + if (hdev->disco_int_count >= hdev->disco_int_phase) { + /* Inquiry scan for General Discovery LAP */ + struct hci_cp_inquiry cp = {{0x33, 0x8b, 0x9e}, 4, 0}; + struct hci_cp_le_set_scan_enable le_cp = {0, 0}; + + hdev->disco_int_phase *= 2; + hdev->disco_int_count = 0; + if (hdev->disco_state == SCAN_LE) { + /* cancel LE scan */ + hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(le_cp), &le_cp); + /* start BR scan */ + cp.num_rsp = (u8) hdev->disco_int_phase; + hci_send_cmd(hdev, HCI_OP_INQUIRY, + sizeof(cp), &cp); + hdev->disco_state = SCAN_BR; + del_timer_sync(&hdev->disco_le_timer); + } + } - return mgmt_event(MGMT_EV_DEVICE_FOUND, hdev, ev, - sizeof(*ev) + eir_len, NULL); +done: + hci_dev_put(hdev); + return 0; } -int mgmt_start_discovery_failed(struct hci_dev *hdev, u8 status) -{ - struct pending_cmd *cmd; - u8 type; - int err; - - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); - cmd = mgmt_pending_find(MGMT_OP_START_DISCOVERY, hdev); - if (!cmd) - return -ENOENT; +int mgmt_remote_name(u16 index, bdaddr_t *bdaddr, u8 status, u8 *name) +{ + struct mgmt_ev_remote_name ev; - type = hdev->discovery.type; + memset(&ev, 0, sizeof(ev)); - err = cmd_complete(cmd->sk, hdev->id, cmd->opcode, mgmt_status(status), - &type, sizeof(type)); - mgmt_pending_remove(cmd); + bacpy(&ev.bdaddr, bdaddr); + ev.status = status; + memcpy(ev.name, name, HCI_MAX_NAME_LENGTH); - return err; + return mgmt_event(MGMT_EV_REMOTE_NAME, index, &ev, sizeof(ev), NULL); } -int mgmt_stop_discovery_failed(struct hci_dev *hdev, u8 status) +int mgmt_encrypt_change(u16 index, bdaddr_t *bdaddr, u8 status) { - struct pending_cmd *cmd; - int err; + struct mgmt_ev_encrypt_change ev; - cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, hdev); - if (!cmd) - return -ENOENT; + BT_DBG("hci%u", index); - err = cmd_complete(cmd->sk, hdev->id, cmd->opcode, mgmt_status(status), - &hdev->discovery.type, sizeof(hdev->discovery.type)); - mgmt_pending_remove(cmd); + bacpy(&ev.bdaddr, bdaddr); + ev.status = status; - return err; + return mgmt_event(MGMT_EV_ENCRYPT_CHANGE, index, &ev, sizeof(ev), + NULL); } -int mgmt_discovering(struct hci_dev *hdev, u8 discovering) +int mgmt_remote_class(u16 index, bdaddr_t *bdaddr, u8 dev_class[3]) { - struct mgmt_ev_discovering ev; - struct pending_cmd *cmd; - - BT_DBG("%s discovering %u", hdev->name, discovering); - - if (discovering) - cmd = mgmt_pending_find(MGMT_OP_START_DISCOVERY, hdev); - else - cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, hdev); - - if (cmd != NULL) { - u8 type = hdev->discovery.type; - - cmd_complete(cmd->sk, hdev->id, cmd->opcode, 0, &type, - sizeof(type)); - mgmt_pending_remove(cmd); - } + struct mgmt_ev_remote_class ev; memset(&ev, 0, sizeof(ev)); - ev.type = hdev->discovery.type; - ev.discovering = discovering; - return mgmt_event(MGMT_EV_DISCOVERING, hdev, &ev, sizeof(ev), NULL); + bacpy(&ev.bdaddr, bdaddr); + memcpy(ev.dev_class, dev_class, 3); + + return mgmt_event(MGMT_EV_REMOTE_CLASS, index, &ev, sizeof(ev), NULL); } -int mgmt_device_blocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) +int mgmt_remote_version(u16 index, bdaddr_t *bdaddr, u8 ver, u16 mnf, + u16 sub_ver) { - struct pending_cmd *cmd; - struct mgmt_ev_device_blocked ev; + struct mgmt_ev_remote_version ev; - cmd = mgmt_pending_find(MGMT_OP_BLOCK_DEVICE, hdev); + memset(&ev, 0, sizeof(ev)); - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = type; + bacpy(&ev.bdaddr, bdaddr); + ev.lmp_ver = ver; + ev.manufacturer = mnf; + ev.lmp_subver = sub_ver; - return mgmt_event(MGMT_EV_DEVICE_BLOCKED, hdev, &ev, sizeof(ev), - cmd ? cmd->sk : NULL); + return mgmt_event(MGMT_EV_REMOTE_VERSION, index, &ev, sizeof(ev), NULL); } -int mgmt_device_unblocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) +int mgmt_remote_features(u16 index, bdaddr_t *bdaddr, u8 features[8]) { - struct pending_cmd *cmd; - struct mgmt_ev_device_unblocked ev; + struct mgmt_ev_remote_features ev; - cmd = mgmt_pending_find(MGMT_OP_UNBLOCK_DEVICE, hdev); + memset(&ev, 0, sizeof(ev)); - bacpy(&ev.addr.bdaddr, bdaddr); - ev.addr.type = type; + bacpy(&ev.bdaddr, bdaddr); + memcpy(ev.features, features, sizeof(ev.features)); - return mgmt_event(MGMT_EV_DEVICE_UNBLOCKED, hdev, &ev, sizeof(ev), - cmd ? cmd->sk : NULL); + return mgmt_event(MGMT_EV_REMOTE_FEATURES, index, &ev, sizeof(ev), + NULL); } - -module_param(enable_hs, bool, 0644); -MODULE_PARM_DESC(enable_hs, "Enable High Speed support"); - -module_param(enable_le, bool, 0644); -MODULE_PARM_DESC(enable_le, "Enable Low Energy support"); diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 9652707b1a059e8c09fafef9d815b94573502cb1..1390ecaa7f2e6925aa3b0a5f5e76300752d32c8a 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,9 @@ #include #define VERSION "1.11" +/* 1 Byte DLCI, 1 Byte Control filed, 2 Bytes Length, 1 Byte for Credits, + * 1 Byte FCS */ +#define RFCOMM_HDR_SIZE 6 static bool disable_cfc; static bool l2cap_ertm; @@ -62,6 +66,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); @@ -81,7 +86,9 @@ static void rfcomm_process_connect(struct rfcomm_session *s); static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst, u8 sec_level, - int *err); + int *err, + u8 channel, + struct rfcomm_dlc *d); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); static void rfcomm_session_del(struct rfcomm_session *s); @@ -115,10 +122,17 @@ static void rfcomm_session_del(struct rfcomm_session *s); #define __get_rpn_stop_bits(line) (((line) >> 2) & 0x1) #define __get_rpn_parity(line) (((line) >> 3) & 0x7) +struct rfcomm_sock_release_work { + struct work_struct work; + struct socket *sock; + int state; +}; + static inline void rfcomm_schedule(void) { if (!rfcomm_thread) return; + set_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event); wake_up_process(rfcomm_thread); } @@ -230,8 +244,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 +258,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) @@ -377,11 +390,13 @@ static void rfcomm_dlc_unlink(struct rfcomm_dlc *d) static struct rfcomm_dlc *rfcomm_dlc_get(struct rfcomm_session *s, u8 dlci) { struct rfcomm_dlc *d; + struct list_head *p; - list_for_each_entry(d, &s->dlcs, list) + list_for_each(p, &s->dlcs) { + d = list_entry(p, struct rfcomm_dlc, list); if (d->dlci == dlci) return d; - + } return NULL; } @@ -402,31 +417,31 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, s = rfcomm_session_get(src, dst); if (!s) { - s = rfcomm_session_create(src, dst, d->sec_level, &err); + s = rfcomm_session_create(src, dst, + d->sec_level, &err, channel, d); if (!s) return err; - } - - dlci = __dlci(!s->initiator, channel); + } else { + dlci = __dlci(!s->initiator, channel); - /* Check if DLCI already exists */ - if (rfcomm_dlc_get(s, dlci)) - return -EBUSY; + /* Check if DLCI already exists */ + if (rfcomm_dlc_get(s, dlci)) + return -EBUSY; - rfcomm_dlc_clear_state(d); + rfcomm_dlc_clear_state(d); - d->dlci = dlci; - d->addr = __addr(s->initiator, dlci); - d->priority = 7; - - d->state = BT_CONFIG; - rfcomm_dlc_link(s, d); + d->dlci = dlci; + d->addr = __addr(s->initiator, dlci); + d->priority = 7; - d->out = 1; + d->state = BT_CONFIG; + rfcomm_dlc_link(s, d); - d->mtu = s->mtu; - d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc; + d->out = 1; + d->mtu = s->mtu; + d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc; + } if (s->state == BT_CONNECTED) { if (rfcomm_check_security(d)) rfcomm_send_pn(s, 1, d); @@ -622,9 +637,25 @@ static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state) return s; } +static void rfcomm_sock_release_worker(struct work_struct *work) +{ + struct rfcomm_sock_release_work *release_work = + container_of(work, struct rfcomm_sock_release_work, work); + + BT_DBG("sock %p", release_work->sock); + + sock_release(release_work->sock); + if (release_work->state != BT_LISTEN) + module_put(THIS_MODULE); + + kfree(release_work); +} + static void rfcomm_session_del(struct rfcomm_session *s) { int state = s->state; + struct socket *sock = s->sock; + struct rfcomm_sock_release_work *release_work; BT_DBG("session %p state %ld", s, s->state); @@ -634,11 +665,19 @@ static void rfcomm_session_del(struct rfcomm_session *s) rfcomm_send_disc(s, 0); rfcomm_session_clear_timer(s); - sock_release(s->sock); + kfree(s); - if (state != BT_LISTEN) - module_put(THIS_MODULE); + release_work = kzalloc(sizeof(*release_work), GFP_ATOMIC); + if (release_work) { + INIT_WORK(&release_work->work, rfcomm_sock_release_worker); + release_work->sock = sock; + release_work->state = state; + + if (!schedule_work(&release_work->work)) + kfree(release_work); + } + } static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst) @@ -682,12 +721,15 @@ static void rfcomm_session_close(struct rfcomm_session *s, int err) static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst, u8 sec_level, - int *err) + int *err, + u8 channel, + struct rfcomm_dlc *d) { struct rfcomm_session *s = NULL; struct sockaddr_l2 addr; struct socket *sock; struct sock *sk; + u8 dlci; BT_DBG("%s %s", batostr(src), batostr(dst)); @@ -706,10 +748,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); @@ -724,11 +766,30 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, addr.l2_family = AF_BLUETOOTH; addr.l2_psm = cpu_to_le16(RFCOMM_PSM); addr.l2_cid = 0; + dlci = __dlci(!s->initiator, channel); + + /* Check if DLCI already exists */ + if (rfcomm_dlc_get(s, dlci)) + return NULL; + + rfcomm_dlc_clear_state(d); + + d->dlci = dlci; + d->addr = __addr(s->initiator, dlci); + d->priority = 7; + + d->state = BT_CONFIG; + rfcomm_dlc_link(s, d); + + d->out = 1; + + d->mtu = s->mtu; + d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc; *err = kernel_connect(sock, (struct sockaddr *) &addr, sizeof(addr), O_NONBLOCK); if (*err == 0 || *err == -EINPROGRESS) return s; - - rfcomm_session_del(s); + BT_ERR("error ret is %d, going to delete session", *err); + rfcomm_dlc_unlink(d); return NULL; failed: @@ -748,6 +809,7 @@ void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *d /* ---- RFCOMM frame sending ---- */ static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len) { + struct socket *sock = s->sock; struct kvec iv = { data, len }; struct msghdr msg; @@ -755,14 +817,7 @@ static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len) memset(&msg, 0, sizeof(msg)); - return kernel_sendmsg(s->sock, &msg, &iv, 1, len); -} - -static int rfcomm_send_cmd(struct rfcomm_session *s, struct rfcomm_cmd *cmd) -{ - BT_DBG("%p cmd %u", s, cmd->ctrl); - - return rfcomm_send_frame(s, (void *) cmd, sizeof(*cmd)); + return kernel_sendmsg(sock, &msg, &iv, 1, len); } static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci) @@ -776,7 +831,7 @@ static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci) cmd.len = __len8(0); cmd.fcs = __fcs2((u8 *) &cmd); - return rfcomm_send_cmd(s, &cmd); + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); } static int rfcomm_send_ua(struct rfcomm_session *s, u8 dlci) @@ -790,7 +845,7 @@ static int rfcomm_send_ua(struct rfcomm_session *s, u8 dlci) cmd.len = __len8(0); cmd.fcs = __fcs2((u8 *) &cmd); - return rfcomm_send_cmd(s, &cmd); + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); } static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci) @@ -804,7 +859,7 @@ static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci) cmd.len = __len8(0); cmd.fcs = __fcs2((u8 *) &cmd); - return rfcomm_send_cmd(s, &cmd); + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); } static int rfcomm_queue_disc(struct rfcomm_dlc *d) @@ -840,7 +895,7 @@ static int rfcomm_send_dm(struct rfcomm_session *s, u8 dlci) cmd.len = __len8(0); cmd.fcs = __fcs2((u8 *) &cmd); - return rfcomm_send_cmd(s, &cmd); + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); } static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type) @@ -1163,18 +1218,12 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) break; case BT_DISCONN: - /* rfcomm_session_put is called later so don't do - * anything here otherwise we will mess up the session - * reference counter: - * - * (a) when we are the initiator dlc_unlink will drive - * the reference counter to 0 (there is no initial put - * after session_add) - * - * (b) when we are not the initiator rfcomm_rx_process - * will explicitly call put to balance the initial hold - * done after session add. - */ + /* When socket is closed and we are not RFCOMM + * initiator rfcomm_process_rx already calls + * rfcomm_session_put() */ + if (s->sock->sk->sk_state != BT_CLOSED) + if (list_empty(&s->dlcs)) + rfcomm_session_put(s); break; } } @@ -1250,7 +1299,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); @@ -1264,7 +1312,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); } @@ -1812,11 +1860,6 @@ static inline void rfcomm_process_dlcs(struct rfcomm_session *s) continue; } - if (test_bit(RFCOMM_ENC_DROP, &d->flags)) { - __rfcomm_dlc_close(d, ECONNREFUSED); - continue; - } - if (test_and_clear_bit(RFCOMM_AUTH_ACCEPT, &d->flags)) { rfcomm_dlc_clear_timer(d); if (d->out) { @@ -1907,9 +1950,10 @@ static inline void rfcomm_accept_connection(struct rfcomm_session *s) rfcomm_session_hold(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; + * L2CAP MTU minus UIH header and FCS. + * Need to accomodate 1 Byte credits information */ + s->mtu = min(l2cap_pi(nsock->sk)->omtu, + l2cap_pi(nsock->sk)->imtu) - RFCOMM_HDR_SIZE; rfcomm_schedule(); } else @@ -1927,8 +1971,9 @@ static inline void rfcomm_check_connection(struct rfcomm_session *s) s->state = BT_CONNECT; /* 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; + * L2CAP MTU minus UIH header, Credits and FCS. */ + s->mtu = min(l2cap_pi(sk)->omtu, l2cap_pi(sk)->imtu) - + RFCOMM_HDR_SIZE; rfcomm_send_sabm(s, 0); break; @@ -2011,7 +2056,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 */ @@ -2054,18 +2099,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 +2138,7 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) if (test_and_clear_bit(RFCOMM_SEC_PENDING, &d->flags)) { rfcomm_dlc_clear_timer(d); if (status || encrypt == 0x00) { - set_bit(RFCOMM_ENC_DROP, &d->flags); + __rfcomm_dlc_close(d, ECONNREFUSED); continue; } } @@ -2103,7 +2149,7 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) rfcomm_dlc_set_timer(d, RFCOMM_AUTH_TIMEOUT); continue; } else if (d->sec_level == BT_SECURITY_HIGH) { - set_bit(RFCOMM_ENC_DROP, &d->flags); + __rfcomm_dlc_close(d, ECONNREFUSED); continue; } } @@ -2111,7 +2157,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); @@ -2130,13 +2176,15 @@ static struct hci_cb rfcomm_cb = { static int rfcomm_dlc_debugfs_show(struct seq_file *f, void *x) { struct rfcomm_session *s; + struct list_head *pp, *p; rfcomm_lock(); - list_for_each_entry(s, &session_list, list) { - struct rfcomm_dlc *d; - list_for_each_entry(d, &s->dlcs, list) { + list_for_each(p, &session_list) { + s = list_entry(p, struct rfcomm_session, list); + list_for_each(pp, &s->dlcs) { struct sock *sk = s->sock->sk; + struct rfcomm_dlc *d = list_entry(pp, struct rfcomm_dlc, list); seq_printf(f, "%s %s %ld %d %d %d %d\n", batostr(&bt_sk(sk)->src), diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index a55a43e9f70e9d35181f1272c3897ac43856a6d9..66cc1f0c3df85c5f5b18c8f179ef8434d23f4cf8 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -42,9 +42,9 @@ #include #include #include -#include #include +#include #include #include @@ -264,8 +264,6 @@ static void rfcomm_sock_init(struct sock *sk, struct sock *parent) pi->sec_level = rfcomm_pi(parent)->sec_level; pi->role_switch = rfcomm_pi(parent)->role_switch; - - security_sk_clone(parent, sk); } else { pi->dlc->defer_setup = 0; @@ -369,7 +367,7 @@ static int rfcomm_sock_bind(struct socket *sock, struct sockaddr *addr, int addr goto done; } - write_lock(&rfcomm_sk_list.lock); + write_lock_bh(&rfcomm_sk_list.lock); if (sa->rc_channel && __rfcomm_get_sock_by_addr(sa->rc_channel, &sa->rc_bdaddr)) { err = -EADDRINUSE; @@ -380,7 +378,7 @@ static int rfcomm_sock_bind(struct socket *sock, struct sockaddr *addr, int addr sk->sk_state = BT_BOUND; } - write_unlock(&rfcomm_sk_list.lock); + write_unlock_bh(&rfcomm_sk_list.lock); done: release_sock(sk); @@ -454,7 +452,7 @@ static int rfcomm_sock_listen(struct socket *sock, int backlog) err = -EINVAL; - write_lock(&rfcomm_sk_list.lock); + write_lock_bh(&rfcomm_sk_list.lock); for (channel = 1; channel < 31; channel++) if (!__rfcomm_get_sock_by_addr(channel, src)) { @@ -463,7 +461,7 @@ static int rfcomm_sock_listen(struct socket *sock, int backlog) break; } - write_unlock(&rfcomm_sk_list.lock); + write_unlock_bh(&rfcomm_sk_list.lock); if (err < 0) goto done; @@ -487,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; @@ -498,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; } @@ -519,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) @@ -599,8 +597,6 @@ static int rfcomm_sock_sendmsg(struct kiocb *iocb, struct socket *sock, break; } - skb->priority = sk->sk_priority; - err = rfcomm_dlc_send(d, skb); if (err < 0) { kfree_skb(skb); @@ -683,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); @@ -746,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; @@ -790,9 +785,10 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, char __u break; } - memset(&cinfo, 0, sizeof(cinfo)); - cinfo.hci_handle = conn->hcon->handle; - memcpy(cinfo.dev_class, conn->hcon->dev_class, 3); + l2cap_sk = rfcomm_pi(sk)->dlc->session->sock->sk; + + 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)) @@ -955,8 +951,6 @@ int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc * if (!sk) goto done; - bt_sock_reclassify_lock(sk, BTPROTO_RFCOMM); - rfcomm_sock_init(sk, parent); bacpy(&bt_sk(sk)->src, &src); bacpy(&bt_sk(sk)->dst, &dst); @@ -983,7 +977,7 @@ static int rfcomm_sock_debugfs_show(struct seq_file *f, void *p) struct sock *sk; struct hlist_node *node; - read_lock(&rfcomm_sk_list.lock); + read_lock_bh(&rfcomm_sk_list.lock); sk_for_each(sk, node, &rfcomm_sk_list.head) { seq_printf(f, "%s %s %d %d\n", @@ -992,7 +986,7 @@ static int rfcomm_sock_debugfs_show(struct seq_file *f, void *p) sk->sk_state, rfcomm_pi(sk)->channel); } - read_unlock(&rfcomm_sk_list.lock); + read_unlock_bh(&rfcomm_sk_list.lock); return 0; } diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index 4bf54b37725565031211110253a07920d52df433..e18781ce49786d474b8bf16529084a87d2fc2cee 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -26,6 +26,7 @@ */ #include +#include #include #include @@ -34,7 +35,6 @@ #include #include #include -#include #include #include @@ -66,7 +66,7 @@ struct rfcomm_dev { struct rfcomm_dlc *dlc; struct tty_struct *tty; wait_queue_head_t wait; - struct work_struct wakeup_task; + struct tasklet_struct wakeup_task; struct device *tty_dev; @@ -76,13 +76,13 @@ struct rfcomm_dev { }; static LIST_HEAD(rfcomm_dev_list); -static DEFINE_SPINLOCK(rfcomm_dev_lock); +static DEFINE_RWLOCK(rfcomm_dev_lock); static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb); static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err); static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig); -static void rfcomm_tty_wakeup(struct work_struct *work); +static void rfcomm_tty_wakeup(unsigned long arg); /* ---- Device functions ---- */ static void rfcomm_dev_destruct(struct rfcomm_dev *dev) @@ -134,10 +134,13 @@ static inline void rfcomm_dev_put(struct rfcomm_dev *dev) static struct rfcomm_dev *__rfcomm_dev_get(int id) { struct rfcomm_dev *dev; + struct list_head *p; - list_for_each_entry(dev, &rfcomm_dev_list, list) + list_for_each(p, &rfcomm_dev_list) { + dev = list_entry(p, struct rfcomm_dev, list); if (dev->id == id) return dev; + } return NULL; } @@ -146,7 +149,7 @@ static inline struct rfcomm_dev *rfcomm_dev_get(int id) { struct rfcomm_dev *dev; - spin_lock(&rfcomm_dev_lock); + read_lock(&rfcomm_dev_lock); dev = __rfcomm_dev_get(id); @@ -157,7 +160,7 @@ static inline struct rfcomm_dev *rfcomm_dev_get(int id) rfcomm_dev_hold(dev); } - spin_unlock(&rfcomm_dev_lock); + read_unlock(&rfcomm_dev_lock); return dev; } @@ -195,8 +198,8 @@ static DEVICE_ATTR(channel, S_IRUGO, show_channel, NULL); static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) { - struct rfcomm_dev *dev, *entry; - struct list_head *head = &rfcomm_dev_list; + struct rfcomm_dev *dev; + struct list_head *head = &rfcomm_dev_list, *p; int err = 0; BT_DBG("id %d channel %d", req->dev_id, req->channel); @@ -205,22 +208,24 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) if (!dev) return -ENOMEM; - spin_lock(&rfcomm_dev_lock); + write_lock_bh(&rfcomm_dev_lock); if (req->dev_id < 0) { dev->id = 0; - list_for_each_entry(entry, &rfcomm_dev_list, list) { - if (entry->id != dev->id) + list_for_each(p, &rfcomm_dev_list) { + if (list_entry(p, struct rfcomm_dev, list)->id != dev->id) break; dev->id++; - head = &entry->list; + head = p; } } else { dev->id = req->dev_id; - list_for_each_entry(entry, &rfcomm_dev_list, list) { + list_for_each(p, &rfcomm_dev_list) { + struct rfcomm_dev *entry = list_entry(p, struct rfcomm_dev, list); + if (entry->id == dev->id) { err = -EADDRINUSE; goto out; @@ -229,7 +234,7 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) if (entry->id > dev->id - 1) break; - head = &entry->list; + head = p; } } @@ -253,7 +258,7 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) atomic_set(&dev->opened, 0); init_waitqueue_head(&dev->wait); - INIT_WORK(&dev->wakeup_task, rfcomm_tty_wakeup); + tasklet_init(&dev->wakeup_task, rfcomm_tty_wakeup, (unsigned long) dev); skb_queue_head_init(&dev->pending); @@ -290,7 +295,7 @@ static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) __module_get(THIS_MODULE); out: - spin_unlock(&rfcomm_dev_lock); + write_unlock_bh(&rfcomm_dev_lock); if (err < 0) goto free; @@ -327,9 +332,9 @@ static void rfcomm_dev_del(struct rfcomm_dev *dev) if (atomic_read(&dev->opened) > 0) return; - spin_lock(&rfcomm_dev_lock); + write_lock_bh(&rfcomm_dev_lock); list_del_init(&dev->list); - spin_unlock(&rfcomm_dev_lock); + write_unlock_bh(&rfcomm_dev_lock); rfcomm_dev_put(dev); } @@ -347,7 +352,7 @@ static void rfcomm_wfree(struct sk_buff *skb) struct rfcomm_dev *dev = (void *) skb->sk; atomic_sub(skb->truesize, &dev->wmem_alloc); if (test_bit(RFCOMM_TTY_ATTACHED, &dev->flags)) - queue_work(system_nrt_wq, &dev->wakeup_task); + tasklet_schedule(&dev->wakeup_task); rfcomm_dev_put(dev); } @@ -451,9 +456,9 @@ static int rfcomm_release_dev(void __user *arg) static int rfcomm_get_dev_list(void __user *arg) { - struct rfcomm_dev *dev; struct rfcomm_dev_list_req *dl; struct rfcomm_dev_info *di; + struct list_head *p; int n = 0, size, err; u16 dev_num; @@ -473,9 +478,10 @@ static int rfcomm_get_dev_list(void __user *arg) di = dl->dev_info; - spin_lock(&rfcomm_dev_lock); + read_lock_bh(&rfcomm_dev_lock); - list_for_each_entry(dev, &rfcomm_dev_list, list) { + list_for_each(p, &rfcomm_dev_list) { + struct rfcomm_dev *dev = list_entry(p, struct rfcomm_dev, list); if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) continue; (di + n)->id = dev->id; @@ -488,7 +494,7 @@ static int rfcomm_get_dev_list(void __user *arg) break; } - spin_unlock(&rfcomm_dev_lock); + read_unlock_bh(&rfcomm_dev_lock); dl->dev_num = n; size = sizeof(*dl) + n * sizeof(*di); @@ -630,10 +636,9 @@ static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig) } /* ---- TTY functions ---- */ -static void rfcomm_tty_wakeup(struct work_struct *work) +static void rfcomm_tty_wakeup(unsigned long arg) { - struct rfcomm_dev *dev = container_of(work, struct rfcomm_dev, - wakeup_task); + struct rfcomm_dev *dev = (void *) arg; struct tty_struct *tty = dev->tty; if (!tty) return; @@ -758,7 +763,7 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp) rfcomm_dlc_close(dev->dlc, 0); clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags); - cancel_work_sync(&dev->wakeup_task); + tasklet_kill(&dev->wakeup_task); rfcomm_dlc_lock(dev->dlc); tty->driver_data = NULL; @@ -766,9 +771,9 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp) rfcomm_dlc_unlock(dev->dlc); if (test_bit(RFCOMM_TTY_RELEASED, &dev->flags)) { - spin_lock(&rfcomm_dev_lock); + write_lock_bh(&rfcomm_dev_lock); list_del_init(&dev->list); - spin_unlock(&rfcomm_dev_lock); + write_unlock_bh(&rfcomm_dev_lock); rfcomm_dev_put(dev); } @@ -1151,12 +1156,11 @@ static const struct tty_operations rfcomm_ops = { int __init rfcomm_init_ttys(void) { - int error; - rfcomm_tty_driver = alloc_tty_driver(RFCOMM_TTY_PORTS); if (!rfcomm_tty_driver) - return -ENOMEM; + return -1; + rfcomm_tty_driver->owner = THIS_MODULE; rfcomm_tty_driver->driver_name = "rfcomm"; rfcomm_tty_driver->name = "rfcomm"; rfcomm_tty_driver->major = RFCOMM_TTY_MAJOR; @@ -1169,11 +1173,10 @@ int __init rfcomm_init_ttys(void) rfcomm_tty_driver->init_termios.c_lflag &= ~ICANON; tty_set_operations(rfcomm_tty_driver, &rfcomm_ops); - error = tty_register_driver(rfcomm_tty_driver); - if (error) { + if (tty_register_driver(rfcomm_tty_driver)) { BT_ERR("Can't register RFCOMM TTY driver"); put_tty_driver(rfcomm_tty_driver); - return error; + return -1; } BT_INFO("RFCOMM TTY layer initialized"); diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 458cec074114d51a4814084fb40bdc973de50fbd..f090a77cf3c46ca2952a46bd12e3fbfdf0cf803f 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -1,6 +1,7 @@ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2011, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -24,6 +25,7 @@ /* Bluetooth SCO sockets. */ +#include #include #include @@ -41,9 +43,9 @@ #include #include #include -#include #include +#include #include #include @@ -61,7 +63,7 @@ static struct bt_sock_list sco_sk_list = { static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent); static void sco_chan_del(struct sock *sk, int err); -static int sco_conn_del(struct hci_conn *conn, int err); +static int sco_conn_del(struct hci_conn *conn, int err, u8 is_process); static void sco_sock_close(struct sock *sk); static void sco_sock_kill(struct sock *sk); @@ -134,7 +136,7 @@ static inline struct sock *sco_chan_get(struct sco_conn *conn) return sk; } -static int sco_conn_del(struct hci_conn *hcon, int err) +static int sco_conn_del(struct hci_conn *hcon, int err, u8 is_process) { struct sco_conn *conn = hcon->sco_data; struct sock *sk; @@ -147,10 +149,16 @@ static int sco_conn_del(struct hci_conn *hcon, int err) /* Kill socket */ sk = sco_chan_get(conn); if (sk) { - bh_lock_sock(sk); + if (is_process) + lock_sock(sk); + else + bh_lock_sock(sk); sco_sock_clear_timer(sk); sco_chan_del(sk, err); - bh_unlock_sock(sk); + if (is_process) + release_sock(sk); + else + bh_unlock_sock(sk); sco_sock_kill(sk); } @@ -173,7 +181,7 @@ static inline int sco_chan_add(struct sco_conn *conn, struct sock *sk, struct so return err; } -static int sco_connect(struct sock *sk) +static int sco_connect(struct sock *sk, __s8 is_wbs) { bdaddr_t *src = &bt_sk(sk)->src; bdaddr_t *dst = &bt_sk(sk)->dst; @@ -189,21 +197,35 @@ static int sco_connect(struct sock *sk) if (!hdev) return -EHOSTUNREACH; - hci_dev_lock(hdev); + hci_dev_lock_bh(hdev); + + hdev->is_wbs = is_wbs; - if (lmp_esco_capable(hdev) && !disable_esco) + if (lmp_esco_capable(hdev) && !disable_esco) { type = ESCO_LINK; - else { + } else if (is_wbs) { + return -ENAVAIL; + } else { type = SCO_LINK; pkt_type &= SCO_ESCO_MASK; } - hcon = hci_connect(hdev, type, pkt_type, dst, BT_SECURITY_LOW, HCI_AT_NO_BONDING); + BT_DBG("type: %d, pkt_type: 0x%x", type, pkt_type); + + 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; } + if (is_wbs && (hcon->type != ESCO_LINK)) { + BT_ERR("WBS [ hcon->type: 0x%x, hcon->pkt_type: 0x%x ]", + hcon->type, hcon->pkt_type); + err = -EREMOTEIO; + goto done; + } + conn = sco_conn_add(hcon, 0); if (!conn) { hci_conn_put(hcon); @@ -227,7 +249,7 @@ static int sco_connect(struct sock *sk) } done: - hci_dev_unlock(hdev); + hci_dev_unlock_bh(hdev); hci_dev_put(hdev); return err; } @@ -372,15 +394,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); @@ -406,10 +419,8 @@ static void sco_sock_init(struct sock *sk, struct sock *parent) { BT_DBG("sk %p", sk); - if (parent) { + if (parent) sk->sk_type = parent->sk_type; - security_sk_clone(parent, sk); - } } static struct proto sco_proto = { @@ -488,7 +499,7 @@ static int sco_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) goto done; } - write_lock(&sco_sk_list.lock); + write_lock_bh(&sco_sk_list.lock); if (bacmp(src, BDADDR_ANY) && __sco_get_sock_by_addr(src)) { err = -EADDRINUSE; @@ -499,7 +510,7 @@ static int sco_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) sk->sk_state = BT_BOUND; } - write_unlock(&sco_sk_list.lock); + write_unlock_bh(&sco_sk_list.lock); done: release_sock(sk); @@ -537,7 +548,7 @@ static int sco_sock_connect(struct socket *sock, struct sockaddr *addr, int alen bacpy(&bt_sk(sk)->dst, &sa.sco_bdaddr); sco_pi(sk)->pkt_type = sa.sco_pkt_type; - err = sco_connect(sk); + err = sco_connect(sk, sa.is_wbs); if (err) goto done; @@ -581,26 +592,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; } @@ -608,12 +623,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) @@ -846,9 +857,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; @@ -908,12 +917,15 @@ static void sco_conn_ready(struct sco_conn *conn) } /* ----- SCO interface with lower layer (HCI) ----- */ -int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr) +static int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type) { register struct sock *sk; struct hlist_node *node; int lm = 0; + if (type != SCO_LINK && type != ESCO_LINK) + return 0; + BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); /* Find listening sockets */ @@ -933,9 +945,13 @@ int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr) return lm; } -int sco_connect_cfm(struct hci_conn *hcon, __u8 status) +static int sco_connect_cfm(struct hci_conn *hcon, __u8 status) { BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status); + + if (hcon->type != SCO_LINK && hcon->type != ESCO_LINK) + return -EINVAL; + if (!status) { struct sco_conn *conn; @@ -943,20 +959,24 @@ 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), 0); return 0; } -int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason) +static int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason, __u8 is_process) { BT_DBG("hcon %p reason %d", hcon, reason); - sco_conn_del(hcon, bt_to_errno(reason)); + if (hcon->type != SCO_LINK && hcon->type != ESCO_LINK) + return -EINVAL; + + sco_conn_del(hcon, bt_err(reason), is_process); + return 0; } -int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb) +static int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb) { struct sco_conn *conn = hcon->sco_data; @@ -980,14 +1000,14 @@ static int sco_debugfs_show(struct seq_file *f, void *p) struct sock *sk; struct hlist_node *node; - read_lock(&sco_sk_list.lock); + read_lock_bh(&sco_sk_list.lock); sk_for_each(sk, node, &sco_sk_list.head) { seq_printf(f, "%s %s %d\n", batostr(&bt_sk(sk)->src), batostr(&bt_sk(sk)->dst), sk->sk_state); } - read_unlock(&sco_sk_list.lock); + read_unlock_bh(&sco_sk_list.lock); return 0; } @@ -1032,6 +1052,15 @@ static const struct net_proto_family sco_sock_family_ops = { .create = sco_sock_create, }; +static struct hci_proto sco_hci_proto = { + .name = "SCO", + .id = HCI_PROTO_SCO, + .connect_ind = sco_connect_ind, + .connect_cfm = sco_connect_cfm, + .disconn_cfm = sco_disconn_cfm, + .recv_scodata = sco_recv_scodata +}; + int __init sco_init(void) { int err; @@ -1046,6 +1075,13 @@ int __init sco_init(void) goto error; } + err = hci_register_proto(&sco_hci_proto); + if (err < 0) { + BT_ERR("SCO protocol registration failed"); + bt_sock_unregister(BTPROTO_SCO); + goto error; + } + if (bt_debugfs) { sco_debugfs = debugfs_create_file("sco", 0444, bt_debugfs, NULL, &sco_debugfs_fops); @@ -1069,6 +1105,9 @@ void __exit sco_exit(void) if (bt_sock_unregister(BTPROTO_SCO) < 0) BT_ERR("SCO socket unregistration failed"); + if (hci_unregister_proto(&sco_hci_proto) < 0) + BT_ERR("SCO protocol unregistration failed"); + proto_unregister(&sco_proto); } diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index deb119875fd93aee3fa125ecfd49de66495b7786..6bbb34b6769cd0175a4342b42e7db7be678284c2 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -20,16 +20,31 @@ SOFTWARE IS DISCLAIMED. */ +#include +#include + #include #include #include #include #include #include -#include #include +#include + +#define SMP_TIMEOUT 30000 /* 30 seconds */ + +#define SMP_MIN_CONN_INTERVAL 40 /* 50ms (40 * 1.25ms) */ +#define SMP_MAX_CONN_INTERVAL 56 /* 70ms (56 * 1.25ms) */ +#define SMP_MAX_CONN_LATENCY 0 /* 0ms (0 * 1.25ms) */ +#define SMP_SUPERVISION_TIMEOUT 500 /* 5 seconds (500 * 10ms) */ -#define SMP_TIMEOUT msecs_to_jiffies(30000) +#ifndef FALSE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +static int smp_distribute_keys(struct l2cap_conn *conn, __u8 force); static inline void swap128(u8 src[16], u8 dst[16]) { @@ -147,7 +162,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; @@ -182,28 +197,25 @@ static void smp_send_cmd(struct l2cap_conn *conn, u8 code, u16 len, void *data) if (!skb) return; - skb->priority = HCI_PRIO_MAX; - hci_send_acl(conn->hchan, skb, 0); - - cancel_delayed_work_sync(&conn->security_timer); - schedule_delayed_work(&conn->security_timer, SMP_TIMEOUT); + hci_send_acl(conn->hcon, NULL, skb, 0); } static __u8 authreq_to_seclevel(__u8 authreq) { if (authreq & SMP_AUTH_MITM) return BT_SECURITY_HIGH; - else + else if (authreq & SMP_AUTH_BONDING) return BT_SECURITY_MEDIUM; + else + return BT_SECURITY_LOW; } -static __u8 seclevel_to_authreq(__u8 sec_level) +static __u8 seclevel_to_authreq(__u8 level) { - switch (sec_level) { + switch (level) { case BT_SECURITY_HIGH: return SMP_AUTH_MITM | SMP_AUTH_BONDING; - case BT_SECURITY_MEDIUM: - return SMP_AUTH_BONDING; + default: return SMP_AUTH_NONE; } @@ -214,583 +226,570 @@ static void build_pairing_cmd(struct l2cap_conn *conn, struct smp_cmd_pairing *rsp, __u8 authreq) { + struct hci_conn *hcon = conn->hcon; + u8 all_keys = 0; u8 dist_keys = 0; - if (test_bit(HCI_PAIRABLE, &conn->hcon->hdev->dev_flags)) { - dist_keys = SMP_DIST_ENC_KEY; - authreq |= SMP_AUTH_BONDING; - } else { - authreq &= ~SMP_AUTH_BONDING; - } + dist_keys = SMP_DIST_ENC_KEY; + authreq |= SMP_AUTH_BONDING; + + BT_DBG("conn->hcon->io_capability:%d", conn->hcon->io_capability); if (rsp == NULL) { req->io_capability = conn->hcon->io_capability; - req->oob_flag = SMP_OOB_NOT_PRESENT; + req->oob_flag = hcon->oob ? SMP_OOB_PRESENT : + SMP_OOB_NOT_PRESENT; req->max_key_size = SMP_MAX_ENC_KEY_SIZE; - req->init_key_dist = 0; + req->init_key_dist = all_keys; req->resp_key_dist = dist_keys; req->auth_req = authreq; + BT_DBG("SMP_CMD_PAIRING_REQ %d %d %d %d %2.2x %2.2x", + req->io_capability, req->oob_flag, + req->auth_req, req->max_key_size, + req->init_key_dist, req->resp_key_dist); return; } + /* Only request OOB if remote AND we support it */ + if (req->oob_flag) + rsp->oob_flag = hcon->oob ? SMP_OOB_PRESENT : + SMP_OOB_NOT_PRESENT; + else + rsp->oob_flag = SMP_OOB_NOT_PRESENT; + 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 = 0; + rsp->init_key_dist = req->init_key_dist & all_keys; rsp->resp_key_dist = req->resp_key_dist & dist_keys; rsp->auth_req = authreq; + BT_DBG("SMP_CMD_PAIRING_RSP %d %d %d %d %2.2x %2.2x", + req->io_capability, req->oob_flag, req->auth_req, + req->max_key_size, req->init_key_dist, + req->resp_key_dist); } static u8 check_enc_key_size(struct l2cap_conn *conn, __u8 max_key_size) { - struct smp_chan *smp = conn->smp_chan; + struct hci_conn *hcon = conn->hcon; if ((max_key_size > SMP_MAX_ENC_KEY_SIZE) || (max_key_size < SMP_MIN_ENC_KEY_SIZE)) return SMP_ENC_KEY_SIZE; - smp->enc_key_size = max_key_size; + hcon->smp_key_size = max_key_size; return 0; } -static void smp_failure(struct l2cap_conn *conn, u8 reason, u8 send) -{ - struct hci_conn *hcon = conn->hcon; - - if (send) - smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), - &reason); - - clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->hcon->flags); - mgmt_auth_failed(conn->hcon->hdev, conn->dst, hcon->type, - hcon->dst_type, reason); - - if (test_and_clear_bit(HCI_CONN_LE_SMP_PEND, &conn->hcon->flags)) { - cancel_delayed_work_sync(&conn->security_timer); - smp_chan_destroy(conn); - } -} - -#define JUST_WORKS 0x00 -#define JUST_CFM 0x01 -#define REQ_PASSKEY 0x02 -#define CFM_PASSKEY 0x03 -#define REQ_OOB 0x04 -#define OVERLAP 0xFF - -static const u8 gen_method[5][5] = { - { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, - { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, - { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, - { JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM }, - { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP }, +#define JUST_WORKS SMP_JUST_WORKS +#define REQ_PASSKEY SMP_REQ_PASSKEY +#define CFM_PASSKEY SMP_CFM_PASSKEY +#define JUST_CFM SMP_JUST_CFM +#define OVERLAP SMP_OVERLAP +static const u8 gen_method[5][5] = { + {JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY}, + {JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY}, + {CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY}, + {JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM}, + {CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP} }; static int tk_request(struct l2cap_conn *conn, u8 remote_oob, u8 auth, u8 local_io, u8 remote_io) { struct hci_conn *hcon = conn->hcon; - struct smp_chan *smp = conn->smp_chan; u8 method; u32 passkey = 0; int ret = 0; - /* Initialize key for JUST WORKS */ - memset(smp->tk, 0, sizeof(smp->tk)); - clear_bit(SMP_FLAG_TK_VALID, &smp->smp_flags); + /* Initialize key to JUST WORKS */ + memset(hcon->tk, 0, sizeof(hcon->tk)); + hcon->tk_valid = FALSE; + hcon->auth = auth; + + /* By definition, OOB data will be used if both sides have it available + */ + if (remote_oob && hcon->oob) { + method = SMP_REQ_OOB; + goto agent_request; + } BT_DBG("tk_request: auth:%d lcl:%d rem:%d", auth, local_io, remote_io); /* If neither side wants MITM, use JUST WORKS */ - /* If either side has unknown io_caps, use JUST WORKS */ - /* Otherwise, look up method from the table */ + /* If either side has unknown io_caps, use JUST_WORKS */ if (!(auth & SMP_AUTH_MITM) || local_io > SMP_IO_KEYBOARD_DISPLAY || - remote_io > SMP_IO_KEYBOARD_DISPLAY) - method = JUST_WORKS; - else - method = gen_method[remote_io][local_io]; - - /* If not bonding, don't ask user to confirm a Zero TK */ - if (!(auth & SMP_AUTH_BONDING) && method == JUST_CFM) - method = JUST_WORKS; - - /* If Just Works, Continue with Zero TK */ - if (method == JUST_WORKS) { - set_bit(SMP_FLAG_TK_VALID, &smp->smp_flags); + remote_io > SMP_IO_KEYBOARD_DISPLAY) { + hcon->auth &= ~SMP_AUTH_MITM; + hcon->tk_valid = TRUE; return 0; } - /* Not Just Works/Confirm results in MITM Authentication */ - if (method != JUST_CFM) - set_bit(SMP_FLAG_MITM_AUTH, &smp->smp_flags); + /* MITM is now officially requested, but not required */ + /* Determine what we need (if anything) from the agent */ + method = gen_method[local_io][remote_io]; - /* If both devices have Keyoard-Display I/O, the master - * Confirms and the slave Enters the passkey. - */ - if (method == OVERLAP) { + BT_DBG("tk_method: %d", method); + + if (method == SMP_JUST_WORKS || method == SMP_JUST_CFM) + hcon->auth &= ~SMP_AUTH_MITM; + + /* Don't bother confirming unbonded JUST_WORKS */ + if (!(auth & SMP_AUTH_BONDING) && method == SMP_JUST_CFM) { + hcon->tk_valid = TRUE; + return 0; + } else if (method == SMP_JUST_WORKS) { + hcon->tk_valid = TRUE; + return 0; + } else if (method == SMP_OVERLAP) { if (hcon->link_mode & HCI_LM_MASTER) - method = CFM_PASSKEY; + method = SMP_CFM_PASSKEY; else - method = REQ_PASSKEY; + method = SMP_REQ_PASSKEY; } - /* Generate random passkey. Not valid until confirmed. */ - if (method == CFM_PASSKEY) { - u8 key[16]; + BT_DBG("tk_method-2: %d", method); + if (method == SMP_CFM_PASSKEY) { + u8 key[16]; + /* Generate a passkey for display. It is not valid until + * confirmed. + */ memset(key, 0, sizeof(key)); get_random_bytes(&passkey, sizeof(passkey)); passkey %= 1000000; put_unaligned_le32(passkey, key); - swap128(key, smp->tk); + swap128(key, hcon->tk); BT_DBG("PassKey: %d", passkey); } +agent_request: hci_dev_lock(hcon->hdev); - if (method == REQ_PASSKEY) - ret = mgmt_user_passkey_request(hcon->hdev, conn->dst, - hcon->type, hcon->dst_type); - else - ret = mgmt_user_confirm_request(hcon->hdev, conn->dst, - hcon->type, hcon->dst_type, - cpu_to_le32(passkey), 0); + switch (method) { + case SMP_REQ_PASSKEY: + ret = mgmt_user_confirm_request(hcon->hdev->id, + HCI_EV_USER_PASSKEY_REQUEST, conn->dst, 0); + break; + case SMP_CFM_PASSKEY: + default: + ret = mgmt_user_confirm_request(hcon->hdev->id, + HCI_EV_USER_CONFIRM_REQUEST, conn->dst, passkey); + break; + } hci_dev_unlock(hcon->hdev); return ret; } -static void confirm_work(struct work_struct *work) +static int send_pairing_confirm(struct l2cap_conn *conn) { - struct smp_chan *smp = container_of(work, struct smp_chan, confirm); - struct l2cap_conn *conn = smp->conn; - struct crypto_blkcipher *tfm; + struct hci_conn *hcon = conn->hcon; + struct crypto_blkcipher *tfm = hcon->hdev->tfm; struct smp_cmd_pairing_confirm cp; int ret; - u8 res[16], reason; - - BT_DBG("conn %p", conn); - - tfm = crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(tfm)) { - reason = SMP_UNSPECIFIED; - goto error; - } - - smp->tfm = tfm; + u8 res[16]; if (conn->hcon->out) - ret = smp_c1(tfm, smp->tk, smp->prnd, smp->preq, smp->prsp, 0, - conn->src, conn->hcon->dst_type, conn->dst, res); + ret = smp_c1(tfm, hcon->tk, hcon->prnd, hcon->preq, hcon->prsp, + 0, conn->src, hcon->dst_type, conn->dst, res); else - ret = smp_c1(tfm, smp->tk, smp->prnd, smp->preq, smp->prsp, - conn->hcon->dst_type, conn->dst, 0, conn->src, - res); - if (ret) { - reason = SMP_UNSPECIFIED; - goto error; - } + ret = smp_c1(tfm, hcon->tk, hcon->prnd, hcon->preq, hcon->prsp, + hcon->dst_type, conn->dst, 0, conn->src, res); - clear_bit(SMP_FLAG_CFM_PENDING, &smp->smp_flags); + if (ret) + return SMP_CONFIRM_FAILED; swap128(res, cp.confirm_val); - smp_send_cmd(smp->conn, SMP_CMD_PAIRING_CONFIRM, sizeof(cp), &cp); - - return; - -error: - smp_failure(conn, reason, 1); -} - -static void random_work(struct work_struct *work) -{ - struct smp_chan *smp = container_of(work, struct smp_chan, random); - struct l2cap_conn *conn = smp->conn; - struct hci_conn *hcon = conn->hcon; - struct crypto_blkcipher *tfm = smp->tfm; - u8 reason, confirm[16], res[16], key[16]; - int ret; - - if (IS_ERR_OR_NULL(tfm)) { - reason = SMP_UNSPECIFIED; - goto error; - } - - BT_DBG("conn %p %s", conn, conn->hcon->out ? "master" : "slave"); - - if (hcon->out) - ret = smp_c1(tfm, smp->tk, smp->rrnd, smp->preq, smp->prsp, 0, - conn->src, hcon->dst_type, conn->dst, res); - else - ret = smp_c1(tfm, smp->tk, smp->rrnd, smp->preq, smp->prsp, - hcon->dst_type, conn->dst, 0, conn->src, res); - if (ret) { - reason = SMP_UNSPECIFIED; - goto error; - } - - swap128(res, confirm); - - if (memcmp(smp->pcnf, confirm, sizeof(smp->pcnf)) != 0) { - BT_ERR("Pairing failed (confirmation values mismatch)"); - reason = SMP_CONFIRM_FAILED; - goto error; - } - - if (hcon->out) { - u8 stk[16], rand[8]; - __le16 ediv; - - memset(rand, 0, sizeof(rand)); - ediv = 0; - - smp_s1(tfm, smp->tk, smp->rrnd, smp->prnd, key); - swap128(key, stk); - - memset(stk + smp->enc_key_size, 0, - SMP_MAX_ENC_KEY_SIZE - smp->enc_key_size); - - if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &hcon->flags)) { - reason = SMP_UNSPECIFIED; - goto error; - } - - hci_le_start_enc(hcon, ediv, rand, stk); - hcon->enc_key_size = smp->enc_key_size; - } else { - u8 stk[16], r[16], rand[8]; - __le16 ediv; - - memset(rand, 0, sizeof(rand)); - ediv = 0; - - swap128(smp->prnd, r); - smp_send_cmd(conn, SMP_CMD_PAIRING_RANDOM, sizeof(r), r); - - smp_s1(tfm, smp->tk, smp->prnd, smp->rrnd, key); - swap128(key, stk); - - memset(stk + smp->enc_key_size, 0, - SMP_MAX_ENC_KEY_SIZE - smp->enc_key_size); - - hci_add_ltk(hcon->hdev, conn->dst, hcon->dst_type, - HCI_SMP_STK_SLAVE, 0, 0, stk, smp->enc_key_size, - ediv, rand); - } - - return; - -error: - smp_failure(conn, reason, 1); -} - -static struct smp_chan *smp_chan_create(struct l2cap_conn *conn) -{ - struct smp_chan *smp; - - smp = kzalloc(sizeof(struct smp_chan), GFP_ATOMIC); - if (!smp) - return NULL; - INIT_WORK(&smp->confirm, confirm_work); - INIT_WORK(&smp->random, random_work); + hcon->cfm_pending = FALSE; - smp->conn = conn; - conn->smp_chan = smp; - conn->hcon->smp_conn = conn; + smp_send_cmd(conn, SMP_CMD_PAIRING_CONFIRM, sizeof(cp), &cp); - hci_conn_hold(conn->hcon); - - return smp; -} - -void smp_chan_destroy(struct l2cap_conn *conn) -{ - struct smp_chan *smp = conn->smp_chan; - - BUG_ON(!smp); - - if (smp->tfm) - crypto_free_blkcipher(smp->tfm); - - kfree(smp); - conn->smp_chan = NULL; - conn->hcon->smp_conn = NULL; - hci_conn_put(conn->hcon); + return 0; } -int smp_user_confirm_reply(struct hci_conn *hcon, u16 mgmt_op, __le32 passkey) +int le_user_confirm_reply(struct hci_conn *hcon, u16 mgmt_op, void *cp) { + struct mgmt_cp_user_passkey_reply *psk_reply = cp; struct l2cap_conn *conn = hcon->smp_conn; - struct smp_chan *smp; - u32 value; u8 key[16]; + u8 reason = 0; + int ret = 0; BT_DBG(""); - if (!conn) - return -ENOTCONN; - - smp = conn->smp_chan; + hcon->tk_valid = TRUE; switch (mgmt_op) { + case MGMT_OP_USER_CONFIRM_NEG_REPLY: + reason = SMP_CONFIRM_FAILED; + break; + case MGMT_OP_USER_CONFIRM_REPLY: + break; case MGMT_OP_USER_PASSKEY_REPLY: - value = le32_to_cpu(passkey); memset(key, 0, sizeof(key)); - BT_DBG("PassKey: %d", value); - put_unaligned_le32(value, key); - swap128(key, smp->tk); - /* Fall Through */ - case MGMT_OP_USER_CONFIRM_REPLY: - set_bit(SMP_FLAG_TK_VALID, &smp->smp_flags); + BT_DBG("PassKey: %d", psk_reply->passkey); + put_unaligned_le32(psk_reply->passkey, key); + swap128(key, hcon->tk); break; - case MGMT_OP_USER_PASSKEY_NEG_REPLY: - case MGMT_OP_USER_CONFIRM_NEG_REPLY: - smp_failure(conn, SMP_PASSKEY_ENTRY_FAILED, 1); - return 0; default: - smp_failure(conn, SMP_PASSKEY_ENTRY_FAILED, 1); - return -EOPNOTSUPP; + reason = SMP_CONFIRM_FAILED; + ret = -EOPNOTSUPP; + break; } - /* If it is our turn to send Pairing Confirm, do so now */ - if (test_bit(SMP_FLAG_CFM_PENDING, &smp->smp_flags)) - queue_work(hcon->hdev->workqueue, &smp->confirm); + if (reason) { + BT_DBG("smp_send_cmd: SMP_CMD_PAIRING_FAIL"); + smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), + &reason); + del_timer(&hcon->smp_timer); + clear_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + mgmt_auth_failed(hcon->hdev->id, conn->dst, reason); + hci_conn_put(hcon); + } else if (hcon->cfm_pending) { + BT_DBG("send_pairing_confirm"); + ret = send_pairing_confirm(conn); + } - return 0; + return ret; } static u8 smp_cmd_pairing_req(struct l2cap_conn *conn, struct sk_buff *skb) { + struct hci_conn *hcon = conn->hcon; struct smp_cmd_pairing rsp, *req = (void *) skb->data; - struct smp_chan *smp; u8 key_size; u8 auth = SMP_AUTH_NONE; int ret; BT_DBG("conn %p", conn); - if (conn->hcon->link_mode & HCI_LM_MASTER) - return SMP_CMD_NOTSUPP; - - if (!test_and_set_bit(HCI_CONN_LE_SMP_PEND, &conn->hcon->flags)) - smp = smp_chan_create(conn); - - smp = conn->smp_chan; - - smp->preq[0] = SMP_CMD_PAIRING_REQ; - memcpy(&smp->preq[1], req, sizeof(*req)); + hcon->preq[0] = SMP_CMD_PAIRING_REQ; + memcpy(&hcon->preq[1], req, sizeof(*req)); skb_pull(skb, sizeof(*req)); - /* We didn't start the pairing, so match remote */ - if (req->auth_req & SMP_AUTH_BONDING) - auth = req->auth_req; - - conn->hcon->pending_sec_level = authreq_to_seclevel(auth); + if (req->oob_flag && hcon->oob) { + /* By definition, OOB data pairing will have MITM protection */ + auth = req->auth_req | SMP_AUTH_MITM; + } else if (req->auth_req & SMP_AUTH_BONDING) { + /* We will attempt MITM for all Bonding attempts */ + auth = SMP_AUTH_BONDING | SMP_AUTH_MITM; + } + /* We didn't start the pairing, so no requirements */ build_pairing_cmd(conn, req, &rsp, auth); key_size = min(req->max_key_size, rsp.max_key_size); if (check_enc_key_size(conn, key_size)) return SMP_ENC_KEY_SIZE; - ret = smp_rand(smp->prnd); + ret = smp_rand(hcon->prnd); if (ret) return SMP_UNSPECIFIED; - smp->prsp[0] = SMP_CMD_PAIRING_RSP; - memcpy(&smp->prsp[1], &rsp, sizeof(rsp)); - - smp_send_cmd(conn, SMP_CMD_PAIRING_RSP, sizeof(rsp), &rsp); - /* Request setup of TK */ - ret = tk_request(conn, 0, auth, rsp.io_capability, req->io_capability); + ret = tk_request(conn, req->oob_flag, auth, rsp.io_capability, + req->io_capability); if (ret) return SMP_UNSPECIFIED; + hcon->prsp[0] = SMP_CMD_PAIRING_RSP; + memcpy(&hcon->prsp[1], &rsp, sizeof(rsp)); + + smp_send_cmd(conn, SMP_CMD_PAIRING_RSP, sizeof(rsp), &rsp); + + mod_timer(&hcon->smp_timer, jiffies + msecs_to_jiffies(SMP_TIMEOUT)); + return 0; } static u8 smp_cmd_pairing_rsp(struct l2cap_conn *conn, struct sk_buff *skb) { + struct hci_conn *hcon = conn->hcon; struct smp_cmd_pairing *req, *rsp = (void *) skb->data; - struct smp_chan *smp = conn->smp_chan; - struct hci_dev *hdev = conn->hcon->hdev; u8 key_size, auth = SMP_AUTH_NONE; int ret; BT_DBG("conn %p", conn); - if (!(conn->hcon->link_mode & HCI_LM_MASTER)) - return SMP_CMD_NOTSUPP; - skb_pull(skb, sizeof(*rsp)); - req = (void *) &smp->preq[1]; + req = (void *) &hcon->preq[1]; key_size = min(req->max_key_size, rsp->max_key_size); if (check_enc_key_size(conn, key_size)) return SMP_ENC_KEY_SIZE; - ret = smp_rand(smp->prnd); + hcon->prsp[0] = SMP_CMD_PAIRING_RSP; + memcpy(&hcon->prsp[1], rsp, sizeof(*rsp)); + + ret = smp_rand(hcon->prnd); if (ret) return SMP_UNSPECIFIED; - smp->prsp[0] = SMP_CMD_PAIRING_RSP; - memcpy(&smp->prsp[1], rsp, sizeof(*rsp)); - if ((req->auth_req & SMP_AUTH_BONDING) && (rsp->auth_req & SMP_AUTH_BONDING)) auth = SMP_AUTH_BONDING; auth |= (req->auth_req | rsp->auth_req) & SMP_AUTH_MITM; - ret = tk_request(conn, 0, auth, rsp->io_capability, req->io_capability); + ret = tk_request(conn, req->oob_flag, auth, rsp->io_capability, + req->io_capability); if (ret) return SMP_UNSPECIFIED; - set_bit(SMP_FLAG_CFM_PENDING, &smp->smp_flags); + hcon->cfm_pending = TRUE; /* Can't compose response until we have been confirmed */ - if (!test_bit(SMP_FLAG_TK_VALID, &smp->smp_flags)) + if (!hcon->tk_valid) return 0; - queue_work(hdev->workqueue, &smp->confirm); + ret = send_pairing_confirm(conn); + if (ret) + return SMP_CONFIRM_FAILED; return 0; } static u8 smp_cmd_pairing_confirm(struct l2cap_conn *conn, struct sk_buff *skb) { - struct smp_chan *smp = conn->smp_chan; - struct hci_dev *hdev = conn->hcon->hdev; + struct hci_conn *hcon = conn->hcon; + int ret; BT_DBG("conn %p %s", conn, conn->hcon->out ? "master" : "slave"); - memcpy(smp->pcnf, skb->data, sizeof(smp->pcnf)); - skb_pull(skb, sizeof(smp->pcnf)); + memcpy(hcon->pcnf, skb->data, sizeof(hcon->pcnf)); + skb_pull(skb, sizeof(hcon->pcnf)); if (conn->hcon->out) { u8 random[16]; - swap128(smp->prnd, random); + swap128(hcon->prnd, random); smp_send_cmd(conn, SMP_CMD_PAIRING_RANDOM, sizeof(random), random); - } else if (test_bit(SMP_FLAG_TK_VALID, &smp->smp_flags)) { - queue_work(hdev->workqueue, &smp->confirm); - } else { - set_bit(SMP_FLAG_CFM_PENDING, &smp->smp_flags); - } + } else if (hcon->tk_valid) { + ret = send_pairing_confirm(conn); + + if (ret) + return SMP_CONFIRM_FAILED; + } else + hcon->cfm_pending = TRUE; + + + mod_timer(&hcon->smp_timer, jiffies + msecs_to_jiffies(SMP_TIMEOUT)); return 0; } static u8 smp_cmd_pairing_random(struct l2cap_conn *conn, struct sk_buff *skb) { - struct smp_chan *smp = conn->smp_chan; - struct hci_dev *hdev = conn->hcon->hdev; + struct hci_conn *hcon = conn->hcon; + struct crypto_blkcipher *tfm = hcon->hdev->tfm; + int ret; + u8 key[16], res[16], random[16], confirm[16]; - BT_DBG("conn %p", conn); + swap128(skb->data, random); + skb_pull(skb, sizeof(random)); + + if (conn->hcon->out) + ret = smp_c1(tfm, hcon->tk, random, hcon->preq, hcon->prsp, 0, + conn->src, hcon->dst_type, conn->dst, + res); + else + ret = smp_c1(tfm, hcon->tk, random, hcon->preq, hcon->prsp, + hcon->dst_type, conn->dst, 0, conn->src, + res); + if (ret) + return SMP_UNSPECIFIED; + + BT_DBG("conn %p %s", conn, conn->hcon->out ? "master" : "slave"); + + swap128(res, confirm); + + if (memcmp(hcon->pcnf, confirm, sizeof(hcon->pcnf)) != 0) { + BT_ERR("Pairing failed (confirmation values mismatch)"); + return SMP_CONFIRM_FAILED; + } + + if (conn->hcon->out) { + u8 stk[16], rand[8]; + __le16 ediv; - swap128(skb->data, smp->rrnd); - skb_pull(skb, sizeof(smp->rrnd)); + memset(rand, 0, sizeof(rand)); + ediv = 0; + + smp_s1(tfm, hcon->tk, random, hcon->prnd, key); + swap128(key, stk); + + memset(stk + hcon->smp_key_size, 0, + SMP_MAX_ENC_KEY_SIZE - hcon->smp_key_size); + + hci_le_start_enc(hcon, ediv, rand, stk); + hcon->enc_key_size = hcon->smp_key_size; + } else { + u8 stk[16], r[16], rand[8]; + __le16 ediv; - queue_work(hdev->workqueue, &smp->random); + memset(rand, 0, sizeof(rand)); + ediv = 0; + + swap128(hcon->prnd, r); + smp_send_cmd(conn, SMP_CMD_PAIRING_RANDOM, sizeof(r), r); + + smp_s1(tfm, hcon->tk, hcon->prnd, random, key); + swap128(key, stk); + + memset(stk + hcon->smp_key_size, 0, + SMP_MAX_ENC_KEY_SIZE - hcon->smp_key_size); + + hci_add_ltk(conn->hcon->hdev, 0, conn->dst, hcon->dst_type, + hcon->smp_key_size, hcon->auth, ediv, rand, stk); + } return 0; } -static u8 smp_ltk_encrypt(struct l2cap_conn *conn) +static int smp_encrypt_link(struct hci_conn *hcon, struct link_key *key) { - struct smp_ltk *key; - struct hci_conn *hcon = conn->hcon; + struct key_master_id *master; + u8 sec_level; + u8 zerobuf[8]; - key = hci_find_ltk_by_addr(hcon->hdev, conn->dst, hcon->dst_type); - if (!key) - return 0; + if (!hcon || !key || !key->data) + return -EINVAL; - if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &hcon->flags)) - return 1; + memset(zerobuf, 0, sizeof(zerobuf)); + + master = (void *) key->data; + + if (!master->ediv && !memcmp(master->rand, zerobuf, sizeof(zerobuf))) + return -EINVAL; + + hcon->enc_key_size = key->pin_len; + hcon->sec_req = TRUE; + sec_level = authreq_to_seclevel(key->auth); - hci_le_start_enc(hcon, key->ediv, key->rand, key->val); - hcon->enc_key_size = key->enc_size; + BT_DBG("cur %d, req: %d", hcon->sec_level, sec_level); - return 1; + if (sec_level > hcon->sec_level) + hcon->pending_sec_level = sec_level; + + if (!(hcon->link_mode & HCI_LM_ENCRYPT)) + hci_conn_hold(hcon); + + hci_le_start_enc(hcon, master->ediv, master->rand, key->val); + + return 0; } + static u8 smp_cmd_security_req(struct l2cap_conn *conn, struct sk_buff *skb) { + struct hci_conn *hcon = conn->hcon; struct smp_cmd_security_req *rp = (void *) skb->data; struct smp_cmd_pairing cp; - struct hci_conn *hcon = conn->hcon; - struct smp_chan *smp; + struct link_key *key; BT_DBG("conn %p", conn); - hcon->pending_sec_level = authreq_to_seclevel(rp->auth_req); - - if (smp_ltk_encrypt(conn)) + if (test_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend)) return 0; - if (test_and_set_bit(HCI_CONN_LE_SMP_PEND, &hcon->flags)) + key = hci_find_link_key_type(hcon->hdev, conn->dst, KEY_TYPE_LTK); + if (key && ((key->auth & SMP_AUTH_MITM) || + !(rp->auth_req & SMP_AUTH_MITM))) { + + if (smp_encrypt_link(hcon, key) < 0) + goto invalid_key; + return 0; + } + +invalid_key: + hcon->sec_req = FALSE; - smp = smp_chan_create(conn); + /* Switch to Pairing Connection Parameters */ + hci_le_conn_update(hcon, SMP_MIN_CONN_INTERVAL, SMP_MAX_CONN_INTERVAL, + SMP_MAX_CONN_LATENCY, SMP_SUPERVISION_TIMEOUT); skb_pull(skb, sizeof(*rp)); memset(&cp, 0, sizeof(cp)); build_pairing_cmd(conn, &cp, NULL, rp->auth_req); - smp->preq[0] = SMP_CMD_PAIRING_REQ; - memcpy(&smp->preq[1], &cp, sizeof(cp)); + hcon->pending_sec_level = authreq_to_seclevel(rp->auth_req); + hcon->preq[0] = SMP_CMD_PAIRING_REQ; + memcpy(&hcon->preq[1], &cp, sizeof(cp)); smp_send_cmd(conn, SMP_CMD_PAIRING_REQ, sizeof(cp), &cp); + mod_timer(&hcon->smp_timer, jiffies + msecs_to_jiffies(SMP_TIMEOUT)); + + set_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + + hci_conn_hold(hcon); + return 0; } int smp_conn_security(struct l2cap_conn *conn, __u8 sec_level) { struct hci_conn *hcon = conn->hcon; - struct smp_chan *smp = conn->smp_chan; __u8 authreq; - BT_DBG("conn %p hcon %p level 0x%2.2x", conn, hcon, sec_level); + BT_DBG("conn %p hcon %p %d req: %d", + conn, hcon, hcon->sec_level, sec_level); - if (!lmp_host_le_capable(hcon->hdev)) + if (IS_ERR(hcon->hdev->tfm)) return 1; + if (test_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend)) + return -EINPROGRESS; + if (sec_level == BT_SECURITY_LOW) return 1; + if (hcon->sec_level >= sec_level) return 1; - if (hcon->link_mode & HCI_LM_MASTER) - if (smp_ltk_encrypt(conn)) - goto done; + authreq = seclevel_to_authreq(sec_level); - if (test_and_set_bit(HCI_CONN_LE_SMP_PEND, &hcon->flags)) - return 0; + hcon->smp_conn = conn; + hcon->pending_sec_level = sec_level; - smp = smp_chan_create(conn); - if (!smp) - return 1; + if ((hcon->link_mode & HCI_LM_MASTER) && !hcon->sec_req) { + struct link_key *key; - authreq = seclevel_to_authreq(sec_level); + key = hci_find_link_key_type(hcon->hdev, conn->dst, + KEY_TYPE_LTK); + + if (smp_encrypt_link(hcon, key) == 0) + goto done; + } + + hcon->sec_req = FALSE; if (hcon->link_mode & HCI_LM_MASTER) { struct smp_cmd_pairing cp; + /* Switch to Pairing Connection Parameters */ + hci_le_conn_update(hcon, SMP_MIN_CONN_INTERVAL, + SMP_MAX_CONN_INTERVAL, SMP_MAX_CONN_LATENCY, + SMP_SUPERVISION_TIMEOUT); + build_pairing_cmd(conn, &cp, NULL, authreq); - smp->preq[0] = SMP_CMD_PAIRING_REQ; - memcpy(&smp->preq[1], &cp, sizeof(cp)); + hcon->preq[0] = SMP_CMD_PAIRING_REQ; + memcpy(&hcon->preq[1], &cp, sizeof(cp)); + + mod_timer(&hcon->smp_timer, jiffies + + msecs_to_jiffies(SMP_TIMEOUT)); smp_send_cmd(conn, SMP_CMD_PAIRING_REQ, sizeof(cp), &cp); + hci_conn_hold(hcon); } else { struct smp_cmd_security_req cp; cp.auth_req = authreq; @@ -798,56 +797,81 @@ int smp_conn_security(struct l2cap_conn *conn, __u8 sec_level) } done: - hcon->pending_sec_level = sec_level; + set_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); return 0; } static int smp_cmd_encrypt_info(struct l2cap_conn *conn, struct sk_buff *skb) { + struct hci_conn *hcon = conn->hcon; struct smp_cmd_encrypt_info *rp = (void *) skb->data; - struct smp_chan *smp = conn->smp_chan; + u8 rand[8]; + int err; skb_pull(skb, sizeof(*rp)); - memcpy(smp->tk, rp->ltk, sizeof(smp->tk)); + BT_DBG("conn %p", conn); + + memset(rand, 0, sizeof(rand)); + + err = hci_add_ltk(hcon->hdev, 0, conn->dst, hcon->dst_type, + 0, 0, 0, rand, rp->ltk); + if (err) + return SMP_UNSPECIFIED; return 0; } 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_chan *smp = conn->smp_chan; - struct hci_dev *hdev = conn->hcon->hdev; struct hci_conn *hcon = conn->hcon; - u8 authenticated; + struct smp_cmd_master_ident *rp = (void *) skb->data; + struct smp_cmd_pairing *paircmd = (void *) &hcon->prsp[1]; + struct link_key *key; + u8 *keydist; skb_pull(skb, sizeof(*rp)); - hci_dev_lock(hdev); - authenticated = (conn->hcon->sec_level == BT_SECURITY_HIGH); - hci_add_ltk(conn->hcon->hdev, conn->dst, hcon->dst_type, - HCI_SMP_LTK, 1, authenticated, smp->tk, smp->enc_key_size, - rp->ediv, rp->rand); - smp_distribute_keys(conn, 1); - hci_dev_unlock(hdev); + key = hci_find_link_key_type(hcon->hdev, conn->dst, KEY_TYPE_LTK); + if (key == NULL) + return SMP_UNSPECIFIED; + + if (hcon->out) + keydist = &paircmd->resp_key_dist; + else + keydist = &paircmd->init_key_dist; + + BT_DBG("keydist 0x%x", *keydist); + + hci_add_ltk(hcon->hdev, 1, conn->dst, hcon->dst_type, + hcon->smp_key_size, hcon->auth, rp->ediv, + rp->rand, key->val); + + *keydist &= ~SMP_DIST_ENC_KEY; + if (hcon->out) { + if (!(*keydist)) + smp_distribute_keys(conn, 1); + } return 0; } int smp_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) { + struct hci_conn *hcon = conn->hcon; __u8 code = skb->data[0]; __u8 reason; int err = 0; - if (!lmp_host_le_capable(conn->hcon->hdev)) { - err = -ENOTSUPP; + if (IS_ERR(hcon->hdev->tfm)) { + err = PTR_ERR(hcon->hdev->tfm); reason = SMP_PAIRING_NOTSUPP; + BT_ERR("SMP_PAIRING_NOTSUPP %p", hcon->hdev->tfm); goto done; } + hcon->smp_conn = conn; skb_pull(skb, sizeof(code)); switch (code) { @@ -856,9 +880,12 @@ int smp_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) break; case SMP_CMD_PAIRING_FAIL: - smp_failure(conn, skb->data[0], 0); reason = 0; err = -EPERM; + del_timer(&hcon->smp_timer); + clear_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + mgmt_auth_failed(hcon->hdev->id, conn->dst, skb->data[0]); + hci_conn_put(hcon); break; case SMP_CMD_PAIRING_RSP: @@ -901,33 +928,40 @@ int smp_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) } done: - if (reason) - smp_failure(conn, reason, 1); + if (reason) { + BT_ERR("SMP_CMD_PAIRING_FAIL: %d", reason); + smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), + &reason); + del_timer(&hcon->smp_timer); + clear_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + mgmt_auth_failed(hcon->hdev->id, conn->dst, reason); + hci_conn_put(hcon); + } kfree_skb(skb); return err; } -int smp_distribute_keys(struct l2cap_conn *conn, __u8 force) +static int smp_distribute_keys(struct l2cap_conn *conn, __u8 force) { + struct hci_conn *hcon = conn->hcon; struct smp_cmd_pairing *req, *rsp; - struct smp_chan *smp = conn->smp_chan; __u8 *keydist; BT_DBG("conn %p force %d", conn, force); - if (!test_bit(HCI_CONN_LE_SMP_PEND, &conn->hcon->flags)) - return 0; + if (IS_ERR(hcon->hdev->tfm)) + return PTR_ERR(hcon->hdev->tfm); - rsp = (void *) &smp->prsp[1]; + rsp = (void *) &hcon->prsp[1]; /* The responder sends its keys first */ - if (!force && conn->hcon->out && (rsp->resp_key_dist & 0x07)) + if (!force && hcon->out && (rsp->resp_key_dist & 0x07)) return 0; - req = (void *) &smp->preq[1]; + req = (void *) &hcon->preq[1]; - if (conn->hcon->out) { + if (hcon->out) { keydist = &rsp->init_key_dist; *keydist &= req->init_key_dist; } else { @@ -941,8 +975,6 @@ int smp_distribute_keys(struct l2cap_conn *conn, __u8 force) if (*keydist & SMP_DIST_ENC_KEY) { struct smp_cmd_encrypt_info enc; struct smp_cmd_master_ident ident; - struct hci_conn *hcon = conn->hcon; - u8 authenticated; __le16 ediv; get_random_bytes(enc.ltk, sizeof(enc.ltk)); @@ -951,10 +983,9 @@ int smp_distribute_keys(struct l2cap_conn *conn, __u8 force) smp_send_cmd(conn, SMP_CMD_ENCRYPT_INFO, sizeof(enc), &enc); - authenticated = hcon->sec_level == BT_SECURITY_HIGH; - hci_add_ltk(conn->hcon->hdev, conn->dst, hcon->dst_type, - HCI_SMP_LTK_SLAVE, 1, authenticated, - enc.ltk, smp->enc_key_size, ediv, ident.rand); + hci_add_ltk(hcon->hdev, 1, conn->dst, hcon->dst_type, + hcon->smp_key_size, hcon->auth, ediv, + ident.rand, enc.ltk); ident.ediv = cpu_to_le16(ediv); @@ -993,11 +1024,55 @@ int smp_distribute_keys(struct l2cap_conn *conn, __u8 force) *keydist &= ~SMP_DIST_SIGN; } - if (conn->hcon->out || force) { - clear_bit(HCI_CONN_LE_SMP_PEND, &conn->hcon->flags); - cancel_delayed_work_sync(&conn->security_timer); - smp_chan_destroy(conn); + if (hcon->out) { + if (hcon->disconn_cfm_cb) + hcon->disconn_cfm_cb(hcon, 0); + del_timer(&hcon->smp_timer); + clear_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + hci_conn_put(hcon); + } else if (rsp->resp_key_dist) { + if (hcon->disconn_cfm_cb) + hcon->disconn_cfm_cb(hcon, SMP_UNSPECIFIED); + clear_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + mgmt_auth_failed(hcon->hdev->id, conn->dst, SMP_UNSPECIFIED); + hci_conn_put(hcon); } return 0; } + +int smp_link_encrypt_cmplt(struct l2cap_conn *conn, u8 status, u8 encrypt) +{ + struct hci_conn *hcon = conn->hcon; + + BT_DBG("smp: %d %d %d", status, encrypt, hcon->sec_req); + + clear_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend); + + if (!status && encrypt && hcon->sec_level < hcon->pending_sec_level) + hcon->sec_level = hcon->pending_sec_level; + + if (!status && encrypt && !hcon->sec_req) + return smp_distribute_keys(conn, 0); + + /* Fall back to Pairing request if failed a Link Security request */ + else if (hcon->sec_req && (status || !encrypt)) + smp_conn_security(conn, hcon->pending_sec_level); + + hci_conn_put(hcon); + + return 0; +} + +void smp_timeout(unsigned long arg) +{ + struct l2cap_conn *conn = (void *) arg; + u8 reason = SMP_UNSPECIFIED; + + BT_DBG("%p", conn); + + smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), &reason); + clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->hcon->pend); + mgmt_auth_failed(conn->hcon->hdev->id, conn->dst, SMP_UNSPECIFIED); + hci_conn_put(conn->hcon); +} diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index b57532d4742c7cccc95a05dd641bfc47875b4717..5190c0be2787bef822357fc289d2872c47519642 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -736,6 +736,7 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev } #if IS_ENABLED(CONFIG_IPV6) else if (skb->protocol == htons(ETH_P_IPV6)) { + struct neighbour *neigh = dst_get_neighbour(skb_dst(skb)); const struct in6_addr *addr6; struct neighbour *neigh; bool do_tx_error_icmp; diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index 240b68469a7a178f990f07559f65ad46b2ae43f0..2177507d21a5c4633dcbd607600411bcdcfc502b 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile @@ -68,4 +68,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 cf54e10b258718cd12a9d8e55c42fb083ffacac0..b2b0e99887a9ddef6b205d58c84eb8b2b37d74db 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -2521,6 +2521,12 @@ void tcp_get_info(const struct sock *sk, struct tcp_info *info) info->tcpi_rcv_space = tp->rcvq_space.space; info->tcpi_total_retrans = tp->total_retrans; + + if (sk->sk_socket) { + struct file *filep = sk->sk_socket->file; + if (filep) + info->tcpi_count = atomic_read(&filep->f_count); + } } EXPORT_SYMBOL_GPL(tcp_get_info); @@ -3369,9 +3375,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 0cb86ceb652ff66432ba584fedef8231d94decc6..ab7bd2c0c5ce2ea7c4d610f96f017b28a2264b31 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1991,6 +1991,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 7d5cb975cc6f8e6c22581daaa638b6b3df5a52d2..f8978419f2db96efde0ab695502eb7d2e825939e 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -1579,6 +1579,16 @@ static int ipv6_generate_eui64(u8 *eui, struct net_device *dev) return addrconf_ifid_sit(eui, dev); case ARPHRD_IPGRE: return addrconf_ifid_gre(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; } @@ -2439,6 +2449,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 b7ca46161cb9b587d02c056f3236ae5725bf21f5..02d7f1cdd2f78d30854b5a86415d37dfb1f88208 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -440,8 +440,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 b5d56a22b1d20f2605771ed8360a75f5d2f8ce75..8129d97e4996fdc5821269a0f06bc2c89647bff7 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_peeked(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/net/wireless/core.h b/net/wireless/core.h index 3ac2dd00d7149b123db51463b449a112a2d1b908..036faeed00f7442879842d8527e4f3a7b3ac8ab5 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -446,6 +446,9 @@ int cfg80211_set_freq(struct cfg80211_registered_device *rdev, enum nl80211_channel_type channel_type); u16 cfg80211_calculate_bitrate(struct rate_info *rate); +int ieee80211_get_ratemask(struct ieee80211_supported_band *sband, + const u8 *rates, unsigned int n_rates, + u32 *mask); int ieee80211_get_ratemask(struct ieee80211_supported_band *sband, const u8 *rates, unsigned int n_rates, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index f432c57af05d03addc5f856bfff8a784c19f19a1..e322d4dfae27ad95f9c65d005ef2e2de67e95f66 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -3846,6 +3846,27 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) nla_data(info->attrs[NL80211_ATTR_IE]), request->ie_len); } + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + if (wiphy->bands[i]) + request->rates[i] = + (1 << wiphy->bands[i]->n_bitrates) - 1; + if (info->attrs[NL80211_ATTR_SCAN_SUPP_RATES]) { + nla_for_each_nested(attr, + info->attrs[NL80211_ATTR_SCAN_SUPP_RATES], + tmp) { + enum ieee80211_band band = nla_type(attr); + if (band < 0 || band >= IEEE80211_NUM_BANDS) { + err = -EINVAL; + goto out_free; + } + err = ieee80211_get_ratemask(wiphy->bands[band], + nla_data(attr), + nla_len(attr), + &request->rates[band]); + if (err) + goto out_free; + } + } for (i = 0; i < IEEE80211_NUM_BANDS; i++) if (wiphy->bands[i]) @@ -3876,6 +3897,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) request->dev = dev; request->wiphy = &rdev->wiphy; + request->no_cck = + nla_get_flag(info->attrs[NL80211_ATTR_TX_NO_CCK_RATE]); rdev->scan_req = request; err = rdev->ops->scan(&rdev->wiphy, dev, request); diff --git a/net/wireless/scan.c b/net/wireless/scan.c index e40104f562ba2a2db7027a4f98fb49db84849afb..3adfe7ffe007ce3bdb309ca800012646898c5c94 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1014,6 +1014,8 @@ int cfg80211_wext_siwscan(struct net_device *dev, if (wreq->scan_type == IW_SCAN_TYPE_PASSIVE) creq->n_ssids = 0; } + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + creq->rates[i] = (1 << wiphy->bands[i]->n_bitrates) - 1; for (i = 0; i < IEEE80211_NUM_BANDS; i++) if (wiphy->bands[i]) diff --git a/net/wireless/util.c b/net/wireless/util.c index 957f2562161753fcec3b8789734bc0a36afebb84..a448b80f70ea966092dc0fc384f9642cc7b4c0f7 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -208,6 +208,10 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, if (params->key_len != WLAN_KEY_LEN_AES_CMAC) return -EINVAL; break; + case WLAN_CIPHER_SUITE_SMS4: + if (params->key_len != WLAN_KEY_LEN_WAPI_SMS4) + return -EINVAL; + break; default: /* * We don't know anything about this algorithm, @@ -1034,7 +1038,6 @@ int ieee80211_get_ratemask(struct ieee80211_supported_band *sband, if (!found) return -EINVAL; } - /* * mask must have at least one bit set here since we * didn't accept a 0-length rates array nor allowed diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost index 08dce14f2dc862d78d30744e0e7cd7b4e915cc64..c1bc4588806a08fac9462acb51324ccef5e56106 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 0000000000000000000000000000000000000000..296d9adf9414a1fbb97d7095820f8b3cb99ce099 --- /dev/null +++ b/scripts/build-all.py @@ -0,0 +1,268 @@ +#! /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/[fm]sm[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 + for n in glob.glob('arch/arm/configs/apq*_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 faea0ec612bfed2932ca5dc25868fe00888a5afc..77a6dd97f783f35c6e0e3a1300a575a3295000fb 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -7,6 +7,13 @@ use strict; +use constant BEFORE_SHORTTEXT => 0; +use constant IN_SHORTTEXT_BLANKLINE => 1; +use constant IN_SHORTTEXT => 2; +use constant AFTER_SHORTTEXT => 3; +use constant CHECK_NEXT_SHORTTEXT => 4; +use constant SHORTTEXT_LIMIT => 75; + my $P = $0; $P =~ s@.*/@@g; @@ -1335,6 +1342,33 @@ sub check_absolute_file { } } +sub cleanup_continuation_headers { + # Collapse any header-continuation lines into a single line so they + # can be parsed meaningfully, as the parser only has one line + # of context to work with. + my $again; + do { + $again = 0; + foreach my $n (0 .. scalar(@rawlines) - 2) { + if ($rawlines[$n]=~/^\s*$/) { + # A blank line means there's no more chance + # of finding headers. Shortcut to done. + return; + } + if ($rawlines[$n]=~/^[\x21-\x39\x3b-\x7e]+:/ && + $rawlines[$n+1]=~/^\s+/) { + # Continuation header. Collapse it. + my $line = splice @rawlines, $n+1, 1; + $line=~s/^\s+/ /; + $rawlines[$n] .= $line; + # We've 'destabilized' the list, so restart. + $again = 1; + last; + } + } + } while ($again); +} + sub pos_last_openparen { my ($line) = @_; @@ -1373,6 +1407,8 @@ sub process { my $prevrawline=""; my $stashline=""; my $stashrawline=""; + my $subjectline=""; + my $sublinenr=""; my $length; my $indent; @@ -1416,8 +1452,16 @@ sub process { my @setup_docs = (); my $setup_docs = 0; + my $exec_file = ""; + + my $shorttext = BEFORE_SHORTTEXT; + my $shorttext_exspc = 0; + my $commit_text_present = 0; + sanitise_line_reset(); + cleanup_continuation_headers(); my $line; + foreach my $rawline (@rawlines) { $linenr++; $line = $rawline; @@ -1556,6 +1600,7 @@ sub process { $realfile = $1; $realfile =~ s@^([^/]*)/@@; $in_commit_log = 0; + $exec_file = $realfile; } elsif ($line =~ /^\+\+\+\s+(\S+)/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@; @@ -1572,15 +1617,128 @@ sub process { ERROR("MODIFIED_INCLUDE_ASM", "do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); } + $exec_file = ""; next; } - + elsif ($rawline =~ /^diff.+a\/(.+)\sb\/.+$/) { + $exec_file = $1; + } + #Check state to make sure we aren't in code block. + elsif (($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_BLANKLINE && $line=~/\S/) { + # the subject line was just processed, + # a blank line must be next + WARN("non-blank line after summary line\n" . $herecurr); + $shorttext = IN_SHORTTEXT; + # this non-blank line may or may not be commit text - + # a warning has been generated so assume it is commit + # text and move on + $commit_text_present = 1; + # fall through and treat this line as IN_SHORTTEXT + } + if ($shorttext == IN_SHORTTEXT) { + if ($line=~/^---/ || $line=~/^diff.*/) { + if ($commit_text_present == 0) { + WARN("please add commit text explaining " . + "*why* the change is needed\n" . + $herecurr); + } + $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 ($line=~/^\s*change-id:/i || + $line=~/^\s*signed-off-by:/i || + $line=~/^\s*crs-fixed:/i || + $line=~/^\s*acked-by:/i) { + # this is a tag, there must be commit + # text by now + if ($commit_text_present == 0) { + WARN("please add commit text explaining " . + "*why* the change is needed\n" . + $herecurr); + # prevent duplicate warnings + $commit_text_present = 1; + } + } elsif ($line=~/\S/) { + $commit_text_present = 1; + } + } elsif ($shorttext == IN_SHORTTEXT_BLANKLINE) { + # case of non-blank line in this state handled above + $shorttext = IN_SHORTTEXT; + } 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"); + # this non-blank line may or may not + # be commit text - a warning has been + # generated so assume it is commit + # text and move on + $commit_text_present = 1; + } + } + # The next two cases are BEFORE_SHORTTEXT. + } elsif ($line=~/^Subject: \[[^\]]*\] (.*)/) { + # This is the subject line. Go to + # CHECK_NEXT_SHORTTEXT to wait for the commit + # text to show up. + $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=~/^ (.*)/) { + # Indented format, this must be the summary + # line (i.e. git show). There will be no more + # headers so we are now in the shorttext. + $shorttext = IN_SHORTTEXT_BLANKLINE; + $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 @@ -1638,6 +1796,14 @@ sub process { "email address '$email' might be better as '$suggested_email$comment'\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 @@ -1766,6 +1932,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("LONG_LINE", @@ -2678,9 +2845,9 @@ 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("SPACING", - "space prohibited after that open parenthesis '('\n" . $herecurr); + "space prohibited after that open parenthesis '('\n" . $herecurr); } if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && $line !~ /for\s*\(.*;\s+\)/ && @@ -2892,7 +3059,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 = ''; ($dstat, $dcond, $ln, $cnt, $off) = @@ -2914,6 +3081,12 @@ 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/; + # Flatten any obvious string concatentation. while ($dstat =~ s/("X*")\s*$Ident/$1/ || $dstat =~ s/$Ident\s*("X*")/$1/) @@ -2926,6 +3099,7 @@ sub process { MODULE_PARAM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| + CLK_[A-Z\d_]+| __typeof__\(| union| struct| @@ -3115,11 +3289,87 @@ sub process { "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); + } + +# MSM - check if a non board-gpiomux file has any gpiomux declarations + if ($realfile =~ /\/mach-msm\/board-[0-9]+/ && + $realfile !~ /camera/ && $realfile !~ /gpiomux/ && + $line =~ /\s*struct msm_gpiomux_config\s*/ ) { + WARN("Non gpiomux board file cannot have a gpiomux config declarations. Please declare gpiomux configs in board-*-gpiomux.c file.\n" . $herecurr); + } + +# MSM - check if vreg_xxx function are used + if ($line =~ /\b(vreg_(get|put|set_level|enable|disable))\b/) { + WARN("Use of $1 API is deprecated: " . + "use regulator APIs\n" . $herecurr); + } + +# unbounded string functions are overflow risks + my %str_fns = ( + "sprintf" => "snprintf", + "strcpy" => "strlcpy", + "strncpy" => "strlcpy", + "strcat" => "strlcat", + "strncat" => "strlcat", + "vsprintf" => "vsnprintf", + "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("REDUNDANT_CODE", - "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 @@ -3156,6 +3406,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"; @@ -3341,6 +3596,11 @@ sub process { "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("USE_FUNC", diff --git a/scripts/gcc-wrapper.py b/scripts/gcc-wrapper.py new file mode 100755 index 0000000000000000000000000000000000000000..37321608a79b7eee4fbdb27035632fbdb4938f41 --- /dev/null +++ b/scripts/gcc-wrapper.py @@ -0,0 +1,97 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2011-2012, 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([ + "return_address.c:62", + "mmu.c:602", + ]) + +# 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 c4e7d1510f9dfd3136d80e63684b8dc764794950..78b70d8af600a96d2d416b30484f7db8dd48c69a 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 */ @@ -2113,7 +2115,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; @@ -2151,6 +2153,9 @@ int main(int argc, char **argv) case 'w': warn_unresolved = 1; break; + case 'E': + section_error_on_mismatch = 1; + break; default: exit(1); } @@ -2200,11 +2205,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/scripts/setlocalversion b/scripts/setlocalversion index bd6dca8a0ab23b78e0499a8868ec08bdb92e48f3..857ea4fccb882587bce656db9e7144bdff77e5ad 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -47,7 +47,20 @@ scm_version() # If we are at a tagged commit (like "v2.6.30-rc6"), we ignore # it, because this version is defined in the top level Makefile. - if [ -z "`git describe --exact-match 2>/dev/null`" ]; then + if atag="`git describe --exact-match --abbrev=0 2>/dev/null`"; then + # Make sure we're at the tag that matches the Makefile. + # If not place the hash of the tag as well for + # v2.6.30-rc5-g314aef + if [ "x$atag" != "x$VERSION" ]; then + # If only the short version is requested, + # don't bother running further git commands + if $short; then + echo "+" + return + fi + printf '%s%s' -g "`git show-ref -s --abbrev --tags $atag 2>/dev/null`" + fi + else # If only the short version is requested, don't bother # running further git commands @@ -56,10 +69,12 @@ scm_version() return fi # If we are past a tagged commit (like - # "v2.6.30-rc5-302-g72357d5"), we pretty print it. + # "v2.6.30-rc5-302-g72357d5"), we pretty print it and + # include the hash of any new tag on top. if atag="`git describe 2>/dev/null`"; then - echo "$atag" | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}' - + tag="`git describe --abbrev=0 2>/dev/null`" + commit="`echo "$atag" | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}'`" + printf '%s%s%s' -g "`git show-ref -s --abbrev --tags $tag 2>/dev/null`" $commit # If we don't have a tag at all we print -g{commitish}. else printf '%s%s' -g $head diff --git a/sound/compress_offload/core.c b/sound/compress_offload/core.c new file mode 100644 index 0000000000000000000000000000000000000000..987594a6b482a1b11e3935eec295431205058409 --- /dev/null +++ b/sound/compress_offload/core.c @@ -0,0 +1,658 @@ +/* + * core.c - compress offload core + * + * Copyright (C) 2011 Intel Corporation + * Authors: Vinod Koul + * Pierre-Louis Bossart + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TODO: + * - Integrate with alsa, compressed devices should register as alsa devices + * as /dev/snd_compr_xxx + * - Integrate with ASoC: + * Opening compressed path should also start the codec dai + * TBD how the cpu dai will be viewed and started. + * ASoC should always be optional part + * (we should be able to use this framework in non asoc systems + * - Multiple node representation + * driver should be able to register multiple nodes + * - Version numbering for API + */ + +static DEFINE_MUTEX(device_mutex); +static LIST_HEAD(device_list); +static LIST_HEAD(misc_list); + +/* + * currently we are using misc device for registration and exposing ioctls + * this is temporary and will be moved to snd + * the device should be registered as /dev/snd_compr..... + */ + +struct snd_compr_misc { + struct miscdevice misc; + struct list_head list; + struct snd_compr *compr; +}; + +struct snd_ioctl_data { + struct snd_compr_misc *misc; + unsigned long caps; + unsigned int minor; + struct snd_compr_stream stream; +}; + +static struct snd_compr_misc *snd_compr_get_device(unsigned int minor) +{ + struct snd_compr_misc *misc; + + list_for_each_entry(misc, &misc_list, list) { + if (minor == misc->misc.minor) + return misc; + } + return NULL; +} + +static int snd_compr_open(struct inode *inode, struct file *f) +{ + unsigned int minor = iminor(inode); + struct snd_compr_misc *misc = snd_compr_get_device(minor); + struct snd_ioctl_data *data; + struct snd_compr_runtime *runtime; + unsigned int direction; + int ret; + + mutex_lock(&device_mutex); + if (f->f_flags & O_WRONLY) + direction = SNDRV_PCM_STREAM_PLAYBACK; + else { + ret = -ENXIO; + goto out; + } + /* curently only encoded playback is supported, above needs to be + * removed once we have recording support */ + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out; + } + data->misc = misc; + data->minor = minor; + data->stream.ops = misc->compr->ops; + data->stream.direction = direction; + data->stream.private_data = misc->compr->private_data; + data->stream.device = misc->compr; + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) { + ret = -ENOMEM; + kfree(data); + goto out; + } + runtime->state = SNDRV_PCM_STATE_OPEN; + init_waitqueue_head(&runtime->sleep); + data->stream.runtime = runtime; + f->private_data = (void *)data; + ret = misc->compr->ops->open(&data->stream); + if (ret) { + kfree(runtime); + kfree(data); + goto out; + } +out: + mutex_unlock(&device_mutex); + return ret; +} + +static int snd_compr_free(struct inode *inode, struct file *f) +{ + struct snd_ioctl_data *data = f->private_data; + mutex_lock(&device_mutex); + data->stream.ops->free(&data->stream); + kfree(data->stream.runtime->buffer); + kfree(data->stream.runtime); + kfree(data); + mutex_unlock(&device_mutex); + return 0; +} + +static void snd_compr_update_tstamp(struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + stream->ops->pointer(stream, tstamp); + stream->runtime->hw_pointer = tstamp->copied_bytes; +} + +static size_t snd_compr_calc_avail(struct snd_compr_stream *stream, + struct snd_compr_avail *avail) +{ + size_t avail_calc; + + snd_compr_update_tstamp(stream, &avail->tstamp); + avail_calc = stream->runtime->app_pointer - stream->runtime->hw_pointer; + if (avail_calc < 0) + avail_calc = stream->runtime->buffer_size + avail_calc; + avail->avail = avail_calc; + return avail_calc; +} + +static size_t snd_compr_get_avail(struct snd_compr_stream *stream) +{ + struct snd_compr_avail avail; + + return snd_compr_calc_avail(stream, &avail); +} + +static int +snd_compr_ioctl_avail(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_avail ioctl_avail; + + snd_compr_calc_avail(stream, &ioctl_avail); + + if (copy_to_user((unsigned long __user *)arg, &ioctl_avail, sizeof(ioctl_avail))) + return -EFAULT; + return 0; +} + +static int snd_compr_write_data(struct snd_compr_stream *stream, + const char __user *buf, size_t count) +{ + void *dstn; + size_t copy; + + dstn = stream->runtime->buffer + stream->runtime->app_pointer; + if (count < stream->runtime->buffer_size - stream->runtime->app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + stream->runtime->app_pointer += count; + } else { + copy = stream->runtime->buffer_size - stream->runtime->app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(stream->runtime->buffer, buf + copy, count - copy)) + return -EFAULT; + stream->runtime->app_pointer = count - copy; + } + /* if DSP cares, let it know data has been written */ + if (stream->ops->ack) + stream->ops->ack(stream); + return count; +} + +static ssize_t snd_compr_write(struct file *f, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_ioctl_data *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + int retval; + + BUG_ON(!data); + stream = &data->stream; + mutex_lock(&stream->device->lock); + /* write is allowed when stream is running or has been steup */ + if (stream->runtime->state != SNDRV_PCM_STATE_SETUP && + stream->runtime->state != SNDRV_PCM_STATE_RUNNING) { + mutex_unlock(&stream->device->lock); + return -EPERM; + } + + avail = snd_compr_get_avail(stream); + /* calculate how much we can write to buffer */ + if (avail > count) + avail = count; + + if (stream->ops->copy) + retval = stream->ops->copy(stream, buf, avail); + else + retval = snd_compr_write_data(stream, buf, avail); + + /* while initiating the stream, write should be called before START + * call, so in setup move state */ + if (stream->runtime->state == SNDRV_PCM_STATE_SETUP) + stream->runtime->state = SNDRV_PCM_STATE_PREPARED; + + mutex_unlock(&stream->device->lock); + return retval; +} + + +static ssize_t snd_compr_read(struct file *f, char __user *buf, + size_t count, loff_t *offset) +{ + return -ENXIO; +} + +static int snd_compr_mmap(struct file *f, struct vm_area_struct *vma) +{ + return -ENXIO; +} + +unsigned int snd_compr_poll(struct file *f, poll_table *wait) +{ + struct snd_ioctl_data *data = f->private_data; + struct snd_compr_stream *stream; + int retval = 0; + + BUG_ON(!data); + stream = &data->stream; + + mutex_lock(&stream->device->lock); + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING) { + retval = -ENXIO; + goto out; + } + poll_wait(f, &stream->runtime->sleep, wait); + + /* this would change after read is implemented, we would need to + * check for direction here */ + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING) + retval = POLLOUT | POLLWRNORM; +out: + mutex_unlock(&stream->device->lock); + return retval; +} + +void snd_compr_fragment_elapsed(struct snd_compr_stream *stream) +{ + size_t avail; + + if (stream->direction != SNDRV_PCM_STREAM_PLAYBACK) + return; + avail = snd_compr_get_avail(stream); + if (avail >= stream->runtime->fragment_size) + wake_up(&stream->runtime->sleep); +} +EXPORT_SYMBOL_GPL(snd_compr_fragment_elapsed); + +void snd_compr_frame_elapsed(struct snd_compr_stream *stream) +{ + size_t avail; + + if (stream->direction != SNDRV_PCM_STREAM_CAPTURE) + return; + avail = snd_compr_get_avail(stream); + if (avail) + wake_up(&stream->runtime->sleep); +} +EXPORT_SYMBOL_GPL(snd_compr_frame_elapsed); + +static int snd_compr_get_caps(struct snd_compr_stream *stream, unsigned long arg) +{ + int retval; + struct snd_compr_caps caps; + + if (!stream->ops->get_caps) + return -ENXIO; + + retval = stream->ops->get_caps(stream, &caps); + if (retval) + goto out; + if (copy_to_user((void __user *)arg, &caps, sizeof(caps))) + retval = -EFAULT; +out: + return retval; +} + +static int snd_compr_get_codec_caps(struct snd_compr_stream *stream, unsigned long arg) +{ + int retval; + struct snd_compr_codec_caps *caps; + + if (!stream->ops->get_codec_caps) + return -ENXIO; + + caps = kmalloc(sizeof(*caps), GFP_KERNEL); + if (!caps) + return -ENOMEM; + + retval = stream->ops->get_codec_caps(stream, caps); + if (retval) + goto out; + if (copy_to_user((void __user *)arg, caps, sizeof(*caps))) + retval = -EFAULT; + +out: + kfree(caps); + return retval; +} + +/* revisit this with snd_pcm_preallocate_xxx */ +static int snd_compr_allocate_buffer(struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + unsigned int buffer_size; + void *buffer; + + buffer_size = params->buffer.fragment_size * params->buffer.fragments; + if (stream->ops->copy) { + buffer = NULL; + /* if copy is defined the driver will be required to copy + * the data from core + */ + } else { + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + } + stream->runtime->fragment_size = params->buffer.fragment_size; + stream->runtime->fragments = params->buffer.fragments; + stream->runtime->buffer = buffer; + stream->runtime->buffer_size = buffer_size; + return 0; +} + +static int snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_params *params; + int retval; + + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + /* + * we should allow parameter change only when stream has been + * opened not in other cases + */ + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + if (copy_from_user(params, (void __user *)arg, sizeof(*params))) + return -EFAULT; + retval = snd_compr_allocate_buffer(stream, params); + if (retval) { + kfree(params); + return -ENOMEM; + } + retval = stream->ops->set_params(stream, params); + if (retval) + goto out; + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + } else + return -EPERM; +out: + kfree(params); + return retval; +} + +static int snd_compr_get_params(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_params *params; + int retval; + + if (!stream->ops->get_params) + return -ENXIO; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + retval = stream->ops->get_params(stream, params); + if (retval) + goto out; + if (copy_to_user((char __user *)arg, params, sizeof(*params))) + retval = -EFAULT; + +out: + kfree(params); + return retval; +} + +static int snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_tstamp tstamp; + + snd_compr_update_tstamp(stream, &tstamp); + if (copy_to_user((struct snd_compr_tstamp __user *)arg, &tstamp, sizeof(tstamp))) + return -EFAULT; + return 0; +} + +static int snd_compr_pause(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state == SNDRV_PCM_STATE_PAUSED) + return 0; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); + if (!retval) { + stream->runtime->state = SNDRV_PCM_STATE_PAUSED; + wake_up(&stream->runtime->sleep); + } + return retval; +} + +static int snd_compr_resume(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state != SNDRV_PCM_STATE_PAUSED) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_RELEASE); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_RUNNING; + return retval; +} + +static int snd_compr_start(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state != SNDRV_PCM_STATE_PREPARED) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_START); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_RUNNING; + return retval; +} + +static int snd_compr_stop(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state != SNDRV_PCM_STATE_PREPARED) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + if (!retval) { + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + wake_up(&stream->runtime->sleep); + } + return retval; +} + +static int snd_compr_drain(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state != SNDRV_PCM_STATE_PREPARED || + stream->runtime->state != SNDRV_PCM_STATE_PAUSED) + return -EPERM; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_DRAIN); + if (!retval) { + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + wake_up(&stream->runtime->sleep); + } + return retval; +} + +static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct snd_ioctl_data *data = f->private_data; + struct snd_compr_stream *stream; + int retval = -ENOTTY; + + BUG_ON(!data); + stream = &data->stream; + mutex_lock(&stream->device->lock); + switch (_IOC_NR(cmd)) { + case _IOC_NR(SNDRV_COMPRESS_GET_CAPS): + retval = snd_compr_get_caps(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_CODEC_CAPS): + retval = snd_compr_get_codec_caps(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_SET_PARAMS): + retval = snd_compr_set_params(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS): + retval = snd_compr_get_params(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_TSTAMP): + retval = snd_compr_tstamp(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_AVAIL): + retval = snd_compr_ioctl_avail(stream, arg); + case _IOC_NR(SNDRV_COMPRESS_PAUSE): + retval = snd_compr_pause(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_RESUME): + retval = snd_compr_resume(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_START): + retval = snd_compr_start(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_STOP): + retval = snd_compr_stop(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_DRAIN): + cmd = SND_COMPR_TRIGGER_DRAIN; + retval = snd_compr_drain(stream); + break; + } + mutex_unlock(&stream->device->lock); + return retval; +} + +static const struct file_operations snd_comp_file = { + .owner = THIS_MODULE, + .open = snd_compr_open, + .release = snd_compr_free, + .read = snd_compr_read, + .write = snd_compr_write, + .unlocked_ioctl = snd_compr_ioctl, + .mmap = snd_compr_mmap, + .poll = snd_compr_poll, +}; + +static int snd_compress_add_device(struct snd_compr *device) +{ + int ret; + + struct snd_compr_misc *misc = kzalloc(sizeof(*misc), GFP_KERNEL); + + misc->misc.name = device->name; + misc->misc.fops = &snd_comp_file; + misc->misc.minor = MISC_DYNAMIC_MINOR; + misc->compr = device; + ret = misc_register(&misc->misc); + if (ret) { + pr_err("couldn't register misc device\n"); + kfree(misc); + } else { + pr_debug("Got minor %d\n", misc->misc.minor); + list_add_tail(&misc->list, &misc_list); + } + return ret; +} + +static int snd_compress_remove_device(struct snd_compr *device) +{ + struct snd_compr_misc *misc, *__misc; + + list_for_each_entry_safe(misc, __misc, &misc_list, list) { + if (device == misc->compr) { + misc_deregister(&misc->misc); + list_del(&device->list); + kfree(misc); + } + } + return 0; +} +/** + * snd_compress_register - register compressed device + * + * @device: compressed device to register + */ +int snd_compress_register(struct snd_compr *device) +{ + int retval; + + if (device->name == NULL || device->dev == NULL || device->ops == NULL) + return -EINVAL; + BUG_ON(!device->ops->open); + BUG_ON(!device->ops->free); + BUG_ON(!device->ops->set_params); + BUG_ON(!device->ops->get_params); + BUG_ON(!device->ops->trigger); + BUG_ON(!device->ops->pointer); + BUG_ON(!device->ops->get_caps); + BUG_ON(!device->ops->get_codec_caps); + + INIT_LIST_HEAD(&device->list); + /* todo register the compressed streams */ + /* todo integrate with asoc */ + + /* register a compressed card TBD if this needs change */ + + pr_debug("Registering compressed device %s\n", device->name); + mutex_lock(&device_mutex); + /* register a msic device for now */ + retval = snd_compress_add_device(device); + if (!retval) + list_add_tail(&device->list, &device_list); + mutex_unlock(&device_mutex); + return retval; +} +EXPORT_SYMBOL_GPL(snd_compress_register); + +int snd_compress_deregister(struct snd_compr *device) +{ + pr_debug("Removing compressed device %s\n", device->name); + mutex_lock(&device_mutex); + snd_compress_remove_device(device); + list_del(&device->list); + mutex_unlock(&device_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_compress_deregister); + +static int __init snd_compress_init(void) +{ + return 0; +} + +static void __exit snd_compress_exit(void) +{ +} + +module_init(snd_compress_init); +module_exit(snd_compress_exit); diff --git a/sound/core/Kconfig b/sound/core/Kconfig index b413ed05e74deae78fbd97809c99f0fd68f1ee46..87dcaeef7742b2272701e718623277929e30c7db 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -149,6 +149,7 @@ config SND_SEQ_RTCTIMER_DEFAULT config SND_DYNAMIC_MINORS bool "Dynamic device file minor numbers" + default y if SND_OMAP_SOC_ABE_DSP help If you say Y here, the minor numbers of ALSA device files in /dev/snd/ are allocated dynamically. This allows you to have diff --git a/sound/core/init.c b/sound/core/init.c index d8ec849af128ed1d248effdd2f606baa52a5f06d..f300bd33d5e9d16ed1fe3ba6518836444a33371c 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -22,13 +22,12 @@ #include #include #include -#include #include #include #include #include #include - +#include #include #include #include @@ -481,104 +480,74 @@ int snd_card_free(struct snd_card *card) EXPORT_SYMBOL(snd_card_free); -/* retrieve the last word of shortname or longname */ -static const char *retrieve_id_from_card_name(const char *name) -{ - const char *spos = name; - - while (*name) { - if (isspace(*name) && isalnum(name[1])) - spos = name + 1; - name++; - } - return spos; -} - -/* return true if the given id string doesn't conflict any other card ids */ -static bool card_id_ok(struct snd_card *card, const char *id) -{ - int i; - if (!snd_info_check_reserved_words(id)) - return false; - for (i = 0; i < snd_ecards_limit; i++) { - if (snd_cards[i] && snd_cards[i] != card && - !strcmp(snd_cards[i]->id, id)) - return false; - } - return true; -} - -/* copy to card->id only with valid letters from nid */ -static void copy_valid_id_string(struct snd_card *card, const char *src, - const char *nid) -{ - char *id = card->id; - - while (*nid && !isalnum(*nid)) - nid++; - if (isdigit(*nid)) - *id++ = isalpha(*src) ? *src : 'D'; - while (*nid && (size_t)(id - card->id) < sizeof(card->id) - 1) { - if (isalnum(*nid)) - *id++ = *nid; - nid++; - } - *id = 0; -} - -/* Set card->id from the given string - * If the string conflicts with other ids, add a suffix to make it unique. - */ -static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, - const char *nid) +static void snd_card_set_id_no_lock(struct snd_card *card, const char *nid) { - int len, loops; - bool with_suffix; - bool is_default = false; + int i, len, idx_flag = 0, loops = SNDRV_CARDS; + const char *spos, *src; char *id; - copy_valid_id_string(card, src, nid); + if (nid == NULL) { + id = card->shortname; + spos = src = id; + while (*id != '\0') { + if (*id == ' ') + spos = id + 1; + id++; + } + } else { + spos = src = nid; + } id = card->id; + while (*spos != '\0' && !isalnum(*spos)) + spos++; + if (isdigit(*spos)) + *id++ = isalpha(src[0]) ? src[0] : 'D'; + while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) { + if (isalnum(*spos)) + *id++ = *spos; + spos++; + } + *id = '\0'; - again: - /* use "Default" for obviously invalid strings - * ("card" conflicts with proc directories) - */ - if (!*id || !strncmp(id, "card", 4)) { + id = card->id; + + if (*id == '\0') strcpy(id, "Default"); - is_default = true; - } - with_suffix = false; - for (loops = 0; loops < SNDRV_CARDS; loops++) { - if (card_id_ok(card, id)) - return; /* OK */ + while (1) { + if (loops-- == 0) { + snd_printk(KERN_ERR "unable to set card id (%s)\n", id); + strcpy(card->id, card->proc_root->name); + return; + } + if (!snd_info_check_reserved_words(id)) + goto __change; + for (i = 0; i < snd_ecards_limit; i++) { + if (snd_cards[i] && !strcmp(snd_cards[i]->id, id)) + goto __change; + } + break; + __change: len = strlen(id); - if (!with_suffix) { - /* add the "_X" suffix */ - char *spos = id + len; - if (len > sizeof(card->id) - 3) - spos = id + sizeof(card->id) - 3; - strcpy(spos, "_1"); - with_suffix = true; - } else { - /* modify the existing suffix */ - if (id[len - 1] != '9') - id[len - 1]++; + if (idx_flag) { + if (id[len-1] != '9') + id[len-1]++; else - id[len - 1] = 'A'; + id[len-1] = 'A'; + } else if ((size_t)len <= sizeof(card->id) - 3) { + strcat(id, "_1"); + idx_flag++; + } else { + spos = id + len - 2; + if ((size_t)len <= sizeof(card->id) - 2) + spos++; + *(char *)spos++ = '_'; + *(char *)spos++ = '1'; + *(char *)spos++ = '\0'; + idx_flag++; } } - /* fallback to the default id */ - if (!is_default) { - *id = 0; - goto again; - } - /* last resort... */ - snd_printk(KERN_ERR "unable to set card id (%s)\n", id); - if (card->proc_root->name) - strcpy(card->id, card->proc_root->name); } /** @@ -595,7 +564,7 @@ void snd_card_set_id(struct snd_card *card, const char *nid) if (card->id[0] != '\0') return; mutex_lock(&snd_card_mutex); - snd_card_set_id_no_lock(card, nid, nid); + snd_card_set_id_no_lock(card, nid); mutex_unlock(&snd_card_mutex); } EXPORT_SYMBOL(snd_card_set_id); @@ -627,12 +596,22 @@ card_id_store_attr(struct device *dev, struct device_attribute *attr, memcpy(buf1, buf, copy); buf1[copy] = '\0'; mutex_lock(&snd_card_mutex); - if (!card_id_ok(NULL, buf1)) { + if (!snd_info_check_reserved_words(buf1)) { + __exist: mutex_unlock(&snd_card_mutex); return -EEXIST; } + for (idx = 0; idx < snd_ecards_limit; idx++) { + if (snd_cards[idx] && !strcmp(snd_cards[idx]->id, buf1)) { + if (card == snd_cards[idx]) + goto __ok; + else + goto __exist; + } + } strcpy(card->id, buf1); snd_info_card_id_change(card); +__ok: mutex_unlock(&snd_card_mutex); return count; @@ -686,18 +665,7 @@ int snd_card_register(struct snd_card *card) mutex_unlock(&snd_card_mutex); return 0; } - if (*card->id) { - /* make a unique id name from the given string */ - char tmpid[sizeof(card->id)]; - memcpy(tmpid, card->id, sizeof(card->id)); - snd_card_set_id_no_lock(card, tmpid, tmpid); - } else { - /* create an id from either shortname or longname */ - const char *src; - src = *card->shortname ? card->shortname : card->longname; - snd_card_set_id_no_lock(card, src, - retrieve_id_from_card_name(src)); - } + snd_card_set_id_no_lock(card, card->id[0] == '\0' ? NULL : card->id); snd_cards[card->number] = card; mutex_unlock(&snd_card_mutex); init_info_for_card(card); diff --git a/sound/core/jack.c b/sound/core/jack.c index 471e1e3b0a996da3fec0d21c82a55b87f3460469..9e2e085b78c942a82c9847e65c0e22933656458b 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -25,13 +25,16 @@ #include #include -static int jack_switch_types[SND_JACK_SWITCH_TYPES] = { +static int jack_switch_types[] = { SW_HEADPHONE_INSERT, SW_MICROPHONE_INSERT, SW_LINEOUT_INSERT, SW_JACK_PHYSICAL_INSERT, SW_VIDEOOUT_INSERT, SW_LINEIN_INSERT, + SW_HPHL_OVERCURRENT, + SW_HPHR_OVERCURRENT, + SW_UNSUPPORT_INSERT, }; static int snd_jack_dev_free(struct snd_device *device) @@ -128,7 +131,7 @@ int snd_jack_new(struct snd_card *card, const char *id, int type, jack->type = type; - for (i = 0; i < SND_JACK_SWITCH_TYPES; i++) + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) if (type & (1 << i)) input_set_capability(jack->input_dev, EV_SW, jack_switch_types[i]); diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 1a3070b4e5b5fc043d04ed734df76a51503bb65f..09bf06e7cee2c0f3d76afba40f9990cc8239cd3a 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -42,6 +41,7 @@ static DEFINE_MUTEX(register_mutex); static int snd_pcm_free(struct snd_pcm *pcm); static int snd_pcm_dev_free(struct snd_device *device); static int snd_pcm_dev_register(struct snd_device *device); +static int snd_pcm_dev_register_soc_be(struct snd_device *device); static int snd_pcm_dev_disconnect(struct snd_device *device); static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device) @@ -651,7 +651,7 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) pstr->stream = stream; pstr->pcm = pcm; pstr->substream_count = substream_count; - if (substream_count > 0 && !pcm->internal) { + if (substream_count > 0) { err = snd_pcm_stream_proc_init(pstr); if (err < 0) { snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n"); @@ -675,18 +675,15 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) pstr->substream = substream; else prev->next = substream; - - if (!pcm->internal) { - err = snd_pcm_substream_proc_init(substream); - if (err < 0) { - snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n"); - if (prev == NULL) - pstr->substream = NULL; - else - prev->next = NULL; - kfree(substream); - return err; - } + err = snd_pcm_substream_proc_init(substream); + if (err < 0) { + snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n"); + if (prev == NULL) + pstr->substream = NULL; + else + prev->next = NULL; + kfree(substream); + return err; } substream->group = &substream->self_group; spin_lock_init(&substream->self_group.lock); @@ -700,9 +697,25 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) EXPORT_SYMBOL(snd_pcm_new_stream); -static int _snd_pcm_new(struct snd_card *card, const char *id, int device, - int playback_count, int capture_count, bool internal, - struct snd_pcm **rpcm) +/** + * snd_pcm_new - create a new PCM instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero based) + * @playback_count: the number of substreams for playback + * @capture_count: the number of substreams for capture + * @rpcm: the pointer to store the new pcm instance + * + * Creates a new PCM instance. + * + * The pcm operators have to be set afterwards to the new instance + * via snd_pcm_set_ops(). + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_new(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, + struct snd_pcm ** rpcm) { struct snd_pcm *pcm; int err; @@ -723,7 +736,7 @@ static int _snd_pcm_new(struct snd_card *card, const char *id, int device, } pcm->card = card; pcm->device = device; - pcm->internal = internal; + if (id) strlcpy(pcm->id, id, sizeof(pcm->id)); if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) { @@ -745,32 +758,49 @@ static int _snd_pcm_new(struct snd_card *card, const char *id, int device, return 0; } -/** - * snd_pcm_new - create a new PCM instance - * @card: the card instance - * @id: the id string - * @device: the device index (zero based) - * @playback_count: the number of substreams for playback - * @capture_count: the number of substreams for capture - * @rpcm: the pointer to store the new pcm instance - * - * Creates a new PCM instance. - * - * The pcm operators have to be set afterwards to the new instance - * via snd_pcm_set_ops(). - * - * Returns zero if successful, or a negative error code on failure. - */ -int snd_pcm_new(struct snd_card *card, const char *id, int device, - int playback_count, int capture_count, struct snd_pcm **rpcm) +EXPORT_SYMBOL(snd_pcm_new); + +static int snd_pcm_new_stream_soc_be(struct snd_pcm *pcm, int stream, + int substream_count) { - return _snd_pcm_new(card, id, device, playback_count, capture_count, - false, rpcm); + int idx; + struct snd_pcm_str *pstr = &pcm->streams[stream]; + struct snd_pcm_substream *substream, *prev; + + pstr->stream = stream; + pstr->pcm = pcm; + pstr->substream_count = substream_count; + + prev = NULL; + for (idx = 0, prev = NULL; idx < substream_count; idx++) { + substream = kzalloc(sizeof(*substream), GFP_KERNEL); + if (substream == NULL) { + snd_printk(KERN_ERR "Cannot allocate BE PCM substream\n"); + return -ENOMEM; + } + substream->pcm = pcm; + substream->pstr = pstr; + substream->number = idx; + substream->stream = stream; + sprintf(substream->name, "subdevice #%i", idx); + substream->buffer_bytes_max = UINT_MAX; + if (prev == NULL) + pstr->substream = substream; + else + prev->next = substream; + + substream->group = &substream->self_group; + spin_lock_init(&substream->self_group.lock); + INIT_LIST_HEAD(&substream->self_group.substreams); + list_add_tail(&substream->link_list, &substream->self_group.substreams); + atomic_set(&substream->mmap_count, 0); + prev = substream; + } + return 0; } -EXPORT_SYMBOL(snd_pcm_new); /** - * snd_pcm_new_internal - create a new internal PCM instance + * snd_pcm_new_soc_be - create a new PCM instance for ASoC BE DAI link * @card: the card instance * @id: the id string * @device: the device index (zero based - shared with normal PCMs) @@ -778,25 +808,62 @@ EXPORT_SYMBOL(snd_pcm_new); * @capture_count: the number of substreams for capture * @rpcm: the pointer to store the new pcm instance * - * Creates a new internal PCM instance with no userspace device or procfs - * entries. This is used by ASoC Back End PCMs in order to create a PCM that - * will only be used internally by kernel drivers. i.e. it cannot be opened - * by userspace. It provides existing ASoC components drivers with a substream - * and access to any private data. + * Creates a new PCM instance with no userspace device or procfs entries. + * This is used by ASoC Back End PCMs in order to create a PCM that will only + * be used internally by kernel drivers. i.e. it cannot be opened by userspace. + * It also provides existing ASoC components drivers with a substream and + * access to any private data. * * The pcm operators have to be set afterwards to the new instance * via snd_pcm_set_ops(). * * Returns zero if successful, or a negative error code on failure. */ -int snd_pcm_new_internal(struct snd_card *card, const char *id, int device, +int snd_pcm_new_soc_be(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, - struct snd_pcm **rpcm) + struct snd_pcm ** rpcm) { - return _snd_pcm_new(card, id, device, playback_count, capture_count, - true, rpcm); + struct snd_pcm *pcm; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_pcm_dev_free, + .dev_register = snd_pcm_dev_register_soc_be, + .dev_disconnect = snd_pcm_dev_disconnect, + }; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (rpcm) + *rpcm = NULL; + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (pcm == NULL) { + snd_printk(KERN_ERR "Cannot allocate virtual PCM\n"); + return -ENOMEM; + } + pcm->card = card; + pcm->device = device; + + if (id) + strlcpy(pcm->id, id, sizeof(pcm->id)); + if ((err = snd_pcm_new_stream_soc_be(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) { + snd_pcm_free(pcm); + return err; + } + if ((err = snd_pcm_new_stream_soc_be(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) { + snd_pcm_free(pcm); + return err; + } + + if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) { + snd_pcm_free(pcm); + return err; + } + if (rpcm) + *rpcm = pcm; + return 0; } -EXPORT_SYMBOL(snd_pcm_new_internal); + +EXPORT_SYMBOL(snd_pcm_new_soc_be); static void snd_pcm_free_stream(struct snd_pcm_str * pstr) { @@ -1034,7 +1101,7 @@ static int snd_pcm_dev_register(struct snd_device *device) } for (cidx = 0; cidx < 2; cidx++) { int devtype = -1; - if (pcm->streams[cidx].substream == NULL || pcm->internal) + if (pcm->streams[cidx].substream == NULL) continue; switch (cidx) { case SNDRV_PCM_STREAM_PLAYBACK: @@ -1075,6 +1142,29 @@ static int snd_pcm_dev_register(struct snd_device *device) return 0; } +static int snd_pcm_dev_register_soc_be(struct snd_device *device) +{ + int err; + struct snd_pcm_notify *notify; + struct snd_pcm *pcm; + + if (snd_BUG_ON(!device || !device->device_data)) + return -ENXIO; + pcm = device->device_data; + mutex_lock(®ister_mutex); + err = snd_pcm_add(pcm); + if (err) { + mutex_unlock(®ister_mutex); + return err; + } + + list_for_each_entry(notify, &snd_pcm_notify_list, list) + notify->n_register(pcm); + + mutex_unlock(®ister_mutex); + return 0; +} + static int snd_pcm_dev_disconnect(struct snd_device *device) { struct snd_pcm *pcm = device->device_data; diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d18941178e6e933cb0f80f969c4d705c4777caf..b5d5a7596c1b6e835037c89648acbf05d4373bec 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 @@ -1030,7 +1029,7 @@ static int snd_interval_ratden(struct snd_interval *i, * Returns non-zero if the value is changed, zero if not changed. */ int snd_interval_list(struct snd_interval *i, unsigned int count, - const unsigned int *list, unsigned int mask) + unsigned int *list, unsigned int mask) { unsigned int k; struct snd_interval list_range; @@ -1992,6 +1991,9 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; + /* TODO: consider and -EINVAL here */ + if (substream->hw_no_buffer) + snd_printd("%s: warning this PCM is host less\n", __func__); runtime = substream->runtime; if (snd_BUG_ON(!substream->ops->copy && !runtime->dma_area)) return -EINVAL; diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c index 9c9eff9afbac85030effbe23b1aa1b6e117912f2..1dbd5ad418cd5e3c20324e330c3016795717f5aa 100644 --- a/sound/core/pcm_misc.c +++ b/sound/core/pcm_misc.c @@ -148,7 +148,9 @@ static struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { .le = -1, .signd = -1, }, [SNDRV_PCM_FORMAT_SPECIAL] = { - .le = -1, .signd = -1, + /* set the width and phys same as S16_LE */ + .width = 16, .phys = 16, .le = -1, .signd = -1, + .silence = {}, }, [SNDRV_PCM_FORMAT_S24_3LE] = { .width = 24, .phys = 24, .le = 1, .signd = 1, diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 3fe99e644eb838a34913f8c6870742e67c227a31..50343932d0ceff12759c75c59e15bd6e61ce9d81 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 @@ -29,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -843,6 +843,7 @@ static int snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) if (runtime->status->state != SNDRV_PCM_STATE_PREPARED) return -EBADFD; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !substream->hw_no_buffer && !snd_pcm_playback_data(substream)) return -EPIPE; runtime->trigger_master = substream; @@ -1520,6 +1521,19 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, return result; } +static int snd_compressed_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void __user *arg) +{ + struct snd_pcm_runtime *runtime; + int err = 0; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + pr_debug("%s called with cmd = %d\n", __func__, cmd); + err = substream->ops->ioctl(substream, cmd, arg); + return err; +} /* * drop ioctl * @@ -2041,6 +2055,12 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, goto error; } + if (substream->ops == NULL) { + snd_printd("cannot open back end PCMs directly\n"); + err = -ENODEV; + goto error; + } + if ((err = substream->ops->open(substream)) < 0) goto error; @@ -2567,6 +2587,12 @@ static int snd_pcm_common_ioctl1(struct file *file, snd_pcm_stream_unlock_irq(substream); return res; } + case SNDRV_COMPRESS_GET_CAPS: + case SNDRV_COMPRESS_GET_CODEC_CAPS: + case SNDRV_COMPRESS_SET_PARAMS: + case SNDRV_COMPRESS_GET_PARAMS: + case SNDRV_COMPRESS_TSTAMP: + return snd_compressed_ioctl(substream, cmd, arg); } snd_printd("unknown ioctl = 0x%x\n", cmd); return -ENOTTY; @@ -2739,7 +2765,7 @@ static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, pcm_file = file->private_data; - if (((cmd >> 8) & 0xff) != 'A') + if ((((cmd >> 8) & 0xff) != 'A') && (((cmd >> 8) & 0xff) != 'C')) return -ENOTTY; return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd, diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 91c985599d32c782c9b42029ccb409bb9afec429..7aa695f1718f385e12f05a2eea31a68b11a6d8d6 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -41,6 +41,7 @@ source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/kirkwood/Kconfig" source "sound/soc/mid-x86/Kconfig" +source "sound/soc/msm/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 2feaf376e94b2fc4ab06ca821e4ac328d3d423bb..a1b1523930dd0630609ff067aca078ceafd203d7 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += mid-x86/ +obj-$(CONFIG_SND_SOC) += msm/ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 59d8efaa17e96eec921774fad61fd149884f2241..80204f5f8e16f533f53fe9bb724db0fb6161778e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -40,7 +40,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98088 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX9850 if I2C - select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 select SND_SOC_RT5631 if I2C @@ -63,7 +62,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_WL1273 if MFD_WL1273_CORE select SND_SOC_WM1250_EV1 if I2C select SND_SOC_WM2000 if I2C - select SND_SOC_WM2200 if I2C select SND_SOC_WM5100 if I2C select SND_SOC_WM8350 if MFD_WM8350 select SND_SOC_WM8400 if MFD_WM8400 @@ -107,6 +105,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 @@ -284,6 +283,15 @@ config SND_SOC_UDA134X config SND_SOC_UDA1380 tristate +config SND_SOC_WCD9304 + tristate + +config SND_SOC_WCD9310 + tristate + +config SND_SOC_CS8427 + tristate + config SND_SOC_WL1273 tristate @@ -429,11 +437,11 @@ config SND_SOC_WM9713 config SND_SOC_LM4857 tristate -config SND_SOC_MAX9768 - tristate - config SND_SOC_MAX9877 tristate config SND_SOC_TPA6130A2 tristate + +config SND_SOC_MSM_STUB + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 6662eb0cdcc0724f2364760a15730346278267ab..965d6a183ed710b902f9bbacca938c832ead7fa5 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -25,7 +25,6 @@ snd-soc-dmic-objs := dmic.o snd-soc-jz4740-codec-objs := jz4740.o snd-soc-l3-objs := l3.o snd-soc-lm4857-objs := lm4857.o -snd-soc-max9768-objs := max9768.o snd-soc-max98088-objs := max98088.o snd-soc-max98095-objs := max98095.o snd-soc-max9850-objs := max9850.o @@ -49,10 +48,12 @@ 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-wcd9304-objs := wcd9304.o wcd9304-tables.o +snd-soc-wcd9310-objs := wcd9310.o wcd9310-tables.o +snd-soc-cs8427-objs := cs8427.o snd-soc-wl1273-objs := wl1273.o snd-soc-wm1250-ev1-objs := wm1250-ev1.o snd-soc-wm2000-objs := wm2000.o -snd-soc-wm2200-objs := wm2200.o snd-soc-wm5100-objs := wm5100.o wm5100-tables.o snd-soc-wm8350-objs := wm8350.o snd-soc-wm8400-objs := wm8400.o @@ -98,6 +99,8 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-timpani-objs := timpani.o +snd-soc-msm-stub-objs := msm_stub.o # Amp snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o @@ -131,7 +134,6 @@ obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o -obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o @@ -153,10 +155,12 @@ 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_WCD9304) += snd-soc-wcd9304.o +obj-$(CONFIG_SND_SOC_WCD9310) += snd-soc-wcd9310.o +obj-$(CONFIG_SND_SOC_CS8427) += snd-soc-cs8427.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_WM2000) += snd-soc-wm2000.o -obj-$(CONFIG_SND_SOC_WM2200) += snd-soc-wm2200.o obj-$(CONFIG_SND_SOC_WM5100) += snd-soc-wm5100.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o @@ -201,6 +205,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_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/cs8427.c b/sound/soc/codecs/cs8427.c new file mode 100644 index 0000000000000000000000000000000000000000..e406a32d92fad9b9295d9aba13e8acbbea10f392 --- /dev/null +++ b/sound/soc/codecs/cs8427.c @@ -0,0 +1,904 @@ +/* + * Routines for control of the CS8427 via i2c bus + * IEC958 (S/PDIF) receiver & transmitter by Cirrus Logic + * Copyright (c) by Jaroslav Kysela + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 CS8427_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000) + +#define CS8427_FORMATS (SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FORMAT_S16_LE |\ + SNDRV_PCM_FORMAT_S20_3LE) + +struct cs8427_stream { + struct snd_pcm_substream *substream; + char hw_status[CHANNEL_STATUS_SIZE]; /* hardware status */ + char def_status[CHANNEL_STATUS_SIZE]; /* default status */ + char pcm_status[CHANNEL_STATUS_SIZE]; /* PCM private status */ + char hw_udata[32]; + struct snd_kcontrol *pcm_ctl; +}; + +struct cs8427 { + struct i2c_client *client; + struct i2c_msg xfer_msg[2]; + unsigned char regmap[0x14]; /* map of first 1 + 13 registers */ + unsigned int reset_timeout; + struct cs8427_stream playback; +}; + +static int cs8427_i2c_write_device(struct cs8427 *cs8427_i2c, + u16 reg, u8 *value, u32 bytes) +{ + struct i2c_msg *msg; + int ret = 0; + u8 reg_addr = 0; + u8 data[bytes + 1]; + + if (cs8427_i2c->client == NULL) { + pr_err("%s: failed to get device info\n", __func__); + return -ENODEV; + } + reg_addr = (u8)reg; + msg = &cs8427_i2c->xfer_msg[0]; + msg->addr = cs8427_i2c->client->addr; + msg->len = bytes + 1; + msg->flags = 0; + data[0] = reg_addr; + data[1] = *value; + msg->buf = data; + ret = i2c_transfer(cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 1); + /* Try again if the write fails + * checking with ebusy and number of bytes executed + * for write ret value should be 1 + */ + if ((ret != 1) || (ret == -EBUSY)) { + ret = i2c_transfer( + cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 1); + if ((ret != 1) || (ret < 0)) { + dev_err(&cs8427_i2c->client->dev, + "failed to write the" + " device reg %d\n", reg); + return ret; + } + } + return 0; +} + +static int cs8427_i2c_write(struct cs8427 *chip, unsigned short reg, + int bytes, void *src) +{ + return cs8427_i2c_write_device(chip, reg, src, bytes); +} +static int cs8427_i2c_read_device(struct cs8427 *cs8427_i2c, + unsigned short reg, + int bytes, unsigned char *dest) +{ + struct i2c_msg *msg; + int ret = 0; + u8 reg_addr = 0; + u8 i = 0; + + if (cs8427_i2c->client == NULL) { + pr_err("%s: failed to get device info\n", __func__); + return -ENODEV; + } + for (i = 0; i < bytes; i++) { + reg_addr = (u8)reg++; + msg = &cs8427_i2c->xfer_msg[0]; + msg->addr = cs8427_i2c->client->addr; + msg->len = 1; + msg->flags = 0; + msg->buf = ®_addr; + + msg = &cs8427_i2c->xfer_msg[1]; + msg->addr = cs8427_i2c->client->addr; + msg->len = 1; + msg->flags = I2C_M_RD; + msg->buf = dest++; + ret = i2c_transfer(cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 2); + + /* Try again if read fails first time + checking with ebusy and number of bytes executed + for read ret value should be 2*/ + if ((ret != 2) || (ret == -EBUSY)) { + ret = i2c_transfer( + cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 2); + if ((ret != 2) || (ret < 0)) { + dev_err(&cs8427_i2c->client->dev, + "failed to read cs8427" + " register %d\n", reg); + return ret; + } + } + } + return 0; +} + +static int cs8427_i2c_read(struct cs8427 *chip, + unsigned short reg, + int bytes, void *dest) +{ + return cs8427_i2c_read_device(chip, reg, + bytes, dest); +} + +static int cs8427_i2c_sendbytes(struct cs8427 *chip, + char *reg_addr, char *data, + int bytes) +{ + u32 ret = 0; + u8 i = 0; + + if (!chip) { + pr_err("%s, invalid device info\n", __func__); + return -ENODEV; + } + if (!data) { + dev_err(&chip->client->dev, "%s:" + "invalid data pointer\n", __func__); + return -EINVAL; + } + for (i = 0; i < bytes; i++) { + ret = cs8427_i2c_write_device(chip, (*reg_addr + i), + &data[i], 1); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to send the data to" + " cs8427 chip\n", __func__); + break; + } + } + return i; +} + +/* + * Reset the chip using run bit, also lock PLL using ILRCK and + * put back AES3INPUT. This workaround is described in latest + * CS8427 datasheet, otherwise TXDSERIAL will not work. + */ +static void snd_cs8427_reset(struct cs8427 *chip) +{ + unsigned long end_time; + int data, aes3input = 0; + unsigned char val = 0; + + if (snd_BUG_ON(!chip)) + return; + if ((chip->regmap[CS8427_REG_CLOCKSOURCE] & CS8427_RXDAES3INPUT) == + CS8427_RXDAES3INPUT) /* AES3 bit is set */ + aes3input = 1; + chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~(CS8427_RUN | CS8427_RXDMASK); + cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, + 1, &chip->regmap[CS8427_REG_CLOCKSOURCE]); + udelay(200); + chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RUN | CS8427_RXDILRCK; + cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, + 1, &chip->regmap[CS8427_REG_CLOCKSOURCE]); + udelay(200); + end_time = jiffies + chip->reset_timeout; + while (time_after_eq(end_time, jiffies)) { + data = cs8427_i2c_read(chip, CS8427_REG_RECVERRORS, + 1, &val); + if (!(val & CS8427_UNLOCK)) + break; + schedule_timeout_uninterruptible(1); + } + chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~CS8427_RXDMASK; + if (aes3input) + chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RXDAES3INPUT; + cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, + 1, &chip->regmap[CS8427_REG_CLOCKSOURCE]); +} + +static int snd_cs8427_in_status_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 = 255; + return 0; +} + +static int snd_cs8427_in_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + unsigned char val = 0; + int err = 0; + + err = cs8427_i2c_read(chip, kcontrol->private_value, 1, &val); + if (err < 0) + return err; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_cs8427_qsubcode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 10; + return 0; +} + +static int snd_cs8427_qsubcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + unsigned char reg = CS8427_REG_QSUBCODE; + int err; + unsigned char val[20]; + + if (!chip) { + pr_err("%s: invalid device info\n", __func__); + return -ENODEV; + } + + err = cs8427_i2c_write(chip, reg, 1, &val[0]); + if (err != 1) { + dev_err(&chip->client->dev, "unable to send register" + " 0x%x byte to CS8427\n", reg); + return err < 0 ? err : -EIO; + } + err = cs8427_i2c_read(chip, *ucontrol->value.bytes.data, 10, &val); + if (err != 10) { + dev_err(&chip->client->dev, "unable to read" + " Q-subcode bytes from CS8427\n"); + return err < 0 ? err : -EIO; + } + return 0; +} + +static int snd_cs8427_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cs8427_select_corudata(struct cs8427 *cs8427_i2c, int udata) +{ + struct cs8427 *chip = cs8427_i2c; + int err; + + udata = udata ? CS8427_BSEL : 0; + if (udata != (chip->regmap[CS8427_REG_CSDATABUF] & udata)) { + chip->regmap[CS8427_REG_CSDATABUF] &= ~CS8427_BSEL; + chip->regmap[CS8427_REG_CSDATABUF] |= udata; + err = cs8427_i2c_write(cs8427_i2c, CS8427_REG_CSDATABUF, + 1, &chip->regmap[CS8427_REG_CSDATABUF]); + if (err < 0) + return err; + } + return 0; +} + +static int snd_cs8427_send_corudata(struct cs8427 *obj, + int udata, + unsigned char *ndata, + int count) +{ + struct cs8427 *chip = obj; + char *hw_data = udata ? + chip->playback.hw_udata : chip->playback.hw_status; + char data[32]; + int err, idx; + unsigned char addr = 0; + int ret = 0; + + if (!memcmp(hw_data, ndata, count)) + return 0; + err = snd_cs8427_select_corudata(chip, udata); + if (err < 0) + return err; + memcpy(hw_data, ndata, count); + if (udata) { + memset(data, 0, sizeof(data)); + if (memcmp(hw_data, data, count) == 0) { + chip->regmap[CS8427_REG_UDATABUF] &= ~CS8427_UBMMASK; + chip->regmap[CS8427_REG_UDATABUF] |= CS8427_UBMZEROS | + CS8427_EFTUI; + err = cs8427_i2c_write(chip, CS8427_REG_UDATABUF, + 1, &chip->regmap[CS8427_REG_UDATABUF]); + return err < 0 ? err : 0; + } + } + idx = 0; + memcpy(data, ndata, CHANNEL_STATUS_SIZE); + /* address from where the bufferhas to write*/ + addr = 0x20; + ret = cs8427_i2c_sendbytes(chip, &addr, data, count); + if (ret != count) + return -EIO; + return 1; +} + +static int snd_cs8427_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + if (!chip) { + pr_err("%s: invalid device info\n", __func__); + return -ENODEV; + } + + memcpy(ucontrol->value.iec958.status, + chip->playback.def_status, CHANNEL_STATUS_SIZE); + return 0; +} + +static int snd_cs8427_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + unsigned char *status; + int err, change; + + if (!chip) { + pr_err("%s: invalid device info\n", __func__); + return -ENODEV; + } + status = kcontrol->private_value ? + chip->playback.pcm_status : chip->playback.def_status; + + change = memcmp(ucontrol->value.iec958.status, status, + CHANNEL_STATUS_SIZE) != 0; + + if (!change) { + memcpy(status, ucontrol->value.iec958.status, + CHANNEL_STATUS_SIZE); + err = snd_cs8427_send_corudata(chip, 0, status, + CHANNEL_STATUS_SIZE); + if (err < 0) + change = err; + } + return change; +} + +static int snd_cs8427_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cs8427_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, CHANNEL_STATUS_SIZE); + return 0; +} + +static struct snd_kcontrol_new snd_cs8427_iec958_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_in_status_info, + .name = "IEC958 CS8427 Input Status", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_in_status_get, + .private_value = 15, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_in_status_info, + .name = "IEC958 CS8427 Error Status", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_in_status_get, + .private_value = 16, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = snd_cs8427_spdif_mask_info, + .get = snd_cs8427_spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, + DEFAULT), + .info = snd_cs8427_spdif_info, + .get = snd_cs8427_spdif_get, + .put = snd_cs8427_spdif_put, + .private_value = 0 + }, + { + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE), + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .info = snd_cs8427_spdif_info, + .get = snd_cs8427_spdif_get, + .put = snd_cs8427_spdif_put, + .private_value = 1 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_qsubcode_info, + .name = "IEC958 Q-subcode Capture Default", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_qsubcode_get + } +}; + +static int cs8427_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs8427 *chip = dev_get_drvdata(codec->dev); + int ret = 0; + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + chip->regmap[CS8427_REG_SERIALINPUT] &= CS8427_BITWIDTH_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + chip->regmap[CS8427_REG_SERIALINPUT] |= CS8427_SIRES16; + ret = cs8427_i2c_write(chip, CS8427_REG_SERIALINPUT, 1, + &chip->regmap[CS8427_REG_SERIALINPUT]); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + chip->regmap[CS8427_REG_SERIALINPUT] |= CS8427_SIRES20; + ret = cs8427_i2c_write(chip, CS8427_REG_SERIALINPUT, 1, + &chip->regmap[CS8427_REG_SERIALINPUT]); + + break; + case SNDRV_PCM_FORMAT_S24_LE: + chip->regmap[CS8427_REG_SERIALINPUT] |= CS8427_SIRES24; + ret = cs8427_i2c_write(chip, CS8427_REG_SERIALINPUT, 1, + &chip->regmap[CS8427_REG_SERIALINPUT]); + break; + default: + pr_err("invalid format\n"); + break; + } + dev_dbg(&chip->client->dev, + "%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + return ret; +} + +static int snd_cs8427_iec958_register_kcontrol(struct cs8427 *cs8427, + struct snd_card *card) +{ + struct cs8427 *chip = cs8427; + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + for (idx = 0; idx < ARRAY_SIZE(snd_cs8427_iec958_controls); idx++) { + kctl = snd_ctl_new1(&snd_cs8427_iec958_controls[idx], chip); + if (kctl == NULL) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err < 0) { + dev_err(&chip->client->dev, + "failed to add the kcontrol\n"); + return err; + } + } + return err; +} + +static int cs8427_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cs8427 *chip = dev_get_drvdata(dai->codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + /* + * we need to make the pll lock for the I2S tranfers + * reset the cs8427 chip for this. + */ + snd_cs8427_reset(chip); + dev_dbg(&chip->client->dev, + "%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + + return 0; +} + +static void cs8427_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cs8427 *chip = dev_get_drvdata(dai->codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return; + } + dev_dbg(&chip->client->dev, + "%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); +} + +static int cs8427_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cs8427 *chip = dev_get_drvdata(dai->codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, "%s\n", __func__); + return 0; +} + +static struct snd_soc_dai_ops cs8427_dai_ops = { + .startup = cs8427_startup, + .shutdown = cs8427_shutdown, + .hw_params = cs8427_hw_params, + .set_fmt = cs8427_set_dai_fmt, +}; + +static struct snd_soc_dai_driver cs8427_dai[] = { + { + .name = "spdif_rx", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .rates = CS8427_RATES, + .formats = CS8427_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &cs8427_dai_ops, + }, +}; + + +static unsigned int cs8427_soc_i2c_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct cs8427 *chip = dev_get_drvdata(codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, "cs8427 soc i2c read\n"); + return 0; +} + +static int cs8427_soc_i2c_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct cs8427 *chip = dev_get_drvdata(codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, "cs8427 soc i2c write\n"); + return 0; +} + +static int cs8427_soc_probe(struct snd_soc_codec *codec) +{ + int ret = 0; + struct cs8427 *chip; + codec->control_data = dev_get_drvdata(codec->dev); + chip = codec->control_data; + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + snd_cs8427_iec958_register_kcontrol(chip, codec->card->snd_card); + dev_set_drvdata(codec->dev, chip); + return ret; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs8427 = { + .read = cs8427_soc_i2c_read, + .write = cs8427_soc_i2c_write, + .probe = cs8427_soc_probe, +}; + +int poweron_cs8427(struct cs8427 *chip) +{ + struct cs8427_platform_data *pdata = chip->client->dev.platform_data; + int ret = 0; + + /*enable the 100KHz level shifter*/ + if (pdata->enable) { + ret = pdata->enable(1); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to enable the level shifter\n"); + return ret; + } + } + + ret = gpio_request(pdata->reset_gpio, "cs8427 reset"); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to request the gpio %d\n", + pdata->reset_gpio); + return ret; + } + /*bring the chip out of reset*/ + gpio_direction_output(pdata->reset_gpio, 1); + msleep(20); + gpio_direction_output(pdata->reset_gpio, 0); + msleep(20); + gpio_direction_output(pdata->reset_gpio, 1); + msleep(20); + return ret; +} + +static __devinit int cs8427_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static unsigned char initvals1[] = { + CS8427_REG_CONTROL1 | CS8427_REG_AUTOINC, + /* CS8427_REG_CONTROL1: RMCK to OMCK, valid PCM audio, disable mutes, + * TCBL=output + */ + CS8427_SWCLK | CS8427_TCBLDIR, + /* CS8427_REG_CONTROL2: hold last valid audio sample, RMCK=256*Fs, + * normal stereo operation + */ + 0x08, + /* CS8427_REG_DATAFLOW: + * AES3 Transmitter data source => Serial Audio input port + * Serial audio output port data source => reserved + */ + CS8427_TXDSERIAL, + /* CS8427_REG_CLOCKSOURCE: Run off, CMCK=256*Fs, + * output time base = OMCK, input time base = recovered input clock, + * recovered input clock source is ILRCK changed to AES3INPUT + * (workaround, see snd_cs8427_reset) + */ + CS8427_RXDILRCK | CS8427_OUTC, + /* CS8427_REG_SERIALINPUT: Serial audio input port data format = I2S, + * 24-bit, 64*Fsi + */ + CS8427_SIDEL | CS8427_SILRPOL | CS8427_SORES16, + /* CS8427_REG_SERIALOUTPUT: Serial audio output port data format + * = I2S, 24-bit, 64*Fsi + */ + CS8427_SODEL | CS8427_SOLRPOL | CS8427_SIRES16, + }; + static unsigned char initvals2[] = { + CS8427_REG_RECVERRMASK | CS8427_REG_AUTOINC, + /* CS8427_REG_RECVERRMASK: unmask the input PLL clock, V, confidence, + * biphase, parity status bits + * CS8427_UNLOCK | CS8427_V | CS8427_CONF | CS8427_BIP | CS8427_PAR, + */ + 0xff, /* set everything */ + /* CS8427_REG_CSDATABUF: + * Registers 32-55 window to CS buffer + * Inhibit D->E transfers from overwriting first 5 bytes of CS data. + * Inhibit D->E transfers (all) of CS data. + * Allow E->F transfer of CS data. + * One byte mode; both A/B channels get same written CB data. + * A channel info is output to chip's EMPH* pin. + */ + CS8427_CBMR | CS8427_DETCI, + /* CS8427_REG_UDATABUF: + * Use internal buffer to transmit User (U) data. + * Chip's U pin is an output. + * Transmit all O's for user data. + * Inhibit D->E transfers. + * Inhibit E->F transfers. + */ + CS8427_UD | CS8427_EFTUI | CS8427_DETUI, + }; + int err; + unsigned char buf[CHANNEL_STATUS_SIZE]; + unsigned char val = 0; + char addr = 0; + unsigned int reset_timeout = 100; + int ret = 0; + struct cs8427 *chip; + + if (!client) { + pr_err("%s: invalid device info\n", __func__); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct cs8427), GFP_KERNEL); + if (chip == NULL) { + dev_err(&client->dev, + "%s: error, allocation failed\n", __func__); + return -ENOMEM; + } + + chip->client = client; + + dev_set_drvdata(&chip->client->dev, chip); + + ret = poweron_cs8427(chip); + + if (ret) { + dev_err(&chip->client->dev, + "failed to bring chip out of reset\n"); + return -ENODEV; + } + + err = cs8427_i2c_read(chip, CS8427_REG_ID_AND_VER, 1, &val); + if (err < 0) { + /* give second chance */ + dev_err(&chip->client->dev, + "failed to read cs8427 trying once again\n"); + err = cs8427_i2c_read(chip, CS8427_REG_ID_AND_VER, + 1, &val); + if (err < 0) { + dev_err(&chip->client->dev, + "failed to read version number\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, + "version number read = %x\n", val); + } + if (val != CS8427_VER8427A) { + dev_err(&chip->client->dev, + "unable to find CS8427 signature " + "(expected 0x%x, read 0x%x),\n", + CS8427_VER8427A, val); + dev_err(&chip->client->dev, + " initialization is not completed\n"); + return -EFAULT; + } + val = 0; + /* turn off run bit while making changes to configuration */ + err = cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, 1, &val); + if (err < 0) + goto __fail; + /* send initial values */ + memcpy(chip->regmap + (initvals1[0] & 0x7f), initvals1 + 1, 6); + addr = 1; + err = cs8427_i2c_sendbytes(chip, &addr, &initvals1[1], 6); + if (err != 6) { + err = err < 0 ? err : -EIO; + goto __fail; + } + /* Turn off CS8427 interrupt stuff that is not used in hardware */ + memset(buf, 0, 7); + /* from address 9 to 15 */ + addr = 9; + err = cs8427_i2c_sendbytes(chip, &addr, buf, 7); + if (err != 7) + goto __fail; + /* send transfer initialization sequence */ + addr = 0x11; + memcpy(chip->regmap + (initvals2[0] & 0x7f), initvals2 + 1, 3); + err = cs8427_i2c_sendbytes(chip, &addr, &initvals2[1], 3); + if (err != 3) { + err = err < 0 ? err : -EIO; + goto __fail; + } + /* write default channel status bytes */ + put_unaligned_le32(SNDRV_PCM_DEFAULT_CON_SPDIF, buf); + memset(buf + 4, 0, CHANNEL_STATUS_SIZE - 4); + if (snd_cs8427_send_corudata(chip, 0, buf, CHANNEL_STATUS_SIZE) < 0) + goto __fail; + memcpy(chip->playback.def_status, buf, CHANNEL_STATUS_SIZE); + memcpy(chip->playback.pcm_status, buf, CHANNEL_STATUS_SIZE); + + /* turn on run bit and rock'n'roll */ + if (reset_timeout < 1) + reset_timeout = 1; + chip->reset_timeout = reset_timeout; + snd_cs8427_reset(chip); + + ret = snd_soc_register_codec(&chip->client->dev, &soc_codec_dev_cs8427, + cs8427_dai, ARRAY_SIZE(cs8427_dai)); + + return 0; + +__fail: + kfree(chip); + return err < 0 ? err : -EIO; +} + +static int __devexit cs8427_remove(struct i2c_client *client) +{ + struct cs8427 *chip; + struct cs8427_platform_data *pdata; + chip = dev_get_drvdata(&client->dev); + if (!chip) { + pr_err("invalid device info\n"); + return -ENODEV; + } + pdata = chip->client->dev.platform_data; + gpio_free(pdata->reset_gpio); + if (pdata->enable) + pdata->enable(0); + kfree(chip); + return 0; +} + +static struct i2c_device_id cs8427_id_table[] = { + {"cs8427", CS8427_ADDR0}, + {"cs8427", CS8427_ADDR2}, + {"cs8427", CS8427_ADDR3}, + {"cs8427", CS8427_ADDR4}, + {"cs8427", CS8427_ADDR5}, + {"cs8427", CS8427_ADDR6}, + {"cs8427", CS8427_ADDR7}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs8427_id_table); + +static struct i2c_driver cs8427_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "cs8427-spdif", + }, + .id_table = cs8427_id_table, + .probe = cs8427_i2c_probe, + .remove = __devexit_p(cs8427_remove), +}; + +static int __init cs8427_module_init(void) +{ + int ret = 0; + ret = i2c_add_driver(&cs8427_i2c_driver); + if (ret != 0) + pr_err("failed to add the I2C driver\n"); + return ret; +} + +static void __exit cs8427_module_exit(void) +{ + pr_info("module exit\n"); +} + +module_init(cs8427_module_init) +module_exit(cs8427_module_exit) + +MODULE_DESCRIPTION("CS8427 interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/msm_stub.c b/sound/soc/codecs/msm_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..0a3157f3c512c728e2feb33ef4af6eff6d31fa95 --- /dev/null +++ b/sound/soc/codecs/msm_stub.c @@ -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. + */ +#include +#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 0000000000000000000000000000000000000000..786b2d6b06a6fda079723390d2c0e745aa682a40 --- /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 0000000000000000000000000000000000000000..bd14eea3a6c9b9c6c732db830108d787d97d86b1 --- /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/wcd9304-tables.c b/sound/soc/codecs/wcd9304-tables.c new file mode 100644 index 0000000000000000000000000000000000000000..252cb0e861927d7d475418e4c5433505ba713288 --- /dev/null +++ b/sound/soc/codecs/wcd9304-tables.c @@ -0,0 +1,722 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "wcd9304.h" + +const u8 sitar_reg_defaults[SITAR_CACHE_SIZE] = { + [WCD9XXX_A_CHIP_CTL] = WCD9XXX_A_CHIP_CTL__POR, + [WCD9XXX_A_CHIP_STATUS] = WCD9XXX_A_CHIP_STATUS__POR, + [WCD9XXX_A_CHIP_ID_BYTE_0] = WCD9XXX_A_CHIP_ID_BYTE_0__POR, + [WCD9XXX_A_CHIP_ID_BYTE_1] = WCD9XXX_A_CHIP_ID_BYTE_1__POR, + [WCD9XXX_A_CHIP_ID_BYTE_2] = WCD9XXX_A_CHIP_ID_BYTE_2__POR, + [WCD9XXX_A_CHIP_ID_BYTE_3] = WCD9XXX_A_CHIP_ID_BYTE_3__POR, + [WCD9XXX_A_CHIP_VERSION] = WCD9XXX_A_CHIP_VERSION__POR, + [WCD9XXX_A_SB_VERSION] = WCD9XXX_A_SB_VERSION__POR, + [WCD9XXX_A_SLAVE_ID_1] = WCD9XXX_A_SLAVE_ID_1__POR, + [WCD9XXX_A_SLAVE_ID_2] = WCD9XXX_A_SLAVE_ID_2__POR, + [WCD9XXX_A_SLAVE_ID_3] = WCD9XXX_A_SLAVE_ID_3__POR, + [SITAR_A_PIN_CTL_OE0] = SITAR_A_PIN_CTL_OE0__POR, + [SITAR_A_PIN_CTL_OE1] = SITAR_A_PIN_CTL_OE1__POR, + [SITAR_A_PIN_CTL_DATA0] = SITAR_A_PIN_CTL_DATA0__POR, + [SITAR_A_PIN_CTL_DATA1] = SITAR_A_PIN_CTL_DATA1__POR, + [SITAR_A_HDRIVE_GENERIC] = SITAR_A_HDRIVE_GENERIC__POR, + [SITAR_A_HDRIVE_OVERRIDE] = SITAR_A_HDRIVE_OVERRIDE__POR, + [SITAR_A_ANA_CSR_WAIT_STATE] = SITAR_A_ANA_CSR_WAIT_STATE__POR, + [SITAR_A_PROCESS_MONITOR_CTL0] = SITAR_A_PROCESS_MONITOR_CTL0__POR, + [SITAR_A_PROCESS_MONITOR_CTL1] = SITAR_A_PROCESS_MONITOR_CTL1__POR, + [SITAR_A_PROCESS_MONITOR_CTL2] = SITAR_A_PROCESS_MONITOR_CTL2__POR, + [SITAR_A_PROCESS_MONITOR_CTL3] = SITAR_A_PROCESS_MONITOR_CTL3__POR, + [SITAR_A_QFUSE_CTL] = SITAR_A_QFUSE_CTL__POR, + [SITAR_A_QFUSE_STATUS] = SITAR_A_QFUSE_STATUS__POR, + [SITAR_A_QFUSE_DATA_OUT0] = SITAR_A_QFUSE_DATA_OUT0__POR, + [SITAR_A_QFUSE_DATA_OUT1] = SITAR_A_QFUSE_DATA_OUT1__POR, + [SITAR_A_QFUSE_DATA_OUT2] = SITAR_A_QFUSE_DATA_OUT2__POR, + [SITAR_A_QFUSE_DATA_OUT3] = SITAR_A_QFUSE_DATA_OUT3__POR, + [SITAR_A_CDC_CTL] = SITAR_A_CDC_CTL__POR, + [SITAR_A_LEAKAGE_CTL] = SITAR_A_LEAKAGE_CTL__POR, + [SITAR_A_INTR_MODE] = SITAR_A_INTR_MODE__POR, + [SITAR_A_INTR_MASK0] = SITAR_A_INTR_MASK0__POR, + [SITAR_A_INTR_MASK1] = SITAR_A_INTR_MASK1__POR, + [SITAR_A_INTR_MASK2] = SITAR_A_INTR_MASK2__POR, + [SITAR_A_INTR_STATUS0] = SITAR_A_INTR_STATUS0__POR, + [SITAR_A_INTR_STATUS1] = SITAR_A_INTR_STATUS1__POR, + [SITAR_A_INTR_STATUS2] = SITAR_A_INTR_STATUS2__POR, + [SITAR_A_INTR_CLEAR0] = SITAR_A_INTR_CLEAR0__POR, + [SITAR_A_INTR_CLEAR1] = SITAR_A_INTR_CLEAR1__POR, + [SITAR_A_INTR_CLEAR2] = SITAR_A_INTR_CLEAR2__POR, + [SITAR_A_INTR_LEVEL0] = SITAR_A_INTR_LEVEL0__POR, + [SITAR_A_INTR_LEVEL1] = SITAR_A_INTR_LEVEL1__POR, + [SITAR_A_INTR_LEVEL2] = SITAR_A_INTR_LEVEL2__POR, + [SITAR_A_INTR_TEST0] = SITAR_A_INTR_TEST0__POR, + [SITAR_A_INTR_TEST1] = SITAR_A_INTR_TEST1__POR, + [SITAR_A_INTR_TEST2] = SITAR_A_INTR_TEST2__POR, + [SITAR_A_INTR_SET0] = SITAR_A_INTR_SET0__POR, + [SITAR_A_INTR_SET1] = SITAR_A_INTR_SET1__POR, + [SITAR_A_INTR_SET2] = SITAR_A_INTR_SET2__POR, + [SITAR_A_CDC_TX_I2S_SCK_MODE] = SITAR_A_CDC_TX_I2S_SCK_MODE__POR, + [SITAR_A_CDC_TX_I2S_WS_MODE] = SITAR_A_CDC_TX_I2S_WS_MODE__POR, + [SITAR_A_CDC_DMIC_DATA0_MODE] = SITAR_A_CDC_DMIC_DATA0_MODE__POR, + [SITAR_A_CDC_DMIC_CLK0_MODE] = SITAR_A_CDC_DMIC_CLK0_MODE__POR, + [SITAR_A_CDC_DMIC_DATA1_MODE] = SITAR_A_CDC_DMIC_DATA1_MODE__POR, + [SITAR_A_CDC_DMIC_CLK1_MODE] = SITAR_A_CDC_DMIC_CLK1_MODE__POR, + [SITAR_A_CDC_TX_I2S_SD0_MODE] = SITAR_A_CDC_TX_I2S_SD0_MODE__POR, + [SITAR_A_CDC_INTR_MODE] = SITAR_A_CDC_INTR_MODE__POR, + [SITAR_A_CDC_RX_I2S_SD0_MODE] = SITAR_A_CDC_RX_I2S_SD0_MODE__POR, + [SITAR_A_CDC_RX_I2S_SD1_MODE] = SITAR_A_CDC_RX_I2S_SD1_MODE__POR, + [SITAR_A_BIAS_REF_CTL] = SITAR_A_BIAS_REF_CTL__POR, + [SITAR_A_BIAS_CENTRAL_BG_CTL] = SITAR_A_BIAS_CENTRAL_BG_CTL__POR, + [SITAR_A_BIAS_PRECHRG_CTL] = SITAR_A_BIAS_PRECHRG_CTL__POR, + [SITAR_A_BIAS_CURR_CTL_1] = SITAR_A_BIAS_CURR_CTL_1__POR, + [SITAR_A_BIAS_CURR_CTL_2] = SITAR_A_BIAS_CURR_CTL_2__POR, + [SITAR_A_BIAS_OSC_BG_CTL] = SITAR_A_BIAS_OSC_BG_CTL__POR, + [SITAR_A_CLK_BUFF_EN1] = SITAR_A_CLK_BUFF_EN1__POR, + [SITAR_A_CLK_BUFF_EN2] = SITAR_A_CLK_BUFF_EN2__POR, + [SITAR_A_LDO_H_MODE_1] = SITAR_A_LDO_H_MODE_1__POR, + [SITAR_A_LDO_H_MODE_2] = SITAR_A_LDO_H_MODE_2__POR, + [SITAR_A_LDO_H_LOOP_CTL] = SITAR_A_LDO_H_LOOP_CTL__POR, + [SITAR_A_LDO_H_COMP_1] = SITAR_A_LDO_H_COMP_1__POR, + [SITAR_A_LDO_H_COMP_2] = SITAR_A_LDO_H_COMP_2__POR, + [SITAR_A_LDO_H_BIAS_1] = SITAR_A_LDO_H_BIAS_1__POR, + [SITAR_A_LDO_H_BIAS_2] = SITAR_A_LDO_H_BIAS_2__POR, + [SITAR_A_LDO_H_BIAS_3] = SITAR_A_LDO_H_BIAS_3__POR, + [SITAR_A_MICB_CFILT_1_CTL] = SITAR_A_MICB_CFILT_1_CTL__POR, + [SITAR_A_MICB_CFILT_1_VAL] = SITAR_A_MICB_CFILT_1_VAL__POR, + [SITAR_A_MICB_CFILT_1_PRECHRG] = SITAR_A_MICB_CFILT_1_PRECHRG__POR, + [SITAR_A_MICB_1_CTL] = SITAR_A_MICB_1_CTL__POR, + [SITAR_A_MICB_1_INT_RBIAS] = SITAR_A_MICB_1_INT_RBIAS__POR, + [SITAR_A_MICB_1_MBHC] = SITAR_A_MICB_1_MBHC__POR, + [SITAR_A_MICB_CFILT_2_CTL] = SITAR_A_MICB_CFILT_2_CTL__POR, + [SITAR_A_MICB_CFILT_2_VAL] = SITAR_A_MICB_CFILT_2_VAL__POR, + [SITAR_A_MICB_CFILT_2_PRECHRG] = SITAR_A_MICB_CFILT_2_PRECHRG__POR, + [SITAR_A_MICB_2_CTL] = SITAR_A_MICB_2_CTL__POR, + [SITAR_A_MICB_2_INT_RBIAS] = SITAR_A_MICB_2_INT_RBIAS__POR, + [SITAR_A_MICB_2_MBHC] = SITAR_A_MICB_2_MBHC__POR, + [SITAR_A_TX_COM_BIAS] = SITAR_A_TX_COM_BIAS__POR, + [SITAR_A_MBHC_SCALING_MUX_1] = SITAR_A_MBHC_SCALING_MUX_1__POR, + [SITAR_A_MBHC_SCALING_MUX_2] = SITAR_A_MBHC_SCALING_MUX_2__POR, + [SITAR_A_TX_SUP_SWITCH_CTRL_1] = SITAR_A_TX_SUP_SWITCH_CTRL_1__POR, + [SITAR_A_TX_SUP_SWITCH_CTRL_2] = SITAR_A_TX_SUP_SWITCH_CTRL_2__POR, + [SITAR_A_TX_1_2_EN] = SITAR_A_TX_1_2_EN__POR, + [SITAR_A_TX_1_2_TEST_EN] = SITAR_A_TX_1_2_TEST_EN__POR, + [SITAR_A_TX_1_2_ADC_CH1] = SITAR_A_TX_1_2_ADC_CH1__POR, + [SITAR_A_TX_1_2_ADC_CH2] = SITAR_A_TX_1_2_ADC_CH2__POR, + [SITAR_A_TX_1_2_ATEST_REFCTRL] = SITAR_A_TX_1_2_ATEST_REFCTRL__POR, + [SITAR_A_TX_1_2_TEST_CTL] = SITAR_A_TX_1_2_TEST_CTL__POR, + [SITAR_A_TX_1_2_TEST_BLOCK_EN] = SITAR_A_TX_1_2_TEST_BLOCK_EN__POR, + [SITAR_A_TX_1_2_TXFE_CLKDIV] = SITAR_A_TX_1_2_TXFE_CLKDIV__POR, + [SITAR_A_TX_1_2_SAR_ERR_CH1] = SITAR_A_TX_1_2_SAR_ERR_CH1__POR, + [SITAR_A_TX_1_2_SAR_ERR_CH2] = SITAR_A_TX_1_2_SAR_ERR_CH2__POR, + [SITAR_A_TX_3_EN] = SITAR_A_TX_3_EN__POR, + [SITAR_A_TX_3_TEST_EN] = SITAR_A_TX_3_TEST_EN__POR, + [SITAR_A_TX_3_ADC] = SITAR_A_TX_3_ADC__POR, + [SITAR_A_TX_3_MBHC_ATEST_REFCTRL] = + SITAR_A_TX_3_MBHC_ATEST_REFCTRL__POR, + [SITAR_A_TX_3_TEST_CTL] = SITAR_A_TX_3_TEST_CTL__POR, + [SITAR_A_TX_3_TEST_BLOCK_EN] = SITAR_A_TX_3_TEST_BLOCK_EN__POR, + [SITAR_A_TX_3_TXFE_CKDIV] = SITAR_A_TX_3_TXFE_CKDIV__POR, + [SITAR_A_TX_3_SAR_ERR] = SITAR_A_TX_3_SAR_ERR__POR, + [SITAR_A_TX_4_MBHC_EN] = SITAR_A_TX_4_MBHC_EN__POR, + [SITAR_A_TX_4_MBHC_ADC] = SITAR_A_TX_4_MBHC_ADC__POR, + [SITAR_A_TX_4_MBHC_TEST_CTL] = SITAR_A_TX_4_MBHC_TEST_CTL__POR, + [SITAR_A_TX_4_MBHC_SAR_ERR] = SITAR_A_TX_4_MBHC_SAR_ERR__POR, + [SITAR_A_TX_4_TXFE_CLKDIV] = SITAR_A_TX_4_TXFE_CLKDIV__POR, + [SITAR_A_AUX_COM_CTL] = SITAR_A_AUX_COM_CTL__POR, + [SITAR_A_AUX_COM_ATEST] = SITAR_A_AUX_COM_ATEST__POR, + [SITAR_A_AUX_L_EN] = SITAR_A_AUX_L_EN__POR, + [SITAR_A_AUX_L_GAIN] = SITAR_A_AUX_L_GAIN__POR, + [SITAR_A_AUX_L_PA_CONN] = SITAR_A_AUX_L_PA_CONN__POR, + [SITAR_A_AUX_L_PA_CONN_INV] = SITAR_A_AUX_L_PA_CONN_INV__POR, + [SITAR_A_AUX_R_EN] = SITAR_A_AUX_R_EN__POR, + [SITAR_A_AUX_R_GAIN] = SITAR_A_AUX_R_GAIN__POR, + [SITAR_A_AUX_R_PA_CONN] = SITAR_A_AUX_R_PA_CONN__POR, + [SITAR_A_AUX_R_PA_CONN_INV] = SITAR_A_AUX_R_PA_CONN_INV__POR, + [SITAR_A_CP_EN] = SITAR_A_CP_EN__POR, + [SITAR_A_CP_CLK] = SITAR_A_CP_CLK__POR, + [SITAR_A_CP_STATIC] = SITAR_A_CP_STATIC__POR, + [SITAR_A_CP_DCC1] = SITAR_A_CP_DCC1__POR, + [SITAR_A_CP_DCC3] = SITAR_A_CP_DCC3__POR, + [SITAR_A_CP_ATEST] = SITAR_A_CP_ATEST__POR, + [SITAR_A_CP_DTEST] = SITAR_A_CP_DTEST__POR, + [SITAR_A_RX_COM_TIMER_DIV] = SITAR_A_RX_COM_TIMER_DIV__POR, + [SITAR_A_RX_COM_OCP_CTL] = SITAR_A_RX_COM_OCP_CTL__POR, + [SITAR_A_RX_COM_OCP_COUNT] = SITAR_A_RX_COM_OCP_COUNT__POR, + [SITAR_A_RX_COM_DAC_CTL] = SITAR_A_RX_COM_DAC_CTL__POR, + [SITAR_A_RX_COM_BIAS] = SITAR_A_RX_COM_BIAS__POR, + [SITAR_A_RX_HPH_BIAS_PA] = SITAR_A_RX_HPH_BIAS_PA__POR, + [SITAR_A_RX_HPH_BIAS_LDO] = SITAR_A_RX_HPH_BIAS_LDO__POR, + [SITAR_A_RX_HPH_BIAS_CNP] = SITAR_A_RX_HPH_BIAS_CNP__POR, + [SITAR_A_RX_HPH_BIAS_WG] = SITAR_A_RX_HPH_BIAS_WG__POR, + [SITAR_A_RX_HPH_OCP_CTL] = SITAR_A_RX_HPH_OCP_CTL__POR, + [SITAR_A_RX_HPH_CNP_EN] = SITAR_A_RX_HPH_CNP_EN__POR, + [SITAR_A_RX_HPH_CNP_WG_CTL] = SITAR_A_RX_HPH_CNP_WG_CTL__POR, + [SITAR_A_RX_HPH_CNP_WG_TIME] = SITAR_A_RX_HPH_CNP_WG_TIME__POR, + [SITAR_A_RX_HPH_L_GAIN] = SITAR_A_RX_HPH_L_GAIN__POR, + [SITAR_A_RX_HPH_L_TEST] = SITAR_A_RX_HPH_L_TEST__POR, + [SITAR_A_RX_HPH_L_PA_CTL] = SITAR_A_RX_HPH_L_PA_CTL__POR, + [SITAR_A_RX_HPH_L_DAC_CTL] = SITAR_A_RX_HPH_L_DAC_CTL__POR, + [SITAR_A_RX_HPH_L_ATEST] = SITAR_A_RX_HPH_L_ATEST__POR, + [SITAR_A_RX_HPH_L_STATUS] = SITAR_A_RX_HPH_L_STATUS__POR, + [SITAR_A_RX_HPH_R_GAIN] = SITAR_A_RX_HPH_R_GAIN__POR, + [SITAR_A_RX_HPH_R_TEST] = SITAR_A_RX_HPH_R_TEST__POR, + [SITAR_A_RX_HPH_R_PA_CTL] = SITAR_A_RX_HPH_R_PA_CTL__POR, + [SITAR_A_RX_HPH_R_DAC_CTL] = SITAR_A_RX_HPH_R_DAC_CTL__POR, + [SITAR_A_RX_HPH_R_ATEST] = SITAR_A_RX_HPH_R_ATEST__POR, + [SITAR_A_RX_HPH_R_STATUS] = SITAR_A_RX_HPH_R_STATUS__POR, + [SITAR_A_RX_EAR_BIAS_PA] = SITAR_A_RX_EAR_BIAS_PA__POR, + [SITAR_A_RX_EAR_BIAS_CMBUFF] = SITAR_A_RX_EAR_BIAS_CMBUFF__POR, + [SITAR_A_RX_EAR_EN] = SITAR_A_RX_EAR_EN__POR, + [SITAR_A_RX_EAR_GAIN] = SITAR_A_RX_EAR_GAIN__POR, + [SITAR_A_RX_EAR_CMBUFF] = SITAR_A_RX_EAR_CMBUFF__POR, + [SITAR_A_RX_EAR_ICTL] = SITAR_A_RX_EAR_ICTL__POR, + [SITAR_A_RX_EAR_CCOMP] = SITAR_A_RX_EAR_CCOMP__POR, + [SITAR_A_RX_EAR_VCM] = SITAR_A_RX_EAR_VCM__POR, + [SITAR_A_RX_EAR_CNP] = SITAR_A_RX_EAR_CNP__POR, + [SITAR_A_RX_EAR_ATEST] = SITAR_A_RX_EAR_ATEST__POR, + [SITAR_A_RX_EAR_STATUS] = SITAR_A_RX_EAR_STATUS__POR, + [SITAR_A_RX_LINE_BIAS_PA] = SITAR_A_RX_LINE_BIAS_PA__POR, + [SITAR_A_RX_LINE_BIAS_LDO] = SITAR_A_RX_LINE_BIAS_LDO__POR, + [SITAR_A_RX_LINE_BIAS_CNP1] = SITAR_A_RX_LINE_BIAS_CNP1__POR, + [SITAR_A_RX_LINE_COM] = SITAR_A_RX_LINE_COM__POR, + [SITAR_A_RX_LINE_CNP_EN] = SITAR_A_RX_LINE_CNP_EN__POR, + [SITAR_A_RX_LINE_CNP_WG_CTL] = SITAR_A_RX_LINE_CNP_WG_CTL__POR, + [SITAR_A_RX_LINE_CNP_WG_TIME] = SITAR_A_RX_LINE_CNP_WG_TIME__POR, + [SITAR_A_RX_LINE_1_GAIN] = SITAR_A_RX_LINE_1_GAIN__POR, + [SITAR_A_RX_LINE_1_TEST] = SITAR_A_RX_LINE_1_TEST__POR, + [SITAR_A_RX_LINE_1_DAC_CTL] = SITAR_A_RX_LINE_1_DAC_CTL__POR, + [SITAR_A_RX_LINE_1_STATUS] = SITAR_A_RX_LINE_1_STATUS__POR, + [SITAR_A_RX_LINE_2_GAIN] = SITAR_A_RX_LINE_2_GAIN__POR, + [SITAR_A_RX_LINE_2_TEST] = SITAR_A_RX_LINE_2_TEST__POR, + [SITAR_A_RX_LINE_2_DAC_CTL] = SITAR_A_RX_LINE_2_DAC_CTL__POR, + [SITAR_A_RX_LINE_2_STATUS] = SITAR_A_RX_LINE_2_STATUS__POR, + [SITAR_A_RX_LINE_BIAS_CNP2] = SITAR_A_RX_LINE_BIAS_CNP2__POR, + [SITAR_A_RX_LINE_OCP_CTL] = SITAR_A_RX_LINE_OCP_CTL__POR, + [SITAR_A_RX_LINE_1_PA_CTL] = SITAR_A_RX_LINE_1_PA_CTL__POR, + [SITAR_A_RX_LINE_2_PA_CTL] = SITAR_A_RX_LINE_2_PA_CTL__POR, + [SITAR_A_RX_LINE_CNP_DBG] = SITAR_A_RX_LINE_CNP_DBG__POR, + [SITAR_A_MBHC_HPH] = SITAR_A_MBHC_HPH__POR, + [SITAR_A_RC_OSC_FREQ] = SITAR_A_RC_OSC_FREQ__POR, + [SITAR_A_RC_OSC_TEST] = SITAR_A_RC_OSC_TEST__POR, + [SITAR_A_RC_OSC_STATUS] = SITAR_A_RC_OSC_STATUS__POR, + [SITAR_A_RC_OSC_TUNER] = SITAR_A_RC_OSC_TUNER__POR, + [SITAR_A_CDC_ANC1_CTL] = SITAR_A_CDC_ANC1_CTL__POR, + [SITAR_A_CDC_ANC1_SHIFT] = SITAR_A_CDC_ANC1_SHIFT__POR, + [SITAR_A_CDC_ANC1_IIR_B1_CTL] = SITAR_A_CDC_ANC1_IIR_B1_CTL__POR, + [SITAR_A_CDC_ANC1_IIR_B2_CTL] = SITAR_A_CDC_ANC1_IIR_B2_CTL__POR, + [SITAR_A_CDC_ANC1_IIR_B3_CTL] = SITAR_A_CDC_ANC1_IIR_B3_CTL__POR, + [SITAR_A_CDC_ANC1_IIR_B4_CTL] = SITAR_A_CDC_ANC1_IIR_B4_CTL__POR, + [SITAR_A_CDC_ANC1_LPF_B1_CTL] = SITAR_A_CDC_ANC1_LPF_B1_CTL__POR, + [SITAR_A_CDC_ANC1_LPF_B2_CTL] = SITAR_A_CDC_ANC1_LPF_B2_CTL__POR, + [SITAR_A_CDC_ANC1_LPF_B3_CTL] = SITAR_A_CDC_ANC1_LPF_B3_CTL__POR, + [SITAR_A_CDC_ANC1_SPARE] = SITAR_A_CDC_ANC1_SPARE__POR, + [SITAR_A_CDC_ANC1_SMLPF_CTL] = SITAR_A_CDC_ANC1_SMLPF_CTL__POR, + [SITAR_A_CDC_ANC1_DCFLT_CTL] = SITAR_A_CDC_ANC1_DCFLT_CTL__POR, + [SITAR_A_CDC_TX1_VOL_CTL_TIMER] = SITAR_A_CDC_TX1_VOL_CTL_TIMER__POR, + [SITAR_A_CDC_TX1_VOL_CTL_GAIN] = SITAR_A_CDC_TX1_VOL_CTL_GAIN__POR, + [SITAR_A_CDC_TX1_VOL_CTL_CFG] = SITAR_A_CDC_TX1_VOL_CTL_CFG__POR, + [SITAR_A_CDC_TX1_MUX_CTL] = SITAR_A_CDC_TX1_MUX_CTL__POR, + [SITAR_A_CDC_TX1_CLK_FS_CTL] = SITAR_A_CDC_TX1_CLK_FS_CTL__POR, + [SITAR_A_CDC_TX1_DMIC_CTL] = SITAR_A_CDC_TX1_DMIC_CTL__POR, + [SITAR_A_CDC_SRC1_PDA_CFG] = SITAR_A_CDC_SRC1_PDA_CFG__POR, + [SITAR_A_CDC_SRC1_FS_CTL] = SITAR_A_CDC_SRC1_FS_CTL__POR, + [SITAR_A_CDC_RX1_B1_CTL] = SITAR_A_CDC_RX1_B1_CTL__POR, + [SITAR_A_CDC_RX1_B2_CTL] = SITAR_A_CDC_RX1_B2_CTL__POR, + [SITAR_A_CDC_RX1_B3_CTL] = SITAR_A_CDC_RX1_B3_CTL__POR, + [SITAR_A_CDC_RX1_B4_CTL] = SITAR_A_CDC_RX1_B4_CTL__POR, + [SITAR_A_CDC_RX1_B5_CTL] = SITAR_A_CDC_RX1_B5_CTL__POR, + [SITAR_A_CDC_RX1_B6_CTL] = SITAR_A_CDC_RX1_B6_CTL__POR, + [SITAR_A_CDC_RX1_VOL_CTL_B1_CTL] = SITAR_A_CDC_RX1_VOL_CTL_B1_CTL__POR, + [SITAR_A_CDC_RX1_VOL_CTL_B2_CTL] = SITAR_A_CDC_RX1_VOL_CTL_B2_CTL__POR, + [SITAR_A_CDC_CLK_ANC_RESET_CTL] = SITAR_A_CDC_CLK_ANC_RESET_CTL__POR, + [SITAR_A_CDC_CLK_RX_RESET_CTL] = SITAR_A_CDC_CLK_RX_RESET_CTL__POR, + [SITAR_A_CDC_CLK_TX_RESET_B1_CTL] = + SITAR_A_CDC_CLK_TX_RESET_B1_CTL__POR, + [SITAR_A_CDC_CLK_TX_RESET_B2_CTL] = + SITAR_A_CDC_CLK_TX_RESET_B2_CTL__POR, + [SITAR_A_CDC_CLK_DMIC_CTL] = SITAR_A_CDC_CLK_DMIC_CTL__POR, + [SITAR_A_CDC_CLK_RX_I2S_CTL] = SITAR_A_CDC_CLK_RX_I2S_CTL__POR, + [SITAR_A_CDC_CLK_TX_I2S_CTL] = SITAR_A_CDC_CLK_TX_I2S_CTL__POR, + [SITAR_A_CDC_CLK_OTHR_RESET_CTL] = SITAR_A_CDC_CLK_OTHR_RESET_CTL__POR, + [SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL] = + SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL__POR, + [SITAR_A_CDC_CLK_OTHR_CTL] = SITAR_A_CDC_CLK_OTHR_CTL__POR, + [SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL] = + SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL__POR, + [SITAR_A_CDC_CLK_ANC_CLK_EN_CTL] = SITAR_A_CDC_CLK_ANC_CLK_EN_CTL__POR, + [SITAR_A_CDC_CLK_RX_B1_CTL] = SITAR_A_CDC_CLK_RX_B1_CTL__POR, + [SITAR_A_CDC_CLK_RX_B2_CTL] = SITAR_A_CDC_CLK_RX_B2_CTL__POR, + [SITAR_A_CDC_CLK_MCLK_CTL] = SITAR_A_CDC_CLK_MCLK_CTL__POR, + [SITAR_A_CDC_CLK_PDM_CTL] = SITAR_A_CDC_CLK_PDM_CTL__POR, + [SITAR_A_CDC_CLK_SD_CTL] = SITAR_A_CDC_CLK_SD_CTL__POR, + [SITAR_A_CDC_CLK_LP_CTL] = SITAR_A_CDC_CLK_LP_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL__POR, + [SITAR_A_CDC_CLSG_GAIN_THRESH_CTL] = + SITAR_A_CDC_CLSG_GAIN_THRESH_CTL__POR, + [SITAR_A_CDC_CLSG_TIMER_B1_CFG] = SITAR_A_CDC_CLSG_TIMER_B1_CFG__POR, + [SITAR_A_CDC_CLSG_TIMER_B2_CFG] = SITAR_A_CDC_CLSG_TIMER_B2_CFG__POR, + [SITAR_A_CDC_CLSG_CTL] = SITAR_A_CDC_CLSG_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B1_CTL] = SITAR_A_CDC_IIR1_GAIN_B1_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B2_CTL] = SITAR_A_CDC_IIR1_GAIN_B2_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B3_CTL] = SITAR_A_CDC_IIR1_GAIN_B3_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B4_CTL] = SITAR_A_CDC_IIR1_GAIN_B4_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B5_CTL] = SITAR_A_CDC_IIR1_GAIN_B5_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B6_CTL] = SITAR_A_CDC_IIR1_GAIN_B6_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B7_CTL] = SITAR_A_CDC_IIR1_GAIN_B7_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B8_CTL] = SITAR_A_CDC_IIR1_GAIN_B8_CTL__POR, + [SITAR_A_CDC_IIR1_CTL] = SITAR_A_CDC_IIR1_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_TIMER_CTL] = + SITAR_A_CDC_IIR1_GAIN_TIMER_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B1_CTL] = SITAR_A_CDC_IIR1_COEF_B1_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B2_CTL] = SITAR_A_CDC_IIR1_COEF_B2_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B3_CTL] = SITAR_A_CDC_IIR1_COEF_B3_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B4_CTL] = SITAR_A_CDC_IIR1_COEF_B4_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B5_CTL] = SITAR_A_CDC_IIR1_COEF_B5_CTL__POR, + [SITAR_A_CDC_TOP_GAIN_UPDATE] = SITAR_A_CDC_TOP_GAIN_UPDATE__POR, + [SITAR_A_CDC_TOP_RDAC_DOUT_CTL] = SITAR_A_CDC_TOP_RDAC_DOUT_CTL__POR, + [SITAR_A_CDC_DEBUG_B1_CTL] = SITAR_A_CDC_DEBUG_B1_CTL__POR, + [SITAR_A_CDC_DEBUG_B2_CTL] = SITAR_A_CDC_DEBUG_B2_CTL__POR, + [SITAR_A_CDC_DEBUG_B3_CTL] = SITAR_A_CDC_DEBUG_B3_CTL__POR, + [SITAR_A_CDC_DEBUG_B4_CTL] = SITAR_A_CDC_DEBUG_B4_CTL__POR, + [SITAR_A_CDC_DEBUG_B5_CTL] = SITAR_A_CDC_DEBUG_B5_CTL__POR, + [SITAR_A_CDC_DEBUG_B6_CTL] = SITAR_A_CDC_DEBUG_B6_CTL__POR, + [SITAR_A_CDC_DEBUG_B7_CTL] = SITAR_A_CDC_DEBUG_B7_CTL__POR, + [SITAR_A_CDC_COMP1_B1_CTL] = SITAR_A_CDC_COMP1_B1_CTL__POR, + [SITAR_A_CDC_COMP1_B2_CTL] = SITAR_A_CDC_COMP1_B2_CTL__POR, + [SITAR_A_CDC_COMP1_B3_CTL] = SITAR_A_CDC_COMP1_B3_CTL__POR, + [SITAR_A_CDC_COMP1_B4_CTL] = SITAR_A_CDC_COMP1_B4_CTL__POR, + [SITAR_A_CDC_COMP1_B5_CTL] = SITAR_A_CDC_COMP1_B5_CTL__POR, + [SITAR_A_CDC_COMP1_B6_CTL] = SITAR_A_CDC_COMP1_B6_CTL__POR, + [SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS] = + SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS__POR, + [SITAR_A_CDC_COMP1_FS_CFG] = SITAR_A_CDC_COMP1_FS_CFG__POR, + [SITAR_A_CDC_CONN_RX1_B1_CTL] = SITAR_A_CDC_CONN_RX1_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX1_B2_CTL] = SITAR_A_CDC_CONN_RX1_B2_CTL__POR, + [SITAR_A_CDC_CONN_RX1_B3_CTL] = SITAR_A_CDC_CONN_RX1_B3_CTL__POR, + [SITAR_A_CDC_CONN_RX2_B1_CTL] = SITAR_A_CDC_CONN_RX2_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX2_B2_CTL] = SITAR_A_CDC_CONN_RX2_B2_CTL__POR, + [SITAR_A_CDC_CONN_RX2_B3_CTL] = SITAR_A_CDC_CONN_RX2_B3_CTL__POR, + [SITAR_A_CDC_CONN_RX3_B1_CTL] = SITAR_A_CDC_CONN_RX3_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX3_B2_CTL] = SITAR_A_CDC_CONN_RX3_B2_CTL__POR, + [SITAR_A_CDC_CONN_RX3_B3_CTL] = SITAR_A_CDC_CONN_RX3_B3_CTL__POR, + [SITAR_A_CDC_CONN_ANC_B1_CTL] = SITAR_A_CDC_CONN_ANC_B1_CTL__POR, + [SITAR_A_CDC_CONN_ANC_B2_CTL] = SITAR_A_CDC_CONN_ANC_B2_CTL__POR, + [SITAR_A_CDC_CONN_TX_B1_CTL] = SITAR_A_CDC_CONN_TX_B1_CTL__POR, + [SITAR_A_CDC_CONN_TX_B2_CTL] = SITAR_A_CDC_CONN_TX_B2_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B1_CTL] = SITAR_A_CDC_CONN_EQ1_B1_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B2_CTL] = SITAR_A_CDC_CONN_EQ1_B2_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B3_CTL] = SITAR_A_CDC_CONN_EQ1_B3_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B4_CTL] = SITAR_A_CDC_CONN_EQ1_B4_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B1_CTL] = SITAR_A_CDC_CONN_EQ2_B1_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B2_CTL] = SITAR_A_CDC_CONN_EQ2_B2_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B3_CTL] = SITAR_A_CDC_CONN_EQ2_B3_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B4_CTL] = SITAR_A_CDC_CONN_EQ2_B4_CTL__POR, + [SITAR_A_CDC_CONN_SRC1_B1_CTL] = SITAR_A_CDC_CONN_SRC1_B1_CTL__POR, + [SITAR_A_CDC_CONN_SRC1_B2_CTL] = SITAR_A_CDC_CONN_SRC1_B2_CTL__POR, + [SITAR_A_CDC_CONN_SRC2_B1_CTL] = SITAR_A_CDC_CONN_SRC2_B1_CTL__POR, + [SITAR_A_CDC_CONN_SRC2_B2_CTL] = SITAR_A_CDC_CONN_SRC2_B2_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B1_CTL] = SITAR_A_CDC_CONN_TX_SB_B1_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B2_CTL] = SITAR_A_CDC_CONN_TX_SB_B2_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B3_CTL] = SITAR_A_CDC_CONN_TX_SB_B3_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B4_CTL] = SITAR_A_CDC_CONN_TX_SB_B4_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B5_CTL] = SITAR_A_CDC_CONN_TX_SB_B5_CTL__POR, + [SITAR_A_CDC_CONN_RX_SB_B1_CTL] = SITAR_A_CDC_CONN_RX_SB_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX_SB_B2_CTL] = SITAR_A_CDC_CONN_RX_SB_B2_CTL__POR, + [SITAR_A_CDC_CONN_CLSG_CTL] = SITAR_A_CDC_CONN_CLSG_CTL__POR, + [SITAR_A_CDC_CONN_SPARE] = SITAR_A_CDC_CONN_SPARE__POR, + [SITAR_A_CDC_MBHC_EN_CTL] = SITAR_A_CDC_MBHC_EN_CTL__POR, + [SITAR_A_CDC_MBHC_FIR_B1_CFG] = SITAR_A_CDC_MBHC_FIR_B1_CFG__POR, + [SITAR_A_CDC_MBHC_FIR_B2_CFG] = SITAR_A_CDC_MBHC_FIR_B2_CFG__POR, + [SITAR_A_CDC_MBHC_TIMER_B1_CTL] = SITAR_A_CDC_MBHC_TIMER_B1_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B2_CTL] = SITAR_A_CDC_MBHC_TIMER_B2_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B3_CTL] = SITAR_A_CDC_MBHC_TIMER_B3_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B4_CTL] = SITAR_A_CDC_MBHC_TIMER_B4_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B5_CTL] = SITAR_A_CDC_MBHC_TIMER_B5_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B6_CTL] = SITAR_A_CDC_MBHC_TIMER_B6_CTL__POR, + [SITAR_A_CDC_MBHC_B1_STATUS] = SITAR_A_CDC_MBHC_B1_STATUS__POR, + [SITAR_A_CDC_MBHC_B2_STATUS] = SITAR_A_CDC_MBHC_B2_STATUS__POR, + [SITAR_A_CDC_MBHC_B3_STATUS] = SITAR_A_CDC_MBHC_B3_STATUS__POR, + [SITAR_A_CDC_MBHC_B4_STATUS] = SITAR_A_CDC_MBHC_B4_STATUS__POR, + [SITAR_A_CDC_MBHC_B5_STATUS] = SITAR_A_CDC_MBHC_B5_STATUS__POR, + [SITAR_A_CDC_MBHC_B1_CTL] = SITAR_A_CDC_MBHC_B1_CTL__POR, + [SITAR_A_CDC_MBHC_B2_CTL] = SITAR_A_CDC_MBHC_B2_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B1_CTL] = SITAR_A_CDC_MBHC_VOLT_B1_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B2_CTL] = SITAR_A_CDC_MBHC_VOLT_B2_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B3_CTL] = SITAR_A_CDC_MBHC_VOLT_B3_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B4_CTL] = SITAR_A_CDC_MBHC_VOLT_B4_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B5_CTL] = SITAR_A_CDC_MBHC_VOLT_B5_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B6_CTL] = SITAR_A_CDC_MBHC_VOLT_B6_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B7_CTL] = SITAR_A_CDC_MBHC_VOLT_B7_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B8_CTL] = SITAR_A_CDC_MBHC_VOLT_B8_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B9_CTL] = SITAR_A_CDC_MBHC_VOLT_B9_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B10_CTL] = SITAR_A_CDC_MBHC_VOLT_B10_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B11_CTL] = SITAR_A_CDC_MBHC_VOLT_B11_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B12_CTL] = SITAR_A_CDC_MBHC_VOLT_B12_CTL__POR, + [SITAR_A_CDC_MBHC_CLK_CTL] = SITAR_A_CDC_MBHC_CLK_CTL__POR, + [SITAR_A_CDC_MBHC_INT_CTL] = SITAR_A_CDC_MBHC_INT_CTL__POR, + [SITAR_A_CDC_MBHC_DEBUG_CTL] = SITAR_A_CDC_MBHC_DEBUG_CTL__POR, + [SITAR_A_CDC_MBHC_SPARE] = SITAR_A_CDC_MBHC_SPARE__POR, +}; + +const u8 sitar_reg_readable[SITAR_CACHE_SIZE] = { + [WCD9XXX_A_CHIP_CTL] = 1, + [WCD9XXX_A_CHIP_STATUS] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_0] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_1] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_2] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_3] = 1, + [WCD9XXX_A_CHIP_VERSION] = 1, + [WCD9XXX_A_SB_VERSION] = 1, + [WCD9XXX_A_SLAVE_ID_1] = 1, + [WCD9XXX_A_SLAVE_ID_2] = 1, + [WCD9XXX_A_SLAVE_ID_3] = 1, + [SITAR_A_PIN_CTL_OE0] = 1, + [SITAR_A_PIN_CTL_OE1] = 1, + [SITAR_A_PIN_CTL_DATA0] = 1, + [SITAR_A_PIN_CTL_DATA1] = 1, + [SITAR_A_HDRIVE_GENERIC] = 1, + [SITAR_A_HDRIVE_OVERRIDE] = 1, + [SITAR_A_ANA_CSR_WAIT_STATE] = 1, + [SITAR_A_PROCESS_MONITOR_CTL0] = 1, + [SITAR_A_PROCESS_MONITOR_CTL1] = 1, + [SITAR_A_PROCESS_MONITOR_CTL2] = 1, + [SITAR_A_PROCESS_MONITOR_CTL3] = 1, + [SITAR_A_QFUSE_CTL] = 1, + [SITAR_A_QFUSE_STATUS] = 1, + [SITAR_A_QFUSE_DATA_OUT0] = 1, + [SITAR_A_QFUSE_DATA_OUT1] = 1, + [SITAR_A_QFUSE_DATA_OUT2] = 1, + [SITAR_A_QFUSE_DATA_OUT3] = 1, + [SITAR_A_CDC_CTL] = 1, + [SITAR_A_LEAKAGE_CTL] = 1, + [SITAR_A_INTR_MODE] = 1, + [SITAR_A_INTR_MASK0] = 1, + [SITAR_A_INTR_MASK1] = 1, + [SITAR_A_INTR_MASK2] = 1, + [SITAR_A_INTR_STATUS0] = 1, + [SITAR_A_INTR_STATUS1] = 1, + [SITAR_A_INTR_STATUS2] = 1, + [SITAR_A_INTR_LEVEL0] = 1, + [SITAR_A_INTR_LEVEL1] = 1, + [SITAR_A_INTR_LEVEL2] = 1, + [SITAR_A_INTR_TEST0] = 1, + [SITAR_A_INTR_TEST1] = 1, + [SITAR_A_INTR_TEST2] = 1, + [SITAR_A_INTR_SET0] = 1, + [SITAR_A_INTR_SET1] = 1, + [SITAR_A_INTR_SET2] = 1, + [SITAR_A_CDC_TX_I2S_SCK_MODE] = 1, + [SITAR_A_CDC_TX_I2S_WS_MODE] = 1, + [SITAR_A_CDC_DMIC_DATA0_MODE] = 1, + [SITAR_A_CDC_DMIC_CLK0_MODE] = 1, + [SITAR_A_CDC_DMIC_DATA1_MODE] = 1, + [SITAR_A_CDC_DMIC_CLK1_MODE] = 1, + [SITAR_A_CDC_TX_I2S_SD0_MODE] = 1, + [SITAR_A_CDC_INTR_MODE] = 1, + [SITAR_A_CDC_RX_I2S_SD0_MODE] = 1, + [SITAR_A_CDC_RX_I2S_SD1_MODE] = 1, + [SITAR_A_BIAS_REF_CTL] = 1, + [SITAR_A_BIAS_CENTRAL_BG_CTL] = 1, + [SITAR_A_BIAS_PRECHRG_CTL] = 1, + [SITAR_A_BIAS_CURR_CTL_1] = 1, + [SITAR_A_BIAS_CURR_CTL_2] = 1, + [SITAR_A_BIAS_OSC_BG_CTL] = 1, + [SITAR_A_CLK_BUFF_EN1] = 1, + [SITAR_A_CLK_BUFF_EN2] = 1, + [SITAR_A_LDO_H_MODE_1] = 1, + [SITAR_A_LDO_H_MODE_2] = 1, + [SITAR_A_LDO_H_LOOP_CTL] = 1, + [SITAR_A_LDO_H_COMP_1] = 1, + [SITAR_A_LDO_H_COMP_2] = 1, + [SITAR_A_LDO_H_BIAS_1] = 1, + [SITAR_A_LDO_H_BIAS_2] = 1, + [SITAR_A_LDO_H_BIAS_3] = 1, + [SITAR_A_MICB_CFILT_1_CTL] = 1, + [SITAR_A_MICB_CFILT_1_VAL] = 1, + [SITAR_A_MICB_CFILT_1_PRECHRG] = 1, + [SITAR_A_MICB_1_CTL] = 1, + [SITAR_A_MICB_1_INT_RBIAS] = 1, + [SITAR_A_MICB_1_MBHC] = 1, + [SITAR_A_MICB_CFILT_2_CTL] = 1, + [SITAR_A_MICB_CFILT_2_VAL] = 1, + [SITAR_A_MICB_CFILT_2_PRECHRG] = 1, + [SITAR_A_MICB_2_CTL] = 1, + [SITAR_A_MICB_2_INT_RBIAS] = 1, + [SITAR_A_MICB_2_MBHC] = 1, + [SITAR_A_TX_COM_BIAS] = 1, + [SITAR_A_MBHC_SCALING_MUX_1] = 1, + [SITAR_A_MBHC_SCALING_MUX_2] = 1, + [SITAR_A_TX_SUP_SWITCH_CTRL_1] = 1, + [SITAR_A_TX_SUP_SWITCH_CTRL_2] = 1, + [SITAR_A_TX_1_2_EN] = 1, + [SITAR_A_TX_1_2_TEST_EN] = 1, + [SITAR_A_TX_1_2_ADC_CH1] = 1, + [SITAR_A_TX_1_2_ADC_CH2] = 1, + [SITAR_A_TX_1_2_ATEST_REFCTRL] = 1, + [SITAR_A_TX_1_2_TEST_CTL] = 1, + [SITAR_A_TX_1_2_TEST_BLOCK_EN] = 1, + [SITAR_A_TX_1_2_TXFE_CLKDIV] = 1, + [SITAR_A_TX_1_2_SAR_ERR_CH1] = 1, + [SITAR_A_TX_1_2_SAR_ERR_CH2] = 1, + [SITAR_A_TX_3_EN] = 1, + [SITAR_A_TX_3_TEST_EN] = 1, + [SITAR_A_TX_3_ADC] = 1, + [SITAR_A_TX_3_MBHC_ATEST_REFCTRL] = 1, + [SITAR_A_TX_3_TEST_CTL] = 1, + [SITAR_A_TX_3_TEST_BLOCK_EN] = 1, + [SITAR_A_TX_3_TXFE_CKDIV] = 1, + [SITAR_A_TX_3_SAR_ERR] = 1, + [SITAR_A_TX_4_MBHC_EN] = 1, + [SITAR_A_TX_4_MBHC_ADC] = 1, + [SITAR_A_TX_4_MBHC_TEST_CTL] = 1, + [SITAR_A_TX_4_MBHC_SAR_ERR] = 1, + [SITAR_A_TX_4_TXFE_CLKDIV] = 1, + [SITAR_A_AUX_COM_CTL] = 1, + [SITAR_A_AUX_COM_ATEST] = 1, + [SITAR_A_AUX_L_EN] = 1, + [SITAR_A_AUX_L_GAIN] = 1, + [SITAR_A_AUX_L_PA_CONN] = 1, + [SITAR_A_AUX_L_PA_CONN_INV] = 1, + [SITAR_A_AUX_R_EN] = 1, + [SITAR_A_AUX_R_GAIN] = 1, + [SITAR_A_AUX_R_PA_CONN] = 1, + [SITAR_A_AUX_R_PA_CONN_INV] = 1, + [SITAR_A_CP_EN] = 1, + [SITAR_A_CP_CLK] = 1, + [SITAR_A_CP_STATIC] = 1, + [SITAR_A_CP_DCC1] = 1, + [SITAR_A_CP_DCC3] = 1, + [SITAR_A_CP_ATEST] = 1, + [SITAR_A_CP_DTEST] = 1, + [SITAR_A_RX_COM_TIMER_DIV] = 1, + [SITAR_A_RX_COM_OCP_CTL] = 1, + [SITAR_A_RX_COM_OCP_COUNT] = 1, + [SITAR_A_RX_COM_DAC_CTL] = 1, + [SITAR_A_RX_COM_BIAS] = 1, + [SITAR_A_RX_HPH_BIAS_PA] = 1, + [SITAR_A_RX_HPH_BIAS_LDO] = 1, + [SITAR_A_RX_HPH_BIAS_CNP] = 1, + [SITAR_A_RX_HPH_BIAS_WG] = 1, + [SITAR_A_RX_HPH_OCP_CTL] = 1, + [SITAR_A_RX_HPH_CNP_EN] = 1, + [SITAR_A_RX_HPH_CNP_WG_CTL] = 1, + [SITAR_A_RX_HPH_CNP_WG_TIME] = 1, + [SITAR_A_RX_HPH_L_GAIN] = 1, + [SITAR_A_RX_HPH_L_TEST] = 1, + [SITAR_A_RX_HPH_L_PA_CTL] = 1, + [SITAR_A_RX_HPH_L_DAC_CTL] = 1, + [SITAR_A_RX_HPH_L_ATEST] = 1, + [SITAR_A_RX_HPH_L_STATUS] = 1, + [SITAR_A_RX_HPH_R_GAIN] = 1, + [SITAR_A_RX_HPH_R_TEST] = 1, + [SITAR_A_RX_HPH_R_PA_CTL] = 1, + [SITAR_A_RX_HPH_R_DAC_CTL] = 1, + [SITAR_A_RX_HPH_R_ATEST] = 1, + [SITAR_A_RX_HPH_R_STATUS] = 1, + [SITAR_A_RX_EAR_BIAS_PA] = 1, + [SITAR_A_RX_EAR_BIAS_CMBUFF] = 1, + [SITAR_A_RX_EAR_EN] = 1, + [SITAR_A_RX_EAR_GAIN] = 1, + [SITAR_A_RX_EAR_CMBUFF] = 1, + [SITAR_A_RX_EAR_ICTL] = 1, + [SITAR_A_RX_EAR_CCOMP] = 1, + [SITAR_A_RX_EAR_VCM] = 1, + [SITAR_A_RX_EAR_CNP] = 1, + [SITAR_A_RX_EAR_ATEST] = 1, + [SITAR_A_RX_EAR_STATUS] = 1, + [SITAR_A_RX_LINE_BIAS_PA] = 1, + [SITAR_A_RX_LINE_BIAS_LDO] = 1, + [SITAR_A_RX_LINE_BIAS_CNP1] = 1, + [SITAR_A_RX_LINE_COM] = 1, + [SITAR_A_RX_LINE_CNP_EN] = 1, + [SITAR_A_RX_LINE_CNP_WG_CTL] = 1, + [SITAR_A_RX_LINE_CNP_WG_TIME] = 1, + [SITAR_A_RX_LINE_1_GAIN] = 1, + [SITAR_A_RX_LINE_1_TEST] = 1, + [SITAR_A_RX_LINE_1_DAC_CTL] = 1, + [SITAR_A_RX_LINE_1_STATUS] = 1, + [SITAR_A_RX_LINE_2_GAIN] = 1, + [SITAR_A_RX_LINE_2_TEST] = 1, + [SITAR_A_RX_LINE_2_DAC_CTL] = 1, + [SITAR_A_RX_LINE_2_STATUS] = 1, + [SITAR_A_RX_LINE_BIAS_CNP2] = 1, + [SITAR_A_RX_LINE_OCP_CTL] = 1, + [SITAR_A_RX_LINE_1_PA_CTL] = 1, + [SITAR_A_RX_LINE_2_PA_CTL] = 1, + [SITAR_A_RX_LINE_CNP_DBG] = 1, + [SITAR_A_MBHC_HPH] = 1, + [SITAR_A_RC_OSC_FREQ] = 1, + [SITAR_A_RC_OSC_TEST] = 1, + [SITAR_A_RC_OSC_STATUS] = 1, + [SITAR_A_RC_OSC_TUNER] = 1, + [SITAR_A_CDC_ANC1_CTL] = 1, + [SITAR_A_CDC_ANC1_SHIFT] = 1, + [SITAR_A_CDC_ANC1_IIR_B1_CTL] = 1, + [SITAR_A_CDC_ANC1_IIR_B2_CTL] = 1, + [SITAR_A_CDC_ANC1_IIR_B3_CTL] = 1, + [SITAR_A_CDC_ANC1_IIR_B4_CTL] = 1, + [SITAR_A_CDC_ANC1_LPF_B1_CTL] = 1, + [SITAR_A_CDC_ANC1_LPF_B2_CTL] = 1, + [SITAR_A_CDC_ANC1_LPF_B3_CTL] = 1, + [SITAR_A_CDC_ANC1_SPARE] = 1, + [SITAR_A_CDC_ANC1_SMLPF_CTL] = 1, + [SITAR_A_CDC_ANC1_DCFLT_CTL] = 1, + [SITAR_A_CDC_TX1_VOL_CTL_TIMER] = 1, + [SITAR_A_CDC_TX1_VOL_CTL_GAIN] = 1, + [SITAR_A_CDC_TX1_VOL_CTL_CFG] = 1, + [SITAR_A_CDC_TX1_MUX_CTL] = 1, + [SITAR_A_CDC_TX1_CLK_FS_CTL] = 1, + [SITAR_A_CDC_TX1_DMIC_CTL] = 1, + [SITAR_A_CDC_SRC1_PDA_CFG] = 1, + [SITAR_A_CDC_SRC1_FS_CTL] = 1, + [SITAR_A_CDC_RX1_B1_CTL] = 1, + [SITAR_A_CDC_RX1_B2_CTL] = 1, + [SITAR_A_CDC_RX1_B3_CTL] = 1, + [SITAR_A_CDC_RX1_B4_CTL] = 1, + [SITAR_A_CDC_RX1_B5_CTL] = 1, + [SITAR_A_CDC_RX2_B5_CTL] = 1, + [SITAR_A_CDC_RX3_B5_CTL] = 1, + [SITAR_A_CDC_RX1_B6_CTL] = 1, + [SITAR_A_CDC_RX1_VOL_CTL_B1_CTL] = 1, + [SITAR_A_CDC_RX1_VOL_CTL_B2_CTL] = 1, + [SITAR_A_CDC_CLK_ANC_RESET_CTL] = 1, + [SITAR_A_CDC_CLK_RX_RESET_CTL] = 1, + [SITAR_A_CDC_CLK_TX_RESET_B1_CTL] = 1, + [SITAR_A_CDC_CLK_TX_RESET_B2_CTL] = 1, + [SITAR_A_CDC_CLK_DMIC_CTL] = 1, + [SITAR_A_CDC_CLK_RX_I2S_CTL] = 1, + [SITAR_A_CDC_CLK_TX_I2S_CTL] = 1, + [SITAR_A_CDC_CLK_OTHR_RESET_CTL] = 1, + [SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL] = 1, + [SITAR_A_CDC_CLK_OTHR_CTL] = 1, + [SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL] = 1, + [SITAR_A_CDC_CLK_ANC_CLK_EN_CTL] = 1, + [SITAR_A_CDC_CLK_RX_B1_CTL] = 1, + [SITAR_A_CDC_CLK_RX_B2_CTL] = 1, + [SITAR_A_CDC_CLK_MCLK_CTL] = 1, + [SITAR_A_CDC_CLK_PDM_CTL] = 1, + [SITAR_A_CDC_CLK_SD_CTL] = 1, + [SITAR_A_CDC_CLK_LP_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = 1, + [SITAR_A_CDC_CLSG_GAIN_THRESH_CTL] = 1, + [SITAR_A_CDC_CLSG_TIMER_B1_CFG] = 1, + [SITAR_A_CDC_CLSG_TIMER_B2_CFG] = 1, + [SITAR_A_CDC_CLSG_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B1_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B2_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B3_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B4_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B5_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B6_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B7_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B8_CTL] = 1, + [SITAR_A_CDC_IIR1_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_TIMER_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B1_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B2_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B3_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B4_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B5_CTL] = 1, + [SITAR_A_CDC_TOP_GAIN_UPDATE] = 1, + [SITAR_A_CDC_TOP_RDAC_DOUT_CTL] = 1, + [SITAR_A_CDC_DEBUG_B1_CTL] = 1, + [SITAR_A_CDC_DEBUG_B2_CTL] = 1, + [SITAR_A_CDC_DEBUG_B3_CTL] = 1, + [SITAR_A_CDC_DEBUG_B4_CTL] = 1, + [SITAR_A_CDC_DEBUG_B5_CTL] = 1, + [SITAR_A_CDC_DEBUG_B6_CTL] = 1, + [SITAR_A_CDC_DEBUG_B7_CTL] = 1, + [SITAR_A_CDC_COMP1_B1_CTL] = 1, + [SITAR_A_CDC_COMP1_B2_CTL] = 1, + [SITAR_A_CDC_COMP1_B3_CTL] = 1, + [SITAR_A_CDC_COMP1_B4_CTL] = 1, + [SITAR_A_CDC_COMP1_B5_CTL] = 1, + [SITAR_A_CDC_COMP1_B6_CTL] = 1, + [SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS] = 1, + [SITAR_A_CDC_COMP1_FS_CFG] = 1, + [SITAR_A_CDC_CONN_RX1_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX1_B2_CTL] = 1, + [SITAR_A_CDC_CONN_RX1_B3_CTL] = 1, + [SITAR_A_CDC_CONN_RX2_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX2_B2_CTL] = 1, + [SITAR_A_CDC_CONN_RX2_B3_CTL] = 1, + [SITAR_A_CDC_CONN_RX3_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX3_B2_CTL] = 1, + [SITAR_A_CDC_CONN_RX3_B3_CTL] = 1, + [SITAR_A_CDC_CONN_ANC_B1_CTL] = 1, + [SITAR_A_CDC_CONN_ANC_B2_CTL] = 1, + [SITAR_A_CDC_CONN_TX_B1_CTL] = 1, + [SITAR_A_CDC_CONN_TX_B2_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B1_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B2_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B3_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B4_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B1_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B2_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B3_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B4_CTL] = 1, + [SITAR_A_CDC_CONN_SRC1_B1_CTL] = 1, + [SITAR_A_CDC_CONN_SRC1_B2_CTL] = 1, + [SITAR_A_CDC_CONN_SRC2_B1_CTL] = 1, + [SITAR_A_CDC_CONN_SRC2_B2_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B1_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B2_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B3_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B4_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B5_CTL] = 1, + [SITAR_A_CDC_CONN_RX_SB_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX_SB_B2_CTL] = 1, + [SITAR_A_CDC_CONN_CLSG_CTL] = 1, + [SITAR_A_CDC_CONN_SPARE] = 1, + [SITAR_A_CDC_MBHC_EN_CTL] = 1, + [SITAR_A_CDC_MBHC_FIR_B1_CFG] = 1, + [SITAR_A_CDC_MBHC_FIR_B2_CFG] = 1, + [SITAR_A_CDC_MBHC_TIMER_B1_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B2_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B3_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B4_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B5_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B6_CTL] = 1, + [SITAR_A_CDC_MBHC_B1_STATUS] = 1, + [SITAR_A_CDC_MBHC_B2_STATUS] = 1, + [SITAR_A_CDC_MBHC_B3_STATUS] = 1, + [SITAR_A_CDC_MBHC_B4_STATUS] = 1, + [SITAR_A_CDC_MBHC_B5_STATUS] = 1, + [SITAR_A_CDC_MBHC_B1_CTL] = 1, + [SITAR_A_CDC_MBHC_B2_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B1_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B2_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B3_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B4_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B5_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B6_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B7_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B8_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B9_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B10_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B11_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B12_CTL] = 1, + [SITAR_A_CDC_MBHC_CLK_CTL] = 1, + [SITAR_A_CDC_MBHC_INT_CTL] = 1, + [SITAR_A_CDC_MBHC_DEBUG_CTL] = 1, + [SITAR_A_CDC_MBHC_SPARE] = 1, +}; diff --git a/sound/soc/codecs/wcd9304.c b/sound/soc/codecs/wcd9304.c new file mode 100644 index 0000000000000000000000000000000000000000..c5dbdaee3cb96509eadfa2f80fab3da20d67fe23 --- /dev/null +++ b/sound/soc/codecs/wcd9304.c @@ -0,0 +1,4817 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "wcd9304.h" + +#define WCD9304_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_48000) + +#define NUM_DECIMATORS 4 +#define NUM_INTERPOLATORS 3 +#define BITS_PER_REG 8 +#define SITAR_CFILT_FAST_MODE 0x00 +#define SITAR_CFILT_SLOW_MODE 0x40 +#define MBHC_FW_READ_ATTEMPTS 15 +#define MBHC_FW_READ_TIMEOUT 2000000 + +#define SITAR_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR) + +#define SITAR_I2S_MASTER_MODE_MASK 0x08 + +#define SITAR_OCP_ATTEMPT 1 + +#define AIF1_PB 1 +#define AIF1_CAP 2 +#define NUM_CODEC_DAIS 2 + +struct sitar_codec_dai_data { + u32 rate; + u32 *ch_num; + u32 ch_act; + u32 ch_tot; +}; + +#define SITAR_MCLK_RATE_12288KHZ 12288000 +#define SITAR_MCLK_RATE_9600KHZ 9600000 + +#define SITAR_FAKE_INS_THRESHOLD_MS 2500 +#define SITAR_FAKE_REMOVAL_MIN_PERIOD_MS 50 +#define SITAR_MBHC_BUTTON_MIN 0x8000 +#define SITAR_GPIO_IRQ_DEBOUNCE_TIME_US 5000 + +#define SITAR_ACQUIRE_LOCK(x) do { mutex_lock(&x); } while (0) +#define SITAR_RELEASE_LOCK(x) do { mutex_unlock(&x); } while (0) + +#define MBHC_NUM_DCE_PLUG_DETECT 3 +#define SITAR_MBHC_FAKE_INSERT_LOW 10 +#define SITAR_MBHC_FAKE_INSERT_HIGH 80 +#define SITAR_MBHC_FAKE_INSERT_VOLT_DELTA_MV 500 +#define SITAR_HS_DETECT_PLUG_TIME_MS (5 * 1000) +#define SITAR_HS_DETECT_PLUG_INERVAL_MS 100 +#define NUM_ATTEMPTS_TO_REPORT 5 +#define SITAR_MBHC_STATUS_REL_DETECTION 0x0C +#define SITAR_MBHC_GPIO_REL_DEBOUNCE_TIME_MS 200 + +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); +static struct snd_soc_dai_driver sitar_dai[]; +static int sitar_codec_enable_slimtx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +static int sitar_codec_enable_slimrx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +enum sitar_bandgap_type { + SITAR_BANDGAP_OFF = 0, + SITAR_BANDGAP_AUDIO_MODE, + SITAR_BANDGAP_MBHC_MODE, +}; + +struct mbhc_micbias_regs { + u16 cfilt_val; + u16 cfilt_ctl; + u16 mbhc_reg; + u16 int_rbias; + u16 ctl_reg; + u8 cfilt_sel; +}; + +/* Codec supports 2 IIR filters */ +enum { + IIR1 = 0, + IIR2, + IIR_MAX, +}; +/* Codec supports 5 bands */ +enum { + BAND1 = 0, + BAND2, + BAND3, + BAND4, + BAND5, + BAND_MAX, +}; + +/* Flags to track of PA and DAC state. + * PA and DAC should be tracked separately as AUXPGA loopback requires + * only PA to be turned on without DAC being on. */ +enum sitar_priv_ack_flags { + SITAR_HPHL_PA_OFF_ACK = 0, + SITAR_HPHR_PA_OFF_ACK, + SITAR_HPHL_DAC_OFF_ACK, + SITAR_HPHR_DAC_OFF_ACK +}; + +/* Data used by MBHC */ +struct mbhc_internal_cal_data { + u16 dce_z; + u16 dce_mb; + u16 sta_z; + u16 sta_mb; + u32 t_sta_dce; + u32 t_dce; + u32 t_sta; + u32 micb_mv; + u16 v_ins_hu; + u16 v_ins_h; + u16 v_b1_hu; + u16 v_b1_h; + u16 v_b1_huc; + u16 v_brh; + u16 v_brl; + u16 v_no_mic; + u8 npoll; + u8 nbounce_wait; +}; + +enum sitar_mbhc_plug_type { + PLUG_TYPE_INVALID = -1, + PLUG_TYPE_NONE, + PLUG_TYPE_HEADSET, + PLUG_TYPE_HEADPHONE, + PLUG_TYPE_HIGH_HPH, +}; + +enum sitar_mbhc_state { + MBHC_STATE_NONE = -1, + MBHC_STATE_POTENTIAL, + MBHC_STATE_POTENTIAL_RECOVERY, + MBHC_STATE_RELEASE, +}; + +struct sitar_priv { + struct snd_soc_codec *codec; + u32 mclk_freq; + u32 adc_count; + u32 cfilt1_cnt; + u32 cfilt2_cnt; + u32 cfilt3_cnt; + u32 rx_bias_count; + enum sitar_bandgap_type bandgap_type; + bool mclk_enabled; + bool clock_active; + bool config_mode_active; + bool mbhc_polling_active; + unsigned long mbhc_fake_ins_start; + int buttons_pressed; + + enum sitar_micbias_num micbias; + /* void* calibration contains: + * struct sitar_mbhc_general_cfg generic; + * struct sitar_mbhc_plug_detect_cfg plug_det; + * struct sitar_mbhc_plug_type_cfg plug_type; + * struct sitar_mbhc_btn_detect_cfg btn_det; + * struct sitar_mbhc_imped_detect_cfg imped_det; + * Note: various size depends on btn_det->num_btn + */ + void *calibration; + struct mbhc_internal_cal_data mbhc_data; + + struct wcd9xxx_pdata *pdata; + u32 anc_slot; + + bool no_mic_headset_override; + + struct mbhc_micbias_regs mbhc_bias_regs; + u8 cfilt_k_value; + bool mbhc_micbias_switched; + + /* track PA/DAC state */ + unsigned long hph_pa_dac_state; + + /*track sitar interface type*/ + u8 intf_type; + + u32 hph_status; /* track headhpone status */ + /* define separate work for left and right headphone OCP to avoid + * additional checking on which OCP event to report so no locking + * to ensure synchronization is required + */ + struct work_struct hphlocp_work; /* reporting left hph ocp off */ + struct work_struct hphrocp_work; /* reporting right hph ocp off */ + + u8 hphlocp_cnt; /* headphone left ocp retry */ + u8 hphrocp_cnt; /* headphone right ocp retry */ + + /* Callback function to enable MCLK */ + int (*mclk_cb) (struct snd_soc_codec*, int); + + /* Work to perform MBHC Firmware Read */ + struct delayed_work mbhc_firmware_dwork; + const struct firmware *mbhc_fw; + + /* num of slim ports required */ + struct sitar_codec_dai_data dai[NUM_CODEC_DAIS]; + + /* Currently, only used for mbhc purpose, to protect + * concurrent execution of mbhc threaded irq handlers and + * kill race between DAPM and MBHC.But can serve as a + * general lock to protect codec resource + */ + struct mutex codec_resource_lock; + + struct sitar_mbhc_config mbhc_cfg; + bool in_gpio_handler; + u8 current_plug; + bool lpi_enabled; + enum sitar_mbhc_state mbhc_state; + struct work_struct hs_correct_plug_work; + bool hs_detect_work_stop; + struct delayed_work mbhc_btn_dwork; + unsigned long mbhc_last_resume; /* in jiffies */ +}; + +#ifdef CONFIG_DEBUG_FS +struct sitar_priv *debug_sitar_priv; +#endif + + +static int sitar_pa_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + ear_pa_gain = snd_soc_read(codec, SITAR_A_RX_EAR_GAIN); + + ear_pa_gain = ear_pa_gain >> 5; + + if (ear_pa_gain == 0x00) { + ucontrol->value.integer.value[0] = 0; + } else if (ear_pa_gain == 0x04) { + ucontrol->value.integer.value[0] = 1; + } else { + pr_err("%s: ERROR: Unsupported Ear Gain = 0x%x\n", + __func__, ear_pa_gain); + return -EINVAL; + } + + pr_debug("%s: ear_pa_gain = 0x%x\n", __func__, ear_pa_gain); + + return 0; +} + +static int sitar_pa_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s: ucontrol->value.integer.value[0] = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + ear_pa_gain = 0x00; + break; + case 1: + ear_pa_gain = 0x80; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, SITAR_A_RX_EAR_GAIN, ear_pa_gain); + return 0; +} + +static int sitar_get_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + snd_soc_read(codec, (SITAR_A_CDC_IIR1_CTL + 16 * iir_idx)) & + (1 << band_idx); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0]); + return 0; +} + +static int sitar_put_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + int value = ucontrol->value.integer.value[0]; + + /* Mask first 5 bits, 6-8 are reserved */ + snd_soc_update_bits(codec, (SITAR_A_CDC_IIR1_CTL + 16 * iir_idx), + (1 << band_idx), (value << band_idx)); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, value); + return 0; +} +static uint32_t get_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx) +{ + /* Address does not automatically update if reading */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + 0x1F, band_idx * BAND_MAX + coeff_idx); + + /* Mask bits top 2 bits since they are reserved */ + return ((snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx)) << 24) | + (snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx)) << 16) | + (snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx)) << 8) | + (snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx)))) & + 0x3FFFFFFF; +} + +static int sitar_get_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + get_iir_band_coeff(codec, iir_idx, band_idx, 0); + ucontrol->value.integer.value[1] = + get_iir_band_coeff(codec, iir_idx, band_idx, 1); + ucontrol->value.integer.value[2] = + get_iir_band_coeff(codec, iir_idx, band_idx, 2); + ucontrol->value.integer.value[3] = + get_iir_band_coeff(codec, iir_idx, band_idx, 3); + ucontrol->value.integer.value[4] = + get_iir_band_coeff(codec, iir_idx, band_idx, 4); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[1], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[2], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[3], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[4]); + return 0; +} + +static void set_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx, uint32_t value) +{ + /* Mask top 3 bits, 6-8 are reserved */ + /* Update address manually each time */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + 0x1F, band_idx * BAND_MAX + coeff_idx); + + /* Mask top 2 bits, 7-8 are reserved */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx), + 0x3F, (value >> 24) & 0x3F); + + /* Isolate 8bits at a time */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx), + 0xFF, (value >> 16) & 0xFF); + + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx), + 0xFF, (value >> 8) & 0xFF); + + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx), + 0xFF, value & 0xFF); +} + +static int sitar_put_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + set_iir_band_coeff(codec, iir_idx, band_idx, 0, + ucontrol->value.integer.value[0]); + set_iir_band_coeff(codec, iir_idx, band_idx, 1, + ucontrol->value.integer.value[1]); + set_iir_band_coeff(codec, iir_idx, band_idx, 2, + ucontrol->value.integer.value[2]); + set_iir_band_coeff(codec, iir_idx, band_idx, 3, + ucontrol->value.integer.value[3]); + set_iir_band_coeff(codec, iir_idx, band_idx, 4, + ucontrol->value.integer.value[4]); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 0), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 1), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 2), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 3), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 4)); + return 0; +} + +static const char *sitar_ear_pa_gain_text[] = {"POS_6_DB", "POS_2_DB"}; +static const struct soc_enum sitar_ear_pa_gain_enum[] = { + SOC_ENUM_SINGLE_EXT(2, sitar_ear_pa_gain_text), +}; + +/*cut of frequency for high pass filter*/ +static const char *cf_text[] = { + "MIN_3DB_4Hz", "MIN_3DB_75Hz", "MIN_3DB_150Hz" +}; + +static const struct soc_enum cf_dec1_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TX1_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_rxmix1_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_RX1_B4_CTL, 1, 3, cf_text); + +static const struct snd_kcontrol_new sitar_snd_controls[] = { + + SOC_ENUM_EXT("EAR PA Gain", sitar_ear_pa_gain_enum[0], + sitar_pa_gain_get, sitar_pa_gain_put), + + SOC_SINGLE_TLV("LINEOUT1 Volume", SITAR_A_RX_LINE_1_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT2 Volume", SITAR_A_RX_LINE_2_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_TLV("HPHL Volume", SITAR_A_RX_HPH_L_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("HPHR Volume", SITAR_A_RX_HPH_R_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_S8_TLV("RX1 Digital Volume", SITAR_A_CDC_RX1_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX2 Digital Volume", SITAR_A_CDC_RX2_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX3 Digital Volume", SITAR_A_CDC_RX3_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + + SOC_SINGLE_S8_TLV("DEC1 Volume", SITAR_A_CDC_TX1_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC2 Volume", SITAR_A_CDC_TX2_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC3 Volume", SITAR_A_CDC_TX3_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC4 Volume", SITAR_A_CDC_TX4_VOL_CTL_GAIN, -84, 40, + digital_gain), + + SOC_SINGLE_S8_TLV("IIR1 INP1 Volume", SITAR_A_CDC_IIR1_GAIN_B1_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP2 Volume", SITAR_A_CDC_IIR1_GAIN_B2_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP3 Volume", SITAR_A_CDC_IIR1_GAIN_B3_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP4 Volume", SITAR_A_CDC_IIR1_GAIN_B4_CTL, -84, + 40, digital_gain), + SOC_SINGLE_TLV("ADC1 Volume", SITAR_A_TX_1_2_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", SITAR_A_TX_1_2_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC3 Volume", SITAR_A_TX_3_EN, 5, 3, 0, analog_gain), + + SOC_SINGLE("MICBIAS1 CAPLESS Switch", SITAR_A_MICB_1_CTL, 4, 1, 1), + SOC_SINGLE("MICBIAS2 CAPLESS Switch", SITAR_A_MICB_2_CTL, 4, 1, 1), + + SOC_ENUM("TX1 HPF cut off", cf_dec1_enum), + + SOC_SINGLE("TX1 HPF Switch", SITAR_A_CDC_TX1_MUX_CTL, 3, 1, 0), + + SOC_SINGLE("RX1 HPF Switch", SITAR_A_CDC_RX1_B5_CTL, 2, 1, 0), + + SOC_ENUM("RX1 HPF cut off", cf_rxmix1_enum), + + SOC_SINGLE_EXT("IIR1 Enable Band1", IIR1, BAND1, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band2", IIR1, BAND2, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band3", IIR1, BAND3, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band4", IIR1, BAND4, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band5", IIR1, BAND5, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band1", IIR2, BAND1, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band2", IIR2, BAND2, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band3", IIR2, BAND3, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band4", IIR2, BAND4, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band5", IIR2, BAND5, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + + SOC_SINGLE_MULTI_EXT("IIR1 Band1", IIR1, BAND1, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band2", IIR1, BAND2, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band3", IIR1, BAND3, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band4", IIR1, BAND4, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band5", IIR1, BAND5, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band1", IIR2, BAND1, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band2", IIR2, BAND2, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band3", IIR2, BAND3, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band4", IIR2, BAND4, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band5", IIR2, BAND5, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), +}; + +static const char *rx_mix1_text[] = { + "ZERO", "SRC1", "SRC2", "IIR1", "IIR2", "RX1", "RX2", "RX3", "RX4", + "RX5" +}; + +static const char *rx_dac1_text[] = { + "ZERO", "RX1", "RX2" +}; + +static const char *rx_dac2_text[] = { + "ZERO", "RX1", +}; + +static const char *rx_dac3_text[] = { + "ZERO", "RX1", "INV_RX1", "RX2" +}; + +static const char *rx_dac4_text[] = { + "ZERO", "ON" +}; + +static const char *sb_tx1_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1" +}; + +static const char *sb_tx2_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC2" +}; + +static const char *sb_tx3_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC3" +}; + +static const char *sb_tx4_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC4" +}; + +static const char *sb_tx5_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "DEC1", "DEC2", "DEC3", "DEC4" +}; + +static const char *dec1_mux_text[] = { + "ZERO", "DMIC1", "ADC1", "ADC2", "ADC3", "MBADC", "DMIC4", "ANCFB1", +}; + +static const char *dec2_mux_text[] = { + "ZERO", "DMIC2", "ADC1", "ADC2", "ADC3", "MBADC", "DMIC3", "ANCFB2", +}; + +static const char *dec3_mux_text[] = { + "ZERO", "DMIC3", "ADC1", "ADC2", "ADC3", "MBADC", "DMIC2", "DMIC4" +}; + +static const char *dec4_mux_text[] = { + "ZERO", "DMIC4", "ADC1", "ADC2", "ADC3", "DMIC3", "DMIC2", "DMIC1" +}; + +static const char *iir1_inp1_text[] = { + "ZERO", "DEC1", "DEC2", "DEC3", "DEC4", "ZERO", "ZERO", "ZERO", + "ZERO", "ZERO", "ZERO", "RX1", "RX2", "RX3", "RX4", "RX5", +}; + +static const struct soc_enum rx_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX1_B1_CTL, 0, 10, rx_mix1_text); + +static const struct soc_enum rx_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX1_B1_CTL, 4, 10, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX2_B1_CTL, 0, 10, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX2_B1_CTL, 4, 10, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX3_B1_CTL, 0, 10, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX3_B1_CTL, 4, 10, rx_mix1_text); + +static const struct soc_enum rx_dac1_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 6, 3, rx_dac1_text); + +static const struct soc_enum rx_dac2_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 4, 2, rx_dac2_text); + +static const struct soc_enum rx_dac3_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 2, 4, rx_dac3_text); + +static const struct soc_enum rx_dac4_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 0, 2, rx_dac4_text); + +static const struct soc_enum sb_tx5_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B5_CTL, 0, 9, sb_tx5_mux_text); + +static const struct soc_enum sb_tx4_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B4_CTL, 0, 9, sb_tx4_mux_text); + +static const struct soc_enum sb_tx3_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B3_CTL, 0, 9, sb_tx3_mux_text); + +static const struct soc_enum sb_tx2_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B2_CTL, 0, 9, sb_tx2_mux_text); + +static const struct soc_enum sb_tx1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B1_CTL, 0, 9, sb_tx1_mux_text); + +static const struct soc_enum dec1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B1_CTL, 0, 8, dec1_mux_text); + +static const struct soc_enum dec2_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B1_CTL, 3, 8, dec2_mux_text); + +static const struct soc_enum dec3_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B2_CTL, 0, 8, dec3_mux_text); + +static const struct soc_enum dec4_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B2_CTL, 3, 8, dec4_mux_text); + +static const struct soc_enum iir1_inp1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_EQ1_B1_CTL, 0, 16, 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 rx_mix1_inp2_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP2 Mux", rx_mix1_inp2_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 rx2_mix1_inp2_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP2 Mux", rx2_mix1_inp2_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 rx3_mix1_inp2_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP2 Mux", rx3_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx_dac1_mux = + SOC_DAPM_ENUM("RX DAC1 Mux", rx_dac1_enum); + +static const struct snd_kcontrol_new rx_dac2_mux = + SOC_DAPM_ENUM("RX DAC2 Mux", rx_dac2_enum); + +static const struct snd_kcontrol_new rx_dac3_mux = + SOC_DAPM_ENUM("RX DAC3 Mux", rx_dac3_enum); + +static const struct snd_kcontrol_new rx_dac4_mux = + SOC_DAPM_ENUM("RX DAC4 Mux", rx_dac4_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_tx4_mux = + SOC_DAPM_ENUM("SLIM TX4 MUX Mux", sb_tx4_mux_enum); + +static const struct snd_kcontrol_new sb_tx3_mux = + SOC_DAPM_ENUM("SLIM TX3 MUX Mux", sb_tx3_mux_enum); + +static const struct snd_kcontrol_new sb_tx2_mux = + SOC_DAPM_ENUM("SLIM TX2 MUX Mux", sb_tx2_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 dec2_mux = + SOC_DAPM_ENUM("DEC2 MUX Mux", dec2_mux_enum); + +static const struct snd_kcontrol_new dec3_mux = + SOC_DAPM_ENUM("DEC3 MUX Mux", dec3_mux_enum); + +static const struct snd_kcontrol_new dec4_mux = + SOC_DAPM_ENUM("DEC4 MUX Mux", dec4_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_switch[] = { + SOC_DAPM_SINGLE("Switch", SITAR_A_RX_EAR_EN, 5, 1, 0), +}; + +static void sitar_codec_enable_adc_block(struct snd_soc_codec *codec, + int enable) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s %d\n", __func__, enable); + + if (enable) { + sitar->adc_count++; + snd_soc_update_bits(codec, SITAR_A_TX_COM_BIAS, 0xE0, 0xE0); + + } else { + sitar->adc_count--; + if (!sitar->adc_count) { + if (!sitar->mbhc_polling_active) + snd_soc_update_bits(codec, SITAR_A_TX_COM_BIAS, + 0xE0, 0x0); + } + } +} + +static int sitar_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 == SITAR_A_TX_1_2_EN) + adc_reg = SITAR_A_TX_1_2_TEST_CTL; + else { + pr_err("%s: Error, invalid adc register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + sitar_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); + snd_soc_update_bits(codec, adc_reg, 0x08, 0x08); + break; + case SND_SOC_DAPM_POST_PMD: + sitar_codec_enable_adc_block(codec, 0); + break; + } + return 0; +} + +static int sitar_lineout_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static int sitar_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 %s\n", __func__, event, w->name); + + switch (w->shift) { + case 0: + lineout_gain_reg = SITAR_A_RX_LINE_1_GAIN; + break; + case 1: + lineout_gain_reg = SITAR_A_RX_LINE_2_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, 0x10, 0x10); + break; + case SND_SOC_DAPM_POST_PMU: + pr_debug("%s: sleeping 16 ms after %s PA turn on\n", + __func__, w->name); + usleep_range(16000, 16000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, lineout_gain_reg, 0x10, 0x00); + break; + } + return 0; +} + +static int sitar_codec_enable_dmic(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 tx_dmic_ctl_reg, tx_mux_ctl_reg; + u8 dmic_clk_sel, dmic_clk_en; + unsigned int dmic; + int ret; + + ret = kstrtouint(strpbrk(w->name, "1234"), 10, &dmic); + if (ret < 0) { + pr_err("%s: Invalid DMIC line on the codec\n", __func__); + return -EINVAL; + } + + switch (dmic) { + case 1: + case 2: + dmic_clk_sel = 0x02; + dmic_clk_en = 0x01; + break; + case 3: + case 4: + dmic_clk_sel = 0x08; + dmic_clk_en = 0x04; + break; + + break; + + default: + pr_err("%s: Invalid DMIC Selection\n", __func__); + return -EINVAL; + } + + tx_mux_ctl_reg = SITAR_A_CDC_TX1_MUX_CTL + 8 * (dmic - 1); + tx_dmic_ctl_reg = SITAR_A_CDC_TX1_DMIC_CTL + 8 * (dmic - 1); + + pr_debug("%s %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x01, 0x01); + + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_DMIC_CTL, + dmic_clk_sel, dmic_clk_sel); + + snd_soc_update_bits(codec, tx_dmic_ctl_reg, 0x1, 0x1); + + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, dmic_clk_en); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, 0); + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x01, 0x00); + break; + } + return 0; +} + +static void sitar_codec_start_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + int mbhc_state = sitar->mbhc_state; + + pr_debug("%s: enter\n", __func__); + if (!sitar->mbhc_polling_active) { + pr_debug("Polling is not active, do not start polling\n"); + return; + } + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + + + if (!sitar->no_mic_headset_override) { + if (mbhc_state == MBHC_STATE_POTENTIAL) { + pr_debug("%s recovering MBHC state macine\n", __func__); + sitar->mbhc_state = MBHC_STATE_POTENTIAL_RECOVERY; + /* set to max button press threshold */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B2_CTL, + 0x7F); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B1_CTL, + 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B4_CTL, + 0x7F); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B3_CTL, + 0xFF); + /* set to max */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B6_CTL, + 0x7F); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B5_CTL, + 0xFF); + } + } + + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x1); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x1); +} + +static void sitar_codec_pause_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enter\n", __func__); + if (!sitar->mbhc_polling_active) { + pr_debug("polling not active, nothing to pause\n"); + return; + } + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + pr_debug("%s: leave\n", __func__); + +} + +static void sitar_codec_switch_cfilt_mode(struct snd_soc_codec *codec, + int mode) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u8 reg_mode_val, cur_mode_val; + bool mbhc_was_polling = false; + + if (mode) + reg_mode_val = SITAR_CFILT_FAST_MODE; + else + reg_mode_val = SITAR_CFILT_SLOW_MODE; + + cur_mode_val = snd_soc_read(codec, + sitar->mbhc_bias_regs.cfilt_ctl) & 0x40; + + if (cur_mode_val != reg_mode_val) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + if (sitar->mbhc_polling_active) { + sitar_codec_pause_hs_polling(codec); + mbhc_was_polling = true; + } + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.cfilt_ctl, 0x40, reg_mode_val); + if (mbhc_was_polling) + sitar_codec_start_hs_polling(codec); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + pr_debug("%s: CFILT mode change (%x to %x)\n", __func__, + cur_mode_val, reg_mode_val); + } else { + pr_err("%s: CFILT Value is already %x\n", + __func__, cur_mode_val); + } +} + +static void sitar_codec_update_cfilt_usage(struct snd_soc_codec *codec, + u8 cfilt_sel, int inc) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u32 *cfilt_cnt_ptr = NULL; + u16 micb_cfilt_reg; + + switch (cfilt_sel) { + case SITAR_CFILT1_SEL: + cfilt_cnt_ptr = &sitar->cfilt1_cnt; + micb_cfilt_reg = SITAR_A_MICB_CFILT_1_CTL; + break; + case SITAR_CFILT2_SEL: + cfilt_cnt_ptr = &sitar->cfilt2_cnt; + micb_cfilt_reg = SITAR_A_MICB_CFILT_2_CTL; + break; + default: + return; /* should not happen */ + } + + if (inc) { + if (!(*cfilt_cnt_ptr)++) { + /* Switch CFILT to slow mode if MBHC CFILT being used */ + if (cfilt_sel == sitar->mbhc_bias_regs.cfilt_sel) + sitar_codec_switch_cfilt_mode(codec, 0); + + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0x80); + } + } else { + /* check if count not zero, decrement + * then check if zero, go ahead disable cfilter + */ + if ((*cfilt_cnt_ptr) && !--(*cfilt_cnt_ptr)) { + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0); + + /* Switch CFILT to fast mode if MBHC CFILT being used */ + if (cfilt_sel == sitar->mbhc_bias_regs.cfilt_sel) + sitar_codec_switch_cfilt_mode(codec, 1); + } + } +} + +static int sitar_find_k_value(unsigned int ldoh_v, unsigned int cfilt_mv) +{ + int rc = -EINVAL; + unsigned min_mv, max_mv; + + switch (ldoh_v) { + case SITAR_LDOH_1P95_V: + min_mv = 160; + max_mv = 1800; + break; + case SITAR_LDOH_2P35_V: + min_mv = 200; + max_mv = 2200; + break; + case SITAR_LDOH_2P75_V: + min_mv = 240; + max_mv = 2600; + break; + case SITAR_LDOH_2P85_V: + min_mv = 250; + max_mv = 2700; + break; + default: + goto done; + } + + if (cfilt_mv < min_mv || cfilt_mv > max_mv) + goto done; + + for (rc = 4; rc <= 44; rc++) { + min_mv = max_mv * (rc) / 44; + if (min_mv >= cfilt_mv) { + rc -= 4; + break; + } + } +done: + return rc; +} + +static bool sitar_is_hph_pa_on(struct snd_soc_codec *codec) +{ + u8 hph_reg_val = 0; + hph_reg_val = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_EN); + + return (hph_reg_val & 0x30) ? true : false; +} + +static bool sitar_is_hph_dac_on(struct snd_soc_codec *codec, int left) +{ + u8 hph_reg_val = 0; + if (left) + hph_reg_val = snd_soc_read(codec, + SITAR_A_RX_HPH_L_DAC_CTL); + else + hph_reg_val = snd_soc_read(codec, + SITAR_A_RX_HPH_R_DAC_CTL); + + return (hph_reg_val & 0xC0) ? true : false; +} + +static void sitar_codec_switch_micbias(struct snd_soc_codec *codec, + int vddio_switch) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + int cfilt_k_val; + bool mbhc_was_polling = false; + + switch (vddio_switch) { + case 1: + if (sitar->mbhc_micbias_switched == 0 && + sitar->mbhc_polling_active) { + + sitar_codec_pause_hs_polling(codec); + /* Enable Mic Bias switch to VDDIO */ + sitar->cfilt_k_value = snd_soc_read(codec, + sitar->mbhc_bias_regs.cfilt_val); + cfilt_k_val = sitar_find_k_value( + sitar->pdata->micbias.ldoh_v, 1800); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x80); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x10, 0x00); + sitar_codec_start_hs_polling(codec); + + sitar->mbhc_micbias_switched = true; + pr_debug("%s: Enabled MBHC Mic bias to VDDIO Switch\n", + __func__); + } + break; + + case 0: + if (sitar->mbhc_micbias_switched) { + if (sitar->mbhc_polling_active) { + sitar_codec_pause_hs_polling(codec); + mbhc_was_polling = true; + } + /* Disable Mic Bias switch to VDDIO */ + if (sitar->cfilt_k_value != 0) + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.cfilt_val, 0XFC, + sitar->cfilt_k_value); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x00); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x10, 0x00); + + if (mbhc_was_polling) + sitar_codec_start_hs_polling(codec); + + sitar->mbhc_micbias_switched = false; + pr_debug("%s: Disabled MBHC Mic bias to VDDIO Switch\n", + __func__); + } + break; + } +} + +static int sitar_codec_enable_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u16 micb_int_reg; + int micb_line; + u8 cfilt_sel_val = 0; + char *internal1_text = "Internal1"; + char *internal2_text = "Internal2"; + + pr_debug("%s %d\n", __func__, event); + switch (w->reg) { + case SITAR_A_MICB_1_CTL: + micb_int_reg = SITAR_A_MICB_1_INT_RBIAS; + cfilt_sel_val = sitar->pdata->micbias.bias1_cfilt_sel; + micb_line = SITAR_MICBIAS1; + break; + case SITAR_A_MICB_2_CTL: + micb_int_reg = SITAR_A_MICB_2_INT_RBIAS; + cfilt_sel_val = sitar->pdata->micbias.bias2_cfilt_sel; + micb_line = SITAR_MICBIAS2; + break; + default: + pr_err("%s: Error, invalid micbias register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Decide whether to switch the micbias for MBHC */ + if (w->reg == sitar->mbhc_bias_regs.ctl_reg) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_switch_micbias(codec, 0); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + } + + snd_soc_update_bits(codec, w->reg, 0x1E, 0x00); + sitar_codec_update_cfilt_usage(codec, cfilt_sel_val, 1); + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0xFF, 0xA4); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x1C, 0x1C); + break; + case SND_SOC_DAPM_POST_PMU: + if (sitar->mbhc_polling_active && + sitar->mbhc_cfg.micbias == micb_line) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_pause_hs_polling(codec); + sitar_codec_start_hs_polling(codec); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + } + break; + case SND_SOC_DAPM_POST_PMD: + + if ((w->reg == sitar->mbhc_bias_regs.ctl_reg) + && sitar_is_hph_pa_on(codec)) + sitar_codec_switch_micbias(codec, 1); + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x80, 0x00); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x10, 0x00); + sitar_codec_update_cfilt_usage(codec, cfilt_sel_val, 0); + break; + } + + return 0; +} + +static int sitar_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 == SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL) + dec_reset_reg = SITAR_A_CDC_CLK_TX_RESET_B1_CTL; + else { + pr_err("%s: Error, incorrect dec\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + 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; + } + return 0; +} + +static int sitar_codec_reset_interpolator(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d %s\n", __func__, event, w->name); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 1 << w->shift); + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 0x0); + break; + } + return 0; +} + +static int sitar_codec_enable_ldo_h(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + usleep_range(1000, 1000); + pr_debug("LDO_H\n"); + break; + } + return 0; +} + +static void sitar_enable_rx_bias(struct snd_soc_codec *codec, u32 enable) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (enable) { + sitar->rx_bias_count++; + if (sitar->rx_bias_count == 1) + snd_soc_update_bits(codec, SITAR_A_RX_COM_BIAS, + 0x80, 0x80); + } else { + sitar->rx_bias_count--; + if (!sitar->rx_bias_count) + snd_soc_update_bits(codec, SITAR_A_RX_COM_BIAS, + 0x80, 0x00); + } +} + +static int sitar_codec_enable_rx_bias(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: + sitar_enable_rx_bias(codec, 1); + break; + case SND_SOC_DAPM_POST_PMD: + sitar_enable_rx_bias(codec, 0); + break; + } + return 0; +} +static int sitar_hph_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static void sitar_snd_soc_jack_report(struct sitar_priv *sitar, + struct snd_soc_jack *jack, int status, + int mask) +{ + /* XXX: wake_lock_timeout()? */ + snd_soc_jack_report(jack, status, mask); +} + +static void hphocp_off_report(struct sitar_priv *sitar, + u32 jack_status, int irq) +{ + struct snd_soc_codec *codec; + + if (!sitar) { + pr_err("%s: Bad sitar private data\n", __func__); + return; + } + + pr_info("%s: clear ocp status %x\n", __func__, jack_status); + codec = sitar->codec; + if (sitar->hph_status & jack_status) { + sitar->hph_status &= ~jack_status; + if (sitar->mbhc_cfg.headset_jack) + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, 0x10); + /* reset retry counter as PA is turned off signifying + * start of new OCP detection session + */ + if (SITAR_IRQ_HPH_PA_OCPL_FAULT) + sitar->hphlocp_cnt = 0; + else + sitar->hphrocp_cnt = 0; + wcd9xxx_enable_irq(codec->control_data, irq); + } +} + +static void hphlocp_off_report(struct work_struct *work) +{ + struct sitar_priv *sitar = container_of(work, struct sitar_priv, + hphlocp_work); + hphocp_off_report(sitar, SND_JACK_OC_HPHL, SITAR_IRQ_HPH_PA_OCPL_FAULT); +} + +static void hphrocp_off_report(struct work_struct *work) +{ + struct sitar_priv *sitar = container_of(work, struct sitar_priv, + hphrocp_work); + hphocp_off_report(sitar, SND_JACK_OC_HPHR, SITAR_IRQ_HPH_PA_OCPR_FAULT); +} + +static int sitar_hph_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u8 mbhc_micb_ctl_val; + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mbhc_micb_ctl_val = snd_soc_read(codec, + sitar->mbhc_bias_regs.ctl_reg); + + if (!(mbhc_micb_ctl_val & 0x80)) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_switch_micbias(codec, 1); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + } + + break; + + case SND_SOC_DAPM_POST_PMD: + /* schedule work is required because at the time HPH PA DAPM + * event callback is called by DAPM framework, CODEC dapm mutex + * would have been locked while snd_soc_jack_report also + * attempts to acquire same lock. + */ + if (w->shift == 5) { + clear_bit(SITAR_HPHL_PA_OFF_ACK, + &sitar->hph_pa_dac_state); + clear_bit(SITAR_HPHL_DAC_OFF_ACK, + &sitar->hph_pa_dac_state); + if (sitar->hph_status & SND_JACK_OC_HPHL) + schedule_work(&sitar->hphlocp_work); + } else if (w->shift == 4) { + clear_bit(SITAR_HPHR_PA_OFF_ACK, + &sitar->hph_pa_dac_state); + clear_bit(SITAR_HPHR_DAC_OFF_ACK, + &sitar->hph_pa_dac_state); + if (sitar->hph_status & SND_JACK_OC_HPHR) + schedule_work(&sitar->hphrocp_work); + } + + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_switch_micbias(codec, 0); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + + pr_debug("%s: sleep 10 ms after %s PA disable.\n", __func__, + w->name); + usleep_range(10000, 10000); + + break; + } + return 0; +} + +static void sitar_get_mbhc_micbias_regs(struct snd_soc_codec *codec, + struct mbhc_micbias_regs *micbias_regs) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + unsigned int cfilt; + + switch (sitar->mbhc_cfg.micbias) { + case SITAR_MICBIAS1: + cfilt = sitar->pdata->micbias.bias1_cfilt_sel; + micbias_regs->mbhc_reg = SITAR_A_MICB_1_MBHC; + micbias_regs->int_rbias = SITAR_A_MICB_1_INT_RBIAS; + micbias_regs->ctl_reg = SITAR_A_MICB_1_CTL; + break; + case SITAR_MICBIAS2: + cfilt = sitar->pdata->micbias.bias2_cfilt_sel; + micbias_regs->mbhc_reg = SITAR_A_MICB_2_MBHC; + micbias_regs->int_rbias = SITAR_A_MICB_2_INT_RBIAS; + micbias_regs->ctl_reg = SITAR_A_MICB_2_CTL; + break; + default: + /* Should never reach here */ + pr_err("%s: Invalid MIC BIAS for MBHC\n", __func__); + return; + } + + micbias_regs->cfilt_sel = cfilt; + + switch (cfilt) { + case SITAR_CFILT1_SEL: + micbias_regs->cfilt_val = SITAR_A_MICB_CFILT_1_VAL; + micbias_regs->cfilt_ctl = SITAR_A_MICB_CFILT_1_CTL; + sitar->mbhc_data.micb_mv = sitar->pdata->micbias.cfilt1_mv; + break; + case SITAR_CFILT2_SEL: + micbias_regs->cfilt_val = SITAR_A_MICB_CFILT_2_VAL; + micbias_regs->cfilt_ctl = SITAR_A_MICB_CFILT_2_CTL; + sitar->mbhc_data.micb_mv = sitar->pdata->micbias.cfilt2_mv; + break; + } +} + +static int sitar_codec_enable_charge_pump(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_POST_PMU: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_RESET_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_CTL, 0x01, + 0x01); + snd_soc_update_bits(codec, SITAR_A_CDC_CLSG_CTL, 0x08, 0x08); + usleep_range(200, 200); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x10, 0x00); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_RESET_CTL, 0x10, + 0x10); + usleep_range(20, 20); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x08, 0x08); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x10, 0x10); + snd_soc_update_bits(codec, SITAR_A_CDC_CLSG_CTL, 0x08, 0x00); + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_CTL, 0x01, + 0x00); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x08, 0x00); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget sitar_dapm_i2s_widgets[] = { + SND_SOC_DAPM_SUPPLY("RX_I2S_CLK", SITAR_A_CDC_CLK_RX_I2S_CTL, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TX_I2S_CLK", SITAR_A_CDC_CLK_TX_I2S_CTL, 4, + 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget sitar_dapm_widgets[] = { + /*RX stuff */ + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_PGA("EAR PA", SITAR_A_RX_EAR_EN, 4, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DAC1", SITAR_A_RX_EAR_EN, 6, 0, dac1_switch, + ARRAY_SIZE(dac1_switch)), + SND_SOC_DAPM_SUPPLY("EAR DRIVER", SITAR_A_RX_EAR_EN, 3, 0, NULL, 0), + SND_SOC_DAPM_AIF_IN_E("SLIM RX1", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX2", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN("SLIM RX3", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM RX4", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM RX5", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Headphone */ + SND_SOC_DAPM_OUTPUT("HEADPHONE"), + SND_SOC_DAPM_PGA_E("HPHL", SITAR_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, + sitar_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("HPHR", SITAR_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, + sitar_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC_E("HPHL DAC", NULL, SITAR_A_RX_HPH_L_DAC_CTL, 7, 0, + sitar_hph_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("HPHR DAC", NULL, SITAR_A_RX_HPH_R_DAC_CTL, 7, 0, + sitar_hph_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Speaker */ + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + + SND_SOC_DAPM_DAC_E("LINEOUT1 DAC", NULL, SITAR_A_RX_LINE_1_DAC_CTL, 7, 0 + , sitar_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("LINEOUT2 DAC", NULL, SITAR_A_RX_LINE_2_DAC_CTL, 7, 0 + , sitar_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("LINEOUT1 PA", SITAR_A_RX_LINE_CNP_EN, 0, 0, NULL, + 0, sitar_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LINEOUT2 PA", SITAR_A_RX_LINE_CNP_EN, 1, 0, NULL, + 0, sitar_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("RX1 MIX1", SITAR_A_CDC_CLK_RX_B1_CTL, 0, 0, NULL, + 0, sitar_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER_E("RX2 MIX1", SITAR_A_CDC_CLK_RX_B1_CTL, 1, 0, NULL, + 0, sitar_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER_E("RX3 MIX1", SITAR_A_CDC_CLK_RX_B1_CTL, 2, 0, NULL, + 0, sitar_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MUX("DAC1 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac1_mux), + SND_SOC_DAPM_MUX("DAC2 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac2_mux), + SND_SOC_DAPM_MUX("DAC3 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac3_mux), + SND_SOC_DAPM_MUX("DAC4 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac4_mux), + + SND_SOC_DAPM_MIXER("RX1 CHAIN", SITAR_A_CDC_RX1_B6_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 CHAIN", SITAR_A_CDC_RX2_B6_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX3 CHAIN", SITAR_A_CDC_RX3_B6_CTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MUX("RX1 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp2_mux), + + SND_SOC_DAPM_SUPPLY("CP", SITAR_A_CP_EN, 0, 0, + sitar_codec_enable_charge_pump, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("RX_BIAS", SND_SOC_NOPM, 0, 0, + sitar_codec_enable_rx_bias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("LDO_H", SITAR_A_LDO_H_MODE_1, 7, 0, + sitar_codec_enable_ldo_h, SND_SOC_DAPM_POST_PMU), + /* TX */ + + SND_SOC_DAPM_SUPPLY("CDC_CONN", SITAR_A_CDC_CLK_OTHR_CTL, 2, 0, NULL, + 0), + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_INPUT("AMIC3"), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 External", SITAR_A_MICB_1_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal1", SITAR_A_MICB_1_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 External", SITAR_A_MICB_2_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal1", SITAR_A_MICB_2_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal2", SITAR_A_MICB_2_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("ADC1", NULL, SITAR_A_TX_1_2_EN, 7, 0, + sitar_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC2", NULL, SITAR_A_TX_1_2_EN, 3, 0, + sitar_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC3", NULL, SITAR_A_TX_3_EN, 7, 0, + sitar_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", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 0, 0, + &dec1_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("DEC2 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 1, 0, + &dec2_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("DEC3 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 2, 0, + &dec3_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("DEC4 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 3, 0, + &dec4_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MUX("SLIM TX1 MUX", SND_SOC_NOPM, 0, 0, &sb_tx1_mux), + SND_SOC_DAPM_MUX("SLIM TX2 MUX", SND_SOC_NOPM, 0, 0, &sb_tx2_mux), + SND_SOC_DAPM_MUX("SLIM TX3 MUX", SND_SOC_NOPM, 0, 0, &sb_tx3_mux), + SND_SOC_DAPM_MUX("SLIM TX4 MUX", SND_SOC_NOPM, 0, 0, &sb_tx4_mux), + SND_SOC_DAPM_MUX("SLIM TX5 MUX", SND_SOC_NOPM, 0, 0, &sb_tx5_mux), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX1", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX2", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX3", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX4", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX5", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Digital Mic Inputs */ + SND_SOC_DAPM_ADC_E("DMIC1", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC2", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC3", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC4", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* Sidetone */ + SND_SOC_DAPM_MUX("IIR1 INP1 MUX", SND_SOC_NOPM, 0, 0, &iir1_inp1_mux), + SND_SOC_DAPM_PGA("IIR1", SITAR_A_CDC_CLK_SD_CTL, 0, 0, NULL, 0), + +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Earpiece (RX MIX1) */ + {"EAR", NULL, "EAR PA"}, + {"EAR PA", "NULL", "DAC1"}, + {"DAC1", "Switch", "DAC1 MUX"}, + {"DAC1", NULL, "CP"}, + {"DAC1", NULL, "EAR DRIVER"}, + + {"CP", NULL, "RX_BIAS"}, + + {"LINEOUT1 DAC", NULL, "RX_BIAS"}, + {"LINEOUT2 DAC", NULL, "RX_BIAS"}, + + {"LINEOUT2", NULL, "LINEOUT2 PA"}, + {"LINEOUT2 PA", NULL, "LINEOUT2 DAC"}, + {"LINEOUT2 DAC", NULL, "DAC3 MUX"}, + + {"LINEOUT1", NULL, "LINEOUT1 PA"}, + {"LINEOUT1 PA", NULL, "LINEOUT1 DAC"}, + {"LINEOUT1 DAC", NULL, "DAC2 MUX"}, + + + /* Headset (RX MIX1 and RX MIX2) */ + {"HEADPHONE", NULL, "HPHL"}, + {"HEADPHONE", NULL, "HPHR"}, + + {"HPHL DAC", NULL, "CP"}, + {"HPHR DAC", NULL, "CP"}, + + {"HPHL", NULL, "HPHL DAC"}, + {"HPHL DAC", "NULL", "DAC4 MUX"}, + {"HPHR", NULL, "HPHR DAC"}, + {"HPHR DAC", NULL, "RX3 MIX1"}, + + {"DAC1 MUX", "RX1", "RX1 CHAIN"}, + {"DAC2 MUX", "RX1", "RX1 CHAIN"}, + + {"DAC3 MUX", "RX1", "RX1 CHAIN"}, + {"DAC3 MUX", "INV_RX1", "RX1 CHAIN"}, + {"DAC3 MUX", "RX2", "RX2 MIX1"}, + + {"DAC4 MUX", "ON", "RX2 MIX1"}, + + {"RX1 CHAIN", NULL, "RX1 MIX1"}, + + {"RX1 MIX1", NULL, "RX1 MIX1 INP1"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP2"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP1"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP2"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP1"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP2"}, + + /* SLIMBUS Connections */ + + /* Slimbus port 5 is non functional in Sitar 1.0 */ + {"RX1 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP1", "IIR1", "IIR1"}, + {"RX1 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP2", "IIR1", "IIR1"}, + {"RX2 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP1", "IIR1", "IIR1"}, + {"RX2 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP2", "IIR1", "IIR1"}, + {"RX3 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP1", "IIR1", "IIR1"}, + {"RX3 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP2", "IIR1", "IIR1"}, + + + /* TX */ + {"SLIM TX1", NULL, "SLIM TX1 MUX"}, + {"SLIM TX2", NULL, "SLIM TX2 MUX"}, + {"SLIM TX3", NULL, "SLIM TX3 MUX"}, + {"SLIM TX4", NULL, "SLIM TX4 MUX"}, + {"SLIM TX5", NULL, "SLIM TX5 MUX"}, + + {"SLIM TX1 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX2 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX3 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX4 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX5 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX5 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX5 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX5 MUX", "DEC4", "DEC4 MUX"}, + + /* Decimator Inputs */ + {"DEC1 MUX", "DMIC1", "DMIC1"}, + {"DEC1 MUX", "DMIC4", "DMIC4"}, + {"DEC1 MUX", "ADC1", "ADC1"}, + {"DEC1 MUX", "ADC2", "ADC2"}, + {"DEC1 MUX", "ADC3", "ADC3"}, + {"DEC1 MUX", NULL, "CDC_CONN"}, + {"DEC2 MUX", "DMIC2", "DMIC2"}, + {"DEC2 MUX", "DMIC3", "DMIC3"}, + {"DEC2 MUX", "ADC1", "ADC1"}, + {"DEC2 MUX", "ADC2", "ADC2"}, + {"DEC2 MUX", "ADC3", "ADC3"}, + {"DEC2 MUX", NULL, "CDC_CONN"}, + {"DEC3 MUX", "DMIC3", "DMIC3"}, + {"DEC3 MUX", "ADC1", "ADC1"}, + {"DEC3 MUX", "ADC2", "ADC2"}, + {"DEC3 MUX", "ADC3", "ADC3"}, + {"DEC3 MUX", "DMIC2", "DMIC2"}, + {"DEC3 MUX", "DMIC4", "DMIC4"}, + {"DEC3 MUX", NULL, "CDC_CONN"}, + {"DEC4 MUX", "DMIC4", "DMIC4"}, + {"DEC4 MUX", "ADC1", "ADC1"}, + {"DEC4 MUX", "ADC2", "ADC2"}, + {"DEC4 MUX", "ADC3", "ADC3"}, + {"DEC4 MUX", "DMIC3", "DMIC3"}, + {"DEC4 MUX", "DMIC2", "DMIC2"}, + {"DEC4 MUX", "DMIC1", "DMIC1"}, + {"DEC4 MUX", NULL, "CDC_CONN"}, + + /* ADC Connections */ + {"ADC1", NULL, "AMIC1"}, + {"ADC2", NULL, "AMIC2"}, + {"ADC3", NULL, "AMIC3"}, + + /* IIR */ + {"IIR1", NULL, "IIR1 INP1 MUX"}, + {"IIR1 INP1 MUX", "DEC1", "DEC1 MUX"}, + {"MIC BIAS1 Internal1", NULL, "LDO_H"}, + {"MIC BIAS1 External", NULL, "LDO_H"}, + {"MIC BIAS2 Internal1", NULL, "LDO_H"}, + {"MIC BIAS2 External", NULL, "LDO_H"}, +}; + +static int sitar_readable(struct snd_soc_codec *ssc, unsigned int reg) +{ + return sitar_reg_readable[reg]; +} + +static int sitar_volatile(struct snd_soc_codec *ssc, unsigned int reg) +{ + /* Registers lower than 0x100 are top level registers which can be + * written by the Sitar core driver. + */ + + if ((reg >= SITAR_A_CDC_MBHC_EN_CTL) || (reg < 0x100)) + return 1; + + /* IIR Coeff registers are not cacheable */ + if ((reg >= SITAR_A_CDC_IIR1_COEF_B1_CTL) && + (reg <= SITAR_A_CDC_IIR1_COEF_B5_CTL)) + return 1; + + return 0; +} + +#define SITAR_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) +static int sitar_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + + BUG_ON(reg > SITAR_MAX_REGISTER); + + if (!sitar_volatile(codec, reg)) { + 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 wcd9xxx_reg_write(codec->control_data, reg, value); +} +static unsigned int sitar_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int val; + int ret; + + BUG_ON(reg > SITAR_MAX_REGISTER); + + if (!sitar_volatile(codec, reg) && sitar_readable(codec, reg) && + reg < codec->driver->reg_cache_size) { + ret = snd_soc_cache_read(codec, reg, &val); + if (ret >= 0) { + return val; + } else + dev_err(codec->dev, "Cache read from %x failed: %d\n", + reg, ret); + } + + val = wcd9xxx_reg_read(codec->control_data, reg); + return val; +} + +static void sitar_codec_enable_audio_mode_bandgap(struct snd_soc_codec *codec) +{ + struct wcd9xxx *sitar_core = dev_get_drvdata(codec->dev->parent); + + if (SITAR_IS_1P0(sitar_core->version)) + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x80, 0x80); + + snd_soc_update_bits(codec, SITAR_A_BIAS_CURR_CTL_2, 0x0C, 0x08); + usleep_range(1000, 1000); + snd_soc_write(codec, SITAR_A_BIAS_REF_CTL, 0x1C); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x04, + 0x04); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x01); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); +} + +static void sitar_codec_enable_bandgap(struct snd_soc_codec *codec, + enum sitar_bandgap_type choice) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + struct wcd9xxx *sitar_core = dev_get_drvdata(codec->dev->parent); + + /* TODO lock resources accessed by audio streams and threaded + * interrupt handlers + */ + + pr_debug("%s, choice is %d, current is %d\n", __func__, choice, + sitar->bandgap_type); + + if (sitar->bandgap_type == choice) + return; + + if ((sitar->bandgap_type == SITAR_BANDGAP_OFF) && + (choice == SITAR_BANDGAP_AUDIO_MODE)) { + sitar_codec_enable_audio_mode_bandgap(codec); + } else if (choice == SITAR_BANDGAP_MBHC_MODE) { + snd_soc_update_bits(codec, SITAR_A_BIAS_CURR_CTL_2, 0x0C, 0x08); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x2, + 0x2); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x4, + 0x4); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x1); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); + } else if ((sitar->bandgap_type == SITAR_BANDGAP_MBHC_MODE) && + (choice == SITAR_BANDGAP_AUDIO_MODE)) { + snd_soc_write(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x50); + usleep_range(100, 100); + sitar_codec_enable_audio_mode_bandgap(codec); + } else if (choice == SITAR_BANDGAP_OFF) { + snd_soc_update_bits(codec, SITAR_A_BIAS_CURR_CTL_2, 0x0C, 0x00); + snd_soc_write(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x50); + if (SITAR_IS_1P0(sitar_core->version)) + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, + 0xFF, 0x65); + usleep_range(1000, 1000); + } else { + pr_err("%s: Error, Invalid bandgap settings\n", __func__); + } + sitar->bandgap_type = choice; +} + +static int sitar_codec_enable_config_mode(struct snd_soc_codec *codec, + int enable) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (enable) { + snd_soc_update_bits(codec, SITAR_A_RC_OSC_FREQ, 0x10, 0); + snd_soc_write(codec, SITAR_A_BIAS_OSC_BG_CTL, 0x17); + usleep_range(5, 5); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_FREQ, 0x80, + 0x80); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_TEST, 0x80, + 0x80); + usleep_range(10, 10); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_TEST, 0x80, 0); + usleep_range(20, 20); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x08, 0x08); + } else { + snd_soc_update_bits(codec, SITAR_A_BIAS_OSC_BG_CTL, 0x1, + 0); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_FREQ, 0x80, 0); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x08, 0x00); + } + sitar->config_mode_active = enable ? true : false; + + return 0; +} + +static int sitar_codec_enable_clock_block(struct snd_soc_codec *codec, + int config_mode) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s\n", __func__); + + if (config_mode) { + sitar_codec_enable_config_mode(codec, 1); + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN2, 0x00); + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN2, 0x02); + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN1, 0x0D); + usleep_range(1000, 1000); + } else + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x08, 0x00); + + if (!config_mode && sitar->mbhc_polling_active) { + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN2, 0x02); + sitar_codec_enable_config_mode(codec, 0); + + } + + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x05); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x02, 0x00); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x04, 0x04); + usleep_range(50, 50); + sitar->clock_active = true; + return 0; +} +static void sitar_codec_disable_clock_block(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + pr_debug("%s\n", __func__); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x04, 0x00); + ndelay(160); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x02, 0x02); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x00); + sitar->clock_active = false; +} + +static int sitar_codec_mclk_index(const struct sitar_priv *sitar) +{ + if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_12288KHZ) + return 0; + else if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_9600KHZ) + return 1; + else { + BUG_ON(1); + return -EINVAL; + } +} + +static void sitar_codec_calibrate_hs_polling(struct snd_soc_codec *codec) +{ + u8 *n_ready, *n_cic; + struct sitar_mbhc_btn_detect_cfg *btn_det; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B1_CTL, + sitar->mbhc_data.v_ins_hu & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B2_CTL, + (sitar->mbhc_data.v_ins_hu >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B3_CTL, + sitar->mbhc_data.v_b1_hu & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B4_CTL, + (sitar->mbhc_data.v_b1_hu >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B5_CTL, + sitar->mbhc_data.v_b1_h & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B6_CTL, + (sitar->mbhc_data.v_b1_h >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B9_CTL, + sitar->mbhc_data.v_brh & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B10_CTL, + (sitar->mbhc_data.v_brh >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B11_CTL, + sitar->mbhc_data.v_brl & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B12_CTL, + (sitar->mbhc_data.v_brl >> 8) & 0xFF); + + n_ready = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_READY); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B1_CTL, + n_ready[sitar_codec_mclk_index(sitar)]); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B2_CTL, + sitar->mbhc_data.npoll); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B3_CTL, + sitar->mbhc_data.nbounce_wait); + n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B6_CTL, + n_cic[sitar_codec_mclk_index(sitar)]); +} + +static int sitar_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(dai->codec->dev->parent); + if ((wcd9xxx != NULL) && (wcd9xxx->dev != NULL) && + (wcd9xxx->dev->parent != NULL)) + pm_runtime_get_sync(wcd9xxx->dev->parent); + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + + return 0; +} + +static void sitar_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(dai->codec->dev->parent); + if ((wcd9xxx != NULL) && (wcd9xxx->dev != NULL) && + (wcd9xxx->dev->parent != NULL)) { + pm_runtime_mark_last_busy(wcd9xxx->dev->parent); + pm_runtime_put(wcd9xxx->dev->parent); + } + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); +} + +int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, bool dapm) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s() mclk_enable = %u\n", __func__, mclk_enable); + + if (dapm) + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + if (mclk_enable) { + sitar->mclk_enabled = true; + + if (sitar->mbhc_polling_active && (sitar->mclk_enabled)) { + sitar_codec_pause_hs_polling(codec); + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_AUDIO_MODE); + sitar_codec_enable_clock_block(codec, 0); + sitar_codec_calibrate_hs_polling(codec); + sitar_codec_start_hs_polling(codec); + } else { + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_AUDIO_MODE); + sitar_codec_enable_clock_block(codec, 0); + } + } else { + + if (!sitar->mclk_enabled) { + if (dapm) + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + pr_err("Error, MCLK already diabled\n"); + return -EINVAL; + } + sitar->mclk_enabled = false; + + if (sitar->mbhc_polling_active) { + if (!sitar->mclk_enabled) { + sitar_codec_pause_hs_polling(codec); + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_MBHC_MODE); + sitar_enable_rx_bias(codec, 1); + sitar_codec_enable_clock_block(codec, 1); + sitar_codec_calibrate_hs_polling(codec); + sitar_codec_start_hs_polling(codec); + } + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, + 0x05, 0x01); + } else { + sitar_codec_disable_clock_block(codec); + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_OFF); + } + } + if (dapm) + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + return 0; +} + +static int sitar_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 sitar_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + u8 val = 0; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(dai->codec); + + pr_debug("%s\n", __func__); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, + SITAR_I2S_MASTER_MODE_MASK, 0); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, + SITAR_I2S_MASTER_MODE_MASK, 0); + } + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + val = SITAR_I2S_MASTER_MODE_MASK; + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, val, val); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, val, val); + } + break; + default: + return -EINVAL; + } + return 0; +} +static int sitar_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) + +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(dai->codec); + u32 i = 0; + if (!tx_slot && !rx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + pr_debug("%s: DAI-ID %x %d %d\n", __func__, dai->id, tx_num, rx_num); + + if (dai->id == AIF1_PB) { + for (i = 0; i < rx_num; i++) { + sitar->dai[dai->id - 1].ch_num[i] = rx_slot[i]; + sitar->dai[dai->id - 1].ch_act = 0; + sitar->dai[dai->id - 1].ch_tot = rx_num; + } + } else if (dai->id == AIF1_CAP) { + for (i = 0; i < tx_num; i++) { + sitar->dai[dai->id - 1].ch_num[i] = tx_slot[i]; + sitar->dai[dai->id - 1].ch_act = 0; + sitar->dai[dai->id - 1].ch_tot = tx_num; + } + } + return 0; +} + +static int sitar_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) + +{ + struct wcd9xxx *sitar = dev_get_drvdata(dai->codec->control_data); + + u32 cnt = 0; + u32 tx_ch[SLIM_MAX_TX_PORTS]; + u32 rx_ch[SLIM_MAX_RX_PORTS]; + + if (!rx_slot && !tx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + pr_debug("%s: DAI-ID %x\n", __func__, dai->id); + /* for virtual port, codec driver needs to do + * housekeeping, for now should be ok + */ + wcd9xxx_get_channel(sitar, rx_ch, tx_ch); + if (dai->id == AIF1_PB) { + *rx_num = sitar_dai[dai->id - 1].playback.channels_max; + while (cnt < *rx_num) { + rx_slot[cnt] = rx_ch[cnt]; + cnt++; + } + } else if (dai->id == AIF1_CAP) { + *tx_num = sitar_dai[dai->id - 1].capture.channels_max; + tx_slot[0] = tx_ch[cnt]; + tx_slot[1] = tx_ch[4 + cnt]; + tx_slot[2] = tx_ch[2 + cnt]; + tx_slot[3] = tx_ch[3 + cnt]; + } + return 0; +} + +static int sitar_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(dai->codec); + u8 path, shift; + u16 tx_fs_reg, rx_fs_reg; + u8 tx_fs_rate, rx_fs_rate, rx_state, tx_state; + + pr_debug("%s: DAI-ID %x\n", __func__, dai->id); + + switch (params_rate(params)) { + case 8000: + tx_fs_rate = 0x00; + rx_fs_rate = 0x00; + break; + case 16000: + tx_fs_rate = 0x01; + rx_fs_rate = 0x20; + break; + case 32000: + tx_fs_rate = 0x02; + rx_fs_rate = 0x40; + break; + case 48000: + tx_fs_rate = 0x03; + rx_fs_rate = 0x60; + break; + default: + pr_err("%s: Invalid sampling rate %d\n", __func__, + params_rate(params)); + return -EINVAL; + } + + + /** + * If current dai is a tx dai, set sample rate to + * all the txfe paths that are currently not active + */ + if (dai->id == AIF1_CAP) { + + tx_state = snd_soc_read(codec, + SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_DECIMATORS; path++, shift++) { + + if (!(tx_state & (1 << shift))) { + tx_fs_reg = SITAR_A_CDC_TX1_CLK_FS_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, tx_fs_reg, + 0x03, tx_fs_rate); + } + } + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_TX_I2S_CTL, + 0x03, tx_fs_rate); + } + } else { + sitar->dai[dai->id - 1].rate = params_rate(params); + } + + /** + * TODO: Need to handle case where same RX chain takes 2 or more inputs + * with varying sample rates + */ + + /** + * If current dai is a rx dai, set sample rate to + * all the rx paths that are currently not active + */ + if (dai->id == AIF1_PB) { + + rx_state = snd_soc_read(codec, + SITAR_A_CDC_CLK_RX_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_INTERPOLATORS; path++, shift++) { + + if (!(rx_state & (1 << shift))) { + rx_fs_reg = SITAR_A_CDC_RX1_B5_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, rx_fs_reg, + 0xE0, rx_fs_rate); + } + } + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_RX_I2S_CTL, + 0x03, (rx_fs_rate >> 0x05)); + } + } else { + sitar->dai[dai->id - 1].rate = params_rate(params); + } + + return 0; +} + +static struct snd_soc_dai_ops sitar_dai_ops = { + .startup = sitar_startup, + .shutdown = sitar_shutdown, + .hw_params = sitar_hw_params, + .set_sysclk = sitar_set_dai_sysclk, + .set_fmt = sitar_set_dai_fmt, + .set_channel_map = sitar_set_channel_map, + .get_channel_map = sitar_get_channel_map, +}; + +static struct snd_soc_dai_driver sitar_dai[] = { + { + .name = "sitar_rx1", + .id = AIF1_PB, + .playback = { + .stream_name = "AIF1 Playback", + .rates = WCD9304_RATES, + .formats = SITAR_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &sitar_dai_ops, + }, + { + .name = "sitar_tx1", + .id = AIF1_CAP, + .capture = { + .stream_name = "AIF1 Capture", + .rates = WCD9304_RATES, + .formats = SITAR_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &sitar_dai_ops, + }, +}; + +static int sitar_codec_enable_slimrx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *sitar; + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar_p = snd_soc_codec_get_drvdata(codec); + u32 j = 0; + codec->control_data = dev_get_drvdata(codec->dev->parent); + sitar = codec->control_data; + /* Execute the callback only if interface type is slimbus */ + if (sitar_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_CAP) + continue; + if (!strncmp(w->sname, + sitar_dai[j].playback.stream_name, 13)) { + ++sitar_p->dai[j].ch_act; + break; + } + } + if (sitar_p->dai[j].ch_act == sitar_p->dai[j].ch_tot) + wcd9xxx_cfg_slim_sch_rx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot, + sitar_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_CAP) + continue; + if (!strncmp(w->sname, + sitar_dai[j].playback.stream_name, 13)) { + --sitar_p->dai[j].ch_act; + break; + } + } + if (!sitar_p->dai[j].ch_act) { + wcd9xxx_close_slim_sch_rx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot); + /* Wait for remove channel to complete + * before derouting Rx path + */ + usleep_range(15000, 15000); + sitar_p->dai[j].rate = 0; + memset(sitar_p->dai[j].ch_num, 0, (sizeof(u32)* + sitar_p->dai[j].ch_tot)); + sitar_p->dai[j].ch_tot = 0; + } + } + return 0; +} + +static int sitar_codec_enable_slimtx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *sitar; + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar_p = snd_soc_codec_get_drvdata(codec); + /* index to the DAI ID, for now hardcoding */ + u32 j = 0; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + sitar = codec->control_data; + + /* Execute the callback only if interface type is slimbus */ + if (sitar_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_PB) + continue; + if (!strncmp(w->sname, + sitar_dai[j].capture.stream_name, 13)) { + ++sitar_p->dai[j].ch_act; + break; + } + } + if (sitar_p->dai[j].ch_act == sitar_p->dai[j].ch_tot) + wcd9xxx_cfg_slim_sch_tx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot, + sitar_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_PB) + continue; + if (!strncmp(w->sname, + sitar_dai[j].capture.stream_name, 13)) { + --sitar_p->dai[j].ch_act; + break; + } + } + if (!sitar_p->dai[j].ch_act) { + wcd9xxx_close_slim_sch_tx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot); + sitar_p->dai[j].rate = 0; + memset(sitar_p->dai[j].ch_num, 0, (sizeof(u32)* + sitar_p->dai[j].ch_tot)); + sitar_p->dai[j].ch_tot = 0; + } + } + return 0; +} + + +static short sitar_codec_read_sta_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B3_STATUS); + bias_lsb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B2_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static short sitar_codec_read_dce_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B5_STATUS); + bias_lsb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B4_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static void sitar_turn_onoff_rel_detection(struct snd_soc_codec *codec, + bool on) +{ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x02, on << 1); +} + +static short __sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool override_bypass, bool noreldetection) +{ + short bias_value; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + if (noreldetection) + sitar_turn_onoff_rel_detection(codec, false); + + /* Turn on the override */ + if (!override_bypass) + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x4, 0x4); + if (dce) { + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x4); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(sitar->mbhc_data.t_sta_dce, + sitar->mbhc_data.t_sta_dce); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x4); + usleep_range(sitar->mbhc_data.t_dce, + sitar->mbhc_data.t_dce); + bias_value = sitar_codec_read_dce_result(codec); + } else { + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x2); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(sitar->mbhc_data.t_sta_dce, + sitar->mbhc_data.t_sta_dce); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x2); + usleep_range(sitar->mbhc_data.t_sta, + sitar->mbhc_data.t_sta); + bias_value = sitar_codec_read_sta_result(codec); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x0); + } + /* Turn off the override after measuring mic voltage */ + if (!override_bypass) + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + + if (noreldetection) + sitar_turn_onoff_rel_detection(codec, true); + wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + + return bias_value; +} + +static short sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool norel) +{ + return __sitar_codec_sta_dce(codec, dce, false, norel); +} + +static void sitar_codec_shutdown_hs_removal_detect(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + const struct sitar_mbhc_general_cfg *generic = + SITAR_MBHC_CAL_GENERAL_PTR(sitar->mbhc_cfg.calibration); + + if (!sitar->mclk_enabled && !sitar->mbhc_polling_active) + sitar_codec_enable_config_mode(codec, 1); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x6, 0x0); + + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x00); + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0xA, 0x8); + if (!sitar->mclk_enabled && !sitar->mbhc_polling_active) + sitar_codec_enable_config_mode(codec, 0); + + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x00); +} + +static void sitar_codec_cleanup_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + sitar_codec_shutdown_hs_removal_detect(codec); + + if (!sitar->mclk_enabled) { + sitar_codec_disable_clock_block(codec); + sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF); + } + + sitar->mbhc_polling_active = false; + sitar->mbhc_state = MBHC_STATE_NONE; +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static short sitar_codec_setup_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + short bias_value; + u8 cfilt_mode; + + if (!sitar->mbhc_cfg.calibration) { + pr_err("Error, no sitar calibration\n"); + return -ENODEV; + } + + if (!sitar->mclk_enabled) { + sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_MBHC_MODE); + sitar_enable_rx_bias(codec, 1); + sitar_codec_enable_clock_block(codec, 1); + } + + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x01); + + /* Make sure CFILT is in fast mode, save current mode */ + cfilt_mode = snd_soc_read(codec, sitar->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x70, 0x00); + + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x1F, 0x16); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, 0x80, 0x80); + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, 0x1F, 0x1C); + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_TEST_CTL, 0x40, 0x40); + + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, 0x80, 0x00); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x00); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + + sitar_codec_calibrate_hs_polling(codec); + + /* don't flip override */ + bias_value = __sitar_codec_sta_dce(codec, 1, true, true); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x13, 0x00); + + return bias_value; +} + +static int sitar_cancel_btn_work(struct sitar_priv *sitar) +{ + int r = 0; + struct wcd9xxx *core = dev_get_drvdata(sitar->codec->dev->parent); + + if (cancel_delayed_work_sync(&sitar->mbhc_btn_dwork)) { + /* if scheduled mbhc_btn_dwork is canceled from here, + * we have to unlock from here instead btn_work */ + wcd9xxx_unlock_sleep(core); + r = 1; + } + return r; +} + + +static u16 sitar_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce, + s16 vin_mv) +{ + short diff, zero; + struct sitar_priv *sitar; + u32 mb_mv, in; + + sitar = snd_soc_codec_get_drvdata(codec); + mb_mv = sitar->mbhc_data.micb_mv; + + if (mb_mv == 0) { + pr_err("%s: Mic Bias voltage is set to zero\n", __func__); + return -EINVAL; + } + + if (dce) { + diff = sitar->mbhc_data.dce_mb - sitar->mbhc_data.dce_z; + zero = sitar->mbhc_data.dce_z; + } else { + diff = sitar->mbhc_data.sta_mb - sitar->mbhc_data.sta_z; + zero = sitar->mbhc_data.sta_z; + } + in = (u32) diff * vin_mv; + + return (u16) (in / mb_mv) + zero; +} + +static s32 sitar_codec_sta_dce_v(struct snd_soc_codec *codec, s8 dce, + u16 bias_value) +{ + struct sitar_priv *sitar; + s16 value, z, mb; + s32 mv; + + sitar = snd_soc_codec_get_drvdata(codec); + value = bias_value; + + if (dce) { + z = (sitar->mbhc_data.dce_z); + mb = (sitar->mbhc_data.dce_mb); + mv = (value - z) * (s32)sitar->mbhc_data.micb_mv / (mb - z); + } else { + z = (sitar->mbhc_data.sta_z); + mb = (sitar->mbhc_data.sta_mb); + mv = (value - z) * (s32)sitar->mbhc_data.micb_mv / (mb - z); + } + + return mv; +} + +static void btn_lpress_fn(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct sitar_priv *sitar; + short bias_value; + int dce_mv, sta_mv; + struct wcd9xxx *core; + + pr_debug("%s:\n", __func__); + + delayed_work = to_delayed_work(work); + sitar = container_of(delayed_work, struct sitar_priv, mbhc_btn_dwork); + core = dev_get_drvdata(sitar->codec->dev->parent); + + if (sitar) { + if (sitar->mbhc_cfg.button_jack) { + bias_value = sitar_codec_read_sta_result(sitar->codec); + sta_mv = sitar_codec_sta_dce_v(sitar->codec, 0, + bias_value); + bias_value = sitar_codec_read_dce_result(sitar->codec); + dce_mv = sitar_codec_sta_dce_v(sitar->codec, 1, + bias_value); + pr_debug("%s: Reporting long button press event" + " STA: %d, DCE: %d\n", __func__, sta_mv, dce_mv); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.button_jack, + sitar->buttons_pressed, + sitar->buttons_pressed); + } + } else { + pr_err("%s: Bad sitar private data\n", __func__); + } + + pr_debug("%s: leave\n", __func__); + wcd9xxx_unlock_sleep(core); +} + + +void sitar_mbhc_cal(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar; + struct sitar_mbhc_btn_detect_cfg *btn_det; + u8 cfilt_mode, bg_mode; + u8 ncic, nmeas, navg; + u32 mclk_rate; + u32 dce_wait, sta_wait; + u8 *n_cic; + void *calibration; + + sitar = snd_soc_codec_get_drvdata(codec); + calibration = sitar->mbhc_cfg.calibration; + + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + sitar_turn_onoff_rel_detection(codec, false); + + /* First compute the DCE / STA wait times + * depending on tunable parameters. + * The value is computed in microseconds + */ + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(calibration); + n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC); + ncic = n_cic[sitar_codec_mclk_index(sitar)]; + nmeas = SITAR_MBHC_CAL_BTN_DET_PTR(calibration)->n_meas; + navg = SITAR_MBHC_CAL_GENERAL_PTR(calibration)->mbhc_navg; + mclk_rate = sitar->mbhc_cfg.mclk_rate; + dce_wait = (1000 * 512 * 60 * (nmeas + 1)) / (mclk_rate / 1000); + sta_wait = (1000 * 128 * (navg + 1)) / (mclk_rate / 1000); + + sitar->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + sitar->mbhc_data.t_sta = DEFAULT_STA_WAIT; + + /* LDOH and CFILT are already configured during pdata handling. + * Only need to make sure CFILT and bandgap are in Fast mode. + * Need to restore defaults once calculation is done. + */ + cfilt_mode = snd_soc_read(codec, sitar->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40, 0x00); + bg_mode = snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x02, + 0x02); + + /* Micbias, CFILT, LDOH, MBHC MUX mode settings + * to perform ADC calibration + */ + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x60, + sitar->mbhc_cfg.micbias << 5); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x60, 0x60); + snd_soc_write(codec, SITAR_A_TX_4_MBHC_TEST_CTL, 0x78); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x04); + + /* DCE measurement for 0 volts */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x04); + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(sitar->mbhc_data.t_dce, sitar->mbhc_data.t_dce); + sitar->mbhc_data.dce_z = sitar_codec_read_dce_result(codec); + + /* DCE measurment for MB voltage */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(sitar->mbhc_data.t_dce, sitar->mbhc_data.t_dce); + sitar->mbhc_data.dce_mb = sitar_codec_read_dce_result(codec); + + /* Sta measuremnt for 0 volts */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x02); + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(sitar->mbhc_data.t_sta, sitar->mbhc_data.t_sta); + sitar->mbhc_data.sta_z = sitar_codec_read_sta_result(codec); + + /* STA Measurement for MB Voltage */ + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(sitar->mbhc_data.t_sta, sitar->mbhc_data.t_sta); + sitar->mbhc_data.sta_mb = sitar_codec_read_sta_result(codec); + + /* Restore default settings. */ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x02, bg_mode); + + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + usleep_range(100, 100); + + wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + sitar_turn_onoff_rel_detection(codec, true); +} + +void *sitar_mbhc_cal_btn_det_mp(const struct sitar_mbhc_btn_detect_cfg* btn_det, + const enum sitar_mbhc_btn_det_mem mem) +{ + void *ret = &btn_det->_v_btn_low; + + switch (mem) { + case SITAR_BTN_DET_GAIN: + ret += sizeof(btn_det->_n_cic); + case SITAR_BTN_DET_N_CIC: + ret += sizeof(btn_det->_n_ready); + case SITAR_BTN_DET_N_READY: + ret += sizeof(btn_det->_v_btn_high[0]) * btn_det->num_btn; + case SITAR_BTN_DET_V_BTN_HIGH: + ret += sizeof(btn_det->_v_btn_low[0]) * btn_det->num_btn; + case SITAR_BTN_DET_V_BTN_LOW: + /* do nothing */ + break; + default: + ret = NULL; + } + + return ret; +} + +static void sitar_mbhc_calc_thres(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar; + s16 btn_mv = 0, btn_delta_mv; + struct sitar_mbhc_btn_detect_cfg *btn_det; + struct sitar_mbhc_plug_type_cfg *plug_type; + u16 *btn_high; + u8 *n_ready; + int i; + + sitar = snd_soc_codec_get_drvdata(codec); + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration); + plug_type = SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration); + + n_ready = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_READY); + if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_12288KHZ) { + sitar->mbhc_data.npoll = 9; + sitar->mbhc_data.nbounce_wait = 30; + } else if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_9600KHZ) { + sitar->mbhc_data.npoll = 7; + sitar->mbhc_data.nbounce_wait = 23; + } + + sitar->mbhc_data.t_sta_dce = ((1000 * 256) / + (sitar->mbhc_cfg.mclk_rate / 1000) * + n_ready[sitar_codec_mclk_index(sitar)]) + + 10; + sitar->mbhc_data.v_ins_hu = + sitar_codec_v_sta_dce(codec, STA, plug_type->v_hs_max); + sitar->mbhc_data.v_ins_h = + sitar_codec_v_sta_dce(codec, DCE, plug_type->v_hs_max); + + btn_high = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) + btn_mv = btn_high[i] > btn_mv ? btn_high[i] : btn_mv; + + sitar->mbhc_data.v_b1_h = sitar_codec_v_sta_dce(codec, DCE, btn_mv); + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_sta; + + sitar->mbhc_data.v_b1_hu = + sitar_codec_v_sta_dce(codec, STA, btn_delta_mv); + + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_cic; + + sitar->mbhc_data.v_b1_huc = + sitar_codec_v_sta_dce(codec, DCE, btn_delta_mv); + + sitar->mbhc_data.v_brh = sitar->mbhc_data.v_b1_h; + sitar->mbhc_data.v_brl = SITAR_MBHC_BUTTON_MIN; + + sitar->mbhc_data.v_no_mic = + sitar_codec_v_sta_dce(codec, STA, plug_type->v_no_mic); +} + +void sitar_mbhc_init(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar; + struct sitar_mbhc_general_cfg *generic; + struct sitar_mbhc_btn_detect_cfg *btn_det; + int n; + u8 *n_cic, *gain; + + pr_err("%s(): ENTER\n", __func__); + sitar = snd_soc_codec_get_drvdata(codec); + generic = SITAR_MBHC_CAL_GENERAL_PTR(sitar->mbhc_cfg.calibration); + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration); + + for (n = 0; n < 8; n++) { + if (n != 7) { + snd_soc_update_bits(codec, + SITAR_A_CDC_MBHC_FIR_B1_CFG, + 0x07, n); + snd_soc_write(codec, SITAR_A_CDC_MBHC_FIR_B2_CFG, + btn_det->c[n]); + } + } + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B2_CTL, 0x07, + btn_det->nc); + + n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_TIMER_B6_CTL, 0xFF, + n_cic[sitar_codec_mclk_index(sitar)]); + + gain = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_GAIN); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B2_CTL, 0x78, + gain[sitar_codec_mclk_index(sitar)] << 3); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_TIMER_B4_CTL, 0x70, + generic->mbhc_nsa << 4); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_TIMER_B4_CTL, 0x0F, + btn_det->n_meas); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B5_CTL, generic->mbhc_navg); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x80, 0x80); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x78, + btn_det->mbhc_nsc << 3); + + snd_soc_update_bits(codec, SITAR_A_MICB_1_MBHC, 0x03, + sitar->mbhc_cfg.micbias); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + + snd_soc_update_bits(codec, SITAR_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0); + +} + +static bool sitar_mbhc_fw_validate(const struct firmware *fw) +{ + u32 cfg_offset; + struct sitar_mbhc_imped_detect_cfg *imped_cfg; + struct sitar_mbhc_btn_detect_cfg *btn_cfg; + + if (fw->size < SITAR_MBHC_CAL_MIN_SIZE) + return false; + + /* previous check guarantees that there is enough fw data up + * to num_btn + */ + btn_cfg = SITAR_MBHC_CAL_BTN_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) btn_cfg - (void *) fw->data); + if (fw->size < (cfg_offset + SITAR_MBHC_CAL_BTN_SZ(btn_cfg))) + return false; + + /* previous check guarantees that there is enough fw data up + * to start of impedance detection configuration + */ + imped_cfg = SITAR_MBHC_CAL_IMPED_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) imped_cfg - (void *) fw->data); + + if (fw->size < (cfg_offset + SITAR_MBHC_CAL_IMPED_MIN_SZ)) + return false; + + if (fw->size < (cfg_offset + SITAR_MBHC_CAL_IMPED_SZ(imped_cfg))) + return false; + + return true; +} + + +static void sitar_turn_onoff_override(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, on << 2); +} + +/* called under codec_resource_lock acquisition */ +void sitar_set_and_turnoff_hph_padac(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u8 wg_time; + + wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + /* If headphone PA is on, check if userspace receives + * removal event to sync-up PA's state */ + if (sitar_is_hph_pa_on(codec)) { + pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__); + set_bit(SITAR_HPHL_PA_OFF_ACK, &sitar->hph_pa_dac_state); + set_bit(SITAR_HPHR_PA_OFF_ACK, &sitar->hph_pa_dac_state); + } else { + pr_debug("%s PA is off\n", __func__); + } + + if (sitar_is_hph_dac_on(codec, 1)) + set_bit(SITAR_HPHL_DAC_OFF_ACK, &sitar->hph_pa_dac_state); + if (sitar_is_hph_dac_on(codec, 0)) + set_bit(SITAR_HPHR_DAC_OFF_ACK, &sitar->hph_pa_dac_state); + + snd_soc_update_bits(codec, SITAR_A_RX_HPH_CNP_EN, 0x30, 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_L_DAC_CTL, + 0xC0, 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_R_DAC_CTL, + 0xC0, 0x00); + usleep_range(wg_time * 1000, wg_time * 1000); +} + +static void sitar_clr_and_turnon_hph_padac(struct sitar_priv *sitar) +{ + bool pa_turned_on = false; + struct snd_soc_codec *codec = sitar->codec; + u8 wg_time; + + wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + if (test_and_clear_bit(SITAR_HPHR_DAC_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable DAC\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_R_DAC_CTL, + 0xC0, 0xC0); + } + if (test_and_clear_bit(SITAR_HPHL_DAC_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable DAC\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_L_DAC_CTL, + 0xC0, 0xC0); + } + + if (test_and_clear_bit(SITAR_HPHR_PA_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable PA\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x10, + 1 << 4); + pa_turned_on = true; + } + if (test_and_clear_bit(SITAR_HPHL_PA_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable PA\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x20, + 1 << 5); + pa_turned_on = true; + } + + if (pa_turned_on) { + pr_debug("%s: PA was turned off by MBHC and not by DAPM\n", + __func__); + usleep_range(wg_time * 1000, wg_time * 1000); + } +} + +static void sitar_codec_report_plug(struct snd_soc_codec *codec, int insertion, + enum snd_jack_types jack_type) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (!insertion) { + /* Report removal */ + sitar->hph_status &= ~jack_type; + if (sitar->mbhc_cfg.headset_jack) { + /* cancel possibly scheduled btn work and + * report release if we reported button press */ + if (sitar_cancel_btn_work(sitar)) { + pr_debug("%s: button press is canceled\n", + __func__); + } else if (sitar->buttons_pressed) { + pr_debug("%s: Reporting release for reported " + "button press %d\n", __func__, + jack_type); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.button_jack, 0, + sitar->buttons_pressed); + sitar->buttons_pressed &= + ~SITAR_JACK_BUTTON_MASK; + } + pr_debug("%s: Reporting removal %d\n", __func__, + jack_type); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + sitar_set_and_turnoff_hph_padac(codec); + hphocp_off_report(sitar, SND_JACK_OC_HPHR, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + hphocp_off_report(sitar, SND_JACK_OC_HPHL, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + sitar->current_plug = PLUG_TYPE_NONE; + sitar->mbhc_polling_active = false; + } else { + /* Report insertion */ + sitar->hph_status |= jack_type; + + if (jack_type == SND_JACK_HEADPHONE) + sitar->current_plug = PLUG_TYPE_HEADPHONE; + else if (jack_type == SND_JACK_HEADSET) { + sitar->mbhc_polling_active = true; + sitar->current_plug = PLUG_TYPE_HEADSET; + } + if (sitar->mbhc_cfg.headset_jack) { + pr_debug("%s: Reporting insertion %d\n", __func__, + jack_type); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + sitar_clr_and_turnon_hph_padac(sitar); + } +} + + +static bool sitar_hs_gpio_level_remove(struct sitar_priv *sitar) +{ + return (gpio_get_value_cansleep(sitar->mbhc_cfg.gpio) != + sitar->mbhc_cfg.gpio_level_insert); +} + +static bool sitar_is_invalid_insert_delta(struct snd_soc_codec *codec, + int mic_volt, int mic_volt_prev) +{ + int delta = abs(mic_volt - mic_volt_prev); + if (delta > SITAR_MBHC_FAKE_INSERT_VOLT_DELTA_MV) { + pr_debug("%s: volt delta %dmv\n", __func__, delta); + return true; + } + return false; +} + +static bool sitar_is_invalid_insertion_range(struct snd_soc_codec *codec, + s32 mic_volt) +{ + bool invalid = false; + + if (mic_volt < SITAR_MBHC_FAKE_INSERT_HIGH + && (mic_volt > SITAR_MBHC_FAKE_INSERT_LOW)) { + invalid = true; + } + + return invalid; +} + +static bool sitar_codec_is_invalid_plug(struct snd_soc_codec *codec, + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT], + enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT]) +{ + int i; + bool r = false; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + struct sitar_mbhc_plug_type_cfg *plug_type_ptr = + SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration); + + for (i = 0 ; i < MBHC_NUM_DCE_PLUG_DETECT && !r; i++) { + if (mic_mv[i] < plug_type_ptr->v_no_mic) + plug_type[i] = PLUG_TYPE_HEADPHONE; + else if (mic_mv[i] < plug_type_ptr->v_hs_max) + plug_type[i] = PLUG_TYPE_HEADSET; + else if (mic_mv[i] > plug_type_ptr->v_hs_max) + plug_type[i] = PLUG_TYPE_HIGH_HPH; + + r = sitar_is_invalid_insertion_range(codec, mic_mv[i]); + if (!r && i > 0) { + if (plug_type[i-1] != plug_type[i]) + r = true; + else + r = sitar_is_invalid_insert_delta(codec, + mic_mv[i], + mic_mv[i - 1]); + } + } + + return r; +} + +/* called under codec_resource_lock acquisition */ +void sitar_find_plug_and_report(struct snd_soc_codec *codec, + enum sitar_mbhc_plug_type plug_type) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (plug_type == PLUG_TYPE_HEADPHONE + && sitar->current_plug == PLUG_TYPE_NONE) { + /* Nothing was reported previously + * reporte a headphone + */ + sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + sitar_codec_cleanup_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HEADSET) { + /* If Headphone was reported previously, this will + * only report the mic line + */ + sitar_codec_report_plug(codec, 1, SND_JACK_HEADSET); + msleep(100); + sitar_codec_start_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + if (sitar->current_plug == PLUG_TYPE_NONE) + sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + sitar_codec_cleanup_hs_polling(codec); + pr_debug("setup mic trigger for further detection\n"); + sitar->lpi_enabled = true; + /* TODO ::: sitar_codec_enable_hs_detect */ + pr_err("%s(): High impedence hph not supported\n", __func__); + } +} + +/* should be called under interrupt context that hold suspend */ +static void sitar_schedule_hs_detect_plug(struct sitar_priv *sitar) +{ + pr_debug("%s: scheduling sitar_hs_correct_gpio_plug\n", __func__); + sitar->hs_detect_work_stop = false; + wcd9xxx_lock_sleep(sitar->codec->control_data); + schedule_work(&sitar->hs_correct_plug_work); +} + +/* called under codec_resource_lock acquisition */ +static void sitar_cancel_hs_detect_plug(struct sitar_priv *sitar) +{ + pr_debug("%s: canceling hs_correct_plug_work\n", __func__); + sitar->hs_detect_work_stop = true; + wmb(); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + if (cancel_work_sync(&sitar->hs_correct_plug_work)) { + pr_debug("%s: hs_correct_plug_work is canceled\n", __func__); + wcd9xxx_unlock_sleep(sitar->codec->control_data); + } + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); +} + +static void sitar_hs_correct_gpio_plug(struct work_struct *work) +{ + struct sitar_priv *sitar; + struct snd_soc_codec *codec; + int retry = 0, i; + bool correction = false; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT]; + unsigned long timeout; + + sitar = container_of(work, struct sitar_priv, hs_correct_plug_work); + codec = sitar->codec; + + pr_debug("%s: enter\n", __func__); + sitar->mbhc_cfg.mclk_cb_fn(codec, 1, false); + + /* Keep override on during entire plug type correction work. + * + * This is okay under the assumption that any GPIO irqs which use + * MBHC block cancel and sync this work so override is off again + * prior to GPIO interrupt handler's MBHC block usage. + * Also while this correction work is running, we can guarantee + * DAPM doesn't use any MBHC block as this work only runs with + * headphone detection. + */ + sitar_turn_onoff_override(codec, true); + + timeout = jiffies + msecs_to_jiffies(SITAR_HS_DETECT_PLUG_TIME_MS); + while (!time_after(jiffies, timeout)) { + ++retry; + rmb(); + if (sitar->hs_detect_work_stop) { + pr_debug("%s: stop requested\n", __func__); + break; + } + + msleep(SITAR_HS_DETECT_PLUG_INERVAL_MS); + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO value is low\n", __func__); + break; + } + + /* can race with removal interrupt */ + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s : DCE run %d, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + + if (sitar_codec_is_invalid_plug(codec, mic_mv, plug_type)) { + pr_debug("Invalid plug in attempt # %d\n", retry); + if (retry == NUM_ATTEMPTS_TO_REPORT && + sitar->current_plug == PLUG_TYPE_NONE) { + sitar_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } + } else if (!sitar_codec_is_invalid_plug(codec, mic_mv, + plug_type) && + plug_type[0] == PLUG_TYPE_HEADPHONE) { + pr_debug("Good headphone detected, continue polling mic\n"); + if (sitar->current_plug == PLUG_TYPE_NONE) { + sitar_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } + } else { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + /* Turn off override */ + sitar_turn_onoff_override(codec, false); + sitar_find_plug_and_report(codec, plug_type[0]); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + pr_debug("Attempt %d found correct plug %d\n", retry, + plug_type[0]); + correction = true; + break; + } + } + + /* Turn off override */ + if (!correction) + sitar_turn_onoff_override(codec, false); + + sitar->mbhc_cfg.mclk_cb_fn(codec, 0, false); + pr_debug("%s: leave\n", __func__); + /* unlock sleep */ + wcd9xxx_unlock_sleep(sitar->codec->control_data); +} + +/* called under codec_resource_lock acquisition */ +static void sitar_codec_decide_gpio_plug(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT]; + int i; + + pr_debug("%s: enter\n", __func__); + + sitar_turn_onoff_override(codec, true); + mb_v[0] = sitar_codec_setup_hs_polling(codec); + mic_mv[0] = sitar_codec_sta_dce_v(codec, 1, mb_v[0]); + pr_debug("%s: DCE run 1, mic_mv = %d\n", __func__, mic_mv[0]); + + for (i = 1; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s: DCE run %d, mic_mv = %d\n", __func__, i + 1, + mic_mv[i]); + } + sitar_turn_onoff_override(codec, false); + + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO value is low when determining plug\n", + __func__); + return; + } + + if (sitar_codec_is_invalid_plug(codec, mic_mv, plug_type)) { + sitar_schedule_hs_detect_plug(sitar); + } else if (plug_type[0] == PLUG_TYPE_HEADPHONE) { + sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + sitar_schedule_hs_detect_plug(sitar); + } else if (plug_type[0] == PLUG_TYPE_HEADSET) { + pr_debug("%s: Valid plug found, determine plug type\n", + __func__); + sitar_find_plug_and_report(codec, plug_type[0]); + } + +} + +/* called under codec_resource_lock acquisition */ +static void sitar_codec_detect_plug_type(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + const struct sitar_mbhc_plug_detect_cfg *plug_det = + SITAR_MBHC_CAL_PLUG_DET_PTR(sitar->mbhc_cfg.calibration); + + if (plug_det->t_ins_complete > 20) + msleep(plug_det->t_ins_complete); + else + usleep_range(plug_det->t_ins_complete * 1000, + plug_det->t_ins_complete * 1000); + + if (sitar_hs_gpio_level_remove(sitar)) + pr_debug("%s: GPIO value is low when determining " + "plug\n", __func__); + else + sitar_codec_decide_gpio_plug(codec); + + return; +} + +static void sitar_hs_gpio_handler(struct snd_soc_codec *codec) +{ + bool insert; + struct sitar_priv *priv = snd_soc_codec_get_drvdata(codec); + bool is_removed = false; + + pr_debug("%s: enter\n", __func__); + + priv->in_gpio_handler = true; + /* Wait here for debounce time */ + usleep_range(SITAR_GPIO_IRQ_DEBOUNCE_TIME_US, + SITAR_GPIO_IRQ_DEBOUNCE_TIME_US); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + + /* cancel pending button press */ + if (sitar_cancel_btn_work(priv)) + pr_debug("%s: button press is canceled\n", __func__); + + insert = (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) == + priv->mbhc_cfg.gpio_level_insert); + if ((priv->current_plug == PLUG_TYPE_NONE) && insert) { + priv->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + sitar_cancel_hs_detect_plug(priv); + + /* Disable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x01, 0x00); + sitar_codec_detect_plug_type(codec); + } else if ((priv->current_plug != PLUG_TYPE_NONE) && !insert) { + priv->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + sitar_cancel_hs_detect_plug(priv); + + if (priv->current_plug == PLUG_TYPE_HEADPHONE) { + sitar_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + is_removed = true; + } else if (priv->current_plug == PLUG_TYPE_HEADSET) { + sitar_codec_pause_hs_polling(codec); + sitar_codec_cleanup_hs_polling(codec); + sitar_codec_report_plug(codec, 0, SND_JACK_HEADSET); + is_removed = true; + } + + if (is_removed) { + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, + priv->mbhc_bias_regs.ctl_reg, 0x01, + 0x01); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x01, + 0x01); + /* Make sure mic trigger is turned off */ + snd_soc_update_bits(codec, + priv->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, + priv->mbhc_bias_regs.mbhc_reg, + 0x90, 0x00); + /* Reset MBHC State Machine */ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, + 0x08, 0x00); + /* Turn off override */ + sitar_turn_onoff_override(codec, false); + } + } + + priv->in_gpio_handler = false; + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + pr_debug("%s: leave\n", __func__); +} + +static irqreturn_t sitar_mechanical_plug_detect_irq(int irq, void *data) +{ + int r = IRQ_HANDLED; + struct snd_soc_codec *codec = data; + + if (unlikely(wcd9xxx_lock_sleep(codec->control_data) == false)) { + pr_warn("%s(): Failed to hold suspend\n", __func__); + r = IRQ_NONE; + } else { + sitar_hs_gpio_handler(codec); + wcd9xxx_unlock_sleep(codec->control_data); + } + return r; +} + +static int sitar_mbhc_init_and_calibrate(struct snd_soc_codec *codec) +{ + int rc = 0; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + sitar->mbhc_cfg.mclk_cb_fn(codec, 1, false); + sitar_mbhc_init(codec); + sitar_mbhc_cal(codec); + sitar_mbhc_calc_thres(codec); + sitar->mbhc_cfg.mclk_cb_fn(codec, 0, false); + sitar_codec_calibrate_hs_polling(codec); + + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, + 0x01, 0x01); + + rc = request_threaded_irq(sitar->mbhc_cfg.gpio_irq, + NULL, + sitar_mechanical_plug_detect_irq, + (IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING), + "sitar-hs-gpio", codec); + + if (!IS_ERR_VALUE(rc)) { + rc = enable_irq_wake(sitar->mbhc_cfg.gpio_irq); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, + 0x10, 0x10); + wcd9xxx_enable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + wcd9xxx_enable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + /* Bootup time detection */ + sitar_hs_gpio_handler(codec); + } + + return rc; +} + +static void mbhc_fw_read(struct work_struct *work) +{ + struct delayed_work *dwork; + struct sitar_priv *sitar; + struct snd_soc_codec *codec; + const struct firmware *fw; + int ret = -1, retry = 0; + + dwork = to_delayed_work(work); + sitar = container_of(dwork, struct sitar_priv, + mbhc_firmware_dwork); + codec = sitar->codec; + + while (retry < MBHC_FW_READ_ATTEMPTS) { + retry++; + pr_info("%s:Attempt %d to request MBHC firmware\n", + __func__, retry); + ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin", + codec->dev); + + if (ret != 0) { + usleep_range(MBHC_FW_READ_TIMEOUT, + MBHC_FW_READ_TIMEOUT); + } else { + pr_info("%s: MBHC Firmware read succesful\n", __func__); + break; + } + } + + if (ret != 0) { + pr_err("%s: Cannot load MBHC firmware use default cal\n", + __func__); + } else if (sitar_mbhc_fw_validate(fw) == false) { + pr_err("%s: Invalid MBHC cal data size use default cal\n", + __func__); + release_firmware(fw); + } else { + sitar->calibration = (void *)fw->data; + sitar->mbhc_fw = fw; + } + + sitar_mbhc_init_and_calibrate(codec); +} + +int sitar_hs_detect(struct snd_soc_codec *codec, + const struct sitar_mbhc_config *cfg) +{ + struct sitar_priv *sitar; + int rc = 0; + + if (!codec || !cfg->calibration) { + pr_err("Error: no codec or calibration\n"); + return -EINVAL; + } + + if (cfg->mclk_rate != SITAR_MCLK_RATE_12288KHZ) { + if (cfg->mclk_rate == SITAR_MCLK_RATE_9600KHZ) + pr_err("Error: clock rate %dHz is not yet supported\n", + cfg->mclk_rate); + else + pr_err("Error: unsupported clock rate %d\n", + cfg->mclk_rate); + return -EINVAL; + } + + sitar = snd_soc_codec_get_drvdata(codec); + sitar->mbhc_cfg = *cfg; + sitar->in_gpio_handler = false; + sitar->current_plug = PLUG_TYPE_NONE; + sitar->lpi_enabled = false; + sitar_get_mbhc_micbias_regs(codec, &sitar->mbhc_bias_regs); + + /* Put CFILT in fast mode by default */ + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, + 0x40, SITAR_CFILT_FAST_MODE); + + INIT_DELAYED_WORK(&sitar->mbhc_firmware_dwork, mbhc_fw_read); + INIT_DELAYED_WORK(&sitar->mbhc_btn_dwork, btn_lpress_fn); + INIT_WORK(&sitar->hphlocp_work, hphlocp_off_report); + INIT_WORK(&sitar->hphrocp_work, hphrocp_off_report); + INIT_WORK(&sitar->hs_correct_plug_work, + sitar_hs_correct_gpio_plug); + + if (!sitar->mbhc_cfg.read_fw_bin) { + rc = sitar_mbhc_init_and_calibrate(codec); + } else { + schedule_delayed_work(&sitar->mbhc_firmware_dwork, + usecs_to_jiffies(MBHC_FW_READ_TIMEOUT)); + } + + return rc; +} +EXPORT_SYMBOL_GPL(sitar_hs_detect); + +static int sitar_determine_button(const struct sitar_priv *priv, + const s32 bias_mv) +{ + s16 *v_btn_low, *v_btn_high; + struct sitar_mbhc_btn_detect_cfg *btn_det; + int i, btn = -1; + + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + v_btn_low = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_V_BTN_LOW); + v_btn_high = sitar_mbhc_cal_btn_det_mp(btn_det, + SITAR_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) { + if ((v_btn_low[i] <= bias_mv) && (v_btn_high[i] >= bias_mv)) { + btn = i; + break; + } + } + + if (btn == -1) + pr_debug("%s: couldn't find button number for mic mv %d\n", + __func__, bias_mv); + + return btn; +} + +static int sitar_get_button_mask(const int btn) +{ + int mask = 0; + switch (btn) { + case 0: + mask = SND_JACK_BTN_0; + break; + case 1: + mask = SND_JACK_BTN_1; + break; + case 2: + mask = SND_JACK_BTN_2; + break; + case 3: + mask = SND_JACK_BTN_3; + break; + case 4: + mask = SND_JACK_BTN_4; + break; + case 5: + mask = SND_JACK_BTN_5; + break; + case 6: + mask = SND_JACK_BTN_6; + break; + case 7: + mask = SND_JACK_BTN_7; + break; + } + return mask; +} + + +static irqreturn_t sitar_dce_handler(int irq, void *data) +{ + int i, mask; + short dce, sta, bias_value_dce; + s32 mv, stamv, bias_mv_dce; + int btn = -1, meas = 0; + struct sitar_priv *priv = data; + const struct sitar_mbhc_btn_detect_cfg *d = + SITAR_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + short btnmeas[d->n_btn_meas + 1]; + struct snd_soc_codec *codec = priv->codec; + struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent); + int n_btn_meas = d->n_btn_meas; + u8 mbhc_status = snd_soc_read(codec, SITAR_A_CDC_MBHC_B1_STATUS) & 0x3E; + + pr_debug("%s: enter\n", __func__); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + if (priv->mbhc_state == MBHC_STATE_POTENTIAL_RECOVERY) { + pr_debug("%s: mbhc is being recovered, skip button press\n", + __func__); + goto done; + } + + priv->mbhc_state = MBHC_STATE_POTENTIAL; + + if (!priv->mbhc_polling_active) { + pr_warn("%s: mbhc polling is not active, skip button press\n", + __func__); + goto done; + } + + dce = sitar_codec_read_dce_result(codec); + mv = sitar_codec_sta_dce_v(codec, 1, dce); + + /* If GPIO interrupt already kicked in, ignore button press */ + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO State Changed, ignore button press\n", + __func__); + btn = -1; + goto done; + } + + if (mbhc_status != SITAR_MBHC_STATUS_REL_DETECTION) { + if (priv->mbhc_last_resume && + !time_after(jiffies, priv->mbhc_last_resume + HZ)) { + pr_debug("%s: Button is already released shortly after " + "resume\n", __func__); + n_btn_meas = 0; + } else { + pr_debug("%s: Button is already released without " + "resume", __func__); + sta = sitar_codec_read_sta_result(codec); + stamv = sitar_codec_sta_dce_v(codec, 0, sta); + btn = sitar_determine_button(priv, mv); + if (btn != sitar_determine_button(priv, stamv)) + btn = -1; + goto done; + } + } + + /* determine pressed button */ + btnmeas[meas++] = sitar_determine_button(priv, mv); + pr_debug("%s: meas %d - DCE %d,%d, button %d\n", __func__, + meas - 1, dce, mv, btnmeas[meas - 1]); + if (n_btn_meas == 0) + btn = btnmeas[0]; + for (; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); meas++) { + bias_value_dce = sitar_codec_sta_dce(codec, 1, false); + bias_mv_dce = sitar_codec_sta_dce_v(codec, 1, bias_value_dce); + btnmeas[meas] = sitar_determine_button(priv, bias_mv_dce); + pr_debug("%s: meas %d - DCE %d,%d, button %d\n", + __func__, meas, bias_value_dce, bias_mv_dce, + btnmeas[meas]); + /* if large enough measurements are collected, + * start to check if last all n_btn_con measurements were + * in same button low/high range */ + if (meas + 1 >= d->n_btn_con) { + for (i = 0; i < d->n_btn_con; i++) + if ((btnmeas[meas] < 0) || + (btnmeas[meas] != btnmeas[meas - i])) + break; + if (i == d->n_btn_con) { + /* button pressed */ + btn = btnmeas[meas]; + break; + } else if ((n_btn_meas - meas) < (d->n_btn_con - 1)) { + /* if left measurements are less than n_btn_con, + * it's impossible to find button number */ + break; + } + } + } + + if (btn >= 0) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO already triggered, ignore button " + "press\n", __func__); + goto done; + } + mask = sitar_get_button_mask(btn); + priv->buttons_pressed |= mask; + wcd9xxx_lock_sleep(core); + if (schedule_delayed_work(&priv->mbhc_btn_dwork, + msecs_to_jiffies(400)) == 0) { + WARN(1, "Button pressed twice without release" + "event\n"); + wcd9xxx_unlock_sleep(core); + } + } else { + pr_debug("%s: bogus button press, too short press?\n", + __func__); + } + + done: + pr_debug("%s: leave\n", __func__); + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static int sitar_is_fake_press(struct sitar_priv *priv) +{ + int i; + int r = 0; + struct snd_soc_codec *codec = priv->codec; + const int dces = MBHC_NUM_DCE_PLUG_DETECT; + short mb_v; + + for (i = 0; i < dces; i++) { + usleep_range(10000, 10000); + if (i == 0) { + mb_v = sitar_codec_sta_dce(codec, 0, true); + pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v, + sitar_codec_sta_dce_v(codec, 0, mb_v)); + if (mb_v < (short)priv->mbhc_data.v_b1_hu || + mb_v > (short)priv->mbhc_data.v_ins_hu) { + r = 1; + break; + } + } else { + mb_v = sitar_codec_sta_dce(codec, 1, true); + pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v, + sitar_codec_sta_dce_v(codec, 1, mb_v)); + if (mb_v < (short)priv->mbhc_data.v_b1_h || + mb_v > (short)priv->mbhc_data.v_ins_h) { + r = 1; + break; + } + } + } + + return r; +} + +static irqreturn_t sitar_release_handler(int irq, void *data) +{ + int ret; + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + priv->mbhc_state = MBHC_STATE_RELEASE; + + if (priv->buttons_pressed & SITAR_JACK_BUTTON_MASK) { + ret = sitar_cancel_btn_work(priv); + if (ret == 0) { + pr_debug("%s: Reporting long button release event\n", + __func__); + if (priv->mbhc_cfg.button_jack) + sitar_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } else { + if (sitar_is_fake_press(priv)) { + pr_debug("%s: Fake button press interrupt\n", + __func__); + } else if (priv->mbhc_cfg.button_jack) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO kicked in, ignore\n", + __func__); + } else { + pr_debug("%s: Reporting short button 0 " + "press and release\n", + __func__); + sitar_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, + priv->buttons_pressed, + priv->buttons_pressed); + sitar_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } + } + } + + priv->buttons_pressed &= ~SITAR_JACK_BUTTON_MASK; + } + + sitar_codec_calibrate_hs_polling(codec); + + if (priv->mbhc_cfg.gpio) + msleep(SITAR_MBHC_GPIO_REL_DEBOUNCE_TIME_MS); + + sitar_codec_start_hs_polling(codec); + + pr_debug("%s: leave\n", __func__); + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + + return IRQ_HANDLED; +} + +static irqreturn_t sitar_hphl_ocp_irq(int irq, void *data) +{ + struct sitar_priv *sitar = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHL OCP irq\n", __func__); + + if (sitar) { + codec = sitar->codec; + if (sitar->hphlocp_cnt++ < SITAR_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + sitar->hphlocp_cnt = 0; + sitar->hph_status |= SND_JACK_OC_HPHL; + if (sitar->mbhc_cfg.headset_jack) + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + } else { + pr_err("%s: Bad sitar private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t sitar_hphr_ocp_irq(int irq, void *data) +{ + struct sitar_priv *sitar = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHR OCP irq\n", __func__); + + if (sitar) { + codec = sitar->codec; + if (sitar->hphrocp_cnt++ < SITAR_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + sitar->hphrocp_cnt = 0; + sitar->hph_status |= SND_JACK_OC_HPHR; + if (sitar->mbhc_cfg.headset_jack) + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + } else { + pr_err("%s: Bad sitar private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t sitar_hs_insert_irq(int irq, void *data) +{ + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x03, 0x00); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, priv->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + + pr_debug("%s: MIC trigger insertion interrupt\n", __func__); + + rmb(); + if (priv->lpi_enabled) + msleep(100); + + rmb(); + if (!priv->lpi_enabled) { + pr_debug("%s: lpi is disabled\n", __func__); + } else if (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) == + priv->mbhc_cfg.gpio_level_insert) { + pr_debug("%s: Valid insertion, " + "detect plug type\n", __func__); + sitar_codec_decide_gpio_plug(codec); + } else { + pr_debug("%s: Invalid insertion, " + "stop plug detection\n", __func__); + } + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static bool is_valid_mic_voltage(struct snd_soc_codec *codec, s32 mic_mv) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + struct sitar_mbhc_plug_type_cfg *plug_type = + SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration); + + return (!(mic_mv > SITAR_MBHC_FAKE_INSERT_LOW + && mic_mv < SITAR_MBHC_FAKE_INSERT_HIGH) + && (mic_mv > plug_type->v_no_mic) + && (mic_mv < plug_type->v_hs_max)) ? true : false; +} + +/* called under codec_resource_lock acquisition + * returns true if mic voltage range is back to normal insertion + * returns false either if timedout or removed */ +static bool sitar_hs_remove_settle(struct snd_soc_codec *codec) +{ + int i; + bool timedout, settled = false; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + unsigned long retry = 0, timeout; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + timeout = jiffies + msecs_to_jiffies(SITAR_HS_DETECT_PLUG_TIME_MS); + while (!(timedout = time_after(jiffies, timeout))) { + retry++; + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + if (retry > 1) + msleep(250); + else + msleep(50); + + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + sitar_turn_onoff_override(codec, true); + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + sitar_turn_onoff_override(codec, false); + + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) + if (!is_valid_mic_voltage(codec, mic_mv[i])) + break; + + if (i == MBHC_NUM_DCE_PLUG_DETECT) { + pr_debug("%s: MIC voltage settled\n", __func__); + settled = true; + msleep(200); + break; + } + } + + if (timedout) + pr_debug("%s: Microphone did not settle in %d seconds\n", + __func__, SITAR_HS_DETECT_PLUG_TIME_MS); + return settled; +} + +static irqreturn_t sitar_hs_remove_irq(int irq, void *data) +{ + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter, removal interrupt\n", __func__); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + if (sitar_hs_remove_settle(codec)) + sitar_codec_start_hs_polling(codec); + pr_debug("%s: remove settle done\n", __func__); + + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + + +static unsigned long slimbus_value; + +static irqreturn_t sitar_slimbus_irq(int irq, void *data) +{ + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + int i, j; + u8 val; + + + for (i = 0; i < WCD9XXX_SLIM_NUM_PORT_REG; i++) { + slimbus_value = wcd9xxx_interface_reg_read(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_STATUS0 + i); + for_each_set_bit(j, &slimbus_value, BITS_PER_BYTE) { + val = wcd9xxx_interface_reg_read(codec->control_data, + SITAR_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); + } + wcd9xxx_interface_reg_write(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF); + } + + return IRQ_HANDLED; +} + + +static int sitar_handle_pdata(struct sitar_priv *sitar) +{ + struct snd_soc_codec *codec = sitar->codec; + struct wcd9xxx_pdata *pdata = sitar->pdata; + int k1, k2, rc = 0; + u8 leg_mode = pdata->amic_settings.legacy_mode; + u8 txfe_bypass = pdata->amic_settings.txfe_enable; + u8 txfe_buff = pdata->amic_settings.txfe_buff; + u8 flag = pdata->amic_settings.use_pdata; + u8 i = 0, j = 0; + u8 val_txfe = 0, value = 0; + + if (!pdata) { + rc = -ENODEV; + goto done; + } + + /* Make sure settings are correct */ + if ((pdata->micbias.ldoh_v > SITAR_LDOH_2P85_V) || + (pdata->micbias.bias1_cfilt_sel > SITAR_CFILT2_SEL) || + (pdata->micbias.bias2_cfilt_sel > SITAR_CFILT2_SEL)) { + rc = -EINVAL; + goto done; + } + + /* figure out k value */ + k1 = sitar_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt1_mv); + k2 = sitar_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt2_mv); + + if (IS_ERR_VALUE(k1) || IS_ERR_VALUE(k2)) { + rc = -EINVAL; + goto done; + } + + /* Set voltage level and always use LDO */ + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x0C, + (pdata->micbias.ldoh_v << 2)); + + snd_soc_update_bits(codec, SITAR_A_MICB_CFILT_1_VAL, 0xFC, + (k1 << 2)); + snd_soc_update_bits(codec, SITAR_A_MICB_CFILT_2_VAL, 0xFC, + (k2 << 2)); + + snd_soc_update_bits(codec, SITAR_A_MICB_1_CTL, 0x60, + (pdata->micbias.bias1_cfilt_sel << 5)); + snd_soc_update_bits(codec, SITAR_A_MICB_2_CTL, 0x60, + (pdata->micbias.bias2_cfilt_sel << 5)); + + for (i = 0; i < 6; j++, i += 2) { + if (flag & (0x01 << i)) { + value = (leg_mode & (0x01 << i)) ? 0x10 : 0x00; + val_txfe = (txfe_bypass & (0x01 << i)) ? 0x20 : 0x00; + val_txfe = val_txfe | + ((txfe_buff & (0x01 << i)) ? 0x10 : 0x00); + snd_soc_update_bits(codec, SITAR_A_TX_1_2_EN + j * 10, + 0x10, value); + snd_soc_update_bits(codec, + SITAR_A_TX_1_2_TEST_EN + j * 10, + 0x30, val_txfe); + } + if (flag & (0x01 << (i + 1))) { + value = (leg_mode & (0x01 << (i + 1))) ? 0x01 : 0x00; + val_txfe = (txfe_bypass & + (0x01 << (i + 1))) ? 0x02 : 0x00; + val_txfe |= (txfe_buff & + (0x01 << (i + 1))) ? 0x01 : 0x00; + snd_soc_update_bits(codec, SITAR_A_TX_1_2_EN + j * 10, + 0x01, value); + snd_soc_update_bits(codec, + SITAR_A_TX_1_2_TEST_EN + j * 10, + 0x03, val_txfe); + } + } + if (flag & 0x40) { + value = (leg_mode & 0x40) ? 0x10 : 0x00; + value = value | ((txfe_bypass & 0x40) ? 0x02 : 0x00); + value = value | ((txfe_buff & 0x40) ? 0x01 : 0x00); + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, + 0x13, value); + } + + + if (pdata->ocp.use_pdata) { + /* not defined in CODEC specification */ + if (pdata->ocp.hph_ocp_limit == 1 || + pdata->ocp.hph_ocp_limit == 5) { + rc = -EINVAL; + goto done; + } + snd_soc_update_bits(codec, SITAR_A_RX_COM_OCP_CTL, + 0x0F, pdata->ocp.num_attempts); + snd_soc_write(codec, SITAR_A_RX_COM_OCP_COUNT, + ((pdata->ocp.run_time << 4) | pdata->ocp.wait_time)); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, + 0xE0, (pdata->ocp.hph_ocp_limit << 5)); + } +done: + return rc; +} + +static const struct sitar_reg_mask_val sitar_1_1_reg_defaults[] = { + + SITAR_REG_VAL(SITAR_A_MICB_1_INT_RBIAS, 0x24), + SITAR_REG_VAL(SITAR_A_MICB_2_INT_RBIAS, 0x24), + + SITAR_REG_VAL(SITAR_A_RX_HPH_BIAS_PA, 0x57), + SITAR_REG_VAL(SITAR_A_RX_HPH_BIAS_LDO, 0x56), + + SITAR_REG_VAL(SITAR_A_RX_EAR_BIAS_PA, 0xA6), + SITAR_REG_VAL(SITAR_A_RX_EAR_GAIN, 0x02), + SITAR_REG_VAL(SITAR_A_RX_EAR_VCM, 0x03), + + SITAR_REG_VAL(SITAR_A_CDC_RX1_B5_CTL, 0x78), + SITAR_REG_VAL(SITAR_A_CDC_RX2_B5_CTL, 0x78), + SITAR_REG_VAL(SITAR_A_CDC_RX3_B5_CTL, 0x78), + + SITAR_REG_VAL(SITAR_A_CDC_RX1_B6_CTL, 0x80), + + SITAR_REG_VAL(SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL, 0x1B), + +}; + +static void sitar_update_reg_defaults(struct snd_soc_codec *codec) +{ + u32 i; + for (i = 0; i < ARRAY_SIZE(sitar_1_1_reg_defaults); i++) + snd_soc_write(codec, sitar_1_1_reg_defaults[i].reg, + sitar_1_1_reg_defaults[i].val); + +} +static const struct sitar_reg_mask_val sitar_codec_reg_init_val[] = { + /* Initialize current threshold to 350MA + * number of wait and run cycles to 4096 + */ + {SITAR_A_RX_HPH_OCP_CTL, 0xF8, 0x60}, + {SITAR_A_RX_COM_OCP_COUNT, 0xFF, 0xFF}, + + {SITAR_A_QFUSE_CTL, 0xFF, 0x03}, + + /* Initialize gain registers to use register gain */ + {SITAR_A_RX_HPH_L_GAIN, 0x10, 0x10}, + {SITAR_A_RX_HPH_R_GAIN, 0x10, 0x10}, + {SITAR_A_RX_LINE_1_GAIN, 0x10, 0x10}, + {SITAR_A_RX_LINE_2_GAIN, 0x10, 0x10}, + + /* Initialize mic biases to differential mode */ + {SITAR_A_MICB_1_INT_RBIAS, 0x24, 0x24}, + {SITAR_A_MICB_2_INT_RBIAS, 0x24, 0x24}, + + {SITAR_A_CDC_CONN_CLSG_CTL, 0x3C, 0x14}, + + /* Use 16 bit sample size for TX1 to TX6 */ + {SITAR_A_CDC_CONN_TX_SB_B1_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B2_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B3_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B4_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B5_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 0x1, 0x1}, + + /* Use 16 bit sample size for RX */ + {SITAR_A_CDC_CONN_RX_SB_B1_CTL, 0xFF, 0xAA}, + {SITAR_A_CDC_CONN_RX_SB_B2_CTL, 0x02, 0x02}, + + /*enable HPF filter for TX paths */ + {SITAR_A_CDC_TX1_MUX_CTL, 0x8, 0x0}, + {SITAR_A_CDC_TX2_MUX_CTL, 0x8, 0x0}, + + /*enable External clock select*/ + {SITAR_A_CDC_CLK_MCLK_CTL, 0x01, 0x01}, +}; + +static void sitar_codec_init_reg(struct snd_soc_codec *codec) +{ + u32 i; + for (i = 0; i < ARRAY_SIZE(sitar_codec_reg_init_val); i++) + snd_soc_update_bits(codec, sitar_codec_reg_init_val[i].reg, + sitar_codec_reg_init_val[i].mask, + sitar_codec_reg_init_val[i].val); +} + +static int sitar_codec_probe(struct snd_soc_codec *codec) +{ + struct sitar *control; + struct sitar_priv *sitar; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret = 0; + int i; + u8 sitar_version; + int ch_cnt; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + control = codec->control_data; + + sitar = kzalloc(sizeof(struct sitar_priv), GFP_KERNEL); + if (!sitar) { + dev_err(codec->dev, "Failed to allocate private data\n"); + return -ENOMEM; + } + + /* Make sure mbhc micbias register addresses are zeroed out */ + memset(&sitar->mbhc_bias_regs, 0, + sizeof(struct mbhc_micbias_regs)); + sitar->cfilt_k_value = 0; + sitar->mbhc_micbias_switched = false; + + /* Make sure mbhc intenal calibration data is zeroed out */ + memset(&sitar->mbhc_data, 0, + sizeof(struct mbhc_internal_cal_data)); + sitar->mbhc_data.t_sta_dce = DEFAULT_DCE_STA_WAIT; + sitar->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + sitar->mbhc_data.t_sta = DEFAULT_STA_WAIT; + snd_soc_codec_set_drvdata(codec, sitar); + + sitar->mclk_enabled = false; + sitar->bandgap_type = SITAR_BANDGAP_OFF; + sitar->clock_active = false; + sitar->config_mode_active = false; + sitar->mbhc_polling_active = false; + sitar->no_mic_headset_override = false; + mutex_init(&sitar->codec_resource_lock); + sitar->codec = codec; + sitar->mbhc_state = MBHC_STATE_NONE; + sitar->mbhc_last_resume = 0; + sitar->pdata = dev_get_platdata(codec->dev->parent); + sitar_update_reg_defaults(codec); + sitar_codec_init_reg(codec); + + ret = sitar_handle_pdata(sitar); + if (IS_ERR_VALUE(ret)) { + pr_err("%s: bad pdata\n", __func__); + goto err_pdata; + } + + snd_soc_add_codec_controls(codec, sitar_snd_controls, + ARRAY_SIZE(sitar_snd_controls)); + snd_soc_dapm_new_controls(dapm, sitar_dapm_widgets, + ARRAY_SIZE(sitar_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + sitar_version = snd_soc_read(codec, WCD9XXX_A_CHIP_VERSION); + pr_info("%s : Sitar version reg 0x%2x\n", __func__, (u32)sitar_version); + + sitar_version &= 0x1F; + pr_info("%s : Sitar version %u\n", __func__, (u32)sitar_version); + + snd_soc_dapm_sync(dapm); + + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION, + sitar_hs_insert_irq, "Headset insert detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_INSERTION); + goto err_insert_irq; + } + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION); + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL, + sitar_hs_remove_irq, "Headset remove detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_REMOVAL); + goto err_remove_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL, + sitar_dce_handler, "DC Estimation detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_POTENTIAL); + goto err_potential_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE, + sitar_release_handler, "Button Release detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_RELEASE); + goto err_release_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_SLIMBUS, + sitar_slimbus_irq, "SLIMBUS Slave", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_SLIMBUS); + goto err_slimbus_irq; + } + + for (i = 0; i < WCD9XXX_SLIM_NUM_PORT_REG; i++) + wcd9xxx_interface_reg_write(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_EN0 + i, 0xFF); + + + ret = wcd9xxx_request_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT, sitar_hphl_ocp_irq, + "HPH_L OCP detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + goto err_hphl_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_HPH_PA_OCPL_FAULT); + + ret = wcd9xxx_request_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPR_FAULT, sitar_hphr_ocp_irq, + "HPH_R OCP detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + goto err_hphr_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_HPH_PA_OCPR_FAULT); + + for (i = 0; i < ARRAY_SIZE(sitar_dai); i++) { + switch (sitar_dai[i].id) { + case AIF1_PB: + ch_cnt = sitar_dai[i].playback.channels_max; + break; + case AIF1_CAP: + ch_cnt = sitar_dai[i].capture.channels_max; + break; + default: + continue; + } + sitar->dai[i].ch_num = kzalloc((sizeof(unsigned int)* + ch_cnt), GFP_KERNEL); + } + + codec->ignore_pmdown_time = 1; + +#ifdef CONFIG_DEBUG_FS + debug_sitar_priv = sitar; +#endif + + return ret; + +err_hphr_ocp_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT, sitar); +err_hphl_ocp_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_SLIMBUS, sitar); +err_slimbus_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_RELEASE, sitar); +err_release_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_POTENTIAL, sitar); +err_potential_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_REMOVAL, sitar); +err_remove_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_INSERTION, sitar); +err_insert_irq: +err_pdata: + mutex_destroy(&sitar->codec_resource_lock); + kfree(sitar); + return ret; +} +static int sitar_codec_remove(struct snd_soc_codec *codec) +{ + int i; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_SLIMBUS, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION, sitar); + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_disable_clock_block(codec); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF); + if (sitar->mbhc_fw) + release_firmware(sitar->mbhc_fw); + for (i = 0; i < ARRAY_SIZE(sitar_dai); i++) + kfree(sitar->dai[i].ch_num); + mutex_destroy(&sitar->codec_resource_lock); + kfree(sitar); + return 0; +} +static struct snd_soc_codec_driver soc_codec_dev_sitar = { + .probe = sitar_codec_probe, + .remove = sitar_codec_remove, + .read = sitar_read, + .write = sitar_write, + + .readable_register = sitar_readable, + .volatile_register = sitar_volatile, + + .reg_cache_size = SITAR_CACHE_SIZE, + .reg_cache_default = sitar_reg_defaults, + .reg_word_size = 1, +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_poke; + +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char lbuf[32]; + char *buf; + int rc; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + buf = (char *)lbuf; + debug_sitar_priv->no_mic_headset_override = (*strsep(&buf, " ") == '0') + ? false : true; + + return rc; +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, +}; +#endif + +#ifdef CONFIG_PM +static int sitar_suspend(struct device *dev) +{ + dev_dbg(dev, "%s: system suspend\n", __func__); + return 0; +} + +static int sitar_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sitar_priv *sitar = platform_get_drvdata(pdev); + dev_dbg(dev, "%s: system resume\n", __func__); + sitar->mbhc_last_resume = jiffies; + return 0; +} + +static const struct dev_pm_ops sitar_pm_ops = { + .suspend = sitar_suspend, + .resume = sitar_resume, +}; +#endif + +static int __devinit sitar_probe(struct platform_device *pdev) +{ + int ret = 0; + pr_err("%s\n", __func__); +#ifdef CONFIG_DEBUG_FS + debugfs_poke = debugfs_create_file("TRRS", + S_IFREG | S_IRUGO, NULL, (void *) "TRRS", &codec_debug_ops); + +#endif + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_sitar, + sitar_dai, ARRAY_SIZE(sitar_dai)); + return ret; +} +static int __devexit sitar_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_poke); +#endif + return 0; +} +static struct platform_driver sitar_codec_driver = { + .probe = sitar_probe, + .remove = sitar_remove, + .driver = { + .name = "sitar_codec", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &sitar_pm_ops, +#endif + }, +}; + +static int __init sitar_codec_init(void) +{ + return platform_driver_register(&sitar_codec_driver); +} + +static void __exit sitar_codec_exit(void) +{ + platform_driver_unregister(&sitar_codec_driver); +} + +module_init(sitar_codec_init); +module_exit(sitar_codec_exit); + +MODULE_DESCRIPTION("Sitar codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wcd9304.h b/sound/soc/codecs/wcd9304.h new file mode 100644 index 0000000000000000000000000000000000000000..70b3f0b24cbd9fd7c51b1ddedbb796094e1fe018 --- /dev/null +++ b/sound/soc/codecs/wcd9304.h @@ -0,0 +1,251 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 SITAR_NUM_REGISTERS 0x400 +#define SITAR_MAX_REGISTER (SITAR_NUM_REGISTERS-1) +#define SITAR_CACHE_SIZE SITAR_NUM_REGISTERS +#define SITAR_1_X_ONLY_REGISTERS 3 +#define SITAR_2_HIGHER_ONLY_REGISTERS 3 + +#define SITAR_REG_VAL(reg, val) {reg, 0, val} + +#define DEFAULT_DCE_STA_WAIT 55 +#define DEFAULT_DCE_WAIT 60000 +#define DEFAULT_STA_WAIT 5000 + +#define STA 0 +#define DCE 1 + +#define SITAR_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3 | \ + SND_JACK_BTN_4 | SND_JACK_BTN_5 | \ + SND_JACK_BTN_6 | SND_JACK_BTN_7) + +extern const u8 sitar_reg_readable[SITAR_CACHE_SIZE]; +extern const u32 sitar_1_reg_readable[SITAR_1_X_ONLY_REGISTERS]; +extern const u32 sitar_2_reg_readable[SITAR_2_HIGHER_ONLY_REGISTERS]; +extern const u8 sitar_reg_defaults[SITAR_CACHE_SIZE]; + +enum sitar_micbias_num { + SITAR_MICBIAS1, + SITAR_MICBIAS2, + SITAR_MICBIAS3, + SITAR_MICBIAS4, +}; + +enum sitar_pid_current { + SITAR_PID_MIC_2P5_UA, + SITAR_PID_MIC_5_UA, + SITAR_PID_MIC_10_UA, + SITAR_PID_MIC_20_UA, +}; + +struct sitar_reg_mask_val { + u16 reg; + u8 mask; + u8 val; +}; + +enum sitar_mbhc_clk_freq { + SITAR_MCLK_12P2MHZ = 0, + SITAR_MCLK_9P6MHZ, + SITAR_NUM_CLK_FREQS, +}; + +enum sitar_mbhc_analog_pwr_cfg { + SITAR_ANALOG_PWR_COLLAPSED = 0, + SITAR_ANALOG_PWR_ON, + SITAR_NUM_ANALOG_PWR_CONFIGS, +}; + +enum sitar_mbhc_btn_det_mem { + SITAR_BTN_DET_V_BTN_LOW, + SITAR_BTN_DET_V_BTN_HIGH, + SITAR_BTN_DET_N_READY, + SITAR_BTN_DET_N_CIC, + SITAR_BTN_DET_GAIN +}; + +struct sitar_mbhc_general_cfg { + u8 t_ldoh; + u8 t_bg_fast_settle; + u8 t_shutdown_plug_rem; + u8 mbhc_nsa; + u8 mbhc_navg; + u8 v_micbias_l; + u8 v_micbias; + u8 mbhc_reserved; + u16 settle_wait; + u16 t_micbias_rampup; + u16 t_micbias_rampdown; + u16 t_supply_bringup; +} __packed; + +struct sitar_mbhc_plug_detect_cfg { + u32 mic_current; + u32 hph_current; + u16 t_mic_pid; + u16 t_ins_complete; + u16 t_ins_retry; + u16 v_removal_delta; + u8 micbias_slow_ramp; + u8 reserved0; + u8 reserved1; + u8 reserved2; +} __packed; + +struct sitar_mbhc_plug_type_cfg { + u8 av_detect; + u8 mono_detect; + u8 num_ins_tries; + u8 reserved0; + s16 v_no_mic; + s16 v_av_min; + s16 v_av_max; + s16 v_hs_min; + s16 v_hs_max; + u16 reserved1; +} __packed; + + +struct sitar_mbhc_btn_detect_cfg { + s8 c[8]; + u8 nc; + u8 n_meas; + u8 mbhc_nsc; + u8 n_btn_meas; + u8 n_btn_con; + u8 num_btn; + u8 reserved0; + u8 reserved1; + u16 t_poll; + u16 t_bounce_wait; + u16 t_rel_timeout; + s16 v_btn_press_delta_sta; + s16 v_btn_press_delta_cic; + u16 t_btn0_timeout; + s16 _v_btn_low[0]; /* v_btn_low[num_btn] */ + s16 _v_btn_high[0]; /* v_btn_high[num_btn] */ + u8 _n_ready[SITAR_NUM_CLK_FREQS]; + u8 _n_cic[SITAR_NUM_CLK_FREQS]; + u8 _gain[SITAR_NUM_CLK_FREQS]; +} __packed; + +struct sitar_mbhc_imped_detect_cfg { + u8 _hs_imped_detect; + u8 _n_rload; + u8 _hph_keep_on; + u8 _repeat_rload_calc; + u16 _t_dac_ramp_time; + u16 _rhph_high; + u16 _rhph_low; + u16 _rload[0]; /* rload[n_rload] */ + u16 _alpha[0]; /* alpha[n_rload] */ + u16 _beta[3]; +} __packed; + +struct sitar_mbhc_config { + struct snd_soc_jack *headset_jack; + struct snd_soc_jack *button_jack; + bool read_fw_bin; + /* void* calibration contains: + * struct tabla_mbhc_general_cfg generic; + * struct tabla_mbhc_plug_detect_cfg plug_det; + * struct tabla_mbhc_plug_type_cfg plug_type; + * struct tabla_mbhc_btn_detect_cfg btn_det; + * struct tabla_mbhc_imped_detect_cfg imped_det; + * Note: various size depends on btn_det->num_btn + */ + void *calibration; + enum sitar_micbias_num micbias; + int (*mclk_cb_fn) (struct snd_soc_codec*, int, bool); + unsigned int mclk_rate; + unsigned int gpio; + unsigned int gpio_irq; + int gpio_level_insert; +}; + +extern int sitar_hs_detect(struct snd_soc_codec *codec, + const struct sitar_mbhc_config *cfg); + +#ifndef anc_header_dec +struct anc_header { + u32 reserved[3]; + u32 num_anc_slots; +}; +#define anc_header_dec +#endif + +extern int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, + bool dapm); + +extern void *sitar_mbhc_cal_btn_det_mp(const struct sitar_mbhc_btn_detect_cfg + *btn_det, + const enum sitar_mbhc_btn_det_mem mem); + +#define SITAR_MBHC_CAL_SIZE(buttons, rload) ( \ + sizeof(enum sitar_micbias_num) + \ + sizeof(struct sitar_mbhc_general_cfg) + \ + sizeof(struct sitar_mbhc_plug_detect_cfg) + \ + ((sizeof(s16) + sizeof(s16)) * buttons) + \ + sizeof(struct sitar_mbhc_plug_type_cfg) + \ + sizeof(struct sitar_mbhc_btn_detect_cfg) + \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + ((sizeof(u16) + sizeof(u16)) * rload) \ + ) + +#define SITAR_MBHC_CAL_GENERAL_PTR(cali) ( \ + (struct sitar_mbhc_general_cfg *) cali) +#define SITAR_MBHC_CAL_PLUG_DET_PTR(cali) ( \ + (struct sitar_mbhc_plug_detect_cfg *) \ + &(SITAR_MBHC_CAL_GENERAL_PTR(cali)[1])) +#define SITAR_MBHC_CAL_PLUG_TYPE_PTR(cali) ( \ + (struct sitar_mbhc_plug_type_cfg *) \ + &(SITAR_MBHC_CAL_PLUG_DET_PTR(cali)[1])) +#define SITAR_MBHC_CAL_BTN_DET_PTR(cali) ( \ + (struct sitar_mbhc_btn_detect_cfg *) \ + &(SITAR_MBHC_CAL_PLUG_TYPE_PTR(cali)[1])) +#define SITAR_MBHC_CAL_IMPED_DET_PTR(cali) ( \ + (struct sitar_mbhc_imped_detect_cfg *) \ + (((void *)&SITAR_MBHC_CAL_BTN_DET_PTR(cali)[1]) + \ + (SITAR_MBHC_CAL_BTN_DET_PTR(cali)->num_btn * \ + (sizeof(SITAR_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_low[0]) + \ + sizeof(SITAR_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_high[0])))) \ + ) + +/* minimum size of calibration data assuming there is only one button and + * one rload. + */ +#define SITAR_MBHC_CAL_MIN_SIZE ( \ + sizeof(struct sitar_mbhc_general_cfg) + \ + sizeof(struct sitar_mbhc_plug_detect_cfg) + \ + sizeof(struct sitar_mbhc_plug_type_cfg) + \ + sizeof(struct sitar_mbhc_btn_detect_cfg) + \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + (sizeof(u16) * 2)) + +#define SITAR_MBHC_CAL_BTN_SZ(cfg_ptr) ( \ + sizeof(struct sitar_mbhc_btn_detect_cfg) + \ + (cfg_ptr->num_btn * (sizeof(cfg_ptr->_v_btn_low[0]) + \ + sizeof(cfg_ptr->_v_btn_high[0])))) + +#define SITAR_MBHC_CAL_IMPED_MIN_SZ ( \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + sizeof(u16) * 2) + +#define SITAR_MBHC_CAL_IMPED_SZ(cfg_ptr) ( \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + (cfg_ptr->_n_rload * (sizeof(cfg_ptr->_rload[0]) + \ + sizeof(cfg_ptr->_alpha[0])))) diff --git a/sound/soc/codecs/wcd9310-tables.c b/sound/soc/codecs/wcd9310-tables.c new file mode 100644 index 0000000000000000000000000000000000000000..2cba59df24582e9d35bcf2198ee2e215ebe48fc8 --- /dev/null +++ b/sound/soc/codecs/wcd9310-tables.c @@ -0,0 +1,1114 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_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_ANC1_CTL] = 1, + [TABLA_A_CDC_ANC1_SHIFT] = 1, + [TABLA_A_CDC_ANC1_FILT1_B1_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT1_B2_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT1_B3_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT1_B4_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT2_B1_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT2_B2_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT2_B3_CTL] = 1, + [TABLA_A_CDC_ANC1_SPARE] = 1, + [TABLA_A_CDC_ANC1_FILT3_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT4_CTL] = 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_ANC2_CTL] = 1, + [TABLA_A_CDC_ANC2_SHIFT] = 1, + [TABLA_A_CDC_ANC2_FILT1_B1_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT1_B2_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT1_B3_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT1_B4_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT2_B1_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT2_B2_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT2_B3_CTL] = 1, + [TABLA_A_CDC_ANC2_SPARE] = 1, + [TABLA_A_CDC_ANC2_FILT3_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT4_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_ANC_RESET_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_ANC_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_COMP1_B1_CTL] = 1, + [TABLA_A_CDC_COMP1_B2_CTL] = 1, + [TABLA_A_CDC_COMP1_B3_CTL] = 1, + [TABLA_A_CDC_COMP1_B4_CTL] = 1, + [TABLA_A_CDC_COMP1_B5_CTL] = 1, + [TABLA_A_CDC_COMP1_B6_CTL] = 1, + [TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS] = 1, + [TABLA_A_CDC_COMP1_FS_CFG] = 1, + [TABLA_A_CDC_COMP2_B1_CTL] = 1, + [TABLA_A_CDC_COMP2_B2_CTL] = 1, + [TABLA_A_CDC_COMP2_B3_CTL] = 1, + [TABLA_A_CDC_COMP2_B4_CTL] = 1, + [TABLA_A_CDC_COMP2_B5_CTL] = 1, + [TABLA_A_CDC_COMP2_B6_CTL] = 1, + [TABLA_A_CDC_COMP2_SHUT_DOWN_STATUS] = 1, + [TABLA_A_CDC_COMP2_FS_CFG] = 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_ANC_B1_CTL] = 1, + [TABLA_A_CDC_CONN_ANC_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 unsigned int tabla_1_reg_readable[TABLA_1_X_ONLY_REGISTERS] = { + TABLA_1_A_MICB_4_CTL, + TABLA_1_A_MICB_4_INT_RBIAS, + TABLA_1_A_MICB_4_MBHC, +}; + +const unsigned int tabla_2_reg_readable[TABLA_2_HIGHER_ONLY_REGISTERS] = { + TABLA_2_A_MICB_4_CTL, + TABLA_2_A_MICB_4_INT_RBIAS, + TABLA_2_A_MICB_4_MBHC, +}; + +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_1_A_MICB_4_CTL] = TABLA_A_MICB_4_CTL__POR, + [TABLA_1_A_MICB_4_INT_RBIAS] = TABLA_A_MICB_4_INT_RBIAS__POR, + [TABLA_1_A_MICB_4_MBHC] = TABLA_A_MICB_4_MBHC__POR, + [TABLA_2_A_MICB_4_CTL] = TABLA_A_MICB_4_CTL__POR, + [TABLA_2_A_MICB_4_INT_RBIAS] = TABLA_A_MICB_4_INT_RBIAS__POR, + [TABLA_2_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_ANC1_CTL] = TABLA_A_CDC_ANC1_CTL__POR, + [TABLA_A_CDC_ANC1_SHIFT] = TABLA_A_CDC_ANC1_SHIFT__POR, + [TABLA_A_CDC_ANC1_FILT1_B1_CTL] = TABLA_A_CDC_ANC1_FILT1_B1_CTL__POR, + [TABLA_A_CDC_ANC1_FILT1_B2_CTL] = TABLA_A_CDC_ANC1_FILT1_B2_CTL__POR, + [TABLA_A_CDC_ANC1_FILT1_B3_CTL] = TABLA_A_CDC_ANC1_FILT1_B3_CTL__POR, + [TABLA_A_CDC_ANC1_FILT1_B4_CTL] = TABLA_A_CDC_ANC1_FILT1_B4_CTL__POR, + [TABLA_A_CDC_ANC1_FILT2_B1_CTL] = TABLA_A_CDC_ANC1_FILT2_B1_CTL__POR, + [TABLA_A_CDC_ANC1_FILT2_B2_CTL] = TABLA_A_CDC_ANC1_FILT2_B2_CTL__POR, + [TABLA_A_CDC_ANC1_FILT2_B3_CTL] = TABLA_A_CDC_ANC1_FILT2_B3_CTL__POR, + [TABLA_A_CDC_ANC1_SPARE] = TABLA_A_CDC_ANC1_SPARE__POR, + [TABLA_A_CDC_ANC1_FILT3_CTL] = TABLA_A_CDC_ANC1_FILT3_CTL__POR, + [TABLA_A_CDC_ANC1_FILT4_CTL] = TABLA_A_CDC_ANC1_FILT4_CTL__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_ANC_RESET_CTL] = TABLA_A_CDC_CLK_ANC_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_ANC_CLK_EN_CTL] = TABLA_A_CDC_CLK_ANC_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_COMP1_B1_CTL] = TABLA_A_CDC_COMP1_B1_CTL__POR, + [TABLA_A_CDC_COMP1_B2_CTL] = TABLA_A_CDC_COMP1_B2_CTL__POR, + [TABLA_A_CDC_COMP1_B3_CTL] = TABLA_A_CDC_COMP1_B3_CTL__POR, + [TABLA_A_CDC_COMP1_B4_CTL] = TABLA_A_CDC_COMP1_B4_CTL__POR, + [TABLA_A_CDC_COMP1_B5_CTL] = TABLA_A_CDC_COMP1_B5_CTL__POR, + [TABLA_A_CDC_COMP1_B6_CTL] = TABLA_A_CDC_COMP1_B6_CTL__POR, + [TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS] = + TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS__POR, + [TABLA_A_CDC_COMP1_FS_CFG] = TABLA_A_CDC_COMP1_FS_CFG__POR, + [TABLA_A_CDC_COMP2_B1_CTL] = TABLA_A_CDC_COMP2_B1_CTL__POR, + [TABLA_A_CDC_COMP2_B2_CTL] = TABLA_A_CDC_COMP2_B2_CTL__POR, + [TABLA_A_CDC_COMP2_B3_CTL] = TABLA_A_CDC_COMP2_B3_CTL__POR, + [TABLA_A_CDC_COMP2_B4_CTL] = TABLA_A_CDC_COMP2_B4_CTL__POR, + [TABLA_A_CDC_COMP2_B5_CTL] = TABLA_A_CDC_COMP2_B5_CTL__POR, + [TABLA_A_CDC_COMP2_B6_CTL] = TABLA_A_CDC_COMP2_B6_CTL__POR, + [TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS] = + TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS__POR, + [TABLA_A_CDC_COMP2_FS_CFG] = TABLA_A_CDC_COMP2_FS_CFG__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_ANC_B1_CTL] = TABLA_A_CDC_CONN_ANC_B1_CTL__POR, + [TABLA_A_CDC_CONN_ANC_B2_CTL] = TABLA_A_CDC_CONN_ANC_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 0000000000000000000000000000000000000000..4b4dae13253d6a7b07bf3e6e52c565ab4d0e5ff9 --- /dev/null +++ b/sound/soc/codecs/wcd9310.c @@ -0,0 +1,7792 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "wcd9310.h" + +#define WCD9310_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) + + +#define NUM_DECIMATORS 10 +#define NUM_INTERPOLATORS 7 +#define BITS_PER_REG 8 +#define TABLA_CFILT_FAST_MODE 0x00 +#define TABLA_CFILT_SLOW_MODE 0x40 +#define MBHC_FW_READ_ATTEMPTS 15 +#define MBHC_FW_READ_TIMEOUT 2000000 + +enum { + MBHC_USE_HPHL_TRIGGER = 1, + MBHC_USE_MB_TRIGGER = 2 +}; + +#define MBHC_NUM_DCE_PLUG_DETECT 3 +#define NUM_ATTEMPTS_INSERT_DETECT 25 +#define NUM_ATTEMPTS_TO_REPORT 5 + +#define TABLA_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \ + SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED) + +#define TABLA_I2S_MASTER_MODE_MASK 0x08 + +#define TABLA_OCP_ATTEMPT 1 + +#define AIF1_PB 1 +#define AIF1_CAP 2 +#define AIF2_PB 3 +#define AIF2_CAP 4 +#define AIF3_CAP 5 +#define AIF3_PB 6 + +#define NUM_CODEC_DAIS 6 +#define TABLA_COMP_DIGITAL_GAIN_OFFSET 3 + +struct tabla_codec_dai_data { + u32 rate; + u32 *ch_num; + u32 ch_act; + u32 ch_tot; +}; + +#define TABLA_MCLK_RATE_12288KHZ 12288000 +#define TABLA_MCLK_RATE_9600KHZ 9600000 + +#define TABLA_FAKE_INS_THRESHOLD_MS 2500 +#define TABLA_FAKE_REMOVAL_MIN_PERIOD_MS 50 + +#define TABLA_MBHC_BUTTON_MIN 0x8000 + +#define TABLA_MBHC_FAKE_INSERT_LOW 10 +#define TABLA_MBHC_FAKE_INSERT_HIGH 80 +#define TABLA_MBHC_FAKE_INS_HIGH_NO_GPIO 150 + +#define TABLA_MBHC_STATUS_REL_DETECTION 0x0C + +#define TABLA_MBHC_GPIO_REL_DEBOUNCE_TIME_MS 200 + +#define TABLA_MBHC_FAKE_INS_DELTA_MV 200 +#define TABLA_MBHC_FAKE_INS_DELTA_SCALED_MV 300 + +#define TABLA_HS_DETECT_PLUG_TIME_MS (5 * 1000) +#define TABLA_HS_DETECT_PLUG_INERVAL_MS 100 + +#define TABLA_GPIO_IRQ_DEBOUNCE_TIME_US 5000 + +#define TABLA_MBHC_GND_MIC_SWAP_THRESHOLD 2 + +#define TABLA_ACQUIRE_LOCK(x) do { mutex_lock(&x); } while (0) +#define TABLA_RELEASE_LOCK(x) do { mutex_unlock(&x); } while (0) + +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); +static struct snd_soc_dai_driver tabla_dai[]; +static const DECLARE_TLV_DB_SCALE(aux_pga_gain, 0, 2, 0); + +enum tabla_bandgap_type { + TABLA_BANDGAP_OFF = 0, + TABLA_BANDGAP_AUDIO_MODE, + TABLA_BANDGAP_MBHC_MODE, +}; + +struct mbhc_micbias_regs { + u16 cfilt_val; + u16 cfilt_ctl; + u16 mbhc_reg; + u16 int_rbias; + u16 ctl_reg; + u8 cfilt_sel; +}; + +/* Codec supports 2 IIR filters */ +enum { + IIR1 = 0, + IIR2, + IIR_MAX, +}; +/* Codec supports 5 bands */ +enum { + BAND1 = 0, + BAND2, + BAND3, + BAND4, + BAND5, + BAND_MAX, +}; + +enum { + COMPANDER_1 = 0, + COMPANDER_2, + COMPANDER_MAX, +}; + +enum { + COMPANDER_FS_8KHZ = 0, + COMPANDER_FS_16KHZ, + COMPANDER_FS_32KHZ, + COMPANDER_FS_48KHZ, + COMPANDER_FS_96KHZ, + COMPANDER_FS_192KHZ, + COMPANDER_FS_MAX, +}; + +/* Flags to track of PA and DAC state. + * PA and DAC should be tracked separately as AUXPGA loopback requires + * only PA to be turned on without DAC being on. */ +enum tabla_priv_ack_flags { + TABLA_HPHL_PA_OFF_ACK = 0, + TABLA_HPHR_PA_OFF_ACK, + TABLA_HPHL_DAC_OFF_ACK, + TABLA_HPHR_DAC_OFF_ACK +}; + + +struct comp_sample_dependent_params { + u32 peak_det_timeout; + u32 rms_meter_div_fact; + u32 rms_meter_resamp_fact; +}; + +/* Data used by MBHC */ +struct mbhc_internal_cal_data { + u16 dce_z; + u16 dce_mb; + u16 sta_z; + u16 sta_mb; + u32 t_sta_dce; + u32 t_dce; + u32 t_sta; + u32 micb_mv; + u16 v_ins_hu; + u16 v_ins_h; + u16 v_b1_hu; + u16 v_b1_h; + u16 v_b1_huc; + u16 v_brh; + u16 v_brl; + u16 v_no_mic; + u8 npoll; + u8 nbounce_wait; + s16 adj_v_hs_max; + u16 adj_v_ins_hu; + u16 adj_v_ins_h; + s16 v_inval_ins_low; + s16 v_inval_ins_high; +}; + +struct tabla_reg_address { + u16 micb_4_ctl; + u16 micb_4_int_rbias; + u16 micb_4_mbhc; +}; + +enum tabla_mbhc_plug_type { + PLUG_TYPE_INVALID = -1, + PLUG_TYPE_NONE, + PLUG_TYPE_HEADSET, + PLUG_TYPE_HEADPHONE, + PLUG_TYPE_HIGH_HPH, + PLUG_TYPE_GND_MIC_SWAP, +}; + +enum tabla_mbhc_state { + MBHC_STATE_NONE = -1, + MBHC_STATE_POTENTIAL, + MBHC_STATE_POTENTIAL_RECOVERY, + MBHC_STATE_RELEASE, +}; + +struct hpf_work { + struct tabla_priv *tabla; + u32 decimator; + u8 tx_hpf_cut_of_freq; + struct delayed_work dwork; +}; + +static struct hpf_work tx_hpf_work[NUM_DECIMATORS]; + +struct tabla_priv { + struct snd_soc_codec *codec; + struct tabla_reg_address reg_addr; + u32 adc_count; + u32 cfilt1_cnt; + u32 cfilt2_cnt; + u32 cfilt3_cnt; + u32 rx_bias_count; + s32 dmic_1_2_clk_cnt; + s32 dmic_3_4_clk_cnt; + s32 dmic_5_6_clk_cnt; + + enum tabla_bandgap_type bandgap_type; + bool mclk_enabled; + bool clock_active; + bool config_mode_active; + bool mbhc_polling_active; + unsigned long mbhc_fake_ins_start; + int buttons_pressed; + enum tabla_mbhc_state mbhc_state; + struct tabla_mbhc_config mbhc_cfg; + struct mbhc_internal_cal_data mbhc_data; + + struct wcd9xxx_pdata *pdata; + u32 anc_slot; + + bool no_mic_headset_override; + /* Delayed work to report long button press */ + struct delayed_work mbhc_btn_dwork; + + struct mbhc_micbias_regs mbhc_bias_regs; + bool mbhc_micbias_switched; + + /* track PA/DAC state */ + unsigned long hph_pa_dac_state; + + /*track tabla interface type*/ + u8 intf_type; + + u32 hph_status; /* track headhpone status */ + /* define separate work for left and right headphone OCP to avoid + * additional checking on which OCP event to report so no locking + * to ensure synchronization is required + */ + struct work_struct hphlocp_work; /* reporting left hph ocp off */ + struct work_struct hphrocp_work; /* reporting right hph ocp off */ + + u8 hphlocp_cnt; /* headphone left ocp retry */ + u8 hphrocp_cnt; /* headphone right ocp retry */ + + /* Work to perform MBHC Firmware Read */ + struct delayed_work mbhc_firmware_dwork; + const struct firmware *mbhc_fw; + + /* num of slim ports required */ + struct tabla_codec_dai_data dai[NUM_CODEC_DAIS]; + + /*compander*/ + int comp_enabled[COMPANDER_MAX]; + u32 comp_fs[COMPANDER_MAX]; + + /* Maintain the status of AUX PGA */ + int aux_pga_cnt; + u8 aux_l_gain; + u8 aux_r_gain; + + struct delayed_work mbhc_insert_dwork; + unsigned long mbhc_last_resume; /* in jiffies */ + + u8 current_plug; + struct work_struct hs_correct_plug_work; + bool hs_detect_work_stop; + bool hs_polling_irq_prepared; + bool lpi_enabled; /* low power insertion detection */ + bool in_gpio_handler; + /* Currently, only used for mbhc purpose, to protect + * concurrent execution of mbhc threaded irq handlers and + * kill race between DAPM and MBHC.But can serve as a + * general lock to protect codec resource + */ + struct mutex codec_resource_lock; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_poke; + struct dentry *debugfs_mbhc; +#endif +}; + +static const u32 comp_shift[] = { + 0, + 2, +}; + +static const int comp_rx_path[] = { + COMPANDER_1, + COMPANDER_1, + COMPANDER_2, + COMPANDER_2, + COMPANDER_2, + COMPANDER_2, + COMPANDER_MAX, +}; + +static const struct comp_sample_dependent_params comp_samp_params[] = { + { + .peak_det_timeout = 0x2, + .rms_meter_div_fact = 0x8 << 4, + .rms_meter_resamp_fact = 0x21, + }, + { + .peak_det_timeout = 0x3, + .rms_meter_div_fact = 0x9 << 4, + .rms_meter_resamp_fact = 0x28, + }, + + { + .peak_det_timeout = 0x5, + .rms_meter_div_fact = 0xB << 4, + .rms_meter_resamp_fact = 0x28, + }, + + { + .peak_det_timeout = 0x5, + .rms_meter_div_fact = 0xB << 4, + .rms_meter_resamp_fact = 0x28, + }, +}; + +static unsigned short rx_digital_gain_reg[] = { + TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX3_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX4_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX5_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX6_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX7_VOL_CTL_B2_CTL, +}; + + +static unsigned short tx_digital_gain_reg[] = { + TABLA_A_CDC_TX1_VOL_CTL_GAIN, + TABLA_A_CDC_TX2_VOL_CTL_GAIN, + TABLA_A_CDC_TX3_VOL_CTL_GAIN, + TABLA_A_CDC_TX4_VOL_CTL_GAIN, + TABLA_A_CDC_TX5_VOL_CTL_GAIN, + TABLA_A_CDC_TX6_VOL_CTL_GAIN, + TABLA_A_CDC_TX7_VOL_CTL_GAIN, + TABLA_A_CDC_TX8_VOL_CTL_GAIN, + TABLA_A_CDC_TX9_VOL_CTL_GAIN, + TABLA_A_CDC_TX10_VOL_CTL_GAIN, +}; + +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; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + 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); + break; + } + return 0; +} + +static int tabla_get_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + ucontrol->value.integer.value[0] = tabla->anc_slot; + return 0; +} + +static int tabla_put_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + tabla->anc_slot = ucontrol->value.integer.value[0]; + return 0; +} + +static int tabla_pa_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + ear_pa_gain = snd_soc_read(codec, TABLA_A_RX_EAR_GAIN); + + ear_pa_gain = ear_pa_gain >> 5; + + if (ear_pa_gain == 0x00) { + ucontrol->value.integer.value[0] = 0; + } else if (ear_pa_gain == 0x04) { + ucontrol->value.integer.value[0] = 1; + } else { + pr_err("%s: ERROR: Unsupported Ear Gain = 0x%x\n", + __func__, ear_pa_gain); + return -EINVAL; + } + + pr_debug("%s: ear_pa_gain = 0x%x\n", __func__, ear_pa_gain); + + return 0; +} + +static int tabla_pa_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s: ucontrol->value.integer.value[0] = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + ear_pa_gain = 0x00; + break; + case 1: + ear_pa_gain = 0x80; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, TABLA_A_RX_EAR_GAIN, 0xE0, ear_pa_gain); + return 0; +} + +static int tabla_get_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + snd_soc_read(codec, (TABLA_A_CDC_IIR1_CTL + 16 * iir_idx)) & + (1 << band_idx); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0]); + return 0; +} + +static int tabla_put_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + int value = ucontrol->value.integer.value[0]; + + /* Mask first 5 bits, 6-8 are reserved */ + snd_soc_update_bits(codec, (TABLA_A_CDC_IIR1_CTL + 16 * iir_idx), + (1 << band_idx), (value << band_idx)); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, value); + return 0; +} +static uint32_t get_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx) +{ + /* Address does not automatically update if reading */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + (band_idx * BAND_MAX + coeff_idx) & 0x1F); + + /* Mask bits top 2 bits since they are reserved */ + return ((snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx)) << 24) | + (snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx)) << 16) | + (snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx)) << 8) | + (snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx)))) & + 0x3FFFFFFF; +} + +static int tabla_get_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + get_iir_band_coeff(codec, iir_idx, band_idx, 0); + ucontrol->value.integer.value[1] = + get_iir_band_coeff(codec, iir_idx, band_idx, 1); + ucontrol->value.integer.value[2] = + get_iir_band_coeff(codec, iir_idx, band_idx, 2); + ucontrol->value.integer.value[3] = + get_iir_band_coeff(codec, iir_idx, band_idx, 3); + ucontrol->value.integer.value[4] = + get_iir_band_coeff(codec, iir_idx, band_idx, 4); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[1], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[2], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[3], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[4]); + return 0; +} + +static void set_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx, uint32_t value) +{ + /* Mask top 3 bits, 6-8 are reserved */ + /* Update address manually each time */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + (band_idx * BAND_MAX + coeff_idx) & 0x1F); + + /* Mask top 2 bits, 7-8 are reserved */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx), + (value >> 24) & 0x3F); + + /* Isolate 8bits at a time */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx), + (value >> 16) & 0xFF); + + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx), + (value >> 8) & 0xFF); + + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx), + value & 0xFF); +} + +static int tabla_put_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + set_iir_band_coeff(codec, iir_idx, band_idx, 0, + ucontrol->value.integer.value[0]); + set_iir_band_coeff(codec, iir_idx, band_idx, 1, + ucontrol->value.integer.value[1]); + set_iir_band_coeff(codec, iir_idx, band_idx, 2, + ucontrol->value.integer.value[2]); + set_iir_band_coeff(codec, iir_idx, band_idx, 3, + ucontrol->value.integer.value[3]); + set_iir_band_coeff(codec, iir_idx, band_idx, 4, + ucontrol->value.integer.value[4]); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 0), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 1), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 2), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 3), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 4)); + return 0; +} + +static int tabla_compander_gain_offset( + struct snd_soc_codec *codec, u32 enable, + unsigned int reg, int mask, int event) +{ + int pa_mode = snd_soc_read(codec, reg) & mask; + int gain_offset = 0; + /* if PMU && enable is 1-> offset is 3 + * if PMU && enable is 0-> offset is 0 + * if PMD && pa_mode is PA -> offset is 0: PMU compander is off + * if PMD && pa_mode is comp -> offset is -3: PMU compander is on. + */ + + if (SND_SOC_DAPM_EVENT_ON(event) && (enable != 0)) + gain_offset = TABLA_COMP_DIGITAL_GAIN_OFFSET; + if (SND_SOC_DAPM_EVENT_OFF(event) && (pa_mode == 0)) + gain_offset = -TABLA_COMP_DIGITAL_GAIN_OFFSET; + return gain_offset; +} + + +static int tabla_config_gain_compander( + struct snd_soc_codec *codec, + u32 compander, u32 enable, int event) +{ + int value = 0; + int mask = 1 << 4; + int gain = 0; + int gain_offset; + if (compander >= COMPANDER_MAX) { + pr_err("%s: Error, invalid compander channel\n", __func__); + return -EINVAL; + } + + if ((enable == 0) || SND_SOC_DAPM_EVENT_OFF(event)) + value = 1 << 4; + + if (compander == COMPANDER_1) { + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_HPH_L_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX1_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_HPH_R_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX2_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + } else if (compander == COMPANDER_2) { + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_1_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_1_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX3_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX3_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_3_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_3_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX4_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX4_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_2_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_2_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX5_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX5_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_4_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_4_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX6_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX6_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + } + return 0; +} +static int tabla_get_compander(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int comp = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->max; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tabla->comp_enabled[comp]; + + return 0; +} + +static int tabla_set_compander(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + int comp = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->max; + int value = ucontrol->value.integer.value[0]; + + if (value == tabla->comp_enabled[comp]) { + pr_debug("%s: compander #%d enable %d no change\n", + __func__, comp, value); + return 0; + } + tabla->comp_enabled[comp] = value; + return 0; +} + + +static int tabla_config_compander(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); + u32 rate = tabla->comp_fs[w->shift]; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (tabla->comp_enabled[w->shift] != 0) { + /* Enable both L/R compander clocks */ + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_RX_B2_CTL, + 0x03 << comp_shift[w->shift], + 0x03 << comp_shift[w->shift]); + /* Clar the HALT for the compander*/ + snd_soc_update_bits(codec, + TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 1 << 2, 0); + /* Toggle compander reset bits*/ + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_OTHR_RESET_CTL, + 0x03 << comp_shift[w->shift], + 0x03 << comp_shift[w->shift]); + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_OTHR_RESET_CTL, + 0x03 << comp_shift[w->shift], 0); + tabla_config_gain_compander(codec, w->shift, 1, event); + /* Update the RMS meter resampling*/ + snd_soc_update_bits(codec, + TABLA_A_CDC_COMP1_B3_CTL + + w->shift * 8, 0xFF, 0x01); + /* Wait for 1ms*/ + usleep_range(1000, 1000); + } + break; + case SND_SOC_DAPM_POST_PMU: + /* Set sample rate dependent paramater*/ + if (tabla->comp_enabled[w->shift] != 0) { + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_FS_CFG + + w->shift * 8, 0x03, rate); + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B2_CTL + + w->shift * 8, 0x0F, + comp_samp_params[rate].peak_det_timeout); + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B2_CTL + + w->shift * 8, 0xF0, + comp_samp_params[rate].rms_meter_div_fact); + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B3_CTL + + w->shift * 8, 0xFF, + comp_samp_params[rate].rms_meter_resamp_fact); + /* Compander enable -> 0x370/0x378*/ + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 0x03, 0x03); + } + break; + case SND_SOC_DAPM_PRE_PMD: + /* Halt the compander*/ + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 1 << 2, 1 << 2); + break; + case SND_SOC_DAPM_POST_PMD: + /* Restore the gain */ + tabla_config_gain_compander(codec, w->shift, + tabla->comp_enabled[w->shift], event); + /* Disable the compander*/ + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 0x03, 0x00); + /* Turn off the clock for compander in pair*/ + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_B2_CTL, + 0x03 << comp_shift[w->shift], 0); + break; + } + return 0; +} + +static const char *tabla_ear_pa_gain_text[] = {"POS_6_DB", "POS_2_DB"}; +static const struct soc_enum tabla_ear_pa_gain_enum[] = { + SOC_ENUM_SINGLE_EXT(2, tabla_ear_pa_gain_text), +}; + +/*cut of frequency for high pass filter*/ +static const char *cf_text[] = { + "MIN_3DB_4Hz", "MIN_3DB_75Hz", "MIN_3DB_150Hz" +}; + +static const struct soc_enum cf_dec1_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX1_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec2_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX2_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec3_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX3_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec4_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX4_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec5_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX5_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec6_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX6_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec7_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX7_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec8_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX8_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec9_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX9_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec10_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX10_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_rxmix1_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX1_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix2_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX2_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix3_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX3_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix4_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX4_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix5_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX5_B4_CTL, 1, 3, cf_text) +; +static const struct soc_enum cf_rxmix6_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX6_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix7_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX7_B4_CTL, 1, 3, cf_text); + +static const struct snd_kcontrol_new tabla_snd_controls[] = { + + SOC_ENUM_EXT("EAR PA Gain", tabla_ear_pa_gain_enum[0], + tabla_pa_gain_get, tabla_pa_gain_put), + + SOC_SINGLE_TLV("LINEOUT1 Volume", TABLA_A_RX_LINE_1_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT2 Volume", TABLA_A_RX_LINE_2_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("LINEOUT4 Volume", TABLA_A_RX_LINE_4_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT5 Volume", TABLA_A_RX_LINE_5_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_S8_TLV("RX1 Digital Volume", TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX2 Digital Volume", TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX3 Digital Volume", TABLA_A_CDC_RX3_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX4 Digital Volume", TABLA_A_CDC_RX4_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX5 Digital Volume", TABLA_A_CDC_RX5_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX6 Digital Volume", TABLA_A_CDC_RX6_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX7 Digital Volume", TABLA_A_CDC_RX7_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + + SOC_SINGLE_S8_TLV("DEC1 Volume", TABLA_A_CDC_TX1_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC2 Volume", TABLA_A_CDC_TX2_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC3 Volume", TABLA_A_CDC_TX3_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC4 Volume", TABLA_A_CDC_TX4_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC5 Volume", TABLA_A_CDC_TX5_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC6 Volume", TABLA_A_CDC_TX6_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC7 Volume", TABLA_A_CDC_TX7_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC8 Volume", TABLA_A_CDC_TX8_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC9 Volume", TABLA_A_CDC_TX9_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC10 Volume", TABLA_A_CDC_TX10_VOL_CTL_GAIN, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP1 Volume", TABLA_A_CDC_IIR1_GAIN_B1_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP2 Volume", TABLA_A_CDC_IIR1_GAIN_B2_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP3 Volume", TABLA_A_CDC_IIR1_GAIN_B3_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP4 Volume", TABLA_A_CDC_IIR1_GAIN_B4_CTL, -84, + 40, digital_gain), + SOC_SINGLE_TLV("ADC1 Volume", TABLA_A_TX_1_2_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", TABLA_A_TX_1_2_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC3 Volume", TABLA_A_TX_3_4_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC4 Volume", TABLA_A_TX_3_4_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC5 Volume", TABLA_A_TX_5_6_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC6 Volume", TABLA_A_TX_5_6_EN, 1, 3, 0, analog_gain), + + SOC_SINGLE_TLV("AUX_PGA_LEFT Volume", TABLA_A_AUX_L_GAIN, 0, 39, 0, + aux_pga_gain), + SOC_SINGLE_TLV("AUX_PGA_RIGHT Volume", TABLA_A_AUX_R_GAIN, 0, 39, 0, + aux_pga_gain), + + SOC_SINGLE("MICBIAS1 CAPLESS Switch", TABLA_A_MICB_1_CTL, 4, 1, 1), + SOC_SINGLE("MICBIAS2 CAPLESS Switch", TABLA_A_MICB_2_CTL, 4, 1, 1), + SOC_SINGLE("MICBIAS3 CAPLESS Switch", TABLA_A_MICB_3_CTL, 4, 1, 1), + + SOC_SINGLE_EXT("ANC Slot", SND_SOC_NOPM, 0, 0, 100, tabla_get_anc_slot, + tabla_put_anc_slot), + SOC_ENUM("TX1 HPF cut off", cf_dec1_enum), + SOC_ENUM("TX2 HPF cut off", cf_dec2_enum), + SOC_ENUM("TX3 HPF cut off", cf_dec3_enum), + SOC_ENUM("TX4 HPF cut off", cf_dec4_enum), + SOC_ENUM("TX5 HPF cut off", cf_dec5_enum), + SOC_ENUM("TX6 HPF cut off", cf_dec6_enum), + SOC_ENUM("TX7 HPF cut off", cf_dec7_enum), + SOC_ENUM("TX8 HPF cut off", cf_dec8_enum), + SOC_ENUM("TX9 HPF cut off", cf_dec9_enum), + SOC_ENUM("TX10 HPF cut off", cf_dec10_enum), + + SOC_SINGLE("TX1 HPF Switch", TABLA_A_CDC_TX1_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX2 HPF Switch", TABLA_A_CDC_TX2_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX3 HPF Switch", TABLA_A_CDC_TX3_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX4 HPF Switch", TABLA_A_CDC_TX4_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX5 HPF Switch", TABLA_A_CDC_TX5_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX6 HPF Switch", TABLA_A_CDC_TX6_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX7 HPF Switch", TABLA_A_CDC_TX7_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX8 HPF Switch", TABLA_A_CDC_TX8_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX9 HPF Switch", TABLA_A_CDC_TX9_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX10 HPF Switch", TABLA_A_CDC_TX10_MUX_CTL, 3, 1, 0), + + SOC_SINGLE("RX1 HPF Switch", TABLA_A_CDC_RX1_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX2 HPF Switch", TABLA_A_CDC_RX2_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX3 HPF Switch", TABLA_A_CDC_RX3_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX4 HPF Switch", TABLA_A_CDC_RX4_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX5 HPF Switch", TABLA_A_CDC_RX5_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX6 HPF Switch", TABLA_A_CDC_RX6_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX7 HPF Switch", TABLA_A_CDC_RX7_B5_CTL, 2, 1, 0), + + SOC_ENUM("RX1 HPF cut off", cf_rxmix1_enum), + SOC_ENUM("RX2 HPF cut off", cf_rxmix2_enum), + SOC_ENUM("RX3 HPF cut off", cf_rxmix3_enum), + SOC_ENUM("RX4 HPF cut off", cf_rxmix4_enum), + SOC_ENUM("RX5 HPF cut off", cf_rxmix5_enum), + SOC_ENUM("RX6 HPF cut off", cf_rxmix6_enum), + SOC_ENUM("RX7 HPF cut off", cf_rxmix7_enum), + + SOC_SINGLE_EXT("IIR1 Enable Band1", IIR1, BAND1, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band2", IIR1, BAND2, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band3", IIR1, BAND3, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band4", IIR1, BAND4, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band5", IIR1, BAND5, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band1", IIR2, BAND1, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band2", IIR2, BAND2, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band3", IIR2, BAND3, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band4", IIR2, BAND4, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band5", IIR2, BAND5, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + + SOC_SINGLE_MULTI_EXT("IIR1 Band1", IIR1, BAND1, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band2", IIR1, BAND2, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band3", IIR1, BAND3, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band4", IIR1, BAND4, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band5", IIR1, BAND5, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band1", IIR2, BAND1, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band2", IIR2, BAND2, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band3", IIR2, BAND3, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band4", IIR2, BAND4, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band5", IIR2, BAND5, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_EXT("COMP1 Switch", SND_SOC_NOPM, 1, COMPANDER_1, 0, + tabla_get_compander, tabla_set_compander), + SOC_SINGLE_EXT("COMP2 Switch", SND_SOC_NOPM, 0, COMPANDER_2, 0, + tabla_get_compander, tabla_set_compander), +}; + +static const struct snd_kcontrol_new tabla_1_x_snd_controls[] = { + SOC_SINGLE("MICBIAS4 CAPLESS Switch", TABLA_1_A_MICB_4_CTL, 4, 1, 1), +}; + +static const struct snd_kcontrol_new tabla_2_higher_snd_controls[] = { + SOC_SINGLE("MICBIAS4 CAPLESS Switch", TABLA_2_A_MICB_4_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 *rx_mix2_text[] = { + "ZERO", "SRC1", "SRC2", "IIR1", "IIR2" +}; + +static const char *rx_dsm_text[] = { + "CIC_OUT", "DSM_INV" +}; + +static const char *sb_tx1_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1" +}; + +static const char *sb_tx2_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC2" +}; + +static const char *sb_tx3_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC3" +}; + +static const char *sb_tx4_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC4" +}; + +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 *dec2_mux_text[] = { + "ZERO", "DMIC2", "ADC5", +}; + +static const char *dec3_mux_text[] = { + "ZERO", "DMIC3", "ADC4", +}; + +static const char *dec4_mux_text[] = { + "ZERO", "DMIC4", "ADC3", +}; + +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 *dec8_mux_text[] = { + "ZERO", "DMIC2", "DMIC5", "ADC2", "ADC5", +}; + +static const char *dec9_mux_text[] = { + "ZERO", "DMIC4", "DMIC5", "ADC2", "ADC3", "ADCMB", "ANC1_FB", "ANC2_FB", +}; + +static const char *dec10_mux_text[] = { + "ZERO", "DMIC3", "DMIC6", "ADC1", "ADC4", "ADCMB", "ANC1_FB", "ANC2_FB", +}; + +static const char const *anc_mux_text[] = { + "ZERO", "ADC1", "ADC2", "ADC3", "ADC4", "ADC5", "ADC6", "ADC_MB", + "RSVD_1", "DMIC1", "DMIC2", "DMIC3", "DMIC4", "DMIC5", "DMIC6" +}; + +static const char const *anc1_fb_mux_text[] = { + "ZERO", "EAR_HPH_L", "EAR_LINE_1", +}; + +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 rx_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx_mix1_inp3_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B2_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 rx2_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B1_CTL, 4, 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 rx3_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B1_CTL, 4, 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 rx4_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX4_B1_CTL, 4, 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 rx5_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX5_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx6_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX6_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx6_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX6_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx7_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX7_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx7_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX7_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx1_mix2_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B3_CTL, 0, 5, rx_mix2_text); + +static const struct soc_enum rx1_mix2_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B3_CTL, 3, 5, rx_mix2_text); + +static const struct soc_enum rx2_mix2_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B3_CTL, 0, 5, rx_mix2_text); + +static const struct soc_enum rx2_mix2_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B3_CTL, 3, 5, rx_mix2_text); + +static const struct soc_enum rx3_mix2_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B3_CTL, 0, 5, rx_mix2_text); + +static const struct soc_enum rx3_mix2_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B3_CTL, 3, 5, rx_mix2_text); + +static const struct soc_enum rx4_dsm_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX4_B6_CTL, 4, 2, rx_dsm_text); + +static const struct soc_enum rx6_dsm_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX6_B6_CTL, 4, 2, rx_dsm_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 sb_tx2_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B2_CTL, 0, 9, sb_tx2_mux_text); + +static const struct soc_enum sb_tx3_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B3_CTL, 0, 9, sb_tx3_mux_text); + +static const struct soc_enum sb_tx4_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B4_CTL, 0, 9, sb_tx4_mux_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_tx9_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B9_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum sb_tx10_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B10_CTL, 0, 18, + sb_tx7_to_tx10_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 dec2_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 2, 3, dec2_mux_text); + +static const struct soc_enum dec3_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 4, 3, dec3_mux_text); + +static const struct soc_enum dec4_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 6, 3, dec4_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 dec8_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B3_CTL, 0, 7, dec8_mux_text); + +static const struct soc_enum dec9_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B3_CTL, 3, 8, dec9_mux_text); + +static const struct soc_enum dec10_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B4_CTL, 0, 8, dec10_mux_text); + +static const struct soc_enum anc1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_ANC_B1_CTL, 0, 16, anc_mux_text); + +static const struct soc_enum anc2_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_ANC_B1_CTL, 4, 16, anc_mux_text); + +static const struct soc_enum anc1_fb_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_ANC_B2_CTL, 0, 3, anc1_fb_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 rx_mix1_inp2_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP2 Mux", rx_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx_mix1_inp3_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP3 Mux", rx_mix1_inp3_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 rx2_mix1_inp2_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP2 Mux", rx2_mix1_inp2_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 rx3_mix1_inp2_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP2 Mux", rx3_mix1_inp2_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 rx4_mix1_inp2_mux = + SOC_DAPM_ENUM("RX4 MIX1 INP2 Mux", rx4_mix1_inp2_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 rx5_mix1_inp2_mux = + SOC_DAPM_ENUM("RX5 MIX1 INP2 Mux", rx5_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx6_mix1_inp1_mux = + SOC_DAPM_ENUM("RX6 MIX1 INP1 Mux", rx6_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx6_mix1_inp2_mux = + SOC_DAPM_ENUM("RX6 MIX1 INP2 Mux", rx6_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx7_mix1_inp1_mux = + SOC_DAPM_ENUM("RX7 MIX1 INP1 Mux", rx7_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx7_mix1_inp2_mux = + SOC_DAPM_ENUM("RX7 MIX1 INP2 Mux", rx7_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx1_mix2_inp1_mux = + SOC_DAPM_ENUM("RX1 MIX2 INP1 Mux", rx1_mix2_inp1_chain_enum); + +static const struct snd_kcontrol_new rx1_mix2_inp2_mux = + SOC_DAPM_ENUM("RX1 MIX2 INP2 Mux", rx1_mix2_inp2_chain_enum); + +static const struct snd_kcontrol_new rx2_mix2_inp1_mux = + SOC_DAPM_ENUM("RX2 MIX2 INP1 Mux", rx2_mix2_inp1_chain_enum); + +static const struct snd_kcontrol_new rx2_mix2_inp2_mux = + SOC_DAPM_ENUM("RX2 MIX2 INP2 Mux", rx2_mix2_inp2_chain_enum); + +static const struct snd_kcontrol_new rx3_mix2_inp1_mux = + SOC_DAPM_ENUM("RX3 MIX2 INP1 Mux", rx3_mix2_inp1_chain_enum); + +static const struct snd_kcontrol_new rx3_mix2_inp2_mux = + SOC_DAPM_ENUM("RX3 MIX2 INP2 Mux", rx3_mix2_inp2_chain_enum); + +static const struct snd_kcontrol_new rx4_dsm_mux = + SOC_DAPM_ENUM("RX4 DSM MUX Mux", rx4_dsm_enum); + +static const struct snd_kcontrol_new rx6_dsm_mux = + SOC_DAPM_ENUM("RX6 DSM MUX Mux", rx6_dsm_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 sb_tx2_mux = + SOC_DAPM_ENUM("SLIM TX2 MUX Mux", sb_tx2_mux_enum); + +static const struct snd_kcontrol_new sb_tx3_mux = + SOC_DAPM_ENUM("SLIM TX3 MUX Mux", sb_tx3_mux_enum); + +static const struct snd_kcontrol_new sb_tx4_mux = + SOC_DAPM_ENUM("SLIM TX4 MUX Mux", sb_tx4_mux_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_tx9_mux = + SOC_DAPM_ENUM("SLIM TX9 MUX Mux", sb_tx9_mux_enum); + +static const struct snd_kcontrol_new sb_tx10_mux = + SOC_DAPM_ENUM("SLIM TX10 MUX Mux", sb_tx10_mux_enum); + + +static int wcd9310_put_dec_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *w = wlist->widgets[0]; + struct snd_soc_codec *codec = w->codec; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int dec_mux, decimator; + char *dec_name = NULL; + char *widget_name = NULL; + char *temp; + u16 tx_mux_ctl_reg; + u8 adc_dmic_sel = 0x0; + int ret = 0; + + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + dec_mux = ucontrol->value.enumerated.item[0]; + + widget_name = kstrndup(w->name, 15, GFP_KERNEL); + if (!widget_name) + return -ENOMEM; + temp = widget_name; + + dec_name = strsep(&widget_name, " "); + widget_name = temp; + if (!dec_name) { + pr_err("%s: Invalid decimator = %s\n", __func__, w->name); + ret = -EINVAL; + goto out; + } + + ret = kstrtouint(strpbrk(dec_name, "123456789"), 10, &decimator); + if (ret < 0) { + pr_err("%s: Invalid decimator = %s\n", __func__, dec_name); + ret = -EINVAL; + goto out; + } + + dev_dbg(w->dapm->dev, "%s(): widget = %s dec_name = %s decimator = %u" + " dec_mux = %u\n", __func__, w->name, dec_name, decimator, + dec_mux); + + + switch (decimator) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + if (dec_mux == 1) + adc_dmic_sel = 0x1; + else + adc_dmic_sel = 0x0; + break; + case 7: + case 8: + case 9: + case 10: + if ((dec_mux == 1) || (dec_mux == 2)) + adc_dmic_sel = 0x1; + else + adc_dmic_sel = 0x0; + break; + default: + pr_err("%s: Invalid Decimator = %u\n", __func__, decimator); + ret = -EINVAL; + goto out; + } + + tx_mux_ctl_reg = TABLA_A_CDC_TX1_MUX_CTL + 8 * (decimator - 1); + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x1, adc_dmic_sel); + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + +out: + kfree(widget_name); + return ret; +} + +#define WCD9310_DEC_ENUM(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = wcd9310_put_dec_enum, \ + .private_value = (unsigned long)&xenum } + +static const struct snd_kcontrol_new dec1_mux = + WCD9310_DEC_ENUM("DEC1 MUX Mux", dec1_mux_enum); + +static const struct snd_kcontrol_new dec2_mux = + WCD9310_DEC_ENUM("DEC2 MUX Mux", dec2_mux_enum); + +static const struct snd_kcontrol_new dec3_mux = + WCD9310_DEC_ENUM("DEC3 MUX Mux", dec3_mux_enum); + +static const struct snd_kcontrol_new dec4_mux = + WCD9310_DEC_ENUM("DEC4 MUX Mux", dec4_mux_enum); + +static const struct snd_kcontrol_new dec5_mux = + WCD9310_DEC_ENUM("DEC5 MUX Mux", dec5_mux_enum); + +static const struct snd_kcontrol_new dec6_mux = + WCD9310_DEC_ENUM("DEC6 MUX Mux", dec6_mux_enum); + +static const struct snd_kcontrol_new dec7_mux = + WCD9310_DEC_ENUM("DEC7 MUX Mux", dec7_mux_enum); + +static const struct snd_kcontrol_new dec8_mux = + WCD9310_DEC_ENUM("DEC8 MUX Mux", dec8_mux_enum); + +static const struct snd_kcontrol_new dec9_mux = + WCD9310_DEC_ENUM("DEC9 MUX Mux", dec9_mux_enum); + +static const struct snd_kcontrol_new dec10_mux = + WCD9310_DEC_ENUM("DEC10 MUX Mux", dec10_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 anc1_mux = + SOC_DAPM_ENUM("ANC1 MUX Mux", anc1_mux_enum); + +static const struct snd_kcontrol_new anc2_mux = + SOC_DAPM_ENUM("ANC2 MUX Mux", anc2_mux_enum); + +static const struct snd_kcontrol_new anc1_fb_mux = + SOC_DAPM_ENUM("ANC1 FB MUX Mux", anc1_fb_mux_enum); + +static const struct snd_kcontrol_new dac1_switch[] = { + 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 hphl_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 7, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 7, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 7, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 7, 1, 0), +}; + +static const struct snd_kcontrol_new hphr_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 6, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 6, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 6, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lineout1_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 5, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 5, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 5, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 5, 1, 0), +}; + +static const struct snd_kcontrol_new lineout2_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 4, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 4, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 4, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 4, 1, 0), +}; + +static const struct snd_kcontrol_new lineout3_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 3, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 3, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 3, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 3, 1, 0), +}; + +static const struct snd_kcontrol_new lineout4_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 2, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 2, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 2, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 2, 1, 0), +}; + +static const struct snd_kcontrol_new lineout5_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 1, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 1, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 1, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 1, 1, 0), +}; + +static const struct snd_kcontrol_new ear_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 0, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 0, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 0, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 0, 1, 0), +}; + +static const struct snd_kcontrol_new lineout3_ground_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_LINE_3_DAC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new lineout4_ground_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_LINE_4_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_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); + } +} + +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; + u8 init_bit_shift; + + 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; + } + + if (w->shift == 3) + init_bit_shift = 6; + else if (w->shift == 7) + init_bit_shift = 7; + else { + pr_err("%s: Error, invalid init bit postion adc register\n", + __func__); + return -EINVAL; + } + + + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + tabla_codec_enable_adc_block(codec, 1); + snd_soc_update_bits(codec, adc_reg, 1 << init_bit_shift, + 1 << init_bit_shift); + break; + case SND_SOC_DAPM_POST_PMU: + + snd_soc_update_bits(codec, adc_reg, 1 << init_bit_shift, 0x00); + + break; + case SND_SOC_DAPM_POST_PMD: + tabla_codec_enable_adc_block(codec, 0); + break; + } + return 0; +} + +static void tabla_codec_enable_audio_mode_bandgap(struct snd_soc_codec *codec) +{ + 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 (choice == TABLA_BANDGAP_MBHC_MODE) { + /* bandgap mode becomes fast, + * mclk should be off or clk buff source souldn't be VBG + * Let's turn off mclk always */ + WARN_ON(snd_soc_read(codec, TABLA_A_CLK_BUFF_EN2) & (1 << 2)); + 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); + 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); + } 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 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); + usleep_range(50, 50); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x02, 0x02); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x00); + usleep_range(50, 50); + tabla->clock_active = false; +} + +static int tabla_codec_mclk_index(const struct tabla_priv *tabla) +{ + if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_12288KHZ) + return 0; + else if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_9600KHZ) + return 1; + else { + BUG_ON(1); + return -EINVAL; + } +} + +static void tabla_enable_rx_bias(struct snd_soc_codec *codec, u32 enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (enable) { + tabla->rx_bias_count++; + if (tabla->rx_bias_count == 1) + snd_soc_update_bits(codec, TABLA_A_RX_COM_BIAS, + 0x80, 0x80); + } else { + tabla->rx_bias_count--; + if (!tabla->rx_bias_count) + snd_soc_update_bits(codec, TABLA_A_RX_COM_BIAS, + 0x80, 0x00); + } +} + +static int tabla_codec_enable_config_mode(struct snd_soc_codec *codec, + int enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x10, 0); + /* bandgap mode to fast */ + 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(10000, 10000); + 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); + /* clk source to ext clk and clk buff ref to VBG */ + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x0C, 0x04); + } + 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: config_mode = %d\n", __func__, config_mode); + + /* transit to RCO requires mclk off */ + WARN_ON(snd_soc_read(codec, TABLA_A_CLK_BUFF_EN2) & (1 << 2)); + if (config_mode) { + /* enable RCO and switch to it */ + tabla_codec_enable_config_mode(codec, 1); + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN2, 0x02); + usleep_range(1000, 1000); + } else { + /* switch to MCLK */ + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x08, 0x00); + + if (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, 0x01, 0x01); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x02, 0x00); + /* on MCLK */ + 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 int tabla_codec_enable_aux_pga(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_PRE_PMU: + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_AUDIO_MODE); + tabla_enable_rx_bias(codec, 1); + + snd_soc_update_bits(codec, TABLA_A_AUX_COM_CTL, + 0x08, 0x08); + /* Enable Zero Cross detect for AUX PGA channel + * and set the initial AUX PGA gain to NEG_0P0_DB + * to avoid glitches. + */ + if (w->reg == TABLA_A_AUX_L_EN) { + snd_soc_update_bits(codec, TABLA_A_AUX_L_EN, + 0x20, 0x20); + tabla->aux_l_gain = snd_soc_read(codec, + TABLA_A_AUX_L_GAIN); + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, 0x1F); + } else { + snd_soc_update_bits(codec, TABLA_A_AUX_R_EN, + 0x20, 0x20); + tabla->aux_r_gain = snd_soc_read(codec, + TABLA_A_AUX_R_GAIN); + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, 0x1F); + } + if (tabla->aux_pga_cnt++ == 1 + && !tabla->mclk_enabled) { + tabla_codec_enable_clock_block(codec, 1); + pr_debug("AUX PGA enabled RC osc\n"); + } + break; + + case SND_SOC_DAPM_POST_PMU: + if (w->reg == TABLA_A_AUX_L_EN) + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, + tabla->aux_l_gain); + else + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, + tabla->aux_r_gain); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Mute AUX PGA channel in use before disabling AUX PGA */ + if (w->reg == TABLA_A_AUX_L_EN) { + tabla->aux_l_gain = snd_soc_read(codec, + TABLA_A_AUX_L_GAIN); + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, 0x1F); + } else { + tabla->aux_r_gain = snd_soc_read(codec, + TABLA_A_AUX_R_GAIN); + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, 0x1F); + } + break; + + case SND_SOC_DAPM_POST_PMD: + tabla_enable_rx_bias(codec, 0); + + snd_soc_update_bits(codec, TABLA_A_AUX_COM_CTL, + 0x08, 0x00); + if (w->reg == TABLA_A_AUX_L_EN) { + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, + tabla->aux_l_gain); + snd_soc_update_bits(codec, TABLA_A_AUX_L_EN, + 0x20, 0x00); + } else { + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, + tabla->aux_r_gain); + snd_soc_update_bits(codec, TABLA_A_AUX_R_EN, + 0x20, 0x00); + } + + if (tabla->aux_pga_cnt-- == 0) { + if (tabla->mbhc_polling_active) + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_MBHC_MODE); + else + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_OFF); + + if (!tabla->mclk_enabled && + !tabla->mbhc_polling_active) { + tabla_codec_enable_clock_block(codec, 0); + } + } + 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 %s\n", __func__, event, w->name); + + 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: + pr_debug("%s: sleeping 16 ms after %s PA turn on\n", + __func__, w->name); + usleep_range(16000, 16000); + 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_dmic(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); + u8 dmic_clk_en; + s32 *dmic_clk_cnt; + unsigned int dmic; + int ret; + + ret = kstrtouint(strpbrk(w->name, "123456"), 10, &dmic); + if (ret < 0) { + pr_err("%s: Invalid DMIC line on the codec\n", __func__); + return -EINVAL; + } + + switch (dmic) { + case 1: + case 2: + dmic_clk_en = 0x01; + dmic_clk_cnt = &(tabla->dmic_1_2_clk_cnt); + + pr_debug("%s() event %d DMIC%d dmic_1_2_clk_cnt %d\n", + __func__, event, dmic, *dmic_clk_cnt); + + break; + + case 3: + case 4: + dmic_clk_en = 0x04; + dmic_clk_cnt = &(tabla->dmic_3_4_clk_cnt); + + pr_debug("%s() event %d DMIC%d dmic_3_4_clk_cnt %d\n", + __func__, event, dmic, *dmic_clk_cnt); + break; + + case 5: + case 6: + dmic_clk_en = 0x10; + dmic_clk_cnt = &(tabla->dmic_5_6_clk_cnt); + + pr_debug("%s() event %d DMIC%d dmic_5_6_clk_cnt %d\n", + __func__, event, dmic, *dmic_clk_cnt); + + break; + + default: + pr_err("%s: Invalid DMIC Selection\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + (*dmic_clk_cnt)++; + if (*dmic_clk_cnt == 1) + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, dmic_clk_en); + + break; + case SND_SOC_DAPM_POST_PMD: + + (*dmic_clk_cnt)--; + if (*dmic_clk_cnt == 0) + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, 0); + break; + } + return 0; +} + +static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + const char *filename; + const struct firmware *fw; + int i; + int ret; + int num_anc_slots; + struct anc_header *anc_head; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u32 anc_writes_size = 0; + int anc_size_remaining; + u32 *anc_ptr; + u16 reg; + u8 mask, val, old_val; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + filename = "wcd9310/wcd9310_anc.bin"; + + ret = request_firmware(&fw, filename, codec->dev); + if (ret != 0) { + dev_err(codec->dev, "Failed to acquire ANC data: %d\n", + ret); + return -ENODEV; + } + + if (fw->size < sizeof(struct anc_header)) { + dev_err(codec->dev, "Not enough data\n"); + release_firmware(fw); + return -ENOMEM; + } + + /* First number is the number of register writes */ + anc_head = (struct anc_header *)(fw->data); + anc_ptr = (u32 *)((u32)fw->data + sizeof(struct anc_header)); + anc_size_remaining = fw->size - sizeof(struct anc_header); + num_anc_slots = anc_head->num_anc_slots; + + if (tabla->anc_slot >= num_anc_slots) { + dev_err(codec->dev, "Invalid ANC slot selected\n"); + release_firmware(fw); + return -EINVAL; + } + + for (i = 0; i < num_anc_slots; i++) { + + if (anc_size_remaining < TABLA_PACKED_REG_SIZE) { + dev_err(codec->dev, "Invalid register format\n"); + release_firmware(fw); + return -EINVAL; + } + anc_writes_size = (u32)(*anc_ptr); + anc_size_remaining -= sizeof(u32); + anc_ptr += 1; + + if (anc_writes_size * TABLA_PACKED_REG_SIZE + > anc_size_remaining) { + dev_err(codec->dev, "Invalid register format\n"); + release_firmware(fw); + return -ENOMEM; + } + + if (tabla->anc_slot == i) + break; + + anc_size_remaining -= (anc_writes_size * + TABLA_PACKED_REG_SIZE); + anc_ptr += anc_writes_size; + } + if (i == num_anc_slots) { + dev_err(codec->dev, "Selected ANC slot not present\n"); + release_firmware(fw); + return -ENOMEM; + } + + for (i = 0; i < anc_writes_size; i++) { + TABLA_CODEC_UNPACK_ENTRY(anc_ptr[i], reg, + mask, val); + old_val = snd_soc_read(codec, reg); + snd_soc_write(codec, reg, (old_val & ~mask) | + (val & mask)); + } + release_firmware(fw); + + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_write(codec, TABLA_A_CDC_CLK_ANC_RESET_CTL, 0xFF); + snd_soc_write(codec, TABLA_A_CDC_CLK_ANC_CLK_EN_CTL, 0); + break; + } + return 0; +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_start_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + int mbhc_state = tabla->mbhc_state; + + pr_debug("%s: enter\n", __func__); + if (!tabla->mbhc_polling_active) { + pr_debug("Polling is not active, do not start polling\n"); + return; + } + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84); + + if (!tabla->no_mic_headset_override) { + if (mbhc_state == MBHC_STATE_POTENTIAL) { + pr_debug("%s recovering MBHC state macine\n", __func__); + tabla->mbhc_state = MBHC_STATE_POTENTIAL_RECOVERY; + /* set to max button press threshold */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + 0x7F); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B4_CTL, + (TABLA_IS_1_X(tabla_core->version) ? + 0x07 : 0x7F)); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL, + 0xFF); + /* set to max */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B6_CTL, + 0x7F); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B5_CTL, + 0xFF); + } + } + + 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); + pr_debug("%s: leave\n", __func__); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_pause_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enter\n", __func__); + if (!tabla->mbhc_polling_active) { + pr_debug("polling not active, nothing to pause\n"); + return; + } + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + pr_debug("%s: leave\n", __func__); +} + +static void tabla_codec_switch_cfilt_mode(struct snd_soc_codec *codec, int mode) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u8 reg_mode_val, cur_mode_val; + bool mbhc_was_polling = false; + + if (mode) + reg_mode_val = TABLA_CFILT_FAST_MODE; + else + reg_mode_val = TABLA_CFILT_SLOW_MODE; + + cur_mode_val = snd_soc_read(codec, + tabla->mbhc_bias_regs.cfilt_ctl) & 0x40; + + if (cur_mode_val != reg_mode_val) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + if (tabla->mbhc_polling_active) { + tabla_codec_pause_hs_polling(codec); + mbhc_was_polling = true; + } + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_ctl, 0x40, reg_mode_val); + if (mbhc_was_polling) + tabla_codec_start_hs_polling(codec); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_debug("%s: CFILT mode change (%x to %x)\n", __func__, + cur_mode_val, reg_mode_val); + } else { + pr_debug("%s: CFILT Value is already %x\n", + __func__, cur_mode_val); + } +} + +static void tabla_codec_update_cfilt_usage(struct snd_soc_codec *codec, + u8 cfilt_sel, int inc) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u32 *cfilt_cnt_ptr = NULL; + u16 micb_cfilt_reg; + + switch (cfilt_sel) { + case TABLA_CFILT1_SEL: + cfilt_cnt_ptr = &tabla->cfilt1_cnt; + micb_cfilt_reg = TABLA_A_MICB_CFILT_1_CTL; + break; + case TABLA_CFILT2_SEL: + cfilt_cnt_ptr = &tabla->cfilt2_cnt; + micb_cfilt_reg = TABLA_A_MICB_CFILT_2_CTL; + break; + case TABLA_CFILT3_SEL: + cfilt_cnt_ptr = &tabla->cfilt3_cnt; + micb_cfilt_reg = TABLA_A_MICB_CFILT_3_CTL; + break; + default: + return; /* should not happen */ + } + + if (inc) { + if (!(*cfilt_cnt_ptr)++) { + /* Switch CFILT to slow mode if MBHC CFILT being used */ + if (cfilt_sel == tabla->mbhc_bias_regs.cfilt_sel) + tabla_codec_switch_cfilt_mode(codec, 0); + + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0x80); + } + } else { + /* check if count not zero, decrement + * then check if zero, go ahead disable cfilter + */ + if ((*cfilt_cnt_ptr) && !--(*cfilt_cnt_ptr)) { + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0); + + /* Switch CFILT to fast mode if MBHC CFILT being used */ + if (cfilt_sel == tabla->mbhc_bias_regs.cfilt_sel) + tabla_codec_switch_cfilt_mode(codec, 1); + } + } +} + +static int tabla_find_k_value(unsigned int ldoh_v, unsigned int cfilt_mv) +{ + int rc = -EINVAL; + unsigned min_mv, max_mv; + + switch (ldoh_v) { + case TABLA_LDOH_1P95_V: + min_mv = 160; + max_mv = 1800; + break; + case TABLA_LDOH_2P35_V: + min_mv = 200; + max_mv = 2200; + break; + case TABLA_LDOH_2P75_V: + min_mv = 240; + max_mv = 2600; + break; + case TABLA_LDOH_2P85_V: + min_mv = 250; + max_mv = 2700; + break; + default: + goto done; + } + + if (cfilt_mv < min_mv || cfilt_mv > max_mv) + goto done; + + for (rc = 4; rc <= 44; rc++) { + min_mv = max_mv * (rc) / 44; + if (min_mv >= cfilt_mv) { + rc -= 4; + break; + } + } +done: + return rc; +} + +static bool tabla_is_hph_pa_on(struct snd_soc_codec *codec) +{ + u8 hph_reg_val = 0; + hph_reg_val = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_EN); + + return (hph_reg_val & 0x30) ? true : false; +} + +static bool tabla_is_hph_dac_on(struct snd_soc_codec *codec, int left) +{ + u8 hph_reg_val = 0; + if (left) + hph_reg_val = snd_soc_read(codec, + TABLA_A_RX_HPH_L_DAC_CTL); + else + hph_reg_val = snd_soc_read(codec, + TABLA_A_RX_HPH_R_DAC_CTL); + + return (hph_reg_val & 0xC0) ? true : false; +} + +static void tabla_turn_onoff_override(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, on << 2); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_drive_v_to_micbias(struct snd_soc_codec *codec, + int usec) +{ + int cfilt_k_val; + bool set = true; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + tabla->mbhc_micbias_switched) { + pr_debug("%s: set mic V to micbias V\n", __func__); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + tabla_turn_onoff_override(codec, true); + while (1) { + cfilt_k_val = tabla_find_k_value( + tabla->pdata->micbias.ldoh_v, + set ? tabla->mbhc_data.micb_mv : + VDDIO_MICBIAS_MV); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + if (!set) + break; + usleep_range(usec, usec); + set = false; + } + tabla_turn_onoff_override(codec, false); + } +} + +/* called under codec_resource_lock acquisition */ +static void __tabla_codec_switch_micbias(struct snd_soc_codec *codec, + int vddio_switch, bool restartpolling, + bool checkpolling) +{ + int cfilt_k_val; + bool override; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (vddio_switch && !tabla->mbhc_micbias_switched && + (!checkpolling || tabla->mbhc_polling_active)) { + if (restartpolling) + tabla_codec_pause_hs_polling(codec); + override = snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x04; + if (!override) + tabla_turn_onoff_override(codec, true); + /* Adjust threshold if Mic Bias voltage changes */ + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + cfilt_k_val = tabla_find_k_value( + tabla->pdata->micbias.ldoh_v, + VDDIO_MICBIAS_MV); + usleep_range(10000, 10000); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + usleep_range(10000, 10000); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + tabla->mbhc_data.adj_v_ins_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + (tabla->mbhc_data.adj_v_ins_hu >> 8) & + 0xFF); + pr_debug("%s: Programmed MBHC thresholds to VDDIO\n", + __func__); + } + + /* enable MIC BIAS Switch to VDDIO */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x00); + if (!override) + tabla_turn_onoff_override(codec, false); + if (restartpolling) + tabla_codec_start_hs_polling(codec); + + tabla->mbhc_micbias_switched = true; + pr_debug("%s: VDDIO switch enabled\n", __func__); + } else if (!vddio_switch && tabla->mbhc_micbias_switched) { + if ((!checkpolling || tabla->mbhc_polling_active) && + restartpolling) + tabla_codec_pause_hs_polling(codec); + /* Reprogram thresholds */ + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + cfilt_k_val = tabla_find_k_value( + tabla->pdata->micbias.ldoh_v, + tabla->mbhc_data.micb_mv); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + usleep_range(10000, 10000); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + tabla->mbhc_data.v_ins_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + (tabla->mbhc_data.v_ins_hu >> 8) & 0xFF); + pr_debug("%s: Programmed MBHC thresholds to MICBIAS\n", + __func__); + } + + /* Disable MIC BIAS Switch to VDDIO */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x00); + + if ((!checkpolling || tabla->mbhc_polling_active) && + restartpolling) + tabla_codec_start_hs_polling(codec); + + tabla->mbhc_micbias_switched = false; + pr_debug("%s: VDDIO switch disabled\n", __func__); + } +} + +static void tabla_codec_switch_micbias(struct snd_soc_codec *codec, + int vddio_switch) +{ + return __tabla_codec_switch_micbias(codec, vddio_switch, true, true); +} + +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; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u16 micb_int_reg; + int micb_line; + u8 cfilt_sel_val = 0; + char *internal1_text = "Internal1"; + char *internal2_text = "Internal2"; + char *internal3_text = "Internal3"; + + pr_debug("%s %d\n", __func__, event); + switch (w->reg) { + case TABLA_A_MICB_1_CTL: + micb_int_reg = TABLA_A_MICB_1_INT_RBIAS; + cfilt_sel_val = tabla->pdata->micbias.bias1_cfilt_sel; + micb_line = TABLA_MICBIAS1; + break; + case TABLA_A_MICB_2_CTL: + micb_int_reg = TABLA_A_MICB_2_INT_RBIAS; + cfilt_sel_val = tabla->pdata->micbias.bias2_cfilt_sel; + micb_line = TABLA_MICBIAS2; + break; + case TABLA_A_MICB_3_CTL: + micb_int_reg = TABLA_A_MICB_3_INT_RBIAS; + cfilt_sel_val = tabla->pdata->micbias.bias3_cfilt_sel; + micb_line = TABLA_MICBIAS3; + break; + case TABLA_1_A_MICB_4_CTL: + case TABLA_2_A_MICB_4_CTL: + micb_int_reg = tabla->reg_addr.micb_4_int_rbias; + cfilt_sel_val = tabla->pdata->micbias.bias4_cfilt_sel; + micb_line = TABLA_MICBIAS4; + break; + default: + pr_err("%s: Error, invalid micbias register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Decide whether to switch the micbias for MBHC */ + if (w->reg == tabla->mbhc_bias_regs.ctl_reg) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 0); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + + snd_soc_update_bits(codec, w->reg, 0x0E, 0x0A); + tabla_codec_update_cfilt_usage(codec, cfilt_sel_val, 1); + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0xE0, 0xE0); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x1C, 0x1C); + else if (strnstr(w->name, internal3_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x3, 0x3); + + break; + case SND_SOC_DAPM_POST_PMU: + + usleep_range(20000, 20000); + + if (tabla->mbhc_polling_active && + tabla->mbhc_cfg.micbias == micb_line) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_pause_hs_polling(codec); + tabla_codec_start_hs_polling(codec); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + break; + + case SND_SOC_DAPM_POST_PMD: + if ((w->reg == tabla->mbhc_bias_regs.ctl_reg) && + tabla_is_hph_pa_on(codec)) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 1); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x80, 0x00); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x10, 0x00); + else if (strnstr(w->name, internal3_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x2, 0x0); + + tabla_codec_update_cfilt_usage(codec, cfilt_sel_val, 0); + break; + } + + return 0; +} + + +static void tx_hpf_corner_freq_callback(struct work_struct *work) +{ + struct delayed_work *hpf_delayed_work; + struct hpf_work *hpf_work; + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + u16 tx_mux_ctl_reg; + u8 hpf_cut_of_freq; + + hpf_delayed_work = to_delayed_work(work); + hpf_work = container_of(hpf_delayed_work, struct hpf_work, dwork); + tabla = hpf_work->tabla; + codec = hpf_work->tabla->codec; + hpf_cut_of_freq = hpf_work->tx_hpf_cut_of_freq; + + tx_mux_ctl_reg = TABLA_A_CDC_TX1_MUX_CTL + + (hpf_work->decimator - 1) * 8; + + pr_debug("%s(): decimator %u hpf_cut_of_freq 0x%x\n", __func__, + hpf_work->decimator, (unsigned int)hpf_cut_of_freq); + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x30, hpf_cut_of_freq << 4); +} + +#define TX_MUX_CTL_CUT_OFF_FREQ_MASK 0x30 +#define CF_MIN_3DB_4HZ 0x0 +#define CF_MIN_3DB_75HZ 0x1 +#define CF_MIN_3DB_150HZ 0x2 + +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; + unsigned int decimator; + char *dec_name = NULL; + char *widget_name = NULL; + char *temp; + int ret = 0; + u16 dec_reset_reg, tx_vol_ctl_reg, tx_mux_ctl_reg; + u8 dec_hpf_cut_of_freq; + int offset; + + + pr_debug("%s %d\n", __func__, event); + + widget_name = kstrndup(w->name, 15, GFP_KERNEL); + if (!widget_name) + return -ENOMEM; + temp = widget_name; + + dec_name = strsep(&widget_name, " "); + widget_name = temp; + if (!dec_name) { + pr_err("%s: Invalid decimator = %s\n", __func__, w->name); + ret = -EINVAL; + goto out; + } + + ret = kstrtouint(strpbrk(dec_name, "123456789"), 10, &decimator); + if (ret < 0) { + pr_err("%s: Invalid decimator = %s\n", __func__, dec_name); + ret = -EINVAL; + goto out; + } + + pr_debug("%s(): widget = %s dec_name = %s decimator = %u\n", __func__, + w->name, dec_name, decimator); + + if (w->reg == TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL) { + dec_reset_reg = TABLA_A_CDC_CLK_TX_RESET_B1_CTL; + offset = 0; + } else if (w->reg == TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL) { + dec_reset_reg = TABLA_A_CDC_CLK_TX_RESET_B2_CTL; + offset = 8; + } else { + pr_err("%s: Error, incorrect dec\n", __func__); + return -EINVAL; + } + + tx_vol_ctl_reg = TABLA_A_CDC_TX1_VOL_CTL_CFG + 8 * (decimator -1); + tx_mux_ctl_reg = TABLA_A_CDC_TX1_MUX_CTL + 8 * (decimator - 1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + // Enableable TX digital mute */ + snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x01, 0x01); + + 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); + + dec_hpf_cut_of_freq = snd_soc_read(codec, tx_mux_ctl_reg); + + dec_hpf_cut_of_freq = (dec_hpf_cut_of_freq & 0x30) >> 4; + + tx_hpf_work[decimator - 1].tx_hpf_cut_of_freq = + dec_hpf_cut_of_freq; + + if ((dec_hpf_cut_of_freq != CF_MIN_3DB_150HZ)) { + + /* set cut of freq to CF_MIN_3DB_150HZ (0x1); */ + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x30, + CF_MIN_3DB_150HZ << 4); + } + + /* enable HPF */ + snd_soc_update_bits(codec, tx_mux_ctl_reg , 0x08, 0x00); + + break; + + case SND_SOC_DAPM_POST_PMU: + + /* Disable TX digital mute */ + snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x01, 0x00); + + if (tx_hpf_work[decimator - 1].tx_hpf_cut_of_freq != + CF_MIN_3DB_150HZ) { + + schedule_delayed_work(&tx_hpf_work[decimator - 1].dwork, + msecs_to_jiffies(300)); + } + /* apply the digital gain after the decimator is enabled*/ + if ((w->shift) < ARRAY_SIZE(rx_digital_gain_reg)) + snd_soc_write(codec, + tx_digital_gain_reg[w->shift + offset], + snd_soc_read(codec, + tx_digital_gain_reg[w->shift + offset]) + ); + + break; + + case SND_SOC_DAPM_PRE_PMD: + + snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x01, 0x01); + cancel_delayed_work_sync(&tx_hpf_work[decimator - 1].dwork); + break; + + case SND_SOC_DAPM_POST_PMD: + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x08, 0x08); + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x30, + (tx_hpf_work[decimator - 1].tx_hpf_cut_of_freq) << 4); + + break; + } +out: + kfree(widget_name); + return ret; +} + +static int tabla_codec_reset_interpolator(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d %s\n", __func__, event, w->name); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 1 << w->shift); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 0x0); + break; + case SND_SOC_DAPM_POST_PMU: + /* apply the digital gain after the interpolator is enabled*/ + if ((w->shift) < ARRAY_SIZE(rx_digital_gain_reg)) + snd_soc_write(codec, + rx_digital_gain_reg[w->shift], + snd_soc_read(codec, + rx_digital_gain_reg[w->shift]) + ); + break; + } + return 0; +} + +static int tabla_codec_enable_ldo_h(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + usleep_range(1000, 1000); + break; + } + return 0; +} + +static int tabla_codec_enable_rx_bias(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: + tabla_enable_rx_bias(codec, 1); + break; + case SND_SOC_DAPM_POST_PMD: + tabla_enable_rx_bias(codec, 0); + break; + } + return 0; +} +static int tabla_hphr_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static void tabla_snd_soc_jack_report(struct tabla_priv *tabla, + struct snd_soc_jack *jack, int status, + int mask) +{ + /* XXX: wake_lock_timeout()? */ + snd_soc_jack_report_no_dapm(jack, status, mask); +} + +static void hphocp_off_report(struct tabla_priv *tabla, + u32 jack_status, int irq) +{ + struct snd_soc_codec *codec; + if (!tabla) { + pr_err("%s: Bad tabla private data\n", __func__); + return; + } + + pr_debug("%s: clear ocp status %x\n", __func__, jack_status); + codec = tabla->codec; + if (tabla->hph_status & jack_status) { + tabla->hph_status &= ~jack_status; + if (tabla->mbhc_cfg.headset_jack) + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x10); + /* reset retry counter as PA is turned off signifying + * start of new OCP detection session + */ + if (TABLA_IRQ_HPH_PA_OCPL_FAULT) + tabla->hphlocp_cnt = 0; + else + tabla->hphrocp_cnt = 0; + wcd9xxx_enable_irq(codec->control_data, irq); + } +} + +static void hphlocp_off_report(struct work_struct *work) +{ + struct tabla_priv *tabla = container_of(work, struct tabla_priv, + hphlocp_work); + hphocp_off_report(tabla, SND_JACK_OC_HPHL, TABLA_IRQ_HPH_PA_OCPL_FAULT); +} + +static void hphrocp_off_report(struct work_struct *work) +{ + struct tabla_priv *tabla = container_of(work, struct tabla_priv, + hphrocp_work); + hphocp_off_report(tabla, SND_JACK_OC_HPHR, TABLA_IRQ_HPH_PA_OCPR_FAULT); +} + +static int tabla_hph_pa_event(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); + u8 mbhc_micb_ctl_val; + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mbhc_micb_ctl_val = snd_soc_read(codec, + tabla->mbhc_bias_regs.ctl_reg); + + if (!(mbhc_micb_ctl_val & 0x80)) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 1); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + break; + + case SND_SOC_DAPM_POST_PMD: + /* schedule work is required because at the time HPH PA DAPM + * event callback is called by DAPM framework, CODEC dapm mutex + * would have been locked while snd_soc_jack_report also + * attempts to acquire same lock. + */ + if (w->shift == 5) { + clear_bit(TABLA_HPHL_PA_OFF_ACK, + &tabla->hph_pa_dac_state); + clear_bit(TABLA_HPHL_DAC_OFF_ACK, + &tabla->hph_pa_dac_state); + if (tabla->hph_status & SND_JACK_OC_HPHL) + schedule_work(&tabla->hphlocp_work); + } else if (w->shift == 4) { + clear_bit(TABLA_HPHR_PA_OFF_ACK, + &tabla->hph_pa_dac_state); + clear_bit(TABLA_HPHR_DAC_OFF_ACK, + &tabla->hph_pa_dac_state); + if (tabla->hph_status & SND_JACK_OC_HPHR) + schedule_work(&tabla->hphrocp_work); + } + + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 0); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + + pr_debug("%s: sleep 10 ms after %s PA disable.\n", __func__, + w->name); + usleep_range(10000, 10000); + break; + } + return 0; +} + +static void tabla_get_mbhc_micbias_regs(struct snd_soc_codec *codec, + struct mbhc_micbias_regs *micbias_regs) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + unsigned int cfilt; + + switch (tabla->mbhc_cfg.micbias) { + case TABLA_MICBIAS1: + cfilt = tabla->pdata->micbias.bias1_cfilt_sel; + micbias_regs->mbhc_reg = TABLA_A_MICB_1_MBHC; + micbias_regs->int_rbias = TABLA_A_MICB_1_INT_RBIAS; + micbias_regs->ctl_reg = TABLA_A_MICB_1_CTL; + break; + case TABLA_MICBIAS2: + cfilt = tabla->pdata->micbias.bias2_cfilt_sel; + micbias_regs->mbhc_reg = TABLA_A_MICB_2_MBHC; + micbias_regs->int_rbias = TABLA_A_MICB_2_INT_RBIAS; + micbias_regs->ctl_reg = TABLA_A_MICB_2_CTL; + break; + case TABLA_MICBIAS3: + cfilt = tabla->pdata->micbias.bias3_cfilt_sel; + micbias_regs->mbhc_reg = TABLA_A_MICB_3_MBHC; + micbias_regs->int_rbias = TABLA_A_MICB_3_INT_RBIAS; + micbias_regs->ctl_reg = TABLA_A_MICB_3_CTL; + break; + case TABLA_MICBIAS4: + cfilt = tabla->pdata->micbias.bias4_cfilt_sel; + micbias_regs->mbhc_reg = tabla->reg_addr.micb_4_mbhc; + micbias_regs->int_rbias = tabla->reg_addr.micb_4_int_rbias; + micbias_regs->ctl_reg = tabla->reg_addr.micb_4_ctl; + break; + default: + /* Should never reach here */ + pr_err("%s: Invalid MIC BIAS for MBHC\n", __func__); + return; + } + + micbias_regs->cfilt_sel = cfilt; + + switch (cfilt) { + case TABLA_CFILT1_SEL: + micbias_regs->cfilt_val = TABLA_A_MICB_CFILT_1_VAL; + micbias_regs->cfilt_ctl = TABLA_A_MICB_CFILT_1_CTL; + tabla->mbhc_data.micb_mv = tabla->pdata->micbias.cfilt1_mv; + break; + case TABLA_CFILT2_SEL: + micbias_regs->cfilt_val = TABLA_A_MICB_CFILT_2_VAL; + micbias_regs->cfilt_ctl = TABLA_A_MICB_CFILT_2_CTL; + tabla->mbhc_data.micb_mv = tabla->pdata->micbias.cfilt2_mv; + break; + case TABLA_CFILT3_SEL: + micbias_regs->cfilt_val = TABLA_A_MICB_CFILT_3_VAL; + micbias_regs->cfilt_ctl = TABLA_A_MICB_CFILT_3_CTL; + tabla->mbhc_data.micb_mv = tabla->pdata->micbias.cfilt3_mv; + break; + } +} +static const struct snd_soc_dapm_widget tabla_dapm_i2s_widgets[] = { + SND_SOC_DAPM_SUPPLY("RX_I2S_CLK", TABLA_A_CDC_CLK_RX_I2S_CTL, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TX_I2S_CLK", TABLA_A_CDC_CLK_TX_I2S_CTL, 4, + 0, NULL, 0), +}; + +static int tabla_lineout_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget tabla_1_x_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS_E("MIC BIAS4 External", TABLA_1_A_MICB_4_CTL, 7, + 0, tabla_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_widget tabla_2_higher_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS_E("MIC BIAS4 External", TABLA_2_A_MICB_4_CTL, 7, + 0, tabla_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route audio_i2s_map[] = { + {"RX_I2S_CLK", NULL, "CDC_CONN"}, + {"SLIM RX1", NULL, "RX_I2S_CLK"}, + {"SLIM RX2", NULL, "RX_I2S_CLK"}, + {"SLIM RX3", NULL, "RX_I2S_CLK"}, + {"SLIM RX4", NULL, "RX_I2S_CLK"}, + + {"SLIM TX7", NULL, "TX_I2S_CLK"}, + {"SLIM TX8", NULL, "TX_I2S_CLK"}, + {"SLIM TX9", NULL, "TX_I2S_CLK"}, + {"SLIM TX10", NULL, "TX_I2S_CLK"}, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* SLIMBUS Connections */ + + {"SLIM TX1", NULL, "SLIM TX1 MUX"}, + {"SLIM TX1 MUX", "DEC1", "DEC1 MUX"}, + + {"SLIM TX2", NULL, "SLIM TX2 MUX"}, + {"SLIM TX2 MUX", "DEC2", "DEC2 MUX"}, + + {"SLIM TX3", NULL, "SLIM TX3 MUX"}, + {"SLIM TX3 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX3 MUX", "RMIX1", "RX1 MIX1"}, + {"SLIM TX3 MUX", "RMIX2", "RX2 MIX1"}, + {"SLIM TX3 MUX", "RMIX3", "RX3 MIX1"}, + {"SLIM TX3 MUX", "RMIX4", "RX4 MIX1"}, + {"SLIM TX3 MUX", "RMIX5", "RX5 MIX1"}, + {"SLIM TX3 MUX", "RMIX6", "RX6 MIX1"}, + {"SLIM TX3 MUX", "RMIX7", "RX7 MIX1"}, + + {"SLIM TX4", NULL, "SLIM TX4 MUX"}, + {"SLIM TX4 MUX", "DEC4", "DEC4 MUX"}, + + {"SLIM TX5", NULL, "SLIM TX5 MUX"}, + {"SLIM TX5 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX5 MUX", "RMIX1", "RX1 MIX1"}, + {"SLIM TX5 MUX", "RMIX2", "RX2 MIX1"}, + {"SLIM TX5 MUX", "RMIX3", "RX3 MIX1"}, + {"SLIM TX5 MUX", "RMIX4", "RX4 MIX1"}, + {"SLIM TX5 MUX", "RMIX5", "RX5 MIX1"}, + {"SLIM TX5 MUX", "RMIX6", "RX6 MIX1"}, + {"SLIM TX5 MUX", "RMIX7", "RX7 MIX1"}, + + {"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", "DEC2", "DEC2 MUX"}, + {"SLIM TX7 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX7 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX7 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX7 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX7 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX7 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX7 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX7 MUX", "DEC10", "DEC10 MUX"}, + {"SLIM TX7 MUX", "RMIX1", "RX1 MIX1"}, + {"SLIM TX7 MUX", "RMIX2", "RX2 MIX1"}, + {"SLIM TX7 MUX", "RMIX3", "RX3 MIX1"}, + {"SLIM TX7 MUX", "RMIX4", "RX4 MIX1"}, + {"SLIM TX7 MUX", "RMIX5", "RX5 MIX1"}, + {"SLIM TX7 MUX", "RMIX6", "RX6 MIX1"}, + {"SLIM TX7 MUX", "RMIX7", "RX7 MIX1"}, + + {"SLIM TX8", NULL, "SLIM TX8 MUX"}, + {"SLIM TX8 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX8 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX8 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX8 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX8 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX8 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX8 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX8 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX8 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX8 MUX", "DEC10", "DEC10 MUX"}, + + {"SLIM TX9", NULL, "SLIM TX9 MUX"}, + {"SLIM TX9 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX9 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX9 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX9 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX9 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX9 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX9 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX9 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX9 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX9 MUX", "DEC10", "DEC10 MUX"}, + + {"SLIM TX10", NULL, "SLIM TX10 MUX"}, + {"SLIM TX10 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX10 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX10 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX10 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX10 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX10 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX10 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX10 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX10 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX10 MUX", "DEC10", "DEC10 MUX"}, + + /* Earpiece (RX MIX1) */ + {"EAR", NULL, "EAR PA"}, + {"EAR PA", NULL, "EAR_PA_MIXER"}, + {"EAR_PA_MIXER", NULL, "DAC1"}, + {"DAC1", NULL, "CP"}, + + {"ANC1 FB MUX", "EAR_HPH_L", "RX1 MIX2"}, + {"ANC1 FB MUX", "EAR_LINE_1", "RX2 MIX2"}, + {"ANC", NULL, "ANC1 FB MUX"}, + + /* Headset (RX MIX1 and RX MIX2) */ + {"HEADPHONE", NULL, "HPHL"}, + {"HEADPHONE", NULL, "HPHR"}, + + {"HPHL", NULL, "HPHL_PA_MIXER"}, + {"HPHL_PA_MIXER", NULL, "HPHL DAC"}, + + {"HPHR", NULL, "HPHR_PA_MIXER"}, + {"HPHR_PA_MIXER", NULL, "HPHR DAC"}, + + {"HPHL DAC", NULL, "CP"}, + {"HPHR DAC", NULL, "CP"}, + + {"ANC", NULL, "ANC1 MUX"}, + {"ANC", NULL, "ANC2 MUX"}, + {"ANC1 MUX", "ADC1", "ADC1"}, + {"ANC1 MUX", "ADC2", "ADC2"}, + {"ANC1 MUX", "ADC3", "ADC3"}, + {"ANC1 MUX", "ADC4", "ADC4"}, + {"ANC2 MUX", "ADC1", "ADC1"}, + {"ANC2 MUX", "ADC2", "ADC2"}, + {"ANC2 MUX", "ADC3", "ADC3"}, + {"ANC2 MUX", "ADC4", "ADC4"}, + + {"ANC", NULL, "CDC_CONN"}, + + {"DAC1", "Switch", "RX1 CHAIN"}, + {"HPHL DAC", "Switch", "RX1 CHAIN"}, + {"HPHR DAC", NULL, "RX2 CHAIN"}, + + {"LINEOUT1", NULL, "LINEOUT1 PA"}, + {"LINEOUT2", NULL, "LINEOUT2 PA"}, + {"LINEOUT3", NULL, "LINEOUT3 PA"}, + {"LINEOUT4", NULL, "LINEOUT4 PA"}, + {"LINEOUT5", NULL, "LINEOUT5 PA"}, + + {"LINEOUT1 PA", NULL, "LINEOUT1_PA_MIXER"}, + {"LINEOUT1_PA_MIXER", NULL, "LINEOUT1 DAC"}, + {"LINEOUT2 PA", NULL, "LINEOUT2_PA_MIXER"}, + {"LINEOUT2_PA_MIXER", NULL, "LINEOUT2 DAC"}, + {"LINEOUT3 PA", NULL, "LINEOUT3_PA_MIXER"}, + {"LINEOUT3_PA_MIXER", NULL, "LINEOUT3 DAC"}, + {"LINEOUT4 PA", NULL, "LINEOUT4_PA_MIXER"}, + {"LINEOUT4_PA_MIXER", NULL, "LINEOUT4 DAC"}, + {"LINEOUT5 PA", NULL, "LINEOUT5_PA_MIXER"}, + {"LINEOUT5_PA_MIXER", NULL, "LINEOUT5 DAC"}, + + {"LINEOUT1 DAC", NULL, "RX3 MIX2"}, + {"LINEOUT5 DAC", NULL, "RX7 MIX1"}, + + {"RX1 CHAIN", NULL, "RX1 MIX2"}, + {"RX2 CHAIN", NULL, "RX2 MIX2"}, + {"RX1 CHAIN", NULL, "ANC"}, + {"RX2 CHAIN", NULL, "ANC"}, + + {"CP", NULL, "RX_BIAS"}, + {"LINEOUT1 DAC", NULL, "RX_BIAS"}, + {"LINEOUT2 DAC", NULL, "RX_BIAS"}, + {"LINEOUT3 DAC", NULL, "RX_BIAS"}, + {"LINEOUT4 DAC", NULL, "RX_BIAS"}, + {"LINEOUT5 DAC", NULL, "RX_BIAS"}, + + {"RX1 MIX1", NULL, "COMP1_CLK"}, + {"RX2 MIX1", NULL, "COMP1_CLK"}, + {"RX3 MIX1", NULL, "COMP2_CLK"}, + {"RX5 MIX1", NULL, "COMP2_CLK"}, + + + {"RX1 MIX1", NULL, "RX1 MIX1 INP1"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP2"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP3"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP1"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP2"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP1"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP2"}, + {"RX4 MIX1", NULL, "RX4 MIX1 INP1"}, + {"RX4 MIX1", NULL, "RX4 MIX1 INP2"}, + {"RX5 MIX1", NULL, "RX5 MIX1 INP1"}, + {"RX5 MIX1", NULL, "RX5 MIX1 INP2"}, + {"RX6 MIX1", NULL, "RX6 MIX1 INP1"}, + {"RX6 MIX1", NULL, "RX6 MIX1 INP2"}, + {"RX7 MIX1", NULL, "RX7 MIX1 INP1"}, + {"RX7 MIX1", NULL, "RX7 MIX1 INP2"}, + {"RX1 MIX2", NULL, "RX1 MIX1"}, + {"RX1 MIX2", NULL, "RX1 MIX2 INP1"}, + {"RX1 MIX2", NULL, "RX1 MIX2 INP2"}, + {"RX2 MIX2", NULL, "RX2 MIX1"}, + {"RX2 MIX2", NULL, "RX2 MIX2 INP1"}, + {"RX2 MIX2", NULL, "RX2 MIX2 INP2"}, + {"RX3 MIX2", NULL, "RX3 MIX1"}, + {"RX3 MIX2", NULL, "RX3 MIX2 INP1"}, + {"RX3 MIX2", NULL, "RX3 MIX2 INP2"}, + + {"RX1 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX1 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX1 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX1 MIX1 INP1", "IIR1", "IIR1"}, + {"RX1 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX1 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX1 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX1 MIX1 INP2", "IIR1", "IIR1"}, + {"RX1 MIX1 INP3", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP3", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP3", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP3", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP3", "RX5", "SLIM RX5"}, + {"RX1 MIX1 INP3", "RX6", "SLIM RX6"}, + {"RX1 MIX1 INP3", "RX7", "SLIM RX7"}, + {"RX2 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX2 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX2 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX2 MIX1 INP1", "IIR1", "IIR1"}, + {"RX2 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX2 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX2 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX2 MIX1 INP2", "IIR1", "IIR1"}, + {"RX3 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX3 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX3 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX3 MIX1 INP1", "IIR1", "IIR1"}, + {"RX3 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX3 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX3 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX3 MIX1 INP2", "IIR1", "IIR1"}, + {"RX4 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX4 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX4 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX4 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX4 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX4 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX4 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX4 MIX1 INP1", "IIR1", "IIR1"}, + {"RX4 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX4 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX4 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX4 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX4 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX4 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX4 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX4 MIX1 INP2", "IIR1", "IIR1"}, + {"RX5 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX5 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX5 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX5 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX5 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX5 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX5 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX5 MIX1 INP1", "IIR1", "IIR1"}, + {"RX5 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX5 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX5 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX5 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX5 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX5 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX5 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX5 MIX1 INP2", "IIR1", "IIR1"}, + {"RX6 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX6 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX6 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX6 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX6 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX6 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX6 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX6 MIX1 INP1", "IIR1", "IIR1"}, + {"RX6 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX6 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX6 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX6 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX6 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX6 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX6 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX6 MIX1 INP2", "IIR1", "IIR1"}, + {"RX7 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX7 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX7 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX7 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX7 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX7 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX7 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX7 MIX1 INP1", "IIR1", "IIR1"}, + {"RX7 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX7 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX7 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX7 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX7 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX7 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX7 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX7 MIX1 INP2", "IIR1", "IIR1"}, + {"RX1 MIX2 INP1", "IIR1", "IIR1"}, + {"RX1 MIX2 INP2", "IIR1", "IIR1"}, + {"RX2 MIX2 INP1", "IIR1", "IIR1"}, + {"RX2 MIX2 INP2", "IIR1", "IIR1"}, + {"RX3 MIX2 INP1", "IIR1", "IIR1"}, + {"RX3 MIX2 INP2", "IIR1", "IIR1"}, + + /* Decimator Inputs */ + {"DEC1 MUX", "DMIC1", "DMIC1"}, + {"DEC1 MUX", "ADC6", "ADC6"}, + {"DEC1 MUX", NULL, "CDC_CONN"}, + {"DEC2 MUX", "DMIC2", "DMIC2"}, + {"DEC2 MUX", "ADC5", "ADC5"}, + {"DEC2 MUX", NULL, "CDC_CONN"}, + {"DEC3 MUX", "DMIC3", "DMIC3"}, + {"DEC3 MUX", "ADC4", "ADC4"}, + {"DEC3 MUX", NULL, "CDC_CONN"}, + {"DEC4 MUX", "DMIC4", "DMIC4"}, + {"DEC4 MUX", "ADC3", "ADC3"}, + {"DEC4 MUX", NULL, "CDC_CONN"}, + {"DEC5 MUX", "DMIC5", "DMIC5"}, + {"DEC5 MUX", "ADC2", "ADC2"}, + {"DEC5 MUX", NULL, "CDC_CONN"}, + {"DEC6 MUX", "DMIC6", "DMIC6"}, + {"DEC6 MUX", "ADC1", "ADC1"}, + {"DEC6 MUX", NULL, "CDC_CONN"}, + {"DEC7 MUX", "DMIC1", "DMIC1"}, + {"DEC7 MUX", "DMIC6", "DMIC6"}, + {"DEC7 MUX", "ADC1", "ADC1"}, + {"DEC7 MUX", "ADC6", "ADC6"}, + {"DEC7 MUX", NULL, "CDC_CONN"}, + {"DEC8 MUX", "DMIC2", "DMIC2"}, + {"DEC8 MUX", "DMIC5", "DMIC5"}, + {"DEC8 MUX", "ADC2", "ADC2"}, + {"DEC8 MUX", "ADC5", "ADC5"}, + {"DEC8 MUX", NULL, "CDC_CONN"}, + {"DEC9 MUX", "DMIC4", "DMIC4"}, + {"DEC9 MUX", "DMIC5", "DMIC5"}, + {"DEC9 MUX", "ADC2", "ADC2"}, + {"DEC9 MUX", "ADC3", "ADC3"}, + {"DEC9 MUX", NULL, "CDC_CONN"}, + {"DEC10 MUX", "DMIC3", "DMIC3"}, + {"DEC10 MUX", "DMIC6", "DMIC6"}, + {"DEC10 MUX", "ADC1", "ADC1"}, + {"DEC10 MUX", "ADC4", "ADC4"}, + {"DEC10 MUX", NULL, "CDC_CONN"}, + + /* ADC Connections */ + {"ADC1", NULL, "AMIC1"}, + {"ADC2", NULL, "AMIC2"}, + {"ADC3", NULL, "AMIC3"}, + {"ADC4", NULL, "AMIC4"}, + {"ADC5", NULL, "AMIC5"}, + {"ADC6", NULL, "AMIC6"}, + + /* AUX PGA Connections */ + {"HPHL_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"HPHL_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"HPHL_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"HPHL_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"HPHR_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"HPHR_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"HPHR_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"HPHR_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"EAR_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"EAR_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"EAR_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"EAR_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"AUX_PGA_Left", NULL, "AMIC5"}, + {"AUX_PGA_Right", NULL, "AMIC6"}, + + {"IIR1", NULL, "IIR1 INP1 MUX"}, + {"IIR1 INP1 MUX", "DEC1", "DEC1 MUX"}, + {"IIR1 INP1 MUX", "DEC2", "DEC2 MUX"}, + {"IIR1 INP1 MUX", "DEC3", "DEC3 MUX"}, + {"IIR1 INP1 MUX", "DEC4", "DEC4 MUX"}, + {"IIR1 INP1 MUX", "DEC5", "DEC5 MUX"}, + {"IIR1 INP1 MUX", "DEC6", "DEC6 MUX"}, + {"IIR1 INP1 MUX", "DEC7", "DEC7 MUX"}, + {"IIR1 INP1 MUX", "DEC8", "DEC8 MUX"}, + {"IIR1 INP1 MUX", "DEC9", "DEC9 MUX"}, + {"IIR1 INP1 MUX", "DEC10", "DEC10 MUX"}, + + {"MIC BIAS1 Internal1", NULL, "LDO_H"}, + {"MIC BIAS1 Internal2", NULL, "LDO_H"}, + {"MIC BIAS1 External", NULL, "LDO_H"}, + {"MIC BIAS2 Internal1", NULL, "LDO_H"}, + {"MIC BIAS2 Internal2", NULL, "LDO_H"}, + {"MIC BIAS2 Internal3", NULL, "LDO_H"}, + {"MIC BIAS2 External", NULL, "LDO_H"}, + {"MIC BIAS3 Internal1", NULL, "LDO_H"}, + {"MIC BIAS3 Internal2", NULL, "LDO_H"}, + {"MIC BIAS3 External", NULL, "LDO_H"}, + {"MIC BIAS4 External", NULL, "LDO_H"}, +}; + +static const struct snd_soc_dapm_route tabla_1_x_lineout_2_to_4_map[] = { + + {"RX4 DSM MUX", "DSM_INV", "RX3 MIX2"}, + {"RX4 DSM MUX", "CIC_OUT", "RX4 MIX1"}, + + {"LINEOUT2 DAC", NULL, "RX4 DSM MUX"}, + + {"LINEOUT3 DAC", NULL, "RX5 MIX1"}, + {"LINEOUT3 DAC GROUND", "Switch", "RX3 MIX2"}, + {"LINEOUT3 DAC", NULL, "LINEOUT3 DAC GROUND"}, + + {"RX6 DSM MUX", "DSM_INV", "RX5 MIX1"}, + {"RX6 DSM MUX", "CIC_OUT", "RX6 MIX1"}, + + {"LINEOUT4 DAC", NULL, "RX6 DSM MUX"}, + {"LINEOUT4 DAC GROUND", "Switch", "RX4 DSM MUX"}, + {"LINEOUT4 DAC", NULL, "LINEOUT4 DAC GROUND"}, +}; + + +static const struct snd_soc_dapm_route tabla_2_x_lineout_2_to_4_map[] = { + + {"RX4 DSM MUX", "DSM_INV", "RX3 MIX2"}, + {"RX4 DSM MUX", "CIC_OUT", "RX4 MIX1"}, + + {"LINEOUT3 DAC", NULL, "RX4 DSM MUX"}, + + {"LINEOUT2 DAC", NULL, "RX5 MIX1"}, + + {"RX6 DSM MUX", "DSM_INV", "RX5 MIX1"}, + {"RX6 DSM MUX", "CIC_OUT", "RX6 MIX1"}, + + {"LINEOUT4 DAC", NULL, "RX6 DSM MUX"}, +}; + +static int tabla_readable(struct snd_soc_codec *ssc, unsigned int reg) +{ + int i; + struct wcd9xxx *tabla_core = dev_get_drvdata(ssc->dev->parent); + + if (TABLA_IS_1_X(tabla_core->version)) { + for (i = 0; i < ARRAY_SIZE(tabla_1_reg_readable); i++) { + if (tabla_1_reg_readable[i] == reg) + return 1; + } + } else { + for (i = 0; i < ARRAY_SIZE(tabla_2_reg_readable); i++) { + if (tabla_2_reg_readable[i] == reg) + return 1; + } + } + + return tabla_reg_readable[reg]; +} +static bool tabla_is_digital_gain_register(unsigned int reg) +{ + bool rtn = false; + switch (reg) { + case TABLA_A_CDC_RX1_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX2_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX3_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX4_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX5_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX6_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX7_VOL_CTL_B2_CTL: + case TABLA_A_CDC_TX1_VOL_CTL_GAIN: + case TABLA_A_CDC_TX2_VOL_CTL_GAIN: + case TABLA_A_CDC_TX3_VOL_CTL_GAIN: + case TABLA_A_CDC_TX4_VOL_CTL_GAIN: + case TABLA_A_CDC_TX5_VOL_CTL_GAIN: + case TABLA_A_CDC_TX6_VOL_CTL_GAIN: + case TABLA_A_CDC_TX7_VOL_CTL_GAIN: + case TABLA_A_CDC_TX8_VOL_CTL_GAIN: + case TABLA_A_CDC_TX9_VOL_CTL_GAIN: + case TABLA_A_CDC_TX10_VOL_CTL_GAIN: + rtn = true; + break; + default: + break; + } + return rtn; +} +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; + + /* IIR Coeff registers are not cacheable */ + if ((reg >= TABLA_A_CDC_IIR1_COEF_B1_CTL) && + (reg <= TABLA_A_CDC_IIR2_COEF_B5_CTL)) + return 1; + + /* Digital gain register is not cacheable so we have to write + * the setting even it is the same + */ + if (tabla_is_digital_gain_register(reg)) + return 1; + + /* HPH status registers */ + if (reg == TABLA_A_RX_HPH_L_STATUS || reg == TABLA_A_RX_HPH_R_STATUS) + 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; + BUG_ON(reg > TABLA_MAX_REGISTER); + + if (!tabla_volatile(codec, reg)) { + 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 wcd9xxx_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) { + ret = snd_soc_cache_read(codec, reg, &val); + if (ret >= 0) { + return val; + } else + dev_err(codec->dev, "Cache read from %x failed: %d\n", + reg, ret); + } + + val = wcd9xxx_reg_read(codec->control_data, reg); + return val; +} + +static s16 tabla_get_current_v_ins(struct tabla_priv *tabla, bool hu) +{ + s16 v_ins; + if ((tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) && + tabla->mbhc_micbias_switched) + v_ins = hu ? (s16)tabla->mbhc_data.adj_v_ins_hu : + (s16)tabla->mbhc_data.adj_v_ins_h; + else + v_ins = hu ? (s16)tabla->mbhc_data.v_ins_hu : + (s16)tabla->mbhc_data.v_ins_h; + return v_ins; +} + +static s16 tabla_get_current_v_hs_max(struct tabla_priv *tabla) +{ + s16 v_hs_max; + struct tabla_mbhc_plug_type_cfg *plug_type; + + plug_type = TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + if ((tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) && + tabla->mbhc_micbias_switched) + v_hs_max = tabla->mbhc_data.adj_v_hs_max; + else + v_hs_max = plug_type->v_hs_max; + return v_hs_max; +} + +static void tabla_codec_calibrate_hs_polling(struct snd_soc_codec *codec) +{ + u8 *n_ready, *n_cic; + struct tabla_mbhc_btn_detect_cfg *btn_det; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const s16 v_ins_hu = tabla_get_current_v_ins(tabla, true); + + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(tabla->mbhc_cfg.calibration); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + v_ins_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + (v_ins_hu >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL, + tabla->mbhc_data.v_b1_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B4_CTL, + (tabla->mbhc_data.v_b1_hu >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B5_CTL, + tabla->mbhc_data.v_b1_h & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B6_CTL, + (tabla->mbhc_data.v_b1_h >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B9_CTL, + tabla->mbhc_data.v_brh & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B10_CTL, + (tabla->mbhc_data.v_brh >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B11_CTL, + tabla->mbhc_data.v_brl & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B12_CTL, + (tabla->mbhc_data.v_brl >> 8) & 0xFF); + + n_ready = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_READY); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B1_CTL, + n_ready[tabla_codec_mclk_index(tabla)]); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B2_CTL, + tabla->mbhc_data.npoll); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B3_CTL, + tabla->mbhc_data.nbounce_wait); + n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B6_CTL, + n_cic[tabla_codec_mclk_index(tabla)]); +} + +static int tabla_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *tabla_core = dev_get_drvdata(dai->codec->dev->parent); + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + if ((tabla_core != NULL) && + (tabla_core->dev != NULL) && + (tabla_core->dev->parent != NULL)) + pm_runtime_get_sync(tabla_core->dev->parent); + + return 0; +} + +static void tabla_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *tabla_core = dev_get_drvdata(dai->codec->dev->parent); + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + if ((tabla_core != NULL) && + (tabla_core->dev != NULL) && + (tabla_core->dev->parent != NULL)) { + pm_runtime_mark_last_busy(tabla_core->dev->parent); + pm_runtime_put(tabla_core->dev->parent); + } +} + +int tabla_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, bool dapm) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: mclk_enable = %u, dapm = %d\n", __func__, mclk_enable, + dapm); + if (dapm) + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + if (mclk_enable) { + tabla->mclk_enabled = true; + + if (tabla->mbhc_polling_active) { + tabla_codec_pause_hs_polling(codec); + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + tabla_codec_calibrate_hs_polling(codec); + tabla_codec_start_hs_polling(codec); + } else { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + } + } else { + + if (!tabla->mclk_enabled) { + if (dapm) + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_err("Error, MCLK already diabled\n"); + return -EINVAL; + } + tabla->mclk_enabled = false; + + if (tabla->mbhc_polling_active) { + tabla_codec_pause_hs_polling(codec); + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_MBHC_MODE); + tabla_enable_rx_bias(codec, 1); + tabla_codec_enable_clock_block(codec, 1); + tabla_codec_calibrate_hs_polling(codec); + tabla_codec_start_hs_polling(codec); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, + 0x05, 0x01); + } else { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_OFF); + } + } + if (dapm) + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + 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) +{ + u8 val = 0; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(dai->codec); + + pr_debug("%s\n", __func__); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, + TABLA_I2S_MASTER_MODE_MASK, 0); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, + TABLA_I2S_MASTER_MODE_MASK, 0); + } + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + val = TABLA_I2S_MASTER_MODE_MASK; + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, val, val); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, val, val); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int tabla_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) + +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(dai->codec); + u32 i = 0; + if (!tx_slot && !rx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + pr_debug("%s(): dai_name = %s DAI-ID %x tx_ch %d rx_ch %d\n", + __func__, dai->name, dai->id, tx_num, rx_num); + + if (dai->id == AIF1_PB || dai->id == AIF2_PB || dai->id == AIF3_PB) { + for (i = 0; i < rx_num; i++) { + tabla->dai[dai->id - 1].ch_num[i] = rx_slot[i]; + tabla->dai[dai->id - 1].ch_act = 0; + tabla->dai[dai->id - 1].ch_tot = rx_num; + } + } else if (dai->id == AIF1_CAP || dai->id == AIF2_CAP || + dai->id == AIF3_CAP) { + for (i = 0; i < tx_num; i++) { + tabla->dai[dai->id - 1].ch_num[i] = tx_slot[i]; + tabla->dai[dai->id - 1].ch_act = 0; + tabla->dai[dai->id - 1].ch_tot = tx_num; + } + } + return 0; +} + +static int tabla_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) + +{ + struct wcd9xxx *tabla = dev_get_drvdata(dai->codec->control_data); + + u32 cnt = 0; + u32 tx_ch[SLIM_MAX_TX_PORTS]; + u32 rx_ch[SLIM_MAX_RX_PORTS]; + + if (!rx_slot && !tx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + + /* for virtual port, codec driver needs to do + * housekeeping, for now should be ok + */ + wcd9xxx_get_channel(tabla, rx_ch, tx_ch); + if (dai->id == AIF1_PB) { + *rx_num = tabla_dai[dai->id - 1].playback.channels_max; + while (cnt < *rx_num) { + rx_slot[cnt] = rx_ch[cnt]; + cnt++; + } + } else if (dai->id == AIF1_CAP) { + *tx_num = tabla_dai[dai->id - 1].capture.channels_max; + while (cnt < *tx_num) { + tx_slot[cnt] = tx_ch[6 + cnt]; + cnt++; + } + } else if (dai->id == AIF2_PB) { + *rx_num = tabla_dai[dai->id - 1].playback.channels_max; + while (cnt < *rx_num) { + rx_slot[cnt] = rx_ch[5 + cnt]; + cnt++; + } + } else if (dai->id == AIF2_CAP) { + *tx_num = tabla_dai[dai->id - 1].capture.channels_max; + tx_slot[0] = tx_ch[cnt]; + tx_slot[1] = tx_ch[1 + cnt]; + tx_slot[2] = tx_ch[5 + cnt]; + tx_slot[3] = tx_ch[3 + cnt]; + + } else if (dai->id == AIF3_PB) { + *rx_num = tabla_dai[dai->id - 1].playback.channels_max; + rx_slot[0] = rx_ch[3]; + rx_slot[1] = rx_ch[4]; + + } else if (dai->id == AIF3_CAP) { + *tx_num = tabla_dai[dai->id - 1].capture.channels_max; + tx_slot[cnt] = tx_ch[2 + cnt]; + tx_slot[cnt + 1] = tx_ch[4 + cnt]; + } + pr_debug("%s(): dai_name = %s DAI-ID %x tx_ch %d rx_ch %d\n", + __func__, dai->name, dai->id, *tx_num, *rx_num); + + + return 0; +} + +static int tabla_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(dai->codec); + u8 path, shift; + u16 tx_fs_reg, rx_fs_reg; + u8 tx_fs_rate, rx_fs_rate, rx_state, tx_state; + u32 compander_fs; + + pr_debug("%s: dai_name = %s DAI-ID %x rate %d num_ch %d\n", __func__, + dai->name, dai->id, params_rate(params), + params_channels(params)); + + switch (params_rate(params)) { + case 8000: + tx_fs_rate = 0x00; + rx_fs_rate = 0x00; + compander_fs = COMPANDER_FS_8KHZ; + break; + case 16000: + tx_fs_rate = 0x01; + rx_fs_rate = 0x20; + compander_fs = COMPANDER_FS_16KHZ; + break; + case 32000: + tx_fs_rate = 0x02; + rx_fs_rate = 0x40; + compander_fs = COMPANDER_FS_32KHZ; + break; + case 48000: + tx_fs_rate = 0x03; + rx_fs_rate = 0x60; + compander_fs = COMPANDER_FS_48KHZ; + break; + case 96000: + tx_fs_rate = 0x04; + rx_fs_rate = 0x80; + compander_fs = COMPANDER_FS_96KHZ; + break; + case 192000: + tx_fs_rate = 0x05; + rx_fs_rate = 0xA0; + compander_fs = COMPANDER_FS_192KHZ; + break; + default: + pr_err("%s: Invalid sampling rate %d\n", __func__, + params_rate(params)); + return -EINVAL; + } + + + /** + * If current dai is a tx dai, set sample rate to + * all the txfe paths that are currently not active + */ + if ((dai->id == AIF1_CAP) || (dai->id == AIF2_CAP) || + (dai->id == AIF3_CAP)) { + + tx_state = snd_soc_read(codec, + TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_DECIMATORS; path++, shift++) { + + if (path == BITS_PER_REG + 1) { + shift = 0; + tx_state = snd_soc_read(codec, + TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL); + } + + if (!(tx_state & (1 << shift))) { + tx_fs_reg = TABLA_A_CDC_TX1_CLK_FS_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, tx_fs_reg, + 0x07, tx_fs_rate); + } + } + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_TX_I2S_CTL, + 0x07, tx_fs_rate); + } else { + tabla->dai[dai->id - 1].rate = params_rate(params); + } + } + /** + * TODO: Need to handle case where same RX chain takes 2 or more inputs + * with varying sample rates + */ + + /** + * If current dai is a rx dai, set sample rate to + * all the rx paths that are currently not active + */ + if (dai->id == AIF1_PB || dai->id == AIF2_PB || dai->id == AIF3_PB) { + + rx_state = snd_soc_read(codec, + TABLA_A_CDC_CLK_RX_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_INTERPOLATORS; path++, shift++) { + + if (!(rx_state & (1 << shift))) { + rx_fs_reg = TABLA_A_CDC_RX1_B5_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, rx_fs_reg, + 0xE0, rx_fs_rate); + if (comp_rx_path[shift] < COMPANDER_MAX) + tabla->comp_fs[comp_rx_path[shift]] + = compander_fs; + } + } + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_I2S_CTL, + 0x03, (rx_fs_rate >> 0x05)); + } else { + tabla->dai[dai->id - 1].rate = params_rate(params); + } + } + + 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, + .set_channel_map = tabla_set_channel_map, + .get_channel_map = tabla_get_channel_map, +}; + +static struct snd_soc_dai_driver tabla_dai[] = { + { + .name = "tabla_rx1", + .id = AIF1_PB, + .playback = { + .stream_name = "AIF1 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx1", + .id = AIF1_CAP, + .capture = { + .stream_name = "AIF1 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_rx2", + .id = AIF2_PB, + .playback = { + .stream_name = "AIF2 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx2", + .id = AIF2_CAP, + .capture = { + .stream_name = "AIF2 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx3", + .id = AIF3_CAP, + .capture = { + .stream_name = "AIF3 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_rx3", + .id = AIF3_PB, + .playback = { + .stream_name = "AIF3 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, +}; + +static struct snd_soc_dai_driver tabla_i2s_dai[] = { + { + .name = "tabla_i2s_rx1", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_i2s_tx1", + .id = 2, + .capture = { + .stream_name = "AIF1 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, +}; + +static int tabla_codec_enable_slimrx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *tabla; + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla_p = snd_soc_codec_get_drvdata(codec); + u32 j = 0; + u32 ret = 0; + codec->control_data = dev_get_drvdata(codec->dev->parent); + tabla = codec->control_data; + /* Execute the callback only if interface type is slimbus */ + if (tabla_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + + pr_debug("%s: %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if ((tabla_dai[j].id == AIF1_CAP) || + (tabla_dai[j].id == AIF2_CAP) || + (tabla_dai[j].id == AIF3_CAP)) + continue; + if (!strncmp(w->sname, + tabla_dai[j].playback.stream_name, 13)) { + ++tabla_p->dai[j].ch_act; + break; + } + } + if (tabla_p->dai[j].ch_act == tabla_p->dai[j].ch_tot) + ret = wcd9xxx_cfg_slim_sch_rx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot, + tabla_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if ((tabla_dai[j].id == AIF1_CAP) || + (tabla_dai[j].id == AIF2_CAP) || + (tabla_dai[j].id == AIF3_CAP)) + continue; + if (!strncmp(w->sname, + tabla_dai[j].playback.stream_name, 13)) { + --tabla_p->dai[j].ch_act; + break; + } + } + if (!tabla_p->dai[j].ch_act) { + ret = wcd9xxx_close_slim_sch_rx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot); + usleep_range(15000, 15000); + tabla_p->dai[j].rate = 0; + memset(tabla_p->dai[j].ch_num, 0, (sizeof(u32)* + tabla_p->dai[j].ch_tot)); + tabla_p->dai[j].ch_tot = 0; + } + } + return ret; +} + +static int tabla_codec_enable_slimtx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *tabla; + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla_p = snd_soc_codec_get_drvdata(codec); + /* index to the DAI ID, for now hardcoding */ + u32 j = 0; + u32 ret = 0; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + tabla = codec->control_data; + + /* Execute the callback only if interface type is slimbus */ + if (tabla_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + + pr_debug("%s(): %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if (tabla_dai[j].id == AIF1_PB || + tabla_dai[j].id == AIF2_PB || + tabla_dai[j].id == AIF3_PB) + continue; + if (!strncmp(w->sname, + tabla_dai[j].capture.stream_name, 13)) { + ++tabla_p->dai[j].ch_act; + break; + } + } + if (tabla_p->dai[j].ch_act == tabla_p->dai[j].ch_tot) + ret = wcd9xxx_cfg_slim_sch_tx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot, + tabla_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if (tabla_dai[j].id == AIF1_PB || + tabla_dai[j].id == AIF2_PB || + tabla_dai[j].id == AIF3_PB) + continue; + if (!strncmp(w->sname, + tabla_dai[j].capture.stream_name, 13)) { + --tabla_p->dai[j].ch_act; + break; + } + } + if (!tabla_p->dai[j].ch_act) { + ret = wcd9xxx_close_slim_sch_tx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot); + tabla_p->dai[j].rate = 0; + memset(tabla_p->dai[j].ch_num, 0, (sizeof(u32)* + tabla_p->dai[j].ch_tot)); + tabla_p->dai[j].ch_tot = 0; + } + } + return ret; +} + +/* Todo: Have seperate dapm widgets for I2S and Slimbus. + * Might Need to have callbacks registered only for slimbus + */ +static const struct snd_soc_dapm_widget tabla_dapm_widgets[] = { + /*RX stuff */ + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_PGA("EAR PA", TABLA_A_RX_EAR_EN, 4, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("DAC1", TABLA_A_RX_EAR_EN, 6, 0, dac1_switch, + ARRAY_SIZE(dac1_switch)), + + SND_SOC_DAPM_AIF_IN_E("SLIM RX1", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX2", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX3", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_IN_E("SLIM RX4", "AIF3 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX5", "AIF3 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_IN_E("SLIM RX6", "AIF2 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX7", "AIF2 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Headphone */ + SND_SOC_DAPM_OUTPUT("HEADPHONE"), + SND_SOC_DAPM_PGA_E("HPHL", TABLA_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, + tabla_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("HPHL DAC", TABLA_A_RX_HPH_L_DAC_CTL, 7, 0, + hphl_switch, ARRAY_SIZE(hphl_switch)), + + SND_SOC_DAPM_PGA_E("HPHR", TABLA_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, + tabla_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC_E("HPHR DAC", NULL, TABLA_A_RX_HPH_R_DAC_CTL, 7, 0, + tabla_hphr_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Speaker */ + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + SND_SOC_DAPM_OUTPUT("LINEOUT3"), + SND_SOC_DAPM_OUTPUT("LINEOUT4"), + SND_SOC_DAPM_OUTPUT("LINEOUT5"), + + SND_SOC_DAPM_PGA_E("LINEOUT1 PA", 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_PGA_E("LINEOUT2 PA", TABLA_A_RX_LINE_CNP_EN, 1, 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_PGA_E("LINEOUT3 PA", 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_PGA_E("LINEOUT4 PA", TABLA_A_RX_LINE_CNP_EN, 3, 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_PGA_E("LINEOUT5 PA", TABLA_A_RX_LINE_CNP_EN, 4, 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_DAC_E("LINEOUT1 DAC", NULL, TABLA_A_RX_LINE_1_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("LINEOUT2 DAC", NULL, TABLA_A_RX_LINE_2_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("LINEOUT3 DAC", NULL, TABLA_A_RX_LINE_3_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LINEOUT3 DAC GROUND", SND_SOC_NOPM, 0, 0, + &lineout3_ground_switch), + SND_SOC_DAPM_DAC_E("LINEOUT4 DAC", NULL, TABLA_A_RX_LINE_4_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LINEOUT4 DAC GROUND", SND_SOC_NOPM, 0, 0, + &lineout4_ground_switch), + SND_SOC_DAPM_DAC_E("LINEOUT5 DAC", NULL, TABLA_A_RX_LINE_5_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("RX1 MIX2", TABLA_A_CDC_CLK_RX_B1_CTL, 0, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX2 MIX2", TABLA_A_CDC_CLK_RX_B1_CTL, 1, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX3 MIX2", TABLA_A_CDC_CLK_RX_B1_CTL, 2, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX4 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 3, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX5 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 4, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX6 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 5, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX7 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 6, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_MIXER("RX1 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX3 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX_E("RX4 DSM MUX", TABLA_A_CDC_CLK_RX_B1_CTL, 3, 0, + &rx4_dsm_mux, tabla_codec_reset_interpolator, + SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MUX_E("RX6 DSM MUX", TABLA_A_CDC_CLK_RX_B1_CTL, 5, 0, + &rx6_dsm_mux, tabla_codec_reset_interpolator, + SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MIXER("RX1 CHAIN", TABLA_A_CDC_RX1_B6_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 CHAIN", TABLA_A_CDC_RX2_B6_CTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MUX("RX1 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP3", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp3_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX4 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx4_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX4 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx4_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX5 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx5_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX5 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx5_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX6 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx6_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX6 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx6_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX7 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx7_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX7 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx7_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX1 MIX2 INP1", SND_SOC_NOPM, 0, 0, + &rx1_mix2_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX2 INP2", SND_SOC_NOPM, 0, 0, + &rx1_mix2_inp2_mux), + SND_SOC_DAPM_MUX("RX2 MIX2 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix2_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX2 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix2_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX2 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix2_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX2 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix2_inp2_mux), + + SND_SOC_DAPM_SUPPLY("CP", TABLA_A_CP_EN, 0, 0, + tabla_codec_enable_charge_pump, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_SUPPLY("RX_BIAS", SND_SOC_NOPM, 0, 0, + tabla_codec_enable_rx_bias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* TX */ + + SND_SOC_DAPM_SUPPLY("CDC_CONN", TABLA_A_CDC_CLK_OTHR_CTL, 2, 0, NULL, + 0), + + SND_SOC_DAPM_SUPPLY("LDO_H", TABLA_A_LDO_H_MODE_1, 7, 0, + tabla_codec_enable_ldo_h, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("COMP1_CLK", SND_SOC_NOPM, 0, 0, + tabla_config_compander, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_SUPPLY("COMP2_CLK", SND_SOC_NOPM, 1, 0, + tabla_config_compander, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD), + + 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_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal1", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal2", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_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_INPUT("AMIC3"), + SND_SOC_DAPM_ADC_E("ADC3", NULL, TABLA_A_TX_3_4_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_INPUT("AMIC4"), + SND_SOC_DAPM_ADC_E("ADC4", NULL, TABLA_A_TX_3_4_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_INPUT("AMIC5"), + SND_SOC_DAPM_ADC_E("ADC5", NULL, TABLA_A_TX_5_6_EN, 7, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_INPUT("AMIC6"), + SND_SOC_DAPM_ADC_E("ADC6", NULL, TABLA_A_TX_5_6_EN, 3, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_POST_PMU), + + 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_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC2 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 1, 0, + &dec2_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC3 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 2, 0, + &dec3_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC4 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 3, 0, + &dec4_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | 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_PMU | + SND_SOC_DAPM_PRE_PMD | 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_PMU | + SND_SOC_DAPM_PRE_PMD | 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_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC8 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 7, 0, + &dec8_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC9 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL, 0, 0, + &dec9_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC10 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL, 1, 0, + &dec10_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("ANC1 MUX", SND_SOC_NOPM, 0, 0, &anc1_mux), + SND_SOC_DAPM_MUX("ANC2 MUX", SND_SOC_NOPM, 0, 0, &anc2_mux), + + SND_SOC_DAPM_MIXER_E("ANC", SND_SOC_NOPM, 0, 0, NULL, 0, + tabla_codec_enable_anc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("ANC1 FB MUX", SND_SOC_NOPM, 0, 0, &anc1_fb_mux), + + 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_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal1", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal2", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal3", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_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_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 Internal1", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 Internal2", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_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_E("SLIM TX1", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX2 MUX", SND_SOC_NOPM, 0, 0, &sb_tx2_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX2", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX3 MUX", SND_SOC_NOPM, 0, 0, &sb_tx3_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX3", "AIF3 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX4 MUX", SND_SOC_NOPM, 0, 0, &sb_tx4_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX4", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX5 MUX", SND_SOC_NOPM, 0, 0, &sb_tx5_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX5", "AIF3 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX6 MUX", SND_SOC_NOPM, 0, 0, &sb_tx6_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX6", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX7 MUX", SND_SOC_NOPM, 0, 0, &sb_tx7_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX7", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX8 MUX", SND_SOC_NOPM, 0, 0, &sb_tx8_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX8", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX9 MUX", SND_SOC_NOPM, 0, 0, &sb_tx9_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX9", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX10 MUX", SND_SOC_NOPM, 0, 0, &sb_tx10_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX10", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Digital Mic Inputs */ + SND_SOC_DAPM_ADC_E("DMIC1", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC2", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC3", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC4", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC5", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC6", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* 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), + + /* AUX PGA */ + SND_SOC_DAPM_ADC_E("AUX_PGA_Left", NULL, TABLA_A_AUX_L_EN, 7, 0, + tabla_codec_enable_aux_pga, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("AUX_PGA_Right", NULL, TABLA_A_AUX_R_EN, 7, 0, + tabla_codec_enable_aux_pga, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD), + + /* Lineout, ear and HPH PA Mixers */ + SND_SOC_DAPM_MIXER("HPHL_PA_MIXER", SND_SOC_NOPM, 0, 0, + hphl_pa_mix, ARRAY_SIZE(hphl_pa_mix)), + + SND_SOC_DAPM_MIXER("HPHR_PA_MIXER", SND_SOC_NOPM, 0, 0, + hphr_pa_mix, ARRAY_SIZE(hphr_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT1_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout1_pa_mix, ARRAY_SIZE(lineout1_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT2_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout2_pa_mix, ARRAY_SIZE(lineout2_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT3_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout3_pa_mix, ARRAY_SIZE(lineout3_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT4_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout4_pa_mix, ARRAY_SIZE(lineout4_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT5_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout5_pa_mix, ARRAY_SIZE(lineout5_pa_mix)), + + SND_SOC_DAPM_MIXER("EAR_PA_MIXER", SND_SOC_NOPM, 0, 0, + ear_pa_mix, ARRAY_SIZE(ear_pa_mix)), +}; + +static short tabla_codec_read_sta_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B3_STATUS); + bias_lsb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B2_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static short tabla_codec_read_dce_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B5_STATUS); + bias_lsb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B4_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static void tabla_turn_onoff_rel_detection(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, on << 1); +} + +static short __tabla_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool override_bypass, bool noreldetection) +{ + short bias_value; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + if (noreldetection) + tabla_turn_onoff_rel_detection(codec, false); + + /* Turn on the override */ + if (!override_bypass) + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x4, 0x4); + if (dce) { + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x4); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(tabla->mbhc_data.t_sta_dce, + tabla->mbhc_data.t_sta_dce); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x4); + usleep_range(tabla->mbhc_data.t_dce, + tabla->mbhc_data.t_dce); + bias_value = tabla_codec_read_dce_result(codec); + } else { + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(tabla->mbhc_data.t_sta_dce, + tabla->mbhc_data.t_sta_dce); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x2); + usleep_range(tabla->mbhc_data.t_sta, + tabla->mbhc_data.t_sta); + bias_value = tabla_codec_read_sta_result(codec); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x0); + } + /* Turn off the override after measuring mic voltage */ + if (!override_bypass) + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + + if (noreldetection) + tabla_turn_onoff_rel_detection(codec, true); + wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + + return bias_value; +} + +static short tabla_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool norel) +{ + return __tabla_codec_sta_dce(codec, dce, false, norel); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static short tabla_codec_setup_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + short bias_value; + u8 cfilt_mode; + + pr_debug("%s: enter, mclk_enabled %d\n", __func__, tabla->mclk_enabled); + if (!tabla->mbhc_cfg.calibration) { + pr_err("Error, no tabla calibration\n"); + return -ENODEV; + } + + if (!tabla->mclk_enabled) { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_MBHC_MODE); + tabla_enable_rx_bias(codec, 1); + tabla_codec_enable_clock_block(codec, 1); + } + + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x01); + + /* Make sure CFILT is in fast mode, save current mode */ + cfilt_mode = snd_soc_read(codec, tabla->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x70, 0x00); + + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x1F, 0x16); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84); + + 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, 0x8, 0x8); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x00); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + + tabla_codec_calibrate_hs_polling(codec); + + /* don't flip override */ + bias_value = __tabla_codec_sta_dce(codec, 1, true, true); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00); + + return bias_value; +} + +static int tabla_cancel_btn_work(struct tabla_priv *tabla) +{ + int r = 0; + struct wcd9xxx *core = dev_get_drvdata(tabla->codec->dev->parent); + + if (cancel_delayed_work_sync(&tabla->mbhc_btn_dwork)) { + /* if scheduled mbhc_btn_dwork is canceled from here, + * we have to unlock from here instead btn_work */ + wcd9xxx_unlock_sleep(core); + r = 1; + } + return r; +} + +/* called under codec_resource_lock acquisition */ +void tabla_set_and_turnoff_hph_padac(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u8 wg_time; + + wg_time = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + /* If headphone PA is on, check if userspace receives + * removal event to sync-up PA's state */ + if (tabla_is_hph_pa_on(codec)) { + pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__); + set_bit(TABLA_HPHL_PA_OFF_ACK, &tabla->hph_pa_dac_state); + set_bit(TABLA_HPHR_PA_OFF_ACK, &tabla->hph_pa_dac_state); + } else { + pr_debug("%s PA is off\n", __func__); + } + + if (tabla_is_hph_dac_on(codec, 1)) + set_bit(TABLA_HPHL_DAC_OFF_ACK, &tabla->hph_pa_dac_state); + if (tabla_is_hph_dac_on(codec, 0)) + set_bit(TABLA_HPHR_DAC_OFF_ACK, &tabla->hph_pa_dac_state); + + snd_soc_update_bits(codec, TABLA_A_RX_HPH_CNP_EN, 0x30, 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_DAC_CTL, + 0xC0, 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_DAC_CTL, + 0xC0, 0x00); + usleep_range(wg_time * 1000, wg_time * 1000); +} + +static void tabla_clr_and_turnon_hph_padac(struct tabla_priv *tabla) +{ + bool pa_turned_on = false; + struct snd_soc_codec *codec = tabla->codec; + u8 wg_time; + + wg_time = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + if (test_and_clear_bit(TABLA_HPHR_DAC_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable DAC\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_R_DAC_CTL, + 0xC0, 0xC0); + } + if (test_and_clear_bit(TABLA_HPHL_DAC_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable DAC\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_L_DAC_CTL, + 0xC0, 0xC0); + } + + if (test_and_clear_bit(TABLA_HPHR_PA_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable PA\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_CNP_EN, 0x10, + 1 << 4); + pa_turned_on = true; + } + if (test_and_clear_bit(TABLA_HPHL_PA_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable PA\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_CNP_EN, 0x20, + 1 << 5); + pa_turned_on = true; + } + + if (pa_turned_on) { + pr_debug("%s: PA was turned off by MBHC and not by DAPM\n", + __func__); + usleep_range(wg_time * 1000, wg_time * 1000); + } +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_report_plug(struct snd_soc_codec *codec, int insertion, + enum snd_jack_types jack_type) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (!insertion) { + /* Report removal */ + tabla->hph_status &= ~jack_type; + if (tabla->mbhc_cfg.headset_jack) { + /* cancel possibly scheduled btn work and + * report release if we reported button press */ + if (tabla_cancel_btn_work(tabla)) { + pr_debug("%s: button press is canceled\n", + __func__); + } else if (tabla->buttons_pressed) { + pr_debug("%s: Reporting release for reported " + "button press %d\n", __func__, + jack_type); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.button_jack, 0, + tabla->buttons_pressed); + tabla->buttons_pressed &= + ~TABLA_JACK_BUTTON_MASK; + } + pr_debug("%s: Reporting removal %d(%x)\n", __func__, + jack_type, tabla->hph_status); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + tabla_set_and_turnoff_hph_padac(codec); + hphocp_off_report(tabla, SND_JACK_OC_HPHR, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + hphocp_off_report(tabla, SND_JACK_OC_HPHL, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + tabla->current_plug = PLUG_TYPE_NONE; + tabla->mbhc_polling_active = false; + } else { + /* Report insertion */ + tabla->hph_status |= jack_type; + + if (jack_type == SND_JACK_HEADPHONE) + tabla->current_plug = PLUG_TYPE_HEADPHONE; + else if (jack_type == SND_JACK_UNSUPPORTED) + tabla->current_plug = PLUG_TYPE_GND_MIC_SWAP; + else if (jack_type == SND_JACK_HEADSET) { + tabla->mbhc_polling_active = true; + tabla->current_plug = PLUG_TYPE_HEADSET; + } + if (tabla->mbhc_cfg.headset_jack) { + pr_debug("%s: Reporting insertion %d(%x)\n", __func__, + jack_type, tabla->hph_status); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + tabla_clr_and_turnon_hph_padac(tabla); + } +} + +static int tabla_codec_enable_hs_detect(struct snd_soc_codec *codec, + int insertion, int trigger, + bool padac_off) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + int central_bias_enabled = 0; + const struct tabla_mbhc_general_cfg *generic = + TABLA_MBHC_CAL_GENERAL_PTR(tabla->mbhc_cfg.calibration); + const struct tabla_mbhc_plug_detect_cfg *plug_det = + TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->mbhc_cfg.calibration); + + if (!tabla->mbhc_cfg.calibration) { + pr_err("Error, no tabla calibration\n"); + return -EINVAL; + } + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0); + + /* Make sure mic bias and Mic line schmitt trigger + * are turned OFF + */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x01); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + + if (insertion) { + tabla_codec_switch_micbias(codec, 0); + + /* DAPM can manipulate PA/DAC bits concurrently */ + if (padac_off == true) { + tabla_set_and_turnoff_hph_padac(codec); + } + + if (trigger & MBHC_USE_HPHL_TRIGGER) { + /* Enable HPH Schmitt Trigger */ + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x11, + 0x11); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x0C, + plug_det->hph_current << 2); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x02, + 0x02); + } + if (trigger & MBHC_USE_MB_TRIGGER) { + /* enable the mic line schmitt trigger */ + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x60, plug_det->mic_current << 5); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x10); + } + + /* setup for insetion detection */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0); + } else { + pr_debug("setup for removal detection\n"); + /* Make sure the HPH schmitt trigger is OFF */ + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x12, 0x00); + + /* enable the mic line schmitt trigger */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, + 0x01, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x60, + plug_det->mic_current << 5); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x10); + + /* Setup for low power removal detection */ + 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) { + /* called called by interrupt */ + if (!(tabla->clock_active)) { + tabla_codec_enable_config_mode(codec, 1); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, + 0x06, 0); + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + tabla_codec_enable_config_mode(codec, 0); + } else + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, + 0x06, 0); + } + + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.int_rbias, 0x80, 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(generic->t_bg_fast_settle, + generic->t_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(generic->t_ldoh, generic->t_ldoh); + 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, tabla->reg_addr.micb_4_mbhc, 0x3, + tabla->mbhc_cfg.micbias); + + wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0x1); + return 0; +} + +static u16 tabla_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce, + s16 vin_mv) +{ + struct tabla_priv *tabla; + s16 diff, zero; + u32 mb_mv, in; + u16 value; + + tabla = snd_soc_codec_get_drvdata(codec); + mb_mv = tabla->mbhc_data.micb_mv; + + if (mb_mv == 0) { + pr_err("%s: Mic Bias voltage is set to zero\n", __func__); + return -EINVAL; + } + + if (dce) { + diff = (tabla->mbhc_data.dce_mb) - (tabla->mbhc_data.dce_z); + zero = (tabla->mbhc_data.dce_z); + } else { + diff = (tabla->mbhc_data.sta_mb) - (tabla->mbhc_data.sta_z); + zero = (tabla->mbhc_data.sta_z); + } + in = (u32) diff * vin_mv; + + value = (u16) (in / mb_mv) + zero; + return value; +} + +static s32 tabla_codec_sta_dce_v(struct snd_soc_codec *codec, s8 dce, + u16 bias_value) +{ + struct tabla_priv *tabla; + s16 value, z, mb; + s32 mv; + + tabla = snd_soc_codec_get_drvdata(codec); + value = bias_value; + if (dce) { + z = (tabla->mbhc_data.dce_z); + mb = (tabla->mbhc_data.dce_mb); + mv = (value - z) * (s32)tabla->mbhc_data.micb_mv / (mb - z); + } else { + z = (tabla->mbhc_data.sta_z); + mb = (tabla->mbhc_data.sta_mb); + mv = (value - z) * (s32)tabla->mbhc_data.micb_mv / (mb - z); + } + + return mv; +} + +static void btn_lpress_fn(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct tabla_priv *tabla; + short bias_value; + int dce_mv, sta_mv; + struct wcd9xxx *core; + + pr_debug("%s:\n", __func__); + + delayed_work = to_delayed_work(work); + tabla = container_of(delayed_work, struct tabla_priv, mbhc_btn_dwork); + core = dev_get_drvdata(tabla->codec->dev->parent); + + if (tabla) { + if (tabla->mbhc_cfg.button_jack) { + bias_value = tabla_codec_read_sta_result(tabla->codec); + sta_mv = tabla_codec_sta_dce_v(tabla->codec, 0, + bias_value); + bias_value = tabla_codec_read_dce_result(tabla->codec); + dce_mv = tabla_codec_sta_dce_v(tabla->codec, 1, + bias_value); + pr_debug("%s: Reporting long button press event" + " STA: %d, DCE: %d\n", __func__, + sta_mv, dce_mv); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.button_jack, + tabla->buttons_pressed, + tabla->buttons_pressed); + } + } else { + pr_err("%s: Bad tabla private data\n", __func__); + } + + pr_debug("%s: leave\n", __func__); + wcd9xxx_unlock_sleep(core); +} + +void tabla_mbhc_cal(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla; + struct tabla_mbhc_btn_detect_cfg *btn_det; + u8 cfilt_mode, bg_mode; + u8 ncic, nmeas, navg; + u32 mclk_rate; + u32 dce_wait, sta_wait; + u8 *n_cic; + void *calibration; + + tabla = snd_soc_codec_get_drvdata(codec); + calibration = tabla->mbhc_cfg.calibration; + + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + tabla_turn_onoff_rel_detection(codec, false); + + /* First compute the DCE / STA wait times + * depending on tunable parameters. + * The value is computed in microseconds + */ + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(calibration); + n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC); + ncic = n_cic[tabla_codec_mclk_index(tabla)]; + nmeas = TABLA_MBHC_CAL_BTN_DET_PTR(calibration)->n_meas; + navg = TABLA_MBHC_CAL_GENERAL_PTR(calibration)->mbhc_navg; + mclk_rate = tabla->mbhc_cfg.mclk_rate; + dce_wait = (1000 * 512 * ncic * (nmeas + 1)) / (mclk_rate / 1000); + sta_wait = (1000 * 128 * (navg + 1)) / (mclk_rate / 1000); + + tabla->mbhc_data.t_dce = dce_wait; + tabla->mbhc_data.t_sta = sta_wait; + + /* LDOH and CFILT are already configured during pdata handling. + * Only need to make sure CFILT and bandgap are in Fast mode. + * Need to restore defaults once calculation is done. + */ + cfilt_mode = snd_soc_read(codec, tabla->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40, 0x00); + bg_mode = snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x02, + 0x02); + + /* Micbias, CFILT, LDOH, MBHC MUX mode settings + * to perform ADC calibration + */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x60, + tabla->mbhc_cfg.micbias << 5); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x60, 0x60); + snd_soc_write(codec, TABLA_A_TX_7_MBHC_TEST_CTL, 0x78); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x04); + + /* DCE measurement for 0 volts */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x04); + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(tabla->mbhc_data.t_dce, tabla->mbhc_data.t_dce); + tabla->mbhc_data.dce_z = tabla_codec_read_dce_result(codec); + + /* DCE measurment for MB voltage */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(tabla->mbhc_data.t_dce, tabla->mbhc_data.t_dce); + tabla->mbhc_data.dce_mb = tabla_codec_read_dce_result(codec); + + /* Sta measuremnt for 0 volts */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x02); + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(tabla->mbhc_data.t_sta, tabla->mbhc_data.t_sta); + tabla->mbhc_data.sta_z = tabla_codec_read_sta_result(codec); + + /* STA Measurement for MB Voltage */ + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(tabla->mbhc_data.t_sta, tabla->mbhc_data.t_sta); + tabla->mbhc_data.sta_mb = tabla_codec_read_sta_result(codec); + + /* Restore default settings. */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x02, bg_mode); + + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84); + usleep_range(100, 100); + + wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + tabla_turn_onoff_rel_detection(codec, true); +} + +void *tabla_mbhc_cal_btn_det_mp(const struct tabla_mbhc_btn_detect_cfg* btn_det, + const enum tabla_mbhc_btn_det_mem mem) +{ + void *ret = &btn_det->_v_btn_low; + + switch (mem) { + case TABLA_BTN_DET_GAIN: + ret += sizeof(btn_det->_n_cic); + case TABLA_BTN_DET_N_CIC: + ret += sizeof(btn_det->_n_ready); + case TABLA_BTN_DET_N_READY: + ret += sizeof(btn_det->_v_btn_high[0]) * btn_det->num_btn; + case TABLA_BTN_DET_V_BTN_HIGH: + ret += sizeof(btn_det->_v_btn_low[0]) * btn_det->num_btn; + case TABLA_BTN_DET_V_BTN_LOW: + /* do nothing */ + break; + default: + ret = NULL; + } + + return ret; +} + +static s16 tabla_scale_v_micb_vddio(struct tabla_priv *tabla, int v, + bool tovddio) +{ + int r; + int vddio_k, mb_k; + vddio_k = tabla_find_k_value(tabla->pdata->micbias.ldoh_v, + VDDIO_MICBIAS_MV); + mb_k = tabla_find_k_value(tabla->pdata->micbias.ldoh_v, + tabla->mbhc_data.micb_mv); + if (tovddio) + r = v * vddio_k / mb_k; + else + r = v * mb_k / vddio_k; + return r; +} + +static void tabla_mbhc_calc_thres(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla; + s16 btn_mv = 0, btn_delta_mv; + struct tabla_mbhc_btn_detect_cfg *btn_det; + struct tabla_mbhc_plug_type_cfg *plug_type; + u16 *btn_high; + u8 *n_ready; + int i; + + tabla = snd_soc_codec_get_drvdata(codec); + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(tabla->mbhc_cfg.calibration); + plug_type = TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + + n_ready = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_READY); + if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_12288KHZ) { + tabla->mbhc_data.npoll = 4; + tabla->mbhc_data.nbounce_wait = 30; + } else if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_9600KHZ) { + tabla->mbhc_data.npoll = 7; + tabla->mbhc_data.nbounce_wait = 23; + } + + tabla->mbhc_data.t_sta_dce = ((1000 * 256) / + (tabla->mbhc_cfg.mclk_rate / 1000) * + n_ready[tabla_codec_mclk_index(tabla)]) + + 10; + tabla->mbhc_data.v_ins_hu = + tabla_codec_v_sta_dce(codec, STA, plug_type->v_hs_max); + tabla->mbhc_data.v_ins_h = + tabla_codec_v_sta_dce(codec, DCE, plug_type->v_hs_max); + + tabla->mbhc_data.v_inval_ins_low = TABLA_MBHC_FAKE_INSERT_LOW; + if (tabla->mbhc_cfg.gpio) + tabla->mbhc_data.v_inval_ins_high = + TABLA_MBHC_FAKE_INSERT_HIGH; + else + tabla->mbhc_data.v_inval_ins_high = + TABLA_MBHC_FAKE_INS_HIGH_NO_GPIO; + + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + tabla->mbhc_data.adj_v_hs_max = + tabla_scale_v_micb_vddio(tabla, plug_type->v_hs_max, true); + tabla->mbhc_data.adj_v_ins_hu = + tabla_codec_v_sta_dce(codec, STA, + tabla->mbhc_data.adj_v_hs_max); + tabla->mbhc_data.adj_v_ins_h = + tabla_codec_v_sta_dce(codec, DCE, + tabla->mbhc_data.adj_v_hs_max); + tabla->mbhc_data.v_inval_ins_low = + tabla_scale_v_micb_vddio(tabla, + tabla->mbhc_data.v_inval_ins_low, + false); + tabla->mbhc_data.v_inval_ins_high = + tabla_scale_v_micb_vddio(tabla, + tabla->mbhc_data.v_inval_ins_high, + false); + } + + btn_high = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) + btn_mv = btn_high[i] > btn_mv ? btn_high[i] : btn_mv; + + tabla->mbhc_data.v_b1_h = tabla_codec_v_sta_dce(codec, DCE, btn_mv); + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_sta; + tabla->mbhc_data.v_b1_hu = + tabla_codec_v_sta_dce(codec, STA, btn_delta_mv); + + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_cic; + + tabla->mbhc_data.v_b1_huc = + tabla_codec_v_sta_dce(codec, DCE, btn_delta_mv); + + tabla->mbhc_data.v_brh = tabla->mbhc_data.v_b1_h; + tabla->mbhc_data.v_brl = TABLA_MBHC_BUTTON_MIN; + + tabla->mbhc_data.v_no_mic = + tabla_codec_v_sta_dce(codec, STA, plug_type->v_no_mic); +} + +void tabla_mbhc_init(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla; + struct tabla_mbhc_general_cfg *generic; + struct tabla_mbhc_btn_detect_cfg *btn_det; + int n; + u8 *n_cic, *gain; + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + + tabla = snd_soc_codec_get_drvdata(codec); + generic = TABLA_MBHC_CAL_GENERAL_PTR(tabla->mbhc_cfg.calibration); + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(tabla->mbhc_cfg.calibration); + + for (n = 0; n < 8; n++) { + if ((!TABLA_IS_1_X(tabla_core->version)) || n != 7) { + snd_soc_update_bits(codec, + TABLA_A_CDC_MBHC_FEATURE_B1_CFG, + 0x07, n); + snd_soc_write(codec, TABLA_A_CDC_MBHC_FEATURE_B2_CFG, + btn_det->c[n]); + } + } + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B2_CTL, 0x07, + btn_det->nc); + + n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B6_CTL, 0xFF, + n_cic[tabla_codec_mclk_index(tabla)]); + + gain = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_GAIN); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B2_CTL, 0x78, + gain[tabla_codec_mclk_index(tabla)] << 3); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B4_CTL, 0x70, + generic->mbhc_nsa << 4); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B4_CTL, 0x0F, + btn_det->n_meas); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B5_CTL, generic->mbhc_navg); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x80, 0x80); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x78, + btn_det->mbhc_nsc << 3); + + snd_soc_update_bits(codec, tabla->reg_addr.micb_4_mbhc, 0x03, + TABLA_MICBIAS2); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + + snd_soc_update_bits(codec, TABLA_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0); +} + +static bool tabla_mbhc_fw_validate(const struct firmware *fw) +{ + u32 cfg_offset; + struct tabla_mbhc_imped_detect_cfg *imped_cfg; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + + if (fw->size < TABLA_MBHC_CAL_MIN_SIZE) + return false; + + /* previous check guarantees that there is enough fw data up + * to num_btn + */ + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) btn_cfg - (void *) fw->data); + if (fw->size < (cfg_offset + TABLA_MBHC_CAL_BTN_SZ(btn_cfg))) + return false; + + /* previous check guarantees that there is enough fw data up + * to start of impedance detection configuration + */ + imped_cfg = TABLA_MBHC_CAL_IMPED_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) imped_cfg - (void *) fw->data); + + if (fw->size < (cfg_offset + TABLA_MBHC_CAL_IMPED_MIN_SZ)) + return false; + + if (fw->size < (cfg_offset + TABLA_MBHC_CAL_IMPED_SZ(imped_cfg))) + return false; + + return true; +} + +/* called under codec_resource_lock acquisition */ +static int tabla_determine_button(const struct tabla_priv *priv, + const s32 micmv) +{ + s16 *v_btn_low, *v_btn_high; + struct tabla_mbhc_btn_detect_cfg *btn_det; + int i, btn = -1; + + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + v_btn_low = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_V_BTN_LOW); + v_btn_high = tabla_mbhc_cal_btn_det_mp(btn_det, + TABLA_BTN_DET_V_BTN_HIGH); + + for (i = 0; i < btn_det->num_btn; i++) { + if ((v_btn_low[i] <= micmv) && (v_btn_high[i] >= micmv)) { + btn = i; + break; + } + } + + if (btn == -1) + pr_debug("%s: couldn't find button number for mic mv %d\n", + __func__, micmv); + + return btn; +} + +static int tabla_get_button_mask(const int btn) +{ + int mask = 0; + switch (btn) { + case 0: + mask = SND_JACK_BTN_0; + break; + case 1: + mask = SND_JACK_BTN_1; + break; + case 2: + mask = SND_JACK_BTN_2; + break; + case 3: + mask = SND_JACK_BTN_3; + break; + case 4: + mask = SND_JACK_BTN_4; + break; + case 5: + mask = SND_JACK_BTN_5; + break; + case 6: + mask = SND_JACK_BTN_6; + break; + case 7: + mask = SND_JACK_BTN_7; + break; + } + return mask; +} + +static irqreturn_t tabla_dce_handler(int irq, void *data) +{ + int i, mask; + short dce, sta; + s32 mv, mv_s, stamv_s; + bool vddio; + int btn = -1, meas = 0; + struct tabla_priv *priv = data; + const struct tabla_mbhc_btn_detect_cfg *d = + TABLA_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + short btnmeas[d->n_btn_meas + 1]; + struct snd_soc_codec *codec = priv->codec; + struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent); + int n_btn_meas = d->n_btn_meas; + u8 mbhc_status = snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_STATUS) & 0x3E; + + pr_debug("%s: enter\n", __func__); + + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + if (priv->mbhc_state == MBHC_STATE_POTENTIAL_RECOVERY) { + pr_debug("%s: mbhc is being recovered, skip button press\n", + __func__); + goto done; + } + + priv->mbhc_state = MBHC_STATE_POTENTIAL; + + if (!priv->mbhc_polling_active) { + pr_warn("%s: mbhc polling is not active, skip button press\n", + __func__); + goto done; + } + + dce = tabla_codec_read_dce_result(codec); + mv = tabla_codec_sta_dce_v(codec, 1, dce); + + /* If GPIO interrupt already kicked in, ignore button press */ + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO State Changed, ignore button press\n", + __func__); + btn = -1; + goto done; + } + + vddio = (priv->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + priv->mbhc_micbias_switched); + mv_s = vddio ? tabla_scale_v_micb_vddio(priv, mv, false) : mv; + + if (mbhc_status != TABLA_MBHC_STATUS_REL_DETECTION) { + if (priv->mbhc_last_resume && + !time_after(jiffies, priv->mbhc_last_resume + HZ)) { + pr_debug("%s: Button is already released shortly after " + "resume\n", __func__); + n_btn_meas = 0; + } else { + pr_debug("%s: Button is already released without " + "resume", __func__); + sta = tabla_codec_read_sta_result(codec); + stamv_s = tabla_codec_sta_dce_v(codec, 0, sta); + if (vddio) + stamv_s = tabla_scale_v_micb_vddio(priv, + stamv_s, + false); + btn = tabla_determine_button(priv, mv_s); + if (btn != tabla_determine_button(priv, stamv_s)) + btn = -1; + goto done; + } + } + + /* determine pressed button */ + btnmeas[meas++] = tabla_determine_button(priv, mv_s); + pr_debug("%s: meas %d - DCE %d,%d,%d button %d\n", __func__, + meas - 1, dce, mv, mv_s, btnmeas[meas - 1]); + if (n_btn_meas == 0) + btn = btnmeas[0]; + for (; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); meas++) { + dce = tabla_codec_sta_dce(codec, 1, false); + mv = tabla_codec_sta_dce_v(codec, 1, dce); + mv_s = vddio ? tabla_scale_v_micb_vddio(priv, mv, false) : mv; + + btnmeas[meas] = tabla_determine_button(priv, mv_s); + pr_debug("%s: meas %d - DCE %d,%d,%d button %d\n", + __func__, meas, dce, mv, mv_s, btnmeas[meas]); + /* if large enough measurements are collected, + * start to check if last all n_btn_con measurements were + * in same button low/high range */ + if (meas + 1 >= d->n_btn_con) { + for (i = 0; i < d->n_btn_con; i++) + if ((btnmeas[meas] < 0) || + (btnmeas[meas] != btnmeas[meas - i])) + break; + if (i == d->n_btn_con) { + /* button pressed */ + btn = btnmeas[meas]; + break; + } else if ((n_btn_meas - meas) < (d->n_btn_con - 1)) { + /* if left measurements are less than n_btn_con, + * it's impossible to find button number */ + break; + } + } + } + + if (btn >= 0) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO already triggered, ignore button " + "press\n", __func__); + goto done; + } + mask = tabla_get_button_mask(btn); + priv->buttons_pressed |= mask; + wcd9xxx_lock_sleep(core); + if (schedule_delayed_work(&priv->mbhc_btn_dwork, + msecs_to_jiffies(400)) == 0) { + WARN(1, "Button pressed twice without release" + "event\n"); + wcd9xxx_unlock_sleep(core); + } + } else { + pr_debug("%s: bogus button press, too short press?\n", + __func__); + } + + done: + pr_debug("%s: leave\n", __func__); + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static int tabla_is_fake_press(struct tabla_priv *priv) +{ + int i; + int r = 0; + struct snd_soc_codec *codec = priv->codec; + const int dces = MBHC_NUM_DCE_PLUG_DETECT; + s16 mb_v, v_ins_hu, v_ins_h; + + v_ins_hu = tabla_get_current_v_ins(priv, true); + v_ins_h = tabla_get_current_v_ins(priv, false); + + for (i = 0; i < dces; i++) { + usleep_range(10000, 10000); + if (i == 0) { + mb_v = tabla_codec_sta_dce(codec, 0, true); + pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v, + tabla_codec_sta_dce_v(codec, 0, mb_v)); + if (mb_v < (s16)priv->mbhc_data.v_b1_hu || + mb_v > v_ins_hu) { + r = 1; + break; + } + } else { + mb_v = tabla_codec_sta_dce(codec, 1, true); + pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v, + tabla_codec_sta_dce_v(codec, 1, mb_v)); + if (mb_v < (s16)priv->mbhc_data.v_b1_h || + mb_v > v_ins_h) { + r = 1; + break; + } + } + } + + return r; +} + +static irqreturn_t tabla_release_handler(int irq, void *data) +{ + int ret; + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + priv->mbhc_state = MBHC_STATE_RELEASE; + + tabla_codec_drive_v_to_micbias(codec, 10000); + + if (priv->buttons_pressed & TABLA_JACK_BUTTON_MASK) { + ret = tabla_cancel_btn_work(priv); + if (ret == 0) { + pr_debug("%s: Reporting long button release event\n", + __func__); + if (priv->mbhc_cfg.button_jack) + tabla_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } else { + if (tabla_is_fake_press(priv)) { + pr_debug("%s: Fake button press interrupt\n", + __func__); + } else if (priv->mbhc_cfg.button_jack) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO kicked in, ignore\n", + __func__); + } else { + pr_debug("%s: Reporting short button " + "press and release\n", + __func__); + tabla_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, + priv->buttons_pressed, + priv->buttons_pressed); + tabla_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } + } + } + + priv->buttons_pressed &= ~TABLA_JACK_BUTTON_MASK; + } + + tabla_codec_calibrate_hs_polling(codec); + + if (priv->mbhc_cfg.gpio) + msleep(TABLA_MBHC_GPIO_REL_DEBOUNCE_TIME_MS); + + tabla_codec_start_hs_polling(codec); + + pr_debug("%s: leave\n", __func__); + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static void tabla_codec_shutdown_hs_removal_detect(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const struct tabla_mbhc_general_cfg *generic = + TABLA_MBHC_CAL_GENERAL_PTR(tabla->mbhc_cfg.calibration); + + if (!tabla->mclk_enabled && !tabla->mbhc_polling_active) + tabla_codec_enable_config_mode(codec, 1); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x6, 0x0); + + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x80, 0x00); + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0xA, 0x8); + if (!tabla->mclk_enabled && !tabla->mbhc_polling_active) + tabla_codec_enable_config_mode(codec, 0); + + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x00); +} + +static void tabla_codec_cleanup_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + tabla_codec_shutdown_hs_removal_detect(codec); + + if (!tabla->mclk_enabled) { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF); + } + + tabla->mbhc_polling_active = false; + tabla->mbhc_state = MBHC_STATE_NONE; +} + +static irqreturn_t tabla_hphl_ocp_irq(int irq, void *data) +{ + struct tabla_priv *tabla = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHL OCP irq\n", __func__); + + if (tabla) { + codec = tabla->codec; + if (tabla->hphlocp_cnt++ < TABLA_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + tabla->hphlocp_cnt = 0; + tabla->hph_status |= SND_JACK_OC_HPHL; + if (tabla->mbhc_cfg.headset_jack) + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + } else { + pr_err("%s: Bad tabla private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t tabla_hphr_ocp_irq(int irq, void *data) +{ + struct tabla_priv *tabla = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHR OCP irq\n", __func__); + + if (tabla) { + codec = tabla->codec; + if (tabla->hphrocp_cnt++ < TABLA_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + tabla->hphrocp_cnt = 0; + tabla->hph_status |= SND_JACK_OC_HPHR; + if (tabla->mbhc_cfg.headset_jack) + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + } else { + pr_err("%s: Bad tabla private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static bool tabla_is_inval_ins_range(struct snd_soc_codec *codec, + s32 mic_volt, bool highhph, bool *highv) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + bool invalid = false; + s16 v_hs_max; + + /* Perform this check only when the high voltage headphone + * needs to be considered as invalid + */ + v_hs_max = tabla_get_current_v_hs_max(tabla); + *highv = mic_volt > v_hs_max; + if (!highhph && *highv) + invalid = true; + else if (mic_volt < tabla->mbhc_data.v_inval_ins_high && + (mic_volt > tabla->mbhc_data.v_inval_ins_low)) + invalid = true; + + return invalid; +} + +static bool tabla_is_inval_ins_delta(struct snd_soc_codec *codec, + int mic_volt, int mic_volt_prev, + int threshold) +{ + return abs(mic_volt - mic_volt_prev) > threshold; +} + +/* called under codec_resource_lock acquisition */ +void tabla_find_plug_and_report(struct snd_soc_codec *codec, + enum tabla_mbhc_plug_type plug_type) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (plug_type == PLUG_TYPE_HEADPHONE && + tabla->current_plug == PLUG_TYPE_NONE) { + /* Nothing was reported previously + * report a headphone or unsupported + */ + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + tabla_codec_cleanup_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + if (tabla->current_plug == PLUG_TYPE_HEADSET) + tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET); + else if (tabla->current_plug == PLUG_TYPE_HEADPHONE) + tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + + tabla_codec_report_plug(codec, 1, SND_JACK_UNSUPPORTED); + tabla_codec_cleanup_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HEADSET) { + /* If Headphone was reported previously, this will + * only report the mic line + */ + tabla_codec_report_plug(codec, 1, SND_JACK_HEADSET); + msleep(100); + tabla_codec_start_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + if (tabla->current_plug == PLUG_TYPE_NONE) + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + tabla_codec_cleanup_hs_polling(codec); + pr_debug("setup mic trigger for further detection\n"); + tabla->lpi_enabled = true; + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + false); + } else { + WARN(1, "Unexpected current plug_type %d, plug_type %d\n", + tabla->current_plug, plug_type); + } +} + +/* should be called under interrupt context that hold suspend */ +static void tabla_schedule_hs_detect_plug(struct tabla_priv *tabla) +{ + pr_debug("%s: scheduling tabla_hs_correct_gpio_plug\n", __func__); + tabla->hs_detect_work_stop = false; + wcd9xxx_lock_sleep(tabla->codec->control_data); + schedule_work(&tabla->hs_correct_plug_work); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_cancel_hs_detect_plug(struct tabla_priv *tabla) +{ + pr_debug("%s: canceling hs_correct_plug_work\n", __func__); + tabla->hs_detect_work_stop = true; + wmb(); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + if (cancel_work_sync(&tabla->hs_correct_plug_work)) { + pr_debug("%s: hs_correct_plug_work is canceled\n", __func__); + wcd9xxx_unlock_sleep(tabla->codec->control_data); + } + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); +} + +static bool tabla_hs_gpio_level_remove(struct tabla_priv *tabla) +{ + return (gpio_get_value_cansleep(tabla->mbhc_cfg.gpio) != + tabla->mbhc_cfg.gpio_level_insert); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_hphr_gnd_switch(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, on); + if (on) + usleep_range(5000, 5000); +} + +/* called under codec_resource_lock acquisition and mbhc override = 1 */ +static enum tabla_mbhc_plug_type +tabla_codec_get_plug_type(struct snd_soc_codec *codec, bool highhph) +{ + int i; + bool gndswitch, vddioswitch; + int scaled; + struct tabla_mbhc_plug_type_cfg *plug_type_ptr; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const bool vddio = (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV); + int num_det = (MBHC_NUM_DCE_PLUG_DETECT + vddio); + enum tabla_mbhc_plug_type plug_type[num_det]; + s16 mb_v[num_det]; + s32 mic_mv[num_det]; + bool inval; + bool highdelta; + bool ahighv = false, highv; + + /* make sure override is on */ + WARN_ON(!(snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x04)); + + /* GND and MIC swap detection requires at least 2 rounds of DCE */ + BUG_ON(num_det < 2); + + plug_type_ptr = + TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + + plug_type[0] = PLUG_TYPE_INVALID; + + /* performs DCEs for N times + * 1st: check if voltage is in invalid range + * 2nd - N-2nd: check voltage range and delta + * N-1st: check voltage range, delta with HPHR GND switch + * Nth: check voltage range with VDDIO switch if micbias V != vddio V*/ + for (i = 0; i < num_det; i++) { + gndswitch = (i == (num_det - 1 - vddio)); + vddioswitch = (vddio && ((i == num_det - 1) || + (i == num_det - 2))); + if (i == 0) { + mb_v[i] = tabla_codec_setup_hs_polling(codec); + mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]); + inval = tabla_is_inval_ins_range(codec, mic_mv[i], + highhph, &highv); + ahighv |= highv; + scaled = mic_mv[i]; + } else { + if (vddioswitch) + __tabla_codec_switch_micbias(tabla->codec, 1, + false, false); + if (gndswitch) + tabla_codec_hphr_gnd_switch(codec, true); + mb_v[i] = __tabla_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]); + if (vddioswitch) + scaled = tabla_scale_v_micb_vddio(tabla, + mic_mv[i], + false); + else + scaled = mic_mv[i]; + /* !gndswitch & vddioswitch means the previous DCE + * was done with gndswitch, don't compare with DCE + * with gndswitch */ + highdelta = tabla_is_inval_ins_delta(codec, scaled, + mic_mv[i - !gndswitch - vddioswitch], + TABLA_MBHC_FAKE_INS_DELTA_SCALED_MV); + inval = (tabla_is_inval_ins_range(codec, mic_mv[i], + highhph, &highv) || + highdelta); + ahighv |= highv; + if (gndswitch) + tabla_codec_hphr_gnd_switch(codec, false); + if (vddioswitch) + __tabla_codec_switch_micbias(tabla->codec, 0, + false, false); + /* claim UNSUPPORTED plug insertion when + * good headset is detected but HPHR GND switch makes + * delta difference */ + if (i == (num_det - 2) && highdelta && !ahighv) + plug_type[0] = PLUG_TYPE_GND_MIC_SWAP; + else if (i == (num_det - 1) && inval) + plug_type[0] = PLUG_TYPE_INVALID; + } + pr_debug("%s: DCE #%d, %04x, V %d, scaled V %d, GND %d, " + "VDDIO %d, inval %d\n", __func__, + i + 1, mb_v[i] & 0xffff, mic_mv[i], scaled, gndswitch, + vddioswitch, inval); + /* don't need to run further DCEs */ + if (ahighv && inval) + break; + mic_mv[i] = scaled; + } + + for (i = 0; (plug_type[0] != PLUG_TYPE_GND_MIC_SWAP && !inval) && + i < num_det; i++) { + /* + * If we are here, means none of the all + * measurements are fake, continue plug type detection. + * If all three measurements do not produce same + * plug type, restart insertion detection + */ + if (mic_mv[i] < plug_type_ptr->v_no_mic) { + plug_type[i] = PLUG_TYPE_HEADPHONE; + pr_debug("%s: Detect attempt %d, detected Headphone\n", + __func__, i); + } else if (highhph && (mic_mv[i] > plug_type_ptr->v_hs_max)) { + plug_type[i] = PLUG_TYPE_HIGH_HPH; + pr_debug("%s: Detect attempt %d, detected High " + "Headphone\n", __func__, i); + } else { + plug_type[i] = PLUG_TYPE_HEADSET; + pr_debug("%s: Detect attempt %d, detected Headset\n", + __func__, i); + } + + if (i > 0 && (plug_type[i - 1] != plug_type[i])) { + pr_err("%s: Detect attempt %d and %d are not same", + __func__, i - 1, i); + plug_type[0] = PLUG_TYPE_INVALID; + inval = true; + break; + } + } + + pr_debug("%s: Detected plug type %d\n", __func__, plug_type[0]); + return plug_type[0]; +} + +static void tabla_hs_correct_gpio_plug(struct work_struct *work) +{ + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + int retry = 0, pt_gnd_mic_swap_cnt = 0; + bool correction = false; + enum tabla_mbhc_plug_type plug_type; + unsigned long timeout; + + tabla = container_of(work, struct tabla_priv, hs_correct_plug_work); + codec = tabla->codec; + + pr_debug("%s: enter\n", __func__); + tabla->mbhc_cfg.mclk_cb_fn(codec, 1, false); + + /* Keep override on during entire plug type correction work. + * + * This is okay under the assumption that any GPIO irqs which use + * MBHC block cancel and sync this work so override is off again + * prior to GPIO interrupt handler's MBHC block usage. + * Also while this correction work is running, we can guarantee + * DAPM doesn't use any MBHC block as this work only runs with + * headphone detection. + */ + tabla_turn_onoff_override(codec, true); + + timeout = jiffies + msecs_to_jiffies(TABLA_HS_DETECT_PLUG_TIME_MS); + while (!time_after(jiffies, timeout)) { + ++retry; + rmb(); + if (tabla->hs_detect_work_stop) { + pr_debug("%s: stop requested\n", __func__); + break; + } + + msleep(TABLA_HS_DETECT_PLUG_INERVAL_MS); + if (tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO value is low\n", __func__); + break; + } + + /* can race with removal interrupt */ + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + plug_type = tabla_codec_get_plug_type(codec, true); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + + if (plug_type == PLUG_TYPE_INVALID) { + pr_debug("Invalid plug in attempt # %d\n", retry); + if (retry == NUM_ATTEMPTS_TO_REPORT && + tabla->current_plug == PLUG_TYPE_NONE) { + tabla_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + pr_debug("Good headphone detected, continue polling mic\n"); + if (tabla->current_plug == PLUG_TYPE_NONE) + tabla_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } else { + if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + pt_gnd_mic_swap_cnt++; + if (pt_gnd_mic_swap_cnt < + TABLA_MBHC_GND_MIC_SWAP_THRESHOLD) + continue; + else if (pt_gnd_mic_swap_cnt > + TABLA_MBHC_GND_MIC_SWAP_THRESHOLD) { + /* This is due to GND/MIC switch didn't + * work, Report unsupported plug */ + } else if (tabla->mbhc_cfg.swap_gnd_mic) { + /* if switch is toggled, check again, + * otherwise report unsupported plug */ + if (tabla->mbhc_cfg.swap_gnd_mic(codec)) + continue; + } + } else + pt_gnd_mic_swap_cnt = 0; + + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + /* Turn off override */ + tabla_turn_onoff_override(codec, false); + /* The valid plug also includes PLUG_TYPE_GND_MIC_SWAP + */ + tabla_find_plug_and_report(codec, plug_type); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_debug("Attempt %d found correct plug %d\n", retry, + plug_type); + correction = true; + break; + } + } + + /* Turn off override */ + if (!correction) + tabla_turn_onoff_override(codec, false); + + tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false); + pr_debug("%s: leave\n", __func__); + /* unlock sleep */ + wcd9xxx_unlock_sleep(tabla->codec->control_data); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_decide_gpio_plug(struct snd_soc_codec *codec) +{ + enum tabla_mbhc_plug_type plug_type; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enter\n", __func__); + + tabla_turn_onoff_override(codec, true); + plug_type = tabla_codec_get_plug_type(codec, true); + tabla_turn_onoff_override(codec, false); + + if (tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO value is low when determining plug\n", + __func__); + return; + } + + if (plug_type == PLUG_TYPE_INVALID || + plug_type == PLUG_TYPE_GND_MIC_SWAP) { + tabla_schedule_hs_detect_plug(tabla); + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + + tabla_schedule_hs_detect_plug(tabla); + } else { + pr_debug("%s: Valid plug found, determine plug type %d\n", + __func__, plug_type); + tabla_find_plug_and_report(codec, plug_type); + } +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_detect_plug_type(struct snd_soc_codec *codec) +{ + enum tabla_mbhc_plug_type plug_type; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const struct tabla_mbhc_plug_detect_cfg *plug_det = + TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->mbhc_cfg.calibration); + + /* Turn on the override, + * tabla_codec_setup_hs_polling requires override on */ + tabla_turn_onoff_override(codec, true); + + if (plug_det->t_ins_complete > 20) + msleep(plug_det->t_ins_complete); + else + usleep_range(plug_det->t_ins_complete * 1000, + plug_det->t_ins_complete * 1000); + + if (tabla->mbhc_cfg.gpio) { + /* Turn off the override */ + tabla_turn_onoff_override(codec, false); + if (tabla_hs_gpio_level_remove(tabla)) + pr_debug("%s: GPIO value is low when determining " + "plug\n", __func__); + else + tabla_codec_decide_gpio_plug(codec); + return; + } + + plug_type = tabla_codec_get_plug_type(codec, false); + tabla_turn_onoff_override(codec, false); + + if (plug_type == PLUG_TYPE_INVALID) { + pr_debug("%s: Invalid plug type detected\n", __func__); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, false); + } else if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + pr_debug("%s: GND-MIC swapped plug type detected\n", __func__); + tabla_codec_report_plug(codec, 1, SND_JACK_UNSUPPORTED); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 0, 0, false); + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + pr_debug("%s: Headphone Detected\n", __func__); + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 0, 0, false); + } else if (plug_type == PLUG_TYPE_HEADSET) { + pr_debug("%s: Headset detected\n", __func__); + tabla_codec_report_plug(codec, 1, SND_JACK_HEADSET); + + /* avoid false button press detect */ + msleep(50); + tabla_codec_start_hs_polling(codec); + } +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_insert_irq_gpio(struct tabla_priv *priv, bool is_removal) +{ + struct snd_soc_codec *codec = priv->codec; + + if (!is_removal) { + pr_debug("%s: MIC trigger insertion interrupt\n", __func__); + + rmb(); + if (priv->lpi_enabled) + msleep(100); + + rmb(); + if (!priv->lpi_enabled) { + pr_debug("%s: lpi is disabled\n", __func__); + } else if (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) == + priv->mbhc_cfg.gpio_level_insert) { + pr_debug("%s: Valid insertion, " + "detect plug type\n", __func__); + tabla_codec_decide_gpio_plug(codec); + } else { + pr_debug("%s: Invalid insertion, " + "stop plug detection\n", __func__); + } + } else { + pr_err("%s: GPIO used, invalid MBHC Removal\n", __func__); + } +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_insert_irq_nogpio(struct tabla_priv *priv, bool is_removal, + bool is_mb_trigger) +{ + int ret; + struct snd_soc_codec *codec = priv->codec; + struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent); + + if (is_removal) { + /* cancel possiblely running hs detect work */ + tabla_cancel_hs_detect_plug(priv); + + /* + * If headphone is removed while playback is in progress, + * it is possible that micbias will be switched to VDDIO. + */ + tabla_codec_switch_micbias(codec, 0); + if (priv->current_plug == PLUG_TYPE_HEADPHONE) + tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + else if (priv->current_plug == PLUG_TYPE_GND_MIC_SWAP) + tabla_codec_report_plug(codec, 0, SND_JACK_UNSUPPORTED); + else + WARN(1, "%s: Unexpected current plug type %d\n", + __func__, priv->current_plug); + tabla_codec_shutdown_hs_removal_detect(codec); + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + true); + } else if (is_mb_trigger && !is_removal) { + pr_debug("%s: Waiting for Headphone left trigger\n", + __func__); + wcd9xxx_lock_sleep(core); + if (schedule_delayed_work(&priv->mbhc_insert_dwork, + usecs_to_jiffies(1000000)) == 0) { + pr_err("%s: mbhc_insert_dwork is already scheduled\n", + __func__); + wcd9xxx_unlock_sleep(core); + } + tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_HPHL_TRIGGER, + false); + } else { + ret = cancel_delayed_work(&priv->mbhc_insert_dwork); + if (ret != 0) { + pr_debug("%s: Complete plug insertion, Detecting plug " + "type\n", __func__); + tabla_codec_detect_plug_type(codec); + wcd9xxx_unlock_sleep(core); + } else { + wcd9xxx_enable_irq(codec->control_data, + TABLA_IRQ_MBHC_INSERTION); + pr_err("%s: Error detecting plug insertion\n", + __func__); + } + } +} + +static irqreturn_t tabla_hs_insert_irq(int irq, void *data) +{ + bool is_mb_trigger, is_removal; + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + + is_mb_trigger = !!(snd_soc_read(codec, priv->mbhc_bias_regs.mbhc_reg) & + 0x10); + is_removal = !!(snd_soc_read(codec, TABLA_A_CDC_MBHC_INT_CTL) & 0x02); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x03, 0x00); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, priv->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + + if (priv->mbhc_cfg.gpio) + tabla_hs_insert_irq_gpio(priv, is_removal); + else + tabla_hs_insert_irq_nogpio(priv, is_removal, is_mb_trigger); + + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static bool is_valid_mic_voltage(struct snd_soc_codec *codec, s32 mic_mv) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const struct tabla_mbhc_plug_type_cfg *plug_type = + TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + const s16 v_hs_max = tabla_get_current_v_hs_max(tabla); + + return (!(mic_mv > 10 && mic_mv < 80) && (mic_mv > plug_type->v_no_mic) + && (mic_mv < v_hs_max)) ? true : false; +} + +/* called under codec_resource_lock acquisition + * returns true if mic voltage range is back to normal insertion + * returns false either if timedout or removed */ +static bool tabla_hs_remove_settle(struct snd_soc_codec *codec) +{ + int i; + bool timedout, settled = false; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + unsigned long retry = 0, timeout; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const s16 v_hs_max = tabla_get_current_v_hs_max(tabla); + + timeout = jiffies + msecs_to_jiffies(TABLA_HS_DETECT_PLUG_TIME_MS); + while (!(timedout = time_after(jiffies, timeout))) { + retry++; + if (tabla->mbhc_cfg.gpio && tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + if (tabla->mbhc_cfg.gpio) { + if (retry > 1) + msleep(250); + else + msleep(50); + } + + if (tabla->mbhc_cfg.gpio && tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = tabla_codec_sta_dce(codec, 1, true); + mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + + if (tabla->mbhc_cfg.gpio && tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + if (tabla->current_plug == PLUG_TYPE_NONE) { + pr_debug("%s : headset/headphone is removed\n", + __func__); + break; + } + + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) + if (!is_valid_mic_voltage(codec, mic_mv[i])) + break; + + if (i == MBHC_NUM_DCE_PLUG_DETECT) { + pr_debug("%s: MIC voltage settled\n", __func__); + settled = true; + msleep(200); + break; + } + + /* only for non-GPIO remove irq */ + if (!tabla->mbhc_cfg.gpio) { + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) + if (mic_mv[i] < v_hs_max) + break; + if (i == MBHC_NUM_DCE_PLUG_DETECT) { + pr_debug("%s: Headset is removed\n", __func__); + break; + } + } + } + + if (timedout) + pr_debug("%s: Microphone did not settle in %d seconds\n", + __func__, TABLA_HS_DETECT_PLUG_TIME_MS); + return settled; +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_remove_irq_gpio(struct tabla_priv *priv) +{ + struct snd_soc_codec *codec = priv->codec; + + if (tabla_hs_remove_settle(codec)) + tabla_codec_start_hs_polling(codec); + pr_debug("%s: remove settle done\n", __func__); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_remove_irq_nogpio(struct tabla_priv *priv) +{ + short bias_value; + bool removed = true; + struct snd_soc_codec *codec = priv->codec; + const struct tabla_mbhc_general_cfg *generic = + TABLA_MBHC_CAL_GENERAL_PTR(priv->mbhc_cfg.calibration); + int min_us = TABLA_FAKE_REMOVAL_MIN_PERIOD_MS * 1000; + + if (priv->current_plug != PLUG_TYPE_HEADSET) { + pr_debug("%s(): Headset is not inserted, ignore removal\n", + __func__); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + return; + } + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + + do { + bias_value = tabla_codec_sta_dce(codec, 1, true); + pr_debug("%s: DCE %d,%d, %d us left\n", __func__, bias_value, + tabla_codec_sta_dce_v(codec, 1, bias_value), min_us); + if (bias_value < tabla_get_current_v_ins(priv, false)) { + pr_debug("%s: checking false removal\n", __func__); + msleep(500); + removed = !tabla_hs_remove_settle(codec); + pr_debug("%s: headset %sactually removed\n", __func__, + removed ? "" : "not "); + break; + } + min_us -= priv->mbhc_data.t_dce; + } while (min_us > 0); + + if (removed) { + /* cancel possiblely running hs detect work */ + tabla_cancel_hs_detect_plug(priv); + /* + * If this removal is not false, first check the micbias + * switch status and switch it to LDOH if it is already + * switched to VDDIO. + */ + tabla_codec_switch_micbias(codec, 0); + + tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + true); + } else { + tabla_codec_start_hs_polling(codec); + } +} + +static irqreturn_t tabla_hs_remove_irq(int irq, void *data) +{ + struct tabla_priv *priv = data; + bool vddio; + pr_debug("%s: enter, removal interrupt\n", __func__); + + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + vddio = (priv->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + priv->mbhc_micbias_switched); + if (vddio) + __tabla_codec_switch_micbias(priv->codec, 0, false, true); + + if (priv->mbhc_cfg.gpio) + tabla_hs_remove_irq_gpio(priv); + else + tabla_hs_remove_irq_nogpio(priv); + + /* if driver turned off vddio switch and headset is not removed, + * turn on the vddio switch back, if headset is removed then vddio + * switch is off by time now and shouldn't be turn on again from here */ + if (vddio && priv->current_plug == PLUG_TYPE_HEADSET) + __tabla_codec_switch_micbias(priv->codec, 1, true, true); + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + + return IRQ_HANDLED; +} + +void mbhc_insert_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + struct wcd9xxx *tabla_core; + + dwork = to_delayed_work(work); + tabla = container_of(dwork, struct tabla_priv, mbhc_insert_dwork); + codec = tabla->codec; + tabla_core = dev_get_drvdata(codec->dev->parent); + + pr_debug("%s:\n", __func__); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + wcd9xxx_disable_irq_sync(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + tabla_codec_detect_plug_type(codec); + wcd9xxx_unlock_sleep(tabla_core); +} + +static void tabla_hs_gpio_handler(struct snd_soc_codec *codec) +{ + bool insert; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + bool is_removed = false; + + pr_debug("%s: enter\n", __func__); + + tabla->in_gpio_handler = true; + /* Wait here for debounce time */ + usleep_range(TABLA_GPIO_IRQ_DEBOUNCE_TIME_US, + TABLA_GPIO_IRQ_DEBOUNCE_TIME_US); + + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + + /* cancel pending button press */ + if (tabla_cancel_btn_work(tabla)) + pr_debug("%s: button press is canceled\n", __func__); + + insert = (gpio_get_value_cansleep(tabla->mbhc_cfg.gpio) == + tabla->mbhc_cfg.gpio_level_insert); + if ((tabla->current_plug == PLUG_TYPE_NONE) && insert) { + tabla->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + tabla_cancel_hs_detect_plug(tabla); + + /* Disable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, 0x00); + tabla_codec_detect_plug_type(codec); + } else if ((tabla->current_plug != PLUG_TYPE_NONE) && !insert) { + tabla->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + tabla_cancel_hs_detect_plug(tabla); + + if (tabla->current_plug == PLUG_TYPE_HEADPHONE) { + tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + is_removed = true; + } else if (tabla->current_plug == PLUG_TYPE_GND_MIC_SWAP) { + tabla_codec_report_plug(codec, 0, SND_JACK_UNSUPPORTED); + is_removed = true; + } else if (tabla->current_plug == PLUG_TYPE_HEADSET) { + tabla_codec_pause_hs_polling(codec); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET); + is_removed = true; + } + + if (is_removed) { + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.ctl_reg, 0x01, + 0x01); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, + 0x01); + /* Make sure mic trigger is turned off */ + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x90, 0x00); + /* Reset MBHC State Machine */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, + 0x08, 0x00); + /* Turn off override */ + tabla_turn_onoff_override(codec, false); + } + } + + tabla->in_gpio_handler = false; + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_debug("%s: leave\n", __func__); +} + +static irqreturn_t tabla_mechanical_plug_detect_irq(int irq, void *data) +{ + int r = IRQ_HANDLED; + struct snd_soc_codec *codec = data; + + if (unlikely(wcd9xxx_lock_sleep(codec->control_data) == false)) { + pr_warn("%s: failed to hold suspend\n", __func__); + r = IRQ_NONE; + } else { + tabla_hs_gpio_handler(codec); + wcd9xxx_unlock_sleep(codec->control_data); + } + + return r; +} + +static int tabla_mbhc_init_and_calibrate(struct tabla_priv *tabla) +{ + int ret = 0; + struct snd_soc_codec *codec = tabla->codec; + + tabla->mbhc_cfg.mclk_cb_fn(codec, 1, false); + tabla_mbhc_init(codec); + tabla_mbhc_cal(codec); + tabla_mbhc_calc_thres(codec); + tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false); + tabla_codec_calibrate_hs_polling(codec); + if (!tabla->mbhc_cfg.gpio) { + ret = tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + false); + + if (IS_ERR_VALUE(ret)) + pr_err("%s: Failed to setup MBHC detection\n", + __func__); + } else { + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, 0x01); + INIT_WORK(&tabla->hs_correct_plug_work, + tabla_hs_correct_gpio_plug); + } + + if (!IS_ERR_VALUE(ret)) { + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x10); + wcd9xxx_enable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + wcd9xxx_enable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + + if (tabla->mbhc_cfg.gpio) { + ret = request_threaded_irq(tabla->mbhc_cfg.gpio_irq, + NULL, + tabla_mechanical_plug_detect_irq, + (IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING), + "tabla-gpio", codec); + if (!IS_ERR_VALUE(ret)) { + ret = enable_irq_wake(tabla->mbhc_cfg.gpio_irq); + /* Bootup time detection */ + tabla_hs_gpio_handler(codec); + } + } + } + + return ret; +} + +static void mbhc_fw_read(struct work_struct *work) +{ + struct delayed_work *dwork; + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + const struct firmware *fw; + int ret = -1, retry = 0; + + dwork = to_delayed_work(work); + tabla = container_of(dwork, struct tabla_priv, mbhc_firmware_dwork); + codec = tabla->codec; + + while (retry < MBHC_FW_READ_ATTEMPTS) { + retry++; + pr_info("%s:Attempt %d to request MBHC firmware\n", + __func__, retry); + ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin", + codec->dev); + + if (ret != 0) { + usleep_range(MBHC_FW_READ_TIMEOUT, + MBHC_FW_READ_TIMEOUT); + } else { + pr_info("%s: MBHC Firmware read succesful\n", __func__); + break; + } + } + + if (ret != 0) { + pr_err("%s: Cannot load MBHC firmware use default cal\n", + __func__); + } else if (tabla_mbhc_fw_validate(fw) == false) { + pr_err("%s: Invalid MBHC cal data size use default cal\n", + __func__); + release_firmware(fw); + } else { + tabla->mbhc_cfg.calibration = (void *)fw->data; + tabla->mbhc_fw = fw; + } + + (void) tabla_mbhc_init_and_calibrate(tabla); +} + +int tabla_hs_detect(struct snd_soc_codec *codec, + const struct tabla_mbhc_config *cfg) +{ + struct tabla_priv *tabla; + int rc = 0; + + if (!codec || !cfg->calibration) { + pr_err("Error: no codec or calibration\n"); + return -EINVAL; + } + + if (cfg->mclk_rate != TABLA_MCLK_RATE_12288KHZ) { + if (cfg->mclk_rate == TABLA_MCLK_RATE_9600KHZ) + pr_err("Error: clock rate %dHz is not yet supported\n", + cfg->mclk_rate); + else + pr_err("Error: unsupported clock rate %d\n", + cfg->mclk_rate); + return -EINVAL; + } + + tabla = snd_soc_codec_get_drvdata(codec); + tabla->mbhc_cfg = *cfg; + tabla->in_gpio_handler = false; + tabla->current_plug = PLUG_TYPE_NONE; + tabla->lpi_enabled = false; + tabla_get_mbhc_micbias_regs(codec, &tabla->mbhc_bias_regs); + + /* Put CFILT in fast mode by default */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, + 0x40, TABLA_CFILT_FAST_MODE); + INIT_DELAYED_WORK(&tabla->mbhc_firmware_dwork, mbhc_fw_read); + INIT_DELAYED_WORK(&tabla->mbhc_btn_dwork, btn_lpress_fn); + INIT_WORK(&tabla->hphlocp_work, hphlocp_off_report); + INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report); + INIT_DELAYED_WORK(&tabla->mbhc_insert_dwork, mbhc_insert_work); + + if (!tabla->mbhc_cfg.read_fw_bin) + rc = tabla_mbhc_init_and_calibrate(tabla); + else + schedule_delayed_work(&tabla->mbhc_firmware_dwork, + usecs_to_jiffies(MBHC_FW_READ_TIMEOUT)); + + return rc; +} +EXPORT_SYMBOL_GPL(tabla_hs_detect); + +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 < WCD9XXX_SLIM_NUM_PORT_REG; i++) { + slimbus_value = wcd9xxx_interface_reg_read(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_STATUS0 + i); + for_each_set_bit(j, &slimbus_value, BITS_PER_BYTE) { + val = wcd9xxx_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); + } + wcd9xxx_interface_reg_write(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF); + } + + return IRQ_HANDLED; +} + +static int tabla_handle_pdata(struct tabla_priv *tabla) +{ + struct snd_soc_codec *codec = tabla->codec; + struct wcd9xxx_pdata *pdata = tabla->pdata; + int k1, k2, k3, rc = 0; + u8 leg_mode = pdata->amic_settings.legacy_mode; + u8 txfe_bypass = pdata->amic_settings.txfe_enable; + u8 txfe_buff = pdata->amic_settings.txfe_buff; + u8 flag = pdata->amic_settings.use_pdata; + u8 i = 0, j = 0; + u8 val_txfe = 0, value = 0; + + if (!pdata) { + rc = -ENODEV; + goto done; + } + + /* Make sure settings are correct */ + if ((pdata->micbias.ldoh_v > TABLA_LDOH_2P85_V) || + (pdata->micbias.bias1_cfilt_sel > TABLA_CFILT3_SEL) || + (pdata->micbias.bias2_cfilt_sel > TABLA_CFILT3_SEL) || + (pdata->micbias.bias3_cfilt_sel > TABLA_CFILT3_SEL) || + (pdata->micbias.bias4_cfilt_sel > TABLA_CFILT3_SEL)) { + rc = -EINVAL; + goto done; + } + + /* figure out k value */ + k1 = tabla_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt1_mv); + k2 = tabla_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt2_mv); + k3 = tabla_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt3_mv); + + if (IS_ERR_VALUE(k1) || IS_ERR_VALUE(k2) || IS_ERR_VALUE(k3)) { + rc = -EINVAL; + goto done; + } + + /* Set voltage level and always use LDO */ + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x0C, + (pdata->micbias.ldoh_v << 2)); + + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_1_VAL, 0xFC, + (k1 << 2)); + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_2_VAL, 0xFC, + (k2 << 2)); + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_3_VAL, 0xFC, + (k3 << 2)); + + snd_soc_update_bits(codec, TABLA_A_MICB_1_CTL, 0x60, + (pdata->micbias.bias1_cfilt_sel << 5)); + snd_soc_update_bits(codec, TABLA_A_MICB_2_CTL, 0x60, + (pdata->micbias.bias2_cfilt_sel << 5)); + snd_soc_update_bits(codec, TABLA_A_MICB_3_CTL, 0x60, + (pdata->micbias.bias3_cfilt_sel << 5)); + snd_soc_update_bits(codec, tabla->reg_addr.micb_4_ctl, 0x60, + (pdata->micbias.bias4_cfilt_sel << 5)); + + for (i = 0; i < 6; j++, i += 2) { + if (flag & (0x01 << i)) { + value = (leg_mode & (0x01 << i)) ? 0x10 : 0x00; + val_txfe = (txfe_bypass & (0x01 << i)) ? 0x20 : 0x00; + val_txfe = val_txfe | + ((txfe_buff & (0x01 << i)) ? 0x10 : 0x00); + snd_soc_update_bits(codec, TABLA_A_TX_1_2_EN + j * 10, + 0x10, value); + snd_soc_update_bits(codec, + TABLA_A_TX_1_2_TEST_EN + j * 10, + 0x30, val_txfe); + } + if (flag & (0x01 << (i + 1))) { + value = (leg_mode & (0x01 << (i + 1))) ? 0x01 : 0x00; + val_txfe = (txfe_bypass & + (0x01 << (i + 1))) ? 0x02 : 0x00; + val_txfe |= (txfe_buff & + (0x01 << (i + 1))) ? 0x01 : 0x00; + snd_soc_update_bits(codec, TABLA_A_TX_1_2_EN + j * 10, + 0x01, value); + snd_soc_update_bits(codec, + TABLA_A_TX_1_2_TEST_EN + j * 10, + 0x03, val_txfe); + } + } + if (flag & 0x40) { + value = (leg_mode & 0x40) ? 0x10 : 0x00; + value = value | ((txfe_bypass & 0x40) ? 0x02 : 0x00); + value = value | ((txfe_buff & 0x40) ? 0x01 : 0x00); + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, + 0x13, value); + } + + if (pdata->ocp.use_pdata) { + /* not defined in CODEC specification */ + if (pdata->ocp.hph_ocp_limit == 1 || + pdata->ocp.hph_ocp_limit == 5) { + rc = -EINVAL; + goto done; + } + snd_soc_update_bits(codec, TABLA_A_RX_COM_OCP_CTL, + 0x0F, pdata->ocp.num_attempts); + snd_soc_write(codec, TABLA_A_RX_COM_OCP_COUNT, + ((pdata->ocp.run_time << 4) | pdata->ocp.wait_time)); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, + 0xE0, (pdata->ocp.hph_ocp_limit << 5)); + } + + for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) { + if (!strncmp(pdata->regulator[i].name, "CDC_VDDA_RX", 11)) { + if (pdata->regulator[i].min_uV == 1800000 && + pdata->regulator[i].max_uV == 1800000) { + snd_soc_write(codec, TABLA_A_BIAS_REF_CTL, + 0x1C); + } else if (pdata->regulator[i].min_uV == 2200000 && + pdata->regulator[i].max_uV == 2200000) { + snd_soc_write(codec, TABLA_A_BIAS_REF_CTL, + 0x1E); + } else { + pr_err("%s: unsupported CDC_VDDA_RX voltage " + "min %d, max %d\n", __func__, + pdata->regulator[i].min_uV, + pdata->regulator[i].max_uV); + rc = -EINVAL; + } + break; + } + } +done: + return rc; +} + +static const struct tabla_reg_mask_val tabla_1_1_reg_defaults[] = { + + /* Tabla 1.1 MICBIAS changes */ + TABLA_REG_VAL(TABLA_A_MICB_1_INT_RBIAS, 0x24), + TABLA_REG_VAL(TABLA_A_MICB_2_INT_RBIAS, 0x24), + TABLA_REG_VAL(TABLA_A_MICB_3_INT_RBIAS, 0x24), + + /* Tabla 1.1 HPH changes */ + TABLA_REG_VAL(TABLA_A_RX_HPH_BIAS_PA, 0x57), + TABLA_REG_VAL(TABLA_A_RX_HPH_BIAS_LDO, 0x56), + + /* Tabla 1.1 EAR PA changes */ + TABLA_REG_VAL(TABLA_A_RX_EAR_BIAS_PA, 0xA6), + TABLA_REG_VAL(TABLA_A_RX_EAR_GAIN, 0x02), + TABLA_REG_VAL(TABLA_A_RX_EAR_VCM, 0x03), + + /* Tabla 1.1 Lineout_5 Changes */ + TABLA_REG_VAL(TABLA_A_RX_LINE_5_GAIN, 0x10), + + /* Tabla 1.1 RX Changes */ + TABLA_REG_VAL(TABLA_A_CDC_RX1_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX2_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX3_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX4_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX5_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX6_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX7_B5_CTL, 0x78), + + /* Tabla 1.1 RX1 and RX2 Changes */ + TABLA_REG_VAL(TABLA_A_CDC_RX1_B6_CTL, 0xA0), + TABLA_REG_VAL(TABLA_A_CDC_RX2_B6_CTL, 0xA0), + + /* Tabla 1.1 RX3 to RX7 Changes */ + TABLA_REG_VAL(TABLA_A_CDC_RX3_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX4_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX5_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX6_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX7_B6_CTL, 0x80), + + /* Tabla 1.1 CLASSG Changes */ + TABLA_REG_VAL(TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL, 0x1B), +}; + +static const struct tabla_reg_mask_val tabla_2_0_reg_defaults[] = { + /* Tabla 2.0 MICBIAS changes */ + TABLA_REG_VAL(TABLA_A_MICB_2_MBHC, 0x02), +}; + +static const struct tabla_reg_mask_val tabla_1_x_only_reg_2_0_defaults[] = { + TABLA_REG_VAL(TABLA_1_A_MICB_4_INT_RBIAS, 0x24), +}; + +static const struct tabla_reg_mask_val tabla_2_only_reg_2_0_defaults[] = { + TABLA_REG_VAL(TABLA_2_A_MICB_4_INT_RBIAS, 0x24), +}; + +static void tabla_update_reg_defaults(struct snd_soc_codec *codec) +{ + u32 i; + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + + for (i = 0; i < ARRAY_SIZE(tabla_1_1_reg_defaults); i++) + snd_soc_write(codec, tabla_1_1_reg_defaults[i].reg, + tabla_1_1_reg_defaults[i].val); + + for (i = 0; i < ARRAY_SIZE(tabla_2_0_reg_defaults); i++) + snd_soc_write(codec, tabla_2_0_reg_defaults[i].reg, + tabla_2_0_reg_defaults[i].val); + + if (TABLA_IS_1_X(tabla_core->version)) { + for (i = 0; i < ARRAY_SIZE(tabla_1_x_only_reg_2_0_defaults); + i++) + snd_soc_write(codec, + tabla_1_x_only_reg_2_0_defaults[i].reg, + tabla_1_x_only_reg_2_0_defaults[i].val); + } else { + for (i = 0; i < ARRAY_SIZE(tabla_2_only_reg_2_0_defaults); i++) + snd_soc_write(codec, + tabla_2_only_reg_2_0_defaults[i].reg, + tabla_2_only_reg_2_0_defaults[i].val); + } +} + +static const struct tabla_reg_mask_val tabla_codec_reg_init_val[] = { + /* Initialize current threshold to 350MA + * number of wait and run cycles to 4096 + */ + {TABLA_A_RX_HPH_OCP_CTL, 0xE0, 0x60}, + {TABLA_A_RX_COM_OCP_COUNT, 0xFF, 0xFF}, + + {TABLA_A_QFUSE_CTL, 0xFF, 0x03}, + + /* Initialize gain registers to use register gain */ + {TABLA_A_RX_HPH_L_GAIN, 0x10, 0x10}, + {TABLA_A_RX_HPH_R_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_1_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_2_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_3_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_4_GAIN, 0x10, 0x10}, + + /* Initialize mic biases to differential mode */ + {TABLA_A_MICB_1_INT_RBIAS, 0x24, 0x24}, + {TABLA_A_MICB_2_INT_RBIAS, 0x24, 0x24}, + {TABLA_A_MICB_3_INT_RBIAS, 0x24, 0x24}, + + {TABLA_A_CDC_CONN_CLSG_CTL, 0x3C, 0x14}, + + /* Use 16 bit sample size for TX1 to TX6 */ + {TABLA_A_CDC_CONN_TX_SB_B1_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B2_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B3_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B4_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B5_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B6_CTL, 0x30, 0x20}, + + /* Use 16 bit sample size for TX7 to TX10 */ + {TABLA_A_CDC_CONN_TX_SB_B7_CTL, 0x60, 0x40}, + {TABLA_A_CDC_CONN_TX_SB_B8_CTL, 0x60, 0x40}, + {TABLA_A_CDC_CONN_TX_SB_B9_CTL, 0x60, 0x40}, + {TABLA_A_CDC_CONN_TX_SB_B10_CTL, 0x60, 0x40}, + + /* Use 16 bit sample size for RX */ + {TABLA_A_CDC_CONN_RX_SB_B1_CTL, 0xFF, 0xAA}, + {TABLA_A_CDC_CONN_RX_SB_B2_CTL, 0xFF, 0xAA}, + + /*enable HPF filter for TX paths */ + {TABLA_A_CDC_TX1_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX2_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX3_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX4_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX5_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX6_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX7_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX8_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX9_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX10_MUX_CTL, 0x8, 0x0}, + + /* config Decimator for DMIC CLK_MODE_1(3.072Mhz@12.88Mhz mclk) */ + {TABLA_A_CDC_TX1_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX2_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX3_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX4_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX5_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX6_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX7_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX8_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX9_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX10_DMIC_CTL, 0x1, 0x1}, + + /* config DMIC clk to CLK_MODE_1 (3.072Mhz@12.88Mhz mclk) */ + {TABLA_A_CDC_CLK_DMIC_CTL, 0x2A, 0x2A}, + +}; + +static const struct tabla_reg_mask_val tabla_1_x_codec_reg_init_val[] = { + /* Initialize mic biases to differential mode */ + {TABLA_1_A_MICB_4_INT_RBIAS, 0x24, 0x24}, +}; + +static const struct tabla_reg_mask_val tabla_2_higher_codec_reg_init_val[] = { + /* Initialize mic biases to differential mode */ + {TABLA_2_A_MICB_4_INT_RBIAS, 0x24, 0x24}, +}; + +static void tabla_codec_init_reg(struct snd_soc_codec *codec) +{ + u32 i; + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + + for (i = 0; i < ARRAY_SIZE(tabla_codec_reg_init_val); i++) + snd_soc_update_bits(codec, tabla_codec_reg_init_val[i].reg, + tabla_codec_reg_init_val[i].mask, + tabla_codec_reg_init_val[i].val); + if (TABLA_IS_1_X(tabla_core->version)) { + for (i = 0; i < ARRAY_SIZE(tabla_1_x_codec_reg_init_val); i++) + snd_soc_update_bits(codec, + tabla_1_x_codec_reg_init_val[i].reg, + tabla_1_x_codec_reg_init_val[i].mask, + tabla_1_x_codec_reg_init_val[i].val); + } else { + for (i = 0; i < ARRAY_SIZE(tabla_2_higher_codec_reg_init_val); + i++) + snd_soc_update_bits(codec, + tabla_2_higher_codec_reg_init_val[i].reg, + tabla_2_higher_codec_reg_init_val[i].mask, + tabla_2_higher_codec_reg_init_val[i].val); + } +} + +static void tabla_update_reg_address(struct tabla_priv *priv) +{ + struct wcd9xxx *tabla_core = dev_get_drvdata(priv->codec->dev->parent); + struct tabla_reg_address *reg_addr = &priv->reg_addr; + + if (TABLA_IS_1_X(tabla_core->version)) { + reg_addr->micb_4_mbhc = TABLA_1_A_MICB_4_MBHC; + reg_addr->micb_4_int_rbias = TABLA_1_A_MICB_4_INT_RBIAS; + reg_addr->micb_4_ctl = TABLA_1_A_MICB_4_CTL; + } else if (TABLA_IS_2_0(tabla_core->version)) { + reg_addr->micb_4_mbhc = TABLA_2_A_MICB_4_MBHC; + reg_addr->micb_4_int_rbias = TABLA_2_A_MICB_4_INT_RBIAS; + reg_addr->micb_4_ctl = TABLA_2_A_MICB_4_CTL; + } +} + +#ifdef CONFIG_DEBUG_FS +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char lbuf[32]; + char *buf; + int rc; + struct tabla_priv *tabla = filp->private_data; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + buf = (char *)lbuf; + tabla->no_mic_headset_override = (*strsep(&buf, " ") == '0') ? + false : true; + return rc; +} + +static ssize_t codec_mbhc_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + const int size = 768; + char buffer[size]; + int n = 0; + struct tabla_priv *tabla = file->private_data; + struct snd_soc_codec *codec = tabla->codec; + const struct mbhc_internal_cal_data *p = &tabla->mbhc_data; + const s16 v_ins_hu_cur = tabla_get_current_v_ins(tabla, true); + const s16 v_ins_h_cur = tabla_get_current_v_ins(tabla, false); + + n = scnprintf(buffer, size - n, "dce_z = %x(%dmv)\n", p->dce_z, + tabla_codec_sta_dce_v(codec, 1, p->dce_z)); + n += scnprintf(buffer + n, size - n, "dce_mb = %x(%dmv)\n", + p->dce_mb, tabla_codec_sta_dce_v(codec, 1, p->dce_mb)); + n += scnprintf(buffer + n, size - n, "sta_z = %x(%dmv)\n", + p->sta_z, tabla_codec_sta_dce_v(codec, 0, p->sta_z)); + n += scnprintf(buffer + n, size - n, "sta_mb = %x(%dmv)\n", + p->sta_mb, tabla_codec_sta_dce_v(codec, 0, p->sta_mb)); + n += scnprintf(buffer + n, size - n, "t_dce = %x\n", p->t_dce); + n += scnprintf(buffer + n, size - n, "t_sta = %x\n", p->t_sta); + n += scnprintf(buffer + n, size - n, "micb_mv = %dmv\n", + p->micb_mv); + n += scnprintf(buffer + n, size - n, "v_ins_hu = %x(%dmv)%s\n", + p->v_ins_hu, + tabla_codec_sta_dce_v(codec, 0, p->v_ins_hu), + p->v_ins_hu == v_ins_hu_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "v_ins_h = %x(%dmv)%s\n", + p->v_ins_h, tabla_codec_sta_dce_v(codec, 1, p->v_ins_h), + p->v_ins_h == v_ins_h_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "adj_v_ins_hu = %x(%dmv)%s\n", + p->adj_v_ins_hu, + tabla_codec_sta_dce_v(codec, 0, p->adj_v_ins_hu), + p->adj_v_ins_hu == v_ins_hu_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "adj_v_ins_h = %x(%dmv)%s\n", + p->adj_v_ins_h, + tabla_codec_sta_dce_v(codec, 1, p->adj_v_ins_h), + p->adj_v_ins_h == v_ins_h_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "v_b1_hu = %x(%dmv)\n", + p->v_b1_hu, tabla_codec_sta_dce_v(codec, 0, p->v_b1_hu)); + n += scnprintf(buffer + n, size - n, "v_b1_h = %x(%dmv)\n", + p->v_b1_h, tabla_codec_sta_dce_v(codec, 1, p->v_b1_h)); + n += scnprintf(buffer + n, size - n, "v_b1_huc = %x(%dmv)\n", + p->v_b1_huc, + tabla_codec_sta_dce_v(codec, 1, p->v_b1_huc)); + n += scnprintf(buffer + n, size - n, "v_brh = %x(%dmv)\n", + p->v_brh, tabla_codec_sta_dce_v(codec, 1, p->v_brh)); + n += scnprintf(buffer + n, size - n, "v_brl = %x(%dmv)\n", p->v_brl, + tabla_codec_sta_dce_v(codec, 0, p->v_brl)); + n += scnprintf(buffer + n, size - n, "v_no_mic = %x(%dmv)\n", + p->v_no_mic, + tabla_codec_sta_dce_v(codec, 0, p->v_no_mic)); + n += scnprintf(buffer + n, size - n, "npoll = %d\n", p->npoll); + n += scnprintf(buffer + n, size - n, "nbounce_wait = %d\n", + p->nbounce_wait); + n += scnprintf(buffer + n, size - n, "v_inval_ins_low = %d\n", + p->v_inval_ins_low); + n += scnprintf(buffer + n, size - n, "v_inval_ins_high = %d\n", + p->v_inval_ins_high); + if (tabla->mbhc_cfg.gpio) + n += scnprintf(buffer + n, size - n, "GPIO insert = %d\n", + tabla_hs_gpio_level_remove(tabla)); + buffer[n] = 0; + + return simple_read_from_buffer(buf, count, pos, buffer, n); +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, +}; + +static const struct file_operations codec_mbhc_debug_ops = { + .open = codec_debug_open, + .read = codec_mbhc_debug_read, +}; +#endif + +static int tabla_codec_probe(struct snd_soc_codec *codec) +{ + struct wcd9xxx *control; + struct tabla_priv *tabla; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret = 0; + int i; + int ch_cnt; + + 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; + } + for (i = 0 ; i < NUM_DECIMATORS; i++) { + tx_hpf_work[i].tabla = tabla; + tx_hpf_work[i].decimator = i + 1; + INIT_DELAYED_WORK(&tx_hpf_work[i].dwork, + tx_hpf_corner_freq_callback); + } + + /* Make sure mbhc micbias register addresses are zeroed out */ + memset(&tabla->mbhc_bias_regs, 0, + sizeof(struct mbhc_micbias_regs)); + tabla->mbhc_micbias_switched = false; + + /* Make sure mbhc intenal calibration data is zeroed out */ + memset(&tabla->mbhc_data, 0, + sizeof(struct mbhc_internal_cal_data)); + tabla->mbhc_data.t_sta_dce = DEFAULT_DCE_STA_WAIT; + tabla->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + tabla->mbhc_data.t_sta = DEFAULT_STA_WAIT; + snd_soc_codec_set_drvdata(codec, tabla); + + tabla->mclk_enabled = false; + tabla->bandgap_type = TABLA_BANDGAP_OFF; + tabla->clock_active = false; + tabla->config_mode_active = false; + tabla->mbhc_polling_active = false; + tabla->mbhc_fake_ins_start = 0; + tabla->no_mic_headset_override = false; + tabla->hs_polling_irq_prepared = false; + mutex_init(&tabla->codec_resource_lock); + tabla->codec = codec; + tabla->mbhc_state = MBHC_STATE_NONE; + tabla->mbhc_last_resume = 0; + for (i = 0; i < COMPANDER_MAX; i++) { + tabla->comp_enabled[i] = 0; + tabla->comp_fs[i] = COMPANDER_FS_48KHZ; + } + tabla->pdata = dev_get_platdata(codec->dev->parent); + tabla->intf_type = wcd9xxx_get_intf_type(); + tabla->aux_pga_cnt = 0; + tabla->aux_l_gain = 0x1F; + tabla->aux_r_gain = 0x1F; + tabla_update_reg_address(tabla); + tabla_update_reg_defaults(codec); + tabla_codec_init_reg(codec); + ret = tabla_handle_pdata(tabla); + if (IS_ERR_VALUE(ret)) { + pr_err("%s: bad pdata\n", __func__); + goto err_pdata; + } + +// snd_soc_add_codec_controls(codec, tabla_snd_controls, +// ARRAY_SIZE(tabla_snd_controls)); + if (TABLA_IS_1_X(control->version)) + snd_soc_add_codec_controls(codec, tabla_1_x_snd_controls, + ARRAY_SIZE(tabla_1_x_snd_controls)); + else + snd_soc_add_codec_controls(codec, tabla_2_higher_snd_controls, + ARRAY_SIZE(tabla_2_higher_snd_controls)); + +// snd_soc_dapm_new_controls(dapm, tabla_dapm_widgets, +// ARRAY_SIZE(tabla_dapm_widgets)); + if (TABLA_IS_1_X(control->version)) + snd_soc_dapm_new_controls(dapm, tabla_1_x_dapm_widgets, + ARRAY_SIZE(tabla_1_x_dapm_widgets)); + else + snd_soc_dapm_new_controls(dapm, tabla_2_higher_dapm_widgets, + ARRAY_SIZE(tabla_2_higher_dapm_widgets)); + + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + snd_soc_dapm_new_controls(dapm, tabla_dapm_i2s_widgets, + ARRAY_SIZE(tabla_dapm_i2s_widgets)); + snd_soc_dapm_add_routes(dapm, audio_i2s_map, + ARRAY_SIZE(audio_i2s_map)); + } +// snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + if (TABLA_IS_1_X(control->version)) { + snd_soc_dapm_add_routes(dapm, tabla_1_x_lineout_2_to_4_map, + ARRAY_SIZE(tabla_1_x_lineout_2_to_4_map)); + } else if (TABLA_IS_2_0(control->version)) { + snd_soc_dapm_add_routes(dapm, tabla_2_x_lineout_2_to_4_map, + ARRAY_SIZE(tabla_2_x_lineout_2_to_4_map)); + } else { + pr_err("%s : ERROR. Unsupported Tabla version 0x%2x\n", + __func__, control->version); + goto err_pdata; + } + + snd_soc_dapm_sync(dapm); + + ret = wcd9xxx_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; + } + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + + ret = wcd9xxx_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; + } + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, + tabla_dce_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; + } + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, + tabla_release_handler, "Button Release detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_RELEASE); + goto err_release_irq; + } + + ret = wcd9xxx_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 < WCD9XXX_SLIM_NUM_PORT_REG; i++) + wcd9xxx_interface_reg_write(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_EN0 + i, 0xFF); + + ret = wcd9xxx_request_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla_hphl_ocp_irq, + "HPH_L OCP detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + goto err_hphl_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_HPH_PA_OCPL_FAULT); + + ret = wcd9xxx_request_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPR_FAULT, tabla_hphr_ocp_irq, + "HPH_R OCP detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + goto err_hphr_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_HPH_PA_OCPR_FAULT); + for (i = 0; i < ARRAY_SIZE(tabla_dai); i++) { + switch (tabla_dai[i].id) { + case AIF1_PB: + ch_cnt = tabla_dai[i].playback.channels_max; + break; + case AIF1_CAP: + ch_cnt = tabla_dai[i].capture.channels_max; + break; + case AIF2_PB: + ch_cnt = tabla_dai[i].playback.channels_max; + break; + case AIF2_CAP: + ch_cnt = tabla_dai[i].capture.channels_max; + break; + case AIF3_PB: + ch_cnt = tabla_dai[i].playback.channels_max; + break; + case AIF3_CAP: + ch_cnt = tabla_dai[i].capture.channels_max; + break; + default: + continue; + } + tabla->dai[i].ch_num = kzalloc((sizeof(unsigned int)* + ch_cnt), GFP_KERNEL); + } + +#ifdef CONFIG_DEBUG_FS + if (ret == 0) { + tabla->debugfs_poke = + debugfs_create_file("TRRS", S_IFREG | S_IRUGO, NULL, tabla, + &codec_debug_ops); + tabla->debugfs_mbhc = + debugfs_create_file("tabla_mbhc", S_IFREG | S_IRUGO, + NULL, tabla, &codec_mbhc_debug_ops); + } +#endif + codec->ignore_pmdown_time = 1; + return ret; + +err_hphr_ocp_irq: + wcd9xxx_free_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla); +err_hphl_ocp_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla); +err_slimbus_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, tabla); +err_release_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla); +err_potential_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla); +err_remove_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla); +err_insert_irq: +err_pdata: + mutex_destroy(&tabla->codec_resource_lock); + kfree(tabla); + return ret; +} +static int tabla_codec_remove(struct snd_soc_codec *codec) +{ + int i; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla); + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_disable_clock_block(codec); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF); + if (tabla->mbhc_fw) + release_firmware(tabla->mbhc_fw); + for (i = 0; i < ARRAY_SIZE(tabla_dai); i++) + kfree(tabla->dai[i].ch_num); + mutex_destroy(&tabla->codec_resource_lock); +#ifdef CONFIG_DEBUG_FS + debugfs_remove(tabla->debugfs_poke); + debugfs_remove(tabla->debugfs_mbhc); +#endif + 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, + .controls = tabla_snd_controls, + .num_controls = ARRAY_SIZE(tabla_snd_controls), + .dapm_widgets = tabla_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tabla_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +#ifdef CONFIG_PM +static int tabla_suspend(struct device *dev) +{ + dev_dbg(dev, "%s: system suspend\n", __func__); + return 0; +} + +static int tabla_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct tabla_priv *tabla = platform_get_drvdata(pdev); + dev_dbg(dev, "%s: system resume\n", __func__); + tabla->mbhc_last_resume = jiffies; + return 0; +} + +static const struct dev_pm_ops tabla_pm_ops = { + .suspend = tabla_suspend, + .resume = tabla_resume, +}; +#endif + +static int __devinit tabla_probe(struct platform_device *pdev) +{ + int ret = 0; + pr_err("tabla_probe\n"); + if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS) + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tabla, + tabla_dai, ARRAY_SIZE(tabla_dai)); + else if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C) + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tabla, + tabla_i2s_dai, ARRAY_SIZE(tabla_i2s_dai)); + return ret; +} +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, +#ifdef CONFIG_PM + .pm = &tabla_pm_ops, +#endif + }, +}; + +static struct platform_driver tabla1x_codec_driver = { + .probe = tabla_probe, + .remove = tabla_remove, + .driver = { + .name = "tabla1x_codec", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tabla_pm_ops, +#endif + }, +}; + +static int __init tabla_codec_init(void) +{ + int rtn = platform_driver_register(&tabla_codec_driver); + if (rtn == 0) { + rtn = platform_driver_register(&tabla1x_codec_driver); + if (rtn != 0) + platform_driver_unregister(&tabla_codec_driver); + } + return rtn; +} + +static void __exit tabla_codec_exit(void) +{ + platform_driver_unregister(&tabla1x_codec_driver); + 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 0000000000000000000000000000000000000000..1cca3609825997ccfef6b8953ac14a22771375b5 --- /dev/null +++ b/sound/soc/codecs/wcd9310.h @@ -0,0 +1,253 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without 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 TABLA_NUM_REGISTERS 0x400 +#define TABLA_MAX_REGISTER (TABLA_NUM_REGISTERS-1) +#define TABLA_CACHE_SIZE TABLA_NUM_REGISTERS +#define TABLA_1_X_ONLY_REGISTERS 3 +#define TABLA_2_HIGHER_ONLY_REGISTERS 3 + +#define TABLA_REG_VAL(reg, val) {reg, 0, val} + +#define DEFAULT_DCE_STA_WAIT 55 +#define DEFAULT_DCE_WAIT 60000 +#define DEFAULT_STA_WAIT 5000 +#define VDDIO_MICBIAS_MV 1800 + +#define STA 0 +#define DCE 1 + +#define TABLA_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3 | \ + SND_JACK_BTN_4 | SND_JACK_BTN_5 | \ + SND_JACK_BTN_6 | SND_JACK_BTN_7) + +extern const u8 tabla_reg_readable[TABLA_CACHE_SIZE]; +extern const u32 tabla_1_reg_readable[TABLA_1_X_ONLY_REGISTERS]; +extern const u32 tabla_2_reg_readable[TABLA_2_HIGHER_ONLY_REGISTERS]; +extern const u8 tabla_reg_defaults[TABLA_CACHE_SIZE]; + +enum tabla_micbias_num { + TABLA_MICBIAS1 = 0, + 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_reg_mask_val { + u16 reg; + u8 mask; + u8 val; +}; + +enum tabla_mbhc_clk_freq { + TABLA_MCLK_12P2MHZ = 0, + TABLA_MCLK_9P6MHZ, + TABLA_NUM_CLK_FREQS, +}; + +enum tabla_mbhc_analog_pwr_cfg { + TABLA_ANALOG_PWR_COLLAPSED = 0, + TABLA_ANALOG_PWR_ON, + TABLA_NUM_ANALOG_PWR_CONFIGS, +}; + +enum tabla_mbhc_btn_det_mem { + TABLA_BTN_DET_V_BTN_LOW, + TABLA_BTN_DET_V_BTN_HIGH, + TABLA_BTN_DET_N_READY, + TABLA_BTN_DET_N_CIC, + TABLA_BTN_DET_GAIN +}; + +struct tabla_mbhc_general_cfg { + u8 t_ldoh; + u8 t_bg_fast_settle; + u8 t_shutdown_plug_rem; + u8 mbhc_nsa; + u8 mbhc_navg; + u8 v_micbias_l; + u8 v_micbias; + u8 mbhc_reserved; + u16 settle_wait; + u16 t_micbias_rampup; + u16 t_micbias_rampdown; + u16 t_supply_bringup; +} __packed; + +struct tabla_mbhc_plug_detect_cfg { + u32 mic_current; + u32 hph_current; + u16 t_mic_pid; + u16 t_ins_complete; + u16 t_ins_retry; + u16 v_removal_delta; + u8 micbias_slow_ramp; + u8 reserved0; + u8 reserved1; + u8 reserved2; +} __packed; + +struct tabla_mbhc_plug_type_cfg { + u8 av_detect; + u8 mono_detect; + u8 num_ins_tries; + u8 reserved0; + s16 v_no_mic; + s16 v_av_min; + s16 v_av_max; + s16 v_hs_min; + s16 v_hs_max; + u16 reserved1; +} __packed; + + +struct tabla_mbhc_btn_detect_cfg { + s8 c[8]; + u8 nc; + u8 n_meas; + u8 mbhc_nsc; + u8 n_btn_meas; + u8 n_btn_con; + u8 num_btn; + u8 reserved0; + u8 reserved1; + u16 t_poll; + u16 t_bounce_wait; + u16 t_rel_timeout; + s16 v_btn_press_delta_sta; + s16 v_btn_press_delta_cic; + u16 t_btn0_timeout; + s16 _v_btn_low[0]; /* v_btn_low[num_btn] */ + s16 _v_btn_high[0]; /* v_btn_high[num_btn] */ + u8 _n_ready[TABLA_NUM_CLK_FREQS]; + u8 _n_cic[TABLA_NUM_CLK_FREQS]; + u8 _gain[TABLA_NUM_CLK_FREQS]; +} __packed; + +struct tabla_mbhc_imped_detect_cfg { + u8 _hs_imped_detect; + u8 _n_rload; + u8 _hph_keep_on; + u8 _repeat_rload_calc; + u16 _t_dac_ramp_time; + u16 _rhph_high; + u16 _rhph_low; + u16 _rload[0]; /* rload[n_rload] */ + u16 _alpha[0]; /* alpha[n_rload] */ + u16 _beta[3]; +} __packed; + +struct tabla_mbhc_config { + struct snd_soc_jack *headset_jack; + struct snd_soc_jack *button_jack; + bool read_fw_bin; + /* void* calibration contains: + * struct tabla_mbhc_general_cfg generic; + * struct tabla_mbhc_plug_detect_cfg plug_det; + * struct tabla_mbhc_plug_type_cfg plug_type; + * struct tabla_mbhc_btn_detect_cfg btn_det; + * struct tabla_mbhc_imped_detect_cfg imped_det; + * Note: various size depends on btn_det->num_btn + */ + void *calibration; + enum tabla_micbias_num micbias; + int (*mclk_cb_fn) (struct snd_soc_codec*, int, bool); + unsigned int mclk_rate; + unsigned int gpio; + unsigned int gpio_irq; + int gpio_level_insert; + /* swap_gnd_mic returns true if extern GND/MIC swap switch toggled */ + bool (*swap_gnd_mic) (struct snd_soc_codec *); +}; + +extern int tabla_hs_detect(struct snd_soc_codec *codec, + const struct tabla_mbhc_config *cfg); + +struct anc_header { + u32 reserved[3]; + u32 num_anc_slots; +}; + +extern int tabla_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, + bool dapm); + +extern void *tabla_mbhc_cal_btn_det_mp(const struct tabla_mbhc_btn_detect_cfg + *btn_det, + const enum tabla_mbhc_btn_det_mem mem); + +#define TABLA_MBHC_CAL_SIZE(buttons, rload) ( \ + sizeof(enum tabla_micbias_num) + \ + sizeof(struct tabla_mbhc_general_cfg) + \ + sizeof(struct tabla_mbhc_plug_detect_cfg) + \ + ((sizeof(s16) + sizeof(s16)) * buttons) + \ + sizeof(struct tabla_mbhc_plug_type_cfg) + \ + sizeof(struct tabla_mbhc_btn_detect_cfg) + \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + ((sizeof(u16) + sizeof(u16)) * rload) \ + ) + +#define TABLA_MBHC_CAL_GENERAL_PTR(cali) ( \ + (struct tabla_mbhc_general_cfg *) cali) +#define TABLA_MBHC_CAL_PLUG_DET_PTR(cali) ( \ + (struct tabla_mbhc_plug_detect_cfg *) \ + &(TABLA_MBHC_CAL_GENERAL_PTR(cali)[1])) +#define TABLA_MBHC_CAL_PLUG_TYPE_PTR(cali) ( \ + (struct tabla_mbhc_plug_type_cfg *) \ + &(TABLA_MBHC_CAL_PLUG_DET_PTR(cali)[1])) +#define TABLA_MBHC_CAL_BTN_DET_PTR(cali) ( \ + (struct tabla_mbhc_btn_detect_cfg *) \ + &(TABLA_MBHC_CAL_PLUG_TYPE_PTR(cali)[1])) +#define TABLA_MBHC_CAL_IMPED_DET_PTR(cali) ( \ + (struct tabla_mbhc_imped_detect_cfg *) \ + (((void *)&TABLA_MBHC_CAL_BTN_DET_PTR(cali)[1]) + \ + (TABLA_MBHC_CAL_BTN_DET_PTR(cali)->num_btn * \ + (sizeof(TABLA_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_low[0]) + \ + sizeof(TABLA_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_high[0])))) \ + ) + +/* minimum size of calibration data assuming there is only one button and + * one rload. + */ +#define TABLA_MBHC_CAL_MIN_SIZE ( \ + sizeof(struct tabla_mbhc_general_cfg) + \ + sizeof(struct tabla_mbhc_plug_detect_cfg) + \ + sizeof(struct tabla_mbhc_plug_type_cfg) + \ + sizeof(struct tabla_mbhc_btn_detect_cfg) + \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + (sizeof(u16) * 2)) + +#define TABLA_MBHC_CAL_BTN_SZ(cfg_ptr) ( \ + sizeof(struct tabla_mbhc_btn_detect_cfg) + \ + (cfg_ptr->num_btn * (sizeof(cfg_ptr->_v_btn_low[0]) + \ + sizeof(cfg_ptr->_v_btn_high[0])))) + +#define TABLA_MBHC_CAL_IMPED_MIN_SZ ( \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + sizeof(u16) * 2) + +#define TABLA_MBHC_CAL_IMPED_SZ(cfg_ptr) ( \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + (cfg_ptr->_n_rload * (sizeof(cfg_ptr->_rload[0]) + \ + sizeof(cfg_ptr->_alpha[0])))) + + diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 6c028c4706016e1f56bb36114d6ee9108e597736..cd2dfe8db587b700a74fb60bcfe72d06b674322f 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -643,6 +643,8 @@ SND_SOC_DAPM_INPUT("IN2RP:VXRP"), SND_SOC_DAPM_SUPPLY("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("LINEOUT_VMID_BUF", WM8993_ANTIPOP1, 7, 0, NULL, 0), + SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, in1l_pga, ARRAY_SIZE(in1l_pga)), SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, @@ -867,9 +869,11 @@ static const struct snd_soc_dapm_route lineout1_diff_routes[] = { }; static const struct snd_soc_dapm_route lineout1_se_routes[] = { + { "LINEOUT1N Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT1N Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT1N Mixer", "Right Output Switch", "Right Output PGA" }, + { "LINEOUT1P Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT1P Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, @@ -886,9 +890,11 @@ static const struct snd_soc_dapm_route lineout2_diff_routes[] = { }; static const struct snd_soc_dapm_route lineout2_se_routes[] = { + { "LINEOUT2N Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT2N Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT2N Mixer", "Right Output Switch", "Right Output PGA" }, + { "LINEOUT2P Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT2P Mixer", "Right Output Switch", "Right Output PGA" }, { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..36cbb23a27cc7f2c15c63e3413a33e826ef143ea --- /dev/null +++ b/sound/soc/msm/Kconfig @@ -0,0 +1,158 @@ +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_MSM7X27 + 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_LPASS_PCM + tristate + +config SND_SOC_MSM8660_LPAIF + tristate + +config SND_VOIP_PCM + tristate + +config SND_SOC_MSM_QDSP6_HDMI_AUDIO + tristate "Soc QDSP6 HDMI Audio DAI driver" + depends on FB_MSM_HDMI_MSM_PANEL + default n + help + To support HDMI Audio on MSM8960 over QDSP6. + +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_VOICE + bool "SoC Q6 voice driver for MSM8960" + depends on SND_SOC_MSM_QDSP6_INTF + default n + help + To add support for SoC voice 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 and APQ8064 boards" + depends on ARCH_MSM8960 || ARCH_APQ8064 + select SND_SOC_VOICE + select SND_SOC_QDSP6 + select SND_SOC_MSM_STUB + select SND_SOC_WCD9310 + select SND_SOC_WCD9304 + select SND_SOC_MSM_HOSTLESS_PCM + select SND_SOC_MSM_QDSP6_HDMI_AUDIO + select SND_SOC_CS8427 + default n + help + To add support for SoC audio on MSM8960 and APQ8064 boards + +config SND_SOC_MDM9615 + tristate "SoC Machine driver for MDM9615 boards" + depends on ARCH_MSM9615 + select SND_SOC_VOICE + select SND_SOC_QDSP6 + select SND_SOC_MSM_STUB + select SND_SOC_WCD9310 + select SND_SOC_MSM_HOSTLESS_PCM + select SND_DYNAMIC_MINORS + help + To add support for SoC audio on MDM9615 boards + +config SND_SOC_MSM8660_APQ + tristate "Soc Machine driver for APQ8060 WM8903 codec" + depends on ARCH_MSM8X60 + select SND_SOC_QDSP6 + select SND_SOC_WM8903 + select SND_SOC_MSM_STUB + default n + help + To add support for SoC audio on APQ8060 board +endmenu diff --git a/sound/soc/msm/Makefile b/sound/soc/msm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0df028cfc0d6454c2152d718a8f4f9b8bd45d6d6 --- /dev/null +++ b/sound/soc/msm/Makefile @@ -0,0 +1,86 @@ +# 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-lpass-objs := lpass-i2s.o lpass-dma.o +obj-$(CONFIG_SND_SOC_MSM8660_LPAIF) += snd-soc-lpass.o + +snd-soc-lpass-pcm-objs := lpass-pcm.o +obj-$(CONFIG_SND_SOC_LPASS_PCM) += snd-soc-lpass-pcm.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-lpass-dma-objs := lpass-dma.o +obj-$(CONFIG_SND_SOC_MSM8X60) += snd-soc-lpass-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-multi-ch-pcm-q6.o msm-pcm-routing.o msm-dai-fe.o msm-compr-q6.o msm-dai-stub.o +obj-$(CONFIG_SND_SOC_MSM_QDSP6_HDMI_AUDIO) += msm-dai-q6-hdmi.o +obj-$(CONFIG_SND_SOC_VOICE) += msm-pcm-voice.o msm-pcm-voip.o +snd-soc-qdsp6-objs += msm-pcm-lpa.o msm-pcm-afe.o +obj-$(CONFIG_SND_SOC_QDSP6) += snd-soc-qdsp6.o + +snd-soc-msm8960-objs := msm8960.o apq8064.o msm8930.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 + +snd-soc-msm8660-apq-objs := msm8660-apq-wm8903.o +obj-$(CONFIG_SND_SOC_MSM8660_APQ) += snd-soc-msm8660-apq.o + +# for MDM 9615 sound card driver +snd-soc-mdm9615-objs := mdm9615.o +obj-$(CONFIG_SND_SOC_MDM9615) += snd-soc-mdm9615.o + +# for MSM 8974 sound card driver +obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += qdsp6v2/ +snd-soc-msm8974-objs := msm8974.o +obj-$(CONFIG_SND_SOC_MSM8974) += snd-soc-msm8974.o + +snd-soc-qdsp6v2-objs := msm-dai-fe.o msm-dai-stub.o +obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o + diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c new file mode 100644 index 0000000000000000000000000000000000000000..353ffd67cb5528e268047f74a69f75668b22256a --- /dev/null +++ b/sound/soc/msm/apq8064.c @@ -0,0 +1,1870 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-routing.h" +#include "../codecs/wcd9310.h" + +/* 8064 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MSM8064_SPK_ON 1 +#define MSM8064_SPK_OFF 0 + +#define MSM_SLIM_0_RX_MAX_CHANNELS 2 +#define MSM_SLIM_0_TX_MAX_CHANNELS 4 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define GPIO_AUX_PCM_DOUT 43 +#define GPIO_AUX_PCM_DIN 44 +#define GPIO_AUX_PCM_SYNC 45 +#define GPIO_AUX_PCM_CLK 46 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +#define JACK_DETECT_GPIO 38 + +/* Shared channel numbers for Slimbus ports that connect APQ to MDM. */ +enum { + SLIM_1_RX_1 = 145, /* BT-SCO and USB TX */ + SLIM_1_TX_1 = 146, /* BT-SCO and USB RX */ + SLIM_3_RX_1 = 151, /* External echo-cancellation ref */ + SLIM_3_RX_2 = 152, /* External echo-cancellation ref */ + SLIM_3_TX_1 = 147, /* HDMI RX */ + SLIM_4_TX_1 = 148, /* In-call recording RX */ + SLIM_4_TX_2 = 149, /* In-call recording RX */ + SLIM_4_RX_1 = 150, /* In-call music delivery TX */ +}; + +enum { + INCALL_REC_MONO, + INCALL_REC_STEREO, +}; + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm_spk_control; +static int msm_ext_bottom_spk_pamp; +static int msm_ext_top_spk_pamp; +static int msm_slim_0_rx_ch = 1; +static int msm_slim_0_tx_ch = 1; +static int msm_slim_3_rx_ch = 1; + +static int msm_btsco_rate = BTSCO_RATE_8KHZ; +static int msm_btsco_ch = 1; + +static int rec_mode = INCALL_REC_MONO; + +static struct clk *codec_clk; +static int clk_users; + +static int msm_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int apq8064_hs_detect_use_gpio = -1; +module_param(apq8064_hs_detect_use_gpio, int, 0444); +MODULE_PARM_DESC(apq8064_hs_detect_use_gpio, "Use GPIO for headset detection"); + +static bool apq8064_hs_detect_use_firmware; +module_param(apq8064_hs_detect_use_firmware, bool, 0444); +MODULE_PARM_DESC(apq8064_hs_detect_use_firmware, "Use firmware for headset " + "detection"); + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void msm_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + 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 (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_bottom_spk_pamp |= spk; + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_top_spk_pamp |= spk; + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!msm_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + if (msm_spk_control == MSM8064_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + ucontrol->value.integer.value[0] = msm_spk_control; + return 0; +} +static int msm_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 (msm_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm_spk_control = ucontrol->value.integer.value[0]; + msm_ext_control(codec); + return 1; +} +static int msm_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)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + tabla_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } + return 0; +} + +static int msm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, 12288000); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(w->codec, 1, true); + + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + break; + case SND_SOC_DAPM_POST_PMD: + + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users == 0) + return 0; + + clk_users--; + + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + + tabla_mclk_enable(w->codec, 0, true); + clk_disable_unprepare(codec_clk); + } + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget apq8064_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm_spkramp_event), + + /************ Analog MICs ************/ + /** + * Analog mic7 (Front Top) on Liquid. + * Used as Handset mic on CDP. + */ + SND_SOC_DAPM_MIC("Analog mic7", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + /*********** Digital Mics ***************/ + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), +}; + +static const struct snd_soc_dapm_route apq8064_common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /************ Analog MIC Paths ************/ + + /* Headset Mic */ + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /* Headset ANC microphones */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, +}; + +static const struct snd_soc_dapm_route apq8064_mtp_audio_map[] = { + + /************ Digital MIC Paths ************/ + + /* + * Digital Mic1 (Front bottom Left) on MTP. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2 (Front bottom right) on MTP. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3 (Back bottom) on MTP. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4 (Back top) on MTP. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5 (Top front Mic) on MTP. + * Conncted to DMIC6 Input on Tabla codec. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + +}; + +static const struct snd_soc_dapm_route apq8064_liquid_cdp_audio_map[] = { + + /************ Analog MIC Paths ************/ + /** + * Analog mic7 (Front Top Mic) on Liquid. + * Used as Handset mic on CDP. + * Not there on MTP. + */ + {"AMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Analog mic7"}, + + + /************ Digital MIC Paths ************/ + /** + * The digital Mic routes are setup considering + * Liquid as default device. + */ + + /** + * Digital Mic1 (Front bottom left corner) on Liquid. + * Digital Mic2 (Front bottom right) on MTP. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2 (Front left side) on Liquid. + * Digital Mic GM2 on CDP mainboard. + * Not there on MTP. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Front bottom left of middle on Liquid. + * Digital Mic5 (Top front Mic) on MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC6 Input on Tabla codec. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back bottom on Liquid. + * Digital Mic GM3 on CDP mainboard. + * Top Front Mic on MTP. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front bottom right of middle on Liquid. + * Digital Mic GM6 on CDP mainboard. + * Not there on MTP. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic5"}, + + /* Digital Mic6 (Front bottom right corner) on Liquid. + * Digital Mic1 (Front bottom Left) on MTP. + * Digital Mic GM4 on CDP. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int msm_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_rx_ch - 1; + return 0; +} + +static int msm_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + return 1; +} + +static int msm_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_tx_ch - 1; + return 0; +} + +static int msm_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + return 1; +} + +static int msm_slim_3_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_3_rx_ch = %d\n", __func__, + msm_slim_3_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_3_rx_ch - 1; + return 0; +} + +static int msm_slim_3_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_3_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_3_rx_ch = %d\n", __func__, + msm_slim_3_rx_ch); + return 1; +} + +static int msm_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_btsco_rate = %d", __func__, + msm_btsco_rate); + ucontrol->value.integer.value[0] = msm_btsco_rate; + return 0; +} + +static int msm_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + msm_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: msm_btsco_rate = %d\n", __func__, + msm_btsco_rate); + return 0; +} + +static int msm_incall_rec_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rec_mode; + return 0; +} + +static int msm_incall_rec_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + rec_mode = ucontrol->value.integer.value[0]; + pr_debug("%s: rec_mode:%d\n", __func__, rec_mode); + + return 0; +} + +static const struct snd_kcontrol_new tabla_msm_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm_enum[0], msm_get_spk, + msm_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm_enum[1], + msm_slim_0_rx_ch_get, msm_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm_enum[2], + msm_slim_0_tx_ch_get, msm_slim_0_tx_ch_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm_btsco_enum[0], + msm_btsco_rate_get, msm_btsco_rate_put), + SOC_SINGLE_EXT("Incall Rec Mode", SND_SOC_NOPM, 0, 1, 0, + msm_incall_rec_mode_get, msm_incall_rec_mode_put), + SOC_ENUM_EXT("SLIM_3_RX Channels", msm_enum[1], + msm_slim_3_rx_ch_get, msm_slim_3_rx_ch_put), +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + unsigned int user_set_tx_ch = 0; + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + pr_debug("%s: rx_0_ch=%d\n", __func__, msm_slim_0_rx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + + if (codec_dai->id == 2) + user_set_tx_ch = msm_slim_0_tx_ch; + else if (codec_dai->id == 4) + user_set_tx_ch = params_channels(params); + else if (codec_dai->id == 5) { + /* DAI 5 is used for external EC reference from codec. + * Since Rx is fed as reference for EC, the config of + * this DAI is based on that of the Rx path. + */ + user_set_tx_ch = msm_slim_0_rx_ch; + } + + pr_debug("%s: %s_tx_dai_id_%d_ch=%d\n", __func__, + codec_dai->name, codec_dai->id, user_set_tx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, + user_set_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + user_set_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + + } +end: + return ret; +} + +static int msm_stubrx_init(struct snd_soc_pcm_runtime *rtd) +{ + rtd->pmdown_time = 0; + + return 0; +} + +static int msm_slimbus_1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch = SLIM_1_RX_1, tx_ch = SLIM_1_TX_1; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: APQ BT/USB TX -> SLIMBUS_1_RX -> MDM TX shared ch %d\n", + __func__, rx_ch); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, 1, &rx_ch); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_1 RX channel map\n", + __func__, ret); + + goto end; + } + } else { + pr_debug("%s: MDM RX -> SLIMBUS_1_TX -> APQ BT/USB Rx shared ch %d\n", + __func__, tx_ch); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 1, &tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_1 TX channel map\n", + __func__, ret); + + goto end; + } + } + +end: + return ret; +} + +static int msm_slimbus_3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[2] = {SLIM_3_RX_1, SLIM_3_RX_2}; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: slim_3_rx_ch %d, sch %d %d\n", + __func__, msm_slim_3_rx_ch, + rx_ch[0], rx_ch[1]); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm_slim_3_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_3 RX channel map\n", + __func__, ret); + + goto end; + } + } else { + pr_err("%s: SLIMBUS_3_TX not defined for this DAI\n", __func__); + } + +end: + return ret; +} + +static int msm_slimbus_4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch = SLIM_4_RX_1, tx_ch[2]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: APQ Incall Playback SLIMBUS_4_RX -> MDM TX shared ch %d\n", + __func__, rx_ch); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, 1, &rx_ch); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_4 RX channel map\n", + __func__, ret); + + } + } else { + if (rec_mode == INCALL_REC_STEREO) { + tx_ch[0] = SLIM_4_TX_1; + tx_ch[1] = SLIM_4_TX_2; + ret = snd_soc_dai_set_channel_map(cpu_dai, 2, + tx_ch, 0, 0); + } else { + tx_ch[0] = SLIM_4_TX_1; + ret = snd_soc_dai_set_channel_map(cpu_dai, 1, + tx_ch, 0, 0); + } + pr_debug("%s: Incall Record shared tx_ch[0]:%d, tx_ch[1]:%d\n", + __func__, tx_ch[0], tx_ch[1]); + + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_4 TX channel map\n", + __func__, ret); + + } + } + + return ret; +} + +static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + uint32_t revision; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + /*if (machine_is_msm_liquid()) { + top_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(19)); + bottom_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(18)); + }*/ + + snd_soc_dapm_new_controls(dapm, apq8064_dapm_widgets, + ARRAY_SIZE(apq8064_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, apq8064_common_audio_map, + ARRAY_SIZE(apq8064_common_audio_map)); + + if (machine_is_apq8064_mtp()) { + snd_soc_dapm_add_routes(dapm, apq8064_mtp_audio_map, + ARRAY_SIZE(apq8064_mtp_audio_map)); + } else { + snd_soc_dapm_add_routes(dapm, apq8064_liquid_cdp_audio_map, + ARRAY_SIZE(apq8064_liquid_cdp_audio_map)); + } + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + /* APQ8064 Rev 1.1 CDP and Liquid have mechanical switch */ + revision = socinfo_get_version(); + if (apq8064_hs_detect_use_gpio != -1) { + if (apq8064_hs_detect_use_gpio == 1) + pr_debug("%s: MBHC mechanical is enabled by request\n", + __func__); + else if (apq8064_hs_detect_use_gpio == 0) + pr_debug("%s: MBHC mechanical is disabled by request\n", + __func__); + else + pr_warn("%s: Invalid hs_detect_use_gpio %d\n", __func__, + apq8064_hs_detect_use_gpio); + } else if (SOCINFO_VERSION_MAJOR(revision) == 0) { + pr_warn("%s: Unknown HW revision detected %d.%d\n", __func__, + SOCINFO_VERSION_MAJOR(revision), + SOCINFO_VERSION_MINOR(revision)); + } else if ((SOCINFO_VERSION_MAJOR(revision) == 1 && + SOCINFO_VERSION_MINOR(revision) >= 1 && + (machine_is_apq8064_cdp() || + machine_is_apq8064_liquid())) || + SOCINFO_VERSION_MAJOR(revision) > 1) { + pr_debug("%s: MBHC mechanical switch available APQ8064 " + "detected\n", __func__); + apq8064_hs_detect_use_gpio = 1; + } + + if (apq8064_hs_detect_use_gpio == 1) { + pr_debug("%s: Using MBHC mechanical switch\n", __func__); + mbhc_cfg.gpio = JACK_DETECT_GPIO; + mbhc_cfg.gpio_irq = gpio_to_irq(JACK_DETECT_GPIO); + err = gpio_request(mbhc_cfg.gpio, "MBHC_HS_DETECT"); + if (err < 0) { + pr_err("%s: gpio_request %d failed %d\n", __func__, + mbhc_cfg.gpio, err); + return err; + } + gpio_direction_input(JACK_DETECT_GPIO); + } else + pr_debug("%s: Not using MBHC mechanical switch\n", __func__); + + mbhc_cfg.read_fw_bin = apq8064_hs_detect_use_firmware; + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int msm_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 = msm_slim_0_rx_ch; + + return 0; +} + +static int msm_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 = msm_slim_0_tx_ch; + + return 0; +} + +static int msm_slim_3_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 = msm_slim_3_rx_ch; + + return 0; +} + +static int msm_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 msm_hdmi_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 channels->min %u channels->max %u ()\n", __func__, + channels->min, channels->max); + + rate->min = rate->max = 48000; + + return 0; +} + +static int msm_btsco_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); + + rate->min = rate->max = msm_btsco_rate; + channels->min = channels->max = msm_btsco_ch; + + return 0; +} +static int msm_auxpcm_be_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); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} + +static int msm_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int msm_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} +static int msm_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static int msm_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return 0; +} + +static void msm_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm_aux_pcm_free_gpios(); +} + +static void msm_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops msm_be_ops = { + .startup = msm_startup, + .hw_params = msm_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_auxpcm_be_ops = { + .startup = msm_auxpcm_startup, + .shutdown = msm_auxpcm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_1_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_1_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_3_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_3_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_4_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_4_hw_params, + .shutdown = msm_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-multi-ch-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + /* .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, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + /* .be_id = do not care */ + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM8960 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "Voice Stub", + .stream_name = "Voice Stub", + .cpu_dai_name = "VOICE_STUB", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* 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 = &msm_audrx_init, + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .ops = &msm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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 = msm_slim_0_tx_be_hw_params_fixup, + .ops = &msm_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, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + }, + { + .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 = msm_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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 = msm_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .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_HDMI_RX, + .be_hw_params_fixup = msm_hdmi_be_hw_params_fixup, + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .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_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .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_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .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_AUXPCM_RX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + .ops = &msm_auxpcm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .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_AUXPCM_TX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + }, + { + .name = LPASS_BE_STUB_RX, + .stream_name = "Stub Playback", + .cpu_dai_name = "msm-dai-stub", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx2", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_EXTPROC_RX, + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .init = &msm_stubrx_init, + .ops = &msm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_STUB_TX, + .stream_name = "Stub Capture", + .cpu_dai_name = "msm-dai-stub", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_EXTPROC_TX, + .be_hw_params_fixup = msm_slim_0_tx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + { + .name = LPASS_BE_SLIMBUS_1_RX, + .stream_name = "Slimbus1 Playback", + .cpu_dai_name = "msm-dai-q6.16386", + .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_SLIMBUS_1_RX, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + .ops = &msm_slimbus_1_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + + }, + { + .name = LPASS_BE_SLIMBUS_1_TX, + .stream_name = "Slimbus1 Capture", + .cpu_dai_name = "msm-dai-q6.16387", + .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_SLIMBUS_1_TX, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + .ops = &msm_slimbus_1_be_ops, + }, + { + .name = "SLIMBUS_2 Hostless", + .stream_name = "SLIMBUS_2 Hostless", + .cpu_dai_name = "msm-dai-q6.16389", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx2", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm_be_ops, + }, + + /* Incall Music Back End DAI Link */ + { + .name = LPASS_BE_SLIMBUS_4_RX, + .stream_name = "Slimbus4 Playback", + .cpu_dai_name = "msm-dai-q6.16392", + .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_SLIMBUS_4_RX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &msm_slimbus_4_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + /* Incall Record Back End DAI Link */ + { + .name = LPASS_BE_SLIMBUS_4_TX, + .stream_name = "Slimbus4 Capture", + .cpu_dai_name = "msm-dai-q6.16393", + .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_SLIMBUS_4_TX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &msm_slimbus_4_be_ops, + }, + { + .name = LPASS_BE_STUB_1_TX, + .stream_name = "Stub1 Capture", + .cpu_dai_name = "msm-dai-stub", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx3", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_EXTPROC_EC_TX, + /* This BE is used for external EC reference from codec. Since + * Rx is fed as reference for EC, the config of this DAI is + * based on that of the Rx path. + */ + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + { + + .name = LPASS_BE_SLIMBUS_3_RX, + .stream_name = "Slimbus3 Playback", + .cpu_dai_name = "msm-dai-q6.16390", + .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_SLIMBUS_3_RX, + .be_hw_params_fixup = msm_slim_3_rx_be_hw_params_fixup, + .ops = &msm_slimbus_3_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, +}; + +struct snd_soc_card snd_soc_card_msm = { + .name = "apq8064-tabla-snd-card", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), + .controls = tabla_msm_controls, + .num_controls = ARRAY_SIZE(tabla_msm_controls), +}; + +static struct platform_device *msm_snd_device; + +static int msm_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), 0); + + return 0; +} +static void msm_free_headset_mic_gpios(void) +{ + if (msm_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm_audio_init(void) +{ + int ret; + + if (!cpu_is_apq8064() || (socinfo_get_id() == 130)) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm_headset_gpios_configured = 0; + } else + msm_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + if (!cpu_is_apq8064() || (socinfo_get_id() == 130)) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm_free_headset_mic_gpios(); + platform_device_unregister(msm_snd_device); + if (mbhc_cfg.gpio) + gpio_free(mbhc_cfg.gpio); + kfree(mbhc_cfg.calibration); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC msm"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/lpass-dma.c b/sound/soc/msm/lpass-dma.c new file mode 100644 index 0000000000000000000000000000000000000000..66c183615ce0260889148085e724fe59f5207d29 --- /dev/null +++ b/sound/soc/msm/lpass-dma.c @@ -0,0 +1,488 @@ +/* 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 "lpass-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)); + mb(); + 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)); + mb(); +} + +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; + mb(); + 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)); + mb(); + + + 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); + + mb(); + dai_print_state(dma_ch); + return 0; +} + +int wait_for_dma_cnt_stop(uint32_t dma_ch) +{ + uint32_t dma_per_cnt_reg_val, dma_per_cnt, prev_dma_per_cnt; + uint32_t i; + + pr_info("%s dma_ch %u\n", __func__, dma_ch); + + dma_per_cnt_reg_val = readl_relaxed(dai_info.base + + LPAIF_DMA_PER_CNT(dma_ch)); + + dma_per_cnt = + ((LPAIF_DMA_PER_CNT_PER_CNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_PER_CNT_SHIFT) - + ((LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT); + + prev_dma_per_cnt = dma_per_cnt; + + i = 1; + pr_info("%s: i = %u dma_per_cnt_reg_val 0x%08x , dma_per_cnt %u\n", + __func__, i, dma_per_cnt_reg_val, dma_per_cnt); + + while (i <= 50) { + msleep(50); + + dma_per_cnt_reg_val = readl_relaxed(dai_info.base + + LPAIF_DMA_PER_CNT(dma_ch)); + + dma_per_cnt = + ((LPAIF_DMA_PER_CNT_PER_CNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_PER_CNT_SHIFT) - + ((LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT); + + i++; + + pr_info("%s: i = %u dma_per_cnt_reg_val 0x%08x , dma_per_cnt %u\n", + __func__, i, dma_per_cnt_reg_val, dma_per_cnt); + + if (prev_dma_per_cnt == dma_per_cnt) + break; + + prev_dma_per_cnt = dma_per_cnt; + } + 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)); + + mb(); + + 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)); + + mb(); + + 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/lpass-i2s.c b/sound/soc/msm/lpass-i2s.c new file mode 100644 index 0000000000000000000000000000000000000000..9583c52c9a6eb6237bab38d1083872c5bcfd906b --- /dev/null +++ b/sound/soc/msm/lpass-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/lpass-pcm.c b/sound/soc/msm/lpass-pcm.c new file mode 100644 index 0000000000000000000000000000000000000000..efd7c06ac64299ecb90796257b64711e0e5f01ca --- /dev/null +++ b/sound/soc/msm/lpass-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 "lpass-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/lpass-pcm.h b/sound/soc/msm/lpass-pcm.h new file mode 100644 index 0000000000000000000000000000000000000000..3bec9a7f41451201624540eeca0055b220213147 --- /dev/null +++ b/sound/soc/msm/lpass-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/mdm9615.c b/sound/soc/msm/mdm9615.c new file mode 100644 index 0000000000000000000000000000000000000000..41a9c48f558ad2c56292527213d4aadec8987e52 --- /dev/null +++ b/sound/soc/msm/mdm9615.c @@ -0,0 +1,2041 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "../codecs/wcd9310.h" +#include + +/* 9615 machine driver */ + +#define PM8018_GPIO_BASE NR_GPIO_IRQS +#define PM8018_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8018_GPIO_BASE) + +#define MDM9615_SPK_ON 1 +#define MDM9615_SPK_OFF 0 + +#define MDM9615_SLIM_0_RX_MAX_CHANNELS 2 +#define MDM9615_SLIM_0_TX_MAX_CHANNELS 4 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define GPIO_AUX_PCM_DOUT 63 +#define GPIO_AUX_PCM_DIN 64 +#define GPIO_AUX_PCM_SYNC 65 +#define GPIO_AUX_PCM_CLK 66 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +/* + * Added for I2S + */ +#define GPIO_SPKR_I2S_MCLK 24 +#define GPIO_PRIM_I2S_SCK 20 +#define GPIO_PRIM_I2S_DOUT 23 +#define GPIO_PRIM_I2S_WS 21 +#define GPIO_PRIM_I2S_DIN 22 +#define GPIO_SEC_I2S_SCK 25 +#define GPIO_SEC_I2S_WS 26 +#define GPIO_SEC_I2S_DOUT 28 +#define GPIO_SEC_I2S_DIN 27 + +static struct gpiomux_setting cdc_i2s_mclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_sclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_dout = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_ws = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_din = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + + +static struct msm_gpiomux_config msm9615_audio_prim_i2s_codec_configs[] = { + { + .gpio = GPIO_SPKR_I2S_MCLK, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_mclk, + }, + }, + { + .gpio = GPIO_PRIM_I2S_SCK, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_sclk, + }, + }, + { + .gpio = GPIO_PRIM_I2S_DOUT, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_dout, + }, + }, + { + .gpio = GPIO_PRIM_I2S_WS, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_ws, + }, + }, + { + .gpio = GPIO_PRIM_I2S_DIN, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_din, + }, + }, +}; + +/* Physical address for LPA CSR + * LPA SIF mux registers. These are + * ioremap( ) for Virtual address. + */ +#define LPASS_CSR_BASE 0x28000000 +#define LPA_IF_BASE 0x28100000 +#define SIF_MUX_REG_BASE (LPASS_CSR_BASE + 0x00000000) +#define LPA_IF_REG_BASE (LPA_IF_BASE + 0x00000000) +#define LPASS_SIF_MUX_ADDR (SIF_MUX_REG_BASE + 0x00004000) +#define LPAIF_SPARE_ADDR (LPA_IF_REG_BASE + 0x00000070) +/* SIF & SPARE MUX Values */ +#define MSM_SIF_FUNC_PCM 0 +#define MSM_SIF_FUNC_I2S_MIC 1 +#define MSM_SIF_FUNC_I2S_SPKR 2 +#define MSM_LPAIF_SPARE_DISABLE 0x0 +#define MSM_LPAIF_SPARE_BOTH_ENABLE 0x3 + +/* I2S INTF CTL */ +#define MSM_INTF_PRIM 0 +#define MSM_INTF_SECN 1 +#define MSM_INTF_BOTH 2 + +/* I2S Dir CTL */ +#define MSM_DIR_RX 0 +#define MSM_DIR_TX 1 +#define MSM_DIR_BOTH 2 +#define MSM_DIR_MAX 3 + +/* I2S HW Params */ +#define NO_OF_BITS_PER_SAMPLE 16 +#define I2S_MIC_SCLK_RATE 1536000 +static int msm9615_i2s_rx_ch = 1; +static int msm9615_i2s_tx_ch = 1; +static int msm9615_i2s_spk_control; +/* SIF mux bit mask & shift */ +#define LPASS_SIF_MUX_CTL_PRI_MUX_SEL_BMSK 0x30000 +#define LPASS_SIF_MUX_CTL_PRI_MUX_SEL_SHFT 0x10 +#define LPASS_SIF_MUX_CTL_SEC_MUX_SEL_BMSK 0x3 +#define LPASS_SIF_MUX_CTL_SEC_MUX_SEL_SHFT 0x0 + +#define LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_BMSK 0x3 +#define LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_SHFT 0x2 +#define LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_BMSK 0x3 +#define LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_SHFT 0x0 + +static u32 spare_shadow; +static u32 sif_shadow; + + +struct msm_i2s_mux_ctl { + const u8 sifconfig; + const u8 spareconfig; +}; +struct msm_clk { + struct clk *osr_clk; + struct clk *bit_clk; + int clk_enable; +}; +struct msm_i2s_clk { + struct msm_clk rx_clk; + struct msm_clk tx_clk; +}; +struct msm_i2s_ctl { + struct msm_i2s_clk prim_clk; + struct msm_i2s_clk sec_clk; + struct msm_i2s_mux_ctl mux_ctl[MSM_DIR_MAX]; + u8 intf_status[MSM_INTF_BOTH][MSM_DIR_BOTH]; + void *sif_virt_addr; + void *spare_virt_addr; +}; +static struct msm_i2s_ctl msm9x15_i2s_ctl = { + {{NULL, NULL, 0}, {NULL, NULL, 0} }, /* prim_clk */ + {{NULL, NULL, 0}, {NULL, NULL, 0} }, /* sec_clk */ + /* mux_ctl */ + { + /* Rx path only */ + { MSM_SIF_FUNC_I2S_SPKR, MSM_LPAIF_SPARE_DISABLE }, + /* Tx path only */ + { MSM_SIF_FUNC_I2S_MIC, MSM_LPAIF_SPARE_DISABLE }, + /* Rx + Tx path only */ + { MSM_SIF_FUNC_I2S_SPKR, MSM_LPAIF_SPARE_BOTH_ENABLE }, + }, + /* intf_status */ + { + /* Prim I2S */ + {0, 0}, + /* Sec I2S */ + {0, 0} + }, + /* sif_virt_addr */ + NULL, + /* spare_virt_addr */ + NULL, +}; + +enum msm9x15_set_i2s_clk { + MSM_I2S_CLK_SET_FALSE, + MSM_I2S_CLK_SET_TRUE, + MSM_I2S_CLK_SET_RATE0, +}; +/* + * Added for I2S + */ + +static u32 top_spk_pamp_gpio = PM8018_GPIO_PM_TO_SYS(3); +static u32 bottom_spk_pamp_gpio = PM8018_GPIO_PM_TO_SYS(5); +static int mdm9615_spk_control; +static int mdm9615_ext_bottom_spk_pamp; +static int mdm9615_ext_top_spk_pamp; +static int mdm9615_slim_0_rx_ch = 1; +static int mdm9615_slim_0_tx_ch = 1; + +static int mdm9615_btsco_rate = BTSCO_RATE_8KHZ; +static int mdm9615_btsco_ch = 1; + +static struct clk *codec_clk; +static int clk_users; + +static int mdm9615_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int mdm9615_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = mdm9615_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void mdm9615_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + 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 (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void mdm9615_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + mdm9615_ext_bottom_spk_pamp |= spk; + + if ((mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + mdm9615_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + mdm9615_ext_top_spk_pamp |= spk; + + if ((mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + mdm9615_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void mdm9615_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!mdm9615_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + mdm9615_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!mdm9615_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + mdm9615_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void mdm9615_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: mdm9615_spk_control = %d", __func__, mdm9615_spk_control); + if (mdm9615_spk_control == MDM9615_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int mdm9615_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_spk_control = %d", __func__, mdm9615_spk_control); + ucontrol->value.integer.value[0] = mdm9615_spk_control; + return 0; +} +static int mdm9615_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 (mdm9615_spk_control == ucontrol->value.integer.value[0]) + return 0; + + mdm9615_spk_control = ucontrol->value.integer.value[0]; + mdm9615_ext_control(codec); + return 1; +} +static int mdm9615_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)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + mdm9615_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + mdm9615_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + mdm9615_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + mdm9615_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + mdm9615_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + mdm9615_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + mdm9615_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + mdm9615_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} +static int mdm9615_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + if (IS_ERR(codec_clk)) { + + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + tabla_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } + return 0; +} + +static int mdm9615_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return mdm9615_enable_codec_ext_clk(w->codec, 1, true); + case SND_SOC_DAPM_POST_PMD: + return mdm9615_enable_codec_ext_clk(w->codec, 0, true); + } + return 0; +} + +static const struct snd_soc_dapm_widget mdm9615_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + mdm9615_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", mdm9615_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", mdm9615_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", mdm9615_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", mdm9615_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Digital Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Front Bottom right Digital Mic on Fluid and MTP. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front top Digital Mic on Fluid. + * Digital Mic GM3 on CDP mainboard. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + + /* Tabla digital Mic6 - back bottom digital Mic on Liquid and + * bottom mic on CDP. FLUID/MTP do not have dmic6 installed. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum mdm9615_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum mdm9615_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int mdm9615_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_slim_0_rx_ch = %d\n", __func__, + mdm9615_slim_0_rx_ch); + ucontrol->value.integer.value[0] = mdm9615_slim_0_rx_ch - 1; + return 0; +} + +static int mdm9615_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mdm9615_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: mdm9615_slim_0_rx_ch = %d\n", __func__, + mdm9615_slim_0_rx_ch); + return 1; +} + +static int mdm9615_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_slim_0_tx_ch = %d\n", __func__, + mdm9615_slim_0_tx_ch); + ucontrol->value.integer.value[0] = mdm9615_slim_0_tx_ch - 1; + return 0; +} + +static int mdm9615_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mdm9615_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: mdm9615_slim_0_tx_ch = %d\n", __func__, + mdm9615_slim_0_tx_ch); + return 1; +} + +static int mdm9615_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_btsco_rate = %d", __func__, mdm9615_btsco_rate); + ucontrol->value.integer.value[0] = mdm9615_btsco_rate; + return 0; +} + +static int mdm9615_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + mdm9615_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + mdm9615_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + mdm9615_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: mdm9615_btsco_rate = %d\n", __func__, mdm9615_btsco_rate); + return 0; +} + +static const struct snd_kcontrol_new tabla_mdm9615_controls[] = { + SOC_ENUM_EXT("Speaker Function", mdm9615_enum[0], mdm9615_get_spk, + mdm9615_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", mdm9615_enum[1], + mdm9615_slim_0_rx_ch_get, mdm9615_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", mdm9615_enum[2], + mdm9615_slim_0_tx_ch_get, mdm9615_slim_0_tx_ch_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", mdm9615_btsco_enum[0], + mdm9615_btsco_rate_get, mdm9615_btsco_rate_put), +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm9615_i2s_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 (msm9615_i2s_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm9615_i2s_spk_control = ucontrol->value.integer.value[0]; + mdm9615_ext_control(codec); + return 1; +} + +static int mdm9615_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + pr_debug("%s: ch=%d\n", __func__, + mdm9615_slim_0_rx_ch); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + mdm9615_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + mdm9615_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + mdm9615_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + mdm9615_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } +end: + return ret; +} + +static int msm9615_i2s_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm9615_i2s_rx_ch = %d\n", __func__, + msm9615_i2s_rx_ch); + ucontrol->value.integer.value[0] = msm9615_i2s_rx_ch - 1; + return 0; +} + +static int msm9615_i2s_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm9615_i2s_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm9615_i2s_rx_ch = %d\n", __func__, + msm9615_i2s_rx_ch); + return 1; +} + +static int msm9615_i2s_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm9615_i2s_tx_ch = %d\n", __func__, + msm9615_i2s_tx_ch); + ucontrol->value.integer.value[0] = msm9615_i2s_tx_ch - 1; + return 0; +} + +static int msm9615_i2s_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm9615_i2s_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm9615_i2s_tx_ch = %d\n", __func__, + msm9615_i2s_tx_ch); + return 1; +} + +static int msm9615_i2s_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm9615_spk_control = %d", __func__, mdm9615_spk_control); + ucontrol->value.integer.value[0] = msm9615_i2s_spk_control; + return 0; +} + +static const struct snd_kcontrol_new tabla_msm9615_i2s_controls[] = { + SOC_ENUM_EXT("Speaker Function", mdm9615_enum[0], msm9615_i2s_get_spk, + msm9615_i2s_set_spk), + SOC_ENUM_EXT("PRI_RX Channels", mdm9615_enum[1], + msm9615_i2s_rx_ch_get, msm9615_i2s_rx_ch_put), + SOC_ENUM_EXT("PRI_TX Channels", mdm9615_enum[2], + msm9615_i2s_tx_ch_get, msm9615_i2s_tx_ch_put), +}; + +static int msm9615_i2s_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; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + snd_soc_dapm_new_controls(dapm, mdm9615_dapm_widgets, + ARRAY_SIZE(mdm9615_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL| + SND_JACK_OC_HPHR), &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + err = tabla_hs_detect(codec, &mbhc_cfg); + return err; +} + +static int msm9615_i2s_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); + rate->min = rate->max = 48000; + channels->min = channels->max = msm9615_i2s_rx_ch; + + return 0; +} + +static int msm9615_i2s_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); + rate->min = rate->max = 48000; + + channels->min = channels->max = msm9615_i2s_tx_ch; + + return 0; +} + +static int mdm9615_i2s_free_gpios(u8 i2s_intf, u8 i2s_dir) +{ + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + if (i2s_intf == MSM_INTF_PRIM) { + if (i2s_dir == MSM_DIR_RX) + gpio_free(GPIO_PRIM_I2S_DOUT); + if (i2s_dir == MSM_DIR_TX) + gpio_free(GPIO_PRIM_I2S_DIN); + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + gpio_free(GPIO_PRIM_I2S_SCK); + gpio_free(GPIO_PRIM_I2S_WS); + } + } else if (i2s_intf == MSM_INTF_SECN) { + if (i2s_dir == MSM_DIR_RX) + gpio_free(GPIO_SEC_I2S_DOUT); + if (i2s_dir == MSM_DIR_TX) + gpio_free(GPIO_SEC_I2S_DIN); + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + gpio_free(GPIO_SEC_I2S_WS); + gpio_free(GPIO_SEC_I2S_SCK); + } + } + return 0; +} + +int msm9615_i2s_intf_dir_sel(const char *cpu_dai_name, + u8 *i2s_intf, u8 *i2s_dir) +{ + int ret = 0; + if (i2s_intf == NULL || i2s_dir == NULL || cpu_dai_name == NULL) { + ret = 1; + goto err; + } + if (!strncmp(cpu_dai_name, "msm-dai-q6.0", 12)) { + *i2s_intf = MSM_INTF_PRIM; + *i2s_dir = MSM_DIR_RX; + } else if (!strncmp(cpu_dai_name, "msm-dai-q6.1", 12)) { + *i2s_intf = MSM_INTF_PRIM; + *i2s_dir = MSM_DIR_TX; + } else if (!strncmp(cpu_dai_name, "msm-dai-q6.4", 12)) { + *i2s_intf = MSM_INTF_SECN; + *i2s_dir = MSM_DIR_RX; + } else if (!strncmp(cpu_dai_name, "msm-dai-q6.5", 12)) { + *i2s_intf = MSM_INTF_SECN; + *i2s_dir = MSM_DIR_TX; + } else { + pr_err("Error in I2S cpu dai name\n"); + ret = 1; + } +err: + return ret; +} + +int msm9615_enable_i2s_gpio(u8 i2s_intf, u8 i2s_dir) +{ + u8 ret = 0; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + if (i2s_intf == MSM_INTF_PRIM) { + if (i2s_dir == MSM_DIR_TX) { + ret = gpio_request(GPIO_PRIM_I2S_DIN, "I2S_PRIM_DIN"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_DIN); + goto err; + } + } else if (i2s_dir == MSM_DIR_RX) { + ret = gpio_request(GPIO_PRIM_I2S_DOUT, + "I2S_PRIM_DOUT"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_DOUT); + goto err; + } + } else if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + ret = gpio_request(GPIO_PRIM_I2S_SCK, "I2S_PRIM_SCK"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_SCK); + goto err; + } + ret = gpio_request(GPIO_PRIM_I2S_WS, "I2S_PRIM_WS"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_WS); + goto err; + } + } + } else if (i2s_intf == MSM_INTF_SECN) { + if (i2s_dir == MSM_DIR_RX) { + ret = gpio_request(GPIO_SEC_I2S_DOUT, "I2S_SEC_DOUT"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_DOUT); + goto err; + } + } else if (i2s_dir == MSM_DIR_TX) { + ret = gpio_request(GPIO_SEC_I2S_DIN, "I2S_SEC_DIN"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_DIN); + goto err; + } + } else if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + ret = gpio_request(GPIO_SEC_I2S_SCK, "I2S_SEC_SCK"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_SCK); + goto err; + } + ret = gpio_request(GPIO_SEC_I2S_WS, "I2S_SEC_WS"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_WS); + goto err; + } + } + } +err: + return ret; +} + +static int msm9615_set_i2s_osr_bit_clk(struct snd_soc_dai *cpu_dai, + u8 i2s_intf, u8 i2s_dir, + enum msm9x15_set_i2s_clk enable) +{ + + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + struct msm_i2s_clk *pclk = &pintf->prim_clk; + struct msm_clk *clk_ctl = &pclk->rx_clk; + u8 ret = 0; + pr_debug("Dev name %s Intf =%d, Dir = %d, Enable=%d\n", + cpu_dai->name, i2s_intf, i2s_dir, enable); + if (i2s_intf == MSM_INTF_PRIM) + pclk = &pintf->prim_clk; + else if (i2s_intf == MSM_INTF_SECN) + pclk = &pintf->sec_clk; + + if (i2s_dir == MSM_DIR_TX) + clk_ctl = &pclk->tx_clk; + else if (i2s_dir == MSM_DIR_RX) + clk_ctl = &pclk->rx_clk; + + if (enable == MSM_I2S_CLK_SET_TRUE || + enable == MSM_I2S_CLK_SET_RATE0) { + if (clk_ctl->clk_enable != 0) { + pr_info("%s: I2S Clk is already enabled" + "clk users %d\n", __func__, + clk_ctl->clk_enable); + ret = 0; + goto err; + } + clk_ctl->osr_clk = clk_get(cpu_dai->dev, "osr_clk"); + if (IS_ERR(clk_ctl->osr_clk)) { + pr_err("%s: Fail to get OSR CLK\n", __func__); + ret = -EINVAL; + goto err; + } + ret = clk_prepare(clk_ctl->osr_clk); + if (ret != 0) { + pr_err("Unable to prepare i2s_spkr_osr_clk\n"); + goto err; + } + clk_set_rate(clk_ctl->osr_clk, TABLA_EXT_CLK_RATE); + ret = clk_enable(clk_ctl->osr_clk); + if (ret != 0) { + pr_err("Fail to enable i2s_spkr_osr_clk\n"); + clk_unprepare(clk_ctl->osr_clk); + goto err; + } + clk_ctl->bit_clk = clk_get(cpu_dai->dev, "bit_clk"); + if (IS_ERR(clk_ctl->bit_clk)) { + pr_err("Fail to get i2s_spkr_bit_clk\n"); + clk_disable(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_put(clk_ctl->osr_clk); + ret = -EINVAL; + goto err; + } + ret = clk_prepare(clk_ctl->bit_clk); + if (ret != 0) { + clk_disable(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_put(clk_ctl->osr_clk); + pr_err("Fail to prepare i2s_spkr_osr_clk\n"); + goto err; + } + if (enable == MSM_I2S_CLK_SET_RATE0) + clk_set_rate(clk_ctl->bit_clk, 0); + else + clk_set_rate(clk_ctl->bit_clk, 8); + ret = clk_enable(clk_ctl->bit_clk); + if (ret != 0) { + clk_disable(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_put(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->bit_clk); + pr_err("Unable to enable i2s_spkr_osr_clk\n"); + goto err; + } + clk_ctl->clk_enable++; + } else if (enable == MSM_I2S_CLK_SET_FALSE && + clk_ctl->clk_enable != 0) { + clk_disable(clk_ctl->osr_clk); + clk_disable(clk_ctl->bit_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->bit_clk); + clk_put(clk_ctl->bit_clk); + clk_put(clk_ctl->osr_clk); + clk_ctl->bit_clk = NULL; + clk_ctl->osr_clk = NULL; + clk_ctl->clk_enable--; + ret = 0; + } +err: + return ret; +} + +void msm9615_config_i2s_sif_mux(u8 value) +{ + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + sif_shadow = 0x00000; + sif_shadow = (sif_shadow & LPASS_SIF_MUX_CTL_PRI_MUX_SEL_BMSK) | + (value << LPASS_SIF_MUX_CTL_PRI_MUX_SEL_SHFT); + iowrite32(sif_shadow, pintf->sif_virt_addr); + /* Dont read SIF register. Device crashes. */ + pr_debug("%s() SIF Reg = 0x%x\n", __func__, sif_shadow); +} + +void msm9615_config_i2s_spare_mux(u8 value, u8 i2s_intf) +{ + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + if (i2s_intf == MSM_INTF_PRIM) { + /* Configure Primary SIF */ + spare_shadow = (spare_shadow & LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_BMSK + ) | (value << LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_SHFT); + } + if (i2s_intf == MSM_INTF_SECN) { + /*Secondary interface configuration*/ + spare_shadow = (spare_shadow & LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_BMSK + ) | (value << LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_SHFT); + } + iowrite32(spare_shadow, pintf->spare_virt_addr); + /* Dont read SPARE register. Device crashes. */ + pr_debug("%s( ): SPARE Reg =0x%x\n", __func__, spare_shadow); +} + +static int msm9615_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + struct msm_i2s_clk *pclk = &pintf->prim_clk; + struct msm_clk *clk_ctl = &pclk->rx_clk; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int bit_clk_set = 0; + u8 i2s_intf, i2s_dir; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!msm9615_i2s_intf_dir_sel(cpu_dai->name, + &i2s_intf, &i2s_dir)) { + bit_clk_set = TABLA_EXT_CLK_RATE / + (rate * 2 * NO_OF_BITS_PER_SAMPLE); + if (bit_clk_set != 8) { + if (i2s_intf == MSM_INTF_PRIM) + pclk = &pintf->prim_clk; + else if (i2s_intf == MSM_INTF_SECN) + pclk = &pintf->sec_clk; + clk_ctl = &pclk->rx_clk; + pr_debug("%s( ): New rate = %d", + __func__, bit_clk_set); + clk_set_rate(clk_ctl->bit_clk, bit_clk_set); + } + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + bit_clk_set = I2S_MIC_SCLK_RATE / (rate * 2 * + NO_OF_BITS_PER_SAMPLE); + /* Not required to modify TX rate. + * Speaker clock are looped back + * to Mic. + */ + } + return 1; +} + +static int msm9615_i2s_startup(struct snd_pcm_substream *substream) +{ + u8 ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + u8 i2s_intf, i2s_dir; + if (!msm9615_i2s_intf_dir_sel(cpu_dai->name, &i2s_intf, &i2s_dir)) { + pr_debug("%s( ): cpu name = %s intf =%d dir = %d\n", + __func__, cpu_dai->name, i2s_intf, i2s_dir); + pr_debug("%s( ): Enable status Rx =%d Tx = %d\n", __func__, + pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); + msm9615_enable_i2s_gpio(i2s_intf, i2s_dir); + if (i2s_dir == MSM_DIR_TX) { + if (pintf->intf_status[i2s_intf][MSM_DIR_RX] > 0) { + /* This means that Rx is enabled before */ + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, + i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_RATE0); + if (ret != 0) { + pr_err("%s: Fail enable I2S clock\n", + __func__); + return -EINVAL; + } + msm9615_config_i2s_sif_mux( + pintf->mux_ctl[MSM_DIR_BOTH].sifconfig); + msm9615_config_i2s_spare_mux( + pintf->mux_ctl[MSM_DIR_BOTH].spareconfig, + i2s_intf); + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + pr_err("set fmt cpu dai failed\n"); + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt codec dai failed\n"); + } else if (pintf->intf_status[i2s_intf][i2s_dir] == 0) { + /* This means that Rx is + * not enabled before. + * only Tx will be used. + */ + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, + i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_TRUE); + if (ret != 0) { + pr_err("%s: Fail Tx I2S clock\n", + __func__); + return -EINVAL; + } + msm9615_config_i2s_sif_mux( + pintf->mux_ctl[MSM_DIR_TX].sifconfig); + msm9615_config_i2s_spare_mux( + pintf->mux_ctl[MSM_DIR_TX].spareconfig, + i2s_intf); + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt cpu dai failed\n"); + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt codec dai failed\n"); + } + } else if (i2s_dir == MSM_DIR_RX) { + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] > 0) { + pr_err("%s: Error shutdown Tx first\n", + __func__); + return -EINVAL; + } else if (pintf->intf_status[i2s_intf][i2s_dir] + == 0) { + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, + i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_TRUE); + if (ret != 0) { + pr_err("%s: Fail Rx I2S clock\n", + __func__); + return -EINVAL; + } + msm9615_config_i2s_sif_mux( + pintf->mux_ctl[MSM_DIR_RX].sifconfig); + msm9615_config_i2s_spare_mux( + pintf->mux_ctl[MSM_DIR_RX].spareconfig, + i2s_intf); + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt cpu dai failed\n"); + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt codec dai failed\n"); + } + } + pintf->intf_status[i2s_intf][i2s_dir]++; + } else { + pr_err("%s: Err in i2s_intf_dir_sel\n", __func__); + return -EINVAL; + } + pr_debug("Exit %s() Enable status Rx =%d Tx = %d\n", __func__, + pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); + return ret; +} + +static void msm9615_i2s_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + u8 i2s_intf = 0, i2s_dir = 0, ret = 0; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + pr_debug("%s( ): Enable status Rx =%d Tx = %d\n", + __func__, pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); + if (!msm9615_i2s_intf_dir_sel(cpu_dai->name, &i2s_intf, &i2s_dir)) { + pr_debug("%s( ): intf =%d dir = %d\n", __func__, + i2s_intf, i2s_dir); + if (i2s_dir == MSM_DIR_RX) + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] > 0) + pr_err("%s: Shutdown Tx First then by RX\n", + __func__); + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_FALSE); + if (ret != 0) + pr_err("%s: Cannot disable I2S clock\n", + __func__); + pintf->intf_status[i2s_intf][i2s_dir]--; + mdm9615_i2s_free_gpios(i2s_intf, i2s_dir); + } + pr_debug("%s( ): Enable status Rx =%d Tx = %d\n", __func__, + pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); +} + +static struct snd_soc_ops msm9615_i2s_be_ops = { + .startup = msm9615_i2s_startup, + .shutdown = msm9615_i2s_shutdown, + .hw_params = msm9615_i2s_hw_params, +}; + +static int mdm9615_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; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + rtd->pmdown_time = 0; + + snd_soc_dapm_new_controls(dapm, mdm9615_dapm_widgets, + ARRAY_SIZE(mdm9615_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | + SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int mdm9615_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 = mdm9615_slim_0_rx_ch; + + return 0; +} + +static int mdm9615_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 = mdm9615_slim_0_tx_ch; + + return 0; +} + +static int mdm9615_btsco_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); + + rate->min = rate->max = mdm9615_btsco_rate; + channels->min = channels->max = mdm9615_btsco_ch; + + return 0; +} +static int mdm9615_auxpcm_be_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); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} +static int mdm9615_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int mdm9615_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} + +static int mdm9615_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static int mdm9615_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = mdm9615_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return 0; +} + +static void mdm9615_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + mdm9615_aux_pcm_free_gpios(); +} + +static void mdm9615_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops mdm9615_be_ops = { + .startup = mdm9615_startup, + .hw_params = mdm9615_hw_params, + .shutdown = mdm9615_shutdown, +}; + +static struct snd_soc_ops mdm9615_auxpcm_be_ops = { + .startup = mdm9615_auxpcm_startup, + .shutdown = mdm9615_auxpcm_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mdm9615_dai_common[] = { + /* FrontEnd DAI Links */ + { + .name = "MDM9615 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MDM9615 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .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, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + /* .be_id = do not care */ + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + }, + { + .name = "VoLTE", + .stream_name = "VoLTE", + .cpu_dai_name = "VoLTE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .be_id = MSM_FRONTEND_DAI_VOLTE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + }, + /* Backend BT 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, + .be_hw_params_fixup = mdm9615_btsco_be_hw_params_fixup, + }, + { + .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, + .be_hw_params_fixup = mdm9615_btsco_be_hw_params_fixup, + }, + + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .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_AFE_PCM_RX, + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .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_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .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_AUXPCM_RX, + .be_hw_params_fixup = mdm9615_auxpcm_be_params_fixup, + .ops = &mdm9615_auxpcm_be_ops, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .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_AUXPCM_TX, + .be_hw_params_fixup = mdm9615_auxpcm_be_params_fixup, + }, + +}; + +static struct snd_soc_dai_link mdm9615_dai_i2s_tabla[] = { + /* Backend I2S DAI Links */ + { + .name = LPASS_BE_PRI_I2S_RX, + .stream_name = "Primary I2S Playback", + .cpu_dai_name = "msm-dai-q6.0", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_i2s_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_PRI_I2S_RX, + .init = &msm9615_i2s_audrx_init, + .be_hw_params_fixup = msm9615_i2s_rx_be_hw_params_fixup, + .ops = &msm9615_i2s_be_ops, + }, + { + .name = LPASS_BE_PRI_I2S_TX, + .stream_name = "Primary I2S Capture", + .cpu_dai_name = "msm-dai-q6.1", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_i2s_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_PRI_I2S_TX, + .be_hw_params_fixup = msm9615_i2s_tx_be_hw_params_fixup, + .ops = &msm9615_i2s_be_ops, + }, +}; + +static struct snd_soc_dai_link mdm9615_dai_slimbus_tabla[] = { + /* Backend SlimBus 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 = &mdm9615_audrx_init, + .be_hw_params_fixup = mdm9615_slim_0_rx_be_hw_params_fixup, + .ops = &mdm9615_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 = mdm9615_slim_0_tx_be_hw_params_fixup, + .ops = &mdm9615_be_ops, + }, +}; + +static struct snd_soc_dai_link mdm9615_i2s_dai[ + ARRAY_SIZE(mdm9615_dai_common) + + ARRAY_SIZE(mdm9615_dai_i2s_tabla)]; + +static struct snd_soc_dai_link mdm9615_slimbus_dai[ + ARRAY_SIZE(mdm9615_dai_common) + + ARRAY_SIZE(mdm9615_dai_slimbus_tabla)]; + + +static struct snd_soc_card snd_soc_card_mdm9615 = { + .name = "mdm9615-tabla-snd-card", + .controls = tabla_mdm9615_controls, + .num_controls = ARRAY_SIZE(tabla_mdm9615_controls), +}; + +static struct platform_device *mdm9615_snd_device; + +static int mdm9615_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(PM8018_GPIO_PM_TO_SYS(23), "AV_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(23)); + return ret; + } + + ret = pm8xxx_gpio_config(PM8018_GPIO_PM_TO_SYS(23), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(23)); + else + gpio_direction_output(PM8018_GPIO_PM_TO_SYS(23), 0); + + ret = gpio_request(PM8018_GPIO_PM_TO_SYS(35), "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(35)); + gpio_free(PM8018_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(PM8018_GPIO_PM_TO_SYS(35), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(35)); + else + gpio_direction_output(PM8018_GPIO_PM_TO_SYS(35), 0); + + return 0; +} +static void mdm9615_free_headset_mic_gpios(void) +{ + if (mdm9615_headset_gpios_configured) { + gpio_free(PM8018_GPIO_PM_TO_SYS(23)); + gpio_free(PM8018_GPIO_PM_TO_SYS(35)); + } +} + +void __init install_codec_i2s_gpio(void) +{ + msm_gpiomux_install(msm9615_audio_prim_i2s_codec_configs, + ARRAY_SIZE(msm9615_audio_prim_i2s_codec_configs)); +} +static int __init mdm9615_audio_init(void) +{ + int ret; + + if (!cpu_is_msm9615()) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV ; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + mdm9615_snd_device = platform_device_alloc("soc-audio", 0); + if (!mdm9615_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + pr_err("%s: Interface Type = %d\n", __func__, + wcd9xxx_get_intf_type()); + if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS) { + memcpy(mdm9615_slimbus_dai, mdm9615_dai_common, + sizeof(mdm9615_dai_common)); + memcpy(mdm9615_slimbus_dai + ARRAY_SIZE(mdm9615_dai_common), + mdm9615_dai_slimbus_tabla, + sizeof(mdm9615_dai_slimbus_tabla)); + snd_soc_card_mdm9615.dai_link = mdm9615_slimbus_dai; + snd_soc_card_mdm9615.num_links = + ARRAY_SIZE(mdm9615_slimbus_dai); + } else if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C) { + install_codec_i2s_gpio(); + memcpy(mdm9615_i2s_dai, mdm9615_dai_common, + sizeof(mdm9615_dai_common)); + memcpy(mdm9615_i2s_dai + ARRAY_SIZE(mdm9615_dai_common), + mdm9615_dai_i2s_tabla, + sizeof(mdm9615_dai_i2s_tabla)); + snd_soc_card_mdm9615.dai_link = mdm9615_i2s_dai; + snd_soc_card_mdm9615.num_links = + ARRAY_SIZE(mdm9615_i2s_dai); + } + platform_set_drvdata(mdm9615_snd_device, &snd_soc_card_mdm9615); + ret = platform_device_add(mdm9615_snd_device); + if (ret) { + platform_device_put(mdm9615_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + if (mdm9615_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + mdm9615_headset_gpios_configured = 0; + } else + mdm9615_headset_gpios_configured = 1; + + msm9x15_i2s_ctl.sif_virt_addr = ioremap(LPASS_SIF_MUX_ADDR, 4); + msm9x15_i2s_ctl.spare_virt_addr = ioremap(LPAIF_SPARE_ADDR, 4); + + return ret; + +} +module_init(mdm9615_audio_init); + +static void __exit mdm9615_audio_exit(void) +{ + if (!cpu_is_msm9615()) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + mdm9615_free_headset_mic_gpios(); + platform_device_unregister(mdm9615_snd_device); + kfree(mbhc_cfg.calibration); + iounmap(msm9x15_i2s_ctl.sif_virt_addr); + iounmap(msm9x15_i2s_ctl.spare_virt_addr); + +} +module_exit(mdm9615_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MDM9615"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/mpq8064.c b/sound/soc/msm/mpq8064.c new file mode 100644 index 0000000000000000000000000000000000000000..0995300e38f15e9998166ed7b296e1d34bba3d4e --- /dev/null +++ b/sound/soc/msm/mpq8064.c @@ -0,0 +1,1470 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm-pcm-routing.h" +#include "../codecs/wcd9310.h" + +/* 8064 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MPQ8064_SPK_ON 1 +#define MPQ8064_SPK_OFF 0 + +#define MSM_SLIM_0_RX_MAX_CHANNELS 2 +#define MSM_SLIM_0_TX_MAX_CHANNELS 4 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +#define GPIO_SEC_I2S_RX_SCK 47 +#define GPIO_SEC_I2S_RX_WS 48 +#define GPIO_SEC_I2S_RX_DOUT 49 +#define GPIO_SEC_I2S_RX_MCLK 50 +#define I2S_MCLK_RATE 1536000 + +#define GPIO_MI2S_WS 27 +#define GPIO_MI2S_SCLK 28 +#define GPIO_MI2S_DOUT3 29 +#define GPIO_MI2S_DOUT2 30 +#define GPIO_MI2S_DOUT1 31 +#define GPIO_MI2S_DOUT0 32 +#define GPIO_MI2S_MCLK 33 + +static struct clk *sec_i2s_rx_osr_clk; +static struct clk *sec_i2s_rx_bit_clk; + +struct request_gpio { + unsigned gpio_no; + char *gpio_name; +}; + +static struct request_gpio sec_i2s_rx_gpio[] = { + { + .gpio_no = GPIO_SEC_I2S_RX_MCLK, + .gpio_name = "SEC_I2S_RX_MCLK", + }, + { + .gpio_no = GPIO_SEC_I2S_RX_SCK, + .gpio_name = "SEC_I2S_RX_SCK", + }, + { + .gpio_no = GPIO_SEC_I2S_RX_WS, + .gpio_name = "SEC_I2S_RX_WS", + }, + { + .gpio_no = GPIO_SEC_I2S_RX_DOUT, + .gpio_name = "SEC_I2S_RX_DOUT", + }, +}; + +static struct request_gpio mi2s_gpio[] = { + { + .gpio_no = GPIO_MI2S_WS, + .gpio_name = "MI2S_WS", + }, + { + .gpio_no = GPIO_MI2S_SCLK, + .gpio_name = "MI2S_SCLK", + }, + { + .gpio_no = GPIO_MI2S_DOUT3, + .gpio_name = "MI2S_DOUT3", + }, + { + .gpio_no = GPIO_MI2S_DOUT2, + .gpio_name = "MI2S_DOUT2", + }, + { + .gpio_no = GPIO_MI2S_DOUT1, + .gpio_name = "MI2S_DOUT1", + }, + { + .gpio_no = GPIO_MI2S_DOUT0, + .gpio_name = "MI2S_DOUT0", + }, + { + .gpio_no = GPIO_MI2S_MCLK, + .gpio_name = "MI2S_MCLK", + }, +}; + +static struct clk *mi2s_bit_clk; + + + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm_spk_control; +static int msm_ext_bottom_spk_pamp; +static int msm_ext_top_spk_pamp; +static int msm_slim_0_rx_ch = 1; +static int msm_slim_0_tx_ch = 1; +static int msm_hdmi_rx_ch = 2; + +static struct clk *codec_clk; +static int clk_users; + +static int msm_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, /* MBHC GPIO is not configured */ + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void msm_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + 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 (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_bottom_spk_pamp |= spk; + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_top_spk_pamp |= spk; + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!msm_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + if (msm_spk_control == MPQ8064_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + ucontrol->value.integer.value[0] = msm_spk_control; + return 0; +} +static int msm_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 (msm_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm_spk_control = ucontrol->value.integer.value[0]; + msm_ext_control(codec); + return 1; +} +static int msm_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)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + clk_disable_unprepare(codec_clk); + tabla_mclk_enable(codec, 0, dapm); + } + } + return 0; +} + +static int msm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, 12288000); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(w->codec, 1, true); + + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + break; + case SND_SOC_DAPM_POST_PMD: + + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users == 0) + return 0; + + clk_users--; + + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + + clk_disable_unprepare(codec_clk); + tabla_mclk_enable(w->codec, 0, true); + } + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget msm_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal1"}, + {"MIC BIAS1 Internal1", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; +static const char *hdmi_rx_ch_text[] = {"Two", "Three", "Four", "Five", "Six"}; + + +static const struct soc_enum msm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), + SOC_ENUM_SINGLE_EXT(5, hdmi_rx_ch_text), + +}; + +static int msm_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_rx_ch - 1; + return 0; +} + +static int msm_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + return 1; +} + +static int msm_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_tx_ch - 1; + return 0; +} + +static int msm_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + return 1; +} + +static int msm_hdmi_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_hdmi_rx_ch = %d\n", __func__, + msm_hdmi_rx_ch); + ucontrol->value.integer.value[0] = msm_hdmi_rx_ch - 2; + return 0; +} + +static int msm_hdmi_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_hdmi_rx_ch = ucontrol->value.integer.value[0] + 2; + + pr_debug("%s: msm_hdmi_rx_ch = %d\n", __func__, + msm_hdmi_rx_ch); + return 1; +} + + +static const struct snd_kcontrol_new tabla_msm_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm_enum[0], msm_get_spk, + msm_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm_enum[1], + msm_slim_0_rx_ch_get, msm_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm_enum[2], + msm_slim_0_tx_ch_get, msm_slim_0_tx_ch_put), + SOC_ENUM_EXT("HDMI_RX Channels", msm_enum[3], + msm_hdmi_rx_ch_get, msm_hdmi_rx_ch_put), + +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + pr_debug("%s: ch=%d\n", __func__, + msm_slim_0_rx_ch); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + msm_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + msm_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + + } +end: + return ret; +} + +static int msm_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; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + snd_soc_dapm_new_controls(dapm, msm_dapm_widgets, + ARRAY_SIZE(msm_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int msm_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 = msm_slim_0_rx_ch; + + return 0; +} + +static int msm_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 = msm_slim_0_tx_ch; + + return 0; +} + +static int msm_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 msm_hdmi_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 channels->min %u channels->max %u ()\n", __func__, + channels->min, channels->max); + + rate->min = rate->max = 48000; + channels->min = channels->max = msm_hdmi_rx_ch; + + return 0; +} + +static int msm_mi2s_free_gpios(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mi2s_gpio); i++) + gpio_free(mi2s_gpio[i].gpio_no); + return 0; +} + +static void msm_mi2s_shutdown(struct snd_pcm_substream *substream) +{ + if (mi2s_bit_clk) { + clk_disable(mi2s_bit_clk); + clk_put(mi2s_bit_clk); + mi2s_bit_clk = NULL; + } + msm_mi2s_free_gpios(); +} + +static int configure_mi2s_gpio(void) +{ + int rtn; + int i; + int j; + for (i = 0; i < ARRAY_SIZE(mi2s_gpio); i++) { + rtn = gpio_request(mi2s_gpio[i].gpio_no, + mi2s_gpio[i].gpio_name); + pr_debug("%s: gpio = %d, gpio name = %s, rtn = %d\n", + __func__, + mi2s_gpio[i].gpio_no, + mi2s_gpio[i].gpio_name, + rtn); + if (rtn) { + pr_err("%s: Failed to request gpio %d\n", + __func__, + mi2s_gpio[i].gpio_no); + for (j = i; j >= 0; j--) + gpio_free(mi2s_gpio[j].gpio_no); + goto err; + } + } +err: + return rtn; +} +static int msm_mi2s_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + configure_mi2s_gpio(); + mi2s_bit_clk = clk_get(cpu_dai->dev, "bit_clk"); + if (IS_ERR(mi2s_bit_clk)) + return PTR_ERR(mi2s_bit_clk); + clk_set_rate(mi2s_bit_clk, 0); + ret = clk_enable(mi2s_bit_clk); + if (IS_ERR_VALUE(ret)) { + pr_err("Unable to enable mi2s_bit_clk\n"); + clk_put(mi2s_bit_clk); + return ret; + } + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + if (IS_ERR_VALUE(ret)) + pr_err("set format for CPU dai failed\n"); + return ret; +} + +static int msm_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static void msm_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops msm_be_ops = { + .startup = msm_startup, + .hw_params = msm_hw_params, + .shutdown = msm_shutdown, +}; + +static int mpq8064_sec_i2s_rx_free_gpios(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(sec_i2s_rx_gpio); i++) + gpio_free(sec_i2s_rx_gpio[i].gpio_no); + return 0; +} + +static int mpq8064_sec_i2s_rx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + int rate = params_rate(params); + int bit_clk_set = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bit_clk_set = I2S_MCLK_RATE/(rate * 2 * 16); + clk_set_rate(sec_i2s_rx_bit_clk, bit_clk_set); + break; + case SNDRV_PCM_FORMAT_S24_LE: + bit_clk_set = I2S_MCLK_RATE/(rate * 2 * 24); + clk_set_rate(sec_i2s_rx_bit_clk, bit_clk_set); + break; + default: + pr_err("wrong format\n"); + break; + } + } + return 0; +} + +static void mpq8064_sec_i2s_rx_shutdown(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (sec_i2s_rx_bit_clk) { + clk_disable_unprepare(sec_i2s_rx_bit_clk); + clk_put(sec_i2s_rx_bit_clk); + sec_i2s_rx_bit_clk = NULL; + } + if (sec_i2s_rx_osr_clk) { + clk_disable_unprepare(sec_i2s_rx_osr_clk); + clk_put(sec_i2s_rx_osr_clk); + sec_i2s_rx_osr_clk = NULL; + } + mpq8064_sec_i2s_rx_free_gpios(); + } + pr_info("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static int configure_sec_i2s_rx_gpio(void) +{ + int rtn; + int i; + int j; + for (i = 0; i < ARRAY_SIZE(sec_i2s_rx_gpio); i++) { + rtn = gpio_request(sec_i2s_rx_gpio[i].gpio_no, + sec_i2s_rx_gpio[i].gpio_name); + pr_debug("%s: gpio = %d, gpio name = %s, rtn = %d\n", + __func__, + sec_i2s_rx_gpio[i].gpio_no, + sec_i2s_rx_gpio[i].gpio_name, + rtn); + if (rtn) { + pr_err("%s: Failed to request gpio %d\n", + __func__, + sec_i2s_rx_gpio[i].gpio_no); + for (j = i; j >= 0; j--) + gpio_free(sec_i2s_rx_gpio[j].gpio_no); + + goto err; + } + } +err: + return rtn; +} + +static int mpq8064_sec_i2s_rx_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + configure_sec_i2s_rx_gpio(); + sec_i2s_rx_osr_clk = clk_get(cpu_dai->dev, "osr_clk"); + if (IS_ERR(sec_i2s_rx_osr_clk)) { + pr_err("Failed to get sec_i2s_rx_osr_clk\n"); + return PTR_ERR(sec_i2s_rx_osr_clk); + } + clk_set_rate(sec_i2s_rx_osr_clk, I2S_MCLK_RATE); + clk_prepare_enable(sec_i2s_rx_osr_clk); + sec_i2s_rx_bit_clk = clk_get(cpu_dai->dev, "bit_clk"); + if (IS_ERR(sec_i2s_rx_bit_clk)) { + pr_err("Failed to get sec i2s osr_clk\n"); + clk_disable_unprepare(sec_i2s_rx_osr_clk); + clk_put(sec_i2s_rx_osr_clk); + return PTR_ERR(sec_i2s_rx_bit_clk); + } + clk_set_rate(sec_i2s_rx_bit_clk, 1); + ret = clk_prepare_enable(sec_i2s_rx_bit_clk); + if (ret != 0) { + pr_err("Unable to enable sec i2s rx_bit_clk\n"); + clk_put(sec_i2s_rx_bit_clk); + clk_disable_unprepare(sec_i2s_rx_osr_clk); + clk_put(sec_i2s_rx_osr_clk); + return ret; + } + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set format for codec dai failed\n"); + } + pr_debug("%s: ret = %d\n", __func__, ret); + pr_info("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return ret; +} + +static struct snd_soc_ops mpq8064_sec_i2s_rx_be_ops = { + .startup = mpq8064_sec_i2s_rx_startup, + .shutdown = mpq8064_sec_i2s_rx_shutdown, + .hw_params = mpq8064_sec_i2s_rx_hw_params, +}; + +static struct snd_soc_ops msm_mi2s_tx_be_ops = { + .startup = msm_mi2s_startup, + .shutdown = msm_mi2s_shutdown, +}; + + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-multi-ch-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM8960 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "Voice Stub", + .stream_name = "Voice Stub", + .cpu_dai_name = "VOICE_STUB", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* MI2S TX Hostless */ + { + .name = "MI2S_TX Hostless", + .stream_name = "MI2S_TX Hostless", + .cpu_dai_name = "MI2S_TX_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* Secondary I2S RX Hostless */ + { + .name = "SEC_I2S_RX Hostless", + .stream_name = "SEC_I2S_RX Hostless", + .cpu_dai_name = "SEC_I2S_RX_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* 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 = &msm_audrx_init, + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .ops = &msm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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 = msm_slim_0_tx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + { + .name = LPASS_BE_SEC_I2S_RX, + .stream_name = "Secondary I2S Playback", + .cpu_dai_name = "msm-dai-q6.4", + .platform_name = "msm-pcm-routing", + .codec_name = "cs8427-spdif.5-0014", + .codec_dai_name = "spdif_rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SEC_I2S_RX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &mpq8064_sec_i2s_rx_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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 = msm_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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 = msm_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .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_HDMI_RX, + .be_hw_params_fixup = msm_hdmi_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_MI2S_TX, + .stream_name = "MI2S Capture", + .cpu_dai_name = "msm-dai-q6-mi2s", + .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_MI2S_TX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &msm_mi2s_tx_be_ops, + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .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_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .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_AFE_PCM_TX, + }, +}; + + +static struct snd_soc_card snd_soc_card_msm = { + .name = "mpq8064-tabla-snd-card", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), + .controls = tabla_msm_controls, + .num_controls = ARRAY_SIZE(tabla_msm_controls), +}; + +static struct platform_device *msm_snd_device; + +static int msm_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), 0); + + return 0; +} +static void msm_free_headset_mic_gpios(void) +{ + if (msm_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm_audio_init(void) +{ + int ret; + + if (socinfo_get_id() != 130) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm_headset_gpios_configured = 0; + } else + msm_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + if (socinfo_get_id() != 130) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm_free_headset_mic_gpios(); + platform_device_unregister(msm_snd_device); + kfree(mbhc_cfg.calibration); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC mpq8064"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-compr-q6.c b/sound/soc/msm/msm-compr-q6.c new file mode 100644 index 0000000000000000000000000000000000000000..8061b06320fc5f1b7a31ad00e0ebb9106c5593f0 --- /dev/null +++ b/sound/soc/msm/msm-compr-q6.c @@ -0,0 +1,753 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-compr-q6.h" +#include "msm-pcm-routing.h" + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm compressed_audio = {NULL, 0x2000} ; + +static struct audio_locks the_locks; + +static struct snd_pcm_hardware msm_compr_hardware_playback = { + .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 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 1200 * 1024 * 2, + .period_bytes_min = 4800, + .period_bytes_max = 1200 * 1024, + .periods_min = 2, + .periods_max = 512, + .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 compr_event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct compr_audio *compr = priv; + struct msm_audio *prtd = &compr->prtd; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = NULL; + int i = 0; + + pr_debug("%s opcode =%08x\n", __func__, opcode); + 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); + else + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing %d bytes of buffer[%d] to dsp 2\n", + __func__, prtd->pcm_count, prtd->out_head); + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * 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: { + if (!atomic_read(&prtd->pending_buffer)) + break; + pr_debug("%s:writing %d bytes" + " of buffer[%d] to dsp\n", + __func__, prtd->pcm_count, prtd->out_head); + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * prtd->pcm_count))); + param.paddr = (unsigned long)buf[prtd->out_head].phys; + 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].phys; + 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); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_compr_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct asm_aac_cfg aac_cfg; + struct asm_wma_cfg wma_cfg; + struct asm_wmapro_cfg wma_pro_cfg; + int ret; + + pr_debug("compressed stream prepare\n"); + 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; + prtd->out_head = 0; + atomic_set(&prtd->out_count, runtime->periods); + + if (prtd->enabled) + return 0; + + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + ret = q6asm_media_format_block(prtd->audio_client, + compr->codec); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + memset(&aac_cfg, 0x0, sizeof(struct asm_aac_cfg)); + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + aac_cfg.format = 0x03; + aac_cfg.ch_cfg = runtime->channels; + aac_cfg.sample_rate = runtime->rate; + ret = q6asm_media_format_block_aac(prtd->audio_client, + &aac_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + case SND_AUDIOCODEC_AC3_PASS_THROUGH: + pr_debug("compressd playback, no need to send" + " the decoder params\n"); + break; + case SND_AUDIOCODEC_WMA: + pr_debug("SND_AUDIOCODEC_WMA\n"); + memset(&wma_cfg, 0x0, sizeof(struct asm_wma_cfg)); + wma_cfg.format_tag = compr->info.codec_param.codec.format; + wma_cfg.ch_cfg = runtime->channels; + wma_cfg.sample_rate = runtime->rate; + wma_cfg.avg_bytes_per_sec = + compr->info.codec_param.codec.bit_rate/8; + wma_cfg.block_align = compr->info.codec_param.codec.align; + wma_cfg.valid_bits_per_sample = + compr->info.codec_param.codec.options.wma.bits_per_sample; + wma_cfg.ch_mask = + compr->info.codec_param.codec.options.wma.channelmask; + wma_cfg.encode_opt = + compr->info.codec_param.codec.options.wma.encodeopt; + ret = q6asm_media_format_block_wma(prtd->audio_client, + &wma_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + case SND_AUDIOCODEC_WMA_PRO: + pr_debug("SND_AUDIOCODEC_WMA_PRO\n"); + memset(&wma_pro_cfg, 0x0, sizeof(struct asm_wmapro_cfg)); + wma_pro_cfg.format_tag = compr->info.codec_param.codec.format; + wma_pro_cfg.ch_cfg = compr->info.codec_param.codec.ch_in; + wma_pro_cfg.sample_rate = runtime->rate; + wma_pro_cfg.avg_bytes_per_sec = + compr->info.codec_param.codec.bit_rate/8; + wma_pro_cfg.block_align = compr->info.codec_param.codec.align; + wma_pro_cfg.valid_bits_per_sample = + compr->info.codec_param.codec.options.wma.bits_per_sample; + wma_pro_cfg.ch_mask = + compr->info.codec_param.codec.options.wma.channelmask; + wma_pro_cfg.encode_opt = + compr->info.codec_param.codec.options.wma.encodeopt; + ret = q6asm_media_format_block_wmapro(prtd->audio_client, + &wma_pro_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + default: + return -EINVAL; + } + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_compr_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->pcm_irq_pos = 0; + if (compr->info.codec_param.codec.id == + SND_AUDIOCODEC_AC3_PASS_THROUGH) { + msm_pcm_routing_reg_psthr_stream( + soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + } + 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); + 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 void populate_codec_list(struct compr_audio *compr, + struct snd_pcm_runtime *runtime) +{ + pr_debug("%s\n", __func__); + /* MP3 Block */ + compr->info.compr_cap.num_codecs = 1; + compr->info.compr_cap.min_fragment_size = runtime->hw.period_bytes_min; + compr->info.compr_cap.max_fragment_size = runtime->hw.period_bytes_max; + compr->info.compr_cap.min_fragments = runtime->hw.periods_min; + compr->info.compr_cap.max_fragments = runtime->hw.periods_max; + compr->info.compr_cap.codecs[0] = SND_AUDIOCODEC_MP3; + compr->info.compr_cap.codecs[1] = SND_AUDIOCODEC_AAC; + compr->info.compr_cap.codecs[2] = SND_AUDIOCODEC_AC3_PASS_THROUGH; + compr->info.compr_cap.codecs[3] = SND_AUDIOCODEC_WMA; + compr->info.compr_cap.codecs[4] = SND_AUDIOCODEC_WMA_PRO; + /* Add new codecs here */ +} + +static int msm_compr_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr; + struct msm_audio *prtd; + int ret = 0; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + pr_debug("%s\n", __func__); + compr = kzalloc(sizeof(struct compr_audio), GFP_KERNEL); + if (compr == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd = &compr->prtd; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)compr_event_handler, compr); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + runtime->hw = msm_compr_hardware_playback; + + pr_info("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + + 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; + atomic_set(&prtd->pending_buffer, 1); + compr->codec = FORMAT_MP3; + populate_codec_list(compr, runtime); + runtime->private_data = compr; + compressed_audio.prtd = &compr->prtd; + ret = compressed_set_volume(compressed_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(compressed_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(compressed_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int compressed_set_volume(unsigned volume) +{ + int rc = 0; + if (compressed_audio.prtd && compressed_audio.prtd->audio_client) { + rc = q6asm_set_volume(compressed_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + compressed_audio.volume = volume; + return rc; +} + +static int msm_compr_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 compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + int dir = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + compressed_audio.prtd = NULL; + 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_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_compr_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} +static int msm_compr_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} + +static snd_pcm_uframes_t msm_compr_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + 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_compr_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +static int msm_compr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EINVAL; + + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_AC3_PASS_THROUGH: + ret = q6asm_open_write_compressed(prtd->audio_client, + compr->codec); + if (ret < 0) { + pr_err("%s: compressed Session out open failed\n", + __func__); + return -ENOMEM; + } + break; + default: + ret = q6asm_open_write(prtd->audio_client, compr->codec); + if (ret < 0) { + pr_err("%s: Session out open failed\n", __func__); + return -ENOMEM; + } + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + break; + } + ret = q6asm_set_io_mode(prtd->audio_client, ASYNC_IO_MODE); + if (ret < 0) { + pr_err("%s: Set IO mode failed\n", __func__); + return -ENOMEM; + } + + 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 int msm_compr_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:," + "timestamp = %lld,\n", __func__, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_COMPRESS_GET_CAPS: + pr_debug("SNDRV_COMPRESS_GET_CAPS\n"); + if (copy_to_user((void *) arg, &compr->info.compr_cap, + sizeof(struct snd_compr_caps))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy to user\n", __func__); + return rc; + } + return 0; + case SNDRV_COMPRESS_SET_PARAMS: + pr_debug("SNDRV_COMPRESS_SET_PARAMS: "); + if (copy_from_user(&compr->info.codec_param, (void *) arg, + sizeof(struct snd_compr_params))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy from user\n", __func__); + return rc; + } + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + /* For MP3 we dont need any other parameter */ + pr_debug("SND_AUDIOCODEC_MP3\n"); + compr->codec = FORMAT_MP3; + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + compr->codec = FORMAT_MPEG4_AAC; + break; + case SND_AUDIOCODEC_AC3_PASS_THROUGH: + pr_debug("SND_AUDIOCODEC_AC3_PASS_THROUGH\n"); + compr->codec = FORMAT_AC3; + break; + case SND_AUDIOCODEC_WMA: + pr_debug("SND_AUDIOCODEC_WMA\n"); + compr->codec = FORMAT_WMA_V9; + break; + case SND_AUDIOCODEC_WMA_PRO: + pr_debug("SND_AUDIOCODEC_WMA_PRO\n"); + compr->codec = FORMAT_WMA_V10PRO; + break; + default: + pr_debug("FORMAT_LINEAR_PCM\n"); + compr->codec = FORMAT_LINEAR_PCM; + break; + } + return 0; + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_compr_ops = { + .open = msm_compr_open, + .hw_params = msm_compr_hw_params, + .close = msm_compr_close, + .ioctl = msm_compr_ioctl, + .prepare = msm_compr_prepare, + .trigger = msm_compr_trigger, + .pointer = msm_compr_pointer, + .mmap = msm_compr_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_compr_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static __devinit int msm_compr_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_compr_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_compr_driver = { + .driver = { + .name = "msm-compr-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_compr_probe, + .remove = __devexit_p(msm_compr_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_compr_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_compr_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-compr-q6.h b/sound/soc/msm/msm-compr-q6.h new file mode 100644 index 0000000000000000000000000000000000000000..cb7f714518111dd9f8c0b54376cfebe91cd33b02 --- /dev/null +++ b/sound/soc/msm/msm-compr-q6.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 _MSM_COMPR_H +#define _MSM_COMPR_H +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" + +struct compr_info { + struct snd_compr_caps compr_cap; + struct snd_compr_codec_caps codec_caps; + struct snd_compr_params codec_param; +}; + +struct compr_audio { + struct msm_audio prtd; + struct compr_info info; + uint32_t codec; +}; + +#endif /*_MSM_COMPR_H*/ diff --git a/sound/soc/msm/msm-dai-fe.c b/sound/soc/msm/msm-dai-fe.c new file mode 100644 index 0000000000000000000000000000000000000000..c8d3a71c03ae46697824d60cedaeb53d6c8b25d8 --- /dev/null +++ b/sound/soc/msm/msm-dai-fe.c @@ -0,0 +1,412 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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 = {}; + +/* 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 multimedia_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + + return 0; +} + +static struct snd_soc_dai_ops msm_fe_Multimedia_dai_ops = { + .startup = multimedia_startup, +}; + +static struct snd_soc_dai_driver msm_fe_dais[] = { + { + .playback = { + .stream_name = "Multimedia1 Playback", + .aif_name = "MM_DL1", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Multimedia1 Capture", + .aif_name = "MM_UL1", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia1", + }, + { + .playback = { + .stream_name = "Multimedia2 Playback", + .aif_name = "MM_DL2", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 6, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Multimedia2 Capture", + .aif_name = "MM_UL2", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia2", + }, + { + .playback = { + .stream_name = "Voice Playback", + .aif_name = "CS-VOICE_DL1", + .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", + .aif_name = "CS-VOICE_UL1", + .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", + .aif_name = "VOIP_DL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_SPECIAL, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "VoIP Capture", + .aif_name = "VOIP_UL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_SPECIAL, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "VoIP", + }, + { + .playback = { + .stream_name = "MultiMedia3 Playback", + .aif_name = "MM_DL3", + .rates = (SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia3", + }, + { + .playback = { + .stream_name = "MultiMedia4 Playback", + .aif_name = "MM_DL4", + .rates = (SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia4", + }, + /* FE DAIs created for hostless operation purpose */ + { + .playback = { + .stream_name = "SLIMBUS0 Hostless Playback", + .aif_name = "SLIM0_DL_HL", + .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", + .aif_name = "SLIM0_UL_HL", + .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", + .aif_name = "INTFM_DL_HL", + .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", + .aif_name = "INTFM_UL_HL", + .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", + }, + { + .playback = { + .stream_name = "AFE-PROXY Playback", + .aif_name = "PCM_RX", + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "AFE-PROXY Capture", + .aif_name = "PCM_TX", + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_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 = "AFE-PROXY", + }, + { + .playback = { + .stream_name = "HDMI_Rx Hostless Playback", + .aif_name = "HDMI_DL_HL", + .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 = "HDMI_HOSTLESS" + }, + { + .playback = { + .stream_name = "AUXPCM Hostless Playback", + .aif_name = "AUXPCM_DL_HL", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .capture = { + .stream_name = "AUXPCM Hostless Capture", + .aif_name = "AUXPCM_UL_HL", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_fe_dai_ops, + .name = "AUXPCM_HOSTLESS", + }, + { + .playback = { + .stream_name = "Voice Stub Playback", + .aif_name = "VOICE_STUB_DL", + .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 Stub Capture", + .aif_name = "VOICE_STUB_UL", + .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 = "VOICE_STUB", + }, + { + .playback = { + .stream_name = "VoLTE Playback", + .aif_name = "VoLTE_DL", + .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 = "VoLTE Capture", + .aif_name = "VoLTE_UL", + .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 = "VoLTE", + }, + { + .capture = { + .stream_name = "MI2S_TX Hostless Capture", + .aif_name = "MI2S_UL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "MI2S_TX_HOSTLESS", + }, + { + .playback = { + .stream_name = "SEC_I2S_RX Hostless Playback", + .aif_name = "SEC_I2S_DL_HL", + .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 = "SEC_I2S_RX_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-hdmi.c b/sound/soc/msm/msm-dai-q6-hdmi.c new file mode 100644 index 0000000000000000000000000000000000000000..dfb090e9187aa1dfefa466103c1c469d7150a19d --- /dev/null +++ b/sound/soc/msm/msm-dai-q6-hdmi.c @@ -0,0 +1,326 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + + +enum { + STATUS_PORT_STARTED, /* track if AFE port has started */ + STATUS_MAX +}; + +struct msm_dai_q6_hdmi_dai_data { + DECLARE_BITMAP(status_mask, STATUS_MAX); + u32 rate; + u32 channels; + union afe_port_config port_config; +}; + +static int msm_dai_q6_hdmi_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_hdmi_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + dai_data->port_config.hdmi_multi_ch.data_type = value; + pr_debug("%s: value = %d\n", __func__, value); + return 0; +} + +static int msm_dai_q6_hdmi_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_hdmi_dai_data *dai_data = kcontrol->private_data; + ucontrol->value.integer.value[0] = + dai_data->port_config.hdmi_multi_ch.data_type; + return 0; +} + + +/* HDMI format field for AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG command + * 0: linear PCM + * 1: non-linear PCM + */ +static const char *hdmi_format[] = { + "LPCM", + "Compr" +}; + +static const struct soc_enum hdmi_config_enum[] = { + SOC_ENUM_SINGLE_EXT(2, hdmi_format), +}; + +static const struct snd_kcontrol_new hdmi_config_controls[] = { + SOC_ENUM_EXT("HDMI RX Format", hdmi_config_enum[0], + msm_dai_q6_hdmi_format_get, + msm_dai_q6_hdmi_format_put), +}; + +/* 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_hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + u32 channel_allocation = 0; + u32 level_shift = 0; /* 0dB */ + bool down_mix = FALSE; + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + dai_data->port_config.hdmi_multi_ch.reserved = 0; + + switch (dai_data->channels) { + case 2: + channel_allocation = 0; + hdmi_msm_audio_info_setup(1, MSM_HDMI_AUDIO_CHANNEL_2, + channel_allocation, level_shift, down_mix); + dai_data->port_config.hdmi_multi_ch.channel_allocation = + channel_allocation; + break; + case 6: + channel_allocation = 0x0B; + hdmi_msm_audio_info_setup(1, MSM_HDMI_AUDIO_CHANNEL_6, + channel_allocation, level_shift, down_mix); + dai_data->port_config.hdmi_multi_ch.channel_allocation = + channel_allocation; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", + dai_data->channels); + return -EINVAL; + } + dev_dbg(dai->dev, "%s() num_ch = %u rate =%u" + " channel_allocation = %u data type = %d\n", __func__, + dai_data->channels, + dai_data->rate, + dai_data->port_config.hdmi_multi_ch.channel_allocation, + dai_data->port_config.hdmi_multi_ch.data_type); + + return 0; +} + + +static void msm_dai_q6_hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + pr_info("%s: afe port not started. dai_data->status_mask" + " = %ld\n", __func__, *dai_data->status_mask); + return; + } + + rc = afe_close(dai->id); /* can block */ + + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + + pr_debug("%s: dai_data->status_mask = %ld\n", __func__, + *dai_data->status_mask); + + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); +} + + +static int msm_dai_q6_hdmi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + + /* 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. + */ + pr_debug("%s:port:%d cmd:%d dai_data->status_mask = %ld", + __func__, dai->id, cmd, *dai_data->status_mask); + + 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: + dev_err(dai->dev, "invalid Trigger command = %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_hdmi_dai_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data; + const struct snd_kcontrol_new *kcontrol; + int rc = 0; + + dai_data = kzalloc(sizeof(struct msm_dai_q6_hdmi_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); + + kcontrol = &hdmi_config_controls[0]; + + rc = snd_ctl_add(dai->card->snd_card, + snd_ctl_new1(kcontrol, dai_data)); + return rc; +} + +static int msm_dai_q6_hdmi_dai_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_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"); + + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static struct snd_soc_dai_ops msm_dai_q6_hdmi_ops = { + .prepare = msm_dai_q6_hdmi_prepare, + .trigger = msm_dai_q6_hdmi_trigger, + .hw_params = msm_dai_q6_hdmi_hw_params, + .shutdown = msm_dai_q6_hdmi_shutdown, +}; + +static struct snd_soc_dai_driver msm_dai_q6_hdmi_hdmi_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 6, + .rate_max = 48000, + .rate_min = 48000, + }, + .ops = &msm_dai_q6_hdmi_ops, + .probe = msm_dai_q6_hdmi_dai_probe, + .remove = msm_dai_q6_hdmi_dai_remove, +}; + + +/* To do: change to register DAIs as batch */ +static __devinit int msm_dai_q6_hdmi_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s dev-id %d\n", + dev_name(&pdev->dev), pdev->id); + + switch (pdev->id) { + case HDMI_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_hdmi_hdmi_rx_dai); + break; + default: + dev_err(&pdev->dev, "invalid device ID %d\n", pdev->id); + rc = -ENODEV; + break; + } + return rc; +} + +static __devexit int msm_dai_q6_hdmi_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_dai_q6_hdmi_driver = { + .probe = msm_dai_q6_hdmi_dev_probe, + .remove = msm_dai_q6_hdmi_dev_remove, + .driver = { + .name = "msm-dai-q6-hdmi", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_q6_hdmi_init(void) +{ + return platform_driver_register(&msm_dai_q6_hdmi_driver); +} +module_init(msm_dai_q6_hdmi_init); + +static void __exit msm_dai_q6_hdmi_exit(void) +{ + platform_driver_unregister(&msm_dai_q6_hdmi_driver); +} +module_exit(msm_dai_q6_hdmi_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM DSP HDMI 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 0000000000000000000000000000000000000000..284320db6f894581c6de4478735b866d8bc5b105 --- /dev/null +++ b/sound/soc/msm/msm-dai-q6.c @@ -0,0 +1,1707 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +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; + u32 bitwidth; + union afe_port_config port_config; +}; + +struct msm_dai_q6_mi2s_dai_data { + struct msm_dai_q6_dai_data tx_dai; + struct msm_dai_q6_dai_data rx_dai; + struct snd_pcm_hw_constraint_list rate_constraint; + struct snd_pcm_hw_constraint_list bitwidth_constraint; +}; + +static struct clk *pcm_clk; +static DEFINE_MUTEX(aux_pcm_mutex); +static int aux_pcm_count; +static struct msm_dai_auxpcm_pdata *auxpcm_plat_data; + +static int msm_dai_q6_mi2s_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + dai_data->port_config.mi2s.format = value; + pr_debug("%s: value = %d, channel = %d, line = %d\n", + __func__, value, dai_data->port_config.mi2s.channel, + dai_data->port_config.mi2s.line); + return 0; +} + +static int msm_dai_q6_mi2s_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_dai_data *dai_data = kcontrol->private_data; + ucontrol->value.integer.value[0] = dai_data->port_config.mi2s.format ; + return 0; +} + + +/* MI2S format field for AFE_PORT_CMD_I2S_CONFIG command + * 0: linear PCM + * 1: non-linear PCM + * 2: PCM data in IEC 60968 container + * 3: compressed data in IEC 60958 container + */ +static const char *mi2s_format[] = { + "LPCM", + "Compr", + "60958-LPCM", + "60958-Compr"}; + +static const struct soc_enum mi2s_config_enum[] = { + SOC_ENUM_SINGLE_EXT(4, mi2s_format), +}; + +static const struct snd_kcontrol_new mi2s_config_controls[] = { + SOC_ENUM_EXT("MI2S RX Format", mi2s_config_enum[0], + msm_dai_q6_mi2s_format_get, + msm_dai_q6_mi2s_format_put), + SOC_ENUM_EXT("SEC RX Format", mi2s_config_enum[0], + msm_dai_q6_mi2s_format_get, + msm_dai_q6_mi2s_format_put), + SOC_ENUM_EXT("MI2S TX Format", mi2s_config_enum[0], + msm_dai_q6_mi2s_format_get, + msm_dai_q6_mi2s_format_put), +}; + +static u8 num_of_bits_set(u8 sd_line_mask) +{ + u8 num_bits_set = 0; + + while (sd_line_mask) { + num_bits_set++; + sd_line_mask = sd_line_mask & (sd_line_mask - 1); + } + return num_bits_set; +} + +static int msm_dai_q6_mi2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: cnst list %p\n", __func__, + mi2s_dai_data->rate_constraint.list); + + if (mi2s_dai_data->rate_constraint.list) { + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &mi2s_dai_data->rate_constraint); + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &mi2s_dai_data->bitwidth_constraint); + } + + return 0; +} + +static int msm_dai_q6_mi2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + + 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: + pr_warn("greater than stereo has not been validated"); + break; + } + dai_data->rate = params_rate(params); + dai_data->port_config.mi2s.bitwidth = 16; + dai_data->bitwidth = 16; + if (!mi2s_dai_data->rate_constraint.list) { + mi2s_dai_data->rate_constraint.list = &dai_data->rate; + mi2s_dai_data->bitwidth_constraint.list = &dai_data->bitwidth; + } + return 0; +} + +static int msm_dai_q6_mi2s_get_lineconfig(u16 sd_lines, u16 *config_ptr, + unsigned int *ch_cnt) +{ + u8 num_of_sd_lines; + + num_of_sd_lines = num_of_bits_set(sd_lines); + + switch (num_of_sd_lines) { + case 0: + pr_debug("%s: no line is assigned\n", __func__); + break; + case 1: + switch (sd_lines) { + case MSM_MI2S_SD0: + *config_ptr = AFE_I2S_SD0; + break; + case MSM_MI2S_SD1: + *config_ptr = AFE_I2S_SD1; + break; + case MSM_MI2S_SD2: + *config_ptr = AFE_I2S_SD2; + break; + case MSM_MI2S_SD3: + *config_ptr = AFE_I2S_SD3; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 2: + switch (sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1: + *config_ptr = AFE_I2S_QUAD01; + break; + case MSM_MI2S_SD2 | MSM_MI2S_SD3: + *config_ptr = AFE_I2S_QUAD23; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 3: + switch (sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2: + *config_ptr = AFE_I2S_6CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + case 4: + switch (sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2 | MSM_MI2S_SD3: + *config_ptr = AFE_I2S_8CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + default: + pr_err("%s: invalid SD lines\n", __func__); + goto error_invalid_data; + } + + *ch_cnt = num_of_sd_lines; + + return 0; + +error_invalid_data: + return -EINVAL; +} + +static int msm_dai_q6_mi2s_platform_data_validation( + struct platform_device *pdev, struct snd_soc_dai_driver *dai_driver) +{ + struct msm_dai_q6_mi2s_dai_data *dai_data = dev_get_drvdata(&pdev->dev); + struct msm_mi2s_pdata *mi2s_pdata = + (struct msm_mi2s_pdata *) pdev->dev.platform_data; + u16 sdline_config; + unsigned int ch_cnt; + int rc = 0; + + if ((mi2s_pdata->rx_sd_lines & mi2s_pdata->tx_sd_lines) || + (!mi2s_pdata->rx_sd_lines && !mi2s_pdata->tx_sd_lines)) { + dev_err(&pdev->dev, + "error sd line conflict or no line assigned\n"); + rc = -EINVAL; + goto rtn; + } + + rc = msm_dai_q6_mi2s_get_lineconfig(mi2s_pdata->rx_sd_lines, + &sdline_config, &ch_cnt); + + if (IS_ERR_VALUE(rc)) { + dev_err(&pdev->dev, "invalid MI2S RX sd line config\n"); + goto rtn; + } + + if (ch_cnt) { + dai_data->rx_dai.port_config.mi2s.line = sdline_config; + dai_driver->playback.channels_min = 1; + dai_driver->playback.channels_max = ch_cnt << 1; + } else { + dai_driver->playback.channels_min = 0; + dai_driver->playback.channels_max = 0; + } + rc = msm_dai_q6_mi2s_get_lineconfig(mi2s_pdata->tx_sd_lines, + &sdline_config, &ch_cnt); + + if (IS_ERR_VALUE(rc)) { + dev_err(&pdev->dev, "invalid MI2S TX sd line config\n"); + goto rtn; + } + + if (ch_cnt) { + dai_data->tx_dai.port_config.mi2s.line = sdline_config; + dai_driver->capture.channels_min = 1; + dai_driver->capture.channels_max = ch_cnt << 1; + } else { + dai_driver->capture.channels_min = 0; + dai_driver->capture.channels_max = 0; + } + + dev_info(&pdev->dev, "%s: playback sdline %x capture sdline %x\n", + __func__, dai_data->rx_dai.port_config.mi2s.line, + dai_data->tx_dai.port_config.mi2s.line); + dev_info(&pdev->dev, "%s: playback ch_max %d capture ch_mx %d\n", + __func__, dai_driver->playback.channels_max, + dai_driver->capture.channels_max); +rtn: + return rc; +} + +static int msm_dai_q6_mi2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + + if (test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask) || + test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask)) { + dev_err(dai->dev, "%s: err chg i2s mode while dai running", + __func__); + return -EPERM; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + mi2s_dai_data->rx_dai.port_config.mi2s.ws = 1; + mi2s_dai_data->tx_dai.port_config.mi2s.ws = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + mi2s_dai_data->rx_dai.port_config.mi2s.ws = 0; + mi2s_dai_data->tx_dai.port_config.mi2s.ws = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_mi2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_mi2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + u16 port_id = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MI2S_RX : MI2S_TX); + int rc = 0; + + dev_dbg(dai->dev, "%s: cmd:%d dai_data->status_mask = %ld", + __func__, cmd, *dai_data->status_mask); + 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(port_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(port_id); + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} + +static void msm_dai_q6_mi2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + u16 port_id = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MI2S_RX : MI2S_TX); + int rc = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + rc = afe_close(port_id); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + + if (!test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask) && + !test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask)) { + mi2s_dai_data->rate_constraint.list = NULL; + mi2s_dai_data->bitwidth_constraint.list = NULL; + } + +} + +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; + return 0; +} + +static int msm_dai_q6_cdc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_data->port_config.mi2s.ws = 1; /* CPU is master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_data->port_config.mi2s.ws = 0; /* CPU is slave */ + break; + default: + return -EINVAL; + } + + 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); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + /* Q6 only supports 16 as now */ + dai_data->port_config.slim_sch.bit_width = 16; + dai_data->port_config.slim_sch.data_format = 0; + dai_data->port_config.slim_sch.num_channels = dai_data->channels; + dai_data->port_config.slim_sch.reserved = 0; + + dev_dbg(dai->dev, "%s:slimbus_dev_id[%hu] bit_wd[%hu] format[%hu]\n" + "num_channel %hu slave_ch_mapping[0] %hu\n" + "slave_port_mapping[1] %hu slave_port_mapping[2] %hu\n" + "slave_port_mapping[3] %hu\n sample_rate %d\n", __func__, + dai_data->port_config.slim_sch.slimbus_dev_id, + dai_data->port_config.slim_sch.bit_width, + dai_data->port_config.slim_sch.data_format, + dai_data->port_config.slim_sch.num_channels, + dai_data->port_config.slim_sch.slave_ch_mapping[0], + dai_data->port_config.slim_sch.slave_ch_mapping[1], + dai_data->port_config.slim_sch.slave_ch_mapping[2], + dai_data->port_config.slim_sch.slave_ch_mapping[3], + 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; +} +static int msm_dai_q6_auxpcm_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + if (params_channels(params) != 1) { + dev_err(dai->dev, "AUX PCM supports only mono stream\n"); + return -EINVAL; + } + dai_data->channels = params_channels(params); + + dai_data->rate = params_rate(params); + switch (dai_data->rate) { + case 8000: + dai_data->port_config.pcm.mode = auxpcm_pdata->mode_8k.mode; + dai_data->port_config.pcm.sync = auxpcm_pdata->mode_8k.sync; + dai_data->port_config.pcm.frame = auxpcm_pdata->mode_8k.frame; + dai_data->port_config.pcm.quant = auxpcm_pdata->mode_8k.quant; + dai_data->port_config.pcm.slot = auxpcm_pdata->mode_8k.slot; + dai_data->port_config.pcm.data = auxpcm_pdata->mode_8k.data; + break; + case 16000: + dai_data->port_config.pcm.mode = auxpcm_pdata->mode_16k.mode; + dai_data->port_config.pcm.sync = auxpcm_pdata->mode_16k.sync; + dai_data->port_config.pcm.frame = auxpcm_pdata->mode_16k.frame; + dai_data->port_config.pcm.quant = auxpcm_pdata->mode_16k.quant; + dai_data->port_config.pcm.slot = auxpcm_pdata->mode_16k.slot; + dai_data->port_config.pcm.data = auxpcm_pdata->mode_16k.data; + break; + default: + dev_err(dai->dev, "AUX PCM supports only 8kHz and 16kHz sampling rate\n"); + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_afe_rtproxy_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); + + dai_data->rate = params_rate(params); + dai_data->port_config.rtproxy.num_ch = + params_channels(params); + + pr_debug("channel %d entered,dai_id: %d,rate: %d\n", + dai_data->port_config.rtproxy.num_ch, dai->id, dai_data->rate); + + dai_data->port_config.rtproxy.bitwidth = 16; /* Q6 only supports 16 */ + dai_data->port_config.rtproxy.interleaved = 1; + dai_data->port_config.rtproxy.frame_sz = params_period_bytes(params); + dai_data->port_config.rtproxy.jitter = + dai_data->port_config.rtproxy.frame_sz/2; + dai_data->port_config.rtproxy.lw_mark = 0; + dai_data->port_config.rtproxy.hw_mark = 0; + dai_data->port_config.rtproxy.rsvd = 0; + + 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: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_hw_params(params, dai, substream->stream); + break; + + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_3_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_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; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_TX: + case RT_PROXY_DAI_002_RX: + rc = msm_dai_q6_afe_rtproxy_hw_params(params, dai); + break; + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = 0; + break; + default: + dev_err(dai->dev, "invalid AFE port ID\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static void msm_dai_q6_auxpcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int rc = 0; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. Just" + " return\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + aux_pcm_count = 0; + mutex_unlock(&aux_pcm_mutex); + return; + } + + pr_debug("%s: dai->id = %d aux_pcm_count = %d\n", __func__, + dai->id, aux_pcm_count); + + clk_disable_unprepare(pcm_clk); + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close PCM_RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX port\n"); + + mutex_unlock(&aux_pcm_mutex); +} + +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 = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + rc = afe_close(dai->id); /* can block */ + break; + } + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + pr_debug("%s: dai_data->status_mask = %ld\n", __func__, + *dai_data->status_mask); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } +} + +static int msm_dai_q6_auxpcm_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; + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + unsigned long pcm_clk_rate; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 2. Just" + " return.\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return 0; + } else if (aux_pcm_count > 2) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d > 2\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + aux_pcm_count++; + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d after " + " increment\n", __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + pr_debug("%s:dai->id:%d aux_pcm_count = %d. opening afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + + /* + * For AUX PCM Interface the below sequence of clk + * settings and afe_open is a strict requirement. + * + * Also using afe_open instead of afe_port_start_nowait + * to make sure the port is open before deasserting the + * clock line. This is required because pcm register is + * not written before clock deassert. Hence the hw does + * not get updated with new setting if the below clock + * assert/deasset and afe_open sequence is not followed. + */ + + clk_reset(pcm_clk, CLK_RESET_ASSERT); + + afe_open(PCM_RX, &dai_data->port_config, dai_data->rate); + + afe_open(PCM_TX, &dai_data->port_config, dai_data->rate); + if (dai_data->rate == 8000) { + pcm_clk_rate = auxpcm_pdata->mode_8k.pcm_clk_rate; + } else if (dai_data->rate == 16000) { + pcm_clk_rate = auxpcm_pdata->mode_8k.pcm_clk_rate; + } else { + dev_err(dai->dev, "%s: Invalid AUX PCM rate %d\n", __func__, + dai_data->rate); + return -EINVAL; + } + + rc = clk_set_rate(pcm_clk, pcm_clk_rate); + if (rc < 0) { + pr_err("%s: clk_set_rate failed\n", __func__); + return rc; + } + + clk_prepare_enable(pcm_clk); + clk_reset(pcm_clk, CLK_RESET_DEASSERT); + + mutex_unlock(&aux_pcm_mutex); + + return rc; +} + +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)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_auxpcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int rc = 0; + + pr_debug("%s:port:%d cmd:%d aux_pcm_count= %d", + __func__, dai->id, cmd, aux_pcm_count); + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* afe_open will be called from prepare */ + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return 0; + + default: + rc = -EINVAL; + } + + 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. + */ + pr_debug("%s:port:%d cmd:%d dai_data->status_mask = %ld", + __func__, dai->id, cmd, *dai_data->status_mask); + 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)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_start_nowait(dai->id); + break; + default: + afe_port_start_nowait(dai->id, + &dai_data->port_config, dai_data->rate); + break; + } + 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)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_stop_nowait(dai->id); + break; + default: + afe_port_stop_nowait(dai->id); + break; + } + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} +static int msm_dai_q6_dai_auxpcm_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + mutex_lock(&aux_pcm_mutex); + + if (!auxpcm_plat_data) + auxpcm_plat_data = auxpcm_pdata; + else if (auxpcm_plat_data != auxpcm_pdata) { + + dev_err(dai->dev, "AUX PCM RX and TX devices does not have" + " same platform data\n"); + return -EINVAL; + } + + /* + * The clk name for AUX PCM operation is passed as platform + * data to the cpu driver, since cpu drive is unaware of any + * boarc specific configuration. + */ + if (!pcm_clk) { + + pcm_clk = clk_get(dai->dev, auxpcm_pdata->clk); + + if (IS_ERR(pcm_clk)) { + pr_err("%s: could not get pcm_clk\n", __func__); + pcm_clk = NULL; + return -ENODEV; + } + } + + mutex_unlock(&aux_pcm_mutex); + + 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); + + pr_debug("%s : probe done for dai->id %d\n", __func__, dai->id); + return rc; +} + +static int msm_dai_q6_dai_auxpcm_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. clean" + " up and return\n", __func__, dai->id); + goto done; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + goto done; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + goto done; + } + + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d." + "closing afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX AFE port\n"); + +done: + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + mutex_unlock(&aux_pcm_mutex); + + return 0; +} + +static int msm_dai_q6_dai_mi2s_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct snd_kcontrol *kcontrol = NULL; + int rc = 0; + + if (mi2s_dai_data->rx_dai.port_config.mi2s.line) { + kcontrol = snd_ctl_new1(&mi2s_config_controls[0], + &mi2s_dai_data->rx_dai); + rc = snd_ctl_add(dai->card->snd_card, kcontrol); + + if (IS_ERR_VALUE(rc)) { + dev_err(dai->dev, "%s: err add RX fmt ctl\n", __func__); + goto rtn; + } + } + + if (mi2s_dai_data->tx_dai.port_config.mi2s.line) { + rc = snd_ctl_add(dai->card->snd_card, + snd_ctl_new1(&mi2s_config_controls[2], + &mi2s_dai_data->tx_dai)); + + if (IS_ERR_VALUE(rc)) { + if (kcontrol) + snd_ctl_remove(dai->card->snd_card, kcontrol); + dev_err(dai->dev, "%s: err add TX fmt ctl\n", __func__); + } + } + +rtn: + return rc; +} + +static int msm_dai_q6_dai_mi2s_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + int rc; + + /* If AFE port is still up, close it */ + if (test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask)) { + rc = afe_close(MI2S_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close MI2S_RX port\n"); + clear_bit(STATUS_PORT_STARTED, + mi2s_dai_data->rx_dai.status_mask); + } + if (test_bit(STATUS_PORT_STARTED, mi2s_dai_data->tx_dai.status_mask)) { + rc = afe_close(MI2S_TX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close MI2S_TX port\n"); + clear_bit(STATUS_PORT_STARTED, + mi2s_dai_data->tx_dai.status_mask); + } + kfree(mi2s_dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + const struct snd_kcontrol_new *kcontrol; + + 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); + if (dai->id == SECONDARY_I2S_RX) { + kcontrol = &mi2s_config_controls[1]; + rc = snd_ctl_add(dai->card->snd_card, + snd_ctl_new1(kcontrol, 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)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + 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); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static int msm_dai_q6_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + int rc = 0; + + dev_dbg(dai->dev, "enter %s, id = %d fmt[%d]\n", __func__, + dai->id, fmt); + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_set_fmt(dai, fmt); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static int msm_dai_q6_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + int rc = 0; + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + unsigned int i = 0; + + dev_dbg(dai->dev, "%s: dai_id = %d\n", __func__, dai->id); + switch (dai->id) { + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_2_RX: + case SLIMBUS_3_RX: + case SLIMBUS_4_RX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!rx_slot) + return -EINVAL; + for (i = 0; i < rx_num; i++) { + dai_data->port_config.slim_sch.slave_ch_mapping[i] = + rx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, + rx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = rx_num; + pr_debug("%s:SLIMBUS_%d_RX cnt[%d] ch[%d %d]\n", __func__, + (dai->id - SLIMBUS_0_RX) / 2, + rx_num, dai_data->port_config.slim_sch.slave_ch_mapping[0], + dai_data->port_config.slim_sch.slave_ch_mapping[1]); + + break; + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case SLIMBUS_2_TX: + case SLIMBUS_4_TX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!tx_slot) + return -EINVAL; + for (i = 0; i < tx_num; i++) { + dai_data->port_config.slim_sch.slave_ch_mapping[i] = + tx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, tx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = tx_num; + pr_debug("%s:SLIMBUS_%d_TX cnt[%d] ch[%d %d]\n", __func__, + (dai->id - SLIMBUS_0_TX) / 2, + tx_num, dai_data->port_config.slim_sch.slave_ch_mapping[0], + dai_data->port_config.slim_sch.slave_ch_mapping[1]); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + return rc; +} + +static struct snd_soc_dai_ops msm_dai_q6_mi2s_ops = { + .startup = msm_dai_q6_mi2s_startup, + .prepare = msm_dai_q6_mi2s_prepare, + .trigger = msm_dai_q6_mi2s_trigger, + .hw_params = msm_dai_q6_mi2s_hw_params, + .shutdown = msm_dai_q6_mi2s_shutdown, + .set_fmt = msm_dai_q6_mi2s_set_fmt, +}; + +static struct snd_soc_dai_ops msm_dai_q6_ops = { + .prepare = msm_dai_q6_prepare, + .trigger = msm_dai_q6_trigger, + .hw_params = msm_dai_q6_hw_params, + .shutdown = msm_dai_q6_shutdown, + .set_fmt = msm_dai_q6_set_fmt, + .set_channel_map = msm_dai_q6_set_channel_map, +}; + +static struct snd_soc_dai_ops msm_dai_q6_auxpcm_ops = { + .prepare = msm_dai_q6_auxpcm_prepare, + .trigger = msm_dai_q6_auxpcm_trigger, + .hw_params = msm_dai_q6_auxpcm_hw_params, + .shutdown = msm_dai_q6_auxpcm_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 = 4, + .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_afe_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_afe_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 = 4, + .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_voice_playback_tx_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_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_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_incall_record_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, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_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_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_tx_dai = { + .capture = { + .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_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + +/* Channel min and max are initialized base on platform data */ +static struct snd_soc_dai_driver msm_dai_q6_mi2s_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_mi2s_ops, + .probe = msm_dai_q6_dai_mi2s_probe, + .remove = msm_dai_q6_dai_mi2s_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_1_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_min = 8000, + .rate_max = 16000, + }, + .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_1_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .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_2_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .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_2_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 192000, + }, + .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_3_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .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, +}; + +/* 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: + case SECONDARY_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 PCM_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_rx_dai); + break; + case PCM_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_tx_dai); + break; + + case SLIMBUS_0_RX: + case SLIMBUS_4_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_rx_dai); + break; + case SLIMBUS_0_TX: + case SLIMBUS_4_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_tx_dai); + break; + case SLIMBUS_1_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_rx_dai); + break; + case SLIMBUS_1_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_tx_dai); + break; + case SLIMBUS_2_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_2_rx_dai); + break; + case SLIMBUS_2_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_2_tx_dai); + break; + case SLIMBUS_3_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_3_rx_dai); + break; + 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; + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_rx_dai); + break; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_002_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_tx_dai); + break; + case VOICE_PLAYBACK_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_voice_playback_tx_dai); + break; + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_incall_record_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 __devinit int msm_dai_q6_mi2s_dev_probe(struct platform_device *pdev) +{ + struct msm_dai_q6_mi2s_dai_data *dai_data; + int rc = 0; + + dev_dbg(&pdev->dev, "%s: pdev %p dev %p\n", __func__, pdev, &pdev->dev); + + dai_data = kzalloc(sizeof(struct msm_dai_q6_mi2s_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(&pdev->dev, "fail to allocate dai data\n"); + rc = -ENOMEM; + goto rtn; + } else + dev_set_drvdata(&pdev->dev, dai_data); + + rc = msm_dai_q6_mi2s_platform_data_validation(pdev, + &msm_dai_q6_mi2s_dai); + if (IS_ERR_VALUE(rc)) + goto err_pdata; + + dai_data->rate_constraint.count = 1; + dai_data->bitwidth_constraint.count = 1; + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_mi2s_dai); + + if (IS_ERR_VALUE(rc)) + goto err_pdata; + + return 0; + +err_pdata: + kfree(dai_data); +rtn: + return rc; +} + +static __devexit int msm_dai_q6_mi2s_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 struct platform_driver msm_dai_q6_mi2s_driver = { + .probe = msm_dai_q6_mi2s_dev_probe, + .remove = msm_dai_q6_mi2s_dev_remove, + .driver = { + .name = "msm-dai-q6-mi2s", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_q6_init(void) +{ + int rc1, rc2; + + rc1 = platform_driver_register(&msm_dai_q6_mi2s_driver); + + if (IS_ERR_VALUE(rc1)) + pr_err("%s: fail to register mi2s dai driver\n", __func__); + + rc2 = platform_driver_register(&msm_dai_q6_driver); + + if (IS_ERR_VALUE(rc2)) + pr_err("%s: fail to register mi2s dai driver\n", __func__); + + return (IS_ERR_VALUE(rc1) && IS_ERR_VALUE(rc2)) ? -1 : 0; +} +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-stub.c b/sound/soc/msm/msm-dai-stub.c new file mode 100644 index 0000000000000000000000000000000000000000..b2bfa2ceb030859d4641780688afd9ded9fa02df --- /dev/null +++ b/sound/soc/msm/msm-dai-stub.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty 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 int msm_dai_stub_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + pr_debug("%s:\n", __func__); + + return 0; +} + +static struct snd_soc_dai_ops msm_dai_stub_ops = { + .set_channel_map = msm_dai_stub_set_channel_map, +}; + +static struct snd_soc_dai_driver msm_dai_stub_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, + }, + .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_stub_ops, +}; + +static __devinit int msm_dai_stub_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_stub_dai); + + return rc; +} + +static __devexit int msm_dai_stub_dev_remove(struct platform_device *pdev) +{ + pr_debug("%s:\n", __func__); + + snd_soc_unregister_dai(&pdev->dev); + + return 0; +} + +static struct platform_driver msm_dai_stub_driver = { + .probe = msm_dai_stub_dev_probe, + .remove = msm_dai_stub_dev_remove, + .driver = { + .name = "msm-dai-stub", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_stub_init(void) +{ + pr_debug("%s:\n", __func__); + + return platform_driver_register(&msm_dai_stub_driver); +} +module_init(msm_dai_stub_init); + +static void __exit msm_dai_stub_exit(void) +{ + pr_debug("%s:\n", __func__); + + platform_driver_unregister(&msm_dai_stub_driver); +} +module_exit(msm_dai_stub_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Stub 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 0000000000000000000000000000000000000000..61e46753890832edfe3d8dbe36577ccd01a6785d --- /dev/null +++ b/sound/soc/msm/msm-dai.c @@ -0,0 +1,150 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2011, 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 "msm-pcm.h" + +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-multi-ch-pcm-q6.c b/sound/soc/msm/msm-multi-ch-pcm-q6.c new file mode 100644 index 0000000000000000000000000000000000000000..734d34f7394ed3f91df7fea15c4272ff6e5c98a6 --- /dev/null +++ b/sound/soc/msm/msm-multi-ch-pcm-q6.c @@ -0,0 +1,804 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; +}; + +struct snd_msm_volume { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm_volume multi_ch_pcm_audio = {NULL, 0x2000}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 4032 +#define PLAYBACK_MIN_PERIOD_SIZE 256 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 320 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .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 = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .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 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 6, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .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[CAPTURE_NUM_PERIODS][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_nolock(IN, + prtd->audio_client, + &size, &idx)) { + 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_is_cpu_buf_avail_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + 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); + }; + } + atomic_set(&prtd->start, 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_multi_ch_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); + 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; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + 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; + } + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_err("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_hardware_playback; + ret = q6asm_open_write(prtd->audio_client, + FORMAT_MULTI_CHANNEL_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) { + runtime->hw = msm_pcm_hardware_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_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); + + 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_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) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PLAYBACK_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE, + PLAYBACK_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE); + if (ret < 0) { + pr_err("constraint for buffer bytes min max ret = %d\n", + ret); + } + } + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + pr_debug("substream->pcm->device = %d\n", substream->pcm->device); + pr_debug("soc_prtd->dai_link->be_id = %d\n", soc_prtd->dai_link->be_id); + multi_ch_pcm_audio.prtd = prtd; + ret = multi_ch_pcm_set_volume(multi_ch_pcm_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(multi_ch_pcm_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(multi_ch_pcm_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int multi_ch_pcm_set_volume(unsigned volume) +{ + int rc = 0; + pr_err("multi_ch_pcm_set_volume\n"); + + if (multi_ch_pcm_audio.prtd && multi_ch_pcm_audio.prtd->audio_client) { + pr_err("%s q6asm_set_volume\n", __func__); + rc = q6asm_set_volume(multi_ch_pcm_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + multi_ch_pcm_audio.volume = volume; + return rc; +} + + +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_cmd(prtd->audio_client, CMD_CLOSE); + 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); + multi_ch_pcm_audio.prtd = NULL; + 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_cmd(prtd->audio_client, CMD_CLOSE); + 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_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) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +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; + + if (dir == OUT) { + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + } else { + /* + *TODO : Need to Add Async IO changes. All period + * size might not be supported. + */ + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + (params_buffer_bytes(params) / params_periods(params)), + params_periods(params)); + } + + 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; + if (dir == OUT) + dma_buf->bytes = runtime->hw.buffer_bytes_max; + else + dma_buf->bytes = params_buffer_bytes(params); + 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-multi-ch-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("Multi channel PCM module platform 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 0000000000000000000000000000000000000000..2e7114c94f0c11bc6a5a0bce76d8567d3147eb17 --- /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-afe.c b/sound/soc/msm/msm-pcm-afe.c new file mode 100644 index 0000000000000000000000000000000000000000..482cbee866729e05b34fb8287cde0379565c0d26 --- /dev/null +++ b/sound/soc/msm/msm-pcm-afe.c @@ -0,0 +1,621 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or 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 "msm-pcm-afe.h" + +#define MIN_PERIOD_SIZE (128 * 2) +#define MAX_PERIOD_SIZE (128 * 2 * 2 * 6) +static struct snd_pcm_hardware msm_afe_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 | + SNDRV_PCM_RATE_48000), + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_PERIOD_SIZE * 32, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = 32, + .periods_max = 384, + .fifo_size = 0, +}; +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt); +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt); + +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + if (prtd->start) { + pr_debug("sending frame to DSP: poll_time: %d\n", + prtd->poll_time); + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + afe_rt_proxy_port_write( + (prtd->dma_addr + + (prtd->dsp_cnt * + snd_pcm_lib_period_bytes(prtd->substream))), + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + if (prtd->start) { + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + afe_rt_proxy_port_read( + (prtd->dma_addr + (prtd->dsp_cnt + * snd_pcm_lib_period_bytes(prtd->substream))), + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + pr_debug("sending frame rec to DSP: poll_time: %d\n", + prtd->poll_time); + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static void pcm_afe_process_tx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes + (prtd->substream) * + 1000 * 1000)/ + (runtime->rate * + runtime->channels * 2))); + pr_debug("prtd->poll_time: %d", + prtd->poll_time); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_SERVICE_CMD_RTPORT_WR: + pr_debug("write done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static void pcm_afe_process_rx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes(prtd->substream) + * 1000 * 1000)/(runtime->rate + * runtime->channels * 2))); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + pr_debug("prtd->poll_time : %d", prtd->poll_time); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_SERVICE_CMD_RTPORT_RD: + pr_debug("Read done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static int msm_afe_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s: sample_rate=%d\n", __func__, runtime->rate); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_tx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return ret; +} + +static int msm_afe_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s\n", __func__); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_rx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return 0; +} + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 16000, 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_afe_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = NULL; + int ret = 0; + + prtd = kzalloc(sizeof(struct pcm_afe_info), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } else + pr_debug("prtd %x\n", (unsigned int)prtd); + + mutex_init(&prtd->lock); + spin_lock_init(&prtd->dsp_lock); + prtd->dsp_cnt = 0; + + mutex_lock(&prtd->lock); + + runtime->hw = msm_afe_hardware; + prtd->substream = substream; + runtime->private_data = prtd; + mutex_unlock(&prtd->lock); + hrtimer_init(&prtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->hrt.function = afe_hrtimer_callback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->hrt.function = afe_hrtimer_rec_callback; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_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) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + return 0; +} + +static int msm_afe_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_dma_buffer *dma_buf; + struct snd_pcm_runtime *runtime; + struct pcm_afe_info *prtd; + struct snd_soc_pcm_runtime *rtd = NULL; + struct snd_soc_dai *dai = NULL; + int ret = 0; + + pr_debug("%s\n", __func__); + if (substream == NULL) { + pr_err("substream is NULL\n"); + return -EINVAL; + } + rtd = substream->private_data; + dai = rtd->cpu_dai; + runtime = substream->runtime; + prtd = runtime->private_data; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } + hrtimer_cancel(&prtd->hrt); + + rc = afe_cmd_memory_unmap(runtime->dma_addr); + if (rc < 0) + pr_err("AFE memory unmap failed\n"); + + pr_debug("release all buffer\n"); + dma_buf = &substream->dma_buffer; + if (dma_buf == NULL) { + pr_debug("dma_buf is NULL\n"); + goto done; + } + + if (dma_buf->area) { + if (msm_subsystem_unmap_buffer(prtd->mem_buffer) < 0) { + pr_err("%s: unmap buffer failed\n", __func__); + prtd->mem_buffer = NULL; + dma_buf->area = NULL; + } + } + + if (dma_buf->addr) + free_contiguous_memory_by_paddr(dma_buf->addr); +done: + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + mutex_unlock(&prtd->lock); + prtd->prepared--; + kfree(prtd); + return 0; +} +static int msm_afe_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + prtd->pcm_irq_pos = 0; + if (prtd->prepared) + return 0; + mutex_lock(&prtd->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_afe_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_afe_capture_prepare(substream); + mutex_unlock(&prtd->lock); + return ret; +} +static int msm_afe_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + int result = 0; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} +static int msm_afe_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *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: SNDRV_PCM_TRIGGER_START\n", __func__); + prtd->start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + prtd->start = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +static int msm_afe_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 pcm_afe_info *prtd = runtime->private_data; + int rc; + unsigned int flags = 0; + + pr_debug("%s:\n", __func__); + + mutex_lock(&prtd->lock); + + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + + dma_buf->addr = allocate_contiguous_ebi_nomap( + runtime->hw.buffer_bytes_max, SZ_4K); + if (!dma_buf->addr) { + pr_err("%s:MSM AFE physical memory allocation failed\n", + __func__); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + + flags = MSM_SUBSYSTEM_MAP_KADDR | MSM_SUBSYSTEM_MAP_CACHED; + + prtd->mem_buffer = msm_subsystem_map_buffer(dma_buf->addr, + runtime->hw.buffer_bytes_max, flags, + NULL, 0); + if (IS_ERR((void *) prtd->mem_buffer)) { + pr_err("%s: map_buffer failed error = %ld\n", __func__, + PTR_ERR((void *)prtd->mem_buffer)); + free_contiguous_memory_by_paddr(dma_buf->addr); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + + dma_buf->area = prtd->mem_buffer->vaddr; + + pr_debug("%s: dma_buf->area: 0x%p, dma_buf->addr: 0x%x", __func__, + (unsigned int *) dma_buf->area, dma_buf->addr); + + if (!dma_buf->area) { + pr_err("%s: Invalid Virtual address\n", __func__); + if (prtd->mem_buffer) { + msm_subsystem_unmap_buffer(prtd->mem_buffer); + prtd->mem_buffer = NULL; + dma_buf->area = NULL; + } + free_contiguous_memory_by_paddr(dma_buf->addr); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + + dma_buf->bytes = runtime->hw.buffer_bytes_max; + memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); + prtd->dma_addr = (u32) dma_buf->addr; + + mutex_unlock(&prtd->lock); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + rc = afe_cmd_memory_map(dma_buf->addr, dma_buf->bytes); + if (rc < 0) + pr_err("fail to map memory to DSP\n"); + + return rc; +} +static snd_pcm_uframes_t msm_afe_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= snd_pcm_lib_buffer_bytes(substream)) + 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 struct snd_pcm_ops msm_afe_ops = { + .open = msm_afe_open, + .hw_params = msm_afe_hw_params, + .trigger = msm_afe_trigger, + .close = msm_afe_close, + .prepare = msm_afe_prepare, + .mmap = msm_afe_mmap, + .pointer = msm_afe_pointer, +}; + + +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("%s\n", __func__); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static int msm_afe_afe_probe(struct snd_soc_platform *platform) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_afe_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_afe_afe_probe, +}; + +static __devinit int msm_afe_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_afe_remove(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_afe_driver = { + .driver = { + .name = "msm-pcm-afe", + .owner = THIS_MODULE, + }, + .probe = msm_afe_probe, + .remove = __devexit_p(msm_afe_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + pr_debug("%s\n", __func__); + return platform_driver_register(&msm_afe_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + pr_debug("%s\n", __func__); + platform_driver_unregister(&msm_afe_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("AFE PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-afe.h b/sound/soc/msm/msm-pcm-afe.h new file mode 100644 index 0000000000000000000000000000000000000000..38026d515dcfd6ea86b1605768020318655029e3 --- /dev/null +++ b/sound/soc/msm/msm-pcm-afe.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_AFE_H +#define _MSM_PCM_AFE_H +#include +#include + + +struct pcm_afe_info { + unsigned long dma_addr; + struct snd_pcm_substream *substream; + unsigned int pcm_irq_pos; /* IRQ position */ + struct mutex lock; + spinlock_t dsp_lock; + uint32_t samp_rate; + uint32_t channel_mode; + uint8_t start; + uint32_t dsp_cnt; + uint32_t buf_phys; + int32_t mmap_flag; + int prepared; + struct hrtimer hrt; + int poll_time; + struct msm_mapped_buffer *mem_buffer; +}; + + +#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_AFE_H*/ diff --git a/sound/soc/msm/msm-pcm-hostless.c b/sound/soc/msm/msm-pcm-hostless.c new file mode 100644 index 0000000000000000000000000000000000000000..c61511dc2e8e61e68e5a4870d0f8188327094c09 --- /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 0000000000000000000000000000000000000000..c65a7d2b045bfd6452900984eb869648fb411c74 --- /dev/null +++ b/sound/soc/msm/msm-pcm-lpa.c @@ -0,0 +1,607 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm-pcm-q6.h" +#include "msm-pcm-routing.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm lpa_audio; + +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 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 1024 * 1024, +/* TODO: Check on the lowest period size we can support */ + .period_bytes_min = 128 * 1024, + .period_bytes_max = 256 * 1024, + .periods_min = 4, + .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 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 = NULL; + unsigned long flag = 0; + int i = 0; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&the_locks.event_lock, flag); + 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 (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + else + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + + buf = prtd->audio_client->port[IN].buf; + 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); + atomic_set(&prtd->pending_buffer, 0); + 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: { + if (!atomic_read(&prtd->pending_buffer)) + break; + if (runtime->status->hw_ptr >= + runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, prtd->pcm_count); + buf = prtd->audio_client->port[IN].buf; + param.paddr = (unsigned long)buf[prtd->out_head].phys; + 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].phys; + 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); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } + spin_unlock_irqrestore(&the_locks.event_lock, flag); +} + +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; + prtd->out_head = 0; + 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: + prtd->pcm_irq_pos = 0; + 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; + 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; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + 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; + atomic_set(&prtd->pending_buffer, 1); + runtime->private_data = prtd; + lpa_audio.prtd = prtd; + lpa_set_volume(lpa_audio.volume); + ret = q6asm_set_softpause(lpa_audio.prtd->audio_client, &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(lpa_audio.prtd->audio_client, &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int lpa_set_volume(unsigned volume) +{ + int rc = 0; + if (lpa_audio.prtd && lpa_audio.prtd->audio_client) { + rc = q6asm_set_volume(lpa_audio.prtd->audio_client, volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + lpa_audio.volume = volume; + return rc; +} + +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 rc = 0; + + /* + If routing is still enabled, we need to issue EOS to + the DSP + To issue EOS to dsp, we need to be run state otherwise + EOS is not honored. + */ + if (msm_routing_check_backend_enabled(soc_prtd->dai_link->be_id)) { + rc = q6asm_run(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->pending_buffer, 0); + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + pr_debug("%s\n", __func__); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("EOS cmd timeout\n"); + prtd->pcm_irq_pos = 0; + } + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + lpa_audio.prtd = NULL; + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + 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_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; + + 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) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +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 == NULL || buf[0].data == NULL) + 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 int msm_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:" + "timestamp = %lld,\n",__func__, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = msm_pcm_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) +{ + spin_lock_init(&the_locks.event_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); + + 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 0000000000000000000000000000000000000000..39ce43683d5ebb04739cc601798110607169da1b --- /dev/null +++ b/sound/soc/msm/msm-pcm-q6.c @@ -0,0 +1,740 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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; +}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_PERIOD_SIZE 2048 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 320 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .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 = 4, + .buffer_bytes_max = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .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 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .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[CAPTURE_NUM_PERIODS][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_nolock(IN, + prtd->audio_client, + &size, &idx)) { + 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_is_cpu_buf_avail_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + 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); + }; + } + atomic_set(&prtd->start, 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_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); + if (prtd->channel_mode > 2) { + ret = q6asm_enc_cfg_blk_multi_ch_pcm(prtd->audio_client, + prtd->samp_rate, prtd->channel_mode); + } else { + 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); + 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; + } + 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) { + runtime->hw = msm_pcm_hardware_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) { + runtime->hw = msm_pcm_hardware_capture; + } + + 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); + + 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_cmd(prtd->audio_client, CMD_CLOSE); + 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_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_cmd(prtd->audio_client, CMD_CLOSE); + 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_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) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +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; + int format = FORMAT_LINEAR_PCM; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + /*capture path*/ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (params_channels(params) > 2) + format = FORMAT_MULTI_CHANNEL_LINEAR_PCM; + pr_debug("%s format = :0x%x\n", __func__, format); + + ret = q6asm_open_read(prtd->audio_client, format); + if (ret < 0) { + pr_err("%s: q6asm_open_read failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + + 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 == NULL || buf[0].data == NULL) + 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, + .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 0000000000000000000000000000000000000000..e5551ea68f7f73774a320f4a149729d29534c018 --- /dev/null +++ b/sound/soc/msm/msm-pcm-q6.h @@ -0,0 +1,83 @@ +/* + * 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 { + spinlock_t event_lock; + 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; + atomic_t pending_buffer; +}; + +#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 0000000000000000000000000000000000000000..7a269ca73680a46fceed81ee8e4907485d495d2c --- /dev/null +++ b/sound/soc/msm/msm-pcm-routing.c @@ -0,0 +1,2409 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-routing.h" +#include "qdsp6/q6voice.h" + +struct msm_pcm_routing_bdai_data { + u16 port_id; /* AFE port ID */ + u8 active; /* track if this backend is enabled */ + unsigned long fe_sessions; /* Front-end sessions */ + unsigned long port_sessions; /* track Tx BE ports -> Rx BE */ + unsigned int sample_rate; + unsigned int channel; +}; + +#define INVALID_SESSION -1 +#define SESSION_TYPE_RX 0 +#define SESSION_TYPE_TX 1 + +static struct mutex routing_lock; + +static int fm_switch_enable; +static int fm_pcmrx_switch_enable; + +#define INT_RX_VOL_MAX_STEPS 0x2000 +#define INT_RX_VOL_GAIN 0x2000 + +static int msm_route_fm_vol_control; +static const DECLARE_TLV_DB_LINEAR(fm_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + +static int msm_route_lpa_vol_control; +static const DECLARE_TLV_DB_LINEAR(lpa_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + +static int msm_route_multimedia2_vol_control; +static const DECLARE_TLV_DB_LINEAR(multimedia2_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + +static int msm_route_compressed_vol_control; +static const DECLARE_TLV_DB_LINEAR(compressed_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + + + +/* Equal to Frontend after last of the MULTIMEDIA SESSIONS */ +#define MAX_EQ_SESSIONS MSM_FRONTEND_DAI_CS_VOICE + +enum { + EQ_BAND1 = 0, + EQ_BAND2, + EQ_BAND3, + EQ_BAND4, + EQ_BAND5, + EQ_BAND6, + EQ_BAND7, + EQ_BAND8, + EQ_BAND9, + EQ_BAND10, + EQ_BAND11, + EQ_BAND12, + EQ_BAND_MAX, +}; + +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; +} __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[EQ_BAND_MAX]; +} __packed; + +struct msm_audio_eq_stream_config eq_data[MAX_EQ_SESSIONS]; + +static void msm_send_eq_values(int eq_idx); +/* This array is indexed by back-end DAI ID defined in msm-pcm-routing.h + * If new back-end is defined, add new back-end DAI ID at the end of enum + */ + +union srs_trumedia_params_u { + struct srs_trumedia_params srs_params; + unsigned short int raw_params[1]; +}; +static union srs_trumedia_params_u msm_srs_trumedia_params[2]; +static int srs_port_id = -1; + +static void srs_send_params(int port_id, unsigned int techs, + int param_block_idx) { + pr_debug("SRS %s: called, port_id = %d, techs flags = %u," + " paramblockidx %d", __func__, port_id, techs, + param_block_idx); + /* force all if techs is set to 1 */ + if (techs == 1) + techs = 0xFFFFFFFF; + + if (techs & (1 << SRS_ID_WOWHD)) + srs_trumedia_open(port_id, SRS_ID_WOWHD, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.wowhd); + if (techs & (1 << SRS_ID_CSHP)) + srs_trumedia_open(port_id, SRS_ID_CSHP, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.cshp); + if (techs & (1 << SRS_ID_HPF)) + srs_trumedia_open(port_id, SRS_ID_HPF, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.hpf); + if (techs & (1 << SRS_ID_PEQ)) + srs_trumedia_open(port_id, SRS_ID_PEQ, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.peq); + if (techs & (1 << SRS_ID_HL)) + srs_trumedia_open(port_id, SRS_ID_HL, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.hl); + if (techs & (1 << SRS_ID_GLOBAL)) + srs_trumedia_open(port_id, SRS_ID_GLOBAL, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.global); +} + +static struct msm_pcm_routing_bdai_data msm_bedais[MSM_BACKEND_DAI_MAX] = { + { PRIMARY_I2S_RX, 0, 0, 0, 0, 0}, + { PRIMARY_I2S_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_0_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_0_TX, 0, 0, 0, 0, 0}, + { HDMI_RX, 0, 0, 0, 0, 0}, + { INT_BT_SCO_RX, 0, 0, 0, 0, 0}, + { INT_BT_SCO_TX, 0, 0, 0, 0, 0}, + { INT_FM_RX, 0, 0, 0, 0, 0}, + { INT_FM_TX, 0, 0, 0, 0, 0}, + { RT_PROXY_PORT_001_RX, 0, 0, 0, 0, 0}, + { RT_PROXY_PORT_001_TX, 0, 0, 0, 0, 0}, + { PCM_RX, 0, 0, 0, 0, 0}, + { PCM_TX, 0, 0, 0, 0, 0}, + { VOICE_PLAYBACK_TX, 0, 0, 0, 0, 0}, + { VOICE_RECORD_RX, 0, 0, 0, 0, 0}, + { VOICE_RECORD_TX, 0, 0, 0, 0, 0}, + { MI2S_RX, 0, 0, 0, 0, 0}, + { MI2S_TX, 0, 0, 0, 0}, + { SECONDARY_I2S_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_1_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_1_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_4_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_4_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_3_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_EXTPROC_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_EXTPROC_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_EXTPROC_RX, 0, 0, 0, 0, 0}, +}; + + +/* Track ASM playback & capture sessions of DAI */ +static int fe_dai_map[MSM_FRONTEND_DAI_MM_SIZE][2] = { + /* MULTIMEDIA1 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA2 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA3 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA4 */ + {INVALID_SESSION, INVALID_SESSION}, +}; + +static uint8_t is_be_dai_extproc(int be_dai) +{ + if (be_dai == MSM_BACKEND_DAI_EXTPROC_RX || + be_dai == MSM_BACKEND_DAI_EXTPROC_TX || + be_dai == MSM_BACKEND_DAI_EXTPROC_EC_TX) + return 1; + else + return 0; +} + +static void msm_pcm_routing_build_matrix(int fedai_id, int dspst_id, + int path_type) +{ + int i, port_type; + struct route_payload payload; + + payload.num_copps = 0; + port_type = (path_type == ADM_PATH_PLAYBACK ? + MSM_AFE_PORT_TYPE_RX : MSM_AFE_PORT_TYPE_TX); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); +} + +void msm_pcm_routing_reg_psthr_stream(int fedai_id, int dspst_id, + int stream_type) +{ + int i, session_type, path_type, port_type; + u32 mode = 0; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + port_type = MSM_AFE_PORT_TYPE_TX; + } + + mutex_lock(&routing_lock); + + fe_dai_map[fedai_id][session_type] = dspst_id; + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) { + mode = afe_get_port_type(msm_bedais[i].port_id); + adm_connect_afe_port(mode, dspst_id, + msm_bedais[i].port_id); + break; + } + } + mutex_unlock(&routing_lock); +} + +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, int stream_type) +{ + int i, session_type, path_type, port_type; + struct route_payload payload; + u32 channels; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID %d\n", __func__, fedai_id); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + port_type = MSM_AFE_PORT_TYPE_TX; + } + + mutex_lock(&routing_lock); + + payload.num_copps = 0; /* only RX needs to use payload */ + fe_dai_map[fedai_id][session_type] = dspst_id; + /* re-enable EQ if active */ + if (eq_data[fedai_id].enable) + msm_send_eq_values(fedai_id); + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) { + + channels = msm_bedais[i].channel; + + if ((stream_type == SNDRV_PCM_STREAM_PLAYBACK) && + (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[i].port_id, + path_type, + msm_bedais[i].sample_rate, + msm_bedais[i].channel, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[i].port_id, + path_type, + msm_bedais[i].sample_rate, + msm_bedais[i].channel, + DEFAULT_COPP_TOPOLOGY); + + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + } + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); + + mutex_unlock(&routing_lock); +} + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type) +{ + int i, port_type, session_type; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + port_type = MSM_AFE_PORT_TYPE_RX; + session_type = SESSION_TYPE_RX; + } else { + port_type = MSM_AFE_PORT_TYPE_TX; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) + adm_close(msm_bedais[i].port_id); + } + + fe_dai_map[fedai_id][session_type] = INVALID_SESSION; + + mutex_unlock(&routing_lock); +} + +/* Check if FE/BE route is set */ +static bool msm_pcm_routing_route_is_set(u16 be_id, u16 fe_id) +{ + bool rc = false; + + if (fe_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return rc; + } + + if (test_bit(fe_id, &msm_bedais[be_id].fe_sessions)) + rc = true; + + return rc; +} + +static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) +{ + int session_type, path_type; + u32 channels; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (val > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (afe_get_port_type(msm_bedais[reg].port_id) == + MSM_AFE_PORT_TYPE_RX) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + } + + mutex_lock(&routing_lock); + + if (set) { + if (!test_bit(val, &msm_bedais[reg].fe_sessions) && + (msm_bedais[reg].port_id == VOICE_PLAYBACK_TX)) + voc_start_playback(set); + + set_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + + channels = msm_bedais[reg].channel; + + if ((session_type == SESSION_TYPE_RX) && (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[reg].port_id, + path_type, + msm_bedais[reg].sample_rate, + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[reg].port_id, + path_type, + msm_bedais[reg].sample_rate, channels, + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } else { + if (test_bit(val, &msm_bedais[reg].fe_sessions) && + (msm_bedais[reg].port_id == VOICE_PLAYBACK_TX)) + voc_start_playback(set); + clear_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + adm_close(msm_bedais[reg].port_id); + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } + if ((msm_bedais[reg].port_id == VOICE_RECORD_RX) + || (msm_bedais[reg].port_id == VOICE_RECORD_TX)) + voc_start_record(msm_bedais[reg].port_id, set); + + mutex_unlock(&routing_lock); +} + +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, &msm_bedais[mc->reg].fe_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_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + + if (ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == false) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else if (!ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == true) { + 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) +{ + u16 session_id = 0; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (val == MSM_FRONTEND_DAI_CS_VOICE) + session_id = voc_get_session_id(VOICE_SESSION_NAME); + else if (val == MSM_FRONTEND_DAI_VOLTE) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOIP_SESSION_NAME); + + pr_debug("%s: FE DAI 0x%x session_id 0x%x\n", + __func__, val, session_id); + + mutex_lock(&routing_lock); + + if (set) + set_bit(val, &msm_bedais[reg].fe_sessions); + else + clear_bit(val, &msm_bedais[reg].fe_sessions); + + mutex_unlock(&routing_lock); + + if (afe_get_port_type(msm_bedais[reg].port_id) == + MSM_AFE_PORT_TYPE_RX) { + voc_set_route_flag(session_id, RX_PATH, set); + if (set) { + voc_set_rxtx_port(session_id, + msm_bedais[reg].port_id, DEV_RX); + + if (voc_get_route_flag(session_id, RX_PATH) && + voc_get_route_flag(session_id, TX_PATH)) + voc_enable_cvp(session_id); + } else { + voc_disable_cvp(session_id); + } + } else { + voc_set_route_flag(session_id, TX_PATH, set); + if (set) { + voc_set_rxtx_port(session_id, + msm_bedais[reg].port_id, DEV_TX); + if (voc_get_route_flag(session_id, RX_PATH) && + voc_get_route_flag(session_id, TX_PATH)) + voc_enable_cvp(session_id); + } else { + voc_disable_cvp(session_id); + } + } +} + +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; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + 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_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + 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_voice_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + 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_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + mutex_lock(&routing_lock); + set_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + mutex_lock(&routing_lock); + clear_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 1; +} + +static int msm_routing_get_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = fm_switch_enable; + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int msm_routing_put_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + if (ucontrol->value.integer.value[0]) + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + else + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + fm_switch_enable = ucontrol->value.integer.value[0]; + return 1; +} + +static int msm_routing_get_fm_pcmrx_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = fm_pcmrx_switch_enable; + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int msm_routing_put_fm_pcmrx_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + if (ucontrol->value.integer.value[0]) + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + else + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + fm_pcmrx_switch_enable = ucontrol->value.integer.value[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, &msm_bedais[mc->reg].port_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, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + set_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } else { + afe_loopback(0, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + clear_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } + + return 1; +} + +static int msm_routing_get_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_fm_vol_control; + return 0; +} + +static int msm_routing_set_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + afe_loopback_gain(INT_FM_TX , ucontrol->value.integer.value[0]); + + msm_route_fm_vol_control = ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_lpa_vol_control; + return 0; +} + +static int msm_routing_set_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!lpa_set_volume(ucontrol->value.integer.value[0])) + msm_route_lpa_vol_control = + ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_multimedia2_vol_control; + return 0; +} + +static int msm_routing_set_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + if (!multi_ch_pcm_set_volume(ucontrol->value.integer.value[0])) + msm_route_multimedia2_vol_control = + ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_compressed_vol_control; + return 0; +} + +static int msm_routing_set_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!compressed_set_volume(ucontrol->value.integer.value[0])) + msm_route_compressed_vol_control = + ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_srs_trumedia_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_routing_set_srs_trumedia_control_(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int techs = 0; + unsigned short offset, value, max, index; + + max = sizeof(msm_srs_trumedia_params) >> 1; + index = (unsigned short)((ucontrol->value.integer.value[0] & + SRS_PARAM_INDEX_MASK) >> 31); + if (SRS_CMD_UPLOAD == + (ucontrol->value.integer.value[0] & SRS_CMD_UPLOAD)) { + techs = ucontrol->value.integer.value[0] & 0xFF; + pr_debug("SRS %s: send params request, flags = %u", + __func__, techs); + if (srs_port_id >= 0 && techs) + srs_send_params(srs_port_id, techs, index); + return 0; + } + offset = (unsigned short)((ucontrol->value.integer.value[0] & + SRS_PARAM_OFFSET_MASK) >> 16); + value = (unsigned short)(ucontrol->value.integer.value[0] & + SRS_PARAM_VALUE_MASK); + if (offset < max) { + msm_srs_trumedia_params[index].raw_params[offset] = value; + pr_debug("SRS %s: index set... (max %d, requested %d," + " val %d, paramblockidx %d)", __func__, max, offset, + value, index); + } else { + pr_err("SRS %s: index out of bounds! (max %d, requested %d)", + __func__, max, offset); + } + if (offset == 4) { + int i; + for (i = 0; i < max; i++) { + if (i == 0) { + pr_debug("SRS %s: global block start", + __func__); + } + if (i == + (sizeof(struct srs_trumedia_params_GLOBAL) >> 1)) { + break; + pr_debug("SRS %s: wowhd block start at" + " offset %d word offset %d", __func__, + i, i>>1); + } + pr_debug("SRS %s: param_index %d index %d val %d", + __func__, index, i, + msm_srs_trumedia_params[index].raw_params[i]); + } + } + return 0; +} + +static int msm_routing_set_srs_trumedia_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + int ret; + + pr_debug("SRS control normal called"); + mutex_lock(&routing_lock); + srs_port_id = SLIMBUS_0_RX; + ret = msm_routing_set_srs_trumedia_control_(kcontrol, ucontrol); + mutex_unlock(&routing_lock); + return ret; +} + +static int msm_routing_set_srs_trumedia_control_I2S( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + int ret; + + pr_debug("SRS control I2S called"); + mutex_lock(&routing_lock); + srs_port_id = PRIMARY_I2S_RX; + ret = msm_routing_set_srs_trumedia_control_(kcontrol, ucontrol); + mutex_unlock(&routing_lock); + return ret; +} + +static int msm_routing_set_srs_trumedia_control_HDMI( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + int ret; + + pr_debug("SRS control HDMI called"); + mutex_lock(&routing_lock); + srs_port_id = HDMI_RX; + ret = msm_routing_set_srs_trumedia_control_(kcontrol, ucontrol); + mutex_unlock(&routing_lock); + return ret; +} + +static void msm_send_eq_values(int eq_idx) +{ + int result; + struct audio_client *ac = + q6asm_get_audio_client(fe_dai_map[eq_idx][SESSION_TYPE_RX]); + + if (ac == NULL) { + pr_err("%s: Could not get audio client for session: %d\n", + __func__, fe_dai_map[eq_idx][SESSION_TYPE_RX]); + goto done; + } + + result = q6asm_equalizer(ac, &eq_data[eq_idx]); + + if (result < 0) + pr_err("%s: Call to ASM equalizer failed, returned = %d\n", + __func__, result); +done: + return; +} + +static int msm_routing_get_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].enable; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, eq_data[eq_idx].enable); + return 0; +} + +static int msm_routing_put_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].enable = value; + + msm_send_eq_values(eq_idx); + return 0; +} + +static int msm_routing_get_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].num_bands; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, eq_data[eq_idx].num_bands); + return eq_data[eq_idx].num_bands; +} + +static int msm_routing_put_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].num_bands = value; + return 0; +} + +static int msm_routing_get_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + eq_data[eq_idx].eq_bands[band_idx].band_idx; + ucontrol->value.integer.value[1] = + eq_data[eq_idx].eq_bands[band_idx].filter_type; + ucontrol->value.integer.value[2] = + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz; + ucontrol->value.integer.value[3] = + eq_data[eq_idx].eq_bands[band_idx].filter_gain; + ucontrol->value.integer.value[4] = + eq_data[eq_idx].eq_bands[band_idx].q_factor; + + pr_debug("%s: band_idx = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].band_idx); + pr_debug("%s: filter_type = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_type); + pr_debug("%s: center_freq_hz = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz); + pr_debug("%s: filter_gain = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_gain); + pr_debug("%s: q_factor = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].q_factor); + return 0; +} + +static int msm_routing_put_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + eq_data[eq_idx].eq_bands[band_idx].band_idx = + ucontrol->value.integer.value[0]; + eq_data[eq_idx].eq_bands[band_idx].filter_type = + ucontrol->value.integer.value[1]; + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz = + ucontrol->value.integer.value[2]; + eq_data[eq_idx].eq_bands[band_idx].filter_gain = + ucontrol->value.integer.value[3]; + eq_data[eq_idx].eq_bands[band_idx].q_factor = + ucontrol->value.integer.value[4]; + return 0; +} + +static const struct snd_kcontrol_new pri_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_PRI_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SEC_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_MI2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + /* incall music delivery mixer */ +static const struct snd_kcontrol_new incall_music_delivery_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SLIMBUS_4_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", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_DL", MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_UL", MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_4_TX", MSM_BACKEND_DAI_SLIMBUS_4_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul2_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 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", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_VOLTE, 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", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_VOLTE, 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", MSM_BACKEND_DAI_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", MSM_BACKEND_DAI_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new aux_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new hdmi_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new stub_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_EXTPROC_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voice", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("MI2S_TX_Voice", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voice", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voice", + MSM_BACKEND_DAI_INT_BT_SCO_TX, MSM_FRONTEND_DAI_CS_VOICE, 1, 0, + msm_routing_get_voice_mixer, msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voice", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voice", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_volte_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_VoLTE", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_VoLTE", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_VoLTE", + MSM_BACKEND_DAI_INT_BT_SCO_TX, MSM_FRONTEND_DAI_VOLTE, 1, 0, + msm_routing_get_voice_mixer, msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_VoLTE", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_VoLTE", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_VOLTE, 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", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("MI2S_TX_Voip", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voip", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voip", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voip", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voip", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_stub_mixer_controls[] = { + SOC_SINGLE_EXT("STUB_TX_HL", MSM_BACKEND_DAI_EXTPROC_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("STUB_1_TX_HL", MSM_BACKEND_DAI_EXTPROC_EC_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new sbus_0_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_SLIMBUS_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", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sbus_1_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sbus_3_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_BT_SCO_RX", MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_BACKEND_DAI_INT_BT_SCO_RX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; +static const struct snd_kcontrol_new bt_sco_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_BACKEND_DAI_INT_FM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + + +static const struct snd_kcontrol_new hdmi_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_HDMI_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_MI2S_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new fm_switch_mixer_controls = + SOC_SINGLE_EXT("Switch", SND_SOC_NOPM, + 0, 1, 0, msm_routing_get_switch_mixer, + msm_routing_put_switch_mixer); + +static const struct snd_kcontrol_new pcm_rx_switch_mixer_controls = + SOC_SINGLE_EXT("Switch", SND_SOC_NOPM, + 0, 1, 0, msm_routing_get_fm_pcmrx_switch_mixer, + msm_routing_put_fm_pcmrx_switch_mixer); + +static const struct snd_kcontrol_new int_fm_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("Internal FM RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_fm_vol_mixer, + msm_routing_set_fm_vol_mixer, fm_rx_vol_gain), +}; + +static const struct snd_kcontrol_new lpa_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("LPA RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_lpa_vol_mixer, + msm_routing_set_lpa_vol_mixer, lpa_rx_vol_gain), +}; + +static const struct snd_kcontrol_new multimedia2_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("HIFI2 RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_multimedia2_vol_mixer, + msm_routing_set_multimedia2_vol_mixer, multimedia2_rx_vol_gain), +}; + +static const struct snd_kcontrol_new compressed_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("COMPRESSED RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_compressed_vol_mixer, + msm_routing_set_compressed_vol_mixer, compressed_rx_vol_gain), +}; + +static const struct snd_kcontrol_new lpa_SRS_trumedia_controls[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SRS TruMedia", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_soc_info_volsw, \ + .get = msm_routing_get_srs_trumedia_control, + .put = msm_routing_set_srs_trumedia_control, + .private_value = ((unsigned long)&(struct soc_mixer_control) + {.reg = SND_SOC_NOPM, + .shift = 0, + .rshift = 0, + .max = 0xFFFFFFFF, + .platform_max = 0xFFFFFFFF, + .invert = 0 + }) + } +}; + +static const struct snd_kcontrol_new lpa_SRS_trumedia_controls_HDMI[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SRS TruMedia HDMI", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_soc_info_volsw, \ + .get = msm_routing_get_srs_trumedia_control, + .put = msm_routing_set_srs_trumedia_control_HDMI, + .private_value = ((unsigned long)&(struct soc_mixer_control) + {.reg = SND_SOC_NOPM, + .shift = 0, + .rshift = 0, + .max = 0xFFFFFFFF, + .platform_max = 0xFFFFFFFF, + .invert = 0 + }) + } +}; + +static const struct snd_kcontrol_new lpa_SRS_trumedia_controls_I2S[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SRS TruMedia I2S", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_soc_info_volsw, \ + .get = msm_routing_get_srs_trumedia_control, + .put = msm_routing_set_srs_trumedia_control_I2S, + .private_value = ((unsigned long)&(struct soc_mixer_control) + {.reg = SND_SOC_NOPM, + .shift = 0, + .rshift = 0, + .max = 0xFFFFFFFF, + .platform_max = 0xFFFFFFFF, + .invert = 0 + }) + } +}; + +static const struct snd_kcontrol_new eq_enable_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), +}; + +static const struct snd_kcontrol_new eq_band_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), +}; + +static const struct snd_kcontrol_new eq_coeff_mixer_controls[] = { + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_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("MM_DL4", "MultiMedia4 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_OUT("MM_UL2", "MultiMedia2 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_IN("VoLTE_DL", "VoLTE Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VoLTE_UL", "VoLTE 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), + SND_SOC_DAPM_AIF_IN("HDMI_DL_HL", "HDMI_HOSTLESS Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_I2S_DL_HL", "SEC_I2S_RX_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUXPCM_DL_HL", "AUXPCM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("AUXPCM_UL_HL", "AUXPCM_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MI2S_UL_HL", "MI2S_TX_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("SEC_I2S_RX", "Secondary 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_OUT("MI2S_RX", "MI2S 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("MI2S_TX", "MI2S 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), + SND_SOC_DAPM_AIF_OUT("PCM_RX", "AFE Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("PCM_TX", "AFE Capture", + 0, 0, 0 , 0), + /* incall */ + SND_SOC_DAPM_AIF_OUT("VOICE_PLAYBACK_TX", "Voice Farend Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_RX", "Slimbus4 Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_TX", "Voice Uplink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_RX", "Voice Downlink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_4_TX", "Slimbus4 Capture", + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_OUT("AUX_PCM_RX", "AUX PCM Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUX_PCM_TX", "AUX PCM Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOICE_STUB_DL", "VOICE_STUB Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOICE_STUB_UL", "VOICE_STUB Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("STUB_RX", "Stub Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("STUB_TX", "Stub Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_RX", "Slimbus1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_1_TX", "Slimbus1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("STUB_1_TX", "Stub1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_RX", "Slimbus3 Playback", 0, 0, 0, 0), + + /* Switch Definitions */ + SND_SOC_DAPM_SWITCH("SLIMBUS_DL_HL", SND_SOC_NOPM, 0, 0, + &fm_switch_mixer_controls), + SND_SOC_DAPM_SWITCH("PCM_RX_DL_HL", SND_SOC_NOPM, 0, 0, + &pcm_rx_switch_mixer_controls), + /* 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("SEC_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_i2s_rx_mixer_controls, ARRAY_SIZE(sec_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("MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + mi2s_rx_mixer_controls, ARRAY_SIZE(mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, + mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + auxpcm_rx_mixer_controls, ARRAY_SIZE(auxpcm_rx_mixer_controls)), + /* incall */ + SND_SOC_DAPM_MIXER("Incall_Music Audio Mixer", SND_SOC_NOPM, 0, 0, + incall_music_delivery_mixer_controls, + ARRAY_SIZE(incall_music_delivery_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_4_rx_mixer_controls, + ARRAY_SIZE(slimbus_4_rx_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("SEC_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + sec_i2s_rx_voice_mixer_controls, + ARRAY_SIZE(sec_i2s_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("AFE_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + afe_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(afe_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + aux_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(aux_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + hdmi_rx_voice_mixer_controls, + ARRAY_SIZE(hdmi_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("MI2S_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + mi2s_rx_voice_mixer_controls, + ARRAY_SIZE(mi2s_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("VoLTE_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_volte_mixer_controls, + ARRAY_SIZE(tx_volte_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("AFE_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + afe_pcm_rx_mixer_controls, ARRAY_SIZE(afe_pcm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice Stub Tx Mixer", SND_SOC_NOPM, 0, 0, + tx_voice_stub_mixer_controls, ARRAY_SIZE(tx_voice_stub_mixer_controls)), + SND_SOC_DAPM_MIXER("STUB_RX Mixer", SND_SOC_NOPM, 0, 0, + stub_rx_mixer_controls, ARRAY_SIZE(stub_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX_Voice Mixer", SND_SOC_NOPM, 0, 0, + slimbus_3_rx_mixer_controls, ARRAY_SIZE(slimbus_3_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)), + SND_SOC_DAPM_MIXER("AUXPCM_RX Port Mixer", + SND_SOC_NOPM, 0, 0, auxpcm_rx_port_mixer_controls, + ARRAY_SIZE(auxpcm_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Port Mixer", SND_SOC_NOPM, 0, 0, + sbus_1_rx_port_mixer_controls, + ARRAY_SIZE(sbus_1_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Port Mixer", SND_SOC_NOPM, 0, 0, + bt_sco_rx_port_mixer_controls, + ARRAY_SIZE(bt_sco_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("AFE_PCM_RX Port Mixer", + SND_SOC_NOPM, 0, 0, afe_pcm_rx_port_mixer_controls, + ARRAY_SIZE(afe_pcm_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI_RX Port Mixer", + SND_SOC_NOPM, 0, 0, hdmi_rx_port_mixer_controls, + ARRAY_SIZE(hdmi_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_I2S_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sec_i2s_rx_port_mixer_controls, + ARRAY_SIZE(sec_i2s_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sbus_3_rx_port_mixer_controls, + ARRAY_SIZE(sbus_3_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("MI2S_RX Port Mixer", SND_SOC_NOPM, 0, 0, + mi2s_rx_port_mixer_controls, ARRAY_SIZE(mi2s_rx_port_mixer_controls)), + + /* Virtual Pins to force backends ON atm */ + SND_SOC_DAPM_OUTPUT("BE_OUT"), + SND_SOC_DAPM_INPUT("BE_IN"), + +}; + +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_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PRI_I2S_RX", NULL, "PRI_RX Audio Mixer"}, + + {"SEC_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SEC_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SEC_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SEC_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SEC_I2S_RX", NULL, "SEC_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 Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"HDMI Mixer", "MultiMedia1", "MM_DL1"}, + {"HDMI Mixer", "MultiMedia2", "MM_DL2"}, + {"HDMI Mixer", "MultiMedia3", "MM_DL3"}, + {"HDMI Mixer", "MultiMedia4", "MM_DL4"}, + {"HDMI", NULL, "HDMI Mixer"}, + + /* incall */ + {"Incall_Music Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"Incall_Music Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"VOICE_PLAYBACK_TX", NULL, "Incall_Music Audio Mixer"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_4_RX", NULL, "SLIMBUS_4_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "VOC_REC_UL", "INCALL_RECORD_TX"}, + {"MultiMedia1 Mixer", "VOC_REC_DL", "INCALL_RECORD_RX"}, + {"MultiMedia1 Mixer", "SLIM_4_TX", "SLIMBUS_4_TX"}, + {"MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"MI2S_RX", NULL, "MI2S_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"}, + {"MultiMedia1 Mixer", "MI2S_TX", "MI2S_TX"}, + {"MultiMedia2 Mixer", "MI2S_TX", "MI2S_TX"}, + {"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"MultiMedia1 Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"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"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"INT_FM_RX", NULL, "INTERNAL_FM_RX Audio Mixer"}, + + {"AFE_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PCM_RX", NULL, "AFE_PCM_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"MultiMedia1 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + + {"MultiMedia1 Mixer", "AFE_PCM_TX", "PCM_TX"}, + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + {"MultiMedia2 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"MM_UL2", NULL, "MultiMedia2 Mixer"}, + + {"AUX_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX Audio Mixer"}, + + {"PRI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"PRI_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"PRI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PRI_I2S_RX", NULL, "PRI_RX_Voice Mixer"}, + + {"SEC_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SEC_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"SEC_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SEC_I2S_RX", NULL, "SEC_RX_Voice Mixer"}, + + {"SLIM_0_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SLIM_0_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"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", "VoLTE", "VoLTE_DL"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX_Voice Mixer"}, + + {"AFE_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AFE_PCM_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"AFE_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PCM_RX", NULL, "AFE_PCM_RX_Voice Mixer"}, + + {"AUX_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AUX_PCM_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"AUX_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX_Voice Mixer"}, + + {"HDMI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"HDMI_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"HDMI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"HDMI", NULL, "HDMI_RX_Voice Mixer"}, + {"HDMI", NULL, "HDMI_DL_HL"}, + + {"Voice_Tx Mixer", "PRI_TX_Voice", "PRI_I2S_TX"}, + {"Voice_Tx Mixer", "MI2S_TX_Voice", "MI2S_TX"}, + {"Voice_Tx Mixer", "SLIM_0_TX_Voice", "SLIMBUS_0_TX"}, + {"Voice_Tx Mixer", "INTERNAL_BT_SCO_TX_Voice", "INT_BT_SCO_TX"}, + {"Voice_Tx Mixer", "AFE_PCM_TX_Voice", "PCM_TX"}, + {"Voice_Tx Mixer", "AUX_PCM_TX_Voice", "AUX_PCM_TX"}, + {"CS-VOICE_UL1", NULL, "Voice_Tx Mixer"}, + {"VoLTE_Tx Mixer", "PRI_TX_VoLTE", "PRI_I2S_TX"}, + {"VoLTE_Tx Mixer", "SLIM_0_TX_VoLTE", "SLIMBUS_0_TX"}, + {"VoLTE_Tx Mixer", "INTERNAL_BT_SCO_TX_VoLTE", "INT_BT_SCO_TX"}, + {"VoLTE_Tx Mixer", "AFE_PCM_TX_VoLTE", "PCM_TX"}, + {"VoLTE_Tx Mixer", "AUX_PCM_TX_VoLTE", "AUX_PCM_TX"}, + {"VoLTE_UL", NULL, "VoLTE_Tx Mixer"}, + {"Voip_Tx Mixer", "PRI_TX_Voip", "PRI_I2S_TX"}, + {"Voip_Tx Mixer", "MI2S_TX_Voip", "MI2S_TX"}, + {"Voip_Tx Mixer", "SLIM_0_TX_Voip", "SLIMBUS_0_TX"}, + {"Voip_Tx Mixer", "INTERNAL_BT_SCO_TX_Voip", "INT_BT_SCO_TX"}, + {"Voip_Tx Mixer", "AFE_PCM_TX_Voip", "PCM_TX"}, + {"Voip_Tx Mixer", "AUX_PCM_TX_Voip", "AUX_PCM_TX"}, + + {"VOIP_UL", NULL, "Voip_Tx Mixer"}, + {"SLIMBUS_DL_HL", "Switch", "SLIM0_DL_HL"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_DL_HL"}, + {"SLIM0_UL_HL", NULL, "SLIMBUS_0_TX"}, + {"INT_FM_RX", NULL, "INTFM_DL_HL"}, + {"INTFM_UL_HL", NULL, "INT_FM_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_DL_HL"}, + {"AUXPCM_UL_HL", NULL, "AUX_PCM_TX"}, + {"PCM_RX_DL_HL", "Switch", "SLIM0_DL_HL"}, + {"PCM_RX", NULL, "PCM_RX_DL_HL"}, + {"MI2S_UL_HL", NULL, "MI2S_TX"}, + {"SEC_I2S_RX", NULL, "SEC_I2S_DL_HL"}, + {"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 Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Port Mixer"}, + {"AFE_PCM_RX Port Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"PCM_RX", NULL, "AFE_PCM_RX Port Mixer"}, + + {"AUXPCM_RX Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"AUXPCM_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_RX Port Mixer"}, + + {"Voice Stub Tx Mixer", "STUB_TX_HL", "STUB_TX"}, + {"Voice Stub Tx Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"Voice Stub Tx Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"Voice Stub Tx Mixer", "STUB_1_TX_HL", "STUB_1_TX"}, + {"Voice Stub Tx Mixer", "MI2S_TX", "MI2S_TX"}, + {"VOICE_STUB_UL", NULL, "Voice Stub Tx Mixer"}, + + {"STUB_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"STUB_RX", NULL, "STUB_RX Mixer"}, + {"SLIMBUS_1_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Mixer"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"MI2S_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"MI2S_RX", NULL, "MI2S_RX_Voice Mixer"}, + + {"SLIMBUS_3_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"SLIMBUS_3_RX", NULL, "SLIMBUS_3_RX_Voice Mixer"}, + + {"SLIMBUS_1_RX Port Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Port Mixer"}, + {"INTERNAL_BT_SCO_RX Port Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Port Mixer"}, + {"SLIMBUS_3_RX Port Mixer", "INTERNAL_BT_SCO_RX", "INT_BT_SCO_RX"}, + {"SLIMBUS_3_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"SLIMBUS_3_RX", NULL, "SLIMBUS_3_RX Port Mixer"}, + + + {"HDMI_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"HDMI", NULL, "HDMI_RX Port Mixer"}, + + {"SEC_I2S_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"SEC_I2S_RX", NULL, "SEC_I2S_RX Port Mixer"}, + + {"MI2S_RX Port Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"MI2S_RX", NULL, "MI2S_RX Port Mixer"}, + /* Backend Enablement */ + + {"BE_OUT", NULL, "PRI_I2S_RX"}, + {"BE_OUT", NULL, "SEC_I2S_RX"}, + {"BE_OUT", NULL, "SLIMBUS_0_RX"}, + {"BE_OUT", NULL, "HDMI"}, + {"BE_OUT", NULL, "MI2S_RX"}, + {"PRI_I2S_TX", NULL, "BE_IN"}, + {"MI2S_TX", NULL, "BE_IN"}, + {"SLIMBUS_0_TX", NULL, "BE_IN" }, + {"BE_OUT", NULL, "INT_BT_SCO_RX"}, + {"INT_BT_SCO_TX", NULL, "BE_IN"}, + {"BE_OUT", NULL, "INT_FM_RX"}, + {"INT_FM_TX", NULL, "BE_IN"}, + {"BE_OUT", NULL, "PCM_RX"}, + {"PCM_TX", NULL, "BE_IN"}, + {"BE_OUT", NULL, "SLIMBUS_3_RX"}, +}; + +static int msm_pcm_routing_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + mutex_lock(&routing_lock); + msm_bedais[be_id].sample_rate = params_rate(params); + msm_bedais[be_id].channel = params_channels(params); + mutex_unlock(&routing_lock); + return 0; +} + +static int msm_pcm_routing_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, session_type; + struct msm_pcm_routing_bdai_data *bedai; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + bedai = &msm_bedais[be_id]; + session_type = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + 0 : 1); + + mutex_lock(&routing_lock); + + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) { + adm_close(bedai->port_id); + srs_port_id = -1; + } + } + + bedai->active = 0; + bedai->sample_rate = 0; + bedai->channel = 0; + mutex_unlock(&routing_lock); + + return 0; +} + +static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, path_type, session_type; + struct msm_pcm_routing_bdai_data *bedai; + u32 channels; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + bedai = &msm_bedais[be_id]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + path_type = ADM_PATH_PLAYBACK; + session_type = SESSION_TYPE_RX; + } else { + path_type = ADM_PATH_LIVE_REC; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + if (bedai->active == 1) + goto done; /* Ignore prepare if back-end already active */ + + /* AFE port is not active at this point. However, still + * go ahead setting active flag under the notion that + * QDSP6 is able to handle ADM starting before AFE port + * is started. + */ + bedai->active = 1; + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) { + + channels = bedai->channel; + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + && (channels > 2)) + adm_multi_ch_copp_open(bedai->port_id, + path_type, + bedai->sample_rate, + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(bedai->port_id, + path_type, + bedai->sample_rate, + channels, + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(i, + fe_dai_map[i][session_type], path_type); + srs_port_id = bedai->port_id; + srs_send_params(srs_port_id, 1, 0); + } + } + +done: + mutex_unlock(&routing_lock); + + return 0; +} + +static struct snd_pcm_ops msm_routing_pcm_ops = { + .hw_params = msm_pcm_routing_hw_params, + .close = msm_pcm_routing_close, + .prepare = msm_pcm_routing_prepare, +}; + +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); + + snd_soc_add_platform_controls(platform, + int_fm_vol_mixer_controls, + ARRAY_SIZE(int_fm_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + lpa_vol_mixer_controls, + ARRAY_SIZE(lpa_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_enable_mixer_controls, + ARRAY_SIZE(eq_enable_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_band_mixer_controls, + ARRAY_SIZE(eq_band_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_coeff_mixer_controls, + ARRAY_SIZE(eq_coeff_mixer_controls)); + + snd_soc_add_platform_controls(platform, + multimedia2_vol_mixer_controls, + ARRAY_SIZE(multimedia2_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + compressed_vol_mixer_controls, + ARRAY_SIZE(compressed_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + lpa_SRS_trumedia_controls, + ARRAY_SIZE(lpa_SRS_trumedia_controls)); + + snd_soc_add_platform_controls(platform, + lpa_SRS_trumedia_controls_HDMI, + ARRAY_SIZE(lpa_SRS_trumedia_controls_HDMI)); + + snd_soc_add_platform_controls(platform, + lpa_SRS_trumedia_controls_I2S, + ARRAY_SIZE(lpa_SRS_trumedia_controls_I2S)); + + 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), +}; + +int msm_routing_check_backend_enabled(int fedai_id) +{ + int i; + if (fedai_id >= MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return 0; + } + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (test_bit(fedai_id, &msm_bedais[i].fe_sessions)) + return msm_bedais[i].active; + } + return 0; +} + +static int __init msm_soc_routing_platform_init(void) +{ + mutex_init(&routing_lock); + 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 0000000000000000000000000000000000000000..ff073ce8037b7709e53d703590150c312abb033a --- /dev/null +++ b/sound/soc/msm/msm-pcm-routing.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 "PRIMARY_I2S_RX" +#define LPASS_BE_PRI_I2S_TX "PRIMARY_I2S_TX" +#define LPASS_BE_SLIMBUS_0_RX "SLIMBUS_0_RX" +#define LPASS_BE_SLIMBUS_0_TX "SLIMBUS_0_TX" +#define LPASS_BE_HDMI "HDMI" +#define LPASS_BE_INT_BT_SCO_RX "INT_BT_SCO_RX" +#define LPASS_BE_INT_BT_SCO_TX "INT_BT_SCO_TX" +#define LPASS_BE_INT_FM_RX "INT_FM_RX" +#define LPASS_BE_INT_FM_TX "INT_FM_TX" +#define LPASS_BE_AFE_PCM_RX "RT_PROXY_DAI_001_RX" +#define LPASS_BE_AFE_PCM_TX "RT_PROXY_DAI_002_TX" +#define LPASS_BE_AUXPCM_RX "AUX_PCM_RX" +#define LPASS_BE_AUXPCM_TX "AUX_PCM_TX" +#define LPASS_BE_VOICE_PLAYBACK_TX "VOICE_PLAYBACK_TX" +#define LPASS_BE_INCALL_RECORD_RX "INCALL_RECORD_TX" +#define LPASS_BE_INCALL_RECORD_TX "INCALL_RECORD_RX" +#define LPASS_BE_SEC_I2S_RX "SECONDARY_I2S_RX" + +#define LPASS_BE_MI2S_RX "MI2S_RX" +#define LPASS_BE_MI2S_TX "MI2S_TX" +#define LPASS_BE_STUB_RX "STUB_RX" +#define LPASS_BE_STUB_TX "STUB_TX" +#define LPASS_BE_SLIMBUS_1_RX "SLIMBUS_1_RX" +#define LPASS_BE_SLIMBUS_1_TX "SLIMBUS_1_TX" +#define LPASS_BE_STUB_1_TX "STUB_1_TX" +#define LPASS_BE_SLIMBUS_3_RX "SLIMBUS_3_RX" +#define LPASS_BE_SLIMBUS_4_RX "SLIMBUS_4_RX" +#define LPASS_BE_SLIMBUS_4_TX "SLIMBUS_4_TX" + +/* For multimedia front-ends, asm session is allocated dynamically. + * Hence, asm session/multimedia front-end mapping has to be maintained. + * Due to this reason, additional multimedia front-end must be placed before + * non-multimedia front-ends. + */ + +enum { + MSM_FRONTEND_DAI_MULTIMEDIA1 = 0, + MSM_FRONTEND_DAI_MULTIMEDIA2, + MSM_FRONTEND_DAI_MULTIMEDIA3, + MSM_FRONTEND_DAI_MULTIMEDIA4, + MSM_FRONTEND_DAI_CS_VOICE, + MSM_FRONTEND_DAI_VOIP, + MSM_FRONTEND_DAI_AFE_RX, + MSM_FRONTEND_DAI_AFE_TX, + MSM_FRONTEND_DAI_VOICE_STUB, + MSM_FRONTEND_DAI_VOLTE, + MSM_FRONTEND_DAI_MAX, +}; + +#define MSM_FRONTEND_DAI_MM_SIZE (MSM_FRONTEND_DAI_MULTIMEDIA4 + 1) +#define MSM_FRONTEND_DAI_MM_MAX_ID MSM_FRONTEND_DAI_MULTIMEDIA4 + +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_AFE_PCM_RX, + MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, + MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_BACKEND_DAI_MI2S_RX, + MSM_BACKEND_DAI_MI2S_TX, + MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_BACKEND_DAI_SLIMBUS_4_RX, + MSM_BACKEND_DAI_SLIMBUS_4_TX, + MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_BACKEND_DAI_EXTPROC_RX, + MSM_BACKEND_DAI_EXTPROC_TX, + MSM_BACKEND_DAI_EXTPROC_EC_TX, + MSM_BACKEND_DAI_MAX, +}; + +/* 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_reg_psthr_stream(int fedai_id, int dspst_id, + int stream_type); + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type); + +int lpa_set_volume(unsigned volume); + +int msm_routing_check_backend_enabled(int fedai_id); + +int multi_ch_pcm_set_volume(unsigned volume); + +int compressed_set_volume(unsigned volume); + +#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 0000000000000000000000000000000000000000..7bdb4f02f0dae5e4f5df3b2d68fbbad724f09393 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voice.c @@ -0,0 +1,558 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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[VOICE_SESSION_INDEX_MAX]; + +static struct snd_pcm_hardware msm_pcm_hardware = { + + .info = (SNDRV_PCM_INFO_INTERLEAVED| + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .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 is_volte(struct msm_voice *pvolte) +{ + if (pvolte == &voice_info[VOLTE_SESSION_INDEX]) + return true; + else + return false; +} + +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; + + if (!strncmp("VoLTE", substream->pcm->id, 5)) { + voice = &voice_info[VOLTE_SESSION_INDEX]; + pr_debug("%s: Open VoLTE Substream Id=%s\n", + __func__, substream->pcm->id); + } else { + voice = &voice_info[VOICE_SESSION_INDEX]; + pr_debug("%s: Open VOICE Substream Id=%s\n", + __func__, substream->pcm->id); + } + 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, Stream ID = %s\n", + __func__ , voice->instance, substream->pcm->id); + 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; + uint16_t session_id = 0; + int ret = 0; + + 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"); + if (is_volte(prtd)) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOICE_SESSION_NAME); + voc_end_voice_call(session_id); + } + 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; + uint16_t session_id = 0; + + 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) { + if (is_volte(prtd)) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOICE_SESSION_NAME); + voc_start_voice_call(session_id); + } + 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_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + uint16_t session_id = 0; + + pr_debug("%s: cmd = %d\n", __func__, cmd); + if (is_volte(prtd)) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOICE_SESSION_NAME); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("Start & Stop Voice call not handled in Trigger.\n"); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: resume call session_id = %d\n", __func__, + session_id); + 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_resume_voice_call(session_id); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: pause call session_id=%d\n", + __func__, session_id); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (prtd->playback_start) + prtd->playback_start = 0; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (prtd->capture_start) + prtd->capture_start = 0; + } + voc_standby_voice_call(session_id); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +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 volume = ucontrol->value.integer.value[0]; + pr_debug("%s: volume: %d\n", __func__, volume); + voc_set_rx_vol_index(voc_get_session_id(VOICE_SESSION_NAME), + RX_PATH, volume); + return 0; +} + +static int msm_volte_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volte_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int volume = ucontrol->value.integer.value[0]; + pr_debug("%s: volume: %d\n", __func__, volume); + voc_set_rx_vol_index(voc_get_session_id(VOLTE_SESSION_NAME), + RX_PATH, volume); + 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 mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_tx_mute(voc_get_session_id(VOICE_SESSION_NAME), TX_PATH, mute); + + return 0; +} + +static int msm_volte_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volte_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_tx_mute(voc_get_session_id(VOLTE_SESSION_NAME), TX_PATH, mute); + + return 0; +} + +static int msm_voice_rx_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_rx_device_mute(voc_get_session_id(VOICE_SESSION_NAME)); + return 0; +} + +static int msm_voice_rx_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_rx_device_mute(voc_get_session_id(VOICE_SESSION_NAME), mute); + + return 0; +} + +static int msm_volte_rx_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_rx_device_mute(voc_get_session_id(VOLTE_SESSION_NAME)); + return 0; +} + +static int msm_volte_rx_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_rx_device_mute(voc_get_session_id(VOLTE_SESSION_NAME), mute); + + return 0; +} + +static const char const *tty_mode[] = {"OFF", "HCO", "VCO", "FULL"}; +static const struct soc_enum msm_tty_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(4, tty_mode), +}; + +static int msm_voice_tty_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_tty_mode(voc_get_session_id(VOICE_SESSION_NAME)); + return 0; +} + +static int msm_voice_tty_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int tty_mode = ucontrol->value.integer.value[0]; + + pr_debug("%s: tty_mode=%d\n", __func__, tty_mode); + + voc_set_tty_mode(voc_get_session_id(VOICE_SESSION_NAME), tty_mode); + + return 0; +} +static int msm_voice_widevoice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int wv_enable = ucontrol->value.integer.value[0]; + + pr_debug("%s: wv enable=%d\n", __func__, wv_enable); + + voc_set_widevoice_enable(voc_get_session_id(VOICE_SESSION_NAME), + wv_enable); + + return 0; +} + +static int msm_voice_widevoice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_widevoice_enable(voc_get_session_id(VOICE_SESSION_NAME)); + return 0; +} + + +static int msm_voice_slowtalk_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int st_enable = ucontrol->value.integer.value[0]; + + pr_debug("%s: st enable=%d\n", __func__, st_enable); + + voc_set_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_ST, st_enable); + + return 0; +} + +static int msm_voice_slowtalk_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_ST); + return 0; +} + +static int msm_voice_fens_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int fens_enable = ucontrol->value.integer.value[0]; + + pr_debug("%s: fens enable=%d\n", __func__, fens_enable); + + voc_set_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_FENS, fens_enable); + + return 0; +} + +static int msm_voice_fens_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_FENS); + return 0; +} + +static struct snd_kcontrol_new msm_voice_controls[] = { + SOC_SINGLE_EXT("Voice Rx Device Mute", SND_SOC_NOPM, 0, 1, 0, + msm_voice_rx_device_mute_get, + msm_voice_rx_device_mute_put), + SOC_SINGLE_EXT("Voice Tx Mute", SND_SOC_NOPM, 0, 1, 0, + msm_voice_mute_get, msm_voice_mute_put), + SOC_SINGLE_EXT("Voice Rx Volume", SND_SOC_NOPM, 0, 5, 0, + msm_voice_volume_get, msm_voice_volume_put), + SOC_ENUM_EXT("TTY Mode", msm_tty_mode_enum[0], msm_voice_tty_mode_get, + msm_voice_tty_mode_put), + SOC_SINGLE_EXT("Widevoice Enable", SND_SOC_NOPM, 0, 1, 0, + msm_voice_widevoice_get, msm_voice_widevoice_put), + SOC_SINGLE_EXT("Slowtalk Enable", SND_SOC_NOPM, 0, 1, 0, + msm_voice_slowtalk_get, msm_voice_slowtalk_put), + SOC_SINGLE_EXT("FENS Enable", SND_SOC_NOPM, 0, 1, 0, + msm_voice_fens_get, msm_voice_fens_put), + SOC_SINGLE_EXT("VoLTE Rx Device Mute", SND_SOC_NOPM, 0, 1, 0, + msm_volte_rx_device_mute_get, + msm_volte_rx_device_mute_put), + SOC_SINGLE_EXT("VoLTE Tx Mute", SND_SOC_NOPM, 0, 1, 0, + msm_volte_mute_get, msm_volte_mute_put), + SOC_SINGLE_EXT("VoLTE Rx Volume", SND_SOC_NOPM, 0, 5, 0, + msm_volte_volume_get, msm_volte_volume_put), +}; + +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, + .trigger = msm_pcm_trigger, +}; + + +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[VOICE_SESSION_INDEX].lock); + mutex_init(&voice_info[VOLTE_SESSION_INDEX].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 0000000000000000000000000000000000000000..aa00577d25c52976edb4555f949e3d1cee4a3cd5 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voice.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 _MSM_PCM_VOICE_H +#define _MSM_PCM_VOICE_H +#include + +enum { + VOICE_SESSION_INDEX, + VOLTE_SESSION_INDEX, + VOICE_SESSION_INDEX_MAX, +}; + +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; +}; + +#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 0000000000000000000000000000000000000000..570d71cd2fac3b6c215c332495468b7ea29dd9f9 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voip.c @@ -0,0 +1,1169 @@ +/* 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 640 +#define VOIP_MIN_VOC_PKT_SIZE 320 + +/* Length of the DSP frame info header added to the voc packet. */ +#define DSP_FRAME_HDR_LEN 1 + +#define MODE_IS127 0x2 +#define MODE_4GV_NB 0x3 +#define MODE_4GV_WB 0x4 +#define MODE_AMR 0x5 +#define MODE_AMR_WB 0xD +#define MODE_PCM 0xC + +enum format { + FORMAT_S16_LE = 2, + FORMAT_SPECIAL = 31, +}; + + +enum amr_rate_type { + AMR_RATE_4750, /* AMR 4.75 kbps */ + AMR_RATE_5150, /* AMR 5.15 kbps */ + AMR_RATE_5900, /* AMR 5.90 kbps */ + AMR_RATE_6700, /* AMR 6.70 kbps */ + AMR_RATE_7400, /* AMR 7.40 kbps */ + AMR_RATE_7950, /* AMR 7.95 kbps */ + AMR_RATE_10200, /* AMR 10.20 kbps */ + AMR_RATE_12200, /* AMR 12.20 kbps */ + AMR_RATE_6600, /* AMR-WB 6.60 kbps */ + AMR_RATE_8850, /* AMR-WB 8.85 kbps */ + AMR_RATE_12650, /* AMR-WB 12.65 kbps */ + AMR_RATE_14250, /* AMR-WB 14.25 kbps */ + AMR_RATE_15850, /* AMR-WB 15.85 kbps */ + AMR_RATE_18250, /* AMR-WB 18.25 kbps */ + AMR_RATE_19850, /* AMR-WB 19.85 kbps */ + AMR_RATE_23050, /* AMR-WB 23.05 kbps */ + AMR_RATE_23850, /* AMR-WB 23.85 kbps */ + AMR_RATE_UNDEF +}; + +enum voip_state { + VOIP_STOPPED, + VOIP_STARTED, +}; + +struct voip_frame { + union { + uint32_t frame_type; + uint32_t packet_rate; + } header; + 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; + wait_queue_head_t in_wait; + + struct mutex lock; + struct mutex in_lock; + struct mutex out_lock; + + spinlock_t dsp_lock; + + uint32_t mode; + uint32_t rate_type; + uint32_t rate; + uint32_t dtx_mode; + + 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 int voip_get_media_type(uint32_t mode, + unsigned int samp_rate); +static int voip_get_rate_type(uint32_t mode, + uint32_t rate, + uint32_t *rate_type); +static int msm_voip_mode_rate_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int msm_voip_mode_rate_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +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 | + SNDRV_PCM_FMTBIT_SPECIAL, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = sizeof(struct voip_buf_node) * VOIP_MAX_Q_LEN, + .period_bytes_min = VOIP_MIN_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, +}; + + +static int msm_voip_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_tx_mute(voc_get_session_id(VOIP_SESSION_NAME), TX_PATH, mute); + + return 0; +} + +static int msm_voip_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voip_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int volume = ucontrol->value.integer.value[0]; + + pr_debug("%s: volume: %d\n", __func__, volume); + + voc_set_rx_vol_index(voc_get_session_id(VOIP_SESSION_NAME), + RX_PATH, + volume); + return 0; +} +static int msm_voip_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voip_dtx_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + voip_info.dtx_mode = ucontrol->value.integer.value[0]; + + pr_debug("%s: dtx: %d\n", __func__, voip_info.dtx_mode); + + mutex_unlock(&voip_info.lock); + + return 0; +} +static int msm_voip_dtx_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + ucontrol->value.integer.value[0] = voip_info.dtx_mode; + + mutex_unlock(&voip_info.lock); + + return 0; +} + +static struct snd_kcontrol_new msm_voip_controls[] = { + SOC_SINGLE_EXT("Voip Tx Mute", SND_SOC_NOPM, 0, 1, 0, + msm_voip_mute_get, msm_voip_mute_put), + SOC_SINGLE_EXT("Voip Rx Volume", SND_SOC_NOPM, 0, 5, 0, + msm_voip_volume_get, msm_voip_volume_put), + SOC_SINGLE_MULTI_EXT("Voip Mode Rate Config", SND_SOC_NOPM, 0, 23850, + 0, 2, msm_voip_mode_rate_config_get, + msm_voip_mode_rate_config_put), + SOC_SINGLE_EXT("Voip Dtx Mode", SND_SOC_NOPM, 0, 1, 0, + msm_voip_dtx_mode_get, msm_voip_dtx_mode_put), +}; + +static int msm_pcm_voip_probe(struct snd_soc_platform *platform) +{ + snd_soc_add_platform_controls(platform, msm_voip_controls, + ARRAY_SIZE(msm_voip_controls)); + + return 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); + switch (prtd->mode) { + case MODE_AMR_WB: + case MODE_AMR: { + /* Remove the DSP frame info header. Header format: + * Bits 0-3: Frame rate + * Bits 4-7: Frame type + */ + buf_node->frame.header.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, &prtd->out_queue); + break; + } + case MODE_IS127: + case MODE_4GV_NB: + case MODE_4GV_WB: { + /* Remove the DSP frame info header. + * Header format: + * Bits 0-3: frame rate + */ + buf_node->frame.header.packet_rate = (*voc_pkt) & 0x0F; + 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, &prtd->out_queue); + break; + } + default: { + 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); + } + } + pr_debug("ul_pkt: pkt_len =%d, frame.len=%d\n", pkt_len, + buf_node->frame.len); + 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); + switch (prtd->mode) { + case MODE_AMR: + case 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.header.frame_type & + 0x0F) << 4) | (prtd->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, &prtd->free_in_queue); + break; + } + case MODE_IS127: + case MODE_4GV_NB: + case MODE_4GV_WB: { + /* Add the DSP frame info header. Header format: + * Bits 0-3 : Frame rate + */ + *voc_pkt = buf_node->frame.header.packet_rate & 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, &prtd->free_in_queue); + 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, &prtd->free_in_queue); + } + } + pr_debug("dl_pkt: pkt_len=%d, frame_len=%d\n", *pkt_len, + buf_node->frame.len); + 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"); + } + wake_up(&prtd->in_wait); +} + +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, frames=%d\n", __func__, count, (int)frames); + + ret = wait_event_interruptible_timeout(prtd->in_wait, + (!list_empty(&prtd->free_in_queue) || + prtd->state == VOIP_STOPPED), + 1 * HZ); + if (ret > 0) { + mutex_lock(&prtd->in_lock); + if (count <= VOIP_MAX_VOC_PKT_SIZE) { + buf_node = + list_first_entry(&prtd->free_in_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + if (prtd->mode == MODE_PCM) { + ret = copy_from_user(&buf_node->frame.voc_pkt, + buf, count); + buf_node->frame.len = count; + } else + ret = copy_from_user(&buf_node->frame, + buf, count); + list_add_tail(&buf_node->list, &prtd->in_queue); + } else { + pr_err("%s: Write cnt %d is > VOIP_MAX_VOC_PKT_SIZE\n", + __func__, count); + ret = -ENOMEM; + } + + mutex_unlock(&prtd->in_lock); + } else if (ret == 0) { + pr_err("%s: No free DL buffs\n", __func__); + ret = -ETIMEDOUT; + } else { + pr_err("%s: playback copy was interrupted\n", __func__); + } + + 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); + if (prtd->mode == MODE_PCM) + ret = copy_to_user(buf, + &buf_node->frame.voc_pkt, + count); + else + ret = copy_to_user(buf, + &buf_node->frame, + count); + 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_get_session_id(VOIP_SESSION_NAME)); + 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; + uint32_t media_type = 0; + uint32_t rate_type = 0; + + 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 ((runtime->format != FORMAT_SPECIAL) && + ((prtd->mode == MODE_AMR) || (prtd->mode == MODE_AMR_WB) || + (prtd->mode == MODE_IS127) || (prtd->mode == MODE_4GV_NB) || + (prtd->mode == MODE_4GV_WB))) { + pr_err("mode:%d and format:%u are not mached\n", + prtd->mode, (uint32_t)runtime->format); + ret = -EINVAL; + goto done; + } + + if ((runtime->format != FORMAT_S16_LE) && + (prtd->mode == MODE_PCM)) { + pr_err("mode:%d and format:%u are not mached\n", + prtd->mode, (uint32_t)runtime->format); + ret = -EINVAL; + goto done; + } + + if (prtd->playback_instance && prtd->capture_instance + && (prtd->state != VOIP_STARTED)) { + + ret = voip_get_rate_type(prtd->mode, + prtd->rate, + &rate_type); + if (ret < 0) { + pr_err("fail at getting rate_type\n"); + ret = -EINVAL; + goto done; + } + prtd->rate_type = rate_type; + media_type = voip_get_media_type(prtd->mode, + prtd->play_samp_rate); + if (media_type < 0) { + pr_err("fail at getting media_type\n"); + ret = -EINVAL; + goto done; + } + pr_debug(" media_type=%d, rate_type=%d\n", media_type, + rate_type); + if ((prtd->play_samp_rate == 8000) && + (prtd->cap_samp_rate == 8000)) + voc_config_vocoder(media_type, rate_type, + VSS_NETWORK_ID_VOIP_NB, + voip_info.dtx_mode); + else if ((prtd->play_samp_rate == 16000) && + (prtd->cap_samp_rate == 16000)) + voc_config_vocoder(media_type, rate_type, + VSS_NETWORK_ID_VOIP_WB, + voip_info.dtx_mode); + else { + pr_debug("%s: Invalid rate playback %d, capture %d\n", + __func__, prtd->play_samp_rate, + prtd->cap_samp_rate); + goto done; + } + voc_register_mvs_cb(voip_process_ul_pkt, + voip_process_dl_pkt, prtd); + voc_start_voice_call(voc_get_session_id(VOIP_SESSION_NAME)); + + prtd->state = VOIP_STARTED; + } +done: + 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 int msm_voip_mode_rate_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + ucontrol->value.integer.value[0] = voip_info.mode; + ucontrol->value.integer.value[1] = voip_info.rate; + + mutex_unlock(&voip_info.lock); + + return 0; +} + +static int msm_voip_mode_rate_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + voip_info.mode = ucontrol->value.integer.value[0]; + voip_info.rate = ucontrol->value.integer.value[1]; + + pr_debug("%s: mode=%d,rate=%d\n", __func__, voip_info.mode, + voip_info.rate); + + mutex_unlock(&voip_info.lock); + + return 0; +} + +static int voip_get_rate_type(uint32_t mode, uint32_t rate, + uint32_t *rate_type) +{ + int ret = 0; + + switch (mode) { + case MODE_AMR: { + switch (rate) { + case 4750: + *rate_type = AMR_RATE_4750; + break; + case 5150: + *rate_type = AMR_RATE_5150; + break; + case 5900: + *rate_type = AMR_RATE_5900; + break; + case 6700: + *rate_type = AMR_RATE_6700; + break; + case 7400: + *rate_type = AMR_RATE_7400; + break; + case 7950: + *rate_type = AMR_RATE_7950; + break; + case 10200: + *rate_type = AMR_RATE_10200; + break; + case 12200: + *rate_type = AMR_RATE_12200; + break; + default: + pr_err("wrong rate for AMR NB.\n"); + ret = -EINVAL; + break; + } + break; + } + case MODE_AMR_WB: { + switch (rate) { + case 6600: + *rate_type = AMR_RATE_6600 - AMR_RATE_6600; + break; + case 8850: + *rate_type = AMR_RATE_8850 - AMR_RATE_6600; + break; + case 12650: + *rate_type = AMR_RATE_12650 - AMR_RATE_6600; + break; + case 14250: + *rate_type = AMR_RATE_14250 - AMR_RATE_6600; + break; + case 15850: + *rate_type = AMR_RATE_15850 - AMR_RATE_6600; + break; + case 18250: + *rate_type = AMR_RATE_18250 - AMR_RATE_6600; + break; + case 19850: + *rate_type = AMR_RATE_19850 - AMR_RATE_6600; + break; + case 23050: + *rate_type = AMR_RATE_23050 - AMR_RATE_6600; + break; + case 23850: + *rate_type = AMR_RATE_23850 - AMR_RATE_6600; + break; + default: + pr_err("wrong rate for AMR_WB.\n"); + ret = -EINVAL; + break; + } + break; + } + case MODE_PCM: { + *rate_type = 0; + break; + } + case MODE_IS127: + case MODE_4GV_NB: + case MODE_4GV_WB: { + switch (rate) { + case VOC_0_RATE: + case VOC_8_RATE: + case VOC_4_RATE: + case VOC_2_RATE: + case VOC_1_RATE: + *rate_type = rate; + break; + default: + pr_err("wrong rate for IS127/4GV_NB/WB.\n"); + ret = -EINVAL; + break; + } + break; + } + default: + pr_err("wrong mode type.\n"); + ret = -EINVAL; + } + pr_debug("%s, mode=%d, rate=%u, rate_type=%d\n", + __func__, mode, rate, *rate_type); + return ret; +} + +static int voip_get_media_type(uint32_t mode, + unsigned int samp_rate) +{ + uint32_t media_type; + + pr_debug("%s: mode=%d, samp_rate=%d\n", __func__, + mode, samp_rate); + switch (mode) { + case MODE_AMR: + media_type = VSS_MEDIA_ID_AMR_NB_MODEM; + break; + case MODE_AMR_WB: + media_type = VSS_MEDIA_ID_AMR_WB_MODEM; + break; + case MODE_PCM: + if (samp_rate == 8000) + media_type = VSS_MEDIA_ID_PCM_NB; + else + media_type = VSS_MEDIA_ID_PCM_WB; + break; + case MODE_IS127: /* EVRC-A */ + media_type = VSS_MEDIA_ID_EVRC_MODEM; + break; + case MODE_4GV_NB: /* EVRC-B */ + media_type = VSS_MEDIA_ID_4GV_NB_MODEM; + break; + case MODE_4GV_WB: /* EVRC-WB */ + media_type = VSS_MEDIA_ID_4GV_WB_MODEM; + break; + default: + pr_debug(" input mode is not supported\n"); + media_type = -EINVAL; + } + + pr_debug("%s: media_type is 0x%x\n", __func__, media_type); + + return media_type; +} + + +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, + .probe = msm_pcm_voip_probe, +}; + +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)); + voip_info.mode = MODE_PCM; + 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_waitqueue_head(&voip_info.in_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 0000000000000000000000000000000000000000..ea319857cdbbc3a871ee300cad16b0c7f0e329cd --- /dev/null +++ b/sound/soc/msm/msm-pcm.c @@ -0,0 +1,646 @@ +/* sound/soc/msm/msm-pcm.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, 2012 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; + +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); + + 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); + 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) { + alsa_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); + +int alsa_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 0000000000000000000000000000000000000000..6e1325ba4b773d85c2b18bf5c3bb336731d4dae7 --- /dev/null +++ b/sound/soc/msm/msm-pcm.h @@ -0,0 +1,204 @@ +/* sound/soc/msm/msm-pcm.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, 2012 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; + int mmap_flag; + int period; +}; + + + +/* 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; + +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 0000000000000000000000000000000000000000..082c840f9b7e166289ef8e6033472573f2a5d1ad --- /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 0000000000000000000000000000000000000000..2a73fd675c3436899062371cbe74290cf6d24baa --- /dev/null +++ b/sound/soc/msm/msm7201.c @@ -0,0 +1,424 @@ +/* linux/sound/soc/msm/msm7201.c + * + * Copyright (c) 2008-2009, 2011, 2012 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; +static uint32_t snd_mute_ear_mute; +static uint32_t snd_mute_mic_mute; + +struct msm_snd_rpc_ids { + unsigned long prog; + unsigned long vers; + unsigned long vers2; + unsigned long rpc_set_snd_device; + unsigned long rpc_set_device_vol; + 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.volume = volume; + msm_audio_volume_update(PCMPLAYBACK_DECODERID, + msm_vol_ctl.volume, msm_vol_ctl.pan); + } + spin_unlock_irq(&the_locks.mixer_lock); + return 0; +} + +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 = 3; /* Device */ + + /* + * The number of devices supported is 26 (0 to 25) + */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 36; + 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; + ucontrol->value.integer.value[1] = snd_mute_ear_mute; + ucontrol->value.integer.value[2] = snd_mute_mic_mute; + return 0; +} + +int msm_snd_init_rpc_ids(void) +{ + snd_rpc_ids.prog = 0x30000002; + snd_rpc_ids.vers = 0x00020001; + snd_rpc_ids.vers2 = 0x00030001; + /* + * The magic number 2 corresponds to the rpc call + * index for snd_set_device + */ + snd_rpc_ids.rpc_set_snd_device = 2; + snd_rpc_ids.rpc_set_device_vol = 3; + 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_DEBUG "%s failed (compatible VERS = %ld) \ + trying again with another API\n", + __func__, snd_rpc_ids.vers); + snd_ep = + msm_rpc_connect_compatible(snd_rpc_ids.prog, + snd_rpc_ids.vers2, 0); + } + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: failed (compatible VERS = %ld)\n", + __func__, snd_rpc_ids.vers2); + 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]; + + if (ucontrol->value.integer.value[1] > 1) + ucontrol->value.integer.value[1] = 1; + if (ucontrol->value.integer.value[2] > 1) + ucontrol->value.integer.value[2] = 1; + + 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((int)ucontrol->value.integer.value[1]); + req.snd_mute_mic_mute = + cpu_to_be32((int)ucontrol->value.integer.value[2]); + 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"); + snd_mute_ear_mute = ucontrol->value.integer.value[1]; + snd_mute_mic_mute = ucontrol->value.integer.value[2]; + printk(KERN_ERR "%s: snd_mute_ear_mute =%d, snd_mute_mic_mute = %d\n", + __func__, snd_mute_ear_mute, snd_mute_mic_mute); + } + + return rc; +} + +static int snd_msm_device_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Device/Volume */ + + /* + * The number of devices supported is 37 (0 to 36) + */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 36; + return 0; +} + +static int snd_msm_device_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct snd_vol_req { + struct rpc_request_hdr hdr; + uint32_t device; + uint32_t method; + uint32_t volume; + uint32_t cb_func; + uint32_t client_data; + } req; + + snd_rpc_ids.device = (int)ucontrol->value.integer.value[0]; + + if ((ucontrol->value.integer.value[1] < 0) || + (ucontrol->value.integer.value[1] > 6)) { + pr_err("Device volume should be in range of 1 to 6\n"); + return -EINVAL; + } + if ((ucontrol->value.integer.value[0] > 36) || + (ucontrol->value.integer.value[0] < 0)) { + pr_err("Device range supported is 0 to 36\n"); + return -EINVAL; + } + + req.device = cpu_to_be32((int)ucontrol->value.integer.value[0]); + req.method = cpu_to_be32(0); + req.volume = cpu_to_be32((int)ucontrol->value.integer.value[1]); + req.cb_func = -1; + req.client_data = cpu_to_be32(0); + + rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_device_vol , + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n", + __func__, rc); + } else { + printk(KERN_ERR "%s: device [%d] volume set to [%d]\n", + __func__, (int)ucontrol->value.integer.value[0], + (int)ucontrol->value.integer.value[1]); + } + + 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", 0, snd_msm_device_info, snd_msm_device_get, \ + snd_msm_device_put, 0), + MSM_EXT("Device Volume", 0, snd_msm_device_vol_info, NULL, \ + snd_msm_device_vol_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(); + snd_mute_ear_mute = 0; + snd_mute_mic_mute = 0; + + 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 0000000000000000000000000000000000000000..1f23a929a63d3d7202367d702bfa208fb1079f81 --- /dev/null +++ b/sound/soc/msm/msm7k-pcm.c @@ -0,0 +1,699 @@ +/* linux/sound/soc/msm/msm7k-pcm.c + * + * Copyright (c) 2008-2009, 2012 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_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + 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 = 4800 * 2, + .period_bytes_min = 4800, + .period_bytes_max = 4800, + .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 = 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 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; + + pr_debug("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 playback_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->playback_substream); + if (prtd->mmap_flag) { + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) + return; + if (!prtd->stopped) + msm_pcm_enqueue_data(prtd->playback_substream); + else + prtd->out_needed++; + } +} + +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; + + if (prtd->enabled | !(prtd->mmap_flag)) + return 0; + + prtd->data = substream->dma_buffer.area; + prtd->phys = 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; + + 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; + 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) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned long flag = 0; + int ret = 0; + + 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->playback_substream); + prtd->out_needed--; + } else { + prtd->out_tail = 1; + msm_pcm_enqueue_data( + prtd->playback_substream); + prtd->out_needed--; + } + if (prtd->out_needed) { + prtd->out_tail ^= 1; + msm_pcm_enqueue_data( + prtd->playback_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; + } + + 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) { + runtime->hw = msm_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + prtd->playback_substream = substream; + prtd->eos_ack = 0; + ret = msm_audio_volume_update(PCMPLAYBACK_DECODERID, + msm_vol_ctl.volume, msm_vol_ctl.pan); + } 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; + if (copy_count == 1) { + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + } + 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; + } + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; + +} + +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; +} + +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_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 0000000000000000000000000000000000000000..e8d51ac955284ab4c6c3691837def84fe0be01e5 --- /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 0000000000000000000000000000000000000000..50bf6fb0e852d57da05741423fadfbbabe574922 --- /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 0000000000000000000000000000000000000000..c64a3ffc44958d628c1c6d679db977f2093f78e2 --- /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 0000000000000000000000000000000000000000..fec7cf57027b740d81df06eeba0729d5150ce3db --- /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 0000000000000000000000000000000000000000..94e37caa0fc0752dfd847d66b3845a267819c8bb --- /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-apq-wm8903.c b/sound/soc/msm/msm8660-apq-wm8903.c new file mode 100644 index 0000000000000000000000000000000000000000..e697c3f1e1df4a3ec5b236de3cd0e735364dd51c --- /dev/null +++ b/sound/soc/msm/msm8660-apq-wm8903.c @@ -0,0 +1,725 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-routing.h" +#include "../codecs/wm8903.h" + +#define MSM_GPIO_CLASS_D0_EN 80 +#define MSM_GPIO_CLASS_D1_EN 81 + +#define MSM_CDC_MIC_I2S_MCLK 108 + +static int msm8660_spk_func; +static int msm8660_headset_func; +static int msm8660_headphone_func; + +static struct clk *mic_bit_clk; +static struct clk *spkr_osr_clk; +static struct clk *spkr_bit_clk; +static struct clk *wm8903_mclk; + +static int rx_hw_param_status; +static int tx_hw_param_status; +/* Platform specific logic */ + +enum { + GET_ERR, + SET_ERR, + ENABLE_ERR, + NONE +}; + +enum { + FUNC_OFF, + FUNC_ON, +}; + +static struct wm8903_vdd { + struct regulator *reg_id; + const char *name; + u32 voltage; +} wm8903_vdds[] = { + { NULL, "8058_l16", 1800000 }, + { NULL, "8058_l0", 1200000 }, + { NULL, "8058_s3", 1800000 }, +}; + +static void classd_amp_pwr(int enable) +{ + int rc; + + pr_debug("%s, enable = %d\n", __func__, enable); + if (enable) { + /* currently external PA isn't used for LINEOUTL */ + rc = gpio_request(MSM_GPIO_CLASS_D0_EN, "CLASSD0_EN"); + if (rc) { + pr_err("%s: spkr PA gpio %d request failed\n", + __func__, MSM_GPIO_CLASS_D0_EN); + return; + } + gpio_direction_output(MSM_GPIO_CLASS_D0_EN, 1); + gpio_set_value_cansleep(MSM_GPIO_CLASS_D0_EN, 1); + rc = gpio_request(MSM_GPIO_CLASS_D1_EN, "CLASSD1_EN"); + if (rc) { + pr_err("%s: spkr PA gpio %d request failed\n", + __func__, MSM_GPIO_CLASS_D1_EN); + return; + } + gpio_direction_output(MSM_GPIO_CLASS_D1_EN, 1); + gpio_set_value_cansleep(MSM_GPIO_CLASS_D1_EN, 1); + } else { + gpio_set_value_cansleep(MSM_GPIO_CLASS_D0_EN, 0); + gpio_free(MSM_GPIO_CLASS_D0_EN); + + gpio_set_value_cansleep(MSM_GPIO_CLASS_D1_EN, 0); + gpio_free(MSM_GPIO_CLASS_D1_EN); + } +} + +static void extern_poweramp_on(void) +{ + pr_debug("%s: enable stereo spkr amp\n", __func__); + classd_amp_pwr(1); +} + +static void extern_poweramp_off(void) +{ + pr_debug("%s: disable stereo spkr amp\n", __func__); + classd_amp_pwr(0); +} + +static int msm8660_wm8903_powerup(void) +{ + int rc = 0, index, stage = NONE; + struct wm8903_vdd *vdd = NULL; + + for (index = 0; index < ARRAY_SIZE(wm8903_vdds); index++) { + vdd = &wm8903_vdds[index]; + vdd->reg_id = regulator_get(NULL, vdd->name); + if (IS_ERR(vdd->reg_id)) { + pr_err("%s: Unable to get %s\n", __func__, vdd->name); + stage = GET_ERR; + rc = -ENODEV; + break; + } + + rc = regulator_set_voltage(vdd->reg_id, + vdd->voltage, vdd->voltage); + if (rc) { + pr_err("%s: unable to set %s voltage to %dV\n", + __func__, vdd->name, vdd->voltage); + stage = SET_ERR; + break; + } + + rc = regulator_enable(vdd->reg_id); + if (rc) { + pr_err("%s:failed to enable %s\n", __func__, vdd->name); + stage = ENABLE_ERR; + break; + } + } + + if (index != ARRAY_SIZE(wm8903_vdds)) { + if (stage != GET_ERR) { + vdd = &wm8903_vdds[index]; + regulator_put(vdd->reg_id); + vdd->reg_id = NULL; + } + + while (index--) { + vdd = &wm8903_vdds[index]; + regulator_disable(vdd->reg_id); + regulator_put(vdd->reg_id); + vdd->reg_id = NULL; + } + } + + return rc; +} + +static void msm8660_wm8903_powerdown(void) +{ + int index = ARRAY_SIZE(wm8903_vdds); + struct wm8903_vdd *vdd = NULL; + + while (index--) { + vdd = &wm8903_vdds[index]; + if (vdd->reg_id) { + regulator_disable(vdd->reg_id); + regulator_put(vdd->reg_id); + } + } +} + +static int msm8660_wm8903_enable_mclk(int enable) +{ + int ret = 0; + + if (enable) { + ret = gpio_request(MSM_CDC_MIC_I2S_MCLK, "I2S_Clock"); + if (ret != 0) { + pr_err("%s: failed to request GPIO\n", __func__); + return ret; + } + + wm8903_mclk = clk_get_sys(NULL, "i2s_mic_osr_clk"); + if (IS_ERR(wm8903_mclk)) { + pr_err("Failed to get i2s_mic_osr_clk\n"); + gpio_free(MSM_CDC_MIC_I2S_MCLK); + return IS_ERR(wm8903_mclk); + } + /* Master clock OSR 256 */ + clk_set_rate(wm8903_mclk, 48000 * 256); + ret = clk_prepare_enable(wm8903_mclk); + if (ret != 0) { + pr_err("Unable to enable i2s_mic_osr_clk\n"); + gpio_free(MSM_CDC_MIC_I2S_MCLK); + clk_put(wm8903_mclk); + return ret; + } + } else { + if (wm8903_mclk) { + clk_disable_unprepare(wm8903_mclk); + clk_put(wm8903_mclk); + gpio_free(MSM_CDC_MIC_I2S_MCLK); + wm8903_mclk = NULL; + } + } + + return ret; +} + +static int msm8660_wm8903_prepare(void) +{ + int ret = 0; + + ret = msm8660_wm8903_powerup(); + if (ret) { + pr_err("Unable to powerup wm8903\n"); + return ret; + } + + ret = msm8660_wm8903_enable_mclk(1); + if (ret) { + pr_err("Unable to enable mclk to wm8903\n"); + return ret; + } + + return ret; +} + +static void msm8660_wm8903_unprepare(void) +{ + msm8660_wm8903_powerdown(); + msm8660_wm8903_enable_mclk(0); +} + +static int msm8660_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int rate = params_rate(params), ret = 0; + + pr_debug("Enter %s rate = %d\n", __func__, rate); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (rx_hw_param_status) + return 0; + /* wm8903 run @ LRC*256 */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rate * 256, + SND_SOC_CLOCK_IN); + snd_soc_dai_digital_mute(codec_dai, 0); + if (ret < 0) { + pr_err("can't set rx codec clk configuration\n"); + return ret; + } + clk_set_rate(wm8903_mclk, rate * 256); + /* set as slave mode CPU */ + clk_set_rate(spkr_bit_clk, 0); + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + rx_hw_param_status++; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (tx_hw_param_status) + return 0; + clk_set_rate(wm8903_mclk, rate * 256); + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rate * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("can't set tx codec clk configuration\n"); + return ret; + } + clk_set_rate(mic_bit_clk, 0); + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + tx_hw_param_status++; + } + return 0; +} + +static int msm8660_i2s_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("Enter %s\n", __func__); + /* ON Dragonboard, I2S between wm8903 and CPU is shared by + * CODEC_SPEAKER and CODEC_MIC therefore CPU only can operate + * as input SLAVE mode. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* config WM8903 in Mater mode */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_I2S); + if (ret != 0) { + pr_err("codec_dai set_fmt error\n"); + return ret; + } + /* config CPU in SLAVE mode */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + if (ret != 0) { + pr_err("cpu_dai set_fmt error\n"); + return ret; + } + spkr_osr_clk = clk_get_sys(NULL, "i2s_spkr_osr_clk"); + if (IS_ERR(spkr_osr_clk)) { + pr_err("Failed to get i2s_spkr_osr_clk\n"); + return PTR_ERR(spkr_osr_clk); + } + clk_set_rate(spkr_osr_clk, 48000 * 256); + ret = clk_prepare_enable(spkr_osr_clk); + if (ret != 0) { + pr_err("Unable to enable i2s_spkr_osr_clk\n"); + clk_put(spkr_osr_clk); + return ret; + } + spkr_bit_clk = clk_get_sys(NULL, "i2s_spkr_bit_clk"); + if (IS_ERR(spkr_bit_clk)) { + pr_err("Failed to get i2s_spkr_bit_clk\n"); + clk_disable_unprepare(spkr_osr_clk); + clk_put(spkr_osr_clk); + return PTR_ERR(spkr_bit_clk); + } + clk_set_rate(spkr_bit_clk, 0); + ret = clk_prepare_enable(spkr_bit_clk); + if (ret != 0) { + pr_err("Unable to enable i2s_spkr_bit_clk\n"); + clk_disable_unprepare(spkr_osr_clk); + clk_put(spkr_osr_clk); + clk_put(spkr_bit_clk); + return ret; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* config WM8903 in Mater mode */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_I2S); + if (ret != 0) { + pr_err("codec_dai set_fmt error\n"); + return ret; + } + /* config CPU in SLAVE mode */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + if (ret != 0) { + pr_err("codec_dai set_fmt error\n"); + return ret; + } + + mic_bit_clk = clk_get_sys(NULL, "i2s_mic_bit_clk"); + if (IS_ERR(mic_bit_clk)) { + pr_err("Failed to get i2s_mic_bit_clk\n"); + return PTR_ERR(mic_bit_clk); + } + clk_set_rate(mic_bit_clk, 0); + ret = clk_prepare_enable(mic_bit_clk); + if (ret != 0) { + pr_err("Unable to enable i2s_mic_bit_clk\n"); + clk_put(mic_bit_clk); + return ret; + } + } + return ret; +} + +static void msm8660_i2s_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("Enter %s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + tx_hw_param_status = 0; + rx_hw_param_status = 0; + if (spkr_bit_clk) { + clk_disable_unprepare(spkr_bit_clk); + clk_put(spkr_bit_clk); + spkr_bit_clk = NULL; + } + if (spkr_osr_clk) { + clk_disable_unprepare(spkr_osr_clk); + clk_put(spkr_osr_clk); + spkr_osr_clk = NULL; + } + if (mic_bit_clk) { + clk_disable_unprepare(mic_bit_clk); + clk_put(mic_bit_clk); + mic_bit_clk = NULL; + } + } +} + +static void msm8660_ext_control(struct snd_soc_codec *codec) +{ + /* set the enpoints to their new connetion states */ + if (msm8660_spk_func == FUNC_ON) + snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk"); + + /* set the enpoints to their new connetion states */ + if (msm8660_headset_func == FUNC_ON) + snd_soc_dapm_enable_pin(&codec->dapm, "Headset Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Headset Jack"); + + /* set the enpoints to their new connetion states */ + if (msm8660_headphone_func == FUNC_ON) + snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack"); + + /* signal a DAPM event */ + snd_soc_dapm_sync(&codec->dapm); +} + +static int msm8660_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm8660_spk_func; + return 0; +} + +static int msm8660_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 (msm8660_spk_func == ucontrol->value.integer.value[0]) + return 0; + + msm8660_spk_func = ucontrol->value.integer.value[0]; + msm8660_ext_control(codec); + return 1; +} + +static int msm8660_get_hs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm8660_headset_func; + return 0; +} + +static int msm8660_set_hs(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 (msm8660_headset_func == ucontrol->value.integer.value[0]) + return 0; + + msm8660_headset_func = ucontrol->value.integer.value[0]; + msm8660_ext_control(codec); + return 1; +} + +static int msm8660_get_hph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm8660_headphone_func; + return 0; +} + +static int msm8660_set_hph(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 (msm8660_headphone_func == ucontrol->value.integer.value[0]) + return 0; + + msm8660_headphone_func = ucontrol->value.integer.value[0]; + msm8660_ext_control(codec); + return 1; +} + +static int msm8660_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + extern_poweramp_on(); + else + extern_poweramp_off(); + return 0; +} + +static struct snd_soc_ops machine_ops = { + .startup = msm8660_i2s_startup, + .shutdown = msm8660_i2s_shutdown, + .hw_params = msm8660_i2s_hw_params, +}; + +static const struct snd_soc_dapm_widget msm8660_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", msm8660_spkramp_event), + SND_SOC_DAPM_MIC("Headset Jack", NULL), + SND_SOC_DAPM_MIC("Headphone Jack", NULL), + /* to fix a bug in wm8903.c, where audio doesn't function + * after suspend/resume + */ + SND_SOC_DAPM_SUPPLY("CLK_SYS_ENA", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Match with wm8903 codec line out pin */ + {"Ext Spk", NULL, "LINEOUTL"}, + {"Ext Spk", NULL, "LINEOUTR"}, + /* Headset connects to IN3L with Bias */ + {"IN3L", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Jack"}, + /* Headphone connects to IN3R with Bias */ + {"IN3R", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headphone Jack"}, + {"ADCL", NULL, "CLK_SYS_ENA"}, + {"ADCR", NULL, "CLK_SYS_ENA"}, + {"DACL", NULL, "CLK_SYS_ENA"}, + {"DACR", NULL, "CLK_SYS_ENA"}, +}; + +static const char *cmn_status[] = {"Off", "On"}; +static const struct soc_enum msm8660_enum[] = { + SOC_ENUM_SINGLE_EXT(2, cmn_status), +}; + +static const struct snd_kcontrol_new wm8903_msm8660_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8660_enum[0], msm8660_get_spk, + msm8660_set_spk), + SOC_ENUM_EXT("Headset Function", msm8660_enum[0], msm8660_get_hs, + msm8660_set_hs), + SOC_ENUM_EXT("Headphone Function", msm8660_enum[0], msm8660_get_hph, + msm8660_set_hph), +}; + +static int msm8660_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int err; + + snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk"); + snd_soc_dapm_enable_pin(&codec->dapm, "CLK_SYS_ENA"); + + err = snd_soc_add_controls(codec, wm8903_msm8660_controls, + ARRAY_SIZE(wm8903_msm8660_controls)); + if (err < 0) + return err; + + snd_soc_dapm_new_controls(&codec->dapm, msm8660_dapm_widgets, + ARRAY_SIZE(msm8660_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(&codec->dapm); + + return 0; +} + +static int pri_i2s_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); + + rate->min = rate->max = 48000; + return 0; +} +/* + * LPA Needs only RX BE DAI links. + * Hence define seperate BE list for lpa + */ +static const char *lpa_mm_be[] = { + LPASS_BE_PRI_I2S_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 *mm1_be[] = { + LPASS_BE_PRI_I2S_RX, + LPASS_BE_PRI_I2S_TX, + LPASS_BE_HDMI, +}; + +static struct snd_soc_dsp_link fe_media = { + .supported_be = mm1_be, + .num_be = ARRAY_SIZE(mm1_be), + .fe_playback_channels = 2, + .fe_capture_channels = 1, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, SND_SOC_DSP_TRIGGER_POST}, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8660_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8660 Media", + .stream_name = "MultiMedia", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8660 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, + }, + /* Backend DAI Links */ + { + .name = LPASS_BE_PRI_I2S_RX, + .stream_name = "Primary I2S Playback", + .cpu_dai_name = "msm-dai-q6.0", + .platform_name = "msm-pcm-routing", + .codec_name = "wm8903-codec.3-001a", + .codec_dai_name = "wm8903-hifi", + .no_pcm = 1, + .be_hw_params_fixup = pri_i2s_be_hw_params_fixup, + .ops = &machine_ops, + .init = &msm8660_audrx_init, + .be_id = MSM_BACKEND_DAI_PRI_I2S_RX + }, + { + .name = LPASS_BE_PRI_I2S_TX, + .stream_name = "Primary I2S Capture", + .cpu_dai_name = "msm-dai-q6.1", + .platform_name = "msm-pcm-routing", + .codec_name = "wm8903-codec.3-001a", + .codec_dai_name = "wm8903-hifi", + .no_pcm = 1, + .ops = &machine_ops, + .be_hw_params_fixup = pri_i2s_be_hw_params_fixup, + .be_id = MSM_BACKEND_DAI_PRI_I2S_TX + }, + /* LPA frontend DAI link*/ + { + .name = "MSM8660 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, + }, + /* HDMI backend 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_codec = 1, + .no_pcm = 1, + .be_hw_params_fixup = pri_i2s_be_hw_params_fixup, + .be_id = MSM_BACKEND_DAI_HDMI_RX + }, +}; + +struct snd_soc_card snd_soc_card_msm8660 = { + .name = "msm8660-snd-card", + .dai_link = msm8660_dai, + .num_links = ARRAY_SIZE(msm8660_dai), +}; + +static struct platform_device *msm_snd_device; + +static int __init msm_audio_init(void) +{ + int ret = 0; + + if (machine_is_msm8x60_dragon()) { + /* wm8903 audio codec needs to power up and mclk existing + before it's probed */ + ret = msm8660_wm8903_prepare(); + if (ret) { + pr_err("failed to prepare wm8903 audio codec\n"); + return ret; + } + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + msm8660_wm8903_unprepare(); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm8660); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + msm8660_wm8903_unprepare(); + return ret; + } + } + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + msm8660_wm8903_unprepare(); + 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/msm8660.c b/sound/soc/msm/msm8660.c new file mode 100644 index 0000000000000000000000000000000000000000..4cbfd45bf9f99a74ff3d86ed5f0b9c05f3d01b22 --- /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_prepare_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_unprepare(rx_osr_clk); + clk_put(rx_osr_clk); + return PTR_ERR(rx_bit_clk); + } + clk_set_rate(rx_bit_clk, 8); + ret = clk_prepare_enable(rx_bit_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_spkr_bit_clk\n"); + clk_put(rx_bit_clk); + clk_disable_unprepare(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_prepare_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_unprepare(tx_osr_clk); + clk_put(tx_osr_clk); + return PTR_ERR(tx_bit_clk); + } + clk_set_rate(tx_bit_clk, 8); + ret = clk_prepare_enable(tx_bit_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_mic_bit_clk\n"); + clk_put(tx_bit_clk); + clk_disable_unprepare(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_unprepare(rx_bit_clk); + clk_put(rx_bit_clk); + rx_bit_clk = NULL; + } + if (rx_osr_clk) { + clk_disable_unprepare(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_unprepare(tx_bit_clk); + clk_put(tx_bit_clk); + tx_bit_clk = NULL; + } + if (tx_osr_clk) { + clk_disable_unprepare(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/msm8930.c b/sound/soc/msm/msm8930.c new file mode 100644 index 0000000000000000000000000000000000000000..ad1278cee11e64685e4800ebeaea0047e5c6b15d --- /dev/null +++ b/sound/soc/msm/msm8930.c @@ -0,0 +1,1146 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "../codecs/wcd9304.h" + +/* 8930 machine driver */ + +#define MSM8930_SPK_ON 1 +#define MSM8930_SPK_OFF 0 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define SPK_AMP_POS 0x1 +#define SPK_AMP_NEG 0x2 +#define SPKR_BOOST_GPIO 15 +#define DEFAULT_PMIC_SPK_GAIN 0x0D +#define SITAR_EXT_CLK_RATE 12288000 + +#define SITAR_MBHC_DEF_BUTTONS 8 +#define SITAR_MBHC_DEF_RLOADS 5 + +static int msm8930_spk_control; +static int msm8930_slim_0_rx_ch = 1; +static int msm8930_slim_0_tx_ch = 1; +static int msm8930_pmic_spk_gain = DEFAULT_PMIC_SPK_GAIN; + +static int msm8930_ext_spk_pamp; +static int msm8930_btsco_rate = BTSCO_RATE_8KHZ; +static int msm8930_btsco_ch = 1; + +static struct clk *codec_clk; +static int clk_users; + +static int msm8930_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int msm8930_enable_codec_ext_clk( + struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct sitar_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = SITAR_MICBIAS2, + .mclk_cb_fn = msm8930_enable_codec_ext_clk, + .mclk_rate = SITAR_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + + +static void msm8930_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm8930_spk_control = %d", __func__, msm8930_spk_control); + if (msm8930_spk_control == MSM8930_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Left Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk left Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Left Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Left Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm8930_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_spk_control = %d", __func__, msm8930_spk_control); + ucontrol->value.integer.value[0] = msm8930_spk_control; + return 0; +} +static int msm8930_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 (msm8930_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm8930_spk_control = ucontrol->value.integer.value[0]; + msm8930_ext_control(codec); + return 1; +} + +static void msm8960_ext_spk_power_amp_on(u32 spk) +{ + int ret = 0; + + if (spk & (SPK_AMP_POS | SPK_AMP_NEG)) { + if ((msm8930_ext_spk_pamp & SPK_AMP_POS) && + (msm8930_ext_spk_pamp & SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm8930_ext_spk_pamp |= spk; + + if ((msm8930_ext_spk_pamp & SPK_AMP_POS) && + (msm8930_ext_spk_pamp & SPK_AMP_NEG)) { + + if (machine_is_msm8930_mtp() + || machine_is_msm8930_fluid()) { + pr_debug("%s: Configure Speaker Boost GPIO %u", + __func__, SPKR_BOOST_GPIO); + ret = gpio_request(SPKR_BOOST_GPIO, + "SPKR_BOOST_EN"); + if (ret) { + pr_err("%s: Failed to configure speaker boost " + "gpio %u\n", __func__, SPKR_BOOST_GPIO); + return; + } + + pr_debug("%s: Enable Speaker boost gpio %u\n", + __func__, SPKR_BOOST_GPIO); + gpio_direction_output(SPKR_BOOST_GPIO, 1); + } + + pm8xxx_spk_enable(MSM8930_SPK_ON); + pr_debug("%s: slepping 4 ms after turning on external " + " Left Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm8960_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (SPK_AMP_POS | SPK_AMP_NEG)) { + if (!msm8930_ext_spk_pamp) + return; + if (machine_is_msm8930_mtp() + || machine_is_msm8930_fluid()) { + pr_debug("%s: Free speaker boost gpio %u\n", + __func__, SPKR_BOOST_GPIO); + gpio_direction_output(SPKR_BOOST_GPIO, 0); + gpio_free(SPKR_BOOST_GPIO); + } + + pm8xxx_spk_enable(MSM8930_SPK_OFF); + msm8930_ext_spk_pamp = 0; + pr_debug("%s: slepping 4 ms after turning on external " + " Left Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static int msm8930_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)) { + if (!strncmp(w->name, "Ext Spk Left Pos", 17)) + msm8960_ext_spk_power_amp_on(SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Left Neg", 17)) + msm8960_ext_spk_power_amp_on(SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } else { + if (!strncmp(w->name, "Ext Spk Left Pos", 17)) + msm8960_ext_spk_power_amp_off(SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Left Neg", 17)) + msm8960_ext_spk_power_amp_off(SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm8930_enable_codec_ext_clk( + struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, SITAR_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + sitar_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Sitar MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + sitar_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } + return 0; +} + +static int msm8930_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return msm8930_enable_codec_ext_clk(w->codec, 1, true); + case SND_SOC_DAPM_POST_PMD: + return msm8930_enable_codec_ext_clk(w->codec, 0, true); + } + return 0; +} + +static const struct snd_soc_dapm_widget msm8930_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm8930_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Left Pos", msm8930_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Left Neg", msm8930_spkramp_event), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + {"MIC BIAS1 Internal1", NULL, "MCLK"}, + {"MIC BIAS2 Internal1", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Left Pos", NULL, "LINEOUT1"}, + {"Ext Spk Left Neg", NULL, "LINEOUT2"}, + + /* Headset Mic */ + {"AMIC2", NULL, "MIC BIAS2 Internal1"}, + {"MIC BIAS2 Internal1", NULL, "Headset Mic"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS2 Internal1"}, + {"MIC BIAS2 Internal1", NULL, "ANCLeft Headset Mic"}, + + {"AMIC3", NULL, "MIC BIAS2 Internal1"}, + {"MIC BIAS2 Internal1", NULL, "ANCRight Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC1 Input on Sitar codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Back top MIC on Fluid. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC2 Input on Sitar codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Sitar codec. + */ + {"DMIC3", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Sitar codec. + */ + {"DMIC4", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic4"}, + + +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm8930_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm8930_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int msm8930_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_slim_0_rx_ch = %d\n", __func__, + msm8930_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm8930_slim_0_rx_ch - 1; + return 0; +} + +static int msm8930_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8930_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm8930_slim_0_rx_ch = %d\n", __func__, + msm8930_slim_0_rx_ch); + return 1; +} + +static int msm8930_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_slim_0_tx_ch = %d\n", __func__, + msm8930_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm8930_slim_0_tx_ch - 1; + return 0; +} + +static int msm8930_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8930_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm8930_slim_0_tx_ch = %d\n", __func__, + msm8930_slim_0_tx_ch); + return 1; +} + +static int msm8930_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_btsco_rate = %d", __func__, msm8930_btsco_rate); + ucontrol->value.integer.value[0] = msm8930_btsco_rate; + return 0; +} + +static int msm8930_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm8930_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + msm8930_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + msm8930_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: msm8930_btsco_rate = %d\n", __func__, msm8930_btsco_rate); + return 0; +} + +static const char *pmic_spk_gain_text[] = { + "NEG_6_DB", "NEG_4_DB", "NEG_2_DB", "ZERO_DB", "POS_2_DB", "POS_4_DB", + "POS_6_DB", "POS_8_DB", "POS_10_DB", "POS_12_DB", "POS_14_DB", + "POS_16_DB", "POS_18_DB", "POS_20_DB", "POS_22_DB", "POS_24_DB" +}; + +static const struct soc_enum msm8960_pmic_spk_gain_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(pmic_spk_gain_text), + pmic_spk_gain_text), +}; + +static int msm8930_pmic_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_pmic_spk_gain = %d\n", __func__, + msm8930_pmic_spk_gain); + ucontrol->value.integer.value[0] = msm8930_pmic_spk_gain; + return 0; +} + +static int msm8930_pmic_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + msm8930_pmic_spk_gain = ucontrol->value.integer.value[0]; + ret = pm8xxx_spk_gain(msm8930_pmic_spk_gain); + pr_debug("%s: msm8930_pmic_spk_gain = %d" + " ucontrol->value.integer.value[0] = %d\n", __func__, + msm8930_pmic_spk_gain, + (int) ucontrol->value.integer.value[0]); + return ret; +} + +static const struct snd_kcontrol_new sitar_msm8930_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8930_enum[0], msm8930_get_spk, + msm8930_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm8930_enum[1], + msm8930_slim_0_rx_ch_get, msm8930_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm8930_enum[2], + msm8930_slim_0_tx_ch_get, msm8930_slim_0_tx_ch_put), + SOC_ENUM_EXT("PMIC SPK Gain", msm8960_pmic_spk_gain_enum[0], + msm8930_pmic_gain_get, msm8930_pmic_gain_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm8930_btsco_enum[0], + msm8930_btsco_rate_get, msm8930_btsco_rate_put), +}; + +static void *def_sitar_mbhc_cal(void) +{ + void *sitar_cal; + struct sitar_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + sitar_cal = kzalloc(SITAR_MBHC_CAL_SIZE(SITAR_MBHC_DEF_BUTTONS, + SITAR_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!sitar_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((SITAR_MBHC_CAL_GENERAL_PTR(sitar_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((SITAR_MBHC_CAL_PLUG_DET_PTR(sitar_cal)->X) = (Y)) + S(mic_current, SITAR_PID_MIC_5_UA); + S(hph_current, SITAR_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((SITAR_MBHC_CAL_BTN_DET_PTR(sitar_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, SITAR_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = SITAR_MBHC_CAL_BTN_DET_PTR(sitar_cal); + btn_low = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_V_BTN_LOW); + btn_high = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return sitar_cal; +} + +static int msm8930_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + pr_debug("%s: ch=%d\n", __func__, + msm8930_slim_0_rx_ch); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm8930_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm8930_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + msm8930_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + msm8930_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + } +end: + return ret; +} + +static int msm8930_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; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s()\n", __func__); + + snd_soc_dapm_new_controls(dapm, msm8930_dapm_widgets, + ARRAY_SIZE(msm8930_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Left Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Left Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + SITAR_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + mbhc_cfg.gpio = 37; + mbhc_cfg.gpio_irq = gpio_to_irq(mbhc_cfg.gpio); + sitar_hs_detect(codec, &mbhc_cfg); + + /* Initialize default PMIC speaker gain */ + pm8xxx_spk_gain(DEFAULT_PMIC_SPK_GAIN); + + return 0; +} + +static int msm8930_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 = msm8930_slim_0_rx_ch; + + return 0; +} + +static int msm8930_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 = msm8930_slim_0_tx_ch; + + return 0; +} + +static int msm8930_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 msm8930_hdmi_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); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int msm8930_btsco_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); + + rate->min = rate->max = msm8930_btsco_rate; + channels->min = channels->max = msm8930_btsco_ch; + + return 0; +} + +static int msm8930_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static void msm8930_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops msm8930_be_ops = { + .startup = msm8930_startup, + .hw_params = msm8930_hw_params, + .shutdown = msm8930_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8930_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8930 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8930 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8930 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + /* .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, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + /* .be_id = do not care */ + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM8930 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* 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 = "sitar_codec", + .codec_dai_name = "sitar_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm8930_audrx_init, + .be_hw_params_fixup = msm8930_slim_0_rx_be_hw_params_fixup, + .ops = &msm8930_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "sitar_codec", + .codec_dai_name = "sitar_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm8930_slim_0_tx_be_hw_params_fixup, + .ops = &msm8930_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, + .be_hw_params_fixup = msm8930_btsco_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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, + .be_hw_params_fixup = msm8930_btsco_be_hw_params_fixup, + }, + { + .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 = msm8930_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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 = msm8930_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .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_HDMI_RX, + .be_hw_params_fixup = msm8930_hdmi_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .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_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .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_AFE_PCM_TX, + }, + /* Incall Music BACK END DAI Link */ + { + .name = LPASS_BE_VOICE_PLAYBACK_TX, + .stream_name = "Voice Farend Playback", + .cpu_dai_name = "msm-dai-q6.32773", + .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_VOICE_PLAYBACK_TX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + }, + /* Incall Record Uplink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_TX, + .stream_name = "Voice Uplink Capture", + .cpu_dai_name = "msm-dai-q6.32772", + .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_INCALL_RECORD_TX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + }, + /* Incall Record Downlink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_RX, + .stream_name = "Voice Downlink Capture", + .cpu_dai_name = "msm-dai-q6.32771", + .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_INCALL_RECORD_RX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, +}; + +struct snd_soc_card snd_soc_card_msm8930 = { + .name = "msm8930-sitar-snd-card", + .dai_link = msm8930_dai, + .num_links = ARRAY_SIZE(msm8930_dai), + .controls = sitar_msm8930_controls, + .num_controls = ARRAY_SIZE(sitar_msm8930_controls), +}; + +static struct platform_device *msm8930_snd_device; + +static int msm8930_configure_headset_mic_gpios(void) +{ + int ret; + ret = gpio_request(80, "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio 80\n", __func__); + return ret; + } + ret = gpio_direction_output(80, 0); + if (ret) { + pr_err("%s: Unable to set direction\n", __func__); + gpio_free(80); + } + msm8930_headset_gpios_configured = 0; + return 0; +} +static void msm8930_free_headset_mic_gpios(void) +{ + if (msm8930_headset_gpios_configured) + gpio_free(80); +} + +static int __init msm8930_audio_init(void) +{ + int ret; + + if (!cpu_is_msm8930()) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV ; + } + mbhc_cfg.calibration = def_sitar_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm8930_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm8930_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm8930_snd_device, &snd_soc_card_msm8930); + ret = platform_device_add(msm8930_snd_device); + if (ret) { + platform_device_put(msm8930_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm8930_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm8930_headset_gpios_configured = 0; + } else + msm8930_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm8930_audio_init); + +static void __exit msm8930_audio_exit(void) +{ + if (!cpu_is_msm8930()) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm8930_free_headset_mic_gpios(); + platform_device_unregister(msm8930_snd_device); + kfree(mbhc_cfg.calibration); +} +module_exit(msm8930_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8930"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c new file mode 100644 index 0000000000000000000000000000000000000000..bb5d817c91992304fad47dd24531bee3065006e3 --- /dev/null +++ b/sound/soc/msm/msm8960.c @@ -0,0 +1,1615 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-routing.h" +#include "../codecs/wcd9310.h" + +/* 8960 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#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 + +#define SAMPLE_RATE_8KHZ 8000 +#define SAMPLE_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define GPIO_AUX_PCM_DOUT 63 +#define GPIO_AUX_PCM_DIN 64 +#define GPIO_AUX_PCM_SYNC 65 +#define GPIO_AUX_PCM_CLK 66 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +#define JACK_DETECT_GPIO 38 +#define JACK_DETECT_INT PM8921_GPIO_IRQ(PM8921_IRQ_BASE, JACK_DETECT_GPIO) +#define JACK_US_EURO_SEL_GPIO 35 + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm8960_spk_control; +static int msm8960_ext_bottom_spk_pamp; +static int msm8960_ext_top_spk_pamp; +static int msm8960_slim_0_rx_ch = 1; +static int msm8960_slim_0_tx_ch = 1; + +static int msm8960_btsco_rate = SAMPLE_RATE_8KHZ; +static int msm8960_btsco_ch = 1; + +static int msm8960_auxpcm_rate = SAMPLE_RATE_8KHZ; + +static struct clk *codec_clk; +static int clk_users; + +static int msm8960_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static bool hs_detect_use_gpio; +module_param(hs_detect_use_gpio, bool, 0444); +MODULE_PARM_DESC(hs_detect_use_gpio, "Use GPIO for headset detection"); + +static bool hs_detect_use_firmware; +module_param(hs_detect_use_firmware, bool, 0444); +MODULE_PARM_DESC(hs_detect_use_firmware, "Use firmware for headset detection"); + +static int msm8960_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); +static bool msm8960_swap_gnd_mic(struct snd_soc_codec *codec); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm8960_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, + .swap_gnd_mic = NULL, +}; + +static u32 us_euro_sel_gpio = PM8921_GPIO_PM_TO_SYS(JACK_US_EURO_SEL_GPIO); + +static struct mutex cdc_mclk_mutex; + +static void msm8960_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + 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 (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm8960_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm8960_ext_bottom_spk_pamp |= spk; + + if ((msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm8960_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((msm8960_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm8960_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm8960_ext_top_spk_pamp |= spk; + + if ((msm8960_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm8960_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + msm8960_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm8960_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm8960_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm8960_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!msm8960_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm8960_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm8960_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + mutex_lock(&dapm->codec->mutex); + + 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 Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); + mutex_unlock(&dapm->codec->mutex); +} + +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)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm8960_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm8960_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm8960_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm8960_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm8960_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm8960_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm8960_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm8960_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm8960_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + int r = 0; + pr_debug("%s: enable = %d\n", __func__, enable); + + mutex_lock(&cdc_mclk_mutex); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 1) { + if (codec_clk) { + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Tabla MCLK\n", + __func__); + clk_users--; + r = -EINVAL; + } + } + } else { + if (clk_users > 0) { + clk_users--; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + tabla_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } else { + pr_err("%s: Error releasing Tabla MCLK\n", __func__); + r = -EINVAL; + } + } + mutex_unlock(&cdc_mclk_mutex); + return r; +} + +static bool msm8960_swap_gnd_mic(struct snd_soc_codec *codec) +{ + int value = gpio_get_value_cansleep(us_euro_sel_gpio); + pr_debug("%s: US EURO select switch %d to %d\n", __func__, value, + !value); + gpio_set_value_cansleep(us_euro_sel_gpio, !value); + return true; +} + +static int msm8960_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return msm8960_enable_codec_ext_clk(w->codec, 1, true); + case SND_SOC_DAPM_POST_PMD: + return msm8960_enable_codec_ext_clk(w->codec, 0, true); + } + return 0; +} + +static const struct snd_soc_dapm_widget msm8960_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm8960_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm8960_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm8960_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm8960_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm8960_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal1"}, + {"MIC BIAS1 Internal1", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Digital Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Front Bottom right Digital Mic on Fluid and MTP. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front top Digital Mic on Fluid. + * Digital Mic GM3 on CDP mainboard. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + + /* Tabla digital Mic6 - back bottom digital Mic on Liquid and + * bottom mic on CDP. FLUID/MTP do not have dmic6 installed. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm8960_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm8960_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static const char *auxpcm_rate_text[] = {"rate_8000", "rate_16000"}; +static const struct soc_enum msm8960_auxpcm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, auxpcm_rate_text), +}; + +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\n", __func__, + msm8960_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm8960_slim_0_rx_ch - 1; + 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] + 1; + + pr_debug("%s: msm8960_slim_0_rx_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\n", __func__, + msm8960_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm8960_slim_0_tx_ch - 1; + 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] + 1; + + pr_debug("%s: msm8960_slim_0_tx_ch = %d\n", __func__, + msm8960_slim_0_tx_ch); + return 1; +} + +static int msm8960_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_btsco_rate = %d", __func__, msm8960_btsco_rate); + ucontrol->value.integer.value[0] = msm8960_btsco_rate; + return 0; +} + +static int msm8960_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm8960_btsco_rate = SAMPLE_RATE_8KHZ; + break; + case 1: + msm8960_btsco_rate = SAMPLE_RATE_16KHZ; + break; + default: + msm8960_btsco_rate = SAMPLE_RATE_8KHZ; + break; + } + pr_debug("%s: msm8960_btsco_rate = %d\n", __func__, msm8960_btsco_rate); + return 0; +} + +static int msm8960_auxpcm_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_auxpcm_rate = %d", __func__, + msm8960_auxpcm_rate); + ucontrol->value.integer.value[0] = msm8960_auxpcm_rate; + return 0; +} + +static int msm8960_auxpcm_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm8960_auxpcm_rate = SAMPLE_RATE_8KHZ; + break; + case 1: + msm8960_auxpcm_rate = SAMPLE_RATE_16KHZ; + break; + default: + msm8960_auxpcm_rate = SAMPLE_RATE_8KHZ; + break; + } + pr_debug("%s: msm8960_auxpcm_rate = %d" + "ucontrol->value.integer.value[0] = %d\n", __func__, + msm8960_auxpcm_rate, + (int)ucontrol->value.integer.value[0]); + return 0; +} + +static const struct snd_kcontrol_new tabla_msm8960_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8960_enum[0], msm8960_get_spk, + msm8960_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm8960_enum[1], + msm8960_slim_0_rx_ch_get, msm8960_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm8960_enum[2], + msm8960_slim_0_tx_ch_get, msm8960_slim_0_tx_ch_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm8960_btsco_enum[0], + msm8960_btsco_rate_get, msm8960_btsco_rate_put), + SOC_ENUM_EXT("AUX PCM SampleRate", msm8960_auxpcm_enum[0], + msm8960_auxpcm_rate_get, msm8960_auxpcm_rate_put), +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 2400); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 20; + btn_low[1] = 21; + btn_high[1] = 62; + btn_low[2] = 63; + btn_high[2] = 104; + btn_low[3] = 105; + btn_high[3] = 143; + btn_low[4] = 144; + btn_high[4] = 181; + btn_low[5] = 182; + btn_high[5] = 218; + btn_low[6] = 219; + btn_high[6] = 254; + btn_low[7] = 255; + btn_high[7] = 330; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 80; + n_ready[1] = 68; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + unsigned int user_set_tx_ch = 0; + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + pr_debug("%s: rx_0_ch=%d\n", __func__, msm8960_slim_0_rx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm8960_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm8960_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + + if (codec_dai->id == 2) + user_set_tx_ch = msm8960_slim_0_tx_ch; + else if (codec_dai->id == 4) + user_set_tx_ch = params_channels(params); + + pr_debug("%s: %s_tx_dai_id_%d_ch=%d\n", __func__, + codec_dai->name, codec_dai->id, user_set_tx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + user_set_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + user_set_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + + } +end: + return ret; +} + +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; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct pm_gpio jack_gpio_cfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + }; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + if (machine_is_msm8960_liquid()) { + top_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(19)); + bottom_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(18)); + } + + snd_soc_dapm_new_controls(dapm, msm8960_dapm_widgets, + ARRAY_SIZE(msm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | + SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + if (machine_is_msm8960_cdp()) + mbhc_cfg.swap_gnd_mic = msm8960_swap_gnd_mic; + + if (hs_detect_use_gpio) { + mbhc_cfg.gpio = PM8921_GPIO_PM_TO_SYS(JACK_DETECT_GPIO); + mbhc_cfg.gpio_irq = JACK_DETECT_INT; + } + + if (mbhc_cfg.gpio) { + err = pm8xxx_gpio_config(mbhc_cfg.gpio, &jack_gpio_cfg); + if (err) { + pr_err("%s: pm8xxx_gpio_config JACK_DETECT failed %d\n", + __func__, err); + return err; + } + } + + mbhc_cfg.read_fw_bin = hs_detect_use_firmware; + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +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_hdmi_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 channels->min %u channels->max %u ()\n", __func__, + channels->min, channels->max); + + rate->min = rate->max = 48000; + + return 0; +} + +static int msm8960_btsco_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); + + rate->min = rate->max = msm8960_btsco_rate; + channels->min = channels->max = msm8960_btsco_ch; + + return 0; +} +static int msm8960_auxpcm_be_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); + + rate->min = rate->max = msm8960_auxpcm_rate; + /* PCM only supports mono output */ + channels->min = channels->max = 1; + + return 0; +} +static int msm8960_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int msm8960_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} +static int msm8960_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static int msm8960_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm8960_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return 0; +} + +static void msm8960_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm8960_aux_pcm_free_gpios(); +} + +static void msm8960_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops msm8960_be_ops = { + .startup = msm8960_startup, + .hw_params = msm8960_hw_params, + .shutdown = msm8960_shutdown, +}; + +static struct snd_soc_ops msm8960_auxpcm_be_ops = { + .startup = msm8960_auxpcm_startup, + .shutdown = msm8960_auxpcm_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8960_dai_common[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-multi-ch-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .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, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM8960 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "VoLTE", + .stream_name = "VoLTE", + .cpu_dai_name = "VoLTE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .be_id = MSM_FRONTEND_DAI_VOLTE, + }, + /* 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, + .be_hw_params_fixup = msm8960_btsco_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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, + .be_hw_params_fixup = msm8960_btsco_be_hw_params_fixup, + }, + { + .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, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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-hdmi.8", + .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_HDMI_RX, + .be_hw_params_fixup = msm8960_hdmi_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .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_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .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_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .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_AUXPCM_RX, + .be_hw_params_fixup = msm8960_auxpcm_be_params_fixup, + .ops = &msm8960_auxpcm_be_ops, + .ignore_pmdown_time = 1, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .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_AUXPCM_TX, + .be_hw_params_fixup = msm8960_auxpcm_be_params_fixup, + }, + /* Incall Music BACK END DAI Link */ + { + .name = LPASS_BE_VOICE_PLAYBACK_TX, + .stream_name = "Voice Farend Playback", + .cpu_dai_name = "msm-dai-q6.32773", + .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_VOICE_PLAYBACK_TX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + /* Incall Record Uplink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_TX, + .stream_name = "Voice Uplink Capture", + .cpu_dai_name = "msm-dai-q6.32772", + .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_INCALL_RECORD_TX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + /* Incall Record Downlink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_RX, + .stream_name = "Voice Downlink Capture", + .cpu_dai_name = "msm-dai-q6.32771", + .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_INCALL_RECORD_RX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, +}; + +static struct snd_soc_dai_link msm8960_dai_delta_tabla1x[] = { + /* 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 = "tabla1x_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, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla1x_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, + }, + { + .name = "SLIMBUS_2 Hostless", + .stream_name = "SLIMBUS_2 Hostless", + .cpu_dai_name = "msm-dai-q6.16389", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla1x_codec", + .codec_dai_name = "tabla_tx2", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm8960_be_ops, + }, +}; + + +static struct snd_soc_dai_link msm8960_dai_delta_tabla2x[] = { + /* 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, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .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, + }, + { + .name = "SLIMBUS_2 Hostless", + .stream_name = "SLIMBUS_2 Hostless", + .cpu_dai_name = "msm-dai-q6.16389", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx2", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm8960_be_ops, + }, +}; + +static struct snd_soc_dai_link msm8960_tabla1x_dai[ + ARRAY_SIZE(msm8960_dai_common) + + ARRAY_SIZE(msm8960_dai_delta_tabla1x)]; + + +static struct snd_soc_dai_link msm8960_dai[ + ARRAY_SIZE(msm8960_dai_common) + + ARRAY_SIZE(msm8960_dai_delta_tabla2x)]; + +static struct snd_soc_card snd_soc_tabla1x_card_msm8960 = { + .name = "msm8960-tabla1x-snd-card", + .dai_link = msm8960_tabla1x_dai, + .num_links = ARRAY_SIZE(msm8960_tabla1x_dai), + .controls = tabla_msm8960_controls, + .num_controls = ARRAY_SIZE(tabla_msm8960_controls), +}; + +static struct snd_soc_card snd_soc_card_msm8960 = { + .name = "msm8960-snd-card", + .dai_link = msm8960_dai, + .num_links = ARRAY_SIZE(msm8960_dai), + .controls = tabla_msm8960_controls, + .num_controls = ARRAY_SIZE(tabla_msm8960_controls), +}; + +static struct platform_device *msm8960_snd_device; +static struct platform_device *msm8960_snd_tabla1x_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(us_euro_sel_gpio, "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + us_euro_sel_gpio); + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(us_euro_sel_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + us_euro_sel_gpio); + else + gpio_direction_output(us_euro_sel_gpio, 0); + + 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(us_euro_sel_gpio); + } +} + +static int __init msm8960_audio_init(void) +{ + int ret; + + if (!cpu_is_msm8960()) { + pr_debug("%s: Not the right machine type\n", __func__); + return -ENODEV ; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm8960_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm8960_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + memcpy(msm8960_dai, msm8960_dai_common, sizeof(msm8960_dai_common)); + memcpy(msm8960_dai + ARRAY_SIZE(msm8960_dai_common), + msm8960_dai_delta_tabla2x, sizeof(msm8960_dai_delta_tabla2x)); + + 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); + kfree(mbhc_cfg.calibration); + return ret; + } + + msm8960_snd_tabla1x_device = platform_device_alloc("soc-audio", 1); + if (!msm8960_snd_tabla1x_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + memcpy(msm8960_tabla1x_dai, msm8960_dai_common, + sizeof(msm8960_dai_common)); + memcpy(msm8960_tabla1x_dai + ARRAY_SIZE(msm8960_dai_common), + msm8960_dai_delta_tabla1x, sizeof(msm8960_dai_delta_tabla1x)); + + platform_set_drvdata(msm8960_snd_tabla1x_device, + &snd_soc_tabla1x_card_msm8960); + ret = platform_device_add(msm8960_snd_tabla1x_device); + if (ret) { + platform_device_put(msm8960_snd_tabla1x_device); + kfree(mbhc_cfg.calibration); + 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; + + mutex_init(&cdc_mclk_mutex); + return ret; + +} +module_init(msm8960_audio_init); + +static void __exit msm8960_audio_exit(void) +{ + if (!cpu_is_msm8960()) { + pr_debug("%s: Not the right machine type\n", __func__); + return ; + } + msm8960_free_headset_mic_gpios(); + platform_device_unregister(msm8960_snd_device); + platform_device_unregister(msm8960_snd_tabla1x_device); + kfree(mbhc_cfg.calibration); + mutex_destroy(&cdc_mclk_mutex); +} +module_exit(msm8960_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8960"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8974.c b/sound/soc/msm/msm8974.c new file mode 100644 index 0000000000000000000000000000000000000000..d47910b940ef599039127b040845bfc47fed2959 --- /dev/null +++ b/sound/soc/msm/msm8974.c @@ -0,0 +1,752 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "../codecs/wcd9310.h" + +/* 8974 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MSM8974_SPK_ON 1 +#define MSM8974_SPK_OFF 0 + +#define MSM_SLIM_0_RX_MAX_CHANNELS 2 +#define MSM_SLIM_0_TX_MAX_CHANNELS 4 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define GPIO_AUX_PCM_DOUT 43 +#define GPIO_AUX_PCM_DIN 44 +#define GPIO_AUX_PCM_SYNC 45 +#define GPIO_AUX_PCM_CLK 46 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +/* Shared channel numbers for Slimbus ports that connect APQ to MDM. */ +enum { + SLIM_1_RX_1 = 145, /* BT-SCO and USB TX */ + SLIM_1_TX_1 = 146, /* BT-SCO and USB RX */ + SLIM_2_RX_1 = 147, /* HDMI RX */ + SLIM_3_RX_1 = 148, /* In-call recording RX */ + SLIM_3_RX_2 = 149, /* In-call recording RX */ + SLIM_4_TX_1 = 150, /* In-call musid delivery TX */ +}; + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm_spk_control; +static int msm_ext_bottom_spk_pamp; +static int msm_ext_top_spk_pamp; +static int msm_slim_0_rx_ch = 1; +static int msm_slim_0_tx_ch = 1; + +static int msm_btsco_rate = BTSCO_RATE_8KHZ; +static int msm_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, /* MBHC GPIO is not configured */ + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void msm_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + 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 (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_bottom_spk_pamp |= spk; + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_top_spk_pamp |= spk; + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!msm_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + if (msm_spk_control == MSM8974_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + ucontrol->value.integer.value[0] = msm_spk_control; + return 0; +} +static int msm_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 (msm_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm_spk_control = ucontrol->value.integer.value[0]; + msm_ext_control(codec); + return 1; +} +static int msm_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)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + return 0; +} + +static int msm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return 0; +} + +static const struct snd_soc_dapm_widget msm_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal1"}, + {"MIC BIAS1 Internal1", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Digital Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Front Bottom right Digital Mic on Fluid and MTP. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front top Digital Mic on Fluid. + * Digital Mic GM3 on CDP mainboard. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + + /* Tabla digital Mic6 - back bottom digital Mic on Liquid and + * bottom mic on CDP. FLUID/MTP do not have dmic6 installed. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int msm_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_rx_ch - 1; + return 0; +} + +static int msm_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + return 1; +} + +static int msm_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_tx_ch - 1; + return 0; +} + +static int msm_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + return 1; +} + +static int msm_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_btsco_rate = %d", __func__, + msm_btsco_rate); + ucontrol->value.integer.value[0] = msm_btsco_rate; + return 0; +} + +static int msm_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + msm_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: msm_btsco_rate = %d\n", __func__, + msm_btsco_rate); + return 0; +} + +static const struct snd_kcontrol_new tabla_msm_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm_enum[0], msm_get_spk, + msm_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm_enum[1], + msm_slim_0_rx_ch_get, msm_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm_enum[2], + msm_slim_0_tx_ch_get, msm_slim_0_tx_ch_put), +}; + +static const struct snd_kcontrol_new int_btsco_rate_mixer_controls[] = { + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm_btsco_enum[0], + msm_btsco_rate_get, msm_btsco_rate_put), +}; + +static struct snd_soc_dsp_link lpa_fe_media = { + .playback = true, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; +static struct snd_soc_dsp_link fe_media = { + .playback = true, + .capture = true, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; +static int msm_auxpcm_be_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); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} +static int msm_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} +static int msm_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} + +static int msm_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return ret; +} + +static void msm_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm_aux_pcm_free_gpios(); +} +static struct snd_soc_ops msm_auxpcm_be_ops = { + .startup = msm_auxpcm_startup, + .shutdown = msm_auxpcm_shutdown, +}; +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8974 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 = "MSM8974 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, + }, + + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.4106", + .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_AUXPCM_RX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + .ops = &msm_auxpcm_be_ops, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.4107", + .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_AUXPCM_TX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + }, + +}; + +struct snd_soc_card snd_soc_card_msm = { + .name = "msm8974-taiko-snd-card", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static struct platform_device *msm_snd_device; + +static void msm_free_headset_mic_gpios(void) +{ + if (msm_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm_audio_init(void) +{ + int ret = 0; + if (!machine_is_copper_sim()) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV; + } + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + if (!machine_is_copper_sim()) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm_free_headset_mic_gpios(); + platform_device_unregister(msm_snd_device); + kfree(mbhc_cfg.calibration); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC msm"); +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 0000000000000000000000000000000000000000..8130f071443fbfa49c78a669f7e6db59d9be6009 --- /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 0000000000000000000000000000000000000000..6f5ad32d63125230f22ff17adb7b6ea06c7a6e15 --- /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_cmd(prtd->audio_client, CMD_CLOSE); + 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__); + 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_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, + substream->number); + 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 0000000000000000000000000000000000000000..d7c9ad8205271343e6b4db8a43dbc1781fd7556d --- /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 0000000000000000000000000000000000000000..48ce610f9d9c649017299ee68313f01750ed6955 --- /dev/null +++ b/sound/soc/msm/msm8x60.c @@ -0,0 +1,1231 @@ +/* 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 + +#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 = 2; /* start, session_id */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_v_call_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 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]; + u32 session_id = ucontrol->value.integer.value[1]; + + if ((session_id != 0) && + ((session_id < SESSION_ID_BASE) || + (session_id >= SESSION_ID_BASE + MAX_VOC_SESSIONS))) { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + if (start) + broadcast_event(AUDDEV_EVT_START_VOICE, DEVICE_IGNORE, + session_id); + else + broadcast_event(AUDDEV_EVT_END_VOICE, DEVICE_IGNORE, + session_id); + 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 = 3; /* dir, mute, session_id */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_v_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + ucontrol->value.integer.value[2] = 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]; + u32 session_id = ucontrol->value.integer.value[2]; + + if ((session_id != 0) && + ((session_id < SESSION_ID_BASE) || + (session_id >= SESSION_ID_BASE + MAX_VOC_SESSIONS))) { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + return msm_set_voice_mute(dir, mute, session_id); +} + +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 = 3; /* dir, volume, session_id */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_v_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + ucontrol->value.integer.value[2] = 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]; + u32 session_id = ucontrol->value.integer.value[2]; + + if ((session_id != 0) && + ((session_id < SESSION_ID_BASE) || + (session_id >= SESSION_ID_BASE + MAX_VOC_SESSIONS))) { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + return msm_set_voice_vol(dir, volume, session_id); +} + +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 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 rc = 0; + u16 gain = 0x2000; + + 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->capability & SNDDEV_CAP_TX)) { + rc = -EINVAL; + return rc; + } + if (mute) + gain = 0; + + pr_debug("%s:dev_name = %s dev_id = %d, gain = %hX\n", + __func__, dev_info->name, dev_id, gain); + rc = afe_apply_gain(dev_info->copp_id, gain); + if (rc < 0) { + pr_err("%s : device %s not able to set device gain " + "control.", __func__, dev_info->name); + return rc; + } + pr_debug("Muting/Unmuting device id %d(%s)\n", dev_id, dev_info->name); + + return rc; +} + +static int msm_voc_session_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 = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_voice_session_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voice_get_session_id("Voice session"); + return 0; +} + +static int msm_voip_session_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = voice_get_session_id("VoIP session"); + 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)) { + 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), + MSM_EXT("Device_Mute", msm_device_mute_info, + msm_device_mute_get, msm_device_mute_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), + MSM_EXT("VoiceVolume Ext", + msm_v_volume_info, msm_v_volume_get, msm_v_volume_put, 0), + MSM_EXT("VoiceMute Ext", + msm_v_mute_info, msm_v_mute_get, msm_v_mute_put, 0), + MSM_EXT("Voice Call Ext", + msm_v_call_info, msm_v_call_get, msm_v_call_put, 0), + MSM_EXT("Voice session", + msm_voc_session_info, msm_voice_session_get, NULL, 0), + MSM_EXT("VoIP session", + msm_voc_session_info, msm_voip_session_get, NULL, 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 0000000000000000000000000000000000000000..728621aebd58129338444927f9fa46eff1f9ea48 --- /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 0000000000000000000000000000000000000000..521c5e54be8112ed9fe98ce85de736ce66d7a64f --- /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 0000000000000000000000000000000000000000..f45bd3362b93b5d602e26a6c245ef4722333513b --- /dev/null +++ b/sound/soc/msm/qdsp6/Makefile @@ -0,0 +1,2 @@ +obj-y := q6asm.o q6adm.o q6afe.o +obj-$(CONFIG_SND_SOC_VOICE) += q6voice.o \ No newline at end of file diff --git a/sound/soc/msm/qdsp6/q6adm.c b/sound/soc/msm/qdsp6/q6adm.c new file mode 100644 index 0000000000000000000000000000000000000000..bf6f743a6fbfbfa8974be95871562e7181dd5dc7 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6adm.c @@ -0,0 +1,1088 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 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]; + wait_queue_head_t wait; +}; + +static struct acdb_cal_block mem_addr_audproc[MAX_AUDPROC_TYPES]; +static struct acdb_cal_block mem_addr_audvol[MAX_AUDPROC_TYPES]; + +static struct adm_ctl this_adm; + +int srs_trumedia_open(int port_id, int srs_tech_id, void *srs_params) +{ + struct asm_pp_params_command *open = NULL; + int ret = 0, sz = 0; + int index; + + pr_debug("SRS - %s", __func__); + switch (srs_tech_id) { + case SRS_ID_GLOBAL: { + struct srs_trumedia_params_GLOBAL *glb_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_GLOBAL); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_GLOBAL) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS; + open->params.param_size = + sizeof(struct srs_trumedia_params_GLOBAL); + glb_params = (struct srs_trumedia_params_GLOBAL *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(glb_params, srs_params, + sizeof(struct srs_trumedia_params_GLOBAL)); + pr_debug("SRS - %s: Global params - 1 = %x, 2 = %x, 3 = %x," + " 4 = %x, 5 = %x, 6 = %x, 7 = %x, 8 = %x\n", + __func__, (int)glb_params->v1, + (int)glb_params->v2, (int)glb_params->v3, + (int)glb_params->v4, (int)glb_params->v5, + (int)glb_params->v6, (int)glb_params->v7, + (int)glb_params->v8); + break; + } + case SRS_ID_WOWHD: { + struct srs_trumedia_params_WOWHD *whd_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_WOWHD); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_WOWHD) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_WOWHD; + open->params.param_size = + sizeof(struct srs_trumedia_params_WOWHD); + whd_params = (struct srs_trumedia_params_WOWHD *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(whd_params, srs_params, + sizeof(struct srs_trumedia_params_WOWHD)); + pr_debug("SRS - %s: WOWHD params - 1 = %x, 2 = %x, 3 = %x," + " 4 = %x, 5 = %x, 6 = %x, 7 = %x, 8 = %x, 9 = %x," + " 10 = %x, 11 = %x\n", __func__, (int)whd_params->v1, + (int)whd_params->v2, (int)whd_params->v3, + (int)whd_params->v4, (int)whd_params->v5, + (int)whd_params->v6, (int)whd_params->v7, + (int)whd_params->v8, (int)whd_params->v9, + (int)whd_params->v10, (int)whd_params->v11); + break; + } + case SRS_ID_CSHP: { + struct srs_trumedia_params_CSHP *chp_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_CSHP); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_CSHP) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_CSHP; + open->params.param_size = + sizeof(struct srs_trumedia_params_CSHP); + chp_params = (struct srs_trumedia_params_CSHP *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(chp_params, srs_params, + sizeof(struct srs_trumedia_params_CSHP)); + pr_debug("SRS - %s: CSHP params - 1 = %x, 2 = %x, 3 = %x," + " 4 = %x, 5 = %x, 6 = %x, 7 = %x, 8 = %x," + " 9 = %x\n", __func__, (int)chp_params->v1, + (int)chp_params->v2, (int)chp_params->v3, + (int)chp_params->v4, (int)chp_params->v5, + (int)chp_params->v6, (int)chp_params->v7, + (int)chp_params->v8, (int)chp_params->v9); + break; + } + case SRS_ID_HPF: { + struct srs_trumedia_params_HPF *hpf_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_HPF); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_HPF) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_HPF; + open->params.param_size = + sizeof(struct srs_trumedia_params_HPF); + hpf_params = (struct srs_trumedia_params_HPF *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(hpf_params, srs_params, + sizeof(struct srs_trumedia_params_HPF)); + pr_debug("SRS - %s: HPF params - 1 = %x\n", __func__, + (int)hpf_params->v1); + break; + } + case SRS_ID_PEQ: { + struct srs_trumedia_params_PEQ *peq_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_PEQ); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_PEQ) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_PEQ; + open->params.param_size = + sizeof(struct srs_trumedia_params_PEQ); + peq_params = (struct srs_trumedia_params_PEQ *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(peq_params, srs_params, + sizeof(struct srs_trumedia_params_PEQ)); + pr_debug("SRS - %s: PEQ params - 1 = %x 2 = %x, 3 = %x," + " 4 = %x\n", __func__, (int)peq_params->v1, + (int)peq_params->v2, (int)peq_params->v3, + (int)peq_params->v4); + break; + } + case SRS_ID_HL: { + struct srs_trumedia_params_HL *hl_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_HL); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_HL) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_HL; + open->params.param_size = sizeof(struct srs_trumedia_params_HL); + hl_params = (struct srs_trumedia_params_HL *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(hl_params, srs_params, + sizeof(struct srs_trumedia_params_HL)); + pr_debug("SRS - %s: HL params - 1 = %x, 2 = %x, 3 = %x, 4 = %x," + " 5 = %x, 6 = %x, 7 = %x\n", __func__, + (int)hl_params->v1, (int)hl_params->v2, + (int)hl_params->v3, (int)hl_params->v4, + (int)hl_params->v5, (int)hl_params->v6, + (int)hl_params->v7); + break; + } + default: + goto fail_cmd; + } + + open->payload = NULL; + open->params.module_id = SRS_TRUMEDIA_MODULE_ID; + open->params.reserved = 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 = sz; + 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; + index = afe_get_port_index(port_id); + open->hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + open->hdr.token = port_id; + open->hdr.opcode = ADM_CMD_SET_PARAMS; + pr_debug("SRS - %s: Command was sent now check Q6 - port id = %d," + " size %d, module id %x, param id %x.\n", __func__, + open->hdr.dest_port, open->payload_size, + open->params.module_id, open->params.param_id); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)open); + if (ret < 0) { + pr_err("SRS - %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, 1, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("SRS - %s: ADM open failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + +fail_cmd: + kfree(open); + return ret; +} + +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 (index < 0 || index >= AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, data->token); + return 0; + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + pr_debug("APR_BASIC_RSP_RESULT id %x\n", payload[0]); + switch (payload[0]) { + case ADM_CMD_SET_PARAMS: + if (rtac_make_adm_callback(payload, + data->payload_size)) + break; + 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: + case ADM_CMD_CONNECT_AFE_PORT: + 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: + case ADM_CMDRSP_MULTI_CHANNEL_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; + case ADM_CMDRSP_GET_PARAMS: + pr_debug("%s: ADM_CMDRSP_GET_PARAMS\n", __func__); + rtac_make_adm_callback(payload, + data->payload_size); + break; + default: + pr_err("%s: Unknown cmd:0x%x\n", __func__, + data->opcode); + break; + } + } + return 0; +} + +static int send_adm_cal_block(int port_id, struct acdb_cal_block *aud_cal) +{ + s32 result = 0; + struct adm_set_params_command adm_params; + int index = afe_get_port_index(port_id); + if (index < 0 || index >= AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d portid %d\n", + __func__, index, port_id); + return 0; + } + + pr_debug("%s: Port id %d, index %d\n", __func__, port_id, index); + + if (!aud_cal || aud_cal->cal_size == 0) { + pr_debug("%s: No ADM cal to send for port_id = %d!\n", + __func__, port_id); + result = -EINVAL; + 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); + result = -EINVAL; + 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); + result = -EINVAL; + goto done; + } + + result = 0; +done: + return result; +} + +static void send_adm_cal(int port_id, int path) +{ + int result = 0; + 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; + + pr_debug("%s: Sending audproc cal\n", __func__); + get_audproc_cal(acdb_path, &aud_cal); + + /* map & cache buffers used */ + if (((mem_addr_audproc[acdb_path].cal_paddr != aud_cal.cal_paddr) && + (aud_cal.cal_size > 0)) || + (aud_cal.cal_size > mem_addr_audproc[acdb_path].cal_size)) { + + if (mem_addr_audproc[acdb_path].cal_paddr != 0) + adm_memory_unmap_regions( + &mem_addr_audproc[acdb_path].cal_paddr, + &mem_addr_audproc[acdb_path].cal_size, 1); + + result = adm_memory_map_regions(&aud_cal.cal_paddr, 0, + &aud_cal.cal_size, 1); + if (result < 0) + pr_err("ADM audproc mmap did not work! path = %d, " + "addr = 0x%x, size = %d\n", acdb_path, + aud_cal.cal_paddr, aud_cal.cal_size); + else + mem_addr_audproc[acdb_path] = aud_cal; + } + + if (!send_adm_cal_block(port_id, &aud_cal)) + pr_debug("%s: Audproc cal sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); + else + pr_debug("%s: Audproc cal not sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); + + pr_debug("%s: Sending audvol cal\n", __func__); + get_audvol_cal(acdb_path, &aud_cal); + + /* map & cache buffers used */ + if (((mem_addr_audvol[acdb_path].cal_paddr != aud_cal.cal_paddr) && + (aud_cal.cal_size > 0)) || + (aud_cal.cal_size > mem_addr_audvol[acdb_path].cal_size)) { + if (mem_addr_audvol[acdb_path].cal_paddr != 0) + adm_memory_unmap_regions( + &mem_addr_audvol[acdb_path].cal_paddr, + &mem_addr_audvol[acdb_path].cal_size, 1); + + result = adm_memory_map_regions(&aud_cal.cal_paddr, 0, + &aud_cal.cal_size, 1); + if (result < 0) + pr_err("ADM audvol mmap did not work! path = %d, " + "addr = 0x%x, size = %d\n", acdb_path, + aud_cal.cal_paddr, aud_cal.cal_size); + else + mem_addr_audvol[acdb_path] = aud_cal; + } + + if (!send_adm_cal_block(port_id, &aud_cal)) + pr_debug("%s: Audvol cal sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); + else + pr_debug("%s: Audvol cal not sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); +} + +int adm_connect_afe_port(int mode, int session_id, int port_id) +{ + struct adm_cmd_connect_afe_port cmd; + int ret = 0; + int index; + + pr_debug("%s: port %d session id:%d mode:%d\n", __func__, + port_id, session_id, mode); + + port_id = afe_convert_virtual_to_portid(port_id); + + if (afe_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + 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; + } + rtac_set_adm_handle(this_adm.apr); + } + index = afe_get_port_index(port_id); + pr_debug("%s: Port ID %d, index %d\n", __func__, port_id, index); + + cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.src_svc = APR_SVC_ADM; + cmd.hdr.src_domain = APR_DOMAIN_APPS; + cmd.hdr.src_port = port_id; + cmd.hdr.dest_svc = APR_SVC_ADM; + cmd.hdr.dest_domain = APR_DOMAIN_ADSP; + cmd.hdr.dest_port = port_id; + cmd.hdr.token = port_id; + cmd.hdr.opcode = ADM_CMD_CONNECT_AFE_PORT; + + cmd.mode = mode; + cmd.session_id = session_id; + cmd.afe_port_id = port_id; + + atomic_set(&this_adm.copp_stat[index], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&cmd); + 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 connect AFE 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_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); + + port_id = afe_convert_virtual_to_portid(port_id); + + 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; + } + rtac_set_adm_handle(this_adm.apr); + } + + + /* 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; + + /* convert path to acdb path */ + if (path == ADM_PATH_PLAYBACK) + open.topology_id = get_adm_rx_topology(); + else { + open.topology_id = get_adm_tx_topology(); + if ((open.topology_id == + VPM_TX_SM_ECNS_COPP_TOPOLOGY) || + (open.topology_id == + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY)) + rate = 16000; + } + + 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_multi_ch_copp_open(int port_id, int path, int rate, int channel_mode, + int topology) +{ + struct adm_multi_ch_copp_open_command open; + int ret = 0; + int index; + + pr_debug("%s: port %d path:%d rate:%d channel :%d\n", __func__, + port_id, path, rate, channel_mode); + + port_id = afe_convert_virtual_to_portid(port_id); + + 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; + } + rtac_set_adm_handle(this_adm.apr); + } + + /* 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(struct adm_multi_ch_copp_open_command); + open.hdr.opcode = ADM_CMD_MULTI_CHANNEL_COPP_OPEN; + memset(open.dev_channel_mapping, 0, 8); + + if (channel_mode == 1) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FC; + } else if (channel_mode == 2) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channel_mode == 4) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + open.dev_channel_mapping[2] = PCM_CHANNEL_RB; + open.dev_channel_mapping[3] = PCM_CHANNEL_LB; + } else if (channel_mode == 6) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + open.dev_channel_mapping[2] = PCM_CHANNEL_LFE; + open.dev_channel_mapping[3] = PCM_CHANNEL_FC; + open.dev_channel_mapping[4] = PCM_CHANNEL_LB; + open.dev_channel_mapping[5] = PCM_CHANNEL_RB; + } else { + pr_err("%s invalid num_chan %d\n", __func__, + channel_mode); + return -EINVAL; + } + + + 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.mode = path; + open.endpoint_id1 = port_id; + open.endpoint_id2 = 0xFFFF; + + /* convert path to acdb path */ + if (path == ADM_PATH_PLAYBACK) + open.topology_id = get_adm_rx_topology(); + else { + open.topology_id = get_adm_tx_topology(); + if ((open.topology_id == + VPM_TX_SM_ECNS_COPP_TOPOLOGY) || + (open.topology_id == + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY)) + rate = 16000; + } + + 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); + if (index < 0 || index >= AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, copp_id); + return 0; + } + + 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; + port_id[i] = afe_convert_virtual_to_portid(port_id[i]); + + tmp = afe_get_port_index(port_id[i]); + + pr_debug("%s: port_id[%d]: %d, index: %d\n", __func__, i, + port_id[i], tmp); + + if (tmp >= 0 && tmp < AFE_MAX_PORTS) + 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); + + for (i = 0; i < num_copps; i++) + rtac_add_adm_device(port_id[i], atomic_read(&this_adm.copp_id + [afe_get_port_index(port_id[i])]), + path, session_id); + 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_debug("%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; + } + rtac_set_adm_handle(this_adm.apr); + } + + 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_debug("%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; +} + +int adm_get_copp_id(int port_index) +{ + pr_debug("%s\n", __func__); + + if (port_index < 0) { + pr_err("%s: invalid port_id = %d\n", __func__, port_index); + return -EINVAL; + } + + return atomic_read(&this_adm.copp_id[port_index]); +} + +int adm_close(int port_id) +{ + struct apr_hdr close; + + int ret = 0; + int index = 0; + + port_id = afe_convert_virtual_to_portid(port_id); + + index = afe_get_port_index(port_id); + if (afe_validate_port(port_id) < 0) + return -EINVAL; + + pr_debug("%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; + } + + rtac_remove_adm_device(port_id); + } + +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 0000000000000000000000000000000000000000..51ef359d7a4187142b03ed83697c3a8900ed3ae2 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6afe.c @@ -0,0 +1,1726 @@ +/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 afe_ctl { + void *apr; + atomic_t state; + atomic_t status; + wait_queue_head_t wait; + struct task_struct *task; + void (*tx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void (*rx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void *tx_private_data; + void *rx_private_data; +}; + +static struct afe_ctl this_afe; + +static struct acdb_cal_block afe_cal_addr[MAX_AUDPROC_TYPES]; + +#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); + return 0; + } + if (data->payload_size) { + uint32_t *payload; + uint16_t port_id = 0; + payload = data->payload; + pr_debug("%s:opcode = 0x%x cmd = 0x%x status = 0x%x\n", + __func__, data->opcode, + 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_I2S_CONFIG: + case AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG: + case AFE_PORT_AUDIO_SLIM_SCH_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: + case AFE_PORT_CMD_APPLY_GAIN: + case AFE_SERVICE_CMD_MEMORY_MAP: + case AFE_SERVICE_CMD_MEMORY_UNMAP: + case AFE_SERVICE_CMD_UNREG_RTPORT: + atomic_set(&this_afe.state, 0); + wake_up(&this_afe.wait); + break; + case AFE_SERVICE_CMD_REG_RTPORT: + break; + case AFE_SERVICE_CMD_RTPORT_WR: + port_id = RT_PROXY_PORT_001_TX; + break; + case AFE_SERVICE_CMD_RTPORT_RD: + port_id = RT_PROXY_PORT_001_RX; + break; + default: + pr_err("Unknown cmd 0x%x\n", + payload[0]); + break; + } + } else if (data->opcode == AFE_EVENT_RT_PROXY_PORT_STATUS) { + port_id = (uint16_t)(0x0000FFFF & payload[0]); + } + pr_debug("%s:port_id = %x\n", __func__, port_id); + switch (port_id) { + case RT_PROXY_PORT_001_TX: { + if (this_afe.tx_cb) { + this_afe.tx_cb(data->opcode, data->token, + data->payload, + this_afe.tx_private_data); + } + break; + } + case RT_PROXY_PORT_001_RX: { + if (this_afe.rx_cb) { + this_afe.rx_cb(data->opcode, data->token, + data->payload, + this_afe.rx_private_data); + } + break; + } + default: + break; + } + } + return 0; +} + +int afe_get_port_type(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PCM_RX: + case SECONDARY_I2S_RX: + case MI2S_RX: + case HDMI_RX: + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_2_RX: + case SLIMBUS_3_RX: + case INT_BT_SCO_RX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case VOICE_PLAYBACK_TX: + case RT_PROXY_PORT_001_RX: + case SLIMBUS_4_RX: + ret = MSM_AFE_PORT_TYPE_RX; + break; + + case PRIMARY_I2S_TX: + case PCM_TX: + case SECONDARY_I2S_TX: + case MI2S_TX: + case DIGI_MIC_TX: + case VOICE_RECORD_TX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case SLIMBUS_2_TX: + case INT_FM_TX: + case VOICE_RECORD_RX: + case INT_BT_SCO_TX: + case RT_PROXY_PORT_001_TX: + case SLIMBUS_4_TX: + ret = MSM_AFE_PORT_TYPE_TX; + break; + + default: + pr_err("%s: invalid port id %d\n", __func__, port_id); + ret = -EINVAL; + } + + return ret; +} + +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 SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case INT_FM_TX: + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + { + ret = 0; + break; + } + + default: + ret = -EINVAL; + } + + return ret; +} + +int afe_convert_virtual_to_portid(u16 port_id) +{ + int ret; + + /* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ + if (afe_validate_port(port_id) < 0) { + if (port_id == RT_PROXY_DAI_001_RX || + port_id == RT_PROXY_DAI_001_TX || + port_id == RT_PROXY_DAI_002_RX || + port_id == RT_PROXY_DAI_002_TX) + ret = VIRTUAL_ID_TO_PORTID(port_id); + else + ret = -EINVAL; + } else + ret = port_id; + + 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 SLIMBUS_1_RX: return IDX_SLIMBUS_1_RX; + case SLIMBUS_1_TX: return IDX_SLIMBUS_1_TX; + case SLIMBUS_2_RX: return IDX_SLIMBUS_2_RX; + case SLIMBUS_2_TX: return IDX_SLIMBUS_2_TX; + case SLIMBUS_3_RX: return IDX_SLIMBUS_3_RX; + 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; + case RT_PROXY_PORT_001_RX: return IDX_RT_PROXY_PORT_001_RX; + case RT_PROXY_PORT_001_TX: return IDX_RT_PROXY_PORT_001_TX; + case SLIMBUS_4_RX: return IDX_SLIMBUS_4_RX; + case SLIMBUS_4_TX: return IDX_SLIMBUS_4_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_multi_ch_cfg); + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_slimbus_sch_cfg); + break; + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_rtproxy_cfg); + break; + case PCM_RX: + case PCM_TX: + default: + ret_size = SIZEOF_CFG_CMD(afe_port_pcm_cfg); + break; + } + return ret_size; +} + +int afe_q6_interface_prepare(void) +{ + int ret = 0; + + pr_debug("%s:", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + } + } + return ret; +} + +static void afe_send_cal_block(int32_t path, u16 port_id) +{ + int result = 0; + struct acdb_cal_block cal_block; + struct afe_port_cmd_set_param_no_payload afe_cal; + pr_debug("%s: path %d\n", __func__, path); + + get_afe_cal(path, &cal_block); + if (cal_block.cal_size <= 0) { + pr_debug("%s: No AFE cal to send!\n", __func__); + goto done; + } + + if ((afe_cal_addr[path].cal_paddr != cal_block.cal_paddr) || + (cal_block.cal_size > afe_cal_addr[path].cal_size)) { + if (afe_cal_addr[path].cal_paddr != 0) + afe_cmd_memory_unmap_nowait( + afe_cal_addr[path].cal_paddr); + + afe_cmd_memory_map_nowait(cal_block.cal_paddr, + cal_block.cal_size); + afe_cal_addr[path].cal_paddr = cal_block.cal_paddr; + afe_cal_addr[path].cal_size = cal_block.cal_size; + } + + afe_cal.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afe_cal.hdr.pkt_size = sizeof(afe_cal); + afe_cal.hdr.src_port = 0; + afe_cal.hdr.dest_port = 0; + afe_cal.hdr.token = 0; + afe_cal.hdr.opcode = AFE_PORT_CMD_SET_PARAM; + afe_cal.port_id = port_id; + afe_cal.payload_size = cal_block.cal_size; + afe_cal.payload_address = cal_block.cal_paddr; + + pr_debug("%s: AFE cal sent for device port = %d, path = %d, " + "cal size = %d, cal addr = 0x%x\n", __func__, + port_id, path, cal_block.cal_size, cal_block.cal_paddr); + + result = apr_send_pkt(this_afe.apr, (uint32_t *) &afe_cal); + if (result < 0) { + pr_err("%s: AFE cal for port %d failed\n", + __func__, port_id); + } + + pr_debug("%s: AFE cal sent for path %d device!\n", __func__, path); +done: + return; +} + +void afe_send_cal(u16 port_id) +{ + pr_debug("%s\n", __func__); + + if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_TX) + afe_send_cal_block(TX_CAL, port_id); + else if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_RX) + afe_send_cal_block(RX_CAL, port_id); +} + +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_debug("%s: %d %d\n", __func__, port_id, rate); + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE APR is not registered\n", __func__); + ret = -ENODEV; + return ret; + } + + if (port_id == HDMI_RX) { + 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_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG; + } else { + + 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; + switch (port_id) { + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + config.hdr.opcode = AFE_PORT_AUDIO_SLIM_SCH_CONFIG; + break; + case MI2S_TX: + case MI2S_RX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + /* AFE_PORT_CMD_I2S_CONFIG command is not supported + * in the LPASS EL 1.0. So we have to distiguish + * which AFE command, AFE_PORT_CMD_I2S_CONFIG or + * AFE_PORT_AUDIO_IF_CONFIG to use. If the format + * is L-PCM, the AFE_PORT_AUDIO_IF_CONFIG is used + * to make the backward compatible. + */ + pr_debug("%s: afe_config->mi2s.format = %d\n", __func__, + afe_config->mi2s.format); + if (afe_config->mi2s.format == MSM_AFE_I2S_FORMAT_LPCM) + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + else + config.hdr.opcode = AFE_PORT_CMD_I2S_CONFIG; + break; + default: + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + break; + } + } + + 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; + } + + /* send AFE cal */ + afe_send_cal(port_id); + + 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_debug("%s: %d %d\n", __func__, port_id, rate); + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + 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; + switch (port_id) { + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + config.hdr.opcode = AFE_PORT_AUDIO_SLIM_SCH_CONFIG; + break; + case MI2S_TX: + case MI2S_RX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + /* AFE_PORT_CMD_I2S_CONFIG command is not supported + * in the LPASS EL 1.0. So we have to distiguish + * which AFE command, AFE_PORT_CMD_I2S_CONFIG or + * AFE_PORT_AUDIO_IF_CONFIG to use. If the format + * is L-PCM, the AFE_PORT_AUDIO_IF_CONFIG is used + * to make the backward compatible. + */ + pr_debug("%s: afe_config->mi2s.format = %d\n", __func__, + afe_config->mi2s.format); + if (afe_config->mi2s.format == MSM_AFE_I2S_FORMAT_LPCM) + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + else + config.hdr.opcode = AFE_PORT_CMD_I2S_CONFIG; + break; + default: + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + break; + } + + 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 dst_port, u16 src_port) +{ + struct afe_loopback_command lb_cmd; + int ret = 0; + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + if ((afe_get_port_type(dst_port) == MSM_AFE_PORT_TYPE_RX) && + (afe_get_port_type(src_port) == MSM_AFE_PORT_TYPE_RX)) + return afe_loopback_cfg(enable, dst_port, src_port, + LB_MODE_EC_REF_VOICE_AUDIO); + + 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 = src_port; + lb_cmd.rx_port_id = dst_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_cfg(u16 enable, u16 dst_port, u16 src_port, u16 mode) +{ + struct afe_port_cmd_set_param lp_cfg; + int ret = 0; + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + pr_debug("%s: src_port %d, dst_port %d\n", + __func__, src_port, dst_port); + + lp_cfg.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + lp_cfg.hdr.pkt_size = sizeof(lp_cfg); + lp_cfg.hdr.src_port = 0; + lp_cfg.hdr.dest_port = 0; + lp_cfg.hdr.token = 0; + lp_cfg.hdr.opcode = AFE_PORT_CMD_SET_PARAM; + + lp_cfg.port_id = src_port; + lp_cfg.payload_size = sizeof(struct afe_param_payload); + lp_cfg.payload_address = 0; + + lp_cfg.payload.module_id = AFE_MODULE_LOOPBACK; + lp_cfg.payload.param_id = AFE_PARAM_ID_LOOPBACK_CONFIG; + lp_cfg.payload.param_size = sizeof(struct afe_param_loopback_cfg); + lp_cfg.payload.reserved = 0; + + lp_cfg.payload.param.loopback_cfg.loopback_cfg_minor_version = + AFE_API_VERSION_LOOPBACK_CONFIG; + lp_cfg.payload.param.loopback_cfg.dst_port_id = dst_port; + lp_cfg.payload.param.loopback_cfg.routing_mode = mode; + lp_cfg.payload.param.loopback_cfg.enable = enable; + lp_cfg.payload.param.loopback_cfg.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &lp_cfg); + if (ret < 0) { + pr_err("%s: AFE loopback config failed for src_port %d, dst_port %d\n", + __func__, src_port, dst_port); + 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_loopback_gain(u16 port_id, u16 volume) +{ + struct afe_port_cmd_set_param set_param; + int ret = 0; + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + 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_apply_gain(u16 port_id, u16 gain) +{ + struct afe_port_gain_command set_gain; + 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 apply 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, gain); + + set_gain.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + set_gain.hdr.pkt_size = sizeof(set_gain); + set_gain.hdr.src_port = 0; + set_gain.hdr.dest_port = 0; + set_gain.hdr.token = 0; + set_gain.hdr.opcode = AFE_PORT_CMD_APPLY_GAIN; + + set_gain.port_id = port_id; + set_gain.gain = gain; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &set_gain); + if (ret < 0) { + pr_err("%s: AFE Gain 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_pseudo_port_start_nowait(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + if (this_afe.apr == NULL) { + pr_err("%s: AFE APR is not registered\n", __func__); + return -ENODEV; + } + + + 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); + return -EINVAL; + } + return 0; +} + +int afe_start_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + 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); + return -EINVAL; + } + + 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__); + return -EINVAL; + } + + return 0; +} + +int afe_pseudo_port_stop_nowait(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + + 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); + return -EINVAL; + } + + return 0; + +} + +int afe_stop_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + + 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); + return -EINVAL; + } + + 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__); + return -EINVAL; + } + + return 0; +} + +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + struct afe_cmd_memory_map mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_MAP; + mregion.phy_addr = dma_addr_p; + mregion.mem_sz = dma_buf_sz; + mregion.mem_id = 0; + mregion.rsvd = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory map cmd 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; +} + +int afe_cmd_memory_map_nowait(u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + struct afe_cmd_memory_map mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_MAP; + mregion.phy_addr = dma_addr_p; + mregion.mem_sz = dma_buf_sz; + mregion.mem_id = 0; + mregion.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + } + return 0; +} + +int afe_cmd_memory_unmap(u32 dma_addr_p) +{ + int ret = 0; + struct afe_cmd_memory_unmap mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_UNMAP; + mregion.phy_addr = dma_addr_p; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd 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; +} + +int afe_cmd_memory_unmap_nowait(u32 dma_addr_p) +{ + int ret = 0; + struct afe_cmd_memory_unmap mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_UNMAP; + mregion.phy_addr = dma_addr_p; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + } + return 0; +} + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data) +{ + int ret = 0; + struct afe_cmd_reg_rtport rtproxy; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = cb; + this_afe.tx_private_data = private_data; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = cb; + this_afe.rx_private_data = private_data; + } + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 1; + rtproxy.hdr.dest_port = 1; + rtproxy.hdr.token = 0; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_REG_RTPORT; + rtproxy.port_id = port_id; + rtproxy.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE reg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_unregister_get_events(u16 port_id) +{ + int ret = 0; + struct afe_cmd_unreg_rtport rtproxy; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 0; + rtproxy.hdr.dest_port = 0; + rtproxy.hdr.token = 0; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_UNREG_RTPORT; + rtproxy.port_id = port_id; + rtproxy.rsvd = 0; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = NULL; + this_afe.tx_private_data = NULL; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = NULL; + this_afe.rx_private_data = NULL; + } + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE enable Unreg. rtproxy_event 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; +} + +int afe_rt_proxy_port_write(u32 buf_addr_p, int bytes) +{ + int ret = 0; + struct afe_cmd_rtport_wr afecmd_wr; + + if (this_afe.apr == NULL) { + pr_err("%s:register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_wr.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_wr.hdr.pkt_size = sizeof(afecmd_wr); + afecmd_wr.hdr.src_port = 0; + afecmd_wr.hdr.dest_port = 0; + afecmd_wr.hdr.token = 0; + afecmd_wr.hdr.opcode = AFE_SERVICE_CMD_RTPORT_WR; + afecmd_wr.buf_addr = (uint32_t)buf_addr_p; + afecmd_wr.port_id = RT_PROXY_PORT_001_TX; + afecmd_wr.bytes_avail = bytes; + afecmd_wr.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_wr); + if (ret < 0) { + pr_err("%s: AFE rtproxy write to port 0x%x failed %d\n", + __func__, afecmd_wr.port_id, ret); + ret = -EINVAL; + return ret; + } + return 0; + +} + +int afe_rt_proxy_port_read(u32 buf_addr_p, int bytes) +{ + int ret = 0; + struct afe_cmd_rtport_rd afecmd_rd; + + if (this_afe.apr == NULL) { + pr_err("%s: register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_rd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_rd.hdr.pkt_size = sizeof(afecmd_rd); + afecmd_rd.hdr.src_port = 0; + afecmd_rd.hdr.dest_port = 0; + afecmd_rd.hdr.token = 0; + afecmd_rd.hdr.opcode = AFE_SERVICE_CMD_RTPORT_RD; + afecmd_rd.buf_addr = (uint32_t)buf_addr_p; + afecmd_rd.port_id = RT_PROXY_PORT_001_RX; + afecmd_rd.bytes_avail = bytes; + afecmd_rd.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_rd); + if (ret < 0) { + pr_err("%s: AFE rtproxy read cmd to port 0x%x failed %d\n", + __func__, afecmd_rd.port_id, ret); + 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_debug("%s: port_id=%d\n", __func__, port_id); + port_id = afe_convert_virtual_to_portid(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_debug("%s: port_id=%d\n", __func__, port_id); + port_id = afe_convert_virtual_to_portid(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) +{ + int i; +#ifdef CONFIG_DEBUG_FS + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); + if (debugfs_afelb_gain) + debugfs_remove(debugfs_afelb_gain); +#endif + for (i = 0; i < MAX_AUDPROC_TYPES; i++) { + if (afe_cal_addr[i].cal_paddr != 0) + afe_cmd_memory_unmap_nowait( + afe_cal_addr[i].cal_paddr); + } +} + +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 0000000000000000000000000000000000000000..09bfd941ef38494e3b5e8c572dcc6c106e38b13d --- /dev/null +++ b/sound/soc/msm/qdsp6/q6asm.c @@ -0,0 +1,3533 @@ + +/* + * Copyright (c) 2010-2012, 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 + + +#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 +#ifdef CONFIG_DEBUG_FS +#define OUT_BUFFER_SIZE 56 +#define IN_BUFFER_SIZE 24 +#endif +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); + +#ifdef CONFIG_DEBUG_FS +static struct timeval out_cold_tv; +static struct timeval out_warm_tv; +static struct timeval out_cont_tv; +static struct timeval in_cont_tv; +static long out_enable_flag; +static long in_enable_flag; +static struct dentry *out_dentry; +static struct dentry *in_dentry; +static int in_cont_index; +/*This var is used to keep track of first write done for cold output latency */ +static int out_cold_index; +static char *out_buffer; +static char *in_buffer; +static int audio_output_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_output_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(out_buffer, OUT_BUFFER_SIZE, "%ld,%ld,%ld,%ld,%ld,%ld,",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec, out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec, out_cont_tv.tv_sec, out_cont_tv.tv_usec); + return simple_read_from_buffer(buf, OUT_BUFFER_SIZE, ppos, + out_buffer, OUT_BUFFER_SIZE); +} +static ssize_t audio_output_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + + out_cold_index = 0; + + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &out_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_output_latency_debug_fops = { + .open = audio_output_latency_dbgfs_open, + .read = audio_output_latency_dbgfs_read, + .write = audio_output_latency_dbgfs_write +}; + +static int audio_input_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_input_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(in_buffer, IN_BUFFER_SIZE, "%ld,%ld,",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + return simple_read_from_buffer(buf, IN_BUFFER_SIZE, ppos, + in_buffer, IN_BUFFER_SIZE); +} +static ssize_t audio_input_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &in_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_input_latency_debug_fops = { + .open = audio_input_latency_dbgfs_open, + .read = audio_input_latency_dbgfs_read, + .write = audio_input_latency_dbgfs_write +}; +#endif +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); + rtac_remove_popp_from_adm_devices(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) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_kernel(port->buf[cnt].client, + port->buf[cnt].handle); + ion_free(port->buf[cnt].client, + port->buf[cnt].handle); + ion_client_destroy(port->buf[cnt].client); +#else + 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 (iounmap( + port->buf[cnt].mem_buffer) < 0) + pr_err("%s: unmap buffer" + " failed\n", __func__); + } + free_contiguous_memory_by_paddr( + port->buf[cnt].phys); + +#endif + 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].phys, dir); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + if (port->buf[0].data) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_kernel(port->buf[0].client, port->buf[0].handle); + ion_free(port->buf[0].client, port->buf[0].handle); + ion_client_destroy(port->buf[0].client); + pr_debug("%s:data[%p]phys[%p][%p]" + ", client[%p] handle[%p]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, + (void *)port->buf[0].client, + (void *)port->buf[0].handle); +#else + pr_debug("%s:data[%p]phys[%p][%p]" + "mem_buffer[%p]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, + (void *)port->buf[0].mem_buffer); + if (IS_ERR((void *)port->buf[0].mem_buffer)) + pr_err("%s:mem buffer invalid, error =" + "%ld\n", __func__, + PTR_ERR((void *)port->buf[0].mem_buffer)); + else { + if (iounmap( + port->buf[0].mem_buffer) < 0) + pr_err("%s: unmap buffer" + " failed\n", __func__); + } + free_contiguous_memory_by_paddr(port->buf[0].phys); +#endif + } + + 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; + } + rtac_set_asm_handle(n, ac->apr); + + 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; +} + +struct audio_client *q6asm_get_audio_client(int session_id) +{ + if ((session_id <= 0) || (session_id > SESSION_MAX)) { + pr_err("%s: invalid session: %d\n", __func__, session_id); + goto err; + } + + if (!session[session_id]) { + pr_err("%s: session not active: %d\n", __func__, session_id); + goto err; + } + + return session[session_id]; +err: + 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; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + int len; +#endif + + 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) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + buf[cnt].client = msm_ion_client_create + (UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *) + buf[cnt].client)) { + pr_err("%s: ION create client" + " for AUDIO failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].handle = ion_alloc + (buf[cnt].client, bufsz, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) + buf[cnt].handle)) { + pr_err("%s: ION memory" + " allocation for AUDIO failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + rc = ion_phys(buf[cnt].client, + buf[cnt].handle, + (ion_phys_addr_t *) + &buf[cnt].phys, + (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical" + " for AUDIO failed, rc = %d\n", + __func__, rc); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[cnt].data = ion_map_kernel + (buf[cnt].client, buf[cnt].handle, + 0); + if (IS_ERR_OR_NULL((void *) + buf[cnt].data)) { + pr_err("%s: ION memory" + " mapping for AUDIO failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + memset((void *)buf[cnt].data, 0, bufsz); +#else + 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; + } + buf[cnt].mem_buffer = + ioremap(buf[cnt].phys, bufsz); + 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)); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].data = + buf[cnt].mem_buffer; + if (!buf[cnt].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } +#endif + buf[cnt].used = 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_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; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + int len; +#else + int flags = 0; +#endif + 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; + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + buf[0].client = msm_ion_client_create(UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *)buf[0].client)) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[0].handle = ion_alloc(buf[0].client, bufsz * bufcnt, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) buf[0].handle)) { + pr_err("%s: ION memory allocation for AUDIO failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + rc = ion_phys(buf[0].client, buf[0].handle, + (ion_phys_addr_t *)&buf[0].phys, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[0].data = ion_map_kernel(buf[0].client, buf[0].handle, 0); + if (IS_ERR_OR_NULL((void *) buf[0].data)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + memset((void *)buf[0].data, 0, (bufsz * bufcnt)); +#else + buf[0].phys = allocate_contiguous_ebi_nomap(bufsz * bufcnt, + SZ_4K); + if (!buf[0].phys) { + pr_err("%s:Buf alloc failed " + " size=%d, bufcnt=%d\n", __func__, + bufsz, bufcnt); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[0].mem_buffer = ioremap(buf[0].phys, bufsz * bufcnt); + if (IS_ERR((void *)buf[cnt].mem_buffer)) { + pr_err("%s:map_buffer failed," + "error = %ld\n", + __func__, PTR_ERR((void *)buf[0].mem_buffer)); + + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[0].data = buf[0].mem_buffer; +#endif + if (!buf[0].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + 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); + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + 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: + if (rtac_make_asm_callback(ac->session, payload, + data->payload_size)) + break; + 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: + case ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED: + 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); +#ifdef CONFIG_DEBUG_FS + if (out_enable_flag) { + /* For first Write done log the time and reset + out_cold_index*/ + if (out_cold_index != 1) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld \ + sec %ld microsec\n",\ + out_cold_tv.tv_sec,\ + out_cold_tv.tv_usec); + out_cold_index = 1; + } + pr_debug("out_enable_flag %ld",\ + out_enable_flag); + } +#endif + for (i = 0; i < port->max_buf_cnt; i++) + pr_debug("%d ", port->buf[i].used); + + } + break; + } + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + rtac_make_asm_callback(ac->session, payload, + data->payload_size); + break; + case ASM_DATA_EVENT_READ_DONE:{ + + struct audio_port_data *port = &ac->port[OUT]; +#ifdef CONFIG_DEBUG_FS + if (in_enable_flag) { + /* when in_cont_index == 7, DSP would be + * writing into the 8th 512 byte buffer and this + * timestamp is tapped here.Once done it then writes + * to 9th 512 byte buffer.These two buffers(8th, 9th) + * reach the test application in 5th iteration and that + * timestamp is tapped at user level. The difference + * of these two timestamps gives us the time between + * the time at which dsp started filling the sample + * required and when it reached the test application. + * Hence continuous input latency + */ + if (in_cont_index == 7) { + do_gettimeofday(&in_cont_tv); + pr_err("In_CONT:previous read buffer done \ + at %ld sec %ld microsec\n",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + } + } +#endif + 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]); +#ifdef CONFIG_DEBUG_FS + if (in_enable_flag) { + in_cont_index++; + } +#endif + 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: + case ASM_DATA_EVENT_ENC_SR_CM_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__); + mutex_unlock(&port->lock); + 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; +} + +void *q6asm_is_cpu_buf_avail_nolock(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; + + port = &ac->port[dir]; + + 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); + 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)); + return data; +} + +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; +#ifdef CONFIG_DEBUG_FS + in_cont_index = 0; +#endif + 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_MULTI_CHANNEL_LINEAR_PCM: + open.uMode = STREAM_PRIORITY_HIGH; + open.format = MULTI_CHANNEL_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; + case FORMAT_AMRWB: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = AMRWB_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_compressed(struct audio_client *ac, uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_write_compressed 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_COMPRESSED; + + switch (format) { + case FORMAT_AC3: + open.format = AC3_DECODER; + break; + case FORMAT_EAC3: + open.format = EAC3_DECODER; + break; + case FORMAT_MP3: + open.format = MP3; + break; + case FORMAT_DTS: + open.format = DTS; + break; + case FORMAT_AAC: + open.format = MPEG4_AAC; + break; + case FORMAT_ATRAC: + open.format = ATRAC; + break; + case FORMAT_WMA_V10PRO: + open.format = WMA_V10PRO; + break; + case FORMAT_MAT: + open.format = MAT; + break; + default: + pr_err("%s: Invalid format[%d]\n", __func__, format); + goto fail_cmd; + } + /*Below flag indicates the DSP that Compressed audio input + stream is not IEC 61937 or IEC 60958 packetizied*/ + open.flags = 0x00000000; + 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_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_MULTI_CHANNEL_LINEAR_PCM: + open.format = MULTI_CHANNEL_PCM; + break; + case FORMAT_MPEG4_AAC: + open.format = MPEG4_AAC; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.format = MPEG4_MULTI_AAC; + break; + case FORMAT_WMA_V9: + open.format = WMA_V9; + break; + case FORMAT_WMA_V10PRO: + open.format = WMA_V10PRO; + break; + case FORMAT_MP3: + open.format = MP3; + 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; + /* 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_MPEG4_MULTI_AAC: + open.write_format = MPEG4_MULTI_AAC; + break; + case FORMAT_WMA_V9: + open.write_format = WMA_V9; + break; + case FORMAT_WMA_V10PRO: + open.write_format = WMA_V10PRO; + break; + case FORMAT_AMRNB: + open.write_format = AMRNB_FS; + break; + case FORMAT_AMRWB: + open.write_format = AMRWB_FS; + break; + case FORMAT_V13K: + open.write_format = V13K_FS; + break; + case FORMAT_EVRC: + open.write_format = EVRC_FS; + break; + case FORMAT_EVRCB: + open.write_format = EVRCB_FS; + break; + case FORMAT_EVRCWB: + open.write_format = EVRCWB_FS; + break; + case FORMAT_MP3: + open.write_format = MP3; + 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; + case FORMAT_AMRWB: + open.read_format = AMRWB_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; +#ifdef CONFIG_DEBUG_FS + if (out_enable_flag) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld sec %ld microsec\n",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec); + } +#endif + 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_enc_cfg_blk_multi_ch_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 = MULTI_CHANNEL_PCM; + enc_cfg.enc_blk.cfg_size = + sizeof(struct asm_multi_channel_pcm_fmt_blk); + enc_cfg.enc_blk.cfg.mpcm.num_channels = channels; + enc_cfg.enc_blk.cfg.mpcm.bits_per_sample = 16; + enc_cfg.enc_blk.cfg.mpcm.sample_rate = rate; + enc_cfg.enc_blk.cfg.mpcm.is_signed = 1; + enc_cfg.enc_blk.cfg.mpcm.is_interleaved = 1; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[0] = PCM_CHANNEL_FL; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[1] = PCM_CHANNEL_FR; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[2] = PCM_CHANNEL_RB; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[3] = PCM_CHANNEL_LB; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[4] = 0; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[5] = 0; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[6] = 0; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[7] = 0; + + 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_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right) +{ + struct asm_stream_cmd_encdec_dualmono dual_mono; + + int rc = 0; + + pr_debug("%s: Session %d, sce_left = %d, sce_right = %d\n", + __func__, ac->session, sce_left, sce_right); + + q6asm_add_hdr(ac, &dual_mono.hdr, sizeof(dual_mono), TRUE); + + dual_mono.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + dual_mono.param_id = ASM_CONFIGURE_DUAL_MONO; + dual_mono.param_size = sizeof(struct asm_dual_mono); + dual_mono.channel_map.sce_left = sce_left; + dual_mono.channel_map.sce_right = sce_right; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &dual_mono); + if (rc < 0) { + pr_err("%s:Command opcode[0x%x]paramid[0x%x] failed\n", + __func__, ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_CONFIGURE_DUAL_MONO); + 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 opcode[0x%x]\n", __func__, + dual_mono.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels) +{ + struct asm_stream_cmd_encdec_channelmap chan_map; + u8 *channel_mapping; + + int rc = 0; + + pr_debug("%s: Session %d, num_channels = %d\n", + __func__, ac->session, num_channels); + + q6asm_add_hdr(ac, &chan_map.hdr, sizeof(chan_map), TRUE); + + chan_map.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + chan_map.param_id = ASM_ENCDEC_DEC_CHAN_MAP; + chan_map.param_size = sizeof(struct asm_dec_chan_map); + chan_map.chan_map.num_channels = num_channels; + + channel_mapping = + chan_map.chan_map.channel_mapping; + + memset(channel_mapping, PCM_CHANNEL_NULL, MAX_CHAN_MAP_CHANNELS); + if (num_channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (num_channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (num_channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + num_channels); + rc = -EINVAL; + goto fail_cmd; + } + + rc = apr_send_pkt(ac->apr, (uint32_t *) &chan_map); + if (rc < 0) { + pr_err("%s:Command opcode[0x%x]paramid[0x%x] failed\n", + __func__, ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_ENCDEC_DEC_CHAN_MAP); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout opcode[0x%x]\n", __func__, + chan_map.hdr.opcode); + rc = -ETIMEDOUT; + goto fail_cmd; + } + return 0; +fail_cmd: + return rc; +} + +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_enc_cfg_blk_amrwb(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 = AMRWB_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_amrwb_read_cfg); + enc_cfg.enc_blk.cfg.amrwb.mode = band_mode; + enc_cfg.enc_blk.cfg.amrwb.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_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_media_format_update fmt; + u8 *channel_mapping; + 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 = MULTI_CHANNEL_PCM; + fmt.cfg_size = sizeof(struct asm_multi_channel_pcm_fmt_blk); + fmt.write_cfg.multi_ch_pcm_cfg.num_channels = channels; + fmt.write_cfg.multi_ch_pcm_cfg.bits_per_sample = 16; + fmt.write_cfg.multi_ch_pcm_cfg.sample_rate = rate; + fmt.write_cfg.multi_ch_pcm_cfg.is_signed = 1; + fmt.write_cfg.multi_ch_pcm_cfg.is_interleaved = 1; + channel_mapping = + fmt.write_cfg.multi_ch_pcm_cfg.channel_mapping; + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + channels); + return -EINVAL; + } + + 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_multi_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_MULTI_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(struct audio_client *ac, uint32_t format) +{ + + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d] format[0x%x]\n", __func__, + ac->session, format); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + switch (format) { + case FORMAT_V13K: + fmt.format = V13K_FS; + break; + case FORMAT_EVRC: + fmt.format = EVRC_FS; + break; + case FORMAT_AMRWB: + fmt.format = AMRWB_FS; + break; + case FORMAT_AMRNB: + fmt.format = AMRNB_FS; + break; + case FORMAT_MP3: + fmt.format = MP3; + break; + default: + pr_err("Invalid format[%d]\n", format); + goto fail_cmd; + } + fmt.cfg_size = 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_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); + if (mmap_region_cmd == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + 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); + if (unmap_region_cmd == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + 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_set_softvolume(struct audio_client *ac, + struct asm_softvolume_params *softvol_param) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_softvolume_params *params = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_softvolume_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_softvolume_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = SOFT_VOLUME_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_softvolume_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + params = (struct asm_softvolume_params *)payload; + + params->period = softvol_param->period; + params->step = softvol_param->step; + params->rampingcurve = softvol_param->rampingcurve; + pr_debug("%s: soft Volume:opcode = %d,payload_sz =%d,module_id =%d," + "param_id = %d, param_sz = %d\n", __func__, + cmd->hdr.opcode, cmd->payload_size, + cmd->params.module_id, cmd->params.param_id, + cmd->params.param_size); + pr_debug("%s: soft Volume Command: period = %d," + "step = %d, curve = %d\n", __func__, 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_volume) 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_volume)" + "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); +#ifdef CONFIG_DEBUG_FS + if (out_enable_flag) { + char zero_pattern[2] = {0x00, 0x00}; + /* If First two byte is non zero and last two byte + is zero then it is warm output pattern */ + if ((strncmp(((char *)ab->data), zero_pattern, 2)) && + (!strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_warm_tv); + pr_debug("WARM:apr_send_pkt at \ + %ld sec %ld microsec\n", out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec); + pr_debug("Warm Pattern Matched"); + } + /* If First two byte is zero and last two byte is + non zero then it is cont ouput pattern */ + else if ((!strncmp(((char *)ab->data), zero_pattern, 2)) + && (strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_cont_tv); + pr_debug("CONT:apr_send_pkt at \ + %ld sec %ld microsec\n", out_cont_tv.tv_sec,\ + out_cont_tv.tv_usec); + pr_debug("Cont Pattern Matched"); + } + } +#endif + 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; +} + +int q6asm_get_apr_service_id(int session_id) +{ + pr_debug("%s\n", __func__); + + if (session_id < 0 || session_id > SESSION_MAX) { + pr_err("%s: invalid session_id = %d\n", __func__, session_id); + return -EINVAL; + } + + return ((struct apr_svc *)session[session_id]->apr)->id; +} + + +static int __init q6asm_init(void) +{ + pr_debug("%s\n", __func__); + init_waitqueue_head(&this_mmap.cmd_wait); + memset(session, 0, sizeof(session)); +#ifdef CONFIG_DEBUG_FS + out_buffer = kmalloc(OUT_BUFFER_SIZE, GFP_KERNEL); + out_dentry = debugfs_create_file("audio_out_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_output_latency_debug_fops); + if (IS_ERR(out_dentry)) + pr_err("debugfs_create_file failed\n"); + in_buffer = kmalloc(IN_BUFFER_SIZE, GFP_KERNEL); + in_dentry = debugfs_create_file("audio_in_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_input_latency_debug_fops); + if (IS_ERR(in_dentry)) + pr_err("debugfs_create_file failed\n"); +#endif + 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 0000000000000000000000000000000000000000..0c30dc9e15d9ce35721c44405400243e1c4a036b --- /dev/null +++ b/sound/soc/msm/qdsp6/q6voice.c @@ -0,0 +1,4013 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "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 +#define VOC_PATH_VOLTE_PASSIVE 2 + +/* CVP CAL Size: 245760 = 240 * 1024 */ +#define CVP_CAL_SIZE 245760 +/* CVS CAL Size: 49152 = 48 * 1024 */ +#define CVS_CAL_SIZE 49152 + +static struct common_data common; + +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 int voice_send_cvp_map_memory_cmd(struct voice_data *v); +static int voice_send_cvp_unmap_memory_cmd(struct voice_data *v); +static int voice_send_cvs_map_memory_cmd(struct voice_data *v); +static int voice_send_cvs_unmap_memory_cmd(struct voice_data *v); +static int voice_send_cvs_register_cal_cmd(struct voice_data *v); +static int voice_send_cvs_deregister_cal_cmd(struct voice_data *v); +static int voice_send_cvp_register_cal_cmd(struct voice_data *v); +static int voice_send_cvp_deregister_cal_cmd(struct voice_data *v); +static int voice_send_cvp_register_vol_cal_table_cmd(struct voice_data *v); +static int voice_send_cvp_deregister_vol_cal_table_cmd(struct voice_data *v); +static int voice_send_set_widevoice_enable_cmd(struct voice_data *v); +static int voice_send_set_pp_enable_cmd(struct voice_data *v, + uint32_t module_id, int enable); +static int voice_cvs_stop_playback(struct voice_data *v); +static int voice_cvs_start_playback(struct voice_data *v); +static int voice_cvs_start_record(struct voice_data *v, uint32_t rec_mode); +static int voice_cvs_stop_record(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) +{ + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return 0; + } + + pr_debug("%s: mvm_handle %d\n", __func__, v->mvm_handle); + + return v->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; + } + + v->mvm_handle = mvm_handle; +} + +static u16 voice_get_cvs_handle(struct voice_data *v) +{ + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return 0; + } + + pr_debug("%s: cvs_handle %d\n", __func__, v->cvs_handle); + + return v->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; + } + + v->cvs_handle = cvs_handle; +} + +static u16 voice_get_cvp_handle(struct voice_data *v) +{ + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return 0; + } + + pr_debug("%s: cvp_handle %d\n", __func__, v->cvp_handle); + + return v->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; + } + + v->cvp_handle = cvp_handle; +} + +uint16_t voc_get_session_id(char *name) +{ + u16 session_id = 0; + + if (name != NULL) { + if (!strncmp(name, "Voice session", 13)) + session_id = common.voice[VOC_PATH_PASSIVE].session_id; + else if (!strncmp(name, "VoLTE session", 13)) + session_id = + common.voice[VOC_PATH_VOLTE_PASSIVE].session_id; + else + session_id = common.voice[VOC_PATH_FULL].session_id; + + pr_debug("%s: %s has session id 0x%x\n", __func__, name, + session_id); + } + + return session_id; +} + +static struct voice_data *voice_get_session(u16 session_id) +{ + struct voice_data *v = NULL; + + if ((session_id >= SESSION_ID_BASE) && + (session_id < SESSION_ID_BASE + MAX_VOC_SESSIONS)) { + v = &common.voice[session_id - SESSION_ID_BASE]; + } + + pr_debug("%s: session_id 0x%x session handle 0x%x\n", + __func__, session_id, (unsigned int)v); + + return v; +} + +static bool is_voice_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_PASSIVE].session_id); +} + +static bool is_voip_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_FULL].session_id); +} + +static bool is_volte_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_VOLTE_PASSIVE].session_id); +} + +static int voice_apr_register(void) +{ + pr_debug("%s\n", __func__); + + mutex_lock(&common.common_lock); + + /* register callback to APR */ + if (common.apr_q6_mvm == NULL) { + pr_debug("%s: Start to register MVM callback\n", __func__); + + common.apr_q6_mvm = apr_register("ADSP", "MVM", + qdsp_mvm_callback, + 0xFFFFFFFF, &common); + + if (common.apr_q6_mvm == NULL) { + pr_err("%s: Unable to register MVM\n", __func__); + goto err; + } + } + + if (common.apr_q6_cvs == NULL) { + pr_debug("%s: Start to register CVS callback\n", __func__); + + common.apr_q6_cvs = apr_register("ADSP", "CVS", + qdsp_cvs_callback, + 0xFFFFFFFF, &common); + + if (common.apr_q6_cvs == NULL) { + pr_err("%s: Unable to register CVS\n", __func__); + goto err; + } + + rtac_set_voice_handle(RTAC_CVS, common.apr_q6_cvs); + } + + if (common.apr_q6_cvp == NULL) { + pr_debug("%s: Start to register CVP callback\n", __func__); + + common.apr_q6_cvp = apr_register("ADSP", "CVP", + qdsp_cvp_callback, + 0xFFFFFFFF, &common); + + if (common.apr_q6_cvp == NULL) { + pr_err("%s: Unable to register CVP\n", __func__); + goto err; + } + + rtac_set_voice_handle(RTAC_CVP, common.apr_q6_cvp); + } + + mutex_unlock(&common.common_lock); + + return 0; + +err: + if (common.apr_q6_cvs != NULL) { + apr_deregister(common.apr_q6_cvs); + common.apr_q6_cvs = NULL; + rtac_set_voice_handle(RTAC_CVS, NULL); + } + if (common.apr_q6_mvm != NULL) { + apr_deregister(common.apr_q6_mvm); + common.apr_q6_mvm = NULL; + } + + mutex_unlock(&common.common_lock); + + return -ENODEV; +} + +static int voice_send_dual_control_cmd(struct voice_data *v) +{ + int ret = 0; + struct mvm_modem_dual_control_session_cmd mvm_voice_ctl_cmd; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + pr_debug("%s: VoLTE command to MVM\n", __func__); + if (is_volte_session(v->session_id)) { + mvm_handle = voice_get_mvm_handle(v); + mvm_voice_ctl_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_voice_ctl_cmd.hdr.pkt_size = APR_PKT_SIZE( + APR_HDR_SIZE, + sizeof(mvm_voice_ctl_cmd) - + APR_HDR_SIZE); + pr_debug("%s: send mvm Voice Ctl pkt size = %d\n", + __func__, mvm_voice_ctl_cmd.hdr.pkt_size); + mvm_voice_ctl_cmd.hdr.src_port = v->session_id; + mvm_voice_ctl_cmd.hdr.dest_port = mvm_handle; + mvm_voice_ctl_cmd.hdr.token = 0; + mvm_voice_ctl_cmd.hdr.opcode = + VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL; + mvm_voice_ctl_cmd.voice_ctl.enable_flag = true; + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_voice_ctl_cmd); + if (ret < 0) { + pr_err("%s: Error sending MVM Voice CTL CMD\n", + __func__); + ret = -EINVAL; + 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__); + ret = -EINVAL; + goto fail; + } + } + ret = 0; +fail: + return ret; +} + + +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, *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 = common.apr_q6_mvm; + apr_cvs = common.apr_q6_cvs; + apr_cvp = common.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 (is_voice_session(v->session_id) || + is_volte_session(v->session_id)) { + 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("%s: send mvm create session pkt size = %d\n", + __func__, mvm_session_cmd.hdr.pkt_size); + mvm_session_cmd.hdr.src_port = v->session_id; + 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; + if (is_volte_session(v->session_id)) { + strlcpy(mvm_session_cmd.mvm_session.name, + "default volte voice", + sizeof(mvm_session_cmd.mvm_session.name)); + } else { + strlcpy(mvm_session_cmd.mvm_session.name, + "default modem voice", + sizeof(mvm_session_cmd.mvm_session.name)); + } + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_cmd); + if (ret < 0) { + pr_err("%s: Error sending MVM_CONTROL_SESSION\n", + __func__); + 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_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); + mvm_session_cmd.hdr.src_port = v->session_id; + mvm_session_cmd.hdr.dest_port = 0; + mvm_session_cmd.hdr.token = 0; + mvm_session_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION; + strlcpy(mvm_session_cmd.mvm_session.name, + "default voip", + sizeof(mvm_session_cmd.mvm_session.name)); + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_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 (is_voice_session(v->session_id) || + is_volte_session(v->session_id)) { + pr_debug("%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); + cvs_session_cmd.hdr.src_port = v->session_id; + 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; + if (is_volte_session(v->session_id)) { + strlcpy(mvm_session_cmd.mvm_session.name, + "default volte voice", + sizeof(mvm_session_cmd.mvm_session.name)); + } else { + strlcpy(cvs_session_cmd.cvs_session.name, + "default modem voice", + sizeof(cvs_session_cmd.cvs_session.name)); + } + 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("%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 = v->session_id; + 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 = + common.mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.dec_media_type = + common.mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.network_id = + common.mvs_info.network_type; + strlcpy(cvs_full_ctl_cmd.cvs_session.name, + "default q6 voice", + sizeof(cvs_full_ctl_cmd.cvs_session.name)); + + 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("%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 = v->session_id; + 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 = common.apr_q6_mvm; + apr_cvs = common.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 (is_voip_session(v->session_id)) { + pr_debug("%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 = v->session_id; + 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("%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 = v->session_id; + 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 = v->session_id; + 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 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 = common.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 (v->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("%s: pkt size = %d\n", + __func__, mvm_tty_mode_cmd.hdr.pkt_size); + mvm_tty_mode_cmd.hdr.src_port = v->session_id; + 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 = v->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("%s: Error %d sending SET_TTY_MODE\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_set_dtx(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + struct cvs_set_enc_dtx_mode_cmd cvs_set_dtx; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + /* Set DTX */ + 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 = v->session_id; + 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 = common.mvs_info.dtx_mode; + + pr_debug("%s: Setting DTX %d\n", __func__, common.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); + return -EINVAL; + } + + 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; + } + + return 0; +} + +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 = common.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 = v->session_id; + 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 = common.mvs_info.media_type; + cvs_set_media_cmd.media_type.rx_media_id = common.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 (common.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 = v->session_id; + 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 = common.mvs_info.rate; + cvs_set_cdma_rate.cdma_rate.max_rate = common.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; + + 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 = v->session_id; + 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 = common.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; + } + + ret = voice_set_dtx(v); + if (ret < 0) + goto fail; + + break; + } + case VSS_MEDIA_ID_AMR_WB_MODEM: { + struct cvs_set_amrwb_enc_rate_cmd cvs_set_amrwb_rate; + + 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 = v->session_id; + 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 = common.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; + } + + ret = voice_set_dtx(v); + if (ret < 0) + goto fail; + + 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. */ + 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 = common.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 = v->session_id; + 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 = common.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 = v->session_id; + 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 = common.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 = v->session_id; + 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 = common.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 = v->session_id; + 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_send_cvs_register_cal_cmd(struct voice_data *v) +{ + struct cvs_register_cal_data_cmd cvs_reg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + uint32_t cal_paddr; + + /* get the cvs cal data */ + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvs_cal.buf) { + cal_paddr = common.cvs_cal.phy; + + memcpy(common.cvs_cal.buf, + (void *) cal_block.cal_kvaddr, + cal_block.cal_size); + } else { + return -EINVAL; + } + } else { + cal_paddr = cal_block.cal_paddr; + } + + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_reg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_reg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_reg_cal_cmd) - APR_HDR_SIZE); + cvs_reg_cal_cmd.hdr.src_port = v->session_id; + cvs_reg_cal_cmd.hdr.dest_port = cvs_handle; + cvs_reg_cal_cmd.hdr.token = 0; + cvs_reg_cal_cmd.hdr.opcode = VSS_ISTREAM_CMD_REGISTER_CALIBRATION_DATA; + + cvs_reg_cal_cmd.cvs_cal_data.phys_addr = cal_paddr; + cvs_reg_cal_cmd.cvs_cal_data.mem_size = cal_block.cal_size; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_reg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\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; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvs_deregister_cal_cmd(struct voice_data *v) +{ + struct cvs_deregister_cal_data_cmd cvs_dereg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_dereg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_dereg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_dereg_cal_cmd) - APR_HDR_SIZE); + cvs_dereg_cal_cmd.hdr.src_port = v->session_id; + cvs_dereg_cal_cmd.hdr.dest_port = cvs_handle; + cvs_dereg_cal_cmd.hdr.token = 0; + cvs_dereg_cal_cmd.hdr.opcode = + VSS_ISTREAM_CMD_DEREGISTER_CALIBRATION_DATA; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_dereg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\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; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_map_memory_cmd(struct voice_data *v) +{ + struct vss_map_memory_cmd cvp_map_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + /* get all cvp cal data */ + get_all_cvp_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvp_cal.buf) + cal_paddr = common.cvp_cal.phy; + else + return -EINVAL; + } else { + cal_paddr = cal_block.cal_paddr; + } + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_map_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_map_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_map_mem_cmd) - APR_HDR_SIZE); + cvp_map_mem_cmd.hdr.src_port = v->session_id; + cvp_map_mem_cmd.hdr.dest_port = cvp_handle; + cvp_map_mem_cmd.hdr.token = 0; + cvp_map_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_MAP_MEMORY; + + pr_debug("%s, phy_addr:0x%x, mem_size:%d\n", __func__, + cal_paddr, cal_block.cal_size); + cvp_map_mem_cmd.vss_map_mem.phys_addr = cal_paddr; + cvp_map_mem_cmd.vss_map_mem.mem_size = cal_block.cal_size; + cvp_map_mem_cmd.vss_map_mem.mem_pool_id = + VSS_ICOMMON_MAP_MEMORY_SHMEM8_4K_POOL; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_map_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\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_cvp_unmap_memory_cmd(struct voice_data *v) +{ + struct vss_unmap_memory_cmd cvp_unmap_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + get_all_cvp_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) + cal_paddr = common.cvp_cal.phy; + else + cal_paddr = cal_block.cal_paddr; + + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_unmap_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_unmap_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_unmap_mem_cmd) - APR_HDR_SIZE); + cvp_unmap_mem_cmd.hdr.src_port = v->session_id; + cvp_unmap_mem_cmd.hdr.dest_port = cvp_handle; + cvp_unmap_mem_cmd.hdr.token = 0; + cvp_unmap_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_UNMAP_MEMORY; + + cvp_unmap_mem_cmd.vss_unmap_mem.phys_addr = cal_paddr; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_unmap_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\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_cvs_map_memory_cmd(struct voice_data *v) +{ + struct vss_map_memory_cmd cvs_map_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + uint32_t cal_paddr; + + /* get all cvs cal data */ + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvs_cal.buf) + cal_paddr = common.cvs_cal.phy; + else + return -EINVAL; + } else { + cal_paddr = cal_block.cal_paddr; + } + + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_map_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_map_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_map_mem_cmd) - APR_HDR_SIZE); + cvs_map_mem_cmd.hdr.src_port = v->session_id; + cvs_map_mem_cmd.hdr.dest_port = cvs_handle; + cvs_map_mem_cmd.hdr.token = 0; + cvs_map_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_MAP_MEMORY; + + pr_debug("%s, phys_addr: 0x%x, mem_size: %d\n", __func__, + cal_paddr, cal_block.cal_size); + cvs_map_mem_cmd.vss_map_mem.phys_addr = cal_paddr; + cvs_map_mem_cmd.vss_map_mem.mem_size = cal_block.cal_size; + cvs_map_mem_cmd.vss_map_mem.mem_pool_id = + VSS_ICOMMON_MAP_MEMORY_SHMEM8_4K_POOL; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_map_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\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; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvs_unmap_memory_cmd(struct voice_data *v) +{ + struct vss_unmap_memory_cmd cvs_unmap_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + uint32_t cal_paddr; + + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) + cal_paddr = common.cvs_cal.phy; + else + cal_paddr = cal_block.cal_paddr; + + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_unmap_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_unmap_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_unmap_mem_cmd) - APR_HDR_SIZE); + cvs_unmap_mem_cmd.hdr.src_port = v->session_id; + cvs_unmap_mem_cmd.hdr.dest_port = cvs_handle; + cvs_unmap_mem_cmd.hdr.token = 0; + cvs_unmap_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_UNMAP_MEMORY; + + cvs_unmap_mem_cmd.vss_unmap_mem.phys_addr = cal_paddr; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_unmap_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\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; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_register_cal_cmd(struct voice_data *v) +{ + struct cvp_register_cal_data_cmd cvp_reg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + /* get the cvp cal data */ + get_all_vocproc_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvp_cal.buf) { + cal_paddr = common.cvp_cal.phy; + + memcpy(common.cvp_cal.buf, + (void *)cal_block.cal_kvaddr, + cal_block.cal_size); + } else { + return -EINVAL; + } + } else { + cal_paddr = cal_block.cal_paddr; + } + + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_reg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_reg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_reg_cal_cmd) - APR_HDR_SIZE); + cvp_reg_cal_cmd.hdr.src_port = v->session_id; + cvp_reg_cal_cmd.hdr.dest_port = cvp_handle; + cvp_reg_cal_cmd.hdr.token = 0; + cvp_reg_cal_cmd.hdr.opcode = VSS_IVOCPROC_CMD_REGISTER_CALIBRATION_DATA; + + cvp_reg_cal_cmd.cvp_cal_data.phys_addr = cal_paddr; + cvp_reg_cal_cmd.cvp_cal_data.mem_size = cal_block.cal_size; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_reg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\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_cvp_deregister_cal_cmd(struct voice_data *v) +{ + struct cvp_deregister_cal_data_cmd cvp_dereg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + get_all_vocproc_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_dereg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_dereg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_dereg_cal_cmd) - APR_HDR_SIZE); + cvp_dereg_cal_cmd.hdr.src_port = v->session_id; + cvp_dereg_cal_cmd.hdr.dest_port = cvp_handle; + cvp_dereg_cal_cmd.hdr.token = 0; + cvp_dereg_cal_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_DEREGISTER_CALIBRATION_DATA; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_dereg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\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_cvp_register_vol_cal_table_cmd(struct voice_data *v) +{ + struct cvp_register_vol_cal_table_cmd cvp_reg_cal_tbl_cmd; + struct acdb_cal_block vol_block; + struct acdb_cal_block voc_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + /* get the cvp vol cal data */ + get_all_vocvol_cal(&vol_block); + get_all_vocproc_cal(&voc_block); + + if (vol_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvp_cal.buf) { + cal_paddr = common.cvp_cal.phy + voc_block.cal_size; + + memcpy(common.cvp_cal.buf + voc_block.cal_size, + (void *) vol_block.cal_kvaddr, + vol_block.cal_size); + } else { + return -EINVAL; + } + } else { + cal_paddr = vol_block.cal_paddr; + } + + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_reg_cal_tbl_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_reg_cal_tbl_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_reg_cal_tbl_cmd) - APR_HDR_SIZE); + cvp_reg_cal_tbl_cmd.hdr.src_port = v->session_id; + cvp_reg_cal_tbl_cmd.hdr.dest_port = cvp_handle; + cvp_reg_cal_tbl_cmd.hdr.token = 0; + cvp_reg_cal_tbl_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_REGISTER_VOLUME_CAL_TABLE; + + cvp_reg_cal_tbl_cmd.cvp_vol_cal_tbl.phys_addr = cal_paddr; + cvp_reg_cal_tbl_cmd.cvp_vol_cal_tbl.mem_size = vol_block.cal_size; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_reg_cal_tbl_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal table,\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_cvp_deregister_vol_cal_table_cmd(struct voice_data *v) +{ + struct cvp_deregister_vol_cal_table_cmd cvp_dereg_cal_tbl_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + get_all_vocvol_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_dereg_cal_tbl_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_dereg_cal_tbl_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_dereg_cal_tbl_cmd) - APR_HDR_SIZE); + cvp_dereg_cal_tbl_cmd.hdr.src_port = v->session_id; + cvp_dereg_cal_tbl_cmd.hdr.dest_port = cvp_handle; + cvp_dereg_cal_tbl_cmd.hdr.token = 0; + cvp_dereg_cal_tbl_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_DEREGISTER_VOLUME_CAL_TABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_dereg_cal_tbl_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal table,\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_widevoice_enable_cmd(struct voice_data *v) +{ + struct mvm_set_widevoice_enable_cmd mvm_set_wv_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 = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + /* fill in the header */ + mvm_set_wv_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_set_wv_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_wv_cmd) - APR_HDR_SIZE); + mvm_set_wv_cmd.hdr.src_port = v->session_id; + mvm_set_wv_cmd.hdr.dest_port = mvm_handle; + mvm_set_wv_cmd.hdr.token = 0; + mvm_set_wv_cmd.hdr.opcode = VSS_IWIDEVOICE_CMD_SET_WIDEVOICE; + + mvm_set_wv_cmd.vss_set_wv.enable = v->wv_enable; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_wv_cmd); + if (ret < 0) { + pr_err("Fail: sending mvm set widevoice enable,\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_set_pp_enable_cmd(struct voice_data *v, + uint32_t module_id, int enable) +{ + struct cvs_set_pp_enable_cmd cvs_set_pp_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 = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_set_pp_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_set_pp_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_pp_cmd) - APR_HDR_SIZE); + cvs_set_pp_cmd.hdr.src_port = v->session_id; + cvs_set_pp_cmd.hdr.dest_port = cvs_handle; + cvs_set_pp_cmd.hdr.token = 0; + cvs_set_pp_cmd.hdr.opcode = VSS_ICOMMON_CMD_SET_UI_PROPERTY; + + cvs_set_pp_cmd.vss_set_pp.module_id = module_id; + cvs_set_pp_cmd.vss_set_pp.param_id = VOICE_PARAM_MOD_ENABLE; + cvs_set_pp_cmd.vss_set_pp.param_size = MOD_ENABLE_PARAM_LEN; + cvs_set_pp_cmd.vss_set_pp.reserved = 0; + cvs_set_pp_cmd.vss_set_pp.enable = enable; + cvs_set_pp_cmd.vss_set_pp.reserved_field = 0; + pr_debug("voice_send_set_pp_enable_cmd, module_id=%d, enable=%d\n", + module_id, enable); + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_pp_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs set slowtalk enable,\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; + } + 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 = common.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 = v->session_id; + 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; + } + + /* send cvs cal */ + ret = voice_send_cvs_map_memory_cmd(v); + if (!ret) + voice_send_cvs_register_cal_cmd(v); + + /* send cvp and vol cal */ + ret = voice_send_cvp_map_memory_cmd(v); + if (!ret) { + voice_send_cvp_register_cal_cmd(v); + voice_send_cvp_register_vol_cal_table_cmd(v); + } + + /* 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); + + /* enable widevoice if wv_enable is set */ + if (v->wv_enable) + voice_send_set_widevoice_enable_cmd(v); + + /* enable slowtalk if st_enable is set */ + if (v->st_enable) + voice_send_set_pp_enable_cmd(v, MODULE_ID_VOICE_MODULE_ST, + v->st_enable); + voice_send_set_pp_enable_cmd(v, MODULE_ID_VOICE_MODULE_FENS, + v->fens_enable); + + if (is_voip_session(v->session_id)) + voice_send_netid_timing_cmd(v); + + /* Start in-call music delivery if this feature is enabled */ + if (v->music_info.play_enable) + voice_cvs_start_playback(v); + + /* Start in-call recording if this feature is enabled */ + if (v->rec_info.rec_enable) + voice_cvs_start_record(v, v->rec_info.rec_mode); + + rtac_add_voice(voice_get_cvs_handle(v), + voice_get_cvp_handle(v), + v->dev_rx.port_id, v->dev_tx.port_id, + v->session_id); + + 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 = common.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 = v->session_id; + 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 = common.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 = v->session_id; + 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 = common.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 = v->session_id; + 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 = common.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 = v->session_id; + 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; + } + + 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 = common.apr_q6_mvm; + apr_cvp = common.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); + + /* stop playback or recording */ + v->music_info.force = 1; + voice_cvs_stop_playback(v); + voice_cvs_stop_record(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 = v->session_id; + 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; + } + + /* deregister cvp and vol cal */ + voice_send_cvp_deregister_vol_cal_table_cmd(v); + voice_send_cvp_deregister_cal_cmd(v); + voice_send_cvp_unmap_memory_cmd(v); + + /* deregister cvs cal */ + voice_send_cvs_deregister_cal_cmd(v); + voice_send_cvs_unmap_memory_cmd(v); + + /* 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 = v->session_id; + 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; + } + + rtac_remove_voice(voice_get_cvs_handle(v)); + 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 = common.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 = v->session_id; + 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_rx_device_mute_cmd(struct voice_data *v) +{ + struct cvp_set_mute_cmd cvp_mute_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 = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + cvp_mute_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_mute_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_mute_cmd) - APR_HDR_SIZE); + cvp_mute_cmd.hdr.src_port = v->session_id; + cvp_mute_cmd.hdr.dest_port = cvp_handle; + cvp_mute_cmd.hdr.token = 0; + cvp_mute_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_MUTE; + cvp_mute_cmd.cvp_set_mute.direction = 1; + cvp_mute_cmd.cvp_set_mute.mute_flag = v->dev_rx.mute; + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_mute_cmd); + if (ret < 0) { + pr_err("Fail in sending RX device mute cmd\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_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 = common.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 = v->session_id; + 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; + u16 cvs_handle; + + struct cvs_start_record_cmd cvs_start_record; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (!v->rec_info.recording) { + 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 = v->session_id; + 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; + } + v->rec_info.recording = 1; + } else { + pr_debug("%s: Start record already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +static int voice_cvs_stop_record(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + struct apr_hdr cvs_stop_record; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (v->rec_info.recording) { + 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 = v->session_id; + 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; + } + v->rec_info.recording = 0; + } else { + pr_debug("%s: Stop record already sent\n", __func__); + } + + return 0; + +fail: + + return ret; +} + +int voc_start_record(uint32_t port_id, uint32_t set) +{ + int ret = 0; + int rec_mode = 0; + u16 cvs_handle; + int i, rec_set = 0; + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + struct voice_data *v = &common.voice[i]; + pr_debug("%s: i:%d port_id: %d, set: %d\n", + __func__, i, port_id, set); + + mutex_lock(&v->lock); + rec_mode = v->rec_info.rec_mode; + rec_set = set; + if (set) { + if ((v->rec_route_state.ul_flag != 0) && + (v->rec_route_state.dl_flag != 0)) { + pr_debug("%s: i=%d, rec mode already set.\n", + __func__, i); + mutex_unlock(&v->lock); + if (i < MAX_VOC_SESSIONS) + continue; + else + return 0; + } + + if (port_id == VOICE_RECORD_TX) { + if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag == 0)) { + rec_mode = VOC_REC_UPLINK; + v->rec_route_state.ul_flag = 1; + } else if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag != 0)) { + voice_cvs_stop_record(v); + rec_mode = VOC_REC_BOTH; + v->rec_route_state.ul_flag = 1; + } + } else if (port_id == VOICE_RECORD_RX) { + if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag == 0)) { + rec_mode = VOC_REC_DOWNLINK; + v->rec_route_state.dl_flag = 1; + } else if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag == 0)) { + voice_cvs_stop_record(v); + rec_mode = VOC_REC_BOTH; + v->rec_route_state.dl_flag = 1; + } + } + rec_set = 1; + } else { + if ((v->rec_route_state.ul_flag == 0) && + (v->rec_route_state.dl_flag == 0)) { + pr_debug("%s: i=%d, rec already stops.\n", + __func__, i); + mutex_unlock(&v->lock); + if (i < MAX_VOC_SESSIONS) + continue; + else + return 0; + } + + if (port_id == VOICE_RECORD_TX) { + if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag == 0)) { + v->rec_route_state.ul_flag = 0; + rec_set = 0; + } else if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag != 0)) { + voice_cvs_stop_record(v); + v->rec_route_state.ul_flag = 0; + rec_mode = VOC_REC_DOWNLINK; + rec_set = 1; + } + } else if (port_id == VOICE_RECORD_RX) { + if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag != 0)) { + v->rec_route_state.dl_flag = 0; + rec_set = 0; + } else if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag != 0)) { + voice_cvs_stop_record(v); + v->rec_route_state.dl_flag = 0; + rec_mode = VOC_REC_UPLINK; + rec_set = 1; + } + } + } + pr_debug("%s: i=%d, mode =%d, set =%d\n", __func__, + i, rec_mode, rec_set); + cvs_handle = voice_get_cvs_handle(v); + + if (cvs_handle != 0) { + if (rec_set) + ret = voice_cvs_start_record(v, rec_mode); + else + ret = voice_cvs_stop_record(v); + } + + /* Cache the value */ + v->rec_info.rec_enable = rec_set; + v->rec_info.rec_mode = rec_mode; + + mutex_unlock(&v->lock); + } + + return ret; +} + +static int voice_cvs_start_playback(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvs_start_playback; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (!v->music_info.playing && v->music_info.count) { + 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 = v->session_id; + 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; + } else { + pr_debug("%s: Start playback already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +static int voice_cvs_stop_playback(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvs_stop_playback; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (v->music_info.playing && ((!v->music_info.count) || + (v->music_info.force))) { + 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 = v->session_id; + 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; + v->music_info.force = 0; + } else { + pr_debug("%s: Stop playback already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +int voc_start_playback(uint32_t set) +{ + int ret = 0; + u16 cvs_handle; + int i; + + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + struct voice_data *v = &common.voice[i]; + + mutex_lock(&v->lock); + v->music_info.play_enable = set; + if (set) + v->music_info.count++; + else + v->music_info.count--; + pr_debug("%s: music_info count =%d\n", __func__, + v->music_info.count); + + cvs_handle = voice_get_cvs_handle(v); + if (cvs_handle != 0) { + if (set) + ret = voice_cvs_start_playback(v); + else + ret = voice_cvs_stop_playback(v); + } + + mutex_unlock(&v->lock); + } + + return ret; +} + +int voc_disable_cvp(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) + afe_sidetone(v->dev_tx.port_id, v->dev_rx.port_id, + 0, 0); + + rtac_remove_voice(voice_get_cvs_handle(v)); + /* 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; + } + + /* deregister cvp and vol cal */ + voice_send_cvp_deregister_vol_cal_table_cmd(v); + voice_send_cvp_deregister_cal_cmd(v); + voice_send_cvp_unmap_memory_cmd(v); + + v->voc_state = VOC_CHANGE; + } + +fail: mutex_unlock(&v->lock); + + return ret; +} + +int voc_enable_cvp(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct sidetone_cal sidetone_cal_data; + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + 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; + } + /* send cvp and vol cal */ + ret = voice_send_cvp_map_memory_cmd(v); + if (!ret) { + voice_send_cvp_register_cal_cmd(v); + voice_send_cvp_register_vol_cal_table_cmd(v); + } + ret = voice_send_enable_vocproc_cmd(v); + if (ret < 0) { + pr_err("%s: enable vocproc failed\n", __func__); + goto fail; + + } + /* send tty mode if tty device is used */ + voice_send_tty_mode_cmd(v); + + /* enable widevoice if wv_enable is set */ + if (v->wv_enable) + voice_send_set_widevoice_enable_cmd(v); + + /* enable slowtalk */ + if (v->st_enable) + voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_ST, + v->st_enable); + /* enable FENS */ + if (v->fens_enable) + voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_FENS, + v->fens_enable); + + get_sidetone_cal(&sidetone_cal_data); + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) { + ret = afe_sidetone(v->dev_tx.port_id, + v->dev_rx.port_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + + if (ret < 0) + pr_err("%s: AFE command sidetone failed\n", + __func__); + } + + rtac_add_voice(voice_get_cvs_handle(v), + voice_get_cvp_handle(v), + v->dev_rx.port_id, v->dev_tx.port_id, + v->session_id); + v->voc_state = VOC_RUN; + } + +fail: + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_tx_mute(uint16_t session_id, uint32_t dir, uint32_t mute) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + 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_device_mute(uint16_t session_id, uint32_t mute) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->dev_rx.mute = mute; + + if (v->voc_state == VOC_RUN) + ret = voice_send_rx_device_mute_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_get_rx_device_mute(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + ret = v->dev_rx.mute; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_tty_mode(uint16_t session_id, uint8_t tty_mode) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->tty_mode = tty_mode; + + mutex_unlock(&v->lock); + + return ret; +} + +uint8_t voc_get_tty_mode(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + ret = v->tty_mode; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_widevoice_enable(uint16_t session_id, uint32_t wv_enable) +{ + struct voice_data *v = voice_get_session(session_id); + u16 mvm_handle; + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->wv_enable = wv_enable; + + mvm_handle = voice_get_mvm_handle(v); + + if (mvm_handle != 0) + voice_send_set_widevoice_enable_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +uint32_t voc_get_widevoice_enable(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + ret = v->wv_enable; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_pp_enable(uint16_t session_id, uint32_t module_id, uint32_t enable) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + if (module_id == MODULE_ID_VOICE_MODULE_ST) + v->st_enable = enable; + else if (module_id == MODULE_ID_VOICE_MODULE_FENS) + v->fens_enable = enable; + + if (v->voc_state == VOC_RUN) { + if (module_id == MODULE_ID_VOICE_MODULE_ST) + ret = voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_ST, + enable); + else if (module_id == MODULE_ID_VOICE_MODULE_FENS) + ret = voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_FENS, + enable); + } + mutex_unlock(&v->lock); + + return ret; +} + +int voc_get_pp_enable(uint16_t session_id, uint32_t module_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + if (module_id == MODULE_ID_VOICE_MODULE_ST) + ret = v->st_enable; + else if (module_id == MODULE_ID_VOICE_MODULE_FENS) + ret = v->fens_enable; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_rx_vol_index(uint16_t session_id, uint32_t dir, uint32_t vol_idx) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + 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; +} + +int voc_set_rxtx_port(uint16_t session_id, uint32_t port_id, uint32_t dev_type) +{ + struct voice_data *v = voice_get_session(session_id); + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + pr_debug("%s: port_id=%d, type=%d\n", __func__, 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 0; +} + +int voc_set_route_flag(uint16_t session_id, uint8_t path_dir, uint8_t set) +{ + struct voice_data *v = voice_get_session(session_id); + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + pr_debug("%s: path_dir=%d, set=%d\n", __func__, 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 0; +} + +uint8_t voc_get_route_flag(uint16_t session_id, uint8_t path_dir) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return 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(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN || v->voc_state == VOC_STANDBY) { + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) + afe_sidetone(v->dev_tx.port_id, v->dev_rx.port_id, + 0, 0); + 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_resume_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct apr_hdr mvm_start_voice_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + pr_debug("%s:\n", __func__); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.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 = v->session_id; + 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; + } + v->voc_state = VOC_RUN; + return 0; +fail: + return -EINVAL; +} + +int voc_start_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct sidetone_cal sidetone_cal_data; + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if ((v->voc_state == VOC_INIT) || + (v->voc_state == VOC_RELEASE)) { + ret = voice_apr_register(); + 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_send_dual_control_cmd(v); + if (ret < 0) { + pr_err("Err Dual command 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; + } + get_sidetone_cal(&sidetone_cal_data); + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) { + ret = afe_sidetone(v->dev_tx.port_id, + v->dev_rx.port_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + if (ret < 0) + pr_err("AFE command sidetone failed\n"); + } + + v->voc_state = VOC_RUN; + } else if (v->voc_state == VOC_STANDBY) { + pr_err("Error: PCM Prepare when in Standby\n"); + ret = -EINVAL; + goto fail; + } +fail: mutex_unlock(&v->lock); + return ret; +} + +int voc_standby_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct apr_hdr mvm_standby_voice_cmd; + void *apr_mvm; + u16 mvm_handle; + int ret = 0; + + pr_debug("%s: voc state=%d", __func__, v->voc_state); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + if (v->voc_state == VOC_RUN) { + apr_mvm = common.apr_q6_mvm; + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + ret = -EINVAL; + goto fail; + } + mvm_handle = voice_get_mvm_handle(v); + mvm_standby_voice_cmd.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_standby_voice_cmd.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_standby_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_standby_voice_cmd pkt size = %d\n", + mvm_standby_voice_cmd.pkt_size); + mvm_standby_voice_cmd.src_port = v->session_id; + mvm_standby_voice_cmd.dest_port = mvm_handle; + mvm_standby_voice_cmd.token = 0; + mvm_standby_voice_cmd.opcode = VSS_IMVM_CMD_STANDBY_VOICE; + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, + (uint32_t *)&mvm_standby_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_STANDBY_VOICE\n"); + ret = -EINVAL; + goto fail; + } + v->voc_state = VOC_STANDBY; + } +fail: + return ret; +} + +void voc_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data) +{ + common.mvs_info.ul_cb = ul_cb; + common.mvs_info.dl_cb = dl_cb; + common.mvs_info.private_data = private_data; +} + +void voc_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type, + uint32_t dtx_mode) +{ + common.mvs_info.media_type = media_type; + common.mvs_info.rate = rate; + common.mvs_info.network_type = network_type; + common.mvs_info.dtx_mode = dtx_mode; +} + +static int32_t qdsp_mvm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr = NULL; + struct common_data *c = NULL; + struct voice_data *v = NULL; + int i = 0; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + + c = priv; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + + return -EINVAL; + } + + 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(c->apr_q6_mvm); + c->apr_q6_mvm = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].mvm_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 */ + 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_IMVM_CMD_ATTACH_VOCPROC: + case VSS_IMVM_CMD_STOP_VOICE: + case VSS_IMVM_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: + case VSS_IWIDEVOICE_CMD_SET_WIDEVOICE: + case VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL: + case VSS_IMVM_CMD_STANDBY_VOICE: + 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 = NULL; + struct common_data *c = NULL; + struct voice_data *v = NULL; + int i = 0; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + + c = priv; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + + return -EINVAL; + } + + 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(c->apr_q6_cvs); + c->apr_q6_cvs = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].cvs_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 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: + case VSS_ISTREAM_CMD_REGISTER_CALIBRATION_DATA: + case VSS_ISTREAM_CMD_DEREGISTER_CALIBRATION_DATA: + case VSS_ICOMMON_CMD_MAP_MEMORY: + case VSS_ICOMMON_CMD_UNMAP_MEMORY: + case VSS_ICOMMON_CMD_SET_UI_PROPERTY: + case VSS_ISTREAM_CMD_START_PLAYBACK: + case VSS_ISTREAM_CMD_STOP_PLAYBACK: + case VSS_ISTREAM_CMD_START_RECORD: + case VSS_ISTREAM_CMD_STOP_RECORD: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + break; + case VOICE_CMD_SET_PARAM: + rtac_make_voice_callback(RTAC_CVS, ptr, + data->payload_size); + 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 && c->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; + + c->mvs_info.ul_cb((uint8_t *)voc_pkt, + pkt_len, + c->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) c->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 (c->mvs_info.dl_cb != NULL) { + send_dec_buf.dec_buf.media_id = c->mvs_info.media_type; + + c->mvs_info.dl_cb( + (uint8_t *)&send_dec_buf.dec_buf.packet_data, + &pkt_len, + c->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 = v->session_id; + 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(c->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 if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVS, data->payload, + data->payload_size); + } 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 = NULL; + struct common_data *c = NULL; + struct voice_data *v = NULL; + int i = 0; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + + c = priv; + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + + return -EINVAL; + } + + 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(c->apr_q6_cvp); + c->apr_q6_cvp = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].cvp_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]); + + 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: + case VSS_IVOCPROC_CMD_REGISTER_VOLUME_CAL_TABLE: + case VSS_IVOCPROC_CMD_DEREGISTER_VOLUME_CAL_TABLE: + case VSS_IVOCPROC_CMD_REGISTER_CALIBRATION_DATA: + case VSS_IVOCPROC_CMD_DEREGISTER_CALIBRATION_DATA: + case VSS_ICOMMON_CMD_MAP_MEMORY: + case VSS_ICOMMON_CMD_UNMAP_MEMORY: + case VSS_IVOCPROC_CMD_SET_MUTE: + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + break; + case VOICE_CMD_SET_PARAM: + rtac_make_voice_callback(RTAC_CVP, ptr, + data->payload_size); + break; + default: + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + break; + } + } + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVP, data->payload, + data->payload_size); + } + return 0; +} + + +static int __init voice_init(void) +{ + int rc = 0, i = 0; + int len; + + memset(&common, 0, sizeof(struct common_data)); + + /* Allocate memory for VoIP calibration */ + common.client = msm_ion_client_create(UINT_MAX, "voip_client"); + if (IS_ERR_OR_NULL((void *)common.client)) { + pr_err("%s: ION create client for Voip failed\n", __func__); + goto cont; + } + common.cvp_cal.handle = ion_alloc(common.client, CVP_CAL_SIZE, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) common.cvp_cal.handle)) { + pr_err("%s: ION memory allocation for CVP failed\n", + __func__); + ion_client_destroy(common.client); + goto cont; + } + + rc = ion_phys(common.client, common.cvp_cal.handle, + (ion_phys_addr_t *)&common.cvp_cal.phy, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for cvp failed, rc = %d\n", + __func__, rc); + ion_free(common.client, common.cvp_cal.handle); + ion_client_destroy(common.client); + goto cont; + } + + common.cvp_cal.buf = ion_map_kernel(common.client, + common.cvp_cal.handle, 0); + if (IS_ERR_OR_NULL((void *) common.cvp_cal.buf)) { + pr_err("%s: ION memory mapping for cvp failed\n", __func__); + common.cvp_cal.buf = NULL; + ion_free(common.client, common.cvp_cal.handle); + ion_client_destroy(common.client); + goto cont; + } + memset((void *)common.cvp_cal.buf, 0, CVP_CAL_SIZE); + + common.cvs_cal.handle = ion_alloc(common.client, CVS_CAL_SIZE, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) common.cvs_cal.handle)) { + pr_err("%s: ION memory allocation for CVS failed\n", + __func__); + goto cont; + } + + rc = ion_phys(common.client, common.cvs_cal.handle, + (ion_phys_addr_t *)&common.cvs_cal.phy, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for cvs failed, rc = %d\n", + __func__, rc); + ion_free(common.client, common.cvs_cal.handle); + goto cont; + } + + common.cvs_cal.buf = ion_map_kernel(common.client, + common.cvs_cal.handle, 0); + if (IS_ERR_OR_NULL((void *) common.cvs_cal.buf)) { + pr_err("%s: ION memory mapping for cvs failed\n", __func__); + common.cvs_cal.buf = NULL; + ion_free(common.client, common.cvs_cal.handle); + goto cont; + } + memset((void *)common.cvs_cal.buf, 0, CVS_CAL_SIZE); +cont: + /* set default value */ + common.default_mute_val = 1; /* default is mute */ + common.default_vol_val = 0; + common.default_sample_val = 8000; + + /* Initialize MVS info. */ + common.mvs_info.network_type = VSS_NETWORK_ID_DEFAULT; + + mutex_init(&common.common_lock); + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + common.voice[i].session_id = SESSION_ID_BASE + i; + + /* initialize dev_rx and dev_tx */ + common.voice[i].dev_rx.volume = common.default_vol_val; + common.voice[i].dev_rx.mute = 0; + common.voice[i].dev_tx.mute = common.default_mute_val; + + common.voice[i].dev_tx.port_id = 1; + common.voice[i].dev_rx.port_id = 0; + common.voice[i].sidetone_gain = 0x512; + + common.voice[i].voc_state = VOC_INIT; + + init_waitqueue_head(&common.voice[i].mvm_wait); + init_waitqueue_head(&common.voice[i].cvs_wait); + init_waitqueue_head(&common.voice[i].cvp_wait); + + mutex_init(&common.voice[i].lock); + } + + 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 0000000000000000000000000000000000000000..88ab0d58d1c92da783078e2201d6c46aceb0fbf3 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6voice.h @@ -0,0 +1,997 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 +#include + +#define MAX_VOC_PKT_SIZE 642 +#define SESSION_NAME_LEN 20 + +#define VOC_REC_UPLINK 0x00 +#define VOC_REC_DOWNLINK 0x01 +#define VOC_REC_BOTH 0x02 + +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; +}; + +struct voice_rec_route_state { + u16 ul_flag; + u16 dl_flag; +}; + +enum { + VOC_INIT = 0, + VOC_RUN, + VOC_CHANGE, + VOC_RELEASE, + VOC_STANDBY, +}; + +/* Common */ +#define VSS_ICOMMON_CMD_SET_UI_PROPERTY 0x00011103 +/* Set a UI property */ +#define VSS_ICOMMON_CMD_MAP_MEMORY 0x00011025 +#define VSS_ICOMMON_CMD_UNMAP_MEMORY 0x00011026 +/* General shared memory; byte-accessible, 4 kB-aligned. */ +#define VSS_ICOMMON_MAP_MEMORY_SHMEM8_4K_POOL 3 + +struct vss_icommon_cmd_map_memory_t { + uint32_t phys_addr; + /* Physical address of a memory region; must be at least + * 4 kB aligned. + */ + + uint32_t mem_size; + /* Number of bytes in the region; should be a multiple of 32. */ + + uint16_t mem_pool_id; + /* Type of memory being provided. The memory ID implicitly defines + * the characteristics of the memory. The characteristics might include + * alignment type, permissions, etc. + * Memory pool ID. Possible values: + * 3 -- VSS_ICOMMON_MEM_TYPE_SHMEM8_4K_POOL. + */ +} __packed; + +struct vss_icommon_cmd_unmap_memory_t { + uint32_t phys_addr; + /* Physical address of a memory region; must be at least + * 4 kB aligned. + */ +} __packed; + +struct vss_map_memory_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_map_memory_t vss_map_mem; +} __packed; + +struct vss_unmap_memory_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_unmap_memory_t vss_unmap_mem; +} __packed; + +/* 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_SET_POLICY_DUAL_CONTROL 0x00011327 +/* + * VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL + * Description: This command is required to let MVM know + * who is in control of session. + * Payload: Defined by vss_imvm_cmd_set_policy_dual_control_t. + * Result: 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 +/* + * Start Voice call command. + * Wait for APRV2_IBASIC_RSP_RESULT response. + * No pay load. + */ + +#define VSS_IMVM_CMD_STANDBY_VOICE 0x00011191 +/* 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. */ + +#define VSS_IWIDEVOICE_CMD_SET_WIDEVOICE 0x00011243 +/* Enable/disable WideVoice */ + +enum msm_audio_voc_rate { + VOC_0_RATE, /* Blank frame */ + VOC_8_RATE, /* 1/8 rate */ + VOC_4_RATE, /* 1/4 rate */ + VOC_2_RATE, /* 1/2 rate */ + VOC_1_RATE /* Full rate */ +}; + +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_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_imvm_cmd_set_policy_dual_control_t { + bool enable_flag; + /* Set to TRUE to enable modem state machine control */ +} __packed; + +struct vss_iwidevoice_cmd_set_widevoice_t { + uint32_t enable; + /* WideVoice enable/disable; possible values: + * - 0 -- WideVoice disabled + * - 1 -- WideVoice enabled + */ +} __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_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_create_control_session_t mvm_session; +} __packed; + +struct mvm_modem_dual_control_session_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_set_policy_dual_control_t voice_ctl; +} __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; + +struct mvm_set_widevoice_enable_cmd { + struct apr_hdr hdr; + struct vss_iwidevoice_cmd_set_widevoice_t vss_set_wv; +} __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_REGISTER_CALIBRATION_DATA 0x00011279 + +#define VSS_ISTREAM_CMD_DEREGISTER_CALIBRATION_DATA 0x0001127A + +#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 MODULE_ID_VOICE_MODULE_FENS 0x00010EEB +#define MODULE_ID_VOICE_MODULE_ST 0x00010EE3 +#define VOICE_PARAM_MOD_ENABLE 0x00010E00 +#define MOD_ENABLE_PARAM_LEN 4 + +#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. */ + +#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_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. + */ +} __packed; + +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. + */ +} __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[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_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 vss_istream_cmd_register_calibration_data_t { + uint32_t phys_addr; + /* Phsical address to be registered with stream. The calibration data + * is stored at this address. + */ + uint32_t mem_size; + /* Size of the calibration data in bytes. */ +}; + +struct vss_icommon_cmd_set_ui_property_enable_t { + uint32_t module_id; + /* Unique ID of the module. */ + uint32_t param_id; + /* Unique ID of the parameter. */ + uint16_t param_size; + /* Size of the parameter in bytes: MOD_ENABLE_PARAM_LEN */ + uint16_t reserved; + /* Reserved; set to 0. */ + uint16_t enable; + uint16_t reserved_field; + /* Reserved, set to 0. */ +}; + +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; +} __packed; + +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; + +struct cvs_register_cal_data_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_register_calibration_data_t cvs_cal_data; +} __packed; + +struct cvs_deregister_cal_data_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvs_set_pp_enable_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_ui_property_enable_t vss_set_pp; +} __packed; +struct cvs_start_record_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_start_record_t rec_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_CMD_REGISTER_CALIBRATION_DATA 0x00011275 +#define VSS_IVOCPROC_CMD_DEREGISTER_CALIBRATION_DATA 0x00011276 + +#define VSS_IVOCPROC_CMD_REGISTER_VOLUME_CAL_TABLE 0x00011277 +#define VSS_IVOCPROC_CMD_DEREGISTER_VOLUME_CAL_TABLE 0x00011278 + +#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 VSS_MEDIA_ID_4GV_NB_MODEM 0x00010FC3 +/*CDMA EVRC-B vocoder modem format */ +#define VSS_MEDIA_ID_4GV_WB_MODEM 0x00010FC4 +/*CDMA EVRC-WB vocoder modem format */ + +#define VSS_IVOCPROC_CMD_SET_MUTE 0x000110EF + +#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 vss_ivocproc_cmd_register_calibration_data_t { + uint32_t phys_addr; + /* Phsical address to be registered with vocproc. Calibration data + * is stored at this address. + */ + uint32_t mem_size; + /* Size of the calibration data in bytes. */ +} __packed; + +struct vss_ivocproc_cmd_register_volume_cal_table_t { + uint32_t phys_addr; + /* Phsical address to be registered with the vocproc. The volume + * calibration table is stored at this location. + */ + + uint32_t mem_size; + /* Size of the volume calibration table in bytes. */ +} __packed; + +struct vss_ivocproc_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 : Disable. + * 1 : Enable. + */ +} __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; + +struct cvp_register_cal_data_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_register_calibration_data_t cvp_cal_data; +} __packed; + +struct cvp_deregister_cal_data_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvp_register_vol_cal_table_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_register_volume_cal_table_t cvp_vol_cal_tbl; +} __packed; + +struct cvp_deregister_vol_cal_table_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvp_set_mute_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_mute_t cvp_set_mute; +} __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 rec_enable; + uint32_t rec_mode; + uint32_t recording; +}; + +struct incall_music_info { + uint32_t play_enable; + uint32_t playing; + int count; + int force; +}; + +struct voice_data { + int voc_state;/*INIT, CHANGE, RELEASE, RUN */ + + wait_queue_head_t mvm_wait; + wait_queue_head_t cvs_wait; + wait_queue_head_t cvp_wait; + + /* cache the values related to Rx and Tx */ + struct device_data dev_rx; + struct device_data dev_tx; + + u32 mvm_state; + u32 cvs_state; + u32 cvp_state; + + /* Handle to MVM in the Q6 */ + u16 mvm_handle; + /* Handle to CVS in the Q6 */ + u16 cvs_handle; + /* Handle to CVP in the Q6 */ + u16 cvp_handle; + + struct mutex lock; + + uint16_t sidetone_gain; + uint8_t tty_mode; + /* widevoice enable value */ + uint8_t wv_enable; + /* slowtalk enable value */ + uint32_t st_enable; + /* FENC enable value */ + uint32_t fens_enable; + + struct voice_dev_route_state voc_route_state; + + u16 session_id; + + struct incall_rec_info rec_info; + + struct incall_music_info music_info; + + struct voice_rec_route_state rec_route_state; +}; + +struct cal_mem { + struct ion_handle *handle; + uint32_t phy; + void *buf; +}; + +#define MAX_VOC_SESSIONS 3 +#define SESSION_ID_BASE 0xFFF0 + +struct common_data { + /* 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; + + struct ion_client *client; + struct cal_mem cvp_cal; + struct cal_mem cvs_cal; + + struct mutex common_lock; + + struct mvs_driver_info mvs_info; + + struct voice_data voice[MAX_VOC_SESSIONS]; +}; + +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, + uint32_t dtx_mode); + +enum { + DEV_RX = 0, + DEV_TX, +}; + +enum { + RX_PATH = 0, + TX_PATH, +}; + +/* called by alsa driver */ +int voc_set_pp_enable(uint16_t session_id, uint32_t module_id, uint32_t enable); +int voc_get_pp_enable(uint16_t session_id, uint32_t module_id); +int voc_set_widevoice_enable(uint16_t session_id, uint32_t wv_enable); +uint32_t voc_get_widevoice_enable(uint16_t session_id); +uint8_t voc_get_tty_mode(uint16_t session_id); +int voc_set_tty_mode(uint16_t session_id, uint8_t tty_mode); +int voc_start_voice_call(uint16_t session_id); +int voc_standby_voice_call(uint16_t session_id); +int voc_resume_voice_call(uint16_t session_id); +int voc_end_voice_call(uint16_t session_id); +int voc_set_rxtx_port(uint16_t session_id, + uint32_t dev_port_id, + uint32_t dev_type); +int voc_set_rx_vol_index(uint16_t session_id, uint32_t dir, uint32_t voc_idx); +int voc_set_tx_mute(uint16_t session_id, uint32_t dir, uint32_t mute); +int voc_set_rx_device_mute(uint16_t session_id, uint32_t mute); +int voc_get_rx_device_mute(uint16_t session_id); +int voc_disable_cvp(uint16_t session_id); +int voc_enable_cvp(uint16_t session_id); +int voc_set_route_flag(uint16_t session_id, uint8_t path_dir, uint8_t set); +uint8_t voc_get_route_flag(uint16_t session_id, uint8_t path_dir); + +#define VOICE_SESSION_NAME "Voice session" +#define VOIP_SESSION_NAME "VoIP session" +#define VOLTE_SESSION_NAME "VoLTE session" +uint16_t voc_get_session_id(char *name); + +int voc_start_playback(uint32_t set); +int voc_start_record(uint32_t port_id, uint32_t set); +#endif diff --git a/sound/soc/msm/qdsp6v2/Makefile b/sound/soc/msm/qdsp6v2/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6f765d1ce205c1821dc435b5185181c9b8e05a51 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/Makefile @@ -0,0 +1,4 @@ +snd-soc-qdsp6v2-objs += msm-dai-q6-v2.o msm-pcm-q6-v2.o msm-pcm-routing-v2.o msm-compr-q6-v2.o msm-multi-ch-pcm-q6-v2.o +snd-soc-qdsp6v2-objs += msm-pcm-lpa-v2.o msm-pcm-afe-v2.o +obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o +obj-y += q6adm.o q6afe.o q6asm.o q6audio-v2.o diff --git a/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..daba79d98b195b4ed2de1886d53d191cd7d3b1fc --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c @@ -0,0 +1,666 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-compr-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm compressed_audio = {NULL, 0x2000} ; + +static struct audio_locks the_locks; + +static struct snd_pcm_hardware msm_compr_hardware_playback = { + .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 = 1200 * 1024 * 2, + .period_bytes_min = 4800, + .period_bytes_max = 1200 * 1024, + .periods_min = 2, + .periods_max = 512, + .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 compr_event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct compr_audio *compr = priv; + struct msm_audio *prtd = &compr->prtd; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = NULL; + int i = 0; + + pr_debug("%s opcode =%08x\n", __func__, opcode); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + 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)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing %d bytes of buffer[%d] to dsp 2\n", + __func__, prtd->pcm_count, prtd->out_head); + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * 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_EVENT_RENDERED_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_V2: { + if (!atomic_read(&prtd->pending_buffer)) + break; + pr_debug("%s:writing %d bytes" + " of buffer[%d] to dsp\n", + __func__, prtd->pcm_count, prtd->out_head); + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * prtd->pcm_count))); + param.paddr = (unsigned long)buf[prtd->out_head].phys; + 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].phys; + 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); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_compr_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct asm_aac_cfg aac_cfg; + 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; + prtd->out_head = 0; + atomic_set(&prtd->out_count, runtime->periods); + + if (prtd->enabled) + return 0; + + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + /* No media format block for mp3 */ + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + memset(&aac_cfg, 0x0, sizeof(struct asm_aac_cfg)); + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + aac_cfg.format = 0x03; + aac_cfg.ch_cfg = runtime->channels; + aac_cfg.sample_rate = runtime->rate; + ret = q6asm_media_format_block_aac(prtd->audio_client, + &aac_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + default: + return -EINVAL; + } + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_compr_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->pcm_irq_pos = 0; + 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); + 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 void populate_codec_list(struct compr_audio *compr, + struct snd_pcm_runtime *runtime) +{ + pr_debug("%s\n", __func__); + /* MP3 Block */ + compr->info.compr_cap.num_codecs = 1; + compr->info.compr_cap.min_fragment_size = runtime->hw.period_bytes_min; + compr->info.compr_cap.max_fragment_size = runtime->hw.period_bytes_max; + compr->info.compr_cap.min_fragments = runtime->hw.periods_min; + compr->info.compr_cap.max_fragments = runtime->hw.periods_max; + compr->info.compr_cap.codecs[0] = SND_AUDIOCODEC_MP3; + compr->info.compr_cap.codecs[1] = SND_AUDIOCODEC_AAC; + /* Add new codecs here */ +} + +static int msm_compr_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr; + struct msm_audio *prtd; + int ret = 0; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + pr_debug("%s\n", __func__); + compr = kzalloc(sizeof(struct compr_audio), GFP_KERNEL); + if (compr == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd = &compr->prtd; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)compr_event_handler, compr); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + runtime->hw = msm_compr_hardware_playback; + + 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); + + 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; + atomic_set(&prtd->pending_buffer, 1); + compr->codec = FORMAT_MP3; + populate_codec_list(compr, runtime); + runtime->private_data = compr; + compressed_audio.prtd = &compr->prtd; + ret = compressed_set_volume(compressed_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(compressed_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(compressed_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int compressed_set_volume(unsigned volume) +{ + int rc = 0; + if (compressed_audio.prtd && compressed_audio.prtd->audio_client) { + rc = q6asm_set_volume(compressed_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + compressed_audio.volume = volume; + return rc; +} + +static int msm_compr_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 compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + int dir = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + compressed_audio.prtd = NULL; + 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_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_compr_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} +static int msm_compr_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} + +static snd_pcm_uframes_t msm_compr_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + 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_compr_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +static int msm_compr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EINVAL; + + ret = q6asm_open_write(prtd->audio_client, compr->codec); + if (ret < 0) { + pr_err("%s: Session out open failed\n", __func__); + 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__); + return -ENOMEM; + } + + 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 int msm_compr_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.rendered = (size_t)(temp & 0xFFFFFFFF); + tstamp.decoded = (size_t)((temp >> 32) & 0xFFFFFFFF); + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:lsb = %d, msb = %d," + "timestamp = %lld,\n", + __func__, tstamp.rendered, tstamp.decoded, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_COMPRESS_GET_CAPS: + pr_debug("SNDRV_COMPRESS_GET_CAPS\n"); + if (copy_to_user((void *) arg, &compr->info.compr_cap, + sizeof(struct snd_compr_caps))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy to user\n", __func__); + return rc; + } + return 0; + case SNDRV_COMPRESS_SET_PARAMS: + pr_debug("SNDRV_COMPRESS_SET_PARAMS: "); + if (copy_from_user(&compr->info.codec_param, (void *) arg, + sizeof(struct snd_compr_params))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy from user\n", __func__); + return rc; + } + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + /* For MP3 we dont need any other parameter */ + pr_debug("SND_AUDIOCODEC_MP3\n"); + compr->codec = FORMAT_MP3; + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + compr->codec = FORMAT_MPEG4_AAC; + break; + default: + pr_debug("FORMAT_LINEAR_PCM\n"); + compr->codec = FORMAT_LINEAR_PCM; + break; + } + return 0; + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_compr_ops = { + .open = msm_compr_open, + .hw_params = msm_compr_hw_params, + .close = msm_compr_close, + .ioctl = msm_compr_ioctl, + .prepare = msm_compr_prepare, + .trigger = msm_compr_trigger, + .pointer = msm_compr_pointer, + .mmap = msm_compr_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_compr_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static __devinit int msm_compr_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_compr_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_compr_driver = { + .driver = { + .name = "msm-compr-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_compr_probe, + .remove = __devexit_p(msm_compr_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_compr_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_compr_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..2183690477ddbd14de36d296254e805c9b64506b --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_COMPR_H +#define _MSM_COMPR_H +#include +#include +#include +#include +#include + +#include "msm-pcm-q6-v2.h" + +struct compr_info { + struct snd_compr_caps compr_cap; + struct snd_compr_codec_caps codec_caps; + struct snd_compr_params codec_param; +}; + +struct compr_audio { + struct msm_audio prtd; + struct compr_info info; + uint32_t codec; +}; + +#endif /*_MSM_COMPR_H*/ diff --git a/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..1605062584417c150f852babf2af370d7a294d3a --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c @@ -0,0 +1,1229 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + +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 struct clk *pcm_clk; +static DEFINE_MUTEX(aux_pcm_mutex); +static int aux_pcm_count; +static struct msm_dai_auxpcm_pdata *auxpcm_plat_data; + +static u8 num_of_bits_set(u8 sd_line_mask) +{ + u8 num_bits_set = 0; + + while (sd_line_mask) { + num_bits_set++; + sd_line_mask = sd_line_mask & (sd_line_mask - 1); + } + return num_bits_set; +} + +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.i2s.mono_stereo = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_MONO; + break; + default: + return -EINVAL; + break; + } + dai_data->rate = params_rate(params); + dai_data->port_config.i2s.sample_rate = dai_data->rate; + dai_data->port_config.i2s.i2s_cfg_minor_version = + AFE_API_VERSION_I2S_CONFIG; + dai_data->port_config.i2s.data_format = AFE_LINEAR_PCM_DATA; + 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.i2s.bit_width = 16; + dai_data->port_config.i2s.channel_mode = 1; + return 0; +} + +static int msm_dai_q6_i2s_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); + struct msm_i2s_data *i2s_pdata = + (struct msm_i2s_data *) dai->dev->platform_data; + + dai_data->channels = params_channels(params); + if (num_of_bits_set(i2s_pdata->sd_lines) == 1) { + switch (dai_data->channels) { + case 2: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_MONO; + break; + default: + pr_warn("greater than stereo has not been validated"); + break; + } + } + dai_data->rate = params_rate(params); + dai_data->port_config.i2s.sample_rate = dai_data->rate; + dai_data->port_config.i2s.i2s_cfg_minor_version = + AFE_API_VERSION_I2S_CONFIG; + dai_data->port_config.i2s.data_format = AFE_LINEAR_PCM_DATA; + /* Q6 only supports 16 as now */ + dai_data->port_config.i2s.bit_width = 16; + dai_data->port_config.i2s.channel_mode = 1; + + return 0; +} + +static int msm_dai_q6_i2s_platform_data_validation( + struct snd_soc_dai *dai) +{ + u8 num_of_sd_lines; + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_i2s_data *i2s_pdata = + (struct msm_i2s_data *)dai->dev->platform_data; + struct snd_soc_dai_driver *dai_driver = + (struct snd_soc_dai_driver *)dai->driver; + + num_of_sd_lines = num_of_bits_set(i2s_pdata->sd_lines); + + switch (num_of_sd_lines) { + case 1: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD0; + break; + case MSM_MI2S_SD1: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD1; + break; + case MSM_MI2S_SD2: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD2; + break; + case MSM_MI2S_SD3: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD3; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 2: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_QUAD01; + break; + case MSM_MI2S_SD2 | MSM_MI2S_SD3: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_QUAD23; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 3: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_6CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + case 4: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2 | MSM_MI2S_SD3: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_8CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + default: + pr_err("%s: invalid SD lines\n", __func__); + goto error_invalid_data; + } + if (i2s_pdata->capability == MSM_MI2S_CAP_RX) + dai_driver->playback.channels_max = num_of_sd_lines << 1; + + return 0; + +error_invalid_data: + return -EINVAL; +} + +static int msm_dai_q6_cdc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_data->port_config.i2s.ws_src = 1; /* CPU is master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_data->port_config.i2s.ws_src = 0; /* CPU is slave */ + break; + default: + return -EINVAL; + } + + 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); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + /* Q6 only supports 16 as now */ + dai_data->port_config.slim_sch.sb_cfg_minor_version = + AFE_API_VERSION_SLIMBUS_CONFIG; + dai_data->port_config.slim_sch.bit_width = 16; + dai_data->port_config.slim_sch.data_format = 0; + dai_data->port_config.slim_sch.num_channels = dai_data->channels; + dai_data->port_config.slim_sch.sample_rate = dai_data->rate; + + dev_dbg(dai->dev, "%s:slimbus_dev_id[%hu] bit_wd[%hu] format[%hu]\n" + "num_channel %hu shared_ch_mapping[0] %hu\n" + "slave_port_mapping[1] %hu slave_port_mapping[2] %hu\n" + "sample_rate %d\n", __func__, + dai_data->port_config.slim_sch.slimbus_dev_id, + dai_data->port_config.slim_sch.bit_width, + dai_data->port_config.slim_sch.data_format, + dai_data->port_config.slim_sch.num_channels, + dai_data->port_config.slim_sch.shared_ch_mapping[0], + dai_data->port_config.slim_sch.shared_ch_mapping[1], + dai_data->port_config.slim_sch.shared_ch_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; +} +static int msm_dai_q6_auxpcm_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + if (params_channels(params) != 1) { + dev_err(dai->dev, "AUX PCM supports only mono stream\n"); + return -EINVAL; + } + dai_data->channels = params_channels(params); + + if (params_rate(params) != 8000) { + dev_err(dai->dev, "AUX PCM supports only 8KHz sampling rate\n"); + return -EINVAL; + } + dai_data->rate = params_rate(params); + + dai_data->port_config.pcm.pcm_cfg_minor_version = + AFE_API_VERSION_PCM_CONFIG; + dai_data->port_config.pcm.aux_mode = auxpcm_pdata->mode; + dai_data->port_config.pcm.sync_src = auxpcm_pdata->sync; + dai_data->port_config.pcm.frame_setting = auxpcm_pdata->frame; + dai_data->port_config.pcm.quantype = auxpcm_pdata->quant; + dai_data->port_config.pcm.ctrl_data_out_enable = auxpcm_pdata->data; + dai_data->port_config.pcm.sample_rate = dai_data->rate; + dai_data->port_config.pcm.num_channels = dai_data->channels; + dai_data->port_config.pcm.bit_width = 16; + dai_data->port_config.pcm.slot_number_mapping[0] = auxpcm_pdata->slot; + + return 0; +} + +static int msm_dai_q6_afe_rtproxy_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); + + dai_data->rate = params_rate(params); + dai_data->port_config.rtproxy.num_channels = params_channels(params); + dai_data->port_config.rtproxy.sample_rate = params_rate(params); + + pr_debug("channel %d entered,dai_id: %d,rate: %d\n", + dai_data->port_config.rtproxy.num_channels, dai->id, dai_data->rate); + + dai_data->port_config.rtproxy.rt_proxy_cfg_minor_version = + AFE_API_VERSION_RT_PROXY_CONFIG; + dai_data->port_config.rtproxy.bit_width = 16; /* Q6 only supports 16 */ + dai_data->port_config.rtproxy.interleaved = 1; + dai_data->port_config.rtproxy.frame_size = params_period_bytes(params); + dai_data->port_config.rtproxy.jitter_allowance = + dai_data->port_config.rtproxy.frame_size/2; + dai_data->port_config.rtproxy.low_water_mark = 0; + dai_data->port_config.rtproxy.high_water_mark = 0; + + 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: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_hw_params(params, dai, substream->stream); + break; + case MI2S_RX: + rc = msm_dai_q6_i2s_hw_params(params, dai, substream->stream); + break; + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_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; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_TX: + case RT_PROXY_DAI_002_RX: + rc = msm_dai_q6_afe_rtproxy_hw_params(params, dai); + break; + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = 0; + break; + default: + dev_err(dai->dev, "invalid AFE port ID\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static void msm_dai_q6_auxpcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int rc = 0; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. Just" + " return\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + aux_pcm_count = 0; + mutex_unlock(&aux_pcm_mutex); + return; + } + + pr_debug("%s: dai->id = %d aux_pcm_count = %d\n", __func__, + dai->id, aux_pcm_count); + + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close PCM_RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX port\n"); + + mutex_unlock(&aux_pcm_mutex); +} + +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 = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + rc = afe_close(dai->id); /* can block */ + break; + } + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + pr_debug("%s: dai_data->status_mask = %ld\n", __func__, + *dai_data->status_mask); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } +} + +static int msm_dai_q6_auxpcm_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; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 2. Just" + " return.\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return 0; + } else if (aux_pcm_count > 2) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d > 2\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + aux_pcm_count++; + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d after " + " increment\n", __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + pr_debug("%s:dai->id:%d aux_pcm_count = %d. opening afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + + /* + * For AUX PCM Interface the below sequence of clk + * settings and afe_open is a strict requirement. + * + * Also using afe_open instead of afe_port_start_nowait + * to make sure the port is open before deasserting the + * clock line. This is required because pcm register is + * not written before clock deassert. Hence the hw does + * not get updated with new setting if the below clock + * assert/deasset and afe_open sequence is not followed. + */ + + afe_open(PCM_RX, &dai_data->port_config, dai_data->rate); + + afe_open(PCM_TX, &dai_data->port_config, dai_data->rate); + + mutex_unlock(&aux_pcm_mutex); + + return rc; +} + +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)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_auxpcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int rc = 0; + + pr_debug("%s:port:%d cmd:%d aux_pcm_count= %d", + __func__, dai->id, cmd, aux_pcm_count); + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* afe_open will be called from prepare */ + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return 0; + + default: + rc = -EINVAL; + } + + 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. + */ + pr_debug("%s:port:%d cmd:%d dai_data->status_mask = %ld", + __func__, dai->id, cmd, *dai_data->status_mask); + 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)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_start_nowait(dai->id); + break; + default: + afe_port_start_nowait(dai->id, + &dai_data->port_config, dai_data->rate); + break; + } + 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)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_stop_nowait(dai->id); + break; + default: + afe_port_stop_nowait(dai->id); + break; + } + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} +static int msm_dai_q6_dai_auxpcm_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + mutex_lock(&aux_pcm_mutex); + + if (!auxpcm_plat_data) + auxpcm_plat_data = auxpcm_pdata; + else if (auxpcm_plat_data != auxpcm_pdata) { + + dev_err(dai->dev, "AUX PCM RX and TX devices does not have" + " same platform data\n"); + return -EINVAL; + } + + /* + * The clk name for AUX PCM operation is passed as platform + * data to the cpu driver, since cpu drive is unaware of any + * boarc specific configuration. + */ + if (!pcm_clk) + pcm_clk = clk_get(dai->dev, auxpcm_pdata->clk); + + mutex_unlock(&aux_pcm_mutex); + + 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); + + pr_err("%s : probe done for dai->id %d\n", __func__, dai->id); + return rc; +} + +static int msm_dai_q6_dai_auxpcm_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. clean" + " up and return\n", __func__, dai->id); + goto done; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + goto done; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + goto done; + } + + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d." + "closing afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX AFE port\n"); + +done: + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + mutex_unlock(&aux_pcm_mutex); + + return 0; +} +static int msm_dai_q6_dai_i2s_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; + goto rtn; + } else + dev_set_drvdata(dai->dev, dai_data); + + rc = msm_dai_q6_i2s_platform_data_validation(dai); + if (rc != 0) { + pr_err("%s: The msm_dai_q6_i2s_platform_data_validation failed\n", + __func__); + kfree(dai_data); + } +rtn: + 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)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + 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); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static int msm_dai_q6_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + int rc = 0; + + dev_dbg(dai->dev, "enter %s, id = %d fmt[%d]\n", __func__, + dai->id, fmt); + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + case MI2S_RX: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_set_fmt(dai, fmt); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static int msm_dai_q6_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) + +{ + int rc = 0; + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + unsigned int i = 0; + + dev_dbg(dai->dev, "enter %s, id = %d\n", __func__, + dai->id); + switch (dai->id) { + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!rx_slot) + return -EINVAL; + for (i = 0; i < rx_num; i++) { + dai_data->port_config.slim_sch.shared_ch_mapping[i] = + rx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, + rx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = rx_num; + pr_debug("%s:SLIMBUS_0_RX cnt[%d] ch[%d %d]\n", __func__, + rx_num, dai_data->port_config.slim_sch.shared_ch_mapping[0], + dai_data->port_config.slim_sch.shared_ch_mapping[1]); + + break; + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!tx_slot) + return -EINVAL; + for (i = 0; i < tx_num; i++) { + dai_data->port_config.slim_sch.shared_ch_mapping[i] = + tx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, tx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = tx_num; + pr_debug("%s:SLIMBUS_0_TX cnt[%d] ch[%d %d]\n", __func__, + tx_num, dai_data->port_config.slim_sch.shared_ch_mapping[0], + dai_data->port_config.slim_sch.shared_ch_mapping[1]); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + return rc; +} + +static struct snd_soc_dai_ops msm_dai_q6_ops = { + .prepare = msm_dai_q6_prepare, + .trigger = msm_dai_q6_trigger, + .hw_params = msm_dai_q6_hw_params, + .shutdown = msm_dai_q6_shutdown, + .set_fmt = msm_dai_q6_set_fmt, + .set_channel_map = msm_dai_q6_set_channel_map, +}; + +static struct snd_soc_dai_ops msm_dai_q6_auxpcm_ops = { + .prepare = msm_dai_q6_auxpcm_prepare, + .trigger = msm_dai_q6_auxpcm_trigger, + .hw_params = msm_dai_q6_auxpcm_hw_params, + .shutdown = msm_dai_q6_auxpcm_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 = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_i2s_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_afe_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_afe_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_voice_playback_tx_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_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_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_incall_record_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, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 8000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 8000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_1_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_min = 8000, + .rate_max = 16000, + }, + .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_1_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +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: + case SECONDARY_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 AFE_PORT_ID_PRIMARY_PCM_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_rx_dai); + break; + case AFE_PORT_ID_PRIMARY_PCM_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_tx_dai); + break; + case MI2S_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_i2s_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); + break; + + case SLIMBUS_1_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_rx_dai); + break; + case SLIMBUS_1_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_tx_dai); + break; + 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; + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_rx_dai); + break; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_002_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_tx_dai); + break; + case VOICE_PLAYBACK_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_voice_playback_tx_dai); + break; + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_incall_record_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/qdsp6v2/msm-multi-ch-pcm-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..cab689d56c78c8be75deec3708734fca128fbd17 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c @@ -0,0 +1,777 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-v2.h" +#include "msm-pcm-routing-v2.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +struct snd_msm_volume { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm_volume multi_ch_pcm_audio = {NULL, 0x2000}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_PERIOD_SIZE 4032 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 320 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .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 = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .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 = 6, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .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[CAPTURE_NUM_PERIODS][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_V2: { + 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_nolock(IN, + prtd->audio_client, + &size, &idx)) { + 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_EVENT_RENDERED_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_V2: { + 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_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + 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); + }; + } + atomic_set(&prtd->start, 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_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); + 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; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + 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; + } + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_err("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_hardware_playback; + ret = q6asm_open_write(prtd->audio_client, + FORMAT_MULTI_CHANNEL_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) { + runtime->hw = msm_pcm_hardware_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_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); + + 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_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) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + pr_debug("substream->pcm->device = %d\n", substream->pcm->device); + pr_debug("soc_prtd->dai_link->be_id = %d\n", soc_prtd->dai_link->be_id); + multi_ch_pcm_audio.prtd = prtd; + ret = multi_ch_pcm_set_volume(multi_ch_pcm_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(multi_ch_pcm_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(multi_ch_pcm_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int multi_ch_pcm_set_volume(unsigned volume) +{ + int rc = 0; + pr_err("multi_ch_pcm_set_volume\n"); + + if (multi_ch_pcm_audio.prtd && multi_ch_pcm_audio.prtd->audio_client) { + pr_err("%s q6asm_set_volume\n", __func__); + rc = q6asm_set_volume(multi_ch_pcm_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + multi_ch_pcm_audio.volume = volume; + return rc; +} + + +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_cmd(prtd->audio_client, CMD_CLOSE); + 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); + multi_ch_pcm_audio.prtd = NULL; + 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_cmd(prtd->audio_client, CMD_CLOSE); + 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_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) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +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-multi-ch-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("Multi channel PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..45937843602c50dbd0160d85ef4b4ea2f475ab93 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c @@ -0,0 +1,581 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or 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-pcm-afe-v2.h" + +#define MIN_PERIOD_SIZE (128 * 2) +#define MAX_PERIOD_SIZE (128 * 2 * 2 * 6) +static struct snd_pcm_hardware msm_afe_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 | + SNDRV_PCM_RATE_48000), + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_PERIOD_SIZE * 32, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = 32, + .periods_max = 384, + .fifo_size = 0, +}; +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt); +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt); + +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + u32 mem_map_handle = 0; + if (prtd->start) { + pr_debug("sending frame to DSP: poll_time: %d\n", + prtd->poll_time); + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + pr_debug("%s: mem_map_handle 0x%x\n", __func__, mem_map_handle); + afe_rt_proxy_port_write( + (prtd->dma_addr + + (prtd->dsp_cnt * + snd_pcm_lib_period_bytes(prtd->substream))), mem_map_handle, + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + u32 mem_map_handle = 0; + if (prtd->start) { + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + pr_err("%s: mem_map_handle 0x%x\n", __func__, mem_map_handle); + afe_rt_proxy_port_read( + (prtd->dma_addr + (prtd->dsp_cnt + * snd_pcm_lib_period_bytes(prtd->substream))), mem_map_handle, + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + pr_debug("sending frame rec to DSP: poll_time: %d\n", + prtd->poll_time); + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static void pcm_afe_process_tx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes + (prtd->substream) * + 1000 * 1000)/ + (runtime->rate * + runtime->channels * 2))); + pr_debug("prtd->poll_time: %d", + prtd->poll_time); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2: + pr_debug("write done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static void pcm_afe_process_rx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes(prtd->substream) + * 1000 * 1000)/(runtime->rate + * runtime->channels * 2))); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + pr_debug("prtd->poll_time : %d", prtd->poll_time); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2: + pr_debug("Read done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static int msm_afe_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s: sample_rate=%d\n", __func__, runtime->rate); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_tx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return ret; +} + +static int msm_afe_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s\n", __func__); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_rx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return 0; +} + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 16000, 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_afe_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = NULL; + int ret = 0; + + prtd = kzalloc(sizeof(struct pcm_afe_info), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } else + pr_debug("prtd %x\n", (unsigned int)prtd); + + mutex_init(&prtd->lock); + spin_lock_init(&prtd->dsp_lock); + prtd->dsp_cnt = 0; + + mutex_lock(&prtd->lock); + + runtime->hw = msm_afe_hardware; + prtd->substream = substream; + runtime->private_data = prtd; + mutex_unlock(&prtd->lock); + hrtimer_init(&prtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->hrt.function = afe_hrtimer_callback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->hrt.function = afe_hrtimer_rec_callback; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_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) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + return 0; +} + +static int msm_afe_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_dma_buffer *dma_buf; + struct snd_pcm_runtime *runtime; + struct pcm_afe_info *prtd; + struct snd_soc_pcm_runtime *rtd = NULL; + struct snd_soc_dai *dai = NULL; + int ret = 0; + + pr_debug("%s\n", __func__); + if (substream == NULL) { + pr_err("substream is NULL\n"); + return -EINVAL; + } + rtd = substream->private_data; + dai = rtd->cpu_dai; + runtime = substream->runtime; + prtd = runtime->private_data; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } + hrtimer_cancel(&prtd->hrt); + + rc = afe_cmd_memory_unmap(runtime->dma_addr); + if (rc < 0) + pr_err("AFE memory unmap failed\n"); + + pr_debug("release all buffer\n"); + dma_buf = &substream->dma_buffer; + if (dma_buf == NULL) { + pr_debug("dma_buf is NULL\n"); + goto done; + } + if (dma_buf->area != NULL) { + dma_free_coherent(substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, dma_buf->area, + dma_buf->addr); + dma_buf->area = NULL; + } +done: + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + mutex_unlock(&prtd->lock); + prtd->prepared--; + kfree(prtd); + return 0; +} +static int msm_afe_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + prtd->pcm_irq_pos = 0; + if (prtd->prepared) + return 0; + mutex_lock(&prtd->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_afe_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_afe_capture_prepare(substream); + mutex_unlock(&prtd->lock); + return ret; +} +static int msm_afe_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *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_afe_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *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: SNDRV_PCM_TRIGGER_START\n", __func__); + prtd->start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + prtd->start = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +static int msm_afe_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 pcm_afe_info *prtd = runtime->private_data; + int rc; + + pr_debug("%s:\n", __func__); + + mutex_lock(&prtd->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(dma_buf->dev.dev, + runtime->hw.buffer_bytes_max, + &dma_buf->addr, GFP_KERNEL); + + pr_debug("%s: dma_buf->area: 0x%p, dma_buf->addr: 0x%x", __func__, + (unsigned int *) dma_buf->area, dma_buf->addr); + if (!dma_buf->area) { + pr_err("%s:MSM AFE memory allocation failed\n", __func__); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + dma_buf->bytes = runtime->hw.buffer_bytes_max; + memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); + prtd->dma_addr = (u32) dma_buf->addr; + + mutex_unlock(&prtd->lock); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + rc = afe_cmd_memory_map(dma_buf->addr, dma_buf->bytes); + if (rc < 0) + pr_err("fail to map memory to DSP\n"); + + return rc; +} +static snd_pcm_uframes_t msm_afe_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= snd_pcm_lib_buffer_bytes(substream)) + 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 struct snd_pcm_ops msm_afe_ops = { + .open = msm_afe_open, + .hw_params = msm_afe_hw_params, + .trigger = msm_afe_trigger, + .close = msm_afe_close, + .prepare = msm_afe_prepare, + .mmap = msm_afe_mmap, + .pointer = msm_afe_pointer, +}; + + +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("%s\n", __func__); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static int msm_afe_afe_probe(struct snd_soc_platform *platform) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_afe_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_afe_afe_probe, +}; + +static __devinit int msm_afe_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_afe_remove(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_afe_driver = { + .driver = { + .name = "msm-pcm-afe", + .owner = THIS_MODULE, + }, + .probe = msm_afe_probe, + .remove = __devexit_p(msm_afe_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + pr_debug("%s\n", __func__); + return platform_driver_register(&msm_afe_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + pr_debug("%s\n", __func__); + platform_driver_unregister(&msm_afe_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("AFE PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..20d63777d51fbd8273913701fe252306e71d2e35 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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_AFE_H +#define _MSM_PCM_AFE_H +#include +#include + + +struct pcm_afe_info { + unsigned long dma_addr; + struct snd_pcm_substream *substream; + unsigned int pcm_irq_pos; /* IRQ position */ + struct mutex lock; + spinlock_t dsp_lock; + uint32_t samp_rate; + uint32_t channel_mode; + uint8_t start; + uint32_t dsp_cnt; + uint32_t buf_phys; + int32_t mmap_flag; + int prepared; + struct hrtimer hrt; + int poll_time; +}; + + +#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_AFE_H*/ diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..ee9275359f0ab88c9278aa59d14de8c027b2c73b --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c @@ -0,0 +1,609 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 "msm-pcm-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm lpa_audio; + +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 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 2 * 1024 * 1024, + .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 = NULL; + unsigned long flag = 0; + int i = 0; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&the_locks.event_lock, flag); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + uint32_t *ptrmem = (uint32_t *)¶m; + pr_debug("ASM_DATA_EVENT_WRITE_DONE_V2\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + else + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + + buf = prtd->audio_client->port[IN].buf; + 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); + atomic_set(&prtd->pending_buffer, 0); + break; + } + case ASM_DATA_EVENT_RENDERED_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_V2: { + if (!atomic_read(&prtd->pending_buffer)) + break; + if (runtime->status->hw_ptr >= + runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, prtd->pcm_count); + buf = prtd->audio_client->port[IN].buf; + param.paddr = (unsigned long)buf[prtd->out_head].phys; + 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].phys; + 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); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } + spin_unlock_irqrestore(&the_locks.event_lock, flag); +} + +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; + prtd->out_head = 0; + 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: + prtd->pcm_irq_pos = 0; + 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; + 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; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + 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; + atomic_set(&prtd->pending_buffer, 1); + runtime->private_data = prtd; + lpa_audio.prtd = prtd; + lpa_set_volume(lpa_audio.volume); + ret = q6asm_set_softpause(lpa_audio.prtd->audio_client, &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(lpa_audio.prtd->audio_client, &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int lpa_set_volume(unsigned volume) +{ + int rc = 0; + if (lpa_audio.prtd && lpa_audio.prtd->audio_client) { + rc = q6asm_set_volume(lpa_audio.prtd->audio_client, volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + lpa_audio.volume = volume; + return rc; +} + +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 rc = 0; + + /* + If routing is still enabled, we need to issue EOS to + the DSP + To issue EOS to dsp, we need to be run state otherwise + EOS is not honored. + */ + if (msm_routing_check_backend_enabled(soc_prtd->dai_link->be_id)) { + rc = q6asm_run(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->pending_buffer, 0); + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + pr_debug("%s\n", __func__); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("EOS cmd timeout\n"); + prtd->pcm_irq_pos = 0; + } + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + lpa_audio.prtd = NULL; + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + 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_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; + + 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) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +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 == NULL || buf[0].data == NULL) + 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 int msm_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.rendered = (size_t)(temp & 0xFFFFFFFF); + tstamp.decoded = (size_t)((temp >> 32) & 0xFFFFFFFF); + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:lsb = %d, msb = %d," + "timestamp = %lld,\n", + __func__, tstamp.rendered, tstamp.decoded, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = msm_pcm_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) +{ + spin_lock_init(&the_locks.event_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); + + 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/qdsp6v2/msm-pcm-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..f94e6c11fcb87f4c40587e8355152e1b615cda3c --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c @@ -0,0 +1,725 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-v2.h" +#include "msm-pcm-routing-v2.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_PERIOD_SIZE 2048 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 512 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .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 = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .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 = PLAYBACK_NUM_PERIODS * PLAYBACK_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .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[CAPTURE_NUM_PERIODS][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; + uint32_t idx = 0; + uint32_t size = 0; + + pr_err("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE_V2\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_nolock(IN, + prtd->audio_client, + &size, &idx)) { + 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_EVENT_RENDERED_EOS: + pr_debug("ASM_DATA_EVENT_RENDERED_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE_V2: { + pr_debug("ASM_DATA_EVENT_READ_DONE_V2\n"); + pr_debug("token = 0x%08x\n", token); + in_frame_info[token][0] = payload[4]; + in_frame_info[token][1] = payload[5]; + 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_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + 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); + }; + } + atomic_set(&prtd->start, 1); + break; + default: + pr_debug("%s:Payload = [0x%x]stat[0x%x]\n", + __func__, payload[0], payload[1]); + 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); + 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; + } + 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) { + runtime->hw = msm_pcm_hardware_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) { + runtime->hw = msm_pcm_hardware_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_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); + + 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_cmd(prtd->audio_client, CMD_CLOSE); + 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_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_cmd(prtd->audio_client, CMD_CLOSE); + 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_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) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +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; +pr_err("%s: before buf alloc\n", __func__); + 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; + } +pr_err("%s: after buf alloc\n", __func__); + buf = prtd->audio_client->port[dir].buf; + if (buf == NULL || buf[0].data == NULL) + 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, + .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/qdsp6v2/msm-pcm-q6-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..44395b71ad5e7241069d18644f41ee5c17419090 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2012 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 { + spinlock_t event_lock; + 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; + atomic_t pending_buffer; +}; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..2eebae5613c797988625f627251c2edcf7d90c7a --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c @@ -0,0 +1,1834 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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-routing-v2.h" +#include "../qdsp6/q6voice.h" + +struct msm_pcm_routing_bdai_data { + u16 port_id; /* AFE port ID */ + u8 active; /* track if this backend is enabled */ + struct snd_pcm_hw_params *hw_params; /* to get freq and channel mode */ + unsigned long fe_sessions; /* Front-end sessions */ + unsigned long port_sessions; /* track Tx BE ports -> Rx BE */ +}; + +#define INVALID_SESSION -1 +#define SESSION_TYPE_RX 0 +#define SESSION_TYPE_TX 1 + +static struct mutex routing_lock; + +static int fm_switch_enable; + +#define INT_FM_RX_VOL_MAX_STEPS 100 +#define INT_FM_RX_VOL_GAIN 2000 + +static int msm_route_fm_vol_control; +static const DECLARE_TLV_DB_SCALE(fm_rx_vol_gain, 0, + INT_FM_RX_VOL_MAX_STEPS, 0); + +#define INT_RX_VOL_MAX_STEPS 100 +#define INT_RX_VOL_GAIN 0x2000 + +static int msm_route_lpa_vol_control; +static const DECLARE_TLV_DB_SCALE(lpa_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS, 0); + +static int msm_route_multimedia2_vol_control; +static const DECLARE_TLV_DB_SCALE(multimedia2_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS, 0); + +static int msm_route_compressed_vol_control; +static const DECLARE_TLV_DB_SCALE(compressed_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS, 0); + + + +/* Equal to Frontend after last of the MULTIMEDIA SESSIONS */ +#define MAX_EQ_SESSIONS MSM_FRONTEND_DAI_CS_VOICE + +enum { + EQ_BAND1 = 0, + EQ_BAND2, + EQ_BAND3, + EQ_BAND4, + EQ_BAND5, + EQ_BAND6, + EQ_BAND7, + EQ_BAND8, + EQ_BAND9, + EQ_BAND10, + EQ_BAND11, + EQ_BAND12, + EQ_BAND_MAX, +}; + +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; +} __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[EQ_BAND_MAX]; +} __packed; + +struct msm_audio_eq_stream_config eq_data[MAX_EQ_SESSIONS]; + +static void msm_send_eq_values(int eq_idx); +/* This array is indexed by back-end DAI ID defined in msm-pcm-routing.h + * If new back-end is defined, add new back-end DAI ID at the end of enum + */ +static struct msm_pcm_routing_bdai_data msm_bedais[MSM_BACKEND_DAI_MAX] = { + { PRIMARY_I2S_RX, 0, NULL, 0, 0}, + { PRIMARY_I2S_TX, 0, NULL, 0, 0}, + { SLIMBUS_0_RX, 0, NULL, 0, 0}, + { SLIMBUS_0_TX, 0, NULL, 0, 0}, + { HDMI_RX, 0, NULL, 0, 0}, + { INT_BT_SCO_RX, 0, NULL, 0, 0}, + { INT_BT_SCO_TX, 0, NULL, 0, 0}, + { INT_FM_RX, 0, NULL, 0, 0}, + { INT_FM_TX, 0, NULL, 0, 0}, + { RT_PROXY_PORT_001_RX, 0, NULL, 0, 0}, + { RT_PROXY_PORT_001_TX, 0, NULL, 0, 0}, + { PCM_RX, 0, NULL, 0, 0}, + { PCM_TX, 0, NULL, 0, 0}, + { VOICE_PLAYBACK_TX, 0, NULL, 0, 0}, + { VOICE_RECORD_RX, 0, NULL, 0, 0}, + { VOICE_RECORD_TX, 0, NULL, 0, 0}, + { MI2S_RX, 0, NULL, 0, 0}, + { SECONDARY_I2S_RX, 0, NULL, 0, 0}, + { SLIMBUS_1_RX, 0, NULL, 0, 0}, + { SLIMBUS_1_TX, 0, NULL, 0, 0}, + { SLIMBUS_INVALID, 0, NULL, 0, 0}, +}; + + +/* Track ASM playback & capture sessions of DAI */ +static int fe_dai_map[MSM_FRONTEND_DAI_MM_SIZE][2] = { + /* MULTIMEDIA1 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA2 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA3 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA4 */ + {INVALID_SESSION, INVALID_SESSION}, +}; + +static void msm_pcm_routing_build_matrix(int fedai_id, int dspst_id, + int path_type) +{ + int i, port_type; + struct route_payload payload; + + payload.num_copps = 0; + port_type = (path_type == ADM_PATH_PLAYBACK ? + MSM_AFE_PORT_TYPE_RX : MSM_AFE_PORT_TYPE_TX); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((afe_get_port_type(msm_bedais[i].port_id) == + port_type) && + msm_bedais[i].active && (test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); +} + +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, int stream_type) +{ + int i, session_type, path_type, port_type; + struct route_payload payload; + u32 channels; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID %d\n", __func__, fedai_id); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + port_type = MSM_AFE_PORT_TYPE_TX; + } + + mutex_lock(&routing_lock); + + payload.num_copps = 0; /* only RX needs to use payload */ + fe_dai_map[fedai_id][session_type] = dspst_id; + /* re-enable EQ if active */ + if (eq_data[fedai_id].enable) + msm_send_eq_values(fedai_id); + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((afe_get_port_type(msm_bedais[i].port_id) == + port_type) && msm_bedais[i].active && + (test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) { + + channels = params_channels(msm_bedais[i].hw_params); + + if ((stream_type == SNDRV_PCM_STREAM_PLAYBACK) && + (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[i].port_id, + path_type, + params_rate(msm_bedais[i].hw_params), + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[i].port_id, + path_type, + params_rate(msm_bedais[i].hw_params), + params_channels(msm_bedais[i].hw_params), + DEFAULT_COPP_TOPOLOGY); + + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + } + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); + + mutex_unlock(&routing_lock); +} + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type) +{ + int i, port_type, session_type; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + port_type = MSM_AFE_PORT_TYPE_RX; + session_type = SESSION_TYPE_RX; + } else { + port_type = MSM_AFE_PORT_TYPE_TX; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((afe_get_port_type(msm_bedais[i].port_id) == + port_type) && msm_bedais[i].active && + (test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) + adm_close(msm_bedais[i].port_id); + } + + fe_dai_map[fedai_id][session_type] = INVALID_SESSION; + + mutex_unlock(&routing_lock); +} + +/* Check if FE/BE route is set */ +static bool msm_pcm_routing_route_is_set(u16 be_id, u16 fe_id) +{ + bool rc = false; + + if (fe_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return rc; + } + + if (test_bit(fe_id, &msm_bedais[be_id].fe_sessions)) + rc = true; + + return rc; +} + +static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) +{ + int session_type, path_type; + u32 channels; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (val > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (afe_get_port_type(msm_bedais[reg].port_id) == + MSM_AFE_PORT_TYPE_RX) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + } + + mutex_lock(&routing_lock); + + if (set) { + set_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + + channels = params_channels(msm_bedais[reg].hw_params); + + if ((session_type == SESSION_TYPE_RX) && (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[reg].port_id, + path_type, + params_rate(msm_bedais[reg].hw_params), + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[reg].port_id, + path_type, + params_rate(msm_bedais[reg].hw_params), + params_channels(msm_bedais[reg].hw_params), + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } else { + clear_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + adm_close(msm_bedais[reg].port_id); + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } + mutex_unlock(&routing_lock); +} + +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, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_info("%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_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + + if (ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == false) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else if (!ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == true) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + pr_info("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 1; +} + +static void msm_pcm_routing_process_voice(u16 reg, u16 val, int set) +{ + return; +} + +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; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + 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_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + 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_voice_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + 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_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + mutex_lock(&routing_lock); + set_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + mutex_lock(&routing_lock); + clear_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 1; +} + +static int msm_routing_get_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = fm_switch_enable; + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int msm_routing_put_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + if (ucontrol->value.integer.value[0]) + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + else + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + fm_switch_enable = ucontrol->value.integer.value[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, &msm_bedais[mc->reg].port_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, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + set_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } else { + afe_loopback(0, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + clear_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } + + return 1; +} + +static int msm_routing_get_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_fm_vol_control; + return 0; +} + +static int msm_routing_set_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + afe_loopback_gain(INT_FM_TX , ucontrol->value.integer.value[0]); + + msm_route_fm_vol_control = ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_lpa_vol_control; + return 0; +} + +static int msm_routing_set_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!lpa_set_volume(ucontrol->value.integer.value[0])) + msm_route_lpa_vol_control = + ucontrol->value.integer.value[0]; + + return 0; + +} + +static int msm_routing_get_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_multimedia2_vol_control; + return 0; +} + +static int msm_routing_set_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!multi_ch_pcm_set_volume(ucontrol->value.integer.value[0])) + msm_route_multimedia2_vol_control = + ucontrol->value.integer.value[0]; + return 0; +} + +static int msm_routing_get_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_compressed_vol_control; + return 0; +} + +static int msm_routing_set_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!compressed_set_volume(ucontrol->value.integer.value[0])) + msm_route_compressed_vol_control = + ucontrol->value.integer.value[0]; + return 0; +} + +static void msm_send_eq_values(int eq_idx) +{ + int result; + struct audio_client *ac = + q6asm_get_audio_client(fe_dai_map[eq_idx][SESSION_TYPE_RX]); + + if (ac == NULL) { + pr_err("%s: Could not get audio client for session: %d\n", + __func__, fe_dai_map[eq_idx][SESSION_TYPE_RX]); + goto done; + } + + result = q6asm_equalizer(ac, &eq_data[eq_idx]); + + if (result < 0) + pr_err("%s: Call to ASM equalizer failed, returned = %d\n", + __func__, result); +done: + return; +} + +static int msm_routing_get_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].enable; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, eq_data[eq_idx].enable); + return 0; +} + +static int msm_routing_put_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].enable = value; + + msm_send_eq_values(eq_idx); + return 0; +} + +static int msm_routing_get_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].num_bands; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, eq_data[eq_idx].num_bands); + return eq_data[eq_idx].num_bands; +} + +static int msm_routing_put_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].num_bands = value; + return 0; +} + +static int msm_routing_get_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + eq_data[eq_idx].eq_bands[band_idx].band_idx; + ucontrol->value.integer.value[1] = + eq_data[eq_idx].eq_bands[band_idx].filter_type; + ucontrol->value.integer.value[2] = + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz; + ucontrol->value.integer.value[3] = + eq_data[eq_idx].eq_bands[band_idx].filter_gain; + ucontrol->value.integer.value[4] = + eq_data[eq_idx].eq_bands[band_idx].q_factor; + + pr_debug("%s: band_idx = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].band_idx); + pr_debug("%s: filter_type = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_type); + pr_debug("%s: center_freq_hz = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz); + pr_debug("%s: filter_gain = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_gain); + pr_debug("%s: q_factor = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].q_factor); + return 0; +} + +static int msm_routing_put_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + eq_data[eq_idx].eq_bands[band_idx].band_idx = + ucontrol->value.integer.value[0]; + eq_data[eq_idx].eq_bands[band_idx].filter_type = + ucontrol->value.integer.value[1]; + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz = + ucontrol->value.integer.value[2]; + eq_data[eq_idx].eq_bands[band_idx].filter_gain = + ucontrol->value.integer.value[3]; + eq_data[eq_idx].eq_bands[band_idx].q_factor = + ucontrol->value.integer.value[4]; + return 0; +} + +static const struct snd_kcontrol_new pri_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_PRI_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SEC_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_MI2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + /* incall music delivery mixer */ +static const struct snd_kcontrol_new incall_music_delivery_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + 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", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 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", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_DL", MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_UL", MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul2_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 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", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_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 sec_i2s_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SEC_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", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_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", MSM_BACKEND_DAI_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", MSM_BACKEND_DAI_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new aux_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new hdmi_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new stub_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_INVALID, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voice", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voice", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voice", + MSM_BACKEND_DAI_INT_BT_SCO_TX, MSM_FRONTEND_DAI_CS_VOICE, 1, 0, + msm_routing_get_voice_mixer, msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voice", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voice", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 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", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voip", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voip", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voip", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voip", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_stub_mixer_controls[] = { + SOC_SINGLE_EXT("STUB_TX_HL", MSM_BACKEND_DAI_INVALID, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new sbus_0_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_SLIMBUS_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", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sbus_1_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new bt_sco_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new fm_switch_mixer_controls = + SOC_SINGLE_EXT("Switch", SND_SOC_NOPM, + 0, 1, 0, msm_routing_get_switch_mixer, + msm_routing_put_switch_mixer); + +static const struct snd_kcontrol_new int_fm_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("Internal FM RX Volume", SND_SOC_NOPM, 0, + INT_FM_RX_VOL_GAIN, 0, msm_routing_get_fm_vol_mixer, + msm_routing_set_fm_vol_mixer, fm_rx_vol_gain), +}; + +static const struct snd_kcontrol_new lpa_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("LPA RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_lpa_vol_mixer, + msm_routing_set_lpa_vol_mixer, lpa_rx_vol_gain), +}; + +static const struct snd_kcontrol_new multimedia2_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("HIFI2 RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_multimedia2_vol_mixer, + msm_routing_set_multimedia2_vol_mixer, multimedia2_rx_vol_gain), +}; + +static const struct snd_kcontrol_new compressed_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("COMPRESSED RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_compressed_vol_mixer, + msm_routing_set_compressed_vol_mixer, compressed_rx_vol_gain), +}; + +static const struct snd_kcontrol_new eq_enable_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), +}; + +static const struct snd_kcontrol_new eq_band_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), +}; + +static const struct snd_kcontrol_new eq_coeff_mixer_controls[] = { + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_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("MM_DL4", "MultiMedia4 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_OUT("MM_UL2", "MultiMedia2 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), + SND_SOC_DAPM_AIF_IN("HDMI_DL_HL", "HDMI_HOSTLESS Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUXPCM_DL_HL", "AUXPCM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("AUXPCM_UL_HL", "AUXPCM_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("SEC_I2S_RX", "Secondary 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_OUT("MI2S_RX", "MI2S 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), + SND_SOC_DAPM_AIF_OUT("PCM_RX", "AFE Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("PCM_TX", "AFE Capture", + 0, 0, 0 , 0), + /* incall */ + SND_SOC_DAPM_AIF_OUT("VOICE_PLAYBACK_TX", "Voice Farend Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_TX", "Voice Uplink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_RX", "Voice Downlink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("AUX_PCM_RX", "AUX PCM Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUX_PCM_TX", "AUX PCM Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOICE_STUB_DL", "VOICE_STUB Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOICE_STUB_UL", "VOICE_STUB Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("STUB_RX", "Stub Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("STUB_TX", "Stub Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_RX", "Slimbus1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_1_TX", "Slimbus1 Capture", 0, 0, 0, 0), + + /* Switch Definitions */ + SND_SOC_DAPM_SWITCH("SLIMBUS_DL_HL", SND_SOC_NOPM, 0, 0, + &fm_switch_mixer_controls), + /* 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("SEC_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_i2s_rx_mixer_controls, ARRAY_SIZE(sec_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("MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + mi2s_rx_mixer_controls, ARRAY_SIZE(mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, + mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + auxpcm_rx_mixer_controls, ARRAY_SIZE(auxpcm_rx_mixer_controls)), + /* incall */ + SND_SOC_DAPM_MIXER("Incall_Music Audio Mixer", SND_SOC_NOPM, 0, 0, + incall_music_delivery_mixer_controls, + ARRAY_SIZE(incall_music_delivery_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("SEC_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + sec_i2s_rx_voice_mixer_controls, + ARRAY_SIZE(sec_i2s_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("AFE_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + afe_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(afe_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + aux_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(aux_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + hdmi_rx_voice_mixer_controls, + ARRAY_SIZE(hdmi_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("AFE_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + afe_pcm_rx_mixer_controls, ARRAY_SIZE(afe_pcm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice Stub Tx Mixer", SND_SOC_NOPM, 0, 0, + tx_voice_stub_mixer_controls, ARRAY_SIZE(tx_voice_stub_mixer_controls)), + SND_SOC_DAPM_MIXER("STUB_RX Mixer", SND_SOC_NOPM, 0, 0, + stub_rx_mixer_controls, ARRAY_SIZE(stub_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, ARRAY_SIZE(slimbus_1_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)), + SND_SOC_DAPM_MIXER("AUXPCM_RX Port Mixer", + SND_SOC_NOPM, 0, 0, auxpcm_rx_port_mixer_controls, + ARRAY_SIZE(auxpcm_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Port Mixer", SND_SOC_NOPM, 0, 0, + sbus_1_rx_port_mixer_controls, + ARRAY_SIZE(sbus_1_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Port Mixer", SND_SOC_NOPM, 0, 0, + bt_sco_rx_port_mixer_controls, + ARRAY_SIZE(bt_sco_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_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PRI_I2S_RX", NULL, "PRI_RX Audio Mixer"}, + + {"SEC_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SEC_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SEC_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SEC_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SEC_I2S_RX", NULL, "SEC_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 Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"HDMI Mixer", "MultiMedia1", "MM_DL1"}, + {"HDMI Mixer", "MultiMedia2", "MM_DL2"}, + {"HDMI Mixer", "MultiMedia3", "MM_DL3"}, + {"HDMI Mixer", "MultiMedia4", "MM_DL4"}, + {"HDMI", NULL, "HDMI Mixer"}, + + /* incall */ + {"Incall_Music Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"Incall_Music Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"VOICE_PLAYBACK_TX", NULL, "Incall_Music Audio Mixer"}, + + {"MultiMedia1 Mixer", "VOC_REC_UL", "INCALL_RECORD_TX"}, + {"MultiMedia1 Mixer", "VOC_REC_DL", "INCALL_RECORD_RX"}, + {"MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"MI2S_RX", NULL, "MI2S_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"}, + {"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"MultiMedia1 Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"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"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"INT_FM_RX", NULL, "INTERNAL_FM_RX Audio Mixer"}, + + {"AFE_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PCM_RX", NULL, "AFE_PCM_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"MultiMedia1 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + + {"MultiMedia1 Mixer", "AFE_PCM_TX", "PCM_TX"}, + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + {"MultiMedia2 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"MM_UL2", NULL, "MultiMedia2 Mixer"}, + + {"AUX_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX Audio Mixer"}, + + {"PRI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"PRI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PRI_I2S_RX", NULL, "PRI_RX_Voice Mixer"}, + + {"SEC_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SEC_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SEC_I2S_RX", NULL, "SEC_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"}, + + {"AFE_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AFE_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PCM_RX", NULL, "AFE_PCM_RX_Voice Mixer"}, + + {"AUX_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AUX_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX_Voice Mixer"}, + + {"HDMI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"HDMI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"HDMI", NULL, "HDMI_RX_Voice Mixer"}, + {"HDMI", NULL, "HDMI_DL_HL"}, + + {"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"}, + {"Voice_Tx Mixer", "AFE_PCM_TX_Voice", "PCM_TX"}, + {"Voice_Tx Mixer", "AUX_PCM_TX_Voice", "AUX_PCM_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_Tx Mixer", "AFE_PCM_TX_Voip", "PCM_TX"}, + {"Voip_Tx Mixer", "AUX_PCM_TX_Voip", "AUX_PCM_TX"}, + + {"VOIP_UL", NULL, "Voip_Tx Mixer"}, + {"SLIMBUS_DL_HL", "Switch", "SLIM0_DL_HL"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_DL_HL"}, + {"SLIM0_UL_HL", NULL, "SLIMBUS_0_TX"}, + {"INT_FM_RX", NULL, "INTFM_DL_HL"}, + {"INTFM_UL_HL", NULL, "INT_FM_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_DL_HL"}, + {"AUXPCM_UL_HL", NULL, "AUX_PCM_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 Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Port Mixer"}, + + {"AUXPCM_RX Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"AUXPCM_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_RX Port Mixer"}, + + {"Voice Stub Tx Mixer", "STUB_TX_HL", "STUB_TX"}, + {"Voice Stub Tx Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"Voice Stub Tx Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"VOICE_STUB_UL", NULL, "Voice Stub Tx Mixer"}, + + {"STUB_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"STUB_RX", NULL, "STUB_RX Mixer"}, + {"SLIMBUS_1_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Mixer"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + + {"SLIMBUS_1_RX Port Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Port Mixer"}, + {"INTERNAL_BT_SCO_RX Port Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Port Mixer"}, +}; + +static int msm_pcm_routing_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + mutex_lock(&routing_lock); + msm_bedais[be_id].hw_params = params; + mutex_unlock(&routing_lock); + return 0; +} + +static int msm_pcm_routing_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, session_type; + struct msm_pcm_routing_bdai_data *bedai; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + bedai = &msm_bedais[be_id]; + + session_type = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + 0 : 1); + + mutex_lock(&routing_lock); + + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) + adm_close(bedai->port_id); + } + + bedai->active = 0; + bedai->hw_params = NULL; + + mutex_unlock(&routing_lock); + + return 0; +} + +static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, path_type, session_type; + struct msm_pcm_routing_bdai_data *bedai; + u32 channels; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + + bedai = &msm_bedais[be_id]; + + if (bedai->hw_params == NULL) { + pr_err("%s: HW param is not configured", __func__); + return -EINVAL; + } + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + path_type = ADM_PATH_PLAYBACK; + session_type = SESSION_TYPE_RX; + } else { + path_type = ADM_PATH_LIVE_REC; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + if (bedai->active == 1) + goto done; /* Ignore prepare if back-end already active */ + + /* AFE port is not active at this point. However, still + * go ahead setting active flag under the notion that + * QDSP6 is able to handle ADM starting before AFE port + * is started. + */ + bedai->active = 1; + + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) { + + channels = params_channels(bedai->hw_params); + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (channels > 2)) + adm_multi_ch_copp_open(bedai->port_id, + path_type, + params_rate(bedai->hw_params), + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(bedai->port_id, + path_type, + params_rate(bedai->hw_params), + params_channels(bedai->hw_params), + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(i, + fe_dai_map[i][session_type], path_type); + } + } + +done: + mutex_unlock(&routing_lock); + + return 0; +} + +static struct snd_pcm_ops msm_routing_pcm_ops = { + .hw_params = msm_pcm_routing_hw_params, + .close = msm_pcm_routing_close, + .prepare = msm_pcm_routing_prepare, +}; + +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); + + snd_soc_add_platform_controls(platform, + int_fm_vol_mixer_controls, + ARRAY_SIZE(int_fm_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + lpa_vol_mixer_controls, + ARRAY_SIZE(lpa_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_enable_mixer_controls, + ARRAY_SIZE(eq_enable_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_band_mixer_controls, + ARRAY_SIZE(eq_band_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_coeff_mixer_controls, + ARRAY_SIZE(eq_coeff_mixer_controls)); + + snd_soc_add_platform_controls(platform, + multimedia2_vol_mixer_controls, + ARRAY_SIZE(multimedia2_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + compressed_vol_mixer_controls, + ARRAY_SIZE(compressed_vol_mixer_controls)); + + 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), +}; + +int msm_routing_check_backend_enabled(int fedai_id) +{ + int i; + if (fedai_id >= MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return 0; + } + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) { + return msm_bedais[i].active; + } + } + return 0; +} + +static int __init msm_soc_routing_platform_init(void) +{ + mutex_init(&routing_lock); + 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/qdsp6v2/msm-pcm-routing-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h new file mode 100644 index 0000000000000000000000000000000000000000..b9717870fb0970a5c7a58da0a5d53682a8190666 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY 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" +#define LPASS_BE_AFE_PCM_RX "(Backend) RT_PROXY_DAI_001_RX" +#define LPASS_BE_AFE_PCM_TX "(Backend) RT_PROXY_DAI_002_TX" +#define LPASS_BE_AUXPCM_RX "(Backend) AUX_PCM_RX" +#define LPASS_BE_AUXPCM_TX "(Backend) AUX_PCM_TX" +#define LPASS_BE_VOICE_PLAYBACK_TX "(Backend) VOICE_PLAYBACK_TX" +#define LPASS_BE_INCALL_RECORD_RX "(Backend) INCALL_RECORD_TX" +#define LPASS_BE_INCALL_RECORD_TX "(Backend) INCALL_RECORD_RX" +#define LPASS_BE_SEC_I2S_RX "(Backend) SECONDARY_I2S_RX" + +#define LPASS_BE_MI2S_RX "(Backend) MI2S_RX" +#define LPASS_BE_STUB_RX "(Backend) STUB_RX" +#define LPASS_BE_STUB_TX "(Backend) STUB_TX" +#define LPASS_BE_SLIMBUS_1_RX "(Backend) SLIMBUS_1_RX" +#define LPASS_BE_SLIMBUS_1_TX "(Backend) SLIMBUS_1_TX" + +/* For multimedia front-ends, asm session is allocated dynamically. + * Hence, asm session/multimedia front-end mapping has to be maintained. + * Due to this reason, additional multimedia front-end must be placed before + * non-multimedia front-ends. + */ + +enum { + MSM_FRONTEND_DAI_MULTIMEDIA1 = 0, + MSM_FRONTEND_DAI_MULTIMEDIA2, + MSM_FRONTEND_DAI_MULTIMEDIA3, + MSM_FRONTEND_DAI_MULTIMEDIA4, + MSM_FRONTEND_DAI_CS_VOICE, + MSM_FRONTEND_DAI_VOIP, + MSM_FRONTEND_DAI_AFE_RX, + MSM_FRONTEND_DAI_AFE_TX, + MSM_FRONTEND_DAI_VOICE_STUB, + MSM_FRONTEND_DAI_MAX, +}; + +#define MSM_FRONTEND_DAI_MM_SIZE (MSM_FRONTEND_DAI_MULTIMEDIA4 + 1) +#define MSM_FRONTEND_DAI_MM_MAX_ID MSM_FRONTEND_DAI_MULTIMEDIA4 + +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_AFE_PCM_RX, + MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, + MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_BACKEND_DAI_MI2S_RX, + MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_BACKEND_DAI_INVALID, + MSM_BACKEND_DAI_MAX, +}; + +/* 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); + +int lpa_set_volume(unsigned volume); + +int msm_routing_check_backend_enabled(int fedai_id); + +int multi_ch_pcm_set_volume(unsigned volume); + +int compressed_set_volume(unsigned volume); + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/qdsp6v2/q6adm.c b/sound/soc/msm/qdsp6v2/q6adm.c new file mode 100644 index 0000000000000000000000000000000000000000..691ca218f8553b444b92ffd3b40f2328417c388a --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6adm.c @@ -0,0 +1,621 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF + +struct adm_ctl { + void *apr; + atomic_t copp_id[Q6_AFE_MAX_PORTS]; + atomic_t copp_cnt[Q6_AFE_MAX_PORTS]; + atomic_t copp_stat[Q6_AFE_MAX_PORTS]; + u32 mem_map_handle[Q6_AFE_MAX_PORTS]; + wait_queue_head_t wait[Q6_AFE_MAX_PORTS]; +}; + +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 < Q6_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 PL#0[%x], PL#1[%x], size = %d\n", __func__, + data->opcode, payload[0], payload[1], + data->payload_size); + + if (data->payload_size) { + index = q6audio_get_port_index(data->token); + if (index < 0 || index >= Q6_AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, data->token); + return 0; + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + pr_debug("APR_BASIC_RSP_RESULT\n"); + switch (payload[0]) { + case ADM_CMD_SET_PP_PARAMS_V5: + if (rtac_make_adm_callback( + payload, data->payload_size)) + pr_debug("%s: payload[0]: 0x%x\n", + __func__, payload[0]); + break; + case ADM_CMD_DEVICE_CLOSE_V5: + case ADM_CMD_SHARED_MEM_UNMAP_REGIONS: + case ADM_CMD_SHARED_MEM_MAP_REGIONS: + case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: + pr_debug("ADM_CMD_MATRIX_MAP_ROUTINGS\n"); + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait[index]); + break; + default: + pr_err("%s: Unknown Cmd: 0x%x\n", __func__, + payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ADM_CMDRSP_DEVICE_OPEN_V5: { + struct adm_cmd_rsp_device_open_v5 *open = + (struct adm_cmd_rsp_device_open_v5 *)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[index]); + 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[index]); + } + break; + case ADM_CMD_GET_PP_PARAMS_V5: + pr_debug("%s: ADM_CMD_GET_PP_PARAMS_V5\n", __func__); + rtac_make_adm_callback(payload, + data->payload_size); + break; + default: + pr_err("%s: Unknown cmd:0x%x\n", __func__, + data->opcode); + break; + } + } + return 0; +} + +/* TODO: send_adm_cal_block function to be defined + when calibration available for 8974 */ +static void send_adm_cal(int port_id, int path) +{ + /* function to be defined when calibration available for 8974 */ + pr_debug("%s\n", __func__); +} + +int adm_open(int port_id, int path, int rate, int channel_mode, int topology) +{ + struct adm_cmd_device_open_v5 open; + int ret = 0; + int index; + int tmp_port = q6audio_get_port_id(port_id); + + pr_debug("%s: port %d path:%d rate:%d mode:%d\n", __func__, + port_id, path, rate, channel_mode); + + port_id = q6audio_convert_virtual_to_portid(port_id); + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = q6audio_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; + } + rtac_set_adm_handle(this_adm.apr); + } + + + /* 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 = tmp_port; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = tmp_port; + open.hdr.token = port_id; + open.hdr.opcode = ADM_CMD_DEVICE_OPEN_V5; + + open.mode_of_operation = path; + /* Reserved for future use, need to set this to 0 */ + open.flags = 0x00; + open.endpoint_id_1 = tmp_port; + open.endpoint_id_2 = 0xFFFF; + + /* convert path to acdb path */ + if (path == ADM_PATH_PLAYBACK) + open.topology_id = get_adm_rx_topology(); + else { + open.topology_id = get_adm_tx_topology(); + if ((open.topology_id == + VPM_TX_SM_ECNS_COPP_TOPOLOGY) || + (open.topology_id == + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY)) + rate = 16000; + } + + if (open.topology_id == 0) + open.topology_id = topology; + + open.dev_num_channel = channel_mode & 0x00FF; + open.bit_width = 16; + open.sample_rate = rate; + memset(open.dev_channel_mapping, 0, 8); + + if (channel_mode == 1) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FC; + } else if (channel_mode == 2) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channel_mode == 6) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + open.dev_channel_mapping[2] = PCM_CHANNEL_LFE; + open.dev_channel_mapping[3] = PCM_CHANNEL_FC; + open.dev_channel_mapping[4] = PCM_CHANNEL_LB; + open.dev_channel_mapping[5] = PCM_CHANNEL_RB; + } else { + pr_err("%s invalid num_chan %d\n", __func__, + channel_mode); + return -EINVAL; + } + + pr_debug("%s: port_id=%d rate=%d" + "topology_id=0x%X\n", __func__, open.endpoint_id_1, \ + open.sample_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 for[%d] failed\n", + __func__, tmp_port, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait[index], + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM open failed for port %d" + "for [%d]\n", __func__, tmp_port, port_id); + ret = -EINVAL; + goto fail_cmd; + } + } + atomic_inc(&this_adm.copp_cnt[index]); + return 0; + +fail_cmd: + + return ret; +} + + +int adm_multi_ch_copp_open(int port_id, int path, int rate, int channel_mode, + int topology) +{ + int ret = 0; + + ret = adm_open(port_id, path, rate, channel_mode, topology); + + return ret; +} + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id) +{ + struct adm_cmd_matrix_map_routings_v5 *route; + struct adm_session_map_node_v5 *node; + uint32_t *copps_list; + int cmd_size = 0; + int ret = 0, i = 0; + void *payload = NULL; + void *matrix_map = NULL; + + /* Assumes port_ids have already been validated during adm_open */ + int index = q6audio_get_port_index(copp_id); + if (index < 0 || index >= Q6_AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, copp_id); + return 0; + } + cmd_size = (sizeof(struct adm_cmd_matrix_map_routings_v5) + + sizeof(struct adm_session_map_node_v5) + + (sizeof(uint32_t) * num_copps)); + matrix_map = kzalloc(cmd_size, GFP_KERNEL); + if (matrix_map == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + ret = -EINVAL; + return ret; + } + route = (struct adm_cmd_matrix_map_routings_v5 *)matrix_map; + + pr_debug("%s: session 0x%x path:%d num_copps:%d port_id[0] :%d coppid[%d]\n", + __func__, session_id, path, num_copps, port_id[0], copp_id); + + 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 = cmd_size; + 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_V5; + route->num_sessions = 1; + + switch (path) { + case 0x1: + route->matrix_id = ADM_MATRIX_ID_AUDIO_RX; + break; + case 0x2: + case 0x3: + route->matrix_id = ADM_MATRIX_ID_AUDIO_TX; + break; + default: + pr_err("%s: Wrong path set[%d]\n", __func__, path); + break; + } + payload = ((u8 *)matrix_map + + sizeof(struct adm_cmd_matrix_map_routings_v5)); + node = (struct adm_session_map_node_v5 *)payload; + + node->session_id = session_id; + node->num_copps = num_copps; + payload = (u8 *)node + sizeof(struct adm_session_map_node_v5); + copps_list = (uint32_t *)payload; + for (i = 0; i < num_copps; i++) { + int tmp; + port_id[i] = q6audio_convert_virtual_to_portid(port_id[i]); + + tmp = q6audio_get_port_index(port_id[i]); + + + if (tmp >= 0 && tmp < Q6_AFE_MAX_PORTS) + copps_list[i] = + atomic_read(&this_adm.copp_id[tmp]); + pr_debug("%s: port_id[%d]: %d, index: %d act coppid[0x%x]\n", + __func__, i, port_id[i], tmp, + atomic_read(&this_adm.copp_id[tmp])); + } + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)matrix_map); + 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[index], + 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); + +fail_cmd: + kfree(matrix_map); + return ret; +} + +int adm_memory_map_regions(int port_id, + uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int ret = 0; + int i = 0; + int cmd_size = 0; + int index = 0; + + pr_debug("%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; + } + rtac_set_adm_handle(this_adm.apr); + } + + port_id = q6audio_convert_virtual_to_portid(port_id); + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s port id[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = q6audio_get_port_index(port_id); + + cmd_size = sizeof(struct avs_cmd_shared_mem_map_regions) + + sizeof(struct avs_shared_map_region_payload) + * 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 avs_cmd_shared_mem_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_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL & 0x00ff; + mmap_regions->num_regions = bufcnt & 0x00ff; + mmap_regions->property_flag = 0x00; + + pr_debug("%s: map_regions->num_regions = %d\n", __func__, + mmap_regions->num_regions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + mregions = (struct avs_shared_map_region_payload *)payload; + + for (i = 0; i < bufcnt; i++) { + mregions->shm_addr_lsw = buf_add[i]; + mregions->shm_addr_msw = 0x00; + mregions->mem_size_bytes = 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[index], + 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(int32_t port_id, uint32_t *buf_add, + uint32_t *bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_unmap_regions unmap_regions; + int ret = 0; + int cmd_size = 0; + int index = 0; + + pr_debug("%s\n", __func__); + + if (this_adm.apr == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + port_id = q6audio_convert_virtual_to_portid(port_id); + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = q6audio_get_port_index(port_id); + + 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_SHARED_MEM_UNMAP_REGIONS; + unmap_regions.mem_map_handle = this_adm.mem_map_handle[index]; + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) &unmap_regions); + 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[index], + 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: + return ret; +} + +int adm_get_copp_id(int port_index) +{ + pr_debug("%s\n", __func__); + + if (port_index < 0) { + pr_err("%s: invalid port_id = %d\n", __func__, port_index); + return -EINVAL; + } + + return atomic_read(&this_adm.copp_id[port_index]); +} + +int adm_close(int port_id) +{ + struct apr_hdr close; + + int ret = 0; + int index = 0; + + port_id = q6audio_convert_virtual_to_portid(port_id); + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + pr_debug("%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_DEVICE_CLOSE_V5; + + 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[index], + 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; + } + + rtac_remove_adm_device(port_id); + } + +fail_cmd: + return ret; +} + +static int __init adm_init(void) +{ + int i = 0; + this_adm.apr = NULL; + + for (i = 0; i < Q6_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); + init_waitqueue_head(&this_adm.wait[i]); + } + return 0; +} + +device_initcall(adm_init); diff --git a/sound/soc/msm/qdsp6v2/q6afe.c b/sound/soc/msm/qdsp6v2/q6afe.c new file mode 100644 index 0000000000000000000000000000000000000000..5b30e8e4615989b2dd534e04c5194426cf66c1d4 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6afe.c @@ -0,0 +1,1584 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or 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 + + +struct afe_ctl { + void *apr; + atomic_t state; + atomic_t status; + wait_queue_head_t wait[AFE_MAX_PORTS]; + void (*tx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void (*rx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void *tx_private_data; + void *rx_private_data; +}; + +static struct afe_ctl this_afe; + +static struct acdb_cal_block afe_cal_addr[MAX_AUDPROC_TYPES]; + +#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; + } + return 0; + } + pr_debug("%s:opcode = 0x%x cmd = 0x%x status = 0x%x\n", + __func__, data->opcode, + ((uint32_t *)(data->payload))[0], + ((uint32_t *)(data->payload))[1]); + if (data->payload_size) { + uint32_t *payload; + uint16_t port_id = 0; + payload = data->payload; + pr_debug("%s:opcode = 0x%x cmd = 0x%x status = 0x%x token=%d\n", + __func__, data->opcode, + payload[0], payload[1], data->token); + /* 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_CMD_DEVICE_STOP: + case AFE_PORT_CMD_DEVICE_START: + case AFE_PORT_CMD_SET_PARAM_V2: + case AFE_PSEUDOPORT_CMD_START: + case AFE_PSEUDOPORT_CMD_STOP: + case AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS: + case AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS: + case AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER: + atomic_set(&this_afe.state, 0); + wake_up(&this_afe.wait[data->token]); + break; + case AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER: + break; + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2: + port_id = RT_PROXY_PORT_001_TX; + break; + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2: + port_id = RT_PROXY_PORT_001_RX; + break; + default: + pr_err("%s:Unknown cmd 0x%x\n", __func__, + payload[0]); + break; + } + } else if (data->opcode == AFE_EVENT_RT_PROXY_PORT_STATUS) { + port_id = (uint16_t)(0x0000FFFF & payload[0]); + } + pr_debug("%s:port_id = %x\n", __func__, port_id); + switch (port_id) { + case RT_PROXY_PORT_001_TX: { + if (this_afe.tx_cb) { + this_afe.tx_cb(data->opcode, data->token, + data->payload, + this_afe.tx_private_data); + } + break; + } + case RT_PROXY_PORT_001_RX: { + if (this_afe.rx_cb) { + this_afe.rx_cb(data->opcode, data->token, + data->payload, + this_afe.rx_private_data); + } + break; + } + default: + break; + } + } + return 0; +} + + +int afe_get_port_type(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PCM_RX: + case SECONDARY_I2S_RX: + case MI2S_RX: + case HDMI_RX: + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case INT_BT_SCO_RX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case VOICE_PLAYBACK_TX: + case RT_PROXY_PORT_001_RX: + ret = MSM_AFE_PORT_TYPE_RX; + break; + + case PRIMARY_I2S_TX: + case PCM_TX: + case SECONDARY_I2S_TX: + case MI2S_TX: + case DIGI_MIC_TX: + case VOICE_RECORD_TX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case INT_FM_TX: + case VOICE_RECORD_RX: + case INT_BT_SCO_TX: + case RT_PROXY_PORT_001_TX: + ret = MSM_AFE_PORT_TYPE_TX; + break; + + default: + pr_err("%s: invalid port id %d\n", __func__, port_id); + ret = -EINVAL; + } + + return ret; +} + +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_param_id_i2s_cfg); + break; + case HDMI_RX: + ret_size = + SIZEOF_CFG_CMD(afe_param_id_hdmi_multi_chan_audio_cfg); + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + ret_size = SIZEOF_CFG_CMD(afe_param_id_slimbus_cfg); + break; + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + ret_size = SIZEOF_CFG_CMD(afe_param_id_rt_proxy_port_cfg); + break; + case PCM_RX: + case PCM_TX: + default: + ret_size = SIZEOF_CFG_CMD(afe_param_id_pcm_cfg); + break; + } + return ret_size; +} + +int afe_q6_interface_prepare(void) +{ + int ret = 0; + + pr_debug("%s:", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + } + } + return ret; +} +static void afe_send_cal_block(int32_t path, u16 port_id) +{ + /* To come back */ +} + +void afe_send_cal(u16 port_id) +{ + pr_debug("%s\n", __func__); + + if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_TX) + afe_send_cal_block(TX_CAL, port_id); + else if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_RX) + afe_send_cal_block(RX_CAL, port_id); +} + +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate) /* This function is no blocking */ +{ + struct afe_port_cmd_device_start start; + struct afe_audioif_config_command config; + int ret; + int cfg_type; + int index = 0; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + pr_err("%s: %d %d\n", __func__, port_id, rate); + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + 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 = index; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + cfg_type = AFE_PARAM_ID_PCM_CONFIG; + break; + case PCM_RX: + case PCM_TX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case HDMI_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; + default: + pr_err("%s: Invalid port id 0x%x\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + config.param.port_id = port_id; + config.param.payload_size = (afe_sizeof_cfg_cmd(port_id) + + sizeof(struct afe_port_param_data_v2)); + config.param.payload_address_lsw = 0x00; + config.param.payload_address_msw = 0x00; + config.param.mem_map_handle = 0x00; + config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE; + config.pdata.param_id = cfg_type; + config.pdata.param_size = afe_sizeof_cfg_cmd(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; + } + + /* send AFE cal */ + afe_send_cal(port_id); + + 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_DEVICE_START; + start.port_id = port_id; + + 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; + } + + return 0; + +fail_cmd: + return ret; +} + +int afe_open(u16 port_id, + union afe_port_config *afe_config, int rate) +{ + struct afe_port_cmd_device_start start; + struct afe_audioif_config_command config; + int ret = 0; + int cfg_type; + int index = 0; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + + pr_err("%s: %d %d\n", __func__, port_id, rate); + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + 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 = sizeof(config); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = index; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case PCM_RX: + case PCM_TX: + cfg_type = AFE_PARAM_ID_PCM_CONFIG; + break; + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case HDMI_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; + default: + pr_err("%s: Invalid port id 0x%x\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + config.param.port_id = q6audio_get_port_id(port_id); + config.param.payload_size = sizeof(config) - sizeof(struct apr_hdr) + - sizeof(config.param); + config.param.payload_address_lsw = 0x00; + config.param.payload_address_msw = 0x00; + config.param.mem_map_handle = 0x00; + config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE; + config.pdata.param_id = cfg_type; + config.pdata.param_size = sizeof(config.port); + + config.port = *afe_config; + pr_debug("%s: param PL size=%d iparam_size[%d][%d %d %d %d]" + " param_id[%x]\n", + __func__, config.param.payload_size, config.pdata.param_size, + sizeof(config), sizeof(config.param), sizeof(config.port), + sizeof(struct apr_hdr), config.pdata.param_id); + 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 opcode[0x%x]failed\n", + __func__, port_id, cfg_type); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait[index], + (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 = index; + start.hdr.opcode = AFE_PORT_CMD_DEVICE_START; + start.port_id = q6audio_get_port_id(port_id); + pr_debug("%s: cmd device start opcode[0x%x] port id[0x%x]\n", + __func__, start.hdr.opcode, start.port_id); + 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[index], + (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; + } + + return 0; +fail_cmd: + return ret; +} + +int afe_loopback(u16 enable, u16 rx_port, u16 tx_port) +{ + struct afe_loopback_cfg_v1 lb_cmd; + int ret = 0; + int index = 0; + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + index = q6audio_get_port_index(rx_port); + if (q6audio_validate_port(rx_port) < 0) + return -EINVAL; + + 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_SET_PARAM_V2; + lb_cmd.param.port_id = tx_port; + lb_cmd.param.payload_size = (sizeof(lb_cmd) - + sizeof(struct apr_hdr) - + sizeof(struct afe_port_cmd_set_param_v2)); + lb_cmd.param.payload_address_lsw = 0x00; + lb_cmd.param.payload_address_msw = 0x00; + lb_cmd.param.mem_map_handle = 0x00; + lb_cmd.pdata.module_id = AFE_MODULE_LOOPBACK; + lb_cmd.pdata.param_id = AFE_PARAM_ID_LOOPBACK_CONFIG; + lb_cmd.pdata.param_size = lb_cmd.param.payload_size - + sizeof(struct afe_port_param_data_v2); + + lb_cmd.dst_port_id = rx_port; + lb_cmd.routing_mode = LB_MODE_DEFAULT; + lb_cmd.enable = (enable ? 1 : 0); + lb_cmd.loopback_cfg_minor_version = + AFE_API_VERSION_LOOPBACK_CONFIG; + 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[index], + (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_loopback_gain_per_path_param set_param; + int ret = 0; + int index = 0; + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + if (q6audio_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + /* 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_V2; + + set_param.param.port_id = port_id; + set_param.param.payload_size = + (sizeof(struct afe_loopback_gain_per_path_param) - + sizeof(struct apr_hdr) - + sizeof(struct afe_port_cmd_set_param_v2)); + set_param.param.payload_address_lsw = 0; + set_param.param.payload_address_msw = 0; + set_param.param.mem_map_handle = 0; + + set_param.pdata.module_id = AFE_MODULE_LOOPBACK; + set_param.pdata.param_id = AFE_PARAM_ID_LOOPBACK_GAIN_PER_PATH; + set_param.pdata.param_size = (set_param.param.payload_size - + sizeof(struct afe_port_param_data_v2)); + set_param.rx_port_id = port_id; + set_param.gain = volume; + + set_param.hdr.token = index; + + 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[index], + (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_pseudo_port_start_nowait(u16 port_id) +{ + struct afe_pseudoport_start_command start; + int ret = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + if (this_afe.apr == NULL) { + pr_err("%s: AFE APR is not registered\n", __func__); + return -ENODEV; + } + + + 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); + return -EINVAL; + } + return 0; +} + +int afe_start_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + int index = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + 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; + + start.hdr.token = index; + 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); + return -EINVAL; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +int afe_pseudo_port_stop_nowait(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + int index = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + 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; + + stop.hdr.token = index; + 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); + return -EINVAL; + } + + return 0; + +} + +int afe_stop_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + int index = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + 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; + + stop.hdr.token = index; + 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); + return -EINVAL; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +/*bharath, memory map handle needs to be stored by AFE client */ +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + int cmd_size = 0; + void *payload = NULL; + void *mmap_region_cmd = NULL; + struct afe_service_cmd_shared_mem_map_regions *mregion = NULL; + struct afe_service_shared_map_region_payload *mregion_pl = NULL; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + cmd_size = sizeof(struct afe_service_cmd_shared_mem_map_regions) \ + + sizeof(struct afe_service_shared_map_region_payload); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + + mregion = (struct afe_service_cmd_shared_mem_map_regions *) + mmap_region_cmd; + mregion->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion->hdr.pkt_size = sizeof(mregion); + mregion->hdr.src_port = 0; + mregion->hdr.dest_port = 0; + mregion->hdr.token = 0; + mregion->hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS; + mregion->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mregion->num_regions = 1; + mregion->property_flag = 0x00; + /* Todo */ + index = mregion->hdr.token = IDX_RSVD_2; + + payload = ((u8 *) mmap_region_cmd + + sizeof(struct afe_service_cmd_shared_mem_map_regions)); + + mregion_pl = (struct afe_service_shared_map_region_payload *)payload; + + mregion_pl->shm_addr_lsw = dma_addr_p; + mregion_pl->shm_addr_msw = 0x00; + mregion_pl->mem_size_bytes = dma_buf_sz; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait[index], + (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_cmd_memory_map_nowait(int port_id, u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + int cmd_size = 0; + void *payload = NULL; + void *mmap_region_cmd = NULL; + struct afe_service_cmd_shared_mem_map_regions *mregion = NULL; + struct afe_service_shared_map_region_payload *mregion_pl = NULL; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + cmd_size = sizeof(struct afe_service_cmd_shared_mem_map_regions) + + sizeof(struct afe_service_shared_map_region_payload); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + mregion = (struct afe_service_cmd_shared_mem_map_regions *) + mmap_region_cmd; + mregion->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion->hdr.pkt_size = sizeof(mregion); + mregion->hdr.src_port = 0; + mregion->hdr.dest_port = 0; + mregion->hdr.token = 0; + mregion->hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS; + mregion->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mregion->num_regions = 1; + mregion->property_flag = 0x00; + + payload = ((u8 *) mmap_region_cmd + + sizeof(struct afe_service_cmd_shared_mem_map_regions)); + mregion_pl = (struct afe_service_shared_map_region_payload *)payload; + + mregion_pl->shm_addr_lsw = dma_addr_p; + mregion_pl->shm_addr_msw = 0x00; + mregion_pl->mem_size_bytes = dma_buf_sz; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_cmd_memory_unmap(u32 mem_map_handle) +{ + int ret = 0; + struct afe_service_cmd_shared_mem_unmap_regions mregion; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS; + mregion.mem_map_handle = mem_map_handle; + + /* Todo */ + index = mregion.hdr.token = IDX_RSVD_2; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait[index], + (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_cmd_memory_unmap_nowait(u32 mem_map_handle) +{ + int ret = 0; + struct afe_service_cmd_shared_mem_unmap_regions mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS; + mregion.mem_map_handle = mem_map_handle; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + } + return 0; +} + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data) +{ + int ret = 0; + struct afe_service_cmd_register_rt_port_driver rtproxy; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = cb; + this_afe.tx_private_data = private_data; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = cb; + this_afe.rx_private_data = private_data; + } + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 1; + rtproxy.hdr.dest_port = 1; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER; + rtproxy.port_id = port_id; + rtproxy.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE reg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_unregister_get_events(u16 port_id) +{ + int ret = 0; + struct afe_service_cmd_unregister_rt_port_driver rtproxy; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 0; + rtproxy.hdr.dest_port = 0; + rtproxy.hdr.token = 0; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER; + rtproxy.port_id = port_id; + rtproxy.reserved = 0; + + rtproxy.hdr.token = index; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = NULL; + this_afe.tx_private_data = NULL; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = NULL; + this_afe.rx_private_data = NULL; + } + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE enable Unreg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait[index], + (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_rt_proxy_port_write(u32 buf_addr_p, u32 mem_map_handle, int bytes) +{ + int ret = 0; + struct afe_port_data_cmd_rt_proxy_port_write_v2 afecmd_wr; + + if (this_afe.apr == NULL) { + pr_err("%s:register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_wr.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_wr.hdr.pkt_size = sizeof(afecmd_wr); + afecmd_wr.hdr.src_port = 0; + afecmd_wr.hdr.dest_port = 0; + afecmd_wr.hdr.token = 0; + afecmd_wr.hdr.opcode = AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2; + afecmd_wr.port_id = RT_PROXY_PORT_001_TX; + afecmd_wr.buffer_address_lsw = (uint32_t)buf_addr_p; + afecmd_wr.buffer_address_msw = 0x00; + afecmd_wr.mem_map_handle = mem_map_handle; + afecmd_wr.available_bytes = bytes; + afecmd_wr.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_wr); + if (ret < 0) { + pr_err("%s: AFE rtproxy write to port 0x%x failed %d\n", + __func__, afecmd_wr.port_id, ret); + ret = -EINVAL; + return ret; + } + return 0; + +} + +int afe_rt_proxy_port_read(u32 buf_addr_p, u32 mem_map_handle, int bytes) +{ + int ret = 0; + struct afe_port_data_cmd_rt_proxy_port_read_v2 afecmd_rd; + + if (this_afe.apr == NULL) { + pr_err("%s: register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_rd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_rd.hdr.pkt_size = sizeof(afecmd_rd); + afecmd_rd.hdr.src_port = 0; + afecmd_rd.hdr.dest_port = 0; + afecmd_rd.hdr.token = 0; + afecmd_rd.hdr.opcode = AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2; + afecmd_rd.port_id = RT_PROXY_PORT_001_RX; + afecmd_rd.buffer_address_lsw = (uint32_t)buf_addr_p; + afecmd_rd.buffer_address_msw = 0x00; + afecmd_rd.available_bytes = bytes; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_rd); + if (ret < 0) { + pr_err("%s: AFE rtproxy read cmd to port 0x%x failed %d\n", + __func__, afecmd_rd.port_id, ret); + 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 (!strncmp(lb_str, "afe_loopback", 12)) { + 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 ((q6audio_validate_port(param[1]) < 0) || + (q6audio_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 (!strncmp(lb_str, "afe_loopback_gain", 17)) { + rc = afe_get_parameters(lbuf, param, 2); + if (!rc) { + pr_info("%s %lu %lu\n", lb_str, param[0], param[1]); + + if (q6audio_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 +}; + +static void config_debug_fs_init(void) +{ + 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); +} +static void config_debug_fs_exit(void) +{ + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); + if (debugfs_afelb_gain) + debugfs_remove(debugfs_afelb_gain); +} +#else +static void config_debug_fs_init(void) +{ + return; +} +static void config_debug_fs_exit(void) +{ + return; +} +#endif +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain) +{ + struct afe_loopback_cfg_v1 cmd_sidetone; + int ret = 0; + int index = 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); + index = q6audio_get_port_index(rx_port_id); + if (q6audio_validate_port(rx_port_id) < 0) + return -EINVAL; + + 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_SET_PARAM_V2; + /* should it be rx or tx port id ?? , bharath*/ + cmd_sidetone.param.port_id = tx_port_id; + /* size of data param & payload */ + cmd_sidetone.param.payload_size = (sizeof(cmd_sidetone) - + sizeof(struct apr_hdr) - + sizeof(struct afe_port_cmd_set_param_v2)); + cmd_sidetone.param.payload_address_lsw = 0x00; + cmd_sidetone.param.payload_address_msw = 0x00; + cmd_sidetone.param.mem_map_handle = 0x00; + cmd_sidetone.pdata.module_id = AFE_MODULE_LOOPBACK; + cmd_sidetone.pdata.param_id = AFE_PARAM_ID_LOOPBACK_CONFIG; + /* size of actual payload only */ + cmd_sidetone.pdata.param_size = cmd_sidetone.param.payload_size - + sizeof(struct afe_port_param_data_v2); + + cmd_sidetone.loopback_cfg_minor_version = + AFE_API_VERSION_LOOPBACK_CONFIG; + cmd_sidetone.dst_port_id = rx_port_id; + cmd_sidetone.routing_mode = LB_MODE_SIDETONE; + 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[index], + (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_cmd_device_stop stop; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_debug("%s: port_id=%d\n", __func__, port_id); + port_id = q6audio_convert_virtual_to_portid(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_DEVICE_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + 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_cmd_device_stop stop; + int ret = 0; + int index = 0; + + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_debug("%s: port_id=%d\n", __func__, port_id); + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + port_id = q6audio_convert_virtual_to_portid(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 = index; + stop.hdr.opcode = AFE_PORT_CMD_DEVICE_STOP; + stop.port_id = q6audio_get_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\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait[index], + (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) +{ + int i = 0; + atomic_set(&this_afe.state, 0); + atomic_set(&this_afe.status, 0); + this_afe.apr = NULL; + for (i = 0; i < AFE_MAX_PORTS; i++) + init_waitqueue_head(&this_afe.wait[i]); + + config_debug_fs_init(); + return 0; +} + +static void __exit afe_exit(void) +{ + int i; + + config_debug_fs_exit(); + for (i = 0; i < MAX_AUDPROC_TYPES; i++) { + if (afe_cal_addr[i].cal_paddr != 0) + afe_cmd_memory_unmap_nowait( + afe_cal_addr[i].cal_paddr); + } +} + +device_initcall(afe_init); +__exitcall(afe_exit); diff --git a/sound/soc/msm/qdsp6v2/q6asm.c b/sound/soc/msm/qdsp6v2/q6asm.c new file mode 100644 index 0000000000000000000000000000000000000000..f982134c7430262fe5bf2527aff66614d4a9295e --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6asm.c @@ -0,0 +1,3342 @@ +/* + * Copyright (c) 2012, 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 + +#define TRUE 0x01 +#define FALSE 0x00 +#define READDONE_IDX_STATUS 0 +#define READDONE_IDX_BUFADD_LSW 1 +#define READDONE_IDX_BUFADD_MSW 2 +#define READDONE_IDX_MEMMAP_HDL 3 +#define READDONE_IDX_SIZE 4 +#define READDONE_IDX_OFFSET 5 +#define READDONE_IDX_LSW_TS 6 +#define READDONE_IDX_MSW_TS 7 +#define READDONE_IDX_FLAGS 8 +#define READDONE_IDX_NUMFRAMES 9 +#define READDONE_IDX_SEQ_ID 10 + +/* TODO, combine them together */ +static DEFINE_MUTEX(session_lock); +struct asm_mmap { + atomic_t ref_cnt; + void *apr; +}; + +static struct asm_mmap this_mmap; +/* session id: 0 reserved */ +static struct audio_client *session[SESSION_MAX+1]; + +struct asm_buffer_node { + struct list_head list; + uint32_t buf_addr_lsw; + uint32_t mmap_hdl; +}; +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); + + +#ifdef CONFIG_DEBUG_FS +#define OUT_BUFFER_SIZE 56 +#define IN_BUFFER_SIZE 24 + +static struct timeval out_cold_tv; +static struct timeval out_warm_tv; +static struct timeval out_cont_tv; +static struct timeval in_cont_tv; +static long out_enable_flag; +static long in_enable_flag; +static struct dentry *out_dentry; +static struct dentry *in_dentry; +static int in_cont_index; +/*This var is used to keep track of first write done for cold output latency */ +static int out_cold_index; +static char *out_buffer; +static char *in_buffer; +static int audio_output_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_output_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(out_buffer, OUT_BUFFER_SIZE, "%ld,%ld,%ld,%ld,%ld,%ld,",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec, out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec, out_cont_tv.tv_sec, out_cont_tv.tv_usec); + return simple_read_from_buffer(buf, OUT_BUFFER_SIZE, ppos, + out_buffer, OUT_BUFFER_SIZE); +} +static ssize_t audio_output_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + + out_cold_index = 0; + + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &out_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_output_latency_debug_fops = { + .open = audio_output_latency_dbgfs_open, + .read = audio_output_latency_dbgfs_read, + .write = audio_output_latency_dbgfs_write +}; +static int audio_input_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_input_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(in_buffer, IN_BUFFER_SIZE, "%ld,%ld,",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + return simple_read_from_buffer(buf, IN_BUFFER_SIZE, ppos, + in_buffer, IN_BUFFER_SIZE); +} +static ssize_t audio_input_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &in_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_input_latency_debug_fops = { + .open = audio_input_latency_dbgfs_open, + .read = audio_input_latency_dbgfs_read, + .write = audio_input_latency_dbgfs_write +}; + +static void config_debug_fs_write_cb(void) +{ + if (out_enable_flag) { + /* For first Write done log the time and reset + out_cold_index*/ + if (out_cold_index != 1) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld" + "sec %ld microsec\n",\ + out_cold_tv.tv_sec,\ + out_cold_tv.tv_usec); + out_cold_index = 1; + } + pr_debug("out_enable_flag %ld",\ + out_enable_flag); + } +} +static void config_debug_fs_read_cb(void) +{ + if (in_enable_flag) { + /* when in_cont_index == 7, DSP would be + * writing into the 8th 512 byte buffer and this + * timestamp is tapped here.Once done it then writes + * to 9th 512 byte buffer.These two buffers(8th, 9th) + * reach the test application in 5th iteration and that + * timestamp is tapped at user level. The difference + * of these two timestamps gives us the time between + * the time at which dsp started filling the sample + * required and when it reached the test application. + * Hence continuous input latency + */ + if (in_cont_index == 7) { + do_gettimeofday(&in_cont_tv); + pr_err("In_CONT:previous read buffer done" + "at %ld sec %ld microsec\n",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + } + in_cont_index++; + } +} + +static void config_debug_fs_reset_index(void) +{ + in_cont_index = 0; +} + +static void config_debug_fs_run(void) +{ + if (out_enable_flag) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld sec %ld microsec\n",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec); + } +} + +static void config_debug_fs_write(struct audio_buffer *ab) +{ + if (out_enable_flag) { + char zero_pattern[2] = {0x00, 0x00}; + /* If First two byte is non zero and last two byte + is zero then it is warm output pattern */ + if ((strncmp(((char *)ab->data), zero_pattern, 2)) && + (!strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_warm_tv); + pr_debug("WARM:apr_send_pkt at" + "%ld sec %ld microsec\n", out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec); + pr_debug("Warm Pattern Matched"); + } + /* If First two byte is zero and last two byte is + non zero then it is cont ouput pattern */ + else if ((!strncmp(((char *)ab->data), zero_pattern, 2)) + && (strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_cont_tv); + pr_debug("CONT:apr_send_pkt at" + "%ld sec %ld microsec\n", out_cont_tv.tv_sec,\ + out_cont_tv.tv_usec); + pr_debug("Cont Pattern Matched"); + } + } +} +static void config_debug_fs_init(void) +{ + out_buffer = kmalloc(OUT_BUFFER_SIZE, GFP_KERNEL); + out_dentry = debugfs_create_file("audio_out_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_output_latency_debug_fops); + if (IS_ERR(out_dentry)) + pr_err("debugfs_create_file failed\n"); + in_buffer = kmalloc(IN_BUFFER_SIZE, GFP_KERNEL); + in_dentry = debugfs_create_file("audio_in_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_input_latency_debug_fops); + if (IS_ERR(in_dentry)) + pr_err("debugfs_create_file failed\n"); +} +#else +static void config_debug_fs_write(struct audio_buffer *ab) +{ + return; +} +static void config_debug_fs_run(void) +{ + return; +} +static void config_debug_fs_reset_index(void) +{ + return; +} +static void config_debug_fs_read_cb(void) +{ + return; +} +static void config_debug_fs_write_cb(void) +{ + return; +} +static void config_debug_fs_init(void) +{ + return; +} +#endif + + +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); + rtac_remove_popp_from_adm_devices(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) { + ion_unmap_kernel(port->buf[cnt].client, + port->buf[cnt].handle); + ion_free(port->buf[cnt].client, + port->buf[cnt].handle); + ion_client_destroy(port->buf[cnt].client); + 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].phys, dir); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + if (port->buf[0].data) { + ion_unmap_kernel(port->buf[0].client, port->buf[0].handle); + ion_free(port->buf[0].client, port->buf[0].handle); + ion_client_destroy(port->buf[0].client); + pr_debug("%s:data[%p]phys[%p][%p]" + ", client[%p] handle[%p]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, + (void *)port->buf[0].client, + (void *)port->buf[0].handle); + } + + 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; +} + +int q6asm_mmap_apr_dereg(void) +{ + 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: + 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); + ac->mmap_apr = NULL; + q6asm_session_free(ac); + q6asm_mmap_apr_dereg(); + + pr_debug("%s: APR De-Register\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; + } +} + +void *q6asm_mmap_apr_reg(void) +{ + 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); + return this_mmap.apr; +fail: + return NULL; +} + +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; + } + rtac_set_asm_handle(n, ac->apr); + + pr_debug("%s Registering the common port with APR\n", __func__); + ac->mmap_apr = q6asm_mmap_apr_reg(); + if (ac->mmap_apr == NULL) + goto fail; + + init_waitqueue_head(&ac->cmd_wait); + INIT_LIST_HEAD(&ac->port[0].mem_map_handle); + INIT_LIST_HEAD(&ac->port[1].mem_map_handle); + pr_debug("%s: mem_map_handle list init'ed\n", __func__); + 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; +} + +struct audio_client *q6asm_get_audio_client(int session_id) +{ + if ((session_id <= 0) || (session_id > SESSION_MAX)) { + pr_err("%s: invalid session: %d\n", __func__, session_id); + goto err; + } + + if (!session[session_id]) { + pr_err("%s: session not active: %d\n", __func__, session_id); + goto err; + } + + return session[session_id]; +err: + 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; + int len; + + 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) { + buf[cnt].client = msm_ion_client_create + (UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *) + buf[cnt].client)) { + pr_err("%s: ION create client" + " for AUDIO failed\n", + __func__); + goto fail; + } + buf[cnt].handle = ion_alloc + (buf[cnt].client, bufsz, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) + buf[cnt].handle)) { + pr_err("%s: ION memory" + " allocation for AUDIO failed\n", + __func__); + goto fail; + } + + rc = ion_phys(buf[cnt].client, + buf[cnt].handle, + (ion_phys_addr_t *) + &buf[cnt].phys, + (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical" + " for AUDIO failed, rc = %d\n", + __func__, rc); + goto fail; + } + + buf[cnt].data = ion_map_kernel + (buf[cnt].client, buf[cnt].handle, + 0); + if (IS_ERR_OR_NULL((void *) + buf[cnt].data)) { + pr_err("%s: ION memory" + " mapping for AUDIO failed\n", __func__); + goto fail; + } + memset((void *)buf[cnt].data, 0, bufsz); + buf[cnt].used = 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_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; + int len; + + 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].client = msm_ion_client_create(UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *)buf[0].client)) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + goto fail; + } + buf[0].handle = ion_alloc(buf[0].client, bufsz * bufcnt, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) buf[0].handle)) { + pr_err("%s: ION memory allocation for AUDIO failed\n", + __func__); + goto fail; + } + + rc = ion_phys(buf[0].client, buf[0].handle, + (ion_phys_addr_t *)&buf[0].phys, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto fail; + } + + buf[0].data = ion_map_kernel(buf[0].client, buf[0].handle, 0); + if (IS_ERR_OR_NULL((void *) buf[0].data)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + goto fail; + } + memset((void *)buf[0].data, 0, (bufsz * bufcnt)); + if (!buf[0].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + 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_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_contiguous(dir, ac); + return -EINVAL; +} + +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv) +{ + uint32_t sid = 0; + uint32_t dir = 0; + uint32_t *payload = data->payload; + unsigned long dsp_flags; + + struct audio_client *ac = NULL; + struct audio_port_data *port; + + if (!data) { + pr_err("%s: Invalid CB\n", __func__); + return 0; + } + 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); + atomic_set(&this_mmap.ref_cnt, 0); + this_mmap.apr = NULL; + return 0; + } + sid = (data->token >> 8) & 0x0F; + ac = q6asm_get_audio_client(sid); + pr_debug("%s:ptr0[0x%x]ptr1[0x%x]opcode[0x%x]" + "token[0x%x]payload_s[%d] src[%d] dest[%d]sid[%d]dir[%d]\n", + __func__, payload[0], payload[1], data->opcode, data->token, + data->payload_size, data->src_port, data->dest_port, sid, dir); + pr_debug("%s:Payload = [0x%x] status[0x%x]\n", + __func__, payload[0], payload[1]); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + switch (payload[0]) { + case ASM_CMD_SHARED_MEM_MAP_REGIONS: + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + pr_debug("%s:Payload = [0x%x] status[0x%x]\n", + __func__, payload[0], payload[1]); + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + return 0; + } + + dir = (data->token & 0x0F); + port = &ac->port[dir]; + + switch (data->opcode) { + case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS:{ + pr_debug("%s:PL#0[0x%x]PL#1 [0x%x] dir=%x s_id=%x\n", + __func__, payload[0], payload[1], dir, sid); + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (atomic_read(&ac->cmd_state)) { + ac->port[dir].tmp_hdl = payload[0]; + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + break; + } + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:{ + pr_debug("%s:PL#0[0x%x]PL#1 [0x%x]\n", + __func__, payload[0], payload[1]); + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + + break; + } + default: + pr_debug("%s:command[0x%x]success [0x%x]\n", + __func__, payload[0], payload[1]); + } + if (ac->cb) + ac->cb(data->opcode, data->token, + data->payload, ac->priv); + 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); + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + 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 != ASM_DATA_EVENT_RENDERED_EOS) && + (data->opcode != ASM_DATA_EVENT_EOS)) + pr_debug("%s:Payload = [0x%x] status[0x%x]\n", + __func__, payload[0], payload[1]); + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + case ASM_STREAM_CMD_SET_PP_PARAMS_V2: + if (rtac_make_asm_callback(ac->session, payload, + data->payload_size)) + break; + 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_V2: + case ASM_SESSION_CMD_REGISTER_FORX_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_V2: + case ASM_STREAM_CMD_OPEN_WRITE_V2: + case ASM_STREAM_CMD_OPEN_READWRITE_V2: + case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + pr_debug("%s:Payload = [0x%x]stat[0x%x]\n", + __func__, payload[0], payload[1]); + 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_V2:{ + 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); + + config_debug_fs_write_cb(); + + for (i = 0; i < port->max_buf_cnt; i++) + pr_debug("%d ", port->buf[i].used); + + } + break; + } + case ASM_STREAM_CMDRSP_GET_PP_PARAMS_V2: + rtac_make_asm_callback(ac->session, payload, + data->payload_size); + break; + case ASM_DATA_EVENT_READ_DONE_V2:{ + + struct audio_port_data *port = &ac->port[OUT]; + + config_debug_fs_read_cb(); + + pr_debug("%s:R-D: status=%d buff_add=%x act_size=%d offset=%d\n", + __func__, payload[READDONE_IDX_STATUS], + payload[READDONE_IDX_BUFADD_LSW], + payload[READDONE_IDX_SIZE], + payload[READDONE_IDX_OFFSET]); + + pr_debug("%s:R-D:msw_ts=%d lsw_ts=%d memmap_hdl=%x flags=%d id=%d num=%d\n", + __func__, payload[READDONE_IDX_MSW_TS], + payload[READDONE_IDX_LSW_TS], + payload[READDONE_IDX_MEMMAP_HDL], + payload[READDONE_IDX_FLAGS], + payload[READDONE_IDX_SEQ_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_BUFADD_LSW]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[token].phys,\ + (void *)payload[READDONE_IDX_BUFADD_LSW]); + 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_EVENT_RENDERED_EOS: + pr_debug("%s:EOS ACK received: rxed opcode[0x%x]\n", + __func__, data->opcode); + break; + case ASM_SESSION_EVENTX_OVERFLOW: + pr_err("ASM_SESSION_EVENTX_OVERFLOW\n"); + break; + case ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3: + pr_debug("%s: ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3, " + "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->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + break; + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + case ASM_DATA_EVENT_ENC_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__); + mutex_unlock(&port->lock); + 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; +} + +void *q6asm_is_cpu_buf_avail_nolock(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; + + port = &ac->port[dir]; + + 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); + 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)); + return data; +} + +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:pkt_size=%d cmd_flg=%d session=%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_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("pkt_size = %d, cmd_flg = %d, session = %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; +} + +static void q6asm_add_mmaphdr(struct audio_client *ac, struct apr_hdr *hdr, + u32 pkt_size, u32 cmd_flg, u32 token) +{ + 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 = token; + atomic_set(&ac->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_v2 open; + + uint16_t bits_per_sample = 16; + + + config_debug_fs_reset_index(); + + 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_V2; + /* Stream prio : High, provide meta info with encoded frames */ + open.src_endpointype = ASM_END_POINT_DEVICE_MATRIX; + + open.preprocopo_id = get_asm_topology(); + if (open.preprocopo_id == 0) + open.preprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT; + open.bits_per_sample = bits_per_sample; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.mode_flags = 0x00; + open.enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.mode_flags = BUFFER_META_ENABLE; + open.enc_cfg_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_V13K: + open.mode_flags = BUFFER_META_ENABLE; + open.enc_cfg_id = ASM_MEDIA_FMT_V13K_FS; + break; + case FORMAT_EVRC: + open.mode_flags = BUFFER_META_ENABLE; + open.enc_cfg_id = ASM_MEDIA_FMT_EVRC_FS; + break; + case FORMAT_AMRNB: + open.mode_flags = BUFFER_META_ENABLE ; + open.enc_cfg_id = ASM_MEDIA_FMT_AMRNB_FS; + break; + case FORMAT_AMRWB: + open.mode_flags = BUFFER_META_ENABLE ; + open.enc_cfg_id = ASM_MEDIA_FMT_AMRWB_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 read 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_v2 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_V2; + open.mode_flags = 0x00; + /* source endpoint : matrix */ + open.sink_endpointype = ASM_END_POINT_DEVICE_MATRIX; + open.bits_per_sample = 16; + + open.postprocopo_id = get_asm_topology(); + if (open.postprocopo_id == 0) + open.postprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_DOLBY_AAC; + break; + case FORMAT_WMA_V9: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V9_V2; + break; + case FORMAT_WMA_V10PRO: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V10PRO_V2; + break; + case FORMAT_MP3: + open.dec_fmt_id = ASM_MEDIA_FMT_MP3; + 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_readwrite_v2 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_V2; + + open.mode_flags = BUFFER_META_ENABLE; + open.bits_per_sample = 16; + /* source endpoint : matrix */ + open.postprocopo_id = get_asm_topology(); + if (open.postprocopo_id == 0) + open.postprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT; + + switch (wr_format) { + case FORMAT_LINEAR_PCM: + open.dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_DOLBY_AAC; + break; + case FORMAT_WMA_V9: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V9_V2; + break; + case FORMAT_WMA_V10PRO: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V10PRO_V2; + break; + case FORMAT_AMRNB: + open.dec_fmt_id = ASM_MEDIA_FMT_AMRNB_FS; + break; + case FORMAT_AMRWB: + open.dec_fmt_id = ASM_MEDIA_FMT_AMRWB_FS; + break; + case FORMAT_V13K: + open.dec_fmt_id = ASM_MEDIA_FMT_V13K_FS; + break; + case FORMAT_EVRC: + open.dec_fmt_id = ASM_MEDIA_FMT_EVRC_FS; + break; + case FORMAT_EVRCB: + open.dec_fmt_id = ASM_MEDIA_FMT_EVRCB_FS; + break; + case FORMAT_EVRCWB: + open.dec_fmt_id = ASM_MEDIA_FMT_EVRCWB_FS; + break; + case FORMAT_MP3: + open.dec_fmt_id = ASM_MEDIA_FMT_MP3; + break; + default: + pr_err("Invalid format[%d]\n", wr_format); + goto fail_cmd; + } + + switch (rd_format) { + case FORMAT_LINEAR_PCM: + open.enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.enc_cfg_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_V13K: + open.enc_cfg_id = ASM_MEDIA_FMT_V13K_FS; + break; + case FORMAT_EVRC: + open.enc_cfg_id = ASM_MEDIA_FMT_EVRC_FS; + break; + case FORMAT_AMRNB: + open.enc_cfg_id = ASM_MEDIA_FMT_AMRNB_FS; + break; + case FORMAT_AMRWB: + open.enc_cfg_id = ASM_MEDIA_FMT_AMRWB_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.enc_cfg_id, open.dec_fmt_id); + + 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 read-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_session_cmd_run_v2 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_V2; + run.flags = flags; + run.time_lsw = lsw_ts; + run.time_msw = msw_ts; + + config_debug_fs_run(); + + 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_session_cmd_run_v2 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_V2; + run.flags = flags; + run.time_lsw = lsw_ts; + run.time_msw = msw_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_aac_enc_cfg_v2 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.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_aac_enc_cfg_v2) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + enc_cfg.bit_rate = bit_rate; + enc_cfg.enc_mode = mode; + enc_cfg.aac_fmt_flag = format; + enc_cfg.channel_cfg = channels; + enc_cfg.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_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels) +{ + /* Todo: */ + return 0; +} + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_multi_channel_pcm_enc_cfg_v2 enc_cfg; + u8 *channel_mapping; + u32 frames_per_buf = 0; + + 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.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(enc_cfg) - sizeof(enc_cfg.hdr) - + sizeof(enc_cfg.encdec); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.num_channels = channels; + enc_cfg.bits_per_sample = 16; + enc_cfg.sample_rate = rate; + enc_cfg.is_signed = 1; + channel_mapping = enc_cfg.channel_mapping; /* ??? PHANI */ + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + channels); + return -EINVAL; + } + + 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_aac_sbr_ps_flag_param sbrps; + u32 frames_per_buf = 0; + + 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.encdec.param_id = ASM_PARAM_ID_AAC_SBR_PS_FLAG; + sbrps.encdec.param_size = sizeof(struct asm_aac_sbr_ps_flag_param) - + sizeof(struct asm_stream_cmd_set_encdec_param); + sbrps.encblk.frames_per_buf = frames_per_buf; + sbrps.encblk.enc_cfg_blk_size = sbrps.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + sbrps.sbr_ps_flag = 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_PARAM_ID_AAC_SBR_PS_FLAG); + 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_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right) +{ + struct asm_aac_dual_mono_mapping_param dual_mono; + u32 frames_per_buf = 0; + + int rc = 0; + + pr_debug("%s: Session %d, sce_left = %d, sce_right = %d\n", + __func__, ac->session, sce_left, sce_right); + + q6asm_add_hdr(ac, &dual_mono.hdr, sizeof(dual_mono), TRUE); + + dual_mono.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + dual_mono.encdec.param_id = ASM_PARAM_ID_AAC_DUAL_MONO_MAPPING; + dual_mono.encdec.param_size = sizeof(struct asm_aac_enc_cfg_v2) - + sizeof(struct asm_stream_cmd_set_encdec_param); + dual_mono.encblk.frames_per_buf = frames_per_buf; + dual_mono.encblk.enc_cfg_blk_size = dual_mono.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + dual_mono.left_channel_sce = sce_left; + dual_mono.right_channel_sce = sce_right; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &dual_mono); + if (rc < 0) { + pr_err("%s:Command opcode[0x%x]paramid[0x%x] failed\n", + __func__, ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_PARAM_ID_AAC_DUAL_MONO_MAPPING); + 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 opcode[0x%x]\n", __func__, + dual_mono.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_v13k_enc_cfg 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.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_v13k_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.min_rate = min_rate; + enc_cfg.max_rate = max_rate; + enc_cfg.reduced_rate_cmd = reduced_rate_level; + enc_cfg.rate_mod_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 setencdec v13k resp\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_evrc_enc_cfg 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.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_evrc_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.min_rate = min_rate; + enc_cfg.max_rate = max_rate; + enc_cfg.rate_mod_cmd = rate_modulation_cmd; + enc_cfg.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 encdec evrc\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_amrnb_enc_cfg 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.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_amrnb_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.enc_mode = band_mode; + enc_cfg.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 set encdec amrnb\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_amrwb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable) +{ + struct asm_amrwb_enc_cfg 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.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_amrwb_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.enc_mode = band_mode; + enc_cfg.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_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + return q6asm_media_format_block_multi_aac(ac, cfg); +} + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_multi_channel_pcm_fmt_blk_v2 fmt; + u8 *channel_mapping; + 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_FMT_UPDATE_V2; + fmt.fmt_blk.fmt_blk_size = sizeof(fmt) - sizeof(fmt.hdr) - + sizeof(fmt.fmt_blk); + fmt.num_channels = channels; + fmt.bits_per_sample = 16; + fmt.sample_rate = rate; + fmt.is_signed = 1; + + channel_mapping = fmt.channel_mapping; + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + channels); + return -EINVAL; + } + + 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_multi_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + struct asm_aac_fmt_blk_v2 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_FMT_UPDATE_V2; + fmt.fmt_blk.fmt_blk_size = sizeof(fmt) - sizeof(fmt.hdr) - + sizeof(fmt.fmt_blk); + fmt.aac_fmt_flag = cfg->format; + fmt.audio_objype = cfg->aot; + /* If zero, PCE is assumed to be available in bitstream*/ + fmt.total_size_of_PCE_bits = 0; + fmt.channel_config = cfg->ch_cfg; + fmt.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.aac_fmt_flag, fmt.fmt_blk.fmt_blk_size, + fmt.aac_fmt_flag, + fmt.audio_objype, + fmt.channel_config, + fmt.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_wmastdv9_fmt_blk_v2 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_MEDIA_FMT_WMA_V9_V2; + + fmt.fmtag = wma_cfg->format_tag; + fmt.num_channels = wma_cfg->ch_cfg; + fmt.sample_rate = wma_cfg->sample_rate; + fmt.avg_bytes_per_sec = wma_cfg->avg_bytes_per_sec; + fmt.blk_align = wma_cfg->block_align; + fmt.bits_per_sample = + wma_cfg->valid_bits_per_sample; + fmt.channel_mask = wma_cfg->ch_mask; + fmt.enc_options = wma_cfg->encode_opt; + + 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_wmaprov10_fmt_blk_v2 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_MEDIA_FMT_WMA_V10PRO_V2; + + fmt.fmtag = wmapro_cfg->format_tag; + fmt.num_channels = wmapro_cfg->ch_cfg; + fmt.sample_rate = wmapro_cfg->sample_rate; + fmt.avg_bytes_per_sec = + wmapro_cfg->avg_bytes_per_sec; + fmt.blk_align = wmapro_cfg->block_align; + fmt.bits_per_sample = wmapro_cfg->valid_bits_per_sample; + fmt.channel_mask = wmapro_cfg->ch_mask; + fmt.enc_options = wmapro_cfg->encode_opt; + fmt.usAdvancedEncodeOpt = wmapro_cfg->adv_encode_opt; + fmt.advanced_enc_options2 = wmapro_cfg->adv_encode_opt2; + + 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 avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + struct asm_buffer_node *buffer_node = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || ac->mmap_apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + buffer_node = kmalloc(sizeof(struct asm_buffer_node), GFP_KERNEL); + if (!buffer_node) + return -ENOMEM; + cmd_size = sizeof(struct avs_cmd_shared_mem_map_regions) + + sizeof(struct avs_shared_map_region_payload) * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (mmap_region_cmd == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + mmap_regions = (struct avs_cmd_shared_mem_map_regions *) + mmap_region_cmd; + q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, + TRUE, ((ac->session << 8) | dir)); + mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mmap_regions->num_regions = bufcnt & 0x00ff; + mmap_regions->property_flag = 0x00; + pr_debug("map_regions->nregions = %d\n", mmap_regions->num_regions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + mregions = (struct avs_shared_map_region_payload *)payload; + + ac->port[dir].tmp_hdl = 0; + port = &ac->port[dir]; + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + mregions->shm_addr_lsw = ab->phys; + /* Using only 32 bit address */ + mregions->shm_addr_msw = 0; + mregions->mem_size_bytes = ab->size; + ++mregions; + } + + rc = apr_send_pkt(ac->mmap_apr, (uint32_t *) mmap_region_cmd); + if (rc < 0) { + pr_err("mmap op[0x%x]rc[%d]\n", + mmap_regions->hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0 && + ac->port[dir].tmp_hdl), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + buffer_node->buf_addr_lsw = buf_add; + buffer_node->mmap_hdl = ac->port[dir].tmp_hdl; + list_add_tail(&buffer_node->list, &ac->port[dir].mem_map_handle); + ac->port[dir].tmp_hdl = 0; + rc = 0; + +fail_cmd: + kfree(mmap_region_cmd); + return rc; +} + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, int dir) +{ + struct avs_cmd_shared_mem_unmap_regions mem_unmap; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + + 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(ac, &mem_unmap.hdr, + sizeof(struct avs_cmd_shared_mem_unmap_regions), + TRUE, ((ac->session << 8) | dir)); + + mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + pr_info("%s: Found the element\n", __func__); + mem_unmap.mem_map_handle = buf_node->mmap_hdl; + break; + } + } + pr_debug("%s: mem_unmap-mem_map_handle: 0x%x", + __func__, mem_unmap.mem_map_handle); + rc = apr_send_pkt(ac->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(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5 * HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + list_del(&buf_node->list); + kfree(buf_node); + } + } + + rc = 0; +fail_cmd: + return rc; +} + + +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + struct asm_buffer_node *buffer_node = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || ac->mmap_apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct avs_cmd_shared_mem_map_regions) + + (sizeof(struct avs_shared_map_region_payload)); + + buffer_node = kzalloc(sizeof(struct asm_buffer_node) * bufcnt, + GFP_KERNEL); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if ((mmap_region_cmd == NULL) || (buffer_node == NULL)) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + mmap_regions = (struct avs_cmd_shared_mem_map_regions *) + mmap_region_cmd; + q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, TRUE, + ((ac->session << 8) | dir)); + pr_debug("mmap_region=0x%p token=0x%x\n", + mmap_regions, ((ac->session << 8) | dir)); + + mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mmap_regions->num_regions = 1; /*bufcnt & 0x00ff; */ + mmap_regions->property_flag = 0x00; + pr_debug("map_regions->nregions = %d\n", mmap_regions->num_regions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + mregions = (struct avs_shared_map_region_payload *)payload; + + ac->port[dir].tmp_hdl = 0; + port = &ac->port[dir]; + ab = &port->buf[0]; + mregions->shm_addr_lsw = ab->phys; + /* Using only 32 bit address */ + mregions->shm_addr_msw = 0; + mregions->mem_size_bytes = (bufsz * bufcnt); + + rc = apr_send_pkt(ac->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(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0) + , 5*HZ); + /*ac->port[dir].tmp_hdl), 5*HZ);*/ + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + mutex_lock(&ac->cmd_lock); + + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + buffer_node[i].buf_addr_lsw = ab->phys; + buffer_node[i].mmap_hdl = ac->port[dir].tmp_hdl; + list_add_tail(&buffer_node[i].list, + &ac->port[dir].mem_map_handle); + pr_debug("%s: i=%d, bufadd[i] = 0x%x, maphdl[i] = 0x%x\n", + __func__, i, buffer_node[i].buf_addr_lsw, + buffer_node[i].mmap_hdl); + } + ac->port[dir].tmp_hdl = 0; + mutex_unlock(&ac->cmd_lock); + rc = 0; + pr_debug("%s: exit\n", __func__); +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 avs_cmd_shared_mem_unmap_regions mem_unmap; + struct audio_port_data *port = NULL; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + uint32_t buf_add; + int rc = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || ac->mmap_apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct avs_cmd_shared_mem_unmap_regions); + q6asm_add_mmaphdr(ac, &mem_unmap.hdr, cmd_size, + TRUE, ((ac->session << 8) | dir)); + port = &ac->port[dir]; + buf_add = (uint32_t)port->buf->phys; + mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + pr_debug("%s: Found the element\n", __func__); + mem_unmap.mem_map_handle = buf_node->mmap_hdl; + break; + } + } + + pr_debug("%s: mem_unmap-mem_map_handle: 0x%x", + __func__, mem_unmap.mem_map_handle); + rc = apr_send_pkt(ac->mmap_apr, (uint32_t *) &mem_unmap); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + mem_unmap.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 memory_unmap\n"); + goto fail_cmd; + } + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + list_del(&buf_node->list); + kfree(buf_node); + } + } + rc = 0; + +fail_cmd: + return rc; +} + +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain) +{ + struct asm_volume_ctrl_lr_chan_gain lrgain; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_volume_ctrl_lr_chan_gain); + q6asm_add_hdr_async(ac, &lrgain.hdr, sz, TRUE); + lrgain.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + lrgain.param.data_payload_addr_lsw = 0; + lrgain.param.data_payload_addr_msw = 0; + lrgain.param.mem_map_handle = 0; + lrgain.param.data_payload_size = sizeof(lrgain) - + sizeof(lrgain.hdr) - sizeof(lrgain.param); + lrgain.data.module_id = ASM_MODULE_ID_VOL_CTRL; + lrgain.data.param_id = ASM_PARAM_ID_VOL_CTRL_LR_CHANNEL_GAIN; + lrgain.data.param_size = lrgain.param.data_payload_size - + sizeof(lrgain.data); + lrgain.data.reserved = 0; + lrgain.l_chan_gain = left_gain; + lrgain.r_chan_gain = right_gain; + rc = apr_send_pkt(ac->apr, (uint32_t *) &lrgain); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + lrgain.data.param_id); + 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, set-params paramid[0x%x]\n", __func__, + lrgain.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_mute(struct audio_client *ac, int muteflag) +{ + struct asm_volume_ctrl_mute_config mute; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_volume_ctrl_mute_config); + q6asm_add_hdr_async(ac, &mute.hdr, sz, TRUE); + mute.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + mute.param.data_payload_addr_lsw = 0; + mute.param.data_payload_addr_msw = 0; + mute.param.mem_map_handle = 0; + mute.param.data_payload_size = sizeof(mute) - + sizeof(mute.hdr) - sizeof(mute.param); + mute.data.module_id = ASM_MODULE_ID_VOL_CTRL; + mute.data.param_id = ASM_PARAM_ID_VOL_CTRL_MUTE_CONFIG; + mute.data.param_size = mute.param.data_payload_size - sizeof(mute.data); + mute.data.reserved = 0; + mute.mute_flag = muteflag; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &mute); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + mute.data.param_id); + 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, set-params paramid[0x%x]\n", __func__, + mute.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_volume(struct audio_client *ac, int volume) +{ + struct asm_volume_ctrl_master_gain vol; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_volume_ctrl_master_gain); + q6asm_add_hdr_async(ac, &vol.hdr, sz, TRUE); + vol.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + vol.param.data_payload_addr_lsw = 0; + vol.param.data_payload_addr_msw = 0; + + + vol.param.mem_map_handle = 0; + vol.param.data_payload_size = sizeof(vol) - + sizeof(vol.hdr) - sizeof(vol.param); + vol.data.module_id = ASM_MODULE_ID_VOL_CTRL; + vol.data.param_id = ASM_PARAM_ID_VOL_CTRL_MASTER_GAIN; + vol.data.param_size = vol.param.data_payload_size - sizeof(vol.data); + vol.data.reserved = 0; + vol.master_gain = volume; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &vol); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + vol.data.param_id); + 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, set-params paramid[0x%x]\n", __func__, + vol.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *pause_param) +{ + struct asm_soft_pause_params softpause; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_soft_pause_params); + q6asm_add_hdr_async(ac, &softpause.hdr, sz, TRUE); + softpause.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + + softpause.param.data_payload_addr_lsw = 0; + softpause.param.data_payload_addr_msw = 0; + softpause.param.mem_map_handle = 0; + softpause.param.data_payload_size = sizeof(softpause) - + sizeof(softpause.hdr) - sizeof(softpause.param); + softpause.data.module_id = ASM_MODULE_ID_VOL_CTRL; + softpause.data.param_id = ASM_PARAM_ID_SOFT_PAUSE_PARAMETERS; + softpause.data.param_size = softpause.param.data_payload_size - + sizeof(softpause.data); + softpause.data.reserved = 0; + softpause.enable_flag = pause_param->enable; + softpause.period = pause_param->period; + softpause.step = pause_param->step; + softpause.ramping_curve = pause_param->rampingcurve; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &softpause); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + softpause.data.param_id); + 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, set-params paramid[0x%x]\n", __func__, + softpause.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_params *softvol_param) +{ + struct asm_soft_step_volume_params softvol; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_soft_step_volume_params); + q6asm_add_hdr_async(ac, &softvol.hdr, sz, TRUE); + softvol.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + softvol.param.data_payload_addr_lsw = 0; + softvol.param.data_payload_addr_msw = 0; + softvol.param.mem_map_handle = 0; + softvol.param.data_payload_size = sizeof(softvol) - + sizeof(softvol.hdr) - sizeof(softvol.param); + softvol.data.module_id = ASM_MODULE_ID_VOL_CTRL; + softvol.data.param_id = ASM_PARAM_ID_SOFT_VOL_STEPPING_PARAMETERS; + softvol.data.param_size = softvol.param.data_payload_size - + sizeof(softvol.data); + softvol.data.reserved = 0; + softvol.period = softvol_param->period; + softvol.step = softvol_param->step; + softvol.ramping_curve = softvol_param->rampingcurve; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &softvol); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + softvol.data.param_id); + 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, set-params paramid[0x%x]\n", __func__, + softvol.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_equalizer(struct audio_client *ac, void *eq_p) +{ + struct asm_eq_params eq; + struct msm_audio_eq_stream_config *eq_params = NULL; + int i = 0; + int sz = 0; + int rc = 0; + + if (eq_p == NULL) { + pr_err("%s[%d]: Invalid Eq param\n", __func__, ac->session); + rc = -EINVAL; + goto fail_cmd; + } + sz = sizeof(struct asm_eq_params); + eq_params = (struct msm_audio_eq_stream_config *) eq_p; + q6asm_add_hdr(ac, &eq.hdr, sz, TRUE); + + eq.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + eq.param.data_payload_addr_lsw = 0; + eq.param.data_payload_addr_msw = 0; + eq.param.mem_map_handle = 0; + eq.param.data_payload_size = sizeof(eq) - + sizeof(eq.hdr) - sizeof(eq.param); + eq.data.module_id = ASM_MODULE_ID_EQUALIZER; + eq.data.param_id = ASM_PARAM_ID_EQUALIZER_PARAMETERS; + eq.data.param_size = eq.param.data_payload_size - sizeof(eq.data); + eq.enable_flag = eq_params->enable; + eq.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++) { + eq.eq_bands[i].band_idx = + eq_params->eq_bands[i].band_idx; + eq.eq_bands[i].filterype = + eq_params->eq_bands[i].filter_type; + eq.eq_bands[i].center_freq_hz = + eq_params->eq_bands[i].center_freq_hz; + eq.eq_bands[i].filter_gain = + eq_params->eq_bands[i].filter_gain; + eq.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); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + eq.data.param_id); + 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, set-params paramid[0x%x]\n", __func__, + eq.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_read(struct audio_client *ac) +{ + struct asm_data_cmd_read_v2 read; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + 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_V2; + read.buf_addr_lsw = ab->phys; + read.buf_addr_msw = 0; + + list_for_each_safe(ptr, next, &ac->port[OUT].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == (uint32_t) ab->phys) + read.mem_map_handle = buf_node->mmap_hdl; + } + pr_debug("memory_map handle in q6asm_read: [%0x]:", + read.mem_map_handle); + read.buf_size = ab->size; + read.seq_id = 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_addr_lsw, + read.hdr.token, + read.seq_id); + 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_data_cmd_read_v2 read; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + 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_V2; + read.buf_addr_lsw = ab->phys; + read.buf_addr_msw = 0; + read.buf_size = ab->size; + read.seq_id = port->dsp_buf; + read.hdr.token = port->dsp_buf; + + list_for_each_safe(ptr, next, &ac->port[OUT].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == (uint32_t)ab->phys) { + read.mem_map_handle = buf_node->mmap_hdl; + break; + } + } + + 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_addr_lsw, + read.hdr.token, + read.seq_id); + pr_debug("q6asm_read_nolock mem-map handle is %x", + read.mem_map_handle); + 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_async_write(struct audio_client *ac, + struct audio_aio_write_param *param) +{ + int rc = 0; + struct asm_data_cmd_write_v2 write; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + struct audio_buffer *ab; + struct audio_port_data *port; + + 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); + + port = &ac->port[IN]; + ab = &port->buf[port->dsp_buf]; + + /* Pass physical address as token for AIO scheme */ + write.hdr.token = param->uid; + write.hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write.buf_addr_lsw = param->paddr; + write.buf_addr_msw = 0x00; + write.buf_size = param->len; + write.timestamp_msw = param->msw_ts; + write.timestamp_lsw = param->lsw_ts; + pr_debug("%s: token[0x%x], buf_addr_lsw[0x%x], buf_size[0x%x]," + "ts_msw[0x%x], ts_lsw[0x%x]\n", + __func__, write.hdr.token, write.buf_addr_lsw, + write.buf_size, write.timestamp_msw, + write.timestamp_lsw); + /* Use 0xFF00 for disabling timestamps */ + if (param->flags == 0xFF00) + write.flags = (0x00000000 | (param->flags & 0x800000FF)); + else + write.flags = (0x80000000 | param->flags); + + write.seq_id = param->uid; + list_for_each_safe(ptr, next, &ac->port[IN].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == (uint32_t)write.buf_addr_lsw) { + write.mem_map_handle = buf_node->mmap_hdl; + pr_debug("%s:buf_node->mmap_hdl = 0x%x," + "write.mem_map_handle = 0x%x\n", + __func__, + buf_node->mmap_hdl, + (uint32_t)write.mem_map_handle); + break; + } + } + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]," + "mem_map_handle[0x%x]\n", __func__, ac->session, + write.buf_addr_lsw, write.buf_size, write.mem_map_handle); + + 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_data_cmd_read_v2 read; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + + 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_V2; + read.buf_addr_lsw = param->paddr; + read.buf_addr_msw = 0; + read.buf_size = param->len; + read.seq_id = param->uid; + + list_for_each_safe(ptr, next, &ac->port[IN].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == param->paddr) + read.mem_map_handle = buf_node->mmap_hdl; + } + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]", __func__, ac->session, + read.buf_addr_lsw, 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_data_cmd_write_v2 write; + struct asm_buffer_node *buf_node = NULL; + 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_V2; + write.buf_addr_lsw = ab->phys; + write.buf_addr_msw = 0; + write.buf_size = len; + write.seq_id = port->dsp_buf; + write.timestamp_lsw = lsw_ts; + write.timestamp_msw = msw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.flags = (0x00000000 | (flags & 0x800000FF)); + else + write.flags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + buf_node = list_first_entry(&ac->port[IN].mem_map_handle, + struct asm_buffer_node, + list); + write.mem_map_handle = buf_node->mmap_hdl; + + pr_debug("%s:ab->phys[0x%x]bufadd[0x%x]" + "token[0x%x]buf_id[0x%x]buf_size[0x%x]mmaphdl[0x%x]" + , __func__, + ab->phys, + write.buf_addr_lsw, + write.hdr.token, + write.seq_id, + write.buf_size, + write.mem_map_handle); + mutex_unlock(&port->lock); + + config_debug_fs_write(ab); + + 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_data_cmd_write_v2 write; + struct asm_buffer_node *buf_node = NULL; + 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_V2; + write.buf_addr_lsw = ab->phys; + write.buf_addr_msw = 0; + write.buf_size = len; + write.seq_id = port->dsp_buf; + write.timestamp_lsw = lsw_ts; + write.timestamp_msw = msw_ts; + buf_node = list_first_entry(&ac->port[IN].mem_map_handle, + struct asm_buffer_node, + list); + write.mem_map_handle = buf_node->mmap_hdl; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.flags = (0x00000000 | (flags & 0x800000FF)); + else + write.flags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + + pr_err("%s:ab->phys[0x%x]bufadd[0x%x]token[0x%x]" + "buf_id[0x%x]buf_size[0x%x]mmaphdl[0x%x]" + , __func__, + ab->phys, + write.buf_addr_lsw, + write.hdr.token, + write.seq_id, + write.buf_size, + write.mem_map_handle); + + 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_SESSIONTIME_V3; + atomic_set(&ac->cmd_state, 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->cmd_wait, + (atomic_read(&ac->cmd_state) == 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_session_cmd_regx_overflow 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_FORX_OVERFLOW_EVENTS; + /* tx overflow event: enable */ + tx_overflow.enable_flag = 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; +} + +int q6asm_get_apr_service_id(int session_id) +{ + pr_debug("%s\n", __func__); + + if (session_id < 0 || session_id > SESSION_MAX) { + pr_err("%s: invalid session_id = %d\n", __func__, session_id); + return -EINVAL; + } + + return ((struct apr_svc *)session[session_id]->apr)->id; +} + + +static int __init q6asm_init(void) +{ + pr_debug("%s\n", __func__); + memset(session, 0, sizeof(session)); + + config_debug_fs_init(); + + return 0; +} + +device_initcall(q6asm_init); diff --git a/sound/soc/msm/qdsp6v2/q6audio-v2.c b/sound/soc/msm/qdsp6v2/q6audio-v2.c new file mode 100644 index 0000000000000000000000000000000000000000..8c524fa15e78abce7bfa7883bd90c6a29aaf5daf --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6audio-v2.c @@ -0,0 +1,151 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int q6audio_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 SLIMBUS_1_RX: return IDX_SLIMBUS_1_RX; + case SLIMBUS_1_TX: return IDX_SLIMBUS_1_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; + case RT_PROXY_PORT_001_RX: return IDX_RT_PROXY_PORT_001_RX; + case RT_PROXY_PORT_001_TX: return IDX_RT_PROXY_PORT_001_TX; + + default: return -EINVAL; + } +} + +int q6audio_get_port_id(u16 port_id) +{ + switch (port_id) { + case PRIMARY_I2S_RX: return AFE_PORT_ID_PRIMARY_MI2S_RX; + case PRIMARY_I2S_TX: return AFE_PORT_ID_PRIMARY_MI2S_TX; + case PCM_RX: return AFE_PORT_ID_PRIMARY_PCM_RX; + case PCM_TX: return AFE_PORT_ID_PRIMARY_PCM_TX; + case SECONDARY_I2S_RX: return AFE_PORT_ID_SECONDARY_MI2S_RX; + case SECONDARY_I2S_TX: return AFE_PORT_ID_SECONDARY_MI2S_TX; + case MI2S_RX: return AFE_PORT_ID_PRIMARY_MI2S_RX; + case MI2S_TX: return AFE_PORT_ID_PRIMARY_MI2S_TX; + case HDMI_RX: return AFE_PORT_ID_MULTICHAN_HDMI_RX; + case RSVD_2: return IDX_RSVD_2; + case RSVD_3: return IDX_RSVD_3; + case DIGI_MIC_TX: return AFE_PORT_ID_DIGITAL_MIC_TX; + case VOICE_RECORD_RX: return AFE_PORT_ID_VOICE_RECORD_RX; + case VOICE_RECORD_TX: return AFE_PORT_ID_VOICE_RECORD_TX; + case VOICE_PLAYBACK_TX: return AFE_PORT_ID_VOICE_PLAYBACK_TX; + case SLIMBUS_0_RX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX; + case SLIMBUS_0_TX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX; + case SLIMBUS_1_RX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX; + case SLIMBUS_1_TX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX; + case INT_BT_SCO_RX: return AFE_PORT_ID_INTERNAL_BT_SCO_RX; + case INT_BT_SCO_TX: return AFE_PORT_ID_INTERNAL_BT_SCO_TX; + case INT_BT_A2DP_RX: return AFE_PORT_ID_INTERNAL_BT_A2DP_RX; + case INT_FM_RX: return AFE_PORT_ID_INTERNAL_FM_RX; + case INT_FM_TX: return AFE_PORT_ID_INTERNAL_FM_TX; + case RT_PROXY_PORT_001_RX: return AFE_PORT_ID_RT_PROXY_PORT_001_RX; + case RT_PROXY_PORT_001_TX: return AFE_PORT_ID_RT_PROXY_PORT_001_TX; + + default: return -EINVAL; + } +} +int q6audio_convert_virtual_to_portid(u16 port_id) +{ + int ret; + + /* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ + if (q6audio_validate_port(port_id) < 0) { + if (port_id == RT_PROXY_DAI_001_RX || + port_id == RT_PROXY_DAI_001_TX || + port_id == RT_PROXY_DAI_002_RX || + port_id == RT_PROXY_DAI_002_TX) + ret = VIRTUAL_ID_TO_PORTID(port_id); + else + ret = -EINVAL; + } else + ret = port_id; + + return ret; +} + +int q6audio_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 SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case INT_FM_TX: + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + { + ret = 0; + break; + } + + default: + ret = -EINVAL; + } + + return ret; +} diff --git a/sound/soc/omap/abe/Makefile b/sound/soc/omap/abe/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0d5649b273631a3f295e7347a79cd9a938ae004d --- /dev/null +++ b/sound/soc/omap/abe/Makefile @@ -0,0 +1,14 @@ +snd-soc-abe-hal-objs += abe_main.o \ + abe_core.o \ + abe_gain.o \ + abe_port.o \ + abe_aess.o \ + abe_dbg.o \ + abe_dat.o \ + abe_ini.o \ + abe_irq.o \ + abe_seq.o \ + abe_asrc.o \ + port_mgr.o \ + +obj-$(CONFIG_SND_OMAP_SOC_ABE_DSP) += snd-soc-abe-hal.o diff --git a/sound/soc/omap/abe/abe.h b/sound/soc/omap/abe/abe.h new file mode 100644 index 0000000000000000000000000000000000000000..c465764e778fc9ae8cb8f7a4d2b4a62d984a6d86 --- /dev/null +++ b/sound/soc/omap/abe/abe.h @@ -0,0 +1,159 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_H_ +#define _ABE_H_ + +#include +#include +#include +#include +#include +#include + +#include "abe_def.h" +#include "abe_define.h" +#include "abe_fw.h" +#include "abe_ext.h" +#include "abe_dbg.h" + +/* + * BASIC TYPES + */ +#define MAX_UINT8 ((((1L << 7) - 1) << 1) + 1) +#define MAX_UINT16 ((((1L << 15) - 1) << 1) + 1) +#define MAX_UINT32 ((((1L << 31) - 1) << 1) + 1) + +#define s8 char +#define u8 unsigned char +#define s16 short +#define u16 unsigned short +#define s32 int +#define u32 unsigned int + +struct omap_abe_equ { + /* type of filter */ + u32 equ_type; + /* filter length */ + u32 equ_length; + union { + /* parameters are the direct and recursive coefficients in */ + /* Q6.26 integer fixed-point format. */ + s32 type1[NBEQ1]; + struct { + /* center frequency of the band [Hz] */ + s32 freq[NBEQ2]; + /* gain of each band. [dB] */ + s32 gain[NBEQ2]; + /* Q factor of this band [dB] */ + s32 q[NBEQ2]; + } type2; + } coef; + s32 equ_param3; +}; + +struct omap_abe { + void __iomem *io_base[5]; + u32 firmware_version_number; + u16 MultiFrame[PROCESSING_SLOTS][TASKS_IN_SLOT]; + u32 compensated_mixer_gain; + u8 muted_gains_indicator[MAX_NBGAIN_CMEM]; + u32 desired_gains_decibel[MAX_NBGAIN_CMEM]; + u32 muted_gains_decibel[MAX_NBGAIN_CMEM]; + u32 desired_gains_linear[MAX_NBGAIN_CMEM]; + u32 desired_ramp_delay_ms[MAX_NBGAIN_CMEM]; + struct mutex mutex; + u32 warm_boot; + + u32 irq_dbg_read_ptr; + + struct omap_abe_dbg dbg; +}; + +extern struct omap_abe *abe; + +void omap_abe_dbg_log(struct omap_abe *abe, u32 x, u32 y, u32 z, u32 t); +void omap_abe_dbg_error(struct omap_abe *abe, int level, int error); +int omap_abe_set_opp_processing(struct omap_abe *abe, u32 opp); +int omap_abe_connect_debug_trace(struct omap_abe *abe, + struct omap_abe_dma *dma2); + +int omap_abe_use_compensated_gain(struct omap_abe *abe, int on_off); +int omap_abe_write_equalizer(struct omap_abe *abe, + u32 id, struct omap_abe_equ *param); + +int omap_abe_disable_gain(struct omap_abe *abe, u32 id, u32 p); +int omap_abe_enable_gain(struct omap_abe *abe, u32 id, u32 p); +int omap_abe_mute_gain(struct omap_abe *abe, u32 id, u32 p); +int omap_abe_unmute_gain(struct omap_abe *abe, u32 id, u32 p); + +int omap_abe_write_gain(struct omap_abe *abe, + u32 id, s32 f_g, u32 ramp, u32 p); +int omap_abe_write_mixer(struct omap_abe *abe, + u32 id, s32 f_g, u32 f_ramp, u32 p); +int omap_abe_read_gain(struct omap_abe *abe, + u32 id, u32 *f_g, u32 p); +int omap_abe_read_mixer(struct omap_abe *abe, + u32 id, u32 *f_g, u32 p); + +/* + * MACROS + */ +#define _log(x, y, z, t) { if (x & abe->dbg.mask) omap_abe_dbg_log(abe, x, y, z, t); } + +#endif/* _ABE_H_ */ diff --git a/sound/soc/omap/abe/abe_aess.c b/sound/soc/omap/abe/abe_aess.c new file mode 100644 index 0000000000000000000000000000000000000000..eb35b58c70963ec852fd9c8f5ed912e627052961 --- /dev/null +++ b/sound/soc/omap/abe/abe_aess.c @@ -0,0 +1,191 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_dbg.h" +#include "abe.h" +#include "abe_mem.h" +#include "abe_aess.h" + +/** + * omap_abe_hw_configuration + * + */ +void omap_abe_hw_configuration(struct omap_abe *abe) +{ + /* enables the DMAreq from AESS AESS_DMAENABLE_SET = 255 */ + omap_abe_reg_writel(abe, AESS_DMAENABLE_SET, DMA_ENABLE_ALL); + /* enables the MCU IRQ from AESS to Cortex A9 */ + omap_abe_reg_writel(abe, AESS_MCU_IRQENABLE_SET, INT_SET); +} + +/** + * omap_abe_clear_irq - clear ABE interrupt + * @abe: Pointer on abe handle + * + * This subroutine is call to clear MCU Irq + */ +int omap_abe_clear_irq(struct omap_abe *abe) +{ + omap_abe_reg_writel(abe, ABE_MCU_IRQSTATUS, INT_CLR); + return 0; +} +EXPORT_SYMBOL(omap_abe_clear_irq); + +/** + * abe_write_event_generator - Selects event generator source + * @abe: Pointer on abe handle + * @e: Event Generation Counter, McPDM, DMIC or default. + * + * Loads the AESS event generator hardware source. + * Loads the firmware parameters accordingly. + * Indicates to the FW which data stream is the most important to preserve + * in case all the streams are asynchronous. + * If the parameter is "default", then HAL decides which Event source + * is the best appropriate based on the opened ports. + * + * When neither the DMIC and the McPDM are activated, the AE will have + * its EVENT generator programmed with the EVENT_COUNTER. + * The event counter will be tuned in order to deliver a pulse frequency higher + * than 96 kHz. + * The DPLL output at 100% OPP is MCLK = (32768kHz x6000) = 196.608kHz + * The ratio is (MCLK/96000)+(1<<1) = 2050 + * (1<<1) in order to have the same speed at 50% and 100% OPP + * (only 15 MSB bits are used at OPP50%) + */ +int omap_abe_write_event_generator(struct omap_abe *abe, u32 e) +{ + u32 event, selection; + u32 counter = EVENT_GENERATOR_COUNTER_DEFAULT; + + _log(ABE_ID_WRITE_EVENT_GENERATOR, e, 0, 0); + + switch (e) { + case EVENT_TIMER: + selection = EVENT_SOURCE_COUNTER; + event = 0; + break; + case EVENT_44100: + selection = EVENT_SOURCE_COUNTER; + event = 0; + counter = EVENT_GENERATOR_COUNTER_44100; + break; + default: + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, ABE_BLOCK_COPY_ERR); + } + omap_abe_reg_writel(abe, EVENT_GENERATOR_COUNTER, counter); + omap_abe_reg_writel(abe, EVENT_SOURCE_SELECTION, selection); + omap_abe_reg_writel(abe, EVENT_GENERATOR_START, EVENT_GENERATOR_ON); + omap_abe_reg_writel(abe, AUDIO_ENGINE_SCHEDULER, event); + return 0; +} +EXPORT_SYMBOL(omap_abe_write_event_generator); + +/** + * omap_abe_start_event_generator - Starts event generator source + * @abe: Pointer on abe handle + * + * Start the event genrator of AESS. No more event will be send to AESS engine. + * Upper layer must wait 1/96kHz to be sure that engine reaches + * the IDLE instruction. + */ +int omap_abe_start_event_generator(struct omap_abe *abe) +{ + /* Start the event Generator */ + omap_abe_reg_writel(abe, EVENT_GENERATOR_START, 1); + return 0; +} +EXPORT_SYMBOL(omap_abe_start_event_generator); + +/** + * omap_abe_stop_event_generator - Stops event generator source + * @abe: Pointer on abe handle + * + * Stop the event genrator of AESS. No more event will be send to AESS engine. + * Upper layer must wait 1/96kHz to be sure that engine reaches + * the IDLE instruction. + */ +int omap_abe_stop_event_generator(struct omap_abe *abe) +{ + /* Stop the event Generator */ + omap_abe_reg_writel(abe, EVENT_GENERATOR_START, 0); + return 0; +} +EXPORT_SYMBOL(omap_abe_stop_event_generator); + +/** + * omap_abe_disable_irq - disable MCU/DSP ABE interrupt + * @abe: Pointer on abe handle + * + * This subroutine is disabling ABE MCU/DSP Irq + */ +int omap_abe_disable_irq(struct omap_abe *abe) +{ + /* disables the DMAreq from AESS AESS_DMAENABLE_CLR = 127 + * DMA_Req7 will still be enabled as it is used for ABE trace */ + omap_abe_reg_writel(abe, AESS_DMAENABLE_CLR, 0x7F); + /* disables the MCU IRQ from AESS to Cortex A9 */ + omap_abe_reg_writel(abe, AESS_MCU_IRQENABLE_CLR, 0x01); + return 0; +} +EXPORT_SYMBOL(omap_abe_disable_irq); diff --git a/sound/soc/omap/abe/abe_aess.h b/sound/soc/omap/abe/abe_aess.h new file mode 100644 index 0000000000000000000000000000000000000000..70c54f8ecc1eecd2ddd05e5fe7ae2a1667be0e34 --- /dev/null +++ b/sound/soc/omap/abe/abe_aess.h @@ -0,0 +1,113 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_AESS_H_ +#define _ABE_AESS_H_ + +#define AESS_REVISION 0x00 +#define AESS_MCU_IRQSTATUS 0x28 +#define AESS_MCU_IRQENABLE_SET 0x3C +#define AESS_MCU_IRQENABLE_CLR 0x40 +#define AESS_DMAENABLE_SET 0x60 +#define AESS_DMAENABLE_CLR 0x64 +#define EVENT_GENERATOR_COUNTER 0x68 +#define EVENT_GENERATOR_START 0x6C +#define EVENT_SOURCE_SELECTION 0x70 +#define AUDIO_ENGINE_SCHEDULER 0x74 + +/* + * AESS_MCU_IRQSTATUS bit field + */ +#define INT_CLEAR 0x01 + +/* + * AESS_MCU_IRQENABLE_SET bit field + */ +#define INT_SET 0x01 + +/* + * AESS_MCU_IRQENABLE_CLR bit field + */ +#define INT_CLR 0x01 + +/* + * AESS_DMAENABLE_SET bit fields + */ +#define DMA_ENABLE_ALL 0xFF + +/* + * AESS_DMAENABLE_CLR bit fields + */ +#define DMA_DISABLE_ALL 0xFF + +/* + * EVENT_GENERATOR_COUNTER COUNTER_VALUE bit field + */ +/* PLL output/desired sampling rate = (32768 * 6000)/96000 */ +#define EVENT_GENERATOR_COUNTER_DEFAULT (2048-1) +/* PLL output/desired sampling rate = (32768 * 6000)/88200 */ +#define EVENT_GENERATOR_COUNTER_44100 (2228-1) + + +int omap_abe_start_event_generator(struct omap_abe *abe); +int omap_abe_stop_event_generator(struct omap_abe *abe); +int omap_abe_write_event_generator(struct omap_abe *abe, u32 e); + +void omap_abe_hw_configuration(struct omap_abe *abe); + +#endif/* _ABE_AESS_H_ */ diff --git a/sound/soc/omap/abe/abe_api.h b/sound/soc/omap/abe/abe_api.h new file mode 100644 index 0000000000000000000000000000000000000000..430aa5b07daf96db7557562bb714f00337200910 --- /dev/null +++ b/sound/soc/omap/abe/abe_api.h @@ -0,0 +1,540 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_API_H_ +#define _ABE_API_H_ + +#include +#include +#include +#include +#include + +#include "abe_dm_addr.h" +#include "abe_dbg.h" + +#define ABE_TASK_ID(ID) (OMAP_ABE_D_TASKSLIST_ADDR + sizeof(ABE_STask)*(ID)) + +#define TASK_ASRC_VX_DL_SLT 0 +#define TASK_ASRC_VX_DL_IDX 3 +#define TASK_VX_DL_SLT 1 +#define TASK_VX_DL_IDX 3 +#define TASK_DL2Mixer_SLT 1 +#define TASK_DL2Mixer_IDX 6 +#define TASK_DL1Mixer_SLT 2 +#define TASK_DL1Mixer_IDX 0 +#define TASK_VX_UL_SLT 12 +#define TASK_VX_UL_IDX 5 +#define TASK_BT_DL_48_8_SLT 14 +#define TASK_BT_DL_48_8_IDX 4 +#define TASK_ASRC_BT_UL_SLT 15 +#define TASK_ASRC_BT_UL_IDX 6 +#define TASK_ASRC_VX_UL_SLT 16 +#define TASK_ASRC_VX_UL_IDX 2 +#define TASK_BT_UL_8_48_SLT 17 +#define TASK_BT_UL_8_48_IDX 2 +#define TASK_IO_MM_DL_SLT 18 +#define TASK_IO_MM_DL_IDX 0 +#define TASK_ASRC_BT_DL_SLT 18 +#define TASK_ASRC_BT_DL_IDX 6 + + +struct omap_abe { + void __iomem *io_base[5]; + u32 firmware_version_number; + u16 MultiFrame[PROCESSING_SLOTS][TASKS_IN_SLOT]; + u32 compensated_mixer_gain; + u8 muted_gains_indicator[MAX_NBGAIN_CMEM]; + u32 desired_gains_decibel[MAX_NBGAIN_CMEM]; + u32 muted_gains_decibel[MAX_NBGAIN_CMEM]; + u32 desired_gains_linear[MAX_NBGAIN_CMEM]; + u32 desired_ramp_delay_ms[MAX_NBGAIN_CMEM]; + struct mutex mutex; + u32 warm_boot; + + u32 irq_dbg_read_ptr; + u32 dbg_param; + + struct omap_abe_dbg dbg; +}; + +/** + * abe_reset_hal - reset the ABE/HAL + * @rdev: regulator source + * @constraints: constraints to apply + * + * Operations : reset the HAL by reloading the static variables and + * default AESS registers. + * Called after a PRCM cold-start reset of ABE + */ +abehal_status abe_reset_hal(void); +/** + * abe_load_fw_param - Load ABE Firmware memories + * @PMEM: Pointer of Program memory data + * @PMEM_SIZE: Size of PMEM data + * @CMEM: Pointer of Coeffients memory data + * @CMEM_SIZE: Size of CMEM data + * @SMEM: Pointer of Sample memory data + * @SMEM_SIZE: Size of SMEM data + * @DMEM: Pointer of Data memory data + * @DMEM_SIZE: Size of DMEM data + * + */ +abehal_status abe_load_fw_param(u32 *FW); +/** + * abe_irq_processing - Process ABE interrupt + * + * This subroutine is call upon reception of "MA_IRQ_99 ABE_MPU_IRQ" Audio + * back-end interrupt. This subroutine will check the ATC Hrdware, the + * IRQ_FIFO from the AE and act accordingly. Some IRQ source are originated + * for the delivery of "end of time sequenced tasks" notifications, some are + * originated from the Ping-Pong protocols, some are generated from + * the embedded debugger when the firmware stops on programmable break-points, + * etc ... + */ +abehal_status abe_irq_processing(void); +/** + * abe_irq_clear - clear ABE interrupt + * + * This subroutine is call to clear MCU Irq + */ +abehal_status abe_clear_irq(void); +/** + * abe_disable_irq - disable MCU/DSP ABE interrupt + * + * This subroutine is disabling ABE MCU/DSP Irq + */ +abehal_status abe_disable_irq(void); +/* + * abe_check_activity - check all ports are closed + */ +u32 abe_check_activity(void); +/** + * abe_wakeup - Wakeup ABE + * + * Wakeup ABE in case of retention + */ +abehal_status abe_wakeup(void); +/** + * abe_start_event_generator - Stops event generator source + * + * Start the event genrator of AESS. No more event will be send to AESS engine. + * Upper layer must wait 1/96kHz to be sure that engine reaches + * the IDLE instruction. + */ +abehal_status abe_start_event_generator(void); +/** + * abe_stop_event_generator - Stops event generator source + * + * Stop the event genrator of AESS. No more event will be send to AESS engine. + * Upper layer must wait 1/96kHz to be sure that engine reaches + * the IDLE instruction. + */ +abehal_status abe_stop_event_generator(void); + +/** + * abe_write_event_generator - Selects event generator source + * @e: Event Generation Counter, McPDM, DMIC or default. + * + * Loads the AESS event generator hardware source. + * Loads the firmware parameters accordingly. + * Indicates to the FW which data stream is the most important to preserve + * in case all the streams are asynchronous. + * If the parameter is "default", then HAL decides which Event source + * is the best appropriate based on the opened ports. + * + * When neither the DMIC and the McPDM are activated, the AE will have + * its EVENT generator programmed with the EVENT_COUNTER. + * The event counter will be tuned in order to deliver a pulse frequency higher + * than 96 kHz. + * The DPLL output at 100% OPP is MCLK = (32768kHz x6000) = 196.608kHz + * The ratio is (MCLK/96000)+(1<<1) = 2050 + * (1<<1) in order to have the same speed at 50% and 100% OPP + * (only 15 MSB bits are used at OPP50%) + */ +abehal_status abe_write_event_generator(u32 e); +/** + * abe_set_opp_processing - Set OPP mode for ABE Firmware + * @opp: OOPP mode + * + * New processing network and OPP: + * 0: Ultra Lowest power consumption audio player (no post-processing, no mixer) + * 1: OPP 25% (simple multimedia features, including low-power player) + * 2: OPP 50% (multimedia and voice calls) + * 3: OPP100% (EANC, multimedia complex use-cases) + * + * Rearranges the FW task network to the corresponding OPP list of features. + * The corresponding AE ports are supposed to be set/reset accordingly before + * this switch. + * + */ +abehal_status abe_set_opp_processing(u32 opp); +/** + * abe_set_ping_pong_buffer + * @port: ABE port ID + * @n_bytes: Size of Ping/Pong buffer + * + * Updates the next ping-pong buffer with "size" bytes copied from the + * host processor. This API notifies the FW that the data transfer is done. + */ +abehal_status abe_set_ping_pong_buffer(u32 port, u32 n_bytes); +/** + * abe_read_next_ping_pong_buffer + * @port: ABE portID + * @p: Next buffer address (pointer) + * @n: Next buffer size (pointer) + * + * Tell the next base address of the next ping_pong Buffer and its size + */ +abehal_status abe_read_next_ping_pong_buffer(u32 port, u32 *p, u32 *n); +/** + * abe_init_ping_pong_buffer + * @id: ABE port ID + * @size_bytes:size of the ping pong + * @n_buffers:number of buffers (2 = ping/pong) + * @p:returned address of the ping-pong list of base address (byte offset + from DMEM start) + * + * Computes the base address of the ping_pong buffers + */ +abehal_status abe_init_ping_pong_buffer(u32 id, u32 size_bytes, u32 n_buffers, + u32 *p); +/** + * abe_read_offset_from_ping_buffer + * @id: ABE port ID + * @n: returned address of the offset + * from the ping buffer start address (in samples) + * + * Computes the current firmware ping pong read pointer location, + * expressed in samples, as the offset from the start address of ping buffer. + */ +abehal_status abe_read_offset_from_ping_buffer(u32 id, u32 *n); +/** + * abe_plug_subroutine + * @id: returned sequence index after plugging a new subroutine + * @f: subroutine address to be inserted + * @n: number of parameters of this subroutine + * @params: pointer on parameters + * + * register a list of subroutines for call-back purpose + */ +abehal_status abe_plug_subroutine(u32 *id, abe_subroutine2 f, u32 n, + u32 *params); +/** + * abe_set_sequence_time_accuracy + * @fast: fast counter + * @slow: slow counter + * + */ +abehal_status abe_set_sequence_time_accuracy(u32 fast, u32 slow); +/** + * abe_reset_port + * @id: ABE port ID + * + * stop the port activity and reload default parameters on the associated + * processing features. + * Clears the internal AE buffers. + */ +abehal_status abe_reset_port(u32 id); +/** + * abe_read_remaining_data + * @id: ABE port_ID + * @n: size pointer to the remaining number of 32bits words + * + * computes the remaining amount of data in the buffer. + */ +abehal_status abe_read_remaining_data(u32 port, u32 *n); +/** + * abe_disable_data_transfer + * @id: ABE port id + * + * disables the ATC descriptor and stop IO/port activities + * disable the IO task (@f = 0) + * clear ATC DMEM buffer, ATC enabled + */ +abehal_status abe_disable_data_transfer(u32 id); +/** + * abe_enable_data_transfer + * @ip: ABE port id + * + * enables the ATC descriptor + * reset ATC pointers + * enable the IO task (@f <> 0) + */ +abehal_status abe_enable_data_transfer(u32 id); +/** + * abe_set_dmic_filter + * @d: DMIC decimation ratio : 16/25/32/40 + * + * Loads in CMEM a specific list of coefficients depending on the DMIC sampling + * frequency (2.4MHz or 3.84MHz). This table compensates the DMIC decimator + * roll-off at 20kHz. + * The default table is loaded with the DMIC 2.4MHz recommended configuration. + */ +abehal_status abe_set_dmic_filter(u32 d); +/** + * abe_connect_cbpr_dmareq_port + * @id: port name + * @f: desired data format + * @d: desired dma_request line (0..7) + * @a: returned pointer to the base address of the CBPr register and number of + * samples to exchange during a DMA_request. + * + * enables the data echange between a DMA and the ABE through the + * CBPr registers of AESS. + */ +abehal_status abe_connect_cbpr_dmareq_port(u32 id, abe_data_format_t *f, u32 d, + abe_dma_t *returned_dma_t); +/** + * abe_connect_irq_ping_pong_port + * @id: port name + * @f: desired data format + * @I: index of the call-back subroutine to call + * @s: half-buffer (ping) size + * @p: returned base address of the first (ping) buffer) + * + * enables the data echanges between a direct access to the DMEM + * memory of ABE using cache flush. On each IRQ activation a subroutine + * registered with "abe_plug_subroutine" will be called. This subroutine + * will generate an amount of samples, send them to DMEM memory and call + * "abe_set_ping_pong_buffer" to notify the new amount of samples in the + * pong buffer. + */ +abehal_status abe_connect_irq_ping_pong_port(u32 id, abe_data_format_t *f, + u32 subroutine_id, u32 size, + u32 *sink, u32 dsp_mcu_flag); +/** + * abe_connect_serial_port() + * @id: port name + * @f: data format + * @i: peripheral ID (McBSP #1, #2, #3) + * + * Operations : enables the data echanges between a McBSP and an ATC buffer in + * DMEM. This API is used connect 48kHz McBSP streams to MM_DL and 8/16kHz + * voice streams to VX_UL, VX_DL, BT_VX_UL, BT_VX_DL. It abstracts the + * abe_write_port API. + */ +abehal_status abe_connect_serial_port(u32 id, abe_data_format_t *f, + u32 mcbsp_id); +/** + * abe_read_port_address + * @dma: output pointer to the DMA iteration and data destination pointer + * + * This API returns the address of the DMA register used on this audio port. + * Depending on the protocol being used, adds the base address offset L3 + * (DMA) or MPU (ARM) + */ +abehal_status abe_read_port_address(u32 port, abe_dma_t *dma2); +/** + * abe_write_equalizer + * @id: name of the equalizer + * @param : equalizer coefficients + * + * Load the coefficients in CMEM. + */ +abehal_status abe_write_equalizer(u32 id, abe_equ_t *param); +/** + * abe_write_asrc + * @id: name of the port + * @param: drift value to compensate [ppm] + * + * Load the drift variables to the FW memory. This API can be called only + * when the corresponding port has been already opened and the ASRC has + * been correctly initialized with API abe_init_asrc_... If this API is + * used such that the drift has been changed from positive to negative drift + * or vice versa, there will be click in the output signal. Loading the drift + * value with zero disables the feature. + */ +abehal_status abe_write_asrc(u32 port, s32 dppm); +/** + * abe_write_aps + * @id: name of the aps filter + * @param: table of filter coefficients + * + * Load the filters and thresholds coefficients in FW memory. This AP + * can be called when the corresponding APS is not activated. After + * reloading the firmware the default coefficients corresponds to "no APS + * activated". + * Loading all the coefficients value with zero disables the feature. + */ +abehal_status abe_write_aps(u32 id, struct abe_aps_t *param); +/** + * abe_write_mixer + * @id: name of the mixer + * @param: list of input gains of the mixer + * @p: list of port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's gain + * in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +abehal_status abe_write_gain(u32 id, s32 f_g, u32 ramp, u32 p); +abehal_status abe_use_compensated_gain(u32 on_off); +abehal_status abe_enable_gain(u32 id, u32 p); +abehal_status abe_disable_gain(u32 id, u32 p); +abehal_status abe_mute_gain(u32 id, u32 p); +abehal_status abe_unmute_gain(u32 id, u32 p); +/** + * abe_write_mixer + * @id: name of the mixer + * @param: input gains and delay ramp of the mixer + * @p: port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's + * gain in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +abehal_status abe_write_mixer(u32 id, s32 f_g, u32 f_ramp, u32 p); +/** + * abe_read_gain + * @id: name of the mixer + * @param: list of input gains of the mixer + * @p: list of port corresponding to the above gains + * + */ +abehal_status abe_read_gain(u32 id, u32 *f_g, u32 p); +/** + * abe_read_mixer + * @id: name of the mixer + * @param: gains of the mixer + * @p: port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's + * gain in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +abehal_status abe_read_mixer(u32 id, u32 *f_g, u32 p); +/** + * abe_mono_mixer + * id: name of the mixer (MIXDL1 or MIXDL2) + * on_off: enable\disable flag + * + * This API Programs DL1Mixer or DL2Mixer to output mono data + * on both left and right data paths. + */ +abehal_status abe_mono_mixer(u32 id, u32 on_off); +/** + * abe_set_router_configuration + * @Id: name of the router + * @Conf: id of the configuration + * @param: list of output index of the route + * + * The uplink router takes its input from DMIC (6 samples), AMIC (2 samples) + * and PORT1/2 (2 stereo ports). Each sample will be individually stored in + * an intermediate table of 10 elements. The intermediate table is used to + * route the samples to three directions : REC1 mixer, 2 EANC DMIC source of + * filtering and MM recording audio path. + */ +abehal_status abe_set_router_configuration(u32 id, u32 k, u32 *param); +/** + * ABE_READ_DEBUG_TRACE + * + * Parameters : + * @data: data destination pointer + * @n : max number of read data + * + * Operations : + * Reads the AE circular data pointer that holds pairs of debug data + + * timestamps, and stores the pairs, via linear addressing, to the parameter + * pointer. + * Stops the copy when the max parameter is reached or when the FIFO is empty. + * + * Return value : + * None. + */ +abehal_status abe_read_debug_trace(u32 *data, u32 *n); +/** + * abe_connect_debug_trace + * @dma2:pointer to the DMEM trace buffer + * + * returns the address and size of the real-time debug trace buffer, + * the content of which will vary from one firmware release to an other + */ +abehal_status abe_connect_debug_trace(abe_dma_t *dma2); +/** + * abe_set_debug_trace + * @debug: debug ID from a list to be defined + * + * load a mask which filters the debug trace to dedicated types of data + */ +abehal_status abe_set_debug_trace(abe_dbg_t debug); +/** + * abe_init_mem - Allocate Kernel space memory map for ABE + * + * Memory map of ABE memory space for PMEM/DMEM/SMEM/DMEM + */ +void abe_init_mem(void __iomem **_io_base); + +/** + * abe_write_pdmdl_offset - write the desired offset on the DL1/DL2 paths + * + * Parameters: + * path: 1 for the DL1 ABE path, 2 for the DL2 ABE path + * offset_left: integer value that will be added on all PDM left samples + * offset_right: integer value that will be added on all PDM right samples + * + */ +void abe_write_pdmdl_offset(u32 path, u32 offset_left, u32 offset_right); + +#endif/* _ABE_API_H_ */ diff --git a/sound/soc/omap/abe/abe_asrc.c b/sound/soc/omap/abe/abe_asrc.c new file mode 100644 index 0000000000000000000000000000000000000000..4a52235f2ff5cdbf6fd129a13191b6b8870635c1 --- /dev/null +++ b/sound/soc/omap/abe/abe_asrc.c @@ -0,0 +1,1231 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_legacy.h" +#include "abe_dbg.h" + +#include "abe_typedef.h" +#include "abe_initxxx_labels.h" +#include "abe_dbg.h" +#include "abe_mem.h" +#include "abe_sm_addr.h" +#include "abe_cm_addr.h" + +/** + * abe_write_fifo + * @mem_bank: currently only ABE_DMEM supported + * @addr: FIFO descriptor address ( descriptor fields : READ ptr, WRITE ptr, + * FIFO START_ADDR, FIFO END_ADDR) + * @data: data to write to FIFO + * @number: number of 32-bit words to write to DMEM FIFO + * + * write DMEM FIFO and update FIFO descriptor, + * it is assumed that FIFO descriptor is located in DMEM + */ +void abe_write_fifo(u32 memory_bank, u32 descr_addr, u32 *data, u32 nb_data32) +{ + u32 fifo_addr[4]; + u32 i; + /* read FIFO descriptor from DMEM */ + omap_abe_mem_read(abe, OMAP_ABE_DMEM, descr_addr, + &fifo_addr[0], 4 * sizeof(u32)); + /* WRITE ptr < FIFO start address */ + if (fifo_addr[1] < fifo_addr[2]) + omap_abe_dbg_error(abe, OMAP_ABE_ERR_DBG, + ABE_FW_FIFO_WRITE_PTR_ERR); + /* WRITE ptr > FIFO end address */ + if (fifo_addr[1] > fifo_addr[3]) + omap_abe_dbg_error(abe, OMAP_ABE_ERR_DBG, + ABE_FW_FIFO_WRITE_PTR_ERR); + switch (memory_bank) { + case ABE_DMEM: + for (i = 0; i < nb_data32; i++) { + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (s32) fifo_addr[1], (u32 *) (data + i), + 4); + /* increment WRITE pointer */ + fifo_addr[1] = fifo_addr[1] + 4; + if (fifo_addr[1] > fifo_addr[3]) + fifo_addr[1] = fifo_addr[2]; + if (fifo_addr[1] == fifo_addr[0]) + omap_abe_dbg_error(abe, OMAP_ABE_ERR_DBG, + ABE_FW_FIFO_WRITE_PTR_ERR); + } + /* update WRITE pointer in DMEM */ + omap_abe_mem_write(abe, OMAP_ABE_DMEM, descr_addr + + sizeof(u32), &fifo_addr[1], 4); + break; + default: + break; + } +} + +/** + * abe_write_asrc + * @id: name of the port + * @param: drift value to compensate [ppm] + * + * Load the drift variables to the FW memory. This API can be called only + * when the corresponding port has been already opened and the ASRC has + * been correctly initialized with API abe_init_asrc_... If this API is + * used such that the drift has been changed from positive to negative drift + * or vice versa, there will be click in the output signal. Loading the drift + * value with zero disables the feature. + */ +abehal_status abe_write_asrc(u32 port, s32 dppm) +{ + s32 dtempvalue, adppm, drift_sign, drift_sign_addr, alpha_params_addr; + s32 alpha_params[3]; + _log(ABE_ID_WRITE_ASRC, port, dppm, dppm >> 8); + /* + * x = ppm + * + * - 1000000/x must be multiple of 16 + * - deltaalpha = round(2^20*x*16/1000000)=round(2^18/5^6*x) on 22 bits. + * then shifted by 2bits + * - minusdeltaalpha + * - oneminusepsilon = 1-deltaalpha/2. + * + * ppm = 250 + * - 1000000/250=4000 + * - deltaalpha = 4194.3 ~ 4195 => 0x00418c + */ + /* examples for -6250 ppm */ + /* atempvalue32[1] = -1; d_driftsign */ + /* atempvalue32[3] = 0x00066668; d_deltaalpha */ + /* atempvalue32[4] = 0xfff99998; d_minusdeltaalpha */ + /* atempvalue32[5] = 0x003ccccc; d_oneminusepsilon */ + /* example for 100 ppm */ + /* atempvalue32[1] = 1;* d_driftsign */ + /* atempvalue32[3] = 0x00001a38; d_deltaalpha */ + /* atempvalue32[4] = 0xffffe5c8; d_minusdeltaalpha */ + /* atempvalue32[5] = 0x003ccccc; d_oneminusepsilon */ + /* compute new value for the ppm */ + if (dppm >= 0) { + /* d_driftsign */ + drift_sign = 1; + adppm = dppm; + } else { + /* d_driftsign */ + drift_sign = -1; + adppm = (-1 * dppm); + } + if (dppm == 0) { + /* delta_alpha */ + alpha_params[0] = 0; + /* minusdelta_alpha */ + alpha_params[1] = 0; + /* one_minusepsilon */ + alpha_params[2] = 0x003ffff0; + } else { + dtempvalue = (adppm << 4) + adppm - ((adppm * 3481L) / 15625L); + /* delta_alpha */ + alpha_params[0] = dtempvalue << 2; + /* minusdelta_alpha */ + alpha_params[1] = (-dtempvalue) << 2; + /* one_minusepsilon */ + alpha_params[2] = (0x00100000 - (dtempvalue / 2)) << 2; + } + switch (port) { + /* asynchronous sample-rate-converter for the uplink voice path */ + case OMAP_ABE_VX_DL_PORT: + drift_sign_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (1 * sizeof(s32)); + alpha_params_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (3 * sizeof(s32)); + break; + /* asynchronous sample-rate-converter for the downlink voice path */ + case OMAP_ABE_VX_UL_PORT: + drift_sign_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (1 * sizeof(s32)); + alpha_params_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (3 * sizeof(s32)); + break; + /* asynchronous sample-rate-converter for the BT_UL path */ + case OMAP_ABE_BT_VX_UL_PORT: + drift_sign_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (1 * sizeof(s32)); + alpha_params_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (3 * sizeof(s32)); + break; + /* asynchronous sample-rate-converter for the BT_DL path */ + case OMAP_ABE_BT_VX_DL_PORT: + drift_sign_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (1 * sizeof(s32)); + alpha_params_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (3 * sizeof(s32)); + break; + default: + /* asynchronous sample-rate-converter for the MM_EXT_IN path */ + case OMAP_ABE_MM_EXT_IN_PORT: + drift_sign_addr = OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (1 * sizeof(s32)); + alpha_params_addr = + OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (3 * sizeof(s32)); + break; + } + omap_abe_mem_write(abe, OMAP_ABE_DMEM, drift_sign_addr, + (u32 *) &drift_sign, 4); + omap_abe_mem_write(abe, OMAP_ABE_DMEM, alpha_params_addr, + (u32 *) &alpha_params[0], 12); + return 0; +} +EXPORT_SYMBOL(abe_write_asrc); +/** + * abe_init_asrc_vx_dl + * + * Initialize the following ASRC VX_DL parameters : + * 1. DriftSign = D_AsrcVars[1] = 1 or -1 + * 2. Subblock = D_AsrcVars[2] = 0 + * 3. DeltaAlpha = D_AsrcVars[3] = + * (round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 4. MinusDeltaAlpha = D_AsrcVars[4] = + * (-round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 5. OneMinusEpsilon = D_AsrcVars[5] = 1 - DeltaAlpha/2 + * 6. AlphaCurrent = 0x000020 (CMEM), initial value of Alpha parameter + * 7. BetaCurrent = 0x3fffe0 (CMEM), initial value of Beta parameter + * AlphaCurrent + BetaCurrent = 1 (=0x400000 in CMEM = 2^20 << 2) + * 8. drift_ASRC = 0 & drift_io = 0 + * 9. SMEM for ASRC_DL_VX_Coefs pointer + * 10. CMEM for ASRC_DL_VX_Coefs pointer + * ASRC_DL_VX_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + * C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 + * 11. SMEM for XinASRC_DL_VX pointer + * 12. CMEM for XinASRC_DL_VX pointer + * XinASRC_DL_VX = S_XinASRC_DL_VX_ADDR/S_XinASRC_DL_VX_sizeof/0/1/0/0/0/0 + * 13. SMEM for IO_VX_DL_ASRC pointer + * 14. CMEM for IO_VX_DL_ASRC pointer + * IO_VX_DL_ASRC = + * S_XinASRC_DL_VX_ADDR/S_XinASRC_DL_VX_sizeof/ + * ASRC_DL_VX_FIR_L+ASRC_margin/1/0/0/0/0 + */ +void abe_init_asrc_vx_dl(s32 dppm) +{ + s32 el[45]; + s32 temp0, temp1, adppm, dtemp, mem_tag, mem_addr; + u32 i = 0; + u32 n_fifo_el = 42; + temp0 = 0; + temp1 = 1; + /* 1. DriftSign = D_AsrcVars[1] = 1 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (1 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm >= 0) { + el[i + 1] = 1; + adppm = dppm; + } else { + el[i + 1] = -1; + adppm = (-1 * dppm); + } + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + dtemp = (adppm << 4) + adppm - ((adppm * 3481L) / 15625L); + /* 2. Subblock = D_AsrcVars[2] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (2 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 3. DeltaAlpha = D_AsrcVars[3] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (3 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = dtemp << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 4. MinusDeltaAlpha = D_AsrcVars[4] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (4 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = (-dtemp) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /*5. OneMinusEpsilon = D_AsrcVars[5] = 0x00400000 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_DL_VX_ADDR + (5 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0x00400000; + else + el[i + 1] = (0x00100000 - (dtemp / 2)) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 6. AlphaCurrent = 0x000020 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_ALPHACURRENT_DL_VX_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x00000020; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 7. BetaCurrent = 0x3fffe0 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_BETACURRENT_DL_VX_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x003fffe0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 8. drift_ASRC = 0 & drift_io = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_VX_DL_PORT * sizeof(struct ABE_SIODescriptor)) + + drift_asrc_; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 9. SMEM for ASRC_DL_VX_Coefs pointer */ + /* ASRC_DL_VX_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_SMEM; + mem_addr = ASRC_DL_VX_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + if (dppm == 0) { + el[i + 1] = OMAP_ABE_C_COEFASRC16_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC16_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC15_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC15_VX_SIZE >> 2); + } else { + el[i + 1] = OMAP_ABE_C_COEFASRC1_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC1_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC2_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC2_VX_SIZE >> 2); + } + i = i + 3; + /* 10. CMEM for ASRC_DL_VX_Coefs pointer */ + /* ASRC_DL_VX_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_CMEM; + mem_addr = ASRC_DL_VX_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp1; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 11. SMEM for XinASRC_DL_VX pointer */ + /* XinASRC_DL_VX = + S_XinASRC_DL_VX_ADDR/S_XinASRC_DL_VX_sizeof/0/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = XinASRC_DL_VX_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_DL_VX_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_DL_VX_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 12. CMEM for XinASRC_DL_VX pointer */ + /* XinASRC_DL_VX = + S_XinASRC_DL_VX_ADDR/S_XinASRC_DL_VX_sizeof/0/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = XinASRC_DL_VX_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 13. SMEM for IO_VX_DL_ASRC pointer */ + /* IO_VX_DL_ASRC = S_XinASRC_DL_VX_ADDR/S_XinASRC_DL_VX_sizeof/ + ASRC_DL_VX_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = IO_VX_DL_ASRC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_DL_VX_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_DL_VX_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 14. CMEM for IO_VX_DL_ASRC pointer */ + /* IO_VX_DL_ASRC = S_XinASRC_DL_VX_ADDR/S_XinASRC_DL_VX_sizeof/ + ASRC_DL_VX_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = IO_VX_DL_ASRC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_DL_VX_FIR_L + ASRC_margin) << 16) + (temp1 << 12) + + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + abe_write_fifo(ABE_DMEM, OMAP_ABE_D_FWMEMINITDESCR_ADDR, (u32 *) &el[0], + n_fifo_el); +} +/** + * abe_init_asrc_vx_ul + * + * Initialize the following ASRC VX_UL parameters : + * 1. DriftSign = D_AsrcVars[1] = 1 or -1 + * 2. Subblock = D_AsrcVars[2] = 0 + * 3. DeltaAlpha = D_AsrcVars[3] = + * (round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 4. MinusDeltaAlpha = D_AsrcVars[4] = + * (-round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 5. OneMinusEpsilon = D_AsrcVars[5] = 1 - DeltaAlpha/2 + * 6. AlphaCurrent = 0x000020 (CMEM), initial value of Alpha parameter + * 7. BetaCurrent = 0x3fffe0 (CMEM), initial value of Beta parameter + * AlphaCurrent + BetaCurrent = 1 (=0x400000 in CMEM = 2^20 << 2) + * 8. drift_ASRC = 0 & drift_io = 0 + * 9. SMEM for ASRC_UL_VX_Coefs pointer + * 10. CMEM for ASRC_UL_VX_Coefs pointer + * ASRC_UL_VX_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + * C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 + * 11. SMEM for XinASRC_UL_VX pointer + * 12. CMEM for XinASRC_UL_VX pointer + * XinASRC_UL_VX = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/0/1/0/0/0/0 + * 13. SMEM for UL_48_8_DEC pointer + * 14. CMEM for UL_48_8_DEC pointer + * UL_48_8_DEC = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/ + * ASRC_UL_VX_FIR_L+ASRC_margin/1/0/0/0/0 + * 15. SMEM for UL_48_16_DEC pointer + * 16. CMEM for UL_48_16_DEC pointer + * UL_48_16_DEC = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/ + * ASRC_UL_VX_FIR_L+ASRC_margin/1/0/0/0/0 + */ +void abe_init_asrc_vx_ul(s32 dppm) +{ + s32 el[51]; + s32 temp0, temp1, adppm, dtemp, mem_tag, mem_addr; + u32 i = 0; + u32 n_fifo_el = 48; + temp0 = 0; + temp1 = 1; + /* 1. DriftSign = D_AsrcVars[1] = 1 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (1 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm >= 0) { + el[i + 1] = 1; + adppm = dppm; + } else { + el[i + 1] = -1; + adppm = (-1 * dppm); + } + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + dtemp = (adppm << 4) + adppm - ((adppm * 3481L) / 15625L); + /* 2. Subblock = D_AsrcVars[2] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (2 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 3. DeltaAlpha = D_AsrcVars[3] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (3 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = dtemp << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 4. MinusDeltaAlpha = D_AsrcVars[4] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (4 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = (-dtemp) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 5. OneMinusEpsilon = D_AsrcVars[5] = 0x00400000 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_UL_VX_ADDR + (5 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0x00400000; + else + el[i + 1] = (0x00100000 - (dtemp / 2)) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 6. AlphaCurrent = 0x000020 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_ALPHACURRENT_UL_VX_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x00000020; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 7. BetaCurrent = 0x3fffe0 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_BETACURRENT_UL_VX_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x003fffe0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 8. drift_ASRC = 0 & drift_io = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_VX_UL_PORT * sizeof(struct ABE_SIODescriptor)) + + drift_asrc_; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 9. SMEM for ASRC_UL_VX_Coefs pointer */ + /* ASRC_UL_VX_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_SMEM; + mem_addr = ASRC_UL_VX_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + if (dppm == 0) { + el[i + 1] = OMAP_ABE_C_COEFASRC16_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC16_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC15_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC15_VX_SIZE >> 2); + } else { + el[i + 1] = OMAP_ABE_C_COEFASRC1_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC1_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC2_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC2_VX_SIZE >> 2); + } + i = i + 3; + /* 10. CMEM for ASRC_UL_VX_Coefs pointer */ + /* ASRC_UL_VX_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_CMEM; + mem_addr = ASRC_UL_VX_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp1; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 11. SMEM for XinASRC_UL_VX pointer */ + /* XinASRC_UL_VX = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/0/1/ + 0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = XinASRC_UL_VX_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_UL_VX_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_UL_VX_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 12. CMEM for XinASRC_UL_VX pointer */ + /* XinASRC_UL_VX = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/0/1/ + 0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = XinASRC_UL_VX_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 13. SMEM for UL_48_8_DEC pointer */ + /* UL_48_8_DEC = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/ + ASRC_UL_VX_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = UL_48_8_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_UL_VX_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_UL_VX_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 14. CMEM for UL_48_8_DEC pointer */ + /* UL_48_8_DEC = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/ + ASRC_UL_VX_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = UL_48_8_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_UL_VX_FIR_L + ASRC_margin) << 16) + (temp1 << 12) + + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 15. SMEM for UL_48_16_DEC pointer */ + /* UL_48_16_DEC = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/ + ASRC_UL_VX_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = UL_48_16_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_UL_VX_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_UL_VX_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 16. CMEM for UL_48_16_DEC pointer */ + /* UL_48_16_DEC = S_XinASRC_UL_VX_ADDR/S_XinASRC_UL_VX_sizeof/ + ASRC_UL_VX_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = UL_48_16_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_UL_VX_FIR_L + ASRC_margin) << 16) + (temp1 << 12) + + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + abe_write_fifo(ABE_DMEM, OMAP_ABE_D_FWMEMINITDESCR_ADDR, (u32 *) &el[0], + n_fifo_el); +} +/** + * abe_init_asrc_mm_ext_in + * + * Initialize the following ASRC MM_EXT_IN parameters : + * 1. DriftSign = D_AsrcVars[1] = 1 or -1 + * 2. Subblock = D_AsrcVars[2] = 0 + * 3. DeltaAlpha = D_AsrcVars[3] = + * (round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 4. MinusDeltaAlpha = D_AsrcVars[4] = + * (-round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 5. OneMinusEpsilon = D_AsrcVars[5] = 1 - DeltaAlpha/2 + * 6. AlphaCurrent = 0x000020 (CMEM), initial value of Alpha parameter + * 7. BetaCurrent = 0x3fffe0 (CMEM), initial value of Beta parameter + * AlphaCurrent + BetaCurrent = 1 (=0x400000 in CMEM = 2^20 << 2) + * 8. drift_ASRC = 0 & drift_io = 0 + * 9. SMEM for ASRC_MM_EXT_IN_Coefs pointer + * 10. CMEM for ASRC_MM_EXT_IN_Coefs pointer + * ASRC_MM_EXT_IN_Coefs = C_CoefASRC16_MM_ADDR/C_CoefASRC16_MM_sizeof/0/1/ + * C_CoefASRC15_MM_ADDR/C_CoefASRC15_MM_sizeof/0/1 + * 11. SMEM for XinASRC_MM_EXT_IN pointer + * 12. CMEM for XinASRC_MM_EXT_IN pointer + * XinASRC_MM_EXT_IN = S_XinASRC_MM_EXT_IN_ADDR/S_XinASRC_MM_EXT_IN_sizeof/0/1/ + * 0/0/0/0 + * 13. SMEM for IO_MM_EXT_IN_ASRC pointer + * 14. CMEM for IO_MM_EXT_IN_ASRC pointer + * IO_MM_EXT_IN_ASRC = S_XinASRC_MM_EXT_IN_ADDR/S_XinASRC_MM_EXT_IN_sizeof/ + * ASRC_MM_EXT_IN_FIR_L+ASRC_margin+ASRC_N_48k/1/0/0/0/0 + */ +void abe_init_asrc_mm_ext_in(s32 dppm) +{ + s32 el[45]; + s32 temp0, temp1, adppm, dtemp, mem_tag, mem_addr; + u32 i = 0; + u32 n_fifo_el = 42; + temp0 = 0; + temp1 = 1; + /* 1. DriftSign = D_AsrcVars[1] = 1 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (1 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm >= 0) { + el[i + 1] = 1; + adppm = dppm; + } else { + el[i + 1] = -1; + adppm = (-1 * dppm); + } + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + dtemp = (adppm << 4) + adppm - ((adppm * 3481L) / 15625L); + /* 2. Subblock = D_AsrcVars[2] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (2 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 3. DeltaAlpha = D_AsrcVars[3] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (3 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = dtemp << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 4. MinusDeltaAlpha = D_AsrcVars[4] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (4 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = (-dtemp) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 5. OneMinusEpsilon = D_AsrcVars[5] = 0x00400000 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR + (5 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0x00400000; + else + el[i + 1] = (0x00100000 - (dtemp / 2)) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 6. AlphaCurrent = 0x000020 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_ALPHACURRENT_MM_EXT_IN_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x00000020; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 7. BetaCurrent = 0x3fffe0 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_BETACURRENT_MM_EXT_IN_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x003fffe0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 8. drift_ASRC = 0 & drift_io = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_MM_EXT_IN_PORT * sizeof(struct ABE_SIODescriptor)) + + drift_asrc_; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 9. SMEM for ASRC_MM_EXT_IN_Coefs pointer */ + /* ASRC_MM_EXT_IN_Coefs = C_CoefASRC16_MM_ADDR/C_CoefASRC16_MM_sizeof/ + 0/1/C_CoefASRC15_MM_ADDR/C_CoefASRC15_MM_sizeof/0/1 */ + mem_tag = ABE_SMEM; + mem_addr = ASRC_MM_EXT_IN_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + if (dppm == 0) { + el[i + 1] = OMAP_ABE_C_COEFASRC16_MM_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC16_MM_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC15_MM_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC15_MM_SIZE >> 2); + } else { + el[i + 1] = OMAP_ABE_C_COEFASRC1_MM_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC1_MM_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC2_MM_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC2_MM_SIZE >> 2); + } + i = i + 3; + /*10. CMEM for ASRC_MM_EXT_IN_Coefs pointer */ + /* ASRC_MM_EXT_IN_Coefs = C_CoefASRC16_MM_ADDR/C_CoefASRC16_MM_sizeof/ + 0/1/C_CoefASRC15_MM_ADDR/C_CoefASRC15_MM_sizeof/0/1 */ + mem_tag = ABE_CMEM; + mem_addr = ASRC_MM_EXT_IN_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp1; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 11. SMEM for XinASRC_MM_EXT_IN pointer */ + /* XinASRC_MM_EXT_IN = S_XinASRC_MM_EXT_IN_ADDR/ + S_XinASRC_MM_EXT_IN_sizeof/0/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = XinASRC_MM_EXT_IN_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_MM_EXT_IN_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_MM_EXT_IN_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 12. CMEM for XinASRC_MM_EXT_IN pointer */ + /* XinASRC_MM_EXT_IN = S_XinASRC_MM_EXT_IN_ADDR/ + S_XinASRC_MM_EXT_IN_sizeof/0/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = XinASRC_MM_EXT_IN_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 13. SMEM for IO_MM_EXT_IN_ASRC pointer */ + /* IO_MM_EXT_IN_ASRC = + S_XinASRC_MM_EXT_IN_ADDR/S_XinASRC_MM_EXT_IN_sizeof/ + ASRC_MM_EXT_IN_FIR_L+ASRC_margin+ASRC_N_48k/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = IO_MM_EXT_IN_ASRC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_MM_EXT_IN_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_MM_EXT_IN_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 14. CMEM for IO_MM_EXT_IN_ASRC pointer */ + /* IO_MM_EXT_IN_ASRC = + S_XinASRC_MM_EXT_IN_ADDR/S_XinASRC_MM_EXT_IN_sizeof/ + ASRC_MM_EXT_IN_FIR_L+ASRC_margin+ASRC_N_48k/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = IO_MM_EXT_IN_ASRC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_MM_EXT_IN_FIR_L + ASRC_margin + ASRC_N_48k) << 16) + + (temp1 << 12) + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + abe_write_fifo(ABE_DMEM, OMAP_ABE_D_FWMEMINITDESCR_ADDR, (u32 *) &el[0], + n_fifo_el); +} +/** + * abe_init_asrc_bt_ul + * + * Initialize the following ASRC BT_UL parameters : + * 1. DriftSign = D_AsrcVars[1] = 1 or -1 + * 2. Subblock = D_AsrcVars[2] = 0 + * 3. DeltaAlpha = D_AsrcVars[3] = + * (round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 4. MinusDeltaAlpha = D_AsrcVars[4] = + * (-round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 5. OneMinusEpsilon = D_AsrcVars[5] = 1 - DeltaAlpha/2 + * 6. AlphaCurrent = 0x000020 (CMEM), initial value of Alpha parameter + * 7. BetaCurrent = 0x3fffe0 (CMEM), initial value of Beta parameter + * AlphaCurrent + BetaCurrent = 1 (=0x400000 in CMEM = 2^20 << 2) + * 8. drift_ASRC = 0 & drift_io = 0 + * 9. SMEM for ASRC_BT_UL_Coefs pointer + * 10. CMEM for ASRC_BT_UL_Coefs pointer + * ASRC_BT_UL_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + * C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 + * 11. SMEM for XinASRC_BT_UL pointer + * 12. CMEM for XinASRC_BT_UL pointer + * XinASRC_BT_UL = S_XinASRC_BT_UL_ADDR/S_XinASRC_BT_UL_sizeof/0/1/0/0/0/0 + * 13. SMEM for IO_BT_UL_ASRC pointer + * 14. CMEM for IO_BT_UL_ASRC pointer + * IO_BT_UL_ASRC = S_XinASRC_BT_UL_ADDR/S_XinASRC_BT_UL_sizeof/ + * ASRC_BT_UL_FIR_L+ASRC_margin/1/0/0/0/0 + */ +void abe_init_asrc_bt_ul(s32 dppm) +{ + s32 el[45]; + s32 temp0, temp1, adppm, dtemp, mem_tag, mem_addr; + u32 i = 0; + u32 n_fifo_el = 42; + temp0 = 0; + temp1 = 1; + /* 1. DriftSign = D_AsrcVars[1] = 1 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (1 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm >= 0) { + el[i + 1] = 1; + adppm = dppm; + } else { + el[i + 1] = -1; + adppm = (-1 * dppm); + } + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + dtemp = (adppm << 4) + adppm - ((adppm * 3481L) / 15625L); + /* 2. Subblock = D_AsrcVars[2] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (2 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 3. DeltaAlpha = D_AsrcVars[3] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (3 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = dtemp << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 4. MinusDeltaAlpha = D_AsrcVars[4] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (4 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = (-dtemp) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /*5. OneMinusEpsilon = D_AsrcVars[5] = 0x00400000 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_UL_ADDR + (5 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0x00400000; + else + el[i + 1] = (0x00100000 - (dtemp / 2)) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 6. AlphaCurrent = 0x000020 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_ALPHACURRENT_BT_UL_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x00000020; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 7. BetaCurrent = 0x3fffe0 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_BETACURRENT_BT_UL_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x003fffe0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 8. drift_ASRC = 0 & drift_io = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_BT_VX_UL_PORT * sizeof(struct ABE_SIODescriptor)) + + drift_asrc_; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 9. SMEM for ASRC_BT_UL_Coefs pointer */ + /* ASRC_BT_UL_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_SMEM; + mem_addr = ASRC_BT_UL_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + if (dppm == 0) { + el[i + 1] = OMAP_ABE_C_COEFASRC16_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC16_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC15_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC15_VX_SIZE >> 2); + } else { + el[i + 1] = OMAP_ABE_C_COEFASRC1_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC1_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC2_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC2_VX_SIZE >> 2); + } + i = i + 3; + /* 10. CMEM for ASRC_BT_UL_Coefs pointer */ + /* ASRC_BT_UL_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_CMEM; + mem_addr = ASRC_BT_UL_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp1; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 11. SMEM for XinASRC_BT_UL pointer */ + /* XinASRC_BT_UL = S_XinASRC_BT_UL_ADDR/S_XinASRC_BT_UL_sizeof/0/1/ + 0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = XinASRC_BT_UL_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_BT_UL_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_BT_UL_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 12. CMEM for XinASRC_BT_UL pointer */ + /* XinASRC_BT_UL = S_XinASRC_BT_UL_ADDR/S_XinASRC_BT_UL_sizeof/0/1/ + 0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = XinASRC_BT_UL_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 13. SMEM for IO_BT_UL_ASRC pointer */ + /* IO_BT_UL_ASRC = S_XinASRC_BT_UL_ADDR/S_XinASRC_BT_UL_sizeof/ + ASRC_BT_UL_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = IO_BT_UL_ASRC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_BT_UL_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_BT_UL_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 14. CMEM for IO_BT_UL_ASRC pointer */ + /* IO_BT_UL_ASRC = S_XinASRC_BT_UL_ADDR/S_XinASRC_BT_UL_sizeof/ + ASRC_BT_UL_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = IO_BT_UL_ASRC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_BT_UL_FIR_L + ASRC_margin) << 16) + (temp1 << 12) + + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + abe_write_fifo(ABE_DMEM, OMAP_ABE_D_FWMEMINITDESCR_ADDR, (u32 *) &el[0], + n_fifo_el); +} +/** + * abe_init_asrc_bt_dl + * + * Initialize the following ASRC BT_DL parameters : + * 1. DriftSign = D_AsrcVars[1] = 1 or -1 + * 2. Subblock = D_AsrcVars[2] = 0 + * 3. DeltaAlpha = D_AsrcVars[3] = + * (round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 4. MinusDeltaAlpha = D_AsrcVars[4] = + * (-round(nb_phases * drift[ppm] * 10^-6 * 2^20)) << 2 + * 5. OneMinusEpsilon = D_AsrcVars[5] = 1 - DeltaAlpha/2 + * 6. AlphaCurrent = 0x000020 (CMEM), initial value of Alpha parameter + * 7. BetaCurrent = 0x3fffe0 (CMEM), initial value of Beta parameter + * AlphaCurrent + BetaCurrent = 1 (=0x400000 in CMEM = 2^20 << 2) + * 8. drift_ASRC = 0 & drift_io = 0 + * 9. SMEM for ASRC_BT_DL_Coefs pointer + * 10. CMEM for ASRC_BT_DL_Coefs pointer + * ASRC_BT_DL_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + * C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 + * 11. SMEM for XinASRC_BT_DL pointer + * 12. CMEM for XinASRC_BT_DL pointer + * XinASRC_BT_DL = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/0/1/0/0/0/0 + * 13. SMEM for DL_48_8_DEC pointer + * 14. CMEM for DL_48_8_DEC pointer + * DL_48_8_DEC = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/ + * ASRC_BT_DL_FIR_L+ASRC_margin/1/0/0/0/0 + * 15. SMEM for DL_48_16_DEC pointer + * 16. CMEM for DL_48_16_DEC pointer + * DL_48_16_DEC = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/ + * ASRC_BT_DL_FIR_L+ASRC_margin/1/0/0/0/0 + */ +void abe_init_asrc_bt_dl(s32 dppm) +{ + s32 el[51]; + s32 temp0, temp1, adppm, dtemp, mem_tag, mem_addr; + u32 i = 0; + u32 n_fifo_el = 48; + temp0 = 0; + temp1 = 1; + /* 1. DriftSign = D_AsrcVars[1] = 1 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (1 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm >= 0) { + el[i + 1] = 1; + adppm = dppm; + } else { + el[i + 1] = -1; + adppm = (-1 * dppm); + } + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + dtemp = (adppm << 4) + adppm - ((adppm * 3481L) / 15625L); + /* 2. Subblock = D_AsrcVars[2] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (2 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 3. DeltaAlpha = D_AsrcVars[3] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (3 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = dtemp << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 4. MinusDeltaAlpha = D_AsrcVars[4] = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (4 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0; + else + el[i + 1] = (-dtemp) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 5. OneMinusEpsilon = D_AsrcVars[5] = 0x00400000 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_ASRCVARS_BT_DL_ADDR + (5 * sizeof(s32)); + el[i] = (mem_tag << 16) + mem_addr; + if (dppm == 0) + el[i + 1] = 0x00400000; + else + el[i + 1] = (0x00100000 - (dtemp / 2)) << 2; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 6. AlphaCurrent = 0x000020 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_ALPHACURRENT_BT_DL_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x00000020; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 7. BetaCurrent = 0x3fffe0 (CMEM) */ + mem_tag = ABE_CMEM; + mem_addr = OMAP_ABE_C_BETACURRENT_BT_DL_ADDR; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = 0x003fffe0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 8. drift_ASRC = 0 & drift_io = 0 */ + mem_tag = ABE_DMEM; + mem_addr = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_BT_VX_DL_PORT * sizeof(struct ABE_SIODescriptor)) + + drift_asrc_; + el[i] = (mem_tag << 16) + mem_addr; + el[i + 1] = temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 9. SMEM for ASRC_BT_DL_Coefs pointer */ + /* ASRC_BT_DL_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_SMEM; + mem_addr = ASRC_BT_DL_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + if (dppm == 0) { + el[i + 1] = OMAP_ABE_C_COEFASRC16_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC16_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC15_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC15_VX_SIZE >> 2); + } else { + el[i + 1] = OMAP_ABE_C_COEFASRC1_VX_ADDR >> 2; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_C_COEFASRC1_VX_SIZE >> 2); + el[i + 2] = OMAP_ABE_C_COEFASRC2_VX_ADDR >> 2; + el[i + 2] = (el[i + 2] << 8) + (OMAP_ABE_C_COEFASRC2_VX_SIZE >> 2); + } + i = i + 3; + /* 10. CMEM for ASRC_BT_DL_Coefs pointer */ + /* ASRC_BT_DL_Coefs = C_CoefASRC16_VX_ADDR/C_CoefASRC16_VX_sizeof/0/1/ + C_CoefASRC15_VX_ADDR/C_CoefASRC15_VX_sizeof/0/1 */ + mem_tag = ABE_CMEM; + mem_addr = ASRC_BT_DL_Coefs_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp1; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 11. SMEM for XinASRC_BT_DL pointer */ + /* XinASRC_BT_DL = + S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/0/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = XinASRC_BT_DL_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_BT_DL_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_BT_DL_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 12. CMEM for XinASRC_BT_DL pointer */ + /* XinASRC_BT_DL = + S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/0/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = XinASRC_BT_DL_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = (temp0 << 16) + (temp1 << 12) + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 13. SMEM for DL_48_8_DEC pointer */ + /* DL_48_8_DEC = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/ + ASRC_BT_DL_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = DL_48_8_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_BT_DL_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_BT_DL_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 14. CMEM for DL_48_8_DEC pointer */ + /* DL_48_8_DEC = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/ + ASRC_BT_DL_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = DL_48_8_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_BT_DL_FIR_L + ASRC_margin) << 16) + (temp1 << 12) + + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + i = i + 3; + /* 15. SMEM for DL_48_16_DEC pointer */ + /* DL_48_16_DEC = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/ + ASRC_BT_DL_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_SMEM; + mem_addr = DL_48_16_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + el[i + 1] = OMAP_ABE_S_XINASRC_BT_DL_ADDR >> 3; + el[i + 1] = (el[i + 1] << 8) + (OMAP_ABE_S_XINASRC_BT_DL_SIZE >> 3); + el[i + 2] = temp0; + i = i + 3; + /* 16. CMEM for DL_48_16_DEC pointer */ + /* DL_48_16_DEC = S_XinASRC_BT_DL_ADDR/S_XinASRC_BT_DL_sizeof/ + ASRC_BT_DL_FIR_L+ASRC_margin/1/0/0/0/0 */ + mem_tag = ABE_CMEM; + mem_addr = DL_48_16_DEC_labelID; + el[i] = (mem_tag << 16) + (mem_addr << 2); + /* el[i+1] = iam1<<16 + inc1<<12 + iam2<<4 + inc2 */ + el[i + 1] = ((ASRC_BT_DL_FIR_L + ASRC_margin) << 16) + (temp1 << 12) + + (temp0 << 4) + temp0; + /* dummy field */ + el[i + 2] = temp0; + abe_write_fifo(ABE_DMEM, OMAP_ABE_D_FWMEMINITDESCR_ADDR, (u32 *) &el[0], + n_fifo_el); +} diff --git a/sound/soc/omap/abe/abe_cm_addr.h b/sound/soc/omap/abe/abe_cm_addr.h new file mode 100644 index 0000000000000000000000000000000000000000..e5c97f341e3396be050dadbeb54706a8d749ac28 --- /dev/null +++ b/sound/soc/omap/abe/abe_cm_addr.h @@ -0,0 +1,217 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated 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. + * + */ +#define OMAP_ABE_INIT_CM_ADDR 0x0 +#define OMAP_ABE_INIT_CM_SIZE 0x640 +#define OMAP_ABE_C_DATA_LSB_2_ADDR 0x640 +#define OMAP_ABE_C_DATA_LSB_2_SIZE 0x4 +#define OMAP_ABE_C_1_ALPHA_ADDR 0x644 +#define OMAP_ABE_C_1_ALPHA_SIZE 0x48 +#define OMAP_ABE_C_ALPHA_ADDR 0x68C +#define OMAP_ABE_C_ALPHA_SIZE 0x48 +#define OMAP_ABE_C_GAINSWRAMP_ADDR 0x6D4 +#define OMAP_ABE_C_GAINSWRAMP_SIZE 0x38 +#define OMAP_ABE_C_GAINS_DL1M_ADDR 0x70C +#define OMAP_ABE_C_GAINS_DL1M_SIZE 0x10 +#define OMAP_ABE_C_GAINS_DL2M_ADDR 0x71C +#define OMAP_ABE_C_GAINS_DL2M_SIZE 0x10 +#define OMAP_ABE_C_GAINS_ECHOM_ADDR 0x72C +#define OMAP_ABE_C_GAINS_ECHOM_SIZE 0x8 +#define OMAP_ABE_C_GAINS_SDTM_ADDR 0x734 +#define OMAP_ABE_C_GAINS_SDTM_SIZE 0x8 +#define OMAP_ABE_C_GAINS_VXRECM_ADDR 0x73C +#define OMAP_ABE_C_GAINS_VXRECM_SIZE 0x10 +#define OMAP_ABE_C_GAINS_ULM_ADDR 0x74C +#define OMAP_ABE_C_GAINS_ULM_SIZE 0x10 +#define OMAP_ABE_C_GAINS_BTUL_ADDR 0x75C +#define OMAP_ABE_C_GAINS_BTUL_SIZE 0x8 +#define OMAP_ABE_C_SDT_COEFS_ADDR 0x764 +#define OMAP_ABE_C_SDT_COEFS_SIZE 0x24 +#define OMAP_ABE_C_COEFASRC1_VX_ADDR 0x788 +#define OMAP_ABE_C_COEFASRC1_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC2_VX_ADDR 0x7D4 +#define OMAP_ABE_C_COEFASRC2_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC3_VX_ADDR 0x820 +#define OMAP_ABE_C_COEFASRC3_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC4_VX_ADDR 0x86C +#define OMAP_ABE_C_COEFASRC4_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC5_VX_ADDR 0x8B8 +#define OMAP_ABE_C_COEFASRC5_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC6_VX_ADDR 0x904 +#define OMAP_ABE_C_COEFASRC6_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC7_VX_ADDR 0x950 +#define OMAP_ABE_C_COEFASRC7_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC8_VX_ADDR 0x99C +#define OMAP_ABE_C_COEFASRC8_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC9_VX_ADDR 0x9E8 +#define OMAP_ABE_C_COEFASRC9_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC10_VX_ADDR 0xA34 +#define OMAP_ABE_C_COEFASRC10_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC11_VX_ADDR 0xA80 +#define OMAP_ABE_C_COEFASRC11_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC12_VX_ADDR 0xACC +#define OMAP_ABE_C_COEFASRC12_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC13_VX_ADDR 0xB18 +#define OMAP_ABE_C_COEFASRC13_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC14_VX_ADDR 0xB64 +#define OMAP_ABE_C_COEFASRC14_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC15_VX_ADDR 0xBB0 +#define OMAP_ABE_C_COEFASRC15_VX_SIZE 0x4C +#define OMAP_ABE_C_COEFASRC16_VX_ADDR 0xBFC +#define OMAP_ABE_C_COEFASRC16_VX_SIZE 0x4C +#define OMAP_ABE_C_ALPHACURRENT_UL_VX_ADDR 0xC48 +#define OMAP_ABE_C_ALPHACURRENT_UL_VX_SIZE 0x4 +#define OMAP_ABE_C_BETACURRENT_UL_VX_ADDR 0xC4C +#define OMAP_ABE_C_BETACURRENT_UL_VX_SIZE 0x4 +#define OMAP_ABE_C_ALPHACURRENT_DL_VX_ADDR 0xC50 +#define OMAP_ABE_C_ALPHACURRENT_DL_VX_SIZE 0x4 +#define OMAP_ABE_C_BETACURRENT_DL_VX_ADDR 0xC54 +#define OMAP_ABE_C_BETACURRENT_DL_VX_SIZE 0x4 +#define OMAP_ABE_C_COEFASRC1_MM_ADDR 0xC58 +#define OMAP_ABE_C_COEFASRC1_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC2_MM_ADDR 0xCA0 +#define OMAP_ABE_C_COEFASRC2_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC3_MM_ADDR 0xCE8 +#define OMAP_ABE_C_COEFASRC3_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC4_MM_ADDR 0xD30 +#define OMAP_ABE_C_COEFASRC4_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC5_MM_ADDR 0xD78 +#define OMAP_ABE_C_COEFASRC5_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC6_MM_ADDR 0xDC0 +#define OMAP_ABE_C_COEFASRC6_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC7_MM_ADDR 0xE08 +#define OMAP_ABE_C_COEFASRC7_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC8_MM_ADDR 0xE50 +#define OMAP_ABE_C_COEFASRC8_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC9_MM_ADDR 0xE98 +#define OMAP_ABE_C_COEFASRC9_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC10_MM_ADDR 0xEE0 +#define OMAP_ABE_C_COEFASRC10_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC11_MM_ADDR 0xF28 +#define OMAP_ABE_C_COEFASRC11_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC12_MM_ADDR 0xF70 +#define OMAP_ABE_C_COEFASRC12_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC13_MM_ADDR 0xFB8 +#define OMAP_ABE_C_COEFASRC13_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC14_MM_ADDR 0x1000 +#define OMAP_ABE_C_COEFASRC14_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC15_MM_ADDR 0x1048 +#define OMAP_ABE_C_COEFASRC15_MM_SIZE 0x48 +#define OMAP_ABE_C_COEFASRC16_MM_ADDR 0x1090 +#define OMAP_ABE_C_COEFASRC16_MM_SIZE 0x48 +#define OMAP_ABE_C_ALPHACURRENT_MM_EXT_IN_ADDR 0x10D8 +#define OMAP_ABE_C_ALPHACURRENT_MM_EXT_IN_SIZE 0x4 +#define OMAP_ABE_C_BETACURRENT_MM_EXT_IN_ADDR 0x10DC +#define OMAP_ABE_C_BETACURRENT_MM_EXT_IN_SIZE 0x4 +#define OMAP_ABE_C_DL2_L_COEFS_ADDR 0x10E0 +#define OMAP_ABE_C_DL2_L_COEFS_SIZE 0x64 +#define OMAP_ABE_C_DL2_R_COEFS_ADDR 0x1144 +#define OMAP_ABE_C_DL2_R_COEFS_SIZE 0x64 +#define OMAP_ABE_C_DL1_COEFS_ADDR 0x11A8 +#define OMAP_ABE_C_DL1_COEFS_SIZE 0x64 +#define OMAP_ABE_C_SRC_3_LP_COEFS_ADDR 0x120C +#define OMAP_ABE_C_SRC_3_LP_COEFS_SIZE 0x34 +#define OMAP_ABE_C_SRC_3_LP_GAIN_COEFS_ADDR 0x1240 +#define OMAP_ABE_C_SRC_3_LP_GAIN_COEFS_SIZE 0x34 +#define OMAP_ABE_C_SRC_3_HP_COEFS_ADDR 0x1274 +#define OMAP_ABE_C_SRC_3_HP_COEFS_SIZE 0x14 +#define OMAP_ABE_C_SRC_6_LP_COEFS_ADDR 0x1288 +#define OMAP_ABE_C_SRC_6_LP_COEFS_SIZE 0x34 +#define OMAP_ABE_C_SRC_6_LP_GAIN_COEFS_ADDR 0x12BC +#define OMAP_ABE_C_SRC_6_LP_GAIN_COEFS_SIZE 0x34 +#define OMAP_ABE_C_SRC_6_HP_COEFS_ADDR 0x12F0 +#define OMAP_ABE_C_SRC_6_HP_COEFS_SIZE 0x1C +#define OMAP_ABE_C_ALPHACURRENT_ECHO_REF_ADDR 0x130C +#define OMAP_ABE_C_ALPHACURRENT_ECHO_REF_SIZE 0x4 +#define OMAP_ABE_C_BETACURRENT_ECHO_REF_ADDR 0x1310 +#define OMAP_ABE_C_BETACURRENT_ECHO_REF_SIZE 0x4 +#define OMAP_ABE_C_VIBRA2_CONSTS_ADDR 0x1314 +#define OMAP_ABE_C_VIBRA2_CONSTS_SIZE 0x10 +#define OMAP_ABE_C_VIBRA1_COEFFS_ADDR 0x1324 +#define OMAP_ABE_C_VIBRA1_COEFFS_SIZE 0x2C +#define OMAP_ABE_C_48_96_LP_COEFS_ADDR 0x1350 +#define OMAP_ABE_C_48_96_LP_COEFS_SIZE 0x3C +#define OMAP_ABE_C_96_48_AMIC_COEFS_ADDR 0x138C +#define OMAP_ABE_C_96_48_AMIC_COEFS_SIZE 0x4C +#define OMAP_ABE_C_96_48_DMIC_COEFS_ADDR 0x13D8 +#define OMAP_ABE_C_96_48_DMIC_COEFS_SIZE 0x4C +#define OMAP_ABE_C_INPUT_SCALE_ADDR 0x1424 +#define OMAP_ABE_C_INPUT_SCALE_SIZE 0x4 +#define OMAP_ABE_C_OUTPUT_SCALE_ADDR 0x1428 +#define OMAP_ABE_C_OUTPUT_SCALE_SIZE 0x4 +#define OMAP_ABE_C_MUTE_SCALING_ADDR 0x142C +#define OMAP_ABE_C_MUTE_SCALING_SIZE 0x4 +#define OMAP_ABE_C_GAINS_0DB_ADDR 0x1430 +#define OMAP_ABE_C_GAINS_0DB_SIZE 0x8 +#define OMAP_ABE_C_ALPHACURRENT_BT_DL_ADDR 0x1438 +#define OMAP_ABE_C_ALPHACURRENT_BT_DL_SIZE 0x4 +#define OMAP_ABE_C_BETACURRENT_BT_DL_ADDR 0x143C +#define OMAP_ABE_C_BETACURRENT_BT_DL_SIZE 0x4 +#define OMAP_ABE_C_ALPHACURRENT_BT_UL_ADDR 0x1440 +#define OMAP_ABE_C_ALPHACURRENT_BT_UL_SIZE 0x4 +#define OMAP_ABE_C_BETACURRENT_BT_UL_ADDR 0x1444 +#define OMAP_ABE_C_BETACURRENT_BT_UL_SIZE 0x4 +#define OMAP_ABE_C_SRC_FIR6_LP_GAIN_COEFS_ADDR 0x1448 +#define OMAP_ABE_C_SRC_FIR6_LP_GAIN_COEFS_SIZE 0x2A0 +#define OMAP_ABE_C_SRC_44P1_COEFS_ADDR 0x16E8 +#define OMAP_ABE_C_SRC_44P1_COEFS_SIZE 0x480 +#define OMAP_ABE_C_SRC_MM_DL_44P1_STEP_ADDR 0x1B68 +#define OMAP_ABE_C_SRC_MM_DL_44P1_STEP_SIZE 0x8 +#define OMAP_ABE_C_SRC_TONES_44P1_STEP_ADDR 0x1B70 +#define OMAP_ABE_C_SRC_TONES_44P1_STEP_SIZE 0x8 +#define OMAP_ABE_C_SRC_44P1_MULFAC2_ADDR 0x1B78 +#define OMAP_ABE_C_SRC_44P1_MULFAC2_SIZE 0x8 diff --git a/sound/soc/omap/abe/abe_core.c b/sound/soc/omap/abe/abe_core.c new file mode 100644 index 0000000000000000000000000000000000000000..cdfbcea4cc8677001489ba1815455e308a7e1a22 --- /dev/null +++ b/sound/soc/omap/abe/abe_core.c @@ -0,0 +1,607 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_dbg.h" +#include "abe.h" +#include "abe_gain.h" +#include "abe_aess.h" +#include "abe_port.h" +#include "abe_mem.h" +#include "abe_taskid.h" + +#define OMAP_ABE_IRQ_FIFO_MASK ((OMAP_ABE_D_MCUIRQFIFO_SIZE >> 2) - 1) + +void abe_init_asrc_vx_dl(s32 dppm); +void abe_init_asrc_vx_ul(s32 dppm); +void abe_init_asrc_mm_ext_in(s32 dppm); +void abe_init_asrc_bt_ul(s32 dppm); +void abe_init_asrc_bt_dl(s32 dppm); + +void abe_irq_aps(u32 aps_info); +void abe_irq_ping_pong(void); +void abe_irq_check_for_sequences(u32 seq_info); +extern u32 abe_size_pingpong; +extern u32 abe_base_address_pingpong[]; + +void abe_add_subroutine(u32 *id, abe_subroutine2 f, u32 nparam, u32 *params); + + +/** + * abe_omap_abe_reset_hal - reset the ABE/HAL + * @abe: Pointer on abe handle + * + * Operations : reset the ABE by reloading the static variables and + * default AESS registers. + * Called after a PRCM cold-start reset of ABE + */ +int omap_abe_reset_hal(struct omap_abe *abe) +{ + u32 i; + + omap_abe_dbg_reset(&abe->dbg); + + _log(ABE_ID_RESET_HAL, 0, 0, 0); + + /* IRQ & DBG circular read pointer in DMEM */ + abe->irq_dbg_read_ptr = 0; + + /* default = disable the mixer's adaptive gain control */ + omap_abe_use_compensated_gain(abe, 0); + + /* reset the default gain values */ + for (i = 0; i < MAX_NBGAIN_CMEM; i++) { + abe->muted_gains_indicator[i] = 0; + abe->desired_gains_decibel[i] = (u32) GAIN_MUTE; + abe->desired_gains_linear[i] = 0; + abe->desired_ramp_delay_ms[i] = 0; + abe->muted_gains_decibel[i] = (u32) GAIN_TOOLOW; + } + omap_abe_hw_configuration(abe); + return 0; +} +EXPORT_SYMBOL(omap_abe_reset_hal); + +/** + * omap_abe_wakeup - Wakeup ABE + * @abe: Pointer on abe handle + * + * Wakeup ABE in case of retention + */ +int omap_abe_wakeup(struct omap_abe *abe) +{ + /* Restart event generator */ + omap_abe_write_event_generator(abe, EVENT_TIMER); + + /* reconfigure DMA Req and MCU Irq visibility */ + omap_abe_hw_configuration(abe); + return 0; +} +EXPORT_SYMBOL(omap_abe_wakeup); + +/** + * abe_monitoring + * + * checks the internal status of ABE and HAL + */ +void abe_monitoring(void) +{ +} + +/** + * omap_abe_irq_processing - Process ABE interrupt + * @abe: Pointer on abe handle + * + * This subroutine is call upon reception of "MA_IRQ_99 ABE_MPU_IRQ" Audio + * back-end interrupt. This subroutine will check the ATC Hrdware, the + * IRQ_FIFO from the AE and act accordingly. Some IRQ source are originated + * for the delivery of "end of time sequenced tasks" notifications, some are + * originated from the Ping-Pong protocols, some are generated from + * the embedded debugger when the firmware stops on programmable break-points, + * etc ... + */ +int omap_abe_irq_processing(struct omap_abe *abe) +{ + u32 abe_irq_dbg_write_ptr, i, cmem_src, sm_cm; + abe_irq_data_t IRQ_data; + + _log(ABE_ID_IRQ_PROCESSING, 0, 0, 0); + + /* extract the write pointer index from CMEM memory (INITPTR format) */ + /* CMEM address of the write pointer in bytes */ + cmem_src = MCU_IRQ_FIFO_ptr_labelID << 2; + omap_abe_mem_read(abe, OMAP_ABE_CMEM, cmem_src, + &sm_cm, sizeof(abe_irq_dbg_write_ptr)); + /* AESS left-pointer index located on MSBs */ + abe_irq_dbg_write_ptr = sm_cm >> 16; + abe_irq_dbg_write_ptr &= 0xFF; + /* loop on the IRQ FIFO content */ + for (i = 0; i < OMAP_ABE_D_MCUIRQFIFO_SIZE; i++) { + /* stop when the FIFO is empty */ + if (abe_irq_dbg_write_ptr == abe->irq_dbg_read_ptr) + break; + /* read the IRQ/DBG FIFO */ + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + (OMAP_ABE_D_MCUIRQFIFO_ADDR + + (abe->irq_dbg_read_ptr << 2)), + (u32 *) &IRQ_data, sizeof(IRQ_data)); + abe->irq_dbg_read_ptr = (abe->irq_dbg_read_ptr + 1) & OMAP_ABE_IRQ_FIFO_MASK; + /* select the source of the interrupt */ + switch (IRQ_data.tag) { + case IRQtag_APS: + _log(ABE_ID_IRQ_PROCESSING, IRQ_data.data, 0, 1); + abe_irq_aps(IRQ_data.data); + break; + case IRQtag_PP: + _log(ABE_ID_IRQ_PROCESSING, 0, 0, 2); + abe_irq_ping_pong(); + break; + case IRQtag_COUNT: + _log(ABE_ID_IRQ_PROCESSING, IRQ_data.data, 0, 3); + abe_irq_check_for_sequences(IRQ_data.data); + break; + default: + break; + } + } + abe_monitoring(); + return 0; +} +EXPORT_SYMBOL(omap_abe_irq_processing); + +/** + * oamp_abe_set_ping_pong_buffer + * @abe: Pointer on abe handle + * @port: ABE port ID + * @n_bytes: Size of Ping/Pong buffer + * + * Updates the next ping-pong buffer with "size" bytes copied from the + * host processor. This API notifies the FW that the data transfer is done. + */ +int omap_abe_set_ping_pong_buffer(struct omap_abe *abe, u32 port, u32 n_bytes) +{ + u32 sio_pp_desc_address, struct_offset, n_samples, datasize, + base_and_size, *src; + struct ABE_SPingPongDescriptor desc_pp; + + _log(ABE_ID_SET_PING_PONG_BUFFER, port, n_bytes, n_bytes >> 8); + + /* ping_pong is only supported on MM_DL */ + if (port != OMAP_ABE_MM_DL_PORT) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } + /* translates the number of bytes in samples */ + /* data size in DMEM words */ + datasize = omap_abe_dma_port_iter_factor((struct omap_abe_data_format *)&((abe_port[port]).format)); + /* data size in bytes */ + datasize = datasize << 2; + n_samples = n_bytes / datasize; + omap_abe_mem_read(abe, OMAP_ABE_DMEM, OMAP_ABE_D_PINGPONGDESC_ADDR, + (u32 *) &desc_pp, sizeof(desc_pp)); + /* + * read the port SIO descriptor and extract the current pointer + * address after reading the counter + */ + if ((desc_pp.counter & 0x1) == 0) { + struct_offset = (u32) &(desc_pp.nextbuff0_BaseAddr) - + (u32) &(desc_pp); + base_and_size = desc_pp.nextbuff0_BaseAddr; + } else { + struct_offset = (u32) &(desc_pp.nextbuff1_BaseAddr) - + (u32) &(desc_pp); + base_and_size = desc_pp.nextbuff1_BaseAddr; + } + base_and_size = (base_and_size & 0xFFFFL) + (n_samples << 16); + sio_pp_desc_address = OMAP_ABE_D_PINGPONGDESC_ADDR + struct_offset; + src = &base_and_size; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, sio_pp_desc_address, + (u32 *) &base_and_size, sizeof(u32)); + + return 0; +} +EXPORT_SYMBOL(omap_abe_set_ping_pong_buffer); + +/** + * omap_abe_read_next_ping_pong_buffer + * @abe: Pointer on abe handle + * @port: ABE portID + * @p: Next buffer address (pointer) + * @n: Next buffer size (pointer) + * + * Tell the next base address of the next ping_pong Buffer and its size + */ +int omap_abe_read_next_ping_pong_buffer(struct omap_abe *abe, u32 port, u32 *p, u32 *n) +{ + u32 sio_pp_desc_address; + struct ABE_SPingPongDescriptor desc_pp; + + _log(ABE_ID_READ_NEXT_PING_PONG_BUFFER, port, 0, 0); + + /* ping_pong is only supported on MM_DL */ + if (port != OMAP_ABE_MM_DL_PORT) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } + /* read the port SIO descriptor and extract the current pointer + address after reading the counter */ + sio_pp_desc_address = OMAP_ABE_D_PINGPONGDESC_ADDR; + omap_abe_mem_read(abe, OMAP_ABE_DMEM, sio_pp_desc_address, + (u32 *) &desc_pp, sizeof(struct ABE_SPingPongDescriptor)); + if ((desc_pp.counter & 0x1) == 0) { + _log(ABE_ID_READ_NEXT_PING_PONG_BUFFER, port, 0, 0); + *p = desc_pp.nextbuff0_BaseAddr; + } else { + _log(ABE_ID_READ_NEXT_PING_PONG_BUFFER, port, 1, 0); + *p = desc_pp.nextbuff1_BaseAddr; + } + /* translates the number of samples in bytes */ + *n = abe_size_pingpong; + + return 0; +} +EXPORT_SYMBOL(omap_abe_read_next_ping_pong_buffer); + +/** + * omap_abe_init_ping_pong_buffer + * @abe: Pointer on abe handle + * @id: ABE port ID + * @size_bytes:size of the ping pong + * @n_buffers:number of buffers (2 = ping/pong) + * @p:returned address of the ping-pong list of base addresses + * (byte offset from DMEM start) + * + * Computes the base address of the ping_pong buffers + */ +int omap_abe_init_ping_pong_buffer(struct omap_abe *abe, + u32 id, u32 size_bytes, u32 n_buffers, + u32 *p) +{ + u32 i, dmem_addr; + + _log(ABE_ID_INIT_PING_PONG_BUFFER, id, size_bytes, n_buffers); + + /* ping_pong is supported in 2 buffers configuration right now but FW + is ready for ping/pong/pung/pang... */ + if (id != OMAP_ABE_MM_DL_PORT || n_buffers > MAX_PINGPONG_BUFFERS) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } + for (i = 0; i < n_buffers; i++) { + dmem_addr = OMAP_ABE_D_PING_ADDR + (i * size_bytes); + /* base addresses of the ping pong buffers in U8 unit */ + abe_base_address_pingpong[i] = dmem_addr; + } + /* global data */ + abe_size_pingpong = size_bytes; + *p = (u32) OMAP_ABE_D_PING_ADDR; + return 0; +} +EXPORT_SYMBOL(omap_abe_init_ping_pong_buffer); + +/** + * omap_abe_read_offset_from_ping_buffer + * @abe: Pointer on abe handle + * @id: ABE port ID + * @n: returned address of the offset + * from the ping buffer start address (in samples) + * + * Computes the current firmware ping pong read pointer location, + * expressed in samples, as the offset from the start address of ping buffer. + */ +int omap_abe_read_offset_from_ping_buffer(struct omap_abe *abe, + u32 id, u32 *n) +{ + u32 sio_pp_desc_address; + struct ABE_SPingPongDescriptor desc_pp; + + /* ping_pong is only supported on MM_DL */ + if (OMAP_ABE_MM_DL_PORT != id) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } else { + /* read the port SIO ping pong descriptor */ + sio_pp_desc_address = OMAP_ABE_D_PINGPONGDESC_ADDR; + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + sio_pp_desc_address, (u32 *) &desc_pp, + sizeof(struct ABE_SPingPongDescriptor)); + /* extract the current ping pong buffer read pointer based on + the value of the counter */ + if ((desc_pp.counter & 0x1) == 0) { + /* the next is buffer0, hence the current is buffer1 */ + switch (abe_port[OMAP_ABE_MM_DL_PORT].format.samp_format) { + case MONO_MSB: + case MONO_RSHIFTED_16: + case STEREO_16_16: + *n = abe_size_pingpong / 4 + + desc_pp.nextbuff1_Samples - + desc_pp.workbuff_Samples; + break; + case STEREO_MSB: + case STEREO_RSHIFTED_16: + *n = abe_size_pingpong / 8 + + desc_pp.nextbuff1_Samples - + desc_pp.workbuff_Samples; + break; + default: + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + break; + } + } else { + /* the next is buffer1, hence the current is buffer0 */ + *n = desc_pp.nextbuff0_Samples - + desc_pp.workbuff_Samples; + } + } + + return 0; +} +EXPORT_SYMBOL(omap_abe_read_offset_from_ping_buffer); + +/** + * abe_set_router_configuration + * @Id: name of the router + * @Conf: id of the configuration + * @param: list of output index of the route + * + * The uplink router takes its input from DMIC (6 samples), AMIC (2 samples) + * and PORT1/2 (2 stereo ports). Each sample will be individually stored in + * an intermediate table of 10 elements. + * + * Example of router table parameter for voice uplink with phoenix microphones + * + * indexes 0 .. 9 = MM_UL description (digital MICs and MMEXTIN) + * DMIC1_L_labelID, DMIC1_R_labelID, DMIC2_L_labelID, DMIC2_R_labelID, + * MM_EXT_IN_L_labelID, MM_EXT_IN_R_labelID, ZERO_labelID, ZERO_labelID, + * ZERO_labelID, ZERO_labelID, + * indexes 10 .. 11 = MM_UL2 description (recording on DMIC3) + * DMIC3_L_labelID, DMIC3_R_labelID, + * indexes 12 .. 13 = VX_UL description (VXUL based on PDMUL data) + * AMIC_L_labelID, AMIC_R_labelID, + * indexes 14 .. 15 = RESERVED (NULL) + * ZERO_labelID, ZERO_labelID, + */ +int omap_abe_set_router_configuration(struct omap_abe *abe, + u32 id, u32 k, u32 *param) +{ + _log(ABE_ID_SET_ROUTER_CONFIGURATION, id, (u32) param, (u32) param >> 8); + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_AUPLINKROUTING_ADDR, + param, OMAP_ABE_D_AUPLINKROUTING_SIZE); + return 0; +} +EXPORT_SYMBOL(omap_abe_set_router_configuration); + +/** + * abe_set_opp_processing - Set OPP mode for ABE Firmware + * @opp: OOPP mode + * + * New processing network and OPP: + * 0: Ultra Lowest power consumption audio player (no post-processing, no mixer) + * 1: OPP 25% (simple multimedia features, including low-power player) + * 2: OPP 50% (multimedia and voice calls) + * 3: OPP100% ( multimedia complex use-cases) + * + * Rearranges the FW task network to the corresponding OPP list of features. + * The corresponding AE ports are supposed to be set/reset accordingly before + * this switch. + * + */ +int omap_abe_set_opp_processing(struct omap_abe *abe, u32 opp) +{ + u32 dOppMode32, sio_desc_address; + struct ABE_SIODescriptor sio_desc; + + _log(ABE_ID_SET_OPP_PROCESSING, opp, 0, 0); + + switch (opp) { + case ABE_OPP25: + /* OPP25% */ + dOppMode32 = DOPPMODE32_OPP25; + break; + case ABE_OPP50: + /* OPP50% */ + dOppMode32 = DOPPMODE32_OPP50; + break; + default: + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_BLOCK_COPY_ERR); + case ABE_OPP100: + /* OPP100% */ + dOppMode32 = DOPPMODE32_OPP100; + break; + } + /* Write Multiframe inside DMEM */ + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MAXTASKBYTESINSLOT_ADDR, &dOppMode32, sizeof(u32)); + + sio_desc_address = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_MM_EXT_IN_PORT * + sizeof(struct ABE_SIODescriptor)); + omap_abe_mem_read(abe, OMAP_ABE_DMEM, sio_desc_address, + (u32 *) &sio_desc, sizeof(sio_desc)); + if (dOppMode32 == DOPPMODE32_OPP100) { + /* ASRC input buffer, size 40 */ + sio_desc.smem_addr1 = smem_mm_ext_in_opp100; + /* Init MM_EXT_IN ASRC and enable its adaptation */ + abe_init_asrc_mm_ext_in(250); + } else + /* at OPP 50 or without ASRC */ + sio_desc.smem_addr1 = smem_mm_ext_in_opp50; + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, sio_desc_address, + (u32 *) &sio_desc, sizeof(sio_desc)); + + sio_desc_address = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_BT_VX_UL_PORT * + sizeof(struct ABE_SIODescriptor)); + omap_abe_mem_read(abe, OMAP_ABE_DMEM, sio_desc_address, + (u32 *) &sio_desc, sizeof(sio_desc)); + + if (abe_port[OMAP_ABE_BT_VX_UL_PORT].format.f == 8000) { + if (dOppMode32 == DOPPMODE32_OPP100) + /* ASRC input buffer, size 40 */ + sio_desc.smem_addr1 = smem_bt_vx_ul_opp100; + else + /* at OPP 50 without ASRC */ + sio_desc.smem_addr1 = BT_UL_8k_labelID; + } else { + if (dOppMode32 == DOPPMODE32_OPP100) + /* ASRC input buffer, size 40 */ + sio_desc.smem_addr1 = smem_bt_vx_ul_opp100; + else + /* at OPP 50 without ASRC */ + sio_desc.smem_addr1 = BT_UL_16k_labelID; + } + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, sio_desc_address, + (u32 *) &sio_desc, sizeof(sio_desc)); + + sio_desc_address = OMAP_ABE_D_IODESCR_ADDR + (OMAP_ABE_BT_VX_DL_PORT * + sizeof(struct ABE_SIODescriptor)); + omap_abe_mem_read(abe, OMAP_ABE_DMEM, sio_desc_address, + (u32 *) &sio_desc, sizeof(sio_desc)); + +#define ABE_TASK_ID(ID) (OMAP_ABE_D_TASKSLIST_ADDR + sizeof(ABE_STask)*(ID)) +#define TASK_BT_DL_48_8_SLT 14 +#define TASK_BT_DL_48_8_IDX 4 + if (abe_port[OMAP_ABE_BT_VX_DL_PORT].format.f == 8000) { + if (dOppMode32 == DOPPMODE32_OPP100) { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_8_OPP100); + sio_desc.smem_addr1 = BT_DL_8k_opp100_labelID; + } else { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_8); + sio_desc.smem_addr1 = BT_DL_8k_labelID; + } + } else { + if (dOppMode32 == DOPPMODE32_OPP100) { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_16_OPP100); + sio_desc.smem_addr1 = BT_DL_16k_opp100_labelID; + } else { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_16); + sio_desc.smem_addr1 = BT_DL_16k_labelID; + } + } + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_MULTIFRAME_ADDR, + (u32 *) abe->MultiFrame, sizeof(abe->MultiFrame)); + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, sio_desc_address, + (u32 *) &sio_desc, sizeof(sio_desc)); + + if (dOppMode32 == DOPPMODE32_OPP100) { + /* Init BT_VX_UL ASRC and enable its adaptation */ + abe_init_asrc_bt_ul(250); + /* Init BT_VX_DL ASRC and enable its adaptation */ + abe_init_asrc_bt_dl(-250); + } + return 0; + +} +EXPORT_SYMBOL(omap_abe_set_opp_processing); + +/** + * omap_abe_check_activity - Check if some ABE activity. + * + * Check if any ABE ports are running. + * return 1: still activity on ABE + * return 0: no more activity on ABE. Event generator can be stopped + * + */ +int omap_abe_check_activity(struct omap_abe *abe) +{ + int i, ret = 0; + + for (i = 0; i < (LAST_PORT_ID - 1); i++) { + if (abe_port[abe_port_priority[i]].status == + OMAP_ABE_PORT_ACTIVITY_RUNNING) + break; + } + if (i < (LAST_PORT_ID - 1)) + ret = 1; + return ret; +} +EXPORT_SYMBOL(omap_abe_check_activity); + +/** + * abe_plug_subroutine + * @id: returned sequence index after plugging a new subroutine + * @f: subroutine address to be inserted + * @n: number of parameters of this subroutine + * @params: pointer on parameters + * + * register a list of subroutines for call-back purpose + */ +abehal_status abe_plug_subroutine(u32 *id, abe_subroutine2 f, u32 n, + u32 *params) +{ + _log(ABE_ID_PLUG_SUBROUTINE, (u32) (*id), (u32) f, n); + + abe_add_subroutine(id, (abe_subroutine2) f, n, (u32 *) params); + return 0; +} +EXPORT_SYMBOL(abe_plug_subroutine); diff --git a/sound/soc/omap/abe/abe_dat.c b/sound/soc/omap/abe/abe_dat.c new file mode 100644 index 0000000000000000000000000000000000000000..62ec79121f2b71601d99a1085413010c40aedaae --- /dev/null +++ b/sound/soc/omap/abe/abe_dat.c @@ -0,0 +1,458 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include "abe_legacy.h" + +struct omap_abe *abe; + +/* + * HAL/FW ports status / format / sampling / protocol(call_back) / features + * / gain / name + */ +abe_port_t abe_port[LAST_PORT_ID]; /* list of ABE ports */ +const abe_port_t abe_port_init[LAST_PORT_ID] = { + /* Status Data Format Drift Call-Back Protocol+selector desc_addr; + buf_addr; buf_size; iter; irq_addr irq_data DMA_T $Features + reseted at start Port Name for the debug trace */ + /* DMIC */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {96000, SIX_MSB}, + NODRIFT, NOCALLBACK, 0, (DMIC_ITER/6), + { + SNK_P, DMIC_PORT_PROT, + {{dmem_dmic, dmem_dmic_size, DMIC_ITER} } + }, + {0, 0}, + {EQDMIC, 0}, "DMIC"}, + /* PDM_UL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {96000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_amic, (MCPDM_UL_ITER/2), + { + SNK_P, MCPDMUL_PORT_PROT, + {{dmem_amic, dmem_amic_size, MCPDM_UL_ITER} } + }, + {0, 0}, + {EQAMIC, 0}, "PDM_UL"}, + /* BT_VX_UL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {8000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_bt_vx_ul_opp50, 1, + { + SNK_P, SERIAL_PORT_PROT, {{ + (MCBSP1_DMA_TX*ATC_SIZE), + dmem_bt_vx_ul, + dmem_bt_vx_ul_size, + (1*SCHED_LOOP_8kHz) + } } + }, + {0, 0}, {0}, "BT_VX_UL"}, + /* MM_UL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_mm_ul, 1, + { + SRC_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX3*ATC_SIZE), + dmem_mm_ul, dmem_mm_ul_size, + (10*SCHED_LOOP_48kHz), + ABE_DMASTATUS_RAW, (1 << 3) + } } + }, + {CIRCULAR_BUFFER_PERIPHERAL_R__3, 120}, + {UPROUTE, 0}, "MM_UL"}, + /* MM_UL2 */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_mm_ul2, 1, + { + SRC_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX4*ATC_SIZE), + dmem_mm_ul2, dmem_mm_ul2_size, + (2*SCHED_LOOP_48kHz), + ABE_DMASTATUS_RAW, (1 << 4) + } } + }, + {CIRCULAR_BUFFER_PERIPHERAL_R__4, 24}, + {UPROUTE, 0}, "MM_UL2"}, + /* VX_UL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {8000, MONO_MSB}, + NODRIFT, NOCALLBACK, smem_vx_ul, 1, + { + SRC_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX2*ATC_SIZE), + dmem_vx_ul, dmem_vx_ul_size, + (1*SCHED_LOOP_8kHz), + ABE_DMASTATUS_RAW, (1 << 2) + } } + }, { + CIRCULAR_BUFFER_PERIPHERAL_R__2, 2}, + {ASRC2, 0}, "VX_UL"}, + /* MM_DL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_mm_dl, 1, + { + SNK_P, PINGPONG_PORT_PROT, {{ + (CBPr_DMA_RTX0*ATC_SIZE), + dmem_mm_dl, dmem_mm_dl_size, + (2*SCHED_LOOP_48kHz), + ABE_DMASTATUS_RAW, (1 << 0) + } } + }, + {CIRCULAR_BUFFER_PERIPHERAL_R__0, 24}, + {ASRC3, 0}, "MM_DL"}, + /* VX_DL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {8000, MONO_MSB}, + NODRIFT, NOCALLBACK, smem_vx_dl, 1, + { + SNK_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX1*ATC_SIZE), + dmem_vx_dl, dmem_vx_dl_size, + (1*SCHED_LOOP_8kHz), + ABE_DMASTATUS_RAW, (1 << 1) + } } + }, + {CIRCULAR_BUFFER_PERIPHERAL_R__1, 2}, + {ASRC1, 0}, "VX_DL"}, + /* TONES_DL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_tones_dl, 1, + { + SNK_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX5*ATC_SIZE), + dmem_tones_dl, + dmem_tones_dl_size, + (2*SCHED_LOOP_48kHz), + ABE_DMASTATUS_RAW, (1 << 5) + } } + }, + {CIRCULAR_BUFFER_PERIPHERAL_R__5, 24}, + {0}, "TONES_DL"}, + /* VIB_DL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {24000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_vib, 1, + { + SNK_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX6*ATC_SIZE), + dmem_vib_dl, dmem_vib_dl_size, + (2*SCHED_LOOP_24kHz), + ABE_DMASTATUS_RAW, (1 << 6) + } } + }, + {CIRCULAR_BUFFER_PERIPHERAL_R__6, 12}, + {0}, "VIB_DL"}, + /* BT_VX_DL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {8000, MONO_MSB}, + NODRIFT, NOCALLBACK, smem_bt_vx_dl_opp50, 1, + { + SRC_P, SERIAL_PORT_PROT, {{ + (MCBSP1_DMA_RX*ATC_SIZE), + dmem_bt_vx_dl, + dmem_bt_vx_dl_size, + (1*SCHED_LOOP_8kHz), + } } + }, + {0, 0}, {0}, "BT_VX_DL"}, + /* PDM_DL */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {96000, SIX_MSB}, + NODRIFT, NOCALLBACK, 0, (MCPDM_DL_ITER/6), + {SRC_P, MCPDMDL_PORT_PROT, {{dmem_mcpdm, + dmem_mcpdm_size} } }, + {0, 0}, + {MIXDL1, EQ1, APS1, MIXDL2, EQ2L, EQ2R, APS2L, APS2R, 0}, + "PDM_DL"}, + /* MM_EXT_OUT */ + { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_mm_ext_out, 1, + { + SRC_P, SERIAL_PORT_PROT, {{ + (MCBSP1_DMA_TX*ATC_SIZE), + dmem_mm_ext_out, dmem_mm_ext_out_size, + (2*SCHED_LOOP_48kHz) + } } + }, {0, 0}, {0}, "MM_EXT_OUT"}, + /* MM_EXT_IN */ + { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, smem_mm_ext_in_opp100, 1, + { + SNK_P, SERIAL_PORT_PROT, {{ + (MCBSP1_DMA_RX*ATC_SIZE), + dmem_mm_ext_in, dmem_mm_ext_in_size, + (2*SCHED_LOOP_48kHz) + } } + }, + {0, 0}, {0}, "MM_EXT_IN"}, + /* PCM3_TX */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, 0, 1, + { + SRC_P, TDM_SERIAL_PORT_PROT, {{ + (MCBSP3_DMA_TX * + ATC_SIZE), + dmem_mm_ext_out, + dmem_mm_ext_out_size, + (2*SCHED_LOOP_48kHz) + } } + }, + {0, 0}, {0}, "TDM_OUT"}, + /* PCM3_RX */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, STEREO_MSB}, + NODRIFT, NOCALLBACK, 0, 1, + { + SRC_P, TDM_SERIAL_PORT_PROT, {{ + (MCBSP3_DMA_RX * + ATC_SIZE), + dmem_mm_ext_in, + dmem_mm_ext_in_size, + (2*SCHED_LOOP_48kHz) + } } + }, + {0, 0}, {0}, "TDM_IN"}, + /* SCHD_DBG_PORT */ { + OMAP_ABE_PORT_ACTIVITY_IDLE, {48000, MONO_MSB}, + NODRIFT, NOCALLBACK, 0, 1, + { + SRC_P, DMAREQ_PORT_PROT, {{ + (CBPr_DMA_RTX7 * + ATC_SIZE), + dmem_mm_trace, + dmem_mm_trace_size, + (2*SCHED_LOOP_48kHz), + ABE_DMASTATUS_RAW, + (1 << 4) + } } + }, {CIRCULAR_BUFFER_PERIPHERAL_R__7, 24}, + {FEAT_SEQ, FEAT_CTL, FEAT_GAINS, 0}, "SCHD_DBG"}, +}; +/* + * AESS/ATC destination and source address translation (except McASPs) + * from the original 64bits words address + */ +const u32 abe_atc_dstid[ABE_ATC_DESC_SIZE >> 3] = { + /* DMA_0 DMIC PDM_DL PDM_UL McB1TX McB1RX McB2TX McB2RX 0 .. 7 */ + 0, 0, 12, 0, 1, 0, 2, 0, + /* McB3TX McB3RX SLIMT0 SLIMT1 SLIMT2 SLIMT3 SLIMT4 SLIMT5 8 .. 15 */ + 3, 0, 4, 5, 6, 7, 8, 9, + /* SLIMT6 SLIMT7 SLIMR0 SLIMR1 SLIMR2 SLIMR3 SLIMR4 SLIMR5 16 .. 23 */ + 10, 11, 0, 0, 0, 0, 0, 0, + /* SLIMR6 SLIMR7 McASP1X ----- ----- McASP1R ----- ----- 24 .. 31 */ + 0, 0, 14, 0, 0, 0, 0, 0, + /* CBPrT0 CBPrT1 CBPrT2 CBPrT3 CBPrT4 CBPrT5 CBPrT6 CBPrT7 32 .. 39 */ + 63, 63, 63, 63, 63, 63, 63, 63, + /* CBP_T0 CBP_T1 CBP_T2 CBP_T3 CBP_T4 CBP_T5 CBP_T6 CBP_T7 40 .. 47 */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* CBP_T8 CBP_T9 CBP_T10 CBP_T11 CBP_T12 CBP_T13 CBP_T14 + CBP_T15 48 .. 63 */ + 0, 0, 0, 0, 0, 0, 0, 0, +}; +const u32 abe_atc_srcid[ABE_ATC_DESC_SIZE >> 3] = { + /* DMA_0 DMIC PDM_DL PDM_UL McB1TX McB1RX McB2TX McB2RX 0 .. 7 */ + 0, 12, 0, 13, 0, 1, 0, 2, + /* McB3TX McB3RX SLIMT0 SLIMT1 SLIMT2 SLIMT3 SLIMT4 SLIMT5 8 .. 15 */ + 0, 3, 0, 0, 0, 0, 0, 0, + /* SLIMT6 SLIMT7 SLIMR0 SLIMR1 SLIMR2 SLIMR3 SLIMR4 SLIMR5 16 .. 23 */ + 0, 0, 4, 5, 6, 7, 8, 9, + /* SLIMR6 SLIMR7 McASP1X ----- ----- McASP1R ----- ----- 24 .. 31 */ + 10, 11, 0, 0, 0, 14, 0, 0, + /* CBPrT0 CBPrT1 CBPrT2 CBPrT3 CBPrT4 CBPrT5 CBPrT6 CBPrT7 32 .. 39 */ + 63, 63, 63, 63, 63, 63, 63, 63, + /* CBP_T0 CBP_T1 CBP_T2 CBP_T3 CBP_T4 CBP_T5 CBP_T6 CBP_T7 40 .. 47 */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* CBP_T8 CBP_T9 CBP_T10 CBP_T11 CBP_T12 CBP_T13 CBP_T14 + CBP_T15 48 .. 63 */ + 0, 0, 0, 0, 0, 0, 0, 0, +}; +/* + * preset default routing configurations + * This is given as implementation EXAMPLES + * the programmer uses "abe_set_router_configuration" with its own tables + */ +const abe_router_t abe_router_ul_table_preset[NBROUTE_CONFIG][NBROUTE_UL] = { + /* VOICE UPLINK WITH PHOENIX MICROPHONES - UPROUTE_CONFIG_AMIC */ + { + /* 0 .. 9 = MM_UL */ + DMIC1_L_labelID, DMIC1_R_labelID, DMIC2_L_labelID, DMIC2_R_labelID, + MM_EXT_IN_L_labelID, MM_EXT_IN_R_labelID, AMIC_L_labelID, + AMIC_L_labelID, + ZERO_labelID, ZERO_labelID, + /* 10 .. 11 = MM_UL2 */ + AMIC_L_labelID, AMIC_L_labelID, + /* 12 .. 13 = VX_UL */ + AMIC_L_labelID, AMIC_R_labelID, + /* 14 .. 15 = RESERVED */ + ZERO_labelID, ZERO_labelID, + }, + /* VOICE UPLINK WITH THE FIRST DMIC PAIR - UPROUTE_CONFIG_DMIC1 */ + { + /* 0 .. 9 = MM_UL */ + DMIC2_L_labelID, DMIC2_R_labelID, DMIC3_L_labelID, DMIC3_R_labelID, + DMIC1_L_labelID, DMIC1_R_labelID, ZERO_labelID, ZERO_labelID, + ZERO_labelID, ZERO_labelID, + /* 10 .. 11 = MM_UL2 */ + DMIC1_L_labelID, DMIC1_R_labelID, + /* 12 .. 13 = VX_UL */ + DMIC1_L_labelID, DMIC1_R_labelID, + /* 14 .. 15 = RESERVED */ + ZERO_labelID, ZERO_labelID, + }, + /* VOICE UPLINK WITH THE SECOND DMIC PAIR - UPROUTE_CONFIG_DMIC2 */ + { + /* 0 .. 9 = MM_UL */ + DMIC3_L_labelID, DMIC3_R_labelID, DMIC1_L_labelID, DMIC1_R_labelID, + DMIC2_L_labelID, DMIC2_R_labelID, ZERO_labelID, ZERO_labelID, + ZERO_labelID, ZERO_labelID, + /* 10 .. 11 = MM_UL2 */ + DMIC2_L_labelID, DMIC2_R_labelID, + /* 12 .. 13 = VX_UL */ + DMIC2_L_labelID, DMIC2_R_labelID, + /* 14 .. 15 = RESERVED */ + ZERO_labelID, ZERO_labelID, + }, + /* VOICE UPLINK WITH THE LAST DMIC PAIR - UPROUTE_CONFIG_DMIC3 */ + { + /* 0 .. 9 = MM_UL */ + AMIC_L_labelID, AMIC_R_labelID, DMIC2_L_labelID, DMIC2_R_labelID, + DMIC3_L_labelID, DMIC3_R_labelID, ZERO_labelID, ZERO_labelID, + ZERO_labelID, ZERO_labelID, + /* 10 .. 11 = MM_UL2 */ + DMIC3_L_labelID, DMIC3_R_labelID, + /* 12 .. 13 = VX_UL */ + DMIC3_L_labelID, DMIC3_R_labelID, + /* 14 .. 15 = RESERVED */ + ZERO_labelID, ZERO_labelID, + }, + /* VOICE UPLINK WITH THE BT - UPROUTE_CONFIG_BT */ + { + /* 0 .. 9 = MM_UL */ + BT_UL_L_labelID, BT_UL_R_labelID, DMIC2_L_labelID, DMIC2_R_labelID, + DMIC3_L_labelID, DMIC3_R_labelID, DMIC1_L_labelID, DMIC1_R_labelID, + ZERO_labelID, ZERO_labelID, + /* 10 .. 11 = MM_UL2 */ + AMIC_L_labelID, AMIC_R_labelID, + /* 12 .. 13 = VX_UL */ + BT_UL_L_labelID, BT_UL_R_labelID, + /* 14 .. 15 = RESERVED */ + ZERO_labelID, ZERO_labelID, + }, + /* VOICE UPLINK WITH THE BT - UPROUTE_ECHO_MMUL2 */ + { + /* 0 .. 9 = MM_UL */ + MM_EXT_IN_L_labelID, MM_EXT_IN_R_labelID, BT_UL_L_labelID, + BT_UL_R_labelID, AMIC_L_labelID, AMIC_R_labelID, + ZERO_labelID, ZERO_labelID, ZERO_labelID, ZERO_labelID, + /* 10 .. 11 = MM_UL2 */ + EchoRef_L_labelID, EchoRef_R_labelID, + /* 12 .. 13 = VX_UL */ + AMIC_L_labelID, AMIC_L_labelID, + /* 14 .. 15 = RESERVED */ + ZERO_labelID, ZERO_labelID, + }, +}; +/* all default routing configurations */ +abe_router_t abe_router_ul_table[NBROUTE_CONFIG_MAX][NBROUTE_UL]; + +const abe_sequence_t seq_null = { + NOMASK, {CL_M1, 0, {0, 0, 0, 0}, 0}, {CL_M1, 0, {0, 0, 0, 0}, 0} +}; +/* table of new subroutines called in the sequence */ +abe_subroutine2 abe_all_subsubroutine[MAXNBSUBROUTINE]; +/* number of parameters per calls */ +u32 abe_all_subsubroutine_nparam[MAXNBSUBROUTINE]; +/* index of the subroutine */ +u32 abe_subroutine_id[MAXNBSUBROUTINE]; +/* paramters of the subroutine (if any) */ +u32 *abe_all_subroutine_params[MAXNBSUBROUTINE]; +u32 abe_subroutine_write_pointer; +/* table of all sequences */ +abe_sequence_t abe_all_sequence[MAXNBSEQUENCE]; +u32 abe_sequence_write_pointer; +/* current number of pending sequences (avoids to look in the table) */ +u32 abe_nb_pending_sequences; +/* pending sequences due to ressource collision */ +u32 abe_pending_sequences[MAXNBSEQUENCE]; +/* mask of unsharable ressources among other sequences */ +u32 abe_global_sequence_mask; +/* table of active sequences */ +abe_seq_t abe_active_sequence[MAXACTIVESEQUENCE][MAXSEQUENCESTEPS]; +/* index of the plugged subroutine doing ping-pong cache-flush DMEM accesses */ +u32 abe_irq_pingpong_player_id; +EXPORT_SYMBOL(abe_irq_pingpong_player_id); +/* index of the plugged subroutine doing acoustics protection adaptation */ +u32 abe_irq_aps_adaptation_id; +/* base addresses of the ping pong buffers in bytes addresses */ +u32 abe_base_address_pingpong[MAX_PINGPONG_BUFFERS]; +/* size of each ping/pong buffers */ +u32 abe_size_pingpong; +/* number of ping/pong buffer being used */ +u32 abe_nb_pingpong; +/* + * MAIN PORT SELECTION + */ +const u32 abe_port_priority[LAST_PORT_ID - 1] = { + OMAP_ABE_PDM_DL_PORT, + OMAP_ABE_PDM_UL_PORT, + OMAP_ABE_MM_EXT_OUT_PORT, + OMAP_ABE_MM_EXT_IN_PORT, + OMAP_ABE_DMIC_PORT, + OMAP_ABE_MM_UL_PORT, + OMAP_ABE_MM_UL2_PORT, + OMAP_ABE_MM_DL_PORT, + OMAP_ABE_TONES_DL_PORT, + OMAP_ABE_VX_UL_PORT, + OMAP_ABE_VX_DL_PORT, + OMAP_ABE_BT_VX_DL_PORT, + OMAP_ABE_BT_VX_UL_PORT, + OMAP_ABE_VIB_DL_PORT, +}; diff --git a/sound/soc/omap/abe/abe_dbg.c b/sound/soc/omap/abe/abe_dbg.c new file mode 100644 index 0000000000000000000000000000000000000000..d1b160f5fd0d18250b80af7b4132523c0a1b3b54 --- /dev/null +++ b/sound/soc/omap/abe/abe_dbg.c @@ -0,0 +1,201 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_dbg.h" +#include "abe.h" +#include "abe_mem.h" + +/** + * omap_abe_dbg_reset + * @dbg: Pointer on abe debug handle + * + * Called in order to reset Audio Back End debug global data. + * This ensures that ABE debug trace pointer is reset correctly. + */ +int omap_abe_dbg_reset(struct omap_abe_dbg *dbg) +{ + dbg->activity_log_write_pointer = 0; + dbg->mask = 0; + + return 0; +} + +/** + * omap_abe_connect_debug_trace + * @abe: Pointer on abe handle + * @dma2:pointer to the DMEM trace buffer + * + * returns the address and size of the real-time debug trace buffer, + * the content of which will vary from one firmware release to another + */ +int omap_abe_connect_debug_trace(struct omap_abe *abe, + struct omap_abe_dma *dma2) +{ + _log(ABE_ID_CONNECT_DEBUG_TRACE, 0, 0, 0); + + /* return tohe base address of the ping buffer in L3 and L4 spaces */ + (*dma2).data = (void *)(OMAP_ABE_D_DEBUG_FIFO_ADDR + + ABE_DEFAULT_BASE_ADDRESS_L3 + ABE_DMEM_BASE_OFFSET_MPU); + (*dma2).l3_dmem = (void *)(OMAP_ABE_D_DEBUG_FIFO_ADDR + + ABE_DEFAULT_BASE_ADDRESS_L3 + ABE_DMEM_BASE_OFFSET_MPU); + (*dma2).l4_dmem = (void *)(OMAP_ABE_D_DEBUG_FIFO_ADDR + + ABE_DEFAULT_BASE_ADDRESS_L4 + ABE_DMEM_BASE_OFFSET_MPU); + (*dma2).iter = (OMAP_ABE_D_DEBUG_FIFO_SIZE + OMAP_ABE_D_DEBUG_FIFO_HAL_SIZE)>>2; + + return 0; +} +EXPORT_SYMBOL(omap_abe_connect_debug_trace); + +/** + * omap_abe_set_debug_trace + * @dbg: Pointer on abe debug handle + * @debug: debug log level + * + * Set the debug level for ABE trace. This level allows to manage the number + * of information put inside the ABE trace buffer. This buffer can contains + * both AESS firmware and MPU traces. + */ +int omap_abe_set_debug_trace(struct omap_abe_dbg *dbg, int debug) +{ + _log(ABE_ID_SET_DEBUG_TRACE, 0, 0, 0); + + dbg->mask = debug; + + return 0; +} +EXPORT_SYMBOL(omap_abe_set_debug_trace); + +/** + * omap_abe_dbg_log - Log ABE trace inside circular buffer + * @x: data to be logged + * @y: data to be logged + * @z: data to be logged + * @t: data to be logged + * Parameter : + * + * abe_dbg_activity_log : global circular buffer holding the data + * abe_dbg_activity_log_write_pointer : circular write pointer + * + * saves data in the log file + */ +void omap_abe_dbg_log(struct omap_abe *abe, u32 x, u32 y, u32 z, u32 t) +{ + u32 time_stamp, data; + struct omap_abe_dbg *dbg = &abe->dbg; + + if (dbg->activity_log_write_pointer >= + (OMAP_ABE_D_DEBUG_HAL_TASK_SIZE - 2)) + dbg->activity_log_write_pointer = 0; + + /* copy in DMEM trace buffer and CortexA9 local buffer and a small 7 + words circular buffer of the DMA trace ending with 0x55555555 + (tag for last word) */ + omap_abe_mem_read(abe, OMAP_ABE_DMEM, OMAP_ABE_D_LOOPCOUNTER_ADDR, + (u32 *) &time_stamp, sizeof(time_stamp)); + dbg->activity_log[dbg->activity_log_write_pointer] = time_stamp; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_DEBUG_HAL_TASK_ADDR + + (dbg->activity_log_write_pointer << 2), + (u32 *) &time_stamp, sizeof(time_stamp)); + dbg->activity_log_write_pointer++; + + data = ((x & MAX_UINT8) << 24) | ((y & MAX_UINT8) << 16) | + ((z & MAX_UINT8) << 8) + | (t & MAX_UINT8); + dbg->activity_log[dbg->activity_log_write_pointer] = data; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_DEBUG_HAL_TASK_ADDR + + (dbg->activity_log_write_pointer << 2), + (u32 *) &data, sizeof(data)); + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_DEBUG_FIFO_HAL_ADDR + + ((dbg->activity_log_write_pointer << 2) & + (OMAP_ABE_D_DEBUG_FIFO_HAL_SIZE - 1)), (u32 *) &data, + sizeof(data)); + + data = ABE_DBG_MAGIC_NUMBER; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_DEBUG_FIFO_HAL_ADDR + + (((dbg->activity_log_write_pointer + 1) << 2) & + (OMAP_ABE_D_DEBUG_FIFO_HAL_SIZE - 1)), + (u32 *) &data, sizeof(data)); + dbg->activity_log_write_pointer++; + + if (dbg->activity_log_write_pointer >= OMAP_ABE_D_DEBUG_HAL_TASK_SIZE) + dbg->activity_log_write_pointer = 0; +} + +/** + * omap_abe_dbg_error_log - Log ABE error + * @abe: Pointer on abe handle + * @level: level of error + * @error: error ID to log + * + * Log the ABE errors. + */ +void omap_abe_dbg_error(struct omap_abe *abe, int level, int error) +{ + omap_abe_dbg_log(abe, error, MAX_UINT8, MAX_UINT8, MAX_UINT8); +} diff --git a/sound/soc/omap/abe/abe_dbg.h b/sound/soc/omap/abe/abe_dbg.h new file mode 100644 index 0000000000000000000000000000000000000000..2cdced99e963d0ebf473684ee2227c7ef52312c7 --- /dev/null +++ b/sound/soc/omap/abe/abe_dbg.h @@ -0,0 +1,206 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_DBG_H_ +#define _ABE_DBG_H_ + +#include "abe_typ.h" +#include "abe_dm_addr.h" + +/* + * Debug trace format + * TIME 2 bytes from ABE : 4kHz period of the FW scheduler + * SUBID 1 byte : HAL API index + * From 0 to 16 bytes : parameters of the subroutine + * on every 32 dumps a tag is pushed on the debug trace : 0x55555555 + */ +#define dbg_bitfield_offset 8 +#define dbg_api_calls 0 +#define dbg_mapi (1L << (dbg_api_calls + dbg_bitfield_offset)) +#define dbg_external_data_access 1 +#define dbg_mdata (1L << (dbg_external_data_access + dbg_bitfield_offset)) +#define dbg_err_codes 2 +#define dbg_merr (1L << (dbg_api_calls + dbg_bitfield_offset)) +#define ABE_DBG_MAGIC_NUMBER 0x55555555 +/* + * IDs used for traces + */ +#define ABE_ID_RESET_HAL (1 + dbg_mapi) +#define ABE_ID_LOAD_FW (2 + dbg_mapi) +#define ABE_ID_DEFAULT_CONFIGURATION (3 + dbg_mapi) +#define ABE_ID_IRQ_PROCESSING (4 + dbg_mapi) +#define ABE_ID_EVENT_GENERATOR_SWITCH (5 + dbg_mapi) +#define ABE_ID_READ_HARDWARE_CONFIGURATION (6 + dbg_mapi) +#define ABE_ID_READ_LOWEST_OPP (7 + dbg_mapi) +#define ABE_ID_WRITE_GAIN (8 + dbg_mapi) +#define ABE_ID_SET_ASRC_DRIFT_CONTROL (9 + dbg_mapi) +#define ABE_ID_PLUG_SUBROUTINE (10 + dbg_mapi) +#define ABE_ID_UNPLUG_SUBROUTINE (11 + dbg_mapi) +#define ABE_ID_PLUG_SEQUENCE (12 + dbg_mapi) +#define ABE_ID_LAUNCH_SEQUENCE (13 + dbg_mapi) +#define ABE_ID_LAUNCH_SEQUENCE_param (14 + dbg_mapi) +#define ABE_ID_CONNECT_IRQ_PING_PONG_PORT (15 + dbg_mapi) +#define ABE_ID_READ_ANALOG_GAIN_DL (16 + dbg_mapi) +#define ABE_ID_READ_ANALOG_GAIN_UL (17 + dbg_mapi) +#define ABE_ID_ENABLE_DYN_UL_GAIN (18 + dbg_mapi) +#define ABE_ID_DISABLE_DYN_UL_GAIN (19 + dbg_mapi) +#define ABE_ID_ENABLE_DYN_EXTENSION (20 + dbg_mapi) +#define ABE_ID_DISABLE_DYN_EXTENSION (21 + dbg_mapi) +#define ABE_ID_NOTIFY_ANALOG_GAIN_CHANGED (22 + dbg_mapi) +#define ABE_ID_RESET_PORT (23 + dbg_mapi) +#define ABE_ID_READ_REMAINING_DATA (24 + dbg_mapi) +#define ABE_ID_DISABLE_DATA_TRANSFER (25 + dbg_mapi) +#define ABE_ID_ENABLE_DATA_TRANSFER (26 + dbg_mapi) +#define ABE_ID_READ_GLOBAL_COUNTER (27 + dbg_mapi) +#define ABE_ID_SET_DMIC_FILTER (28 + dbg_mapi) +#define ABE_ID_SET_OPP_PROCESSING (29 + dbg_mapi) +#define ABE_ID_SET_PING_PONG_BUFFER (30 + dbg_mapi) +#define ABE_ID_READ_PORT_ADDRESS (31 + dbg_mapi) +#define ABE_ID_LOAD_FW_param (32 + dbg_mapi) +#define ABE_ID_WRITE_HEADSET_OFFSET (33 + dbg_mapi) +#define ABE_ID_READ_GAIN_RANGES (34 + dbg_mapi) +#define ABE_ID_WRITE_EQUALIZER (35 + dbg_mapi) +#define ABE_ID_WRITE_ASRC (36 + dbg_mapi) +#define ABE_ID_WRITE_APS (37 + dbg_mapi) +#define ABE_ID_WRITE_MIXER (38 + dbg_mapi) +#define ABE_ID_WRITE_EANC (39 + dbg_mapi) +#define ABE_ID_WRITE_ROUTER (40 + dbg_mapi) +#define ABE_ID_READ_PORT_GAIN (41 + dbg_mapi) +#define ABE_ID_ASRC (42 + dbg_mapi) +#define ABE_ID_READ_APS (43 + dbg_mapi) +#define ABE_ID_READ_APS_energy (44 + dbg_mapi) +#define ABE_ID_READ_MIXER (45 + dbg_mapi) +#define ABE_READ_EANC (46 + dbg_mapi) +#define ABE_ID_READ_ROUTER (47 + dbg_mapi) +#define ABE_ID_READ_DEBUG_TRACE (48 + dbg_mapi) +#define ABE_ID_SET_SEQUENCE_TIME_ACCURACY (49 + dbg_mapi) +#define ABE_ID_SET_DEBUG_PINS (50 + dbg_mapi) +#define ABE_ID_SELECT_MAIN_PORT (51 + dbg_mapi) +#define ABE_ID_WRITE_EVENT_GENERATOR (52 + dbg_mapi) +#define ABE_ID_READ_USE_CASE_OPP (53 + dbg_mapi) +#define ABE_ID_SELECT_DATA_SOURCE (54 + dbg_mapi) +#define ABE_ID_READ_NEXT_PING_PONG_BUFFER (55 + dbg_mapi) +#define ABE_ID_INIT_PING_PONG_BUFFER (56 + dbg_mapi) +#define ABE_ID_CONNECT_CBPR_DMAREQ_PORT (57 + dbg_mapi) +#define ABE_ID_CONNECT_DMAREQ_PORT (58 + dbg_mapi) +#define ABE_ID_CONNECT_DMAREQ_PING_PONG_PORT (59 + dbg_mapi) +#define ABE_ID_CONNECT_SERIAL_PORT (60 + dbg_mapi) +#define ABE_ID_CONNECT_SLIMBUS_PORT (61 + dbg_mapi) +#define ABE_ID_READ_GAIN (62 + dbg_mapi) +#define ABE_ID_SET_ROUTER_CONFIGURATION (63 + dbg_mapi) +#define ABE_ID_CONNECT_DEBUG_TRACE (64 + dbg_mapi) +#define ABE_ID_SET_DEBUG_TRACE (65 + dbg_mapi) +#define ABE_ID_REMOTE_DEBUGGER_INTERFACE (66 + dbg_mapi) +#define ABE_ID_ENABLE_TEST_PATTERN (67 + dbg_mapi) +#define ABE_ID_CONNECT_TDM_PORT (68 + dbg_mapi) +/* + * IDs used for error codes + */ +#define NOERR 0 +#define ABE_SET_MEMORY_CONFIG_ERR (1 + dbg_merr) +#define ABE_BLOCK_COPY_ERR (2 + dbg_merr) +#define ABE_SEQTOOLONG (3 + dbg_merr) +#define ABE_BADSAMPFORMAT (4 + dbg_merr) +#define ABE_SET_ATC_ABE_BLOCK_COPY_ERR MEMORY_CONFIG_ERR (5 + dbg_merr) +#define ABE_PROTOCOL_ERROR (6 + dbg_merr) +#define ABE_PARAMETER_ERROR (7 + dbg_merr) +/* port programmed while still running */ +#define ABE_PORT_REPROGRAMMING (8 + dbg_merr) +#define ABE_READ_USE_CASE_OPP_ERR (9 + dbg_merr) +#define ABE_PARAMETER_OVERFLOW (10 + dbg_merr) +#define ABE_FW_FIFO_WRITE_PTR_ERR (11 + dbg_merr) + +/* + * IDs used for error codes + */ +#define OMAP_ABE_ERR_LIB (1 << 1) +#define OMAP_ABE_ERR_API (1 << 2) +#define OMAP_ABE_ERR_INI (1 << 3) +#define OMAP_ABE_ERR_SEQ (1 << 4) +#define OMAP_ABE_ERR_DBG (1 << 5) +#define OMAP_ABE_ERR_EXT (1 << 6) + +struct omap_abe_dbg { + /* Debug Data */ + u32 activity_log[OMAP_ABE_D_DEBUG_HAL_TASK_SIZE]; + u32 activity_log_write_pointer; + u32 mask; +}; + +struct omap_abe_dma { + /* OCP L3 pointer to the first address of the */ + void *data; + /* destination buffer (either DMA or Ping-Pong read/write pointers). */ + /* address L3 when addressing the DMEM buffer instead of CBPr */ + void *l3_dmem; + /* address L3 translated to L4 the ARM memory space */ + void *l4_dmem; + /* number of iterations for the DMA data moves. */ + u32 iter; +}; + +/** + * omap_abe_dbg_reset + * @dbg: Pointer on abe debug handle + * + * Called in order to reset Audio Back End debug global data. + * This ensures that ABE debug trace pointer is reset correctly. + */ +int omap_abe_dbg_reset(struct omap_abe_dbg *dbg); + +#endif /* _ABE_DBG_H_ */ diff --git a/sound/soc/omap/abe/abe_def.h b/sound/soc/omap/abe/abe_def.h new file mode 100644 index 0000000000000000000000000000000000000000..ac1d2638f0b685989fbfd508649fb18aac5d8ac7 --- /dev/null +++ b/sound/soc/omap/abe/abe_def.h @@ -0,0 +1,307 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_DEF_H_ +#define _ABE_DEF_H_ +/* + * HARDWARE AND PERIPHERAL DEFINITIONS + */ +/* MM_DL */ +#define ABE_CBPR0_IDX 0 +/* VX_DL */ +#define ABE_CBPR1_IDX 1 +/* VX_UL */ +#define ABE_CBPR2_IDX 2 +/* MM_UL */ +#define ABE_CBPR3_IDX 3 +/* MM_UL2 */ +#define ABE_CBPR4_IDX 4 +/* TONES */ +#define ABE_CBPR5_IDX 5 +/* VIB */ +#define ABE_CBPR6_IDX 6 +/* DEBUG/CTL */ +#define ABE_CBPR7_IDX 7 +#define CIRCULAR_BUFFER_PERIPHERAL_R__0 (0x100 + ABE_CBPR0_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__1 (0x100 + ABE_CBPR1_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__2 (0x100 + ABE_CBPR2_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__3 (0x100 + ABE_CBPR3_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__4 (0x100 + ABE_CBPR4_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__5 (0x100 + ABE_CBPR5_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__6 (0x100 + ABE_CBPR6_IDX*4) +#define CIRCULAR_BUFFER_PERIPHERAL_R__7 (0x100 + ABE_CBPR7_IDX*4) +#define PING_PONG_WITH_MCU_IRQ 1 +#define PING_PONG_WITH_DSP_IRQ 2 +/* ID used for LIB memory copy subroutines */ +#define COPY_FROM_ABE_TO_HOST 1 +#define COPY_FROM_HOST_TO_ABE 2 +/* + * INTERNAL DEFINITIONS + */ +#define ABE_FIRMWARE_MAX_SIZE 26629 +/* 24 Q6.26 coefficients */ +#define NBEQ1 25 +/* 2x12 Q6.26 coefficients */ +#define NBEQ2 13 +/* TBD APS first set of parameters */ +#define NBAPS1 10 +/* TBD APS second set of parameters */ +#define NBAPS2 10 +/* Mixer used for sending tones to the uplink voice path */ +#define NBMIX_AUDIO_UL 2 +/* Main downlink mixer */ +#define NBMIX_DL1 4 +/* Handsfree downlink mixer */ +#define NBMIX_DL2 4 +/* Side-tone mixer */ +#define NBMIX_SDT 2 +/* Echo reference mixer */ +#define NBMIX_ECHO 2 +/* Voice record mixer */ +#define NBMIX_VXREC 4 +/* unsigned version of (-1) */ +#define CC_M1 0xFF +#define CS_M1 0xFFFF +#define CL_M1 0xFFFFFFFFL +/* + Mixer ID Input port ID Comments + DL1_MIXER 0 MMDL path + 1 MMUL2 path + 2 VXDL path + 3 TONES path + SDT_MIXER 0 Uplink path + 1 Downlink path + ECHO_MIXER 0 DL1_MIXER path + 1 DL2_MIXER path + AUDUL_MIXER 0 TONES_DL path + 1 Uplink path + 2 MM_DL path + VXREC_MIXER 0 TONES_DL path + 1 VX_DL path + 2 MM_DL path + 3 VX_UL path +*/ +#define MIX_VXUL_INPUT_MM_DL 0 +#define MIX_VXUL_INPUT_TONES 1 +#define MIX_VXUL_INPUT_VX_UL 2 +#define MIX_VXUL_INPUT_VX_DL 3 +#define MIX_DL1_INPUT_MM_DL 0 +#define MIX_DL1_INPUT_MM_UL2 1 +#define MIX_DL1_INPUT_VX_DL 2 +#define MIX_DL1_INPUT_TONES 3 +#define MIX_DL2_INPUT_MM_DL 0 +#define MIX_DL2_INPUT_MM_UL2 1 +#define MIX_DL2_INPUT_VX_DL 2 +#define MIX_DL2_INPUT_TONES 3 +#define MIX_SDT_INPUT_UP_MIXER 0 +#define MIX_SDT_INPUT_DL1_MIXER 1 +#define MIX_AUDUL_INPUT_MM_DL 0 +#define MIX_AUDUL_INPUT_TONES 1 +#define MIX_AUDUL_INPUT_UPLINK 2 +#define MIX_AUDUL_INPUT_VX_DL 3 +#define MIX_VXREC_INPUT_MM_DL 0 +#define MIX_VXREC_INPUT_TONES 1 +#define MIX_VXREC_INPUT_VX_UL 2 +#define MIX_VXREC_INPUT_VX_DL 3 +#define MIX_ECHO_DL1 0 +#define MIX_ECHO_DL2 1 +/* nb of samples to route */ +#define NBROUTE_UL 16 +/* 10 routing tables max */ +#define NBROUTE_CONFIG_MAX 10 +/* 5 pre-computed routing tables */ +#define NBROUTE_CONFIG 6 +/* AMIC on VX_UL */ +#define UPROUTE_CONFIG_AMIC 0 +/* DMIC first pair on VX_UL */ +#define UPROUTE_CONFIG_DMIC1 1 +/* DMIC second pair on VX_UL */ +#define UPROUTE_CONFIG_DMIC2 2 +/* DMIC last pair on VX_UL */ +#define UPROUTE_CONFIG_DMIC3 3 +/* BT_UL on VX_UL */ +#define UPROUTE_CONFIG_BT 4 +/* ECHO_REF on MM_UL2 */ +#define UPROUTE_ECHO_MMUL2 5 +/* call-back indexes */ +#define MAXCALLBACK 100 +/* subroutines */ +#define MAXNBSUBROUTINE 100 +/* time controlled sequenced */ +#define MAXNBSEQUENCE 20 +/* maximum simultaneous active sequences */ +#define MAXACTIVESEQUENCE 20 +/* max number of steps in the sequences */ +#define MAXSEQUENCESTEPS 2 +/* max number of feature associated to a port */ +#define MAXFEATUREPORT 12 +#define SUB_0_PARAM 0 +/* number of parameters per sequence calls */ +#define SUB_1_PARAM 1 +#define SUB_2_PARAM 2 +#define SUB_3_PARAM 3 +#define SUB_4_PARAM 4 +/* active sequence mask = 0 means the line is free */ +#define FREE_LINE 0 +/* no ask for collision protection */ +#define NOMASK (1 << 0) +/* do not allow a PDM OFF during the execution of this sequence */ +#define MASK_PDM_OFF (1 << 1) +/* do not allow a PDM ON during the execution of this sequence */ +#define MASK_PDM_ON (1 << 2) +/* explicit name of the feature */ +#define NBCHARFEATURENAME 16 +/* explicit name of the port */ +#define NBCHARPORTNAME 16 +/* sink / input port from Host point of view (or AESS for DMIC/McPDM/.. */ +#define SNK_P ABE_ATC_DIRECTION_IN +/* source / ouptut port */ +#define SRC_P ABE_ATC_DIRECTION_OUT +/* no ASRC applied */ +#define NODRIFT 0 +/* for abe_set_asrc_drift_control */ +#define FORCED_DRIFT_CONTROL 1 +/* for abe_set_asrc_drift_control */ +#define ADPATIVE_DRIFT_CONTROL 2 +/* number of task/slot depending on the OPP value */ +#define DOPPMODE32_OPP100 (0x00000010) +#define DOPPMODE32_OPP50 (0x0000000C) +#define DOPPMODE32_OPP25 (0x0000004) +/* + * ABE CONST AREA FOR PARAMETERS TRANSLATION + */ +#define GAIN_MAXIMUM 3000L +#define GAIN_24dB 2400L +#define GAIN_18dB 1800L +#define GAIN_12dB 1200L +#define GAIN_6dB 600L +/* default gain = 1 */ +#define GAIN_0dB 0L +#define GAIN_M6dB -600L +#define GAIN_M12dB -1200L +#define GAIN_M18dB -1800L +#define GAIN_M24dB -2400L +#define GAIN_M30dB -3000L +#define GAIN_M40dB -4000L +#define GAIN_M50dB -5000L +/* muted gain = -120 decibels */ +#define MUTE_GAIN -12000L +#define GAIN_TOOLOW -13000L +#define GAIN_MUTE MUTE_GAIN +#define RAMP_MINLENGTH 0L +/* ramp_t is in milli- seconds */ +#define RAMP_0MS 0L +#define RAMP_1MS 1L +#define RAMP_2MS 2L +#define RAMP_5MS 5L +#define RAMP_10MS 10L +#define RAMP_20MS 20L +#define RAMP_50MS 50L +#define RAMP_100MS 100L +#define RAMP_200MS 200L +#define RAMP_500MS 500L +#define RAMP_1000MS 1000L +#define RAMP_MAXLENGTH 10000L +/* for abe_translate_gain_format */ +#define LINABE_TO_DECIBELS 1 +#define DECIBELS_TO_LINABE 2 +/* for abe_translate_ramp_format */ +#define IIRABE_TO_MICROS 1 +#define MICROS_TO_IIABE 2 +/* + * ABE CONST AREA FOR PERIPHERAL TUNING + */ +/* port idled IDLE_P */ +#define OMAP_ABE_PORT_ACTIVITY_IDLE 1 +/* port initialized, ready to be activated */ +#define OMAP_ABE_PORT_INITIALIZED 3 +/* port activated RUN_P */ +#define OMAP_ABE_PORT_ACTIVITY_RUNNING 2 +#define NOCALLBACK 0 +#define NOPARAMETER 0 +/* number of ATC access upon AMIC DMArequests, all the FIFOs are enabled */ +#define MCPDM_UL_ITER 4 +/* All the McPDM FIFOs are enabled simultaneously */ +#define MCPDM_DL_ITER 24 +/* All the DMIC FIFOs are enabled simultaneously */ +#define DMIC_ITER 12 +/* TBD later if needed */ +#define MAX_PINGPONG_BUFFERS 2 +/* + * Indexes to the subroutines + */ +#define SUB_WRITE_MIXER 1 +#define SUB_WRITE_PORT_GAIN 2 +/* OLD WAY */ +#define c_feat_init_eq 1 +#define c_feat_read_eq1 2 +#define c_write_eq1 3 +#define c_feat_read_eq2 4 +#define c_write_eq2 5 +#define c_feat_read_eq3 6 +#define c_write_eq3 7 +/* max number of gain to be controlled by HAL */ +#define MAX_NBGAIN_CMEM 36 +/* + * MACROS + */ +#define maximum(a, b) (((a) < (b)) ? (b) : (a)) +#define minimum(a, b) (((a) > (b)) ? (b) : (a)) +#define absolute(a) (((a) > 0) ? (a) : ((-1)*(a))) +#define HAL_VERSIONS 9 +#endif/* _ABE_DEF_H_ */ diff --git a/sound/soc/omap/abe/abe_define.h b/sound/soc/omap/abe/abe_define.h new file mode 100644 index 0000000000000000000000000000000000000000..41b700acaf65df134bf84193d49bac91653922c9 --- /dev/null +++ b/sound/soc/omap/abe/abe_define.h @@ -0,0 +1,120 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifndef _ABE_DEFINE_H_ +#define _ABE_DEFINE_H_ + +#define ATC_DESCRIPTOR_NUMBER 64 +#define PROCESSING_SLOTS 25 +#define TASK_POOL_LENGTH 136 +#define MCU_IRQ 0x24 +#define MCU_IRQ_SHIFT2 0x90 +#define DMA_REQ_SHIFT2 0x210 +#define DSP_IRQ 0x4c +#define IRQtag_APS 0x000a +#define IRQtag_COUNT 0x000c +#define IRQtag_PP 0x000d +#define DMAreq_7 0x0080 +#define IRQ_FIFO_LENGTH 16 +#define SDT_EQ_ORDER 4 +#define DL_EQ_ORDER 12 +#define MIC_FILTER_ORDER 4 +#define GAINS_WITH_RAMP1 14 +#define GAINS_WITH_RAMP2 22 +#define GAINS_WITH_RAMP_TOTAL 36 +#define ASRC_MEMLENGTH 40 +#define ASRC_UL_VX_FIR_L 19 +#define ASRC_DL_VX_FIR_L 19 +#define ASRC_MM_EXT_IN_FIR_L 18 +#define ASRC_margin 2 +#define ASRC_N_8k 2 +#define ASRC_N_16k 4 +#define ASRC_N_48k 12 +#define VIBRA_N 5 +#define VIBRA1_IIR_MEMSIZE 11 +#define SAMP_LOOP_96K 24 +#define SAMP_LOOP_48K 12 +#define SAMP_LOOP_48KM1 11 +#define SAMP_LOOP_48KM2 10 +#define SAMP_LOOP_16K 4 +#define SAMP_LOOP_8K 2 +#define INPUT_SCALE_SHIFTM2 5156 +#define SATURATION 8420 +#define SATURATION_7FFF 8416 +#define OUTPUT_SCALE_SHIFTM2 5160 +#define NTAPS_SRC_44P1 24 +#define NTAPS_SRC_44P1_M4 96 +#define NTAPS_SRC_44P1_THR 48 +#define NTAPS_SRC_44P1_THRM4 192 +#define DRIFT_COUNTER_44P1M1 443 +#define NB_OF_PHASES_SRC44P1 12 +#define NB_OF_PHASES_SRC44P1M1 11 +#define SRC44P1_BUFFER_SIZE 96 +#define SRC44P1_BUFFER_SIZE_M4 384 +#define SRC44P1_INIT_RPTR 60 +#define MUTE_SCALING 5164 +#define ABE_PMEM 1 +#define ABE_CMEM 2 +#define ABE_SMEM 3 +#define ABE_DMEM 4 +#define ABE_ATC 5 +#define ASRC_BT_UL_FIR_L 19 +#define ASRC_BT_DL_FIR_L 19 +#define SRC44P1_COEF_ADDR 1466 +#define NTAPS_P_SRC_44P1_M4 144 + +#endif /* _ABE_DEFINE_H_ */ diff --git a/sound/soc/omap/abe/abe_dm_addr.h b/sound/soc/omap/abe/abe_dm_addr.h new file mode 100644 index 0000000000000000000000000000000000000000..a9c67a185fd75e940bd3718557aa1275127b16a6 --- /dev/null +++ b/sound/soc/omap/abe/abe_dm_addr.h @@ -0,0 +1,229 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated 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. + * + */ +#define OMAP_ABE_D_ATCDESCRIPTORS_ADDR 0x0 +#define OMAP_ABE_D_ATCDESCRIPTORS_SIZE 0x200 +#define OMAP_ABE_STACK_ADDR 0x200 +#define OMAP_ABE_STACK_SIZE 0x70 +#define OMAP_ABE_D_VERSION_ADDR 0x270 +#define OMAP_ABE_D_VERSION_SIZE 0x4 +#define OMAP_ABE_D_IODESCR_ADDR 0x274 +#define OMAP_ABE_D_IODESCR_SIZE 0x280 +#define OMAP_ABE_D_ZERO_ADDR 0x4F4 +#define OMAP_ABE_D_ZERO_SIZE 0x4 +#define OMAP_ABE_DBG_TRACE1_ADDR 0x4F8 +#define OMAP_ABE_DBG_TRACE1_SIZE 0x1 +#define OMAP_ABE_DBG_TRACE2_ADDR 0x4F9 +#define OMAP_ABE_DBG_TRACE2_SIZE 0x1 +#define OMAP_ABE_DBG_TRACE3_ADDR 0x4FA +#define OMAP_ABE_DBG_TRACE3_SIZE 0x1 +#define OMAP_ABE_D_MULTIFRAME_ADDR 0x4FC +#define OMAP_ABE_D_MULTIFRAME_SIZE 0x190 +#define OMAP_ABE_D_IDLETASK_ADDR 0x68C +#define OMAP_ABE_D_IDLETASK_SIZE 0x2 +#define OMAP_ABE_D_TYPELENGTHCHECK_ADDR 0x68E +#define OMAP_ABE_D_TYPELENGTHCHECK_SIZE 0x2 +#define OMAP_ABE_D_MAXTASKBYTESINSLOT_ADDR 0x690 +#define OMAP_ABE_D_MAXTASKBYTESINSLOT_SIZE 0x2 +#define OMAP_ABE_D_REWINDTASKBYTES_ADDR 0x692 +#define OMAP_ABE_D_REWINDTASKBYTES_SIZE 0x2 +#define OMAP_ABE_D_PCURRENTTASK_ADDR 0x694 +#define OMAP_ABE_D_PCURRENTTASK_SIZE 0x2 +#define OMAP_ABE_D_PFASTLOOPBACK_ADDR 0x696 +#define OMAP_ABE_D_PFASTLOOPBACK_SIZE 0x2 +#define OMAP_ABE_D_PNEXTFASTLOOPBACK_ADDR 0x698 +#define OMAP_ABE_D_PNEXTFASTLOOPBACK_SIZE 0x4 +#define OMAP_ABE_D_PPCURRENTTASK_ADDR 0x69C +#define OMAP_ABE_D_PPCURRENTTASK_SIZE 0x2 +#define OMAP_ABE_D_SLOTCOUNTER_ADDR 0x6A0 +#define OMAP_ABE_D_SLOTCOUNTER_SIZE 0x2 +#define OMAP_ABE_D_LOOPCOUNTER_ADDR 0x6A4 +#define OMAP_ABE_D_LOOPCOUNTER_SIZE 0x4 +#define OMAP_ABE_D_REWINDFLAG_ADDR 0x6A8 +#define OMAP_ABE_D_REWINDFLAG_SIZE 0x2 +#define OMAP_ABE_D_SLOT23_CTRL_ADDR 0x6AC +#define OMAP_ABE_D_SLOT23_CTRL_SIZE 0x4 +#define OMAP_ABE_D_MCUIRQFIFO_ADDR 0x6B0 +#define OMAP_ABE_D_MCUIRQFIFO_SIZE 0x40 +#define OMAP_ABE_D_PINGPONGDESC_ADDR 0x6F0 +#define OMAP_ABE_D_PINGPONGDESC_SIZE 0x18 +#define OMAP_ABE_D_PP_MCU_IRQ_ADDR 0x708 +#define OMAP_ABE_D_PP_MCU_IRQ_SIZE 0x2 +#define OMAP_ABE_D_SRC44P1_MMDL_STRUCT_ADDR 0x70C +#define OMAP_ABE_D_SRC44P1_MMDL_STRUCT_SIZE 0x12 +#define OMAP_ABE_D_SRC44P1_TONES_STRUCT_ADDR 0x720 +#define OMAP_ABE_D_SRC44P1_TONES_STRUCT_SIZE 0x12 +#define OMAP_ABE_D_CTRLPORTFIFO_ADDR 0x740 +#define OMAP_ABE_D_CTRLPORTFIFO_SIZE 0x10 +#define OMAP_ABE_D_IDLE_STATE_ADDR 0x750 +#define OMAP_ABE_D_IDLE_STATE_SIZE 0x4 +#define OMAP_ABE_D_STOP_REQUEST_ADDR 0x754 +#define OMAP_ABE_D_STOP_REQUEST_SIZE 0x4 +#define OMAP_ABE_D_REF0_ADDR 0x758 +#define OMAP_ABE_D_REF0_SIZE 0x2 +#define OMAP_ABE_D_DEBUGREGISTER_ADDR 0x75C +#define OMAP_ABE_D_DEBUGREGISTER_SIZE 0x8C +#define OMAP_ABE_D_GCOUNT_ADDR 0x7E8 +#define OMAP_ABE_D_GCOUNT_SIZE 0x2 +#define OMAP_ABE_D_FASTCOUNTER_ADDR 0x7EC +#define OMAP_ABE_D_FASTCOUNTER_SIZE 0x4 +#define OMAP_ABE_D_SLOWCOUNTER_ADDR 0x7F0 +#define OMAP_ABE_D_SLOWCOUNTER_SIZE 0x4 +#define OMAP_ABE_D_AUPLINKROUTING_ADDR 0x7F4 +#define OMAP_ABE_D_AUPLINKROUTING_SIZE 0x20 +#define OMAP_ABE_D_VIRTAUDIOLOOP_ADDR 0x814 +#define OMAP_ABE_D_VIRTAUDIOLOOP_SIZE 0x4 +#define OMAP_ABE_D_ASRCVARS_DL_VX_ADDR 0x818 +#define OMAP_ABE_D_ASRCVARS_DL_VX_SIZE 0x20 +#define OMAP_ABE_D_ASRCVARS_UL_VX_ADDR 0x838 +#define OMAP_ABE_D_ASRCVARS_UL_VX_SIZE 0x20 +#define OMAP_ABE_D_COEFADDRESSES_VX_ADDR 0x858 +#define OMAP_ABE_D_COEFADDRESSES_VX_SIZE 0x20 +#define OMAP_ABE_D_ASRCVARS_MM_EXT_IN_ADDR 0x878 +#define OMAP_ABE_D_ASRCVARS_MM_EXT_IN_SIZE 0x20 +#define OMAP_ABE_D_COEFADDRESSES_MM_ADDR 0x898 +#define OMAP_ABE_D_COEFADDRESSES_MM_SIZE 0x20 +#define OMAP_ABE_D_TRACEBUFADR_ADDR 0x8B8 +#define OMAP_ABE_D_TRACEBUFADR_SIZE 0x2 +#define OMAP_ABE_D_TRACEBUFOFFSET_ADDR 0x8BA +#define OMAP_ABE_D_TRACEBUFOFFSET_SIZE 0x2 +#define OMAP_ABE_D_TRACEBUFLENGTH_ADDR 0x8BC +#define OMAP_ABE_D_TRACEBUFLENGTH_SIZE 0x2 +#define OMAP_ABE_D_PEMPTY_ADDR 0x8C0 +#define OMAP_ABE_D_PEMPTY_SIZE 0x54 +#define OMAP_ABE_D_ECHO_REF_48_16_WRAP_ADDR 0x914 +#define OMAP_ABE_D_ECHO_REF_48_16_WRAP_SIZE 0x8 +#define OMAP_ABE_D_ECHO_REF_48_8_WRAP_ADDR 0x91C +#define OMAP_ABE_D_ECHO_REF_48_8_WRAP_SIZE 0x8 +#define OMAP_ABE_D_BT_UL_16_48_WRAP_ADDR 0x924 +#define OMAP_ABE_D_BT_UL_16_48_WRAP_SIZE 0x8 +#define OMAP_ABE_D_BT_UL_8_48_WRAP_ADDR 0x92C +#define OMAP_ABE_D_BT_UL_8_48_WRAP_SIZE 0x8 +#define OMAP_ABE_D_BT_DL_48_16_WRAP_ADDR 0x934 +#define OMAP_ABE_D_BT_DL_48_16_WRAP_SIZE 0x8 +#define OMAP_ABE_D_BT_DL_48_8_WRAP_ADDR 0x93C +#define OMAP_ABE_D_BT_DL_48_8_WRAP_SIZE 0x8 +#define OMAP_ABE_D_VX_DL_16_48_WRAP_ADDR 0x944 +#define OMAP_ABE_D_VX_DL_16_48_WRAP_SIZE 0x8 +#define OMAP_ABE_D_VX_DL_8_48_WRAP_ADDR 0x94C +#define OMAP_ABE_D_VX_DL_8_48_WRAP_SIZE 0x8 +#define OMAP_ABE_D_VX_UL_48_16_WRAP_ADDR 0x954 +#define OMAP_ABE_D_VX_UL_48_16_WRAP_SIZE 0x8 +#define OMAP_ABE_D_VX_UL_48_8_WRAP_ADDR 0x95C +#define OMAP_ABE_D_VX_UL_48_8_WRAP_SIZE 0x8 +#define OMAP_ABE_D_ASRCVARS_BT_UL_ADDR 0x964 +#define OMAP_ABE_D_ASRCVARS_BT_UL_SIZE 0x20 +#define OMAP_ABE_D_ASRCVARS_BT_DL_ADDR 0x984 +#define OMAP_ABE_D_ASRCVARS_BT_DL_SIZE 0x20 +#define OMAP_ABE_D_BT_DL_48_8_OPP100_WRAP_ADDR 0x9A4 +#define OMAP_ABE_D_BT_DL_48_8_OPP100_WRAP_SIZE 0x8 +#define OMAP_ABE_D_BT_DL_48_16_OPP100_WRAP_ADDR 0x9AC +#define OMAP_ABE_D_BT_DL_48_16_OPP100_WRAP_SIZE 0x8 +#define OMAP_ABE_D_VX_DL_8_48_FIR_WRAP_ADDR 0x9B4 +#define OMAP_ABE_D_VX_DL_8_48_FIR_WRAP_SIZE 0x8 +#define OMAP_ABE_D_BT_UL_8_48_FIR_WRAP_ADDR 0x9BC +#define OMAP_ABE_D_BT_UL_8_48_FIR_WRAP_SIZE 0x8 +#define OMAP_ABE_D_TASKSLIST_ADDR 0x9C4 +#define OMAP_ABE_D_TASKSLIST_SIZE 0x880 +#define OMAP_ABE_D_HW_TEST_ADDR 0x1244 +#define OMAP_ABE_D_HW_TEST_SIZE 0x28 +#define OMAP_ABE_D_TRACEBUFADR_HAL_ADDR 0x126C +#define OMAP_ABE_D_TRACEBUFADR_HAL_SIZE 0x4 +#define OMAP_ABE_D_DEBUG_FW_TASK_ADDR 0x1400 +#define OMAP_ABE_D_DEBUG_FW_TASK_SIZE 0x100 +#define OMAP_ABE_D_DEBUG_FIFO_ADDR 0x1500 +#define OMAP_ABE_D_DEBUG_FIFO_SIZE 0x60 +#define OMAP_ABE_D_DEBUG_FIFO_HAL_ADDR 0x1560 +#define OMAP_ABE_D_DEBUG_FIFO_HAL_SIZE 0x20 +#define OMAP_ABE_D_FWMEMINIT_ADDR 0x1580 +#define OMAP_ABE_D_FWMEMINIT_SIZE 0x3C0 +#define OMAP_ABE_D_FWMEMINITDESCR_ADDR 0x1940 +#define OMAP_ABE_D_FWMEMINITDESCR_SIZE 0x10 +#define OMAP_ABE_D_BT_DL_FIFO_ADDR 0x1C00 +#define OMAP_ABE_D_BT_DL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_BT_UL_FIFO_ADDR 0x1E00 +#define OMAP_ABE_D_BT_UL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_MM_EXT_OUT_FIFO_ADDR 0x2000 +#define OMAP_ABE_D_MM_EXT_OUT_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_MM_EXT_IN_FIFO_ADDR 0x2200 +#define OMAP_ABE_D_MM_EXT_IN_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_MM_UL2_FIFO_ADDR 0x2400 +#define OMAP_ABE_D_MM_UL2_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_DMIC_UL_FIFO_ADDR 0x2600 +#define OMAP_ABE_D_DMIC_UL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_MM_UL_FIFO_ADDR 0x2800 +#define OMAP_ABE_D_MM_UL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_MM_DL_FIFO_ADDR 0x2A00 +#define OMAP_ABE_D_MM_DL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_TONES_DL_FIFO_ADDR 0x2C00 +#define OMAP_ABE_D_TONES_DL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_VIB_DL_FIFO_ADDR 0x2E00 +#define OMAP_ABE_D_VIB_DL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_DEBUG_HAL_TASK_ADDR 0x3000 +#define OMAP_ABE_D_DEBUG_HAL_TASK_SIZE 0x800 +#define OMAP_ABE_D_MCPDM_DL_FIFO_ADDR 0x3800 +#define OMAP_ABE_D_MCPDM_DL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_MCPDM_UL_FIFO_ADDR 0x3A00 +#define OMAP_ABE_D_MCPDM_UL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_VX_UL_FIFO_ADDR 0x3C00 +#define OMAP_ABE_D_VX_UL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_VX_DL_FIFO_ADDR 0x3E00 +#define OMAP_ABE_D_VX_DL_FIFO_SIZE 0x1E0 +#define OMAP_ABE_D_PING_ADDR 0x4000 +#define OMAP_ABE_D_PING_SIZE 0x6000 +#define OMAP_ABE_D_PONG_ADDR 0xA000 +#define OMAP_ABE_D_PONG_SIZE 0x6000 diff --git a/sound/soc/omap/abe/abe_ext.h b/sound/soc/omap/abe/abe_ext.h new file mode 100644 index 0000000000000000000000000000000000000000..8fb1aacc5e60dbc3a584fc0ebd2a1e77eeb56c3a --- /dev/null +++ b/sound/soc/omap/abe/abe_ext.h @@ -0,0 +1,242 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_EXT_H_ +#define _ABE_EXT_H_ + +/* + * OS DEPENDENT MMU CONFIGURATION + */ +#define ABE_PMEM_BASE_OFFSET_MPU 0xe0000 +#define ABE_CMEM_BASE_OFFSET_MPU 0xa0000 +#define ABE_SMEM_BASE_OFFSET_MPU 0xc0000 +#define ABE_DMEM_BASE_OFFSET_MPU 0x80000 +#define ABE_ATC_BASE_OFFSET_MPU 0xf1000 +/* default base address for io_base */ +#define ABE_DEFAULT_BASE_ADDRESS_L3 0x49000000L +#define ABE_DEFAULT_BASE_ADDRESS_L4 0x40100000L +#define ABE_DEFAULT_BASE_ADDRESS_DEFAULT ABE_DEFAULT_BASE_ADDRESS_L3 +/* + * HARDWARE AND PERIPHERAL DEFINITIONS + */ +/* PMEM SIZE in bytes (1024 words of 64 bits: : #32bits words x 4)*/ +#define ABE_PMEM_SIZE 8192 +/* CMEM SIZE in bytes (2048 coeff : #32bits words x 4)*/ +#define ABE_CMEM_SIZE 8192 +/* SMEM SIZE in bytes (3072 stereo samples : #32bits words x 4)*/ +#define ABE_SMEM_SIZE 24576 +/* DMEM SIZE in bytes */ +#define ABE_DMEM_SIZE 65536L +/* ATC REGISTERS SIZE in bytes */ +#define ABE_ATC_DESC_SIZE 512 +/* holds the MCU Irq signal */ +#define ABE_MCU_IRQSTATUS_RAW 0x24 +/* status : clear the IRQ */ +#define ABE_MCU_IRQSTATUS 0x28 +/* holds the DSP Irq signal */ +#define ABE_DSP_IRQSTATUS_RAW 0x4C +/* holds the DMA req lines to the sDMA */ +#define ABE_DMASTATUS_RAW 0x84 +#define EVENT_GENERATOR_COUNTER 0x68 +/* PLL output/desired sampling rate = (32768 * 6000)/96000 */ +#define EVENT_GENERATOR_COUNTER_DEFAULT (2048-1) +/* PLL output/desired sampling rate = (32768 * 6000)/88200 */ +#define EVENT_GENERATOR_COUNTER_44100 (2228-1) +/* start / stop the EVENT generator */ +#define EVENT_GENERATOR_START 0x6C +#define EVENT_GENERATOR_ON 1 +#define EVENT_GENERATOR_OFF 0 +/* selection of the EVENT generator source */ +#define EVENT_SOURCE_SELECTION 0x70 +#define EVENT_SOURCE_DMA 0 +#define EVENT_SOURCE_COUNTER 1 +/* selection of the ABE DMA req line from ATC */ +#define AUDIO_ENGINE_SCHEDULER 0x74 +#define ABE_ATC_DMIC_DMA_REQ 1 +#define ABE_ATC_MCPDMDL_DMA_REQ 2 +#define ABE_ATC_MCPDMUL_DMA_REQ 3 +/* Direction=0 means input from ABE point of view */ +#define ABE_ATC_DIRECTION_IN 0 +/* Direction=1 means output from ABE point of view */ +#define ABE_ATC_DIRECTION_OUT 1 +/* + * DMA requests + */ +/*Internal connection doesn't connect at ABE boundary */ +#define External_DMA_0 0 +/*Transmit request digital microphone */ +#define DMIC_DMA_REQ 1 +/*Multichannel PDM downlink */ +#define McPDM_DMA_DL 2 +/*Multichannel PDM uplink */ +#define McPDM_DMA_UP 3 +/*MCBSP module 1 - transmit request */ +#define MCBSP1_DMA_TX 4 +/*MCBSP module 1 - receive request */ +#define MCBSP1_DMA_RX 5 +/*MCBSP module 2 - transmit request */ +#define MCBSP2_DMA_TX 6 +/*MCBSP module 2 - receive request */ +#define MCBSP2_DMA_RX 7 +/*MCBSP module 3 - transmit request */ +#define MCBSP3_DMA_TX 8 +/*MCBSP module 3 - receive request */ +#define MCBSP3_DMA_RX 9 +/*SLIMBUS module 1 - transmit request channel 0 */ +#define SLIMBUS1_DMA_TX0 10 +/*SLIMBUS module 1 - transmit request channel 1 */ +#define SLIMBUS1_DMA_TX1 11 +/*SLIMBUS module 1 - transmit request channel 2 */ +#define SLIMBUS1_DMA_TX2 12 +/*SLIMBUS module 1 - transmit request channel 3 */ +#define SLIMBUS1_DMA_TX3 13 +/*SLIMBUS module 1 - transmit request channel 4 */ +#define SLIMBUS1_DMA_TX4 14 +/*SLIMBUS module 1 - transmit request channel 5 */ +#define SLIMBUS1_DMA_TX5 15 +/*SLIMBUS module 1 - transmit request channel 6 */ +#define SLIMBUS1_DMA_TX6 16 +/*SLIMBUS module 1 - transmit request channel 7 */ +#define SLIMBUS1_DMA_TX7 17 +/*SLIMBUS module 1 - receive request channel 0 */ +#define SLIMBUS1_DMA_RX0 18 +/*SLIMBUS module 1 - receive request channel 1 */ +#define SLIMBUS1_DMA_RX1 19 +/*SLIMBUS module 1 - receive request channel 2 */ +#define SLIMBUS1_DMA_RX2 20 +/*SLIMBUS module 1 - receive request channel 3 */ +#define SLIMBUS1_DMA_RX3 21 +/*SLIMBUS module 1 - receive request channel 4 */ +#define SLIMBUS1_DMA_RX4 22 +/*SLIMBUS module 1 - receive request channel 5 */ +#define SLIMBUS1_DMA_RX5 23 +/*SLIMBUS module 1 - receive request channel 6 */ +#define SLIMBUS1_DMA_RX6 24 +/*SLIMBUS module 1 - receive request channel 7 */ +#define SLIMBUS1_DMA_RX7 25 +/*McASP - Data transmit DMA request line */ +#define McASP1_AXEVT 26 +/*McASP - Data receive DMA request line */ +#define McASP1_AREVT 29 +/*DUMMY FIFO @@@ */ +#define _DUMMY_FIFO_ 30 +/*DMA of the Circular buffer peripheral 0 */ +#define CBPr_DMA_RTX0 32 +/*DMA of the Circular buffer peripheral 1 */ +#define CBPr_DMA_RTX1 33 +/*DMA of the Circular buffer peripheral 2 */ +#define CBPr_DMA_RTX2 34 +/*DMA of the Circular buffer peripheral 3 */ +#define CBPr_DMA_RTX3 35 +/*DMA of the Circular buffer peripheral 4 */ +#define CBPr_DMA_RTX4 36 +/*DMA of the Circular buffer peripheral 5 */ +#define CBPr_DMA_RTX5 37 +/*DMA of the Circular buffer peripheral 6 */ +#define CBPr_DMA_RTX6 38 +/*DMA of the Circular buffer peripheral 7 */ +#define CBPr_DMA_RTX7 39 +/* + * ATC DESCRIPTORS - DESTINATIONS + */ +#define DEST_DMEM_access 0x00 +#define DEST_MCBSP1_ TX 0x01 +#define DEST_MCBSP2_ TX 0x02 +#define DEST_MCBSP3_TX 0x03 +#define DEST_SLIMBUS1_TX0 0x04 +#define DEST_SLIMBUS1_TX1 0x05 +#define DEST_SLIMBUS1_TX2 0x06 +#define DEST_SLIMBUS1_TX3 0x07 +#define DEST_SLIMBUS1_TX4 0x08 +#define DEST_SLIMBUS1_TX5 0x09 +#define DEST_SLIMBUS1_TX6 0x0A +#define DEST_SLIMBUS1_TX7 0x0B +#define DEST_MCPDM_DL 0x0C +#define DEST_MCASP_TX0 0x0D +#define DEST_MCASP_TX1 0x0E +#define DEST_MCASP_TX2 0x0F +#define DEST_MCASP_TX3 0x10 +#define DEST_EXTPORT0 0x11 +#define DEST_EXTPORT1 0x12 +#define DEST_EXTPORT2 0x13 +#define DEST_EXTPORT3 0x14 +#define DEST_MCPDM_ON 0x15 +#define DEST_CBP_CBPr 0x3F +/* + * ATC DESCRIPTORS - SOURCES + */ +#define SRC_DMEM_access 0x0 +#define SRC_MCBSP1_ RX 0x01 +#define SRC_MCBSP2_RX 0x02 +#define SRC_MCBSP3_RX 0x03 +#define SRC_SLIMBUS1_RX0 0x04 +#define SRC_SLIMBUS1_RX1 0x05 +#define SRC_SLIMBUS1_RX2 0x06 +#define SRC_SLIMBUS1_RX3 0x07 +#define SRC_SLIMBUS1_RX4 0x08 +#define SRC_SLIMBUS1_RX5 0x09 +#define SRC_SLIMBUS1_RX6 0x0A +#define SRC_SLIMBUS1_RX7 0x0B +#define SRC_DMIC_UP 0x0C +#define SRC_MCPDM_UP 0x0D +#define SRC_MCASP_RX0 0x0E +#define SRC_MCASP_RX1 0x0F +#define SRC_MCASP_RX2 0x10 +#define SRC_MCASP_RX3 0x11 +#define SRC_CBP_CBPr 0x3F +#endif/* _ABE_EXT_H_ */ diff --git a/sound/soc/omap/abe/abe_firmware.c b/sound/soc/omap/abe/abe_firmware.c new file mode 100644 index 0000000000000000000000000000000000000000..ce70be3b32eecc1ace3e2c0ba1a5bea0a152a0ba --- /dev/null +++ b/sound/soc/omap/abe/abe_firmware.c @@ -0,0 +1,25886 @@ +0xabeabe00, +0x00000000, +0x000187fc, +0x00000c60, +0x00000001, +0x00009450, +0x00000006, +0x20314c44, +0x61757145, +0x657a696c, +0x00000072, +0x00000000, +0x00000004, +0x00000019, +0x74616c46, +0x73657220, +0x736e6f70, +0x00000065, +0x00000000, +0x68676948, +0x7361702d, +0x64302073, +0x00000042, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426432, +0x00000000, +0x68676948, +0x7361702d, +0x322d2073, +0x00426430, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x20324c44, +0x7466654c, +0x75714520, +0x7a696c61, +0x00007265, +0x00000004, +0x00000019, +0x74616c46, +0x73657220, +0x736e6f70, +0x00000065, +0x00000000, +0x68676948, +0x7361702d, +0x64302073, +0x00000042, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426432, +0x00000000, +0x68676948, +0x7361702d, +0x322d2073, +0x00426430, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x20324c44, +0x68676952, +0x71452074, +0x696c6175, +0x0072657a, +0x00000004, +0x00000019, +0x74616c46, +0x73657220, +0x736e6f70, +0x00000065, +0x00000000, +0x68676948, +0x7361702d, +0x64302073, +0x00000042, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426432, +0x00000000, +0x68676948, +0x7361702d, +0x322d2073, +0x00426430, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x65646953, +0x656e6f74, +0x75714520, +0x7a696c61, +0x00007265, +0x00000004, +0x00000009, +0x74616c46, +0x73657220, +0x736e6f70, +0x00000065, +0x00000000, +0x68676948, +0x7361702d, +0x64302073, +0x00000042, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426432, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426438, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x43494d41, +0x75714520, +0x7a696c61, +0x00007265, +0x00000000, +0x00000003, +0x00000013, +0x68676948, +0x7361702d, +0x64302073, +0x00000042, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426432, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426438, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x43494d44, +0x75714520, +0x7a696c61, +0x00007265, +0x00000000, +0x00000003, +0x00000013, +0x68676948, +0x7361702d, +0x64302073, +0x00000042, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426432, +0x00000000, +0x68676948, +0x7361702d, +0x312d2073, +0x00426438, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xff8cbb51, +0x000ace72, +0xfff53192, +0x007344b1, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xffc65da8, +0x00567385, +0xffa98c7d, +0x0039a258, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xffe8f244, +0x00452938, +0xffbad6c8, +0x00170dbc, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xff8cbb51, +0x000ace72, +0xfff53192, +0x007344b1, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xffc65da8, +0x00567385, +0xffa98c7d, +0x0039a258, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xffe8f244, +0x00452938, +0xffbad6c8, +0x00170dbc, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xff8cbb51, +0x000ace72, +0xfff53192, +0x007344b1, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xffc65da8, +0x00567385, +0xffa98c7d, +0x0039a258, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xffe8f244, +0x00452938, +0xffbad6c8, +0x00170dbc, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0xff8cbb51, +0x000ace72, +0xfff53192, +0x007344b1, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0xffc65da8, +0x00567385, +0xffa98c7d, +0x0039a258, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0x00000000, +0xffe8f244, +0x00452938, +0xffbad6c8, +0x00170dbc, +0x00000000, +0x0067cd91, +0xfff596e6, +0x000b29a2, +0xffc1248b, +0xfffd1080, +0xfffaca4c, +0xfffab048, +0xfffdb0ac, +0x00024f54, +0x00054fb8, +0x000535b4, +0x0002ef80, +0x003edb7b, +0x001d92ec, +0xff962b59, +0x000bd422, +0xffe48132, +0x002dbdc2, +0xffc7a94a, +0x0033fbe6, +0xffdd3502, +0x000fea26, +0xfff0490f, +0xffd10817, +0xffaca4df, +0xffab0493, +0xffdb0acb, +0x0024f537, +0x0054fb6f, +0x00535b23, +0x002ef7eb, +0x000fb6f3, +0x001d930c, +0xff962afd, +0x000bd42a, +0xffe48122, +0x002dbdda, +0xffc7a932, +0x0033fbf6, +0xffdd34fa, +0x000fea26, +0xfff82487, +0xffe8840b, +0xffd6526f, +0xffd5824b, +0xffed8567, +0x00127a9b, +0x002a7db7, +0x0029ad93, +0x00177bf7, +0x0007db7b, +0x001d930c, +0xff962afd, +0x000bd42a, +0xffe48122, +0x002dbdda, +0xffc7a932, +0x0033fbf6, +0xffdd34fa, +0x000fea26, +0xffc1248b, +0xfffd1080, +0xfffaca4c, +0xfffab048, +0xfffdb0ac, +0x00024f54, +0x00054fb8, +0x000535b4, +0x0002ef80, +0x003edb7b, +0x001d92ec, +0xff962b59, +0x000bd422, +0xffe48132, +0x002dbdc2, +0xffc7a94a, +0x0033fbe6, +0xffdd3502, +0x000fea26, +0xfff0490f, +0xffd10817, +0xffaca4df, +0xffab0493, +0xffdb0acb, +0x0024f537, +0x0054fb6f, +0x00535b23, +0x002ef7eb, +0x000fb6f3, +0x001d930c, +0xff962afd, +0x000bd42a, +0xffe48122, +0x002dbdda, +0xffc7a932, +0x0033fbf6, +0xffdd34fa, +0x000fea26, +0xfff82487, +0xffe8840b, +0xffd6526f, +0xffd5824b, +0xffed8567, +0x00127a9b, +0x002a7db7, +0x0029ad93, +0x00177bf7, +0x0007db7b, +0x001d930c, +0xff962afd, +0x000bd42a, +0xffe48122, +0x002dbdda, +0xffc7a932, +0x0033fbf6, +0xffdd34fa, +0x000fea26, +0x00009450, +0x00002000, +0x00001b80, +0x00010000, +0x00004c68, +0x1600200f, +0x0a000940, +0x08200000, +0x08200000, +0x07800000, +0x160075ce, +0x014000e0, +0x014000e1, +0x014000e2, +0x014000e3, +0x014000e4, +0x014000e5, +0x014000e6, +0x014000e7, +0x014000e8, +0x014000e9, +0x014000ea, +0x014000eb, +0x014000ec, +0x014000ed, +0x014000ef, +0x014000ef, +0x144000e4, +0x9e000000, +0x0a200e10, +0x9e000040, +0x0a200e10, +0x9e000080, +0x0a200e10, +0x9e0000c0, +0x0a200e10, +0x9e080000, +0x0a200e10, +0x9e080100, +0x0a200e10, +0x9e080200, +0x0a200e10, +0x9e080300, +0x0a200e10, +0x9e080400, +0x0a200e10, +0x9e080500, +0x0a200e10, +0x9e080600, +0x0a200e10, +0x9e080700, +0x0a200e10, +0x9c050800, +0x0a200e10, +0x16000010, +0x16000001, +0x17000102, +0x01400042, +0x17800103, +0x01400043, +0x98020000, +0x160003c6, +0x07800000, +0x07800000, +0x9c03b660, +0x0a0003f0, +0x9d0c8118, +0x07800000, +0x9c0c07b0, +0x9f16001a, +0x9f12021a, +0x9f12031a, +0x9f12051a, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x988003d0, +0x07800000, +0x9d0c8118, +0x08200000, +0x160003c6, +0x07800000, +0x07800000, +0x9c03b660, +0x0a000540, +0x9d0c8158, +0x07800000, +0x9c0c07b0, +0x9f16001a, +0x9f12021a, +0x9f12031a, +0x9f12051a, +0x9f040040, +0x9c0c07b0, +0x9f03fc10, +0x9f092020, +0x9f082070, +0x98800520, +0x07800000, +0x9d0c8158, +0x08200000, +0x160003c6, +0x07800000, +0x07800000, +0x9c03b660, +0x0a000690, +0x9d0c8118, +0x07800000, +0x9c0c07b0, +0x9f15001a, +0x9f11041a, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x98800670, +0x07800000, +0x9d0c8118, +0x08200000, +0x400002c0, +0x048002ff, +0x000000c5, +0x000004c6, +0x9c028000, +0x400006c7, +0x12000155, +0x013ffefe, +0xc00008c4, +0x1e080000, +0x020005de, +0x00000ac3, +0xdc02b160, +0x04c3ff2d, +0xdc01ba70, +0x128002dd, +0xdc02a440, +0x048fffdd, +0x9c061830, +0x0b200000, +0x003ffefe, +0x000002c4, +0x400004c5, +0x048ffeff, +0x000006c6, +0x000008c7, +0x9d02a040, +0x9d02a950, +0x9d01b260, +0x9d02bc70, +0x08200000, +0x16006906, +0x00000068, +0x16003fc5, +0x01000058, +0x160069ca, +0x000000a9, +0x16003fc6, +0x00000068, +0x0400089b, +0x4000009c, +0x1600694e, +0x410000ec, +0x0600000c, +0x1601270d, +0x0a800a40, +0x0a200750, +0x04800299, +0x410000a9, +0x05c00b90, +0x4ac009d0, +0x04a01085, +0x16006a04, +0x40000047, +0x16006a8e, +0x04200599, +0x400000e1, +0x04800177, +0x010000a9, +0x41000047, +0x04a00111, +0x410000e1, +0x06000001, +0x4aa00c20, +0x16006a4d, +0x400000d6, +0x16004fc9, +0x400002d7, +0x04800166, +0x410000a9, +0x04900077, +0x010000d6, +0x010002d7, +0x16006906, +0x00000068, +0x16003fc5, +0x01000058, +0x1600c005, +0x16007541, +0x16000002, +0x40000011, +0x16007500, +0x9e0e0550, +0xdd140530, +0x160ffff4, +0x41000002, +0x06000001, +0x08400000, +0x01000004, +0x9d140550, +0x0a800980, +0x0a000c20, +0x048006ff, +0x013ffafb, +0x013ffcfc, +0x413ffefe, +0x04a0020b, +0x004002bc, +0x0600000c, +0x1601270d, +0x0a800dc0, +0x0a200750, +0x0a000d60, +0x003ffefe, +0x003ffcfc, +0x003ffafb, +0x048ffaff, +0x08200000, +0x07800000, +0x01400040, +0x01400041, +0x01400042, +0x01400043, +0x08200000, +0x160004a4, +0x160004b5, +0x160004c6, +0x16000007, +0x9c032040, +0x9c032950, +0x9c033260, +0x9e0f0070, +0x9e0f0170, +0x9e0f0270, +0x9d032040, +0x9d032950, +0x9d033260, +0x08200000, +0x9f158048, +0x9c0c07b0, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x07800000, +0x07800000, +0x9d088118, +0x98800f50, +0x08200000, +0x9f158048, +0x9f040040, +0x9c0c07b0, +0x9f03fc10, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x07800000, +0x07800000, +0x9d188148, +0x98801010, +0x08200000, +0x9f158048, +0x9c0c07b0, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x07800000, +0x9d188108, +0x9d188148, +0x988010f0, +0x08200000, +0x9f158048, +0x9c0c07b0, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x07800000, +0x9d1e8108, +0x9d1e8148, +0x988011b0, +0x08200000, +0x9f158018, +0x9f040010, +0x9c0c07b0, +0x9f03fc10, +0x9f092020, +0x9f082030, +0x9c0c07b0, +0x9f092060, +0x9f082070, +0x9d1e8108, +0x98801270, +0x08200000, +0x9c080048, +0x9f1d0010, +0x07800000, +0x07800000, +0x9d0c8118, +0x98801330, +0x08200000, +0x9c180028, +0x9f1d0010, +0x07800000, +0x07800000, +0x9d0c8108, +0x988013a0, +0x08200000, +0x9c180068, +0x9c180028, +0x9f1d0010, +0x07800000, +0x07800000, +0x9d0c8148, +0x98801410, +0x08200000, +0x9c1e0048, +0x9c1e0008, +0x9f1d0010, +0x07800000, +0x07800000, +0x9d0c8148, +0x98801490, +0x08200000, +0x9c1e0008, +0x9f1d0010, +0x07800000, +0x07800000, +0x9d0c8108, +0x98801510, +0x08200000, +0x160004a4, +0x160004b5, +0x160004c6, +0x160000bd, +0x9c032340, +0x9c032c50, +0x9c033560, +0x9c180028, +0x9c180068, +0x9f1d0010, +0x9c1800a8, +0x9c1800e8, +0x9f1d00b0, +0x07800000, +0x9d0c8318, +0x9d0c84b8, +0x9c180028, +0x9c180068, +0x9f1d0010, +0x07800000, +0x07800000, +0x9d0c8518, +0x988015f0, +0x9d032340, +0x9d032c50, +0x9d033560, +0x08200000, +0x160003c2, +0x16000504, +0x16000515, +0x16000526, +0x9c011720, +0x9c03a440, +0x9c03ad50, +0x9c03b660, +0x160000bd, +0x9f158418, +0x9c0c02b0, +0x9f091020, +0x9f081030, +0x9c0c02b0, +0x9f091060, +0x9f081070, +0x07800000, +0x9d180108, +0x9d180148, +0x9f158518, +0x9c0c02b0, +0x9f091020, +0x9f081030, +0x9c0c02b0, +0x9f091060, +0x9f081070, +0x07800000, +0x9d180108, +0x9d180148, +0x9c0c0618, +0x07800000, +0x07800000, +0x9d180108, +0x9d180148, +0x988017c0, +0x9d032440, +0x9d032d50, +0x9d033660, +0x08200000, +0x1600000d, +0x9e0f00d0, +0x00800e0d, +0x9f158038, +0x07800000, +0x04a002dd, +0x9d188108, +0x9f158038, +0x07800000, +0x98801a00, +0x9d188108, +0x08200000, +0x9e088100, +0x07800000, +0x07800000, +0x12800277, +0x04c0ff77, +0x04a00174, +0x12800266, +0x04c0ff66, +0x04000645, +0x060ffff4, +0x17000454, +0x12000244, +0x9e0f0140, +0x07800000, +0x07800000, +0x9c0c0118, +0x07800000, +0x07800000, +0x9d0c8118, +0x98801b80, +0x08200000, +0x08200000, +0x08200000, +0x08200000, +0x9c038600, +0x07800000, +0x07800000, +0x9c180770, +0xdc100348, +0x160fff05, +0x9f000810, +0x9f118412, +0x9f001010, +0x9f002810, +0x9c0c00b8, +0x160ffd80, +0x9d0c8410, +0x9f1d8012, +0x9f001810, +0x9f0400d0, +0x9c0c0210, +0x16000204, +0xdd0e00b0, +0x16000005, +0x9f1d80b2, +0x9f0000b0, +0x9f0020b0, +0x9f0400d0, +0x05800560, +0x0a801da0, +0x9c0c0510, +0x0a001db0, +0x9c0c0618, +0x16000014, +0x9d0c81e8, +0x9d0c8148, +0x0a801e20, +0x9c0c05b0, +0x9c0c0510, +0x0a001e40, +0x9c0c06b8, +0x9c0c0618, +0x07800000, +0x9d0c81e8, +0x9d0c8148, +0x98801c20, +0x9d180750, +0x08200000, +0x9d019220, +0x048002ff, +0x14400004, +0x413ffefe, +0x16000040, +0x9c010910, +0x0a204610, +0x14400040, +0x9c030810, +0x16000171, +0x9c009f30, +0x9c019220, +0x0a204110, +0x9c009830, +0x003ffefe, +0x048ffeff, +0x08200000, +0x40001807, +0x160000bd, +0x05800370, +0x9e088000, +0x0ba00000, +0x41801003, +0x14400073, +0x9e088200, +0x16001806, +0x16000005, +0x04200377, +0x05800570, +0x17800566, +0x04000677, +0x04a09076, +0x05800560, +0x16000184, +0x4ac021e0, +0x04a0c077, +0x160003d6, +0x05800570, +0x9d02b060, +0x0ac02130, +0x01801005, +0x0a002170, +0x9c1800a8, +0x9d02b060, +0x07800000, +0xa0062019, +0x07800000, +0x9c02b060, +0x9d0c8118, +0x98802140, +0x07800000, +0x08200000, +0x01000015, +0x9e0f0050, +0x9e0f0450, +0x9e0f0140, +0x08200000, +0x40001807, +0x160000bd, +0x05800370, +0x9e088000, +0x0ba00000, +0x048002ff, +0x41801003, +0x14400073, +0x013ffefe, +0x9e088200, +0x16001806, +0x16000005, +0x04200377, +0x05800570, +0x17800566, +0x04000677, +0x04a09076, +0x05800560, +0x00000212, +0x4ac027c0, +0x04a0c077, +0x05800570, +0x00000413, +0x4ac023d0, +0x04800816, +0x01801005, +0x00001017, +0x9e0e0760, +0xdc029e30, +0x16000a44, +0x9e0e0470, +0xdc029320, +0x160000bd, +0x9c1807a4, +0x9c1807e0, +0xdc03a540, +0x160003d2, +0x40000613, +0x16000184, +0x00000010, +0x9d029020, +0x0a002500, +0x9d0c8118, +0x9d029020, +0x9f0608b0, +0xa0062019, +0xdc029020, +0x04a00133, +0xdd0c0618, +0x04a00100, +0x9f040020, +0xdc100388, +0x05800050, +0x9f040070, +0x9f1185b2, +0x0ae02650, +0xdd100380, +0x05800350, +0x9e0f0450, +0x4ae02680, +0x160000b0, +0x9f0304b0, +0x9d029020, +0x9d100380, +0x4a002500, +0x16001bb3, +0x9d100380, +0x9c1800a8, +0x07800000, +0x9f1d8010, +0x9f138612, +0x9f1f8012, +0x988024d0, +0x07800000, +0x9d0c8118, +0x04800814, +0x9e0e0740, +0x01000613, +0x01000010, +0x00000413, +0x9e088400, +0x9d188704, +0x9d188740, +0x01001014, +0x9d029e30, +0x003ffefe, +0x048ffeff, +0x9d029e30, +0x08200000, +0x9f030410, +0x160003c4, +0x160000b0, +0x16001bb3, +0x9e0f0250, +0x9e0f0450, +0x9e0f0040, +0x0a0026e0, +0x40000024, +0x048002ff, +0x41000224, +0x16000005, +0x413ffefe, +0x04000400, +0x9e0f0150, +0x01000025, +0x0a202bb0, +0x403ffefe, +0x16000007, +0x9e0f0170, +0x048ffeff, +0x08200000, +0x40000024, +0x048002ff, +0x41000224, +0x16000005, +0x413ffefe, +0x16000016, +0x41800dc6, +0x04000400, +0x9e0f0150, +0x01000025, +0x0a2034f0, +0x403ffefe, +0x16000007, +0x9e0f0170, +0x048ffeff, +0x08200000, +0x048002ff, +0x413ffefe, +0x16000005, +0x01000025, +0x0a202bb0, +0x40000024, +0x16000005, +0x403ffefe, +0x04200454, +0x41000224, +0x048ffeff, +0x08200000, +0x048002ff, +0x413ffefe, +0x16000005, +0x01000025, +0x01800dc5, +0x0a2034f0, +0x40000024, +0x16000005, +0x403ffefe, +0x04200454, +0x41000224, +0x048ffeff, +0x08200000, +0x048008ff, +0x413ff8f8, +0x1440000d, +0x9c038e10, +0x413ffaf9, +0x04a001dd, +0x413ffcfa, +0x16000001, +0x413ffefb, +0x160000f0, +0x9c100400, +0x9c100480, +0x9c1d06c4, +0x9f085030, +0x9c180674, +0x9c180650, +0x058001a0, +0x0aa030b0, +0x04800144, +0x04400044, +0x05800040, +0x0aa02df0, +0x05800160, +0x0ac02d90, +0x9e090000, +0x07800000, +0x07800000, +0x9e0d0500, +0x9d040508, +0x0a002f80, +0x9d040008, +0x9e090000, +0x07800000, +0x9d040008, +0x9e0d0500, +0x0a002f80, +0x9d040008, +0x9e090000, +0x07800000, +0x07800000, +0x9e0d0500, +0x1280010a, +0x048001a9, +0x05800940, +0x0aa02f80, +0x05800160, +0x40000628, +0x160ffff9, +0x0ac02f10, +0x05800180, +0x0ae02f80, +0x160ffff6, +0x160ffff7, +0x0a002f50, +0x05800810, +0x0ae02f80, +0x16000016, +0x16000007, +0x9d044690, +0x04a00144, +0x9d180674, +0x05800160, +0x9d180654, +0x0ac02ff0, +0x0420040a, +0x04a001ab, +0x4a003020, +0x044000bb, +0x0480014b, +0x044000bb, +0x1440004a, +0x120001aa, +0x42000a38, +0x120001bb, +0x42000b39, +0x12000288, +0x12000299, +0x9e0e8280, +0xca0031c0, +0x1e0e8390, +0xdd040604, +0x05800160, +0x0ac03160, +0x9d040008, +0x9e090000, +0x07800000, +0x05800040, +0x9e0d0500, +0x0aa031c0, +0x9d040508, +0x0a0031c0, +0x9e090000, +0x05800040, +0x9d040008, +0x9e0d0500, +0x0a8031c0, +0x9d040508, +0x9c1d06c4, +0xdc1d0644, +0x1f0400b0, +0x9c100700, +0xdc1d06c4, +0x1f040010, +0x9d108480, +0x9f0940b0, +0x9d108700, +0x00000cc9, +0x06000008, +0x0aa033c0, +0xdc1d0684, +0x14400005, +0xdc1d0604, +0x160fff8a, +0x04a00255, +0xdd108480, +0x16000017, +0xdd108700, +0x160ffff8, +0x05800540, +0x0aa03380, +0x05800160, +0x0ac03370, +0x01000027, +0x0a003380, +0x01000028, +0x9e088000, +0xa0054dba, +0xa005c81a, +0x0a003450, +0x9e088000, +0xa0054dba, +0xa005c81a, +0x160fffaa, +0x9f1f80b0, +0x9f1e0010, +0x9f040020, +0x9f040070, +0x9f020810, +0x9d0446a0, +0x9e0f0070, +0x9d0c8118, +0x98802c50, +0x003ffefb, +0x003ffcfa, +0x003ffaf9, +0x003ff8f8, +0x048ff8ff, +0x08200000, +0x048008ff, +0x413ff8f8, +0x1440000d, +0x9c038e10, +0x413ffaf9, +0x04a001dd, +0x413ffcfa, +0x16000001, +0x413ffefb, +0x04a00100, +0x9c100400, +0x9c100480, +0x9c1d06c4, +0x9f085030, +0x9c180674, +0x9c180650, +0x058001a0, +0x4aa03a20, +0x160000f7, +0x04800144, +0x04400744, +0x05800740, +0x0aa03740, +0x05800160, +0x0ac036e0, +0x9e090000, +0x07800000, +0x07800000, +0x9e0d0500, +0x9d040508, +0x0a0038e0, +0x9d040008, +0x9e090000, +0x07800000, +0x9d040008, +0x9e0d0500, +0x0a0038e0, +0x9d040008, +0x9e090000, +0x160000f7, +0x07800000, +0x9e0d0500, +0x1280017a, +0x048001a9, +0x05800940, +0x0aa038e0, +0x05800160, +0x00000ec8, +0x40000688, +0x160ffff9, +0x0ac03870, +0x05800810, +0x0ae038e0, +0x160ffff6, +0x160ffff7, +0x0a0038b0, +0x05800180, +0x0ae038e0, +0x16000016, +0x16000007, +0x9d044690, +0x04a00144, +0x9d180674, +0x05800160, +0x9d180654, +0x4ac03960, +0x160000f7, +0x0420047a, +0x04a001ab, +0x4a003990, +0x044007bb, +0x0480014b, +0x044007bb, +0x1440004a, +0x120001aa, +0x42000a38, +0x120001bb, +0x42000b39, +0x12000288, +0x12000299, +0x9e0e8280, +0xca003b30, +0x1e0e8390, +0xdd040604, +0x05800160, +0x0ac03ad0, +0x9d040008, +0x9e090000, +0x07800000, +0x060000f4, +0x9e0d0500, +0x0aa03b30, +0x9d040508, +0x0a003b30, +0x9e090000, +0x060000f4, +0x9d040008, +0x9e0d0500, +0x0a803b30, +0x9d040508, +0x060000f4, +0x0aa03db0, +0x9c1d0600, +0x9f065060, +0x9f020830, +0x9f095010, +0xdc1d0600, +0x160fffe9, +0x9f065060, +0x9f020c30, +0x0600000a, +0x0a803db0, +0x9f095010, +0x00800dcb, +0x16000028, +0x0600000a, +0x0aa03db0, +0x0600000b, +0x0a803d20, +0x0600000d, +0x0aa03db0, +0x9d044480, +0x9d044780, +0x9c100480, +0x9c100700, +0x9d044490, +0x9d044790, +0x9d044680, +0x9d108480, +0x9d108700, +0x0a003e30, +0x058000d0, +0x0aa03db0, +0x9d044490, +0x9c100700, +0x9d044790, +0x9d108480, +0x9d108700, +0x9d044480, +0x9d044780, +0x9c1d06c4, +0xdc1d0644, +0x1f0400b0, +0x9c100700, +0x9f040010, +0x9d108480, +0x07800000, +0x9d108700, +0x9c1d06c4, +0x07800000, +0x9f0940b0, +0x07800000, +0x00800cc9, +0x06000008, +0x0aa03fe0, +0xdc1d0684, +0x160000f5, +0xdc1d0604, +0x160fff8a, +0x04a00255, +0xdd108480, +0x16000017, +0xdd108700, +0x160ffff8, +0x05800540, +0x0aa03fa0, +0x05800160, +0x0ac03f90, +0x01000027, +0x0a003fa0, +0x01000028, +0x9e088000, +0xa0054dba, +0xa005c81a, +0x0a004070, +0x9e088000, +0xa0054dba, +0xa005c81a, +0x160fffaa, +0x9f1f80b0, +0x9f1e0010, +0x9f040020, +0x9f040070, +0x9f020810, +0x9d0446a0, +0x9e0f0070, +0x9d0c8118, +0x98803590, +0x003ffefb, +0x003ffcfa, +0x003ffaf9, +0x003ff8f8, +0x048ff8ff, +0x08200000, +0x9c0c0018, +0x1440001d, +0x04a001dd, +0x9d0c8318, +0x07800000, +0x9c0c0018, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c81b8, +0x9d0c02b8, +0x98804160, +0x07800000, +0xa00602ba, +0x07800000, +0x07800000, +0x9d0c81b8, +0x9d0c82b8, +0x08200000, +0x9c0c0018, +0x160000ad, +0x07800000, +0x9d0c8318, +0x07800000, +0x9c0c0018, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c81b8, +0x9d0c02b8, +0x07800000, +0x9c0c0018, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c02b8, +0x98804290, +0x9c0c0018, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c81b8, +0x9d0c02b8, +0x07800000, +0xa00602ba, +0x07800000, +0x07800000, +0x9d0c02b8, +0x08200000, +0x9c0c0038, +0x1440001d, +0x04a001dd, +0x9d0c8338, +0x07800000, +0xa00602ba, +0xa006821a, +0x9c0c0038, +0x07800000, +0x9d0c8298, +0x9d0c8338, +0x9d0c8198, +0x98804470, +0x07800000, +0xa00602ba, +0xa006821a, +0x07800000, +0x07800000, +0x9d0c8298, +0x9d0c8198, +0x08200000, +0xdc0c0018, +0x04a00201, +0x04a001dd, +0xdd040008, +0x06000001, +0x04a00111, +0x0aa04590, +0x9d0c8118, +0x98804570, +0x08200000, +0x9c0c02b0, +0x9c0c0018, +0x04a00205, +0x07800000, +0x9d0c8118, +0xdd0c81b8, +0x06000005, +0x04a00155, +0x0aa04660, +0x98804620, +0x08200000, +0x9c039e30, +0x07800000, +0x9c0c0018, +0x9f138510, +0x1600004d, +0x07800000, +0x9d0c8318, +0x07800000, +0x9c0c0510, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c81b8, +0x9d0c02b8, +0x07800000, +0x9c0c0018, +0x9f138510, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c81b8, +0x9d0c02b8, +0x98804740, +0x9c0c0510, +0xa00602ba, +0x07800000, +0x9d0c8318, +0x9d0c81b8, +0x9d0c02b8, +0x07800000, +0xa00602ba, +0x07800000, +0x07800000, +0x9d0c81b8, +0x9d0c82b8, +0x08200000, +0x9c0c0018, +0x1440001d, +0x04a001dd, +0x9d0c8218, +0x07800000, +0x9c0c0018, +0xa00582ba, +0x07800000, +0x07800000, +0x9d0c81b8, +0x98804960, +0x1440001d, +0x9d0c8218, +0x04a001dd, +0xa00582ba, +0x07800000, +0x07800000, +0x9d0c81b8, +0x988049e0, +0x08200000, +0x9f160028, +0x9f168298, +0x04a001dd, +0x07800000, +0x9d0c8128, +0x07800000, +0x9f160028, +0x9f168298, +0x98804a80, +0x9d0c8128, +0x08200000, +0x9f160020, +0x9f168098, +0x9c0c03b0, +0x9f092020, +0x9f082030, +0x9c0c03b0, +0x9f092060, +0x9f082070, +0x07800000, +0x07800000, +0x9d0c8108, +0x9d0c8258, +0x98804af0, +0x08200000, +0x9f160020, +0x9f168098, +0x9c0c02b0, +0x9f092020, +0x9f082030, +0x9c0c02b0, +0x9f092060, +0x9f082070, +0x07800000, +0x07800000, +0x9d0c8118, +0x98804bd0, +0x08200000, +0x9d008810, +0x1280020d, +0x07800000, +0x9c038810, +0x9e0e0620, +0x04a001dd, +0x9f16801a, +0x9f12011a, +0x9f039810, +0x9f026810, +0x9f118610, +0x9f1680ba, +0x9f1201ba, +0x9f0398b0, +0x9f0268b0, +0x9f1186b0, +0x9d0c8718, +0x9d108248, +0x9d108208, +0x9d0c87b8, +0x9d1082c8, +0x9d108288, +0x98804d00, +0x08200000, +0x00000003, +0x00000205, +0x1440001d, +0x9c039830, +0x9c03aa50, +0x07800000, +0x9c0c0018, +0x9c0c02b8, +0x07800000, +0x07800000, +0x9d0c8128, +0x98804e80, +0x08200000, +0x048002ff, +0x013ffefe, +0x00801605, +0x16012ac3, +0x12000155, +0x0200035e, +0x0b200000, +0x00800405, +0x16012ac3, +0x12000155, +0x0200035e, +0x0b200000, +0x00801705, +0x16012ac3, +0x12000155, +0x0200035e, +0x0b200000, +0x003ffefe, +0x048ffeff, +0x07800000, +0x08200000, +0x048004ff, +0x16000181, +0x40800a0d, +0x04000101, +0x00800b03, +0x00000212, +0x00000017, +0x9e0e0420, +0x00000416, +0xdc180404, +0x06000003, +0x9c180480, +0x0aa054c0, +0x9c052b20, +0x9c042820, +0x9c023970, +0x40800e04, +0x16000005, +0x40800503, +0x0600000d, +0x9d01b060, +0x4a805220, +0x0400033d, +0x04200427, +0x04200d77, +0x05800750, +0x0ae05220, +0x16000006, +0x16000145, +0x0a0054a0, +0x160fffd6, +0x05800420, +0x0ae053a0, +0x160fffe6, +0x04000344, +0x05800420, +0x0ae05490, +0x160ffff6, +0x04000344, +0x05800420, +0x0ae05490, +0x16000006, +0x04000344, +0x05800420, +0x0ae05490, +0x16000016, +0x04000344, +0x05800420, +0x0ae05490, +0x16000026, +0x04000344, +0x05800420, +0x0ae05490, +0x16000036, +0x013ffcf6, +0x12000132, +0x04000233, +0x9e088300, +0x40800e02, +0x16000005, +0x04000233, +0x12000233, +0x04200377, +0x05800570, +0x16001e02, +0x17800523, +0x04000377, +0x9e0f0070, +0x003ffcf6, +0x00800715, +0x01000606, +0x0a0058d0, +0x9c042b20, +0x9c052920, +0x9c023870, +0x07800000, +0x07800000, +0x9d00b360, +0x16000004, +0x16000005, +0x160fffb6, +0x00800503, +0x05800420, +0x0ae057d0, +0x160fffc6, +0x04000344, +0x05800420, +0x0ae058b0, +0x160fffd6, +0x04000344, +0x05800420, +0x0ae058b0, +0x160fffe6, +0x04000344, +0x05800420, +0x0ae058b0, +0x160ffff6, +0x04000344, +0x05800420, +0x0ae058b0, +0x16000006, +0x04000344, +0x05800420, +0x0ae058b0, +0x16000016, +0x04000344, +0x05800420, +0x0ae058b0, +0x16000026, +0x04000344, +0x05800420, +0x0ae058b0, +0x16000036, +0x04000344, +0x05800420, +0x0ae058b0, +0x16000046, +0x04000344, +0x05800420, +0x0ae058b0, +0x16000056, +0x013ffcf6, +0x12000232, +0x04000233, +0x9e088300, +0x16000005, +0x12000233, +0x04000377, +0x04a1e077, +0x05800570, +0x16001e02, +0x17800523, +0x04000377, +0x9e0f0170, +0x003ffcf6, +0x01000606, +0x00800715, +0x16012ac6, +0x413ffefe, +0x12000155, +0x00000202, +0x00800d04, +0x0200056e, +0x16020e05, +0x16014246, +0x16014287, +0x0400042d, +0x04a001dd, +0x9e0e0750, +0x9e0e0260, +0x9e0e0370, +0x0b200000, +0x00000806, +0x003ffefe, +0x0000021d, +0x9e0e0560, +0x40800b05, +0x048ffcff, +0x408007d7, +0x06000005, +0x40800f02, +0x04c07f77, +0x4a805b20, +0x04500273, +0x00800a02, +0x9e088100, +0x00000011, +0x418007d3, +0x16000003, +0x12800277, +0x018003d7, +0x9d140530, +0x9d038810, +0x08200000, +0x00800a02, +0x9e088000, +0x00000011, +0x418007d3, +0x16000003, +0x12800277, +0x018000d7, +0x9d140530, +0x9d038910, +0x08200000, +0x00001807, +0x00801e02, +0x16000003, +0x9c01b970, +0x06000082, +0x17000233, +0x9e088100, +0x07800000, +0x12000c33, +0x04c3ff66, +0x04500366, +0x07800000, +0x16000003, +0x9e0c8100, +0x16007f46, +0x00000064, +0x1600003d, +0x04a00122, +0x9c03a040, +0x04800266, +0x9e0f0130, +0x04800433, +0x06000002, +0x9c0c0038, +0x9c0c0078, +0x9c0c00b8, +0x9d0c810c, +0x9d0c815c, +0x9d0c81ac, +0x98805d30, +0x0aa05cb0, +0x9e0f0120, +0x08200000, +0x4080070d, +0x048002ff, +0x00800203, +0x00800905, +0x40000e04, +0x040003dd, +0x413ffefe, +0x06000004, +0x9c03a950, +0x4aa05ed0, +0x144000d2, +0x0a206190, +0x40000e04, +0x1440002d, +0x06000004, +0x0a806010, +0x05800d40, +0x0ae05f10, +0x0a206040, +0x0a005fc0, +0x042004d2, +0x1440004d, +0x0a206040, +0x0a206190, +0x06000002, +0x0a805fc0, +0x1440002d, +0x00000e04, +0x05800d40, +0x0ac06010, +0x0a206040, +0x003ffefe, +0x40800905, +0x048ffeff, +0x9d03a950, +0x08200000, +0x04a0012d, +0x0a201a60, +0x0a005fc0, +0x16014246, +0x40800605, +0x048002ff, +0x413ffefe, +0x144000d3, +0x16012ace, +0x9e0e0260, +0x12000155, +0x420005ee, +0x04a001dd, +0x0b200000, +0x9e088000, +0x403ffefe, +0x16000006, +0x40000e05, +0x048ffeff, +0x9e0e8040, +0x9e0f0060, +0x04200355, +0x01000e05, +0x08200000, +0x40800b0d, +0x16000246, +0x40000403, +0x04c001d7, +0x06000007, +0x4a806280, +0x16000017, +0x00001604, +0x06000004, +0x0a8063a0, +0x40001405, +0x048001dd, +0x01000e04, +0x01000c05, +0x0a0062f0, +0x00001204, +0x06000004, +0x0a8063a0, +0x40001005, +0x048001dd, +0x01000e04, +0x01000c05, +0x9e0e8050, +0x41800b0d, +0x16000005, +0x40800a04, +0x05c00630, +0x0a806390, +0x12000233, +0x9e0e0530, +0x9d140550, +0x0a0063a0, +0x01800017, +0x08200000, +0x048008ff, +0x413ff8f8, +0x1440001d, +0x013ffaf9, +0x413ffcfa, +0x16006a49, +0x413ffefb, +0x16015002, +0x00000095, +0x00000296, +0x9c018201, +0x01000025, +0x41400226, +0x16002743, +0x04800122, +0x9e088200, +0x9e090300, +0x07800000, +0x12800277, +0x128002bb, +0x01c00127, +0x01c0012b, +0x9c018201, +0x988064a0, +0x04800633, +0x1440001d, +0x00000034, +0x04802833, +0x01c00124, +0x98806550, +0x16002102, +0x9e0e0220, +0x16000806, +0x16007eca, +0x16007f0b, +0x9d140270, +0x000000a8, +0x000000b9, +0x04a00188, +0x04a00199, +0x16000005, +0x16000006, +0x06000008, +0x0aa06690, +0x16000015, +0x000002a8, +0x06000009, +0x0aa066d0, +0x16000016, +0x000002b9, +0x16007082, +0x410000a8, +0x12000166, +0x410000b9, +0x04500655, +0x40800021, +0x16006a4d, +0x41800027, +0x06000004, +0x0aa067c0, +0x06000005, +0x0aa06840, +0x06000001, +0x0aa068d0, +0x0a0069c0, +0x160000a8, +0x400000d6, +0x12000c88, +0x04500487, +0x07800000, +0x06000005, +0x9d180078, +0x0a8068b0, +0x160000c8, +0x400000d6, +0x12000c88, +0x04500587, +0x07800000, +0x07800000, +0x9d180078, +0x06000001, +0x0a806940, +0x160000d8, +0x400000d6, +0x12000c88, +0x04500187, +0x07800000, +0x07800000, +0x9d180078, +0x16000903, +0x16000fb9, +0x16000016, +0x9e0e0530, +0x16000007, +0x9d03c890, +0x07800000, +0x9d140570, +0x16006ac8, +0x40000280, +0x16000013, +0x00000084, +0x06000000, +0x40000049, +0x16006a82, +0x4a806a60, +0x16000005, +0x04200959, +0x05800590, +0x41000045, +0x160ffff6, +0x17000353, +0x17800363, +0x04800233, +0x01000023, +0x16015002, +0x01004a23, +0x403ffefb, +0x16002202, +0x9e0e0220, +0x403ffcfa, +0x16000806, +0x003ffaf9, +0x003ff8f8, +0x9d140270, +0x048ff8ff, +0x08200000, +0x048008ff, +0x413ff8f8, +0x16015002, +0x013ffaf9, +0x013ffcfa, +0x413ffefb, +0x04803322, +0x16008385, +0x16000e0d, +0x00000454, +0x00000856, +0x9c0768d0, +0x01c00124, +0x01c00126, +0x40000087, +0x16003129, +0x0000028d, +0x40000c54, +0x12000299, +0x00000e56, +0x01c00127, +0x0180012d, +0x9e0e0490, +0x41400224, +0x16003138, +0x41400226, +0x12000288, +0x9c100480, +0x9f03e0b0, +0x9e0e0580, +0x9e010080, +0xdc1005c0, +0x1600060d, +0x9f03e0b0, +0x01400229, +0x9e0080c0, +0x0140022a, +0x9c01ead0, +0x01400225, +0x41400226, +0x16000633, +0x9e090200, +0x04800122, +0x9c029c30, +0x128002bb, +0x01c0012b, +0x160005bd, +0x9e088400, +0x07800000, +0x9c01ead0, +0x12800277, +0x01c00127, +0x9e090200, +0x16000023, +0x1600005d, +0x128002bb, +0x9c029c30, +0x01c0012b, +0x04800122, +0x9e088400, +0x9c01ead0, +0x07800000, +0x12800277, +0x9e090200, +0x07800000, +0x01c00127, +0x128002bb, +0x01c0012b, +0x04800222, +0x16008185, +0x16000e2d, +0x00000454, +0x00000856, +0x9c0768d0, +0x01c00124, +0x01c00126, +0x40000087, +0x16003149, +0x0000028d, +0x40000c54, +0x12000299, +0x00000e56, +0x01c00127, +0x0180012d, +0x9e0e0490, +0x41400224, +0x16003158, +0x41400226, +0x12000288, +0x9c100480, +0x9f03e0b0, +0x9e0e0580, +0x9e010080, +0xdc1005c0, +0x1600009d, +0x9f03e0b0, +0x01400229, +0x9e0080c0, +0x0140022a, +0x9c01ead0, +0x01400225, +0x16000563, +0x01400226, +0x9e090200, +0x9c029c30, +0x07800000, +0x128002bb, +0x9e088400, +0x01c0022b, +0x07800000, +0x12800277, +0x01c00227, +0x003ffefb, +0x003ffcfa, +0x003ffaf9, +0x003ff8f8, +0x048ff8ff, +0x08200000, +0x00000004, +0x00000405, +0x00000806, +0x00000c07, +0x05c00540, +0x0b800000, +0x9e0e8040, +0x9c180034, +0x07800000, +0x07800000, +0x06000033, +0x9e0e8220, +0x0aa07410, +0x9c1d0004, +0x9c1d0044, +0x07800000, +0x9d0c0210, +0x0a0074c0, +0x06000023, +0x0aa07470, +0x9c1d0004, +0x9d040004, +0x9d100200, +0x0a0074c0, +0x06000043, +0x0aa074c0, +0x9c180024, +0x9d040004, +0x9d180200, +0x04800c44, +0x05c00740, +0x17800644, +0x01000004, +0x0a007330, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x07800000, +0x07800000, +0x07800000, +0x08400000, +0x0a000000, +0x00000000, +0x00000000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00151000, +0x00201000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001001, +0x00000000, +0x00001011, +0x00001011, +0x00021031, +0x00041051, +0x00061071, +0x00081091, +0x000a10b1, +0x000c10d1, +0x00001001, +0x00001001, +0x00001000, +0x00001000, +0x00001001, +0x00001001, +0x00001011, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001001, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001041, +0x00001000, +0x00000000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001001, +0x00001001, +0x00001000, +0x00001000, +0x00001001, +0x00001000, +0x00001001, +0x00001001, +0x00001001, +0x00000000, +0x00000000, +0x00001001, +0x00001001, +0x00001000, +0x00001001, +0x00001001, +0x00001001, +0x00001001, +0x00001001, +0x00001001, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00000000, +0x00000000, +0x00001000, +0x00001000, +0x00001000, +0x00000000, +0x00000000, +0x00000000, +0x00001000, +0x00001001, +0x00000001, +0x00001000, +0x00000858, +0x00001000, +0x00001001, +0x00000001, +0x00001000, +0x00000858, +0x00151000, +0x00000858, +0x00000858, +0x00151000, +0x00001000, +0x00001001, +0x00000001, +0x00001000, +0x00000898, +0x00001000, +0x00001001, +0x00001000, +0x00001000, +0x00001001, +0x00001001, +0x000010c1, +0x00001000, +0x000010c1, +0x00001001, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001061, +0x00001031, +0x00001061, +0x00001021, +0x00001061, +0x00001031, +0x00001061, +0x00001021, +0x00001061, +0x00001031, +0x00001061, +0x00001021, +0x00001061, +0x00001031, +0x00001061, +0x00001021, +0x00001061, +0x00001021, +0x00001061, +0x00001031, +0x00151000, +0x00001000, +0x00001000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00001071, +0x000000aa, +0x00001071, +0x00000000, +0x00000000, +0x00000000, +0x00001000, +0x00001000, +0x000000a6, +0x000000a8, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00000000, +0x00000000, +0x00001000, +0x003c1000, +0x00001000, +0x003c1000, +0x00001001, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00001001, +0x00001000, +0x00001051, +0x00001000, +0x00001001, +0x00001051, +0x00001001, +0x000000c6, +0x00001000, +0x00001001, +0x00001001, +0x00001001, +0x00001001, +0x00001001, +0x0000fff9, +0x00001000, +0x00001000, +0x00000000, +0x00001001, +0x00001091, +0x00001000, +0x00001000, +0x00000000, +0x00000000, +0x00001091, +0x00001091, +0x00001091, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00001000, +0x00000000, +0x00000000, +0x00000000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001001, +0x00001001, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00001000, +0x00001001, +0x00000000, +0x00011001, +0x00001000, +0x00151000, +0x00001001, +0x00000001, +0x00001000, +0x00000858, +0x00000858, +0x00001000, +0x00151000, +0x00151000, +0x00001000, +0x00001000, +0x00001000, +0x00001000, +0x00001001, +0x00000001, +0x00001000, +0x00000858, +0x00000858, +0x00000000, +0x00000000, +0x00001001, +0x00000000, +0x00000000, +0x00001000, +0x00000000, +0x00000000, +0x00001001, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000008, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00700001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00100001, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00fc4793, +0x000a8b43, +0x00e7110b, +0x003411d3, +0x009d1cfb, +0x0002b6a8, +0x00fb8ca8, +0x0006dac8, +0x00f610e8, +0x000d8628, +0x00eed498, +0x0013f0e8, +0x00eb9d08, +0x000f02e8, +0x00056260, +0x00bf42f0, +0x006a9a5d, +0x005ed280, +0x00023c94, +0x00fb2e83, +0x000c8ac3, +0x00e408e3, +0x0037ceab, +0x0099fe5b, +0x0002b430, +0x00fbb618, +0x000661c0, +0x00f71850, +0x000b9710, +0x00f220d0, +0x000eb9d8, +0x00f37700, +0x0003c304, +0x00142c90, +0x00b09170, +0x0066c791, +0x006d23e0, +0x000208b8, +0x00fa9443, +0x000d8413, +0x00e2e293, +0x00386703, +0x009badeb, +0x000296d8, +0x00fc0144, +0x0005c418, +0x00f83d98, +0x0009a2c0, +0x00f53c30, +0x000a1678, +0x00fa10a0, +0x00fadbf0, +0x001ef5c0, +0x00a7ab80, +0x0061b101, +0x00782010, +0x000464a8, +0x00fa1fab, +0x000e2353, +0x00e28193, +0x003770b3, +0x00a04a0b, +0x000264f0, +0x00fc6e8c, +0x0004f108, +0x00f9b218, +0x00073f50, +0x00f8ea80, +0x0004be58, +0x005c1863, +0x00f141c8, +0x002a0528, +0x009fa4a8, +0x005b6439, +0x0042ccf5, +0x0005d8f0, +0x00f9f08b, +0x000e2e23, +0x00e33ce3, +0x00348833, +0x00a80de3, +0x00022008, +0x00fcf654, +0x0003fc0c, +0x00fb4d68, +0x0004b6b0, +0x00fcaf74, +0x00dcf75b, +0x000875d0, +0x00e88c00, +0x00334af8, +0x009a9170, +0x0053f3c9, +0x00498425, +0x0007d850, +0x00fa0193, +0x000db28b, +0x00e4f083, +0x0030005b, +0x00b24bfb, +0x007356ab, +0x00fd8f38, +0x0002f50c, +0x00fcf624, +0x00022f7c, +0x0014e623, +0x00fa8240, +0x000ec210, +0x00e12888, +0x003a6068, +0x00989408, +0x004b98d9, +0x004fdc61, +0x000aadb0, +0x00fa47fb, +0x000cc843, +0x00e76cb3, +0x002a3303, +0x00be6543, +0x005c437b, +0x008ca913, +0x007977d3, +0x00a7922b, +0x00ef1dc3, +0x0003bf5c, +0x00f604a8, +0x00143b08, +0x00db27e8, +0x003f50c0, +0x009968b8, +0x00427b21, +0x0055cb8d, +0x000e5198, +0x00faba2b, +0x000b83a3, +0x00ea894b, +0x00236a23, +0x00cbda13, +0x004398b3, +0x00b6e5bb, +0x003575db, +0x000eee5b, +0x00fd6ac0, +0x0006e2f8, +0x00f20c30, +0x0018d298, +0x00d68e40, +0x00423000, +0x009cd148, +0x00716ee8, +0x005b537d, +0x0012b270, +0x00fb4feb, +0x0009f65b, +0x00ee2313, +0x001be763, +0x00da372b, +0x002a1533, +0x00e15813, +0x00f2fbd3, +0x007127a3, +0x00fb4778, +0x0009ae08, +0x00eeab88, +0x001c7728, +0x00d365f8, +0x004306e0, +0x00a2a0d0, +0x005cd070, +0x00606a75, +0x0017d078, +0x00fc00bb, +0x0008330b, +0x00f2141b, +0x0013f29b, +0x00e8fcc3, +0x00108f73, +0x000aabc3, +0x00b409ab, +0x00032d94, +0x00f961f0, +0x000c0d48, +0x00ebf900, +0x001f1310, +0x00d1bd70, +0x0041db98, +0x00aaa7f0, +0x004768e0, +0x0064f695, +0x001db640, +0x00fcc40b, +0x00064cab, +0x00f635fb, +0x000bd423, +0x00f7ab83, +0x00f7da9b, +0x00319613, +0x00fdea24, +0x00046be0, +0x00f7c838, +0x000def88, +0x00ea0778, +0x002095b8, +0x00d19bf0, +0x003ebd10, +0x00b4aec0, +0x00318e40, +0x0068d739, +0x002477b8, +0x00fd9103, +0x000456bb, +0x00fa6163, +0x0003d51b, +0x0005c47b, +0x00e0c55b, +0x0054da23, +0x00fd2110, +0x000575e0, +0x00f68680, +0x000f46d0, +0x00e8e4e8, +0x0020f5c0, +0x00d2fe70, +0x0039c7a8, +0x00c06ee8, +0x001ba3e0, +0x006be82d, +0x002c29d8, +0x00fe5ef3, +0x000264b3, +0x00fe6f83, +0x00fc3c73, +0x0012cf0b, +0x00cc0f3b, +0x00735b43, +0x00fc7b10, +0x00064398, +0x00f5a608, +0x001009d8, +0x00e89830, +0x002033b8, +0x00d5d488, +0x003327e0, +0x00cd9208, +0x000617a0, +0x006e0619, +0x0034dcb8, +0x00ff2633, +0x0000878b, +0x00023efb, +0x00f54613, +0x001e683b, +0x00ba501b, +0x00023104, +0x00fbfc98, +0x0006cfe8, +0x00f52c00, +0x001034f8, +0x00e92088, +0x001e5960, +0x00da0390, +0x002b1358, +0x00dbbf58, +0x00f14c40, +0x006f129d, +0x003ea030, +0x00ffdfeb, +0x00feceb3, +0x0005b193, +0x00ef271b, +0x00283a63, +0x00ac06cb, +0x00027b84, +0x00fba8d8, +0x00071778, +0x00f51a98, +0x000fc900, +0x00ea7720, +0x001b7880, +0x00df6710, +0x0021ca50, +0x00ea95d0, +0x00dda780, +0x006ef2a5, +0x00497dd8, +0x00000003, +0x00fd4a6b, +0x0008a803, +0x00ea151b, +0x002ff333, +0x00a1a463, +0x0002aad0, +0x00fb81d8, +0x00071978, +0x00f56fc8, +0x000ecde0, +0x00ec89f0, +0x0017b2e0, +0x00e5c408, +0x0017aaa0, +0x00f99118, +0x00cbaf78, +0x006d93e5, +0x005561e0, +0x00000020, +0x003fffe0, +0x00000020, +0x003fffe0, +0x00069cf3, +0x00eda3d3, +0x00284343, +0x00b2821b, +0x000228bc, +0x00fc4508, +0x0006a660, +0x00f19bc0, +0x007f26c5, +0x00107398, +0x00f8cbc0, +0x0003fef8, +0x00fdafb8, +0x00539323, +0x00d40cc3, +0x0014740b, +0x00f855f3, +0x0001b16b, +0x000c0373, +0x00ddec9b, +0x004b880b, +0x00fdb6d0, +0x00041728, +0x00f8ede8, +0x000c8b38, +0x00e57730, +0x007ca649, +0x0022b568, +0x00f146b8, +0x00081d20, +0x00fb4da0, +0x0002a8ac, +0x00a5ff43, +0x002a4a93, +0x00efdbfb, +0x0003c6eb, +0x00101e33, +0x00d13c0b, +0x0068d11b, +0x00fcce9c, +0x0005bc08, +0x00f61488, +0x001185c8, +0x00dbb070, +0x00788dfd, +0x00367598, +0x00e9b5b8, +0x000c31f8, +0x00f8f1a0, +0x00040178, +0x00fdde98, +0x0040ab93, +0x00e6e21b, +0x0006309b, +0x0012ea3b, +0x00c7c91b, +0x007f746f, +0x00fc1754, +0x00070c10, +0x00f3cc50, +0x00157878, +0x00d45408, +0x0072f70d, +0x004b56d0, +0x00e26390, +0x001012a8, +0x00f6b500, +0x00054a20, +0x00fd2bf0, +0x0056a233, +0x00ddc993, +0x0008d62b, +0x00147503, +0x00c1a1c3, +0x00023c70, +0x00fb9490, +0x0007fff0, +0x00f221f8, +0x00185148, +0x00cf5d50, +0x006c0395, +0x0060f058, +0x00db9f08, +0x00139340, +0x00f4b188, +0x00067398, +0x00fc8844, +0x006b2573, +0x00d501e3, +0x000b96fb, +0x0014d9d3, +0x00beae3b, +0x00025f04, +0x00fb4790, +0x00089470, +0x00f11b68, +0x001a0988, +0x00ccb738, +0x0063ddc5, +0x0076d0d4, +0x00d5b880, +0x001688a8, +0x00f30060, +0x00076f18, +0x00fbfbf8, +0x007d228f, +0x00cd04b3, +0x000e4b13, +0x00143ecb, +0x00beb5b3, +0x000266a0, +0x00fb2f48, +0x0008ca48, +0x00f0b7e8, +0x001aa578, +0x00cc3da8, +0x005ab67d, +0x004640a1, +0x00d0ff50, +0x0018ca30, +0x00f1b920, +0x00082ea8, +0x00fb8f00, +0x00022e24, +0x00c650c3, +0x0010c49b, +0x0012d1ab, +0x00c1642b, +0x0002555c, +0x00fb48b0, +0x0008a5d0, +0x00f0f0a0, +0x001a3350, +0x00cdbf38, +0x0050c415, +0x0050c415, +0x00cdbf38, +0x001a3350, +0x00f0f0a0, +0x0008a5d0, +0x00fb48b0, +0x0002555c, +0x00c1642b, +0x0012d1ab, +0x0010c49b, +0x00c650c3, +0x00022e24, +0x00fb8f00, +0x00082ea8, +0x00f1b920, +0x0018ca30, +0x00d0ff50, +0x004640a1, +0x005ab67d, +0x00cc3da8, +0x001aa578, +0x00f0b7e8, +0x0008ca48, +0x00fb2f48, +0x000266a0, +0x00beb5b3, +0x00143ecb, +0x000e4b13, +0x00cd04b3, +0x007d228f, +0x00fbfbf8, +0x00076f18, +0x00f30060, +0x001688a8, +0x00d5b880, +0x0076d0d4, +0x0063ddc5, +0x00ccb738, +0x001a0988, +0x00f11b68, +0x00089470, +0x00fb4790, +0x00025f04, +0x00beae3b, +0x0014d9d3, +0x000b96fb, +0x00d501e3, +0x006b2573, +0x00fc8844, +0x00067398, +0x00f4b188, +0x00139340, +0x00db9f08, +0x0060f058, +0x006c0395, +0x00cf5d50, +0x00185148, +0x00f221f8, +0x0007fff0, +0x00fb9490, +0x00023c70, +0x00c1a1c3, +0x00147503, +0x0008d62b, +0x00ddc993, +0x0056a233, +0x00fd2bf0, +0x00054a20, +0x00f6b500, +0x001012a8, +0x00e26390, +0x004b56d0, +0x0072f70d, +0x00d45408, +0x00157878, +0x00f3cc50, +0x00070c10, +0x00fc1754, +0x007f746f, +0x00c7c91b, +0x0012ea3b, +0x0006309b, +0x00e6e21b, +0x0040ab93, +0x00fdde98, +0x00040178, +0x00f8f1a0, +0x000c31f8, +0x00e9b5b8, +0x00367598, +0x00788dfd, +0x00dbb070, +0x001185c8, +0x00f61488, +0x0005bc08, +0x00fcce9c, +0x0068d11b, +0x00d13c0b, +0x00101e33, +0x0003c6eb, +0x00efdbfb, +0x002a4a93, +0x00a5ff43, +0x0002a8ac, +0x00fb4da0, +0x00081d20, +0x00f146b8, +0x0022b568, +0x007ca649, +0x00e57730, +0x000c8b38, +0x00f8ede8, +0x00041728, +0x00fdb6d0, +0x004b880b, +0x00ddec9b, +0x000c0373, +0x0001b16b, +0x00f855f3, +0x0014740b, +0x00d40cc3, +0x00539323, +0x00fdafb8, +0x0003fef8, +0x00f8cbc0, +0x00107398, +0x007f26c5, +0x00f19bc0, +0x0006a660, +0x00fc4508, +0x000228bc, +0x00b2821b, +0x00284343, +0x00eda3d3, +0x00069cf3, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00040002, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000003, +0x00000020, +0x003fffe0, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00040002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x004b54e7, +0x002866b7, +0x0002526c, +0x005d3e43, +0x0002526c, +0x002866b7, +0x004b54e7, +0x0090828c, +0x00097262, +0x00e8875a, +0x0021f9ea, +0x00e1aff2, +0x000feece, +0x000387fc, +0x0079341b, +0x0006f740, +0x00045eec, +0x0006f740, +0x0079341b, +0x000387fc, +0x0090828c, +0x00097262, +0x00e8875a, +0x0021f9ea, +0x00e1aff2, +0x000feece, +0x007de295, +0x00f821da, +0x007de295, +0x008431e5, +0x0007dde2, +0x0012f747, +0x00c81d03, +0x005f165b, +0x0094600b, +0x005f165b, +0x00c81d03, +0x0012f747, +0x00aa0ab1, +0x001057be, +0x00d5b832, +0x003b8bf6, +0x00cfd2ca, +0x00154066, +0x0071cb9f, +0x00fac2b8, +0x0008ea18, +0x00f5e900, +0x0008ea18, +0x00fac2b8, +0x0071cb9f, +0x00aa0ab1, +0x001057be, +0x00d5b832, +0x003b8bf6, +0x00cfd2ca, +0x00154066, +0x0084edbd, +0x007b1245, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00762489, +0x00000020, +0x003fffe0, +0x00269ec3, +0x000d0ff4, +0x00051eba, +0x00640001, +0x0002f290, +0x00fdd340, +0x0002a810, +0x0002a810, +0x00fdd340, +0x0002f290, +0x0045a895, +0x00f4a186, +0x0018a312, +0x00e445b2, +0x0010419e, +0x000b68e0, +0x0021f7f0, +0x0044471c, +0x005c5e48, +0x005c5e48, +0x0044471c, +0x0021f7f0, +0x000b68e0, +0x0020ff38, +0x00b24b3d, +0x00062d86, +0x00f4f6ea, +0x000d3f5a, +0x00f2ea1a, +0x00075f92, +0x00c1248b, +0x00fd1080, +0x00faca4c, +0x00fab048, +0x00fdb0ac, +0x00024f54, +0x00054fb8, +0x000535b4, +0x0002ef80, +0x003edb7b, +0x001d92ec, +0x00962b59, +0x000bd422, +0x00e48132, +0x002dbdc2, +0x00c7a94a, +0x0033fbe6, +0x00dd3502, +0x000fea26, +0x00c1248b, +0x00fd1080, +0x00faca4c, +0x00fab048, +0x00fdb0ac, +0x00024f54, +0x00054fb8, +0x000535b4, +0x0002ef80, +0x003edb7b, +0x001d92ec, +0x00962b59, +0x000bd422, +0x00e48132, +0x002dbdc2, +0x00c7a94a, +0x0033fbe6, +0x00dd3502, +0x000fea26, +0x00400000, +0x00100002, +0x007f0001, +0x00040002, +0x00040002, +0x00000020, +0x003fffe0, +0x00000020, +0x003fffe0, +0x0000e95b, +0x00045e13, +0x00e38bd3, +0x0049a0eb, +0x0086a523, +0x00021a60, +0x00b3b22b, +0x00c008cb, +0x0003ee58, +0x00f98190, +0x0007bebc, +0x00f927cc, +0x000369f8, +0x000210a0, +0x00f7d700, +0x000cf630, +0x00f0b350, +0x000f00d8, +0x00f45884, +0x0004930c, +0x000cca50, +0x00d5b1a0, +0x0036ee50, +0x00c50200, +0x00c1a4a3, +0x005fc0f5, +0x004f99c8, +0x00026924, +0x00ff83fb, +0x0008de8b, +0x00de91f3, +0x0044c043, +0x00a56883, +0x0040eb8b, +0x001e74a3, +0x00fd21b0, +0x000554e4, +0x00f94c68, +0x000647f4, +0x00fc5390, +0x00c48fa3, +0x00067e44, +0x00f4b078, +0x000daa00, +0x00f31630, +0x00095c18, +0x00fd153c, +0x00f92d4c, +0x00187260, +0x00d118b0, +0x002e0ce0, +0x00dfc588, +0x00d88710, +0x005ade01, +0x0069ab18, +0x000581a8, +0x00ff0f2b, +0x000a2003, +0x00e0f9b3, +0x00341ca3, +0x00d0769b, +0x00f72c73, +0x0079f1eb, +0x00fbed28, +0x0005aab8, +0x00fa5660, +0x0003b918, +0x00028fb3, +0x00fb129c, +0x00098f30, +0x00f3bff8, +0x000bc1d8, +0x00f7e048, +0x000228e8, +0x0005dda0, +0x00f00dc0, +0x001eb940, +0x00d459f0, +0x001db068, +0x00fcb1e0, +0x00bb70e0, +0x00500125, +0x00423c25, +0x000cb760, +0x00ff3a63, +0x0008dcf3, +0x00e89f33, +0x001cf00b, +0x00fdff03, +0x00b7a13b, +0x0002d9ac, +0x00fb9038, +0x0004ee8c, +0x00fc6088, +0x0025bba3, +0x000396e8, +0x00f82434, +0x000abf70, +0x00f51e3c, +0x0007b174, +0x0083c45b, +0x00fadcd0, +0x000d0158, +0x00eaba28, +0x001ef828, +0x00de61d8, +0x00091358, +0x00172bd8, +0x00aaa5f8, +0x007f9968, +0x004ead35, +0x0016d358, +0x00ff9df3, +0x00063423, +0x00f2bc93, +0x0004db3b, +0x002510d3, +0x008d0163, +0x00032dec, +0x00fc0c14, +0x00035394, +0x00bfd6bb, +0x00fd7bc4, +0x000653e0, +0x00f6c644, +0x0009e92c, +0x00f874d4, +0x00025014, +0x000466f8, +0x00f4e050, +0x00113910, +0x00e9e9d8, +0x0019a460, +0x00ed06b8, +0x00f3f088, +0x002b3ea8, +0x00a75d50, +0x00573b88, +0x0058acdd, +0x00253e48, +0x00ffe99b, +0x00034623, +0x00fca67b, +0x00f0ade3, +0x003f433b, +0x00fdf30c, +0x0002e438, +0x00fd3880, +0x004d26e3, +0x006be023, +0x00fafa08, +0x0007c8c0, +0x00f72900, +0x000747f8, +0x00fd12b4, +0x00fcac44, +0x0009b130, +0x00f14b30, +0x0011de10, +0x00ed7168, +0x00102888, +0x00fd78fc, +0x00e1dd38, +0x003632e8, +0x00b0f308, +0x002ad4a0, +0x005ec5d9, +0x00385800, +0x00fed5c7, +0x0001d26f, +0x00fa588b, +0x000daca7, +0x00e3d6d3, +0x00339bc7, +0x00a97e63, +0x00021db8, +0x00fcd778, +0x00049f28, +0x00f8fde8, +0x000d23e8, +0x007d7e59, +0x00f89bb0, +0x000259b4, +0x00d1846b, +0x0003ff83, +0x000bf0f7, +0x00f0e3cf, +0x000ca4f7, +0x00f77a0b, +0x0004c883, +0x00fdcf83, +0x0000c0c7, +0x00ff662f, +0x0002ff27, +0x00f6afb3, +0x0016defb, +0x00cfc55f, +0x005b288f, +0x00fd84f0, +0x000411f4, +0x00f99078, +0x000a2044, +0x00ef1978, +0x0024d648, +0x007aa5a5, +0x00e7c3e8, +0x000aa4ec, +0x00fa6a80, +0x000304e0, +0x009a1a6f, +0x0032b7db, +0x00e8d6d7, +0x000963f3, +0x00fcc6cf, +0x0000dc7b, +0x00ffda2f, +0x00ff1ed3, +0x0004309f, +0x00f31b9b, +0x001faed3, +0x00bcc34f, +0x00020190, +0x00fc755c, +0x0005e8c8, +0x00f675cc, +0x000f6978, +0x00e54b98, +0x003efe58, +0x007512ad, +0x00daf028, +0x001174bc, +0x00f652f4, +0x000589a8, +0x00fce2b4, +0x006b13b7, +0x00ca4a77, +0x00188ddf, +0x00f61837, +0x000357df, +0x00ff2823, +0x00fed6b7, +0x00054b13, +0x00efed7f, +0x0027599b, +0x00ac68e3, +0x00028110, +0x00fb8cf0, +0x00077d74, +0x00f3c420, +0x00141628, +0x00dc4340, +0x005ac084, +0x006d00dd, +0x00d23fd0, +0x00167f08, +0x00f33398, +0x00077ccc, +0x00fbb300, +0x00025c40, +0x00b2862b, +0x0024406f, +0x00f10627, +0x00052b3f, +0x00feadf3, +0x00fe9647, +0x00062edf, +0x00ed7c03, +0x002d1867, +0x00a04bef, +0x0002df40, +0x00fae088, +0x0008acc0, +0x00f1b5e8, +0x0017c370, +0x00d4b638, +0x0077237c, +0x0062c57d, +0x00cd9f14, +0x00199c28, +0x00f12c50, +0x0008c91c, +0x00fae638, +0x0002d380, +0x00a270f7, +0x002c1793, +0x00edb29f, +0x00065057, +0x00fe69af, +0x00fe678b, +0x0006badb, +0x00ec1c5f, +0x00302fa7, +0x0099df87, +0x00031180, +0x00fa8294, +0x000957b0, +0x00f07efc, +0x001a1714, +0x00cf5714, +0x00498dbd, +0x0056cb65, +0x00cccae8, +0x001ac64c, +0x00f04854, +0x0009661c, +0x00fa8180, +0x00030f60, +0x009a52c3, +0x00300017, +0x00ec1ad7, +0x0006cf1f, +0x00fe552f, +0x00fe552f, +0x0006cf1f, +0x00ec1ad7, +0x00300017, +0x009a52c3, +0x00030f60, +0x00fa8180, +0x0009661c, +0x00f04854, +0x001ac64c, +0x00cccae8, +0x0056cb65, +0x00498dbd, +0x00cf5714, +0x001a1714, +0x00f07efc, +0x000957b0, +0x00fa8294, +0x00031180, +0x0099df87, +0x00302fa7, +0x00ec1c5f, +0x0006badb, +0x00fe678b, +0x00fe69af, +0x00065057, +0x00edb29f, +0x002c1793, +0x00a270f7, +0x0002d380, +0x00fae638, +0x0008c91c, +0x00f12c50, +0x00199c28, +0x00cd9f14, +0x0062c57d, +0x0077237c, +0x00d4b638, +0x0017c370, +0x00f1b5e8, +0x0008acc0, +0x00fae088, +0x0002df40, +0x00a04bef, +0x002d1867, +0x00ed7c03, +0x00062edf, +0x00fe9647, +0x00feadf3, +0x00052b3f, +0x00f10627, +0x0024406f, +0x00b2862b, +0x00025c40, +0x00fbb300, +0x00077ccc, +0x00f33398, +0x00167f08, +0x00d23fd0, +0x006d00dd, +0x005ac084, +0x00dc4340, +0x00141628, +0x00f3c420, +0x00077d74, +0x00fb8cf0, +0x00028110, +0x00ac68e3, +0x0027599b, +0x00efed7f, +0x00054b13, +0x00fed6b7, +0x00ff2823, +0x000357df, +0x00f61837, +0x00188ddf, +0x00ca4a77, +0x006b13b7, +0x00fce2b4, +0x000589a8, +0x00f652f4, +0x001174bc, +0x00daf028, +0x007512ad, +0x003efe58, +0x00e54b98, +0x000f6978, +0x00f675cc, +0x0005e8c8, +0x00fc755c, +0x00020190, +0x00bcc34f, +0x001faed3, +0x00f31b9b, +0x0004309f, +0x00ff1ed3, +0x00ffda2f, +0x0000dc7b, +0x00fcc6cf, +0x000963f3, +0x00e8d6d7, +0x0032b7db, +0x009a1a6f, +0x000304e0, +0x00fa6a80, +0x000aa4ec, +0x00e7c3e8, +0x007aa5a5, +0x0024d648, +0x00ef1978, +0x000a2044, +0x00f99078, +0x000411f4, +0x00fd84f0, +0x005b288f, +0x00cfc55f, +0x0016defb, +0x00f6afb3, +0x0002ff27, +0x00ff662f, +0x0000c0c7, +0x00fdcf83, +0x0004c883, +0x00f77a0b, +0x000ca4f7, +0x00f0e3cf, +0x000bf0f7, +0x0003ff83, +0x00d1846b, +0x000259b4, +0x00f89bb0, +0x007d7e59, +0x000d23e8, +0x00f8fde8, +0x00049f28, +0x00fcd778, +0x00021db8, +0x00a97e63, +0x00339bc7, +0x00e3d6d3, +0x000daca7, +0x00fa588b, +0x0001d26f, +0x00fed5c7, +0x00000000, +0x00040002, +0x00000000, +0x00040002, +0x00020002, +0x00f80002, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00009450, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000010, +0x00000000, +0x00000000, +0x000004fc, +0x00000000, +0x00000000, +0x00000018, +0x000004f4, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00a0000c, +0x003c00a1, +0x00000000, +0x00000000, +0x000005ba, +0x00a2000c, +0x003c00a3, +0x00000000, +0x00000000, +0x000005ba, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0000000a, +0x7fff7fff, +0x7fff7fff, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000004, +0x00000001, +0x00000000, +0x00000000, +0x00000000, +0x003ffff0, +0x00000000, +0x00400000, +0x00000004, +0x00000001, +0x00000000, +0x00000000, +0x00000000, +0x003ffff0, +0x00000000, +0x00400000, +0x01f501e2, +0x021b0208, +0x0241022e, +0x02670254, +0x028d027a, +0x02b302a0, +0x02d902c6, +0x02ff02ec, +0x00000004, +0x00000001, +0x00000000, +0x00000000, +0x00000000, +0x003ffff0, +0x00000000, +0x00400000, +0x03280316, +0x034c033a, +0x0370035e, +0x03940382, +0x03b803a6, +0x03dc03ca, +0x040003ee, +0x04240412, +0x00001400, +0x000000ff, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0aa40c24, +0x00000c34, +0x0a940c04, +0x00000c14, +0x0e640bb4, +0x00000ba4, +0x0e540b94, +0x00000b84, +0x0a840be4, +0x00000bf4, +0x0a740bc4, +0x00000bd4, +0x0e440b34, +0x00000b24, +0x0e340b14, +0x00000b04, +0x0a640b64, +0x00000b74, +0x0a540b44, +0x00000b54, +0x00000004, +0x00000001, +0x00000000, +0x00000000, +0x00000000, +0x003ffff0, +0x00000000, +0x00400000, +0x00000004, +0x00000001, +0x00000000, +0x00000000, +0x00000000, +0x003ffff0, +0x00000000, +0x00400000, +0x0a740bc4, +0x000010f4, +0x0a840be4, +0x00001104, +0x11340b14, +0x00000000, +0x0e540b94, +0x00000000, +0x00560014, +0x00570003, +0x005a0058, +0x00000013, +0x00560014, +0x00570006, +0x00610058, +0x00000013, +0x00560019, +0x00570003, +0x005a0058, +0x033c0013, +0x00560019, +0x00570006, +0x00610058, +0x033c0013, +0x00640014, +0x0065010d, +0x00680066, +0x00000012, +0x005b0015, +0x005c0002, +0x005f005d, +0x00000013, +0x005b0015, +0x005c0005, +0x0062005d, +0x00000013, +0x005b001a, +0x005c0002, +0x005f005d, +0x038c0013, +0x005b001a, +0x005c0005, +0x0062005d, +0x038c0013, +0x01050003, +0x0001008e, +0x00900001, +0x00000000, +0x01060003, +0x0001008f, +0x00910001, +0x00000000, +0x01070003, +0x00010129, +0x00900001, +0x00000000, +0x01070003, +0x0001012a, +0x00910001, +0x00000000, +0x004d0003, +0x0001008e, +0x00900001, +0x00000000, +0x004d0003, +0x0001008f, +0x00910001, +0x00000000, +0x0032000b, +0x00710030, +0x004e0072, +0x00000000, +0x00010013, +0x00010001, +0x01100001, +0x00000000, +0x00010013, +0x00010001, +0x01110001, +0x00000000, +0x000d000a, +0x000f000e, +0x00110010, +0x00000000, +0x00280000, +0x002b002a, +0x002d002c, +0x00000000, +0x00240000, +0x00790024, +0x00920074, +0x00000000, +0x00030000, +0x007a0003, +0x00930075, +0x00000000, +0x00240000, +0x007b0024, +0x00940077, +0x00000000, +0x00060000, +0x007c0006, +0x00950078, +0x00000000, +0x00290000, +0x007d0105, +0x00920073, +0x00000000, +0x008e0000, +0x007e0060, +0x00930075, +0x00000000, +0x00290000, +0x007f0106, +0x00940076, +0x00000000, +0x008f0000, +0x00800063, +0x00950078, +0x00000000, +0x01080000, +0x00810108, +0x00920074, +0x00000000, +0x010b0000, +0x0082010b, +0x00930075, +0x00000000, +0x01080000, +0x00830108, +0x00940077, +0x00000000, +0x010c0000, +0x0084010c, +0x00950078, +0x00000000, +0x00310000, +0x00850107, +0x00920073, +0x00000000, +0x01290000, +0x00860109, +0x00930075, +0x00000000, +0x00310000, +0x00870107, +0x00940076, +0x00000000, +0x012a0000, +0x0088010a, +0x00950078, +0x00000000, +0x00270000, +0x008b004d, +0x00920073, +0x00000000, +0x008e0000, +0x008c008d, +0x00930075, +0x00000000, +0x00270000, +0x0089004d, +0x00940076, +0x00000000, +0x008f0000, +0x008a008d, +0x00950078, +0x00000000, +0x00310000, +0x006f002f, +0x004e0070, +0x00000000, +0x00300018, +0x00980051, +0x009700d0, +0x00000000, +0x002f0018, +0x00960050, +0x009700d2, +0x00000000, +0x002e0008, +0x003c0031, +0x00000017, +0x00000000, +0x00220008, +0x003c0032, +0x00000018, +0x00000000, +0x0137000c, +0x00010001, +0x01380001, +0x00000000, +0x0001000d, +0x00010001, +0x00db0001, +0x00000000, +0x0001000d, +0x00010001, +0x00dc0001, +0x00000000, +0x0001000d, +0x00010001, +0x00dd0001, +0x00000000, +0x0001000d, +0x00010001, +0x00de0001, +0x00000000, +0x0001000d, +0x00010001, +0x00df0001, +0x00000000, +0x0001000d, +0x00010001, +0x00e00001, +0x00000000, +0x0001000d, +0x00010001, +0x00e10001, +0x00000000, +0x0001000d, +0x00010001, +0x00e20001, +0x00000000, +0x0001000d, +0x00010001, +0x00e30001, +0x00000000, +0x0001000d, +0x00010001, +0x00e40001, +0x00000000, +0x0001000d, +0x00010001, +0x00e50001, +0x00000000, +0x0001000d, +0x00010001, +0x00e60001, +0x00000000, +0x0001000d, +0x00010001, +0x00e70001, +0x00000000, +0x0001000d, +0x00010001, +0x00e80001, +0x00000000, +0x00fb000e, +0x00010001, +0x00fc0001, +0x00000000, +0x002f0005, +0x001c0027, +0x00000030, +0x00000000, +0x002a0005, +0x001d002e, +0x00000021, +0x00000000, +0x00080006, +0x00260021, +0x0000001a, +0x00000000, +0x00080006, +0x00260022, +0x0000001b, +0x00000000, +0x00080007, +0x00260021, +0x0000001a, +0x00000000, +0x00080007, +0x00260022, +0x0000001b, +0x00000000, +0x00080006, +0x006a0069, +0x0000001e, +0x00000000, +0x00080006, +0x006a0029, +0x0000001f, +0x00000000, +0x00080007, +0x006a0029, +0x0000001f, +0x00000000, +0x00c10001, +0x00ca0052, +0x0000011e, +0x00000000, +0x00030004, +0x000c0024, +0x00900001, +0x00000000, +0x00060004, +0x000c0024, +0x00910001, +0x00000000, +0x010b0004, +0x000c0108, +0x00900001, +0x00000000, +0x010c0004, +0x000c0108, +0x00910001, +0x00000000, +0x00300004, +0x00fe0051, +0x00fd0001, +0x00000000, +0x002f0004, +0x00fe0050, +0x00fd0001, +0x00000000, +0x00410002, +0x00430042, +0x00000016, +0x00000000, +0x00330002, +0x00350034, +0x00000013, +0x00000000, +0x00360002, +0x00380037, +0x00000014, +0x00000000, +0x00390002, +0x003b003a, +0x00000015, +0x00000000, +0x00690002, +0x006c006b, +0x00000019, +0x00000000, +0x01080002, +0x0040003f, +0x00000020, +0x00000000, +0x00080002, +0x00470046, +0x00000019, +0x00000000, +0x00cf0002, +0x00c900c0, +0x00000019, +0x00000000, +0x010d0002, +0x010f010e, +0x00000019, +0x00000000, +0x00270002, +0x00450044, +0x00000019, +0x00000000, +0x00010002, +0x00010001, +0x00000019, +0x00000000, +0x00010009, +0x00010028, +0x00530001, +0x00000000, +0x00010009, +0x00010023, +0x00540001, +0x00000000, +0x00c00011, +0x00c200c1, +0x00c700c3, +0x00000000, +0x00c80010, +0x00cb00ca, +0x00ce00cc, +0x00000000, +0x00010013, +0x00010001, +0x01120001, +0x00000000, +0x00010013, +0x00010001, +0x01130001, +0x00000000, +0x00010013, +0x00010001, +0x01140001, +0x00000000, +0x00010013, +0x00010001, +0x01150001, +0x00000000, +0x00010013, +0x00010001, +0x01160001, +0x00000000, +0x00010013, +0x00010001, +0x01170001, +0x00000000, +0x00010013, +0x00010001, +0x01180001, +0x00000000, +0x00010013, +0x00010001, +0x01190001, +0x00000000, +0x0001000f, +0x00010001, +0x00010001, +0x00000000, +0x00490012, +0x00d30041, +0x00d600d4, +0x00000000, +0x004a0012, +0x00d80033, +0x00d700d5, +0x00000000, +0x004b0012, +0x00d90036, +0x00d700d5, +0x00000000, +0x004c0012, +0x00da0039, +0x00d700d5, +0x00000000, +0x011b0016, +0x011c0001, +0x011d0001, +0x00000000, +0x00010017, +0x00010001, +0x00000001, +0x00000000, +0x011f0014, +0x0121010b, +0x01240122, +0x00000013, +0x011f0014, +0x0121010c, +0x01250122, +0x00000013, +0x011f0019, +0x0121010b, +0x01240122, +0x04040013, +0x011f0019, +0x0121010c, +0x01250122, +0x04040013, +0x01260015, +0x012d012b, +0x0130012e, +0x00000013, +0x01260015, +0x012d012c, +0x0131012e, +0x00000013, +0x0126001a, +0x012d012b, +0x0130012e, +0x02c40013, +0x0126001a, +0x012d012c, +0x0131012e, +0x02c40013, +0x01290000, +0x00860127, +0x00930075, +0x00000000, +0x012a0000, +0x00880128, +0x00950078, +0x00000000, +0x00010013, +0x00010001, +0x01320001, +0x00000000, +0x00010013, +0x00010001, +0x01330001, +0x00000000, +0x0003001b, +0x01340024, +0x01350001, +0x00000000, +0x00010013, +0x00010001, +0x01360001, +0x00000000, +0x010b001b, +0x013a0108, +0x01350001, +0x00000000, +0x00010013, +0x00010001, +0x01390001, +0x00000000, +0x00a7001c, +0x00a60008, +0x009e009c, +0x00000000, +0x00a9001c, +0x00a80025, +0x009f009d, +0x00000000, +0x00a7001d, +0x00a60008, +0x009e009c, +0x00000000, +0x00a9001d, +0x00a80025, +0x009f009d, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00003000, +0x04a40411, +0x045704af, +0x00620461, +0x004d0038, +0x04e204bd, +0x044204ca, +0x04ef05dd, +0x00e7063b, +0x01ea01be, +0x00d10424, +0x028402a2, +0x06b9072f, +0x02ae046c, +0x04900292, +0x01fb0223, +0x00f50002, +0x010f0101, +0x0127011b, +0x013a0133, +0x01490151, +0x01580141, +0x019a0173, +0x01bb0003, +0x01bd01bc, +0x050405bc, +0x000001a6, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00001580, +0x00001580, +0x00001580, +0x0000193f, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00046f03, +0x00000000, +0x00047202, +0x00000000, +0x0006ba03, +0x00000000, +0x0004bc05, +0x00000000, +0x0004c104, +0x00000000, +0x0006b505, +0x00000000, +0x0001dc0c, +0x00000000, +0x0004c528, +0x00000000, +0x00051528, +0x00000000, +0x0006ef06, +0x00000000, +0x00028318, +0x00000000, +0x00019312, +0x00000000, +0x0001a512, +0x00019000, +0x0001b524, +0x00019112, +0x0001a312, +0x00120024, +0x0006dc00, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b50e, +0x0001b80c, +0x0001c304, +0x0001b80c, +0x0001c704, +0x00000000, +0x0001cb02, +0x00000000, +0x0001cd02, +0x0001c40c, +0x0001cf04, +0x0001c40c, +0x0001d304, +0x0001d702, +0x0001d702, +0x00000000, +0x0001e80c, +0x00000000, +0x0001f40c, +0x00000000, +0x0001d00c, +0x00000000, +0x0001c40c, +0x00000000, +0x0001b80c, +0x0001c40c, +0x0001d00c, +0x00000000, +0x0002000c, +0x00000000, +0x0002180c, +0x00000000, +0x0002240c, +0x00000000, +0x0002560c, +0x00026209, +0x00026209, +0x00000000, +0x0001d909, +0x000b0009, +0x00000000, +0x00000000, +0x00020c0c, +0x00000000, +0x0006bd0c, +0x00000000, +0x0006c90c, +0x00000000, +0x0006d50c, +0x00000000, +0x0006e10c, +0x00000000, +0x00029b0c, +0x00028318, +0x0002cb0c, +0x00083900, +0x0002d70c, +0x00000000, +0x0002a70c, +0x00028300, +0x0002e30c, +0x00083900, +0x0002ef0c, +0x00000000, +0x0002b30c, +0x00028318, +0x0002fb0c, +0x00083900, +0x0003070c, +0x00083801, +0x00083901, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00028318, +0x0003130c, +0x00083900, +0x00031f0c, +0x00000000, +0x0002bf0c, +0x00028318, +0x00032b0c, +0x00083900, +0x0003370c, +0x00028318, +0x0003430c, +0x00083900, +0x00034f0c, +0x00028318, +0x00035b0c, +0x00083900, +0x0003670c, +0x00000000, +0x00037378, +0x00000000, +0x0003eb18, +0x00000000, +0x00040318, +0x00000000, +0x00041b18, +0x00000000, +0x00043318, +0x00000000, +0x0004630c, +0x000b0019, +0x00000000, +0x000b0009, +0x00000000, +0x00000000, +0x00047418, +0x00000000, +0x00048c18, +0x00000000, +0x0004a418, +0x000b080c, +0x00000000, +0x000b0808, +0x00000000, +0x000b07f4, +0x00000000, +0x00000000, +0x0004c528, +0x0002ec13, +0x0002ff13, +0x0004c528, +0x00031400, +0x00031500, +0x00020600, +0x00590002, +0x00038c00, +0x00000000, +0x0004ed28, +0x0002ec13, +0x0002ff13, +0x0004ed28, +0x00031200, +0x00031300, +0x00020e00, +0x005e0002, +0x00033c00, +0x00000000, +0x0004ed28, +0x00590004, +0x00038c00, +0x005e0004, +0x00033c00, +0x00000000, +0x0004ed28, +0x00000000, +0x00051528, +0x00041212, +0x00042412, +0x00051528, +0x00043600, +0x00043700, +0x00021e00, +0x0067000c, +0x00047c00, +0x00000000, +0x00053d0c, +0x0002180c, +0x0001b80c, +0x00000000, +0x0005490c, +0x00083900, +0x0005550c, +0x00028318, +0x0005610c, +0x00083900, +0x00056d0c, +0x00059219, +0x00059219, +0x00000000, +0x00046a19, +0x00057919, +0x00057919, +0x00045119, +0x00043819, +0x00000000, +0x0004a20d, +0x00000000, +0x0004af0d, +0x00000000, +0x0004bc07, +0x00000000, +0x0004830d, +0x00000000, +0x0004900d, +0x00000000, +0x00049d05, +0x0005cf0d, +0x0005cf0d, +0x0005dc07, +0x0005dc07, +0x0005e30d, +0x0005e30d, +0x0005f005, +0x0005f005, +0x0005f50d, +0x0005f50d, +0x00060207, +0x00060207, +0x0006090d, +0x0006090d, +0x00061605, +0x00061605, +0x00061b0d, +0x00061b0d, +0x00062807, +0x00062807, +0x00062f0d, +0x00062f0d, +0x00063c05, +0x00063c05, +0x0006410d, +0x0006410d, +0x00064e07, +0x00064e07, +0x0006550d, +0x0006550d, +0x00066205, +0x00066205, +0x00067b0d, +0x00067b0d, +0x00068805, +0x00068805, +0x0006670d, +0x0006670d, +0x00067407, +0x00067407, +0x00000000, +0x00068d28, +0x00000000, +0x0005c902, +0x00000000, +0x0005cb04, +0x00000006, +0x00000000, +0x00000003, +0x00000000, +0x000b000d, +0x00000000, +0x00010007, +0x00000000, +0x000b000d, +0x00000000, +0x00030005, +0x00000000, +0x0005ab0f, +0x0005ab0f, +0x0000000f, +0x00000000, +0x0005ba0f, +0x0005ba0f, +0x00030007, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0005ba01, +0x00000000, +0x0005ba01, +0x000c0364, +0x00000007, +0x002003b4, +0x00000007, +0x00000000, +0x0006da02, +0x00000000, +0x00098802, +0x00000000, +0x0006dc02, +0x00000000, +0x00098a02, +0x0006de02, +0x00098c01, +0x00000000, +0x00000000, +0x00000000, +0x0008c860, +0x00000000, +0x0008c860, +0x00000000, +0x00092860, +0x00000000, +0x00092860, +0x00050c01, +0x00083918, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00072706, +0x00028300, +0x00074518, +0x00075d0b, +0x00075d0b, +0x00000000, +0x0004c90b, +0x00019000, +0x00072d18, +0x00075d0b, +0x00075d0b, +0x00074518, +0x00072706, +0x00c4000b, +0x0000c500, +0x00000000, +0x0006f506, +0x00083900, +0x0006f506, +0x00028318, +0x0006fc18, +0x0004c504, +0x0006fb00, +0x00019000, +0x00071400, +0x00023000, +0x00071512, +0x00ea00cd, +0x000026ff, +0x00000000, +0x0006ef06, +0x0006ee00, +0x0004d40f, +0x00000000, +0x00000000, +0x0006ed00, +0x0004d40f, +0x00078013, +0x00078013, +0x00000000, +0x0004e313, +0x00000000, +0x0004f613, +0x00170013, +0x00000000, +0x00170013, +0x00000000, +0x00079313, +0x00079313, +0x0007a613, +0x0007a613, +0x0007b913, +0x0007b913, +0x00000274, +0x00000000, +0x0000029c, +0x00000000, +0x000002c4, +0x00000000, +0x000002ec, +0x00000000, +0x00000314, +0x00000000, +0x0000033c, +0x00000000, +0x00000364, +0x00000000, +0x0000038c, +0x00000000, +0x000003b4, +0x00000000, +0x000003dc, +0x00000000, +0x00000404, +0x00000000, +0x0000042c, +0x00000000, +0x00000454, +0x00000000, +0x0000047c, +0x00000000, +0x000004a4, +0x00000000, +0x000004cc, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0001ac10, +0x000f00eb, +0x00000000, +0x00000002, +0x00000000, +0x00000000, +0x00019000, +0x00000000, +0x00019a0c, +0x00000000, +0x0007cc02, +0x00000000, +0x0007ce04, +0x00000000, +0x0007d206, +0x00000000, +0x0007d80c, +0x00000000, +0x0007e418, +0x00000000, +0x00044b0c, +0x00000000, +0x0004570c, +0x00000000, +0x0002300c, +0x00000000, +0x00023c0c, +0x00000000, +0x00024802, +0x00000000, +0x00024b04, +0x00000000, +0x00025002, +0x00000000, +0x00025204, +0x00000000, +0x0007fc0c, +0x00028318, +0x0008080c, +0x00083900, +0x0008140c, +0x00000914, +0x00000000, +0x0000091c, +0x00000000, +0x00000924, +0x00000000, +0x0000092c, +0x00000000, +0x00000934, +0x00000000, +0x0000093c, +0x00000000, +0x00000944, +0x00000000, +0x0000094c, +0x00000000, +0x00000954, +0x00000000, +0x0000095c, +0x00000000, +0x00000000, +0x00013d00, +0x00000000, +0x00056000, +0x00013d00, +0x00019000, +0x00001940, +0x00000000, +0x00050c00, +0x00050c01, +0x00000000, +0x00083a28, +0x00000000, +0x00083a28, +0x0002ec13, +0x0002ff13, +0x00083a28, +0x00051000, +0x00051100, +0x00025900, +0x00230002, +0x0002c401, +0x00230004, +0x0002c401, +0x00000000, +0x00086228, +0x00000000, +0x00086228, +0x00000000, +0x00086228, +0x00000000, +0x00088a02, +0x00000000, +0x00088c04, +0x00000000, +0x00024803, +0x00000000, +0x00024b05, +0x0002ec13, +0x0002ff13, +0x00086228, +0x00050e00, +0x00050f00, +0x00026100, +0x002f0002, +0x00040401, +0x002f0004, +0x00040401, +0x000009a4, +0x00000000, +0x000009ac, +0x00000000, +0x000512a8, +0x0008901c, +0x0006001c, +0x00000000, +0x000009b4, +0x00000000, +0x00000000, +0x00100020, +0x000806f0, +0x00000007, +0x000009bc, +0x00000000, +0x000512a8, +0x0008ac1c, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000001, +0x00000010, +0x00000000, +0x00000000, +0x00400000, +0x00400000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000001, +0x00000001, +0x00000001, +0x00000001, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x0000001b, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000003, +0x00000000, +0x00000003, +0x00000000, +0x00000003, +0x00000000, +0x00000006, +0x00000000, +0x00000006, +0x00000000, +0x00000006, +0x00000000, +0x00000009, +0x00000000, +0x00000009, +0x00000000, +0x00000009, +0x00000000, +0x00000005, +0x00000000, +0x00000005, +0x00000000, +0x00000005, +0x00000000, +0x00000007, +0x00000000, +0x00000007, +0x00000000, +0x00000007, +0x00000000, +0x00000008, +0x00000000, +0x00000008, +0x00000000, +0x00000008, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00e66666, +0x00199999, +0x00199999, +0x00199999, +0x00000000, +0x00000000, +0x00e66666, +0x00e66666, +0x00ffffff, +0x00ffffff, +0x00199999, +0x00199999, +0x00f33333, +0x000ccccc, +0x00f33333, +0x00f33333, +0x00199999, +0x00e66666, +0x00f33333, +0x00f33333, +0x00f33333, +0x000ccccc, +0x00199999, +0x00199999, +0x000ccccc, +0x00162b95, +0x00f33333, +0x000ccccc, +0x00e66666, +0x00000000, +0x00f33333, +0x00f33333, +0x000ccccc, +0x00e9d46a, +0x00199999, +0x00e66666, +0x000ccccc, +0x00e9d46a, +0x00f33333, +0x00f33333, +0x00e66666, +0x00ffffff, +0x00f33333, +0x000ccccc, +0x000ccccc, +0x00162b95, +0x00199999, +0x00199999, +0x00162b95, +0x0018ba4a, +0x000ccccc, +0x00162b95, +0x00000000, +0x00121a18, +0x00f33333, +0x000ccccc, +0x00e9d46a, +0x0006a032, +0x00e66666, +0x00000000, +0x00e9d46a, +0x00f95fcd, +0x00f33333, +0x00f33333, +0x00ffffff, +0x00ede5e7, +0x000ccccc, +0x00e9d46a, +0x00162b95, +0x00e745b5, +0x00199999, +0x00e66666, +0x00162b95, +0x00e745b5, +0x000ccccc, +0x00e9d46a, +0x00000000, +0x00ede5e7, +0x00f33333, +0x00f33333, +0x00e9d46a, +0x00f95fcf, +0x00e66666, +0x00ffffff, +0x00e9d46a, +0x0006a032, +0x00f33333, +0x000ccccc, +0x00ffffff, +0x00121a18, +0x000ccccc, +0x00162b95, +0x00162b95, +0x0018ba4a, +0x00199999, +0x00199999, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x007fffff, +0x00800000, +0x001fffff, +0x00e00000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x00000000, +0x000049cc, +0x000049cc, diff --git a/sound/soc/omap/abe/abe_functionsid.h b/sound/soc/omap/abe/abe_functionsid.h new file mode 100644 index 0000000000000000000000000000000000000000..db219886a19c9bf9ffe5ddeb34d6b587ff10af33 --- /dev/null +++ b/sound/soc/omap/abe/abe_functionsid.h @@ -0,0 +1,117 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifndef _ABE_FUNCTIONSID_H_ +#define _ABE_FUNCTIONSID_H_ +/* + * TASK function ID definitions + */ +#define C_ABE_FW_FUNCTION_IIR 0 +#define C_ABE_FW_FUNCTION_monoToStereoPack 1 +#define C_ABE_FW_FUNCTION_stereoToMonoSplit 2 +#define C_ABE_FW_FUNCTION_decimator 3 +#define C_ABE_FW_FUNCTION_OS0Fill 4 +#define C_ABE_FW_FUNCTION_mixer2 5 +#define C_ABE_FW_FUNCTION_mixer4 6 +#define C_ABE_FW_FUNCTION_mixer4_dual_mono 7 +#define C_ABE_FW_FUNCTION_inplaceGain 8 +#define C_ABE_FW_FUNCTION_StreamRouting 9 +#define C_ABE_FW_FUNCTION_gainConverge 10 +#define C_ABE_FW_FUNCTION_dualIir 11 +#define C_ABE_FW_FUNCTION_IO_DL_pp 12 +#define C_ABE_FW_FUNCTION_IO_generic 13 +#define C_ABE_FW_FUNCTION_irq_fifo_debug 14 +#define C_ABE_FW_FUNCTION_synchronize_pointers 15 +#define C_ABE_FW_FUNCTION_VIBRA2 16 +#define C_ABE_FW_FUNCTION_VIBRA1 17 +#define C_ABE_FW_FUNCTION_IIR_SRC_MIC 18 +#define C_ABE_FW_FUNCTION_wrappers 19 +#define C_ABE_FW_FUNCTION_ASRC_DL_wrapper 20 +#define C_ABE_FW_FUNCTION_ASRC_UL_wrapper 21 +#define C_ABE_FW_FUNCTION_mem_init 22 +#define C_ABE_FW_FUNCTION_debug_vx_asrc 23 +#define C_ABE_FW_FUNCTION_IIR_SRC2 24 +#define C_ABE_FW_FUNCTION_ASRC_DL_wrapper_sibling 25 +#define C_ABE_FW_FUNCTION_ASRC_UL_wrapper_sibling 26 +#define C_ABE_FW_FUNCTION_FIR6 27 +#define C_ABE_FW_FUNCTION_SRC44P1 28 +#define C_ABE_FW_FUNCTION_SRC44P1_1211 29 +/* + * COPY function ID definitions + */ +#define NULL_COPY_CFPID 0 +#define S2D_STEREO_16_16_CFPID 1 +#define S2D_MONO_MSB_CFPID 2 +#define S2D_STEREO_MSB_CFPID 3 +#define S2D_STEREO_RSHIFTED_16_CFPID 4 +#define S2D_MONO_RSHIFTED_16_CFPID 5 +#define D2S_STEREO_16_16_CFPID 6 +#define D2S_MONO_MSB_CFPID 7 +#define D2S_MONO_RSHIFTED_16_CFPID 8 +#define D2S_STEREO_RSHIFTED_16_CFPID 9 +#define D2S_STEREO_MSB_CFPID 10 +#define COPY_DMIC_CFPID 11 +#define COPY_MCPDM_DL_CFPID 12 +#define COPY_MM_UL_CFPID 13 +#define SPLIT_SMEM_CFPID 14 +#define MERGE_SMEM_CFPID 15 +#define SPLIT_TDM_CFPID 16 +#define MERGE_TDM_CFPID 17 +#define ROUTE_MM_UL_CFPID 18 +#define IO_IP_CFPID 19 +#define COPY_UNDERFLOW_CFPID 20 +#endif /* _ABE_FUNCTIONSID_H_ */ diff --git a/sound/soc/omap/abe/abe_fw.h b/sound/soc/omap/abe/abe_fw.h new file mode 100644 index 0000000000000000000000000000000000000000..a29dbb39892d63aeac13016d3a576887f9c9f445 --- /dev/null +++ b/sound/soc/omap/abe/abe_fw.h @@ -0,0 +1,214 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_FW_H_ +#define _ABE_FW_H_ + +#include "abe_cm_addr.h" +#include "abe_sm_addr.h" +#include "abe_dm_addr.h" +#include "abe_typedef.h" +/* + * GLOBAL DEFINITION + */ +/* one scheduler loop = 4kHz = 12 samples at 48kHz */ +#define FW_SCHED_LOOP_FREQ 4000 +/* one scheduler loop = 4kHz = 12 samples at 48kHz */ +#define FW_SCHED_LOOP_FREQ_DIV1000 (FW_SCHED_LOOP_FREQ/1000) +#define EVENT_FREQUENCY 96000 +#define SLOTS_IN_SCHED_LOOP (96000/FW_SCHED_LOOP_FREQ) +#define SCHED_LOOP_8kHz (8000/FW_SCHED_LOOP_FREQ) +#define SCHED_LOOP_16kHz (16000/FW_SCHED_LOOP_FREQ) +#define SCHED_LOOP_24kHz (24000/FW_SCHED_LOOP_FREQ) +#define SCHED_LOOP_48kHz (48000/FW_SCHED_LOOP_FREQ) +#define TASKS_IN_SLOT 8 +/* + * DMEM AREA - SCHEDULER + */ +#define dmem_mm_trace OMAP_ABE_D_DEBUG_FIFO_ADDR +#define dmem_mm_trace_size ((OMAP_ABE_D_DEBUG_FIFO_SIZE)/4) +#define ATC_SIZE 8 /* 8 bytes per descriptors */ +struct omap_abe_atc_desc { + unsigned rdpt:7; /* first 32bits word of the descriptor */ + unsigned reserved0:1; + unsigned cbsize:7; + unsigned irqdest:1; + unsigned cberr:1; + unsigned reserved1:5; + unsigned cbdir:1; + unsigned nw:1; + unsigned wrpt:7; + unsigned reserved2:1; + unsigned badd:12; /* second 32bits word of the descriptor */ + unsigned iter:7; /* iteration field overlaps 16-bit boundary */ + unsigned srcid:6; + unsigned destid:6; + unsigned desen:1; +}; +/* + * Infinite counter incremented on each sheduler periods (~250 us) + * uint16 dmem_debug_time_stamp + */ +#define dmem_debug_time_stamp OMAP_ABE_D_LOOPCOUNTER_ADDR +/* + * ATC BUFFERS + IO TASKS SMEM buffers + */ +#define dmem_dmic OMAP_ABE_D_DMIC_UL_FIFO_ADDR +#define dmem_dmic_size (OMAP_ABE_D_DMIC_UL_FIFO_SIZE/4) +#define dmem_amic OMAP_ABE_D_MCPDM_UL_FIFO_ADDR +#define dmem_amic_size (OMAP_ABE_D_MCPDM_UL_FIFO_SIZE/4) +#define smem_amic AMIC_96_labelID +#define dmem_mcpdm OMAP_ABE_D_MCPDM_DL_FIFO_ADDR +#define dmem_mcpdm_size (OMAP_ABE_D_MCPDM_DL_FIFO_SIZE/4) +#define dmem_mm_ul OMAP_ABE_D_MM_UL_FIFO_ADDR +#define dmem_mm_ul_size (OMAP_ABE_D_MM_UL_FIFO_SIZE/4) +/* managed directly by the router */ +#define smem_mm_ul MM_UL_labelID +#define dmem_mm_ul2 OMAP_ABE_D_MM_UL2_FIFO_ADDR +#define dmem_mm_ul2_size (OMAP_ABE_D_MM_UL2_FIFO_SIZE/4) +/* managed directly by the router */ +#define smem_mm_ul2 MM_UL2_labelID +#define dmem_mm_dl OMAP_ABE_D_MM_DL_FIFO_ADDR +#define dmem_mm_dl_size (OMAP_ABE_D_MM_DL_FIFO_SIZE/4) +#define smem_mm_dl MM_DL_labelID +#define dmem_vx_dl OMAP_ABE_D_VX_DL_FIFO_ADDR +#define dmem_vx_dl_size (OMAP_ABE_D_VX_DL_FIFO_SIZE/4) +#define smem_vx_dl IO_VX_DL_ASRC_labelID /* Voice_16k_DL_labelID */ +#define dmem_vx_ul OMAP_ABE_D_VX_UL_FIFO_ADDR +#define dmem_vx_ul_size (OMAP_ABE_D_VX_UL_FIFO_SIZE/4) +#define smem_vx_ul Voice_8k_UL_labelID +#define dmem_tones_dl OMAP_ABE_D_TONES_DL_FIFO_ADDR +#define dmem_tones_dl_size (OMAP_ABE_D_TONES_DL_FIFO_SIZE/4) +#define smem_tones_dl Tones_labelID +#define dmem_vib_dl OMAP_ABE_D_VIB_DL_FIFO_ADDR +#define dmem_vib_dl_size (OMAP_ABE_D_VIB_DL_FIFO_SIZE/4) +#define smem_vib IO_VIBRA_DL_labelID +#define dmem_mm_ext_out OMAP_ABE_D_MM_EXT_OUT_FIFO_ADDR +#define dmem_mm_ext_out_size (OMAP_ABE_D_MM_EXT_OUT_FIFO_SIZE/4) +#define smem_mm_ext_out DL1_GAIN_out_labelID +#define dmem_mm_ext_in OMAP_ABE_D_MM_EXT_IN_FIFO_ADDR +#define dmem_mm_ext_in_size (OMAP_ABE_D_MM_EXT_IN_FIFO_SIZE/4) +/*IO_MM_EXT_IN_ASRC_labelID ASRC input buffer, size 40 */ +#define smem_mm_ext_in_opp100 IO_MM_EXT_IN_ASRC_labelID +/* at OPP 50 without ASRC */ +#define smem_mm_ext_in_opp50 MM_EXT_IN_labelID +#define dmem_bt_vx_dl OMAP_ABE_D_BT_DL_FIFO_ADDR +#define dmem_bt_vx_dl_size (OMAP_ABE_D_BT_DL_FIFO_SIZE/4) +#define smem_bt_vx_dl_opp50 BT_DL_8k_labelID +/*BT_DL_8k_opp100_labelID ASRC output buffer, size 40 */ +#define smem_bt_vx_dl_opp100 BT_DL_8k_opp100_labelID +#define dmem_bt_vx_ul OMAP_ABE_D_BT_UL_FIFO_ADDR +#define dmem_bt_vx_ul_size (OMAP_ABE_D_BT_UL_FIFO_SIZE/4) +#define smem_bt_vx_ul_opp50 BT_UL_8k_labelID +/*IO_BT_UL_ASRC_labelID ASRC input buffer, size 40 */ +#define smem_bt_vx_ul_opp100 IO_BT_UL_ASRC_labelID +/* + * SMEM AREA + */ +/* + * GAIN SMEM on PORT + * int32 smem_G0 [18] : desired gain on the ports + * format of G0 = 6 bits left shifted desired gain in linear 24bits format + * int24 stereo G0 [18] = G0 + * int24 stereo GI [18] current value of the gain in the same format of G0 + * List of smoothed gains : + * 6 DMIC 0 1 2 3 4 5 + * 2 AMIC L R + * 4 PORT1/2_RX L R + * 2 MM_EXT L R + * 2 MM_VX_DL L R + * 2 IHF L R + * --------------- + * 18 = TOTAL + */ +/* + * COEFFICIENTS AREA + */ +/* + * delay coefficients used in the IIR-1 filters + * int24 cmem_gain_delay_iir1[9 x 2] (a, (1-a)) + * + * 3 for 6 DMIC 0 1 2 3 4 5 + * 1 for 2 AMIC L R + * 2 for 4 PORT1/2_RX L R + * 1 for 2 MM_EXT L R + * 1 for 2 MM_VX_DL L R + * 1 for 2 IHF L R + */ +/* + * gain controls + */ +#define GAIN_LEFT_OFFSET 0 +#define GAIN_RIGHT_OFFSET 1 +/* stereo gains */ +#define dmic1_gains_offset 0 +#define dmic2_gains_offset 2 +#define dmic3_gains_offset 4 +#define amic_gains_offset 6 +#define dl1_gains_offset 8 +#define dl2_gains_offset 10 +#define splitters_gains_offset 12 +#define mixer_dl1_offset 14 +#define mixer_dl2_offset 18 +#define mixer_echo_offset 22 +#define mixer_sdt_offset 24 +#define mixer_vxrec_offset 26 +#define mixer_audul_offset 30 +#define btul_gains_offset 34 + +#endif/* _ABE_FW_H_ */ diff --git a/sound/soc/omap/abe/abe_gain.c b/sound/soc/omap/abe/abe_gain.c new file mode 100644 index 0000000000000000000000000000000000000000..9c148da365cdd53ae9cdefb04cac420e09046d26 --- /dev/null +++ b/sound/soc/omap/abe/abe_gain.c @@ -0,0 +1,790 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_dbg.h" +#include "abe.h" +#include "abe_gain.h" +#include "abe_mem.h" + +/* + * ABE CONST AREA FOR PARAMETERS TRANSLATION + */ +#define min_mdb (-12000) +#define max_mdb (3000) +#define sizeof_db2lin_table (1 + ((max_mdb - min_mdb)/100)) + +const u32 abe_db2lin_table[sizeof_db2lin_table] = { + 0x00000000, /* SMEM coding of -120 dB */ + 0x00000000, /* SMEM coding of -119 dB */ + 0x00000000, /* SMEM coding of -118 dB */ + 0x00000000, /* SMEM coding of -117 dB */ + 0x00000000, /* SMEM coding of -116 dB */ + 0x00000000, /* SMEM coding of -115 dB */ + 0x00000000, /* SMEM coding of -114 dB */ + 0x00000000, /* SMEM coding of -113 dB */ + 0x00000000, /* SMEM coding of -112 dB */ + 0x00000000, /* SMEM coding of -111 dB */ + 0x00000000, /* SMEM coding of -110 dB */ + 0x00000000, /* SMEM coding of -109 dB */ + 0x00000001, /* SMEM coding of -108 dB */ + 0x00000001, /* SMEM coding of -107 dB */ + 0x00000001, /* SMEM coding of -106 dB */ + 0x00000001, /* SMEM coding of -105 dB */ + 0x00000001, /* SMEM coding of -104 dB */ + 0x00000001, /* SMEM coding of -103 dB */ + 0x00000002, /* SMEM coding of -102 dB */ + 0x00000002, /* SMEM coding of -101 dB */ + 0x00000002, /* SMEM coding of -100 dB */ + 0x00000002, /* SMEM coding of -99 dB */ + 0x00000003, /* SMEM coding of -98 dB */ + 0x00000003, /* SMEM coding of -97 dB */ + 0x00000004, /* SMEM coding of -96 dB */ + 0x00000004, /* SMEM coding of -95 dB */ + 0x00000005, /* SMEM coding of -94 dB */ + 0x00000005, /* SMEM coding of -93 dB */ + 0x00000006, /* SMEM coding of -92 dB */ + 0x00000007, /* SMEM coding of -91 dB */ + 0x00000008, /* SMEM coding of -90 dB */ + 0x00000009, /* SMEM coding of -89 dB */ + 0x0000000A, /* SMEM coding of -88 dB */ + 0x0000000B, /* SMEM coding of -87 dB */ + 0x0000000D, /* SMEM coding of -86 dB */ + 0x0000000E, /* SMEM coding of -85 dB */ + 0x00000010, /* SMEM coding of -84 dB */ + 0x00000012, /* SMEM coding of -83 dB */ + 0x00000014, /* SMEM coding of -82 dB */ + 0x00000017, /* SMEM coding of -81 dB */ + 0x0000001A, /* SMEM coding of -80 dB */ + 0x0000001D, /* SMEM coding of -79 dB */ + 0x00000021, /* SMEM coding of -78 dB */ + 0x00000025, /* SMEM coding of -77 dB */ + 0x00000029, /* SMEM coding of -76 dB */ + 0x0000002E, /* SMEM coding of -75 dB */ + 0x00000034, /* SMEM coding of -74 dB */ + 0x0000003A, /* SMEM coding of -73 dB */ + 0x00000041, /* SMEM coding of -72 dB */ + 0x00000049, /* SMEM coding of -71 dB */ + 0x00000052, /* SMEM coding of -70 dB */ + 0x0000005D, /* SMEM coding of -69 dB */ + 0x00000068, /* SMEM coding of -68 dB */ + 0x00000075, /* SMEM coding of -67 dB */ + 0x00000083, /* SMEM coding of -66 dB */ + 0x00000093, /* SMEM coding of -65 dB */ + 0x000000A5, /* SMEM coding of -64 dB */ + 0x000000B9, /* SMEM coding of -63 dB */ + 0x000000D0, /* SMEM coding of -62 dB */ + 0x000000E9, /* SMEM coding of -61 dB */ + 0x00000106, /* SMEM coding of -60 dB */ + 0x00000126, /* SMEM coding of -59 dB */ + 0x0000014A, /* SMEM coding of -58 dB */ + 0x00000172, /* SMEM coding of -57 dB */ + 0x0000019F, /* SMEM coding of -56 dB */ + 0x000001D2, /* SMEM coding of -55 dB */ + 0x0000020B, /* SMEM coding of -54 dB */ + 0x0000024A, /* SMEM coding of -53 dB */ + 0x00000292, /* SMEM coding of -52 dB */ + 0x000002E2, /* SMEM coding of -51 dB */ + 0x0000033C, /* SMEM coding of -50 dB */ + 0x000003A2, /* SMEM coding of -49 dB */ + 0x00000413, /* SMEM coding of -48 dB */ + 0x00000492, /* SMEM coding of -47 dB */ + 0x00000521, /* SMEM coding of -46 dB */ + 0x000005C2, /* SMEM coding of -45 dB */ + 0x00000676, /* SMEM coding of -44 dB */ + 0x0000073F, /* SMEM coding of -43 dB */ + 0x00000822, /* SMEM coding of -42 dB */ + 0x00000920, /* SMEM coding of -41 dB */ + 0x00000A3D, /* SMEM coding of -40 dB */ + 0x00000B7D, /* SMEM coding of -39 dB */ + 0x00000CE4, /* SMEM coding of -38 dB */ + 0x00000E76, /* SMEM coding of -37 dB */ + 0x0000103A, /* SMEM coding of -36 dB */ + 0x00001235, /* SMEM coding of -35 dB */ + 0x0000146E, /* SMEM coding of -34 dB */ + 0x000016EC, /* SMEM coding of -33 dB */ + 0x000019B8, /* SMEM coding of -32 dB */ + 0x00001CDC, /* SMEM coding of -31 dB */ + 0x00002061, /* SMEM coding of -30 dB */ + 0x00002455, /* SMEM coding of -29 dB */ + 0x000028C4, /* SMEM coding of -28 dB */ + 0x00002DBD, /* SMEM coding of -27 dB */ + 0x00003352, /* SMEM coding of -26 dB */ + 0x00003995, /* SMEM coding of -25 dB */ + 0x0000409C, /* SMEM coding of -24 dB */ + 0x0000487E, /* SMEM coding of -23 dB */ + 0x00005156, /* SMEM coding of -22 dB */ + 0x00005B43, /* SMEM coding of -21 dB */ + 0x00006666, /* SMEM coding of -20 dB */ + 0x000072E5, /* SMEM coding of -19 dB */ + 0x000080E9, /* SMEM coding of -18 dB */ + 0x000090A4, /* SMEM coding of -17 dB */ + 0x0000A24B, /* SMEM coding of -16 dB */ + 0x0000B618, /* SMEM coding of -15 dB */ + 0x0000CC50, /* SMEM coding of -14 dB */ + 0x0000E53E, /* SMEM coding of -13 dB */ + 0x00010137, /* SMEM coding of -12 dB */ + 0x0001209A, /* SMEM coding of -11 dB */ + 0x000143D1, /* SMEM coding of -10 dB */ + 0x00016B54, /* SMEM coding of -9 dB */ + 0x000197A9, /* SMEM coding of -8 dB */ + 0x0001C967, /* SMEM coding of -7 dB */ + 0x00020137, /* SMEM coding of -6 dB */ + 0x00023FD6, /* SMEM coding of -5 dB */ + 0x00028619, /* SMEM coding of -4 dB */ + 0x0002D4EF, /* SMEM coding of -3 dB */ + 0x00032D64, /* SMEM coding of -2 dB */ + 0x000390A4, /* SMEM coding of -1 dB */ + 0x00040000, /* SMEM coding of 0 dB */ + 0x00047CF2, /* SMEM coding of 1 dB */ + 0x00050923, /* SMEM coding of 2 dB */ + 0x0005A670, /* SMEM coding of 3 dB */ + 0x000656EE, /* SMEM coding of 4 dB */ + 0x00071CF5, /* SMEM coding of 5 dB */ + 0x0007FB26, /* SMEM coding of 6 dB */ + 0x0008F473, /* SMEM coding of 7 dB */ + 0x000A0C2B, /* SMEM coding of 8 dB */ + 0x000B4606, /* SMEM coding of 9 dB */ + 0x000CA62C, /* SMEM coding of 10 dB */ + 0x000E314A, /* SMEM coding of 11 dB */ + 0x000FEC9E, /* SMEM coding of 12 dB */ + 0x0011DE0A, /* SMEM coding of 13 dB */ + 0x00140C28, /* SMEM coding of 14 dB */ + 0x00167E60, /* SMEM coding of 15 dB */ + 0x00193D00, /* SMEM coding of 16 dB */ + 0x001C515D, /* SMEM coding of 17 dB */ + 0x001FC5EB, /* SMEM coding of 18 dB */ + 0x0023A668, /* SMEM coding of 19 dB */ + 0x00280000, /* SMEM coding of 20 dB */ + 0x002CE178, /* SMEM coding of 21 dB */ + 0x00325B65, /* SMEM coding of 22 dB */ + 0x00388062, /* SMEM coding of 23 dB */ + 0x003F654E, /* SMEM coding of 24 dB */ + 0x00472194, /* SMEM coding of 25 dB */ + 0x004FCF7C, /* SMEM coding of 26 dB */ + 0x00598C81, /* SMEM coding of 27 dB */ + 0x006479B7, /* SMEM coding of 28 dB */ + 0x0070BC3D, /* SMEM coding of 29 dB */ + 0x007E7DB9, /* SMEM coding of 30 dB */ +}; + +const u32 abe_1_alpha_iir[64] = { + 0x040002, 0x040002, 0x040002, 0x040002, /* 0 */ + 0x50E955, 0x48CA65, 0x40E321, 0x72BE78, /* 1 [ms] */ + 0x64BA68, 0x57DF14, 0x4C3D60, 0x41D690, /* 2 */ + 0x38A084, 0x308974, 0x297B00, 0x235C7C, /* 4 */ + 0x1E14B0, 0x198AF0, 0x15A800, 0x125660, /* 8 */ + 0x0F82A0, 0x0D1B5C, 0x0B113C, 0x0956CC, /* 16 */ + 0x07E054, 0x06A3B8, 0x059844, 0x04B680, /* 32 */ + 0x03F80C, 0x035774, 0x02D018, 0x025E0C, /* 64 */ + 0x7F8057, 0x6B482F, 0x5A4297, 0x4BEECB, /* 128 */ + 0x3FE00B, 0x35BAA7, 0x2D3143, 0x2602AF, /* 256 */ + 0x1FF803, 0x1AE2FB, 0x169C9F, 0x13042B, /* 512 */ + 0x0FFE03, 0x0D72E7, 0x0B4F4F, 0x0982CB, /* 1.024 [s] */ + 0x07FF83, 0x06B9CF, 0x05A7E7, 0x04C193, /* 2.048 */ + 0x03FFE3, 0x035CFF, 0x02D403, 0x0260D7, /* 4.096 */ + 0x01FFFB, 0x01AE87, 0x016A07, 0x01306F, /* 8.192 */ + 0x00FFFF, 0x00D743, 0x00B503, 0x009837, +}; + +const u32 abe_alpha_iir[64] = { + 0x000000, 0x000000, 0x000000, 0x000000, /* 0 */ + 0x5E2D58, 0x6E6B3C, 0x7E39C0, 0x46A0C5, /* 1 [ms] */ + 0x4DA2CD, 0x541079, 0x59E151, 0x5F14B9, /* 2 */ + 0x63AFC1, 0x67BB45, 0x6B4281, 0x6E51C1, /* 4 */ + 0x70F5A9, 0x733A89, 0x752C01, 0x76D4D1, /* 8 */ + 0x783EB1, 0x797251, 0x7A7761, 0x7B549D, /* 16 */ + 0x7C0FD5, 0x7CAE25, 0x7D33DD, 0x7DA4C1, /* 32 */ + 0x7E03FD, 0x7E5449, 0x7E97F5, 0x7ED0F9, /* 64 */ + 0x7F0101, 0x7F2971, 0x7F4B7D, 0x7F6825, /* 128 */ + 0x7F8041, 0x7F948D, 0x7FA59D, 0x7FB3FD, /* 256 */ + 0x7FC011, 0x7FCA3D, 0x7FD2C9, 0x7FD9F9, /* 512 */ + 0x7FE005, 0x7FE51D, 0x7FE961, 0x7FECFD, /* 1.024 [s] */ + 0x7FF001, 0x7FF28D, 0x7FF4B1, 0x7FF67D, /* 2.048 */ + 0x7FF801, 0x7FF949, 0x7FFA59, 0x7FFB41, /* 4.096 */ + 0x7FFC01, 0x7FFCA5, 0x7FFD2D, 0x7FFDA1, /* 8.192 */ + 0x7FFE01, 0x7FFE51, 0x7FFE95, 0x7FFED1, +}; + +/** + * abe_int_2_float + * returns a mantissa on 16 bits and the exponent + * 0x4000.0000 leads to M=0x4000 X=15 + * 0x0004.0000 leads to M=0x4000 X=4 + * 0x0000.0001 leads to M=0x4000 X=-14 + * + */ +void abe_int_2_float16(u32 data, u32 *mantissa, u32 *exp) +{ + u32 i; + *exp = 0; + *mantissa = 0; + for (i = 0; i < 32; i++) { + if ((1 << i) > data) + break; + } + *exp = i - 15; + *mantissa = (*exp > 0) ? data >> (*exp) : data << (*exp); +} + +/** + * abe_use_compensated_gain + * @on_off: + * + * Selects the automatic Mixer's gain management + * on_off = 1 allows the "abe_write_gain" to adjust the overall + * gains of the mixer to be tuned not to create saturation + */ +int omap_abe_use_compensated_gain(struct omap_abe *abe, int on_off) +{ + abe->compensated_mixer_gain = on_off; + return 0; +} +EXPORT_SYMBOL(omap_abe_use_compensated_gain); + +/** + * omap_abe_gain_offset + * returns the offset to firmware data structures + * + */ +void omap_abe_gain_offset(struct omap_abe *abe, u32 id, u32 *mixer_offset) +{ + switch (id) { + default: + case GAINS_DMIC1: + *mixer_offset = dmic1_gains_offset; + break; + case GAINS_DMIC2: + *mixer_offset = dmic2_gains_offset; + break; + case GAINS_DMIC3: + *mixer_offset = dmic3_gains_offset; + break; + case GAINS_AMIC: + *mixer_offset = amic_gains_offset; + break; + case GAINS_DL1: + *mixer_offset = dl1_gains_offset; + break; + case GAINS_DL2: + *mixer_offset = dl2_gains_offset; + break; + case GAINS_SPLIT: + *mixer_offset = splitters_gains_offset; + break; + case MIXDL1: + *mixer_offset = mixer_dl1_offset; + break; + case MIXDL2: + *mixer_offset = mixer_dl2_offset; + break; + case MIXECHO: + *mixer_offset = mixer_echo_offset; + break; + case MIXSDT: + *mixer_offset = mixer_sdt_offset; + break; + case MIXVXREC: + *mixer_offset = mixer_vxrec_offset; + break; + case MIXAUDUL: + *mixer_offset = mixer_audul_offset; + break; + case GAINS_BTUL: + *mixer_offset = btul_gains_offset; + break; + } +} + +/** + * oamp_abe_write_equalizer + * @abe: Pointer on abe handle + * @id: name of the equalizer + * @param : equalizer coefficients + * + * Load the coefficients in CMEM. + */ +int omap_abe_write_equalizer(struct omap_abe *abe, + u32 id, struct omap_abe_equ *param) +{ + u32 eq_offset, length, *src, eq_mem, eq_mem_len; + _log(ABE_ID_WRITE_EQUALIZER, id, 0, 0); + switch (id) { + default: + case EQ1: + eq_offset = OMAP_ABE_C_DL1_COEFS_ADDR; + eq_mem = OMAP_ABE_S_DL1_M_EQ_DATA_ADDR; + eq_mem_len = OMAP_ABE_S_DL1_M_EQ_DATA_SIZE; + break; + case EQ2L: + eq_offset = OMAP_ABE_C_DL2_L_COEFS_ADDR; + eq_mem = OMAP_ABE_S_DL2_M_LR_EQ_DATA_ADDR; + eq_mem_len = OMAP_ABE_S_DL2_M_LR_EQ_DATA_SIZE; + break; + case EQ2R: + eq_offset = OMAP_ABE_C_DL2_R_COEFS_ADDR; + eq_mem = OMAP_ABE_S_DL2_M_LR_EQ_DATA_ADDR; + eq_mem_len = OMAP_ABE_S_DL2_M_LR_EQ_DATA_SIZE; + break; + case EQSDT: + eq_offset = OMAP_ABE_C_SDT_COEFS_ADDR; + eq_mem = OMAP_ABE_S_SDT_F_DATA_ADDR; + eq_mem_len = OMAP_ABE_S_SDT_F_DATA_SIZE; + break; + case EQAMIC: + eq_offset = OMAP_ABE_C_96_48_AMIC_COEFS_ADDR; + eq_mem = OMAP_ABE_S_AMIC_96_48_DATA_ADDR; + eq_mem_len = OMAP_ABE_S_AMIC_96_48_DATA_SIZE; + break; + case EQDMIC: + eq_offset = OMAP_ABE_C_96_48_DMIC_COEFS_ADDR; + eq_mem = OMAP_ABE_S_DMIC0_96_48_DATA_ADDR; + eq_mem_len = OMAP_ABE_S_DMIC0_96_48_DATA_SIZE; + /* three DMIC are clear at the same time DMIC0 DMIC1 DMIC2 */ + eq_mem_len *= 3; + break; + } + /* reset SMEM buffers before the coefficients are loaded */ + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, eq_mem, eq_mem_len); + + length = param->equ_length; + src = (u32 *) ((param->coef).type1); + /* translate in bytes */ + length <<= 2; + omap_abe_mem_write(abe, OMAP_ABE_CMEM, eq_offset, src, length); + + /* reset SMEM buffers after the coefficients are loaded */ + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, eq_mem, eq_mem_len); + return 0; +} +EXPORT_SYMBOL(omap_abe_write_equalizer); + +/** + * omap_abe_disable_gain + * @abe: Pointer on abe handle + * Parameters: + * mixer id + * sub-port id + * + */ +int omap_abe_disable_gain(struct omap_abe *abe, u32 id, u32 p) +{ + u32 mixer_offset, f_g, ramp; + omap_abe_gain_offset(abe, id, &mixer_offset); + /* save the input parameters for mute/unmute */ + ramp = abe->desired_ramp_delay_ms[mixer_offset + p]; + f_g = GAIN_MUTE; + if (!(abe->muted_gains_indicator[mixer_offset + p] & + OMAP_ABE_GAIN_DISABLED)) { + /* Check if we are in mute */ + if (!(abe->muted_gains_indicator[mixer_offset + p] & + OMAP_ABE_GAIN_MUTED)) { + abe->muted_gains_decibel[mixer_offset + p] = + abe->desired_gains_decibel[mixer_offset + p]; + /* mute the gain */ + omap_abe_write_gain(abe, id, f_g, ramp, p); + } + abe->muted_gains_indicator[mixer_offset + p] |= + OMAP_ABE_GAIN_DISABLED; + } + return 0; +} +EXPORT_SYMBOL(omap_abe_disable_gain); + +/** + * omap_abe_enable_gain + * Parameters: + * mixer id + * sub-port id + * + */ +int omap_abe_enable_gain(struct omap_abe *abe, u32 id, u32 p) +{ + u32 mixer_offset, f_g, ramp; + omap_abe_gain_offset(abe, id, &mixer_offset); + if ((abe->muted_gains_indicator[mixer_offset + p] & + OMAP_ABE_GAIN_DISABLED)) { + /* restore the input parameters for mute/unmute */ + f_g = abe->muted_gains_decibel[mixer_offset + p]; + ramp = abe->desired_ramp_delay_ms[mixer_offset + p]; + abe->muted_gains_indicator[mixer_offset + p] &= + ~OMAP_ABE_GAIN_DISABLED; + /* unmute the gain */ + omap_abe_write_gain(abe, id, f_g, ramp, p); + } + return 0; +} +EXPORT_SYMBOL(omap_abe_enable_gain); +/** + * omap_abe_mute_gain + * Parameters: + * mixer id + * sub-port id + * + */ +int omap_abe_mute_gain(struct omap_abe *abe, u32 id, u32 p) +{ + u32 mixer_offset, f_g, ramp; + omap_abe_gain_offset(abe, id, &mixer_offset); + /* save the input parameters for mute/unmute */ + ramp = abe->desired_ramp_delay_ms[mixer_offset + p]; + f_g = GAIN_MUTE; + if (!abe->muted_gains_indicator[mixer_offset + p]) { + abe->muted_gains_decibel[mixer_offset + p] = + abe->desired_gains_decibel[mixer_offset + p]; + /* mute the gain */ + omap_abe_write_gain(abe, id, f_g, ramp, p); + } + abe->muted_gains_indicator[mixer_offset + p] |= OMAP_ABE_GAIN_MUTED; + return 0; +} +EXPORT_SYMBOL(omap_abe_mute_gain); +/** + * omap_abe_unmute_gain + * Parameters: + * mixer id + * sub-port id + * + */ +int omap_abe_unmute_gain(struct omap_abe *abe, u32 id, u32 p) +{ + u32 mixer_offset, f_g, ramp; + omap_abe_gain_offset(abe, id, &mixer_offset); + if ((abe->muted_gains_indicator[mixer_offset + p] & + OMAP_ABE_GAIN_MUTED)) { + /* restore the input parameters for mute/unmute */ + f_g = abe->muted_gains_decibel[mixer_offset + p]; + ramp = abe->desired_ramp_delay_ms[mixer_offset + p]; + abe->muted_gains_indicator[mixer_offset + p] &= + ~OMAP_ABE_GAIN_MUTED; + /* unmute the gain */ + omap_abe_write_gain(abe, id, f_g, ramp, p); + } + return 0; +} +EXPORT_SYMBOL(omap_abe_unmute_gain); + +/** + * omap_abe_write_gain + * @id: gain name or mixer name + * @f_g: list of input gains of the mixer + * @ramp: gain ramp speed factor + * @p: list of ports corresponding to the above gains + * + * Loads the gain coefficients to FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's gain + * in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +int omap_abe_write_gain(struct omap_abe *abe, + u32 id, s32 f_g, u32 ramp, u32 p) +{ + u32 lin_g, sum_g, mixer_target, mixer_offset, i, mean_gain, mean_exp; + u32 new_gain_linear[4]; + s32 gain_index; + u32 alpha, beta; + u32 ramp_index; + + _log(ABE_ID_WRITE_GAIN, id, f_g, p); + gain_index = ((f_g - min_mdb) / 100); + gain_index = maximum(gain_index, 0); + gain_index = minimum(gain_index, sizeof_db2lin_table); + lin_g = abe_db2lin_table[gain_index]; + omap_abe_gain_offset(abe, id, &mixer_offset); + /* save the input parameters for mute/unmute */ + abe->desired_gains_linear[mixer_offset + p] = lin_g; + abe->desired_gains_decibel[mixer_offset + p] = f_g; + abe->desired_ramp_delay_ms[mixer_offset + p] = ramp; + /* SMEM address in bytes */ + mixer_target = OMAP_ABE_S_GTARGET1_ADDR; + mixer_target += (mixer_offset<<2); + mixer_target += (p<<2); + + if (abe->compensated_mixer_gain) { + switch (id) { + case MIXDL1: + case MIXDL2: + case MIXVXREC: + case MIXAUDUL: + /* compute the sum of the gain of the mixer */ + for (sum_g = i = 0; i < 4; i++) + sum_g += abe->desired_gains_linear[mixer_offset + + i]; + /* lets avoid a division by 0 */ + if (sum_g == 0) + break; + /* if the sum is OK with less than 1, then + do not weight the gains */ + if (sum_g < 0x00040000) { /* REMOVE HARD CONST */ + /* recompute all gains from original + desired values */ + sum_g = 0x00040000; + } + /* translate it in Q16 format for the later division */ + abe_int_2_float16(sum_g, &mean_gain, &mean_exp); + mean_exp = 10 - mean_exp; + for (i = 0; i < 4; i++) { + /* new gain = desired gain divided by sum of gains */ + new_gain_linear[i] = + (abe->desired_gains_linear + [mixer_offset + i] + << 8) / mean_gain; + new_gain_linear[i] = (mean_exp > 0) ? + new_gain_linear[i] << mean_exp : + new_gain_linear[i] >> mean_exp; + } + /* load the whole adpated S_G_Target SMEM MIXER table */ + omap_abe_mem_write(abe, OMAP_ABE_SMEM, + mixer_target - (p << 2), + new_gain_linear, (4 * sizeof(lin_g))); + break; + default: + /* load the S_G_Target SMEM table */ + omap_abe_mem_write(abe, OMAP_ABE_SMEM, + mixer_target, + (u32 *) &lin_g, sizeof(lin_g)); + break; + } + } else { + if (!abe->muted_gains_indicator[mixer_offset + p]) + /* load the S_G_Target SMEM table */ + omap_abe_mem_write(abe, OMAP_ABE_SMEM, + mixer_target, (u32 *) &lin_g, + sizeof(lin_g)); + else + /* update muted gain with new value */ + abe->muted_gains_decibel[mixer_offset + p] = f_g; + } + ramp = maximum(minimum(RAMP_MAXLENGTH, ramp), RAMP_MINLENGTH); + /* ramp data should be interpolated in the table instead */ + ramp_index = 3; + if ((RAMP_2MS <= ramp) && (ramp < RAMP_5MS)) + ramp_index = 8; + if ((RAMP_5MS <= ramp) && (ramp < RAMP_50MS)) + ramp_index = 24; + if ((RAMP_50MS <= ramp) && (ramp < RAMP_500MS)) + ramp_index = 36; + if (ramp > RAMP_500MS) + ramp_index = 48; + beta = abe_alpha_iir[ramp_index]; + alpha = abe_1_alpha_iir[ramp_index]; + /* CMEM bytes address */ + mixer_target = OMAP_ABE_C_1_ALPHA_ADDR; + /* a pair of gains is updated once in the firmware */ + mixer_target += ((p + mixer_offset) >> 1) << 2; + /* load the ramp delay data */ + omap_abe_mem_write(abe, OMAP_ABE_CMEM, mixer_target, + (u32 *) &alpha, sizeof(alpha)); + /* CMEM bytes address */ + mixer_target = OMAP_ABE_C_ALPHA_ADDR; + /* a pair of gains is updated once in the firmware */ + mixer_target += ((p + mixer_offset) >> 1) << 2; + omap_abe_mem_write(abe, OMAP_ABE_CMEM, mixer_target, + (u32 *) &beta, sizeof(beta)); + return 0; +} +EXPORT_SYMBOL(omap_abe_write_gain); +/** + * omap_abe_write_mixer + * @id: name of the mixer + * @param: input gains and delay ramp of the mixer + * @p: port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's + * gain in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +int omap_abe_write_mixer(struct omap_abe *abe, + u32 id, s32 f_g, u32 f_ramp, u32 p) +{ + _log(ABE_ID_WRITE_MIXER, id, f_ramp, p); + omap_abe_write_gain(abe, id, f_g, f_ramp, p); + return 0; +} +EXPORT_SYMBOL(omap_abe_write_mixer); + +/** + * omap_abe_read_gain + * @id: name of the mixer + * @param: list of input gains of the mixer + * @p: list of port corresponding to the above gains + * + */ +int omap_abe_read_gain(struct omap_abe *abe, + u32 id, u32 *f_g, u32 p) +{ + u32 mixer_target, mixer_offset, i; + _log(ABE_ID_READ_GAIN, id, (u32) f_g, p); + omap_abe_gain_offset(abe, id, &mixer_offset); + /* SMEM bytes address */ + mixer_target = OMAP_ABE_S_GTARGET1_ADDR; + mixer_target += (mixer_offset<<2); + mixer_target += (p<<2); + if (!abe->muted_gains_indicator[mixer_offset + p]) { + /* load the S_G_Target SMEM table */ + omap_abe_mem_read(abe, OMAP_ABE_SMEM, mixer_target, + (u32 *) f_g, sizeof(*f_g)); + for (i = 0; i < sizeof_db2lin_table; i++) { + if (abe_db2lin_table[i] == *f_g) + goto found; + } + *f_g = 0; + return -1; + found: + *f_g = (i * 100) + min_mdb; + } else { + /* update muted gain with new value */ + *f_g = abe->muted_gains_decibel[mixer_offset + p]; + } + return 0; +} +EXPORT_SYMBOL(omap_abe_read_gain); + +/** + * abe_read_mixer + * @id: name of the mixer + * @param: gains of the mixer + * @p: port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's + * gain in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +int omap_abe_read_mixer(struct omap_abe *abe, + u32 id, u32 *f_g, u32 p) +{ + _log(ABE_ID_READ_MIXER, id, 0, p); + omap_abe_read_gain(abe, id, f_g, p); + return 0; +} +EXPORT_SYMBOL(omap_abe_read_mixer); + +/** + * abe_reset_gain_mixer + * @id: name of the mixer + * @p: list of port corresponding to the above gains + * + * restart the working gain value of the mixers when a port is enabled + */ +void omap_abe_reset_gain_mixer(struct omap_abe *abe, u32 id, u32 p) +{ + u32 lin_g, mixer_target, mixer_offset; + switch (id) { + default: + case GAINS_DMIC1: + mixer_offset = dmic1_gains_offset; + break; + case GAINS_DMIC2: + mixer_offset = dmic2_gains_offset; + break; + case GAINS_DMIC3: + mixer_offset = dmic3_gains_offset; + break; + case GAINS_AMIC: + mixer_offset = amic_gains_offset; + break; + case GAINS_DL1: + mixer_offset = dl1_gains_offset; + break; + case GAINS_DL2: + mixer_offset = dl2_gains_offset; + break; + case GAINS_SPLIT: + mixer_offset = splitters_gains_offset; + break; + case MIXDL1: + mixer_offset = mixer_dl1_offset; + break; + case MIXDL2: + mixer_offset = mixer_dl2_offset; + break; + case MIXECHO: + mixer_offset = mixer_echo_offset; + break; + case MIXSDT: + mixer_offset = mixer_sdt_offset; + break; + case MIXVXREC: + mixer_offset = mixer_vxrec_offset; + break; + case MIXAUDUL: + mixer_offset = mixer_audul_offset; + break; + case GAINS_BTUL: + mixer_offset = btul_gains_offset; + break; + } + /* SMEM bytes address for the CURRENT gain values */ + mixer_target = OMAP_ABE_S_GCURRENT_ADDR; + mixer_target += (mixer_offset<<2); + mixer_target += (p<<2); + lin_g = 0; + /* load the S_G_Target SMEM table */ + omap_abe_mem_write(abe, OMAP_ABE_SMEM, mixer_target, + (u32 *) &lin_g, sizeof(lin_g)); +} diff --git a/sound/soc/omap/abe/abe_gain.h b/sound/soc/omap/abe/abe_gain.h new file mode 100644 index 0000000000000000000000000000000000000000..f3328376b20d457cb91fc3381a04be96bedfa408 --- /dev/null +++ b/sound/soc/omap/abe/abe_gain.h @@ -0,0 +1,111 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_GAIN_H_ +#define _ABE_GAIN_H_ + +#include "abe_typ.h" +#include "abe_dm_addr.h" +#include "abe_sm_addr.h" +#include "abe_cm_addr.h" + +#define OMAP_ABE_GAIN_MUTED (0x0001<<0) +#define OMAP_ABE_GAIN_DISABLED (0x0001<<1) + +#define OMAP_ABE_GAIN_DMIC1_LEFT 0 +#define OMAP_ABE_GAIN_DMIC1_RIGTH 1 +#define OMAP_ABE_GAIN_DMIC2_LEFT 2 +#define OMAP_ABE_GAIN_DMIC2_RIGTH 3 +#define OMAP_ABE_GAIN_DMIC3_LEFT 4 +#define OMAP_ABE_GAIN_DMIC3_RIGTH 5 +#define OMAP_ABE_GAIN_AMIC_LEFT 6 +#define OMAP_ABE_GAIN_AMIC_RIGTH 7 +#define OMAP_ABE_GAIN_DL1_LEFT 8 +#define OMAP_ABE_GAIN_DL1_RIGTH 9 +#define OMAP_ABE_GAIN_DL2_LEFT 10 +#define OMAP_ABE_GAIN_DL2_RIGTH 11 +#define OMAP_ABE_GAIN_SPLIT_LEFT 12 +#define OMAP_ABE_GAIN_SPLIT_RIGTH 13 +#define OMAP_ABE_MIXDL1_MM_DL 14 +#define OMAP_ABE_MIXDL1_MM_UL2 15 +#define OMAP_ABE_MIXDL1_VX_DL 16 +#define OMAP_ABE_MIXDL1_TONES 17 +#define OMAP_ABE_MIXDL2_MM_DL 18 +#define OMAP_ABE_MIXDL2_MM_UL2 19 +#define OMAP_ABE_MIXDL2_VX_DL 20 +#define OMAP_ABE_MIXDL2_TONES 21 +#define OMAP_ABE_MIXECHO_DL1 22 +#define OMAP_ABE_MIXECHO_DL2 23 +#define OMAP_ABE_MIXSDT_UL 24 +#define OMAP_ABE_MIXECHO_DL 25 +#define OMAP_ABE_MIXVXREC_MM_DL 26 +#define OMAP_ABE_MIXVXREC_TONES 27 +#define OMAP_ABE_MIXVXREC_VX_UL 28 +#define OMAP_ABE_MIXVXREC_VX_DL 29 +#define OMAP_ABE_MIXAUDUL_MM_DL 30 +#define OMAP_ABE_MIXAUDUL_TONES 31 +#define OMAP_ABE_MIXAUDUL_UPLINK 32 +#define OMAP_ABE_MIXAUDUL_VX_DL 33 +#define OMAP_ABE_GAIN_BTUL_LEFT 34 +#define OMAP_ABE_GAIN_BTUL_RIGTH 35 + +void omap_abe_reset_gain_mixer(struct omap_abe *abe, u32 id, u32 p); + +void abe_int_2_float16(u32 data, u32 *mantissa, u32 *exp); + +#endif /* _ABE_GAIN_H_ */ diff --git a/sound/soc/omap/abe/abe_ini.c b/sound/soc/omap/abe/abe_ini.c new file mode 100644 index 0000000000000000000000000000000000000000..288a3d3c052d0213f854aea21d184bb341a3b2e4 --- /dev/null +++ b/sound/soc/omap/abe/abe_ini.c @@ -0,0 +1,447 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_dbg.h" +#include "abe.h" +#include "abe_aess.h" +#include "abe_gain.h" +#include "abe_mem.h" +#include "abe_port.h" +#include "abe_seq.h" + +#include "abe_taskid.h" + + +#define ABE_TASK_ID(ID) (OMAP_ABE_D_TASKSLIST_ADDR + sizeof(ABE_STask)*(ID)) +void omap_abe_build_scheduler_table(struct omap_abe *abe); +void omap_abe_reset_all_ports(struct omap_abe *abe); + +const u32 abe_firmware_array[ABE_FIRMWARE_MAX_SIZE] = { +#include "abe_firmware.c" +}; + + +/* + * initialize the default values for call-backs to subroutines + * - FIFO IRQ call-backs for sequenced tasks + * - FIFO IRQ call-backs for audio player/recorders (ping-pong protocols) + * - Remote debugger interface + * - Error monitoring + * - Activity Tracing + */ + +/** + * abe_init_mem - Allocate Kernel space memory map for ABE + * + * Memory map of ABE memory space for PMEM/DMEM/SMEM/DMEM + */ +void abe_init_mem(void __iomem **_io_base) +{ + int i; + + abe = kzalloc(sizeof(struct omap_abe), GFP_KERNEL); + if (abe == NULL) + printk(KERN_ERR "ABE Allocation ERROR "); + + for (i = 0; i < 5; i++) + abe->io_base[i] = _io_base[i]; + + mutex_init(&abe->mutex); + +} +EXPORT_SYMBOL(abe_init_mem); + +/** + * abe_load_fw_param - Load ABE Firmware memories + * @PMEM: Pointer of Program memory data + * @PMEM_SIZE: Size of PMEM data + * @CMEM: Pointer of Coeffients memory data + * @CMEM_SIZE: Size of CMEM data + * @SMEM: Pointer of Sample memory data + * @SMEM_SIZE: Size of SMEM data + * @DMEM: Pointer of Data memory data + * @DMEM_SIZE: Size of DMEM data + * + */ +int abe_load_fw_param(u32 *ABE_FW) +{ + u32 pmem_size, dmem_size, smem_size, cmem_size; + u32 *pmem_ptr, *dmem_ptr, *smem_ptr, *cmem_ptr, *fw_ptr; + _log(ABE_ID_LOAD_FW_param, 0, 0, 0); +#define ABE_FW_OFFSET 5 + fw_ptr = ABE_FW; + abe->firmware_version_number = *fw_ptr++; + pmem_size = *fw_ptr++; + cmem_size = *fw_ptr++; + dmem_size = *fw_ptr++; + smem_size = *fw_ptr++; + pmem_ptr = fw_ptr; + cmem_ptr = pmem_ptr + (pmem_size >> 2); + dmem_ptr = cmem_ptr + (cmem_size >> 2); + smem_ptr = dmem_ptr + (dmem_size >> 2); + /* do not load PMEM */ + if (abe->warm_boot) { + /* Stop the event Generator */ + omap_abe_stop_event_generator(abe); + + /* Now we are sure the firmware is stalled */ + omap_abe_mem_write(abe, OMAP_ABE_CMEM, 0, cmem_ptr, + cmem_size); + omap_abe_mem_write(abe, OMAP_ABE_SMEM, 0, smem_ptr, + smem_size); + omap_abe_mem_write(abe, OMAP_ABE_DMEM, 0, dmem_ptr, + dmem_size); + /* Restore the event Generator status */ + omap_abe_start_event_generator(abe); + } else { + omap_abe_mem_write(abe, OMAP_ABE_PMEM, 0, pmem_ptr, + pmem_size); + omap_abe_mem_write(abe, OMAP_ABE_CMEM, 0, cmem_ptr, + cmem_size); + omap_abe_mem_write(abe, OMAP_ABE_SMEM, 0, smem_ptr, + smem_size); + omap_abe_mem_write(abe, OMAP_ABE_DMEM, 0, dmem_ptr, + dmem_size); + } + abe->warm_boot = 1; + return 0; +} +EXPORT_SYMBOL(abe_load_fw_param); + +/** + * omap_abe_load_fw - Load ABE Firmware and initialize memories + * @abe: Pointer on abe handle + * + */ +int omap_abe_load_fw(struct omap_abe *abe, u32 *firmware) +{ + _log(ABE_ID_LOAD_FW, 0, 0, 0); + abe_load_fw_param(firmware); + omap_abe_reset_all_ports(abe); + omap_abe_build_scheduler_table(abe); + omap_abe_reset_all_sequence(abe); + omap_abe_select_main_port(OMAP_ABE_PDM_DL_PORT); + return 0; +} +EXPORT_SYMBOL(omap_abe_load_fw); + +/** + * abe_reload_fw - Reload ABE Firmware after OFF mode + */ +int omap_abe_reload_fw(struct omap_abe *abe, u32 *firmware) +{ + abe->warm_boot = 0; + abe_load_fw_param(firmware); + omap_abe_build_scheduler_table(abe); + omap_abe_dbg_reset(&abe->dbg); + /* IRQ circular read pointer in DMEM */ + abe->irq_dbg_read_ptr = 0; + /* Restore Gains not managed by the drivers */ + omap_abe_write_gain(abe, GAINS_SPLIT, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_SPLIT, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL1, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL1, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL2, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL2, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + return 0; +} +EXPORT_SYMBOL(omap_abe_reload_fw); + +/** + * omap_abe_get_default_fw + * + * Get default ABE firmware + */ +u32 *omap_abe_get_default_fw(struct omap_abe *abe) +{ + return (u32 *)abe_firmware_array; +} + +/** + * abe_build_scheduler_table + * + */ +void omap_abe_build_scheduler_table(struct omap_abe *abe) +{ + u16 i, n; + u8 *ptr; + u16 aUplinkMuxing[NBROUTE_UL]; + + /* LOAD OF THE TASKS' MULTIFRAME */ + /* WARNING ON THE LOCATION OF IO_MM_DL WHICH IS PATCHED + IN "abe_init_io_tasks" */ + for (ptr = (u8 *) &(abe->MultiFrame[0][0]), i = 0; + i < sizeof(abe->MultiFrame); i++) + *ptr++ = 0; + + abe->MultiFrame[0][2] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_VX_DL)*/; + abe->MultiFrame[0][3] = ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_DL_8); + + abe->MultiFrame[1][3] = ABE_TASK_ID(C_ABE_FW_TASK_VX_DL_8_48_FIR); + abe->MultiFrame[1][6] = ABE_TASK_ID(C_ABE_FW_TASK_DL2Mixer); + abe->MultiFrame[1][7] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_VIB_DL)*/; + + abe->MultiFrame[2][0] = ABE_TASK_ID(C_ABE_FW_TASK_DL1Mixer); + abe->MultiFrame[2][1] = ABE_TASK_ID(C_ABE_FW_TASK_SDTMixer); + abe->MultiFrame[2][5] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_DMIC)*/; + + abe->MultiFrame[3][0] = ABE_TASK_ID(C_ABE_FW_TASK_DL1_GAIN); + abe->MultiFrame[3][6] = ABE_TASK_ID(C_ABE_FW_TASK_DL2_GAIN); + abe->MultiFrame[3][7] = ABE_TASK_ID(C_ABE_FW_TASK_DL2_EQ); + + abe->MultiFrame[4][0] = ABE_TASK_ID(C_ABE_FW_TASK_DL1_EQ); + abe->MultiFrame[4][2] = ABE_TASK_ID(C_ABE_FW_TASK_VXRECMixer); + abe->MultiFrame[4][3] = ABE_TASK_ID(C_ABE_FW_TASK_VXREC_SPLIT); + abe->MultiFrame[4][6] = ABE_TASK_ID(C_ABE_FW_TASK_VIBRA1); + abe->MultiFrame[4][7] = ABE_TASK_ID(C_ABE_FW_TASK_VIBRA2); + + abe->MultiFrame[5][0] = 0; + abe->MultiFrame[5][1] = ABE_TASK_ID(C_ABE_FW_TASK_EARP_48_96_LP); + abe->MultiFrame[5][2] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_PDM_UL)*/; + abe->MultiFrame[5][7] = ABE_TASK_ID(C_ABE_FW_TASK_VIBRA_SPLIT); + + abe->MultiFrame[6][0] = ABE_TASK_ID(C_ABE_FW_TASK_EARP_48_96_LP); + abe->MultiFrame[6][4] = ABE_TASK_ID(C_ABE_FW_TASK_EchoMixer); + abe->MultiFrame[6][5] = ABE_TASK_ID(C_ABE_FW_TASK_BT_UL_SPLIT); + + abe->MultiFrame[7][0] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_PDM_DL)*/; + abe->MultiFrame[7][3] = ABE_TASK_ID(C_ABE_FW_TASK_DBG_SYNC); + abe->MultiFrame[7][5] = ABE_TASK_ID(C_ABE_FW_TASK_ECHO_REF_SPLIT); + + abe->MultiFrame[8][2] = ABE_TASK_ID(C_ABE_FW_TASK_DMIC1_96_48_LP); + abe->MultiFrame[8][4] = ABE_TASK_ID(C_ABE_FW_TASK_DMIC1_SPLIT); + + abe->MultiFrame[9][2] = ABE_TASK_ID(C_ABE_FW_TASK_DMIC2_96_48_LP); + abe->MultiFrame[9][4] = ABE_TASK_ID(C_ABE_FW_TASK_DMIC2_SPLIT); + abe->MultiFrame[9][6] = 0; + abe->MultiFrame[9][7] = ABE_TASK_ID(C_ABE_FW_TASK_IHF_48_96_LP); + + abe->MultiFrame[10][2] = ABE_TASK_ID(C_ABE_FW_TASK_DMIC3_96_48_LP); + abe->MultiFrame[10][4] = ABE_TASK_ID(C_ABE_FW_TASK_DMIC3_SPLIT); + abe->MultiFrame[10][7] = ABE_TASK_ID(C_ABE_FW_TASK_IHF_48_96_LP); + + abe->MultiFrame[11][2] = ABE_TASK_ID(C_ABE_FW_TASK_AMIC_96_48_LP); + abe->MultiFrame[11][4] = ABE_TASK_ID(C_ABE_FW_TASK_AMIC_SPLIT); + abe->MultiFrame[11][7] = ABE_TASK_ID(C_ABE_FW_TASK_VIBRA_PACK); + + abe->MultiFrame[12][3] = ABE_TASK_ID(C_ABE_FW_TASK_VX_UL_ROUTING); + abe->MultiFrame[12][4] = ABE_TASK_ID(C_ABE_FW_TASK_ULMixer); + abe->MultiFrame[12][5] = ABE_TASK_ID(C_ABE_FW_TASK_VX_UL_48_8); + + abe->MultiFrame[13][2] = ABE_TASK_ID(C_ABE_FW_TASK_MM_UL2_ROUTING); + abe->MultiFrame[13][3] = ABE_TASK_ID(C_ABE_FW_TASK_SideTone); + abe->MultiFrame[13][5] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_BT_VX_DL)*/; + + abe->MultiFrame[14][3] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_DMIC)*/; + abe->MultiFrame[14][4] = ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_8); + + abe->MultiFrame[15][0] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_EXT_OUT)*/; + abe->MultiFrame[15][3] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_BT_VX_UL)*/; + abe->MultiFrame[15][6] = ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_UL_8); + + abe->MultiFrame[16][2] = ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_UL_8); + abe->MultiFrame[16][3] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_VX_UL)*/; + + abe->MultiFrame[17][2] = ABE_TASK_ID(C_ABE_FW_TASK_BT_UL_8_48); + abe->MultiFrame[17][3] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_UL2)*/; + + abe->MultiFrame[18][0] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_DL)*/; + abe->MultiFrame[18][6] = ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_DL_8); + + abe->MultiFrame[19][0] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_PDM_DL)*/; + + /* MM_UL is moved to OPP 100% */ + abe->MultiFrame[19][6] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_UL)*/; + + abe->MultiFrame[20][0] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_TONES_DL)*/; + abe->MultiFrame[20][6] = ABE_TASK_ID(C_ABE_FW_TASK_ASRC_MM_EXT_IN); + + abe->MultiFrame[21][1] = ABE_TASK_ID(C_ABE_FW_TASK_DEBUGTRACE_VX_ASRCs); + abe->MultiFrame[21][3] = 0/*ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_EXT_IN)*/; + /* MUST STAY ON SLOT 22 */ + abe->MultiFrame[22][0] = ABE_TASK_ID(C_ABE_FW_TASK_DEBUG_IRQFIFO); + abe->MultiFrame[22][1] = ABE_TASK_ID(C_ABE_FW_TASK_INIT_FW_MEMORY); + abe->MultiFrame[22][2] = 0; + /* MM_EXT_IN_SPLIT task must be after IO_MM_EXT_IN and before + ASRC_MM_EXT_IN in order to manage OPP50 <-> transitions */ + abe->MultiFrame[22][4] = ABE_TASK_ID(C_ABE_FW_TASK_MM_EXT_IN_SPLIT); + + abe->MultiFrame[23][0] = ABE_TASK_ID(C_ABE_FW_TASK_GAIN_UPDATE); + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_MULTIFRAME_ADDR, + (u32 *) abe->MultiFrame, sizeof(abe->MultiFrame)); + /* reset the uplink router */ + n = (OMAP_ABE_D_AUPLINKROUTING_SIZE) >> 1; + for (i = 0; i < n; i++) + aUplinkMuxing[i] = ZERO_labelID; + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_AUPLINKROUTING_ADDR, + (u32 *) aUplinkMuxing, sizeof(aUplinkMuxing)); +} + +/** + * omap_abe_reset_port + * @id: ABE port ID + * + * stop the port activity and reload default parameters on the associated + * processing features. + * Clears the internal AE buffers. + */ +int omap_abe_reset_port(u32 id) +{ + _log(ABE_ID_RESET_PORT, id, 0, 0); + abe_port[id] = ((abe_port_t *) abe_port_init)[id]; + return 0; +} + +/** + * abe_reset_all_ports + * + * load default configuration for all features + */ +void omap_abe_reset_all_ports(struct omap_abe *abe) +{ + u16 i; + for (i = 0; i < LAST_PORT_ID; i++) + omap_abe_reset_port(i); + /* mixers' configuration */ + omap_abe_write_mixer(abe, MIXDL1, MUTE_GAIN, + RAMP_5MS, MIX_DL1_INPUT_MM_DL); + omap_abe_write_mixer(abe, MIXDL1, MUTE_GAIN, + RAMP_5MS, MIX_DL1_INPUT_MM_UL2); + omap_abe_write_mixer(abe, MIXDL1, MUTE_GAIN, + RAMP_5MS, MIX_DL1_INPUT_VX_DL); + omap_abe_write_mixer(abe, MIXDL1, MUTE_GAIN, + RAMP_5MS, MIX_DL1_INPUT_TONES); + omap_abe_write_mixer(abe, MIXDL2, MUTE_GAIN, + RAMP_5MS, MIX_DL2_INPUT_TONES); + omap_abe_write_mixer(abe, MIXDL2, MUTE_GAIN, + RAMP_5MS, MIX_DL2_INPUT_VX_DL); + omap_abe_write_mixer(abe, MIXDL2, MUTE_GAIN, + RAMP_5MS, MIX_DL2_INPUT_MM_DL); + omap_abe_write_mixer(abe, MIXDL2, MUTE_GAIN, + RAMP_5MS, MIX_DL2_INPUT_MM_UL2); + omap_abe_write_mixer(abe, MIXSDT, MUTE_GAIN, + RAMP_5MS, MIX_SDT_INPUT_UP_MIXER); + omap_abe_write_mixer(abe, MIXSDT, GAIN_0dB, + RAMP_5MS, MIX_SDT_INPUT_DL1_MIXER); + omap_abe_write_mixer(abe, MIXECHO, MUTE_GAIN, + RAMP_5MS, MIX_ECHO_DL1); + omap_abe_write_mixer(abe, MIXECHO, MUTE_GAIN, + RAMP_5MS, MIX_ECHO_DL2); + omap_abe_write_mixer(abe, MIXAUDUL, MUTE_GAIN, + RAMP_5MS, MIX_AUDUL_INPUT_MM_DL); + omap_abe_write_mixer(abe, MIXAUDUL, MUTE_GAIN, + RAMP_5MS, MIX_AUDUL_INPUT_TONES); + omap_abe_write_mixer(abe, MIXAUDUL, GAIN_0dB, + RAMP_5MS, MIX_AUDUL_INPUT_UPLINK); + omap_abe_write_mixer(abe, MIXAUDUL, MUTE_GAIN, + RAMP_5MS, MIX_AUDUL_INPUT_VX_DL); + omap_abe_write_mixer(abe, MIXVXREC, MUTE_GAIN, + RAMP_5MS, MIX_VXREC_INPUT_TONES); + omap_abe_write_mixer(abe, MIXVXREC, MUTE_GAIN, + RAMP_5MS, MIX_VXREC_INPUT_VX_DL); + omap_abe_write_mixer(abe, MIXVXREC, MUTE_GAIN, + RAMP_5MS, MIX_VXREC_INPUT_MM_DL); + omap_abe_write_mixer(abe, MIXVXREC, MUTE_GAIN, + RAMP_5MS, MIX_VXREC_INPUT_VX_UL); + omap_abe_write_gain(abe, GAINS_DMIC1, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DMIC1, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_DMIC2, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DMIC2, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_DMIC3, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DMIC3, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_AMIC, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_AMIC, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_SPLIT, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_SPLIT, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL1, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL1, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL2, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_DL2, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); + omap_abe_write_gain(abe, GAINS_BTUL, GAIN_0dB, + RAMP_5MS, GAIN_LEFT_OFFSET); + omap_abe_write_gain(abe, GAINS_BTUL, GAIN_0dB, + RAMP_5MS, GAIN_RIGHT_OFFSET); +} diff --git a/sound/soc/omap/abe/abe_initxxx_labels.h b/sound/soc/omap/abe/abe_initxxx_labels.h new file mode 100644 index 0000000000000000000000000000000000000000..66f1856028c37be4487ee4598d0200d398845c2f --- /dev/null +++ b/sound/soc/omap/abe/abe_initxxx_labels.h @@ -0,0 +1,460 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifndef _ABE_INITXXX_LABELS_H_ +#define _ABE_INITXXX_LABELS_H_ +#define Dummy_Regs_labelID 0 +#define Dummy_AM_labelID 1 +#define Voice_8k_UL_labelID 2 +#define Voice_8k_DL_labelID 3 +#define ECHO_REF_8K_labelID 4 +#define Voice_16k_UL_labelID 5 +#define Voice_16k_DL_labelID 6 +#define ECHO_REF_16K_labelID 7 +#define MM_DL_labelID 8 +#define IO_VX_DL_ASRC_labelID 9 +#define IO_MM_EXT_IN_ASRC_labelID 10 +#define IO_VIBRA_DL_labelID 11 +#define ZERO_labelID 12 +#define GTarget_labelID 13 +#define GCurrent_labelID 14 +#define Gr_1_labelID 15 +#define Gr_2_labelID 16 +#define Gr_Regs_labelID 17 +#define DMIC0_Gain_labelID 18 +#define DMIC1_Gain_labelID 19 +#define DMIC2_Gain_labelID 20 +#define DMIC3_Gain_labelID 21 +#define AMIC_Gain_labelID 22 +#define MIXDL1_Gain_labelID 23 +#define MIXDL2_Gain_labelID 24 +#define DEFAULT_Gain_labelID 25 +#define DL1_M_G_Tones_labelID 26 +#define DL2_M_G_Tones_labelID 27 +#define Echo_M_G_labelID 28 +#define SDT_M_G_labelID 29 +#define VXREC_M_G_VX_DL_labelID 30 +#define UL_M_G_VX_DL_labelID 31 +#define BTUL_Gain_labelID 32 +#define DL1_M_labelID 33 +#define DL2_M_labelID 34 +#define MM_UL2_labelID 35 +#define VX_DL_labelID 36 +#define Tones_labelID 37 +#define DL_M_MM_UL2_VX_DL_labelID 38 +#define Echo_M_labelID 39 +#define VX_UL_labelID 40 +#define VX_UL_M_labelID 41 +#define SDT_F_labelID 42 +#define SDT_F_data_labelID 43 +#define SDT_Coef_labelID 44 +#define SDT_Regs_labelID 45 +#define SDT_M_labelID 46 +#define DL1_EQ_labelID 47 +#define DL2_EQ_labelID 48 +#define DL1_GAIN_out_labelID 49 +#define DL2_GAIN_out_labelID 50 +#define DMIC1_labelID 51 +#define DMIC1_L_labelID 52 +#define DMIC1_R_labelID 53 +#define DMIC2_labelID 54 +#define DMIC2_L_labelID 55 +#define DMIC2_R_labelID 56 +#define DMIC3_labelID 57 +#define DMIC3_L_labelID 58 +#define DMIC3_R_labelID 59 +#define SaturationMinMax_labelID 60 +#define TEMPORARY0_labelID 61 +#define TEMPORARY1_labelID 62 +#define BT_UL_L_labelID 63 +#define BT_UL_R_labelID 64 +#define AMIC_labelID 65 +#define AMIC_L_labelID 66 +#define AMIC_R_labelID 67 +#define EchoRef_L_labelID 68 +#define EchoRef_R_labelID 69 +#define MM_DL_L_labelID 70 +#define MM_DL_R_labelID 71 +#define MM_UL_labelID 72 +#define AMIC_96_labelID 73 +#define DMIC0_96_labelID 74 +#define DMIC1_96_labelID 75 +#define DMIC2_96_labelID 76 +#define UL_MIC_48K_labelID 77 +#define EQ_DL_48K_labelID 78 +#define EQ_48K_labelID 79 +#define McPDM_Out1_labelID 80 +#define McPDM_Out2_labelID 81 +#define McPDM_Out3_labelID 82 +#define VX_UL_MUX_labelID 83 +#define MM_UL2_MUX_labelID 84 +#define MM_UL_MUX_labelID 85 +#define XinASRC_DL_VX_labelID 86 +#define ASRC_DL_VX_Coefs_labelID 87 +#define ASRC_DL_VX_Alpha_labelID 88 +#define ASRC_DL_VX_VarsBeta_labelID 89 +#define ASRC_DL_VX_8k_Regs_labelID 90 +#define XinASRC_UL_VX_labelID 91 +#define ASRC_UL_VX_Coefs_labelID 92 +#define ASRC_UL_VX_Alpha_labelID 93 +#define ASRC_UL_VX_VarsBeta_labelID 94 +#define ASRC_UL_VX_8k_Regs_labelID 95 +#define UL_48_8_DEC_labelID 96 +#define ASRC_DL_VX_16k_Regs_labelID 97 +#define ASRC_UL_VX_16k_Regs_labelID 98 +#define UL_48_16_DEC_labelID 99 +#define XinASRC_MM_EXT_IN_labelID 100 +#define ASRC_MM_EXT_IN_Coefs_labelID 101 +#define ASRC_MM_EXT_IN_Alpha_labelID 102 +#define ASRC_MM_EXT_IN_VarsBeta_labelID 103 +#define ASRC_MM_EXT_IN_Regs_labelID 104 +#define VX_REC_labelID 105 +#define VXREC_UL_M_Tones_VX_UL_labelID 106 +#define VX_REC_L_labelID 107 +#define VX_REC_R_labelID 108 +#define DL2_M_L_labelID 109 +#define DL2_M_R_labelID 110 +#define DL1_M_data_labelID 111 +#define DL1_M_Coefs_labelID 112 +#define DL2_M_LR_data_labelID 113 +#define DL2_M_LR_Coefs_labelID 114 +#define SRC_6_LP_COEFS_labelID 115 +#define SRC_6_LP_GAIN_COEFS_labelID 116 +#define SRC_6_HP_COEFS_labelID 117 +#define SRC_3_LP_COEFS_labelID 118 +#define SRC_3_LP_GAIN_COEFS_labelID 119 +#define SRC_3_HP_COEFS_labelID 120 +#define VX_DL_8_48_LP_DATA_labelID 121 +#define VX_DL_8_48_HP_DATA_labelID 122 +#define VX_DL_16_48_LP_DATA_labelID 123 +#define VX_DL_16_48_HP_DATA_labelID 124 +#define VX_UL_48_8_LP_DATA_labelID 125 +#define VX_UL_48_8_HP_DATA_labelID 126 +#define VX_UL_48_16_LP_DATA_labelID 127 +#define VX_UL_48_16_HP_DATA_labelID 128 +#define BT_UL_8_48_LP_DATA_labelID 129 +#define BT_UL_8_48_HP_DATA_labelID 130 +#define BT_UL_16_48_LP_DATA_labelID 131 +#define BT_UL_16_48_HP_DATA_labelID 132 +#define BT_DL_48_8_LP_DATA_labelID 133 +#define BT_DL_48_8_HP_DATA_labelID 134 +#define BT_DL_48_16_LP_DATA_labelID 135 +#define BT_DL_48_16_HP_DATA_labelID 136 +#define ECHO_REF_48_16_LP_DATA_labelID 137 +#define ECHO_REF_48_16_HP_DATA_labelID 138 +#define ECHO_REF_48_8_LP_DATA_labelID 139 +#define ECHO_REF_48_8_HP_DATA_labelID 140 +#define ECHO_REF_DEC_labelID 141 +#define VX_UL_8_TEMP_labelID 142 +#define VX_UL_16_TEMP_labelID 143 +#define UP_DOWN_8_48_labelID 144 +#define UP_DOWN_16_48_labelID 145 +#define SRC_6_LP_48k_labelID 146 +#define SRC_6_HP_labelID 147 +#define SRC_3_LP_48k_labelID 148 +#define SRC_3_HP_labelID 149 +#define EARP_48_96_LP_DATA_labelID 150 +#define SRC_48_96_LP_labelID 151 +#define IHF_48_96_LP_DATA_labelID 152 +#define EQ_VX_UL_16K_labelID 153 +#define AB0_labelID 154 +#define AC0_labelID 155 +#define MM_DL_C_labelID 156 +#define TONES_C_labelID 157 +#define MM_DL_44P1_REGS_labelID 158 +#define TONES_44P1_REGS_labelID 159 +#define MM_DL_44P1_DRIFT_labelID 160 +#define MM_DL_44P1_XK_labelID 161 +#define TONES_44P1_DRIFT_labelID 162 +#define TONES_44P1_XK_labelID 163 +#define SRC_44P1_MULFAC1_2_labelID 164 +#define A00_labelID 165 +#define MM_DL_44P1_WPTR_labelID 166 +#define MM_DL_44P1_RPTR_labelID 167 +#define TONES_44P1_WPTR_labelID 168 +#define TONES_44P1_RPTR_labelID 169 +#define C_0DB_SAT_labelID 170 +#define AC_labelID 171 +#define AD_labelID 172 +#define AE_labelID 173 +#define AF_labelID 174 +#define AG_labelID 175 +#define AH_labelID 176 +#define AI_labelID 177 +#define AJ_labelID 178 +#define AK_labelID 179 +#define AL_labelID 180 +#define AM_labelID 181 +#define AN_labelID 182 +#define AO_labelID 183 +#define AP_labelID 184 +#define AQ_labelID 185 +#define AR_labelID 186 +#define AS_labelID 187 +#define AT_labelID 188 +#define AU_labelID 189 +#define AV_labelID 190 +#define AW_labelID 191 +#define pVIBRA1_p0_labelID 192 +#define pVIBRA1_p1_labelID 193 +#define pVIBRA1_p23_labelID 194 +#define pVIBRA1_p45_labelID 195 +#define pVibra1_pR1_labelID 196 +#define pVibra1_pR2_labelID 197 +#define pVibra1_pR3_labelID 198 +#define pVIBRA1_r_labelID 199 +#define pVIBRA2_p0_0_labelID 200 +#define pVIBRA2_p0_labelID 201 +#define pVIBRA2_p1_labelID 202 +#define pVIBRA2_p23_labelID 203 +#define pVIBRA2_p45_labelID 204 +#define pCtrl_p67_labelID 205 +#define pVIBRA2_r_labelID 206 +#define VIBRA_labelID 207 +#define UP_48_96_LP_COEFS_DC_HF_labelID 208 +#define AX_labelID 209 +#define UP_48_96_LP_COEFS_DC_HS_labelID 210 +#define AMIC_96_48_data_labelID 211 +#define DOWN_96_48_AMIC_Coefs_labelID 212 +#define DOWN_96_48_DMIC_Coefs_labelID 213 +#define DOWN_96_48_AMIC_Regs_labelID 214 +#define DOWN_96_48_DMIC_Regs_labelID 215 +#define DMIC0_96_48_data_labelID 216 +#define DMIC1_96_48_data_labelID 217 +#define DMIC2_96_48_data_labelID 218 +#define SIO_DMIC_labelID 219 +#define SIO_PDM_UL_labelID 220 +#define SIO_BT_VX_UL_labelID 221 +#define SIO_MM_UL_labelID 222 +#define SIO_MM_UL2_labelID 223 +#define SIO_VX_UL_labelID 224 +#define SIO_MM_DL_labelID 225 +#define SIO_VX_DL_labelID 226 +#define SIO_TONES_DL_labelID 227 +#define SIO_VIB_DL_labelID 228 +#define SIO_BT_VX_DL_labelID 229 +#define SIO_PDM_DL_labelID 230 +#define SIO_MM_EXT_OUT_labelID 231 +#define SIO_MM_EXT_IN_labelID 232 +#define SIO_TDM_OUT_labelID 233 +#define SIO_TDM_IN_labelID 234 +#define DMIC_ATC_PTR_labelID 235 +#define MCPDM_UL_ATC_PTR_labelID 236 +#define BT_VX_UL_ATC_PTR_labelID 237 +#define MM_UL_ATC_PTR_labelID 238 +#define MM_UL2_ATC_PTR_labelID 239 +#define VX_UL_ATC_PTR_labelID 240 +#define MM_DL_ATC_PTR_labelID 241 +#define VX_DL_ATC_PTR_labelID 242 +#define TONES_DL_ATC_PTR_labelID 243 +#define VIB_DL_ATC_PTR_labelID 244 +#define BT_VX_DL_ATC_PTR_labelID 245 +#define PDM_DL_ATC_PTR_labelID 246 +#define MM_EXT_OUT_ATC_PTR_labelID 247 +#define MM_EXT_IN_ATC_PTR_labelID 248 +#define TDM_OUT_ATC_PTR_labelID 249 +#define TDM_IN_ATC_PTR_labelID 250 +#define MCU_IRQ_FIFO_ptr_labelID 251 +#define DEBUG_IRQ_FIFO_reg_labelID 252 +#define UP_DOWN_48_96_labelID 253 +#define OSR96_2_labelID 254 +#define DEBUG_GAINS_labelID 255 +#define DBG_8K_PATTERN_labelID 256 +#define DBG_16K_PATTERN_labelID 257 +#define DBG_24K_PATTERN_labelID 258 +#define DBG_48K_PATTERN_labelID 259 +#define DBG_96K_PATTERN_labelID 260 +#define UL_VX_UL_48_8K_labelID 261 +#define UL_VX_UL_48_16K_labelID 262 +#define BT_DL_labelID 263 +#define BT_UL_labelID 264 +#define BT_DL_8k_labelID 265 +#define BT_DL_16k_labelID 266 +#define BT_UL_8k_labelID 267 +#define BT_UL_16k_labelID 268 +#define MM_EXT_IN_labelID 269 +#define MM_EXT_IN_L_labelID 270 +#define MM_EXT_IN_R_labelID 271 +#define ECHO_REF_48_16_WRAP_labelID 272 +#define ECHO_REF_48_8_WRAP_labelID 273 +#define BT_UL_16_48_WRAP_labelID 274 +#define BT_UL_8_48_WRAP_labelID 275 +#define BT_DL_48_16_WRAP_labelID 276 +#define BT_DL_48_8_WRAP_labelID 277 +#define VX_DL_16_48_WRAP_labelID 278 +#define VX_DL_8_48_WRAP_labelID 279 +#define VX_UL_48_16_WRAP_labelID 280 +#define VX_UL_48_8_WRAP_labelID 281 +#define ATC_NULL_BUFFER_labelID 282 +#define MEM_INIT_hal_mem_labelID 283 +#define MEM_INIT_write_mem_labelID 284 +#define MEM_INIT_regs_labelID 285 +#define GAIN_0DB_labelID 286 +#define XinASRC_BT_UL_labelID 287 +#define IO_BT_UL_ASRC_labelID 288 +#define ASRC_BT_UL_Coefs_labelID 289 +#define ASRC_BT_UL_Alpha_labelID 290 +#define ASRC_BT_UL_VarsBeta_labelID 291 +#define ASRC_BT_UL_8k_Regs_labelID 292 +#define ASRC_BT_UL_16k_Regs_labelID 293 +#define XinASRC_BT_DL_labelID 294 +#define DL_48_8_DEC_labelID 295 +#define DL_48_16_DEC_labelID 296 +#define BT_DL_8k_TEMP_labelID 297 +#define BT_DL_16k_TEMP_labelID 298 +#define BT_DL_8k_opp100_labelID 299 +#define BT_DL_16k_opp100_labelID 300 +#define ASRC_BT_DL_Coefs_labelID 301 +#define ASRC_BT_DL_Alpha_labelID 302 +#define ASRC_BT_DL_VarsBeta_labelID 303 +#define ASRC_BT_DL_8k_Regs_labelID 304 +#define ASRC_BT_DL_16k_Regs_labelID 305 +#define BT_DL_48_8_OPP100_WRAP_labelID 306 +#define BT_DL_48_16_OPP100_WRAP_labelID 307 +#define VX_DL_8_48_OSR_LP_labelID 308 +#define SRC_FIR6_OSR_LP_labelID 309 +#define VX_DL_8_48_FIR_WRAP_labelID 310 +#define PING_labelID 311 +#define PING_Regs_labelID 312 +#define BT_UL_8_48_FIR_WRAP_labelID 313 +#define BT_UL_8_48_OSR_LP_labelID 314 +#define Dummy_315_labelID 315 +#define Dummy_316_labelID 316 +#define Dummy_317_labelID 317 +#define Dummy_318_labelID 318 +#define Dummy_319_labelID 319 +#define Dummy_320_labelID 320 +#define Dummy_321_labelID 321 +#define Dummy_322_labelID 322 +#define Dummy_323_labelID 323 +#define Dummy_324_labelID 324 +#define Dummy_325_labelID 325 +#define Dummy_326_labelID 326 +#define Dummy_327_labelID 327 +#define Dummy_328_labelID 328 +#define Dummy_329_labelID 329 +#define Dummy_330_labelID 330 +#define Dummy_331_labelID 331 +#define Dummy_332_labelID 332 +#define Dummy_333_labelID 333 +#define Dummy_334_labelID 334 +#define Dummy_335_labelID 335 +#define Dummy_336_labelID 336 +#define Dummy_337_labelID 337 +#define Dummy_338_labelID 338 +#define Dummy_339_labelID 339 +#define Dummy_340_labelID 340 +#define Dummy_341_labelID 341 +#define Dummy_342_labelID 342 +#define Dummy_343_labelID 343 +#define Dummy_344_labelID 344 +#define Dummy_345_labelID 345 +#define Dummy_346_labelID 346 +#define Dummy_347_labelID 347 +#define Dummy_348_labelID 348 +#define Dummy_349_labelID 349 +#define Dummy_350_labelID 350 +#define Dummy_351_labelID 351 +#define Dummy_352_labelID 352 +#define Dummy_353_labelID 353 +#define Dummy_354_labelID 354 +#define Dummy_355_labelID 355 +#define Dummy_356_labelID 356 +#define Dummy_357_labelID 357 +#define Dummy_358_labelID 358 +#define Dummy_359_labelID 359 +#define Dummy_360_labelID 360 +#define Dummy_361_labelID 361 +#define Dummy_362_labelID 362 +#define Dummy_363_labelID 363 +#define Dummy_364_labelID 364 +#define Dummy_365_labelID 365 +#define Dummy_366_labelID 366 +#define Dummy_367_labelID 367 +#define Dummy_368_labelID 368 +#define Dummy_369_labelID 369 +#define Dummy_370_labelID 370 +#define Dummy_371_labelID 371 +#define Dummy_372_labelID 372 +#define Dummy_373_labelID 373 +#define Dummy_374_labelID 374 +#define Dummy_375_labelID 375 +#define Dummy_376_labelID 376 +#define Dummy_377_labelID 377 +#define Dummy_378_labelID 378 +#define Dummy_379_labelID 379 +#define Dummy_380_labelID 380 +#define Dummy_381_labelID 381 +#define Dummy_382_labelID 382 +#define Dummy_383_labelID 383 +#define Dummy_384_labelID 384 +#define Dummy_385_labelID 385 +#define Dummy_386_labelID 386 +#define Dummy_387_labelID 387 +#define Dummy_388_labelID 388 +#define Dummy_389_labelID 389 +#define Dummy_390_labelID 390 +#define Dummy_391_labelID 391 +#define Dummy_392_labelID 392 +#define Dummy_393_labelID 393 +#define Dummy_394_labelID 394 +#define Dummy_395_labelID 395 +#define Dummy_396_labelID 396 +#define Dummy_397_labelID 397 +#define Dummy_398_labelID 398 +#define Dummy_399_labelID 399 +#endif /* _ABE_INITXXXX_LABELS_H_ */ diff --git a/sound/soc/omap/abe/abe_irq.c b/sound/soc/omap/abe/abe_irq.c new file mode 100644 index 0000000000000000000000000000000000000000..d6398942c0d99706c08f5a41ecbd2cfe9085614f --- /dev/null +++ b/sound/soc/omap/abe/abe_irq.c @@ -0,0 +1,107 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include "abe_legacy.h" + +extern u32 abe_irq_pingpong_player_id; + +/* + * initialize the default values for call-backs to subroutines + * - FIFO IRQ call-backs for sequenced tasks + * - FIFO IRQ call-backs for audio player/recorders (ping-pong protocols) + * - Remote debugger interface + * - Error monitoring + * - Activity Tracing + */ +/** + * abe_irq_ping_pong + * + * Call the respective subroutine depending on the IRQ FIFO content: + * APS interrupts : IRQ_FIFO[31:28] = IRQtag_APS, + * IRQ_FIFO[27:16] = APS_IRQs, IRQ_FIFO[15:0] = loopCounter + * SEQ interrupts : IRQ_FIFO[31:28] = IRQtag_COUNT, + * IRQ_FIFO[27:16] = Count_IRQs, IRQ_FIFO[15:0] = loopCounter + * Ping-Pong Interrupts : IRQ_FIFO[31:28] = IRQtag_PP, + * IRQ_FIFO[27:16] = PP_MCU_IRQ, IRQ_FIFO[15:0] = loopCounter + */ +void abe_irq_ping_pong(void) +{ + abe_call_subroutine(abe_irq_pingpong_player_id, NOPARAMETER, + NOPARAMETER, NOPARAMETER, NOPARAMETER); +} +/** + * abe_irq_check_for_sequences +* @i: sequence ID + * + * check the active sequence list + * + */ +void abe_irq_check_for_sequences(u32 i) +{ +} +/** + * abe_irq_aps + * + * call the application subroutines that updates + * the acoustics protection filters + */ +void abe_irq_aps(u32 aps_info) +{ + abe_call_subroutine(abe_irq_aps_adaptation_id, NOPARAMETER, NOPARAMETER, + NOPARAMETER, NOPARAMETER); +} diff --git a/sound/soc/omap/abe/abe_legacy.h b/sound/soc/omap/abe/abe_legacy.h new file mode 100644 index 0000000000000000000000000000000000000000..ca73dc21a634ab39c77dffe60f22496d4284559b --- /dev/null +++ b/sound/soc/omap/abe/abe_legacy.h @@ -0,0 +1,98 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_MAIN_H_ +#define _ABE_MAIN_H_ + +#include + +#include "abe_dm_addr.h" +#include "abe_sm_addr.h" +#include "abe_cm_addr.h" +#include "abe_define.h" +#include "abe_fw.h" +#include "abe_def.h" +#include "abe_typ.h" +#include "abe_ext.h" +#include "abe_dbg.h" +#include "abe_ref.h" +#include "abe_api.h" +#include "abe_typedef.h" +#include "abe_functionsid.h" +#include "abe_taskid.h" +#include "abe_initxxx_labels.h" +#include "abe_fw.h" + +/* pipe connection to the TARGET simulator */ +#define ABE_DEBUG_CHECKERS 0 +/* simulator data extracted from a text-file */ +#define ABE_DEBUG_HWFILE 0 +/* low-level log files */ +#define ABE_DEBUG_LL_LOG 0 + +extern struct omap_abe *abe; + +void omap_abe_dbg_log(struct omap_abe *abe, u32 x, u32 y, u32 z, u32 t); +void omap_abe_dbg_error(struct omap_abe *abe, int level, int error); + +/* + * MACROS + */ +#define _log(x, y, z, t) { if (x & abe->dbg.mask) omap_abe_dbg_log(abe, x, y, z, t); } + +#endif /* _ABE_MAIN_H_ */ diff --git a/sound/soc/omap/abe/abe_main.c b/sound/soc/omap/abe/abe_main.c new file mode 100644 index 0000000000000000000000000000000000000000..86e969e932dd0890f98bcf6d8f4de8c43d8e998e --- /dev/null +++ b/sound/soc/omap/abe/abe_main.c @@ -0,0 +1,747 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_legacy.h" +#include "abe_dbg.h" +#include "abe_port.h" + + +struct omap_abe_equ { + /* type of filter */ + u32 equ_type; + /* filter length */ + u32 equ_length; + union { + /* parameters are the direct and recursive coefficients in */ + /* Q6.26 integer fixed-point format. */ + s32 type1[NBEQ1]; + struct { + /* center frequency of the band [Hz] */ + s32 freq[NBEQ2]; + /* gain of each band. [dB] */ + s32 gain[NBEQ2]; + /* Q factor of this band [dB] */ + s32 q[NBEQ2]; + } type2; + } coef; + s32 equ_param3; +}; + +#include "abe_gain.h" +#include "abe_aess.h" +#include "abe_seq.h" + + +int omap_abe_connect_debug_trace(struct omap_abe *abe, + struct omap_abe_dma *dma2); + +int omap_abe_reset_hal(struct omap_abe *abe); +int omap_abe_load_fw(struct omap_abe *abe, u32 *firmware); +int omap_abe_reload_fw(struct omap_abe *abe, u32 *firmware); +u32* omap_abe_get_default_fw(struct omap_abe *abe); +int omap_abe_wakeup(struct omap_abe *abe); +int omap_abe_irq_processing(struct omap_abe *abe); +int omap_abe_clear_irq(struct omap_abe *abe); +int omap_abe_disable_irq(struct omap_abe *abe); +int omap_abe_set_debug_trace(struct omap_abe_dbg *dbg, int debug); +int omap_abe_set_ping_pong_buffer(struct omap_abe *abe, + u32 port, u32 n_bytes); +int omap_abe_read_next_ping_pong_buffer(struct omap_abe *abe, + u32 port, u32 *p, u32 *n); +int omap_abe_init_ping_pong_buffer(struct omap_abe *abe, + u32 id, u32 size_bytes, u32 n_buffers, + u32 *p); +int omap_abe_read_offset_from_ping_buffer(struct omap_abe *abe, + u32 id, u32 *n); +int omap_abe_set_router_configuration(struct omap_abe *abe, + u32 id, u32 k, u32 *param); +int omap_abe_set_opp_processing(struct omap_abe *abe, u32 opp); +int omap_abe_disable_data_transfer(struct omap_abe *abe, u32 id); +int omap_abe_enable_data_transfer(struct omap_abe *abe, u32 id); +int omap_abe_connect_cbpr_dmareq_port(struct omap_abe *abe, + u32 id, abe_data_format_t *f, + u32 d, + abe_dma_t *returned_dma_t); +int omap_abe_connect_irq_ping_pong_port(struct omap_abe *abe, + u32 id, abe_data_format_t *f, + u32 subroutine_id, u32 size, + u32 *sink, u32 dsp_mcu_flag); +int omap_abe_connect_serial_port(struct omap_abe *abe, + u32 id, abe_data_format_t *f, + u32 mcbsp_id); +int omap_abe_read_port_address(struct omap_abe *abe, + u32 port, abe_dma_t *dma2); +int omap_abe_check_activity(struct omap_abe *abe); + +int omap_abe_use_compensated_gain(struct omap_abe *abe, int on_off); +int omap_abe_write_equalizer(struct omap_abe *abe, + u32 id, struct omap_abe_equ *param); + +int omap_abe_disable_gain(struct omap_abe *abe, u32 id, u32 p); +int omap_abe_enable_gain(struct omap_abe *abe, u32 id, u32 p); +int omap_abe_mute_gain(struct omap_abe *abe, u32 id, u32 p); +int omap_abe_unmute_gain(struct omap_abe *abe, u32 id, u32 p); + +int omap_abe_write_gain(struct omap_abe *abe, + u32 id, s32 f_g, u32 ramp, u32 p); +int omap_abe_write_mixer(struct omap_abe *abe, + u32 id, s32 f_g, u32 f_ramp, u32 p); +int omap_abe_read_gain(struct omap_abe *abe, + u32 id, u32 *f_g, u32 p); +int omap_abe_read_mixer(struct omap_abe *abe, + u32 id, u32 *f_g, u32 p); + +extern struct omap_abe *abe; + +#if 0 +/** + * abe_init_mem - Allocate Kernel space memory map for ABE + * + * Memory map of ABE memory space for PMEM/DMEM/SMEM/DMEM + */ +void abe_init_mem(void __iomem *_io_base) +{ + omap_abe_init_mem(abe, _io_base); +} +EXPORT_SYMBOL(abe_init_mem); + +struct omap_abe* abe_probe_aess(void) +{ + return omap_abe_probe_aess(abe); +} +EXPORT_SYMBOL(abe_probe_aess); + +void abe_remove_aess(void) +{ + omap_abe_remove_aess(abe); +} +EXPORT_SYMBOL(abe_remove_aess); + +void abe_add_subroutine(u32 *id, abe_subroutine2 f, + u32 nparam, u32 *params) +{ + omap_abe_add_subroutine(abe, id, f, nparam, params); +} +EXPORT_SYMBOL(abe_add_subroutine); + +#endif + +/** + * abe_reset_hal - reset the ABE/HAL + * @rdev: regulator source + * @constraints: constraints to apply + * + * Operations : reset the HAL by reloading the static variables and + * default AESS registers. + * Called after a PRCM cold-start reset of ABE + */ +u32 abe_reset_hal(void) +{ + omap_abe_reset_hal(abe); + return 0; +} +EXPORT_SYMBOL(abe_reset_hal); + +/** + * abe_load_fw - Load ABE Firmware and initialize memories + * + */ +u32 abe_load_fw(u32 *firmware) +{ + omap_abe_load_fw(abe, firmware); + return 0; +} +EXPORT_SYMBOL(abe_load_fw); + +/** + * abe_reload_fw - Reload ABE Firmware and initialize memories + * + */ +u32 abe_reload_fw(u32 *firmware) +{ + omap_abe_reload_fw(abe, firmware); + return 0; +} +EXPORT_SYMBOL(abe_reload_fw); + +u32* abe_get_default_fw(void) +{ + return omap_abe_get_default_fw(abe); +} +EXPORT_SYMBOL(abe_get_default_fw); + +/** + * abe_wakeup - Wakeup ABE + * + * Wakeup ABE in case of retention + */ +u32 abe_wakeup(void) +{ + omap_abe_wakeup(abe); + return 0; +} +EXPORT_SYMBOL(abe_wakeup); + +/** + * abe_irq_processing - Process ABE interrupt + * + * This subroutine is call upon reception of "MA_IRQ_99 ABE_MPU_IRQ" Audio + * back-end interrupt. This subroutine will check the ATC Hrdware, the + * IRQ_FIFO from the AE and act accordingly. Some IRQ source are originated + * for the delivery of "end of time sequenced tasks" notifications, some are + * originated from the Ping-Pong protocols, some are generated from + * the embedded debugger when the firmware stops on programmable break-points, + * etc ... + */ +u32 abe_irq_processing(void) +{ + omap_abe_irq_processing(abe); + return 0; +} +EXPORT_SYMBOL(abe_irq_processing); + +/** + * abe_clear_irq - clear ABE interrupt + * + * This subroutine is call to clear MCU Irq + */ +u32 abe_clear_irq(void) +{ + omap_abe_clear_irq(abe); + return 0; +} +EXPORT_SYMBOL(abe_clear_irq); + +/** + * abe_disable_irq - disable MCU/DSP ABE interrupt + * + * This subroutine is disabling ABE MCU/DSP Irq + */ +u32 abe_disable_irq(void) +{ + omap_abe_disable_irq(abe); + + return 0; +} +EXPORT_SYMBOL(abe_disable_irq); + +/** + * abe_write_event_generator - Selects event generator source + * @e: Event Generation Counter, McPDM, DMIC or default. + * + * Loads the AESS event generator hardware source. + * Loads the firmware parameters accordingly. + * Indicates to the FW which data stream is the most important to preserve + * in case all the streams are asynchronous. + * If the parameter is "default", then HAL decides which Event source + * is the best appropriate based on the opened ports. + * + * When neither the DMIC and the McPDM are activated, the AE will have + * its EVENT generator programmed with the EVENT_COUNTER. + * The event counter will be tuned in order to deliver a pulse frequency higher + * than 96 kHz. + * The DPLL output at 100% OPP is MCLK = (32768kHz x6000) = 196.608kHz + * The ratio is (MCLK/96000)+(1<<1) = 2050 + * (1<<1) in order to have the same speed at 50% and 100% OPP + * (only 15 MSB bits are used at OPP50%) + */ +u32 abe_write_event_generator(u32 e) // should integarte abe as parameter +{ + omap_abe_write_event_generator(abe, e); + return 0; +} +EXPORT_SYMBOL(abe_write_event_generator); + +/** + * abe_start_event_generator - Starts event generator source + * + * Start the event genrator of AESS. No more event will be send to AESS engine. + * Upper layer must wait 1/96kHz to be sure that engine reaches + * the IDLE instruction. + */ +u32 abe_stop_event_generator(void) +{ + omap_abe_stop_event_generator(abe); + return 0; +} +EXPORT_SYMBOL(abe_stop_event_generator); + +/** + * abe_connect_debug_trace + * @dma2:pointer to the DMEM trace buffer + * + * returns the address and size of the real-time debug trace buffer, + * the content of which will vary from one firmware release to another + */ +u32 abe_connect_debug_trace(abe_dma_t *dma2) +{ + omap_abe_connect_debug_trace(abe, (struct omap_abe_dma *)dma2); + return 0; +} +EXPORT_SYMBOL(abe_connect_debug_trace); + +/** + * abe_set_debug_trace + * @debug: debug ID from a list to be defined + * + * loads a mask which filters the debug trace to dedicated types of data + */ +u32 abe_set_debug_trace(abe_dbg_t debug) +{ + omap_abe_set_debug_trace(&abe->dbg, (int)(debug)); + return 0; +} +EXPORT_SYMBOL(abe_set_debug_trace); + +/** + * abe_set_ping_pong_buffer + * @port: ABE port ID + * @n_bytes: Size of Ping/Pong buffer + * + * Updates the next ping-pong buffer with "size" bytes copied from the + * host processor. This API notifies the FW that the data transfer is done. + */ +u32 abe_set_ping_pong_buffer(u32 port, u32 n_bytes) +{ + omap_abe_set_ping_pong_buffer(abe, port, n_bytes); + return 0; +} +EXPORT_SYMBOL(abe_set_ping_pong_buffer); + +/** + * abe_read_next_ping_pong_buffer + * @port: ABE portID + * @p: Next buffer address (pointer) + * @n: Next buffer size (pointer) + * + * Tell the next base address of the next ping_pong Buffer and its size + */ +u32 abe_read_next_ping_pong_buffer(u32 port, u32 *p, u32 *n) +{ + omap_abe_read_next_ping_pong_buffer(abe, port, p, n); + return 0; +} +EXPORT_SYMBOL(abe_read_next_ping_pong_buffer); + +/** + * abe_init_ping_pong_buffer + * @id: ABE port ID + * @size_bytes:size of the ping pong + * @n_buffers:number of buffers (2 = ping/pong) + * @p:returned address of the ping-pong list of base addresses + * (byte offset from DMEM start) + * + * Computes the base address of the ping_pong buffers + */ +u32 abe_init_ping_pong_buffer(u32 id, u32 size_bytes, u32 n_buffers, + u32 *p) +{ + omap_abe_init_ping_pong_buffer(abe, id, size_bytes, n_buffers, p); + return 0; +} +EXPORT_SYMBOL(abe_init_ping_pong_buffer); + +/** + * abe_read_offset_from_ping_buffer + * @id: ABE port ID + * @n: returned address of the offset + * from the ping buffer start address (in samples) + * + * Computes the current firmware ping pong read pointer location, + * expressed in samples, as the offset from the start address of ping buffer. + */ +u32 abe_read_offset_from_ping_buffer(u32 id, u32 *n) +{ + omap_abe_read_offset_from_ping_buffer(abe, id, n); + return 0; +} +EXPORT_SYMBOL(abe_read_offset_from_ping_buffer); + +/** + * abe_write_equalizer + * @id: name of the equalizer + * @param : equalizer coefficients + * + * Load the coefficients in CMEM. + */ +u32 abe_write_equalizer(u32 id, abe_equ_t *param) +{ + omap_abe_write_equalizer(abe, id, (struct omap_abe_equ *)param); + return 0; +} +EXPORT_SYMBOL(abe_write_equalizer); +/** + * abe_disable_gain + * Parameters: + * mixer id + * sub-port id + * + */ +u32 abe_disable_gain(u32 id, u32 p) +{ + omap_abe_disable_gain(abe, id, p); + return 0; +} +EXPORT_SYMBOL(abe_disable_gain); +/** + * abe_enable_gain + * Parameters: + * mixer id + * sub-port id + * + */ +u32 abe_enable_gain(u32 id, u32 p) +{ + omap_abe_enable_gain(abe, id, p); + return 0; +} +EXPORT_SYMBOL(abe_enable_gain); + +/** + * abe_mute_gain + * Parameters: + * mixer id + * sub-port id + * + */ +u32 abe_mute_gain(u32 id, u32 p) +{ + omap_abe_mute_gain(abe, id, p); + return 0; +} +EXPORT_SYMBOL(abe_mute_gain); + +/** + * abe_unmute_gain + * Parameters: + * mixer id + * sub-port id + * + */ +u32 abe_unmute_gain(u32 id, u32 p) +{ + omap_abe_unmute_gain(abe, id, p); + return 0; +} +EXPORT_SYMBOL(abe_unmute_gain); + +/** + * abe_write_gain + * @id: gain name or mixer name + * @f_g: list of input gains of the mixer + * @ramp: gain ramp speed factor + * @p: list of ports corresponding to the above gains + * + * Loads the gain coefficients to FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's gain + * in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +u32 abe_write_gain(u32 id, s32 f_g, u32 ramp, u32 p) +{ + omap_abe_write_gain(abe, id, f_g, ramp, p); + return 0; +} +EXPORT_SYMBOL(abe_write_gain); + +/** + * abe_write_mixer + * @id: name of the mixer + * @param: input gains and delay ramp of the mixer + * @p: port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's + * gain in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +u32 abe_write_mixer(u32 id, s32 f_g, u32 f_ramp, u32 p) +{ + omap_abe_write_gain(abe, id, f_g, f_ramp, p); + return 0; +} +EXPORT_SYMBOL(abe_write_mixer); + +/** + * abe_read_gain + * @id: name of the mixer + * @param: list of input gains of the mixer + * @p: list of port corresponding to the above gains + * + */ +u32 abe_read_gain(u32 id, u32 *f_g, u32 p) +{ + omap_abe_read_gain(abe, id, f_g, p); + return 0; +} +EXPORT_SYMBOL(abe_read_gain); + +/** + * abe_read_mixer + * @id: name of the mixer + * @param: gains of the mixer + * @p: port corresponding to the above gains + * + * Load the gain coefficients in FW memory. This API can be called when + * the corresponding MIXER is not activated. After reloading the firmware + * the default coefficients corresponds to "all input and output mixer's + * gain in mute state". A mixer is disabled with a network reconfiguration + * corresponding to an OPP value. + */ +u32 abe_read_mixer(u32 id, u32 *f_g, u32 p) +{ + omap_abe_read_gain(abe, id, f_g, p); + return 0; +} +EXPORT_SYMBOL(abe_read_mixer); + +/** + * abe_set_router_configuration + * @Id: name of the router + * @Conf: id of the configuration + * @param: list of output index of the route + * + * The uplink router takes its input from DMIC (6 samples), AMIC (2 samples) + * and PORT1/2 (2 stereo ports). Each sample will be individually stored in + * an intermediate table of 10 elements. + * + * Example of router table parameter for voice uplink with phoenix microphones + * + * indexes 0 .. 9 = MM_UL description (digital MICs and MMEXTIN) + * DMIC1_L_labelID, DMIC1_R_labelID, DMIC2_L_labelID, DMIC2_R_labelID, + * MM_EXT_IN_L_labelID, MM_EXT_IN_R_labelID, ZERO_labelID, ZERO_labelID, + * ZERO_labelID, ZERO_labelID, + * indexes 10 .. 11 = MM_UL2 description (recording on DMIC3) + * DMIC3_L_labelID, DMIC3_R_labelID, + * indexes 12 .. 13 = VX_UL description (VXUL based on PDMUL data) + * AMIC_L_labelID, AMIC_R_labelID, + * indexes 14 .. 15 = RESERVED (NULL) + * ZERO_labelID, ZERO_labelID, + */ +u32 abe_set_router_configuration(u32 id, u32 k, u32 *param) +{ + omap_abe_set_router_configuration(abe, id, k, param); + return 0; +} +EXPORT_SYMBOL(abe_set_router_configuration); + +/** + * abe_set_opp_processing - Set OPP mode for ABE Firmware + * @opp: OOPP mode + * + * New processing network and OPP: + * 0: Ultra Lowest power consumption audio player (no post-processing, no mixer) + * 1: OPP 25% (simple multimedia features, including low-power player) + * 2: OPP 50% (multimedia and voice calls) + * 3: OPP100% ( multimedia complex use-cases) + * + * Rearranges the FW task network to the corresponding OPP list of features. + * The corresponding AE ports are supposed to be set/reset accordingly before + * this switch. + * + */ +u32 abe_set_opp_processing(u32 opp) +{ + omap_abe_set_opp_processing(abe, opp); + return 0; +} +EXPORT_SYMBOL(abe_set_opp_processing); + +/** + * abe_disable_data_transfer + * @id: ABE port id + * + * disables the ATC descriptor and stop IO/port activities + * disable the IO task (@f = 0) + * clear ATC DMEM buffer, ATC enabled + */ +u32 abe_disable_data_transfer(u32 id) +{ + omap_abe_disable_data_transfer(abe, id); + return 0; +} +EXPORT_SYMBOL(abe_disable_data_transfer); + +/** + * abe_enable_data_transfer + * @ip: ABE port id + * + * enables the ATC descriptor + * reset ATC pointers + * enable the IO task (@f <> 0) + */ +u32 abe_enable_data_transfer(u32 id) +{ + omap_abe_enable_data_transfer(abe, id); + return 0; +} +EXPORT_SYMBOL(abe_enable_data_transfer); + +/** + * abe_connect_cbpr_dmareq_port + * @id: port name + * @f: desired data format + * @d: desired dma_request line (0..7) + * @a: returned pointer to the base address of the CBPr register and number of + * samples to exchange during a DMA_request. + * + * enables the data echange between a DMA and the ABE through the + * CBPr registers of AESS. + */ +u32 abe_connect_cbpr_dmareq_port(u32 id, abe_data_format_t *f, u32 d, + abe_dma_t *returned_dma_t) +{ + omap_abe_connect_cbpr_dmareq_port(abe, id, f, d, returned_dma_t); + return 0; +} +EXPORT_SYMBOL(abe_connect_cbpr_dmareq_port); + +/** + * abe_connect_irq_ping_pong_port + * @id: port name + * @f: desired data format + * @I: index of the call-back subroutine to call + * @s: half-buffer (ping) size + * @p: returned base address of the first (ping) buffer) + * + * enables the data echanges between a direct access to the DMEM + * memory of ABE using cache flush. On each IRQ activation a subroutine + * registered with "abe_plug_subroutine" will be called. This subroutine + * will generate an amount of samples, send them to DMEM memory and call + * "abe_set_ping_pong_buffer" to notify the new amount of samples in the + * pong buffer. + */ +u32 abe_connect_irq_ping_pong_port(u32 id, abe_data_format_t *f, + u32 subroutine_id, u32 size, + u32 *sink, u32 dsp_mcu_flag) +{ + omap_abe_connect_irq_ping_pong_port(abe, id, f, subroutine_id, size, + sink, dsp_mcu_flag); + return 0; +} +EXPORT_SYMBOL(abe_connect_irq_ping_pong_port); + +/** + * abe_connect_serial_port() + * @id: port name + * @f: data format + * @i: peripheral ID (McBSP #1, #2, #3) + * + * Operations : enables the data echanges between a McBSP and an ATC buffer in + * DMEM. This API is used connect 48kHz McBSP streams to MM_DL and 8/16kHz + * voice streams to VX_UL, VX_DL, BT_VX_UL, BT_VX_DL. It abstracts the + * abe_write_port API. + */ +u32 abe_connect_serial_port(u32 id, abe_data_format_t *f, + u32 mcbsp_id) +{ + omap_abe_connect_serial_port(abe, id, f, mcbsp_id); + return 0; +} +EXPORT_SYMBOL(abe_connect_serial_port); + +/** + * abe_read_port_address + * @dma: output pointer to the DMA iteration and data destination pointer + * + * This API returns the address of the DMA register used on this audio port. + * Depending on the protocol being used, adds the base address offset L3 + * (DMA) or MPU (ARM) + */ +u32 abe_read_port_address(u32 port, abe_dma_t *dma2) +{ + omap_abe_read_port_address(abe, port, dma2); + return 0; +} +EXPORT_SYMBOL(abe_read_port_address); + +/** + * abe_check_activity - Check if some ABE activity. + * + * Check if any ABE ports are running. + * return 1: still activity on ABE + * return 0: no more activity on ABE. Event generator can be stopped + * + */ +u32 abe_check_activity(void) +{ + return (u32)omap_abe_check_activity(abe); +} +EXPORT_SYMBOL(abe_check_activity); +/** + * abe_use_compensated_gain + * @on_off: + * + * Selects the automatic Mixer's gain management + * on_off = 1 allows the "abe_write_gain" to adjust the overall + * gains of the mixer to be tuned not to create saturation + */ +abehal_status abe_use_compensated_gain(u32 on_off) +{ + omap_abe_use_compensated_gain(abe, (int)(on_off)); + return 0; +} +EXPORT_SYMBOL(abe_use_compensated_gain); diff --git a/sound/soc/omap/abe/abe_main.h b/sound/soc/omap/abe/abe_main.h new file mode 100644 index 0000000000000000000000000000000000000000..cf1837637b991aa49e99817026b571ec515df9b0 --- /dev/null +++ b/sound/soc/omap/abe/abe_main.h @@ -0,0 +1,664 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_MAIN_H_ +#define _ABE_MAIN_H_ + +#include + +#include "abe_initxxx_labels.h" + +#define D_DEBUG_FIFO_ADDR 8160 +#define D_DEBUG_FIFO_ADDR_END 8255 + +#define SUB_0_PARAM 0 +#define SUB_1_PARAM 1 + +#define ABE_DEFAULT_BASE_ADDRESS_L3 0x49000000L +#define ABE_DMEM_BASE_ADDRESS_MPU 0x40180000L +#define ABE_DMEM_BASE_OFFSET_MPU 0x00080000L +#define ABE_DMEM_BASE_ADDRESS_L3 (ABE_DEFAULT_BASE_ADDRESS_L3 + \ + ABE_DMEM_BASE_OFFSET_MPU) + +/* + * HARDWARE AND PERIPHERAL DEFINITIONS + */ +/* MM_DL */ +#define ABE_CBPR0_IDX 0 +/* VX_DL */ +#define ABE_CBPR1_IDX 1 +/* VX_UL */ +#define ABE_CBPR2_IDX 2 +/* MM_UL */ +#define ABE_CBPR3_IDX 3 +/* MM_UL2 */ +#define ABE_CBPR4_IDX 4 +/* TONES */ +#define ABE_CBPR5_IDX 5 +/* VIB */ +#define ABE_CBPR6_IDX 6 +/* DEBUG/CTL */ +#define ABE_CBPR7_IDX 7 + +/* + * OPP TYPE + * + * 0: Ultra Lowest power consumption audio player + * 1: OPP 25% (simple multimedia features) + * 2: OPP 50% (multimedia and voice calls) + * 3: OPP100% (multimedia complex use-cases) + */ +#define ABE_OPP0 0 +#define ABE_OPP25 1 +#define ABE_OPP50 2 +#define ABE_OPP100 3 +/* + * SAMPLES TYPE + * + * mono 16 bit sample LSB aligned, 16 MSB bits are unused; + * mono right shifted to 16bits LSBs on a 32bits DMEM FIFO for McBSP + * TX purpose; + * mono sample MSB aligned (16/24/32bits); + * two successive mono samples in one 32bits container; + * Two L/R 16bits samples in a 32bits container; + * Two channels defined with two MSB aligned samples; + * Three channels defined with three MSB aligned samples (MIC); + * Four channels defined with four MSB aligned samples (MIC); + * . . . + * Eight channels defined with eight MSB aligned samples (MIC); + */ +#define MONO_MSB 1 +#define MONO_RSHIFTED_16 2 +#define STEREO_RSHIFTED_16 3 +#define STEREO_16_16 4 +#define STEREO_MSB 5 +#define THREE_MSB 6 +#define FOUR_MSB 7 +#define FIVE_MSB 8 +#define SIX_MSB 9 +#define SEVEN_MSB 10 +#define EIGHT_MSB 11 +#define NINE_MSB 12 +#define TEN_MSB 13 +/* + * PORT PROTOCOL TYPE - abe_port_protocol_switch_id + */ +#define SLIMBUS_PORT_PROT 1 +#define SERIAL_PORT_PROT 2 +#define TDM_SERIAL_PORT_PROT 3 +#define DMIC_PORT_PROT 4 +#define MCPDMDL_PORT_PROT 5 +#define MCPDMUL_PORT_PROT 6 +#define PINGPONG_PORT_PROT 7 +#define DMAREQ_PORT_PROT 8 +/* + * PORT IDs, this list is aligned with the FW data mapping + */ +#define DMIC_PORT 0 +#define PDM_UL_PORT 1 +#define BT_VX_UL_PORT 2 +#define MM_UL_PORT 3 +#define MM_UL2_PORT 4 +#define VX_UL_PORT 5 +#define MM_DL_PORT 6 +#define VX_DL_PORT 7 +#define TONES_DL_PORT 8 +#define VIB_DL_PORT 9 +#define BT_VX_DL_PORT 10 +#define PDM_DL_PORT 11 +#define MM_EXT_OUT_PORT 12 +#define MM_EXT_IN_PORT 13 +#define TDM_DL_PORT 14 +#define TDM_UL_PORT 15 +#define DEBUG_PORT 16 +#define LAST_PORT_ID 17 +/* definitions for the compatibility with HAL05xx */ +#define PDM_DL1_PORT 18 +#define PDM_DL2_PORT 19 +#define PDM_VIB_PORT 20 +/* There is only one DMIC port, always used with 6 samples + per 96kHz periods */ +#define DMIC_PORT1 DMIC_PORT +#define DMIC_PORT2 DMIC_PORT +#define DMIC_PORT3 DMIC_PORT +/* + * Signal processing module names - EQ APS MIX ROUT + */ +/* equalizer downlink path headset + earphone */ +#define FEAT_EQ1 1 +/* equalizer downlink path integrated handsfree LEFT */ +#define FEAT_EQ2L (FEAT_EQ1+1) +/* equalizer downlink path integrated handsfree RIGHT */ +#define FEAT_EQ2R (FEAT_EQ2L+1) +/* equalizer downlink path side-tone */ +#define FEAT_EQSDT (FEAT_EQ2R+1) +/* equalizer uplink path AMIC */ +#define FEAT_EQAMIC (FEAT_EQSDT+1) +/* equalizer uplink path DMIC */ +#define FEAT_EQDMIC (FEAT_EQAMIC+1) +/* Acoustic protection for headset */ +#define FEAT_APS1 (FEAT_EQDMIC+1) +/* acoustic protection high-pass filter for handsfree "Left" */ +#define FEAT_APS2 (FEAT_APS1+1) +/* acoustic protection high-pass filter for handsfree "Right" */ +#define FEAT_APS3 (FEAT_APS2+1) +/* asynchronous sample-rate-converter for the downlink voice path */ +#define FEAT_ASRC1 (FEAT_APS3+1) +/* asynchronous sample-rate-converter for the uplink voice path */ +#define FEAT_ASRC2 (FEAT_ASRC1+1) +/* asynchronous sample-rate-converter for the multimedia player */ +#define FEAT_ASRC3 (FEAT_ASRC2+1) +/* asynchronous sample-rate-converter for the echo reference */ +#define FEAT_ASRC4 (FEAT_ASRC3+1) +/* mixer of the headset and earphone path */ +#define FEAT_MIXDL1 (FEAT_ASRC4+1) +/* mixer of the hands-free path */ +#define FEAT_MIXDL2 (FEAT_MIXDL1+1) +/* mixer for audio being sent on the voice_ul path */ +#define FEAT_MIXAUDUL (FEAT_MIXDL2+1) +/* mixer for voice communication recording */ +#define FEAT_MIXVXREC (FEAT_MIXAUDUL+1) +/* mixer for side-tone */ +#define FEAT_MIXSDT (FEAT_MIXVXREC+1) +/* mixer for echo reference */ +#define FEAT_MIXECHO (FEAT_MIXSDT+1) +/* router of the uplink path */ +#define FEAT_UPROUTE (FEAT_MIXECHO+1) +/* all gains */ +#define FEAT_GAINS (FEAT_UPROUTE+1) +#define FEAT_GAINS_DMIC1 (FEAT_GAINS+1) +#define FEAT_GAINS_DMIC2 (FEAT_GAINS_DMIC1+1) +#define FEAT_GAINS_DMIC3 (FEAT_GAINS_DMIC2+1) +#define FEAT_GAINS_AMIC (FEAT_GAINS_DMIC3+1) +#define FEAT_GAINS_SPLIT (FEAT_GAINS_AMIC+1) +#define FEAT_GAINS_DL1 (FEAT_GAINS_SPLIT+1) +#define FEAT_GAINS_DL2 (FEAT_GAINS_DL1+1) +#define FEAT_GAIN_BTUL (FEAT_GAINS_DL2+1) +/* sequencing queue of micro tasks */ +#define FEAT_SEQ (FEAT_GAIN_BTUL+1) +/* Phoenix control queue through McPDM */ +#define FEAT_CTL (FEAT_SEQ+1) +/* list of features of the firmware -------------------------------*/ +#define MAXNBFEATURE FEAT_CTL +/* abe_equ_id */ +/* equalizer downlink path headset + earphone */ +#define EQ1 FEAT_EQ1 +/* equalizer downlink path integrated handsfree LEFT */ +#define EQ2L FEAT_EQ2L +#define EQ2R FEAT_EQ2R +/* equalizer downlink path side-tone */ +#define EQSDT FEAT_EQSDT +#define EQAMIC FEAT_EQAMIC +#define EQDMIC FEAT_EQDMIC +/* abe_aps_id */ +/* Acoustic protection for headset */ +#define APS1 FEAT_APS1 +#define APS2L FEAT_APS2 +#define APS2R FEAT_APS3 +/* abe_asrc_id */ +/* asynchronous sample-rate-converter for the downlink voice path */ +#define ASRC1 FEAT_ASRC1 +/* asynchronous sample-rate-converter for the uplink voice path */ +#define ASRC2 FEAT_ASRC2 +/* asynchronous sample-rate-converter for the multimedia player */ +#define ASRC3 FEAT_ASRC3 +/* asynchronous sample-rate-converter for the voice uplink echo_reference */ +#define ASRC4 FEAT_ASRC4 +/* abe_mixer_id */ +#define MIXDL1 FEAT_MIXDL1 +#define MIXDL2 FEAT_MIXDL2 +#define MIXSDT FEAT_MIXSDT +#define MIXECHO FEAT_MIXECHO +#define MIXAUDUL FEAT_MIXAUDUL +#define MIXVXREC FEAT_MIXVXREC +/* abe_router_id */ +/* there is only one router up to now */ +#define UPROUTE FEAT_UPROUTE +/* + * gain controls + */ +#define GAIN_LEFT_OFFSET 0 +#define GAIN_RIGHT_OFFSET 1 +/* + * GAIN IDs + */ +#define GAINS_DMIC1 FEAT_GAINS_DMIC1 +#define GAINS_DMIC2 FEAT_GAINS_DMIC2 +#define GAINS_DMIC3 FEAT_GAINS_DMIC3 +#define GAINS_AMIC FEAT_GAINS_AMIC +#define GAINS_SPLIT FEAT_GAINS_SPLIT +#define GAINS_DL1 FEAT_GAINS_DL1 +#define GAINS_DL2 FEAT_GAINS_DL2 +#define GAINS_BTUL FEAT_GAIN_BTUL +/* + * ABE CONST AREA FOR PARAMETERS TRANSLATION + */ +#define sizeof_alpha_iir_table 61 +#define sizeof_beta_iir_table 61 +#define GAIN_MAXIMUM 3000L +#define GAIN_24dB 2400L +#define GAIN_18dB 1800L +#define GAIN_12dB 1200L +#define GAIN_6dB 600L +/* default gain = 1 */ +#define GAIN_0dB 0L +#define GAIN_M6dB -600L +#define GAIN_M12dB -1200L +#define GAIN_M18dB -1800L +#define GAIN_M24dB -2400L +#define GAIN_M30dB -3000L +#define GAIN_M40dB -4000L +#define GAIN_M50dB -5000L +/* muted gain = -120 decibels */ +#define MUTE_GAIN -12000L +#define GAIN_TOOLOW -13000L +#define GAIN_MUTE MUTE_GAIN +#define RAMP_MINLENGTH 3L +/* ramp_t is in milli- seconds */ +#define RAMP_0MS 0L +#define RAMP_1MS 1L +#define RAMP_2MS 2L +#define RAMP_5MS 5L +#define RAMP_10MS 10L +#define RAMP_20MS 20L +#define RAMP_50MS 50L +#define RAMP_100MS 100L +#define RAMP_200MS 200L +#define RAMP_500MS 500L +#define RAMP_1000MS 1000L +#define RAMP_MAXLENGTH 10000L +/* for abe_translate_gain_format */ +#define LINABE_TO_DECIBELS 1 +#define DECIBELS_TO_LINABE 2 +/* for abe_translate_ramp_format */ +#define IIRABE_TO_MICROS 1 +#define MICROS_TO_IIABE 2 +/* + * EVENT GENERATORS - abe_event_id + */ +#define EVENT_TIMER 0 +#define EVENT_44100 1 +/* + * DMA requests + */ +/*Internal connection doesn't connect at ABE boundary */ +#define External_DMA_0 0 +/*Transmit request digital microphone */ +#define DMIC_DMA_REQ 1 +/*Multichannel PDM downlink */ +#define McPDM_DMA_DL 2 +/*Multichannel PDM uplink */ +#define McPDM_DMA_UP 3 +/*MCBSP module 1 - transmit request */ +#define MCBSP1_DMA_TX 4 +/*MCBSP module 1 - receive request */ +#define MCBSP1_DMA_RX 5 +/*MCBSP module 2 - transmit request */ +#define MCBSP2_DMA_TX 6 +/*MCBSP module 2 - receive request */ +#define MCBSP2_DMA_RX 7 +/*MCBSP module 3 - transmit request */ +#define MCBSP3_DMA_TX 8 +/*MCBSP module 3 - receive request */ +#define MCBSP3_DMA_RX 9 +/* + * SERIAL PORTS IDs - abe_mcbsp_id + */ +#define MCBSP1_TX MCBSP1_DMA_TX +#define MCBSP1_RX MCBSP1_DMA_RX +#define MCBSP2_TX MCBSP2_DMA_TX +#define MCBSP2_RX MCBSP2_DMA_RX +#define MCBSP3_TX MCBSP3_DMA_TX +#define MCBSP3_RX MCBSP3_DMA_RX + +#define PING_PONG_WITH_MCU_IRQ 1 +#define PING_PONG_WITH_DSP_IRQ 2 + +/* + Mixer ID Input port ID Comments + DL1_MIXER 0 MMDL path + 1 MMUL2 path + 2 VXDL path + 3 TONES path + SDT_MIXER 0 Uplink path + 1 Downlink path + ECHO_MIXER 0 DL1_MIXER path + 1 DL2_MIXER path + AUDUL_MIXER 0 TONES_DL path + 1 Uplink path + 2 MM_DL path + VXREC_MIXER 0 TONES_DL path + 1 VX_DL path + 2 MM_DL path + 3 VX_UL path +*/ +#define MIX_VXUL_INPUT_MM_DL 0 +#define MIX_VXUL_INPUT_TONES 1 +#define MIX_VXUL_INPUT_VX_UL 2 +#define MIX_VXUL_INPUT_VX_DL 3 +#define MIX_DL1_INPUT_MM_DL 0 +#define MIX_DL1_INPUT_MM_UL2 1 +#define MIX_DL1_INPUT_VX_DL 2 +#define MIX_DL1_INPUT_TONES 3 +#define MIX_DL2_INPUT_MM_DL 0 +#define MIX_DL2_INPUT_MM_UL2 1 +#define MIX_DL2_INPUT_VX_DL 2 +#define MIX_DL2_INPUT_TONES 3 +#define MIX_SDT_INPUT_UP_MIXER 0 +#define MIX_SDT_INPUT_DL1_MIXER 1 +#define MIX_AUDUL_INPUT_MM_DL 0 +#define MIX_AUDUL_INPUT_TONES 1 +#define MIX_AUDUL_INPUT_UPLINK 2 +#define MIX_AUDUL_INPUT_VX_DL 3 +#define MIX_VXREC_INPUT_MM_DL 0 +#define MIX_VXREC_INPUT_TONES 1 +#define MIX_VXREC_INPUT_VX_UL 2 +#define MIX_VXREC_INPUT_VX_DL 3 +#define MIX_ECHO_DL1 0 +#define MIX_ECHO_DL2 1 +/* nb of samples to route */ +#define NBROUTE_UL 16 +/* 10 routing tables max */ +#define NBROUTE_CONFIG_MAX 10 +/* 5 pre-computed routing tables */ +#define NBROUTE_CONFIG 6 +/* AMIC on VX_UL */ +#define UPROUTE_CONFIG_AMIC 0 +/* DMIC first pair on VX_UL */ +#define UPROUTE_CONFIG_DMIC1 1 +/* DMIC second pair on VX_UL */ +#define UPROUTE_CONFIG_DMIC2 2 +/* DMIC last pair on VX_UL */ +#define UPROUTE_CONFIG_DMIC3 3 +/* BT_UL on VX_UL */ +#define UPROUTE_CONFIG_BT 4 +/* ECHO_REF on MM_UL2 */ +#define UPROUTE_ECHO_MMUL2 5 + +/* + * DMA_T + * + * dma structure for easing programming + */ +typedef struct { + /* OCP L3 pointer to the first address of the */ + void *data; + /* destination buffer (either DMA or Ping-Pong read/write pointers). */ + /* address L3 when addressing the DMEM buffer instead of CBPr */ + void *l3_dmem; + /* address L3 translated to L4 the ARM memory space */ + void *l4_dmem; + /* number of iterations for the DMA data moves. */ + u32 iter; +} abe_dma_t; +typedef u32 abe_dbg_t; +/* + * ROUTER_T + * + * table of indexes in unsigned bytes + */ +typedef u16 abe_router_t; +/* + * DATA_FORMAT_T + * + * used in port declaration + */ +typedef struct { + /* Sampling frequency of the stream */ + u32 f; + /* Sample format type */ + u32 samp_format; +} abe_data_format_t; +/* + * PORT_PROTOCOL_T + * + * port declaration + */ +typedef struct { + /* Direction=0 means input from AESS point of view */ + u32 direction; + /* Protocol type (switch) during the data transfers */ + u32 protocol_switch; + union { + /* Slimbus peripheral connected to ATC */ + struct { + /* Address of ATC Slimbus descriptor's index */ + u32 desc_addr1; + /* DMEM address 1 in bytes */ + u32 buf_addr1; + /* DMEM buffer size size in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + /* Second ATC index for SlimBus reception (or NULL) */ + u32 desc_addr2; + /* DMEM address 2 in bytes */ + u32 buf_addr2; + } prot_slimbus; + /* McBSP/McASP peripheral connected to ATC */ + struct { + u32 desc_addr; + /* Address of ATC McBSP/McASP descriptor's in bytes */ + u32 buf_addr; + /* DMEM address in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + } prot_serial; + /* DMIC peripheral connected to ATC */ + struct { + /* DMEM address in bytes */ + u32 buf_addr; + /* DMEM buffer size in bytes */ + u32 buf_size; + /* Number of activated DMIC */ + u32 nbchan; + } prot_dmic; + /* McPDMDL peripheral connected to ATC */ + struct { + /* DMEM address in bytes */ + u32 buf_addr; + /* DMEM size in bytes */ + u32 buf_size; + /* Control allowed on McPDM DL */ + u32 control; + } prot_mcpdmdl; + /* McPDMUL peripheral connected to ATC */ + struct { + /* DMEM address size in bytes */ + u32 buf_addr; + /* DMEM buffer size size in bytes */ + u32 buf_size; + } prot_mcpdmul; + /* Ping-Pong interface to the Host using cache-flush */ + struct { + /* Address of ATC descriptor's */ + u32 desc_addr; + /* DMEM buffer base address in bytes */ + u32 buf_addr; + /* DMEM size in bytes for each ping and pong buffers */ + u32 buf_size; + /* IRQ address (either DMA (0) MCU (1) or DSP(2)) */ + u32 irq_addr; + /* IRQ data content loaded in the AESS IRQ register */ + u32 irq_data; + /* Call-back function upon IRQ reception */ + u32 callback; + } prot_pingpong; + /* DMAreq line to CBPr */ + struct { + /* Address of ATC descriptor's */ + u32 desc_addr; + /* DMEM buffer address in bytes */ + u32 buf_addr; + /* DMEM buffer size size in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + /* DMAreq address */ + u32 dma_addr; + /* DMA/AESS = 1 << #DMA */ + u32 dma_data; + } prot_dmareq; + /* Circular buffer - direct addressing to DMEM */ + struct { + /* DMEM buffer base address in bytes */ + u32 buf_addr; + /* DMEM buffer size in bytes */ + u32 buf_size; + /* DMAreq address */ + u32 dma_addr; + /* DMA/AESS = 1 << #DMA */ + u32 dma_data; + } prot_circular_buffer; + } p; +} abe_port_protocol_t; + +/* + * EQU_T + * + * coefficients of the equalizer + */ +/* 24 Q6.26 coefficients */ +#define NBEQ1 25 +/* 2x12 Q6.26 coefficients */ +#define NBEQ2 13 + +typedef struct { + /* type of filter */ + u32 equ_type; + /* filter length */ + u32 equ_length; + union { + /* parameters are the direct and recursive coefficients in */ + /* Q6.26 integer fixed-point format. */ + s32 type1[NBEQ1]; + struct { + /* center frequency of the band [Hz] */ + s32 freq[NBEQ2]; + /* gain of each band. [dB] */ + s32 gain[NBEQ2]; + /* Q factor of this band [dB] */ + s32 q[NBEQ2]; + } type2; + } coef; + s32 equ_param3; +} abe_equ_t; + + +/* subroutine with no parameter */ +typedef void (*abe_subroutine0) (void); +/* subroutine with one parameter */ +typedef void (*abe_subroutine1) (u32); +typedef void (*abe_subroutine2) (u32, u32); +typedef void (*abe_subroutine3) (u32, u32, u32); +typedef void (*abe_subroutine4) (u32, u32, u32, u32); + + +extern u32 abe_irq_pingpong_player_id; + + +void abe_init_mem(void __iomem **_io_base); +u32 abe_reset_hal(void); +int abe_load_fw(u32 *firmware); +int abe_reload_fw(u32 *firmware); +u32 *abe_get_default_fw(void); +u32 abe_wakeup(void); +u32 abe_irq_processing(void); +u32 abe_clear_irq(void); +u32 abe_disable_irq(void); +u32 abe_write_event_generator(u32 e); +u32 abe_stop_event_generator(void); +u32 abe_connect_debug_trace(abe_dma_t *dma2); +u32 abe_set_debug_trace(abe_dbg_t debug); +u32 abe_set_ping_pong_buffer(u32 port, u32 n_bytes); +u32 abe_read_next_ping_pong_buffer(u32 port, u32 *p, u32 *n); +u32 abe_init_ping_pong_buffer(u32 id, u32 size_bytes, u32 n_buffers, + u32 *p); +u32 abe_read_offset_from_ping_buffer(u32 id, u32 *n); +u32 abe_write_equalizer(u32 id, abe_equ_t *param); +u32 abe_disable_gain(u32 id, u32 p); +u32 abe_enable_gain(u32 id, u32 p); +u32 abe_mute_gain(u32 id, u32 p); +u32 abe_unmute_gain(u32 id, u32 p); +u32 abe_write_gain(u32 id, s32 f_g, u32 ramp, u32 p); +u32 abe_write_mixer(u32 id, s32 f_g, u32 f_ramp, u32 p); +u32 abe_read_gain(u32 id, u32 *f_g, u32 p); +u32 abe_read_mixer(u32 id, u32 *f_g, u32 p); +u32 abe_set_router_configuration(u32 id, u32 k, u32 *param); +u32 abe_set_opp_processing(u32 opp); +u32 abe_disable_data_transfer(u32 id); +u32 abe_enable_data_transfer(u32 id); +u32 abe_connect_cbpr_dmareq_port(u32 id, abe_data_format_t *f, u32 d, + abe_dma_t *returned_dma_t); +u32 abe_connect_irq_ping_pong_port(u32 id, abe_data_format_t *f, + u32 subroutine_id, u32 size, + u32 *sink, u32 dsp_mcu_flag); +u32 abe_connect_serial_port(u32 id, abe_data_format_t *f, + u32 mcbsp_id); +u32 abe_read_port_address(u32 port, abe_dma_t *dma2); +void abe_add_subroutine(u32 *id, abe_subroutine2 f, u32 nparam, u32 *params); +u32 abe_read_next_ping_pong_buffer(u32 port, u32 *p, u32 *n); +u32 abe_check_activity(void); +void abe_add_subroutine(u32 *id, abe_subroutine2 f, + u32 nparam, u32 *params); + +u32 abe_plug_subroutine(u32 *id, abe_subroutine2 f, u32 n, + u32 *params); + +#endif /* _ABE_MAIN_H_ */ diff --git a/sound/soc/omap/abe/abe_mem.h b/sound/soc/omap/abe/abe_mem.h new file mode 100644 index 0000000000000000000000000000000000000000..683968e9b180437a712c01eed8defcf9ddeeb618 --- /dev/null +++ b/sound/soc/omap/abe/abe_mem.h @@ -0,0 +1,99 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_MEM_H_ +#define _ABE_MEM_H_ + +#define OMAP_ABE_DMEM 0 +#define OMAP_ABE_CMEM 1 +#define OMAP_ABE_SMEM 2 +#define OMAP_ABE_PMEM 3 +#define OMAP_ABE_AESS 4 + +/* Distinction between Read and Write from/to ABE memory + * is useful for simulation tool */ +static inline void omap_abe_mem_write(struct omap_abe *abe, int bank, + u32 offset, u32 *src, size_t bytes) +{ + memcpy((abe->io_base[bank] + offset), src, bytes); +} + +static inline void omap_abe_mem_read(struct omap_abe *abe, int bank, + u32 offset, u32 *dest, size_t bytes) +{ + memcpy(dest, (abe->io_base[bank] + offset), bytes); +} + +static inline u32 omap_abe_reg_readl(struct omap_abe *abe, u32 offset) +{ + return __raw_readl(abe->io_base[OMAP_ABE_AESS] + offset); +} + +static inline void omap_abe_reg_writel(struct omap_abe *abe, + u32 offset, u32 val) +{ + __raw_writel(val, (abe->io_base[OMAP_ABE_AESS] + offset)); +} + +static inline void *omap_abe_reset_mem(struct omap_abe *abe, int bank, + u32 offset, size_t bytes) +{ + return memset(abe->io_base[bank] + offset, 0, bytes); +} + +#endif /*_ABE_MEM_H_*/ diff --git a/sound/soc/omap/abe/abe_port.c b/sound/soc/omap/abe/abe_port.c new file mode 100644 index 0000000000000000000000000000000000000000..1923d35a8ce43d1c58c09977ffb007acbd48bc69 --- /dev/null +++ b/sound/soc/omap/abe/abe_port.c @@ -0,0 +1,1747 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include +#include +#include +#include +#include + +#include "abe_legacy.h" +#include "abe_port.h" +#include "abe_dbg.h" +#include "abe_mem.h" +#include "abe_gain.h" + +/** + * abe_clean_temporay buffers + * + * clear temporary buffers + */ +void omap_abe_clean_temporary_buffers(struct omap_abe *abe, u32 id) +{ + switch (id) { + case OMAP_ABE_DMIC_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_DMIC_UL_FIFO_ADDR, + OMAP_ABE_D_DMIC_UL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_DMIC0_96_48_DATA_ADDR, + OMAP_ABE_S_DMIC0_96_48_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_DMIC1_96_48_DATA_ADDR, + OMAP_ABE_S_DMIC1_96_48_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_DMIC2_96_48_DATA_ADDR, + OMAP_ABE_S_DMIC2_96_48_DATA_SIZE); + /* reset working values of the gain, target gain is preserved */ + omap_abe_reset_gain_mixer(abe, GAINS_DMIC1, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DMIC1, GAIN_RIGHT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DMIC2, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DMIC2, GAIN_RIGHT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DMIC3, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DMIC3, GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_PDM_UL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MCPDM_UL_FIFO_ADDR, + OMAP_ABE_D_MCPDM_UL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_AMIC_96_48_DATA_ADDR, + OMAP_ABE_S_AMIC_96_48_DATA_SIZE); + /* reset working values of the gain, target gain is preserved */ + omap_abe_reset_gain_mixer(abe, GAINS_AMIC, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_AMIC, GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_BT_VX_UL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_BT_UL_FIFO_ADDR, + OMAP_ABE_D_BT_UL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_UL_ADDR, + OMAP_ABE_S_BT_UL_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_UL_8_48_HP_DATA_ADDR, + OMAP_ABE_S_BT_UL_8_48_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_UL_8_48_LP_DATA_ADDR, + OMAP_ABE_S_BT_UL_8_48_LP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_UL_16_48_HP_DATA_ADDR, + OMAP_ABE_S_BT_UL_16_48_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_UL_16_48_LP_DATA_ADDR, + OMAP_ABE_S_BT_UL_16_48_LP_DATA_SIZE); + /* reset working values of the gain, target gain is preserved */ + omap_abe_reset_gain_mixer(abe, GAINS_BTUL, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_BTUL, GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_MM_UL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MM_UL_FIFO_ADDR, + OMAP_ABE_D_MM_UL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_MM_UL_ADDR, + OMAP_ABE_S_MM_UL_SIZE); + break; + case OMAP_ABE_MM_UL2_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MM_UL2_FIFO_ADDR, + OMAP_ABE_D_MM_UL2_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_MM_UL2_ADDR, + OMAP_ABE_S_MM_UL2_SIZE); + break; + case OMAP_ABE_VX_UL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_VX_UL_FIFO_ADDR, + OMAP_ABE_D_VX_UL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_UL_ADDR, + OMAP_ABE_S_VX_UL_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_UL_48_8_HP_DATA_ADDR, + OMAP_ABE_S_VX_UL_48_8_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_UL_48_8_LP_DATA_ADDR, + OMAP_ABE_S_VX_UL_48_8_LP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_UL_48_16_HP_DATA_ADDR, + OMAP_ABE_S_VX_UL_48_16_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_UL_48_16_LP_DATA_ADDR, + OMAP_ABE_S_VX_UL_48_16_LP_DATA_SIZE); + omap_abe_reset_gain_mixer(abe, MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); + break; + case OMAP_ABE_MM_DL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MM_DL_FIFO_ADDR, + OMAP_ABE_D_MM_DL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_MM_DL_ADDR, + OMAP_ABE_S_MM_DL_SIZE); + omap_abe_reset_gain_mixer(abe, MIXDL1, MIX_DL1_INPUT_MM_DL); + omap_abe_reset_gain_mixer(abe, MIXDL2, MIX_DL2_INPUT_MM_DL); + break; + case OMAP_ABE_VX_DL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_VX_DL_FIFO_ADDR, + OMAP_ABE_D_VX_DL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_DL_ADDR, + OMAP_ABE_S_VX_DL_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_DL_8_48_HP_DATA_ADDR, + OMAP_ABE_S_VX_DL_8_48_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_DL_8_48_LP_DATA_ADDR, + OMAP_ABE_S_VX_DL_8_48_LP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_DL_8_48_OSR_LP_DATA_ADDR, + OMAP_ABE_S_VX_DL_8_48_OSR_LP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_DL_16_48_HP_DATA_ADDR, + OMAP_ABE_S_VX_DL_16_48_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VX_DL_16_48_LP_DATA_ADDR, + OMAP_ABE_S_VX_DL_16_48_LP_DATA_SIZE); + omap_abe_reset_gain_mixer(abe, MIXDL1, MIX_DL1_INPUT_VX_DL); + omap_abe_reset_gain_mixer(abe, MIXDL2, MIX_DL2_INPUT_VX_DL); + break; + case OMAP_ABE_TONES_DL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_TONES_DL_FIFO_ADDR, + OMAP_ABE_D_TONES_DL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_TONES_ADDR, + OMAP_ABE_S_TONES_SIZE); + omap_abe_reset_gain_mixer(abe, MIXDL1, MIX_DL1_INPUT_TONES); + omap_abe_reset_gain_mixer(abe, MIXDL2, MIX_DL2_INPUT_TONES); + break; + case OMAP_ABE_VIB_DL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_VIB_DL_FIFO_ADDR, + OMAP_ABE_D_VIB_DL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_VIBRA_ADDR, + OMAP_ABE_S_VIBRA_SIZE); + break; + case OMAP_ABE_BT_VX_DL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_BT_DL_FIFO_ADDR, + OMAP_ABE_D_BT_DL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_DL_ADDR, + OMAP_ABE_S_BT_DL_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_DL_48_8_HP_DATA_ADDR, + OMAP_ABE_S_BT_DL_48_8_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_DL_48_8_LP_DATA_ADDR, + OMAP_ABE_S_BT_DL_48_8_LP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_DL_48_16_HP_DATA_ADDR, + OMAP_ABE_S_BT_DL_48_16_HP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_BT_DL_48_16_LP_DATA_ADDR, + OMAP_ABE_S_BT_DL_48_16_LP_DATA_SIZE); + break; + case OMAP_ABE_PDM_DL_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MCPDM_DL_FIFO_ADDR, + OMAP_ABE_D_MCPDM_DL_FIFO_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_DL2_M_LR_EQ_DATA_ADDR, + OMAP_ABE_S_DL2_M_LR_EQ_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_DL1_M_EQ_DATA_ADDR, + OMAP_ABE_S_DL1_M_EQ_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_EARP_48_96_LP_DATA_ADDR, + OMAP_ABE_S_EARP_48_96_LP_DATA_SIZE); + omap_abe_reset_mem(abe, OMAP_ABE_SMEM, + OMAP_ABE_S_IHF_48_96_LP_DATA_ADDR, + OMAP_ABE_S_IHF_48_96_LP_DATA_SIZE); + omap_abe_reset_gain_mixer(abe, GAINS_DL1, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DL1, GAIN_RIGHT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DL2, GAIN_LEFT_OFFSET); + omap_abe_reset_gain_mixer(abe, GAINS_DL2, GAIN_RIGHT_OFFSET); + omap_abe_reset_gain_mixer(abe, MIXSDT, MIX_SDT_INPUT_UP_MIXER); + omap_abe_reset_gain_mixer(abe, MIXSDT, MIX_SDT_INPUT_DL1_MIXER); + break; + case OMAP_ABE_MM_EXT_OUT_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MM_EXT_OUT_FIFO_ADDR, + OMAP_ABE_D_MM_EXT_OUT_FIFO_SIZE); + break; + case OMAP_ABE_MM_EXT_IN_PORT: + omap_abe_reset_mem(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MM_EXT_IN_FIFO_ADDR, + OMAP_ABE_D_MM_EXT_IN_FIFO_SIZE); + break; + } +} + +/** + * omap_abe_disable_enable_dma_request + * Parameter: + * Operations: + * Return value: + */ +void omap_abe_disable_enable_dma_request(struct omap_abe *abe, u32 id, + u32 on_off) +{ + u8 desc_third_word[4], irq_dmareq_field; + u32 sio_desc_address; + u32 struct_offset; + struct ABE_SIODescriptor sio_desc; + struct ABE_SPingPongDescriptor desc_pp; + + if (abe_port[id].protocol.protocol_switch == PINGPONG_PORT_PROT) { + irq_dmareq_field = + (u8) (on_off * + abe_port[id].protocol.p.prot_pingpong.irq_data); + sio_desc_address = OMAP_ABE_D_PINGPONGDESC_ADDR; + struct_offset = (u32) &(desc_pp.data_size) - (u32) &(desc_pp); + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + sio_desc_address + struct_offset, + (u32 *) desc_third_word, 4); + desc_third_word[2] = irq_dmareq_field; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + sio_desc_address + struct_offset, + (u32 *) desc_third_word, 4); + } else { + /* serial interface: sync ATC with Firmware activity */ + sio_desc_address = + OMAP_ABE_D_IODESCR_ADDR + + (id * sizeof(struct ABE_SIODescriptor)); + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + sio_desc_address, (u32 *) &sio_desc, + sizeof(sio_desc)); + if (on_off) { + if (abe_port[id].protocol.protocol_switch != SERIAL_PORT_PROT) + sio_desc.atc_irq_data = + (u8) abe_port[id].protocol.p.prot_dmareq. + dma_data; + sio_desc.on_off = 0x80; + } else { + sio_desc.atc_irq_data = 0; + sio_desc.on_off = 0; + } + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + sio_desc_address, (u32 *) &sio_desc, + sizeof(sio_desc)); + } + +} + +/** + * omap_abe_enable_dma_request + * + * Parameter: + * Operations: + * Return value: + * + */ +void omap_abe_enable_dma_request(struct omap_abe *abe, u32 id) +{ + omap_abe_disable_enable_dma_request(abe, id, 1); +} + +/** + * omap_abe_disable_dma_request + * + * Parameter: + * Operations: + * Return value: + * + */ +void omap_abe_disable_dma_request(struct omap_abe *abe, u32 id) +{ + omap_abe_disable_enable_dma_request(abe, id, 0); +} + +/** + * abe_init_atc + * @id: ABE port ID + * + * load the DMEM ATC/AESS descriptors + */ +void omap_abe_init_atc(struct omap_abe *abe, u32 id) +{ + u8 iter; + s32 datasize; + struct omap_abe_atc_desc atc_desc; + +#define JITTER_MARGIN 4 + /* load default values of the descriptor */ + atc_desc.rdpt = 0; + atc_desc.wrpt = 0; + atc_desc.irqdest = 0; + atc_desc.cberr = 0; + atc_desc.desen = 0; + atc_desc.nw = 0; + atc_desc.reserved0 = 0; + atc_desc.reserved1 = 0; + atc_desc.reserved2 = 0; + atc_desc.srcid = 0; + atc_desc.destid = 0; + atc_desc.badd = 0; + atc_desc.iter = 0; + atc_desc.cbsize = 0; + datasize = abe_dma_port_iter_factor(&((abe_port[id]).format)); + iter = (u8) abe_dma_port_iteration(&((abe_port[id]).format)); + /* if the ATC FIFO is too small there will be two ABE firmware + utasks to do the copy this happems on DMIC and MCPDMDL */ + /* VXDL_8kMono = 4 = 2 + 2x1 */ + /* VXDL_16kstereo = 12 = 8 + 2x2 */ + /* MM_DL_1616 = 14 = 12 + 2x1 */ + /* DMIC = 84 = 72 + 2x6 */ + /* VXUL_8kMono = 2 */ + /* VXUL_16kstereo = 4 */ + /* MM_UL2_Stereo = 4 */ + /* PDMDL = 12 */ + /* IN from AESS point of view */ + if (abe_port[id].protocol.direction == ABE_ATC_DIRECTION_IN) + if (iter + 2 * datasize > 126) + atc_desc.wrpt = (iter >> 1) + + ((JITTER_MARGIN-1) * datasize); + else + atc_desc.wrpt = iter + ((JITTER_MARGIN-1) * datasize); + else + atc_desc.wrpt = 0 + ((JITTER_MARGIN+1) * datasize); + switch ((abe_port[id]).protocol.protocol_switch) { + case SLIMBUS_PORT_PROT: + atc_desc.cbdir = (abe_port[id]).protocol.direction; + atc_desc.cbsize = + (abe_port[id]).protocol.p.prot_slimbus.buf_size; + atc_desc.badd = + ((abe_port[id]).protocol.p.prot_slimbus.buf_addr1) >> 4; + atc_desc.iter = (abe_port[id]).protocol.p.prot_slimbus.iter; + atc_desc.srcid = + abe_atc_srcid[(abe_port[id]).protocol.p.prot_slimbus. + desc_addr1 >> 3]; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_slimbus. + desc_addr1, (u32 *) &atc_desc, sizeof(atc_desc)); + atc_desc.badd = + (abe_port[id]).protocol.p.prot_slimbus.buf_addr2; + atc_desc.srcid = + abe_atc_srcid[(abe_port[id]).protocol.p.prot_slimbus. + desc_addr2 >> 3]; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_slimbus. + desc_addr2, (u32 *) &atc_desc, sizeof(atc_desc)); + break; + case SERIAL_PORT_PROT: + atc_desc.cbdir = (abe_port[id]).protocol.direction; + atc_desc.cbsize = + (abe_port[id]).protocol.p.prot_serial.buf_size; + atc_desc.badd = + ((abe_port[id]).protocol.p.prot_serial.buf_addr) >> 4; + atc_desc.iter = (abe_port[id]).protocol.p.prot_serial.iter; + atc_desc.srcid = + abe_atc_srcid[(abe_port[id]).protocol.p.prot_serial. + desc_addr >> 3]; + atc_desc.destid = + abe_atc_dstid[(abe_port[id]).protocol.p.prot_serial. + desc_addr >> 3]; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_serial.desc_addr, + (u32 *) &atc_desc, sizeof(atc_desc)); + break; + case DMIC_PORT_PROT: + atc_desc.cbdir = ABE_ATC_DIRECTION_IN; + atc_desc.cbsize = (abe_port[id]).protocol.p.prot_dmic.buf_size; + atc_desc.badd = + ((abe_port[id]).protocol.p.prot_dmic.buf_addr) >> 4; + atc_desc.iter = DMIC_ITER; + atc_desc.srcid = abe_atc_srcid[ABE_ATC_DMIC_DMA_REQ]; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (ABE_ATC_DMIC_DMA_REQ*ATC_SIZE), + (u32 *) &atc_desc, sizeof(atc_desc)); + break; + case MCPDMDL_PORT_PROT: + atc_desc.cbdir = ABE_ATC_DIRECTION_OUT; + atc_desc.cbsize = + (abe_port[id]).protocol.p.prot_mcpdmdl.buf_size; + atc_desc.badd = + ((abe_port[id]).protocol.p.prot_mcpdmdl.buf_addr) >> 4; + atc_desc.iter = MCPDM_DL_ITER; + atc_desc.destid = abe_atc_dstid[ABE_ATC_MCPDMDL_DMA_REQ]; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (ABE_ATC_MCPDMDL_DMA_REQ*ATC_SIZE), + (u32 *) &atc_desc, sizeof(atc_desc)); + break; + case MCPDMUL_PORT_PROT: + atc_desc.cbdir = ABE_ATC_DIRECTION_IN; + atc_desc.cbsize = + (abe_port[id]).protocol.p.prot_mcpdmul.buf_size; + atc_desc.badd = + ((abe_port[id]).protocol.p.prot_mcpdmul.buf_addr) >> 4; + atc_desc.iter = MCPDM_UL_ITER; + atc_desc.srcid = abe_atc_srcid[ABE_ATC_MCPDMUL_DMA_REQ]; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (ABE_ATC_MCPDMUL_DMA_REQ*ATC_SIZE), + (u32 *) &atc_desc, sizeof(atc_desc)); + break; + case PINGPONG_PORT_PROT: + /* software protocol, nothing to do on ATC */ + break; + case DMAREQ_PORT_PROT: + atc_desc.cbdir = (abe_port[id]).protocol.direction; + atc_desc.cbsize = + (abe_port[id]).protocol.p.prot_dmareq.buf_size; + atc_desc.badd = + ((abe_port[id]).protocol.p.prot_dmareq.buf_addr) >> 4; + /* CBPr needs ITER=1. + It is the job of eDMA to do the iterations */ + atc_desc.iter = 1; + /* input from ABE point of view */ + if (abe_port[id].protocol.direction == ABE_ATC_DIRECTION_IN) { + /* atc_atc_desc.rdpt = 127; */ + /* atc_atc_desc.wrpt = 0; */ + atc_desc.srcid = abe_atc_srcid + [(abe_port[id]).protocol.p.prot_dmareq. + desc_addr >> 3]; + } else { + /* atc_atc_desc.rdpt = 0; */ + /* atc_atc_desc.wrpt = 127; */ + atc_desc.destid = abe_atc_dstid + [(abe_port[id]).protocol.p.prot_dmareq. + desc_addr >> 3]; + } + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_dmareq.desc_addr, + (u32 *) &atc_desc, sizeof(atc_desc)); + break; + } +} + +/** + * omap_abe_enable_pp_io_task + * @id: port_id + * + * + */ +void omap_abe_enable_pp_io_task(struct omap_abe *abe, u32 id) +{ + if (OMAP_ABE_MM_DL_PORT == id) { + /* MM_DL managed in ping-pong */ + abe->MultiFrame[TASK_IO_MM_DL_SLT][TASK_IO_MM_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_IO_PING_PONG); + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MULTIFRAME_ADDR, (u32 *) abe->MultiFrame, + sizeof(abe->MultiFrame)); + } else { + /* ping_pong is only supported on MM_DL */ + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } +} +/** + * omap_abe_disable_pp_io_task + * @id: port_id + * + * + */ +void omap_abe_disable_pp_io_task(struct omap_abe *abe, u32 id) +{ + if (OMAP_ABE_MM_DL_PORT == id) { + /* MM_DL managed in ping-pong */ + abe->MultiFrame[TASK_IO_MM_DL_SLT][TASK_IO_MM_DL_IDX] = 0; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MULTIFRAME_ADDR, (u32 *) abe->MultiFrame, + sizeof(abe->MultiFrame)); + } else { + /* ping_pong is only supported on MM_DL */ + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } +} + +/** + * omap_abe_disable_data_transfer + * @id: ABE port id + * + * disables the ATC descriptor and stop IO/port activities + * disable the IO task (@f = 0) + * clear ATC DMEM buffer, ATC enabled + */ +int omap_abe_disable_data_transfer(struct omap_abe *abe, u32 id) +{ + + _log(ABE_ID_DISABLE_DATA_TRANSFER, id, 0, 0); + + /* MM_DL managed in ping-pong */ + if (id == OMAP_ABE_MM_DL_PORT) { + if (abe_port[OMAP_ABE_MM_DL_PORT].protocol.protocol_switch == PINGPONG_PORT_PROT) + omap_abe_disable_pp_io_task(abe, OMAP_ABE_MM_DL_PORT); + } + /* local host variable status= "port is running" */ + abe_port[id].status = OMAP_ABE_PORT_ACTIVITY_IDLE; + /* disable DMA requests */ + omap_abe_disable_dma_request(abe, id); + /* disable ATC transfers */ + omap_abe_init_atc(abe, id); + omap_abe_clean_temporary_buffers(abe, id); + /* select the main port based on the desactivation of this port */ + abe_decide_main_port(); + + return 0; +} +EXPORT_SYMBOL(omap_abe_disable_data_transfer); + +/** + * omap_abe_enable_data_transfer + * @ip: ABE port id + * + * enables the ATC descriptor + * reset ATC pointers + * enable the IO task (@f <> 0) + */ +int omap_abe_enable_data_transfer(struct omap_abe *abe, u32 id) +{ + abe_port_protocol_t *protocol; + abe_data_format_t format; + + _log(ABE_ID_ENABLE_DATA_TRANSFER, id, 0, 0); + omap_abe_clean_temporary_buffers(abe, id); + if (id == OMAP_ABE_PDM_UL_PORT) { + /* initializes the ABE ATC descriptors in DMEM - MCPDM_UL */ + protocol = &(abe_port[OMAP_ABE_PDM_UL_PORT].protocol); + format = abe_port[OMAP_ABE_PDM_UL_PORT].format; + omap_abe_init_atc(abe, OMAP_ABE_PDM_UL_PORT); + abe_init_io_tasks(OMAP_ABE_PDM_UL_PORT, &format, protocol); + } + if (id == OMAP_ABE_PDM_DL_PORT) { + /* initializes the ABE ATC descriptors in DMEM - MCPDM_DL */ + protocol = &(abe_port[OMAP_ABE_PDM_DL_PORT].protocol); + format = abe_port[OMAP_ABE_PDM_DL_PORT].format; + omap_abe_init_atc(abe, OMAP_ABE_PDM_DL_PORT); + abe_init_io_tasks(OMAP_ABE_PDM_DL_PORT, &format, protocol); + } + /* MM_DL managed in ping-pong */ + if (id == OMAP_ABE_MM_DL_PORT) { + protocol = &(abe_port[OMAP_ABE_MM_DL_PORT].protocol); + if (protocol->protocol_switch == PINGPONG_PORT_PROT) + omap_abe_enable_pp_io_task(abe, OMAP_ABE_MM_DL_PORT); + } + if (id == OMAP_ABE_DMIC_PORT) { + /* one DMIC port enabled = all DMICs enabled, + * since there is a single DMIC path for all DMICs */ + protocol = &(abe_port[OMAP_ABE_DMIC_PORT].protocol); + format = abe_port[OMAP_ABE_DMIC_PORT].format; + omap_abe_init_atc(abe, OMAP_ABE_DMIC_PORT); + abe_init_io_tasks(OMAP_ABE_DMIC_PORT, &format, protocol); + } + if (id == OMAP_ABE_VX_UL_PORT) { + if (abe_port[OMAP_ABE_VX_DL_PORT].status == OMAP_ABE_PORT_ACTIVITY_RUNNING) { + /* VX_DL port already started, hence no need to + initialize ASRC */ + } else { + /* Init VX_UL ASRC & VX_DL ASRC and enable its adaptation */ + abe_init_asrc_vx_ul(-250); + abe_init_asrc_vx_dl(250); + } + } + if (id == OMAP_ABE_VX_DL_PORT) { + if (abe_port[OMAP_ABE_VX_UL_PORT].status == OMAP_ABE_PORT_ACTIVITY_RUNNING) { + /* VX_UL port already started, hence no need to + initialize ASRC */ + } else { + /* Init VX_UL ASRC & VX_DL ASRC and enable its adaptation */ + abe_init_asrc_vx_ul(-250); + abe_init_asrc_vx_dl(250); + } + } + + /* local host variable status= "port is running" */ + abe_port[id].status = OMAP_ABE_PORT_ACTIVITY_RUNNING; + /* enable DMA requests */ + omap_abe_enable_dma_request(abe, id); + /* select the main port based on the activation of this new port */ + abe_decide_main_port(); + + return 0; +} +EXPORT_SYMBOL(omap_abe_enable_data_transfer); + +/** + * omap_abe_connect_cbpr_dmareq_port + * @id: port name + * @f: desired data format + * @d: desired dma_request line (0..7) + * @a: returned pointer to the base address of the CBPr register and number of + * samples to exchange during a DMA_request. + * + * enables the data echange between a DMA and the ABE through the + * CBPr registers of AESS. + */ +int omap_abe_connect_cbpr_dmareq_port(struct omap_abe *abe, + u32 id, abe_data_format_t *f, + u32 d, + abe_dma_t *returned_dma_t) +{ + _log(ABE_ID_CONNECT_CBPR_DMAREQ_PORT, id, f->f, f->samp_format); + + abe_port[id] = ((abe_port_t *) abe_port_init)[id]; + (abe_port[id]).format = (*f); + abe_port[id].protocol.protocol_switch = DMAREQ_PORT_PROT; + abe_port[id].protocol.p.prot_dmareq.iter = abe_dma_port_iteration(f); + abe_port[id].protocol.p.prot_dmareq.dma_addr = ABE_DMASTATUS_RAW; + abe_port[id].protocol.p.prot_dmareq.dma_data = (1 << d); + /* load the dma_t with physical information from AE memory mapping */ + abe_init_dma_t(id, &((abe_port[id]).protocol)); + + /* load the micro-task parameters */ + abe_init_io_tasks(id, &((abe_port[id]).format), + &((abe_port[id]).protocol)); + abe_port[id].status = OMAP_ABE_PORT_INITIALIZED; + + /* load the ATC descriptors - disabled */ + omap_abe_init_atc(abe, id); + /* return the dma pointer address */ + abe_read_port_address(id, returned_dma_t); + return 0; +} +EXPORT_SYMBOL(omap_abe_connect_cbpr_dmareq_port); + +/** + * omap_abe_connect_irq_ping_pong_port + * @id: port name + * @f: desired data format + * @I: index of the call-back subroutine to call + * @s: half-buffer (ping) size + * @p: returned base address of the first (ping) buffer) + * + * enables the data echanges between a direct access to the DMEM + * memory of ABE using cache flush. On each IRQ activation a subroutine + * registered with "abe_plug_subroutine" will be called. This subroutine + * will generate an amount of samples, send them to DMEM memory and call + * "abe_set_ping_pong_buffer" to notify the new amount of samples in the + * pong buffer. + */ +int omap_abe_connect_irq_ping_pong_port(struct omap_abe *abe, + u32 id, abe_data_format_t *f, + u32 subroutine_id, u32 size, + u32 *sink, u32 dsp_mcu_flag) +{ + _log(ABE_ID_CONNECT_IRQ_PING_PONG_PORT, id, f->f, f->samp_format); + + /* ping_pong is only supported on MM_DL */ + if (id != OMAP_ABE_MM_DL_PORT) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } + abe_port[id] = ((abe_port_t *) abe_port_init)[id]; + (abe_port[id]).format = (*f); + (abe_port[id]).protocol.protocol_switch = PINGPONG_PORT_PROT; + (abe_port[id]).protocol.p.prot_pingpong.buf_addr = + OMAP_ABE_D_PING_ADDR; + (abe_port[id]).protocol.p.prot_pingpong.buf_size = size; + (abe_port[id]).protocol.p.prot_pingpong.irq_data = (1); + abe_init_ping_pong_buffer(OMAP_ABE_MM_DL_PORT, size, 2, sink); + if (dsp_mcu_flag == PING_PONG_WITH_MCU_IRQ) + (abe_port[id]).protocol.p.prot_pingpong.irq_addr = + ABE_MCU_IRQSTATUS_RAW; + if (dsp_mcu_flag == PING_PONG_WITH_DSP_IRQ) + (abe_port[id]).protocol.p.prot_pingpong.irq_addr = + ABE_DSP_IRQSTATUS_RAW; + abe_port[id].status = OMAP_ABE_PORT_INITIALIZED; + /* load the micro-task parameters */ + abe_init_io_tasks(id, &((abe_port[id]).format), + &((abe_port[id]).protocol)); + /* load the ATC descriptors - disabled */ + omap_abe_init_atc(abe, id); + *sink = (abe_port[id]).protocol.p.prot_pingpong.buf_addr; + return 0; +} +EXPORT_SYMBOL(omap_abe_connect_irq_ping_pong_port); + +/** + * omap_abe_connect_serial_port() + * @id: port name + * @f: data format + * @i: peripheral ID (McBSP #1, #2, #3) + * + * Operations : enables the data echanges between a McBSP and an ATC buffer in + * DMEM. This API is used connect 48kHz McBSP streams to MM_DL and 8/16kHz + * voice streams to VX_UL, VX_DL, BT_VX_UL, BT_VX_DL. It abstracts the + * abe_write_port API. + */ +int omap_abe_connect_serial_port(struct omap_abe *abe, + u32 id, abe_data_format_t *f, + u32 mcbsp_id) +{ + _log(ABE_ID_CONNECT_SERIAL_PORT, id, f->samp_format, mcbsp_id); + + abe_port[id] = ((abe_port_t *) abe_port_init)[id]; + (abe_port[id]).format = (*f); + (abe_port[id]).protocol.protocol_switch = SERIAL_PORT_PROT; + /* McBSP peripheral connected to ATC */ + (abe_port[id]).protocol.p.prot_serial.desc_addr = mcbsp_id*ATC_SIZE; + /* check the iteration of ATC */ + (abe_port[id]).protocol.p.prot_serial.iter = + abe_dma_port_iter_factor(f); + + /* load the micro-task parameters */ + abe_init_io_tasks(id, &((abe_port[id]).format), + &((abe_port[id]).protocol)); + abe_port[id].status = OMAP_ABE_PORT_INITIALIZED; + + /* load the ATC descriptors - disabled */ + omap_abe_init_atc(abe, id); + + return 0; +} +EXPORT_SYMBOL(omap_abe_connect_serial_port); + +/** + * omap_abe_read_port_address + * @dma: output pointer to the DMA iteration and data destination pointer + * + * This API returns the address of the DMA register used on this audio port. + * Depending on the protocol being used, adds the base address offset L3 + * (DMA) or MPU (ARM) + */ +int omap_abe_read_port_address(struct omap_abe *abe, + u32 port, abe_dma_t *dma2) +{ + abe_dma_t_offset dma1; + u32 protocol_switch; + + _log(ABE_ID_READ_PORT_ADDRESS, port, 0, 0); + + dma1 = (abe_port[port]).dma; + protocol_switch = abe_port[port].protocol.protocol_switch; + switch (protocol_switch) { + case PINGPONG_PORT_PROT: + /* return the base address of the buffer in L3 and L4 spaces */ + (*dma2).data = (void *)(dma1.data + + ABE_DEFAULT_BASE_ADDRESS_L3 + ABE_DMEM_BASE_OFFSET_MPU); + (*dma2).l3_dmem = (void *)(dma1.data + + ABE_DEFAULT_BASE_ADDRESS_L3 + ABE_DMEM_BASE_OFFSET_MPU); + (*dma2).l4_dmem = (void *)(dma1.data + + ABE_DEFAULT_BASE_ADDRESS_L4 + ABE_DMEM_BASE_OFFSET_MPU); + break; + case DMAREQ_PORT_PROT: + /* return the CBPr(L3), DMEM(L3), DMEM(L4) address */ + (*dma2).data = (void *)(dma1.data + + ABE_DEFAULT_BASE_ADDRESS_L3 + ABE_ATC_BASE_OFFSET_MPU); + (*dma2).l3_dmem = + (void *)((abe_port[port]).protocol.p.prot_dmareq.buf_addr + + ABE_DEFAULT_BASE_ADDRESS_L3 + ABE_DMEM_BASE_OFFSET_MPU); + (*dma2).l4_dmem = + (void *)((abe_port[port]).protocol.p.prot_dmareq.buf_addr + + ABE_DEFAULT_BASE_ADDRESS_L4 + ABE_DMEM_BASE_OFFSET_MPU); + break; + default: + break; + } + (*dma2).iter = (dma1.iter); + + return 0; +} +EXPORT_SYMBOL(omap_abe_read_port_address); + +/** + * abe_init_dma_t + * @ id: ABE port ID + * @ prot: protocol being used + * + * load the dma_t with physical information from AE memory mapping + */ +void abe_init_dma_t(u32 id, abe_port_protocol_t *prot) +{ + abe_dma_t_offset dma; + u32 idx; + /* default dma_t points to address 0000... */ + dma.data = 0; + dma.iter = 0; + switch (prot->protocol_switch) { + case PINGPONG_PORT_PROT: + for (idx = 0; idx < 32; idx++) { + if (((prot->p).prot_pingpong.irq_data) == + (u32) (1 << idx)) + break; + } + (prot->p).prot_dmareq.desc_addr = + ((CBPr_DMA_RTX0 + idx)*ATC_SIZE); + /* translate byte address/size in DMEM words */ + dma.data = (prot->p).prot_pingpong.buf_addr >> 2; + dma.iter = (prot->p).prot_pingpong.buf_size >> 2; + break; + case DMAREQ_PORT_PROT: + for (idx = 0; idx < 32; idx++) { + if (((prot->p).prot_dmareq.dma_data) == + (u32) (1 << idx)) + break; + } + dma.data = (CIRCULAR_BUFFER_PERIPHERAL_R__0 + (idx << 2)); + dma.iter = (prot->p).prot_dmareq.iter; + (prot->p).prot_dmareq.desc_addr = + ((CBPr_DMA_RTX0 + idx)*ATC_SIZE); + break; + case SLIMBUS_PORT_PROT: + case SERIAL_PORT_PROT: + case DMIC_PORT_PROT: + case MCPDMDL_PORT_PROT: + case MCPDMUL_PORT_PROT: + default: + break; + } + /* upload the dma type */ + abe_port[id].dma = dma; +} + +/** + * abe_enable_atc + * Parameter: + * Operations: + * Return value: + */ +void abe_enable_atc(u32 id) +{ + struct omap_abe_atc_desc atc_desc; + + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_dmareq.desc_addr, + (u32 *) &atc_desc, sizeof(atc_desc)); + atc_desc.desen = 1; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_dmareq.desc_addr, + (u32 *) &atc_desc, sizeof(atc_desc)); + +} +/** + * abe_disable_atc + * Parameter: + * Operations: + * Return value: + */ +void abe_disable_atc(u32 id) +{ + struct omap_abe_atc_desc atc_desc; + + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_dmareq.desc_addr, + (u32 *) &atc_desc, sizeof(atc_desc)); + atc_desc.desen = 0; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + (abe_port[id]).protocol.p.prot_dmareq.desc_addr, + (u32 *) &atc_desc, sizeof(atc_desc)); + +} +/** + * abe_init_io_tasks + * @prot : protocol being used + * + * load the micro-task parameters doing to DMEM <==> SMEM data moves + * + * I/O descriptors input parameters : + * For Read from DMEM usually THR1/THR2 = X+1/X-1 + * For Write to DMEM usually THR1/THR2 = 2/0 + * UP_1/2 =X+1/X-1 + */ +void abe_init_io_tasks(u32 id, abe_data_format_t *format, + abe_port_protocol_t *prot) +{ + u32 x_io, direction, iter_samples, smem1, smem2, smem3, io_sub_id, + io_flag; + u32 copy_func_index, before_func_index, after_func_index; + u32 dmareq_addr, dmareq_field; + u32 sio_desc_address, datasize, iter, nsamp, datasize2, dOppMode32; + u32 atc_ptr_saved, atc_ptr_saved2, copy_func_index1; + u32 copy_func_index2, atc_desc_address1, atc_desc_address2; + struct ABE_SPingPongDescriptor desc_pp; + struct ABE_SIODescriptor sio_desc; + + if (prot->protocol_switch == PINGPONG_PORT_PROT) { + /* ping_pong is only supported on MM_DL */ + if (OMAP_ABE_MM_DL_PORT != id) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_PARAMETER_ERROR); + } + smem1 = smem_mm_dl; + copy_func_index = (u8) abe_dma_port_copy_subroutine_id(id); + dmareq_addr = abe_port[id].protocol.p.prot_pingpong.irq_addr; + dmareq_field = abe_port[id].protocol.p.prot_pingpong.irq_data; + datasize = abe_dma_port_iter_factor(format); + /* number of "samples" either mono or stereo */ + iter = abe_dma_port_iteration(format); + iter_samples = (iter / datasize); + /* load the IO descriptor */ + /* no drift */ + desc_pp.drift_ASRC = 0; + /* no drift */ + desc_pp.drift_io = 0; + desc_pp.hw_ctrl_addr = (u16) dmareq_addr; + desc_pp.copy_func_index = (u8) copy_func_index; + desc_pp.smem_addr = (u8) smem1; + /* DMA req 0 is used for CBPr0 */ + desc_pp.atc_irq_data = (u8) dmareq_field; + /* size of block transfer */ + desc_pp.x_io = (u8) iter_samples; + desc_pp.data_size = (u8) datasize; + /* address comunicated in Bytes */ + desc_pp.workbuff_BaseAddr = + (u16) (abe_base_address_pingpong[1]); + /* size comunicated in XIO sample */ + desc_pp.workbuff_Samples = 0; + desc_pp.nextbuff0_BaseAddr = + (u16) (abe_base_address_pingpong[0]); + desc_pp.nextbuff1_BaseAddr = + (u16) (abe_base_address_pingpong[1]); + if (dmareq_addr == ABE_DMASTATUS_RAW) { + desc_pp.nextbuff0_Samples = + (u16) ((abe_size_pingpong >> 2) / datasize); + desc_pp.nextbuff1_Samples = + (u16) ((abe_size_pingpong >> 2) / datasize); + } else { + desc_pp.nextbuff0_Samples = 0; + desc_pp.nextbuff1_Samples = 0; + } + /* next buffer to send is B1, first IRQ fills B0 */ + desc_pp.counter = 0; + /* send a DMA req to fill B0 with N samples + abe_block_copy (COPY_FROM_HOST_TO_ABE, + ABE_ATC, + ABE_DMASTATUS_RAW, + &(abe_port[id].protocol.p.prot_pingpong.irq_data), + 4); */ + sio_desc_address = OMAP_ABE_D_PINGPONGDESC_ADDR; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + sio_desc_address, (u32 *) &desc_pp, + sizeof(desc_pp)); + } else { + io_sub_id = dmareq_addr = ABE_DMASTATUS_RAW; + dmareq_field = 0; + atc_desc_address1 = atc_desc_address2 = 0; + /* default: repeat of the last downlink samples in case of + DMA errors, (disable=0x00) */ + io_flag = 0xFF; + datasize2 = datasize = abe_dma_port_iter_factor(format); + x_io = (u8) abe_dma_port_iteration(format); + nsamp = (x_io / datasize); + atc_ptr_saved2 = atc_ptr_saved = DMIC_ATC_PTR_labelID + id; + smem1 = abe_port[id].smem_buffer1; + smem3 = smem2 = abe_port[id].smem_buffer2; + copy_func_index1 = (u8) abe_dma_port_copy_subroutine_id(id); + before_func_index = after_func_index = + copy_func_index2 = NULL_COPY_CFPID; + switch (prot->protocol_switch) { + case DMIC_PORT_PROT: + /* DMIC port is read in two steps */ + x_io = x_io >> 1; + nsamp = nsamp >> 1; + atc_desc_address1 = (ABE_ATC_DMIC_DMA_REQ*ATC_SIZE); + io_sub_id = IO_IP_CFPID; + break; + case MCPDMDL_PORT_PROT: + /* PDMDL port is written to in two steps */ + x_io = x_io >> 1; + atc_desc_address1 = + (ABE_ATC_MCPDMDL_DMA_REQ*ATC_SIZE); + io_sub_id = IO_IP_CFPID; + break; + case MCPDMUL_PORT_PROT: + atc_desc_address1 = + (ABE_ATC_MCPDMUL_DMA_REQ*ATC_SIZE); + io_sub_id = IO_IP_CFPID; + break; + case SLIMBUS_PORT_PROT: + atc_desc_address1 = + abe_port[id].protocol.p.prot_slimbus.desc_addr1; + atc_desc_address2 = + abe_port[id].protocol.p.prot_slimbus.desc_addr2; + copy_func_index2 = NULL_COPY_CFPID; + /* @@@@@@ + #define SPLIT_SMEM_CFPID 9 + #define MERGE_SMEM_CFPID 10 + #define SPLIT_TDM_12_CFPID 11 + #define MERGE_TDM_12_CFPID 12 + */ + io_sub_id = IO_IP_CFPID; + break; + case SERIAL_PORT_PROT: /* McBSP/McASP */ + atc_desc_address1 = + (s16) abe_port[id].protocol.p.prot_serial. + desc_addr; + io_sub_id = IO_IP_CFPID; + break; + case DMAREQ_PORT_PROT: /* DMA w/wo CBPr */ + dmareq_addr = + abe_port[id].protocol.p.prot_dmareq.dma_addr; + dmareq_field = 0; + atc_desc_address1 = + abe_port[id].protocol.p.prot_dmareq.desc_addr; + io_sub_id = IO_IP_CFPID; + break; + } + /* special situation of the PING_PONG protocol which + has its own SIO descriptor format */ + /* + Sequence of operations on ping-pong buffers B0/B1 + -------------- time ---------------------------->>>> + Host Application is ready to send data from DDR to B0 + SDMA is initialized from "abe_connect_irq_ping_pong_port" to B0 + FIRMWARE starts with #12 B1 data, + sends IRQ/DMAreq, sends #pong B1 data, + sends IRQ/DMAreq, sends #ping B0, + sends B1 samples + ARM / SDMA | fills B0 | fills B1 ... | fills B0 ... + Counter 0 1 2 3 + */ + switch (id) { + case OMAP_ABE_PDM_DL_PORT: + abe->MultiFrame[7][0] = ABE_TASK_ID(C_ABE_FW_TASK_IO_PDM_DL); + abe->MultiFrame[19][0] = ABE_TASK_ID(C_ABE_FW_TASK_IO_PDM_DL); + break; + case OMAP_ABE_TONES_DL_PORT: + abe->MultiFrame[20][0] = ABE_TASK_ID(C_ABE_FW_TASK_IO_TONES_DL); + break; + case OMAP_ABE_PDM_UL_PORT: + abe->MultiFrame[5][2] = ABE_TASK_ID(C_ABE_FW_TASK_IO_PDM_UL); + break; + case OMAP_ABE_DMIC_PORT: + abe->MultiFrame[2][5] = ABE_TASK_ID(C_ABE_FW_TASK_IO_DMIC); + abe->MultiFrame[14][3] = ABE_TASK_ID(C_ABE_FW_TASK_IO_DMIC); + break; + case OMAP_ABE_MM_UL_PORT: + copy_func_index1 = COPY_MM_UL_CFPID; + before_func_index = ROUTE_MM_UL_CFPID; + abe->MultiFrame[19][6] = ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_UL); + break; + case OMAP_ABE_MM_UL2_PORT: + abe->MultiFrame[17][3] = ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_UL2); + break; + case OMAP_ABE_VX_DL_PORT: + /* check for 8kHz/16kHz */ + if (abe_port[id].format.f == 8000) { + abe->MultiFrame[TASK_VX_DL_SLT][TASK_VX_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_VX_DL_8_48_FIR); + /*Voice_8k_DL_labelID */ + smem1 = IO_VX_DL_ASRC_labelID; + + if ((abe_port[OMAP_ABE_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is VX_DL_PORT + * both VX_UL ASRC and VX_DL ASRC will add/remove sample + * referring to VX_DL flow_counter */ + abe->MultiFrame[TASK_ASRC_VX_DL_SLT][TASK_ASRC_VX_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_DL_8); + abe->MultiFrame[TASK_ASRC_VX_UL_SLT][TASK_ASRC_VX_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_UL_8_SIB); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } else { + abe->MultiFrame[TASK_VX_DL_SLT][TASK_VX_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_VX_DL_16_48); + /* Voice_16k_DL_labelID */ + smem1 = IO_VX_DL_ASRC_labelID; + + if ((abe_port[OMAP_ABE_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is VX_DL_PORT + * both VX_UL ASRC and VX_DL ASRC will add/remove sample + * referring to VX_DL flow_counter */ + abe->MultiFrame[TASK_ASRC_VX_DL_SLT][TASK_ASRC_VX_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_DL_16); + abe->MultiFrame[TASK_ASRC_VX_UL_SLT][TASK_ASRC_VX_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_UL_16_SIB); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } + abe->MultiFrame[0][2] = ABE_TASK_ID(C_ABE_FW_TASK_IO_VX_DL); + break; + case OMAP_ABE_VX_UL_PORT: + /* check for 8kHz/16kHz */ + if (abe_port[id].format.f == 8000) { + abe->MultiFrame[TASK_VX_UL_SLT][TASK_VX_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_VX_UL_48_8); + /* MultiFrame[TASK_ECHO_SLT][TASK_ECHO_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ECHO_REF_48_8); */ + smem1 = Voice_8k_UL_labelID; + + if ((abe_port[OMAP_ABE_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is VX_UL_PORT + * both VX_UL ASRC and VX_DL ASRC will add/remove sample + * referring to VX_UL flow_counter */ + abe->MultiFrame[TASK_ASRC_VX_DL_SLT][TASK_ASRC_VX_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_DL_8_SIB); + abe->MultiFrame[TASK_ASRC_VX_UL_SLT][TASK_ASRC_VX_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_UL_8); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } else { + abe->MultiFrame[TASK_VX_UL_SLT][TASK_VX_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_VX_UL_48_16); + /* MultiFrame[TASK_ECHO_SLT][TASK_ECHO_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ECHO_REF_48_16); */ + smem1 = Voice_16k_UL_labelID; + + if ((abe_port[OMAP_ABE_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is VX_UL_PORT + * both VX_UL ASRC and VX_DL ASRC will add/remove sample + * referring to VX_UL flow_counter */ + abe->MultiFrame[TASK_ASRC_VX_DL_SLT][TASK_ASRC_VX_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_DL_16_SIB); + abe->MultiFrame[TASK_ASRC_VX_UL_SLT][TASK_ASRC_VX_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_VX_UL_16); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } + abe->MultiFrame[16][3] = ABE_TASK_ID(C_ABE_FW_TASK_IO_VX_UL); + break; + case OMAP_ABE_BT_VX_DL_PORT: + /* check for 8kHz/16kHz */ + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MAXTASKBYTESINSLOT_ADDR, &dOppMode32, + sizeof(u32)); + + if (abe_port[id].format.f == 8000) { + if (dOppMode32 == DOPPMODE32_OPP100) { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_8_OPP100); + smem1 = BT_DL_8k_opp100_labelID; + } else { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_8); + smem1 = BT_DL_8k_labelID; + } + if ((abe_port[OMAP_ABE_BT_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_BT_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is BT_VX_DL_PORT + * both BT_VX_DL ASRC and BT_VX_UL ASRC will add/remove sample + * referring to BT_VX_DL flow_counter */ + abe->MultiFrame[TASK_ASRC_BT_DL_SLT][TASK_ASRC_BT_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_DL_8); + abe->MultiFrame[TASK_ASRC_BT_UL_SLT][TASK_ASRC_BT_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_UL_8_SIB); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } else { + if (dOppMode32 == DOPPMODE32_OPP100) { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_16_OPP100); + smem1 = BT_DL_16k_opp100_labelID; + } else { + abe->MultiFrame[TASK_BT_DL_48_8_SLT][TASK_BT_DL_48_8_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_DL_48_16); + smem1 = BT_DL_16k_labelID; + } + if ((abe_port[OMAP_ABE_BT_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_BT_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is BT_VX_DL_PORT + * both BT_VX_DL ASRC and BT_VX_UL ASRC will add/remove sample + * referring to BT_VX_DL flow_counter */ + abe->MultiFrame[TASK_ASRC_BT_DL_SLT][TASK_ASRC_BT_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_DL_16); + abe->MultiFrame[TASK_ASRC_BT_UL_SLT][TASK_ASRC_BT_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_UL_16_SIB); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } + abe->MultiFrame[13][5] = ABE_TASK_ID(C_ABE_FW_TASK_IO_BT_VX_DL); + break; + case OMAP_ABE_BT_VX_UL_PORT: + /* check for 8kHz/16kHz */ + /* set the SMEM buffer -- programming sequence */ + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MAXTASKBYTESINSLOT_ADDR, &dOppMode32, + sizeof(u32)); + + if (abe_port[id].format.f == 8000) { + abe->MultiFrame[TASK_BT_UL_8_48_SLT][TASK_BT_UL_8_48_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_UL_8_48); + if (dOppMode32 == DOPPMODE32_OPP100) + /* ASRC input buffer, size 40 */ + smem1 = smem_bt_vx_ul_opp100; + else + /* at OPP 50 without ASRC */ + smem1 = BT_UL_8k_labelID; + if ((abe_port[OMAP_ABE_BT_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_BT_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is BT_VX_UL_PORT */ + /* both BT_VX_UL ASRC and BT_VX_DL ASRC will add/remove sample + referring to BT_VX_UL flow_counter */ + abe->MultiFrame[TASK_ASRC_BT_UL_SLT][TASK_ASRC_BT_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_UL_8); + abe->MultiFrame[TASK_ASRC_BT_DL_SLT][TASK_ASRC_BT_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_DL_8_SIB); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } else { + abe->MultiFrame[TASK_BT_UL_8_48_SLT][TASK_BT_UL_8_48_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_BT_UL_16_48); + if (dOppMode32 == DOPPMODE32_OPP100) + /* ASRC input buffer, size 40 */ + smem1 = smem_bt_vx_ul_opp100; + else + /* at OPP 50 without ASRC */ + smem1 = BT_UL_16k_labelID; + if ((abe_port[OMAP_ABE_BT_VX_UL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE) && + (abe_port[OMAP_ABE_BT_VX_DL_PORT].status == + OMAP_ABE_PORT_ACTIVITY_IDLE)) { + /* the 1st opened port is BT_VX_UL_PORT */ + /* both BT_VX_UL ASRC and BT_VX_DL ASRC will add/remove sample + referring to BT_VX_UL flow_counter */ + abe->MultiFrame[TASK_ASRC_BT_UL_SLT][TASK_ASRC_BT_UL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_UL_16); + abe->MultiFrame[TASK_ASRC_BT_DL_SLT][TASK_ASRC_BT_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_ASRC_BT_DL_16_SIB); + } else { + /* Do nothing, Scheduling Table has already been patched */ + } + } + abe->MultiFrame[15][3] = ABE_TASK_ID(C_ABE_FW_TASK_IO_BT_VX_UL); + break; + case OMAP_ABE_MM_DL_PORT: + /* check for CBPr / serial_port / Ping-pong access */ + abe->MultiFrame[TASK_IO_MM_DL_SLT][TASK_IO_MM_DL_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_DL); + smem1 = smem_mm_dl; + break; + case OMAP_ABE_MM_EXT_IN_PORT: + /* set the SMEM buffer -- programming sequence */ + omap_abe_mem_read(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MAXTASKBYTESINSLOT_ADDR, &dOppMode32, + sizeof(u32)); + if (dOppMode32 == DOPPMODE32_OPP100) + /* ASRC input buffer, size 40 */ + smem1 = smem_mm_ext_in_opp100; + else + /* at OPP 50 without ASRC */ + smem1 = smem_mm_ext_in_opp50; + + abe->MultiFrame[21][3] = ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_EXT_IN); + break; + case OMAP_ABE_MM_EXT_OUT_PORT: + abe->MultiFrame[15][0] = ABE_TASK_ID(C_ABE_FW_TASK_IO_MM_EXT_OUT); + break; + default: + break; + } + + if (abe_port[id].protocol.direction == ABE_ATC_DIRECTION_IN) + direction = 0; + else + /* offset of the write pointer in the ATC descriptor */ + direction = 3; + + sio_desc.drift_ASRC = 0; + sio_desc.drift_io = 0; + sio_desc.io_type_idx = (u8) io_sub_id; + sio_desc.samp_size = (u8) datasize; + sio_desc.hw_ctrl_addr = (u16) (dmareq_addr << 2); + sio_desc.atc_irq_data = (u8) dmareq_field; + sio_desc.flow_counter = (u16) 0; + sio_desc.direction_rw = (u8) direction; + sio_desc.repeat_last_samp = (u8) io_flag; + sio_desc.nsamp = (u8) nsamp; + sio_desc.x_io = (u8) x_io; + /* set ATC ON */ + sio_desc.on_off = 0x80; + sio_desc.split_addr1 = (u16) smem1; + sio_desc.split_addr2 = (u16) smem2; + sio_desc.split_addr3 = (u16) smem3; + sio_desc.before_f_index = (u8) before_func_index; + sio_desc.after_f_index = (u8) after_func_index; + sio_desc.smem_addr1 = (u16) smem1; + sio_desc.atc_address1 = (u16) atc_desc_address1; + sio_desc.atc_pointer_saved1 = (u16) atc_ptr_saved; + sio_desc.data_size1 = (u8) datasize; + sio_desc.copy_f_index1 = (u8) copy_func_index1; + sio_desc.smem_addr2 = (u16) smem2; + sio_desc.atc_address2 = (u16) atc_desc_address2; + sio_desc.atc_pointer_saved2 = (u16) atc_ptr_saved2; + sio_desc.data_size2 = (u8) datasize2; + sio_desc.copy_f_index2 = (u8) copy_func_index2; + sio_desc_address = OMAP_ABE_D_IODESCR_ADDR + (id * + sizeof(struct ABE_SIODescriptor)); + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + sio_desc_address, (u32 *) &sio_desc, + sizeof(sio_desc)); + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, + OMAP_ABE_D_MULTIFRAME_ADDR, (u32 *) abe->MultiFrame, + sizeof(abe->MultiFrame)); + } + +} + +/** + * omap_abe_select_main_port - Select stynchronization port for Event generator. + * @id: audio port name + * + * tells the FW which is the reference stream for adjusting + * the processing on 23/24/25 slots + */ +int omap_abe_select_main_port(u32 id) +{ + u32 selection; + + _log(ABE_ID_SELECT_MAIN_PORT, id, 0, 0); + + /* flow control */ + selection = OMAP_ABE_D_IODESCR_ADDR + id * sizeof(struct ABE_SIODescriptor) + + flow_counter_; + /* when the main port is a sink port from AESS point of view + the sign the firmware task analysis must be changed */ + selection &= 0xFFFFL; + if (abe_port[id].protocol.direction == ABE_ATC_DIRECTION_IN) + selection |= 0x80000; + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_SLOT23_CTRL_ADDR, + &selection, 4); + return 0; +} +/** + * abe_decide_main_port - Select stynchronization port for Event generator. + * @id: audio port name + * + * tells the FW which is the reference stream for adjusting + * the processing on 23/24/25 slots + * + * takes the first port in a list which is slave on the data interface + */ +u32 abe_valid_port_for_synchro(u32 id) +{ + if ((abe_port[id].protocol.protocol_switch == + DMAREQ_PORT_PROT) || + (abe_port[id].protocol.protocol_switch == + PINGPONG_PORT_PROT) || + (abe_port[id].status != OMAP_ABE_PORT_ACTIVITY_RUNNING)) + return 0; + else + return 1; +} +void abe_decide_main_port(void) +{ + u32 id, id_not_found; + id_not_found = 1; + for (id = 0; id < LAST_PORT_ID - 1; id++) { + if (abe_valid_port_for_synchro(abe_port_priority[id])) { + id_not_found = 0; + break; + } + } + /* if no port is currently activated, the default one is PDM_DL */ + if (id_not_found) + omap_abe_select_main_port(OMAP_ABE_PDM_DL_PORT); + else + omap_abe_select_main_port(abe_port_priority[id]); +} +/** + * abe_format_switch + * @f: port format + * @iter: port iteration + * @mulfac: multiplication factor + * + * translates the sampling and data length to ITER number for the DMA + * and the multiplier factor to apply during data move with DMEM + * + */ +void abe_format_switch(abe_data_format_t *f, u32 *iter, u32 *mulfac) +{ + u32 n_freq; +#if FW_SCHED_LOOP_FREQ == 4000 + switch (f->f) { + /* nb of samples processed by scheduling loop */ + case 8000: + n_freq = 2; + break; + case 16000: + n_freq = 4; + break; + case 24000: + n_freq = 6; + break; + case 44100: + n_freq = 12; + break; + case 96000: + n_freq = 24; + break; + default/*case 48000 */ : + n_freq = 12; + break; + } +#else + /* erroneous cases */ + n_freq = 0; +#endif + switch (f->samp_format) { + case MONO_MSB: + case MONO_RSHIFTED_16: + case STEREO_16_16: + *mulfac = 1; + break; + case STEREO_MSB: + case STEREO_RSHIFTED_16: + *mulfac = 2; + break; + case THREE_MSB: + *mulfac = 3; + break; + case FOUR_MSB: + *mulfac = 4; + break; + case FIVE_MSB: + *mulfac = 5; + break; + case SIX_MSB: + *mulfac = 6; + break; + case SEVEN_MSB: + *mulfac = 7; + break; + case EIGHT_MSB: + *mulfac = 8; + break; + case NINE_MSB: + *mulfac = 9; + break; + default: + *mulfac = 1; + break; + } + *iter = (n_freq * (*mulfac)); +} +/** + * abe_dma_port_iteration + * @f: port format + * + * translates the sampling and data length to ITER number for the DMA + */ +u32 abe_dma_port_iteration(abe_data_format_t *f) +{ + u32 iter, mulfac; + abe_format_switch(f, &iter, &mulfac); + return iter; +} +/** + * abe_dma_port_iter_factor + * @f: port format + * + * returns the multiplier factor to apply during data move with DMEM + */ +u32 abe_dma_port_iter_factor(abe_data_format_t *f) +{ + u32 iter, mulfac; + abe_format_switch(f, &iter, &mulfac); + return mulfac; +} +/** + * omap_abe_dma_port_iter_factor + * @f: port format + * + * returns the multiplier factor to apply during data move with DMEM + */ +u32 omap_abe_dma_port_iter_factor(struct omap_abe_data_format *f) +{ + u32 iter, mulfac; + abe_format_switch((abe_data_format_t *)f, &iter, &mulfac); + return mulfac; +} +/** + * abe_dma_port_copy_subroutine_id + * + * @port_id: ABE port ID + * + * returns the index of the function doing the copy in I/O tasks + */ +u32 abe_dma_port_copy_subroutine_id(u32 port_id) +{ + u32 sub_id; + if (abe_port[port_id].protocol.direction == ABE_ATC_DIRECTION_IN) { + switch (abe_port[port_id].format.samp_format) { + case MONO_MSB: + sub_id = D2S_MONO_MSB_CFPID; + break; + case MONO_RSHIFTED_16: + sub_id = D2S_MONO_RSHIFTED_16_CFPID; + break; + case STEREO_RSHIFTED_16: + sub_id = D2S_STEREO_RSHIFTED_16_CFPID; + break; + case STEREO_16_16: + sub_id = D2S_STEREO_16_16_CFPID; + break; + case STEREO_MSB: + sub_id = D2S_STEREO_MSB_CFPID; + break; + case SIX_MSB: + if (port_id == OMAP_ABE_DMIC_PORT) { + sub_id = COPY_DMIC_CFPID; + break; + } + default: + sub_id = NULL_COPY_CFPID; + break; + } + } else { + switch (abe_port[port_id].format.samp_format) { + case MONO_MSB: + sub_id = S2D_MONO_MSB_CFPID; + break; + case MONO_RSHIFTED_16: + sub_id = S2D_MONO_RSHIFTED_16_CFPID; + break; + case STEREO_RSHIFTED_16: + sub_id = S2D_STEREO_RSHIFTED_16_CFPID; + break; + case STEREO_16_16: + sub_id = S2D_STEREO_16_16_CFPID; + break; + case STEREO_MSB: + sub_id = S2D_STEREO_MSB_CFPID; + break; + case SIX_MSB: + if (port_id == OMAP_ABE_PDM_DL_PORT) { + sub_id = COPY_MCPDM_DL_CFPID; + break; + } + if (port_id == OMAP_ABE_MM_UL_PORT) { + sub_id = COPY_MM_UL_CFPID; + break; + } + case THREE_MSB: + case FOUR_MSB: + case FIVE_MSB: + case SEVEN_MSB: + case EIGHT_MSB: + case NINE_MSB: + sub_id = COPY_MM_UL_CFPID; + break; + default: + sub_id = NULL_COPY_CFPID; + break; + } + } + return sub_id; +} + +/** + * abe_read_remaining_data + * @id: ABE port_ID + * @n: size pointer to the remaining number of 32bits words + * + * computes the remaining amount of data in the buffer. + */ +abehal_status abe_read_remaining_data(u32 port, u32 *n) +{ + u32 sio_pp_desc_address; + struct ABE_SPingPongDescriptor desc_pp; + + _log(ABE_ID_READ_REMAINING_DATA, port, 0, 0); + + /* + * read the port SIO descriptor and extract the + * current pointer address after reading the counter + */ + sio_pp_desc_address = OMAP_ABE_D_PINGPONGDESC_ADDR; + omap_abe_mem_read(abe, OMAP_ABE_DMEM, sio_pp_desc_address, + (u32 *) &desc_pp, sizeof(struct ABE_SPingPongDescriptor)); + *n = desc_pp.workbuff_Samples; + + return 0; +} +EXPORT_SYMBOL(abe_read_remaining_data); + +/** + * abe_mono_mixer + * @id: name of the mixer (MIXDL1 or MIXDL2) + * on_off: enable\disable flag + * + * This API Programs DL1Mixer or DL2Mixer to output mono data + * on both left and right data paths. + */ +abehal_status abe_mono_mixer(u32 id, u32 on_off) +{ + + switch (id) { + case MIXDL1: + if (on_off) + abe->MultiFrame[TASK_DL1Mixer_SLT][TASK_DL1Mixer_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_DL1Mixer_dual_mono); + else + abe->MultiFrame[TASK_DL1Mixer_SLT][TASK_DL1Mixer_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_DL1Mixer); + break; + case MIXDL2: + if (on_off) + abe->MultiFrame[TASK_DL2Mixer_SLT][TASK_DL2Mixer_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_DL2Mixer_dual_mono); + else + abe->MultiFrame[TASK_DL2Mixer_SLT][TASK_DL2Mixer_IDX] = + ABE_TASK_ID(C_ABE_FW_TASK_DL2Mixer); + break; + case MIXAUDUL: + if (on_off) + abe->MultiFrame[12][4] = + ABE_TASK_ID(C_ABE_FW_TASK_ULMixer_dual_mono); + else + abe->MultiFrame[12][4] = + ABE_TASK_ID(C_ABE_FW_TASK_ULMixer); + break; + default: + break; + } + + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_MULTIFRAME_ADDR, + (u32 *) abe->MultiFrame, sizeof(abe->MultiFrame)); + + return 0; +} +EXPORT_SYMBOL(abe_mono_mixer); +/** + * abe_write_pdmdl_offset - write the desired offset on the DL1/DL2 paths + * + * Parameters: + * path: 1 for the DL1 ABE path, 2 for the DL2 ABE path + * offset_left: integer value that will be added on all PDM left samples + * offset_right: integer value that will be added on all PDM right samples + * + */ +void abe_write_pdmdl_offset(u32 path, u32 offset_left, u32 offset_right) +{ + switch (path) { + case 1: + omap_abe_mem_write(abe, OMAP_ABE_SMEM, OMAP_ABE_S_DC_HS_ADDR + 4, + &offset_left, sizeof(u32)); + omap_abe_mem_write(abe, OMAP_ABE_SMEM, OMAP_ABE_S_DC_HS_ADDR, + &offset_right, sizeof(u32)); + break; + case 2: + omap_abe_mem_write(abe, OMAP_ABE_SMEM, OMAP_ABE_S_DC_HF_ADDR + 4, + &offset_left, sizeof(u32)); + omap_abe_mem_write(abe, OMAP_ABE_SMEM, OMAP_ABE_S_DC_HF_ADDR, + &offset_right, sizeof(u32)); + break; + default: + break; + } +} +EXPORT_SYMBOL(abe_write_pdmdl_offset); + diff --git a/sound/soc/omap/abe/abe_port.h b/sound/soc/omap/abe/abe_port.h new file mode 100644 index 0000000000000000000000000000000000000000..290f8b52c00b21871d9a152427c95c26eac3a34c --- /dev/null +++ b/sound/soc/omap/abe/abe_port.h @@ -0,0 +1,161 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_PORT_H_ +#define _ABE_PORT_H_ + +struct omap_abe_data_format { + /* Sampling frequency of the stream */ + u32 f; + /* Sample format type */ + u32 samp_format; +}; + +struct omap_abe_port_protocol { + /* Direction=0 means input from AESS point of view */ + u32 direction; + /* Protocol type (switch) during the data transfers */ + u32 protocol_switch; + union { + /* McBSP/McASP peripheral connected to ATC */ + struct { + u32 desc_addr; + /* Address of ATC McBSP/McASP descriptor's in bytes */ + u32 buf_addr; + /* DMEM address in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + } serial; + /* DMIC peripheral connected to ATC */ + struct { + /* DMEM address in bytes */ + u32 buf_addr; + /* DMEM buffer size in bytes */ + u32 buf_size; + /* Number of activated DMIC */ + u32 nbchan; + } dmic; + /* McPDMDL peripheral connected to ATC */ + struct { + /* DMEM address in bytes */ + u32 buf_addr; + /* DMEM size in bytes */ + u32 buf_size; + /* Control allowed on McPDM DL */ + u32 control; + } mcpdmdl; + /* McPDMUL peripheral connected to ATC */ + struct { + /* DMEM address size in bytes */ + u32 buf_addr; + /* DMEM buffer size size in bytes */ + u32 buf_size; + } mcpdmul; + /* Ping-Pong interface to the Host using cache-flush */ + struct { + /* Address of ATC descriptor's */ + u32 desc_addr; + /* DMEM buffer base address in bytes */ + u32 buf_addr; + /* DMEM size in bytes for each ping and pong buffers */ + u32 buf_size; + /* IRQ address (either DMA (0) MCU (1) or DSP(2)) */ + u32 irq_addr; + /* IRQ data content loaded in the AESS IRQ register */ + u32 irq_data; + /* Call-back function upon IRQ reception */ + u32 callback; + } pingpong; + /* DMAreq line to CBPr */ + struct { + /* Address of ATC descriptor's */ + u32 desc_addr; + /* DMEM buffer address in bytes */ + u32 buf_addr; + /* DMEM buffer size size in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + /* DMAreq address */ + u32 dma_addr; + /* DMA/AESS = 1 << #DMA */ + u32 dma_data; + } dmareq; + /* Circular buffer - direct addressing to DMEM */ + struct { + /* DMEM buffer base address in bytes */ + u32 buf_addr; + /* DMEM buffer size in bytes */ + u32 buf_size; + /* DMAreq address */ + u32 dma_addr; + /* DMA/AESS = 1 << #DMA */ + u32 dma_data; + } circular_buffer; + } port; +}; + +extern const abe_port_t abe_port_init[]; +extern abe_port_t abe_port[]; +extern const u32 abe_port_priority[]; + +int omap_abe_select_main_port(u32 id); +u32 omap_abe_dma_port_iter_factor(struct omap_abe_data_format *f); + +#endif/* _ABE_PORT_H_ */ diff --git a/sound/soc/omap/abe/abe_ref.h b/sound/soc/omap/abe/abe_ref.h new file mode 100644 index 0000000000000000000000000000000000000000..4c8a9bb93169ad6d4662d54d049935ec07415653 --- /dev/null +++ b/sound/soc/omap/abe/abe_ref.h @@ -0,0 +1,152 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_REF_H_ +#define _ABE_REF_H_ + +#include "abe_api.h" + +/* + * 'ABE_PRO.H' all non-API prototypes for INI, IRQ, SEQ ... + */ +/* + * HAL EXTERNAL AP + */ +/* + * HAL INTERNAL AP + */ +void abe_decide_main_port(void); +void abe_reset_all_ports(void); +void abe_reset_all_fifo(void); +void abe_reset_all_sequence(void); +u32 abe_dma_port_iteration(abe_data_format_t *format); +void abe_read_sys_clock(u32 *time); +void abe_enable_atc(u32 id); +void abe_disable_atc(u32 id); +void abe_init_io_tasks(u32 id, abe_data_format_t *format, + abe_port_protocol_t *prot); +void abe_init_dma_t(u32 id, abe_port_protocol_t *prot); +u32 abe_dma_port_iter_factor(abe_data_format_t *f); +u32 abe_dma_port_copy_subroutine_id(u32 i); +void abe_call_subroutine(u32 idx, u32 p1, u32 p2, u32 p3, u32 p4); +void abe_monitoring(void); +void abe_add_subroutine(u32 *id, abe_subroutine2 f, u32 nparam, u32 *params); +abehal_status abe_read_next_ping_pong_buffer(u32 port, u32 *p, u32 *n); +void abe_irq_ping_pong(void); +void abe_irq_check_for_sequences(u32 seq_info); +void abe_default_irq_pingpong_player(void); +void abe_default_irq_pingpong_player_32bits(void); +void abe_rshifted16_irq_pingpong_player_32bits(void); +void abe_1616_irq_pingpong_player_1616bits(void); +void abe_default_irq_aps_adaptation(void); +void abe_irq_aps(u32 aps_info); +void abe_dbg_error_log(u32 x); +void abe_init_asrc_vx_dl(s32 dppm); +void abe_init_asrc_vx_ul(s32 dppm); +void abe_init_asrc_mm_ext_in(s32 dppm); +void abe_init_asrc_bt_ul(s32 dppm); +void abe_init_asrc_bt_dl(s32 dppm); + +void omap_abe_hw_configuration(struct omap_abe *abe); +void omap_abe_gain_offset(struct omap_abe *abe, u32 id, u32 *mixer_offset); +int omap_abe_use_compensated_gain(struct omap_abe *abe, int on_off); + +/* + * HAL INTERNAL DATA + */ +extern const u32 abe_port_priority[LAST_PORT_ID - 1]; +extern const u32 abe_firmware_array[ABE_FIRMWARE_MAX_SIZE]; +extern const u32 abe_atc_srcid[]; +extern const u32 abe_atc_dstid[]; +extern const abe_port_t abe_port_init[]; +extern const abe_seq_t all_sequence_init[]; +extern const abe_router_t abe_router_ul_table_preset + [NBROUTE_CONFIG][NBROUTE_UL]; +extern const abe_sequence_t seq_null; + +extern abe_port_t abe_port[]; +extern abe_seq_t all_sequence[]; +extern abe_router_t abe_router_ul_table[NBROUTE_CONFIG_MAX][NBROUTE_UL]; +/* table of new subroutines called in the sequence */ +extern abe_subroutine2 abe_all_subsubroutine[MAXNBSUBROUTINE]; +/* number of parameters per calls */ +extern u32 abe_all_subsubroutine_nparam[MAXNBSUBROUTINE]; +extern u32 abe_subroutine_id[MAXNBSUBROUTINE]; +extern u32 *abe_all_subroutine_params[MAXNBSUBROUTINE]; +extern u32 abe_subroutine_write_pointer; +extern abe_sequence_t abe_all_sequence[MAXNBSEQUENCE]; +extern u32 abe_sequence_write_pointer; +/* current number of pending sequences (avoids to look in the table) */ +extern u32 abe_nb_pending_sequences; +/* pending sequences due to ressource collision */ +extern u32 abe_pending_sequences[MAXNBSEQUENCE]; +/* mask of unsharable ressources among other sequences */ +extern u32 abe_global_sequence_mask; +/* table of active sequences */ +extern abe_seq_t abe_active_sequence[MAXACTIVESEQUENCE][MAXSEQUENCESTEPS]; +/* index of the plugged subroutine doing ping-pong cache-flush + DMEM accesses */ +extern u32 abe_irq_aps_adaptation_id; +/* base addresses of the ping pong buffers */ +extern u32 abe_base_address_pingpong[MAX_PINGPONG_BUFFERS]; +/* size of each ping/pong buffers */ +extern u32 abe_size_pingpong; +/* number of ping/pong buffer being used */ +extern u32 abe_nb_pingpong; + +#endif/* _ABE_REF_H_ */ diff --git a/sound/soc/omap/abe/abe_seq.c b/sound/soc/omap/abe/abe_seq.c new file mode 100644 index 0000000000000000000000000000000000000000..6ae2aa50b8117b35596bd1ddc25fa0776a113963 --- /dev/null +++ b/sound/soc/omap/abe/abe_seq.c @@ -0,0 +1,308 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include "abe_legacy.h" + +#include "abe_mem.h" + +extern struct omap_abe *abe; +extern u32 abe_irq_pingpong_player_id; + +/** + * abe_null_subroutine + * + */ +void abe_null_subroutine_0(void) +{ +} +void abe_null_subroutine_2(u32 a, u32 b) +{ +} +void abe_null_subroutine_4(u32 a, u32 b, u32 c, u32 d) +{ +} +/** + * abe_init_subroutine_table - initializes the default table of pointers + * to subroutines + * + * initializes the default table of pointers to subroutines + * + */ +void abe_init_subroutine_table(void) +{ + u32 id; + /* reset the table's pointers */ + abe_subroutine_write_pointer = 0; + /* the first index is the NULL task */ + abe_add_subroutine(&id, (abe_subroutine2) abe_null_subroutine_2, + SUB_0_PARAM, (u32 *) 0); + /* write mixer has 4 parameters */ + abe_add_subroutine(&(abe_subroutine_id[SUB_WRITE_MIXER]), + (abe_subroutine2) abe_write_mixer, SUB_4_PARAM, + (u32 *) 0); + /* ping-pong player IRQ */ + abe_add_subroutine(&abe_irq_pingpong_player_id, + (abe_subroutine2) abe_null_subroutine_0, SUB_0_PARAM, + (u32 *) 0); +} +/** + * abe_add_subroutine + * @id: ABE port id + * @f: pointer to the subroutines + * @nparam: number of parameters + * @params: pointer to the psrameters + * + * add one function pointer more and returns the index to it + */ +void abe_add_subroutine(u32 *id, abe_subroutine2 f, u32 nparam, u32 *params) +{ + u32 i, i_found; + if ((abe_subroutine_write_pointer >= MAXNBSUBROUTINE) || + ((u32) f == 0)) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_SEQ, + ABE_PARAMETER_OVERFLOW); + } else { + /* search if this subroutine address was not already + * declared, then return the previous index + */ + for (i_found = abe_subroutine_write_pointer, i = 0; + i < abe_subroutine_write_pointer; i++) { + if (f == abe_all_subsubroutine[i]) + i_found = i; + } + if (i_found == abe_subroutine_write_pointer) { + *id = abe_subroutine_write_pointer; + abe_all_subsubroutine + [abe_subroutine_write_pointer] = (f); + abe_all_subroutine_params + [abe_subroutine_write_pointer] = params; + abe_all_subsubroutine_nparam + [abe_subroutine_write_pointer] = nparam; + abe_subroutine_write_pointer++; + } else { + abe_all_subroutine_params[i_found] = params; + *id = i_found; + } + } +} +/** + * abe_add_sequence + * @id: returned sequence index after pluging a new sequence + * (index in the tables) + * @s: sequence to be inserted + * + * Load a time-sequenced operations. + */ +void abe_add_sequence(u32 *id, abe_sequence_t *s) +{ + abe_seq_t *seq_src, *seq_dst; + u32 i, no_end_of_sequence_found; + seq_src = &(s->seq1); + seq_dst = &((abe_all_sequence[abe_sequence_write_pointer]).seq1); + if ((abe_sequence_write_pointer >= MAXNBSEQUENCE) || ((u32) s == 0)) { + omap_abe_dbg_error(abe, OMAP_ABE_ERR_SEQ, + ABE_PARAMETER_OVERFLOW); + } else { + *id = abe_subroutine_write_pointer; + /* copy the mask */ + (abe_all_sequence[abe_sequence_write_pointer]).mask = s->mask; + for (no_end_of_sequence_found = 1, i = 0; i < MAXSEQUENCESTEPS; + i++, seq_src++, seq_dst++) { + /* sequence copied line by line */ + (*seq_dst) = (*seq_src); + /* stop when the line start with time=(-1) */ + if ((*(s32 *) seq_src) == (-1)) { + /* stop when the line start with time=(-1) */ + no_end_of_sequence_found = 0; + break; + } + } + abe_subroutine_write_pointer++; + if (no_end_of_sequence_found) + omap_abe_dbg_error(abe, OMAP_ABE_ERR_API, + ABE_SEQTOOLONG); + } +} +/** + * abe_reset_one_sequence + * @id: sequence ID + * + * load default configuration for that sequence + * kill running activities + */ +void abe_reset_one_sequence(u32 id) +{ +} +/** + * abe_reset_all_sequence + * + * load default configuration for all sequences + * kill any running activities + */ +void omap_abe_reset_all_sequence(struct omap_abe *abe) +{ + u32 i; + abe_init_subroutine_table(); + /* arrange to have the first sequence index=0 to the NULL operation + sequence */ + abe_add_sequence(&i, (abe_sequence_t *) &seq_null); + /* reset the the collision protection mask */ + abe_global_sequence_mask = 0; + /* reset the pending sequences list */ + for (abe_nb_pending_sequences = i = 0; i < MAXNBSEQUENCE; i++) + abe_pending_sequences[i] = 0; +} +/** + * abe_call_subroutine + * @idx: index to the table of all registered Call-backs and subroutines + * + * run and log a subroutine + */ +void abe_call_subroutine(u32 idx, u32 p1, u32 p2, u32 p3, u32 p4) +{ + abe_subroutine0 f0; + abe_subroutine1 f1; + abe_subroutine2 f2; + abe_subroutine3 f3; + abe_subroutine4 f4; + u32 *params; + if (idx > MAXNBSUBROUTINE) + return; + switch (idx) { + /* call the subroutines defined at compilation time + (const .. sequences) */ +#if 0 + case SUB_WRITE_MIXER_DL1: + abe_write_mixer_dl1(p1, p2, p3) + abe_fprintf("write_mixer"); + break; +#endif + /* call the subroutines defined at execution time + (dynamic sequences) */ + default: + switch (abe_all_subsubroutine_nparam[idx]) { + case SUB_0_PARAM: + f0 = (abe_subroutine0) abe_all_subsubroutine[idx]; + (*f0) (); + break; + case SUB_1_PARAM: + f1 = (abe_subroutine1) abe_all_subsubroutine[idx]; + params = abe_all_subroutine_params + [abe_irq_pingpong_player_id]; + if (params != (u32 *) 0) + p1 = params[0]; + (*f1) (p1); + break; + case SUB_2_PARAM: + f2 = abe_all_subsubroutine[idx]; + params = abe_all_subroutine_params + [abe_irq_pingpong_player_id]; + if (params != (u32 *) 0) { + p1 = params[0]; + p2 = params[1]; + } + (*f2) (p1, p2); + break; + case SUB_3_PARAM: + f3 = (abe_subroutine3) abe_all_subsubroutine[idx]; + params = abe_all_subroutine_params + [abe_irq_pingpong_player_id]; + if (params != (u32 *) 0) { + p1 = params[0]; + p2 = params[1]; + p3 = params[2]; + } + (*f3) (p1, p2, p3); + break; + case SUB_4_PARAM: + f4 = (abe_subroutine4) abe_all_subsubroutine[idx]; + params = abe_all_subroutine_params + [abe_irq_pingpong_player_id]; + if (params != (u32 *) 0) { + p1 = params[0]; + p2 = params[1]; + p3 = params[2]; + p4 = params[3]; + } + (*f4) (p1, p2, p3, p4); + break; + default: + break; + } + } +} + +/** + * abe_set_sequence_time_accuracy + * @fast: fast counter + * @slow: slow counter + * + */ +abehal_status abe_set_sequence_time_accuracy(u32 fast, u32 slow) +{ + u32 data; + _log(ABE_ID_SET_SEQUENCE_TIME_ACCURACY, fast, slow, 0); + data = minimum(MAX_UINT16, fast / FW_SCHED_LOOP_FREQ_DIV1000); + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_FASTCOUNTER_ADDR, + &data, sizeof(data)); + data = minimum(MAX_UINT16, slow / FW_SCHED_LOOP_FREQ_DIV1000); + omap_abe_mem_write(abe, OMAP_ABE_DMEM, OMAP_ABE_D_SLOWCOUNTER_ADDR, + &data, sizeof(data)); + return 0; +} +EXPORT_SYMBOL(abe_set_sequence_time_accuracy); diff --git a/sound/soc/omap/abe/abe_seq.h b/sound/soc/omap/abe/abe_seq.h new file mode 100644 index 0000000000000000000000000000000000000000..e5047ad6bdae0a40a03594ad38f2837962c5bc0a --- /dev/null +++ b/sound/soc/omap/abe/abe_seq.h @@ -0,0 +1,64 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_SEQ_H_ +#define _ABE_SEQ_H_ + +void omap_abe_reset_all_sequence(struct omap_abe *abe); + +#endif /* _ABE_SEQ_H_ */ diff --git a/sound/soc/omap/abe/abe_sm_addr.h b/sound/soc/omap/abe/abe_sm_addr.h new file mode 100644 index 0000000000000000000000000000000000000000..a9e28ac66a2478d2b3d8fc0343fb5b57836c6256 --- /dev/null +++ b/sound/soc/omap/abe/abe_sm_addr.h @@ -0,0 +1,353 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated 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. + * + */ +#define OMAP_ABE_INIT_SM_ADDR 0x0 +#define OMAP_ABE_INIT_SM_SIZE 0xC80 +#define OMAP_ABE_S_DATA0_ADDR 0xC80 +#define OMAP_ABE_S_DATA0_SIZE 0x8 +#define OMAP_ABE_S_TEMP_ADDR 0xC88 +#define OMAP_ABE_S_TEMP_SIZE 0x8 +#define OMAP_ABE_S_PHOENIXOFFSET_ADDR 0xC90 +#define OMAP_ABE_S_PHOENIXOFFSET_SIZE 0x8 +#define OMAP_ABE_S_GTARGET1_ADDR 0xC98 +#define OMAP_ABE_S_GTARGET1_SIZE 0x38 +#define OMAP_ABE_S_GTARGET_DL1_ADDR 0xCD0 +#define OMAP_ABE_S_GTARGET_DL1_SIZE 0x10 +#define OMAP_ABE_S_GTARGET_DL2_ADDR 0xCE0 +#define OMAP_ABE_S_GTARGET_DL2_SIZE 0x10 +#define OMAP_ABE_S_GTARGET_ECHO_ADDR 0xCF0 +#define OMAP_ABE_S_GTARGET_ECHO_SIZE 0x8 +#define OMAP_ABE_S_GTARGET_SDT_ADDR 0xCF8 +#define OMAP_ABE_S_GTARGET_SDT_SIZE 0x8 +#define OMAP_ABE_S_GTARGET_VXREC_ADDR 0xD00 +#define OMAP_ABE_S_GTARGET_VXREC_SIZE 0x10 +#define OMAP_ABE_S_GTARGET_UL_ADDR 0xD10 +#define OMAP_ABE_S_GTARGET_UL_SIZE 0x10 +#define OMAP_ABE_S_GTARGET_BTUL_ADDR 0xD20 +#define OMAP_ABE_S_GTARGET_BTUL_SIZE 0x8 +#define OMAP_ABE_S_GCURRENT_ADDR 0xD28 +#define OMAP_ABE_S_GCURRENT_SIZE 0x90 +#define OMAP_ABE_S_GAIN_ONE_ADDR 0xDB8 +#define OMAP_ABE_S_GAIN_ONE_SIZE 0x8 +#define OMAP_ABE_S_TONES_ADDR 0xDC0 +#define OMAP_ABE_S_TONES_SIZE 0x60 +#define OMAP_ABE_S_VX_DL_ADDR 0xE20 +#define OMAP_ABE_S_VX_DL_SIZE 0x60 +#define OMAP_ABE_S_MM_UL2_ADDR 0xE80 +#define OMAP_ABE_S_MM_UL2_SIZE 0x60 +#define OMAP_ABE_S_MM_DL_ADDR 0xEE0 +#define OMAP_ABE_S_MM_DL_SIZE 0x60 +#define OMAP_ABE_S_DL1_M_OUT_ADDR 0xF40 +#define OMAP_ABE_S_DL1_M_OUT_SIZE 0x60 +#define OMAP_ABE_S_DL2_M_OUT_ADDR 0xFA0 +#define OMAP_ABE_S_DL2_M_OUT_SIZE 0x60 +#define OMAP_ABE_S_ECHO_M_OUT_ADDR 0x1000 +#define OMAP_ABE_S_ECHO_M_OUT_SIZE 0x60 +#define OMAP_ABE_S_SDT_M_OUT_ADDR 0x1060 +#define OMAP_ABE_S_SDT_M_OUT_SIZE 0x60 +#define OMAP_ABE_S_VX_UL_ADDR 0x10C0 +#define OMAP_ABE_S_VX_UL_SIZE 0x60 +#define OMAP_ABE_S_VX_UL_M_ADDR 0x1120 +#define OMAP_ABE_S_VX_UL_M_SIZE 0x60 +#define OMAP_ABE_S_BT_DL_ADDR 0x1180 +#define OMAP_ABE_S_BT_DL_SIZE 0x60 +#define OMAP_ABE_S_BT_UL_ADDR 0x11E0 +#define OMAP_ABE_S_BT_UL_SIZE 0x60 +#define OMAP_ABE_S_BT_DL_8K_ADDR 0x1240 +#define OMAP_ABE_S_BT_DL_8K_SIZE 0x18 +#define OMAP_ABE_S_BT_DL_16K_ADDR 0x1258 +#define OMAP_ABE_S_BT_DL_16K_SIZE 0x28 +#define OMAP_ABE_S_BT_UL_8K_ADDR 0x1280 +#define OMAP_ABE_S_BT_UL_8K_SIZE 0x10 +#define OMAP_ABE_S_BT_UL_16K_ADDR 0x1290 +#define OMAP_ABE_S_BT_UL_16K_SIZE 0x20 +#define OMAP_ABE_S_SDT_F_ADDR 0x12B0 +#define OMAP_ABE_S_SDT_F_SIZE 0x60 +#define OMAP_ABE_S_SDT_F_DATA_ADDR 0x1310 +#define OMAP_ABE_S_SDT_F_DATA_SIZE 0x48 +#define OMAP_ABE_S_MM_DL_OSR_ADDR 0x1358 +#define OMAP_ABE_S_MM_DL_OSR_SIZE 0xC0 +#define OMAP_ABE_S_24_ZEROS_ADDR 0x1418 +#define OMAP_ABE_S_24_ZEROS_SIZE 0xC0 +#define OMAP_ABE_S_DMIC1_ADDR 0x14D8 +#define OMAP_ABE_S_DMIC1_SIZE 0x60 +#define OMAP_ABE_S_DMIC2_ADDR 0x1538 +#define OMAP_ABE_S_DMIC2_SIZE 0x60 +#define OMAP_ABE_S_DMIC3_ADDR 0x1598 +#define OMAP_ABE_S_DMIC3_SIZE 0x60 +#define OMAP_ABE_S_AMIC_ADDR 0x15F8 +#define OMAP_ABE_S_AMIC_SIZE 0x60 +#define OMAP_ABE_S_DMIC1_L_ADDR 0x1658 +#define OMAP_ABE_S_DMIC1_L_SIZE 0x60 +#define OMAP_ABE_S_DMIC1_R_ADDR 0x16B8 +#define OMAP_ABE_S_DMIC1_R_SIZE 0x60 +#define OMAP_ABE_S_DMIC2_L_ADDR 0x1718 +#define OMAP_ABE_S_DMIC2_L_SIZE 0x60 +#define OMAP_ABE_S_DMIC2_R_ADDR 0x1778 +#define OMAP_ABE_S_DMIC2_R_SIZE 0x60 +#define OMAP_ABE_S_DMIC3_L_ADDR 0x17D8 +#define OMAP_ABE_S_DMIC3_L_SIZE 0x60 +#define OMAP_ABE_S_DMIC3_R_ADDR 0x1838 +#define OMAP_ABE_S_DMIC3_R_SIZE 0x60 +#define OMAP_ABE_S_BT_UL_L_ADDR 0x1898 +#define OMAP_ABE_S_BT_UL_L_SIZE 0x60 +#define OMAP_ABE_S_BT_UL_R_ADDR 0x18F8 +#define OMAP_ABE_S_BT_UL_R_SIZE 0x60 +#define OMAP_ABE_S_AMIC_L_ADDR 0x1958 +#define OMAP_ABE_S_AMIC_L_SIZE 0x60 +#define OMAP_ABE_S_AMIC_R_ADDR 0x19B8 +#define OMAP_ABE_S_AMIC_R_SIZE 0x60 +#define OMAP_ABE_S_ECHOREF_L_ADDR 0x1A18 +#define OMAP_ABE_S_ECHOREF_L_SIZE 0x60 +#define OMAP_ABE_S_ECHOREF_R_ADDR 0x1A78 +#define OMAP_ABE_S_ECHOREF_R_SIZE 0x60 +#define OMAP_ABE_S_MM_DL_L_ADDR 0x1AD8 +#define OMAP_ABE_S_MM_DL_L_SIZE 0x60 +#define OMAP_ABE_S_MM_DL_R_ADDR 0x1B38 +#define OMAP_ABE_S_MM_DL_R_SIZE 0x60 +#define OMAP_ABE_S_MM_UL_ADDR 0x1B98 +#define OMAP_ABE_S_MM_UL_SIZE 0x3C0 +#define OMAP_ABE_S_AMIC_96K_ADDR 0x1F58 +#define OMAP_ABE_S_AMIC_96K_SIZE 0xC0 +#define OMAP_ABE_S_DMIC0_96K_ADDR 0x2018 +#define OMAP_ABE_S_DMIC0_96K_SIZE 0xC0 +#define OMAP_ABE_S_DMIC1_96K_ADDR 0x20D8 +#define OMAP_ABE_S_DMIC1_96K_SIZE 0xC0 +#define OMAP_ABE_S_DMIC2_96K_ADDR 0x2198 +#define OMAP_ABE_S_DMIC2_96K_SIZE 0xC0 +#define OMAP_ABE_S_UL_VX_UL_48_8K_ADDR 0x2258 +#define OMAP_ABE_S_UL_VX_UL_48_8K_SIZE 0x60 +#define OMAP_ABE_S_UL_VX_UL_48_16K_ADDR 0x22B8 +#define OMAP_ABE_S_UL_VX_UL_48_16K_SIZE 0x60 +#define OMAP_ABE_S_UL_MIC_48K_ADDR 0x2318 +#define OMAP_ABE_S_UL_MIC_48K_SIZE 0x60 +#define OMAP_ABE_S_VOICE_8K_UL_ADDR 0x2378 +#define OMAP_ABE_S_VOICE_8K_UL_SIZE 0x18 +#define OMAP_ABE_S_VOICE_8K_DL_ADDR 0x2390 +#define OMAP_ABE_S_VOICE_8K_DL_SIZE 0x10 +#define OMAP_ABE_S_MCPDM_OUT1_ADDR 0x23A0 +#define OMAP_ABE_S_MCPDM_OUT1_SIZE 0xC0 +#define OMAP_ABE_S_MCPDM_OUT2_ADDR 0x2460 +#define OMAP_ABE_S_MCPDM_OUT2_SIZE 0xC0 +#define OMAP_ABE_S_MCPDM_OUT3_ADDR 0x2520 +#define OMAP_ABE_S_MCPDM_OUT3_SIZE 0xC0 +#define OMAP_ABE_S_VOICE_16K_UL_ADDR 0x25E0 +#define OMAP_ABE_S_VOICE_16K_UL_SIZE 0x28 +#define OMAP_ABE_S_VOICE_16K_DL_ADDR 0x2608 +#define OMAP_ABE_S_VOICE_16K_DL_SIZE 0x20 +#define OMAP_ABE_S_XINASRC_DL_VX_ADDR 0x2628 +#define OMAP_ABE_S_XINASRC_DL_VX_SIZE 0x140 +#define OMAP_ABE_S_XINASRC_UL_VX_ADDR 0x2768 +#define OMAP_ABE_S_XINASRC_UL_VX_SIZE 0x140 +#define OMAP_ABE_S_XINASRC_MM_EXT_IN_ADDR 0x28A8 +#define OMAP_ABE_S_XINASRC_MM_EXT_IN_SIZE 0x140 +#define OMAP_ABE_S_VX_REC_ADDR 0x29E8 +#define OMAP_ABE_S_VX_REC_SIZE 0x60 +#define OMAP_ABE_S_VX_REC_L_ADDR 0x2A48 +#define OMAP_ABE_S_VX_REC_L_SIZE 0x60 +#define OMAP_ABE_S_VX_REC_R_ADDR 0x2AA8 +#define OMAP_ABE_S_VX_REC_R_SIZE 0x60 +#define OMAP_ABE_S_DL2_M_L_ADDR 0x2B08 +#define OMAP_ABE_S_DL2_M_L_SIZE 0x60 +#define OMAP_ABE_S_DL2_M_R_ADDR 0x2B68 +#define OMAP_ABE_S_DL2_M_R_SIZE 0x60 +#define OMAP_ABE_S_DL2_M_LR_EQ_DATA_ADDR 0x2BC8 +#define OMAP_ABE_S_DL2_M_LR_EQ_DATA_SIZE 0xC8 +#define OMAP_ABE_S_DL1_M_EQ_DATA_ADDR 0x2C90 +#define OMAP_ABE_S_DL1_M_EQ_DATA_SIZE 0xC8 +#define OMAP_ABE_S_EARP_48_96_LP_DATA_ADDR 0x2D58 +#define OMAP_ABE_S_EARP_48_96_LP_DATA_SIZE 0x78 +#define OMAP_ABE_S_IHF_48_96_LP_DATA_ADDR 0x2DD0 +#define OMAP_ABE_S_IHF_48_96_LP_DATA_SIZE 0x78 +#define OMAP_ABE_S_VX_UL_8_TEMP_ADDR 0x2E48 +#define OMAP_ABE_S_VX_UL_8_TEMP_SIZE 0x10 +#define OMAP_ABE_S_VX_UL_16_TEMP_ADDR 0x2E58 +#define OMAP_ABE_S_VX_UL_16_TEMP_SIZE 0x20 +#define OMAP_ABE_S_VX_DL_8_48_LP_DATA_ADDR 0x2E78 +#define OMAP_ABE_S_VX_DL_8_48_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_VX_DL_8_48_HP_DATA_ADDR 0x2EE0 +#define OMAP_ABE_S_VX_DL_8_48_HP_DATA_SIZE 0x38 +#define OMAP_ABE_S_VX_DL_16_48_LP_DATA_ADDR 0x2F18 +#define OMAP_ABE_S_VX_DL_16_48_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_VX_DL_16_48_HP_DATA_ADDR 0x2F80 +#define OMAP_ABE_S_VX_DL_16_48_HP_DATA_SIZE 0x28 +#define OMAP_ABE_S_VX_UL_48_8_LP_DATA_ADDR 0x2FA8 +#define OMAP_ABE_S_VX_UL_48_8_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_VX_UL_48_8_HP_DATA_ADDR 0x3010 +#define OMAP_ABE_S_VX_UL_48_8_HP_DATA_SIZE 0x38 +#define OMAP_ABE_S_VX_UL_48_16_LP_DATA_ADDR 0x3048 +#define OMAP_ABE_S_VX_UL_48_16_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_VX_UL_48_16_HP_DATA_ADDR 0x30B0 +#define OMAP_ABE_S_VX_UL_48_16_HP_DATA_SIZE 0x28 +#define OMAP_ABE_S_BT_UL_8_48_LP_DATA_ADDR 0x30D8 +#define OMAP_ABE_S_BT_UL_8_48_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_BT_UL_8_48_HP_DATA_ADDR 0x3140 +#define OMAP_ABE_S_BT_UL_8_48_HP_DATA_SIZE 0x38 +#define OMAP_ABE_S_BT_UL_16_48_LP_DATA_ADDR 0x3178 +#define OMAP_ABE_S_BT_UL_16_48_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_BT_UL_16_48_HP_DATA_ADDR 0x31E0 +#define OMAP_ABE_S_BT_UL_16_48_HP_DATA_SIZE 0x28 +#define OMAP_ABE_S_BT_DL_48_8_LP_DATA_ADDR 0x3208 +#define OMAP_ABE_S_BT_DL_48_8_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_BT_DL_48_8_HP_DATA_ADDR 0x3270 +#define OMAP_ABE_S_BT_DL_48_8_HP_DATA_SIZE 0x38 +#define OMAP_ABE_S_BT_DL_48_16_LP_DATA_ADDR 0x32A8 +#define OMAP_ABE_S_BT_DL_48_16_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_BT_DL_48_16_HP_DATA_ADDR 0x3310 +#define OMAP_ABE_S_BT_DL_48_16_HP_DATA_SIZE 0x28 +#define OMAP_ABE_S_ECHO_REF_48_8_LP_DATA_ADDR 0x3338 +#define OMAP_ABE_S_ECHO_REF_48_8_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_ECHO_REF_48_8_HP_DATA_ADDR 0x33A0 +#define OMAP_ABE_S_ECHO_REF_48_8_HP_DATA_SIZE 0x38 +#define OMAP_ABE_S_ECHO_REF_48_16_LP_DATA_ADDR 0x33D8 +#define OMAP_ABE_S_ECHO_REF_48_16_LP_DATA_SIZE 0x68 +#define OMAP_ABE_S_ECHO_REF_48_16_HP_DATA_ADDR 0x3440 +#define OMAP_ABE_S_ECHO_REF_48_16_HP_DATA_SIZE 0x28 +#define OMAP_ABE_S_XINASRC_ECHO_REF_ADDR 0x3468 +#define OMAP_ABE_S_XINASRC_ECHO_REF_SIZE 0x140 +#define OMAP_ABE_S_ECHO_REF_16K_ADDR 0x35A8 +#define OMAP_ABE_S_ECHO_REF_16K_SIZE 0x28 +#define OMAP_ABE_S_ECHO_REF_8K_ADDR 0x35D0 +#define OMAP_ABE_S_ECHO_REF_8K_SIZE 0x18 +#define OMAP_ABE_S_DL1_EQ_ADDR 0x35E8 +#define OMAP_ABE_S_DL1_EQ_SIZE 0x60 +#define OMAP_ABE_S_DL2_EQ_ADDR 0x3648 +#define OMAP_ABE_S_DL2_EQ_SIZE 0x60 +#define OMAP_ABE_S_DL1_GAIN_OUT_ADDR 0x36A8 +#define OMAP_ABE_S_DL1_GAIN_OUT_SIZE 0x60 +#define OMAP_ABE_S_DL2_GAIN_OUT_ADDR 0x3708 +#define OMAP_ABE_S_DL2_GAIN_OUT_SIZE 0x60 +#define OMAP_ABE_S_DC_HS_ADDR 0x3768 +#define OMAP_ABE_S_DC_HS_SIZE 0x8 +#define OMAP_ABE_S_DC_HF_ADDR 0x3770 +#define OMAP_ABE_S_DC_HF_SIZE 0x8 +#define OMAP_ABE_S_VIBRA_ADDR 0x3778 +#define OMAP_ABE_S_VIBRA_SIZE 0x30 +#define OMAP_ABE_S_VIBRA2_IN_ADDR 0x37A8 +#define OMAP_ABE_S_VIBRA2_IN_SIZE 0x30 +#define OMAP_ABE_S_VIBRA2_ADDR_ADDR 0x37D8 +#define OMAP_ABE_S_VIBRA2_ADDR_SIZE 0x8 +#define OMAP_ABE_S_VIBRACTRL_FORRIGHTSM_ADDR 0x37E0 +#define OMAP_ABE_S_VIBRACTRL_FORRIGHTSM_SIZE 0xC0 +#define OMAP_ABE_S_RNOISE_MEM_ADDR 0x38A0 +#define OMAP_ABE_S_RNOISE_MEM_SIZE 0x8 +#define OMAP_ABE_S_CTRL_ADDR 0x38A8 +#define OMAP_ABE_S_CTRL_SIZE 0x90 +#define OMAP_ABE_S_VIBRA1_IN_ADDR 0x3938 +#define OMAP_ABE_S_VIBRA1_IN_SIZE 0x30 +#define OMAP_ABE_S_VIBRA1_TEMP_ADDR 0x3968 +#define OMAP_ABE_S_VIBRA1_TEMP_SIZE 0xC0 +#define OMAP_ABE_S_VIBRACTRL_FORLEFTSM_ADDR 0x3A28 +#define OMAP_ABE_S_VIBRACTRL_FORLEFTSM_SIZE 0xC0 +#define OMAP_ABE_S_VIBRA1_MEM_ADDR 0x3AE8 +#define OMAP_ABE_S_VIBRA1_MEM_SIZE 0x58 +#define OMAP_ABE_S_VIBRACTRL_STEREO_ADDR 0x3B40 +#define OMAP_ABE_S_VIBRACTRL_STEREO_SIZE 0xC0 +#define OMAP_ABE_S_AMIC_96_48_DATA_ADDR 0x3C00 +#define OMAP_ABE_S_AMIC_96_48_DATA_SIZE 0x98 +#define OMAP_ABE_S_DMIC0_96_48_DATA_ADDR 0x3C98 +#define OMAP_ABE_S_DMIC0_96_48_DATA_SIZE 0x98 +#define OMAP_ABE_S_DMIC1_96_48_DATA_ADDR 0x3D30 +#define OMAP_ABE_S_DMIC1_96_48_DATA_SIZE 0x98 +#define OMAP_ABE_S_DMIC2_96_48_DATA_ADDR 0x3DC8 +#define OMAP_ABE_S_DMIC2_96_48_DATA_SIZE 0x98 +#define OMAP_ABE_S_DBG_8K_PATTERN_ADDR 0x3E60 +#define OMAP_ABE_S_DBG_8K_PATTERN_SIZE 0x10 +#define OMAP_ABE_S_DBG_16K_PATTERN_ADDR 0x3E70 +#define OMAP_ABE_S_DBG_16K_PATTERN_SIZE 0x20 +#define OMAP_ABE_S_DBG_24K_PATTERN_ADDR 0x3E90 +#define OMAP_ABE_S_DBG_24K_PATTERN_SIZE 0x30 +#define OMAP_ABE_S_DBG_48K_PATTERN_ADDR 0x3EC0 +#define OMAP_ABE_S_DBG_48K_PATTERN_SIZE 0x60 +#define OMAP_ABE_S_DBG_96K_PATTERN_ADDR 0x3F20 +#define OMAP_ABE_S_DBG_96K_PATTERN_SIZE 0xC0 +#define OMAP_ABE_S_MM_EXT_IN_ADDR 0x3FE0 +#define OMAP_ABE_S_MM_EXT_IN_SIZE 0x60 +#define OMAP_ABE_S_MM_EXT_IN_L_ADDR 0x4040 +#define OMAP_ABE_S_MM_EXT_IN_L_SIZE 0x60 +#define OMAP_ABE_S_MM_EXT_IN_R_ADDR 0x40A0 +#define OMAP_ABE_S_MM_EXT_IN_R_SIZE 0x60 +#define OMAP_ABE_S_MIC4_ADDR 0x4100 +#define OMAP_ABE_S_MIC4_SIZE 0x60 +#define OMAP_ABE_S_MIC4_L_ADDR 0x4160 +#define OMAP_ABE_S_MIC4_L_SIZE 0x60 +#define OMAP_ABE_S_SATURATION_7FFF_ADDR 0x41C0 +#define OMAP_ABE_S_SATURATION_7FFF_SIZE 0x8 +#define OMAP_ABE_S_SATURATION_ADDR 0x41C8 +#define OMAP_ABE_S_SATURATION_SIZE 0x8 +#define OMAP_ABE_S_XINASRC_BT_UL_ADDR 0x41D0 +#define OMAP_ABE_S_XINASRC_BT_UL_SIZE 0x140 +#define OMAP_ABE_S_XINASRC_BT_DL_ADDR 0x4310 +#define OMAP_ABE_S_XINASRC_BT_DL_SIZE 0x140 +#define OMAP_ABE_S_BT_DL_8K_TEMP_ADDR 0x4450 +#define OMAP_ABE_S_BT_DL_8K_TEMP_SIZE 0x10 +#define OMAP_ABE_S_BT_DL_16K_TEMP_ADDR 0x4460 +#define OMAP_ABE_S_BT_DL_16K_TEMP_SIZE 0x20 +#define OMAP_ABE_S_VX_DL_8_48_OSR_LP_DATA_ADDR 0x4480 +#define OMAP_ABE_S_VX_DL_8_48_OSR_LP_DATA_SIZE 0xE0 +#define OMAP_ABE_S_BT_UL_8_48_OSR_LP_DATA_ADDR 0x4560 +#define OMAP_ABE_S_BT_UL_8_48_OSR_LP_DATA_SIZE 0xE0 +#define OMAP_ABE_S_MM_DL_44P1_ADDR 0x4640 +#define OMAP_ABE_S_MM_DL_44P1_SIZE 0x300 +#define OMAP_ABE_S_TONES_44P1_ADDR 0x4940 +#define OMAP_ABE_S_TONES_44P1_SIZE 0x300 +#define OMAP_ABE_S_MM_DL_44P1_XK_ADDR 0x4C40 +#define OMAP_ABE_S_MM_DL_44P1_XK_SIZE 0x10 +#define OMAP_ABE_S_TONES_44P1_XK_ADDR 0x4C50 +#define OMAP_ABE_S_TONES_44P1_XK_SIZE 0x10 +#define OMAP_ABE_S_SRC_44P1_MULFAC1_ADDR 0x4C60 +#define OMAP_ABE_S_SRC_44P1_MULFAC1_SIZE 0x8 diff --git a/sound/soc/omap/abe/abe_taskid.h b/sound/soc/omap/abe/abe_taskid.h new file mode 100644 index 0000000000000000000000000000000000000000..b72c3a5f803255f93ffe6b962c2ea208e1a66c77 --- /dev/null +++ b/sound/soc/omap/abe/abe_taskid.h @@ -0,0 +1,187 @@ +/* + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * BSD LICENSE + * + * Copyright(c) 2010-2011 Texas Instruments Incorporated, + * 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 Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifndef _ABE_TASKID_H_ +#define _ABE_TASKID_H_ +#define C_ABE_FW_TASK_ASRC_VX_DL_8 0 +#define C_ABE_FW_TASK_ASRC_VX_DL_16 1 +#define C_ABE_FW_TASK_ASRC_VX_DL_8_SIB 2 +#define C_ABE_FW_TASK_ASRC_VX_DL_16_SIB 3 +#define C_ABE_FW_TASK_ASRC_MM_EXT_IN 4 +#define C_ABE_FW_TASK_ASRC_VX_UL_8 5 +#define C_ABE_FW_TASK_ASRC_VX_UL_16 6 +#define C_ABE_FW_TASK_ASRC_VX_UL_8_SIB 7 +#define C_ABE_FW_TASK_ASRC_VX_UL_16_SIB 8 +#define C_ABE_FW_TASK_VX_UL_48_8_DEC 9 +#define C_ABE_FW_TASK_VX_UL_48_16_DEC 10 +#define C_ABE_FW_TASK_BT_DL_48_8_DEC 11 +#define C_ABE_FW_TASK_BT_DL_48_16_DEC 12 +#define C_ABE_FW_TASK_ECHO_REF_48_8_DEC 13 +#define C_ABE_FW_TASK_ECHO_REF_48_16_DEC 14 +#define C_ABE_FW_TASK_DL2_EQ 15 +#define C_ABE_FW_TASK_ECHO_REF_48_16 16 +#define C_ABE_FW_TASK_ECHO_REF_48_8 17 +#define C_ABE_FW_TASK_GAIN_UPDATE 18 +#define C_ABE_FW_TASK_SideTone 19 +#define C_ABE_FW_TASK_VX_DL_8_48_LP 20 +#define C_ABE_FW_TASK_VX_DL_8_48_HP 21 +#define C_ABE_FW_TASK_VX_DL_16_48_LP 22 +#define C_ABE_FW_TASK_VX_DL_16_48_HP 23 +#define C_ABE_FW_TASK_VX_UL_48_8_LP 24 +#define C_ABE_FW_TASK_VX_UL_48_8_HP 25 +#define C_ABE_FW_TASK_VX_UL_48_16_LP 26 +#define C_ABE_FW_TASK_VX_UL_48_16_HP 27 +#define C_ABE_FW_TASK_BT_UL_8_48_LP 28 +#define C_ABE_FW_TASK_BT_UL_8_48_HP 29 +#define C_ABE_FW_TASK_BT_UL_16_48_LP 30 +#define C_ABE_FW_TASK_BT_UL_16_48_HP 31 +#define C_ABE_FW_TASK_BT_DL_48_8_LP 32 +#define C_ABE_FW_TASK_BT_DL_48_8_HP 33 +#define C_ABE_FW_TASK_BT_DL_48_16_LP 34 +#define C_ABE_FW_TASK_BT_DL_48_16_HP 35 +#define C_ABE_FW_TASK_ECHO_REF_48_8_LP 36 +#define C_ABE_FW_TASK_ECHO_REF_48_8_HP 37 +#define C_ABE_FW_TASK_ECHO_REF_48_16_LP 38 +#define C_ABE_FW_TASK_ECHO_REF_48_16_HP 39 +#define C_ABE_FW_TASK_DL1_EQ 40 +#define C_ABE_FW_TASK_IHF_48_96_LP 41 +#define C_ABE_FW_TASK_EARP_48_96_LP 42 +#define C_ABE_FW_TASK_DL1_GAIN 43 +#define C_ABE_FW_TASK_DL2_GAIN 44 +#define C_ABE_FW_TASK_IO_PING_PONG 45 +#define C_ABE_FW_TASK_IO_DMIC 46 +#define C_ABE_FW_TASK_IO_PDM_UL 47 +#define C_ABE_FW_TASK_IO_BT_VX_UL 48 +#define C_ABE_FW_TASK_IO_MM_UL 49 +#define C_ABE_FW_TASK_IO_MM_UL2 50 +#define C_ABE_FW_TASK_IO_VX_UL 51 +#define C_ABE_FW_TASK_IO_MM_DL 52 +#define C_ABE_FW_TASK_IO_VX_DL 53 +#define C_ABE_FW_TASK_IO_TONES_DL 54 +#define C_ABE_FW_TASK_IO_VIB_DL 55 +#define C_ABE_FW_TASK_IO_BT_VX_DL 56 +#define C_ABE_FW_TASK_IO_PDM_DL 57 +#define C_ABE_FW_TASK_IO_MM_EXT_OUT 58 +#define C_ABE_FW_TASK_IO_MM_EXT_IN 59 +#define C_ABE_FW_TASK_DEBUG_IRQFIFO 60 +#define C_ABE_FW_TASK_EchoMixer 61 +#define C_ABE_FW_TASK_SDTMixer 62 +#define C_ABE_FW_TASK_DL1Mixer 63 +#define C_ABE_FW_TASK_DL2Mixer 64 +#define C_ABE_FW_TASK_DL1Mixer_dual_mono 65 +#define C_ABE_FW_TASK_DL2Mixer_dual_mono 66 +#define C_ABE_FW_TASK_VXRECMixer 67 +#define C_ABE_FW_TASK_ULMixer 68 +#define C_ABE_FW_TASK_ULMixer_dual_mono 69 +#define C_ABE_FW_TASK_VIBRA_PACK 70 +#define C_ABE_FW_TASK_VX_DL_8_48_0SR 71 +#define C_ABE_FW_TASK_VX_DL_16_48_0SR 72 +#define C_ABE_FW_TASK_BT_UL_8_48_0SR 73 +#define C_ABE_FW_TASK_BT_UL_16_48_0SR 74 +#define C_ABE_FW_TASK_IHF_48_96_0SR 75 +#define C_ABE_FW_TASK_EARP_48_96_0SR 76 +#define C_ABE_FW_TASK_AMIC_SPLIT 77 +#define C_ABE_FW_TASK_DMIC1_SPLIT 78 +#define C_ABE_FW_TASK_DMIC2_SPLIT 79 +#define C_ABE_FW_TASK_DMIC3_SPLIT 80 +#define C_ABE_FW_TASK_VXREC_SPLIT 81 +#define C_ABE_FW_TASK_BT_UL_SPLIT 82 +#define C_ABE_FW_TASK_MM_SPLIT 83 +#define C_ABE_FW_TASK_VIBRA_SPLIT 84 +#define C_ABE_FW_TASK_MM_EXT_IN_SPLIT 85 +#define C_ABE_FW_TASK_ECHO_REF_SPLIT 86 +#define C_ABE_FW_TASK_UNUSED_1 87 +#define C_ABE_FW_TASK_VX_UL_ROUTING 88 +#define C_ABE_FW_TASK_MM_UL2_ROUTING 89 +#define C_ABE_FW_TASK_VIBRA1 90 +#define C_ABE_FW_TASK_VIBRA2 91 +#define C_ABE_FW_TASK_BT_UL_16_48 92 +#define C_ABE_FW_TASK_BT_UL_8_48 93 +#define C_ABE_FW_TASK_BT_DL_48_16 94 +#define C_ABE_FW_TASK_BT_DL_48_8 95 +#define C_ABE_FW_TASK_VX_DL_16_48 96 +#define C_ABE_FW_TASK_VX_DL_8_48 97 +#define C_ABE_FW_TASK_VX_UL_48_16 98 +#define C_ABE_FW_TASK_VX_UL_48_8 99 +#define C_ABE_FW_TASK_DBG_SYNC 100 +#define C_ABE_FW_TASK_AMIC_96_48_LP 101 +#define C_ABE_FW_TASK_DMIC1_96_48_LP 102 +#define C_ABE_FW_TASK_DMIC2_96_48_LP 103 +#define C_ABE_FW_TASK_DMIC3_96_48_LP 104 +#define C_ABE_FW_TASK_INIT_FW_MEMORY 105 +#define C_ABE_FW_TASK_DEBUGTRACE_VX_ASRCs 106 +#define C_ABE_FW_TASK_ASRC_BT_UL_8 107 +#define C_ABE_FW_TASK_ASRC_BT_UL_16 108 +#define C_ABE_FW_TASK_ASRC_BT_UL_8_SIB 109 +#define C_ABE_FW_TASK_ASRC_BT_UL_16_SIB 110 +#define C_ABE_FW_TASK_ASRC_BT_DL_8 111 +#define C_ABE_FW_TASK_ASRC_BT_DL_16 112 +#define C_ABE_FW_TASK_ASRC_BT_DL_8_SIB 113 +#define C_ABE_FW_TASK_ASRC_BT_DL_16_SIB 114 +#define C_ABE_FW_TASK_BT_DL_48_8_HP_OPP100 115 +#define C_ABE_FW_TASK_BT_DL_48_16_HP_OPP100 116 +#define C_ABE_FW_TASK_BT_DL_48_8_OPP100 117 +#define C_ABE_FW_TASK_BT_DL_48_16_OPP100 118 +#define C_ABE_FW_TASK_VX_DL_8_48_OSR_LP 119 +#define C_ABE_FW_TASK_VX_DL_8_48_FIR 120 +#define C_ABE_FW_TASK_BT_UL_8_48_OSR_LP 121 +#define C_ABE_FW_TASK_BT_UL_8_48_FIR 122 +#define C_ABE_FW_TASK_SRC44P1_MMDL 123 +#define C_ABE_FW_TASK_SRC44P1_TONES 124 +#define C_ABE_FW_TASK_SRC44P1_MMDL_1211 125 +#define C_ABE_FW_TASK_SRC44P1_TONES_1211 126 +#endif /* _ABE_TASKID_H_ */ diff --git a/sound/soc/omap/abe/abe_typ.h b/sound/soc/omap/abe/abe_typ.h new file mode 100644 index 0000000000000000000000000000000000000000..650d04330d3ebd0eeb7ba39be1382228562dfbf7 --- /dev/null +++ b/sound/soc/omap/abe/abe_typ.h @@ -0,0 +1,654 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated 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. + +*/ + +#include "abe_def.h" +#include "abe_initxxx_labels.h" + +#ifndef _ABE_TYP_H_ +#define _ABE_TYP_H_ +/* + * BASIC TYPES + */ +#define MAX_UINT8 ((((1L << 7) - 1) << 1) + 1) +#define MAX_UINT16 ((((1L << 15) - 1) << 1) + 1) +#define MAX_UINT32 ((((1L << 31) - 1) << 1) + 1) +#define s8 char +#define u8 unsigned char +#define s16 short +#define u16 unsigned short +#define s32 int +#define u32 unsigned int +/* returned status from HAL APIs */ +#define abehal_status u32 +/* 4 bytes Bit field indicating the type of informations to be traced */ +typedef u32 abe_dbg_mask_t; +/* scheduling task loops (250us / 272us with respectively 48kHz / + 44.1kHz on Phoenix). */ +typedef u32 abe_dbg_t; +/* Index to the table of sequences */ +typedef u32 abe_seq_code_t; +/* Index to the table of subroutines called in the sequence */ +typedef u32 abe_sub_code_t; +/* subroutine with no parameter */ +typedef void (*abe_subroutine0) (void); +/* subroutine with one parameter */ +typedef void (*abe_subroutine1) (u32); +typedef void (*abe_subroutine2) (u32, u32); +typedef void (*abe_subroutine3) (u32, u32, u32); +typedef void (*abe_subroutine4) (u32, u32, u32, u32); +/* + * CODE PORTABILITY - FUTURE PATCHES + * + * 32bits field for having the code compatible with future revisions of + * the hardware (audio integration) or evolution of the software + * partitionning. Used for the highest level APIs (launch_sequences) + */ +typedef u32 abe_patch_rev; +/* + * ENUMS + */ +/* + * MEMORY CONFIG TYPE + * + * 0: Ultra Lowest power consumption audio player + * 1: OPP 25% (simple multimedia features) + * 2: OPP 50% (multimedia and voice calls) + * 3: OPP100% (multimedia complex use-cases) + */ +#define ABE_AUDIO_PLAYER_ON_HEADSET_OR_EARPHONE 1 +#define ABE_DRIFT_MANAGEMENT_FOR_AUDIO_PLAYER 2 +#define ABE_DRIFT_MANAGEMENT_FOR_VOICE_CALL 3 +#define ABE_VOICE_CALL_ON_HEADSET_OR_EARPHONE_OR_BT 4 +#define ABE_MULTIMEDIA_AUDIO_RECORDER 5 +#define ABE_VIBRATOR_OR_HAPTICS 6 +#define ABE_VOICE_CALL_ON_HANDS_FREE_SPEAKER 7 +#define ABE_RINGER_TONES 8 +#define ABE_VOICE_CALL_WITH_EARPHONE_ACTIVE_NOISE_CANCELLER 9 +#define ABE_LAST_USE_CASE 10 +/* + * OPP TYPE + * + * 0: Ultra Lowest power consumption audio player + * 1: OPP 25% (simple multimedia features) + * 2: OPP 50% (multimedia and voice calls) + * 3: OPP100% (multimedia complex use-cases) + */ +#define ABE_OPP0 0 +#define ABE_OPP25 1 +#define ABE_OPP50 2 +#define ABE_OPP100 3 +/* + * DMIC DECIMATION RATIO + * + */ +#define ABE_DEC16 16 +#define ABE_DEC25 25 +#define ABE_DEC32 32 +#define ABE_DEC40 40 +/* + * SAMPLES TYPE + * + * mono 16 bit sample LSB aligned, 16 MSB bits are unused; + * mono right shifted to 16bits LSBs on a 32bits DMEM FIFO for McBSP + * TX purpose; + * mono sample MSB aligned (16/24/32bits); + * two successive mono samples in one 32bits container; + * Two L/R 16bits samples in a 32bits container; + * Two channels defined with two MSB aligned samples; + * Three channels defined with three MSB aligned samples (MIC); + * Four channels defined with four MSB aligned samples (MIC); + * . . . + * Eight channels defined with eight MSB aligned samples (MIC); + */ +#define MONO_MSB 1 +#define MONO_RSHIFTED_16 2 +#define STEREO_RSHIFTED_16 3 +#define STEREO_16_16 4 +#define STEREO_MSB 5 +#define THREE_MSB 6 +#define FOUR_MSB 7 +#define FIVE_MSB 8 +#define SIX_MSB 9 +#define SEVEN_MSB 10 +#define EIGHT_MSB 11 +#define NINE_MSB 12 +#define TEN_MSB 13 +/* + * PORT PROTOCOL TYPE - abe_port_protocol_switch_id + */ +#define SLIMBUS_PORT_PROT 1 +#define SERIAL_PORT_PROT 2 +#define TDM_SERIAL_PORT_PROT 3 +#define DMIC_PORT_PROT 4 +#define MCPDMDL_PORT_PROT 5 +#define MCPDMUL_PORT_PROT 6 +#define PINGPONG_PORT_PROT 7 +#define DMAREQ_PORT_PROT 8 +/* + * PORT IDs, this list is aligned with the FW data mapping + */ +#define OMAP_ABE_DMIC_PORT 0 +#define OMAP_ABE_PDM_UL_PORT 1 +#define OMAP_ABE_BT_VX_UL_PORT 2 +#define OMAP_ABE_MM_UL_PORT 3 +#define OMAP_ABE_MM_UL2_PORT 4 +#define OMAP_ABE_VX_UL_PORT 5 +#define OMAP_ABE_MM_DL_PORT 6 +#define OMAP_ABE_VX_DL_PORT 7 +#define OMAP_ABE_TONES_DL_PORT 8 +#define OMAP_ABE_VIB_DL_PORT 9 +#define OMAP_ABE_BT_VX_DL_PORT 10 +#define OMAP_ABE_PDM_DL_PORT 11 +#define OMAP_ABE_MM_EXT_OUT_PORT 12 +#define OMAP_ABE_MM_EXT_IN_PORT 13 +#define TDM_DL_PORT 14 +#define TDM_UL_PORT 15 +#define DEBUG_PORT 16 +#define LAST_PORT_ID 17 +/* definitions for the compatibility with HAL05xx */ +#define PDM_DL1_PORT 18 +#define PDM_DL2_PORT 19 +#define PDM_VIB_PORT 20 +/* There is only one DMIC port, always used with 6 samples + per 96kHz periods */ +#define DMIC_PORT1 DMIC_PORT +#define DMIC_PORT2 DMIC_PORT +#define DMIC_PORT3 DMIC_PORT +/* + * ABE_DL_SRC_ID source of samples + */ +#define SRC_DL1_MIXER_OUTPUT DL1_M_labelID +#define SRC_SDT_MIXER_OUTPUT SDT_M_labelID +#define SRC_DL1_GAIN_OUTPUT DL1_GAIN_out_labelID +#define SRC_DL1_EQ_OUTPUT DL1_EQ_labelID +#define SRC_DL2_GAIN_OUTPUT DL2_GAIN_out_labelID +#define SRC_DL2_EQ_OUTPUT DL2_EQ_labelID +#define SRC_MM_DL MM_DL_labelID +#define SRC_TONES_DL Tones_labelID +#define SRC_VX_DL VX_DL_labelID +#define SRC_VX_UL VX_UL_labelID +#define SRC_MM_UL2 MM_UL2_labelID +#define SRC_MM_UL MM_UL_labelID +/* + * abe_patched_pattern_id + * selection of the audio engine signal to + * replace by a precomputed pattern + */ +#define DBG_PATCH_AMIC 1 +#define DBG_PATCH_DMIC1 2 +#define DBG_PATCH_DMIC2 3 +#define DBG_PATCH_DMIC3 4 +#define DBG_PATCH_VX_REC 5 +#define DBG_PATCH_BT_UL 6 +#define DBG_PATCH_MM_DL 7 +#define DBG_PATCH_DL2_EQ 8 +#define DBG_PATCH_VIBRA 9 +#define DBG_PATCH_MM_EXT_IN 10 +#define DBG_PATCH_EANC_FBK_Out 11 +#define DBG_PATCH_MIC4 12 +#define DBG_PATCH_MM_DL_MIXDL1 13 +#define DBG_PATCH_MM_DL_MIXDL2 14 +/* + * Signal processing module names - EQ APS MIX ROUT + */ +/* equalizer downlink path headset + earphone */ +#define FEAT_EQ1 1 +/* equalizer downlink path integrated handsfree LEFT */ +#define FEAT_EQ2L (FEAT_EQ1+1) +/* equalizer downlink path integrated handsfree RIGHT */ +#define FEAT_EQ2R (FEAT_EQ2L+1) +/* equalizer downlink path side-tone */ +#define FEAT_EQSDT (FEAT_EQ2R+1) +/* equalizer uplink path AMIC */ +#define FEAT_EQAMIC (FEAT_EQSDT+1) +/* equalizer uplink path DMIC */ +#define FEAT_EQDMIC (FEAT_EQAMIC+1) +/* Acoustic protection for headset */ +#define FEAT_APS1 (FEAT_EQDMIC+1) +/* acoustic protection high-pass filter for handsfree "Left" */ +#define FEAT_APS2 (FEAT_APS1+1) +/* acoustic protection high-pass filter for handsfree "Right" */ +#define FEAT_APS3 (FEAT_APS2+1) +/* asynchronous sample-rate-converter for the downlink voice path */ +#define FEAT_ASRC1 (FEAT_APS3+1) +/* asynchronous sample-rate-converter for the uplink voice path */ +#define FEAT_ASRC2 (FEAT_ASRC1+1) +/* asynchronous sample-rate-converter for the multimedia player */ +#define FEAT_ASRC3 (FEAT_ASRC2+1) +/* asynchronous sample-rate-converter for the echo reference */ +#define FEAT_ASRC4 (FEAT_ASRC3+1) +/* mixer of the headset and earphone path */ +#define FEAT_MIXDL1 (FEAT_ASRC4+1) +/* mixer of the hands-free path */ +#define FEAT_MIXDL2 (FEAT_MIXDL1+1) +/* mixer for audio being sent on the voice_ul path */ +#define FEAT_MIXAUDUL (FEAT_MIXDL2+1) +/* mixer for voice communication recording */ +#define FEAT_MIXVXREC (FEAT_MIXAUDUL+1) +/* mixer for side-tone */ +#define FEAT_MIXSDT (FEAT_MIXVXREC+1) +/* mixer for echo reference */ +#define FEAT_MIXECHO (FEAT_MIXSDT+1) +/* router of the uplink path */ +#define FEAT_UPROUTE (FEAT_MIXECHO+1) +/* all gains */ +#define FEAT_GAINS (FEAT_UPROUTE+1) +#define FEAT_GAINS_DMIC1 (FEAT_GAINS+1) +#define FEAT_GAINS_DMIC2 (FEAT_GAINS_DMIC1+1) +#define FEAT_GAINS_DMIC3 (FEAT_GAINS_DMIC2+1) +#define FEAT_GAINS_AMIC (FEAT_GAINS_DMIC3+1) +#define FEAT_GAINS_SPLIT (FEAT_GAINS_AMIC+1) +#define FEAT_GAINS_DL1 (FEAT_GAINS_SPLIT+1) +#define FEAT_GAINS_DL2 (FEAT_GAINS_DL1+1) +#define FEAT_GAIN_BTUL (FEAT_GAINS_DL2+1) +/* sequencing queue of micro tasks */ +#define FEAT_SEQ (FEAT_GAIN_BTUL+1) +/* Phoenix control queue through McPDM */ +#define FEAT_CTL (FEAT_SEQ+1) +/* list of features of the firmware -------------------------------*/ +#define MAXNBFEATURE FEAT_CTL +/* abe_equ_id */ +/* equalizer downlink path headset + earphone */ +#define EQ1 FEAT_EQ1 +/* equalizer downlink path integrated handsfree LEFT */ +#define EQ2L FEAT_EQ2L +#define EQ2R FEAT_EQ2R +/* equalizer downlink path side-tone */ +#define EQSDT FEAT_EQSDT +#define EQAMIC FEAT_EQAMIC +#define EQDMIC FEAT_EQDMIC +/* abe_aps_id */ +/* Acoustic protection for headset */ +#define APS1 FEAT_APS1 +#define APS2L FEAT_APS2 +#define APS2R FEAT_APS3 +/* abe_asrc_id */ +/* asynchronous sample-rate-converter for the downlink voice path */ +#define ASRC1 FEAT_ASRC1 +/* asynchronous sample-rate-converter for the uplink voice path */ +#define ASRC2 FEAT_ASRC2 +/* asynchronous sample-rate-converter for the multimedia player */ +#define ASRC3 FEAT_ASRC3 +/* asynchronous sample-rate-converter for the voice uplink echo_reference */ +#define ASRC4 FEAT_ASRC4 +/* abe_mixer_id */ +#define MIXDL1 FEAT_MIXDL1 +#define MIXDL2 FEAT_MIXDL2 +#define MIXSDT FEAT_MIXSDT +#define MIXECHO FEAT_MIXECHO +#define MIXAUDUL FEAT_MIXAUDUL +#define MIXVXREC FEAT_MIXVXREC +/* abe_router_id */ +/* there is only one router up to now */ +#define UPROUTE FEAT_UPROUTE +/* + * GAIN IDs + */ +#define GAINS_DMIC1 FEAT_GAINS_DMIC1 +#define GAINS_DMIC2 FEAT_GAINS_DMIC2 +#define GAINS_DMIC3 FEAT_GAINS_DMIC3 +#define GAINS_AMIC FEAT_GAINS_AMIC +#define GAINS_SPLIT FEAT_GAINS_SPLIT +#define GAINS_DL1 FEAT_GAINS_DL1 +#define GAINS_DL2 FEAT_GAINS_DL2 +#define GAINS_BTUL FEAT_GAIN_BTUL +/* + * EVENT GENERATORS - abe_event_id + */ +#define EVENT_TIMER 0 +#define EVENT_44100 1 +/* + * SERIAL PORTS IDs - abe_mcbsp_id + */ +#define MCBSP1_TX MCBSP1_DMA_TX +#define MCBSP1_RX MCBSP1_DMA_RX +#define MCBSP2_TX MCBSP2_DMA_TX +#define MCBSP2_RX MCBSP2_DMA_RX +#define MCBSP3_TX MCBSP3_DMA_TX +#define MCBSP3_RX MCBSP3_DMA_RX +/* + * SERIAL PORTS IDs - abe_slimbus_id; + */ +#define SLIMBUS1_TX0 SLIMBUS1_DMA_TX0 +#define SLIMBUS1_TX1 SLIMBUS1_DMA_TX1 +#define SLIMBUS1_TX2 SLIMBUS1_DMA_TX2 +#define SLIMBUS1_TX3 SLIMBUS1_DMA_TX3 +#define SLIMBUS1_TX4 SLIMBUS1_DMA_TX4 +#define SLIMBUS1_TX5 SLIMBUS1_DMA_TX5 +#define SLIMBUS1_TX6 SLIMBUS1_DMA_TX6 +#define SLIMBUS1_TX7 SLIMBUS1_DMA_TX7 +#define SLIMBUS1_RX0 SLIMBUS1_DMA_RX0 +#define SLIMBUS1_RX1 SLIMBUS1_DMA_RX1 +#define SLIMBUS1_RX2 SLIMBUS1_DMA_RX2 +#define SLIMBUS1_RX3 SLIMBUS1_DMA_RX3 +#define SLIMBUS1_RX4 SLIMBUS1_DMA_RX4 +#define SLIMBUS1_RX5 SLIMBUS1_DMA_RX5 +#define SLIMBUS1_RX6 SLIMBUS1_DMA_RX6 +#define SLIMBUS1_RX7 SLIMBUS1_DMA_RX7 +#define SLIMBUS_UNUSED _DUMMY_FIFO_ +/* + * ----------------- TYPES USED FOR APIS --------------- + */ + +/* + * EQU_T + * + * coefficients of the equalizer + */ +typedef struct { + /* type of filter */ + u32 equ_type; + /* filter length */ + u32 equ_length; + union { + /* parameters are the direct and recursive coefficients in */ + /* Q6.26 integer fixed-point format. */ + s32 type1[NBEQ1]; + struct { + /* center frequency of the band [Hz] */ + s32 freq[NBEQ2]; + /* gain of each band. [dB] */ + s32 gain[NBEQ2]; + /* Q factor of this band [dB] */ + s32 q[NBEQ2]; + } type2; + } coef; + s32 equ_param3; +} abe_equ_t; + +/* + * APS_T + * + * coefficients of the Acoustics Protection and Safety + */ +struct abe_aps_t { + s32 coef1[NBAPS1]; + s32 coef2[NBAPS2]; +}; + +struct abe_aps_energy_t { + /* structure of two energy_t estimation for coil and membrane */ + u32 e1; + u32 e2; +}; +/* + * ROUTER_T + * + * table of indexes in unsigned bytes + */ +typedef u16 abe_router_t; +/* + * DATA_FORMAT_T + * + * used in port declaration + */ +typedef struct { + /* Sampling frequency of the stream */ + u32 f; + /* Sample format type */ + u32 samp_format; +} abe_data_format_t; +/* + * PORT_PROTOCOL_T + * + * port declaration + */ +typedef struct { + /* Direction=0 means input from AESS point of view */ + u32 direction; + /* Protocol type (switch) during the data transfers */ + u32 protocol_switch; + union { + /* Slimbus peripheral connected to ATC */ + struct { + /* Address of ATC Slimbus descriptor's index */ + u32 desc_addr1; + /* DMEM address 1 in bytes */ + u32 buf_addr1; + /* DMEM buffer size size in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + /* Second ATC index for SlimBus reception (or NULL) */ + u32 desc_addr2; + /* DMEM address 2 in bytes */ + u32 buf_addr2; + } prot_slimbus; + /* McBSP/McASP peripheral connected to ATC */ + struct { + u32 desc_addr; + /* Address of ATC McBSP/McASP descriptor's in bytes */ + u32 buf_addr; + /* DMEM address in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + } prot_serial; + /* DMIC peripheral connected to ATC */ + struct { + /* DMEM address in bytes */ + u32 buf_addr; + /* DMEM buffer size in bytes */ + u32 buf_size; + /* Number of activated DMIC */ + u32 nbchan; + } prot_dmic; + /* McPDMDL peripheral connected to ATC */ + struct { + /* DMEM address in bytes */ + u32 buf_addr; + /* DMEM size in bytes */ + u32 buf_size; + /* Control allowed on McPDM DL */ + u32 control; + } prot_mcpdmdl; + /* McPDMUL peripheral connected to ATC */ + struct { + /* DMEM address size in bytes */ + u32 buf_addr; + /* DMEM buffer size size in bytes */ + u32 buf_size; + } prot_mcpdmul; + /* Ping-Pong interface to the Host using cache-flush */ + struct { + /* Address of ATC descriptor's */ + u32 desc_addr; + /* DMEM buffer base address in bytes */ + u32 buf_addr; + /* DMEM size in bytes for each ping and pong buffers */ + u32 buf_size; + /* IRQ address (either DMA (0) MCU (1) or DSP(2)) */ + u32 irq_addr; + /* IRQ data content loaded in the AESS IRQ register */ + u32 irq_data; + /* Call-back function upon IRQ reception */ + u32 callback; + } prot_pingpong; + /* DMAreq line to CBPr */ + struct { + /* Address of ATC descriptor's */ + u32 desc_addr; + /* DMEM buffer address in bytes */ + u32 buf_addr; + /* DMEM buffer size size in bytes */ + u32 buf_size; + /* ITERation on each DMAreq signals */ + u32 iter; + /* DMAreq address */ + u32 dma_addr; + /* DMA/AESS = 1 << #DMA */ + u32 dma_data; + } prot_dmareq; + /* Circular buffer - direct addressing to DMEM */ + struct { + /* DMEM buffer base address in bytes */ + u32 buf_addr; + /* DMEM buffer size in bytes */ + u32 buf_size; + /* DMAreq address */ + u32 dma_addr; + /* DMA/AESS = 1 << #DMA */ + u32 dma_data; + } prot_circular_buffer; + } p; +} abe_port_protocol_t; +/* + * DMA_T + * + * dma structure for easing programming + */ +typedef struct { + /* OCP L3 pointer to the first address of the */ + void *data; + /* destination buffer (either DMA or Ping-Pong read/write pointers). */ + /* address L3 when addressing the DMEM buffer instead of CBPr */ + void *l3_dmem; + /* address L3 translated to L4 the ARM memory space */ + void *l4_dmem; + /* number of iterations for the DMA data moves. */ + u32 iter; +} abe_dma_t; + +typedef struct { + /* Offset to the first address of the */ + u32 data; + /* number of iterations for the DMA data moves. */ + u32 iter; +} abe_dma_t_offset; +/* + * SEQ_T + * + * struct { + * micros_t time; Waiting time before executing next line + * seq_code_t code Subroutine index interpreted in the HAL + * and translated to FW subroutine codes + * in case of ABE tasks + * int32 param[2] Two parameters + * } seq_t + * + */ +typedef struct { + u32 delta_time; + u32 code; + u32 param[4]; + u8 tag; +} abe_seq_t; + +typedef struct { + u32 mask; + abe_seq_t seq1; + abe_seq_t seq2; +} abe_sequence_t; +/* + * DRIFT_T abe_drift_t = s32 + * + * ASRC drift parameter in [ppm] value + */ +/* + * -------------------- INTERNAL DATA TYPES --------------------- + */ +/* + * ABE_IRQ_DATA_T + * + * IRQ FIFO content declaration + * APS interrupts : IRQ_FIFO[31:28] = IRQtag_APS, + * IRQ_FIFO[27:16] = APS_IRQs, IRQ_FIFO[15:0] = loopCounter + * SEQ interrupts : IRQ_FIFO[31:28] IRQtag_COUNT, + * IRQ_FIFO[27:16] = Count_IRQs, IRQ_FIFO[15:0] = loopCounter + * Ping-Pong Interrupts : IRQ_FIFO[31:28] = IRQtag_PP, + * IRQ_FIFO[27:16] = PP_MCU_IRQ, IRQ_FIFO[15:0] = loopCounter + */ +typedef struct { + unsigned int counter:16; + unsigned int data:12; + unsigned int tag:4; +} abe_irq_data_t; +/* + * ABE_PORT_T status / format / sampling / protocol(call_back) / + * features / gain / name .. + * + */ +typedef struct { + /* running / idled */ + u16 status; + /* Sample format type */ + abe_data_format_t format; + /* API : for ASRC */ + s32 drift; + /* optionnal call-back index for errors and ack */ + u16 callback; + /* IO tasks buffers */ + u16 smem_buffer1; + u16 smem_buffer2; + abe_port_protocol_t protocol; + /* pointer and iteration counter of the xDMA */ + abe_dma_t_offset dma; + /* list of features associated to a port (EQ, APS, ... , ends with 0) */ + u16 feature_index[MAXFEATUREPORT]; + char name[NBCHARPORTNAME]; +} abe_port_t; +/* + * ABE_SUBROUTINE_T + * + */ +typedef struct { + u32 sub_id; + s32 param[4]; +} abe_subroutine_t; + +#endif/* ifndef _ABE_TYP_H_ */ diff --git a/sound/soc/omap/abe/abe_typedef.h b/sound/soc/omap/abe/abe_typedef.h new file mode 100644 index 0000000000000000000000000000000000000000..59d7221de480c871fd69c4fee5f5dc23f09dea20 --- /dev/null +++ b/sound/soc/omap/abe/abe_typedef.h @@ -0,0 +1,232 @@ +/* + + This file is provided under a dual BSD/GPLv2 license. When using or + redistributing this file, you may do so under either license. + + GPL LICENSE SUMMARY + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + BSD LICENSE + + Copyright(c) 2010-2011 Texas Instruments Incorporated, + 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 Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _ABE_TYPEDEF_H_ +#define _ABE_TYPEDEF_H_ + +#include "abe_define.h" +#include "abe_typ.h" + +/* + * Basic types definition + */ +/* + * Commonly used structures + */ +typedef struct abetaskTag { + /* 0 ... Index of called function */ + u16 iF; + /* 2 ... for INITPTR of A0 */ + u16 A0; + /* 4 ... for INITPTR of A1 */ + u16 A1; + /* 6 ... for INITPTR of A2 & A3 */ + u16 A2_3; + /* 8 ... for INITPTR of A4 & A5 */ + u16 A4_5; + /* 10 ... for INITREG of R0, R1, R2, R3 */ + u16 R; + /* 12 */ + u16 misc0; + /* 14 */ + u16 misc1; +} ABE_STask; +typedef ABE_STask *pABE_STask; +typedef ABE_STask **ppABE_STask; + +struct ABE_SIODescriptor { + /* 0 */ + u16 drift_ASRC; + /* 2 */ + u16 drift_io; + /* 4 "Function index" of XLS sheet "Functions" */ + u8 io_type_idx; + /* 5 1 = MONO or Stereo1616, 2= STEREO, ... */ + u8 samp_size; + /* 6 drift "issues" for ASRC */ + s16 flow_counter; + /* 8 address for IRQ or DMArequests */ + u16 hw_ctrl_addr; + /* 10 DMA request bit-field or IRQ (DSP/MCU) */ + u8 atc_irq_data; + /* 11 0 = Read, 3 = Write */ + u8 direction_rw; + /* 12 */ + u8 repeat_last_samp; + /* 13 12 at 48kHz, ... */ + u8 nsamp; + /* 14 nsamp x samp_size */ + u8 x_io; + /* 15 ON = 0x80, OFF = 0x00 */ + u8 on_off; + /* 16 For Slimbus and TDM purpose */ + u16 split_addr1; + /* 18 */ + u16 split_addr2; + /* 20 */ + u16 split_addr3; + /* 22 */ + u8 before_f_index; + /* 23 */ + u8 after_f_index; + /* 24 SM/CM INITPTR field */ + u16 smem_addr1; + /* 26 in bytes */ + u16 atc_address1; + /* 28 DMIC_ATC_PTR, MCPDM_UL_ATC_PTR, ... */ + u16 atc_pointer_saved1; + /* 30 samp_size (except in TDM or Slimbus) */ + u8 data_size1; + /* 31 "Function index" of XLS sheet "Functions" */ + u8 copy_f_index1; + /* 32 For Slimbus and TDM purpose */ + u16 smem_addr2; + /* 34 */ + u16 atc_address2; + /* 36 */ + u16 atc_pointer_saved2; + /* 38 */ + u8 data_size2; + /* 39 */ + u8 copy_f_index2; +}; + +/* [w] asrc output used for the next asrc call (+/- 1 / 0) */ +#define drift_asrc_ 0 +/* [w] asrc output used for controlling the number of samples to be + exchanged (+/- 1 / 0) */ +#define drift_io_ 2 +/* address of the IO subroutine */ +#define io_type_idx_ 4 +#define samp_size_ 5 +/* flow error counter */ +#define flow_counter_ 6 +/* dmareq address or host irq buffer address (atc address) */ +#define hw_ctrl_addr_ 8 +/* data content to be loaded to "hw_ctrl_addr" */ +#define atc_irq_data_ 10 +/* read dmem =0, write dmem =3 (atc offset of the access pointer) */ +#define direction_rw_ 11 +/* flag set to allow repeating the last sample on downlink paths */ +#define repeat_last_samp_ 12 +/* number of samples (either mono stereo...) */ +#define nsamp_ 13 +/* x number of raw DMEM data moved */ +#define x_io_ 14 +#define on_off_ 15 +/* internal smem buffer initptr pointer index */ +#define split_addr1_ 16 +/* internal smem buffer initptr pointer index */ +#define split_addr2_ 18 +/* internal smem buffer initptr pointer index */ +#define split_addr3_ 20 +/* index of the copy subroutine */ +#define before_f_index_ 22 +/* index of the copy subroutine */ +#define after_f_index_ 23 +#define minidesc1_ 24 +/* internal smem buffer initptr pointer index */ +#define rel_smem_ 0 +/* atc descriptor address (byte address x4) */ +#define rel_atc_ 2 +/* location of the saved ATC pointer (+debug info) */ +#define rel_atc_saved 4 +/* size of each sample (1:mono/1616 2:stereo ... ) */ +#define rel_size_ 6 +/* index of the copy subroutine */ +#define rel_f_ 7 +#define s_mem_mm_ul 24 +#define s_mm_ul_size 30 +#define minidesc2_ 32 +#define Struct_Size 40 + +struct ABE_SPingPongDescriptor { + /* 0: [W] asrc output used for the next ASRC call (+/- 1 / 0) */ + u16 drift_ASRC; + /* 2: [W] asrc output used for controlling the number of + samples to be exchanged (+/- 1 / 0) */ + u16 drift_io; + /* 4: DMAReq address or HOST IRQ buffer address (ATC ADDRESS) */ + u16 hw_ctrl_addr; + /* 6: index of the copy subroutine */ + u8 copy_func_index; + /* 7: X number of SMEM samples to move */ + u8 x_io; + /* 8: 0 for mono data, 1 for stereo data */ + u8 data_size; + /* 9: internal SMEM buffer INITPTR pointer index */ + u8 smem_addr; + /* 10: data content to be loaded to "hw_ctrl_addr" */ + u8 atc_irq_data; + /* 11: ping/pong buffer flag */ + u8 counter; + /* 12: current Base address of the working buffer */ + u16 workbuff_BaseAddr; + /* 14: samples left in the working buffer */ + u16 workbuff_Samples; + /* 16: Base address of the ping/pong buffer 0 */ + u16 nextbuff0_BaseAddr; + /* 18: samples available in the ping/pong buffer 0 */ + u16 nextbuff0_Samples; + /* 20: Base address of the ping/pong buffer 1 */ + u16 nextbuff1_BaseAddr; + /* 22: samples available in the ping/pong buffer 1 */ + u16 nextbuff1_Samples; +}; + +#endif/* _ABE_TYPEDEF_H_ */ diff --git a/sound/soc/omap/abe/port_mgr.c b/sound/soc/omap/abe/port_mgr.c new file mode 100644 index 0000000000000000000000000000000000000000..aef5ceeb573fcf12fe141831b9b13c2dac5746fb --- /dev/null +++ b/sound/soc/omap/abe/port_mgr.c @@ -0,0 +1,341 @@ +/* + * ALSA SoC OMAP ABE port manager + * + * 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 + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include "port_mgr.h" +#include "abe_main.h" + +/* this must match logical ID numbers in port_mgr.h */ +static const char *lport_name[] = { + "dmic0", "dmic1", "dmic2", "pdmdl1", "pdmdl2", "pdmvib", + "pdmul1", "bt_vx_dl", "bt_vx_ul", "mm_ext_ul", "mm_ext_dl", + "mm_dl1", "mm_ul1", "mm_ul2", "vx_dl", "vx_ul", "vib", "tones" +}; + +static DEFINE_MUTEX(port_mgr_mutex); +static struct abe *the_abe = NULL; +static int users = 0; + +/* + * Get the Physical port ID based on the logical port ID + * + * FE and BE ports have unique ID's within the driver but share + * ID's within the ABE. This maps a driver port ID to an ABE port ID. + */ +static int get_physical_id(int logical_id) +{ + switch (logical_id) { + /* backend ports */ + case OMAP_ABE_BE_PORT_DMIC0: + case OMAP_ABE_BE_PORT_DMIC1: + case OMAP_ABE_BE_PORT_DMIC2: + return DMIC_PORT; + case OMAP_ABE_BE_PORT_PDM_DL1: + case OMAP_ABE_BE_PORT_PDM_DL2: + return PDM_DL_PORT; + case OMAP_ABE_BE_PORT_PDM_VIB: + return VIB_DL_PORT; + case OMAP_ABE_BE_PORT_PDM_UL1: + return PDM_UL_PORT; + case OMAP_ABE_BE_PORT_BT_VX_DL: + return BT_VX_DL_PORT; + case OMAP_ABE_BE_PORT_BT_VX_UL: + return BT_VX_UL_PORT; + case OMAP_ABE_BE_PORT_MM_EXT_UL: + return MM_EXT_OUT_PORT; + case OMAP_ABE_BE_PORT_MM_EXT_DL: + return MM_EXT_IN_PORT; + /* front end ports */ + case OMAP_ABE_FE_PORT_MM_DL1: + return MM_DL_PORT; + case OMAP_ABE_FE_PORT_MM_UL1: + return MM_UL_PORT; + case OMAP_ABE_FE_PORT_MM_UL2: + return MM_UL2_PORT; + case OMAP_ABE_FE_PORT_VX_DL: + return VX_DL_PORT; + case OMAP_ABE_FE_PORT_VX_UL: + return VX_UL_PORT; + case OMAP_ABE_FE_PORT_VIB: + return VIB_DL_PORT; + case OMAP_ABE_FE_PORT_TONES: + return TONES_DL_PORT; + } + return -EINVAL; +} + +/* + * Get the number of enabled users of the physical port shared by this client. + * Locks held by callers. + */ +static int port_get_num_users(struct abe *abe, struct omap_abe_port *port) +{ + struct omap_abe_port *p; + int users = 0; + + list_for_each_entry(p, &abe->ports, list) { + if (p->physical_id == port->physical_id && p->state == PORT_ENABLED) + users++; + } + return users; +} + +static int port_is_open(struct abe *abe, int phy_port) +{ + struct omap_abe_port *p; + + list_for_each_entry(p, &abe->ports, list) { + if (p->physical_id == phy_port && p->state == PORT_ENABLED) + return 1; + } + return 0; +} + +/* + * Check whether the physical port is enabled for this PHY port ID. + * Locks held by callers. + */ +int omap_abe_port_is_enabled(struct abe *abe, struct omap_abe_port *port) +{ + struct omap_abe_port *p; + unsigned long flags; + + spin_lock_irqsave(&abe->lock, flags); + + list_for_each_entry(p, &abe->ports, list) { + if (p->physical_id == port->physical_id && p->state == PORT_ENABLED) { + spin_unlock_irqrestore(&abe->lock, flags); + return 1; + } + } + + spin_unlock_irqrestore(&abe->lock, flags); + return 0; +} +EXPORT_SYMBOL(omap_abe_port_is_enabled); + +/* + * omap_abe_port_enable - enable ABE logical port + * + * @abe - ABE. + * @port - logical ABE port ID to be enabled. + */ +int omap_abe_port_enable(struct abe *abe, struct omap_abe_port *port) +{ + int ret = 0; + unsigned long flags; + + /* only enable the physical port iff it is disabled */ + pr_debug("port %s increment count %d\n", + lport_name[port->logical_id], port->users); + + spin_lock_irqsave(&abe->lock, flags); + if (port->users == 0 && port_get_num_users(abe, port) == 0) { + + /* enable the physical port */ + pr_debug("port %s phy port %d enabled\n", + lport_name[port->logical_id], port->physical_id); + abe_enable_data_transfer(port->physical_id); + } + + port->state = PORT_ENABLED; + port->users++; + spin_unlock_irqrestore(&abe->lock, flags); + return ret; +} +EXPORT_SYMBOL(omap_abe_port_enable); + +/* + * omap_abe_port_disable - disable ABE logical port + * + * @abe - ABE. + * @port - logical ABE port ID to be disabled. + */ +int omap_abe_port_disable(struct abe *abe, struct omap_abe_port *port) +{ + int ret = 0; + unsigned long flags; + + /* only disable the port iff no other users are using it */ + pr_debug("port %s decrement count %d\n", + lport_name[port->logical_id], port->users); + + spin_lock_irqsave(&abe->lock, flags); + if (port->users == 1 && port_get_num_users(abe, port) == 1) { + /* disable the physical port */ + pr_debug("port %s phy port %d disabled\n", + lport_name[port->logical_id], port->physical_id); + + abe_disable_data_transfer(port->physical_id); + } + + port->state = PORT_DISABLED; + port->users--; + spin_unlock_irqrestore(&abe->lock, flags); + return ret; +} +EXPORT_SYMBOL(omap_abe_port_disable); + +/* + * omap_abe_port_open - open ABE logical port + * + * @abe - ABE. + * @logical_id - logical ABE port ID to be opened. + */ +struct omap_abe_port *omap_abe_port_open(struct abe *abe, int logical_id) +{ + struct omap_abe_port *port; + unsigned long flags; + +#ifdef CONFIG_DEBUG_FS + char debug_fs_name[32]; +#endif + + if (logical_id < 0 || logical_id > OMAP_ABE_MAX_PORT_ID) + return NULL; + + if (port_is_open(abe, logical_id)) + return NULL; + + port = kzalloc(sizeof(struct omap_abe_port), GFP_KERNEL); + if (port == NULL) + return NULL; + + port->logical_id = logical_id; + port->physical_id = get_physical_id(logical_id); + port->state = PORT_DISABLED; + port->abe = abe; + + spin_lock_irqsave(&abe->lock, flags); + list_add(&port->list, &abe->ports); + spin_unlock_irqrestore(&abe->lock, flags); + port->physical_users = port_get_num_users(abe, port); + +#ifdef CONFIG_DEBUG_FS + sprintf(debug_fs_name, "%s_state", lport_name[logical_id]); + port->debugfs_lstate = debugfs_create_u32(debug_fs_name, 0644, + abe->debugfs_root, &port->state); + sprintf(debug_fs_name, "%s_phy", lport_name[logical_id]); + port->debugfs_lphy = debugfs_create_u32(debug_fs_name, 0644, + abe->debugfs_root, &port->physical_id); + sprintf(debug_fs_name, "%s_users", lport_name[logical_id]); + port->debugfs_lusers = debugfs_create_u32(debug_fs_name, 0644, + abe->debugfs_root, &port->users); +#endif + + pr_debug("opened port %s\n", lport_name[logical_id]); + return port; +} +EXPORT_SYMBOL(omap_abe_port_open); + +/* + * omap_abe_port_close - close ABE logical port + * + * @port - logical ABE port to be closed (and disabled). + */ +void omap_abe_port_close(struct abe *abe, struct omap_abe_port *port) +{ + unsigned long flags; + + /* disable the port */ + omap_abe_port_disable(abe, port); + + spin_lock_irqsave(&abe->lock, flags); + list_del(&port->list); + spin_unlock_irqrestore(&abe->lock, flags); + + pr_debug("closed port %s\n", lport_name[port->logical_id]); + kfree(port); +} +EXPORT_SYMBOL(omap_abe_port_close); + +static struct abe *omap_abe_port_mgr_init(void) +{ + struct abe *abe; + + abe = kzalloc(sizeof(struct abe), GFP_KERNEL); + if (abe == NULL) + return NULL; + + spin_lock_init(&abe->lock); + + INIT_LIST_HEAD(&abe->ports); + the_abe = abe; + +#ifdef CONFIG_DEBUG_FS + abe->debugfs_root = debugfs_create_dir("abe_port", NULL); + if (!abe->debugfs_root) { + pr_debug( "Failed to create port manager debugfs directory\n"); + } +#endif + return abe; +} + +static void omap_abe_port_mgr_free(struct abe *abe) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(abe->debugfs_root); +#endif + kfree(abe); + the_abe = NULL; +} + +struct abe *omap_abe_port_mgr_get(void) +{ + struct abe * abe; + + mutex_lock(&port_mgr_mutex); + + if (the_abe) + abe = the_abe; + else + abe = omap_abe_port_mgr_init(); + + users++; + mutex_unlock(&port_mgr_mutex); + return abe; +} +EXPORT_SYMBOL(omap_abe_port_mgr_get); + +void omap_abe_port_mgr_put(struct abe *abe) +{ + mutex_lock(&port_mgr_mutex); + + if (users == 0) + goto out; + + if (--users == 0) + omap_abe_port_mgr_free(abe); + +out: + mutex_unlock(&port_mgr_mutex); +} +EXPORT_SYMBOL(omap_abe_port_mgr_put); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/abe/port_mgr.h b/sound/soc/omap/abe/port_mgr.h new file mode 100644 index 0000000000000000000000000000000000000000..a65b0d37738c9af66dec1d7e9c6537e11324bfcd --- /dev/null +++ b/sound/soc/omap/abe/port_mgr.h @@ -0,0 +1,98 @@ +/* + * ABE Port manager + * + * 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_SOC_OMAP_PORT_MGR_H +#define __LINUX_SND_SOC_OMAP_PORT_MGR_H + +#include + +/* + * TODO: These structures, enums and port ID macros should be moved to the + * new public ABE API header. + */ + +/* Logical PORT IDs - Backend */ +#define OMAP_ABE_BE_PORT_DMIC0 0 +#define OMAP_ABE_BE_PORT_DMIC1 1 +#define OMAP_ABE_BE_PORT_DMIC2 2 +#define OMAP_ABE_BE_PORT_PDM_DL1 3 +#define OMAP_ABE_BE_PORT_PDM_DL2 4 +#define OMAP_ABE_BE_PORT_PDM_VIB 5 +#define OMAP_ABE_BE_PORT_PDM_UL1 6 +#define OMAP_ABE_BE_PORT_BT_VX_DL 7 +#define OMAP_ABE_BE_PORT_BT_VX_UL 8 +#define OMAP_ABE_BE_PORT_MM_EXT_UL 9 +#define OMAP_ABE_BE_PORT_MM_EXT_DL 10 + +/* Logical PORT IDs - Frontend */ +#define OMAP_ABE_FE_PORT_MM_DL1 11 +#define OMAP_ABE_FE_PORT_MM_UL1 12 +#define OMAP_ABE_FE_PORT_MM_UL2 13 +#define OMAP_ABE_FE_PORT_VX_DL 14 +#define OMAP_ABE_FE_PORT_VX_UL 15 +#define OMAP_ABE_FE_PORT_VIB 16 +#define OMAP_ABE_FE_PORT_TONES 17 + +#define OMAP_ABE_MAX_PORT_ID OMAP_ABE_FE_PORT_TONES + +/* ports can either be enabled or disabled */ +enum port_state { + PORT_DISABLED = 0, + PORT_ENABLED, +}; + +/* structure used for client port info */ +struct omap_abe_port { + + /* logical and physical port IDs that correspond this port */ + int logical_id; + int physical_id; + int physical_users; + + /* enabled or disabled */ + enum port_state state; + + /* logical port ref count */ + int users; + + struct list_head list; + struct abe *abe; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_lstate; + struct dentry *debugfs_lphy; + struct dentry *debugfs_lusers; +#endif +}; + +/* main ABE structure */ +struct abe { + + /* List of open ABE logical ports */ + struct list_head ports; + + /* spinlock */ + spinlock_t lock; + + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; +#endif +}; + +struct omap_abe_port *omap_abe_port_open(struct abe *abe, int logical_id); +void omap_abe_port_close(struct abe *abe, struct omap_abe_port *port); +int omap_abe_port_enable(struct abe *abe, struct omap_abe_port *port); +int omap_abe_port_disable(struct abe *abe, struct omap_abe_port *port); +int omap_abe_port_is_enabled(struct abe *abe, struct omap_abe_port *port); +struct abe *omap_abe_port_mgr_get(void); +void omap_abe_port_mgr_put(struct abe *abe); + +#endif /* __LINUX_SND_SOC_OMAP_PORT_MGR_H */ diff --git a/sound/soc/omap/omap-abe-dsp.c b/sound/soc/omap/omap-abe-dsp.c new file mode 100644 index 0000000000000000000000000000000000000000..13ae8e9b4caaca985b6964699ec5a9902d633a2c --- /dev/null +++ b/sound/soc/omap/omap-abe-dsp.c @@ -0,0 +1,2443 @@ +/* + * omap-abe-dsp.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * Copyright (C) 2010 Texas Instruments Inc. + * + * Authors: Liam Girdwood + * Misael Lopez Cruz + * Sebastien Guiriec + * + */ + +#define DEBUG + +#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 "omap-abe-dsp.h" +#include "omap-abe.h" +#include "abe/abe_main.h" +#include "abe/port_mgr.h" + +#warning need omap_device_set_rate +#define omap_device_set_rate(x, y, z) + +static const char *abe_memory_bank[5] = { + "dmem", + "cmem", + "smem", + "pmem", + "mpu" +}; + + +/* + * ABE loadable coefficients. + * The coefficient and their mixer configurations are loaded with the firmware + * blob duing probe(). + */ + +struct coeff_config { + char name[ABE_COEFF_NAME_SIZE]; + u32 count; + u32 coeff; + char texts[ABE_COEFF_NUM_TEXTS][ABE_COEFF_TEXT_SIZE]; +}; + +/* + * ABE Firmware Header. + * The ABE firmware blob has a header that describes each data section. This + * way we can store coefficients etc in the firmware. + */ +struct fw_header { + u32 magic; /* magic number */ + u32 crc; /* optional crc */ + u32 firmware_size; /* payload size */ + u32 coeff_size; /* payload size */ + u32 coeff_version; /* coefficent version */ + u32 firmware_version; /* min version of ABE firmware required */ + u32 num_equ; /* number of equalizers */ +}; + +/* + * ABE private data. + */ +struct abe_data { + struct omap4_abe_dsp_pdata *abe_pdata; + struct device *dev; + struct snd_soc_platform *platform; + struct delayed_work delayed_work; + struct mutex mutex; + struct mutex opp_mutex; + struct clk *clk; + void __iomem *io_base[5]; + int irq; + int opp; + int active; + + /* coefficients */ + struct fw_header hdr; + u32 *firmware; + s32 *equ[ABE_MAX_EQU]; + int equ_profile[ABE_MAX_EQU]; + struct soc_enum equalizer_enum[ABE_MAX_EQU]; + struct snd_kcontrol_new equalizer_control[ABE_MAX_EQU]; + struct coeff_config *equ_texts; + + /* DAPM mixer config - TODO: some of this can be replaced with HAL update */ + u32 widget_opp[ABE_NUM_DAPM_REG + 1]; + + u16 router[16]; + int loss_count; + + struct snd_pcm_substream *ping_pong_substream; + int first_irq; + + struct snd_pcm_substream *psubs; + +#ifdef CONFIG_DEBUG_FS + /* ABE runtime debug config */ + + /* its intended we can switch on/off individual debug items */ + u32 dbg_format1; /* TODO: match flag names here to debug format flags */ + u32 dbg_format2; + u32 dbg_format3; + + u32 dbg_buffer_bytes; + u32 dbg_circular; + u32 dbg_buffer_msecs; /* size of buffer in secs */ + u32 dbg_elem_bytes; + dma_addr_t dbg_buffer_addr; + wait_queue_head_t wait; + int dbg_reader_offset; + int dbg_dma_offset; + int dbg_complete; + struct dentry *debugfs_root; + struct dentry *debugfs_fmt1; + struct dentry *debugfs_fmt2; + struct dentry *debugfs_fmt3; + struct dentry *debugfs_size; + struct dentry *debugfs_data; + struct dentry *debugfs_circ; + struct dentry *debugfs_elem_bytes; + struct dentry *debugfs_opp_level; + char *dbg_buffer; + struct omap_pcm_dma_data *dma_data; + int dma_ch; + int dma_req; +#endif +}; + +static struct abe_data *the_abe; + +// TODO: map to the new version of HAL +static unsigned int abe_dsp_read(struct snd_soc_platform *platform, + unsigned int reg) +{ + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + + BUG_ON(reg > ABE_NUM_DAPM_REG); + return abe->widget_opp[reg]; +} + +static int abe_dsp_write(struct snd_soc_platform *platform, unsigned int reg, + unsigned int val) +{ + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + + BUG_ON(reg > ABE_NUM_DAPM_REG); + abe->widget_opp[reg] = val; + return 0; +} + +static void abe_irq_pingpong_subroutine(u32 *data) +{ + u32 dst, n_bytes; + + abe_read_next_ping_pong_buffer(MM_DL_PORT, &dst, &n_bytes); + abe_set_ping_pong_buffer(MM_DL_PORT, n_bytes); + + /* Do not call ALSA function for first IRQ */ + if (the_abe->first_irq) { + the_abe->first_irq = 0; + } else { + if (the_abe->ping_pong_substream) + snd_pcm_period_elapsed(the_abe->ping_pong_substream); + } +} + +static irqreturn_t abe_irq_handler(int irq, void *dev_id) +{ + struct abe_data *abe = dev_id; + + /* TODO: handle underruns/overruns/errors */ + pm_runtime_get_sync(abe->dev); + abe_clear_irq(); // TODO: why is IRQ not cleared after processing ? + abe_irq_processing(); + pm_runtime_put_sync(abe->dev); + return IRQ_HANDLED; +} + +// TODO: these should really be called internally since we will know the McPDM state +void abe_dsp_pm_get(void) +{ + pm_runtime_get_sync(the_abe->dev); +} +EXPORT_SYMBOL_GPL(abe_dsp_pm_get); + +void abe_dsp_pm_put(void) +{ + pm_runtime_put_sync(the_abe->dev); +} +EXPORT_SYMBOL_GPL(abe_dsp_pm_put); + +void abe_dsp_shutdown(void) +{ + if (!the_abe->active && !abe_check_activity()) { + abe_set_opp_processing(ABE_OPP25); + the_abe->opp = 25; + abe_stop_event_generator(); + udelay(250); + omap_device_set_rate(the_abe->dev, the_abe->dev, 0); + } +} +EXPORT_SYMBOL_GPL(abe_dsp_shutdown); + +/* + * These TLV settings will need fine tuned for each individual control + */ + +/* Media DL1 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(mm_dl1_tlv, -12000, 100, 3000); + +/* Media DL1 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(tones_dl1_tlv, -12000, 100, 3000); + +/* Media DL1 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(voice_dl1_tlv, -12000, 100, 3000); + +/* Media DL1 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(capture_dl1_tlv, -12000, 100, 3000); + +/* Media DL2 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(mm_dl2_tlv, -12000, 100, 3000); + +/* Media DL2 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(tones_dl2_tlv, -12000, 100, 3000); + +/* Media DL2 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(voice_dl2_tlv, -12000, 100, 3000); + +/* Media DL2 volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(capture_dl2_tlv, -12000, 100, 3000); + +/* SDT volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(sdt_ul_tlv, -12000, 100, 3000); + +/* SDT volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(sdt_dl_tlv, -12000, 100, 3000); + +/* AUDUL volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(audul_mm_tlv, -12000, 100, 3000); + +/* AUDUL volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(audul_tones_tlv, -12000, 100, 3000); + +/* AUDUL volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(audul_vx_ul_tlv, -12000, 100, 3000); + +/* AUDUL volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(audul_vx_dl_tlv, -12000, 100, 3000); + +/* VXREC volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(vxrec_mm_dl_tlv, -12000, 100, 3000); + +/* VXREC volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(vxrec_tones_tlv, -12000, 100, 3000); + +/* VXREC volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(vxrec_vx_dl_tlv, -12000, 100, 3000); + +/* VXREC volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(vxrec_vx_ul_tlv, -12000, 100, 3000); + +/* DMIC volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(dmic_tlv, -12000, 100, 3000); + +/* BT UL volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(btul_tlv, -12000, 100, 3000); + +/* AMIC volume control from -120 to 30 dB in 1 dB steps */ +static DECLARE_TLV_DB_SCALE(amic_tlv, -12000, 100, 3000); + +//TODO: we have to use the shift value atm to represent register id due to current HAL +static int dl1_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + // TODO: optimise all of these to call HAL abe_enable_gain(mixer, enable) + if (ucontrol->value.integer.value[0]) { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + abe_enable_gain(MIXDL1, mc->reg); + } else { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + abe_disable_gain(MIXDL1, mc->reg); + } + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int dl2_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + if (ucontrol->value.integer.value[0]) { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + abe_enable_gain(MIXDL2, mc->reg); + } else { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + abe_disable_gain(MIXDL2, mc->reg); + } + + pm_runtime_put_sync(the_abe->dev); + return 1; +} + +static int audio_ul_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + if (ucontrol->value.integer.value[0]) { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + abe_enable_gain(MIXAUDUL, mc->reg); + } else { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + abe_disable_gain(MIXAUDUL, mc->reg); + } + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int vxrec_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + if (ucontrol->value.integer.value[0]) { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + abe_enable_gain(MIXVXREC, mc->reg); + } else { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + abe_disable_gain(MIXVXREC, mc->reg); + } + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int sdt_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + if (ucontrol->value.integer.value[0]) { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + abe_enable_gain(MIXSDT, mc->reg); + } else { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + abe_disable_gain(MIXSDT, mc->reg); + } + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int abe_get_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + ucontrol->value.integer.value[0] = the_abe->widget_opp[mc->shift]; + return 0; +} + +/* router IDs that match our mixer strings */ +static const abe_router_t router[] = { + ZERO_labelID, /* strangely this is not 0 */ + DMIC1_L_labelID, DMIC1_R_labelID, + DMIC2_L_labelID, DMIC2_R_labelID, + DMIC3_L_labelID, DMIC3_R_labelID, + BT_UL_L_labelID, BT_UL_R_labelID, + MM_EXT_IN_L_labelID, MM_EXT_IN_R_labelID, + AMIC_L_labelID, AMIC_R_labelID, + VX_REC_L_labelID, VX_REC_R_labelID, +}; + +static int ul_mux_put_route(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int mux = ucontrol->value.enumerated.item[0]; + int reg = e->reg - ABE_MUX(0); + + pm_runtime_get_sync(the_abe->dev); + + if (mux > ABE_ROUTES_UL) + return 0; + + // TODO: get all this via firmware + if (reg < 8) { + /* 0 .. 9 = MM_UL */ + the_abe->router[reg] = router[mux]; + } else if (reg < 12) { + /* 10 .. 11 = MM_UL2 */ + /* 12 .. 13 = VX_UL */ + the_abe->router[reg + 2] = router[mux]; + } + + /* 2nd arg here is unused */ + abe_set_router_configuration(UPROUTE, 0, (u32 *)the_abe->router); + + if (router[mux] != ZERO_labelID) + the_abe->widget_opp[e->reg] = e->shift_l; + else + the_abe->widget_opp[e->reg] = 0; + + snd_soc_dapm_mux_update_power(widget, kcontrol, 1, mux, e); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int ul_mux_get_route(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = + (struct soc_enum *)kcontrol->private_value; + int reg = e->reg - ABE_MUX(0), i, rval = 0; + + // TODO: get all this via firmware + if (reg < 8) { + /* 0 .. 9 = MM_UL */ + rval = the_abe->router[reg]; + } else if (reg < 12) { + /* 10 .. 11 = MM_UL2 */ + /* 12 .. 13 = VX_UL */ + rval = the_abe->router[reg + 2]; + } + + for (i = 0; i < ARRAY_SIZE(router); i++) { + if (router[i] == rval) { + ucontrol->value.integer.value[0] = i; + return 0; + } + } + + return 1; +} + + +static int abe_put_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + if (ucontrol->value.integer.value[0]) { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + the_abe->widget_opp[mc->shift] = ucontrol->value.integer.value[0]; + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + + +static int volume_put_sdt_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + + abe_write_mixer(MIXSDT, abe_val_to_gain(ucontrol->value.integer.value[0]), + RAMP_0MS, mc->reg); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int volume_put_audul_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + abe_write_mixer(MIXAUDUL, abe_val_to_gain(ucontrol->value.integer.value[0]), + RAMP_0MS, mc->reg); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int volume_put_vxrec_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + abe_write_mixer(MIXVXREC, abe_val_to_gain(ucontrol->value.integer.value[0]), + RAMP_0MS, mc->reg); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int volume_put_dl1_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + abe_write_mixer(MIXDL1, abe_val_to_gain(ucontrol->value.integer.value[0]), + RAMP_0MS, mc->reg); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int volume_put_dl2_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + abe_write_mixer(MIXDL2, abe_val_to_gain(ucontrol->value.integer.value[0]), + RAMP_0MS, mc->reg); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int volume_put_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pm_runtime_get_sync(the_abe->dev); + abe_write_gain(mc->reg, + abe_val_to_gain(ucontrol->value.integer.value[0]), + RAMP_20MS, mc->shift); + abe_write_gain(mc->reg, + -12000 + (ucontrol->value.integer.value[1] * 100), + RAMP_20MS, mc->rshift); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +static int volume_get_dl1_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 val; + + pm_runtime_get_sync(the_abe->dev); + abe_read_mixer(MIXDL1, &val, mc->reg); + ucontrol->value.integer.value[0] = abe_gain_to_val(val); + pm_runtime_put_sync(the_abe->dev); + + return 0; +} + +static int volume_get_dl2_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 val; + + pm_runtime_get_sync(the_abe->dev); + abe_read_mixer(MIXDL2, &val, mc->reg); + ucontrol->value.integer.value[0] = abe_gain_to_val(val); + pm_runtime_put_sync(the_abe->dev); + + return 0; +} + +static int volume_get_audul_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 val; + + pm_runtime_get_sync(the_abe->dev); + abe_read_mixer(MIXAUDUL, &val, mc->reg); + ucontrol->value.integer.value[0] = abe_gain_to_val(val); + pm_runtime_put_sync(the_abe->dev); + + return 0; +} + +static int volume_get_vxrec_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 val; + + pm_runtime_get_sync(the_abe->dev); + abe_read_mixer(MIXVXREC, &val, mc->reg); + ucontrol->value.integer.value[0] = abe_gain_to_val(val); + pm_runtime_put_sync(the_abe->dev); + + return 0; +} + +static int volume_get_sdt_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 val; + + pm_runtime_get_sync(the_abe->dev); + abe_read_mixer(MIXSDT, &val, mc->reg); + ucontrol->value.integer.value[0] = abe_gain_to_val(val); + pm_runtime_put_sync(the_abe->dev); + + return 0; +} + +static int volume_get_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 val; + + pm_runtime_get_sync(the_abe->dev); + abe_read_gain(mc->reg, &val, mc->shift); + ucontrol->value.integer.value[0] = abe_gain_to_val(val); + abe_read_gain(mc->reg, &val, mc->rshift); + ucontrol->value.integer.value[1] = abe_gain_to_val(val); + pm_runtime_put_sync(the_abe->dev); + + return 0; +} + +static int abe_get_equalizer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *eqc = (struct soc_enum *)kcontrol->private_value; + + ucontrol->value.integer.value[0] = the_abe->equ_profile[eqc->reg]; + return 0; +} + +static int abe_put_equalizer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *eqc = (struct soc_enum *)kcontrol->private_value; + u16 val = ucontrol->value.enumerated.item[0]; + abe_equ_t equ_params; + int len; + + if (eqc->reg >= the_abe->hdr.num_equ) + return -EINVAL; + + if (val >= the_abe->equ_texts[eqc->reg].count) + return -EINVAL; + + len = the_abe->equ_texts[eqc->reg].coeff; + equ_params.equ_length = len; + memcpy(equ_params.coef.type1, the_abe->equ[eqc->reg] + val * len, + len * sizeof(u32)); + the_abe->equ_profile[eqc->reg] = val; + + pm_runtime_get_sync(the_abe->dev); + abe_write_equalizer(eqc->reg + 1, &equ_params); + pm_runtime_put_sync(the_abe->dev); + + return 1; +} + +int snd_soc_info_enum_ext1(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = e->max; + + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; + strcpy(uinfo->value.enumerated.name, + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); + + return 0; +} + +static const char *route_ul_texts[] = { + "None", "DMic0L", "DMic0R", "DMic1L", "DMic1R", "DMic2L", "DMic2R", + "BT Left", "BT Right", "MMExt Left", "MMExt Right", "AMic0", "AMic1", + "VX Left", "VX Right" +}; + +/* ROUTE_UL Mux table */ +static const struct soc_enum abe_enum[] = { + SOC_ENUM_SINGLE(MUX_MM_UL10, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL11, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL12, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL13, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL14, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL15, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL16, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL17, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL20, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_MM_UL21, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_VX_UL0, 0, 15, route_ul_texts), + SOC_ENUM_SINGLE(MUX_VX_UL1, 0, 15, route_ul_texts), +}; + +static const struct snd_kcontrol_new mm_ul00_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[0], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul01_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[1], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul02_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[2], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul03_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[3], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul04_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[4], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul05_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[5], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul06_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[6], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul07_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[7], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul10_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[8], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_ul11_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[9], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_vx0_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[10], + ul_mux_get_route, ul_mux_put_route); + +static const struct snd_kcontrol_new mm_vx1_control = + SOC_DAPM_ENUM_EXT("Route", abe_enum[11], + ul_mux_get_route, ul_mux_put_route); + +/* DL1 mixer paths */ +static const struct snd_kcontrol_new dl1_mixer_controls[] = { + SOC_SINGLE_EXT("Tones", MIX_DL1_INPUT_TONES, MIX_DL1_TONES, 1, 0, + abe_get_mixer, dl1_put_mixer), + SOC_SINGLE_EXT("Voice", MIX_DL1_INPUT_VX_DL, MIX_DL1_VOICE, 1, 0, + abe_get_mixer, dl1_put_mixer), + SOC_SINGLE_EXT("Capture", MIX_DL1_INPUT_MM_UL2, MIX_DL1_CAPTURE, 1, 0, + abe_get_mixer, dl1_put_mixer), + SOC_SINGLE_EXT("Multimedia", MIX_DL1_INPUT_MM_DL, MIX_DL1_MEDIA, 1, 0, + abe_get_mixer, dl1_put_mixer), +}; + +/* DL2 mixer paths */ +static const struct snd_kcontrol_new dl2_mixer_controls[] = { + SOC_SINGLE_EXT("Tones", MIX_DL2_INPUT_TONES, MIX_DL2_TONES, 1, 0, + abe_get_mixer, dl2_put_mixer), + SOC_SINGLE_EXT("Voice", MIX_DL2_INPUT_VX_DL, MIX_DL2_VOICE, 1, 0, + abe_get_mixer, dl2_put_mixer), + SOC_SINGLE_EXT("Capture", MIX_DL2_INPUT_MM_UL2, MIX_DL2_CAPTURE, 1, 0, + abe_get_mixer, dl2_put_mixer), + SOC_SINGLE_EXT("Multimedia", MIX_DL2_INPUT_MM_DL, MIX_DL2_MEDIA, 1, 0, + abe_get_mixer, dl2_put_mixer), +}; + +/* AUDUL ("Voice Capture Mixer") mixer paths */ +static const struct snd_kcontrol_new audio_ul_mixer_controls[] = { + SOC_SINGLE_EXT("Tones Playback", MIX_AUDUL_INPUT_TONES, MIX_AUDUL_TONES, 1, 0, + abe_get_mixer, audio_ul_put_mixer), + SOC_SINGLE_EXT("Media Playback", MIX_AUDUL_INPUT_MM_DL, MIX_AUDUL_MEDIA, 1, 0, + abe_get_mixer, audio_ul_put_mixer), + SOC_SINGLE_EXT("Capture", MIX_AUDUL_INPUT_UPLINK, MIX_AUDUL_CAPTURE, 1, 0, + abe_get_mixer, audio_ul_put_mixer), +}; + +/* VXREC ("Capture Mixer") mixer paths */ +static const struct snd_kcontrol_new vx_rec_mixer_controls[] = { + SOC_SINGLE_EXT("Tones", MIX_VXREC_INPUT_TONES, MIX_VXREC_TONES, 1, 0, + abe_get_mixer, vxrec_put_mixer), + SOC_SINGLE_EXT("Voice Playback", MIX_VXREC_INPUT_VX_DL, + MIX_VXREC_VOICE_PLAYBACK, 1, 0, abe_get_mixer, vxrec_put_mixer), + SOC_SINGLE_EXT("Voice Capture", MIX_VXREC_INPUT_VX_UL, + MIX_VXREC_VOICE_CAPTURE, 1, 0, abe_get_mixer, vxrec_put_mixer), + SOC_SINGLE_EXT("Media Playback", MIX_VXREC_INPUT_MM_DL, + MIX_VXREC_MEDIA, 1, 0, abe_get_mixer, vxrec_put_mixer), +}; + +/* SDT ("Sidetone Mixer") mixer paths */ +static const struct snd_kcontrol_new sdt_mixer_controls[] = { + SOC_SINGLE_EXT("Capture", MIX_SDT_INPUT_UP_MIXER, MIX_SDT_CAPTURE, 1, 0, + abe_get_mixer, sdt_put_mixer), + SOC_SINGLE_EXT("Playback", MIX_SDT_INPUT_DL1_MIXER, MIX_SDT_PLAYBACK, 1, 0, + abe_get_mixer, sdt_put_mixer), +}; + +/* Virtual PDM_DL Switch */ +static const struct snd_kcontrol_new pdm_dl1_switch_controls = + SOC_SINGLE_EXT("Switch", ABE_VIRTUAL_SWITCH, MIX_SWITCH_PDM_DL, 1, 0, + abe_get_mixer, abe_put_switch); + +/* Virtual BT_VX_DL Switch */ +static const struct snd_kcontrol_new bt_vx_dl_switch_controls = + SOC_SINGLE_EXT("Switch", ABE_VIRTUAL_SWITCH, MIX_SWITCH_BT_VX_DL, 1, 0, + abe_get_mixer, abe_put_switch); + +/* Virtual MM_EXT_DL Switch */ +static const struct snd_kcontrol_new mm_ext_dl_switch_controls = + SOC_SINGLE_EXT("Switch", ABE_VIRTUAL_SWITCH, MIX_SWITCH_MM_EXT_DL, 1, 0, + abe_get_mixer, abe_put_switch); + +static const struct snd_kcontrol_new abe_controls[] = { + /* DL1 mixer gains */ + SOC_SINGLE_EXT_TLV("DL1 Media Playback Volume", + MIX_DL1_INPUT_MM_DL, 0, 149, 0, + volume_get_dl1_mixer, volume_put_dl1_mixer, mm_dl1_tlv), + SOC_SINGLE_EXT_TLV("DL1 Tones Playback Volume", + MIX_DL1_INPUT_TONES, 0, 149, 0, + volume_get_dl1_mixer, volume_put_dl1_mixer, tones_dl1_tlv), + SOC_SINGLE_EXT_TLV("DL1 Voice Playback Volume", + MIX_DL1_INPUT_VX_DL, 0, 149, 0, + volume_get_dl1_mixer, volume_put_dl1_mixer, voice_dl1_tlv), + SOC_SINGLE_EXT_TLV("DL1 Capture Playback Volume", + MIX_DL1_INPUT_MM_UL2, 0, 149, 0, + volume_get_dl1_mixer, volume_put_dl1_mixer, capture_dl1_tlv), + + /* DL2 mixer gains */ + SOC_SINGLE_EXT_TLV("DL2 Media Playback Volume", + MIX_DL2_INPUT_MM_DL, 0, 149, 0, + volume_get_dl2_mixer, volume_put_dl2_mixer, mm_dl2_tlv), + SOC_SINGLE_EXT_TLV("DL2 Tones Playback Volume", + MIX_DL2_INPUT_TONES, 0, 149, 0, + volume_get_dl2_mixer, volume_put_dl2_mixer, tones_dl2_tlv), + SOC_SINGLE_EXT_TLV("DL2 Voice Playback Volume", + MIX_DL2_INPUT_VX_DL, 0, 149, 0, + volume_get_dl2_mixer, volume_put_dl2_mixer, voice_dl2_tlv), + SOC_SINGLE_EXT_TLV("DL2 Capture Playback Volume", + MIX_DL2_INPUT_MM_UL2, 0, 149, 0, + volume_get_dl2_mixer, volume_put_dl2_mixer, capture_dl2_tlv), + + /* VXREC mixer gains */ + SOC_SINGLE_EXT_TLV("VXREC Media Volume", + MIX_VXREC_INPUT_MM_DL, 0, 149, 0, + volume_get_vxrec_mixer, volume_put_vxrec_mixer, vxrec_mm_dl_tlv), + SOC_SINGLE_EXT_TLV("VXREC Tones Volume", + MIX_VXREC_INPUT_TONES, 0, 149, 0, + volume_get_vxrec_mixer, volume_put_vxrec_mixer, vxrec_tones_tlv), + SOC_SINGLE_EXT_TLV("VXREC Voice DL Volume", + MIX_VXREC_INPUT_VX_UL, 0, 149, 0, + volume_get_vxrec_mixer, volume_put_vxrec_mixer, vxrec_vx_dl_tlv), + SOC_SINGLE_EXT_TLV("VXREC Voice UL Volume", + MIX_VXREC_INPUT_VX_DL, 0, 149, 0, + volume_get_vxrec_mixer, volume_put_vxrec_mixer, vxrec_vx_ul_tlv), + + /* AUDUL mixer gains */ + SOC_SINGLE_EXT_TLV("AUDUL Media Volume", + MIX_AUDUL_INPUT_MM_DL, 0, 149, 0, + volume_get_audul_mixer, volume_put_audul_mixer, audul_mm_tlv), + SOC_SINGLE_EXT_TLV("AUDUL Tones Volume", + MIX_AUDUL_INPUT_TONES, 0, 149, 0, + volume_get_audul_mixer, volume_put_audul_mixer, audul_tones_tlv), + SOC_SINGLE_EXT_TLV("AUDUL Voice UL Volume", + MIX_AUDUL_INPUT_UPLINK, 0, 149, 0, + volume_get_audul_mixer, volume_put_audul_mixer, audul_vx_ul_tlv), + SOC_SINGLE_EXT_TLV("AUDUL Voice DL Volume", + MIX_AUDUL_INPUT_VX_DL, 0, 149, 0, + volume_get_audul_mixer, volume_put_audul_mixer, audul_vx_dl_tlv), + + /* SDT mixer gains */ + SOC_SINGLE_EXT_TLV("SDT UL Volume", + MIX_SDT_INPUT_UP_MIXER, 0, 149, 0, + volume_get_sdt_mixer, volume_put_sdt_mixer, sdt_ul_tlv), + SOC_SINGLE_EXT_TLV("SDT DL Volume", + MIX_SDT_INPUT_DL1_MIXER, 0, 149, 0, + volume_get_sdt_mixer, volume_put_sdt_mixer, sdt_dl_tlv), + + /* DMIC gains */ + SOC_DOUBLE_EXT_TLV("DMIC1 UL Volume", + GAINS_DMIC1, GAIN_LEFT_OFFSET, GAIN_RIGHT_OFFSET, 149, 0, + volume_get_gain, volume_put_gain, dmic_tlv), + + SOC_DOUBLE_EXT_TLV("DMIC2 UL Volume", + GAINS_DMIC2, GAIN_LEFT_OFFSET, GAIN_RIGHT_OFFSET, 149, 0, + volume_get_gain, volume_put_gain, dmic_tlv), + + SOC_DOUBLE_EXT_TLV("DMIC3 UL Volume", + GAINS_DMIC3, GAIN_LEFT_OFFSET, GAIN_RIGHT_OFFSET, 149, 0, + volume_get_gain, volume_put_gain, dmic_tlv), + + SOC_DOUBLE_EXT_TLV("AMIC UL Volume", + GAINS_AMIC, GAIN_LEFT_OFFSET, GAIN_RIGHT_OFFSET, 149, 0, + volume_get_gain, volume_put_gain, amic_tlv), + + SOC_DOUBLE_EXT_TLV("BT UL Volume", + GAINS_BTUL, GAIN_LEFT_OFFSET, GAIN_RIGHT_OFFSET, 149, 0, + volume_get_gain, volume_put_gain, btul_tlv), +}; + +static const struct snd_soc_dapm_widget abe_dapm_widgets[] = { + + /* Frontend AIFs */ + SND_SOC_DAPM_AIF_IN("TONES_DL", "Tones Playback", 0, + W_AIF_TONES_DL, ABE_OPP_25, 0), + SND_SOC_DAPM_AIF_IN("VX_DL", "Voice Playback", 0, + W_AIF_VX_DL, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_OUT("VX_UL", "Voice Capture", 0, + W_AIF_VX_UL, ABE_OPP_50, 0), + /* the MM_UL mapping is intentional */ + SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, + W_AIF_MM_UL1, ABE_OPP_100, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, + W_AIF_MM_UL2, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_IN("MM_DL", " MultiMedia1 Playback", 0, + W_AIF_MM_DL, ABE_OPP_25, 0), + SND_SOC_DAPM_AIF_IN("MM_DL_LP", " MultiMedia1 LP Playback", 0, + W_AIF_MM_DL_LP, ABE_OPP_25, 0), + SND_SOC_DAPM_AIF_IN("VIB_DL", "Vibra Playback", 0, + W_AIF_VIB_DL, ABE_OPP_100, 0), + SND_SOC_DAPM_AIF_IN("MODEM_DL", "MODEM Playback", 0, + W_AIF_MODEM_DL, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_OUT("MODEM_UL", "MODEM Capture", 0, + W_AIF_MODEM_UL, ABE_OPP_50, 0), + + /* Backend DAIs */ + SND_SOC_DAPM_AIF_IN("PDM_UL1", "Analog Capture", 0, + W_AIF_PDM_UL1, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_OUT("PDM_DL1", "HS Playback", 0, + W_AIF_PDM_DL1, ABE_OPP_25, 0), + SND_SOC_DAPM_AIF_OUT("PDM_DL2", "HF Playback", 0, + W_AIF_PDM_DL2, ABE_OPP_100, 0), + SND_SOC_DAPM_AIF_OUT("PDM_VIB", "Vibra Playback", 0, + W_AIF_PDM_VIB, ABE_OPP_100, 0), + SND_SOC_DAPM_AIF_IN("BT_VX_UL", "BT Capture", 0, + W_AIF_BT_VX_UL, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_OUT("BT_VX_DL", "BT Playback", 0, + W_AIF_BT_VX_DL, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_IN("MM_EXT_UL", "FM Capture", 0, + W_AIF_MM_EXT_UL, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_OUT("MM_EXT_DL", "FM Playback", 0, + W_AIF_MM_EXT_DL, ABE_OPP_25, 0), + SND_SOC_DAPM_AIF_IN("DMIC0", "DMIC0 Capture", 0, + W_AIF_DMIC0, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_IN("DMIC1", "DMIC1 Capture", 0, + W_AIF_DMIC1, ABE_OPP_50, 0), + SND_SOC_DAPM_AIF_IN("DMIC2", "DMIC2 Capture", 0, + W_AIF_DMIC2, ABE_OPP_50, 0), + + /* ROUTE_UL Capture Muxes */ + SND_SOC_DAPM_MUX("MUX_UL00", + W_MUX_UL00, ABE_OPP_50, 0, &mm_ul00_control), + SND_SOC_DAPM_MUX("MUX_UL01", + W_MUX_UL01, ABE_OPP_50, 0, &mm_ul01_control), + SND_SOC_DAPM_MUX("MUX_UL02", + W_MUX_UL02, ABE_OPP_50, 0, &mm_ul02_control), + SND_SOC_DAPM_MUX("MUX_UL03", + W_MUX_UL03, ABE_OPP_50, 0, &mm_ul03_control), + SND_SOC_DAPM_MUX("MUX_UL04", + W_MUX_UL04, ABE_OPP_50, 0, &mm_ul04_control), + SND_SOC_DAPM_MUX("MUX_UL05", + W_MUX_UL05, ABE_OPP_50, 0, &mm_ul05_control), + SND_SOC_DAPM_MUX("MUX_UL06", + W_MUX_UL06, ABE_OPP_50, 0, &mm_ul06_control), + SND_SOC_DAPM_MUX("MUX_UL07", + W_MUX_UL07, ABE_OPP_50, 0, &mm_ul07_control), + SND_SOC_DAPM_MUX("MUX_UL10", + W_MUX_UL10, ABE_OPP_50, 0, &mm_ul10_control), + SND_SOC_DAPM_MUX("MUX_UL11", + W_MUX_UL11, ABE_OPP_50, 0, &mm_ul11_control), + SND_SOC_DAPM_MUX("MUX_VX0", + W_MUX_VX00, ABE_OPP_50, 0, &mm_vx0_control), + SND_SOC_DAPM_MUX("MUX_VX1", + W_MUX_VX01, ABE_OPP_50, 0, &mm_vx1_control), + + /* DL1 & DL2 Playback Mixers */ + SND_SOC_DAPM_MIXER("DL1 Mixer", + W_MIXER_DL1, ABE_OPP_25, 0, dl1_mixer_controls, + ARRAY_SIZE(dl1_mixer_controls)), + SND_SOC_DAPM_MIXER("DL2 Mixer", + W_MIXER_DL2, ABE_OPP_100, 0, dl2_mixer_controls, + ARRAY_SIZE(dl2_mixer_controls)), + + /* DL1 Mixer Input volumes ?????*/ + SND_SOC_DAPM_PGA("DL1 Media Volume", + W_VOLUME_DL1, 0, 0, NULL, 0), + + /* AUDIO_UL_MIXER */ + SND_SOC_DAPM_MIXER("Voice Capture Mixer", + W_MIXER_AUDIO_UL, ABE_OPP_50, 0, audio_ul_mixer_controls, + ARRAY_SIZE(audio_ul_mixer_controls)), + + /* VX_REC_MIXER */ + SND_SOC_DAPM_MIXER("Capture Mixer", + W_MIXER_VX_REC, ABE_OPP_50, 0, vx_rec_mixer_controls, + ARRAY_SIZE(vx_rec_mixer_controls)), + + /* SDT_MIXER - TODO: shoult this not be OPP25 ??? */ + SND_SOC_DAPM_MIXER("Sidetone Mixer", + W_MIXER_SDT, ABE_OPP_25, 0, sdt_mixer_controls, + ARRAY_SIZE(sdt_mixer_controls)), + + /* + * The Following three are virtual switches to select the output port + * after DL1 Gain. + */ + + /* Virtual PDM_DL1 Switch */ + SND_SOC_DAPM_MIXER("DL1 PDM", + W_VSWITCH_DL1_PDM, ABE_OPP_25, 0, &pdm_dl1_switch_controls, 1), + + /* Virtual BT_VX_DL Switch */ + SND_SOC_DAPM_MIXER("DL1 BT_VX", + W_VSWITCH_DL1_BT_VX, ABE_OPP_50, 0, &bt_vx_dl_switch_controls, 1), + + /* Virtual MM_EXT_DL Switch TODO: confrm OPP level here */ + SND_SOC_DAPM_MIXER("DL1 MM_EXT", + W_VSWITCH_DL1_MM_EXT, ABE_OPP_50, 0, &mm_ext_dl_switch_controls, 1), + + /* Virtuals to join our capture sources */ + SND_SOC_DAPM_MIXER("Sidetone Capture VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Voice Capture VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DL1 Capture VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DL2 Capture VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Join our MM_DL and MM_DL_LP playback */ + SND_SOC_DAPM_MIXER("MM_DL VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Virtual MODEM and VX_UL mixer */ + SND_SOC_DAPM_MIXER("VX UL VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("VX DL VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Virtual Pins to force backends ON atm */ + SND_SOC_DAPM_OUTPUT("BE_OUT"), + SND_SOC_DAPM_INPUT("BE_IN"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* MUX_UL00 - ROUTE_UL - Chan 0 */ + {"MUX_UL00", "DMic0L", "DMIC0"}, + {"MUX_UL00", "DMic0R", "DMIC0"}, + {"MUX_UL00", "DMic1L", "DMIC1"}, + {"MUX_UL00", "DMic1R", "DMIC1"}, + {"MUX_UL00", "DMic2L", "DMIC2"}, + {"MUX_UL00", "DMic2R", "DMIC2"}, + {"MUX_UL00", "BT Left", "BT_VX_UL"}, + {"MUX_UL00", "BT Right", "BT_VX_UL"}, + {"MUX_UL00", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL00", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL00", "AMic0", "PDM_UL1"}, + {"MUX_UL00", "AMic1", "PDM_UL1"}, + {"MUX_UL00", "VX Left", "Capture Mixer"}, + {"MUX_UL00", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL00"}, + + /* MUX_UL01 - ROUTE_UL - Chan 1 */ + {"MUX_UL01", "DMic0L", "DMIC0"}, + {"MUX_UL01", "DMic0R", "DMIC0"}, + {"MUX_UL01", "DMic1L", "DMIC1"}, + {"MUX_UL01", "DMic1R", "DMIC1"}, + {"MUX_UL01", "DMic2L", "DMIC2"}, + {"MUX_UL01", "DMic2R", "DMIC2"}, + {"MUX_UL01", "BT Left", "BT_VX_UL"}, + {"MUX_UL01", "BT Right", "BT_VX_UL"}, + {"MUX_UL01", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL01", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL01", "AMic0", "PDM_UL1"}, + {"MUX_UL01", "AMic1", "PDM_UL1"}, + {"MUX_UL01", "VX Left", "Capture Mixer"}, + {"MUX_UL01", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL01"}, + + /* MUX_UL02 - ROUTE_UL - Chan 2 */ + {"MUX_UL02", "DMic0L", "DMIC0"}, + {"MUX_UL02", "DMic0R", "DMIC0"}, + {"MUX_UL02", "DMic1L", "DMIC1"}, + {"MUX_UL02", "DMic1R", "DMIC1"}, + {"MUX_UL02", "DMic2L", "DMIC2"}, + {"MUX_UL02", "DMic2R", "DMIC2"}, + {"MUX_UL02", "BT Left", "BT_VX_UL"}, + {"MUX_UL02", "BT Right", "BT_VX_UL"}, + {"MUX_UL02", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL02", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL02", "AMic0", "PDM_UL1"}, + {"MUX_UL02", "AMic1", "PDM_UL1"}, + {"MUX_UL02", "VX Left", "Capture Mixer"}, + {"MUX_UL02", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL02"}, + + /* MUX_UL03 - ROUTE_UL - Chan 3 */ + {"MUX_UL03", "DMic0L", "DMIC0"}, + {"MUX_UL03", "DMic0R", "DMIC0"}, + {"MUX_UL03", "DMic1L", "DMIC1"}, + {"MUX_UL03", "DMic1R", "DMIC1"}, + {"MUX_UL03", "DMic2L", "DMIC2"}, + {"MUX_UL03", "DMic2R", "DMIC2"}, + {"MUX_UL03", "BT Left", "BT_VX_UL"}, + {"MUX_UL03", "BT Right", "BT_VX_UL"}, + {"MUX_UL03", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL03", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL03", "AMic0", "PDM_UL1"}, + {"MUX_UL03", "AMic1", "PDM_UL1"}, + {"MUX_UL03", "VX Left", "Capture Mixer"}, + {"MUX_UL03", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL03"}, + + /* MUX_UL04 - ROUTE_UL - Chan 4 */ + {"MUX_UL04", "DMic0L", "DMIC0"}, + {"MUX_UL04", "DMic0R", "DMIC0"}, + {"MUX_UL04", "DMic1L", "DMIC1"}, + {"MUX_UL04", "DMic1R", "DMIC1"}, + {"MUX_UL04", "DMic2L", "DMIC2"}, + {"MUX_UL04", "DMic2R", "DMIC2"}, + {"MUX_UL04", "BT Left", "BT_VX_UL"}, + {"MUX_UL04", "BT Right", "BT_VX_UL"}, + {"MUX_UL04", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL04", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL04", "AMic0", "PDM_UL1"}, + {"MUX_UL04", "AMic1", "PDM_UL1"}, + {"MUX_UL04", "VX Left", "Capture Mixer"}, + {"MUX_UL04", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL04"}, + + /* MUX_UL05 - ROUTE_UL - Chan 5 */ + {"MUX_UL05", "DMic0L", "DMIC0"}, + {"MUX_UL05", "DMic0R", "DMIC0"}, + {"MUX_UL05", "DMic1L", "DMIC1"}, + {"MUX_UL05", "DMic1R", "DMIC1"}, + {"MUX_UL05", "DMic2L", "DMIC2"}, + {"MUX_UL05", "DMic2R", "DMIC2"}, + {"MUX_UL05", "BT Left", "BT_VX_UL"}, + {"MUX_UL05", "BT Right", "BT_VX_UL"}, + {"MUX_UL05", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL05", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL05", "AMic0", "PDM_UL1"}, + {"MUX_UL05", "AMic1", "PDM_UL1"}, + {"MUX_UL05", "VX Left", "Capture Mixer"}, + {"MUX_UL05", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL05"}, + + /* MUX_UL06 - ROUTE_UL - Chan 6 */ + {"MUX_UL06", "DMic0L", "DMIC0"}, + {"MUX_UL06", "DMic0R", "DMIC0"}, + {"MUX_UL06", "DMic1L", "DMIC1"}, + {"MUX_UL06", "DMic1R", "DMIC1"}, + {"MUX_UL06", "DMic2L", "DMIC2"}, + {"MUX_UL06", "DMic2R", "DMIC2"}, + {"MUX_UL06", "BT Left", "BT_VX_UL"}, + {"MUX_UL06", "BT Right", "BT_VX_UL"}, + {"MUX_UL06", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL06", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL06", "AMic0", "PDM_UL1"}, + {"MUX_UL06", "AMic1", "PDM_UL1"}, + {"MUX_UL06", "VX Left", "Capture Mixer"}, + {"MUX_UL06", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL06"}, + + /* MUX_UL07 - ROUTE_UL - Chan 7 */ + {"MUX_UL07", "DMic0L", "DMIC0"}, + {"MUX_UL07", "DMic0R", "DMIC0"}, + {"MUX_UL07", "DMic1L", "DMIC1"}, + {"MUX_UL07", "DMic1R", "DMIC1"}, + {"MUX_UL07", "DMic2L", "DMIC2"}, + {"MUX_UL07", "DMic2R", "DMIC2"}, + {"MUX_UL07", "BT Left", "BT_VX_UL"}, + {"MUX_UL07", "BT Right", "BT_VX_UL"}, + {"MUX_UL07", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL07", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL07", "AMic0", "PDM_UL1"}, + {"MUX_UL07", "AMic1", "PDM_UL1"}, + {"MUX_UL07", "VX Left", "Capture Mixer"}, + {"MUX_UL07", "VX Right", "Capture Mixer"}, + {"MM_UL1", NULL, "MUX_UL07"}, + + /* MUX_UL10 - ROUTE_UL - Chan 10 */ + {"MUX_UL10", "DMic0L", "DMIC0"}, + {"MUX_UL10", "DMic0R", "DMIC0"}, + {"MUX_UL10", "DMic1L", "DMIC1"}, + {"MUX_UL10", "DMic1R", "DMIC1"}, + {"MUX_UL10", "DMic2L", "DMIC2"}, + {"MUX_UL10", "DMic2R", "DMIC2"}, + {"MUX_UL10", "BT Left", "BT_VX_UL"}, + {"MUX_UL10", "BT Right", "BT_VX_UL"}, + {"MUX_UL10", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL10", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL10", "AMic0", "PDM_UL1"}, + {"MUX_UL10", "AMic1", "PDM_UL1"}, + {"MUX_UL10", "VX Left", "Capture Mixer"}, + {"MUX_UL10", "VX Right", "Capture Mixer"}, + {"MM_UL2", NULL, "MUX_UL10"}, + + /* MUX_UL11 - ROUTE_UL - Chan 11 */ + {"MUX_UL11", "DMic0L", "DMIC0"}, + {"MUX_UL11", "DMic0R", "DMIC0"}, + {"MUX_UL11", "DMic1L", "DMIC1"}, + {"MUX_UL11", "DMic1R", "DMIC1"}, + {"MUX_UL11", "DMic2L", "DMIC2"}, + {"MUX_UL11", "DMic2R", "DMIC2"}, + {"MUX_UL11", "BT Left", "BT_VX_UL"}, + {"MUX_UL11", "BT Right", "BT_VX_UL"}, + {"MUX_UL11", "MMExt Left", "MM_EXT_UL"}, + {"MUX_UL11", "MMExt Right", "MM_EXT_UL"}, + {"MUX_UL11", "AMic0", "PDM_UL1"}, + {"MUX_UL11", "AMic1", "PDM_UL1"}, + {"MUX_UL11", "VX Left", "Capture Mixer"}, + {"MUX_UL11", "VX Right", "Capture Mixer"}, + {"MM_UL2", NULL, "MUX_UL11"}, + + /* MUX_VX0 - ROUTE_UL - Chan 20 */ + {"MUX_VX0", "DMic0L", "DMIC0"}, + {"MUX_VX0", "DMic0R", "DMIC0"}, + {"MUX_VX0", "DMic1L", "DMIC1"}, + {"MUX_VX0", "DMic1R", "DMIC1"}, + {"MUX_VX0", "DMic2L", "DMIC2"}, + {"MUX_VX0", "DMic2R", "DMIC2"}, + {"MUX_VX0", "BT Left", "BT_VX_UL"}, + {"MUX_VX0", "BT Right", "BT_VX_UL"}, + {"MUX_VX0", "MMExt Left", "MM_EXT_UL"}, + {"MUX_VX0", "MMExt Right", "MM_EXT_UL"}, + {"MUX_VX0", "AMic0", "PDM_UL1"}, + {"MUX_VX0", "AMic1", "PDM_UL1"}, + {"MUX_VX0", "VX Left", "Capture Mixer"}, + {"MUX_VX0", "VX Right", "Capture Mixer"}, + + /* MUX_VX1 - ROUTE_UL - Chan 20 */ + {"MUX_VX1", "DMic0L", "DMIC0"}, + {"MUX_VX1", "DMic0R", "DMIC0"}, + {"MUX_VX1", "DMic1L", "DMIC1"}, + {"MUX_VX1", "DMic1R", "DMIC1"}, + {"MUX_VX1", "DMic2L", "DMIC2"}, + {"MUX_VX1", "DMic2R", "DMIC2"}, + {"MUX_VX1", "BT Left", "BT_VX_UL"}, + {"MUX_VX1", "BT Right", "BT_VX_UL"}, + {"MUX_VX1", "MMExt Left", "MM_EXT_UL"}, + {"MUX_VX1", "MMExt Right", "MM_EXT_UL"}, + {"MUX_VX1", "AMic0", "PDM_UL1"}, + {"MUX_VX1", "AMic1", "PDM_UL1"}, + {"MUX_VX1", "VX Left", "Capture Mixer"}, + {"MUX_VX1", "VX Right", "Capture Mixer"}, + + /* Headset (DL1) playback path */ + {"DL1 Mixer", "Tones", "TONES_DL"}, + {"DL1 Mixer", "Voice", "VX DL VMixer"}, + {"DL1 Mixer", "Capture", "DL1 Capture VMixer"}, + {"DL1 Capture VMixer", NULL, "MUX_UL10"}, + {"DL1 Capture VMixer", NULL, "MUX_UL11"}, + {"DL1 Mixer", "Multimedia", "MM_DL VMixer"}, + {"MM_DL VMixer", NULL, "MM_DL"}, + {"MM_DL VMixer", NULL, "MM_DL_LP"}, + + /* Sidetone Mixer */ + {"Sidetone Mixer", "Playback", "DL1 Mixer"}, + {"Sidetone Mixer", "Capture", "Sidetone Capture VMixer"}, + {"Sidetone Capture VMixer", NULL, "MUX_VX0"}, + {"Sidetone Capture VMixer", NULL, "MUX_VX1"}, + + /* Playback Output selection after DL1 Gain */ + {"DL1 BT_VX", "Switch", "Sidetone Mixer"}, + {"DL1 MM_EXT", "Switch", "Sidetone Mixer"}, + {"DL1 PDM", "Switch", "Sidetone Mixer"}, + {"PDM_DL1", NULL, "DL1 PDM"}, + {"BT_VX_DL", NULL, "DL1 BT_VX"}, + {"MM_EXT_DL", NULL, "DL1 MM_EXT"}, + + /* Handsfree (DL2) playback path */ + {"DL2 Mixer", "Tones", "TONES_DL"}, + {"DL2 Mixer", "Voice", "VX DL VMixer"}, + {"DL2 Mixer", "Capture", "DL2 Capture VMixer"}, + {"DL2 Capture VMixer", NULL, "MUX_UL10"}, + {"DL2 Capture VMixer", NULL, "MUX_UL11"}, + {"DL2 Mixer", "Multimedia", "MM_DL VMixer"}, + {"MM_DL VMixer", NULL, "MM_DL"}, + {"MM_DL VMixer", NULL, "MM_DL_LP"}, + {"PDM_DL2", NULL, "DL2 Mixer"}, + + /* VxREC Mixer */ + {"Capture Mixer", "Tones", "TONES_DL"}, + {"Capture Mixer", "Voice Playback", "VX DL VMixer"}, + {"Capture Mixer", "Voice Capture", "VX UL VMixer"}, + {"Capture Mixer", "Media Playback", "MM_DL VMixer"}, + {"MM_DL VMixer", NULL, "MM_DL"}, + {"MM_DL VMixer", NULL, "MM_DL_LP"}, + + /* Audio UL mixer */ + {"Voice Capture Mixer", "Tones Playback", "TONES_DL"}, + {"Voice Capture Mixer", "Media Playback", "MM_DL VMixer"}, + {"MM_DL VMixer", NULL, "MM_DL"}, + {"MM_DL VMixer", NULL, "MM_DL_LP"}, + {"Voice Capture Mixer", "Capture", "Voice Capture VMixer"}, + {"Voice Capture VMixer", NULL, "MUX_VX0"}, + {"Voice Capture VMixer", NULL, "MUX_VX1"}, + + /* BT */ + {"VX UL VMixer", NULL, "Voice Capture Mixer"}, + + /* Vibra */ + {"PDM_VIB", NULL, "VIB_DL"}, + + /* VX and MODEM */ + {"VX_UL", NULL, "VX UL VMixer"}, + {"MODEM_UL", NULL, "VX UL VMixer"}, + {"VX DL VMixer", NULL, "VX_DL"}, + {"VX DL VMixer", NULL, "MODEM_DL"}, + + /* Backend Enablement - TODO: maybe re-work*/ + {"BE_OUT", NULL, "PDM_DL1"}, + {"BE_OUT", NULL, "PDM_DL2"}, + {"BE_OUT", NULL, "PDM_VIB"}, + {"BE_OUT", NULL, "MM_EXT_DL"}, + {"BE_OUT", NULL, "BT_VX_DL"}, + {"PDM_UL1", NULL, "BE_IN"}, + {"BT_VX_UL", NULL, "BE_IN"}, + {"MM_EXT_UL", NULL, "BE_IN"}, + {"DMIC0", NULL, "BE_IN"}, + {"DMIC1", NULL, "BE_IN"}, + {"DMIC2", NULL, "BE_IN"}, +}; + +#ifdef CONFIG_DEBUG_FS + +static int abe_dbg_get_dma_pos(struct abe_data *abe) +{ + return omap_get_dma_dst_pos(abe->dma_ch) - abe->dbg_buffer_addr; +} + +static void abe_dbg_dma_irq(int ch, u16 stat, void *data) +{ +} + +static int abe_dbg_start_dma(struct abe_data *abe, int circular) +{ + struct omap_dma_channel_params dma_params; + int err; + + /* TODO: start the DMA in either :- + * + * 1) circular buffer mode where the DMA will restart when it get to + * the end of the buffer. + * 2) default mode, where DMA stops at the end of the buffer. + */ + + abe->dma_req = OMAP44XX_DMA_ABE_REQ_7; + err = omap_request_dma(abe->dma_req, "ABE debug", + abe_dbg_dma_irq, abe, &abe->dma_ch); + if (abe->dbg_circular) { + /* + * Link channel with itself so DMA doesn't need any + * reprogramming while looping the buffer + */ + omap_dma_link_lch(abe->dma_ch, abe->dma_ch); + } + + memset(&dma_params, 0, sizeof(dma_params)); + dma_params.data_type = OMAP_DMA_DATA_TYPE_S32; + dma_params.trigger = abe->dma_req; + dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; + dma_params.src_amode = OMAP_DMA_AMODE_DOUBLE_IDX; + dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; + dma_params.src_or_dst_synch = OMAP_DMA_SRC_SYNC; + dma_params.src_start = D_DEBUG_FIFO_ADDR + ABE_DMEM_BASE_ADDRESS_L3; + dma_params.dst_start = abe->dbg_buffer_addr; + dma_params.src_port = OMAP_DMA_PORT_MPUI; + dma_params.src_ei = 1; + dma_params.src_fi = 1 - abe->dbg_elem_bytes; + + dma_params.elem_count = abe->dbg_elem_bytes >> 2; /* 128 bytes shifted into words */ + dma_params.frame_count = abe->dbg_buffer_bytes / abe->dbg_elem_bytes; + omap_set_dma_params(abe->dma_ch, &dma_params); + + omap_enable_dma_irq(abe->dma_ch, OMAP_DMA_FRAME_IRQ); + omap_set_dma_src_burst_mode(abe->dma_ch, OMAP_DMA_DATA_BURST_16); + omap_set_dma_dest_burst_mode(abe->dma_ch, OMAP_DMA_DATA_BURST_16); + + abe->dbg_reader_offset = 0; + + pm_runtime_get_sync(abe->dev); + omap_start_dma(abe->dma_ch); + return 0; +} + +static void abe_dbg_stop_dma(struct abe_data *abe) +{ + while (omap_get_dma_active_status(abe->dma_ch)) + omap_stop_dma(abe->dma_ch); + + if (abe->dbg_circular) + omap_dma_unlink_lch(abe->dma_ch, abe->dma_ch); + omap_free_dma(abe->dma_ch); + pm_runtime_put_sync(abe->dev); +} + +static int abe_open_data(struct inode *inode, struct file *file) +{ + struct abe_data *abe = inode->i_private; + + abe->dbg_elem_bytes = 128; /* size of debug data per tick */ + + if (abe->dbg_format1) + abe->dbg_elem_bytes += ABE_DBG_FLAG1_SIZE; + if (abe->dbg_format2) + abe->dbg_elem_bytes += ABE_DBG_FLAG2_SIZE; + if (abe->dbg_format3) + abe->dbg_elem_bytes += ABE_DBG_FLAG3_SIZE; + + abe->dbg_buffer_bytes = abe->dbg_elem_bytes * 4 * + abe->dbg_buffer_msecs; + + abe->dbg_buffer = dma_alloc_writecombine(abe->dev, + abe->dbg_buffer_bytes, &abe->dbg_buffer_addr, GFP_KERNEL); + if (abe->dbg_buffer == NULL) + return -ENOMEM; + + file->private_data = inode->i_private; + abe->dbg_complete = 0; + abe_dbg_start_dma(abe, abe->dbg_circular); + + return 0; +} + +static int abe_release_data(struct inode *inode, struct file *file) +{ + struct abe_data *abe = inode->i_private; + + abe_dbg_stop_dma(abe); + + dma_free_writecombine(abe->dev, abe->dbg_buffer_bytes, + abe->dbg_buffer, abe->dbg_buffer_addr); + return 0; +} + +static ssize_t abe_copy_to_user(struct abe_data *abe, char __user *user_buf, + size_t count) +{ + /* check for reader buffer wrap */ + if (abe->dbg_reader_offset + count > abe->dbg_buffer_bytes) { + int size = abe->dbg_buffer_bytes - abe->dbg_reader_offset; + + /* wrap */ + if (copy_to_user(user_buf, + abe->dbg_buffer + abe->dbg_reader_offset, size)) + return -EFAULT; + + /* need to just return if non circular */ + if (!abe->dbg_circular) { + abe->dbg_complete = 1; + return count; + } + + if (copy_to_user(user_buf, + abe->dbg_buffer, count - size)) + return -EFAULT; + abe->dbg_reader_offset = count - size; + return count; + } else { + /* no wrap */ + if (copy_to_user(user_buf, + abe->dbg_buffer + abe->dbg_reader_offset, count)) + return -EFAULT; + abe->dbg_reader_offset += count; + + if (!abe->dbg_circular && + abe->dbg_reader_offset == abe->dbg_buffer_bytes) + abe->dbg_complete = 1; + + return count; + } +} + +static ssize_t abe_read_data(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret = 0; + struct abe_data *abe = file->private_data; + DECLARE_WAITQUEUE(wait, current); + int dma_offset, bytes; + + add_wait_queue(&abe->wait, &wait); + do { + set_current_state(TASK_INTERRUPTIBLE); + /* TODO: Check if really needed. Or adjust sleep delay + * If not delay trace is not working */ + msleep_interruptible(1); + dma_offset = abe_dbg_get_dma_pos(abe); + + /* is DMA finished ? */ + if (abe->dbg_complete) + break; + + /* get maximum amount of debug bytes we can read */ + if (dma_offset >= abe->dbg_reader_offset) { + /* dma ptr is ahead of reader */ + bytes = dma_offset - abe->dbg_reader_offset; + } else { + /* dma ptr is behind reader */ + bytes = dma_offset + abe->dbg_buffer_bytes - + abe->dbg_reader_offset; + } + + if (count > bytes) + count = bytes; + + if (count > 0) { + ret = abe_copy_to_user(abe, user_buf, count); + break; + } + + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + schedule(); + + } while (1); + + __set_current_state(TASK_RUNNING); + remove_wait_queue(&abe->wait, &wait); + + return ret; +} + +static const struct file_operations abe_data_fops = { + .open = abe_open_data, + .read = abe_read_data, + .release = abe_release_data, +}; + +static void abe_init_debugfs(struct abe_data *abe) +{ + abe->debugfs_root = debugfs_create_dir("omap4-abe", NULL); + if (!abe->debugfs_root) { + printk(KERN_WARNING "ABE: Failed to create debugfs directory\n"); + return; + } + + abe->debugfs_fmt1 = debugfs_create_bool("format1", 0644, + abe->debugfs_root, + &abe->dbg_format1); + if (!abe->debugfs_fmt1) + printk(KERN_WARNING "ABE: Failed to create format1 debugfs file\n"); + + abe->debugfs_fmt2 = debugfs_create_bool("format2", 0644, + abe->debugfs_root, + &abe->dbg_format2); + if (!abe->debugfs_fmt2) + printk(KERN_WARNING "ABE: Failed to create format2 debugfs file\n"); + + abe->debugfs_fmt3 = debugfs_create_bool("format3", 0644, + abe->debugfs_root, + &abe->dbg_format3); + if (!abe->debugfs_fmt3) + printk(KERN_WARNING "ABE: Failed to create format3 debugfs file\n"); + + abe->debugfs_elem_bytes = debugfs_create_u32("element_bytes", 0604, + abe->debugfs_root, + &abe->dbg_elem_bytes); + if (!abe->debugfs_elem_bytes) + printk(KERN_WARNING "ABE: Failed to create element size debugfs file\n"); + + abe->debugfs_size = debugfs_create_u32("msecs", 0644, + abe->debugfs_root, + &abe->dbg_buffer_msecs); + if (!abe->debugfs_size) + printk(KERN_WARNING "ABE: Failed to create buffer size debugfs file\n"); + + abe->debugfs_circ = debugfs_create_bool("circular", 0644, + abe->debugfs_root, + &abe->dbg_circular); + if (!abe->debugfs_size) + printk(KERN_WARNING "ABE: Failed to create circular mode debugfs file\n"); + + abe->debugfs_data = debugfs_create_file("debug", 0644, + abe->debugfs_root, + abe, &abe_data_fops); + if (!abe->debugfs_data) + printk(KERN_WARNING "ABE: Failed to create data debugfs file\n"); + + abe->debugfs_opp_level = debugfs_create_u32("opp_level", 0604, + abe->debugfs_root, + &abe->opp); + if (!abe->debugfs_opp_level) + printk(KERN_WARNING "ABE: Failed to create OPP level debugfs file\n"); + + abe->dbg_buffer_msecs = 500; + init_waitqueue_head(&abe->wait); +} + +static void abe_cleanup_debugfs(struct abe_data *abe) +{ + debugfs_remove_recursive(abe->debugfs_root); +} + +#else + +static inline void abe_init_debugfs(struct abe_data *abe) +{ +} + +static inline void abe_cleanup_debugfs(struct abe_data *abe) +{ +} +#endif + +static const struct snd_pcm_hardware omap_abe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 4 * 1024, + .period_bytes_max = 24 * 1024, + .periods_min = 2, + .periods_max = 2, + .buffer_bytes_max = 24 * 1024 * 2, +}; + + +static int abe_set_opp_mode(struct abe_data *abe) +{ + int i, opp = 0; + + /* now calculate OPP level based upon DAPM widget status */ + for (i = 0; i < ABE_NUM_WIDGETS; i++) { + if (abe->widget_opp[ABE_WIDGET(i)]) { + dev_dbg(abe->dev, "OPP: id %d = %d%%\n", i, + abe->widget_opp[ABE_WIDGET(i)] * 25); + opp |= abe->widget_opp[ABE_WIDGET(i)]; + } + } + opp = (1 << (fls(opp) - 1)) * 25; + + if (abe->opp > opp) { + /* Decrease OPP mode - no need of OPP100% */ + switch (opp) { + case 25: + abe_set_opp_processing(ABE_OPP25); + udelay(250); + omap_device_set_rate(abe->dev, abe->dev, 49150000); + break; + case 50: + default: + abe_set_opp_processing(ABE_OPP50); + udelay(250); + omap_device_set_rate(abe->dev, abe->dev, 98300000); + break; + } + } else if (abe->opp < opp) { + /* Increase OPP mode */ + switch (opp) { + case 25: + omap_device_set_rate(abe->dev, abe->dev, 49000000); + abe_set_opp_processing(ABE_OPP25); + break; + case 50: + omap_device_set_rate(abe->dev, abe->dev, 98300000); + abe_set_opp_processing(ABE_OPP50); + break; + case 100: + default: + omap_device_set_rate(abe->dev, abe->dev, 196600000); + abe_set_opp_processing(ABE_OPP100); + break; + } + } + abe->opp = opp; + dev_dbg(abe->dev, "new OPP level is %d\n", opp); + + return 0; +} + +static int aess_set_runtime_opp_level(struct abe_data *abe) +{ + mutex_lock(&abe->opp_mutex); + + pm_runtime_get_sync(abe->dev); + abe_set_opp_mode(abe); + pm_runtime_put_sync(abe->dev); + + mutex_unlock(&abe->opp_mutex); + + return 0; +} + +static int aess_save_context(struct abe_data *abe) +{ + struct omap4_abe_dsp_pdata *pdata = abe->abe_pdata; + + /* TODO: Find a better way to save/retore gains after OFF mode */ + + abe_mute_gain(MIXSDT, MIX_SDT_INPUT_UP_MIXER); + abe_mute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); + abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_MM_DL); + abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_TONES); + abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); + abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_VX_DL); + abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_TONES); + abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_DL); + abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_MM_DL); + abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_UL); + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_TONES); + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_TONES); + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); + abe_mute_gain(MIXECHO, MIX_ECHO_DL1); + abe_mute_gain(MIXECHO, MIX_ECHO_DL2); + abe_mute_gain(GAINS_DMIC1, GAIN_LEFT_OFFSET); + abe_mute_gain(GAINS_DMIC1, GAIN_RIGHT_OFFSET); + abe_mute_gain(GAINS_DMIC2, GAIN_LEFT_OFFSET); + abe_mute_gain(GAINS_DMIC2, GAIN_RIGHT_OFFSET); + abe_mute_gain(GAINS_DMIC3, GAIN_LEFT_OFFSET); + abe_mute_gain(GAINS_DMIC3, GAIN_RIGHT_OFFSET); + abe_mute_gain(GAINS_AMIC, GAIN_LEFT_OFFSET); + abe_mute_gain(GAINS_AMIC, GAIN_RIGHT_OFFSET); + + if (pdata->get_context_loss_count) + abe->loss_count = pdata->get_context_loss_count(abe->dev); + + return 0; +} + +static int aess_restore_context(struct abe_data *abe) +{ + struct omap4_abe_dsp_pdata *pdata = abe->abe_pdata; + int loss_count = 0; + + omap_device_set_rate(&abe->dev, &abe->dev, 98000000); + + if (pdata->get_context_loss_count) + loss_count = pdata->get_context_loss_count(abe->dev); + + if (loss_count != the_abe->loss_count) + abe_reload_fw(abe->firmware); + + /* TODO: Find a better way to save/retore gains after dor OFF mode */ + abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_UP_MIXER); + abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); + abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_MM_DL); + abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_TONES); + abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); + abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_VX_DL); + abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_TONES); + abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_DL); + abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_MM_DL); + abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_UL); + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_TONES); + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_TONES); + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); + abe_unmute_gain(MIXECHO, MIX_ECHO_DL1); + abe_unmute_gain(MIXECHO, MIX_ECHO_DL2); + abe_unmute_gain(GAINS_DMIC1, GAIN_LEFT_OFFSET); + abe_unmute_gain(GAINS_DMIC1, GAIN_RIGHT_OFFSET); + abe_unmute_gain(GAINS_DMIC2, GAIN_LEFT_OFFSET); + abe_unmute_gain(GAINS_DMIC2, GAIN_RIGHT_OFFSET); + abe_unmute_gain(GAINS_DMIC3, GAIN_LEFT_OFFSET); + abe_unmute_gain(GAINS_DMIC3, GAIN_RIGHT_OFFSET); + abe_unmute_gain(GAINS_AMIC, GAIN_LEFT_OFFSET); + abe_unmute_gain(GAINS_AMIC, GAIN_RIGHT_OFFSET); +/* + abe_dsp_set_equalizer(EQ1, abe->dl1_equ_profile); + abe_dsp_set_equalizer(EQ2L, abe->dl20_equ_profile); + abe_dsp_set_equalizer(EQ2R, abe->dl21_equ_profile); + abe_dsp_set_equalizer(EQAMIC, abe->amic_equ_profile); + abe_dsp_set_equalizer(EQDMIC, abe->dmic_equ_profile); + abe_dsp_set_equalizer(EQSDT, abe->sdt_equ_profile); +*/ + abe_set_router_configuration(UPROUTE, 0, (u32 *)abe->router); + + return 0; +} + +static int aess_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + mutex_lock(&abe->mutex); + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + pm_runtime_get_sync(abe->dev); + + if (!abe->active++) { + abe->opp = 0; + aess_restore_context(abe); + abe_set_opp_mode(abe); + abe_wakeup(); + } + + switch (dai->id) { + case ABE_FRONTEND_DAI_MODEM: + break; + case ABE_FRONTEND_DAI_LP_MEDIA: + snd_soc_set_runtime_hwparams(substream, &omap_abe_hardware); + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 1024); + break; + default: + break; + } + + mutex_unlock(&abe->mutex); + return ret; +} + +static int aess_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_platform *platform = rtd->platform; + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + struct snd_soc_dai *dai = rtd->cpu_dai; + abe_data_format_t format; + size_t period_size; + u32 dst; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + if (dai->id != ABE_FRONTEND_DAI_LP_MEDIA) + return 0; + + /*Storing substream pointer for irq*/ + abe->ping_pong_substream = substream; + + format.f = params_rate(params); + if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE) + format.samp_format = STEREO_MSB; + else + format.samp_format = STEREO_16_16; + + if (format.f == 44100) + abe_write_event_generator(EVENT_44100); + + period_size = params_period_bytes(params); + + /*Adding ping pong buffer subroutine*/ + abe_plug_subroutine(&abe_irq_pingpong_player_id, + (abe_subroutine2) abe_irq_pingpong_subroutine, + SUB_1_PARAM, (u32 *)abe); + + /* Connect a Ping-Pong cache-flush protocol to MM_DL port */ + abe_connect_irq_ping_pong_port(MM_DL_PORT, &format, + abe_irq_pingpong_player_id, + period_size, &dst, + PING_PONG_WITH_MCU_IRQ); + + /* Memory mapping for hw params */ + runtime->dma_area = abe->io_base[0] + dst; + runtime->dma_addr = 0; + runtime->dma_bytes = period_size * 2; + + /* Need to set the first buffer in order to get interrupt */ + abe_set_ping_pong_buffer(MM_DL_PORT, period_size); + abe->first_irq = 1; + + return 0; +} + +static int aess_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + struct snd_soc_dai *dai = rtd->cpu_dai; + + mutex_lock(&abe->mutex); + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + aess_set_runtime_opp_level(abe); + mutex_unlock(&abe->mutex); + return 0; +} + +static int aess_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + struct snd_soc_dai *dai = rtd->cpu_dai; + + mutex_lock(&abe->mutex); + aess_set_runtime_opp_level(abe); + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + if (!--abe->active) { + abe_disable_irq(); + aess_save_context(abe); + abe_dsp_shutdown(); + } + + pm_runtime_put_sync(abe->dev); + + mutex_unlock(&abe->mutex); + return 0; +} + +static int aess_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int offset, size, err; + + if (dai->id != ABE_FRONTEND_DAI_LP_MEDIA) + return -EINVAL; + + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + size = vma->vm_end - vma->vm_start; + offset = vma->vm_pgoff << PAGE_SHIFT; + + err = io_remap_pfn_range(vma, vma->vm_start, + (ABE_DMEM_BASE_ADDRESS_MPU + + ABE_DMEM_BASE_OFFSET_PING_PONG + offset) >> PAGE_SHIFT, + size, vma->vm_page_prot); + + if (err) + return -EAGAIN; + + return 0; +} + +static snd_pcm_uframes_t aess_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t offset; + u32 pingpong; + + abe_read_offset_from_ping_buffer(MM_DL_PORT, &pingpong); + offset = (snd_pcm_uframes_t)pingpong; + + return offset; +} + +static struct snd_pcm_ops omap_aess_pcm_ops = { + .open = aess_open, + .hw_params = aess_hw_params, + .prepare = aess_prepare, + .close = aess_close, + .pointer = aess_pointer, + .mmap = aess_mmap, +}; + +#if CONFIG_PM +static int aess_suspend(struct device *dev) +{ + struct abe_data *abe = dev_get_drvdata(dev); + + pm_runtime_get_sync(abe->dev); + + aess_save_context(abe); + + pm_runtime_put_sync(abe->dev); + + return 0; +} + +static int aess_resume(struct device *dev) +{ + struct abe_data *abe = dev_get_drvdata(dev); + + pm_runtime_get_sync(abe->dev); + + aess_restore_context(abe); + + pm_runtime_put_sync(abe->dev); + + return 0; +} + +#else +#define aess_suspend NULL +#define aess_resume NULL +#endif + +static const struct dev_pm_ops aess_pm_ops = { + .suspend = aess_suspend, + .resume = aess_resume, +}; + +static int aess_stream_event(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_platform *platform = dapm->platform; + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + + pm_runtime_get_sync(abe->dev); + + if (abe->active) + aess_set_runtime_opp_level(abe); + + pm_runtime_put_sync(abe->dev); + + return 0; +} + +static int abe_add_widgets(struct snd_soc_platform *platform) +{ + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + struct fw_header *hdr = &abe->hdr; + int i, j; + + /* create equalizer controls */ + for (i = 0; i < hdr->num_equ; i++) { + struct soc_enum *equalizer_enum = &abe->equalizer_enum[i]; + struct snd_kcontrol_new *equalizer_control = + &abe->equalizer_control[i]; + + equalizer_enum->reg = i; + equalizer_enum->max = abe->equ_texts[i].count; + for (j = 0; j < abe->equ_texts[i].count; j++) + equalizer_enum->dtexts[j] = abe->equ_texts[i].texts[j]; + + equalizer_control->name = abe->equ_texts[i].name; + equalizer_control->private_value = (unsigned long)equalizer_enum; + equalizer_control->get = abe_get_equalizer; + equalizer_control->put = abe_put_equalizer; + equalizer_control->info = snd_soc_info_enum_ext1; + equalizer_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + + dev_dbg(platform->dev, "added EQU mixer: %s profiles %d\n", + abe->equ_texts[i].name, abe->equ_texts[i].count); + + for (j = 0; j < abe->equ_texts[i].count; j++) + dev_dbg(platform->dev, " %s\n", equalizer_enum->dtexts[j]); + } + + snd_soc_add_platform_controls(platform, abe->equalizer_control, + hdr->num_equ); + + snd_soc_add_platform_controls(platform, abe_controls, + ARRAY_SIZE(abe_controls)); + + snd_soc_dapm_new_controls(&platform->dapm, abe_dapm_widgets, + ARRAY_SIZE(abe_dapm_widgets)); + + snd_soc_dapm_add_routes(&platform->dapm, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(&platform->dapm); + + return 0; +} + +static int abe_probe(struct snd_soc_platform *platform) +{ + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + u8 *fw_data; + int i, offset = 0; + int ret = 0; +#if defined(CONFIG_SND_OMAP_SOC_ABE_DSP_MODULE) + const struct firmware *fw; +#endif + + abe->platform = platform; + + pm_runtime_enable(abe->dev); + +#if defined(CONFIG_SND_OMAP_SOC_ABE_DSP_MODULE) + /* request firmware & coefficients */ + ret = request_firmware(&fw, "omap4_abe", platform->dev); + if (ret != 0) { + dev_err(abe->dev, "Failed to load firmware: %d\n", ret); + return ret; + } + fw_data = fw->data; +#else + fw_data = (u8 *)abe_get_default_fw(); +#endif + + /* get firmware and coefficients header info */ + memcpy(&abe->hdr, fw_data, sizeof(struct fw_header)); + if (abe->hdr.firmware_size > ABE_MAX_FW_SIZE) { + dev_err(abe->dev, "Firmware too large at %d bytes: %d\n", + abe->hdr.firmware_size, ret); + ret = -EINVAL; + goto err_fw; + } + dev_dbg(abe->dev, "ABE firmware size %d bytes\n", abe->hdr.firmware_size); + + if (abe->hdr.coeff_size > ABE_MAX_COEFF_SIZE) { + dev_err(abe->dev, "Coefficients too large at %d bytes: %d\n", + abe->hdr.coeff_size, ret); + ret = -EINVAL; + goto err_fw; + } + dev_dbg(abe->dev, "ABE coefficients size %d bytes\n", abe->hdr.coeff_size); + + /* get coefficient EQU mixer strings */ + if (abe->hdr.num_equ >= ABE_MAX_EQU) { + dev_err(abe->dev, "Too many equalizers got %d\n", abe->hdr.num_equ); + ret = -EINVAL; + goto err_fw; + } + abe->equ_texts = kzalloc(abe->hdr.num_equ * sizeof(struct coeff_config), + GFP_KERNEL); + if (abe->equ_texts == NULL) { + ret = -ENOMEM; + goto err_fw; + } + offset = sizeof(struct fw_header); + memcpy(abe->equ_texts, fw_data + offset, + abe->hdr.num_equ * sizeof(struct coeff_config)); + + /* get coefficients from firmware */ + abe->equ[0] = kmalloc(abe->hdr.coeff_size, GFP_KERNEL); + if (abe->equ[0] == NULL) { + ret = -ENOMEM; + goto err_equ; + } + offset += abe->hdr.num_equ * sizeof(struct coeff_config); + memcpy(abe->equ[0], fw_data + offset, abe->hdr.coeff_size); + + /* allocate coefficient mixer texts */ + dev_dbg(abe->dev, "loaded %d equalizers\n", abe->hdr.num_equ); + for (i = 0; i < abe->hdr.num_equ; i++) { + dev_dbg(abe->dev, "equ %d: %s profiles %d\n", i, + abe->equ_texts[i].name, abe->equ_texts[i].count); + if (abe->equ_texts[i].count >= ABE_MAX_PROFILES) { + dev_err(abe->dev, "Too many profiles got %d for equ %d\n", + abe->equ_texts[i].count, i); + ret = -EINVAL; + goto err_texts; + } + abe->equalizer_enum[i].dtexts = + kzalloc(abe->equ_texts[i].count * sizeof(char *), GFP_KERNEL); + if (abe->equalizer_enum[i].dtexts == NULL) { + ret = -ENOMEM; + goto err_texts; + } + } + + /* initialise coefficient equalizers */ + for (i = 1; i < abe->hdr.num_equ; i++) { + abe->equ[i] = abe->equ[i - 1] + + abe->equ_texts[i - 1].count * abe->equ_texts[i - 1].coeff; + } + + /* store ABE firmware for later context restore */ + abe->firmware = kzalloc(abe->hdr.firmware_size, GFP_KERNEL); + memcpy(abe->firmware, + fw_data + sizeof(struct fw_header) + abe->hdr.coeff_size, + abe->hdr.firmware_size); + + ret = request_irq(abe->irq, abe_irq_handler, 0, "ABE", (void *)abe); + if (ret) { + dev_err(platform->dev, "request for ABE IRQ %d failed %d\n", + abe->irq, ret); + goto err_texts; + } + + /* aess_clk has to be enabled to access hal register. + * Disable the clk after it has been used. + */ + pm_runtime_get_sync(abe->dev); + + abe_init_mem(abe->io_base); + + abe_reset_hal(); + + abe_load_fw(abe->firmware); + + /* Config OPP 100 for now */ + abe_set_opp_processing(ABE_OPP100); + + /* "tick" of the audio engine */ + abe_write_event_generator(EVENT_TIMER); + /* Stop the engine */ + abe_stop_event_generator(); + abe_disable_irq(); + + pm_runtime_put_sync(abe->dev); + abe_add_widgets(platform); + +#if defined(CONFIG_SND_OMAP_SOC_ABE_DSP_MODULE) + release_firmware(fw); +#endif + return ret; + +err_texts: + kfree(abe->firmware); + for (i = 0; i < abe->hdr.num_equ; i++) + kfree(abe->equalizer_enum[i].texts); + kfree(abe->equ[0]); +err_equ: + kfree(abe->equ_texts); +err_fw: +#if defined(CONFIG_SND_OMAP_SOC_ABE_DSP_MODULE) + release_firmware(fw); +#endif + return ret; +} + +static int abe_remove(struct snd_soc_platform *platform) +{ + struct abe_data *abe = snd_soc_platform_get_drvdata(platform); + int i; + + free_irq(abe->irq, (void *)abe); + + for (i = 0; i < abe->hdr.num_equ; i++) + kfree(abe->equalizer_enum[i].texts); + + kfree(abe->equ[0]); + kfree(abe->equ_texts); + kfree(abe->firmware); + + pm_runtime_disable(abe->dev); + + return 0; +} + +static struct snd_soc_platform_driver omap_aess_platform = { + .ops = &omap_aess_pcm_ops, + .probe = abe_probe, + .remove = abe_remove, + .read = abe_dsp_read, + .write = abe_dsp_write, + .stream_event = aess_stream_event, +}; + +static int __devinit abe_engine_probe(struct platform_device *pdev) +{ + struct resource *res; + struct omap4_abe_dsp_pdata *pdata = pdev->dev.platform_data; + struct abe_data *abe; + int ret = -EINVAL, i; + + abe = kzalloc(sizeof(struct abe_data), GFP_KERNEL); + if (abe == NULL) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, abe); + the_abe = abe; + + /* ZERO_labelID should really be 0 */ + for (i = 0; i < ABE_ROUTES_UL + 2; i++) + abe->router[i] = ZERO_labelID; + + for (i = 0; i < 5; i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + abe_memory_bank[i]); + if (res == NULL) { + dev_err(&pdev->dev, "no resource %s\n", + abe_memory_bank[i]); + goto err; + } + abe->io_base[i] = ioremap(res->start, resource_size(res)); + if (!abe->io_base[i]) { + ret = -ENOMEM; + goto err; + } + } + + abe->irq = platform_get_irq(pdev, 0); + if (abe->irq < 0) { + ret = abe->irq; + goto err; + } + + abe->abe_pdata = pdata; + abe->dev = &pdev->dev; + mutex_init(&abe->mutex); + mutex_init(&abe->opp_mutex); + + ret = snd_soc_register_platform(abe->dev, + &omap_aess_platform); + if (ret < 0) + return ret; + + abe_init_debugfs(abe); + return ret; + +err: + for (--i; i >= 0; i--) + iounmap(abe->io_base[i]); + kfree(abe); + return ret; +} + +static int __devexit abe_engine_remove(struct platform_device *pdev) +{ + struct abe_data *abe = dev_get_drvdata(&pdev->dev); + int i; + + abe_cleanup_debugfs(abe); + snd_soc_unregister_platform(&pdev->dev); + for (i = 0; i < 5; i++) + iounmap(abe->io_base[i]); + kfree(abe); + return 0; +} + +static struct platform_driver omap_aess_driver = { + .driver = { + .name = "aess", + .owner = THIS_MODULE, + .pm = &aess_pm_ops, + }, + .probe = abe_engine_probe, + .remove = __devexit_p(abe_engine_remove), +}; + +static int __init abe_engine_init(void) +{ + return platform_driver_register(&omap_aess_driver); +} +module_init(abe_engine_init); + +static void __exit abe_engine_exit(void) +{ + platform_driver_unregister(&omap_aess_driver); +} +module_exit(abe_engine_exit); + +MODULE_DESCRIPTION("ASoC OMAP4 ABE"); +MODULE_AUTHOR("Liam Girdwood "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-abe-dsp.h b/sound/soc/omap/omap-abe-dsp.h new file mode 100644 index 0000000000000000000000000000000000000000..5d7016e5d6bbfc450d9afcfaaabe81dd2ee32279 --- /dev/null +++ b/sound/soc/omap/omap-abe-dsp.h @@ -0,0 +1,163 @@ +/* + * omap-abe-dsp.h + * + * Copyright (C) 2010 Texas Instruments + * + * Contact: Liam Girdwood + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __OMAP_ABE_DSP_H__ +#define __OMAP_ABE_DSP_H__ + +#define ABE_MIXER(x) (x) + +#define MIX_DL1_TONES ABE_MIXER(0) +#define MIX_DL1_VOICE ABE_MIXER(1) +#define MIX_DL1_CAPTURE ABE_MIXER(2) +#define MIX_DL1_MEDIA ABE_MIXER(3) +#define MIX_DL2_TONES ABE_MIXER(4) +#define MIX_DL2_VOICE ABE_MIXER(5) +#define MIX_DL2_CAPTURE ABE_MIXER(6) +#define MIX_DL2_MEDIA ABE_MIXER(7) +#define MIX_AUDUL_TONES ABE_MIXER(8) +#define MIX_AUDUL_MEDIA ABE_MIXER(9) +#define MIX_AUDUL_CAPTURE ABE_MIXER(10) +#define MIX_VXREC_TONES ABE_MIXER(11) +#define MIX_VXREC_VOICE_PLAYBACK ABE_MIXER(12) +#define MIX_VXREC_VOICE_CAPTURE ABE_MIXER(13) +#define MIX_VXREC_MEDIA ABE_MIXER(14) +#define MIX_SDT_CAPTURE ABE_MIXER(15) +#define MIX_SDT_PLAYBACK ABE_MIXER(16) +#define MIX_SWITCH_PDM_DL ABE_MIXER(17) +#define MIX_SWITCH_BT_VX_DL ABE_MIXER(18) +#define MIX_SWITCH_MM_EXT_DL ABE_MIXER(19) + +#define ABE_NUM_MIXERS (MIX_SWITCH_MM_EXT_DL + 1) + +#define ABE_MUX(x) (x + ABE_NUM_MIXERS) + +#define MUX_MM_UL10 ABE_MUX(0) +#define MUX_MM_UL11 ABE_MUX(1) +#define MUX_MM_UL12 ABE_MUX(2) +#define MUX_MM_UL13 ABE_MUX(3) +#define MUX_MM_UL14 ABE_MUX(4) +#define MUX_MM_UL15 ABE_MUX(5) +#define MUX_MM_UL16 ABE_MUX(6) +#define MUX_MM_UL17 ABE_MUX(7) +#define MUX_MM_UL20 ABE_MUX(8) +#define MUX_MM_UL21 ABE_MUX(9) +#define MUX_VX_UL0 ABE_MUX(10) +#define MUX_VX_UL1 ABE_MUX(11) + +#define ABE_NUM_MUXES (MUX_VX_UL1 - MUX_MM_UL10) + +#define ABE_WIDGET(x) (x + ABE_NUM_MIXERS + ABE_NUM_MUXES) + +/* ABE AIF Frontend Widgets */ +#define W_AIF_TONES_DL ABE_WIDGET(0) +#define W_AIF_VX_DL ABE_WIDGET(1) +#define W_AIF_VX_UL ABE_WIDGET(2) +#define W_AIF_MM_UL1 ABE_WIDGET(3) +#define W_AIF_MM_UL2 ABE_WIDGET(4) +#define W_AIF_MM_DL ABE_WIDGET(5) +#define W_AIF_MM_DL_LP W_AIF_MM_DL +#define W_AIF_VIB_DL ABE_WIDGET(6) +#define W_AIF_MODEM_DL ABE_WIDGET(7) +#define W_AIF_MODEM_UL ABE_WIDGET(8) + +/* ABE AIF Backend Widgets */ +#define W_AIF_PDM_UL1 ABE_WIDGET(9) +#define W_AIF_PDM_DL1 ABE_WIDGET(10) +#define W_AIF_PDM_DL2 ABE_WIDGET(11) +#define W_AIF_PDM_VIB ABE_WIDGET(12) +#define W_AIF_BT_VX_UL ABE_WIDGET(13) +#define W_AIF_BT_VX_DL ABE_WIDGET(14) +#define W_AIF_MM_EXT_UL ABE_WIDGET(15) +#define W_AIF_MM_EXT_DL ABE_WIDGET(16) +#define W_AIF_DMIC0 ABE_WIDGET(17) +#define W_AIF_DMIC1 ABE_WIDGET(18) +#define W_AIF_DMIC2 ABE_WIDGET(19) + +/* ABE ROUTE_UL MUX Widgets */ +#define W_MUX_UL00 ABE_WIDGET(20) +#define W_MUX_UL01 ABE_WIDGET(21) +#define W_MUX_UL02 ABE_WIDGET(22) +#define W_MUX_UL03 ABE_WIDGET(23) +#define W_MUX_UL04 ABE_WIDGET(24) +#define W_MUX_UL05 ABE_WIDGET(25) +#define W_MUX_UL06 ABE_WIDGET(26) +#define W_MUX_UL07 ABE_WIDGET(27) +#define W_MUX_UL10 ABE_WIDGET(28) +#define W_MUX_UL11 ABE_WIDGET(29) +#define W_MUX_VX00 ABE_WIDGET(30) +#define W_MUX_VX01 ABE_WIDGET(31) + +/* ABE Volume and Mixer Widgets */ +#define W_MIXER_DL1 ABE_WIDGET(32) +#define W_MIXER_DL2 ABE_WIDGET(33) +#define W_VOLUME_DL1 ABE_WIDGET(34) +#define W_MIXER_AUDIO_UL ABE_WIDGET(35) +#define W_MIXER_VX_REC ABE_WIDGET(36) +#define W_MIXER_SDT ABE_WIDGET(37) +#define W_VSWITCH_DL1_PDM ABE_WIDGET(38) +#define W_VSWITCH_DL1_BT_VX ABE_WIDGET(39) +#define W_VSWITCH_DL1_MM_EXT ABE_WIDGET(40) + +#define ABE_NUM_WIDGETS (W_VSWITCH_DL1_MM_EXT - W_AIF_TONES_DL) +#define ABE_WIDGET_LAST W_VSWITCH_DL1_MM_EXT + +#define ABE_NUM_DAPM_REG \ + (ABE_NUM_MIXERS + ABE_NUM_MUXES + ABE_NUM_WIDGETS) + +#define ABE_VIRTUAL_SWITCH 0 +#define ABE_ROUTES_UL 14 + +// TODO: OPP bitmask - Use HAL version after update +#define ABE_OPP_25 0 +#define ABE_OPP_50 1 +#define ABE_OPP_100 2 + +/* TODO: size in bytes of debug options */ +#define ABE_DBG_FLAG1_SIZE 0 +#define ABE_DBG_FLAG2_SIZE 0 +#define ABE_DBG_FLAG3_SIZE 0 + +/* TODO: Pong start offset of DMEM */ +/* Ping pong buffer DMEM offset */ +#define ABE_DMEM_BASE_OFFSET_PING_PONG 0x4000 + +/* Gain value conversion */ +#define ABE_MAX_GAIN 12000 +#define ABE_GAIN_SCALE 100 +#define abe_gain_to_val(gain) ((val + ABE_MAX_GAIN) / ABE_GAIN_SCALE) +#define abe_val_to_gain(val) (-ABE_MAX_GAIN + (val * ABE_GAIN_SCALE)) + +/* Firmware coefficients and equalizers */ +#define ABE_MAX_FW_SIZE (1024 * 128) +#define ABE_MAX_COEFF_SIZE (1024 * 4) +#define ABE_COEFF_NAME_SIZE 20 +#define ABE_COEFF_TEXT_SIZE 20 +#define ABE_COEFF_NUM_TEXTS 10 +#define ABE_MAX_EQU 10 +#define ABE_MAX_PROFILES 30 + +void abe_dsp_shutdown(void); +void abe_dsp_pm_get(void); +void abe_dsp_pm_put(void); + +#endif /* End of __OMAP_ABE_DSP_H__ */ diff --git a/sound/soc/omap/omap-abe.c b/sound/soc/omap/omap-abe.c new file mode 100644 index 0000000000000000000000000000000000000000..049f8b6f182851ba5875d8ab60529309b4e5c468 --- /dev/null +++ b/sound/soc/omap/omap-abe.c @@ -0,0 +1,1255 @@ +/* + * omap-abe.c -- OMAP ALSA SoC DAI driver using Audio Backend + * + * Copyright (C) 2010 Texas Instruments + * + * Contact: Liam Girdwood + * Misael Lopez Cruz + * Sebastien Guiriec + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "omap-pcm.h" +#include "omap-abe.h" +#include "omap-abe-dsp.h" +#include "abe/abe_main.h" +#include "abe/port_mgr.h" + +#define OMAP_ABE_FORMATS SNDRV_PCM_FMTBIT_S32_LE + +struct omap_abe_data { + /* MODEM FE*/ + struct snd_pcm_substream *modem_substream[2]; + struct snd_soc_dai *modem_dai; + + struct abe *abe; + + /* BE & FE Ports */ + struct omap_abe_port *port[OMAP_ABE_MAX_PORT_ID + 1]; +}; + +/* + * Stream DMA parameters + */ +static struct omap_pcm_dma_data omap_abe_dai_dma_params[7][2] = { +{ + { + .name = "Media Playback", + .dma_req = OMAP44XX_DMA_ABE_REQ_0, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, + { + .name = "Media Capture1", + .dma_req = OMAP44XX_DMA_ABE_REQ_3, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, +}, +{ + {}, + { + .name = "Media Capture2", + .dma_req = OMAP44XX_DMA_ABE_REQ_4, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, +}, +{ + { + .name = "Voice Playback", + .dma_req = OMAP44XX_DMA_ABE_REQ_1, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, + { + .name = "Voice Capture", + .dma_req = OMAP44XX_DMA_ABE_REQ_2, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, +}, +{ + { + .name = "Tones Playback", + .dma_req = OMAP44XX_DMA_ABE_REQ_5, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + },{}, +}, +{ + { + .name = "Vibra Playback", + .dma_req = OMAP44XX_DMA_ABE_REQ_6, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + },{}, +}, +{ + { + .name = "MODEM Playback", + .dma_req = OMAP44XX_DMA_ABE_REQ_1, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, + { + .name = "MODEM Capture", + .dma_req = OMAP44XX_DMA_ABE_REQ_2, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + }, +}, +{ + { + .name = "Low Power Playback", + .dma_req = OMAP44XX_DMA_ABE_REQ_0, + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, + },{}, +},}; + +static int modem_get_dai(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + struct snd_soc_pcm_runtime *modem_rtd; + + abe_priv->modem_substream[substream->stream] = + snd_soc_get_dai_substream(rtd->card, + OMAP_ABE_BE_MM_EXT1, substream->stream); + + if (abe_priv->modem_substream[substream->stream] == NULL) + return -ENODEV; + + modem_rtd = abe_priv->modem_substream[substream->stream]->private_data; + abe_priv->modem_substream[substream->stream]->runtime = substream->runtime; + abe_priv->modem_dai = modem_rtd->cpu_dai; + + return 0; +} + +static void mute_be(struct snd_soc_pcm_runtime *be, + struct snd_soc_dai *dai, int stream) +{ + dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (be->dai_link->be_id) { + case OMAP_ABE_DAI_PDM_DL1: + abe_write_gain(GAINS_DL1, MUTE_GAIN, RAMP_5MS, + GAIN_LEFT_OFFSET); + abe_write_gain(GAINS_DL1, MUTE_GAIN, RAMP_5MS, + GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_DAI_PDM_DL2: + abe_write_gain(GAINS_DL2, MUTE_GAIN, RAMP_5MS, + GAIN_LEFT_OFFSET); + abe_write_gain(GAINS_DL2, MUTE_GAIN, RAMP_5MS, + GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_DAI_PDM_VIB: + case OMAP_ABE_DAI_BT_VX: + case OMAP_ABE_DAI_MM_FM: + case OMAP_ABE_DAI_MODEM: + break; + } + } else { + switch (be->dai_link->be_id) { + case OMAP_ABE_DAI_PDM_UL: + break; + case OMAP_ABE_DAI_BT_VX: + case OMAP_ABE_DAI_MM_FM: + case OMAP_ABE_DAI_MODEM: + case OMAP_ABE_DAI_DMIC0: + case OMAP_ABE_DAI_DMIC1: + case OMAP_ABE_DAI_DMIC2: + break; + } + } +} + +static void unmute_be(struct snd_soc_pcm_runtime *be, + struct snd_soc_dai *dai, int stream) +{ + dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (be->dai_link->be_id) { + case OMAP_ABE_DAI_PDM_DL1: + abe_write_gain(GAINS_DL1, GAIN_0dB, RAMP_5MS, + GAIN_LEFT_OFFSET); + abe_write_gain(GAINS_DL1, GAIN_0dB, RAMP_5MS, + GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_DAI_PDM_DL2: + abe_write_gain(GAINS_DL2, GAIN_0dB, RAMP_5MS, + GAIN_LEFT_OFFSET); + abe_write_gain(GAINS_DL2, GAIN_0dB, RAMP_5MS, + GAIN_RIGHT_OFFSET); + break; + case OMAP_ABE_DAI_PDM_VIB: + case OMAP_ABE_DAI_BT_VX: + case OMAP_ABE_DAI_MM_FM: + case OMAP_ABE_DAI_MODEM: + break; + } + } else { + + switch (be->dai_link->be_id) { + case OMAP_ABE_DAI_PDM_UL: + break; + case OMAP_ABE_DAI_BT_VX: + case OMAP_ABE_DAI_MM_FM: + case OMAP_ABE_DAI_MODEM: + case OMAP_ABE_DAI_DMIC0: + case OMAP_ABE_DAI_DMIC1: + case OMAP_ABE_DAI_DMIC2: + break; + } + } +} + +static void enable_be_port(struct snd_soc_pcm_runtime *be, + struct snd_soc_dai *dai, int stream) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + abe_data_format_t format; + + dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); + + switch (be->dai_link->be_id) { + /* McPDM Downlink is special case and handled by McPDM driver */ + case OMAP_ABE_DAI_PDM_DL1: + case OMAP_ABE_DAI_PDM_DL2: + case OMAP_ABE_DAI_PDM_VIB: + case OMAP_ABE_DAI_PDM_UL: + break; + case OMAP_ABE_DAI_BT_VX: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + + /* port can only be configured if it's not running */ + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL])) + return; + + /* BT_DL connection to McBSP 1 ports */ + format.f = 8000; + format.samp_format = MONO_RSHIFTED_16; + abe_connect_serial_port(BT_VX_DL_PORT, &format, MCBSP1_TX); + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL]); + } else { + + /* port can only be configured if it's not running */ + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL])) + return; + + /* BT_UL connection to McBSP 1 ports */ + format.f = 8000; + format.samp_format = MONO_RSHIFTED_16; + abe_connect_serial_port(BT_VX_UL_PORT, &format, MCBSP1_RX); + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL]); + } + break; + case OMAP_ABE_DAI_MM_FM: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + + /* port can only be configured if it's not running */ + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL])) + return; + + /* MM_EXT connection to McBSP 2 ports */ + format.f = 48000; + format.samp_format = STEREO_RSHIFTED_16; + abe_connect_serial_port(MM_EXT_OUT_PORT, &format, MCBSP2_TX); + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL]); + } else { + + /* port can only be configured if it's not running */ + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_UL])) + return; + + /* MM_EXT connection to McBSP 2 ports */ + format.f = 48000; + format.samp_format = STEREO_RSHIFTED_16; + abe_connect_serial_port(MM_EXT_IN_PORT, &format, MCBSP2_RX); + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_UL]); + } + break; + case OMAP_ABE_DAI_DMIC0: + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_DMIC0]); + break; + case OMAP_ABE_DAI_DMIC1: + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_DMIC1]); + break; + case OMAP_ABE_DAI_DMIC2: + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_DMIC2]); + break; + } +} + +static void enable_fe_port(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); + + switch(dai->id) { + case ABE_FRONTEND_DAI_MEDIA: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_MM_DL1]); + else + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_MM_UL1]); + break; + case ABE_FRONTEND_DAI_LP_MEDIA: + abe_enable_data_transfer(MM_DL_PORT); + break; + case ABE_FRONTEND_DAI_MEDIA_CAPTURE: + if (stream == SNDRV_PCM_STREAM_CAPTURE) + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_MM_UL2]); + break; + case ABE_FRONTEND_DAI_MODEM: + case ABE_FRONTEND_DAI_VOICE: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_VX_DL]); + else + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_VX_UL]); + break; + case ABE_FRONTEND_DAI_TONES: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_TONES]); + break; + case ABE_FRONTEND_DAI_VIBRA: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_enable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_VIB]); + break; + } +} + +static void disable_be_port(struct snd_soc_pcm_runtime *be, + struct snd_soc_dai *dai, int stream) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); + + switch (be->dai_link->be_id) { + /* McPDM Downlink is special case and handled by McPDM driver */ + case OMAP_ABE_DAI_PDM_DL1: + case OMAP_ABE_DAI_PDM_DL2: + case OMAP_ABE_DAI_PDM_VIB: + case OMAP_ABE_DAI_PDM_UL: + break; + case OMAP_ABE_DAI_BT_VX: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL]); + else + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL]); + break; + case OMAP_ABE_DAI_MM_FM: + case OMAP_ABE_DAI_MODEM: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL]); + else + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_UL]); + break; + case OMAP_ABE_DAI_DMIC0: + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_DMIC0]); + break; + case OMAP_ABE_DAI_DMIC1: + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_DMIC1]); + break; + case OMAP_ABE_DAI_DMIC2: + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_DMIC2]); + break; + } +} + +static void disable_fe_port(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); + + switch(dai->id) { + case ABE_FRONTEND_DAI_MEDIA: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_MM_DL1]); + else + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_MM_UL1]); + break; + case ABE_FRONTEND_DAI_LP_MEDIA: + abe_disable_data_transfer(MM_DL_PORT); + break; + case ABE_FRONTEND_DAI_MEDIA_CAPTURE: + if (stream == SNDRV_PCM_STREAM_CAPTURE) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_MM_UL2]); + break; + case ABE_FRONTEND_DAI_MODEM: + case ABE_FRONTEND_DAI_VOICE: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_VX_DL]); + else + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_VX_UL]); + break; + case ABE_FRONTEND_DAI_TONES: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_TONES]); + break; + case ABE_FRONTEND_DAI_VIBRA: + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_abe_port_disable(abe_priv->abe, + abe_priv->port[OMAP_ABE_FE_PORT_VIB]); + break; + } +} + +static void mute_fe_port(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); + + switch(dai->id) { + case ABE_FRONTEND_DAI_MEDIA: + case ABE_FRONTEND_DAI_LP_MEDIA: + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1])) + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); + break; + case ABE_FRONTEND_DAI_VOICE: + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1])) + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); + break; + case ABE_FRONTEND_DAI_TONES: + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) + abe_mute_gain(MIXDL2, MIX_DL2_INPUT_TONES); + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1])) + abe_mute_gain(MIXDL1, MIX_DL1_INPUT_TONES); + break; + case ABE_FRONTEND_DAI_VIBRA: + case ABE_FRONTEND_DAI_MEDIA_CAPTURE: + break; + } +} + +static void unmute_fe_port(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); + + switch(dai->id) { + case ABE_FRONTEND_DAI_MEDIA: + case ABE_FRONTEND_DAI_LP_MEDIA: + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1])) + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); + break; + case ABE_FRONTEND_DAI_VOICE: + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1])) + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); + break; + case ABE_FRONTEND_DAI_TONES: + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) + abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_TONES); + if (omap_abe_port_is_enabled(abe_priv->abe, + abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1])) + abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_TONES); + break; + case ABE_FRONTEND_DAI_VIBRA: + case ABE_FRONTEND_DAI_MEDIA_CAPTURE: + break; + } +} + +static void capture_trigger(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int cmd) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_dsp_params *dsp_params, *tmp; + struct snd_pcm_substream *be_substream; + int stream = substream->stream; + + dev_dbg(&fe->dev, "%s: %s %d\n", __func__, fe->cpu_dai->name, stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + + /* mute and enable BE ports */ + list_for_each_entry_safe(dsp_params, tmp, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; + + /* does this trigger() apply to this BE and stream ? */ + if (!snd_soc_dsp_is_trigger_for_be(fe, be, stream)) + continue; + + /* is the BE already in the trigger START state ? */ + if (dsp_params->state == SND_SOC_DSP_LINK_STATE_START) + continue; + + be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + + /* mute the BE port */ + mute_be(be, dai, stream); + + /* enable the BE port */ + enable_be_port(be, dai, stream); + + /* DAI work must be started/stopped at least 250us after ABE */ + udelay(250); + + /* trigger the BE port */ + snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + } + + /* does this trigger() apply to the FE ? */ + if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { + /* Enable Frontend sDMA */ + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + enable_fe_port(substream, dai, stream); + } + + /* Restore ABE GAINS AMIC */ + list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; + + /* does this trigger() apply to this BE and stream ? */ + if (!snd_soc_dsp_is_trigger_for_be(fe, be, stream)) + continue; + + /* unmute this BE port */ + unmute_be(be, dai, stream); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* Enable sDMA */ + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + enable_fe_port(substream, dai, stream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* Disable sDMA */ + disable_fe_port(substream, dai, stream); + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + break; + case SNDRV_PCM_TRIGGER_STOP: + + /* does this trigger() apply to the FE ? */ + if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { + /* Disable sDMA */ + disable_fe_port(substream, dai, stream); + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + } + + /* disable BE ports */ + list_for_each_entry_safe(dsp_params, tmp, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; + + /* does this trigger() apply to this BE and stream ? */ + if (!snd_soc_dsp_is_trigger_for_be(fe, be, stream)) + continue; + + /* only STOP BE in FREE state */ + /* REVISIT: Investigate the appropriate state to check against */ + //if (dsp_params->state != SND_SOC_DSP_LINK_STATE_FREE) + // continue; + + be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + + /* disable the BE port */ + disable_be_port(be, dai, stream); + + /* DAI work must be started/stopped at least 250us after ABE */ + udelay(250); + + /* trigger BE port */ + snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + } + break; + default: + break; + } +} + +static void playback_trigger(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int cmd) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_dsp_params *dsp_params, *tmp; + struct snd_pcm_substream *be_substream; + int stream = substream->stream; + + dev_dbg(&fe->dev, "%s: %s %d\n", __func__, fe->cpu_dai->name, stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + + /* mute and enable ports */ + list_for_each_entry_safe(dsp_params, tmp, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; + + /* does this trigger() apply to the FE ? */ + if (!snd_soc_dsp_is_trigger_for_be(fe, be, stream)) + continue; + + /* is the BE already in the trigger START state ? */ + if (dsp_params->state == SND_SOC_DSP_LINK_STATE_START) + continue; + + be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + + /* mute BE port */ + mute_be(be, dai, stream); + + /* enabled BE port */ + enable_be_port(be, dai, stream); + + /* DAI work must be started/stopped at least 250us after ABE */ + udelay(250); + + /* trigger BE port */ + snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + + /* unmute the BE port */ + unmute_be(be, dai, stream); + } + + /* does this trigger() apply to the FE ? */ + if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { + + /* Enable Frontend sDMA */ + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + enable_fe_port(substream, dai, stream); + + /* unmute FE port */ + unmute_fe_port(substream, dai, stream); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* Enable Frontend sDMA */ + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + enable_fe_port(substream, dai, stream); + + /* unmute FE port */ + unmute_fe_port(substream, dai, stream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* disable Frontend sDMA */ + disable_fe_port(substream, dai, stream); + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + + /* mute FE port */ + mute_fe_port(substream, dai, stream); + break; + case SNDRV_PCM_TRIGGER_STOP: + + /* does this trigger() apply to the FE ? */ + if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { + + /* disable the transfer */ + disable_fe_port(substream, dai, stream); + snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); + + /* mute FE port */ + mute_fe_port(substream, dai, stream); + } + + /* disable BE ports */ + list_for_each_entry_safe(dsp_params, tmp, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; + + /* does this trigger() apply to this BE and stream ? */ + if (!snd_soc_dsp_is_trigger_for_be(fe, be, stream)) + continue; + + /* only STOP BE in FREE state */ + if (dsp_params->state != SND_SOC_DSP_LINK_STATE_FREE) + continue; + + be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + + /* disable the BE */ + disable_be_port(be, dai, stream); + + /* DAI work must be started/stopped at least 250us after ABE */ + udelay(250); + + /* trigger the BE port */ + snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + } + break; + default: + break; + } +} + +static int omap_abe_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + + ret = modem_get_dai(substream, dai); + if (ret < 0) { + dev_err(dai->dev, "failed to get MODEM DAI\n"); + return ret; + } + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", + __func__, substream->stream); + + ret = snd_soc_dai_startup(abe_priv->modem_substream[substream->stream], + abe_priv->modem_dai); + if (ret < 0) { + dev_err(abe_priv->modem_dai->dev, "failed to open DAI %d\n", ret); + return ret; + } + } + + return ret; +} + +static int omap_abe_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + abe_data_format_t format; + abe_dma_t dma_sink; + abe_dma_t dma_params; + int ret; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + switch (params_channels(params)) { + case 1: + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + format.samp_format = MONO_RSHIFTED_16; + else + format.samp_format = MONO_MSB; + break; + case 2: + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + format.samp_format = STEREO_16_16; + else + format.samp_format = STEREO_MSB; + break; + case 3: + format.samp_format = THREE_MSB; + break; + case 4: + format.samp_format = FOUR_MSB; + break; + case 5: + format.samp_format = FIVE_MSB; + break; + case 6 : + format.samp_format = SIX_MSB; + break; + case 7 : + format.samp_format = SEVEN_MSB; + break; + case 8: + format.samp_format = EIGHT_MSB; + break; + default: + dev_err(dai->dev, "%d channels not supported", + params_channels(params)); + return -EINVAL; + } + + format.f = params_rate(params); + + switch (dai->id) { + case ABE_FRONTEND_DAI_MEDIA: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + abe_connect_cbpr_dmareq_port(MM_DL_PORT, &format, ABE_CBPR0_IDX, + &dma_sink); + abe_read_port_address(MM_DL_PORT, &dma_params); + } else { + abe_connect_cbpr_dmareq_port(MM_UL_PORT, &format, ABE_CBPR3_IDX, + &dma_sink); + abe_read_port_address(MM_UL_PORT, &dma_params); + } + break; + case ABE_FRONTEND_DAI_LP_MEDIA: + return 0; + break; + case ABE_FRONTEND_DAI_MEDIA_CAPTURE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + else { + abe_connect_cbpr_dmareq_port(MM_UL2_PORT, &format, ABE_CBPR4_IDX, + &dma_sink); + abe_read_port_address(MM_UL2_PORT, &dma_params); + } + break; + case ABE_FRONTEND_DAI_VOICE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + abe_connect_cbpr_dmareq_port(VX_DL_PORT, &format, ABE_CBPR1_IDX, + &dma_sink); + abe_read_port_address(VX_DL_PORT, &dma_params); + } else { + abe_connect_cbpr_dmareq_port(VX_UL_PORT, &format, ABE_CBPR2_IDX, + &dma_sink); + abe_read_port_address(VX_UL_PORT, &dma_params); + } + break; + case ABE_FRONTEND_DAI_TONES: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + abe_connect_cbpr_dmareq_port(TONES_DL_PORT, &format, ABE_CBPR5_IDX, + &dma_sink); + abe_read_port_address(TONES_DL_PORT, &dma_params); + } else + return -EINVAL; + break; + case ABE_FRONTEND_DAI_VIBRA: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + abe_connect_cbpr_dmareq_port(VIB_DL_PORT, &format, ABE_CBPR6_IDX, + &dma_sink); + abe_read_port_address(VIB_DL_PORT, &dma_params); + } else + return -EINVAL; + break; + case ABE_FRONTEND_DAI_MODEM: + /* MODEM is special case where data IO is performed by McBSP2 + * directly onto VX_DL and VX_UL (instead of SDMA). + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Vx_DL connection to McBSP 2 ports */ + format.samp_format = STEREO_RSHIFTED_16; + abe_connect_serial_port(VX_DL_PORT, &format, MCBSP2_RX); + abe_read_port_address(VX_DL_PORT, &dma_params); + } else { + /* Vx_UL connection to McBSP 2 ports */ + format.samp_format = STEREO_RSHIFTED_16; + abe_connect_serial_port(VX_UL_PORT, &format, MCBSP2_TX); + abe_read_port_address(VX_UL_PORT, &dma_params); + } + break; + } + + /* configure frontend SDMA data */ + omap_abe_dai_dma_params[dai->id][substream->stream].port_addr = + (unsigned long)dma_params.data; + omap_abe_dai_dma_params[dai->id][substream->stream].packet_size = + dma_params.iter; + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + /* call hw_params on McBSP with correct DMA data */ + snd_soc_dai_set_dma_data(abe_priv->modem_dai, substream, + &omap_abe_dai_dma_params[dai->id][substream->stream]); + + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", + __func__, substream->stream); + + ret = snd_soc_dai_hw_params(abe_priv->modem_substream[substream->stream], + params, abe_priv->modem_dai); + if (ret < 0) + dev_err(abe_priv->modem_dai->dev, "MODEM hw_params failed\n"); + return ret; + } + + snd_soc_dai_set_dma_data(dai, substream, + &omap_abe_dai_dma_params[dai->id][substream->stream]); + + return 0; +} + +static int omap_abe_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + ret = snd_soc_dai_prepare(abe_priv->modem_substream[substream->stream], + abe_priv->modem_dai); + + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", + __func__, substream->stream); + + if (ret < 0) { + dev_err(abe_priv->modem_dai->dev, "MODEM prepare failed\n"); + return ret; + } + } + return ret; +} + +static int omap_abe_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s: %s cmd %d\n", __func__, dai->name, cmd); + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d cmd %d\n", + __func__, substream->stream, cmd); + + ret = snd_soc_dai_trigger(abe_priv->modem_substream[substream->stream], + cmd, abe_priv->modem_dai); + if (ret < 0) { + dev_err(abe_priv->modem_dai->dev, "MODEM trigger failed\n"); + return ret; + } + } + + return ret; +} + +static int omap_abe_dai_bespoke_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s: %s cmd %d\n", __func__, dai->name, cmd); + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d cmd %d\n", + __func__, substream->stream, cmd); + + ret = snd_soc_dai_trigger(abe_priv->modem_substream[substream->stream], + cmd, abe_priv->modem_dai); + if (ret < 0) { + dev_err(abe_priv->modem_dai->dev, "MODEM trigger failed\n"); + return ret; + } + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + playback_trigger(substream, dai, cmd); + else + capture_trigger(substream, dai, cmd); + + return ret; +} + +static int omap_abe_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", + __func__, substream->stream); + + ret = snd_soc_dai_hw_free(abe_priv->modem_substream[substream->stream], + abe_priv->modem_dai); + if (ret < 0) { + dev_err(abe_priv->modem_dai->dev, "MODEM hw_free failed\n"); + return ret; + } + } + return ret; +} + +static void omap_abe_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + if (dai->id == ABE_FRONTEND_DAI_MODEM) { + dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", + __func__, substream->stream); + + snd_soc_dai_shutdown(abe_priv->modem_substream[substream->stream], + abe_priv->modem_dai); + } +} + +static int omap_abe_dai_probe(struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv; + int i; + + abe_priv = kzalloc(sizeof(struct omap_abe_data), GFP_KERNEL); + if (abe_priv == NULL) + return -ENOMEM; + + abe_priv->abe = omap_abe_port_mgr_get(); + if (!abe_priv->abe) + goto err; + + for (i = 0; i <= OMAP_ABE_MAX_PORT_ID; i++) { + + abe_priv->port[i] = omap_abe_port_open(abe_priv->abe, i); + if (abe_priv->port[i] == NULL) { + for (--i; i >= 0; i--) + omap_abe_port_close(abe_priv->abe, abe_priv->port[i]); + + goto err_port; + } + } + + snd_soc_dai_set_drvdata(dai, abe_priv); + return 0; + +err_port: + omap_abe_port_mgr_put(abe_priv->abe); +err: + kfree(abe_priv); + return -ENOMEM; +} + +static int omap_abe_dai_remove(struct snd_soc_dai *dai) +{ + struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); + + omap_abe_port_mgr_put(abe_priv->abe); + kfree(abe_priv); + return 0; +} + +static struct snd_soc_dai_ops omap_abe_dai_ops = { + .startup = omap_abe_dai_startup, + .shutdown = omap_abe_dai_shutdown, + .hw_params = omap_abe_dai_hw_params, + .hw_free = omap_abe_dai_hw_free, + .prepare = omap_abe_dai_prepare, + .trigger = omap_abe_dai_trigger, + .bespoke_trigger = omap_abe_dai_bespoke_trigger, +}; + +static struct snd_soc_dai_driver omap_abe_dai[] = { + { /* Multimedia Playback and Capture */ + .name = "MultiMedia1", + .probe = omap_abe_dai_probe, + .remove = omap_abe_dai_remove, + .playback = { + .stream_name = "MultiMedia1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = OMAP_ABE_FORMATS, + }, + .capture = { + .stream_name = "MultiMedia1 Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = OMAP_ABE_FORMATS, + }, + .ops = &omap_abe_dai_ops, + }, + { /* Multimedia Capture */ + .name = "MultiMedia2", + .capture = { + .stream_name = "MultiMedia2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = OMAP_ABE_FORMATS, + }, + .ops = &omap_abe_dai_ops, + }, + { /* Voice Playback and Capture */ + .name = "Voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = OMAP_ABE_FORMATS, + }, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = OMAP_ABE_FORMATS, + }, + .ops = &omap_abe_dai_ops, + }, + { /* Tones Playback */ + .name = "Tones", + .playback = { + .stream_name = "Tones Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = OMAP_ABE_FORMATS, + }, + .ops = &omap_abe_dai_ops, + }, + { /* Vibra */ + .name = "Vibra", + .playback = { + .stream_name = "Vibra Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = OMAP_ABE_FORMATS, + }, + .ops = &omap_abe_dai_ops, + }, + { /* MODEM Voice Playback and Capture */ + .name = "MODEM", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = OMAP_ABE_FORMATS | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = OMAP_ABE_FORMATS | SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &omap_abe_dai_ops, + }, + { /* Low Power HiFi Playback */ + .name = "MultiMedia1 LP", + .playback = { + .stream_name = "MultiMedia1 LP Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = OMAP_ABE_FORMATS | SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &omap_abe_dai_ops, + }, +}; + +static int __devinit omap_abe_probe(struct platform_device *pdev) +{ + return snd_soc_register_dais(&pdev->dev, omap_abe_dai, + ARRAY_SIZE(omap_abe_dai)); +} + +static int __devexit omap_abe_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(omap_abe_dai)); + return 0; +} + +static struct platform_driver omap_abe_driver = { + .driver = { + .name = "omap-abe-dai", + .owner = THIS_MODULE, + }, + .probe = omap_abe_probe, + .remove = __devexit_p(omap_abe_remove), +}; + +static int __init omap_abe_init(void) +{ + return platform_driver_register(&omap_abe_driver); +} +module_init(omap_abe_init); + +static void __exit omap_abe_exit(void) +{ + platform_driver_unregister(&omap_abe_driver); +} +module_exit(omap_abe_exit); + +MODULE_AUTHOR("Liam Girdwood "); +MODULE_DESCRIPTION("OMAP ABE SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-abe.h b/sound/soc/omap/omap-abe.h new file mode 100644 index 0000000000000000000000000000000000000000..f6fad970df6c34d039d1519c7252fa0d0875349e --- /dev/null +++ b/sound/soc/omap/omap-abe.h @@ -0,0 +1,59 @@ +/* + * omap-abe.h + * + * Copyright (C) 2010 Texas Instruments + * + * Contact: Liam Girdwood + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __OMAP_ABE_H__ +#define __OMAP_ABE_H__ + +#define ABE_FRONTEND_DAI_MEDIA 0 +#define ABE_FRONTEND_DAI_MEDIA_CAPTURE 1 +#define ABE_FRONTEND_DAI_VOICE 2 +#define ABE_FRONTEND_DAI_TONES 3 +#define ABE_FRONTEND_DAI_VIBRA 4 +#define ABE_FRONTEND_DAI_MODEM 5 +#define ABE_FRONTEND_DAI_LP_MEDIA 6 + +/* This must currently match the BE order in DSP */ +#define OMAP_ABE_DAI_PDM_UL 0 +#define OMAP_ABE_DAI_PDM_DL1 1 +#define OMAP_ABE_DAI_PDM_DL2 2 +#define OMAP_ABE_DAI_PDM_VIB 3 +#define OMAP_ABE_DAI_BT_VX 4 +#define OMAP_ABE_DAI_MM_FM 5 +#define OMAP_ABE_DAI_MODEM 6 +#define OMAP_ABE_DAI_DMIC0 7 +#define OMAP_ABE_DAI_DMIC1 8 +#define OMAP_ABE_DAI_DMIC2 9 + +#define OMAP_ABE_BE_PDM_DL1 "PDM-DL1" +#define OMAP_ABE_BE_PDM_UL1 "PDM-UL1" +#define OMAP_ABE_BE_PDM_DL2 "PDM-DL2" +#define OMAP_ABE_BE_PDM_VIB "PDM-VIB" +#define OMAP_ABE_BE_BT_VX "BT-VX" +#define OMAP_ABE_BE_MM_EXT0 "FM-EXT" +#define OMAP_ABE_BE_MM_EXT1 "MODEM-EXT" +#define OMAP_ABE_BE_DMIC0 "DMIC0" +#define OMAP_ABE_BE_DMIC1 "DMIC1" +#define OMAP_ABE_BE_DMIC2 "DMIC2" + + +#endif /* End of __OMAP_MCPDM_H__ */ diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c index 5cfcc655e95ffe33670c68e0e998e7a1ceec17af..1a970a6a72e6a98036789c2a885d91ac3386521a 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-core.c b/sound/soc/soc-core.c index c88d9741b9e7942e60ba17e3d98231136b607dc9..4522d150f94264bdd598e27ccf9ed5eca81b1da6 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #define CREATE_TRACE_POINTS @@ -59,12 +60,22 @@ static LIST_HEAD(dai_list); static LIST_HEAD(platform_list); static LIST_HEAD(codec_list); +int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num); +int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd); +int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute); +int soc_dpcm_be_ac97_cpu_dai_suspend(struct snd_soc_pcm_runtime *fe); +int soc_dpcm_be_ac97_cpu_dai_resume(struct snd_soc_pcm_runtime *fe); +int soc_dpcm_be_cpu_dai_resume(struct snd_soc_pcm_runtime *fe); +int soc_dpcm_be_cpu_dai_suspend(struct snd_soc_pcm_runtime *fe); +int soc_dpcm_be_platform_suspend(struct snd_soc_pcm_runtime *fe); +int soc_dpcm_be_platform_resume(struct snd_soc_pcm_runtime *fe); + /* * This is a timeout to do a DAPM powerdown after a stream is closed(). * It can be used to eliminate pops between different playback streams, e.g. * between two audio tracks. */ -static int pmdown_time = 5000; +static int pmdown_time; module_param(pmdown_time, int, 0); MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)"); @@ -201,6 +212,12 @@ static ssize_t pmdown_time_set(struct device *dev, static DEVICE_ATTR(pmdown_time, 0644, pmdown_time_show, pmdown_time_set); #ifdef CONFIG_DEBUG_FS +static int codec_reg_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + static ssize_t codec_reg_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { @@ -258,7 +275,7 @@ static ssize_t codec_reg_write_file(struct file *file, } static const struct file_operations codec_reg_fops = { - .open = simple_open, + .open = codec_reg_open_file, .read = codec_reg_read_file, .write = codec_reg_write_file, .llseek = default_llseek, @@ -271,7 +288,8 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec) codec->debugfs_codec_root = debugfs_create_dir(codec->name, debugfs_card_root); if (!codec->debugfs_codec_root) { - dev_warn(codec->dev, "Failed to create codec debugfs directory\n"); + printk(KERN_WARNING + "ASoC: Failed to create codec debugfs directory\n"); return; } @@ -284,7 +302,8 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec) codec->debugfs_codec_root, codec, &codec_reg_fops); if (!codec->debugfs_reg) - dev_warn(codec->dev, "Failed to create codec register debugfs file\n"); + printk(KERN_WARNING + "ASoC: Failed to create codec register debugfs file\n"); snd_soc_dapm_debugfs_init(&codec->dapm, codec->debugfs_codec_root); } @@ -301,13 +320,12 @@ static void soc_init_platform_debugfs(struct snd_soc_platform *platform) platform->debugfs_platform_root = debugfs_create_dir(platform->name, debugfs_card_root); if (!platform->debugfs_platform_root) { - dev_warn(platform->dev, - "Failed to create platform debugfs directory\n"); + printk(KERN_WARNING + "ASoC: Failed to create platform debugfs directory\n"); return; } - snd_soc_dapm_debugfs_init(&platform->dapm, - platform->debugfs_platform_root); + snd_soc_dapm_debugfs_init(&platform->dapm, platform->debugfs_platform_root); } static void soc_cleanup_platform_debugfs(struct snd_soc_platform *platform) @@ -465,6 +483,35 @@ static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) } #endif +struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, + const char *dai_link, int stream) +{ + int i; + + for (i = 0; i < card->num_links; i++) { + if (card->rtd[i].dai_link->no_pcm && + !strcmp(card->rtd[i].dai_link->name, dai_link)) + return card->rtd[i].pcm->streams[stream].substream; + } + dev_dbg(card->dev, "failed to find dai link %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_substream); + +struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, + const char *dai_link) +{ + int i; + + for (i = 0; i < card->num_links; i++) { + if (!strcmp(card->rtd[i].dai_link->name, dai_link)) + return &card->rtd[i]; + } + dev_dbg(card->dev, "failed to find rtd %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime); + #ifdef CONFIG_SND_SOC_AC97_BUS /* unregister ac97 codec */ static int soc_ac97_dev_unregister(struct snd_soc_codec *codec) @@ -527,16 +574,22 @@ int snd_soc_suspend(struct device *dev) struct snd_soc_dai *dai = card->rtd[i].codec_dai; struct snd_soc_dai_driver *drv = dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 1); + if (card->rtd[i].dai_link->dynamic) + soc_dpcm_be_digital_mute(&card->rtd[i], 1); + else { + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 1); + } } /* suspend all pcms */ for (i = 0; i < card->num_rtd; i++) { - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; snd_pcm_suspend_all(card->rtd[i].pcm); @@ -549,14 +602,20 @@ int snd_soc_suspend(struct device *dev) struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; struct snd_soc_platform *platform = card->rtd[i].platform; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control) - cpu_dai->driver->suspend(cpu_dai); - if (platform->driver->suspend && !platform->suspended) { - platform->driver->suspend(cpu_dai); - platform->suspended = 1; + if (card->rtd[i].dai_link->dynamic) { + soc_dpcm_be_cpu_dai_suspend(&card->rtd[i]); + soc_dpcm_be_platform_suspend(&card->rtd[i]); + } else { + if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control) + cpu_dai->driver->suspend(cpu_dai); + if (platform->driver->suspend && !platform->suspended) { + platform->driver->suspend(cpu_dai); + platform->suspended = 1; + } } } @@ -567,20 +626,19 @@ int snd_soc_suspend(struct device *dev) } for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; + struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - snd_soc_dapm_stream_event(&card->rtd[i], - SNDRV_PCM_STREAM_PLAYBACK, - codec_dai, - SND_SOC_DAPM_STREAM_SUSPEND); + if (driver->playback.stream_name != NULL) + snd_soc_dapm_stream_event(&card->rtd[i], driver->playback.stream_name, + SND_SOC_DAPM_STREAM_SUSPEND); - snd_soc_dapm_stream_event(&card->rtd[i], - SNDRV_PCM_STREAM_CAPTURE, - codec_dai, - SND_SOC_DAPM_STREAM_SUSPEND); + if (driver->capture.stream_name != NULL) + snd_soc_dapm_stream_event(&card->rtd[i], driver->capture.stream_name, + SND_SOC_DAPM_STREAM_SUSPEND); } /* suspend all CODECs */ @@ -590,17 +648,6 @@ int snd_soc_suspend(struct device *dev) if (!codec->suspended && codec->driver->suspend) { switch (codec->dapm.bias_level) { case SND_SOC_BIAS_STANDBY: - /* - * If the CODEC is capable of idle - * bias off then being in STANDBY - * means it's doing something, - * otherwise fall through. - */ - if (codec->dapm.idle_bias_off) { - dev_dbg(codec->dev, - "idle_bias_off CODEC on over suspend\n"); - break; - } case SND_SOC_BIAS_OFF: codec->driver->suspend(codec); codec->suspended = 1; @@ -616,11 +663,15 @@ int snd_soc_suspend(struct device *dev) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control) - cpu_dai->driver->suspend(cpu_dai); + if (card->rtd[i].dai_link->dynamic) + soc_dpcm_be_ac97_cpu_dai_suspend(&card->rtd[i]); + else + if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control) + cpu_dai->driver->suspend(cpu_dai); } if (card->suspend_post) @@ -656,11 +707,15 @@ static void soc_resume_deferred(struct work_struct *work) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control) - cpu_dai->driver->resume(cpu_dai); + if (card->rtd[i].dai_link->dynamic) + soc_dpcm_be_ac97_cpu_dai_resume(&card->rtd[i]); + else + if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control) + cpu_dai->driver->resume(cpu_dai); } list_for_each_entry(codec, &card->codec_dev_list, card_list) { @@ -683,18 +738,19 @@ static void soc_resume_deferred(struct work_struct *work) } for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; + struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - snd_soc_dapm_stream_event(&card->rtd[i], - SNDRV_PCM_STREAM_PLAYBACK, codec_dai, - SND_SOC_DAPM_STREAM_RESUME); + if (driver->playback.stream_name != NULL) + snd_soc_dapm_stream_event(&card->rtd[i], driver->playback.stream_name, + SND_SOC_DAPM_STREAM_RESUME); - snd_soc_dapm_stream_event(&card->rtd[i], - SNDRV_PCM_STREAM_CAPTURE, codec_dai, - SND_SOC_DAPM_STREAM_RESUME); + if (driver->capture.stream_name != NULL) + snd_soc_dapm_stream_event(&card->rtd[i], driver->capture.stream_name, + SND_SOC_DAPM_STREAM_RESUME); } /* unmute any active DACs */ @@ -702,25 +758,36 @@ static void soc_resume_deferred(struct work_struct *work) struct snd_soc_dai *dai = card->rtd[i].codec_dai; struct snd_soc_dai_driver *drv = dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 0); + if (card->rtd[i].dai_link->dynamic) + soc_dpcm_be_digital_mute(&card->rtd[i], 0); + else { + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 0); + } } for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; struct snd_soc_platform *platform = card->rtd[i].platform; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control) - cpu_dai->driver->resume(cpu_dai); - if (platform->driver->resume && platform->suspended) { - platform->driver->resume(cpu_dai); - platform->suspended = 0; + if (card->rtd[i].dai_link->dynamic) { + soc_dpcm_be_cpu_dai_resume(&card->rtd[i]); + soc_dpcm_be_platform_resume(&card->rtd[i]); + } else { + if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control) + cpu_dai->driver->resume(cpu_dai); + if (platform->driver->resume && platform->suspended) { + platform->driver->resume(cpu_dai); + platform->suspended = 0; + } } } @@ -927,8 +994,7 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order) if (codec_dai->driver->remove) { err = codec_dai->driver->remove(codec_dai); if (err < 0) - pr_err("asoc: failed to remove %s: %d\n", - codec_dai->name, err); + printk(KERN_ERR "asoc: failed to remove %s\n", codec_dai->name); } codec_dai->probed = 0; list_del(&codec_dai->card_list); @@ -940,10 +1006,8 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order) if (platform->driver->remove) { err = platform->driver->remove(platform); if (err < 0) - pr_err("asoc: failed to remove %s: %d\n", - platform->name, err); + printk(KERN_ERR "asoc: failed to remove %s\n", platform->name); } - /* Make sure all DAPM widgets are freed */ snd_soc_dapm_free(&platform->dapm); @@ -964,8 +1028,7 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order) if (cpu_dai->driver->remove) { err = cpu_dai->driver->remove(cpu_dai); if (err < 0) - pr_err("asoc: failed to remove %s: %d\n", - cpu_dai->name, err); + printk(KERN_ERR "asoc: failed to remove %s\n", cpu_dai->name); } cpu_dai->probed = 0; list_del(&cpu_dai->card_list); @@ -1007,7 +1070,6 @@ static int soc_probe_codec(struct snd_soc_card *card, { int ret = 0; const struct snd_soc_codec_driver *driver = codec->driver; - struct snd_soc_dai *dai; codec->card = card; codec->dapm.card = card; @@ -1022,14 +1084,6 @@ static int soc_probe_codec(struct snd_soc_card *card, snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets, driver->num_dapm_widgets); - /* Create DAPM widgets for each DAI stream */ - list_for_each_entry(dai, &dai_list, list) { - if (dai->dev != codec->dev) - continue; - - snd_soc_dapm_new_dai_widgets(&codec->dapm, dai); - } - codec->dapm.idle_bias_off = driver->idle_bias_off; if (driver->probe) { @@ -1081,8 +1135,6 @@ static int soc_probe_platform(struct snd_soc_card *card, snd_soc_dapm_new_controls(&platform->dapm, driver->dapm_widgets, driver->num_dapm_widgets); - platform->dapm.idle_bias_off = 1; - if (driver->probe) { ret = driver->probe(platform); if (ret < 0) { @@ -1108,7 +1160,6 @@ static int soc_probe_platform(struct snd_soc_card *card, return 0; err_probe: - soc_cleanup_platform_debugfs(platform); module_put(platform->dev->driver->owner); return ret; @@ -1170,6 +1221,10 @@ static int soc_post_component_init(struct snd_soc_card *card, rtd->dev->init_name = name; dev_set_drvdata(rtd->dev, rtd); mutex_init(&rtd->pcm_mutex); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].fe_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].fe_clients); ret = device_add(rtd->dev); if (ret < 0) { dev_err(card->dev, @@ -1191,6 +1246,17 @@ static int soc_post_component_init(struct snd_soc_card *card, dev_err(codec->dev, "asoc: failed to add codec sysfs files: %d\n", ret); +#ifdef CONFIG_DEBUG_FS + /* add DSP sysfs entries */ + if (!dai_link->dynamic) + goto out; + + ret = soc_dpcm_debugfs_add(rtd); + if (ret < 0) + dev_err(rtd->dev, "asoc: failed to add dpcm sysfs entries: %d\n", ret); + +out: +#endif return 0; } @@ -1224,8 +1290,8 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) if (cpu_dai->driver->probe) { ret = cpu_dai->driver->probe(cpu_dai); if (ret < 0) { - pr_err("asoc: failed to probe CPU DAI %s: %d\n", - cpu_dai->name, ret); + printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n", + cpu_dai->name); module_put(cpu_dai->dev->driver->owner); return ret; } @@ -1256,8 +1322,8 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) if (codec_dai->driver->probe) { ret = codec_dai->driver->probe(codec_dai); if (ret < 0) { - pr_err("asoc: failed to probe CODEC DAI %s: %d\n", - codec_dai->name, ret); + printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n", + codec_dai->name); return ret; } } @@ -1277,13 +1343,12 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) ret = device_create_file(rtd->dev, &dev_attr_pmdown_time); if (ret < 0) - pr_warn("asoc: failed to add pmdown_time sysfs:%d\n", ret); + printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n"); /* create the pcm */ ret = soc_new_pcm(rtd, num); if (ret < 0) { - pr_err("asoc: can't create pcm %s :%d\n", - dai_link->stream_name, ret); + printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name); return ret; } @@ -1316,7 +1381,7 @@ static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) ret = soc_ac97_dev_register(rtd->codec); if (ret < 0) { - pr_err("asoc: AC97 device register failed:%d\n", ret); + printk(KERN_ERR "asoc: AC97 device register failed\n"); return ret; } @@ -1404,6 +1469,96 @@ static int snd_soc_init_codec_cache(struct snd_soc_codec *codec, return 0; } +static void soc_init_dai_aif_channel_map(struct snd_soc_card *card, + struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_dapm_widget *w = NULL; + const char *aif_name; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + aif_name = dai->driver->playback.aif_name; + else + aif_name = dai->driver->capture.aif_name; + + if (dai->codec) { + struct snd_soc_codec *codec; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + w = snd_soc_get_codec_widget(card, codec, aif_name); + if (w) + break; + } + } else if (dai->platform) { + struct snd_soc_platform *platform; + + list_for_each_entry(platform, &card->platform_dev_list, card_list) { + w = snd_soc_get_platform_widget(card, platform, aif_name); + if (w) + break; + } + } + + if (w) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_aif = w; + else + dai->capture_aif = w; + } else + dev_err(dai->dev, "unable to find %s DAI AIF %s\n", + stream ? "capture" : "playback", aif_name); + + dai->channel_map_instanciated = 1; +} + +static int soc_is_dai_pcm(struct snd_soc_card *card, struct snd_soc_dai *dai) +{ + int i; + + for (i = 0; i < card->num_rtd; i++) { + if (card->rtd[i].cpu_dai == dai && !card->rtd[i].dai_link->no_pcm) + return 1; + } + return 0; +} + +static void soc_init_card_aif_channel_map(struct snd_soc_card *card) +{ + struct snd_soc_dai *dai; + + list_for_each_entry(dai, &card->dai_dev_list, card_list) { + + /* only process DAIs that use the new API until + * the old "stream name" API is fully deprecated */ + if (!dai->driver->playback.aif_name && !dai->driver->capture.aif_name) + continue; + + /* channels are only mapped from PCM DAIs */ + if (!soc_is_dai_pcm(card, dai)) + continue; + + /* skip if already instanciated */ + if (dai->channel_map_instanciated) + continue; + + /* create unique channels masks for each DAI in the sound card */ + dai->playback_channel_map = + ((1 << dai->driver->playback.channels_max) - 1) + << card->num_playback_channels; + card->num_playback_channels += dai->driver->playback.channels_max; + + dai->capture_channel_map = + ((1 << dai->driver->capture.channels_max) - 1) + << card->num_capture_channels; + card->num_capture_channels += dai->driver->capture.channels_max; + + if (dai->driver->playback.channels_max) + soc_init_dai_aif_channel_map(card, dai, SNDRV_PCM_STREAM_PLAYBACK); + if (dai->driver->capture.channels_max) + soc_init_dai_aif_channel_map(card, dai, SNDRV_PCM_STREAM_CAPTURE); + } +} + + static void snd_soc_instantiate_card(struct snd_soc_card *card) { struct snd_soc_codec *codec; @@ -1456,8 +1611,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, card->owner, 0, &card->snd_card); if (ret < 0) { - pr_err("asoc: can't create sound card for card %s: %d\n", - card->name, ret); + printk(KERN_ERR "asoc: can't create sound card for card %s\n", + card->name); mutex_unlock(&card->mutex); return; } @@ -1510,8 +1665,6 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) } } - snd_soc_dapm_link_dai_widgets(card); - if (card->controls) snd_soc_add_card_controls(card, card->controls, card->num_controls); @@ -1527,14 +1680,14 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) if (dai_link->dai_fmt) { ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai, dai_link->dai_fmt); - if (ret != 0 && ret != -ENOTSUPP) + if (ret != 0) dev_warn(card->rtd[i].codec_dai->dev, "Failed to set DAI format: %d\n", ret); ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai, dai_link->dai_fmt); - if (ret != 0 && ret != -ENOTSUPP) + if (ret != 0) dev_warn(card->rtd[i].cpu_dai->dev, "Failed to set DAI format: %d\n", ret); @@ -1570,6 +1723,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) } snd_soc_dapm_new_widgets(&card->dapm); + soc_init_card_aif_channel_map(card); if (card->fully_routed) list_for_each_entry(codec, &card->codec_dev_list, card_list) @@ -1577,8 +1731,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) ret = snd_card_register(card->snd_card); if (ret < 0) { - pr_err("asoc: failed to register soundcard for %s: %d\n", - card->name, ret); + printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name); goto probe_aux_dev_err; } @@ -1587,8 +1740,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) for (i = 0; i < card->num_rtd; i++) { ret = soc_register_ac97_dai_link(&card->rtd[i]); if (ret < 0) { - pr_err("asoc: failed to register AC97 %s: %d\n", - card->name, ret); + printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name); while (--i >= 0) soc_unregister_ac97_dai_link(card->rtd[i].codec); goto probe_aux_dev_err; @@ -1641,10 +1793,6 @@ static int soc_probe(struct platform_device *pdev) if (!card) return -EINVAL; - dev_warn(&pdev->dev, - "ASoC machine %s should use snd_soc_register_card()\n", - card->name); - /* Bodge while we unpick instantiation */ card->dev = &pdev->dev; @@ -1682,6 +1830,7 @@ static int soc_cleanup_card_resources(struct snd_soc_card *card) snd_soc_dapm_free(&card->dapm); + kfree(card->rtd); snd_card_free(card->snd_card); return 0; @@ -1720,10 +1869,7 @@ EXPORT_SYMBOL_GPL(snd_soc_poweroff); const struct dev_pm_ops snd_soc_pm_ops = { .suspend = snd_soc_suspend, .resume = snd_soc_resume, - .freeze = snd_soc_suspend, - .thaw = snd_soc_resume, .poweroff = snd_soc_poweroff, - .restore = snd_soc_resume, }; EXPORT_SYMBOL_GPL(snd_soc_pm_ops); @@ -2015,6 +2161,8 @@ int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, const struct snd_pcm_hardware *hw) { struct snd_pcm_runtime *runtime = substream->runtime; + if (!runtime) + return 0; runtime->hw.info = hw->info; runtime->hw.formats = hw->formats; runtime->hw.period_bytes_min = hw->period_bytes_min; @@ -2039,7 +2187,7 @@ EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams); * Returns 0 for success, else error. */ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, - void *data, const char *long_name, + void *data, char *long_name, const char *prefix) { struct snd_kcontrol_new template; @@ -2196,7 +2344,8 @@ int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item > e->max - 1) uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); + return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_enum_double); @@ -2360,7 +2509,7 @@ int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item > e->max - 1) uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext); @@ -2391,6 +2540,39 @@ int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); +/** + * snd_soc_info_multi_ext - external single mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a single external mixer control. + * that accepts multiple input. + * + * Returns 0 for success. + */ +int snd_soc_info_multi_ext(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_multi_mixer_control *mc = + (struct soc_multi_mixer_control *)kcontrol->private_value; + int platform_max; + + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strnstr(kcontrol->id.name, " Volume", 30)) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = mc->count; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = platform_max; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_multi_ext); + /** * snd_soc_info_volsw - single mixer info callback * @kcontrol: mixer control @@ -2542,13 +2724,15 @@ int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; int platform_max; int min = mc->min; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; if (!mc->platform_max) mc->platform_max = mc->max; platform_max = mc->platform_max; uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 2; + uinfo->count = shift == rshift ? 1 : 2; uinfo->value.integer.min = 0; uinfo->value.integer.max = platform_max - min; return 0; @@ -2571,13 +2755,16 @@ int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; int min = mc->min; int val = snd_soc_read(codec, reg); ucontrol->value.integer.value[0] = - ((signed char)(val & 0xff))-min; - ucontrol->value.integer.value[1] = - ((signed char)((val >> 8) & 0xff))-min; + ((signed char)((val >> shift) & 0xff))-min; + if (shift != rshift) + ucontrol->value.integer.value[1] = + ((signed char)((val >> rshift) & 0xff))-min; return 0; } EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8); @@ -2598,13 +2785,20 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; int min = mc->min; - unsigned int val; + unsigned int val, val2, val_mask; - val = (ucontrol->value.integer.value[0]+min) & 0xff; - val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8; + val = ((ucontrol->value.integer.value[0]+min) & 0xff) << shift; + val_mask = 0xff << shift; + if (shift != rshift) { + val2 = (ucontrol->value.integer.value[1]+min) & 0xff; + val |= val2 << rshift; + val_mask |= 0xff << rshift; + } - return snd_soc_update_bits_locked(codec, reg, 0xffff, val); + return snd_soc_update_bits_locked(codec, reg, val_mask, val); } EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); @@ -2740,115 +2934,6 @@ int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx); -int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct soc_bytes *params = (void *)kcontrol->private_value; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; - uinfo->count = params->num_regs * codec->val_bytes; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_info); - -int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_bytes *params = (void *)kcontrol->private_value; - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int ret; - - if (codec->using_regmap) - ret = regmap_raw_read(codec->control_data, params->base, - ucontrol->value.bytes.data, - params->num_regs * codec->val_bytes); - else - ret = -EINVAL; - - /* Hide any masked bytes to ensure consistent data reporting */ - if (ret == 0 && params->mask) { - switch (codec->val_bytes) { - case 1: - ucontrol->value.bytes.data[0] &= ~params->mask; - break; - case 2: - ((u16 *)(&ucontrol->value.bytes.data))[0] - &= ~params->mask; - break; - case 4: - ((u32 *)(&ucontrol->value.bytes.data))[0] - &= ~params->mask; - break; - default: - return -EINVAL; - } - } - - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_get); - -int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_bytes *params = (void *)kcontrol->private_value; - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int ret, len; - unsigned int val; - void *data; - - if (!codec->using_regmap) - return -EINVAL; - - data = ucontrol->value.bytes.data; - len = params->num_regs * codec->val_bytes; - - /* - * If we've got a mask then we need to preserve the register - * bits. We shouldn't modify the incoming data so take a - * copy. - */ - if (params->mask) { - ret = regmap_read(codec->control_data, params->base, &val); - if (ret != 0) - return ret; - - val &= params->mask; - - data = kmemdup(data, len, GFP_KERNEL); - if (!data) - return -ENOMEM; - - switch (codec->val_bytes) { - case 1: - ((u8 *)data)[0] &= ~params->mask; - ((u8 *)data)[0] |= val; - break; - case 2: - ((u16 *)data)[0] &= cpu_to_be16(~params->mask); - ((u16 *)data)[0] |= cpu_to_be16(val); - break; - case 4: - ((u32 *)data)[0] &= cpu_to_be32(~params->mask); - ((u32 *)data)[0] |= cpu_to_be32(val); - break; - default: - return -EINVAL; - } - } - - ret = regmap_raw_write(codec->control_data, params->base, - data, len); - - if (params->mask) - kfree(data); - - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_put); - /** * snd_soc_dai_set_sysclk - configure DAI system or master clock. * @dai: DAI @@ -2966,11 +3051,10 @@ EXPORT_SYMBOL_GPL(snd_soc_codec_set_pll); */ int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - if (dai->driver == NULL) + if (dai->driver && dai->driver->ops->set_fmt) + return dai->driver->ops->set_fmt(dai, fmt); + else return -EINVAL; - if (dai->driver->ops->set_fmt == NULL) - return -ENOTSUPP; - return dai->driver->ops->set_fmt(dai, fmt); } EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); @@ -3020,6 +3104,29 @@ int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, } EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); +/** + * snd_soc_dai_get_channel_map - configure DAI audio channel map + * @dai: DAI + * @tx_num: how many TX channels + * @tx_slot: pointer to an array which imply the TX slot number channel + * 0~num-1 uses + * @rx_num: how many RX channels + * @rx_slot: pointer to an array which imply the RX slot number channel + * 0~num-1 uses + * + * configure the relationship between channel number and TDM slot number. + */ +int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) +{ + if (dai->driver && dai->driver->ops->get_channel_map) + return dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, + rx_num, rx_slot); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); /** * snd_soc_dai_set_tristate - configure DAI system or master clock. * @dai: DAI @@ -3061,6 +3168,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); int snd_soc_register_card(struct snd_soc_card *card) { int i; + int ret = 0; if (!card->name || !card->dev) return -EINVAL; @@ -3107,10 +3215,9 @@ int snd_soc_register_card(struct snd_soc_card *card) soc_init_card_debugfs(card); - card->rtd = devm_kzalloc(card->dev, - sizeof(struct snd_soc_pcm_runtime) * - (card->num_links + card->num_aux_devs), - GFP_KERNEL); + card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * + (card->num_links + card->num_aux_devs), + GFP_KERNEL); if (card->rtd == NULL) return -ENOMEM; card->num_rtd = 0; @@ -3123,6 +3230,7 @@ int snd_soc_register_card(struct snd_soc_card *card) INIT_LIST_HEAD(&card->dapm_dirty); card->instantiated = 0; mutex_init(&card->mutex); + mutex_init(&card->dpcm_mutex); mutex_lock(&client_mutex); list_add(&card->list, &card_list); @@ -3131,7 +3239,7 @@ int snd_soc_register_card(struct snd_soc_card *card) dev_dbg(card->dev, "Registered card '%s'\n", card->name); - return 0; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -3205,7 +3313,7 @@ static inline char *fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv) { if (dai_drv->name == NULL) { - pr_err("asoc: error - multiple DAI %s registered with no name\n", + printk(KERN_ERR "asoc: error - multiple DAI %s registered with no name\n", dev_name(dev)); return NULL; } @@ -3310,6 +3418,7 @@ int snd_soc_register_dais(struct device *dev, dai->dev = dev; dai->driver = &dai_drv[i]; + if (dai->driver->id) dai->id = dai->driver->id; else @@ -3380,7 +3489,6 @@ int snd_soc_register_platform(struct device *dev, platform->dapm.dev = dev; platform->dapm.platform = platform; platform->dapm.stream_event = platform_drv->stream_event; - mutex_init(&platform->mutex); mutex_lock(&client_mutex); list_add(&platform->list, &platform_list); @@ -3489,7 +3597,6 @@ int snd_soc_register_codec(struct device *dev, codec->volatile_register = codec_drv->volatile_register; codec->readable_register = codec_drv->readable_register; codec->writable_register = codec_drv->writable_register; - codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time; codec->dapm.bias_level = SND_SOC_BIAS_OFF; codec->dapm.dev = dev; codec->dapm.codec = codec; @@ -3678,7 +3785,8 @@ static int __init snd_soc_init(void) #ifdef CONFIG_DEBUG_FS snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) { - pr_warn("ASoC: Failed to create debugfs directory\n"); + printk(KERN_WARNING + "ASoC: Failed to create debugfs directory\n"); snd_soc_debugfs_root = NULL; } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1bb6d4a63cd8630854ae2b82f47a70057cdd753a..729142fea564df23124ff1de7c4444a2f23eef86 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -14,13 +14,19 @@ * dynamic configuration of codec internal audio paths and active * DACs/ADCs. * o Platform power domain - can support external components i.e. amps and - * mic/headphone insertion events. + * mic/meadphone insertion events. * o Automatic Mic Bias support * o Jack insertion power event initiation - e.g. hp insertion will enable * sinks, dacs, etc - * o Delayed power down of audio subsystem to reduce pops between a quick + * o Delayed powerdown of audio susbsystem to reduce pops between a quick * device reopen. * + * Todo: + * o DAPM power change sequencing - allow for configurable per + * codec sequences. + * o Support for analogue bias optimisation. + * o Support for reduced codec oversampling rates. + * o Support for reduced codec bias currents. */ #include @@ -34,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -43,6 +48,7 @@ #include #include +int soc_dpcm_runtime_update(struct snd_soc_dapm_widget *); #define DAPM_UPDATE_STAT(widget, val) widget->dapm->card->dapm_stats.val++; @@ -50,11 +56,8 @@ static int dapm_up_seq[] = { [snd_soc_dapm_pre] = 0, [snd_soc_dapm_supply] = 1, - [snd_soc_dapm_regulator_supply] = 1, [snd_soc_dapm_micbias] = 2, - [snd_soc_dapm_dai] = 3, - [snd_soc_dapm_aif_in] = 3, - [snd_soc_dapm_aif_out] = 3, + [snd_soc_dapm_adc] = 3, [snd_soc_dapm_mic] = 4, [snd_soc_dapm_mux] = 5, [snd_soc_dapm_virt_mux] = 5, @@ -63,20 +66,21 @@ static int dapm_up_seq[] = { [snd_soc_dapm_mixer] = 7, [snd_soc_dapm_mixer_named_ctl] = 7, [snd_soc_dapm_pga] = 8, - [snd_soc_dapm_adc] = 9, + [snd_soc_dapm_aif_in] = 8, + [snd_soc_dapm_aif_out] = 8, [snd_soc_dapm_out_drv] = 10, [snd_soc_dapm_hp] = 10, [snd_soc_dapm_spk] = 10, - [snd_soc_dapm_line] = 10, [snd_soc_dapm_post] = 11, }; static int dapm_down_seq[] = { [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_aif_in] = 1, + [snd_soc_dapm_aif_out] = 1, [snd_soc_dapm_adc] = 1, [snd_soc_dapm_hp] = 2, [snd_soc_dapm_spk] = 2, - [snd_soc_dapm_line] = 2, [snd_soc_dapm_out_drv] = 2, [snd_soc_dapm_pga] = 4, [snd_soc_dapm_mixer_named_ctl] = 5, @@ -87,10 +91,6 @@ static int dapm_down_seq[] = { [snd_soc_dapm_mux] = 9, [snd_soc_dapm_virt_mux] = 9, [snd_soc_dapm_value_mux] = 9, - [snd_soc_dapm_aif_in] = 10, - [snd_soc_dapm_aif_out] = 10, - [snd_soc_dapm_dai] = 10, - [snd_soc_dapm_regulator_supply] = 11, [snd_soc_dapm_supply] = 11, [snd_soc_dapm_post] = 12, }; @@ -173,19 +173,6 @@ static inline struct snd_soc_card *dapm_get_soc_card( return NULL; } -static void dapm_reset(struct snd_soc_card *card) -{ - struct snd_soc_dapm_widget *w; - - memset(&card->dapm_stats, 0, sizeof(card->dapm_stats)); - - list_for_each_entry(w, &card->widgets, list) { - w->power_checked = false; - w->inputs = -1; - w->outputs = -1; - } -} - static int soc_widget_read(struct snd_soc_dapm_widget *w, int reg) { if (w->codec) @@ -316,7 +303,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, p->connect = 0; for (i = 0; i < e->max; i++) { - if (!(strcmp(p->name, e->texts[i])) && item == i) + if (!(strcmp(p->name, snd_soc_get_enum_text(e, i))) && item == i) p->connect = 1; } } @@ -332,7 +319,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, * that the default mux choice (the first) will be * correctly powered up during initialization. */ - if (!strcmp(p->name, e->texts[0])) + if (!strcmp(p->name, snd_soc_get_enum_text(e, 0))) p->connect = 1; } break; @@ -350,7 +337,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, p->connect = 0; for (i = 0; i < e->max; i++) { - if (!(strcmp(p->name, e->texts[i])) && item == i) + if (!(strcmp(p->name, snd_soc_get_enum_text(e, i))) && item == i) p->connect = 1; } } @@ -366,10 +353,8 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_micbias: case snd_soc_dapm_vmid: case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: - case snd_soc_dapm_dai: case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_spk: @@ -394,11 +379,11 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, int i; for (i = 0; i < e->max; i++) { - if (!(strcmp(control_name, e->texts[i]))) { + if (!(strcmp(control_name, snd_soc_get_enum_text(e, i)))) { list_add(&path->list, &dapm->card->paths); list_add(&path->list_sink, &dest->sources); list_add(&path->list_source, &src->sinks); - path->name = (char*)e->texts[i]; + path->name = (char*)snd_soc_get_enum_text(e, i); dapm_set_path_status(dest, path, 0); return 0; } @@ -527,17 +512,17 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w) * for widgets so cut the prefix off * the front of the widget name. */ - snprintf((char *)path->long_name, name_len, - "%s %s", w->name + prefix_len, + snprintf(path->long_name, name_len, "%s %s", + w->name + prefix_len, w->kcontrol_news[i].name); break; case snd_soc_dapm_mixer_named_ctl: - snprintf((char *)path->long_name, name_len, - "%s", w->kcontrol_news[i].name); + snprintf(path->long_name, name_len, "%s", + w->kcontrol_news[i].name); break; } - ((char *)path->long_name)[name_len - 1] = '\0'; + path->long_name[name_len - 1] = '\0'; path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i], wlist, path->long_name, @@ -571,7 +556,7 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w) struct snd_soc_dapm_widget_list *wlist; int shared, wlistentries; size_t wlistsize; - const char *name; + char *name; if (w->num_kcontrols != 1) { dev_err(dapm->dev, @@ -653,15 +638,6 @@ static int dapm_new_pga(struct snd_soc_dapm_widget *w) return 0; } -/* reset 'walked' bit for each dapm path */ -static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm) -{ - struct snd_soc_dapm_path *p; - - list_for_each_entry(p, &dapm->card->paths, list) - p->walked = 0; -} - /* We implement power down on suspend by checking the power state of * the ALSA card - when we are suspending the ALSA state for the card * is set to D3. @@ -682,11 +658,60 @@ static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) } } +/* reset 'walked' bit for each dapm path */ +static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_dapm_path *p; + + list_for_each_entry(p, &dapm->card->paths, list) + p->walked = 0; +} + +/* add widget to list if it's not already in the list */ +static int dapm_list_add_widget(struct snd_soc_dapm_widget_list **list, + struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_widget_list *wlist; + int wlistsize, wlistentries, i; + + if (*list == NULL) + return -EINVAL; + + wlist = *list; + + /* is this widget already in the list */ + for (i = 0; i < wlist->num_widgets; i++) { + if (wlist->widgets[i] == w) + return 0; + } + + /* allocate some new space */ + wlistentries = wlist->num_widgets + 1; + wlistsize = sizeof(struct snd_soc_dapm_widget_list) + + wlistentries * sizeof(struct snd_soc_dapm_widget *); + *list = krealloc(wlist, wlistsize, GFP_KERNEL); + if (*list == NULL) { + dev_err(w->dapm->dev, "can't allocate widget list for %s\n", + w->name); + return -ENOMEM; + } + wlist = *list; + + /* insert the widget */ + dev_dbg(w->dapm->dev, "added %s in widget list pos %d\n", + w->name, wlist->num_widgets); + + wlist->widgets[wlist->num_widgets] = w; + wlist->num_widgets++; + return 1; +} + /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. */ -static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) +static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, + struct snd_soc_dapm_widget_list **list) { struct snd_soc_dapm_path *path; int con = 0; @@ -696,18 +721,12 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) DAPM_UPDATE_STAT(widget, path_checks); - switch (widget->id) { - case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: + if (widget->id == snd_soc_dapm_supply) return 0; - default: - break; - } switch (widget->id) { case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: - case snd_soc_dapm_dai: if (widget->active) { widget->outputs = snd_soc_dapm_suspend_check(widget); return widget->outputs; @@ -742,9 +761,26 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) if (path->walked) continue; + dev_vdbg(widget->dapm->dev," %c : %s -> %s -> %s : %c\n", + path->sink && path->connect ? '*' : ' ', + widget->name, path->name, path->sink->name, + path->weak ? 'w': ' '); + if (path->sink && path->connect) { path->walked = 1; - con += is_connected_output_ep(path->sink); + + /* do we need to add this widget to the list ? */ + if (list) { + int err; + err = dapm_list_add_widget(list, path->sink); + if (err < 0) { + dev_err(widget->dapm->dev, "could not add widget %s\n", + widget->name); + return con; + } + } + + con += is_connected_output_ep(path->sink, list); } } @@ -757,7 +793,8 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) * Recursively check for a completed path to an active or physically connected * input widget. Returns number of complete paths. */ -static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) +static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, + struct snd_soc_dapm_widget_list **list) { struct snd_soc_dapm_path *path; int con = 0; @@ -767,19 +804,13 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) DAPM_UPDATE_STAT(widget, path_checks); - switch (widget->id) { - case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: + if (widget->id == snd_soc_dapm_supply) return 0; - default: - break; - } /* active stream ? */ switch (widget->id) { case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: - case snd_soc_dapm_dai: if (widget->active) { widget->inputs = snd_soc_dapm_suspend_check(widget); return widget->inputs; @@ -819,6 +850,12 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) list_for_each_entry(path, &widget->sources, list_sink) { DAPM_UPDATE_STAT(widget, neighbour_checks); + if (path->source) + dev_vdbg(widget->dapm->dev," %c : %s <- %s <- %s : %c\n", + path->source && path->connect ? '*' : ' ', + widget->name, path->name, path->source->name, + path->weak ? 'w': ' '); + if (path->weak) continue; @@ -827,7 +864,19 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) if (path->source && path->connect) { path->walked = 1; - con += is_connected_input_ep(path->source); + + /* do we need to add this widget to the list ? */ + if (list) { + int err; + err = dapm_list_add_widget(list, path->sink); + if (err < 0) { + dev_err(widget->dapm->dev, "could not add widget %s\n", + widget->name); + return con; + } + } + + con += is_connected_input_ep(path->source, list); } } @@ -836,6 +885,120 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) return con; } +/* Get the DAPM AIF widget for thie DAI stream */ +struct snd_soc_dapm_widget *snd_soc_get_codec_widget(struct snd_soc_card *card, + struct snd_soc_codec *codec, const char *name) +{ + struct snd_soc_dapm_widget *w; + + /* get stream root widget AIF from stream string and direction */ + list_for_each_entry(w, &card->widgets, list) { + + /* make sure the widget belongs the DAI codec or platform */ + if (w->codec && w->codec != codec) + continue; + + if (!strcmp(w->name, name)) + return w; + + } + dev_err(card->dapm.dev, "DAI AIF widget for %s not found\n", name); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_codec_widget); + +struct snd_soc_dapm_widget *snd_soc_get_platform_widget(struct snd_soc_card *card, + struct snd_soc_platform *platform, const char *name) +{ + struct snd_soc_dapm_widget *w; + + /* get stream root widget AIF from stream string and direction */ + list_for_each_entry(w, &card->widgets, list) { + + /* make sure the widget belongs the DAI codec or platform */ + if (w->platform && w->platform != platform) + continue; + + if (!strcmp(w->name, name)) + return w; + } + dev_err(card->dapm.dev, "DAI AIF widget for %s not found\n", name); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_platform_widget); + +static int dapm_get_playback_paths(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *root, + struct snd_soc_dapm_widget_list **list) +{ + int paths; + + if (!root) { + dev_err(dapm->dev, "no root widget for path discovery\n"); + return 0; + } + + dev_dbg(dapm->dev, "Playback: checking paths from %s\n",root->name); + paths = is_connected_output_ep(root, list); + dev_dbg(dapm->dev, "Playback: found %d paths from %s\n", paths, root->name); + + return paths; +} + +static int dapm_get_capture_paths(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *root, + struct snd_soc_dapm_widget_list **list) +{ + int paths; + + if (!root) { + dev_err(dapm->dev, "no root widget for path discovery\n"); + return 0; + } + + dev_dbg(dapm->dev, "Capture: checking paths from %s\n",root->name); + paths = is_connected_input_ep(root, list); + dev_dbg(dapm->dev, "Capture: found %d paths from %s\n", paths, root->name); + + return paths; +} + +/** + * snd_soc_dapm_get_connected_widgets - query audio path and it's widgets. + * @dai: the soc DAI. + * @stream: stream direction. + * @list: list of active widgets for this stream. + * + * Queries DAPM graph as to whether an valid audio stream path exists for + * the initial stream specified by name. This takes into account + * current mixer and mux kcontrol settings. Creates list of valid widgets. + * + * Returns the number of valid paths or negative error. + */ +int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, + struct snd_soc_dapm_widget_list **list) +{ + struct snd_soc_card *card = dai->card; + struct snd_soc_dapm_widget *w; + int paths; + + memset(&card->dapm_stats, 0, sizeof(card->dapm_stats)); + + list_for_each_entry(w, &card->widgets, list) { + w->power_checked = false; + w->inputs = -1; + w->outputs = -1; + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + paths = dapm_get_playback_paths(&card->dapm, dai->playback_aif, list); + else + paths = dapm_get_capture_paths(&card->dapm, dai->capture_aif, list); + + dapm_clear_walk(&card->dapm); + return paths; +} + /* * Handler for generic register modifier widget. */ @@ -856,19 +1019,6 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w, } EXPORT_SYMBOL_GPL(dapm_reg_event); -/* - * Handler for regulator supply widget. - */ -int dapm_regulator_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - if (SND_SOC_DAPM_EVENT_ON(event)) - return regulator_enable(w->priv); - else - return regulator_disable_deferred(w->priv, w->shift); -} -EXPORT_SYMBOL_GPL(dapm_regulator_event); - static int dapm_widget_power_check(struct snd_soc_dapm_widget *w) { if (w->power_checked) @@ -892,20 +1042,13 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); - in = is_connected_input_ep(w); + in = is_connected_input_ep(w, NULL); dapm_clear_walk(w->dapm); - out = is_connected_output_ep(w); + out = is_connected_output_ep(w, NULL); dapm_clear_walk(w->dapm); return out != 0 && in != 0; } -static int dapm_dai_check_power(struct snd_soc_dapm_widget *w) -{ - DAPM_UPDATE_STAT(w, power_checks); - - return w->active; -} - /* Check to see if an ADC has power */ static int dapm_adc_check_power(struct snd_soc_dapm_widget *w) { @@ -914,7 +1057,7 @@ static int dapm_adc_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); if (w->active) { - in = is_connected_input_ep(w); + in = is_connected_input_ep(w, NULL); dapm_clear_walk(w->dapm); return in != 0; } else { @@ -930,7 +1073,7 @@ static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); if (w->active) { - out = is_connected_output_ep(w); + out = is_connected_output_ep(w, NULL); dapm_clear_walk(w->dapm); return out != 0; } else { @@ -1306,7 +1449,7 @@ static void dapm_post_sequence_async(void *data, async_cookie_t cookie) dev_err(d->dev, "Failed to turn off bias: %d\n", ret); if (d->dev) - pm_runtime_put(d->dev); + pm_runtime_put_sync(d->dev); } /* If we just powered up then move to active bias */ @@ -1356,7 +1499,6 @@ static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power, } switch (w->id) { case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: /* Supplies can't affect their outputs, only their inputs */ break; default: @@ -1369,10 +1511,15 @@ static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power, break; } - if (power) + if (power) { dapm_seq_insert(w, up_list, true); - else + dev_dbg(w->dapm->dev, + "dapm: power up widget %s\n", w->name); + } else { dapm_seq_insert(w, down_list, false); + dev_dbg(w->dapm->dev, + "dapm: power down widget %s\n", w->name); + } w->power = power; } @@ -1429,7 +1576,13 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) } } - dapm_reset(card); + memset(&card->dapm_stats, 0, sizeof(card->dapm_stats)); + + list_for_each_entry(w, &card->widgets, list) { + w->power_checked = false; + w->inputs = -1; + w->outputs = -1; + } /* Check which widgets we need to power and store them in * lists indicating if they should be powered up or down. We @@ -1450,15 +1603,10 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) /* Supplies and micbiases only bring the * context up to STANDBY as unless something * else is active and passing audio they - * generally don't require full power. Signal - * generators are virtual pins and have no - * power impact themselves. + * generally don't require full power. */ switch (w->id) { - case snd_soc_dapm_siggen: - break; case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: case snd_soc_dapm_micbias: if (d->target_bias_level < SND_SOC_BIAS_STANDBY) d->target_bias_level = SND_SOC_BIAS_STANDBY; @@ -1530,12 +1678,6 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) &async_domain); async_synchronize_full_domain(&async_domain); - /* do we need to notify any clients that DAPM event is complete */ - list_for_each_entry(d, &card->dapm_list, list) { - if (d->stream_event) - d->stream_event(d, event); - } - pop_dbg(dapm->dev, card->pop_time, "DAPM sequencing finished, waiting %dms\n", card->pop_time); pop_wait(card->pop_time); @@ -1546,6 +1688,12 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) } #ifdef CONFIG_DEBUG_FS +static int dapm_widget_power_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + static ssize_t dapm_widget_power_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) @@ -1560,14 +1708,13 @@ static ssize_t dapm_widget_power_read_file(struct file *file, if (!buf) return -ENOMEM; - in = is_connected_input_ep(w); + in = is_connected_input_ep(w, NULL); dapm_clear_walk(w->dapm); - out = is_connected_output_ep(w); + out = is_connected_output_ep(w, NULL); dapm_clear_walk(w->dapm); - ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", - w->name, w->power ? "On" : "Off", - w->force ? " (forced)" : "", in, out); + ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d", + w->name, w->power ? "On" : "Off", in, out); if (w->reg >= 0) ret += snprintf(buf + ret, PAGE_SIZE - ret, @@ -1609,11 +1756,17 @@ static ssize_t dapm_widget_power_read_file(struct file *file, } static const struct file_operations dapm_widget_power_fops = { - .open = simple_open, + .open = dapm_widget_power_open_file, .read = dapm_widget_power_read_file, .llseek = default_llseek, }; +static int dapm_bias_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { @@ -1644,7 +1797,7 @@ static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf, } static const struct file_operations dapm_bias_fops = { - .open = simple_open, + .open = dapm_bias_open_file, .read = dapm_bias_read_file, .llseek = default_llseek, }; @@ -1657,7 +1810,7 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, dapm->debugfs_dapm = debugfs_create_dir("dapm", parent); if (!dapm->debugfs_dapm) { - dev_warn(dapm->dev, + printk(KERN_WARNING "Failed to create DAPM debugfs directory\n"); return; } @@ -1710,7 +1863,8 @@ static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) /* test and update the power status of a mux widget */ int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, - struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e) + struct snd_kcontrol *kcontrol, int change, + int mux, struct soc_enum *e) { struct snd_soc_dapm_path *path; int found = 0; @@ -1720,17 +1874,21 @@ int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, widget->id != snd_soc_dapm_value_mux) return -ENODEV; + if (!change) + return 0; + /* find dapm widget path assoc with kcontrol */ list_for_each_entry(path, &widget->dapm->card->paths, list) { + if (path->kcontrol != kcontrol) continue; - if (!path->name || !e->texts[mux]) + if (!path->name || !snd_soc_get_enum_text(e, mux)) continue; found = 1; /* we now need to match the string in the enum to the path */ - if (!(strcmp(path->name, e->texts[mux]))) { + if (!(strcmp(path->name, snd_soc_get_enum_text(e, mux)))) { path->connect = 1; /* new connection */ dapm_mark_dirty(path->source, "mux connection"); } else { @@ -1742,8 +1900,13 @@ int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget, } if (found) { - dapm_mark_dirty(widget, "mux change"); - dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP); + if (widget->platform) { + soc_dpcm_runtime_update(widget); + } else { + dapm_mark_dirty(widget, "mux change"); + dapm_power_widgets(widget->dapm, + SND_SOC_DAPM_STREAM_NOP); + } } return 0; @@ -1774,8 +1937,13 @@ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, } if (found) { + if (widget->platform) { + soc_dpcm_runtime_update(widget); + } else { dapm_mark_dirty(widget, "mixer update"); dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP); + } + } return 0; @@ -1810,7 +1978,6 @@ static ssize_t dapm_widget_show(struct device *dev, case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: if (w->name) count += sprintf(buf + count, "%s: %s\n", w->name, w->power ? "On":"Off"); @@ -1918,12 +2085,10 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, return -EINVAL; } - if (w->connected != status) - dapm_mark_dirty(w, "pin configuration"); - w->connected = status; if (status == 0) w->force = 0; + dapm_mark_dirty(w, "pin configuration"); return 0; } @@ -2051,10 +2216,8 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, case snd_soc_dapm_pre: case snd_soc_dapm_post: case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: - case snd_soc_dapm_dai: list_add(&path->list, &dapm->card->paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); @@ -2459,7 +2622,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, update.val = val; widget->dapm->update = &update; - snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e); + snd_soc_dapm_mux_update_power(widget, kcontrol, change, mux, e); widget->dapm->update = NULL; } @@ -2520,7 +2683,8 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol, widget->value = ucontrol->value.enumerated.item[0]; - snd_soc_dapm_mux_update_power(widget, kcontrol, widget->value, e); + snd_soc_dapm_mux_update_power(widget, kcontrol, change, + widget->value, e); } } @@ -2623,7 +2787,7 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, update.val = val; widget->dapm->update = &update; - snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e); + snd_soc_dapm_mux_update_power(widget, kcontrol, change, mux, e); widget->dapm->update = NULL; } @@ -2663,15 +2827,15 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_info_pin_switch); int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); const char *pin = (const char *)kcontrol->private_value; - mutex_lock(&card->mutex); + mutex_lock(&codec->mutex); ucontrol->value.integer.value[0] = - snd_soc_dapm_get_pin_status(&card->dapm, pin); + snd_soc_dapm_get_pin_status(&codec->dapm, pin); - mutex_unlock(&card->mutex); + mutex_unlock(&codec->mutex); return 0; } @@ -2686,48 +2850,41 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_switch); int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); const char *pin = (const char *)kcontrol->private_value; - mutex_lock(&card->mutex); + mutex_lock(&codec->mutex); if (ucontrol->value.integer.value[0]) - snd_soc_dapm_enable_pin(&card->dapm, pin); + snd_soc_dapm_enable_pin(&codec->dapm, pin); else - snd_soc_dapm_disable_pin(&card->dapm, pin); + snd_soc_dapm_disable_pin(&codec->dapm, pin); - snd_soc_dapm_sync(&card->dapm); + snd_soc_dapm_sync(&codec->dapm); - mutex_unlock(&card->mutex); + mutex_unlock(&codec->mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch); -static struct snd_soc_dapm_widget * -snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, - const struct snd_soc_dapm_widget *widget) +/** + * snd_soc_dapm_new_control - create new dapm control + * @dapm: DAPM context + * @widget: widget template + * + * Creates a new dapm control based upon the template. + * + * Returns 0 for success else error. + */ +int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget) { struct snd_soc_dapm_widget *w; size_t name_len; - int ret; if ((w = dapm_cnew_widget(widget)) == NULL) - return NULL; - - switch (w->id) { - case snd_soc_dapm_regulator_supply: - w->priv = devm_regulator_get(dapm->dev, w->name); - if (IS_ERR(w->priv)) { - ret = PTR_ERR(w->priv); - dev_err(dapm->dev, "Failed to request %s: %d\n", - w->name, ret); - return NULL; - } - break; - default: - break; - } + return -ENOMEM; name_len = strlen(widget->name) + 1; if (dapm->codec && dapm->codec->name_prefix) @@ -2735,13 +2892,13 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, w->name = kmalloc(name_len, GFP_KERNEL); if (w->name == NULL) { kfree(w); - return NULL; + return -ENOMEM; } if (dapm->codec && dapm->codec->name_prefix) - snprintf((char *)w->name, name_len, "%s %s", + snprintf(w->name, name_len, "%s %s", dapm->codec->name_prefix, widget->name); else - snprintf((char *)w->name, name_len, "%s", widget->name); + snprintf(w->name, name_len, "%s", widget->name); switch (w->id) { case snd_soc_dapm_switch: @@ -2774,12 +2931,8 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: w->power_check = dapm_supply_check_power; break; - case snd_soc_dapm_dai: - w->power_check = dapm_dai_check_power; - break; default: w->power_check = dapm_always_on_check_power; break; @@ -2789,6 +2942,7 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, w->dapm = dapm; w->codec = dapm->codec; w->platform = dapm->platform; + w->dai = dapm->dai; INIT_LIST_HEAD(&w->sources); INIT_LIST_HEAD(&w->sinks); INIT_LIST_HEAD(&w->list); @@ -2797,8 +2951,9 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, /* machine layer set ups unconnected pins and insertions */ w->connected = 1; - return w; + return 0; } +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control); /** * snd_soc_dapm_new_controls - create new dapm controls @@ -2814,16 +2969,15 @@ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num) { - struct snd_soc_dapm_widget *w; - int i; + int i, ret; for (i = 0; i < num; i++) { - w = snd_soc_dapm_new_control(dapm, widget); - if (!w) { + ret = snd_soc_dapm_new_control(dapm, widget); + if (ret < 0) { dev_err(dapm->dev, - "ASoC: Failed to create DAPM control %s\n", - widget->name); - return -ENOMEM; + "ASoC: Failed to create DAPM control %s: %d\n", + widget->name, ret); + return ret; } widget++; } @@ -2831,126 +2985,54 @@ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, } EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls); -int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, - struct snd_soc_dai *dai) +static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, + const char *stream, int event) { - struct snd_soc_dapm_widget template; struct snd_soc_dapm_widget *w; - WARN_ON(dapm->dev != dai->dev); - - memset(&template, 0, sizeof(template)); - template.reg = SND_SOC_NOPM; - - if (dai->driver->playback.stream_name) { - template.id = snd_soc_dapm_dai; - template.name = dai->driver->playback.stream_name; - template.sname = dai->driver->playback.stream_name; - - dev_dbg(dai->dev, "adding %s widget\n", - template.name); - - w = snd_soc_dapm_new_control(dapm, &template); - if (!w) { - dev_err(dapm->dev, "Failed to create %s widget\n", - dai->driver->playback.stream_name); - } - - w->priv = dai; - dai->playback_widget = w; - } - - if (dai->driver->capture.stream_name) { - template.id = snd_soc_dapm_dai; - template.name = dai->driver->capture.stream_name; - template.sname = dai->driver->capture.stream_name; - - dev_dbg(dai->dev, "adding %s widget\n", - template.name); - - w = snd_soc_dapm_new_control(dapm, &template); - if (!w) { - dev_err(dapm->dev, "Failed to create %s widget\n", - dai->driver->capture.stream_name); - } - - w->priv = dai; - dai->capture_widget = w; - } - - return 0; -} - -int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card) -{ - struct snd_soc_dapm_widget *dai_w, *w; - struct snd_soc_dai *dai; - struct snd_soc_dapm_route r; - - memset(&r, 0, sizeof(r)); + if (!dapm) + return; - /* For each DAI widget... */ - list_for_each_entry(dai_w, &card->widgets, list) { - if (dai_w->id != snd_soc_dapm_dai) + list_for_each_entry(w, &dapm->card->widgets, list) + { + if (!w->sname || w->dapm != dapm) continue; - - dai = dai_w->priv; - - /* ...find all widgets with the same stream and link them */ - list_for_each_entry(w, &card->widgets, list) { - if (w->dapm != dai_w->dapm) - continue; - - if (w->id == snd_soc_dapm_dai) - continue; - - if (!w->sname) - continue; - - if (dai->driver->playback.stream_name && - strstr(w->sname, - dai->driver->playback.stream_name)) { - r.source = dai->playback_widget->name; - r.sink = w->name; - dev_dbg(dai->dev, "%s -> %s\n", - r.source, r.sink); - - snd_soc_dapm_add_route(w->dapm, &r); - } - - if (dai->driver->capture.stream_name && - strstr(w->sname, - dai->driver->capture.stream_name)) { - r.source = w->name; - r.sink = dai->capture_widget->name; - dev_dbg(dai->dev, "%s -> %s\n", - r.source, r.sink); - - snd_soc_dapm_add_route(w->dapm, &r); + dev_vdbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n", + w->name, w->sname, stream, event); + if (strstr(w->sname, stream)) { + dapm_mark_dirty(w, "stream event"); + switch(event) { + case SND_SOC_DAPM_STREAM_START: + w->active = 1; + break; + case SND_SOC_DAPM_STREAM_STOP: + w->active = 0; + break; + case SND_SOC_DAPM_STREAM_SUSPEND: + case SND_SOC_DAPM_STREAM_RESUME: + case SND_SOC_DAPM_STREAM_PAUSE_PUSH: + case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: + break; } } } - return 0; + dapm_power_widgets(dapm, event); + + /* do we need to notify any clients that DAPM stream is complete */ + if (dapm->stream_event) + dapm->stream_event(dapm, event); } -static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, - int stream, struct snd_soc_dai *dai, - int event) +static void widget_stream_event(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *w, int event) { - struct snd_soc_dapm_widget *w; - - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; - if (!w) return; dapm_mark_dirty(w, "stream event"); - switch (event) { + switch(event) { case SND_SOC_DAPM_STREAM_START: w->active = 1; break; @@ -2967,6 +3049,30 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, dapm_power_widgets(dapm, event); } +void snd_soc_dapm_rtd_stream_event(struct snd_soc_pcm_runtime *rtd, + int stream, int event) +{ + struct snd_soc_dapm_context *pdapm = &rtd->platform->dapm; + struct snd_soc_dapm_context *cdapm = &rtd->codec->dapm; + + dev_dbg(rtd->dev, "rtd stream %d event %d\n", stream, event); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + widget_stream_event(pdapm, rtd->cpu_dai->playback_aif, event); + widget_stream_event(cdapm, rtd->codec_dai->playback_aif, event); + } else { + widget_stream_event(pdapm, rtd->cpu_dai->capture_aif, event); + widget_stream_event(cdapm, rtd->codec_dai->capture_aif, event); + } + + /* do we need to notify any clients that DAPM stream is complete */ + if (pdapm->stream_event) + pdapm->stream_event(pdapm, event); + if (cdapm->stream_event) + cdapm->stream_event(cdapm, event); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_rtd_stream_event); + /** * snd_soc_dapm_stream_event - send a stream event to the dapm core * @rtd: PCM runtime data @@ -2978,17 +3084,30 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, * * Returns 0 for success else error. */ -int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, - struct snd_soc_dai *dai, int event) +int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, + const char *stream, int event) { struct snd_soc_codec *codec = rtd->codec; - mutex_lock(&codec->mutex); - soc_dapm_stream_event(&codec->dapm, stream, dai, event); - mutex_unlock(&codec->mutex); + if (stream == NULL) + return 0; + + //mutex_lock(&codec->mutex); + soc_dapm_stream_event(&codec->dapm, stream, event); + //mutex_unlock(&codec->mutex); + return 0; } +void snd_soc_dapm_codec_stream_event(struct snd_soc_codec *codec, + const char *stream, int event) +{ +// mutex_lock(&codec->card->dapm_mutex); + soc_dapm_stream_event(&codec->dapm, stream, event); +// mutex_unlock(&codec->card->dapm_mutex); +} +EXPORT_SYMBOL(snd_soc_dapm_codec_stream_event); + /** * snd_soc_dapm_enable_pin - enable pin. * @dapm: DAPM context @@ -3228,13 +3347,9 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm) * standby. */ if (powerdown) { - if (dapm->bias_level == SND_SOC_BIAS_ON) - snd_soc_dapm_set_bias_level(dapm, - SND_SOC_BIAS_PREPARE); + snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_PREPARE); dapm_seq_run(dapm, &down_list, 0, false); - if (dapm->bias_level == SND_SOC_BIAS_PREPARE) - snd_soc_dapm_set_bias_level(dapm, - SND_SOC_BIAS_STANDBY); + snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_STANDBY); } } @@ -3247,9 +3362,7 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card) list_for_each_entry(codec, &card->codec_dev_list, list) { soc_dapm_shutdown_codec(&codec->dapm); - if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) - snd_soc_dapm_set_bias_level(&codec->dapm, - SND_SOC_BIAS_OFF); + snd_soc_dapm_set_bias_level(&codec->dapm, SND_SOC_BIAS_OFF); } } diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index ee4353f843eae07a82b7358004d9defb63ae5e96..9eb96e53100a636ffb6a7513411a579b7e063274 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -113,6 +113,23 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) } EXPORT_SYMBOL_GPL(snd_soc_jack_report); +/** + * snd_soc_jack_report_no_dapm - Report the current status for a jack + * without DAPM sync + * @jack: the jack + * @status: a bitmask of enum snd_jack_type values that are currently detected. + * @mask: a bitmask of enum snd_jack_type values that being reported. + */ +void snd_soc_jack_report_no_dapm(struct snd_soc_jack *jack, int status, + int mask) +{ + jack->status &= ~mask; + jack->status |= status & mask; + + snd_jack_report(jack->jack, jack->status); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_report_no_dapm); + /** * snd_soc_jack_add_zones - Associate voltage zones with jack * diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 0ad8dcacd2f39eefbdc434ac8bed0a4a56e3ea6f..3a9fbe18f2f0367acc9e6e303bdd1d89ed9cc9a4 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -22,12 +22,84 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include #include +#define MAX_BE_USERS 8 /* adjust if too low for everday use */ + +static int soc_dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream); + +/* ASoC no host IO hardware. + * TODO: fine tune these values for all host less transfers. + */ +static const struct snd_pcm_hardware no_host_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = PAGE_SIZE >> 2, + .period_bytes_max = PAGE_SIZE >> 1, + .periods_min = 2, + .periods_max = 4, + .buffer_bytes_max = PAGE_SIZE, +}; + +/* + * We can only hw_free, stop, pause or suspend a BE DAI if any of it's FE + * are not running, paused or suspended for the specified stream direction. + */ +int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + + list_for_each_entry(dpcm_params, &be->dpcm[stream].fe_clients, list_fe) { + + if (dpcm_params->fe == fe) + continue; + + if (dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_START || + dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PAUSED || + dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_SUSPEND) + return 0; + } + return 1; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop); + +/* + * We can only change hw params a BE DAI if any of it's FE are not prepared, + * running, paused or suspended for the specified stream direction. + */ +static int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + + list_for_each_entry(dpcm_params, &be->dpcm[stream].fe_clients, list_fe) { + + if (dpcm_params->fe == fe) + continue; + + if (dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_START || + dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PAUSED || + dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_SUSPEND || + dpcm_params->fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PREPARE) + return 0; + } + return 1; +} + static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, struct snd_soc_dai *soc_dai) { @@ -68,7 +140,7 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, * like the DAC/ADC resolution to use but there isn't right now. */ static int sample_sizes[] = { - 24, 32, + 8, 16, 24, 32, }; static void soc_pcm_apply_msb(struct snd_pcm_substream *substream, @@ -97,6 +169,29 @@ static void soc_pcm_apply_msb(struct snd_pcm_substream *substream, } } +/* + * stream event, send event to FE and all active BEs. + */ +int soc_dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, + int dir, const char *stream, int event) +{ + struct snd_soc_dpcm_params *dpcm_params; + + snd_soc_dapm_rtd_stream_event(fe, dir, event); + + list_for_each_entry(dpcm_params, &fe->dpcm[dir].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + + dev_dbg(be->dev, "pm: BE %s stream %s event %d dir %d\n", + be->dai_link->name, stream, event, dir); + + snd_soc_dapm_rtd_stream_event(be, dir, event); + } + + return 0; +} + /* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls @@ -119,12 +214,15 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) + snd_soc_set_runtime_hwparams(substream, &no_host_hardware); + /* startup the audio subsystem */ if (cpu_dai->driver->ops->startup) { ret = cpu_dai->driver->ops->startup(substream, cpu_dai); if (ret < 0) { - dev_err(cpu_dai->dev, "can't open interface %s: %d\n", - cpu_dai->name, ret); + printk(KERN_ERR "asoc: can't open interface %s\n", + cpu_dai->name); goto out; } } @@ -132,8 +230,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) if (platform->driver->ops && platform->driver->ops->open) { ret = platform->driver->ops->open(substream); if (ret < 0) { - dev_err(platform->dev, "can't open platform %s: %d\n", - platform->name, ret); + printk(KERN_ERR "asoc: can't open platform %s\n", platform->name); goto platform_err; } } @@ -141,8 +238,8 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) if (codec_dai->driver->ops->startup) { ret = codec_dai->driver->ops->startup(substream, codec_dai); if (ret < 0) { - dev_err(codec_dai->dev, "can't open codec %s: %d\n", - codec_dai->name, ret); + printk(KERN_ERR "asoc: can't open codec %s\n", + codec_dai->name); goto codec_dai_err; } } @@ -150,12 +247,15 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { ret = rtd->dai_link->ops->startup(substream); if (ret < 0) { - pr_err("asoc: %s startup failed: %d\n", - rtd->dai_link->name, ret); + printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name); goto machine_err; } } + /* DSP DAI links compat checks are different */ + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) + goto dynamic; + /* Check that the codec and cpu DAIs are compatible */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { runtime->hw.rate_min = @@ -248,6 +348,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); +dynamic: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active++; codec_dai->playback_active++; @@ -307,8 +408,9 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { codec_dai->pop_wait = 0; - snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, - codec_dai, SND_SOC_DAPM_STREAM_STOP); + snd_soc_dapm_stream_event(rtd, + codec_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_STOP); } mutex_unlock(&rtd->pcm_mutex); @@ -368,13 +470,13 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) cpu_dai->runtime = NULL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (!rtd->pmdown_time || codec->ignore_pmdown_time || - rtd->dai_link->ignore_pmdown_time) { + if (codec->ignore_pmdown_time || + rtd->dai_link->ignore_pmdown_time || + !rtd->pmdown_time) { /* powered down playback stream now */ snd_soc_dapm_stream_event(rtd, - SNDRV_PCM_STREAM_PLAYBACK, - codec_dai, - SND_SOC_DAPM_STREAM_STOP); + codec_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_STOP); } else { /* start delayed pop wq here for playback streams */ codec_dai->pop_wait = 1; @@ -383,8 +485,9 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) } } else { /* capture streams can be powered down now */ - snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE, - codec_dai, SND_SOC_DAPM_STREAM_STOP); + snd_soc_dapm_stream_event(rtd, + codec_dai->driver->capture.stream_name, + SND_SOC_DAPM_STREAM_STOP); } mutex_unlock(&rtd->pcm_mutex); @@ -414,7 +517,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) { ret = rtd->dai_link->ops->prepare(substream); if (ret < 0) { - pr_err("asoc: machine prepare error: %d\n", ret); + printk(KERN_ERR "asoc: machine prepare error\n"); goto out; } } @@ -422,8 +525,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) if (platform->driver->ops && platform->driver->ops->prepare) { ret = platform->driver->ops->prepare(substream); if (ret < 0) { - dev_err(platform->dev, "platform prepare error: %d\n", - ret); + printk(KERN_ERR "asoc: platform prepare error\n"); goto out; } } @@ -431,8 +533,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) if (codec_dai->driver->ops->prepare) { ret = codec_dai->driver->ops->prepare(substream, codec_dai); if (ret < 0) { - dev_err(codec_dai->dev, "DAI prepare error: %d\n", - ret); + printk(KERN_ERR "asoc: codec DAI prepare error\n"); goto out; } } @@ -440,8 +541,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) if (cpu_dai->driver->ops->prepare) { ret = cpu_dai->driver->ops->prepare(substream, cpu_dai); if (ret < 0) { - dev_err(cpu_dai->dev, "DAI prepare error: %d\n", - ret); + printk(KERN_ERR "asoc: cpu DAI prepare error\n"); goto out; } } @@ -453,8 +553,14 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) cancel_delayed_work(&rtd->delayed_work); } - snd_soc_dapm_stream_event(rtd, substream->stream, codec_dai, - SND_SOC_DAPM_STREAM_START); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_stream_event(rtd, + codec_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_START); + else + snd_soc_dapm_stream_event(rtd, + codec_dai->driver->capture.stream_name, + SND_SOC_DAPM_STREAM_START); snd_soc_dai_digital_mute(codec_dai, 0); @@ -482,7 +588,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) { ret = rtd->dai_link->ops->hw_params(substream, params); if (ret < 0) { - pr_err("asoc: machine hw_params failed: %d\n", ret); + printk(KERN_ERR "asoc: machine hw_params failed\n"); goto out; } } @@ -490,8 +596,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, if (codec_dai->driver->ops->hw_params) { ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai); if (ret < 0) { - dev_err(codec_dai->dev, "can't set %s hw params: %d\n", - codec_dai->name, ret); + printk(KERN_ERR "asoc: can't set codec %s hw params\n", + codec_dai->name); goto codec_err; } } @@ -499,8 +605,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, if (cpu_dai->driver->ops->hw_params) { ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai); if (ret < 0) { - dev_err(cpu_dai->dev, "%s hw params failed: %d\n", - cpu_dai->name, ret); + printk(KERN_ERR "asoc: interface %s hw params failed\n", + cpu_dai->name); goto interface_err; } } @@ -508,8 +614,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, if (platform->driver->ops && platform->driver->ops->hw_params) { ret = platform->driver->ops->hw_params(substream, params); if (ret < 0) { - dev_err(platform->dev, "%s hw params failed: %d\n", - platform->name, ret); + printk(KERN_ERR "asoc: platform %s hw params failed\n", + platform->name); goto platform_err; } } @@ -518,6 +624,20 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, cpu_dai->rate = params_rate(params); codec_dai->rate = params_rate(params); + /* malloc a page for hostless IO. + * FIXME: rework with alsa-lib changes so that this malloc is not required. + */ + if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) { + substream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV; + substream->dma_buffer.dev.dev = rtd->dev; + substream->dma_buffer.dev.dev->coherent_dma_mask = DMA_BIT_MASK(32); + substream->dma_buffer.private_data = NULL; + + ret = snd_pcm_lib_malloc_pages(substream, PAGE_SIZE); + if (ret < 0) + goto platform_err; + } + out: mutex_unlock(&rtd->pcm_mutex); return ret; @@ -570,6 +690,9 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) if (cpu_dai->driver->ops->hw_free) cpu_dai->driver->ops->hw_free(substream, cpu_dai); + if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) + snd_pcm_lib_free_pages(substream); + mutex_unlock(&rtd->pcm_mutex); return 0; } @@ -602,6 +725,34 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } +int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + if (codec_dai->driver->ops->bespoke_trigger) { + ret = codec_dai->driver->ops->bespoke_trigger(substream, cmd, codec_dai); + if (ret < 0) + return ret; + } + + if (platform->driver->bespoke_trigger) { + ret = platform->driver->bespoke_trigger(substream, cmd); + if (ret < 0) + return ret; + } + + if (cpu_dai->driver->ops->bespoke_trigger) { + ret = cpu_dai->driver->ops->bespoke_trigger(substream, cmd, cpu_dai); + if (ret < 0) + return ret; + } + return 0; +} + /* * soc level wrapper for pointer callback * If cpu_dai, codec_dai, platform driver has the delay callback, than @@ -634,74 +785,1918 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) return offset; } -/* create a new pcm */ -int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) +static inline int be_connect(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) { - struct snd_soc_codec *codec = rtd->codec; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_pcm_ops *soc_pcm_ops = &rtd->ops; - struct snd_pcm *pcm; - char new_name[64]; - int ret = 0, playback = 0, capture = 0; + struct snd_soc_dpcm_params *dpcm_params; - soc_pcm_ops->open = soc_pcm_open; - soc_pcm_ops->close = soc_pcm_close; - soc_pcm_ops->hw_params = soc_pcm_hw_params; - soc_pcm_ops->hw_free = soc_pcm_hw_free; - soc_pcm_ops->prepare = soc_pcm_prepare; - soc_pcm_ops->trigger = soc_pcm_trigger; - soc_pcm_ops->pointer = soc_pcm_pointer; - - /* check client and interface hw capabilities */ - snprintf(new_name, sizeof(new_name), "%s %s-%d", - rtd->dai_link->stream_name, codec_dai->name, num); - - if (codec_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min) - capture = 1; - - dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name); - ret = snd_pcm_new(rtd->card->snd_card, new_name, - num, playback, capture, &pcm); - if (ret < 0) { - printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name); - return ret; + if (!fe->dpcm[stream].runtime) { + dev_err(fe->dev, "%s no runtime\n", fe->dai_link->name); + return -ENODEV; } - /* DAPM dai link stream work */ - INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); + /* only add new dpcm_paramss */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + if (dpcm_params->be == be && dpcm_params->fe == fe) + return 0; + } - rtd->pcm = pcm; - pcm->private_data = rtd; - if (platform->driver->ops) { - soc_pcm_ops->mmap = platform->driver->ops->mmap; - soc_pcm_ops->pointer = platform->driver->ops->pointer; - soc_pcm_ops->ioctl = platform->driver->ops->ioctl; - soc_pcm_ops->copy = platform->driver->ops->copy; - soc_pcm_ops->silence = platform->driver->ops->silence; - soc_pcm_ops->ack = platform->driver->ops->ack; - soc_pcm_ops->page = platform->driver->ops->page; + dpcm_params = kzalloc(sizeof(struct snd_soc_dpcm_params), GFP_KERNEL); + if (!dpcm_params) + return -ENOMEM; + + dpcm_params->be = be; + dpcm_params->fe = fe; + be->dpcm[stream].runtime = fe->dpcm[stream].runtime; + dpcm_params->state = SND_SOC_DPCM_LINK_STATE_NEW; + list_add(&dpcm_params->list_be, &fe->dpcm[stream].be_clients); + list_add(&dpcm_params->list_fe, &be->dpcm[stream].fe_clients); + + dev_dbg(fe->dev, " connected new DSP %s path %s %s %s\n", + stream ? "capture" : "playback", fe->dai_link->name, + stream ? "<-" : "->", be->dai_link->name); + +#ifdef CONFIG_DEBUG_FS + dpcm_params->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, + fe->debugfs_dpcm_root, &dpcm_params->state); +#endif + + return 1; +} + +static inline void be_reparent(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + struct snd_pcm_substream *fe_substream, *be_substream; + + /* reparent if BE is connected to other FEs */ + if (!be->dpcm[stream].users) + return; + + be_substream = snd_soc_dpcm_get_substream(be, stream); + + list_for_each_entry(dpcm_params, &be->dpcm[stream].fe_clients, list_fe) { + if (dpcm_params->fe != fe) { + + dev_dbg(fe->dev, " reparent %s path %s %s %s\n", + stream ? "capture" : "playback", + dpcm_params->fe->dai_link->name, + stream ? "<-" : "->", dpcm_params->be->dai_link->name); + + fe_substream = snd_soc_dpcm_get_substream(dpcm_params->fe, + stream); + be_substream->runtime = fe_substream->runtime; + break; + } } +} - if (playback) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops); +static inline void be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params, *d; - if (capture) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops); + list_for_each_entry_safe(dpcm_params, d, &fe->dpcm[stream].be_clients, list_be) { + dev_dbg(fe->dev, "BE %s disconnect check for %s\n", + stream ? "capture" : "playback", + dpcm_params->be->dai_link->name); - if (platform->driver->pcm_new) { - ret = platform->driver->pcm_new(rtd); - if (ret < 0) { - pr_err("asoc: platform pcm constructor failed\n"); - return ret; + if (dpcm_params->state == SND_SOC_DPCM_LINK_STATE_FREE) { + dev_dbg(fe->dev, " freed DSP %s path %s %s %s\n", + stream ? "capture" : "playback", fe->dai_link->name, + stream ? "<-" : "->", dpcm_params->be->dai_link->name); + + /* BEs still alive need new FE */ + be_reparent(fe, dpcm_params->be, stream); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(dpcm_params->debugfs_state); +#endif + + list_del(&dpcm_params->list_be); + list_del(&dpcm_params->list_fe); + kfree(dpcm_params); } } +} - pcm->private_free = platform->driver->pcm_free; - printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name, - cpu_dai->name); +static struct snd_soc_pcm_runtime *be_get_rtd(struct snd_soc_card *card, + struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_pcm_runtime *be; + int i; + + if (!widget->sname) { + dev_err(card->dev, "widget %s has no stream\n", widget->name); + return NULL; + } + + for (i = 0; i < card->num_links; i++) { + be = &card->rtd[i]; + + if (!strcmp(widget->sname, be->dai_link->stream_name)) + return be; + } + + dev_err(card->dev, "can't get BE for %s\n", widget->name); + return NULL; +} + +static struct snd_soc_dapm_widget *be_get_widget(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_widget *widget; + + list_for_each_entry(widget, &card->widgets, list) { + + if (!widget->sname) + continue; + + if (!strcmp(widget->sname, rtd->dai_link->stream_name)) + return widget; + } + + dev_err(card->dev, "can't get widget for %s\n", + rtd->dai_link->stream_name); + return NULL; +} + +static int widget_in_list(struct snd_soc_dapm_widget_list *list, + struct snd_soc_dapm_widget *widget) +{ + int i; + + for (i = 0; i < list->num_widgets; i++) { + if (widget == list->widgets[i]) + return 1; + } + + return 0; +} + +static int fe_path_get(struct snd_soc_pcm_runtime *fe, + int stream, struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + struct snd_soc_dapm_widget_list *list; + int paths; + + list = kzalloc(sizeof(struct snd_soc_dapm_widget_list) + + sizeof(struct snd_soc_dapm_widget *), GFP_KERNEL); + if (list == NULL) + return -ENOMEM; + + /* get number of valid DAI paths and their widgets */ + paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, &list); + + dev_dbg(fe->dev, "found %d audio %s paths\n", paths, + stream ? "capture" : "playback"); + + *list_ = list; + return paths; +} + +static inline void fe_path_put(struct snd_soc_dapm_widget_list **list) +{ + kfree(*list); +} + +static int be_prune_old(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_card *card = fe->card; + struct snd_soc_dpcm_params *dpcm_params; + struct snd_soc_dapm_widget_list *list = *list_; + struct snd_soc_dapm_widget *widget; + int old = 0; + + /* Destroy any old FE <--> BE connections */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + /* is there a valid widget for this BE */ + widget = be_get_widget(card, dpcm_params->be); + if (!widget) { + dev_err(fe->dev, "no widget found for %s\n", + dpcm_params->be->dai_link->name); + continue; + } + + /* prune the BE if it's no longer in our active list */ + if (widget_in_list(list, widget)) + continue; + + dev_dbg(fe->dev, "pruning %s BE %s for %s\n", + stream ? "capture" : "playback", dpcm_params->be->dai_link->name, + fe->dai_link->name); + dpcm_params->state = SND_SOC_DPCM_LINK_STATE_FREE; + dpcm_params->be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + old++; + } + + dev_dbg(fe->dev, "found %d old BEs\n", old); + return old; +} + +static int be_add_new(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_card *card = fe->card; + struct snd_soc_dapm_widget_list *list = *list_; + enum snd_soc_dapm_type be_type; + int i, new = 0, err; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + be_type = snd_soc_dapm_aif_out; + else + be_type = snd_soc_dapm_aif_in; + + /* Create any new FE <--> BE connections */ + for (i = 0; i < list->num_widgets; i++) { + + if (list->widgets[i]->id == be_type) { + struct snd_soc_pcm_runtime *be; + + /* is there a valid BE rtd for this widget */ + be = be_get_rtd(card, list->widgets[i]); + if (!be) { + dev_err(fe->dev, "no BE found for %s\n", + list->widgets[i]->name); + continue; + } + + /* don't connect if FE is not running */ + if (!fe->dpcm[stream].runtime) + continue; + + /* newly connected FE and BE */ + err = be_connect(fe, be, stream); + if (err < 0) { + dev_err(fe->dev, "can't connect %s\n", list->widgets[i]->name); + break; + } else if (err == 0) /* already connected */ + continue; + + /* new */ + be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + new++; + } + } + + dev_dbg(fe->dev, "found %d new BEs\n", new); + return new; +} + +/* + * Find the corresponding BE DAIs that source or sink audio to this + * FE substream. + */ +static int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, + int stream, struct snd_soc_dapm_widget_list **list, int new) +{ + if (new) + return be_add_new(fe, stream, list); + else + return be_prune_old(fe, stream, list); + return 0; +} + +/* + * Clear the runtime pending state of all BE's. + */ +static void fe_clear_pending(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) + dpcm_params->be->dpcm[stream].runtime_update = + SND_SOC_DPCM_UPDATE_NO; +} + +/* Unwind the BE startup */ +static void soc_dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* disable any enabled and non active backends */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "no users %s at close - state %d\n", + stream ? "capture" : "playback", be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + continue; + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } +} + +/* Startup all new BE */ +static int soc_dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + int err, count = 0; + + /* only startup BE DAIs that are either sinks or sources to this FE DAI */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* first time the dpcm_params is open ? */ + if (be->dpcm[stream].users == MAX_BE_USERS) + dev_err(be->dev, "too many users %s at open - state %d\n", + stream ? "capture" : "playback", be->dpcm[stream].state); + + if (be->dpcm[stream].users++ != 0) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_NEW) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) + continue; + + dev_dbg(be->dev, "dpcm: open BE %s\n", be->dai_link->name); + + be_substream->runtime = be->dpcm[stream].runtime; + err = soc_pcm_open(be_substream); + if (err < 0) { + dev_err(be->dev, "BE open failed %d\n", err); + be->dpcm[stream].users--; + if (be->dpcm[stream].users < 0) + dev_err(be->dev, "no users %s at unwind - state %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + goto unwind; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + count++; + } + + return count; + +unwind: + /* disable any enabled and non active backends */ + list_for_each_entry_continue_reverse(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "no users %s at close - state %d\n", + stream ? "capture" : "playback", be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + continue; + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } + + return err; +} + +void soc_dpcm_set_dynamic_runtime(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 *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.rate_min = cpu_dai_drv->playback.rate_min; + runtime->hw.rate_max = cpu_dai_drv->playback.rate_max; + runtime->hw.channels_min = cpu_dai_drv->playback.channels_min; + runtime->hw.channels_max = cpu_dai_drv->playback.channels_max; + runtime->hw.formats &= cpu_dai_drv->playback.formats; + runtime->hw.rates = cpu_dai_drv->playback.rates; + } else { + runtime->hw.rate_min = cpu_dai_drv->capture.rate_min; + runtime->hw.rate_max = cpu_dai_drv->capture.rate_max; + runtime->hw.channels_min = cpu_dai_drv->capture.channels_min; + runtime->hw.channels_max = cpu_dai_drv->capture.channels_max; + runtime->hw.formats &= cpu_dai_drv->capture.formats; + runtime->hw.rates = cpu_dai_drv->capture.rates; + } +} + +static int soc_dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_pcm_runtime *runtime = fe_substream->runtime; + int stream = fe_substream->stream, ret = 0; + + mutex_lock(&fe->card->dpcm_mutex); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + ret = soc_dpcm_be_dai_startup(fe, fe_substream->stream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: failed to start some BEs %d\n", ret); + goto be_err; + } + + dev_dbg(fe->dev, "dpcm: open FE %s\n", fe->dai_link->name); + + /* start the DAI frontend */ + ret = soc_pcm_open(fe_substream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: failed to start FE %d\n", ret); + goto unwind; + } + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + + soc_dpcm_set_dynamic_runtime(fe_substream); + snd_pcm_limit_hw_rates(runtime); + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->dpcm_mutex); + return 0; + +unwind: + soc_dpcm_be_dai_startup_unwind(fe, fe_substream->stream); +be_err: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->dpcm_mutex); return ret; } + +/* BE shutdown - called on DAPM sync updates (i.e. FE is already running)*/ +static int soc_dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* only shutdown backends that are either sinks or sources to this frontend DAI */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "no users %s at close - state %d\n", + stream ? "capture" : "playback", be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN)) + continue; + + dev_dbg(be->dev, "dpcm: close BE %s\n", + dpcm_params->fe->dai_link->name); + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } + return 0; +} + +/* FE +BE shutdown - called on FE PCM ops */ +static int soc_dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream; + + mutex_lock(&fe->card->dpcm_mutex); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + dev_dbg(fe->dev, "dpcm: close FE %s\n", fe->dai_link->name); + + /* now shutdown the frontend */ + soc_pcm_close(substream); + + /* shutdown the BEs */ + soc_dpcm_be_dai_shutdown(fe, substream->stream); + /* run the stream event for each BE */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_STOP); + else + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->capture.stream_name, + SND_SOC_DAPM_STREAM_STOP); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + mutex_unlock(&fe->card->dpcm_mutex); + return 0; +} + +static int soc_dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + int ret; + + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only allow hw_params() if no connected FEs are running */ + if (!snd_soc_dpcm_can_be_params(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE)) + continue; + + dev_dbg(be->dev, "dpcm: hw_params BE %s\n", + dpcm_params->fe->dai_link->name); + + /* copy params for each dpcm_params */ + memcpy(&dpcm_params->hw_params, &fe->dpcm[stream].hw_params, + sizeof(struct snd_pcm_hw_params)); + + /* perform any hw_params fixups */ + if (be->dai_link->be_hw_params_fixup) { + ret = be->dai_link->be_hw_params_fixup(be, + &dpcm_params->hw_params); + if (ret < 0) { + dev_err(be->dev, + "dpcm: hw_params BE fixup failed %d\n", + ret); + goto unwind; + } + } + + ret = soc_pcm_hw_params(be_substream, &dpcm_params->hw_params); + if (ret < 0) { + dev_err(dpcm_params->be->dev, "dpcm: hw_params BE failed %d\n", ret); + goto unwind; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; + } + return 0; + +unwind: + /* disable any enabled and non active backends */ + list_for_each_entry_continue_reverse(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only allow hw_free() if no connected FEs are running */ + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + soc_pcm_hw_free(be_substream); + } + + return ret; +} + +int soc_dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int ret, stream = substream->stream; + + mutex_lock(&fe->card->dpcm_mutex); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + memcpy(&fe->dpcm[substream->stream].hw_params, params, + sizeof(struct snd_pcm_hw_params)); + ret = soc_dpcm_be_dai_hw_params(fe, substream->stream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: hw_params failed for some BEs %d\n", ret); + goto out; + } + + dev_dbg(fe->dev, "dpcm: hw_params FE %s rate %d chan %x fmt %d\n", + fe->dai_link->name, params_rate(params), params_channels(params), + params_format(params)); + + /* call hw_params on the frontend */ + ret = soc_pcm_hw_params(substream, params); + if (ret < 0) { + dev_err(fe->dev,"dpcm: hw_params FE failed %d\n", ret); + soc_dpcm_be_dai_hw_free(fe, stream); + } else + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->dpcm_mutex); + return ret; +} + +static int dpcm_do_trigger(struct snd_soc_dpcm_params *dpcm_params, + struct snd_pcm_substream *substream, int cmd) +{ + int ret; + + dev_dbg(dpcm_params->be->dev, "dpcm: trigger BE %s cmd %d\n", + dpcm_params->fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) + dev_err(dpcm_params->be->dev,"dpcm: trigger BE failed %d\n", ret); + + return ret; +} + +int soc_dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, int cmd) +{ + struct snd_soc_dpcm_params *dpcm_params; + int ret = 0; + + if ((cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) || + (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)) + return ret; + + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + ret = dpcm_do_trigger(dpcm_params, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_RESUME: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) + continue; + + ret = dpcm_do_trigger(dpcm_params, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + continue; + + ret = dpcm_do_trigger(dpcm_params, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm_params, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm_params, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm_params, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(soc_dpcm_be_dai_trigger); + +int soc_dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream, ret; + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE: + /* call trigger on the frontend before the backend. */ + + dev_dbg(fe->dev, "dpcm: pre trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto out; + } + + ret = soc_dpcm_be_dai_trigger(fe, substream->stream, cmd); + break; + case SND_SOC_DPCM_TRIGGER_POST: + /* call trigger on the frontend after the backend. */ + + ret = soc_dpcm_be_dai_trigger(fe, substream->stream, cmd); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto out; + } + + dev_dbg(fe->dev, "dpcm: post trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + break; + case SND_SOC_DPCM_TRIGGER_BESPOKE: + /* bespoke trigger() - handles both FE and BEs */ + + dev_dbg(fe->dev, "dpcm: bespoke trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_bespoke_trigger(substream, cmd); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto out; + } + break; + default: + dev_err(fe->dev, "dpcm: invalid trigger cmd %d for %s\n", cmd, + fe->dai_link->name); + ret = -EINVAL; + goto out; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + } + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + return ret; +} + +static int soc_dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + int ret = 0; + + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + dev_dbg(be->dev, "dpcm: prepare BE %s\n", + dpcm_params->fe->dai_link->name); + + ret = soc_pcm_prepare(be_substream); + if (ret < 0) { + dev_err(be->dev, "dpcm: backend prepare failed %d\n", + ret); + break; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + } + return ret; +} + +int soc_dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream, ret = 0; + + mutex_lock(&fe->card->dpcm_mutex); + + dev_dbg(fe->dev, "dpcm: prepare FE %s\n", fe->dai_link->name); + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + /* there is no point preparing this FE if there are no BEs */ + if (list_empty(&fe->dpcm[stream].be_clients)) { + dev_err(fe->dev, "dpcm: no backend DAIs enabled for %s\n", + fe->dai_link->name); + ret = -EINVAL; + goto out; + } + + ret = soc_dpcm_be_dai_prepare(fe, substream->stream); + if (ret < 0) + goto out; + + /* call prepare on the frontend */ + ret = soc_pcm_prepare(substream); + if (ret < 0) { + dev_err(fe->dev,"dpcm: prepare FE %s failed\n", fe->dai_link->name); + goto out; + } + + /* run the stream event for each BE */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_START); + else + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->capture.stream_name, + SND_SOC_DAPM_STREAM_START); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->dpcm_mutex); + return ret; +} + +static int soc_dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* only hw_params backends that are either sinks or sources + * to this frontend DAI */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only free hw when no longer used - check all FEs */ + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + dev_dbg(be->dev, "dpcm: hw_free BE %s\n", + dpcm_params->fe->dai_link->name); + + soc_pcm_hw_free(be_substream); + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + } + + return 0; +} + +int soc_dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int err, stream = substream->stream; + + mutex_lock(&fe->card->dpcm_mutex); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + dev_dbg(fe->dev, "dpcm: hw_free FE %s\n", fe->dai_link->name); + + /* call hw_free on the frontend */ + err = soc_pcm_hw_free(substream); + if (err < 0) + dev_err(fe->dev,"dpcm: hw_free FE %s failed\n", fe->dai_link->name); + + /* only hw_params backends that are either sinks or sources + * to this frontend DAI */ + err = soc_dpcm_be_dai_hw_free(fe, stream); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + mutex_unlock(&fe->card->dpcm_mutex); + return 0; +} + +static int soc_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + + if (platform->driver->ops->ioctl) + return platform->driver->ops->ioctl(substream, cmd, arg); + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *substream = snd_soc_dpcm_get_substream(fe, stream); + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int err; + + dev_dbg(fe->dev, "runtime %s close on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { + /* call bespoke trigger - FE takes care of all BE triggers */ + dev_dbg(fe->dev, "dpcm: bespoke trigger FE %s cmd stop\n", + fe->dai_link->name); + + err = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", err); + } else { + dev_dbg(fe->dev, "dpcm: trigger FE %s cmd stop\n", + fe->dai_link->name); + + err = soc_dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", err); + } + + err = soc_dpcm_be_dai_hw_free(fe, stream); + if (err < 0) + dev_err(fe->dev,"dpcm: hw_free FE failed %d\n", err); + + err = soc_dpcm_be_dai_shutdown(fe, stream); + if (err < 0) + dev_err(fe->dev,"dpcm: shutdown FE failed %d\n", err); + + /* run the stream event for each BE */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_NOP); + else + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->capture.stream_name, + SND_SOC_DAPM_STREAM_NOP); + + return 0; +} + +static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *substream = snd_soc_dpcm_get_substream(fe, stream); + struct snd_soc_dpcm_params *dpcm_params; + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int ret; + + dev_dbg(fe->dev, "runtime %s open on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + /* Only start the BE if the FE is ready */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_FREE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE) + return -EINVAL; + + /* startup must always be called for new BEs */ + ret = soc_dpcm_be_dai_startup(fe, stream); + if (ret < 0) { + goto disconnect; + return ret; + } + + /* keep going if FE state is > open */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_OPEN) + return 0; + + ret = soc_dpcm_be_dai_hw_params(fe, stream); + if (ret < 0) { + goto close; + return ret; + } + + /* keep going if FE state is > hw_params */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_PARAMS) + return 0; + + + ret = soc_dpcm_be_dai_prepare(fe, stream); + if (ret < 0) { + goto hw_free; + return ret; + } + + /* run the stream event for each BE */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_NOP); + else + soc_dpcm_dapm_stream_event(fe, stream, + fe->cpu_dai->driver->capture.stream_name, + SND_SOC_DAPM_STREAM_NOP); + + /* keep going if FE state is > prepare */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PREPARE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_STOP) + return 0; + + if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { + /* call trigger on the frontend - FE takes care of all BE triggers */ + dev_dbg(fe->dev, "dpcm: bespoke trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"dpcm: bespoke trigger FE failed %d\n", ret); + goto hw_free; + } + } else { + dev_dbg(fe->dev, "dpcm: trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = soc_dpcm_be_dai_trigger(fe, stream, + SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"dpcm: trigger FE failed %d\n", ret); + goto hw_free; + } + } + + return 0; + +hw_free: + soc_dpcm_be_dai_hw_free(fe, stream); +close: + soc_dpcm_be_dai_shutdown(fe, stream); +disconnect: + /* disconnect any non started BEs */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm_params->be; + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + dpcm_params->state = SND_SOC_DPCM_LINK_STATE_FREE; + } + + return ret; +} + +static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + int ret; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + ret = dpcm_run_update_startup(fe, stream); + if (ret < 0) + dev_err(fe->dev, "failed to startup some BEs\n"); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + return ret; +} + +static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + int ret; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + ret = dpcm_run_update_shutdown(fe, stream); + if (ret < 0) + dev_err(fe->dev, "failed to shutdown some BEs\n"); + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + return ret; +} + +/* called when any mixer updates change FE -> BE the stream */ +int soc_dpcm_runtime_update(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_card *card; + int i, ret = 0, old, new, paths; + + if (widget->codec) + card = widget->codec->card; + else if (widget->platform) + card = widget->platform->card; + else + return -EINVAL; + + mutex_lock(&card->dpcm_mutex); + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dapm_widget_list *list; + struct snd_soc_pcm_runtime *fe = &card->rtd[i]; + + /* make sure link is FE */ + if (!fe->dai_link->dynamic) + continue; + + /* only check active links */ + if (!fe->cpu_dai->active) + continue; + + /* DAPM sync will call this to update DSP paths */ + dev_dbg(fe->dev, "DPCM runtime update for FE %s\n", fe->dai_link->name); + + /* skip if FE doesn't have playback capability */ + if (!fe->cpu_dai->driver->playback.channels_min) + goto capture; + + paths = fe_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list); + if (paths < 0) { + dev_warn(fe->dev, "%s no valid %s route from source to sink\n", + fe->dai_link->name, "playback"); + ret = paths; + goto out; + } + + /* update any new playback paths */ + new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 1); + if (new) { + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + fe_clear_pending(fe, SNDRV_PCM_STREAM_PLAYBACK); + be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + + /* update any old playback paths */ + old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 0); + if (old) { + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + fe_clear_pending(fe, SNDRV_PCM_STREAM_PLAYBACK); + be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + +capture: + /* skip if FE doesn't have capture capability */ + if (!fe->cpu_dai->driver->capture.channels_min) + continue; + + paths = fe_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list); + if (paths < 0) { + dev_warn(fe->dev, "%s no valid %s route from source to sink\n", + fe->dai_link->name, "capture"); + ret = paths; + goto out; + } + + /* update any new capture paths */ + new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 1); + if (new) { + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_CAPTURE); + fe_clear_pending(fe, SNDRV_PCM_STREAM_CAPTURE); + be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + /* update any old capture paths */ + old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 0); + if (old) { + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_CAPTURE); + fe_clear_pending(fe, SNDRV_PCM_STREAM_CAPTURE); + be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + fe_path_put(&list); + } + +out: + mutex_unlock(&card->dpcm_mutex); + return ret; +} + +int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) +{ + struct snd_soc_dpcm_params *dpcm_params; + + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->codec_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "BE digital mute %s\n", be->dai_link->name); + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, mute); + } + + return 0; +} + +int soc_dpcm_be_cpu_dai_suspend(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* suspend for playback */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI playback suspend %s\n", + be->dai_link->name); + + if (drv->suspend && !drv->ac97_control) + drv->suspend(dai); + } + + /* suspend for capture */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI capture suspend %s\n", + be->dai_link->name); + + if (drv->suspend && !drv->ac97_control) + drv->suspend(dai); + } + + return 0; +} + +int soc_dpcm_be_ac97_cpu_dai_suspend(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* suspend for playback */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI playback suspend %s\n", + be->dai_link->name); + + if (drv->suspend && drv->ac97_control) + drv->suspend(dai); + } + + /* suspend for capture */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI capture suspend %s\n", + be->dai_link->name); + + if (drv->suspend && drv->ac97_control) + drv->suspend(dai); + } + + return 0; +} + +int soc_dpcm_be_platform_suspend(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* suspend for playback */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_platform *platform = be->platform; + struct snd_soc_platform_driver *drv = platform->driver; + struct snd_soc_dai *dai = be->cpu_dai; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE platform playback suspend %s\n", + be->dai_link->name); + + if (drv->suspend && !platform->suspended) { + drv->suspend(dai); + platform->suspended = 1; + } + } + + /* suspend for capture */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_platform *platform = be->platform; + struct snd_soc_platform_driver *drv = platform->driver; + struct snd_soc_dai *dai = be->cpu_dai; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE platform capture suspend %s\n", + be->dai_link->name); + + if (drv->suspend && !platform->suspended) { + drv->suspend(dai); + platform->suspended = 1; + } + } + return 0; +} + +int soc_dpcm_fe_suspend(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dai *dai = fe->cpu_dai; + struct snd_soc_dai_driver *dai_drv = dai->driver; + struct snd_soc_platform *platform = fe->platform; + struct snd_soc_platform_driver *plat_drv = platform->driver; + + if (dai_drv->suspend && !dai_drv->ac97_control) + dai_drv->suspend(dai); + + if (plat_drv->suspend && !platform->suspended) { + plat_drv->suspend(dai); + platform->suspended = 1; + } + + soc_dpcm_be_cpu_dai_suspend(fe); + soc_dpcm_be_platform_suspend(fe); + + return 0; +} + +int soc_dpcm_be_cpu_dai_resume(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* resume for playback */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI playback resume %s\n", + be->dai_link->name); + + if (drv->resume && !drv->ac97_control) + drv->resume(dai); + } + + /* suspend for capture */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI capture resume %s\n", + be->dai_link->name); + + if (drv->resume && !drv->ac97_control) + drv->resume(dai); + } + + return 0; +} + +int soc_dpcm_be_ac97_cpu_dai_resume(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* resume for playback */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI playback resume %s\n", + be->dai_link->name); + + if (drv->resume && drv->ac97_control) + drv->resume(dai); + } + + /* suspend for capture */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_dai *dai = be->cpu_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE CPU DAI capture resume %s\n", + be->dai_link->name); + + if (drv->resume && drv->ac97_control) + drv->resume(dai); + } + + return 0; +} + +int soc_dpcm_be_platform_resume(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dpcm_params *dpcm_params; + + /* resume for playback */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_platform *platform = be->platform; + struct snd_soc_platform_driver *drv = platform->driver; + struct snd_soc_dai *dai = be->cpu_dai; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE platform playback resume %s\n", + be->dai_link->name); + + if (drv->resume && platform->suspended) { + drv->resume(dai); + platform->suspended = 0; + } + } + + /* resume for capture */ + list_for_each_entry(dpcm_params, + &fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm_params->be; + struct snd_soc_platform *platform = be->platform; + struct snd_soc_platform_driver *drv = platform->driver; + struct snd_soc_dai *dai = be->cpu_dai; + + if (be->dai_link->ignore_suspend) + continue; + + dev_dbg(be->dev, "pm: BE platform capture resume %s\n", + be->dai_link->name); + + if (drv->resume && platform->suspended) { + drv->resume(dai); + platform->suspended = 0; + } + } + + return 0; +} + +int soc_dpcm_fe_resume(struct snd_soc_pcm_runtime *fe) +{ + struct snd_soc_dai *dai = fe->cpu_dai; + struct snd_soc_dai_driver *dai_drv = dai->driver; + struct snd_soc_platform *platform = fe->platform; + struct snd_soc_platform_driver *plat_drv = platform->driver; + + soc_dpcm_be_cpu_dai_resume(fe); + soc_dpcm_be_platform_resume(fe); + + if (dai_drv->resume && !dai_drv->ac97_control) + dai_drv->resume(dai); + + if (plat_drv->resume && platform->suspended) { + plat_drv->resume(dai); + platform->suspended = 0; + } + + return 0; +} + +/* called when opening FE stream */ +int soc_dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dpcm_params *dpcm_params; + struct snd_soc_dapm_widget_list *list; + int ret; + int stream = fe_substream->stream; + + fe->dpcm[stream].runtime = fe_substream->runtime; + + if (fe_path_get(fe, stream, &list) <= 0) { + dev_warn(fe->dev, "asoc: %s no valid %s route from source to sink\n", + fe->dai_link->name, stream ? "capture" : "playback"); + return -EINVAL; + } + + /* calculate valid and active FE <-> BE dpcm_paramss */ + dpcm_process_paths(fe, stream, &list, 1); + + ret = soc_dpcm_fe_dai_startup(fe_substream); + if (ret < 0) { + /* clean up all links */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) + dpcm_params->state = SND_SOC_DPCM_LINK_STATE_FREE; + + be_disconnect(fe, stream); + fe->dpcm[stream].runtime = NULL; + } + + fe_clear_pending(fe, stream); + fe_path_put(&list); + return ret; +} + +/* called when closing FE stream */ +int soc_dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dpcm_params *dpcm_params; + int stream = fe_substream->stream, ret; + + ret = soc_dpcm_fe_dai_shutdown(fe_substream); + + /* mark FE's links ready to prune */ + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) + dpcm_params->state = SND_SOC_DPCM_LINK_STATE_FREE; + + be_disconnect(fe, stream); + + fe->dpcm[stream].runtime = NULL; + + return ret; +} + +/* create a new pcm */ +int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_platform *platform = rtd->platform; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_substream *substream[2]; + struct snd_pcm *pcm; + char new_name[64]; + int ret = 0, playback = 0, capture = 0; + + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { + if (cpu_dai->driver->playback.channels_min) + playback = 1; + if (cpu_dai->driver->capture.channels_min) + capture = 1; + } else { + if (codec_dai->driver->playback.channels_min) + playback = 1; + if (codec_dai->driver->capture.channels_min) + capture = 1; + } + + /* create the PCM */ + if (rtd->dai_link->no_pcm) { + snprintf(new_name, sizeof(new_name), "(%s)", + rtd->dai_link->stream_name); + + ret = snd_pcm_new_soc_be(rtd->card->snd_card, new_name, num, + playback, capture, &pcm); + } else { + if (rtd->dai_link->dynamic) + snprintf(new_name, sizeof(new_name), "%s (*)", + rtd->dai_link->stream_name); + else + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, codec_dai->name, num); + + ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, + capture, &pcm); + } + if (ret < 0) { + printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name); + return ret; + } + dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num, new_name); + + rtd->pcm = pcm; + pcm->private_data = rtd; + INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); + + substream[SNDRV_PCM_STREAM_PLAYBACK] = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + substream[SNDRV_PCM_STREAM_CAPTURE] = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + + if (rtd->dai_link->no_pcm) { + if (playback) + substream[SNDRV_PCM_STREAM_PLAYBACK]->private_data = rtd; + if (capture) + substream[SNDRV_PCM_STREAM_CAPTURE]->private_data = rtd; + goto out; + } + + /* setup any hostless PCMs - i.e. no host IO is performed */ + if (rtd->dai_link->no_host_mode) { + if (substream[SNDRV_PCM_STREAM_PLAYBACK]) { + substream[SNDRV_PCM_STREAM_PLAYBACK]->hw_no_buffer = 1; + snd_soc_set_runtime_hwparams( + substream[SNDRV_PCM_STREAM_PLAYBACK], + &no_host_hardware); + } + if (substream[SNDRV_PCM_STREAM_CAPTURE]) { + substream[SNDRV_PCM_STREAM_CAPTURE]->hw_no_buffer = 1; + snd_soc_set_runtime_hwparams( + substream[SNDRV_PCM_STREAM_CAPTURE], + &no_host_hardware); + } + } + + /* ASoC PCM operations */ + if (rtd->dai_link->dynamic) { + rtd->ops.open = soc_dpcm_fe_dai_open; + rtd->ops.hw_params = soc_dpcm_fe_dai_hw_params; + rtd->ops.prepare = soc_dpcm_fe_dai_prepare; + rtd->ops.trigger = soc_dpcm_fe_dai_trigger; + rtd->ops.hw_free = soc_dpcm_fe_dai_hw_free; + rtd->ops.close = soc_dpcm_fe_dai_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } else { + rtd->ops.open = soc_pcm_open; + rtd->ops.hw_params = soc_pcm_hw_params; + rtd->ops.prepare = soc_pcm_prepare; + rtd->ops.trigger = soc_pcm_trigger; + rtd->ops.hw_free = soc_pcm_hw_free; + rtd->ops.close = soc_pcm_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } + + if (platform->driver->ops) { + rtd->ops.ack = platform->driver->ops->ack; + rtd->ops.copy = platform->driver->ops->copy; + rtd->ops.silence = platform->driver->ops->silence; + rtd->ops.page = platform->driver->ops->page; + rtd->ops.mmap = platform->driver->ops->mmap; + } + + if (playback) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops); + + if (capture) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops); + + if (platform->driver->pcm_new) { + ret = platform->driver->pcm_new(rtd); + if (ret < 0) { + pr_err("asoc: platform pcm constructor failed\n"); + return ret; + } + } + + pcm->private_free = platform->driver->pcm_free; +out: + printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name, + cpu_dai->name); + return ret; +} + +#ifdef CONFIG_DEBUG_FS +static char *dpcm_state_string(enum snd_soc_dpcm_state state) +{ + switch (state) { + case SND_SOC_DPCM_STATE_NEW: + return "new"; + case SND_SOC_DPCM_STATE_OPEN: + return "open"; + case SND_SOC_DPCM_STATE_HW_PARAMS: + return "hw_params"; + case SND_SOC_DPCM_STATE_PREPARE: + return "prepare"; + case SND_SOC_DPCM_STATE_START: + return "start"; + case SND_SOC_DPCM_STATE_STOP: + return "stop"; + case SND_SOC_DPCM_STATE_SUSPEND: + return "suspend"; + case SND_SOC_DPCM_STATE_PAUSED: + return "paused"; + case SND_SOC_DPCM_STATE_HW_FREE: + return "hw_free"; + case SND_SOC_DPCM_STATE_CLOSE: + return "close"; + } + + return "unknown"; +} + +static int soc_dpcm_state_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t soc_dpcm_show_state(struct snd_soc_pcm_runtime *fe, + int stream, char *buf, size_t size) +{ + struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; + struct snd_soc_dpcm_params *dpcm_params; + ssize_t offset = 0; + + /* FE state */ + offset += snprintf(buf + offset, size - offset, + "[%s - %s]\n", fe->dai_link->name, + stream ? "Capture" : "Playback"); + + offset += snprintf(buf + offset, size - offset, "State: %s\n", + dpcm_state_string(fe->dpcm[stream].state)); + + if ((fe->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (fe->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += snprintf(buf + offset, size - offset, + "Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + + /* BEs state */ + offset += snprintf(buf + offset, size - offset, "Backends:\n"); + + if (list_empty(&fe->dpcm[stream].be_clients)) { + offset += snprintf(buf + offset, size - offset, + " No active DSP links\n"); + goto out; + } + + list_for_each_entry(dpcm_params, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm_params->be; + + offset += snprintf(buf + offset, size - offset, + "- %s\n", be->dai_link->name); + + offset += snprintf(buf + offset, size - offset, + " State: %s\n", + dpcm_state_string(fe->dpcm[stream].state)); + + if ((be->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += snprintf(buf + offset, size - offset, + " Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + } + +out: + return offset; +} + +static ssize_t soc_dpcm_state_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_pcm_runtime *fe = file->private_data; + ssize_t out_count = PAGE_SIZE, offset = 0, ret = 0; + char *buf; + + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (fe->cpu_dai->driver->playback.channels_min) + offset += soc_dpcm_show_state(fe, SNDRV_PCM_STREAM_PLAYBACK, + buf + offset, out_count - offset); + + if (fe->cpu_dai->driver->capture.channels_min) + offset += soc_dpcm_show_state(fe, SNDRV_PCM_STREAM_CAPTURE, + buf + offset, out_count - offset); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset); + + kfree(buf); + + return ret; +} + +static const struct file_operations soc_dpcm_state_fops = { + .open = soc_dpcm_state_open_file, + .read = soc_dpcm_state_read_file, + .llseek = default_llseek, +}; + +int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) +{ + rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, + rtd->card->debugfs_card_root); + if (!rtd->debugfs_dpcm_root) { + dev_dbg(rtd->dev, + "ASoC: Failed to create dpcm debugfs directory %s\n", + rtd->dai_link->name); + return -EINVAL; + } + + rtd->debugfs_dpcm_state = debugfs_create_file("state", 0644, + rtd->debugfs_dpcm_root, + rtd, &soc_dpcm_state_fops); + + return 0; +} +#endif diff --git a/sound/usb/card.c b/sound/usb/card.c index 4a7be7b98331538228a49e13c81faf6b7f8243b7..1452312cd69a26e37625e06aa3856cca25c32ece 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -86,6 +87,7 @@ static int nrpacks = 8; /* max. number of packets per urb */ static bool async_unlink = 1; static int device_setup[SNDRV_CARDS]; /* device parameter for this card */ static bool ignore_ctl_error; +struct switch_dev *usbaudiosdev; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); @@ -419,6 +421,7 @@ static int snd_usb_audio_create(struct usb_device *dev, int idx, } snd_usb_audio_create_proc(chip); + switch_set_state(usbaudiosdev, 1); *rchip = chip; return 0; @@ -581,6 +584,7 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, mutex_unlock(&chip->shutdown_mutex); mutex_unlock(®ister_mutex); } + switch_set_state(usbaudiosdev, 0); } /* @@ -713,16 +717,27 @@ static struct usb_driver usb_audio_driver = { static int __init snd_usb_audio_init(void) { + int err; if (nrpacks < 1 || nrpacks > MAX_PACKS) { printk(KERN_WARNING "invalid nrpacks value.\n"); return -EINVAL; } + + usbaudiosdev = kzalloc(sizeof(usbaudiosdev), GFP_KERNEL); + usbaudiosdev->name = "usb_audio"; + + err = switch_dev_register(usbaudiosdev); + if (err) + pr_err("Usb-audio switch registration failed\n"); + else + pr_debug("usb hs_detected\n"); return usb_register(&usb_audio_driver); } static void __exit snd_usb_audio_cleanup(void) { usb_deregister(&usb_audio_driver); + kfree(usbaudiosdev); } module_init(snd_usb_audio_init); diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 92271d32bc30b2056c472f273b1af6bed9847b31..80f23c079027ad0e114e9d97f4ddb061d108ed91 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -419,6 +419,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-list.o BUILTIN_OBJS += $(OUTPUT)builtin-record.o BUILTIN_OBJS += $(OUTPUT)builtin-report.o BUILTIN_OBJS += $(OUTPUT)builtin-stat.o +BUILTIN_OBJS += $(OUTPUT)builtin-periodic.o BUILTIN_OBJS += $(OUTPUT)builtin-timechart.o BUILTIN_OBJS += $(OUTPUT)builtin-top.o BUILTIN_OBJS += $(OUTPUT)builtin-script.o diff --git a/tools/perf/builtin-periodic.c b/tools/perf/builtin-periodic.c new file mode 100644 index 0000000000000000000000000000000000000000..060e9099c3b7227e356f98bdeb13663a3449e09c --- /dev/null +++ b/tools/perf/builtin-periodic.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * A very simple perf program to periodically print the performance + * counter reqested on the command line to standard out at the rate + * specified. + * + * This is valuable for showing the output in a simple plot or + * exporting the counter data for post processing. No attempt + * to process the data is made. + * + * Scaling is not supported, use only as many counters as are + * provided by the hardware. + * + * Math functions are support to combine counter results by using + * the -m flag. + * + * The -r -w flags supports user signalling for input. This assumes + * that a pipe/fifo is needed so the -rw cmd line arg is a string + * that is the name of the named pipe to open for read/write. User + * sends data on the read pipe to the process to collect a sample. + * Commands are also supported on the pipe. + * + */ + +#include "perf.h" +#include "builtin.h" +#include "util/util.h" +#include "util/parse-options.h" +#include "util/parse-events.h" +#include "util/event.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/debug.h" +#include "util/header.h" +#include "util/cpumap.h" +#include "util/thread.h" +#include +#include + +#define PERF_PERIODIC_ERROR -1 + +/* number of pieces of data on each read. */ +#define DATA_SIZE 2 + +#define DEFAULT_FIFO_NAME "xxbadFiFo" +#define MAX_NAMELEN 50 + +struct perf_evlist *evsel_list; + +/* + * command line variables and settings + * Default to current process, no_inherit, process + */ +static pid_t target_pid = -1; /* all */ +static bool system_wide; +static int cpumask = -1; /* all */ +static int ncounts; +static int ms_sleep = 1000; /* 1 second */ +static char const *operations = "nnnnnnnnnnnnnnnn"; /* nop */ +static bool math_enabled; +static bool calc_delta; +static double old_accum, accum; +static int math_op_index; +static char const *wfifo_name = DEFAULT_FIFO_NAME; +static char const *rfifo_name = DEFAULT_FIFO_NAME; +static bool use_fifo; +static bool is_ratio; +static FILE *fd_in, *fd_out; + +static FILE *tReadFifo, *tWriteFifo; + +/* + * Raw results from perf, we track the current value and + * the old value. + */ +struct perf_raw_results_s { + u64 values; + u64 old_value; +}; + +/* + * Everything we need to support a perf counter across multiple + * CPUs. We need to support multiple file descriptors (perf_fd) + * because perf requires a fd per counter, so 1 per core enabled. + * + * Raw results values are calculated across all the cores as they + * are read. + */ +struct perf_setup_s { + int event_index; + struct perf_event_attr *attr; + int perf_fd[MAX_NR_CPUS]; + pid_t pid; + int cpu; + int flags; + int group; + struct perf_raw_results_s data; + struct perf_raw_results_s totals; + struct perf_raw_results_s output; +}; + +static void do_cleanup(void) +{ + if (fd_in) { + if (0 != fclose(fd_in)) + error("Error closing fd_in\n"); + } + if (fd_out) { + if (0 != fclose(fd_out)) + error("Error closing fd_out\n"); + } + if (use_fifo) { + if (0 != unlink(rfifo_name)) + error("Error unlinking rfifo\n"); + if (0 != unlink(wfifo_name)) + error("Error unlinking wfifo\n"); + } +} + +/* + * Unexpected signal for error indication, cleanup + */ +static int sig_dummy; +static void sig_do_cleanup(int sig) +{ + sig_dummy = sig; + do_cleanup(); + exit(0); +} + +#define PERIODIC_MAX_STRLEN 100 +/* + * Delay for either a timed period or the wait on the read_fifo + */ +static void delay(unsigned long milli) +{ + char tmp_stg[PERIODIC_MAX_STRLEN]; + int done; + int ret; + + if (use_fifo) { + do { + done = true; + ret = fscanf(tReadFifo, "%s", tmp_stg); + if (ret == 0) + return; + /* + * Look for a command request, and if we get a command + * Need to process and then wait again w/o sending data. + */ + if (strncmp(tmp_stg, "PID", strnlen(tmp_stg, + PERIODIC_MAX_STRLEN)) == 0) { + fprintf(fd_out, " %u\n", getpid()); + fflush(fd_out); + done = false; + } else if (strncmp(tmp_stg, "EXIT", + strnlen(tmp_stg, PERIODIC_MAX_STRLEN)) + == 0) { + do_cleanup(); + exit(0); + } + + } while (done != true); + } else + usleep(milli*1000); +} + +/* + * Create a perf counter event. + * Some interesting behaviour that is not documented anywhere else: + * the CPU will not work if out of range. + * The CPU will only work for a single CPU, so to collect the counts + * on the system in SMP based systems a counter needs to be created + * for each CPU. + */ +static int create_perf_counter(struct perf_setup_s *p) +{ + struct cpu_map *cpus; + int cpu; + + cpus = cpu_map__new(NULL); + if (p == NULL) + return PERF_PERIODIC_ERROR; + for (cpu = 0; cpu < cpus->nr; cpu++) { + if (((1 << cpu) & cpumask) == 0) + continue; + p->perf_fd[cpu] = sys_perf_event_open(p->attr, target_pid, cpu, + -1, 0); + if (p->perf_fd[cpu] < 0) + return PERF_PERIODIC_ERROR; + } + return 0; +} + +/* + * Perf init setup + */ +static int perf_setup_init(struct perf_setup_s *p) +{ + if (p == NULL) + return PERF_PERIODIC_ERROR; + + bzero(p, sizeof(struct perf_setup_s)); + p->group = -1; + p->flags = 0; + + p->output.values = 0; + p->output.old_value = 0; + p->data.values = 0; + p->data.old_value = 0; + p->totals.old_value = 0; + p->totals.values = 0; + + return 0; +} + +/* + * Read in ALL the performance counters configured for the CPU, + * one performance monitor per core that was configured during + * "all" mode + */ +static int perf_setup_read(struct perf_setup_s *p) +{ + u64 data[DATA_SIZE]; + int i, status; + + p->totals.values = 0; + p->data.values = 0; + for (i = 0; i < MAX_NR_CPUS; i++) { + if (p->perf_fd[i] == 0) + continue; + status = read(p->perf_fd[i], &data, sizeof(data)); + p->data.values += data[0]; + p->totals.values += data[0]; + } + + /* + * Normally we show totals, we want to support + * showing deltas from the previous value so external apps do not have + * to do this... + */ + if (calc_delta) { + p->output.values = p->data.values - p->data.old_value; + p->data.old_value = p->data.values; + } else + p->output.values = p->totals.values; + return 0; +} + +static int perf_setup_show(struct perf_setup_s *p) +{ + if (p == NULL) + return PERF_PERIODIC_ERROR; + fprintf(fd_out, " %llu", p->output.values); + return 0; +} + + +static const char * const periodic_usage[] = { + "perf periodic []", + NULL +}; + +static const struct option options[] = { + OPT_CALLBACK('e', "event", &evsel_list, "event", + "event selector. use 'perf list' to list available events", + parse_events), + OPT_STRING('m', "math-operations", &operations, "nnnnnn", + "math operation to perform on values collected asmd in order"), + OPT_STRING('r', "readpipe", &rfifo_name, "xxbadFiFo", + "wait for a user input fifo - will be created"), + OPT_STRING('w', "writepipe", &wfifo_name, "xxbadFifo", + "write data out on this pipe - pipe is created"), + OPT_INTEGER('i', "increment", &ncounts, + "number of times periods to count/iterate (default 0-forever)"), + OPT_INTEGER('p', "pid", &target_pid, + "stat events on existing process id"), + OPT_INTEGER('c', "cpumask", &cpumask, + "cpumask to enable counters, default all (-1)"), + OPT_INTEGER('s', "sleep", &ms_sleep, + "how long to sleep in ms between each sample (default 1000)"), + OPT_BOOLEAN('a', "all-cpus", &system_wide, + "system-wide collection from all CPUs overrides cpumask"), + OPT_BOOLEAN('d', "delta", &calc_delta, + "calculate and display the delta values math funcs will use delta"), + OPT_INCR('v', "verbose", &verbose, + "be more verbose (show counter open errors, etc)"), + OPT_END() +}; + +/* + * After every period we reset any math that was performed. + */ +static void reset_math(void) +{ + math_op_index = 0; + old_accum = accum; + accum = 0; +} + +static void do_math_op(struct perf_setup_s *p) +{ + if (!math_enabled) + return; + switch (operations[math_op_index++]) { + case 'm': + accum *= (double)p->output.values; break; + case 'a': + accum += (double)p->output.values; break; + case 's': + accum -= (double)p->output.values; break; + case 'd': + accum /= (double)p->output.values; break; + case 'z': + accum = 0; break; + case 't': + accum = (double)p->output.values; break; /*transfer*/ + case 'T': + accum += old_accum; break; /*total*/ + case 'i': /* ignore */ + default: + break; + } +} + +int cmd_periodic(int argc, const char **argv, const char *prefix __used) +{ + int status = 0; + int c, i; + struct perf_setup_s *p[MAX_COUNTERS]; + struct perf_evsel *counter; + FILE *fp; + int nr_counters = 0; + + evsel_list = perf_evlist__new(NULL, NULL); + if (evsel_list == NULL) + return -ENOMEM; + + argc = parse_options(argc, argv, options, periodic_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (system_wide) + cpumask = -1; + + /* + * The r & w option redirects stdout to a newly created pipe and + * waits for input on the read pipe before continuing + */ + fd_in = stdin; + fd_out = stdout; + if (strncmp(rfifo_name, DEFAULT_FIFO_NAME, + strnlen(rfifo_name, MAX_NAMELEN))) { + fp = fopen(rfifo_name, "r"); + if (fp != NULL) { + fclose(fp); + remove(rfifo_name); + } + if (mkfifo(rfifo_name, 0777) == -1) { + error("Could not open read fifo\n"); + do_cleanup(); + return PERF_PERIODIC_ERROR; + } + tReadFifo = fopen(rfifo_name, "r+"); + if (tReadFifo == 0) { + do_cleanup(); + error("Could not open read fifo file\n"); + return PERF_PERIODIC_ERROR; + } + use_fifo = true; + } + if (strncmp(wfifo_name, DEFAULT_FIFO_NAME, + strnlen(wfifo_name, MAX_NAMELEN))) { + fp = fopen(wfifo_name, "r"); + if (fp != NULL) { + fclose(fp); + remove(wfifo_name); + } + if (mkfifo(wfifo_name, 0777) == -1) { + do_cleanup(); + error("Could not open write fifo\n"); + return PERF_PERIODIC_ERROR; + } + fd_out = fopen(wfifo_name, "w+"); + if (fd_out == 0) { + do_cleanup(); + error("Could not open write fifo file\n"); + return PERF_PERIODIC_ERROR; + } + tWriteFifo = fd_out; + } + + math_enabled = (operations[0] != 'n'); + + /* + * If we don't ignore SIG_PIPE then when the other side + * of a pipe closes we shutdown too... + */ + signal(SIGPIPE, SIG_IGN); + signal(SIGINT, sig_do_cleanup); + signal(SIGQUIT, sig_do_cleanup); + signal(SIGKILL, sig_do_cleanup); + signal(SIGTERM, sig_do_cleanup); + + i = 0; + list_for_each_entry(counter, &evsel_list->entries, node) { + p[i] = malloc(sizeof(struct perf_setup_s)); + if (p[i] == NULL) { + error("Error allocating perf_setup_s\n"); + do_cleanup(); + return PERF_PERIODIC_ERROR; + } + bzero(p[i], sizeof(struct perf_setup_s)); + perf_setup_init(p[i]); + p[i]->attr = &(counter->attr); + p[i]->event_index = counter->idx; + if (create_perf_counter(p[i]) < 0) { + do_cleanup(); + die("Not all events could be opened.\n"); + return PERF_PERIODIC_ERROR; + } + i++; + nr_counters++; + } + i = 0; + while (1) { + + /* + * Wait first otherwise single sample will print w/o signal + * when using the -u (user signal) flag + */ + delay(ms_sleep); + + /* + * Do the collection, read and then perform any math operations + */ + for (c = 0; c < nr_counters; c++) { + status = perf_setup_read(p[c]); + do_math_op(p[c]); + } + + /* + * After all collection and math, we perform one last math + * to allow totaling, if enabled etc, then either printout + * a single float value when the math is enabled or ... + */ + if (math_enabled) { + do_math_op(p[c]); + if (is_ratio) + fprintf(fd_out, "%#f\n", accum*100); + else + fprintf(fd_out, "%#f\n", accum); + } else { + /* + * ... print out one integer value for each counter + */ + for (c = 0; c < nr_counters; c++) + status = perf_setup_show(p[c]); + fprintf(fd_out, "\n"); + } + + /* + * Did the user give us an iteration count? + */ + if ((ncounts != 0) && (++i >= ncounts)) + break; + reset_math(); + fflush(fd_out); /* make sure data is flushed out the pipe*/ + } + + do_cleanup(); + + return status; +} diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index b382bd551aacd3d427522400fe11be1cbeb9f435..da3a6816d1ba6933479279c6ee0acf8080336134 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -25,6 +25,7 @@ extern int cmd_list(int argc, const char **argv, const char *prefix); extern int cmd_record(int argc, const char **argv, const char *prefix); extern int cmd_report(int argc, const char **argv, const char *prefix); extern int cmd_stat(int argc, const char **argv, const char *prefix); +extern int cmd_periodic(int argc, const char **argv, const char *prefix); extern int cmd_timechart(int argc, const char **argv, const char *prefix); extern int cmd_top(int argc, const char **argv, const char *prefix); extern int cmd_script(int argc, const char **argv, const char *prefix); diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 2b2e225a4d4c6a655690b30b622669a597521570..3dffec62a1f9698d033e42f4c6b167a42048d2fc 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -307,6 +307,7 @@ static void handle_internal_command(int argc, const char **argv) { "report", cmd_report, 0 }, { "bench", cmd_bench, 0 }, { "stat", cmd_stat, 0 }, + { "periodic", cmd_periodic, 0 }, { "timechart", cmd_timechart, 0 }, { "top", cmd_top, 0 }, { "annotate", cmd_annotate, 0 }, diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c index dfd1bd8371a4cba9fc31d164bada0400f0ca01a9..26503d647090a5f5bacbdb01d88a64920aeeb050 100644 --- a/tools/perf/util/trace-event-parse.c +++ b/tools/perf/util/trace-event-parse.c @@ -1590,6 +1590,8 @@ process_symbols(struct event *event, struct print_arg *arg, char **tok) field = malloc_or_die(sizeof(*field)); type = process_arg(event, field, &token); + while (type == EVENT_OP) + type = process_op(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; diff --git a/virt/kvm/iommu.c b/virt/kvm/iommu.c index e9fff9830bf0bf6f2229603516ebdd633996fd20..5aefbc49201a4b9eb1f114d4a02aa5a791d418f7 100644 --- a/virt/kvm/iommu.c +++ b/virt/kvm/iommu.c @@ -242,7 +242,7 @@ int kvm_iommu_map_guest(struct kvm *kvm) mutex_lock(&kvm->slots_lock); - kvm->arch.iommu_domain = iommu_domain_alloc(&pci_bus_type); + kvm->arch.iommu_domain = iommu_domain_alloc(&pci_bus_type, 0); if (!kvm->arch.iommu_domain) { r = -ENOMEM; goto out_unlock;